145 Commits

Author SHA1 Message Date
shawn ea75c41745 settings: nav sticks to top with bigger buttons
- 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
2026-06-16 19:03:20 -04:00
shawn f6fe3b2523 settings: add horizontal section nav bar
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.
2026-06-16 18:57:53 -04:00
shawn bdf1434718 fix: port 80 hardcoded in main.py (was 8080)
main.py passes port=8080 to WebServer constructor, overriding the
DEFAULT_PORT in server.py. Changed to port=80.
2026-06-16 18:46:55 -04:00
shawn 753de36dc2 serve on port 80 — pedal.local works without :8080
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.
2026-06-16 18:45:53 -04:00
shawn 7078217783 nuke all old UIs — templates, React SPA, /v2 mount, legacy routes
Removed:
- src/web/templates/*.html (all 6 Jinja2 template files)
- src/web/ui-dist/ (old React SPA build)
- /v2 duplicate static mount (same as /)
- /ui static mount (old React SPA)

Server.py changes:
- Root handler now directly serves v2 UI (no fallback chain)
- Old template routes (/presets, /models, /irs, /settings) → redirect to /
- Removed Jinja2Templates, TEMPLATES_DIR, TEMPLATE_VERSION, UI_DIST_DIR
2026-06-16 18:45:04 -04:00
shawn 9cc08d16da settings: open as v2 overlay instead of redirecting to old settings page
- 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
2026-06-16 18:41:13 -04:00
shawn 93eb8cc4aa Make v2 UI Settings button navigate to /settings page
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.
2026-06-16 18:35:51 -04:00
shawn 4fa49d46c1 Bump template version to 8 for CSS cache bust 2026-06-16 18:33:03 -04:00
shawn 88ee1e24dc Fix mobile settings sidebar: keep vertical layout instead of horizontal row 2026-06-16 18:32:45 -04:00
shawn 3b1bb43895 Add cache-busting ?v= to template CSS/JS, bump version
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.
2026-06-16 18:30:07 -04:00
shawn a7d8a27975 Settings ⚙ button now links to /settings page with left nav sidebar
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.
2026-06-16 18:24:32 -04:00
shawn 23a83cbd7b Settings: left sidebar nav with separate page views per category
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)
2026-06-16 18:19:32 -04:00
shawn 29542e196b Fix settings 500: add missing block_id to FXBlock, rebuild UI
- 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.
2026-06-16 17:45:12 -04:00
shawn 1281bf5943 Restore lost UI dist build (prefers-reduced-motion, updated SVG assets)
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
2026-06-16 17:40:05 -04:00
shawn 44f04887dc Add missing save_config() to config.py (was imported by server.py but never pushed) 2026-06-16 17:29:41 -04:00
shawn 2c4d7298ec Fix audio timeout when changing buffer size/sample rate
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.
2026-06-16 17:15:22 -04:00
shawn 0fa8bd387a NAM hero fills screen, pedal-style FX blocks below 2026-06-15 06:03:46 +00:00
shawn a69aee357c v2 UI at root, instrument type selector, Gitea push 2026-06-15 05:53:11 +00:00
shawn e2f81fd309 Fix NAM engine block_size at boot to match audio profile period
- 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
2026-06-13 22:27:22 -04:00
shawn 2a74c8e114 Sync NAM engine block_size with JACK period on profile switch
- 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)
2026-06-13 22:26:27 -04:00
shawn fa787debd1 Add stable audio profile: period=512 (10.6ms) for Pi 4B stability 2026-06-13 22:24:06 -04:00
shawn 11c62868cf Add UX & API test plan — 10 sections, 60 tests covering block toggle, param updates, preset CRUD, audio profile switching, volume/bypass, WebSocket broadcasts, UI visuals, edge cases, and channel isolation 2026-06-13 22:20:20 -04:00
shawn e7a778d33d Delete all presets everywhere
- Remove factory presets (151 files across 30 banks, 5 channels)
- Remove default_captures (5 NAM preset mappings)
- Disable factory preset installation on boot (if False)
- Clean from local dev repo, pedal project copy, and pedal runtime
2026-06-13 21:10:21 -04:00
shawn 67ffc3252f Fix UnboundLocalError in MIDI handler when pyserial is missing
- Remove serial.SerialException from except clause since 'serial'
  is not defined when import serial fails, causing UnboundLocalError
- Catch only ImportError and OSError
2026-06-13 21:05:21 -04:00
shawn 2aefa5303d Add /api/audio/profile endpoints for runtime latency profile switching
- 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
2026-06-13 20:47:35 -04:00
shawn c0c0ffe4f2 fix: toggle_block now sets bypass=not enabled - was leaving bypass=True, blocking FX 2026-06-13 20:40:11 -04:00
shawn 1b250cd5a3 fix: critical DSP bugs causing crackles/pops and clipping
1. NAM_AMP: removed double process() call (was processing through C++
   engine twice per block, corrupting internal state), removed os.system()
   debug logging on every audio callback (caused JACK xruns on RPi 4),
   fixed level*2.0 input clipping to proper 0.0-1.0 gain drive

2. Added np.clip() to all wet/dry mix returns across 9+ effect types
   (delay, reverb, chorus, flanger, phaser, ring mod, envelope filter,
   rotary, formant, ping-pong, multi-tap, reverse, shimmer reverb)

3. Clipped all delay feedback write paths (digital, analog, ping-pong,
   multi-tap, reverse, tape echo) to prevent delay-line runaway

4. Added output clipping to _process_mono and _process_4cm master volume

5. Fixed analog delay tanh(*0.5)*2.0 -> tanh(*0.5) (was re-expanding
   after soft-clip, reintroducing digital distortion)
2026-06-13 20:35:38 -04:00
shawn a43e65b81f fix: delay time param was 0.35 (seconds) instead of 350 (ms) — inaudible
- Changed delay time from 0.35ms to 350ms for audible delay
- Increased reverb mix 0.25→0.35 and decay 0.35→0.4
- Increased delay mix 0.25→0.35 and feedback 0.2→0.3
- Both reverb and delay now enabled+active by default (bypass=false)
2026-06-13 20:20:25 -04:00
shawn 103d36a14b cleanup: remove separate ui repo dependency, ui-dist is canonical 2026-06-14 00:05:06 +00:00
shawn 9e3ab9d53b fix: NAM amp level param now controls input drive, JACK period 256, toggle API matching 2026-06-14 00:01:46 +00:00
shawn 9fade4a2d2 fix: add missing API endpoints for volume POST, bypass/toggle, block params, block toggle; fix JACK 1.9.22 cmd args; fix Scarlett 2i2 channel count 2026-06-13 21:09:13 +00:00
shawn cd2ed273e9 fix: handle NaN output_level in state gathering to prevent crash 2026-06-13 12:24:32 -04:00
shawn 6e07aa2b38 fix: NAM SlimmableContainer (A2) model loading with correct weight import
Complete rewrite of _build_nam_model and _build_wavenet to handle:
- SlimmableContainer (A2): pick submodel by quality, recurse into WaveNet
- WaveNet: proper dilated convs with causal padding, 1x1 layer1x1 post-conv
- Linear: simple pass-through with gain/bias

Weight import matches NAM export order:
  rechannel -> per-layer(conv w+b, input_mixer w, layer1x1 w+b) -> head_rechannel w+b -> optional head -> head_scale

Also fixes MOCK_PRESETS in frontend to match actual factory preset names.
2026-06-13 12:21:53 -04:00
shawn 42be40f698 fix: replace nam package import with local init_from_nam
The PyPI 'nam' package (v0.0.3) is Neural Additive Models for tabular
data — completely different from Neural Audio Model (guitar amp modeling).
Implemented a local _init_from_nam() that builds a WaveNet NAM model
from the Core ML .nam JSON format. Uninstalling the wrong package.
2026-06-13 12:06:11 -04:00
shawn 80485186af fix: activate preset on boot after restore_state()
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.
2026-06-13 11:58:38 -04:00
shawn d917d4b20d docs(server): add comment about UI_DIST_DIR env var on pedal 2026-06-13 11:32:54 -04:00
shawn 8e2b090eaf fix(server): prioritize pi-multifx-pedal-ui/dist over src/web/ui-dist for Helix UX
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.
2026-06-13 11:31:39 -04:00
shawn 659e53766a fix(bass-presets): move factory presets to channel subdirs + per-channel install
Root cause: factory presets for bass (banks 17-20) sat in the root
factory dir alongside guitar presets, and install_factory_presets()
used rglob() which picked up ALL presets into the GUITAR channel.

Changes:
- Move bank_17-20 from presets/factory/ → presets/factory/bass/
  (matching the existing keys/, vocals/, backing_tracks/ layout)
- Fix install_factory_presets() to scan channel-specific subdirectory:
  GUITAR scans root bank_* dirs, all others scan <channel>/bank_*
- Fix main.py to iterate over all 5 channels when installing factory
  presets (was only installing into GUITAR)
- Import Channel enum in main.py

Tests: 56/56 preset manager tests pass. Integration verified:
- GUITAR: 68 presets (banks 0-16)
- BASS: 16 presets (banks 17-20, SansAmp/Darkglass/Ampeg SVT)
- KEYS: 12 presets (banks 21-23)
- VOCALS: 12 presets (banks 24-26)
- BACKING_TRACKS: 12 presets (banks 27-29)
2026-06-13 10:23:57 -04:00
shawn 575535762c fix: bank display showing 1 instead of 0 + build fixes
- Removed 'e.bank||1' falsy trap (was converting bank 0 to 1)
- Fixed TypeScript errors: cp guard, unused vars, path aliases
- Added base: '/ui/' to vite config for correct asset paths
- Bank persistence already implemented (localStorage pedal-footswitch-bank)
- Rebuilt and deployed to src/web/ui-dist/
2026-06-13 10:18:33 -04:00
shawn 82f687323c t_926ea4a1: fix midi import path and channel-aware preset routing
- Fix ModuleNotFoundError: 'from presets.types' -> 'from src.presets.types'
- Share same PresetManager for both channels (it routes internally)
- Pass channel param to list_banks() and load() so bass queries
  read from bass/ subdirectory, not guitar/
- Verified: bass presets isolate correctly, survive restart
2026-06-13 10:18:07 -04:00
shawn 4b59e4422c fix: 'Bank @' → 'Bank A' (off-by-one in transport mode bank letter gen)
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.
2026-06-13 10:17:44 -04:00
shawn 42d0592856 Add factory presets for Keys, Vocals, and Backing Tracks channels
3 banks per channel (9 total, 36 presets):
- Keys: Piano & Electric (bank 21), Synth Pads & Leads (22), Organ & Vintage Keys (23)
- Vocals: Lead Vocals (24), Harmony & Backing (25), Vocal FX (26)
- Backing Tracks: Mix Ready (27), Click & Guide (28), Ambient Pads (29)

Appropriate FX chains per channel — de-esser, HPF/LPF, shimmer_reverb,
stereo_widener, rotary_speaker for vocals and keys; master bus chain
for backing tracks. No IR cab blocks on vocal/backing presets.

Generated by scripts/generate_channel_presets.py
2026-06-13 10:09:25 -04:00
shawn 5c51b3db28 feat(nam): download 10 new A2 models for bass, orange, and other missing categories
Bank 7 — Bass: Ampeg PF20T, Aguilar Tone Hammer 500, GK 800RB (Gallien-Krueger), Aguilar DB751
Bank 7 — Orange: Orange Rocker 30 Dirty + Natural
Bank 7 — Others: Fender Bassman dimed, Hiwatt DR103 Bright + Cornish, Supro Black Magick M3

All SlimmableContainer v0.7.0, ~287-295 KB, verified JSON.
Also fixed Bank 2 Tone3000 storage keys to include .nam extension (Tone3000 changed URL format).
Inventory updated: 54 → 64 total models.
2026-06-13 02:09:56 -04:00
shawn 11c838a27e docs: add 17 NAM rock, high-gain & ambient models to inventory (Banks 4-6) 2026-06-13 01:56:40 -04:00
shawn 04fb12dcdc download: 62 real cabinet IRs from GitHub (fnpngn/IR)
Sources downloaded via git-lfs:
- DYNAX IR pack (Fender, Vox, Marshall, Mesa, Bogner, etc.)
- 1960a Marshall 4x12 SM57/Audix i5 captures (96kHz → 48kHz)
- Allure Pack (vintage 1x10, 1x12, 2x12)
- ML Sound Lab free IRs
- Dauntless IRs (Engl Fireball)
- Master Of Puppets IRs
- Modern Metal Songwriter IRs
- FREE BASS IR Pack
- ML NAM AI Cab.nam bonus model

All IRs converted to 48kHz, 16-bit mono WAV. Inventory updated with
full per-file breakdown. Original 7 synthetic IRs preserved.
2026-06-13 01:54:20 -04:00
shawn e11956881f Add Bank 12: Blues genre presets — SRV, Muddy/Buddy, B.B. King, Gary Moore
Bank 12 (Blues) with 4 factory presets covering 9 legendary blues artists:
- Preset 0: Texas Flood (SRV) — TS808 + Fender Super Reverb
- Preset 1: Mannish Boy (Muddy Waters) — Fender Bassman cranked w/ slapback
- Preset 2: The Thrill (B.B. King) — Twin Reverb clean w/ comp
- Preset 3: Still Got the Blues (Gary Moore) — Marshall crunch w/ delay

Includes docs/blues-genre-research.md with full gear deep-dives, NAM
model sourcing guide (Tone3000 URLs), chain architectures, and
parameter value rationale for all 9 artists (SRV, B.B. King, Albert King,
Freddie King, Muddy Waters, Buddy Guy, Gary Moore, Joe Bonamassa,
John Mayer).
2026-06-13 00:52:28 -04:00
shawn adb35a730b feat: Add analog and ping_pong delay subtypes under FXType.DELAY
- Refactor _apply_delay into subtype dispatcher (digital/analog/ping_pong/tape)
- Add _apply_analog_delay: BBD-style with one-pole LPF on feedback path
  and subtle saturation (tanh) for warm, darker repeats. tone param
  controls feedback brightness (0.0=dark, 1.0=bright).
- Wire existing _apply_ping_pong_delay and _apply_tape_echo as subtypes
- All existing delay tests (TestDelay, TestPingPongDelay) pass unchanged
- Add TestAnalogDelay: output range, dry passthrough, feedback tail decay,
  tone spectral effect, state initialization, bypass
- Add TestDelaySubtypePingPong: output finite, dry path, state accumulation,
  ping alternation, zero mix passthrough, bypass

Unblocks child task t_14bae7ea (FXBlock subtype field + pipeline dispatch)
2026-06-13 00:41:22 -04:00
shawn bfe1434f75 feat: separate preset banks per channel (guitar/bass)
- Channel enum (GUITAR, BASS) with channel field on Preset dataclass
- Channel-prefixed storage: {root}/{channel}/bank_{bank}/preset_{program}.json
- PresetManager channel awareness: set_channel(), current_channel property
- Per-channel state persistence (channel_state.json per channel dir)
- Legacy migration: flat bank_* dirs auto-migrate to guitar/ on first boot
- All CRUD methods accept optional channel parameter
- API endpoints accept channel param on GET/PUT/DELETE /api/presets
- /api/channel GET/POST for channel switching
- 10 new channel tests (independence, switching, migration, scoping)
- Factory presets install to specified channel
2026-06-12 23:22:47 -04:00
shawn 0a21297f77 t_59dfed5b: Dual DSP Chain feasibility research document 2026-06-12 23:10:32 -04:00
shawn 013588f2ca feat: Tuner — pitch detection + audio mute in pipeline, API endpoint for pitch data 2026-06-12 19:23:38 -04:00