From 2dfc564edc0c89c4a8de7e384806aae5d593426d Mon Sep 17 00:00:00 2001 From: cbdev Date: Fri, 2 Mar 2018 03:20:11 +0100 Subject: Move backend implementations to subdirectory --- Makefile | 32 +-- artnet.c | 562 ------------------------------------ artnet.h | 82 ------ backends/Makefile | 17 ++ backends/artnet.c | 562 ++++++++++++++++++++++++++++++++++++ backends/artnet.h | 82 ++++++ backends/evdev.c | 401 ++++++++++++++++++++++++++ backends/evdev.h | 31 ++ backends/loopback.c | 120 ++++++++ backends/loopback.h | 16 ++ backends/midi.c | 366 +++++++++++++++++++++++ backends/midi.h | 17 ++ backends/osc.c | 813 ++++++++++++++++++++++++++++++++++++++++++++++++++++ backends/osc.h | 55 ++++ backends/sacn.c | 736 +++++++++++++++++++++++++++++++++++++++++++++++ backends/sacn.h | 126 ++++++++ evdev.c | 401 -------------------------- evdev.h | 31 -- loopback.c | 120 -------- loopback.h | 16 -- midi.c | 366 ----------------------- midi.h | 17 -- osc.c | 813 ---------------------------------------------------- osc.h | 55 ---- sacn.c | 736 ----------------------------------------------- sacn.h | 126 -------- 26 files changed, 3354 insertions(+), 3345 deletions(-) delete mode 100644 artnet.c delete mode 100644 artnet.h create mode 100644 backends/Makefile create mode 100644 backends/artnet.c create mode 100644 backends/artnet.h create mode 100644 backends/evdev.c create mode 100644 backends/evdev.h create mode 100644 backends/loopback.c create mode 100644 backends/loopback.h create mode 100644 backends/midi.c create mode 100644 backends/midi.h create mode 100644 backends/osc.c create mode 100644 backends/osc.h create mode 100644 backends/sacn.c create mode 100644 backends/sacn.h delete mode 100644 evdev.c delete mode 100644 evdev.h delete mode 100644 loopback.c delete mode 100644 loopback.h delete mode 100644 midi.c delete mode 100644 midi.h delete mode 100644 osc.c delete mode 100644 osc.h delete mode 100644 sacn.c delete mode 100644 sacn.h diff --git a/Makefile b/Makefile index 5f403ac..4a85eab 100644 --- a/Makefile +++ b/Makefile @@ -1,36 +1,28 @@ -.PHONY: all clean run sanitize -BACKENDS = artnet.so midi.so osc.so loopback.so evdev.so sacn.so +.PHONY: all clean run sanitize backends OBJS = config.o backend.o plugin.o -PLUGINDIR = "\"./\"" +PLUGINDIR = "\"./backends/\"" CFLAGS ?= -g -Wall +LDLIBS = -ldl +CFLAGS += -DPLUGINS=$(PLUGINDIR) #CFLAGS += -DDEBUG -%.so: CFLAGS += -fPIC -%.so: LDFLAGS += -shared +LDFLAGS += -Wl,-export-dynamic -midimonster: LDLIBS = -ldl -midimonster: CFLAGS += -DPLUGINS=$(PLUGINDIR) -midimonster: LDFLAGS += -Wl,-export-dynamic -midi.so: LDLIBS = -lasound -evdev.so: CFLAGS += $(shell pkg-config --cflags libevdev) -evdev.so: LDLIBS = $(shell pkg-config --libs libevdev) +all: midimonster backends - -%.so :: %.c %.h - $(CC) $(CFLAGS) $(LDLIBS) $< -o $@ $(LDFLAGS) - -all: midimonster $(BACKENDS) +backends: + $(MAKE) -C backends midimonster: midimonster.c $(OBJS) clean: $(RM) midimonster $(RM) $(OBJS) - $(RM) $(BACKENDS) + $(MAKE) -C backends clean run: valgrind --leak-check=full --show-leak-kinds=all ./midimonster -sanitize: CC = clang -sanitize: CFLAGS = -g -Wall -Wpedantic -fsanitize=address -fsanitize=undefined -fno-omit-frame-pointer -sanitize: midimonster $(BACKENDS) +sanitize: export CC = clang +sanitize: export CFLAGS = -g -Wall -Wpedantic -fsanitize=address -fsanitize=undefined -fno-omit-frame-pointer +sanitize: midimonster backends diff --git a/artnet.c b/artnet.c deleted file mode 100644 index c16e630..0000000 --- a/artnet.c +++ /dev/null @@ -1,562 +0,0 @@ -#include -#include -#include -#include -#include -#include -#include -#include - -#include "artnet.h" -#define MAX_FDS 255 -#define BACKEND_NAME "artnet" - -static uint8_t default_net = 0; -static size_t artnet_fds = 0; -static artnet_descriptor* artnet_fd = NULL; - -static int artnet_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; - - if(artnet_fds >= MAX_FDS){ - fprintf(stderr, "ArtNet backend descriptor limit reached\n"); - return -1; - } - - 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 ArtNet descriptor nonblocking\n"); - return -1; - } - - //store fd - artnet_fd = realloc(artnet_fd, (artnet_fds + 1) * sizeof(artnet_descriptor)); - if(!artnet_fd){ - fprintf(stderr, "Failed to allocate memory\n"); - return -1; - } - - fprintf(stderr, "ArtNet backend interface %zu bound to %s port %s\n", artnet_fds, host, port); - artnet_fd[artnet_fds].fd = fd; - artnet_fd[artnet_fds].output_instances = 0; - artnet_fd[artnet_fds].output_instance = NULL; - artnet_fd[artnet_fds].last_frame = NULL; - artnet_fds++; - return 0; -} - -static int artnet_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 artnet_separate_hostspec(char* in, char** host, char** 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 = ARTNET_PORT; - } - return 0; -} - -int init(){ - backend artnet = { - .name = BACKEND_NAME, - .conf = artnet_configure, - .create = artnet_instance, - .conf_instance = artnet_configure_instance, - .channel = artnet_channel, - .handle = artnet_set, - .process = artnet_handle, - .start = artnet_start, - .shutdown = artnet_shutdown - }; - - //register backend - if(mm_backend_register(artnet)){ - fprintf(stderr, "Failed to register ArtNet backend\n"); - return 1; - } - return 0; -} - -static int artnet_configure(char* option, char* value){ - char* host = NULL, *port = NULL; - if(!strcmp(option, "net")){ - //configure default net - default_net = strtoul(value, NULL, 0); - return 0; - } - else if(!strcmp(option, "bind")){ - if(artnet_separate_hostspec(value, &host, &port)){ - fprintf(stderr, "Not a valid ArtNet bind address: %s\n", value); - return 1; - } - - if(artnet_listener(host, port)){ - fprintf(stderr, "Failed to bind ArtNet descriptor: %s\n", value); - return 1; - } - return 0; - } - - fprintf(stderr, "Unknown ArtNet backend option %s\n", option); - return 1; -} - -static instance* artnet_instance(){ - artnet_instance_data* data = NULL; - instance* inst = mm_instance(); - if(!inst){ - return NULL; - } - - data = calloc(1, sizeof(artnet_instance_data)); - if(!data){ - fprintf(stderr, "Failed to allocate memory\n"); - return NULL; - } - - data->net = default_net; - - inst->impl = data; - return inst; -} - -static int artnet_configure_instance(instance* inst, char* option, char* value){ - char* host = NULL, *port = NULL; - artnet_instance_data* data = (artnet_instance_data*) inst->impl; - - if(!strcmp(option, "net")){ - data->net = strtoul(value, NULL, 0); - return 0; - } - else if(!strcmp(option, "uni") || !strcmp(option, "universe")){ - data->uni = strtoul(value, NULL, 0); - return 0; - } - else if(!strcmp(option, "iface") || !strcmp(option, "interface")){ - data->fd_index = strtoul(value, NULL, 0); - - if(data->fd_index >= artnet_fds){ - fprintf(stderr, "Invalid interface configured for ArtNet instance %s\n", inst->name); - return 1; - } - return 0; - } - else if(!strcmp(option, "dest") || !strcmp(option, "destination")){ - if(artnet_separate_hostspec(value, &host, &port)){ - fprintf(stderr, "Not a valid ArtNet destination for instance %s\n", inst->name); - return 1; - } - - return artnet_parse_addr(host, port, &data->dest_addr, &data->dest_len); - } - - fprintf(stderr, "Unknown ArtNet option %s for instance %s\n", option, inst->name); - return 1; -} - -static channel* artnet_channel(instance* inst, char* spec){ - artnet_instance_data* data = (artnet_instance_data*) inst->impl; - char* spec_next = spec; - unsigned chan_a = strtoul(spec, &spec_next, 10); - unsigned chan_b = 0; - - //primary channel sanity check - if(!chan_a || chan_a > 512){ - fprintf(stderr, "Invalid ArtNet channel specification %s\n", spec); - return NULL; - } - chan_a--; - - //secondary channel setup - if(*spec_next == '+'){ - chan_b = strtoul(spec_next + 1, NULL, 10); - if(!chan_b || chan_b > 512){ - fprintf(stderr, "Invalid wide-channel spec %s\n", spec); - return NULL; - } - chan_b--; - - //if mapped mode differs, bail - if(IS_ACTIVE(data->data.map[chan_b]) && data->data.map[chan_b] != (MAP_FINE | chan_a)){ - fprintf(stderr, "Fine channel already mapped for ArtNet spec %s\n", spec); - return NULL; - } - - data->data.map[chan_b] = MAP_FINE | chan_a; - } - - //check current map mode - if(IS_ACTIVE(data->data.map[chan_a])){ - if((*spec_next == '+' && data->data.map[chan_a] != (MAP_COARSE | chan_b)) - || (*spec_next != '+' && data->data.map[chan_a] != (MAP_SINGLE | chan_a))){ - fprintf(stderr, "Primary ArtNet channel already mapped at differing mode: %s\n", spec); - return NULL; - } - } - data->data.map[chan_a] = (*spec_next == '+') ? (MAP_COARSE | chan_b) : (MAP_SINGLE | chan_a); - - return mm_channel(inst, chan_a, 1); -} - -static int artnet_transmit(instance* inst){ - size_t u; - artnet_instance_data* data = (artnet_instance_data*) inst->impl; - //output frame - artnet_pkt frame = { - .magic = {'A', 'r', 't', '-', 'N', 'e', 't', 0x00}, - .opcode = htobe16(OpDmx), - .version = htobe16(ARTNET_VERSION), - .sequence = data->data.seq++, - .port = 0, - .universe = data->uni, - .net = data->net, - .length = htobe16(512), - .data = {0} - }; - memcpy(frame.data, data->data.out, 512); - - if(sendto(artnet_fd[data->fd_index].fd, &frame, sizeof(frame), 0, (struct sockaddr*) &data->dest_addr, data->dest_len) < 0){ - fprintf(stderr, "Failed to output ArtNet frame for instance %s: %s\n", inst->name, strerror(errno)); - } - - //update last frame timestamp - for(u = 0; u < artnet_fd[data->fd_index].output_instances; u++){ - if(artnet_fd[data->fd_index].output_instance[u].label == inst->ident){ - artnet_fd[data->fd_index].last_frame[u] = mm_timestamp(); - } - } - return 0; -} - -static int artnet_set(instance* inst, size_t num, channel** c, channel_value* v){ - size_t u, mark = 0; - artnet_instance_data* data = (artnet_instance_data*) inst->impl; - - if(!data->dest_len){ - fprintf(stderr, "ArtNet instance %s not enabled for output (%zu channel events)\n", inst->name, num); - return 0; - } - - //FIXME maybe introduce minimum frame interval - for(u = 0; u < num; u++){ - if(IS_WIDE(data->data.map[c[u]->ident])){ - uint32_t val = v[u].normalised * ((double) 0xFFFF); - //the primary (coarse) channel is the one registered to the core, so we don't have to check for that - if(data->data.out[c[u]->ident] != ((val >> 8) & 0xFF)){ - mark = 1; - data->data.out[c[u]->ident] = (val >> 8) & 0xFF; - } - - if(data->data.out[MAPPED_CHANNEL(data->data.map[c[u]->ident])] != (val & 0xFF)){ - mark = 1; - data->data.out[MAPPED_CHANNEL(data->data.map[c[u]->ident])] = val & 0xFF; - } - } - else if(data->data.out[c[u]->ident] != (v[u].normalised * 255.0)){ - mark = 1; - data->data.out[c[u]->ident] = v[u].normalised * 255.0; - } - } - - if(mark){ - return artnet_transmit(inst); - } - - return 0; -} - -static inline int artnet_process_frame(instance* inst, artnet_pkt* frame){ - size_t p, max_mark = 0; - uint16_t wide_val = 0; - channel* chan = NULL; - channel_value val; - artnet_instance_data* data = (artnet_instance_data*) inst->impl; - - if(be16toh(frame->length) > 512){ - fprintf(stderr, "Invalid ArtNet frame channel count\n"); - return 1; - } - - //read data, mark update channels - for(p = 0; p < be16toh(frame->length); p++){ - if(IS_ACTIVE(data->data.map[p]) && frame->data[p] != data->data.in[p]){ - data->data.in[p] = frame->data[p]; - data->data.map[p] |= MAP_MARK; - max_mark = p; - } - } - - //generate events - for(p = 0; p <= max_mark; p++){ - if(data->data.map[p] & MAP_MARK){ - data->data.map[p] &= ~MAP_MARK; - if(data->data.map[p] & MAP_FINE){ - chan = mm_channel(inst, MAPPED_CHANNEL(data->data.map[p]), 0); - } - else{ - chan = mm_channel(inst, p, 0); - } - - if(!chan){ - fprintf(stderr, "Active channel %zu on %s not known to core\n", p, inst->name); - return 1; - } - - if(IS_WIDE(data->data.map[p])){ - data->data.map[MAPPED_CHANNEL(data->data.map[p])] &= ~MAP_MARK; - wide_val = data->data.in[p] << ((data->data.map[p] & MAP_COARSE) ? 8 : 0); - wide_val |= data->data.in[MAPPED_CHANNEL(data->data.map[p])] << ((data->data.map[p] & MAP_COARSE) ? 0 : 8); - - val.raw.u64 = wide_val; - val.normalised = (double) wide_val / (double) 0xFFFF; - } - else{ - //single channel - val.raw.u64 = data->data.in[p]; - val.normalised = (double) data->data.in[p] / 255.0; - } - - if(mm_channel_event(chan, val)){ - fprintf(stderr, "Failed to push ArtNet channel event to core\n"); - return 1; - } - } - } - return 0; -} - -static int artnet_handle(size_t num, managed_fd* fds){ - size_t u, c; - uint64_t timestamp = mm_timestamp(); - ssize_t bytes_read; - char recv_buf[ARTNET_RECV_BUF]; - artnet_instance_id inst_id = { - .label = 0 - }; - instance* inst = NULL; - artnet_pkt* frame = (artnet_pkt*) recv_buf; - - //transmit keepalive frames - for(u = 0; u < artnet_fds; u++){ - for(c = 0; c < artnet_fd[u].output_instances; c++){ - if(timestamp - artnet_fd[u].last_frame[c] >= ARTNET_KEEPALIVE_INTERVAL){ - inst = mm_instance_find(BACKEND_NAME, artnet_fd[u].output_instance[c].label); - if(inst){ - artnet_transmit(inst); - } - } - } - } - - if(!num){ - //early exit - return 0; - } - - for(u = 0; u < num; u++){ - do{ - bytes_read = recv(fds[u].fd, recv_buf, sizeof(recv_buf), 0); - if(bytes_read > 0 && bytes_read > sizeof(artnet_hdr)){ - if(!memcmp(frame->magic, "Art-Net\0", 8) && be16toh(frame->opcode) == OpDmx){ - //find matching instance - inst_id.fields.fd_index = ((uint64_t) fds[u].impl) & 0xFF; - inst_id.fields.net = frame->net; - inst_id.fields.uni = frame->universe; - inst = mm_instance_find(BACKEND_NAME, inst_id.label); - if(inst && artnet_process_frame(inst, frame)){ - fprintf(stderr, "Failed to process ArtNet frame\n"); - } - } - } - } while(bytes_read > 0); - - if(bytes_read < 0 && errno != EAGAIN){ - fprintf(stderr, "ArtNet failed to receive data: %s\n", strerror(errno)); - } - - if(bytes_read == 0){ - fprintf(stderr, "ArtNet listener closed\n"); - return 1; - } - } - - return 0; -} - -static int artnet_start(){ - size_t n, u, p; - int rv = 1; - instance** inst = NULL; - artnet_instance_data* data = NULL; - artnet_instance_id id = { - .label = 0 - }; - - //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(!artnet_fds){ - fprintf(stderr, "Failed to start ArtNet backend: no descriptors bound\n"); - return 1; - } - - for(u = 0; u < n; u++){ - data = (artnet_instance_data*) inst[u]->impl; - //set instance identifier - id.fields.fd_index = data->fd_index; - id.fields.net = data->net; - id.fields.uni = data->uni; - inst[u]->ident = id.label; - - //check for duplicates - for(p = 0; p < u; p++){ - if(inst[u]->ident == inst[p]->ident){ - fprintf(stderr, "Universe specified multiple times, use one instance: %s - %s\n", inst[u]->name, inst[p]->name); - goto bail; - } - } - - //if enabled for output, add to keepalive tracking - if(data->dest_len){ - artnet_fd[data->fd_index].output_instance = realloc(artnet_fd[data->fd_index].output_instance, (artnet_fd[data->fd_index].output_instances + 1) * sizeof(artnet_instance_id)); - artnet_fd[data->fd_index].last_frame = realloc(artnet_fd[data->fd_index].last_frame, (artnet_fd[data->fd_index].output_instances + 1) * sizeof(uint64_t)); - - if(!artnet_fd[data->fd_index].output_instance || !artnet_fd[data->fd_index].last_frame){ - fprintf(stderr, "Failed to allocate memory\n"); - goto bail; - } - artnet_fd[data->fd_index].output_instance[artnet_fd[data->fd_index].output_instances] = id; - artnet_fd[data->fd_index].last_frame[artnet_fd[data->fd_index].output_instances] = 0; - - artnet_fd[data->fd_index].output_instances++; - } - } - - fprintf(stderr, "ArtNet backend registering %zu descriptors to core\n", artnet_fds); - for(u = 0; u < artnet_fds; u++){ - if(mm_manage_fd(artnet_fd[u].fd, BACKEND_NAME, 1, (void*) u)){ - goto bail; - } - } - - rv = 0; -bail: - free(inst); - return rv; -} - -static int artnet_shutdown(){ - size_t n, p; - instance** inst = NULL; - if(mm_backend_instances(BACKEND_NAME, &n, &inst)){ - fprintf(stderr, "Failed to fetch instance list\n"); - return 1; - } - - for(p = 0; p < n; p++){ - free(inst[p]->impl); - } - free(inst); - - for(p = 0; p < artnet_fds; p++){ - close(artnet_fd[p].fd); - free(artnet_fd[p].output_instance); - free(artnet_fd[p].last_frame); - } - free(artnet_fd); - - fprintf(stderr, "ArtNet backend shut down\n"); - return 0; -} diff --git a/artnet.h b/artnet.h deleted file mode 100644 index 90aedd5..0000000 --- a/artnet.h +++ /dev/null @@ -1,82 +0,0 @@ -#include -#include "midimonster.h" - -int init(); -static int artnet_configure(char* option, char* value); -static int artnet_configure_instance(instance* instance, char* option, char* value); -static instance* artnet_instance(); -static channel* artnet_channel(instance* instance, char* spec); -static int artnet_set(instance* inst, size_t num, channel** c, channel_value* v); -static int artnet_handle(size_t num, managed_fd* fds); -static int artnet_start(); -static int artnet_shutdown(); - -#define ARTNET_PORT "6454" -#define ARTNET_VERSION 14 -#define ARTNET_RECV_BUF 4096 -#define ARTNET_KEEPALIVE_INTERVAL 2000 - -#define MAP_COARSE 0x0200 -#define MAP_FINE 0x0400 -#define MAP_SINGLE 0x0800 -#define MAP_MARK 0x1000 -#define MAPPED_CHANNEL(a) ((a) & 0x01FF) -#define IS_ACTIVE(a) ((a) & 0xFE00) -#define IS_WIDE(a) ((a) & (MAP_FINE | MAP_COARSE)) -#define IS_SINGLE(a) ((a) & MAP_SINGLE) - -typedef struct /*_artnet_universe_model*/ { - uint8_t seq; - uint8_t in[512]; - uint8_t out[512]; - uint16_t map[512]; -} artnet_universe; - -typedef struct /*_artnet_instance_model*/ { - uint8_t net; - uint8_t uni; - struct sockaddr_storage dest_addr; - socklen_t dest_len; - artnet_universe data; - size_t fd_index; -} artnet_instance_data; - -typedef union /*_artnet_instance_id*/ { - struct { - uint8_t fd_index; - uint8_t net; - uint8_t uni; - } fields; - uint64_t label; -} artnet_instance_id; - -typedef struct /*_artnet_fd*/ { - int fd; - size_t output_instances; - artnet_instance_id* output_instance; - uint64_t* last_frame; -} artnet_descriptor; - -#pragma pack(push, 1) -typedef struct /*_artnet_hdr*/ { - uint8_t magic[8]; - uint16_t opcode; - uint16_t version; -} artnet_hdr; - -typedef struct /*_artnet_pkt*/ { - uint8_t magic[8]; - uint16_t opcode; - uint16_t version; - uint8_t sequence; - uint8_t port; - uint8_t universe; - uint8_t net; - uint16_t length; - uint8_t data[512]; -} artnet_pkt; -#pragma pack(pop) - -enum artnet_pkt_opcode { - OpDmx = 0x0050 -}; diff --git a/backends/Makefile b/backends/Makefile new file mode 100644 index 0000000..85fe152 --- /dev/null +++ b/backends/Makefile @@ -0,0 +1,17 @@ +.PHONY: all clean +BACKENDS = artnet.so midi.so osc.so loopback.so evdev.so sacn.so + +CFLAGS += -fPIC -I../ +LDFLAGS += -shared + +midi.so: LDLIBS = -lasound +evdev.so: CFLAGS += $(shell pkg-config --cflags libevdev) +evdev.so: LDLIBS = $(shell pkg-config --libs libevdev) + +%.so :: %.c %.h + $(CC) $(CFLAGS) $(LDLIBS) $< -o $@ $(LDFLAGS) + +all: $(BACKENDS) + +clean: + $(RM) $(BACKENDS) diff --git a/backends/artnet.c b/backends/artnet.c new file mode 100644 index 0000000..c16e630 --- /dev/null +++ b/backends/artnet.c @@ -0,0 +1,562 @@ +#include +#include +#include +#include +#include +#include +#include +#include + +#include "artnet.h" +#define MAX_FDS 255 +#define BACKEND_NAME "artnet" + +static uint8_t default_net = 0; +static size_t artnet_fds = 0; +static artnet_descriptor* artnet_fd = NULL; + +static int artnet_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; + + if(artnet_fds >= MAX_FDS){ + fprintf(stderr, "ArtNet backend descriptor limit reached\n"); + return -1; + } + + 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 ArtNet descriptor nonblocking\n"); + return -1; + } + + //store fd + artnet_fd = realloc(artnet_fd, (artnet_fds + 1) * sizeof(artnet_descriptor)); + if(!artnet_fd){ + fprintf(stderr, "Failed to allocate memory\n"); + return -1; + } + + fprintf(stderr, "ArtNet backend interface %zu bound to %s port %s\n", artnet_fds, host, port); + artnet_fd[artnet_fds].fd = fd; + artnet_fd[artnet_fds].output_instances = 0; + artnet_fd[artnet_fds].output_instance = NULL; + artnet_fd[artnet_fds].last_frame = NULL; + artnet_fds++; + return 0; +} + +static int artnet_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 artnet_separate_hostspec(char* in, char** host, char** 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 = ARTNET_PORT; + } + return 0; +} + +int init(){ + backend artnet = { + .name = BACKEND_NAME, + .conf = artnet_configure, + .create = artnet_instance, + .conf_instance = artnet_configure_instance, + .channel = artnet_channel, + .handle = artnet_set, + .process = artnet_handle, + .start = artnet_start, + .shutdown = artnet_shutdown + }; + + //register backend + if(mm_backend_register(artnet)){ + fprintf(stderr, "Failed to register ArtNet backend\n"); + return 1; + } + return 0; +} + +static int artnet_configure(char* option, char* value){ + char* host = NULL, *port = NULL; + if(!strcmp(option, "net")){ + //configure default net + default_net = strtoul(value, NULL, 0); + return 0; + } + else if(!strcmp(option, "bind")){ + if(artnet_separate_hostspec(value, &host, &port)){ + fprintf(stderr, "Not a valid ArtNet bind address: %s\n", value); + return 1; + } + + if(artnet_listener(host, port)){ + fprintf(stderr, "Failed to bind ArtNet descriptor: %s\n", value); + return 1; + } + return 0; + } + + fprintf(stderr, "Unknown ArtNet backend option %s\n", option); + return 1; +} + +static instance* artnet_instance(){ + artnet_instance_data* data = NULL; + instance* inst = mm_instance(); + if(!inst){ + return NULL; + } + + data = calloc(1, sizeof(artnet_instance_data)); + if(!data){ + fprintf(stderr, "Failed to allocate memory\n"); + return NULL; + } + + data->net = default_net; + + inst->impl = data; + return inst; +} + +static int artnet_configure_instance(instance* inst, char* option, char* value){ + char* host = NULL, *port = NULL; + artnet_instance_data* data = (artnet_instance_data*) inst->impl; + + if(!strcmp(option, "net")){ + data->net = strtoul(value, NULL, 0); + return 0; + } + else if(!strcmp(option, "uni") || !strcmp(option, "universe")){ + data->uni = strtoul(value, NULL, 0); + return 0; + } + else if(!strcmp(option, "iface") || !strcmp(option, "interface")){ + data->fd_index = strtoul(value, NULL, 0); + + if(data->fd_index >= artnet_fds){ + fprintf(stderr, "Invalid interface configured for ArtNet instance %s\n", inst->name); + return 1; + } + return 0; + } + else if(!strcmp(option, "dest") || !strcmp(option, "destination")){ + if(artnet_separate_hostspec(value, &host, &port)){ + fprintf(stderr, "Not a valid ArtNet destination for instance %s\n", inst->name); + return 1; + } + + return artnet_parse_addr(host, port, &data->dest_addr, &data->dest_len); + } + + fprintf(stderr, "Unknown ArtNet option %s for instance %s\n", option, inst->name); + return 1; +} + +static channel* artnet_channel(instance* inst, char* spec){ + artnet_instance_data* data = (artnet_instance_data*) inst->impl; + char* spec_next = spec; + unsigned chan_a = strtoul(spec, &spec_next, 10); + unsigned chan_b = 0; + + //primary channel sanity check + if(!chan_a || chan_a > 512){ + fprintf(stderr, "Invalid ArtNet channel specification %s\n", spec); + return NULL; + } + chan_a--; + + //secondary channel setup + if(*spec_next == '+'){ + chan_b = strtoul(spec_next + 1, NULL, 10); + if(!chan_b || chan_b > 512){ + fprintf(stderr, "Invalid wide-channel spec %s\n", spec); + return NULL; + } + chan_b--; + + //if mapped mode differs, bail + if(IS_ACTIVE(data->data.map[chan_b]) && data->data.map[chan_b] != (MAP_FINE | chan_a)){ + fprintf(stderr, "Fine channel already mapped for ArtNet spec %s\n", spec); + return NULL; + } + + data->data.map[chan_b] = MAP_FINE | chan_a; + } + + //check current map mode + if(IS_ACTIVE(data->data.map[chan_a])){ + if((*spec_next == '+' && data->data.map[chan_a] != (MAP_COARSE | chan_b)) + || (*spec_next != '+' && data->data.map[chan_a] != (MAP_SINGLE | chan_a))){ + fprintf(stderr, "Primary ArtNet channel already mapped at differing mode: %s\n", spec); + return NULL; + } + } + data->data.map[chan_a] = (*spec_next == '+') ? (MAP_COARSE | chan_b) : (MAP_SINGLE | chan_a); + + return mm_channel(inst, chan_a, 1); +} + +static int artnet_transmit(instance* inst){ + size_t u; + artnet_instance_data* data = (artnet_instance_data*) inst->impl; + //output frame + artnet_pkt frame = { + .magic = {'A', 'r', 't', '-', 'N', 'e', 't', 0x00}, + .opcode = htobe16(OpDmx), + .version = htobe16(ARTNET_VERSION), + .sequence = data->data.seq++, + .port = 0, + .universe = data->uni, + .net = data->net, + .length = htobe16(512), + .data = {0} + }; + memcpy(frame.data, data->data.out, 512); + + if(sendto(artnet_fd[data->fd_index].fd, &frame, sizeof(frame), 0, (struct sockaddr*) &data->dest_addr, data->dest_len) < 0){ + fprintf(stderr, "Failed to output ArtNet frame for instance %s: %s\n", inst->name, strerror(errno)); + } + + //update last frame timestamp + for(u = 0; u < artnet_fd[data->fd_index].output_instances; u++){ + if(artnet_fd[data->fd_index].output_instance[u].label == inst->ident){ + artnet_fd[data->fd_index].last_frame[u] = mm_timestamp(); + } + } + return 0; +} + +static int artnet_set(instance* inst, size_t num, channel** c, channel_value* v){ + size_t u, mark = 0; + artnet_instance_data* data = (artnet_instance_data*) inst->impl; + + if(!data->dest_len){ + fprintf(stderr, "ArtNet instance %s not enabled for output (%zu channel events)\n", inst->name, num); + return 0; + } + + //FIXME maybe introduce minimum frame interval + for(u = 0; u < num; u++){ + if(IS_WIDE(data->data.map[c[u]->ident])){ + uint32_t val = v[u].normalised * ((double) 0xFFFF); + //the primary (coarse) channel is the one registered to the core, so we don't have to check for that + if(data->data.out[c[u]->ident] != ((val >> 8) & 0xFF)){ + mark = 1; + data->data.out[c[u]->ident] = (val >> 8) & 0xFF; + } + + if(data->data.out[MAPPED_CHANNEL(data->data.map[c[u]->ident])] != (val & 0xFF)){ + mark = 1; + data->data.out[MAPPED_CHANNEL(data->data.map[c[u]->ident])] = val & 0xFF; + } + } + else if(data->data.out[c[u]->ident] != (v[u].normalised * 255.0)){ + mark = 1; + data->data.out[c[u]->ident] = v[u].normalised * 255.0; + } + } + + if(mark){ + return artnet_transmit(inst); + } + + return 0; +} + +static inline int artnet_process_frame(instance* inst, artnet_pkt* frame){ + size_t p, max_mark = 0; + uint16_t wide_val = 0; + channel* chan = NULL; + channel_value val; + artnet_instance_data* data = (artnet_instance_data*) inst->impl; + + if(be16toh(frame->length) > 512){ + fprintf(stderr, "Invalid ArtNet frame channel count\n"); + return 1; + } + + //read data, mark update channels + for(p = 0; p < be16toh(frame->length); p++){ + if(IS_ACTIVE(data->data.map[p]) && frame->data[p] != data->data.in[p]){ + data->data.in[p] = frame->data[p]; + data->data.map[p] |= MAP_MARK; + max_mark = p; + } + } + + //generate events + for(p = 0; p <= max_mark; p++){ + if(data->data.map[p] & MAP_MARK){ + data->data.map[p] &= ~MAP_MARK; + if(data->data.map[p] & MAP_FINE){ + chan = mm_channel(inst, MAPPED_CHANNEL(data->data.map[p]), 0); + } + else{ + chan = mm_channel(inst, p, 0); + } + + if(!chan){ + fprintf(stderr, "Active channel %zu on %s not known to core\n", p, inst->name); + return 1; + } + + if(IS_WIDE(data->data.map[p])){ + data->data.map[MAPPED_CHANNEL(data->data.map[p])] &= ~MAP_MARK; + wide_val = data->data.in[p] << ((data->data.map[p] & MAP_COARSE) ? 8 : 0); + wide_val |= data->data.in[MAPPED_CHANNEL(data->data.map[p])] << ((data->data.map[p] & MAP_COARSE) ? 0 : 8); + + val.raw.u64 = wide_val; + val.normalised = (double) wide_val / (double) 0xFFFF; + } + else{ + //single channel + val.raw.u64 = data->data.in[p]; + val.normalised = (double) data->data.in[p] / 255.0; + } + + if(mm_channel_event(chan, val)){ + fprintf(stderr, "Failed to push ArtNet channel event to core\n"); + return 1; + } + } + } + return 0; +} + +static int artnet_handle(size_t num, managed_fd* fds){ + size_t u, c; + uint64_t timestamp = mm_timestamp(); + ssize_t bytes_read; + char recv_buf[ARTNET_RECV_BUF]; + artnet_instance_id inst_id = { + .label = 0 + }; + instance* inst = NULL; + artnet_pkt* frame = (artnet_pkt*) recv_buf; + + //transmit keepalive frames + for(u = 0; u < artnet_fds; u++){ + for(c = 0; c < artnet_fd[u].output_instances; c++){ + if(timestamp - artnet_fd[u].last_frame[c] >= ARTNET_KEEPALIVE_INTERVAL){ + inst = mm_instance_find(BACKEND_NAME, artnet_fd[u].output_instance[c].label); + if(inst){ + artnet_transmit(inst); + } + } + } + } + + if(!num){ + //early exit + return 0; + } + + for(u = 0; u < num; u++){ + do{ + bytes_read = recv(fds[u].fd, recv_buf, sizeof(recv_buf), 0); + if(bytes_read > 0 && bytes_read > sizeof(artnet_hdr)){ + if(!memcmp(frame->magic, "Art-Net\0", 8) && be16toh(frame->opcode) == OpDmx){ + //find matching instance + inst_id.fields.fd_index = ((uint64_t) fds[u].impl) & 0xFF; + inst_id.fields.net = frame->net; + inst_id.fields.uni = frame->universe; + inst = mm_instance_find(BACKEND_NAME, inst_id.label); + if(inst && artnet_process_frame(inst, frame)){ + fprintf(stderr, "Failed to process ArtNet frame\n"); + } + } + } + } while(bytes_read > 0); + + if(bytes_read < 0 && errno != EAGAIN){ + fprintf(stderr, "ArtNet failed to receive data: %s\n", strerror(errno)); + } + + if(bytes_read == 0){ + fprintf(stderr, "ArtNet listener closed\n"); + return 1; + } + } + + return 0; +} + +static int artnet_start(){ + size_t n, u, p; + int rv = 1; + instance** inst = NULL; + artnet_instance_data* data = NULL; + artnet_instance_id id = { + .label = 0 + }; + + //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(!artnet_fds){ + fprintf(stderr, "Failed to start ArtNet backend: no descriptors bound\n"); + return 1; + } + + for(u = 0; u < n; u++){ + data = (artnet_instance_data*) inst[u]->impl; + //set instance identifier + id.fields.fd_index = data->fd_index; + id.fields.net = data->net; + id.fields.uni = data->uni; + inst[u]->ident = id.label; + + //check for duplicates + for(p = 0; p < u; p++){ + if(inst[u]->ident == inst[p]->ident){ + fprintf(stderr, "Universe specified multiple times, use one instance: %s - %s\n", inst[u]->name, inst[p]->name); + goto bail; + } + } + + //if enabled for output, add to keepalive tracking + if(data->dest_len){ + artnet_fd[data->fd_index].output_instance = realloc(artnet_fd[data->fd_index].output_instance, (artnet_fd[data->fd_index].output_instances + 1) * sizeof(artnet_instance_id)); + artnet_fd[data->fd_index].last_frame = realloc(artnet_fd[data->fd_index].last_frame, (artnet_fd[data->fd_index].output_instances + 1) * sizeof(uint64_t)); + + if(!artnet_fd[data->fd_index].output_instance || !artnet_fd[data->fd_index].last_frame){ + fprintf(stderr, "Failed to allocate memory\n"); + goto bail; + } + artnet_fd[data->fd_index].output_instance[artnet_fd[data->fd_index].output_instances] = id; + artnet_fd[data->fd_index].last_frame[artnet_fd[data->fd_index].output_instances] = 0; + + artnet_fd[data->fd_index].output_instances++; + } + } + + fprintf(stderr, "ArtNet backend registering %zu descriptors to core\n", artnet_fds); + for(u = 0; u < artnet_fds; u++){ + if(mm_manage_fd(artnet_fd[u].fd, BACKEND_NAME, 1, (void*) u)){ + goto bail; + } + } + + rv = 0; +bail: + free(inst); + return rv; +} + +static int artnet_shutdown(){ + size_t n, p; + instance** inst = NULL; + if(mm_backend_instances(BACKEND_NAME, &n, &inst)){ + fprintf(stderr, "Failed to fetch instance list\n"); + return 1; + } + + for(p = 0; p < n; p++){ + free(inst[p]->impl); + } + free(inst); + + for(p = 0; p < artnet_fds; p++){ + close(artnet_fd[p].fd); + free(artnet_fd[p].output_instance); + free(artnet_fd[p].last_frame); + } + free(artnet_fd); + + fprintf(stderr, "ArtNet backend shut down\n"); + return 0; +} diff --git a/backends/artnet.h b/backends/artnet.h new file mode 100644 index 0000000..90aedd5 --- /dev/null +++ b/backends/artnet.h @@ -0,0 +1,82 @@ +#include +#include "midimonster.h" + +int init(); +static int artnet_configure(char* option, char* value); +static int artnet_configure_instance(instance* instance, char* option, char* value); +static instance* artnet_instance(); +static channel* artnet_channel(instance* instance, char* spec); +static int artnet_set(instance* inst, size_t num, channel** c, channel_value* v); +static int artnet_handle(size_t num, managed_fd* fds); +static int artnet_start(); +static int artnet_shutdown(); + +#define ARTNET_PORT "6454" +#define ARTNET_VERSION 14 +#define ARTNET_RECV_BUF 4096 +#define ARTNET_KEEPALIVE_INTERVAL 2000 + +#define MAP_COARSE 0x0200 +#define MAP_FINE 0x0400 +#define MAP_SINGLE 0x0800 +#define MAP_MARK 0x1000 +#define MAPPED_CHANNEL(a) ((a) & 0x01FF) +#define IS_ACTIVE(a) ((a) & 0xFE00) +#define IS_WIDE(a) ((a) & (MAP_FINE | MAP_COARSE)) +#define IS_SINGLE(a) ((a) & MAP_SINGLE) + +typedef struct /*_artnet_universe_model*/ { + uint8_t seq; + uint8_t in[512]; + uint8_t out[512]; + uint16_t map[512]; +} artnet_universe; + +typedef struct /*_artnet_instance_model*/ { + uint8_t net; + uint8_t uni; + struct sockaddr_storage dest_addr; + socklen_t dest_len; + artnet_universe data; + size_t fd_index; +} artnet_instance_data; + +typedef union /*_artnet_instance_id*/ { + struct { + uint8_t fd_index; + uint8_t net; + uint8_t uni; + } fields; + uint64_t label; +} artnet_instance_id; + +typedef struct /*_artnet_fd*/ { + int fd; + size_t output_instances; + artnet_instance_id* output_instance; + uint64_t* last_frame; +} artnet_descriptor; + +#pragma pack(push, 1) +typedef struct /*_artnet_hdr*/ { + uint8_t magic[8]; + uint16_t opcode; + uint16_t version; +} artnet_hdr; + +typedef struct /*_artnet_pkt*/ { + uint8_t magic[8]; + uint16_t opcode; + uint16_t version; + uint8_t sequence; + uint8_t port; + uint8_t universe; + uint8_t net; + uint16_t length; + uint8_t data[512]; +} artnet_pkt; +#pragma pack(pop) + +enum artnet_pkt_opcode { + OpDmx = 0x0050 +}; diff --git a/backends/evdev.c b/backends/evdev.c new file mode 100644 index 0000000..ac63850 --- /dev/null +++ b/backends/evdev.c @@ -0,0 +1,401 @@ +#include +#include +#include +#include +#include +#include +#include +#ifndef EVDEV_NO_UINPUT +#include +#endif + +#include "midimonster.h" +#include "evdev.h" + +#define BACKEND_NAME "evdev" + +typedef union { + struct { + uint32_t pad; + uint16_t type; + uint16_t code; + } fields; + uint64_t label; +} evdev_channel_ident; + +int init(){ + backend evdev = { + .name = BACKEND_NAME, + .conf = evdev_configure, + .create = evdev_instance, + .conf_instance = evdev_configure_instance, + .channel = evdev_channel, + .handle = evdev_set, + .process = evdev_handle, + .start = evdev_start, + .shutdown = evdev_shutdown + }; + + if(mm_backend_register(evdev)){ + fprintf(stderr, "Failed to register evdev backend\n"); + return 1; + } + + return 0; +} + +static int evdev_configure(char* option, char* value) { + fprintf(stderr, "The evdev backend does not take any global configuration\n"); + return 1; +} + +static instance* evdev_instance(){ + instance* inst = mm_instance(); + if(!inst){ + return NULL; + } + + evdev_instance_data* data = calloc(1, sizeof(evdev_instance_data)); + if(!data){ + fprintf(stderr, "Failed to allocate memory\n"); + return NULL; + } + + data->input_fd = -1; +#ifndef EVDEV_NO_UINPUT + data->output_proto = libevdev_new(); + if(!data->output_proto){ + fprintf(stderr, "Failed to initialize libevdev output prototype device\n"); + free(data); + return NULL; + } +#endif + + inst->impl = data; + return inst; +} + +static int evdev_configure_instance(instance* inst, char* option, char* value) { + evdev_instance_data* data = (evdev_instance_data*) inst->impl; +#ifndef EVDEV_NO_UINPUT + char* next_token = NULL; + struct input_absinfo abs_info = { + 0 + }; +#endif + + if(!strcmp(option, "input")){ + if(data->input_fd >= 0){ + fprintf(stderr, "Instance %s already was assigned an input device\n", inst->name); + return 1; + } + + data->input_fd = open(value, O_RDONLY | O_NONBLOCK); + if(data->input_fd < 0){ + fprintf(stderr, "Failed to open evdev input device node %s: %s\n", value, strerror(errno)); + return 1; + } + + if(libevdev_new_from_fd(data->input_fd, &data->input_ev)){ + fprintf(stderr, "Failed to initialize libevdev for %s\n", value); + close(data->input_fd); + data->input_fd = -1; + return 1; + } + + if(data->exclusive && libevdev_grab(data->input_ev, LIBEVDEV_GRAB)){ + fprintf(stderr, "Failed to obtain exclusive device access on %s\n", value); + } + } + else if(!strcmp(option, "exclusive")){ + if(data->input_fd >= 0 && libevdev_grab(data->input_ev, LIBEVDEV_GRAB)){ + fprintf(stderr, "Failed to obtain exclusive device access on %s\n", inst->name); + } + data->exclusive = 1; + } +#ifndef EVDEV_NO_UINPUT + else if(!strcmp(option, "name")){ + data->output_enabled = 1; + libevdev_set_name(data->output_proto, value); + } + else if(!strcmp(option, "id")){ + next_token = value; + libevdev_set_id_vendor(data->output_proto, strtol(next_token, &next_token, 0)); + libevdev_set_id_product(data->output_proto, strtol(next_token, &next_token, 0)); + libevdev_set_id_version(data->output_proto, strtol(next_token, &next_token, 0)); + } + else if(!strncmp(option, "axis.", 5)){ + //value minimum maximum fuzz flat resolution + next_token = value; + abs_info.value = strtol(next_token, &next_token, 0); + abs_info.minimum = strtol(next_token, &next_token, 0); + abs_info.maximum = strtol(next_token, &next_token, 0); + abs_info.fuzz = strtol(next_token, &next_token, 0); + abs_info.flat = strtol(next_token, &next_token, 0); + abs_info.resolution = strtol(next_token, &next_token, 0); + if(libevdev_enable_event_code(data->output_proto, EV_ABS, libevdev_event_code_from_name(EV_ABS, option + 5), &abs_info)){ + fprintf(stderr, "Failed to enable absolute axis %s for output\n", option + 5); + return 1; + } + } +#endif + else{ + fprintf(stderr, "Unknown configuration parameter %s for evdev backend\n", option); + return 1; + } + return 0; +} + +static channel* evdev_channel(instance* inst, char* spec){ +#ifndef EVDEV_NO_UINPUT + evdev_instance_data* data = (evdev_instance_data*) inst->impl; +#endif + char* separator = strchr(spec, '.'); + evdev_channel_ident ident = { + .label = 0 + }; + + if(!separator){ + fprintf(stderr, "Invalid evdev channel specification %s\n", spec); + return NULL; + } + + *(separator++) = 0; + + if(libevdev_event_type_from_name(spec) < 0){ + fprintf(stderr, "Invalid evdev type specification: %s", spec); + return NULL; + } + ident.fields.type = libevdev_event_type_from_name(spec); + + if(libevdev_event_code_from_name(ident.fields.type, separator) >= 0){ + ident.fields.code = libevdev_event_code_from_name(ident.fields.type, separator); + } + else{ + fprintf(stderr, "evdev Code name not recognized, using as number: %s\n", separator); + ident.fields.code = strtoul(separator, NULL, 10); + } + +#ifndef EVDEV_NO_UINPUT + if(data->output_enabled){ + if(!libevdev_has_event_code(data->output_proto, ident.fields.type, ident.fields.code)){ + //enable the event on the device + //the previous check is necessary to not fail while enabling axes, which require additional information + if(libevdev_enable_event_code(data->output_proto, ident.fields.type, ident.fields.code, NULL)){ + fprintf(stderr, "Failed to enable output event %s.%s%s\n", + libevdev_event_type_get_name(ident.fields.type), + libevdev_event_code_get_name(ident.fields.type, ident.fields.code), + (ident.fields.type == EV_ABS) ? ": To output absolute axes, specify their details in the configuration":""); + return NULL; + } + } + } +#endif + + return mm_channel(inst, ident.label, 1); +} + +static int evdev_push_event(instance* inst, evdev_instance_data* data, struct input_event event){ + uint64_t range = 0; + channel_value val; + evdev_channel_ident ident = { + .fields.type = event.type, + .fields.code = event.code + }; + channel* chan = mm_channel(inst, ident.label, 0); + + if(chan){ + val.raw.u64 = event.value; + switch(event.type){ + case EV_REL: + val.normalised = 0.5 + ((event.value < 0) ? 0.5 : -0.5); + break; + case EV_ABS: + range = libevdev_get_abs_maximum(data->input_ev, event.code) - libevdev_get_abs_minimum(data->input_ev, event.code); + val.normalised = (event.value - libevdev_get_abs_minimum(data->input_ev, event.code)) / (double) range; + break; + case EV_KEY: + case EV_SW: + default: + val.normalised = 1.0 * event.value; + break; + } + + if(mm_channel_event(chan, val)){ + fprintf(stderr, "Failed to push evdev channel event to core\n"); + return 1; + } + } + + return 0; +} + +static int evdev_handle(size_t num, managed_fd* fds){ + instance* inst = NULL; + evdev_instance_data* data = NULL; + size_t fd; + unsigned int read_flags = LIBEVDEV_READ_FLAG_NORMAL; + int read_status; + struct input_event ev; + + if(!num){ + return 0; + } + + for(fd = 0; fd < num; fd++){ + inst = (instance*) fds[fd].impl; + if(!inst){ + fprintf(stderr, "evdev backend signaled for unknown fd\n"); + continue; + } + + data = (evdev_instance_data*) inst->impl; + + for(read_status = libevdev_next_event(data->input_ev, read_flags, &ev); read_status >= 0; read_status = libevdev_next_event(data->input_ev, read_flags, &ev)){ + read_flags = LIBEVDEV_READ_FLAG_NORMAL; + if(read_status == LIBEVDEV_READ_STATUS_SYNC){ + read_flags = LIBEVDEV_READ_FLAG_SYNC; + } + + //handle event + if(evdev_push_event(inst, data, ev)){ + return 1; + } + } + } + + return 0; +} + +static int evdev_start(){ + size_t n, u, fds = 0; + instance** inst = NULL; + evdev_instance_data* data = NULL; + + if(mm_backend_instances(BACKEND_NAME, &n, &inst)){ + fprintf(stderr, "Failed to fetch instance list\n"); + return 1; + } + + if(!n){ + free(inst); + return 0; + } + + for(u = 0; u < n; u++){ + data = (evdev_instance_data*) inst[u]->impl; + +#ifndef EVDEV_NO_UINPUT + if(data->output_enabled){ + if(libevdev_uinput_create_from_device(data->output_proto, LIBEVDEV_UINPUT_OPEN_MANAGED, &data->output_ev)){ + fprintf(stderr, "Failed to create evdev output device: %s\n", strerror(errno)); + return 1; + } + fprintf(stderr, "Created device node %s for instance %s\n", libevdev_uinput_get_devnode(data->output_ev), inst[u]->name); + } +#endif + + inst[u]->ident = data->input_fd; + if(data->input_fd >= 0){ + if(mm_manage_fd(data->input_fd, BACKEND_NAME, 1, inst[u])){ + fprintf(stderr, "Failed to register event input descriptor for instance %s\n", inst[u]->name); + free(inst); + return 1; + } + fds++; + } + + } + + fprintf(stderr, "evdev backend registered %zu descriptors to core\n", fds); + free(inst); + return 0; +} + +static int evdev_set(instance* inst, size_t num, channel** c, channel_value* v) { +#ifndef EVDEV_NO_UINPUT + size_t evt = 0; + evdev_instance_data* data = (evdev_instance_data*) inst->impl; + evdev_channel_ident ident = { + .label = 0 + }; + int32_t value = 0; + uint64_t range = 0; + + if(!num){ + return 0; + } + + if(!data->output_enabled){ + fprintf(stderr, "Instance %s not enabled for output\n", inst->name); + return 0; + } + + for(evt = 0; evt < num; evt++){ + ident.label = c[evt]->ident; + + switch(ident.fields.type){ + case EV_REL: + value = (v[evt].normalised < 0.5) ? -1 : ((v[evt].normalised > 0.5) ? 1 : 0); + break; + case EV_ABS: + range = libevdev_get_abs_maximum(data->output_proto, ident.fields.code) - libevdev_get_abs_minimum(data->output_proto, ident.fields.code); + value = (range * v[evt].normalised) + libevdev_get_abs_minimum(data->output_proto, ident.fields.code); + break; + case EV_KEY: + case EV_SW: + default: + value = (v[evt].normalised > 0.9) ? 1 : 0; + break; + } + + if(libevdev_uinput_write_event(data->output_ev, ident.fields.type, ident.fields.code, value)){ + fprintf(stderr, "Failed to output event on instance %s\n", inst->name); + return 1; + } + } + + //send syn event to publish all events + if(libevdev_uinput_write_event(data->output_ev, EV_SYN, SYN_REPORT, 0)){ + fprintf(stderr, "Failed to output sync event on instance %s\n", inst->name); + return 1; + } + + return 0; +#else + fprintf(stderr, "The evdev backend does not support output on this platform\n"); + return 1; +#endif +} + +static int evdev_shutdown(){ + evdev_instance_data* data = NULL; + instance** instances = NULL; + size_t n, u; + + if(mm_backend_instances(BACKEND_NAME, &n, &instances)){ + fprintf(stderr, "Failed to fetch instance list\n"); + return 1; + } + + for(u = 0; u < n; u++){ + data = (evdev_instance_data*) instances[u]->impl; + + if(data->input_fd >= 0){ + libevdev_free(data->input_ev); + close(data->input_fd); + } + +#ifndef EVDEV_NO_UINPUT + if(data->output_enabled){ + libevdev_uinput_destroy(data->output_ev); + } + + libevdev_free(data->output_proto); +#endif + free(data); + } + + free(instances); + return 0; +} diff --git a/backends/evdev.h b/backends/evdev.h new file mode 100644 index 0000000..89a08ae --- /dev/null +++ b/backends/evdev.h @@ -0,0 +1,31 @@ +#include + +#include "midimonster.h" + +/* + * This provides read-write access to the Linux kernel evdev subsystem + * via libevdev. On systems where uinput is not supported, output can be + * disabled by building with -DEVDEV_NO_UINPUT + */ + +int init(); +static int evdev_configure(char* option, char* value); +static int evdev_configure_instance(instance* instance, char* option, char* value); +static instance* evdev_instance(); +static channel* evdev_channel(instance* instance, char* spec); +static int evdev_set(instance* inst, size_t num, channel** c, channel_value* v); +static int evdev_handle(size_t num, managed_fd* fds); +static int evdev_start(); +static int evdev_shutdown(); + +typedef struct /*_evdev_instance_model*/ { + int input_fd; + struct libevdev* input_ev; + int exclusive; + + int output_enabled; +#ifndef EVDEV_NO_UINPUT + struct libevdev* output_proto; + struct libevdev_uinput* output_ev; +#endif +} evdev_instance_data; diff --git a/backends/loopback.c b/backends/loopback.c new file mode 100644 index 0000000..bb93a1f --- /dev/null +++ b/backends/loopback.c @@ -0,0 +1,120 @@ +#include +#include "loopback.h" + +#define BACKEND_NAME "loopback" + +int init(){ + backend loopback = { + .name = BACKEND_NAME, + .conf = backend_configure, + .create = backend_instance, + .conf_instance = backend_configure_instance, + .channel = backend_channel, + .handle = backend_set, + .process = backend_handle, + .start = backend_start, + .shutdown = backend_shutdown + }; + + //register backend + if(mm_backend_register(loopback)){ + fprintf(stderr, "Failed to register loopback backend\n"); + return 1; + } + return 0; +} + +static int backend_configure(char* option, char* value){ + //intentionally ignored + return 0; +} + +static int backend_configure_instance(instance* inst, char* option, char* value){ + //intentionally ignored + return 0; +} + +static instance* backend_instance(){ + instance* i = mm_instance(); + if(!i){ + return NULL; + } + + i->impl = calloc(1, sizeof(loopback_instance)); + if(!i->impl){ + fprintf(stderr, "Failed to allocate memory\n"); + return NULL; + } + + return i; +} + +static channel* backend_channel(instance* inst, char* spec){ + size_t u; + loopback_instance* data = (loopback_instance*) inst->impl; + + //find matching channel + for(u = 0; u < data->n; u++){ + if(!strcmp(spec, data->name[u])){ + break; + } + } + + //allocate new channel + if(u == data->n){ + data->name = realloc(data->name, (u + 1) * sizeof(char*)); + if(!data->name){ + fprintf(stderr, "Failed to allocate memory\n"); + return NULL; + } + + data->name[u] = strdup(spec); + if(!data->name[u]){ + fprintf(stderr, "Failed to allocate memory\n"); + return NULL; + } + data->n++; + } + + return mm_channel(inst, u, 1); +} + +static int backend_set(instance* inst, size_t num, channel** c, channel_value* v){ + size_t n; + for(n = 0; n < num; n++){ + mm_channel_event(c[n], v[n]); + } + return 0; +} + +static int backend_handle(size_t num, managed_fd* fds){ + //no events generated here + return 0; +} + +static int backend_start(){ + return 0; +} + +static int backend_shutdown(){ + size_t n, u, p; + instance** inst = NULL; + loopback_instance* data = NULL; + + if(mm_backend_instances(BACKEND_NAME, &n, &inst)){ + fprintf(stderr, "Failed to fetch instance list\n"); + return 1; + } + + for(u = 0; u < n; u++){ + data = (loopback_instance*) inst[u]->impl; + for(p = 0; p < data->n; p++){ + free(data->name[p]); + } + free(data->name); + free(inst[u]->impl); + } + + free(inst); + return 0; +} diff --git a/backends/loopback.h b/backends/loopback.h new file mode 100644 index 0000000..fe44e91 --- /dev/null +++ b/backends/loopback.h @@ -0,0 +1,16 @@ +#include "midimonster.h" + +int init(); +static int backend_configure(char* option, char* value); +static int backend_configure_instance(instance* instance, char* option, char* value); +static instance* backend_instance(); +static channel* backend_channel(instance* instance, char* spec); +static int backend_set(instance* inst, size_t num, channel** c, channel_value* v); +static int backend_handle(size_t num, managed_fd* fds); +static int backend_start(); +static int backend_shutdown(); + +typedef struct /*_loopback_instance_data*/ { + size_t n; + char** name; +} loopback_instance; diff --git a/backends/midi.c b/backends/midi.c new file mode 100644 index 0000000..d856ced --- /dev/null +++ b/backends/midi.c @@ -0,0 +1,366 @@ +#include +#include +#include "midi.h" + +#define BACKEND_NAME "midi" +static snd_seq_t* sequencer = NULL; +typedef union { + struct { + uint8_t pad[5]; + uint8_t type; + uint8_t channel; + uint8_t control; + } fields; + uint64_t label; +} midi_channel_ident; + +/* + * TODO + * Optionally send note-off messages + * Optionally send updates as after-touch + */ + +enum /*_midi_channel_type*/ { + none = 0, + note, + cc, + nrpn, + sysmsg +}; + +int init(){ + backend midi = { + .name = BACKEND_NAME, + .conf = midi_configure, + .create = midi_instance, + .conf_instance = midi_configure_instance, + .channel = midi_channel, + .handle = midi_set, + .process = midi_handle, + .start = midi_start, + .shutdown = midi_shutdown + }; + + if(snd_seq_open(&sequencer, "default", SND_SEQ_OPEN_DUPLEX, 0) < 0){ + fprintf(stderr, "Failed to open ALSA sequencer\n"); + return 1; + } + + //register backend + if(mm_backend_register(midi)){ + fprintf(stderr, "Failed to register MIDI backend\n"); + return 1; + } + + snd_seq_nonblock(sequencer, 1); + + fprintf(stderr, "MIDI client ID is %d\n", snd_seq_client_id(sequencer)); + return 0; +} + +static int midi_configure(char* option, char* value){ + if(!strcmp(option, "name")){ + if(snd_seq_set_client_name(sequencer, value) < 0){ + fprintf(stderr, "Failed to set MIDI client name to %s\n", value); + return 1; + } + return 0; + } + + fprintf(stderr, "Unknown MIDI backend option %s\n", option); + return 1; +} + +static instance* midi_instance(){ + instance* inst = mm_instance(); + if(!inst){ + return NULL; + } + + inst->impl = calloc(1, sizeof(midi_instance_data)); + if(!inst->impl){ + fprintf(stderr, "Failed to allocate memory\n"); + return NULL; + } + + return inst; +} + +static int midi_configure_instance(instance* instance, char* option, char* value){ + midi_instance_data* data = (midi_instance_data*) instance->impl; + + //FIXME maybe allow connecting more than one device + if(!strcmp(option, "read")){ + //connect input device + if(data->read){ + fprintf(stderr, "MIDI port already connected to an input device\n"); + return 1; + } + data->read = strdup(value); + return 0; + } + else if(!strcmp(option, "write")){ + //connect output device + if(data->write){ + fprintf(stderr, "MIDI port already connected to an output device\n"); + return 1; + } + data->write = strdup(value); + return 0; + } + + fprintf(stderr, "Unknown MIDI instance option %s\n", option); + return 1; +} + +static channel* midi_channel(instance* instance, char* spec){ + midi_channel_ident ident = { + .label = 0 + }; + + char* channel; + + if(!strncmp(spec, "cc", 2)){ + ident.fields.type = cc; + channel = spec + 2; + } + else if(!strncmp(spec, "note", 4)){ + ident.fields.type = note; + channel = spec + 4; + } + else if(!strncmp(spec, "nrpn", 4)){ + ident.fields.type = nrpn; + channel = spec + 4; + } + else{ + fprintf(stderr, "Unknown MIDI channel specification %s\n", spec); + return NULL; + } + + ident.fields.channel = strtoul(channel, &channel, 10); + + //FIXME test this + if(ident.fields.channel > 16){ + fprintf(stderr, "MIDI channel out of range in channel spec %s\n", spec); + return NULL; + } + + if(*channel != '.'){ + fprintf(stderr, "Need MIDI channel specification of form channel.control, had %s\n", spec); + return NULL; + } + channel++; + + ident.fields.control = strtoul(channel, NULL, 10); + + if(ident.label){ + return mm_channel(instance, ident.label, 1); + } + + return NULL; +} + +static int midi_set(instance* inst, size_t num, channel** c, channel_value* v){ + size_t u; + snd_seq_event_t ev; + midi_instance_data* data; + midi_channel_ident ident = { + .label = 0 + }; + + for(u = 0; u < num; u++){ + data = (midi_instance_data*) c[u]->instance->impl; + ident.label = c[u]->ident; + + snd_seq_ev_clear(&ev); + snd_seq_ev_set_source(&ev, data->port); + snd_seq_ev_set_subs(&ev); + snd_seq_ev_set_direct(&ev); + + switch(ident.fields.type){ + case note: + snd_seq_ev_set_noteon(&ev, ident.fields.channel, ident.fields.control, v[u].normalised * 127.0); + break; + case cc: + snd_seq_ev_set_controller(&ev, ident.fields.channel, ident.fields.control, v[u].normalised * 127.0); + break; + case nrpn: + //FIXME set to nrpn output + break; + } + + snd_seq_event_output(sequencer, &ev); + } + + snd_seq_drain_output(sequencer); + return 0; +} + +static int midi_handle(size_t num, managed_fd* fds){ + snd_seq_event_t* ev = NULL; + instance* inst = NULL; + channel* changed = NULL; + channel_value val; + midi_channel_ident ident = { + .label = 0 + }; + + if(!num){ + return 0; + } + + while(snd_seq_event_input(sequencer, &ev) > 0){ + ident.label = 0; + switch(ev->type){ + case SND_SEQ_EVENT_NOTEON: + case SND_SEQ_EVENT_NOTEOFF: + case SND_SEQ_EVENT_KEYPRESS: + case SND_SEQ_EVENT_NOTE: + ident.fields.type = note; + ident.fields.channel = ev->data.note.channel; + ident.fields.control = ev->data.note.note; + val.normalised = (double)ev->data.note.velocity / 127.0; + break; + case SND_SEQ_EVENT_CONTROLLER: + ident.fields.type = cc; + ident.fields.channel = ev->data.control.channel; + ident.fields.control = ev->data.control.param; + val.raw.u64 = ev->data.control.value; + val.normalised = (double)ev->data.control.value / 127.0; + break; + case SND_SEQ_EVENT_CONTROL14: + case SND_SEQ_EVENT_NONREGPARAM: + case SND_SEQ_EVENT_REGPARAM: + //FIXME value calculation + ident.fields.type = nrpn; + ident.fields.channel = ev->data.control.channel; + ident.fields.control = ev->data.control.param; + break; + default: + fprintf(stderr, "Ignored MIDI event of unsupported type\n"); + continue; + } + + inst = mm_instance_find(BACKEND_NAME, ev->dest.port); + if(!inst){ + //FIXME might want to return failure + fprintf(stderr, "Delivered MIDI event did not match any instance\n"); + continue; + } + + changed = mm_channel(inst, ident.label, 0); + if(changed){ + if(mm_channel_event(changed, val)){ + free(ev); + return 1; + } + } + } + free(ev); + return 0; +} + +static int midi_start(){ + size_t n, p; + int nfds, rv = 1; + struct pollfd* pfds = NULL; + instance** inst = NULL; + midi_instance_data* data = NULL; + snd_seq_addr_t addr; + + if(mm_backend_instances(BACKEND_NAME, &n, &inst)){ + fprintf(stderr, "Failed to fetch instance list\n"); + return 1; + } + + //if there are no ports, do nothing + if(!n){ + free(inst); + return 0; + } + + //create all ports + for(p = 0; p < n; p++){ + data = (midi_instance_data*) inst[p]->impl; + data->port = snd_seq_create_simple_port(sequencer, inst[p]->name, SND_SEQ_PORT_CAP_WRITE | SND_SEQ_PORT_CAP_SUBS_WRITE | SND_SEQ_PORT_CAP_READ | SND_SEQ_PORT_CAP_SUBS_READ, SND_SEQ_PORT_TYPE_MIDI_GENERIC); + inst[p]->ident = data->port; + + //make connections + if(data->write){ + if(snd_seq_parse_address(sequencer, &addr, data->write) == 0){ + fprintf(stderr, "Connecting output of instance %s to MIDI device %s (%d:%d)\n", inst[p]->name, data->write, addr.client, addr.port); + snd_seq_connect_to(sequencer, data->port, addr.client, addr.port); + } + else{ + fprintf(stderr, "Failed to get destination MIDI device address: %s\n", data->write); + } + free(data->write); + data->write = NULL; + } + + if(data->read){ + if(snd_seq_parse_address(sequencer, &addr, data->read) == 0){ + fprintf(stderr, "Connecting input from MIDI device %s to instance %s (%d:%d)\n", data->read, inst[p]->name, addr.client, addr.port); + snd_seq_connect_from(sequencer, data->port, addr.client, addr.port); + } + else{ + fprintf(stderr, "Failed to get source MIDI device address: %s\n", data->read); + } + free(data->read); + data->read = NULL; + } + } + + //register all fds to core + nfds = snd_seq_poll_descriptors_count(sequencer, POLLIN | POLLOUT); + pfds = calloc(nfds, sizeof(struct pollfd)); + if(!pfds){ + fprintf(stderr, "Failed to allocate memory\n"); + goto bail; + } + nfds = snd_seq_poll_descriptors(sequencer, pfds, nfds, POLLIN | POLLOUT); + + fprintf(stderr, "MIDI backend registering %d descriptors to core\n", nfds); + for(p = 0; p < nfds; p++){ + if(mm_manage_fd(pfds[p].fd, BACKEND_NAME, 1, NULL)){ + goto bail; + } + } + + rv = 0; + +bail: + free(pfds); + free(inst); + return rv; +} + +static int midi_shutdown(){ + size_t n, p; + instance** inst = NULL; + midi_instance_data* data = NULL; + if(mm_backend_instances(BACKEND_NAME, &n, &inst)){ + fprintf(stderr, "Failed to fetch instance list\n"); + return 1; + } + + for(p = 0; p < n; p++){ + data = (midi_instance_data*) inst[p]->impl; + free(data->read); + free(data->write); + data->read = NULL; + data->write = NULL; + free(inst[p]->impl); + } + free(inst); + + //close midi + snd_seq_close(sequencer); + sequencer = NULL; + + //free configuration cache + snd_config_update_free_global(); + + fprintf(stderr, "MIDI backend shut down\n"); + return 0; +} diff --git a/backends/midi.h b/backends/midi.h new file mode 100644 index 0000000..556706f --- /dev/null +++ b/backends/midi.h @@ -0,0 +1,17 @@ +#include "midimonster.h" + +int init(); +static int midi_configure(char* option, char* value); +static int midi_configure_instance(instance* instance, char* option, char* value); +static instance* midi_instance(); +static channel* midi_channel(instance* instance, char* spec); +static int midi_set(instance* inst, size_t num, channel** c, channel_value* v); +static int midi_handle(size_t num, managed_fd* fds); +static int midi_start(); +static int midi_shutdown(); + +typedef struct /*_midi_instance_data*/ { + int port; + char* read; + char* write; +} midi_instance_data; diff --git a/backends/osc.c b/backends/osc.c new file mode 100644 index 0000000..adc91f5 --- /dev/null +++ b/backends/osc.c @@ -0,0 +1,813 @@ +#include +#include +#include +#include +#include +#include +#include "osc.h" + +/* + * TODO + * ping method + */ + +#define osc_align(a) ((((a) / 4) + (((a) % 4) ? 1 : 0)) * 4) +#define BACKEND_NAME "osc" + +int init(){ + backend osc = { + .name = BACKEND_NAME, + .conf = backend_configure, + .create = backend_instance, + .conf_instance = backend_configure_instance, + .channel = backend_channel, + .handle = backend_set, + .process = backend_handle, + .start = backend_start, + .shutdown = backend_shutdown + }; + + //register backend + if(mm_backend_register(osc)){ + fprintf(stderr, "Failed to register OSC backend\n"); + return 1; + } + return 0; +} + +static size_t osc_data_length(osc_parameter_type t){ + switch(t){ + case int32: + case float32: + return 4; + case int64: + case double64: + return 8; + default: + fprintf(stderr, "Invalid OSC format specified %c\n", t); + return 0; + } +} + +static inline void osc_defaults(osc_parameter_type t, osc_parameter_value* max, osc_parameter_value* min){ + memset(max, 0, sizeof(osc_parameter_value)); + memset(min, 0, sizeof(osc_parameter_value)); + switch(t){ + case int32: + max->i32 = 255; + return; + case float32: + max->f = 1.0; + return; + case int64: + max->i64 = 1024; + return; + case double64: + max->d = 1.0; + return; + default: + fprintf(stderr, "Invalid OSC type, not setting any sane defaults\n"); + return; + } +} + +static inline osc_parameter_value osc_parse(osc_parameter_type t, uint8_t* data){ + osc_parameter_value v = {0}; + switch(t){ + case int32: + case float32: + v.i32 = be32toh(*((int32_t*) data)); + break; + case int64: + case double64: + v.i64 = be64toh(*((int64_t*) data)); + break; + default: + fprintf(stderr, "Invalid OSC type passed to parsing routine\n"); + } + return v; +} + +static inline int osc_deparse(osc_parameter_type t, osc_parameter_value v, uint8_t* data){ + uint64_t u64 = 0; + uint32_t u32 = 0; + switch(t){ + case int32: + case float32: + u32 = htobe32(v.i32); + memcpy(data, &u32, sizeof(u32)); + break; + case int64: + case double64: + u64 = htobe64(v.i64); + memcpy(data, &u64, sizeof(u64)); + break; + default: + fprintf(stderr, "Invalid OSC type passed to parsing routine\n"); + return 1; + } + return 0; +} + +static inline osc_parameter_value osc_parse_value_spec(osc_parameter_type t, char* value){ + osc_parameter_value v = {0}; + switch(t){ + case int32: + v.i32 = strtol(value, NULL, 0); + break; + case float32: + v.f = strtof(value, NULL); + break; + case int64: + v.i64 = strtoll(value, NULL, 0); + break; + case double64: + v.d = strtod(value, NULL); + break; + default: + fprintf(stderr, "Invalid OSC type passed to value parser\n"); + } + return v; +} + +static inline channel_value osc_parameter_normalise(osc_parameter_type t, osc_parameter_value min, osc_parameter_value max, osc_parameter_value cur){ + channel_value v = { + .raw = {0}, + .normalised = 0 + }; + + union { + uint32_t u32; + float f32; + uint64_t u64; + double d64; + } range; + + switch(t){ + case int32: + range.u32 = max.i32 - min.i32; + v.raw.u64 = cur.i32 - min.i32; + v.normalised = v.raw.u64 / range.u32; + break; + case float32: + range.f32 = max.f - min.f; + v.raw.dbl = cur.f - min.f; + v.normalised = v.raw.dbl / range.f32; + break; + case int64: + range.u64 = max.i64 - min.i64; + v.raw.u64 = cur.i64 - min.i64; + v.normalised = v.raw.u64 / range.u64; + break; + case double64: + range.d64 = max.d - min.d; + v.raw.dbl = cur.d - min.d; + v.normalised = v.raw.dbl / range.d64; + break; + default: + fprintf(stderr, "Invalid OSC type passed to interpolation routine\n"); + } + + //fix overshoot + if(v.normalised > 1.0){ + v.normalised = 1.0; + } + else if(v.normalised < 0.0){ + v.normalised = 0.0; + } + + return v; +} + +static inline osc_parameter_value osc_parameter_denormalise(osc_parameter_type t, osc_parameter_value min, osc_parameter_value max, channel_value cur){ + osc_parameter_value v = {0}; + + union { + uint32_t u32; + float f32; + uint64_t u64; + double d64; + } range; + + switch(t){ + case int32: + range.u32 = max.i32 - min.i32; + v.i32 = (range.u32 * cur.normalised) + min.i32; + break; + case float32: + range.f32 = max.f - min.f; + v.f = (range.f32 * cur.normalised) + min.f; + break; + case int64: + range.u64 = max.i64 - min.i64; + v.i64 = (range.u64 * cur.normalised) + min.i64; + break; + case double64: + range.d64 = max.d - min.d; + v.d = (range.d64 * cur.normalised) + min.d; + break; + default: + fprintf(stderr, "Invalid OSC type passed to interpolation routine\n"); + } + + return v; +} + +static int osc_generate_event(channel* c, osc_channel* info, char* fmt, uint8_t* data, size_t data_len){ + size_t p, off = 0; + if(!c || !info){ + return 0; + } + + osc_parameter_value min, max, cur; + channel_value evt; + + if(!fmt || !data || data_len % 4 || !*fmt){ + fprintf(stderr, "Invalid OSC packet, data length %zu\n", data_len); + return 1; + } + + //find offset for this parameter + for(p = 0; p < info->param_index; p++){ + off += osc_data_length(fmt[p]); + } + + if(info->type != not_set){ + max = info->max; + min = info->min; + } + else{ + osc_defaults(fmt[info->param_index], &max, &min); + } + + cur = osc_parse(fmt[info->param_index], data + off); + evt = osc_parameter_normalise(fmt[info->param_index], min, max, cur); + + return mm_channel_event(c, evt); +} + +static int osc_validate_path(char* path){ + if(path[0] != '/'){ + fprintf(stderr, "%s is not a valid OSC path: Missing root /\n", path); + return 1; + } + return 0; +} + +static int osc_separate_hostspec(char* in, char** host, char** 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 = NULL; + } + return 0; +} + +static int osc_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 OSC descriptor nonblocking\n"); + return -1; + } + + return fd; +} + +static int osc_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 backend_configure(char* option, char* value){ + fprintf(stderr, "The OSC backend does not take any global configuration\n"); + return 1; +} + +static int backend_configure_instance(instance* inst, char* option, char* value){ + osc_instance* data = (osc_instance*) inst->impl; + char* host = NULL, *port = NULL, *token = NULL, *format = NULL; + size_t u, p; + + if(!strcmp(option, "root")){ + if(osc_validate_path(value)){ + fprintf(stderr, "Not a valid OSC root: %s\n", value); + return 1; + } + + if(data->root){ + free(data->root); + } + data->root = strdup(value); + + if(!data->root){ + fprintf(stderr, "Failed to allocate memory\n"); + return 1; + } + return 0; + } + else if(!strcmp(option, "bind")){ + if(osc_separate_hostspec(value, &host, &port)){ + fprintf(stderr, "Invalid bind address for instance %s\n", inst->name); + return 1; + } + + data->fd = osc_listener(host, port); + if(data->fd < 0){ + fprintf(stderr, "Failed to bind for instance %s\n", inst->name); + return 1; + } + return 0; + } + else if(!strcmp(option, "dest") || !strcmp(option, "destination")){ + if(!strncmp(value, "learn", 5)){ + data->learn = 1; + + //check if a forced port was provided + if(value[5] == '@'){ + data->forced_rport = strtoul(value + 6, NULL, 0); + } + return 0; + } + + if(osc_separate_hostspec(value, &host, &port)){ + fprintf(stderr, "Invalid destination address for instance %s\n", inst->name); + return 1; + } + + if(osc_parse_addr(host, port, &data->dest, &data->dest_len)){ + fprintf(stderr, "Failed to parse destination address for instance %s\n", inst->name); + return 1; + } + return 0; + } + else if(*option == '/'){ + //pre-configure channel + if(osc_validate_path(option)){ + fprintf(stderr, "Not a valid OSC path: %s\n", option); + return 1; + } + + for(u = 0; u < data->channels; u++){ + if(!strcmp(option, data->channel[u].path)){ + fprintf(stderr, "OSC channel %s already configured\n", option); + return 1; + } + } + + //tokenize configuration + format = strtok(value, " "); + if(!format || strlen(format) < 1){ + fprintf(stderr, "Not a valid format for OSC path %s\n", option); + return 1; + } + + //check format validity, create subchannels + for(p = 0; p < strlen(format); p++){ + if(!osc_data_length(format[p])){ + fprintf(stderr, "Invalid format specifier %c for path %s, ignoring\n", format[p], option); + continue; + } + + //register new sub-channel + data->channel = realloc(data->channel, (data->channels + 1) * sizeof(osc_channel)); + if(!data->channel){ + fprintf(stderr, "Failed to allocate memory\n"); + return 1; + } + + memset(data->channel + data->channels, 0, sizeof(osc_channel)); + data->channel[data->channels].params = strlen(format); + data->channel[data->channels].param_index = p; + data->channel[data->channels].type = format[p]; + data->channel[data->channels].path = strdup(option); + + if(!data->channel[data->channels].path){ + fprintf(stderr, "Failed to allocate memory\n"); + return 1; + } + + //parse min/max values + token = strtok(NULL, " "); + if(!token){ + fprintf(stderr, "Missing minimum specification for parameter %zu of %s\n", p, option); + return 1; + } + data->channel[data->channels].min = osc_parse_value_spec(format[p], token); + + token = strtok(NULL, " "); + if(!token){ + fprintf(stderr, "Missing maximum specification for parameter %zu of %s\n", p, option); + return 1; + } + data->channel[data->channels].max = osc_parse_value_spec(format[p], token); + + //allocate channel from core + if(!mm_channel(inst, data->channels, 1)){ + fprintf(stderr, "Failed to register core channel\n"); + return 1; + } + + //increase channel count + data->channels++; + } + return 0; + } + + fprintf(stderr, "Unknown configuration parameter %s for OSC backend\n", option); + return 1; +} + +static instance* backend_instance(){ + instance* inst = mm_instance(); + if(!inst){ + return NULL; + } + + osc_instance* data = calloc(1, sizeof(osc_instance)); + if(!data){ + fprintf(stderr, "Failed to allocate memory\n"); + return NULL; + } + + data->fd = -1; + inst->impl = data; + return inst; +} + +static channel* backend_channel(instance* inst, char* spec){ + size_t u; + osc_instance* data = (osc_instance*) inst->impl; + size_t param_index = 0; + + //check spec for correctness + if(osc_validate_path(spec)){ + return NULL; + } + + //parse parameter offset + if(strrchr(spec, ':')){ + param_index = strtoul(strrchr(spec, ':') + 1, NULL, 10); + *(strrchr(spec, ':')) = 0; + } + + //find matching channel + for(u = 0; u < data->channels; u++){ + if(!strcmp(spec, data->channel[u].path) && data->channel[u].param_index == param_index){ + //fprintf(stderr, "Reusing previously created channel %s parameter %zu\n", data->channel[u].path, data->channel[u].param_index); + break; + } + } + + //allocate new channel + if(u == data->channels){ + data->channel = realloc(data->channel, (u + 1) * sizeof(osc_channel)); + if(!data->channel){ + fprintf(stderr, "Failed to allocate memory\n"); + return NULL; + } + + memset(data->channel + u, 0, sizeof(osc_channel)); + data->channel[u].param_index = param_index; + data->channel[u].path = strdup(spec); + + if(!data->channel[u].path){ + fprintf(stderr, "Failed to allocate memory\n"); + return NULL; + } + data->channels++; + } + + return mm_channel(inst, u, 1); +} + +static int backend_set(instance* inst, size_t num, channel** c, channel_value* v){ + uint8_t xmit_buf[OSC_XMIT_BUF], *format = NULL; + size_t evt = 0, off, members, p; + if(!num){ + return 0; + } + + osc_instance* data = (osc_instance*) inst->impl; + if(!data->dest_len){ + fprintf(stderr, "OSC instance %s does not have a destination, output is disabled (%zu channels)\n", inst->name, num); + return 0; + } + + for(evt = 0; evt < num; evt++){ + off = c[evt]->ident; + + //sanity check + if(off >= data->channels){ + fprintf(stderr, "OSC channel identifier out of range\n"); + return 1; + } + + //if the format is unknown, don't output + if(data->channel[off].type == not_set || data->channel[off].params == 0){ + fprintf(stderr, "OSC channel %s.%s requires format specification for output\n", inst->name, data->channel[off].path); + continue; + } + + //update current value + data->channel[off].current = osc_parameter_denormalise(data->channel[off].type, data->channel[off].min, data->channel[off].max, v[evt]); + //mark channel + data->channel[off].mark = 1; + } + + //fix destination rport if required + if(data->forced_rport){ + //cheating a bit because both IPv4 and IPv6 have the port at the same offset + struct sockaddr_in* sockadd = (struct sockaddr_in*) &(data->dest); + sockadd->sin_port = htobe16(data->forced_rport); + } + + //find all marked channels + for(evt = 0; evt < data->channels; evt++){ + //zero output buffer + memset(xmit_buf, 0, sizeof(xmit_buf)); + if(data->channel[evt].mark){ + //determine minimum packet size + if(osc_align((data->root ? strlen(data->root) : 0) + strlen(data->channel[evt].path) + 1) + osc_align(data->channel[evt].params + 2) >= sizeof(xmit_buf)){ + fprintf(stderr, "Insufficient buffer size for OSC transmitting channel %s.%s\n", inst->name, data->channel[evt].path); + return 1; + } + + off = 0; + //copy osc target path + if(data->root){ + memcpy(xmit_buf, data->root, strlen(data->root)); + off += strlen(data->root); + } + memcpy(xmit_buf + off, data->channel[evt].path, strlen(data->channel[evt].path)); + off += strlen(data->channel[evt].path) + 1; + off = osc_align(off); + + //get format string offset, initialize + format = xmit_buf + off; + off += osc_align(data->channel[evt].params + 2); + *format = ','; + format++; + + //gather subchannels, unmark + members = 0; + for(p = 0; p < data->channels && members < data->channel[evt].params; p++){ + if(!strcmp(data->channel[evt].path, data->channel[p].path)){ + //unmark channel + data->channel[p].mark = 0; + + //sanity check + if(data->channel[p].param_index >= data->channel[evt].params){ + fprintf(stderr, "OSC channel %s.%s has multiple parameter offset definitions\n", inst->name, data->channel[evt].path); + return 1; + } + + //write format specifier + format[data->channel[p].param_index] = data->channel[p].type; + + //write data + //FIXME this currently depends on all channels being registered in the correct order, since it just appends data + if(off + osc_data_length(data->channel[p].type) >= sizeof(xmit_buf)){ + fprintf(stderr, "Insufficient buffer size for OSC transmitting channel %s.%s at parameter %zu\n", inst->name, data->channel[evt].path, members); + return 1; + } + + osc_deparse(data->channel[p].type, data->channel[p].current, xmit_buf + off); + off += osc_data_length(data->channel[p].type); + members++; + } + } + + //output packet + if(sendto(data->fd, xmit_buf, off, 0, (struct sockaddr*) &(data->dest), data->dest_len) < 0){ + fprintf(stderr, "Failed to transmit OSC packet: %s\n", strerror(errno)); + } + } + } + return 0; +} + +static int backend_handle(size_t num, managed_fd* fds){ + size_t fd; + char recv_buf[OSC_RECV_BUF]; + instance* inst = NULL; + osc_instance* data = NULL; + ssize_t bytes_read = 0; + size_t c; + char* osc_fmt = NULL; + char* osc_local = NULL; + uint8_t* osc_data = NULL; + + for(fd = 0; fd < num; fd++){ + inst = (instance*) fds[fd].impl; + if(!inst){ + fprintf(stderr, "OSC backend signaled for unknown fd\n"); + continue; + } + + data = (osc_instance*) inst->impl; + + do{ + if(data->learn){ + data->dest_len = sizeof(data->dest); + bytes_read = recvfrom(fds[fd].fd, recv_buf, sizeof(recv_buf), 0, (struct sockaddr*) &(data->dest), &(data->dest_len)); + } + else{ + bytes_read = recv(fds[fd].fd, recv_buf, sizeof(recv_buf), 0); + } + if(data->root && strncmp(recv_buf, data->root, strlen(data->root))){ + //ignore packet for different root + continue; + } + osc_local = recv_buf + (data->root ? strlen(data->root) : 0); + + if(bytes_read < 0){ + break; + } + + osc_fmt = recv_buf + osc_align(strlen(recv_buf) + 1); + if(*osc_fmt != ','){ + //invalid format string + fprintf(stderr, "Invalid OSC format string in packet\n"); + continue; + } + osc_fmt++; + + osc_data = (uint8_t*) osc_fmt + (osc_align(strlen(osc_fmt) + 2) - 1); + //FIXME check supplied data length + + for(c = 0; c < data->channels; c++){ + //FIXME implement proper OSC path match + //prefix match + if(!strcmp(osc_local, data->channel[c].path)){ + if(strlen(osc_fmt) > data->channel[c].param_index){ + //fprintf(stderr, "Taking parameter %zu of %s (%s), %zd bytes, data offset %zu\n", data->channel[c].param_index, recv_buf, osc_fmt, bytes_read, (osc_data - (uint8_t*)recv_buf)); + if(osc_generate_event(mm_channel(inst, c, 0), data->channel + c, osc_fmt, osc_data, bytes_read - (osc_data - (uint8_t*) recv_buf))){ + fprintf(stderr, "Failed to generate OSC channel event\n"); + } + } + } + } + } while(bytes_read > 0); + + if(bytes_read < 0 && errno != EAGAIN){ + fprintf(stderr, "OSC failed to receive data for instance %s: %s\n", inst->name, strerror(errno)); + } + + if(bytes_read == 0){ + fprintf(stderr, "OSC descriptor for instance %s closed\n", inst->name); + return 1; + } + } + + return 0; +} + +static int backend_start(){ + size_t n, u, fds = 0; + instance** inst = NULL; + osc_instance* data = NULL; + + //fetch all 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; + } + + //update instance identifiers + for(u = 0; u < n; u++){ + data = (osc_instance*) inst[u]->impl; + + if(data->fd >= 0){ + inst[u]->ident = data->fd; + if(mm_manage_fd(data->fd, BACKEND_NAME, 1, inst[u])){ + fprintf(stderr, "Failed to register OSC descriptor for instance %s\n", inst[u]->name); + free(inst); + return 1; + } + fds++; + } + else{ + inst[u]->ident = -1; + } + } + + fprintf(stderr, "OSC backend registered %zu descriptors to core\n", fds); + + free(inst); + return 0; +} + +static int backend_shutdown(){ + size_t n, u, c; + instance** inst = NULL; + osc_instance* data = NULL; + + if(mm_backend_instances(BACKEND_NAME, &n, &inst)){ + fprintf(stderr, "Failed to fetch instance list\n"); + return 1; + } + + for(u = 0; u < n; u++){ + data = (osc_instance*) inst[u]->impl; + for(c = 0; c < data->channels; c++){ + free(data->channel[c].path); + } + free(data->channel); + free(data->root); + close(data->fd); + data->fd = -1; + data->channels = 0; + free(inst[u]->impl); + } + + free(inst); + return 0; +} diff --git a/backends/osc.h b/backends/osc.h new file mode 100644 index 0000000..5938f12 --- /dev/null +++ b/backends/osc.h @@ -0,0 +1,55 @@ +#include "midimonster.h" +#include +#include + +#define OSC_RECV_BUF 8192 +#define OSC_XMIT_BUF 8192 + +int init(); +static int backend_configure(char* option, char* value); +static int backend_configure_instance(instance* instance, char* option, char* value); +static instance* backend_instance(); +static channel* backend_channel(instance* instance, char* spec); +static int backend_set(instance* inst, size_t num, channel** c, channel_value* v); +static int backend_handle(size_t num, managed_fd* fds); +static int backend_start(); +static int backend_shutdown(); + +typedef enum { + not_set = 0, + int32 = 'i', + float32 = 'f', + /*s, b*/ //ignored + int64 = 'h', + double64 = 'd', +} osc_parameter_type; + +typedef union { + int32_t i32; + float f; + int64_t i64; + double d; +} osc_parameter_value; + +typedef struct /*_osc_channel*/ { + char* path; + size_t params; + size_t param_index; + uint8_t mark; + + osc_parameter_type type; + osc_parameter_value max; + osc_parameter_value min; + osc_parameter_value current; +} osc_channel; + +typedef struct /*_osc_instance_data*/ { + size_t channels; + osc_channel* channel; + char* root; + socklen_t dest_len; + struct sockaddr_storage dest; + int fd; + uint8_t learn; + uint16_t forced_rport; +} osc_instance; diff --git a/backends/sacn.c b/backends/sacn.c new file mode 100644 index 0000000..9a3202d --- /dev/null +++ b/backends/sacn.c @@ -0,0 +1,736 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "sacn.h" +//upper limit imposed by using the fd index as 16-bit part of the instance id +#define MAX_FDS 4096 +#define BACKEND_NAME "sacn" + +static struct /*_sacn_global_config*/ { + uint8_t source_name[64]; + uint8_t cid[16]; + size_t fds; + sacn_fd* fd; + uint64_t last_announce; +} global_cfg = { + .source_name = "MIDIMonster", + .cid = {'M', 'I', 'D', 'I', 'M', 'o', 'n', 's', 't', 'e', 'r'}, + .fds = 0, + .fd = NULL, + .last_announce = 0 +}; + +int init(){ + backend sacn = { + .name = BACKEND_NAME, + .conf = sacn_configure, + .create = sacn_instance, + .conf_instance = sacn_configure_instance, + .channel = sacn_channel, + .handle = sacn_set, + .process = sacn_handle, + .start = sacn_start, + .shutdown = sacn_shutdown + }; + + //register the backend + if(mm_backend_register(sacn)){ + fprintf(stderr, "Failed to register sACN backend\n"); + return 1; + } + + return 0; +} + +static int sacn_listener(char* host, char* port, uint8_t fd_flags){ + 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; + + if(global_cfg.fds >= MAX_FDS){ + fprintf(stderr, "sACN backend descriptor limit reached\n"); + return -1; + } + + 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 sACN descriptor nonblocking\n"); + return -1; + } + + //store fd + global_cfg.fd = realloc(global_cfg.fd, (global_cfg.fds + 1) * sizeof(sacn_fd)); + if(!global_cfg.fd){ + fprintf(stderr, "Failed to allocate memory\n"); + return -1; + } + + fprintf(stderr, "sACN backend interface %zu bound to %s port %s\n", global_cfg.fds, host, port); + global_cfg.fd[global_cfg.fds].fd = fd; + global_cfg.fd[global_cfg.fds].flags = fd_flags; + global_cfg.fd[global_cfg.fds].universes = 0; + global_cfg.fd[global_cfg.fds].universe = NULL; + global_cfg.fd[global_cfg.fds].last_frame = NULL; + global_cfg.fds++; + return 0; +} + +static int sacn_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 sacn_parse_hostspec(char* in, char** host, char** port, uint8_t* flags){ + 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 = SACN_PORT; + } + + if(flags){ + //TODO parse hostspec trailing data for options + *flags = 0; + } + return 0; +} + +static int sacn_configure(char* option, char* value){ + char* host = NULL, *port = NULL, *next = NULL; + uint8_t flags = 0; + size_t u; + + if(!strcmp(option, "name")){ + if(strlen(value) > 63){ + fprintf(stderr, "Invalid sACN source name %s, limit is 63 characters\n", value); + return 1; + } + + memset(global_cfg.source_name, 0, sizeof(global_cfg.source_name)); + memcpy(global_cfg.source_name, value, strlen(value)); + return 0; + } + else if(!strcmp(option, "cid")){ + next = value; + for(u = 0; u < sizeof(global_cfg.cid); u++){ + global_cfg.cid[u] = (strtoul(next, &next, 0) & 0xFF); + } + } + else if(!strcmp(option, "bind")){ + if(sacn_parse_hostspec(value, &host, &port, &flags)){ + fprintf(stderr, "Not a valid sACN bind address: %s\n", value); + return 1; + } + + if(sacn_listener(host, port, flags)){ + fprintf(stderr, "Failed to bind sACN descriptor: %s\n", value); + return 1; + } + return 0; + } + + fprintf(stderr, "Unknown sACN backend option %s\n", option); + return 1; +} + +static int sacn_configure_instance(instance* inst, char* option, char* value){ + sacn_instance_data* data = (sacn_instance_data*) inst->impl; + char* host = NULL, *port = NULL, *next = NULL; + size_t u; + + if(!strcmp(option, "universe")){ + data->uni = strtoul(value, NULL, 10); + return 0; + } + else if(!strcmp(option, "interface")){ + data->fd_index = strtoul(value, NULL, 10); + + if(data->fd_index >= global_cfg.fds){ + fprintf(stderr, "Configured sACN interface index is out of range on instance %s\n", inst->name); + return 1; + } + return 0; + } + else if(!strcmp(option, "priority")){ + data->xmit_prio = strtoul(value, NULL, 10); + return 0; + } + else if(!strcmp(option, "destination")){ + if(sacn_parse_hostspec(value, &host, &port, NULL)){ + fprintf(stderr, "Not a valid sACN destination for instance %s: %s\n", inst->name, value); + return 1; + } + + return sacn_parse_addr(host, port, &data->dest_addr, &data->dest_len); + } + else if(!strcmp(option, "from")){ + next = value; + data->filter_enabled = 1; + for(u = 0; u < sizeof(data->cid_filter); u++){ + data->cid_filter[u] = (strtoul(next, &next, 0) & 0xFF); + } + fprintf(stderr, "Enabled source CID filter for instance %s\n", inst->name); + return 0; + } + else if(!strcmp(option, "unicast")){ + data->unicast_input = strtoul(value, NULL, 10); + } + + fprintf(stderr, "Unknown configuration option %s for sACN backend\n", option); + return 1; +} + +static instance* sacn_instance(){ + instance* inst = mm_instance(); + if(!inst){ + return NULL; + } + + inst->impl = calloc(1, sizeof(sacn_instance_data)); + if(!inst->impl){ + fprintf(stderr, "Failed to allocate memory"); + return NULL; + } + + return inst; +} + +static channel* sacn_channel(instance* inst, char* spec){ + sacn_instance_data* data = (sacn_instance_data*) inst->impl; + char* spec_next = spec; + + unsigned chan_a = strtoul(spec, &spec_next, 10), chan_b = 0; + + //range check + if(!chan_a || chan_a > 512){ + fprintf(stderr, "sACN channel out of range on instance %s: %s\n", inst->name, spec); + return NULL; + } + chan_a--; + + //if wide channel, mark fine + if(*spec_next == '+'){ + chan_b = strtoul(spec_next + 1, NULL, 10); + if(!chan_b || chan_b > 512){ + fprintf(stderr, "Invalid wide-channel spec on instance %s: %s\n", inst->name, spec); + return NULL; + } + chan_b--; + + //if already mapped, bail + if(IS_ACTIVE(data->data.map[chan_b]) && data->data.map[chan_b] != (MAP_FINE | chan_a)){ + fprintf(stderr, "Fine channel %u already mapped on instance %s\n", chan_b, inst->name); + return NULL; + } + + data->data.map[chan_b] = MAP_FINE | chan_a; + } + + //if already active, assert that nothing changes + if(IS_ACTIVE(data->data.map[chan_a])){ + if((*spec_next == '+' && data->data.map[chan_a] != (MAP_COARSE | chan_b)) + || (*spec_next != '+' && data->data.map[chan_a] != (MAP_SINGLE | chan_a))){ + fprintf(stderr, "Primary sACN channel %u already mapped in another mode on instance %s\n", chan_a, inst->name); + return NULL; + } + } + + data->data.map[chan_a] = (*spec_next == '+') ? (MAP_COARSE | chan_b) : (MAP_SINGLE | chan_a); + return mm_channel(inst, chan_a, 1); +} + +static int sacn_transmit(instance* inst){ + size_t u; + sacn_instance_data* data = (sacn_instance_data*) inst->impl; + sacn_data_pdu pdu = { + .root = { + .preamble_size = htobe16(0x10), + .postamble_size = 0, + .magic = { 0 }, //memcpy'd + .flags = htobe16(0x7000 | 0x026e), + .vector = htobe32(ROOT_E131_DATA), + .sender_cid = { 0 }, //memcpy'd + .frame_flags = htobe16(0x7000 | 0x0258), + .frame_vector = htobe32(FRAME_E131_DATA) + }, + .data = { + .source_name = "", //memcpy'd + .priority = data->xmit_prio, + .sync_addr = 0, + .sequence = data->data.last_seq++, + .options = 0, + .universe = htobe16(data->uni), + .flags = htobe16(0x7000 | 0x0205), + .vector = DMP_SET_PROPERTY, + .format = 0xA1, + .startcode_offset = 0, + .address_increment = htobe16(1), + .channels = htobe16(513), + .data = { 0 } //memcpy'd + } + }; + + memcpy(pdu.root.magic, SACN_PDU_MAGIC, sizeof(pdu.root.magic)); + memcpy(pdu.root.sender_cid, global_cfg.cid, sizeof(pdu.root.sender_cid)); + memcpy(pdu.data.source_name, global_cfg.source_name, sizeof(pdu.data.source_name)); + memcpy((((uint8_t*)pdu.data.data) + 1), data->data.out, 512); + + if(sendto(global_cfg.fd[data->fd_index].fd, &pdu, sizeof(pdu), 0, (struct sockaddr*) &data->dest_addr, data->dest_len) < 0){ + fprintf(stderr, "Failed to output sACN frame for instance %s: %s\n", inst->name, strerror(errno)); + } + + //update last transmit timestamp + for(u = 0; u < global_cfg.fd[data->fd_index].universes; u++){ + if(global_cfg.fd[data->fd_index].universe[u] == data->uni){ + global_cfg.fd[data->fd_index].last_frame[u] = mm_timestamp(); + } + } + return 0; +} + +static int sacn_set(instance* inst, size_t num, channel** c, channel_value* v){ + size_t u, mark = 0; + sacn_instance_data* data = (sacn_instance_data*) inst->impl; + + if(!num){ + return 0; + } + + if(!data->xmit_prio){ + fprintf(stderr, "sACN instance %s not enabled for output (%zu channel events)\n", inst->name, num); + return 0; + } + + for(u = 0; u < num; u++){ + if(IS_WIDE(data->data.map[c[u]->ident])){ + uint32_t val = v[u].normalised * ((double) 0xFFFF); + + if(data->data.out[c[u]->ident] != ((val >> 8) & 0xFF)){ + mark = 1; + data->data.out[c[u]->ident] = (val >> 8) & 0xFF; + } + + if(data->data.out[MAPPED_CHANNEL(data->data.map[c[u]->ident])] != (val & 0xFF)){ + mark = 1; + data->data.out[MAPPED_CHANNEL(data->data.map[c[u]->ident])] = val & 0xFF; + } + } + else if(data->data.out[c[u]->ident] != (v[u].normalised * 255.0)){ + mark = 1; + data->data.out[c[u]->ident] = v[u].normalised * 255.0; + } + } + + //send packet if required + if(mark){ + sacn_transmit(inst); + } + + return 0; +} + +static int sacn_process_frame(instance* inst, sacn_frame_root* frame, sacn_frame_data* data){ + size_t u, max_mark = 0; + channel* chan = NULL; + channel_value val; + sacn_instance_data* inst_data = (sacn_instance_data*) inst->impl; + + //source filtering + if(inst_data->filter_enabled && memcmp(inst_data->cid_filter, frame->sender_cid, 16)){ + return 0; + } + + if(data->format != 0xa1 + || data->startcode_offset + || be16toh(data->address_increment) != 1){ + fprintf(stderr, "sACN framing not supported\n"); + return 1; + } + + if(be16toh(data->channels) > 513){ + fprintf(stderr, "Invalid sACN frame channel count\n"); + return 1; + } + + //handle source priority (currently a 1-bit counter) + if(inst_data->data.last_priority > data->priority){ + inst_data->data.last_priority = data->priority; + return 0; + } + inst_data->data.last_priority = data->priority; + + //read data (except start code), mark changed channels + for(u = 1; u < be16toh(data->channels); u++){ + if(IS_ACTIVE(inst_data->data.map[u - 1]) + && data->data[u] != inst_data->data.in[u - 1]){ + inst_data->data.in[u - 1] = data->data[u]; + inst_data->data.map[u - 1] |= MAP_MARK; + max_mark = u - 1; + } + } + + //generate events + for(u = 0; u <= max_mark; u++){ + if(inst_data->data.map[u] & MAP_MARK){ + //unmark and get channel + inst_data->data.map[u] &= ~MAP_MARK; + if(inst_data->data.map[u] & MAP_FINE){ + chan = mm_channel(inst, MAPPED_CHANNEL(inst_data->data.map[u]), 0); + } + else{ + chan = mm_channel(inst, u, 0); + } + + if(!chan){ + fprintf(stderr, "Active channel %zu on %s not known to core", u, inst->name); + return 1; + } + + //generate value + if(IS_WIDE(inst_data->data.map[u])){ + inst_data->data.map[MAPPED_CHANNEL(inst_data->data.map[u])] &= ~MAP_MARK; + val.raw.u64 = inst_data->data.in[u] << ((inst_data->data.map[u] & MAP_COARSE) ? 8 : 0); + val.raw.u64 |= inst_data->data.in[MAPPED_CHANNEL(inst_data->data.map[u])] << ((inst_data->data.map[u] & MAP_COARSE) ? 0 : 8); + val.normalised = (double) val.raw.u64 / (double) 0xFFFF; + } + else{ + val.raw.u64 = inst_data->data.in[u]; + val.normalised = (double) val.raw.u64 / 255.0; + } + + if(mm_channel_event(chan, val)){ + fprintf(stderr, "Failed to push sACN channel event to core\n"); + return 1; + } + } + } + return 0; +} + +static void sacn_discovery(size_t fd){ + size_t page = 0, pages = (global_cfg.fd[fd].universes / 512) + 1, universes; + struct sockaddr_in discovery_dest = { + .sin_family = AF_INET, + .sin_port = htobe16(SACN_PORT), + .sin_addr.s_addr = htobe32(((uint32_t) 0xefff0000) | 64214) + }; + + sacn_discovery_pdu pdu = { + .root = { + .preamble_size = htobe16(0x10), + .postamble_size = 0, + .magic = { 0 }, //memcpy'd + .flags = 0, //filled later + .vector = htobe32(ROOT_E131_EXTENDED), + .sender_cid = { 0 }, //memcpy'd + .frame_flags = 0, //filled later + .frame_vector = htobe32(FRAME_E131_DISCOVERY) + }, + .data = { + .source_name = "", //memcpy'd + .flags = 0, //filled later + .vector = htobe32(DISCOVERY_UNIVERSE_LIST), + .page = 0, //filled later + .max_page = pages - 1, + .data = { 0 } //memcpy'd + } + }; + + memcpy(pdu.root.magic, SACN_PDU_MAGIC, sizeof(pdu.root.magic)); + memcpy(pdu.root.sender_cid, global_cfg.cid, sizeof(pdu.root.sender_cid)); + memcpy(pdu.data.source_name, global_cfg.source_name, sizeof(pdu.data.source_name)); + + for(; page < pages; page++){ + universes = (global_cfg.fd[fd].universes - page * 512 >= 512) ? 512 : (global_cfg.fd[fd].universes % 512); + pdu.root.flags = htobe16(0x7000 | (104 + universes * sizeof(uint16_t))); + pdu.root.frame_flags = htobe16(0x7000 | (82 + universes * sizeof(uint16_t))); + pdu.data.flags = htobe16(0x7000 | (8 + universes * sizeof(uint16_t))); + + pdu.data.page = page; + memcpy(pdu.data.data, global_cfg.fd[fd].universe + page * 512, universes * sizeof(uint16_t)); + + if(sendto(global_cfg.fd[fd].fd, &pdu, sizeof(pdu) - (512 - universes) * sizeof(uint16_t), 0, (struct sockaddr*) &discovery_dest, sizeof(discovery_dest)) < 0){ + fprintf(stderr, "Failed to output sACN universe discovery frame for interface %zu: %s\n", fd, strerror(errno)); + } + } +} + +static int sacn_handle(size_t num, managed_fd* fds){ + size_t u, c; + uint64_t timestamp = mm_timestamp(); + ssize_t bytes_read; + char recv_buf[SACN_RECV_BUF]; + instance* inst = NULL; + sacn_instance_id instance_id = { + .label = 0 + }; + sacn_frame_root* frame = (sacn_frame_root*) recv_buf; + sacn_frame_data* data = (sacn_frame_data*) (recv_buf + sizeof(sacn_frame_root)); + + if(mm_timestamp() - global_cfg.last_announce > SACN_DISCOVERY_TIMEOUT){ + //send universe discovery pdu + for(u = 0; u < global_cfg.fds; u++){ + if(global_cfg.fd[u].universes){ + sacn_discovery(u); + } + } + global_cfg.last_announce = timestamp; + } + + //check for keepalive frames + for(u = 0; u < global_cfg.fds; u++){ + for(c = 0; c < global_cfg.fd[u].universes; c++){ + if(timestamp - global_cfg.fd[u].last_frame[c] >= SACN_KEEPALIVE_INTERVAL){ + instance_id.fields.fd_index = u; + instance_id.fields.uni = global_cfg.fd[u].universe[c]; + inst = mm_instance_find(BACKEND_NAME, instance_id.label); + if(inst){ + sacn_transmit(inst); + } + } + } + } + + //early exit + if(!num){ + return 0; + } + + for(u = 0; u < num; u++){ + do{ + bytes_read = recv(fds[u].fd, recv_buf, sizeof(recv_buf), 0); + if(bytes_read > 0 && bytes_read > sizeof(sacn_frame_root)){ + if(!memcmp(frame->magic, SACN_PDU_MAGIC, 12) + && be16toh(frame->preamble_size) == 0x10 + && frame->postamble_size == 0 + && be32toh(frame->vector) == ROOT_E131_DATA + && be32toh(frame->frame_vector) == FRAME_E131_DATA + && data->vector == DMP_SET_PROPERTY){ + instance_id.fields.fd_index = ((uint64_t) fds[u].impl) & 0xFFFF; + instance_id.fields.uni = be16toh(data->universe); + inst = mm_instance_find(BACKEND_NAME, instance_id.label); + if(inst && sacn_process_frame(inst, frame, data)){ + fprintf(stderr, "Failed to process sACN frame\n"); + } + } + } + } while(bytes_read > 0); + + if(bytes_read < 0 && errno != EAGAIN){ + fprintf(stderr, "sACN failed to receive data: %s\n", strerror(errno)); + } + + if(bytes_read == 0){ + fprintf(stderr, "sACN listener closed\n"); + return 1; + } + } + + return 0; +} + +static int sacn_start(){ + size_t n, u, p; + int rv = 1; + instance** inst = NULL; + sacn_instance_data* data = NULL; + sacn_instance_id id = { + .label = 0 + }; + struct ip_mreq mcast_req = { + .imr_interface = { INADDR_ANY } + }; + struct sockaddr_in* dest_v4 = NULL; + + //fetch all 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(!global_cfg.fds){ + fprintf(stderr, "Failed to start sACN backend: no descriptors bound\n"); + return 1; + } + + //update instance identifiers, join multicast groups + for(u = 0; u < n; u++){ + data = (sacn_instance_data*) inst[u]->impl; + id.fields.fd_index = data->fd_index; + id.fields.uni = data->uni; + inst[u]->ident = id.label; + + if(!data->uni){ + fprintf(stderr, "Please specify a universe on instance %s\n", inst[u]->name); + goto bail; + } + + //find duplicates + for(p = 0; p < u; p++){ + if(inst[u]->ident == inst[p]->ident){ + fprintf(stderr, "Colliding sACN instances, use one: %s - %s\n", inst[u]->name, inst[p]->name); + goto bail; + } + } + + if(!data->unicast_input){ + mcast_req.imr_multiaddr.s_addr = htobe32(((uint32_t) 0xefff0000) | ((uint32_t) data->uni)); + if(setsockopt(global_cfg.fd[data->fd_index].fd, IPPROTO_IP, IP_ADD_MEMBERSHIP, &mcast_req, sizeof(mcast_req))){ + fprintf(stderr, "Failed to join Multicast group for sACN universe %u on instance %s: %s\n", data->uni, inst[u]->name, strerror(errno)); + } + } + + if(data->xmit_prio){ + //add to list of advertised universes for this fd + global_cfg.fd[data->fd_index].universe = realloc(global_cfg.fd[data->fd_index].universe, (global_cfg.fd[data->fd_index].universes + 1) * sizeof(uint16_t)); + if(!global_cfg.fd[data->fd_index].universe){ + fprintf(stderr, "Failed to allocate memory\n"); + goto bail; + } + + global_cfg.fd[data->fd_index].universe[global_cfg.fd[data->fd_index].universes] = data->uni; + global_cfg.fd[data->fd_index].universes++; + + //generate multicast destination address if none set + if(!data->dest_len){ + data->dest_len = sizeof(struct sockaddr_in); + dest_v4 = (struct sockaddr_in*) (&data->dest_addr); + dest_v4->sin_family = AF_INET; + dest_v4->sin_port = htobe16(strtoul(SACN_PORT, NULL, 10)); + dest_v4->sin_addr = mcast_req.imr_multiaddr; + } + } + } + + fprintf(stderr, "sACN backend registering %zu descriptors to core\n", global_cfg.fds); + for(u = 0; u < global_cfg.fds; u++){ + //allocate memory for storing last frame transmission timestamp + global_cfg.fd[u].last_frame = calloc(global_cfg.fd[u].universes, sizeof(uint64_t)); + if(!global_cfg.fd[u].last_frame){ + fprintf(stderr, "Failed to allocate memory\n"); + goto bail; + } + if(mm_manage_fd(global_cfg.fd[u].fd, BACKEND_NAME, 1, (void*) u)){ + goto bail; + } + } + + rv = 0; +bail: + free(inst); + return rv; +} + +static int sacn_shutdown(){ + size_t n, p; + instance** inst = NULL; + + if(mm_backend_instances(BACKEND_NAME, &n, &inst)){ + fprintf(stderr, "Failed to fetch instance list\n"); + return 1; + } + + for(p = 0; p < n; p++){ + free(inst[p]->impl); + } + free(inst); + + for(p = 0; p < global_cfg.fds; p++){ + close(global_cfg.fd[p].fd); + free(global_cfg.fd[p].universe); + free(global_cfg.fd[p].last_frame); + } + free(global_cfg.fd); + fprintf(stderr, "sACN backend shut down\n"); + return 0; +} diff --git a/backends/sacn.h b/backends/sacn.h new file mode 100644 index 0000000..e7106f7 --- /dev/null +++ b/backends/sacn.h @@ -0,0 +1,126 @@ +#include +#include "midimonster.h" + +int init(); +static int sacn_configure(char* option, char* value); +static int sacn_configure_instance(instance* instance, char* option, char* value); +static instance* sacn_instance(); +static channel* sacn_channel(instance* instance, char* spec); +static int sacn_set(instance* inst, size_t num, channel** c, channel_value* v); +static int sacn_handle(size_t num, managed_fd* fds); +static int sacn_start(); +static int sacn_shutdown(); + +#define SACN_PORT "5568" +#define SACN_RECV_BUF 8192 +#define SACN_KEEPALIVE_INTERVAL 2000 +#define SACN_DISCOVERY_TIMEOUT 9000 +#define SACN_PDU_MAGIC "ASC-E1.17\0\0\0" + +#define MAP_COARSE 0x0200 +#define MAP_FINE 0x0400 +#define MAP_SINGLE 0x0800 +#define MAP_MARK 0x1000 +#define MAPPED_CHANNEL(a) ((a) & 0x01FF) +#define IS_ACTIVE(a) ((a) & 0xFE00) +#define IS_WIDE(a) ((a) & (MAP_FINE | MAP_COARSE)) +#define IS_SINGLE(a) ((a) & MAP_SINGLE) + +typedef struct /*_sacn_universe_model*/ { + uint8_t last_priority; + uint8_t last_seq; + uint8_t in[512]; + uint8_t out[512]; + uint16_t map[512]; +} sacn_universe; + +typedef struct /*_sacn_instance_model*/ { + uint16_t uni; + uint8_t xmit_prio; + uint8_t cid_filter[16]; + uint8_t filter_enabled; + uint8_t unicast_input; + struct sockaddr_storage dest_addr; + socklen_t dest_len; + sacn_universe data; + size_t fd_index; +} sacn_instance_data; + +typedef union /*_sacn_instance_id*/ { + struct { + uint16_t fd_index; + uint16_t uni; + uint8_t pad[4]; + } fields; + uint64_t label; +} sacn_instance_id; + +typedef struct /*_sacn_socket*/ { + int fd; + uint8_t flags; + size_t universes; + uint16_t* universe; + uint64_t* last_frame; +} sacn_fd; + +#pragma pack(push, 1) +typedef struct /*_sacn_frame_root*/ { + uint16_t preamble_size; + uint16_t postamble_size; + uint8_t magic[12]; + uint16_t flags; + uint32_t vector; + uint8_t sender_cid[16]; + //framing + uint16_t frame_flags; + uint32_t frame_vector; +} sacn_frame_root; + +typedef struct /*_sacn_frame_data*/ { + //framing + uint8_t source_name[64]; + uint8_t priority; + uint16_t sync_addr; + uint8_t sequence; + uint8_t options; + uint16_t universe; + //dmp + uint16_t flags; + uint8_t vector; + uint8_t format; + uint16_t startcode_offset; + uint16_t address_increment; + uint16_t channels; + uint8_t data[513]; +} sacn_frame_data; + +typedef struct /*_sacn_frame_discovery*/ { + //framing + uint8_t source_name[64]; + uint32_t reserved; + //universe discovery + uint16_t flags; + uint32_t vector; + uint8_t page; + uint8_t max_page; + uint16_t data[512]; +} sacn_frame_discovery; + +typedef struct /*_sacn_xmit_data*/ { + sacn_frame_root root; + sacn_frame_data data; +} sacn_data_pdu; + +typedef struct /*_sacn_xmit_discovery*/ { + sacn_frame_root root; + sacn_frame_discovery data; +} sacn_discovery_pdu; +#pragma pack(pop) + +#define ROOT_E131_DATA 0x4 +#define FRAME_E131_DATA 0x2 +#define DMP_SET_PROPERTY 0x2 + +#define ROOT_E131_EXTENDED 0x8 +#define FRAME_E131_DISCOVERY 0x2 +#define DISCOVERY_UNIVERSE_LIST 0x1 diff --git a/evdev.c b/evdev.c deleted file mode 100644 index ac63850..0000000 --- a/evdev.c +++ /dev/null @@ -1,401 +0,0 @@ -#include -#include -#include -#include -#include -#include -#include -#ifndef EVDEV_NO_UINPUT -#include -#endif - -#include "midimonster.h" -#include "evdev.h" - -#define BACKEND_NAME "evdev" - -typedef union { - struct { - uint32_t pad; - uint16_t type; - uint16_t code; - } fields; - uint64_t label; -} evdev_channel_ident; - -int init(){ - backend evdev = { - .name = BACKEND_NAME, - .conf = evdev_configure, - .create = evdev_instance, - .conf_instance = evdev_configure_instance, - .channel = evdev_channel, - .handle = evdev_set, - .process = evdev_handle, - .start = evdev_start, - .shutdown = evdev_shutdown - }; - - if(mm_backend_register(evdev)){ - fprintf(stderr, "Failed to register evdev backend\n"); - return 1; - } - - return 0; -} - -static int evdev_configure(char* option, char* value) { - fprintf(stderr, "The evdev backend does not take any global configuration\n"); - return 1; -} - -static instance* evdev_instance(){ - instance* inst = mm_instance(); - if(!inst){ - return NULL; - } - - evdev_instance_data* data = calloc(1, sizeof(evdev_instance_data)); - if(!data){ - fprintf(stderr, "Failed to allocate memory\n"); - return NULL; - } - - data->input_fd = -1; -#ifndef EVDEV_NO_UINPUT - data->output_proto = libevdev_new(); - if(!data->output_proto){ - fprintf(stderr, "Failed to initialize libevdev output prototype device\n"); - free(data); - return NULL; - } -#endif - - inst->impl = data; - return inst; -} - -static int evdev_configure_instance(instance* inst, char* option, char* value) { - evdev_instance_data* data = (evdev_instance_data*) inst->impl; -#ifndef EVDEV_NO_UINPUT - char* next_token = NULL; - struct input_absinfo abs_info = { - 0 - }; -#endif - - if(!strcmp(option, "input")){ - if(data->input_fd >= 0){ - fprintf(stderr, "Instance %s already was assigned an input device\n", inst->name); - return 1; - } - - data->input_fd = open(value, O_RDONLY | O_NONBLOCK); - if(data->input_fd < 0){ - fprintf(stderr, "Failed to open evdev input device node %s: %s\n", value, strerror(errno)); - return 1; - } - - if(libevdev_new_from_fd(data->input_fd, &data->input_ev)){ - fprintf(stderr, "Failed to initialize libevdev for %s\n", value); - close(data->input_fd); - data->input_fd = -1; - return 1; - } - - if(data->exclusive && libevdev_grab(data->input_ev, LIBEVDEV_GRAB)){ - fprintf(stderr, "Failed to obtain exclusive device access on %s\n", value); - } - } - else if(!strcmp(option, "exclusive")){ - if(data->input_fd >= 0 && libevdev_grab(data->input_ev, LIBEVDEV_GRAB)){ - fprintf(stderr, "Failed to obtain exclusive device access on %s\n", inst->name); - } - data->exclusive = 1; - } -#ifndef EVDEV_NO_UINPUT - else if(!strcmp(option, "name")){ - data->output_enabled = 1; - libevdev_set_name(data->output_proto, value); - } - else if(!strcmp(option, "id")){ - next_token = value; - libevdev_set_id_vendor(data->output_proto, strtol(next_token, &next_token, 0)); - libevdev_set_id_product(data->output_proto, strtol(next_token, &next_token, 0)); - libevdev_set_id_version(data->output_proto, strtol(next_token, &next_token, 0)); - } - else if(!strncmp(option, "axis.", 5)){ - //value minimum maximum fuzz flat resolution - next_token = value; - abs_info.value = strtol(next_token, &next_token, 0); - abs_info.minimum = strtol(next_token, &next_token, 0); - abs_info.maximum = strtol(next_token, &next_token, 0); - abs_info.fuzz = strtol(next_token, &next_token, 0); - abs_info.flat = strtol(next_token, &next_token, 0); - abs_info.resolution = strtol(next_token, &next_token, 0); - if(libevdev_enable_event_code(data->output_proto, EV_ABS, libevdev_event_code_from_name(EV_ABS, option + 5), &abs_info)){ - fprintf(stderr, "Failed to enable absolute axis %s for output\n", option + 5); - return 1; - } - } -#endif - else{ - fprintf(stderr, "Unknown configuration parameter %s for evdev backend\n", option); - return 1; - } - return 0; -} - -static channel* evdev_channel(instance* inst, char* spec){ -#ifndef EVDEV_NO_UINPUT - evdev_instance_data* data = (evdev_instance_data*) inst->impl; -#endif - char* separator = strchr(spec, '.'); - evdev_channel_ident ident = { - .label = 0 - }; - - if(!separator){ - fprintf(stderr, "Invalid evdev channel specification %s\n", spec); - return NULL; - } - - *(separator++) = 0; - - if(libevdev_event_type_from_name(spec) < 0){ - fprintf(stderr, "Invalid evdev type specification: %s", spec); - return NULL; - } - ident.fields.type = libevdev_event_type_from_name(spec); - - if(libevdev_event_code_from_name(ident.fields.type, separator) >= 0){ - ident.fields.code = libevdev_event_code_from_name(ident.fields.type, separator); - } - else{ - fprintf(stderr, "evdev Code name not recognized, using as number: %s\n", separator); - ident.fields.code = strtoul(separator, NULL, 10); - } - -#ifndef EVDEV_NO_UINPUT - if(data->output_enabled){ - if(!libevdev_has_event_code(data->output_proto, ident.fields.type, ident.fields.code)){ - //enable the event on the device - //the previous check is necessary to not fail while enabling axes, which require additional information - if(libevdev_enable_event_code(data->output_proto, ident.fields.type, ident.fields.code, NULL)){ - fprintf(stderr, "Failed to enable output event %s.%s%s\n", - libevdev_event_type_get_name(ident.fields.type), - libevdev_event_code_get_name(ident.fields.type, ident.fields.code), - (ident.fields.type == EV_ABS) ? ": To output absolute axes, specify their details in the configuration":""); - return NULL; - } - } - } -#endif - - return mm_channel(inst, ident.label, 1); -} - -static int evdev_push_event(instance* inst, evdev_instance_data* data, struct input_event event){ - uint64_t range = 0; - channel_value val; - evdev_channel_ident ident = { - .fields.type = event.type, - .fields.code = event.code - }; - channel* chan = mm_channel(inst, ident.label, 0); - - if(chan){ - val.raw.u64 = event.value; - switch(event.type){ - case EV_REL: - val.normalised = 0.5 + ((event.value < 0) ? 0.5 : -0.5); - break; - case EV_ABS: - range = libevdev_get_abs_maximum(data->input_ev, event.code) - libevdev_get_abs_minimum(data->input_ev, event.code); - val.normalised = (event.value - libevdev_get_abs_minimum(data->input_ev, event.code)) / (double) range; - break; - case EV_KEY: - case EV_SW: - default: - val.normalised = 1.0 * event.value; - break; - } - - if(mm_channel_event(chan, val)){ - fprintf(stderr, "Failed to push evdev channel event to core\n"); - return 1; - } - } - - return 0; -} - -static int evdev_handle(size_t num, managed_fd* fds){ - instance* inst = NULL; - evdev_instance_data* data = NULL; - size_t fd; - unsigned int read_flags = LIBEVDEV_READ_FLAG_NORMAL; - int read_status; - struct input_event ev; - - if(!num){ - return 0; - } - - for(fd = 0; fd < num; fd++){ - inst = (instance*) fds[fd].impl; - if(!inst){ - fprintf(stderr, "evdev backend signaled for unknown fd\n"); - continue; - } - - data = (evdev_instance_data*) inst->impl; - - for(read_status = libevdev_next_event(data->input_ev, read_flags, &ev); read_status >= 0; read_status = libevdev_next_event(data->input_ev, read_flags, &ev)){ - read_flags = LIBEVDEV_READ_FLAG_NORMAL; - if(read_status == LIBEVDEV_READ_STATUS_SYNC){ - read_flags = LIBEVDEV_READ_FLAG_SYNC; - } - - //handle event - if(evdev_push_event(inst, data, ev)){ - return 1; - } - } - } - - return 0; -} - -static int evdev_start(){ - size_t n, u, fds = 0; - instance** inst = NULL; - evdev_instance_data* data = NULL; - - if(mm_backend_instances(BACKEND_NAME, &n, &inst)){ - fprintf(stderr, "Failed to fetch instance list\n"); - return 1; - } - - if(!n){ - free(inst); - return 0; - } - - for(u = 0; u < n; u++){ - data = (evdev_instance_data*) inst[u]->impl; - -#ifndef EVDEV_NO_UINPUT - if(data->output_enabled){ - if(libevdev_uinput_create_from_device(data->output_proto, LIBEVDEV_UINPUT_OPEN_MANAGED, &data->output_ev)){ - fprintf(stderr, "Failed to create evdev output device: %s\n", strerror(errno)); - return 1; - } - fprintf(stderr, "Created device node %s for instance %s\n", libevdev_uinput_get_devnode(data->output_ev), inst[u]->name); - } -#endif - - inst[u]->ident = data->input_fd; - if(data->input_fd >= 0){ - if(mm_manage_fd(data->input_fd, BACKEND_NAME, 1, inst[u])){ - fprintf(stderr, "Failed to register event input descriptor for instance %s\n", inst[u]->name); - free(inst); - return 1; - } - fds++; - } - - } - - fprintf(stderr, "evdev backend registered %zu descriptors to core\n", fds); - free(inst); - return 0; -} - -static int evdev_set(instance* inst, size_t num, channel** c, channel_value* v) { -#ifndef EVDEV_NO_UINPUT - size_t evt = 0; - evdev_instance_data* data = (evdev_instance_data*) inst->impl; - evdev_channel_ident ident = { - .label = 0 - }; - int32_t value = 0; - uint64_t range = 0; - - if(!num){ - return 0; - } - - if(!data->output_enabled){ - fprintf(stderr, "Instance %s not enabled for output\n", inst->name); - return 0; - } - - for(evt = 0; evt < num; evt++){ - ident.label = c[evt]->ident; - - switch(ident.fields.type){ - case EV_REL: - value = (v[evt].normalised < 0.5) ? -1 : ((v[evt].normalised > 0.5) ? 1 : 0); - break; - case EV_ABS: - range = libevdev_get_abs_maximum(data->output_proto, ident.fields.code) - libevdev_get_abs_minimum(data->output_proto, ident.fields.code); - value = (range * v[evt].normalised) + libevdev_get_abs_minimum(data->output_proto, ident.fields.code); - break; - case EV_KEY: - case EV_SW: - default: - value = (v[evt].normalised > 0.9) ? 1 : 0; - break; - } - - if(libevdev_uinput_write_event(data->output_ev, ident.fields.type, ident.fields.code, value)){ - fprintf(stderr, "Failed to output event on instance %s\n", inst->name); - return 1; - } - } - - //send syn event to publish all events - if(libevdev_uinput_write_event(data->output_ev, EV_SYN, SYN_REPORT, 0)){ - fprintf(stderr, "Failed to output sync event on instance %s\n", inst->name); - return 1; - } - - return 0; -#else - fprintf(stderr, "The evdev backend does not support output on this platform\n"); - return 1; -#endif -} - -static int evdev_shutdown(){ - evdev_instance_data* data = NULL; - instance** instances = NULL; - size_t n, u; - - if(mm_backend_instances(BACKEND_NAME, &n, &instances)){ - fprintf(stderr, "Failed to fetch instance list\n"); - return 1; - } - - for(u = 0; u < n; u++){ - data = (evdev_instance_data*) instances[u]->impl; - - if(data->input_fd >= 0){ - libevdev_free(data->input_ev); - close(data->input_fd); - } - -#ifndef EVDEV_NO_UINPUT - if(data->output_enabled){ - libevdev_uinput_destroy(data->output_ev); - } - - libevdev_free(data->output_proto); -#endif - free(data); - } - - free(instances); - return 0; -} diff --git a/evdev.h b/evdev.h deleted file mode 100644 index 89a08ae..0000000 --- a/evdev.h +++ /dev/null @@ -1,31 +0,0 @@ -#include - -#include "midimonster.h" - -/* - * This provides read-write access to the Linux kernel evdev subsystem - * via libevdev. On systems where uinput is not supported, output can be - * disabled by building with -DEVDEV_NO_UINPUT - */ - -int init(); -static int evdev_configure(char* option, char* value); -static int evdev_configure_instance(instance* instance, char* option, char* value); -static instance* evdev_instance(); -static channel* evdev_channel(instance* instance, char* spec); -static int evdev_set(instance* inst, size_t num, channel** c, channel_value* v); -static int evdev_handle(size_t num, managed_fd* fds); -static int evdev_start(); -static int evdev_shutdown(); - -typedef struct /*_evdev_instance_model*/ { - int input_fd; - struct libevdev* input_ev; - int exclusive; - - int output_enabled; -#ifndef EVDEV_NO_UINPUT - struct libevdev* output_proto; - struct libevdev_uinput* output_ev; -#endif -} evdev_instance_data; diff --git a/loopback.c b/loopback.c deleted file mode 100644 index bb93a1f..0000000 --- a/loopback.c +++ /dev/null @@ -1,120 +0,0 @@ -#include -#include "loopback.h" - -#define BACKEND_NAME "loopback" - -int init(){ - backend loopback = { - .name = BACKEND_NAME, - .conf = backend_configure, - .create = backend_instance, - .conf_instance = backend_configure_instance, - .channel = backend_channel, - .handle = backend_set, - .process = backend_handle, - .start = backend_start, - .shutdown = backend_shutdown - }; - - //register backend - if(mm_backend_register(loopback)){ - fprintf(stderr, "Failed to register loopback backend\n"); - return 1; - } - return 0; -} - -static int backend_configure(char* option, char* value){ - //intentionally ignored - return 0; -} - -static int backend_configure_instance(instance* inst, char* option, char* value){ - //intentionally ignored - return 0; -} - -static instance* backend_instance(){ - instance* i = mm_instance(); - if(!i){ - return NULL; - } - - i->impl = calloc(1, sizeof(loopback_instance)); - if(!i->impl){ - fprintf(stderr, "Failed to allocate memory\n"); - return NULL; - } - - return i; -} - -static channel* backend_channel(instance* inst, char* spec){ - size_t u; - loopback_instance* data = (loopback_instance*) inst->impl; - - //find matching channel - for(u = 0; u < data->n; u++){ - if(!strcmp(spec, data->name[u])){ - break; - } - } - - //allocate new channel - if(u == data->n){ - data->name = realloc(data->name, (u + 1) * sizeof(char*)); - if(!data->name){ - fprintf(stderr, "Failed to allocate memory\n"); - return NULL; - } - - data->name[u] = strdup(spec); - if(!data->name[u]){ - fprintf(stderr, "Failed to allocate memory\n"); - return NULL; - } - data->n++; - } - - return mm_channel(inst, u, 1); -} - -static int backend_set(instance* inst, size_t num, channel** c, channel_value* v){ - size_t n; - for(n = 0; n < num; n++){ - mm_channel_event(c[n], v[n]); - } - return 0; -} - -static int backend_handle(size_t num, managed_fd* fds){ - //no events generated here - return 0; -} - -static int backend_start(){ - return 0; -} - -static int backend_shutdown(){ - size_t n, u, p; - instance** inst = NULL; - loopback_instance* data = NULL; - - if(mm_backend_instances(BACKEND_NAME, &n, &inst)){ - fprintf(stderr, "Failed to fetch instance list\n"); - return 1; - } - - for(u = 0; u < n; u++){ - data = (loopback_instance*) inst[u]->impl; - for(p = 0; p < data->n; p++){ - free(data->name[p]); - } - free(data->name); - free(inst[u]->impl); - } - - free(inst); - return 0; -} diff --git a/loopback.h b/loopback.h deleted file mode 100644 index fe44e91..0000000 --- a/loopback.h +++ /dev/null @@ -1,16 +0,0 @@ -#include "midimonster.h" - -int init(); -static int backend_configure(char* option, char* value); -static int backend_configure_instance(instance* instance, char* option, char* value); -static instance* backend_instance(); -static channel* backend_channel(instance* instance, char* spec); -static int backend_set(instance* inst, size_t num, channel** c, channel_value* v); -static int backend_handle(size_t num, managed_fd* fds); -static int backend_start(); -static int backend_shutdown(); - -typedef struct /*_loopback_instance_data*/ { - size_t n; - char** name; -} loopback_instance; diff --git a/midi.c b/midi.c deleted file mode 100644 index d856ced..0000000 --- a/midi.c +++ /dev/null @@ -1,366 +0,0 @@ -#include -#include -#include "midi.h" - -#define BACKEND_NAME "midi" -static snd_seq_t* sequencer = NULL; -typedef union { - struct { - uint8_t pad[5]; - uint8_t type; - uint8_t channel; - uint8_t control; - } fields; - uint64_t label; -} midi_channel_ident; - -/* - * TODO - * Optionally send note-off messages - * Optionally send updates as after-touch - */ - -enum /*_midi_channel_type*/ { - none = 0, - note, - cc, - nrpn, - sysmsg -}; - -int init(){ - backend midi = { - .name = BACKEND_NAME, - .conf = midi_configure, - .create = midi_instance, - .conf_instance = midi_configure_instance, - .channel = midi_channel, - .handle = midi_set, - .process = midi_handle, - .start = midi_start, - .shutdown = midi_shutdown - }; - - if(snd_seq_open(&sequencer, "default", SND_SEQ_OPEN_DUPLEX, 0) < 0){ - fprintf(stderr, "Failed to open ALSA sequencer\n"); - return 1; - } - - //register backend - if(mm_backend_register(midi)){ - fprintf(stderr, "Failed to register MIDI backend\n"); - return 1; - } - - snd_seq_nonblock(sequencer, 1); - - fprintf(stderr, "MIDI client ID is %d\n", snd_seq_client_id(sequencer)); - return 0; -} - -static int midi_configure(char* option, char* value){ - if(!strcmp(option, "name")){ - if(snd_seq_set_client_name(sequencer, value) < 0){ - fprintf(stderr, "Failed to set MIDI client name to %s\n", value); - return 1; - } - return 0; - } - - fprintf(stderr, "Unknown MIDI backend option %s\n", option); - return 1; -} - -static instance* midi_instance(){ - instance* inst = mm_instance(); - if(!inst){ - return NULL; - } - - inst->impl = calloc(1, sizeof(midi_instance_data)); - if(!inst->impl){ - fprintf(stderr, "Failed to allocate memory\n"); - return NULL; - } - - return inst; -} - -static int midi_configure_instance(instance* instance, char* option, char* value){ - midi_instance_data* data = (midi_instance_data*) instance->impl; - - //FIXME maybe allow connecting more than one device - if(!strcmp(option, "read")){ - //connect input device - if(data->read){ - fprintf(stderr, "MIDI port already connected to an input device\n"); - return 1; - } - data->read = strdup(value); - return 0; - } - else if(!strcmp(option, "write")){ - //connect output device - if(data->write){ - fprintf(stderr, "MIDI port already connected to an output device\n"); - return 1; - } - data->write = strdup(value); - return 0; - } - - fprintf(stderr, "Unknown MIDI instance option %s\n", option); - return 1; -} - -static channel* midi_channel(instance* instance, char* spec){ - midi_channel_ident ident = { - .label = 0 - }; - - char* channel; - - if(!strncmp(spec, "cc", 2)){ - ident.fields.type = cc; - channel = spec + 2; - } - else if(!strncmp(spec, "note", 4)){ - ident.fields.type = note; - channel = spec + 4; - } - else if(!strncmp(spec, "nrpn", 4)){ - ident.fields.type = nrpn; - channel = spec + 4; - } - else{ - fprintf(stderr, "Unknown MIDI channel specification %s\n", spec); - return NULL; - } - - ident.fields.channel = strtoul(channel, &channel, 10); - - //FIXME test this - if(ident.fields.channel > 16){ - fprintf(stderr, "MIDI channel out of range in channel spec %s\n", spec); - return NULL; - } - - if(*channel != '.'){ - fprintf(stderr, "Need MIDI channel specification of form channel.control, had %s\n", spec); - return NULL; - } - channel++; - - ident.fields.control = strtoul(channel, NULL, 10); - - if(ident.label){ - return mm_channel(instance, ident.label, 1); - } - - return NULL; -} - -static int midi_set(instance* inst, size_t num, channel** c, channel_value* v){ - size_t u; - snd_seq_event_t ev; - midi_instance_data* data; - midi_channel_ident ident = { - .label = 0 - }; - - for(u = 0; u < num; u++){ - data = (midi_instance_data*) c[u]->instance->impl; - ident.label = c[u]->ident; - - snd_seq_ev_clear(&ev); - snd_seq_ev_set_source(&ev, data->port); - snd_seq_ev_set_subs(&ev); - snd_seq_ev_set_direct(&ev); - - switch(ident.fields.type){ - case note: - snd_seq_ev_set_noteon(&ev, ident.fields.channel, ident.fields.control, v[u].normalised * 127.0); - break; - case cc: - snd_seq_ev_set_controller(&ev, ident.fields.channel, ident.fields.control, v[u].normalised * 127.0); - break; - case nrpn: - //FIXME set to nrpn output - break; - } - - snd_seq_event_output(sequencer, &ev); - } - - snd_seq_drain_output(sequencer); - return 0; -} - -static int midi_handle(size_t num, managed_fd* fds){ - snd_seq_event_t* ev = NULL; - instance* inst = NULL; - channel* changed = NULL; - channel_value val; - midi_channel_ident ident = { - .label = 0 - }; - - if(!num){ - return 0; - } - - while(snd_seq_event_input(sequencer, &ev) > 0){ - ident.label = 0; - switch(ev->type){ - case SND_SEQ_EVENT_NOTEON: - case SND_SEQ_EVENT_NOTEOFF: - case SND_SEQ_EVENT_KEYPRESS: - case SND_SEQ_EVENT_NOTE: - ident.fields.type = note; - ident.fields.channel = ev->data.note.channel; - ident.fields.control = ev->data.note.note; - val.normalised = (double)ev->data.note.velocity / 127.0; - break; - case SND_SEQ_EVENT_CONTROLLER: - ident.fields.type = cc; - ident.fields.channel = ev->data.control.channel; - ident.fields.control = ev->data.control.param; - val.raw.u64 = ev->data.control.value; - val.normalised = (double)ev->data.control.value / 127.0; - break; - case SND_SEQ_EVENT_CONTROL14: - case SND_SEQ_EVENT_NONREGPARAM: - case SND_SEQ_EVENT_REGPARAM: - //FIXME value calculation - ident.fields.type = nrpn; - ident.fields.channel = ev->data.control.channel; - ident.fields.control = ev->data.control.param; - break; - default: - fprintf(stderr, "Ignored MIDI event of unsupported type\n"); - continue; - } - - inst = mm_instance_find(BACKEND_NAME, ev->dest.port); - if(!inst){ - //FIXME might want to return failure - fprintf(stderr, "Delivered MIDI event did not match any instance\n"); - continue; - } - - changed = mm_channel(inst, ident.label, 0); - if(changed){ - if(mm_channel_event(changed, val)){ - free(ev); - return 1; - } - } - } - free(ev); - return 0; -} - -static int midi_start(){ - size_t n, p; - int nfds, rv = 1; - struct pollfd* pfds = NULL; - instance** inst = NULL; - midi_instance_data* data = NULL; - snd_seq_addr_t addr; - - if(mm_backend_instances(BACKEND_NAME, &n, &inst)){ - fprintf(stderr, "Failed to fetch instance list\n"); - return 1; - } - - //if there are no ports, do nothing - if(!n){ - free(inst); - return 0; - } - - //create all ports - for(p = 0; p < n; p++){ - data = (midi_instance_data*) inst[p]->impl; - data->port = snd_seq_create_simple_port(sequencer, inst[p]->name, SND_SEQ_PORT_CAP_WRITE | SND_SEQ_PORT_CAP_SUBS_WRITE | SND_SEQ_PORT_CAP_READ | SND_SEQ_PORT_CAP_SUBS_READ, SND_SEQ_PORT_TYPE_MIDI_GENERIC); - inst[p]->ident = data->port; - - //make connections - if(data->write){ - if(snd_seq_parse_address(sequencer, &addr, data->write) == 0){ - fprintf(stderr, "Connecting output of instance %s to MIDI device %s (%d:%d)\n", inst[p]->name, data->write, addr.client, addr.port); - snd_seq_connect_to(sequencer, data->port, addr.client, addr.port); - } - else{ - fprintf(stderr, "Failed to get destination MIDI device address: %s\n", data->write); - } - free(data->write); - data->write = NULL; - } - - if(data->read){ - if(snd_seq_parse_address(sequencer, &addr, data->read) == 0){ - fprintf(stderr, "Connecting input from MIDI device %s to instance %s (%d:%d)\n", data->read, inst[p]->name, addr.client, addr.port); - snd_seq_connect_from(sequencer, data->port, addr.client, addr.port); - } - else{ - fprintf(stderr, "Failed to get source MIDI device address: %s\n", data->read); - } - free(data->read); - data->read = NULL; - } - } - - //register all fds to core - nfds = snd_seq_poll_descriptors_count(sequencer, POLLIN | POLLOUT); - pfds = calloc(nfds, sizeof(struct pollfd)); - if(!pfds){ - fprintf(stderr, "Failed to allocate memory\n"); - goto bail; - } - nfds = snd_seq_poll_descriptors(sequencer, pfds, nfds, POLLIN | POLLOUT); - - fprintf(stderr, "MIDI backend registering %d descriptors to core\n", nfds); - for(p = 0; p < nfds; p++){ - if(mm_manage_fd(pfds[p].fd, BACKEND_NAME, 1, NULL)){ - goto bail; - } - } - - rv = 0; - -bail: - free(pfds); - free(inst); - return rv; -} - -static int midi_shutdown(){ - size_t n, p; - instance** inst = NULL; - midi_instance_data* data = NULL; - if(mm_backend_instances(BACKEND_NAME, &n, &inst)){ - fprintf(stderr, "Failed to fetch instance list\n"); - return 1; - } - - for(p = 0; p < n; p++){ - data = (midi_instance_data*) inst[p]->impl; - free(data->read); - free(data->write); - data->read = NULL; - data->write = NULL; - free(inst[p]->impl); - } - free(inst); - - //close midi - snd_seq_close(sequencer); - sequencer = NULL; - - //free configuration cache - snd_config_update_free_global(); - - fprintf(stderr, "MIDI backend shut down\n"); - return 0; -} diff --git a/midi.h b/midi.h deleted file mode 100644 index 556706f..0000000 --- a/midi.h +++ /dev/null @@ -1,17 +0,0 @@ -#include "midimonster.h" - -int init(); -static int midi_configure(char* option, char* value); -static int midi_configure_instance(instance* instance, char* option, char* value); -static instance* midi_instance(); -static channel* midi_channel(instance* instance, char* spec); -static int midi_set(instance* inst, size_t num, channel** c, channel_value* v); -static int midi_handle(size_t num, managed_fd* fds); -static int midi_start(); -static int midi_shutdown(); - -typedef struct /*_midi_instance_data*/ { - int port; - char* read; - char* write; -} midi_instance_data; diff --git a/osc.c b/osc.c deleted file mode 100644 index adc91f5..0000000 --- a/osc.c +++ /dev/null @@ -1,813 +0,0 @@ -#include -#include -#include -#include -#include -#include -#include "osc.h" - -/* - * TODO - * ping method - */ - -#define osc_align(a) ((((a) / 4) + (((a) % 4) ? 1 : 0)) * 4) -#define BACKEND_NAME "osc" - -int init(){ - backend osc = { - .name = BACKEND_NAME, - .conf = backend_configure, - .create = backend_instance, - .conf_instance = backend_configure_instance, - .channel = backend_channel, - .handle = backend_set, - .process = backend_handle, - .start = backend_start, - .shutdown = backend_shutdown - }; - - //register backend - if(mm_backend_register(osc)){ - fprintf(stderr, "Failed to register OSC backend\n"); - return 1; - } - return 0; -} - -static size_t osc_data_length(osc_parameter_type t){ - switch(t){ - case int32: - case float32: - return 4; - case int64: - case double64: - return 8; - default: - fprintf(stderr, "Invalid OSC format specified %c\n", t); - return 0; - } -} - -static inline void osc_defaults(osc_parameter_type t, osc_parameter_value* max, osc_parameter_value* min){ - memset(max, 0, sizeof(osc_parameter_value)); - memset(min, 0, sizeof(osc_parameter_value)); - switch(t){ - case int32: - max->i32 = 255; - return; - case float32: - max->f = 1.0; - return; - case int64: - max->i64 = 1024; - return; - case double64: - max->d = 1.0; - return; - default: - fprintf(stderr, "Invalid OSC type, not setting any sane defaults\n"); - return; - } -} - -static inline osc_parameter_value osc_parse(osc_parameter_type t, uint8_t* data){ - osc_parameter_value v = {0}; - switch(t){ - case int32: - case float32: - v.i32 = be32toh(*((int32_t*) data)); - break; - case int64: - case double64: - v.i64 = be64toh(*((int64_t*) data)); - break; - default: - fprintf(stderr, "Invalid OSC type passed to parsing routine\n"); - } - return v; -} - -static inline int osc_deparse(osc_parameter_type t, osc_parameter_value v, uint8_t* data){ - uint64_t u64 = 0; - uint32_t u32 = 0; - switch(t){ - case int32: - case float32: - u32 = htobe32(v.i32); - memcpy(data, &u32, sizeof(u32)); - break; - case int64: - case double64: - u64 = htobe64(v.i64); - memcpy(data, &u64, sizeof(u64)); - break; - default: - fprintf(stderr, "Invalid OSC type passed to parsing routine\n"); - return 1; - } - return 0; -} - -static inline osc_parameter_value osc_parse_value_spec(osc_parameter_type t, char* value){ - osc_parameter_value v = {0}; - switch(t){ - case int32: - v.i32 = strtol(value, NULL, 0); - break; - case float32: - v.f = strtof(value, NULL); - break; - case int64: - v.i64 = strtoll(value, NULL, 0); - break; - case double64: - v.d = strtod(value, NULL); - break; - default: - fprintf(stderr, "Invalid OSC type passed to value parser\n"); - } - return v; -} - -static inline channel_value osc_parameter_normalise(osc_parameter_type t, osc_parameter_value min, osc_parameter_value max, osc_parameter_value cur){ - channel_value v = { - .raw = {0}, - .normalised = 0 - }; - - union { - uint32_t u32; - float f32; - uint64_t u64; - double d64; - } range; - - switch(t){ - case int32: - range.u32 = max.i32 - min.i32; - v.raw.u64 = cur.i32 - min.i32; - v.normalised = v.raw.u64 / range.u32; - break; - case float32: - range.f32 = max.f - min.f; - v.raw.dbl = cur.f - min.f; - v.normalised = v.raw.dbl / range.f32; - break; - case int64: - range.u64 = max.i64 - min.i64; - v.raw.u64 = cur.i64 - min.i64; - v.normalised = v.raw.u64 / range.u64; - break; - case double64: - range.d64 = max.d - min.d; - v.raw.dbl = cur.d - min.d; - v.normalised = v.raw.dbl / range.d64; - break; - default: - fprintf(stderr, "Invalid OSC type passed to interpolation routine\n"); - } - - //fix overshoot - if(v.normalised > 1.0){ - v.normalised = 1.0; - } - else if(v.normalised < 0.0){ - v.normalised = 0.0; - } - - return v; -} - -static inline osc_parameter_value osc_parameter_denormalise(osc_parameter_type t, osc_parameter_value min, osc_parameter_value max, channel_value cur){ - osc_parameter_value v = {0}; - - union { - uint32_t u32; - float f32; - uint64_t u64; - double d64; - } range; - - switch(t){ - case int32: - range.u32 = max.i32 - min.i32; - v.i32 = (range.u32 * cur.normalised) + min.i32; - break; - case float32: - range.f32 = max.f - min.f; - v.f = (range.f32 * cur.normalised) + min.f; - break; - case int64: - range.u64 = max.i64 - min.i64; - v.i64 = (range.u64 * cur.normalised) + min.i64; - break; - case double64: - range.d64 = max.d - min.d; - v.d = (range.d64 * cur.normalised) + min.d; - break; - default: - fprintf(stderr, "Invalid OSC type passed to interpolation routine\n"); - } - - return v; -} - -static int osc_generate_event(channel* c, osc_channel* info, char* fmt, uint8_t* data, size_t data_len){ - size_t p, off = 0; - if(!c || !info){ - return 0; - } - - osc_parameter_value min, max, cur; - channel_value evt; - - if(!fmt || !data || data_len % 4 || !*fmt){ - fprintf(stderr, "Invalid OSC packet, data length %zu\n", data_len); - return 1; - } - - //find offset for this parameter - for(p = 0; p < info->param_index; p++){ - off += osc_data_length(fmt[p]); - } - - if(info->type != not_set){ - max = info->max; - min = info->min; - } - else{ - osc_defaults(fmt[info->param_index], &max, &min); - } - - cur = osc_parse(fmt[info->param_index], data + off); - evt = osc_parameter_normalise(fmt[info->param_index], min, max, cur); - - return mm_channel_event(c, evt); -} - -static int osc_validate_path(char* path){ - if(path[0] != '/'){ - fprintf(stderr, "%s is not a valid OSC path: Missing root /\n", path); - return 1; - } - return 0; -} - -static int osc_separate_hostspec(char* in, char** host, char** 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 = NULL; - } - return 0; -} - -static int osc_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 OSC descriptor nonblocking\n"); - return -1; - } - - return fd; -} - -static int osc_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 backend_configure(char* option, char* value){ - fprintf(stderr, "The OSC backend does not take any global configuration\n"); - return 1; -} - -static int backend_configure_instance(instance* inst, char* option, char* value){ - osc_instance* data = (osc_instance*) inst->impl; - char* host = NULL, *port = NULL, *token = NULL, *format = NULL; - size_t u, p; - - if(!strcmp(option, "root")){ - if(osc_validate_path(value)){ - fprintf(stderr, "Not a valid OSC root: %s\n", value); - return 1; - } - - if(data->root){ - free(data->root); - } - data->root = strdup(value); - - if(!data->root){ - fprintf(stderr, "Failed to allocate memory\n"); - return 1; - } - return 0; - } - else if(!strcmp(option, "bind")){ - if(osc_separate_hostspec(value, &host, &port)){ - fprintf(stderr, "Invalid bind address for instance %s\n", inst->name); - return 1; - } - - data->fd = osc_listener(host, port); - if(data->fd < 0){ - fprintf(stderr, "Failed to bind for instance %s\n", inst->name); - return 1; - } - return 0; - } - else if(!strcmp(option, "dest") || !strcmp(option, "destination")){ - if(!strncmp(value, "learn", 5)){ - data->learn = 1; - - //check if a forced port was provided - if(value[5] == '@'){ - data->forced_rport = strtoul(value + 6, NULL, 0); - } - return 0; - } - - if(osc_separate_hostspec(value, &host, &port)){ - fprintf(stderr, "Invalid destination address for instance %s\n", inst->name); - return 1; - } - - if(osc_parse_addr(host, port, &data->dest, &data->dest_len)){ - fprintf(stderr, "Failed to parse destination address for instance %s\n", inst->name); - return 1; - } - return 0; - } - else if(*option == '/'){ - //pre-configure channel - if(osc_validate_path(option)){ - fprintf(stderr, "Not a valid OSC path: %s\n", option); - return 1; - } - - for(u = 0; u < data->channels; u++){ - if(!strcmp(option, data->channel[u].path)){ - fprintf(stderr, "OSC channel %s already configured\n", option); - return 1; - } - } - - //tokenize configuration - format = strtok(value, " "); - if(!format || strlen(format) < 1){ - fprintf(stderr, "Not a valid format for OSC path %s\n", option); - return 1; - } - - //check format validity, create subchannels - for(p = 0; p < strlen(format); p++){ - if(!osc_data_length(format[p])){ - fprintf(stderr, "Invalid format specifier %c for path %s, ignoring\n", format[p], option); - continue; - } - - //register new sub-channel - data->channel = realloc(data->channel, (data->channels + 1) * sizeof(osc_channel)); - if(!data->channel){ - fprintf(stderr, "Failed to allocate memory\n"); - return 1; - } - - memset(data->channel + data->channels, 0, sizeof(osc_channel)); - data->channel[data->channels].params = strlen(format); - data->channel[data->channels].param_index = p; - data->channel[data->channels].type = format[p]; - data->channel[data->channels].path = strdup(option); - - if(!data->channel[data->channels].path){ - fprintf(stderr, "Failed to allocate memory\n"); - return 1; - } - - //parse min/max values - token = strtok(NULL, " "); - if(!token){ - fprintf(stderr, "Missing minimum specification for parameter %zu of %s\n", p, option); - return 1; - } - data->channel[data->channels].min = osc_parse_value_spec(format[p], token); - - token = strtok(NULL, " "); - if(!token){ - fprintf(stderr, "Missing maximum specification for parameter %zu of %s\n", p, option); - return 1; - } - data->channel[data->channels].max = osc_parse_value_spec(format[p], token); - - //allocate channel from core - if(!mm_channel(inst, data->channels, 1)){ - fprintf(stderr, "Failed to register core channel\n"); - return 1; - } - - //increase channel count - data->channels++; - } - return 0; - } - - fprintf(stderr, "Unknown configuration parameter %s for OSC backend\n", option); - return 1; -} - -static instance* backend_instance(){ - instance* inst = mm_instance(); - if(!inst){ - return NULL; - } - - osc_instance* data = calloc(1, sizeof(osc_instance)); - if(!data){ - fprintf(stderr, "Failed to allocate memory\n"); - return NULL; - } - - data->fd = -1; - inst->impl = data; - return inst; -} - -static channel* backend_channel(instance* inst, char* spec){ - size_t u; - osc_instance* data = (osc_instance*) inst->impl; - size_t param_index = 0; - - //check spec for correctness - if(osc_validate_path(spec)){ - return NULL; - } - - //parse parameter offset - if(strrchr(spec, ':')){ - param_index = strtoul(strrchr(spec, ':') + 1, NULL, 10); - *(strrchr(spec, ':')) = 0; - } - - //find matching channel - for(u = 0; u < data->channels; u++){ - if(!strcmp(spec, data->channel[u].path) && data->channel[u].param_index == param_index){ - //fprintf(stderr, "Reusing previously created channel %s parameter %zu\n", data->channel[u].path, data->channel[u].param_index); - break; - } - } - - //allocate new channel - if(u == data->channels){ - data->channel = realloc(data->channel, (u + 1) * sizeof(osc_channel)); - if(!data->channel){ - fprintf(stderr, "Failed to allocate memory\n"); - return NULL; - } - - memset(data->channel + u, 0, sizeof(osc_channel)); - data->channel[u].param_index = param_index; - data->channel[u].path = strdup(spec); - - if(!data->channel[u].path){ - fprintf(stderr, "Failed to allocate memory\n"); - return NULL; - } - data->channels++; - } - - return mm_channel(inst, u, 1); -} - -static int backend_set(instance* inst, size_t num, channel** c, channel_value* v){ - uint8_t xmit_buf[OSC_XMIT_BUF], *format = NULL; - size_t evt = 0, off, members, p; - if(!num){ - return 0; - } - - osc_instance* data = (osc_instance*) inst->impl; - if(!data->dest_len){ - fprintf(stderr, "OSC instance %s does not have a destination, output is disabled (%zu channels)\n", inst->name, num); - return 0; - } - - for(evt = 0; evt < num; evt++){ - off = c[evt]->ident; - - //sanity check - if(off >= data->channels){ - fprintf(stderr, "OSC channel identifier out of range\n"); - return 1; - } - - //if the format is unknown, don't output - if(data->channel[off].type == not_set || data->channel[off].params == 0){ - fprintf(stderr, "OSC channel %s.%s requires format specification for output\n", inst->name, data->channel[off].path); - continue; - } - - //update current value - data->channel[off].current = osc_parameter_denormalise(data->channel[off].type, data->channel[off].min, data->channel[off].max, v[evt]); - //mark channel - data->channel[off].mark = 1; - } - - //fix destination rport if required - if(data->forced_rport){ - //cheating a bit because both IPv4 and IPv6 have the port at the same offset - struct sockaddr_in* sockadd = (struct sockaddr_in*) &(data->dest); - sockadd->sin_port = htobe16(data->forced_rport); - } - - //find all marked channels - for(evt = 0; evt < data->channels; evt++){ - //zero output buffer - memset(xmit_buf, 0, sizeof(xmit_buf)); - if(data->channel[evt].mark){ - //determine minimum packet size - if(osc_align((data->root ? strlen(data->root) : 0) + strlen(data->channel[evt].path) + 1) + osc_align(data->channel[evt].params + 2) >= sizeof(xmit_buf)){ - fprintf(stderr, "Insufficient buffer size for OSC transmitting channel %s.%s\n", inst->name, data->channel[evt].path); - return 1; - } - - off = 0; - //copy osc target path - if(data->root){ - memcpy(xmit_buf, data->root, strlen(data->root)); - off += strlen(data->root); - } - memcpy(xmit_buf + off, data->channel[evt].path, strlen(data->channel[evt].path)); - off += strlen(data->channel[evt].path) + 1; - off = osc_align(off); - - //get format string offset, initialize - format = xmit_buf + off; - off += osc_align(data->channel[evt].params + 2); - *format = ','; - format++; - - //gather subchannels, unmark - members = 0; - for(p = 0; p < data->channels && members < data->channel[evt].params; p++){ - if(!strcmp(data->channel[evt].path, data->channel[p].path)){ - //unmark channel - data->channel[p].mark = 0; - - //sanity check - if(data->channel[p].param_index >= data->channel[evt].params){ - fprintf(stderr, "OSC channel %s.%s has multiple parameter offset definitions\n", inst->name, data->channel[evt].path); - return 1; - } - - //write format specifier - format[data->channel[p].param_index] = data->channel[p].type; - - //write data - //FIXME this currently depends on all channels being registered in the correct order, since it just appends data - if(off + osc_data_length(data->channel[p].type) >= sizeof(xmit_buf)){ - fprintf(stderr, "Insufficient buffer size for OSC transmitting channel %s.%s at parameter %zu\n", inst->name, data->channel[evt].path, members); - return 1; - } - - osc_deparse(data->channel[p].type, data->channel[p].current, xmit_buf + off); - off += osc_data_length(data->channel[p].type); - members++; - } - } - - //output packet - if(sendto(data->fd, xmit_buf, off, 0, (struct sockaddr*) &(data->dest), data->dest_len) < 0){ - fprintf(stderr, "Failed to transmit OSC packet: %s\n", strerror(errno)); - } - } - } - return 0; -} - -static int backend_handle(size_t num, managed_fd* fds){ - size_t fd; - char recv_buf[OSC_RECV_BUF]; - instance* inst = NULL; - osc_instance* data = NULL; - ssize_t bytes_read = 0; - size_t c; - char* osc_fmt = NULL; - char* osc_local = NULL; - uint8_t* osc_data = NULL; - - for(fd = 0; fd < num; fd++){ - inst = (instance*) fds[fd].impl; - if(!inst){ - fprintf(stderr, "OSC backend signaled for unknown fd\n"); - continue; - } - - data = (osc_instance*) inst->impl; - - do{ - if(data->learn){ - data->dest_len = sizeof(data->dest); - bytes_read = recvfrom(fds[fd].fd, recv_buf, sizeof(recv_buf), 0, (struct sockaddr*) &(data->dest), &(data->dest_len)); - } - else{ - bytes_read = recv(fds[fd].fd, recv_buf, sizeof(recv_buf), 0); - } - if(data->root && strncmp(recv_buf, data->root, strlen(data->root))){ - //ignore packet for different root - continue; - } - osc_local = recv_buf + (data->root ? strlen(data->root) : 0); - - if(bytes_read < 0){ - break; - } - - osc_fmt = recv_buf + osc_align(strlen(recv_buf) + 1); - if(*osc_fmt != ','){ - //invalid format string - fprintf(stderr, "Invalid OSC format string in packet\n"); - continue; - } - osc_fmt++; - - osc_data = (uint8_t*) osc_fmt + (osc_align(strlen(osc_fmt) + 2) - 1); - //FIXME check supplied data length - - for(c = 0; c < data->channels; c++){ - //FIXME implement proper OSC path match - //prefix match - if(!strcmp(osc_local, data->channel[c].path)){ - if(strlen(osc_fmt) > data->channel[c].param_index){ - //fprintf(stderr, "Taking parameter %zu of %s (%s), %zd bytes, data offset %zu\n", data->channel[c].param_index, recv_buf, osc_fmt, bytes_read, (osc_data - (uint8_t*)recv_buf)); - if(osc_generate_event(mm_channel(inst, c, 0), data->channel + c, osc_fmt, osc_data, bytes_read - (osc_data - (uint8_t*) recv_buf))){ - fprintf(stderr, "Failed to generate OSC channel event\n"); - } - } - } - } - } while(bytes_read > 0); - - if(bytes_read < 0 && errno != EAGAIN){ - fprintf(stderr, "OSC failed to receive data for instance %s: %s\n", inst->name, strerror(errno)); - } - - if(bytes_read == 0){ - fprintf(stderr, "OSC descriptor for instance %s closed\n", inst->name); - return 1; - } - } - - return 0; -} - -static int backend_start(){ - size_t n, u, fds = 0; - instance** inst = NULL; - osc_instance* data = NULL; - - //fetch all 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; - } - - //update instance identifiers - for(u = 0; u < n; u++){ - data = (osc_instance*) inst[u]->impl; - - if(data->fd >= 0){ - inst[u]->ident = data->fd; - if(mm_manage_fd(data->fd, BACKEND_NAME, 1, inst[u])){ - fprintf(stderr, "Failed to register OSC descriptor for instance %s\n", inst[u]->name); - free(inst); - return 1; - } - fds++; - } - else{ - inst[u]->ident = -1; - } - } - - fprintf(stderr, "OSC backend registered %zu descriptors to core\n", fds); - - free(inst); - return 0; -} - -static int backend_shutdown(){ - size_t n, u, c; - instance** inst = NULL; - osc_instance* data = NULL; - - if(mm_backend_instances(BACKEND_NAME, &n, &inst)){ - fprintf(stderr, "Failed to fetch instance list\n"); - return 1; - } - - for(u = 0; u < n; u++){ - data = (osc_instance*) inst[u]->impl; - for(c = 0; c < data->channels; c++){ - free(data->channel[c].path); - } - free(data->channel); - free(data->root); - close(data->fd); - data->fd = -1; - data->channels = 0; - free(inst[u]->impl); - } - - free(inst); - return 0; -} diff --git a/osc.h b/osc.h deleted file mode 100644 index 5938f12..0000000 --- a/osc.h +++ /dev/null @@ -1,55 +0,0 @@ -#include "midimonster.h" -#include -#include - -#define OSC_RECV_BUF 8192 -#define OSC_XMIT_BUF 8192 - -int init(); -static int backend_configure(char* option, char* value); -static int backend_configure_instance(instance* instance, char* option, char* value); -static instance* backend_instance(); -static channel* backend_channel(instance* instance, char* spec); -static int backend_set(instance* inst, size_t num, channel** c, channel_value* v); -static int backend_handle(size_t num, managed_fd* fds); -static int backend_start(); -static int backend_shutdown(); - -typedef enum { - not_set = 0, - int32 = 'i', - float32 = 'f', - /*s, b*/ //ignored - int64 = 'h', - double64 = 'd', -} osc_parameter_type; - -typedef union { - int32_t i32; - float f; - int64_t i64; - double d; -} osc_parameter_value; - -typedef struct /*_osc_channel*/ { - char* path; - size_t params; - size_t param_index; - uint8_t mark; - - osc_parameter_type type; - osc_parameter_value max; - osc_parameter_value min; - osc_parameter_value current; -} osc_channel; - -typedef struct /*_osc_instance_data*/ { - size_t channels; - osc_channel* channel; - char* root; - socklen_t dest_len; - struct sockaddr_storage dest; - int fd; - uint8_t learn; - uint16_t forced_rport; -} osc_instance; diff --git a/sacn.c b/sacn.c deleted file mode 100644 index 9a3202d..0000000 --- a/sacn.c +++ /dev/null @@ -1,736 +0,0 @@ -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "sacn.h" -//upper limit imposed by using the fd index as 16-bit part of the instance id -#define MAX_FDS 4096 -#define BACKEND_NAME "sacn" - -static struct /*_sacn_global_config*/ { - uint8_t source_name[64]; - uint8_t cid[16]; - size_t fds; - sacn_fd* fd; - uint64_t last_announce; -} global_cfg = { - .source_name = "MIDIMonster", - .cid = {'M', 'I', 'D', 'I', 'M', 'o', 'n', 's', 't', 'e', 'r'}, - .fds = 0, - .fd = NULL, - .last_announce = 0 -}; - -int init(){ - backend sacn = { - .name = BACKEND_NAME, - .conf = sacn_configure, - .create = sacn_instance, - .conf_instance = sacn_configure_instance, - .channel = sacn_channel, - .handle = sacn_set, - .process = sacn_handle, - .start = sacn_start, - .shutdown = sacn_shutdown - }; - - //register the backend - if(mm_backend_register(sacn)){ - fprintf(stderr, "Failed to register sACN backend\n"); - return 1; - } - - return 0; -} - -static int sacn_listener(char* host, char* port, uint8_t fd_flags){ - 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; - - if(global_cfg.fds >= MAX_FDS){ - fprintf(stderr, "sACN backend descriptor limit reached\n"); - return -1; - } - - 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 sACN descriptor nonblocking\n"); - return -1; - } - - //store fd - global_cfg.fd = realloc(global_cfg.fd, (global_cfg.fds + 1) * sizeof(sacn_fd)); - if(!global_cfg.fd){ - fprintf(stderr, "Failed to allocate memory\n"); - return -1; - } - - fprintf(stderr, "sACN backend interface %zu bound to %s port %s\n", global_cfg.fds, host, port); - global_cfg.fd[global_cfg.fds].fd = fd; - global_cfg.fd[global_cfg.fds].flags = fd_flags; - global_cfg.fd[global_cfg.fds].universes = 0; - global_cfg.fd[global_cfg.fds].universe = NULL; - global_cfg.fd[global_cfg.fds].last_frame = NULL; - global_cfg.fds++; - return 0; -} - -static int sacn_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 sacn_parse_hostspec(char* in, char** host, char** port, uint8_t* flags){ - 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 = SACN_PORT; - } - - if(flags){ - //TODO parse hostspec trailing data for options - *flags = 0; - } - return 0; -} - -static int sacn_configure(char* option, char* value){ - char* host = NULL, *port = NULL, *next = NULL; - uint8_t flags = 0; - size_t u; - - if(!strcmp(option, "name")){ - if(strlen(value) > 63){ - fprintf(stderr, "Invalid sACN source name %s, limit is 63 characters\n", value); - return 1; - } - - memset(global_cfg.source_name, 0, sizeof(global_cfg.source_name)); - memcpy(global_cfg.source_name, value, strlen(value)); - return 0; - } - else if(!strcmp(option, "cid")){ - next = value; - for(u = 0; u < sizeof(global_cfg.cid); u++){ - global_cfg.cid[u] = (strtoul(next, &next, 0) & 0xFF); - } - } - else if(!strcmp(option, "bind")){ - if(sacn_parse_hostspec(value, &host, &port, &flags)){ - fprintf(stderr, "Not a valid sACN bind address: %s\n", value); - return 1; - } - - if(sacn_listener(host, port, flags)){ - fprintf(stderr, "Failed to bind sACN descriptor: %s\n", value); - return 1; - } - return 0; - } - - fprintf(stderr, "Unknown sACN backend option %s\n", option); - return 1; -} - -static int sacn_configure_instance(instance* inst, char* option, char* value){ - sacn_instance_data* data = (sacn_instance_data*) inst->impl; - char* host = NULL, *port = NULL, *next = NULL; - size_t u; - - if(!strcmp(option, "universe")){ - data->uni = strtoul(value, NULL, 10); - return 0; - } - else if(!strcmp(option, "interface")){ - data->fd_index = strtoul(value, NULL, 10); - - if(data->fd_index >= global_cfg.fds){ - fprintf(stderr, "Configured sACN interface index is out of range on instance %s\n", inst->name); - return 1; - } - return 0; - } - else if(!strcmp(option, "priority")){ - data->xmit_prio = strtoul(value, NULL, 10); - return 0; - } - else if(!strcmp(option, "destination")){ - if(sacn_parse_hostspec(value, &host, &port, NULL)){ - fprintf(stderr, "Not a valid sACN destination for instance %s: %s\n", inst->name, value); - return 1; - } - - return sacn_parse_addr(host, port, &data->dest_addr, &data->dest_len); - } - else if(!strcmp(option, "from")){ - next = value; - data->filter_enabled = 1; - for(u = 0; u < sizeof(data->cid_filter); u++){ - data->cid_filter[u] = (strtoul(next, &next, 0) & 0xFF); - } - fprintf(stderr, "Enabled source CID filter for instance %s\n", inst->name); - return 0; - } - else if(!strcmp(option, "unicast")){ - data->unicast_input = strtoul(value, NULL, 10); - } - - fprintf(stderr, "Unknown configuration option %s for sACN backend\n", option); - return 1; -} - -static instance* sacn_instance(){ - instance* inst = mm_instance(); - if(!inst){ - return NULL; - } - - inst->impl = calloc(1, sizeof(sacn_instance_data)); - if(!inst->impl){ - fprintf(stderr, "Failed to allocate memory"); - return NULL; - } - - return inst; -} - -static channel* sacn_channel(instance* inst, char* spec){ - sacn_instance_data* data = (sacn_instance_data*) inst->impl; - char* spec_next = spec; - - unsigned chan_a = strtoul(spec, &spec_next, 10), chan_b = 0; - - //range check - if(!chan_a || chan_a > 512){ - fprintf(stderr, "sACN channel out of range on instance %s: %s\n", inst->name, spec); - return NULL; - } - chan_a--; - - //if wide channel, mark fine - if(*spec_next == '+'){ - chan_b = strtoul(spec_next + 1, NULL, 10); - if(!chan_b || chan_b > 512){ - fprintf(stderr, "Invalid wide-channel spec on instance %s: %s\n", inst->name, spec); - return NULL; - } - chan_b--; - - //if already mapped, bail - if(IS_ACTIVE(data->data.map[chan_b]) && data->data.map[chan_b] != (MAP_FINE | chan_a)){ - fprintf(stderr, "Fine channel %u already mapped on instance %s\n", chan_b, inst->name); - return NULL; - } - - data->data.map[chan_b] = MAP_FINE | chan_a; - } - - //if already active, assert that nothing changes - if(IS_ACTIVE(data->data.map[chan_a])){ - if((*spec_next == '+' && data->data.map[chan_a] != (MAP_COARSE | chan_b)) - || (*spec_next != '+' && data->data.map[chan_a] != (MAP_SINGLE | chan_a))){ - fprintf(stderr, "Primary sACN channel %u already mapped in another mode on instance %s\n", chan_a, inst->name); - return NULL; - } - } - - data->data.map[chan_a] = (*spec_next == '+') ? (MAP_COARSE | chan_b) : (MAP_SINGLE | chan_a); - return mm_channel(inst, chan_a, 1); -} - -static int sacn_transmit(instance* inst){ - size_t u; - sacn_instance_data* data = (sacn_instance_data*) inst->impl; - sacn_data_pdu pdu = { - .root = { - .preamble_size = htobe16(0x10), - .postamble_size = 0, - .magic = { 0 }, //memcpy'd - .flags = htobe16(0x7000 | 0x026e), - .vector = htobe32(ROOT_E131_DATA), - .sender_cid = { 0 }, //memcpy'd - .frame_flags = htobe16(0x7000 | 0x0258), - .frame_vector = htobe32(FRAME_E131_DATA) - }, - .data = { - .source_name = "", //memcpy'd - .priority = data->xmit_prio, - .sync_addr = 0, - .sequence = data->data.last_seq++, - .options = 0, - .universe = htobe16(data->uni), - .flags = htobe16(0x7000 | 0x0205), - .vector = DMP_SET_PROPERTY, - .format = 0xA1, - .startcode_offset = 0, - .address_increment = htobe16(1), - .channels = htobe16(513), - .data = { 0 } //memcpy'd - } - }; - - memcpy(pdu.root.magic, SACN_PDU_MAGIC, sizeof(pdu.root.magic)); - memcpy(pdu.root.sender_cid, global_cfg.cid, sizeof(pdu.root.sender_cid)); - memcpy(pdu.data.source_name, global_cfg.source_name, sizeof(pdu.data.source_name)); - memcpy((((uint8_t*)pdu.data.data) + 1), data->data.out, 512); - - if(sendto(global_cfg.fd[data->fd_index].fd, &pdu, sizeof(pdu), 0, (struct sockaddr*) &data->dest_addr, data->dest_len) < 0){ - fprintf(stderr, "Failed to output sACN frame for instance %s: %s\n", inst->name, strerror(errno)); - } - - //update last transmit timestamp - for(u = 0; u < global_cfg.fd[data->fd_index].universes; u++){ - if(global_cfg.fd[data->fd_index].universe[u] == data->uni){ - global_cfg.fd[data->fd_index].last_frame[u] = mm_timestamp(); - } - } - return 0; -} - -static int sacn_set(instance* inst, size_t num, channel** c, channel_value* v){ - size_t u, mark = 0; - sacn_instance_data* data = (sacn_instance_data*) inst->impl; - - if(!num){ - return 0; - } - - if(!data->xmit_prio){ - fprintf(stderr, "sACN instance %s not enabled for output (%zu channel events)\n", inst->name, num); - return 0; - } - - for(u = 0; u < num; u++){ - if(IS_WIDE(data->data.map[c[u]->ident])){ - uint32_t val = v[u].normalised * ((double) 0xFFFF); - - if(data->data.out[c[u]->ident] != ((val >> 8) & 0xFF)){ - mark = 1; - data->data.out[c[u]->ident] = (val >> 8) & 0xFF; - } - - if(data->data.out[MAPPED_CHANNEL(data->data.map[c[u]->ident])] != (val & 0xFF)){ - mark = 1; - data->data.out[MAPPED_CHANNEL(data->data.map[c[u]->ident])] = val & 0xFF; - } - } - else if(data->data.out[c[u]->ident] != (v[u].normalised * 255.0)){ - mark = 1; - data->data.out[c[u]->ident] = v[u].normalised * 255.0; - } - } - - //send packet if required - if(mark){ - sacn_transmit(inst); - } - - return 0; -} - -static int sacn_process_frame(instance* inst, sacn_frame_root* frame, sacn_frame_data* data){ - size_t u, max_mark = 0; - channel* chan = NULL; - channel_value val; - sacn_instance_data* inst_data = (sacn_instance_data*) inst->impl; - - //source filtering - if(inst_data->filter_enabled && memcmp(inst_data->cid_filter, frame->sender_cid, 16)){ - return 0; - } - - if(data->format != 0xa1 - || data->startcode_offset - || be16toh(data->address_increment) != 1){ - fprintf(stderr, "sACN framing not supported\n"); - return 1; - } - - if(be16toh(data->channels) > 513){ - fprintf(stderr, "Invalid sACN frame channel count\n"); - return 1; - } - - //handle source priority (currently a 1-bit counter) - if(inst_data->data.last_priority > data->priority){ - inst_data->data.last_priority = data->priority; - return 0; - } - inst_data->data.last_priority = data->priority; - - //read data (except start code), mark changed channels - for(u = 1; u < be16toh(data->channels); u++){ - if(IS_ACTIVE(inst_data->data.map[u - 1]) - && data->data[u] != inst_data->data.in[u - 1]){ - inst_data->data.in[u - 1] = data->data[u]; - inst_data->data.map[u - 1] |= MAP_MARK; - max_mark = u - 1; - } - } - - //generate events - for(u = 0; u <= max_mark; u++){ - if(inst_data->data.map[u] & MAP_MARK){ - //unmark and get channel - inst_data->data.map[u] &= ~MAP_MARK; - if(inst_data->data.map[u] & MAP_FINE){ - chan = mm_channel(inst, MAPPED_CHANNEL(inst_data->data.map[u]), 0); - } - else{ - chan = mm_channel(inst, u, 0); - } - - if(!chan){ - fprintf(stderr, "Active channel %zu on %s not known to core", u, inst->name); - return 1; - } - - //generate value - if(IS_WIDE(inst_data->data.map[u])){ - inst_data->data.map[MAPPED_CHANNEL(inst_data->data.map[u])] &= ~MAP_MARK; - val.raw.u64 = inst_data->data.in[u] << ((inst_data->data.map[u] & MAP_COARSE) ? 8 : 0); - val.raw.u64 |= inst_data->data.in[MAPPED_CHANNEL(inst_data->data.map[u])] << ((inst_data->data.map[u] & MAP_COARSE) ? 0 : 8); - val.normalised = (double) val.raw.u64 / (double) 0xFFFF; - } - else{ - val.raw.u64 = inst_data->data.in[u]; - val.normalised = (double) val.raw.u64 / 255.0; - } - - if(mm_channel_event(chan, val)){ - fprintf(stderr, "Failed to push sACN channel event to core\n"); - return 1; - } - } - } - return 0; -} - -static void sacn_discovery(size_t fd){ - size_t page = 0, pages = (global_cfg.fd[fd].universes / 512) + 1, universes; - struct sockaddr_in discovery_dest = { - .sin_family = AF_INET, - .sin_port = htobe16(SACN_PORT), - .sin_addr.s_addr = htobe32(((uint32_t) 0xefff0000) | 64214) - }; - - sacn_discovery_pdu pdu = { - .root = { - .preamble_size = htobe16(0x10), - .postamble_size = 0, - .magic = { 0 }, //memcpy'd - .flags = 0, //filled later - .vector = htobe32(ROOT_E131_EXTENDED), - .sender_cid = { 0 }, //memcpy'd - .frame_flags = 0, //filled later - .frame_vector = htobe32(FRAME_E131_DISCOVERY) - }, - .data = { - .source_name = "", //memcpy'd - .flags = 0, //filled later - .vector = htobe32(DISCOVERY_UNIVERSE_LIST), - .page = 0, //filled later - .max_page = pages - 1, - .data = { 0 } //memcpy'd - } - }; - - memcpy(pdu.root.magic, SACN_PDU_MAGIC, sizeof(pdu.root.magic)); - memcpy(pdu.root.sender_cid, global_cfg.cid, sizeof(pdu.root.sender_cid)); - memcpy(pdu.data.source_name, global_cfg.source_name, sizeof(pdu.data.source_name)); - - for(; page < pages; page++){ - universes = (global_cfg.fd[fd].universes - page * 512 >= 512) ? 512 : (global_cfg.fd[fd].universes % 512); - pdu.root.flags = htobe16(0x7000 | (104 + universes * sizeof(uint16_t))); - pdu.root.frame_flags = htobe16(0x7000 | (82 + universes * sizeof(uint16_t))); - pdu.data.flags = htobe16(0x7000 | (8 + universes * sizeof(uint16_t))); - - pdu.data.page = page; - memcpy(pdu.data.data, global_cfg.fd[fd].universe + page * 512, universes * sizeof(uint16_t)); - - if(sendto(global_cfg.fd[fd].fd, &pdu, sizeof(pdu) - (512 - universes) * sizeof(uint16_t), 0, (struct sockaddr*) &discovery_dest, sizeof(discovery_dest)) < 0){ - fprintf(stderr, "Failed to output sACN universe discovery frame for interface %zu: %s\n", fd, strerror(errno)); - } - } -} - -static int sacn_handle(size_t num, managed_fd* fds){ - size_t u, c; - uint64_t timestamp = mm_timestamp(); - ssize_t bytes_read; - char recv_buf[SACN_RECV_BUF]; - instance* inst = NULL; - sacn_instance_id instance_id = { - .label = 0 - }; - sacn_frame_root* frame = (sacn_frame_root*) recv_buf; - sacn_frame_data* data = (sacn_frame_data*) (recv_buf + sizeof(sacn_frame_root)); - - if(mm_timestamp() - global_cfg.last_announce > SACN_DISCOVERY_TIMEOUT){ - //send universe discovery pdu - for(u = 0; u < global_cfg.fds; u++){ - if(global_cfg.fd[u].universes){ - sacn_discovery(u); - } - } - global_cfg.last_announce = timestamp; - } - - //check for keepalive frames - for(u = 0; u < global_cfg.fds; u++){ - for(c = 0; c < global_cfg.fd[u].universes; c++){ - if(timestamp - global_cfg.fd[u].last_frame[c] >= SACN_KEEPALIVE_INTERVAL){ - instance_id.fields.fd_index = u; - instance_id.fields.uni = global_cfg.fd[u].universe[c]; - inst = mm_instance_find(BACKEND_NAME, instance_id.label); - if(inst){ - sacn_transmit(inst); - } - } - } - } - - //early exit - if(!num){ - return 0; - } - - for(u = 0; u < num; u++){ - do{ - bytes_read = recv(fds[u].fd, recv_buf, sizeof(recv_buf), 0); - if(bytes_read > 0 && bytes_read > sizeof(sacn_frame_root)){ - if(!memcmp(frame->magic, SACN_PDU_MAGIC, 12) - && be16toh(frame->preamble_size) == 0x10 - && frame->postamble_size == 0 - && be32toh(frame->vector) == ROOT_E131_DATA - && be32toh(frame->frame_vector) == FRAME_E131_DATA - && data->vector == DMP_SET_PROPERTY){ - instance_id.fields.fd_index = ((uint64_t) fds[u].impl) & 0xFFFF; - instance_id.fields.uni = be16toh(data->universe); - inst = mm_instance_find(BACKEND_NAME, instance_id.label); - if(inst && sacn_process_frame(inst, frame, data)){ - fprintf(stderr, "Failed to process sACN frame\n"); - } - } - } - } while(bytes_read > 0); - - if(bytes_read < 0 && errno != EAGAIN){ - fprintf(stderr, "sACN failed to receive data: %s\n", strerror(errno)); - } - - if(bytes_read == 0){ - fprintf(stderr, "sACN listener closed\n"); - return 1; - } - } - - return 0; -} - -static int sacn_start(){ - size_t n, u, p; - int rv = 1; - instance** inst = NULL; - sacn_instance_data* data = NULL; - sacn_instance_id id = { - .label = 0 - }; - struct ip_mreq mcast_req = { - .imr_interface = { INADDR_ANY } - }; - struct sockaddr_in* dest_v4 = NULL; - - //fetch all 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(!global_cfg.fds){ - fprintf(stderr, "Failed to start sACN backend: no descriptors bound\n"); - return 1; - } - - //update instance identifiers, join multicast groups - for(u = 0; u < n; u++){ - data = (sacn_instance_data*) inst[u]->impl; - id.fields.fd_index = data->fd_index; - id.fields.uni = data->uni; - inst[u]->ident = id.label; - - if(!data->uni){ - fprintf(stderr, "Please specify a universe on instance %s\n", inst[u]->name); - goto bail; - } - - //find duplicates - for(p = 0; p < u; p++){ - if(inst[u]->ident == inst[p]->ident){ - fprintf(stderr, "Colliding sACN instances, use one: %s - %s\n", inst[u]->name, inst[p]->name); - goto bail; - } - } - - if(!data->unicast_input){ - mcast_req.imr_multiaddr.s_addr = htobe32(((uint32_t) 0xefff0000) | ((uint32_t) data->uni)); - if(setsockopt(global_cfg.fd[data->fd_index].fd, IPPROTO_IP, IP_ADD_MEMBERSHIP, &mcast_req, sizeof(mcast_req))){ - fprintf(stderr, "Failed to join Multicast group for sACN universe %u on instance %s: %s\n", data->uni, inst[u]->name, strerror(errno)); - } - } - - if(data->xmit_prio){ - //add to list of advertised universes for this fd - global_cfg.fd[data->fd_index].universe = realloc(global_cfg.fd[data->fd_index].universe, (global_cfg.fd[data->fd_index].universes + 1) * sizeof(uint16_t)); - if(!global_cfg.fd[data->fd_index].universe){ - fprintf(stderr, "Failed to allocate memory\n"); - goto bail; - } - - global_cfg.fd[data->fd_index].universe[global_cfg.fd[data->fd_index].universes] = data->uni; - global_cfg.fd[data->fd_index].universes++; - - //generate multicast destination address if none set - if(!data->dest_len){ - data->dest_len = sizeof(struct sockaddr_in); - dest_v4 = (struct sockaddr_in*) (&data->dest_addr); - dest_v4->sin_family = AF_INET; - dest_v4->sin_port = htobe16(strtoul(SACN_PORT, NULL, 10)); - dest_v4->sin_addr = mcast_req.imr_multiaddr; - } - } - } - - fprintf(stderr, "sACN backend registering %zu descriptors to core\n", global_cfg.fds); - for(u = 0; u < global_cfg.fds; u++){ - //allocate memory for storing last frame transmission timestamp - global_cfg.fd[u].last_frame = calloc(global_cfg.fd[u].universes, sizeof(uint64_t)); - if(!global_cfg.fd[u].last_frame){ - fprintf(stderr, "Failed to allocate memory\n"); - goto bail; - } - if(mm_manage_fd(global_cfg.fd[u].fd, BACKEND_NAME, 1, (void*) u)){ - goto bail; - } - } - - rv = 0; -bail: - free(inst); - return rv; -} - -static int sacn_shutdown(){ - size_t n, p; - instance** inst = NULL; - - if(mm_backend_instances(BACKEND_NAME, &n, &inst)){ - fprintf(stderr, "Failed to fetch instance list\n"); - return 1; - } - - for(p = 0; p < n; p++){ - free(inst[p]->impl); - } - free(inst); - - for(p = 0; p < global_cfg.fds; p++){ - close(global_cfg.fd[p].fd); - free(global_cfg.fd[p].universe); - free(global_cfg.fd[p].last_frame); - } - free(global_cfg.fd); - fprintf(stderr, "sACN backend shut down\n"); - return 0; -} diff --git a/sacn.h b/sacn.h deleted file mode 100644 index e7106f7..0000000 --- a/sacn.h +++ /dev/null @@ -1,126 +0,0 @@ -#include -#include "midimonster.h" - -int init(); -static int sacn_configure(char* option, char* value); -static int sacn_configure_instance(instance* instance, char* option, char* value); -static instance* sacn_instance(); -static channel* sacn_channel(instance* instance, char* spec); -static int sacn_set(instance* inst, size_t num, channel** c, channel_value* v); -static int sacn_handle(size_t num, managed_fd* fds); -static int sacn_start(); -static int sacn_shutdown(); - -#define SACN_PORT "5568" -#define SACN_RECV_BUF 8192 -#define SACN_KEEPALIVE_INTERVAL 2000 -#define SACN_DISCOVERY_TIMEOUT 9000 -#define SACN_PDU_MAGIC "ASC-E1.17\0\0\0" - -#define MAP_COARSE 0x0200 -#define MAP_FINE 0x0400 -#define MAP_SINGLE 0x0800 -#define MAP_MARK 0x1000 -#define MAPPED_CHANNEL(a) ((a) & 0x01FF) -#define IS_ACTIVE(a) ((a) & 0xFE00) -#define IS_WIDE(a) ((a) & (MAP_FINE | MAP_COARSE)) -#define IS_SINGLE(a) ((a) & MAP_SINGLE) - -typedef struct /*_sacn_universe_model*/ { - uint8_t last_priority; - uint8_t last_seq; - uint8_t in[512]; - uint8_t out[512]; - uint16_t map[512]; -} sacn_universe; - -typedef struct /*_sacn_instance_model*/ { - uint16_t uni; - uint8_t xmit_prio; - uint8_t cid_filter[16]; - uint8_t filter_enabled; - uint8_t unicast_input; - struct sockaddr_storage dest_addr; - socklen_t dest_len; - sacn_universe data; - size_t fd_index; -} sacn_instance_data; - -typedef union /*_sacn_instance_id*/ { - struct { - uint16_t fd_index; - uint16_t uni; - uint8_t pad[4]; - } fields; - uint64_t label; -} sacn_instance_id; - -typedef struct /*_sacn_socket*/ { - int fd; - uint8_t flags; - size_t universes; - uint16_t* universe; - uint64_t* last_frame; -} sacn_fd; - -#pragma pack(push, 1) -typedef struct /*_sacn_frame_root*/ { - uint16_t preamble_size; - uint16_t postamble_size; - uint8_t magic[12]; - uint16_t flags; - uint32_t vector; - uint8_t sender_cid[16]; - //framing - uint16_t frame_flags; - uint32_t frame_vector; -} sacn_frame_root; - -typedef struct /*_sacn_frame_data*/ { - //framing - uint8_t source_name[64]; - uint8_t priority; - uint16_t sync_addr; - uint8_t sequence; - uint8_t options; - uint16_t universe; - //dmp - uint16_t flags; - uint8_t vector; - uint8_t format; - uint16_t startcode_offset; - uint16_t address_increment; - uint16_t channels; - uint8_t data[513]; -} sacn_frame_data; - -typedef struct /*_sacn_frame_discovery*/ { - //framing - uint8_t source_name[64]; - uint32_t reserved; - //universe discovery - uint16_t flags; - uint32_t vector; - uint8_t page; - uint8_t max_page; - uint16_t data[512]; -} sacn_frame_discovery; - -typedef struct /*_sacn_xmit_data*/ { - sacn_frame_root root; - sacn_frame_data data; -} sacn_data_pdu; - -typedef struct /*_sacn_xmit_discovery*/ { - sacn_frame_root root; - sacn_frame_discovery data; -} sacn_discovery_pdu; -#pragma pack(pop) - -#define ROOT_E131_DATA 0x4 -#define FRAME_E131_DATA 0x2 -#define DMP_SET_PROPERTY 0x2 - -#define ROOT_E131_EXTENDED 0x8 -#define FRAME_E131_DISCOVERY 0x2 -#define DISCOVERY_UNIVERSE_LIST 0x1 -- cgit v1.2.3