#include "AH.hpp" #include "Core.hpp" #include "UI.hpp" #include "componentlibrary.hpp" #include "dsp/digital.hpp" #include namespace rack_plugin_AmalgamatedHarmonics { struct Arpeggio { virtual std::string getName() = 0; virtual void initialise(int nPitches, int offset) = 0; virtual void advance() = 0; virtual int getPitch() = 0; virtual bool isArpeggioFinished() = 0; // For RL and LR arps we have the following logic // Convert from npitch (1-6) to index (0 -> 9), but do no repeat first note // 1,2,3,4,5,6 (6) -> // 0 (1) // 1 (2) // 2 (3) // 3 (4) // 4 (5) // 5 (6) // 6 (5) // 7 (3) // 8 (2) // 9 (END, do not repeat 1) }; struct RightArp : Arpeggio { int index = 0; int nPitches = 0; std::string getName() override { return "Right"; }; void initialise(int np, int offset) override { if (offset != 0) { index = offset % np; } else { index = 0; } nPitches = np; // std::cout << nPitches << " " << offset << " " << index << std::endl; } void advance() override { index++; } int getPitch() override { return index; } bool isArpeggioFinished() override { return (index >= nPitches - 1); } }; struct LeftArp : Arpeggio { int index = 0; int nPitches = 0; std::string getName() override { return "Left"; }; void initialise(int np, int offset) override { if (offset != 0) { offset = offset % np; index = np - offset - 1; } else { index = np - 1; } nPitches = np; // std::cout << nPitches << " " << offset << " " << index << std::endl; } void advance() override { index--; } int getPitch() override { return index; } bool isArpeggioFinished() override { return (index == 0); } }; struct RightLeftArp : Arpeggio { int currSt = 0; int mag = 0; // index of last pitch int end = 0; // index of end of arp int nPitches = 0; std::string getName() override { return "RightLeft"; }; void initialise(int np, int offset) override { nPitches = np; mag = np - 1; end = 2 * mag - 1; if (end < 1) { end = 1; } currSt = offset; if (end < currSt) { end = currSt; } else { if (offset > 0) { end++; } } } void advance() override { currSt++; } int getPitch() override { int p = abs((mag - abs(mag - currSt)) % nPitches); return p; } bool isArpeggioFinished() override { return(currSt == end); } }; struct LeftRightArp : Arpeggio { int currSt = 0; int mag = 0; int end = 0; int nPitches = 0; std::string getName() override { return "LeftRight"; }; void initialise(int np, int offset) override { nPitches = np; mag = np - 1; end = 2 * mag - 1; if (end < 1) { end = 1; } currSt = offset; if (end < currSt) { end = currSt; } else { if (offset > 0) { end++; } } } void advance() override { currSt++; } int getPitch() override { int p = abs(abs(mag - currSt) % nPitches); return p; } bool isArpeggioFinished() override { return(currSt == end); } }; struct Arp31 : AHModule { const static int MAX_STEPS = 16; const static int MAX_DIST = 12; //Octave const static int NUM_PITCHES = 6; enum ParamIds { ARP_PARAM, OFFSET_PARAM, NUM_PARAMS }; enum InputIds { CLOCK_INPUT, ENUMS(PITCH_INPUT,6), ARP_INPUT, NUM_INPUTS }; enum OutputIds { OUT_OUTPUT, GATE_OUTPUT, EOC_OUTPUT, NUM_OUTPUTS }; enum LightIds { ENUMS(CURR_LIGHT,6), NUM_LIGHTS }; Arp31() : AHModule(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS) { reset(); id = rand(); debugFlag = false; } void step() override; void reset() override { isRunning = false; } json_t *toJson() override { json_t *rootJ = json_object(); // gateMode json_t *gateModeJ = json_integer((int) gateMode); json_object_set_new(rootJ, "gateMode", gateModeJ); return rootJ; } void fromJson(json_t *rootJ) override { // gateMode json_t *gateModeJ = json_object_get(rootJ, "gateMode"); if (gateModeJ) { gateMode = (GateMode)json_integer_value(gateModeJ); } } enum GateMode { TRIGGER, RETRIGGER, CONTINUOUS, }; GateMode gateMode = TRIGGER; SchmittTrigger clockTrigger; // for clock PulseGenerator gatePulse; PulseGenerator eocPulse; bool locked = false; float outVolts = 0; bool isRunning = false; int error = 0; int inputArp = 0; int arp = 0; int poll = 5000; float semiTone = 1.0 / 12.0; RightArp arp_right; LeftArp arp_left; RightLeftArp arp_rightleft; LeftRightArp arp_leftright; RightArp ui_arp_right; LeftArp ui_arp_left; RightLeftArp ui_arp_rightleft; LeftRightArp ui_arp_leftright; Arpeggio *currArp = &arp_right; Arpeggio *uiArp = &arp_right; float pitches[6]; int pitchIndex[NUM_PITCHES]; int nPitches = 0; int id = 0; int currLight = 0; }; void Arp31::step() { AHModule::step(); // Wait a few steps for the inputs to flow through Rack if (stepX < 10) { return; } // Get inputs from Rack float clockInput = inputs[CLOCK_INPUT].value; bool clockActive = inputs[CLOCK_INPUT].active; if (inputs[ARP_INPUT].active) { inputArp = inputs[ARP_INPUT].value; } else { inputArp = params[ARP_PARAM].value; } int offset = params[OFFSET_PARAM].value; // Process inputs bool clockStatus = clockTrigger.process(clockInput); // If there is no clock input, then force that we are not running if (!clockActive) { isRunning = false; } bool restart = false; int oldLight = 0; // Have we been clocked? if (clockStatus) { // If we are already running, process cycle if (isRunning) { if (debugEnabled()) { std::cout << stepX << " " << id << " Advance Cycle: " << currArp->getPitch() << " " << pitches[currArp->getPitch()] << std::endl; } // Reached the end of the pattern? if (currArp->isArpeggioFinished()) { // Pulse the EOC gate eocPulse.trigger(Core::TRIGGER); if (debugEnabled()) { std::cout << stepX << " " << id << " Finished Cycle" << std::endl; } restart = true; } // Finally set the out voltage int i = currArp->getPitch(); outVolts = clamp(pitches[i], -10.0f, 10.0f); oldLight = currLight; currLight = pitchIndex[i]; if (debugEnabled()) { std::cout << stepX << " " << id << " Index: " << i << " V: " << outVolts << " Light: " << currLight << std::endl; } // Pulse the output gate gatePulse.trigger(Core::TRIGGER); // Completed 1 step currArp->advance(); } else { // Start a cycle restart = true; } } // If we have been triggered, start a new sequence if (restart) { // Read input pitches and assign to pitch array int nValidPitches = 0; float inputPitches[NUM_PITCHES]; for (int p = 0; p < NUM_PITCHES; p++) { int index = PITCH_INPUT + p; if (inputs[index].active) { inputPitches[nValidPitches] = inputs[index].value; pitchIndex[nValidPitches] = p; nValidPitches++; } else { inputPitches[nValidPitches] = 0.0; pitchIndex[nValidPitches] = 0; } } // if (debugEnabled()) { // for (int p = 0; p < nValidPitches; p++) { // std::cout << inputPitches[p] << std::endl; // } // } // Always play something if (nValidPitches == 0) { if (debugEnabled()) { std::cout << stepX << " " << id << " No inputs, assume single 0V pitch" << std::endl; } inputPitches[0] = 0.0; pitchIndex[0] = 0; nValidPitches = 1; } // At the first step of the cycle // So this is where we tweak the cycle parameters arp = inputArp; switch(arp) { case 0: currArp = &arp_right; break; case 1: currArp = &arp_left; break; case 2: currArp = &arp_rightleft; break; case 3: currArp = &arp_leftright; break; default: currArp = &arp_right; break; }; // Copy pitches for (int p = 0; p < nValidPitches; p++) { pitches[p] = inputPitches[p]; } nPitches = nValidPitches; if (debugEnabled()) { std::cout << stepX << " " << id << " Initiatise new Cycle: Pattern: " << currArp->getName() << " nPitches: " << nPitches << std::endl; } currArp->initialise(nPitches, offset); // Start isRunning = true; } // Update UI switch(inputArp) { case 0: uiArp = &ui_arp_right; break; case 1: uiArp = &ui_arp_left; break; case 2: uiArp = &ui_arp_rightleft; break; case 3: uiArp = &ui_arp_leftright; break; default: uiArp = &ui_arp_right; break; }; // uiArp->initialise(nValidPitches, 1); // Set the value outputs[OUT_OUTPUT].value = outVolts; // Set the light lights[CURR_LIGHT + oldLight].value = 0.0; lights[CURR_LIGHT + currLight].value = 1.0; bool gPulse = gatePulse.process(delta); bool cPulse = eocPulse.process(delta); bool gatesOn = isRunning; if (gateMode == TRIGGER) { gatesOn = gatesOn && gPulse; } else if (gateMode == RETRIGGER) { gatesOn = gatesOn && !gPulse; } outputs[GATE_OUTPUT].value = gatesOn ? 10.0 : 0.0; outputs[EOC_OUTPUT].value = cPulse ? 10.0 : 0.0; } struct Arp31Display : TransparentWidget { Arp31 *module; int frame = 0; std::shared_ptr font; Arp31Display() { font = Font::load(assetPlugin(plugin, "res/EurostileBold.ttf")); } void draw(NVGcontext *vg) override { Vec pos = Vec(0, 15); nvgFontSize(vg, 16); nvgFontFaceId(vg, font->handle); nvgTextLetterSpacing(vg, -1); nvgFillColor(vg, nvgRGBA(255, 0, 0, 0xff)); char text[128]; snprintf(text, sizeof(text), "%s", module->uiArp->getName().c_str()); nvgText(vg, pos.x + 10, pos.y + 65, text, NULL); } }; struct Arp31Widget : ModuleWidget { Arp31Widget(Arp31 *module); Menu *createContextMenu() override; }; Arp31Widget::Arp31Widget(Arp31 *module) : ModuleWidget(module) { UI ui; box.size = Vec(135, 380); { SVGPanel *panel = new SVGPanel(); panel->box.size = box.size; panel->setBackground(SVG::load(assetPlugin(plugin, "res/Arp31c.svg"))); addChild(panel); } { Arp31Display *display = new Arp31Display(); display->module = module; display->box.pos = Vec(40, 100); display->box.size = Vec(100, 70); addChild(display); } addOutput(Port::create(ui.getPosition(UI::PORT, 0, 5, true, false), Port::OUTPUT, module, Arp31::OUT_OUTPUT)); addOutput(Port::create(ui.getPosition(UI::PORT, 1, 5, true, false), Port::OUTPUT, module, Arp31::GATE_OUTPUT)); addOutput(Port::create(ui.getPosition(UI::PORT, 2, 5, true, false), Port::OUTPUT, module, Arp31::EOC_OUTPUT)); for (int i = 0; i < 3; i++) { addInput(Port::create(ui.getPosition(UI::PORT, i, 0, true, false), Port::INPUT, module, Arp31::PITCH_INPUT + i)); Vec v = ui.getPosition(UI::LIGHT, i, 1, true, false); v.x = v.x + 2; v.y = 75; addChild(ModuleLightWidget::create>(v, module, Arp31::CURR_LIGHT + i)); } for (int i = 3; i < 6; i++) { addInput(Port::create(ui.getPosition(UI::PORT, i - 3, 1, true, false), Port::INPUT, module, Arp31::PITCH_INPUT + i)); Vec v = ui.getPosition(UI::LIGHT, i - 3, 2, true, false); v.x = v.x + 2; v.y = 131; addChild(ModuleLightWidget::create>(v, module, Arp31::CURR_LIGHT + i)); } addInput(Port::create(ui.getPosition(UI::PORT, 0, 4, true, false), Port::INPUT, module, Arp31::CLOCK_INPUT)); addParam(ParamWidget::create(ui.getPosition(UI::KNOB, 1, 4, true, false), module, Arp31::OFFSET_PARAM, 0.0, 10.0, 0.0)); addParam(ParamWidget::create(ui.getPosition(UI::KNOB, 0, 2, true, false), module, Arp31::ARP_PARAM, 0.0, 3.0, 0.0)); addInput(Port::create(ui.getPosition(UI::PORT, 0, 3, true, false), Port::INPUT, module, Arp31::ARP_INPUT)); } struct ArpGateModeItem : MenuItem { Arp31 *arp; Arp31::GateMode gateMode; void onAction(EventAction &e) override { arp->gateMode = gateMode; } void step() override { rightText = (arp->gateMode == gateMode) ? "✔" : ""; } }; Menu *Arp31Widget::createContextMenu() { Menu *menu = ModuleWidget::createContextMenu(); MenuLabel *spacerLabel = new MenuLabel(); menu->addChild(spacerLabel); Arp31 *arp = dynamic_cast(module); assert(arp); MenuLabel *modeLabel = new MenuLabel(); modeLabel->text = "Gate Mode"; menu->addChild(modeLabel); ArpGateModeItem *triggerItem = new ArpGateModeItem(); triggerItem->text = "Trigger"; triggerItem->arp = arp; triggerItem->gateMode = Arp31::TRIGGER; menu->addChild(triggerItem); ArpGateModeItem *retriggerItem = new ArpGateModeItem(); retriggerItem->text = "Retrigger"; retriggerItem->arp = arp; retriggerItem->gateMode = Arp31::RETRIGGER; menu->addChild(retriggerItem); ArpGateModeItem *continuousItem = new ArpGateModeItem(); continuousItem->text = "Continuous"; continuousItem->arp = arp; continuousItem->gateMode = Arp31::CONTINUOUS; menu->addChild(continuousItem); return menu; } } // namespace rack_plugin_AmalgamatedHarmonics using namespace rack_plugin_AmalgamatedHarmonics; RACK_PLUGIN_MODEL_INIT(AmalgamatedHarmonics, Arp31) { Model *modelArp31 = Model::create( "Amalgamated Harmonics", "Arp31", "Arp 3.1 - Chord", ARPEGGIATOR_TAG); return modelArp31; }