├── .gitignore ├── README.md ├── alsa-seq-autoconnect ├── Makefile └── main.c └── rpi-midi-ble.jpg /.gitignore: -------------------------------------------------------------------------------- 1 | alsa-seq-autoconnect/alsa-seq-autoconnect 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | rpi-midi-ble 2 | ============ 3 | 4 | This project allows the [Raspberry Pi 3](https://www.raspberrypi.org/products/raspberry-pi-3-model-b/) to be used as an USB-MIDI over BLE-MIDI ([Bluetooth Low Energy MIDI](https://developer.apple.com/bluetooth/Apple-Bluetooth-Low-Energy-MIDI-Specification.pdf)) device. 5 | 6 | ![rpi-midi-ble](https://raw.githubusercontent.com/oxesoft/rpi-midi-ble/master/rpi-midi-ble.jpg) 7 | 8 | It has two components: 9 | 10 | [btmidi-server](https://github.com/oxesoft/bluez/blob/midi/tools/btmidi-server.c) 11 | ------------- 12 | This tool creates an ALSA sequencer port everytime some central BLE app 13 | (eg.: [Korg Module](http://www.korg.com/us/products/software/korg_module/), [GarageBand](http://www.apple.com/ios/garageband/), ...) is connected with the rpi-midi-ble peripheral. 14 | 15 | [alsa-seq-autoconnect](https://github.com/oxesoft/rpi-midi-ble/blob/master/alsa-seq-autoconnect/main.c) 16 | -------------------- 17 | This tool was created to be used with the tool [btmidi-server](https://github.com/oxesoft/bluez/blob/midi/tools/btmidi-server.c) 18 | and is intended to perform the same use case as [Mourino](https://github.com/oxesoft/mourino) when used in a [Raspberry Pi 3](https://www.raspberrypi.org/products/raspberry-pi-3-model-b/). 19 | Everytime some MIDI hardware is connected to the rpi-midi-ble USB port it automatically connects the respective ALSA sequencer port to the "BLE MIDI Device" port. 20 | -------------------------------------------------------------------------------- /alsa-seq-autoconnect/Makefile: -------------------------------------------------------------------------------- 1 | all: 2 | @gcc -o alsa-seq-autoconnect main.c -lasound 3 | 4 | clean: 5 | @rm alsa-seq-autoconnect -------------------------------------------------------------------------------- /alsa-seq-autoconnect/main.c: -------------------------------------------------------------------------------- 1 | /* 2 | rpi-midi-ble: Raspberry Pi 3 as a USB-MIDI over BLE-MIDI device 3 | Copyright (C) 2017 Daniel Moura 4 | 5 | This code is originally hosted at https://github.com/oxesoft/rpi-midi-ble 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 3 of the License, or 10 | (at your option) any later version. 11 | 12 | This program is distributed in the hope that it will be useful, 13 | but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | GNU General Public License for more details. 16 | 17 | You should have received a copy of the GNU General Public License 18 | along with this program. If not, see . 19 | */ 20 | 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | 28 | #define MAX_SENDERS 16 29 | #define APP_NAME "alsa-seq-autoconnect" 30 | static char *dest_name = "BLE-MIDI Device"; 31 | static bool stop = false; 32 | typedef struct snd_seq_connect_ports { 33 | bool senders_connected[MAX_SENDERS]; 34 | bool senders_found [MAX_SENDERS]; 35 | snd_seq_addr_t senders [MAX_SENDERS]; 36 | bool dest_found; 37 | snd_seq_addr_t dest; 38 | } snd_seq_connect_ports_t; 39 | static snd_seq_connect_ports_t ports; 40 | 41 | static void sighandler(int sig) 42 | { 43 | stop = true; 44 | } 45 | 46 | static void connect_ports(snd_seq_t *seq, int index) 47 | { 48 | snd_seq_port_subscribe_t *subs; 49 | 50 | if (index >= MAX_SENDERS) 51 | { 52 | return; 53 | } 54 | 55 | if (ports.senders_connected[index] == true) 56 | { 57 | return; 58 | } 59 | 60 | printf("Connecting %d,%d to %d,%d\n", 61 | ports.senders[index].client, 62 | ports.senders[index].port, 63 | ports.dest.client, 64 | ports.dest.port); 65 | 66 | snd_seq_port_subscribe_alloca(&subs); 67 | snd_seq_port_subscribe_set_sender(subs, &ports.senders[index]); 68 | snd_seq_port_subscribe_set_dest(subs, &ports.dest); 69 | snd_seq_subscribe_port(seq, subs); 70 | 71 | ports.senders_connected[index] == true; 72 | } 73 | 74 | static bool verify_port(snd_seq_t *seq, int client, int port) 75 | { 76 | snd_seq_port_info_t *pinfo; 77 | snd_seq_port_info_alloca(&pinfo); 78 | snd_seq_get_any_port_info(seq, client, port, pinfo); 79 | const char* pname = snd_seq_port_info_get_name(pinfo); 80 | unsigned int caps = snd_seq_port_info_get_capability(pinfo); 81 | unsigned int type = snd_seq_port_info_get_type(pinfo); 82 | int i; 83 | 84 | if (strcmp(pname, dest_name) == 0) 85 | { 86 | printf("Found destination port \"%s\"\n", pname); 87 | ports.dest.client = client; 88 | ports.dest.port = port; 89 | ports.dest_found = true; 90 | } 91 | else if (caps & SND_SEQ_PORT_CAP_READ && 92 | caps & SND_SEQ_PORT_CAP_SUBS_READ && 93 | type & SND_SEQ_PORT_TYPE_HARDWARE) 94 | { 95 | bool already_used = false; 96 | for (i = 0; i < MAX_SENDERS; i++) 97 | { 98 | if (ports.senders[i].client == client && 99 | ports.senders[i].port == port && 100 | ports.senders_found[i] == true) 101 | { 102 | already_used = true; 103 | break; 104 | } 105 | } 106 | 107 | if (already_used == false) 108 | { 109 | int available_index = -1; 110 | for (i = 0; i < MAX_SENDERS; i++) 111 | { 112 | if (ports.senders_found[i] == false) 113 | { 114 | available_index = i; 115 | break; 116 | } 117 | } 118 | if (available_index != -1) 119 | { 120 | printf("Found source port \"%s\"\n", pname); 121 | ports.senders[available_index].client = client; 122 | ports.senders[available_index].port = port; 123 | ports.senders_found[available_index] = true; 124 | ports.senders_connected[available_index] = false; 125 | } 126 | } 127 | } 128 | 129 | if (ports.dest_found == false) 130 | { 131 | return false; 132 | } 133 | 134 | for (i = 0; i < MAX_SENDERS; i++) 135 | { 136 | if (ports.senders_found[i] == true && ports.senders_connected[i] == false) 137 | { 138 | connect_ports(seq, i); 139 | } 140 | } 141 | 142 | return true; 143 | } 144 | 145 | static int list_ports(snd_seq_t *seq) 146 | { 147 | snd_seq_client_info_t *cinfo; 148 | snd_seq_port_info_t *pinfo; 149 | int found = false; 150 | 151 | snd_seq_client_info_alloca(&cinfo); 152 | snd_seq_port_info_alloca(&pinfo); 153 | snd_seq_client_info_set_client(cinfo, -1); 154 | while (snd_seq_query_next_client(seq, cinfo) >= 0 && found == false) 155 | { 156 | int client = snd_seq_client_info_get_client(cinfo); 157 | snd_seq_port_info_set_client(pinfo, client); 158 | snd_seq_port_info_set_port(pinfo, -1); 159 | while (snd_seq_query_next_port(seq, pinfo) >= 0 && found == false) 160 | { 161 | int port = snd_seq_port_info_get_port(pinfo); 162 | found = verify_port(seq, client, port); 163 | } 164 | } 165 | } 166 | 167 | int main(int argc, char *argv[]) 168 | { 169 | int err; 170 | snd_seq_t *seq; 171 | struct pollfd *pfds; 172 | int npfds; 173 | int i; 174 | 175 | memset(&ports, 0, sizeof(ports)); 176 | 177 | signal(SIGINT, sighandler); 178 | signal(SIGTERM, sighandler); 179 | 180 | err = snd_seq_open(&seq, "default", SND_SEQ_OPEN_DUPLEX, 0); 181 | if (err < 0) 182 | { 183 | fprintf(stderr, "Error creating port\n"); 184 | goto _err; 185 | } 186 | 187 | err = snd_seq_set_client_name(seq, APP_NAME); 188 | if (err < 0) 189 | { 190 | fprintf(stderr, "Error naming port: %s\n", snd_strerror(err)); 191 | goto _err_seq_close; 192 | } 193 | 194 | err = snd_seq_create_simple_port(seq, APP_NAME, 195 | SND_SEQ_PORT_CAP_WRITE | 196 | SND_SEQ_PORT_CAP_SUBS_WRITE, 197 | SND_SEQ_PORT_TYPE_APPLICATION); 198 | if (err < 0) 199 | { 200 | fprintf(stderr, "Error creating port: %s\n", snd_strerror(err)); 201 | goto _err_seq_close; 202 | } 203 | 204 | err = snd_seq_nonblock(seq, 1); 205 | if (err < 0) 206 | { 207 | fprintf(stderr, "Error setting non block mode: %s\n", snd_strerror(err)); 208 | goto _err_seq_close; 209 | } 210 | 211 | err = snd_seq_connect_from(seq, 0, 0, 1); 212 | if (err < 0) 213 | { 214 | fprintf(stderr, "Cannot connect from announce port: %s\n", snd_strerror(err)); 215 | goto _err_seq_close; 216 | } 217 | 218 | list_ports(seq); 219 | 220 | /* listens to the events announcements */ 221 | npfds = snd_seq_poll_descriptors_count(seq, POLLIN); 222 | pfds = alloca(sizeof(*pfds) * npfds); 223 | while (stop == false) 224 | { 225 | snd_seq_poll_descriptors(seq, pfds, npfds, POLLIN); 226 | err = poll(pfds, npfds, -1); 227 | if (err < 0) 228 | { 229 | err = 0; 230 | break; 231 | } 232 | do 233 | { 234 | snd_seq_event_t *event; 235 | err = snd_seq_event_input(seq, &event); 236 | if (err < 0) 237 | { 238 | err = 0; 239 | break; 240 | } 241 | if (event) 242 | { 243 | switch (event->type) 244 | { 245 | case SND_SEQ_EVENT_PORT_START: 246 | verify_port(seq, event->data.addr.client, event->data.addr.port); 247 | break; 248 | case SND_SEQ_EVENT_PORT_EXIT: 249 | if (event->data.addr.client == ports.dest.client && 250 | event->data.addr.port == ports.dest.port && 251 | ports.dest_found == true) 252 | { 253 | printf("Exited destination port\n"); 254 | ports.dest_found = false; 255 | for (i = 0; i < MAX_SENDERS; i++) 256 | { 257 | ports.senders_connected[i] = false; 258 | } 259 | } 260 | else 261 | { 262 | for (i = 0; i < MAX_SENDERS; i++) 263 | { 264 | if (event->data.addr.client == ports.senders[i].client && 265 | event->data.addr.port == ports.senders[i].port && 266 | ports.senders_found[i] == true) 267 | { 268 | printf("Exited source port\n"); 269 | ports.senders_found[i] = false; 270 | ports.senders_connected[i] = false; 271 | break; 272 | } 273 | } 274 | } 275 | break; 276 | } 277 | } 278 | } while (err > 0); 279 | fflush(stdout); 280 | } 281 | 282 | _err_seq_close: 283 | snd_seq_close(seq); 284 | _err: 285 | return err; 286 | } 287 | -------------------------------------------------------------------------------- /rpi-midi-ble.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oxesoft/rpi-midi-ble/388e1db5a524d97f19da3c44c48b7344b7a776dc/rpi-midi-ble.jpg --------------------------------------------------------------------------------