#include "Southpole.hpp" #include "dsp/digital.hpp" #include "VAStateVariableFilter.h" namespace rack_plugin_Southpole { struct Piste : Module { enum ParamIds { FREQ_PARAM, RESO_PARAM, DECAY1_PARAM, DECAY2_PARAM, SCALE1_PARAM, SCALE2_PARAM, DRIVE_PARAM, NUM_PARAMS }; enum InputIds { IN_INPUT, DECAY1_INPUT, DECAY2_INPUT, TRIG1_INPUT, TRIG2_INPUT, SCALE1_INPUT, SCALE2_INPUT, MUTE_INPUT, NUM_INPUTS }; enum OutputIds { ENV1_OUTPUT, ENV2_OUTPUT, OUT_OUTPUT, NUM_OUTPUTS }; enum LightIds { DECAY1_LIGHT, DECAY2_LIGHT, NUM_LIGHTS }; VAStateVariableFilter lpFilter; VAStateVariableFilter hpFilter; float env1 = 0.0; float env2 = 0.0; SchmittTrigger trigger1; SchmittTrigger trigger2; SchmittTrigger mute; Piste() : Module(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS) { params.resize(NUM_PARAMS); inputs.resize(NUM_INPUTS); outputs.resize(NUM_OUTPUTS); lights.resize(NUM_LIGHTS); //trigger1.setThresholds(0.0, 2.0); //trigger1.setThresholds(0.0, 2.0); lpFilter.setFilterType(SVFLowpass); hpFilter.setFilterType(SVFHighpass); } void step() override; unsigned timer; }; void Piste::step() { float drive = clamp(params[DRIVE_PARAM].value, 0.0f, 1.0f); float freq = clamp(params[FREQ_PARAM].value, -1.0f, 1.0f); float reso = clamp(params[RESO_PARAM].value, 0.0f, 1.0f); float decay1 = clamp(params[DECAY1_PARAM].value + inputs[DECAY1_INPUT].value / 10.0f, 0.0f, 1.0f); float decay2 = decay1 * clamp(params[DECAY2_PARAM].value + inputs[DECAY2_INPUT].value / 10.0f, 0.0f, 1.0f); float scale1 = clamp(params[SCALE1_PARAM].value, 0.0, 1.0); float scale2 = scale1 * clamp(params[SCALE2_PARAM].value, 0.0, 1.0); bool muted = inputs[MUTE_INPUT].normalize(0.) >= 1.0; if (!muted) { if (trigger1.process(inputs[TRIG1_INPUT].value)) { env1 = 1.; } if (trigger2.process(inputs[TRIG2_INPUT].value)) { env2 = 1.; } } const float base = 20000.0; const float maxTime = 1.0; if (decay1 < 1e-4) { env1 = 0.; } else { env1 += powf(base, 1. - decay1) / maxTime * ( - env1) / engineGetSampleRate(); } if (decay2 < 1e-4) { env2 = 0.; } else { env2 += powf(base, 1. - decay2) / maxTime * ( - env2) / engineGetSampleRate(); } outputs[ENV1_OUTPUT].value = 10.*scale1 * env1; outputs[ENV2_OUTPUT].value = 10.*scale2 * env2; float v = inputs[IN_INPUT].value; // DRIVE v = (1.-drive)*v + drive * 10.*tanhf(10.*drive*v); const float f0 = 261.626; const float rmax = 0.9995; // Qmax = 1000 float fout = v; // FILTER if (freq < 0.) { float lp_cutoff = f0 * powf(2.f, 8.*(freq+1.)-4.); lpFilter.setResonance(reso*rmax); lpFilter.setSampleRate(engineGetSampleRate()); lpFilter.setCutoffFreq(lp_cutoff); fout = lpFilter.processAudioSample( v, 1); } else if ( freq > 0.) { float hp_cutoff = f0 * powf(2.f, 8.*freq-3.); hpFilter.setResonance(reso*rmax); hpFilter.setSampleRate(engineGetSampleRate()); hpFilter.setCutoffFreq(hp_cutoff); fout = hpFilter.processAudioSample( v, 1); } // VCA v = fout * 10.*scale1 * env1 * (1. + 10* scale2 * env2); outputs[OUT_OUTPUT].value = v; // Lights lights[DECAY1_LIGHT].value = env1; lights[DECAY2_LIGHT].value = env2; } struct PisteWidget : ModuleWidget { PisteWidget(Module *module) : ModuleWidget(module) { box.size = Vec(15*4, 380); { SVGPanel *panel = new SVGPanel(); panel->box.size = box.size; panel->setBackground(SVG::load(assetPlugin(plugin, "res/Piste.svg"))); addChild(panel); } const float x1 = 5.; const float x2 = 36.; const float y1 = 47.; const float yh = 31.; addInput(Port::create(Vec(x1, y1), Port::INPUT, module, Piste::IN_INPUT)); addParam(ParamWidget::create(Vec(x2, y1), module, Piste::DRIVE_PARAM, 0.0, 1.0, 0.0)); addParam(ParamWidget::create(Vec(x1, y1+1*yh), module, Piste::FREQ_PARAM, -1.0, 1.0, 0.)); addParam(ParamWidget::create(Vec(x2, y1+1*yh), module, Piste::RESO_PARAM, .0, 1.0, 0.)); addChild(ModuleLightWidget::create>(Vec(x1+6, y1+2*yh+5), module, Piste::DECAY1_LIGHT)); addChild(ModuleLightWidget::create>(Vec(x2+6, y1+2*yh+5), module, Piste::DECAY2_LIGHT)); addInput(Port::create(Vec(x1, y1+2.5*yh), Port::INPUT, module, Piste::TRIG1_INPUT)); addInput(Port::create(Vec(x2, y1+2.5*yh), Port::INPUT, module, Piste::TRIG2_INPUT)); addParam(ParamWidget::create(Vec(x1, y1+3.5*yh), module, Piste::SCALE1_PARAM, 0.0, 1.0, .5)); addParam(ParamWidget::create(Vec(x2, y1+3.5*yh), module, Piste::SCALE2_PARAM, 0.0, 1.0, 1.)); addParam(ParamWidget::create(Vec(x1, y1+4.5*yh), module, Piste::DECAY1_PARAM, 0.0, 1.0, 0.5)); addParam(ParamWidget::create(Vec(x2, y1+4.5*yh), module, Piste::DECAY2_PARAM, 0.0, 1.0, 1.)); addInput(Port::create(Vec(x1, y1+5.25*yh), Port::INPUT, module, Piste::DECAY1_INPUT)); addInput(Port::create(Vec(x2, y1+5.25*yh), Port::INPUT, module, Piste::DECAY2_INPUT)); addOutput(Port::create(Vec(x1, y1+6.5*yh), Port::OUTPUT, module, Piste::ENV1_OUTPUT)); addOutput(Port::create(Vec(x2, y1+6.5*yh), Port::OUTPUT, module, Piste::ENV2_OUTPUT)); addInput(Port::create(Vec(0.5*(x1+x2), 7.75*yh+y1), Port::INPUT, module, Piste::MUTE_INPUT)); addOutput(Port::create(Vec(0.5*(x1+x2), y1+9*yh), Port::OUTPUT, module, Piste::OUT_OUTPUT)); } }; } // namespace rack_plugin_Southpole using namespace rack_plugin_Southpole; RACK_PLUGIN_MODEL_INIT(Southpole, Piste) { Model *modelPiste = Model::create( "Southpole", "Piste", "Piste - drum processor", ENVELOPE_GENERATOR_TAG, EFFECT_TAG); return modelPiste; }