Commit Graph

70 Commits

Author SHA1 Message Date
shawn a37e381c4e backing tracks: add source selector + bluealsa in device list
- 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
2026-06-16 19:20:30 -04:00
shawn 334e035cde ch output: per-channel output routing dropdowns
- Added GET/POST /api/channel-output endpoints for per-channel
  output routing (playback_1 / playback_2 / both)
- Added output dropdowns to CH 1 and CH 2 cards in Audio Interface
- Added setChOutput() and loadChOutput() JS functions
- Updated connect_fx_ports to read per-channel output config
2026-06-16 19:18:30 -04:00
shawn e324d9007b fix: loadAudioDevices now correctly accesses devs.devices array
Also simplified device auto-selection to match by trailing device
number (e.g. 'hw:USB,0' → finds option ending in ',0').
2026-06-16 19:15:51 -04:00
shawn 427c77cbe7 fix: loadAudioDevices accesses .devices from response object
API.listAudioDevices() returns {devices: [...]} not the array
directly. The response also uses card numbers hw:X,Y format.
2026-06-16 19:14:51 -04:00
shawn 93610e4d98 audio interface: separate input/output device selectors
- 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
2026-06-16 19:13:25 -04:00
shawn 1530aa266f audio: replace profiles with direct sample rate + buffer size controls
- Removed audio profile grid and preset cards
- Added two dropdowns: Sample Rate (44.1/48/96/192 kHz) and Buffer
  Size (64-2048 frames)
- New loadAudioParams() reads current rate+period from API
- New setAudioParams() POSTs {period, rate} directly on change
- Cleaned up unused CSS (.audio-profile-grid, .ap-card, .ap-key, etc.)
- Removed unused audioProfiles variable
2026-06-16 19:11:21 -04:00
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 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 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 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 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 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 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 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 013588f2ca feat: Tuner — pitch detection + audio mute in pipeline, API endpoint for pitch data 2026-06-12 19:23:38 -04:00
shawn b6dff3c0f3 feat: Snapshots — 8 per preset save/recall
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
2026-06-12 19:20:56 -04:00
shawn a1f35a56e3 feat: new preset, looper recorder, IR display improvements
- 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
2026-06-12 17:24:14 -04:00
shawn 2a30592ab3 fix: all dummy data replaced with real API data
- 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
2026-06-12 17:16:03 -04:00
shawn 7498ad55fc fix: real VU meter levels from pipeline instead of random animation
- 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
2026-06-12 17:09:49 -04:00
shawn 32690c293e feat: comprehensive React SPA replacing Jinja2 UI
- 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
2026-06-12 15:30:03 -04:00