diff options
author | cbdev <cb@cbcdn.com> | 2019-12-08 15:43:05 +0100 |
---|---|---|
committer | cbdev <cb@cbcdn.com> | 2019-12-08 15:43:05 +0100 |
commit | aa092469e21674bb99275dda08a695f0d426f6de (patch) | |
tree | e50add552af0c3ac12627d0290ec8f53557b06cb | |
parent | ac4aa9935bfb6406c71ea5ed14a68bd41617b701 (diff) | |
download | midimonster-aa092469e21674bb99275dda08a695f0d426f6de.tar.gz midimonster-aa092469e21674bb99275dda08a695f0d426f6de.tar.bz2 midimonster-aa092469e21674bb99275dda08a695f0d426f6de.zip |
rtpmidi channel spec parsing
-rw-r--r-- | backends/rtpmidi.c | 199 | ||||
-rw-r--r-- | backends/rtpmidi.h | 20 | ||||
-rw-r--r-- | backends/rtpmidi.md | 2 |
3 files changed, 200 insertions, 21 deletions
diff --git a/backends/rtpmidi.c b/backends/rtpmidi.c index ea97565..4fb0ce0 100644 --- a/backends/rtpmidi.c +++ b/backends/rtpmidi.c @@ -35,6 +35,11 @@ int init(){ .shutdown = rtpmidi_shutdown }; + if(sizeof(rtpmidi_channel_ident) != sizeof(uint64_t)){ + fprintf(stderr, "rtpmidi channel identification union out of bounds\n"); + return 1; + } + if(mm_backend_register(rtpmidi)){ fprintf(stderr, "Failed to register rtpmidi backend\n"); return 1; @@ -45,7 +50,6 @@ int init(){ static int rtpmidi_configure(char* option, char* value){ char* host = NULL, *port = NULL; - char next_port[10]; if(!strcmp(option, "mdns-name")){ if(cfg.mdns_name){ @@ -93,17 +97,170 @@ static int rtpmidi_configure(char* option, char* value){ } static int rtpmidi_configure_instance(instance* inst, char* option, char* value){ - //TODO + rtpmidi_instance_data* data = (rtpmidi_instance_data*) inst->impl; + + if(!strcmp(option, "mode")){ + if(!strcmp(value, "direct")){ + data->mode = direct; + return 0; + } + else if(!strcmp(value, "apple")){ + data->mode = apple; + return 0; + } + fprintf(stderr, "Unknown rtpmidi instance mode %s for instance %s\n", value, inst->name); + return 1; + } + else if(!strcmp(option, "ssrc")){ + data->ssrc = strtoul(value, NULL, 0); + if(!data->ssrc){ + fprintf(stderr, "Random SSRC will be generated for rtpmidi instance %s\n", inst->name); + } + return 0; + } + else if(!strcmp(option, "bind")){ + //TODO set the bind host + } + else if(!strcmp(option, "learn")){ + if(data->mode != direct){ + fprintf(stderr, "The rtpmidi 'learn' option is only valid for direct mode instances\n"); + return 1; + } + data->learn_peers = 0; + if(!strcmp(value, "true")){ + data->learn_peers = 1; + } + return 0; + } + else if(!strcmp(option, "peer")){ + if(data->mode != direct){ + fprintf(stderr, "The rtpmidi 'peer' option is only valid for direct mode instances\n"); + return 1; + } + + //TODO add peer + return 0; + } + else if(!strcmp(option, "session")){ + if(data->mode != apple){ + fprintf(stderr, "The rtpmidi 'session' option is only valid for apple mode instances\n"); + return 1; + } + free(data->session_name); + data->session_name = strdup(value); + if(!data->session_name){ + fprintf(stderr, "Failed to allocate memory\n"); + return 1; + } + return 0; + } + else if(!strcmp(option, "invite")){ + if(data->mode != apple){ + fprintf(stderr, "The rtpmidi 'invite' option is only valid for apple mode instances\n"); + return 1; + } + free(data->invite_peers); + data->invite_peers = strdup(value); + if(!data->invite_peers){ + fprintf(stderr, "Failed to allocate memory\n"); + return 1; + } + return 0; + } + else if(!strcmp(option, "join")){ + if(data->mode != apple){ + fprintf(stderr, "The rtpmidi 'join' option is only valid for apple mode instances\n"); + return 1; + } + free(data->invite_accept); + data->invite_accept = strdup(value); + if(!data->invite_accept){ + fprintf(stderr, "Failed to allocate memory\n"); + return 1; + } + return 0; + } + + fprintf(stderr, "Unknown rtpmidi instance option %s\n", option); return 1; } static instance* rtpmidi_instance(){ - //TODO - return NULL; + rtpmidi_instance_data* data = NULL; + instance* inst = mm_instance(); + + if(!inst){ + return NULL; + } + + data = calloc(1, sizeof(rtpmidi_instance_data)); + if(!data){ + fprintf(stderr, "Failed to allocate memory\n"); + return NULL; + } + + inst->impl = data; + return inst; } static channel* rtpmidi_channel(instance* inst, char* spec, uint8_t flags){ - //TODO + char* next_token = spec; + rtpmidi_channel_ident ident = { + .label = 0 + }; + + if(!strncmp(spec, "ch", 2)){ + next_token += 2; + if(!strncmp(spec, "channel", 7)){ + next_token = spec + 7; + } + } + else{ + fprintf(stderr, "Invalid rtpmidi channel specification %s\n", spec); + return NULL; + } + + ident.fields.channel = strtoul(next_token, &next_token, 10); + if(ident.fields.channel > 15){ + fprintf(stderr, "rtpmidi channel out of range in channel spec %s\n", spec); + return NULL; + } + + if(*next_token != '.'){ + fprintf(stderr, "rtpmidi channel specification %s does not conform to channel<X>.<control><Y>\n", spec); + return NULL; + } + + next_token++; + + if(!strncmp(next_token, "cc", 2)){ + ident.fields.type = cc; + next_token += 2; + } + else if(!strncmp(next_token, "note", 4)){ + ident.fields.type = note; + next_token += 4; + } + else if(!strncmp(next_token, "pressure", 8)){ + ident.fields.type = pressure; + next_token += 8; + } + else if(!strncmp(next_token, "pitch", 5)){ + ident.fields.type = pitchbend; + } + else if(!strncmp(next_token, "aftertouch", 10)){ + ident.fields.type = aftertouch; + } + else{ + fprintf(stderr, "Unknown rtpmidi channel control type in spec %s\n", spec); + return NULL; + } + + ident.fields.control = strtoul(next_token, NULL, 10); + + if(ident.label){ + return mm_channel(inst, ident.label, 1); + } return NULL; } @@ -124,35 +281,37 @@ static int rtpmidi_handle(size_t num, managed_fd* fds){ } static int rtpmidi_start(){ - size_t n, u, p; + size_t n, u; int rv = 1; instance** inst = NULL; rtpmidi_instance_data* data = NULL; //TODO if mdns name defined and no socket, bind default values + if(cfg.mdns_fd < 0){ + fprintf(stderr, "No mDNS discovery interface bound, AppleMIDI session support disabled\n"); + } + //fetch all defined instances if(mm_backend_instances(BACKEND_NAME, &n, &inst)){ fprintf(stderr, "Failed to fetch instance list\n"); return 1; } - if(!n){ - free(inst); - return 0; - } - - if(!cfg.nfds){ - fprintf(stderr, "Failed to start rtpMIDI backend: no descriptors bound\n"); - goto bail; - } + for(u = 0; u < n; u++){ + data = (rtpmidi_instance_data*) inst[u]->impl; + //check whether instances are explicitly configured to a mode + if(data->mode == unconfigured){ + fprintf(stderr, "rtpmidi instance %s is missing a mode configuration\n", inst[u]->name); + goto bail; + } - if(cfg.mdns_fd < 0){ - fprintf(stderr, "No mDNS discovery interface bound, APPLEMIDI session support disabled\n"); + //generate random ssrc's + if(!data->ssrc){ + data->ssrc = rand() << 16 | rand(); + } } - //TODO initialize all instances - rv = 0; bail: free(inst); @@ -160,7 +319,7 @@ bail: } static int rtpmidi_shutdown(){ - size_t u; + //TODO cleanup instance data free(cfg.mdns_name); if(cfg.mdns_fd >= 0){ diff --git a/backends/rtpmidi.h b/backends/rtpmidi.h index af6a189..c076fd0 100644 --- a/backends/rtpmidi.h +++ b/backends/rtpmidi.h @@ -16,11 +16,31 @@ static int rtpmidi_shutdown(); #define RTPMIDI_MDNS_PORT "5353" #define RTPMIDI_HEADER_MAGIC htobe16(0x80E1) +enum /*_rtpmidi_channel_type*/ { + none = 0, + note = 0x90, + cc = 0xB0, + pressure = 0xA0, + aftertouch = 0xD0, + pitchbend = 0xE0 +}; + typedef enum /*_rtpmidi_instance_mode*/ { + unconfigured = 0, direct, apple } rtpmidi_instance_mode; +typedef union { + struct { + uint8_t pad[5]; + uint8_t type; + uint8_t channel; + uint8_t control; + } fields; + uint64_t label; +} rtpmidi_channel_ident; + typedef struct /*_rtpmidi_peer*/ { struct sockaddr_storage dest; socklen_t dest_len; diff --git a/backends/rtpmidi.md b/backends/rtpmidi.md index cea69e2..c84c5b3 100644 --- a/backends/rtpmidi.md +++ b/backends/rtpmidi.md @@ -50,7 +50,7 @@ Common instance configuration parameters | `bind` | `10.1.2.1 9001` | `0.0.0.0 <random>` | Local network address to bind to (note that AppleMIDI requires two consecutive port numbers to be allocated) | | `session` | `Just Jamming` | `MIDIMonster` | Session name to announce via mDNS | | `invite` | `pad,piano` | none | Devices to send invitations to when discovered (the special value `*` invites all discovered peers). Setting this option makes the instance a session initiator | -| `joint` | `Just Jamming` | none | Sessions for which to accept invitations (the special value `*` accepts all invitations). Setting this option makes the instance a session participant | +| `join` | `Just Jamming` | none | Sessions for which to accept invitations (the special value `*` accepts all invitations). Setting this option makes the instance a session participant | Note that AppleMIDI session establishment requires mDNS functionality, thus the `mdns-name` global parameter (and, depending on your setup, the `mdns-bind` parameter) need to be configured properly. |