Add mic ON/OFF toggle, headphone and studio output busses

- Channels 0-1 are now dedicated Mic 1/Mic 2 with mic_on toggle
- Mic audio only routes to main when mic_on=True, never to headphone
- New headphone_out_L/R JACK ports (all channels except mics)
- New studio_out_L/R JACK ports (copies main, mutes when any mic is on)
- Mic ON/OFF toggle button in mixer UI (hidden on non-mic channels)
- Session save/load includes mic_on state
This commit is contained in:
2026-05-15 00:36:48 +10:00
parent 89fb7010fb
commit 9e4315886d
4 changed files with 135 additions and 5 deletions
+67 -4
View File
@@ -15,8 +15,8 @@ FFMPEG_FORMATS = {'.mp3', '.m4a', '.mp4', '.opus', '.aac', '.wma'}
NUM_MIXER_CHANNELS = 8
DEFAULT_CHANNEL_NAMES = [
"Turntable 1", "Turntable 2", "Mic", "Aux 1",
"Aux 2", "Aux 3", "Aux 4", "Aux 5",
"Mic 1", "Mic 2", "Aux 1",
"Aux 2", "Aux 3", "Aux 4", "Aux 5", "Aux 6",
]
@@ -163,6 +163,7 @@ class MixerChannel:
self.muted = False
self.solo = False
self.pfl = False
self.mic_on = False
self.vu_peak = 0.0
self.in_port_L = None
self.in_port_R = None
@@ -285,6 +286,18 @@ class AudioEngine:
self.client.outports.register("master_out_R"),
)
# Headphone output ports (all channels except mics)
self.headphone_out = (
self.client.outports.register("headphone_out_L"),
self.client.outports.register("headphone_out_R"),
)
# Studio output ports (same as main, mutes when any mic is on)
self.studio_out = (
self.client.outports.register("studio_out_L"),
self.client.outports.register("studio_out_R"),
)
# PFL output ports
self.pfl_out = (
self.client.outports.register("pfl_out_L"),
@@ -325,6 +338,15 @@ class AudioEngine:
if 0 <= index < NUM_MIXER_CHANNELS:
self.mixer['channels'][index].pfl = pfl
def set_channel_mic_on(self, index: int, on: bool):
if 0 <= index < NUM_MIXER_CHANNELS:
self.mixer['channels'][index].mic_on = on
def get_channel_mic_on(self, index: int) -> bool:
if 0 <= index < NUM_MIXER_CHANNELS:
return self.mixer['channels'][index].mic_on
return False
def _fill_port(self, port_L, port_R, deck: DeckState, frames: int):
buf_L = port_L.get_array()
buf_R = port_R.get_array()
@@ -423,7 +445,18 @@ class AudioEngine:
master_buf_L.fill(0.0)
master_buf_R.fill(0.0)
headphone_buf_L = self.headphone_out[0].get_array()
headphone_buf_R = self.headphone_out[1].get_array()
headphone_buf_L.fill(0.0)
headphone_buf_R.fill(0.0)
studio_buf_L = self.studio_out[0].get_array()
studio_buf_R = self.studio_out[1].get_array()
studio_buf_L.fill(0.0)
studio_buf_R.fill(0.0)
any_solo = any(ch.solo for ch in self.mixer['channels'])
any_mic_on = any(ch.mic_on for ch in self.mixer['channels'][:2])
# PFL bus
pfl_buf_L = self.pfl_out[0].get_array()
@@ -436,6 +469,7 @@ class AudioEngine:
in_R = ch.in_port_R.get_array()
mute = ch.muted or (any_solo and not ch.solo)
is_mic = ch.index < 2
# PFL: pre-fader, pre-mute signal to PFL bus
if ch.pfl:
@@ -447,16 +481,31 @@ class AudioEngine:
continue
vol = ch.volume
goes_to_main = not is_mic or ch.mic_on
goes_to_headphone = not is_mic
peak = 0.0
for i in range(frames):
s_L = in_L[i] * vol
s_R = in_R[i] * vol
master_buf_L[i] += s_L
master_buf_R[i] += s_R
if goes_to_main:
master_buf_L[i] += s_L
master_buf_R[i] += s_R
if goes_to_headphone:
headphone_buf_L[i] += s_L
headphone_buf_R[i] += s_R
peak = max(peak, abs(s_L), abs(s_R))
ch.vu_peak = peak
# ── Studio bus: same as main, muted when any mic is on ────────
if any_mic_on:
studio_buf_L.fill(0.0)
studio_buf_R.fill(0.0)
else:
studio_buf_L[:] = master_buf_L[:]
studio_buf_R[:] = master_buf_R[:]
# ── Master DSP ──────────────────────────────────────────────────
master_audio = np.array([master_buf_L, master_buf_R])
@@ -466,6 +515,20 @@ class AudioEngine:
master_buf_L[:] = master_audio[0]
master_buf_R[:] = master_audio[1]
# Apply same DSP to headphone (without mics)
hp_audio = np.array([headphone_buf_L, headphone_buf_R])
self.mixer['master'].compressor.process(hp_audio)
self.mixer['master'].limiter.process(hp_audio)
headphone_buf_L[:] = hp_audio[0]
headphone_buf_R[:] = hp_audio[1]
# Apply same DSP to studio
studio_audio = np.array([studio_buf_L, studio_buf_R])
self.mixer['master'].compressor.process(studio_audio)
self.mixer['master'].limiter.process(studio_audio)
studio_buf_L[:] = studio_audio[0]
studio_buf_R[:] = studio_audio[1]
master_peak = max(np.max(np.abs(master_audio[0])), np.max(np.abs(master_audio[1])))
self.mixer['master'].vu_peak = master_peak