Profiled hot spots on a 4-min track and cut the worst offenders:
- Remove BPM: librosa.beat.beat_track ran on every load (~3.7s) for a number no
better than tapping by hand. Dropped from AudioFile + the metadata panel.
- LUFS short-term: replace 474 per-window pyloudnorm.integrated_loudness calls
with one K-weighting pass (reusing pyloudnorm's own filter coefficients) + a
vectorised sliding mean-square. This is true *ungated* EBU R128 short-term
(the old loop wrongly gated each 3s window). Integrated + LRA still use
pyloudnorm's gated calls. ~3.8s -> ~1.9s.
- PSR: reuse LUFS's short-term series (memoised on the AudioFile) + vectorised
sample-peak. ~3.0s -> ~0.2s.
- True Peak: oversample the whole signal once, then an O(N) running max over
windows instead of per-window resample_poly. Bit-identical to the old loop
(max|diff| 0.0000 dB). ~2.1s -> ~1.1s.
- Crest Factor: peaks via the same O(N) running max (last per-window loop gone).
lufs+psr+true_peak: ~9.2s -> ~3.2s, plus ~3.7s of BPM removed from every load.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Introduce a Metric ABC (compute on worker thread, render on GUI thread)
with a METRICS registry, and refactor the analysis pipeline around it.
RMS power (ported), raw waveform, and BS.1770 LUFS (via pyloudnorm) ship
as the initial three; new metrics drop in by appending to METRICS.
- metrics.py: Metric ABC + RMSPowerMetric, WaveformMetric (locked to
+/-1.1 y-range for float headroom), LUFSMetric (short-term 3 s window
+ integrated value, with streaming-target reference line).
- plot_control_widget.py: metric selector dropdown + Refresh Plot button
(moved out of FontControlWidget).
- analysis_results_manager.py: AnalysisResult caches the AudioFile and a
per-metric data dict; new MetricComputeWorker runs metric switches off
the GUI thread via metricComputeStarted/metricReady/metricComputeError
signals, so LUFS on a 12-minute track no longer stalls the UI.
- main.py: all redraw paths funnel through one _render_or_request helper;
stale-result guards keep slow computes from overwriting fresh selections.
- plotting_engine.py removed (metadata text moved onto AnalysisResult;
figure construction lives in each Metric).
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Major refactor from popup-based to persistent PyQt5 interface:
- Extract plotting logic from AudioFile class into separate PlottingEngine
- Create AudioVisualizationWidget with embedded matplotlib canvas
- Add AnalysisResultsManager as bridge between processing and GUI
- Replace simple drag-drop widget with professional splitter layout
- Preserve legacy batch processing mode with execution guard
Features:
- Drag-and-drop audio analysis (.mp3/.wav/.flac support)
- File list with metadata display (BPM, amplitudes, track info)
- Persistent visualization area (no more matplotlib popups)
- Multi-file support with click-to-view functionality
- Threading-ready architecture for future background processing
Technical improvements:
- Clean separation of concerns (analysis/visualization/GUI)
- Qt signal-slot communication pattern
- Modular component design ready for multithreading
- Proper import guards prevent legacy code interference
🤖 Generated with [Claude Code](https://claude.ai/code)
Co-Authored-By: Claude <noreply@anthropic.com>