diff --git a/CQ-20B.org b/CQ-20B.org new file mode 100644 index 0000000..98d7515 --- /dev/null +++ b/CQ-20B.org @@ -0,0 +1,48 @@ +# -*- mode: org; coding: utf-8; -*- +#+LaTeX_CLASS: zzkt-article +#+LateX_Header: \setcounter{secnumdepth}{0} +#+OPTIONS: toc:2 num:nil html-style:nil +#+author: +#+title: Allen & Heath CQ-20B + +based on MIDI Protocol V1.2 Issue 2 + +[[file:img/CQ-20B.png]] + +The CQ uses MIDI Channel 1 for all control messaging. + +* Available Controls +** Scene change +** Soft Keys +** Mutes +** Levels +** Panning/Balance +** Getting values +* Reference Tables + +** Decimal, HEX, Note conversions + +[[file:img/cq_ref_1.png]] + +** Soft Key Note and Hex values + +[[file:img/cq_ref_2.png]] + +** Mutes Parameter Numbers + +[[file:img/cq_ref_3.png]] + +** Level Parameter Numbers — Inputs and FX to Outputs and FX + +[[file:img/cq_ref_4.png]] + +** Level Parameter Numbers — Outputs, FX unit input and DCAs + +[[file:img/cq_ref_5.png]] + +** Pan Balance Parameter Numbers — Inputs and FX to Main LR and Outputs + +[[file:img/cq_ref_6.png]] + +* Further +- CQ MIDI Protocol diff --git a/README.org b/README.org new file mode 100644 index 0000000..3d7ac5f --- /dev/null +++ b/README.org @@ -0,0 +1,14 @@ +# -*- mode: org; coding: utf-8; -*- +#+title: OSC bridge(s) for A&H Equipment + +* XONE:K2 +“Xone:K2 is a compact, slim line universal MIDI controller – incorporating a 4 channel soundcard, for use with any DJ software.” +- MIDI controller +- output OSC +- https://www.allen-heath.com/hardware/xone-series/xonek2/ + +* CQ-20B +“Ultra-Compact 20-in / 8-out Digital Mixer with Wi-Fi” +- Digital mixer/stagebox +- config, control & monitoring via OSC +- https://www.allen-heath.com/hardware/cq/cq-20b/ diff --git a/XONE-K2.org b/XONE-K2.org new file mode 100644 index 0000000..787e517 --- /dev/null +++ b/XONE-K2.org @@ -0,0 +1,260 @@ +# -*- mode: org; coding: utf-8; -*- +#+LaTeX_CLASS: zzkt-article +#+LateX_Header: \setcounter{secnumdepth}{0} +#+OPTIONS: toc:2 num:nil html-style:nil +#+title: XONE:K2 + +file:img/XONE-K2-squared.jpg + +* install & setup + +requirements +#+BEGIN_SRC shell :dir :wrap SRC text :results raw +python -m pip install oscpy mido python-rtmidi argparse +#+END_SRC + +make it go +#+BEGIN_SRC shell :dir :wrap SRC text :results raw +python k2-misc.py +#+END_SRC + +usage +#+BEGIN_SRC text +usage: k2-misc [-h] [--host OSC-host] [--port OSC-port] [--midi MIDI-port] + [-v verbose] [-q quiet] + +MIDI->OSC bridge for Allen & Heath XONE:K2 controller + +options: + -h, --help show this help message and exit + --host OSC-host hostname or address of OSC destination + --port OSC-port port for OSC messages + --midi MIDI-port port name of MIDI device + -v verbose + -q quiet +#+END_SRC + +defaults +#+BEGIN_SRC shell :dir :wrap SRC text :results raw +python k2-misc.py --host "127.0.0.1" --port 5111 --midi 'XONE:K2' +#+END_SRC + +* Overview + +* Physical layout + + XONE:K2 Publication AP8509 + +** Rotary Encoders + +Turning an encoder produces MIDI CC (continuous controller) messages with a unique controller number in two’s compliment binary encoding These encoders feature a built in momentary push switch. Pressing down on the encoder knob activates the switch and sends a “Note On” MIDI message, releasing the switch sends a corresponding “Note Off” message. The window below the top row of encoders can be used to display the state of the encoder switch in the same manner as the other switches. + +** Rotary Potentiometers + +These controls are standard potentiometers with end stops. Turning a pot from left to right will send MIDI messages with a unique CC number and a control value from 0 to 127. + +** Pot Switches + + Each rotary potentiometer has a switch with tri-colour illumination below it. + +** Linear Faders + +Moving a linear fader will send a MIDI message with a unique CC number and a control value from 0 (bottom) to 127 (top). + +** Switch Matrix + + The switch matrix consists of 16 back-lit tri-colour switches. + +** Layer Button + + The Layer button is completely user assignable but can also function as an embedded layer button. + +* OSC layout & mapping + +Physical controls are numbered L->R and top->down + +[[file:img/K2-layout.png]] +** Rotary potentiometers + +Rotary potentiometers are numbered 1-12 (soft sync/pickup?) and send value 0-127 when turned, and =pressed= / =released= messages are sent when knobs are pressed (and released). + +#+BEGIN_SRC text +/xone/k2/rp/ +/xone/k2/rp//pressed +/xone/k2/rp//released +#+END_SRC + +** Rotary encoders + +Rotary encoders on the top row are numbered 1-4 and the bottom row are numbered 5 and 6. Encoders send =inc= messages when turned right (clockwise) or =dec= when lurned left (widdershins). While turned & pressed, the encoder sends =inc-fine= and =dec-fine= messages. + +#+BEGIN_SRC text +/xone/k2/re//inc +/xone/k2/re//dec +/xone/k2/re//inc-fine +/xone/k2/re//dec-fine +#+END_SRC + +** Linear faders + +Linear faders are numbered 1-4 and send values from 0 (fully down) up to 127 (fully up). +#+BEGIN_SRC text +/xone/k2/fader//value +#+END_SRC + +** Buttons + +The upper block of buttons (above the faders) are numbered from 1-12 and the lower block (grid below the faders) are named =A-P=, =LAYER=, and =SHIFT= as labled) + +#+BEGIN_SRC text +/xone/k2/button//pressed +/xone/k2/button//released +#+END_SRC + +set button colour (not yet implemented) +#+BEGIN_SRC text +/xone/k2/button//set + = red, orange, green, off (string) +#+END_SRC + +* MIDI layout (MIDI IMPLEMENTATION SEND / RETURN) + +By default the MIDI Channel number is set to 15 (14) to prevent control interaction with Xone DB series mixers which default to channel 16 (15). + +[[file:img/XONE-K2-layers.jpg]] + +* MIDI NOTE IMPLEMENTATION TABLE + +| DEC | HEX | NOTE | +| 0 | 00 | C-1 | +| 1 | 01 | C#-1 | +| 2 | 02 | D-1 | +| 3 | 03 | D#-1 | +| 4 | 04 | E-1 | +| 5 | 05 | F-1 | +| 6 | 06 | F#-1 | +| 7 | 07 | G-1 | +| 8 | 08 | G#-1 | +| 9 | 09 | A-1 | +| 10 | 0A | A#-1 | +| 11 | 0B | B-1 | +| 12 | 0C | C0 | +| 13 | 0D | C#0 | +| 14 | 0E | D0 | +| 15 | 0F | D#0 | +| 16 | 10 | E0 | +| 17 | 11 | F0 | +| 18 | 12 | F#0 | +| 19 | 13 | G0 | +| 20 | 14 | G#0 | +| 21 | 15 | A0 | +| 22 | 16 | A#0 | +| 23 | 17 | B0 | +| 24 | 18 | C1 | +| 25 | 19 | C#1 | +| 26 | 1A | D1 | +| 27 | 1B | D#1 | +| 28 | 1C | E1 | +| 29 | 1D | F1 | +| 30 | 1E | F#1 | +| 31 | 1F | G1 | +| 32 | 20 | G#1 | +| 33 | 21 | A1 | +| 34 | 22 | A#1 | +| 35 | 23 | B1 | +| 36 | 24 | C2 | +| 37 | 25 | C#2 | +| 38 | 26 | D2 | +| 39 | 27 | D#2 | +| 40 | 28 | E2 | +| 41 | 29 | F2 | +| 42 | 2A | F#2 | +| 43 | 2B | G2 | +| 44 | 2C | G#2 | +| 45 | 2D | A2 | +| 46 | 2E | A#2 | +| 47 | 2F | B2 | +| 48 | 30 | C3 | +| 49 | 31 | C#3 | +| 50 | 32 | D3 | +| 51 | 33 | D#3 | +| 52 | 34 | E3 | +| 53 | 35 | F3 | +| 54 | 36 | F#3 | +| 55 | 37 | G3 | +| 56 | 38 | G#3 | +| 57 | 39 | A3 | +| 58 | 3A | A#3 | +| 59 | 3B | B3 | +| 60 | 3C | C4 | +| 61 | 3D | C#4 | +| 62 | 3E | D4 | +| 63 | 3F | D#4 | +| 64 | 40 | E4 | +| 65 | 41 | F4 | +| 66 | 42 | F#4 | +| 67 | 43 | G4 | +| 68 | 44 | G#4 | +| 69 | 45 | A4 | +| 70 | 46 | A#4 | +| 71 | 47 | B4 | +| 72 | 48 | C5 | +| 73 | 49 | C#5 | +| 74 | 4A | D5 | +| 75 | 4B | D#5 | +| 76 | 4C | E5 | +| 77 | 4D | F5 | +| 78 | 4E | F#5 | +| 79 | 4F | G5 | +| 80 | 50 | G#5 | +| 81 | 51 | A5 | +| 82 | 52 | A#5 | +| 83 | 53 | B5 | +| 84 | 54 | C6 | +| 85 | 55 | C#6 | +| 86 | 56 | D6 | +| 87 | 57 | D#6 | +| 88 | 58 | E6 | +| 89 | 59 | F6 | +| 90 | 5A | F#6 | +| 91 | 5B | G6 | +| 92 | 5C | G#6 | +| 93 | 5D | A6 | +| 94 | 5E | A#6 | +| 95 | 5F | B6 | +| 96 | 60 | C7 | +| 97 | 61 | C#7 | +| 98 | 62 | D7 | +| 99 | 63 | D#7 | +| 100 | 64 | E7 | +| 101 | 65 | F7 | +| 102 | 66 | F#7 | +| 103 | 67 | G7 | +| 104 | 68 | G#7 | +| 105 | 69 | A7 | +| 106 | 6A | A#7 | +| 107 | 6B | B7 | +| 108 | 6C | C8 | +| 109 | 6D | C#8 | +| 110 | 6E | D8 | +| 111 | 6F | D#8 | +| 112 | 70 | E8 | +| 113 | 71 | F8 | +| 114 | 72 | F#8 | +| 115 | 73 | G8 | +| 116 | 74 | G#8 | +| 117 | 75 | A8 | +| 118 | 76 | A#8 | +| 119 | 77 | B8 | +| 120 | 78 | C9 | +| 121 | 79 | C#9 | +| 122 | 7A | D9 | +| 123 | 7B | D#9 | +| 124 | 7C | E9 | +| 125 | 7D | F9 | +| 126 | 7E | F#9 | +| 127 | 7F | G9 | + +* various +- Allen & Heath [[https://www.allen-heath.com/hardware/xone-series/xonek2/][hardware notes]] & MIDI docs +- see also https://github.com/taw10/x1k2-midi-osc-alsa diff --git a/img/CQ-20B.png b/img/CQ-20B.png new file mode 100644 index 0000000..f481602 Binary files /dev/null and b/img/CQ-20B.png differ diff --git a/img/K2-layout.png b/img/K2-layout.png new file mode 100644 index 0000000..fa81c21 Binary files /dev/null and b/img/K2-layout.png differ diff --git a/img/K2-layout.svg b/img/K2-layout.svg new file mode 100644 index 0000000..607c4a6 --- /dev/null +++ b/img/K2-layout.svg @@ -0,0 +1,1155 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + re + rp + fader + button + re + + + + + A + B + C + D + E + F + G + H + I + J + K + L + M + N + O + P + 1 + 2 + 3 + 4 + button + 1 + 2 + 3 + 4 + 1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9 + 10 + 11 + 12 + 5 + 6 + 7 + 8 + 9 + 10 + 11 + 12 + 5 + 6 + + diff --git a/img/XONE-K2-layers.jpg b/img/XONE-K2-layers.jpg new file mode 100644 index 0000000..49755a2 Binary files /dev/null and b/img/XONE-K2-layers.jpg differ diff --git a/img/XONE-K2-squared.jpg b/img/XONE-K2-squared.jpg new file mode 100644 index 0000000..b4947d8 Binary files /dev/null and b/img/XONE-K2-squared.jpg differ diff --git a/img/cq_ref_1.png b/img/cq_ref_1.png new file mode 100644 index 0000000..b01c8e2 Binary files /dev/null and b/img/cq_ref_1.png differ diff --git a/img/cq_ref_2.png b/img/cq_ref_2.png new file mode 100644 index 0000000..0e08280 Binary files /dev/null and b/img/cq_ref_2.png differ diff --git a/img/cq_ref_3.png b/img/cq_ref_3.png new file mode 100644 index 0000000..b6ef8d1 Binary files /dev/null and b/img/cq_ref_3.png differ diff --git a/img/cq_ref_4.png b/img/cq_ref_4.png new file mode 100644 index 0000000..9acb877 Binary files /dev/null and b/img/cq_ref_4.png differ diff --git a/img/cq_ref_5.png b/img/cq_ref_5.png new file mode 100644 index 0000000..d662b81 Binary files /dev/null and b/img/cq_ref_5.png differ diff --git a/img/cq_ref_6.png b/img/cq_ref_6.png new file mode 100644 index 0000000..1e9fb2b Binary files /dev/null and b/img/cq_ref_6.png differ diff --git a/k2-misc.py b/k2-misc.py new file mode 100644 index 0000000..949b274 --- /dev/null +++ b/k2-misc.py @@ -0,0 +1,166 @@ +#!/usr/bin/env python + +# File name: K2-misc.py +# Description: MIDI->OSC bridge for Allen & Heath XONE:K2 controller +# Author: nik gaffney +# Created: 2024-09-10 +# SPDX-License-Identifier: GPL-3.0-or-later + +import argparse +import mido +import mido.backends.rtmidi +from oscpy.client import OSCClient + + +def parse_arguments(): + parser = argparse.ArgumentParser( + prog='k2osc', + description='MIDI->OSC bridge for Allen & Heath XONE:K2 controller') + parser.add_argument("--host", metavar='OSC-host', required=False, + help='hostname or address of OSC destination', + dest='osc_host', default='127.0.0.1') + parser.add_argument("--port", metavar='OSC-port', required=False, + help='port for OSC messages', + dest='osc_port', default=5111) + parser.add_argument("--midi", metavar='MIDI-port', + required=False, help='port name of MIDI device', + dest='midi_port', default='XONE:K2') + parser.add_argument("-v", metavar='verbose', + required=False, dest='verbose') + parser.add_argument("-q", metavar='quiet', + required=False, dest='quiet') + args = parser.parse_args() + return args + + +# XONE:K2 controller mapping + +# valid types of controllers +# rp - rotary pots from 0-127 +# re - rotary encoders (inc/dec) +# fader - linear faders 0-127 +# button - pressed/released (MIDI notes) +CONTROLLERS = ["rp", "re", "fader", "button"] + +# controller types and corresponding MIDI control_id +rotary_encoder_channels = [0, 1, 2, 3, 20, 21] +rotary_potentiometer_channels = [4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15] +fader_channels = [16, 17, 18, 19] + + +# map MIDI control_id to controller number +def normalise_control_id(id): + id_map = [1, 2, 3, 4, 1, + 2, 3, 4, 5, 6, + 7, 8, 9, 10, 11, + 12, 1, 2, 3, 4, + 5, 6, 0, 0, 0,] + return id_map[id] + + +# map MIDI note to button number (NOTE: no latching layers or LEDs yet) +def normalise_button_id(id): + id_map = { + # upper block + 48: 1, 49: 2, 50: 3, 51: 4, + 44: 5, 45: 6, 46: 7, 47: 8, + 40: 9, 41: 10, 42: 11, 43: 12, + # lower block + 36: "A", 37: "B", 38: "C", 39: "D", + 32: "E", 33: "F", 34: "G", 35: "H", + 28: "I", 29: "J", 30: "K", 31: "L", + 24: "M", 25: "N", 26: "O", 27: "P", + # square buttons + 12: "LAYER", 15: "EXIT"} + return id_map[id] + + +# predicates for mapped controllers +def is_rp(id): + return id in rotary_potentiometer_channels + + +def is_re(id): + return id in rotary_encoder_channels + + +def is_fader(id): + return id in fader_channels + + +def is_button(note): + return True + + +# MIDI in OSC out +def parse_midi_message(msg): + msg_type = msg.type + print(f"\nrecv message of type '{msg_type}': {msg}") + if msg_type == 'control_change': + control_id, value = msg.control, msg.value + id = normalise_control_id(control_id) + if is_rp(control_id): + mutaliate("rp", id, value) + elif is_re(control_id): + mutaliate("re", id, "inc" if (value == 1) else "dec") + elif is_fader(control_id): + mutaliate("fader", id, value) + if msg_type in ['note_on', 'note_off']: + note = msg.note + if is_button(note): + id = normalise_button_id(note) + mutaliate("button", id, + "pressed" if (msg_type == 'note_on') else "released") + elif is_re(note): + print("increase resolution of encoder when pressed...") + # print(f"note: {note}") + print(f"unrecognised: {msg}") + + +# OSC interslonk +# see also -> https://github.com/kivy/oscpy + +def osc_setup(address="127.0.0.1", port=5111): + osc = OSCClient(address, port) + print(f"OSC client active. Sending to {address} on port {port}") + return osc + + +# send some OSC messages etc+ +def mutaliate(control, control_id, value=""): + global osc + # print(f"mutaliate: {control}, {control_id}, {arg}, {value}") + if control in CONTROLLERS: + path = f"/xone/k2/{control}/{control_id}" + print(f"osc: {path} {value}") + osc.send_message(path, value) + return True + + +def looper(): + loop = 0 + while True: + loop += 1 + return True + + +# setup MIDI ports +# send_port = mido.open_output('send-to-K2') + +def midi_setup(label): + mido.open_input(label, callback=parse_midi_message) + port = mido.open_output(label) + return port + + +def main(): + global osc, k2_send_port + args = parse_arguments() + osc = osc_setup(args.osc_host, args.osc_port) + # midi = midi_setup('XONE:K2') + # event loop + looper() + + +if __name__ == '__main__': + main()