From ee75bee08b8fb280fc1d76e8635cf29c576835da Mon Sep 17 00:00:00 2001 From: cbdev Date: Sun, 30 Jun 2019 14:13:22 +0200 Subject: Update OSC backend to API design --- backends/osc.h | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) (limited to 'backends/osc.h') diff --git a/backends/osc.h b/backends/osc.h index 5938f12..dc6cb3a 100644 --- a/backends/osc.h +++ b/backends/osc.h @@ -6,14 +6,14 @@ #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(); +static int osc_configure(char* option, char* value); +static int osc_configure_instance(instance* inst, char* option, char* value); +static instance* osc_instance(); +static channel* osc_map_channel(instance* inst, char* spec); +static int osc_set(instance* inst, size_t num, channel** c, channel_value* v); +static int osc_handle(size_t num, managed_fd* fds); +static int osc_start(); +static int osc_shutdown(); typedef enum { not_set = 0, @@ -52,4 +52,4 @@ typedef struct /*_osc_instance_data*/ { int fd; uint8_t learn; uint16_t forced_rport; -} osc_instance; +} osc_instance_data; -- cgit v1.2.3 From 26ee2eacc7d60aa379c9e4b9b9c6b8bcdcd4bc6b Mon Sep 17 00:00:00 2001 From: cbdev Date: Sat, 27 Jul 2019 19:31:21 +0200 Subject: Refactor OSC backend, implement pattern matching --- README.md | 4 +- TODO | 1 - backends/Makefile | 4 +- backends/osc.c | 635 ++++++++++++++++++++++++++++++++-------------- backends/osc.h | 25 +- backends/osc.md | 21 +- configs/flying-faders.cfg | 24 ++ configs/flying-faders.lua | 10 + configs/osc-xy.cfg | 26 ++ 9 files changed, 543 insertions(+), 207 deletions(-) create mode 100644 configs/flying-faders.cfg create mode 100644 configs/flying-faders.lua create mode 100644 configs/osc-xy.cfg (limited to 'backends/osc.h') diff --git a/README.md b/README.md index 3704f5f..9647d4f 100644 --- a/README.md +++ b/README.md @@ -7,8 +7,8 @@ Currently, the MIDIMonster supports the following protocols: * MIDI (Linux, via ALSA) * ArtNet -* sACN / E1.31 -* OSC +* Streaming ACN (sACN / E1.31) +* OpenSoundControl (OSC) * evdev input devices (Linux) * Open Lighting Architecture (OLA) diff --git a/TODO b/TODO index d114640..2a97c30 100644 --- a/TODO +++ b/TODO @@ -3,6 +3,5 @@ Note source in channel value struct Optimize core channel search (store backend offset) Printing backend / Verbose mode -document example configs evdev relative axis size mm_managed_fd.impl is not freed currently diff --git a/backends/Makefile b/backends/Makefile index fe88669..22cb95b 100644 --- a/backends/Makefile +++ b/backends/Makefile @@ -6,8 +6,8 @@ BACKEND_LIB = libmmbackend.o SYSTEM := $(shell uname -s) -CFLAGS += -fPIC -I../ -CPPFLAGS += -fPIC -I../ +CFLAGS += -g -fPIC -I../ +CPPFLAGS += -g -fPIC -I../ LDFLAGS += -shared # Build Linux backends if possible diff --git a/backends/osc.c b/backends/osc.c index 36b0993..3f19abf 100644 --- a/backends/osc.c +++ b/backends/osc.c @@ -13,6 +13,14 @@ #define osc_align(a) ((((a) / 4) + (((a) % 4) ? 1 : 0)) * 4) #define BACKEND_NAME "osc" +typedef union { + struct { + uint32_t channel; + uint32_t parameter; + } fields; + uint64_t label; +} osc_channel_ident; + static struct { uint8_t detect; } osc_global_config = { @@ -41,6 +49,7 @@ int init(){ } static size_t osc_data_length(osc_parameter_type t){ + //binary representation lengths for osc data types switch(t){ case int32: case float32: @@ -55,6 +64,7 @@ static size_t osc_data_length(osc_parameter_type t){ } static inline void osc_defaults(osc_parameter_type t, osc_parameter_value* max, osc_parameter_value* min){ + //data type default ranges memset(max, 0, sizeof(osc_parameter_value)); memset(min, 0, sizeof(osc_parameter_value)); switch(t){ @@ -77,6 +87,7 @@ static inline void osc_defaults(osc_parameter_type t, osc_parameter_value* max, } static inline osc_parameter_value osc_parse(osc_parameter_type t, uint8_t* data){ + //read value from binary representation osc_parameter_value v = {0}; switch(t){ case int32: @@ -94,6 +105,7 @@ static inline osc_parameter_value osc_parse(osc_parameter_type t, uint8_t* data) } static inline int osc_deparse(osc_parameter_type t, osc_parameter_value v, uint8_t* data){ + //write value to binary representation uint64_t u64 = 0; uint32_t u32 = 0; switch(t){ @@ -115,6 +127,7 @@ static inline int osc_deparse(osc_parameter_type t, osc_parameter_value v, uint8 } static inline osc_parameter_value osc_parse_value_spec(osc_parameter_type t, char* value){ + //read value from string osc_parameter_value v = {0}; switch(t){ case int32: @@ -136,6 +149,7 @@ static inline osc_parameter_value osc_parse_value_spec(osc_parameter_type t, cha } static inline channel_value osc_parameter_normalise(osc_parameter_type t, osc_parameter_value min, osc_parameter_value max, osc_parameter_value cur){ + //normalise osc value wrt given min/max channel_value v = { .raw = {0}, .normalised = 0 @@ -179,6 +193,7 @@ static inline channel_value osc_parameter_normalise(osc_parameter_type t, osc_pa } static inline osc_parameter_value osc_parameter_denormalise(osc_parameter_type t, osc_parameter_value min, osc_parameter_value max, channel_value cur){ + //convert normalised value to osc value wrt given min/max osc_parameter_value v = {0}; union { @@ -212,45 +227,192 @@ static inline osc_parameter_value osc_parameter_denormalise(osc_parameter_type t 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; +static int osc_path_validate(char* path, uint8_t allow_patterns){ + //validate osc path or pattern + char illegal_chars[] = " #,"; + char pattern_chars[] = "?[]{}*"; + size_t u, c; + uint8_t square_open = 0, curly_open = 0; + + if(path[0] != '/'){ + fprintf(stderr, "%s is not a valid OSC path: Missing root /\n", path); + return 1; } - osc_parameter_value min, max, cur; - channel_value evt; + for(u = 0; u < strlen(path); u++){ + for(c = 0; c < sizeof(illegal_chars); c++){ + if(path[u] == illegal_chars[c]){ + fprintf(stderr, "%s is not a valid OSC path: Illegal '%c' at %lu\n", path, illegal_chars[c], u); + return 1; + } + } - if(!fmt || !data || data_len % 4 || !*fmt){ - fprintf(stderr, "Invalid OSC packet, data length %zu\n", data_len); - return 1; - } + if(!isgraph(path[u])){ + fprintf(stderr, "%s is not a valid OSC path: Illegal '%c' at %lu\n", path, pattern_chars[c], u); + return 1; + } - //find offset for this parameter - for(p = 0; p < info->param_index; p++){ - off += osc_data_length(fmt[p]); - } + if(!allow_patterns){ + for(c = 0; c < sizeof(pattern_chars); c++){ + if(path[u] == pattern_chars[c]){ + fprintf(stderr, "%s is not a valid OSC path: Illegal '%c' at %lu\n", path, pattern_chars[c], u); + return 1; + } + } + } - if(info->type != not_set){ - max = info->max; - min = info->min; + switch(path[u]){ + case '{': + if(square_open || curly_open){ + fprintf(stderr, "%s is not a valid OSC path: Illegal '%c' at %lu\n", path, pattern_chars[c], u); + return 1; + } + curly_open = 1; + break; + case '[': + if(square_open || curly_open){ + fprintf(stderr, "%s is not a valid OSC path: Illegal '%c' at %lu\n", path, pattern_chars[c], u); + return 1; + } + square_open = 1; + break; + case '}': + curly_open = 0; + break; + case ']': + square_open = 0; + break; + case '/': + if(square_open || curly_open){ + fprintf(stderr, "%s is not a valid OSC path: Pattern across part boundaries\n", path); + return 1; + } + } } - else{ - osc_defaults(fmt[info->param_index], &max, &min); + + if(square_open || curly_open){ + fprintf(stderr, "%s is not a valid OSC path: Unterminated pattern expression\n", path); + return 1; } + return 0; +} - cur = osc_parse(fmt[info->param_index], data + off); - evt = osc_parameter_normalise(fmt[info->param_index], min, max, cur); +static int osc_path_match(char* pattern, char* path){ + size_t u, p = 0, match_begin, match_end; + uint8_t match_any = 0, inverted, match; - return mm_channel_event(c, evt); -} + for(u = 0; u < strlen(path); u++){ + switch(pattern[p]){ + case '/': + if(match_any){ + for(; path[u] && path[u] != '/'; u++){ + } + } + if(path[u] != '/'){ + return 0; + } + match_any = 0; + p++; + break; + case '?': + match_any = 0; + p++; + break; + case '*': + match_any = 1; + p++; + break; + case '[': + inverted = (pattern[p + 1] == '!') ? 1 : 0; + match_end = match_begin = inverted ? p + 2 : p + 1; + match = 0; + for(; pattern[match_end] != ']'; match_end++){ + if(pattern[match_end] == path[u]){ + match = 1; + break; + } -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; + if(pattern[match_end + 1] == '-' && pattern[match_end + 2] != ']'){ + if((pattern[match_end] > pattern[match_end + 2] + && path[u] >= pattern[match_end + 2] + && path[u] <= pattern[match_end]) + || (pattern[match_end] <= pattern[match_end + 2] + && path[u] >= pattern[match_end] + && path[u] <= pattern[match_end + 2])){ + match = 1; + break; + } + match_end += 2; + } + + if(pattern[match_end + 1] == ']' && match_any && !match + && path[u + 1] && path[u + 1] != '/'){ + match_end = match_begin - 1; + u++; + } + } + + if(match == inverted){ + return 0; + } + + match_any = 0; + //advance to end of pattern + for(; pattern[p] != ']'; p++){ + } + p++; + break; + case '{': + for(match_begin = p + 1; pattern[match_begin] != '}'; match_begin++){ + //find end + for(match_end = match_begin; pattern[match_end] != ',' && pattern[match_end] != '}'; match_end++){ + } + + if(!strncmp(path + u, pattern + match_begin, match_end - match_begin)){ + //advance pattern + for(; pattern[p] != '}'; p++){ + } + p++; + //advance path + u += match_end - match_begin - 1; + break; + } + + if(pattern[match_end] == '}'){ + //retry with next if in match_any + if(match_any && path[u + 1] && path[u + 1] != '/'){ + u++; + match_begin = p; + continue; + } + return 0; + } + match_begin = match_end; + } + match_any = 0; + break; + case 0: + if(match_any){ + for(; path[u] && path[u] != '/'; u++){ + } + } + if(path[u]){ + return 0; + } + break; + default: + if(match_any){ + for(; path[u] && path[u] != '/' && path[u] != pattern[p]; u++){ + } + } + if(pattern[p] != path[u]){ + return 0; + } + p++; + break; + } } - return 0; + return 1; } static int osc_configure(char* option, char* value){ @@ -266,13 +428,81 @@ static int osc_configure(char* option, char* value){ return 1; } +static int osc_register_pattern(osc_instance_data* data, char* pattern_path, char* configuration){ + size_t u, pattern; + char* format = NULL, *token = NULL; + + if(osc_path_validate(pattern_path, 1)){ + fprintf(stderr, "Not a valid OSC pattern: %s\n", pattern_path); + return 1; + } + + //tokenize configuration + format = strtok(configuration, " "); + if(!format || strlen(format) < 1){ + fprintf(stderr, "Not a valid format specification for OSC pattern %s\n", pattern_path); + return 1; + } + + //create pattern + data->pattern = realloc(data->pattern, (data->patterns + 1) * sizeof(osc_channel)); + if(!data->pattern){ + fprintf(stderr, "Failed to allocate memory\n"); + return 1; + } + pattern = data->patterns; + + data->pattern[pattern].params = strlen(format); + data->pattern[pattern].path = strdup(pattern_path); + data->pattern[pattern].type = calloc(strlen(format), sizeof(osc_parameter_type)); + data->pattern[pattern].max = calloc(strlen(format), sizeof(osc_parameter_value)); + data->pattern[pattern].min = calloc(strlen(format), sizeof(osc_parameter_value)); + + if(!data->pattern[pattern].path + || !data->pattern[pattern].type + || !data->pattern[pattern].max + || !data->pattern[pattern].min){ + //this should fail config parsing and thus call the shutdown function, + //which should properly free the rest of the data + fprintf(stderr, "Failed to allocate memory\n"); + return 1; + } + + //check format validity and store min/max values + for(u = 0; u < strlen(format); u++){ + if(!osc_data_length(format[u])){ + fprintf(stderr, "Invalid format specifier %c for pattern %s\n", format[u], pattern_path); + return 1; + } + + data->pattern[pattern].type[u] = format[u]; + + //parse min/max values + token = strtok(NULL, " "); + if(!token){ + fprintf(stderr, "Missing minimum specification for parameter %zu of OSC pattern %s\n", u, pattern_path); + return 1; + } + data->pattern[pattern].min[u] = osc_parse_value_spec(format[u], token); + + token = strtok(NULL, " "); + if(!token){ + fprintf(stderr, "Missing maximum specification for parameter %zu of OSC pattern %s\n", u, pattern_path); + return 1; + } + data->pattern[pattern].max[u] = osc_parse_value_spec(format[u], token); + } + + data->patterns++; + return 0; +} + static int osc_configure_instance(instance* inst, char* option, char* value){ osc_instance_data* data = (osc_instance_data*) inst->impl; - char* host = NULL, *port = NULL, *token = NULL, *format = NULL; - size_t u, p; + char* host = NULL, *port = NULL, *token = NULL; if(!strcmp(option, "root")){ - if(osc_validate_path(value)){ + if(osc_path_validate(value, 0)){ fprintf(stderr, "Not a valid OSC root: %s\n", value); return 1; } @@ -326,76 +556,7 @@ static int osc_configure_instance(instance* inst, char* option, char* value){ 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; + return osc_register_pattern(data, option, value); } fprintf(stderr, "Unknown configuration parameter %s for OSC instance %s\n", option, inst->name); @@ -420,31 +581,38 @@ static instance* osc_instance(){ } static channel* osc_map_channel(instance* inst, char* spec){ - size_t u; + size_t u, p; osc_instance_data* data = (osc_instance_data*) inst->impl; - size_t param_index = 0; + osc_channel_ident ident = { + .label = 0 + }; //check spec for correctness - if(osc_validate_path(spec)){ + if(osc_path_validate(spec, 0)){ return NULL; } //parse parameter offset if(strrchr(spec, ':')){ - param_index = strtoul(strrchr(spec, ':') + 1, NULL, 10); + ident.fields.parameter = 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); + if(!strcmp(spec, data->channel[u].path)){ break; } } //allocate new channel if(u == data->channels){ + for(p = 0; p < data->patterns; p++){ + if(osc_path_match(data->pattern[p].path, spec)){ + break; + } + } + data->channel = realloc(data->channel, (u + 1) * sizeof(osc_channel)); if(!data->channel){ fprintf(stderr, "Failed to allocate memory\n"); @@ -452,22 +620,100 @@ static channel* osc_map_channel(instance* inst, char* spec){ } memset(data->channel + u, 0, sizeof(osc_channel)); - data->channel[u].param_index = param_index; data->channel[u].path = strdup(spec); + if(p != data->patterns){ + fprintf(stderr, "Matched pattern %s for %s\n", data->pattern[p].path, spec); + data->channel[u].params = data->pattern[p].params; + //just reuse the pointers from the pattern + data->channel[u].type = data->pattern[p].type; + data->channel[u].max = data->pattern[p].max; + data->channel[u].min = data->pattern[p].min; + + //these are per channel + data->channel[u].in = calloc(data->channel[u].params, sizeof(osc_parameter_value)); + data->channel[u].out = calloc(data->channel[u].params, sizeof(osc_parameter_value)); + } + else if(data->patterns){ + fprintf(stderr, "No pattern match found for %s\n", spec); + } - if(!data->channel[u].path){ + if(!data->channel[u].path + || (data->channel[u].params && (!data->channel[u].in || !data->channel[u].out))){ fprintf(stderr, "Failed to allocate memory\n"); return NULL; } data->channels++; } - return mm_channel(inst, u, 1); + ident.fields.channel = u; + return mm_channel(inst, ident.label, 1); +} + +static int osc_output_channel(instance* inst, size_t channel){ + osc_instance_data* data = (osc_instance_data*) inst->impl; + uint8_t xmit_buf[OSC_XMIT_BUF] = "", *format = NULL; + size_t offset = 0, p; + + //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); + } + + //determine minimum packet size + if(osc_align((data->root ? strlen(data->root) : 0) + strlen(data->channel[channel].path) + 1) + osc_align(data->channel[channel].params + 2) >= sizeof(xmit_buf)){ + fprintf(stderr, "Insufficient buffer size for OSC transmitting channel %s.%s\n", inst->name, data->channel[channel].path); + return 1; + } + + //copy osc target path + if(data->root){ + memcpy(xmit_buf, data->root, strlen(data->root)); + offset += strlen(data->root); + } + + memcpy(xmit_buf + offset, data->channel[channel].path, strlen(data->channel[channel].path)); + offset += strlen(data->channel[channel].path) + 1; + offset = osc_align(offset); + + //get format string offset, initialize + format = xmit_buf + offset; + offset += osc_align(data->channel[channel].params + 2); + *format = ','; + format++; + + for(p = 0; p < data->channel[channel].params; p++){ + //write format specifier + format[p] = data->channel[channel].type[p]; + + //write data + if(offset + osc_data_length(data->channel[channel].type[p]) >= sizeof(xmit_buf)){ + fprintf(stderr, "Insufficient buffer size for OSC transmitting channel %s.%s at parameter %zu\n", inst->name, data->channel[channel].path, p); + return 1; + } + + osc_deparse(data->channel[channel].type[p], + data->channel[channel].out[p], + xmit_buf + offset); + offset += osc_data_length(data->channel[channel].type[p]); + } + + //output packet + if(sendto(data->fd, xmit_buf, offset, 0, (struct sockaddr*) &(data->dest), data->dest_len) < 0){ + fprintf(stderr, "Failed to transmit OSC packet: %s\n", strerror(errno)); + } + return 0; } static int osc_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; + size_t evt = 0, mark = 0; + int rv; + osc_channel_ident ident = { + .label = 0 + }; + osc_parameter_value current; + if(!num){ return 0; } @@ -479,95 +725,97 @@ static int osc_set(instance* inst, size_t num, channel** c, channel_value* v){ } for(evt = 0; evt < num; evt++){ - off = c[evt]->ident; + ident.label = c[evt]->ident; //sanity check - if(off >= data->channels){ + if(ident.fields.channel >= data->channels + || ident.fields.parameter >= data->channel[ident.fields.channel].params){ 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); + if(!data->channel[ident.fields.channel].params){ + fprintf(stderr, "OSC channel %s.%s requires format specification for output\n", inst->name, data->channel[ident.fields.channel].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; + //only output on change + current = osc_parameter_denormalise(data->channel[ident.fields.channel].type[ident.fields.parameter], + data->channel[ident.fields.channel].min[ident.fields.parameter], + data->channel[ident.fields.channel].max[ident.fields.parameter], + v[evt]); + if(memcmp(¤t, &data->channel[ident.fields.channel].out[ident.fields.parameter], sizeof(current))){ + //update current value + data->channel[ident.fields.channel].out[ident.fields.parameter] = current; + //mark channel + data->channel[ident.fields.channel].mark = 1; + 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); + + if(mark){ + //output all marked channels + for(evt = 0; !rv && evt < num; evt++){ + ident.label = c[evt]->ident; + if(data->channel[ident.fields.channel].mark){ + rv |= osc_output_channel(inst, ident.fields.channel); + data->channel[ident.fields.channel].mark = 0; + } + } } + return rv; +} - //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; - } +static int osc_process_packet(instance* inst, char* local_path, char* format, uint8_t* payload, size_t payload_len){ + osc_instance_data* data = (osc_instance_data*) inst->impl; + size_t c, p, offset = 0; + osc_parameter_value min, max, cur; + channel_value evt; + osc_channel_ident ident = { + .label = 0 + }; + channel* chan = NULL; - 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; - } + if(payload_len % 4){ + fprintf(stderr, "Invalid OSC packet, data length %zu\n", payload_len); + return 0; + } - //write format specifier - format[data->channel[p].param_index] = data->channel[p].type; + for(c = 0; c < data->channels; c++){ + if(!strcmp(local_path, data->channel[c].path)){ + ident.fields.channel = c; + //unconfigured input should work without errors (using default limits) + if(data->channel[c].params && strlen(format) != data->channel[c].params){ + fprintf(stderr, "OSC message %s.%s had format %s, internal representation has %lu parameters\n", inst->name, local_path, format, data->channel[c].params); + continue; + } - //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; + for(p = 0; p < strlen(format); p++){ + ident.fields.parameter = p; + if(data->channel[c].params){ + max = data->channel[c].max[p]; + min = data->channel[c].min[p]; + } + else{ + osc_defaults(format[p], &max, &min); + } + cur = osc_parse(format[p], payload + offset); + if(!data->channel[c].params || memcmp(&cur, &data->channel[c].in, sizeof(cur))){ + evt = osc_parameter_normalise(format[p], min, max, cur); + chan = mm_channel(inst, ident.label, 0); + if(chan){ + mm_channel_event(chan, evt); } - - 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)); + //skip to next parameter data + offset += osc_data_length(format[p]); + //TODO check offset against payload length } } } + return 0; } @@ -577,7 +825,6 @@ static int osc_handle(size_t num, managed_fd* fds){ instance* inst = NULL; osc_instance_data* data = NULL; ssize_t bytes_read = 0; - size_t c; char* osc_fmt = NULL; char* osc_local = NULL; uint8_t* osc_data = NULL; @@ -600,7 +847,7 @@ static int osc_handle(size_t num, managed_fd* fds){ bytes_read = recv(fds[fd].fd, recv_buf, sizeof(recv_buf), 0); } - if(bytes_read < 0){ + if(bytes_read <= 0){ break; } @@ -622,20 +869,11 @@ static int osc_handle(size_t num, managed_fd* fds){ fprintf(stderr, "Incoming OSC data: Path %s.%s Format %s\n", inst->name, osc_local, osc_fmt); } - osc_data = (uint8_t*) osc_fmt + (osc_align(strlen(osc_fmt) + 2) - 1); //FIXME check supplied data length + osc_data = (uint8_t*) osc_fmt + (osc_align(strlen(osc_fmt) + 2) - 1); - 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"); - } - } - } + if(osc_process_packet(inst, osc_local, osc_fmt, osc_data, bytes_read - (osc_data - (uint8_t*) recv_buf))){ + return 1; } } while(bytes_read > 0); @@ -706,14 +944,25 @@ static int osc_shutdown(){ data = (osc_instance_data*) inst[u]->impl; for(c = 0; c < data->channels; c++){ free(data->channel[c].path); + free(data->channel[c].in); + free(data->channel[c].out); } free(data->channel); + for(c = 0; c < data->patterns; c++){ + free(data->pattern[c].path); + free(data->pattern[c].type); + free(data->pattern[c].min); + free(data->pattern[c].max); + } + free(data->pattern); + free(data->root); if(data->fd >= 0){ close(data->fd); } data->fd = -1; data->channels = 0; + data->patterns = 0; free(inst[u]->impl); } diff --git a/backends/osc.h b/backends/osc.h index dc6cb3a..4e9dec5 100644 --- a/backends/osc.h +++ b/backends/osc.h @@ -34,22 +34,33 @@ typedef union { 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_parameter_type* type; + osc_parameter_value* max; + osc_parameter_value* min; + osc_parameter_value* in; + osc_parameter_value* out; } osc_channel; typedef struct /*_osc_instance_data*/ { + //pre-configured channel patterns + size_t patterns; + osc_channel* pattern; + + //actual channel registry size_t channels; osc_channel* channel; + + //instance config char* root; + uint8_t learn; + + //peer addressing socklen_t dest_len; struct sockaddr_storage dest; - int fd; - uint8_t learn; uint16_t forced_rport; + + //peer fd + int fd; } osc_instance_data; diff --git a/backends/osc.md b/backends/osc.md index e9aa4d5..b7ce527 100644 --- a/backends/osc.md +++ b/backends/osc.md @@ -23,13 +23,21 @@ it are ignored early in processing. Channels that are to be output or require a value range different from the default ranges (see below) require special configuration, as their types and limits have to be set. -This is done in the instance configuration using an assignment of the syntax +This is done by specifying *patterns* in the instance configuration using an assignment of the syntax ``` /local/osc/path = ... ``` -The OSC path to be configured must only be the local part (omitting a configured instance root). +The pattern will be matched only against the local part (that is, the path excluding any configured instance root). +Patterns may contain the following expressions (conforming to the [OSC pattern matching specification](http://opensoundcontrol.org/spec-1_0)): +* `?` matches any single legal character +* `*` matches zero or more legal characters +* A comma-separated list of strings inside curly braces `{}` matches any of the strings +* A string of characters within square brackets `[]` matches any character in the string + * Two characters with a `-` between them specify a range of characters + * An exclamation mark immediately after the opening `[` negates the meaning of the expression (ie. it matches characters not in the range) +* Any other legal character matches only itself **format** may be any sequence of valid OSC type characters. See below for a table of supported OSC types. @@ -44,6 +52,12 @@ a range between 0.0 and 2.0 (for example, an X-Y control), would look as follows /1/xy1 = ff 0.0 2.0 0.0 2.0 ``` +To configure a range of faders, an expression similar to the following line could be used + +``` +/1/fader* = f 0.0 1.0 +``` + #### Channel specification A channel may be any valid OSC path, to which the instance root will be prepended if @@ -80,4 +94,7 @@ The default ranges are: #### Known bugs / problems +The OSC path match currently works on the unit of characters. This may lead to some unexpected results +when matching expressions of the form `*`. + Ping requests are not yet answered. There may be some problems using broadcast output and input. diff --git a/configs/flying-faders.cfg b/configs/flying-faders.cfg new file mode 100644 index 0000000..4197581 --- /dev/null +++ b/configs/flying-faders.cfg @@ -0,0 +1,24 @@ +; Create a 'flying faders' effect using lua and output +; it onto TouchOSC (Layout 'Mix16', Multifader view on page 4) + +[osc touch] +bind = * 8000 +dest = learn@9000 + +; Pre-declare the fader values so the range mapping is correct +/*/fader* = f 0.0 1.0 +/*/toggle* = f 0.0 1.0 +/*/push* = f 0.0 1.0 +/*/multifader*/* = f 0.0 1.0 +/1/xy = ff 0.0 1.0 0.0 1.0 + +[lua generator] +script = configs/flying-faders.lua + +[map] + +generator.wave{1..24} > touch./4/multifader1/{1..24} +;generator.wave{1..24} > touch./4/multifader2/{1..24} + +touch./4/multifader2/1 > generator.magnitude + diff --git a/configs/flying-faders.lua b/configs/flying-faders.lua new file mode 100644 index 0000000..0b0faef --- /dev/null +++ b/configs/flying-faders.lua @@ -0,0 +1,10 @@ +step = 0 + +function wave() + for chan=1,24 do + output("wave" .. chan, (math.sin(math.rad((step + chan * 360 / 24) % 360)) + 1) / 2) + end + step = (step + 5) % 360 +end + +interval(wave, 100) diff --git a/configs/osc-xy.cfg b/configs/osc-xy.cfg new file mode 100644 index 0000000..fc5c5f3 --- /dev/null +++ b/configs/osc-xy.cfg @@ -0,0 +1,26 @@ +; Test for bi-directional OSC with an XY pad (TouchOSC Layout 'Mix16', Page 1) + +[backend osc] +detect = on + +[osc touch] +bind = 0.0.0.0 8000 +dest = learn@9000 + +; Pre-declare the fader values so the range mapping is correct +/*/xy = ff 0.0 1.0 0.0 1.0 + +[evdev xbox] +device = /dev/input/event16 + +[midi launch] + +[map] +xbox.EV_ABS.ABS_X > touch./1/xy:1 +xbox.EV_ABS.ABS_Y > touch./1/xy:0 + +xbox.EV_ABS.ABS_X > launch.ch0.note2 +;xbox.EV_ABS.ABS_Y > launch.ch0.note3 + +launch.ch0.note0 <> touch./1/xy:0 +launch.ch0.note1 <> touch./1/xy:1 -- cgit v1.2.3 From 20a6882a063404858588596bd3f12bdd9e53460a Mon Sep 17 00:00:00 2001 From: cbdev Date: Sat, 3 Aug 2019 18:42:39 +0200 Subject: Windows build compatiblity --- .gitignore | 3 +++ Makefile | 23 +++++++++++++++++-- backend.c | 31 ++++++++++++++----------- backend.h | 9 +++++++- backends/Makefile | 26 +++++++++++++++++++-- backends/artnet.c | 16 ++++++++----- backends/artnet.h | 2 ++ backends/libmmbackend.c | 12 ++++++++-- backends/libmmbackend.h | 6 +++++ backends/lua.c | 4 ---- backends/osc.c | 18 +++++++++------ backends/osc.h | 2 ++ backends/sacn.c | 28 ++++++++++++++--------- backends/sacn.h | 1 - config.c | 56 +++++++++++++++++++++++++++++++++++++++++++++ midimonster.c | 39 ++++++++++++++++++++++++++------ midimonster.h | 38 ++++++++++++++++++++++++------- monster.cfg | 12 +++------- plugin.c | 60 +++++++++++++++++++++++++++++++++++++++++++++---- portability.h | 17 ++++++++++++++ 20 files changed, 326 insertions(+), 77 deletions(-) (limited to 'backends/osc.h') diff --git a/.gitignore b/.gitignore index 3afd872..4396a38 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,7 @@ midimonster +midimonster.exe +libmmapi.a *.swp *.o *.so +*.dll diff --git a/Makefile b/Makefile index 5ee9cd9..b82d6d8 100644 --- a/Makefile +++ b/Makefile @@ -1,10 +1,14 @@ -.PHONY: all clean run sanitize backends full backends-full +.PHONY: all clean run sanitize backends windows full backends-full OBJS = config.o backend.o plugin.o PLUGINDIR = "\"./backends/\"" +PLUGINDIR_W32 = "\"backends\\\\\"" SYSTEM := $(shell uname -s) -CFLAGS ?= -g -Wall +CFLAGS ?= -g -Wall -Wpedantic +# Hide all non-API symbols for export +CFLAGS += -fvisibility=hidden + #CFLAGS += -DDEBUG midimonster: LDLIBS = -ldl midimonster: CFLAGS += -DPLUGINS=$(PLUGINDIR) @@ -21,6 +25,8 @@ all: midimonster backends full: midimonster backends-full +windows: midimonster.exe + backends: $(MAKE) -C backends @@ -31,8 +37,21 @@ backends-full: midimonster: midimonster.c portability.h $(OBJS) $(CC) $(CFLAGS) $(LDFLAGS) $< $(OBJS) $(LDLIBS) -o $@ +midimonster.exe: export CC = x86_64-w64-mingw32-gcc +#midimonster.exe: CFLAGS += -Wno-format +midimonster.exe: CFLAGS += -DPLUGINS=$(PLUGINDIR_W32) -Wno-format +midimonster.exe: LDLIBS = -lws2_32 +midimonster.exe: LDFLAGS += -Wl,--out-implib,libmmapi.a +midimonster.exe: midimonster.c portability.h $(OBJS) + $(CC) $(CFLAGS) $(LDFLAGS) $< $(OBJS) $(LDLIBS) -o $@ + # The windows build for backends requires the import library generated with the build, + # so the backends can't be a prerequisite for the executable... + $(MAKE) -C backends windows + clean: $(RM) midimonster + $(RM) midimonster.exe + $(RM) libmmapi.a $(RM) $(OBJS) $(MAKE) -C backends clean diff --git a/backend.c b/backend.c index 5df5d73..4fa7704 100644 --- a/backend.c +++ b/backend.c @@ -1,4 +1,9 @@ #include +#ifndef _WIN32 +#define MM_API __attribute__((visibility ("default"))) +#else +#define MM_API __attribute__((dllexport)) +#endif #include "midimonster.h" #include "backend.h" @@ -26,7 +31,7 @@ int backends_handle(size_t nfds, managed_fd* fds){ } } - DBGPF("Notifying backend %s of %zu waiting FDs\n", backends[u].name, n); + DBGPF("Notifying backend %s of %lu waiting FDs\n", backends[u].name, n); rv |= backends[u].process(n, fds); if(rv){ fprintf(stderr, "Backend %s failed to handle input\n", backends[u].name); @@ -59,28 +64,28 @@ int backends_notify(size_t nev, channel** c, channel_value* v){ } } - DBGPF("Calling handler for instance %s with %zu events\n", instances[u]->name, n); + DBGPF("Calling handler for instance %s with %lu events\n", instances[u]->name, n); rv |= instances[u]->backend->handle(instances[u], n, c, v); } return 0; } -channel* mm_channel(instance* i, uint64_t ident, uint8_t create){ +channel* MM_API mm_channel(instance* inst, uint64_t ident, uint8_t create){ size_t u; for(u = 0; u < nchannels; u++){ - if(channels[u]->instance == i && channels[u]->ident == ident){ - DBGPF("Requested channel %zu on instance %s already exists, reusing\n", ident, i->name); + if(channels[u]->instance == inst && channels[u]->ident == ident){ + DBGPF("Requested channel %lu on instance %s already exists, reusing\n", ident, inst->name); return channels[u]; } } if(!create){ - DBGPF("Requested unknown channel %zu on instance %s\n", ident, i->name); + DBGPF("Requested unknown channel %lu on instance %s\n", ident, inst->name); return NULL; } - DBGPF("Creating previously unknown channel %zu on instance %s\n", ident, i->name); + DBGPF("Creating previously unknown channel %lu on instance %s\n", ident, inst->name); channel** new_chan = realloc(channels, (nchannels + 1) * sizeof(channel*)); if(!new_chan){ fprintf(stderr, "Failed to allocate memory\n"); @@ -95,12 +100,12 @@ channel* mm_channel(instance* i, uint64_t ident, uint8_t create){ return NULL; } - channels[nchannels]->instance = i; + channels[nchannels]->instance = inst; channels[nchannels]->ident = ident; return channels[nchannels++]; } -instance* mm_instance(){ +instance* MM_API mm_instance(){ instance** new_inst = realloc(instances, (ninstances + 1) * sizeof(instance*)); if(!new_inst){ //TODO free @@ -118,7 +123,7 @@ instance* mm_instance(){ return instances[ninstances++]; } -instance* mm_instance_find(char* name, uint64_t ident){ +instance* MM_API mm_instance_find(char* name, uint64_t ident){ size_t u; backend* b = backend_match(name); if(!b){ @@ -134,7 +139,7 @@ instance* mm_instance_find(char* name, uint64_t ident){ return NULL; } -int mm_backend_instances(char* name, size_t* ninst, instance*** inst){ +int MM_API mm_backend_instances(char* name, size_t* ninst, instance*** inst){ backend* b = backend_match(name); size_t n = 0, u; //count number of affected instances @@ -177,7 +182,7 @@ void instances_free(){ void channels_free(){ size_t u; for(u = 0; u < nchannels; u++){ - DBGPF("Destroying channel %zu on instance %s\n", channels[u]->ident, channels[u]->instance->name); + DBGPF("Destroying channel %lu on instance %s\n", channels[u]->ident, channels[u]->instance->name); if(channels[u]->impl){ channels[u]->instance->backend->channel_free(channels[u]); } @@ -232,7 +237,7 @@ struct timeval backend_timeout(){ return tv; } -int mm_backend_register(backend b){ +int MM_API mm_backend_register(backend b){ if(!backend_match(b.name)){ backends = realloc(backends, (nbackends + 1) * sizeof(backend)); if(!backends){ diff --git a/backend.h b/backend.h index daf96bc..7529154 100644 --- a/backend.h +++ b/backend.h @@ -1,8 +1,8 @@ #include +/* Internal API */ int backends_handle(size_t nfds, managed_fd* fds); int backends_notify(size_t nev, channel** c, channel_value* v); - backend* backend_match(char* name); instance* instance_match(char* name); struct timeval backend_timeout(); @@ -10,3 +10,10 @@ int backends_start(); int backends_stop(); void instances_free(); void channels_free(); + +/* Backend API */ +channel* MM_API mm_channel(instance* inst, uint64_t ident, uint8_t create); +instance* MM_API mm_instance(); +instance* MM_API mm_instance_find(char* name, uint64_t ident); +int MM_API mm_backend_instances(char* name, size_t* ninst, instance*** inst); +int MM_API mm_backend_register(backend b); diff --git a/backends/Makefile b/backends/Makefile index 22cb95b..2374df0 100644 --- a/backends/Makefile +++ b/backends/Makefile @@ -1,12 +1,13 @@ .PHONY: all clean full OPTIONAL_BACKENDS = ola.so +WINDOWS_BACKENDS = loopback.dll artnet.dll osc.dll sacn.dll LINUX_BACKENDS = midi.so evdev.so BACKENDS = artnet.so osc.so loopback.so sacn.so lua.so BACKEND_LIB = libmmbackend.o SYSTEM := $(shell uname -s) -CFLAGS += -g -fPIC -I../ +CFLAGS += -g -fPIC -I../ -Wall -Wpedantic CPPFLAGS += -g -fPIC -I../ LDFLAGS += -shared @@ -20,8 +21,17 @@ LDFLAGS += -undefined dynamic_lookup endif artnet.so: ADDITIONAL_OBJS += $(BACKEND_LIB) +artnet.dll: ADDITIONAL_OBJS += $(BACKEND_LIB) +artnet.dll: LDLIBS += -lws2_32 + osc.so: ADDITIONAL_OBJS += $(BACKEND_LIB) +osc.dll: ADDITIONAL_OBJS += $(BACKEND_LIB) +osc.dll: LDLIBS += -lws2_32 + sacn.so: ADDITIONAL_OBJS += $(BACKEND_LIB) +sacn.dll: ADDITIONAL_OBJS += $(BACKEND_LIB) +sacn.dll: LDLIBS += -lws2_32 + midi.so: LDLIBS = -lasound evdev.so: CFLAGS += $(shell pkg-config --cflags libevdev) evdev.so: LDLIBS = $(shell pkg-config --libs libevdev) @@ -33,12 +43,24 @@ lua.so: LDLIBS += $(shell pkg-config --libs lua5.3) %.so :: %.c %.h $(BACKEND_LIB) $(CC) $(CFLAGS) $(LDLIBS) $< $(ADDITIONAL_OBJS) -o $@ $(LDFLAGS) +%.dll :: %.c %.h $(BACKEND_LIB) + $(CC) $(CFLAGS) $< $(ADDITIONAL_OBJS) -o $@ $(LDFLAGS) $(LDLIBS) + %.so :: %.cpp %.h $(CXX) $(CPPFLAGS) $(LDLIBS) $< $(ADDITIONAL_OBJS) -o $@ $(LDFLAGS) all: $(BACKEND_LIB) $(BACKENDS) +../libmmapi.a: + $(MAKE) -C ../ midimonster.exe + +windows: export CC = x86_64-w64-mingw32-gcc +windows: LDLIBS += -lmmapi +windows: LDFLAGS += -L../ +windows: CFLAGS += -Wno-format -Wno-pointer-sign +windows: ../libmmapi.a $(BACKEND_LIB) $(WINDOWS_BACKENDS) + full: $(BACKEND_LIB) $(BACKENDS) $(OPTIONAL_BACKENDS) clean: - $(RM) $(BACKEND_LIB) $(BACKENDS) $(OPTIONAL_BACKENDS) + $(RM) $(BACKEND_LIB) $(BACKENDS) $(OPTIONAL_BACKENDS) $(WINDOWS_BACKENDS) diff --git a/backends/artnet.c b/backends/artnet.c index 8b404a6..a6df4ab 100644 --- a/backends/artnet.c +++ b/backends/artnet.c @@ -3,8 +3,8 @@ #include #include "libmmbackend.h" - #include "artnet.h" + #define MAX_FDS 255 #define BACKEND_NAME "artnet" @@ -32,7 +32,7 @@ static int artnet_listener(char* host, char* port){ return -1; } - fprintf(stderr, "ArtNet backend interface %zu bound to %s port %s\n", artnet_fds, host, port); + fprintf(stderr, "ArtNet backend interface %lu 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; @@ -212,7 +212,7 @@ static int artnet_transmit(instance* inst){ }; 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){ + if(sendto(artnet_fd[data->fd_index].fd, (uint8_t*) &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)); } @@ -230,7 +230,7 @@ static int artnet_set(instance* inst, size_t num, channel** c, channel_value* v) 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); + fprintf(stderr, "ArtNet instance %s not enabled for output (%lu channel events)\n", inst->name, num); return 0; } @@ -295,7 +295,7 @@ static inline int artnet_process_frame(instance* inst, artnet_pkt* frame){ } if(!chan){ - fprintf(stderr, "Active channel %zu on %s not known to core\n", p, inst->name); + fprintf(stderr, "Active channel %lu on %s not known to core\n", p, inst->name); return 1; } @@ -367,7 +367,11 @@ static int artnet_handle(size_t num, managed_fd* fds){ } } while(bytes_read > 0); + #ifdef _WIN32 + if(bytes_read < 0 && WSAGetLastError() != WSAEWOULDBLOCK){ + #else if(bytes_read < 0 && errno != EAGAIN){ + #endif fprintf(stderr, "ArtNet failed to receive data: %s\n", strerror(errno)); } @@ -438,7 +442,7 @@ static int artnet_start(){ } } - fprintf(stderr, "ArtNet backend registering %zu descriptors to core\n", artnet_fds); + fprintf(stderr, "ArtNet backend registering %lu 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; diff --git a/backends/artnet.h b/backends/artnet.h index 90aedd5..f5aa745 100644 --- a/backends/artnet.h +++ b/backends/artnet.h @@ -1,4 +1,6 @@ +#ifndef _WIN32 #include +#endif #include "midimonster.h" int init(); diff --git a/backends/libmmbackend.c b/backends/libmmbackend.c index 6320611..b27ebc5 100644 --- a/backends/libmmbackend.c +++ b/backends/libmmbackend.c @@ -53,7 +53,7 @@ int mmbackend_parse_sockaddr(char* host, char* port, struct sockaddr_storage* ad } int mmbackend_socket(char* host, char* port, int socktype, uint8_t listener){ - int fd = -1, status, yes = 1, flags; + int fd = -1, status, yes = 1; struct addrinfo hints = { .ai_family = AF_UNSPEC, .ai_socktype = socktype, @@ -106,12 +106,20 @@ int mmbackend_socket(char* host, char* port, int socktype, uint8_t listener){ } //set nonblocking - flags = fcntl(fd, F_GETFL, 0); + #ifdef _WIN32 + u_long mode = 1; + if(ioctlsocket(fd, FIONBIO, &mode) != NO_ERROR){ + closesocket(fd); + return 1; + } + #else + int flags = fcntl(fd, F_GETFL, 0); if(fcntl(fd, F_SETFL, flags | O_NONBLOCK) < 0){ fprintf(stderr, "Failed to set socket nonblocking\n"); close(fd); return -1; } + #endif return fd; } diff --git a/backends/libmmbackend.h b/backends/libmmbackend.h index 38bfca0..77cad6a 100644 --- a/backends/libmmbackend.h +++ b/backends/libmmbackend.h @@ -1,14 +1,20 @@ #include #include #include +#ifdef _WIN32 +#include +//#define close closesocket +#else #include #include +#endif #include #include #include #include #include #include +#include "../portability.h" /* Parse spec as host specification in the form * host port diff --git a/backends/lua.c b/backends/lua.c index 4a910a2..ec02575 100644 --- a/backends/lua.c +++ b/backends/lua.c @@ -161,8 +161,6 @@ static int lua_callback_output(lua_State* interpreter){ static int lua_callback_interval(lua_State* interpreter){ size_t n = 0; - instance* inst = NULL; - lua_instance_data* data = NULL; uint64_t interval = 0; int reference = LUA_NOREF; @@ -174,8 +172,6 @@ static int lua_callback_interval(lua_State* interpreter){ //get instance pointer from registry lua_pushstring(interpreter, LUA_REGISTRY_KEY); lua_gettable(interpreter, LUA_REGISTRYINDEX); - inst = (instance*) lua_touserdata(interpreter, -1); - data = (lua_instance_data*) inst->impl; //fetch and round the interval interval = luaL_checkinteger(interpreter, 2); diff --git a/backends/osc.c b/backends/osc.c index 18c8bad..03e431f 100644 --- a/backends/osc.c +++ b/backends/osc.c @@ -1,8 +1,8 @@ #include #include #include -#include "libmmbackend.h" +#include "libmmbackend.h" #include "osc.h" /* @@ -480,14 +480,14 @@ static int osc_register_pattern(osc_instance_data* data, char* pattern_path, cha //parse min/max values token = strtok(NULL, " "); if(!token){ - fprintf(stderr, "Missing minimum specification for parameter %zu of OSC pattern %s\n", u, pattern_path); + fprintf(stderr, "Missing minimum specification for parameter %lu of OSC pattern %s\n", u, pattern_path); return 1; } data->pattern[pattern].min[u] = osc_parse_value_spec(format[u], token); token = strtok(NULL, " "); if(!token){ - fprintf(stderr, "Missing maximum specification for parameter %zu of OSC pattern %s\n", u, pattern_path); + fprintf(stderr, "Missing maximum specification for parameter %lu of OSC pattern %s\n", u, pattern_path); return 1; } data->pattern[pattern].max[u] = osc_parse_value_spec(format[u], token); @@ -689,7 +689,7 @@ static int osc_output_channel(instance* inst, size_t channel){ //write data if(offset + osc_data_length(data->channel[channel].type[p]) >= sizeof(xmit_buf)){ - fprintf(stderr, "Insufficient buffer size for OSC transmitting channel %s.%s at parameter %zu\n", inst->name, data->channel[channel].path, p); + fprintf(stderr, "Insufficient buffer size for OSC transmitting channel %s.%s at parameter %lu\n", inst->name, data->channel[channel].path, p); return 1; } @@ -720,7 +720,7 @@ static int osc_set(instance* inst, size_t num, channel** c, channel_value* v){ osc_instance_data* data = (osc_instance_data*) 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); + fprintf(stderr, "OSC instance %s does not have a destination, output is disabled (%lu channels)\n", inst->name, num); return 0; } @@ -778,7 +778,7 @@ static int osc_process_packet(instance* inst, char* local_path, char* format, ui channel* chan = NULL; if(payload_len % 4){ - fprintf(stderr, "Invalid OSC packet, data length %zu\n", payload_len); + fprintf(stderr, "Invalid OSC packet, data length %lu\n", payload_len); return 0; } @@ -877,7 +877,11 @@ static int osc_handle(size_t num, managed_fd* fds){ } } while(bytes_read > 0); + #ifdef _WIN32 + if(bytes_read < 0 && WSAGetLastError() != WSAEWOULDBLOCK){ + #else if(bytes_read < 0 && errno != EAGAIN){ + #endif fprintf(stderr, "OSC failed to receive data for instance %s: %s\n", inst->name, strerror(errno)); } @@ -924,7 +928,7 @@ static int osc_start(){ } } - fprintf(stderr, "OSC backend registered %zu descriptors to core\n", fds); + fprintf(stderr, "OSC backend registered %lu descriptors to core\n", fds); free(inst); return 0; diff --git a/backends/osc.h b/backends/osc.h index 4e9dec5..b2aaea7 100644 --- a/backends/osc.h +++ b/backends/osc.h @@ -1,6 +1,8 @@ #include "midimonster.h" #include +#ifndef _WIN32 #include +#endif #define OSC_RECV_BUF 8192 #define OSC_XMIT_BUF 8192 diff --git a/backends/sacn.c b/backends/sacn.c index 75bb76f..6f7d1a5 100644 --- a/backends/sacn.c +++ b/backends/sacn.c @@ -1,16 +1,18 @@ #include #include -#include -#include #include #include #include #include +#ifndef _WIN32 +#include #include +#include +#endif #include "libmmbackend.h" - #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" @@ -71,7 +73,7 @@ static int sacn_listener(char* host, char* port, uint8_t fd_flags){ return -1; } - fprintf(stderr, "sACN backend interface %zu bound to %s port %s\n", global_cfg.fds, host, port); + fprintf(stderr, "sACN backend interface %lu 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; @@ -271,7 +273,7 @@ static int sacn_transmit(instance* inst){ 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){ + if(sendto(global_cfg.fd[data->fd_index].fd, (uint8_t*) &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)); } @@ -293,7 +295,7 @@ static int sacn_set(instance* inst, size_t num, channel** c, channel_value* v){ } if(!data->xmit_prio){ - fprintf(stderr, "sACN instance %s not enabled for output (%zu channel events)\n", inst->name, num); + fprintf(stderr, "sACN instance %s not enabled for output (%lu channel events)\n", inst->name, num); return 0; } @@ -378,7 +380,7 @@ static int sacn_process_frame(instance* inst, sacn_frame_root* frame, sacn_frame } if(!chan){ - fprintf(stderr, "Active channel %zu on %s not known to core", u, inst->name); + fprintf(stderr, "Active channel %lu on %s not known to core", u, inst->name); return 1; } @@ -445,8 +447,8 @@ static void sacn_discovery(size_t fd){ 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)); + if(sendto(global_cfg.fd[fd].fd, (uint8_t*) &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 %lu: %s\n", fd, strerror(errno)); } } } @@ -512,7 +514,11 @@ static int sacn_handle(size_t num, managed_fd* fds){ } } while(bytes_read > 0); + #ifdef _WIN32 + if(bytes_read < 0 && WSAGetLastError() != WSAEWOULDBLOCK){ + #else if(bytes_read < 0 && errno != EAGAIN){ + #endif fprintf(stderr, "sACN failed to receive data: %s\n", strerror(errno)); } @@ -577,7 +583,7 @@ static int sacn_start(){ 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))){ + if(setsockopt(global_cfg.fd[data->fd_index].fd, IPPROTO_IP, IP_ADD_MEMBERSHIP, (uint8_t*) &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)); } } @@ -604,7 +610,7 @@ static int sacn_start(){ } } - fprintf(stderr, "sACN backend registering %zu descriptors to core\n", global_cfg.fds); + fprintf(stderr, "sACN backend registering %lu 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)); diff --git a/backends/sacn.h b/backends/sacn.h index e7106f7..7af2a36 100644 --- a/backends/sacn.h +++ b/backends/sacn.h @@ -1,4 +1,3 @@ -#include #include "midimonster.h" int init(); diff --git a/config.c b/config.c index 6d5fd16..93fb56d 100644 --- a/config.c +++ b/config.c @@ -20,6 +20,62 @@ typedef enum { static backend* current_backend = NULL; static instance* current_instance = NULL; +#ifdef _WIN32 +#define GETLINE_BUFFER 4096 + +static ssize_t getline(char** line, size_t* alloc, FILE* stream){ + size_t bytes_read = 0; + char c; + //sanity checks + if(!line || !alloc || !stream){ + return -1; + } + + //allocate buffer if none provided + if(!*line || !*alloc){ + *alloc = GETLINE_BUFFER; + *line = calloc(GETLINE_BUFFER, sizeof(char)); + if(!*line){ + fprintf(stderr, "Failed to allocate memory\n"); + return -1; + } + } + + if(feof(stream)){ + return -1; + } + + for(c = fgetc(stream); 1; c = fgetc(stream)){ + //end of buffer, resize + if(bytes_read == (*alloc) - 1){ + *alloc += GETLINE_BUFFER; + *line = realloc(*line, (*alloc) * sizeof(char)); + if(!*line){ + fprintf(stderr, "Failed to allocate memory\n"); + return -1; + } + } + + //store character + (*line)[bytes_read] = c; + + //end of line + if(feof(stream) || c == '\n'){ + //terminate string + (*line)[bytes_read + 1] = 0; + return bytes_read; + } + + //input broken + if(ferror(stream) || c < 0){ + return -1; + } + + bytes_read++; + } +} +#endif + static char* config_trim_line(char* in){ ssize_t n; //trim front diff --git a/midimonster.c b/midimonster.c index fb664a4..df27ca3 100644 --- a/midimonster.c +++ b/midimonster.c @@ -1,9 +1,14 @@ #include #include -#include #include #include #include +#ifndef _WIN32 +#include +#define MM_API __attribute__((visibility("default"))) +#else +#define MM_API __attribute__((dllexport)) +#endif #include "midimonster.h" #include "config.h" #include "backend.h" @@ -35,11 +40,14 @@ static void signal_handler(int signum){ shutdown_requested = 1; } -uint64_t mm_timestamp(){ +uint64_t MM_API mm_timestamp(){ return global_timestamp; } static void update_timestamp(){ + #ifdef _WIN32 + global_timestamp = GetTickCount(); + #else struct timespec current; if(clock_gettime(CLOCK_MONOTONIC_COARSE, ¤t)){ fprintf(stderr, "Failed to update global timestamp, time-based processing for some backends may be impaired: %s\n", strerror(errno)); @@ -47,6 +55,7 @@ static void update_timestamp(){ } global_timestamp = current.tv_sec * 1000 + current.tv_nsec / 1000000; + #endif } int mm_map_channel(channel* from, channel* to){ @@ -99,7 +108,7 @@ void map_free(){ map = NULL; } -int mm_manage_fd(int new_fd, char* back, int manage, void* impl){ +int MM_API mm_manage_fd(int new_fd, char* back, int manage, void* impl){ backend* b = backend_match(back); size_t u; @@ -163,7 +172,7 @@ void fds_free(){ fd = NULL; } -int mm_channel_event(channel* c, channel_value v){ +int MM_API mm_channel_event(channel* c, channel_value v){ size_t u, p; //find mapped channels @@ -229,7 +238,7 @@ static fd_set fds_collect(int* max_fd){ *max_fd = -1; } - DBGPF("Building selector set from %zu FDs registered to core\n", fds); + DBGPF("Building selector set from %lu FDs registered to core\n", fds); FD_ZERO(&rv_fds); for(u = 0; u < fds; u++){ if(fd[u].fd >= 0){ @@ -243,6 +252,17 @@ static fd_set fds_collect(int* max_fd){ return rv_fds; } +int platform_initialize(){ +#ifdef _WIN32 + WSADATA wsa; + WORD version = MAKEWORD(2, 2); + if(WSAStartup(version, &wsa)){ + return 1; + } +#endif + return 0; +} + int main(int argc, char** argv){ fd_set all_fds, read_fds; event_collection* secondary = NULL; @@ -255,6 +275,11 @@ int main(int argc, char** argv){ cfg_file = argv[1]; } + if(platform_initialize()){ + fprintf(stderr, "Failed to perform platform-specific initialization\n"); + return EXIT_FAILURE; + } + FD_ZERO(&all_fds); //initialize backends if(plugins_load(PLUGINS)){ @@ -316,14 +341,14 @@ int main(int argc, char** argv){ update_timestamp(); //run backend processing, collect events - DBGPF("%zu backend FDs signaled\n", n); + DBGPF("%lu backend FDs signaled\n", n); if(backends_handle(n, signaled_fds)){ goto bail; } while(primary->n){ //swap primary and secondary event collectors - DBGPF("Swapping event collectors, %zu events in primary\n", primary->n); + DBGPF("Swapping event collectors, %lu events in primary\n", primary->n); for(u = 0; u < sizeof(event_pool) / sizeof(event_collection); u++){ if(primary != event_pool + u){ secondary = primary; diff --git a/midimonster.h b/midimonster.h index 7f70f5b..eb118c6 100644 --- a/midimonster.h +++ b/midimonster.h @@ -4,6 +4,21 @@ #include #include +#ifndef MM_API + #ifdef _WIN32 + #define MM_API __attribute__((dllimport)) + #else + #define MM_API + #endif +#endif + +/* GCC ignores the visibility attributes on some API functions, so override visibility */ +#if !defined(_WIN32) && defined(__GNUC__) && !defined(__clang__) + #undef MM_API + #define MM_API + #pragma GCC visibility push(default) +#endif + /* Straight-forward min / max macros */ #define max(a,b) (((a) > (b)) ? (a) : (b)) #define min(a,b) (((a) < (b)) ? (a) : (b)) @@ -187,7 +202,7 @@ typedef struct /*_mm_channel_mapping*/ { /* * Register a new backend. */ -int mm_backend_register(backend b); +int MM_API mm_backend_register(backend b); /* * Provides a pointer to a newly (zero-)allocated instance. @@ -201,7 +216,8 @@ int mm_backend_register(backend b); * mmbackend_shutdown procedure of the backend, eg. by querying * all instances for the backend. */ -instance* mm_instance(); +instance* MM_API mm_instance(); + /* * Finds an instance matching the specified backend and identifier. * Since setting an identifier for an instance is optional, @@ -209,7 +225,8 @@ instance* mm_instance(); * Instance identifiers may for example be set in the backends * mmbackend_start call. */ -instance* mm_instance_find(char* backend, uint64_t ident); +instance* MM_API mm_instance_find(char* backend, uint64_t ident); + /* * Provides a pointer to a channel structure, pre-filled with * the provided instance reference and identifier. @@ -224,30 +241,35 @@ instance* mm_instance_find(char* backend, uint64_t ident); * this function, the backend will receive a call to its channel_free * function. */ -channel* mm_channel(instance* i, uint64_t ident, uint8_t create); +channel* MM_API mm_channel(instance* i, uint64_t ident, uint8_t create); //TODO channel* mm_channel_find() + /* * Register (manage = 1) or unregister (manage = 0) a file descriptor * to be selected on. The backend will be notified when the descriptor * becomes ready to read via its registered mmbackend_process_fd call. */ -int mm_manage_fd(int fd, char* backend, int manage, void* impl); +int MM_API mm_manage_fd(int fd, char* backend, int manage, void* impl); + /* * Notifies the core of a channel event. Called by backends to * inject events gathered from their backing implementation. */ -int mm_channel_event(channel* c, channel_value v); +int MM_API mm_channel_event(channel* c, channel_value v); + /* * Query all active instances for a given backend. * *i will need to be freed by the caller. */ -int mm_backend_instances(char* backend, size_t* n, instance*** i); +int MM_API mm_backend_instances(char* backend, size_t* n, instance*** i); + /* * Query an internal timestamp, which is updated every core iteration. * This timestamp should not be used as a performance counter, but can be * used for timeouting. Resolution is milliseconds. */ -uint64_t mm_timestamp(); +uint64_t MM_API mm_timestamp(); + /* * Create a channel-to-channel mapping. This API should not * be used by backends. It is only exported for core modules. diff --git a/monster.cfg b/monster.cfg index 2e6f76f..2413f6d 100644 --- a/monster.cfg +++ b/monster.cfg @@ -1,18 +1,12 @@ [backend artnet] bind = 0.0.0.0 -[backend evdev] +[loopback loop] [artnet art] universe = 0 dest = 255.255.255.255 -[evdev mouse] -input = TPPS - -[loopback loop] - [map] -mouse.EV_REL.REL_X > loop.chan0 -art.{3..4}{4..3} > loop.chan{4..3}{3..4} -art.{1..10} > loop.data{1..10} + +art.1+2 > loop.b diff --git a/plugin.c b/plugin.c index fc642ac..a452559 100644 --- a/plugin.c +++ b/plugin.c @@ -1,11 +1,20 @@ #include #include -#include #include #include #include #include #include +#include "portability.h" +#ifdef _WIN32 +#define dlclose FreeLibrary +#define dlsym GetProcAddress +#define dlerror() "Failed" +#define dlopen(lib,ig) LoadLibrary(lib) +#else +#include +#endif + #include "plugin.h" static size_t plugins = 0; @@ -14,19 +23,29 @@ static void** plugin_handle = NULL; static int plugin_attach(char* path, char* file){ plugin_init init = NULL; void* handle = NULL; + char* lib = NULL; + char* error = NULL; - char* lib = calloc(strlen(path) + strlen(file) + 1, sizeof(char)); + lib = calloc(strlen(path) + strlen(file) + 1, sizeof(char)); if(!lib){ fprintf(stderr, "Failed to allocate memory\n"); return 1; } - snprintf(lib, strlen(path) + strlen(file) + 1, "%s%s", path, file); handle = dlopen(lib, RTLD_NOW); if(!handle){ - fprintf(stderr, "Failed to load plugin %s: %s\n", lib, dlerror()); + #ifdef _WIN32 + FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, + NULL, GetLastError(), MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPTSTR) &error, 0, NULL); + #else + error = dlerror(); + #endif + fprintf(stderr, "Failed to load plugin %s: %s\n", lib, error); free(lib); + #ifdef _WIN32 + LocalFree(error); + #endif return 0; } @@ -62,6 +81,38 @@ static int plugin_attach(char* path, char* file){ int plugins_load(char* path){ int rv = -1; +#ifdef _WIN32 + char* search_expression = calloc(strlen(path) + strlen("*.dll") + 1, sizeof(char)); + if(!search_expression){ + fprintf(stderr, "Failed to allocate memory\n"); + return -1; + } + snprintf(search_expression, strlen(path) + strlen("*.dll"), "%s*.dll", path); + + WIN32_FIND_DATA result; + HANDLE hSearch = FindFirstFile(search_expression, &result); + + if(hSearch == INVALID_HANDLE_VALUE){ + LPVOID lpMsgBuf = NULL; + FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, + NULL, GetLastError(), MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPTSTR) &lpMsgBuf, 0, NULL); + fprintf(stderr, "Failed to search for backend plugin files in %s: %s\n", path, lpMsgBuf); + LocalFree(lpMsgBuf); + return -1; + } + + do { + if(plugin_attach(path, result.cFileName)){ + goto load_done; + } + } while(FindNextFile(hSearch, &result)); + + rv = 0; +load_done: + free(search_expression); + FindClose(hSearch); + return rv; +#else struct dirent* entry; struct stat file_stat; DIR* directory = opendir(path); @@ -100,6 +151,7 @@ load_done: return -1; } return rv; +#endif } int plugins_close(){ diff --git a/portability.h b/portability.h index 25aee01..903ecd8 100644 --- a/portability.h +++ b/portability.h @@ -19,3 +19,20 @@ #define be64toh(x) OSSwapBigToHostInt64(x) #define le64toh(x) OSSwapLittleToHostInt64(x) #endif + +#ifdef _WIN32 + #define WIN32_LEAN_AND_MEAN + #include + #include + + #define htobe16(x) htons(x) + #define be16toh(x) ntohs(x) + + #define htobe32(x) htonl(x) + #define be32toh(x) ntohl(x) + + #define htobe64(x) _byteswap_uint64(x) + #define htole64(x) (x) + #define be64toh(x) _byteswap_uint64(x) + #define le64toh(x) (x) +#endif -- cgit v1.2.3 From 48bf96602023b2ead855f13477b6f5e26b663b45 Mon Sep 17 00:00:00 2001 From: cbdev Date: Sat, 10 Aug 2019 20:30:07 +0200 Subject: Clean up & check unions --- backends/artnet.c | 5 +++++ backends/evdev.c | 14 +++++--------- backends/evdev.h | 9 +++++++++ backends/midi.c | 14 +++++--------- backends/midi.h | 10 ++++++++++ backends/osc.c | 13 +++++-------- backends/osc.h | 8 ++++++++ backends/sacn.c | 5 +++++ 8 files changed, 52 insertions(+), 26 deletions(-) (limited to 'backends/osc.h') diff --git a/backends/artnet.c b/backends/artnet.c index 7f3f08c..8e47d4f 100644 --- a/backends/artnet.c +++ b/backends/artnet.c @@ -54,6 +54,11 @@ int init(){ .shutdown = artnet_shutdown }; + if(sizeof(artnet_instance_id) != sizeof(uint64_t)){ + fprintf(stderr, "ArtNet instance identification union out of bounds\n"); + return 1; + } + //register backend if(mm_backend_register(artnet)){ fprintf(stderr, "Failed to register ArtNet backend\n"); diff --git a/backends/evdev.c b/backends/evdev.c index 7a7913d..bd2098d 100644 --- a/backends/evdev.c +++ b/backends/evdev.c @@ -18,15 +18,6 @@ #define BACKEND_NAME "evdev" -typedef union { - struct { - uint32_t pad; - uint16_t type; - uint16_t code; - } fields; - uint64_t label; -} evdev_channel_ident; - static struct { uint8_t detect; } evdev_config = { @@ -46,6 +37,11 @@ int init(){ .shutdown = evdev_shutdown }; + if(sizeof(evdev_channel_ident) != sizeof(uint64_t)){ + fprintf(stderr, "evdev channel identification union out of bounds\n"); + return 1; + } + if(mm_backend_register(evdev)){ fprintf(stderr, "Failed to register evdev backend\n"); return 1; diff --git a/backends/evdev.h b/backends/evdev.h index f89e362..b26664b 100644 --- a/backends/evdev.h +++ b/backends/evdev.h @@ -44,3 +44,12 @@ typedef struct /*_evdev_instance_model*/ { struct libevdev_uinput* output_ev; #endif } evdev_instance_data; + +typedef union { + struct { + uint32_t pad; + uint16_t type; + uint16_t code; + } fields; + uint64_t label; +} evdev_channel_ident; \ No newline at end of file diff --git a/backends/midi.c b/backends/midi.c index 9c6ba80..5b8e561 100644 --- a/backends/midi.c +++ b/backends/midi.c @@ -4,15 +4,6 @@ #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; enum /*_midi_channel_type*/ { none = 0, @@ -44,6 +35,11 @@ int init(){ .shutdown = midi_shutdown }; + if(sizeof(midi_channel_ident) != sizeof(uint64_t)){ + fprintf(stderr, "MIDI channel identification union out of bounds\n"); + return 1; + } + if(snd_seq_open(&sequencer, "default", SND_SEQ_OPEN_DUPLEX, 0) < 0){ fprintf(stderr, "Failed to open ALSA sequencer\n"); return 1; diff --git a/backends/midi.h b/backends/midi.h index 556706f..5ec17ea 100644 --- a/backends/midi.h +++ b/backends/midi.h @@ -15,3 +15,13 @@ typedef struct /*_midi_instance_data*/ { char* read; char* write; } midi_instance_data; + +typedef union { + struct { + uint8_t pad[5]; + uint8_t type; + uint8_t channel; + uint8_t control; + } fields; + uint64_t label; +} midi_channel_ident; \ No newline at end of file diff --git a/backends/osc.c b/backends/osc.c index 77bbde4..beb5527 100644 --- a/backends/osc.c +++ b/backends/osc.c @@ -13,14 +13,6 @@ #define osc_align(a) ((((a) / 4) + (((a) % 4) ? 1 : 0)) * 4) #define BACKEND_NAME "osc" -typedef union { - struct { - uint32_t channel; - uint32_t parameter; - } fields; - uint64_t label; -} osc_channel_ident; - static struct { uint8_t detect; } osc_global_config = { @@ -40,6 +32,11 @@ int init(){ .shutdown = osc_shutdown }; + if(sizeof(osc_channel_ident) != sizeof(uint64_t)){ + fprintf(stderr, "OSC channel identification union out of bounds\n"); + return 1; + } + //register backend if(mm_backend_register(osc)){ fprintf(stderr, "Failed to register OSC backend\n"); diff --git a/backends/osc.h b/backends/osc.h index b2aaea7..ab19463 100644 --- a/backends/osc.h +++ b/backends/osc.h @@ -66,3 +66,11 @@ typedef struct /*_osc_instance_data*/ { //peer fd int fd; } osc_instance_data; + +typedef union { + struct { + uint32_t channel; + uint32_t parameter; + } fields; + uint64_t label; +} osc_channel_ident; \ No newline at end of file diff --git a/backends/sacn.c b/backends/sacn.c index 2f418e5..470e3bd 100644 --- a/backends/sacn.c +++ b/backends/sacn.c @@ -44,6 +44,11 @@ int init(){ .shutdown = sacn_shutdown }; + if(sizeof(sacn_instance_id) != sizeof(uint64_t)){ + fprintf(stderr, "sACN instance identification union out of bounds\n"); + return 1; + } + //register the backend if(mm_backend_register(sacn)){ fprintf(stderr, "Failed to register sACN backend\n"); -- cgit v1.2.3 From 8b016f61a4b3d3be0c7b1e311209ab991276af0c Mon Sep 17 00:00:00 2001 From: cbdev Date: Thu, 22 Aug 2019 21:13:48 +0200 Subject: Implement input for the maweb backend (with a few limitations) --- README.md | 2 +- backends/Makefile | 4 +- backends/evdev.h | 3 +- backends/libmmbackend.c | 241 ++++++++++++++++++++++++++++++-- backends/libmmbackend.h | 33 ++--- backends/maweb.c | 358 ++++++++++++++++++++++++++++++++++++++++-------- backends/maweb.h | 14 +- backends/maweb.md | 36 +++-- backends/midi.h | 3 +- backends/osc.h | 3 +- 10 files changed, 582 insertions(+), 115 deletions(-) (limited to 'backends/osc.h') diff --git a/README.md b/README.md index 4d8fe18..40f8fbb 100644 --- a/README.md +++ b/README.md @@ -20,7 +20,7 @@ on any other (or the same) supported protocol, for example to: * Translate MIDI Control Changes into Notes ([Example configuration](configs/unifest-17.cfg)) * Translate MIDI Notes into ArtNet or sACN ([Example configuration](configs/launchctl-sacn.cfg)) * Translate OSC messages into MIDI ([Example configuration](configs/midi-osc.cfg)) -* Dynamically route and modify events using the Lua programming language ([Example configuration](configs/lua.cfg) and [Script](configs/demo.lua)) to create your own lighting controller or run effects on TouchOSC (Flying faders demo [configuration](configs/flying-faders.cfg) and [script](configs/flying-faders.lua)) +* Dynamically generate, route and modify events using the Lua programming language ([Example configuration](configs/lua.cfg) and [Script](configs/demo.lua)) to create your own lighting controller or run effects on TouchOSC (Flying faders demo [configuration](configs/flying-faders.cfg) and [script](configs/flying-faders.lua)) * Use an OSC app as a simple lighting controller via ArtNet or sACN * Visualize ArtNet data using OSC tools * Control lighting fixtures or DAWs using gamepad controllers, trackballs, etc ([Example configuration](configs/evdev.cfg)) diff --git a/backends/Makefile b/backends/Makefile index 582655c..5c5b677 100644 --- a/backends/Makefile +++ b/backends/Makefile @@ -1,7 +1,7 @@ .PHONY: all clean full LINUX_BACKENDS = midi.so evdev.so -WINDOWS_BACKENDS = artnet.dll osc.dll loopback.dll sacn.dll -BACKENDS = artnet.so osc.so loopback.so sacn.so lua.so +WINDOWS_BACKENDS = artnet.dll osc.dll loopback.dll sacn.dll maweb.dll +BACKENDS = artnet.so osc.so loopback.so sacn.so lua.so maweb.so OPTIONAL_BACKENDS = ola.so BACKEND_LIB = libmmbackend.o diff --git a/backends/evdev.h b/backends/evdev.h index b26664b..48bd0ab 100644 --- a/backends/evdev.h +++ b/backends/evdev.h @@ -52,4 +52,5 @@ typedef union { uint16_t code; } fields; uint64_t label; -} evdev_channel_ident; \ No newline at end of file +} evdev_channel_ident; + diff --git a/backends/libmmbackend.c b/backends/libmmbackend.c index c98cfe3..ccbeb52 100644 --- a/backends/libmmbackend.c +++ b/backends/libmmbackend.c @@ -208,10 +208,11 @@ size_t json_validate(char* json, size_t length){ size_t json_validate_string(char* json, size_t length){ size_t string_length = 0, offset; - for(offset = 0; json[offset] && offset < length && json[offset] != '"'; offset++){ + //skip leading whitespace + for(offset = 0; json[offset] && offset < length && isspace(json[offset]); offset++){ } - if(offset == length){ + if(offset == length || json[offset] != '"'){ return 0; } @@ -230,17 +231,122 @@ size_t json_validate_string(char* json, size_t length){ } size_t json_validate_array(char* json, size_t length){ - //TODO + size_t offset = 0; + + //skip leading whitespace + for(offset = 0; json[offset] && offset < length && isspace(json[offset]); offset++){ + } + + if(offset == length || json[offset] != '['){ + return 0; + } + + for(offset++; offset < length; offset++){ + offset += json_validate(json + offset, length - offset); + + //skip trailing whitespace, find terminator + for(; offset < length && isspace(json[offset]); offset++){ + } + + if(json[offset] == ','){ + continue; + } + + if(json[offset] == ']'){ + return offset + 1; + } + + break; + } + return 0; } size_t json_validate_object(char* json, size_t length){ - //TODO + size_t offset = 0; + + //skip whitespace + for(offset = 0; json[offset] && isspace(json[offset]); offset++){ + } + + if(offset == length || json[offset] != '{'){ + return 0; + } + + for(offset++; offset < length; offset++){ + if(json_identify(json + offset, length - offset) != JSON_STRING){ + //still could be an empty object... + for(; offset < length && isspace(json[offset]); offset++){ + } + if(json[offset] == '}'){ + return offset + 1; + } + return 0; + } + offset += json_validate(json + offset, length - offset); + + //find value separator + for(; offset < length && isspace(json[offset]); offset++){ + } + + if(json[offset] != ':'){ + return 0; + } + + offset++; + offset += json_validate(json + offset, length - offset); + + //skip trailing whitespace + for(; json[offset] && isspace(json[offset]); offset++){ + } + + if(json[offset] == '}'){ + return offset + 1; + } + else if(json[offset] != ','){ + return 0; + } + } return 0; } size_t json_validate_value(char* json, size_t length){ - //TODO + size_t offset = 0, value_length; + + //skip leading whitespace + for(offset = 0; json[offset] && offset < length && isspace(json[offset]); offset++){ + } + + if(offset == length){ + return 0; + } + + //match complete values + if(length - offset >= 4 && !strncmp(json + offset, "null", 4)){ + return offset + 4; + } + else if(length - offset >= 4 && !strncmp(json + offset, "true", 4)){ + return offset + 4; + } + else if(length - offset >= 5 && !strncmp(json + offset, "false", 5)){ + return offset + 5; + } + + if(json[offset] == '-' || isdigit(json[offset])){ + //json number parsing is dumb. + for(value_length = 1; offset + value_length < length && + (isdigit(json[offset + value_length]) + || json[offset + value_length] == '+' + || json[offset + value_length] == '-' + || json[offset + value_length] == '.' + || tolower(json[offset + value_length]) == 'e'); value_length++){ + } + + if(value_length > 0){ + return offset + value_length; + } + } + return 0; } @@ -284,13 +390,51 @@ size_t json_obj_offset(char* json, char* key){ //add length of value offset += json_validate(json + offset, strlen(json + offset)); - //find comma or closing brace - for(; json[offset] && json[offset] != ',' && json[offset] != '}'; offset++){ + //skip trailing whitespace + for(; json[offset] && isspace(json[offset]); offset++){ } if(json[offset] == ','){ offset++; + continue; + } + + break; + } + + return 0; +} + +size_t json_array_offset(char* json, uint64_t key){ + size_t offset = 0, index = 0; + + //skip leading whitespace + for(offset = 0; json[offset] && isspace(json[offset]); offset++){ + } + + if(json[offset] != '['){ + return 0; + } + + for(offset++; index <= key; offset++){ + //skip whitespace + for(; json[offset] && isspace(json[offset]); offset++){ + } + + if(index == key){ + return offset; + } + + offset += json_validate(json + offset, strlen(json + offset)); + + //skip trailing whitespace, find terminator + for(; json[offset] && isspace(json[offset]); offset++){ } + + if(json[offset] != ','){ + break; + } + index++; } return 0; @@ -304,6 +448,14 @@ json_type json_obj(char* json, char* key){ return JSON_INVALID; } +json_type json_array(char* json, uint64_t key){ + size_t offset = json_array_offset(json, key); + if(offset){ + return json_identify(json + offset, strlen(json + offset)); + } + return JSON_INVALID; +} + uint8_t json_obj_bool(char* json, char* key, uint8_t fallback){ size_t offset = json_obj_offset(json, key); if(offset){ @@ -317,6 +469,19 @@ uint8_t json_obj_bool(char* json, char* key, uint8_t fallback){ return fallback; } +uint8_t json_array_bool(char* json, uint64_t key, uint8_t fallback){ + size_t offset = json_array_offset(json, key); + if(offset){ + if(!strncmp(json + offset, "true", 4)){ + return 1; + } + if(!strncmp(json + offset, "false", 5)){ + return 0; + } + } + return fallback; +} + int64_t json_obj_int(char* json, char* key, int64_t fallback){ char* next_token = NULL; int64_t result; @@ -332,7 +497,7 @@ int64_t json_obj_int(char* json, char* key, int64_t fallback){ double json_obj_double(char* json, char* key, double fallback){ char* next_token = NULL; - int64_t result; + double result; size_t offset = json_obj_offset(json, key); if(offset){ result = strtod(json + offset, &next_token); @@ -343,6 +508,32 @@ double json_obj_double(char* json, char* key, double fallback){ return fallback; } +int64_t json_array_int(char* json, uint64_t key, int64_t fallback){ + char* next_token = NULL; + int64_t result; + size_t offset = json_array_offset(json, key); + if(offset){ + result = strtol(json + offset, &next_token, 10); + if(next_token != json + offset){ + return result; + } + } + return fallback; +} + +double json_array_double(char* json, uint64_t key, double fallback){ + char* next_token = NULL; + double result; + size_t offset = json_array_offset(json, key); + if(offset){ + result = strtod(json + offset, &next_token); + if(next_token != json + offset){ + return result; + } + } + return fallback; +} + char* json_obj_str(char* json, char* key, size_t* length){ size_t offset = json_obj_offset(json, key), raw_length; if(offset){ @@ -356,15 +547,37 @@ char* json_obj_str(char* json, char* key, size_t* length){ } char* json_obj_strdup(char* json, char* key){ - size_t offset = json_obj_offset(json, key), raw_length; - char* rv = NULL; + size_t len = 0; + char* value = json_obj_str(json, key, &len), *rv = NULL; + if(len){ + rv = calloc(len + 1, sizeof(char)); + if(rv){ + memcpy(rv, value, len); + } + } + return rv; +} + +char* json_array_str(char* json, uint64_t key, size_t* length){ + size_t offset = json_array_offset(json, key), raw_length; if(offset){ raw_length = json_validate_string(json + offset, strlen(json + offset)); - rv = calloc(raw_length - 1, sizeof(char)); - if(rv){ - memcpy(rv, json + offset + 1, raw_length - 2); + if(length){ + *length = raw_length - 2; } - return rv; + return json + offset + 1; } return NULL; } + +char* json_array_strdup(char* json, uint64_t key){ + size_t len = 0; + char* value = json_array_str(json, key, &len), *rv = NULL; + if(len){ + rv = calloc(len + 1, sizeof(char)); + if(rv){ + memcpy(rv, value, len); + } + } + return rv; +} diff --git a/backends/libmmbackend.h b/backends/libmmbackend.h index aa0ac0c..5749119 100644 --- a/backends/libmmbackend.h +++ b/backends/libmmbackend.h @@ -78,49 +78,50 @@ json_type json_identify(char* json, size_t length); * Returns the length of a detected JSON document, 0 otherwise (ie. parse failures) */ size_t json_validate(char* json, size_t length); - size_t json_validate_string(char* json, size_t length); - size_t json_validate_array(char* json, size_t length); - size_t json_validate_object(char* json, size_t length); - size_t json_validate_value(char* json, size_t length); /* * Calculate offset for value of `key` - * Assumes a zero-terminated, validated JSON object as input + * Assumes a zero-terminated, validated JSON object / array as input * Returns offset on success, 0 on failure */ size_t json_obj_offset(char* json, char* key); +size_t json_array_offset(char* json, uint64_t key); /* - * Check for for a key within a JSON object - * Assumes a zero-terminated, validated JSON object as input + * Check for for a key within a JSON object / index within an array + * Assumes a zero-terminated, validated JSON object / array as input * Returns type of value */ json_type json_obj(char* json, char* key); - -//json_type json_array(char* json, size_t index) +json_type json_array(char* json, uint64_t key); /* - * Fetch boolean value for an object key - * Assumes a zero-terminated, validated JSON object as input + * Fetch boolean value for an object / array key + * Assumes a zero-terminated, validated JSON object / array as input */ uint8_t json_obj_bool(char* json, char* key, uint8_t fallback); +uint8_t json_array_bool(char* json, uint64_t key, uint8_t fallback); /* - * Fetch integer/double value for an object key - * Assumes a zero-terminated validated JSON object as input + * Fetch integer/double value for an object / array key + * Assumes a zero-terminated validated JSON object / array as input */ int64_t json_obj_int(char* json, char* key, int64_t fallback); double json_obj_double(char* json, char* key, double fallback); +int64_t json_array_int(char* json, uint64_t key, int64_t fallback); +double json_array_double(char* json, uint64_t key, double fallback); /* - * Fetch a string value for an object key - * Assumes a zero-terminated validated JSON object as input - * json_obj_strdup returns a newly-allocated buffer containing + * Fetch a string value for an object / array key + * Assumes a zero-terminated validated JSON object / array as input + * json_*_strdup returns a newly-allocated buffer containing * only the requested value */ char* json_obj_str(char* json, char* key, size_t* length); char* json_obj_strdup(char* json, char* key); +char* json_array_str(char* json, uint64_t key, size_t* length); +char* json_array_strdup(char* json, uint64_t key); diff --git a/backends/maweb.c b/backends/maweb.c index 79e223f..07595be 100644 --- a/backends/maweb.c +++ b/backends/maweb.c @@ -14,7 +14,13 @@ #define WS_FLAG_FIN 0x80 #define WS_FLAG_MASK 0x80 +//TODO test using different pages simultaneously +//TODO test dot2 button virtual faders in fader view + static uint64_t last_keepalive = 0; +static uint64_t update_interval = 50; +static uint64_t last_update = 0; +static uint64_t updates_inflight = 0; static char* cmdline_keys[] = { "SET", @@ -94,7 +100,8 @@ int init(){ .handle = maweb_set, .process = maweb_handle, .start = maweb_start, - .shutdown = maweb_shutdown + .shutdown = maweb_shutdown, + .interval = maweb_interval }; if(sizeof(maweb_channel_ident) != sizeof(uint64_t)){ @@ -110,8 +117,27 @@ int init(){ return 0; } +static int channel_comparator(const void* raw_a, const void* raw_b){ + maweb_channel_ident* a = (maweb_channel_ident*) raw_a; + maweb_channel_ident* b = (maweb_channel_ident*) raw_b; + + if(a->fields.page != b->fields.page){ + return a->fields.page - b->fields.page; + } + return a->fields.index - b->fields.index; +} + +static uint32_t maweb_interval(){ + return update_interval - (last_update % update_interval); +} + static int maweb_configure(char* option, char* value){ - fprintf(stderr, "The maweb backend does not take any global configuration\n"); + if(!strcmp(option, "interval")){ + update_interval = strtoul(value, NULL, 10); + return 0; + } + + fprintf(stderr, "Unknown maweb backend configuration option %s\n", option); return 1; } @@ -187,6 +213,7 @@ static instance* maweb_instance(){ } static channel* maweb_channel(instance* inst, char* spec){ + maweb_instance_data* data = (maweb_instance_data*) inst->impl; maweb_channel_ident ident = { .label = 0 }; @@ -214,7 +241,7 @@ static channel* maweb_channel(instance* inst, char* spec){ next_token += 5; } else if(!strncmp(next_token, "flash", 5)){ - ident.fields.type = exec_flash; + ident.fields.type = exec_button; next_token += 5; } else if(!strncmp(next_token, "button", 6)){ @@ -238,6 +265,24 @@ static channel* maweb_channel(instance* inst, char* spec){ //actually, those are zero-indexed... ident.fields.index--; ident.fields.page--; + + //check if the channel is already known + for(n = 0; n < data->input_channels; n++){ + if(data->input_channel[n].label == ident.label){ + break; + } + } + + if(n == data->input_channels){ + data->input_channel = realloc(data->input_channel, (data->input_channels + 1) * sizeof(maweb_channel_ident)); + if(!data->input_channel){ + fprintf(stderr, "Failed to allocate memory\n"); + return NULL; + } + data->input_channel[n].label = ident.label; + data->input_channels++; + } + return mm_channel(inst, ident.label, 1); } fprintf(stderr, "Failed to parse maweb channel spec %s\n", spec); @@ -276,11 +321,216 @@ static int maweb_send_frame(instance* inst, maweb_operation op, uint8_t* payload return 0; } +static int maweb_process_playback(instance* inst, int64_t page, maweb_channel_type metatype, char* payload, size_t payload_length){ + size_t exec_blocks = json_obj_offset(payload, (metatype == 2) ? "executorBlocks" : "bottomButtons"), offset, block = 0, control; + channel* chan = NULL; + channel_value evt; + maweb_channel_ident ident = { + .fields.page = page, + .fields.index = json_obj_int(payload, "iExec", 191) + }; + + if(!exec_blocks){ + if(metatype == 3){ + //ignore unused buttons + return 0; + } + fprintf(stderr, "maweb missing exec block data on exec %d\n", ident.fields.index); + return 1; + } + + if(metatype == 3){ + exec_blocks += json_obj_offset(payload + exec_blocks, "items"); + } + + //TODO detect unused faders + //TODO state tracking for fader values / exec run state + + //iterate over executor blocks + for(offset = json_array_offset(payload + exec_blocks, block); offset; offset = json_array_offset(payload + exec_blocks, block)){ + control = exec_blocks + offset + json_obj_offset(payload + exec_blocks + offset, "fader"); + ident.fields.type = exec_fader; + chan = mm_channel(inst, ident.label, 0); + if(chan){ + evt.normalised = json_obj_double(payload + control, "v", 0.0); + mm_channel_event(chan, evt); + } + + ident.fields.type = exec_button; + chan = mm_channel(inst, ident.label, 0); + if(chan){ + evt.normalised = json_obj_int(payload, "isRun", 0); + mm_channel_event(chan, evt); + } + + //printf("maweb page %ld exec %d value %f running %lu\n", page, ident.fields.index, json_obj_double(payload + control, "v", 0.0), json_obj_int(payload, "isRun", 0)); + ident.fields.index++; + block++; + } + + return 0; +} + +static int maweb_process_playbacks(instance* inst, int64_t page, char* payload, size_t payload_length){ + size_t base_offset = json_obj_offset(payload, "itemGroups"), group_offset, subgroup_offset, item_offset; + uint64_t group = 0, subgroup, item, metatype; + + if(!page){ + fprintf(stderr, "maweb received playbacks for invalid page\n"); + return 0; + } + + if(!base_offset){ + fprintf(stderr, "maweb playback data missing item key\n"); + return 0; + } + + //iterate .itemGroups + for(group_offset = json_array_offset(payload + base_offset, group); + group_offset; + group_offset = json_array_offset(payload + base_offset, group)){ + metatype = json_obj_int(payload + base_offset + group_offset, "itemsType", 0); + //iterate .itemGroups.items + //FIXME this is problematic if there is no "items" key + group_offset = group_offset + json_obj_offset(payload + base_offset + group_offset, "items"); + if(group_offset){ + subgroup = 0; + group_offset += base_offset; + for(subgroup_offset = json_array_offset(payload + group_offset, subgroup); + subgroup_offset; + subgroup_offset = json_array_offset(payload + group_offset, subgroup)){ + //iterate .itemGroups.items[n] + item = 0; + subgroup_offset += group_offset; + for(item_offset = json_array_offset(payload + subgroup_offset, item); + item_offset; + item_offset = json_array_offset(payload + subgroup_offset, item)){ + maweb_process_playback(inst, page, metatype, + payload + subgroup_offset + item_offset, + payload_length - subgroup_offset - item_offset); + item++; + } + subgroup++; + } + } + group++; + } + updates_inflight--; + fprintf(stderr, "maweb playback message processing done, %lu updates inflight\n", updates_inflight); + return 0; +} + +static int maweb_request_playbacks(instance* inst){ + maweb_instance_data* data = (maweb_instance_data*) inst->impl; + char xmit_buffer[MAWEB_XMIT_CHUNK]; + int rv = 0; + + char item_indices[1024] = "[0,100,200]", item_counts[1024] = "[21,21,21]", item_types[1024] = "[2,3,3]"; + //char item_indices[1024] = "[300,400]", item_counts[1024] = "[18,18]", item_types[1024] = "[3,3]"; + size_t page_index = 0, view = 2, channel = 0, offsets[3], channel_offset, channels; + + if(updates_inflight){ + fprintf(stderr, "maweb skipping update request, %lu updates still inflight\n", updates_inflight); + return 0; + } + + for(channel = 0; channel < data->input_channels; channel++){ + offsets[0] = offsets[1] = offsets[2] = 0; + page_index = data->input_channel[channel].fields.page; + if(data->peer_type == peer_dot2){ + //TODO implement poll segmentation for dot + //"\"startIndex\":[0,100,200]," + //"\"itemsCount\":[21,21,21]," + //"\"itemsType\":[2,3,3]," + //"\"view\":2," + //view = (data->input_channel[channel].fields.index >= 300) ? 3 : 2; + //observed + //"startIndex":[300,400,500,600,700,800], + //"itemsCount":[13,13,13,13,13,13] + //"itemsType":[3,3,3,3,3,3] + /*fprintf(stderr, "range start at %lu.%lu (%lu/%lu) end at %lu.%lu (%lu/%lu)\n", + page_index, + data->input_channel[channel].fields.index, + channel, + data->input_channels, + page_index, + data->input_channel[channel + channel_offset - 1].fields.index, + channel + channel_offset - 1, + data->input_channels + );*/ + //only send one request currently + channel = data->input_channels; + } + else{ + view = (data->input_channel[channel].fields.index >= 100) ? 3 : 2; + //for the ma, the view equals the exec type + snprintf(item_types, sizeof(item_types), "[%lu]", view); + //this channel must be included, so it must be in range for the first startindex + snprintf(item_indices, sizeof(item_indices), "[%d]", (data->input_channel[channel].fields.index / 5) * 5); + + for(channel_offset = 1; channel + channel_offset < data->input_channels + && data->input_channel[channel].fields.page == data->input_channel[channel + channel_offset].fields.page + && data->input_channel[channel].fields.index / 100 == data->input_channel[channel + channel_offset].fields.index / 100; channel_offset++){ + } + + channels = data->input_channel[channel + channel_offset - 1].fields.index - (data->input_channel[channel].fields.index / 5) * 5; + + + snprintf(item_counts, sizeof(item_indices), "[%lu]", ((channels / 5) * 5 + 5)); + channel += channel_offset - 1; + } + snprintf(xmit_buffer, sizeof(xmit_buffer), + "{" + "\"requestType\":\"playbacks\"," + "\"startIndex\":%s," + "\"itemsCount\":%s," + "\"pageIndex\":%lu," + "\"itemsType\":%s," + "\"view\":%lu," + "\"execButtonViewMode\":2," //extended + "\"buttonsViewMode\":0," //get vfader for button execs + "\"session\":%lu" + "}", + item_indices, + item_counts, + page_index, + item_types, + view, + data->session); + rv |= maweb_send_frame(inst, ws_text, (uint8_t*) xmit_buffer, strlen(xmit_buffer)); + //fprintf(stderr, "req: %s\n", xmit_buffer); + updates_inflight++; + } + + return rv; +} + static int maweb_handle_message(instance* inst, char* payload, size_t payload_length){ char xmit_buffer[MAWEB_XMIT_CHUNK]; char* field; maweb_instance_data* data = (maweb_instance_data*) inst->impl; + //query this early to save on unnecessary parser passes with stupid-huge data messages + if(json_obj(payload, "responseType") == JSON_STRING){ + field = json_obj_str(payload, "responseType", NULL); + if(!strncmp(field, "login", 5)){ + if(json_obj_bool(payload, "result", 0)){ + fprintf(stderr, "maweb login successful\n"); + data->login = 1; + } + else{ + fprintf(stderr, "maweb login failed\n"); + data->login = 0; + } + } + if(!strncmp(field, "playbacks", 9)){ + if(maweb_process_playbacks(inst, json_obj_int(payload, "iPage", 0), payload, payload_length)){ + fprintf(stderr, "maweb failed to handle/request input data\n"); + } + return 0; + } + } + fprintf(stderr, "maweb message (%lu): %s\n", payload_length, payload); if(json_obj(payload, "session") == JSON_NUMBER){ data->session = json_obj_int(payload, "session", data->session); @@ -294,7 +544,6 @@ static int maweb_handle_message(instance* inst, char* payload, size_t payload_le (data->peer_type == peer_dot2) ? "remote" : data->user, data->pass, data->session); maweb_send_frame(inst, ws_text, (uint8_t*) xmit_buffer, strlen(xmit_buffer)); } - if(json_obj(payload, "status") && json_obj(payload, "appType")){ fprintf(stderr, "maweb connection established\n"); field = json_obj_str(payload, "appType", NULL); @@ -307,31 +556,6 @@ static int maweb_handle_message(instance* inst, char* payload, size_t payload_le maweb_send_frame(inst, ws_text, (uint8_t*) "{\"session\":0}", 13); } - if(json_obj(payload, "responseType") == JSON_STRING){ - field = json_obj_str(payload, "responseType", NULL); - if(!strncmp(field, "login", 5)){ - if(json_obj_bool(payload, "result", 0)){ - fprintf(stderr, "maweb login successful\n"); - data->login = 1; - } - else{ - fprintf(stderr, "maweb login failed\n"); - data->login = 0; - } - } - else if(!strncmp(field, "getdata", 7)){ - //FIXME stupid keepalive logic - snprintf(xmit_buffer, sizeof(xmit_buffer), - "{\"requestType\":\"getdata\"," - "\"data\":\"set,clear,solo,high\"," - "\"realtime\":true," - "\"maxRequests\":10," - ",\"session\":%ld}", - data->session); - maweb_send_frame(inst, ws_text, (uint8_t*) xmit_buffer, strlen(xmit_buffer)); - } - } - return 0; } @@ -376,7 +600,7 @@ static ssize_t maweb_handle_lines(instance* inst, ssize_t bytes_read){ maweb_instance_data* data = (maweb_instance_data*) inst->impl; size_t n, begin = 0; - for(n = 0; n < bytes_read - 2; n++){ + for(n = 0; n < bytes_read - 1; n++){ if(!strncmp((char*) data->buffer + data->offset + n, "\r\n", 2)){ if(data->state == ws_new){ if(!strncmp((char*) data->buffer, "HTTP/1.1 101", 12)){ @@ -397,7 +621,7 @@ static ssize_t maweb_handle_lines(instance* inst, ssize_t bytes_read){ } } - return begin; + return data->offset + begin; } static ssize_t maweb_handle_ws(instance* inst, ssize_t bytes_read){ @@ -507,6 +731,7 @@ static int maweb_handle_fd(instance* inst){ if(bytes_handled < 0){ bytes_handled = data->offset + bytes_read; + data->offset = 0; //TODO close, reopen fprintf(stderr, "maweb failed to handle incoming data\n"); return 1; @@ -517,8 +742,6 @@ static int maweb_handle_fd(instance* inst){ memmove(data->buffer, data->buffer + bytes_handled, (data->offset + bytes_read) - bytes_handled); - //FIXME this might be somewhat borked - bytes_read -= data->offset; bytes_handled -= data->offset; bytes_read -= bytes_handled; data->offset = 0; @@ -551,30 +774,10 @@ static int maweb_set(instance* inst, size_t num, channel** c, channel_value* v){ "\"type\":1," "\"session\":%ld" "}", ident.fields.index, ident.fields.page, v[n].normalised, data->session); - fprintf(stderr, "maweb out %s\n", xmit_buffer); maweb_send_frame(inst, ws_text, (uint8_t*) xmit_buffer, strlen(xmit_buffer)); break; case exec_upper: case exec_lower: - case exec_flash: - snprintf(xmit_buffer, sizeof(xmit_buffer), - "{\"requestType\":\"playbacks_userInput\"," - //"\"cmdline\":\"\"," - "\"execIndex\":%d," - "\"pageIndex\":%d," - "\"buttonId\":%d," - "\"pressed\":%s," - "\"released\":%s," - "\"type\":0," - "\"session\":%ld" - "}", ident.fields.index, ident.fields.page, - (data->peer_type == peer_dot2) ? (ident.fields.type - 3) : (exec_flash - ident.fields.type), - (v[n].normalised > 0.9) ? "true" : "false", - (v[n].normalised > 0.9) ? "false" : "true", - data->session); - fprintf(stderr, "maweb out %s\n", xmit_buffer); - maweb_send_frame(inst, ws_text, (uint8_t*) xmit_buffer, strlen(xmit_buffer)); - break; case exec_button: snprintf(xmit_buffer, sizeof(xmit_buffer), "{\"requestType\":\"playbacks_userInput\"," @@ -586,13 +789,11 @@ static int maweb_set(instance* inst, size_t num, channel** c, channel_value* v){ "\"released\":%s," "\"type\":0," "\"session\":%ld" - "}", ident.fields.index, - ident.fields.page, - 0, + "}", ident.fields.index, ident.fields.page, + (data->peer_type == peer_dot2 && ident.fields.type == exec_upper) ? 0 : (ident.fields.type - exec_button), (v[n].normalised > 0.9) ? "true" : "false", (v[n].normalised > 0.9) ? "false" : "true", data->session); - fprintf(stderr, "maweb out %s\n", xmit_buffer); maweb_send_frame(inst, ws_text, (uint8_t*) xmit_buffer, strlen(xmit_buffer)); break; case cmdline_button: @@ -602,7 +803,6 @@ static int maweb_set(instance* inst, size_t num, channel** c, channel_value* v){ "\"value\":%d" "}", cmdline_keys[ident.fields.index], (v[n].normalised > 0.9) ? 1 : 0); - fprintf(stderr, "maweb out %s\n", xmit_buffer); maweb_send_frame(inst, ws_text, (uint8_t*) xmit_buffer, strlen(xmit_buffer)); break; default: @@ -638,6 +838,29 @@ static int maweb_keepalive(){ return 0; } +static int maweb_poll(){ + size_t n, u; + instance** inst = NULL; + maweb_instance_data* data = NULL; + + //fetch all defined instances + if(mm_backend_instances(BACKEND_NAME, &n, &inst)){ + fprintf(stderr, "Failed to fetch instance list\n"); + return 1; + } + + //send data polls for logged-in instances + for(u = 0; u < n; u++){ + data = (maweb_instance_data*) inst[u]->impl; + if(data->login){ + maweb_request_playbacks(inst[u]); + } + } + + free(inst); + return 0; +} + static int maweb_handle(size_t num, managed_fd* fds){ size_t n = 0; int rv = 0; @@ -646,17 +869,24 @@ static int maweb_handle(size_t num, managed_fd* fds){ rv |= maweb_handle_fd((instance*) fds[n].impl); } + //FIXME all keepalive processing allocates temporary buffers, this might an optimization target if(last_keepalive && mm_timestamp() - last_keepalive >= MAWEB_CONNECTION_KEEPALIVE){ rv |= maweb_keepalive(); last_keepalive = mm_timestamp(); } + if(last_update && mm_timestamp() - last_update >= update_interval){ + rv |= maweb_poll(); + last_update = mm_timestamp(); + } + return rv; } static int maweb_start(){ size_t n, u; instance** inst = NULL; + maweb_instance_data* data = NULL; //fetch all defined instances if(mm_backend_instances(BACKEND_NAME, &n, &inst)){ @@ -665,6 +895,10 @@ static int maweb_start(){ } for(u = 0; u < n; u++){ + //sort channels + data = (maweb_instance_data*) inst[u]->impl; + qsort(data->input_channel, data->input_channels, sizeof(maweb_channel_ident), channel_comparator); + if(maweb_connect(inst[u])){ fprintf(stderr, "Failed to open connection to MA Web Remote for instance %s\n", inst[u]->name); return 1; @@ -678,8 +912,8 @@ static int maweb_start(){ fprintf(stderr, "maweb backend registering %lu descriptors to core\n", n); - //initialize keepalive timeout - last_keepalive = mm_timestamp(); + //initialize timeouts + last_keepalive = last_update = mm_timestamp(); return 0; } @@ -713,6 +947,10 @@ static int maweb_shutdown(){ data->offset = data->allocated = 0; data->state = ws_new; + + free(data->input_channel); + data->input_channel = NULL; + data->input_channels = 0; } free(inst); diff --git a/backends/maweb.h b/backends/maweb.h index 5f59cc1..a868426 100644 --- a/backends/maweb.h +++ b/backends/maweb.h @@ -9,22 +9,22 @@ static int maweb_set(instance* inst, size_t num, channel** c, channel_value* v); static int maweb_handle(size_t num, managed_fd* fds); static int maweb_start(); static int maweb_shutdown(); +static uint32_t maweb_interval(); //Default login password: MD5("midimonster") #define MAWEB_DEFAULT_PASSWORD "2807623134739142b119aff358f8a219" #define MAWEB_DEFAULT_PORT "80" #define MAWEB_RECV_CHUNK 1024 -#define MAWEB_XMIT_CHUNK 2048 +#define MAWEB_XMIT_CHUNK 4096 #define MAWEB_FRAME_HEADER_LENGTH 16 #define MAWEB_CONNECTION_KEEPALIVE 10000 typedef enum /*_maweb_channel_type*/ { type_unset = 0, exec_fader = 1, - exec_button = 2, - exec_upper = 3, - exec_lower = 4, - exec_flash = 5, + exec_button = 2, //gma: 0 dot: 0 + exec_lower = 3, //gma: 1 dot: 1 + exec_upper = 4, //gma: 2 dot: 0 cmdline_button } maweb_channel_type; @@ -69,6 +69,10 @@ typedef struct /*_maweb_instance_data*/ { int64_t session; maweb_peer_type peer_type; + //need to keep an internal registry to optimize data polls + size_t input_channels; + maweb_channel_ident* input_channel; + int fd; maweb_state state; size_t offset; diff --git a/backends/maweb.md b/backends/maweb.md index d713d82..fe430db 100644 --- a/backends/maweb.md +++ b/backends/maweb.md @@ -2,8 +2,7 @@ This backend connects directly with the integrated *MA Web Remote* of MA Lighting consoles and OnPC instances (GrandMA2 / GrandMA2 OnPC / GrandMA Dot2 / GrandMA Dot2 OnPC). -It grants read-write access to the console's playback faders and buttons as well as write access to -the command line buttons. +It grants read-write access to the console's playback controls as well as write access to the command line. #### Setting up the console @@ -16,7 +15,9 @@ Web Remote. Set a web remote password using the option below the activation sett #### Global configuration -The `maweb` backend does not take any global configuration. +| Option | Example value | Default value | Description | +|---------------|-----------------------|-----------------------|---------------------------------------------------------------| +| `interval` | `100` | `50` | Query interval for input data polling (in msec) | #### Instance configuration @@ -28,39 +29,44 @@ The `maweb` backend does not take any global configuration. #### Channel specification -Currently, three types of channels can be assigned +Currently, three types of MA controls can be assigned, with each having some subcontrols + +* Fader executor +* Button executor +* Command line buttons ##### Executors * For the GrandMA2, executors are arranged in pages, with each page having 90 fader executors (numbered 1 through 90) and 90 button executors (numbered 101 through 190). - * A fader executor consists of a `fader`, two buttons above it (`upper`, `lower`) and one `flash` button below it. - * A button executor consists of a `button` control. + * A fader executor consists of a `fader`, two buttons above it (`upper`, `lower`) and one `button` below it. + * A button executor consists of a `button` control and a virtual `fader` (visible on the console in the "Action Buttons" view). * For the dot2, executors are also arranged in pages, but the controls are non-obviously numbered. * For the faders, they are numerically right-to-left from the Core Fader section (Faders 6 to 1) over the F-Wing 1 (Faders 13 to 6) to F-Wing 2 (Faders 21 to 14). * Above the fader sections are two rows of 21 `button` executors, numbered 122 through 101 (upper row) and 222 through 201 (lower row), in the same order as the faders are. * Fader executors have two buttons below them (`upper` and `lower`). - * The button executor section consists of six rows of 18 buttons, divided into two button wings. Buttons on the wings + * The button executor section consists of six rows of 16 buttons, divided into two button wings. Buttons on the wings are once again numbered right-to-left. - * B-Wing 1 has `button` executors 308 to 301 (top row), 408 to 401 (second row), and so on until 808 through 801 (bottom row) + * B-Wing 1 has `button` controls 308 to 301 (top row), 408 to 401 (second row), and so on until 808 through 801 (bottom row) * B-Wing 2 has 316 to 309 (top row) through 816 to 809 (bottom row) When creating a new show, only the first page is created and active. Additional pages have to be created explicitly within -the console before being usable. +the console before being usable. `fader` controls, when mapped as outputs from the MA, output their value, `button` controls +output 1 when the corresponding executor is running, 0 otherwise. These controls can be addressed like ``` mw1.page1.fader5 > mw1.page1.upper5 -mw1.page3.lower3 > mw1.page2.flash2 +mw1.page3.lower3 > mw1.page2.button2 ``` A button executor can likewise be mapped using the syntax ``` -mw1.page2.button103 > mw1.page3.button101 +mw1.page2.button103 > mw1.page3.fader101 mw1.page2.button803 > mw1.page3.button516 ``` @@ -99,10 +105,12 @@ Since this may be a problem on some platforms, the backend can be built with thi to set arbitrary passwords. The backend will always try to log in with the default password `midimonster` in this case. The user name is still configurable. +Data input from the console is done by actively querying the state of all mapped controls, which is resource-intensive if done +at low latency. A lower input interval value will produce data with lower latency, at the cost of network & CPU usage. +Higher values will make the input "step" more, but will not consume as many CPU cycles and network bandwidth. + This backend is currently in active development. It therefore has some limitations: * It outputs a lot of debug information -* It currently is write-only, channel events are only sent to the MA, not generated by it -* Fader executors (and their buttons) seem to work, I haven't tested button executors yet. * Command line events are sent, but I'm not sure they're being handled yet -* I have so far only tested it with GradMA2 OnPC +* For the dot2, currently only the Core & F-Wings are supported for input from the console, not the B-Wings diff --git a/backends/midi.h b/backends/midi.h index 5ec17ea..6c3fcf9 100644 --- a/backends/midi.h +++ b/backends/midi.h @@ -24,4 +24,5 @@ typedef union { uint8_t control; } fields; uint64_t label; -} midi_channel_ident; \ No newline at end of file +} midi_channel_ident; + diff --git a/backends/osc.h b/backends/osc.h index ab19463..dd5afb0 100644 --- a/backends/osc.h +++ b/backends/osc.h @@ -73,4 +73,5 @@ typedef union { uint32_t parameter; } fields; uint64_t label; -} osc_channel_ident; \ No newline at end of file +} osc_channel_ident; + -- cgit v1.2.3 From 1107a91861189d28d771d02d721d61b403aac38a Mon Sep 17 00:00:00 2001 From: cbdev Date: Wed, 4 Dec 2019 01:21:14 +0100 Subject: Explicitly mark the backend init symbol visible --- backends/artnet.c | 2 +- backends/artnet.h | 2 +- backends/evdev.c | 2 +- backends/evdev.h | 2 +- backends/jack.c | 4 +++- backends/jack.h | 2 +- backends/loopback.c | 2 +- backends/loopback.h | 2 +- backends/lua.c | 2 +- backends/lua.h | 2 +- backends/maweb.c | 2 +- backends/maweb.h | 2 +- backends/midi.c | 2 +- backends/midi.h | 2 +- backends/ola.cpp | 2 +- backends/ola.h | 2 +- backends/osc.c | 2 +- backends/osc.h | 2 +- backends/sacn.c | 2 +- backends/sacn.h | 2 +- backends/winmidi.c | 2 +- backends/winmidi.h | 2 +- midimonster.h | 10 ++++++++++ 23 files changed, 34 insertions(+), 22 deletions(-) (limited to 'backends/osc.h') diff --git a/backends/artnet.c b/backends/artnet.c index e01ac94..8a62a43 100644 --- a/backends/artnet.c +++ b/backends/artnet.c @@ -41,7 +41,7 @@ static int artnet_listener(char* host, char* port){ return 0; } -int init(){ +MM_PLUGIN_API int init(){ backend artnet = { .name = BACKEND_NAME, .conf = artnet_configure, diff --git a/backends/artnet.h b/backends/artnet.h index f5aa745..cce11d1 100644 --- a/backends/artnet.h +++ b/backends/artnet.h @@ -3,7 +3,7 @@ #endif #include "midimonster.h" -int init(); +MM_PLUGIN_API 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(); diff --git a/backends/evdev.c b/backends/evdev.c index b19cda8..dd2231b 100644 --- a/backends/evdev.c +++ b/backends/evdev.c @@ -24,7 +24,7 @@ static struct { .detect = 0 }; -int init(){ +MM_PLUGIN_API int init(){ backend evdev = { .name = BACKEND_NAME, .conf = evdev_configure, diff --git a/backends/evdev.h b/backends/evdev.h index 48bd0ab..30ce892 100644 --- a/backends/evdev.h +++ b/backends/evdev.h @@ -8,7 +8,7 @@ * disabled by building with -DEVDEV_NO_UINPUT */ -int init(); +MM_PLUGIN_API 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(); diff --git a/backends/jack.c b/backends/jack.c index b3aacd4..e8a63bc 100644 --- a/backends/jack.c +++ b/backends/jack.c @@ -4,6 +4,8 @@ #include #include +#define DEBUG + #include "jack.h" #include #include @@ -27,7 +29,7 @@ static struct /*_mmjack_backend_cfg*/ { .jack_shutdown = 0 }; -int init(){ +MM_PLUGIN_API int init(){ backend mmjack = { .name = BACKEND_NAME, .conf = mmjack_configure, diff --git a/backends/jack.h b/backends/jack.h index dd59cd2..5598042 100644 --- a/backends/jack.h +++ b/backends/jack.h @@ -2,7 +2,7 @@ #include #include -int init(); +MM_PLUGIN_API int init(); static int mmjack_configure(char* option, char* value); static int mmjack_configure_instance(instance* inst, char* option, char* value); static instance* mmjack_instance(); diff --git a/backends/loopback.c b/backends/loopback.c index 083a312..0a45bde 100644 --- a/backends/loopback.c +++ b/backends/loopback.c @@ -3,7 +3,7 @@ #define BACKEND_NAME "loopback" -int init(){ +MM_PLUGIN_API int init(){ backend loopback = { .name = BACKEND_NAME, .conf = loopback_configure, diff --git a/backends/loopback.h b/backends/loopback.h index c73ca20..a08417b 100644 --- a/backends/loopback.h +++ b/backends/loopback.h @@ -1,6 +1,6 @@ #include "midimonster.h" -int init(); +MM_PLUGIN_API int init(); static int loopback_configure(char* option, char* value); static int loopback_configure_instance(instance* inst, char* option, char* value); static instance* loopback_instance(); diff --git a/backends/lua.c b/backends/lua.c index 3555e72..0b47b2c 100644 --- a/backends/lua.c +++ b/backends/lua.c @@ -19,7 +19,7 @@ static int timer_fd = -1; static uint64_t last_timestamp; #endif -int init(){ +MM_PLUGIN_API int init(){ backend lua = { #ifndef MMBACKEND_LUA_TIMERFD .interval = lua_interval, diff --git a/backends/lua.h b/backends/lua.h index f2583a8..e187a8e 100644 --- a/backends/lua.h +++ b/backends/lua.h @@ -9,7 +9,7 @@ #define MMBACKEND_LUA_TIMERFD #endif -int init(); +MM_PLUGIN_API int init(); static int lua_configure(char* option, char* value); static int lua_configure_instance(instance* inst, char* option, char* value); static instance* lua_instance(); diff --git a/backends/maweb.c b/backends/maweb.c index c495512..08156f2 100644 --- a/backends/maweb.c +++ b/backends/maweb.c @@ -72,7 +72,7 @@ static maweb_command_key cmdline_keys[] = { {"GO_MINUS_SMALL", 50}, {"PAUSE_SMALL", 51}, {"GO_PLUS_SMALL", 52} }; -int init(){ +MM_PLUGIN_API int init(){ backend maweb = { .name = BACKEND_NAME, .conf = maweb_configure, diff --git a/backends/maweb.h b/backends/maweb.h index 14e4755..9091cda 100644 --- a/backends/maweb.h +++ b/backends/maweb.h @@ -1,6 +1,6 @@ #include "midimonster.h" -int init(); +MM_PLUGIN_API int init(); static int maweb_configure(char* option, char* value); static int maweb_configure_instance(instance* inst, char* option, char* value); static instance* maweb_instance(); diff --git a/backends/midi.c b/backends/midi.c index cde278b..f380f59 100644 --- a/backends/midi.c +++ b/backends/midi.c @@ -23,7 +23,7 @@ static struct { .detect = 0 }; -int init(){ +MM_PLUGIN_API int init(){ backend midi = { .name = BACKEND_NAME, .conf = midi_configure, diff --git a/backends/midi.h b/backends/midi.h index 6c3fcf9..b9934f1 100644 --- a/backends/midi.h +++ b/backends/midi.h @@ -1,6 +1,6 @@ #include "midimonster.h" -int init(); +MM_PLUGIN_API 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(); diff --git a/backends/ola.cpp b/backends/ola.cpp index 632cef7..d069a8c 100644 --- a/backends/ola.cpp +++ b/backends/ola.cpp @@ -11,7 +11,7 @@ static ola::io::SelectServer* ola_select = NULL; static ola::OlaCallbackClient* ola_client = NULL; -int init(){ +MM_PLUGIN_API int init(){ backend ola = { .name = BACKEND_NAME, .conf = ola_configure, diff --git a/backends/ola.h b/backends/ola.h index c943d52..1637495 100644 --- a/backends/ola.h +++ b/backends/ola.h @@ -4,7 +4,7 @@ extern "C" { #undef min #undef max - int init(); + MM_PLUGIN_API int init(); static int ola_configure(char* option, char* value); static int ola_configure_instance(instance* instance, char* option, char* value); static instance* ola_instance(); diff --git a/backends/osc.c b/backends/osc.c index bffbba8..d9f9139 100644 --- a/backends/osc.c +++ b/backends/osc.c @@ -19,7 +19,7 @@ static struct { .detect = 0 }; -int init(){ +MM_PLUGIN_API int init(){ backend osc = { .name = BACKEND_NAME, .conf = osc_configure, diff --git a/backends/osc.h b/backends/osc.h index dd5afb0..86be285 100644 --- a/backends/osc.h +++ b/backends/osc.h @@ -7,7 +7,7 @@ #define OSC_RECV_BUF 8192 #define OSC_XMIT_BUF 8192 -int init(); +MM_PLUGIN_API int init(); static int osc_configure(char* option, char* value); static int osc_configure_instance(instance* inst, char* option, char* value); static instance* osc_instance(); diff --git a/backends/sacn.c b/backends/sacn.c index 6e1b20b..d8b3eb3 100644 --- a/backends/sacn.c +++ b/backends/sacn.c @@ -31,7 +31,7 @@ static struct /*_sacn_global_config*/ { .last_announce = 0 }; -int init(){ +MM_PLUGIN_API int init(){ backend sacn = { .name = BACKEND_NAME, .conf = sacn_configure, diff --git a/backends/sacn.h b/backends/sacn.h index 7af2a36..631d3a4 100644 --- a/backends/sacn.h +++ b/backends/sacn.h @@ -1,6 +1,6 @@ #include "midimonster.h" -int init(); +MM_PLUGIN_API 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(); diff --git a/backends/winmidi.c b/backends/winmidi.c index ffca3b4..bda5401 100644 --- a/backends/winmidi.c +++ b/backends/winmidi.c @@ -23,7 +23,7 @@ static struct { //TODO receive feedback socket until EAGAIN -int init(){ +MM_PLUGIN_API int init(){ backend winmidi = { .name = BACKEND_NAME, .conf = winmidi_configure, diff --git a/backends/winmidi.h b/backends/winmidi.h index e4abda1..ffa6a26 100644 --- a/backends/winmidi.h +++ b/backends/winmidi.h @@ -1,6 +1,6 @@ #include "midimonster.h" -int init(); +MM_PLUGIN_API int init(); static int winmidi_configure(char* option, char* value); static int winmidi_configure_instance(instance* inst, char* option, char* value); static instance* winmidi_instance(); diff --git a/midimonster.h b/midimonster.h index 3922b03..1192d6a 100644 --- a/midimonster.h +++ b/midimonster.h @@ -5,6 +5,7 @@ #include #include +/* API call attributes and visibilities */ #ifndef MM_API #ifdef _WIN32 #define MM_API __attribute__((dllimport)) @@ -13,6 +14,15 @@ #endif #endif +/* Some build systems may apply the -fvisibility=hidden parameter from the core build to the backends, so mark the init function visible */ +#ifndef MM_PLUGIN_API + #ifdef _WIN32 + #define MM_PLUGIN_API __attribute__((dllexport)) + #else + #define MM_PLUGIN_API __attribute__((visibility ("default"))) + #endif +#endif + /* Straight-forward min / max macros */ #define max(a,b) (((a) > (b)) ? (a) : (b)) #define min(a,b) (((a) < (b)) ? (a) : (b)) -- cgit v1.2.3 From 3eada28582b144519e95a44ee3adc3f46d39036e Mon Sep 17 00:00:00 2001 From: cbdev Date: Thu, 5 Dec 2019 21:05:14 +0100 Subject: Add flags parameter to channel parser plugin API (Fixes #31) --- backends/artnet.c | 2 +- backends/artnet.h | 2 +- backends/evdev.c | 2 +- backends/evdev.h | 2 +- backends/jack.c | 2 +- backends/jack.h | 2 +- backends/loopback.c | 2 +- backends/loopback.h | 2 +- backends/lua.c | 2 +- backends/lua.h | 2 +- backends/maweb.c | 2 +- backends/maweb.h | 2 +- backends/midi.c | 2 +- backends/midi.h | 2 +- backends/ola.cpp | 2 +- backends/ola.h | 2 +- backends/osc.c | 2 +- backends/osc.h | 2 +- backends/sacn.c | 2 +- backends/sacn.h | 2 +- backends/winmidi.c | 2 +- backends/winmidi.h | 2 +- config.c | 8 ++++---- midimonster.h | 15 ++++++++++++--- 24 files changed, 38 insertions(+), 29 deletions(-) (limited to 'backends/osc.h') diff --git a/backends/artnet.c b/backends/artnet.c index 8a62a43..57eb7b1 100644 --- a/backends/artnet.c +++ b/backends/artnet.c @@ -156,7 +156,7 @@ static int artnet_configure_instance(instance* inst, char* option, char* value){ return 1; } -static channel* artnet_channel(instance* inst, char* spec){ +static channel* artnet_channel(instance* inst, char* spec, uint8_t flags){ artnet_instance_data* data = (artnet_instance_data*) inst->impl; char* spec_next = spec; unsigned chan_a = strtoul(spec, &spec_next, 10); diff --git a/backends/artnet.h b/backends/artnet.h index cce11d1..f6a6709 100644 --- a/backends/artnet.h +++ b/backends/artnet.h @@ -7,7 +7,7 @@ MM_PLUGIN_API 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 channel* artnet_channel(instance* instance, char* spec, uint8_t flags); 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(); diff --git a/backends/evdev.c b/backends/evdev.c index dd2231b..0da5ae6 100644 --- a/backends/evdev.c +++ b/backends/evdev.c @@ -249,7 +249,7 @@ static int evdev_configure_instance(instance* inst, char* option, char* value) { return 1; } -static channel* evdev_channel(instance* inst, char* spec){ +static channel* evdev_channel(instance* inst, char* spec, uint8_t flags){ #ifndef EVDEV_NO_UINPUT evdev_instance_data* data = (evdev_instance_data*) inst->impl; #endif diff --git a/backends/evdev.h b/backends/evdev.h index 30ce892..6504416 100644 --- a/backends/evdev.h +++ b/backends/evdev.h @@ -12,7 +12,7 @@ MM_PLUGIN_API 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 channel* evdev_channel(instance* instance, char* spec, uint8_t flags); 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(); diff --git a/backends/jack.c b/backends/jack.c index 926f800..e7bed04 100644 --- a/backends/jack.c +++ b/backends/jack.c @@ -409,7 +409,7 @@ static int mmjack_parse_midispec(mmjack_channel_ident* ident, char* spec){ return 0; } -static channel* mmjack_channel(instance* inst, char* spec){ +static channel* mmjack_channel(instance* inst, char* spec, uint8_t flags){ mmjack_instance_data* data = (mmjack_instance_data*) inst->impl; mmjack_channel_ident ident = { .label = 0 diff --git a/backends/jack.h b/backends/jack.h index 5598042..a7f3e8b 100644 --- a/backends/jack.h +++ b/backends/jack.h @@ -6,7 +6,7 @@ MM_PLUGIN_API int init(); static int mmjack_configure(char* option, char* value); static int mmjack_configure_instance(instance* inst, char* option, char* value); static instance* mmjack_instance(); -static channel* mmjack_channel(instance* inst, char* spec); +static channel* mmjack_channel(instance* inst, char* spec, uint8_t flags); static int mmjack_set(instance* inst, size_t num, channel** c, channel_value* v); static int mmjack_handle(size_t num, managed_fd* fds); static int mmjack_start(); diff --git a/backends/loopback.c b/backends/loopback.c index 0a45bde..41e6f85 100644 --- a/backends/loopback.c +++ b/backends/loopback.c @@ -49,7 +49,7 @@ static instance* loopback_instance(){ return i; } -static channel* loopback_channel(instance* inst, char* spec){ +static channel* loopback_channel(instance* inst, char* spec, uint8_t flags){ size_t u; loopback_instance_data* data = (loopback_instance_data*) inst->impl; diff --git a/backends/loopback.h b/backends/loopback.h index a08417b..ee51c66 100644 --- a/backends/loopback.h +++ b/backends/loopback.h @@ -4,7 +4,7 @@ MM_PLUGIN_API int init(); static int loopback_configure(char* option, char* value); static int loopback_configure_instance(instance* inst, char* option, char* value); static instance* loopback_instance(); -static channel* loopback_channel(instance* inst, char* spec); +static channel* loopback_channel(instance* inst, char* spec, uint8_t flags); static int loopback_set(instance* inst, size_t num, channel** c, channel_value* v); static int loopback_handle(size_t num, managed_fd* fds); static int loopback_start(); diff --git a/backends/lua.c b/backends/lua.c index 0b47b2c..40e6613 100644 --- a/backends/lua.c +++ b/backends/lua.c @@ -330,7 +330,7 @@ static instance* lua_instance(){ return inst; } -static channel* lua_channel(instance* inst, char* spec){ +static channel* lua_channel(instance* inst, char* spec, uint8_t flags){ size_t u; lua_instance_data* data = (lua_instance_data*) inst->impl; diff --git a/backends/lua.h b/backends/lua.h index e187a8e..4ea5b0a 100644 --- a/backends/lua.h +++ b/backends/lua.h @@ -13,7 +13,7 @@ MM_PLUGIN_API int init(); static int lua_configure(char* option, char* value); static int lua_configure_instance(instance* inst, char* option, char* value); static instance* lua_instance(); -static channel* lua_channel(instance* inst, char* spec); +static channel* lua_channel(instance* inst, char* spec, uint8_t flags); static int lua_set(instance* inst, size_t num, channel** c, channel_value* v); static int lua_handle(size_t num, managed_fd* fds); static int lua_start(); diff --git a/backends/maweb.c b/backends/maweb.c index 08156f2..d008cc0 100644 --- a/backends/maweb.c +++ b/backends/maweb.c @@ -229,7 +229,7 @@ static instance* maweb_instance(){ return inst; } -static channel* maweb_channel(instance* inst, char* spec){ +static channel* maweb_channel(instance* inst, char* spec, uint8_t flags){ maweb_instance_data* data = (maweb_instance_data*) inst->impl; maweb_channel_data chan = { 0 diff --git a/backends/maweb.h b/backends/maweb.h index 9091cda..05095f8 100644 --- a/backends/maweb.h +++ b/backends/maweb.h @@ -4,7 +4,7 @@ MM_PLUGIN_API int init(); static int maweb_configure(char* option, char* value); static int maweb_configure_instance(instance* inst, char* option, char* value); static instance* maweb_instance(); -static channel* maweb_channel(instance* inst, char* spec); +static channel* maweb_channel(instance* inst, char* spec, uint8_t flags); static int maweb_set(instance* inst, size_t num, channel** c, channel_value* v); static int maweb_handle(size_t num, managed_fd* fds); static int maweb_start(); diff --git a/backends/midi.c b/backends/midi.c index f380f59..92776ca 100644 --- a/backends/midi.c +++ b/backends/midi.c @@ -110,7 +110,7 @@ static int midi_configure_instance(instance* inst, char* option, char* value){ return 1; } -static channel* midi_channel(instance* inst, char* spec){ +static channel* midi_channel(instance* inst, char* spec, uint8_t flags){ midi_channel_ident ident = { .label = 0 }; diff --git a/backends/midi.h b/backends/midi.h index b9934f1..4e16f90 100644 --- a/backends/midi.h +++ b/backends/midi.h @@ -4,7 +4,7 @@ MM_PLUGIN_API 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 channel* midi_channel(instance* instance, char* spec, uint8_t flags); 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(); diff --git a/backends/ola.cpp b/backends/ola.cpp index d069a8c..c13e8f9 100644 --- a/backends/ola.cpp +++ b/backends/ola.cpp @@ -68,7 +68,7 @@ static int ola_configure_instance(instance* inst, char* option, char* value){ return 1; } -static channel* ola_channel(instance* inst, char* spec){ +static channel* ola_channel(instance* inst, char* spec, uint8_t flags){ ola_instance_data* data = (ola_instance_data*) inst->impl; char* spec_next = spec; unsigned chan_a = strtoul(spec, &spec_next, 10); diff --git a/backends/ola.h b/backends/ola.h index 1637495..0c42bac 100644 --- a/backends/ola.h +++ b/backends/ola.h @@ -8,7 +8,7 @@ extern "C" { static int ola_configure(char* option, char* value); static int ola_configure_instance(instance* instance, char* option, char* value); static instance* ola_instance(); - static channel* ola_channel(instance* instance, char* spec); + static channel* ola_channel(instance* instance, char* spec, uint8_t flags); static int ola_set(instance* inst, size_t num, channel** c, channel_value* v); static int ola_handle(size_t num, managed_fd* fds); static int ola_start(); diff --git a/backends/osc.c b/backends/osc.c index d9f9139..757ad89 100644 --- a/backends/osc.c +++ b/backends/osc.c @@ -577,7 +577,7 @@ static instance* osc_instance(){ return inst; } -static channel* osc_map_channel(instance* inst, char* spec){ +static channel* osc_map_channel(instance* inst, char* spec, uint8_t flags){ size_t u, p; osc_instance_data* data = (osc_instance_data*) inst->impl; osc_channel_ident ident = { diff --git a/backends/osc.h b/backends/osc.h index 86be285..6f3b923 100644 --- a/backends/osc.h +++ b/backends/osc.h @@ -11,7 +11,7 @@ MM_PLUGIN_API int init(); static int osc_configure(char* option, char* value); static int osc_configure_instance(instance* inst, char* option, char* value); static instance* osc_instance(); -static channel* osc_map_channel(instance* inst, char* spec); +static channel* osc_map_channel(instance* inst, char* spec, uint8_t flags); static int osc_set(instance* inst, size_t num, channel** c, channel_value* v); static int osc_handle(size_t num, managed_fd* fds); static int osc_start(); diff --git a/backends/sacn.c b/backends/sacn.c index d8b3eb3..2229b8a 100644 --- a/backends/sacn.c +++ b/backends/sacn.c @@ -198,7 +198,7 @@ static instance* sacn_instance(){ return inst; } -static channel* sacn_channel(instance* inst, char* spec){ +static channel* sacn_channel(instance* inst, char* spec, uint8_t flags){ sacn_instance_data* data = (sacn_instance_data*) inst->impl; char* spec_next = spec; diff --git a/backends/sacn.h b/backends/sacn.h index 631d3a4..1d3268c 100644 --- a/backends/sacn.h +++ b/backends/sacn.h @@ -4,7 +4,7 @@ MM_PLUGIN_API 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 channel* sacn_channel(instance* instance, char* spec, uint8_t flags); 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(); diff --git a/backends/winmidi.c b/backends/winmidi.c index b274c06..790257b 100644 --- a/backends/winmidi.c +++ b/backends/winmidi.c @@ -110,7 +110,7 @@ static instance* winmidi_instance(){ return i; } -static channel* winmidi_channel(instance* inst, char* spec){ +static channel* winmidi_channel(instance* inst, char* spec, uint8_t flags){ char* next_token = NULL; winmidi_channel_ident ident = { .label = 0 diff --git a/backends/winmidi.h b/backends/winmidi.h index 8c2d76b..985c46a 100644 --- a/backends/winmidi.h +++ b/backends/winmidi.h @@ -4,7 +4,7 @@ MM_PLUGIN_API int init(); static int winmidi_configure(char* option, char* value); static int winmidi_configure_instance(instance* inst, char* option, char* value); static instance* winmidi_instance(); -static channel* winmidi_channel(instance* inst, char* spec); +static channel* winmidi_channel(instance* inst, char* spec, uint8_t flags); static int winmidi_set(instance* inst, size_t num, channel** c, channel_value* v); static int winmidi_handle(size_t num, managed_fd* fds); static int winmidi_start(); diff --git a/config.c b/config.c index 8e7e581..0b9173e 100644 --- a/config.c +++ b/config.c @@ -175,7 +175,7 @@ static int config_glob_scan(instance* inst, channel_spec* spec){ return 0; } -static channel* config_glob_resolve(instance* inst, channel_spec* spec, uint64_t n){ +static channel* config_glob_resolve(instance* inst, channel_spec* spec, uint64_t n, uint8_t map_direction){ size_t glob = 0, glob_length; ssize_t bytes = 0; uint64_t current_value = 0; @@ -216,7 +216,7 @@ static channel* config_glob_resolve(instance* inst, channel_spec* spec, uint64_t } } - result = inst->backend->channel(inst, resolved_spec); + result = inst->backend->channel(inst, resolved_spec, map_direction); if(spec->globs && !result){ fprintf(stderr, "Failed to match multichannel evaluation %s to a channel\n", resolved_spec); } @@ -294,8 +294,8 @@ static int config_map(char* to_raw, char* from_raw){ //iterate, resolve globs and map rv = 0; for(n = 0; !rv && n < max(spec_from.channels, spec_to.channels); n++){ - channel_from = config_glob_resolve(instance_from, &spec_from, min(n, spec_from.channels)); - channel_to = config_glob_resolve(instance_to, &spec_to, min(n, spec_to.channels)); + channel_from = config_glob_resolve(instance_from, &spec_from, min(n, spec_from.channels), mmchannel_input); + channel_to = config_glob_resolve(instance_to, &spec_to, min(n, spec_to.channels), mmchannel_output); if(!channel_from || !channel_to){ rv = 1; diff --git a/midimonster.h b/midimonster.h index 1192d6a..5ce0c73 100644 --- a/midimonster.h +++ b/midimonster.h @@ -87,8 +87,11 @@ struct _managed_fd; * Parse instance configuration from the user-supplied configuration * file. Returning a non-zero value fails config parsing. * * mmbackend_channel - * Parse a channel-spec to be mapped to/from. Returning NULL signals an - * out-of-memory condition and terminates the program. + * Parse a channel-spec to be mapped to/from. The `falgs` parameter supplies + * additional information to the parser, such as whether the channel is being + * queried for use as input (to the MIDIMonster core) and/or output + * (from the MIDIMonster core) channel (on a per-query basis). + * Returning NULL signals an out-of-memory condition and terminates the program. * * mmbackend_start * Called after all instances have been created and all mappings * have been set up. Only backends for which instances have been configured @@ -121,7 +124,7 @@ struct _managed_fd; */ typedef int (*mmbackend_handle_event)(struct _backend_instance* inst, size_t channels, struct _backend_channel** c, struct _channel_value* v); typedef struct _backend_instance* (*mmbackend_create_instance)(); -typedef struct _backend_channel* (*mmbackend_parse_channel)(struct _backend_instance* instance, char* spec); +typedef struct _backend_channel* (*mmbackend_parse_channel)(struct _backend_instance* instance, char* spec, uint8_t flags); typedef void (*mmbackend_free_channel)(struct _backend_channel* c); typedef int (*mmbackend_configure)(char* option, char* value); typedef int (*mmbackend_configure_instance)(struct _backend_instance* instance, char* option, char* value); @@ -130,6 +133,12 @@ typedef int (*mmbackend_start)(); typedef uint32_t (*mmbackend_interval)(); typedef int (*mmbackend_shutdown)(); +/* Bit masks for the `flags` parameter to mmbackend_parse_channel */ +typedef enum { + mmchannel_input = 0x1, + mmchannel_output = 0x2 +} mmbe_channel_flags; + /* Channel event value, .normalised is used by backends to determine channel values */ typedef struct _channel_value { union { -- cgit v1.2.3