| @@ -31,7 +31,7 @@ $(efsw): | |||||
| cd efsw && cp lib/libefsw-static-release.a $(DEP_PATH)/lib/ | cd efsw && cp lib/libefsw-static-release.a $(DEP_PATH)/lib/ | ||||
| cd efsw && cp -R include/efsw $(DEP_PATH)/include/ | cd efsw && cp -R include/efsw $(DEP_PATH)/include/ | ||||
| # LibPD | |||||
| # libpd | |||||
| ifeq ($(LIBPD), 1) | ifeq ($(LIBPD), 1) | ||||
| libpd := dep/lib/libpd.a | libpd := dep/lib/libpd.a | ||||
| SOURCES += src/LibPDEngine.cpp | 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) | ||||
| # cd dep/llvm-8.0.1.src/build && $(MAKE) install | # 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 | include $(RACK_DIR)/plugin.mk | ||||
| @@ -8,7 +8,10 @@ Scripting language host for [VCV Rack](https://vcvrack.com/) containing: | |||||
| - 6 switches with RGB LEDs | - 6 switches with RGB LEDs | ||||
| Supported scripting languages: | 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) | - [Add your own below](#adding-a-script-engine) | ||||
| [Discussion thread](https://community.vcvrack.com/t/vcv-prototype/3271) | [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 | ## Build dependencies | ||||
| ### Windows | ### Windows | ||||
| @@ -111,6 +117,23 @@ sudo apt install premake4 | |||||
| sudo pacman -S premake | 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 | ## 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`. | - 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 | ## Contributors | ||||
| - [Wes Milholen](https://grayscale.info/): panel design | - [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) | - [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 | - add your name here | ||||
| @@ -1,4 +1,8 @@ | |||||
| <<<<<<< HEAD | |||||
| #N canvas 547 398 828 731 12; | #N canvas 547 398 828 731 12; | ||||
| ======= | |||||
| #N canvas 698 144 821 731 12; | |||||
| >>>>>>> 2d8748675208b3c97908663eb33499dbf4213d11 | |||||
| #N canvas 250 355 563 749 hsv2rgb 0; | #N canvas 250 355 563 749 hsv2rgb 0; | ||||
| #X obj 20 64 route 0; | #X obj 20 64 route 0; | ||||
| #X msg 20 36 \$2 \$3 \$1; | #X msg 20 36 \$2 \$3 \$1; | ||||
| @@ -912,8 +916,11 @@ | |||||
| #X msg 317 400 S4 \$1 \$2 \$3; | #X msg 317 400 S4 \$1 \$2 \$3; | ||||
| #X msg 413 400 S5 \$1 \$2 \$3; | #X msg 413 400 S5 \$1 \$2 \$3; | ||||
| #X msg 509 400 S6 \$1 \$2 \$3; | #X msg 509 400 S6 \$1 \$2 \$3; | ||||
| <<<<<<< HEAD | |||||
| #X obj 294 17 loadbang; | #X obj 294 17 loadbang; | ||||
| #X obj 294 71 print toVCV; | #X obj 294 71 print toVCV; | ||||
| ======= | |||||
| >>>>>>> 2d8748675208b3c97908663eb33499dbf4213d11 | |||||
| #X obj 30 92 metro 2000; | #X obj 30 92 metro 2000; | ||||
| #X msg 62 426 L1 \$1 \$2 \$3; | #X msg 62 426 L1 \$1 \$2 \$3; | ||||
| #X msg 158 426 L2 \$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 447 426 L5 \$1 \$2 \$3; | ||||
| #X msg 542 426 L6 \$1 \$2 \$3; | #X msg 542 426 L6 \$1 \$2 \$3; | ||||
| #X obj 29 592 dac~ 1 2 3 4 5 6, f 69; | #X obj 29 592 dac~ 1 2 3 4 5 6, f 69; | ||||
| <<<<<<< HEAD | |||||
| #X msg 294 43 display Hello world!; | #X msg 294 43 display Hello world!; | ||||
| ======= | |||||
| >>>>>>> 2d8748675208b3c97908663eb33499dbf4213d11 | |||||
| #X obj 29 369 t l l; | #X obj 29 369 t l l; | ||||
| #X obj 125 369 t l l; | #X obj 125 369 t l l; | ||||
| #X obj 221 369 t l l; | #X obj 221 369 t l l; | ||||
| @@ -930,7 +940,10 @@ | |||||
| #X obj 413 369 t l l; | #X obj 413 369 t l l; | ||||
| #X obj 509 369 t l l; | #X obj 509 369 t l l; | ||||
| #X msg 125 316 \$1 1 1; | #X msg 125 316 \$1 1 1; | ||||
| <<<<<<< HEAD | |||||
| #X msg 30 118 0 \, 100 2000; | #X msg 30 118 0 \, 100 2000; | ||||
| ======= | |||||
| >>>>>>> 2d8748675208b3c97908663eb33499dbf4213d11 | |||||
| #X obj 125 201 + 16.6667; | #X obj 125 201 + 16.6667; | ||||
| #X obj 125 227 % 100; | #X obj 125 227 % 100; | ||||
| #X obj 29 249 / 100; | #X obj 29 249 / 100; | ||||
| @@ -976,11 +989,34 @@ | |||||
| #X obj 316 559 +~ 1; | #X obj 316 559 +~ 1; | ||||
| #X obj 413 560 +~ 1; | #X obj 413 560 +~ 1; | ||||
| #X obj 508 560 +~ 1; | #X obj 508 560 +~ 1; | ||||
| <<<<<<< HEAD | |||||
| #X connect 0 0 29 0; | #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 2 0 1 0; | ||||
| #X connect 3 0 4 0; | #X connect 3 0 4 0; | ||||
| #X connect 4 0 6 0; | #X connect 4 0 6 0; | ||||
| #X connect 5 0 0 0; | #X connect 5 0 0 0; | ||||
| <<<<<<< HEAD | |||||
| #X connect 6 0 20 0; | #X connect 6 0 20 0; | ||||
| #X connect 7 0 56 0; | #X connect 7 0 56 0; | ||||
| #X connect 8 0 30 0; | #X connect 8 0 30 0; | ||||
| @@ -988,17 +1024,33 @@ | |||||
| #X connect 10 0 32 0; | #X connect 10 0 32 0; | ||||
| #X connect 11 0 33 0; | #X connect 11 0 33 0; | ||||
| #X connect 12 0 34 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 13 0 1 0; | ||||
| #X connect 14 0 1 0; | #X connect 14 0 1 0; | ||||
| #X connect 15 0 1 0; | #X connect 15 0 1 0; | ||||
| #X connect 16 0 1 0; | #X connect 16 0 1 0; | ||||
| #X connect 17 0 1 0; | #X connect 17 0 1 0; | ||||
| <<<<<<< HEAD | |||||
| #X connect 18 0 28 0; | #X connect 18 0 28 0; | ||||
| #X connect 20 0 36 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 21 0 1 0; | ||||
| #X connect 22 0 1 0; | #X connect 22 0 1 0; | ||||
| #X connect 23 0 1 0; | #X connect 23 0 1 0; | ||||
| #X connect 24 0 1 0; | #X connect 24 0 1 0; | ||||
| <<<<<<< HEAD | |||||
| #X connect 25 0 1 0; | #X connect 25 0 1 0; | ||||
| #X connect 26 0 1 0; | #X connect 26 0 1 0; | ||||
| #X connect 28 0 19 0; | #X connect 28 0 19 0; | ||||
| @@ -1066,3 +1118,69 @@ | |||||
| #X connect 79 0 27 3; | #X connect 79 0 27 3; | ||||
| #X connect 80 0 27 4; | #X connect 80 0 27 4; | ||||
| #X connect 81 0 27 5; | #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 { | struct LuaJITEngine : ScriptEngine { | ||||
| lua_State* L = NULL; | 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() { | ~LuaJITEngine() { | ||||
| if (L) | if (L) | ||||
| lua_close(L); | lua_close(L); | ||||
| @@ -22,6 +33,20 @@ struct LuaJITEngine : ScriptEngine { | |||||
| int run(const std::string& path, const std::string& script) override { | int run(const std::string& path, const std::string& script) override { | ||||
| ProcessBlock* block = getProcessBlock(); | 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(); | L = luaL_newstate(); | ||||
| if (!L) { | if (!L) { | ||||
| display("Could not create LuaJIT context"); | display("Could not create LuaJIT context"); | ||||
| @@ -29,16 +54,22 @@ struct LuaJITEngine : ScriptEngine { | |||||
| } | } | ||||
| // Import a subset of the standard library | // 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 | // Set user pointer | ||||
| lua_pushlightuserdata(L, this); | lua_pushlightuserdata(L, this); | ||||
| @@ -63,7 +94,46 @@ struct LuaJITEngine : ScriptEngine { | |||||
| } | } | ||||
| lua_setglobal(L, "config"); | 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())) { | if (luaL_loadbuffer(L, script.c_str(), script.size(), path.c_str())) { | ||||
| const char* s = lua_tostring(L, -1); | const char* s = lua_tostring(L, -1); | ||||
| WARN("LuaJIT: %s", s); | WARN("LuaJIT: %s", s); | ||||
| @@ -104,102 +174,15 @@ struct LuaJITEngine : ScriptEngine { | |||||
| return -1; | 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 | // 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; | return 0; | ||||
| @@ -208,20 +191,11 @@ struct LuaJITEngine : ScriptEngine { | |||||
| int process() override { | int process() override { | ||||
| ProcessBlock* block = getProcessBlock(); | 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 | // Duplicate process function | ||||
| lua_pushvalue(L, -2); | lua_pushvalue(L, -2); | ||||
| @@ -264,62 +238,6 @@ struct LuaJITEngine : ScriptEngine { | |||||
| getEngine(L)->display(s); | getEngine(L)->display(s); | ||||
| return 0; | 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"); | |||||
| } | |||||