From 871bc1568f94ee4026fd64df062572b91d45d462 Mon Sep 17 00:00:00 2001 From: cbdev Date: Sat, 4 Jan 2020 18:39:04 +0100 Subject: Add input_channel call to Lua backend --- backends/lua.c | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) (limited to 'backends/lua.c') diff --git a/backends/lua.c b/backends/lua.c index ee9e03f..510fc72 100644 --- a/backends/lua.c +++ b/backends/lua.c @@ -9,6 +9,7 @@ #endif #define LUA_REGISTRY_KEY "_midimonster_lua_instance" +#define LUA_REGISTRY_CURRENT_CHANNEL "_midimonster_lua_channel" static size_t timers = 0; static lua_timer* timer = NULL; @@ -189,6 +190,7 @@ static int lua_callback_interval(lua_State* interpreter){ if(lua_gettable(interpreter, LUA_REGISTRYINDEX) == LUA_TNUMBER){ //already interval'd reference = luaL_checkinteger(interpreter, 4); + DBGPF("Updating interval to %" PRIu64 " msec", interval); } else if(interval){ //get a reference to the function @@ -199,6 +201,8 @@ static int lua_callback_interval(lua_State* interpreter){ lua_pushvalue(interpreter, 1); lua_pushinteger(interpreter, reference); lua_settable(interpreter, LUA_REGISTRYINDEX); + + DBGPF("Registered interval with %" PRIu64 " msec", interval); } //find matching timer @@ -273,6 +277,12 @@ static int lua_callback_output_value(lua_State* interpreter){ return lua_callback_value(interpreter, 0); } +static int lua_callback_input_channel(lua_State* interpreter){ + lua_pushstring(interpreter, LUA_REGISTRY_CURRENT_CHANNEL); + lua_gettable(interpreter, LUA_REGISTRYINDEX); + return 1; +} + static int lua_configure(char* option, char* value){ LOG("No backend configuration possible"); return 1; @@ -320,6 +330,7 @@ static instance* lua_instance(){ 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); + lua_register(data->interpreter, "input_channel", lua_callback_input_channel); //store instance pointer to the lua state lua_pushstring(data->interpreter, LUA_REGISTRY_KEY); @@ -374,6 +385,11 @@ static int lua_set(instance* inst, size_t num, channel** c, channel_value* v){ data->input[c[n]->ident] = v[n].normalised; //call lua channel handlers if present if(data->reference[c[n]->ident] != LUA_NOREF){ + //push the channel name + lua_pushstring(data->interpreter, LUA_REGISTRY_CURRENT_CHANNEL); + lua_pushstring(data->interpreter, data->channel_name[c[n]->ident]); + lua_settable(data->interpreter, LUA_REGISTRYINDEX); + 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){ @@ -382,6 +398,11 @@ static int lua_set(instance* inst, size_t num, channel** c, channel_value* v){ } } } + + //clear the channel name + lua_pushstring(data->interpreter, LUA_REGISTRY_CURRENT_CHANNEL); + lua_pushnil(data->interpreter); + lua_settable(data->interpreter, LUA_REGISTRYINDEX); return 0; } @@ -422,6 +443,7 @@ static int lua_handle(size_t num, managed_fd* fds){ timer[n].delta %= timer[n].interval; lua_rawgeti(timer[n].interpreter, LUA_REGISTRYINDEX, timer[n].reference); lua_pcall(timer[n].interpreter, 0, 0, 0); + DBGPF("Calling interval timer function %" PRIsize_t, n); } } } -- cgit v1.2.3 From 78b21a9ac3f975f35ec7b61108531e1495eb91c0 Mon Sep 17 00:00:00 2001 From: cbdev Date: Sun, 12 Jan 2020 17:34:14 +0100 Subject: Rework instance creation --- README.md | 6 +++--- backend.c | 6 +++--- backend.h | 2 +- backends/artnet.c | 14 ++++---------- backends/artnet.h | 2 +- backends/evdev.c | 13 ++++--------- backends/evdev.h | 2 +- backends/jack.c | 12 +++--------- backends/jack.h | 2 +- backends/loopback.c | 15 +++++---------- backends/loopback.h | 2 +- backends/lua.c | 13 ++++--------- backends/lua.h | 2 +- backends/maweb.c | 13 ++++--------- backends/maweb.h | 2 +- backends/midi.c | 11 +++-------- backends/midi.h | 2 +- backends/ola.cpp | 16 +++++----------- backends/ola.h | 2 +- backends/osc.c | 11 +++-------- backends/osc.h | 2 +- backends/sacn.c | 11 +++-------- backends/sacn.h | 2 +- backends/winmidi.c | 15 +++++---------- backends/winmidi.h | 2 +- config.c | 14 +++++++++----- midimonster.c | 2 +- midimonster.h | 21 ++++----------------- 28 files changed, 75 insertions(+), 142 deletions(-) (limited to 'backends/lua.c') diff --git a/README.md b/README.md index b9be3cf..725390f 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ # The MIDIMonster -Named for its scary math, the MIDIMonster is a universal translation -tool between multi-channel absolute-value-based control and/or bus protocols. +Named for its scary math, the MIDIMonster is a universal control and translation +tool for multi-channel absolute-value-based control and/or bus protocols. Currently, the MIDIMonster supports the following protocols: @@ -18,7 +18,7 @@ Currently, the MIDIMonster supports the following protocols: with additional flexibility provided by a [Lua scripting environment](backends/lua.md). -The MIDIMonster allows the user to translate any channel on one protocol into channel(s) +With these features, 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)) diff --git a/backend.c b/backend.c index a2b120f..e9c3829 100644 --- a/backend.c +++ b/backend.c @@ -54,7 +54,7 @@ int backends_notify(size_t nev, channel** c, channel_value* v){ if(c[p]->instance == instances[u]){ xval = v[n]; xchnl = c[n]; - + v[n] = v[p]; c[n] = c[p]; @@ -105,7 +105,7 @@ MM_API channel* mm_channel(instance* inst, uint64_t ident, uint8_t create){ return channels[nchannels++]; } -MM_API instance* mm_instance(){ +instance* mm_instance(){ instance** new_inst = realloc(instances, (ninstances + 1) * sizeof(instance*)); if(!new_inst){ //TODO free @@ -274,7 +274,7 @@ int backends_start(){ if(p == ninstances){ continue; } - + //fetch list of instances if(mm_backend_instances(backends[u].name, &n, &inst)){ fprintf(stderr, "Failed to fetch instance list for initialization of backend %s\n", backends[u].name); diff --git a/backend.h b/backend.h index 6573e17..de9b5dc 100644 --- a/backend.h +++ b/backend.h @@ -10,10 +10,10 @@ int backends_start(); int backends_stop(); void instances_free(); void channels_free(); +instance* mm_instance(); /* Backend API */ MM_API channel* mm_channel(instance* inst, uint64_t ident, uint8_t create); -MM_API instance* mm_instance(); MM_API instance* mm_instance_find(char* name, uint64_t ident); MM_API int mm_backend_instances(char* name, size_t* ninst, instance*** inst); MM_API int mm_backend_register(backend b); diff --git a/backends/artnet.c b/backends/artnet.c index 0bd1a32..ed426b0 100644 --- a/backends/artnet.c +++ b/backends/artnet.c @@ -94,23 +94,17 @@ static int artnet_configure(char* option, char* value){ return 1; } -static instance* artnet_instance(){ - artnet_instance_data* data = NULL; - instance* inst = mm_instance(); - if(!inst){ - return NULL; - } - - data = calloc(1, sizeof(artnet_instance_data)); +static int artnet_instance(instance* inst){ + artnet_instance_data* data = calloc(1, sizeof(artnet_instance_data)); if(!data){ LOG("Failed to allocate memory"); - return NULL; + return 1; } data->net = default_net; inst->impl = data; - return inst; + return 0; } static int artnet_configure_instance(instance* inst, char* option, char* value){ diff --git a/backends/artnet.h b/backends/artnet.h index 59bd53f..1efdee6 100644 --- a/backends/artnet.h +++ b/backends/artnet.h @@ -6,7 +6,7 @@ MM_PLUGIN_API int init(); static int artnet_configure(char* option, char* value); static int artnet_configure_instance(instance* instance, char* option, char* value); -static instance* artnet_instance(); +static int artnet_instance(instance* inst); static channel* artnet_channel(instance* instance, char* spec, uint8_t flags); static int artnet_set(instance* inst, size_t num, channel** c, channel_value* v); static int artnet_handle(size_t num, managed_fd* fds); diff --git a/backends/evdev.c b/backends/evdev.c index 4725ef7..af5ec74 100644 --- a/backends/evdev.c +++ b/backends/evdev.c @@ -63,16 +63,11 @@ static int evdev_configure(char* option, char* value) { return 1; } -static instance* evdev_instance(){ - instance* inst = mm_instance(); - if(!inst){ - return NULL; - } - +static int evdev_instance(instance* inst){ evdev_instance_data* data = calloc(1, sizeof(evdev_instance_data)); if(!data){ LOG("Failed to allocate memory"); - return NULL; + return 1; } data->input_fd = -1; @@ -81,12 +76,12 @@ static instance* evdev_instance(){ if(!data->output_proto){ LOG("Failed to initialize libevdev output prototype device"); free(data); - return NULL; + return 1; } #endif inst->impl = data; - return inst; + return 0; } static int evdev_attach(instance* inst, evdev_instance_data* data, char* node){ diff --git a/backends/evdev.h b/backends/evdev.h index 0c877fc..e896d2d 100644 --- a/backends/evdev.h +++ b/backends/evdev.h @@ -11,7 +11,7 @@ MM_PLUGIN_API int init(); static int evdev_configure(char* option, char* value); static int evdev_configure_instance(instance* instance, char* option, char* value); -static instance* evdev_instance(); +static int evdev_instance(instance* inst); static channel* evdev_channel(instance* instance, char* spec, uint8_t flags); static int evdev_set(instance* inst, size_t num, channel** c, channel_value* v); static int evdev_handle(size_t num, managed_fd* fds); diff --git a/backends/jack.c b/backends/jack.c index d7f68c4..c862096 100644 --- a/backends/jack.c +++ b/backends/jack.c @@ -334,19 +334,13 @@ static int mmjack_configure_instance(instance* inst, char* option, char* value){ return 0; } -static instance* mmjack_instance(){ - instance* inst = mm_instance(); - if(!inst){ - return NULL; - } - +static int mmjack_instance(instance* inst){ inst->impl = calloc(1, sizeof(mmjack_instance_data)); if(!inst->impl){ LOG("Failed to allocate memory"); - return NULL; + return 1; } - - return inst; + return 0; } static int mmjack_parse_midispec(mmjack_channel_ident* ident, char* spec){ diff --git a/backends/jack.h b/backends/jack.h index 66c66db..03ce052 100644 --- a/backends/jack.h +++ b/backends/jack.h @@ -5,7 +5,7 @@ MM_PLUGIN_API int init(); static int mmjack_configure(char* option, char* value); static int mmjack_configure_instance(instance* inst, char* option, char* value); -static instance* mmjack_instance(); +static int mmjack_instance(instance* inst); static channel* mmjack_channel(instance* inst, char* spec, uint8_t flags); static int mmjack_set(instance* inst, size_t num, channel** c, channel_value* v); static int mmjack_handle(size_t num, managed_fd* fds); diff --git a/backends/loopback.c b/backends/loopback.c index 085d1df..eaecdb4 100644 --- a/backends/loopback.c +++ b/backends/loopback.c @@ -34,19 +34,14 @@ static int loopback_configure_instance(instance* inst, char* option, char* value return 0; } -static instance* loopback_instance(){ - instance* i = mm_instance(); - if(!i){ - return NULL; - } - - i->impl = calloc(1, sizeof(loopback_instance_data)); - if(!i->impl){ +static int loopback_instance(instance* inst){ + inst->impl = calloc(1, sizeof(loopback_instance_data)); + if(!inst->impl){ LOG("Failed to allocate memory"); - return NULL; + return 1; } - return i; + return 0; } static channel* loopback_channel(instance* inst, char* spec, uint8_t flags){ diff --git a/backends/loopback.h b/backends/loopback.h index c508d72..cfb2e19 100644 --- a/backends/loopback.h +++ b/backends/loopback.h @@ -3,7 +3,7 @@ MM_PLUGIN_API int init(); static int loopback_configure(char* option, char* value); static int loopback_configure_instance(instance* inst, char* option, char* value); -static instance* loopback_instance(); +static int loopback_instance(instance* inst); static channel* loopback_channel(instance* inst, char* spec, uint8_t flags); static int loopback_set(instance* inst, size_t num, channel** c, channel_value* v); static int loopback_handle(size_t num, managed_fd* fds); diff --git a/backends/lua.c b/backends/lua.c index 510fc72..7d20fb1 100644 --- a/backends/lua.c +++ b/backends/lua.c @@ -304,16 +304,11 @@ static int lua_configure_instance(instance* inst, char* option, char* value){ return 1; } -static instance* lua_instance(){ - instance* inst = mm_instance(); - if(!inst){ - return NULL; - } - +static int lua_instance(instance* inst){ lua_instance_data* data = calloc(1, sizeof(lua_instance_data)); if(!data){ LOG("Failed to allocate memory"); - return NULL; + return 1; } //load the interpreter @@ -321,7 +316,7 @@ static instance* lua_instance(){ if(!data->interpreter){ LOG("Failed to initialize interpreter"); free(data); - return NULL; + return 1; } luaL_openlibs(data->interpreter); @@ -338,7 +333,7 @@ static instance* lua_instance(){ lua_settable(data->interpreter, LUA_REGISTRYINDEX); inst->impl = data; - return inst; + return 0; } static channel* lua_channel(instance* inst, char* spec, uint8_t flags){ diff --git a/backends/lua.h b/backends/lua.h index 75f03c4..ebe2046 100644 --- a/backends/lua.h +++ b/backends/lua.h @@ -12,7 +12,7 @@ MM_PLUGIN_API int init(); static int lua_configure(char* option, char* value); static int lua_configure_instance(instance* inst, char* option, char* value); -static instance* lua_instance(); +static int lua_instance(instance* inst); static channel* lua_channel(instance* inst, char* spec, uint8_t flags); static int lua_set(instance* inst, size_t num, channel** c, channel_value* v); static int lua_handle(size_t num, managed_fd* fds); diff --git a/backends/maweb.c b/backends/maweb.c index f81ab46..6a006bd 100644 --- a/backends/maweb.c +++ b/backends/maweb.c @@ -205,16 +205,11 @@ static int maweb_configure_instance(instance* inst, char* option, char* value){ return 1; } -static instance* maweb_instance(){ - instance* inst = mm_instance(); - if(!inst){ - return NULL; - } - +static int maweb_instance(instance* inst){ maweb_instance_data* data = calloc(1, sizeof(maweb_instance_data)); if(!data){ LOG("Failed to allocate memory"); - return NULL; + return 1; } data->fd = -1; @@ -222,12 +217,12 @@ static instance* maweb_instance(){ if(!data->buffer){ LOG("Failed to allocate memory"); free(data); - return NULL; + return 1; } data->allocated = MAWEB_RECV_CHUNK; inst->impl = data; - return inst; + return 0; } static channel* maweb_channel(instance* inst, char* spec, uint8_t flags){ diff --git a/backends/maweb.h b/backends/maweb.h index 50b777a..80835d9 100644 --- a/backends/maweb.h +++ b/backends/maweb.h @@ -3,7 +3,7 @@ MM_PLUGIN_API int init(); static int maweb_configure(char* option, char* value); static int maweb_configure_instance(instance* inst, char* option, char* value); -static instance* maweb_instance(); +static int maweb_instance(instance* inst); static channel* maweb_channel(instance* inst, char* spec, uint8_t flags); static int maweb_set(instance* inst, size_t num, channel** c, channel_value* v); static int maweb_handle(size_t num, managed_fd* fds); diff --git a/backends/midi.c b/backends/midi.c index 11d759d..f73ebb4 100644 --- a/backends/midi.c +++ b/backends/midi.c @@ -69,19 +69,14 @@ static int midi_configure(char* option, char* value){ return 1; } -static instance* midi_instance(){ - instance* inst = mm_instance(); - if(!inst){ - return NULL; - } - +static int midi_instance(instance* inst){ inst->impl = calloc(1, sizeof(midi_instance_data)); if(!inst->impl){ LOG("Failed to allocate memory"); - return NULL; + return 1; } - return inst; + return 0; } static int midi_configure_instance(instance* inst, char* option, char* value){ diff --git a/backends/midi.h b/backends/midi.h index 66a02bc..dcee010 100644 --- a/backends/midi.h +++ b/backends/midi.h @@ -3,7 +3,7 @@ MM_PLUGIN_API int init(); static int midi_configure(char* option, char* value); static int midi_configure_instance(instance* instance, char* option, char* value); -static instance* midi_instance(); +static int midi_instance(instance* inst); static channel* midi_channel(instance* instance, char* spec, uint8_t flags); static int midi_set(instance* inst, size_t num, channel** c, channel_value* v); static int midi_handle(size_t num, managed_fd* fds); diff --git a/backends/ola.cpp b/backends/ola.cpp index 09d68c9..106dbd5 100644 --- a/backends/ola.cpp +++ b/backends/ola.cpp @@ -40,21 +40,15 @@ static int ola_configure(char* option, char* value){ return 1; } -static instance* ola_instance(){ - ola_instance_data* data = NULL; - instance* inst = mm_instance(); - if(!inst){ - return NULL; - } - - data = (ola_instance_data*)calloc(1, sizeof(ola_instance_data)); +static int ola_instance(instance* inst){ + ola_instance_data* data = (ola_instance_data*) calloc(1, sizeof(ola_instance_data)); if(!data){ LOG("Failed to allocate memory"); - return NULL; + return 1; } inst->impl = data; - return inst; + return 0; } static int ola_configure_instance(instance* inst, char* option, char* value){ @@ -188,7 +182,7 @@ static void ola_data_receive(unsigned int universe, const ola::DmxBuffer& ola_dm else{ chan = mm_channel(inst, p, 0); } - + if(!chan){ LOGPF("Active channel %" PRIsize_t " on %s not known to core", p, inst->name); return; diff --git a/backends/ola.h b/backends/ola.h index 083e971..68244ec 100644 --- a/backends/ola.h +++ b/backends/ola.h @@ -7,7 +7,7 @@ extern "C" { MM_PLUGIN_API int init(); static int ola_configure(char* option, char* value); static int ola_configure_instance(instance* instance, char* option, char* value); - static instance* ola_instance(); + static int ola_instance(instance* inst); static channel* ola_channel(instance* instance, char* spec, uint8_t flags); static int ola_set(instance* inst, size_t num, channel** c, channel_value* v); static int ola_handle(size_t num, managed_fd* fds); diff --git a/backends/osc.c b/backends/osc.c index 7b9a5a5..754c290 100644 --- a/backends/osc.c +++ b/backends/osc.c @@ -562,21 +562,16 @@ static int osc_configure_instance(instance* inst, char* option, char* value){ return 1; } -static instance* osc_instance(){ - instance* inst = mm_instance(); - if(!inst){ - return NULL; - } - +static int osc_instance(instance* inst){ osc_instance_data* data = calloc(1, sizeof(osc_instance_data)); if(!data){ LOG("Failed to allocate memory"); - return NULL; + return 1; } data->fd = -1; inst->impl = data; - return inst; + return 0; } static channel* osc_map_channel(instance* inst, char* spec, uint8_t flags){ diff --git a/backends/osc.h b/backends/osc.h index f8ff3ff..ec75e3f 100644 --- a/backends/osc.h +++ b/backends/osc.h @@ -10,7 +10,7 @@ MM_PLUGIN_API int init(); static int osc_configure(char* option, char* value); static int osc_configure_instance(instance* inst, char* option, char* value); -static instance* osc_instance(); +static int osc_instance(instance* inst); static channel* osc_map_channel(instance* inst, char* spec, uint8_t flags); static int osc_set(instance* inst, size_t num, channel** c, channel_value* v); static int osc_handle(size_t num, managed_fd* fds); diff --git a/backends/sacn.c b/backends/sacn.c index ff2b61e..5096123 100644 --- a/backends/sacn.c +++ b/backends/sacn.c @@ -195,19 +195,14 @@ static int sacn_configure_instance(instance* inst, char* option, char* value){ return 1; } -static instance* sacn_instance(){ - instance* inst = mm_instance(); - if(!inst){ - return NULL; - } - +static int sacn_instance(instance* inst){ inst->impl = calloc(1, sizeof(sacn_instance_data)); if(!inst->impl){ LOG("Failed to allocate memory"); - return NULL; + return 1; } - return inst; + return 0; } static channel* sacn_channel(instance* inst, char* spec, uint8_t flags){ diff --git a/backends/sacn.h b/backends/sacn.h index c8d11e9..ac59441 100644 --- a/backends/sacn.h +++ b/backends/sacn.h @@ -3,7 +3,7 @@ MM_PLUGIN_API int init(); static int sacn_configure(char* option, char* value); static int sacn_configure_instance(instance* instance, char* option, char* value); -static instance* sacn_instance(); +static int sacn_instance(instance* inst); static channel* sacn_channel(instance* instance, char* spec, uint8_t flags); static int sacn_set(instance* inst, size_t num, channel** c, channel_value* v); static int sacn_handle(size_t num, managed_fd* fds); diff --git a/backends/winmidi.c b/backends/winmidi.c index c2aee2f..ad9b02d 100644 --- a/backends/winmidi.c +++ b/backends/winmidi.c @@ -95,19 +95,14 @@ static int winmidi_configure_instance(instance* inst, char* option, char* value) return 1; } -static instance* winmidi_instance(){ - instance* i = mm_instance(); - if(!i){ - return NULL; - } - - i->impl = calloc(1, sizeof(winmidi_instance_data)); - if(!i->impl){ +static int winmidi_instance(instance* inst){ + inst->impl = calloc(1, sizeof(winmidi_instance_data)); + if(!inst->impl){ LOG("Failed to allocate memory"); - return NULL; + return 1; } - return i; + return 0; } static channel* winmidi_channel(instance* inst, char* spec, uint8_t flags){ diff --git a/backends/winmidi.h b/backends/winmidi.h index 81e7439..4c740ea 100644 --- a/backends/winmidi.h +++ b/backends/winmidi.h @@ -3,7 +3,7 @@ MM_PLUGIN_API int init(); static int winmidi_configure(char* option, char* value); static int winmidi_configure_instance(instance* inst, char* option, char* value); -static instance* winmidi_instance(); +static int winmidi_instance(instance* inst); static channel* winmidi_channel(instance* inst, char* spec, uint8_t flags); static int winmidi_set(instance* inst, size_t num, channel** c, channel_value* v); static int winmidi_handle(size_t num, managed_fd* fds); diff --git a/config.c b/config.c index d2b65d4..920e161 100644 --- a/config.c +++ b/config.c @@ -279,7 +279,7 @@ static int config_map(char* to_raw, char* from_raw){ || config_glob_scan(instance_from, &spec_from)){ goto done; } - + if((spec_to.channels != spec_from.channels && spec_from.channels != 1 && spec_to.channels != 1) || spec_to.channels == 0 || spec_from.channels == 0){ @@ -298,7 +298,7 @@ static int config_map(char* to_raw, char* from_raw){ for(n = 0; !rv && n < max(spec_from.channels, spec_to.channels); n++){ channel_from = config_glob_resolve(instance_from, &spec_from, min(n, spec_from.channels), mmchannel_input); channel_to = config_glob_resolve(instance_to, &spec_to, min(n, spec_to.channels), mmchannel_output); - + if(!channel_from || !channel_to){ rv = 1; goto done; @@ -356,7 +356,7 @@ static int config_line(char* line){ else{ //backend instance configuration parser_state = instance_cfg; - + //trim braces line[strlen(line) - 1] = 0; line++; @@ -388,9 +388,13 @@ static int config_line(char* line){ return 1; } - current_instance = current_backend->create(); + current_instance = mm_instance(); if(!current_instance){ - fprintf(stderr, "Failed to instantiate backend %s\n", line); + return 1; + } + + if(current_backend->create(current_instance)){ + fprintf(stderr, "Failed to create %s instance %s\n", line, separator); return 1; } diff --git a/midimonster.c b/midimonster.c index 583601e..b6f7758 100644 --- a/midimonster.c +++ b/midimonster.c @@ -341,7 +341,7 @@ int main(int argc, char** argv){ config_free(); return usage(argv[0]); } - + //load an initial timestamp update_timestamp(); diff --git a/midimonster.h b/midimonster.h index d6f04e7..bb1eb24 100644 --- a/midimonster.h +++ b/midimonster.h @@ -93,8 +93,9 @@ struct _managed_fd; * Parse backend-global configuration options from the user-supplied * configuration file. Returning a non-zero value fails config parsing. * * mmbackend_instance - * Allocate space for a backend instance. Returning NULL signals an out-of-memory - * condition and terminates the program. + * Allocate the backend-specific data parts of the supplied instance + * structure. Returning non-zero signals an error condition and + * terminates the program. * * mmbackend_configure_instance * Parse instance configuration from the user-supplied configuration * file. Returning a non-zero value fails config parsing. @@ -135,7 +136,7 @@ struct _managed_fd; * Return value is currently ignored. */ typedef int (*mmbackend_handle_event)(struct _backend_instance* inst, size_t channels, struct _backend_channel** c, struct _channel_value* v); -typedef struct _backend_instance* (*mmbackend_create_instance)(); +typedef int (*mmbackend_create_instance)(struct _backend_instance* inst); typedef struct _backend_channel* (*mmbackend_parse_channel)(struct _backend_instance* instance, char* spec, uint8_t flags); typedef void (*mmbackend_free_channel)(struct _backend_channel* c); typedef int (*mmbackend_configure)(char* option, char* value); @@ -222,20 +223,6 @@ typedef struct /*_mm_channel_mapping*/ { */ MM_API int mm_backend_register(backend b); -/* - * Provides a pointer to a newly (zero-)allocated instance. - * All instance pointers need to be allocated via this API - * in order to be assignable from the configuration parser. - * This API should be called from the mmbackend_create_instance - * call of your backend. - * - * Instances returned from this call are freed by midimonster. - * The contents of the impl members should be freed in the - * mmbackend_shutdown procedure of the backend, eg. by querying - * all instances for the backend. - */ -MM_API instance* mm_instance(); - /* * Finds an instance matching the specified backend and identifier. * Since setting an identifier for an instance is optional, -- cgit v1.2.3 From 9c564af18dc3faad8910bfe14b45ed4ab884d797 Mon Sep 17 00:00:00 2001 From: cbdev Date: Sat, 29 Feb 2020 15:35:08 +0100 Subject: Keep console alive when exiting as last process on Windows --- backends/lua.c | 1 + midimonster.c | 21 ++++++++++++++++++--- 2 files changed, 19 insertions(+), 3 deletions(-) (limited to 'backends/lua.c') diff --git a/backends/lua.c b/backends/lua.c index 7d20fb1..955341a 100644 --- a/backends/lua.c +++ b/backends/lua.c @@ -457,6 +457,7 @@ static int lua_start(size_t n, instance** inst){ 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], "input_channel") && strcmp(data->channel_name[p], "interval")){ lua_getglobal(data->interpreter, data->channel_name[p]); data->reference[p] = luaL_ref(data->interpreter, LUA_REGISTRYINDEX); diff --git a/midimonster.c b/midimonster.c index d9a99d9..9cbc3a9 100644 --- a/midimonster.c +++ b/midimonster.c @@ -264,13 +264,27 @@ static fd_set fds_collect(int* max_fd){ } static int platform_initialize(){ -#ifdef _WIN32 + #ifdef _WIN32 WSADATA wsa; WORD version = MAKEWORD(2, 2); if(WSAStartup(version, &wsa)){ return 1; } -#endif + #endif + return 0; +} + +static int platform_shutdown(){ + #ifdef _WIN32 + DWORD processes; + if(GetConsoleProcessList(&processes, 1) == 1){ + fprintf(stderr, "\nMIDIMonster is the last process in this console, please press any key to exit\n"); + HANDLE input = GetStdHandle(STD_INPUT_HANDLE); + SetConsoleMode(input, 0); + FlushConsoleInputBuffer(input); + WaitForSingleObject(input, INFINITE); + } + #endif return 0; } @@ -346,7 +360,7 @@ int main(int argc, char** argv){ fds_free(); plugins_close(); config_free(); - return usage(argv[0]); + return (usage(argv[0]) | platform_shutdown()); } //load an initial timestamp @@ -433,6 +447,7 @@ bail: event_free(); plugins_close(); config_free(); + platform_shutdown(); return rv; } -- cgit v1.2.3 From f9829ae90d4017940047b561e412c6eb7f431adb Mon Sep 17 00:00:00 2001 From: cbdev Date: Sun, 8 Mar 2020 12:13:38 +0100 Subject: Implement timestamp() callback for Lua --- backends/lua.c | 16 +++++++++++----- backends/lua.md | 5 +++-- 2 files changed, 14 insertions(+), 7 deletions(-) (limited to 'backends/lua.c') diff --git a/backends/lua.c b/backends/lua.c index 955341a..9a8091e 100644 --- a/backends/lua.c +++ b/backends/lua.c @@ -75,7 +75,7 @@ static int lua_update_timerfd(){ size_t n = 0; #ifdef MMBACKEND_LUA_TIMERFD struct itimerspec timer_config = { - 0 + {0} }; #endif @@ -260,7 +260,7 @@ static int lua_callback_value(lua_State* interpreter, uint8_t input){ //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]); + lua_pushnumber(interpreter, (input) ? data->input[n] : data->output[n]); return 1; } } @@ -283,6 +283,11 @@ static int lua_callback_input_channel(lua_State* interpreter){ return 1; } +static int lua_callback_timestamp(lua_State* interpreter){ + lua_pushnumber(interpreter, mm_timestamp()); + return 1; +} + static int lua_configure(char* option, char* value){ LOG("No backend configuration possible"); return 1; @@ -326,6 +331,7 @@ static int lua_instance(instance* inst){ lua_register(data->interpreter, "input_value", lua_callback_input_value); lua_register(data->interpreter, "output_value", lua_callback_output_value); lua_register(data->interpreter, "input_channel", lua_callback_input_channel); + lua_register(data->interpreter, "timestamp", lua_callback_timestamp); //store instance pointer to the lua state lua_pushstring(data->interpreter, LUA_REGISTRY_KEY); @@ -417,9 +423,6 @@ static int lua_handle(size_t num, managed_fd* fds){ return 1; } #else - if(!last_timestamp){ - last_timestamp = mm_timestamp(); - } delta = mm_timestamp() - last_timestamp; last_timestamp = mm_timestamp(); #endif @@ -458,6 +461,7 @@ static int lua_start(size_t n, instance** inst){ && strcmp(data->channel_name[p], "input_value") && strcmp(data->channel_name[p], "output_value") && strcmp(data->channel_name[p], "input_channel") + && strcmp(data->channel_name[p], "timestamp") && strcmp(data->channel_name[p], "interval")){ lua_getglobal(data->interpreter, data->channel_name[p]); data->reference[p] = luaL_ref(data->interpreter, LUA_REGISTRYINDEX); @@ -474,6 +478,8 @@ static int lua_start(size_t n, instance** inst){ if(mm_manage_fd(timer_fd, BACKEND_NAME, 1, NULL)){ return 1; } + #else + last_timestamp = mm_timestamp(); #endif return 0; } diff --git a/backends/lua.md b/backends/lua.md index 0c592b7..db4cf39 100644 --- a/backends/lua.md +++ b/backends/lua.md @@ -17,6 +17,7 @@ The following functions are provided within the Lua interpreter for interaction | `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 | | `input_channel()` | `print(input_channel())` | Returns the name of the input channel whose handler function is currently running or `nil` if in an `interval`'ed function (or the initial parse step) | +| `timestamp()` | `print(timestamp())` | Returns the core timestamp for this iteration with millisecond resolution. This is not a performance timer, but intended for timeouting, etc | Example script: ``` @@ -58,8 +59,8 @@ lua1.foo > lua2.bar #### Known bugs / problems -Using any of the interface functions (`output`, `interval`, `input_value`, `output_value`, `input_channel`) -as an input channel name to a Lua instance will not call any handler functions. +Using any of the interface functions (`output`, `interval`, `input_value`, `output_value`, `input_channel`, +`timestamp`) 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 -- cgit v1.2.3 From 5f4b349aff49be0a5f6895631a93c47fcafcff93 Mon Sep 17 00:00:00 2001 From: cbdev Date: Sun, 8 Mar 2020 14:26:50 +0100 Subject: Add lua debug logging --- backends/lua.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) (limited to 'backends/lua.c') diff --git a/backends/lua.c b/backends/lua.c index 9a8091e..e7ba9f9 100644 --- a/backends/lua.c +++ b/backends/lua.c @@ -64,6 +64,7 @@ static uint32_t lua_interval(){ next_timer = timer[n].interval - timer[n].delta; } } + DBGPF("Next timer fires in %" PRIu32, next_timer); return next_timer; } return 1000; @@ -85,6 +86,7 @@ static int lua_update_timerfd(){ interval = timer[n].interval; } } + DBGPF("Recalculating timers, minimum is %" PRIu64, interval); //calculate gcd of all timers if any are active if(interval){ @@ -111,11 +113,13 @@ static int lua_update_timerfd(){ } if(interval == timer_interval){ + DBGPF("Keeping interval at %" PRIu64, interval); return 0; } #ifdef MMBACKEND_LUA_TIMERFD - //configure the new interval + //configure the new interval, 0.0 disarms the timer + DBGPF("Reconfiguring timerfd to %" PRIu64 ".%" PRIu64, timer_config.it_interval.tv_sec, timer_config.it_interval.tv_nsec); timerfd_settime(timer_fd, 0, &timer_config, NULL); #endif timer_interval = interval; -- cgit v1.2.3 From 9718e10c7f4151cea895f515c785c14e0021d967 Mon Sep 17 00:00:00 2001 From: cbdev Date: Wed, 18 Mar 2020 22:36:35 +0100 Subject: Implement default channel handlers for Lua/Python --- backends/lua.c | 17 +++++++++++++- backends/lua.h | 2 ++ backends/lua.md | 10 +++++---- backends/python.c | 65 +++++++++++++++++++++++++++++++++++++----------------- backends/python.h | 3 +++ backends/python.md | 8 ++++--- 6 files changed, 77 insertions(+), 28 deletions(-) (limited to 'backends/lua.c') diff --git a/backends/lua.c b/backends/lua.c index e7ba9f9..498a037 100644 --- a/backends/lua.c +++ b/backends/lua.c @@ -308,6 +308,11 @@ static int lua_configure_instance(instance* inst, char* option, char* value){ } return 0; } + else if(!strcmp(option, "default-handler")){ + free(data->default_handler); + data->default_handler = strdup(value); + return 0; + } LOGPF("Unknown instance configuration parameter %s for instance %s", option, inst->name); return 1; @@ -461,7 +466,8 @@ static int lua_start(size_t n, instance** inst){ data = (lua_instance_data*) inst[u]->impl; for(p = 0; p < data->channels; p++){ //exclude reserved names - if(strcmp(data->channel_name[p], "output") + if(!data->default_handler + && 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], "input_channel") @@ -473,6 +479,14 @@ static int lua_start(size_t n, instance** inst){ data->reference[p] = LUA_NOREF; } } + else if(data->default_handler){ + lua_getglobal(data->interpreter, data->default_handler); + data->reference[p] = luaL_ref(data->interpreter, LUA_REGISTRYINDEX); + if(data->reference[p] == LUA_REFNIL){ + data->reference[p] = LUA_NOREF; + LOGPF("Failed to resolve default handler function %s on instance %s", data->default_handler, inst[u]->name); + } + } } } @@ -504,6 +518,7 @@ static int lua_shutdown(size_t n, instance** inst){ free(data->reference); free(data->input); free(data->output); + free(data->default_handler); free(inst[u]->impl); } diff --git a/backends/lua.h b/backends/lua.h index ebe2046..743f978 100644 --- a/backends/lua.h +++ b/backends/lua.h @@ -29,6 +29,8 @@ typedef struct /*_lua_instance_data*/ { double* input; double* output; lua_State* interpreter; + + char* default_handler; } lua_instance_data; typedef struct /*_lua_interval_callback*/ { diff --git a/backends/lua.md b/backends/lua.md index db4cf39..96e53c8 100644 --- a/backends/lua.md +++ b/backends/lua.md @@ -6,7 +6,8 @@ and manipulate events using the Lua scripting language. Every instance has its own interpreter state which can be loaded with custom handler scripts. To process incoming channel events, the MIDIMonster calls corresponding Lua functions (if they exist) -with the value (as a Lua `number` type) as parameter. +with the value (as a Lua `number` type) as parameter. Alternatively, a designated default channel handler +may be supplied in the configuration. The following functions are provided within the Lua interpreter for interaction with the MIDIMonster @@ -42,9 +43,10 @@ The `lua` backend does not take any global configuration. #### Instance configuration -| Option | Example value | Default value | Description | -|---------------|-----------------------|-----------------------|-----------------------| -| `script` | `script.lua` | none | Lua source file (relative to configuration file)| +| Option | Example value | Default value | Description | +|-----------------------|-----------------------|-----------------------|-----------------------| +| `script` | `script.lua` | none | Lua source file (relative to configuration file) | +| `default-handler` | `handler` | none | Name of a function to be called as handler for all incoming channels (instead of the per-channel handlers) | A single instance may have multiple `script` options specified, which will all be read cumulatively. diff --git a/backends/python.c b/backends/python.c index 70c2548..735e838 100644 --- a/backends/python.c +++ b/backends/python.c @@ -378,6 +378,11 @@ static int python_configure_instance(instance* inst, char* option, char* value){ PyEval_ReleaseThread(data->interpreter); return 0; } + else if(!strcmp(option, "default-handler")){ + free(data->default_handler); + data->default_handler = strdup(value); + return 0; + } LOGPF("Unknown instance parameter %s for instance %s", option, inst->name); return 1; @@ -600,38 +605,56 @@ static int python_handle(size_t num, managed_fd* fds){ return 0; } +static PyObject* python_resolve_symbol(char* spec_raw){ + char* module_name = NULL, *object_name = NULL, *spec = strdup(spec_raw); + PyObject* module = NULL, *result = NULL; + + module = PyImport_AddModule("__main__"); + object_name = spec; + module_name = strchr(object_name, '.'); + if(module_name){ + *module_name = 0; + //returns borrowed reference + module = PyImport_AddModule(object_name); + + if(!module){ + LOGPF("Module %s for symbol %s.%s is not loaded", object_name, object_name, module_name + 1); + return NULL; + } + + object_name = module_name + 1; + + //returns new reference + result = PyObject_GetAttrString(module, object_name); + } + + free(spec); + return result; +} + static int python_start(size_t n, instance** inst){ python_instance_data* data = NULL; - PyObject* module = NULL; size_t u, p; - char* module_name = NULL, *channel_name = NULL; //resolve channel references to handler functions for(u = 0; u < n; u++){ data = (python_instance_data*) inst[u]->impl; + DBGPF("Starting up instance %s", inst[u]->name); //switch to interpreter PyEval_RestoreThread(data->interpreter); - for(p = 0; p < data->channels; p++){ - module = PyImport_AddModule("__main__"); - channel_name = data->channel[p].name; - module_name = strchr(channel_name, '.'); - if(module_name){ - *module_name = 0; - //returns borrowed reference - module = PyImport_AddModule(channel_name); - - if(!module){ - LOGPF("Module %s for qualified channel %s.%s is not loaded on instance %s", channel_name, channel_name, module_name + 1, inst[u]->name); - return 1; - } - *module_name = '.'; - channel_name = module_name + 1; - } + if(data->default_handler){ + data->handler = python_resolve_symbol(data->default_handler); + } - //returns new reference - data->channel[p].handler = PyObject_GetAttrString(module, channel_name); + for(p = 0; p < data->channels; p++){ + if(!strchr(data->channel[p].name, '.') && data->handler){ + data->channel[p].handler = data->handler; + } + else{ + data->channel[p].handler = python_resolve_symbol(data->channel[p].name); + } } //release interpreter @@ -654,6 +677,7 @@ static int python_shutdown(size_t n, instance** inst){ Py_XDECREF(data->channel[p].handler); } free(data->channel); + free(data->default_handler); //do not free data here, needed for shutting down interpreters } @@ -675,6 +699,7 @@ static int python_shutdown(size_t n, instance** inst){ for(p = 0; p handler); DBGPF("Shutting down interpreter for instance %s", inst[u]->name); //swap to interpreter and end it, GIL is held after this but state is NULL diff --git a/backends/python.h b/backends/python.h index 8ca12f9..a40098b 100644 --- a/backends/python.h +++ b/backends/python.h @@ -41,4 +41,7 @@ typedef struct /*_python_instance_data*/ { size_t channels; mmpython_channel* channel; mmpython_channel* current_channel; + + char* default_handler; + PyObject* handler; } python_instance_data; diff --git a/backends/python.md b/backends/python.md index f06e504..6852a79 100644 --- a/backends/python.md +++ b/backends/python.md @@ -6,6 +6,7 @@ to route, generate and manipulate channel events using the Python 3 scripting la Every instance has its own interpreter, which can be loaded with multiple Python modules. These modules may contain member functions accepting a single `float` parameter, which can then be used as target channels. For each incoming event, the handler function is called. +Channels in the global scope may be assigned a default handler function. Python modules may also register `socket` objects (and an associated callback function) with the MIDIMonster core, which will then alert the module when there is data ready to be read. @@ -67,9 +68,10 @@ The `python` backend does not take any global configuration. #### Instance configuration -| Option | Example value | Default value | Description | -|---------------|-----------------------|-----------------------|-----------------------------------------------| -| `module` | `my_handlers.py` | none | (Path to) Python module source file, relative to configuration file location | +| Option | Example value | Default value | Description | +|-----------------------|-----------------------|-----------------------|-----------------------------------------------| +| `module` | `my_handlers.py` | none | (Path to) Python module source file, relative to configuration file location | +| `default-handler` | `mu_handlers.default` | none | Function to be called as handler for all top-level channels (not belonging to a module) | A single instance may have multiple `module` options specified. This will make all handlers available within their module namespaces (see the section on channel specification). -- cgit v1.2.3 From 2d66d5cee9bf3ed5779f65d8a99b40ee5181bf30 Mon Sep 17 00:00:00 2001 From: cbdev Date: Fri, 20 Mar 2020 21:50:33 +0100 Subject: Implement Lua threading --- backends/lua.c | 114 +++++++++++++++++++++++++++++++++++++++++++++++++++++++- backends/lua.h | 7 ++++ backends/lua.md | 30 ++++++++++++--- 3 files changed, 144 insertions(+), 7 deletions(-) (limited to 'backends/lua.c') diff --git a/backends/lua.c b/backends/lua.c index 498a037..b66a27a 100644 --- a/backends/lua.c +++ b/backends/lua.c @@ -10,6 +10,7 @@ #define LUA_REGISTRY_KEY "_midimonster_lua_instance" #define LUA_REGISTRY_CURRENT_CHANNEL "_midimonster_lua_channel" +#define LUA_REGISTRY_CURRENT_THREAD "_midimonster_lua_thread" static size_t timers = 0; static lua_timer* timer = NULL; @@ -20,6 +21,9 @@ static int timer_fd = -1; static uint64_t last_timestamp; #endif +static size_t threads = 0; +static lua_thread* thread = NULL; + MM_PLUGIN_API int init(){ backend lua = { #ifndef MMBACKEND_LUA_TIMERFD @@ -86,6 +90,12 @@ static int lua_update_timerfd(){ interval = timer[n].interval; } } + + for(n = 0; n < threads; n++){ + if(thread[n].timeout && (!interval || thread[n].timeout < interval)){ + interval = thread[n].timeout; + } + } DBGPF("Recalculating timers, minimum is %" PRIu64, interval); //calculate gcd of all timers if any are active @@ -100,7 +110,8 @@ static int lua_update_timerfd(){ gcd = residual; } //since we round everything, 10 is the lowest interval we get - if(interval == 10){ + if(interval <= 10){ + interval = 10; break; } } @@ -126,6 +137,89 @@ static int lua_update_timerfd(){ return 0; } +static void lua_thread_resume(size_t current_thread){ + //push coroutine reference + lua_pushstring(thread[current_thread].thread, LUA_REGISTRY_CURRENT_THREAD); + lua_pushnumber(thread[current_thread].thread, current_thread); + lua_settable(thread[current_thread].thread, LUA_REGISTRYINDEX); + + //call thread main + DBGPF("Resuming thread %" PRIsize_t " on %s", current_thread, thread[current_thread].instance->name); + if(lua_resume(thread[current_thread].thread, NULL, 0) != LUA_YIELD){ + DBGPF("Thread %" PRIsize_t " on %s terminated", current_thread, thread[current_thread].instance->name); + thread[current_thread].timeout = 0; + } + + //remove coroutine reference + lua_pushstring(thread[current_thread].thread, LUA_REGISTRY_CURRENT_THREAD); + lua_pushnil(thread[current_thread].thread); + lua_settable(thread[current_thread].thread, LUA_REGISTRYINDEX); +} + +static int lua_callback_thread(lua_State* interpreter){ + instance* inst = NULL; + size_t u = threads; + if(lua_gettop(interpreter) != 1){ + LOGPF("Thread function called with %d arguments, expected function", lua_gettop(interpreter)); + return 0; + } + + luaL_checktype(interpreter, 1, LUA_TFUNCTION); + + //get instance pointer from registry + lua_pushstring(interpreter, LUA_REGISTRY_KEY); + lua_gettable(interpreter, LUA_REGISTRYINDEX); + inst = (instance*) lua_touserdata(interpreter, -1); + + //make space for a new thread + thread = realloc(thread, (threads + 1) * sizeof(lua_thread)); + if(!thread){ + threads = 0; + LOG("Failed to allocate memory"); + return 0; + } + threads++; + + thread[u].thread = lua_newthread(interpreter); + thread[u].instance = inst; + thread[u].timeout = 0; + thread[u].reference = luaL_ref(interpreter, LUA_REGISTRYINDEX); + + DBGPF("Registered thread %" PRIsize_t " on %s", threads, inst->name); + + //push thread main + luaL_checktype(interpreter, 1, LUA_TFUNCTION); + lua_pushvalue(interpreter, 1); + lua_xmove(interpreter, thread[u].thread, 1); + + lua_thread_resume(u); + lua_update_timerfd(); + return 0; +} + +static int lua_callback_sleep(lua_State* interpreter){ + uint64_t timeout = 0; + size_t current_thread = threads; + if(lua_gettop(interpreter) != 1){ + LOGPF("Sleep function called with %d arguments, expected number", lua_gettop(interpreter)); + return 0; + } + + timeout = luaL_checkinteger(interpreter, 1); + + lua_pushstring(interpreter, LUA_REGISTRY_CURRENT_THREAD); + lua_gettable(interpreter, LUA_REGISTRYINDEX); + + current_thread = luaL_checkinteger(interpreter, -1); + + if(current_thread < threads){ + DBGPF("Yielding for %" PRIu64 "msec on thread %" PRIsize_t, timeout, current_thread); + thread[current_thread].timeout = timeout; + lua_yield(interpreter, 0); + } + return 0; +} + static int lua_callback_output(lua_State* interpreter){ size_t n = 0; channel_value val; @@ -341,6 +435,8 @@ static int lua_instance(instance* inst){ lua_register(data->interpreter, "output_value", lua_callback_output_value); lua_register(data->interpreter, "input_channel", lua_callback_input_channel); lua_register(data->interpreter, "timestamp", lua_callback_timestamp); + lua_register(data->interpreter, "thread", lua_callback_thread); + lua_register(data->interpreter, "sleep", lua_callback_sleep); //store instance pointer to the lua state lua_pushstring(data->interpreter, LUA_REGISTRY_KEY); @@ -454,6 +550,17 @@ static int lua_handle(size_t num, managed_fd* fds){ } } } + + //check for threads to wake up + for(n = 0; n < threads; n++){ + if(thread[n].timeout && delta >= thread[n].timeout){ + lua_thread_resume(n); + lua_update_timerfd(); + } + else if(thread[n].timeout){ + thread[n].timeout -= delta; + } + } return 0; } @@ -468,6 +575,8 @@ static int lua_start(size_t n, instance** inst){ //exclude reserved names if(!data->default_handler && strcmp(data->channel_name[p], "output") + && strcmp(data->channel_name[p], "thread") + && strcmp(data->channel_name[p], "sleep") && strcmp(data->channel_name[p], "input_value") && strcmp(data->channel_name[p], "output_value") && strcmp(data->channel_name[p], "input_channel") @@ -526,6 +635,9 @@ static int lua_shutdown(size_t n, instance** inst){ free(timer); timer = NULL; timers = 0; + free(thread); + thread = NULL; + threads = 0; #ifdef MMBACKEND_LUA_TIMERFD close(timer_fd); timer_fd = -1; diff --git a/backends/lua.h b/backends/lua.h index 743f978..d8e720a 100644 --- a/backends/lua.h +++ b/backends/lua.h @@ -39,3 +39,10 @@ typedef struct /*_lua_interval_callback*/ { lua_State* interpreter; int reference; } lua_timer; + +typedef struct /*_lua_coroutine*/ { + instance* instance; + lua_State* thread; + int reference; + uint64_t timeout; +} lua_thread; diff --git a/backends/lua.md b/backends/lua.md index 96e53c8..05509b6 100644 --- a/backends/lua.md +++ b/backends/lua.md @@ -19,24 +19,41 @@ The following functions are provided within the Lua interpreter for interaction | `output_value(string)` | `output_value("bar")` | Get the last output value on a channel | | `input_channel()` | `print(input_channel())` | Returns the name of the input channel whose handler function is currently running or `nil` if in an `interval`'ed function (or the initial parse step) | | `timestamp()` | `print(timestamp())` | Returns the core timestamp for this iteration with millisecond resolution. This is not a performance timer, but intended for timeouting, etc | +| `thread(function)` | `thread(run_show)` | Run a function as a Lua thread (see below) | +| `sleep(number)` | `sleep(100)` | Suspend current thread for time specified in milliseconds | Example script: -``` +```lua function bar(value) - output("foo", value / 2) + output("foo", value / 2); end step = 0 function toggle() - output("bar", step * 1.0) + output("bar", step * 1.0); step = (step + 1) % 2; end +function run_show() + while(true) do + sleep(1000); + output("narf", 0); + sleep(1000); + output("narf", 1.0); + end +end + interval(toggle, 1000) +thread(run_show) ``` Input values range between 0.0 and 1.0, output values are clamped to the same range. +Threads are implemented as Lua coroutines, not operating system threads. This means that +cooperative multithreading is required, which can be achieved by calling the `sleep(number)` +function from within a running thread. Calling that function from any other context is +not supported. + #### Global configuration The `lua` backend does not take any global configuration. @@ -61,9 +78,10 @@ lua1.foo > lua2.bar #### Known bugs / problems -Using any of the interface functions (`output`, `interval`, `input_value`, `output_value`, `input_channel`, -`timestamp`) 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. +Using any of the interface functions (`output`, `interval`, etc.) 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. When using a default handler, the default handler will +be called. Output values will not trigger corresponding input event handlers unless the channel is mapped back in the MIDIMonster configuration. This is intentional. -- cgit v1.2.3 From 03a75ea3b4e72f09843bc8b12f8bac84a2f27709 Mon Sep 17 00:00:00 2001 From: cbdev Date: Fri, 20 Mar 2020 23:35:53 +0100 Subject: Restructure lua channel resolution --- backends/lua.c | 105 +++++++++++++++++++++++++++++------------------------- backends/lua.h | 14 +++++--- backends/python.c | 3 +- 3 files changed, 68 insertions(+), 54 deletions(-) (limited to 'backends/lua.c') diff --git a/backends/lua.c b/backends/lua.c index b66a27a..b71ca97 100644 --- a/backends/lua.c +++ b/backends/lua.c @@ -245,13 +245,13 @@ static int lua_callback_output(lua_State* interpreter){ //find correct channel & output value for(n = 0; n < data->channels; n++){ - if(!strcmp(channel_name, data->channel_name[n])){ + if(!strcmp(channel_name, data->channel[n].name)){ channel = mm_channel(inst, n, 0); if(!channel){ return 0; } mm_channel_event(channel, val); - data->output[n] = val.normalised; + data->channel[n].out = val.normalised; return 0; } } @@ -357,8 +357,8 @@ static int lua_callback_value(lua_State* interpreter, uint8_t input){ //find correct channel & return value for(n = 0; n < data->channels; n++){ - if(!strcmp(channel_name, data->channel_name[n])){ - lua_pushnumber(interpreter, (input) ? data->input[n] : data->output[n]); + if(!strcmp(channel_name, data->channel[n].name)){ + lua_pushnumber(interpreter, (input) ? data->channel[n].in : data->channel[n].out); return 1; } } @@ -453,26 +453,23 @@ static channel* lua_channel(instance* inst, char* spec, uint8_t flags){ //find matching channel for(u = 0; u < data->channels; u++){ - if(!strcmp(spec, data->channel_name[u])){ + if(!strcmp(spec, data->channel[u].name)){ break; } } //allocate new channel if(u == data->channels){ - data->channel_name = realloc(data->channel_name, (u + 1) * sizeof(char*)); - 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){ + data->channel = realloc(data->channel, (data->channels + 1) * sizeof(lua_channel_data)); + if(!data->channel){ LOG("Failed to allocate memory"); + data->channels = 0; 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]){ + data->channel[u].in = data->channel[u].out = 0.0; + data->channel[u].name = strdup(spec); + if(!data->channel[u].name){ LOG("Failed to allocate memory"); return NULL; } @@ -483,23 +480,24 @@ static channel* lua_channel(instance* inst, char* spec, uint8_t flags){ } static int lua_set(instance* inst, size_t num, channel** c, channel_value* v){ - size_t n = 0; + size_t n = 0, ident; lua_instance_data* data = (lua_instance_data*) inst->impl; //handle all incoming events for(n = 0; n < num; n++){ - data->input[c[n]->ident] = v[n].normalised; + ident = c[n]->ident; + data->channel[ident].in = v[n].normalised; //call lua channel handlers if present - if(data->reference[c[n]->ident] != LUA_NOREF){ + if(data->channel[ident].reference != LUA_NOREF){ //push the channel name lua_pushstring(data->interpreter, LUA_REGISTRY_CURRENT_CHANNEL); - lua_pushstring(data->interpreter, data->channel_name[c[n]->ident]); + lua_pushstring(data->interpreter, data->channel[ident].name); lua_settable(data->interpreter, LUA_REGISTRYINDEX); - lua_rawgeti(data->interpreter, LUA_REGISTRYINDEX, data->reference[c[n]->ident]); + lua_rawgeti(data->interpreter, LUA_REGISTRYINDEX, data->channel[ident].reference); lua_pushnumber(data->interpreter, v[n].normalised); if(lua_pcall(data->interpreter, 1, 0, 0) != LUA_OK){ - LOGPF("Failed to call handler for %s.%s: %s", inst->name, data->channel_name[c[n]->ident], lua_tostring(data->interpreter, -1)); + LOGPF("Failed to call handler for %s.%s: %s", inst->name, data->channel[ident].name, lua_tostring(data->interpreter, -1)); lua_pop(data->interpreter, 1); } } @@ -564,37 +562,51 @@ static int lua_handle(size_t num, managed_fd* fds){ return 0; } +static int lua_resolve_symbol(lua_State* interpreter, char* symbol){ + int reference = LUA_REFNIL; + + //exclude reserved names + if(!strcmp(symbol, "output") + || !strcmp(symbol, "thread") + || !strcmp(symbol, "sleep") + || !strcmp(symbol, "input_value") + || !strcmp(symbol, "output_value") + || !strcmp(symbol, "input_channel") + || !strcmp(symbol, "timestamp") + || !strcmp(symbol, "interval")){ + return LUA_NOREF; + } + + lua_getglobal(interpreter, symbol); + reference = luaL_ref(interpreter, LUA_REGISTRYINDEX); + if(reference == LUA_REFNIL){ + return LUA_NOREF; + } + return reference; +} + static int lua_start(size_t n, instance** inst){ size_t u, p; lua_instance_data* data = NULL; + int default_handler; //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(!data->default_handler - && strcmp(data->channel_name[p], "output") - && strcmp(data->channel_name[p], "thread") - && strcmp(data->channel_name[p], "sleep") - && strcmp(data->channel_name[p], "input_value") - && strcmp(data->channel_name[p], "output_value") - && strcmp(data->channel_name[p], "input_channel") - && strcmp(data->channel_name[p], "timestamp") - && 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; - } + default_handler = LUA_NOREF; + + //try to resolve default handler if given + if(data->default_handler){ + default_handler = lua_resolve_symbol(data->interpreter, data->default_handler); + if(default_handler == LUA_NOREF){ + LOGPF("Failed to resolve default handler %s on %s", data->default_handler, inst[u]->name); } - else if(data->default_handler){ - lua_getglobal(data->interpreter, data->default_handler); - data->reference[p] = luaL_ref(data->interpreter, LUA_REGISTRYINDEX); - if(data->reference[p] == LUA_REFNIL){ - data->reference[p] = LUA_NOREF; - LOGPF("Failed to resolve default handler function %s on instance %s", data->default_handler, inst[u]->name); - } + } + + for(p = 0; p < data->channels; p++){ + data->channel[p].reference = default_handler; + if(!data->default_handler){ + data->channel[p].reference = lua_resolve_symbol(data->interpreter, data->channel[p].name); } } } @@ -621,12 +633,9 @@ static int lua_shutdown(size_t n, instance** inst){ lua_close(data->interpreter); //cleanup channel data for(p = 0; p < data->channels; p++){ - free(data->channel_name[p]); + free(data->channel[p].name); } - free(data->channel_name); - free(data->reference); - free(data->input); - free(data->output); + free(data->channel); free(data->default_handler); free(inst[u]->impl); } diff --git a/backends/lua.h b/backends/lua.h index d8e720a..3c7568b 100644 --- a/backends/lua.h +++ b/backends/lua.h @@ -22,14 +22,18 @@ static int lua_shutdown(size_t n, instance** inst); static uint32_t lua_interval(); #endif +typedef struct /*_lua_channel*/ { + char* name; + int reference; + double in; + double out; +} lua_channel_data; + typedef struct /*_lua_instance_data*/ { size_t channels; - char** channel_name; - int* reference; - double* input; - double* output; - lua_State* interpreter; + lua_channel_data* channel; + lua_State* interpreter; char* default_handler; } lua_instance_data; diff --git a/backends/python.c b/backends/python.c index 735e838..c658f30 100644 --- a/backends/python.c +++ b/backends/python.c @@ -78,7 +78,8 @@ static void python_timer_recalculate(){ } //10msec is absolute lower limit and minimum gcd due to rounding - if(next_interval == 10){ + if(next_interval <= 10){ + next_interval = 10; break; } } -- cgit v1.2.3 From d1baab6ce4b13c562867147ed60906dfe651ae81 Mon Sep 17 00:00:00 2001 From: cbdev Date: Sat, 21 Mar 2020 19:00:28 +0100 Subject: Enable setting channel values at load time for lua --- backends/lua.c | 29 +++++++++++++++++++---------- backends/lua.h | 1 + 2 files changed, 20 insertions(+), 10 deletions(-) (limited to 'backends/lua.c') diff --git a/backends/lua.c b/backends/lua.c index b71ca97..7f80cc7 100644 --- a/backends/lua.c +++ b/backends/lua.c @@ -17,9 +17,8 @@ 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 +static uint64_t last_timestamp = 0; static size_t threads = 0; static lua_thread* thread = NULL; @@ -224,7 +223,6 @@ static int lua_callback_output(lua_State* interpreter){ size_t n = 0; channel_value val; const char* channel_name = NULL; - channel* channel = NULL; instance* inst = NULL; lua_instance_data* data = NULL; @@ -243,15 +241,21 @@ static int lua_callback_output(lua_State* interpreter){ channel_name = lua_tostring(interpreter, 1); val.normalised = clamp(luaL_checknumber(interpreter, 2), 1.0, 0.0); + //if not started yet, create any requested channels so scripts may set them at load time + if(!last_timestamp && channel_name){ + lua_channel(inst, (char*) channel_name, mmchannel_output); + } + //find correct channel & output value for(n = 0; n < data->channels; n++){ if(!strcmp(channel_name, data->channel[n].name)){ - channel = mm_channel(inst, n, 0); - if(!channel){ - return 0; - } - mm_channel_event(channel, val); data->channel[n].out = val.normalised; + if(!last_timestamp){ + data->channel[n].mark = 1; + } + else{ + mm_channel_event(mm_channel(inst, n, 0), val); + } return 0; } } @@ -589,6 +593,7 @@ static int lua_start(size_t n, instance** inst){ size_t u, p; lua_instance_data* data = NULL; int default_handler; + channel_value v; //resolve channels to their handler functions for(u = 0; u < n; u++){ @@ -608,6 +613,11 @@ static int lua_start(size_t n, instance** inst){ if(!data->default_handler){ data->channel[p].reference = lua_resolve_symbol(data->interpreter, data->channel[p].name); } + //push initial values + if(data->channel[p].mark){ + v.normalised = data->channel[p].out; + mm_channel_event(mm_channel(inst[u], p, 0), v); + } } } @@ -617,9 +627,8 @@ static int lua_start(size_t n, instance** inst){ if(mm_manage_fd(timer_fd, BACKEND_NAME, 1, NULL)){ return 1; } - #else - last_timestamp = mm_timestamp(); #endif + last_timestamp = mm_timestamp(); return 0; } diff --git a/backends/lua.h b/backends/lua.h index 3c7568b..4583dfe 100644 --- a/backends/lua.h +++ b/backends/lua.h @@ -27,6 +27,7 @@ typedef struct /*_lua_channel*/ { int reference; double in; double out; + uint8_t mark; } lua_channel_data; typedef struct /*_lua_instance_data*/ { -- cgit v1.2.3