Files
uj-mastering-master/plot_control_widget.py
T
Mikkeli Matlock 7bdf465799 Add pluggable metrics architecture with LUFS
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>
2026-05-30 00:42:45 +09:00

62 lines
2.0 KiB
Python

"""
Plot control widget: pick which metric to display and refresh the current plot.
Mirrors FontControlWidget's clustered-groupbox style so the two sit naturally
next to each other in the left panel.
"""
import logging
from PyQt5.QtWidgets import (
QWidget, QVBoxLayout, QHBoxLayout, QLabel, QComboBox, QPushButton, QGroupBox,
)
from PyQt5.QtCore import pyqtSignal
from metrics import METRICS, DEFAULT_METRIC_ID
class PlotControlWidget(QWidget):
"""Metric selector + manual plot refresh."""
metricChanged = pyqtSignal(str) # metric_id
plotRefreshRequested = pyqtSignal()
def __init__(self, parent=None):
super().__init__(parent)
self.logger = logging.getLogger(__name__)
self.initUI()
def initUI(self):
layout = QVBoxLayout(self)
layout.setContentsMargins(5, 5, 5, 5)
group_box = QGroupBox("Plot")
group_layout = QVBoxLayout(group_box)
group_layout.addWidget(QLabel("Metric:"))
self.metric_combo = QComboBox()
for metric_id, metric in METRICS.items():
self.metric_combo.addItem(metric.display_name, metric_id)
default_idx = self.metric_combo.findData(DEFAULT_METRIC_ID)
if default_idx >= 0:
self.metric_combo.setCurrentIndex(default_idx)
self.metric_combo.currentIndexChanged.connect(self._on_metric_changed)
group_layout.addWidget(self.metric_combo)
button_row = QHBoxLayout()
self.refresh_button = QPushButton("Refresh Plot")
self.refresh_button.setToolTip("Re-render the current plot with current settings")
self.refresh_button.clicked.connect(self.plotRefreshRequested.emit)
button_row.addWidget(self.refresh_button)
group_layout.addLayout(button_row)
layout.addWidget(group_box)
def _on_metric_changed(self, _index: int):
metric_id = self.metric_combo.currentData()
if metric_id:
self.logger.info(f"Metric changed: {metric_id}")
self.metricChanged.emit(metric_id)
def current_metric_id(self) -> str:
return self.metric_combo.currentData() or DEFAULT_METRIC_ID