# lufs_meter_widget.py from PySide6.QtWidgets import QFrame, QVBoxLayout, QLabel from PySide6.QtCore import Qt, QTimer, QRect from PySide6.QtGui import QFont, QPainter, QColor, QLinearGradient class LUFSMeterWidget(QFrame): """ Vertical LUFS meter for broadcast monitoring. EBU R128 standard: Target -23 LUFS ±1 LU (Loudness Units). """ 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) 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) # Meter canvas self.meter_canvas = QFrame() self.meter_canvas.setStyleSheet(""" QFrame { background-color: #000000; border: 1px solid #333333; border-radius: 3px; } """) layout.addWidget(self.meter_canvas, stretch=1) # Numeric readout 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 paintEvent(self, event): super().paintEvent(event) lufs = self.engine.lufs_current self.readout.setText(f"{lufs:.1f}") painter = QPainter(self.meter_canvas) rect = self.meter_canvas.rect().adjusted(2, 2, -2, -2) # LUFS scale: -70 (bottom) to 0 (top) # Target zone: -24 to -22 (±1 LU around -23) lufs_range = 70.0 # -70 to 0 if lufs < -70: lufs = -70 if lufs > 0: lufs = 0 fill_pct = (lufs + 70) / lufs_range fill_h = int(rect.height() * fill_pct) # Draw gradient meter gradient = QLinearGradient(0, rect.height(), 0, 0) # Color zones (broadcast standard) if lufs < -30: gradient.setColorAt(0, QColor("#003300")) # Very quiet - dark green gradient.setColorAt(1, QColor("#005500")) elif lufs < -24: gradient.setColorAt(0, QColor("#00aa00")) # Quiet - green gradient.setColorAt(1, QColor("#00dd00")) elif lufs < -22: gradient.setColorAt(0, QColor("#00ff00")) # Target zone - bright green gradient.setColorAt(1, QColor("#00ff00")) elif lufs < -9: gradient.setColorAt(0, QColor("#ffcc00")) # Getting hot - yellow gradient.setColorAt(1, QColor("#ffaa00")) else: gradient.setColorAt(0, QColor("#ff0000")) # Too hot - red gradient.setColorAt(1, QColor("#ff0000")) painter.fillRect( rect.x(), rect.y() + rect.height() - fill_h, rect.width(), fill_h, gradient ) # Draw target line at -23 LUFS target_y = rect.y() + int(rect.height() * (1.0 - ((-23 + 70) / lufs_range))) painter.setPen(QColor("#ffffff")) painter.drawLine(rect.x(), target_y, rect.x() + rect.width(), target_y)