├── .gitignore ├── .gitmodules ├── CHANGELOG.md ├── Faust.md ├── LICENSE-GPLv3.txt ├── LICENSE-dist.txt ├── LICENSE.md ├── Makefile ├── README.md ├── examples ├── factorial_router.js ├── gain.js ├── gain.lua ├── gain.pd ├── gain.scd ├── organ.dsp ├── physicalmodel.dsp ├── rainbow.js ├── rainbow.lua ├── rainbow.pd ├── rainbow.scd ├── synth.dsp ├── synth.vult ├── template.js ├── template.lua ├── template.pd ├── template.vult ├── vco.js ├── vco.lua ├── vco.pd ├── vco.scd └── vco.vult ├── faust_libraries └── rack.lib ├── plugin.json ├── res └── Prototype.svg ├── src ├── DuktapeEngine.cpp ├── FaustEngine.cpp ├── LibPDEngine.cpp ├── LuaJITEngine.cpp ├── Prototype.cpp ├── PythonEngine.cpp ├── QuickJSEngine.cpp ├── ScriptEngine.hpp ├── SuperColliderEngine.cpp └── VultEngine.cpp ├── support └── supercollider_extensions │ └── VcvPrototypeProcessBlock.sc └── tests ├── fall.js ├── hello.lua ├── hello.py ├── process_error.js ├── run_error.js ├── sandbox.js └── syntax_error.js /.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | /dist 3 | /dep 4 | /plugin.so 5 | /plugin.dylib 6 | /plugin.dll 7 | .DS_Store 8 | /faust_libraries 9 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "dep/efsw"] 2 | path = efsw 3 | url = https://github.com/SpartanJ/efsw.git 4 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ### 1.3.0 (in development) 2 | - Add libpd (Pure Data) engine. 3 | - Add Vult engine. 4 | - Add "New script" and "Edit script" menu items to make script creation easier. 5 | - Allow copying display message to clipboard. 6 | - Improve LuaJIT performance by enabling JIT compilation. 7 | - Add `bit` library to LuaJIT engine. 8 | 9 | ### 1.2.0 (2019-11-15) 10 | - Add Lua script engine. 11 | - Add security warning when loading script from patch or module preset. 12 | 13 | ### 1.1.1 (2019-09-27) 14 | - Switch JavaScript engine to QuickJS from Duktape. Supports ES2020 and is ~2x faster. 15 | - Automatically reload script when script file changes. 16 | - Make knobs writable. 17 | - Allow script to be loaded by dragging-and-dropping on the panel. 18 | - Add "Save script as" in panel context menu. 19 | - Reload script on initialize. 20 | 21 | ### 1.1.0 (2019-09-15) 22 | - Add block buffering with `config.bufferSize`. 23 | - Scripts must change `inputs[i]` to `inputs[i][0]` and `outputs[i]` to `outputs[i][0]`. 24 | - Allow multiple lines of text in LED display. 25 | - Duktape (JavaScript): 26 | - Use array instead of object for RGB lights and switch lights. 27 | - Scripts must change `.r` to `[0]`, `.g` to `[1]`, and `.b` to `[2]` for `lights` and `switchLights`. 28 | - Use ArrayBuffers instead of arrays, improving performance \~5x. 29 | 30 | ### 1.0.0 (2019-09-15) 31 | - Initial release. 32 | -------------------------------------------------------------------------------- /Faust.md: -------------------------------------------------------------------------------- 1 | # Using Faust DSP language in VCV Prototype 2 | 3 | The [Faust audio DSP language](https://faust.grame.fr) can be used in VCV Prototype. The compiler can be embedded in applications or plugins using [libfaust](https://faustdoc.grame.fr/manual/embedding/), and DSP code can be edited and JIT compiled on the fly. 4 | 5 | Note that to facilitate compilation and deployement, the interpreter backend is used instead of the [LLVM IR](https://llvm.org) backend (faster produced DSP code). 6 | 7 | ## Compilation 8 | 9 | - type `make && make install` to build it 10 | - you can now add a Faust aware VCV Prototype in your Rack session and start coding in Faust 11 | 12 | ## Loading/editing/compiling .dsp files 13 | 14 | Faust DSP files have to be loaded in VCV Prototype and edited in a external editor (Visual Studio Code, Atom...). Each time the file is saved, it will be recompiled and executed. To possibly save compilation time, the DSP machine code is saved in a cache, and possibly restored the next time the session will be loaded. 15 | 16 | The 6 *switches*, *knobs* as well as the *lights* and *switchLights* can be connected to UI controllers using metadata: 17 | 18 | - `[switch:N]` (with N from 1 to 6) has to be used in a `button` or `checkbox` item to connect it to the prototype interface switch number N. Pushed buttons will become red when on, and checkboxes will become white when on. 19 | - `[knob:N]` (with N from 1 to 6) has to be used in a `vslider`, `hslider` or `nentry` item to connect it to the prototype interface knob number N. The knob [0..1] range will be mapped to the slider/nentry [min..max] range. 20 | - `[light_red|green|red:N]` (with N from 1 to 6) has to be used in a `vbargraph` or `hbargraph` item to connect it to the prototype interface light number N. 21 | - `[switchlight_red|green|red:N]` (with N from 1 to 6) has to be used in a `vbargraph` or `hbargraph` to connect it to the prototype interface switchLight number N. 22 | 23 | Other metadata: 24 | - `[scale:lin|log|exp]` metadata is implemented. 25 | 26 | The [faust_libraries/rack.lib](https://github.com/sletz/VCV-Prototype/blob/master/faust_libraries/rack.lib) Faust library contains useful functions to convert VC signals, and can be enriched if needed. 27 | 28 | ## DSP examples 29 | 30 | Here is a simple example showing how oscillators can be controlled by UI items: 31 | 32 | ``` 33 | import("stdfaust.lib"); 34 | 35 | // UI controllers connected using metadata 36 | freq = hslider("freq [knob:1]", 200, 50, 5000, 0.01); 37 | gain = hslider("gain [knob:2]", 0.5, 0, 1, 0.01); 38 | gate = button("gate [switch:1]"); 39 | 40 | // DSP processor 41 | process = os.osc(freq) * gain, os.sawtooth(freq) * gain * gate; 42 | ``` 43 | 44 | Some additional files can be seen in the examples folder: 45 | 46 | - [synth.dsp](https://github.com/VCVRack/VCV-Prototype/blob/v1/examples/synth.dsp) demonstrates how to use all different VCV Prototype UI items 47 | - [organ.dsp](https://github.com/VCVRack/VCV-Prototype/blob/v1/examples/organ.dsp) demonstrates a MIDI controllable additive synthesis based organ 48 | - [physicalmodel.dsp](https://github.com/VCVRack/VCV-Prototype/blob/v1/examples/physicalmodel.dsp) demonstrates a modal synthesis based bell connected to a reverb 49 | -------------------------------------------------------------------------------- /LICENSE-dist.txt: -------------------------------------------------------------------------------- 1 | # Duktape 2 | 3 | Copyright (c) 2013-2019 by Duktape authors (see AUTHORS.rst) 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | All **source code** is copyright © 2019-2020 Andrew Belt and VCV Prototype contributors. 2 | 3 | This program is free software: you can redistribute it and/or modify it under the terms of the [GNU General Public License](https://www.gnu.org/licenses/gpl-3.0.en.html) as published by the [Free Software Foundation](https://www.fsf.org/), either version 3 of the License, or (at your option) any later version. 4 | 5 | All **example scripts** in the `examples` directory are licensed under [CC0-1.0](https://creativecommons.org/publicdomain/zero/1.0/legalcode) (public domain). 6 | 7 | The **panel graphics** in the `res` directory are copyright © 2019 [Grayscale](http://grayscale.info/) and licensed under [CC BY-NC-ND 4.0](https://creativecommons.org/licenses/by-nc-nd/4.0/). 8 | You may not distribute modified adaptations of these graphics. 9 | 10 | **Dependencies** included in the binary distributable may have other licenses. 11 | See [LICENSE-dist.txt](LICENSE-dist.txt) for a full list. -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | RACK_DIR ?= ../.. 2 | 3 | FLAGS += -Idep/include 4 | CFLAGS += 5 | CXXFLAGS += 6 | 7 | LDFLAGS += 8 | SOURCES += src/Prototype.cpp 9 | 10 | DISTRIBUTABLES += res examples 11 | DISTRIBUTABLES += $(wildcard LICENSE*) 12 | 13 | include $(RACK_DIR)/arch.mk 14 | 15 | DUKTAPE ?= 0 16 | QUICKJS ?= 1 17 | LUAJIT ?= 1 18 | PYTHON ?= 0 19 | SUPERCOLLIDER ?= 1 20 | VULT ?= 1 21 | LIBPD ?= 1 22 | FAUST ?= 1 23 | 24 | # Vult depends on both LuaJIT and QuickJS 25 | ifeq ($(VULT), 1) 26 | QUICKJS := 1 27 | LUAJIT := 1 28 | endif 29 | 30 | 31 | # Entropia File System Watcher 32 | ifdef ARCH_WIN 33 | efsw := dep/lib/efsw-static-release.lib 34 | else 35 | efsw := dep/lib/libefsw-static-release.a 36 | endif 37 | DEPS += $(efsw) 38 | OBJECTS += $(efsw) 39 | $(efsw): 40 | ifdef ARCH_WIN 41 | cd efsw && premake5 gmake 42 | cd efsw && $(MAKE) -C make/* config=release_x86_64 efsw-static-lib 43 | else 44 | cd efsw && premake4 gmake 45 | cd efsw && $(MAKE) -C make/* config=release efsw-static-lib 46 | endif 47 | mkdir -p dep/lib dep/include 48 | ifdef ARCH_WIN 49 | cd efsw && cp lib/efsw-static-release.lib $(DEP_PATH)/lib/ 50 | else 51 | cd efsw && cp lib/libefsw-static-release.a $(DEP_PATH)/lib/ 52 | endif 53 | cd efsw && cp -R include/efsw $(DEP_PATH)/include/ 54 | 55 | 56 | # Duktape 57 | ifeq ($(DUKTAPE), 1) 58 | SOURCES += src/DuktapeEngine.cpp 59 | duktape := dep/duktape-2.4.0/src/duktape.c 60 | DEPS += $(duktape) 61 | SOURCES += $(duktape) 62 | FLAGS += -Idep/duktape-2.4.0/src 63 | $(duktape): 64 | $(WGET) "https://duktape.org/duktape-2.4.0.tar.xz" 65 | $(SHA256) duktape-2.4.0.tar.xz 86a89307d1633b5cedb2c6e56dc86e92679fc34b05be551722d8cc69ab0771fc 66 | cd dep && $(UNTAR) ../duktape-2.4.0.tar.xz 67 | endif 68 | 69 | 70 | # QuickJS 71 | ifeq ($(QUICKJS), 1) 72 | SOURCES += src/QuickJSEngine.cpp 73 | quickjs := dep/lib/quickjs/libquickjs.a 74 | DEPS += $(quickjs) 75 | OBJECTS += $(quickjs) 76 | QUICKJS_MAKE_FLAGS += prefix="$(DEP_PATH)" 77 | ifdef ARCH_WIN 78 | QUICKJS_MAKE_FLAGS += CONFIG_WIN32=y 79 | endif 80 | $(quickjs): 81 | cd dep && git clone "https://github.com/JerrySievert/QuickJS.git" 82 | cd dep/QuickJS && git checkout 807adc8ca9010502853d471bd8331cdc1d376b94 83 | cd dep/QuickJS && $(MAKE) $(QUICKJS_MAKE_FLAGS) install 84 | endif 85 | 86 | 87 | # LuaJIT 88 | ifeq ($(LUAJIT), 1) 89 | SOURCES += src/LuaJITEngine.cpp 90 | luajit := dep/lib/libluajit-5.1.a 91 | OBJECTS += $(luajit) 92 | DEPS += $(luajit) 93 | $(luajit): 94 | $(WGET) "http://luajit.org/download/LuaJIT-2.0.5.tar.gz" 95 | $(SHA256) LuaJIT-2.0.5.tar.gz 874b1f8297c697821f561f9b73b57ffd419ed8f4278c82e05b48806d30c1e979 96 | cd dep && $(UNTAR) ../LuaJIT-2.0.5.tar.gz 97 | cd dep/LuaJIT-2.0.5 && $(MAKE) BUILDMODE=static PREFIX="$(DEP_PATH)" install 98 | endif 99 | 100 | 101 | # SuperCollider 102 | ifeq ($(SUPERCOLLIDER), 1) 103 | SOURCES += src/SuperColliderEngine.cpp 104 | FLAGS += -Idep/supercollider/include -Idep/supercollider/include/common -Idep/supercollider/lang -Idep/supercollider/common -Idep/supercollider/include/plugin_interface 105 | # FLAGS += -DSC_VCV_ENGINE_TIMING # uncomment to turn on timing printing while running 106 | supercollider := dep/supercollider/build/lang/libsclang.a 107 | OBJECTS += $(supercollider) 108 | DEPS += $(supercollider) 109 | DISTRIBUTABLES += dep/supercollider/SCClassLibrary 110 | DISTRIBUTABLES += support/supercollider_extensions 111 | SUPERCOLLIDER_CMAKE_FLAGS += -DSUPERNOVA=0 -DSC_EL=0 -DSC_VIM=0 -DSC_ED=0 -DSC_IDE=0 -DSC_ABLETON_LINK=0 -DSC_QT=0 -DCMAKE_BUILD_TYPE=Release -DSCLANG_SERVER=0 -DBUILD_TESTING=0 -DNO_LIBSNDFILE=1 112 | SUPERCOLLIDER_SUBMODULES += external_libraries/hidapi external_libraries/nova-simd external_libraries/nova-tt external_libraries/portaudio_sc_org external_libraries/yaml-cpp 113 | SUPERCOLLIDER_BRANCH := topic/vcv-prototype-support 114 | 115 | OBJECTS += dep/supercollider/build/external_libraries/libtlsf.a 116 | OBJECTS += dep/supercollider/build/external_libraries/hidapi/linux/libhidapi.a 117 | OBJECTS += dep/supercollider/build/external_libraries/hidapi/hidapi_parser/libhidapi_parser.a 118 | OBJECTS += dep/supercollider/build/external_libraries/libboost_thread_lib.a 119 | OBJECTS += dep/supercollider/build/external_libraries/libboost_system_lib.a 120 | OBJECTS += dep/supercollider/build/external_libraries/libboost_regex_lib.a 121 | OBJECTS += dep/supercollider/build/external_libraries/libboost_filesystem_lib.a 122 | OBJECTS += dep/supercollider/build/external_libraries/libyaml.a 123 | 124 | LDFLAGS += -lpthread -lasound -ludev 125 | 126 | $(supercollider): 127 | cd dep && git clone "https://github.com/supercollider/supercollider" --branch $(SUPERCOLLIDER_BRANCH) --depth 1 128 | cd dep/supercollider && git checkout 84b14d10d49edce6dd8303045a884fb7f2bc92e8 129 | cd dep/supercollider && git submodule update --depth 1 --init -- $(SUPERCOLLIDER_SUBMODULES) 130 | cd dep/supercollider && mkdir build 131 | cd dep/supercollider/build && $(CMAKE) .. $(SUPERCOLLIDER_CMAKE_FLAGS) 132 | cd dep/supercollider/build && $(MAKE) libsclang 133 | endif 134 | 135 | 136 | # Python 137 | ifeq ($(PYTHON), 1) 138 | SOURCES += src/PythonEngine.cpp 139 | # Note this is a dynamic library, not static. 140 | python := dep/lib/libpython3.8.so.1.0 141 | DEPS += $(python) $(numpy) 142 | FLAGS += -Idep/include/python3.8 143 | # TODO Test these flags on all platforms 144 | # Make dynamic linker look in the plugin folder for libpython. 145 | LDFLAGS += -Wl,-rpath,'$$ORIGIN'/dep/lib 146 | LDFLAGS += -Ldep/lib -lpython3.8 147 | LDFLAGS += -lcrypt -lpthread -ldl -lutil -lm 148 | DISTRIBUTABLES += $(python) 149 | DISTRIBUTABLES += dep/lib/python3.8 150 | $(python): 151 | $(WGET) "https://www.python.org/ftp/python/3.8.0/Python-3.8.0.tar.xz" 152 | $(SHA256) Python-3.8.0.tar.xz b356244e13fb5491da890b35b13b2118c3122977c2cd825e3eb6e7d462030d84 153 | cd dep && $(UNTAR) ../Python-3.8.0.tar.xz 154 | cd dep/Python-3.8.0 && $(CONFIGURE) --build=$(MACHINE) --enable-shared --enable-optimizations 155 | cd dep/Python-3.8.0 && $(MAKE) build_all 156 | cd dep/Python-3.8.0 && $(MAKE) install 157 | 158 | numpy := dep/lib/python3.8/site-packages/numpy 159 | FLAGS += -Idep/lib/python3.8/site-packages/numpy/core/include 160 | $(numpy): $(python) 161 | $(WGET) "https://github.com/numpy/numpy/releases/download/v1.17.3/numpy-1.17.3.tar.gz" 162 | $(SHA256) numpy-1.17.3.tar.gz c93733dbebc2599d2747ceac4b18825a73767d289176ed8e02090325656d69aa 163 | cd dep && $(UNTAR) ../numpy-1.17.3.tar.gz 164 | # Don't try to find an external BLAS and LAPACK library. 165 | # Don't install to an egg folder. 166 | # Make sure to use our built Python. 167 | cd dep/numpy-1.17.3 && LD_LIBRARY_PATH=../lib NPY_BLAS_ORDER= NPY_LAPACK_ORDER= "$(DEP_PATH)"/bin/python3.8 setup.py build -j4 install --single-version-externally-managed --root=/ 168 | 169 | # scipy: $(numpy) 170 | # $(WGET) "https://github.com/scipy/scipy/releases/download/v1.3.1/scipy-1.3.1.tar.xz" 171 | # $(SHA256) scipy-1.3.1.tar.xz 326ffdad79f113659ed0bca80f5d0ed5e28b2e967b438bb1f647d0738073a92e 172 | # cd dep && $(UNTAR) ../scipy-1.3.1.tar.xz 173 | # cd dep/scipy-1.3.1 && "$(DEP_PATH)"/bin/python3.7 setup.py build -j4 install 174 | endif 175 | 176 | # # Julia 177 | # julia := dep/lib/libjulia.a 178 | # DEPS += $(julia) 179 | # $(julia): 180 | # $(WGET) "https://github.com/JuliaLang/julia/releases/download/v1.2.0/julia-1.2.0-full.tar.gz" 181 | # $(SHA256) julia-1.2.0-full.tar.gz 2419b268fc5c3666dd9aeb554815fe7cf9e0e7265bc9b94a43957c31a68d9184 182 | # cd dep && $(UNTAR) ../julia-1.2.0-full.tar.gz 183 | 184 | # # Csound 185 | # csound := dep/lib/libcsound.a 186 | # DEPS += $(csound) 187 | # $(csound): 188 | # $(WGET) "https://github.com/csound/csound/archive/6.13.0.tar.gz" 189 | # $(SHA256) 6.13.0.tar.gz 183beeb3b720bfeab6cc8af12fbec0bf9fef2727684ac79289fd12d0dfee728b 190 | # cd dep && $(UNTAR) ../6.13.0.tar.gz 191 | 192 | # # LLVM 193 | # llvm := dep/lib/libllvm.a 194 | # DEPS += $(llvm) 195 | # $(llvm): 196 | # $(WGET) "https://github.com/llvm/llvm-project/releases/download/llvmorg-8.0.1/llvm-8.0.1.src.tar.xz" 197 | # $(SHA256) llvm-8.0.1.src.tar.xz 44787a6d02f7140f145e2250d56c9f849334e11f9ae379827510ed72f12b75e7 198 | # cd dep && $(UNTAR) ../llvm-8.0.1.src.tar.xz 199 | # cd dep/llvm-8.0.1.src && mkdir -p build 200 | # cd dep/llvm-8.0.1.src/build && $(CMAKE) .. 201 | # cd dep/llvm-8.0.1.src/build && $(MAKE) 202 | # cd dep/llvm-8.0.1.src/build && $(MAKE) install 203 | 204 | 205 | # Vult 206 | ifeq ($(VULT), 1) 207 | SOURCES += src/VultEngine.cpp 208 | vult := dep/vult/vultc.h 209 | $(vult): 210 | cd dep && mkdir -p vult 211 | cd dep/vult && $(WGET) "https://github.com/modlfo/vult/releases/download/v0.4.12/vultc.h" 212 | $(SHA256) $(vult) 3e80f6d30defe7df2804568f0314dbb33e6bf69d53d18a804c8b8132cf5ad146 213 | FLAGS += -Idep/vult 214 | DEPS += $(vult) 215 | endif 216 | 217 | 218 | # LibPD 219 | ifeq ($(LIBPD), 1) 220 | libpd := dep/lib/libpd.a 221 | SOURCES += src/LibPDEngine.cpp 222 | OBJECTS += $(libpd) 223 | DEPS += $(libpd) 224 | FLAGS += -Idep/include/libpd -DHAVE_LIBDL 225 | 226 | ifdef ARCH_WIN 227 | # PD_INTERNAL leaves the function declarations for libpd unchanged 228 | # not specifying that flag would enable the "EXTERN __declspec(dllexport) extern" macro 229 | # which throws a linker error. I guess this macro should only be used for the windows 230 | # specific .dll dynamic linking format. 231 | # The corresponding #define resides in "m_pd.h" inside th Pure Data sources 232 | FLAGS += -DPD_INTERNAL -Ofast 233 | LDFLAGS += -Wl,--export-all-symbols 234 | LDFLAGS += -lws2_32 235 | endif 236 | 237 | $(libpd): 238 | cd dep && git clone "https://github.com/libpd/libpd.git" --recursive 239 | cd dep/libpd && git checkout 5772a612527f06597d44d195843307ad0e3578fe 240 | 241 | ifdef ARCH_MAC 242 | # libpd's Makefile is handmade, and it doesn't honor CFLAGS and LDFLAGS environments. 243 | # So in order for Mac 10.15 (for example) to make a build that works on Mac 10.7+, we have to manually add DEP_MAC_SDK_FLAGS to CFLAGS and LDFLAGS. 244 | # We can't just add the environment's CFLAGS/LDFLAGS because `-march=nocona` makes libpd segfault when initialized. 245 | # Perhaps inline assembly is used in libpd? Who knows. 246 | cd dep/libpd && $(MAKE) MULTI=true STATIC=true ADDITIONAL_CFLAGS='-DPD_LONGINTTYPE="long long" $(DEP_MAC_SDK_FLAGS) -stdlib=libc++' ADDITIONAL_LDFLAGS='$(DEP_MAC_SDK_FLAGS) -stdlib=libc++' 247 | else 248 | cd dep/libpd && $(MAKE) MULTI=true STATIC=true ADDITIONAL_CFLAGS='-DPD_LONGINTTYPE="long long"' 249 | endif 250 | cd dep/libpd && $(MAKE) install prefix="$(DEP_PATH)" 251 | endif 252 | 253 | 254 | # Faust 255 | ifeq ($(FAUST), 1) 256 | libfaust := dep/lib/libfaust.a 257 | SOURCES += src/FaustEngine.cpp 258 | OBJECTS += $(libfaust) 259 | DEPS += $(libfaust) 260 | FLAGS += -DINTERP 261 | DISTRIBUTABLES += faust_libraries 262 | 263 | # Test using LLVM 264 | #LDFLAGS += -L/usr/local/lib -lfaust 265 | 266 | # Test using MIR 267 | #LDFLAGS += -L/usr/local/lib -lfaust dep/lib/mir-gen.o dep/lib/mir.o 268 | 269 | $(libfaust): 270 | cd dep && git clone "https://github.com/grame-cncm/faust.git" --recursive 271 | cd dep/faust && git checkout 1dfc452a8250f3123b5100edf8c882e1cea407a1 272 | cd dep/faust/build && make cmake BACKENDS=interp.cmake TARGETS=interp.cmake 273 | cd dep/faust/build && make install PREFIX="$(DEP_PATH)" 274 | cp -r dep/faust/libraries/* faust_libraries/ 275 | rm -rf faust_libraries/doc 276 | rm -rf faust_libraries/docs 277 | 278 | endif 279 | 280 | include $(RACK_DIR)/plugin.mk 281 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # VCV Prototype 2 | 3 | Scripting language host for [VCV Rack](https://vcvrack.com/) containing: 4 | - 6 inputs 5 | - 6 outputs 6 | - 6 knobs 7 | - 6 lights (RGB LEDs) 8 | - 6 switches with RGB LEDs 9 | 10 | Supported scripting languages: 11 | - JavaScript (ES2020) (.js) 12 | - [Lua](https://www.lua.org/) (.lua) 13 | - [Vult](https://github.com/modlfo/vult) (.vult) 14 | - [Pure Data](https://puredata.info) (.pd) 15 | - [Faust](https://faust.grame.fr) (.dsp) 16 | - [SuperCollider](https://supercollider.github.io) (.scd) 17 | - [Add your own below](#adding-a-script-engine) 18 | 19 | [Discussion thread](https://community.vcvrack.com/t/vcv-prototype/3271) 20 | 21 | ## Scripting API 22 | 23 | This is the reference API for the JavaScript script engine, along with default property values. 24 | Other script engines may vary in their syntax (e.g. `block.inputs[i][j]` vs `block.getInput(i, j)` vs `input(i, j)`), but the functionality should be similar. 25 | 26 | ```js 27 | /** Display message on LED display. 28 | */ 29 | display(message) 30 | 31 | /** Skip this many sample frames before running process(). 32 | For CV generators and processors, 256 is reasonable. 33 | For sequencers, 32 is reasonable since process() will be called every 0.7ms with 34 | a 44100kHz sample rate, which will capture 1ms-long triggers. 35 | For audio generators and processors, 1 is recommended, but use `bufferSize` below. 36 | If this is too slow for your purposes, consider writing a C++ plugin, since 37 | native VCV Rack plugins have 10-100x better performance. 38 | */ 39 | config.frameDivider // 32 40 | 41 | /** Instead of calling process() every sample frame, hold this many input/output 42 | voltages in a buffer and call process() when it is full. 43 | This decreases CPU usage, since processing buffers is faster than processing one 44 | frame at a time. 45 | The total latency of your script in seconds is 46 | `config.frameDivider * config.bufferSize * block.sampleTime`. 47 | */ 48 | config.bufferSize // 1 49 | 50 | /** Called when the next block is ready to be processed. 51 | */ 52 | function process(block) { 53 | /** Engine sample rate in Hz. Read-only. 54 | */ 55 | block.sampleRate 56 | 57 | /** Engine sample timestep in seconds. Equal to `1 / sampleRate`. Read-only. 58 | Note that the actual time between process() calls is 59 | `block.sampleTime * config.frameDivider`. 60 | */ 61 | block.sampleTime 62 | 63 | /** The actual size of the input/output buffers. 64 | */ 65 | block.bufferSize 66 | 67 | /** Voltage of the input port of column `i`. Read-only. 68 | */ 69 | block.inputs[i][bufferIndex] // 0.0 70 | 71 | /** Voltage of the output port of column `i`. Read/write. 72 | */ 73 | block.outputs[i][bufferIndex] // 0.0 74 | 75 | /** Value of the knob of column `i`. Between 0 and 1. Read/write. 76 | */ 77 | block.knobs[i] // 0.0 78 | 79 | /** Pressed state of the switch of column `i`. Read-only. 80 | */ 81 | block.switches[i] // false 82 | 83 | /** Brightness of the RGB LED of column `i`, between 0 and 1. Read/write. 84 | */ 85 | block.lights[i][0] // 0.0 (red) 86 | block.lights[i][1] // 0.0 (green) 87 | block.lights[i][2] // 0.0 (blue) 88 | 89 | /** Brightness of the switch RGB LED of column `i`. Read/write. 90 | */ 91 | block.switchLights[i][0] // 0.0 (red) 92 | block.switchLights[i][1] // 0.0 (green) 93 | block.switchLights[i][2] // 0.0 (blue) 94 | } 95 | ``` 96 | 97 | *The Vult API is slightly different than Prototype's scripting API. 98 | See `examples/template.vult` for a reference of the Vult API.* 99 | 100 | ## Build dependencies 101 | 102 | Set up your build environment like described here, including the dependencies: https://vcvrack.com/manual/Building 103 | 104 | Additionally: 105 | 106 | ### Windows 107 | ```bash 108 | pacman -S mingw-w64-x86_64-premake 109 | ``` 110 | 111 | ### Mac 112 | ```bash 113 | brew install premake 114 | ``` 115 | 116 | ### Ubuntu 16.04+ 117 | ```bash 118 | sudo apt install premake4 119 | ``` 120 | 121 | ### Arch Linux 122 | ```bash 123 | sudo pacman -S premake 124 | ``` 125 | 126 | ## Build 127 | ### Add path to Rack-SDK 128 | ```bash 129 | export RACK_DIR=/set/path/to/Rack-SDK/ 130 | ``` 131 | 132 | ### load submodules 133 | ```bash 134 | git submodule update --init --recursive 135 | ``` 136 | 137 | ### Make 138 | ```bash 139 | make dep 140 | make 141 | ``` 142 | 143 | ## Adding a script engine 144 | 145 | - Add your scripting language library to the build system so it builds with `make dep`, following the Duktape example in `Makefile`. 146 | - Create a `MyEngine.cpp` file (for example) in `src/` with a `ScriptEngine` subclass defining the virtual methods, using `src/DuktapeEngine.cpp` as an example. 147 | - Build and test the plugin. 148 | - Add a few example scripts and tests to `examples/`. These will be included in the plugin package for the user. 149 | - Add your name to the Contributors list below. 150 | - Send a pull request. Once merged, you will be added as a repo maintainer. Be sure to "watch" this repo to be notified of bug reports and feature requests for your engine. 151 | 152 | ## Contributors 153 | 154 | - [Wes Milholen](https://grayscale.info/): panel design 155 | - [Andrew Belt](https://github.com/AndrewBelt): host code, Duktape (JavaScript, not used), LuaJIT (Lua), Python (in development) 156 | - [Jerry Sievert](https://github.com/JerrySievert): QuickJS (JavaScript) 157 | - [Leonardo Laguna Ruiz](https://github.com/modlfo): Vult 158 | - [CHAIR](https://chair.audio) (Clemens Wegener, Max Neupert): libpd 159 | - [GRAME](https://github.com/grame-cncm): Faust 160 | - [Moss Heim](https://github.com/mossheim): Supercollider 161 | - add your name here 162 | -------------------------------------------------------------------------------- /examples/factorial_router.js: -------------------------------------------------------------------------------- 1 | /** 2 | Factorial router 3 | by Andrew Belt 4 | 5 | There are 120 ways to route 5 inputs into 5 outputs. 6 | This uses input 6 as a CV control to choose the particular permutation. 7 | The CV is split into 120 intervals, each corresponding to a permutation of the inputs 1 through 5. 8 | */ 9 | 10 | function permutation(arr, k) { 11 | let n = arr.length 12 | // Clone array 13 | arr = arr.slice(0) 14 | // Compute n! 15 | let factorial = 1 16 | for (let i = 2; i <= n; i++) { 17 | factorial *= i 18 | } 19 | 20 | // Build permutation array by selecting the j'th element from the remaining elements until all are chosen. 21 | let perm = [] 22 | for (let i = n; i >= 1; i--) { 23 | factorial /= i 24 | let j = Math.floor(k / factorial) 25 | k %= factorial 26 | let el = arr[j] 27 | arr.splice(j, 1) 28 | perm.push(el) 29 | } 30 | 31 | return perm 32 | } 33 | 34 | config.bufferSize = 16 35 | 36 | function process(block) { 37 | // Get factorial index from input 6 38 | let k = Math.floor(block.inputs[5][0] / 10 * 120) 39 | k = Math.min(Math.max(k, 0), 120 - 1) 40 | // Get index set permutation 41 | let perm = permutation([1, 2, 3, 4, 5], k) 42 | display(perm.join(", ")) 43 | for (let i = 0; i < 5; i++) { 44 | // Permute input i 45 | for (let j = 0; j < block.bufferSize; j++) { 46 | block.outputs[i][j] = block.inputs[perm[i] - 1][j] 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /examples/gain.js: -------------------------------------------------------------------------------- 1 | // Simplest possible script using all variables, demonstrating buffering 2 | // by Andrew Belt 3 | 4 | config.frameDivider = 1 5 | config.bufferSize = 32 6 | 7 | function process(block) { 8 | // Loop through each column 9 | for (let i = 0; i < 6; i++) { 10 | // Get gain knob 11 | let gain = block.knobs[i] 12 | // Set gain light (red = 0) 13 | block.lights[i][0] = gain 14 | // Check mute switch 15 | if (block.switches[i]) { 16 | // Mute output 17 | gain = 0 18 | // Enable mute light (red = 0) 19 | block.switchLights[i][0] = 1 20 | } 21 | else { 22 | // Disable mute light 23 | block.switchLights[i][0] = 0 24 | } 25 | // Iterate input/output buffer 26 | for (let j = 0; j < block.bufferSize; j++) { 27 | // Get input 28 | let x = block.inputs[i][j] 29 | // Apply gain to input 30 | let y = x * gain 31 | // Set output 32 | block.outputs[i][j] = y 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /examples/gain.lua: -------------------------------------------------------------------------------- 1 | -- Simplest possible script using all variables, demonstrating buffering 2 | -- by Andrew Belt 3 | 4 | config.frameDivider = 1 5 | config.bufferSize = 32 6 | 7 | function process(block) 8 | -- Loop through each column 9 | for i=1,6 do 10 | -- Get gain knob 11 | gain = block.knobs[i] 12 | -- Set gain light (red = 1) 13 | block.lights[i][1] = gain 14 | -- Check mute switch 15 | if block.switches[i] then 16 | -- Mute output 17 | gain = 0 18 | -- Enable mute light (red = 1) 19 | block.switchLights[i][1] = 1 20 | else 21 | -- Disable mute light 22 | block.switchLights[i][1] = 0 23 | end 24 | -- Iterate input/output buffer 25 | for j=1,block.bufferSize do 26 | -- Get input 27 | x = block.inputs[i][j] 28 | -- Apply gain to input 29 | y = x * gain 30 | -- Set output 31 | block.outputs[i][j] = y 32 | end 33 | end 34 | end 35 | -------------------------------------------------------------------------------- /examples/gain.pd: -------------------------------------------------------------------------------- 1 | #N canvas 837 209 813 659 12; 2 | #X obj 104 30 adc~ 1 2 3 4 5 6, f 47; 3 | #X obj 105 261 dac~ 1 2 3 4 5 6, f 47; 4 | #X obj 104 98 *~ 1; 5 | #X obj 169 98 *~ 1; 6 | #X obj 233 98 *~ 1; 7 | #X obj 299 98 *~ 1; 8 | #X obj 363 98 *~ 1; 9 | #X obj 429 98 *~ 1; 10 | #X msg 171 365 L3 \$1 0 0; 11 | #X msg 247 365 L4 \$1 0 0; 12 | #X msg 397 365 L6 \$1 0 0; 13 | #X msg 21 365 L1 \$1 0 0; 14 | #X msg 96 365 L2 \$1 0 0; 15 | #X msg 322 365 L5 \$1 0 0; 16 | #X obj 105 229 *~ 1; 17 | #X obj 170 228 *~ 1; 18 | #X obj 234 228 *~ 1; 19 | #X obj 300 228 *~ 1; 20 | #X obj 364 228 *~ 1; 21 | #X obj 430 228 *~ 1; 22 | #X msg 21 521 S1 \$1 0 0; 23 | #X msg 96 522 S2 \$1 0 0; 24 | #X msg 171 522 S3 \$1 0 0; 25 | #X msg 247 523 S4 \$1 0 0; 26 | #X msg 322 523 S5 \$1 0 0; 27 | #X msg 397 523 S6 \$1 0 0; 28 | #X obj 129 70 route K1 K2 K3 K4 K5 K6, f 56; 29 | #X obj 130 169 route S1 S2 S3 S4 S5 S6, f 56; 30 | #X obj 130 198 == 0; 31 | #X obj 195 198 == 0; 32 | #X obj 259 198 == 0; 33 | #X obj 325 198 == 0; 34 | #X obj 389 198 == 0; 35 | #X obj 455 198 == 0; 36 | #X obj 21 336 route K1 K2 K3 K4 K5 K6, f 65; 37 | #X obj 21 492 route S1 S2 S3 S4 S5 S6, f 65; 38 | #X text 541 368 Just using the red channels in the RGB triplet for 39 | the LED., f 35; 40 | #X text 542 492 Same for the switch., f 35; 41 | #X obj 21 569 print toRack; 42 | #X obj 20 30 r fromRack; 43 | #X obj 19 134 r fromRack; 44 | #X obj 21 307 r fromRack; 45 | #X obj 21 422 print toRack; 46 | #X obj 21 463 r fromRack; 47 | #X text 539 52 Usually we'd interpolate here with line~ but VCVRack 48 | is already sending one message per sample so there seems hardly a point 49 | to complicate this example., f 38; 50 | #X connect 0 0 2 0; 51 | #X connect 0 1 3 0; 52 | #X connect 0 2 4 0; 53 | #X connect 0 3 5 0; 54 | #X connect 0 4 6 0; 55 | #X connect 0 5 7 0; 56 | #X connect 2 0 14 0; 57 | #X connect 3 0 15 0; 58 | #X connect 4 0 16 0; 59 | #X connect 5 0 17 0; 60 | #X connect 6 0 18 0; 61 | #X connect 7 0 19 0; 62 | #X connect 8 0 42 0; 63 | #X connect 9 0 42 0; 64 | #X connect 10 0 42 0; 65 | #X connect 11 0 42 0; 66 | #X connect 12 0 42 0; 67 | #X connect 13 0 42 0; 68 | #X connect 14 0 1 0; 69 | #X connect 15 0 1 1; 70 | #X connect 16 0 1 2; 71 | #X connect 17 0 1 3; 72 | #X connect 18 0 1 4; 73 | #X connect 19 0 1 5; 74 | #X connect 20 0 38 0; 75 | #X connect 21 0 38 0; 76 | #X connect 22 0 38 0; 77 | #X connect 23 0 38 0; 78 | #X connect 24 0 38 0; 79 | #X connect 25 0 38 0; 80 | #X connect 26 0 2 1; 81 | #X connect 26 1 3 1; 82 | #X connect 26 2 4 1; 83 | #X connect 26 3 5 1; 84 | #X connect 26 4 6 1; 85 | #X connect 26 5 7 1; 86 | #X connect 27 0 28 0; 87 | #X connect 27 1 29 0; 88 | #X connect 27 2 30 0; 89 | #X connect 27 3 31 0; 90 | #X connect 27 4 32 0; 91 | #X connect 27 5 33 0; 92 | #X connect 28 0 14 1; 93 | #X connect 29 0 15 1; 94 | #X connect 30 0 16 1; 95 | #X connect 31 0 17 1; 96 | #X connect 32 0 18 1; 97 | #X connect 33 0 19 1; 98 | #X connect 34 0 11 0; 99 | #X connect 34 1 12 0; 100 | #X connect 34 2 8 0; 101 | #X connect 34 3 9 0; 102 | #X connect 34 4 13 0; 103 | #X connect 34 5 10 0; 104 | #X connect 35 0 20 0; 105 | #X connect 35 1 21 0; 106 | #X connect 35 2 22 0; 107 | #X connect 35 3 23 0; 108 | #X connect 35 4 24 0; 109 | #X connect 35 5 25 0; 110 | #X connect 39 0 26 0; 111 | #X connect 40 0 27 0; 112 | #X connect 41 0 34 0; 113 | #X connect 43 0 35 0; 114 | -------------------------------------------------------------------------------- /examples/gain.scd: -------------------------------------------------------------------------------- 1 | // Simplest possible script using all variables, demonstrating buffering 2 | // by Andrew Belt 3 | // adapted for SC by Brian Heim 4 | 5 | ~vcv_frameDivider = 1; 6 | ~vcv_bufferSize = 32; 7 | 8 | ~vcv_process = { |block| 9 | // Loop through each row 10 | VcvPrototypeProcessBlock.numRows.do { |i| 11 | // Get gain knob 12 | var gain = block.knobs[i]; 13 | // Set gain light (red = 0) 14 | block.lights[i][0] = gain; 15 | 16 | // Check mute switch 17 | block.switchLights[i][0] = if (block.switches[i]) { 18 | // Mute output 19 | gain = 0; 20 | // Enable mute light (red = 0) 21 | 1 22 | } { 23 | // Disable mute light 24 | 0 25 | }; 26 | 27 | // Iterate input/output buffer 28 | block.outputs[i] = block.inputs[i] * gain; 29 | }; 30 | 31 | block 32 | } 33 | -------------------------------------------------------------------------------- /examples/organ.dsp: -------------------------------------------------------------------------------- 1 | import("stdfaust.lib"); 2 | import("rack.lib"); 3 | 4 | vol = hslider("vol [knob:1]", 0.3, 0, 10, 0.01); 5 | pan = hslider("pan [knob:2]", 0.5, 0, 1, 0.01); 6 | 7 | attack = hslider("attack", 0.01, 0, 1, 0.001); 8 | decay = hslider("decay", 0.3, 0, 1, 0.001); 9 | sustain = hslider("sustain", 0.5, 0, 1, 0.01); 10 | release = hslider("release", 0.2, 0, 1, 0.001); 11 | 12 | panner(c) = _ <: *(1-c), *(c); 13 | 14 | voice(freq) = os.osc(freq) + 0.5*os.osc(2*freq) + 0.25*os.osc(3*freq); 15 | 16 | /* 17 | Additive synth: 3 sine oscillators with adsr envelop. 18 | Use the 3 first VC inputs to control pitch, gate and velocity. 19 | */ 20 | 21 | process(pitch, gate, vel) = voice(freq) * en.adsr(attack, decay, sustain, release, gate) * vel : *(vol) : panner(pan) 22 | with { 23 | freq = cv_pitch2freq(pitch); 24 | }; -------------------------------------------------------------------------------- /examples/physicalmodel.dsp: -------------------------------------------------------------------------------- 1 | import("stdfaust.lib"); 2 | import("rack.lib"); 3 | 4 | frenchBell_ui = pm.frenchBell(strikePosition,strikeCutoff,strikeSharpness,gain,gate) 5 | with { 6 | strikePosition = nentry("v:frenchBell/[0]strikePosition", 0,0,4,1); 7 | strikeCutoff = hslider("v:frenchBell/[1]strikeCutOff", 6500,20,20000,1); 8 | strikeSharpness = hslider("v:frenchBell/[2]strikeSharpness", 0.5,0.01,5,0.01); 9 | 10 | // Connection with VCV knob and switch 11 | gain = hslider("v:frenchBell/[3]gain [knob:1]",1,0,1,0.01); 12 | gate = button("v:frenchBell/[4]gate [switch:1]"); 13 | }; 14 | 15 | freeverb_demo = _,_ <: (*(g)*fixedgain,*(g)*fixedgain : 16 | re.stereo_freeverb(combfeed, allpassfeed, damping, spatSpread)), 17 | *(1-g), *(1-g) :> _,_ 18 | with{ 19 | scaleroom = 0.28; 20 | offsetroom = 0.7; 21 | allpassfeed = 0.5; 22 | scaledamp = 0.4; 23 | fixedgain = 0.1; 24 | origSR = 44100; 25 | 26 | parameters(x) = hgroup("Freeverb",x); 27 | knobGroup(x) = parameters(vgroup("[0]",x)); 28 | 29 | // Connection with VCV knobs 30 | damping = knobGroup(vslider("[0] Damp [knob:2] [style: knob] [tooltip: Somehow control the 31 | density of the reverb.]",0.5, 0, 1, 0.025)*scaledamp*origSR/ma.SR); 32 | 33 | combfeed = knobGroup(vslider("[1] RoomSize [knob:3] [style: knob] [tooltip: The room size 34 | between 0 and 1 with 1 for the largest room.]", 0.5, 0, 1, 0.025)*scaleroom* 35 | origSR/ma.SR + offsetroom); 36 | 37 | spatSpread = knobGroup(vslider("[2] Stereo Spread [knob:4] [style: knob] [tooltip: Spatial 38 | spread between 0 and 1 with 1 for maximum spread.]",0.5,0,1,0.01)*46*ma.SR/origSR 39 | : int); 40 | g = parameters(vslider("[1] Wet [knob:5] [tooltip: The amount of reverb applied to the signal 41 | between 0 and 1 with 1 for the maximum amount of reverb.]", 0.3333, 0, 1, 0.025)); 42 | }; 43 | 44 | process = frenchBell_ui <: freeverb_demo; 45 | -------------------------------------------------------------------------------- /examples/rainbow.js: -------------------------------------------------------------------------------- 1 | // Rainbow RGB LED example 2 | // by Andrew Belt 3 | 4 | // Call process() every 256 audio samples 5 | config.frameDivider = 256 6 | 7 | 8 | // From https://en.wikipedia.org/wiki/HSL_and_HSV#HSV_to_RGB 9 | function hsvToRgb(h, s, v) { 10 | h *= 6 11 | let c = v * s 12 | let x = c * (1 - Math.abs(h % 2 - 1)) 13 | let rgb; 14 | if (h < 1) rgb = [c, x, 0] 15 | else if (h < 2) rgb = [x, c, 0] 16 | else if (h < 3) rgb = [0, c, x] 17 | else if (h < 4) rgb = [0, x, c] 18 | else if (h < 5) rgb = [x, 0, c] 19 | else rgb = [c, 0, x] 20 | let m = v - c 21 | rgb[0] += m 22 | rgb[1] += m 23 | rgb[2] += m 24 | return rgb 25 | } 26 | 27 | 28 | let phase = 0 29 | function process(block) { 30 | phase += block.sampleTime * config.frameDivider * 0.5 31 | phase %= 1 32 | 33 | for (let i = 0; i < 6; i++) { 34 | let h = (1 - i / 6 + phase) % 1 35 | let rgb = hsvToRgb(h, 1, 1) 36 | for (let c = 0; c < 3; c++) { 37 | block.lights[i][c] = rgb[c] 38 | block.switchLights[i][c] = rgb[c] 39 | } 40 | block.outputs[i][0] = Math.sin(2 * Math.PI * h) * 5 + 5 41 | } 42 | } 43 | 44 | display("Hello, world!") 45 | -------------------------------------------------------------------------------- /examples/rainbow.lua: -------------------------------------------------------------------------------- 1 | -- Rainbow RGB LED example 2 | -- by Andrew Belt 3 | 4 | -- Call process() every 256 audio samples 5 | config.frameDivider = 256 6 | 7 | 8 | -- From https://en.wikipedia.org/wiki/HSL_and_HSV#HSV_to_RGB 9 | function hsvToRgb(h, s, v) 10 | h = h * 6 11 | c = v * s 12 | x = c * (1 - math.abs(h % 2 - 1)) 13 | if (h < 1) then rgb = {c, x, 0} 14 | elseif (h < 2) then rgb = {x, c, 0} 15 | elseif (h < 3) then rgb = {0, c, x} 16 | elseif (h < 4) then rgb = {0, x, c} 17 | elseif (h < 5) then rgb = {x, 0, c} 18 | else rgb = {c, 0, x} 19 | end 20 | m = v - c 21 | rgb[1] = rgb[1] + m 22 | rgb[2] = rgb[2] + m 23 | rgb[3] = rgb[3] + m 24 | return rgb 25 | end 26 | 27 | 28 | phase = 0 29 | function process(block) 30 | phase = phase + block.sampleTime * config.frameDivider * 0.5 31 | phase = phase % 1 32 | 33 | for i=1,6 do 34 | h = (1 - i / 6 + phase) % 1 35 | rgb = hsvToRgb(h, 1, 1) 36 | for c=1,3 do 37 | block.lights[i][c] = rgb[c] 38 | block.switchLights[i][c] = rgb[c] 39 | end 40 | block.outputs[i][1] = math.sin(2 * math.pi * h) * 5 + 5 41 | end 42 | end 43 | 44 | display("Hello, world!") 45 | -------------------------------------------------------------------------------- /examples/rainbow.pd: -------------------------------------------------------------------------------- 1 | #N canvas 698 177 821 731 12; 2 | #N canvas 250 355 563 749 hsv2rgb 0; 3 | #X obj 20 64 route 0; 4 | #X msg 20 36 \$2 \$3 \$1; 5 | #X obj 20 552 f; 6 | #X obj 66 92 unpack 0 0 0, f 27; 7 | #X text 87 37 svh; 8 | #X obj 20 8 inlet hsv; 9 | #X obj 20 663 outlet rgb; 10 | #X obj 303 166 * 6; 11 | #X obj 284 213 % 6; 12 | #X obj 252 190 t f f; 13 | #X msg 252 166 0; 14 | #X text 308 213 i; 15 | #X obj 252 264 -; 16 | #X obj 66 501 *; 17 | #X obj 66 169 - 1; 18 | #X obj 66 196 * -1; 19 | #X obj 103 502 *; 20 | #X obj 103 399 - 1; 21 | #X obj 103 424 * -1; 22 | #X obj 103 377 *; 23 | #X obj 252 288 t f f; 24 | #X obj 282 311 - 1; 25 | #X obj 282 334 * -1; 26 | #X obj 140 503 *; 27 | #X obj 140 400 - 1; 28 | #X obj 140 425 * -1; 29 | #X obj 140 378 *; 30 | #X obj 66 142 t f f f, f 11; 31 | #X obj 66 535 pack 0 0 0 0, f 16; 32 | #N canvas 1219 718 529 336 select 0; 33 | #X obj 290 37 inlet; 34 | #X obj 16 281 outlet; 35 | #X obj 16 58 t b l, f 20; 36 | #X obj 152 176 unpack 0 0 0 0; 37 | #X obj 16 29 inlet p q t v; 38 | #X obj 16 123 i; 39 | #X obj 16 175 select 0 1 2 3 4 5; 40 | #X obj 16 239 pack 0 0 0; 41 | #X obj 16 209 f; 42 | #X obj 96 239 pack 0 0 0; 43 | #X obj 96 209 f; 44 | #X obj 176 239 pack 0 0 0; 45 | #X obj 176 209 f; 46 | #X obj 256 239 pack 0 0 0; 47 | #X obj 256 209 f; 48 | #X obj 336 239 pack 0 0 0; 49 | #X obj 336 209 f; 50 | #X obj 416 239 pack 0 0 0; 51 | #X obj 416 209 f; 52 | #X connect 0 0 5 1; 53 | #X connect 2 0 5 0; 54 | #X connect 2 1 3 0; 55 | #X connect 3 0 7 2; 56 | #X connect 3 0 9 2; 57 | #X connect 3 0 12 1; 58 | #X connect 3 0 14 1; 59 | #X connect 3 0 15 1; 60 | #X connect 3 0 17 1; 61 | #X connect 3 1 10 1; 62 | #X connect 3 1 13 1; 63 | #X connect 3 1 17 2; 64 | #X connect 3 2 7 1; 65 | #X connect 3 2 11 2; 66 | #X connect 3 2 16 1; 67 | #X connect 3 3 8 1; 68 | #X connect 3 3 9 1; 69 | #X connect 3 3 11 1; 70 | #X connect 3 3 13 2; 71 | #X connect 3 3 15 2; 72 | #X connect 3 3 18 1; 73 | #X connect 4 0 2 0; 74 | #X connect 5 0 6 0; 75 | #X connect 6 0 8 0; 76 | #X connect 6 1 10 0; 77 | #X connect 6 2 12 0; 78 | #X connect 6 3 14 0; 79 | #X connect 6 4 16 0; 80 | #X connect 6 5 18 0; 81 | #X connect 7 0 1 0; 82 | #X connect 8 0 7 0; 83 | #X connect 9 0 1 0; 84 | #X connect 10 0 9 0; 85 | #X connect 11 0 1 0; 86 | #X connect 12 0 11 0; 87 | #X connect 13 0 1 0; 88 | #X connect 14 0 13 0; 89 | #X connect 15 0 1 0; 90 | #X connect 16 0 15 0; 91 | #X connect 17 0 1 0; 92 | #X connect 18 0 17 0; 93 | #X restore 66 560 pd select; 94 | #X f 28; 95 | #X msg 20 621 \$1 \$1 \$1; 96 | #X obj 20 593 clip 0 1; 97 | #X obj 66 117 clip 0 1; 98 | #X obj 175 119 clip 0 1; 99 | #X obj 252 118 clip 0 1; 100 | #X obj 252 142 select 1; 101 | #X obj 175 462 t f f f f; 102 | #X obj 260 237 t f f; 103 | #X text 26 699 taken from the GEM helpfile hsv2rgb-help; 104 | #X connect 0 0 2 0; 105 | #X connect 0 1 3 0; 106 | #X connect 1 0 0 0; 107 | #X connect 2 0 31 0; 108 | #X connect 3 0 32 0; 109 | #X connect 3 1 33 0; 110 | #X connect 3 2 34 0; 111 | #X connect 5 0 1 0; 112 | #X connect 7 0 9 0; 113 | #X connect 8 0 37 0; 114 | #X connect 9 0 12 0; 115 | #X connect 9 1 8 0; 116 | #X connect 10 0 9 0; 117 | #X connect 12 0 20 0; 118 | #X connect 13 0 28 0; 119 | #X connect 14 0 15 0; 120 | #X connect 15 0 13 0; 121 | #X connect 16 0 28 1; 122 | #X connect 17 0 18 0; 123 | #X connect 18 0 16 0; 124 | #X connect 19 0 17 0; 125 | #X connect 20 0 19 1; 126 | #X connect 20 1 21 0; 127 | #X connect 21 0 22 0; 128 | #X connect 22 0 26 1; 129 | #X connect 23 0 28 2; 130 | #X connect 24 0 25 0; 131 | #X connect 25 0 23 0; 132 | #X connect 26 0 24 0; 133 | #X connect 27 0 14 0; 134 | #X connect 27 1 19 0; 135 | #X connect 27 2 26 0; 136 | #X connect 28 0 29 0; 137 | #X connect 29 0 6 0; 138 | #X connect 30 0 6 0; 139 | #X connect 31 0 30 0; 140 | #X connect 32 0 27 0; 141 | #X connect 33 0 36 0; 142 | #X connect 34 0 35 0; 143 | #X connect 35 0 10 0; 144 | #X connect 35 1 7 0; 145 | #X connect 36 0 13 1; 146 | #X connect 36 1 16 1; 147 | #X connect 36 2 23 1; 148 | #X connect 36 3 28 3; 149 | #X connect 37 0 29 1; 150 | #X connect 37 1 12 1; 151 | #X restore 29 343 pd hsv2rgb; 152 | #X msg 29 400 S1 \$1 \$2 \$3; 153 | #X obj 30 16 loadbang; 154 | #X msg 30 41 1; 155 | #X msg 29 315 \$1 1 1; 156 | #X obj 30 67 tgl 15 0 empty empty empty 17 7 0 10 -262144 -1 -1 1 1 157 | ; 158 | #X obj 30 143 line; 159 | #N canvas 250 355 563 749 hsv2rgb 0; 160 | #X obj 20 64 route 0; 161 | #X msg 20 36 \$2 \$3 \$1; 162 | #X obj 20 552 f; 163 | #X obj 66 92 unpack 0 0 0, f 27; 164 | #X text 87 37 svh; 165 | #X obj 20 8 inlet hsv; 166 | #X obj 20 663 outlet rgb; 167 | #X obj 303 166 * 6; 168 | #X obj 284 213 % 6; 169 | #X obj 252 190 t f f; 170 | #X msg 252 166 0; 171 | #X text 308 213 i; 172 | #X obj 252 264 -; 173 | #X obj 66 501 *; 174 | #X obj 66 169 - 1; 175 | #X obj 66 196 * -1; 176 | #X obj 103 502 *; 177 | #X obj 103 399 - 1; 178 | #X obj 103 424 * -1; 179 | #X obj 103 377 *; 180 | #X obj 252 288 t f f; 181 | #X obj 282 311 - 1; 182 | #X obj 282 334 * -1; 183 | #X obj 140 503 *; 184 | #X obj 140 400 - 1; 185 | #X obj 140 425 * -1; 186 | #X obj 140 378 *; 187 | #X obj 66 142 t f f f, f 11; 188 | #X obj 66 535 pack 0 0 0 0, f 16; 189 | #N canvas 1219 718 529 336 select 0; 190 | #X obj 290 37 inlet; 191 | #X obj 16 281 outlet; 192 | #X obj 16 58 t b l, f 20; 193 | #X obj 152 176 unpack 0 0 0 0; 194 | #X obj 16 29 inlet p q t v; 195 | #X obj 16 123 i; 196 | #X obj 16 175 select 0 1 2 3 4 5; 197 | #X obj 16 239 pack 0 0 0; 198 | #X obj 16 209 f; 199 | #X obj 96 239 pack 0 0 0; 200 | #X obj 96 209 f; 201 | #X obj 176 239 pack 0 0 0; 202 | #X obj 176 209 f; 203 | #X obj 256 239 pack 0 0 0; 204 | #X obj 256 209 f; 205 | #X obj 336 239 pack 0 0 0; 206 | #X obj 336 209 f; 207 | #X obj 416 239 pack 0 0 0; 208 | #X obj 416 209 f; 209 | #X connect 0 0 5 1; 210 | #X connect 2 0 5 0; 211 | #X connect 2 1 3 0; 212 | #X connect 3 0 7 2; 213 | #X connect 3 0 9 2; 214 | #X connect 3 0 12 1; 215 | #X connect 3 0 14 1; 216 | #X connect 3 0 15 1; 217 | #X connect 3 0 17 1; 218 | #X connect 3 1 10 1; 219 | #X connect 3 1 13 1; 220 | #X connect 3 1 17 2; 221 | #X connect 3 2 7 1; 222 | #X connect 3 2 11 2; 223 | #X connect 3 2 16 1; 224 | #X connect 3 3 8 1; 225 | #X connect 3 3 9 1; 226 | #X connect 3 3 11 1; 227 | #X connect 3 3 13 2; 228 | #X connect 3 3 15 2; 229 | #X connect 3 3 18 1; 230 | #X connect 4 0 2 0; 231 | #X connect 5 0 6 0; 232 | #X connect 6 0 8 0; 233 | #X connect 6 1 10 0; 234 | #X connect 6 2 12 0; 235 | #X connect 6 3 14 0; 236 | #X connect 6 4 16 0; 237 | #X connect 6 5 18 0; 238 | #X connect 7 0 1 0; 239 | #X connect 8 0 7 0; 240 | #X connect 9 0 1 0; 241 | #X connect 10 0 9 0; 242 | #X connect 11 0 1 0; 243 | #X connect 12 0 11 0; 244 | #X connect 13 0 1 0; 245 | #X connect 14 0 13 0; 246 | #X connect 15 0 1 0; 247 | #X connect 16 0 15 0; 248 | #X connect 17 0 1 0; 249 | #X connect 18 0 17 0; 250 | #X restore 66 560 pd select; 251 | #X f 28; 252 | #X msg 20 621 \$1 \$1 \$1; 253 | #X obj 20 593 clip 0 1; 254 | #X obj 66 117 clip 0 1; 255 | #X obj 175 119 clip 0 1; 256 | #X obj 252 118 clip 0 1; 257 | #X obj 252 142 select 1; 258 | #X obj 175 462 t f f f f; 259 | #X obj 260 237 t f f; 260 | #X text 26 699 taken from the GEM helpfile hsv2rgb-help; 261 | #X connect 0 0 2 0; 262 | #X connect 0 1 3 0; 263 | #X connect 1 0 0 0; 264 | #X connect 2 0 31 0; 265 | #X connect 3 0 32 0; 266 | #X connect 3 1 33 0; 267 | #X connect 3 2 34 0; 268 | #X connect 5 0 1 0; 269 | #X connect 7 0 9 0; 270 | #X connect 8 0 37 0; 271 | #X connect 9 0 12 0; 272 | #X connect 9 1 8 0; 273 | #X connect 10 0 9 0; 274 | #X connect 12 0 20 0; 275 | #X connect 13 0 28 0; 276 | #X connect 14 0 15 0; 277 | #X connect 15 0 13 0; 278 | #X connect 16 0 28 1; 279 | #X connect 17 0 18 0; 280 | #X connect 18 0 16 0; 281 | #X connect 19 0 17 0; 282 | #X connect 20 0 19 1; 283 | #X connect 20 1 21 0; 284 | #X connect 21 0 22 0; 285 | #X connect 22 0 26 1; 286 | #X connect 23 0 28 2; 287 | #X connect 24 0 25 0; 288 | #X connect 25 0 23 0; 289 | #X connect 26 0 24 0; 290 | #X connect 27 0 14 0; 291 | #X connect 27 1 19 0; 292 | #X connect 27 2 26 0; 293 | #X connect 28 0 29 0; 294 | #X connect 29 0 6 0; 295 | #X connect 30 0 6 0; 296 | #X connect 31 0 30 0; 297 | #X connect 32 0 27 0; 298 | #X connect 33 0 36 0; 299 | #X connect 34 0 35 0; 300 | #X connect 35 0 10 0; 301 | #X connect 35 1 7 0; 302 | #X connect 36 0 13 1; 303 | #X connect 36 1 16 1; 304 | #X connect 36 2 23 1; 305 | #X connect 36 3 28 3; 306 | #X connect 37 0 29 1; 307 | #X connect 37 1 12 1; 308 | #X restore 125 343 pd hsv2rgb; 309 | #N canvas 250 355 563 749 hsv2rgb 0; 310 | #X obj 20 64 route 0; 311 | #X msg 20 36 \$2 \$3 \$1; 312 | #X obj 20 552 f; 313 | #X obj 66 92 unpack 0 0 0, f 27; 314 | #X text 87 37 svh; 315 | #X obj 20 8 inlet hsv; 316 | #X obj 20 663 outlet rgb; 317 | #X obj 303 166 * 6; 318 | #X obj 284 213 % 6; 319 | #X obj 252 190 t f f; 320 | #X msg 252 166 0; 321 | #X text 308 213 i; 322 | #X obj 252 264 -; 323 | #X obj 66 501 *; 324 | #X obj 66 169 - 1; 325 | #X obj 66 196 * -1; 326 | #X obj 103 502 *; 327 | #X obj 103 399 - 1; 328 | #X obj 103 424 * -1; 329 | #X obj 103 377 *; 330 | #X obj 252 288 t f f; 331 | #X obj 282 311 - 1; 332 | #X obj 282 334 * -1; 333 | #X obj 140 503 *; 334 | #X obj 140 400 - 1; 335 | #X obj 140 425 * -1; 336 | #X obj 140 378 *; 337 | #X obj 66 142 t f f f, f 11; 338 | #X obj 66 535 pack 0 0 0 0, f 16; 339 | #N canvas 1219 718 529 336 select 0; 340 | #X obj 290 37 inlet; 341 | #X obj 16 281 outlet; 342 | #X obj 16 58 t b l, f 20; 343 | #X obj 152 176 unpack 0 0 0 0; 344 | #X obj 16 29 inlet p q t v; 345 | #X obj 16 123 i; 346 | #X obj 16 175 select 0 1 2 3 4 5; 347 | #X obj 16 239 pack 0 0 0; 348 | #X obj 16 209 f; 349 | #X obj 96 239 pack 0 0 0; 350 | #X obj 96 209 f; 351 | #X obj 176 239 pack 0 0 0; 352 | #X obj 176 209 f; 353 | #X obj 256 239 pack 0 0 0; 354 | #X obj 256 209 f; 355 | #X obj 336 239 pack 0 0 0; 356 | #X obj 336 209 f; 357 | #X obj 416 239 pack 0 0 0; 358 | #X obj 416 209 f; 359 | #X connect 0 0 5 1; 360 | #X connect 2 0 5 0; 361 | #X connect 2 1 3 0; 362 | #X connect 3 0 7 2; 363 | #X connect 3 0 9 2; 364 | #X connect 3 0 12 1; 365 | #X connect 3 0 14 1; 366 | #X connect 3 0 15 1; 367 | #X connect 3 0 17 1; 368 | #X connect 3 1 10 1; 369 | #X connect 3 1 13 1; 370 | #X connect 3 1 17 2; 371 | #X connect 3 2 7 1; 372 | #X connect 3 2 11 2; 373 | #X connect 3 2 16 1; 374 | #X connect 3 3 8 1; 375 | #X connect 3 3 9 1; 376 | #X connect 3 3 11 1; 377 | #X connect 3 3 13 2; 378 | #X connect 3 3 15 2; 379 | #X connect 3 3 18 1; 380 | #X connect 4 0 2 0; 381 | #X connect 5 0 6 0; 382 | #X connect 6 0 8 0; 383 | #X connect 6 1 10 0; 384 | #X connect 6 2 12 0; 385 | #X connect 6 3 14 0; 386 | #X connect 6 4 16 0; 387 | #X connect 6 5 18 0; 388 | #X connect 7 0 1 0; 389 | #X connect 8 0 7 0; 390 | #X connect 9 0 1 0; 391 | #X connect 10 0 9 0; 392 | #X connect 11 0 1 0; 393 | #X connect 12 0 11 0; 394 | #X connect 13 0 1 0; 395 | #X connect 14 0 13 0; 396 | #X connect 15 0 1 0; 397 | #X connect 16 0 15 0; 398 | #X connect 17 0 1 0; 399 | #X connect 18 0 17 0; 400 | #X restore 66 560 pd select; 401 | #X f 28; 402 | #X msg 20 621 \$1 \$1 \$1; 403 | #X obj 20 593 clip 0 1; 404 | #X obj 66 117 clip 0 1; 405 | #X obj 175 119 clip 0 1; 406 | #X obj 252 118 clip 0 1; 407 | #X obj 252 142 select 1; 408 | #X obj 175 462 t f f f f; 409 | #X obj 260 237 t f f; 410 | #X text 26 699 taken from the GEM helpfile hsv2rgb-help; 411 | #X connect 0 0 2 0; 412 | #X connect 0 1 3 0; 413 | #X connect 1 0 0 0; 414 | #X connect 2 0 31 0; 415 | #X connect 3 0 32 0; 416 | #X connect 3 1 33 0; 417 | #X connect 3 2 34 0; 418 | #X connect 5 0 1 0; 419 | #X connect 7 0 9 0; 420 | #X connect 8 0 37 0; 421 | #X connect 9 0 12 0; 422 | #X connect 9 1 8 0; 423 | #X connect 10 0 9 0; 424 | #X connect 12 0 20 0; 425 | #X connect 13 0 28 0; 426 | #X connect 14 0 15 0; 427 | #X connect 15 0 13 0; 428 | #X connect 16 0 28 1; 429 | #X connect 17 0 18 0; 430 | #X connect 18 0 16 0; 431 | #X connect 19 0 17 0; 432 | #X connect 20 0 19 1; 433 | #X connect 20 1 21 0; 434 | #X connect 21 0 22 0; 435 | #X connect 22 0 26 1; 436 | #X connect 23 0 28 2; 437 | #X connect 24 0 25 0; 438 | #X connect 25 0 23 0; 439 | #X connect 26 0 24 0; 440 | #X connect 27 0 14 0; 441 | #X connect 27 1 19 0; 442 | #X connect 27 2 26 0; 443 | #X connect 28 0 29 0; 444 | #X connect 29 0 6 0; 445 | #X connect 30 0 6 0; 446 | #X connect 31 0 30 0; 447 | #X connect 32 0 27 0; 448 | #X connect 33 0 36 0; 449 | #X connect 34 0 35 0; 450 | #X connect 35 0 10 0; 451 | #X connect 35 1 7 0; 452 | #X connect 36 0 13 1; 453 | #X connect 36 1 16 1; 454 | #X connect 36 2 23 1; 455 | #X connect 36 3 28 3; 456 | #X connect 37 0 29 1; 457 | #X connect 37 1 12 1; 458 | #X restore 221 343 pd hsv2rgb; 459 | #N canvas 250 355 563 749 hsv2rgb 0; 460 | #X obj 20 64 route 0; 461 | #X msg 20 36 \$2 \$3 \$1; 462 | #X obj 20 552 f; 463 | #X obj 66 92 unpack 0 0 0, f 27; 464 | #X text 87 37 svh; 465 | #X obj 20 8 inlet hsv; 466 | #X obj 20 663 outlet rgb; 467 | #X obj 303 166 * 6; 468 | #X obj 284 213 % 6; 469 | #X obj 252 190 t f f; 470 | #X msg 252 166 0; 471 | #X text 308 213 i; 472 | #X obj 252 264 -; 473 | #X obj 66 501 *; 474 | #X obj 66 169 - 1; 475 | #X obj 66 196 * -1; 476 | #X obj 103 502 *; 477 | #X obj 103 399 - 1; 478 | #X obj 103 424 * -1; 479 | #X obj 103 377 *; 480 | #X obj 252 288 t f f; 481 | #X obj 282 311 - 1; 482 | #X obj 282 334 * -1; 483 | #X obj 140 503 *; 484 | #X obj 140 400 - 1; 485 | #X obj 140 425 * -1; 486 | #X obj 140 378 *; 487 | #X obj 66 142 t f f f, f 11; 488 | #X obj 66 535 pack 0 0 0 0, f 16; 489 | #N canvas 1219 718 529 336 select 0; 490 | #X obj 290 37 inlet; 491 | #X obj 16 281 outlet; 492 | #X obj 16 58 t b l, f 20; 493 | #X obj 152 176 unpack 0 0 0 0; 494 | #X obj 16 29 inlet p q t v; 495 | #X obj 16 123 i; 496 | #X obj 16 175 select 0 1 2 3 4 5; 497 | #X obj 16 239 pack 0 0 0; 498 | #X obj 16 209 f; 499 | #X obj 96 239 pack 0 0 0; 500 | #X obj 96 209 f; 501 | #X obj 176 239 pack 0 0 0; 502 | #X obj 176 209 f; 503 | #X obj 256 239 pack 0 0 0; 504 | #X obj 256 209 f; 505 | #X obj 336 239 pack 0 0 0; 506 | #X obj 336 209 f; 507 | #X obj 416 239 pack 0 0 0; 508 | #X obj 416 209 f; 509 | #X connect 0 0 5 1; 510 | #X connect 2 0 5 0; 511 | #X connect 2 1 3 0; 512 | #X connect 3 0 7 2; 513 | #X connect 3 0 9 2; 514 | #X connect 3 0 12 1; 515 | #X connect 3 0 14 1; 516 | #X connect 3 0 15 1; 517 | #X connect 3 0 17 1; 518 | #X connect 3 1 10 1; 519 | #X connect 3 1 13 1; 520 | #X connect 3 1 17 2; 521 | #X connect 3 2 7 1; 522 | #X connect 3 2 11 2; 523 | #X connect 3 2 16 1; 524 | #X connect 3 3 8 1; 525 | #X connect 3 3 9 1; 526 | #X connect 3 3 11 1; 527 | #X connect 3 3 13 2; 528 | #X connect 3 3 15 2; 529 | #X connect 3 3 18 1; 530 | #X connect 4 0 2 0; 531 | #X connect 5 0 6 0; 532 | #X connect 6 0 8 0; 533 | #X connect 6 1 10 0; 534 | #X connect 6 2 12 0; 535 | #X connect 6 3 14 0; 536 | #X connect 6 4 16 0; 537 | #X connect 6 5 18 0; 538 | #X connect 7 0 1 0; 539 | #X connect 8 0 7 0; 540 | #X connect 9 0 1 0; 541 | #X connect 10 0 9 0; 542 | #X connect 11 0 1 0; 543 | #X connect 12 0 11 0; 544 | #X connect 13 0 1 0; 545 | #X connect 14 0 13 0; 546 | #X connect 15 0 1 0; 547 | #X connect 16 0 15 0; 548 | #X connect 17 0 1 0; 549 | #X connect 18 0 17 0; 550 | #X restore 66 560 pd select; 551 | #X f 28; 552 | #X msg 20 621 \$1 \$1 \$1; 553 | #X obj 20 593 clip 0 1; 554 | #X obj 66 117 clip 0 1; 555 | #X obj 175 119 clip 0 1; 556 | #X obj 252 118 clip 0 1; 557 | #X obj 252 142 select 1; 558 | #X obj 175 462 t f f f f; 559 | #X obj 260 237 t f f; 560 | #X text 26 699 taken from the GEM helpfile hsv2rgb-help; 561 | #X connect 0 0 2 0; 562 | #X connect 0 1 3 0; 563 | #X connect 1 0 0 0; 564 | #X connect 2 0 31 0; 565 | #X connect 3 0 32 0; 566 | #X connect 3 1 33 0; 567 | #X connect 3 2 34 0; 568 | #X connect 5 0 1 0; 569 | #X connect 7 0 9 0; 570 | #X connect 8 0 37 0; 571 | #X connect 9 0 12 0; 572 | #X connect 9 1 8 0; 573 | #X connect 10 0 9 0; 574 | #X connect 12 0 20 0; 575 | #X connect 13 0 28 0; 576 | #X connect 14 0 15 0; 577 | #X connect 15 0 13 0; 578 | #X connect 16 0 28 1; 579 | #X connect 17 0 18 0; 580 | #X connect 18 0 16 0; 581 | #X connect 19 0 17 0; 582 | #X connect 20 0 19 1; 583 | #X connect 20 1 21 0; 584 | #X connect 21 0 22 0; 585 | #X connect 22 0 26 1; 586 | #X connect 23 0 28 2; 587 | #X connect 24 0 25 0; 588 | #X connect 25 0 23 0; 589 | #X connect 26 0 24 0; 590 | #X connect 27 0 14 0; 591 | #X connect 27 1 19 0; 592 | #X connect 27 2 26 0; 593 | #X connect 28 0 29 0; 594 | #X connect 29 0 6 0; 595 | #X connect 30 0 6 0; 596 | #X connect 31 0 30 0; 597 | #X connect 32 0 27 0; 598 | #X connect 33 0 36 0; 599 | #X connect 34 0 35 0; 600 | #X connect 35 0 10 0; 601 | #X connect 35 1 7 0; 602 | #X connect 36 0 13 1; 603 | #X connect 36 1 16 1; 604 | #X connect 36 2 23 1; 605 | #X connect 36 3 28 3; 606 | #X connect 37 0 29 1; 607 | #X connect 37 1 12 1; 608 | #X restore 317 343 pd hsv2rgb; 609 | #N canvas 250 355 563 749 hsv2rgb 0; 610 | #X obj 20 64 route 0; 611 | #X msg 20 36 \$2 \$3 \$1; 612 | #X obj 20 552 f; 613 | #X obj 66 92 unpack 0 0 0, f 27; 614 | #X text 87 37 svh; 615 | #X obj 20 8 inlet hsv; 616 | #X obj 20 663 outlet rgb; 617 | #X obj 303 166 * 6; 618 | #X obj 284 213 % 6; 619 | #X obj 252 190 t f f; 620 | #X msg 252 166 0; 621 | #X text 308 213 i; 622 | #X obj 252 264 -; 623 | #X obj 66 501 *; 624 | #X obj 66 169 - 1; 625 | #X obj 66 196 * -1; 626 | #X obj 103 502 *; 627 | #X obj 103 399 - 1; 628 | #X obj 103 424 * -1; 629 | #X obj 103 377 *; 630 | #X obj 252 288 t f f; 631 | #X obj 282 311 - 1; 632 | #X obj 282 334 * -1; 633 | #X obj 140 503 *; 634 | #X obj 140 400 - 1; 635 | #X obj 140 425 * -1; 636 | #X obj 140 378 *; 637 | #X obj 66 142 t f f f, f 11; 638 | #X obj 66 535 pack 0 0 0 0, f 16; 639 | #N canvas 1219 718 529 336 select 0; 640 | #X obj 290 37 inlet; 641 | #X obj 16 281 outlet; 642 | #X obj 16 58 t b l, f 20; 643 | #X obj 152 176 unpack 0 0 0 0; 644 | #X obj 16 29 inlet p q t v; 645 | #X obj 16 123 i; 646 | #X obj 16 175 select 0 1 2 3 4 5; 647 | #X obj 16 239 pack 0 0 0; 648 | #X obj 16 209 f; 649 | #X obj 96 239 pack 0 0 0; 650 | #X obj 96 209 f; 651 | #X obj 176 239 pack 0 0 0; 652 | #X obj 176 209 f; 653 | #X obj 256 239 pack 0 0 0; 654 | #X obj 256 209 f; 655 | #X obj 336 239 pack 0 0 0; 656 | #X obj 336 209 f; 657 | #X obj 416 239 pack 0 0 0; 658 | #X obj 416 209 f; 659 | #X connect 0 0 5 1; 660 | #X connect 2 0 5 0; 661 | #X connect 2 1 3 0; 662 | #X connect 3 0 7 2; 663 | #X connect 3 0 9 2; 664 | #X connect 3 0 12 1; 665 | #X connect 3 0 14 1; 666 | #X connect 3 0 15 1; 667 | #X connect 3 0 17 1; 668 | #X connect 3 1 10 1; 669 | #X connect 3 1 13 1; 670 | #X connect 3 1 17 2; 671 | #X connect 3 2 7 1; 672 | #X connect 3 2 11 2; 673 | #X connect 3 2 16 1; 674 | #X connect 3 3 8 1; 675 | #X connect 3 3 9 1; 676 | #X connect 3 3 11 1; 677 | #X connect 3 3 13 2; 678 | #X connect 3 3 15 2; 679 | #X connect 3 3 18 1; 680 | #X connect 4 0 2 0; 681 | #X connect 5 0 6 0; 682 | #X connect 6 0 8 0; 683 | #X connect 6 1 10 0; 684 | #X connect 6 2 12 0; 685 | #X connect 6 3 14 0; 686 | #X connect 6 4 16 0; 687 | #X connect 6 5 18 0; 688 | #X connect 7 0 1 0; 689 | #X connect 8 0 7 0; 690 | #X connect 9 0 1 0; 691 | #X connect 10 0 9 0; 692 | #X connect 11 0 1 0; 693 | #X connect 12 0 11 0; 694 | #X connect 13 0 1 0; 695 | #X connect 14 0 13 0; 696 | #X connect 15 0 1 0; 697 | #X connect 16 0 15 0; 698 | #X connect 17 0 1 0; 699 | #X connect 18 0 17 0; 700 | #X restore 66 560 pd select; 701 | #X f 28; 702 | #X msg 20 621 \$1 \$1 \$1; 703 | #X obj 20 593 clip 0 1; 704 | #X obj 66 117 clip 0 1; 705 | #X obj 175 119 clip 0 1; 706 | #X obj 252 118 clip 0 1; 707 | #X obj 252 142 select 1; 708 | #X obj 175 462 t f f f f; 709 | #X obj 260 237 t f f; 710 | #X text 26 699 taken from the GEM helpfile hsv2rgb-help; 711 | #X connect 0 0 2 0; 712 | #X connect 0 1 3 0; 713 | #X connect 1 0 0 0; 714 | #X connect 2 0 31 0; 715 | #X connect 3 0 32 0; 716 | #X connect 3 1 33 0; 717 | #X connect 3 2 34 0; 718 | #X connect 5 0 1 0; 719 | #X connect 7 0 9 0; 720 | #X connect 8 0 37 0; 721 | #X connect 9 0 12 0; 722 | #X connect 9 1 8 0; 723 | #X connect 10 0 9 0; 724 | #X connect 12 0 20 0; 725 | #X connect 13 0 28 0; 726 | #X connect 14 0 15 0; 727 | #X connect 15 0 13 0; 728 | #X connect 16 0 28 1; 729 | #X connect 17 0 18 0; 730 | #X connect 18 0 16 0; 731 | #X connect 19 0 17 0; 732 | #X connect 20 0 19 1; 733 | #X connect 20 1 21 0; 734 | #X connect 21 0 22 0; 735 | #X connect 22 0 26 1; 736 | #X connect 23 0 28 2; 737 | #X connect 24 0 25 0; 738 | #X connect 25 0 23 0; 739 | #X connect 26 0 24 0; 740 | #X connect 27 0 14 0; 741 | #X connect 27 1 19 0; 742 | #X connect 27 2 26 0; 743 | #X connect 28 0 29 0; 744 | #X connect 29 0 6 0; 745 | #X connect 30 0 6 0; 746 | #X connect 31 0 30 0; 747 | #X connect 32 0 27 0; 748 | #X connect 33 0 36 0; 749 | #X connect 34 0 35 0; 750 | #X connect 35 0 10 0; 751 | #X connect 35 1 7 0; 752 | #X connect 36 0 13 1; 753 | #X connect 36 1 16 1; 754 | #X connect 36 2 23 1; 755 | #X connect 36 3 28 3; 756 | #X connect 37 0 29 1; 757 | #X connect 37 1 12 1; 758 | #X restore 413 343 pd hsv2rgb; 759 | #N canvas 250 355 563 749 hsv2rgb 0; 760 | #X obj 20 64 route 0; 761 | #X msg 20 36 \$2 \$3 \$1; 762 | #X obj 20 552 f; 763 | #X obj 66 92 unpack 0 0 0, f 27; 764 | #X text 87 37 svh; 765 | #X obj 20 8 inlet hsv; 766 | #X obj 20 663 outlet rgb; 767 | #X obj 303 166 * 6; 768 | #X obj 284 213 % 6; 769 | #X obj 252 190 t f f; 770 | #X msg 252 166 0; 771 | #X text 308 213 i; 772 | #X obj 252 264 -; 773 | #X obj 66 501 *; 774 | #X obj 66 169 - 1; 775 | #X obj 66 196 * -1; 776 | #X obj 103 502 *; 777 | #X obj 103 399 - 1; 778 | #X obj 103 424 * -1; 779 | #X obj 103 377 *; 780 | #X obj 252 288 t f f; 781 | #X obj 282 311 - 1; 782 | #X obj 282 334 * -1; 783 | #X obj 140 503 *; 784 | #X obj 140 400 - 1; 785 | #X obj 140 425 * -1; 786 | #X obj 140 378 *; 787 | #X obj 66 142 t f f f, f 11; 788 | #X obj 66 535 pack 0 0 0 0, f 16; 789 | #N canvas 1219 718 529 336 select 0; 790 | #X obj 290 37 inlet; 791 | #X obj 16 281 outlet; 792 | #X obj 16 58 t b l, f 20; 793 | #X obj 152 176 unpack 0 0 0 0; 794 | #X obj 16 29 inlet p q t v; 795 | #X obj 16 123 i; 796 | #X obj 16 175 select 0 1 2 3 4 5; 797 | #X obj 16 239 pack 0 0 0; 798 | #X obj 16 209 f; 799 | #X obj 96 239 pack 0 0 0; 800 | #X obj 96 209 f; 801 | #X obj 176 239 pack 0 0 0; 802 | #X obj 176 209 f; 803 | #X obj 256 239 pack 0 0 0; 804 | #X obj 256 209 f; 805 | #X obj 336 239 pack 0 0 0; 806 | #X obj 336 209 f; 807 | #X obj 416 239 pack 0 0 0; 808 | #X obj 416 209 f; 809 | #X connect 0 0 5 1; 810 | #X connect 2 0 5 0; 811 | #X connect 2 1 3 0; 812 | #X connect 3 0 7 2; 813 | #X connect 3 0 9 2; 814 | #X connect 3 0 12 1; 815 | #X connect 3 0 14 1; 816 | #X connect 3 0 15 1; 817 | #X connect 3 0 17 1; 818 | #X connect 3 1 10 1; 819 | #X connect 3 1 13 1; 820 | #X connect 3 1 17 2; 821 | #X connect 3 2 7 1; 822 | #X connect 3 2 11 2; 823 | #X connect 3 2 16 1; 824 | #X connect 3 3 8 1; 825 | #X connect 3 3 9 1; 826 | #X connect 3 3 11 1; 827 | #X connect 3 3 13 2; 828 | #X connect 3 3 15 2; 829 | #X connect 3 3 18 1; 830 | #X connect 4 0 2 0; 831 | #X connect 5 0 6 0; 832 | #X connect 6 0 8 0; 833 | #X connect 6 1 10 0; 834 | #X connect 6 2 12 0; 835 | #X connect 6 3 14 0; 836 | #X connect 6 4 16 0; 837 | #X connect 6 5 18 0; 838 | #X connect 7 0 1 0; 839 | #X connect 8 0 7 0; 840 | #X connect 9 0 1 0; 841 | #X connect 10 0 9 0; 842 | #X connect 11 0 1 0; 843 | #X connect 12 0 11 0; 844 | #X connect 13 0 1 0; 845 | #X connect 14 0 13 0; 846 | #X connect 15 0 1 0; 847 | #X connect 16 0 15 0; 848 | #X connect 17 0 1 0; 849 | #X connect 18 0 17 0; 850 | #X restore 66 560 pd select; 851 | #X f 28; 852 | #X msg 20 621 \$1 \$1 \$1; 853 | #X obj 20 593 clip 0 1; 854 | #X obj 66 117 clip 0 1; 855 | #X obj 175 119 clip 0 1; 856 | #X obj 252 118 clip 0 1; 857 | #X obj 252 142 select 1; 858 | #X obj 175 462 t f f f f; 859 | #X obj 260 237 t f f; 860 | #X text 26 699 taken from the GEM helpfile hsv2rgb-help; 861 | #X connect 0 0 2 0; 862 | #X connect 0 1 3 0; 863 | #X connect 1 0 0 0; 864 | #X connect 2 0 31 0; 865 | #X connect 3 0 32 0; 866 | #X connect 3 1 33 0; 867 | #X connect 3 2 34 0; 868 | #X connect 5 0 1 0; 869 | #X connect 7 0 9 0; 870 | #X connect 8 0 37 0; 871 | #X connect 9 0 12 0; 872 | #X connect 9 1 8 0; 873 | #X connect 10 0 9 0; 874 | #X connect 12 0 20 0; 875 | #X connect 13 0 28 0; 876 | #X connect 14 0 15 0; 877 | #X connect 15 0 13 0; 878 | #X connect 16 0 28 1; 879 | #X connect 17 0 18 0; 880 | #X connect 18 0 16 0; 881 | #X connect 19 0 17 0; 882 | #X connect 20 0 19 1; 883 | #X connect 20 1 21 0; 884 | #X connect 21 0 22 0; 885 | #X connect 22 0 26 1; 886 | #X connect 23 0 28 2; 887 | #X connect 24 0 25 0; 888 | #X connect 25 0 23 0; 889 | #X connect 26 0 24 0; 890 | #X connect 27 0 14 0; 891 | #X connect 27 1 19 0; 892 | #X connect 27 2 26 0; 893 | #X connect 28 0 29 0; 894 | #X connect 29 0 6 0; 895 | #X connect 30 0 6 0; 896 | #X connect 31 0 30 0; 897 | #X connect 32 0 27 0; 898 | #X connect 33 0 36 0; 899 | #X connect 34 0 35 0; 900 | #X connect 35 0 10 0; 901 | #X connect 35 1 7 0; 902 | #X connect 36 0 13 1; 903 | #X connect 36 1 16 1; 904 | #X connect 36 2 23 1; 905 | #X connect 36 3 28 3; 906 | #X connect 37 0 29 1; 907 | #X connect 37 1 12 1; 908 | #X restore 509 343 pd hsv2rgb; 909 | #X msg 125 400 S2 \$1 \$2 \$3; 910 | #X msg 221 400 S3 \$1 \$2 \$3; 911 | #X msg 317 400 S4 \$1 \$2 \$3; 912 | #X msg 413 400 S5 \$1 \$2 \$3; 913 | #X msg 509 400 S6 \$1 \$2 \$3; 914 | #X obj 30 92 metro 2000; 915 | #X msg 62 426 L1 \$1 \$2 \$3; 916 | #X msg 158 426 L2 \$1 \$2 \$3; 917 | #X msg 254 426 L3 \$1 \$2 \$3; 918 | #X msg 350 426 L4 \$1 \$2 \$3; 919 | #X msg 447 426 L5 \$1 \$2 \$3; 920 | #X msg 542 426 L6 \$1 \$2 \$3; 921 | #X obj 29 619 dac~ 1 2 3 4 5 6, f 69; 922 | #X obj 29 369 t l l; 923 | #X obj 125 369 t l l; 924 | #X obj 221 369 t l l; 925 | #X obj 317 369 t l l; 926 | #X obj 413 369 t l l; 927 | #X obj 509 369 t l l; 928 | #X msg 125 316 \$1 1 1; 929 | #X obj 125 201 + 16.6667; 930 | #X obj 125 227 % 100; 931 | #X obj 29 249 / 100; 932 | #X obj 125 252 / 100; 933 | #X obj 221 224 % 100; 934 | #X obj 221 249 / 100; 935 | #X msg 221 315 \$1 1 1; 936 | #X obj 317 225 % 100; 937 | #X obj 317 250 / 100; 938 | #X msg 317 316 \$1 1 1; 939 | #X obj 413 226 % 100; 940 | #X obj 413 251 / 100; 941 | #X msg 413 317 \$1 1 1; 942 | #X obj 509 226 % 100; 943 | #X obj 509 251 / 100; 944 | #X msg 509 317 \$1 1 1; 945 | #X obj 221 198 + 33.3334; 946 | #X obj 413 200 + 66.6667; 947 | #X obj 509 200 + 83.3334; 948 | #X obj 29 167 t f f f f f f, f 69; 949 | #X obj 317 199 + 50; 950 | #X obj 40 281 s out1; 951 | #X obj 30 511 r out1; 952 | #X obj 127 511 r out2; 953 | #X obj 316 509 r out4; 954 | #X obj 223 509 r out3; 955 | #X obj 413 509 r out5; 956 | #X obj 508 507 r out6; 957 | #X obj 136 282 s out2; 958 | #X obj 229 283 s out3; 959 | #X obj 328 283 s out4; 960 | #X obj 427 286 s out5; 961 | #X obj 529 284 s out6; 962 | #X obj 127 536 cos~; 963 | #X obj 222 534 cos~; 964 | #X obj 316 534 cos~; 965 | #X obj 413 534 cos~; 966 | #X obj 508 532 cos~; 967 | #X obj 30 564 +~ 1; 968 | #X obj 30 536 cos~; 969 | #X obj 126 562 +~ 1; 970 | #X obj 222 561 +~ 1; 971 | #X obj 316 559 +~ 1; 972 | #X obj 413 560 +~ 1; 973 | #X obj 508 560 +~ 1; 974 | #X msg 30 118 100 \, 0 2000; 975 | #X obj 30 590 *~ 5; 976 | #X obj 125 589 *~ 5; 977 | #X obj 222 588 *~ 5; 978 | #X obj 317 587 *~ 5; 979 | #X obj 413 588 *~ 5; 980 | #X obj 508 588 *~ 5; 981 | #X obj 251 17 loadbang; 982 | #X msg 251 41 display Hello world!; 983 | #X text 608 326 This should be an abstraction \, but to keep the example 984 | directory tidy it is duplicated code here (subpatches)., f 28; 985 | #X obj 29 478 print toRack; 986 | #X obj 251 66 print toRack; 987 | #X connect 0 0 25 0; 988 | #X connect 1 0 87 0; 989 | #X connect 2 0 3 0; 990 | #X connect 3 0 5 0; 991 | #X connect 4 0 0 0; 992 | #X connect 5 0 17 0; 993 | #X connect 6 0 51 0; 994 | #X connect 7 0 26 0; 995 | #X connect 8 0 27 0; 996 | #X connect 9 0 28 0; 997 | #X connect 10 0 29 0; 998 | #X connect 11 0 30 0; 999 | #X connect 12 0 87 0; 1000 | #X connect 13 0 87 0; 1001 | #X connect 14 0 87 0; 1002 | #X connect 15 0 87 0; 1003 | #X connect 16 0 87 0; 1004 | #X connect 17 0 77 0; 1005 | #X connect 18 0 87 0; 1006 | #X connect 19 0 87 0; 1007 | #X connect 20 0 87 0; 1008 | #X connect 21 0 87 0; 1009 | #X connect 22 0 87 0; 1010 | #X connect 23 0 87 0; 1011 | #X connect 25 0 1 0; 1012 | #X connect 25 1 18 0; 1013 | #X connect 26 0 12 0; 1014 | #X connect 26 1 19 0; 1015 | #X connect 27 0 13 0; 1016 | #X connect 27 1 20 0; 1017 | #X connect 28 0 14 0; 1018 | #X connect 28 1 21 0; 1019 | #X connect 29 0 15 0; 1020 | #X connect 29 1 22 0; 1021 | #X connect 30 0 16 0; 1022 | #X connect 30 1 23 0; 1023 | #X connect 31 0 7 0; 1024 | #X connect 32 0 33 0; 1025 | #X connect 33 0 35 0; 1026 | #X connect 34 0 4 0; 1027 | #X connect 34 0 53 0; 1028 | #X connect 35 0 31 0; 1029 | #X connect 35 0 60 0; 1030 | #X connect 36 0 37 0; 1031 | #X connect 37 0 38 0; 1032 | #X connect 37 0 61 0; 1033 | #X connect 38 0 8 0; 1034 | #X connect 39 0 40 0; 1035 | #X connect 40 0 41 0; 1036 | #X connect 40 0 62 0; 1037 | #X connect 41 0 9 0; 1038 | #X connect 42 0 43 0; 1039 | #X connect 43 0 44 0; 1040 | #X connect 43 0 63 0; 1041 | #X connect 44 0 10 0; 1042 | #X connect 45 0 46 0; 1043 | #X connect 46 0 47 0; 1044 | #X connect 46 0 64 0; 1045 | #X connect 47 0 11 0; 1046 | #X connect 48 0 36 0; 1047 | #X connect 49 0 42 0; 1048 | #X connect 50 0 45 0; 1049 | #X connect 51 0 34 0; 1050 | #X connect 51 1 32 0; 1051 | #X connect 51 2 48 0; 1052 | #X connect 51 3 52 0; 1053 | #X connect 51 4 49 0; 1054 | #X connect 51 5 50 0; 1055 | #X connect 52 0 39 0; 1056 | #X connect 54 0 71 0; 1057 | #X connect 55 0 65 0; 1058 | #X connect 56 0 67 0; 1059 | #X connect 57 0 66 0; 1060 | #X connect 58 0 68 0; 1061 | #X connect 59 0 69 0; 1062 | #X connect 65 0 72 0; 1063 | #X connect 66 0 73 0; 1064 | #X connect 67 0 74 0; 1065 | #X connect 68 0 75 0; 1066 | #X connect 69 0 76 0; 1067 | #X connect 70 0 78 0; 1068 | #X connect 71 0 70 0; 1069 | #X connect 72 0 79 0; 1070 | #X connect 73 0 80 0; 1071 | #X connect 74 0 81 0; 1072 | #X connect 75 0 82 0; 1073 | #X connect 76 0 83 0; 1074 | #X connect 77 0 6 0; 1075 | #X connect 78 0 24 0; 1076 | #X connect 79 0 24 1; 1077 | #X connect 80 0 24 2; 1078 | #X connect 81 0 24 3; 1079 | #X connect 82 0 24 4; 1080 | #X connect 83 0 24 5; 1081 | #X connect 84 0 85 0; 1082 | #X connect 85 0 88 0; 1083 | -------------------------------------------------------------------------------- /examples/rainbow.scd: -------------------------------------------------------------------------------- 1 | // Rainbow RGB LED example 2 | // by Andrew Belt 3 | // adapted for SC by Brian Heim 4 | 5 | // Call process() every 256 audio samples 6 | ~vcv_frameDivider = 256; 7 | ~vcv_bufferSize = 1; 8 | 9 | // From https://en.wikipedia.org/wiki/HSL_and_HSV#HSV_to_RGB 10 | ~hsvToRgb = { |h, s, v| 11 | var c, x, rgb, m; 12 | h = h * 6; 13 | c = v * s; 14 | x = c * (1 - abs(h % 2 - 1)); 15 | rgb = case 16 | { h < 1 } { [c, x, 0] } 17 | { h < 2 } { [x, c, 0] } 18 | { h < 3 } { [0, c, x] } 19 | { h < 4 } { [0, x, c] } 20 | { h < 5 } { [x, 0, c] } 21 | { [c, 0, x] }; 22 | 23 | rgb + (v - c); 24 | }; 25 | 26 | ~phase = 0; 27 | ~vcv_process = { |block| 28 | ~phase = ~phase + block.sampleTime * ~vcv_frameDivider * 0.5; 29 | ~phase = ~phase % 1.0; 30 | 31 | VcvPrototypeProcessBlock.numRows.do { |i| 32 | var h = (1 - i / 6 + ~phase) % 1; 33 | var rgb = ~hsvToRgb.value(h, 1, 1); 34 | 3.do { |c| 35 | block.lights[i][c] = rgb[c]; 36 | block.switchLights[i][c] = rgb[c]; 37 | }; 38 | block.outputs[i][0] = sin(2pi * h) * 5 + 5; 39 | }; 40 | 41 | block 42 | } 43 | -------------------------------------------------------------------------------- /examples/synth.dsp: -------------------------------------------------------------------------------- 1 | /* 2 | All controllers of the VCV Prototype are accessed using metadata. 3 | */ 4 | 5 | import("stdfaust.lib"); 6 | import("rack.lib"); 7 | 8 | // Using knobs ([knob:N] metadata with N from 1 to 6). Knob [0..1] range is mapped on [min..max] slider range, taking 'scale' metadata in account 9 | 10 | vol1 = hslider("volume1 [knob:1]", 0.1, 0, 1, 0.01); 11 | freq1 = hslider("freq1 [knob:2] [unit:Hz] [scale:lin]", 300, 200, 300, 1); 12 | 13 | vol2 = hslider("volume2 [knob:3]", 0.1, 0, 1, 0.01); 14 | freq2 = hslider("freq2 [knob:4] [unit:Hz] ", 300, 200, 300, 1); 15 | 16 | // Using switches ([switch:N] metadata with N from 1 to 6) 17 | 18 | gate = button("gate [switch:1]"); 19 | 20 | // Checkbox can be used, the switch button will go be white when checked 21 | 22 | check = checkbox("check [switch:2]"); 23 | 24 | // Using bargraph to control lights ([light_red|green|blue:N] metadata with N from 1 to 6, to control 3 colors) 25 | 26 | light_1_r = vbargraph("[light_red:1]", 0, 1); 27 | light_1_g = vbargraph("[light_green:1]", 0, 1); 28 | light_1_b = vbargraph("[light_blue:1]", 0, 1); 29 | 30 | // Using bargraph to control switchlights ([switchlight_red|green|blue:N] metadata with N from 1 to 6, to control 3 colors) 31 | 32 | swl_2_r = vbargraph("[switchlight_red:3]", 0, 1); 33 | swl_2_g = vbargraph("[switchlight_green:3]", 0, 1); 34 | swl_2_b = vbargraph("[switchlight_blue:3]", 0, 1); 35 | 36 | process = os.osc(freq1) * vol1, 37 | os.sawtooth(freq2) * vol2 * gate, 38 | os.square(freq2) * vol2 * check, 39 | (os.osc(1):light_1_r + os.osc(1.4):light_1_g + os.osc(1.7):light_1_b), 40 | (os.osc(1):swl_2_r + os.osc(1.2):swl_2_g + os.osc(1.7):swl_2_b); 41 | -------------------------------------------------------------------------------- /examples/synth.vult: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | Simple synthesizer with one oscillator, LFO and envelope. 4 | 5 | Author: Leonardo Laguna Ruiz - leonardo@vult-dsp.com 6 | 7 | Check the API documentation in the basic.vult example 8 | 9 | */ 10 | 11 | fun env(gate) { 12 | mem x; 13 | val k = if gate > x then 0.05 else 0.0002; 14 | x = x + (gate - x) * k; 15 | return x; 16 | } 17 | 18 | fun edge(x):bool { 19 | mem pre_x; 20 | val v:bool = (pre_x <> x) && (pre_x == false); 21 | pre_x = x; 22 | return v; 23 | } 24 | 25 | fun pitchToFreq(cv) { 26 | return 261.6256 * exp(cv * 0.69314718056); 27 | } 28 | 29 | fun phasor(pitch, reset){ 30 | mem phase; 31 | val rate = pitchToFreq(pitch) * sampletime(); 32 | phase = phase + rate; 33 | if(phase > 1.0) 34 | phase = phase - 1.0; 35 | if(reset) 36 | phase = 0.0; 37 | return phase; 38 | } 39 | 40 | fun oscillator(pitch, mod) { 41 | mem pre_phase1; 42 | // Implements the resonant filter simulation as shown in 43 | // http://en.wikipedia.org/wiki/Phase_distortion_synthesis 44 | val phase1 = phasor(pitch, false); 45 | val comp = 1.0 - phase1; 46 | val reset = edge((pre_phase1 - phase1) > 0.5); 47 | pre_phase1 = phase1; 48 | val phase2 = phasor(pitch + mod, reset); 49 | val sine = sin(2.0 * pi() * phase2); 50 | return sine * comp; 51 | } 52 | 53 | fun lfo(f, gate){ 54 | mem phase; 55 | val rate = f * 10.0 * sampletime(); 56 | if(edge(gate > 0.0)) 57 | phase = 0.0; 58 | phase = phase + rate; 59 | if(phase > 1.0) 60 | phase = phase - 1.0; 61 | return sin(phase * 2.0 * pi()) - 0.5; 62 | } 63 | 64 | // Main processing function 65 | fun process(cv, gate){ 66 | // LFO 67 | val lfo_rate = getKnob(3); 68 | val lfo_amt = getKnob(4); 69 | val lfo_val = lfo(lfo_rate, gate) * lfo_amt; 70 | // Oscillator 71 | val pitch = getKnob(1) + 10.0 * cv - 2.0; 72 | val mod = getKnob(2) * 2.0 + lfo_val; 73 | val o = oscillator(pitch, mod); 74 | // Envelope 75 | val e = env(gate); 76 | return o * e; 77 | } 78 | and update() { 79 | _ = display("IN1: CV, IN2: GATE"); 80 | } -------------------------------------------------------------------------------- /examples/template.js: -------------------------------------------------------------------------------- 1 | config.frameDivider = 1 2 | config.bufferSize = 32 3 | 4 | function process(block) { 5 | // Per-block inputs: 6 | // block.knobs[i] 7 | // block.switches[i] 8 | 9 | for (let j = 0; j < block.bufferSize; j++) { 10 | // Per-sample inputs: 11 | // block.inputs[i][j] 12 | 13 | // Per-sample outputs: 14 | // block.outputs[i][j] 15 | } 16 | 17 | // Per-block outputs: 18 | // block.lights[i][color] 19 | // block.switchLights[i][color] 20 | } 21 | -------------------------------------------------------------------------------- /examples/template.lua: -------------------------------------------------------------------------------- 1 | config.frameDivider = 1 2 | config.bufferSize = 32 3 | 4 | function process(block) 5 | -- Per-block inputs: 6 | -- block.knobs[i] 7 | -- block.switches[i] 8 | 9 | for j=1,block.bufferSize do 10 | -- Per-sample inputs: 11 | -- block.inputs[i][j] 12 | 13 | -- Per-sample outputs: 14 | -- block.outputs[i][j] 15 | end 16 | 17 | -- Per-block outputs: 18 | -- block.lights[i][color] 19 | -- block.switchLights[i][color] 20 | end 21 | -------------------------------------------------------------------------------- /examples/template.pd: -------------------------------------------------------------------------------- 1 | #N canvas 589 332 483 581 12; 2 | #X obj 32 525 dac~ 1 2 3 4 5 6; 3 | #X obj 32 401 adc~ 1 2 3 4 5 6; 4 | #X obj 36 62 route K1 K2 K3 K4 K5 K6; 5 | #X obj 194 106 route S1 S2 S3 S4 S5 S6; 6 | #X text 214 60 knobs; 7 | #X text 369 104 buttons; 8 | #X msg 54 333 display this text will print; 9 | #X msg 32 259 L1 \$1 \$2 \$3; 10 | #X obj 32 230 pack f f f; 11 | #X obj 47 202 t b f; 12 | #X obj 89 203 t b f; 13 | #X obj 32 123 vsl 15 50 0 1 0 0 empty empty empty 0 -9 0 10 -258113 14 | -1 -1 0 1; 15 | #X obj 47 123 vsl 15 50 0 1 0 0 empty empty empty 0 -9 0 10 -4034 -1 16 | -1 0 1; 17 | #X obj 62 123 vsl 15 50 0 1 0 0 empty empty empty 0 -9 0 10 -4160 -1 18 | -1 0 1; 19 | #X obj 36 25 r fromRack; 20 | #X obj 32 367 print toRack; 21 | #X text 263 332 to the display; 22 | #X text 121 25 receiving control data from VCV Prototype module; 23 | #X text 129 368 sending control data from VCV Prototype module; 24 | #X text 158 401 receiving audio from VCV Prototype module; 25 | #X text 158 525 sending audio to VCV Prototype module; 26 | #X connect 2 6 3 0; 27 | #X connect 6 0 15 0; 28 | #X connect 7 0 15 0; 29 | #X connect 8 0 7 0; 30 | #X connect 9 0 8 0; 31 | #X connect 9 1 8 1; 32 | #X connect 10 0 8 0; 33 | #X connect 10 1 8 2; 34 | #X connect 11 0 8 0; 35 | #X connect 12 0 9 0; 36 | #X connect 13 0 10 0; 37 | #X connect 14 0 2 0; 38 | -------------------------------------------------------------------------------- /examples/template.vult: -------------------------------------------------------------------------------- 1 | /* 2 | Vult API documentation. 3 | 4 | Author: Leonardo Laguna Ruiz - leonardo@vult-dsp.com 5 | 6 | The main difference of the Vult API compared to the JavaScript and Lua is that all interactions 7 | happen through functions rather than accessing to the block arrays. 8 | 9 | A Vult script requires the following two functions: 10 | 11 | fun process() { } 12 | and update() { } 13 | 14 | The 'process' function is called every audio sample. As inputs, it will receive the values from 15 | the input jacks but normalized to 1.0. This means that a value of 10.0 V in VCV Rack is received 16 | as 1.0. Similarly, when you return a value of 1.0 it will be output by the prototype as 10.0V. 17 | 18 | You can use the input and output jacks by adding or removing arguments to the function. For example, 19 | to pass all the inputs to the outputs you can declare the function as follows: 20 | 21 | fun process(i1, i2, i3, i4, i5, i6) { 22 | return i1, i2, i3, i4, i5, i6; 23 | } 24 | 25 | The 'update' function is called once every 32 samples. You can use this function to perform actions 26 | that do not require audio rate speed e.g. setting light colors or displying characters in the screen. 27 | The function 'update' do not takes or returns any value. 28 | 29 | Important: Notice that the 'update' function is declared with the keyword 'and'. In Vult language, 30 | this means that they share context. At the moment, declaring them differently could have an undefined 31 | behavior. 32 | 33 | To interact with knobs, switches, lights the following builtin functions are provided. 34 | NOTE: the knobs, switches and lights are numbered from 1 to 6 35 | 36 | getKnob(n:int) : real // value of the nth knob range: 0.0-1.0 37 | getSwitch(n:int) : bool // value of the nth switch: true/false 38 | 39 | setLight(n:int, r:real, g:real, b:real) // r, g, b range: 0.0-1.0 40 | setSwitchLight(n:int, r:real, g:real, b:real) // r, g, b range: 0.0-1.0 41 | 42 | samplerate() : real // current sample rate 43 | sampletime() : real // current time step (1.0 / samplerate()) 44 | display(text:string) // display text in the screen 45 | 46 | */ 47 | 48 | 49 | // Returns the r,g,b values for a given voltage 50 | fun getRGB(v) { 51 | if (v > 0.0) 52 | return v, 0.0, 0.0; 53 | else 54 | return 0.0, -v, 0.0; 55 | } 56 | 57 | // Takes two inputs and returns the result of different operations on them 58 | fun process(in1, in2) { 59 | // theses are declared as 'mem' so we can remember them and use them in 'update' 60 | mem sum = clip(in1 + in2, -1.0, 1.0); // use 'clip' to keep the signals in the specified range 61 | mem sub = clip(in1 - in2, -1.0, 1.0); 62 | mem mul = clip(in1 * in2, -1.0, 1.0); 63 | return sum, sub, mul; 64 | } 65 | and update() { 66 | _ = display("Add two LFO to IN1 and IN2"); 67 | val r, g, b; 68 | // Set the light no 1 with the 'sum' value 69 | r, g, b = getRGB(sum); 70 | _ = setLight(1, r, g, b); 71 | _ = setSwitchLight(1, r, g, b); 72 | 73 | // Set the light no 2 with the 'sub' value 74 | r, g, b = getRGB(sub); 75 | _ = setLight(2, r, g, b); 76 | _ = setSwitchLight(2, r, g, b); 77 | 78 | // Set the light no 2 with the 'mul' value 79 | r, g, b = getRGB(mul); 80 | _ = setLight(3, r, g, b); 81 | _ = setSwitchLight(3, r, g, b); 82 | 83 | } -------------------------------------------------------------------------------- /examples/vco.js: -------------------------------------------------------------------------------- 1 | // Voltage-controlled oscillator example 2 | // by Andrew Belt 3 | 4 | // For audio synthesis and process, JavaScript is 10-100x less efficient than C++, but it's still an easy way to learn to program DSP. 5 | 6 | config.frameDivider = 1 7 | config.bufferSize = 16 8 | 9 | let phase = 0 10 | function process(block) { 11 | // Knob ranges from -5 to 5 octaves 12 | let pitch = block.knobs[0] * 10 - 5 13 | // Input follows 1V/oct standard 14 | // Take the first input's first buffer value 15 | pitch += block.inputs[0][0] 16 | 17 | // The relationship between 1V/oct pitch and frequency is `freq = 2^pitch`. 18 | // Default frequency is middle C (C4) in Hz. 19 | // https://vcvrack.com/manual/VoltageStandards.html#pitch-and-frequencies 20 | let freq = 261.6256 * Math.pow(2, pitch) 21 | display("Freq: " + freq.toFixed(3) + " Hz") 22 | 23 | // Set all samples in output buffer 24 | let deltaPhase = config.frameDivider * block.sampleTime * freq 25 | for (let i = 0; i < block.bufferSize; i++) { 26 | // Accumulate phase 27 | phase += deltaPhase 28 | // Wrap phase around range [0, 1] 29 | phase %= 1 30 | 31 | // Convert phase to sine output 32 | block.outputs[0][i] = Math.sin(2 * Math.PI * phase) * 5 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /examples/vco.lua: -------------------------------------------------------------------------------- 1 | -- Voltage-controlled oscillator example 2 | -- by Andrew Belt 3 | 4 | -- For audio synthesis and process, Lua is 10-100x less efficient than C++, but it's still an easy way to learn to program DSP. 5 | 6 | config.frameDivider = 1 7 | config.bufferSize = 16 8 | 9 | phase = 0 10 | function process(block) 11 | -- Knob ranges from -5 to 5 octaves 12 | pitch = block.knobs[1] * 10 - 5 13 | -- Input follows 1V/oct standard 14 | -- Take the first input's first buffer value 15 | pitch = pitch + block.inputs[1][1] 16 | 17 | -- The relationship between 1V/oct pitch and frequency is `freq = 2^pitch`. 18 | -- Default frequency is middle C (C4) in Hz. 19 | -- https://vcvrack.com/manual/VoltageStandards.html#pitch-and-frequencies 20 | freq = 261.6256 * math.pow(2, pitch) 21 | display("Freq: " .. string.format("%.3f", freq) .. " Hz") 22 | 23 | -- Set all samples in output buffer 24 | deltaPhase = config.frameDivider * block.sampleTime * freq 25 | for i=1,block.bufferSize do 26 | -- Accumulate phase 27 | phase = phase + deltaPhase 28 | -- Wrap phase around range [0, 1] 29 | phase = phase % 1 30 | 31 | -- Convert phase to sine output 32 | block.outputs[1][i] = math.sin(2 * math.pi * phase) * 5 33 | end 34 | end -------------------------------------------------------------------------------- /examples/vco.pd: -------------------------------------------------------------------------------- 1 | #N canvas 150 332 439 466 12; 2 | #X obj 69 34 route K1; 3 | #X obj 16 321 osc~; 4 | #X msg 101 346 display Freq: \$1 Hz; 5 | #X obj 115 112 adc~ 1; 6 | #X obj 69 111 sig~; 7 | #X obj 120 201 loadbang; 8 | #X msg 120 226 1; 9 | #X obj 120 251 tgl 15 0 empty empty empty 17 7 0 10 -262144 -1 -1 1 10 | 1; 11 | #X obj 120 272 metro 50; 12 | #X obj 101 297 snapshot~; 13 | #X obj 101 323 change; 14 | #X obj 69 138 +~, f 7; 15 | #X obj 69 60 * 10; 16 | #X obj 69 85 - 5; 17 | #X obj 16 185 *~ 261.626; 18 | #X obj 16 161 pow~ 2, f 8; 19 | #X obj 16 137 sig~ 2; 20 | #X obj 16 370 dac~ 1; 21 | #X text 145 162 <--- FM with CV signal from IN1; 22 | #X obj 16 346 *~ 5; 23 | #X obj 69 8 r fromRack; 24 | #X obj 101 370 print toRack; 25 | #X connect 0 0 12 0; 26 | #X connect 1 0 19 0; 27 | #X connect 2 0 21 0; 28 | #X connect 3 0 11 1; 29 | #X connect 4 0 11 0; 30 | #X connect 5 0 6 0; 31 | #X connect 6 0 7 0; 32 | #X connect 7 0 8 0; 33 | #X connect 8 0 9 0; 34 | #X connect 9 0 10 0; 35 | #X connect 10 0 2 0; 36 | #X connect 11 0 15 1; 37 | #X connect 12 0 13 0; 38 | #X connect 13 0 4 0; 39 | #X connect 14 0 1 0; 40 | #X connect 14 0 9 0; 41 | #X connect 15 0 14 0; 42 | #X connect 16 0 15 0; 43 | #X connect 19 0 17 0; 44 | #X connect 20 0 0 0; 45 | -------------------------------------------------------------------------------- /examples/vco.scd: -------------------------------------------------------------------------------- 1 | // Voltage-controlled oscillator example 2 | // by Andrew Belt 3 | // adapted for SC by Brian Heim 4 | 5 | ~vcv_frameDivider = 1; 6 | ~vcv_bufferSize = 32; 7 | 8 | ~phase = 0; 9 | ~vcv_process = { |block| 10 | 11 | var pitch, freq, deltaPhase; 12 | 13 | // Knob ranges from -5 to 5 octaves 14 | pitch = block.knobs[0] * 10 - 5; 15 | // Input follows 1V/oct standard 16 | // Take the first input's first buffer value 17 | pitch = pitch + block.inputs[0][0]; 18 | 19 | // The relationship between 1V/oct pitch and frequency is `freq = 2^pitch`. 20 | // Default frequency is middle C (C4) in Hz. 21 | // https://vcvrack.com/manual/VoltageStandards.html#pitch-and-frequencies 22 | freq = 261.6256 * pow(2, pitch); 23 | postf("Freq: % Hz", freq); 24 | 25 | deltaPhase = ~vcv_frameDivider * block.sampleTime * freq; 26 | 27 | // Set all samples in output buffer 28 | block.bufferSize.do { |i| 29 | // Accumulate phase & wrap around range [0, 1] 30 | ~phase = (~phase + deltaPhase) % 1.0; 31 | 32 | // Convert phase to sine output 33 | block.outputs[0][i] = sin(2pi * ~phase) * 5; 34 | }; 35 | 36 | block 37 | } 38 | -------------------------------------------------------------------------------- /examples/vco.vult: -------------------------------------------------------------------------------- 1 | 2 | /* 3 | 4 | Simple wave table VCO. 5 | 6 | Author: Leonardo Laguna Ruiz - leonardo@vult-dsp.com 7 | 8 | Check the API documentation in the basic.vult example 9 | 10 | */ 11 | 12 | fun pitchToFreq(cv) @[table(size=128, min=-10.0, max=10.0)] { 13 | return 261.6256 * exp(cv * 0.69314718056); 14 | } 15 | 16 | // Generates (at compile time) a wave table based on the provided harmonics 17 | // To change the wave table, change the 'harmonics' array 18 | fun wavetable(phase) @[table(size=128, min=0.0, max=1.0)] { 19 | val harmonics = [0.0, 1.0, 0.7, 0.5, 0.3]; 20 | val n = size(harmonics); 21 | val acc = 0.0; 22 | val i = 0; 23 | while(i < n) { 24 | acc = acc + harmonics[i] * sin(2.0 * pi() * real(i) * phase); 25 | i = i + 1; 26 | } 27 | return acc / sqrt(real(n)); 28 | } 29 | 30 | fun process(input_cv:real) { 31 | mem phase; 32 | val knob_cv = getKnob(1) - 0.5; 33 | val cv = 10.0 * (input_cv + knob_cv); 34 | val freq = pitchToFreq(cv); 35 | val delta = sampletime() * freq; 36 | phase = phase + delta; 37 | if(phase > 1.0) { 38 | phase = phase - 1.0; 39 | } 40 | 41 | return wavetable(phase); 42 | } 43 | and update() { 44 | 45 | } 46 | -------------------------------------------------------------------------------- /faust_libraries/rack.lib: -------------------------------------------------------------------------------- 1 | /* ---------------------------------------------------------------------------- 2 | 3 | ProtoFaust 4 | ========== 5 | DSP prototyping in Faust for VCV Rack 6 | 7 | Copyright (c) 2019-2020 Martin Zuther (http://www.mzuther.de/) and 8 | contributors 9 | 10 | This program is free software: you can redistribute it and/or modify 11 | it under the terms of the GNU General Public License as published by 12 | the Free Software Foundation, either version 3 of the License, or 13 | (at your option) any later version. 14 | 15 | This program is distributed in the hope that it will be useful, 16 | but WITHOUT ANY WARRANTY; without even the implied warranty of 17 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 18 | GNU General Public License for more details. 19 | 20 | You should have received a copy of the GNU General Public License 21 | along with this program. If not, see . 22 | 23 | Thank you for using free software! 24 | 25 | ---------------------------------------------------------------------------- */ 26 | 27 | 28 | // Converts 1 V/oct to frequency in Hertz. 29 | // 30 | // The conversion formula is: 440 * 2 ^ (volts - 0.75) 31 | // The factor 0.75 shifts 0 V to C-4 (261.6256 Hz) 32 | cv_pitch2freq(cv_pitch) = 440 * 2 ^ (cv_pitch - 0.75); 33 | 34 | 35 | // Converts frequency in Hertz to 1 V/oct. 36 | // 37 | // The conversion formula is: log2(hertz / 440) + 0.75 38 | // The factor 0.75 shifts 0 V to C-4 (261.6256 Hz) 39 | freq2cv_pitch(freq) = ma.log2(freq / 440) + 0.75; 40 | 41 | 42 | // Converts 200 mV/oct to frequency in Hertz. 43 | i_cv_pitch2freq(i_cv_pitch) = i_cv_pitch : internal2cv_pitch : cv_pitch2freq; 44 | 45 | 46 | // Converts frequency in Hertz to 200 mV/oct. 47 | freq2i_cv_pitch(freq) = freq : freq2cv_pitch : cv_pitch2internal; 48 | 49 | 50 | // Converts Eurorack's 1 V/oct to internal 200 mv/oct. 51 | cv_pitch2internal(cv_pitch) = cv_pitch / 5; 52 | 53 | 54 | // Converts internal 200 mv/oct to Eurorack's 1 V/oct. 55 | internal2cv_pitch(i_cv_pitch) = i_cv_pitch * 5; 56 | 57 | 58 | // Converts Eurorack's CV (range of 10V) to internal CV (range of 1V) 59 | cv2internal(cv) = cv / 10; 60 | 61 | 62 | // Converts internal CV (range of 1V) to Eurorack's CV (range of 10V) 63 | internal2cv(i_cv) = i_cv * 10; 64 | -------------------------------------------------------------------------------- /plugin.json: -------------------------------------------------------------------------------- 1 | { 2 | "slug": "VCV-Prototype", 3 | "name": "Prototype", 4 | "version": "1.3.0", 5 | "license": "GPL-3.0-or-later", 6 | "brand": "VCV", 7 | "author": "VCV", 8 | "authorEmail": "contact@vcvrack.com", 9 | "authorUrl": "https://vcvrack.com/", 10 | "pluginUrl": "https://vcvrack.com/Prototype.html", 11 | "manualUrl": "https://vcvrack.com/Prototype.html#manual", 12 | "sourceUrl": "https://github.com/VCVRack/VCV-Prototype", 13 | "donateUrl": "", 14 | "changelogUrl": "https://github.com/VCVRack/VCV-Prototype/blob/master/CHANGELOG.md", 15 | "modules": [ 16 | { 17 | "slug": "Prototype", 18 | "name": "Prototype", 19 | "description": "Run scripting languages for prototyping, learning, and live coding.", 20 | "tags": [ 21 | "External" 22 | ] 23 | } 24 | ] 25 | } -------------------------------------------------------------------------------- /src/DuktapeEngine.cpp: -------------------------------------------------------------------------------- 1 | #include "ScriptEngine.hpp" 2 | #include 3 | 4 | 5 | struct DuktapeEngine : ScriptEngine { 6 | duk_context* ctx = NULL; 7 | 8 | ~DuktapeEngine() { 9 | if (ctx) 10 | duk_destroy_heap(ctx); 11 | } 12 | 13 | std::string getEngineName() override { 14 | return "JavaScript"; 15 | } 16 | 17 | int run(const std::string& path, const std::string& script) override { 18 | assert(!ctx); 19 | ProcessBlock* block = getProcessBlock(); 20 | 21 | // Create duktape context 22 | ctx = duk_create_heap_default(); 23 | if (!ctx) { 24 | display("Could not create duktape context"); 25 | return -1; 26 | } 27 | 28 | // Initialize globals 29 | // user pointer 30 | duk_push_pointer(ctx, this); 31 | duk_put_global_string(ctx, DUK_HIDDEN_SYMBOL("engine")); 32 | 33 | // console 34 | duk_idx_t consoleIdx = duk_push_object(ctx); 35 | { 36 | // log 37 | duk_push_c_function(ctx, native_console_log, 1); 38 | duk_put_prop_string(ctx, consoleIdx, "log"); 39 | } 40 | duk_put_global_string(ctx, "console"); 41 | 42 | // display 43 | duk_push_c_function(ctx, native_display, 1); 44 | duk_put_global_string(ctx, "display"); 45 | 46 | // config: Set defaults 47 | duk_idx_t configIdx = duk_push_object(ctx); 48 | { 49 | // frameDivider 50 | duk_push_int(ctx, 32); 51 | duk_put_prop_string(ctx, configIdx, "frameDivider"); 52 | // bufferSize 53 | duk_push_int(ctx, 1); 54 | duk_put_prop_string(ctx, configIdx, "bufferSize"); 55 | } 56 | duk_put_global_string(ctx, "config"); 57 | 58 | // Compile string 59 | duk_push_string(ctx, path.c_str()); 60 | if (duk_pcompile_lstring_filename(ctx, 0, script.c_str(), script.size()) != 0) { 61 | const char* s = duk_safe_to_string(ctx, -1); 62 | WARN("duktape: %s", s); 63 | display(s); 64 | duk_pop(ctx); 65 | return -1; 66 | } 67 | // Execute function 68 | if (duk_pcall(ctx, 0)) { 69 | const char* s = duk_safe_to_string(ctx, -1); 70 | WARN("duktape: %s", s); 71 | display(s); 72 | duk_pop(ctx); 73 | return -1; 74 | } 75 | // Ignore return value 76 | duk_pop(ctx); 77 | 78 | // config: Read values 79 | duk_get_global_string(ctx, "config"); 80 | { 81 | // frameDivider 82 | duk_get_prop_string(ctx, -1, "frameDivider"); 83 | setFrameDivider(duk_get_int(ctx, -1)); 84 | duk_pop(ctx); 85 | // bufferSize 86 | duk_get_prop_string(ctx, -1, "bufferSize"); 87 | setBufferSize(duk_get_int(ctx, -1)); 88 | duk_pop(ctx); 89 | } 90 | duk_pop(ctx); 91 | 92 | // Keep process function on stack for faster calling 93 | duk_get_global_string(ctx, "process"); 94 | if (!duk_is_function(ctx, -1)) { 95 | display("No process() function"); 96 | return -1; 97 | } 98 | 99 | // block (keep on stack) 100 | duk_idx_t blockIdx = duk_push_object(ctx); 101 | { 102 | // inputs 103 | duk_idx_t inputsIdx = duk_push_array(ctx); 104 | for (int i = 0; i < NUM_ROWS; i++) { 105 | duk_push_external_buffer(ctx); 106 | duk_config_buffer(ctx, -1, block->inputs[i], sizeof(float) * block->bufferSize); 107 | duk_push_buffer_object(ctx, -1, 0, sizeof(float) * block->bufferSize, DUK_BUFOBJ_FLOAT32ARRAY); 108 | duk_put_prop_index(ctx, inputsIdx, i); 109 | duk_pop(ctx); 110 | } 111 | duk_put_prop_string(ctx, blockIdx, "inputs"); 112 | 113 | // outputs 114 | duk_idx_t outputsIdx = duk_push_array(ctx); 115 | for (int i = 0; i < NUM_ROWS; i++) { 116 | duk_push_external_buffer(ctx); 117 | duk_config_buffer(ctx, -1, block->outputs[i], sizeof(float) * block->bufferSize); 118 | duk_push_buffer_object(ctx, -1, 0, sizeof(float) * block->bufferSize, DUK_BUFOBJ_FLOAT32ARRAY); 119 | duk_put_prop_index(ctx, outputsIdx, i); 120 | duk_pop(ctx); 121 | } 122 | duk_put_prop_string(ctx, blockIdx, "outputs"); 123 | 124 | // knobs 125 | duk_push_external_buffer(ctx); 126 | duk_config_buffer(ctx, -1, block->knobs, sizeof(float) * NUM_ROWS); 127 | duk_push_buffer_object(ctx, -1, 0, sizeof(float) * NUM_ROWS, DUK_BUFOBJ_FLOAT32ARRAY); 128 | duk_put_prop_string(ctx, blockIdx, "knobs"); 129 | duk_pop(ctx); 130 | 131 | // switches 132 | duk_push_external_buffer(ctx); 133 | duk_config_buffer(ctx, -1, block->switches, sizeof(bool) * NUM_ROWS); 134 | duk_push_buffer_object(ctx, -1, 0, sizeof(bool) * NUM_ROWS, DUK_BUFOBJ_UINT8ARRAY); 135 | duk_put_prop_string(ctx, blockIdx, "switches"); 136 | duk_pop(ctx); 137 | 138 | // lights 139 | duk_idx_t lightsIdx = duk_push_array(ctx); 140 | for (int i = 0; i < NUM_ROWS; i++) { 141 | duk_push_external_buffer(ctx); 142 | duk_config_buffer(ctx, -1, block->lights[i], sizeof(float) * 3); 143 | duk_push_buffer_object(ctx, -1, 0, sizeof(float) * 3, DUK_BUFOBJ_FLOAT32ARRAY); 144 | duk_put_prop_index(ctx, lightsIdx, i); 145 | duk_pop(ctx); 146 | } 147 | duk_put_prop_string(ctx, blockIdx, "lights"); 148 | 149 | // switchLights 150 | duk_idx_t switchLightsIdx = duk_push_array(ctx); 151 | for (int i = 0; i < NUM_ROWS; i++) { 152 | duk_push_external_buffer(ctx); 153 | duk_config_buffer(ctx, -1, block->switchLights[i], sizeof(float) * 3); 154 | duk_push_buffer_object(ctx, -1, 0, sizeof(float) * 3, DUK_BUFOBJ_FLOAT32ARRAY); 155 | duk_put_prop_index(ctx, switchLightsIdx, i); 156 | duk_pop(ctx); 157 | } 158 | duk_put_prop_string(ctx, blockIdx, "switchLights"); 159 | } 160 | 161 | return 0; 162 | } 163 | 164 | int process() override { 165 | // block 166 | ProcessBlock* block = getProcessBlock(); 167 | duk_idx_t blockIdx = duk_get_top(ctx) - 1; 168 | { 169 | // sampleRate 170 | duk_push_number(ctx, block->sampleRate); 171 | duk_put_prop_string(ctx, blockIdx, "sampleRate"); 172 | 173 | // sampleTime 174 | duk_push_number(ctx, block->sampleTime); 175 | duk_put_prop_string(ctx, blockIdx, "sampleTime"); 176 | 177 | // bufferSize 178 | duk_push_int(ctx, block->bufferSize); 179 | duk_put_prop_string(ctx, blockIdx, "bufferSize"); 180 | } 181 | 182 | // Duplicate process function 183 | duk_dup(ctx, -2); 184 | // Duplicate block object 185 | duk_dup(ctx, -2); 186 | // Call process function 187 | if (duk_pcall(ctx, 1)) { 188 | const char* s = duk_safe_to_string(ctx, -1); 189 | WARN("duktape: %s", s); 190 | display(s); 191 | duk_pop(ctx); 192 | return -1; 193 | } 194 | // return value 195 | duk_pop(ctx); 196 | 197 | return 0; 198 | } 199 | 200 | static DuktapeEngine* getDuktapeEngine(duk_context* ctx) { 201 | duk_get_global_string(ctx, DUK_HIDDEN_SYMBOL("engine")); 202 | DuktapeEngine* engine = (DuktapeEngine*) duk_get_pointer(ctx, -1); 203 | duk_pop(ctx); 204 | return engine; 205 | } 206 | 207 | static duk_ret_t native_console_log(duk_context* ctx) { 208 | const char* s = duk_safe_to_string(ctx, -1); 209 | INFO("Duktape: %s", s); 210 | return 0; 211 | } 212 | static duk_ret_t native_display(duk_context* ctx) { 213 | const char* s = duk_safe_to_string(ctx, -1); 214 | getDuktapeEngine(ctx)->display(s); 215 | return 0; 216 | } 217 | }; 218 | 219 | 220 | __attribute__((constructor(1000))) 221 | static void constructor() { 222 | addScriptEngine("js"); 223 | } 224 | -------------------------------------------------------------------------------- /src/FaustEngine.cpp: -------------------------------------------------------------------------------- 1 | /************************************************************************ 2 | FAUST Architecture File 3 | Copyright (C) 2020 GRAME, Centre National de Creation Musicale 4 | --------------------------------------------------------------------- 5 | This Architecture section is free software; you can redistribute it 6 | and/or modify it under the terms of the GNU General Public License 7 | as published by the Free Software Foundation; either version 3 of 8 | the License, or (at your option) any later version. 9 | 10 | This program is distributed in the hope that it will be useful, 11 | but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | GNU General Public License for more details. 14 | 15 | You should have received a copy of the GNU General Public License 16 | along with this program; If not, see . 17 | 18 | EXCEPTION : As a special exception, you may create a larger work 19 | that contains this FAUST architecture section and distribute 20 | that work under terms of your choice, so long as this FAUST 21 | architecture section is not modified. 22 | ************************************************************************/ 23 | 24 | #include "ScriptEngine.hpp" 25 | 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include 31 | #include 32 | 33 | #pragma GCC diagnostic push 34 | #ifndef __clang__ 35 | #pragma GCC diagnostic ignored "-Wsuggest-override" 36 | #endif 37 | #include 38 | #include 39 | #include 40 | 41 | #define kBufferSize 64 42 | 43 | #ifdef INTERP 44 | #include 45 | #else 46 | #include 47 | #endif 48 | #pragma GCC diagnostic pop 49 | 50 | extern rack::Plugin* pluginInstance; 51 | 52 | // UI handler for switches, knobs and lights 53 | struct PrototypeUI : public GenericUI { 54 | typedef std::function updateFunction; 55 | 56 | std::vector fConverters; 57 | std::vector fUpdateFunIn; 58 | std::vector fUpdateFunOut; 59 | 60 | // For checkbox handling 61 | struct CheckBox { 62 | float fLastButton = 0.0f; 63 | }; 64 | std::map fCheckBoxes; 65 | 66 | std::string fKey, fValue, fScale; 67 | 68 | int getIndex(const std::string& value) { 69 | try { 70 | int index = stoi(value); 71 | if (index >= 0 && index <= NUM_ROWS) { 72 | return index; 73 | } 74 | else { 75 | WARN("ERROR : incorrect '%d' value", index); 76 | return -1; 77 | } 78 | } 79 | catch (std::invalid_argument& e) { 80 | return -1; 81 | } 82 | } 83 | 84 | PrototypeUI(): fScale("lin") 85 | {} 86 | 87 | virtual ~PrototypeUI() { 88 | for (auto& it : fConverters) 89 | delete it; 90 | } 91 | 92 | void addButton(const char* label, FAUSTFLOAT* zone) override { 93 | int index = getIndex(fValue); 94 | if (fKey == "switch" && (index != -1)) { 95 | fUpdateFunIn.push_back([ = ](ProcessBlock * block) { 96 | *zone = block->switches[index - 1]; 97 | 98 | // And set the color to red when ON 99 | block->switchLights[index - 1][0] = *zone; 100 | }); 101 | } 102 | fKey = fValue = ""; 103 | } 104 | 105 | void addCheckButton(const char* label, FAUSTFLOAT* zone) override { 106 | int index = getIndex(fValue); 107 | if (fKey == "switch" && (index != -1)) { 108 | // Add a checkbox 109 | fCheckBoxes[zone] = CheckBox(); 110 | // Update function 111 | fUpdateFunIn.push_back([ = ](ProcessBlock * block) { 112 | float button = block->switches[index - 1]; 113 | // Detect upfront 114 | if (button == 1.0 && (button != fCheckBoxes[zone].fLastButton)) { 115 | // Switch button state 116 | *zone = !*zone; 117 | // And set the color to white when ON 118 | block->switchLights[index - 1][0] = *zone; 119 | block->switchLights[index - 1][1] = *zone; 120 | block->switchLights[index - 1][2] = *zone; 121 | } 122 | // Keep previous button state 123 | fCheckBoxes[zone].fLastButton = button; 124 | }); 125 | } 126 | fKey = fValue = ""; 127 | } 128 | 129 | void addVerticalSlider(const char* label, FAUSTFLOAT* zone, FAUSTFLOAT init, FAUSTFLOAT min, FAUSTFLOAT max, FAUSTFLOAT step) override { 130 | addNumEntry(label, zone, init, min, max, step); 131 | } 132 | 133 | void addHorizontalSlider(const char* label, FAUSTFLOAT* zone, FAUSTFLOAT init, FAUSTFLOAT min, FAUSTFLOAT max, FAUSTFLOAT step) override { 134 | addNumEntry(label, zone, init, min, max, step); 135 | } 136 | 137 | void addNumEntry(const char* label, FAUSTFLOAT* zone, FAUSTFLOAT init, FAUSTFLOAT min, FAUSTFLOAT max, FAUSTFLOAT step) override { 138 | int index = getIndex(fValue); 139 | if (fKey == "knob" && (index != -1)) { 140 | ConverterZoneControl* converter; 141 | if (fScale == "log") { 142 | converter = new ConverterZoneControl(zone, new LogValueConverter(0., 1., min, max)); 143 | } 144 | else if (fScale == "exp") { 145 | converter = new ConverterZoneControl(zone, new ExpValueConverter(0., 1., min, max)); 146 | } 147 | else { 148 | converter = new ConverterZoneControl(zone, new LinearValueConverter(0., 1., min, max)); 149 | } 150 | fUpdateFunIn.push_back([ = ](ProcessBlock * block) { 151 | converter->update(block->knobs[index - 1]); 152 | }); 153 | fConverters.push_back(converter); 154 | } 155 | fScale = "lin"; 156 | fKey = fValue = ""; 157 | } 158 | 159 | void addBarGraph(FAUSTFLOAT* zone) { 160 | int index = getIndex(fValue); 161 | if ((fKey == "light_red") && (index != -1)) { 162 | fUpdateFunOut.push_back([ = ](ProcessBlock * block) { 163 | block->lights[index - 1][0] = *zone; 164 | }); 165 | } 166 | else if ((fKey == "light_green") && (index != -1)) { 167 | fUpdateFunOut.push_back([ = ](ProcessBlock * block) { 168 | block->lights[index - 1][1] = *zone; 169 | }); 170 | } 171 | else if ((fKey == "light_blue") && (index != -1)) { 172 | fUpdateFunOut.push_back([ = ](ProcessBlock * block) { 173 | block->lights[index - 1][2] = *zone; 174 | }); 175 | } 176 | else if ((fKey == "switchlight_red") && (index != -1)) { 177 | fUpdateFunOut.push_back([ = ](ProcessBlock * block) { 178 | block->switchLights[index - 1][0] = *zone; 179 | }); 180 | } 181 | else if ((fKey == "switchlight_green") && (index != -1)) { 182 | fUpdateFunOut.push_back([ = ](ProcessBlock * block) { 183 | block->switchLights[index - 1][1] = *zone; 184 | }); 185 | } 186 | else if ((fKey == "switchlight_blue") && (index != -1)) { 187 | fUpdateFunOut.push_back([ = ](ProcessBlock * block) { 188 | block->switchLights[index - 1][2] = *zone; 189 | }); 190 | } 191 | fKey = fValue = ""; 192 | } 193 | 194 | void addHorizontalBargraph(const char* label, FAUSTFLOAT* zone, FAUSTFLOAT min, FAUSTFLOAT max) override { 195 | addBarGraph(zone); 196 | } 197 | 198 | void addVerticalBargraph(const char* label, FAUSTFLOAT* zone, FAUSTFLOAT min, FAUSTFLOAT max) override { 199 | addBarGraph(zone); 200 | } 201 | 202 | void addSoundfile(const char* label, const char* soundpath, Soundfile** sf_zone) override { 203 | WARN("Faust Prototype : 'soundfile' primitive not yet supported"); 204 | } 205 | 206 | void declare(FAUSTFLOAT* zone, const char* key, const char* val) override { 207 | static std::vector keys = {"switch", "knob", "light_red", "light_green", "light_blue", "switchlight_red", "switchlight_green", "switchlight_blue"}; 208 | if (find(keys.begin(), keys.end(), key) != keys.end()) { 209 | fKey = key; 210 | fValue = val; 211 | } 212 | else if (std::string(key) == "scale") { 213 | fScale = val; 214 | } 215 | } 216 | }; 217 | 218 | // Faust engine using libfaust/LLVM 219 | class FaustEngine : public ScriptEngine { 220 | 221 | public: 222 | 223 | FaustEngine(): 224 | fDSPFactory(nullptr), 225 | fDSP(nullptr), 226 | fInputs(nullptr), 227 | fOutputs(nullptr), 228 | fDSPLibraries(rack::asset::plugin(pluginInstance, "faust_libraries")) 229 | {} 230 | 231 | ~FaustEngine() { 232 | delete [] fInputs; 233 | delete [] fOutputs; 234 | delete fDSP; 235 | #ifdef INTERP 236 | deleteInterpreterDSPFactory(static_cast(fDSPFactory)); 237 | #else 238 | deleteDSPFactory(static_cast(fDSPFactory)); 239 | #endif 240 | } 241 | 242 | std::string getEngineName() override { 243 | return "Faust"; 244 | } 245 | 246 | int run(const std::string& path, const std::string& script) override { 247 | #if defined ARCH_LIN 248 | std::string temp_cache = "/tmp/VCVPrototype_" + generateSHA1(script); 249 | #elif defined ARCH_MAC 250 | std::string temp_cache = "/tmp/VCVPrototype_" + generateSHA1(script); 251 | #elif defined ARCH_WIN 252 | char buf[MAX_PATH + 1] = {0}; 253 | GetTempPath(sizeof(buf), buf); 254 | std::string temp_cache = std::string(buf) + "/VCVPrototype_" + generateSHA1(script); 255 | #endif 256 | std::string error_msg; 257 | 258 | // Try to load the machine code cache 259 | #ifdef INTERP 260 | fDSPFactory = readInterpreterDSPFactoryFromBitcodeFile(temp_cache, error_msg); 261 | #else 262 | fDSPFactory = readDSPFactoryFromMachineFile(temp_cache, "", error_msg); 263 | #endif 264 | 265 | if (!fDSPFactory) { 266 | // Otherwise recompile the DSP 267 | int argc = 0; 268 | const char* argv[8]; 269 | argv[argc++] = "-I"; 270 | argv[argc++] = fDSPLibraries.c_str(); 271 | argv[argc] = nullptr; // NULL terminated argv 272 | 273 | #ifdef INTERP 274 | fDSPFactory = createInterpreterDSPFactoryFromString("FaustDSP", script, argc, argv, error_msg); 275 | #else 276 | fDSPFactory = createDSPFactoryFromString("FaustDSP", script, argc, argv, "", error_msg, -1); 277 | #endif 278 | if (!fDSPFactory) { 279 | display("ERROR : cannot create factory !"); 280 | WARN("Faust Prototype : %s", error_msg.c_str()); 281 | return -1; 282 | } 283 | else { 284 | // And save the cache 285 | display("Compiling factory finished"); 286 | #ifdef INTERP 287 | writeInterpreterDSPFactoryToBitcodeFile(static_cast(fDSPFactory), temp_cache); 288 | #else 289 | writeDSPFactoryToMachineFile(static_cast(fDSPFactory), temp_cache, ""); 290 | #endif 291 | } 292 | } 293 | 294 | // Create DSP 295 | fDSP = fDSPFactory->createDSPInstance(); 296 | if (!fDSP) { 297 | display("ERROR: cannot create instance !"); 298 | return -1; 299 | } 300 | else { 301 | display("Created DSP"); 302 | } 303 | 304 | // Check inputs/outputs 305 | if (fDSP->getNumInputs() > NUM_ROWS) { 306 | display("ERROR: DSP has " + std::to_string(fDSP->getNumInputs()) + " inputs !"); 307 | return -1; 308 | } 309 | 310 | if (fDSP->getNumOutputs() > NUM_ROWS) { 311 | display("ERROR: DSP has " + std::to_string(fDSP->getNumInputs()) + " outputs !"); 312 | return -1; 313 | } 314 | 315 | // Prepare buffers for process 316 | ProcessBlock* block = getProcessBlock(); 317 | 318 | fInputs = new FAUSTFLOAT*[fDSP->getNumInputs()]; 319 | for (int chan = 0; chan < fDSP->getNumInputs(); chan++) { 320 | fInputs[chan] = block->inputs[chan]; 321 | } 322 | 323 | fOutputs = new FAUSTFLOAT*[fDSP->getNumOutputs()]; 324 | for (int chan = 0; chan < fDSP->getNumOutputs(); chan++) { 325 | fOutputs[chan] = block->outputs[chan]; 326 | } 327 | 328 | // Setup UI 329 | fDSP->buildUserInterface(&fPrototypeUI); 330 | 331 | setFrameDivider(1); 332 | setBufferSize(kBufferSize); 333 | 334 | // Init DSP with default SR 335 | fDSP->init(44100); 336 | return 0; 337 | } 338 | 339 | int process() override { 340 | ProcessBlock* block = getProcessBlock(); 341 | 342 | // Possibly update SR 343 | if (block->sampleRate != fDSP->getSampleRate()) { 344 | fDSP->init(block->sampleRate); 345 | } 346 | 347 | // Update inputs controllers 348 | for (auto& it : fPrototypeUI.fUpdateFunIn) 349 | it(block); 350 | 351 | // Compute samples 352 | fDSP->compute(block->bufferSize, fInputs, fOutputs); 353 | 354 | // Update output controllers 355 | for (auto& it : fPrototypeUI.fUpdateFunOut) 356 | it(block); 357 | 358 | return 0; 359 | } 360 | 361 | private: 362 | dsp_factory* fDSPFactory; 363 | dsp* fDSP; 364 | FAUSTFLOAT** fInputs; 365 | FAUSTFLOAT** fOutputs; 366 | PrototypeUI fPrototypeUI; 367 | std::string fDSPLibraries; 368 | }; 369 | 370 | __attribute__((constructor(1000))) 371 | static void constructor() { 372 | addScriptEngine("dsp"); 373 | } 374 | -------------------------------------------------------------------------------- /src/LibPDEngine.cpp: -------------------------------------------------------------------------------- 1 | #include "ScriptEngine.hpp" 2 | #include "z_libpd.h" 3 | #include "util/z_print_util.h" 4 | using namespace rack; 5 | 6 | static const int BUFFERSIZE = MAX_BUFFER_SIZE * NUM_ROWS; 7 | 8 | 9 | // there is no multi-instance support for receiving messages from libpd 10 | // for now, received values for the prototype gui will be stored in global variables 11 | 12 | static float g_lights[NUM_ROWS][3] = {}; 13 | static float g_switchLights[NUM_ROWS][3] = {}; 14 | static std::string g_utility[2] = {}; 15 | static bool g_display_is_valid = false; 16 | 17 | static std::vector split(const std::string& s, char delim) { 18 | std::vector result; 19 | std::stringstream ss(s); 20 | std::string item; 21 | 22 | while (getline(ss, item, delim)) { 23 | result.push_back(item); 24 | } 25 | 26 | return result; 27 | } 28 | 29 | 30 | struct LibPDEngine : ScriptEngine { 31 | t_pdinstance* _lpd = NULL; 32 | int _pd_block_size = 64; 33 | int _sampleRate = 0; 34 | int _ticks = 0; 35 | bool _init = true; 36 | 37 | float _old_knobs[NUM_ROWS] = {}; 38 | bool _old_switches[NUM_ROWS] = {}; 39 | float _output[BUFFERSIZE] = {}; 40 | float _input[BUFFERSIZE] = {};// = (float*)malloc(1024*2*sizeof(float)); 41 | const static std::map _light_map; 42 | const static std::map _switchLight_map; 43 | const static std::map _utility_map; 44 | 45 | ~LibPDEngine() { 46 | if (_lpd) 47 | libpd_free_instance(_lpd); 48 | } 49 | 50 | void sendInitialStates(const ProcessBlock* block); 51 | static void receiveLights(const char* s); 52 | bool knobChanged(const float* knobs, int idx); 53 | bool switchChanged(const bool* knobs, int idx); 54 | void sendKnob(const int idx, const float value); 55 | void sendSwitch(const int idx, const bool value); 56 | 57 | std::string getEngineName() override { 58 | return "Pure Data"; 59 | } 60 | 61 | int run(const std::string& path, const std::string& script) override { 62 | ProcessBlock* block = getProcessBlock(); 63 | _sampleRate = block->sampleRate; 64 | setBufferSize(_pd_block_size); 65 | setFrameDivider(1); 66 | libpd_init(); 67 | _lpd = libpd_new_instance(); 68 | 69 | libpd_set_printhook((t_libpd_printhook)libpd_print_concatenator); 70 | libpd_set_concatenated_printhook(receiveLights); 71 | 72 | if (libpd_num_instances() > 2) { 73 | display("Multiple simultaneous libpd (Pure Data) instances not yet supported."); 74 | return -1; 75 | } 76 | 77 | //display(std::to_string(libpd_num_instances())); 78 | libpd_init_audio(NUM_ROWS, NUM_ROWS, _sampleRate); 79 | 80 | // compute audio [; pd dsp 1( 81 | libpd_start_message(1); // one enstry in list 82 | libpd_add_float(1.0f); 83 | libpd_finish_message("pd", "dsp"); 84 | 85 | std::string version = "pd " + std::to_string(PD_MAJOR_VERSION) + "." + 86 | std::to_string(PD_MINOR_VERSION) + "." + 87 | std::to_string(PD_BUGFIX_VERSION); 88 | 89 | display(version); 90 | 91 | std::string name = string::filename(path); 92 | std::string dir = string::directory(path); 93 | libpd_openfile(name.c_str(), dir.c_str()); 94 | 95 | sendInitialStates(block); 96 | 97 | return 0; 98 | } 99 | 100 | int process() override { 101 | // block 102 | ProcessBlock* block = getProcessBlock(); 103 | 104 | // get samples prototype 105 | int rows = NUM_ROWS; 106 | for (int s = 0; s < _pd_block_size; s++) { 107 | for (int r = 0; r < rows; r++) { 108 | _input[s * rows + r] = block->inputs[r][s]; 109 | } 110 | } 111 | 112 | libpd_set_instance(_lpd); 113 | 114 | // knobs 115 | for (int i = 0; i < NUM_ROWS; i++) { 116 | if (knobChanged(block->knobs, i)) { 117 | sendKnob(i, block->knobs[i]); 118 | } 119 | } 120 | // lights 121 | for (int i = 0; i < NUM_ROWS; i++) { 122 | block->lights[i][0] = g_lights[i][0]; 123 | block->lights[i][1] = g_lights[i][1]; 124 | block->lights[i][2] = g_lights[i][2]; 125 | } 126 | // switch lights 127 | for (int i = 0; i < NUM_ROWS; i++) { 128 | block->switchLights[i][0] = g_switchLights[i][0]; 129 | block->switchLights[i][1] = g_switchLights[i][1]; 130 | block->switchLights[i][2] = g_switchLights[i][2]; 131 | } 132 | // switches 133 | for (int i = 0; i < NUM_ROWS; i++) { 134 | if (switchChanged(block->switches, i)) { 135 | sendSwitch(i, block->switches[i]); 136 | } 137 | } 138 | 139 | // display 140 | if (g_display_is_valid) { 141 | display(g_utility[1]); 142 | g_display_is_valid = false; 143 | } 144 | // process samples in libpd 145 | _ticks = 1; 146 | libpd_process_float(_ticks, _input, _output); 147 | 148 | // return samples to prototype 149 | for (int s = 0; s < _pd_block_size; s++) { 150 | for (int r = 0; r < rows; r++) { 151 | block->outputs[r][s] = _output[s * rows + r]; // scale up again to +-5V signal 152 | // there is a correction multiplier, because libpd's output is too quiet(?) 153 | } 154 | } 155 | 156 | return 0; 157 | } 158 | }; 159 | 160 | 161 | void LibPDEngine::receiveLights(const char* s) { 162 | std::string str = std::string(s); 163 | std::vector atoms = split(str, ' '); 164 | 165 | if (atoms[0] == "toRack:") { 166 | // parse lights list 167 | bool light_is_valid = true; 168 | int light_idx = -1; 169 | try { 170 | light_idx = _light_map.at(atoms[1]); // map::at throws an out-of-range 171 | } 172 | catch (const std::out_of_range& oor) { 173 | light_is_valid = false; 174 | //display("Warning:"+atoms[1]+" not found!"); 175 | } 176 | //std::cout << v[1] << ", " << g_led_map[v[1]] << std::endl; 177 | if (light_is_valid && atoms.size() == 5) { 178 | g_lights[light_idx][0] = stof(atoms[2]); // red 179 | g_lights[light_idx][1] = stof(atoms[3]); // green 180 | g_lights[light_idx][2] = stof(atoms[4]); // blue 181 | } 182 | else { 183 | // error 184 | } 185 | // parse switch lights list 186 | bool switchLight_is_valid = true; 187 | int switchLight_idx = -1; 188 | try { 189 | switchLight_idx = _switchLight_map.at(atoms[1]); // map::at throws an out-of-range 190 | } 191 | catch (const std::out_of_range& oor) { 192 | switchLight_is_valid = false; 193 | //display("Warning:"+atoms[1]+" not found!"); 194 | } 195 | //std::cout << v[1] << ", " << g_led_map[v[1]] << std::endl; 196 | if (switchLight_is_valid && atoms.size() == 5) { 197 | g_switchLights[switchLight_idx][0] = stof(atoms[2]); // red 198 | g_switchLights[switchLight_idx][1] = stof(atoms[3]); // green 199 | g_switchLights[switchLight_idx][2] = stof(atoms[4]); // blue 200 | } 201 | else { 202 | // error 203 | } 204 | 205 | // parse switch lights list 206 | bool utility_is_valid = true; 207 | try { 208 | _utility_map.at(atoms[1]); // map::at throws an out-of-range 209 | } 210 | catch (const std::out_of_range& oor) { 211 | utility_is_valid = false; 212 | //g_display_is_valid = true; 213 | //display("Warning:"+atoms[1]+" not found!"); 214 | } 215 | //std::cout << v[1] << ", " << g_led_map[v[1]] << std::endl; 216 | if (utility_is_valid && atoms.size() >= 3) { 217 | g_utility[0] = atoms[1]; // display 218 | g_utility[1] = ""; 219 | for (unsigned i = 0; i < atoms.size() - 2; i++) { 220 | g_utility[1] += " " + atoms[i + 2]; // concatenate message 221 | } 222 | g_display_is_valid = true; 223 | } 224 | else { 225 | // error 226 | } 227 | } 228 | else { 229 | bool utility_is_valid = true; 230 | int utility_idx = -1; 231 | try { 232 | utility_idx = _utility_map.at(atoms[0]); // map::at throws an out-of-range 233 | } 234 | catch (const std::out_of_range& oor) { 235 | WARN("Prototype libpd: %s", s); 236 | utility_is_valid = false; 237 | //display("Warning:"+atoms[1]+" not found!"); 238 | // print out on command line 239 | } 240 | if (utility_is_valid) { 241 | switch (utility_idx) { 242 | case 1: 243 | WARN("Prototype libpd: %s", s); 244 | break; 245 | 246 | default: 247 | break; 248 | } 249 | } 250 | } 251 | } 252 | 253 | bool LibPDEngine::knobChanged(const float* knobs, int i) { 254 | bool knob_changed = false; 255 | if (_old_knobs[i] != knobs[i]) { 256 | knob_changed = true; 257 | _old_knobs[i] = knobs[i]; 258 | } 259 | return knob_changed; 260 | } 261 | 262 | bool LibPDEngine::switchChanged(const bool* switches, int i) { 263 | bool switch_changed = false; 264 | if (_old_switches[i] != switches[i]) { 265 | switch_changed = true; 266 | _old_switches[i] = switches[i]; 267 | } 268 | return switch_changed; 269 | } 270 | 271 | const std::map LibPDEngine::_light_map{ 272 | { "L1", 0 }, 273 | { "L2", 1 }, 274 | { "L3", 2 }, 275 | { "L4", 3 }, 276 | { "L5", 4 }, 277 | { "L6", 5 } 278 | }; 279 | 280 | const std::map LibPDEngine::_switchLight_map{ 281 | { "S1", 0 }, 282 | { "S2", 1 }, 283 | { "S3", 2 }, 284 | { "S4", 3 }, 285 | { "S5", 4 }, 286 | { "S6", 5 } 287 | }; 288 | 289 | const std::map LibPDEngine::_utility_map{ 290 | { "display", 0 }, 291 | { "error:", 1 } 292 | }; 293 | 294 | 295 | void LibPDEngine::sendKnob(const int idx, const float value) { 296 | std::string knob = "K" + std::to_string(idx + 1); 297 | libpd_start_message(1); 298 | libpd_add_float(value); 299 | libpd_finish_message("fromRack", knob.c_str()); 300 | } 301 | 302 | void LibPDEngine::sendSwitch(const int idx, const bool value) { 303 | std::string sw = "S" + std::to_string(idx + 1); 304 | libpd_start_message(1); 305 | libpd_add_float(value); 306 | libpd_finish_message("fromRack", sw.c_str()); 307 | } 308 | 309 | void LibPDEngine::sendInitialStates(const ProcessBlock* block) { 310 | // knobs 311 | for (int i = 0; i < NUM_ROWS; i++) { 312 | sendKnob(i, block->knobs[i]); 313 | sendSwitch(i, block->knobs[i]); 314 | } 315 | 316 | for (int i = 0; i < NUM_ROWS; i++) { 317 | g_lights[i][0] = 0; 318 | g_lights[i][1] = 0; 319 | g_lights[i][2] = 0; 320 | g_switchLights[i][0] = 0; 321 | g_switchLights[i][1] = 0; 322 | g_switchLights[i][2] = 0; 323 | } 324 | 325 | //g_utility[0] = ""; 326 | //g_utility[1] = ""; 327 | 328 | //g_display_is_valid = false; 329 | } 330 | 331 | 332 | __attribute__((constructor(1000))) 333 | static void constructor() { 334 | addScriptEngine("pd"); 335 | } 336 | -------------------------------------------------------------------------------- /src/LuaJITEngine.cpp: -------------------------------------------------------------------------------- 1 | #include "ScriptEngine.hpp" 2 | #include 3 | 4 | 5 | struct LuaJITEngine : ScriptEngine { 6 | lua_State* L = NULL; 7 | 8 | // This is a mirror of ProcessBlock that we are going to use 9 | // to provide 1-based indices within the Lua VM 10 | struct LuaProcessBlock { 11 | float sampleRate; 12 | float sampleTime; 13 | int bufferSize; 14 | float* inputs[NUM_ROWS + 1]; 15 | float* outputs[NUM_ROWS + 1]; 16 | float* knobs; 17 | bool* switches; 18 | float* lights[NUM_ROWS + 1]; 19 | float* switchLights[NUM_ROWS + 1]; 20 | }; 21 | 22 | LuaProcessBlock luaBlock; 23 | 24 | ~LuaJITEngine() { 25 | if (L) 26 | lua_close(L); 27 | } 28 | 29 | std::string getEngineName() override { 30 | return "Lua"; 31 | } 32 | 33 | int run(const std::string& path, const std::string& script) override { 34 | ProcessBlock* block = getProcessBlock(); 35 | 36 | // Initialize all the pointers with an offset of -1 37 | #pragma GCC diagnostic push 38 | #pragma GCC diagnostic ignored "-Warray-bounds" 39 | luaBlock.knobs = &block->knobs[-1]; 40 | luaBlock.switches = &block->switches[-1]; 41 | 42 | for (int i = 0; i < NUM_ROWS; i++) { 43 | luaBlock.inputs[i + 1] = &block->inputs[i][-1]; 44 | luaBlock.outputs[i + 1] = &block->outputs[i][-1]; 45 | luaBlock.lights[i + 1] = &block->lights[i][-1]; 46 | luaBlock.switchLights[i + 1] = &block->switchLights[i][-1]; 47 | } 48 | #pragma GCC diagnostic pop 49 | 50 | L = luaL_newstate(); 51 | if (!L) { 52 | display("Could not create LuaJIT context"); 53 | return -1; 54 | } 55 | 56 | // Import a subset of the standard library 57 | static const luaL_Reg lj_lib_load[] = { 58 | {"", luaopen_base}, 59 | {LUA_LOADLIBNAME, luaopen_package}, 60 | {LUA_TABLIBNAME, luaopen_table}, 61 | {LUA_STRLIBNAME, luaopen_string}, 62 | {LUA_MATHLIBNAME, luaopen_math}, 63 | {LUA_BITLIBNAME, luaopen_bit}, 64 | {LUA_JITLIBNAME, luaopen_jit}, 65 | {LUA_FFILIBNAME, luaopen_ffi}, 66 | {NULL, NULL} 67 | }; 68 | for (const luaL_Reg* lib = lj_lib_load; lib->func; lib++) { 69 | lua_pushcfunction(L, lib->func); 70 | lua_pushstring(L, lib->name); 71 | lua_call(L, 1, 0); 72 | } 73 | 74 | // Set user pointer 75 | lua_pushlightuserdata(L, this); 76 | lua_setglobal(L, "_engine"); 77 | 78 | // Set global functions 79 | // lua_pushcfunction(L, native_print); 80 | // lua_setglobal(L, "print"); 81 | 82 | lua_pushcfunction(L, native_display); 83 | lua_setglobal(L, "display"); 84 | 85 | // Set config 86 | lua_newtable(L); 87 | { 88 | // frameDivider 89 | lua_pushinteger(L, 32); 90 | lua_setfield(L, -2, "frameDivider"); 91 | // bufferSize 92 | lua_pushinteger(L, 1); 93 | lua_setfield(L, -2, "bufferSize"); 94 | } 95 | lua_setglobal(L, "config"); 96 | 97 | // Load the FFI auxiliary functions. 98 | std::stringstream ffi_stream; 99 | ffi_stream 100 | << "local ffi = require('ffi')" << std::endl 101 | // Describe the struct `LuaProcessBlock` so that LuaJIT knows how to access the data 102 | << "ffi.cdef[[" << std::endl 103 | << "struct LuaProcessBlock {" << std::endl 104 | << "float sampleRate;" << std::endl 105 | << "float sampleTime;" << std::endl 106 | << "int bufferSize;" << std::endl 107 | << "float *inputs[" << NUM_ROWS + 1 << "];" << std::endl 108 | << "float *outputs[" << NUM_ROWS + 1 << "];" << std::endl 109 | << "float *knobs;" << std::endl 110 | << "bool *switches;" << std::endl 111 | << "float *lights[" << NUM_ROWS + 1 << "];" << std::endl 112 | << "float *switchLights[" << NUM_ROWS + 1 << "];" << std::endl 113 | << "};]]" << std::endl 114 | // Declare the function `_castBlock` used to transform `luaBlock` pointer into a LuaJIT cdata 115 | << "_ffi_cast = ffi.cast" << std::endl 116 | << "function _castBlock(b) return _ffi_cast('struct LuaProcessBlock*', b) end" << std::endl 117 | // Remove global functions that could be abused 118 | << "jit = nil; require = nil; ffi = nil; load = nil; loadfile = nil; loadstring = nil; dofile = nil;" << std::endl; 119 | std::string ffi_script = ffi_stream.str(); 120 | 121 | // Compile the ffi script 122 | if (luaL_loadbuffer(L, ffi_script.c_str(), ffi_script.size(), "ffi_script.lua")) { 123 | const char* s = lua_tostring(L, -1); 124 | WARN("LuaJIT: %s", s); 125 | display(s); 126 | lua_pop(L, 1); 127 | return -1; 128 | } 129 | 130 | // Run the ffi script 131 | if (lua_pcall(L, 0, 0, 0)) { 132 | const char* s = lua_tostring(L, -1); 133 | WARN("LuaJIT: %s", s); 134 | display(s); 135 | lua_pop(L, 1); 136 | return -1; 137 | } 138 | 139 | // Compile user script 140 | if (luaL_loadbuffer(L, script.c_str(), script.size(), path.c_str())) { 141 | const char* s = lua_tostring(L, -1); 142 | WARN("LuaJIT: %s", s); 143 | display(s); 144 | lua_pop(L, 1); 145 | return -1; 146 | } 147 | 148 | // Run script 149 | if (lua_pcall(L, 0, 0, 0)) { 150 | const char* s = lua_tostring(L, -1); 151 | WARN("LuaJIT: %s", s); 152 | display(s); 153 | lua_pop(L, 1); 154 | return -1; 155 | } 156 | 157 | // Get config 158 | lua_getglobal(L, "config"); 159 | { 160 | // frameDivider 161 | lua_getfield(L, -1, "frameDivider"); 162 | int frameDivider = lua_tointeger(L, -1); 163 | setFrameDivider(frameDivider); 164 | lua_pop(L, 1); 165 | // bufferSize 166 | lua_getfield(L, -1, "bufferSize"); 167 | int bufferSize = lua_tointeger(L, -1); 168 | setBufferSize(bufferSize); 169 | lua_pop(L, 1); 170 | } 171 | lua_pop(L, 1); 172 | 173 | // Get process function 174 | lua_getglobal(L, "process"); 175 | if (!lua_isfunction(L, -1)) { 176 | display("No process() function"); 177 | return -1; 178 | } 179 | 180 | // Create block object 181 | lua_getglobal(L, "_castBlock"); 182 | lua_pushlightuserdata(L, (void*) &luaBlock); 183 | if (lua_pcall(L, 1, 1, 0)) { 184 | const char* s = lua_tostring(L, -1); 185 | WARN("LuaJIT: Error casting block: %s", s); 186 | display(s); 187 | lua_pop(L, 1); 188 | return -1; 189 | } 190 | 191 | return 0; 192 | } 193 | 194 | int process() override { 195 | ProcessBlock* block = getProcessBlock(); 196 | 197 | // Update the values of the block. 198 | // The pointer values do not change. 199 | luaBlock.sampleRate = block->sampleRate; 200 | luaBlock.sampleTime = block->sampleTime; 201 | luaBlock.bufferSize = block->bufferSize; 202 | 203 | // Duplicate process function 204 | lua_pushvalue(L, -2); 205 | // Duplicate block 206 | lua_pushvalue(L, -2); 207 | // Call process function 208 | if (lua_pcall(L, 1, 0, 0)) { 209 | const char* err = lua_tostring(L, -1); 210 | WARN("LuaJIT: %s", err); 211 | display(err); 212 | return -1; 213 | } 214 | 215 | return 0; 216 | } 217 | 218 | static LuaJITEngine* getEngine(lua_State* L) { 219 | lua_getglobal(L, "_engine"); 220 | LuaJITEngine* engine = (LuaJITEngine*) lua_touserdata(L, -1); 221 | lua_pop(L, 1); 222 | return engine; 223 | } 224 | 225 | // static int native_print(lua_State* L) { 226 | // lua_getglobal(L, "tostring"); 227 | // lua_pushvalue(L, 1); 228 | // lua_call(L, 1, 1); 229 | // const char* s = lua_tostring(L, 1); 230 | // INFO("LuaJIT: %s", s); 231 | // return 0; 232 | // } 233 | 234 | static int native_display(lua_State* L) { 235 | lua_getglobal(L, "tostring"); 236 | lua_pushvalue(L, 1); 237 | lua_call(L, 1, 1); 238 | const char* s = lua_tostring(L, -1); 239 | if (!s) 240 | s = "(null)"; 241 | getEngine(L)->display(s); 242 | return 0; 243 | } 244 | }; 245 | 246 | 247 | __attribute__((constructor(1000))) 248 | static void constructor() { 249 | addScriptEngine("lua"); 250 | } 251 | -------------------------------------------------------------------------------- /src/Prototype.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include "ScriptEngine.hpp" 9 | #include 10 | #if defined ARCH_WIN 11 | #include 12 | #endif 13 | 14 | 15 | using namespace rack; 16 | Plugin* pluginInstance; 17 | 18 | 19 | // Don't bother deleting this with a destructor. 20 | __attribute((init_priority(999))) 21 | std::map scriptEngineFactories; 22 | 23 | ScriptEngine* createScriptEngine(std::string extension) { 24 | auto it = scriptEngineFactories.find(extension); 25 | if (it == scriptEngineFactories.end()) 26 | return NULL; 27 | return it->second->createScriptEngine(); 28 | } 29 | 30 | 31 | static std::string settingsEditorPath; 32 | static std::string settingsPdEditorPath = 33 | #if defined ARCH_LIN 34 | "\"/usr/bin/pd-gui\""; 35 | #else 36 | ""; 37 | #endif 38 | 39 | 40 | json_t* settingsToJson() { 41 | json_t* rootJ = json_object(); 42 | json_object_set_new(rootJ, "editorPath", json_string(settingsEditorPath.c_str())); 43 | json_object_set_new(rootJ, "pdEditorPath", json_string(settingsPdEditorPath.c_str())); 44 | return rootJ; 45 | } 46 | 47 | void settingsFromJson(json_t* rootJ) { 48 | json_t* editorPathJ = json_object_get(rootJ, "editorPath"); 49 | if (editorPathJ) 50 | settingsEditorPath = json_string_value(editorPathJ); 51 | 52 | json_t* pdEditorPathJ = json_object_get(rootJ, "pdEditorPath"); 53 | if (pdEditorPathJ) 54 | settingsPdEditorPath = json_string_value(pdEditorPathJ); 55 | } 56 | 57 | void settingsLoad() { 58 | // Load plugin settings 59 | std::string filename = asset::user("VCV-Prototype.json"); 60 | FILE* file = std::fopen(filename.c_str(), "r"); 61 | if (!file) { 62 | return; 63 | } 64 | DEFER({ 65 | std::fclose(file); 66 | }); 67 | 68 | json_error_t error; 69 | json_t* rootJ = json_loadf(file, 0, &error); 70 | if (rootJ) { 71 | settingsFromJson(rootJ); 72 | json_decref(rootJ); 73 | } 74 | } 75 | 76 | void settingsSave() { 77 | json_t* rootJ = settingsToJson(); 78 | 79 | std::string filename = asset::user("VCV-Prototype.json"); 80 | FILE* file = std::fopen(filename.c_str(), "w"); 81 | if (file) { 82 | json_dumpf(rootJ, file, JSON_INDENT(2) | JSON_REAL_PRECISION(9)); 83 | std::fclose(file); 84 | } 85 | 86 | json_decref(rootJ); 87 | } 88 | 89 | std::string getApplicationPathDialog() { 90 | char* pathC = NULL; 91 | #if defined ARCH_LIN 92 | pathC = osdialog_file(OSDIALOG_OPEN, "/usr/bin/", NULL, NULL); 93 | #elif defined ARCH_WIN 94 | osdialog_filters* filters = osdialog_filters_parse("Executable:exe"); 95 | pathC = osdialog_file(OSDIALOG_OPEN, "C:/", NULL, filters); 96 | osdialog_filters_free(filters); 97 | #elif defined ARCH_MAC 98 | osdialog_filters* filters = osdialog_filters_parse("Application:app"); 99 | pathC = osdialog_file(OSDIALOG_OPEN, "/Applications/", NULL, filters); 100 | osdialog_filters_free(filters); 101 | #endif 102 | if (!pathC) 103 | return ""; 104 | 105 | std::string path = "\""; 106 | path += pathC; 107 | path += "\""; 108 | std::free(pathC); 109 | return path; 110 | } 111 | 112 | void setEditorDialog() { 113 | std::string path = getApplicationPathDialog(); 114 | if (path == "") 115 | return; 116 | settingsEditorPath = path; 117 | settingsSave(); 118 | } 119 | 120 | void setPdEditorDialog() { 121 | std::string path = getApplicationPathDialog(); 122 | if (path == "") 123 | return; 124 | settingsPdEditorPath = path; 125 | settingsSave(); 126 | } 127 | 128 | 129 | struct Prototype : Module { 130 | enum ParamIds { 131 | ENUMS(KNOB_PARAMS, NUM_ROWS), 132 | ENUMS(SWITCH_PARAMS, NUM_ROWS), 133 | NUM_PARAMS 134 | }; 135 | enum InputIds { 136 | ENUMS(IN_INPUTS, NUM_ROWS), 137 | NUM_INPUTS 138 | }; 139 | enum OutputIds { 140 | ENUMS(OUT_OUTPUTS, NUM_ROWS), 141 | NUM_OUTPUTS 142 | }; 143 | enum LightIds { 144 | ENUMS(LIGHT_LIGHTS, NUM_ROWS * 3), 145 | ENUMS(SWITCH_LIGHTS, NUM_ROWS * 3), 146 | NUM_LIGHTS 147 | }; 148 | 149 | std::string message; 150 | std::string path; 151 | std::string script; 152 | std::string engineName; 153 | std::mutex scriptMutex; 154 | ScriptEngine* scriptEngine = NULL; 155 | int frame = 0; 156 | int frameDivider; 157 | // This is dynamically allocated to have some protection against script bugs. 158 | ProcessBlock* block; 159 | int bufferIndex = 0; 160 | 161 | efsw_watcher efsw = NULL; 162 | 163 | /** Script that has not yet been approved to load */ 164 | std::string unsecureScript; 165 | bool securityRequested = false; 166 | bool securityAccepted = false; 167 | 168 | Prototype() { 169 | config(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS); 170 | for (int i = 0; i < NUM_ROWS; i++) 171 | configParam(KNOB_PARAMS + i, 0.f, 1.f, 0.5f, string::f("Knob %d", i + 1)); 172 | for (int i = 0; i < NUM_ROWS; i++) 173 | configParam(SWITCH_PARAMS + i, 0.f, 1.f, 0.f, string::f("Switch %d", i + 1)); 174 | // for (int i = 0; i < NUM_ROWS; i++) 175 | // configInput(IN_INPUTS + i, string::f("#%d", i + 1)); 176 | // for (int i = 0; i < NUM_ROWS; i++) 177 | // configOutput(OUT_OUTPUTS + i, string::f("#%d", i + 1)); 178 | 179 | block = new ProcessBlock; 180 | setPath(""); 181 | } 182 | 183 | ~Prototype() { 184 | setPath(""); 185 | delete block; 186 | } 187 | 188 | void onReset() override { 189 | setScript(script); 190 | } 191 | 192 | void process(const ProcessArgs& args) override { 193 | // Load security-sandboxed script if the security warning message is accepted. 194 | if (unsecureScript != "" && securityAccepted) { 195 | setScript(unsecureScript); 196 | unsecureScript = ""; 197 | } 198 | 199 | // Frame divider for reducing sample rate 200 | if (++frame < frameDivider) 201 | return; 202 | frame = 0; 203 | 204 | // Clear outputs if no script is running 205 | if (!scriptEngine) { 206 | for (int i = 0; i < NUM_ROWS; i++) 207 | for (int c = 0; c < 3; c++) 208 | lights[LIGHT_LIGHTS + i * 3 + c].setBrightness(0.f); 209 | for (int i = 0; i < NUM_ROWS; i++) 210 | for (int c = 0; c < 3; c++) 211 | lights[SWITCH_LIGHTS + i * 3 + c].setBrightness(0.f); 212 | for (int i = 0; i < NUM_ROWS; i++) 213 | outputs[OUT_OUTPUTS + i].setVoltage(0.f); 214 | return; 215 | } 216 | 217 | // Inputs 218 | for (int i = 0; i < NUM_ROWS; i++) 219 | block->inputs[i][bufferIndex] = inputs[IN_INPUTS + i].getVoltage(); 220 | 221 | // Process block 222 | if (++bufferIndex >= block->bufferSize) { 223 | std::lock_guard lock(scriptMutex); 224 | bufferIndex = 0; 225 | 226 | // Block settings 227 | block->sampleRate = args.sampleRate; 228 | block->sampleTime = args.sampleTime; 229 | 230 | // Params 231 | for (int i = 0; i < NUM_ROWS; i++) 232 | block->knobs[i] = params[KNOB_PARAMS + i].getValue(); 233 | for (int i = 0; i < NUM_ROWS; i++) 234 | block->switches[i] = params[SWITCH_PARAMS + i].getValue() > 0.f; 235 | float oldKnobs[NUM_ROWS]; 236 | std::memcpy(oldKnobs, block->knobs, sizeof(oldKnobs)); 237 | 238 | // Run ScriptEngine's process function 239 | { 240 | // Process buffer 241 | if (scriptEngine) { 242 | if (scriptEngine->process()) { 243 | WARN("Script %s process() failed. Stopped script.", path.c_str()); 244 | delete scriptEngine; 245 | scriptEngine = NULL; 246 | return; 247 | } 248 | } 249 | } 250 | 251 | // Params 252 | // Only set params if values were changed by the script. This avoids issues when the user is manipulating them from the UI thread. 253 | for (int i = 0; i < NUM_ROWS; i++) { 254 | if (block->knobs[i] != oldKnobs[i]) 255 | params[KNOB_PARAMS + i].setValue(block->knobs[i]); 256 | } 257 | // Lights 258 | for (int i = 0; i < NUM_ROWS; i++) 259 | for (int c = 0; c < 3; c++) 260 | lights[LIGHT_LIGHTS + i * 3 + c].setBrightness(block->lights[i][c]); 261 | for (int i = 0; i < NUM_ROWS; i++) 262 | for (int c = 0; c < 3; c++) 263 | lights[SWITCH_LIGHTS + i * 3 + c].setBrightness(block->switchLights[i][c]); 264 | } 265 | 266 | // Outputs 267 | for (int i = 0; i < NUM_ROWS; i++) 268 | outputs[OUT_OUTPUTS + i].setVoltage(block->outputs[i][bufferIndex]); 269 | } 270 | 271 | void setPath(std::string path) { 272 | // Cleanup 273 | if (efsw) { 274 | efsw_release(efsw); 275 | efsw = NULL; 276 | } 277 | this->path = ""; 278 | setScript(""); 279 | 280 | if (path == "") 281 | return; 282 | 283 | this->path = path; 284 | loadPath(); 285 | 286 | if (this->script == "") 287 | return; 288 | 289 | // Watch file 290 | std::string dir = string::directory(path); 291 | efsw = efsw_create(false); 292 | efsw_addwatch(efsw, dir.c_str(), watchCallback, false, this); 293 | efsw_watch(efsw); 294 | } 295 | 296 | void loadPath() { 297 | // Read file 298 | std::ifstream file; 299 | file.exceptions(std::ifstream::failbit | std::ifstream::badbit); 300 | try { 301 | file.open(path); 302 | std::stringstream buffer; 303 | buffer << file.rdbuf(); 304 | std::string script = buffer.str(); 305 | setScript(script); 306 | } 307 | catch (const std::runtime_error& err) { 308 | // Fail silently 309 | } 310 | } 311 | 312 | void setScript(std::string script) { 313 | std::lock_guard lock(scriptMutex); 314 | // Reset script state 315 | if (scriptEngine) { 316 | delete scriptEngine; 317 | scriptEngine = NULL; 318 | } 319 | this->script = ""; 320 | this->engineName = ""; 321 | this->message = ""; 322 | // Reset process state 323 | frameDivider = 32; 324 | frame = 0; 325 | bufferIndex = 0; 326 | // Reset block 327 | *block = ProcessBlock(); 328 | 329 | if (script == "") 330 | return; 331 | this->script = script; 332 | 333 | // Create script engine from path extension 334 | std::string extension = string::filenameExtension(string::filename(path)); 335 | scriptEngine = createScriptEngine(extension); 336 | if (!scriptEngine) { 337 | message = string::f("No engine for .%s extension", extension.c_str()); 338 | return; 339 | } 340 | scriptEngine->module = this; 341 | 342 | // Run script 343 | if (scriptEngine->run(path, script)) { 344 | // Error message should have been set by ScriptEngine 345 | delete scriptEngine; 346 | scriptEngine = NULL; 347 | return; 348 | } 349 | this->engineName = scriptEngine->getEngineName(); 350 | } 351 | 352 | static void watchCallback(efsw_watcher watcher, efsw_watchid watchid, const char* dir, const char* filename, enum efsw_action action, const char* old_filename, void* param) { 353 | Prototype* that = (Prototype*) param; 354 | if (action == EFSW_ADD || action == EFSW_DELETE || action == EFSW_MODIFIED || action == EFSW_MOVED) { 355 | // Check filename 356 | std::string pathFilename = string::filename(that->path); 357 | if (pathFilename == filename) { 358 | that->loadPath(); 359 | } 360 | } 361 | } 362 | 363 | json_t* dataToJson() override { 364 | json_t* rootJ = json_object(); 365 | 366 | json_object_set_new(rootJ, "path", json_string(path.c_str())); 367 | 368 | std::string script = this->script; 369 | // If we haven't accepted the security of this script, serialize the security-sandboxed script anyway. 370 | if (script == "") 371 | script = unsecureScript; 372 | json_object_set_new(rootJ, "script", json_stringn(script.data(), script.size())); 373 | 374 | return rootJ; 375 | } 376 | 377 | void dataFromJson(json_t* rootJ) override { 378 | json_t* pathJ = json_object_get(rootJ, "path"); 379 | if (pathJ) { 380 | std::string path = json_string_value(pathJ); 381 | setPath(path); 382 | } 383 | 384 | // Only get the script string if the script file wasn't found. 385 | if (this->path != "" && this->script == "") { 386 | WARN("Script file %s not found, using script in patch", this->path.c_str()); 387 | json_t* scriptJ = json_object_get(rootJ, "script"); 388 | if (scriptJ) { 389 | std::string script = std::string(json_string_value(scriptJ), json_string_length(scriptJ)); 390 | if (script != "") { 391 | // Request security warning message 392 | securityAccepted = false; 393 | securityRequested = true; 394 | unsecureScript = script; 395 | } 396 | } 397 | } 398 | } 399 | 400 | bool doesPathExist() { 401 | if (path == "") 402 | return false; 403 | // Try to open file 404 | std::ifstream file(path); 405 | return file.good(); 406 | } 407 | 408 | void newScriptDialog() { 409 | std::string ext = "js"; 410 | // Get current extension if a script is currently loaded 411 | if (!path.empty()) { 412 | ext = string::filenameExtension(string::filename(path)); 413 | } 414 | std::string dir = asset::plugin(pluginInstance, "examples"); 415 | std::string filename = "Untitled." + ext; 416 | char* newPathC = osdialog_file(OSDIALOG_SAVE, dir.c_str(), filename.c_str(), NULL); 417 | if (!newPathC) { 418 | return; 419 | } 420 | std::string newPath = newPathC; 421 | std::free(newPathC); 422 | 423 | // Unload script so the user is guaranteed to see the following error messages if they occur. 424 | setPath(""); 425 | 426 | // Get extension of requested filename 427 | ext = string::filenameExtension(string::filename(newPath)); 428 | if (ext == "") { 429 | message = "File extension required"; 430 | return; 431 | } 432 | auto it = scriptEngineFactories.find(ext); 433 | if (it == scriptEngineFactories.end()) { 434 | message = "File extension \"" + ext + "\" not recognized"; 435 | return; 436 | } 437 | 438 | // Copy template to new script 439 | std::string templatePath = asset::plugin(pluginInstance, "examples/template." + ext); 440 | { 441 | std::ifstream templateFile(templatePath, std::ios::binary); 442 | std::ofstream newFile(newPath, std::ios::binary); 443 | newFile << templateFile.rdbuf(); 444 | } 445 | setPath(newPath); 446 | editScript(); 447 | } 448 | 449 | void loadScriptDialog() { 450 | std::string dir = asset::plugin(pluginInstance, "examples"); 451 | char* pathC = osdialog_file(OSDIALOG_OPEN, dir.c_str(), NULL, NULL); 452 | if (!pathC) { 453 | return; 454 | } 455 | std::string path = pathC; 456 | std::free(pathC); 457 | 458 | setPath(path); 459 | } 460 | 461 | void reloadScript() { 462 | loadPath(); 463 | } 464 | 465 | void saveScriptDialog() { 466 | if (script == "") 467 | return; 468 | 469 | std::string ext = string::filenameExtension(string::filename(path)); 470 | std::string dir = asset::plugin(pluginInstance, "examples"); 471 | std::string filename = "Untitled." + ext; 472 | char* newPathC = osdialog_file(OSDIALOG_SAVE, dir.c_str(), filename.c_str(), NULL); 473 | if (!newPathC) { 474 | return; 475 | } 476 | std::string newPath = newPathC; 477 | std::free(newPathC); 478 | // Add extension if user didn't specify one 479 | std::string newExt = string::filenameExtension(string::filename(newPath)); 480 | if (newExt == "") 481 | newPath += "." + ext; 482 | 483 | // Write and close file 484 | { 485 | std::ofstream f(newPath); 486 | f << script; 487 | } 488 | // Load path so that it reloads and is watched. 489 | setPath(newPath); 490 | } 491 | 492 | void editScript() { 493 | std::string editorPath = getEditorPath(); 494 | if (editorPath.empty()) 495 | return; 496 | if (path.empty()) 497 | return; 498 | // Launch editor and detach 499 | #if defined ARCH_LIN 500 | std::string command = editorPath + " \"" + path + "\" &"; 501 | (void) std::system(command.c_str()); 502 | #elif defined ARCH_MAC 503 | std::string command = "open -a " + editorPath + " \"" + path + "\" &"; 504 | (void) std::system(command.c_str()); 505 | #elif defined ARCH_WIN 506 | std::string command = editorPath + " \"" + path + "\""; 507 | std::wstring commandW = string::toWstring(command); 508 | STARTUPINFOW startupInfo; 509 | std::memset(&startupInfo, 0, sizeof(startupInfo)); 510 | startupInfo.cb = sizeof(startupInfo); 511 | PROCESS_INFORMATION processInfo; 512 | // Use the non-const [] accessor for commandW. Since C++11, it is null-terminated. 513 | CreateProcessW(NULL, &commandW[0], NULL, NULL, false, 0, NULL, NULL, &startupInfo, &processInfo); 514 | #endif 515 | } 516 | 517 | void setClipboardMessage() { 518 | glfwSetClipboardString(APP->window->win, message.c_str()); 519 | } 520 | 521 | void appendContextMenu(Menu* menu) { 522 | struct NewScriptItem : MenuItem { 523 | Prototype* module; 524 | void onAction(const event::Action& e) override { 525 | module->newScriptDialog(); 526 | } 527 | }; 528 | NewScriptItem* newScriptItem = createMenuItem("New script"); 529 | newScriptItem->module = this; 530 | menu->addChild(newScriptItem); 531 | 532 | struct LoadScriptItem : MenuItem { 533 | Prototype* module; 534 | void onAction(const event::Action& e) override { 535 | module->loadScriptDialog(); 536 | } 537 | }; 538 | LoadScriptItem* loadScriptItem = createMenuItem("Load script"); 539 | loadScriptItem->module = this; 540 | menu->addChild(loadScriptItem); 541 | 542 | struct ReloadScriptItem : MenuItem { 543 | Prototype* module; 544 | void onAction(const event::Action& e) override { 545 | module->reloadScript(); 546 | } 547 | }; 548 | ReloadScriptItem* reloadScriptItem = createMenuItem("Reload script"); 549 | reloadScriptItem->module = this; 550 | menu->addChild(reloadScriptItem); 551 | 552 | struct SaveScriptItem : MenuItem { 553 | Prototype* module; 554 | void onAction(const event::Action& e) override { 555 | module->saveScriptDialog(); 556 | } 557 | }; 558 | SaveScriptItem* saveScriptItem = createMenuItem("Save script as"); 559 | saveScriptItem->module = this; 560 | menu->addChild(saveScriptItem); 561 | 562 | struct EditScriptItem : MenuItem { 563 | Prototype* module; 564 | void onAction(const event::Action& e) override { 565 | module->editScript(); 566 | } 567 | }; 568 | EditScriptItem* editScriptItem = createMenuItem("Edit script"); 569 | editScriptItem->module = this; 570 | 571 | editScriptItem->disabled = !doesPathExist() || (getEditorPath() == ""); 572 | menu->addChild(editScriptItem); 573 | 574 | menu->addChild(new MenuSeparator); 575 | 576 | struct SetEditorItem : MenuItem { 577 | void onAction(const event::Action& e) override { 578 | setEditorDialog(); 579 | } 580 | }; 581 | SetEditorItem* setEditorItem = createMenuItem("Set text editor application"); 582 | menu->addChild(setEditorItem); 583 | 584 | struct SetPdEditorItem : MenuItem { 585 | void onAction(const event::Action& e) override { 586 | setPdEditorDialog(); 587 | } 588 | }; 589 | SetPdEditorItem* setPdEditorItem = createMenuItem("Set Pure Data application"); 590 | menu->addChild(setPdEditorItem); 591 | } 592 | 593 | std::string getEditorPath() { 594 | if (path == "") 595 | return ""; 596 | // HACK check if extension is .pd 597 | if (string::filenameExtension(string::filename(path)) == "pd") 598 | return settingsPdEditorPath; 599 | return settingsEditorPath; 600 | } 601 | }; 602 | 603 | 604 | void ScriptEngine::display(const std::string& message) { 605 | module->message = message; 606 | } 607 | void ScriptEngine::setFrameDivider(int frameDivider) { 608 | module->frameDivider = std::max(frameDivider, 1); 609 | } 610 | void ScriptEngine::setBufferSize(int bufferSize) { 611 | module->block->bufferSize = clamp(bufferSize, 1, MAX_BUFFER_SIZE); 612 | } 613 | ProcessBlock* ScriptEngine::getProcessBlock() { 614 | return module->block; 615 | } 616 | 617 | 618 | struct FileChoice : LedDisplayChoice { 619 | Prototype* module; 620 | 621 | void step() override { 622 | if (module && module->engineName != "") 623 | text = module->engineName; 624 | else 625 | text = "Script"; 626 | text += ": "; 627 | if (module && module->path != "") 628 | text += string::filename(module->path); 629 | else 630 | text += "(click to load)"; 631 | } 632 | 633 | void onAction(const event::Action& e) override { 634 | Menu* menu = createMenu(); 635 | module->appendContextMenu(menu); 636 | } 637 | }; 638 | 639 | 640 | struct MessageChoice : LedDisplayChoice { 641 | Prototype* module; 642 | 643 | void step() override { 644 | text = module ? module->message : ""; 645 | } 646 | 647 | void draw(const DrawArgs& args) override { 648 | nvgScissor(args.vg, RECT_ARGS(args.clipBox)); 649 | if (font->handle >= 0) { 650 | nvgFillColor(args.vg, color); 651 | nvgFontFaceId(args.vg, font->handle); 652 | nvgTextLetterSpacing(args.vg, 0.0); 653 | nvgTextLineHeight(args.vg, 1.08); 654 | 655 | nvgFontSize(args.vg, 12); 656 | nvgTextBox(args.vg, textOffset.x, textOffset.y, box.size.x - textOffset.x, text.c_str(), NULL); 657 | } 658 | nvgResetScissor(args.vg); 659 | } 660 | 661 | void onAction(const event::Action& e) override { 662 | Menu* menu = createMenu(); 663 | 664 | struct SetClipboardMessageItem : MenuItem { 665 | Prototype* module; 666 | void onAction(const event::Action& e) override { 667 | module->setClipboardMessage(); 668 | } 669 | }; 670 | SetClipboardMessageItem* item = createMenuItem("Copy"); 671 | item->module = module; 672 | menu->addChild(item); 673 | } 674 | }; 675 | 676 | 677 | struct PrototypeDisplay : LedDisplay { 678 | PrototypeDisplay() { 679 | box.size = mm2px(Vec(69.879, 27.335)); 680 | } 681 | 682 | void setModule(Prototype* module) { 683 | FileChoice* fileChoice = new FileChoice; 684 | fileChoice->box.size.x = box.size.x; 685 | fileChoice->module = module; 686 | addChild(fileChoice); 687 | 688 | LedDisplaySeparator* fileSeparator = new LedDisplaySeparator; 689 | fileSeparator->box.size.x = box.size.x; 690 | fileSeparator->box.pos = fileChoice->box.getBottomLeft(); 691 | addChild(fileSeparator); 692 | 693 | MessageChoice* messageChoice = new MessageChoice; 694 | messageChoice->box.pos = fileChoice->box.getBottomLeft(); 695 | messageChoice->box.size.x = box.size.x; 696 | messageChoice->box.size.y = box.size.y - messageChoice->box.pos.y; 697 | messageChoice->module = module; 698 | addChild(messageChoice); 699 | } 700 | }; 701 | 702 | 703 | struct PrototypeWidget : ModuleWidget { 704 | PrototypeWidget(Prototype* module) { 705 | setModule(module); 706 | setPanel(APP->window->loadSvg(asset::plugin(pluginInstance, "res/Prototype.svg"))); 707 | 708 | addChild(createWidget(Vec(RACK_GRID_WIDTH, 0))); 709 | addChild(createWidget(Vec(box.size.x - 2 * RACK_GRID_WIDTH, 0))); 710 | addChild(createWidget(Vec(RACK_GRID_WIDTH, RACK_GRID_HEIGHT - RACK_GRID_WIDTH))); 711 | addChild(createWidget(Vec(box.size.x - 2 * RACK_GRID_WIDTH, RACK_GRID_HEIGHT - RACK_GRID_WIDTH))); 712 | 713 | addParam(createParamCentered(mm2px(Vec(8.099, 64.401)), module, Prototype::KNOB_PARAMS + 0)); 714 | addParam(createParamCentered(mm2px(Vec(20.099, 64.401)), module, Prototype::KNOB_PARAMS + 1)); 715 | addParam(createParamCentered(mm2px(Vec(32.099, 64.401)), module, Prototype::KNOB_PARAMS + 2)); 716 | addParam(createParamCentered(mm2px(Vec(44.099, 64.401)), module, Prototype::KNOB_PARAMS + 3)); 717 | addParam(createParamCentered(mm2px(Vec(56.099, 64.401)), module, Prototype::KNOB_PARAMS + 4)); 718 | addParam(createParamCentered(mm2px(Vec(68.099, 64.401)), module, Prototype::KNOB_PARAMS + 5)); 719 | addParam(createParamCentered(mm2px(Vec(8.099, 80.151)), module, Prototype::SWITCH_PARAMS + 0)); 720 | addParam(createParamCentered(mm2px(Vec(20.099, 80.151)), module, Prototype::SWITCH_PARAMS + 1)); 721 | addParam(createParamCentered(mm2px(Vec(32.099, 80.151)), module, Prototype::SWITCH_PARAMS + 2)); 722 | addParam(createParamCentered(mm2px(Vec(44.099, 80.151)), module, Prototype::SWITCH_PARAMS + 3)); 723 | addParam(createParamCentered(mm2px(Vec(56.099, 80.151)), module, Prototype::SWITCH_PARAMS + 4)); 724 | addParam(createParamCentered(mm2px(Vec(68.099, 80.151)), module, Prototype::SWITCH_PARAMS + 5)); 725 | 726 | addInput(createInputCentered(mm2px(Vec(8.099, 96.025)), module, Prototype::IN_INPUTS + 0)); 727 | addInput(createInputCentered(mm2px(Vec(20.099, 96.025)), module, Prototype::IN_INPUTS + 1)); 728 | addInput(createInputCentered(mm2px(Vec(32.099, 96.025)), module, Prototype::IN_INPUTS + 2)); 729 | addInput(createInputCentered(mm2px(Vec(44.099, 96.025)), module, Prototype::IN_INPUTS + 3)); 730 | addInput(createInputCentered(mm2px(Vec(56.099, 96.025)), module, Prototype::IN_INPUTS + 4)); 731 | addInput(createInputCentered(mm2px(Vec(68.099, 96.025)), module, Prototype::IN_INPUTS + 5)); 732 | 733 | addOutput(createOutputCentered(mm2px(Vec(8.099, 112.25)), module, Prototype::OUT_OUTPUTS + 0)); 734 | addOutput(createOutputCentered(mm2px(Vec(20.099, 112.25)), module, Prototype::OUT_OUTPUTS + 1)); 735 | addOutput(createOutputCentered(mm2px(Vec(32.099, 112.25)), module, Prototype::OUT_OUTPUTS + 2)); 736 | addOutput(createOutputCentered(mm2px(Vec(44.099, 112.25)), module, Prototype::OUT_OUTPUTS + 3)); 737 | addOutput(createOutputCentered(mm2px(Vec(56.099, 112.25)), module, Prototype::OUT_OUTPUTS + 4)); 738 | addOutput(createOutputCentered(mm2px(Vec(68.099, 112.25)), module, Prototype::OUT_OUTPUTS + 5)); 739 | 740 | addChild(createLightCentered>(mm2px(Vec(8.099, 51.4)), module, Prototype::LIGHT_LIGHTS + 3 * 0)); 741 | addChild(createLightCentered>(mm2px(Vec(20.099, 51.4)), module, Prototype::LIGHT_LIGHTS + 3 * 1)); 742 | addChild(createLightCentered>(mm2px(Vec(32.099, 51.4)), module, Prototype::LIGHT_LIGHTS + 3 * 2)); 743 | addChild(createLightCentered>(mm2px(Vec(44.099, 51.4)), module, Prototype::LIGHT_LIGHTS + 3 * 3)); 744 | addChild(createLightCentered>(mm2px(Vec(56.099, 51.4)), module, Prototype::LIGHT_LIGHTS + 3 * 4)); 745 | addChild(createLightCentered>(mm2px(Vec(68.099, 51.4)), module, Prototype::LIGHT_LIGHTS + 3 * 5)); 746 | addChild(createLightCentered>(mm2px(Vec(8.099, 80.151)), module, Prototype::SWITCH_LIGHTS + 0)); 747 | addChild(createLightCentered>(mm2px(Vec(20.099, 80.151)), module, Prototype::SWITCH_LIGHTS + 3 * 1)); 748 | addChild(createLightCentered>(mm2px(Vec(32.099, 80.151)), module, Prototype::SWITCH_LIGHTS + 3 * 2)); 749 | addChild(createLightCentered>(mm2px(Vec(44.099, 80.151)), module, Prototype::SWITCH_LIGHTS + 3 * 3)); 750 | addChild(createLightCentered>(mm2px(Vec(56.099, 80.151)), module, Prototype::SWITCH_LIGHTS + 3 * 4)); 751 | addChild(createLightCentered>(mm2px(Vec(68.099, 80.151)), module, Prototype::SWITCH_LIGHTS + 3 * 5)); 752 | 753 | PrototypeDisplay* display = createWidget(mm2px(Vec(3.16, 14.837))); 754 | display->setModule(module); 755 | addChild(display); 756 | } 757 | 758 | void appendContextMenu(Menu* menu) override { 759 | Prototype* module = dynamic_cast(this->module); 760 | 761 | menu->addChild(new MenuSeparator); 762 | module->appendContextMenu(menu); 763 | } 764 | 765 | void onPathDrop(const event::PathDrop& e) override { 766 | Prototype* module = dynamic_cast(this->module); 767 | if (!module) 768 | return; 769 | if (e.paths.size() < 1) 770 | return; 771 | module->setPath(e.paths[0]); 772 | } 773 | 774 | void step() override { 775 | Prototype* module = dynamic_cast(this->module); 776 | if (module && module->securityRequested) { 777 | if (osdialog_message(OSDIALOG_WARNING, OSDIALOG_OK_CANCEL, "VCV Prototype is requesting to run a script from a patch or module preset. Running Prototype scripts from untrusted sources may compromise your computer and personal information. Proceed and run script?")) { 778 | module->securityAccepted = true; 779 | } 780 | module->securityRequested = false; 781 | } 782 | ModuleWidget::step(); 783 | } 784 | }; 785 | 786 | void init(Plugin* p) { 787 | pluginInstance = p; 788 | 789 | p->addModel(createModel("Prototype")); 790 | settingsLoad(); 791 | } 792 | -------------------------------------------------------------------------------- /src/PythonEngine.cpp: -------------------------------------------------------------------------------- 1 | extern "C" { 2 | #define PY_SSIZE_T_CLEAN 3 | #include 4 | #define NPY_NO_DEPRECATED_API NPY_1_7_API_VERSION 5 | #include 6 | } 7 | #include 8 | #include "ScriptEngine.hpp" 9 | #include 10 | 11 | 12 | /* 13 | TODO: 14 | - Fix Numpy loading on Linux. 15 | - "undefined symbol: PyExc_RecursionError" 16 | - This worked in Python 3.7, but because some other crash (regarding GIL interpreter objects, I forgot) was fixed on Python's issue tracker in the last month, I switched to Python 3.8 to solve that problem. 17 | - Test build and running on Windows/Mac. (Has not been attempted.) 18 | - Allow multiple instances with GIL. 19 | - Fix destructors. 20 | */ 21 | 22 | 23 | extern rack::Plugin* pluginInstance; 24 | 25 | 26 | static void initPython() { 27 | if (Py_IsInitialized()) 28 | return; 29 | 30 | std::string pythonDir = rack::asset::plugin(pluginInstance, "dep/lib/python3.8"); 31 | // Set python path 32 | std::string sep = ":"; 33 | std::string pythonPath = pythonDir; 34 | pythonPath += sep + pythonDir + "/lib-dynload"; 35 | pythonPath += sep + pythonDir + "/site-packages"; 36 | wchar_t* pythonPathW = Py_DecodeLocale(pythonPath.c_str(), NULL); 37 | Py_SetPath(pythonPathW); 38 | PyMem_RawFree(pythonPathW); 39 | // Initialize but don't register signal handlers 40 | Py_InitializeEx(0); 41 | assert(Py_IsInitialized()); 42 | 43 | // PyEval_InitThreads(); 44 | 45 | // Import numpy 46 | if (_import_array()) { 47 | PyErr_Print(); 48 | abort(); 49 | } 50 | } 51 | 52 | 53 | struct PythonEngine : ScriptEngine { 54 | PyObject* mainDict = NULL; 55 | PyObject* processFunc = NULL; 56 | PyObject* blockObj = NULL; 57 | PyInterpreterState* interp = NULL; 58 | 59 | ~PythonEngine() { 60 | if (mainDict) 61 | Py_DECREF(mainDict); 62 | if (processFunc) 63 | Py_DECREF(processFunc); 64 | if (blockObj) 65 | Py_DECREF(blockObj); 66 | if (interp) 67 | PyInterpreterState_Delete(interp); 68 | } 69 | 70 | std::string getEngineName() override { 71 | return "Python"; 72 | } 73 | 74 | int run(const std::string& path, const std::string& script) override { 75 | ProcessBlock* block = getProcessBlock(); 76 | initPython(); 77 | 78 | // PyThreadState* tstate = PyThreadState_Get(); 79 | // interp = PyInterpreterState_New(); 80 | // PyThreadState_Swap(tstate); 81 | 82 | // Get globals dictionary 83 | PyObject* mainModule = PyImport_AddModule("__main__"); 84 | assert(mainModule); 85 | DEFER({Py_DECREF(mainModule);}); 86 | mainDict = PyModule_GetDict(mainModule); 87 | assert(mainDict); 88 | 89 | // Set context pointer 90 | PyObject* engineObj = PyCapsule_New(this, NULL, NULL); 91 | PyDict_SetItemString(mainDict, "_engine", engineObj); 92 | 93 | // Add functions to globals 94 | static PyMethodDef native_functions[] = { 95 | {"display", nativeDisplay, METH_VARARGS, ""}, 96 | {NULL, NULL, 0, NULL}, 97 | }; 98 | if (PyModule_AddFunctions(mainModule, native_functions)) { 99 | WARN("Could not add global functions"); 100 | return -1; 101 | } 102 | 103 | // Set config 104 | static PyStructSequence_Field configFields[] = { 105 | {"frameDivider", ""}, 106 | {"bufferSize", ""}, 107 | {NULL, NULL}, 108 | }; 109 | static PyStructSequence_Desc configDesc = {"Config", "", configFields, LENGTHOF(configFields) - 1}; 110 | PyTypeObject* configType = PyStructSequence_NewType(&configDesc); 111 | assert(configType); 112 | 113 | PyObject* configObj = PyStructSequence_New(configType); 114 | assert(configObj); 115 | PyDict_SetItemString(mainDict, "config", configObj); 116 | 117 | // frameDivider 118 | PyStructSequence_SetItem(configObj, 0, PyLong_FromLong(32)); 119 | // bufferSize 120 | PyStructSequence_SetItem(configObj, 1, PyLong_FromLong(1)); 121 | 122 | // Compile string 123 | PyObject* code = Py_CompileString(script.c_str(), path.c_str(), Py_file_input); 124 | if (!code) { 125 | PyErr_Print(); 126 | return -1; 127 | } 128 | DEFER({Py_DECREF(code);}); 129 | 130 | // Evaluate string 131 | PyObject* result = PyEval_EvalCode(code, mainDict, mainDict); 132 | if (!result) { 133 | PyErr_Print(); 134 | return -1; 135 | } 136 | DEFER({Py_DECREF(result);}); 137 | 138 | // Create block 139 | static PyStructSequence_Field blockFields[] = { 140 | {"inputs", ""}, 141 | {"outputs", ""}, 142 | {"knobs", ""}, 143 | {"switches", ""}, 144 | {"lights", ""}, 145 | {"switch_lights", ""}, 146 | {NULL, NULL}, 147 | }; 148 | static PyStructSequence_Desc blockDesc = {"Block", "", blockFields, LENGTHOF(blockFields) - 1}; 149 | PyTypeObject* blockType = PyStructSequence_NewType(&blockDesc); 150 | assert(blockType); 151 | DEBUG("ref %d", Py_REFCNT(blockType)); 152 | 153 | blockObj = PyStructSequence_New(blockType); 154 | assert(blockObj); 155 | DEBUG("ref %d", Py_REFCNT(blockObj)); 156 | 157 | // inputs 158 | npy_intp inputsDims[] = {NUM_ROWS, MAX_BUFFER_SIZE}; 159 | PyObject* inputs = PyArray_SimpleNewFromData(2, inputsDims, NPY_FLOAT32, block->inputs); 160 | PyStructSequence_SetItem(blockObj, 0, inputs); 161 | 162 | // outputs 163 | npy_intp outputsDims[] = {NUM_ROWS, MAX_BUFFER_SIZE}; 164 | PyObject* outputs = PyArray_SimpleNewFromData(2, outputsDims, NPY_FLOAT32, block->outputs); 165 | PyStructSequence_SetItem(blockObj, 1, outputs); 166 | 167 | // knobs 168 | npy_intp knobsDims[] = {NUM_ROWS}; 169 | PyObject* knobs = PyArray_SimpleNewFromData(1, knobsDims, NPY_FLOAT32, block->knobs); 170 | PyStructSequence_SetItem(blockObj, 2, knobs); 171 | 172 | // switches 173 | npy_intp switchesDims[] = {NUM_ROWS}; 174 | PyObject* switches = PyArray_SimpleNewFromData(1, switchesDims, NPY_BOOL, block->switches); 175 | PyStructSequence_SetItem(blockObj, 3, switches); 176 | 177 | // lights 178 | npy_intp lightsDims[] = {NUM_ROWS, 3}; 179 | PyObject* lights = PyArray_SimpleNewFromData(2, lightsDims, NPY_FLOAT32, block->lights); 180 | PyStructSequence_SetItem(blockObj, 4, lights); 181 | 182 | // switchLights 183 | npy_intp switchLightsDims[] = {NUM_ROWS, 3}; 184 | PyObject* switchLights = PyArray_SimpleNewFromData(2, switchLightsDims, NPY_FLOAT32, block->switchLights); 185 | PyStructSequence_SetItem(blockObj, 5, switchLights); 186 | 187 | // Get process function from globals 188 | processFunc = PyDict_GetItemString(mainDict, "process"); 189 | if (!processFunc) { 190 | display("No process() function"); 191 | return -1; 192 | } 193 | if (!PyCallable_Check(processFunc)) { 194 | display("process() is not callable"); 195 | return -1; 196 | } 197 | 198 | return 0; 199 | } 200 | 201 | int process() override { 202 | // DEBUG("ref %d", Py_REFCNT(blockObj)); 203 | // Call process() 204 | PyObject* args = PyTuple_Pack(1, blockObj); 205 | assert(args); 206 | DEFER({Py_DECREF(args);}); 207 | PyObject* processResult = PyObject_CallObject(processFunc, args); 208 | if (!processResult) { 209 | PyErr_Print(); 210 | 211 | // PyObject *ptype, *pvalue, *ptraceback; 212 | // PyErr_Fetch(&ptype, &pvalue, &ptraceback); 213 | // const char* str = PyUnicode_AsUTF8(pvalue); 214 | // if (!str) 215 | // return -1; 216 | 217 | // display(str); 218 | return -1; 219 | } 220 | DEFER({Py_DECREF(processResult);}); 221 | 222 | // PyThreadState* tstate = PyThreadState_New(interp); 223 | // PyEval_RestoreThread(tstate); 224 | // PyThreadState_Clear(tstate); 225 | // PyThreadState_DeleteCurrent(); 226 | return 0; 227 | } 228 | 229 | static PyObject* nativeDisplay(PyObject* self, PyObject* args) { 230 | PyObject* mainDict = PyEval_GetGlobals(); 231 | assert(mainDict); 232 | PyObject* engineObj = PyDict_GetItemString(mainDict, "_engine"); 233 | assert(engineObj); 234 | PythonEngine* engine = (PythonEngine*) PyCapsule_GetPointer(engineObj, NULL); 235 | assert(engine); 236 | 237 | PyObject* msgO = PyTuple_GetItem(args, 0); 238 | if (!msgO) 239 | return NULL; 240 | 241 | PyObject* msgS = PyObject_Str(msgO); 242 | DEFER({Py_DECREF(msgS);}); 243 | 244 | const char* msg = PyUnicode_AsUTF8(msgS); 245 | engine->display(msg); 246 | 247 | Py_INCREF(Py_None); 248 | return Py_None; 249 | } 250 | }; 251 | 252 | 253 | __attribute__((constructor(1000))) 254 | static void constructor() { 255 | addScriptEngine("py"); 256 | } 257 | -------------------------------------------------------------------------------- /src/QuickJSEngine.cpp: -------------------------------------------------------------------------------- 1 | #include "ScriptEngine.hpp" 2 | #include 3 | 4 | static JSClassID QuickJSEngineClass; 5 | 6 | static std::string ErrorToString(JSContext *ctx) { 7 | JSValue exception_val, val; 8 | const char *stack; 9 | const char *str; 10 | bool is_error; 11 | std::string ret; 12 | size_t s1, s2; 13 | 14 | exception_val = JS_GetException(ctx); 15 | is_error = JS_IsError(ctx, exception_val); 16 | str = JS_ToCStringLen(ctx, &s1, exception_val); 17 | 18 | if (!str) { 19 | return std::string("error thrown but no error message"); 20 | } 21 | 22 | if (!is_error) { 23 | ret = std::string("Throw:\n") + std::string(str); 24 | } else { 25 | val = JS_GetPropertyStr(ctx, exception_val, "stack"); 26 | 27 | if (!JS_IsUndefined(val)) { 28 | stack = JS_ToCStringLen(ctx, &s2, val); 29 | ret = std::string(str) + std::string("\n") + std::string(stack); 30 | JS_FreeCString(ctx, stack); 31 | } 32 | JS_FreeValue(ctx, val); 33 | } 34 | 35 | JS_FreeCString(ctx, str); 36 | JS_FreeValue(ctx, exception_val); 37 | 38 | return ret; 39 | } 40 | 41 | 42 | struct QuickJSEngine : ScriptEngine { 43 | JSRuntime *rt = NULL; 44 | JSContext *ctx = NULL; 45 | 46 | QuickJSEngine() { 47 | rt = JS_NewRuntime(); 48 | } 49 | 50 | ~QuickJSEngine() { 51 | if (ctx) { 52 | JS_FreeContext(ctx); 53 | } 54 | if (rt) { 55 | JS_FreeRuntime(rt); 56 | } 57 | } 58 | 59 | std::string getEngineName() override { 60 | return "JavaScript"; 61 | } 62 | 63 | int run(const std::string& path, const std::string& script) override { 64 | assert(!ctx); 65 | // Create quickjs context 66 | ctx = JS_NewContext(rt); 67 | if (!ctx) { 68 | display("Could not create QuickJS context"); 69 | return -1; 70 | } 71 | 72 | // Initialize globals 73 | // user pointer 74 | JSValue global_obj = JS_GetGlobalObject(ctx); 75 | JSValue handle = JS_NewObjectClass(ctx, QuickJSEngineClass); 76 | JS_SetOpaque(handle, this); 77 | JS_SetPropertyStr(ctx, global_obj, "p", handle); 78 | 79 | // console 80 | JSValue console = JS_NewObject(ctx); 81 | JS_SetPropertyStr(ctx, console, "log", 82 | JS_NewCFunction(ctx, native_console_log, "log", 1)); 83 | JS_SetPropertyStr(ctx, console, "info", 84 | JS_NewCFunction(ctx, native_console_info, "info", 1)); 85 | JS_SetPropertyStr(ctx, console, "debug", 86 | JS_NewCFunction(ctx, native_console_debug, "debug", 1)); 87 | JS_SetPropertyStr(ctx, console, "warn", 88 | JS_NewCFunction(ctx, native_console_warn, "warn", 1)); 89 | JS_SetPropertyStr(ctx, global_obj, "console", console); 90 | 91 | // display 92 | JS_SetPropertyStr(ctx, global_obj, "display", 93 | JS_NewCFunction(ctx, native_display, "display", 1)); 94 | 95 | // config: Set defaults 96 | JSValue config = JS_NewObject(ctx); 97 | // frameDivider 98 | JSValue fd =JS_NewInt32(ctx, 32); 99 | JS_SetPropertyStr(ctx, config, "frameDivider", fd); 100 | JS_FreeValue(ctx, fd); 101 | // bufferSize 102 | JSValue bs = JS_NewInt32(ctx, 1); 103 | JS_SetPropertyStr(ctx, config, "bufferSize", bs); 104 | JS_SetPropertyStr(ctx, global_obj, "config", config); 105 | JS_FreeValue(ctx, bs); 106 | 107 | // Compile string 108 | JSValue val = JS_Eval(ctx, script.c_str(), script.size(), path.c_str(), 0); 109 | if (JS_IsException(val)) { 110 | display(ErrorToString(ctx)); 111 | JS_FreeValue(ctx, val); 112 | JS_FreeValue(ctx, global_obj); 113 | 114 | return -1; 115 | } 116 | 117 | ProcessBlock* block = getProcessBlock(); 118 | // config: Read values 119 | config = JS_GetPropertyStr(ctx, global_obj, "config"); 120 | { 121 | // frameDivider 122 | JSValue divider = JS_GetPropertyStr(ctx, config, "frameDivider"); 123 | int32_t dividerValue; 124 | if (JS_ToInt32(ctx, ÷rValue, divider) == 0) { 125 | setFrameDivider(dividerValue); 126 | } 127 | 128 | // bufferSize 129 | JSValue buffer = JS_GetPropertyStr(ctx, config, "bufferSize"); 130 | int32_t bufferValue; 131 | if (JS_ToInt32(ctx, &bufferValue, buffer) == 0) { 132 | setBufferSize(bufferValue); 133 | } 134 | 135 | JS_FreeValue(ctx, config); 136 | } 137 | 138 | // block 139 | JSValue blockIdx = JS_NewObject(ctx); 140 | { 141 | // inputs 142 | JSValue arr = JS_NewArray(ctx); 143 | for (int i = 0; i < NUM_ROWS; i++) { 144 | JSValue buffer = JS_NewArrayBuffer(ctx, (uint8_t *) block->inputs[i], sizeof(float) * block->bufferSize, NULL, NULL, true); 145 | if (JS_SetPropertyUint32(ctx, arr, i, buffer) < 0) { 146 | WARN("Unable to set property %d of inputs array", i); 147 | } 148 | } 149 | JS_SetPropertyStr(ctx, blockIdx, "inputs", arr); 150 | 151 | // outputs 152 | arr = JS_NewArray(ctx); 153 | for (int i = 0; i < NUM_ROWS; i++) { 154 | JSValue buffer = JS_NewArrayBuffer(ctx, (uint8_t *) block->outputs[i], sizeof(float) * block->bufferSize, NULL, NULL, true); 155 | if (JS_SetPropertyUint32(ctx, arr, i, buffer) < 0) { 156 | WARN("Unable to set property %d of outputs array", i); 157 | } 158 | } 159 | JS_SetPropertyStr(ctx, blockIdx, "outputs", arr); 160 | 161 | // knobs 162 | JSValue knobsIdx = JS_NewArrayBuffer(ctx, (uint8_t *) &block->knobs, sizeof(float) * NUM_ROWS, NULL, NULL, true); 163 | JS_SetPropertyStr(ctx, blockIdx, "knobs", knobsIdx); 164 | 165 | // switches 166 | JSValue switchesIdx = JS_NewArrayBuffer(ctx, (uint8_t *) &block->switches, sizeof(bool) * NUM_ROWS, NULL, NULL, true); 167 | JS_SetPropertyStr(ctx, blockIdx, "switches", switchesIdx); 168 | 169 | // lights 170 | arr = JS_NewArray(ctx); 171 | for (int i = 0; i < NUM_ROWS; i++) { 172 | JSValue buffer = JS_NewArrayBuffer(ctx, (uint8_t *) &block->lights[i], sizeof(float) * 3, NULL, NULL, true); 173 | if (JS_SetPropertyUint32(ctx, arr, i, buffer) < 0) { 174 | WARN("Unable to set property %d of lights array", i); 175 | } 176 | } 177 | JS_SetPropertyStr(ctx, blockIdx, "lights", arr); 178 | 179 | // switchlights 180 | arr = JS_NewArray(ctx); 181 | for (int i = 0; i < NUM_ROWS; i++) { 182 | JSValue buffer = JS_NewArrayBuffer(ctx, (uint8_t *) &block->switchLights[i], sizeof(float) * 3, NULL, NULL, true); 183 | if (JS_SetPropertyUint32(ctx, arr, i, buffer) < 0) { 184 | WARN("Unable to set property %d of switchLights array", i); 185 | } 186 | } 187 | JS_SetPropertyStr(ctx, blockIdx, "switchLights", arr); 188 | } 189 | 190 | JS_SetPropertyStr(ctx, global_obj, "block", blockIdx); 191 | 192 | // this is a horrible hack to get QuickJS to allocate the correct types 193 | static const std::string updateTypes = R"( 194 | for (var i = 0; i < 6; i++) { 195 | block.inputs[i] = new Float32Array(block.inputs[i]); 196 | block.outputs[i] = new Float32Array(block.outputs[i]); 197 | block.lights[i] = new Float32Array(block.lights[i]); 198 | block.switchLights[i] = new Float32Array(block.switchLights[i]); 199 | } 200 | block.knobs = new Float32Array(block.knobs); 201 | block.switches = new Uint8Array(block.switches); 202 | )"; 203 | 204 | JSValue hack = JS_Eval(ctx, updateTypes.c_str(), updateTypes.size(), "QuickJS Hack", 0); 205 | if (JS_IsException(hack)) { 206 | std::string errorString = ErrorToString(ctx); 207 | WARN("QuickJS: %s", errorString.c_str()); 208 | display(errorString.c_str()); 209 | } 210 | 211 | JS_FreeValue(ctx, hack); 212 | 213 | JS_FreeValue(ctx, val); 214 | JS_FreeValue(ctx, global_obj); 215 | 216 | if (JS_IsException(val)) { 217 | std::string errorString = ErrorToString(ctx); 218 | WARN("QuickJS: %s", errorString.c_str()); 219 | display(errorString.c_str()); 220 | 221 | JS_FreeValue(ctx, val); 222 | JS_FreeValue(ctx, blockIdx); 223 | JS_FreeValue(ctx, global_obj); 224 | 225 | return -1; 226 | } 227 | 228 | return 0; 229 | } 230 | 231 | int process() override { 232 | // global object 233 | JSValue global_obj = JS_GetGlobalObject(ctx); 234 | 235 | // block 236 | ProcessBlock* block = getProcessBlock(); 237 | JSValue blockIdx = JS_GetPropertyStr(ctx, global_obj, "block"); 238 | { 239 | // sampleRate 240 | JSValue sampleRate = JS_NewFloat64(ctx, (double) block->sampleRate); 241 | JS_SetPropertyStr(ctx, blockIdx, "sampleRate", sampleRate); 242 | 243 | // sampleTime 244 | JSValue sampleTime = JS_NewFloat64(ctx, (double) block->sampleTime); 245 | JS_SetPropertyStr(ctx, blockIdx, "sampleTime", sampleTime); 246 | 247 | // bufferSize 248 | JSValue bufferSize = JS_NewInt32(ctx, (double) block->bufferSize); 249 | JS_SetPropertyStr(ctx, blockIdx, "bufferSize", bufferSize); 250 | } 251 | 252 | JSValue process = JS_GetPropertyStr(ctx, global_obj, "process"); 253 | 254 | JSValue val = JS_Call(ctx, process, JS_UNDEFINED, 1, &blockIdx); 255 | 256 | if (JS_IsException(val)) { 257 | std::string errorString = ErrorToString(ctx); 258 | WARN("QuickJS: %s", errorString.c_str()); 259 | display(errorString.c_str()); 260 | 261 | JS_FreeValue(ctx, val); 262 | JS_FreeValue(ctx, process); 263 | JS_FreeValue(ctx, blockIdx); 264 | JS_FreeValue(ctx, global_obj); 265 | 266 | return -1; 267 | } 268 | 269 | JS_FreeValue(ctx, val); 270 | JS_FreeValue(ctx, process); 271 | JS_FreeValue(ctx, global_obj); 272 | 273 | return 0; 274 | } 275 | 276 | static QuickJSEngine* getQuickJSEngine(JSContext* ctx) { 277 | JSValue global_obj = JS_GetGlobalObject(ctx); 278 | JSValue p = JS_GetPropertyStr(ctx, global_obj, "p"); 279 | QuickJSEngine* engine = (QuickJSEngine*) JS_GetOpaque(p, QuickJSEngineClass); 280 | 281 | JS_FreeValue(ctx, p); 282 | JS_FreeValue(ctx, global_obj); 283 | 284 | return engine; 285 | } 286 | 287 | static JSValue native_console_log(JSContext* ctx, JSValueConst this_val, 288 | int argc, JSValueConst *argv) { 289 | if (argc) { 290 | const char *s = JS_ToCString(ctx, argv[0]); 291 | INFO("QuickJS: %s", s); 292 | } 293 | return JS_UNDEFINED; 294 | } 295 | static JSValue native_console_debug(JSContext* ctx, JSValueConst this_val, 296 | int argc, JSValueConst *argv) { 297 | if (argc) { 298 | const char *s = JS_ToCString(ctx, argv[0]); 299 | DEBUG("QuickJS: %s", s); 300 | } 301 | return JS_UNDEFINED; 302 | } 303 | static JSValue native_console_info(JSContext* ctx, JSValueConst this_val, 304 | int argc, JSValueConst *argv) { 305 | if (argc) { 306 | const char *s = JS_ToCString(ctx, argv[0]); 307 | INFO("QuickJS: %s", s); 308 | } 309 | return JS_UNDEFINED; 310 | } 311 | static JSValue native_console_warn(JSContext* ctx, JSValueConst this_val, 312 | int argc, JSValueConst *argv) { 313 | if (argc) { 314 | const char *s = JS_ToCString(ctx, argv[0]); 315 | WARN("QuickJS: %s", s); 316 | } 317 | return JS_UNDEFINED; 318 | } 319 | static JSValue native_display(JSContext* ctx, JSValueConst this_val, 320 | int argc, JSValueConst *argv) { 321 | if (argc) { 322 | const char *s = JS_ToCString(ctx, argv[0]); 323 | getQuickJSEngine(ctx)->display(s); 324 | } 325 | return JS_UNDEFINED; 326 | } 327 | }; 328 | 329 | 330 | __attribute__((constructor(1000))) 331 | static void constructor() { 332 | addScriptEngine("js"); 333 | } 334 | -------------------------------------------------------------------------------- /src/ScriptEngine.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | 4 | 5 | static const int NUM_ROWS = 6; 6 | static const int MAX_BUFFER_SIZE = 4096; 7 | 8 | 9 | struct Prototype; 10 | 11 | 12 | struct ProcessBlock { 13 | float sampleRate = 0.f; 14 | float sampleTime = 0.f; 15 | int bufferSize = 1; 16 | float inputs[NUM_ROWS][MAX_BUFFER_SIZE] = {}; 17 | float outputs[NUM_ROWS][MAX_BUFFER_SIZE] = {}; 18 | float knobs[NUM_ROWS] = {}; 19 | bool switches[NUM_ROWS] = {}; 20 | float lights[NUM_ROWS][3] = {}; 21 | float switchLights[NUM_ROWS][3] = {}; 22 | }; 23 | 24 | 25 | struct ScriptEngine { 26 | // Virtual methods for subclasses 27 | virtual ~ScriptEngine() {} 28 | virtual std::string getEngineName() {return "";} 29 | /** Executes the script. 30 | Return nonzero if failure, and set error message with setMessage(). 31 | Called only once per instance. 32 | */ 33 | virtual int run(const std::string& path, const std::string& script) {return 0;} 34 | 35 | /** Calls the script's process() method. 36 | Return nonzero if failure, and set error message with setMessage(). 37 | */ 38 | virtual int process() {return 0;} 39 | 40 | // Communication with Prototype module. 41 | // These cannot be called from your constructor, so initialize your engine in the run() method. 42 | void display(const std::string& message); 43 | void setFrameDivider(int frameDivider); 44 | void setBufferSize(int bufferSize); 45 | ProcessBlock* getProcessBlock(); 46 | // private 47 | Prototype* module = NULL; 48 | }; 49 | 50 | 51 | struct ScriptEngineFactory { 52 | virtual ScriptEngine* createScriptEngine() = 0; 53 | }; 54 | extern std::map scriptEngineFactories; 55 | 56 | /** Called from functions with 57 | __attribute__((constructor(1000))) 58 | */ 59 | template 60 | void addScriptEngine(std::string extension) { 61 | struct TScriptEngineFactory : ScriptEngineFactory { 62 | ScriptEngine* createScriptEngine() override { 63 | return new TScriptEngine; 64 | } 65 | }; 66 | scriptEngineFactories[extension] = new TScriptEngineFactory; 67 | } 68 | -------------------------------------------------------------------------------- /src/SuperColliderEngine.cpp: -------------------------------------------------------------------------------- 1 | #include "ScriptEngine.hpp" 2 | 3 | #include "lang/SC_LanguageClient.h" 4 | #include "LangSource/SC_LanguageConfig.hpp" 5 | #include "LangSource/SCBase.h" 6 | #include "LangSource/VMGlobals.h" 7 | #include "LangSource/PyrObject.h" 8 | #include "LangSource/PyrKernel.h" 9 | 10 | #include 11 | #include 12 | #include 13 | #include // getcwd 14 | 15 | // SuperCollider script engine for VCV-Prototype 16 | // Original author: Brian Heim 17 | 18 | /* DESIGN 19 | * 20 | * The idea is that the user writes a script that defines a couple environment variables: 21 | * 22 | * ~vcv_frameDivider: Integer 23 | * ~vcv_bufferSize: Integer 24 | * ~vcv_process: Function (VcvPrototypeProcessBlock -> VcvPrototypeProcessBlock) 25 | * 26 | * ~vcv_process is invoked once per process block. Users should not manipulate 27 | * the block object in any way other than by writing directly to the arrays in `outputs`, 28 | * `knobs`, `lights`, and `switchLights`. 29 | */ 30 | 31 | extern rack::plugin::Plugin* pluginInstance; // plugin's version of 'this' 32 | class SuperColliderEngine; 33 | 34 | class SC_VcvPrototypeClient final : public SC_LanguageClient { 35 | public: 36 | SC_VcvPrototypeClient(SuperColliderEngine* engine); 37 | ~SC_VcvPrototypeClient(); 38 | 39 | // These will invoke the interpreter 40 | void interpretScript(const std::string& script) noexcept 41 | { 42 | // Insert our own environment variable in the script so we can check 43 | // later (in testCompile()) whether it compiled all right. 44 | auto modifiedScript = std::string(compileTestVariableName) + "=1;" + script; 45 | interpret(modifiedScript.c_str()); 46 | testCompile(); 47 | } 48 | void evaluateProcessBlock(ProcessBlock* block) noexcept; 49 | void setNumRows() noexcept { 50 | auto&& command = "VcvPrototypeProcessBlock.numRows = " + std::to_string(NUM_ROWS); 51 | interpret(command.c_str()); 52 | } 53 | int getFrameDivider() noexcept { 54 | return getEnvVarAsPositiveInt("~vcv_frameDivider", "~vcv_frameDivider should be an Integer"); 55 | } 56 | int getBufferSize() noexcept { 57 | return getEnvVarAsPositiveInt("~vcv_bufferSize", "~vcv_bufferSize should be an Integer"); 58 | } 59 | 60 | bool isOk() const noexcept { return _ok; } 61 | 62 | void postText(const char* str, size_t len) override; 63 | 64 | // No concept of flushing or stdout vs stderr 65 | void postFlush(const char* str, size_t len) override { postText(str, len); } 66 | void postError(const char* str, size_t len) override { postText(str, len); } 67 | void flush() override {} 68 | 69 | private: 70 | static const char * compileTestVariableName; 71 | 72 | void interpret(const char * text) noexcept; 73 | 74 | void testCompile() noexcept { getEnvVarAsPositiveInt(compileTestVariableName, "Script failed to compile"); } 75 | 76 | // Called on unrecoverable error, will stop the plugin 77 | void fail(const std::string& msg) noexcept; 78 | 79 | const char* buildScProcessBlockString(const ProcessBlock* block) const noexcept; 80 | 81 | int getEnvVarAsPositiveInt(const char* envVarName, const char* errorMsg) noexcept; 82 | 83 | // converts top of stack back to ProcessBlock data 84 | void readScProcessBlockResult(ProcessBlock* block) noexcept; 85 | 86 | // helpers for copying SC info back into process block's arrays 87 | bool isVcvPrototypeProcessBlock(const PyrSlot* slot) const noexcept; 88 | bool copyFloatArray(const PyrSlot& inSlot, const char* context, const char* extraContext, float* outArray, int size) noexcept; 89 | template 90 | bool copyArrayOfFloatArrays(const PyrSlot& inSlot, const char* context, Array& array, int size) noexcept; 91 | 92 | SuperColliderEngine* _engine; 93 | PyrSymbol* _vcvPrototypeProcessBlockSym; 94 | bool _ok = true; 95 | }; 96 | 97 | const char * SC_VcvPrototypeClient::compileTestVariableName = "~vcv_secretTestCompileSentinel"; 98 | 99 | class SuperColliderEngine final : public ScriptEngine { 100 | public: 101 | ~SuperColliderEngine() noexcept { 102 | // Only join if client was started in the first place. 103 | if (_clientThread.joinable()) 104 | _clientThread.join(); 105 | engineRunning = false; 106 | } 107 | 108 | std::string getEngineName() override { return "SuperCollider"; } 109 | 110 | int run(const std::string& path, const std::string& script) override { 111 | if (engineRunning) { 112 | display("Only one SuperCollider engine may run at once"); 113 | return 1; 114 | } 115 | 116 | engineRunning = true; 117 | 118 | if (!_clientThread.joinable()) { 119 | _clientThread = std::thread([this, script]() { 120 | _client.reset(new SC_VcvPrototypeClient(this)); 121 | _client->setNumRows(); 122 | _client->interpretScript(script); 123 | setFrameDivider(_client->getFrameDivider()); 124 | setBufferSize(_client->getBufferSize()); 125 | finishClientLoading(); 126 | }); 127 | } 128 | 129 | return 0; 130 | } 131 | 132 | int process() override { 133 | if (waitingOnClient()) 134 | return 0; 135 | 136 | if (clientHasError()) 137 | return 1; 138 | 139 | _client->evaluateProcessBlock(getProcessBlock()); 140 | return clientHasError() ? 1 : 0; 141 | } 142 | 143 | private: 144 | // Used to limit the number of running SC instances to 1. 145 | static bool engineRunning; 146 | 147 | bool waitingOnClient() const noexcept { return !_clientRunning; } 148 | bool clientHasError() const noexcept { return !_client->isOk(); } 149 | void finishClientLoading() noexcept { _clientRunning = true; } 150 | 151 | std::unique_ptr _client; 152 | std::thread _clientThread; // used only to start up client 153 | std::atomic_bool _clientRunning{false}; // set to true when client is ready to process data 154 | }; 155 | 156 | bool SuperColliderEngine::engineRunning = false; 157 | 158 | SC_VcvPrototypeClient::SC_VcvPrototypeClient(SuperColliderEngine* engine) 159 | : SC_LanguageClient("SC VCV-Prototype client") 160 | , _engine(engine) 161 | { 162 | using Path = SC_LanguageConfig::Path; 163 | Path sc_lib_root = rack::asset::plugin(pluginInstance, "dep/supercollider/SCClassLibrary"); 164 | Path sc_ext_root = rack::asset::plugin(pluginInstance, "support/supercollider_extensions"); 165 | Path sc_yaml_path = rack::asset::plugin(pluginInstance, "dep/supercollider/sclang_vcv_config.yml"); 166 | 167 | if (!SC_LanguageConfig::defaultLibraryConfig(/* isStandalone */ true)) 168 | fail("Failed setting default library config"); 169 | if (!gLanguageConfig->addIncludedDirectory(sc_lib_root)) 170 | fail("Failed to add main include directory"); 171 | if (!gLanguageConfig->addIncludedDirectory(sc_ext_root)) 172 | fail("Failed to add extensions include directory"); 173 | if (!SC_LanguageConfig::writeLibraryConfigYAML(sc_yaml_path)) 174 | fail("Failed to write library config YAML file"); 175 | 176 | SC_LanguageConfig::setConfigPath(sc_yaml_path); 177 | 178 | // TODO allow users to add extensions somehow? 179 | 180 | initRuntime(); 181 | compileLibrary(/* isStandalone */ true); 182 | if (!isLibraryCompiled()) 183 | fail("Error while compiling class library"); 184 | 185 | _vcvPrototypeProcessBlockSym = getsym("VcvPrototypeProcessBlock"); 186 | } 187 | 188 | SC_VcvPrototypeClient::~SC_VcvPrototypeClient() { 189 | shutdownLibrary(); 190 | shutdownRuntime(); 191 | } 192 | 193 | void SC_VcvPrototypeClient::interpret(const char* text) noexcept { 194 | setCmdLine(text); 195 | interpretCmdLine(); 196 | } 197 | 198 | #ifdef SC_VCV_ENGINE_TIMING 199 | static long long int gmax = 0; 200 | static constexpr unsigned int nTimes = 1024; 201 | static long long int times[nTimes] = {}; 202 | static unsigned int timesIndex = 0; 203 | #endif 204 | 205 | void SC_VcvPrototypeClient::evaluateProcessBlock(ProcessBlock* block) noexcept { 206 | #ifdef SC_VCV_ENGINE_TIMING 207 | auto start = std::chrono::high_resolution_clock::now(); 208 | #endif 209 | auto* buf = buildScProcessBlockString(block); 210 | interpret(buf); 211 | readScProcessBlockResult(block); 212 | #ifdef SC_VCV_ENGINE_TIMING 213 | auto end = std::chrono::high_resolution_clock::now(); 214 | auto ticks = (end - start).count(); 215 | 216 | times[timesIndex] = ticks; 217 | timesIndex++; 218 | timesIndex %= nTimes; 219 | if (gmax < ticks) 220 | { 221 | gmax = ticks; 222 | std::printf("MAX TIME %lld\n", ticks); 223 | } 224 | if (timesIndex == 0) 225 | { 226 | std::printf("AVG TIME %lld\n", std::accumulate(std::begin(times), std::end(times), 0ull) / nTimes); 227 | } 228 | #endif 229 | } 230 | 231 | void SC_VcvPrototypeClient::postText(const char* str, size_t len) { 232 | // Ensure the last message logged (presumably an error) stays onscreen. 233 | if (_ok) 234 | _engine->display(std::string(str, len)); 235 | } 236 | 237 | void SC_VcvPrototypeClient::fail(const std::string& msg) noexcept { 238 | // Ensure the last messaged logged in a previous failure stays onscreen. 239 | if (_ok) 240 | _engine->display(msg); 241 | _ok = false; 242 | } 243 | 244 | // This should be well above what we ever need to represent a process block. 245 | // Currently comes out to around 500 KB. 246 | constexpr unsigned buildPbOverhead = 1024; 247 | constexpr unsigned buildPbFloatSize = 10; 248 | constexpr unsigned buildPbInsOutsSize = MAX_BUFFER_SIZE * NUM_ROWS * 2 * buildPbFloatSize; 249 | constexpr unsigned buildPbOtherArraysSize = buildPbFloatSize * NUM_ROWS * 8; 250 | constexpr unsigned buildPbBufferSize = buildPbOverhead + buildPbInsOutsSize + buildPbOtherArraysSize; 251 | 252 | // Don't write initial string every time 253 | #define BUILD_PB_BEGIN_STRING "^~vcv_process.(VcvPrototypeProcessBlock.new(" 254 | static char buildPbStringScratchBuf[buildPbBufferSize] = BUILD_PB_BEGIN_STRING; 255 | constexpr unsigned buildPbBeginStringOffset = sizeof(BUILD_PB_BEGIN_STRING); 256 | #undef BUILD_PB_BEGIN_STRING 257 | 258 | const char* SC_VcvPrototypeClient::buildScProcessBlockString(const ProcessBlock* block) const noexcept { 259 | auto* buf = buildPbStringScratchBuf + buildPbBeginStringOffset - 1; 260 | 261 | // Perhaps imprudently assuming snprintf never returns a negative code 262 | buf += std::sprintf(buf, "%.6f,%.6f,%d,", block->sampleRate, block->sampleTime, block->bufferSize); 263 | 264 | auto&& appendInOutArray = [&buf](const int bufferSize, const float (&data)[NUM_ROWS][MAX_BUFFER_SIZE]) { 265 | buf += std::sprintf(buf, "["); 266 | for (int i = 0; i < NUM_ROWS; ++i) { 267 | buf += std::sprintf(buf, "Signal["); 268 | for (int j = 0; j < bufferSize; ++j) { 269 | buf += std::sprintf(buf, "%g%c", data[i][j], j == bufferSize - 1 ? ' ' : ','); 270 | } 271 | buf += std::sprintf(buf, "]%c", i == NUM_ROWS - 1 ? ' ' : ','); 272 | } 273 | buf += std::sprintf(buf, "],"); 274 | }; 275 | 276 | appendInOutArray(block->bufferSize, block->inputs); 277 | appendInOutArray(block->bufferSize, block->outputs); 278 | 279 | // knobs 280 | buf += std::sprintf(buf, "FloatArray["); 281 | for (int i = 0; i < NUM_ROWS; ++i) 282 | buf += std::sprintf(buf, "%g%c", block->knobs[i], i == NUM_ROWS - 1 ? ' ' : ','); 283 | 284 | // switches 285 | buf += std::sprintf(buf, "],["); 286 | for (int i = 0; i < NUM_ROWS; ++i) 287 | buf += std::sprintf(buf, "%s%c", block->switches[i] ? "true" : "false", i == NUM_ROWS - 1 ? ' ' : ','); 288 | buf += std::sprintf(buf, "]"); 289 | 290 | // lights, switchlights 291 | auto&& appendLightsArray = [&buf](const float (&array)[NUM_ROWS][3]) { 292 | buf += std::sprintf(buf, ",["); 293 | for (int i = 0; i < NUM_ROWS; ++i) { 294 | buf += std::sprintf(buf, "FloatArray[%g,%g,%g]%c", array[i][0], array[i][1], array[i][2], 295 | i == NUM_ROWS - 1 ? ' ' : ','); 296 | } 297 | buf += std::sprintf(buf, "]"); 298 | }; 299 | 300 | appendLightsArray(block->lights); 301 | appendLightsArray(block->switchLights); 302 | 303 | buf += std::sprintf(buf, "));"); 304 | 305 | return buildPbStringScratchBuf; 306 | } 307 | 308 | int SC_VcvPrototypeClient::getEnvVarAsPositiveInt(const char* envVarName, const char* errorMsg) noexcept { 309 | auto command = std::string("^") + envVarName; 310 | interpret(command.c_str()); 311 | 312 | auto* resultSlot = &scGlobals()->result; 313 | if (IsInt(resultSlot)) { 314 | auto intResult = slotRawInt(resultSlot); 315 | if (intResult > 0) { 316 | return intResult; 317 | } else { 318 | fail(std::string(envVarName) + " should be > 0"); 319 | return -1; 320 | } 321 | } else { 322 | fail(errorMsg); 323 | return -1; 324 | } 325 | } 326 | 327 | 328 | void SC_VcvPrototypeClient::readScProcessBlockResult(ProcessBlock* block) noexcept { 329 | auto* resultSlot = &scGlobals()->result; 330 | if (!isVcvPrototypeProcessBlock(resultSlot)) { 331 | fail("Result of ~vcv_process must be an instance of VcvPrototypeProcessBlock"); 332 | return; 333 | } 334 | 335 | PyrObject* object = slotRawObject(resultSlot); 336 | auto* rawSlots = static_cast(object->slots); 337 | 338 | // See .sc object definition 339 | constexpr unsigned outputsSlotIndex = 4; 340 | constexpr unsigned knobsSlotIndex = 5; 341 | constexpr unsigned lightsSlotIndex = 7; 342 | constexpr unsigned switchLightsSlotIndex = 8; 343 | 344 | if (!copyArrayOfFloatArrays(rawSlots[outputsSlotIndex], "outputs", block->outputs, block->bufferSize)) 345 | return; 346 | if (!copyArrayOfFloatArrays(rawSlots[lightsSlotIndex], "lights", block->lights, 3)) 347 | return; 348 | if (!copyArrayOfFloatArrays(rawSlots[switchLightsSlotIndex], "switchLights", block->switchLights, 3)) 349 | return; 350 | if (!copyFloatArray(rawSlots[knobsSlotIndex], "", "knobs", block->knobs, NUM_ROWS)) 351 | return; 352 | } 353 | 354 | bool SC_VcvPrototypeClient::isVcvPrototypeProcessBlock(const PyrSlot* slot) const noexcept { 355 | if (NotObj(slot)) 356 | return false; 357 | 358 | auto* klass = slotRawObject(slot)->classptr; 359 | auto* klassNameSymbol = slotRawSymbol(&klass->name); 360 | return klassNameSymbol == _vcvPrototypeProcessBlockSym; 361 | } 362 | 363 | // It's somewhat bad design that we pass two const char*s here, but this avoids an allocation while also providing 364 | // good context for errors. 365 | bool SC_VcvPrototypeClient::copyFloatArray(const PyrSlot& inSlot, const char* context, const char* extraContext, float* outArray, int size) noexcept 366 | { 367 | if (!isKindOfSlot(const_cast(&inSlot), class_floatarray)) { 368 | fail(std::string(context) + extraContext + " must be a FloatArray"); 369 | return false; 370 | } 371 | auto* floatArrayObj = slotRawObject(&inSlot); 372 | if (floatArrayObj->size != size) { 373 | fail(std::string(context) + extraContext + " must be of size " + std::to_string(size)); 374 | return false; 375 | } 376 | 377 | auto* floatArray = reinterpret_cast(floatArrayObj); 378 | auto* rawArray = static_cast(floatArray->f); 379 | std::memcpy(outArray, rawArray, size * sizeof(float)); 380 | return true; 381 | } 382 | 383 | template 384 | bool SC_VcvPrototypeClient::copyArrayOfFloatArrays(const PyrSlot& inSlot, const char* context, Array& outArray, int size) noexcept 385 | { 386 | // OUTPUTS 387 | if (!isKindOfSlot(const_cast(&inSlot), class_array)) { 388 | fail(std::string(context) + " must be a Array"); 389 | return false; 390 | } 391 | auto* inObj = slotRawObject(&inSlot); 392 | if (inObj->size != NUM_ROWS) { 393 | fail(std::string(context) + " must be of size " + std::to_string(NUM_ROWS)); 394 | return false; 395 | } 396 | 397 | for (int i = 0; i < NUM_ROWS; ++i) { 398 | if (!copyFloatArray(inObj->slots[i], "subarray of ", context, outArray[i], size)) { 399 | return false; 400 | } 401 | } 402 | 403 | return true; 404 | } 405 | 406 | __attribute__((constructor(1000))) 407 | static void constructor() { 408 | addScriptEngine("sc"); 409 | addScriptEngine("scd"); 410 | } 411 | -------------------------------------------------------------------------------- /src/VultEngine.cpp: -------------------------------------------------------------------------------- 1 | #include "ScriptEngine.hpp" 2 | #include "vultc.h" 3 | #include 4 | 5 | /* The Vult engine relies on both QuickJS and LuaJIT. 6 | * 7 | * The compiler is written in OCaml but converted to JavaScript. The JavaScript 8 | * code is embedded as a string and executed by the QuickJs engine. The Vult 9 | * compiler generates Lua code that is executed by the LuaJIT engine. 10 | */ 11 | 12 | // Special version of createScriptEngine that only creates Lua engines 13 | ScriptEngine* createLuaEngine() { 14 | auto it = scriptEngineFactories.find("lua"); 15 | if (it == scriptEngineFactories.end()) 16 | return NULL; 17 | return it->second->createScriptEngine(); 18 | } 19 | 20 | struct VultEngine : ScriptEngine { 21 | 22 | // used to run the lua generated code 23 | ScriptEngine* luaEngine; 24 | 25 | // used to run the Vult compiler 26 | JSRuntime* rt = NULL; 27 | JSContext* ctx = NULL; 28 | 29 | VultEngine() { 30 | rt = JS_NewRuntime(); 31 | // Create QuickJS context 32 | ctx = JS_NewContext(rt); 33 | if (!ctx) { 34 | display("Could not create QuickJS context"); 35 | return; 36 | } 37 | 38 | JSValue global_obj = JS_GetGlobalObject(ctx); 39 | 40 | // Load the Vult compiler code 41 | JSValue val = 42 | JS_Eval(ctx, (const char*)vultc_h, vultc_h_size, "vultc.js", 0); 43 | if (JS_IsException(val)) { 44 | display("Error loading the Vult compiler"); 45 | JS_FreeValue(ctx, val); 46 | JS_FreeValue(ctx, global_obj); 47 | return; 48 | } 49 | } 50 | 51 | ~VultEngine() { 52 | if (ctx) { 53 | JS_FreeContext(ctx); 54 | } 55 | if (rt) { 56 | JS_FreeRuntime(rt); 57 | } 58 | } 59 | 60 | std::string getEngineName() override { 61 | return "Vult"; 62 | } 63 | 64 | int run(const std::string& path, const std::string& script) override { 65 | display("Loading..."); 66 | 67 | JSValue global_obj = JS_GetGlobalObject(ctx); 68 | 69 | // Put the script text in the 'code' variable 70 | JSValue code = JS_NewString(ctx, script.c_str()); 71 | JS_SetPropertyStr(ctx, global_obj, "code", code); 72 | 73 | // Put the script path in 'file' variable 74 | JSValue file = JS_NewString(ctx, path.c_str()); 75 | JS_SetPropertyStr(ctx, global_obj, "file", file); 76 | 77 | display("Compiling..."); 78 | // Call the Vult compiler to generate Lua code 79 | static const std::string testVult = R"( 80 | var result = vult.generateLua([{ file:file, code:code}],{ output:'Engine', template:'vcv-prototype'});)"; 81 | 82 | JSValue compile = 83 | JS_Eval(ctx, testVult.c_str(), testVult.size(), "Compile", 0); 84 | 85 | JS_FreeValue(ctx, code); 86 | JS_FreeValue(ctx, file); 87 | 88 | // If there are any internal errors, the execution could fail 89 | if (JS_IsException(compile)) { 90 | display("Fatal error in the Vult compiler"); 91 | JS_FreeValue(ctx, global_obj); 92 | return -1; 93 | } 94 | 95 | // Retrive the variable 'result' 96 | JSValue result = JS_GetPropertyStr(ctx, global_obj, "result"); 97 | // Get the first element of the 'result' array 98 | JSValue first = JS_GetPropertyUint32(ctx, result, 0); 99 | // Try to get the 'msg' field which is only present in error messages 100 | JSValue msg = JS_GetPropertyStr(ctx, first, "msg"); 101 | // Display the error if any 102 | if (!JS_IsUndefined(msg)) { 103 | const char* text = JS_ToCString(ctx, msg); 104 | const char* row = 105 | JS_ToCString(ctx, JS_GetPropertyStr(ctx, first, "line")); 106 | const char* col = JS_ToCString(ctx, JS_GetPropertyStr(ctx, first, "col")); 107 | // Compose the error message 108 | std::stringstream error; 109 | error << "line:" << row << ":" << col << ": " << text; 110 | WARN("Vult Error: %s", error.str().c_str()); 111 | display(error.str().c_str()); 112 | 113 | JS_FreeValue(ctx, result); 114 | JS_FreeValue(ctx, first); 115 | JS_FreeValue(ctx, msg); 116 | return -1; 117 | } 118 | // In case of no error, retrieve the generated code 119 | JSValue luacode = JS_GetPropertyStr(ctx, first, "code"); 120 | std::string luacode_str(JS_ToCString(ctx, luacode)); 121 | 122 | //WARN("Generated Code: %s", luacode_str.c_str()); 123 | 124 | luaEngine = createLuaEngine(); 125 | 126 | if (!luaEngine) { 127 | WARN("Could not create a Lua script engine"); 128 | return -1; 129 | } 130 | 131 | luaEngine->module = this->module; 132 | 133 | display("Running..."); 134 | 135 | JS_FreeValue(ctx, luacode); 136 | JS_FreeValue(ctx, first); 137 | JS_FreeValue(ctx, msg); 138 | JS_FreeValue(ctx, msg); 139 | JS_FreeValue(ctx, global_obj); 140 | 141 | return luaEngine->run(path, luacode_str); 142 | } 143 | 144 | int process() override { 145 | if (!luaEngine) 146 | return -1; 147 | return luaEngine->process(); 148 | } 149 | }; 150 | 151 | __attribute__((constructor(1000))) 152 | static void constructor() { 153 | addScriptEngine("vult"); 154 | } 155 | -------------------------------------------------------------------------------- /support/supercollider_extensions/VcvPrototypeProcessBlock.sc: -------------------------------------------------------------------------------- 1 | // Represents a process block in VCV Prototype. Users should generally not create instances 2 | // of this class themselves, and should only modify the values in `outputs`, `knobs`, `lights`, 3 | // and `switchLights`. 4 | // 5 | // Original author: Brian Heim 6 | 7 | VcvPrototypeProcessBlock { 8 | classvar <>numRows; // Set internally, do not modify 9 | 10 | // Code in SuperColliderEngine.cpp relies on ordering. 11 | var outputs; // Array[numRows] of Signal[bufferSize] 16 | var <>knobs; // FloatArray[numRows] 17 | var lights; // Array[numRows] of FloatArray[3] 19 | var <>switchLights; // Array[numRows] of FloatArray[3] 20 | 21 | // TODO not needed? 22 | *new { |... args| ^super.newCopyArgs(*args); } 23 | } 24 | -------------------------------------------------------------------------------- /tests/fall.js: -------------------------------------------------------------------------------- 1 | config.frameDivider = 1 2 | config.bufferSize = 32 3 | 4 | var v = 0 5 | function process(block) { 6 | for (var i = 0; i < block.bufferSize; i++) { 7 | block.outputs[0][i] = v 8 | v -= block.sampleTime * 0.1 9 | } 10 | } -------------------------------------------------------------------------------- /tests/hello.lua: -------------------------------------------------------------------------------- 1 | 2 | config.frameDivider = 1 3 | config.bufferSize = 16 4 | 5 | function process(block) 6 | for i=1,6 do 7 | for j=1,block.bufferSize do 8 | block.outputs[i][j] = block.inputs[i][j] 9 | end 10 | end 11 | end 12 | 13 | print("Hello, world!") 14 | -------------------------------------------------------------------------------- /tests/hello.py: -------------------------------------------------------------------------------- 1 | frame = 0 2 | 3 | print(config) 4 | print(config.frameDivider) 5 | print(config.bufferSize) 6 | 7 | def process(block): 8 | global frame 9 | frame += 1 10 | display(frame) 11 | # print(block) 12 | # block.switch_lights[:, 2] = 1 13 | print(block.inputs) 14 | print(block.outputs) 15 | print(block.knobs) 16 | print(block.switches) 17 | print(block.lights) 18 | print(block.switch_lights) 19 | # print("===") 20 | 21 | print("Hello, world!") 22 | -------------------------------------------------------------------------------- /tests/process_error.js: -------------------------------------------------------------------------------- 1 | 2 | function process(args) { 3 | args.test() 4 | } -------------------------------------------------------------------------------- /tests/run_error.js: -------------------------------------------------------------------------------- 1 | test() 2 | -------------------------------------------------------------------------------- /tests/sandbox.js: -------------------------------------------------------------------------------- 1 | 2 | config.frameDivider = 256 3 | 4 | var knobPresets = [] 5 | for (var i = 0; i < 6; i++) { 6 | knobPresets[i] = [0, 0, 0, 0, 0, 0] 7 | } 8 | var lastI = 0 9 | 10 | function process(block) { 11 | for (var j = 0; j < 6; j++) { 12 | knobPresets[lastI][j] = block.knobs[j] 13 | block.lights[j][0] = block.knobs[j] 14 | } 15 | for (var i = 0; i < 6; i++) { 16 | if (block.switches[i]) { 17 | for (var j = 0; j < 6; j++) { 18 | block.knobs[j] = knobPresets[i][j] 19 | } 20 | lastI = i 21 | } 22 | block.switchLights[i][0] = (lastI == i) ? 1 : 0 23 | } 24 | } -------------------------------------------------------------------------------- /tests/syntax_error.js: -------------------------------------------------------------------------------- 1 | .test() --------------------------------------------------------------------------------