diff --git a/res/Octave.svg b/res/Octave.svg new file mode 100644 index 0000000..ac3c344 --- /dev/null +++ b/res/Octave.svg @@ -0,0 +1,168 @@ + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Fundamental.cpp b/src/Fundamental.cpp index e096a6a..04caa69 100644 --- a/src/Fundamental.cpp +++ b/src/Fundamental.cpp @@ -25,4 +25,5 @@ void init(rack::Plugin *p) { p->addModel(modelSEQ3); p->addModel(modelSequentialSwitch1); p->addModel(modelSequentialSwitch2); + p->addModel(modelOctave); } diff --git a/src/Fundamental.hpp b/src/Fundamental.hpp index 31237be..54e1ad8 100644 --- a/src/Fundamental.hpp +++ b/src/Fundamental.hpp @@ -23,5 +23,4 @@ extern Model *modelScope; extern Model *modelSEQ3; extern Model *modelSequentialSwitch1; extern Model *modelSequentialSwitch2; - - +extern Model *modelOctave; diff --git a/src/Octave.cpp b/src/Octave.cpp new file mode 100644 index 0000000..b34b9cf --- /dev/null +++ b/src/Octave.cpp @@ -0,0 +1,165 @@ +#include "Fundamental.hpp" + +struct Octave : Module { + enum ParamIds { + NUM_PARAMS + }; + enum InputIds { + CV_INPUT, + NUM_INPUTS + }; + enum OutputIds { + CV_OUTPUT, + NUM_OUTPUTS + }; + enum LightIds { + NUM_LIGHTS + }; + + int octave = 0; + + Octave() : Module(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS) {} + + void onReset() override { + octave = 0; + } + + void onRandomize() override { + octave = (randomu32() % 9) - 4; + } + + json_t *toJson() override { + json_t *rootJ = json_object(); + json_object_set_new(rootJ, "octave", json_integer(octave)); + return rootJ; + } + + void fromJson(json_t *rootJ) override { + json_t *octaveJ = json_object_get(rootJ, "octave"); + if (octaveJ) + octave = json_integer_value(octaveJ); + } + + void step() override { + float cv = inputs[CV_INPUT].value; + cv += octave; + outputs[CV_OUTPUT].value = cv; + } +}; + + +struct OctaveButton : OpaqueWidget { + Octave *module; + int octave; + + void draw(NVGcontext *vg) override { + Vec c = box.size.div(2); + + if (module->octave == octave) { + // Enabled + nvgBeginPath(vg); + nvgCircle(vg, c.x, c.y, mm2px(4.0/2)); + if (octave < 0) + nvgFillColor(vg, COLOR_RED); + else if (octave > 0) + nvgFillColor(vg, COLOR_GREEN); + else + nvgFillColor(vg, colorAlpha(COLOR_WHITE, 0.33)); + nvgFill(vg); + } + else { + // Disabled + nvgBeginPath(vg); + nvgCircle(vg, c.x, c.y, mm2px(4.0/2)); + nvgFillColor(vg, colorAlpha(COLOR_WHITE, 0.33)); + nvgFill(vg); + + nvgBeginPath(vg); + nvgCircle(vg, c.x, c.y, mm2px(3.0/2)); + nvgFillColor(vg, COLOR_BLACK); + nvgFill(vg); + + if (octave == 0) { + nvgBeginPath(vg); + nvgCircle(vg, c.x, c.y, mm2px(1.0/2)); + nvgFillColor(vg, colorAlpha(COLOR_WHITE, 0.33)); + nvgFill(vg); + } + } + } + + void onMouseDown(EventMouseDown &e) override { + if (e.button == 1) { + module->octave = 0; + e.target = this; + e.consumed = true; + return; + } + OpaqueWidget::onMouseDown(e); + } + + void onDragEnter(EventDragEnter &e) override { + OctaveButton *w = dynamic_cast(e.origin); + if (w) { + module->octave = octave; + } + } +}; + + +struct OctaveDisplay : OpaqueWidget { + OctaveDisplay() { + box.size = mm2px(Vec(15.240, 72.000)); + } + + void setModule(Octave *module) { + clearChildren(); + + const int octaves = 9; + const float margin = mm2px(2.0); + float height = box.size.y - 2*margin; + for (int i = 0; i < octaves; i++) { + OctaveButton *octaveButton = new OctaveButton(); + octaveButton->box.pos = Vec(0, height / octaves * i + margin); + octaveButton->box.size = Vec(box.size.x, height / octaves); + octaveButton->module = module; + octaveButton->octave = 4 - i; + addChild(octaveButton); + } + } + + void draw(NVGcontext *vg) override { + // Background + nvgBeginPath(vg); + nvgRect(vg, 0, 0, box.size.x, box.size.y); + nvgFillColor(vg, nvgRGB(0, 0, 0)); + nvgFill(vg); + + Widget::draw(vg); + } +}; + + +struct OctaveWidget : ModuleWidget { + OctaveWidget(Octave *module) : ModuleWidget(module) { + setPanel(SVG::load(assetPlugin(plugin, "res/Octave.svg"))); + + addChild(createWidget(Vec(RACK_GRID_WIDTH, 0))); + addChild(createWidget(Vec(box.size.x - 2 * RACK_GRID_WIDTH, 0))); + addChild(createWidget(Vec(RACK_GRID_WIDTH, RACK_GRID_HEIGHT - RACK_GRID_WIDTH))); + addChild(createWidget(Vec(box.size.x - 2 * RACK_GRID_WIDTH, RACK_GRID_HEIGHT - RACK_GRID_WIDTH))); + + addInput(createInputCentered(mm2px(Vec(7.62, 97.253)), module, Octave::CV_INPUT)); + + addOutput(createOutputCentered(mm2px(Vec(7.62, 112.253)), module, Octave::CV_OUTPUT)); + + OctaveDisplay *octaveDisplay = new OctaveDisplay(); + octaveDisplay->box.pos = mm2px(Vec(0.0, 14.584)); + octaveDisplay->setModule(module); + addChild(octaveDisplay); + } +}; + + +Model *modelOctave = createModel("Fundamental", "Octave", "Octave", UTILITY_TAG); +