From ff63873e236ef3c2b7d0a6b53aa60721ceca98c1 Mon Sep 17 00:00:00 2001 From: cbdev Date: Thu, 27 Dec 2018 23:14:54 +0100 Subject: New configuration --- configs/osc-artnet.cfg | 63 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 63 insertions(+) create mode 100644 configs/osc-artnet.cfg (limited to 'configs') diff --git a/configs/osc-artnet.cfg b/configs/osc-artnet.cfg new file mode 100644 index 0000000..3eeba2e --- /dev/null +++ b/configs/osc-artnet.cfg @@ -0,0 +1,63 @@ +; This configuration maps the multifader page of the TouchOSC 'Mix 16' Layout +; to the first 48 ArtNet channels + +[backend artnet] +bind = 0.0.0.0 + +[osc touch] +bind = * 8000 +dest = learn@8001 + +[artnet out] +destination = 255.255.255.255 + +[map] +touch./4/multifader1/1 > out.1 +touch./4/multifader1/2 > out.2 +touch./4/multifader1/3 > out.3 +touch./4/multifader1/4 > out.4 +touch./4/multifader1/5 > out.5 +touch./4/multifader1/6 > out.6 +touch./4/multifader1/7 > out.7 +touch./4/multifader1/8 > out.8 +touch./4/multifader1/9 > out.9 +touch./4/multifader1/10 > out.10 +touch./4/multifader1/11 > out.11 +touch./4/multifader1/12 > out.12 +touch./4/multifader1/13 > out.13 +touch./4/multifader1/14 > out.14 +touch./4/multifader1/15 > out.15 +touch./4/multifader1/16 > out.16 +touch./4/multifader1/17 > out.17 +touch./4/multifader1/18 > out.18 +touch./4/multifader1/19 > out.19 +touch./4/multifader1/20 > out.20 +touch./4/multifader1/21 > out.21 +touch./4/multifader1/22 > out.22 +touch./4/multifader1/23 > out.23 +touch./4/multifader1/24 > out.24 + +touch./4/multifader2/1 > out.25 +touch./4/multifader2/2 > out.26 +touch./4/multifader2/3 > out.27 +touch./4/multifader2/4 > out.28 +touch./4/multifader2/5 > out.29 +touch./4/multifader2/6 > out.30 +touch./4/multifader2/7 > out.31 +touch./4/multifader2/8 > out.32 +touch./4/multifader2/9 > out.33 +touch./4/multifader2/10 > out.34 +touch./4/multifader2/11 > out.35 +touch./4/multifader2/12 > out.36 +touch./4/multifader2/13 > out.37 +touch./4/multifader2/14 > out.38 +touch./4/multifader2/15 > out.39 +touch./4/multifader2/16 > out.40 +touch./4/multifader2/17 > out.41 +touch./4/multifader2/18 > out.42 +touch./4/multifader2/19 > out.43 +touch./4/multifader2/20 > out.44 +touch./4/multifader2/21 > out.45 +touch./4/multifader2/22 > out.46 +touch./4/multifader2/23 > out.47 +touch./4/multifader2/24 > out.48 -- cgit v1.2.3 From a2b0728027dd8961ef84220c8c8eaf8a81154c71 Mon Sep 17 00:00:00 2001 From: cbdev Date: Sat, 30 Mar 2019 14:34:20 +0100 Subject: Fix MIDI mapping syntax --- README.md | 17 ++- backends/midi.c | 35 +++++- configs/launchctl-sacn.cfg | 48 ++++---- configs/midi-osc.cfg | 32 ++--- configs/osc-kbd.cfg | 24 ++-- configs/unifest-17.cfg | 288 ++++++++++++++++++++++----------------------- 6 files changed, 239 insertions(+), 205 deletions(-) (limited to 'configs') diff --git a/README.md b/README.md index 3f5606a..8dfcdd8 100644 --- a/README.md +++ b/README.md @@ -245,11 +245,19 @@ The MIDI backend supports multiple channel types * `note` - Note On/Off messages * `nrpn` - NRPNs (not yet implemented) -A channel is specified using `.`. +A channel is specified using the syntax `channel.`. The shorthand `ch` may be used instead +of `channel`. +The earlier syntax of `.` is officially deprecated but still supported for compatability +reasons. This support may be removed at some future time. -Example mapping: +Channels range from `0` to `15`. Each channel consists of 128 notes (numbered `0` through `127`) and 128 CC's +(numbered likewise), a channel pressure control (also called 'channel aftertouch') and a pitch control. +Each Note also has an additional pressure value associated with it. + +Example mappings: ``` -midi1.cc0.9 > midi2.note1.4 +midi1.ch0.note9 > midi2.channel1.cc4 +midi1.channel15.cc1 > midi1.channel0.note0 ``` #### Known bugs / problems @@ -259,7 +267,8 @@ a configuration option at a later time. NRPNs are not yet fully implemented, though rudimentary support is in the codebase. -The channel specification syntax is currently a bit clunky. +To see which events your MIDI devices output, ALSA provides the `aseqdump` utility. You can +list all incoming events using `aseqdump -p `. ### The `evdev` backend diff --git a/backends/midi.c b/backends/midi.c index d856ced..ad7f6fe 100644 --- a/backends/midi.c +++ b/backends/midi.c @@ -118,19 +118,30 @@ static channel* midi_channel(instance* instance, char* spec){ .label = 0 }; + //support deprecated syntax for a transition period... + uint8_t old_syntax = 0; char* channel; - if(!strncmp(spec, "cc", 2)){ + if(!strncmp(spec, "ch", 2)){ + channel = spec + 2; + if(!strncmp(spec, "channel", 7)){ + channel = spec + 7; + } + } + else if(!strncmp(spec, "cc", 2)){ ident.fields.type = cc; channel = spec + 2; + old_syntax = 1; } else if(!strncmp(spec, "note", 4)){ ident.fields.type = note; channel = spec + 4; + old_syntax = 1; } else if(!strncmp(spec, "nrpn", 4)){ ident.fields.type = nrpn; channel = spec + 4; + old_syntax = 1; } else{ fprintf(stderr, "Unknown MIDI channel specification %s\n", spec); @@ -138,19 +149,33 @@ static channel* midi_channel(instance* instance, char* spec){ } ident.fields.channel = strtoul(channel, &channel, 10); - - //FIXME test this - if(ident.fields.channel > 16){ + if(ident.fields.channel > 15){ fprintf(stderr, "MIDI channel out of range in channel spec %s\n", spec); return NULL; } if(*channel != '.'){ - fprintf(stderr, "Need MIDI channel specification of form channel.control, had %s\n", spec); + fprintf(stderr, "Need MIDI channel specification of form channel., had %s\n", spec); return NULL; } + //skip the period channel++; + if(!old_syntax){ + if(!strncmp(channel, "cc", 2)){ + ident.fields.type = cc; + channel += 2; + } + else if(!strncmp(channel, "note", 4)){ + ident.fields.type = note; + channel += 4; + } + else if(!strncmp(channel, "nrpn", 4)){ + ident.fields.type = nrpn; + channel += 4; + } + } + ident.fields.control = strtoul(channel, NULL, 10); if(ident.label){ diff --git a/configs/launchctl-sacn.cfg b/configs/launchctl-sacn.cfg index 164b477..c2dec84 100644 --- a/configs/launchctl-sacn.cfg +++ b/configs/launchctl-sacn.cfg @@ -18,28 +18,28 @@ priority = 100 [map] -lc.cc0.0 > out.1 -lc.cc0.1 > out.2 -lc.cc0.2 > out.3 -lc.cc0.3 > out.4 -lc.cc0.4 > out.5 -lc.cc0.5 > out.6 -lc.cc0.6 > out.7 -lc.cc0.7 > out.8 -lc.cc0.8 > out.9 -lc.cc0.9 > out.10 -lc.cc0.10 > out.11 -lc.cc0.11 > out.12 -lc.cc0.12 > out.13 -lc.cc0.13 > out.14 -lc.cc0.14 > out.15 -lc.cc0.15 > out.16 +lc.ch0.cc0 > out.1 +lc.ch0.cc1 > out.2 +lc.ch0.cc2 > out.3 +lc.ch0.cc3 > out.4 +lc.ch0.cc4 > out.5 +lc.ch0.cc5 > out.6 +lc.ch0.cc6 > out.7 +lc.ch0.cc7 > out.8 +lc.ch0.cc8 > out.9 +lc.ch0.cc9 > out.10 +lc.ch0.cc10 > out.11 +lc.ch0.cc11 > out.12 +lc.ch0.cc12 > out.13 +lc.ch0.cc13 > out.14 +lc.ch0.cc14 > out.15 +lc.ch0.cc15 > out.16 -lc.note0.0 > out.1 -lc.note0.1 > out.2 -lc.note0.2 > out.3 -lc.note0.3 > out.4 -lc.note0.4 > out.5 -lc.note0.5 > out.6 -lc.note0.6 > out.7 -lc.note0.7 > out.8 +lc.ch0.note0 > out.1 +lc.ch0.note1 > out.2 +lc.ch0.note2 > out.3 +lc.ch0.note3 > out.4 +lc.ch0.note4 > out.5 +lc.ch0.note5 > out.6 +lc.ch0.note6 > out.7 +lc.ch0.note7 > out.8 diff --git a/configs/midi-osc.cfg b/configs/midi-osc.cfg index 215daa9..755077c 100644 --- a/configs/midi-osc.cfg +++ b/configs/midi-osc.cfg @@ -33,20 +33,20 @@ write = BCF [map] -bcf.cc0.81 <> touch./fader1 -bcf.cc0.82 <> touch./fader2 -bcf.cc0.83 <> touch./fader3 -bcf.cc0.84 <> touch./fader4 -bcf.cc0.85 <> touch./fader5 -bcf.cc0.86 <> touch./fader6 -bcf.cc0.87 <> touch./fader7 -bcf.cc0.88 <> touch./fader8 +bcf.ch0.cc81 <> touch./fader1 +bcf.ch0.cc82 <> touch./fader2 +bcf.ch0.cc83 <> touch./fader3 +bcf.ch0.cc84 <> touch./fader4 +bcf.ch0.cc85 <> touch./fader5 +bcf.ch0.cc86 <> touch./fader6 +bcf.ch0.cc87 <> touch./fader7 +bcf.ch0.cc88 <> touch./fader8 -bcf.cc0.81 <> touch./multifader1/1 -bcf.cc0.82 <> touch./multifader1/2 -bcf.cc0.83 <> touch./multifader1/3 -bcf.cc0.84 <> touch./multifader1/4 -bcf.cc0.85 <> touch./multifader1/5 -bcf.cc0.86 <> touch./multifader1/6 -bcf.cc0.87 <> touch./multifader1/7 -bcf.cc0.88 <> touch./multifader1/8 +bcf.ch0.cc81 <> touch./multifader1/1 +bcf.ch0.cc82 <> touch./multifader1/2 +bcf.ch0.cc83 <> touch./multifader1/3 +bcf.ch0.cc84 <> touch./multifader1/4 +bcf.ch0.cc85 <> touch./multifader1/5 +bcf.ch0.cc86 <> touch./multifader1/6 +bcf.ch0.cc87 <> touch./multifader1/7 +bcf.ch0.cc88 <> touch./multifader1/8 diff --git a/configs/osc-kbd.cfg b/configs/osc-kbd.cfg index 0abd131..eb80378 100644 --- a/configs/osc-kbd.cfg +++ b/configs/osc-kbd.cfg @@ -11,15 +11,15 @@ bind = * 8000 dest = learn@8001 [map] -pad./1/push1 > out.note0.60 -pad./1/push2 > out.note0.61 -pad./1/push3 > out.note0.62 -pad./1/push4 > out.note0.63 -pad./1/push5 > out.note0.64 -pad./1/push6 > out.note0.65 -pad./1/push7 > out.note0.66 -pad./1/push8 > out.note0.67 -pad./1/push9 > out.note0.68 -pad./1/push10 > out.note0.69 -pad./1/push11 > out.note0.70 -pad./1/push12 > out.note0.71 +pad./1/push1 > out.ch0.note60 +pad./1/push2 > out.ch0.note61 +pad./1/push3 > out.ch0.note62 +pad./1/push4 > out.ch0.note63 +pad./1/push5 > out.ch0.note64 +pad./1/push6 > out.ch0.note65 +pad./1/push7 > out.ch0.note66 +pad./1/push8 > out.ch0.note67 +pad./1/push9 > out.ch0.note68 +pad./1/push10 > out.ch0.note69 +pad./1/push11 > out.ch0.note70 +pad./1/push12 > out.ch0.note71 diff --git a/configs/unifest-17.cfg b/configs/unifest-17.cfg index 1fab484..47e9ec2 100644 --- a/configs/unifest-17.cfg +++ b/configs/unifest-17.cfg @@ -40,156 +40,156 @@ read = 36:1 [map] ; ArtNet -claudius.1 < lc1.cc0.1 -claudius.2 < lc1.cc0.2 -claudius.3 < lc1.cc0.3 -claudius.4 < lc1.cc0.4 -claudius.5 < lc1.cc0.5 -claudius.6 < lc1.cc0.6 -claudius.7 < lc1.cc0.7 -claudius.8 < lc1.cc0.8 -claudius.9 < lc1.cc0.9 -claudius.10 < lc1.cc0.10 -claudius.11 < lc1.cc0.11 -claudius.12 < lc1.cc0.12 -claudius.13 < lc1.cc0.13 -claudius.14 < lc1.cc0.14 -claudius.15 < lc1.cc0.15 -claudius.16 < lc1.cc0.16 +claudius.1 < lc1.ch0.cc1 +claudius.2 < lc1.ch0.cc2 +claudius.3 < lc1.ch0.cc3 +claudius.4 < lc1.ch0.cc4 +claudius.5 < lc1.ch0.cc5 +claudius.6 < lc1.ch0.cc6 +claudius.7 < lc1.ch0.cc7 +claudius.8 < lc1.ch0.cc8 +claudius.9 < lc1.ch0.cc9 +claudius.10 < lc1.ch0.cc10 +claudius.11 < lc1.ch0.cc11 +claudius.12 < lc1.ch0.cc12 +claudius.13 < lc1.ch0.cc13 +claudius.14 < lc1.ch0.cc14 +claudius.15 < lc1.ch0.cc15 +claudius.16 < lc1.ch0.cc16 ; BCF Fader -out.note0.0 < bcf1.cc0.81 -out.note0.1 < bcf1.cc0.82 -out.note0.2 < bcf1.cc0.83 -out.note0.3 < bcf1.cc0.84 -out.note0.4 < bcf1.cc0.85 -out.note0.5 < bcf1.cc0.86 -out.note0.6 < bcf1.cc0.87 -out.note0.7 < bcf1.cc0.88 -out.note0.8 < bcf2.cc0.81 -out.note0.9 < bcf2.cc0.82 -out.note0.10 < bcf2.cc0.83 -out.note0.11 < bcf2.cc0.84 -out.note0.12 < bcf2.cc0.85 -out.note0.13 < bcf2.cc0.86 -out.note0.14 < bcf2.cc0.87 -out.note0.15 < bcf2.cc0.88 +out.ch0.ch0.note< bcf1.ch0.cc81 +out.ch0.note1 < bcf1.ch0.cc82 +out.ch0.note2 < bcf1.ch0.cc83 +out.ch0.note3 < bcf1.ch0.cc84 +out.ch0.note4 < bcf1.ch0.cc85 +out.ch0.note5 < bcf1.ch0.cc86 +out.ch0.note6 < bcf1.ch0.cc87 +out.ch0.note7 < bcf1.ch0.cc88 +out.ch0.note8 < bcf2.ch0.cc81 +out.ch0.note9 < bcf2.ch0.cc82 +out.ch0.note10 < bcf2.ch0.cc83 +out.ch0.note11 < bcf2.ch0.cc84 +out.ch0.note12 < bcf2.ch0.cc85 +out.ch0.note13 < bcf2.ch0.cc86 +out.ch0.note14 < bcf2.ch0.cc87 +out.ch0.note15 < bcf2.ch0.cc88 ; LC Rotary -out.note0.16 < lc1.cc0.1 -out.note0.17 < lc1.cc0.2 -out.note0.18 < lc1.cc0.3 -out.note0.19 < lc1.cc0.4 -out.note0.20 < lc1.cc0.5 -out.note0.21 < lc1.cc0.6 -out.note0.22 < lc1.cc0.7 -out.note0.23 < lc1.cc0.8 -out.note0.24 < lc1.cc0.9 -out.note0.25 < lc1.cc0.10 -out.note0.26 < lc1.cc0.11 -out.note0.27 < lc1.cc0.12 -out.note0.28 < lc1.cc0.13 -out.note0.29 < lc1.cc0.14 -out.note0.30 < lc1.cc0.15 -out.note0.31 < lc1.cc0.16 -out.note0.32 < lc2.cc0.1 -out.note0.33 < lc2.cc0.2 -out.note0.34 < lc2.cc0.3 -out.note0.35 < lc2.cc0.4 -out.note0.36 < lc2.cc0.5 -out.note0.37 < lc2.cc0.6 -out.note0.38 < lc2.cc0.7 -out.note0.39 < lc2.cc0.8 -out.note0.40 < lc2.cc0.9 -out.note0.41 < lc2.cc0.10 -out.note0.42 < lc2.cc0.11 -out.note0.43 < lc2.cc0.12 -out.note0.44 < lc2.cc0.13 -out.note0.45 < lc2.cc0.14 -out.note0.46 < lc2.cc0.15 -out.note0.47 < lc2.cc0.16 +out.ch0.note16 < lc1.ch0.cc1 +out.ch0.note17 < lc1.ch0.cc2 +out.ch0.note18 < lc1.ch0.cc3 +out.ch0.note19 < lc1.ch0.cc4 +out.ch0.note20 < lc1.ch0.cc5 +out.ch0.note21 < lc1.ch0.cc6 +out.ch0.note22 < lc1.ch0.cc7 +out.ch0.note23 < lc1.ch0.cc8 +out.ch0.note24 < lc1.ch0.cc9 +out.ch0.note25 < lc1.ch0.cc10 +out.ch0.note26 < lc1.ch0.cc11 +out.ch0.note27 < lc1.ch0.cc12 +out.ch0.note28 < lc1.ch0.cc13 +out.ch0.note29 < lc1.ch0.cc14 +out.ch0.note30 < lc1.ch0.cc15 +out.ch0.note31 < lc1.ch0.cc16 +out.ch0.note32 < lc2.ch0.cc1 +out.ch0.note33 < lc2.ch0.cc2 +out.ch0.note34 < lc2.ch0.cc3 +out.ch0.note35 < lc2.ch0.cc4 +out.ch0.note36 < lc2.ch0.cc5 +out.ch0.note37 < lc2.ch0.cc6 +out.ch0.note38 < lc2.ch0.cc7 +out.ch0.note39 < lc2.ch0.cc8 +out.ch0.note40 < lc2.ch0.cc9 +out.ch0.note41 < lc2.ch0.cc10 +out.ch0.note42 < lc2.ch0.cc11 +out.ch0.note43 < lc2.ch0.cc12 +out.ch0.note44 < lc2.ch0.cc13 +out.ch0.note45 < lc2.ch0.cc14 +out.ch0.note46 < lc2.ch0.cc15 +out.ch0.note47 < lc2.ch0.cc16 ; LC Button -out.note0.48 < lc1.note0.0 -out.note0.49 < lc1.note0.1 -out.note0.50 < lc1.note0.2 -out.note0.51 < lc1.note0.3 -out.note0.52 < lc1.note0.4 -out.note0.53 < lc1.note0.5 -out.note0.54 < lc1.note0.6 -out.note0.55 < lc1.note0.7 +out.ch0.note48 < lc1.ch0.note0 +out.ch0.note49 < lc1.ch0.note1 +out.ch0.note50 < lc1.ch0.note2 +out.ch0.note51 < lc1.ch0.note3 +out.ch0.note52 < lc1.ch0.note4 +out.ch0.note53 < lc1.ch0.note5 +out.ch0.note54 < lc1.ch0.note6 +out.ch0.note55 < lc1.ch0.note7 -out.note0.56 < lc2.note0.0 -out.note0.57 < lc2.note0.1 -out.note0.58 < lc2.note0.2 -out.note0.59 < lc2.note0.3 -out.note0.60 < lc2.note0.4 -out.note0.61 < lc2.note0.5 -out.note0.62 < lc2.note0.6 -out.note0.63 < lc2.note0.7 +out.ch0.note56 < lc2.ch0.note0 +out.ch0.note57 < lc2.ch0.note1 +out.ch0.note58 < lc2.ch0.note2 +out.ch0.note59 < lc2.ch0.note3 +out.ch0.note60 < lc2.ch0.note4 +out.ch0.note61 < lc2.ch0.note5 +out.ch0.note62 < lc2.ch0.note6 +out.ch0.note63 < lc2.ch0.note7 ; Launchpad -out.note0.64 < pad.note0.0 -out.note0.65 < pad.note0.1 -out.note0.66 < pad.note0.2 -out.note0.67 < pad.note0.3 -out.note0.68 < pad.note0.4 -out.note0.69 < pad.note0.5 -out.note0.70 < pad.note0.6 -out.note0.71 < pad.note0.7 -out.note0.72 < pad.note0.16 -out.note0.73 < pad.note0.17 -out.note0.74 < pad.note0.18 -out.note0.75 < pad.note0.19 -out.note0.76 < pad.note0.20 -out.note0.77 < pad.note0.21 -out.note0.78 < pad.note0.22 -out.note0.79 < pad.note0.23 -out.note0.80 < pad.note0.32 -out.note0.81 < pad.note0.33 -out.note0.82 < pad.note0.34 -out.note0.83 < pad.note0.35 -out.note0.84 < pad.note0.36 -out.note0.85 < pad.note0.37 -out.note0.86 < pad.note0.38 -out.note0.87 < pad.note0.39 -out.note0.88 < pad.note0.48 -out.note0.89 < pad.note0.49 -out.note0.90 < pad.note0.50 -out.note0.91 < pad.note0.51 -out.note0.92 < pad.note0.52 -out.note0.93 < pad.note0.53 -out.note0.94 < pad.note0.54 -out.note0.95 < pad.note0.55 -out.note0.96 < pad.note0.64 -out.note0.97 < pad.note0.65 -out.note0.98 < pad.note0.66 -out.note0.99 < pad.note0.67 -out.note0.100 < pad.note0.68 -out.note0.101 < pad.note0.69 -out.note0.102 < pad.note0.70 -out.note0.103 < pad.note0.71 -out.note0.104 < pad.note0.80 -out.note0.105 < pad.note0.81 -out.note0.106 < pad.note0.82 -out.note0.107 < pad.note0.83 -out.note0.108 < pad.note0.84 -out.note0.109 < pad.note0.85 -out.note0.110 < pad.note0.86 -out.note0.111 < pad.note0.87 -out.note0.112 < pad.note0.96 -out.note0.113 < pad.note0.97 -out.note0.114 < pad.note0.98 -out.note0.115 < pad.note0.99 -out.note0.116 < pad.note0.100 -out.note0.117 < pad.note0.101 -out.note0.118 < pad.note0.102 -out.note0.119 < pad.note0.103 -out.note0.120 < pad.note0.112 -out.note0.121 < pad.note0.113 -out.note0.122 < pad.note0.114 -out.note0.123 < pad.note0.115 -out.note0.124 < pad.note0.116 -out.note0.125 < pad.note0.117 -out.note0.126 < pad.note0.118 -out.note0.127 < pad.note0.119 +out.ch0.note64 < pad.ch0.note0 +out.ch0.note65 < pad.ch0.note1 +out.ch0.note66 < pad.ch0.note2 +out.ch0.note67 < pad.ch0.note3 +out.ch0.note68 < pad.ch0.note4 +out.ch0.note69 < pad.ch0.note5 +out.ch0.note70 < pad.ch0.note6 +out.ch0.note71 < pad.ch0.note7 +out.ch0.note72 < pad.ch0.note16 +out.ch0.note73 < pad.ch0.note17 +out.ch0.note74 < pad.ch0.note18 +out.ch0.note75 < pad.ch0.note19 +out.ch0.note76 < pad.ch0.note20 +out.ch0.note77 < pad.ch0.note21 +out.ch0.note78 < pad.ch0.note22 +out.ch0.note79 < pad.ch0.note23 +out.ch0.note80 < pad.ch0.note32 +out.ch0.note81 < pad.ch0.note33 +out.ch0.note82 < pad.ch0.note34 +out.ch0.note83 < pad.ch0.note35 +out.ch0.note84 < pad.ch0.note36 +out.ch0.note85 < pad.ch0.note37 +out.ch0.note86 < pad.ch0.note38 +out.ch0.note87 < pad.ch0.note39 +out.ch0.note88 < pad.ch0.note48 +out.ch0.note89 < pad.ch0.note49 +out.ch0.note90 < pad.ch0.note50 +out.ch0.note91 < pad.ch0.note51 +out.ch0.note92 < pad.ch0.note52 +out.ch0.note93 < pad.ch0.note53 +out.ch0.note94 < pad.ch0.note54 +out.ch0.note95 < pad.ch0.note55 +out.ch0.note96 < pad.ch0.note64 +out.ch0.note97 < pad.ch0.note65 +out.ch0.note98 < pad.ch0.note66 +out.ch0.note99 < pad.ch0.note67 +out.ch0.note100 < pad.ch0.note68 +out.ch0.note101 < pad.ch0.note69 +out.ch0.note102 < pad.ch0.note70 +out.ch0.note103 < pad.ch0.note71 +out.ch0.note104 < pad.ch0.note80 +out.ch0.note105 < pad.ch0.note81 +out.ch0.note106 < pad.ch0.note82 +out.ch0.note107 < pad.ch0.note83 +out.ch0.note108 < pad.ch0.note84 +out.ch0.note109 < pad.ch0.note85 +out.ch0.note110 < pad.ch0.note86 +out.ch0.note111 < pad.ch0.note87 +out.ch0.note112 < pad.ch0.note96 +out.ch0.note113 < pad.ch0.note97 +out.ch0.note114 < pad.ch0.note98 +out.ch0.note115 < pad.ch0.note99 +out.ch0.note116 < pad.ch0.note100 +out.ch0.note117 < pad.ch0.note101 +out.ch0.note118 < pad.ch0.note102 +out.ch0.note119 < pad.ch0.note103 +out.ch0.note120 < pad.ch0.note112 +out.ch0.note121 < pad.ch0.note113 +out.ch0.note122 < pad.ch0.note114 +out.ch0.note123 < pad.ch0.note115 +out.ch0.note124 < pad.ch0.note116 +out.ch0.note125 < pad.ch0.note117 +out.ch0.note126 < pad.ch0.note118 +out.ch0.note127 < pad.ch0.note119 -- cgit v1.2.3 From b618c4a6b74a52f830ca53029e1cc680d56a2501 Mon Sep 17 00:00:00 2001 From: cbdev Date: Sat, 6 Jul 2019 17:25:12 +0200 Subject: Implement Lua backend --- .travis.yml | 1 + README.md | 13 ++-- backends/Makefile | 4 +- backends/lua.c | 202 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ backends/lua.h | 21 ++++++ backends/lua.md | 49 +++++++++++++ configs/demo.lua | 21 ++++++ configs/lua.cfg | 35 ++++++++++ midimonster.h | 1 + 9 files changed, 342 insertions(+), 5 deletions(-) create mode 100644 backends/lua.c create mode 100644 backends/lua.h create mode 100644 backends/lua.md create mode 100644 configs/demo.lua create mode 100644 configs/lua.cfg (limited to 'configs') diff --git a/.travis.yml b/.travis.yml index ab047ce..66bfd9a 100644 --- a/.travis.yml +++ b/.travis.yml @@ -20,6 +20,7 @@ addons: - libasound2-dev - libevdev-dev - libola-dev + - liblua5.3-dev packages: &core_build_gpp_latest - *core_build - gcc-8 diff --git a/README.md b/README.md index 6265581..ee907a6 100644 --- a/README.md +++ b/README.md @@ -12,12 +12,15 @@ Currently, the MIDIMonster supports the following protocols: * evdev input devices (Linux) * Open Lighting Architecture (OLA) +with additional flexibility provided by a Lua scripting environment. + The MIDIMonster allows the user to translate any channel on one protocol into channel(s) 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 * 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 ([Example configuration](configs/evdev.conf)) @@ -88,6 +91,7 @@ special information. These documentation files are located in the `backends/` di * [`loopback` backend documentation](backends/loopback.md) * [`ola` backend documentation](backends/ola.md) * [`osc` backend documentation](backends/osc.md) +* [`lua` backend documentation](backends/lua.md) ## Building @@ -99,10 +103,11 @@ This section will explain how to build the provided sources to be able to run In order to build the MIDIMonster, you'll need some libraries that provide support for the protocols to translate. -* libasound2-dev (for the MIDI backend) -* libevdev-dev (for the evdev backend) -* libola-dev (for the optional OLA backend) -* pkg-config (as some projects and systems like to spread their files around) +* `libasound2-dev` (for the MIDI backend) +* `libevdev-dev` (for the evdev backend) +* `liblua5.3-dev` (for the lua backend) +* `libola-dev` (for the optional OLA backend) +* `pkg-config` (as some projects and systems like to spread their files around) * A C compiler * GNUmake diff --git a/backends/Makefile b/backends/Makefile index c11de56..fe88669 100644 --- a/backends/Makefile +++ b/backends/Makefile @@ -1,7 +1,7 @@ .PHONY: all clean full OPTIONAL_BACKENDS = ola.so LINUX_BACKENDS = midi.so evdev.so -BACKENDS = artnet.so osc.so loopback.so sacn.so +BACKENDS = artnet.so osc.so loopback.so sacn.so lua.so BACKEND_LIB = libmmbackend.o SYSTEM := $(shell uname -s) @@ -27,6 +27,8 @@ evdev.so: CFLAGS += $(shell pkg-config --cflags libevdev) evdev.so: LDLIBS = $(shell pkg-config --libs libevdev) ola.so: LDLIBS = -lola ola.so: CPPFLAGS += -Wno-write-strings +lua.so: CFLAGS += $(shell pkg-config --cflags lua5.3) +lua.so: LDLIBS += $(shell pkg-config --libs lua5.3) %.so :: %.c %.h $(BACKEND_LIB) $(CC) $(CFLAGS) $(LDLIBS) $< $(ADDITIONAL_OBJS) -o $@ $(LDFLAGS) diff --git a/backends/lua.c b/backends/lua.c new file mode 100644 index 0000000..ae2d460 --- /dev/null +++ b/backends/lua.c @@ -0,0 +1,202 @@ +#include +#include "lua.h" + +#define BACKEND_NAME "lua" +#define LUA_REGISTRY_KEY "_midimonster_lua_instance" + +//TODO instance identification for callvacks + +int init(){ + backend lua = { + .name = BACKEND_NAME, + .conf = lua_configure, + .create = lua_instance, + .conf_instance = lua_configure_instance, + .channel = lua_channel, + .handle = lua_set, + .process = lua_handle, + .start = lua_start, + .shutdown = lua_shutdown + }; + + //register backend + if(mm_backend_register(lua)){ + fprintf(stderr, "Failed to register lua backend\n"); + return 1; + } + return 0; +} + +static int lua_callback_output(lua_State* interpreter){ + int arguments = lua_gettop(interpreter); + size_t n; + channel_value val; + const char* channel_name = NULL; + channel* channel = NULL; + instance* inst = NULL; + lua_instance_data* data = NULL; + + if(arguments != 2){ + fprintf(stderr, "Lua output function called with %d arguments, expected 2\n", arguments); + return 0; + } + + channel_name = lua_tostring(interpreter, 1); + val.normalised = clamp(lua_tonumber(interpreter, 2), 1.0, 0.0); + + lua_pushstring(interpreter, LUA_REGISTRY_KEY); + lua_gettable(interpreter, LUA_REGISTRYINDEX); + inst = (instance *) lua_touserdata(interpreter, -1); + data = (lua_instance_data*) inst->impl; + + for(n = 0; n < data->channels; n++){ + if(!strcmp(channel_name, data->channel_name[n])){ + channel = mm_channel(inst, n, 0); + if(!channel){ + return 0; + } + mm_channel_event(channel, val); + return 0; + } + } + + fprintf(stderr, "Tried to set unknown channel %s.%s\n", inst->name, channel_name); + return 0; +} + +static int lua_configure(char* option, char* value){ + fprintf(stderr, "The lua backend does not take any global configuration\n"); + return 1; +} + +static int lua_configure_instance(instance* inst, char* option, char* value){ + lua_instance_data* data = (lua_instance_data*) inst->impl; + + if(!strcmp(option, "script")){ + if(luaL_dofile(data->interpreter, value)){ + fprintf(stderr, "Failed to load lua source file %s for instance %s: %s\n", value, inst->name, lua_tostring(data->interpreter, -1)); + return 1; + } + return 0; + } + + fprintf(stderr, "Unknown configuration parameter %s for lua backend\n", option); + return 1; +} + +static instance* lua_instance(){ + instance* inst = mm_instance(); + if(!inst){ + return NULL; + } + + lua_instance_data* data = calloc(1, sizeof(lua_instance_data)); + if(!data){ + fprintf(stderr, "Failed to allocate memory\n"); + return NULL; + } + + //load the interpreter + data->interpreter = luaL_newstate(); + if(!data->interpreter){ + fprintf(stderr, "Failed to initialize LUA\n"); + free(data); + return NULL; + } + luaL_openlibs(data->interpreter); + + //register lua api functions + lua_register(data->interpreter, "output", lua_callback_output); + + //store instance pointer to the lua state + lua_pushstring(data->interpreter, LUA_REGISTRY_KEY); + lua_pushlightuserdata(data->interpreter, (void *) inst); + lua_settable(data->interpreter, LUA_REGISTRYINDEX); + + inst->impl = data; + return inst; +} + +static channel* lua_channel(instance* inst, char* spec){ + size_t u; + lua_instance_data* data = (lua_instance_data*) inst->impl; + + //find matching channel + for(u = 0; u < data->channels; u++){ + if(!strcmp(spec, data->channel_name[u])){ + break; + } + } + + //allocate new channel + if(u == data->channels){ + data->channel_name = realloc(data->channel_name, (u + 1) * sizeof(char*)); + if(!data->channel_name){ + fprintf(stderr, "Failed to allocate memory\n"); + return NULL; + } + + data->channel_name[u] = strdup(spec); + if(!data->channel_name[u]){ + fprintf(stderr, "Failed to allocate memory\n"); + return NULL; + } + data->channels++; + } + + return mm_channel(inst, u, 1); +} + +static int lua_set(instance* inst, size_t num, channel** c, channel_value* v){ + size_t n = 0; + lua_instance_data* data = (lua_instance_data*) inst->impl; + + for(n = 0; n < num; n++){ + //call lua channel handlers + lua_getglobal(data->interpreter, data->channel_name[c[n]->ident]); + lua_pushnumber(data->interpreter, v[n].normalised); + if(lua_pcall(data->interpreter, 1, 0, 0) != LUA_OK){ + fprintf(stderr, "Failed to call handler for %s.%s: %s\n", inst->name, data->channel_name[c[n]->ident], lua_tostring(data->interpreter, -1)); + lua_pop(data->interpreter, 1); + } + } + return 0; +} + +static int lua_handle(size_t num, managed_fd* fds){ + //TODO call timer callbacks + return 0; +} + +static int lua_start(){ + //TODO start timers / register fds + return 0; +} + +static int lua_shutdown(){ + size_t n, u, p; + instance** inst = NULL; + lua_instance_data* data = NULL; + + if(mm_backend_instances(BACKEND_NAME, &n, &inst)){ + fprintf(stderr, "Failed to fetch instance list\n"); + return 1; + } + + for(u = 0; u < n; u++){ + data = (lua_instance_data*) inst[u]->impl; + //stop the interpreter + lua_close(data->interpreter); + //cleanup channel data + for(p = 0; p < data->channels; p++){ + free(data->channel_name[p]); + } + free(data->channel_name); + free(inst[u]->impl); + } + + free(inst); + + fprintf(stderr, "Lua backend shut down\n"); + return 0; +} diff --git a/backends/lua.h b/backends/lua.h new file mode 100644 index 0000000..27e1afd --- /dev/null +++ b/backends/lua.h @@ -0,0 +1,21 @@ +#include "midimonster.h" + +#include +#include +#include + +int init(); +static int lua_configure(char* option, char* value); +static int lua_configure_instance(instance* inst, char* option, char* value); +static instance* lua_instance(); +static channel* lua_channel(instance* inst, char* spec); +static int lua_set(instance* inst, size_t num, channel** c, channel_value* v); +static int lua_handle(size_t num, managed_fd* fds); +static int lua_start(); +static int lua_shutdown(); + +typedef struct /*_lua_instance_data*/ { + size_t channels; + char** channel_name; + lua_State* interpreter; +} lua_instance_data; diff --git a/backends/lua.md b/backends/lua.md new file mode 100644 index 0000000..91e8fe2 --- /dev/null +++ b/backends/lua.md @@ -0,0 +1,49 @@ +### The `lua` backend + +The `lua` backend provides a flexible programming environment, allowing users to route and manipulate +events using the Lua programming language. + +Every instance has it's own interpreter state which can be loaded with custom handler scripts. + +To process incoming channel events, the MIDIMonster calls corresponding Lua functions with +the value (as a Lua `number` type) as parameter. To send output on a channel, the Lua environment +provides the function `output(channel-name, value)`. + +Example script: +``` +function bar(value) + output("foo", value / 2) +end +``` + +Input values range between 0.0 and 1.0, output values are clamped to the same range. + +#### Global configuration + +The backend does not take any global configuration. + +#### Instance configuration + +| Option | Example value | Default value | Description | +|---------------|-----------------------|-----------------------|-----------------------| +| `source` | `script.lua` | none | Lua source file | + +A single instance may have multiple `source` options specified, which will all be read cumulatively. + +#### Channel specification + +Channel names may be any valid Lua function name. + +Example mapping: +``` +lua1.foo > lua2.bar +``` + +#### Known bugs / problems + +Using `output` as an input channel name to a Lua instance does not work, as the interpreter has +`output` globally assigned to the event output function. Using `output` as an output channel name +via `output("output", value)` works as intended. + +The path to the Lua source files is relative to the current working directory. This may lead +to problems when copying configuration between installations. diff --git a/configs/demo.lua b/configs/demo.lua new file mode 100644 index 0000000..e816ac4 --- /dev/null +++ b/configs/demo.lua @@ -0,0 +1,21 @@ +-- This example MIDIMonstaer Lua script spreads one input channel onto multiple output +-- channels using a polynomial function evaluated at multiple points. This effect can +-- be visualized e.g. with martrix (https://github.com/cbdevnet/martrix). + +-- This is just a demonstration of global variables +foo = 0 + +-- The polynomial to evaluate +function polynomial(offset, x) + return math.exp(-20 * (x - offset) ^ 2) +end + +-- Handler function for the input channel +function input(value) + foo = foo + 1 + print("input at ", value, foo) + + for chan=0,10 do + output("out" .. chan, polynomial(value, (1 / 10) * chan)) + end +end diff --git a/configs/lua.cfg b/configs/lua.cfg new file mode 100644 index 0000000..9182122 --- /dev/null +++ b/configs/lua.cfg @@ -0,0 +1,35 @@ +; This configuration uses a Lua script to distribute one input channel (from either a mouse +; button or an axis control) onto multiple output channels (on ArtNet). + +[backend artnet] +bind = 0.0.0.0 + +[evdev mouse] +device = /dev/input/by-path/platform-i8042-serio-2-event-mouse + +[evdev xbox] +input = Xbox Wireless +axis.ABS_X = 34300 0 65535 255 4095 + +[lua lua] +script = configs/demo.lua + +[artnet art] +universe = 0 +destination = 255.255.255.255 + +[map] +mouse.EV_KEY.BTN_LEFT > lua.input +xbox.EV_ABS.ABS_X > lua.input + +art.1 < lua.out0 +art.2 < lua.out1 +art.3 < lua.out2 +art.4 < lua.out3 +art.5 < lua.out4 +art.6 < lua.out5 +art.7 < lua.out6 +art.8 < lua.out7 +art.9 < lua.out8 +art.10 < lua.out9 +art.11 < lua.out10 diff --git a/midimonster.h b/midimonster.h index 572b5fb..8a18155 100644 --- a/midimonster.h +++ b/midimonster.h @@ -5,6 +5,7 @@ #include #define max(a,b) (((a) > (b)) ? (a) : (b)) #define min(a,b) (((a) < (b)) ? (a) : (b)) +#define clamp(val,max,min) (((val) > (max)) ? (max) : (((val) < (min)) ? (min) : (val))) #ifdef DEBUG #define DBGPF(format, ...) fprintf(stderr, (format), __VA_ARGS__) #define DBG(message) fprintf(stderr, "%s", (message)) -- cgit v1.2.3 From ee4a46105acecb6a7adc1e7189e8b0a66404b421 Mon Sep 17 00:00:00 2001 From: cbdev Date: Sat, 13 Jul 2019 17:51:11 +0200 Subject: Improved Lua backend with intervals --- TODO | 2 +- backends/lua.c | 302 +++++++++++++++++++++++++++++++++++++++++++++++++++---- backends/lua.h | 10 ++ backends/lua.md | 32 ++++-- configs/demo.lua | 45 ++++++--- configs/lua.cfg | 10 +- 6 files changed, 359 insertions(+), 42 deletions(-) (limited to 'configs') diff --git a/TODO b/TODO index cfdd409..f39dae0 100644 --- a/TODO +++ b/TODO @@ -5,4 +5,4 @@ Printing backend document example configs lua timer -evdev relaxes size +evdev relative axis size diff --git a/backends/lua.c b/backends/lua.c index ae2d460..08d8d3c 100644 --- a/backends/lua.c +++ b/backends/lua.c @@ -1,10 +1,18 @@ #include +#include +#include +#include #include "lua.h" #define BACKEND_NAME "lua" #define LUA_REGISTRY_KEY "_midimonster_lua_instance" -//TODO instance identification for callvacks +static size_t timers = 0; +static lua_timer* timer = NULL; +static struct itimerspec timer_config = { + 0 +}; +static int timer_fd = -1; int init(){ backend lua = { @@ -24,31 +32,83 @@ int init(){ fprintf(stderr, "Failed to register lua backend\n"); return 1; } + + //create the timer to expire intervals + timer_fd = timerfd_create(CLOCK_MONOTONIC, TFD_NONBLOCK); + if(timer_fd < 0){ + fprintf(stderr, "Failed to create timer for Lua backend\n"); + return 1; + } + return 0; +} + +static int lua_update_timerfd(){ + uint64_t interval = 0, gcd, residual; + size_t n = 0; + + //find the minimum for the lower interval bounds + for(n = 0; n < timers; n++){ + if(timer[n].interval && (!interval || timer[n].interval < interval)){ + interval = timer[n].interval; + } + } + + //stop the timer + if(!interval){ + memset(&timer_config, 0, sizeof(struct itimerspec)); + } + //calculate gcd of all timers + else{ + for(n = 0; n < timers; n++){ + if(timer[n].interval){ + //calculate gcd of current interval and this timers interval + gcd = timer[n].interval; + while(gcd){ + residual = interval % gcd; + interval = gcd; + gcd = residual; + } + //since we round everything, 10 is the lowest interval we get + if(interval == 10){ + break; + } + } + } + + timer_config.it_interval.tv_sec = interval / 1000; + timer_config.it_interval.tv_nsec = (interval % 1000) * 1e6; + timer_config.it_value.tv_nsec = 1; + } + + //configure the new interval + timerfd_settime(timer_fd, 0, &timer_config, NULL); return 0; } static int lua_callback_output(lua_State* interpreter){ - int arguments = lua_gettop(interpreter); - size_t n; + size_t n = 0; channel_value val; const char* channel_name = NULL; channel* channel = NULL; instance* inst = NULL; lua_instance_data* data = NULL; - if(arguments != 2){ - fprintf(stderr, "Lua output function called with %d arguments, expected 2\n", arguments); + if(lua_gettop(interpreter) != 2){ + fprintf(stderr, "Lua output function called with %d arguments, expected 2 (string, number)\n", lua_gettop(interpreter)); return 0; } - channel_name = lua_tostring(interpreter, 1); - val.normalised = clamp(lua_tonumber(interpreter, 2), 1.0, 0.0); - + //get instance pointer from registry lua_pushstring(interpreter, LUA_REGISTRY_KEY); lua_gettable(interpreter, LUA_REGISTRYINDEX); - inst = (instance *) lua_touserdata(interpreter, -1); + inst = (instance*) lua_touserdata(interpreter, -1); data = (lua_instance_data*) inst->impl; + //fetch function parameters + channel_name = lua_tostring(interpreter, 1); + val.normalised = clamp(luaL_checknumber(interpreter, 2), 1.0, 0.0); + + //find correct channel & output value for(n = 0; n < data->channels; n++){ if(!strcmp(channel_name, data->channel_name[n])){ channel = mm_channel(inst, n, 0); @@ -56,6 +116,7 @@ static int lua_callback_output(lua_State* interpreter){ return 0; } mm_channel_event(channel, val); + data->output[n] = val.normalised; return 0; } } @@ -64,6 +125,122 @@ static int lua_callback_output(lua_State* interpreter){ return 0; } +static int lua_callback_interval(lua_State* interpreter){ + size_t n = 0; + instance* inst = NULL; + lua_instance_data* data = NULL; + uint64_t interval = 0; + int reference = LUA_NOREF; + + if(lua_gettop(interpreter) != 2){ + fprintf(stderr, "Lua output function called with %d arguments, expected 2 (string, number)\n", lua_gettop(interpreter)); + return 0; + } + + //get instance pointer from registry + lua_pushstring(interpreter, LUA_REGISTRY_KEY); + lua_gettable(interpreter, LUA_REGISTRYINDEX); + inst = (instance*) lua_touserdata(interpreter, -1); + data = (lua_instance_data*) inst->impl; + + //fetch and round the interval + interval = luaL_checkinteger(interpreter, 2); + if(interval % 10 < 5){ + interval -= interval % 10; + } + else{ + interval += (10 - (interval % 10)); + } + + //push the function again + lua_pushvalue(interpreter, 1); + if(lua_gettable(interpreter, LUA_REGISTRYINDEX) == LUA_TNUMBER){ + //already interval'd + reference = luaL_checkinteger(interpreter, 4); + } + else if(interval){ + //get a reference to the function + lua_pushvalue(interpreter, 1); + reference = luaL_ref(interpreter, LUA_REGISTRYINDEX); + + //the function indexes the reference + lua_pushvalue(interpreter, 1); + lua_pushinteger(interpreter, reference); + lua_settable(interpreter, LUA_REGISTRYINDEX); + } + + //find matching timer + for(n = 0; n < timers; n++){ + if(timer[n].reference == reference && timer[n].interpreter == interpreter){ + break; + } + } + + if(n < timers){ + //set new interval + timer[n].interval = interval; + timer[n].delta = 0; + } + else if(interval){ + //append new timer + timer = realloc(timer, (timers + 1) * sizeof(lua_timer)); + if(!timer){ + fprintf(stderr, "Failed to allocate memory\n"); + timers = 0; + return 0; + } + timer[timers].interval = interval; + timer[timers].delta = 0; + timer[timers].interpreter = interpreter; + timer[timers].reference = reference; + timers++; + } + + //recalculate timerspec + lua_update_timerfd(); + return 0; +} + +static int lua_callback_value(lua_State* interpreter, uint8_t input){ + size_t n = 0; + instance* inst = NULL; + lua_instance_data* data = NULL; + const char* channel_name = NULL; + + if(lua_gettop(interpreter) != 1){ + fprintf(stderr, "Lua get_value function called with %d arguments, expected 1 (string)\n", lua_gettop(interpreter)); + return 0; + } + + //get instance pointer from registry + lua_pushstring(interpreter, LUA_REGISTRY_KEY); + lua_gettable(interpreter, LUA_REGISTRYINDEX); + inst = (instance*) lua_touserdata(interpreter, -1); + data = (lua_instance_data*) inst->impl; + + //fetch argument + channel_name = lua_tostring(interpreter, 1); + + //find correct channel & return value + for(n = 0; n < data->channels; n++){ + if(!strcmp(channel_name, data->channel_name[n])){ + lua_pushnumber(data->interpreter, (input) ? data->input[n] : data->output[n]); + return 1; + } + } + + fprintf(stderr, "Tried to get unknown channel %s.%s\n", inst->name, channel_name); + return 0; +} + +static int lua_callback_input_value(lua_State* interpreter){ + return lua_callback_value(interpreter, 1); +} + +static int lua_callback_output_value(lua_State* interpreter){ + return lua_callback_value(interpreter, 0); +} + static int lua_configure(char* option, char* value){ fprintf(stderr, "The lua backend does not take any global configuration\n"); return 1; @@ -72,6 +249,7 @@ static int lua_configure(char* option, char* value){ static int lua_configure_instance(instance* inst, char* option, char* value){ lua_instance_data* data = (lua_instance_data*) inst->impl; + //load a lua file into the interpreter if(!strcmp(option, "script")){ if(luaL_dofile(data->interpreter, value)){ fprintf(stderr, "Failed to load lua source file %s for instance %s: %s\n", value, inst->name, lua_tostring(data->interpreter, -1)); @@ -105,13 +283,16 @@ static instance* lua_instance(){ } luaL_openlibs(data->interpreter); - //register lua api functions + //register lua interface functions lua_register(data->interpreter, "output", lua_callback_output); + lua_register(data->interpreter, "interval", lua_callback_interval); + lua_register(data->interpreter, "input_value", lua_callback_input_value); + lua_register(data->interpreter, "output_value", lua_callback_output_value); //store instance pointer to the lua state lua_pushstring(data->interpreter, LUA_REGISTRY_KEY); lua_pushlightuserdata(data->interpreter, (void *) inst); - lua_settable(data->interpreter, LUA_REGISTRYINDEX); + lua_settable(data->interpreter, LUA_REGISTRYINDEX); inst->impl = data; return inst; @@ -131,11 +312,16 @@ static channel* lua_channel(instance* inst, char* spec){ //allocate new channel if(u == data->channels){ data->channel_name = realloc(data->channel_name, (u + 1) * sizeof(char*)); - if(!data->channel_name){ + data->reference = realloc(data->reference, (u + 1) * sizeof(int)); + data->input = realloc(data->input, (u + 1) * sizeof(double)); + data->output = realloc(data->output, (u + 1) * sizeof(double)); + if(!data->channel_name || !data->reference || !data->input || !data->output){ fprintf(stderr, "Failed to allocate memory\n"); return NULL; } + data->reference[u] = LUA_NOREF; + data->input[u] = data->output[u] = 0.0; data->channel_name[u] = strdup(spec); if(!data->channel_name[u]){ fprintf(stderr, "Failed to allocate memory\n"); @@ -151,25 +337,91 @@ static int lua_set(instance* inst, size_t num, channel** c, channel_value* v){ size_t n = 0; lua_instance_data* data = (lua_instance_data*) inst->impl; + //handle all incoming events for(n = 0; n < num; n++){ - //call lua channel handlers - lua_getglobal(data->interpreter, data->channel_name[c[n]->ident]); - lua_pushnumber(data->interpreter, v[n].normalised); - if(lua_pcall(data->interpreter, 1, 0, 0) != LUA_OK){ - fprintf(stderr, "Failed to call handler for %s.%s: %s\n", inst->name, data->channel_name[c[n]->ident], lua_tostring(data->interpreter, -1)); - lua_pop(data->interpreter, 1); + data->input[c[n]->ident] = v[n].normalised; + //call lua channel handlers if present + if(data->reference[n] != LUA_NOREF){ + lua_rawgeti(data->interpreter, LUA_REGISTRYINDEX, data->reference[c[n]->ident]); + lua_pushnumber(data->interpreter, v[n].normalised); + if(lua_pcall(data->interpreter, 1, 0, 0) != LUA_OK){ + fprintf(stderr, "Failed to call handler for %s.%s: %s\n", inst->name, data->channel_name[c[n]->ident], lua_tostring(data->interpreter, -1)); + lua_pop(data->interpreter, 1); + } } } return 0; } static int lua_handle(size_t num, managed_fd* fds){ - //TODO call timer callbacks + uint8_t read_buffer[100]; + uint64_t delta = timer_config.it_interval.tv_sec * 1000 + timer_config.it_interval.tv_nsec / 1e6; + size_t n; + + if(!num){ + return 0; + } + + //read the timer iteration to acknowledge the fd + if(read(timer_fd, read_buffer, sizeof(read_buffer)) < 0){ + fprintf(stderr, "Failed to read from Lua timer: %s\n", strerror(errno)); + return 1; + } + + //add delta to all active timers + for(n = 0; n < timers; n++){ + if(timer[n].interval){ + timer[n].delta += delta; + //call lua function if timer expired + if(timer[n].delta >= timer[n].interval){ + timer[n].delta %= timer[n].interval; + lua_rawgeti(timer[n].interpreter, LUA_REGISTRYINDEX, timer[n].reference); + lua_pcall(timer[n].interpreter, 0, 0, 0); + } + } + } return 0; } static int lua_start(){ - //TODO start timers / register fds + size_t n, u, p; + instance** inst = NULL; + lua_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; + } + + //resolve channels to their handler functions + for(u = 0; u < n; u++){ + data = (lua_instance_data*) inst[u]->impl; + for(p = 0; p < data->channels; p++){ + //exclude reserved names + if(strcmp(data->channel_name[p], "output") + && strcmp(data->channel_name[p], "input_value") + && strcmp(data->channel_name[p], "output_value") + && strcmp(data->channel_name[p], "interval")){ + lua_getglobal(data->interpreter, data->channel_name[p]); + data->reference[p] = luaL_ref(data->interpreter, LUA_REGISTRYINDEX); + if(data->reference[p] == LUA_REFNIL){ + data->reference[p] = LUA_NOREF; + } + } + } + } + + free(inst); + if(!n){ + return 0; + } + + //register the timer with the core + fprintf(stderr, "Lua backend registering 1 descriptor to core\n"); + if(mm_manage_fd(timer_fd, BACKEND_NAME, 1, NULL)){ + return 1; + } return 0; } @@ -178,6 +430,7 @@ static int lua_shutdown(){ instance** inst = NULL; lua_instance_data* data = NULL; + //fetch all instances if(mm_backend_instances(BACKEND_NAME, &n, &inst)){ fprintf(stderr, "Failed to fetch instance list\n"); return 1; @@ -192,10 +445,19 @@ static int lua_shutdown(){ free(data->channel_name[p]); } free(data->channel_name); + free(data->reference); + free(data->input); + free(data->output); free(inst[u]->impl); } free(inst); + //free module-global data + free(timer); + timer = NULL; + timers = 0; + close(timer_fd); + timer_fd = -1; fprintf(stderr, "Lua backend shut down\n"); return 0; diff --git a/backends/lua.h b/backends/lua.h index 27e1afd..ccffef7 100644 --- a/backends/lua.h +++ b/backends/lua.h @@ -17,5 +17,15 @@ static int lua_shutdown(); typedef struct /*_lua_instance_data*/ { size_t channels; char** channel_name; + int* reference; + double* input; + double* output; lua_State* interpreter; } lua_instance_data; + +typedef struct /*_lua_interval_callback*/ { + uint64_t interval; + uint64_t delta; + lua_State* interpreter; + int reference; +} lua_timer; diff --git a/backends/lua.md b/backends/lua.md index 91e8fe2..970e8e2 100644 --- a/backends/lua.md +++ b/backends/lua.md @@ -5,15 +5,32 @@ events using the Lua programming language. Every instance has it's own interpreter state which can be loaded with custom handler scripts. -To process incoming channel events, the MIDIMonster calls corresponding Lua functions with -the value (as a Lua `number` type) as parameter. To send output on a channel, the Lua environment -provides the function `output(channel-name, value)`. +To process incoming channel events, the MIDIMonster calls corresponding Lua functions (if they exist) +with the value (as a Lua `number` type) as parameter. + +The following functions are provided within the Lua interpreter for interaction with the MIDIMonster + +| Function | Usage example | Description | +|-------------------------------|-------------------------------|---------------------------------------| +| `output(string, number)` | `output("foo", 0.75)` | Output a value event to a channel | +| `interval(function, number)` | `interval(update, 100)` | Register a function to be called periodically. Intervals are milliseconds (rounded to the nearest 10 ms) | +| `input_value(string)` | `input_value("foo")` | Get the last input value on a channel | +| `output_value(string)` | `output_value("bar") | Get the last output value on a channel | + Example script: ``` function bar(value) output("foo", value / 2) end + +step = 0 +function toggle() + output("bar", step * 1.0) + step = (step + 1) % 2; +end + +interval(toggle, 1000) ``` Input values range between 0.0 and 1.0, output values are clamped to the same range. @@ -41,9 +58,12 @@ lua1.foo > lua2.bar #### Known bugs / problems -Using `output` as an input channel name to a Lua instance does not work, as the interpreter has -`output` globally assigned to the event output function. Using `output` as an output channel name -via `output("output", value)` works as intended. +Using any of the interface functions (`output`, `interval`, `input\_value`, `output\_value`) as an +input channel name to a Lua instance will not call any handler functions. +Using these names as arguments to the output and value interface functions works as intended. + +Output values will not trigger corresponding input event handlers unless the channel is mapped +back in the MIDIMonster configuration. The path to the Lua source files is relative to the current working directory. This may lead to problems when copying configuration between installations. diff --git a/configs/demo.lua b/configs/demo.lua index e816ac4..24a8396 100644 --- a/configs/demo.lua +++ b/configs/demo.lua @@ -1,21 +1,42 @@ --- This example MIDIMonstaer Lua script spreads one input channel onto multiple output +-- This example MIDIMonster Lua script spreads one input channel onto multiple output -- channels using a polynomial function evaluated at multiple points. This effect can -- be visualized e.g. with martrix (https://github.com/cbdevnet/martrix). --- This is just a demonstration of global variables -foo = 0 - -- The polynomial to evaluate -function polynomial(offset, x) - return math.exp(-20 * (x - offset) ^ 2) +function polynomial(x) + return math.exp(-40 * input_value("width") * (x - input_value("offset")) ^ 2) end --- Handler function for the input channel -function input(value) - foo = foo + 1 - print("input at ", value, foo) - +-- Evaluate and set output channels +function evaluate() for chan=0,10 do - output("out" .. chan, polynomial(value, (1 / 10) * chan)) + output("out" .. chan, polynomial((1 / 10) * chan)) + end +end + +-- Handler functions for the input channels +function offset(value) + evaluate() +end + +function width(value) + evaluate() +end + +-- This is an example showing a simple chase running on its own without the need +-- (but the possibility) for external input + +-- Global value for the current step +current_step = 0 + +function step() + if(current_step > 0) then + output("dim", 0.0) + else + output("dim", 1.0) end + + current_step = (current_step + 1) % 2 end + +interval(step, 1000) diff --git a/configs/lua.cfg b/configs/lua.cfg index 9182122..42fd27d 100644 --- a/configs/lua.cfg +++ b/configs/lua.cfg @@ -8,8 +8,9 @@ bind = 0.0.0.0 device = /dev/input/by-path/platform-i8042-serio-2-event-mouse [evdev xbox] -input = Xbox Wireless +device = /dev/input/event17 axis.ABS_X = 34300 0 65535 255 4095 +axis.ABS_Y = 34300 0 65535 255 4095 [lua lua] script = configs/demo.lua @@ -19,8 +20,9 @@ universe = 0 destination = 255.255.255.255 [map] -mouse.EV_KEY.BTN_LEFT > lua.input -xbox.EV_ABS.ABS_X > lua.input +mouse.EV_KEY.BTN_LEFT > lua.click +xbox.EV_ABS.ABS_X > lua.offset +xbox.EV_ABS.ABS_Y > lua.width art.1 < lua.out0 art.2 < lua.out1 @@ -33,3 +35,5 @@ art.8 < lua.out7 art.9 < lua.out8 art.10 < lua.out9 art.11 < lua.out10 + +art.12 < lua.dim -- cgit v1.2.3 From e776e02531aca45f424f7139e5d7304ba3096b45 Mon Sep 17 00:00:00 2001 From: cbdev Date: Sun, 14 Jul 2019 16:49:31 +0200 Subject: Work around missing timerfd on OSX/Windows --- backend.c | 2 +- backends/lua.c | 40 ++++++++++++++++++++++++++++++++++++++++ backends/lua.h | 6 ++++++ backends/lua.md | 4 ++-- configs/lua.cfg | 2 +- 5 files changed, 50 insertions(+), 4 deletions(-) (limited to 'configs') diff --git a/backend.c b/backend.c index ed5e6db..5df5d73 100644 --- a/backend.c +++ b/backend.c @@ -227,7 +227,7 @@ struct timeval backend_timeout(){ struct timeval tv = { secs, - msecs + msecs * 1000 }; return tv; } diff --git a/backends/lua.c b/backends/lua.c index 8069f8f..7fcd0ab 100644 --- a/backends/lua.c +++ b/backends/lua.c @@ -10,10 +10,17 @@ static size_t timers = 0; static lua_timer* timer = NULL; uint64_t timer_interval = 0; +#ifdef MMBACKEND_LUA_TIMERFD static int timer_fd = -1; +#else +static uint64_t last_timestamp; +#endif int init(){ backend lua = { + #ifndef MMBACKEND_LUA_TIMERFD + .interval = lua_interval, + #endif .name = BACKEND_NAME, .conf = lua_configure, .create = lua_instance, @@ -31,21 +38,33 @@ int init(){ return 1; } + #ifdef MMBACKEND_LUA_TIMERFD //create the timer to expire intervals timer_fd = timerfd_create(CLOCK_MONOTONIC, TFD_NONBLOCK); if(timer_fd < 0){ fprintf(stderr, "Failed to create timer for Lua backend\n"); return 1; } + #endif return 0; } +static uint32_t lua_interval(){ + //FIXME Return delta for next timer here + if(timer_interval){ + return timer_interval; + } + return 1000; +} + static int lua_update_timerfd(){ uint64_t interval, gcd, residual; size_t n = 0; + #ifdef MMBACKEND_LUA_TIMERFD struct itimerspec timer_config = { 0 }; + #endif //find the minimum for the lower interval bounds for(n = 0; n < timers; n++){ @@ -72,16 +91,20 @@ static int lua_update_timerfd(){ } } + #ifdef MMBACKEND_LUA_TIMERFD timer_config.it_interval.tv_sec = timer_config.it_value.tv_sec = interval / 1000; timer_config.it_interval.tv_nsec = timer_config.it_value.tv_nsec = (interval % 1000) * 1e6; + #endif } if(interval == timer_interval){ return 0; } + #ifdef MMBACKEND_LUA_TIMERFD //configure the new interval timerfd_settime(timer_fd, 0, &timer_config, NULL); + #endif timer_interval = interval; return 0; } @@ -359,6 +382,7 @@ static int lua_handle(size_t num, managed_fd* fds){ uint64_t delta = timer_interval; size_t n; + #ifdef MMBACKEND_LUA_TIMERFD if(!num){ return 0; } @@ -368,6 +392,18 @@ static int lua_handle(size_t num, managed_fd* fds){ fprintf(stderr, "Failed to read from Lua timer: %s\n", strerror(errno)); return 1; } + #else + if(!last_timestamp){ + last_timestamp = mm_timestamp(); + } + delta = mm_timestamp() - last_timestamp; + last_timestamp = mm_timestamp(); + #endif + + //no timers active + if(!timer_interval){ + return 0; + } //add delta to all active timers for(n = 0; n < timers; n++){ @@ -418,11 +454,13 @@ static int lua_start(){ return 0; } + #ifdef MMBACKEND_LUA_TIMERFD //register the timer with the core fprintf(stderr, "Lua backend registering 1 descriptor to core\n"); if(mm_manage_fd(timer_fd, BACKEND_NAME, 1, NULL)){ return 1; } + #endif return 0; } @@ -457,8 +495,10 @@ static int lua_shutdown(){ free(timer); timer = NULL; timers = 0; + #ifdef MMBACKEND_LUA_TIMERFD close(timer_fd); timer_fd = -1; + #endif fprintf(stderr, "Lua backend shut down\n"); return 0; diff --git a/backends/lua.h b/backends/lua.h index ccffef7..7aad891 100644 --- a/backends/lua.h +++ b/backends/lua.h @@ -4,6 +4,11 @@ #include #include +//OSX and Windows don't have the cool new toys... +#ifdef __linux__ + #define MMBACKEND_LUA_TIMERFD +#endif + int init(); static int lua_configure(char* option, char* value); static int lua_configure_instance(instance* inst, char* option, char* value); @@ -13,6 +18,7 @@ static int lua_set(instance* inst, size_t num, channel** c, channel_value* v); static int lua_handle(size_t num, managed_fd* fds); static int lua_start(); static int lua_shutdown(); +static uint32_t lua_interval(); typedef struct /*_lua_instance_data*/ { size_t channels; diff --git a/backends/lua.md b/backends/lua.md index 970e8e2..e273b28 100644 --- a/backends/lua.md +++ b/backends/lua.md @@ -15,7 +15,7 @@ The following functions are provided within the Lua interpreter for interaction | `output(string, number)` | `output("foo", 0.75)` | Output a value event to a channel | | `interval(function, number)` | `interval(update, 100)` | Register a function to be called periodically. Intervals are milliseconds (rounded to the nearest 10 ms) | | `input_value(string)` | `input_value("foo")` | Get the last input value on a channel | -| `output_value(string)` | `output_value("bar") | Get the last output value on a channel | +| `output_value(string)` | `output_value("bar")` | Get the last output value on a channel | Example script: @@ -58,7 +58,7 @@ lua1.foo > lua2.bar #### Known bugs / problems -Using any of the interface functions (`output`, `interval`, `input\_value`, `output\_value`) as an +Using any of the interface functions (`output`, `interval`, `input_value`, `output_value`) as an input channel name to a Lua instance will not call any handler functions. Using these names as arguments to the output and value interface functions works as intended. diff --git a/configs/lua.cfg b/configs/lua.cfg index 42fd27d..b892e91 100644 --- a/configs/lua.cfg +++ b/configs/lua.cfg @@ -8,7 +8,7 @@ bind = 0.0.0.0 device = /dev/input/by-path/platform-i8042-serio-2-event-mouse [evdev xbox] -device = /dev/input/event17 +;device = /dev/input/event17 axis.ABS_X = 34300 0 65535 255 4095 axis.ABS_Y = 34300 0 65535 255 4095 -- cgit v1.2.3 From 5bd8e81e2821f1378c6773fbc1f06df063dbbd22 Mon Sep 17 00:00:00 2001 From: cbdev Date: Sat, 20 Jul 2019 18:10:56 +0200 Subject: Implement multi-channel mapping syntax --- README.md | 30 ++++++- TODO | 2 +- config.c | 196 +++++++++++++++++++++++++++++++++++++++++---- configs/launchctl-sacn.cfg | 25 ++---- midimonster.h | 53 +++++++++++- monster.cfg | 46 ++--------- 6 files changed, 274 insertions(+), 78 deletions(-) (limited to 'configs') diff --git a/README.md b/README.md index ee907a6..bce7b71 100644 --- a/README.md +++ b/README.md @@ -56,10 +56,24 @@ lines of the form `option = value`. Lines starting with a semicolon are treated as comments and ignored. Inline comments are not currently supported. +Example configuration files may be found in [configs/](configs/). + +### Backend and instance configuration + A configuration section may either be a *backend configuration* section, started by `[backend ]`, an *instance configuration* section, started by `[ ]` or a *mapping* section started by `[map]`. +Backends document their global options in their [backend documentation](#backend-documentation). +Some backends may not require global configuration, in which case the configuration +section for that particular backend can be omitted. + +To make an instance available for mapping channels, it requires at least the +`[ ]` configuration stanza. Most backends require +additional configuration for their instances. + +### Channel mapping + The `[map]` section consists of lines of channel-to-channel assignments, reading like ``` @@ -76,7 +90,21 @@ output eachothers events. The last line is a shorter way to create a bi-directional mapping. -Example configuration files may be found in [configs/](configs/). +### Multi-channel mapping + +To make mapping large contiguous sets of channels easier, channel names may contain +expressions of the form `{..}`, with *start* and *end* being positive integers +delimiting a range of channels. Multiple such expressions may be used in one channel +specification, with the rightmost expression being incremented (or decremented) first for +evaluation. + +Both sides of a multi-channel assignment need to have the same number of channels. + +Example multi-channel mapping: + +``` +instance-a.channel{1..10} > instance-b.{10..1} +``` ## Backend documentation diff --git a/TODO b/TODO index f39dae0..76f2c4e 100644 --- a/TODO +++ b/TODO @@ -4,5 +4,5 @@ Optimize core channel search (store backend offset) Printing backend document example configs -lua timer evdev relative axis size +mm_managed_fd.impl is not freed currently diff --git a/config.c b/config.c index b81aeaf..24f1223 100644 --- a/config.c +++ b/config.c @@ -21,25 +21,164 @@ static backend* current_backend = NULL; static instance* current_instance = NULL; static char* config_trim_line(char* in){ - ssize_t u; + ssize_t n; //trim front for(; *in && !isgraph(*in); in++){ } //trim back - for(u = strlen(in); u >= 0 && !isgraph(in[u]); u--){ - in[u] = 0; + for(n = strlen(in); n >= 0 && !isgraph(in[n]); n--){ + in[n] = 0; } return in; } +static int config_glob_parse(channel_glob* glob, char* spec, size_t length){ + char* parse_offset = NULL; + //FIXME might want to allow negative delimiters at some point + + //first interval member + glob->limits.u64[0] = strtoul(spec, &parse_offset, 10); + if(!parse_offset || parse_offset - spec >= length || strncmp(parse_offset, "..", 2)){ + return 1; + } + + parse_offset += 2; + //second interval member + glob->limits.u64[1] = strtoul(parse_offset, &parse_offset, 10); + if(!parse_offset || parse_offset - spec != length || *parse_offset != '}'){ + return 1; + } + + //calculate number of channels within interval + if(glob->limits.u64[0] < glob->limits.u64[1]){ + glob->values = glob->limits.u64[1] - glob->limits.u64[0] + 1; + } + else if(glob->limits.u64[0] > glob->limits.u64[1]){ + glob->values = glob->limits.u64[0] - glob->limits.u64[1] + 1; + } + else{ + glob->values = 1; + } + + return 0; +} + +static int config_glob_scan(instance* inst, channel_spec* spec){ + char* glob_start = spec->spec, *glob_end = NULL; + size_t u; + + //assume a spec is one channel as default + spec->channels = 1; + + //scan and mark globs + for(glob_start = strchr(glob_start, '{'); glob_start; glob_start = strchr(glob_start, '{')){ + glob_end = strchr(glob_start, '}'); + if(!glob_end){ + fprintf(stderr, "Failed to parse channel spec, unterminated glob: %s\n", spec->spec); + return 1; + } + + spec->glob = realloc(spec->glob, (spec->globs + 1) * sizeof(channel_glob)); + if(!spec->glob){ + fprintf(stderr, "Failed to allocate memory\n"); + return 1; + } + + spec->glob[spec->globs].offset[0] = glob_start - spec->spec; + spec->glob[spec->globs].offset[1] = glob_end - spec->spec; + spec->globs++; + + //skip this opening brace + glob_start++; + } + + //try to parse globs internally + spec->internal = 1; + for(u = 0; u < spec->globs; u++){ + if(config_glob_parse(spec->glob + u, + spec->spec + spec->glob[u].offset[0] + 1, + spec->glob[u].offset[1] - spec->glob[u].offset[0] - 1)){ + spec->internal = 0; + break; + } + } + if(!spec->internal){ + //TODO try to parse globs externally + fprintf(stderr, "Failed to parse glob %lu in %s internally\n", u + 1, spec->spec); + return 1; + } + + //calculate channel total + for(u = 0; u < spec->globs; u++){ + spec->channels *= spec->glob[u].values; + } + return 0; +} + +static channel* config_glob_resolve(instance* inst, channel_spec* spec, uint64_t n){ + size_t glob = 0, glob_length; + ssize_t bytes = 0; + uint64_t current_value = 0; + channel* result = NULL; + char* resolved_spec = strdup(spec->spec); + + if(!resolved_spec){ + fprintf(stderr, "Failed to allocate memory\n"); + return NULL; + } + + //TODO if not internal, try to resolve externally + + //iterate and resolve globs + for(glob = spec->globs; glob > 0; glob--){ + current_value = spec->glob[glob - 1].limits.u64[0] + (n % spec->glob[glob - 1].values); + if(spec->glob[glob - 1].limits.u64[0] > spec->glob[glob - 1].limits.u64[1]){ + current_value = spec->glob[glob - 1].limits.u64[0] - (n % spec->glob[glob - 1].values); + } + glob_length = spec->glob[glob - 1].offset[1] - spec->glob[glob - 1].offset[0]; + n /= spec->glob[glob - 1].values; + + //write out value + bytes = snprintf(resolved_spec + spec->glob[glob - 1].offset[0], + glob_length, + "%lu", + current_value); + if(bytes > glob_length){ + fprintf(stderr, "Internal error resolving glob %s\n", spec->spec); + goto bail; + } + + //move trailing data + if(bytes < glob_length){ + memmove(resolved_spec + spec->glob[glob - 1].offset[0] + bytes, + resolved_spec + spec->glob[glob - 1].offset[1] + 1, + strlen(spec->spec) - spec->glob[glob - 1].offset[1]); + } + } + + result = inst->backend->channel(inst, resolved_spec); + if(spec->globs && !result){ + fprintf(stderr, "Failed to match multichannel evaluation %s to a channel\n", resolved_spec); + } + +bail: + free(resolved_spec); + return result; +} + static int config_map(char* to_raw, char* from_raw){ //create a copy because the original pointer may be used multiple times char* to = strdup(to_raw), *from = strdup(from_raw); - char* chanspec_to = to, *chanspec_from = from; + channel_spec spec_to = { + .spec = to + }, spec_from = { + .spec = from + }; instance* instance_to = NULL, *instance_from = NULL; channel* channel_from = NULL, *channel_to = NULL; + uint64_t n = 0; int rv = 1; if(!from || !to){ @@ -50,21 +189,21 @@ static int config_map(char* to_raw, char* from_raw){ } //separate channel spec from instance - for(; *chanspec_to && *chanspec_to != '.'; chanspec_to++){ + for(; *(spec_to.spec) && *(spec_to.spec) != '.'; spec_to.spec++){ } - for(; *chanspec_from && *chanspec_from != '.'; chanspec_from++){ + for(; *(spec_from.spec) && *(spec_from.spec) != '.'; spec_from.spec++){ } - if(!*chanspec_to || !*chanspec_from){ + if(!spec_from.spec[0] || !spec_to.spec[0]){ fprintf(stderr, "Mapping does not contain a proper instance specification\n"); goto done; } //terminate - *chanspec_to = *chanspec_from = 0; - chanspec_to++; - chanspec_from++; + spec_from.spec[0] = spec_to.spec[0] = 0; + spec_from.spec++; + spec_to.spec++; //find matching instances instance_to = instance_match(to); @@ -75,16 +214,41 @@ static int config_map(char* to_raw, char* from_raw){ goto done; } - channel_from = instance_from->backend->channel(instance_from, chanspec_from); - channel_to = instance_to->backend->channel(instance_to, chanspec_to); - - if(!channel_from || !channel_to){ - fprintf(stderr, "Failed to parse channel specifications\n"); + //scan for globs + if(config_glob_scan(instance_to, &spec_to) + || config_glob_scan(instance_from, &spec_from)){ goto done; } + + if(spec_to.channels != spec_from.channels + || spec_to.channels == 0 + || spec_from.channels == 0){ + fprintf(stderr, "Multi-channel specification size mismatch: %s.%s (%lu channels) - %s.%s (%lu channels)\n", + instance_from->name, + spec_from.spec, + spec_from.channels, + instance_to->name, + spec_to.spec, + spec_to.channels); + goto done; + } + + //iterate, resolve globs and map + rv = 0; + for(n = 0; !rv && n < spec_from.channels; n++){ + channel_from = config_glob_resolve(instance_from, &spec_from, n); + channel_to = config_glob_resolve(instance_to, &spec_to, n); + + if(!channel_from || !channel_to){ + rv = 1; + goto done; + } + rv |= mm_map_channel(channel_from, channel_to); + } - rv = mm_map_channel(channel_from, channel_to); done: + free(spec_from.glob); + free(spec_to.glob); free(from); free(to); return rv; diff --git a/configs/launchctl-sacn.cfg b/configs/launchctl-sacn.cfg index c2dec84..02cd152 100644 --- a/configs/launchctl-sacn.cfg +++ b/configs/launchctl-sacn.cfg @@ -6,34 +6,19 @@ [backend midi] name = MIDIMonster -[backend sacn] +[backend artnet] bind = 0.0.0.0 [midi lc] read = Launch Control -[sacn out] -universe = 1 -priority = 100 +[artnet out] +universe = 0 +destination = 255.255.255.255 [map] -lc.ch0.cc0 > out.1 -lc.ch0.cc1 > out.2 -lc.ch0.cc2 > out.3 -lc.ch0.cc3 > out.4 -lc.ch0.cc4 > out.5 -lc.ch0.cc5 > out.6 -lc.ch0.cc6 > out.7 -lc.ch0.cc7 > out.8 -lc.ch0.cc8 > out.9 -lc.ch0.cc9 > out.10 -lc.ch0.cc10 > out.11 -lc.ch0.cc11 > out.12 -lc.ch0.cc12 > out.13 -lc.ch0.cc13 > out.14 -lc.ch0.cc14 > out.15 -lc.ch0.cc15 > out.16 +lc.ch0.cc{0..15} > out.{1..16} lc.ch0.note0 > out.1 lc.ch0.note1 > out.2 diff --git a/midimonster.h b/midimonster.h index 8a18155..1fc85b7 100644 --- a/midimonster.h +++ b/midimonster.h @@ -3,9 +3,15 @@ #include #include #include + +/* Straight-forward min / max macros */ #define max(a,b) (((a) > (b)) ? (a) : (b)) #define min(a,b) (((a) < (b)) ? (a) : (b)) + +/* Clamp a value to a range */ #define clamp(val,max,min) (((val) > (max)) ? (max) : (((val) < (min)) ? (min) : (val))) + +/* Debug messages only compile in when DEBUG is set */ #ifdef DEBUG #define DBGPF(format, ...) fprintf(stderr, (format), __VA_ARGS__) #define DBG(message) fprintf(stderr, "%s", (message)) @@ -14,10 +20,13 @@ #define DBG(message) #endif +/* Pull in additional defines for non-linux platforms */ #include "portability.h" +/* Default configuration file name to read when no other is specified */ #define DEFAULT_CFG "monster.cfg" +/* Forward declare some of the structs so we can use them in eachother */ struct _channel_value; struct _backend_channel; struct _backend_instance; @@ -86,6 +95,7 @@ typedef int (*mmbackend_start)(); typedef uint32_t (*mmbackend_interval)(); typedef int (*mmbackend_shutdown)(); +/* Channel event value, .normalised is used by backends to determine channel values */ typedef struct _channel_value { union { double dbl; @@ -94,6 +104,10 @@ typedef struct _channel_value { double normalised; } channel_value; +/* + * Backend callback structure + * Used to register a backend with the core using mm_backend_register() + */ typedef struct /*_mm_backend*/ { char* name; mmbackend_configure conf; @@ -108,6 +122,10 @@ typedef struct /*_mm_backend*/ { mmbackend_interval interval; } backend; +/* + * Backend instance structure - do not allocate directly! + * Use the memory returned by mm_instance() + */ typedef struct _backend_instance { backend* backend; uint64_t ident; @@ -115,20 +133,51 @@ typedef struct _backend_instance { char* name; } instance; +/* + * Channel specification glob + */ +typedef struct /*_mm_channel_glob*/ { + size_t offset[2]; + union { + void* impl; + uint64_t u64[2]; + } limits; + uint64_t values; +} channel_glob; + +/* + * (Multi-)Channel specification + */ +typedef struct /*_mm_channel_spec*/ { + char* spec; + uint8_t internal; + size_t channels; + size_t globs; + channel_glob* glob; +} channel_spec; + +/* + * Instance channel structure + * Backends may either manage their own channel registry + * or use the memory returned by mm_channel() + */ typedef struct _backend_channel { instance* instance; uint64_t ident; void* impl; } channel; -//FIXME might be replaced by struct pollfd -//FIXME who frees impl +/* + * File descriptor management structure + * Register for the core event loop using mm_manage_fd() + */ typedef struct _managed_fd { int fd; backend* backend; void* impl; } managed_fd; +/* Internal channel mapping structure - Core use only */ typedef struct /*_mm_channel_mapping*/ { channel* from; size_t destinations; diff --git a/monster.cfg b/monster.cfg index 0760571..7db3ec3 100644 --- a/monster.cfg +++ b/monster.cfg @@ -1,46 +1,16 @@ -[backend sacn] -name = sACN source -bind = 0.0.0.0 - [backend artnet] bind = 0.0.0.0 -[backend ola] - [artnet art] -universe = 1 -dest = 129.13.215.0 - -[backend midi] -name = Monster - -;[evdev in] -;input = Xbox Wireless Controller - -[sacn sacn] -universe = 1 -priority = 100 +universe = 0 +dest = 255.255.255.255 -[midi midi] -read = Axiom +[evdev mouse] +input = TPPS -;[ola ola] +[loopback loop] [map] -;in.EV_ABS.ABS_X > sacn.1+2 -;in.EV_ABS.ABS_Y > sacn.3 -;in.EV_ABS.ABS_X > art.1+2 -;in.EV_ABS.ABS_Y > art.3 -;in.EV_ABS.ABS_X > ola.1+2 -;in.EV_ABS.ABS_Y > ola.3 -;in.EV_KEY.BTN_THUMBL > sacn.4 -;in.EV_KEY.BTN_THUMBR > sacn.5 -;in.EV_ABS.ABS_GAS > sacn.6+7 -;in.EV_ABS.ABS_BRAKE > sacn.8 -;ola.1 > midi.cc0.1 -;ola.2+3 > midi.cc0.2 -midi.ch0.pitch > midi.ch0.cc1 -midi.ch0.aftertouch > midi.ch0.cc2 -midi.ch0.cc71 > midi.ch0.pitch -midi.ch0.cc74 > midi.ch0.aftertouch -midi.ch0.cc91 > midi.ch0.pressure1 +art.{3..4}{4..3} > loop.chan{4..3}{3..4} +art.{1..10} > loop.data{1..10} +art.{500..599} > loop.test{500..599} -- cgit v1.2.3 From 1f0de6d91c14217ae3893da0e4b6089b799ed026 Mon Sep 17 00:00:00 2001 From: cbdev Date: Wed, 24 Jul 2019 21:30:40 +0200 Subject: Update example configurations to new syntax, add comments --- TODO | 2 +- configs/evdev.cfg | 32 ++++++++ configs/evdev.conf | 31 -------- configs/launchctl-sacn.cfg | 10 +-- configs/lua.cfg | 13 +-- configs/midi-osc.cfg | 25 ++---- configs/osc-artnet.cfg | 51 +----------- configs/osc-kbd.cfg | 16 +--- configs/unifest-17.cfg | 191 +++++++++------------------------------------ 9 files changed, 82 insertions(+), 289 deletions(-) create mode 100644 configs/evdev.cfg delete mode 100644 configs/evdev.conf (limited to 'configs') diff --git a/TODO b/TODO index 76f2c4e..d114640 100644 --- a/TODO +++ b/TODO @@ -1,7 +1,7 @@ MIDI NRPN Note source in channel value struct Optimize core channel search (store backend offset) -Printing backend +Printing backend / Verbose mode document example configs evdev relative axis size diff --git a/configs/evdev.cfg b/configs/evdev.cfg new file mode 100644 index 0000000..bb27caf --- /dev/null +++ b/configs/evdev.cfg @@ -0,0 +1,32 @@ +; Map the (admittedly weird) bluetooth profile of an Xbox One +; Gamepad to some ArtNet output channels. Uses both analog joysticks +; and the analog triggers. + +[backend artnet] +bind = 0.0.0.0 6454 +net = 0 + +[evdev xbox] +device = /dev/input/event14 + +[artnet out] +uni = 0 +dest = 255.255.255.255 + +[map] +xbox.EV_ABS.ABS_X > out.1+2 +xbox.EV_ABS.ABS_Y > out.3+4 + +xbox.EV_ABS.ABS_Z > out.16+17 +xbox.EV_ABS.ABS_RZ > out.18+19 + +xbox.EV_ABS.ABS_BRAKE > out.8 +xbox.EV_ABS.ABS_GAS > out.23 + +xbox.EV_KEY.BTN_NORTH > out.5 +xbox.EV_KEY.BTN_EAST > out.6 +xbox.EV_KEY.BTN_SOUTH > out.7 + +xbox.EV_KEY.BTN_NORTH > out.20 +xbox.EV_KEY.BTN_EAST > out.21 +xbox.EV_KEY.BTN_SOUTH > out.22 diff --git a/configs/evdev.conf b/configs/evdev.conf deleted file mode 100644 index 386e154..0000000 --- a/configs/evdev.conf +++ /dev/null @@ -1,31 +0,0 @@ -[backend artnet] -bind = * 6454 -net = 0 - -[evdev xbox] -device = /dev/input/event14 -axis.ABS_X = 34300 0 65535 255 4095 -axis.ABS_RZ = 34300 0 65535 255 4095 -axis.ABS_Y = 34300 0 65535 255 4095 - -[artnet out] -uni = 0 -dest = 129.13.215.127 - -[map] -xbox.EV_ABS.ABS_X > out.1+2 -xbox.EV_ABS.ABS_Y > out.3+4 - -xbox.EV_ABS.ABS_Z > out.16+17 -xbox.EV_ABS.ABS_RZ > out.18+19 - -xbox.EV_ABS.ABS_BRAKE > out.8 -xbox.EV_ABS.ABS_GAS > out.23 - -xbox.EV_KEY.BTN_NORTH > out.5 -xbox.EV_KEY.BTN_EAST > out.6 -xbox.EV_KEY.BTN_SOUTH > out.7 - -xbox.EV_KEY.BTN_NORTH > out.20 -xbox.EV_KEY.BTN_EAST > out.21 -xbox.EV_KEY.BTN_SOUTH > out.22 diff --git a/configs/launchctl-sacn.cfg b/configs/launchctl-sacn.cfg index 02cd152..781ca40 100644 --- a/configs/launchctl-sacn.cfg +++ b/configs/launchctl-sacn.cfg @@ -19,12 +19,4 @@ destination = 255.255.255.255 [map] lc.ch0.cc{0..15} > out.{1..16} - -lc.ch0.note0 > out.1 -lc.ch0.note1 > out.2 -lc.ch0.note2 > out.3 -lc.ch0.note3 > out.4 -lc.ch0.note4 > out.5 -lc.ch0.note5 > out.6 -lc.ch0.note6 > out.7 -lc.ch0.note7 > out.8 +lc.ch0.note{0..7} > out.{1..8} diff --git a/configs/lua.cfg b/configs/lua.cfg index b892e91..af17496 100644 --- a/configs/lua.cfg +++ b/configs/lua.cfg @@ -24,16 +24,5 @@ mouse.EV_KEY.BTN_LEFT > lua.click xbox.EV_ABS.ABS_X > lua.offset xbox.EV_ABS.ABS_Y > lua.width -art.1 < lua.out0 -art.2 < lua.out1 -art.3 < lua.out2 -art.4 < lua.out3 -art.5 < lua.out4 -art.6 < lua.out5 -art.7 < lua.out6 -art.8 < lua.out7 -art.9 < lua.out8 -art.10 < lua.out9 -art.11 < lua.out10 - +art.{1..11} < lua.out{0..10} art.12 < lua.dim diff --git a/configs/midi-osc.cfg b/configs/midi-osc.cfg index 755077c..1b3ccd6 100644 --- a/configs/midi-osc.cfg +++ b/configs/midi-osc.cfg @@ -1,14 +1,18 @@ +; Translate a MIDI fader wing into an OSC fader view and vice versa + [backend midi] name = MIDIMonster [backend artnet] -bind = * 6454 +bind = 0.0.0.0 6454 net = 0 [osc touch] bind = * 8000 dest = learn@8000 root = /4 + +; Pre-declare the fader values so the range mapping is correct /fader1 = f 0.0 1.0 /fader2 = f 0.0 1.0 /fader3 = f 0.0 1.0 @@ -33,20 +37,5 @@ write = BCF [map] -bcf.ch0.cc81 <> touch./fader1 -bcf.ch0.cc82 <> touch./fader2 -bcf.ch0.cc83 <> touch./fader3 -bcf.ch0.cc84 <> touch./fader4 -bcf.ch0.cc85 <> touch./fader5 -bcf.ch0.cc86 <> touch./fader6 -bcf.ch0.cc87 <> touch./fader7 -bcf.ch0.cc88 <> touch./fader8 - -bcf.ch0.cc81 <> touch./multifader1/1 -bcf.ch0.cc82 <> touch./multifader1/2 -bcf.ch0.cc83 <> touch./multifader1/3 -bcf.ch0.cc84 <> touch./multifader1/4 -bcf.ch0.cc85 <> touch./multifader1/5 -bcf.ch0.cc86 <> touch./multifader1/6 -bcf.ch0.cc87 <> touch./multifader1/7 -bcf.ch0.cc88 <> touch./multifader1/8 +bcf.ch0.cc{81..88} <> touch./fader{1..8} +bcf.ch0.cc{81..88} <> touch./multifader1/{1..8} diff --git a/configs/osc-artnet.cfg b/configs/osc-artnet.cfg index 3eeba2e..ab1d767 100644 --- a/configs/osc-artnet.cfg +++ b/configs/osc-artnet.cfg @@ -12,52 +12,5 @@ dest = learn@8001 destination = 255.255.255.255 [map] -touch./4/multifader1/1 > out.1 -touch./4/multifader1/2 > out.2 -touch./4/multifader1/3 > out.3 -touch./4/multifader1/4 > out.4 -touch./4/multifader1/5 > out.5 -touch./4/multifader1/6 > out.6 -touch./4/multifader1/7 > out.7 -touch./4/multifader1/8 > out.8 -touch./4/multifader1/9 > out.9 -touch./4/multifader1/10 > out.10 -touch./4/multifader1/11 > out.11 -touch./4/multifader1/12 > out.12 -touch./4/multifader1/13 > out.13 -touch./4/multifader1/14 > out.14 -touch./4/multifader1/15 > out.15 -touch./4/multifader1/16 > out.16 -touch./4/multifader1/17 > out.17 -touch./4/multifader1/18 > out.18 -touch./4/multifader1/19 > out.19 -touch./4/multifader1/20 > out.20 -touch./4/multifader1/21 > out.21 -touch./4/multifader1/22 > out.22 -touch./4/multifader1/23 > out.23 -touch./4/multifader1/24 > out.24 - -touch./4/multifader2/1 > out.25 -touch./4/multifader2/2 > out.26 -touch./4/multifader2/3 > out.27 -touch./4/multifader2/4 > out.28 -touch./4/multifader2/5 > out.29 -touch./4/multifader2/6 > out.30 -touch./4/multifader2/7 > out.31 -touch./4/multifader2/8 > out.32 -touch./4/multifader2/9 > out.33 -touch./4/multifader2/10 > out.34 -touch./4/multifader2/11 > out.35 -touch./4/multifader2/12 > out.36 -touch./4/multifader2/13 > out.37 -touch./4/multifader2/14 > out.38 -touch./4/multifader2/15 > out.39 -touch./4/multifader2/16 > out.40 -touch./4/multifader2/17 > out.41 -touch./4/multifader2/18 > out.42 -touch./4/multifader2/19 > out.43 -touch./4/multifader2/20 > out.44 -touch./4/multifader2/21 > out.45 -touch./4/multifader2/22 > out.46 -touch./4/multifader2/23 > out.47 -touch./4/multifader2/24 > out.48 +touch./4/multifader1/{1..24} > out.{1..24} +touch./4/multifader2/{1..24} > out.{25..48} diff --git a/configs/osc-kbd.cfg b/configs/osc-kbd.cfg index eb80378..bd2e2c0 100644 --- a/configs/osc-kbd.cfg +++ b/configs/osc-kbd.cfg @@ -1,4 +1,5 @@ -; Maps a TouchOSC simpl keyboard layout to MIDI notes +; Maps a TouchOSC simple keyboard layout to MIDI notes +; and writes them out to a FLUIDSynth instance [backend midi] name = MIDIMonster @@ -11,15 +12,4 @@ bind = * 8000 dest = learn@8001 [map] -pad./1/push1 > out.ch0.note60 -pad./1/push2 > out.ch0.note61 -pad./1/push3 > out.ch0.note62 -pad./1/push4 > out.ch0.note63 -pad./1/push5 > out.ch0.note64 -pad./1/push6 > out.ch0.note65 -pad./1/push7 > out.ch0.note66 -pad./1/push8 > out.ch0.note67 -pad./1/push9 > out.ch0.note68 -pad./1/push10 > out.ch0.note69 -pad./1/push11 > out.ch0.note70 -pad./1/push12 > out.ch0.note71 +pad./1/push{1..12} > out.ch0.note{60..71} diff --git a/configs/unifest-17.cfg b/configs/unifest-17.cfg index 47e9ec2..2504550 100644 --- a/configs/unifest-17.cfg +++ b/configs/unifest-17.cfg @@ -1,27 +1,36 @@ -; Note that this configuration file was originally written with -; an older syntax and thus only contains right-to-left mappings +; This configuration was used as central control translator for the following tasks +; * Translate 2 Fader Wings and 2 Launch Control from MIDI CC to MIDI notes +; to be used as input to the GrandMA (connected to OUT A on Fader 1) +; Since both fader wings have the same name, we need to refer to them by portid +; -> Instances fader1, fader2, lc2, grandma +; * Remap buttons from a LaunchPad as input to the GrandMA +; -> Instances launchpad, grandma +; * Translate the rotaries of one Launch Control to ArtNet for additional effect control +; -> Instances lc1, xlaser +; +; Note that the MIDI port specifications might not be reusable 1:1 [backend midi] name = MIDIMonster [backend artnet] -bind = * 6454 +bind = 0.0.0.0 6454 net = 0 ; XLaser environment -[artnet claudius] -uni = 0 +[artnet xlaser] +universe = 0 ; MIDI input devices -[midi pad] +[midi launchpad] read = Launchpad write = Launchpad -[midi bcf1] +[midi fader1] read = 20:0 write = 20:0 -[midi bcf2] +[midi fader2] read = 36:0 write = 36:0 @@ -34,162 +43,32 @@ read = 32:0 write = 32:0 ; Output MIDI via OUT A on BCF -[midi out] +[midi grandma] write = 36:1 read = 36:1 [map] -; ArtNet -claudius.1 < lc1.ch0.cc1 -claudius.2 < lc1.ch0.cc2 -claudius.3 < lc1.ch0.cc3 -claudius.4 < lc1.ch0.cc4 -claudius.5 < lc1.ch0.cc5 -claudius.6 < lc1.ch0.cc6 -claudius.7 < lc1.ch0.cc7 -claudius.8 < lc1.ch0.cc8 -claudius.9 < lc1.ch0.cc9 -claudius.10 < lc1.ch0.cc10 -claudius.11 < lc1.ch0.cc11 -claudius.12 < lc1.ch0.cc12 -claudius.13 < lc1.ch0.cc13 -claudius.14 < lc1.ch0.cc14 -claudius.15 < lc1.ch0.cc15 -claudius.16 < lc1.ch0.cc16 +; Effect control +xlaser.{1..16} < lc1.ch0.cc{1..16} -; BCF Fader -out.ch0.ch0.note< bcf1.ch0.cc81 -out.ch0.note1 < bcf1.ch0.cc82 -out.ch0.note2 < bcf1.ch0.cc83 -out.ch0.note3 < bcf1.ch0.cc84 -out.ch0.note4 < bcf1.ch0.cc85 -out.ch0.note5 < bcf1.ch0.cc86 -out.ch0.note6 < bcf1.ch0.cc87 -out.ch0.note7 < bcf1.ch0.cc88 -out.ch0.note8 < bcf2.ch0.cc81 -out.ch0.note9 < bcf2.ch0.cc82 -out.ch0.note10 < bcf2.ch0.cc83 -out.ch0.note11 < bcf2.ch0.cc84 -out.ch0.note12 < bcf2.ch0.cc85 -out.ch0.note13 < bcf2.ch0.cc86 -out.ch0.note14 < bcf2.ch0.cc87 -out.ch0.note15 < bcf2.ch0.cc88 +; BCF Faders to GrandMA +grandma.ch0.note{0..7} < fader1.ch0.cc{81..88} +grandma.ch0.note{8..15} < fader2.ch0.cc{81..88} ; LC Rotary -out.ch0.note16 < lc1.ch0.cc1 -out.ch0.note17 < lc1.ch0.cc2 -out.ch0.note18 < lc1.ch0.cc3 -out.ch0.note19 < lc1.ch0.cc4 -out.ch0.note20 < lc1.ch0.cc5 -out.ch0.note21 < lc1.ch0.cc6 -out.ch0.note22 < lc1.ch0.cc7 -out.ch0.note23 < lc1.ch0.cc8 -out.ch0.note24 < lc1.ch0.cc9 -out.ch0.note25 < lc1.ch0.cc10 -out.ch0.note26 < lc1.ch0.cc11 -out.ch0.note27 < lc1.ch0.cc12 -out.ch0.note28 < lc1.ch0.cc13 -out.ch0.note29 < lc1.ch0.cc14 -out.ch0.note30 < lc1.ch0.cc15 -out.ch0.note31 < lc1.ch0.cc16 -out.ch0.note32 < lc2.ch0.cc1 -out.ch0.note33 < lc2.ch0.cc2 -out.ch0.note34 < lc2.ch0.cc3 -out.ch0.note35 < lc2.ch0.cc4 -out.ch0.note36 < lc2.ch0.cc5 -out.ch0.note37 < lc2.ch0.cc6 -out.ch0.note38 < lc2.ch0.cc7 -out.ch0.note39 < lc2.ch0.cc8 -out.ch0.note40 < lc2.ch0.cc9 -out.ch0.note41 < lc2.ch0.cc10 -out.ch0.note42 < lc2.ch0.cc11 -out.ch0.note43 < lc2.ch0.cc12 -out.ch0.note44 < lc2.ch0.cc13 -out.ch0.note45 < lc2.ch0.cc14 -out.ch0.note46 < lc2.ch0.cc15 -out.ch0.note47 < lc2.ch0.cc16 +grandma.ch0.note{16..31} < lc1.ch0.cc{1..16} +grandma.ch0.note{32..47} < lc2.ch0.cc{1..16} ; LC Button -out.ch0.note48 < lc1.ch0.note0 -out.ch0.note49 < lc1.ch0.note1 -out.ch0.note50 < lc1.ch0.note2 -out.ch0.note51 < lc1.ch0.note3 -out.ch0.note52 < lc1.ch0.note4 -out.ch0.note53 < lc1.ch0.note5 -out.ch0.note54 < lc1.ch0.note6 -out.ch0.note55 < lc1.ch0.note7 - -out.ch0.note56 < lc2.ch0.note0 -out.ch0.note57 < lc2.ch0.note1 -out.ch0.note58 < lc2.ch0.note2 -out.ch0.note59 < lc2.ch0.note3 -out.ch0.note60 < lc2.ch0.note4 -out.ch0.note61 < lc2.ch0.note5 -out.ch0.note62 < lc2.ch0.note6 -out.ch0.note63 < lc2.ch0.note7 +grandma.ch0.note{48..55} < lc1.ch0.note{0..7} +grandma.ch0.note{56..63} < lc2.ch0.note{0..7} ; Launchpad -out.ch0.note64 < pad.ch0.note0 -out.ch0.note65 < pad.ch0.note1 -out.ch0.note66 < pad.ch0.note2 -out.ch0.note67 < pad.ch0.note3 -out.ch0.note68 < pad.ch0.note4 -out.ch0.note69 < pad.ch0.note5 -out.ch0.note70 < pad.ch0.note6 -out.ch0.note71 < pad.ch0.note7 -out.ch0.note72 < pad.ch0.note16 -out.ch0.note73 < pad.ch0.note17 -out.ch0.note74 < pad.ch0.note18 -out.ch0.note75 < pad.ch0.note19 -out.ch0.note76 < pad.ch0.note20 -out.ch0.note77 < pad.ch0.note21 -out.ch0.note78 < pad.ch0.note22 -out.ch0.note79 < pad.ch0.note23 -out.ch0.note80 < pad.ch0.note32 -out.ch0.note81 < pad.ch0.note33 -out.ch0.note82 < pad.ch0.note34 -out.ch0.note83 < pad.ch0.note35 -out.ch0.note84 < pad.ch0.note36 -out.ch0.note85 < pad.ch0.note37 -out.ch0.note86 < pad.ch0.note38 -out.ch0.note87 < pad.ch0.note39 -out.ch0.note88 < pad.ch0.note48 -out.ch0.note89 < pad.ch0.note49 -out.ch0.note90 < pad.ch0.note50 -out.ch0.note91 < pad.ch0.note51 -out.ch0.note92 < pad.ch0.note52 -out.ch0.note93 < pad.ch0.note53 -out.ch0.note94 < pad.ch0.note54 -out.ch0.note95 < pad.ch0.note55 -out.ch0.note96 < pad.ch0.note64 -out.ch0.note97 < pad.ch0.note65 -out.ch0.note98 < pad.ch0.note66 -out.ch0.note99 < pad.ch0.note67 -out.ch0.note100 < pad.ch0.note68 -out.ch0.note101 < pad.ch0.note69 -out.ch0.note102 < pad.ch0.note70 -out.ch0.note103 < pad.ch0.note71 -out.ch0.note104 < pad.ch0.note80 -out.ch0.note105 < pad.ch0.note81 -out.ch0.note106 < pad.ch0.note82 -out.ch0.note107 < pad.ch0.note83 -out.ch0.note108 < pad.ch0.note84 -out.ch0.note109 < pad.ch0.note85 -out.ch0.note110 < pad.ch0.note86 -out.ch0.note111 < pad.ch0.note87 -out.ch0.note112 < pad.ch0.note96 -out.ch0.note113 < pad.ch0.note97 -out.ch0.note114 < pad.ch0.note98 -out.ch0.note115 < pad.ch0.note99 -out.ch0.note116 < pad.ch0.note100 -out.ch0.note117 < pad.ch0.note101 -out.ch0.note118 < pad.ch0.note102 -out.ch0.note119 < pad.ch0.note103 -out.ch0.note120 < pad.ch0.note112 -out.ch0.note121 < pad.ch0.note113 -out.ch0.note122 < pad.ch0.note114 -out.ch0.note123 < pad.ch0.note115 -out.ch0.note124 < pad.ch0.note116 -out.ch0.note125 < pad.ch0.note117 -out.ch0.note126 < pad.ch0.note118 -out.ch0.note127 < pad.ch0.note119 +grandma.ch0.note{64..71} < launchpad.ch0.note{0..7} +grandma.ch0.note{72..79} < launchpad.ch0.note{16..23} +grandma.ch0.note{80..87} < launchpad.ch0.note{32..39} +grandma.ch0.note{88..95} < launchpad.ch0.note{48..55} +grandma.ch0.note{96..103} < launchpad.ch0.note{64..71} +grandma.ch0.note{104..111} < launchpad.ch0.note{80..87} +grandma.ch0.note{112..119} < launchpad.ch0.note{96..103} +grandma.ch0.note{120..127} < launchpad.ch0.note{112..119} -- cgit v1.2.3 From 26ee2eacc7d60aa379c9e4b9b9c6b8bcdcd4bc6b Mon Sep 17 00:00:00 2001 From: cbdev Date: Sat, 27 Jul 2019 19:31:21 +0200 Subject: Refactor OSC backend, implement pattern matching --- README.md | 4 +- TODO | 1 - backends/Makefile | 4 +- backends/osc.c | 635 ++++++++++++++++++++++++++++++++-------------- backends/osc.h | 25 +- backends/osc.md | 21 +- configs/flying-faders.cfg | 24 ++ configs/flying-faders.lua | 10 + configs/osc-xy.cfg | 26 ++ 9 files changed, 543 insertions(+), 207 deletions(-) create mode 100644 configs/flying-faders.cfg create mode 100644 configs/flying-faders.lua create mode 100644 configs/osc-xy.cfg (limited to 'configs') diff --git a/README.md b/README.md index 3704f5f..9647d4f 100644 --- a/README.md +++ b/README.md @@ -7,8 +7,8 @@ Currently, the MIDIMonster supports the following protocols: * MIDI (Linux, via ALSA) * ArtNet -* sACN / E1.31 -* OSC +* Streaming ACN (sACN / E1.31) +* OpenSoundControl (OSC) * evdev input devices (Linux) * Open Lighting Architecture (OLA) diff --git a/TODO b/TODO index d114640..2a97c30 100644 --- a/TODO +++ b/TODO @@ -3,6 +3,5 @@ Note source in channel value struct Optimize core channel search (store backend offset) Printing backend / Verbose mode -document example configs evdev relative axis size mm_managed_fd.impl is not freed currently diff --git a/backends/Makefile b/backends/Makefile index fe88669..22cb95b 100644 --- a/backends/Makefile +++ b/backends/Makefile @@ -6,8 +6,8 @@ BACKEND_LIB = libmmbackend.o SYSTEM := $(shell uname -s) -CFLAGS += -fPIC -I../ -CPPFLAGS += -fPIC -I../ +CFLAGS += -g -fPIC -I../ +CPPFLAGS += -g -fPIC -I../ LDFLAGS += -shared # Build Linux backends if possible diff --git a/backends/osc.c b/backends/osc.c index 36b0993..3f19abf 100644 --- a/backends/osc.c +++ b/backends/osc.c @@ -13,6 +13,14 @@ #define osc_align(a) ((((a) / 4) + (((a) % 4) ? 1 : 0)) * 4) #define BACKEND_NAME "osc" +typedef union { + struct { + uint32_t channel; + uint32_t parameter; + } fields; + uint64_t label; +} osc_channel_ident; + static struct { uint8_t detect; } osc_global_config = { @@ -41,6 +49,7 @@ int init(){ } static size_t osc_data_length(osc_parameter_type t){ + //binary representation lengths for osc data types switch(t){ case int32: case float32: @@ -55,6 +64,7 @@ static size_t osc_data_length(osc_parameter_type t){ } static inline void osc_defaults(osc_parameter_type t, osc_parameter_value* max, osc_parameter_value* min){ + //data type default ranges memset(max, 0, sizeof(osc_parameter_value)); memset(min, 0, sizeof(osc_parameter_value)); switch(t){ @@ -77,6 +87,7 @@ static inline void osc_defaults(osc_parameter_type t, osc_parameter_value* max, } static inline osc_parameter_value osc_parse(osc_parameter_type t, uint8_t* data){ + //read value from binary representation osc_parameter_value v = {0}; switch(t){ case int32: @@ -94,6 +105,7 @@ static inline osc_parameter_value osc_parse(osc_parameter_type t, uint8_t* data) } static inline int osc_deparse(osc_parameter_type t, osc_parameter_value v, uint8_t* data){ + //write value to binary representation uint64_t u64 = 0; uint32_t u32 = 0; switch(t){ @@ -115,6 +127,7 @@ static inline int osc_deparse(osc_parameter_type t, osc_parameter_value v, uint8 } static inline osc_parameter_value osc_parse_value_spec(osc_parameter_type t, char* value){ + //read value from string osc_parameter_value v = {0}; switch(t){ case int32: @@ -136,6 +149,7 @@ static inline osc_parameter_value osc_parse_value_spec(osc_parameter_type t, cha } static inline channel_value osc_parameter_normalise(osc_parameter_type t, osc_parameter_value min, osc_parameter_value max, osc_parameter_value cur){ + //normalise osc value wrt given min/max channel_value v = { .raw = {0}, .normalised = 0 @@ -179,6 +193,7 @@ static inline channel_value osc_parameter_normalise(osc_parameter_type t, osc_pa } static inline osc_parameter_value osc_parameter_denormalise(osc_parameter_type t, osc_parameter_value min, osc_parameter_value max, channel_value cur){ + //convert normalised value to osc value wrt given min/max osc_parameter_value v = {0}; union { @@ -212,45 +227,192 @@ static inline osc_parameter_value osc_parameter_denormalise(osc_parameter_type t return v; } -static int osc_generate_event(channel* c, osc_channel* info, char* fmt, uint8_t* data, size_t data_len){ - size_t p, off = 0; - if(!c || !info){ - return 0; +static int osc_path_validate(char* path, uint8_t allow_patterns){ + //validate osc path or pattern + char illegal_chars[] = " #,"; + char pattern_chars[] = "?[]{}*"; + size_t u, c; + uint8_t square_open = 0, curly_open = 0; + + if(path[0] != '/'){ + fprintf(stderr, "%s is not a valid OSC path: Missing root /\n", path); + return 1; } - osc_parameter_value min, max, cur; - channel_value evt; + for(u = 0; u < strlen(path); u++){ + for(c = 0; c < sizeof(illegal_chars); c++){ + if(path[u] == illegal_chars[c]){ + fprintf(stderr, "%s is not a valid OSC path: Illegal '%c' at %lu\n", path, illegal_chars[c], u); + return 1; + } + } - if(!fmt || !data || data_len % 4 || !*fmt){ - fprintf(stderr, "Invalid OSC packet, data length %zu\n", data_len); - return 1; - } + if(!isgraph(path[u])){ + fprintf(stderr, "%s is not a valid OSC path: Illegal '%c' at %lu\n", path, pattern_chars[c], u); + return 1; + } - //find offset for this parameter - for(p = 0; p < info->param_index; p++){ - off += osc_data_length(fmt[p]); - } + if(!allow_patterns){ + for(c = 0; c < sizeof(pattern_chars); c++){ + if(path[u] == pattern_chars[c]){ + fprintf(stderr, "%s is not a valid OSC path: Illegal '%c' at %lu\n", path, pattern_chars[c], u); + return 1; + } + } + } - if(info->type != not_set){ - max = info->max; - min = info->min; + switch(path[u]){ + case '{': + if(square_open || curly_open){ + fprintf(stderr, "%s is not a valid OSC path: Illegal '%c' at %lu\n", path, pattern_chars[c], u); + return 1; + } + curly_open = 1; + break; + case '[': + if(square_open || curly_open){ + fprintf(stderr, "%s is not a valid OSC path: Illegal '%c' at %lu\n", path, pattern_chars[c], u); + return 1; + } + square_open = 1; + break; + case '}': + curly_open = 0; + break; + case ']': + square_open = 0; + break; + case '/': + if(square_open || curly_open){ + fprintf(stderr, "%s is not a valid OSC path: Pattern across part boundaries\n", path); + return 1; + } + } } - else{ - osc_defaults(fmt[info->param_index], &max, &min); + + if(square_open || curly_open){ + fprintf(stderr, "%s is not a valid OSC path: Unterminated pattern expression\n", path); + return 1; } + return 0; +} - cur = osc_parse(fmt[info->param_index], data + off); - evt = osc_parameter_normalise(fmt[info->param_index], min, max, cur); +static int osc_path_match(char* pattern, char* path){ + size_t u, p = 0, match_begin, match_end; + uint8_t match_any = 0, inverted, match; - return mm_channel_event(c, evt); -} + for(u = 0; u < strlen(path); u++){ + switch(pattern[p]){ + case '/': + if(match_any){ + for(; path[u] && path[u] != '/'; u++){ + } + } + if(path[u] != '/'){ + return 0; + } + match_any = 0; + p++; + break; + case '?': + match_any = 0; + p++; + break; + case '*': + match_any = 1; + p++; + break; + case '[': + inverted = (pattern[p + 1] == '!') ? 1 : 0; + match_end = match_begin = inverted ? p + 2 : p + 1; + match = 0; + for(; pattern[match_end] != ']'; match_end++){ + if(pattern[match_end] == path[u]){ + match = 1; + break; + } -static int osc_validate_path(char* path){ - if(path[0] != '/'){ - fprintf(stderr, "%s is not a valid OSC path: Missing root /\n", path); - return 1; + if(pattern[match_end + 1] == '-' && pattern[match_end + 2] != ']'){ + if((pattern[match_end] > pattern[match_end + 2] + && path[u] >= pattern[match_end + 2] + && path[u] <= pattern[match_end]) + || (pattern[match_end] <= pattern[match_end + 2] + && path[u] >= pattern[match_end] + && path[u] <= pattern[match_end + 2])){ + match = 1; + break; + } + match_end += 2; + } + + if(pattern[match_end + 1] == ']' && match_any && !match + && path[u + 1] && path[u + 1] != '/'){ + match_end = match_begin - 1; + u++; + } + } + + if(match == inverted){ + return 0; + } + + match_any = 0; + //advance to end of pattern + for(; pattern[p] != ']'; p++){ + } + p++; + break; + case '{': + for(match_begin = p + 1; pattern[match_begin] != '}'; match_begin++){ + //find end + for(match_end = match_begin; pattern[match_end] != ',' && pattern[match_end] != '}'; match_end++){ + } + + if(!strncmp(path + u, pattern + match_begin, match_end - match_begin)){ + //advance pattern + for(; pattern[p] != '}'; p++){ + } + p++; + //advance path + u += match_end - match_begin - 1; + break; + } + + if(pattern[match_end] == '}'){ + //retry with next if in match_any + if(match_any && path[u + 1] && path[u + 1] != '/'){ + u++; + match_begin = p; + continue; + } + return 0; + } + match_begin = match_end; + } + match_any = 0; + break; + case 0: + if(match_any){ + for(; path[u] && path[u] != '/'; u++){ + } + } + if(path[u]){ + return 0; + } + break; + default: + if(match_any){ + for(; path[u] && path[u] != '/' && path[u] != pattern[p]; u++){ + } + } + if(pattern[p] != path[u]){ + return 0; + } + p++; + break; + } } - return 0; + return 1; } static int osc_configure(char* option, char* value){ @@ -266,13 +428,81 @@ static int osc_configure(char* option, char* value){ return 1; } +static int osc_register_pattern(osc_instance_data* data, char* pattern_path, char* configuration){ + size_t u, pattern; + char* format = NULL, *token = NULL; + + if(osc_path_validate(pattern_path, 1)){ + fprintf(stderr, "Not a valid OSC pattern: %s\n", pattern_path); + return 1; + } + + //tokenize configuration + format = strtok(configuration, " "); + if(!format || strlen(format) < 1){ + fprintf(stderr, "Not a valid format specification for OSC pattern %s\n", pattern_path); + return 1; + } + + //create pattern + data->pattern = realloc(data->pattern, (data->patterns + 1) * sizeof(osc_channel)); + if(!data->pattern){ + fprintf(stderr, "Failed to allocate memory\n"); + return 1; + } + pattern = data->patterns; + + data->pattern[pattern].params = strlen(format); + data->pattern[pattern].path = strdup(pattern_path); + data->pattern[pattern].type = calloc(strlen(format), sizeof(osc_parameter_type)); + data->pattern[pattern].max = calloc(strlen(format), sizeof(osc_parameter_value)); + data->pattern[pattern].min = calloc(strlen(format), sizeof(osc_parameter_value)); + + if(!data->pattern[pattern].path + || !data->pattern[pattern].type + || !data->pattern[pattern].max + || !data->pattern[pattern].min){ + //this should fail config parsing and thus call the shutdown function, + //which should properly free the rest of the data + fprintf(stderr, "Failed to allocate memory\n"); + return 1; + } + + //check format validity and store min/max values + for(u = 0; u < strlen(format); u++){ + if(!osc_data_length(format[u])){ + fprintf(stderr, "Invalid format specifier %c for pattern %s\n", format[u], pattern_path); + return 1; + } + + data->pattern[pattern].type[u] = format[u]; + + //parse min/max values + token = strtok(NULL, " "); + if(!token){ + fprintf(stderr, "Missing minimum specification for parameter %zu of OSC pattern %s\n", u, pattern_path); + return 1; + } + data->pattern[pattern].min[u] = osc_parse_value_spec(format[u], token); + + token = strtok(NULL, " "); + if(!token){ + fprintf(stderr, "Missing maximum specification for parameter %zu of OSC pattern %s\n", u, pattern_path); + return 1; + } + data->pattern[pattern].max[u] = osc_parse_value_spec(format[u], token); + } + + data->patterns++; + return 0; +} + static int osc_configure_instance(instance* inst, char* option, char* value){ osc_instance_data* data = (osc_instance_data*) inst->impl; - char* host = NULL, *port = NULL, *token = NULL, *format = NULL; - size_t u, p; + char* host = NULL, *port = NULL, *token = NULL; if(!strcmp(option, "root")){ - if(osc_validate_path(value)){ + if(osc_path_validate(value, 0)){ fprintf(stderr, "Not a valid OSC root: %s\n", value); return 1; } @@ -326,76 +556,7 @@ static int osc_configure_instance(instance* inst, char* option, char* value){ return 0; } else if(*option == '/'){ - //pre-configure channel - if(osc_validate_path(option)){ - fprintf(stderr, "Not a valid OSC path: %s\n", option); - return 1; - } - - for(u = 0; u < data->channels; u++){ - if(!strcmp(option, data->channel[u].path)){ - fprintf(stderr, "OSC channel %s already configured\n", option); - return 1; - } - } - - //tokenize configuration - format = strtok(value, " "); - if(!format || strlen(format) < 1){ - fprintf(stderr, "Not a valid format for OSC path %s\n", option); - return 1; - } - - //check format validity, create subchannels - for(p = 0; p < strlen(format); p++){ - if(!osc_data_length(format[p])){ - fprintf(stderr, "Invalid format specifier %c for path %s, ignoring\n", format[p], option); - continue; - } - - //register new sub-channel - data->channel = realloc(data->channel, (data->channels + 1) * sizeof(osc_channel)); - if(!data->channel){ - fprintf(stderr, "Failed to allocate memory\n"); - return 1; - } - - memset(data->channel + data->channels, 0, sizeof(osc_channel)); - data->channel[data->channels].params = strlen(format); - data->channel[data->channels].param_index = p; - data->channel[data->channels].type = format[p]; - data->channel[data->channels].path = strdup(option); - - if(!data->channel[data->channels].path){ - fprintf(stderr, "Failed to allocate memory\n"); - return 1; - } - - //parse min/max values - token = strtok(NULL, " "); - if(!token){ - fprintf(stderr, "Missing minimum specification for parameter %zu of %s\n", p, option); - return 1; - } - data->channel[data->channels].min = osc_parse_value_spec(format[p], token); - - token = strtok(NULL, " "); - if(!token){ - fprintf(stderr, "Missing maximum specification for parameter %zu of %s\n", p, option); - return 1; - } - data->channel[data->channels].max = osc_parse_value_spec(format[p], token); - - //allocate channel from core - if(!mm_channel(inst, data->channels, 1)){ - fprintf(stderr, "Failed to register core channel\n"); - return 1; - } - - //increase channel count - data->channels++; - } - return 0; + return osc_register_pattern(data, option, value); } fprintf(stderr, "Unknown configuration parameter %s for OSC instance %s\n", option, inst->name); @@ -420,31 +581,38 @@ static instance* osc_instance(){ } static channel* osc_map_channel(instance* inst, char* spec){ - size_t u; + size_t u, p; osc_instance_data* data = (osc_instance_data*) inst->impl; - size_t param_index = 0; + osc_channel_ident ident = { + .label = 0 + }; //check spec for correctness - if(osc_validate_path(spec)){ + if(osc_path_validate(spec, 0)){ return NULL; } //parse parameter offset if(strrchr(spec, ':')){ - param_index = strtoul(strrchr(spec, ':') + 1, NULL, 10); + ident.fields.parameter = strtoul(strrchr(spec, ':') + 1, NULL, 10); *(strrchr(spec, ':')) = 0; } //find matching channel for(u = 0; u < data->channels; u++){ - if(!strcmp(spec, data->channel[u].path) && data->channel[u].param_index == param_index){ - //fprintf(stderr, "Reusing previously created channel %s parameter %zu\n", data->channel[u].path, data->channel[u].param_index); + if(!strcmp(spec, data->channel[u].path)){ break; } } //allocate new channel if(u == data->channels){ + for(p = 0; p < data->patterns; p++){ + if(osc_path_match(data->pattern[p].path, spec)){ + break; + } + } + data->channel = realloc(data->channel, (u + 1) * sizeof(osc_channel)); if(!data->channel){ fprintf(stderr, "Failed to allocate memory\n"); @@ -452,22 +620,100 @@ static channel* osc_map_channel(instance* inst, char* spec){ } memset(data->channel + u, 0, sizeof(osc_channel)); - data->channel[u].param_index = param_index; data->channel[u].path = strdup(spec); + if(p != data->patterns){ + fprintf(stderr, "Matched pattern %s for %s\n", data->pattern[p].path, spec); + data->channel[u].params = data->pattern[p].params; + //just reuse the pointers from the pattern + data->channel[u].type = data->pattern[p].type; + data->channel[u].max = data->pattern[p].max; + data->channel[u].min = data->pattern[p].min; + + //these are per channel + data->channel[u].in = calloc(data->channel[u].params, sizeof(osc_parameter_value)); + data->channel[u].out = calloc(data->channel[u].params, sizeof(osc_parameter_value)); + } + else if(data->patterns){ + fprintf(stderr, "No pattern match found for %s\n", spec); + } - if(!data->channel[u].path){ + if(!data->channel[u].path + || (data->channel[u].params && (!data->channel[u].in || !data->channel[u].out))){ fprintf(stderr, "Failed to allocate memory\n"); return NULL; } data->channels++; } - return mm_channel(inst, u, 1); + ident.fields.channel = u; + return mm_channel(inst, ident.label, 1); +} + +static int osc_output_channel(instance* inst, size_t channel){ + osc_instance_data* data = (osc_instance_data*) inst->impl; + uint8_t xmit_buf[OSC_XMIT_BUF] = "", *format = NULL; + size_t offset = 0, p; + + //fix destination rport if required + if(data->forced_rport){ + //cheating a bit because both IPv4 and IPv6 have the port at the same offset + struct sockaddr_in* sockadd = (struct sockaddr_in*) &(data->dest); + sockadd->sin_port = htobe16(data->forced_rport); + } + + //determine minimum packet size + if(osc_align((data->root ? strlen(data->root) : 0) + strlen(data->channel[channel].path) + 1) + osc_align(data->channel[channel].params + 2) >= sizeof(xmit_buf)){ + fprintf(stderr, "Insufficient buffer size for OSC transmitting channel %s.%s\n", inst->name, data->channel[channel].path); + return 1; + } + + //copy osc target path + if(data->root){ + memcpy(xmit_buf, data->root, strlen(data->root)); + offset += strlen(data->root); + } + + memcpy(xmit_buf + offset, data->channel[channel].path, strlen(data->channel[channel].path)); + offset += strlen(data->channel[channel].path) + 1; + offset = osc_align(offset); + + //get format string offset, initialize + format = xmit_buf + offset; + offset += osc_align(data->channel[channel].params + 2); + *format = ','; + format++; + + for(p = 0; p < data->channel[channel].params; p++){ + //write format specifier + format[p] = data->channel[channel].type[p]; + + //write data + if(offset + osc_data_length(data->channel[channel].type[p]) >= sizeof(xmit_buf)){ + fprintf(stderr, "Insufficient buffer size for OSC transmitting channel %s.%s at parameter %zu\n", inst->name, data->channel[channel].path, p); + return 1; + } + + osc_deparse(data->channel[channel].type[p], + data->channel[channel].out[p], + xmit_buf + offset); + offset += osc_data_length(data->channel[channel].type[p]); + } + + //output packet + if(sendto(data->fd, xmit_buf, offset, 0, (struct sockaddr*) &(data->dest), data->dest_len) < 0){ + fprintf(stderr, "Failed to transmit OSC packet: %s\n", strerror(errno)); + } + return 0; } static int osc_set(instance* inst, size_t num, channel** c, channel_value* v){ - uint8_t xmit_buf[OSC_XMIT_BUF], *format = NULL; - size_t evt = 0, off, members, p; + size_t evt = 0, mark = 0; + int rv; + osc_channel_ident ident = { + .label = 0 + }; + osc_parameter_value current; + if(!num){ return 0; } @@ -479,95 +725,97 @@ static int osc_set(instance* inst, size_t num, channel** c, channel_value* v){ } for(evt = 0; evt < num; evt++){ - off = c[evt]->ident; + ident.label = c[evt]->ident; //sanity check - if(off >= data->channels){ + if(ident.fields.channel >= data->channels + || ident.fields.parameter >= data->channel[ident.fields.channel].params){ fprintf(stderr, "OSC channel identifier out of range\n"); return 1; } //if the format is unknown, don't output - if(data->channel[off].type == not_set || data->channel[off].params == 0){ - fprintf(stderr, "OSC channel %s.%s requires format specification for output\n", inst->name, data->channel[off].path); + if(!data->channel[ident.fields.channel].params){ + fprintf(stderr, "OSC channel %s.%s requires format specification for output\n", inst->name, data->channel[ident.fields.channel].path); continue; } - //update current value - data->channel[off].current = osc_parameter_denormalise(data->channel[off].type, data->channel[off].min, data->channel[off].max, v[evt]); - //mark channel - data->channel[off].mark = 1; + //only output on change + current = osc_parameter_denormalise(data->channel[ident.fields.channel].type[ident.fields.parameter], + data->channel[ident.fields.channel].min[ident.fields.parameter], + data->channel[ident.fields.channel].max[ident.fields.parameter], + v[evt]); + if(memcmp(¤t, &data->channel[ident.fields.channel].out[ident.fields.parameter], sizeof(current))){ + //update current value + data->channel[ident.fields.channel].out[ident.fields.parameter] = current; + //mark channel + data->channel[ident.fields.channel].mark = 1; + mark = 1; + } } - - //fix destination rport if required - if(data->forced_rport){ - //cheating a bit because both IPv4 and IPv6 have the port at the same offset - struct sockaddr_in* sockadd = (struct sockaddr_in*) &(data->dest); - sockadd->sin_port = htobe16(data->forced_rport); + + if(mark){ + //output all marked channels + for(evt = 0; !rv && evt < num; evt++){ + ident.label = c[evt]->ident; + if(data->channel[ident.fields.channel].mark){ + rv |= osc_output_channel(inst, ident.fields.channel); + data->channel[ident.fields.channel].mark = 0; + } + } } + return rv; +} - //find all marked channels - for(evt = 0; evt < data->channels; evt++){ - //zero output buffer - memset(xmit_buf, 0, sizeof(xmit_buf)); - if(data->channel[evt].mark){ - //determine minimum packet size - if(osc_align((data->root ? strlen(data->root) : 0) + strlen(data->channel[evt].path) + 1) + osc_align(data->channel[evt].params + 2) >= sizeof(xmit_buf)){ - fprintf(stderr, "Insufficient buffer size for OSC transmitting channel %s.%s\n", inst->name, data->channel[evt].path); - return 1; - } +static int osc_process_packet(instance* inst, char* local_path, char* format, uint8_t* payload, size_t payload_len){ + osc_instance_data* data = (osc_instance_data*) inst->impl; + size_t c, p, offset = 0; + osc_parameter_value min, max, cur; + channel_value evt; + osc_channel_ident ident = { + .label = 0 + }; + channel* chan = NULL; - off = 0; - //copy osc target path - if(data->root){ - memcpy(xmit_buf, data->root, strlen(data->root)); - off += strlen(data->root); - } - memcpy(xmit_buf + off, data->channel[evt].path, strlen(data->channel[evt].path)); - off += strlen(data->channel[evt].path) + 1; - off = osc_align(off); - - //get format string offset, initialize - format = xmit_buf + off; - off += osc_align(data->channel[evt].params + 2); - *format = ','; - format++; - - //gather subchannels, unmark - members = 0; - for(p = 0; p < data->channels && members < data->channel[evt].params; p++){ - if(!strcmp(data->channel[evt].path, data->channel[p].path)){ - //unmark channel - data->channel[p].mark = 0; - - //sanity check - if(data->channel[p].param_index >= data->channel[evt].params){ - fprintf(stderr, "OSC channel %s.%s has multiple parameter offset definitions\n", inst->name, data->channel[evt].path); - return 1; - } + if(payload_len % 4){ + fprintf(stderr, "Invalid OSC packet, data length %zu\n", payload_len); + return 0; + } - //write format specifier - format[data->channel[p].param_index] = data->channel[p].type; + for(c = 0; c < data->channels; c++){ + if(!strcmp(local_path, data->channel[c].path)){ + ident.fields.channel = c; + //unconfigured input should work without errors (using default limits) + if(data->channel[c].params && strlen(format) != data->channel[c].params){ + fprintf(stderr, "OSC message %s.%s had format %s, internal representation has %lu parameters\n", inst->name, local_path, format, data->channel[c].params); + continue; + } - //write data - //FIXME this currently depends on all channels being registered in the correct order, since it just appends data - if(off + osc_data_length(data->channel[p].type) >= sizeof(xmit_buf)){ - fprintf(stderr, "Insufficient buffer size for OSC transmitting channel %s.%s at parameter %zu\n", inst->name, data->channel[evt].path, members); - return 1; + for(p = 0; p < strlen(format); p++){ + ident.fields.parameter = p; + if(data->channel[c].params){ + max = data->channel[c].max[p]; + min = data->channel[c].min[p]; + } + else{ + osc_defaults(format[p], &max, &min); + } + cur = osc_parse(format[p], payload + offset); + if(!data->channel[c].params || memcmp(&cur, &data->channel[c].in, sizeof(cur))){ + evt = osc_parameter_normalise(format[p], min, max, cur); + chan = mm_channel(inst, ident.label, 0); + if(chan){ + mm_channel_event(chan, evt); } - - osc_deparse(data->channel[p].type, data->channel[p].current, xmit_buf + off); - off += osc_data_length(data->channel[p].type); - members++; } - } - //output packet - if(sendto(data->fd, xmit_buf, off, 0, (struct sockaddr*) &(data->dest), data->dest_len) < 0){ - fprintf(stderr, "Failed to transmit OSC packet: %s\n", strerror(errno)); + //skip to next parameter data + offset += osc_data_length(format[p]); + //TODO check offset against payload length } } } + return 0; } @@ -577,7 +825,6 @@ static int osc_handle(size_t num, managed_fd* fds){ instance* inst = NULL; osc_instance_data* data = NULL; ssize_t bytes_read = 0; - size_t c; char* osc_fmt = NULL; char* osc_local = NULL; uint8_t* osc_data = NULL; @@ -600,7 +847,7 @@ static int osc_handle(size_t num, managed_fd* fds){ bytes_read = recv(fds[fd].fd, recv_buf, sizeof(recv_buf), 0); } - if(bytes_read < 0){ + if(bytes_read <= 0){ break; } @@ -622,20 +869,11 @@ static int osc_handle(size_t num, managed_fd* fds){ fprintf(stderr, "Incoming OSC data: Path %s.%s Format %s\n", inst->name, osc_local, osc_fmt); } - osc_data = (uint8_t*) osc_fmt + (osc_align(strlen(osc_fmt) + 2) - 1); //FIXME check supplied data length + osc_data = (uint8_t*) osc_fmt + (osc_align(strlen(osc_fmt) + 2) - 1); - for(c = 0; c < data->channels; c++){ - //FIXME implement proper OSC path match - //prefix match - if(!strcmp(osc_local, data->channel[c].path)){ - if(strlen(osc_fmt) > data->channel[c].param_index){ - //fprintf(stderr, "Taking parameter %zu of %s (%s), %zd bytes, data offset %zu\n", data->channel[c].param_index, recv_buf, osc_fmt, bytes_read, (osc_data - (uint8_t*)recv_buf)); - if(osc_generate_event(mm_channel(inst, c, 0), data->channel + c, osc_fmt, osc_data, bytes_read - (osc_data - (uint8_t*) recv_buf))){ - fprintf(stderr, "Failed to generate OSC channel event\n"); - } - } - } + if(osc_process_packet(inst, osc_local, osc_fmt, osc_data, bytes_read - (osc_data - (uint8_t*) recv_buf))){ + return 1; } } while(bytes_read > 0); @@ -706,14 +944,25 @@ static int osc_shutdown(){ data = (osc_instance_data*) inst[u]->impl; for(c = 0; c < data->channels; c++){ free(data->channel[c].path); + free(data->channel[c].in); + free(data->channel[c].out); } free(data->channel); + for(c = 0; c < data->patterns; c++){ + free(data->pattern[c].path); + free(data->pattern[c].type); + free(data->pattern[c].min); + free(data->pattern[c].max); + } + free(data->pattern); + free(data->root); if(data->fd >= 0){ close(data->fd); } data->fd = -1; data->channels = 0; + data->patterns = 0; free(inst[u]->impl); } diff --git a/backends/osc.h b/backends/osc.h index dc6cb3a..4e9dec5 100644 --- a/backends/osc.h +++ b/backends/osc.h @@ -34,22 +34,33 @@ typedef union { typedef struct /*_osc_channel*/ { char* path; size_t params; - size_t param_index; uint8_t mark; - osc_parameter_type type; - osc_parameter_value max; - osc_parameter_value min; - osc_parameter_value current; + osc_parameter_type* type; + osc_parameter_value* max; + osc_parameter_value* min; + osc_parameter_value* in; + osc_parameter_value* out; } osc_channel; typedef struct /*_osc_instance_data*/ { + //pre-configured channel patterns + size_t patterns; + osc_channel* pattern; + + //actual channel registry size_t channels; osc_channel* channel; + + //instance config char* root; + uint8_t learn; + + //peer addressing socklen_t dest_len; struct sockaddr_storage dest; - int fd; - uint8_t learn; uint16_t forced_rport; + + //peer fd + int fd; } osc_instance_data; diff --git a/backends/osc.md b/backends/osc.md index e9aa4d5..b7ce527 100644 --- a/backends/osc.md +++ b/backends/osc.md @@ -23,13 +23,21 @@ it are ignored early in processing. Channels that are to be output or require a value range different from the default ranges (see below) require special configuration, as their types and limits have to be set. -This is done in the instance configuration using an assignment of the syntax +This is done by specifying *patterns* in the instance configuration using an assignment of the syntax ``` /local/osc/path = ... ``` -The OSC path to be configured must only be the local part (omitting a configured instance root). +The pattern will be matched only against the local part (that is, the path excluding any configured instance root). +Patterns may contain the following expressions (conforming to the [OSC pattern matching specification](http://opensoundcontrol.org/spec-1_0)): +* `?` matches any single legal character +* `*` matches zero or more legal characters +* A comma-separated list of strings inside curly braces `{}` matches any of the strings +* A string of characters within square brackets `[]` matches any character in the string + * Two characters with a `-` between them specify a range of characters + * An exclamation mark immediately after the opening `[` negates the meaning of the expression (ie. it matches characters not in the range) +* Any other legal character matches only itself **format** may be any sequence of valid OSC type characters. See below for a table of supported OSC types. @@ -44,6 +52,12 @@ a range between 0.0 and 2.0 (for example, an X-Y control), would look as follows /1/xy1 = ff 0.0 2.0 0.0 2.0 ``` +To configure a range of faders, an expression similar to the following line could be used + +``` +/1/fader* = f 0.0 1.0 +``` + #### Channel specification A channel may be any valid OSC path, to which the instance root will be prepended if @@ -80,4 +94,7 @@ The default ranges are: #### Known bugs / problems +The OSC path match currently works on the unit of characters. This may lead to some unexpected results +when matching expressions of the form `*`. + Ping requests are not yet answered. There may be some problems using broadcast output and input. diff --git a/configs/flying-faders.cfg b/configs/flying-faders.cfg new file mode 100644 index 0000000..4197581 --- /dev/null +++ b/configs/flying-faders.cfg @@ -0,0 +1,24 @@ +; Create a 'flying faders' effect using lua and output +; it onto TouchOSC (Layout 'Mix16', Multifader view on page 4) + +[osc touch] +bind = * 8000 +dest = learn@9000 + +; Pre-declare the fader values so the range mapping is correct +/*/fader* = f 0.0 1.0 +/*/toggle* = f 0.0 1.0 +/*/push* = f 0.0 1.0 +/*/multifader*/* = f 0.0 1.0 +/1/xy = ff 0.0 1.0 0.0 1.0 + +[lua generator] +script = configs/flying-faders.lua + +[map] + +generator.wave{1..24} > touch./4/multifader1/{1..24} +;generator.wave{1..24} > touch./4/multifader2/{1..24} + +touch./4/multifader2/1 > generator.magnitude + diff --git a/configs/flying-faders.lua b/configs/flying-faders.lua new file mode 100644 index 0000000..0b0faef --- /dev/null +++ b/configs/flying-faders.lua @@ -0,0 +1,10 @@ +step = 0 + +function wave() + for chan=1,24 do + output("wave" .. chan, (math.sin(math.rad((step + chan * 360 / 24) % 360)) + 1) / 2) + end + step = (step + 5) % 360 +end + +interval(wave, 100) diff --git a/configs/osc-xy.cfg b/configs/osc-xy.cfg new file mode 100644 index 0000000..fc5c5f3 --- /dev/null +++ b/configs/osc-xy.cfg @@ -0,0 +1,26 @@ +; Test for bi-directional OSC with an XY pad (TouchOSC Layout 'Mix16', Page 1) + +[backend osc] +detect = on + +[osc touch] +bind = 0.0.0.0 8000 +dest = learn@9000 + +; Pre-declare the fader values so the range mapping is correct +/*/xy = ff 0.0 1.0 0.0 1.0 + +[evdev xbox] +device = /dev/input/event16 + +[midi launch] + +[map] +xbox.EV_ABS.ABS_X > touch./1/xy:1 +xbox.EV_ABS.ABS_Y > touch./1/xy:0 + +xbox.EV_ABS.ABS_X > launch.ch0.note2 +;xbox.EV_ABS.ABS_Y > launch.ch0.note3 + +launch.ch0.note0 <> touch./1/xy:0 +launch.ch0.note1 <> touch./1/xy:1 -- cgit v1.2.3 From aef72140273b3d98a4d86a42f0f3bcce6d5899ca Mon Sep 17 00:00:00 2001 From: cbdev Date: Sun, 28 Jul 2019 23:19:54 +0200 Subject: Fix Lua timing on non-linux --- backends/lua.c | 11 +++++++++-- backends/osc.md | 3 +++ configs/midi-osc.cfg | 19 ++----------------- 3 files changed, 14 insertions(+), 19 deletions(-) (limited to 'configs') diff --git a/backends/lua.c b/backends/lua.c index 61e4e08..4a910a2 100644 --- a/backends/lua.c +++ b/backends/lua.c @@ -53,9 +53,16 @@ int init(){ } static uint32_t lua_interval(){ - //FIXME Return delta for next timer here + size_t n = 0; + uint64_t next_timer = 1000; + if(timer_interval){ - return timer_interval; + for(n = 0; n < timers; n++){ + if(timer[n].interval && timer[n].interval - timer[n].delta < next_timer){ + next_timer = timer[n].interval - timer[n].delta; + } + } + return next_timer; } return 1000; } diff --git a/backends/osc.md b/backends/osc.md index b7ce527..1446e06 100644 --- a/backends/osc.md +++ b/backends/osc.md @@ -58,6 +58,9 @@ To configure a range of faders, an expression similar to the following line coul /1/fader* = f 0.0 1.0 ``` +When matching channels against the patterns to use, the first matching pattern (in the order in which they have been configured) will be used +as configuration for that channel. + #### Channel specification A channel may be any valid OSC path, to which the instance root will be prepended if diff --git a/configs/midi-osc.cfg b/configs/midi-osc.cfg index 1b3ccd6..7753a24 100644 --- a/configs/midi-osc.cfg +++ b/configs/midi-osc.cfg @@ -13,23 +13,8 @@ dest = learn@8000 root = /4 ; Pre-declare the fader values so the range mapping is correct -/fader1 = f 0.0 1.0 -/fader2 = f 0.0 1.0 -/fader3 = f 0.0 1.0 -/fader4 = f 0.0 1.0 -/fader5 = f 0.0 1.0 -/fader6 = f 0.0 1.0 -/fader7 = f 0.0 1.0 -/fader8 = f 0.0 1.0 - -/multifader1/1 = f 0.0 1.0 -/multifader1/2 = f 0.0 1.0 -/multifader1/3 = f 0.0 1.0 -/multifader1/4 = f 0.0 1.0 -/multifader1/5 = f 0.0 1.0 -/multifader1/6 = f 0.0 1.0 -/multifader1/7 = f 0.0 1.0 -/multifader1/8 = f 0.0 1.0 +/fader* = f 0.0 1.0 +/multifader1/* = f 0.0 1.0 [midi bcf] read = BCF -- cgit v1.2.3 From 33f4e9c7d865ce518b0ffaf49615121db52a6a9e Mon Sep 17 00:00:00 2001 From: cbdev Date: Sat, 17 Aug 2019 01:46:36 +0200 Subject: Add new example config --- configs/midi-mouse.cfg | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) create mode 100644 configs/midi-mouse.cfg (limited to 'configs') diff --git a/configs/midi-mouse.cfg b/configs/midi-mouse.cfg new file mode 100644 index 0000000..7e110ff --- /dev/null +++ b/configs/midi-mouse.cfg @@ -0,0 +1,22 @@ +; Use a Launch Control MIDI controller as mouse input +; Running this configuration requires root privileges on most systems, +; as creating additional input devices could potentially be misused for +; nefarious purposes + +[backend midi] +detect = on + +[evdev mouse] +output = MIDI Mouse +relaxis.REL_X = 255 +relaxis.REL_Y = 255 + +[midi launch] +read = Launch + +[map] +launch.ch0.cc0 > mouse.EV_REL.REL_X +launch.ch0.cc1 > mouse.EV_REL.REL_Y + +launch.ch0.note0 > mouse.EV_KEY.BTN_LEFT +launch.ch0.note1 > mouse.EV_KEY.BTN_RIGHT -- cgit v1.2.3 From 62eabb1c1d840cbab26b9ea747b102550199bad3 Mon Sep 17 00:00:00 2001 From: Spacelord09 Date: Thu, 22 Aug 2019 20:02:54 +0200 Subject: Added a config to test the maweb backend. --- configs/maweb-flying faders.cfg | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100644 configs/maweb-flying faders.cfg (limited to 'configs') diff --git a/configs/maweb-flying faders.cfg b/configs/maweb-flying faders.cfg new file mode 100644 index 0000000..0101f1e --- /dev/null +++ b/configs/maweb-flying faders.cfg @@ -0,0 +1,13 @@ +; Create a 'flying faders' effect using lua and output it onto maweb faders 1..6 + +[maweb ma] +host = 10.23.42.21 80 ; Thats the IP of your console or OnPC. +user = midimonster ; If a Dot2 is used, the username is automatically set to "remote". +password = midimonster + +[lua generator] +script = configs/flying-faders.lua + + +[map] +generator.wave{1..6} > ma.page1.fader{1..6} \ No newline at end of file -- cgit v1.2.3 From 5dcbae830db5289b4e269c1913511b890e3e1d5d Mon Sep 17 00:00:00 2001 From: Spacelord09 Date: Thu, 22 Aug 2019 20:12:21 +0200 Subject: Rename maweb-flying faders.cfg to maweb-flying-faders.cfg --- configs/maweb-flying faders.cfg | 13 ------------- configs/maweb-flying-faders.cfg | 13 +++++++++++++ 2 files changed, 13 insertions(+), 13 deletions(-) delete mode 100644 configs/maweb-flying faders.cfg create mode 100644 configs/maweb-flying-faders.cfg (limited to 'configs') diff --git a/configs/maweb-flying faders.cfg b/configs/maweb-flying faders.cfg deleted file mode 100644 index 0101f1e..0000000 --- a/configs/maweb-flying faders.cfg +++ /dev/null @@ -1,13 +0,0 @@ -; Create a 'flying faders' effect using lua and output it onto maweb faders 1..6 - -[maweb ma] -host = 10.23.42.21 80 ; Thats the IP of your console or OnPC. -user = midimonster ; If a Dot2 is used, the username is automatically set to "remote". -password = midimonster - -[lua generator] -script = configs/flying-faders.lua - - -[map] -generator.wave{1..6} > ma.page1.fader{1..6} \ No newline at end of file diff --git a/configs/maweb-flying-faders.cfg b/configs/maweb-flying-faders.cfg new file mode 100644 index 0000000..85aeada --- /dev/null +++ b/configs/maweb-flying-faders.cfg @@ -0,0 +1,13 @@ +; Create a 'flying faders' effect using lua and output it onto maweb faders 1..6 + +[maweb ma] +host = 10.23.42.21 80 ; Thats the IP of your console or OnPC. +user = midimonster ; If a Dot2 is used, the username is automatically set to "remote". +password = midimonster + +[lua generator] +script = configs/flying-faders.lua + + +[map] +generator.wave{1..6} > ma.page1.fader{1..6} -- cgit v1.2.3 From 90759c9ff4fc4380eec03962656ae4b9ca09bf5a Mon Sep 17 00:00:00 2001 From: Spacelord09 Date: Thu, 22 Aug 2019 20:35:34 +0200 Subject: Added Dot2 B-wing to example config. --- configs/maweb-flying-faders.cfg | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'configs') diff --git a/configs/maweb-flying-faders.cfg b/configs/maweb-flying-faders.cfg index 85aeada..e2fe6c6 100644 --- a/configs/maweb-flying-faders.cfg +++ b/configs/maweb-flying-faders.cfg @@ -10,4 +10,5 @@ script = configs/flying-faders.lua [map] -generator.wave{1..6} > ma.page1.fader{1..6} +generator.wave{1..6} > ma.page1.fader{1..6} ; Fader 1 to 6 +;generator.wave{7..14} > ma.page1.fader{7..14]} ; Fader 7 to 14 (F-wing1 on Dot2) -- cgit v1.2.3 From 88f59354ef22c815ca2731a13ac87677c3571343 Mon Sep 17 00:00:00 2001 From: cbdev Date: Thu, 22 Aug 2019 21:21:59 +0200 Subject: Fix spelling, fix comments Inline-comments are not currently supported --- configs/maweb-flying-faders.cfg | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) (limited to 'configs') diff --git a/configs/maweb-flying-faders.cfg b/configs/maweb-flying-faders.cfg index e2fe6c6..03b0939 100644 --- a/configs/maweb-flying-faders.cfg +++ b/configs/maweb-flying-faders.cfg @@ -1,14 +1,17 @@ ; Create a 'flying faders' effect using lua and output it onto maweb faders 1..6 [maweb ma] -host = 10.23.42.21 80 ; Thats the IP of your console or OnPC. -user = midimonster ; If a Dot2 is used, the username is automatically set to "remote". +; That's the IP of your console or OnPC. +host = 10.23.42.21 80 +; If a Dot2 is used, the username is automatically set to "remote". +user = midimonster password = midimonster [lua generator] script = configs/flying-faders.lua - [map] -generator.wave{1..6} > ma.page1.fader{1..6} ; Fader 1 to 6 -;generator.wave{7..14} > ma.page1.fader{7..14]} ; Fader 7 to 14 (F-wing1 on Dot2) +; Fader 1 to 6 (Core Wing) +generator.wave{1..6} > ma.page1.fader{1..6} +; Fader 7 to 14 (F-wing1 on Dot2) +;generator.wave{7..14} > ma.page1.fader{7..14]} -- cgit v1.2.3 From 350f0d2d2eaff5f0d57b09857102e2df1e96d733 Mon Sep 17 00:00:00 2001 From: cbdev Date: Thu, 7 Nov 2019 18:44:19 +0100 Subject: Makefile install target and packaging instructions (Fixes #28) --- Makefile | 22 +++++++++++++++++----- README.md | 23 +++++++++++++++++++++++ backends/lua.md | 5 +---- backends/maweb.c | 2 +- backends/winmidi.c | 2 +- config.c | 38 +++++++++++++++++++++++++++++++++++--- configs/flying-faders.cfg | 2 +- configs/lua.cfg | 2 +- midimonster.h | 13 ++++++++++++- plugin.c | 12 ++++++++++-- 10 files changed, 102 insertions(+), 19 deletions(-) (limited to 'configs') diff --git a/Makefile b/Makefile index 2d88c49..5a83a2d 100644 --- a/Makefile +++ b/Makefile @@ -1,8 +1,7 @@ -.PHONY: all clean run sanitize backends windows full backends-full +.PHONY: all clean run sanitize backends windows full backends-full install OBJS = config.o backend.o plugin.o -PLUGINDIR = "\"./backends/\"" -PLUGINDIR_W32 = "\"backends\\\\\"" +PREFIX ?= /usr SYSTEM := $(shell uname -s) CFLAGS ?= -g -Wall -Wpedantic @@ -11,7 +10,6 @@ CFLAGS += -fvisibility=hidden #CFLAGS += -DDEBUG midimonster: LDLIBS = -ldl -midimonster: CFLAGS += -DPLUGINS=$(PLUGINDIR) # Work around strange linker passing convention differences in Linux and OSX ifeq ($(SYSTEM),Linux) @@ -21,6 +19,14 @@ ifeq ($(SYSTEM),Darwin) midimonster: LDFLAGS += -Wl,-export_dynamic endif +# Allow overriding the locations for backend plugins and default configuration +ifdef DEFAULT_CFG +midimonster: CFLAGS += -DDEFAULT_CFG=\"$(DEFAULT_CFG)\" +endif +ifdef PLUGINS +midimonster: CFLAGS += -DPLUGINS=\"$(PLUGINS)\" +endif + all: midimonster backends full: midimonster backends-full @@ -39,7 +45,7 @@ midimonster: midimonster.c portability.h $(OBJS) $(CC) $(CFLAGS) $(LDFLAGS) $< $(OBJS) $(LDLIBS) -o $@ midimonster.exe: export CC = x86_64-w64-mingw32-gcc -midimonster.exe: CFLAGS += -DPLUGINS=$(PLUGINDIR_W32) -Wno-format +midimonster.exe: CFLAGS += -Wno-format midimonster.exe: LDLIBS = -lws2_32 midimonster.exe: LDFLAGS += -Wl,--out-implib,libmmapi.a midimonster.exe: midimonster.c portability.h $(OBJS) @@ -55,6 +61,12 @@ clean: run: valgrind --leak-check=full --show-leak-kinds=all ./midimonster +install: + install -d "$(DESTDIR)$(PREFIX)/bin" + install -d "$(DESTDIR)$(PREFIX)/lib/midimonster" + install -m 0755 midimonster "$(DESTDIR)$(PREFIX)/bin" + install -m 0755 backends/*.so "$(DESTDIR)$(PREFIX)/lib/midimonster" + sanitize: export CC = clang 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 130945a..c31f16c 100644 --- a/README.md +++ b/README.md @@ -156,6 +156,16 @@ be used to build a subset of the backends as well as the core. For Linux and OSX, just running `make` in the source directory should do the trick. +The build process accepts the following parameters, either from the environment or +as arguments to the `make` invocation: + +| Target | Parameter | Default value | Description | +|---------------|-----------------------|-------------------------------|-------------------------------| +| build targets | `DEFAULT_CFG` | `monster.cfg` | Default configuration file | +| build targets | `PLUGINS` | Linux/OSX: `./backends/`, Windows: `backends\` | Backend plugin library path | +| `install` | `DESTDIR` | empty | Destination directory for packaging builds | +| `install` | `PREFIX` | `/usr` | Install prefix for binaries | + Some backends have been marked as optional as they require rather large additional software to be installed, for example the `ola` backend. To create a build including these, run `make full`. @@ -163,6 +173,19 @@ To build for Windows, you still need to compile on a Linux machine. Install the crosscompiler package listed above and run `make windows`. This will build `midimonster.exe` as well as a set of backends as DLL files. +For system-wide install or packaging builds, the following steps are recommended: + +``` +export PREFIX=/usr +export PLUGINS=$PREFIX/lib/midimonster +export DEFAULT_CFG=/etc/midimonster.cfg +make +make install +``` + +Depending on your configuration of `DESTDIR`, the `make install` step may require root privileges to +install the binaries to the appropriate destinations. + ## Development The architecture is split into the `midimonster` core, handling mapping diff --git a/backends/lua.md b/backends/lua.md index 6ad5c2a..f38e189 100644 --- a/backends/lua.md +++ b/backends/lua.md @@ -43,7 +43,7 @@ The `lua` backend does not take any global configuration. | Option | Example value | Default value | Description | |---------------|-----------------------|-----------------------|-----------------------| -| `script` | `script.lua` | none | Lua source file | +| `script` | `script.lua` | none | Lua source file (relative to configuration file)| A single instance may have multiple `source` options specified, which will all be read cumulatively. @@ -64,6 +64,3 @@ Using these names as arguments to the output and value interface functions works Output values will not trigger corresponding input event handlers unless the channel is mapped back in the MIDIMonster configuration. - -The path to the Lua source files is relative to the current working directory. This may lead -to problems when copying configuration between installations. diff --git a/backends/maweb.c b/backends/maweb.c index c98d04b..57d04ae 100644 --- a/backends/maweb.c +++ b/backends/maweb.c @@ -470,7 +470,7 @@ static int maweb_request_playbacks(instance* inst){ offsets[0] = offsets[1] = offsets[2] = 1; page_index = data->channel[channel].page; //poll logic differs between the consoles because reasons - //dont quote me on this section + //don't quote me on this section if(data->peer_type == peer_dot2){ //blocks 0, 100 & 200 have 21 execs and need to be queried from fader view view = (data->channel[channel].index >= 300) ? 3 : 2; diff --git a/backends/winmidi.c b/backends/winmidi.c index de7d867..d6bc0bc 100644 --- a/backends/winmidi.c +++ b/backends/winmidi.c @@ -84,7 +84,7 @@ static int winmidi_configure_instance(instance* inst, char* option, char* value) } if(!strcmp(option, "write")){ if(data->write){ - fprintf(stderr, "winmidi instance %s already connected to an otput device\n", inst->name); + fprintf(stderr, "winmidi instance %s already connected to an output device\n", inst->name); return 1; } data->write = strdup(value); diff --git a/config.c b/config.c index f4d928e..ddee720 100644 --- a/config.c +++ b/config.c @@ -1,5 +1,7 @@ #include #include +#include +#include #include "midimonster.h" #include "config.h" #include "backend.h" @@ -310,16 +312,45 @@ done: return rv; } -int config_read(char* cfg_file){ +int config_read(char* cfg_filepath){ int rv = 1; size_t line_alloc = 0; ssize_t status; map_type mapping_type = map_rtl; char* line_raw = NULL, *line, *separator; - FILE* source = fopen(cfg_file, "r"); + + //create heap copy of file name because original might be in readonly memory + char* source_dir = strdup(cfg_filepath), *source_file = NULL; + #ifdef _WIN32 + char path_separator = '\\'; + #else + char path_separator = '/'; + #endif + + if(!source_dir){ + fprintf(stderr, "Failed to allocate memory\n"); + return 1; + } + + //change working directory to the one containing the configuration file so relative paths work as expected + source_file = strrchr(source_dir, path_separator); + if(source_file){ + *source_file = 0; + source_file++; + if(chdir(source_dir)){ + fprintf(stderr, "Failed to change to configuration file directory %s: %s\n", source_dir, strerror(errno)); + goto bail; + } + } + else{ + source_file = source_dir; + } + + FILE* source = fopen(source_file, "r"); + if(!source){ fprintf(stderr, "Failed to open configuration file for reading\n"); - return 1; + goto bail; } for(status = getline(&line_raw, &line_alloc, source); status >= 0; status = getline(&line_raw, &line_alloc, source)){ @@ -459,6 +490,7 @@ int config_read(char* cfg_file){ rv = 0; bail: + free(source_dir); fclose(source); free(line_raw); return rv; diff --git a/configs/flying-faders.cfg b/configs/flying-faders.cfg index 4197581..d331f38 100644 --- a/configs/flying-faders.cfg +++ b/configs/flying-faders.cfg @@ -13,7 +13,7 @@ dest = learn@9000 /1/xy = ff 0.0 1.0 0.0 1.0 [lua generator] -script = configs/flying-faders.lua +script = flying-faders.lua [map] diff --git a/configs/lua.cfg b/configs/lua.cfg index af17496..098c0f1 100644 --- a/configs/lua.cfg +++ b/configs/lua.cfg @@ -13,7 +13,7 @@ axis.ABS_X = 34300 0 65535 255 4095 axis.ABS_Y = 34300 0 65535 255 4095 [lua lua] -script = configs/demo.lua +script = demo.lua [artnet art] universe = 0 diff --git a/midimonster.h b/midimonster.h index 491cc11..b05326c 100644 --- a/midimonster.h +++ b/midimonster.h @@ -33,7 +33,18 @@ #include "portability.h" /* Default configuration file name to read when no other is specified */ -#define DEFAULT_CFG "monster.cfg" +#ifndef DEFAULT_CFG + #define DEFAULT_CFG "monster.cfg" +#endif + +/* Default backend plugin location */ +#ifndef PLUGINS + #ifndef _WIN32 + #define PLUGINS "./backends/" + #else + #define PLUGINS "backends\\" + #endif +#endif /* Forward declare some of the structs so we can use them in each other */ struct _channel_value; diff --git a/plugin.c b/plugin.c index dd99041..a14baff 100644 --- a/plugin.c +++ b/plugin.c @@ -24,13 +24,21 @@ static int plugin_attach(char* path, char* file){ plugin_init init = NULL; void* handle = NULL; char* lib = NULL; + #ifdef _WIN32 + char* path_separator = "\\"; + #else + char* path_separator = "/"; + #endif - lib = calloc(strlen(path) + strlen(file) + 1, sizeof(char)); + lib = calloc(strlen(path) + strlen(file) + 2, sizeof(char)); if(!lib){ fprintf(stderr, "Failed to allocate memory\n"); return 1; } - snprintf(lib, strlen(path) + strlen(file) + 1, "%s%s", path, file); + snprintf(lib, strlen(path) + strlen(file) + 2, "%s%s%s", + path, + (path[strlen(path)] == path_separator[0]) ? "" : path_separator, + file); handle = dlopen(lib, RTLD_NOW); if(!handle){ -- cgit v1.2.3 From ddbc61cf653120b513e4e76d893909ba4a77b97f Mon Sep 17 00:00:00 2001 From: cbdev Date: Sat, 9 Nov 2019 14:00:29 +0100 Subject: Fix configurations --- configs/launchctl-sacn.cfg | 7 +++---- configs/maweb-flying-faders.cfg | 2 +- 2 files changed, 4 insertions(+), 5 deletions(-) (limited to 'configs') diff --git a/configs/launchctl-sacn.cfg b/configs/launchctl-sacn.cfg index 781ca40..0f4a19b 100644 --- a/configs/launchctl-sacn.cfg +++ b/configs/launchctl-sacn.cfg @@ -6,15 +6,14 @@ [backend midi] name = MIDIMonster -[backend artnet] +[backend sacn] bind = 0.0.0.0 [midi lc] read = Launch Control -[artnet out] -universe = 0 -destination = 255.255.255.255 +[sacn out] +universe = 1 [map] diff --git a/configs/maweb-flying-faders.cfg b/configs/maweb-flying-faders.cfg index 03b0939..806c4d4 100644 --- a/configs/maweb-flying-faders.cfg +++ b/configs/maweb-flying-faders.cfg @@ -8,7 +8,7 @@ user = midimonster password = midimonster [lua generator] -script = configs/flying-faders.lua +script = flying-faders.lua [map] ; Fader 1 to 6 (Core Wing) -- cgit v1.2.3