Installing the Mixer Server

shiloh-mixer is the hub. It registers as a JACK client, mixes all audio channels, and fans the result out over UDP to every connected relay and broadcaster.

Prerequisites

  • Linux (tested on Debian/Ubuntu)
  • JACK audio — either classic jackd2 or the pipewire-jack drop-in:
    # Classic JACK
    sudo apt install jackd2 jack-tools
    
    # Or: PipeWire's JACK compatibility layer (recommended on modern distros)
    sudo apt install pipewire-jack
    
  • Rust toolchain via rustup:
    curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
    source "$HOME/.cargo/env"
    
  • Real-time scheduling permissions — add yourself to the audio group (or configure /etc/security/limits.d/):
    sudo usermod -aG audio "$USER"
    # Log out and back in for the group to take effect
    

1. Clone the repository

git clone https://git.shilohbv.com/shiloh/broadcaster ~/shiloh-broadcaster
cd ~/shiloh-broadcaster

2. Build

cargo build --release -p shiloh-mixer

The binary lands at target/release/shiloh-mixer.

NOTE: The [profile.release] in the workspace Cargo.toml enables LTO and strips the binary. The first build will be slow (several minutes). Subsequent incremental builds are fast.

3. Create the working directory

mkdir -p ~/mixer

The systemd unit sets WorkingDirectory=%h/mixer, so the mixer must be able to find its config and state files there. Copy or symlink the binary:

cp target/release/shiloh-mixer ~/mixer/shiloh-mixer
# or use the release binary name the unit expects:
cp target/release/shiloh-mixer ~/mixer/shiloh-mixer-linux-x86_64

4. Write a minimal config file

Create ~/mixer/shiloh-mixer.toml. Below is a minimal working example with two stereo ingest channels (one studio laptop, one guest):

## shiloh-mixer.toml — minimal two-channel example

jack_name        = "shiloh-mixer"
control_port     = 19997          # mixer_web talks here
broadcast_port   = 5005           # relay + broadcaster clients connect here
broadcast_frames = 128            # 128 frames @ 48 kHz = 2.67 ms packets
ramp_ms          = 150            # fader ramp time for scene transitions

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 ─────────────────────────────────────────────────────────
# ingest_slot links this channel to the broadcaster ingest bank.
# Stereo channels consume two consecutive slots.

[[channels]]
id          = "studio"
label       = "Studio"
kind        = "stereo"
main_gain   = 1.0
ingest_slot = 0

[[channels]]
id          = "guest"
label       = "Guest"
kind        = "stereo"
main_gain   = 1.0
ingest_slot = 2

# ── 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

# ── ingest (broadcaster senders) ─────────────────────────────────────

[ingest]
slot_count = 4

[[ingest.sender]]
name       = "studio"
channels   = 2
start_slot = 0

[[ingest.sender]]
name       = "guest"
channels   = 2
start_slot = 2

Create the state directory referenced above:

mkdir -p ~/mixer-state

NOTE: The name in [[ingest.sender]] must exactly match the --name flag passed to shiloh-broadcaster on the sender machine. If a sender connects with an unrecognised name the mixer will reject it.

5. First run

Make sure JACK is running first:

# With pipewire-jack: JACK is already running as part of PipeWire
pw-jack jackd -d dummy &   # only needed if PipeWire is not started

# Then start the mixer:
RUST_LOG=info ~/mixer/shiloh-mixer --config ~/mixer/shiloh-mixer.toml

Look for these lines in the output to confirm a healthy start:

INFO shiloh_mixer: JACK client "shiloh-mixer" registered
INFO shiloh_mixer: broadcast server listening on 0.0.0.0:5005
INFO shiloh_mixer: control plane listening on 127.0.0.1:19997

Verify the CLI help is accessible:

~/mixer/shiloh-mixer --help

Stop with Ctrl-C. The mixer sends a graceful BYE to all connected clients.

6. systemd user unit

The repo ships a ready-made user service at server/systemd/shiloh-mixer.service. Install it:

mkdir -p ~/.config/systemd/user
cp ~/shiloh-broadcaster/server/systemd/shiloh-mixer.service \
   ~/.config/systemd/user/shiloh-mixer.service

The unit file (shown below for reference) expects the binary at ~/mixer/shiloh-mixer-linux-x86_64 and the config at ~/mixer/shiloh-mixer.toml:

[Unit]
Description=shiloh-mixer — Rust JACK mix bus + UDP broadcaster relay
After=pipewire.service wireplumber.service
Wants=pipewire.service wireplumber.service

[Service]
Type=simple
WorkingDirectory=%h/mixer
# pw-jack wraps the mixer so it uses PipeWire's JACK shim instead of native libjack.
# This makes PipeWire clients (Firefox, Desktop Audio) visible as JACK ports.
ExecStart=/usr/bin/pw-jack %h/mixer/shiloh-mixer-linux-x86_64 --config %h/mixer/shiloh-mixer.toml
Restart=on-failure
RestartSec=3

LimitRTPRIO=95
LimitMEMLOCK=infinity

Environment=XDG_RUNTIME_DIR=/run/user/1000
Environment=RUST_LOG=info
Environment=RUST_BACKTRACE=1

[Install]
WantedBy=default.target

NOTE: Replace 1000 in XDG_RUNTIME_DIR with your actual UID (id -u). PipeWire and JACK use this path to find the audio session socket.

NOTE: The pw-jack wrapper is required on modern PipeWire systems. Without it, the mixer uses native libjack and cannot see PipeWire-managed audio sources (Firefox, Desktop Audio, etc.).

Enable and start:

systemctl --user daemon-reload
systemctl --user enable shiloh-mixer.service
systemctl --user start  shiloh-mixer.service
systemctl --user status shiloh-mixer.service

To follow live logs:

journalctl --user -u shiloh-mixer.service -f

Troubleshooting

Symptom Likely cause
JACK server not running PipeWire/jackd not started yet; check pw-jack jack_lsp
broadcast server bind failed Port 5005 already in use; check `ss -ulnp
rejected sender "studio": not in allow-list Name mismatch between [[ingest.sender]] and --name on the broadcaster
High CPU after many reconnects Increase broadcast_frames to reduce packet rate