diff options
author | Thomas White <taw@physics.org> | 2023-04-22 22:03:19 +0200 |
---|---|---|
committer | Thomas White <taw@physics.org> | 2023-04-23 09:01:10 +0200 |
commit | eefca3c5bcb7faa8bbb402e982f040d4ac8ea91f (patch) | |
tree | c56e6cb0005801ab92701635206762b0465c985b |
Initial import
-rw-r--r-- | README.md | 46 | ||||
-rw-r--r-- | meson.build | 13 | ||||
-rw-r--r-- | x1k2-midi-osc.c | 227 |
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; +} |