Add UI_DIST_DIR auto-detection and /ui static mount in the
FastAPI server so the built React frontend is served alongside
the main dashboard.
- UI_DIST_DIR env var override
- Auto-detect common project layouts (frontend-react/dist,
pi-multifx-pedal-ui/dist)
- Mount with html=True for SPA client-side routing
- 17 net definitions (GND, +3.3V/+5V, BCK/LRCK/DIN/DOUT, audio signals)
- Net assignments to 35+ pads across all components (PCM1808, PCM5102A, TL072, AMS1117)
- 20 trace segments for I2S bus and audio signal routing
- 4 GND copper pour zones (F.Cu + B.Cu)
Uses string-based S-expression manipulation with proper parenthesis tracking.
The test_state_connected test timed out because _gather_bt_state calls real
bluetoothctl which hangs for 30s when no BT adapter is present. Since these
gathers depend on hardware (nmcli, bluetoothctl) that only exists on the Pi,
mock them in the test fixture so the test suite runs cleanly on any machine.
43/43 tests pass in 2.93s.
- TestAPIRouting: 7 tests covering get/set routing mode and breakpoint
- mock_pipeline now has a real set_routing side_effect that updates properties
- Covers connected/disconnected states, mode toggle, breakpoint change, both
- 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).
Pipeline:
- process() dispatches to _process_mono() or _process_4cm() based on routing_mode
- _process_4cm() splits chain at routing_breakpoint: pre blocks on ch0, post on ch1
- _process_single_block() extracted for reuse in both mono and 4cm paths
- routing_mode/routing_breakpoint load from preset via load_preset()
- set_routing() for runtime configuration
- Properties: routing_mode (mono|4cm), routing_breakpoint with validation
Web server:
- GET /api/routing — current routing mode and breakpoint
- POST /api/routing — set routing mode/breakpoint, persist to current preset, WS broadcast
- _gather_state() includes routing_mode and routing_breakpoint
Web UI:
- Settings page: 4CM toggle + breakpoint slider with routing description
- Dashboard: routing badge (4CM/Mono) with breakpoint info
- app.js: WebSocket handler, updateRoutingUI(), toggle4cm(), set4cmBreakpoint()
- style.css: .badge, .badge-4cm, .badge-mono, .routing-status, .routing-desc
Tests: mock pipeline fixture updated with routing_mode/routing_breakpoint
- Add routing_mode (mono/4cm) and routing_breakpoint (default 7) to Preset
- Serialize/deserialize in _preset_to_dict / _preset_from_dict
- Backward compatible — old presets default to mono/7
- Add get_4cm_config() / set_4cm_config() helpers on PresetManager
- set_4cm_config validates mode and saves back to disk
- All 48 related tests pass (46 preset + 2 integration activation)
- Full FFT overlap-add IR convolution in IRLoader (process(), set_mix(), toggle)
- Lazy FFT computation — IR FFT padded to correct block+ir size on first process()
- Wet/dry mix control, enabled/disabled toggle with tail clearing
- Fixed pipeline._apply_ir_cab() to delegate to IRLoader.process() instead of
poking internals (old code had array-size mismatch bug: IR FFT at ir_len vs
block FFT at conv_size)
- 46 tests: loading, convolution correctness, overlap-add state, mix, toggle,
directory listing, performance budget (all <5ms even at 8192 taps), edge cases
- scripts/download_irs.sh: free IR pack downloader (God's Cab, Seacow)
- 15 FX blocks implemented with per-block state isolation
- All blocks <500us per 256-sample block (reverb closest at 465us on x86)
- 57 unit tests all passing (per-effect, chain, bypass, state isolation)
- Benchmark script at scripts/benchmark_fx.py
- Vectorised delay reads via read_block_varying()
- scipy.lfilter for EQ (3-band RBJ) and reverb damping
- Fixed _DelayLine.write_block wraparound crash for large blocks
- Comb/Allpass buffers sized to BLOCK_SIZE * 2 minimum