From a6ee957e216cdd3d51723b8137d47b89df652313 Mon Sep 17 00:00:00 2001 From: coon Date: Thu, 2 Oct 2025 19:30:52 +0200 Subject: [PATCH 1/5] mixer.py: make channel debug output one indexed --- mixer.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mixer.py b/mixer.py index 981edf9..08dd4ca 100755 --- a/mixer.py +++ b/mixer.py @@ -93,7 +93,7 @@ class Mixer: sysex_header = response.data[:8] - midi_channel = int(response.data[7]) + midi_channel = int(response.data[7]) + 1 id = int(response.data[8]) i_pad_flag = int(response.data[9]) major_ver = int(response.data[10]) From 35bf8a492884890d49d6ff7d364b67cf8b931a0c Mon Sep 17 00:00:00 2001 From: coon Date: Thu, 2 Oct 2025 19:56:34 +0200 Subject: [PATCH 2/5] mixer.py: get_system_state: return data in a struct instead of printing them --- mixer.py | 77 ++++++++++++++++++++++++++++++++++---------------------- 1 file changed, 47 insertions(+), 30 deletions(-) diff --git a/mixer.py b/mixer.py index 08dd4ca..1a581e7 100755 --- a/mixer.py +++ b/mixer.py @@ -5,12 +5,40 @@ import socket import mido from enum import Enum +from dataclasses import dataclass # For Allen & Heath Qu MIDI protocol documentation see: # https://www.allen-heath.com/content/uploads/2023/06/Qu_MIDI_Protocol_V1.9.pdf class Mixer: + @dataclass + class SystemState: + class QuModel(Enum): + QU16 = 1 + QU24 = 2 + QU32 = 3 + QUPAC = 4 + QUSB = 5 + + def __str__(self): + labels = { + 1: "Qu-16", + 2: "Qu-24", + 3: "Qu-32", + 4: "Qu-Pac", + 5: "Qu-SB", + } + + return labels[self.value] + + sys_ex_header : list + midi_channel : int + id : int + model : QuModel + major_ver : int + minor_ver : int + def __init__(self, ip): self.MIXER_PORT = 51325 self.sock = socket.create_connection((ip, self.MIXER_PORT)) @@ -73,37 +101,18 @@ class Mixer: response = self.recv_sys_ex(self.SysExMessageId.GET_SYSTEM_STATE_RESPONSE) - class QuModel(Enum): - QU16 = 1 - QU24 = 2 - QU32 = 3 - QUPAC = 4 - QUSB = 5 - - def __str__(self): - labels = { - 1: "Qu-16", - 2: "Qu-24", - 3: "Qu-32", - 4: "Qu-Pac", - 5: "Qu-SB", - } - - return labels[self.value] - - sysex_header = response.data[:8] - - midi_channel = int(response.data[7]) + 1 - id = int(response.data[8]) i_pad_flag = int(response.data[9]) - major_ver = int(response.data[10]) - minor_ver = int(response.data[11]) - print(f"sysex_header: {sysex_header}") - print(f"MIDI channel: {midi_channel}") - print(f"ID: 0x{id:02X}") - print(f"Model: {QuModel(i_pad_flag)}") - print(f"Firmware Version: {major_ver}.{minor_ver}") + state = self.SystemState( + sys_ex_header=response.data[:8], + midi_channel=int(response.data[7]), + id=int(response.data[8]), + model=self.SystemState.QuModel(i_pad_flag), + major_ver=int(response.data[10]), + minor_ver = int(response.data[11]) + ) + + return state def get_name_from_qu(self, channel_no, name_to_set): msg_id = self.SysExMessageId.GET_NAME_FROM_QU_REQUEST.value @@ -206,7 +215,15 @@ def main(): match args.command: case "get_name_from_qu": mixer.get_name_from_qu(args.channel_id, args.name) - case "get_system_state": mixer.get_system_state() + case "get_system_state": + state = mixer.get_system_state() + + print(f"sysex_header: {state.sys_ex_header}") + print(f"MIDI channel: {state.midi_channel}") + print(f"ID: 0x{state.id:02X}") + print(f"Model: {state.model}") + print(f"Firmware Version: {state.major_ver}.{state.minor_ver}") + case "shutdown": mixer.shutdown() case "scene_recall": mixer.scene_recall(args.scene_number) case "scene_recall_default": mixer.scene_recall(0) From 9f33bf3d8fb794b8d7b094793a83a72ca4a06528 Mon Sep 17 00:00:00 2001 From: coon Date: Thu, 2 Oct 2025 21:05:59 +0200 Subject: [PATCH 3/5] mixer.py: set iPad flag = 0 (no need for periodic active_sense byte to keep TCP connection alive) --- mixer.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mixer.py b/mixer.py index 1a581e7..7583495 100755 --- a/mixer.py +++ b/mixer.py @@ -89,7 +89,7 @@ class Mixer: def get_system_state(self): msg_id = self.SysExMessageId.GET_SYSTEM_STATE_REQUEST.value - i_pad_flag = 0x01 + i_pad_flag = 0x00 data = self.SYSEX_ALL_CALL + [msg_id, i_pad_flag] From efd6281ebcdce81ce2d3997f29832f2a57829111 Mon Sep 17 00:00:00 2001 From: coon Date: Thu, 2 Oct 2025 21:41:41 +0200 Subject: [PATCH 4/5] mixer.py: replace Mixer.set_bank_1() by Mixer.set_bank(bank_no) --- mixer.py | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/mixer.py b/mixer.py index 7583495..3aeefcd 100755 --- a/mixer.py +++ b/mixer.py @@ -155,11 +155,17 @@ class Mixer: def shutdown(self): self.nrpn_parameter_control(midi_ch=0, mixer_ch=0, id=0x5F, va=0x00, vx=0x00) - def set_bank_1(self): - msb = mido.Message("control_change", channel=0, control=0x00, value=0x00) - lsb = mido.Message("control_change", channel=0, control=0x20, value=0x00) + def set_bank(self, bank_no): + if bank_no < 1 or bank_no > 16384: + raise ValueError(f"bank_no param must be between 0 and 16384, got {bank_no}") - msg_bytes = bytes(msb.bytes() + lsb.bytes()) + msb_val = ((bank_no - 1) >> 7) & 0xFF + lsb_val = (bank_no - 1) & 0x7F + + control_change_msb = mido.Message("control_change", channel=0, control=0x00, value=msb_val) + control_change_lsb = mido.Message("control_change", channel=0, control=0x20, value=lsb_val) + + msg_bytes = bytes(control_change_msb.bytes() + control_change_lsb.bytes()) print(" ".join(f"{b:02X}" for b in msg_bytes)) self.sock.sendall(msg_bytes) @@ -167,7 +173,7 @@ class Mixer: def scene_recall(self, scene_id): print(f"scene_recall: scene_id={scene_id}") - self.set_bank_1() + self.set_bank(1) msb = mido.Message("program_change", channel=0, program=scene_id) msg_bytes = bytes(msb.bytes()) From b17b26a326d0c93759faffb58fa6201103a9f125 Mon Sep 17 00:00:00 2001 From: coon Date: Thu, 2 Oct 2025 21:53:59 +0200 Subject: [PATCH 5/5] mixer.py: get and use qu + daw midi channel on connect --- mixer.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/mixer.py b/mixer.py index 3aeefcd..e8c36d5 100755 --- a/mixer.py +++ b/mixer.py @@ -43,6 +43,8 @@ class Mixer: self.MIXER_PORT = 51325 self.sock = socket.create_connection((ip, self.MIXER_PORT)) self.mido_parser = mido.Parser() + self.qu_midi_channel = self.get_system_state().midi_channel + self.daw_midi_channel = self.qu_midi_channel + 1 ALLEN_HEATH_ID = [0x00, 0x00, 0x1A] QU_MIXER = [0x50, 0x11] @@ -162,8 +164,8 @@ class Mixer: msb_val = ((bank_no - 1) >> 7) & 0xFF lsb_val = (bank_no - 1) & 0x7F - control_change_msb = mido.Message("control_change", channel=0, control=0x00, value=msb_val) - control_change_lsb = mido.Message("control_change", channel=0, control=0x20, value=lsb_val) + control_change_msb = mido.Message("control_change", channel=self.qu_midi_channel, control=0x00, value=msb_val) + control_change_lsb = mido.Message("control_change", channel=self.qu_midi_channel, control=0x20, value=lsb_val) msg_bytes = bytes(control_change_msb.bytes() + control_change_lsb.bytes()) @@ -174,7 +176,7 @@ class Mixer: print(f"scene_recall: scene_id={scene_id}") self.set_bank(1) - msb = mido.Message("program_change", channel=0, program=scene_id) + msb = mido.Message("program_change", channel=self.qu_midi_channel, program=scene_id) msg_bytes = bytes(msb.bytes())