The openmixer server. It wires a chosen device adapter into core's
MixerEngine and exposes it to the browser surface over a single REST entity
API (rest-router.ts, mounted under /api): GET/PATCH for state, GET ?watch=1 for a live Server-Sent Events stream of the same resource. The
surface and engine never know which console is behind the adapter — only the
adapter does.
web-ui ──GET/PATCH /api/*──────▶ @freemixer/server ──▶ MixerEngine (@freemixer/core)
──GET /api/*?watch=1 (SSE)──▶ │ │
│ MixerAdapter (midas | x32 | roland | software | mock)
└── GET /health ────────┘
MixerEngine.GET /health → { ok, model, connected } (connected = adapter.checkConnection()).rest-router.ts, prefix /api) is the primary control
surface: every entity supports GET (seed state, ?watch=1 for SSE) and PATCH
(apply a delta, answered by the re-read state). This is what the web UI's
useResource composable talks to.Everything is configuration — nothing about a device is hard-coded. Build a
ServerConfig from a plain object (parseConfig) or the environment
(configFromEnv):
| env var | config path | default |
|---|---|---|
OPENMIXER_ADAPTER |
adapter (midas|x32|roland|software|mock) |
mock |
OPENMIXER_DEVICE_HOST |
device.host |
127.0.0.1 |
OPENMIXER_DEVICE_PORT |
device.port |
adapter default |
OPENMIXER_WEB_HOST |
web.host |
0.0.0.0 |
OPENMIXER_WEB_PORT |
web.port |
8080 |
OPENMIXER_RIG |
rig.preset |
— |
OPENMIXER_CONSOLE |
rig.console (IN:OUT pin) |
— |
OPENMIXER_GIG |
rig.gig |
— |
OPENMIXER_CATALOG |
rig.catalog |
bundled @freemixer/catalog |
OPENMIXER_DEMO_SOURCES |
rig.demoSources (1 = true) |
false |
OPENMIXER_CONFIG |
config-file path (--config wins) |
— |
See src/config.ts for the full, authoritative list.
OPENMIXER_ADAPTER=midas OPENMIXER_DEVICE_HOST=192.0.2.5 OPENMIXER_DEVICE_PORT=10002 openmixer-server
Or embed it:
import { MixerServer, parseConfig } from '@freemixer/server';
const server = new MixerServer({ config: parseConfig({ adapter: 'mock' }) });
await server.start();
The real device adapters (@freemixer/adapter-midas, -x32, -roland) live in
this monorepo as separate packages, but the server never statically imports them:
it codes to the @freemixer/core MixerAdapter interface and resolves the
concrete adapter with a guarded dynamic import() only when that adapter is
selected (adapter-loader.ts) — so this package builds and tests with no
adapter packages installed as a runtime dependency. Selecting a name whose
package isn't installed fails at boot with an actionable error. The built-in
MockAdapter (hardware-free) ships here as the default and as the test surface;
software (the PipeWire-native @freemixer/audio-engine mixer) is also built in.
pnpm -F @freemixer/server test
Tests boot a real server with the MockAdapter and drive it over real HTTP:
a PATCH /api/* reaches the adapter's setFader/setMute, and a device-side
push (via the adapter's MixerReceiver) shows up on a GET /api/*?watch=1
SSE stream. No hardware.
GPL-3.0-or-later. © Pau Aliagas linuxnow@gmail.com.