| @@ -31,7 +31,7 @@ $(efsw): | |||
| cd efsw && cp lib/libefsw-static-release.a $(DEP_PATH)/lib/ | |||
| cd efsw && cp -R include/efsw $(DEP_PATH)/include/ | |||
| # LibPD | |||
| # libpd | |||
| ifeq ($(LIBPD), 1) | |||
| libpd := dep/lib/libpd.a | |||
| SOURCES += src/LibPDEngine.cpp | |||
| @@ -206,5 +206,16 @@ endif | |||
| # cd dep/llvm-8.0.1.src/build && $(MAKE) | |||
| # cd dep/llvm-8.0.1.src/build && $(MAKE) install | |||
| # Vult | |||
| ifeq ($(VULT), 1) | |||
| SOURCES += src/VultEngine.cpp | |||
| vult := dep/vult/vultc.h | |||
| $(vult): | |||
| cd dep && mkdir -p vult | |||
| cd dep/vult && $(WGET) "https://github.com/modlfo/vult/releases/download/v0.4.9/vultc.h" | |||
| $(SHA256) $(vult) 91f575afd2913d0879df90ee666021065ad726372f0bd306198024dc771cce55 | |||
| FLAGS += -Idep/vult | |||
| DEPS += $(vult) | |||
| endif | |||
| include $(RACK_DIR)/plugin.mk | |||
| @@ -8,7 +8,10 @@ Scripting language host for [VCV Rack](https://vcvrack.com/) containing: | |||
| - 6 switches with RGB LEDs | |||
| Supported scripting languages: | |||
| - JavaScript (.js) | |||
| - JavaScript (ES2020) (.js) | |||
| - [Lua](https://www.lua.org/) (.lua) | |||
| - [Vult](https://github.com/modlfo/vult) (.vult) | |||
| - [Pure Data](https://puredata.info) (.pd) | |||
| - [Add your own below](#adding-a-script-engine) | |||
| [Discussion thread](https://community.vcvrack.com/t/vcv-prototype/3271) | |||
| @@ -89,6 +92,9 @@ function process(block) { | |||
| } | |||
| ``` | |||
| *The Vult API is slightly different than Prototype's scripting API. | |||
| See `examples/template.vult` for a reference of the Vult API.* | |||
| ## Build dependencies | |||
| ### Windows | |||
| @@ -111,6 +117,23 @@ sudo apt install premake4 | |||
| sudo pacman -S premake | |||
| ``` | |||
| ## Build | |||
| ### Add path to Rack-SDK | |||
| ```bash | |||
| export RACK_DIR=/set/path/to/Rack-SDK/ | |||
| ``` | |||
| ### load submodules | |||
| ```bash | |||
| git submodule update --init --recursive | |||
| ``` | |||
| ### Make | |||
| ```bash | |||
| make dep | |||
| make | |||
| ``` | |||
| ## Adding a script engine | |||
| - Add your scripting language library to the build system so it builds with `make dep`, following the Duktape example in `Makefile`. | |||
| @@ -123,6 +146,8 @@ sudo pacman -S premake | |||
| ## Contributors | |||
| - [Wes Milholen](https://grayscale.info/): panel design | |||
| - [Andrew Belt](https://github.com/AndrewBelt): host code, Duktape (JavaScript, disabled), LuaJIT (Lua), Python (in development) | |||
| - [Andrew Belt](https://github.com/AndrewBelt): host code, Duktape (JavaScript, not used), LuaJIT (Lua), Python (in development) | |||
| - [Jerry Sievert](https://github.com/JerrySievert): QuickJS (JavaScript) | |||
| - [Leonardo Laguna Ruiz](https://github.com/modlfo): Vult | |||
| - [CHAIR](https://chair.audio) [Clemens Wegener (libpd), Max Neupert (patches)] : libpd | |||
| - add your name here | |||
| @@ -1,4 +1,8 @@ | |||
| <<<<<<< HEAD | |||
| #N canvas 547 398 828 731 12; | |||
| ======= | |||
| #N canvas 698 144 821 731 12; | |||
| >>>>>>> 2d8748675208b3c97908663eb33499dbf4213d11 | |||
| #N canvas 250 355 563 749 hsv2rgb 0; | |||
| #X obj 20 64 route 0; | |||
| #X msg 20 36 \$2 \$3 \$1; | |||
| @@ -912,8 +916,11 @@ | |||
| #X msg 317 400 S4 \$1 \$2 \$3; | |||
| #X msg 413 400 S5 \$1 \$2 \$3; | |||
| #X msg 509 400 S6 \$1 \$2 \$3; | |||
| <<<<<<< HEAD | |||
| #X obj 294 17 loadbang; | |||
| #X obj 294 71 print toVCV; | |||
| ======= | |||
| >>>>>>> 2d8748675208b3c97908663eb33499dbf4213d11 | |||
| #X obj 30 92 metro 2000; | |||
| #X msg 62 426 L1 \$1 \$2 \$3; | |||
| #X msg 158 426 L2 \$1 \$2 \$3; | |||
| @@ -922,7 +929,10 @@ | |||
| #X msg 447 426 L5 \$1 \$2 \$3; | |||
| #X msg 542 426 L6 \$1 \$2 \$3; | |||
| #X obj 29 592 dac~ 1 2 3 4 5 6, f 69; | |||
| <<<<<<< HEAD | |||
| #X msg 294 43 display Hello world!; | |||
| ======= | |||
| >>>>>>> 2d8748675208b3c97908663eb33499dbf4213d11 | |||
| #X obj 29 369 t l l; | |||
| #X obj 125 369 t l l; | |||
| #X obj 221 369 t l l; | |||
| @@ -930,7 +940,10 @@ | |||
| #X obj 413 369 t l l; | |||
| #X obj 509 369 t l l; | |||
| #X msg 125 316 \$1 1 1; | |||
| <<<<<<< HEAD | |||
| #X msg 30 118 0 \, 100 2000; | |||
| ======= | |||
| >>>>>>> 2d8748675208b3c97908663eb33499dbf4213d11 | |||
| #X obj 125 201 + 16.6667; | |||
| #X obj 125 227 % 100; | |||
| #X obj 29 249 / 100; | |||
| @@ -976,11 +989,34 @@ | |||
| #X obj 316 559 +~ 1; | |||
| #X obj 413 560 +~ 1; | |||
| #X obj 508 560 +~ 1; | |||
| <<<<<<< HEAD | |||
| #X connect 0 0 29 0; | |||
| ======= | |||
| #N canvas 0 68 624 300 display 0; | |||
| #X obj 29 10 loadbang; | |||
| #X msg 29 34 44; | |||
| #X obj 29 140 print toVCV; | |||
| #X obj 29 90 list prepend display; | |||
| #X obj 29 115 list trim; | |||
| #X obj 29 62 makefilename Hello%c\ world!; | |||
| #X text 264 22 A somewhat ugly workaround just to print a comma. In | |||
| Pd \, commas separate messages., f 32; | |||
| #X connect 0 0 1 0; | |||
| #X connect 1 0 5 0; | |||
| #X connect 3 0 4 0; | |||
| #X connect 4 0 2 0; | |||
| #X connect 5 0 3 0; | |||
| #X restore 229 19 pd display; | |||
| #X text 608 326 This should be an abstraction \, but to keep the example | |||
| directory tidy it here is duplicated code., f 28; | |||
| #X msg 30 118 100 \, 0 2000; | |||
| #X connect 0 0 26 0; | |||
| >>>>>>> 2d8748675208b3c97908663eb33499dbf4213d11 | |||
| #X connect 2 0 1 0; | |||
| #X connect 3 0 4 0; | |||
| #X connect 4 0 6 0; | |||
| #X connect 5 0 0 0; | |||
| <<<<<<< HEAD | |||
| #X connect 6 0 20 0; | |||
| #X connect 7 0 56 0; | |||
| #X connect 8 0 30 0; | |||
| @@ -988,17 +1024,33 @@ | |||
| #X connect 10 0 32 0; | |||
| #X connect 11 0 33 0; | |||
| #X connect 12 0 34 0; | |||
| ======= | |||
| #X connect 6 0 18 0; | |||
| #X connect 7 0 52 0; | |||
| #X connect 8 0 27 0; | |||
| #X connect 9 0 28 0; | |||
| #X connect 10 0 29 0; | |||
| #X connect 11 0 30 0; | |||
| #X connect 12 0 31 0; | |||
| >>>>>>> 2d8748675208b3c97908663eb33499dbf4213d11 | |||
| #X connect 13 0 1 0; | |||
| #X connect 14 0 1 0; | |||
| #X connect 15 0 1 0; | |||
| #X connect 16 0 1 0; | |||
| #X connect 17 0 1 0; | |||
| <<<<<<< HEAD | |||
| #X connect 18 0 28 0; | |||
| #X connect 20 0 36 0; | |||
| ======= | |||
| #X connect 18 0 80 0; | |||
| #X connect 19 0 1 0; | |||
| #X connect 20 0 1 0; | |||
| >>>>>>> 2d8748675208b3c97908663eb33499dbf4213d11 | |||
| #X connect 21 0 1 0; | |||
| #X connect 22 0 1 0; | |||
| #X connect 23 0 1 0; | |||
| #X connect 24 0 1 0; | |||
| <<<<<<< HEAD | |||
| #X connect 25 0 1 0; | |||
| #X connect 26 0 1 0; | |||
| #X connect 28 0 19 0; | |||
| @@ -1066,3 +1118,69 @@ | |||
| #X connect 79 0 27 3; | |||
| #X connect 80 0 27 4; | |||
| #X connect 81 0 27 5; | |||
| ======= | |||
| #X connect 26 0 2 0; | |||
| #X connect 26 1 19 0; | |||
| #X connect 27 0 13 0; | |||
| #X connect 27 1 20 0; | |||
| #X connect 28 0 14 0; | |||
| #X connect 28 1 21 0; | |||
| #X connect 29 0 15 0; | |||
| #X connect 29 1 22 0; | |||
| #X connect 30 0 16 0; | |||
| #X connect 30 1 23 0; | |||
| #X connect 31 0 17 0; | |||
| #X connect 31 1 24 0; | |||
| #X connect 32 0 8 0; | |||
| #X connect 33 0 34 0; | |||
| #X connect 34 0 36 0; | |||
| #X connect 35 0 5 0; | |||
| #X connect 35 0 54 0; | |||
| #X connect 36 0 32 0; | |||
| #X connect 36 0 61 0; | |||
| #X connect 37 0 38 0; | |||
| #X connect 38 0 39 0; | |||
| #X connect 38 0 62 0; | |||
| #X connect 39 0 9 0; | |||
| #X connect 40 0 41 0; | |||
| #X connect 41 0 42 0; | |||
| #X connect 41 0 63 0; | |||
| #X connect 42 0 10 0; | |||
| #X connect 43 0 44 0; | |||
| #X connect 44 0 45 0; | |||
| #X connect 44 0 64 0; | |||
| #X connect 45 0 11 0; | |||
| #X connect 46 0 47 0; | |||
| #X connect 47 0 48 0; | |||
| #X connect 47 0 65 0; | |||
| #X connect 48 0 12 0; | |||
| #X connect 49 0 37 0; | |||
| #X connect 50 0 43 0; | |||
| #X connect 51 0 46 0; | |||
| #X connect 52 0 35 0; | |||
| #X connect 52 1 33 0; | |||
| #X connect 52 2 49 0; | |||
| #X connect 52 3 53 0; | |||
| #X connect 52 4 50 0; | |||
| #X connect 52 5 51 0; | |||
| #X connect 53 0 40 0; | |||
| #X connect 55 0 72 0; | |||
| #X connect 56 0 66 0; | |||
| #X connect 57 0 68 0; | |||
| #X connect 58 0 67 0; | |||
| #X connect 59 0 69 0; | |||
| #X connect 60 0 70 0; | |||
| #X connect 66 0 73 0; | |||
| #X connect 67 0 74 0; | |||
| #X connect 68 0 75 0; | |||
| #X connect 69 0 76 0; | |||
| #X connect 70 0 77 0; | |||
| #X connect 71 0 25 0; | |||
| #X connect 72 0 71 0; | |||
| #X connect 73 0 25 1; | |||
| #X connect 74 0 25 2; | |||
| #X connect 75 0 25 3; | |||
| #X connect 76 0 25 4; | |||
| #X connect 77 0 25 5; | |||
| #X connect 80 0 7 0; | |||
| >>>>>>> 2d8748675208b3c97908663eb33499dbf4213d11 | |||
| @@ -0,0 +1,80 @@ | |||
| /* | |||
| Simple synthesizer with one oscillator, LFO and envelope. | |||
| Author: Leonardo Laguna Ruiz - leonardo@vult-dsp.com | |||
| Check the API documentation in the basic.vult example | |||
| */ | |||
| fun env(gate) { | |||
| mem x; | |||
| val k = if gate > x then 0.05 else 0.0002; | |||
| x = x + (gate - x) * k; | |||
| return x; | |||
| } | |||
| fun edge(x):bool { | |||
| mem pre_x; | |||
| val v:bool = (pre_x <> x) && (pre_x == false); | |||
| pre_x = x; | |||
| return v; | |||
| } | |||
| fun pitchToFreq(cv) { | |||
| return 261.6256 * exp(cv * 0.69314718056); | |||
| } | |||
| fun phasor(pitch, reset){ | |||
| mem phase; | |||
| val rate = pitchToFreq(pitch) * sampletime(); | |||
| phase = phase + rate; | |||
| if(phase > 1.0) | |||
| phase = phase - 1.0; | |||
| if(reset) | |||
| phase = 0.0; | |||
| return phase; | |||
| } | |||
| fun oscillator(pitch, mod) { | |||
| mem pre_phase1; | |||
| // Implements the resonant filter simulation as shown in | |||
| // http://en.wikipedia.org/wiki/Phase_distortion_synthesis | |||
| val phase1 = phasor(pitch, false); | |||
| val comp = 1.0 - phase1; | |||
| val reset = edge((pre_phase1 - phase1) > 0.5); | |||
| pre_phase1 = phase1; | |||
| val phase2 = phasor(pitch + mod, reset); | |||
| val sine = sin(2.0 * pi() * phase2); | |||
| return sine * comp; | |||
| } | |||
| fun lfo(f, gate){ | |||
| mem phase; | |||
| val rate = f * 10.0 * sampletime(); | |||
| if(edge(gate > 0.0)) | |||
| phase = 0.0; | |||
| phase = phase + rate; | |||
| if(phase > 1.0) | |||
| phase = phase - 1.0; | |||
| return sin(phase * 2.0 * pi()) - 0.5; | |||
| } | |||
| // Main processing function | |||
| fun process(cv, gate){ | |||
| // LFO | |||
| val lfo_rate = getKnob(3); | |||
| val lfo_amt = getKnob(4); | |||
| val lfo_val = lfo(lfo_rate, gate) * lfo_amt; | |||
| // Oscillator | |||
| val pitch = getKnob(1) + 10.0 * cv - 2.0; | |||
| val mod = getKnob(2) * 2.0 + lfo_val; | |||
| val o = oscillator(pitch, mod); | |||
| // Envelope | |||
| val e = env(gate); | |||
| return o * e; | |||
| } | |||
| and update() { | |||
| _ = display("IN1: CV, IN2: GATE"); | |||
| } | |||
| @@ -0,0 +1,45 @@ | |||
| /* | |||
| Simple wave table VCO. | |||
| Author: Leonardo Laguna Ruiz - leonardo@vult-dsp.com | |||
| Check the API documentation in the basic.vult example | |||
| */ | |||
| fun pitchToFreq(cv) @[table(size=128, min=-10.0, max=10.0)] { | |||
| return 261.6256 * exp(cv * 0.69314718056); | |||
| } | |||
| // Generates (at compile time) a wave table based on the provided harmonics | |||
| // To change the wave table, change the 'harmonics' array | |||
| fun wavetable(phase) @[table(size=128, min=0.0, max=1.0)] { | |||
| val harmonics = [0.0, 1.0, 0.7, 0.5, 0.3]; | |||
| val n = size(harmonics); | |||
| val acc = 0.0; | |||
| val i = 0; | |||
| while(i < n) { | |||
| acc = acc + harmonics[i] * sin(2.0 * pi() * real(i) * phase); | |||
| i = i + 1; | |||
| } | |||
| return acc / sqrt(real(n)); | |||
| } | |||
| fun process(input_cv:real) { | |||
| mem phase; | |||
| val knob_cv = getKnob(1) - 0.5; | |||
| val cv = 10.0 * (input_cv + knob_cv); | |||
| val freq = pitchToFreq(cv); | |||
| val delta = sampletime() * freq; | |||
| phase = phase + delta; | |||
| if(phase > 1.0) { | |||
| phase = phase - 1.0; | |||
| } | |||
| return wavetable(phase); | |||
| } | |||
| and update() { | |||
| } | |||
| @@ -5,11 +5,22 @@ | |||
| struct LuaJITEngine : ScriptEngine { | |||
| lua_State* L = NULL; | |||
| struct SafeArray { | |||
| void* p; | |||
| size_t len; | |||
| // This is a mirror of ProcessBlock that we are going to use | |||
| // to provide 1-based indices within the Lua VM | |||
| struct LuaProcessBlock { | |||
| float sampleRate; | |||
| float sampleTime; | |||
| int bufferSize; | |||
| float* inputs[NUM_ROWS + 1]; | |||
| float* outputs[NUM_ROWS + 1]; | |||
| float* knobs; | |||
| bool* switches; | |||
| float* lights[NUM_ROWS + 1]; | |||
| float* switchLights[NUM_ROWS + 1]; | |||
| }; | |||
| LuaProcessBlock luaBlock; | |||
| ~LuaJITEngine() { | |||
| if (L) | |||
| lua_close(L); | |||
| @@ -22,6 +33,20 @@ struct LuaJITEngine : ScriptEngine { | |||
| int run(const std::string& path, const std::string& script) override { | |||
| ProcessBlock* block = getProcessBlock(); | |||
| // Initialize all the pointers with an offset of -1 | |||
| #pragma GCC diagnostic push | |||
| #pragma GCC diagnostic ignored "-Warray-bounds" | |||
| luaBlock.knobs = &block->knobs[-1]; | |||
| luaBlock.switches = &block->switches[-1]; | |||
| for (int i = 0; i < NUM_ROWS; i++) { | |||
| luaBlock.inputs[i + 1] = &block->inputs[i][-1]; | |||
| luaBlock.outputs[i + 1] = &block->outputs[i][-1]; | |||
| luaBlock.lights[i + 1] = &block->lights[i][-1]; | |||
| luaBlock.switchLights[i + 1] = &block->switchLights[i][-1]; | |||
| } | |||
| #pragma GCC diagnostic pop | |||
| L = luaL_newstate(); | |||
| if (!L) { | |||
| display("Could not create LuaJIT context"); | |||
| @@ -29,16 +54,22 @@ struct LuaJITEngine : ScriptEngine { | |||
| } | |||
| // Import a subset of the standard library | |||
| luaopen_base(L); | |||
| luaopen_string(L); | |||
| luaopen_table(L); | |||
| luaopen_math(L); | |||
| luaopen_bit(L); | |||
| // Loads the JIT package otherwise it will be off | |||
| luaopen_jit(L); | |||
| // Disables access to the JIT package | |||
| lua_pushnil(L); | |||
| lua_setglobal(L,"jit"); | |||
| static const luaL_Reg lj_lib_load[] = { | |||
| {"", luaopen_base}, | |||
| {LUA_LOADLIBNAME, luaopen_package}, | |||
| {LUA_TABLIBNAME, luaopen_table}, | |||
| {LUA_STRLIBNAME, luaopen_string}, | |||
| {LUA_MATHLIBNAME, luaopen_math}, | |||
| {LUA_BITLIBNAME, luaopen_bit}, | |||
| {LUA_JITLIBNAME, luaopen_jit}, | |||
| {LUA_FFILIBNAME, luaopen_ffi}, | |||
| {NULL, NULL} | |||
| }; | |||
| for (const luaL_Reg* lib = lj_lib_load; lib->func; lib++) { | |||
| lua_pushcfunction(L, lib->func); | |||
| lua_pushstring(L, lib->name); | |||
| lua_call(L, 1, 0); | |||
| } | |||
| // Set user pointer | |||
| lua_pushlightuserdata(L, this); | |||
| @@ -63,7 +94,46 @@ struct LuaJITEngine : ScriptEngine { | |||
| } | |||
| lua_setglobal(L, "config"); | |||
| // Compile script | |||
| // Load the FFI auxiliary functions. | |||
| std::stringstream ffi_stream; | |||
| ffi_stream | |||
| << "local ffi = require('ffi')" << std::endl | |||
| // Describe the struct `LuaProcessBlock` so that LuaJIT knows how to access the data | |||
| << "ffi.cdef[[" << std::endl | |||
| << "struct LuaProcessBlock {" << std::endl | |||
| << "float sampleRate;" << std::endl | |||
| << "float sampleTime;" << std::endl | |||
| << "int bufferSize;" << std::endl | |||
| << "float *inputs[" << NUM_ROWS + 1 << "];" << std::endl | |||
| << "float *outputs[" << NUM_ROWS + 1 << "];" << std::endl | |||
| << "float *knobs;" << std::endl | |||
| << "bool *switches;" << std::endl | |||
| << "float *lights[" << NUM_ROWS + 1 << "];" << std::endl | |||
| << "float *switchLights[" << NUM_ROWS + 1 << "];" << std::endl | |||
| << "};]]" << std::endl | |||
| // Declare the function `_castBlock` used to transform `luaBlock` pointer into a LuaJIT cdata | |||
| << "function _castBlock(b) return ffi.cast('struct LuaProcessBlock*', b) end"; | |||
| std::string ffi_script = ffi_stream.str(); | |||
| // Compile the ffi script | |||
| if (luaL_loadbuffer(L, ffi_script.c_str(), ffi_script.size(), "ffi_script.lua")) { | |||
| const char* s = lua_tostring(L, -1); | |||
| WARN("LuaJIT: %s", s); | |||
| display(s); | |||
| lua_pop(L, 1); | |||
| return -1; | |||
| } | |||
| // Run the ffi script | |||
| if (lua_pcall(L, 0, 0, 0)) { | |||
| const char* s = lua_tostring(L, -1); | |||
| WARN("LuaJIT: %s", s); | |||
| display(s); | |||
| lua_pop(L, 1); | |||
| return -1; | |||
| } | |||
| // Compile user script | |||
| if (luaL_loadbuffer(L, script.c_str(), script.size(), path.c_str())) { | |||
| const char* s = lua_tostring(L, -1); | |||
| WARN("LuaJIT: %s", s); | |||
| @@ -104,102 +174,15 @@ struct LuaJITEngine : ScriptEngine { | |||
| return -1; | |||
| } | |||
| // FloatArray metatable | |||
| lua_newtable(L); | |||
| { | |||
| // __index | |||
| lua_pushcfunction(L, native_FloatArray_index); | |||
| lua_setfield(L, -2, "__index"); | |||
| // __newindex | |||
| lua_pushcfunction(L, native_FloatArray_newindex); | |||
| lua_setfield(L, -2, "__newindex"); | |||
| // __len | |||
| lua_pushcfunction(L, native_FloatArray_len); | |||
| lua_setfield(L, -2, "__len"); | |||
| } | |||
| lua_setglobal(L, "FloatArray"); | |||
| // BoolArray metatable | |||
| lua_newtable(L); | |||
| { | |||
| // __index | |||
| lua_pushcfunction(L, native_BoolArray_index); | |||
| lua_setfield(L, -2, "__index"); | |||
| // __newindex | |||
| lua_pushcfunction(L, native_BoolArray_newindex); | |||
| lua_setfield(L, -2, "__newindex"); | |||
| // __len | |||
| lua_pushcfunction(L, native_BoolArray_len); | |||
| lua_setfield(L, -2, "__len"); | |||
| } | |||
| lua_setglobal(L, "BoolArray"); | |||
| // Create block object | |||
| lua_newtable(L); | |||
| { | |||
| // inputs | |||
| lua_newtable(L); | |||
| for (int i = 0; i < NUM_ROWS; i++) { | |||
| SafeArray* input = (SafeArray*) lua_newuserdata(L, sizeof(SafeArray)); | |||
| input->p = &block->inputs[i]; | |||
| input->len = block->bufferSize; | |||
| lua_getglobal(L, "FloatArray"); | |||
| lua_setmetatable(L, -2); | |||
| lua_rawseti(L, -2, i + 1); | |||
| } | |||
| lua_setfield(L, -2, "inputs"); | |||
| // outputs | |||
| lua_newtable(L); | |||
| for (int i = 0; i < NUM_ROWS; i++) { | |||
| SafeArray* output = (SafeArray*) lua_newuserdata(L, sizeof(SafeArray)); | |||
| output->p = &block->outputs[i]; | |||
| output->len = block->bufferSize; | |||
| lua_getglobal(L, "FloatArray"); | |||
| lua_setmetatable(L, -2); | |||
| lua_rawseti(L, -2, i + 1); | |||
| } | |||
| lua_setfield(L, -2, "outputs"); | |||
| // knobs | |||
| SafeArray* knobs = (SafeArray*) lua_newuserdata(L, sizeof(SafeArray)); | |||
| knobs->p = &block->knobs; | |||
| knobs->len = 6; | |||
| lua_getglobal(L, "FloatArray"); | |||
| lua_setmetatable(L, -2); | |||
| lua_setfield(L, -2, "knobs"); | |||
| // switches | |||
| SafeArray* switches = (SafeArray*) lua_newuserdata(L, sizeof(SafeArray)); | |||
| switches->p = &block->switches; | |||
| switches->len = 6; | |||
| lua_getglobal(L, "BoolArray"); | |||
| lua_setmetatable(L, -2); | |||
| lua_setfield(L, -2, "switches"); | |||
| // lights | |||
| lua_newtable(L); | |||
| for (int i = 0; i < NUM_ROWS; i++) { | |||
| SafeArray* light = (SafeArray*) lua_newuserdata(L, sizeof(SafeArray)); | |||
| light->p = &block->lights[i]; | |||
| light->len = 3; | |||
| lua_getglobal(L, "FloatArray"); | |||
| lua_setmetatable(L, -2); | |||
| lua_rawseti(L, -2, i + 1); | |||
| } | |||
| lua_setfield(L, -2, "lights"); | |||
| // switchLights | |||
| lua_newtable(L); | |||
| for (int i = 0; i < NUM_ROWS; i++) { | |||
| SafeArray* switchLight = (SafeArray*) lua_newuserdata(L, sizeof(SafeArray)); | |||
| switchLight->p = &block->switchLights[i]; | |||
| switchLight->len = 3; | |||
| lua_getglobal(L, "FloatArray"); | |||
| lua_setmetatable(L, -2); | |||
| lua_rawseti(L, -2, i + 1); | |||
| } | |||
| lua_setfield(L, -2, "switchLights"); | |||
| lua_getglobal(L, "_castBlock"); | |||
| lua_pushlightuserdata(L, (void*) &luaBlock); | |||
| if (lua_pcall(L, 1, 1, 0)) { | |||
| const char* s = lua_tostring(L, -1); | |||
| WARN("LuaJIT: Error casting block: %s", s); | |||
| display(s); | |||
| lua_pop(L, 1); | |||
| return -1; | |||
| } | |||
| return 0; | |||
| @@ -208,20 +191,11 @@ struct LuaJITEngine : ScriptEngine { | |||
| int process() override { | |||
| ProcessBlock* block = getProcessBlock(); | |||
| // Set block | |||
| { | |||
| // sampleRate | |||
| lua_pushnumber(L, block->sampleRate); | |||
| lua_setfield(L, -2, "sampleRate"); | |||
| // sampleTime | |||
| lua_pushnumber(L, block->sampleTime); | |||
| lua_setfield(L, -2, "sampleTime"); | |||
| // bufferSize | |||
| lua_pushinteger(L, block->bufferSize); | |||
| lua_setfield(L, -2, "bufferSize"); | |||
| } | |||
| // Update the values of the block. | |||
| // The pointer values do not change. | |||
| luaBlock.sampleRate = block->sampleRate; | |||
| luaBlock.sampleTime = block->sampleTime; | |||
| luaBlock.bufferSize = block->bufferSize; | |||
| // Duplicate process function | |||
| lua_pushvalue(L, -2); | |||
| @@ -264,62 +238,6 @@ struct LuaJITEngine : ScriptEngine { | |||
| getEngine(L)->display(s); | |||
| return 0; | |||
| } | |||
| static int native_FloatArray_index(lua_State* L) { | |||
| SafeArray* a = (SafeArray*) lua_touserdata(L, 1); | |||
| float* data = (float*) a->p; | |||
| size_t index = lua_tointeger(L, 2) - 1; | |||
| if (index >= a->len) { | |||
| lua_pushstring(L, "Array out of bounds"); | |||
| lua_error(L); | |||
| } | |||
| lua_pushnumber(L, data[index]); | |||
| return 1; | |||
| } | |||
| static int native_FloatArray_newindex(lua_State* L) { | |||
| SafeArray* a = (SafeArray*) lua_touserdata(L, 1); | |||
| float* data = (float*) a->p; | |||
| size_t index = lua_tointeger(L, 2) - 1; | |||
| if (index >= a->len) { | |||
| lua_pushstring(L, "Array out of bounds"); | |||
| lua_error(L); | |||
| } | |||
| data[index] = lua_tonumber(L, 3); | |||
| return 0; | |||
| } | |||
| static int native_FloatArray_len(lua_State* L) { | |||
| SafeArray* a = (SafeArray*) lua_touserdata(L, 1); | |||
| lua_pushinteger(L, a->len); | |||
| return 1; | |||
| } | |||
| static int native_BoolArray_index(lua_State* L) { | |||
| SafeArray* a = (SafeArray*) lua_touserdata(L, 1); | |||
| bool* data = (bool*) a->p; | |||
| size_t index = lua_tointeger(L, 2) - 1; | |||
| if (index >= a->len) { | |||
| lua_pushstring(L, "Array out of bounds"); | |||
| lua_error(L); | |||
| } | |||
| lua_pushboolean(L, data[index]); | |||
| return 1; | |||
| } | |||
| static int native_BoolArray_newindex(lua_State* L) { | |||
| SafeArray* a = (SafeArray*) lua_touserdata(L, 1); | |||
| bool* data = (bool*) a->p; | |||
| size_t index = lua_tointeger(L, 2) - 1; | |||
| if (index >= a->len) { | |||
| lua_pushstring(L, "Array out of bounds"); | |||
| lua_error(L); | |||
| } | |||
| data[index] = lua_toboolean(L, 3); | |||
| return 0; | |||
| } | |||
| static int native_BoolArray_len(lua_State* L) { | |||
| SafeArray* a = (SafeArray*) lua_touserdata(L, 1); | |||
| lua_pushinteger(L, a->len); | |||
| return 1; | |||
| } | |||
| }; | |||
| @@ -0,0 +1,154 @@ | |||
| #include "ScriptEngine.hpp" | |||
| #include "vultc.h" | |||
| #include <quickjs/quickjs.h> | |||
| /* The Vult engine relies on both QuickJS and LuaJIT. | |||
| * | |||
| * The compiler is written in OCaml but converted to JavaScript. The JavaScript | |||
| * code is embedded as a string and executed by the QuickJs engine. The Vult | |||
| * compiler generates Lua code that is executed by the LuaJIT engine. | |||
| */ | |||
| // Special version of createScriptEngine that only creates Lua engines | |||
| ScriptEngine* createLuaEngine() { | |||
| auto it = scriptEngineFactories.find("lua"); | |||
| if (it == scriptEngineFactories.end()) | |||
| return NULL; | |||
| return it->second->createScriptEngine(); | |||
| } | |||
| struct VultEngine : ScriptEngine { | |||
| // used to run the lua generated code | |||
| ScriptEngine* luaEngine; | |||
| // used to run the Vult compiler | |||
| JSRuntime* rt = NULL; | |||
| JSContext* ctx = NULL; | |||
| VultEngine() { | |||
| rt = JS_NewRuntime(); | |||
| // Create QuickJS context | |||
| ctx = JS_NewContext(rt); | |||
| if (!ctx) { | |||
| display("Could not create QuickJS context"); | |||
| return; | |||
| } | |||
| JSValue global_obj = JS_GetGlobalObject(ctx); | |||
| // Load the Vult compiler code | |||
| JSValue val = | |||
| JS_Eval(ctx, (const char*)vultc_h, vultc_h_size, "vultc.js", 0); | |||
| if (JS_IsException(val)) { | |||
| display("Error loading the Vult compiler"); | |||
| JS_FreeValue(ctx, val); | |||
| JS_FreeValue(ctx, global_obj); | |||
| return; | |||
| } | |||
| } | |||
| ~VultEngine() { | |||
| if (ctx) { | |||
| JS_FreeContext(ctx); | |||
| } | |||
| if (rt) { | |||
| JS_FreeRuntime(rt); | |||
| } | |||
| } | |||
| std::string getEngineName() override { | |||
| return "Vult"; | |||
| } | |||
| int run(const std::string& path, const std::string& script) override { | |||
| display("Loading..."); | |||
| JSValue global_obj = JS_GetGlobalObject(ctx); | |||
| // Put the script text in the 'code' variable | |||
| JSValue code = JS_NewString(ctx, script.c_str()); | |||
| JS_SetPropertyStr(ctx, global_obj, "code", code); | |||
| // Put the script path in 'file' variable | |||
| JSValue file = JS_NewString(ctx, path.c_str()); | |||
| JS_SetPropertyStr(ctx, global_obj, "file", file); | |||
| display("Compiling..."); | |||
| // Call the Vult compiler to generate Lua code | |||
| static const std::string testVult = R"( | |||
| var result = vult.generateLua([{ file:file, code:code}],{ output:'Engine', template:'vcv-prototype'});)"; | |||
| JSValue compile = | |||
| JS_Eval(ctx, testVult.c_str(), testVult.size(), "Compile", 0); | |||
| JS_FreeValue(ctx, code); | |||
| JS_FreeValue(ctx, file); | |||
| // If there are any internal errors, the execution could fail | |||
| if (JS_IsException(compile)) { | |||
| display("Fatal error in the Vult compiler"); | |||
| JS_FreeValue(ctx, global_obj); | |||
| return -1; | |||
| } | |||
| // Retrive the variable 'result' | |||
| JSValue result = JS_GetPropertyStr(ctx, global_obj, "result"); | |||
| // Get the first element of the 'result' array | |||
| JSValue first = JS_GetPropertyUint32(ctx, result, 0); | |||
| // Try to get the 'msg' field which is only present in error messages | |||
| JSValue msg = JS_GetPropertyStr(ctx, first, "msg"); | |||
| // Display the error if any | |||
| if (!JS_IsUndefined(msg)) { | |||
| const char* text = JS_ToCString(ctx, msg); | |||
| const char* row = | |||
| JS_ToCString(ctx, JS_GetPropertyStr(ctx, first, "line")); | |||
| const char* col = JS_ToCString(ctx, JS_GetPropertyStr(ctx, first, "col")); | |||
| // Compose the error message | |||
| std::stringstream error; | |||
| error << "line:" << row << ":" << col << ": " << text; | |||
| WARN("Vult Error: %s", error.str().c_str()); | |||
| display(error.str().c_str()); | |||
| JS_FreeValue(ctx, result); | |||
| JS_FreeValue(ctx, first); | |||
| JS_FreeValue(ctx, msg); | |||
| return -1; | |||
| } | |||
| // In case of no error, retrieve the generated code | |||
| JSValue luacode = JS_GetPropertyStr(ctx, first, "code"); | |||
| std::string luacode_str(JS_ToCString(ctx, luacode)); | |||
| //WARN("Generated Code: %s", luacode_str.c_str()); | |||
| luaEngine = createLuaEngine(); | |||
| if (!luaEngine) { | |||
| WARN("Could not create a Lua script engine"); | |||
| return -1; | |||
| } | |||
| luaEngine->module = this->module; | |||
| display("Running..."); | |||
| JS_FreeValue(ctx, luacode); | |||
| JS_FreeValue(ctx, first); | |||
| JS_FreeValue(ctx, msg); | |||
| JS_FreeValue(ctx, msg); | |||
| JS_FreeValue(ctx, global_obj); | |||
| return luaEngine->run(path, luacode_str); | |||
| } | |||
| int process() override { | |||
| if (!luaEngine) | |||
| return -1; | |||
| return luaEngine->process(); | |||
| } | |||
| }; | |||
| __attribute__((constructor(1000))) | |||
| static void constructor() { | |||
| addScriptEngine<VultEngine>("vult"); | |||
| } | |||