From bb6111986bf7a997055287b916d0822957c5d13c Mon Sep 17 00:00:00 2001 From: cbdev Date: Sun, 11 Aug 2019 20:29:17 +0200 Subject: Initial maweb backend --- backends/maweb.h | 69 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 69 insertions(+) create mode 100644 backends/maweb.h (limited to 'backends/maweb.h') diff --git a/backends/maweb.h b/backends/maweb.h new file mode 100644 index 0000000..6e6e652 --- /dev/null +++ b/backends/maweb.h @@ -0,0 +1,69 @@ +#include "midimonster.h" + +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 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(); + +//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_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, + cmdline_button +} maweb_channel_type; + +typedef enum /*_ws_conn_state*/ { + ws_new, + ws_http, + ws_open, + ws_closed +} maweb_state; + +typedef enum /*_ws_frame_op*/ { + ws_text = 1, + ws_binary = 2, + ws_ping = 9, + ws_pong = 10 +} maweb_operation; + +typedef union { + struct { + uint8_t padding[3]; + uint8_t type; + uint16_t page; + uint16_t index; + } fields; + uint64_t label; +} maweb_channel_ident; + +typedef struct /*_maweb_instance_data*/ { + char* host; + char* port; + char* user; + char* pass; + + uint8_t login; + int64_t session; + + int fd; + maweb_state state; + size_t offset; + size_t allocated; + uint8_t* buffer; +} maweb_instance_data; -- cgit v1.2.3 From 7997c74b421437c64ad3c25d5e7943470a6b9bc7 Mon Sep 17 00:00:00 2001 From: cbdev Date: Fri, 16 Aug 2019 19:57:25 +0200 Subject: Implement dot2 specific workarounds --- backends/maweb.c | 16 ++++++++-------- backends/maweb.h | 10 +++++++++- backends/maweb.md | 35 +++++++++++++++++++++++++++-------- 3 files changed, 44 insertions(+), 17 deletions(-) (limited to 'backends/maweb.h') diff --git a/backends/maweb.c b/backends/maweb.c index 07fce12..4d93987 100644 --- a/backends/maweb.c +++ b/backends/maweb.c @@ -239,8 +239,7 @@ static channel* maweb_channel(instance* inst, char* spec){ } } - if(ident.fields.type && ident.fields.index && ident.fields.page - && ident.fields.index <= 90){ + if(ident.fields.type && ident.fields.index && ident.fields.page){ //actually, those are zero-indexed... ident.fields.index--; ident.fields.page--; @@ -297,7 +296,7 @@ static int maweb_handle_message(instance* inst, char* payload, size_t payload_le fprintf(stderr, "maweb sending user credentials\n"); snprintf(xmit_buffer, sizeof(xmit_buffer), "{\"requestType\":\"login\",\"username\":\"%s\",\"password\":\"%s\",\"session\":%ld}", - data->user, data->pass, data->session); + (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)); } @@ -305,9 +304,10 @@ static int maweb_handle_message(instance* inst, char* payload, size_t payload_le fprintf(stderr, "maweb connection established\n"); field = json_obj_str(payload, "appType", NULL); if(!strncmp(field, "dot2", 4)){ - fprintf(stderr, "maweb peer detected as dot2, forcing user name 'remote'\n"); - free(data->user); - data->user = strdup("remote"); + data->peer_type = peer_dot2; + } + else if(!strncmp(field, "gma2", 4)){ + data->peer_type = peer_ma2; } maweb_send_frame(inst, ws_text, (uint8_t*) "{\"session\":0}", 13); } @@ -573,7 +573,7 @@ static int maweb_set(instance* inst, size_t num, channel** c, channel_value* v){ "\"type\":0," "\"session\":%ld" "}", ident.fields.index, ident.fields.page, - (exec_flash - ident.fields.type), + (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); @@ -591,7 +591,7 @@ static int maweb_set(instance* inst, size_t num, channel** c, channel_value* v){ "\"released\":%s," "\"type\":0," "\"session\":%ld" - "}", ident.fields.index + 100, + "}", ident.fields.index, ident.fields.page, 0, (v[n].normalised > 0.9) ? "true" : "false", diff --git a/backends/maweb.h b/backends/maweb.h index 6e6e652..5f59cc1 100644 --- a/backends/maweb.h +++ b/backends/maweb.h @@ -28,6 +28,13 @@ typedef enum /*_maweb_channel_type*/ { cmdline_button } maweb_channel_type; +typedef enum /*_maweb_peer_type*/ { + peer_unidentified = 0, + peer_ma2, + peer_ma3, + peer_dot2 +} maweb_peer_type; + typedef enum /*_ws_conn_state*/ { ws_new, ws_http, @@ -57,9 +64,10 @@ typedef struct /*_maweb_instance_data*/ { char* port; char* user; char* pass; - + uint8_t login; int64_t session; + maweb_peer_type peer_type; int fd; maweb_state state; diff --git a/backends/maweb.md b/backends/maweb.md index dd13db9..6076f44 100644 --- a/backends/maweb.md +++ b/backends/maweb.md @@ -5,10 +5,15 @@ 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. -To allow this backend to connect to the console, enter the console configuration (`Setup` key), -select `Console`/`Global Settings` and set the `Remotes` option to `Login enabled`. +#### Setting up the console + +For the GrandMA2 enter the console configuration (`Setup` key), select `Console`/`Global Settings` and +set the `Remotes` option to `Login enabled`. Create an additional user that is able to log into the Web Remote using `Setup`/`Console`/`User & Profiles Setup`. +For the dot2, enter the console configuration using the `Setup` key, select `Global Settings` and enable the +Web Remote. Set a web remote password using the option below the activation setting. + #### Global configuration The `maweb` backend does not take any global configuration. @@ -27,12 +32,23 @@ Currently, three types of channels can be assigned ##### Executors -Executors are arranged in pages, with each page having 90 fader executors (numbered 1 through 90) and -90 button executors (numbered 101 through 190). - -Note that when creating a new show, only the first page is created and active. - -A fader executor consists of a fader, two buttons (`upper`, `lower`) above it and one `flash` button below it. +* 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. +* For the dot2, executors are also arranged in pages, but the controls are non-obviously numbered. + * For the faders, they are 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 + 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 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. These controls can be addressed like @@ -45,6 +61,7 @@ A button executor can likewise be mapped using the syntax ``` mw1.page2.button103 > mw1.page3.button101 +mw1.page2.button803 > mw1.page3.button516 ``` ##### Command line buttons @@ -58,6 +75,8 @@ mw1. The following button names are recognized by the backend: +| Supported | Command | Line | Keys | | | | +|---------------|---------------|---------------|---------------|-----------------------|-------------------------------|---------------| | `SET` | `PREV` | `NEXT` | `CLEAR` | `FIXTURE_CHANNEL` | `FIXTURE_GROUP_PRESET` | `EXEC_CUE` | | `STORE_UPDATE`| `OOPS` | `ESC` | `OFF` | `ON` | `MA` | `STORE` | | `0` | `1` | `2` | `3` | `4` | `5` | `6` | -- 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/maweb.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 f19e3fe0d131c68d1e77fcd8706b260c28f508b8 Mon Sep 17 00:00:00 2001 From: cbdev Date: Sat, 7 Sep 2019 14:20:00 +0200 Subject: maweb I/O buffering --- README.md | 6 +- backends/maweb.c | 245 ++++++++++++++++++++++++++++++++----------------------- backends/maweb.h | 27 +++--- 3 files changed, 158 insertions(+), 120 deletions(-) (limited to 'backends/maweb.h') diff --git a/README.md b/README.md index 25cdce0..33c4f5a 100644 --- a/README.md +++ b/README.md @@ -155,9 +155,9 @@ For Linux and OSX, just running `make` in the source directory should do the tri Some backends have been marked as optional as they require rather large additional software to be installed, for example the `ola` backend. To create a build including these, run `make full`. -To build on windows, install the crosscompiler package listed above and run -`make windows`. This will build `midimonster.exe` as well as a set of backends as DLL -files. +To build for Windows, you still need to compile on a Linux machine. +Install the crosscompiler package listed above and run `make windows`. +This will build `midimonster.exe` as well as a set of backends as DLL files. ## Development diff --git a/backends/maweb.c b/backends/maweb.c index 2b7a37c..ca8a47d 100644 --- a/backends/maweb.c +++ b/backends/maweb.c @@ -14,9 +14,6 @@ #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; @@ -104,11 +101,6 @@ int init(){ .interval = maweb_interval }; - if(sizeof(maweb_channel_ident) != sizeof(uint64_t)){ - fprintf(stderr, "maweb channel identification union out of bounds\n"); - return 1; - } - //register backend if(mm_backend_register(maweb)){ fprintf(stderr, "Failed to register maweb backend\n"); @@ -117,14 +109,31 @@ 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; +static ssize_t maweb_channel_index(maweb_instance_data* data, maweb_channel_type type, uint16_t page, uint16_t index){ + size_t n; + for(n = 0; n < data->channels; n++){ + if(data->channel[n].type == type + && data->channel[n].page == page + && data->channel[n].index == index){ + return n; + } } - return a->fields.index - b->fields.index; + return -1; +} + +static int channel_comparator(const void* raw_a, const void* raw_b){ + maweb_channel_data* a = (maweb_channel_data*) raw_a; + maweb_channel_data* b = (maweb_channel_data*) raw_b; + + //this needs to take into account command line channels + //they need to be sorted last so that the channel poll logic works properly + if(a->type != b->type){ + return a->type - b->type; + } + if(a->page != b->page){ + return a->page - b->page; + } + return a->index - b->index; } static uint32_t maweb_interval(){ @@ -214,14 +223,14 @@ 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 + maweb_channel_data chan = { + 0 }; char* next_token = NULL; size_t n; if(!strncmp(spec, "page", 4)){ - ident.fields.page = strtoul(spec + 4, &next_token, 10); + chan.page = strtoul(spec + 4, &next_token, 10); if(*next_token != '.'){ fprintf(stderr, "Failed to parse maweb channel spec %s: Missing separator\n", spec); return NULL; @@ -229,65 +238,57 @@ static channel* maweb_channel(instance* inst, char* spec){ next_token++; if(!strncmp(next_token, "fader", 5)){ - ident.fields.type = exec_fader; + chan.type = exec_fader; next_token += 5; } else if(!strncmp(next_token, "upper", 5)){ - ident.fields.type = exec_upper; + chan.type = exec_upper; next_token += 5; } else if(!strncmp(next_token, "lower", 5)){ - ident.fields.type = exec_lower; + chan.type = exec_lower; next_token += 5; } else if(!strncmp(next_token, "flash", 5)){ - ident.fields.type = exec_button; + chan.type = exec_button; next_token += 5; } else if(!strncmp(next_token, "button", 6)){ - ident.fields.type = exec_button; + chan.type = exec_button; next_token += 6; } - ident.fields.index = strtoul(next_token, NULL, 10); + chan.index = strtoul(next_token, NULL, 10); } else{ for(n = 0; n < sizeof(cmdline_keys) / sizeof(char*); n++){ - if(!strcmp(spec, cmdline_keys[n])){ - ident.fields.type = cmdline_button; - ident.fields.index = n + 1; - ident.fields.page = 1; + //FIXME this is broken for layerMode + if(!strcmp(spec, cmdline_keys[n]) || (*spec == 'l' && !strcmp(spec + 1, cmdline_keys[n]))){ + chan.type = (*spec == 'l') ? cmdline_local : cmdline; + chan.index = n + 1; + chan.page = 1; break; } } } - if(ident.fields.type && ident.fields.index && ident.fields.page){ + if(chan.type && chan.index && chan.page){ //actually, those are zero-indexed... - ident.fields.index--; - ident.fields.page--; - - //check if the (exec/meta) channel is already known - for(n = 0; n < data->input_channels; n++){ - if(data->input_channel[n].fields.page == ident.fields.page - && data->input_channel[n].fields.index == ident.fields.index){ - break; - } - } + chan.index--; + chan.page--; - //FIXME only register channels that are mapped as outputs - //only register exec channels for updates - if(n == data->input_channels && ident.fields.type != cmdline_button){ - data->input_channel = realloc(data->input_channel, (data->input_channels + 1) * sizeof(maweb_channel_ident)); - if(!data->input_channel){ + if(maweb_channel_index(data, chan.type, chan.page, chan.index) == -1){ + data->channel = realloc(data->channel, (data->channels + 1) * sizeof(maweb_channel_data)); + if(!data->channel){ fprintf(stderr, "Failed to allocate memory\n"); return NULL; } - data->input_channel[n].label = ident.label; - data->input_channels++; + data->channel[data->channels] = chan; + data->channels++; } - return mm_channel(inst, ident.label, 1); + return mm_channel(inst, maweb_channel_index(data, chan.type, chan.page, chan.index), 1); } + fprintf(stderr, "Failed to parse maweb channel spec %s\n", spec); return NULL; } @@ -325,20 +326,18 @@ static int maweb_send_frame(instance* inst, maweb_operation op, uint8_t* payload } static int maweb_process_playback(instance* inst, int64_t page, maweb_channel_type metatype, char* payload, size_t payload_length){ + maweb_instance_data* data = (maweb_instance_data*) inst->impl; 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 - 1, - .fields.index = json_obj_int(payload, "iExec", 191) - }; + int64_t exec_index = json_obj_int(payload, "iExec", 191); + ssize_t channel_index; if(!exec_blocks){ if(metatype == 3){ //ignore unused buttons return 0; } - fprintf(stderr, "maweb missing exec block data on exec %" PRIu64 ".%d\n", page, ident.fields.index); + fprintf(stderr, "maweb missing exec block data on exec %" PRIu64 ".%" PRIu64 "\n", page, exec_index); return 1; } @@ -348,27 +347,41 @@ static int maweb_process_playback(instance* inst, int64_t page, maweb_channel_ty } //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); + + channel_index = maweb_channel_index(data, exec_fader, page - 1, exec_index); + if(channel_index >= 0){ + if(!data->channel[channel_index].input_blocked){ + evt.normalised = json_obj_double(payload + control, "v", 0.0); + if(evt.normalised != data->channel[channel_index].in){ + mm_channel_event(mm_channel(inst, channel_index, 0), evt); + data->channel[channel_index].in = evt.normalised; + } + } + else{ + data->channel[channel_index].input_blocked--; + } } - 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); + channel_index = maweb_channel_index(data, exec_button, page - 1, exec_index); + if(channel_index >= 0){ + if(!data->channel[channel_index].input_blocked){ + evt.normalised = json_obj_int(payload, "isRun", 0); + if(evt.normalised != data->channel[channel_index].in){ + mm_channel_event(mm_channel(inst, channel_index, 0), evt); + data->channel[channel_index].in = evt.normalised; + } + } + else{ + data->channel[channel_index].input_blocked--; + } } - DBGPF("maweb page %" PRIu64 " exec %d value %f running %" PRIu64 "\n", page, ident.fields.index, json_obj_double(payload + control, "v", 0.0), json_obj_int(payload, "isRun", 0)); - ident.fields.index++; + DBGPF("maweb page %" PRIu64 " exec %" PRIu64 " value %f running %" PRIu64 "\n", page, exec_index, json_obj_double(payload + control, "v", 0.0), json_obj_int(payload, "isRun", 0)); + exec_index++; block++; } @@ -437,33 +450,37 @@ static int maweb_request_playbacks(instance* inst){ return 0; } + //TODO this needs to ignore non-metamoded execs //don't quote me on this whole segment - for(channel = 0; channel < data->input_channels; channel++){ + + //only request faders and buttons + for(channel = 0; channel < data->channels && data->channel[channel].type <= exec_button; channel++){ offsets[0] = offsets[1] = offsets[2] = 1; - page_index = data->input_channel[channel].fields.page; + page_index = data->channel[channel].page; if(data->peer_type == peer_dot2){ //blocks 0, 100 & 200 have 21 execs and need to be queried from fader view - view = (data->input_channel[channel].fields.index >= 300) ? 3 : 2; + view = (data->channel[channel].index >= 300) ? 3 : 2; - for(channel_offset = 1; channel + channel_offset <= data->input_channels; channel_offset++){ + for(channel_offset = 1; channel + channel_offset <= data->channels && data->channel[channel + channel_offset - 1].type < exec_button; channel_offset++){ channels = channel + channel_offset - 1; //find end for this exec block - for(; channel + channel_offset < data->input_channels; channel_offset++){ - if(data->input_channel[channel + channel_offset].fields.page != page_index - || (data->input_channel[channels].fields.index / 100) != (data->input_channel[channel + channel_offset].fields.index / 100)){ + for(; channel + channel_offset < data->channels; channel_offset++){ + if(data->channel[channel + channel_offset].page != page_index + || data->channel[channels].type != data->channel[channel + channel_offset].type + || (data->channel[channels].index / 100) != (data->channel[channel + channel_offset].index / 100)){ break; } } //add request block for the exec block - offsets[0] += snprintf(item_indices + offsets[0], sizeof(item_indices) - offsets[0], "%d,", data->input_channel[channels].fields.index); - offsets[1] += snprintf(item_counts + offsets[1], sizeof(item_counts) - offsets[1], "%d,", data->input_channel[channel + channel_offset - 1].fields.index - data->input_channel[channels].fields.index + 1); - offsets[2] += snprintf(item_types + offsets[2], sizeof(item_types) - offsets[2], "%d,", (data->input_channel[channels].fields.index < 100) ? 2 : 3); - - //send on page boundary, metamode boundary, last channel - if(channel + channel_offset >= data->input_channels - || data->input_channel[channel + channel_offset].fields.page != page_index - || (data->input_channel[channel].fields.index < 300) != (data->input_channel[channel + channel_offset].fields.index < 300)){ + offsets[0] += snprintf(item_indices + offsets[0], sizeof(item_indices) - offsets[0], "%d,", data->channel[channels].index); + offsets[1] += snprintf(item_counts + offsets[1], sizeof(item_counts) - offsets[1], "%d,", data->channel[channel + channel_offset - 1].index - data->channel[channels].index + 1); + offsets[2] += snprintf(item_types + offsets[2], sizeof(item_types) - offsets[2], "%d,", (data->channel[channels].index < 100) ? 2 : 3); + + //send on last channel, page boundary, metamode boundary + if(channel + channel_offset >= data->channels + || data->channel[channel + channel_offset].page != page_index + || (data->channel[channel].index < 300) != (data->channel[channel + channel_offset].index < 300)){ break; } } @@ -475,17 +492,18 @@ static int maweb_request_playbacks(instance* inst){ } else{ //for the ma, the view equals the exec type requested (we can query all button execs from button view, all fader execs from fader view) - view = (data->input_channel[channel].fields.index >= 100) ? 3 : 2; + view = (data->channel[channel].index >= 100) ? 3 : 2; snprintf(item_types, sizeof(item_types), "[%" PRIsize_t "]", 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); + snprintf(item_indices, sizeof(item_indices), "[%d]", (data->channel[channel].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++){ + for(channel_offset = 1; channel + channel_offset < data->channels + && data->channel[channel].type == data->channel[channel + channel_offset].type + && data->channel[channel].page == data->channel[channel + channel_offset].page + && data->channel[channel].index / 100 == data->channel[channel + channel_offset].index / 100; channel_offset++){ } - channels = data->input_channel[channel + channel_offset - 1].fields.index - (data->input_channel[channel].fields.index / 5) * 5; + channels = data->channel[channel + channel_offset - 1].index - (data->channel[channel].index / 5) * 5; snprintf(item_counts, sizeof(item_indices), "[%" PRIsize_t "]", ((channels / 5) * 5 + 5)); } @@ -549,7 +567,12 @@ static int maweb_handle_message(instance* inst, char* payload, size_t payload_le DBGPF("maweb message (%" PRIsize_t "): %s\n", payload_length, payload); if(json_obj(payload, "session") == JSON_NUMBER){ data->session = json_obj_int(payload, "session", data->session); - fprintf(stderr, "maweb session id is now %" PRIu64 "\n", data->session); + if(data->session < 0){ + fprintf(stderr, "maweb login failed\n"); + data->login = 0; + return 0; + } + fprintf(stderr, "maweb session id is now %" PRId64 "\n", data->session); } if(json_obj_bool(payload, "forceLogin", 0)){ @@ -768,8 +791,8 @@ static int maweb_handle_fd(instance* inst){ static int maweb_set(instance* inst, size_t num, channel** c, channel_value* v){ maweb_instance_data* data = (maweb_instance_data*) inst->impl; + maweb_channel_data* chan = NULL; char xmit_buffer[MAWEB_XMIT_CHUNK]; - maweb_channel_ident ident; size_t n; if(num && !data->login){ @@ -778,8 +801,23 @@ static int maweb_set(instance* inst, size_t num, channel** c, channel_value* v){ } for(n = 0; n < num; n++){ - ident.label = c[n]->ident; - switch(ident.fields.type){ + //sanity check + if(c[n]->ident >= data->channels){ + return 1; + } + chan = data->channel + c[n]->ident; + + //channel state tracking + if(chan->out == v[n].normalised){ + continue; + } + chan->out = v[n].normalised; + + //i/o value space separation + chan->in = v[n].normalised; + chan->input_blocked = 1; + + switch(chan->type){ case exec_fader: snprintf(xmit_buffer, sizeof(xmit_buffer), "{\"requestType\":\"playbacks_userInput\"," @@ -788,8 +826,7 @@ static int maweb_set(instance* inst, size_t num, channel** c, channel_value* v){ "\"faderValue\":%f," "\"type\":1," "\"session\":%" PRIu64 - "}", ident.fields.index, ident.fields.page, v[n].normalised, data->session); - maweb_send_frame(inst, ws_text, (uint8_t*) xmit_buffer, strlen(xmit_buffer)); + "}", chan->index, chan->page, v[n].normalised, data->session); break; case exec_upper: case exec_lower: @@ -804,26 +841,26 @@ static int maweb_set(instance* inst, size_t num, channel** c, channel_value* v){ "\"released\":%s," "\"type\":0," "\"session\":%" PRIu64 - "}", ident.fields.index, ident.fields.page, - (data->peer_type == peer_dot2 && ident.fields.type == exec_upper) ? 0 : (ident.fields.type - exec_button), + "}", chan->index, chan->page, + (data->peer_type == peer_dot2 && chan->type == exec_upper) ? 0 : (chan->type - exec_button), (v[n].normalised > 0.9) ? "true" : "false", (v[n].normalised > 0.9) ? "false" : "true", data->session); - maweb_send_frame(inst, ws_text, (uint8_t*) xmit_buffer, strlen(xmit_buffer)); break; - case cmdline_button: + case cmdline: snprintf(xmit_buffer, sizeof(xmit_buffer), "{\"keyname\":\"%s\"," //"\"autoSubmit\":false," "\"value\":%d" - "}", cmdline_keys[ident.fields.index], + "}", cmdline_keys[chan->index], (v[n].normalised > 0.9) ? 1 : 0); - maweb_send_frame(inst, ws_text, (uint8_t*) xmit_buffer, strlen(xmit_buffer)); break; + //TODO cmdline_local default: fprintf(stderr, "maweb control not yet implemented\n"); - break; + return 1; } + maweb_send_frame(inst, ws_text, (uint8_t*) xmit_buffer, strlen(xmit_buffer)); } return 0; } @@ -912,7 +949,7 @@ 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); + qsort(data->channel, data->channels, sizeof(maweb_channel_data), channel_comparator); if(maweb_connect(inst[u])){ fprintf(stderr, "Failed to open connection to MA Web Remote for instance %s\n", inst[u]->name); @@ -964,9 +1001,9 @@ 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(data->channel); + data->channel = NULL; + data->channels = 0; } free(inst); diff --git a/backends/maweb.h b/backends/maweb.h index a868426..3738367 100644 --- a/backends/maweb.h +++ b/backends/maweb.h @@ -25,7 +25,8 @@ typedef enum /*_maweb_channel_type*/ { exec_button = 2, //gma: 0 dot: 0 exec_lower = 3, //gma: 1 dot: 1 exec_upper = 4, //gma: 2 dot: 0 - cmdline_button + cmdline, + cmdline_local } maweb_channel_type; typedef enum /*_maweb_peer_type*/ { @@ -49,15 +50,16 @@ typedef enum /*_ws_frame_op*/ { ws_pong = 10 } maweb_operation; -typedef union { - struct { - uint8_t padding[3]; - uint8_t type; - uint16_t page; - uint16_t index; - } fields; - uint64_t label; -} maweb_channel_ident; +typedef struct /*_maweb_channel*/ { + maweb_channel_type type; + uint16_t page; + uint16_t index; + + uint8_t input_blocked; + + double in; + double out; +} maweb_channel_data; typedef struct /*_maweb_instance_data*/ { char* host; @@ -69,9 +71,8 @@ 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; + size_t channels; + maweb_channel_data* channel; int fd; maweb_state state; -- cgit v1.2.3 From 079baff220a963c365ab8448c421e22e896caaf1 Mon Sep 17 00:00:00 2001 From: cbdev Date: Wed, 18 Sep 2019 23:44:36 +0200 Subject: Fix maweb command key handling --- backends/maweb.c | 189 +++++++++++++++++++++++++++++++---------------------- backends/maweb.h | 18 ++++- backends/maweb.md | 76 ++++++++++++++------- backends/winmidi.c | 3 +- 4 files changed, 181 insertions(+), 105 deletions(-) (limited to 'backends/maweb.h') diff --git a/backends/maweb.c b/backends/maweb.c index c88ca8c..be356d0 100644 --- a/backends/maweb.c +++ b/backends/maweb.c @@ -19,72 +19,57 @@ static uint64_t update_interval = 50; static uint64_t last_update = 0; static uint64_t updates_inflight = 0; -static char* cmdline_keys[] = { - "SET", - "PREV", - "NEXT", - "CLEAR", - "FIXTURE_CHANNEL", - "FIXTURE_GROUP_PRESET", - "EXEC_CUE", - "STORE_UPDATE", - "OOPS", - "ESC", - "0", - "1", - "2", - "3", - "4", - "5", - "6", - "7", - "8", - "9", - "PUNKT", - "PLUS", - "MINUS", - "THRU", - "IF", - "AT", - "FULL", - "HIGH", - "ENTER", - "OFF", - "ON", - "ASSIGN", - "LABEL", - "COPY", - "TIME", - "PAGE", - "MACRO", - "DELETE", - "GOTO", - "GO_PLUS", - "GO_MINUS", - "PAUSE", - "SELECT", - "FIXTURE", - "SEQU", - "CUE", - "PRESET", - "EDIT", - "UPDATE", - "EXEC", - "STORE", - "GROUP", - "PROG_ONLY", - "SPECIAL_DIALOGUE", - "SOLO", - "ODD", - "EVEN", - "WINGS", - "RESET", - "MA", - "layerMode", - "featureSort", - "fixtureSort", - "channelSort", - "hideName" +static maweb_command_key cmdline_keys[] = { + {"PREV", 109, 0, 1}, {"SET", 108, 1, 0, 1}, {"NEXT", 110, 0, 1}, + {"TIME", 58, 1, 1}, {"EDIT", 55, 1, 1}, {"UPDATE", 57, 1, 1}, + {"OOPS", 53, 1, 1}, {"ESC", 54, 1, 1}, {"CLEAR", 105, 1, 1}, + {"0", 86, 1, 1}, {"1", 87, 1, 1}, {"2", 88, 1, 1}, + {"3", 89, 1, 1}, {"4", 90, 1, 1}, {"5", 91, 1, 1}, + {"6", 92, 1, 1}, {"7", 93, 1, 1}, {"8", 94, 1, 1}, + {"9", 95, 1, 1}, {"PUNKT", 98, 1, 1}, {"ENTER", 106, 1, 1}, + {"PLUS", 96, 1, 1}, {"MINUS", 97, 1, 1}, {"THRU", 102, 1, 1}, + {"IF", 103, 1, 1}, {"AT", 104, 1, 1}, {"FULL", 99, 1, 1}, + {"MA", 68, 0, 1}, {"HIGH", 100, 1, 1, 1}, {"SOLO", 101, 1, 1, 1}, + {"SELECT", 42, 1, 1}, {"OFF", 43, 1, 1}, {"ON", 46, 1, 1}, + {"ASSIGN", 63, 1, 1}, {"LABEL", 0, 1, 1}, + {"COPY", 73, 1, 1}, {"DELETE", 69, 1, 1}, {"STORE", 59, 1, 1}, + {"GOTO", 56, 1, 1}, {"PAGE", 70, 1, 1}, {"MACRO", 71, 1, 1}, + {"PRESET", 72, 1, 1}, {"SEQU", 74, 1, 1}, {"CUE", 75, 1, 1}, + {"EXEC", 76, 1, 1}, {"FIXTURE", 83, 1, 1}, {"GROUP", 84, 1, 1}, + {"GO_MINUS", 10, 1, 1}, {"PAUSE", 9, 1, 1}, {"GO_PLUS", 11, 1, 1}, + + {"FIXTURE_CHANNEL", 0, 1, 1}, {"FIXTURE_GROUP_PRESET", 0, 1, 1}, + {"EXEC_CUE", 0, 1, 1}, {"STORE_UPDATE", 0, 1, 1}, {"PROG_ONLY", 0, 1, 1, 1}, + {"SPECIAL_DIALOGUE", 0, 1, 1}, + {"ODD", 0, 1, 1}, {"EVEN", 0, 1, 1}, + {"WINGS", 0, 1, 1}, {"RESET", 0, 1, 1}, + //gma2 internal only + {"CHPGPLUS", 3}, {"CHPGMINUS", 4}, + {"FDPGPLUS", 5}, {"FDPGMINUS", 6}, + {"BTPGPLUS", 7}, {"BTPGMINUS", 8}, + {"X1", 12}, {"X2", 13}, {"X3", 14}, + {"X4", 15}, {"X5", 16}, {"X6", 17}, + {"X7", 18}, {"X8", 19}, {"X9", 20}, + {"X10", 21}, {"X11", 22}, {"X12", 23}, + {"X13", 24}, {"X14", 25}, {"X15", 26}, + {"X16", 27}, {"X17", 28}, {"X18", 29}, + {"X19", 30}, {"X20", 31}, + {"V1", 120}, {"V2", 121}, {"V3", 122}, + {"V4", 123}, {"V5", 124}, {"V6", 125}, + {"V7", 126}, {"V8", 127}, {"V9", 128}, + {"V10", 129}, + {"NIPPLE", 40}, + {"TOOLS", 119}, {"SETUP", 117}, {"BACKUP", 117}, + {"BLIND", 60}, {"FREEZE", 61}, {"PREVIEW", 62}, + {"FIX", 41}, {"TEMP", 44}, {"TOP", 45}, + {"VIEW", 66}, {"EFFECT", 67}, {"CHANNEL", 82}, + {"MOVE", 85}, {"BLACKOUT", 65}, + {"PLEASE", 106}, + {"LIST", 32}, {"USER1", 33}, {"USER2", 34}, + {"ALIGN", 64}, {"HELP", 116}, + {"UP", 107}, {"DOWN", 111}, + {"FASTREVERSE", 47}, {"LEARN", 48}, {"FASTFORWARD", 49}, + {"GO_MINUS_SMALL", 50}, {"PAUSE_SMALL", 51}, {"GO_PLUS_SMALL", 52} }; int init(){ @@ -199,6 +184,22 @@ static int maweb_configure_instance(instance* inst, char* option, char* value){ return 1; #endif } + else if(!strcmp(option, "cmdline")){ + if(!strcmp(value, "console")){ + data->cmdline = cmd_console; + } + else if(!strcmp(value, "remote")){ + data->cmdline = cmd_remote; + } + else if(!strcmp(value, "downgrade")){ + data->cmdline = cmd_downgrade; + } + else{ + fprintf(stderr, "Unknown maweb commandline mode %s for instance %s\n", value, inst->name); + return 1; + } + return 0; + } fprintf(stderr, "Unknown configuration parameter %s for maweb instance %s\n", option, inst->name); return 1; @@ -268,10 +269,15 @@ static channel* maweb_channel(instance* inst, char* spec){ chan.index = strtoul(next_token, NULL, 10); } else{ - for(n = 0; n < sizeof(cmdline_keys) / sizeof(char*); n++){ - //FIXME this is broken for layerMode - if(!strcmp(spec, cmdline_keys[n]) || (*spec == 'l' && !strcmp(spec + 1, cmdline_keys[n]))){ - chan.type = (*spec == 'l') ? cmdline_local : cmdline; + for(n = 0; n < sizeof(cmdline_keys) / sizeof(maweb_command_key); n++){ + if(!strcmp(spec, cmdline_keys[n].name)){ + if((data->cmdline == cmd_remote && !cmdline_keys[n].press && !cmdline_keys[n].release) + || (data->cmdline == cmd_console && !cmdline_keys[n].lua)){ + fprintf(stderr, "maweb cmdline key %s does not work with the current commandline mode for instance %s\n", spec, inst->name); + return NULL; + } + + chan.type = cmdline; chan.index = n + 1; chan.page = 1; break; @@ -599,6 +605,8 @@ static int maweb_handle_message(instance* inst, char* payload, size_t payload_le field = json_obj_str(payload, "appType", NULL); if(!strncmp(field, "dot2", 4)){ data->peer_type = peer_dot2; + //the dot2 can't handle lua commands + data->cmdline = cmd_remote; } else if(!strncmp(field, "gma2", 4)){ data->peer_type = peer_ma2; @@ -860,14 +868,41 @@ static int maweb_set(instance* inst, size_t num, channel** c, channel_value* v){ data->session); break; case cmdline: - snprintf(xmit_buffer, sizeof(xmit_buffer), - "{\"keyname\":\"%s\"," - //"\"autoSubmit\":false," - "\"value\":%d" - "}", cmdline_keys[chan->index], - (v[n].normalised > 0.9) ? 1 : 0); + if(cmdline_keys[chan->index].lua + && (data->cmdline == cmd_console || data->cmdline == cmd_downgrade) + && data->peer_type != peer_dot2){ + //push canbus events + snprintf(xmit_buffer, sizeof(xmit_buffer), + "{\"command\":\"LUA 'gma.canbus.hardkey(%d, %s, false)'\"," + "\"requestType\":\"command\"," + "\"session\":%" PRIu64 + "}", cmdline_keys[chan->index].lua, + (v[n].normalised > 0.9) ? "true" : "false", + data->session); + } + else if((cmdline_keys[chan->index].press || cmdline_keys[chan->index].release) + && (data->cmdline != cmd_console)){ + //send press/release events if required + if((cmdline_keys[chan->index].press && v[n].normalised > 0.9) + || (cmdline_keys[chan->index].release && v[n].normalised < 0.9)){ + snprintf(xmit_buffer, sizeof(xmit_buffer), + "{\"keyname\":\"%s\"," + "\"autoSubmit\":%s," + "\"value\":%d" + "}", cmdline_keys[chan->index].name, + cmdline_keys[chan->index].auto_submit ? "true" : "null", + (v[n].normalised > 0.9) ? 1 : 0); + } + else{ + continue; + } + } + else{ + fprintf(stderr, "maweb commandline key %s not executed on %s due to mode mismatch\n", + cmdline_keys[chan->index].name, inst->name); + continue; + } break; - //TODO cmdline_local default: fprintf(stderr, "maweb control not yet implemented\n"); return 1; diff --git a/backends/maweb.h b/backends/maweb.h index 3738367..b9de68f 100644 --- a/backends/maweb.h +++ b/backends/maweb.h @@ -25,8 +25,7 @@ typedef enum /*_maweb_channel_type*/ { exec_button = 2, //gma: 0 dot: 0 exec_lower = 3, //gma: 1 dot: 1 exec_upper = 4, //gma: 2 dot: 0 - cmdline, - cmdline_local + cmdline } maweb_channel_type; typedef enum /*_maweb_peer_type*/ { @@ -43,6 +42,12 @@ typedef enum /*_ws_conn_state*/ { ws_closed } maweb_state; +typedef enum /*_maweb_cmdline_mode*/ { + cmd_remote = 0, + cmd_console, + cmd_downgrade +} maweb_cmdline_mode; + typedef enum /*_ws_frame_op*/ { ws_text = 1, ws_binary = 2, @@ -50,6 +55,14 @@ typedef enum /*_ws_frame_op*/ { ws_pong = 10 } maweb_operation; +typedef struct { + char* name; + unsigned lua; + uint8_t press; + uint8_t release; + uint8_t auto_submit; +} maweb_command_key; + typedef struct /*_maweb_channel*/ { maweb_channel_type type; uint16_t page; @@ -73,6 +86,7 @@ typedef struct /*_maweb_instance_data*/ { size_t channels; maweb_channel_data* channel; + maweb_cmdline_mode cmdline; int fd; maweb_state state; diff --git a/backends/maweb.md b/backends/maweb.md index 93cd776..a18cde4 100644 --- a/backends/maweb.md +++ b/backends/maweb.md @@ -26,6 +26,14 @@ Web Remote. Set a web remote password using the option below the activation sett | `host` | `10.23.42.21 80` | none | Host address (and optional port) of the MA Web Remote | | `user` | `midimonster` | none | User for the remote session (GrandMA2) | | `password` | `midimonster` | `midimonster` | Password for the remote session | +| `cmdline` | `console` | `remote` | Commandline key handling mode (see below) | + +The per-instance command line mode may be one of `remote`, `console` or `downgrade`. The first option handles +command keys with a "virtual" commandline belonging to the Web Remote connection. Any commands entered are +not visible on the main console. The `console` mode is only available with GrandMA2 remotes and injects the +key events into the main console's command line. When connected to a dot2 console, the use of commandline keys +will not be possible. With the `downgrade` mode, keys are handled on the console if possible, falling back to +remote handling if not. #### Channel specification @@ -33,7 +41,7 @@ Currently, three types of MA controls can be assigned, with each having some sub * Fader executor * Button executor -* Command line buttons +* Command keys ##### Executors @@ -70,33 +78,52 @@ mw1.page2.button103 > mw1.page3.fader101 mw1.page2.button803 > mw1.page3.button516 ``` -##### Command line buttons +##### Command keys -Command line buttons will be pressed when the incoming event value is greater than `0.9` and released when it is less than that. +Command keys will be pressed when the incoming event value is greater than `0.9` and released when it is less than that. They can be mapped using the syntax ``` -mw1. +mw1. ``` -The following button names are recognized by the backend: - -| Supported | Command | Line | Keys | | | | -|---------------|---------------|---------------|---------------|-----------------------|-------------------------------|---------------| -| `SET` | `PREV` | `NEXT` | `CLEAR` | `FIXTURE_CHANNEL` | `FIXTURE_GROUP_PRESET` | `EXEC_CUE` | -| `STORE_UPDATE`| `OOPS` | `ESC` | `OFF` | `ON` | `MA` | `STORE` | -| `0` | `1` | `2` | `3` | `4` | `5` | `6` | -| `7` | `8` | `9` | `PUNKT` | `PLUS` | `MINUS` | `THRU` | -| `IF` | `AT` | `FULL` | `HIGH` | `ENTER` | `ASSIGN` | `LABEL` | -| `COPY` | `TIME` | `PAGE` | `MACRO` | `DELETE` | `GOTO` | `GO_PLUS` | -| `GO_MINUS` | `PAUSE` | `SELECT` | `FIXTURE` | `SEQU` | `CUE` | `PRESET` | -| `EDIT` | `UPDATE` | `EXEC` | `GROUP` | `PROG_ONLY` | `SPECIAL_DIALOGUE` | `SOLO` | -| `ODD` | `EVEN` | `WINGS` | `RESET` | `layerMode` | `featureSort` | `fixtureSort` | -| `channelSort` | `hideName` | | | | | | - -Note that each Web Remote connection has it's own command line, as such commands entered using this backend will not affect -the command line on the main console. To do that, you will need to use another backend to feed input to the MA, such as -the ArtNet or MIDI backends. +The following keys are mappable in all commandline modes and work on all consoles + +| Supported | Command | Line | Keys | | | +|---------------|---------------|---------------|---------------|---------------|---------------| +| `PREV` | `SET` | `NEXT` | `TIME` | `EDIT` | `UPDATE` | +| `OOPS` | `ESC` | `CLEAR` | `0` | `1` | `2` | +| `3` | `4` | `5` | `6` | `7` | `8` | +| `9` | `PUNKT` | `ENTER` | `PLUS` | `MINUS` | `THRU` | +| `IF` | `AT` | `FULL` | `MA` | `HIGH` | `SOLO` | +| `SELECT` | `OFF` | `ON` | `ASSIGN` | `COPY` | `DELETE` | +| `STORE` | `GOTO` | `PAGE` | `MACRO` | `PRESET` | `SEQU` | +| `CUE` | `EXEC` | `FIXTURE` | `GROUP` | `GO_MINUS` | `PAUSE` | +| `GO_PLUS` | | | | | | + +The following keys only work in the `remote` or `downgrade` commandline mode, but on all consoles + +| Web | Remote | specific | | | +|---------------|-----------------------|-------------------------------|---------------|-----------------------| +| `LABEL` |`FIXTURE_CHANNEL` | `FIXTURE_GROUP_PRESET` | `EXEC_CUE` | `STORE_UPDATE` | +| `PROG_ONLY` | `SPECIAL_DIALOGUE` | `ODD` | `EVEN` | `WINGS` | +| `RESET` | | | | | + +The following keys only work in the `console` or `downgrade` command line modes on a GrandMA2 + +| GrandMA2 | console | only | | | | +|---------------|---------------|---------------|---------------|---------------|---------------| +| `CHPGPLUS` | `CHPGMINUS` | `FDPGPLUS` | `FDPGMINUS` | `BTPGPLUS` | `BTPGMINUS` | +| `X1` | `X2` | `X3` | `X4` | `X5` | `X6` | +| `X7` | `X8` | `X9` | `X10` | `X11` | `X12` | +| `X13` | `X14` | `X15` | `X16` | `X17` | `X18` | +| `X19` | `X20` | `V1` | `V2` | `V3` | `V4` | +| `V5` | `V6` | `V7` | `V8` | `V9` | `V10` | +| `NIPPLE` | `TOOLS` | `SETUP` | `BACKUP` | `BLIND` | `FREEZE` | +| `PREVIEW` | `FIX` | `TEMP` | `TOP` | `VIEW` | `EFFECT` | +| `CHANNEL` | `MOVE` | `BLACKOUT` | `PLEASE` | `LIST` | `USER1` | +| `USER2` | `ALIGN` | `HELP` | `UP` | `DOWN` | `FASTREVERSE` | +| `LEARN` | `FASTFORWARD` | `GO_MINUS_SMALL` | `PAUSE_SMALL` | `GO_PLUS_SMALL` | | #### Known bugs / problems @@ -109,6 +136,5 @@ Data input from the console is done by actively querying the state of all mapped 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. -When requesting button executor events on the fader pages (execs 101 to 222) of a dot2 console, map at least one fader control from the 0 - 22 range or input will not work due to strange limitations in the MA Web API. - -Command line events are sent, but I'm not sure they're being handled yet. +When requesting button executor events on the fader pages (execs 101 to 222) of a dot2 console, map at least one fader control from the 0 - 22 range +or input will not work due to strange limitations in the MA Web API. diff --git a/backends/winmidi.c b/backends/winmidi.c index 67187ac..13c4b4a 100644 --- a/backends/winmidi.c +++ b/backends/winmidi.c @@ -21,6 +21,7 @@ static struct { }; //TODO detect option +//TODO receive feedback socket until EAGAIN int init(){ backend winmidi = { @@ -465,7 +466,7 @@ static int winmidi_start(){ } //open the feedback sockets - //for some reason this while construct fails to work on 'real' windows with ipv6 + //for some reason the feedback connection fails to work on 'real' windows with ipv6 backend_config.socket_pair[0] = mmbackend_socket("127.0.0.1", "0", SOCK_DGRAM, 1, 0); if(backend_config.socket_pair[0] < 0){ fprintf(stderr, "winmidi failed to open feedback socket\n"); -- cgit v1.2.3 From e556b1719a8906c14d329eb3c08574428cad0aae Mon Sep 17 00:00:00 2001 From: cbdev Date: Fri, 20 Sep 2019 17:58:27 +0200 Subject: Fix maweb channel ordering --- backends/maweb.c | 14 ++++++++++++-- backends/maweb.h | 4 ++++ 2 files changed, 16 insertions(+), 2 deletions(-) (limited to 'backends/maweb.h') diff --git a/backends/maweb.c b/backends/maweb.c index 450c388..baa516a 100644 --- a/backends/maweb.c +++ b/backends/maweb.c @@ -5,6 +5,7 @@ #include #endif +#define DEBUG #include "libmmbackend.h" #include "maweb.h" @@ -235,6 +236,7 @@ static channel* maweb_channel(instance* inst, char* spec){ 0 }; char* next_token = NULL; + channel* channel_ref = NULL; size_t n; if(!strncmp(spec, "page", 4)){ @@ -299,7 +301,9 @@ static channel* maweb_channel(instance* inst, char* spec){ data->channels++; } - return mm_channel(inst, maweb_channel_index(data, chan.type, chan.page, chan.index), 1); + channel_ref = mm_channel(inst, maweb_channel_index(data, chan.type, chan.page, chan.index), 1); + data->channel[maweb_channel_index(data, chan.type, chan.page, chan.index)].chan = channel_ref; + return channel_ref; } fprintf(stderr, "Failed to parse maweb channel spec %s\n", spec); @@ -906,6 +910,7 @@ static int maweb_set(instance* inst, size_t num, channel** c, channel_value* v){ fprintf(stderr, "maweb control not yet implemented\n"); return 1; } + DBGPF("maweb command out %s\n", xmit_buffer); maweb_send_frame(inst, ws_text, (uint8_t*) xmit_buffer, strlen(xmit_buffer)); } return 0; @@ -982,7 +987,7 @@ static int maweb_handle(size_t num, managed_fd* fds){ } static int maweb_start(){ - size_t n, u; + size_t n, u, p; instance** inst = NULL; maweb_instance_data* data = NULL; @@ -997,6 +1002,11 @@ static int maweb_start(){ data = (maweb_instance_data*) inst[u]->impl; qsort(data->channel, data->channels, sizeof(maweb_channel_data), channel_comparator); + //re-set channel identifiers + for(p = 0; p < data->channels; p++){ + data->channel[p].chan->ident = p; + } + if(maweb_connect(inst[u])){ fprintf(stderr, "Failed to open connection to MA Web Remote for instance %s\n", inst[u]->name); free(inst); diff --git a/backends/maweb.h b/backends/maweb.h index b9de68f..14e4755 100644 --- a/backends/maweb.h +++ b/backends/maweb.h @@ -72,6 +72,10 @@ typedef struct /*_maweb_channel*/ { double in; double out; + + //reverse reference required because the identifiers are not stable + //because we sort the backing store... + channel* chan; } maweb_channel_data; typedef struct /*_maweb_instance_data*/ { -- 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/maweb.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/maweb.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