The profile change handler was calling nam_host.set_block_size() AFTER
jack_client.start(), which means the JACK process callback started firing
with the new buffer size while the old NAM subprocess was still running
at the old block size. The pipe read/write byte counts desynchronized,
causing NAMEngineProcess.process() to return audio_block as passthrough
(direct input / DI signal) — every subsequent callback showed dry audio.
Fix: reload the NAM engine with the correct block size BEFORE restarting
the JACK client. Now when the process callback fires, the NAM subprocess
is already ready with the matching block size.
The setAudioParams call restarts JACK which takes up to 10 seconds.
The default API timeout was 6s, causing the request to abort and
leaving JACK in a broken state.
- Changed setAudioParams to use API._fetch directly with timeout:15000
- Also calls loadAudioParams() after success to refresh dropdown values
- NAM/IR tab toggle in Downloads overlay
- Architecture filter (All/Nano/Feather/Standard) - filters via
Supabase architecture_version column
- Model detail overlay: tap a search result to see full description,
gear info, author, pack, architecture, thumbnail before downloading
- IR search + install with .meta.json metadata saving
- Backend: search_models/get_trending_models accept arch param
- Backend: tone_gear field added to model search response
- UI now sends full Tone3000 metadata on install (id, author,
architecture, pack_name, description, thumbnail, etc.)
- Search results show size, architecture, and pack name
- Install endpoint saves a .meta.json file next to each .nam with:
author, architecture, description, pack, thumbnail, download date
- Enables offline browsing of model provenance
- Audio devices endpoint now includes Bluetooth Audio (bluealsa) and
other software PCM devices from aplay -L
- Added GET/POST /api/backing-source endpoints with JACK port routing
- Backing Track Source dropdown in Audio section (None, Bluetooth
Audio, or any JACK capture port found)
- Selecting a source routes it into fx_in:input_2 via jack_connect
- Also supports bt_audio:capture_1/2 from existing JACK bridge
- Added Output Device dropdown alongside Input Device
- loadAudioDevices() now populates both dropdowns from the API and
selects current values
- Separated setAudioInputDevice / setAudioOutputDevice functions
(each calls API with only its respective field)
- Added input_device / output_device to GET /api/audio/profile response
- Removed the old combined setAudioDevice function
- Moved .settings-nav out of .overlay-body so it stays pinned below
the header when scrolling
- Buttons bumped to 12px font, 8px 16px padding, 38px min-height
- Added gap, padding, background and border-bottom to nav strip
Adds a scrollable row of section tabs at the top of the settings
overlay: Network · Audio · Interface · Bluetooth · System.
Each tab scrolls smoothly to its section and highlights as active.
Changed DEFAULT_PORT to 80 so the UI is accessible at
http://pedal.local/ directly (no port needed).
Killed orphaned old port-80 instance that was running stale code.
- Changed both tab bar ⚙ and status bar ⚙ buttons to open the
inline Settings overlay (openOverlay('Settings')) with data loads
instead of navigating to /settings (old Bootstrap page)
- The overlay was already fully built — HTML, CSS, JS loaders were all
present, just never wired up
Both the tab bar ⚙ Settings button and the status bar ⚙ button
in the v2 UI now redirect to /settings instead of opening an
inline overlay. The full settings page has the left sidebar nav
with category views.
All static asset references now include ?v={{ version }} to force
browser to reload on deploy instead of serving cached versions.
Increment TEMPLATE_VERSION in server.py on each deploy.
The React tab bar's Settings button previously showed a minimal inline
component. Now it navigates to /settings which has the full settings
page with left sidebar navigation and category views.
Replaced horizontal tab bar with a left sidebar navigation
that feels like separate pages:
- Left nav with icons + labels (Connection, Audio, 4CM Routing,
Network, Bluetooth, About)
- Active category highlighted in accent color
- Each category has its own URL via hash (#connection, #audio, etc.)
- Back/forward browser navigation via popstate handler
- Stacked layout on mobile (sidebar becomes scrollable row)
- FXBlock was missing block_id field (and __post_init__ auto-generator)
causing AttributeError on /settings and /api/presets endpoints.
- Rebuilt Helix Vite UI from ui/ source (index-jhtO2NOz.js,
index-D3aC6klh.css). Replaces older dist files.
Commit faa7bdb was on a local branch that got orphaned during
git reset --hard origin/main. Cherry-picked back its UI changes:
- New Vite build: index-DI0gBBIR.css + index-LMuhdreF.js
- Updated SVG icons for effects (amplifier, analyzer, dial, etc.)
- prefers-reduced-motion in index.html
- Updated bank/presets icons
Two bugs:
1. **Deadlock in set_audio_profile() (root cause of timeout)**
jack_client.stop() was called BEFORE killing the JACK server.
jack.Client.deactivate() is a synchronous call that blocks until
the JACK RT callback thread acknowledges. When ALSA reconfigures
(period/rate change), the RT thread can stall and deactivate()
hangs permanently. Reversed order: kill JACK server first (via
killall jackd) to force-kill the RT thread, then clean up the
Python client. Same fix applied to device hot-swap path.
2. **Pipeline crash after period change**
_CombFilter.buf and _AllpassFilter.buf were fixed-size arrays
at BLOCK_SIZE (256). If JACK restarts with a different period
(e.g. 512), self.buf[:] = block throws a shape mismatch in the
RT callback -> audio dies permanently. Made internal buffers
dynamically resize to match input block size.
- Add FastNAMHost.set_block_size() — reloads current model with
new block_size, keeping the engine in sync with JACK's period
- Wire into POST /api/audio/profile so NAM engine restarts with
correct frame count when switching between standard/low/stable
- Prevents sample-alignment drift when JACK period changes but
NAM engine stays at old block_size (was causing passthrough)
- 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
Reordered UI_DIST_DIR candidates so pi-multifx-pedal-ui/dist (the
correct Helix-style pedal UI) is checked first. The old priority
put src/web/ui-dist first, which allowed wrong-build deploys to
mask the real UI.
Also add UI_DIST_DIR env var to systemd service for explicit path.
Two issues found and fixed in the deployed UI bundle:
1. String.fromCharCode(64+y) where y is 0-based bank index
→ 64+y gives 64+0=64 = '@' for bank 0
→ Fixed: 65+y gives 65+0=65 = 'A' for bank 0
2. e.bank||1 where falsy 0 falls through to 1
→ Shows 'Bank 1' for bank 0, 'Bank 2' for bank 1
→ Fixed: e.bank||0 so 0 renders as 'Bank 0'
Source code (frontend-react/src/App.jsx) was already clean —
the bug was in a transport/song mode feature that was built
from uncommitted changes then reverted.
Adds snapshot system to Helix Stadium:
Backend:
- Snapshot data model (8 slots per preset, captures block bypass
+ params + master volume)
- Snapshot API endpoints: list, save, recall, rename, delete
- Integrated into preset serialization/deserialization
- Tracks current_snapshot in state / WebSocket broadcasts
Frontend:
- Camera icon in header bar with current snapshot number
- SnapshotPanel as slide-up overlay
- 8 snapshot slots in a horizontal row
- Tap to recall filled snapshot, tap empty slot to save
- Press-and-hold (600ms) to save current state
- Inline name editing on tap
- Discard Edits toggle
- Green flash indicator on save
- Delete button for active snapshot
- New Preset button: finds first empty slot, saves with default name
- Looper/Recorder tab: Record/Stop via arecord backend, list captures, play back
- Rig: shows loaded IR name more clearly + quick-link buttons to browse IRs/Models
- Backend: capture/start, capture/stop, captures API endpoints with file serve
- Status bar: CPU load + sample rate from /api/state, TUNER badge shown immediately
- Rig: optimistic toggle updates (bypass/tuner respond instantly), prominent tuner card
- FX Chain: loads real block params from /api/block-params/{fx_type}, Add Block picker with all FX types
- Record: replaced fake multitrack with real Signal Monitor (live I/O VU, clip detect, level history)
- Backend: added _gather_system_stats() returning cpu_percent and sample_rate
- Added input_level and output_level to /api/state (scaled 0-100)
- React RigScreen now reads real levels from state instead of random walk
- When no audio flows, VU meters show flat (0) instead of fake animation
- New dark UI with 7 tabs: Rig, FX Chain, Models, IRs, Presets, Settings, Record
- All screens wired to live API endpoints
- Real-time state via WebSocket + polling fallback
- Models screen: list/load/unload/upload NAM files + Tone3000 search/install
- IRs screen: list/load/unload/upload IR files + Tone3000 search/install
- Presets screen: browse banks, activate, save, delete
- Settings screen: WiFi scan/connect/hotspot, Bluetooth/MIDI, 4CM routing
- Rig screen: live state, volume, bypass, tuner, signal chain
- FX Chain screen: block params, per-block bypass
- Serves at / instead of /ui
- Built with Vite, ~71KB gzipped
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