From 2dfc564edc0c89c4a8de7e384806aae5d593426d Mon Sep 17 00:00:00 2001 From: cbdev Date: Fri, 2 Mar 2018 03:20:11 +0100 Subject: Move backend implementations to subdirectory --- backends/midi.c | 366 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 366 insertions(+) create mode 100644 backends/midi.c (limited to 'backends/midi.c') diff --git a/backends/midi.c b/backends/midi.c new file mode 100644 index 0000000..d856ced --- /dev/null +++ b/backends/midi.c @@ -0,0 +1,366 @@ +#include +#include +#include "midi.h" + +#define BACKEND_NAME "midi" +static snd_seq_t* sequencer = NULL; +typedef union { + struct { + uint8_t pad[5]; + uint8_t type; + uint8_t channel; + uint8_t control; + } fields; + uint64_t label; +} midi_channel_ident; + +/* + * TODO + * Optionally send note-off messages + * Optionally send updates as after-touch + */ + +enum /*_midi_channel_type*/ { + none = 0, + note, + cc, + nrpn, + sysmsg +}; + +int init(){ + backend midi = { + .name = BACKEND_NAME, + .conf = midi_configure, + .create = midi_instance, + .conf_instance = midi_configure_instance, + .channel = midi_channel, + .handle = midi_set, + .process = midi_handle, + .start = midi_start, + .shutdown = midi_shutdown + }; + + if(snd_seq_open(&sequencer, "default", SND_SEQ_OPEN_DUPLEX, 0) < 0){ + fprintf(stderr, "Failed to open ALSA sequencer\n"); + return 1; + } + + //register backend + if(mm_backend_register(midi)){ + fprintf(stderr, "Failed to register MIDI backend\n"); + return 1; + } + + snd_seq_nonblock(sequencer, 1); + + fprintf(stderr, "MIDI client ID is %d\n", snd_seq_client_id(sequencer)); + return 0; +} + +static int midi_configure(char* option, char* value){ + if(!strcmp(option, "name")){ + if(snd_seq_set_client_name(sequencer, value) < 0){ + fprintf(stderr, "Failed to set MIDI client name to %s\n", value); + return 1; + } + return 0; + } + + fprintf(stderr, "Unknown MIDI backend option %s\n", option); + return 1; +} + +static instance* midi_instance(){ + instance* inst = mm_instance(); + if(!inst){ + return NULL; + } + + inst->impl = calloc(1, sizeof(midi_instance_data)); + if(!inst->impl){ + fprintf(stderr, "Failed to allocate memory\n"); + return NULL; + } + + return inst; +} + +static int midi_configure_instance(instance* instance, char* option, char* value){ + midi_instance_data* data = (midi_instance_data*) instance->impl; + + //FIXME maybe allow connecting more than one device + if(!strcmp(option, "read")){ + //connect input device + if(data->read){ + fprintf(stderr, "MIDI port already connected to an input device\n"); + return 1; + } + data->read = strdup(value); + return 0; + } + else if(!strcmp(option, "write")){ + //connect output device + if(data->write){ + fprintf(stderr, "MIDI port already connected to an output device\n"); + return 1; + } + data->write = strdup(value); + return 0; + } + + fprintf(stderr, "Unknown MIDI instance option %s\n", option); + return 1; +} + +static channel* midi_channel(instance* instance, char* spec){ + midi_channel_ident ident = { + .label = 0 + }; + + char* channel; + + if(!strncmp(spec, "cc", 2)){ + ident.fields.type = cc; + channel = spec + 2; + } + else if(!strncmp(spec, "note", 4)){ + ident.fields.type = note; + channel = spec + 4; + } + else if(!strncmp(spec, "nrpn", 4)){ + ident.fields.type = nrpn; + channel = spec + 4; + } + else{ + fprintf(stderr, "Unknown MIDI channel specification %s\n", spec); + return NULL; + } + + ident.fields.channel = strtoul(channel, &channel, 10); + + //FIXME test this + if(ident.fields.channel > 16){ + fprintf(stderr, "MIDI channel out of range in channel spec %s\n", spec); + return NULL; + } + + if(*channel != '.'){ + fprintf(stderr, "Need MIDI channel specification of form channel.control, had %s\n", spec); + return NULL; + } + channel++; + + ident.fields.control = strtoul(channel, NULL, 10); + + if(ident.label){ + return mm_channel(instance, ident.label, 1); + } + + return NULL; +} + +static int midi_set(instance* inst, size_t num, channel** c, channel_value* v){ + size_t u; + snd_seq_event_t ev; + midi_instance_data* data; + midi_channel_ident ident = { + .label = 0 + }; + + for(u = 0; u < num; u++){ + data = (midi_instance_data*) c[u]->instance->impl; + ident.label = c[u]->ident; + + snd_seq_ev_clear(&ev); + snd_seq_ev_set_source(&ev, data->port); + snd_seq_ev_set_subs(&ev); + snd_seq_ev_set_direct(&ev); + + switch(ident.fields.type){ + case note: + snd_seq_ev_set_noteon(&ev, ident.fields.channel, ident.fields.control, v[u].normalised * 127.0); + break; + case cc: + snd_seq_ev_set_controller(&ev, ident.fields.channel, ident.fields.control, v[u].normalised * 127.0); + break; + case nrpn: + //FIXME set to nrpn output + break; + } + + snd_seq_event_output(sequencer, &ev); + } + + snd_seq_drain_output(sequencer); + return 0; +} + +static int midi_handle(size_t num, managed_fd* fds){ + snd_seq_event_t* ev = NULL; + instance* inst = NULL; + channel* changed = NULL; + channel_value val; + midi_channel_ident ident = { + .label = 0 + }; + + if(!num){ + return 0; + } + + while(snd_seq_event_input(sequencer, &ev) > 0){ + ident.label = 0; + switch(ev->type){ + case SND_SEQ_EVENT_NOTEON: + case SND_SEQ_EVENT_NOTEOFF: + case SND_SEQ_EVENT_KEYPRESS: + case SND_SEQ_EVENT_NOTE: + ident.fields.type = note; + ident.fields.channel = ev->data.note.channel; + ident.fields.control = ev->data.note.note; + val.normalised = (double)ev->data.note.velocity / 127.0; + break; + case SND_SEQ_EVENT_CONTROLLER: + ident.fields.type = cc; + ident.fields.channel = ev->data.control.channel; + ident.fields.control = ev->data.control.param; + val.raw.u64 = ev->data.control.value; + val.normalised = (double)ev->data.control.value / 127.0; + break; + case SND_SEQ_EVENT_CONTROL14: + case SND_SEQ_EVENT_NONREGPARAM: + case SND_SEQ_EVENT_REGPARAM: + //FIXME value calculation + ident.fields.type = nrpn; + ident.fields.channel = ev->data.control.channel; + ident.fields.control = ev->data.control.param; + break; + default: + fprintf(stderr, "Ignored MIDI event of unsupported type\n"); + continue; + } + + inst = mm_instance_find(BACKEND_NAME, ev->dest.port); + if(!inst){ + //FIXME might want to return failure + fprintf(stderr, "Delivered MIDI event did not match any instance\n"); + continue; + } + + changed = mm_channel(inst, ident.label, 0); + if(changed){ + if(mm_channel_event(changed, val)){ + free(ev); + return 1; + } + } + } + free(ev); + return 0; +} + +static int midi_start(){ + size_t n, p; + int nfds, rv = 1; + struct pollfd* pfds = NULL; + instance** inst = NULL; + midi_instance_data* data = NULL; + snd_seq_addr_t addr; + + if(mm_backend_instances(BACKEND_NAME, &n, &inst)){ + fprintf(stderr, "Failed to fetch instance list\n"); + return 1; + } + + //if there are no ports, do nothing + if(!n){ + free(inst); + return 0; + } + + //create all ports + for(p = 0; p < n; p++){ + data = (midi_instance_data*) inst[p]->impl; + data->port = snd_seq_create_simple_port(sequencer, inst[p]->name, SND_SEQ_PORT_CAP_WRITE | SND_SEQ_PORT_CAP_SUBS_WRITE | SND_SEQ_PORT_CAP_READ | SND_SEQ_PORT_CAP_SUBS_READ, SND_SEQ_PORT_TYPE_MIDI_GENERIC); + inst[p]->ident = data->port; + + //make connections + if(data->write){ + if(snd_seq_parse_address(sequencer, &addr, data->write) == 0){ + fprintf(stderr, "Connecting output of instance %s to MIDI device %s (%d:%d)\n", inst[p]->name, data->write, addr.client, addr.port); + snd_seq_connect_to(sequencer, data->port, addr.client, addr.port); + } + else{ + fprintf(stderr, "Failed to get destination MIDI device address: %s\n", data->write); + } + free(data->write); + data->write = NULL; + } + + if(data->read){ + if(snd_seq_parse_address(sequencer, &addr, data->read) == 0){ + fprintf(stderr, "Connecting input from MIDI device %s to instance %s (%d:%d)\n", data->read, inst[p]->name, addr.client, addr.port); + snd_seq_connect_from(sequencer, data->port, addr.client, addr.port); + } + else{ + fprintf(stderr, "Failed to get source MIDI device address: %s\n", data->read); + } + free(data->read); + data->read = NULL; + } + } + + //register all fds to core + nfds = snd_seq_poll_descriptors_count(sequencer, POLLIN | POLLOUT); + pfds = calloc(nfds, sizeof(struct pollfd)); + if(!pfds){ + fprintf(stderr, "Failed to allocate memory\n"); + goto bail; + } + nfds = snd_seq_poll_descriptors(sequencer, pfds, nfds, POLLIN | POLLOUT); + + fprintf(stderr, "MIDI backend registering %d descriptors to core\n", nfds); + for(p = 0; p < nfds; p++){ + if(mm_manage_fd(pfds[p].fd, BACKEND_NAME, 1, NULL)){ + goto bail; + } + } + + rv = 0; + +bail: + free(pfds); + free(inst); + return rv; +} + +static int midi_shutdown(){ + size_t n, p; + instance** inst = NULL; + midi_instance_data* data = NULL; + if(mm_backend_instances(BACKEND_NAME, &n, &inst)){ + fprintf(stderr, "Failed to fetch instance list\n"); + return 1; + } + + for(p = 0; p < n; p++){ + data = (midi_instance_data*) inst[p]->impl; + free(data->read); + free(data->write); + data->read = NULL; + data->write = NULL; + free(inst[p]->impl); + } + free(inst); + + //close midi + snd_seq_close(sequencer); + sequencer = NULL; + + //free configuration cache + snd_config_update_free_global(); + + fprintf(stderr, "MIDI backend shut down\n"); + return 0; +} -- cgit v1.2.3