#include "RJModules.hpp" #include "dsp/digital.hpp" #include "common.hpp" #include #include #include #include namespace rack_plugin_RJModules { struct LowFrequencyOscillator { float phase = 0.0; float pw = 0.5; float freq = 1.0; bool offset = false; bool invert = false; SchmittTrigger resetTrigger; LowFrequencyOscillator() {} void setPitch(float pitch) { pitch = fminf(pitch, 8.0); freq = powf(2.0, pitch); } void setPulseWidth(float pw_) { const float pwMin = 0.01; pw = clamp(pw_, pwMin, 1.0 - pwMin); } void setReset(float reset) { if (resetTrigger.process(reset)) { phase = 0.0; } } void step(float dt) { float deltaPhase = fminf(freq * dt, 0.5); phase += deltaPhase; if (phase >= 1.0) phase -= 1.0; } float sin() { if (offset) return 1.0 - cosf(2*M_PI * phase) * (invert ? -1.0 : 1.0); else return sinf(2*M_PI * phase) * (invert ? -1.0 : 1.0); } float tri(float x) { return 4.0 * fabsf(x - roundf(x)); } float tri() { if (offset) return tri(invert ? phase - 0.5 : phase); else return -1.0 + tri(invert ? phase - 0.25 : phase - 0.75); } float saw(float x) { return 2.0 * (x - roundf(x)); } float saw() { if (offset) return invert ? 2.0 * (1.0 - phase) : 2.0 * phase; else return saw(phase) * (invert ? -1.0 : 1.0); } float sqr() { float sqr = (phase < pw) ^ invert ? 1.0 : -1.0; return offset ? sqr + 1.0 : sqr; } float light() { return sinf(2*M_PI * phase); } }; /* Display */ struct SmallIntegerDisplayWidgeter : TransparentWidget { float *value; std::shared_ptr font; SmallIntegerDisplayWidgeter() { 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, 16); nvgFontFaceId(vg, font->handle); nvgTextLetterSpacing(vg, 0.5); std::stringstream to_display; to_display = format4display(*value); Vec textPos = Vec(8.0f, 33.0f); NVGcolor textColor = nvgRGB(0x00, 0x00, 0x00); nvgFillColor(vg, textColor); nvgText(vg, textPos.x, textPos.y, to_display.str().substr(0, 4).c_str(), NULL); nvgFontSize(vg, 8); textPos = Vec(1.0f, (*value<0?28.0f:32.0f)); nvgText(vg, textPos.x, textPos.y, (*value<0?"-":"+"), NULL); } }; /* Widget */ struct RangeLFO : Module { enum ParamIds { OFFSET_PARAM, INVERT_PARAM, FREQ_PARAM, FM1_PARAM, FM2_PARAM, PW_PARAM, PWM_PARAM, NUM_PARAMS, CH1_PARAM, CH2_PARAM, }; enum InputIds { FM1_INPUT, FM2_INPUT, RESET_INPUT, PW_INPUT, RATE_CV_INPUT, FROM_CV_INPUT, TO_CV_INPUT, NUM_INPUTS }; enum OutputIds { SIN_OUTPUT, TRI_OUTPUT, SAW_OUTPUT, SQR_OUTPUT, NUM_OUTPUTS }; enum LightIds { PHASE_POS_LIGHT, PHASE_NEG_LIGHT, NUM_LIGHTS }; LowFrequencyOscillator oscillator; float display1_val; float display2_val; float display3_val; float display4_val; float display5_val; float display6_val; RangeLFO() : Module(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS) {} void step() override; }; void RangeLFO::step() { // display display1_val = params[CH1_PARAM].value * clamp(inputs[FROM_CV_INPUT].normalize(10.0f) / 10.0f, -1.0f, 1.0f); display2_val = params[CH2_PARAM].value * clamp(inputs[TO_CV_INPUT].normalize(10.0f) / 10.0f, -1.0f, 1.0f); float osc_pitch = params[FREQ_PARAM].value + params[FM1_PARAM].value * inputs[FM1_INPUT].value + params[FM2_PARAM].value * inputs[FM2_INPUT].value; osc_pitch = osc_pitch * clamp(inputs[RATE_CV_INPUT].normalize(10.0f) / 10.0f, 0.0f, 1.0f); oscillator.setPitch(osc_pitch); oscillator.setPulseWidth(params[PW_PARAM].value + params[PWM_PARAM].value * inputs[PW_INPUT].value / 10.0); oscillator.offset = (params[OFFSET_PARAM].value > 0.0); oscillator.invert = (params[INVERT_PARAM].value <= 0.0); oscillator.step(1.0 / engineGetSampleRate()); oscillator.setReset(inputs[RESET_INPUT].value); float sin = oscillator.sin(); float tri = oscillator.tri(); float saw = oscillator.saw(); float sqr = oscillator.sqr(); // new_value = ( (old_value - old_min) / (old_max - old_min) ) * (new_max - new_min) + new_min float sin_output = ( (sin - (-1)) / (1 - (-1)) ) * (display2_val - display1_val) + display1_val; float tri_output = ( (tri - (-1)) / (1 - (-1)) ) * (display2_val - display1_val) + display1_val; float saw_output = ( (saw - (-1)) / (1 - (-1)) ) * (display2_val - display1_val) + display1_val; float sqr_output = ( (sqr - (-1)) / (1 - (-1)) ) * (display2_val - display1_val) + display1_val; outputs[SIN_OUTPUT].value = sin_output; outputs[TRI_OUTPUT].value = tri_output; outputs[SAW_OUTPUT].value = saw_output; outputs[SQR_OUTPUT].value = sqr_output; lights[PHASE_POS_LIGHT].setBrightnessSmooth(fmaxf(0.0, oscillator.light())); lights[PHASE_NEG_LIGHT].setBrightnessSmooth(fmaxf(0.0, -oscillator.light())); } struct RangeLFOWidget: ModuleWidget { RangeLFOWidget(RangeLFO *module); }; RangeLFOWidget::RangeLFOWidget(RangeLFO *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/RangeLFO.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))); SmallIntegerDisplayWidgeter *display = new SmallIntegerDisplayWidgeter(); display->box.pos = Vec(23, 160); display->box.size = Vec(50, 40); display->value = &module->display1_val; addChild(display); addParam(ParamWidget::create(Vec(28, 205), module, RangeLFO::CH1_PARAM, -12.0, 12.0, -12.0)); addInput(Port::create(Vec(5, 235), Port::INPUT, module, RangeLFO::FROM_CV_INPUT)); SmallIntegerDisplayWidgeter *display2 = new SmallIntegerDisplayWidgeter(); display2->box.pos = Vec(83, 160); display2->box.size = Vec(50, 40); display2->value = &module->display2_val; addChild(display2); addParam(ParamWidget::create(Vec(88, 205), module, RangeLFO::CH2_PARAM, -12.0, 12.0, 12.0)); addInput(Port::create(Vec(62, 235), Port::INPUT, module, RangeLFO::TO_CV_INPUT)); addParam(ParamWidget::create(Vec(47, 61), module, RangeLFO::FREQ_PARAM, -8.0, 6.0, -1.0)); // addParam(ParamWidget::create(Vec(23, 143), module, RangeLFO::FM1_PARAM, 0.0, 1.0, 0.0)); // addParam(ParamWidget::create(Vec(91, 143), module, RangeLFO::PW_PARAM, 0.0, 1.0, 0.5)); // addParam(ParamWidget::create(Vec(23, 208), module, RangeLFO::FM2_PARAM, 0.0, 1.0, 0.0)); // addParam(ParamWidget::create(Vec(91, 208), module, RangeLFO::PWM_PARAM, 0.0, 1.0, 0.0)); addInput(Port::create(Vec(22, 100), Port::INPUT, module, RangeLFO::RATE_CV_INPUT)); addInput(Port::create(Vec(11, 276), Port::INPUT, module, RangeLFO::FM1_INPUT)); addInput(Port::create(Vec(45, 276), Port::INPUT, module, RangeLFO::RESET_INPUT)); // addInput(Port::create(Vec(80, 276), Port::INPUT, module, RangeLFO::RESET_INPUT)); // addInput(Port::create(Vec(114, 276), Port::INPUT, module, RangeLFO::PW_INPUT)); addParam(ParamWidget::create(Vec(85, 276), module, RangeLFO::INVERT_PARAM, 0.0, 1.0, 0.0)); //addParam(ParamWidget::create(Vec(119, 276), module, RangeLFO::OFFSET_PARAM, 0.0, 1.0, 0.0)); addOutput(Port::create(Vec(11, 320), Port::OUTPUT, module, RangeLFO::SIN_OUTPUT)); addOutput(Port::create(Vec(45, 320), Port::OUTPUT, module, RangeLFO::TRI_OUTPUT)); addOutput(Port::create(Vec(80, 320), Port::OUTPUT, module, RangeLFO::SAW_OUTPUT)); addOutput(Port::create(Vec(114, 320), Port::OUTPUT, module, RangeLFO::SQR_OUTPUT)); addChild(ModuleLightWidget::create>(Vec(99, 60), module, RangeLFO::PHASE_POS_LIGHT)); } } // namespace rack_plugin_RJModules using namespace rack_plugin_RJModules; RACK_PLUGIN_MODEL_INIT(RJModules, RangeLFO) { Model *modelRangeLFO = Model::create("RJModules", "RangeLFO", "[GEN] RangeLFO", LFO_TAG); return modelRangeLFO; }