Werner's Miscellanea
Sign in or create your account | Project List | Help
Werner's Miscellanea Git Source Tree
Root/
| 1 | /* |
| 2 | * midi2osc.c - MIDI to OSC forwarder |
| 3 | * |
| 4 | * Written 2011 by Werner Almesberger |
| 5 | * Copyright 2011 Werner Almesberger |
| 6 | * |
| 7 | * This program is free software; you can redistribute it and/or modify |
| 8 | * it under the terms of the GNU General Public License as published by |
| 9 | * the Free Software Foundation; either version 2 of the License, or |
| 10 | * (at your option) any later version. |
| 11 | */ |
| 12 | |
| 13 | |
| 14 | #include <stdint.h> |
| 15 | #include <stdlib.h> |
| 16 | #include <stdio.h> |
| 17 | #include <alsa/asoundlib.h> |
| 18 | #include "lo/lo.h" |
| 19 | |
| 20 | |
| 21 | #define NAME "midi2osc" |
| 22 | |
| 23 | |
| 24 | static int debug = 0; |
| 25 | |
| 26 | |
| 27 | static struct map { |
| 28 | int chan_in, ctrl_in; |
| 29 | int chan_out, ctrl_out; |
| 30 | int value; |
| 31 | struct map *next; |
| 32 | } *mappings = NULL; |
| 33 | |
| 34 | |
| 35 | static void add(int chan_in, int ctrl_in, int chan_out, int ctrl_out, |
| 36 | int value) |
| 37 | { |
| 38 | struct map *new; |
| 39 | |
| 40 | new = malloc(sizeof(struct map)); |
| 41 | new->chan_in = chan_in; |
| 42 | new->ctrl_in = ctrl_in; |
| 43 | new->chan_out = chan_out; |
| 44 | new->ctrl_out = ctrl_out; |
| 45 | new->value = value; |
| 46 | new->next = mappings; |
| 47 | mappings = new; |
| 48 | } |
| 49 | |
| 50 | |
| 51 | static void add_mapping(const char *s) |
| 52 | { |
| 53 | unsigned chan_in, ctrl_in, chan_out, ctrl_out, value; |
| 54 | |
| 55 | if (sscanf(s, "c%u.%u=c%u.%u=%u", |
| 56 | &chan_in, &ctrl_in, &chan_out, &ctrl_out, &value) == 5) |
| 57 | add(chan_in, ctrl_in, chan_out, ctrl_out, value); |
| 58 | else if (sscanf(s, "c%u.%u=c%u.%u", |
| 59 | &chan_in, &ctrl_in, &chan_out, &ctrl_out) == 4) |
| 60 | add(chan_in, ctrl_in, chan_out, ctrl_out, -1); |
| 61 | else if (sscanf(s, "c%u.%u=c%u", &chan_in, &ctrl_in, &chan_out) == 3) |
| 62 | add(chan_in, ctrl_in, chan_out, -1, -1); |
| 63 | else if (sscanf(s, "c%u=c%u.%u", &chan_in, &chan_out, &ctrl_out) == 3) |
| 64 | add(chan_in, -1, chan_out, ctrl_out, -1); |
| 65 | else if (sscanf(s, "c.%u=c%u.%u", &ctrl_in, &chan_out, &ctrl_out) == 3) |
| 66 | add(-1, ctrl_in, chan_out, ctrl_out, -1); |
| 67 | else if (sscanf(s, "c%u=c%u", &chan_in, &chan_out) == 2) |
| 68 | add(chan_in, -1, chan_out, -1, -1); |
| 69 | else { |
| 70 | fprintf(stderr, "unrecognized mapping syntax\n"); |
| 71 | exit(1); |
| 72 | } |
| 73 | } |
| 74 | |
| 75 | |
| 76 | static void map(uint8_t *chan, uint8_t *ctrl, uint8_t *value) |
| 77 | { |
| 78 | const struct map *m; |
| 79 | |
| 80 | for (m = mappings; m; m = m->next) |
| 81 | if ((m->chan_in == -1 || m->chan_in == *chan) && |
| 82 | (m->ctrl_in == -1 || m->ctrl_in == *ctrl)) { |
| 83 | if (m->chan_out != -1) |
| 84 | *chan = m->chan_out; |
| 85 | if (m->ctrl_out != -1) |
| 86 | *ctrl = m->ctrl_out; |
| 87 | if (m->value != -1) |
| 88 | *value = m->value; |
| 89 | return; |
| 90 | } |
| 91 | } |
| 92 | |
| 93 | |
| 94 | static void forward(snd_seq_t *midi, lo_address osc) |
| 95 | { |
| 96 | snd_seq_event_t *ev; |
| 97 | uint8_t msg[4] = { 0, }; |
| 98 | uint8_t chan, ctrl, value; |
| 99 | |
| 100 | while (snd_seq_event_input(midi, &ev)) { |
| 101 | switch (ev->type) { |
| 102 | case SND_SEQ_EVENT_NOTEON: |
| 103 | if (debug) |
| 104 | fprintf(stderr, "note c%u.%u=%u\n", |
| 105 | ev->data.note.channel, |
| 106 | ev->data.note.note, |
| 107 | ev->data.note.velocity); |
| 108 | msg[1] = 0x90 | ev->data.note.channel; |
| 109 | msg[2] = ev->data.note.note; |
| 110 | msg[3] = ev->data.note.velocity; |
| 111 | break; |
| 112 | case SND_SEQ_EVENT_CONTROLLER: |
| 113 | chan = ev->data.control.channel; |
| 114 | ctrl = ev->data.control.param; |
| 115 | value = ev->data.control.value; |
| 116 | map(&chan, &ctrl, &value); |
| 117 | if (debug) |
| 118 | fprintf(stderr, |
| 119 | "control c%u.%u=%u -> c%u.%u=%u\n", |
| 120 | ev->data.control.channel, |
| 121 | ev->data.control.param, |
| 122 | ev->data.control.value, |
| 123 | chan, ctrl, value); |
| 124 | msg[1] = 0xb0 | chan; |
| 125 | msg[2] = ctrl; |
| 126 | msg[3] = value; |
| 127 | break; |
| 128 | case SND_SEQ_EVENT_PGMCHANGE: |
| 129 | if (debug) |
| 130 | fprintf(stderr, "prog c%u=%u\n", |
| 131 | ev->data.control.channel, |
| 132 | ev->data.control.value); |
| 133 | msg[1] = 0xe0 | ev->data.control.channel; |
| 134 | msg[2] = ev->data.control.value; |
| 135 | msg[3] = 0; |
| 136 | break; |
| 137 | case SND_SEQ_EVENT_PITCHBEND: |
| 138 | if (debug) |
| 139 | fprintf(stderr, "pitch c%u=%u\n", |
| 140 | ev->data.control.channel, |
| 141 | ev->data.control.value); |
| 142 | msg[1] = 0xe0 | ev->data.control.channel; |
| 143 | msg[2] = ev->data.control.value & 0x7f; |
| 144 | msg[3] = ev->data.control.value >> 7; |
| 145 | break; |
| 146 | default: |
| 147 | /* Flickernoise currently doesn't support any others */ |
| 148 | if (debug) |
| 149 | fprintf(stderr, "unrecognized MIDI event %u\n", |
| 150 | ev->type); |
| 151 | snd_seq_free_event(ev); |
| 152 | continue; |
| 153 | } |
| 154 | if (lo_send(osc, "/midi", "m", msg) < 0) |
| 155 | fprintf(stderr, "%s\n", lo_address_errstr(osc)); |
| 156 | snd_seq_free_event(ev); |
| 157 | } |
| 158 | } |
| 159 | |
| 160 | |
| 161 | static void usage(const char *name) |
| 162 | { |
| 163 | fprintf(stderr, |
| 164 | "usage: %s [-d] [mapping ...] hostname [port]\n\n" |
| 165 | " mappings are of the form\n" |
| 166 | " c[<chan>][.<control>]=c<chan>[.<control>[=<value>]]\n\n" |
| 167 | " -d debug mode: print all MIDI messages\n", |
| 168 | name); |
| 169 | exit(1); |
| 170 | } |
| 171 | |
| 172 | |
| 173 | int main(int argc, char **argv) |
| 174 | { |
| 175 | const char *port = "4444"; /* Milkymist One OSC port */ |
| 176 | lo_address osc; |
| 177 | snd_seq_t *midi; |
| 178 | int c, arg; |
| 179 | |
| 180 | while ((c = getopt(argc, argv, "d")) != EOF) |
| 181 | switch (c) { |
| 182 | case 'd': |
| 183 | debug = 1; |
| 184 | break; |
| 185 | default: |
| 186 | usage(*argv); |
| 187 | } |
| 188 | |
| 189 | for (arg = optind; arg != argc; arg++) { |
| 190 | if (!strchr(argv[arg], '=')) |
| 191 | break; |
| 192 | add_mapping(argv[arg]); |
| 193 | } |
| 194 | switch (argc-arg) { |
| 195 | case 1: |
| 196 | break; |
| 197 | case 2: |
| 198 | port = argv[arg+1]; |
| 199 | break; |
| 200 | default: |
| 201 | usage(*argv); |
| 202 | } |
| 203 | |
| 204 | osc = lo_address_new(argv[arg], port); |
| 205 | if (!osc) { |
| 206 | fprintf(stderr, "invalid address %s %s\n", argv[arg], port); |
| 207 | exit(1); |
| 208 | } |
| 209 | |
| 210 | if (snd_seq_open(&midi, "hw", SND_SEQ_OPEN_INPUT,0) < 0) { |
| 211 | fprintf(stderr, "can't open ALSA sequencer\n"); |
| 212 | exit(1); |
| 213 | } |
| 214 | snd_seq_set_client_name(midi, NAME); |
| 215 | if (snd_seq_create_simple_port(midi, NAME, |
| 216 | SND_SEQ_PORT_CAP_WRITE | SND_SEQ_PORT_CAP_SUBS_WRITE, |
| 217 | SND_SEQ_PORT_TYPE_APPLICATION) < 0) { |
| 218 | fprintf(stderr, "can't create sequencer port\n"); |
| 219 | exit(1); |
| 220 | } |
| 221 | |
| 222 | forward(midi, osc); |
| 223 | |
| 224 | return 0; |
| 225 | } |
| 226 |
Branches:
master
