Last month I did a quick proof of concept for a Lua module:
You can see the source code of the echo example here. This was just a quick hack, I want to discuss now how it should look like and what features do you want.
Some ideas what I think should be possible with this module:
- custom module size and SVG
- add GUI elements with the script: inputs, outputs, knobs, switches, push buttons, LEDs, labels etc.
- process realtime audio, same as Module::process() in C++
- visual updates, same as ModuleWidget::step() in C++
- implementing your own widgets for drawing anything you like, for example as you can in the ScopeDisplay of Fundamental
Ideas for the API (unlike the VCV Rack API, the Lua script API should be object oriented and easy to use, even if it means it doesn’t run at maximum speed) :
One global Rack class as the interface to the Rack program, with the following functions:
- Rack.setProcess(function): sets the process callback function, which is called at audio rate
- Rack.setStep(function): sets the step callback function, which is called at video frame update rate
- Rack.addLabel(x, y, text, size, [color]): adds a label
- Rack.addInput(x, y): adds an input jack, returns an Input object. The Input object has the method getVoltage, with an optional parameter for the polyphonic input number.
- Rack.addOutput(x, y): adds an output jack, returns an Output object. The Output object has the method setVoltage, with an optional parameter for the polyphonic output number.
- Rack.addKnob(x, y, min, max, default): adds a knob, returns a Knob object. The Knob object has the method getValue(), which returns the current value of the knob.
So a simple sum script would look like this:
-- create the GUI
Rack.addLabel(50, 133, "-> Sum ->", 20)
inp1 = addInput(20, 120)
inp2 = addInput(20, 140)
out = addOutput(170, 130)
-- process function
function process()
out:setVoltage(inp1:getVoltage() + inp2:getVoltage())
Rack.setProcess(process)
Note: In Lua the “:” syntax means it implicitly passes the “self” object to the method, so inp1.getVoltage(inp1) is the same as inp1:getVoltage(), but nicer to write and read. Rack has only one object, which means no need to pass a “self” object, think of it as static member functions if you know C++.
To organize the scripts I think it would be a good idea to have a directory for each script, in the script directory, within the Lua plugin directory. I think it would be good to have 3 files at least: module.lua, module.svg and module.json. All module.json files are scanned at start and provide a human readable name of the script module. A right click context menu allows you to select one of the scanned modules. When a module is loaded, the module.svg is displayed, the module is sized accordingly, and the module.lua script is executed. This means multiple independent Lua script module instances can be placed in Rack, with the same or different scripts running.
Don’t know how far this should go with automatic updates etc., but the module.json file could have a version tag as well and there could be a library page somewhere on the web to load Lua modules of other users, or later maybe even later to the VCV main homepage. I think to make it easier it is good to have one script directory for one module, no plugin hierarchy above it.
I think it would be nice if it reloads module.lua automatically when changed. This would make the development process very fast, good for quick experiments that don’t need max performance.