When JACK is killed forcefully (killall -9), the /dev/shm/jack_sem.*
shared memory segments and /dev/shm/jack_db-0/ directory are left
behind. The next start_jack() call finds these stale segments and
fails silently — no audio output, JACK process may appear briefly
then crash.
Fix:
- AudioSystem.stop_jack(): after killing jackd, also rm -rf
/dev/shm/jack* to clean up stale SHM.
- main.py boot sequence: clean SHM before first start_jack(), so
the pedal recovers from crashes that happened before this fix.
The profile change handler was calling nam_host.set_block_size() AFTER
jack_client.start(), creating a race where the JACK callback fired with
the new buffer size while the old NAM subprocess was still running at
the old block_size. Pipe byte counts desynced => NAM returned passthrough.
+ also: auto_connect disable during restart, connect_fx_ports reorder,
Tone3000 gear/sort/pagination params, NAM engine switch API endpoints
- FastNAMHost now initialized with block_size from audio config's
latency profile (e.g. 512 for stable, 256 for standard)
- Prevents sample-alignment mismatch between JACK period and
NAM engine internal buffer size at boot
- GET /api/audio/profiles — list available profiles with params
- GET /api/audio/profile — current profile + jack_running + xrun_count
- POST /api/audio/profile {profile: 'standard'|'low'} — switch profile,
restart JACK server + JACK audio client atomically
- Adds audio_system + jack_audio refs to WebServerDeps
- Rollback on JACK restart failure
restore_state() loads the last state from disk but doesn't call
activate() on the pipeline, so the NAM model was never loaded
on boot and no audio passed through. Added select() call with
the restored bank/program values so the pipeline loads the
preset chain including the NAM model.
Bug: PedalApp._on_midi_cc() received (value, channel) where channel was
the MIDI channel (0-15) but was treated as the CC number (0-127). The
register_cc callback only passes value + MIDI channel, not the CC number.
Fix: Use a lambda closure in _wire_midi_callbacks to capture the CC
number so _on_midi_cc gets (value, cc_number, channel). Moved expression
pedal (CC#11) check before the presets/pipeline guard so it works even
when no preset is active.
Tests: 21 new integration tests covering:
- CC lambda closure capturing correct CC numbers
- Expression pedal → master volume (CC#11)
- Full parse→dispatch→callback chain for PC and CC
- Preset MIDI mapping lookup by CC number
- Edge cases: min/max values, rapid switching, empty slots
- Add JackAudioClient class to src/system/audio.py — real-time JACK
client owning ports and a process callback that streams audio through
the DSP pipeline. Handles mono (1ch I/O) and stereo 4CM (2ch I/O).
- Wire PedalApp.boot(): read audio.mode from config, set pipeline
routing_mode (mono/4cm), create and start JackAudioClient with
matching channel count.
- Add PedalApp.set_routing_mode(mode) — hot-switch between mono and
stereo_4cm at runtime: updates pipeline.routing_mode, restarts JACK
client with new channel count via set_channel_count(), re-connects
JACK ports.
- Wire shutdown: stop JackAudioClient before MIDI/footswitch.
- Export JackAudioClient from src.system.__init__.
- 53 tests pass (22 integration + 31 audio).