aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
-rw-r--r--backends/rtpmidi.c166
-rw-r--r--backends/rtpmidi.h10
-rw-r--r--backends/rtpmidi.md85
3 files changed, 114 insertions, 147 deletions
diff --git a/backends/rtpmidi.c b/backends/rtpmidi.c
index 6a8032a..5387d86 100644
--- a/backends/rtpmidi.c
+++ b/backends/rtpmidi.c
@@ -11,36 +11,14 @@
#define BACKEND_NAME "rtpmidi"
-/** rtpMIDI backend w/ AppleMIDI support
- *
- * Global configuration
- * bind = 0.0.0.0
- * apple-bind = 0.0.0.1
- * mdns-bind = 0.0.0.0
- * mdns-name = mdns-name
- *
- * Instance configuration
- * interface = 0
- * (opt) ssrc = X
- *
- * apple-session = session-name
- * apple-invite = invite-peer
- * apple-allow = *
- * or
- * connect =
- * reply-any = 1
- */
-
static struct /*_rtpmidi_global*/ {
int mdns_fd;
char* mdns_name;
- size_t nfds;
- rtpmidi_fd* fds;
+ uint8_t detect;
} cfg = {
.mdns_fd = -1,
.mdns_name = NULL,
- .nfds = 0,
- .fds = NULL
+ .detect = 0
};
int init(){
@@ -57,121 +35,17 @@ int init(){
};
if(mm_backend_register(rtpmidi)){
- fprintf(stderr, "Failed to register rtpMIDI backend\n");
- return 1;
- }
-
- return 0;
-}
-
-static int rtpmidi_listener(char* host, char* port){
- int fd = -1, status, yes = 1, flags;
- struct addrinfo hints = {
- .ai_family = AF_UNSPEC,
- .ai_socktype = SOCK_DGRAM,
- .ai_flags = AI_PASSIVE
- };
- struct addrinfo* info;
- struct addrinfo* addr_it;
-
- status = getaddrinfo(host, port, &hints, &info);
- if(status){
- fprintf(stderr, "Failed to get socket info for %s port %s: %s\n", host, port, gai_strerror(status));
- return -1;
- }
-
- for(addr_it = info; addr_it != NULL; addr_it = addr_it->ai_next){
- fd = socket(addr_it->ai_family, addr_it->ai_socktype, addr_it->ai_protocol);
- if(fd < 0){
- continue;
- }
-
- yes = 1;
- if(setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, (void*)&yes, sizeof(yes)) < 0){
- fprintf(stderr, "Failed to set SO_REUSEADDR on socket\n");
- }
-
- yes = 1;
- if(setsockopt(fd, SOL_SOCKET, SO_BROADCAST, (void*)&yes, sizeof(yes)) < 0){
- fprintf(stderr, "Failed to set SO_BROADCAST on socket\n");
- }
-
- yes = 0;
- if(setsockopt(fd, IPPROTO_IP, IP_MULTICAST_LOOP, (void*)&yes, sizeof(yes)) < 0){
- fprintf(stderr, "Failed to unset IP_MULTICAST_LOOP option: %s\n", strerror(errno));
- }
-
- status = bind(fd, addr_it->ai_addr, addr_it->ai_addrlen);
- if(status < 0){
- close(fd);
- continue;
- }
-
- break;
- }
-
- freeaddrinfo(info);
-
- if(!addr_it){
- fprintf(stderr, "Failed to create listening socket for %s port %s\n", host, port);
- return -1;
- }
-
- //set nonblocking
- flags = fcntl(fd, F_GETFL, 0);
- if(fcntl(fd, F_SETFL, flags | O_NONBLOCK) < 0){
- fprintf(stderr, "Failed to set rtpMIDI descriptor nonblocking\n");
- close(fd);
- return -1;
- }
- return 0;
-}
-
-static int rtpmidi_parse_addr(char* host, char* port, struct sockaddr_storage* addr, socklen_t* len){
- struct addrinfo* head;
- struct addrinfo hints = {
- .ai_family = AF_UNSPEC,
- .ai_socktype = SOCK_DGRAM
- };
-
- int error = getaddrinfo(host, port, &hints, &head);
- if(error || !head){
- fprintf(stderr, "Failed to parse address %s port %s: %s\n", host, port, gai_strerror(error));
- return 1;
- }
-
- memcpy(addr, head->ai_addr, head->ai_addrlen);
- *len = head->ai_addrlen;
-
- freeaddrinfo(head);
- return 0;
-}
-
-static int rtpmidi_separate_hostspec(char* in, char** host, char** port, char* default_port){
- size_t u;
-
- if(!in || !host || !port){
+ fprintf(stderr, "Failed to register rtpmidi backend\n");
return 1;
}
- for(u = 0; in[u] && !isspace(in[u]); u++){
- }
-
- //guess
- *host = in;
-
- if(in[u]){
- in[u] = 0;
- *port = in + u + 1;
- }
- else{
- //no port given
- *port = default_port;
- }
return 0;
}
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){
fprintf(stderr, "Duplicate mdns-name assignment\n");
@@ -191,17 +65,27 @@ static int rtpmidi_configure(char* option, char* value){
return 1;
}
+ if(mmbackend_parse_hostspec(value, &host, &port)){
+ fprintf(stderr, "Not a valid mDNS bind address: %s\n", value);
+ return 1;
+ }
- //TODO create mdns broadcast/responder socket
- }
- else if(!strcmp(option, "bind")){
- //TODO open listening data fd for raw rtpmidi instance
+ cfg.mdns_fd = mmbackend_socket(host, (port ? port : RTPMIDI_MDNS_PORT), SOCK_DGRAM, 1, 1);
+ if(cfg.mdns_fd < 0){
+ fprintf(stderr, "Failed to bind mDNS interface: %s\n", value);
+ return 1;
+ }
+ return 0;
}
- else if(!strcmp(option, "apple-bind")){
- //TODO open control and data fd for applemidi session/rtpmidi+apple-extensions instance
+ else if(!strcmp(option, "detect")){
+ cfg.detect = 0;
+ if(!strcmp(value, "on")){
+ cfg.detect = 1;
+ }
+ return 0;
}
- fprintf(stderr, "Unknown rtpMIDI backend option %s\n", option);
+ fprintf(stderr, "Unknown rtpmidi backend option %s\n", option);
return 1;
}
@@ -215,7 +99,7 @@ static instance* rtpmidi_instance(){
return NULL;
}
-static channel* rtpmidi_channel(instance* inst, char* spec){
+static channel* rtpmidi_channel(instance* inst, char* spec, uint8_t flags){
//TODO
return NULL;
}
@@ -242,6 +126,8 @@ static int rtpmidi_start(){
instance** inst = NULL;
rtpmidi_instance_data* data = NULL;
+ //TODO if mdns name defined and no socket, bind default values
+
//fetch all defined instances
if(mm_backend_instances(BACKEND_NAME, &n, &inst)){
fprintf(stderr, "Failed to fetch instance list\n");
diff --git a/backends/rtpmidi.h b/backends/rtpmidi.h
index f14357f..6985ede 100644
--- a/backends/rtpmidi.h
+++ b/backends/rtpmidi.h
@@ -5,7 +5,7 @@ int init();
static int rtpmidi_configure(char* option, char* value);
static int rtpmidi_configure_instance(instance* instance, char* option, char* value);
static instance* rtpmidi_instance();
-static channel* rtpmidi_channel(instance* instance, char* spec);
+static channel* rtpmidi_channel(instance* instance, char* spec, uint8_t flags);
static int rtpmidi_set(instance* inst, size_t num, channel** c, channel_value* v);
static int rtpmidi_handle(size_t num, managed_fd* fds);
static int rtpmidi_start();
@@ -13,11 +13,7 @@ static int rtpmidi_shutdown();
#define RTPMIDI_DEFAULT_PORTBASE "9001"
#define RTPMIDI_RECV_BUF 4096
-
-#define RTPMIDI_COMMAND_LEN
-#define RTPMIDI_COMMAND_DATA_OFFSET
-#define RTPMIDI_HAS_JOURNAL
-#define RTPMIDI_IS_PHANTOM
+#define RTPMIDI_MDNS_PORT "5353"
typedef enum /*_rtpmidi_peer_mode*/ {
peer_learned,
@@ -40,7 +36,7 @@ typedef struct /*_rtpmidi_fd*/ {
} rtpmidi_fd;
typedef struct /*_rtmidi_instance_data*/ {
- size_t fd_index;
+ int fd;
size_t npeers;
rtpmidi_peer* peers;
uint32_t ssrc;
diff --git a/backends/rtpmidi.md b/backends/rtpmidi.md
new file mode 100644
index 0000000..49c6baf
--- /dev/null
+++ b/backends/rtpmidi.md
@@ -0,0 +1,85 @@
+### The `rtpmidi` backend
+
+This backend provides read-write access to RTP MIDI streams, which transfer MIDI data
+over the network.
+
+As the specification for RTP MIDI does not normatively indicate any method
+for session management, most vendors define their own standards for this.
+The MIDIMonster supports the following session management methods, which are
+selectable per-instance, with some methods requiring additional global configuration:
+
+* Direct connection: The instance will send and receive data from peers configured in the
+ instance configuration
+* Direct connection with peer learning: The instance will send and receive data from peers
+ configured in the instance configuration as well as previously unknown peers that
+ voluntarily send data to the instance.
+* AppleMIDI session management:
+
+Note that instances that receive data from multiple peers will combine all inputs into one
+stream, which may lead to inconsistencies during playback.
+
+#### Global configuration
+
+| Option | Example value | Default value | Description |
+|---------------|-----------------------|-----------------------|-----------------------|
+| `detect` | `on` | `off` | Output channel specifications for any events coming in on configured instances to help with configuration |
+| `mdns-bind` | `10.1.2.1 5353` | `0.0.0.0 5353` | Bind host for the mDNS discovery server |
+| `mdns-name` | `computer1` | none | mDNS hostname to announce, also used as AppleMIDI peer name |
+
+#### Instance configuration
+
+Common instance configuration parameters
+
+| Option | Example value | Default value | Description |
+|---------------|-----------------------|-----------------------|-----------------------|
+| `ssrc` | `0xDEADBEEF` | Randomly generated | 32-bit synchronization source identifier |
+| `mode` | `direct` | none | Instance session management mode (`direct` or `apple`) |
+
+`direct` mode instance configuration parameters
+
+| Option | Example value | Default value | Description |
+|---------------|-----------------------|-----------------------|-----------------------|
+| `bind` | `10.1.2.1 9001` | `0.0.0.0 <random>` | Local network address to bind to |
+| `learn` | `true` | `false` | Accept new peers for data exchange at runtime |
+| `peer` | `10.1.2.3 9001` | none | MIDI session peer, may be specified multiple times |
+
+`apple` mode instance configuration parameters
+
+| Option | Example value | Default value | Description |
+|---------------|-----------------------|-----------------------|-----------------------|
+| `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) |
+
+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.
+
+#### Channel specification
+
+The `rtpmidi` backend supports mapping different MIDI events to MIDIMonster channels. The currently supported event types are
+
+* `cc` - Control Changes
+* `note` - Note On/Off messages
+* `pressure` - Note pressure/aftertouch messages
+* `aftertouch` - Channel-wide aftertouch messages
+* `pitch` - Channel pitchbend messages
+
+A MIDIMonster channel is specified using the syntax `channel<channel>.<type><index>`. The shorthand `ch` may be
+used instead of the word `channel` (Note that `channel` here refers to the MIDI channel number).
+
+The `pitch` and `aftertouch` events are channel-wide, thus they can be specified as `channel<channel>.<type>`.
+
+MIDI channels range from `0` to `15`. Each MIDI channel consists of 128 notes (numbered `0` through `127`), which
+additionally each have a pressure control, 128 CC's (numbered likewise), a channel pressure control (also called
+'channel aftertouch') and a pitch control which may all be mapped to individual MIDIMonster channels.
+
+Example mappings:
+
+```
+rmidi1.ch0.note9 > rmidi2.channel1.cc4
+rmidi1.channel15.pressure1 > rmidi1.channel0.note0
+rmidi1.ch1.aftertouch > rmidi2.ch2.cc0
+rmidi1.ch0.pitch > rmidi2.ch1.pitch
+```
+
+#### Known bugs / problems