Fix AttributeError: remove redundant mapping_learned connection in main.py

TopBarWidget already connects mapping_learned to _on_mapping_learned internally
in set_midi_engine, so the external connection in main.py was both redundant
and referenced a non-existent method.
This commit is contained in:
2026-05-13 16:47:02 +10:00
parent d2240c61c3
commit 7de0cc3de0
8 changed files with 1117 additions and 533 deletions
+72 -15
View File
@@ -143,6 +143,14 @@ class DeckState:
return f"-{int(remaining//60):02d}:{int(remaining%60):02d}"
class ExternalInputMonitor:
def __init__(self, buffer_size=600):
self.buffer = np.zeros(buffer_size, dtype=np.float32)
self.write_pos = 0
self.buffer_size = buffer_size
self.monitoring = False
class MixerChannel:
def __init__(self, name: str, index: int):
self.name = name
@@ -150,14 +158,10 @@ class MixerChannel:
self.volume = 1.0
self.muted = False
self.solo = False
self.pfl = False
self.vu_peak = 0.0
self.in_port_L = None
self.in_port_R = None
self.out_port_L = None
self.out_port_R = None
def reset_vu(self):
self.vu_peak = 0.0
class Compressor:
@@ -169,7 +173,6 @@ class Compressor:
self.release_ms = 100.0
self.makeup_gain_db = 0.0
self.bypassed = False
self._envelope = 0.0
self._attack_coeff = self._time_constant(self.attack_ms)
self._release_coeff = self._time_constant(self.release_ms)
@@ -226,16 +229,13 @@ class MasterBus:
self.vu_peak = 0.0
self.lufs_current = -70.0
def reset_vu(self):
self.vu_peak = 0.0
class AudioEngine:
def __init__(self):
self.client = jack.Client("RadioPanel")
self.sr = self.client.samplerate
# Output ports for decks (standalone, not mixed into master)
# Output ports for digital decks (standalone, not mixed into master)
self.ports = {
'deck1': (
self.client.outports.register("deck1_L"),
@@ -251,6 +251,17 @@ class AudioEngine:
),
}
# External input monitors (deck3, deck4 — pass-through with waveform)
self.ext_monitors = {}
self.ext_ports = {}
for ext_key, label in [('deck3', 'deck3'), ('deck4', 'deck4')]:
in_L = self.client.inports.register(f"{label}_in_L")
in_R = self.client.inports.register(f"{label}_in_R")
out_L = self.client.outports.register(f"{label}_out_L")
out_R = self.client.outports.register(f"{label}_out_R")
self.ext_ports[ext_key] = (in_L, in_R, out_L, out_R)
self.ext_monitors[ext_key] = ExternalInputMonitor()
# Mixer channels: 8 stereo input ports
self.mixer = {
'channels': [],
@@ -268,7 +279,13 @@ class AudioEngine:
self.client.outports.register("master_out_R"),
)
# Deck states (unchanged)
# PFL output ports
self.pfl_out = (
self.client.outports.register("pfl_out_L"),
self.client.outports.register("pfl_out_R"),
)
# Deck states (for digital decks and carts)
self.decks = {
'deck1': DeckState(),
'deck2': DeckState(),
@@ -300,6 +317,10 @@ class AudioEngine:
if 0 <= index < NUM_MIXER_CHANNELS:
self.mixer['channels'][index].solo = solo
def set_channel_pfl(self, index: int, pfl: bool):
if 0 <= index < NUM_MIXER_CHANNELS:
self.mixer['channels'][index].pfl = pfl
def _fill_port(self, port_L, port_R, deck: DeckState, frames: int):
buf_L = port_L.get_array()
buf_R = port_R.get_array()
@@ -327,7 +348,7 @@ class AudioEngine:
deck.position = track.num_samples - 1
def _process(self, frames: int):
# ── Decks (unchanged) ──────────────────────────────────────────
# ── Digital decks ───────────────────────────────────────────────
self._fill_port(*self.ports['deck1'], self.decks['deck1'], frames)
self._fill_port(*self.ports['deck2'], self.decks['deck2'], frames)
@@ -355,6 +376,34 @@ class AudioEngine:
deck.playing = False
deck.position = track.num_samples - 1
# ── External input monitors (deck3, deck4) ─────────────────────
gate_linear = 10 ** (-46.0 / 20.0)
for ext_key in ('deck3', 'deck4'):
in_L, in_R, out_L, out_R = self.ext_ports[ext_key]
in_buf_L = in_L.get_array()
in_buf_R = in_R.get_array()
out_buf_L = out_L.get_array()
out_buf_R = out_R.get_array()
out_buf_L[:] = in_buf_L[:]
out_buf_R[:] = in_buf_R[:]
monitor = self.ext_monitors[ext_key]
if not monitor.monitoring:
monitor.buffer[:] = 0.0
continue
rms = np.sqrt(np.mean((in_buf_L ** 2 + in_buf_R ** 2) / 2.0))
if rms > gate_linear:
db = 20 * np.log10(max(rms, 1e-10))
display = max(0.0, (db + 46.0) / 46.0)
display = min(1.0, display)
else:
display = 0.0
monitor.buffer[monitor.write_pos] = display
monitor.write_pos = (monitor.write_pos + 1) % monitor.buffer_size
# ── Mixer ───────────────────────────────────────────────────────
master_buf_L = self.master_out[0].get_array()
master_buf_R = self.master_out[1].get_array()
@@ -363,12 +412,23 @@ class AudioEngine:
any_solo = any(ch.solo for ch in self.mixer['channels'])
# PFL bus
pfl_buf_L = self.pfl_out[0].get_array()
pfl_buf_R = self.pfl_out[1].get_array()
pfl_buf_L.fill(0.0)
pfl_buf_R.fill(0.0)
for ch in self.mixer['channels']:
in_L = ch.in_port_L.get_array()
in_R = ch.in_port_R.get_array()
mute = ch.muted or (any_solo and not ch.solo)
# PFL: pre-fader, pre-mute signal to PFL bus
if ch.pfl:
pfl_buf_L[:] += in_L[:]
pfl_buf_R[:] += in_R[:]
if mute:
ch.vu_peak = 0.0
continue
@@ -390,15 +450,12 @@ class AudioEngine:
self.mixer['master'].compressor.process(master_audio)
self.mixer['master'].limiter.process(master_audio)
# Copy processed audio back to port buffers
master_buf_L[:] = master_audio[0]
master_buf_R[:] = master_audio[1]
# Master VU
master_peak = max(np.max(np.abs(master_audio[0])), np.max(np.abs(master_audio[1])))
self.mixer['master'].vu_peak = master_peak
# LUFS calculation (every ~512 frames)
if frames % 512 < frames:
self._calculate_lufs(master_audio)