aboutsummaryrefslogtreecommitdiffhomepage
path: root/backends
diff options
context:
space:
mode:
Diffstat (limited to 'backends')
-rw-r--r--backends/Makefile8
-rw-r--r--backends/rtpmidi.c290
-rw-r--r--backends/rtpmidi.h94
3 files changed, 390 insertions, 2 deletions
diff --git a/backends/Makefile b/backends/Makefile
index feefd7b..df01ec8 100644
--- a/backends/Makefile
+++ b/backends/Makefile
@@ -1,7 +1,7 @@
.PHONY: all clean full
LINUX_BACKENDS = midi.so evdev.so
-WINDOWS_BACKENDS = artnet.dll osc.dll loopback.dll sacn.dll maweb.dll winmidi.dll
-BACKENDS = artnet.so osc.so loopback.so sacn.so lua.so maweb.so jack.so
+WINDOWS_BACKENDS = artnet.dll osc.dll loopback.dll sacn.dll maweb.dll winmidi.dll rtpmidi.dll
+BACKENDS = artnet.so osc.so loopback.so sacn.so lua.so maweb.so jack.so rtpmidi.so
OPTIONAL_BACKENDS = ola.so
BACKEND_LIB = libmmbackend.o
@@ -38,6 +38,10 @@ maweb.dll: ADDITIONAL_OBJS += $(BACKEND_LIB)
maweb.dll: LDLIBS += -lws2_32
maweb.dll: CFLAGS += -DMAWEB_NO_LIBSSL
+rtpmidi.so: ADDITIONAL_OBJS += $(BACKEND_LIB)
+rtpmidi.dll: ADDITIONAL_OBJS += $(BACKEND_LIB)
+rtpmidi.dll: LDLIBS += -lws2_32
+
winmidi.dll: ADDITIONAL_OBJS += $(BACKEND_LIB)
winmidi.dll: LDLIBS += -lwinmm -lws2_32
diff --git a/backends/rtpmidi.c b/backends/rtpmidi.c
new file mode 100644
index 0000000..6a8032a
--- /dev/null
+++ b/backends/rtpmidi.c
@@ -0,0 +1,290 @@
+#include <sys/socket.h>
+#include <sys/types.h>
+#include <netdb.h>
+#include <string.h>
+#include <errno.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <ctype.h>
+
+#include "rtpmidi.h"
+
+#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;
+} cfg = {
+ .mdns_fd = -1,
+ .mdns_name = NULL,
+ .nfds = 0,
+ .fds = NULL
+};
+
+int init(){
+ backend rtpmidi = {
+ .name = BACKEND_NAME,
+ .conf = rtpmidi_configure,
+ .create = rtpmidi_instance,
+ .conf_instance = rtpmidi_configure_instance,
+ .channel = rtpmidi_channel,
+ .handle = rtpmidi_set,
+ .process = rtpmidi_handle,
+ .start = rtpmidi_start,
+ .shutdown = rtpmidi_shutdown
+ };
+
+ 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){
+ 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){
+ if(!strcmp(option, "mdns-name")){
+ if(cfg.mdns_name){
+ fprintf(stderr, "Duplicate mdns-name assignment\n");
+ return 1;
+ }
+
+ cfg.mdns_name = strdup(value);
+ if(!cfg.mdns_name){
+ fprintf(stderr, "Failed to allocate memory\n");
+ return 1;
+ }
+ return 0;
+ }
+ else if(!strcmp(option, "mdns-bind")){
+ if(cfg.mdns_fd >= 0){
+ fprintf(stderr, "Only one mDNS discovery bind is supported\n");
+ return 1;
+ }
+
+
+ //TODO create mdns broadcast/responder socket
+ }
+ else if(!strcmp(option, "bind")){
+ //TODO open listening data fd for raw rtpmidi instance
+ }
+ else if(!strcmp(option, "apple-bind")){
+ //TODO open control and data fd for applemidi session/rtpmidi+apple-extensions instance
+ }
+
+ fprintf(stderr, "Unknown rtpMIDI backend option %s\n", option);
+ return 1;
+}
+
+static int rtpmidi_configure_instance(instance* inst, char* option, char* value){
+ //TODO
+ return 1;
+}
+
+static instance* rtpmidi_instance(){
+ //TODO
+ return NULL;
+}
+
+static channel* rtpmidi_channel(instance* inst, char* spec){
+ //TODO
+ return NULL;
+}
+
+static int rtpmidi_set(instance* inst, size_t num, channel** c, channel_value* v){
+ //TODO
+ return 1;
+}
+
+static int rtpmidi_handle(size_t num, managed_fd* fds){
+ //TODO handle discovery
+
+ if(!num){
+ return 0;
+ }
+
+ //TODO
+ return 1;
+}
+
+static int rtpmidi_start(){
+ size_t n, u, p;
+ int rv = 1;
+ instance** inst = NULL;
+ rtpmidi_instance_data* data = NULL;
+
+ //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;
+ }
+
+ if(cfg.mdns_fd < 0){
+ fprintf(stderr, "No mDNS discovery interface bound, APPLEMIDI session support disabled\n");
+ }
+
+ //TODO initialize all instances
+
+ rv = 0;
+bail:
+ free(inst);
+ return rv;
+}
+
+static int rtpmidi_shutdown(){
+ size_t u;
+
+ free(cfg.mdns_name);
+ if(cfg.mdns_fd >= 0){
+ close(cfg.mdns_fd);
+ }
+
+ for(u = 0; u < cfg.nfds; u++){
+ if(cfg.fds[u].data >= 0){
+ close(cfg.fds[u].data);
+ }
+ if(cfg.fds[u].control >= 0){
+ close(cfg.fds[u].control);
+ }
+ }
+ return 0;
+}
diff --git a/backends/rtpmidi.h b/backends/rtpmidi.h
new file mode 100644
index 0000000..f14357f
--- /dev/null
+++ b/backends/rtpmidi.h
@@ -0,0 +1,94 @@
+#include <sys/socket.h>
+#include "midimonster.h"
+
+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 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();
+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
+
+typedef enum /*_rtpmidi_peer_mode*/ {
+ peer_learned,
+ peer_invited,
+ peer_invited_by,
+ peer_sync,
+ peer_connect
+} rtpmidi_peer_mode;
+
+typedef struct /*_rtpmidi_peer*/ {
+ rtpmidi_peer_mode mode;
+ struct sockaddr_storage dest;
+ socklen_t dest_len;
+ uint32_t ssrc;
+} rtpmidi_peer;
+
+typedef struct /*_rtpmidi_fd*/ {
+ int data;
+ int control;
+} rtpmidi_fd;
+
+typedef struct /*_rtmidi_instance_data*/ {
+ size_t fd_index;
+ size_t npeers;
+ rtpmidi_peer* peers;
+ uint32_t ssrc;
+
+ //apple-midi config
+ char* session_name;
+ char* invite_peers;
+ char* invite_accept;
+
+ //generic mode config
+ uint8_t learn_peers;
+} rtpmidi_instance_data;
+
+#pragma pack(push, 1)
+typedef struct /*_apple_session_command*/ {
+ uint16_t res1;
+ uint8_t command[2];
+ uint32_t version;
+ uint32_t token;
+ uint32_t ssrc;
+ //char* name
+} apple_command;
+
+typedef struct /*_apple_session_sync*/ {
+ uint16_t res1;
+ uint8_t command[2];
+ uint32_t ssrc;
+ uint8_t count;
+ uint8_t res2[3];
+ uint64_t timestamp[3];
+} apple_sync;
+
+typedef struct /*_apple_session_feedback*/ {
+ uint16_t res1;
+ uint8_t command[2];
+ uint32_t ssrc;
+ uint32_t sequence;
+} apple_feedback;
+
+typedef struct /*_rtp_midi_header*/ {
+ uint16_t vpxccmpt; //this is really just an amalgamated constant value
+ uint16_t sequence;
+ uint32_t timestamp;
+ uint32_t ssrc;
+} rtpmidi_header;
+
+typedef struct /*_rtp_midi_command*/ {
+ uint8_t flags;
+ uint8_t additional_length;
+} rtpmidi_command;
+#pragma pack(pop)