Files
uj-mastering-master/CLAUDE.md
T
Mikkeli Matlock b400551321 Move plotting to pyqtgraph: interactive, overlay-capable render layer
Replace the fire-and-forget matplotlib pipeline (render() -> throwaway Figure ->
canvas teardown) with a three-stage architecture that supports zoom/pan, lin/log
toggling, and multi-file overlay:

  compute(audio_file) -> data        # heavy, worker thread, backend-neutral
  build_spec(data, view) -> PlotSpec # cheap, GUI thread, view-aware
  show_specs([(label, spec, color)]) # pyqtgraph, persistent PlotItem, overlay

- plotspec.py: backend-agnostic descriptors (Curve, Band, HLine, Heatmap,
  AxisSpec, PlotSpec) + ViewState (recompute-free lin/log)
- audio_visualization_widget.py: persistent pyqtgraph plot, never torn down;
  per-dataset colours for overlay; spectrogram log-freq via row resample
  (ImageItem is affine-only); ColorBarItem at a fixed cell
- Compare/overlay driven by file-list checkboxes; stable per-song colour by row
- Custom draggable reference lines (add/clear), persist across redraws
- Axis-constrained scroll zoom: Ctrl=time, Shift=value (_AxisZoomViewBox)
- RMS render no longer per-segment fill_between (was the slow path)

Fixes found in review/testing:
- FillBetweenItem needs penned child curves or it fills nothing (RMS/Waveform
  were blank); band fill verified by pixel count
- band overlay alpha was a no-op (QBrush.color() returns a copy)
- colorbar could stack across renders; now added/removed at a fixed layout cell

Deferred (per scope): stereo retention, deep perf rewrites (eager beat_track,
true-peak/crest loops, shared LUFS), per-song colour picker UI.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-14 00:35:10 +09:00

234 lines
11 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# uj-mastering-master
A custom mastering toolkit that provides metrics to evaluate audio masterings through visual analysis.
## Current implementation
### Core features
- **Audio Analysis**: Uses librosa to analyze audio files (MP3/WAV/FLAC support) at native sample rate (no resampling)
- **Pluggable Metrics**: Switchable visualizations (RMS Power, Waveform, LUFS, Crest Factor, PSR, True Peak, Spectrogram; DR next) via a `Metric` ABC
- **Metadata Extraction**: Reads ID3 tags from MP3 files for better file identification
- **Modular GUI Architecture**: Complete PyQt5 interface with drag-and-drop and file dialog support
- **Font Management**: Comprehensive CJK-compatible font system with user-provided font support
- **Threading & Logging**: Robust background processing with detailed logging system
### Technical stack
- **Audio Processing**: librosa, numpy
- **Visualization**: pyqtgraph — persistent, interactive (mouse zoom/pan, lin/log
toggle, multi-dataset overlay). matplotlib remains only for its colormaps
(consumed by pyqtgraph) and as a librosa dependency
- **GUI Framework**: PyQt5 with modular widget architecture
- **Metadata**: mutagen for audio tag reading
- **Font Support**: Custom font loading system with CJK fallback
### Key components
#### `main.py`
- Complete GUI application with modular architecture
- Drag-and-drop and file dialog support for audio files
- Integrated font control system
- Real-time analysis display and file management
#### `analysis_results_manager.py`
- Background threading for audio analysis
- Caches both the loaded `AudioFile` and per-metric `compute()` output, so
metric/font switches re-render from cache without reloading librosa
- Progress tracking and error handling
#### `audio_visualization_widget.py`
- Persistent pyqtgraph plot — the PlotItem is reused across renders, never torn
down, so mouse zoom/pan and scale toggles survive every redraw
- `show_specs([(label, PlotSpec), ...], view)` draws one or more datasets onto
the shared axes, assigning a distinct colour per dataset for overlay/compare
- Spectrogram log-frequency is realised by resampling STFT rows onto a log grid
(`ImageItem` is affine-only and won't follow a log axis) — see `_render_heatmap`
#### `plotspec.py`
- Backend-agnostic drawing descriptors: `Curve`, `Band`, `HLine`, `Heatmap`,
`AxisSpec`, `PlotSpec`, plus the `ViewState` (recompute-free lin/log options)
- The seam that decouples metrics from the plotting library: metrics emit
*intent*, the renderer owns colour/layout/library specifics
#### `font_control_widget.py` & `font_manager.py`
- Unified font control system with clustered interface
- Auto-detection of custom fonts from `fonts/` directory
- System font discovery and CJK compatibility
- Font changes trigger a cheap re-render of the cached metric data
#### `plot_control_widget.py`
- Metric selector dropdown driven by the `metrics.METRICS` registry
- Log-frequency toggle (view-state; recompute-free, currently honoured by the
spectrogram) and the `Refresh Plot` button
- Compare/overlay is *not* here — it is driven by the file-list checkboxes
#### `metrics.py`
- Pluggable `Metric` ABC: `compute(audio_file) -> data` (heavy, worker thread,
backend-neutral numpy/scalars) and `build_spec(data, view) -> PlotSpec` (cheap,
GUI thread, view-aware). Metrics no longer touch the plotting library
- Compute-time vs view-time split: scale (lin/log) is a `ViewState` argument to
`build_spec`, so toggling it never recomputes
- Current registry:
- `RMSPowerMetric` — 10 s rolling RMS with adaptive colour scale
- `WaveformMetric` — min/max envelope, fixed ±1.1 y-range
- `LUFSMetric` — BS.1770 short-term (3 s) + integrated + LRA, via pyloudnorm
- `CrestFactorMetric` — 20·log10(peak/RMS) per 1 s window
- `PSRMetric` — sample-peak minus short-term LUFS (3 s window)
- `TruePeakMetric` — 4× oversampled dBTP via `scipy.signal.resample_poly`
- `SpectrogramMetric` — log-frequency STFT heatmap; adaptive hop caps time
bins at ~4000, `N_FFT=4096`. Log/linear frequency is a view toggle
- Drop in new ones (DR, spectral balance) by appending an instance to `METRICS`;
return a `PlotSpec` from `build_spec` (curves overlay automatically; heatmaps
show one dataset at a time)
- Note: the old matplotlib `_show_axis_extents` exact-endpoint tick labelling is
gone with the matplotlib render path. If wanted back, it belongs in the
renderer, applied uniformly to every metric — not per-metric
#### `master_core.py`
- Defines the `AudioFile` class: librosa loading, rolling RMS power, BPM detection
- Loads at **native sample rate** (`librosa.load(..., sr=None)`) so the full
band is preserved — analysis runs ~2× heavier on 44.1/48 kHz files than the
old 22050 Hz default, by design
- No batch / CLI mode — all analysis is driven from `main.py` via `AnalysisResultsManager`
### Current analysis features
- **Native-rate loading**: full-band analysis up to the file's own nyquist
- **RMS power analysis**: 10-second rolling window with 2-second hops
- **Adaptive colour mapping**: Automatically adjusts scale based on detected headroom
- High dynamic range: 0-0.6 scale for loud masters
- Conservative mastering: 0-0.3 scale for quiet masters
- **Loudness metrics**: LUFS (short-term + integrated + LRA), PSR, Crest Factor
- **Peak analysis**: True Peak (4× oversampled dBTP)
- **Spectral view**: log-frequency spectrogram heatmap over time
- **Readable axes**: exact min/max of every axis is always labelled, even on log scale
- **BPM detection**: Automatic tempo analysis
- **Metadata display**: Artist and title from audio tags
- **Real-time visualization**: Embedded matplotlib plots with font-aware rendering
### GUI features
- **File management**: Drag-and-drop and file dialog for audio selection
- **Compare/overlay**: each analysed file has a checkbox; the ticked set is
overlaid on one graph for the current metric (curve metrics overlay; the
spectrogram shows one track at a time). Highlighting a row drives the metadata
panel, independent of the overlay set
- **Interactive plot**: mouse drag-zoom, scroll-wheel zoom, pan, right-click menu
(pyqtgraph ViewBox); log/linear frequency toggle. Scroll zooms both axes;
**Ctrl+scroll** zooms time only, **Shift+scroll** zooms the value axis only
(`_AxisZoomViewBox`); scrolling over an axis also zooms just that axis
- **Custom reference lines**: "Add ref line" drops a draggable horizontal marker
on any metric (e.g. an eyeballed effective average); lines persist across
redraws/overlay changes and are cleared automatically when the metric changes
- **Font control**: Unified font selector with size control
- **Plot control**: Metric selector + log-frequency toggle + ref-line add/clear
+ refresh-plot button
- **Analysis display**: Real-time visualization with metadata panels
- **Modular architecture**: Self-contained widgets for easy layout management
## Future development plans
### Short-term (urgent)
1. **Plot control widget cluster** *(metric selector + Refresh Plot done; still TODO)*
- Plot style controller (colormap, line vs bar, etc.)
- Foundation for mastering comparison features
### Short-term (not urgent)
1. **Enhanced metrics** *(plug new ones into `metrics.METRICS`)*
- Dynamic range measurement (DR meter)
- Long-term average spectrum (LTAS) / tonal-balance curve
- Stereo metrics (correlation, mid/side) — needs `AudioFile` to retain stereo
2. **Interactive plot features** *(zoom/pan, axis-range select, lin/log done via
pyqtgraph)*
- GUI-controllable plotting styles (colormap, visualization type)
- Export analysis results to CSV/JSON
3. **Advanced GUI controls**
- Plot style customization interface
- Real-time axis range selection (zooming in/out)
- Interactive plot manipulation tools
4. **Better looking UI**
- Graphical loading bar
- Graphical logging text box
### Mid-to-long-term (very not urgent)
1. **Audio comparison system** *(multi-file overlay done via file-list checkboxes;
each song has a stable palette colour keyed to its list row)*
- Per-song colour picker: clickable swatch in the file list (overlay already
accepts a caller-supplied colour per dataset via `show_specs`, so this is a
UI + override-map addition, not a render change)
- Reference vs. comparee designation (vs. flat overlay)
- Side-by-side track comparison interface (incl. spectrogram, which can't overlay)
- A/B testing for mastering versions
2. **Distribution & deployment**
- Self-contained executable releases
- Cross-platform packaging
- Installer creation and distribution
### Future vision
1. **Advanced analysis tools**
- Spectral centroid and bandwidth analysis
- Stereo width measurements
- Transient detection and analysis
- Harmonic distortion detection
2. **Professional features**
- EBU R128 compliance checking
- Custom target curves
- Professional reporting formats
- Multi-format export capabilities
3. **VST plugin development**
- Real-time analysis during mixing/mastering
- Integration with DAWs
- Live feedback during production
## Development notes
### Dependencies
- librosa: Audio analysis and feature extraction
- numpy: Numerical computations
- scipy: Signal processing (true-peak polyphase oversampling, spectrogram
log-frequency resample)
- pyloudnorm: BS.1770 loudness (LUFS, LRA)
- pyqtgraph: Interactive plotting (zoom/pan, overlay, lin/log)
- matplotlib: Colormaps only (consumed by pyqtgraph) + librosa dependency
- mutagen: Audio metadata extraction
- PyQt5: GUI framework
### Architecture considerations
- Three-stage split: `metrics.compute` (heavy, worker thread, backend-neutral
data) → `metrics.build_spec` (cheap, GUI thread, view-aware `PlotSpec`) →
`AudioVisualizationWidget.show_specs` (pyqtgraph rendering, overlay, colours)
- File path handling needs improvement for cross-platform compatibility
- Error handling should be enhanced for production use
- Consider moving from PyQt5 to PyQt6 or PySide for better licensing
### Testing requirements
- Unit tests for audio analysis functions
- GUI component testing
- File format compatibility testing
- Performance testing with large audio files
## Usage
### Running the app
```bash
uv sync # one-time, after cloning
uv run ujm # launch the GUI
```
Optional flags (handled by `logger_setup.parse_log_args`):
```bash
uv run ujm --log-level DEBUG # ERROR | WARN | INFO | DEBUG | TRACE
uv run ujm --log-file # also write audio_analysis.log
```
The only entry point is `ujm` (defined in `pyproject.toml` as
`ujm = "main:main"`). The previous `files.txt` batch mode and the
`python master_core.py` workflow have been removed.
### Planned usage enhancements
1. Interactive plot manipulation and style customization
2. Audio file comparison features (reference vs. comparee)
3. Self-contained executable releases