#include "Template.hpp" #include "dsp/digital.hpp" #include "formula/Formula.h" namespace rack_plugin_FrankBussFormula { struct FrankBussFormulaModule; class MyTextField : public LedDisplayTextField { public: MyTextField() : LedDisplayTextField() {} void setModule(FrankBussFormulaModule* _module) { module = _module; } virtual void onTextChange() override; private: FrankBussFormulaModule* module; }; struct FrankBussFormulaModule : Module { enum ParamIds { X_PARAM, Y_PARAM, Z_PARAM, KNOB_PARAM, CLAMP_PARAM, B_MINUS_1_PARAM, B_0_PARAM, B_1_PARAM, NUM_PARAMS }; enum InputIds { X_INPUT, Y_INPUT, Z_INPUT, W_INPUT, NUM_INPUTS }; enum OutputIds { FORMULA_OUTPUT, NUM_OUTPUTS }; enum LightIds { BLINK_LIGHT, CLAMP_LIGHT, B_MINUS_1_LIGHT, B_0_LIGHT, B_1_LIGHT, NUM_LIGHTS }; MyTextField* textField; MyTextField* freqField; float blinkPhase = 0.0f; Formula formula; Formula freqFormula; bool compiled = false; bool doclamp = true; bool freqFormulaEnabled = false; float radiobutton = 0.0f; float phase = 0.0f; SchmittTrigger clampTrigger; SchmittTrigger bMinus1Trigger; SchmittTrigger b0Trigger; SchmittTrigger b1Trigger; float* formulaP = NULL; float* formulaK = NULL; float* formulaB = NULL; float* formulaW = NULL; float* formulaX = NULL; float* formulaY = NULL; float* formulaZ = NULL; float* freqFormulaP = NULL; float* freqFormulaK = NULL; float* freqFormulaB = NULL; float* freqFormulaW = NULL; float* freqFormulaX = NULL; float* freqFormulaY = NULL; float* freqFormulaZ = NULL; FrankBussFormulaModule() : Module(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS) { } void step() override { if (clampTrigger.process(params[CLAMP_PARAM].value)) { doclamp = !doclamp; } if (bMinus1Trigger.process(params[B_MINUS_1_PARAM].value)) { radiobutton = -1.0f; } if (b0Trigger.process(params[B_0_PARAM].value)) { radiobutton = 0.0f; } if (b1Trigger.process(params[B_1_PARAM].value)) { radiobutton = 1.0f; } float deltaTime = engineGetSampleTime(); // evaluate frequency and output formula float val = 0; if (compiled) { try { // get inputs float w = inputs[W_INPUT].value; float x = inputs[X_INPUT].value; float y = inputs[Y_INPUT].value; float z = inputs[Z_INPUT].value; // knob float k = params[KNOB_PARAM].value; // set all variables *formulaP = phase; *formulaK = k; *formulaB = radiobutton; *formulaW = w; *formulaX = x; *formulaY = y; *formulaZ = z; if (freqFormulaEnabled) { *freqFormulaP = phase; *freqFormulaK = k; *freqFormulaB = radiobutton; *freqFormulaW = w; *freqFormulaX = x; *freqFormulaY = y; *freqFormulaZ = z; float freq = evalFormula(freqFormula); phase += freq * engineGetSampleTime(); if (phase > 1.0f) phase -= 1.0f; } val = evalFormula(formula); if (doclamp) val = clamp(val, -5.0f, 5.0f); } catch (MathError&) { // ignore math errors, e.g. division by zero } catch (exception&) { // for all other exceptions, set compiled to false, e.g. VariableNotFound compiled = false; } } // set output outputs[FORMULA_OUTPUT].value = val; // Blink light at 1Hz blinkPhase += deltaTime; if (blinkPhase >= 1.0f) blinkPhase -= 1.0f; if (compiled) { lights[BLINK_LIGHT].value = 1.0f; } else { lights[BLINK_LIGHT].value = (blinkPhase < 0.5f) ? 1.0f : 0.0f; } lights[CLAMP_LIGHT].value = (doclamp); lights[B_MINUS_1_LIGHT].value = (radiobutton == -1.0f); lights[B_0_LIGHT].value = (radiobutton == -0.0f); lights[B_1_LIGHT].value = (radiobutton == 1.0f); } void parseFormula(Formula& formula, string expr) { formula.setVariable("pi", float(M_PI)); formula.setVariable("e", float(M_E)); formula.setVariable("p", 0); formula.setVariable("k", 0); formula.setVariable("b", 0); formula.setVariable("w", 0); formula.setVariable("x", 0); formula.setVariable("y", 0); formula.setVariable("z", 0); formula.setExpression(expr); } float evalFormula(Formula& formula) { // eval float val = formula.eval(); if (!isfinite(val) || isnan(val)) val = 0.0f; return val; } void onCreate () override { compiled = false; phase = 0; if (textField->text.size() > 0) { try { parseFormula(formula, textField->text); freqFormulaEnabled = false; if (freqField->text.size() > 0) { parseFormula(freqFormula, freqField->text); freqFormulaEnabled = true; } formulaP = formula.getVariableAddress("p"); formulaK = formula.getVariableAddress("k"); formulaB = formula.getVariableAddress("b"); formulaW = formula.getVariableAddress("w"); formulaX = formula.getVariableAddress("x"); formulaY = formula.getVariableAddress("y"); formulaZ = formula.getVariableAddress("z"); if (freqFormulaEnabled) { freqFormulaP = freqFormula.getVariableAddress("p"); freqFormulaK = freqFormula.getVariableAddress("k"); freqFormulaB = freqFormula.getVariableAddress("b"); freqFormulaW = freqFormula.getVariableAddress("w"); freqFormulaX = freqFormula.getVariableAddress("x"); freqFormulaY = freqFormula.getVariableAddress("y"); freqFormulaZ = freqFormula.getVariableAddress("z"); } compiled = true; } catch (exception& e) { printf("formula exception: %s\n", e.what()); } } } void onReset () override { onCreate(); } json_t *toJson() override { json_t *rootJ = json_object(); json_object_set_new(rootJ, "text", json_string(textField->text.c_str())); json_object_set_new(rootJ, "freq", json_string(freqField->text.c_str())); json_object_set_new(rootJ, "clamp", json_boolean(doclamp)); json_object_set_new(rootJ, "button", json_real(radiobutton)); return rootJ; } void fromJson(json_t *rootJ) override { json_t *textJ = json_object_get(rootJ, "text"); if (textJ) textField->text = json_string_value(textJ); json_t *freqJ = json_object_get(rootJ, "freq"); if (freqJ) freqField->text = json_string_value(freqJ); json_t *clampJ = json_object_get(rootJ, "clamp"); if (clampJ) doclamp = json_is_true(clampJ); json_t *buttonJ = json_object_get(rootJ, "button"); if (buttonJ) radiobutton = (float)json_real_value(buttonJ); onCreate(); } }; void MyTextField::onTextChange() { module->onCreate(); } struct FrankBussFormulaWidget : ModuleWidget { FrankBussFormulaWidget(FrankBussFormulaModule *module) : ModuleWidget(module) { setPanel(SVG::load(assetPlugin(plugin, "res/MyModule.svg"))); addChild(Widget::create(Vec(RACK_GRID_WIDTH, 0))); addChild(Widget::create(Vec(box.size.x - 2 * RACK_GRID_WIDTH, 0))); addChild(Widget::create(Vec(RACK_GRID_WIDTH, RACK_GRID_HEIGHT - RACK_GRID_WIDTH))); addChild(Widget::create(Vec(box.size.x - 2 * RACK_GRID_WIDTH, RACK_GRID_HEIGHT - RACK_GRID_WIDTH))); textField = Widget::create(mm2px(Vec(3, 15))); textField->setModule(module); textField->box.size = mm2px(Vec(85, 50)); textField->multiline = true; addChild(textField); module->textField = textField; freqField = Widget::create(mm2px(Vec(16, 68))); freqField->setModule(module); freqField->box.size = mm2px(Vec(72, 10)); freqField->multiline = false; addChild(freqField); module->freqField = freqField; addParam(ParamWidget::create(Vec(30, 260), module, FrankBussFormulaModule::B_MINUS_1_PARAM, 0.0f, 1.0f, 0.0f)); addChild(ModuleLightWidget::create>(Vec(34.4f, 264.4f), module, FrankBussFormulaModule::B_MINUS_1_LIGHT)); addParam(ParamWidget::create(Vec(75, 260), module, FrankBussFormulaModule::B_0_PARAM, 0.0f, 1.0f, 0.0f)); addChild(ModuleLightWidget::create>(Vec(79.4f, 264.4f), module, FrankBussFormulaModule::B_0_LIGHT)); addParam(ParamWidget::create(Vec(120, 260), module, FrankBussFormulaModule::B_1_PARAM, 0.0f, 1.0f, 0.0f)); addChild(ModuleLightWidget::create>(Vec(124.4f, 264.4f), module, FrankBussFormulaModule::B_1_LIGHT)); addParam(ParamWidget::create(Vec(170, 240), module, FrankBussFormulaModule::KNOB_PARAM, -1.0f, 1.0f, 0.0f)); addChild(ModuleLightWidget::create>(Vec(240, 240), module, FrankBussFormulaModule::BLINK_LIGHT)); addInput(Port::create(Vec(20, 310), Port::INPUT, module, FrankBussFormulaModule::W_INPUT)); addInput(Port::create(Vec(60, 310), Port::INPUT, module, FrankBussFormulaModule::X_INPUT)); addInput(Port::create(Vec(100, 310), Port::INPUT, module, FrankBussFormulaModule::Y_INPUT)); addInput(Port::create(Vec(140, 310), Port::INPUT, module, FrankBussFormulaModule::Z_INPUT)); addParam(ParamWidget::create(Vec(190, 314), module, FrankBussFormulaModule::CLAMP_PARAM, 0.0f, 1.0f, 0.0f)); addChild(ModuleLightWidget::create>(Vec(194.4f, 318.4f), module, FrankBussFormulaModule::CLAMP_LIGHT)); addOutput(Port::create(Vec(220, 310), Port::OUTPUT, module, FrankBussFormulaModule::FORMULA_OUTPUT)); } // for backward compatibility, now it is all saved in the module void fromJson(json_t *rootJ) override { ModuleWidget::fromJson(rootJ); // text json_t *textJ = json_object_get(rootJ, "text"); if (textJ) { textField->text = json_string_value(textJ); module->onCreate(); } } MyTextField* textField; MyTextField* freqField; }; } // namespace rack_plugin_FrankBussFormula using namespace rack_plugin_FrankBussFormula; RACK_PLUGIN_MODEL_INIT(FrankBussFormula, FrankBussFormula) { Model *modelFrankBussFormula = Model::create("Frank Buss", "FrankBussFormula", "Formula", UTILITY_TAG); return modelFrankBussFormula; }