#include "ML_modules.hpp" #include "dsp/digital.hpp" #include "util/math.hpp" #include #include #include namespace rack_plugin_ML_modules { struct BPMdetect : Module { enum ParamIds { SMOOTH_PARAM, MULT2_PARAM, MULT3_PARAM, SWING2_PARAM, SWING3_PARAM, DELAY1_PARAM, DELAY2_PARAM, NUM_PARAMS }; enum InputIds { GATE_INPUT, NUM_INPUTS }; enum OutputIds { LFO_OUTPUT, SEQ_OUTPUT, DELAY_OUTPUT, TRIG1_OUTPUT, TRIG2_OUTPUT, TRIG3_OUTPUT, NUM_OUTPUTS }; enum LighIds { NUM_LIGHTS }; BPMdetect() : Module(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS) { misses = 0; onSampleRateChange();}; void step() override; int misses = 0; int count2 = 0; int count3 = 0; float timer = 0.0; float timer1 = 0.0; float timer2 = 0.0; float timer3 = 0.0; float seconds = 0.0; float deltaT; float BPM=0.0; float lfo_volts=0.0; float delay_volts=0.0; bool fine = false; inline bool checkBeat(float timer, int mult) { return ( ((timer - mult*seconds) * (timer - mult*seconds) / (seconds*seconds) < 0.2 ) && misses < 4); } float gSampleRate; void reset() override {onSampleRateChange();}; void onSampleRateChange() override {gSampleRate = engineGetSampleRate(); deltaT = 1.0/gSampleRate;} SchmittTrigger gateTrigger; PulseGenerator outPulse1, outPulse2, outPulse3; }; void BPMdetect::step() { float mult2 = roundf(params[MULT2_PARAM].value); float mult3 = roundf(params[MULT3_PARAM].value); float factor2 = ( fine ? 1.0f + 0.25f * (params[SWING2_PARAM].value - 1.0f): params[SWING2_PARAM].value ) / mult2; float factor3 = ( fine ? 1.0f + 0.25f * (params[SWING3_PARAM].value - 1.0f): params[SWING3_PARAM].value ) / mult3; if( inputs[GATE_INPUT].active) { if( timer1 > seconds ) { outPulse1.trigger(0.01); timer1 = 0.0; } if( (timer2 > seconds*factor2) /* && (count2 < mult2) */ ) { // if(nearf(factor2,1.0)) std::cerr << timer2 << "\n"; outPulse2.trigger(0.01); timer2 = 0.0; // count2++; } if( (timer3 > seconds*factor3) /* && (count30) { float new_seconds; bool found=false; for(int mult=1; !found && mult < 20; mult++ ) { if(checkBeat(timer, mult)) { new_seconds = timer/mult; if(mult==1) misses=0; else misses++; found = true; }; }; if( !found ) { // std::cerr << "default. misses = " << misses << "\n"; new_seconds = timer; misses=0; } float a = params[SMOOTH_PARAM].value; seconds = ( (1.0-a)*seconds + a*new_seconds); BPM=60.0/seconds; lfo_volts = 1.0 - log2(seconds) ; float num = roundf(params[DELAY1_PARAM].value); float denom = roundf(params[DELAY2_PARAM].value); delay_volts = 10.0*(3.0+log10(seconds * num/denom))/4.0; timer -= seconds; timer1 = 0.0; timer2 = 0.0; timer3 = 0.0; count2 = 1; count3 = 1; outPulse1.trigger(0.01); outPulse2.trigger(0.01); outPulse3.trigger(0.01); } }; }; timer += deltaT; timer1 += deltaT; timer2 += deltaT; timer3 += deltaT; outputs[TRIG1_OUTPUT].value = outPulse1.process(deltaT) ? 10.0 : 0.0; outputs[TRIG2_OUTPUT].value = outPulse2.process(deltaT) ? 10.0 : 0.0; outputs[TRIG3_OUTPUT].value = outPulse3.process(deltaT) ? 10.0 : 0.0; outputs[LFO_OUTPUT].value = lfo_volts; outputs[SEQ_OUTPUT].value = lfo_volts-3.0; outputs[DELAY_OUTPUT].value = delay_volts; }; struct NumberDisplayWidget2 : TransparentWidget { float *value; std::shared_ptr font; NumberDisplayWidget2() { font = Font::load(assetPlugin(plugin, "res/Segment7Standard.ttf")); }; void draw(NVGcontext *vg) { // Background NVGcolor backgroundColor = nvgRGB(0x20, 0x20, 0x20); NVGcolor borderColor = nvgRGB(0x10, 0x10, 0x10); nvgBeginPath(vg); nvgRoundedRect(vg, 0.0, 0.0, box.size.x, box.size.y, 4.0); nvgFillColor(vg, backgroundColor); nvgFill(vg); nvgStrokeWidth(vg, 1.0); nvgStrokeColor(vg, borderColor); nvgStroke(vg); nvgFontSize(vg, 18); nvgFontFaceId(vg, font->handle); nvgTextLetterSpacing(vg, 2.5); char display_string[10]; sprintf(display_string,"%6.1f",*value); Vec textPos = Vec(6.0f, 17.0f); NVGcolor textColor = nvgRGB(0xdf, 0xd2, 0x2c); nvgFillColor(vg, nvgTransRGBA(textColor, 16)); nvgText(vg, textPos.x, textPos.y, "~~~~~", NULL); textColor = nvgRGB(0xda, 0xe9, 0x29); nvgFillColor(vg, nvgTransRGBA(textColor, 16)); nvgText(vg, textPos.x, textPos.y, "\\\\\\\\\\", NULL); textColor = nvgRGB(0xf0, 0x00, 0x00); nvgFillColor(vg, textColor); nvgText(vg, textPos.x, textPos.y, display_string, NULL); } }; struct FineMenuItem : MenuItem { BPMdetect *module; bool mfine; void onAction(EventAction &e) override { module->fine = mfine; }; void step() override { rightText = (module->fine == mfine)? "✔" : ""; }; }; struct NormalMenuItem : MenuItem { BPMdetect *module; bool mfine; void onAction(EventAction &e) override { module->fine = mfine; }; void step() override { rightText = (module->fine != mfine)? "✔" : ""; }; }; struct BPMdetectWidget : ModuleWidget { BPMdetectWidget(BPMdetect *module); json_t *toJsonData() ; void fromJsonData(json_t *root) ; Menu *createContextMenu() override; }; Menu *BPMdetectWidget::createContextMenu() { Menu *menu = ModuleWidget::createContextMenu(); MenuLabel *spacerLabel = new MenuLabel(); menu->addChild(spacerLabel); BPMdetect *myModule = dynamic_cast(module); assert(myModule); MenuLabel *modeLabel2 = new MenuLabel(); modeLabel2->text = "Swing Range"; menu->addChild(modeLabel2); FineMenuItem *fineMenuItem = new FineMenuItem(); fineMenuItem->text = "Fine"; fineMenuItem->module = myModule; fineMenuItem->mfine = true; menu->addChild(fineMenuItem); NormalMenuItem *normalMenuItem = new NormalMenuItem(); normalMenuItem->text = "Legacy"; normalMenuItem->module = myModule; normalMenuItem->mfine = false; menu->addChild(normalMenuItem); return menu; }; BPMdetectWidget::BPMdetectWidget(BPMdetect *module) : ModuleWidget(module) { box.size = Vec(15*10, 380); { SVGPanel *panel = new SVGPanel(); panel->box.size = box.size; panel->setBackground(SVG::load(assetPlugin(plugin,"res/BPMdetect.svg"))); addChild(panel); } const float column1 = 15; const float column2 = 61; const float column3 = 110; const float row1 = 84; const float row2 = 140; const float row3 = row2 + 60; const float row4 = row3 + 58; const float row5 = 316; 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))); addInput(Port::create(Vec(column1+5, row1+2), Port::INPUT, module, BPMdetect::GATE_INPUT)); addParam(ParamWidget::create(Vec(column2, row1), module, BPMdetect::SMOOTH_PARAM, 0.0, 1.0, 0.5)); addOutput(Port::create(Vec(column3-5,row1+2), Port::OUTPUT, module, BPMdetect::TRIG1_OUTPUT)); addParam(ParamWidget::create(Vec(column1, row2), module, BPMdetect::MULT2_PARAM, 1.0, 8.0, 2.0)); addParam(ParamWidget::create(Vec(column2, row2), module, BPMdetect::SWING2_PARAM, 0.0, 2.0, 1.0)); addOutput(Port::create(Vec(column3, row2+2), Port::OUTPUT, module, BPMdetect::TRIG2_OUTPUT)); addParam(ParamWidget::create(Vec(column1, row3), module, BPMdetect::MULT3_PARAM, 1.0, 8.0, 3.0)); addParam(ParamWidget::create(Vec(column2, row3), module, BPMdetect::SWING3_PARAM, 0.0, 2.0, 1.0)); addOutput(Port::create(Vec(column3, row3+2), Port::OUTPUT, module, BPMdetect::TRIG3_OUTPUT)); addOutput(Port::create(Vec(column1, row4), Port::OUTPUT, module, BPMdetect::LFO_OUTPUT)); addOutput(Port::create(Vec(column3, row4), Port::OUTPUT, module, BPMdetect::SEQ_OUTPUT)); addParam(ParamWidget::create(Vec(column1, row5), module, BPMdetect::DELAY1_PARAM, 1.0, 8.0, 1.0)); addParam(ParamWidget::create(Vec(column2, row5), module, BPMdetect::DELAY2_PARAM, 1.0, 8.0, 1.0)); addOutput(Port::create(Vec(column3, row5), Port::OUTPUT, module, BPMdetect::DELAY_OUTPUT)); NumberDisplayWidget2 *display = new NumberDisplayWidget2(); display->box.pos = Vec(25,40); display->box.size = Vec(100, 20); display->value = &module->BPM; addChild(display); } } // namespace rack_plugin_ML_modules using namespace rack_plugin_ML_modules; RACK_PLUGIN_MODEL_INIT(ML_modules, BPMdetect) { Model *modelBPMdetect = Model::create("ML modules", "BPMdetect", "BPM Tools", UTILITY_TAG, CLOCK_TAG); return modelBPMdetect; }