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 --- 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 ++++++++ 13 files changed, 3342 insertions(+) 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 (limited to 'backends') 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 -- cgit v1.2.3