MIDI Control

shiloh-mixer includes a UDP MIDI listener that drives a vim-style modal state machine.
A USB MIDI keyboard connected to any machine on the network can control channel faders,
mutes, and Ardour transport without touching the browser.


Architecture

USB piano (any machine)
   │  USB MIDI class-compliant device
   ▼
shiloh-midi-sender            (runs on the PC with the keyboard)
   │  raw MIDI note bytes in UDP datagrams → stg-srv001:19999
   ▼
shiloh-mixer MIDI listener
   │
   ├──► modal state machine   (fader/mute changes on mixer channels)
   └──► Ardour :3819          (OSC transport / marker commands)

Ardour WebSocket ws://localhost:3818/
   └──► mixer_web             (DAW strip fader/mute feedback)

All transport is fire-and-forget UDP. No acknowledgement or retransmit.

Port Direction Purpose
19999 keyboard PC → mixer Raw MIDI datagrams from shiloh-midi-sender
3819 mixer → Ardour OSC transport, navigation, and fader commands
3818 Ardour → mixer_web Ardour WebSocket surface (strip gain/mute feedback)

Sending MIDI from Your PC

On the machine with the keyboard:

shiloh-midi-sender \
    --device "USB MIDI" \
    --server stg-srv001.bq.shilohbv.com:19999

--device takes a case-insensitive substring of the MIDI input port name. For most
class-compliant USB keyboards the default (USB MIDI) works. List available devices:

shiloh-midi-sender --list-devices

The sender logs every note-on it forwards:

[midi] note_on: note=60 (C4) vel=100

Modal State Machine

The mixer interprets note-ons through a modal machine. You stay in a mode until you
explicitly switch. A3 (MIDI 57) is the universal reset to Idle from any mode.

Idle ─ C3 ─► ChannelSelect ─ <channel key (C4 octave)> ─► ChannelControl{id}
                                                                 │
                                                                 │  action keys (C5 octave)
                                                                 │  fader_down / fader_up / mute_toggle
Idle ─ G3 ─► Transport
               │  transport keys (C6 octave)
               │  play / stop / record / goto_start / markers

A3 from anywhere ──► Idle  (universal reset)

Modes do not auto-timeout by default (configurable via idle_timeout_ms in [midi]).
Use A3 whenever you feel stuck or want to start a fresh selection.


Default Key Map

The map starts at C3 (MIDI 48) to avoid the unreliable low C2 on our instrument.

Mode entry — C3 octave

Note MIDI # Effect
A3 57 Reset to Idle (works from any mode)
C3 48 Enter ChannelSelect
G3 55 Enter Transport

Channel selection — C4 octave (in ChannelSelect)

White keys correspond to channels left-to-right in the UI:

Note MIDI # Channel
C4 60 Studio (bcast1)
D4 62 Laptop (bcast2)
E4 64 Guest (bcast3)
F4 65 Desktop audio
G4 67 Mopidy
A4 69 USB mic L
B4 71 USB mic R

Channel actions — C5 octave (in ChannelControl)

Note MIDI # Action
C5 72 Fader down (−fader_step, default −0.05 linear)
D5 74 Fader up (+fader_step)
E5 76 Mute toggle (main bus)

Transport — C6 octave (in Transport mode)

Note MIDI # Ardour action
C6 84 Play
D6 86 Stop
E6 88 Record enable toggle
F6 89 Goto start
G6 91 Goto end
A6 93 Previous marker
B6 95 Next marker
C7 96 Add marker

NOTE: Ardour has no pause primitive. Stop keeps the playhead in place; a subsequent
play resumes from there.


Configuring Custom Mappings in TOML

Key maps are defined in shiloh-mixer.toml under [midi]. Note names use the
convention C4 = 60 (middle C). Sharps use # or s suffix; flats use b.

[midi]
listen_port     = 19999
ardour_osc      = "127.0.0.1:3819"
idle_timeout_ms = 0        # 0 = no timeout
fader_step      = 0.05     # linear step per fader_down/up press

# Mode-entry keys (in Idle mode)
[midi.mode_keys]
"C3" = "channel_select"
"G3" = "transport"
"A3" = "idle"

# Channel selection keys (in ChannelSelect mode)
[midi.channel_keys]
"C4" = "bcast1"
"D4" = "bcast2"
"E4" = "bcast3"
"F4" = "desktop"
"G4" = "mopidy"
"A4" = "lmic"

# Channel action keys (in ChannelControl mode)
[midi.channel_action_keys]
"C5" = "fader_down"
"D5" = "fader_up"
"E5" = "mute_toggle"

# Transport keys (in Transport mode)
[midi.transport_keys]
"C6" = "play"
"D6" = "stop"
"E6" = "record"
"F6" = "goto_start"
"G6" = "goto_end"
"A6" = "prev_marker"
"B6" = "next_marker"
"C7" = "add_marker"

Valid action names:

  • mode_keys: idle, channel_select, transport, navigation
  • channel_action_keys: fader_down, fader_up, mute_toggle
  • transport_keys: play, stop, play_stop, record, goto_start, goto_end,
    prev_marker, next_marker, add_marker
  • channel_keys: any id from [[channels]]

Unknown action names log a warning at startup and silently no-op at runtime.

After editing the config, restart the mixer:

systemctl --user restart shiloh-mixer

Ardour Setup

For bidirectional Ardour integration:

  1. Open Edit → Preferences → Control Surfaces → Open Sound Control → Show Protocol Settings.
  2. Enable OSC. Set the listen port to 3819.
  3. Under Feedback Settings, enable fader and mute feedback on strips/master.

For the WebSocket surface (fader/mute feedback to mixer_web):

The mixer connects to ws://localhost:3818/ automatically if the Ardour WebSocket server
is enabled. Enable it in Ardour’s preferences under Control Surfaces → WebSockets Server.
The mixer matches Ardour strip names by the prefix convention "NN Name" (e.g.
"01 Studio", "02 Laptop").


Troubleshooting MIDI

Keys appear in MIDI panel but action does nothing

  • Check which mode you are in (MODE badge in the MIDI panel).
  • The key may not be mapped in the current mode — the badge shows if unmapped.
  • Press A3 to reset to Idle and try again from ChannelSelect.

No key activity in MIDI panel at all

  1. Check :19999 label in the MIDI panel — confirms the mixer is listening.
  2. On the server: sudo tcpdump -i any -nn udp port 19999 — look for packets when you
    press keys.
  3. On the keyboard PC: check shiloh-midi-sender logs — it prints every note-on forwarded.
  4. If shiloh-midi-sender shows no output, USB MIDI isn’t routing to it. Try listing
    devices: shiloh-midi-sender --list-devices.

Ardour transport does nothing

  1. Check the mixer log for [midi] transport → play (or similar) lines.
  2. If those appear but Ardour ignores them: confirm Ardour OSC is enabled and listening
    on port 3819.
  3. Use tcpdump -i any -nn udp port 3819 on the server to confirm packets are being sent.

DAW fader doesn’t follow Ardour

  1. Check the mixer log for osc feedback listener or ardour web surface at startup.
  2. Verify the Ardour WebSocket server is enabled (not OSC feedback — we use WebSocket).
  3. In mixer_web at /diag, check the Ardour Web Surface card: look for connection
    state and strip count. If strip count is 0, Ardour’s track names may not match the
    "NN Name" prefix convention.