import math from typing import Optional, List import mido PREFIX = [0x00, 0x20, 0x0D, 0x10] SUBID_IN_STATUS = 0 SUBID_IN_RMS = 1 SUBID_IN_PEAK = 2 SUBID_OPTIONS = 0 SUBID_MIXER = 1 SUBID_LOOPBACK = 2 SUBID_INPUT_OUTS = 3 SUBID_GAIN = 4 def decode_message(msg: mido.Message) -> tuple[Optional[int], Optional[List[int]]]: msg_bytes = msg.bytes() if msg_bytes[:5] != [0xF0] + PREFIX: return None, None # noop result_words = [] try: chunks = zip(*[iter(msg_bytes[6:-1])] * 5, strict=True) for packed in chunks: value = packed[4] << 28 | packed[3] << 21 | packed[2] << 14 | packed[1] << 7 | packed[0] result_words.append(value) except ValueError: pass # invalid message return msg_bytes[5], result_words def encode_message(subid: int, payload: List[int]) -> mido.Message: data = [subid] for word in payload: data += [ word & 0x7F, word >> 7 & 0x7F, word >> 14 & 0x7F, word >> 21 & 0x7F, word >> 28 & 0x7F, ] return mido.Message('sysex', data=PREFIX + data) def hex_format(msg, width=2): return ' '.join([("{x:0" + str(width) + "x}").format(x=x) for x in msg]) def bin_format(msg, width=32): return ' '.join([("{x:0" + str(width) + "b}").format(x=x) for x in msg]) def get_bitfield(word, position, length): return (word >> position) & ((1 << length) - 1) def set_bitfield(word, val, position, length): mask = ((1 << length) - 1) val = (val & mask) << position return (word & mask << position) | val """ Special level calculation for main/ph outputs """ def level_to_db(val): return 6 + (val - 0xFF) / 2 """ Special level calculation for main/ph outputs """ def db_to_level(val): return int((val - 6) * 2) + 0xFF def db_to_fader(db): return int(10 ** (db / 20) * 0x40000) def format_db(val, min_db=-120): if math.isnan(val) or val <= min_db: return '∞' else: return format(val, '.1f') def map_range(val, min_, max_, low, high): return ((clamp(val, min_, max_) - min_) / (max_ - min_)) * (high - low) + low def clamp(val, low, high): return max(min(val, high), low)