// // UGraph.cpp // Author: Dale Johnson // Contact: valley.audio.soft@gmail.com // Date: 5/12/2017 // // UGraph, a port of "Mutable Instruments Grids" for VCV Rack // Original author: Olivier Gillet (ol.gillet@gmail.com) // https://github.com/pichenettes/eurorack/tree/master/grids // Copyright 2012 Olivier Gillet. // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // You should have received a copy of the GNU General Public License // along with this program. If not, see . #include "../Valley.hpp" #include "../ValleyComponents.hpp" #include "dsp/digital.hpp" #include "../Common/Metronome.hpp" #include "../Common/Oneshot.hpp" #include "../Topograph/TopographPatternGenerator.hpp" #include // setprecision #include // stringstream namespace rack_plugin_Valley { struct UGraph : Module { enum ParamIds { RESET_BUTTON_PARAM, RUN_BUTTON_PARAM, TEMPO_PARAM, MAPX_PARAM, MAPY_PARAM, CHAOS_PARAM, BD_DENS_PARAM, SN_DENS_PARAM, HH_DENS_PARAM, SWING_PARAM, NUM_PARAMS }; enum InputIds { CLOCK_INPUT, RESET_INPUT, MAPX_CV, MAPY_CV, CHAOS_CV, BD_FILL_CV, SN_FILL_CV, HH_FILL_CV, SWING_CV, RUN_INPUT, NUM_INPUTS }; enum OutputIds { BD_OUTPUT, SN_OUTPUT, HH_OUTPUT, BD_ACC_OUTPUT, SN_ACC_OUTPUT, HH_ACC_OUTPUT, NUM_OUTPUTS }; enum LightIds { RUNNING_LIGHT, RESET_LIGHT, BD_LIGHT, SN_LIGHT, HH_LIGHT, NUM_LIGHTS }; Metronome metro; PatternGenerator grids; uint8_t numTicks; SchmittTrigger clockTrig; SchmittTrigger resetTrig; SchmittTrigger resetButtonTrig; SchmittTrigger runButtonTrig; SchmittTrigger runInputTrig; bool initExtReset = true; int running = 0; bool extClock = false; bool advStep = false; long seqStep = 0; float swing = 0.5; float swingHighTempo = 0.0; float swingLowTempo = 0.0; long elapsedTicks = 0; float tempoParam = 0.0; std::shared_ptr tempo = std::make_shared(120.0); std::shared_ptr mapX = std::make_shared(0.0); std::shared_ptr mapY = std::make_shared(0.0); std::shared_ptr chaos = std::make_shared(0.0); float BDFill = 0.0; float SNFill = 0.0; float HHFill = 0.0; uint8_t state = 0; // LED Triggers Oneshot drumLED[3]; const LightIds drumLEDIds[3] = {BD_LIGHT, SN_LIGHT, HH_LIGHT}; Oneshot BDLed; Oneshot SNLed; Oneshot HHLed; Oneshot resetLed; Oneshot runningLed; // Drum Triggers Oneshot drumTriggers[6]; bool gateState[6]; const OutputIds outIDs[6] = {BD_OUTPUT, SN_OUTPUT, HH_OUTPUT, BD_ACC_OUTPUT, SN_ACC_OUTPUT, HH_ACC_OUTPUT}; enum SequencerMode { HENRI, OLIVIER, EUCLIDEAN }; SequencerMode sequencerMode = OLIVIER; int inEuclideanMode = 0; unsigned long sequencerModeChoice = 0; unsigned long prevClockResChoice = 0; unsigned long clockResChoice = 0; enum TriggerOutputMode { PULSE, GATE }; TriggerOutputMode triggerOutputMode = PULSE; enum AccOutputMode { INDIVIDUAL_ACCENTS, ACC_CLK_RST }; AccOutputMode accOutputMode = INDIVIDUAL_ACCENTS; enum ExtClockResolution { EXTCLOCK_RES_4_PPQN, EXTCLOCK_RES_8_PPQN, EXTCLOCK_RES_24_PPQN, }; ExtClockResolution extClockResolution = EXTCLOCK_RES_24_PPQN; enum ChaosKnobMode { CHAOS, SWING }; ChaosKnobMode chaosKnobMode = CHAOS; enum RunMode { TOGGLE, MOMENTARY }; RunMode runMode = TOGGLE; int panelStyle; std::string clockBPM; std::string mapXText = "Map X"; std::string mapYText = "Map Y"; std::string chaosText = "Chaos"; int textVisible = 1; UGraph() : Module(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS) { metro = Metronome(120, engineGetSampleRate(), 24.0, 0.0); numTicks = ticks_granularity[2]; srand(time(NULL)); BDLed = Oneshot(0.1, engineGetSampleRate()); SNLed = Oneshot(0.1, engineGetSampleRate()); HHLed = Oneshot(0.1, engineGetSampleRate()); resetLed = Oneshot(0.1, engineGetSampleRate()); for(int i = 0; i < 6; ++i) { drumTriggers[i] = Oneshot(0.001, engineGetSampleRate()); gateState[i] = false; } for(int i = 0; i < 3; ++i) { drumLED[i] = Oneshot(0.1, engineGetSampleRate()); } panelStyle = 0; } json_t *toJson() override { json_t *rootJ = json_object(); json_object_set_new(rootJ, "sequencerMode", json_integer(sequencerModeChoice)); json_object_set_new(rootJ, "triggerOutputMode", json_integer(triggerOutputMode)); json_object_set_new(rootJ, "accOutputMode", json_integer(accOutputMode)); json_object_set_new(rootJ, "extClockResolution", json_integer(clockResChoice)); json_object_set_new(rootJ, "chaosKnobMode", json_integer(chaosKnobMode)); json_object_set_new(rootJ, "runMode", json_integer(runMode)); json_object_set_new(rootJ, "panelStyle", json_integer(panelStyle)); json_object_set_new(rootJ, "running", json_integer(running)); return rootJ; } void fromJson(json_t *rootJ) override { json_t *sequencerModeJ = json_object_get(rootJ, "sequencerMode"); if (sequencerModeJ) { sequencerModeChoice = json_integer_value(sequencerModeJ); } json_t *triggerOutputModeJ = json_object_get(rootJ, "triggerOutputMode"); if (triggerOutputModeJ) { triggerOutputMode = (UGraph::TriggerOutputMode) json_integer_value(triggerOutputModeJ); } json_t *accOutputModeJ = json_object_get(rootJ, "accOutputMode"); if (accOutputModeJ) { accOutputMode = (UGraph::AccOutputMode) json_integer_value(accOutputModeJ); switch(accOutputMode) { case INDIVIDUAL_ACCENTS: grids.setAccentAltMode(false); break; case ACC_CLK_RST: grids.setAccentAltMode(true); } } json_t *extClockResolutionJ = json_object_get(rootJ, "extClockResolution"); if (extClockResolutionJ) { clockResChoice = json_integer_value(extClockResolutionJ); grids.reset(); } json_t *chaosKnobModeJ = json_object_get(rootJ, "chaosKnobMode"); if (chaosKnobModeJ) { chaosKnobMode = (UGraph::ChaosKnobMode) json_integer_value(chaosKnobModeJ); } json_t *runModeJ = json_object_get(rootJ, "runMode"); if (runModeJ) { runMode = (UGraph::RunMode) json_integer_value(runModeJ); } json_t *panelStyleJ = json_object_get(rootJ, "panelStyle"); if (panelStyleJ) { panelStyle = (int)json_integer_value(panelStyleJ); } json_t *runningJ = json_object_get(rootJ, "running"); if (runningJ) { running = (int)json_integer_value(runningJ); } } void step() override; void onSampleRateChange() override; void updateUI(); void updateOutputs(); }; void UGraph::step() { if(runMode == TOGGLE) { if (runButtonTrig.process(params[RUN_BUTTON_PARAM].value) || runInputTrig.process(inputs[RUN_INPUT].value)) { if(runMode == TOGGLE){ running = !running; } } } else { running = params[RUN_BUTTON_PARAM].value + inputs[RUN_INPUT].value; if(running == 0) { metro.reset(); } } lights[RUNNING_LIGHT].value = running ? 1.0 : 0.0; if(resetButtonTrig.process(params[RESET_BUTTON_PARAM].value) || resetTrig.process(inputs[RESET_INPUT].value)) { grids.reset(); metro.reset(); resetLed.trigger(); seqStep = 0; elapsedTicks = 0; } switch(sequencerModeChoice) { case 0: grids.setPatternMode(PATTERN_OLIVIER); inEuclideanMode = 0; break; case 1: grids.setPatternMode(PATTERN_HENRI); inEuclideanMode = 0; break; case 2: grids.setPatternMode(PATTERN_EUCLIDEAN); inEuclideanMode = 1; break; } // Clock, tempo and swing tempoParam = params[TEMPO_PARAM].value; *tempo = rescale(tempoParam, 0.01f, 1.f, 40.f, 240.f); swing = clamp(params[SWING_PARAM].value + inputs[SWING_CV].value / 10.f, 0.f, 0.9f); swingHighTempo = *tempo / (1 - swing); swingLowTempo = *tempo / (1 + swing); if(elapsedTicks < 6) { metro.setTempo(swingLowTempo); } else { metro.setTempo(swingHighTempo); } if(clockResChoice != prevClockResChoice) { prevClockResChoice = clockResChoice; grids.reset(); } // External clock select if(tempoParam < 0.01) { clockBPM = "Ext."; if(initExtReset) { grids.reset(); initExtReset = false; } numTicks = ticks_granularity[clockResChoice]; extClock = true; } else { initExtReset = true; numTicks = ticks_granularity[2]; extClock = false; metro.process(); } *mapX = params[MAPX_PARAM].value + (inputs[MAPX_CV].value / 10.f); *mapX = clamp(*mapX, 0.f, 1.f); *mapY = params[MAPY_PARAM].value + (inputs[MAPY_CV].value / 10.f); *mapY = clamp(*mapY, 0.f, 1.f); BDFill = params[BD_DENS_PARAM].value + (inputs[BD_FILL_CV].value / 10.f); BDFill = clamp(BDFill, 0.f, 1.f); SNFill = params[SN_DENS_PARAM].value + (inputs[SN_FILL_CV].value / 10.f); SNFill = clamp(SNFill, 0.f, 1.f); HHFill = params[HH_DENS_PARAM].value + (inputs[HH_FILL_CV].value / 10.f); HHFill = clamp(HHFill, 0.f, 1.f); *chaos = params[CHAOS_PARAM].value + (inputs[CHAOS_CV].value / 10.f); *chaos = clamp(*chaos, 0.f, 1.f); if(running) { if(extClock) { if(clockTrig.process(inputs[CLOCK_INPUT].value)) { advStep = true; } } else if(metro.hasTicked()){ advStep = true; elapsedTicks++; elapsedTicks %= 12; } else { advStep = false; } grids.setMapX((uint8_t)(*mapX * 255.0)); grids.setMapY((uint8_t)(*mapY * 255.0)); grids.setBDDensity((uint8_t)(BDFill * 255.0)); grids.setSDDensity((uint8_t)(SNFill * 255.0)); grids.setHHDensity((uint8_t)(HHFill * 255.0)); grids.setRandomness((uint8_t)(*chaos * 255.0)); grids.setEuclideanLength(0, (uint8_t)(*mapX * 255.0)); grids.setEuclideanLength(1, (uint8_t)(*mapY * 255.0)); grids.setEuclideanLength(2, (uint8_t)(*chaos * 255.0)); } if(advStep) { grids.tick(numTicks); for(int i = 0; i < 6; ++i) { if(grids.getDrumState(i)) { drumTriggers[i].trigger(); gateState[i] = true; if(i < 3) { drumLED[i].trigger(); } } } seqStep++; if(seqStep >= 32) { seqStep = 0; } advStep = false; } updateOutputs(); updateUI(); } void UGraph::updateUI() { resetLed.process(); for(int i = 0; i < 3; ++i) { drumLED[i].process(); if(drumLED[i].getState() == 1) { lights[drumLEDIds[i]].value = 1.0; } else { lights[drumLEDIds[i]].value = 0.0; } } if(resetLed.getState() == 1) { lights[RESET_LIGHT].value = 1.0; } else { lights[RESET_LIGHT].value = 0.0; } } void UGraph::updateOutputs() { if(triggerOutputMode == PULSE) { for(int i = 0; i < 6; ++i) { drumTriggers[i].process(); if(drumTriggers[i].getState()) { outputs[outIDs[i]].value = 10; } else { outputs[outIDs[i]].value = 0; } } } else if(extClock && triggerOutputMode == GATE) { for(int i = 0; i < 6; ++i) { if(inputs[CLOCK_INPUT].value > 0 && gateState[i]) { gateState[i] = false; outputs[outIDs[i]].value = 10; } if(inputs[CLOCK_INPUT].value <= 0) { outputs[outIDs[i]].value = 0; } } } else { for(int i = 0; i < 6; ++i) { if(metro.getElapsedTickTime() < 0.5 && gateState[i]) { outputs[outIDs[i]].value = 10; } else { outputs[outIDs[i]].value = 0; gateState[i] = false; } } } } void UGraph::onSampleRateChange() { metro.setSampleRate(engineGetSampleRate()); for(int i = 0; i < 3; ++i) { drumLED[i].setSampleRate(engineGetSampleRate()); } resetLed.setSampleRate(engineGetSampleRate()); for(int i = 0; i < 6; ++i) { drumTriggers[i].setSampleRate(engineGetSampleRate()); } } // The widget struct PanelBorder : TransparentWidget { void draw(NVGcontext *vg) override { NVGcolor borderColor = nvgRGBAf(0.5, 0.5, 0.5, 0.5); nvgBeginPath(vg); nvgRect(vg, 0.5, 0.5, box.size.x - 1.0, box.size.y - 1.0); nvgStrokeColor(vg, borderColor); nvgStrokeWidth(vg, 1.0); nvgStroke(vg); } }; struct UGraphDynamicText : TransparentWidget { std::string oldText; std::string* pText; std::shared_ptr font; int size; NVGcolor drawColour; int* visibility; DynamicViewMode viewMode; enum Colour { COLOUR_WHITE, COLOUR_BLACK }; int* colourHandle; UGraphDynamicText() { font = Font::load(assetPlugin(plugin, "res/din1451alt.ttf")); size = 16; visibility = nullptr; pText = nullptr; viewMode = ACTIVE_HIGH_VIEW; } void draw(NVGcontext* vg) { nvgFontSize(vg, size); nvgFontFaceId(vg, font->handle); nvgTextLetterSpacing(vg, 0.f); Vec textPos = Vec(0.f, 0.f); if(colourHandle != nullptr) { switch(*colourHandle) { case COLOUR_BLACK : drawColour = nvgRGB(0x00,0x00,0x00); break; case COLOUR_WHITE : drawColour = nvgRGB(0xFF,0xFF,0xFF); break; default : drawColour = nvgRGB(0x00,0x00,0x00); } } else { drawColour = nvgRGB(0x00,0x00,0x00); } nvgFillColor(vg, drawColour); nvgTextAlign(vg, NVG_ALIGN_CENTER | NVG_ALIGN_TOP); if(pText != nullptr) { nvgText(vg, textPos.x, textPos.y, pText->c_str(), NULL); } } void step() { if(visibility != nullptr) { if(*visibility) { visible = true; } else { visible = false; } if(viewMode == ACTIVE_LOW_VIEW) { visible = !visible; } } else { visible = true; } } }; UGraphDynamicText* createUGraphDynamicText(const Vec& pos, int size, int* colourHandle, std::string* pText, int* visibilityHandle, DynamicViewMode viewMode) { UGraphDynamicText* dynText = new UGraphDynamicText(); dynText->size = size; dynText->colourHandle = colourHandle; dynText->pText = pText; dynText->box.pos = pos; dynText->box.size = Vec(82,14); dynText->visibility = visibilityHandle; dynText->viewMode = viewMode; return dynText; } //////////////////////////////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////// Context Menu /////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////////////////// struct UGraphWidget : ModuleWidget { const std::string seqModeItemText[3] = {"Olivier", "Henri", "Euclid"}; const std::string clockResText[3] = {"4 PPQN", "8 PPQN", "24 PPQN"}; UGraphWidget(UGraph *module); void appendContextMenu(Menu* menu) override; }; UGraphWidget::UGraphWidget(UGraph *module) : ModuleWidget(module){ { DynamicPanelWidget *panel = new DynamicPanelWidget(); panel->addPanel(SVG::load(assetPlugin(plugin, "res/UGraphPanel.svg"))); panel->addPanel(SVG::load(assetPlugin(plugin, "res/UGraphPanelLight.svg"))); box.size = panel->box.size; panel->mode = &module->panelStyle; addChild(panel); } addChild(Widget::create(Vec(RACK_GRID_WIDTH, 0))); addChild(Widget::create(Vec(box.size.x - 2 * RACK_GRID_WIDTH, 0))); addChild(Widget::create(Vec(RACK_GRID_WIDTH, RACK_GRID_HEIGHT - RACK_GRID_WIDTH))); addChild(Widget::create(Vec(box.size.x - 2 * RACK_GRID_WIDTH, RACK_GRID_HEIGHT - RACK_GRID_WIDTH))); auto floatToTempoText = [](float a){ std::stringstream stream; stream << std::fixed << std::setprecision(1) << a; if(a >= 40.0) { return stream.str(); } std::string out = "Ext."; return out; }; auto floatToEuclideanText = [](float a){ return std::to_string(((uint8_t)(a * 255.0) >> 3) + 1); }; // Tempo text { std::shared_ptr i = module->tempo; DynamicValueText* vText = new DynamicValueText(i, floatToTempoText); vText->box.pos = Vec(53, 66.75); vText->size = 14; vText->viewMode = ACTIVE_HIGH_VIEW; vText->colorHandle = &module->panelStyle; addChild(vText); } // Map X Text { addChild(createDynamicText(Vec(53, 163), 14, "Map X", &module->inEuclideanMode, &module->panelStyle, ACTIVE_LOW_VIEW)); std::shared_ptr i = module->mapX; DynamicValueText* vText = new DynamicValueText(i, floatToEuclideanText); vText->box.pos = Vec(53, 163); vText->size = 14; vText->viewMode = ACTIVE_HIGH_VIEW; vText->visibility = &module->inEuclideanMode; vText->colorHandle = &module->panelStyle; addChild(vText); } // Map Y Text { addChild(createDynamicText(Vec(89, 163), 14, "Map Y", &module->inEuclideanMode, &module->panelStyle, ACTIVE_LOW_VIEW)); std::shared_ptr i = module->mapY; DynamicValueText* vText = new DynamicValueText(i, floatToEuclideanText); vText->box.pos = Vec(89, 163); vText->size = 14; vText->viewMode = ACTIVE_HIGH_VIEW; vText->visibility = &module->inEuclideanMode; vText->colorHandle = &module->panelStyle; addChild(vText); } // Chaos Text { addChild(createDynamicText(Vec(125, 163), 14, "Chaos", &module->inEuclideanMode, &module->panelStyle, ACTIVE_LOW_VIEW)); std::shared_ptr i = module->chaos; DynamicValueText* vText = new DynamicValueText(i, floatToEuclideanText); vText->box.pos = Vec(125, 163); vText->size = 14; vText->viewMode = ACTIVE_HIGH_VIEW; vText->visibility = &module->inEuclideanMode; vText->colorHandle = &module->panelStyle; addChild(vText); } addParam(ParamWidget::create(Vec(36.5, 30.15), module, UGraph::TEMPO_PARAM, 0.0, 1.0, 0.406)); addParam(ParamWidget::create(Vec(43.5, 137), module, UGraph::MAPX_PARAM, 0.0, 1.0, 0.0)); addParam(ParamWidget::create(Vec(79.5, 137), module, UGraph::MAPY_PARAM, 0.0, 1.0, 0.0)); addParam(ParamWidget::create(Vec(115.5, 137), module, UGraph::CHAOS_PARAM, 0.0, 1.0, 0.0)); addParam(ParamWidget::create(Vec(43.5, 217.65), module, UGraph::BD_DENS_PARAM, 0.0, 1.0, 0.5)); addParam(ParamWidget::create(Vec(79.5, 217.65), module, UGraph::SN_DENS_PARAM, 0.0, 1.0, 0.5)); addParam(ParamWidget::create(Vec(115.5, 217.65), module, UGraph::HH_DENS_PARAM, 0.0, 1.0, 0.5)); addParam(ParamWidget::create(Vec(108.5, 30.15), module, UGraph::SWING_PARAM, 0.0, 0.9, 0.0)); addInput(Port::create(Vec(8.0, 35.5), Port::INPUT, module, UGraph::CLOCK_INPUT)); addInput(Port::create(Vec(8.0, 214), Port::INPUT, module, UGraph::RESET_INPUT)); addInput(Port::create(Vec(42.5, 186), Port::INPUT, module, UGraph::MAPX_CV)); addInput(Port::create(Vec(78.5, 186), Port::INPUT, module, UGraph::MAPY_CV)); addInput(Port::create(Vec(114.5, 186), Port::INPUT, module, UGraph::CHAOS_CV)); addInput(Port::create(Vec(42.5, 261.5), Port::INPUT, module, UGraph::BD_FILL_CV)); addInput(Port::create(Vec(78.5, 261.5), Port::INPUT, module, UGraph::SN_FILL_CV)); addInput(Port::create(Vec(114.5, 261.5), Port::INPUT, module, UGraph::HH_FILL_CV)); addInput(Port::create(Vec(78.5, 35.5), Port::INPUT, module, UGraph::SWING_CV)); addInput(Port::create(Vec(8.0, 133.35), Port::INPUT, module, UGraph::RUN_INPUT)); addOutput(Port::create(Vec(42.5, 299.736), Port::OUTPUT, module, UGraph::BD_OUTPUT)); addOutput(Port::create(Vec(78.5, 299.736), Port::OUTPUT, module, UGraph::SN_OUTPUT)); addOutput(Port::create(Vec(114.5, 299.736), Port::OUTPUT, module, UGraph::HH_OUTPUT)); addOutput(Port::create(Vec(42.5, 327.736), Port::OUTPUT, module, UGraph::BD_ACC_OUTPUT)); addOutput(Port::create(Vec(78.5, 327.736), Port::OUTPUT, module, UGraph::SN_ACC_OUTPUT)); addOutput(Port::create(Vec(114.5, 327.736), Port::OUTPUT, module, UGraph::HH_ACC_OUTPUT)); addChild(ModuleLightWidget::create>(Vec(50.1, 247), module, UGraph::BD_LIGHT)); addChild(ModuleLightWidget::create>(Vec(86.1, 247), module, UGraph::SN_LIGHT)); addChild(ModuleLightWidget::create>(Vec(122.1, 247), module, UGraph::HH_LIGHT)); addParam(ParamWidget::create(Vec(12.1, 189.6), module, UGraph::RESET_BUTTON_PARAM, 0.0, 1.0, 0.0)); addChild(ModuleLightWidget::create>(Vec(14.5, 192), module, UGraph::RESET_LIGHT)); addParam(ParamWidget::create(Vec(12.1, 107), module, UGraph::RUN_BUTTON_PARAM, 0.0, 1.0, 0.0)); addChild(ModuleLightWidget::create>(Vec(14.5, 109.5), module, UGraph::RUNNING_LIGHT)); std::vector seqModeItems(seqModeItemText, seqModeItemText + 3); addChild(createDynamicChoice(Vec(90, 88), 55.f, seqModeItems, &module->sequencerModeChoice, nullptr, ACTIVE_LOW_VIEW)); std::vector clockResItems(clockResText, clockResText + 3); addChild(createDynamicChoice(Vec(90, 111), 55.f, clockResItems, &module->clockResChoice, nullptr, ACTIVE_LOW_VIEW)); } struct UGraphPanelStyleItem : MenuItem { UGraph* module; int panelStyle; void onAction(EventAction &e) override { module->panelStyle = panelStyle; } void step() override { rightText = (module->panelStyle == panelStyle) ? "✔" : ""; MenuItem::step(); } }; struct UGraphTriggerOutputModeItem : MenuItem { UGraph* module; UGraph::TriggerOutputMode triggerOutputMode; void onAction(EventAction &e) override { module->triggerOutputMode = triggerOutputMode; } void step() override { rightText = (module->triggerOutputMode == triggerOutputMode) ? "✔" : ""; MenuItem::step(); } }; struct UGraphAccOutputModeItem : MenuItem { UGraph* module; UGraph::AccOutputMode accOutputMode; void onAction(EventAction &e) override { module->accOutputMode = accOutputMode; switch(accOutputMode) { case UGraph::INDIVIDUAL_ACCENTS: module->grids.setAccentAltMode(false); break; case UGraph::ACC_CLK_RST: module->grids.setAccentAltMode(true); } } void step() override { rightText = (module->accOutputMode == accOutputMode) ? "✔" : ""; MenuItem::step(); } }; struct UGraphRunModeItem : MenuItem { UGraph* module; UGraph::RunMode runMode; void onAction(EventAction &e) override { module->runMode = runMode; } void step() override { rightText = (module->runMode == runMode) ? "✔" : ""; MenuItem::step(); } }; void UGraphWidget::appendContextMenu(Menu *menu) { UGraph *module = dynamic_cast(this->module); assert(module); // Panel style menu->addChild(construct()); menu->addChild(construct(&MenuLabel::text, "Panel style")); menu->addChild(construct(&MenuItem::text, "Dark", &UGraphPanelStyleItem::module, module, &UGraphPanelStyleItem::panelStyle, 0)); menu->addChild(construct(&MenuItem::text, "Light", &UGraphPanelStyleItem::module, module, &UGraphPanelStyleItem::panelStyle, 1)); // Trigger Output Modes menu->addChild(construct()); menu->addChild(construct(&MenuLabel::text, "Trigger Output Mode")); menu->addChild(construct(&MenuItem::text, "1ms Pulse", &UGraphTriggerOutputModeItem::module, module, &UGraphTriggerOutputModeItem::triggerOutputMode, UGraph::PULSE)); menu->addChild(construct(&MenuItem::text, "Gate", &UGraphTriggerOutputModeItem::module, module, &UGraphTriggerOutputModeItem::triggerOutputMode, UGraph::GATE)); // Acc Output Modes menu->addChild(construct()); menu->addChild(construct(&MenuLabel::text, "Accent Output Mode")); menu->addChild(construct(&MenuItem::text, "Individual accents", &UGraphAccOutputModeItem::module, module, &UGraphAccOutputModeItem::accOutputMode, UGraph::INDIVIDUAL_ACCENTS)); menu->addChild(construct(&MenuItem::text, "Accent / Clock / Reset", &UGraphAccOutputModeItem::module, module, &UGraphAccOutputModeItem::accOutputMode, UGraph::ACC_CLK_RST)); // Run Mode menu->addChild(construct()); menu->addChild(construct(&MenuLabel::text, "Run Mode")); menu->addChild(construct(&MenuItem::text, "Toggle", &UGraphRunModeItem::module, module, &UGraphRunModeItem::runMode, UGraph::RunMode::TOGGLE)); menu->addChild(construct(&MenuItem::text, "Momentary", &UGraphRunModeItem::module, module, &UGraphRunModeItem::runMode, UGraph::RunMode::MOMENTARY)); } } // namespace rack_plugin_Valley using namespace rack_plugin_Valley; RACK_PLUGIN_MODEL_INIT(Valley, UGraph) { Model *modelUGraph = Model::create(TOSTRING(SLUG), "uGraph", "uGraph", SEQUENCER_TAG); return modelUGraph; }