From 27a2c76da3b6c2894507bd133454dca6403a4036 Mon Sep 17 00:00:00 2001 From: Andrew Belt Date: Sun, 15 Sep 2019 14:26:45 -0400 Subject: [PATCH] Add block buffering. Use array instead of object for RGB lights. Add changelog. --- CHANGELOG.md | 9 +++ Makefile | 12 ++++ examples/rainbow.js | 9 ++- examples/vco.js | 4 +- plugin.json | 1 + src/DuktapeEngine.cpp | 126 ++++++++++++++++++++++-------------------- src/Prototype.cpp | 80 +++++++++++++++++---------- src/ScriptEngine.hpp | 20 ++++--- 8 files changed, 161 insertions(+), 100 deletions(-) create mode 100644 CHANGELOG.md diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..67ad083 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,9 @@ + +### 1.1.0 (in development) +- Add block buffering with `config.bufferSize`. + - Scripts must change `inputs[i]` to `inputs[i][0]` and `outputs[i]` to `outputs[i][0]`. +- Duktape (JavaScript): Use array instead of object for RGB lights and switch lights. + - Scripts must change `.r` to `[0]`, `.g` to `[1]`, and `.b` to `[2]` for `lights` and `switchLights`. + +### 1.0.0 (2019-09-15) +- Initial release. \ No newline at end of file diff --git a/Makefile b/Makefile index ce84e3a..3d79adc 100644 --- a/Makefile +++ b/Makefile @@ -21,7 +21,19 @@ FLAGS += -Idep/duktape-2.4.0/src $(duktape): cd dep && $(WGET) "https://duktape.org/duktape-2.4.0.tar.xz" + cd dep && $(SHA256) duktape-2.4.0.tar.xz 86a89307d1633b5cedb2c6e56dc86e92679fc34b05be551722d8cc69ab0771fc cd dep && $(UNTAR) duktape-2.4.0.tar.xz +# # LuaJIT + +# luajit := dep/lib/luajit.a +# DEPS += $(luajit) + +# $(luajit): +# cd dep && $(WGET) "http://luajit.org/download/LuaJIT-2.0.5.tar.gz" +# cd dep && $(SHA256) LuaJIT-2.0.5.tar.gz 874b1f8297c697821f561f9b73b57ffd419ed8f4278c82e05b48806d30c1e979 +# cd dep && $(UNTAR) LuaJIT-2.0.5.tar.gz +# cd dep/LuaJIT-2.0.5 && $(MAKE) + include $(RACK_DIR)/plugin.mk diff --git a/examples/rainbow.js b/examples/rainbow.js index f66fcaa..399e83b 100644 --- a/examples/rainbow.js +++ b/examples/rainbow.js @@ -15,7 +15,10 @@ function hsvToRgb(h, s, v) { else if (h < 5) rgb = [x, 0, c] else rgb = [c, 0, x] var m = v - c - return {r: rgb[0] + m, g: rgb[1] + m, b: rgb[2] + m} + rgb[0] += m + rgb[1] += m + rgb[2] += m + return rgb } @@ -29,8 +32,10 @@ function process(args) { var rgb = hsvToRgb(h, 1, 1) args.lights[i] = rgb args.switchLights[i] = rgb - args.outputs[i] = Math.sin(2 * Math.PI * h) * 5 + 5 + args.outputs[i][0] = Math.sin(2 * Math.PI * h) * 5 + 5 } } display("Hello, world!") + +// 12.2us \ No newline at end of file diff --git a/examples/vco.js b/examples/vco.js index d8c800a..b62a39d 100644 --- a/examples/vco.js +++ b/examples/vco.js @@ -10,7 +10,7 @@ function process(args) { // Knob ranges from -5 to 5 octaves var pitch = args.knobs[0] * 10 - 5 // Input follows 1V/oct standard - pitch += args.inputs[0] + pitch += args.inputs[0][0] // The relationship between 1V/oct pitch and frequency is `freq = 2^pitch`. // Default frequency is middle C (C4) in Hz. @@ -24,5 +24,5 @@ function process(args) { phase %= 1 // Convert phase to sine output - args.outputs[0] = Math.sin(2 * Math.PI * phase) * 5 + args.outputs[0][0] = Math.sin(2 * Math.PI * phase) * 5 } \ No newline at end of file diff --git a/plugin.json b/plugin.json index 0c23db9..48df7e7 100644 --- a/plugin.json +++ b/plugin.json @@ -11,6 +11,7 @@ "manualUrl": "https://github.com/VCVRack/VCV-Prototype/blob/master/README.md", "sourceUrl": "https://github.com/VCVRack/VCV-Prototype", "donateUrl": "", + "changelogUrl": "https://github.com/VCVRack/VCV-Prototype/blob/master/CHANGELOG.md", "modules": [ { "slug": "Prototype", diff --git a/src/DuktapeEngine.cpp b/src/DuktapeEngine.cpp index 1956d8a..b1d52f5 100644 --- a/src/DuktapeEngine.cpp +++ b/src/DuktapeEngine.cpp @@ -56,6 +56,9 @@ struct DuktapeEngine : ScriptEngine { // frameDivider duk_push_int(ctx, getFrameDivider()); duk_put_prop_string(ctx, configIdx, "frameDivider"); + // bufferSize + duk_push_int(ctx, getBufferSize()); + duk_put_prop_string(ctx, configIdx, "bufferSize"); } duk_put_global_string(ctx, "config"); @@ -86,6 +89,10 @@ struct DuktapeEngine : ScriptEngine { duk_get_prop_string(ctx, -1, "frameDivider"); setFrameDivider(duk_get_int(ctx, -1)); duk_pop(ctx); + // bufferSize + duk_get_prop_string(ctx, -1, "bufferSize"); + setBufferSize(duk_get_int(ctx, -1)); + duk_pop(ctx); } duk_pop(ctx); @@ -96,24 +103,37 @@ struct DuktapeEngine : ScriptEngine { return -1; } - // args (keep on stack) - duk_idx_t argsIdx = duk_push_object(ctx); + // block (keep on stack) + duk_idx_t blockIdx = duk_push_object(ctx); { + // bufferSize + int bufferSize = getBufferSize(); + duk_push_int(ctx, bufferSize); + duk_put_prop_string(ctx, blockIdx, "bufferSize"); + // inputs duk_idx_t inputsIdx = duk_push_array(ctx); for (int i = 0; i < NUM_ROWS; i++) { - duk_push_number(ctx, 0.0); + duk_idx_t inputIdx = duk_push_array(ctx); + for (int j = 0; j < bufferSize; j++) { + duk_push_number(ctx, 0.0); + duk_put_prop_index(ctx, inputIdx, j); + } duk_put_prop_index(ctx, inputsIdx, i); } - duk_put_prop_string(ctx, argsIdx, "inputs"); + duk_put_prop_string(ctx, blockIdx, "inputs"); // outputs duk_idx_t outputsIdx = duk_push_array(ctx); for (int i = 0; i < NUM_ROWS; i++) { - duk_push_number(ctx, 0.0); + duk_idx_t outputIdx = duk_push_array(ctx); + for (int j = 0; j < bufferSize; j++) { + duk_push_number(ctx, 0.0); + duk_put_prop_index(ctx, outputIdx, j); + } duk_put_prop_index(ctx, outputsIdx, i); } - duk_put_prop_string(ctx, argsIdx, "outputs"); + duk_put_prop_string(ctx, blockIdx, "outputs"); // knobs duk_idx_t knobsIdx = duk_push_array(ctx); @@ -121,7 +141,7 @@ struct DuktapeEngine : ScriptEngine { duk_push_number(ctx, 0.0); duk_put_prop_index(ctx, knobsIdx, i); } - duk_put_prop_string(ctx, argsIdx, "knobs"); + duk_put_prop_string(ctx, blockIdx, "knobs"); // switches duk_idx_t switchesIdx = duk_push_array(ctx); @@ -129,76 +149,72 @@ struct DuktapeEngine : ScriptEngine { duk_push_number(ctx, 0.0); duk_put_prop_index(ctx, switchesIdx, i); } - duk_put_prop_string(ctx, argsIdx, "switches"); + duk_put_prop_string(ctx, blockIdx, "switches"); // lights duk_idx_t lightsIdx = duk_push_array(ctx); for (int i = 0; i < NUM_ROWS; i++) { - duk_idx_t lightIdx = duk_push_object(ctx); - { - duk_push_number(ctx, 0.0); - duk_put_prop_string(ctx, lightIdx, "r"); + duk_idx_t lightIdx = duk_push_array(ctx); + for (int c = 0; c < 3; c++) { duk_push_number(ctx, 0.0); - duk_put_prop_string(ctx, lightIdx, "g"); - duk_push_number(ctx, 0.0); - duk_put_prop_string(ctx, lightIdx, "b"); + duk_put_prop_index(ctx, lightIdx, c); } duk_put_prop_index(ctx, lightsIdx, i); } - duk_put_prop_string(ctx, argsIdx, "lights"); + duk_put_prop_string(ctx, blockIdx, "lights"); // switchLights duk_idx_t switchLightsIdx = duk_push_array(ctx); for (int i = 0; i < NUM_ROWS; i++) { - duk_idx_t switchLightIdx = duk_push_object(ctx); - { - duk_push_number(ctx, 0.0); - duk_put_prop_string(ctx, switchLightIdx, "r"); + duk_idx_t switchLightIdx = duk_push_array(ctx); + for (int c = 0; c < 3; c++) { duk_push_number(ctx, 0.0); - duk_put_prop_string(ctx, switchLightIdx, "g"); - duk_push_number(ctx, 0.0); - duk_put_prop_string(ctx, switchLightIdx, "b"); + duk_put_prop_index(ctx, switchLightIdx, c); } duk_put_prop_index(ctx, switchLightsIdx, i); } - duk_put_prop_string(ctx, argsIdx, "switchLights"); + duk_put_prop_string(ctx, blockIdx, "switchLights"); } return 0; } - int process(ProcessArgs& args) override { - // args - duk_idx_t argsIdx = duk_get_top(ctx) - 1; + int process(ProcessBlock& block) override { + // block + duk_idx_t blockIdx = duk_get_top(ctx) - 1; { // sampleRate - duk_push_number(ctx, args.sampleRate); - duk_put_prop_string(ctx, argsIdx, "sampleRate"); + duk_push_number(ctx, block.sampleRate); + duk_put_prop_string(ctx, blockIdx, "sampleRate"); // sampleTime - duk_push_number(ctx, args.sampleTime); - duk_put_prop_string(ctx, argsIdx, "sampleTime"); + duk_push_number(ctx, block.sampleTime); + duk_put_prop_string(ctx, blockIdx, "sampleTime"); // inputs - duk_get_prop_string(ctx, argsIdx, "inputs"); + duk_get_prop_string(ctx, blockIdx, "inputs"); for (int i = 0; i < NUM_ROWS; i++) { - duk_push_number(ctx, getInput(i)); - duk_put_prop_index(ctx, -2, i); + duk_get_prop_index(ctx, -1, i); + for (int j = 0; j < block.bufferSize; j++) { + duk_push_number(ctx, block.inputs[i][j]); + duk_put_prop_index(ctx, -2, j); + } + duk_pop(ctx); } duk_pop(ctx); // knobs - duk_get_prop_string(ctx, argsIdx, "knobs"); + duk_get_prop_string(ctx, blockIdx, "knobs"); for (int i = 0; i < NUM_ROWS; i++) { - duk_push_number(ctx, getKnob(i)); + duk_push_number(ctx, block.knobs[i]); duk_put_prop_index(ctx, -2, i); } duk_pop(ctx); // switches - duk_get_prop_string(ctx, argsIdx, "switches"); + duk_get_prop_string(ctx, blockIdx, "switches"); for (int i = 0; i < NUM_ROWS; i++) { - duk_push_boolean(ctx, getSwitch(i)); + duk_push_boolean(ctx, block.switches[i]); duk_put_prop_index(ctx, -2, i); } duk_pop(ctx); @@ -206,7 +222,7 @@ struct DuktapeEngine : ScriptEngine { // Duplicate process function duk_dup(ctx, -2); - // Duplicate args object + // Duplicate block object duk_dup(ctx, -2); // Call process function if (duk_pcall(ctx, 1)) { @@ -219,13 +235,17 @@ struct DuktapeEngine : ScriptEngine { // return value duk_pop(ctx); - // args + // block { // outputs duk_get_prop_string(ctx, -1, "outputs"); for (int i = 0; i < NUM_ROWS; i++) { duk_get_prop_index(ctx, -1, i); - setOutput(i, duk_get_number(ctx, -1)); + for (int j = 0; j < block.bufferSize; j++) { + duk_get_prop_index(ctx, -1, j); + block.outputs[i][j] = duk_get_number(ctx, -1); + duk_pop(ctx); + } duk_pop(ctx); } duk_pop(ctx); @@ -234,15 +254,9 @@ struct DuktapeEngine : ScriptEngine { duk_get_prop_string(ctx, -1, "lights"); for (int i = 0; i < NUM_ROWS; i++) { duk_get_prop_index(ctx, -1, i); - { - duk_get_prop_string(ctx, -1, "r"); - setLight(i, 0, duk_get_number(ctx, -1)); - duk_pop(ctx); - duk_get_prop_string(ctx, -1, "g"); - setLight(i, 1, duk_get_number(ctx, -1)); - duk_pop(ctx); - duk_get_prop_string(ctx, -1, "b"); - setLight(i, 2, duk_get_number(ctx, -1)); + for (int c = 0; c < 3; c++) { + duk_get_prop_index(ctx, -1, c); + block.lights[i][c] = duk_get_number(ctx, -1); duk_pop(ctx); } duk_pop(ctx); @@ -253,15 +267,9 @@ struct DuktapeEngine : ScriptEngine { duk_get_prop_string(ctx, -1, "switchLights"); for (int i = 0; i < NUM_ROWS; i++) { duk_get_prop_index(ctx, -1, i); - { - duk_get_prop_string(ctx, -1, "r"); - setSwitchLight(i, 0, duk_get_number(ctx, -1)); - duk_pop(ctx); - duk_get_prop_string(ctx, -1, "g"); - setSwitchLight(i, 1, duk_get_number(ctx, -1)); - duk_pop(ctx); - duk_get_prop_string(ctx, -1, "b"); - setSwitchLight(i, 2, duk_get_number(ctx, -1)); + for (int c = 0; c < 3; c++) { + duk_get_prop_index(ctx, -1, c); + block.switchLights[i][c] = duk_get_number(ctx, -1); duk_pop(ctx); } duk_pop(ctx); diff --git a/src/Prototype.cpp b/src/Prototype.cpp index 2b37d86..88dec46 100644 --- a/src/Prototype.cpp +++ b/src/Prototype.cpp @@ -39,6 +39,8 @@ struct Prototype : Module { ScriptEngine* scriptEngine = NULL; int frame = 0; int frameDivider; + ScriptEngine::ProcessBlock block; + int bufferIndex = 0; Prototype() { config(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS); @@ -64,21 +66,49 @@ struct Prototype : Module { return; frame = 0; - ScriptEngine::ProcessArgs scriptArgs; - scriptArgs.sampleRate = args.sampleRate; - scriptArgs.sampleTime = args.sampleTime; - - { - std::lock_guard lock(scriptMutex); - // Check for certain inside the mutex - if (scriptEngine) { - if (scriptEngine->process(scriptArgs)) { - WARN("Script %s process() failed. Stopped script.", path.c_str()); - clearScriptEngine(); - return; + // Inputs + for (int i = 0; i < NUM_ROWS; i++) + block.inputs[i][bufferIndex] = inputs[IN_INPUTS + i].getVoltage(); + + // Process block + if (++bufferIndex >= block.bufferSize) { + bufferIndex = 0; + + // Block settings + block.sampleRate = args.sampleRate; + block.sampleTime = args.sampleTime; + + // Params + for (int i = 0; i < NUM_ROWS; i++) + block.knobs[i] = params[KNOB_PARAMS + i].getValue(); + for (int i = 0; i < NUM_ROWS; i++) + block.switches[i] = params[SWITCH_PARAMS + i].getValue() > 0.f; + + // Run ScriptEngine's process function + { + std::lock_guard lock(scriptMutex); + // Check for certain inside the mutex + if (scriptEngine) { + if (scriptEngine->process(block)) { + WARN("Script %s process() failed. Stopped script.", path.c_str()); + clearScriptEngine(); + return; + } } } + + // Lights + for (int i = 0; i < NUM_ROWS; i++) + for (int c = 0; c < 3; c++) + lights[LIGHT_LIGHTS + i * 3 + c].setBrightness(block.lights[i][c]); + for (int i = 0; i < NUM_ROWS; i++) + for (int c = 0; c < 3; c++) + lights[SWITCH_LIGHTS + i * 3 + c].setBrightness(block.switchLights[i][c]); } + + // Outputs + for (int i = 0; i < NUM_ROWS; i++) + outputs[OUT_OUTPUTS + i].setVoltage(block.outputs[i][bufferIndex]); } void clearScriptEngine() { @@ -98,6 +128,9 @@ struct Prototype : Module { // Reset settings frameDivider = 32; frame = 0; + block.bufferSize = 1; + std::memset(block.inputs, 0, sizeof(block.inputs)); + bufferIndex = 0; } void setScriptString(std::string path, std::string script) { @@ -183,23 +216,12 @@ int ScriptEngine::getFrameDivider() { void ScriptEngine::setFrameDivider(int frameDivider) { module->frameDivider = frameDivider; } -float ScriptEngine::getInput(int index) { - return module->inputs[Prototype::IN_INPUTS + index].getVoltage(); -} -void ScriptEngine::setOutput(int index, float voltage) { - module->outputs[Prototype::OUT_OUTPUTS + index].setVoltage(voltage); -} -float ScriptEngine::getKnob(int index) { - return module->params[Prototype::KNOB_PARAMS + index].getValue(); -} -bool ScriptEngine::getSwitch(int index) { - return module->params[Prototype::SWITCH_PARAMS + index].getValue() > 0.f; -} -void ScriptEngine::setLight(int index, int color, float brightness) { - module->lights[Prototype::LIGHT_LIGHTS + index * 3 + color].setBrightness(brightness); +int ScriptEngine::getBufferSize() { + return module->block.bufferSize; } -void ScriptEngine::setSwitchLight(int index, int color, float brightness) { - module->lights[Prototype::SWITCH_LIGHTS + index * 3 + color].setBrightness(brightness); +void ScriptEngine::setBufferSize(int bufferSize) { + bufferSize = clamp(bufferSize, 1, MAX_BUFFER_SIZE); + module->block.bufferSize = bufferSize; } @@ -219,7 +241,7 @@ struct FileChoice : LedDisplayChoice { } void onAction(const event::Action& e) override { - std::string dir = asset::user(""); + std::string dir = asset::plugin(pluginInstance, "examples"); char* pathC = osdialog_file(OSDIALOG_OPEN, dir.c_str(), NULL, NULL); if (!pathC) { return; diff --git a/src/ScriptEngine.hpp b/src/ScriptEngine.hpp index a486e86..2dc0151 100644 --- a/src/ScriptEngine.hpp +++ b/src/ScriptEngine.hpp @@ -3,6 +3,7 @@ static const int NUM_ROWS = 6; +static const int MAX_BUFFER_SIZE = 4096; struct Prototype; @@ -18,26 +19,29 @@ struct ScriptEngine { */ virtual int run(const std::string& path, const std::string& script) {return 0;} - struct ProcessArgs { + struct ProcessBlock { float sampleRate; float sampleTime; + int bufferSize = 1; + float inputs[NUM_ROWS][MAX_BUFFER_SIZE] = {}; + float outputs[NUM_ROWS][MAX_BUFFER_SIZE] = {}; + float knobs[NUM_ROWS] = {}; + bool switches[NUM_ROWS] = {}; + float lights[NUM_ROWS][3] = {}; + float switchLights[NUM_ROWS][3] = {}; }; /** Calls the script's process() method. Return nonzero if failure, and set error message with setMessage(). */ - virtual int process(ProcessArgs& block) {return 0;} + virtual int process(ProcessBlock& block) {return 0;} // Communication with Prototype module. // These cannot be called from your constructor, so initialize your engine in the run() method. void setMessage(const std::string& message); int getFrameDivider(); void setFrameDivider(int frameDivider); - float getInput(int index); - void setOutput(int index, float voltage); - float getKnob(int index); - bool getSwitch(int index); - void setLight(int index, int color, float brightness); - void setSwitchLight(int index, int color, float brightness); + int getBufferSize(); + void setBufferSize(int bufferSize); // private Prototype* module; };