aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
-rw-r--r--monster.cfg60
-rw-r--r--osc.c545
-rw-r--r--osc.h54
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 <string.h>
+#include <unistd.h>
+#include <ctype.h>
+#include <netdb.h>
+#include <errno.h>
+#include <fcntl.h>
+#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 <sys/types.h>
+#include <sys/socket.h>
+
+#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;