Files
gti_radiostudio/lufs_meter_widget.py
T

139 lines
4.7 KiB
Python

# 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()