LUFS integrated + LRA:
- Reimplement BS.1770 two-stage gating (integrated) and EBU 3342 (LRA) directly
from the cached K-weighted signal, dropping pyloudnorm's two whole-signal
re-filters. Validated bit-equal to pyloudnorm across steady/dynamic/quiet
signals (0.0000 diff). LUFS compute ~1.9s -> ~0.6s (orig 3.8s). pyloudnorm now
only supplies the filter coefficients.
Prefetch on load:
- After a file loads, PrefetchWorker computes the remaining metrics in the
background (sequential, cooperatively cancellable, skips on-demand hits), so
the first switch to any metric is instant. Superseded when a new file loads.
Status slip:
- Workers measure compute time; metricTiming + phase/duration progress messages
drive the slip ("Loaded in Ns - computing X...", "X computed in Ys"). The old
fake percentage (10% then done) is logged but no longer shown.
- shutdown() stops background threads on window close.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
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>
- SpectrogramMetric: log-frequency STFT power heatmap over time, magma
colormap, -80 dB floor. Adaptive hop caps time bins at ~4000 so long
tracks stay responsive on redraw; N_FFT=4096 keeps low-freq resolution.
- master_core: load audio at native sample rate (librosa.load sr=None)
instead of librosa's 22050 Hz default, so the full band up to the
file's own nyquist (~22 kHz at 44.1 kHz) is analysed. ~2x heavier on
44.1/48 kHz files, by design.
- metrics: shared _show_axis_extents helper forces each axis's exact
min/max onto the tick list with compact labels (_fmt_tick), so the
true range is always readable -- notably the spectrogram's 22 kHz top,
which otherwise sits unlabelled between log-scale decade ticks. Applied
to all metric renders.
- Docs: README + CLAUDE updated for the new metric, native-rate loading,
and axis-readability behaviour.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Three new metrics plug into the existing Metric registry. All inherit
the async compute path, so first-use is non-blocking and subsequent
switches hit the per-(file, metric) cache.
- CrestFactorMetric: 20*log10(peak/RMS) per 1 s window, 0.25 s hop.
Uses cumsum-of-squares for O(N) RMS. Reference lines at 12 dB
(roomy) and 6 dB (heavily limited).
- PSRMetric: sample-peak (dBFS) minus short-term LUFS over the same
3 s window as LUFSMetric, so the two plots line up. Reference lines
at 10 LU (good punch) and 4 LU (squashed).
- TruePeakMetric: ITU-R BS.1770 oversampled true peak via
scipy.signal.resample_poly at 4x over 250 ms / 100 ms windows.
data["integrated_tp_db"] carries the track-wide max.
- LUFSMetric now also computes Meter.loudness_range and surfaces it
in the plot legend alongside the integrated value.
- Shared _to_dbfs helper floors 20*log10 at _EPS so silent windows
don't explode.
Co-Authored-By: Claude Opus 4.7 <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>