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.md | 142 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 142 insertions(+) create mode 100644 backends/maweb.md (limited to 'backends/maweb.md') diff --git a/backends/maweb.md b/backends/maweb.md new file mode 100644 index 0000000..eb1ed44 --- /dev/null +++ b/backends/maweb.md @@ -0,0 +1,142 @@ +### The `maweb` backend + +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. + +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`. +Create an additional user that is able to log into the Web Remote using `Setup`/`Console`/`User & Profiles Setup`. + +#### Global configuration + +The `maweb` backend does not take any global configuration. + +#### Instance configuration + +| Option | Example value | Default value | Description | +|---------------|-----------------------|-----------------------|---------------------------------------------------------------| +| `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 | +| `password` | `midimonster` | `midimonster` | Password for the remote session | + +#### Channel specification + +Currently, three types of channels can be assigned + +##### Executors + +Executors are arranged in pages, with each page having 90 fader executors and 90 button executors. +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. + +These controls can be adressed like + +``` +mw1.page1.fader5 > mw1.page1.upper5 +mw1.page3.lower3 > mw1.page2.flash2 +``` + +A button executor can likewise be mapped using the syntax + +``` +mw1.page2.button3 > mw1.page3.fader1 +``` + +##### Command line buttons + +Command line buttons 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. +``` + +The following button names are recognized by the backend: + +* `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` + +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. + +#### Known bugs / problems + +To properly encode the user password, this backend depends on a library providing cryptographic functions (`libssl` / `openssl`). +Since this may be a problem on some platforms, the backend can be built with this requirement disabled, which also disables the possibility +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. + +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 consumed 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 -- cgit v1.2.3 From cc3aa7d8c1d680e75374a0c296a34d66a919f201 Mon Sep 17 00:00:00 2001 From: cbdev Date: Sun, 11 Aug 2019 20:29:17 +0200 Subject: Minor text fix --- backends/maweb.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'backends/maweb.md') diff --git a/backends/maweb.md b/backends/maweb.md index eb1ed44..3a7372c 100644 --- a/backends/maweb.md +++ b/backends/maweb.md @@ -42,7 +42,7 @@ mw1.page3.lower3 > mw1.page2.flash2 A button executor can likewise be mapped using the syntax ``` -mw1.page2.button3 > mw1.page3.fader1 +mw1.page2.button3 > mw1.page3.button1 ``` ##### Command line buttons @@ -136,7 +136,7 @@ configurable. 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 consumed by it +* 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 -- cgit v1.2.3 From bad0fdac1e725b4b2efbbbfff4cef74c2e05efb8 Mon Sep 17 00:00:00 2001 From: cbdev Date: Mon, 12 Aug 2019 20:58:04 +0200 Subject: Fix maweb button execs --- backends/maweb.c | 27 ++++++++++++++++++++++++++- backends/maweb.md | 6 ++++-- monster.cfg | 2 +- 3 files changed, 31 insertions(+), 4 deletions(-) (limited to 'backends/maweb.md') diff --git a/backends/maweb.c b/backends/maweb.c index be4c2ac..38d3d69 100644 --- a/backends/maweb.c +++ b/backends/maweb.c @@ -218,10 +218,15 @@ static channel* maweb_channel(instance* inst, char* spec){ next_token += 5; } else if(!strncmp(next_token, "button", 6)){ - ident.fields.type = exec_fader; + ident.fields.type = exec_button; next_token += 6; } ident.fields.index = strtoul(next_token, NULL, 10); + + //fix up the identifiers for button execs + if(ident.fields.index > 100){ + ident.fields.index -= 100; + } } else{ for(n = 0; n < sizeof(cmdline_keys) / sizeof(char*); n++){ @@ -568,6 +573,26 @@ static int maweb_set(instance* inst, size_t num, channel** c, channel_value* v){ 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\"," + //"\"cmdline\":\"\"," + "\"execIndex\":%d," + "\"pageIndex\":%d," + "\"buttonId\":%d," + "\"pressed\":%s," + "\"released\":%s," + "\"type\":0," + "\"session\":%ld" + "}", ident.fields.index + 100, + ident.fields.page, + 0, + (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: snprintf(xmit_buffer, sizeof(xmit_buffer), "{\"keyname\":\"%s\"," diff --git a/backends/maweb.md b/backends/maweb.md index 3a7372c..096f69c 100644 --- a/backends/maweb.md +++ b/backends/maweb.md @@ -27,7 +27,9 @@ Currently, three types of channels can be assigned ##### Executors -Executors are arranged in pages, with each page having 90 fader executors and 90 button 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. @@ -42,7 +44,7 @@ mw1.page3.lower3 > mw1.page2.flash2 A button executor can likewise be mapped using the syntax ``` -mw1.page2.button3 > mw1.page3.button1 +mw1.page2.button103 > mw1.page3.button101 ``` ##### Command line buttons diff --git a/monster.cfg b/monster.cfg index d272cee..e6258a7 100644 --- a/monster.cfg +++ b/monster.cfg @@ -36,4 +36,4 @@ bcf.channel{0..7}.pitch > ma.page1.fader{1..8} bcf.channel0.note{16..23} > ma.page1.upper{1..8} bcf.channel0.note{24..31} > ma.page1.lower{1..8} mouse.EV_REL.REL_Y > ma.page1.fader1 -mouse.EV_KEY.BTN_LEFT > ma.ASSIGN +mouse.EV_KEY.BTN_LEFT > ma.page2.button102 -- cgit v1.2.3 From 047c76b48d4dd72b68ffaf99a210f9a3d5460f71 Mon Sep 17 00:00:00 2001 From: cbdev Date: Mon, 12 Aug 2019 20:59:48 +0200 Subject: Placate spellintian... --- backends/maweb.c | 2 +- backends/maweb.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) (limited to 'backends/maweb.md') diff --git a/backends/maweb.c b/backends/maweb.c index 38d3d69..b708015 100644 --- a/backends/maweb.c +++ b/backends/maweb.c @@ -355,7 +355,7 @@ static int maweb_connect(instance* inst){ || mmbackend_send_str(data->fd, "Connection: Upgrade\r\n") || mmbackend_send_str(data->fd, "Upgrade: websocket\r\n") || mmbackend_send_str(data->fd, "Sec-WebSocket-Version: 13\r\n") - //the websocket key probably should not be hardcoded, but this is not security criticial + //the websocket key probably should not be hardcoded, but this is not security critical //and the whole websocket 'accept key' dance is plenty stupid as it is || mmbackend_send_str(data->fd, "Sec-WebSocket-Key: rbEQrXMEvCm4ZUjkj6juBQ==\r\n") || mmbackend_send_str(data->fd, "\r\n")){ diff --git a/backends/maweb.md b/backends/maweb.md index 096f69c..b54f1c3 100644 --- a/backends/maweb.md +++ b/backends/maweb.md @@ -34,7 +34,7 @@ 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. -These controls can be adressed like +These controls can be addressed like ``` mw1.page1.fader5 > mw1.page1.upper5 -- cgit v1.2.3 From c2bf894835d01c648e3f64826e69944a2a27373f Mon Sep 17 00:00:00 2001 From: cbdev Date: Thu, 15 Aug 2019 10:55:23 +0200 Subject: Fix CI, add dot2 detection to maweb --- .travis.yml | 3 +++ backends/maweb.c | 7 +++++ backends/maweb.md | 81 +++++++++---------------------------------------------- 3 files changed, 23 insertions(+), 68 deletions(-) (limited to 'backends/maweb.md') diff --git a/.travis.yml b/.travis.yml index 74af4c3..8d21ef3 100644 --- a/.travis.yml +++ b/.travis.yml @@ -123,6 +123,9 @@ matrix: - os: linux dist: xenial env: TASK='spellintian-duplicates' + - os: linux + dist: xenial + env: TASK='codespell' env: global: diff --git a/backends/maweb.c b/backends/maweb.c index b708015..07fce12 100644 --- a/backends/maweb.c +++ b/backends/maweb.c @@ -303,6 +303,12 @@ static int maweb_handle_message(instance* inst, char* payload, size_t payload_le if(json_obj(payload, "status") && json_obj(payload, "appType")){ 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"); + } maweb_send_frame(inst, ws_text, (uint8_t*) "{\"session\":0}", 13); } @@ -535,6 +541,7 @@ static int maweb_set(instance* inst, size_t num, channel** c, channel_value* v){ if(num && !data->login){ fprintf(stderr, "maweb instance %s can not send output, not logged in\n", inst->name); + return 0; } for(n = 0; n < num; n++){ diff --git a/backends/maweb.md b/backends/maweb.md index b54f1c3..dd13db9 100644 --- a/backends/maweb.md +++ b/backends/maweb.md @@ -15,10 +15,10 @@ The `maweb` backend does not take any global configuration. #### Instance configuration -| Option | Example value | Default value | Description | +| Option | Example value | Default value | Description | |---------------|-----------------------|-----------------------|---------------------------------------------------------------| -| `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 | +| `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 | #### Channel specification @@ -58,71 +58,16 @@ mw1. The following button names are recognized by the backend: -* `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` +| `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 -- 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.md') 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 25d168454d53bb990118825f9bca808876aa4d59 Mon Sep 17 00:00:00 2001 From: cbdev Date: Sat, 17 Aug 2019 00:01:43 +0200 Subject: Hopefully clarify text a bit --- backends/maweb.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'backends/maweb.md') diff --git a/backends/maweb.md b/backends/maweb.md index 6076f44..d713d82 100644 --- a/backends/maweb.md +++ b/backends/maweb.md @@ -37,7 +37,7 @@ Currently, three types of channels can be assigned * 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 + * 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. -- 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.md') 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 6464a418f2bf13c8c04b10abf5ebdc4c95aae861 Mon Sep 17 00:00:00 2001 From: cbdev Date: Thu, 22 Aug 2019 22:28:25 +0200 Subject: Try to fix failing build by providing additional hints to MacOS --- .travis.yml | 3 +++ backends/maweb.md | 4 ++-- 2 files changed, 5 insertions(+), 2 deletions(-) (limited to 'backends/maweb.md') diff --git a/.travis.yml b/.travis.yml index 305dc61..9f2034c 100644 --- a/.travis.yml +++ b/.travis.yml @@ -164,6 +164,9 @@ install: before_install: - if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then brew update; fi - if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then brew install ccache ola lua openssl; fi +# OpenSSL is not a proper install due to some Apple bull, so provide additional locations via the environment... + - export CPPFLAGS="$CPPFLAGS -I/usr/local/opt/openssl/include" + - export LDFLAGS="$LDFLAGS -L/usr/local/opt/openssl/lib" - if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then PATH=/usr/local/opt/ccache/libexec:$PATH; fi # Use ccache on Mac too #Coverity doesn't work with g++ 5 or 6, so only upgrade to g++ 4.9 for that - if [ "$TRAVIS_OS_NAME" == "linux" -a \( "$TASK" = "compile" -o "$TASK" = "sanitize" \) -a "$CC" = "gcc" ]; then export CC="ccache gcc-8"; export CXX="ccache g++-8"; fi diff --git a/backends/maweb.md b/backends/maweb.md index fe430db..f3b40b6 100644 --- a/backends/maweb.md +++ b/backends/maweb.md @@ -53,8 +53,8 @@ Currently, three types of MA controls can be assigned, with each having some sub * 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. `fader` controls, when mapped as outputs from the MA, output their value, `button` controls -output 1 when the corresponding executor is running, 0 otherwise. +the console before being usable. When mapped as outputs, `fader` controls output their value, `button` controls output 1 when the corresponding +executor is running, 0 otherwise. These controls can be addressed like -- cgit v1.2.3 From b4b27aa4a90899d5b025bbef11d444d4c47800d9 Mon Sep 17 00:00:00 2001 From: cbdev Date: Fri, 23 Aug 2019 19:47:53 +0200 Subject: Finalize & publish maweb backend --- Makefile | 2 +- README.md | 2 ++ backends/maweb.c | 92 ++++++++++++++++++++++++++++++++----------------------- backends/maweb.md | 6 +--- 4 files changed, 58 insertions(+), 44 deletions(-) (limited to 'backends/maweb.md') diff --git a/Makefile b/Makefile index 57fe089..2d88c49 100644 --- a/Makefile +++ b/Makefile @@ -56,5 +56,5 @@ run: valgrind --leak-check=full --show-leak-kinds=all ./midimonster sanitize: export CC = clang -sanitize: export CFLAGS = -g -Wall -Wpedantic -fsanitize=address -fsanitize=undefined -fno-omit-frame-pointer +sanitize: export CFLAGS += -g -Wall -Wpedantic -fsanitize=address -fsanitize=undefined -fno-omit-frame-pointer sanitize: midimonster backends diff --git a/README.md b/README.md index 40f8fbb..5c4233f 100644 --- a/README.md +++ b/README.md @@ -11,6 +11,7 @@ Currently, the MIDIMonster supports the following protocols: * OpenSoundControl (OSC) * evdev input devices (Linux) * Open Lighting Architecture (OLA) +* MA Lighting Web Remote with additional flexibility provided by a Lua scripting environment. @@ -121,6 +122,7 @@ special information. These documentation files are located in the `backends/` di * [`ola` backend documentation](backends/ola.md) * [`osc` backend documentation](backends/osc.md) * [`lua` backend documentation](backends/lua.md) +* [`maweb` backend documentation](backends/maweb.md) ## Building diff --git a/backends/maweb.c b/backends/maweb.c index 9ba04fa..4e6fad1 100644 --- a/backends/maweb.c +++ b/backends/maweb.c @@ -266,14 +266,17 @@ static channel* maweb_channel(instance* inst, char* spec){ ident.fields.index--; ident.fields.page--; - //check if the channel is already known + //check if the (exec/meta) channel is already known for(n = 0; n < data->input_channels; n++){ - if(data->input_channel[n].label == ident.label){ + if(data->input_channel[n].fields.page == ident.fields.page + && data->input_channel[n].fields.index == ident.fields.index){ break; } } - if(n == data->input_channels){ + //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){ fprintf(stderr, "Failed to allocate memory\n"); @@ -324,9 +327,9 @@ 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){ size_t exec_blocks = json_obj_offset(payload, (metatype == 2) ? "executorBlocks" : "bottomButtons"), offset, block = 0, control; channel* chan = NULL; - channel_value evt; + channel_value evt; maweb_channel_ident ident = { - .fields.page = page, + .fields.page = page - 1, .fields.index = json_obj_int(payload, "iExec", 191) }; @@ -335,10 +338,11 @@ static int maweb_process_playback(instance* inst, int64_t page, maweb_channel_ty //ignore unused buttons return 0; } - fprintf(stderr, "maweb missing exec block data on exec %d\n", ident.fields.index); + fprintf(stderr, "maweb missing exec block data on exec %ld.%d\n", page, ident.fields.index); return 1; } + //the bottomButtons key has an additional subentry if(metatype == 3){ exec_blocks += json_obj_offset(payload + exec_blocks, "items"); } @@ -363,7 +367,7 @@ static int maweb_process_playback(instance* inst, int64_t page, maweb_channel_ty 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)); + DBGPF("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++; } @@ -416,7 +420,7 @@ static int maweb_process_playbacks(instance* inst, int64_t page, char* payload, group++; } updates_inflight--; - fprintf(stderr, "maweb playback message processing done, %lu updates inflight\n", updates_inflight); + DBGPF("maweb playback message processing done, %lu updates inflight\n", updates_inflight); return 0; } @@ -425,45 +429,53 @@ static int maweb_request_playbacks(instance* inst){ 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; + char item_indices[1024] = "[300,400,500]", item_counts[1024] = "[16,16,16]", item_types[1024] = "[3,3,3]"; + size_t page_index = 0, view = 3, 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; } + //don't quote me on this whole segment for(channel = 0; channel < data->input_channels; channel++){ - offsets[0] = offsets[1] = offsets[2] = 0; + offsets[0] = offsets[1] = offsets[2] = 1; 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; + //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; + + for(channel_offset = 1; channel + channel_offset <= data->input_channels; 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)){ + 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)){ + break; + } + } + + //terminate arrays (overwriting the last array separator) + offsets[0] += snprintf(item_indices + offsets[0] - 1, sizeof(item_indices) - offsets[0], "]"); + offsets[1] += snprintf(item_counts + offsets[1] - 1, sizeof(item_counts) - offsets[1], "]"); + offsets[2] += snprintf(item_types + offsets[2] - 1, sizeof(item_types) - offsets[2], "]"); } 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; - //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); @@ -476,8 +488,12 @@ static int maweb_request_playbacks(instance* inst){ 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; } + + //advance base channel + channel += channel_offset - 1; + + //send current request snprintf(xmit_buffer, sizeof(xmit_buffer), "{" "\"requestType\":\"playbacks\"," @@ -497,7 +513,7 @@ static int maweb_request_playbacks(instance* inst){ view, data->session); rv |= maweb_send_frame(inst, ws_text, (uint8_t*) xmit_buffer, strlen(xmit_buffer)); - //fprintf(stderr, "req: %s\n", xmit_buffer); + DBGPF("maweb poll request: %s\n", xmit_buffer); updates_inflight++; } @@ -530,7 +546,7 @@ static int maweb_handle_message(instance* inst, char* payload, size_t payload_le } } - fprintf(stderr, "maweb message (%lu): %s\n", payload_length, payload); + DBGPF("maweb message (%lu): %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 %ld\n", data->session); diff --git a/backends/maweb.md b/backends/maweb.md index f3b40b6..9fe4790 100644 --- a/backends/maweb.md +++ b/backends/maweb.md @@ -109,8 +109,4 @@ 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. -This backend is currently in active development. It therefore has some limitations: - -* It outputs a lot of debug information -* Command line events are sent, but I'm not sure they're being handled yet -* For the dot2, currently only the Core & F-Wings are supported for input from the console, not the B-Wings +Command line events are sent, but I'm not sure they're being handled yet. -- cgit v1.2.3 From 191949152d803229275d7b98b184ff722a9549a2 Mon Sep 17 00:00:00 2001 From: cbdev Date: Sat, 31 Aug 2019 17:23:22 +0200 Subject: Update documentation --- README.md | 11 +++++++++-- backends/maweb.md | 2 ++ backends/midi.md | 3 +++ 3 files changed, 14 insertions(+), 2 deletions(-) (limited to 'backends/maweb.md') diff --git a/README.md b/README.md index 5c4233f..cf4d8d1 100644 --- a/README.md +++ b/README.md @@ -143,12 +143,19 @@ support for the protocols to translate. * A C compiler * GNUmake +To build for Windows, the package `mingw-w64` provides a cross-compiler that can +be used to build a subset of the backends as well as the core. + ### Build -Just running `make` in the source directory should do the trick. +For Linux and OSX, just running `make` in the source directory should do the trick. Some backends have been marked as optional as they require rather large additional software to be installed, -for example the `ola` backend. To build these, run `make full` in the backends directory. +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. ## Development diff --git a/backends/maweb.md b/backends/maweb.md index 9fe4790..93cd776 100644 --- a/backends/maweb.md +++ b/backends/maweb.md @@ -109,4 +109,6 @@ 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. diff --git a/backends/midi.md b/backends/midi.md index 5e295ee..108860e 100644 --- a/backends/midi.md +++ b/backends/midi.md @@ -52,6 +52,9 @@ midi1.ch0.pitch > midi2.ch1.pitch ``` #### Known bugs / problems +To access MIDI data, the user running MIDIMonster needs read & write access to the ALSA sequencer. +This can usually be done by adding this user to the `audio` system group. + Currently, no Note Off messages are sent (instead, Note On messages with a velocity of 0 are generated, which amount to the same thing according to the spec). This may be implemented as a configuration option at a later time. -- 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.md') 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 fe4a7b538b9b6c53299d883bee258e4d3597be9d Mon Sep 17 00:00:00 2001 From: cbdev Date: Fri, 20 Sep 2019 03:43:13 +0200 Subject: Update documentation, fix minor leaks --- backends/maweb.c | 1 - backends/maweb.md | 13 +++++++------ backends/midi.c | 4 ++-- 3 files changed, 9 insertions(+), 9 deletions(-) (limited to 'backends/maweb.md') diff --git a/backends/maweb.c b/backends/maweb.c index be356d0..450c388 100644 --- a/backends/maweb.c +++ b/backends/maweb.c @@ -121,7 +121,6 @@ static int channel_comparator(const void* raw_a, const void* raw_b){ return a->index - b->index; } return a->type - b->type; - } //if either one is not an exec, sort by type first, index second if(a->type != b->type){ diff --git a/backends/maweb.md b/backends/maweb.md index a18cde4..5b996a9 100644 --- a/backends/maweb.md +++ b/backends/maweb.md @@ -2,7 +2,8 @@ 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 controls as well as write access to the command line. +It grants read-write access to the console's playback controls as well as write access to most command +line and control keys. #### Setting up the console @@ -30,10 +31,10 @@ Web Remote. Set a web remote password using the option below the activation sett 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. +not visible on the main console. The `console` mode is only available with GrandMA2 remotes and injects key events +into the main console. This mode also supports additional hardkeys that are only available on GrandMA consoles. +When connected to a dot2 console while this mode is active, 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 @@ -101,7 +102,7 @@ The following keys are mappable in all commandline modes and work on all console | `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 +The following keys only work when keys are being handled with a virtual command line | Web | Remote | specific | | | |---------------|-----------------------|-------------------------------|---------------|-----------------------| diff --git a/backends/midi.c b/backends/midi.c index 65ce48a..d9ac698 100644 --- a/backends/midi.c +++ b/backends/midi.c @@ -358,7 +358,7 @@ static int midi_start(){ //connect to the sequencer if(snd_seq_open(&sequencer, "default", SND_SEQ_OPEN_DUPLEX, 0) < 0){ fprintf(stderr, "Failed to open ALSA sequencer\n"); - return 0; + goto bail; } snd_seq_nonblock(sequencer, 1); @@ -367,7 +367,7 @@ static int midi_start(){ //update the sequencer client name if(snd_seq_set_client_name(sequencer, sequencer_name ? sequencer_name : "MIDIMonster") < 0){ fprintf(stderr, "Failed to set MIDI client name to %s\n", sequencer_name); - return 1; + goto bail; } //create all ports -- cgit v1.2.3 From 30feadde5b18f49fd853f8ce61d85168db912bb6 Mon Sep 17 00:00:00 2001 From: cbdev Date: Sun, 1 Dec 2019 13:30:43 +0100 Subject: Fix minor error in maweb documentation --- backends/maweb.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'backends/maweb.md') diff --git a/backends/maweb.md b/backends/maweb.md index 5b996a9..45dc778 100644 --- a/backends/maweb.md +++ b/backends/maweb.md @@ -53,7 +53,7 @@ Currently, three types of MA controls can be assigned, with each having some sub * 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), + * Above the fader sections are two rows of 21 `button` executors, numbered 122 through 101 (lower row) and 222 through 201 (upper 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 16 buttons, divided into two button wings. Buttons on the wings -- cgit v1.2.3