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)))