summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorThomas White <taw@physics.org>2023-04-22 22:03:19 +0200
committerThomas White <taw@physics.org>2023-04-23 09:01:10 +0200
commiteefca3c5bcb7faa8bbb402e982f040d4ac8ea91f (patch)
treec56e6cb0005801ab92701635206762b0465c985b
Initial import
-rw-r--r--README.md46
-rw-r--r--meson.build13
-rw-r--r--x1k2-midi-osc.c227
3 files changed, 286 insertions, 0 deletions
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..d43c062
--- /dev/null
+++ b/README.md
@@ -0,0 +1,46 @@
+Open Sound Control interface for Allen & Heath Xone:K2
+======================================================
+
+This small program implements OSC methods for an [A&H
+Xone:K2](https://www.allen-heath.com/ahproducts/xonek2/) MIDI control surface.
+
+The program is not intended to be a general solution for MIDI<-->OSC
+communication. This implements things the way I use them. If you want
+something else, I gleefully encourage you to fork and modify the code.
+
+
+Installation
+------------
+
+You will need the development files for Guile and liblo installed, as well as
+[Meson](https://mesonbuild.com/). Then:
+
+```
+meson setup build
+ninja -C build
+```
+
+Run with `build/x1k2-midi-osc` (no arguments) or install with `meson install build`.
+
+
+OSC methods - receive
+---------------------
+
+* `/x1k2/leds/<n> <colour>`
+ `<n>` = 1..32,101,102 (int)
+ `<colour>` = red,orange,green,off (string)
+ Turn LEDs on/off. Numbering is from top left (1) to bottom right (32). Top
+ right is number 4. The two large buttons at the bottom are 101 (left) and
+ 102 (right).
+
+
+License
+-------
+
+GPL3+
+
+
+Note
+----
+
+This project is not supported by, or in any way affiliated with, Allen&Heath (duh).
diff --git a/meson.build b/meson.build
new file mode 100644
index 0000000..98a2788
--- /dev/null
+++ b/meson.build
@@ -0,0 +1,13 @@
+project('x1k2-midi-osc', ['c'],
+ version: '0.1.0',
+ license: 'GPL3+',
+ default_options: ['buildtype=debugoptimized'])
+
+# Dependencies
+lo_dep = dependency('liblo', required: true)
+alsa_dep = dependency('alsa', required: true)
+
+executable('x1k2-midi-osc',
+ ['x1k2-midi-osc.c'],
+ dependencies: [lo_dep, alsa_dep],
+ install: true)
diff --git a/x1k2-midi-osc.c b/x1k2-midi-osc.c
new file mode 100644
index 0000000..1cbdda0
--- /dev/null
+++ b/x1k2-midi-osc.c
@@ -0,0 +1,227 @@
+/*
+ * x1k2-midi-osc.c
+ *
+ * (c) 2023 Thomas White <taw@bitwiz.me.uk>
+ *
+ * X1K2-midi-osc is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * X1K2-midi-osc is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with X1K2-midi-osc. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <getopt.h>
+
+#include <alsa/asoundlib.h>
+#include <lo/lo.h>
+
+
+static void show_help(const char *s)
+{
+ printf("Syntax: %s [-h] [-d /dev/snd/midiXXXX]\n\n", s);
+ printf("MIDI to OSC interface for A&H Xone:K2\n"
+ "\n"
+ " -h, --help Display this help message.\n"
+ " -d, --device <dev> MIDI device name.\n");
+}
+
+
+static void error_callback(int num, const char *msg, const char *path)
+{
+ fprintf(stderr, "liblo error %i (%s) for path %s\n", num, msg, path);
+}
+
+
+static void send_note_on(snd_rawmidi_t *midi_out, int note)
+{
+ unsigned char sbuf[3];
+ ssize_t r;
+ sbuf[0] = 0x9e;
+ sbuf[1] = note;
+ sbuf[2] = 127;
+ r = snd_rawmidi_write(midi_out, sbuf, 3);
+ if ( r != 3 ) {
+ printf("snd_rawmidi_write said %li\n", r);
+ }
+ snd_rawmidi_drain(midi_out);
+}
+
+
+static void send_note_off(snd_rawmidi_t *midi_out, int note)
+{
+ unsigned char sbuf[3];
+ ssize_t r;
+ sbuf[0] = 0x8e;
+ sbuf[1] = note;
+ sbuf[2] = 0;
+ r = snd_rawmidi_write(midi_out, sbuf, 3);
+ if ( r != 3 ) {
+ printf("snd_rawmidi_write said %li\n", r);
+ }
+ snd_rawmidi_drain(midi_out);
+}
+
+
+struct led_callback_data
+{
+ int red;
+ int orange;
+ int green;
+ snd_rawmidi_t *midi_out;
+};
+
+
+static int led_handler(const char *path, const char *types, lo_arg **argv,
+ int argc, lo_message msg, void *vp)
+{
+ struct led_callback_data *cb = vp;
+ if ( strcmp("red", &argv[0]->s) == 0 ) {
+ send_note_on(cb->midi_out, cb->red);
+ } else if ( strcmp("orange", &argv[0]->s) == 0 ) {
+ send_note_on(cb->midi_out, cb->orange);
+ } else if ( strcmp("green", &argv[0]->s) == 0 ) {
+ send_note_on(cb->midi_out, cb->green);
+ } else if ( strcmp("off", &argv[0]->s) == 0 ) {
+
+ /* Usually, turning off any one of the colours turns off the
+ * LED, regardless of the current colour. However, the bottom
+ * left button's LED is weird, I think because it's
+ * also the "layer" button. It can only be switched off
+ * from the same colour. So, we force it to be red. */
+ if ( cb->red == 12 ) {
+ send_note_on(cb->midi_out, cb->red);
+ }
+ send_note_off(cb->midi_out, cb->red);
+ } else {
+ fprintf(stderr, "Unrecognised LED mode '%s'\n", &argv[0]->s);
+ }
+
+ return 1;
+}
+
+
+static void add_led(lo_server_thread osc_server, snd_rawmidi_t *midi_out,
+ int led, int red, int orange, int green)
+{
+ char tmp[256];
+ struct led_callback_data *cb;
+
+ cb = malloc(sizeof(struct led_callback_data));
+ if ( cb == NULL ) return;
+
+ cb->midi_out = midi_out;
+ cb->red = red;
+ cb->orange = orange;
+ cb->green = green;
+
+ snprintf(tmp, 255, "/x1k2/leds/%i", led);
+ lo_server_thread_add_method(osc_server, tmp, "s", led_handler, cb);
+}
+
+
+static int flip_position(int i)
+{
+ int x = i % 4;
+ int y = i / 4;
+ y = 7-y;
+ return 1+y*4+x;
+}
+
+
+int main(int argc, char *argv[])
+{
+ int c, r, i;
+ char *dev = NULL;
+ snd_rawmidi_t *midi_in;
+ snd_rawmidi_t *midi_out;
+ lo_server_thread osc_server;
+
+ /* Long options */
+ const struct option longopts[] = {
+ {"help", 0, NULL, 'h'},
+ {"device", 1, NULL, 'd'},
+ {0, 0, NULL, 0}
+ };
+
+ /* Short options */
+ while ((c = getopt_long(argc, argv, "hd:",
+ longopts, NULL)) != -1) {
+
+ switch (c) {
+
+ case 'h' :
+ show_help(argv[0]);
+ return 0;
+
+ case 'd':
+ dev = strdup(optarg);
+ break;
+
+ case 0 :
+ break;
+
+ default :
+ return 1;
+
+ }
+
+ }
+
+ if ( dev == NULL ) {
+
+ /* FIXME: Enumerate and select device */
+ dev = strdup("hw:1");
+
+ printf("Found MIDI device: %s\n", dev);
+
+ }
+
+ r = snd_rawmidi_open(&midi_in, &midi_out, dev, SND_RAWMIDI_SYNC);
+ if ( r ) {
+ fprintf(stderr, "Couldn't open MIDI device: %i\n", r);
+ return 1;
+ }
+
+ osc_server = lo_server_thread_new("7771", error_callback);
+ lo_server_thread_start(osc_server);
+
+ for ( i=0; i<32; i++ ) {
+ add_led(osc_server, midi_out, flip_position(i),
+ i+24, i+60, i+96);
+ }
+ add_led(osc_server, midi_out, 101, 12, 16, 20);
+ add_led(osc_server, midi_out, 102, 15, 19, 23);
+
+ do {
+
+ ssize_t r;
+ char buf[1024];
+
+ r = snd_rawmidi_read(midi_in, buf, 1024);
+ if ( r > 0 ) {
+ printf("got %li\n", r);
+ }
+ usleep(1);
+
+ } while ( 1 );
+
+ snd_rawmidi_drain(midi_in);
+ snd_rawmidi_close(midi_in);
+
+ snd_rawmidi_drain(midi_out);
+ snd_rawmidi_close(midi_out);
+
+ return 0;
+}