#include "pwmClock.hpp" namespace rack_plugin_TheXOR { #define BPM_MINVALUE (10) #define BPM_MAXVALUE (300) #define PWM_MINVALUE (0.05) #define PWM_MAXVALUE (0.95) #define SWING_MINVALUE (0.0) #define SWING_MAXVALUE (0.5) void PwmClock::on_loaded() { bpm = 0; swing = 0; _reset(); load(); } void PwmClock::_reset() { for(int k = 0; k < OUT_SOCKETS; k++) { sa_timer[k].Reset(); odd_beat[k] = false; } } void PwmClock::load() { updateBpm(); } void PwmClock::updateBpm() { bool updated = false; float new_bpm; if(inputs[EXT_BPM].active) new_bpm = rescale(inputs[EXT_BPM].value, LVL_OFF, LVL_ON, BPM_MINVALUE, BPM_MAXVALUE); else new_bpm = (roundf(params[BPMDEC].value) + 10 * bpm_integer) / 10.0; if(bpm != new_bpm) { updated = true; bpm = new_bpm; duration[0] = 240.0 / bpm; // 1/1 duration[1] = duration[0] + duration[0] / 2.0; duration[2] = 2.0* duration[0] / 3.0; for(int k = 1; k < 7; k++) { duration[3 * k] = duration[3 * (k - 1)] / 2.0; duration[3 * k + 1] = duration[3 * (k - 1) + 1] / 2.0; duration[3 * k + 2] = duration[3 * (k - 1) + 2] / 2.0; } } float new_swing = getSwing(); if(updated || new_swing != swing) { swing = new_swing; for(int k = 0; k < OUT_SOCKETS; k++) swingAmt[k] = duration[k] + duration[k] * swing; } } void PwmClock::process_keys() { if(btnup.process(params[BPM_INC].value)) { if(bpm_integer < BPM_MAXVALUE) bpm_integer += 1; pWidget->SetBpm(bpm_integer); } if(btndwn.process(params[BPM_DEC].value)) { if(bpm_integer > 0) bpm_integer -= 1; pWidget->SetBpm(bpm_integer); } } void PwmClock::step() { process_keys(); bpm_integer = roundf(params[BPM].value); updateBpm(); if((params[OFFON].value + inputs[OFFON_IN].value) > 0.5) { lights[ACTIVE].value = LVL_ON; if(resetTrigger.process(inputs[RESET].value)) { _reset(); } else { for(int k = 0; k < OUT_SOCKETS; k++) { float gate_len = getDuration(k) * getPwm(); sa_timer[k].Step(); float elps = sa_timer[k].Elapsed(); if(elps >= getDuration(k)) { elps = sa_timer[k].Reset(); odd_beat[k] = !odd_beat[k]; } if(elps <= gate_len) outputs[OUT_1 + k].value = LVL_ON; else outputs[OUT_1 + k].value = LVL_OFF; } } } else { for(int k = 0; k < OUT_SOCKETS; k++) { outputs[OUT_1 + k].value = LVL_OFF; } lights[ACTIVE].value = LVL_OFF; } } float PwmClock::getPwm() { float offs = inputs[PWM_IN].active ? rescale(inputs[PWM_IN].value, 0.0, 5.0, PWM_MINVALUE, PWM_MAXVALUE) : 0.0; return clamp(offs + params[PWM].value, PWM_MINVALUE, PWM_MAXVALUE); } float PwmClock::getSwing() { float offs = inputs[SWING_IN].active ? rescale(inputs[SWING_IN].value, 0.0, 5.0, SWING_MINVALUE, SWING_MAXVALUE) : 0.0; return clamp(offs + params[SWING].value, SWING_MINVALUE, SWING_MAXVALUE); } PwmClockWidget::PwmClockWidget(PwmClock *module) : SequencerWidget(module) { module->setWidget(this); box.size = Vec(15 * RACK_GRID_WIDTH, RACK_GRID_HEIGHT); { SVGPanel *panel = new SVGPanel(); panel->box.size = box.size; panel->setBackground(SVG::load(assetPlugin(plugin, "res/modules/PwmClock.svg"))); addChild(panel); } addChild(Widget::create(Vec(15, 0))); addChild(Widget::create(Vec(box.size.x - 30, 0))); addChild(Widget::create(Vec(15, 365))); addChild(Widget::create(Vec(box.size.x - 30, 365))); addParam(ParamWidget::create(Vec(mm2px(7.572), yncscape(104.588,4.115)), module, PwmClock::BPM_INC, 0.0, 1.0, 0.0)); addParam(ParamWidget::create(Vec(mm2px(7.572), yncscape(99.788, 4.115)), module, PwmClock::BPM_DEC, 0.0, 1.0, 0.0)); SigDisplayWidget *display = new SigDisplayWidget(4, 1); display->box.pos = Vec(mm2px(20), RACK_GRID_HEIGHT-mm2px(108)); display->box.size = Vec(30+53, 24); display->value = &module->bpm; addChild(display); ParamWidget *pw = ParamWidget::create(Vec(mm2px(50.364), yncscape(100.245, 8)), module, PwmClock::BPMDEC, 0.0, 9.0, 0.0); ((Davies1900hKnob *)pw)->snap = true; addParam(pw); pw = ParamWidget::create(Vec(mm2px(62.528), yncscape(99.483, 9.525)), module, PwmClock::BPM, BPM_MINVALUE, BPM_MAXVALUE, 120.0); ((Davies1900hKnob *)pw)->snap = true; addParam(pw); addInput(Port::create(Vec(mm2px(50.326), yncscape(86.857, 8.255)), Port::INPUT, module, PwmClock::EXT_BPM)); addInput(Port::create(Vec(mm2px(63.162), yncscape(86.857, 8.255)), Port::INPUT, module, PwmClock::RESET)); addParam(ParamWidget::create(Vec(mm2px(49.040), yncscape(64.997, 9.488)), module, PwmClock::OFFON, 0.0, 1.0, 0.0)); addChild(ModuleLightWidget::create>(Vec(mm2px(59.141), yncscape(67.715, 2.176)), module, PwmClock::ACTIVE)); addInput(Port::create(Vec(mm2px(63.162), yncscape(64.675, 8.255)), Port::INPUT, module, PwmClock::OFFON_IN)); addParam(ParamWidget::create(Vec(mm2px(48.511), yncscape(42.040, 9.525)), module, PwmClock::SWING, SWING_MINVALUE, SWING_MAXVALUE, SWING_MINVALUE)); addInput(Port::create(Vec(mm2px(63.162), yncscape(42.675, 8.255)), Port::INPUT, module, PwmClock::SWING_IN)); addParam(ParamWidget::create(Vec(mm2px(48.511), yncscape(20.040, 9.525)), module, PwmClock::PWM, PWM_MINVALUE, PWM_MAXVALUE, 0.5)); addInput(Port::create(Vec(mm2px(63.162), yncscape(20.675, 8.255)), Port::INPUT, module, PwmClock::PWM_IN)); float col_x[3] = {7.875, 21.633, 35.392}; float pos_y = yncscape(70.175, 8.255); int col = 0; for(int k = 0; k < OUT_SOCKETS; k++) { addOutput(Port::create(Vec(mm2px(col_x[col]), pos_y), Port::OUTPUT, module, PwmClock::OUT_1 + k)); if(++col >= 3) { col = 0; pos_y += mm2px(11); } } } void PwmClockWidget::SetBpm(float bpm_integer) { int index = getParamIndex(PwmClock::BPM); if(index >= 0) { // VCV interface update is ahem.... migliorabile.... bool smooth = params[index]->smooth; params[index]->smooth = false; params[index]->setValue((double)bpm_integer); params[index]->smooth = smooth; } } } // namespace rack_plugin_TheXOR using namespace rack_plugin_TheXOR; RACK_PLUGIN_MODEL_INIT(TheXOR, PwmClock) { return Model::create("TheXOR", "PWMClock", "PWM Clock", CLOCK_TAG); }