From b618c4a6b74a52f830ca53029e1cc680d56a2501 Mon Sep 17 00:00:00 2001 From: cbdev Date: Sat, 6 Jul 2019 17:25:12 +0200 Subject: Implement Lua backend --- configs/lua.cfg | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) create mode 100644 configs/lua.cfg (limited to 'configs/lua.cfg') 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 -- 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/lua.cfg') 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/lua.cfg') 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 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/lua.cfg') 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 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/lua.cfg') 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