From ac43a8ce585f1819277e1925a202656c7632968d Mon Sep 17 00:00:00 2001 From: Stephane Letz Date: Sun, 19 Jul 2020 14:59:26 +0200 Subject: [PATCH] Implement UI handler for switches, knobs and leds. --- Makefile | 1 + faust_libraries/rack.lib | 63 ++++++++++++ src/FaustEngine.cpp | 212 +++++++++++++++++++++++++++++++++++++-- 3 files changed, 269 insertions(+), 7 deletions(-) create mode 100644 faust_libraries/rack.lib diff --git a/Makefile b/Makefile index 2c74d2c..69ece5d 100644 --- a/Makefile +++ b/Makefile @@ -258,6 +258,7 @@ FLAGS += -I/use/local/include LDFLAGS += -L/usr/local/lib -lfaust DEPS += $(faust) OBJECTS += $(faust) +DISTRIBUTABLES += faust_libraries FAUST_MAKE_FLAGS += prefix="$(DEP_PATH)" endif diff --git a/faust_libraries/rack.lib b/faust_libraries/rack.lib new file mode 100644 index 0000000..03a036d --- /dev/null +++ b/faust_libraries/rack.lib @@ -0,0 +1,63 @@ +/* ---------------------------------------------------------------------------- + + ProtoFaust + ========== + DSP prototyping in Faust for VCV Rack + + Copyright (c) 2019-2020 Martin Zuther (http://www.mzuther.de/) and + contributors + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + + Thank you for using free software! + +---------------------------------------------------------------------------- */ + + +// Converts 1 V/oct to frequency in Hertz. +// +// The conversion formula is: 440 * 2 ^ (volts - 0.75) +// The factor 0.75 shifts 0 V to C-4 (261.6256 Hz) +cv_pitch2freq(cv_pitch) = 440 * 2 ^ (cv_pitch - 0.75); + + +// Converts frequency in Hertz to 1 V/oct. +// +// The conversion formula is: log2(hertz / 440) + 0.75 +// The factor 0.75 shifts 0 V to C-4 (261.6256 Hz) +freq2cv_pitch(freq) = ma.log2(freq / 440) + 0.75; + + +// Converts 200 mV/oct to frequency in Hertz. +i_cv_pitch2freq(i_cv_pitch) = i_cv_pitch : internal2cv_pitch : cv_pitch2freq; + + +// Converts frequency in Hertz to 200 mV/oct. +freq2i_cv_pitch(freq) = freq : freq2cv_pitch : cv_pitch2internal; + + +// Converts Eurorack's 1 V/oct to internal 200 mv/oct. +cv_pitch2internal(cv_pitch) = cv_pitch / 5; + + +// Converts internal 200 mv/oct to Eurorack's 1 V/oct. +internal2cv_pitch(i_cv_pitch) = i_cv_pitch * 5; + + +// Converts Eurorack's CV (range of 10V) to internal CV (range of 1V) +cv2internal(cv) = cv / 10; + + +// Converts internal CV (range of 1V) to Eurorack's CV (range of 10V) +internal2cv(i_cv) = i_cv * 10; diff --git a/src/FaustEngine.cpp b/src/FaustEngine.cpp index df781e6..e302a39 100644 --- a/src/FaustEngine.cpp +++ b/src/FaustEngine.cpp @@ -1,17 +1,175 @@ +/************************************************************************ + FAUST Architecture File + Copyright (C) 2020 GRAME, Centre National de Creation Musicale + --------------------------------------------------------------------- + This Architecture section is free software; you can redistribute it + and/or modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 3 of + the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; If not, see . + + EXCEPTION : As a special exception, you may create a larger work + that contains this FAUST architecture section and distribute + that work under terms of your choice, so long as this FAUST + architecture section is not modified. + ************************************************************************/ + #include "ScriptEngine.hpp" #include #include +#include +#include #include #define kBufferSize 64 +extern rack::Plugin* pluginInstance; + +// UI handler for switches, knobs and leds +struct RackUI : public GenericUI +{ + FAUSTFLOAT* fSwitches[NUM_ROWS]; + ConverterZoneControl* fKnobs[NUM_ROWS]; + FAUSTFLOAT* fLedRed[NUM_ROWS]; + FAUSTFLOAT* fLedGreen[NUM_ROWS]; + FAUSTFLOAT* fLedBlue[NUM_ROWS]; + + std::string fKey, fValue, fScale; + + void addItem(FAUSTFLOAT* table[NUM_ROWS], FAUSTFLOAT* zone, const std::string& value) + { + try { + int index = std::stoi(value); + if (index >= 0 && index <= NUM_ROWS) { + table[index-1] = zone; + } else { + std::cerr << "ERROR : incorrect '" << index << "' value !\n"; + } + } catch (std::invalid_argument& e) { + std::cerr << "ERROR : " << e.what() << std::endl; + } + fValue = fKey = fScale = ""; + } + + void addItemConverter(ConverterZoneControl* table[NUM_ROWS], FAUSTFLOAT* zone, FAUSTFLOAT min, FAUSTFLOAT max, const std::string& value) + { + try { + int index = std::stoi(value); + if (index >= 0 && index <= NUM_ROWS) { + // Select appropriate converter according to scale mode + if (fScale == "log") { + table[index-1] = new ConverterZoneControl(zone, new LogValueConverter(0., 1., min, max)); + } else if (fScale == "exp") { + table[index-1] = new ConverterZoneControl(zone, new ExpValueConverter(0., 1., min, max)); + } else { + table[index-1] = new ConverterZoneControl(zone, new LinearValueConverter(0., 1., min, max)); + } + } else { + std::cerr << "ERROR : incorrect '" << index << "' value !\n"; + } + } catch (std::invalid_argument& e) { + std::cerr << "ERROR : " << e.what() << std::endl; + } + fValue = fKey = fScale = ""; + } + + RackUI() + { + fScale = "lin"; + for (int i = 0; i < NUM_ROWS; i++) { + fSwitches[i] = nullptr; + fKnobs[i] = nullptr; + fLedRed[i] = nullptr; + fLedGreen[i] = nullptr; + fLedBlue[i] = nullptr; + } + } + + virtual ~RackUI() + { + for (int i = 0; i < NUM_ROWS; i++) { + delete fKnobs[i]; + } + } + + void addButton(const char* label, FAUSTFLOAT* zone) + { + if (fKey == "switch") { + addItem(fSwitches, zone, fValue); + } + } + + void addVerticalSlider(const char* label, FAUSTFLOAT* zone, FAUSTFLOAT init, FAUSTFLOAT min, FAUSTFLOAT max, FAUSTFLOAT step) + { + addNumEntry(label, zone, init, min, max, step); + } + void addHorizontalSlider(const char* label, FAUSTFLOAT* zone, FAUSTFLOAT init, FAUSTFLOAT min, FAUSTFLOAT max, FAUSTFLOAT step) + { + addNumEntry(label, zone, init, min, max, step); + } + void addNumEntry(const char* label, FAUSTFLOAT* zone, FAUSTFLOAT init, FAUSTFLOAT min, FAUSTFLOAT max, FAUSTFLOAT step) + { + if (fKey == "knob") { + addItemConverter(fKnobs, zone, min, max, fValue); + } + } + + void addBarGraph(FAUSTFLOAT* zone) + { + if (fKey == "led_red") { + addItem(fLedRed, zone, fValue); + } else if (fKey == "led_green") { + addItem(fLedGreen, zone, fValue); + } else if (fKey == "led_blue") { + addItem(fLedBlue, zone, fValue); + } + } + + void addHorizontalBargraph(const char* label, FAUSTFLOAT* zone, FAUSTFLOAT min, FAUSTFLOAT max) + { + addBarGraph(zone); + } + void addVerticalBargraph(const char* label, FAUSTFLOAT* zone, FAUSTFLOAT min, FAUSTFLOAT max) + { + addBarGraph(zone); + } + + void declare(FAUSTFLOAT* zone, const char* key, const char* val) + { + if ((std::string(key) == "switch") + || (std::string(key) == "knob") + || (std::string(key) == "led_red") + || (std::string(key) == "led_green") + || (std::string(key) == "led_blue")) { + fKey = key; + fValue = val; + } else if (std::string(key) == "scale") { + fScale = val; + } + } + +}; + +// Faust engine using libfaust/LLVM class FaustEngine : public ScriptEngine { public: - FaustEngine():fDSPFactory(nullptr), fDSP(nullptr), fInputs(nullptr), fOutputs(nullptr) + FaustEngine(): + fDSPFactory(nullptr), + fDSP(nullptr), + fInputs(nullptr), + fOutputs(nullptr), + fDSPLibraries(rack::asset::plugin(pluginInstance, "faust_libraries")) {} ~FaustEngine() @@ -41,9 +199,16 @@ class FaustEngine : public ScriptEngine { if (!fDSPFactory) { // Otherwise recompile the DSP - fDSPFactory = createDSPFactoryFromString("FaustDSP", script, 0, NULL, "", error_msg, -1); + int argc = 0; + const char* argv[8]; + argv[argc++] = "-I"; + argv[argc++] = fDSPLibraries.c_str(); + argv[argc] = nullptr; // NULL terminated argv + + fDSPFactory = createDSPFactoryFromString("FaustDSP", script, argc, argv, "", error_msg, -1); if (!fDSPFactory) { - display("ERROR: cannot create factory !"); + display("ERROR : cannot create factory !"); + std::cerr << error_msg; return -1; } else { // And save the cache @@ -62,16 +227,19 @@ class FaustEngine : public ScriptEngine { } // Prepare inputs/outputs - if (fDSP->getNumInputs() > 6) { + if (fDSP->getNumInputs() > NUM_ROWS) { display("ERROR: DSP has " + std::to_string(fDSP->getNumInputs()) + " inputs !"); return -1; } - if (fDSP->getNumOutputs() > 6) { + if (fDSP->getNumOutputs() > NUM_ROWS) { display("ERROR: DSP has " + std::to_string(fDSP->getNumInputs()) + " outputs !"); return -1; } + // Setup UI + fDSP->buildUserInterface(&fRackUI); + setFrameDivider(1); setBufferSize(kBufferSize); @@ -95,11 +263,39 @@ class FaustEngine : public ScriptEngine { int process() override { + ProcessBlock* block = getProcessBlock(); + // Possibly update SR - if (getProcessBlock()->sampleRate != fDSP->getSampleRate()) { - fDSP->init(getProcessBlock()->sampleRate); + if (block->sampleRate != fDSP->getSampleRate()) { + fDSP->init(block->sampleRate); } + + // Update inputs controllers + for (int i = 0; i < NUM_ROWS; i++) { + if (fRackUI.fSwitches[i]) { + *fRackUI.fSwitches[i] = block->switches[i]; + } + if (fRackUI.fKnobs[i]) { + fRackUI.fKnobs[i]->update(block->knobs[i]); + } + } + + // Compute samples fDSP->compute(kBufferSize, fInputs, fOutputs); + + // Update output controllers + for (int i = 0; i < NUM_ROWS; i++) { + if (fRackUI.fLedRed[i]) { + block->lights[i][0] = *fRackUI.fLedRed[i]; + } + if (fRackUI.fLedGreen[i]) { + block->lights[i][1] = *fRackUI.fLedGreen[i]; + } + if (fRackUI.fLedBlue[i]) { + block->lights[i][2] = *fRackUI.fLedBlue[i]; + } + } + return 0; } @@ -108,6 +304,8 @@ class FaustEngine : public ScriptEngine { llvm_dsp* fDSP; FAUSTFLOAT** fInputs; FAUSTFLOAT** fOutputs; + RackUI fRackUI; + std::string fDSPLibraries; }; __attribute__((constructor(1000)))