# lufs_meter_widget.py from PySide6.QtWidgets import QFrame, QVBoxLayout, QLabel, QWidget from PySide6.QtCore import Qt, QTimer from PySide6.QtGui import QFont, QPainter, QColor, QLinearGradient, QPen class LUFSMeterWidget(QFrame): """Vertical LUFS meter with -14 LUFS target for streaming/broadcast.""" def __init__(self, engine, parent=None): super().__init__(parent) self.engine = engine self.setFixedWidth(80) self.setFrameStyle(QFrame.Box | QFrame.Raised) self.setStyleSheet(""" QFrame { background-color: #0d0d0d; border: 2px solid #444444; border-radius: 6px; } """) self._build_ui() self.timer = QTimer(self) self.timer.setInterval(100) self.timer.timeout.connect(self._update_meter) self.timer.start() def _build_ui(self): layout = QVBoxLayout(self) layout.setContentsMargins(6, 8, 6, 8) header = QLabel("LUFS") header.setFont(QFont("Arial", 10, QFont.Bold)) header.setStyleSheet("color: #888888; background: transparent; border: none;") header.setAlignment(Qt.AlignCenter) layout.addWidget(header) self.meter_canvas = MeterCanvas(self.engine) layout.addWidget(self.meter_canvas, stretch=1) self.readout = QLabel("-70.0") self.readout.setFont(QFont("Courier New", 12, QFont.Bold)) self.readout.setStyleSheet("color: #00ff41; background: transparent; border: none;") self.readout.setAlignment(Qt.AlignCenter) layout.addWidget(self.readout) def _update_meter(self): lufs = self.engine.lufs_current self.readout.setText(f"{lufs:.1f}") self.meter_canvas.update() class MeterCanvas(QWidget): """Custom painted LUFS bar with -14 LUFS target.""" def __init__(self, engine, parent=None): super().__init__(parent) self.engine = engine self.setStyleSheet(""" QWidget { background-color: #000000; border: 1px solid #333333; border-radius: 3px; } """) self.setMinimumHeight(200) def paintEvent(self, event): painter = QPainter(self) painter.setRenderHint(QPainter.Antialiasing) rect = self.rect().adjusted(3, 3, -3, -3) lufs = self.engine.lufs_current # Scale: -50 LUFS (bottom) to 0 (top) - makes -14 more readable lufs_min = -50 lufs_max = 0 lufs_range = lufs_max - lufs_min lufs = max(lufs_min, min(lufs_max, lufs)) fill_pct = (lufs - lufs_min) / lufs_range fill_h = int(rect.height() * fill_pct) # Color zones optimized for -14 LUFS target gradient = QLinearGradient(0, rect.bottom(), 0, rect.top()) if lufs < -30: # Very quiet - dark green gradient.setColorAt(0, QColor("#002200")) gradient.setColorAt(1, QColor("#004400")) elif lufs < -18: # Quiet - green gradient.setColorAt(0, QColor("#00aa00")) gradient.setColorAt(1, QColor("#00dd00")) elif lufs < -12: # Target zone -18 to -12 LUFS (±4 LU around -14) - bright green gradient.setColorAt(0, QColor("#00ff00")) gradient.setColorAt(1, QColor("#00ff00")) elif lufs < -6: # Getting hot - yellow gradient.setColorAt(0, QColor("#ffcc00")) gradient.setColorAt(1, QColor("#ffaa00")) else: # Too hot - red gradient.setColorAt(0, QColor("#ff0000")) gradient.setColorAt(1, QColor("#ff0000")) painter.fillRect( rect.x(), rect.bottom() - fill_h, rect.width(), fill_h, gradient ) # Draw reference lines painter.setPen(QPen(QColor("#ffffff"), 1)) # Target line at -14 LUFS (main target) target_pct = ((-14 - lufs_min) / lufs_range) target_y = rect.bottom() - int(rect.height() * target_pct) painter.drawLine(rect.x(), target_y, rect.right(), target_y) # Upper boundary at -12 LUFS (dotted) painter.setPen(QPen(QColor("#888888"), 1, Qt.DotLine)) upper_pct = ((-12 - lufs_min) / lufs_range) upper_y = rect.bottom() - int(rect.height() * upper_pct) painter.drawLine(rect.x(), upper_y, rect.right(), upper_y) # Lower boundary at -16 LUFS (dotted) lower_pct = ((-16 - lufs_min) / lufs_range) lower_y = rect.bottom() - int(rect.height() * lower_pct) painter.drawLine(rect.x(), lower_y, rect.right(), lower_y) painter.end()