Mixer TOML Config Reference
shiloh-mixer uses named CLI flags for its config path:
shiloh-mixer --state-dir ~/mixer-state --config ~/mixer/shiloh-mixer.toml
Config format lifecycle: On startup, the mixer resolves config in three tiers:
runtime-config.jsonin the state directory (primary format)shiloh-mixer.toml(legacy – auto-migrated to JSON on first run)- Empty defaults (first-boot mode)
TOML is read once for migration and is never re-read. All web UI changes
persist toruntime-config.json. The TOML example below is representative
of what the legacy file should contain.
The production config lives at server/shiloh-mixer.toml in this repository.
Complete annotated example
## shiloh-mixer.toml — example production config
jack_name = "shiloh-mixer" # JACK client name
control_port = 19997 # mixer_web talks here
control_bind = "127.0.0.1" # change to WG/LAN addr for remote web-relay
broadcast_port = 5005 # shiloh-relay + shiloh-broadcaster connect here
broadcast_frames = 128 # frames per UDP audio packet (deployed; code default is 160)
ramp_ms = 150 # scene transition duration (ms)
meters_file = "/home/shiloh/mixer-state/meters.json"
gains_file = "/home/shiloh/mixer-state/gains.json"
scenes_file = "/home/shiloh/mixer-state/scenes.json"
relay_assignments_file = "/home/shiloh/mixer-state/relay-assignments.json"
# ── channels ─────────────────────────────────────────────────────────────────
[[channels]]
id = "bcast1"
label = "CH1 — Studio"
kind = "stereo" # registers ports: bcast1_in_1, bcast1_in_2
main_gain = 1.0
ingest_slot = 0 # reads from ingest bank slots 0–1
[[channels]]
id = "mopidy"
label = "CH5 — Mopidy"
kind = "stereo"
main_gain = 1.0
# no ingest_slot → reads from JACK input ports
[[channels]]
id = "sc1"
label = "SC1 — Scarlett 1"
kind = "mono" # registers one port: sc1_in
main_gain = 1.0
main_muted = true # muted at cold start
# ── output buses ─────────────────────────────────────────────────────────────
[buses]
main = ["out_1", "out_2"]
main_gain = 1.0
monitor = ["monitor_out_1", "monitor_out_2"]
monitor_gain = 1.0
cue = ["cue_out_1", "cue_out_2"]
cue_gain = 1.0
main_relay = ["main_relay_line_1", "main_relay_line_2"]
main_relay_gain = 1.0
main_relay_on = true
monitor_relay = ["monitor_relay_line_1", "monitor_relay_line_2"]
monitor_relay_gain = 1.0
monitor_relay_on = true
cue_relay = ["cue_relay_line_1", "cue_relay_line_2"]
cue_relay_gain = 1.0
cue_relay_on = true
# ── JACK autoconnect ──────────────────────────────────────────────────────────
[[autoconnect]]
src = "Desktop Audio:monitor_FL"
dst = "shiloh-mixer:bcast1_in_1"
[[autoconnect]]
src = "Desktop Audio:monitor_FR"
dst = "shiloh-mixer:bcast1_in_2"
# ── local relay sinks ─────────────────────────────────────────────────────────
[[local_relay_sink]]
name = "usb_codec_out"
label = "USB Codec Out"
ports = ["usb_out_1", "usb_out_2"]
default_bus = "main"
# ── ingest allow-list ─────────────────────────────────────────────────────────
[ingest]
slot_count = 6
[[ingest.sender]]
name = "studio"
channels = 2
start_slot = 0
[[ingest.sender]]
name = "laptop"
channels = 2
start_slot = 2
[[ingest.sender]]
name = "guest"
channels = 2
start_slot = 4
# ── MIDI control ──────────────────────────────────────────────────────────────
[midi]
listen_port = 19999
ardour_osc = "127.0.0.1:3819"
idle_timeout_ms = 3000
fader_step = 0.05
midi_state_file = "/home/shiloh/mixer-state/midi-state.json"
[midi.mode_keys]
"A3" = "idle"
"C3" = "channel_select"
"G3" = "transport"
[midi.channel_keys]
# leave empty for iteration-based channel selection
[midi.channel_action_keys]
"C4" = "channel_prev"
"D4" = "channel_next"
"C5" = "fader_down"
"D5" = "fader_up"
"E5" = "mute_toggle"
[midi.transport_keys]
"C6" = "play"
"D6" = "stop"
"E6" = "record"
"F6" = "goto_start"
"G6" = "goto_end"
"A6" = "prev_marker"
"B6" = "next_marker"
"C7" = "add_marker"
[midi.navigation_keys]
# optional; transport_keys now cover goto/marker actions
Top-level fields
| Key | Type | Default | Description |
|---|---|---|---|
jack_name |
string | "shiloh-mixer" |
JACK client name registered with the server |
control_port |
integer | 19997 |
UDP port for the control plane (mixer_web sends commands here) |
control_bind |
string | "127.0.0.1" |
Bind address for the control-plane socket. Set to a WireGuard or LAN address to allow remote web-relay instances to send set_relay_assignment |
broadcast_port |
integer | 5005 |
UDP port for relay and ingest (both shiloh-relay and shiloh-broadcaster connect here) |
broadcast_frames |
integer | 160 |
Frames per UDP audio packet (affects latency vs. overhead tradeoff) |
meters_file |
string | "/home/shiloh/mixer-state/meters.json" |
Path where peak meter snapshots are written for mixer_web |
gains_file |
string | "/home/shiloh/mixer-state/gains.json" |
Path where gains and current scene are persisted across restarts |
scenes_file |
string | "/home/shiloh/mixer-state/scenes.json" |
Path where named scenes are persisted |
relay_assignments_file |
string | "/home/shiloh/mixer-state/relay-assignments.json" |
Path where per-client relay feed assignments are persisted |
session_delay_file |
string | "/home/shiloh/mixer-state/session-delays.json" |
Path where per-session delay settings are persisted (name -> delay_ms) |
ramp_ms |
integer | 150 |
Fader ramp duration for scene transitions in milliseconds |
[[channels]]
Defines one input channel. Repeat the block for each channel. Channel id values must be unique.
| Key | Type | Default | Required | Description |
|---|---|---|---|---|
id |
string | — | Yes | Short identifier. Used as the gain/mute key and as part of JACK port names. E.g. "net" registers net_in_1 and net_in_2 (stereo) or net_in (mono) |
label |
string | "" |
No | Human-readable label shown in the mixer web UI |
kind |
"stereo" or "mono" |
"stereo" |
No | Stereo registers two input ports (id_in_1, id_in_2); mono registers one (id_in) and sums equally into both L and R of each bus |
main_gain |
float | 1.0 |
No | Initial gain on the main mix bus (linear, 0.0–2.0 range typical) |
main_muted |
bool | false |
No | Whether the channel starts muted on the main and relay buses |
monitor_gain |
float | 0.0 |
No | Initial send level to the monitor bus |
cue_gain |
float | 0.0 |
No | Initial send level to the cue bus |
pan |
float | 0.0 |
No | Stereo pan / balance. Range [-1, 1]. -1 = full left, 0 = center, 1 = full right. For mono channels: constant-power pan. For stereo channels: balance (attenuates opposite side) |
ingest_slot |
integer or absent | absent | No | If set, this channel reads from the ingest ring-buffer bank starting at this zero-based slot index instead of JACK input ports. Stereo channels consume two contiguous slots; mono channels consume one |
JACK port name convention
kind |
Ports registered |
|---|---|
"stereo" |
<id>_in_1, <id>_in_2 |
"mono" |
<id>_in |
These names appear under the mixer’s JACK client (e.g. shiloh-mixer:bcast1_in_1).
[buses]
Configures the three program buses and their corresponding relay feeds. All fields are optional — the defaults match the production config.
Program buses
| Key | Type | Default | Description |
|---|---|---|---|
main |
[string, string] |
["out_1", "out_2"] |
JACK output port names for the main bus (L, R) |
main_gain |
float | 1.0 |
Master gain for the main bus (linear) |
monitor |
[string, string] |
["monitor_out_1", "monitor_out_2"] |
JACK output port names for the monitor bus |
monitor_gain |
float | 1.0 |
Master gain for the monitor bus |
cue |
[string, string] |
["cue_out_1", "cue_out_2"] |
JACK output port names for the cue bus |
cue_gain |
float | 1.0 |
Master gain for the cue bus |
Relay feeds
Each program bus has a matching relay feed — a gain-scaled, on/off-gated copy of the bus signal that is handed to the broadcast worker. UDP relay clients (headphone boxes, web-relay) receive from relay feeds, not directly from program buses.
| Key | Type | Default | Description |
|---|---|---|---|
main_relay |
[string, string] |
["main_relay_line_1", "main_relay_line_2"] |
JACK port names for the main relay tap |
main_relay_gain |
float | 1.0 |
Gain applied to the main relay feed |
main_relay_on |
bool | true |
Global on/off for the main relay feed |
monitor_relay |
[string, string] |
["monitor_relay_line_1", "monitor_relay_line_2"] |
JACK port names for the monitor relay tap |
monitor_relay_gain |
float | 1.0 |
Gain applied to the monitor relay feed |
monitor_relay_on |
bool | true |
Global on/off for the monitor relay feed |
cue_relay |
[string, string] |
["cue_relay_line_1", "cue_relay_line_2"] |
JACK port names for the cue relay tap |
cue_relay_gain |
float | 1.0 |
Gain applied to the cue relay feed |
cue_relay_on |
bool | true |
Global on/off for the cue relay feed |
Per-bus DSP plugin chains
Each program bus can have a DSP plugin chain applied post-mix. Three optional keys in [buses], each taking a plugins array:
| Key | Type | Description |
|---|---|---|
main_dsp |
BusDspConfig |
DSP plugins applied to the main bus (post-mix, pre-output) |
monitor_dsp |
BusDspConfig |
DSP plugins applied to the monitor bus |
cue_dsp |
BusDspConfig |
DSP plugins applied to the cue bus |
Each plugins entry is a tagged object with type and type-specific params:
| Plugin type | Params | Description |
|---|---|---|
echo |
delay_ms (default 200), feedback (default 0.3), mix (default 0.25) |
Stereo echo/delay with feedback |
reverb |
wet_mix (default 0.3), decay (default 0.5) |
Stereo reverb |
high_pass |
freq_hz (required), q (default 0.707) |
High-pass (low-cut) filter |
low_pass |
freq_hz (required), q (default 0.707) |
Low-pass (high-cut) filter |
mid_pass |
freq_hz (required), q (default 0.707) |
Band-pass filter |
delay |
delay_ms (required) |
Simple delay (no feedback) |
Example:
[buses]
main_dsp = [
{ type = "high_pass", freq_hz = 80.0 },
{ type = "echo", delay_ms = 250.0, feedback = 0.2, mix = 0.15 }
]
cue_dsp = [
{ type = "reverb", wet_mix = 0.4, decay = 0.6 }
]
[[autoconnect]]
Each entry wires one JACK port to another. The autoconnect watcher reconciles this list every ~5 seconds, reconnecting if either port disappears and comes back.
| Key | Type | Required | Description |
|---|---|---|---|
src |
string | Yes | Source JACK port in "client:port" format |
dst |
string | Yes | Destination JACK port in "client:port" format |
Use JACK port names as reported by jack_lsp. Mixer input ports follow the shiloh-mixer:<id>_in_1 naming convention.
# Hardware capture → mixer input
[[autoconnect]]
src = "Scarlett Solo 4th Gen Pro:capture_AUX0"
dst = "shiloh-mixer:sc1_in"
# Mixer output → hardware playback
[[autoconnect]]
src = "shiloh-mixer:monitor_out_1"
dst = "Scarlett Solo 4th Gen Pro:playback_AUX0"
# Mono source splat to both mixer inputs (centering trick)
[[autoconnect]]
src = "mopidy-out:out_jackaudiosink0_1"
dst = "shiloh-mixer:mopidy_in_1"
[[autoconnect]]
src = "mopidy-out:out_jackaudiosink0_1"
dst = "shiloh-mixer:mopidy_in_2"
[ingest]
Configures the client→server audio ingest system (senders running shiloh-broadcaster). Omit the entire [ingest] section to disable ingest.
[ingest] top-level
| Key | Type | Default | Required | Description |
|---|---|---|---|---|
slot_count |
integer | 0 |
Yes (if ingest is used) | Total mono slots in the ingest ring-buffer bank. 0 disables ingest. Must be large enough to cover all senders |
[[ingest.sender]]
One entry per allowed sender. Senders not in this list are rejected with REJECT_UNKNOWN_SENDER.
| Key | Type | Required | Description |
|---|---|---|---|
name |
string | Yes | Must match the --name the client passes to shiloh-broadcaster connect |
channels |
integer | Yes | Number of mono channels this sender will push. Must match the client’s --channels value |
start_slot |
integer | Yes | Zero-based first slot in the ingest bank this sender writes to. Sender occupies slots start_slot through start_slot + channels - 1 |
Sender slot ranges must not overlap, and start_slot + channels must not exceed slot_count. The mixer validates this at startup and exits with an error if the config is invalid.
Channels with ingest_slot set read from this bank. A stereo channel at ingest_slot = 0 reads slots 0 and 1; a mono channel at ingest_slot = 2 reads slot 2.
[ingest]
slot_count = 6
[[ingest.sender]]
name = "studio" # shiloh-broadcaster --name studio --channels 2
channels = 2
start_slot = 0 # occupies slots 0, 1
[[ingest.sender]]
name = "laptop" # shiloh-broadcaster --name laptop --channels 2
channels = 2
start_slot = 2 # occupies slots 2, 3
[[ingest.sender]]
name = "guest"
channels = 2
start_slot = 4 # occupies slots 4, 5
[[local_relay_sink]]
Each entry creates a stereo JACK output pair on the mixer client that mirrors one of the three relay feeds (main/monitor/cue). Feed assignment is switchable at runtime from the mixer web UI without restarting anything.
Local relay sinks tap the relay feeds (with per-feed gain and on/off applied), not the raw program buses.
| Key | Type | Default | Required | Description |
|---|---|---|---|---|
name |
string | — | Yes | Unique identifier. Used as the persistence key in relay_assignments_file. Must not collide with any UDP relay client names |
label |
string | name |
No | Human-readable label shown in the mixer web UI |
ports |
[string, string] |
— | Yes | JACK output port names (L, R) to register under the mixer’s JACK client |
default_bus |
"main" / "monitor" / "cue" |
"main" |
No | Which feed to use at cold start when no persisted assignment exists |
[[local_relay_sink]]
name = "usb_codec_out"
label = "USB Codec Out"
ports = ["usb_out_1", "usb_out_2"]
default_bus = "main"
The ports are then wired via [[autoconnect]] to physical outputs:
[[autoconnect]]
src = "shiloh-mixer:usb_out_1"
dst = "PCM2902 Audio Codec Analog Stereo:playback_FL"
[[autoconnect]]
src = "shiloh-mixer:usb_out_2"
dst = "PCM2902 Audio Codec Analog Stereo:playback_FR"
[multicast]
Optional multicast audio relay. When enabled, the mixer publishes one AUDIO packet
per bus to a multicast group, and relays join the group to receive audio. This avoids
the N× unicast fan-out — one send reaches all LAN relays. Per-relay delay moves to
the relay side, controlled from the mixer via the JSON control plane (UDP 19997).
[multicast] top-level fields
| Field | Type | Default | Description |
|---|---|---|---|
enabled |
bool | false |
Enable multicast relay (coexists with unicast) |
group_addr |
string | "239.0.0.1" |
IPv4 multicast group address |
group_port |
u16 | 5006 |
UDP port for multicast audio |
ttl |
u32 | 1 |
Multicast packet TTL (1 = local subnet only) |
Example:
[multicast]
enabled = true
group_addr = "239.0.0.1"
group_port = 5006
ttl = 1
Use shiloh-relay connect-multicast on the relay side instead of connect.
The relay registers via the control plane (port 19997) and receives per-relay
delay and bus assignment commands from the mixer.
[midi]
Configures the MIDI modal control system. Omit the entire [midi] block to disable MIDI — no UDP socket will be opened on listen_port.
MIDI arrives from shiloh-midi-sender (running on the machine with the keyboard) as raw MIDI bytes in UDP datagrams. The mixer interprets note-ons through a modal state machine.
[midi] top-level fields
| Key | Type | Default | Required | Description |
|---|---|---|---|---|
listen_port |
integer | 19999 |
No | UDP port to listen on for incoming MIDI datagrams |
ardour_osc |
string | "127.0.0.1:3819" |
No | Ardour OSC target address for transport and navigation commands |
idle_timeout_ms |
integer | 3000 |
No | Retained for backward compatibility; no longer enforced (modes no longer auto-time-out) |
fader_step |
float | 0.05 |
No | Linear gain step per fader_down / fader_up press (on a 0.0–2.0 scale) |
midi_state_file |
string | "/home/shiloh/mixer-state/midi-state.json" |
No | Path where the MIDI modal state (mode, current channel) is written for mixer_web |
osc_feedback_port |
integer | absent | No | UDP port to listen on for Ardour OSC fader feedback. Must be explicitly set; no default. Configure Ardour to send feedback to this port |
Modal state machine
The mixer interprets notes through a vim-style modal machine:
Idle ──► ChannelSelect ──► ChannelControl{id}
│ ▲ │
│ C3 key action keys
│ │
└──► Transport (sticky) │
G3 key │
│
A3 from any mode ──────────────┴──► Idle (universal reset)
Modes stay active until a mode key is pressed. There is no auto-timeout in normal operation.
[midi.mode_keys]
Maps note names to mode transitions. Valid mode values:
| Value | Effect |
|---|---|
"idle" |
Return to Idle from any mode |
"channel_select" |
Enter ChannelSelect mode |
"transport" |
Enter Transport mode (sticky) |
"navigation" |
Enter Navigation mode |
Note names use standard MIDI convention: C4 = 60 (middle C). C3 = 48, D3 = 50, etc. Sharps use # suffix: "C#3".
[midi.mode_keys]
"A3" = "idle" # MIDI note 57 — universal reset
"C3" = "channel_select" # MIDI note 48
"G3" = "transport" # MIDI note 55
[midi.channel_keys]
Maps note names to channel ids (in ChannelSelect mode). Values must match an id in [[channels]].
[midi.channel_keys]
"C4" = "bcast1" # selects the Studio channel
"D4" = "bcast2"
"E4" = "bcast3"
Leave empty to use iteration-based channel selection (navigate with channel_prev / channel_next actions in channel_action_keys).
[midi.channel_action_keys]
Maps note names to actions available in ChannelControl mode. Valid actions:
| Action | Effect |
|---|---|
"fader_down" |
Decrease the channel’s main gain by fader_step |
"fader_up" |
Increase the channel’s main gain by fader_step |
"mute_toggle" |
Toggle mute on the main bus |
"channel_prev" |
Move to the previous channel in config order (wraps) |
"channel_next" |
Move to the next channel in config order (wraps) |
[midi.channel_action_keys]
"C4" = "channel_prev"
"D4" = "channel_next"
"C5" = "fader_down"
"D5" = "fader_up"
"E5" = "mute_toggle"
[midi.transport_keys]
Maps note names to transport actions (in Transport mode). Commands are sent as OSC messages to ardour_osc. Valid actions:
| Action | OSC message |
|---|---|
"play" |
/transport_play |
"stop" |
/transport_stop |
"play_stop" |
Toggle play/stop |
"record" |
/rec_enable_toggle |
"goto_start" |
/goto_start |
"goto_end" |
/goto_end |
"prev_marker" |
/prev_marker |
"next_marker" |
/next_marker |
"add_marker" |
/add_marker |
[midi.transport_keys]
"C6" = "play"
"D6" = "stop"
"E6" = "record"
"F6" = "goto_start"
"G6" = "goto_end"
"A6" = "prev_marker"
"B6" = "next_marker"
"C7" = "add_marker"
[midi.navigation_keys]
Maps note names to navigation actions. Valid actions: "goto_start", "goto_end", "add_marker", "prev_marker", "next_marker". In the production config this table is typically left empty — the transport_keys table now covers these actions in Transport (sticky) mode.
Validation rules
The mixer validates the config at startup and exits with an error if any of the following hold:
- Duplicate
idvalues in[[channels]] - A channel’s
ingest_slot+ slot width exceedsingest.slot_count - Duplicate
namevalues in[[ingest.sender]] - A sender’s
start_slot + channelsexceedsingest.slot_count - Duplicate
namevalues in[[local_relay_sink]] - A
[[local_relay_sink]]has adefault_busother than"main","monitor", or"cue"
Default paths and ports summary
| Setting | Default |
|---|---|
broadcast_port |
5005 |
control_port |
19997 |
control_bind |
127.0.0.1 |
broadcast_frames |
128 frames (deployed; code default is 160) |
ramp_ms |
150 ms |
meters_file |
/home/shiloh/mixer-state/meters.json |
gains_file |
/home/shiloh/mixer-state/gains.json |
scenes_file |
/home/shiloh/mixer-state/scenes.json |
relay_assignments_file |
/home/shiloh/mixer-state/relay-assignments.json |
midi.listen_port |
19999 |
midi.ardour_osc |
127.0.0.1:3819 |
midi.fader_step |
0.05 |
midi.midi_state_file |
/home/shiloh/mixer-state/midi-state.json |