#include "plugin.hpp" using namespace simd; const float MIN_TIME = 1e-3f; const float MAX_TIME = 10.f; const float LAMBDA_BASE = MAX_TIME / MIN_TIME; struct ADSR : Module { enum ParamIds { ATTACK_PARAM, DECAY_PARAM, SUSTAIN_PARAM, RELEASE_PARAM, NUM_PARAMS }; enum InputIds { ATTACK_INPUT, DECAY_INPUT, SUSTAIN_INPUT, RELEASE_INPUT, GATE_INPUT, TRIG_INPUT, NUM_INPUTS }; enum OutputIds { ENVELOPE_OUTPUT, NUM_OUTPUTS }; enum LightIds { ATTACK_LIGHT, DECAY_LIGHT, SUSTAIN_LIGHT, RELEASE_LIGHT, NUM_LIGHTS }; float_4 attacking[4] = {float_4::zero()}; float_4 env[4] = {0.f}; dsp::TSchmittTrigger trigger[4]; dsp::ClockDivider cvDivider; float_4 attackLambda[4] = {0.f}; float_4 decayLambda[4] = {0.f}; float_4 releaseLambda[4] = {0.f}; float_4 sustain[4] = {0.f}; dsp::ClockDivider lightDivider; ADSR() { config(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS); configParam(ATTACK_PARAM, 0.f, 1.f, 0.5f, "Attack", " ms", LAMBDA_BASE, MIN_TIME * 1000); configParam(DECAY_PARAM, 0.f, 1.f, 0.5f, "Decay", " ms", LAMBDA_BASE, MIN_TIME * 1000); configParam(SUSTAIN_PARAM, 0.f, 1.f, 0.5f, "Sustain", "%", 0, 100); configParam(RELEASE_PARAM, 0.f, 1.f, 0.5f, "Release", " ms", LAMBDA_BASE, MIN_TIME * 1000); cvDivider.setDivision(16); lightDivider.setDivision(128); } void process(const ProcessArgs &args) override { // 0.16-0.19 us serial // 0.23 us serial with all lambdas computed // 0.15-0.18 us serial with all lambdas computed with SSE int channels = inputs[GATE_INPUT].getChannels(); // Compute lambdas if (cvDivider.process()) { float attackParam = params[ATTACK_PARAM].getValue(); float decayParam = params[DECAY_PARAM].getValue(); float sustainParam = params[SUSTAIN_PARAM].getValue(); float releaseParam = params[RELEASE_PARAM].getValue(); for (int c = 0; c < channels; c += 4) { // CV float_4 attack = attackParam + inputs[ATTACK_INPUT].getPolyVoltageSimd(c) / 10.f; float_4 decay = decayParam + inputs[DECAY_INPUT].getPolyVoltageSimd(c) / 10.f; float_4 sustain = sustainParam + inputs[SUSTAIN_INPUT].getPolyVoltageSimd(c) / 10.f; float_4 release = releaseParam + inputs[RELEASE_INPUT].getPolyVoltageSimd(c) / 10.f; attack = simd::clamp(attack, 0.f, 1.f); decay = simd::clamp(decay, 0.f, 1.f); sustain = simd::clamp(sustain, 0.f, 1.f); release = simd::clamp(release, 0.f, 1.f); attackLambda[c / 4] = simd::pow(LAMBDA_BASE, -attack) / MIN_TIME; decayLambda[c / 4] = simd::pow(LAMBDA_BASE, -decay) / MIN_TIME; releaseLambda[c / 4] = simd::pow(LAMBDA_BASE, -release) / MIN_TIME; this->sustain[c / 4] = sustain; } } float_4 gate[4]; for (int c = 0; c < channels; c += 4) { // Gate gate[c / 4] = inputs[GATE_INPUT].getVoltageSimd(c) >= 1.f; // Retrigger float_4 triggered = trigger[c / 4].process(inputs[TRIG_INPUT].getPolyVoltageSimd(c)); attacking[c / 4] = simd::ifelse(triggered, float_4::mask(), attacking[c / 4]); // Get target and lambda for exponential decay const float attackTarget = 1.2f; float_4 target = simd::ifelse(gate[c / 4], simd::ifelse(attacking[c / 4], attackTarget, sustain[c / 4]), 0.f); float_4 lambda = simd::ifelse(gate[c / 4], simd::ifelse(attacking[c / 4], attackLambda[c / 4], decayLambda[c / 4]), releaseLambda[c / 4]); // Adjust env env[c / 4] += (target - env[c / 4]) * lambda * args.sampleTime; // Turn off attacking state if envelope is HIGH attacking[c / 4] = simd::ifelse(env[c / 4] >= 1.f, float_4::zero(), attacking[c / 4]); // Turn on attacking state if gate is LOW attacking[c / 4] = simd::ifelse(gate[c / 4], attacking[c / 4], float_4::mask()); // Set output outputs[ENVELOPE_OUTPUT].setVoltageSimd(10.f * env[c / 4], c); } outputs[ENVELOPE_OUTPUT].setChannels(channels); // Lights if (lightDivider.process()) { lights[ATTACK_LIGHT].setBrightness(0); lights[DECAY_LIGHT].setBrightness(0); lights[SUSTAIN_LIGHT].setBrightness(0); lights[RELEASE_LIGHT].setBrightness(0); for (int c = 0; c < channels; c += 4) { const float epsilon = 0.01f; float_4 sustaining = (sustain[c / 4] <= env[c / 4]) & (env[c / 4] < sustain[c / 4] + epsilon); float_4 resting = (env[c / 4] < epsilon); if (simd::movemask(gate[c / 4] & attacking[c / 4])) lights[ATTACK_LIGHT].setBrightness(1); if (simd::movemask(gate[c / 4] & ~attacking[c / 4] & ~sustaining)) lights[DECAY_LIGHT].setBrightness(1); if (simd::movemask(gate[c / 4] & ~attacking[c / 4] & sustaining)) lights[SUSTAIN_LIGHT].setBrightness(1); if (simd::movemask(~gate[c / 4] & ~resting)) lights[RELEASE_LIGHT].setBrightness(1); } } } }; struct ADSRWidget : ModuleWidget { ADSRWidget(ADSR *module) { setModule(module); setPanel(APP->window->loadSvg(asset::plugin(pluginInstance, "res/ADSR.svg"))); addChild(createWidget(Vec(15, 0))); addChild(createWidget(Vec(box.size.x - 30, 0))); addChild(createWidget(Vec(15, 365))); addChild(createWidget(Vec(box.size.x - 30, 365))); addParam(createParam(Vec(62, 57), module, ADSR::ATTACK_PARAM)); addParam(createParam(Vec(62, 124), module, ADSR::DECAY_PARAM)); addParam(createParam(Vec(62, 191), module, ADSR::SUSTAIN_PARAM)); addParam(createParam(Vec(62, 257), module, ADSR::RELEASE_PARAM)); addInput(createInput(Vec(9, 63), module, ADSR::ATTACK_INPUT)); addInput(createInput(Vec(9, 129), module, ADSR::DECAY_INPUT)); addInput(createInput(Vec(9, 196), module, ADSR::SUSTAIN_INPUT)); addInput(createInput(Vec(9, 263), module, ADSR::RELEASE_INPUT)); addInput(createInput(Vec(9, 320), module, ADSR::GATE_INPUT)); addInput(createInput(Vec(48, 320), module, ADSR::TRIG_INPUT)); addOutput(createOutput(Vec(87, 320), module, ADSR::ENVELOPE_OUTPUT)); addChild(createLight>(Vec(94, 41), module, ADSR::ATTACK_LIGHT)); addChild(createLight>(Vec(94, 109), module, ADSR::DECAY_LIGHT)); addChild(createLight>(Vec(94, 175), module, ADSR::SUSTAIN_LIGHT)); addChild(createLight>(Vec(94, 242), module, ADSR::RELEASE_LIGHT)); } }; Model *modelADSR = createModel("ADSR");