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>
This commit is contained in:
+33
-1
@@ -8,17 +8,26 @@ next to each other in the left panel.
|
||||
import logging
|
||||
from PyQt5.QtWidgets import (
|
||||
QWidget, QVBoxLayout, QHBoxLayout, QLabel, QComboBox, QPushButton, QGroupBox,
|
||||
QCheckBox,
|
||||
)
|
||||
from PyQt5.QtCore import pyqtSignal
|
||||
|
||||
from metrics import METRICS, DEFAULT_METRIC_ID
|
||||
from plotspec import ViewState
|
||||
|
||||
|
||||
class PlotControlWidget(QWidget):
|
||||
"""Metric selector + manual plot refresh."""
|
||||
"""Metric selector, view-scale toggle, and manual plot refresh.
|
||||
|
||||
Overlay/compare is driven by the file-list checkboxes, not here — this cluster
|
||||
only governs *what* metric and *how* its axes are scaled.
|
||||
"""
|
||||
|
||||
metricChanged = pyqtSignal(str) # metric_id
|
||||
viewChanged = pyqtSignal() # view-state (scale) changed
|
||||
plotRefreshRequested = pyqtSignal()
|
||||
addReferenceLineRequested = pyqtSignal()
|
||||
clearReferenceLinesRequested = pyqtSignal()
|
||||
|
||||
def __init__(self, parent=None):
|
||||
super().__init__(parent)
|
||||
@@ -42,6 +51,26 @@ class PlotControlWidget(QWidget):
|
||||
self.metric_combo.currentIndexChanged.connect(self._on_metric_changed)
|
||||
group_layout.addWidget(self.metric_combo)
|
||||
|
||||
# Frequency-axis scale. Only the spectrogram honours it today; harmless
|
||||
# elsewhere (build_spec ignores unsupported toggles).
|
||||
self.log_freq_check = QCheckBox("Log frequency (spectrogram)")
|
||||
self.log_freq_check.setChecked(True)
|
||||
self.log_freq_check.toggled.connect(lambda _: self.viewChanged.emit())
|
||||
group_layout.addWidget(self.log_freq_check)
|
||||
|
||||
# Custom reference lines: drop a draggable horizontal marker (e.g. an
|
||||
# eyeballed effective average) onto whatever metric is showing.
|
||||
ref_row = QHBoxLayout()
|
||||
self.add_ref_button = QPushButton("Add ref line")
|
||||
self.add_ref_button.setToolTip("Drop a draggable horizontal reference line")
|
||||
self.add_ref_button.clicked.connect(self.addReferenceLineRequested.emit)
|
||||
ref_row.addWidget(self.add_ref_button)
|
||||
self.clear_ref_button = QPushButton("Clear")
|
||||
self.clear_ref_button.setToolTip("Remove all custom reference lines")
|
||||
self.clear_ref_button.clicked.connect(self.clearReferenceLinesRequested.emit)
|
||||
ref_row.addWidget(self.clear_ref_button)
|
||||
group_layout.addLayout(ref_row)
|
||||
|
||||
button_row = QHBoxLayout()
|
||||
self.refresh_button = QPushButton("Refresh Plot")
|
||||
self.refresh_button.setToolTip("Re-render the current plot with current settings")
|
||||
@@ -59,3 +88,6 @@ class PlotControlWidget(QWidget):
|
||||
|
||||
def current_metric_id(self) -> str:
|
||||
return self.metric_combo.currentData() or DEFAULT_METRIC_ID
|
||||
|
||||
def current_view_state(self) -> ViewState:
|
||||
return ViewState(y_log=self.log_freq_check.isChecked())
|
||||
|
||||
Reference in New Issue
Block a user