#include "RJModules.hpp" #include "dsp/digital.hpp" #include #include #include #include namespace rack_plugin_RJModules { /* Thank you to MSCHacks for the starting point on this one! Thanks to Strum for the display widget! */ struct BPM: Module { enum ParamIds { BPM_PARAM, RESET_PARAM, NUM_PARAMS }; enum InputIds { CH1_CV_INPUT, RESET_CV_INPUT, NUM_INPUTS }; enum OutputIds { CH1_OUTPUT, CH2_OUTPUT, CH3_OUTPUT, CH4_OUTPUT, CH5_OUTPUT, CH6_OUTPUT, NUM_OUTPUTS }; enum LightIds { RESET_LIGHT, PULSE_LIGHT, NUM_LIGHTS }; float resetLight = 0.0; float pulseLight = 0.0; int m_fBPM = 133.0; float m_fBeatsPers; float m_fMainClockCount; BPM() : Module(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS) {} void step() override; void BPMChange( float fbmp, bool bforce ); }; template struct BigOlLight : BASE { BigOlLight() { this->box.size = mm2px(Vec(34, 34)); } }; struct NumberDisplayWidget : TransparentWidget { int *value; std::shared_ptr font; NumberDisplayWidget() { font = Font::load(assetPlugin(plugin, "res/Segment7Standard.ttf")); }; void draw(NVGcontext *vg) override { // Background NVGcolor backgroundColor = nvgRGB(0xC0, 0xC0, 0xC0); nvgBeginPath(vg); nvgRoundedRect(vg, 0.0, 0.0, box.size.x, box.size.y, 4.0); nvgFillColor(vg, backgroundColor); nvgFill(vg); // text nvgFontSize(vg, 32); nvgFontFaceId(vg, font->handle); nvgTextLetterSpacing(vg, 2.5); std::stringstream to_display; to_display << std::setw(3) << (int) *value; Vec textPos = Vec(16.0f, 33.0f); NVGcolor textColor = nvgRGB(0x00, 0x00, 0x00); nvgFillColor(vg, textColor); nvgText(vg, textPos.x, textPos.y, to_display.str().c_str(), NULL); } }; void BPM::BPMChange( float fbpm, bool bforce ){ // // don't change if it is already the same if( !bforce && ( (int)(fbpm * 1000.0f ) == (int)(m_fBPM * 1000.0f ) ) ) return; m_fBPM = fbpm; m_fBeatsPers = fbpm / 60.0; } void BPM::step() { const float lightLambda = 0.075; float output = 0.0; SchmittTrigger resetTrigger; bool bMainClockTrig = false; // new_value = ( (old_value - old_min) / (old_max - old_min) ) * (new_max - new_min) + new_min float bpm_val = params[BPM_PARAM].value * clamp(inputs[CH1_CV_INPUT].normalize(10.0f) / 10.0f, 0.0f, 1.0f); float mapped_bpm = ((bpm_val - 0.0) / (1.0 - 0.0) ) * (600.0 - 40.0) + 40.0; m_fBPM = mapped_bpm; m_fMainClockCount += (mapped_bpm/60.0); if( ( m_fMainClockCount ) >= engineGetSampleRate() ) { m_fMainClockCount = m_fMainClockCount - engineGetSampleRate(); bMainClockTrig = true; } if( bMainClockTrig ) { output = 12.0; resetLight = 1.0; pulseLight = 1.0; } // Reset if (params[RESET_PARAM].value > 0 || inputs[RESET_CV_INPUT].value > 0) { resetLight = 1.0; output = 12.0; m_fMainClockCount = 0; } pulseLight -= pulseLight / lightLambda / engineGetSampleRate(); outputs[CH1_OUTPUT].value = output; outputs[CH2_OUTPUT].value = output; outputs[CH3_OUTPUT].value = output; outputs[CH4_OUTPUT].value = output; outputs[CH5_OUTPUT].value = output; outputs[CH6_OUTPUT].value = output; lights[PULSE_LIGHT].value = pulseLight; } struct BPMWidget: ModuleWidget { BPMWidget(BPM *module); }; BPMWidget::BPMWidget(BPM *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/BPM.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))); addInput(Port::create(Vec(24, 160), Port::INPUT, module, BPM::CH1_CV_INPUT)); addInput(Port::create(Vec(106, 165), Port::INPUT, module, BPM::RESET_CV_INPUT)); addParam(ParamWidget::create(Vec(109, 132), module, BPM::RESET_PARAM, 0.0, 1.0, 0.0)); addChild(ModuleLightWidget::create>(Vec(113.4, 136.4), module, BPM::RESET_LIGHT)); addOutput(Port::create(Vec(24, 223), Port::OUTPUT, module, BPM::CH1_OUTPUT)); addOutput(Port::create(Vec(65, 223), Port::OUTPUT, module, BPM::CH2_OUTPUT)); addOutput(Port::create(Vec(105, 223), Port::OUTPUT, module, BPM::CH3_OUTPUT)); addOutput(Port::create(Vec(24, 274), Port::OUTPUT, module, BPM::CH4_OUTPUT)); addOutput(Port::create(Vec(65, 274), Port::OUTPUT, module, BPM::CH5_OUTPUT)); addOutput(Port::create(Vec(106, 274), Port::OUTPUT, module, BPM::CH6_OUTPUT)); addParam(ParamWidget::create(Vec(58, 140), module, BPM::BPM_PARAM, 0.0, 1.0, 0.165)); // addChild(ModuleLightWidget::create>(Vec(28, 130), module, BPM::PULSE_LIGHT)); //addChild(ModuleLightWidget::create>(Vec(25, 70), module, BPM::RESET_LIGHT)); NumberDisplayWidget *display = new NumberDisplayWidget(); display->box.pos = Vec(28, 70); display->box.size = Vec(100, 40); display->value = &module->m_fBPM; addChild(display); } } // namespace rack_plugin_RJModules using namespace rack_plugin_RJModules; RACK_PLUGIN_MODEL_INIT(RJModules, BPM) { Model *modelBPM = Model::create("RJModules", "BPM", "[LIVE] BPM", UTILITY_TAG); return modelBPM; }