Compare commits

...

5 commits

View file

@ -5,16 +5,46 @@ import socket
import mido import mido
from enum import Enum from enum import Enum
from dataclasses import dataclass
# For Allen & Heath Qu MIDI protocol documentation see: # For Allen & Heath Qu MIDI protocol documentation see:
# https://www.allen-heath.com/content/uploads/2023/06/Qu_MIDI_Protocol_V1.9.pdf # https://www.allen-heath.com/content/uploads/2023/06/Qu_MIDI_Protocol_V1.9.pdf
class Mixer: 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): def __init__(self, ip):
self.MIXER_PORT = 51325 self.MIXER_PORT = 51325
self.sock = socket.create_connection((ip, self.MIXER_PORT)) self.sock = socket.create_connection((ip, self.MIXER_PORT))
self.mido_parser = mido.Parser() 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] ALLEN_HEATH_ID = [0x00, 0x00, 0x1A]
QU_MIXER = [0x50, 0x11] QU_MIXER = [0x50, 0x11]
@ -61,7 +91,7 @@ class Mixer:
def get_system_state(self): def get_system_state(self):
msg_id = self.SysExMessageId.GET_SYSTEM_STATE_REQUEST.value 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] data = self.SYSEX_ALL_CALL + [msg_id, i_pad_flag]
@ -73,37 +103,18 @@ class Mixer:
response = self.recv_sys_ex(self.SysExMessageId.GET_SYSTEM_STATE_RESPONSE) 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])
id = int(response.data[8])
i_pad_flag = int(response.data[9]) 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}") state = self.SystemState(
print(f"MIDI channel: {midi_channel}") sys_ex_header=response.data[:8],
print(f"ID: 0x{id:02X}") midi_channel=int(response.data[7]),
print(f"Model: {QuModel(i_pad_flag)}") id=int(response.data[8]),
print(f"Firmware Version: {major_ver}.{minor_ver}") 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): def get_name_from_qu(self, channel_no, name_to_set):
msg_id = self.SysExMessageId.GET_NAME_FROM_QU_REQUEST.value msg_id = self.SysExMessageId.GET_NAME_FROM_QU_REQUEST.value
@ -146,11 +157,17 @@ class Mixer:
def shutdown(self): def shutdown(self):
self.nrpn_parameter_control(midi_ch=0, mixer_ch=0, id=0x5F, va=0x00, vx=0x00) self.nrpn_parameter_control(midi_ch=0, mixer_ch=0, id=0x5F, va=0x00, vx=0x00)
def set_bank_1(self): def set_bank(self, bank_no):
msb = mido.Message("control_change", channel=0, control=0x00, value=0x00) if bank_no < 1 or bank_no > 16384:
lsb = mido.Message("control_change", channel=0, control=0x20, value=0x00) 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=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())
print(" ".join(f"{b:02X}" for b in msg_bytes)) print(" ".join(f"{b:02X}" for b in msg_bytes))
self.sock.sendall(msg_bytes) self.sock.sendall(msg_bytes)
@ -158,8 +175,8 @@ class Mixer:
def scene_recall(self, scene_id): def scene_recall(self, scene_id):
print(f"scene_recall: scene_id={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) msb = mido.Message("program_change", channel=self.qu_midi_channel, program=scene_id)
msg_bytes = bytes(msb.bytes()) msg_bytes = bytes(msb.bytes())
@ -206,7 +223,15 @@ def main():
match args.command: match args.command:
case "get_name_from_qu": mixer.get_name_from_qu(args.channel_id, args.name) 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 "shutdown": mixer.shutdown()
case "scene_recall": mixer.scene_recall(args.scene_number) case "scene_recall": mixer.scene_recall(args.scene_number)
case "scene_recall_default": mixer.scene_recall(0) case "scene_recall_default": mixer.scene_recall(0)