aboutsummaryrefslogtreecommitdiffhomepage
path: root/backends
diff options
context:
space:
mode:
Diffstat (limited to 'backends')
-rw-r--r--backends/Makefile4
-rw-r--r--backends/lua.c202
-rw-r--r--backends/lua.h21
-rw-r--r--backends/lua.md49
4 files changed, 275 insertions, 1 deletions
diff --git a/backends/Makefile b/backends/Makefile
index c11de56..fe88669 100644
--- a/backends/Makefile
+++ b/backends/Makefile
@@ -1,7 +1,7 @@
.PHONY: all clean full
OPTIONAL_BACKENDS = ola.so
LINUX_BACKENDS = midi.so evdev.so
-BACKENDS = artnet.so osc.so loopback.so sacn.so
+BACKENDS = artnet.so osc.so loopback.so sacn.so lua.so
BACKEND_LIB = libmmbackend.o
SYSTEM := $(shell uname -s)
@@ -27,6 +27,8 @@ evdev.so: CFLAGS += $(shell pkg-config --cflags libevdev)
evdev.so: LDLIBS = $(shell pkg-config --libs libevdev)
ola.so: LDLIBS = -lola
ola.so: CPPFLAGS += -Wno-write-strings
+lua.so: CFLAGS += $(shell pkg-config --cflags lua5.3)
+lua.so: LDLIBS += $(shell pkg-config --libs lua5.3)
%.so :: %.c %.h $(BACKEND_LIB)
$(CC) $(CFLAGS) $(LDLIBS) $< $(ADDITIONAL_OBJS) -o $@ $(LDFLAGS)
diff --git a/backends/lua.c b/backends/lua.c
new file mode 100644
index 0000000..ae2d460
--- /dev/null
+++ b/backends/lua.c
@@ -0,0 +1,202 @@
+#include <string.h>
+#include "lua.h"
+
+#define BACKEND_NAME "lua"
+#define LUA_REGISTRY_KEY "_midimonster_lua_instance"
+
+//TODO instance identification for callvacks
+
+int init(){
+ backend lua = {
+ .name = BACKEND_NAME,
+ .conf = lua_configure,
+ .create = lua_instance,
+ .conf_instance = lua_configure_instance,
+ .channel = lua_channel,
+ .handle = lua_set,
+ .process = lua_handle,
+ .start = lua_start,
+ .shutdown = lua_shutdown
+ };
+
+ //register backend
+ if(mm_backend_register(lua)){
+ fprintf(stderr, "Failed to register lua backend\n");
+ return 1;
+ }
+ return 0;
+}
+
+static int lua_callback_output(lua_State* interpreter){
+ int arguments = lua_gettop(interpreter);
+ size_t n;
+ channel_value val;
+ const char* channel_name = NULL;
+ channel* channel = NULL;
+ instance* inst = NULL;
+ lua_instance_data* data = NULL;
+
+ if(arguments != 2){
+ fprintf(stderr, "Lua output function called with %d arguments, expected 2\n", arguments);
+ return 0;
+ }
+
+ channel_name = lua_tostring(interpreter, 1);
+ val.normalised = clamp(lua_tonumber(interpreter, 2), 1.0, 0.0);
+
+ lua_pushstring(interpreter, LUA_REGISTRY_KEY);
+ lua_gettable(interpreter, LUA_REGISTRYINDEX);
+ inst = (instance *) lua_touserdata(interpreter, -1);
+ data = (lua_instance_data*) inst->impl;
+
+ for(n = 0; n < data->channels; n++){
+ if(!strcmp(channel_name, data->channel_name[n])){
+ channel = mm_channel(inst, n, 0);
+ if(!channel){
+ return 0;
+ }
+ mm_channel_event(channel, val);
+ return 0;
+ }
+ }
+
+ fprintf(stderr, "Tried to set unknown channel %s.%s\n", inst->name, channel_name);
+ return 0;
+}
+
+static int lua_configure(char* option, char* value){
+ fprintf(stderr, "The lua backend does not take any global configuration\n");
+ return 1;
+}
+
+static int lua_configure_instance(instance* inst, char* option, char* value){
+ lua_instance_data* data = (lua_instance_data*) inst->impl;
+
+ if(!strcmp(option, "script")){
+ if(luaL_dofile(data->interpreter, value)){
+ fprintf(stderr, "Failed to load lua source file %s for instance %s: %s\n", value, inst->name, lua_tostring(data->interpreter, -1));
+ return 1;
+ }
+ return 0;
+ }
+
+ fprintf(stderr, "Unknown configuration parameter %s for lua backend\n", option);
+ return 1;
+}
+
+static instance* lua_instance(){
+ instance* inst = mm_instance();
+ if(!inst){
+ return NULL;
+ }
+
+ lua_instance_data* data = calloc(1, sizeof(lua_instance_data));
+ if(!data){
+ fprintf(stderr, "Failed to allocate memory\n");
+ return NULL;
+ }
+
+ //load the interpreter
+ data->interpreter = luaL_newstate();
+ if(!data->interpreter){
+ fprintf(stderr, "Failed to initialize LUA\n");
+ free(data);
+ return NULL;
+ }
+ luaL_openlibs(data->interpreter);
+
+ //register lua api functions
+ lua_register(data->interpreter, "output", lua_callback_output);
+
+ //store instance pointer to the lua state
+ lua_pushstring(data->interpreter, LUA_REGISTRY_KEY);
+ lua_pushlightuserdata(data->interpreter, (void *) inst);
+ lua_settable(data->interpreter, LUA_REGISTRYINDEX);
+
+ inst->impl = data;
+ return inst;
+}
+
+static channel* lua_channel(instance* inst, char* spec){
+ size_t u;
+ lua_instance_data* data = (lua_instance_data*) inst->impl;
+
+ //find matching channel
+ for(u = 0; u < data->channels; u++){
+ if(!strcmp(spec, data->channel_name[u])){
+ break;
+ }
+ }
+
+ //allocate new channel
+ if(u == data->channels){
+ data->channel_name = realloc(data->channel_name, (u + 1) * sizeof(char*));
+ if(!data->channel_name){
+ fprintf(stderr, "Failed to allocate memory\n");
+ return NULL;
+ }
+
+ data->channel_name[u] = strdup(spec);
+ if(!data->channel_name[u]){
+ fprintf(stderr, "Failed to allocate memory\n");
+ return NULL;
+ }
+ data->channels++;
+ }
+
+ return mm_channel(inst, u, 1);
+}
+
+static int lua_set(instance* inst, size_t num, channel** c, channel_value* v){
+ size_t n = 0;
+ lua_instance_data* data = (lua_instance_data*) inst->impl;
+
+ for(n = 0; n < num; n++){
+ //call lua channel handlers
+ lua_getglobal(data->interpreter, data->channel_name[c[n]->ident]);
+ lua_pushnumber(data->interpreter, v[n].normalised);
+ if(lua_pcall(data->interpreter, 1, 0, 0) != LUA_OK){
+ fprintf(stderr, "Failed to call handler for %s.%s: %s\n", inst->name, data->channel_name[c[n]->ident], lua_tostring(data->interpreter, -1));
+ lua_pop(data->interpreter, 1);
+ }
+ }
+ return 0;
+}
+
+static int lua_handle(size_t num, managed_fd* fds){
+ //TODO call timer callbacks
+ return 0;
+}
+
+static int lua_start(){
+ //TODO start timers / register fds
+ return 0;
+}
+
+static int lua_shutdown(){
+ size_t n, u, p;
+ instance** inst = NULL;
+ lua_instance_data* data = NULL;
+
+ if(mm_backend_instances(BACKEND_NAME, &n, &inst)){
+ fprintf(stderr, "Failed to fetch instance list\n");
+ return 1;
+ }
+
+ for(u = 0; u < n; u++){
+ data = (lua_instance_data*) inst[u]->impl;
+ //stop the interpreter
+ lua_close(data->interpreter);
+ //cleanup channel data
+ for(p = 0; p < data->channels; p++){
+ free(data->channel_name[p]);
+ }
+ free(data->channel_name);
+ free(inst[u]->impl);
+ }
+
+ free(inst);
+
+ fprintf(stderr, "Lua backend shut down\n");
+ return 0;
+}
diff --git a/backends/lua.h b/backends/lua.h
new file mode 100644
index 0000000..27e1afd
--- /dev/null
+++ b/backends/lua.h
@@ -0,0 +1,21 @@
+#include "midimonster.h"
+
+#include <lua.h>
+#include <lualib.h>
+#include <lauxlib.h>
+
+int init();
+static int lua_configure(char* option, char* value);
+static int lua_configure_instance(instance* inst, char* option, char* value);
+static instance* lua_instance();
+static channel* lua_channel(instance* inst, char* spec);
+static int lua_set(instance* inst, size_t num, channel** c, channel_value* v);
+static int lua_handle(size_t num, managed_fd* fds);
+static int lua_start();
+static int lua_shutdown();
+
+typedef struct /*_lua_instance_data*/ {
+ size_t channels;
+ char** channel_name;
+ lua_State* interpreter;
+} lua_instance_data;
diff --git a/backends/lua.md b/backends/lua.md
new file mode 100644
index 0000000..91e8fe2
--- /dev/null
+++ b/backends/lua.md
@@ -0,0 +1,49 @@
+### The `lua` backend
+
+The `lua` backend provides a flexible programming environment, allowing users to route and manipulate
+events using the Lua programming language.
+
+Every instance has it's own interpreter state which can be loaded with custom handler scripts.
+
+To process incoming channel events, the MIDIMonster calls corresponding Lua functions with
+the value (as a Lua `number` type) as parameter. To send output on a channel, the Lua environment
+provides the function `output(channel-name, value)`.
+
+Example script:
+```
+function bar(value)
+ output("foo", value / 2)
+end
+```
+
+Input values range between 0.0 and 1.0, output values are clamped to the same range.
+
+#### Global configuration
+
+The backend does not take any global configuration.
+
+#### Instance configuration
+
+| Option | Example value | Default value | Description |
+|---------------|-----------------------|-----------------------|-----------------------|
+| `source` | `script.lua` | none | Lua source file |
+
+A single instance may have multiple `source` options specified, which will all be read cumulatively.
+
+#### Channel specification
+
+Channel names may be any valid Lua function name.
+
+Example mapping:
+```
+lua1.foo > lua2.bar
+```
+
+#### Known bugs / problems
+
+Using `output` as an input channel name to a Lua instance does not work, as the interpreter has
+`output` globally assigned to the event output function. Using `output` as an output channel name
+via `output("output", value)` works as intended.
+
+The path to the Lua source files is relative to the current working directory. This may lead
+to problems when copying configuration between installations.