diff options
Diffstat (limited to 'backends')
-rw-r--r-- | backends/Makefile | 2 | ||||
-rw-r--r-- | backends/rtpmidi.c | 290 | ||||
-rw-r--r-- | backends/rtpmidi.h | 94 |
3 files changed, 385 insertions, 1 deletions
diff --git a/backends/Makefile b/backends/Makefile index 446ad70..771e97e 100644 --- a/backends/Makefile +++ b/backends/Makefile @@ -1,6 +1,6 @@ .PHONY: all clean LINUX_BACKENDS = midi.so evdev.so -BACKENDS = artnet.so osc.so loopback.so sacn.so +BACKENDS = artnet.so osc.so loopback.so sacn.so rtpmidi.so SYSTEM := $(shell uname -s) 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) |