├── .gitignore ├── res ├── Segment7Standard.ttf ├── LightupButtonDown.svg ├── LightupButton.svg ├── ModIsOn.svg ├── PurplePort.svg ├── BigMuteButtonMute.svg ├── PurpleTrimpot.svg ├── ShiftIsOn.svg ├── BigMuteButton.svg ├── AltIsOn.svg ├── AltIsOff.svg ├── ModIsOff.svg ├── CtrlIsOff.svg ├── CtrlIsOn.svg └── SuperIsOn.svg ├── screenshots ├── modules.png ├── color_panel.png ├── gate_length.png ├── idle_switch.png ├── shift_pedal.png ├── value_saver.png ├── hovered_value.png ├── inject_value.png ├── big_mute_button.png ├── specific_value.png ├── reference_voltages.png └── momentary_on_buttons.png ├── presets ├── SpecificValue │ └── 1_hz_lfo.vcvm ├── ColorPanel │ └── rgb_red.vcvm └── GateLength │ └── 150ms.vcvm ├── src ├── ParamFloatField.cpp ├── ParamFloatField.hpp ├── alikins.cpp ├── alikins.hpp ├── MsDisplayWidget.hpp ├── specificValueWidgets.hpp ├── Reference.cpp ├── cv_utils.hpp ├── MomentaryOnButtons.cpp ├── GateLength.cpp ├── BigMuteButton.cpp ├── enharmonic.hpp ├── ValueSaver.cpp ├── InjectValue.cpp └── ColorPanel.cpp ├── .gitattributes ├── Makefile ├── plugin.json ├── test ├── test_rgb1.vcv ├── value_saver_test.vcv └── test-reference.vcv └── res-src └── SmallPurpleTrimpot.svg /.gitignore: -------------------------------------------------------------------------------- 1 | build/ 2 | dist/ 3 | dist-archive/ 4 | res-src/ 5 | .DS_Store 6 | plugin.dylib 7 | -------------------------------------------------------------------------------- /res/Segment7Standard.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alikins/Alikins-rack-plugins/HEAD/res/Segment7Standard.ttf -------------------------------------------------------------------------------- /screenshots/modules.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alikins/Alikins-rack-plugins/HEAD/screenshots/modules.png -------------------------------------------------------------------------------- /screenshots/color_panel.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alikins/Alikins-rack-plugins/HEAD/screenshots/color_panel.png -------------------------------------------------------------------------------- /screenshots/gate_length.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alikins/Alikins-rack-plugins/HEAD/screenshots/gate_length.png -------------------------------------------------------------------------------- /screenshots/idle_switch.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alikins/Alikins-rack-plugins/HEAD/screenshots/idle_switch.png -------------------------------------------------------------------------------- /screenshots/shift_pedal.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alikins/Alikins-rack-plugins/HEAD/screenshots/shift_pedal.png -------------------------------------------------------------------------------- /screenshots/value_saver.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alikins/Alikins-rack-plugins/HEAD/screenshots/value_saver.png -------------------------------------------------------------------------------- /screenshots/hovered_value.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alikins/Alikins-rack-plugins/HEAD/screenshots/hovered_value.png -------------------------------------------------------------------------------- /screenshots/inject_value.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alikins/Alikins-rack-plugins/HEAD/screenshots/inject_value.png -------------------------------------------------------------------------------- /screenshots/big_mute_button.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alikins/Alikins-rack-plugins/HEAD/screenshots/big_mute_button.png -------------------------------------------------------------------------------- /screenshots/specific_value.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alikins/Alikins-rack-plugins/HEAD/screenshots/specific_value.png -------------------------------------------------------------------------------- /screenshots/reference_voltages.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alikins/Alikins-rack-plugins/HEAD/screenshots/reference_voltages.png -------------------------------------------------------------------------------- /screenshots/momentary_on_buttons.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alikins/Alikins-rack-plugins/HEAD/screenshots/momentary_on_buttons.png -------------------------------------------------------------------------------- /presets/SpecificValue/1_hz_lfo.vcvm: -------------------------------------------------------------------------------- 1 | { 2 | "plugin": "Alikins", 3 | "model": "SpecificValue", 4 | "version": "2.0.1", 5 | "params": [ 6 | { 7 | "value": -1.0, 8 | "id": 0 9 | }, 10 | { 11 | "value": 0.0, 12 | "id": 1 13 | } 14 | ] 15 | } -------------------------------------------------------------------------------- /presets/ColorPanel/rgb_red.vcvm: -------------------------------------------------------------------------------- 1 | { 2 | "plugin": "Alikins", 3 | "model": "ColorPanel", 4 | "version": "2.0.1", 5 | "params": [ 6 | { 7 | "value": 1.0, 8 | "id": 0 9 | }, 10 | { 11 | "value": 0.0, 12 | "id": 1 13 | }, 14 | { 15 | "value": 0.0, 16 | "id": 2 17 | } 18 | ], 19 | "data": { 20 | "inputRange": 0, 21 | "colorMode": 0 22 | } 23 | } -------------------------------------------------------------------------------- /presets/GateLength/150ms.vcvm: -------------------------------------------------------------------------------- 1 | { 2 | "plugin": "Alikins", 3 | "model": "GateLength", 4 | "version": "2.0.1", 5 | "params": [ 6 | { 7 | "value": 1.5, 8 | "id": 0 9 | }, 10 | { 11 | "value": 1.5, 12 | "id": 1 13 | }, 14 | { 15 | "value": 1.5, 16 | "id": 2 17 | }, 18 | { 19 | "value": 1.5, 20 | "id": 3 21 | }, 22 | { 23 | "value": 1.5, 24 | "id": 4 25 | } 26 | ] 27 | } 28 | -------------------------------------------------------------------------------- /src/ParamFloatField.cpp: -------------------------------------------------------------------------------- 1 | #include "rack.hpp" 2 | #include "ParamFloatField.hpp" 3 | 4 | ParamFloatField::ParamFloatField(Module *_module) 5 | { 6 | module = _module; 7 | } 8 | 9 | void ParamFloatField::setValue(float value) { 10 | this->hovered_value = value; 11 | // this->module->param_value = value; 12 | event::Change e; 13 | onChange(e); 14 | } 15 | 16 | void ParamFloatField::onChange(const event::Change &e) { 17 | std::string new_text = string::f("%#.4g", hovered_value); 18 | setText(new_text); 19 | } 20 | -------------------------------------------------------------------------------- /src/ParamFloatField.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "alikins.hpp" 4 | //using namespace rack; 5 | //#include "rack.hpp" 6 | // #include "ui.hpp" 7 | 8 | // TODO/FIXME: This is more or less adhoc TextField mixed with QuantityWidget 9 | // just inherit from both? 10 | struct ParamFloatField : TextField 11 | { 12 | Module *module; 13 | float hovered_value; 14 | 15 | ParamFloatField(Module *module); 16 | 17 | void setValue(float value); 18 | void onChange(const event::Change &e) override; 19 | 20 | }; 21 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | 2 | # To use gsvg tool ( https://github.com/noahbrenner/gsvg) 3 | # for less noisy git diffs 4 | # Add a 'svg' diff target to gitconfig like: 5 | # 6 | # [diff "svg"] 7 | # textconv = gsvg 8 | # 9 | # And set the diff attr for svg to 'svg' in 10 | # ~/.config/git/attributes (global), .gitattributes (local but checked in) 11 | # or .git/info/attributes (repo local but not checked in/shared) 12 | # 13 | # *.svg diff=svg 14 | 15 | # But easiest is to just default to treating .svg files as binary. 16 | # If you want to override when using 'git log -p', use 'git log -p --no-textconv' 17 | 18 | *.svg binary 19 | -------------------------------------------------------------------------------- /src/alikins.cpp: -------------------------------------------------------------------------------- 1 | #include "alikins.hpp" 2 | 3 | // The pluginInstance-wide instance of the Plugin class 4 | Plugin *pluginInstance; 5 | 6 | void init(rack::Plugin *p) { 7 | pluginInstance = p; 8 | 9 | p->addModel(modelBigMuteButton); 10 | p->addModel(modelGateLength); 11 | p->addModel(modelIdleSwitch); 12 | p->addModel(modelMomentaryOnButtons); 13 | p->addModel(modelReference); 14 | p->addModel(modelShiftPedal); 15 | p->addModel(modelValueSaver); 16 | p->addModel(modelSpecificValue); 17 | p->addModel(modelColorPanel); 18 | p->addModel(modelHoveredValue); 19 | p->addModel(modelInjectValue); 20 | } 21 | -------------------------------------------------------------------------------- /src/alikins.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include "rack.hpp" 6 | 7 | const int MOMENTARY_BUTTONS = 13; 8 | const int INPUT_SOURCES = 1; 9 | const int GATE_LENGTH_INPUTS = 5; 10 | 11 | enum VoltageRange { 12 | MINUS_PLUS_TEN, 13 | ZERO_TEN, 14 | MINUS_PLUS_FIVE, 15 | }; 16 | 17 | const float voltage_min[3] = {-10.0f, 0.0f, -5.0f}; 18 | const float voltage_max[3] = {10.0f, 10.0f, 5.0f}; 19 | 20 | using namespace rack; 21 | 22 | 23 | extern Plugin *pluginInstance; 24 | 25 | //////////////////// 26 | // module widgets 27 | //////////////////// 28 | 29 | extern Model *modelBigMuteButton; 30 | extern Model *modelGateLength; 31 | extern Model *modelIdleSwitch; 32 | extern Model *modelMomentaryOnButtons; 33 | extern Model *modelReference; 34 | extern Model *modelShiftPedal; 35 | extern Model *modelValueSaver; 36 | extern Model *modelColorPanel; 37 | extern Model *modelSpecificValue; 38 | extern Model *modelHoveredValue; 39 | extern Model *modelInjectValue; 40 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | RACK_DIR ?= ../.. 2 | 3 | # FLAGS will be passed to both the C and C++ compiler 4 | # FLAGS += 5 | # FLAGS += -w -g 6 | CFLAGS += 7 | # CXXFLAGS += -Wdeprecated-declarations -Wall -Werror 8 | # FLAGS += -Wdeprecated-declarations -Wall -Werror 9 | 10 | # Careful about linking to libraries, since you can't assume much about the user's environment and library search path. 11 | # Static libraries are fine. 12 | LDFLAGS += 13 | 14 | # Add .cpp and .c files to the build 15 | # SOURCES = $(wildcard src/*.cpp) 16 | SOURCES = src/alikins.cpp \ 17 | src/IdleSwitch.cpp \ 18 | src/BigMuteButton.cpp \ 19 | src/GateLength.cpp \ 20 | src/MomentaryOnButtons.cpp \ 21 | src/Reference.cpp \ 22 | src/ValueSaver.cpp \ 23 | src/ColorPanel.cpp \ 24 | src/ShiftPedal.cpp \ 25 | src/SpecificValue.cpp \ 26 | src/HoveredValue.cpp \ 27 | src/ParamFloatField.cpp \ 28 | src/InjectValue.cpp \ 29 | 30 | 31 | # Must include the VCV plugin Makefile framework 32 | include $(RACK_DIR)/plugin.mk 33 | 34 | # http://cppcheck.sourceforge.net/ 35 | cppcheck: 36 | cppcheck -i$(RACK_DIR)/dep/include -i$(RACK_DIR)/include --enable=style -DVERSION=0.5.1 --quiet src/ 37 | 38 | # https://github.com/google/styleguide 39 | cpplint: 40 | cpplint --headers=hpp --filter=-whitespace/line_length,-legal/copyright,-whitespace/blank_line src/*.cpp src/*.hpp 41 | 42 | 43 | DISTRIBUTABLES += $(wildcard LICENSE*) res 44 | 45 | .PHONY: cppcheck cpplint 46 | -------------------------------------------------------------------------------- /src/MsDisplayWidget.hpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | // From AS DelayPlus.cpp https://github.com/AScustomWorks/AS 5 | struct MsDisplayWidget : TransparentWidget { 6 | 7 | float *value = NULL; 8 | 9 | 10 | MsDisplayWidget() { 11 | } 12 | 13 | void draw(NVGcontext *vg) override { 14 | std::shared_ptr font = APP->window->loadFont(asset::plugin(pluginInstance, "res/Segment7Standard.ttf")); 15 | // Background 16 | // these go to... 17 | NVGcolor backgroundColor = nvgRGB(0x11, 0x11, 0x11); 18 | 19 | NVGcolor borderColor = nvgRGB(0xff, 0xff, 0xff); 20 | 21 | nvgBeginPath(vg); 22 | 23 | nvgRoundedRect(vg, 0.0, 0.0, box.size.x, box.size.y, 3.0); 24 | nvgFillColor(vg, backgroundColor); 25 | nvgFill(vg); 26 | 27 | nvgStrokeWidth(vg, 1.0f); 28 | nvgStrokeColor(vg, borderColor); 29 | 30 | nvgStroke(vg); 31 | 32 | // text 33 | if (font && value) { 34 | nvgFontSize(vg, 14); 35 | nvgFontFaceId(vg, font->handle); 36 | nvgTextLetterSpacing(vg, 2.5); 37 | 38 | std::stringstream to_display; 39 | // DEBUG("about to format *value"); 40 | // to_display << std::setiosflags(std::ios::fixed) << std::right << std::setw(5) << std::setprecision(4) << *value; 41 | to_display << std::setiosflags(std::ios::fixed) << std::left << std::setprecision(4) << *value; 42 | 43 | Vec textPos = Vec(1.0f, 19.0f); 44 | 45 | NVGcolor textColor = nvgRGB(0x65, 0xf6, 0x78); 46 | nvgFillColor(vg, textColor); 47 | nvgText(vg, textPos.x, textPos.y, to_display.str().c_str(), NULL); 48 | } 49 | } 50 | }; 51 | 52 | -------------------------------------------------------------------------------- /src/specificValueWidgets.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | // #include "rack.hpp" 4 | #include "alikins.hpp" 5 | // using namespace rack; 6 | 7 | struct PurpleTrimpot : SvgKnob { 8 | Module *module; 9 | bool initialized = false; 10 | PurpleTrimpot(); 11 | void step() override; 12 | // void reset() override; 13 | // void randomize() override; 14 | }; 15 | 16 | PurpleTrimpot::PurpleTrimpot() : SvgKnob() { 17 | setSvg(APP->window->loadSvg(asset::plugin(pluginInstance, "res/PurpleTrimpot.svg"))); 18 | shadow->blurRadius = 0.0; 19 | shadow->opacity = 0.10; 20 | shadow->box.pos = Vec(0.0, box.size.y * 0.05); 21 | } 22 | 23 | 24 | // FIXME: if we are getting moving inputs and we are hovering 25 | // over the trimpot, we kind of jitter arround. 26 | // maybe run this via an onChange()? 27 | void PurpleTrimpot::step() { 28 | // debug("paramId=%d this->initialized: %d initialized: %d this->value: %f value: %f param.value: %f", 29 | // paramId, this->initialized, initialized, this->value, value, module->params[paramId].getValue()); 30 | /* 31 | if (this->value != module->params[paramId].getValue()) { 32 | if (this != gHoveredWidget && this->initialized) { 33 | // this->value = module->params[paramId].getValue(); 34 | setValue(module->params[paramId].getValue()); 35 | } else { 36 | module->params[paramId].getValue() = this->value; 37 | this->initialized |= true; 38 | } 39 | event::Change e; 40 | onChange(e); 41 | } 42 | */ 43 | SvgKnob::step(); 44 | } 45 | 46 | /* 47 | void PurpleTrimpot::reset() { 48 | this->initialized = false; 49 | Trimpot::reset(); 50 | } 51 | */ 52 | 53 | /* 54 | void PurpleTrimpot::randomize() { 55 | reset(); 56 | float value = math::rescale(random::uniform(), 0.f, 1.f, paramQuantity->getMinValue(), paramQuantity->getMaxValue()); 57 | paramQuantity->setValue(value); 58 | // setValue(rescale(random::uniform(), 0.0f, 1.0f, minValue, maxValue)); 59 | } 60 | */ 61 | -------------------------------------------------------------------------------- /res/LightupButtonDown.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 19 | 21 | 40 | 42 | 43 | 45 | image/svg+xml 46 | 48 | 49 | 50 | 51 | 52 | 57 | 66 | 75 | 76 | 77 | -------------------------------------------------------------------------------- /res/LightupButton.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 20 | 22 | 25 | 29 | 30 | 31 | 50 | 52 | 53 | 55 | image/svg+xml 56 | 58 | 59 | 60 | 61 | 62 | 67 | 76 | 85 | 86 | 87 | -------------------------------------------------------------------------------- /plugin.json: -------------------------------------------------------------------------------- 1 | { 2 | "slug": "Alikins", 3 | "name": "Alikins", 4 | "version": "2.0.1", 5 | "license": "GPL-3.0-or-later", 6 | "author": "Adrian Likins", 7 | "authorEmail": "adrian@likins.com", 8 | "authorUrl": "https://github.com/alikins/", 9 | "pluginUrl": "https://github.com/alikins/Alikins-rack-plugins", 10 | "manualUrl": "https://github.com/alikins/Alikins-rack-plugins/blob/master/README.md", 11 | "sourceUrl": "https://github.com/alikins/Alikins-rack-plugins", 12 | "donateUrl": "", 13 | "modules": [ 14 | { 15 | "slug": "GateLength", 16 | "name": "Gate Length", 17 | "description": "Create a gate with CV controlled length", 18 | "tags": [ 19 | "Utility" 20 | ] 21 | }, 22 | { 23 | "slug": "BigMuteButton", 24 | "name": "Big Mute Button", 25 | "description": "A big mute button", 26 | "tags": [ 27 | "Utility" 28 | ] 29 | }, 30 | { 31 | "slug": "Reference", 32 | "name": "Reference Voltages", 33 | "description": "Fixed output voltages for -10V,-5V, -1V, 0V, 1V, 5V, 10V", 34 | "tags": [ 35 | "Utility" 36 | ] 37 | }, 38 | { 39 | "slug": "ColorPanel", 40 | "name": "Color Panel", 41 | "description": "Control the color of the panel with CV", 42 | "tags": [ 43 | "Visual" 44 | ] 45 | }, 46 | { 47 | "slug": "MomentaryOnButtons", 48 | "name": "Momentary On Buttons", 49 | "description": "Set of momentarily on buttons", 50 | "tags": [ 51 | "Switch" 52 | ] 53 | }, 54 | { 55 | "slug": "ShiftPedal", 56 | "name": "Shift Pedal", 57 | "description": "Generate gates on mod key presses", 58 | "tags": [ 59 | "Utility" 60 | ] 61 | }, 62 | { 63 | "slug": "SpecificValue", 64 | "name": "Specific Value", 65 | "description": "Set or view voltages with text widget", 66 | "tags": [ 67 | "Utility" 68 | ] 69 | }, 70 | { 71 | "slug": "HoveredValue", 72 | "name": "Hovered Value", 73 | "description": "Display and output the value of the currently hovered widget", 74 | "tags": [ 75 | "Utility", 76 | "Controller" 77 | ] 78 | }, 79 | { 80 | "slug": "InjectValue", 81 | "name": "Inject Value", 82 | "description": "Inject value into param under cursor", 83 | "tags": [ 84 | "Utility", 85 | "Controller" 86 | ] 87 | }, 88 | { 89 | "slug": "ValueSaver", 90 | "name": "Value Saver", 91 | "description": "Save last used value", 92 | "tags": [ 93 | "Utility" 94 | ] 95 | }, 96 | { 97 | "slug": "IdleSwitch", 98 | "name": "Idle Switch", 99 | "description": "If a signal goes idle, flip a switch", 100 | "tags": [ 101 | "Utility", 102 | "Switch" 103 | ] 104 | } 105 | ] 106 | } 107 | -------------------------------------------------------------------------------- /src/Reference.cpp: -------------------------------------------------------------------------------- 1 | #include "alikins.hpp" 2 | 3 | 4 | struct Reference : Module { 5 | enum ParamIds { 6 | NUM_PARAMS 7 | }; 8 | enum InputIds { 9 | NUM_INPUTS 10 | }; 11 | enum OutputIds { 12 | MINUS_TEN_OUTPUT, 13 | MINUS_FIVE_OUTPUT, 14 | MINUS_ONE_OUTPUT, 15 | ZERO_OUTPUT, 16 | PLUS_ONE_OUTPUT, 17 | PLUS_FIVE_OUTPUT, 18 | PLUS_TEN_OUTPUT, 19 | NUM_OUTPUTS 20 | }; 21 | enum LightIds { 22 | NUM_LIGHTS 23 | }; 24 | 25 | Reference() { 26 | config(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS); 27 | } 28 | 29 | void process(const ProcessArgs &args) override; 30 | 31 | void onReset() override { 32 | } 33 | 34 | }; 35 | 36 | void Reference::process(const ProcessArgs &args) { 37 | outputs[MINUS_TEN_OUTPUT].setVoltage(-10.0f); 38 | outputs[MINUS_FIVE_OUTPUT].setVoltage(-5.0f); 39 | outputs[MINUS_ONE_OUTPUT].setVoltage(-1.0f); 40 | 41 | outputs[ZERO_OUTPUT].setVoltage(0.0f); 42 | 43 | outputs[PLUS_ONE_OUTPUT].setVoltage(1.0f); 44 | outputs[PLUS_FIVE_OUTPUT].setVoltage(5.0f); 45 | outputs[PLUS_TEN_OUTPUT].setVoltage(10.0f); 46 | 47 | } 48 | 49 | struct ReferenceWidget : ModuleWidget { 50 | ReferenceWidget(Reference *module) { 51 | setModule(module); 52 | // box.size = Vec(4 * RACK_GRID_WIDTH, RACK_GRID_HEIGHT); 53 | setPanel(APP->window->loadSvg(asset::plugin(pluginInstance, "res/Reference.svg"))); 54 | 55 | float y_pos = 18.0f; 56 | 57 | float x_pos = 2.0f; 58 | float y_offset = 50.0f; 59 | 60 | addOutput(createOutput(Vec(x_pos, y_pos), 61 | module, 62 | Reference::PLUS_TEN_OUTPUT)); 63 | 64 | y_pos += y_offset; 65 | addOutput(createOutput(Vec(x_pos, y_pos), 66 | module, 67 | Reference::PLUS_FIVE_OUTPUT)); 68 | 69 | y_pos += y_offset; 70 | addOutput(createOutput(Vec(x_pos, y_pos), 71 | module, 72 | Reference::PLUS_ONE_OUTPUT)); 73 | 74 | y_pos += y_offset; 75 | addOutput(createOutput(Vec(x_pos, y_pos), 76 | module, 77 | Reference::ZERO_OUTPUT)); 78 | 79 | y_pos += y_offset; 80 | addOutput(createOutput(Vec(x_pos, y_pos), 81 | module, 82 | Reference::MINUS_ONE_OUTPUT)); 83 | 84 | y_pos += y_offset; 85 | addOutput(createOutput(Vec(x_pos, y_pos), 86 | module, 87 | Reference::MINUS_FIVE_OUTPUT)); 88 | 89 | y_pos += y_offset; 90 | addOutput(createOutput(Vec(x_pos, y_pos), 91 | module, 92 | Reference::MINUS_TEN_OUTPUT)); 93 | 94 | 95 | addChild(createWidget(Vec(0.0f, 0.0f))); 96 | addChild(createWidget(Vec(box.size.x - 15.0f, 0.0f))); 97 | addChild(createWidget(Vec(0.0f, 365.0f))); 98 | addChild(createWidget(Vec(box.size.x - 15.0f, 365.0f))); 99 | } 100 | }; 101 | 102 | 103 | Model *modelReference = createModel("Reference"); 104 | -------------------------------------------------------------------------------- /src/cv_utils.hpp: -------------------------------------------------------------------------------- 1 | #include "rack.hpp" 2 | using namespace rack; 3 | 4 | // TODO: mv to header 5 | // float A440_VOLTAGE = 4.75f; 6 | // https://vcvrack.com/manual/VoltageStandards.html#pitch-and-frequencies 7 | // 0v = C4 = 261.626f 8 | float VCO_BASELINE_VOLTAGE = 0.0f; 9 | float VCO_BASELINE_FREQ = 261.626f; 10 | float VCO_BASELINE_NOTE_OCTAVE_OFFSET = 4.0f; 11 | 12 | //float VCO_BASELINE_VOLTAGE = 4.75f; 13 | //float VCO_BASELINE_FREQ = 440.0f; 14 | 15 | 16 | int A440_MIDI_NUMBER = 69; 17 | 18 | // bogaudio lfo, defaults to 0v == 16.35f 19 | // float LFO_BASELINE_FREQ = 16.35f; 20 | 21 | // https://vcvrack.com/manual/VoltageStandards.html#pitch-and-frequencies 22 | float LFO_BASELINE_VOLTAGE = 0.0f; 23 | float LFO_BASELINE_FREQ = 2.0f; 24 | 25 | 26 | // FIXME: can/should be inline 27 | // FIXME: likely should be a NoteInfo type/struct/object 28 | // These are assuming 29 | // 440.0 hz == A4 == 0.75v (A440) 30 | // 261.626f == C4 == 0v 31 | float freq_to_cv(float freq) { 32 | float volts = log2f(freq / VCO_BASELINE_FREQ * powf(2.0f, VCO_BASELINE_VOLTAGE)); 33 | // debug("freq_to_vc freq=%f -> vc volts=%f (vco_baseline_freq=%f)", 34 | // freq, volts, VCO_BASELINE_VOLTAGE); 35 | return volts; 36 | } 37 | 38 | float lfo_freq_to_cv(float lfo_freq) { 39 | float volts = log2f(lfo_freq / LFO_BASELINE_FREQ * powf(2.0f, LFO_BASELINE_VOLTAGE)); 40 | // debug("lfo_freq_to_cv: lfo_freq=%f volts=%f LFO_BASELINE_VOLTAGE=%f", 41 | // lfo_freq, volts, LFO_BASELINE_VOLTAGE); 42 | return volts; 43 | } 44 | 45 | float cv_to_freq(float volts) { 46 | float freq = VCO_BASELINE_FREQ / powf(2.0f, VCO_BASELINE_VOLTAGE) * powf(2.0f, volts); 47 | // debug("cv_to_freq: cv volts=%f -> freq=%f (vco_baseline_freq=%f)", 48 | // volts, freq, VCO_BASELINE_FREQ); 49 | return freq; 50 | } 51 | 52 | float lfo_cv_to_freq(float volts) { 53 | // TODO: figure out what a common LFO baseline is 54 | float freq = LFO_BASELINE_FREQ / powf(2.0f, LFO_BASELINE_VOLTAGE) * powf(2.0f, volts); 55 | // debug("lfo_cv_to_freq freq=%f volts=%f ", freq, volts); 56 | return freq; 57 | } 58 | 59 | // can return negative 60 | double volts_of_nearest_note(float volts) { 61 | double res = round( (volts * 12.0) ) / 12.0; 62 | return res; 63 | } 64 | 65 | int volts_to_note(float volts) { 66 | int res = abs(static_cast( roundf( ( volts * 12.0f) ) ) ) % 12; 67 | // FIXME: ugly, sure there is a more elegant way to do this 68 | if (volts < 0.0f && res > 0) { 69 | res = 12 - res; 70 | } 71 | 72 | // DEBUG("volts_to_note1: volts=%f res=%d", volts, res); 73 | res = clampSafe(res, 0, 11); 74 | 75 | // DEBUG("volts_to_note2: volts=%f res=%d", volts, res); 76 | return res; 77 | } 78 | 79 | int volts_to_octave(float volts) { 80 | if (!std::isfinite(volts)) { 81 | return VCO_BASELINE_NOTE_OCTAVE_OFFSET; 82 | } 83 | 84 | int octave = floor(volts) + VCO_BASELINE_NOTE_OCTAVE_OFFSET; 85 | //debug("volts_to_octaves: volts=%f -> octave=%d (offset_from_baseline=%f, v+ofb=%f)", 86 | // volts, octave, offset_from_baseline, volts+offset_from_baseline); 87 | return octave; 88 | } 89 | 90 | float volts_to_note_cents(float volts) { 91 | float nearest_note = volts_of_nearest_note(volts); 92 | double cent_volt = 1.0f / 12.0f / 100.0f; 93 | 94 | float offset_cents = (volts - nearest_note)/cent_volt; 95 | // debug("volts: %f volts_of_nearest: %f volts-volts_nearest: %f offset_cents %f", 96 | // volts, nearest_note, volts-nearest_note, offset_cents); 97 | 98 | return offset_cents; 99 | } 100 | 101 | int volts_to_midi(float volts) { 102 | int midi_note = floor(volts * 12.0f) + 21; 103 | return midi_note; 104 | } 105 | -------------------------------------------------------------------------------- /src/MomentaryOnButtons.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include "alikins.hpp" 3 | 4 | struct MomentaryOnButtons : Module { 5 | enum ParamIds { 6 | ENUMS(BUTTON_PARAM, MOMENTARY_BUTTONS), 7 | NUM_PARAMS 8 | }; 9 | enum InputIds { 10 | ENUMS(BUTTON_INPUT, MOMENTARY_BUTTONS), 11 | NUM_INPUTS 12 | }; 13 | enum OutputIds { 14 | ENUMS(BUTTON_OUTPUT, MOMENTARY_BUTTONS), 15 | NUM_OUTPUTS 16 | }; 17 | enum LightIds { 18 | ENUMS(BLINK_LIGHT, MOMENTARY_BUTTONS), 19 | NUM_LIGHTS 20 | }; 21 | 22 | MomentaryOnButtons() { 23 | config(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS); 24 | for (int i = 0; i < MOMENTARY_BUTTONS; i++) { 25 | configParam(BUTTON_PARAM + i, 26 | 0.0, 1.0, 0.0, string::f("Button %d", i+1)); 27 | } 28 | } 29 | 30 | void process(const ProcessArgs &args) override; 31 | 32 | }; 33 | 34 | 35 | void MomentaryOnButtons::process(const ProcessArgs &args) { 36 | 37 | for (int i = 0; i < MOMENTARY_BUTTONS; i++) { 38 | 39 | lights[BLINK_LIGHT + i].setBrightness(0.0); 40 | outputs[BUTTON_OUTPUT + i].setVoltage(0.0f); 41 | 42 | if (params[BUTTON_PARAM + i].getValue()) { 43 | outputs[BUTTON_OUTPUT + i].setVoltage(5.0f); 44 | lights[BLINK_LIGHT + i].setBrightness(1.0); 45 | } 46 | } 47 | } 48 | 49 | struct LightupButton : SvgSwitch { 50 | LightupButton() { 51 | momentary = true; 52 | addFrame(APP->window->loadSvg(asset::plugin(pluginInstance, "res/LightupButtonDown.svg"))); 53 | addFrame(APP->window->loadSvg(asset::plugin(pluginInstance, "res/LightupButton.svg"))); 54 | // shadow->opacity = 0.0f; 55 | }; 56 | }; 57 | 58 | struct MomentaryOnButtonsWidget : ModuleWidget { 59 | MomentaryOnButtonsWidget(MomentaryOnButtons *module) { 60 | setModule(module); 61 | box.size = Vec(4 * RACK_GRID_WIDTH, RACK_GRID_HEIGHT); 62 | 63 | int x_offset = 8; 64 | int y_offset = 26; 65 | 66 | int x_start = 0; 67 | int y_start = 27; 68 | 69 | int x_pos = 0; 70 | int y_pos = 0; 71 | 72 | int light_size = 20; 73 | 74 | { 75 | SvgPanel *panel = new SvgPanel(); 76 | panel->box.size = box.size; 77 | panel->setBackground(APP->window->loadSvg(asset::plugin(pluginInstance, "res/MomentaryOnButtons.svg"))); 78 | addChild(panel); 79 | } 80 | 81 | addChild(createWidget(Vec(0.0, 0))); 82 | addChild(createWidget(Vec(box.size.x-15, 0))); 83 | addChild(createWidget(Vec(0.0f, 365.0f))); 84 | 85 | /* 86 | addChild(createWidget(Vec(RACK_GRID_WIDTH, 0))); 87 | addChild(createWidget(Vec(box.size.x - 2 * RACK_GRID_WIDTH, 0))); 88 | addChild(createWidget(Vec(RACK_GRID_WIDTH, RACK_GRID_HEIGHT - RACK_GRID_WIDTH))); 89 | addChild(createWidget(Vec(box.size.x - 2 * RACK_GRID_WIDTH, RACK_GRID_HEIGHT - RACK_GRID_WIDTH))); 90 | */ 91 | 92 | for (int i = 0; i < MOMENTARY_BUTTONS; i++) { 93 | 94 | x_pos = x_start + x_offset; 95 | y_pos = y_start + (i * y_offset); 96 | 97 | addParam(createParam(Vec(x_pos, y_pos + 3), 98 | module, MomentaryOnButtons::BUTTON_PARAM + i)); 99 | 100 | //addChild(createLight>(Vec(x_pos + 5 + light_radius, y_pos + light_radius), 101 | // module, MomentaryOnButtons::BLINK_LIGHT + i)); 102 | 103 | addOutput(createOutput(Vec(x_pos + light_size + 4, y_pos), 104 | module, MomentaryOnButtons::BUTTON_OUTPUT + i)); 105 | } 106 | 107 | } 108 | }; 109 | 110 | 111 | Model *modelMomentaryOnButtons = createModel("MomentaryOnButtons"); 112 | -------------------------------------------------------------------------------- /src/GateLength.cpp: -------------------------------------------------------------------------------- 1 | #include "alikins.hpp" 2 | #include "MsDisplayWidget.hpp" 3 | 4 | /* 5 | This module was inspired by the question and discussion at: 6 | https://www.facebook.com/groups/vcvrack/permalink/161960391130780/ 7 | 8 | "Okay , Rackheads... ;) Looking for a module that can "stretch" the length , extend the duration , of a gate/ trigger pulse." 9 | */ 10 | 11 | struct GateLength : Module { 12 | enum ParamIds { 13 | GATE_LENGTH_PARAM1, 14 | GATE_LENGTH_PARAM2, 15 | GATE_LENGTH_PARAM3, 16 | GATE_LENGTH_PARAM4, 17 | GATE_LENGTH_PARAM5, 18 | NUM_PARAMS 19 | }; 20 | enum InputIds { 21 | TRIGGER_INPUT1, 22 | TRIGGER_INPUT2, 23 | TRIGGER_INPUT3, 24 | TRIGGER_INPUT4, 25 | TRIGGER_INPUT5, 26 | GATE_LENGTH_INPUT1, 27 | GATE_LENGTH_INPUT2, 28 | GATE_LENGTH_INPUT3, 29 | GATE_LENGTH_INPUT4, 30 | GATE_LENGTH_INPUT5, 31 | NUM_INPUTS 32 | }; 33 | enum OutputIds { 34 | GATE_OUTPUT1, 35 | GATE_OUTPUT2, 36 | GATE_OUTPUT3, 37 | GATE_OUTPUT4, 38 | GATE_OUTPUT5, 39 | NUM_OUTPUTS 40 | }; 41 | enum LightIds { 42 | NUM_LIGHTS 43 | }; 44 | 45 | float gate_length[GATE_LENGTH_INPUTS]; 46 | 47 | dsp::SchmittTrigger inputOnTrigger[GATE_LENGTH_INPUTS]; 48 | 49 | dsp::PulseGenerator gateGenerator[GATE_LENGTH_INPUTS]; 50 | 51 | GateLength() { 52 | config(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS); 53 | for (int i = 0; i < GATE_LENGTH_INPUTS; i++) { 54 | configParam(GATE_LENGTH_PARAM1 + i, 55 | 0.0f, 10.0f, 0.1f, "Length of gate", " sec"); 56 | } 57 | } 58 | void process(const ProcessArgs &args) override; 59 | 60 | void onReset() override { 61 | } 62 | 63 | }; 64 | 65 | void GateLength::process(const ProcessArgs &args) { 66 | // FIXME: add way to support >10.0s gate length 67 | 68 | float sample_time = args.sampleTime; 69 | 70 | for (int i = 0; i < GATE_LENGTH_INPUTS; i++) { 71 | gate_length[i] = clamp(params[GATE_LENGTH_PARAM1 + i].getValue() + inputs[GATE_LENGTH_INPUT1 + i].getVoltage(), 0.0f, 10.0f); 72 | 73 | if (inputOnTrigger[i].process(inputs[TRIGGER_INPUT1 + i].getVoltage())) { 74 | // debug("GL INPUT ON TRIGGER %d gate_length: %f", i, gate_length[i]); 75 | gateGenerator[i].trigger(gate_length[i]); 76 | } 77 | 78 | outputs[GATE_OUTPUT1 + i].setVoltage(gateGenerator[i].process(sample_time) ? 10.0f : 0.0f); 79 | } 80 | } 81 | 82 | struct GateLengthWidget : ModuleWidget { 83 | GateLengthWidget(GateLength *module) { 84 | setModule(module); 85 | box.size = Vec(4 * RACK_GRID_WIDTH, RACK_GRID_HEIGHT); 86 | setPanel(APP->window->loadSvg(asset::plugin(pluginInstance, "res/GateLength.svg"))); 87 | 88 | float y_pos = 2.0f; 89 | 90 | for (int i = 0; i < GATE_LENGTH_INPUTS; i++) { 91 | float x_pos = 4.0f; 92 | y_pos += 39.0f; 93 | 94 | addInput(createInput(Vec(x_pos, y_pos), 95 | module, 96 | GateLength::TRIGGER_INPUT1 + i)); 97 | 98 | x_pos += 30.0f; 99 | 100 | MsDisplayWidget *gate_length_display = new MsDisplayWidget(); 101 | gate_length_display->box.pos = Vec(x_pos, y_pos + 1.0f); 102 | gate_length_display->box.size = Vec(84, 24); 103 | if (module) { 104 | gate_length_display->value = &module->gate_length[i]; 105 | } 106 | addChild(gate_length_display); 107 | 108 | // FIXME: use new sequential box hbox/vbox thing 109 | x_pos += 84.0f; 110 | x_pos += 4.0f; 111 | addOutput(createOutput(Vec(x_pos, y_pos), 112 | module, 113 | GateLength::GATE_OUTPUT1 + i)); 114 | 115 | x_pos = 4.0f; 116 | y_pos += 26.0f; 117 | 118 | addInput(createInput(Vec(x_pos, y_pos), 119 | module, 120 | GateLength::GATE_LENGTH_INPUT1 + i)); 121 | 122 | x_pos += 30.0f; 123 | addParam(createParam(Vec(x_pos, y_pos + 3.0f), 124 | module, 125 | GateLength::GATE_LENGTH_PARAM1 + i)); 126 | } 127 | 128 | addChild(createWidget(Vec(0.0f, 0.0f))); 129 | addChild(createWidget(Vec(box.size.x - 15.0f, 0.0f))); 130 | addChild(createWidget(Vec(0.0f, 365.0f))); 131 | addChild(createWidget(Vec(box.size.x - 15.0f, 365.0f))); 132 | 133 | } 134 | }; 135 | 136 | Model *modelGateLength = createModel("GateLength"); 137 | -------------------------------------------------------------------------------- /src/BigMuteButton.cpp: -------------------------------------------------------------------------------- 1 | #include "alikins.hpp" 2 | 3 | struct BigMuteButton : Module { 4 | enum ParamIds { 5 | BIG_MUTE_BUTTON_PARAM, 6 | NUM_PARAMS 7 | }; 8 | enum InputIds { 9 | LEFT_INPUT, 10 | RIGHT_INPUT, 11 | NUM_INPUTS 12 | }; 13 | enum OutputIds { 14 | LEFT_OUTPUT, 15 | RIGHT_OUTPUT, 16 | NUM_OUTPUTS 17 | }; 18 | enum LightIds { 19 | NUM_LIGHTS 20 | }; 21 | 22 | float gain_mult = 1.0f; 23 | 24 | enum FadeState { 25 | UNMUTED_STEADY, 26 | MUTED_STEADY, 27 | MUTED_FADE_DOWN, 28 | UNMUTED_FADE_UP, 29 | INITIAL 30 | }; 31 | 32 | // FadeState state = UNMUTED_STEADY; 33 | FadeState state = INITIAL; 34 | dsp::SchmittTrigger muteOnTrigger; 35 | dsp::SchmittTrigger muteOffTrigger; 36 | 37 | float gmult2 = 1.0f; 38 | 39 | float crossfade_mix = 0.005f; 40 | 41 | BigMuteButton() { 42 | config(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS); 43 | configParam(BIG_MUTE_BUTTON_PARAM, 0.0f, 1.0f, 0.0f); 44 | } 45 | 46 | void process(const ProcessArgs &args) override; 47 | 48 | void onReset() override { 49 | state = UNMUTED_STEADY; 50 | } 51 | 52 | }; 53 | 54 | void BigMuteButton::process(const ProcessArgs &args) { 55 | 56 | // INITIAL state, choose next state based on current value of BIG_MUTE_BUTTON_PARAM 57 | // since BIG_MUTE_BUTTON_PARAM should be based on either the default, or for a saved 58 | // patch, the value saved to the params JSON. 59 | 60 | if (muteOnTrigger.process(params[BIG_MUTE_BUTTON_PARAM].getValue())) { 61 | // debug("MUTE ON"); 62 | state = MUTED_FADE_DOWN; 63 | gmult2 = 1.0f; 64 | } 65 | 66 | if (muteOffTrigger.process(!params[BIG_MUTE_BUTTON_PARAM].getValue())) { 67 | // debug("MUTE OFF"); 68 | state = UNMUTED_FADE_UP; 69 | gmult2 = 0.0f; 70 | } 71 | 72 | switch(state) { 73 | case INITIAL: 74 | state = (params[BIG_MUTE_BUTTON_PARAM].getValue() == 0.0f) ? UNMUTED_STEADY : MUTED_STEADY; 75 | break; 76 | case MUTED_STEADY: 77 | gmult2 = 0.0f; 78 | break; 79 | case UNMUTED_STEADY: 80 | gmult2 = 1.0f; 81 | break; 82 | case MUTED_FADE_DOWN: 83 | if (isNear(gmult2, 0.0f)) { 84 | state = MUTED_STEADY; 85 | // debug("faded down crossfade to 0.0"); 86 | gmult2 = 0.0f; 87 | break; 88 | } 89 | gmult2 = crossfade(gmult2, 0.0f, crossfade_mix); 90 | break; 91 | case UNMUTED_FADE_UP: 92 | if (isNear(gmult2, 1.0f)) { 93 | state = UNMUTED_STEADY; 94 | // debug("faded up crossfade to 1.0"); 95 | gmult2 = 1.0f; 96 | break; 97 | } 98 | gmult2 = crossfade(gmult2, 1.0f, crossfade_mix); 99 | break; 100 | } 101 | 102 | gmult2 = clamp(gmult2, 0.0f, 1.0f); 103 | 104 | outputs[LEFT_OUTPUT].setVoltage(inputs[LEFT_INPUT].getVoltage() * gmult2); 105 | outputs[RIGHT_OUTPUT].setVoltage(inputs[RIGHT_INPUT].getVoltage() * gmult2); 106 | 107 | // debug("state: %d, gmult2: %f", state, gmult2); 108 | 109 | // TODO: to eliminate worse case DC thump, also apply a RC filter of some sort? 110 | } 111 | 112 | struct BigSwitch : SvgSwitch { 113 | BigSwitch() { 114 | addFrame(APP->window->loadSvg(asset::plugin(pluginInstance, "res/BigMuteButtonMute.svg"))); 115 | addFrame(APP->window->loadSvg(asset::plugin(pluginInstance, "res/BigMuteButtonUnmute.svg"))); 116 | } 117 | }; 118 | 119 | 120 | struct BigMuteButtonWidget : ModuleWidget { 121 | BigMuteButtonWidget(BigMuteButton *module) { 122 | setModule(module); 123 | // box.size = Vec(6 * RACK_GRID_WIDTH, RACK_GRID_HEIGHT); 124 | setPanel(APP->window->loadSvg(asset::plugin(pluginInstance, "res/BigMuteButton.svg"))); 125 | 126 | addParam(createParam(Vec(0.0f, 0.0f), 127 | module, 128 | BigMuteButton::BIG_MUTE_BUTTON_PARAM)); 129 | 130 | addInput(createInput(Vec(4.0f, 302.0f), 131 | module, 132 | BigMuteButton::LEFT_INPUT)); 133 | addInput(createInput(Vec(4.0f, 330.0f), 134 | module, 135 | BigMuteButton::RIGHT_INPUT)); 136 | 137 | addOutput(createOutput(Vec(60.0f, 302.0f), 138 | module, 139 | BigMuteButton::LEFT_OUTPUT)); 140 | addOutput(createOutput(Vec(60.0f, 330.0f), 141 | module, 142 | BigMuteButton::RIGHT_OUTPUT)); 143 | 144 | addChild(createWidget(Vec(0.0, 0))); 145 | addChild(createWidget(Vec(box.size.x-15, 0))); 146 | addChild(createWidget(Vec(30, 365))); 147 | } 148 | }; 149 | 150 | Model *modelBigMuteButton = createModel("BigMuteButton"); 151 | -------------------------------------------------------------------------------- /src/enharmonic.hpp: -------------------------------------------------------------------------------- 1 | #include "rack.hpp" 2 | using namespace rack; 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | // using namespace rack; 10 | 11 | std::vector note_name_vec = {"C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B"}; 12 | 13 | enum NoteName { 14 | C_NATURAL, 15 | C_SHARP, 16 | D_NATURAL, 17 | D_SHARP, 18 | E_NATURAL, 19 | F_NATURAL, 20 | F_SHARP, 21 | G_NATURAL, 22 | G_SHARP, 23 | A_NATURAL, 24 | A_SHARP, 25 | B_NATURAL 26 | }; 27 | 28 | // add a NoteValue obj based on volts, with 29 | // methods for voltToText, voltToNote, etc 30 | struct NoteOct { 31 | std::string name; 32 | std::string octave; 33 | std::string flag; 34 | int rank; 35 | 36 | NoteOct() { 37 | // if no octave number, assume it is octave 4 38 | name = "C"; 39 | octave = "4"; 40 | flag = ""; 41 | rank = 0; 42 | } 43 | }; 44 | 45 | // Build a map of note_name ('C4', 'Ab3', etc) to it's CV voltage 46 | std::map gen_note_name_map() { 47 | 48 | double volt = -10.0; 49 | std::string note_name; 50 | std::map note_name_map; 51 | std::vector::iterator it; 52 | double semi = 1.0/12.0; 53 | 54 | // FIXME: add a map of note name (including enharmonic) to voltage offset from C 55 | // then just iterate over it for each octave 56 | for (int i = -6; i <= 14; i++) 57 | { 58 | for (int j = 0; j < 12; j++) 59 | { 60 | // debug("oct=%d note=%s volt=%f ", i, note_name_vec[j].c_str(), volt); 61 | note_name_map[string::f("%s%d", 62 | note_name_vec[j].c_str(), i)] = volt; 63 | volt += semi; 64 | } 65 | } 66 | return note_name_map; 67 | } 68 | 69 | std::map gen_enharmonic_name_map() { 70 | std::map enharmonic_map; 71 | 72 | enharmonic_map["c"] = "C"; 73 | enharmonic_map["C"] = "C"; 74 | 75 | enharmonic_map["C#"] = "C#"; 76 | enharmonic_map["c#"] = "C#"; 77 | enharmonic_map["Db"] = "C#"; 78 | enharmonic_map["db"] = "C#"; 79 | 80 | enharmonic_map["d"] = "D"; 81 | enharmonic_map["D"] = "D"; 82 | 83 | enharmonic_map["D#"] = "D#"; 84 | enharmonic_map["d#"] = "D#"; 85 | enharmonic_map["Eb"] = "D#"; 86 | enharmonic_map["eb"] = "D#"; 87 | 88 | enharmonic_map["E"] = "E"; 89 | enharmonic_map["e"] = "E"; 90 | enharmonic_map["Fb"] = "E"; 91 | enharmonic_map["fb"] = "E"; 92 | 93 | enharmonic_map["E#"] = "F"; 94 | enharmonic_map["e#"] = "F"; 95 | enharmonic_map["F"] = "F"; 96 | enharmonic_map["f"] = "F"; 97 | 98 | enharmonic_map["F#"] = "F#"; 99 | enharmonic_map["f#"] = "F#"; 100 | enharmonic_map["Gb"] = "F#"; 101 | enharmonic_map["Gb"] = "F#"; 102 | 103 | enharmonic_map["G"] = "G"; 104 | enharmonic_map["g"] = "G"; 105 | 106 | enharmonic_map["G#"] = "G#"; 107 | enharmonic_map["g#"] = "G#"; 108 | enharmonic_map["Ab"] = "G#"; 109 | enharmonic_map["ab"] = "G#"; 110 | 111 | enharmonic_map["A"] = "A"; 112 | enharmonic_map["a"] = "A"; 113 | 114 | enharmonic_map["A#"] = "A#"; 115 | enharmonic_map["a#"] = "A#"; 116 | enharmonic_map["Bb"] = "A#"; 117 | enharmonic_map["bb"] = "A#"; 118 | 119 | enharmonic_map["B"] = "B"; 120 | enharmonic_map["b"] = "B"; 121 | enharmonic_map["Cb"] = "B"; 122 | enharmonic_map["cb"] = "B"; 123 | 124 | enharmonic_map["B#"] = "C"; 125 | enharmonic_map["b#"] = "C"; 126 | 127 | return enharmonic_map; 128 | } 129 | 130 | std::map enharmonic_name_map = gen_enharmonic_name_map(); 131 | std::map note_name_to_volts_map = gen_note_name_map(); 132 | 133 | NoteOct* parseNote(std::string text) { 134 | // split into 'stuff before any int or -' and a number like string 135 | // ie C#11 -> C# 11, A-4 -> A 4 136 | std::size_t note_flag_found_loc = text.find_last_of("#♯b♭"); 137 | 138 | std::string note_flag = ""; 139 | if(note_flag_found_loc!=std::string::npos){ 140 | note_flag = text[note_flag_found_loc]; 141 | } 142 | 143 | std::size_t found = text.find_first_of("-0123456789"); 144 | 145 | // if no octave number, assume it is octave 4 146 | std::string note_name = text; 147 | std::string note_oct = "4"; 148 | 149 | if(found != std::string::npos){ 150 | note_name = text.substr(0, found); 151 | note_oct = text.substr(found, text.length()); 152 | } 153 | 154 | // debug("parseNote note_name: %s, note_oct: %s", note_name.c_str(), note_oct.c_str()); 155 | 156 | std::string can_note_name = enharmonic_name_map[note_name]; 157 | // debug("parseNote can_not_name: %s", can_note_name.c_str()); 158 | 159 | NoteOct *noteOct = new NoteOct(); 160 | noteOct->name = can_note_name; 161 | noteOct->octave = note_oct; 162 | noteOct->flag = note_flag; 163 | 164 | return noteOct; 165 | } 166 | 167 | std::string getCanNoteId(NoteOct *noteOct) { 168 | std::string can_note_name = enharmonic_name_map[noteOct->name]; 169 | 170 | std::string can_note_id = string::f("%s%s", can_note_name.c_str(), noteOct->octave.c_str()); 171 | 172 | return can_note_id; 173 | } -------------------------------------------------------------------------------- /test/test_rgb1.vcv: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.6.0dev", 3 | "modules": [ 4 | { 5 | "plugin": "Alikins", 6 | "version": "0.6.0dev", 7 | "model": "ColorPanel", 8 | "pos": [ 9 | 21, 10 | 0 11 | ], 12 | "params": [], 13 | "data": { 14 | "inputRange": 1 15 | } 16 | }, 17 | { 18 | "plugin": "Fundamental", 19 | "version": "0.6.0dev", 20 | "model": "Scope", 21 | "pos": [ 22 | 27, 23 | 0 24 | ], 25 | "params": [ 26 | { 27 | "paramId": 0, 28 | "value": 0.0 29 | }, 30 | { 31 | "paramId": 1, 32 | "value": 0.0 33 | }, 34 | { 35 | "paramId": 2, 36 | "value": 0.0 37 | }, 38 | { 39 | "paramId": 3, 40 | "value": 0.0 41 | }, 42 | { 43 | "paramId": 4, 44 | "value": -10.5650015 45 | }, 46 | { 47 | "paramId": 5, 48 | "value": 0.0 49 | }, 50 | { 51 | "paramId": 6, 52 | "value": 0.0 53 | }, 54 | { 55 | "paramId": 7, 56 | "value": 0.0 57 | } 58 | ], 59 | "data": { 60 | "lissajous": 0, 61 | "external": 0 62 | } 63 | }, 64 | { 65 | "plugin": "Fundamental", 66 | "version": "0.6.0dev", 67 | "model": "LFO", 68 | "pos": [ 69 | 9, 70 | 0 71 | ], 72 | "params": [ 73 | { 74 | "paramId": 0, 75 | "value": 0.0 76 | }, 77 | { 78 | "paramId": 1, 79 | "value": 1.0 80 | }, 81 | { 82 | "paramId": 2, 83 | "value": -0.923002422 84 | }, 85 | { 86 | "paramId": 3, 87 | "value": 0.0 88 | }, 89 | { 90 | "paramId": 5, 91 | "value": 0.5 92 | }, 93 | { 94 | "paramId": 4, 95 | "value": 0.0 96 | }, 97 | { 98 | "paramId": 6, 99 | "value": 0.0 100 | } 101 | ] 102 | }, 103 | { 104 | "plugin": "Fundamental", 105 | "version": "0.6.0dev", 106 | "model": "LFO", 107 | "pos": [ 108 | 9, 109 | 1 110 | ], 111 | "params": [ 112 | { 113 | "paramId": 0, 114 | "value": 0.0 115 | }, 116 | { 117 | "paramId": 1, 118 | "value": 1.0 119 | }, 120 | { 121 | "paramId": 2, 122 | "value": -5.97000027 123 | }, 124 | { 125 | "paramId": 3, 126 | "value": 0.0 127 | }, 128 | { 129 | "paramId": 5, 130 | "value": 0.5 131 | }, 132 | { 133 | "paramId": 4, 134 | "value": 0.0 135 | }, 136 | { 137 | "paramId": 6, 138 | "value": 0.0 139 | } 140 | ] 141 | }, 142 | { 143 | "plugin": "Fundamental", 144 | "version": "0.6.0dev", 145 | "model": "LFO", 146 | "pos": [ 147 | 19, 148 | 1 149 | ], 150 | "params": [ 151 | { 152 | "paramId": 0, 153 | "value": 1.0 154 | }, 155 | { 156 | "paramId": 1, 157 | "value": 1.0 158 | }, 159 | { 160 | "paramId": 2, 161 | "value": 5.70599937 162 | }, 163 | { 164 | "paramId": 3, 165 | "value": 0.0 166 | }, 167 | { 168 | "paramId": 5, 169 | "value": 0.5 170 | }, 171 | { 172 | "paramId": 4, 173 | "value": 0.0 174 | }, 175 | { 176 | "paramId": 6, 177 | "value": 0.0 178 | } 179 | ] 180 | }, 181 | { 182 | "plugin": "Fundamental", 183 | "version": "0.6.0dev", 184 | "model": "VCO", 185 | "pos": [ 186 | 33, 187 | 1 188 | ], 189 | "params": [ 190 | { 191 | "paramId": 0, 192 | "value": 1.0 193 | }, 194 | { 195 | "paramId": 1, 196 | "value": 1.0 197 | }, 198 | { 199 | "paramId": 2, 200 | "value": -28.5660191 201 | }, 202 | { 203 | "paramId": 3, 204 | "value": 0.0 205 | }, 206 | { 207 | "paramId": 5, 208 | "value": 0.727999985 209 | }, 210 | { 211 | "paramId": 4, 212 | "value": 0.0 213 | }, 214 | { 215 | "paramId": 6, 216 | "value": 0.0 217 | } 218 | ] 219 | } 220 | ], 221 | "wires": [ 222 | { 223 | "color": { 224 | "r": 0.788235366, 225 | "g": 0.0941176564, 226 | "b": 0.278431386, 227 | "a": 1.0 228 | }, 229 | "outputModuleId": 2, 230 | "outputId": 1, 231 | "inputModuleId": 0, 232 | "inputId": 0 233 | }, 234 | { 235 | "color": { 236 | "r": 0.788235366, 237 | "g": 0.717647076, 238 | "b": 0.054901965, 239 | "a": 1.0 240 | }, 241 | "outputModuleId": 5, 242 | "outputId": 0, 243 | "inputModuleId": 1, 244 | "inputId": 1 245 | }, 246 | { 247 | "color": { 248 | "r": 0.788235366, 249 | "g": 0.0941176564, 250 | "b": 0.278431386, 251 | "a": 1.0 252 | }, 253 | "outputModuleId": 3, 254 | "outputId": 1, 255 | "inputModuleId": 0, 256 | "inputId": 2 257 | }, 258 | { 259 | "color": { 260 | "r": 0.0470588282, 261 | "g": 0.556862772, 262 | "b": 0.0823529437, 263 | "a": 1.0 264 | }, 265 | "outputModuleId": 3, 266 | "outputId": 0, 267 | "inputModuleId": 0, 268 | "inputId": 1 269 | }, 270 | { 271 | "color": { 272 | "r": 0.0352941193, 273 | "g": 0.525490224, 274 | "b": 0.678431392, 275 | "a": 1.0 276 | }, 277 | "outputModuleId": 4, 278 | "outputId": 1, 279 | "inputModuleId": 3, 280 | "inputId": 0 281 | } 282 | ] 283 | } 284 | -------------------------------------------------------------------------------- /test/value_saver_test.vcv: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.6.2b", 3 | "modules": [ 4 | { 5 | "plugin": "Alikins", 6 | "version": "0.6.5", 7 | "model": "ValueSaver", 8 | "params": [], 9 | "data": { 10 | "values": [ 11 | -0.322059393, 12 | -4.39999723, 13 | 1.33333337, 14 | 0.0 15 | ] 16 | }, 17 | "labels": [ 18 | "some lfo", 19 | "A now disconnected input", 20 | "from midi CV sending 0 on startup", 21 | "Nothing hooked up here" 22 | ], 23 | "pos": [ 24 | 8, 25 | 0 26 | ] 27 | }, 28 | { 29 | "plugin": "Alikins", 30 | "version": "0.6.5", 31 | "model": "SpecificValue", 32 | "params": [ 33 | { 34 | "paramId": 0, 35 | "value": 1.33333337 36 | } 37 | ], 38 | "pos": [ 39 | 14, 40 | 0 41 | ] 42 | }, 43 | { 44 | "plugin": "Alikins", 45 | "version": "0.6.5", 46 | "model": "SpecificValue", 47 | "params": [ 48 | { 49 | "paramId": 0, 50 | "value": -1.7675662 51 | } 52 | ], 53 | "pos": [ 54 | 26, 55 | 0 56 | ] 57 | }, 58 | { 59 | "plugin": "Alikins", 60 | "version": "0.6.5", 61 | "model": "SpecificValue", 62 | "params": [ 63 | { 64 | "paramId": 0, 65 | "value": -4.39999723 66 | } 67 | ], 68 | "pos": [ 69 | 20, 70 | 0 71 | ] 72 | }, 73 | { 74 | "plugin": "Alikins", 75 | "version": "0.6.5", 76 | "model": "SpecificValue", 77 | "params": [ 78 | { 79 | "paramId": 0, 80 | "value": 3.33636189 81 | } 82 | ], 83 | "pos": [ 84 | 0, 85 | 1 86 | ] 87 | }, 88 | { 89 | "plugin": "FrozenWasteland", 90 | "version": "0.6.7", 91 | "model": "SeriouslySlowLFO", 92 | "params": [ 93 | { 94 | "paramId": 0, 95 | "value": 0.0 96 | }, 97 | { 98 | "paramId": 1, 99 | "value": 1.0 100 | }, 101 | { 102 | "paramId": 2, 103 | "value": 0.0 104 | } 105 | ], 106 | "data": { 107 | "timeBase": 0 108 | }, 109 | "pos": [ 110 | 26, 111 | 1 112 | ] 113 | }, 114 | { 115 | "plugin": "Core", 116 | "version": "0.6.2b", 117 | "model": "MIDIToCVInterface", 118 | "params": [], 119 | "data": { 120 | "divisions": [ 121 | 24, 122 | 6 123 | ], 124 | "midi": { 125 | "driver": -11, 126 | "deviceName": "QWERTY keyboard (US)", 127 | "channel": -1 128 | } 129 | }, 130 | "pos": [ 131 | 0, 132 | 0 133 | ] 134 | }, 135 | { 136 | "plugin": "SubmarineFree", 137 | "version": "0.6.8", 138 | "model": "LA-108", 139 | "params": [ 140 | { 141 | "paramId": 0, 142 | "value": 2.75213289 143 | }, 144 | { 145 | "paramId": 1, 146 | "value": 0.344524562 147 | }, 148 | { 149 | "paramId": 5, 150 | "value": 0.0 151 | }, 152 | { 153 | "paramId": 6, 154 | "value": 0.0 155 | }, 156 | { 157 | "paramId": 2, 158 | "value": -12.4130116 159 | }, 160 | { 161 | "paramId": 3, 162 | "value": 0.0 163 | }, 164 | { 165 | "paramId": 4, 166 | "value": 1.0 167 | }, 168 | { 169 | "paramId": 7, 170 | "value": 0.0 171 | } 172 | ], 173 | "data": { 174 | "voltage0": -10.0, 175 | "voltage1": 10.0 176 | }, 177 | "pos": [ 178 | 6, 179 | 1 180 | ] 181 | }, 182 | { 183 | "plugin": "ML_modules", 184 | "version": "0.6.3", 185 | "model": "VoltMeter", 186 | "params": [], 187 | "pos": [ 188 | 36, 189 | 1 190 | ] 191 | }, 192 | { 193 | "plugin": "Alikins", 194 | "version": "0.6.5", 195 | "model": "HoveredValue", 196 | "params": [ 197 | { 198 | "paramId": 2, 199 | "value": 0.0 200 | }, 201 | { 202 | "paramId": 1, 203 | "value": 2.0 204 | } 205 | ], 206 | "data": { 207 | "useTooltip": true 208 | }, 209 | "pos": [ 210 | 32, 211 | 0 212 | ] 213 | } 214 | ], 215 | "wires": [ 216 | { 217 | "color": "#0c8e15", 218 | "outputModuleId": 0, 219 | "outputId": 0, 220 | "inputModuleId": 2, 221 | "inputId": 0 222 | }, 223 | { 224 | "color": "#0986ad", 225 | "outputModuleId": 0, 226 | "outputId": 1, 227 | "inputModuleId": 3, 228 | "inputId": 0 229 | }, 230 | { 231 | "color": "#0986ad", 232 | "outputModuleId": 5, 233 | "outputId": 2, 234 | "inputModuleId": 0, 235 | "inputId": 0 236 | }, 237 | { 238 | "color": "#c9b70e", 239 | "outputModuleId": 5, 240 | "outputId": 2, 241 | "inputModuleId": 7, 242 | "inputId": 0 243 | }, 244 | { 245 | "color": "#0c8e15", 246 | "outputModuleId": 4, 247 | "outputId": 0, 248 | "inputModuleId": 7, 249 | "inputId": 2 250 | }, 251 | { 252 | "color": "#0986ad", 253 | "outputModuleId": 6, 254 | "outputId": 0, 255 | "inputModuleId": 0, 256 | "inputId": 2 257 | }, 258 | { 259 | "color": "#0986ad", 260 | "outputModuleId": 0, 261 | "outputId": 2, 262 | "inputModuleId": 1, 263 | "inputId": 0 264 | }, 265 | { 266 | "color": "#c9b70e", 267 | "outputModuleId": 0, 268 | "outputId": 0, 269 | "inputModuleId": 8, 270 | "inputId": 0 271 | }, 272 | { 273 | "color": "#c91847", 274 | "outputModuleId": 0, 275 | "outputId": 1, 276 | "inputModuleId": 8, 277 | "inputId": 1 278 | }, 279 | { 280 | "color": "#0c8e15", 281 | "outputModuleId": 0, 282 | "outputId": 2, 283 | "inputModuleId": 8, 284 | "inputId": 2 285 | }, 286 | { 287 | "color": "#0986ad", 288 | "outputModuleId": 0, 289 | "outputId": 3, 290 | "inputModuleId": 8, 291 | "inputId": 3 292 | } 293 | ] 294 | } -------------------------------------------------------------------------------- /res/ModIsOn.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 20 | 22 | 60 | 67 | 72 | 77 | 78 | 80 | 81 | 83 | image/svg+xml 84 | 86 | 87 | 88 | 89 | 90 | 96 | 106 | 107 | 113 | 117 | 121 | 122 | 126 | 130 | 131 | 135 | 139 | 140 | 141 | 142 | -------------------------------------------------------------------------------- /res/PurplePort.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 19 | 21 | 23 | 29 | 30 | 32 | 36 | 37 | 39 | 43 | 50 | 51 | 52 | 59 | 64 | 65 | 67 | 73 | 74 | 76 | 80 | 81 | 83 | 89 | 90 | 92 | 96 | 97 | 99 | 103 | 110 | 111 | 112 | 119 | 124 | 125 | 127 | 133 | 134 | 136 | 140 | 141 | 142 | 161 | 163 | 164 | 166 | image/svg+xml 167 | 169 | 170 | 171 | 172 | 173 | 178 | 182 | 187 | 192 | 197 | 198 | 199 | 200 | -------------------------------------------------------------------------------- /res/BigMuteButtonMute.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 20 | 22 | 56 | 61 | 66 | 71 | 72 | 74 | 75 | 77 | image/svg+xml 78 | 80 | 81 | 82 | 83 | 84 | 90 | 98 | 102 | 107 | 112 | 116 | 117 | 122 | 126 | 127 | 132 | 136 | 137 | 142 | 146 | 147 | 148 | 149 | 150 | 151 | -------------------------------------------------------------------------------- /src/ValueSaver.cpp: -------------------------------------------------------------------------------- 1 | #include "alikins.hpp" 2 | 3 | #define VALUE_COUNT 4 4 | #define CLOSE_ENOUGH 0.01f 5 | 6 | struct ValueSaverWidget; 7 | 8 | struct ValueSaver : Module { 9 | enum ParamIds { 10 | NUM_PARAMS 11 | }; 12 | enum InputIds { 13 | ENUMS(VALUE_INPUT, VALUE_COUNT), 14 | NUM_INPUTS 15 | }; 16 | enum OutputIds { 17 | ENUMS(VALUE_OUTPUT, VALUE_COUNT), 18 | NUM_OUTPUTS 19 | }; 20 | enum LightIds { 21 | NUM_LIGHTS 22 | }; 23 | 24 | ValueSaver() { 25 | config(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS); 26 | } 27 | 28 | void process(const ProcessArgs &args) override; 29 | 30 | void onReset() override { 31 | } 32 | 33 | json_t *dataToJson() override; 34 | void dataFromJson(json_t *rootJ) override; 35 | 36 | float values[VALUE_COUNT] = {0.0f}; 37 | float prevInputs[VALUE_COUNT] = {0.0f}; 38 | 39 | bool changingInputs[VALUE_COUNT] = {}; 40 | 41 | dsp::SchmittTrigger valueUpTrigger[VALUE_COUNT]; 42 | dsp::SchmittTrigger valueDownTrigger[VALUE_COUNT]; 43 | 44 | ValueSaverWidget* widget; 45 | }; 46 | 47 | void ValueSaver::process(const ProcessArgs &args) 48 | { 49 | // states: 50 | // - active inputs, meaningful current input -> output 51 | // - active inputs, 52 | // - in active inputs, meaningful 'saved' input -> output 53 | // - in active inputs, default/unset value -> output 54 | // 55 | for (int i = 0; i < VALUE_COUNT; i++) { 56 | // Just output the "saved" value if NO ACTIVE input 57 | if (!inputs[VALUE_INPUT + i].isConnected()) { 58 | outputs[VALUE_OUTPUT + i].setVoltage(prevInputs[i]); 59 | continue; 60 | } 61 | 62 | // ACTIVE INPUTS 63 | // process(rescale(in, low, high, 0.f, 1.f)) 64 | // if we haven't already figured out this is a useful changing input, check 65 | if (!changingInputs[i]) { 66 | float down = rescale(inputs[VALUE_INPUT + i].getVoltage(), 0.0f, -CLOSE_ENOUGH, 0.f, 1.f); 67 | float up = rescale(inputs[VALUE_INPUT + i].getVoltage(), 0.0f, CLOSE_ENOUGH, 0.f, 1.f); 68 | 69 | // TODO: if input is changing from 0.0f 70 | // if input is 0.0f but that is changing from prevInput (explicitly sent 0.0f) 71 | if (valueUpTrigger[i].process(up) || valueDownTrigger[i].process(down)) { 72 | // debug("value*Trigger[%d] triggered value: %f %f", i, values[i], up); 73 | changingInputs[i] = true; 74 | } 75 | } 76 | 77 | if (!changingInputs[i]) { 78 | // active input but it is 0.0f, like a midi-1 on startup that hasn't sent any signal yet 79 | // debug("[%d] ACTIVE but input is ~0.0f, prevInputs[%d]=%f", i, i, prevInputs[i]); 80 | values[i] = prevInputs[i]; 81 | outputs[VALUE_OUTPUT + i].setVoltage(values[i]); 82 | } 83 | else { 84 | // input value copied to output value and stored in values[] 85 | values[i] = inputs[VALUE_INPUT + i].getVoltage();; 86 | outputs[VALUE_OUTPUT + i].setVoltage(values[i]); 87 | prevInputs[i] = values[i]; 88 | 89 | // We are getting meaningful input values (ie, not just 0.0f), we can 90 | // pay attention to the inputs now 91 | changingInputs[i] = true; 92 | continue; 93 | } 94 | } 95 | } 96 | 97 | struct LabelDisplay : LedDisplay { 98 | LedDisplayTextField* textField = createWidget(Vec(0, 0)); 99 | 100 | LabelDisplay() { 101 | textField->multiline = true; 102 | textField->textOffset = Vec(-2.0f, -3.0f); 103 | textField->color = SCHEME_CYAN; 104 | addChild(textField); 105 | } 106 | 107 | void setFieldSize(rack::math::Vec boxsize) { 108 | textField->box.size = boxsize; 109 | } 110 | 111 | void setTextField(std::string text) { 112 | textField->text = text; 113 | } 114 | 115 | std::string getTextField() { 116 | return textField->text; 117 | } 118 | }; 119 | 120 | struct ValueSaverWidget : ModuleWidget { 121 | LabelDisplay *labelDisplays[VALUE_COUNT]; 122 | 123 | ValueSaverWidget(ValueSaver *module) { 124 | setModule(module); 125 | 126 | if (module) 127 | module->widget = this; 128 | 129 | box.size = Vec(4 * RACK_GRID_WIDTH, RACK_GRID_HEIGHT); 130 | setPanel(APP->window->loadSvg(asset::plugin(pluginInstance, "res/ValueSaverPanel.svg"))); 131 | 132 | float y_baseline = 48.0f; 133 | float y_pos = y_baseline; 134 | 135 | for (int i = 0; i < VALUE_COUNT; i++) { 136 | float x_pos = 4.0f; 137 | 138 | addInput(createInput(Vec(x_pos, y_pos), 139 | module, 140 | ValueSaver::VALUE_INPUT + i)); 141 | 142 | x_pos += 30.0f; 143 | 144 | addOutput(createOutput(Vec(box.size.x - 30.0f, y_pos), 145 | module, 146 | ValueSaver::VALUE_OUTPUT + i)); 147 | 148 | y_pos += 28.0f; 149 | labelDisplays[i] = new LabelDisplay(); 150 | 151 | labelDisplays[i]->box.pos = (Vec(4.0f, y_pos)); 152 | labelDisplays[i]->box.size = Vec(box.size.x - 8.0f, 40.0f); 153 | labelDisplays[i]->setTextField(""); 154 | labelDisplays[i]->setFieldSize(Vec(85, 40)); 155 | addChild(labelDisplays[i]); 156 | 157 | y_pos += labelDisplays[i]->box.size.y; 158 | y_pos += 14.0f; 159 | } 160 | 161 | addChild(createWidget(Vec(0.0f, 0.0f))); 162 | addChild(createWidget(Vec(box.size.x - 15.0f, 0.0f))); 163 | addChild(createWidget(Vec(0.0f, 365.0f))); 164 | addChild(createWidget(Vec(box.size.x - 15.0f, 365.0f))); 165 | 166 | } 167 | }; 168 | 169 | json_t* ValueSaver::dataToJson() { 170 | json_t *rootJ = json_object(); 171 | json_t *valuesJ = json_array(); 172 | json_t *labelsJ = json_array(); 173 | 174 | for (int i = 0; i < VALUE_COUNT; i++) { 175 | json_t *valueJ = json_real(values[i]); 176 | json_array_append_new(valuesJ, valueJ); 177 | if (widget) { 178 | json_t *labelJ = json_string(widget->labelDisplays[i]->getTextField().c_str()); 179 | json_array_append_new(labelsJ, labelJ); 180 | } 181 | } 182 | json_object_set_new(rootJ, "values", valuesJ); 183 | json_object_set_new(rootJ, "labels", labelsJ); 184 | 185 | return rootJ; 186 | } 187 | 188 | void ValueSaver::dataFromJson(json_t *rootJ) { 189 | json_t *valuesJ = json_object_get(rootJ, "values"); 190 | float savedInput; 191 | 192 | if (valuesJ) { 193 | for (int i = 0; i < VALUE_COUNT; i++) { 194 | json_t *valueJ = json_array_get(valuesJ, i); 195 | if (valueJ) { 196 | savedInput = json_number_value(valueJ); 197 | prevInputs[i] = savedInput; 198 | values[i] = savedInput; 199 | changingInputs[i] = false; 200 | } 201 | } 202 | } 203 | 204 | json_t *labelsJ = json_object_get(rootJ, "labels"); 205 | if (labelsJ) { 206 | for (int i = 0; i < VALUE_COUNT; i++) { 207 | json_t *labelJ = json_array_get(labelsJ, i); 208 | if (labelJ && widget) { 209 | widget->labelDisplays[i]->setTextField(json_string_value(labelJ)); 210 | } 211 | } 212 | } 213 | } 214 | 215 | Model *modelValueSaver = createModel("ValueSaver"); 216 | -------------------------------------------------------------------------------- /res-src/SmallPurpleTrimpot.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 19 | 38 | 40 | 42 | 43 | 45 | image/svg+xml 46 | 48 | 49 | 50 | 51 | 52 | 58 | 64 | 71 | 72 | 83 | 94 | 95 | -------------------------------------------------------------------------------- /res/PurpleTrimpot.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 19 | 38 | 40 | 42 | 43 | 45 | image/svg+xml 46 | 48 | 49 | 50 | 51 | 52 | 58 | 61 | 67 | 75 | 76 | 77 | 88 | 99 | 100 | -------------------------------------------------------------------------------- /src/InjectValue.cpp: -------------------------------------------------------------------------------- 1 | #include "alikins.hpp" 2 | #include 3 | 4 | #include "ParamFloatField.hpp" 5 | 6 | struct InjectValue : Module 7 | { 8 | enum ParamIds 9 | { 10 | INJECT_ENABLED_PARAM, 11 | INPUT_VOLTAGE_RANGE_PARAM, 12 | NUM_PARAMS 13 | }; 14 | enum InputIds 15 | { 16 | VALUE_INPUT, 17 | NUM_INPUTS 18 | }; 19 | enum OutputIds 20 | { 21 | DEBUG1_OUTPUT, 22 | NUM_OUTPUTS 23 | }; 24 | enum LightIds 25 | { 26 | NUM_LIGHTS 27 | }; 28 | 29 | enum InjectEnabled 30 | { 31 | OFF, 32 | WITH_SHIFT, 33 | ALWAYS 34 | }; 35 | 36 | InjectValue() { 37 | config(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS); 38 | configParam(InjectValue::INPUT_VOLTAGE_RANGE_PARAM, 0.0f, 2.0f, 0.0f, "Input Voltage Range"); 39 | configParam(InjectValue::INJECT_ENABLED_PARAM, 0.0f, 2.0f, 0.0f, "Enable Inject"); 40 | } 41 | 42 | void process(const ProcessArgs &args) override; 43 | 44 | float param_value = 0.0f; 45 | float input_param_value = 0.0f; 46 | 47 | InjectEnabled enabled = WITH_SHIFT; 48 | VoltageRange inputRange = MINUS_PLUS_FIVE; 49 | }; 50 | 51 | void InjectValue::process(const ProcessArgs &args) 52 | { 53 | enabled = (InjectEnabled) clamp((int) round(params[INJECT_ENABLED_PARAM].getValue()), 0, 2); 54 | 55 | inputRange = (VoltageRange) clamp((int) round(params[INPUT_VOLTAGE_RANGE_PARAM].getValue()), 0, 2); 56 | 57 | if (!inputs[VALUE_INPUT].isConnected()) { 58 | return; 59 | } 60 | 61 | param_value = inputs[VALUE_INPUT].getVoltage(); 62 | } 63 | 64 | struct InjectValueWidget : ModuleWidget 65 | { 66 | 67 | void step() override; 68 | void onChange(const event::Change &e) override; 69 | 70 | // TODO: enum/params/ui for input range 71 | 72 | ParamWidget *enableInjectSwitch; 73 | 74 | ParamFloatField *param_value_field; 75 | TextField *min_field; 76 | TextField *max_field; 77 | TextField *default_field; 78 | TextField *widget_type_field; 79 | 80 | InjectValueWidget(InjectValue *module) { 81 | setModule(module); 82 | setPanel(APP->window->loadSvg(asset::plugin(pluginInstance, "res/InjectValue.svg"))); 83 | 84 | float y_baseline = 45.0f; 85 | 86 | Vec text_field_size = Vec(70.0f, 22.0f); 87 | 88 | float x_pos = 10.0f; 89 | 90 | y_baseline = 38.0f; 91 | 92 | param_value_field = new ParamFloatField(module); 93 | param_value_field->box.pos = Vec(x_pos, y_baseline); 94 | param_value_field->box.size = text_field_size; 95 | if (module) { 96 | param_value_field->setValue(module->param_value); 97 | } 98 | 99 | addChild(param_value_field); 100 | 101 | y_baseline = 78.0f; 102 | min_field = new TextField(); 103 | min_field->box.pos = Vec(x_pos, y_baseline); 104 | min_field->box.size = text_field_size; 105 | 106 | addChild(min_field); 107 | 108 | y_baseline = 118.0f; 109 | max_field = new TextField(); 110 | max_field->box.pos = Vec(x_pos, y_baseline); 111 | max_field->box.size = text_field_size; 112 | 113 | addChild(max_field); 114 | 115 | y_baseline = 158.0f; 116 | default_field = new TextField(); 117 | default_field->box.pos = Vec(x_pos, y_baseline); 118 | default_field->box.size = text_field_size; 119 | 120 | addChild(default_field); 121 | 122 | y_baseline = 198.0f; 123 | widget_type_field = new TextField(); 124 | widget_type_field->box.pos = Vec(x_pos, y_baseline); 125 | widget_type_field->box.size = text_field_size; 126 | 127 | addChild(widget_type_field); 128 | 129 | y_baseline = box.size.y - 128.0f; 130 | 131 | addParam(createParam(Vec(5.0f, y_baseline ), module, InjectValue::INPUT_VOLTAGE_RANGE_PARAM)); 132 | 133 | addInput(createInput( 134 | Vec(60.0f, y_baseline - 2.0), 135 | module, 136 | InjectValue::VALUE_INPUT)); 137 | 138 | y_baseline = box.size.y - 65.0f; 139 | 140 | enableInjectSwitch = createParam(Vec(5, box.size.y - 62.0f), module, InjectValue::INJECT_ENABLED_PARAM); 141 | 142 | addParam(enableInjectSwitch); 143 | 144 | /* 145 | addChild(createWidget(Vec(0.0f, 0.0f))); 146 | addChild(createWidget(Vec(box.size.x - 15.0f, 0.0f))); 147 | addChild(createWidget(Vec(0.0f, 365.0f))); 148 | addChild(createWidget(Vec(box.size.x - 15.0f, 365.0f))); 149 | */ 150 | 151 | // fire off an event to refresh all the widgets 152 | event::Change e; 153 | onChange(e); 154 | } 155 | }; 156 | 157 | 158 | void InjectValueWidget::step() { 159 | 160 | ModuleWidget::step(); 161 | 162 | if (!module) 163 | return; 164 | 165 | InjectValue *injectValueModule = dynamic_cast(module); 166 | 167 | if (!injectValueModule) { 168 | return; 169 | } 170 | 171 | if (!APP->event->hoveredWidget) { 172 | return; 173 | } 174 | 175 | // TODO/FIXME: I assume there is a better way to check type? 176 | ParamWidget *pwidget = dynamic_cast(APP->event->hoveredWidget); 177 | 178 | if (!pwidget) { 179 | min_field->setText(""); 180 | max_field->setText(""); 181 | default_field->setText(""); 182 | widget_type_field->setText("unknown"); 183 | 184 | ModuleWidget::step(); 185 | return; 186 | } 187 | 188 | // Let's not inject into our own module 189 | if (pwidget->module == module) 190 | return; 191 | 192 | // float input = module->inputs[InjectValue::VALUE_INPUT].getVoltage(); 193 | float input_value = injectValueModule->param_value; 194 | 195 | // clamp the input to withing input voltage range before scaling it 196 | float clamped_input = clamp(input_value, 197 | voltage_min[injectValueModule->inputRange], 198 | voltage_max[injectValueModule->inputRange]); 199 | 200 | // rescale the input CV to whatever the range of the param widget is 201 | float scaled_value = rescale(clamped_input, 202 | voltage_min[injectValueModule->inputRange], 203 | voltage_max[injectValueModule->inputRange], 204 | pwidget->getParamQuantity()->getMinValue(), 205 | pwidget->getParamQuantity()->getMaxValue()); 206 | 207 | /* 208 | debug("input_value: %f (in_min: %f, in_max:%f) clamped_in: %f out_min: %f, out_max: %f) scaled_value: %f", 209 | input_value, 210 | voltage_min[injectValueModule->inputRange], 211 | voltage_max[injectValueModule->inputRange], 212 | clamped_input, 213 | pwidget->minValue, 214 | pwidget->maxValue, 215 | scaled_value); 216 | */ 217 | 218 | // Show the value that will be injected 219 | // TODO: show the original input value and scaled output? 220 | 221 | bool shift_pressed = ((APP->window->getMods() & RACK_MOD_MASK) == GLFW_MOD_SHIFT); 222 | 223 | if (!injectValueModule->enabled || (injectValueModule->enabled == InjectValue::WITH_SHIFT && !shift_pressed)) 224 | { 225 | return; 226 | } 227 | 228 | param_value_field->setValue(scaled_value); 229 | 230 | min_field->setText(string::f("%#.4g", pwidget->getParamQuantity()->getMinValue())); 231 | max_field->setText(string::f("%#.4g", pwidget->getParamQuantity()->getMaxValue())); 232 | default_field->setText(string::f("%#.4g", pwidget->getParamQuantity()->getDefaultValue())); 233 | widget_type_field->setText("Param"); 234 | 235 | // TODO: would be useful to have a light to indicate when values are being injected 236 | pwidget->getParamQuantity()->setValue(scaled_value); 237 | 238 | // force a step of the param widget to get it to 'animate' 239 | pwidget->step(); 240 | 241 | } 242 | 243 | void InjectValueWidget::onChange(const event::Change &e) { 244 | ModuleWidget::onChange(e); 245 | param_value_field->onChange(e); 246 | } 247 | 248 | Model *modelInjectValue = createModel("InjectValue"); 249 | -------------------------------------------------------------------------------- /res/ShiftIsOn.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 20 | 22 | 60 | 67 | 72 | 77 | 78 | 80 | 81 | 83 | image/svg+xml 84 | 86 | 87 | 88 | 89 | 90 | 95 | 102 | 103 | 109 | 119 | 120 | 126 | 131 | 135 | 136 | 141 | 145 | 146 | 151 | 155 | 156 | 161 | 165 | 166 | 171 | 175 | 176 | 177 | 178 | -------------------------------------------------------------------------------- /res/BigMuteButton.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 20 | 22 | 56 | 61 | 66 | 71 | 76 | 77 | 79 | 80 | 82 | image/svg+xml 83 | 85 | 86 | 87 | 88 | 89 | 94 | 103 | 104 | 109 | 115 | 119 | 123 | 124 | 130 | 135 | 140 | 145 | 146 | 151 | 155 | 156 | 162 | 166 | 167 | 168 | 169 | -------------------------------------------------------------------------------- /res/AltIsOn.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 20 | 22 | 24 | 30 | 31 | 33 | 37 | 38 | 40 | 44 | 51 | 52 | 53 | 60 | 65 | 66 | 68 | 74 | 75 | 77 | 81 | 82 | 84 | 90 | 91 | 93 | 97 | 98 | 100 | 104 | 111 | 112 | 113 | 120 | 125 | 126 | 128 | 134 | 135 | 137 | 141 | 142 | 143 | 181 | 188 | 193 | 198 | 199 | 201 | 202 | 204 | image/svg+xml 205 | 207 | 208 | 209 | 210 | 211 | 217 | 227 | 228 | 234 | 239 | 243 | 244 | 249 | 253 | 254 | 259 | 263 | 264 | 265 | 266 | -------------------------------------------------------------------------------- /res/AltIsOff.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 20 | 22 | 24 | 30 | 31 | 33 | 37 | 38 | 40 | 44 | 51 | 52 | 53 | 60 | 65 | 66 | 68 | 74 | 75 | 77 | 81 | 82 | 84 | 90 | 91 | 93 | 97 | 98 | 100 | 104 | 111 | 112 | 113 | 120 | 125 | 126 | 128 | 134 | 135 | 137 | 141 | 142 | 143 | 181 | 188 | 193 | 198 | 199 | 201 | 202 | 204 | image/svg+xml 205 | 207 | 208 | 209 | 210 | 211 | 217 | 227 | 228 | 234 | 237 | 242 | 246 | 247 | 252 | 256 | 257 | 262 | 266 | 267 | 268 | 269 | 270 | -------------------------------------------------------------------------------- /test/test-reference.vcv: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.6.0", 3 | "modules": [ 4 | { 5 | "plugin": "Alikins", 6 | "version": "0.6.0", 7 | "model": "Reference", 8 | "pos": [ 9 | 0, 10 | 0 11 | ], 12 | "params": [] 13 | }, 14 | { 15 | "plugin": "Fundamental", 16 | "version": "0.6.0", 17 | "model": "Scope", 18 | "pos": [ 19 | 2, 20 | 1 21 | ], 22 | "params": [ 23 | { 24 | "paramId": 0, 25 | "value": 0.0 26 | }, 27 | { 28 | "paramId": 1, 29 | "value": 0.0 30 | }, 31 | { 32 | "paramId": 2, 33 | "value": 0.0 34 | }, 35 | { 36 | "paramId": 3, 37 | "value": 0.0 38 | }, 39 | { 40 | "paramId": 4, 41 | "value": -7.99999571 42 | }, 43 | { 44 | "paramId": 5, 45 | "value": 0.0 46 | }, 47 | { 48 | "paramId": 6, 49 | "value": -10.0 50 | }, 51 | { 52 | "paramId": 7, 53 | "value": 0.0 54 | } 55 | ], 56 | "data": { 57 | "lissajous": 0, 58 | "external": 0 59 | } 60 | }, 61 | { 62 | "plugin": "Alikins", 63 | "version": "0.6.0", 64 | "model": "SpecificValue", 65 | "pos": [ 66 | 2, 67 | 0 68 | ], 69 | "params": [ 70 | { 71 | "paramId": 1, 72 | "value": 4.0 73 | }, 74 | { 75 | "paramId": 0, 76 | "value": 10.0 77 | } 78 | ] 79 | }, 80 | { 81 | "plugin": "Alikins", 82 | "version": "0.6.0", 83 | "model": "SpecificValue", 84 | "pos": [ 85 | 8, 86 | 0 87 | ], 88 | "params": [ 89 | { 90 | "paramId": 1, 91 | "value": 4.0 92 | }, 93 | { 94 | "paramId": 0, 95 | "value": 5.0 96 | } 97 | ] 98 | }, 99 | { 100 | "plugin": "Alikins", 101 | "version": "0.6.0", 102 | "model": "SpecificValue", 103 | "pos": [ 104 | 14, 105 | 0 106 | ], 107 | "params": [ 108 | { 109 | "paramId": 1, 110 | "value": 4.0 111 | }, 112 | { 113 | "paramId": 0, 114 | "value": 1.0 115 | } 116 | ] 117 | }, 118 | { 119 | "plugin": "Alikins", 120 | "version": "0.6.0", 121 | "model": "SpecificValue", 122 | "pos": [ 123 | 20, 124 | 0 125 | ], 126 | "params": [ 127 | { 128 | "paramId": 1, 129 | "value": 4.0 130 | }, 131 | { 132 | "paramId": 0, 133 | "value": 0.0 134 | } 135 | ] 136 | }, 137 | { 138 | "plugin": "Alikins", 139 | "version": "0.6.0", 140 | "model": "SpecificValue", 141 | "pos": [ 142 | 26, 143 | 0 144 | ], 145 | "params": [ 146 | { 147 | "paramId": 1, 148 | "value": 4.0 149 | }, 150 | { 151 | "paramId": 0, 152 | "value": -1.0 153 | } 154 | ] 155 | }, 156 | { 157 | "plugin": "Alikins", 158 | "version": "0.6.0", 159 | "model": "SpecificValue", 160 | "pos": [ 161 | 32, 162 | 0 163 | ], 164 | "params": [ 165 | { 166 | "paramId": 1, 167 | "value": 4.0 168 | }, 169 | { 170 | "paramId": 0, 171 | "value": -5.0 172 | } 173 | ] 174 | }, 175 | { 176 | "plugin": "Alikins", 177 | "version": "0.6.0", 178 | "model": "SpecificValue", 179 | "pos": [ 180 | 38, 181 | 0 182 | ], 183 | "params": [ 184 | { 185 | "paramId": 1, 186 | "value": 4.0 187 | }, 188 | { 189 | "paramId": 0, 190 | "value": -10.0 191 | } 192 | ] 193 | }, 194 | { 195 | "plugin": "Alikins", 196 | "version": "0.6.0", 197 | "model": "Reference", 198 | "pos": [ 199 | 0, 200 | 1 201 | ], 202 | "params": [] 203 | }, 204 | { 205 | "plugin": "Fundamental", 206 | "version": "0.6.0", 207 | "model": "Scope", 208 | "pos": [ 209 | 15, 210 | 1 211 | ], 212 | "params": [ 213 | { 214 | "paramId": 0, 215 | "value": 0.0 216 | }, 217 | { 218 | "paramId": 1, 219 | "value": 0.0 220 | }, 221 | { 222 | "paramId": 2, 223 | "value": 0.0 224 | }, 225 | { 226 | "paramId": 3, 227 | "value": 0.0 228 | }, 229 | { 230 | "paramId": 4, 231 | "value": -7.99999571 232 | }, 233 | { 234 | "paramId": 5, 235 | "value": 0.0 236 | }, 237 | { 238 | "paramId": 6, 239 | "value": -10.0 240 | }, 241 | { 242 | "paramId": 7, 243 | "value": 0.0 244 | } 245 | ], 246 | "data": { 247 | "lissajous": 0, 248 | "external": 0 249 | } 250 | }, 251 | { 252 | "plugin": "Fundamental", 253 | "version": "0.6.0", 254 | "model": "Scope", 255 | "pos": [ 256 | 28, 257 | 1 258 | ], 259 | "params": [ 260 | { 261 | "paramId": 0, 262 | "value": 0.0 263 | }, 264 | { 265 | "paramId": 1, 266 | "value": 0.0 267 | }, 268 | { 269 | "paramId": 2, 270 | "value": 0.0 271 | }, 272 | { 273 | "paramId": 3, 274 | "value": 0.0 275 | }, 276 | { 277 | "paramId": 4, 278 | "value": -7.99999571 279 | }, 280 | { 281 | "paramId": 5, 282 | "value": 0.0 283 | }, 284 | { 285 | "paramId": 6, 286 | "value": -10.0 287 | }, 288 | { 289 | "paramId": 7, 290 | "value": 0.0 291 | } 292 | ], 293 | "data": { 294 | "lissajous": 0, 295 | "external": 0 296 | } 297 | } 298 | ], 299 | "wires": [ 300 | { 301 | "color": { 302 | "r": 0.788235366, 303 | "g": 0.0941176564, 304 | "b": 0.278431386, 305 | "a": 1.0 306 | }, 307 | "outputModuleId": 0, 308 | "outputId": 6, 309 | "inputModuleId": 2, 310 | "inputId": 0 311 | }, 312 | { 313 | "color": { 314 | "r": 0.0470588282, 315 | "g": 0.556862772, 316 | "b": 0.0823529437, 317 | "a": 1.0 318 | }, 319 | "outputModuleId": 0, 320 | "outputId": 5, 321 | "inputModuleId": 3, 322 | "inputId": 0 323 | }, 324 | { 325 | "color": { 326 | "r": 0.0352941193, 327 | "g": 0.525490224, 328 | "b": 0.678431392, 329 | "a": 1.0 330 | }, 331 | "outputModuleId": 0, 332 | "outputId": 4, 333 | "inputModuleId": 4, 334 | "inputId": 0 335 | }, 336 | { 337 | "color": { 338 | "r": 0.788235366, 339 | "g": 0.717647076, 340 | "b": 0.054901965, 341 | "a": 1.0 342 | }, 343 | "outputModuleId": 0, 344 | "outputId": 3, 345 | "inputModuleId": 5, 346 | "inputId": 0 347 | }, 348 | { 349 | "color": { 350 | "r": 0.788235366, 351 | "g": 0.0941176564, 352 | "b": 0.278431386, 353 | "a": 1.0 354 | }, 355 | "outputModuleId": 0, 356 | "outputId": 2, 357 | "inputModuleId": 6, 358 | "inputId": 0 359 | }, 360 | { 361 | "color": { 362 | "r": 0.0470588282, 363 | "g": 0.556862772, 364 | "b": 0.0823529437, 365 | "a": 1.0 366 | }, 367 | "outputModuleId": 0, 368 | "outputId": 1, 369 | "inputModuleId": 7, 370 | "inputId": 0 371 | }, 372 | { 373 | "color": { 374 | "r": 0.0352941193, 375 | "g": 0.525490224, 376 | "b": 0.678431392, 377 | "a": 1.0 378 | }, 379 | "outputModuleId": 0, 380 | "outputId": 0, 381 | "inputModuleId": 8, 382 | "inputId": 0 383 | }, 384 | { 385 | "color": { 386 | "r": 0.788235366, 387 | "g": 0.717647076, 388 | "b": 0.054901965, 389 | "a": 1.0 390 | }, 391 | "outputModuleId": 9, 392 | "outputId": 6, 393 | "inputModuleId": 1, 394 | "inputId": 0 395 | }, 396 | { 397 | "color": { 398 | "r": 0.788235366, 399 | "g": 0.0941176564, 400 | "b": 0.278431386, 401 | "a": 1.0 402 | }, 403 | "outputModuleId": 9, 404 | "outputId": 5, 405 | "inputModuleId": 1, 406 | "inputId": 1 407 | }, 408 | { 409 | "color": { 410 | "r": 0.0470588282, 411 | "g": 0.556862772, 412 | "b": 0.0823529437, 413 | "a": 1.0 414 | }, 415 | "outputModuleId": 9, 416 | "outputId": 4, 417 | "inputModuleId": 10, 418 | "inputId": 0 419 | }, 420 | { 421 | "color": { 422 | "r": 0.788235366, 423 | "g": 0.717647076, 424 | "b": 0.054901965, 425 | "a": 1.0 426 | }, 427 | "outputModuleId": 9, 428 | "outputId": 1, 429 | "inputModuleId": 11, 430 | "inputId": 0 431 | }, 432 | { 433 | "color": { 434 | "r": 0.788235366, 435 | "g": 0.0941176564, 436 | "b": 0.278431386, 437 | "a": 1.0 438 | }, 439 | "outputModuleId": 9, 440 | "outputId": 0, 441 | "inputModuleId": 11, 442 | "inputId": 1 443 | }, 444 | { 445 | "color": { 446 | "r": 0.0470588282, 447 | "g": 0.556862772, 448 | "b": 0.0823529437, 449 | "a": 1.0 450 | }, 451 | "outputModuleId": 9, 452 | "outputId": 2, 453 | "inputModuleId": 10, 454 | "inputId": 1 455 | } 456 | ] 457 | } -------------------------------------------------------------------------------- /res/ModIsOff.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 20 | 22 | 24 | 30 | 31 | 33 | 37 | 38 | 40 | 44 | 51 | 52 | 53 | 60 | 65 | 66 | 68 | 74 | 75 | 77 | 81 | 82 | 84 | 90 | 91 | 93 | 97 | 98 | 100 | 104 | 111 | 112 | 113 | 120 | 125 | 126 | 128 | 134 | 135 | 137 | 141 | 142 | 143 | 181 | 188 | 193 | 198 | 199 | 201 | 202 | 204 | image/svg+xml 205 | 207 | 208 | 209 | 210 | 211 | 217 | 227 | 228 | 234 | 238 | 242 | 243 | 247 | 251 | 252 | 256 | 260 | 261 | 262 | 263 | -------------------------------------------------------------------------------- /res/CtrlIsOff.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 20 | 22 | 24 | 30 | 31 | 33 | 37 | 38 | 40 | 44 | 51 | 52 | 53 | 60 | 65 | 66 | 68 | 74 | 75 | 77 | 81 | 82 | 84 | 90 | 91 | 93 | 97 | 98 | 100 | 104 | 111 | 112 | 113 | 120 | 125 | 126 | 128 | 134 | 135 | 137 | 141 | 142 | 143 | 181 | 188 | 193 | 198 | 199 | 201 | 202 | 204 | image/svg+xml 205 | 207 | 208 | 209 | 210 | 211 | 217 | 227 | 228 | 234 | 239 | 243 | 244 | 249 | 253 | 254 | 259 | 263 | 264 | 269 | 273 | 274 | 275 | 276 | -------------------------------------------------------------------------------- /res/CtrlIsOn.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 20 | 22 | 24 | 30 | 31 | 33 | 37 | 38 | 40 | 44 | 51 | 52 | 53 | 60 | 65 | 66 | 68 | 74 | 75 | 77 | 81 | 82 | 84 | 90 | 91 | 93 | 97 | 98 | 100 | 104 | 111 | 112 | 113 | 120 | 125 | 126 | 128 | 134 | 135 | 137 | 141 | 142 | 143 | 181 | 188 | 193 | 198 | 199 | 201 | 202 | 204 | image/svg+xml 205 | 207 | 208 | 209 | 210 | 211 | 217 | 227 | 230 | 235 | 239 | 240 | 245 | 249 | 250 | 255 | 259 | 260 | 265 | 269 | 270 | 271 | 272 | 278 | 279 | -------------------------------------------------------------------------------- /src/ColorPanel.cpp: -------------------------------------------------------------------------------- 1 | #include "alikins.hpp" 2 | 3 | #include "math.hpp" 4 | 5 | struct ColorPanel : Module { 6 | enum ParamIds { 7 | RED_PARAM, 8 | GREEN_PARAM, 9 | BLUE_PARAM, 10 | NUM_PARAMS 11 | }; 12 | enum InputIds { 13 | RED_INPUT, 14 | GREEN_INPUT, 15 | BLUE_INPUT, 16 | NUM_INPUTS 17 | }; 18 | enum OutputIds { 19 | NUM_OUTPUTS 20 | }; 21 | enum LightIds { 22 | NUM_LIGHTS 23 | }; 24 | 25 | float red = 0.0f; 26 | float green = 0.0f; 27 | float blue = 0.0f; 28 | 29 | enum InputRange { 30 | ZERO_TEN, 31 | MINUS_PLUS_FIVE 32 | }; 33 | 34 | InputRange inputRange = MINUS_PLUS_FIVE; 35 | const float in_min[2] = {0.0, -5.0}; 36 | const float in_max[2] = {10.0, 5.0}; 37 | 38 | enum ColorMode { 39 | RGB_MODE, 40 | HSL_MODE, 41 | }; 42 | 43 | ColorMode colorMode = HSL_MODE; 44 | 45 | NVGcolor defaultColor = nvgRGB(0x91, 0x87, 0xff); 46 | 47 | ColorPanel() { 48 | config(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS); 49 | configParam(RED_PARAM, 50 | 0.0f, 1.0f, 0.75, "Red or Hue"); 51 | configParam(GREEN_PARAM, 52 | 0.0f, 1.0f, 1.0f, "Green or Saturation"); 53 | configParam(BLUE_PARAM, 54 | 0.0f, 1.0f, 0.5f, "Blue or Lightness"); 55 | } 56 | 57 | void process(const ProcessArgs &args) override; 58 | 59 | json_t *dataToJson() override; 60 | void dataFromJson(json_t *rootJ) override; 61 | 62 | }; 63 | 64 | json_t* ColorPanel::dataToJson() { 65 | json_t *rootJ = json_object(); 66 | 67 | json_object_set_new(rootJ, "inputRange", json_integer(inputRange)); 68 | json_object_set_new(rootJ, "colorMode", json_integer(colorMode)); 69 | 70 | return rootJ; 71 | } 72 | 73 | void ColorPanel::dataFromJson(json_t *rootJ) { 74 | json_t *inputRangeJ = json_object_get(rootJ, "inputRange"); 75 | if (inputRangeJ) { 76 | inputRange = (InputRange) json_integer_value(inputRangeJ); 77 | } 78 | 79 | json_t *colorModeJ = json_object_get(rootJ, "colorMode"); 80 | if (colorModeJ) { 81 | colorMode = (ColorMode) json_integer_value(colorModeJ); 82 | } 83 | 84 | } 85 | 86 | void ColorPanel::process(const ProcessArgs &args) 87 | { 88 | if (inputs[RED_INPUT].isConnected()) { 89 | float in_value = clamp(inputs[RED_INPUT].getVoltage(), in_min[inputRange], in_max[inputRange]); 90 | red = rescale(in_value, in_min[inputRange], in_max[inputRange], 0.0f, 1.0f); 91 | params[RED_PARAM].setValue(red); 92 | } else { 93 | red = params[RED_PARAM].getValue(); 94 | } 95 | 96 | if (inputs[GREEN_INPUT].isConnected()) { 97 | float in_value = clamp(inputs[GREEN_INPUT].getVoltage(), in_min[inputRange], in_max[inputRange]); 98 | green = rescale(in_value, in_min[inputRange], in_max[inputRange], 0.0f, 1.0f); 99 | params[GREEN_PARAM].setValue(green); 100 | } else { 101 | green = params[GREEN_PARAM].getValue(); 102 | } 103 | 104 | if (inputs[BLUE_INPUT].isConnected()) { 105 | float in_value = clamp(inputs[BLUE_INPUT].getVoltage(), in_min[inputRange], in_max[inputRange]); 106 | blue = rescale(in_value, in_min[inputRange], in_max[inputRange], 0.0f, 1.0f); 107 | params[BLUE_PARAM].setValue(blue); 108 | } else { 109 | blue = params[BLUE_PARAM].getValue(); 110 | } 111 | } 112 | 113 | // From Rack/src/core/Blank.cpp 114 | struct ColorPanelModuleResizeHandle : OpaqueWidget { 115 | bool right = false; 116 | float dragX; 117 | Vec dragPos; 118 | 119 | Rect originalBox; 120 | 121 | ColorPanelModuleResizeHandle() { 122 | box.size = Vec(RACK_GRID_WIDTH * 1, RACK_GRID_HEIGHT); 123 | dragX = 0.0f; 124 | } 125 | 126 | // void onMouseDown(EventMouseDown &e) override { 127 | void onButton(const event::Button &e) override { 128 | if ((e.button == 0) && (e.action == GLFW_PRESS)) { 129 | // e.consumed = true; 130 | //ie.target = this; 131 | e.consume(this); 132 | } 133 | } 134 | 135 | void onDragStart(const event::DragStart &e) override { 136 | // dragX = gRackWidget->lastMousePos.x; 137 | dragX = APP->scene->rack->getMousePos().x; 138 | ModuleWidget *m = getAncestorOfType(); 139 | originalBox = m->box; 140 | } 141 | 142 | void onDragMove(const event::DragMove &e) override { 143 | ModuleWidget *m = getAncestorOfType(); 144 | 145 | float newDragX = APP->scene->rack->getMousePos().x; 146 | float deltaX = newDragX - dragX; 147 | 148 | Rect newBox = originalBox; 149 | Rect oldBox = m->box; 150 | 151 | const float minWidth = 6 * RACK_GRID_WIDTH; 152 | 153 | if (right) { 154 | newBox.size.x += deltaX; 155 | newBox.size.x = std::fmax(newBox.size.x, minWidth); 156 | newBox.size.x = std::round(newBox.size.x / RACK_GRID_WIDTH) * RACK_GRID_WIDTH; 157 | } else { 158 | newBox.size.x -= deltaX; 159 | newBox.size.x = std::fmax(newBox.size.x, minWidth); 160 | newBox.size.x = std::round(newBox.size.x / RACK_GRID_WIDTH) * RACK_GRID_WIDTH; 161 | newBox.pos.x = originalBox.pos.x + originalBox.size.x - newBox.size.x; 162 | } 163 | m->box = newBox; 164 | if (!APP->scene->rack->requestModulePos(m, newBox.pos)) { 165 | m->box = oldBox; 166 | } 167 | // gRackWidget->requestModuleBox(m, newBox); 168 | } 169 | }; 170 | 171 | 172 | 173 | struct ColorFrame : TransparentWidget { 174 | 175 | ColorPanel *module; 176 | 177 | NVGcolor panelColor; 178 | 179 | ColorFrame() { 180 | } 181 | 182 | void step() override { 183 | Widget::step(); 184 | 185 | if (!module) { 186 | return; 187 | } 188 | 189 | if (module->colorMode == ColorPanel::HSL_MODE) { 190 | panelColor = nvgHSL(module->red, module->green, module->blue); 191 | } 192 | 193 | if (module->colorMode == ColorPanel::RGB_MODE) { 194 | panelColor = nvgRGBf(module->red, module->green, module->blue); 195 | } 196 | } 197 | 198 | void draw(const DrawArgs &args) override { 199 | // FIXME: not really red, green, blue anymore 200 | // could include alpha 201 | // debug("RgbPanel.draw red=%f, green=%f, blue=%f", red, green, blue); 202 | 203 | nvgBeginPath(args.vg); 204 | 205 | nvgRect(args.vg, 0.0, 0.0, box.size.x, box.size.y); 206 | nvgFillColor(args.vg, panelColor); 207 | nvgFill(args.vg); 208 | } 209 | }; 210 | 211 | struct ColorModeItem : MenuItem { 212 | 213 | ColorPanel *colorPanel; 214 | ColorPanel::ColorMode colorMode; 215 | 216 | void onAction(const event::Action &e) override { 217 | DEBUG("setting colorPanel->colorMode = %d", colorMode); 218 | colorPanel->colorMode = colorMode; 219 | }; 220 | 221 | void step() override { 222 | rightText = (colorPanel->colorMode == colorMode)? "✔" : ""; 223 | }; 224 | 225 | }; 226 | 227 | 228 | 229 | struct InputRangeItem : MenuItem { 230 | ColorPanel *colorPanel; 231 | ColorPanel::InputRange inputRange; 232 | 233 | void onAction(const event::Action &e) override { 234 | colorPanel->inputRange = inputRange; 235 | }; 236 | 237 | void step() override { 238 | rightText = (colorPanel->inputRange == inputRange)? "✔" : ""; 239 | }; 240 | 241 | }; 242 | 243 | 244 | struct ColorPanelWidget : ModuleWidget { 245 | Widget *rightHandle; 246 | ColorFrame *colorFrame; 247 | 248 | // Menu *createContextMenu(); 249 | 250 | void step() override; 251 | 252 | json_t *toJson(); 253 | void fromJson(json_t *rootJ); 254 | 255 | ColorPanelWidget(ColorPanel *module) { 256 | setModule(module); 257 | box.size = Vec(6 * RACK_GRID_WIDTH, RACK_GRID_HEIGHT); 258 | 259 | { 260 | colorFrame = new ColorFrame(); 261 | colorFrame->box.size = box.size; 262 | colorFrame->module = module; 263 | // purple-ish 264 | // 9187ff 265 | colorFrame->panelColor = nvgRGB(0x91, 0x87, 0xff); 266 | addChild(colorFrame); 267 | } 268 | 269 | ColorPanelModuleResizeHandle *leftHandle = new ColorPanelModuleResizeHandle(); 270 | ColorPanelModuleResizeHandle *rightHandle = new ColorPanelModuleResizeHandle(); 271 | rightHandle->right = true; 272 | this->rightHandle = rightHandle; 273 | addChild(leftHandle); 274 | addChild(rightHandle); 275 | 276 | // FIXME: how do I figure that out before creating the instance? 277 | float port_width = 24.672108f; 278 | float empty_space = box.size.x - (3.0f * port_width); 279 | float interstitial = empty_space / 5.0f; 280 | 281 | float x_pos = interstitial; 282 | addInput(createInput(Vec(x_pos, 340.0f), 283 | module, 284 | ColorPanel::RED_INPUT)); 285 | 286 | x_pos = x_pos + interstitial + port_width;; 287 | addInput(createInput(Vec(x_pos, 340.0f), 288 | module, 289 | ColorPanel::GREEN_INPUT)); 290 | 291 | x_pos = x_pos + interstitial + port_width;; 292 | addInput(createInput(Vec(x_pos, 340.0f), 293 | module, 294 | ColorPanel::BLUE_INPUT)); 295 | 296 | } 297 | 298 | void appendContextMenu(Menu *menu) override { 299 | MenuLabel *spacerLabel = new MenuLabel(); 300 | menu->addChild(spacerLabel); 301 | 302 | ColorPanel *colorPanel = dynamic_cast(module); 303 | assert(colorPanel); 304 | 305 | MenuLabel *colorModeLabel = new MenuLabel(); 306 | colorModeLabel->text = "ColorMode"; 307 | menu->addChild(colorModeLabel); 308 | 309 | // FIXME: colorModeItem looks too much like colorModelItem 310 | ColorModeItem *rgbModeItem = new ColorModeItem(); 311 | rgbModeItem->text = "RGB"; 312 | rgbModeItem->colorPanel = colorPanel; 313 | rgbModeItem->colorMode = ColorPanel::RGB_MODE; 314 | menu->addChild(rgbModeItem); 315 | 316 | ColorModeItem *hslModeItem = new ColorModeItem(); 317 | hslModeItem->text = "HSL"; 318 | hslModeItem->colorPanel = colorPanel; 319 | hslModeItem->colorMode = ColorPanel::HSL_MODE; 320 | menu->addChild(hslModeItem); 321 | 322 | 323 | MenuLabel *modeLabel2 = new MenuLabel(); 324 | modeLabel2->text = "Input Range"; 325 | menu->addChild(modeLabel2); 326 | 327 | InputRangeItem *zeroTenItem = new InputRangeItem(); 328 | zeroTenItem->text = "0 - +10V (uni)"; 329 | zeroTenItem->colorPanel = colorPanel; 330 | zeroTenItem->inputRange = ColorPanel::ZERO_TEN; 331 | menu->addChild(zeroTenItem); 332 | 333 | InputRangeItem *fiveFiveItem = new InputRangeItem(); 334 | fiveFiveItem->text = "-5 - +5V (bi)"; 335 | fiveFiveItem->colorPanel = colorPanel; 336 | fiveFiveItem->inputRange = ColorPanel::MINUS_PLUS_FIVE; 337 | menu->addChild(fiveFiveItem); 338 | } 339 | }; 340 | 341 | 342 | void ColorPanelWidget::step() { 343 | colorFrame->box.size = box.size; 344 | rightHandle->box.pos.x = box.size.x - rightHandle->box.size.x; 345 | 346 | // debug("box.size (%f, %f)", box.size.x, box.size.y); 347 | ModuleWidget::step(); 348 | } 349 | 350 | json_t *ColorPanelWidget::toJson() { 351 | json_t *rootJ = ModuleWidget::toJson(); 352 | json_object_set_new(rootJ, "width", json_real(box.size.x)); 353 | return rootJ; 354 | } 355 | 356 | void ColorPanelWidget::fromJson(json_t *rootJ) { 357 | ModuleWidget::fromJson(rootJ); 358 | json_t *widthJ = json_object_get(rootJ, "width"); 359 | if (widthJ) 360 | box.size.x = json_number_value(widthJ); 361 | } 362 | 363 | Model *modelColorPanel = createModel("ColorPanel"); 364 | -------------------------------------------------------------------------------- /res/SuperIsOn.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 20 | 22 | 24 | 30 | 31 | 33 | 37 | 38 | 40 | 44 | 51 | 52 | 53 | 60 | 65 | 66 | 68 | 74 | 75 | 77 | 81 | 82 | 84 | 90 | 91 | 93 | 97 | 98 | 100 | 104 | 111 | 112 | 113 | 120 | 125 | 126 | 128 | 134 | 135 | 137 | 141 | 142 | 143 | 181 | 188 | 193 | 198 | 199 | 201 | 202 | 204 | image/svg+xml 205 | 207 | 208 | 209 | 210 | 211 | 217 | 227 | 228 | 234 | 239 | 243 | 244 | 248 | 252 | 253 | 258 | 262 | 263 | 268 | 272 | 273 | 277 | 281 | 282 | 283 | 284 | --------------------------------------------------------------------------------