You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

339 lines
9.7KB

  1. #include "Template.hpp"
  2. #include "dsp/digital.hpp"
  3. #include "formula/Formula.h"
  4. namespace rack_plugin_FrankBussFormula {
  5. struct FrankBussFormulaModule;
  6. class MyTextField : public LedDisplayTextField {
  7. public:
  8. MyTextField() : LedDisplayTextField() {}
  9. void setModule(FrankBussFormulaModule* _module) {
  10. module = _module;
  11. }
  12. virtual void onTextChange() override;
  13. private:
  14. FrankBussFormulaModule* module;
  15. };
  16. struct FrankBussFormulaModule : Module {
  17. enum ParamIds {
  18. X_PARAM,
  19. Y_PARAM,
  20. Z_PARAM,
  21. KNOB_PARAM,
  22. CLAMP_PARAM,
  23. B_MINUS_1_PARAM,
  24. B_0_PARAM,
  25. B_1_PARAM,
  26. NUM_PARAMS
  27. };
  28. enum InputIds {
  29. X_INPUT,
  30. Y_INPUT,
  31. Z_INPUT,
  32. W_INPUT,
  33. NUM_INPUTS
  34. };
  35. enum OutputIds {
  36. FORMULA_OUTPUT,
  37. NUM_OUTPUTS
  38. };
  39. enum LightIds {
  40. BLINK_LIGHT,
  41. CLAMP_LIGHT,
  42. B_MINUS_1_LIGHT,
  43. B_0_LIGHT,
  44. B_1_LIGHT,
  45. NUM_LIGHTS
  46. };
  47. MyTextField* textField;
  48. MyTextField* freqField;
  49. float blinkPhase = 0.0f;
  50. Formula formula;
  51. Formula freqFormula;
  52. bool compiled = false;
  53. bool doclamp = true;
  54. bool freqFormulaEnabled = false;
  55. float radiobutton = 0.0f;
  56. float phase = 0.0f;
  57. SchmittTrigger clampTrigger;
  58. SchmittTrigger bMinus1Trigger;
  59. SchmittTrigger b0Trigger;
  60. SchmittTrigger b1Trigger;
  61. float* formulaP = NULL;
  62. float* formulaK = NULL;
  63. float* formulaB = NULL;
  64. float* formulaW = NULL;
  65. float* formulaX = NULL;
  66. float* formulaY = NULL;
  67. float* formulaZ = NULL;
  68. float* freqFormulaP = NULL;
  69. float* freqFormulaK = NULL;
  70. float* freqFormulaB = NULL;
  71. float* freqFormulaW = NULL;
  72. float* freqFormulaX = NULL;
  73. float* freqFormulaY = NULL;
  74. float* freqFormulaZ = NULL;
  75. FrankBussFormulaModule() : Module(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS) {
  76. }
  77. void step() override {
  78. if (clampTrigger.process(params[CLAMP_PARAM].value)) {
  79. doclamp = !doclamp;
  80. }
  81. if (bMinus1Trigger.process(params[B_MINUS_1_PARAM].value)) {
  82. radiobutton = -1.0f;
  83. }
  84. if (b0Trigger.process(params[B_0_PARAM].value)) {
  85. radiobutton = 0.0f;
  86. }
  87. if (b1Trigger.process(params[B_1_PARAM].value)) {
  88. radiobutton = 1.0f;
  89. }
  90. float deltaTime = engineGetSampleTime();
  91. // evaluate frequency and output formula
  92. float val = 0;
  93. if (compiled) {
  94. try {
  95. // get inputs
  96. float w = inputs[W_INPUT].value;
  97. float x = inputs[X_INPUT].value;
  98. float y = inputs[Y_INPUT].value;
  99. float z = inputs[Z_INPUT].value;
  100. // knob
  101. float k = params[KNOB_PARAM].value;
  102. // set all variables
  103. *formulaP = phase;
  104. *formulaK = k;
  105. *formulaB = radiobutton;
  106. *formulaW = w;
  107. *formulaX = x;
  108. *formulaY = y;
  109. *formulaZ = z;
  110. if (freqFormulaEnabled) {
  111. *freqFormulaP = phase;
  112. *freqFormulaK = k;
  113. *freqFormulaB = radiobutton;
  114. *freqFormulaW = w;
  115. *freqFormulaX = x;
  116. *freqFormulaY = y;
  117. *freqFormulaZ = z;
  118. float freq = evalFormula(freqFormula);
  119. phase += freq * engineGetSampleTime();
  120. if (phase > 1.0f) phase -= 1.0f;
  121. }
  122. val = evalFormula(formula);
  123. if (doclamp) val = clamp(val, -5.0f, 5.0f);
  124. } catch (MathError&) {
  125. // ignore math errors, e.g. division by zero
  126. } catch (exception&) {
  127. // for all other exceptions, set compiled to false, e.g. VariableNotFound
  128. compiled = false;
  129. }
  130. }
  131. // set output
  132. outputs[FORMULA_OUTPUT].value = val;
  133. // Blink light at 1Hz
  134. blinkPhase += deltaTime;
  135. if (blinkPhase >= 1.0f)
  136. blinkPhase -= 1.0f;
  137. if (compiled) {
  138. lights[BLINK_LIGHT].value = 1.0f;
  139. } else {
  140. lights[BLINK_LIGHT].value = (blinkPhase < 0.5f) ? 1.0f : 0.0f;
  141. }
  142. lights[CLAMP_LIGHT].value = (doclamp);
  143. lights[B_MINUS_1_LIGHT].value = (radiobutton == -1.0f);
  144. lights[B_0_LIGHT].value = (radiobutton == -0.0f);
  145. lights[B_1_LIGHT].value = (radiobutton == 1.0f);
  146. }
  147. void parseFormula(Formula& formula, string expr) {
  148. formula.setVariable("pi", float(M_PI));
  149. formula.setVariable("e", float(M_E));
  150. formula.setVariable("p", 0);
  151. formula.setVariable("k", 0);
  152. formula.setVariable("b", 0);
  153. formula.setVariable("w", 0);
  154. formula.setVariable("x", 0);
  155. formula.setVariable("y", 0);
  156. formula.setVariable("z", 0);
  157. formula.setExpression(expr);
  158. }
  159. float evalFormula(Formula& formula) {
  160. // eval
  161. float val = formula.eval();
  162. if (!isfinite(val) || isnan(val)) val = 0.0f;
  163. return val;
  164. }
  165. void onCreate () override
  166. {
  167. compiled = false;
  168. phase = 0;
  169. if (textField->text.size() > 0) {
  170. try {
  171. parseFormula(formula, textField->text);
  172. freqFormulaEnabled = false;
  173. if (freqField->text.size() > 0) {
  174. parseFormula(freqFormula, freqField->text);
  175. freqFormulaEnabled = true;
  176. }
  177. formulaP = formula.getVariableAddress("p");
  178. formulaK = formula.getVariableAddress("k");
  179. formulaB = formula.getVariableAddress("b");
  180. formulaW = formula.getVariableAddress("w");
  181. formulaX = formula.getVariableAddress("x");
  182. formulaY = formula.getVariableAddress("y");
  183. formulaZ = formula.getVariableAddress("z");
  184. if (freqFormulaEnabled) {
  185. freqFormulaP = freqFormula.getVariableAddress("p");
  186. freqFormulaK = freqFormula.getVariableAddress("k");
  187. freqFormulaB = freqFormula.getVariableAddress("b");
  188. freqFormulaW = freqFormula.getVariableAddress("w");
  189. freqFormulaX = freqFormula.getVariableAddress("x");
  190. freqFormulaY = freqFormula.getVariableAddress("y");
  191. freqFormulaZ = freqFormula.getVariableAddress("z");
  192. }
  193. compiled = true;
  194. } catch (exception& e) {
  195. printf("formula exception: %s\n", e.what());
  196. }
  197. }
  198. }
  199. void onReset () override
  200. {
  201. onCreate();
  202. }
  203. json_t *toJson() override {
  204. json_t *rootJ = json_object();
  205. json_object_set_new(rootJ, "text", json_string(textField->text.c_str()));
  206. json_object_set_new(rootJ, "freq", json_string(freqField->text.c_str()));
  207. json_object_set_new(rootJ, "clamp", json_boolean(doclamp));
  208. json_object_set_new(rootJ, "button", json_real(radiobutton));
  209. return rootJ;
  210. }
  211. void fromJson(json_t *rootJ) override {
  212. json_t *textJ = json_object_get(rootJ, "text");
  213. if (textJ) textField->text = json_string_value(textJ);
  214. json_t *freqJ = json_object_get(rootJ, "freq");
  215. if (freqJ) freqField->text = json_string_value(freqJ);
  216. json_t *clampJ = json_object_get(rootJ, "clamp");
  217. if (clampJ) doclamp = json_is_true(clampJ);
  218. json_t *buttonJ = json_object_get(rootJ, "button");
  219. if (buttonJ) radiobutton = (float)json_real_value(buttonJ);
  220. onCreate();
  221. }
  222. };
  223. void MyTextField::onTextChange() {
  224. module->onCreate();
  225. }
  226. struct FrankBussFormulaWidget : ModuleWidget {
  227. FrankBussFormulaWidget(FrankBussFormulaModule *module) : ModuleWidget(module) {
  228. setPanel(SVG::load(assetPlugin(plugin, "res/MyModule.svg")));
  229. addChild(Widget::create<ScrewSilver>(Vec(RACK_GRID_WIDTH, 0)));
  230. addChild(Widget::create<ScrewSilver>(Vec(box.size.x - 2 * RACK_GRID_WIDTH, 0)));
  231. addChild(Widget::create<ScrewSilver>(Vec(RACK_GRID_WIDTH, RACK_GRID_HEIGHT - RACK_GRID_WIDTH)));
  232. addChild(Widget::create<ScrewSilver>(Vec(box.size.x - 2 * RACK_GRID_WIDTH, RACK_GRID_HEIGHT - RACK_GRID_WIDTH)));
  233. textField = Widget::create<MyTextField>(mm2px(Vec(3, 15)));
  234. textField->setModule(module);
  235. textField->box.size = mm2px(Vec(85, 50));
  236. textField->multiline = true;
  237. addChild(textField);
  238. module->textField = textField;
  239. freqField = Widget::create<MyTextField>(mm2px(Vec(16, 68)));
  240. freqField->setModule(module);
  241. freqField->box.size = mm2px(Vec(72, 10));
  242. freqField->multiline = false;
  243. addChild(freqField);
  244. module->freqField = freqField;
  245. addParam(ParamWidget::create<LEDButton>(Vec(30, 260), module, FrankBussFormulaModule::B_MINUS_1_PARAM, 0.0f, 1.0f, 0.0f));
  246. addChild(ModuleLightWidget::create<MediumLight<GreenLight>>(Vec(34.4f, 264.4f), module, FrankBussFormulaModule::B_MINUS_1_LIGHT));
  247. addParam(ParamWidget::create<LEDButton>(Vec(75, 260), module, FrankBussFormulaModule::B_0_PARAM, 0.0f, 1.0f, 0.0f));
  248. addChild(ModuleLightWidget::create<MediumLight<GreenLight>>(Vec(79.4f, 264.4f), module, FrankBussFormulaModule::B_0_LIGHT));
  249. addParam(ParamWidget::create<LEDButton>(Vec(120, 260), module, FrankBussFormulaModule::B_1_PARAM, 0.0f, 1.0f, 0.0f));
  250. addChild(ModuleLightWidget::create<MediumLight<GreenLight>>(Vec(124.4f, 264.4f), module, FrankBussFormulaModule::B_1_LIGHT));
  251. addParam(ParamWidget::create<RoundLargeBlackKnob>(Vec(170, 240), module, FrankBussFormulaModule::KNOB_PARAM, -1.0f, 1.0f, 0.0f));
  252. addChild(ModuleLightWidget::create<MediumLight<RedLight>>(Vec(240, 240), module, FrankBussFormulaModule::BLINK_LIGHT));
  253. addInput(Port::create<PJ301MPort>(Vec(20, 310), Port::INPUT, module, FrankBussFormulaModule::W_INPUT));
  254. addInput(Port::create<PJ301MPort>(Vec(60, 310), Port::INPUT, module, FrankBussFormulaModule::X_INPUT));
  255. addInput(Port::create<PJ301MPort>(Vec(100, 310), Port::INPUT, module, FrankBussFormulaModule::Y_INPUT));
  256. addInput(Port::create<PJ301MPort>(Vec(140, 310), Port::INPUT, module, FrankBussFormulaModule::Z_INPUT));
  257. addParam(ParamWidget::create<LEDButton>(Vec(190, 314), module, FrankBussFormulaModule::CLAMP_PARAM, 0.0f, 1.0f, 0.0f));
  258. addChild(ModuleLightWidget::create<MediumLight<GreenLight>>(Vec(194.4f, 318.4f), module, FrankBussFormulaModule::CLAMP_LIGHT));
  259. addOutput(Port::create<PJ301MPort>(Vec(220, 310), Port::OUTPUT, module, FrankBussFormulaModule::FORMULA_OUTPUT));
  260. }
  261. // for backward compatibility, now it is all saved in the module
  262. void fromJson(json_t *rootJ) override {
  263. ModuleWidget::fromJson(rootJ);
  264. // text
  265. json_t *textJ = json_object_get(rootJ, "text");
  266. if (textJ)
  267. {
  268. textField->text = json_string_value(textJ);
  269. module->onCreate();
  270. }
  271. }
  272. MyTextField* textField;
  273. MyTextField* freqField;
  274. };
  275. } // namespace rack_plugin_FrankBussFormula
  276. using namespace rack_plugin_FrankBussFormula;
  277. RACK_PLUGIN_MODEL_INIT(FrankBussFormula, FrankBussFormula) {
  278. Model *modelFrankBussFormula = Model::create<FrankBussFormulaModule, FrankBussFormulaWidget>("Frank Buss", "FrankBussFormula", "Formula", UTILITY_TAG);
  279. return modelFrankBussFormula;
  280. }