aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
-rw-r--r--backends/rtpmidi.c199
-rw-r--r--backends/rtpmidi.h20
-rw-r--r--backends/rtpmidi.md2
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.