diff --git a/Makefile b/Makefile index 8d01326..0c0cc98 100644 --- a/Makefile +++ b/Makefile @@ -22,8 +22,8 @@ VULT ?= 1 # Vult depends on both LuaJIT and QuickJS ifeq ($(VULT), 1) -QUICKJS = 1 -LUAJIT = 1 +QUICKJS := 1 +LUAJIT := 1 endif # Entropia File System Watcher diff --git a/README.md b/README.md index 625daaf..268cc6c 100644 --- a/README.md +++ b/README.md @@ -8,8 +8,8 @@ Scripting language host for [VCV Rack](https://vcvrack.com/) containing: - 6 switches with RGB LEDs Supported scripting languages: -- JavaScript (.js) -- Lua (.lua) +- JavaScript (ES2020) (.js) +- [Lua](https://www.lua.org/) (.lua) - [Vult](https://github.com/modlfo/vult) (.vult) - [Add your own below](#adding-a-script-engine) @@ -91,7 +91,8 @@ function process(block) { } ``` -The Vult API is slightly different from the JavaScript version. Check the Vult examples included with the plugin to learn how to use the language with the VCV-Prototype plugin. +*The Vult API is slightly different than Prototype's scripting API. +See `examples/template.vult` for a reference of the Vult API.* ## Build dependencies @@ -127,7 +128,7 @@ 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 - add your name here diff --git a/examples/basic.vult b/examples/basic.vult deleted file mode 100644 index f77c865..0000000 --- a/examples/basic.vult +++ /dev/null @@ -1,83 +0,0 @@ -/* - Vult API documentation. - - Author: Leonardo Laguna Ruiz - leonardo@vult-dsp.com - - The main difference of the Vult API compared to the JavaScript and Lua is that all interactions - happen through functions rather than accessing to the block arrays. - - A Vult script requires the following two functions: - - fun process() { } - and update() { } - - The 'process' function is called every audio sample. As inputs, it will receive the values from - the input jacks but normalized to 1.0. This means that a value of 10.0 V in VCV Rack is received - as 1.0. Similarly, when you return a value of 1.0 it will be output by the prototype as 10.0V. - - You can use the input and output jacks by adding or removing arguments to the function. For example, - to pass all the inputs to the outputs you can declare the function as follows: - - fun process(i1, i2, i3, i4, i5, i6) { - return i1, i2, i3, i4, i5, i6; - } - - The 'update' function is called once every 32 samples. You can use this function to perform actions - that do not require audio rate speed e.g. setting light colors or displying characters in the screen. - The function 'update' do not takes or returns any value. - - Important: Notice that the 'update' function is declared with the keyword 'and'. In Vult language, - this means that they share context. At the moment, declaring them differently could have an undefined - behavior. - - To interact with knobs, switches, lights the following builtin functions are provided. - NOTE: the knobs, switches and lights are numbered from 1 to 6 - - getKnob(n:int) : real // value of the nth knob range: 0.0-1.0 - getSwitch(n:int) : bool // value of the nth switch: true/false - - setLight(n:int, r:real, g:real, b:real) // r, g, b range: 0.0-1.0 - setSwitchLight(n:int, r:real, g:real, b:real) // r, g, b range: 0.0-1.0 - - samplerate() : real // current sample rate - sampletime() : real // current time step (1.0 / samplerate()) - display(text:string) // display text in the screen - - */ - - -// Returns the r,g,b values for a given voltage -fun getRGB(v) { - if (v > 0.0) - return v, 0.0, 0.0; - else - return 0.0, -v, 0.0; -} - -// Takes two inputs and returns the result of different operations on them -fun process(in1, in2) { - // theses are declared as 'mem' so we can remember them and use them in 'update' - mem sum = clip(in1 + in2, -1.0, 1.0); // use 'clip' to keep the signals in the specified range - mem sub = clip(in1 - in2, -1.0, 1.0); - mem mul = clip(in1 * in2, -1.0, 1.0); - return sum, sub, mul; -} -and update() { - _ = display("Add two LFO to IN1 and IN2"); - val r, g, b; - // Set the light no 1 with the 'sum' value - r, g, b = getRGB(sum); - _ = setLight(1, r, g, b); - _ = setSwitchLight(1, r, g, b); - - // Set the light no 2 with the 'sub' value - r, g, b = getRGB(sub); - _ = setLight(2, r, g, b); - _ = setSwitchLight(2, r, g, b); - - // Set the light no 2 with the 'mul' value - r, g, b = getRGB(mul); - _ = setLight(3, r, g, b); - _ = setSwitchLight(3, r, g, b); - -} \ No newline at end of file diff --git a/src/VultEngine.cpp b/src/VultEngine.cpp index c454b0d..4370f53 100644 --- a/src/VultEngine.cpp +++ b/src/VultEngine.cpp @@ -9,145 +9,146 @@ * compiler generates Lua code that is executed by the LuaJIT engine. */ -extern std::map scriptEngineFactories; // 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(); +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"( + // 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"); + 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; - } - - 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 luaEngine->process(); - else - return 0; - } + return luaEngine->process(); + } }; -__attribute__((constructor(1000))) static void constructor() { - addScriptEngine("vult"); +__attribute__((constructor(1000))) +static void constructor() { + addScriptEngine("vult"); }