From 1ffe53ba1493741b209c3040a359799c5ada4cf4 Mon Sep 17 00:00:00 2001 From: cbdev Date: Mon, 3 Jul 2017 23:55:07 +0200 Subject: Working OSC receiver --- monster.cfg | 60 +++++-- osc.c | 545 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++- osc.h | 54 +++++- 3 files changed, 647 insertions(+), 12 deletions(-) diff --git a/monster.cfg b/monster.cfg index f1caf87..d915fae 100644 --- a/monster.cfg +++ b/monster.cfg @@ -5,18 +5,58 @@ name = MIDIMonster bind = * 6454 net = 0 -[midi pad] -read = Launchpad -write = Launchpad +[osc test] +bind = * 8000 +dest = ::1 8001 +root = /1 + +[midi lc] +write = FLUID [loopback loop] [map] -loop.test = pad.note0.0 +loop.x = test./xy:0 +loop.y = test./xy:1 +loop.f1 = test./fader1 +loop.f2 = test./fader2 +loop.f3 = test./fader3 +loop.f4 = test./fader4 +loop.t1 = test./toggle1 +loop.t2 = test./toggle2 +loop.t3 = test./toggle3 +loop.pa1 = test./push1 +loop.pa2 = test./push2 +loop.pa3 = test./push3 +loop.pa4 = test./push4 +loop.pa5 = test./push5 +loop.pa6 = test./push6 +loop.pa7 = test./push7 +loop.pa8 = test./push8 +loop.pa9 = test./push9 +loop.pb0 = test./push10 +loop.pb1 = test./push11 +loop.pb2 = test./push12 + +lc.note0.30 = loop.x +lc.note0.31 = loop.y +lc.note0.32 = loop.f1 +lc.note0.33 = loop.f2 +lc.note0.34 = loop.f3 +lc.note0.35 = loop.f4 +lc.note0.36 = loop.t1 +lc.note0.37 = loop.t2 +lc.note0.38 = loop.t3 -pad.note0.1 = loop.test -pad.note0.2 = loop.test -pad.note0.3 = loop.test -pad.note0.4 = loop.test -pad.note0.5 = loop.test -pad.note0.6 = loop.test +lc.note0.63 = loop.pa1 +lc.note0.64 = loop.pa2 +lc.note0.65 = loop.pa3 +lc.note0.66 = loop.pa4 +lc.note0.67 = loop.pa5 +lc.note0.68 = loop.pa6 +lc.note0.69 = loop.pa7 +lc.note0.70 = loop.pa8 +lc.note0.71 = loop.pa9 +lc.note0.72 = loop.pb0 +lc.note0.73 = loop.pb1 +lc.note0.74 = loop.pb2 diff --git a/osc.c b/osc.c index 040dd01..660c9ac 100644 --- a/osc.c +++ b/osc.c @@ -1,3 +1,546 @@ -int osc_start(){ +#include +#include +#include +#include +#include +#include +#include "osc.h" + +#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 channel_value osc_parameter_interpolate(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 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_interpolate(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; + + if(!strcmp(option, "root")){ + if(osc_validate_path(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")){ + 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; + } + + //TODO channel configuration + //create path.INDEX channels + fprintf(stderr, "Unknown configuration parameter %s for OSC backend\n", option); return 1; } + +static instance* backend_instance(){ + instance* i = mm_instance(); + osc_instance* data = calloc(1, sizeof(osc_instance)); + data->fd = -1; + + if(!i || !data){ + fprintf(stderr, "Failed to allocate memory\n"); + return NULL; + } + + i->impl = data; + return i; +} + +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){ + 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){ + osc_instance* data = (osc_instance*) inst->impl; + if(!data->dest_len){ + fprintf(stderr, "OSC instance %s does not have a destination, output is disabled\n", inst->name); + return 0; + } + + //TODO aggregate dot-channels + 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{ + 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(!strncmp(osc_local, data->channel[c].path, strlen(data->channel[c].path)) && strlen(data->channel[c].path) == strlen(osc_local)){ + 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 registering %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[c].param); + } + 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 index 6cc321d..8fcad71 100644 --- a/osc.h +++ b/osc.h @@ -1,2 +1,54 @@ #include "midimonster.h" -int osc_start(); +#include +#include + +#define OSC_RECV_BUF 4096 + +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; + size_t* param; + 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; + uint8_t output; + char* root; + socklen_t dest_len; + struct sockaddr_storage dest; + int fd; +} osc_instance; -- cgit v1.2.3