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
+26 -16
View File
@@ -15,7 +15,7 @@ from playlist_widget import PlaylistWidget
from midi_engine import MidiEngine
from top_bar_widget import TopBarWidget, THEMES
from history_widget import HistoryWidget
from lufs_meter_widget import LUFSMeterWidget
from mixer_widget import MixerWidget
class RadioPanelWindow(QMainWindow):
@@ -70,29 +70,30 @@ class RadioPanelWindow(QMainWindow):
self.history_widget = HistoryWidget()
self.lufs_meter = LUFSMeterWidget(self.engine)
self.mixer = MixerWidget(self.engine)
# ── Centre column ─────────────────────────────────────────────────
centre_col = QHBoxLayout()
# ── Centre column (decks + carts + mixer) ─────────────────────────
centre_col = QVBoxLayout()
centre_col.setSpacing(8)
# Decks column
deck_col = QVBoxLayout()
deck_col.setSpacing(8)
# Top: decks row + carts
deck_area = QWidget()
deck_area.setStyleSheet("background: transparent; border: none;")
deck_layout = QVBoxLayout(deck_area)
deck_layout.setContentsMargins(0, 0, 0, 0)
deck_layout.setSpacing(8)
deck_row = QHBoxLayout()
deck_row.setSpacing(8)
deck_row.addWidget(self.deck1)
deck_row.addWidget(self.deck2)
deck_col.addLayout(deck_row)
deck_layout.addLayout(deck_row)
deck_layout.addWidget(self.cart_bank)
deck_layout.addStretch()
deck_col.addWidget(self.cart_bank)
deck_col.addStretch()
centre_col.addLayout(deck_col)
# LUFS meter on right of centre section
centre_col.addWidget(self.lufs_meter)
# Bottom: mixer
centre_col.addWidget(deck_area, stretch=1)
centre_col.addWidget(self.mixer)
centre_widget = QWidget()
centre_widget.setStyleSheet("background: transparent; border: none;")
@@ -138,6 +139,7 @@ class RadioPanelWindow(QMainWindow):
self.deck2.apply_theme(theme)
self.cart_bank.apply_theme(theme)
self.history_widget.apply_theme(theme)
self.mixer.apply_theme(theme)
def _apply_global_style(self, theme: dict):
self.setStyleSheet(f"""
@@ -164,6 +166,8 @@ class RadioPanelWindow(QMainWindow):
self.midi.deck_cue.connect(self._on_midi_cue)
self.midi.deck_jog.connect(self._on_midi_jog)
self.midi.cart_trigger.connect(self._on_midi_cart)
self.midi.mixer_volume.connect(self._on_midi_mixer_volume)
self.midi.mixer_mute.connect(self._on_midi_mixer_mute)
self.midi.controller_connected.connect(self._on_midi_status)
self.midi.start()
@@ -189,6 +193,12 @@ class RadioPanelWindow(QMainWindow):
def _on_midi_cart(self, slot: int):
self.cart_bank.slots[slot].toggle()
def _on_midi_mixer_volume(self, channel: int, value: float):
self.engine.set_channel_volume(channel, value)
def _on_midi_mixer_mute(self, channel: int, muted: bool):
self.engine.set_channel_mute(channel, muted)
def _on_midi_status(self, connected: bool, name: str):
if connected:
print(f"[MIDI] Connected: {name}")
@@ -225,4 +235,4 @@ def main():
if __name__ == "__main__":
main()
main()