Production-readiness pass before merging the Studio Reference redesign
to master.
Audio (backend):
- Reset AGC `_spectrum_ref` envelope on `start()` so a long silent gap
between sessions doesn't make the first new transients clip at the
ceiling. Annotated the trade-off (loud transient lifts reference for
a few seconds afterwards — the price of real loudness).
- Add `tests/test_audio_analyzer.py` with 10 cases: bin-edge layout,
AGC attack/release asymmetry, lifecycle reset. Skips numpy-dependent
cases when numpy isn't installed; CI has it.
Vinyl mode dead code removed:
- The toggle button was dropped during the sleeve refactor but the JS
state, 2 s `setInterval`, `beforeunload` handler, and `applyVinylMode`
call (commented out in app.js) all stayed. Now properly excised from
player.js + app.js + window.* exports.
- Stripped the matching `.album-art-container.vinyl*` CSS block and its
`vinylSpin` keyframes (~95 LoC).
Sleeve + tonearm fixes:
- Removed the duplicate `.now-playing .vinyl-stage` / `.vinyl-label` /
`.tonearm` block that was overriding the new `.vinyl-stage` rules by
source order — the uncommitted tonearm geometry never took effect
because the stale clone won the cascade.
- Tightened tonearm to 36% × 36% at right:-6%, top:26% so the SVG
bounding box stays right of the sleeve (sleeve right edge ~68%).
Needle now lands on the visible disc grooves at both rest and
playing rotations and never overlaps the cover.
- Removed sleeve `transform: rotate(-2.5deg)` + the matching mobile
`-1.8deg` override; sleeve now sits flat and squared-off.
- Removed the 1px inset hairline on the sleeve and the 1px outline +
inset highlight on the album art — cleaner, no semitransparent
border noise.
- Album art inset 5% to expose a cardstock margin around the print
(using explicit width/height — `inset` shorthand triggered the CSS
replaced-element rule that uses the image's intrinsic size and blew
out the grid track).
Mobile + misc:
- Removed mobile tonearm overrides at 720px and 420px — they were
calibrated for the pre-sleeve geometry and put the needle back over
the cover on phones; desktop geometry is proportional and works.
- Added `<meta name="mobile-web-app-capable">` alongside the legacy
Apple variant to silence the deprecation warning in Chromium.
- Replaced the "PRIMARY" badge on display cards with a copper star
icon (translation key still drives title + aria-label).
- `.gitattributes` with `* text=auto eol=lf` so Windows checkouts stop
nagging "LF will be replaced by CRLF".
Annotations:
- "REF · 24" record-label catalogue mark marked as intentional non-i18n
decoration in index.html.
CI: ruff clean, pytest 7 passed + 3 numpy-skipped (all 10 run on CI).
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- Backend computes time-domain RMS, maps -60..-6 dB to 0..1, sends as
`level` alongside the per-frame-normalized frequency bins.
- Frontend prefers `level` directly; drops the peak-of-bins fallback
and the redundant volume-slider attenuation (loopback capture is
already post-volume on Windows/macOS).
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- Generate numpy/_distributor_init_local.py during build so libopenblas
can be located when running from the Windows installer
- Add os.add_dll_directory() call at runtime as a fallback for embedded Python
- Broaden audio import errors from ImportError to Exception, log at warning
- Move visualizer WS re-subscription into loadAudioDevices() so it runs
after availability is confirmed from the API
- Show/hide the visualizer toggle button based on fetched availability
- Visualizer: FPS 25→30, chunk_size 2048→1024, smoothing 0.65→0.15
- Beat effect: scale 0.03→0.04, glow range 0.5-0.8→0.4-0.8
- UI: reduce container/section paddings from 2rem to 1rem
- Source name: add ellipsis overflow for long names
- Mobile browser toolbar: use flex-wrap instead of column stack,
hide "Items per page" label text on small screens
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Audio capture starts only when first client subscribes,
stops when last client unsubscribes (saves CPU/battery)
- Add lifecycle lock to AudioAnalyzer for thread-safe start/stop
- Status badge uses local visualizer state instead of server flag
- Fix script name vertical text break on narrow screens
- Fix script grid minimum column width on small viewports
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- New audio_analyzer service: loopback capture via soundcard + numpy FFT
- Real-time spectrogram bars below album art with accent color gradient
- Album art and vinyl pulse to bass energy beats
- WebSocket subscriber pattern for opt-in audio data streaming
- Audio device selection in Settings tab with auto-detect fallback
- Optimized FFT pipeline: vectorized cumsum bin grouping, pre-serialized JSON broadcast
- Visualizer config: enabled/fps/bins/device in config.yaml
- Optional deps: soundcard + numpy (graceful degradation if missing)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>