Add 8-channel mixer with master compressor/limiter, MIDI learn system, separate deck outputs

- Mixer: 8 renamable stereo input channels with VU meter, volume fader, mute/solo
- Master bus: compressor (threshold, ratio, attack, release, makeup) + limiter (ceiling), LUFS metering
- Decks/carts keep their own JACK outputs, completely separate from mixer
- MIDI learn: configurable bindings saved to ~/.gti_radiostudio/midi_map.json
- mixer_widget.py: QDial-based DSP controls, scrollable channel strips
This commit is contained in:
2026-05-12 19:29:16 +10:00
parent 680a9b5d51
commit d2240c61c3
5 changed files with 934 additions and 84 deletions
+56
View File
@@ -223,6 +223,62 @@ def icon_export(color: str = "#aaaaaa") -> QIcon:
return _make_icon(paint, color)
def icon_mute(color: str = "#ff4444") -> QIcon:
def paint(p, r, c):
pen = QPen(c, r.width() * 0.12)
pen.setCapStyle(Qt.RoundCap)
p.setPen(pen)
p.setBrush(Qt.NoBrush)
# Speaker body
cx = r.left() + r.width() * 0.3
p.drawLine(QPointF(cx, r.top() + r.height() * 0.25),
QPointF(cx, r.bottom() - r.height() * 0.25))
p.drawLine(QPointF(cx, r.top() + r.height() * 0.25),
QPointF(r.left() + r.width() * 0.5, r.center().y() - r.height() * 0.15))
p.drawLine(QPointF(cx, r.bottom() - r.height() * 0.25),
QPointF(r.left() + r.width() * 0.5, r.center().y() + r.height() * 0.15))
p.drawLine(QPointF(r.left() + r.width() * 0.2, r.center().y() - r.height() * 0.15),
QPointF(r.left() + r.width() * 0.5, r.center().y() - r.height() * 0.15))
p.drawLine(QPointF(r.left() + r.width() * 0.2, r.center().y() + r.height() * 0.15),
QPointF(r.left() + r.width() * 0.5, r.center().y() + r.height() * 0.15))
# X over speaker
x = r.right() - r.width() * 0.2
y1 = r.top() + r.height() * 0.2
y2 = r.bottom() - r.height() * 0.2
p.drawLine(QPointF(x, y1), QPointF(r.right() - r.width() * 0.05, y2))
p.drawLine(QPointF(r.right() - r.width() * 0.05, y1), QPointF(x, y2))
return _make_icon(paint, color)
def icon_solo(color: str = "#ffaa00") -> QIcon:
def paint(p, r, c):
p.setPen(Qt.NoPen)
p.setBrush(c)
# Circle with "S"
center = r.center()
radius = min(r.width(), r.height()) * 0.45
p.drawEllipse(center, radius, radius)
# S letter (white)
pen = QPen(QColor("#000000"), r.width() * 0.12)
pen.setCapStyle(Qt.RoundCap)
p.setPen(pen)
p.setBrush(Qt.NoBrush)
path = QPainterPath()
path.moveTo(r.left() + r.width() * 0.45, r.top() + r.height() * 0.3)
path.cubicTo(
r.left() + r.width() * 0.7, r.top() + r.height() * 0.15,
r.right() - r.width() * 0.25, r.top() + r.height() * 0.35,
r.left() + r.width() * 0.55, r.top() + r.height() * 0.5
)
path.cubicTo(
r.left() + r.width() * 0.3, r.top() + r.height() * 0.65,
r.right() - r.width() * 0.15, r.top() + r.height() * 0.7,
r.left() + r.width() * 0.45, r.bottom() - r.height() * 0.2
)
p.drawPath(path)
return _make_icon(paint, color)
def icon_theme(color: str = "#00ff41") -> QIcon:
def paint(p, r, c):
p.setPen(Qt.NoPen)