Commit Graph

36 Commits

Author SHA1 Message Date
shawn 5dc769ad30 fix: move NAM block size sync before JACK client restart
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.
2026-06-17 21:07:48 +00:00
shawn 177d412c84 tone3000: IR downloads, model detail, arch filter, metadata
- 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
2026-06-16 19:59:38 -04:00
shawn 5cdc751312 tone3000 metadata capture: save .meta.json alongside downloaded models
- 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
2026-06-16 19:49:23 -04:00
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 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 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 4fa49d46c1 Bump template version to 8 for CSS cache bust 2026-06-16 18:33:03 -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 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 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 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 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 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 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 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 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
shawn 77a757cee6 R1: Dashboard 500 fix, Phase 2/7 uncommitted changes
- Fix dashboard 500 when deps disconnected — _gather_state() now returns
  full defaults instead of bare {'connected': False}
- Phase 2: FX param schemas aligned with DSP implementations
- Phase 7: Pipeline routing, preset manager improvements
- Factory presets: cleanup and normalization

Fixes dashboard crash on disconnected state (dogfood issue #1)
2026-06-12 14:02:13 -04:00
shawn 9a8d42e1b6 P2: Fix FX param schemas to match DSP implementations
- EQ: Renamed low_gain/mid_gain/high_gain to bass/mid/treble + added
  bass_freq/mid_freq/treble_freq/q params matching _apply_eq()
- Chorus: Added 'delay' param matching _apply_chorus()
- Flanger: Added 'delay' param matching _apply_flanger()
- Phaser: Added 'stages' and 'mix' params matching _apply_phaser()
- Tremolo: Added 'shape' param matching _apply_tremolo()
- Delay: Added 'tap_tempo' param matching _apply_delay()
- Reverb: Added 'predelay' param matching _apply_reverb()
- Added missing nam_amp + ir_cab param schemas

Verified: 49/49 FX DSP blocks pass, 227 tests pass
2026-06-12 12:57:40 -04:00
shawn e890a4aad3 Wire React UI dist mount at /ui/
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
2026-06-12 03:08:07 -04:00
shawn a31342b478 feat(tonedownload): Tone3000 NAM + IR browser, real anon key, tests
- Tonedownload.py: Tone3000 API client with search/download/cache/enrich
- Server.py: 4 endpoints (search models, search IRs, install model, install IR)
- Models.html + irs.html: Browse Tone3000 tab with search, trending, offline
- Models.js + irs.js: Tone3000 search UI with install tracking via localStorage
- Style.css: Card layout for search results with thumbnail, meta, install btn
- Tests: 47 tests covering data classes, cache, API, search, download, enrichment
- Anon key: Updated from placeholder to real Supabase anon key (verified: API works)
2026-06-09 01:39:12 -04:00
shawn b4237f2f1d Add Network + Bluetooth config in Web UI
- src/system/network.py: WiFi scan/connect/hotspot via nmcli + iwlist fallback
- src/system/bluetooth.py: BT scan/pair/connect/MIDI via bluetoothctl
- REST endpoints: /api/wifi/* and /api/bluetooth/* (16 endpoints)
- Web UI: tabbed settings with Network + Bluetooth panels
- network.js: WiFi scan, connect modal, saved nets, hotspot
- bluetooth.js: BT scan, pair/unpair, connect, MIDI service
- style.css: tabs, network list, BT device list, signal indicators
- scripts/setup-bt-midi.sh: BT MIDI systemd bridge service
- tests/test_network.py: 22 tests (nmcli/iwlist, fallback, hotspot)
- tests/test_bt.py: 24 tests (status, scan, pair, MIDI, edge cases)
- _gather_state includes wifi + bluetooth sub-objects
- All 93 tests pass
2026-06-09 01:14:41 -04:00
shawn c8d7541065 feat: full FX palette expansion — 32 new effects
Adds all 27 new DSP implementations plus tests and parameter schemas:

Pitch & Frequency (5): octaver, pitch_shifter, harmonizer, whammy, detune
Modulation (7): ring_modulator, auto_wah, envelope_filter, rotary_speaker,
  uni_vibe, auto_pan, stereo_widener
Drive & Saturation (3): bitcrusher, wavefolder, rectifier
Dynamics (4): expander, de_esser, transient_shaper, sidechain_compressor
Filters & EQ (6): parametric_eq, high_pass_filter, low_pass_filter,
  band_pass_filter, notch_filter, formant_filter
Time-Based (6): ping_pong_delay, multi_tap_delay, reverse_delay, tape_echo,
  shimmer_reverb, looper
Ambience (1): early_reflections

Each effect has: DSP function (5-30 lines numpy), case dispatch entry,
parameter schema in _FX_PARAM_SCHEMAS, and tests for critical effects.
CPU-heavy effects (pitch_shifter, shimmer_reverb) tagged as BETA.
All 79 tests pass.
2026-06-08 18:04:02 -04:00
shawn 8ff584cea9 4CM split routing: stereo pipeline + breakpoint + Web UI
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
2026-06-08 10:57:47 -04:00
shawn 1c5b728e8b Build: Web UI + mDNS (pedal.local)
FastAPI web server with REST + WebSocket, 5 Jinja2 HTML pages,
mobile-friendly dark theme CSS, page-specific JS controllers,
and Avahi mDNS service advertisement for pedal.local.

Files:
  src/web/server.py          — FastAPI app with 17 REST endpoints + /ws WebSocket
  src/web/__init__.py         — Package init with WebServer, WebServerDeps exports
  src/web/templates/*.html    — Dashboard, Presets, Models, IRs, Settings (base)
  src/web/static/style.css    — Dark mobile-first CSS (80-82% max-width)
  src/web/static/websocket.js — Auto-reconnecting WebSocket manager
  src/web/static/app.js       — Dashboard live updates + REST helpers
  src/web/static/presets.js   — Bank/preset CRUD modal UI
  src/web/static/models.js    — NAM model list/load/upload
  src/web/static/irs.js       — IR file list/load/upload
  etc/avahi/pi-multifx-pedal.service — Avahi service advertisement
  scripts/setup-mdns.sh       — One-shot mDNS setup (avahi, hostname, restart)
  main.py                     — Wire WebServer into boot/shutdown
  requirements.txt            — Add fastapi, uvicorn, jinja2, python-multipart
  tests/test_web.py           — 36 tests (pages, REST, WebSocket, errors)

All 36 tests pass.
2026-06-08 00:57:26 -04:00