//************************************************************************************** //Delay Plus module for VCV Rack by Alfredo Santamaria - AS - https://github.com/AScustomWorks/AS // //Code based on Fundamental plugins by Andrew Belt http://www.vcvrack.com //************************************************************************************** #include "AS.hpp" #include "dsp/samplerate.hpp" #include "dsp/ringbuffer.hpp" #include "dsp/filter.hpp" #include "dsp/digital.hpp" #include #include #define HISTORY_SIZE (1<<21) struct DelayPlusFx : Module { enum ParamIds { TIME_PARAM, FEEDBACK_PARAM, COLOR_PARAM, MIX_PARAM, BYPASS_SWITCH, NUM_PARAMS }; enum InputIds { TIME_INPUT, FEEDBACK_INPUT, COLOR_INPUT, COLOR_RETURN, MIX_INPUT, IN_INPUT, BYPASS_CV_INPUT, NUM_INPUTS }; enum OutputIds { COLOR_SEND, OUT_OUTPUT, NUM_OUTPUTS }; enum LightIds { BYPASS_LED, NUM_LIGHTS }; RCFilter lowpassFilter; RCFilter highpassFilter; DoubleRingBuffer historyBuffer; DoubleRingBuffer outBuffer; SampleRateConverter<1> src; SchmittTrigger bypass_button_trig; SchmittTrigger bypass_cv_trig; int lcd_tempo = 0; bool fx_bypass = false; float lastWet = 0.0f; float fade_in_fx = 0.0f; float fade_in_dry = 0.0f; float fade_out_fx = 1.0f; float fade_out_dry = 1.0f; const float fade_speed = 0.001f; DelayPlusFx() : Module(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS) { } void step() override; json_t *toJson()override { json_t *rootJm = json_object(); json_t *statesJ = json_array(); json_t *bypassJ = json_boolean(fx_bypass); json_array_append_new(statesJ, bypassJ); json_object_set_new(rootJm, "as_FxBypass", statesJ); return rootJm; } void fromJson(json_t *rootJm)override { json_t *statesJ = json_object_get(rootJm, "as_FxBypass"); json_t *bypassJ = json_array_get(statesJ, 0); fx_bypass = !!json_boolean_value(bypassJ); } void resetFades(){ fade_in_fx = 0.0f; fade_in_dry = 0.0f; fade_out_fx = 1.0f; fade_out_dry = 1.0f; } }; void DelayPlusFx::step() { if (bypass_button_trig.process(params[BYPASS_SWITCH].value) || bypass_cv_trig.process(inputs[BYPASS_CV_INPUT].value) ){ fx_bypass = !fx_bypass; resetFades(); } lights[BYPASS_LED].value = fx_bypass ? 1.0f : 0.0f; // Get input to delay block float signal_input = inputs[IN_INPUT].value; float feedback = clamp(params[FEEDBACK_PARAM].value + inputs[FEEDBACK_INPUT].value / 10.0f, 0.0f, 1.0f); float dry = signal_input + lastWet * feedback; // Compute delay time in seconds //float delay = 1e-3 * powf(10.0 / 1e-3, clampf(params[TIME_PARAM].value + inputs[TIME_INPUT].value / 10.0, 0.0, 1.0)); float delay = clamp(params[TIME_PARAM].value + inputs[TIME_INPUT].value, 0.001f, 10.0f); //LCD display tempo - show value as ms lcd_tempo = std::round(delay*1000); // Number of delay samples float index = delay * engineGetSampleRate(); // Push dry sample into history buffer if (!historyBuffer.full()) { historyBuffer.push(dry); } // How many samples do we need consume to catch up? float consume = index - historyBuffer.size(); if (outBuffer.empty()) { double ratio = 1.0; if (consume <= -16) ratio = 0.5; else if (consume >= 16) ratio = 2.0; float inSR = engineGetSampleRate(); float outSR = ratio * inSR; int inFrames = min(historyBuffer.size(), 16); int outFrames = outBuffer.capacity(); src.setRates(inSR, outSR); src.process((const Frame<1>*)historyBuffer.startData(), &inFrames, (Frame<1>*)outBuffer.endData(), &outFrames); historyBuffer.startIncr(inFrames); outBuffer.endIncr(outFrames); } float out; float mix; float wet = 0.0f; if (!outBuffer.empty()) { wet = outBuffer.shift(); } if (outputs[COLOR_SEND].active == false) { //internal color // Apply color to delay wet output float color = clamp(params[COLOR_PARAM].value + inputs[COLOR_INPUT].value / 10.0f, 0.0f, 1.0f); float lowpassFreq = 10000.0f * powf(10.0f, clamp(2.0*color, 0.0f, 1.0f)); lowpassFilter.setCutoff(lowpassFreq / engineGetSampleRate()); lowpassFilter.process(wet); wet = lowpassFilter.lowpass(); float highpassFreq = 10.0f * powf(100.0f, clamp(2.0f*color - 1.0f, 0.0f, 1.0f)); highpassFilter.setCutoff(highpassFreq / engineGetSampleRate()); highpassFilter.process(wet); wet = highpassFilter.highpass(); //lastWet = wet; }else { //external color, to filter the wet delay signal outside of the module, or to feed another module outputs[COLOR_SEND].value = wet; wet = inputs[COLOR_RETURN].value; } lastWet = wet; mix = clamp(params[MIX_PARAM].value + inputs[MIX_INPUT].value / 10.0f, 0.0f, 1.0f); out = crossfade(signal_input, wet, mix); //check bypass switch status if (fx_bypass){ fade_in_dry += fade_speed; if ( fade_in_dry > 1.0f ) { fade_in_dry = 1.0f; } fade_out_fx -= fade_speed; if ( fade_out_fx < 0.0f ) { fade_out_fx = 0.0f; } outputs[OUT_OUTPUT].value = ( signal_input * fade_in_dry ) + ( out * fade_out_fx ); }else{ fade_in_fx += fade_speed; if ( fade_in_fx > 1.0f ) { fade_in_fx = 1.0f; } fade_out_dry -= fade_speed; if ( fade_out_dry < 0.0f ) { fade_out_dry = 0.0f; } outputs[OUT_OUTPUT].value = ( signal_input * fade_out_dry ) + ( out * fade_in_fx ); } } /////////////////////////////////// struct MsDisplayWidget : TransparentWidget { int *value; std::shared_ptr font; MsDisplayWidget() { font = Font::load(assetPlugin(plugin, "res/Segment7Standard.ttf")); }; void draw(NVGcontext *vg) override { // Background NVGcolor backgroundColor = nvgRGB(0x20, 0x10, 0x10); 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.5); nvgStrokeColor(vg, borderColor); nvgStroke(vg); // text nvgFontSize(vg, 18); nvgFontFaceId(vg, font->handle); nvgTextLetterSpacing(vg, 2.5); std::stringstream to_display; to_display << std::right << std::setw(5) << *value; Vec textPos = Vec(4.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, to_display.str().c_str(), NULL); } }; //////////////////////////////////// struct DelayPlusFxWidget : ModuleWidget { DelayPlusFxWidget(DelayPlusFx *module); }; DelayPlusFxWidget::DelayPlusFxWidget(DelayPlusFx *module) : ModuleWidget(module) { setPanel(SVG::load(assetPlugin(plugin, "res/DelayPlus.svg"))); //MS DISPLAY MsDisplayWidget *display = new MsDisplayWidget(); display->box.pos = Vec(14,50); display->box.size = Vec(70, 20); display->value = &module->lcd_tempo; addChild(display); int y_offset=40; //SCREWS addChild(Widget::create(Vec(RACK_GRID_WIDTH, 0))); addChild(Widget::create(Vec(box.size.x - 2 * RACK_GRID_WIDTH, 0))); addChild(Widget::create(Vec(RACK_GRID_WIDTH, RACK_GRID_HEIGHT - RACK_GRID_WIDTH))); addChild(Widget::create(Vec(box.size.x - 2 * RACK_GRID_WIDTH, RACK_GRID_HEIGHT - RACK_GRID_WIDTH))); //KNOBS addParam(ParamWidget::create(Vec(74, 38+y_offset), module, DelayPlusFx::TIME_PARAM, 0.0f, 10.0f, 0.350f)); addParam(ParamWidget::create(Vec(74, 90+y_offset), module, DelayPlusFx::FEEDBACK_PARAM, 0.0f, 1.0f, 0.5f)); addParam(ParamWidget::create(Vec(74, 140+y_offset), module, DelayPlusFx::COLOR_PARAM, 0.0f, 1.0f, 0.5f)); addParam(ParamWidget::create(Vec(74, 213+y_offset), module, DelayPlusFx::MIX_PARAM, 0.0f, 1.0f, 0.5f)); //BYPASS SWITCH addParam(ParamWidget::create(Vec(49.5, 250+y_offset), module, DelayPlusFx::BYPASS_SWITCH , 0.0f, 1.0f, 0.0f));//Y=272 addChild(ModuleLightWidget::create>(Vec(51.7, 252+y_offset), module, DelayPlusFx::BYPASS_LED));//Y=274 //INPUTS addInput(Port::create(Vec(10, 45+y_offset), Port::INPUT, module, DelayPlusFx::TIME_INPUT)); addInput(Port::create(Vec(10, 95+y_offset), Port::INPUT, module, DelayPlusFx::FEEDBACK_INPUT)); addInput(Port::create(Vec(10, 145+y_offset), Port::INPUT, module, DelayPlusFx::COLOR_INPUT)); //DELAY SIGNAL SEND addOutput(Port::create(Vec(20, 184+y_offset), Port::OUTPUT, module, DelayPlusFx::COLOR_SEND)); //DELAY SIGNAL RETURN addInput(Port::create(Vec(75, 184+y_offset), Port::INPUT, module, DelayPlusFx::COLOR_RETURN)); //INPUTS addInput(Port::create(Vec(10, 220+y_offset), Port::INPUT, module, DelayPlusFx::MIX_INPUT)); addInput(Port::create(Vec(10, 310), Port::INPUT, module, DelayPlusFx::IN_INPUT)); //OUTPUT addOutput(Port::create(Vec(85, 310), Port::OUTPUT, module, DelayPlusFx::OUT_OUTPUT)); //BYPASS CV INPUT addInput(Port::create(Vec(49, 320), Port::INPUT, module, DelayPlusFx::BYPASS_CV_INPUT)); } RACK_PLUGIN_MODEL_INIT(AS, DelayPlusFx) { Model *modelDelayPlusFx = Model::create("AS", "DelayPlusFx", "DelayPlus Fx", DELAY_TAG, EFFECT_TAG); return modelDelayPlusFx; }