Files
gti_radiostudio/lufs_meter_widget.py
T

115 lines
3.8 KiB
Python
Raw Normal View History

2026-04-30 00:30:23 +10:00
# 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)