|
@@ -1,6 +1,14 @@ |
|
|
#include "plugin.hpp" |
|
|
#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 { |
|
|
struct ADSR : Module { |
|
|
enum ParamIds { |
|
|
enum ParamIds { |
|
|
ATTACK_PARAM, |
|
|
ATTACK_PARAM, |
|
@@ -30,77 +38,112 @@ struct ADSR : Module { |
|
|
NUM_LIGHTS |
|
|
NUM_LIGHTS |
|
|
}; |
|
|
}; |
|
|
|
|
|
|
|
|
bool decaying = false; |
|
|
|
|
|
float env = 0.f; |
|
|
|
|
|
dsp::SchmittTrigger trigger; |
|
|
|
|
|
|
|
|
float_4 attacking[4] = {float_4::zero()}; |
|
|
|
|
|
float_4 env[4] = {0.f}; |
|
|
|
|
|
dsp::TSchmittTrigger<float_4> 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() { |
|
|
ADSR() { |
|
|
config(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS); |
|
|
config(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS); |
|
|
configParam(ATTACK_PARAM, 0.f, 1.f, 0.5f, "Attack"); |
|
|
|
|
|
configParam(DECAY_PARAM, 0.f, 1.f, 0.5f, "Decay"); |
|
|
|
|
|
configParam(SUSTAIN_PARAM, 0.f, 1.f, 0.5f, "Sustain"); |
|
|
|
|
|
configParam(RELEASE_PARAM, 0.f, 1.f, 0.5f, "Release"); |
|
|
|
|
|
|
|
|
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 { |
|
|
void process(const ProcessArgs &args) override { |
|
|
float attack = clamp(params[ATTACK_PARAM].getValue() + inputs[ATTACK_INPUT].getVoltage() / 10.f, 0.f, 1.f); |
|
|
|
|
|
float decay = clamp(params[DECAY_PARAM].getValue() + inputs[DECAY_INPUT].getVoltage() / 10.f, 0.f, 1.f); |
|
|
|
|
|
float sustain = clamp(params[SUSTAIN_PARAM].getValue() + inputs[SUSTAIN_INPUT].getVoltage() / 10.f, 0.f, 1.f); |
|
|
|
|
|
float release = clamp(params[RELEASE_PARAM].getValue() + inputs[RELEASE_INPUT].getVoltage() / 10.f, 0.f, 1.f); |
|
|
|
|
|
|
|
|
|
|
|
// Gate and trigger |
|
|
|
|
|
bool gated = inputs[GATE_INPUT].getVoltage() >= 1.f; |
|
|
|
|
|
if (trigger.process(inputs[TRIG_INPUT].getVoltage())) |
|
|
|
|
|
decaying = false; |
|
|
|
|
|
|
|
|
|
|
|
const float base = 20000.f; |
|
|
|
|
|
const float maxTime = 10.f; |
|
|
|
|
|
if (gated) { |
|
|
|
|
|
if (decaying) { |
|
|
|
|
|
// Decay |
|
|
|
|
|
if (decay < 1e-4) { |
|
|
|
|
|
env = sustain; |
|
|
|
|
|
} |
|
|
|
|
|
else { |
|
|
|
|
|
env += std::pow(base, 1 - decay) / maxTime * (sustain - env) * args.sampleTime; |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
else { |
|
|
|
|
|
// Attack |
|
|
|
|
|
// Skip ahead if attack is all the way down (infinitely fast) |
|
|
|
|
|
if (attack < 1e-4) { |
|
|
|
|
|
env = 1.f; |
|
|
|
|
|
} |
|
|
|
|
|
else { |
|
|
|
|
|
env += std::pow(base, 1 - attack) / maxTime * (1.01f - env) * args.sampleTime; |
|
|
|
|
|
} |
|
|
|
|
|
if (env >= 1.f) { |
|
|
|
|
|
env = 1.f; |
|
|
|
|
|
decaying = true; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
// 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<float_4>(c) / 10.f; |
|
|
|
|
|
float_4 decay = decayParam + inputs[DECAY_INPUT].getPolyVoltageSimd<float_4>(c) / 10.f; |
|
|
|
|
|
float_4 sustain = sustainParam + inputs[SUSTAIN_INPUT].getPolyVoltageSimd<float_4>(c) / 10.f; |
|
|
|
|
|
float_4 release = releaseParam + inputs[RELEASE_INPUT].getPolyVoltageSimd<float_4>(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; |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
else { |
|
|
|
|
|
// Release |
|
|
|
|
|
if (release < 1e-4) { |
|
|
|
|
|
env = 0.f; |
|
|
|
|
|
} |
|
|
|
|
|
else { |
|
|
|
|
|
env += std::pow(base, 1 - release) / maxTime * (0.f - env) * args.sampleTime; |
|
|
|
|
|
} |
|
|
|
|
|
decaying = false; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
bool sustaining = isNear(env, sustain, 1e-3); |
|
|
|
|
|
bool resting = isNear(env, 0.f, 1e-3); |
|
|
|
|
|
|
|
|
float_4 gate[4]; |
|
|
|
|
|
|
|
|
|
|
|
for (int c = 0; c < channels; c += 4) { |
|
|
|
|
|
// Gate |
|
|
|
|
|
gate[c / 4] = inputs[GATE_INPUT].getVoltageSimd<float_4>(c) >= 1.f; |
|
|
|
|
|
|
|
|
outputs[ENVELOPE_OUTPUT].setVoltage(10.f * env); |
|
|
|
|
|
|
|
|
// Retrigger |
|
|
|
|
|
float_4 triggered = trigger[c / 4].process(inputs[TRIG_INPUT].getPolyVoltageSimd<float_4>(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 |
|
|
// Lights |
|
|
lights[ATTACK_LIGHT].value = (gated && !decaying) ? 1.f : 0.f; |
|
|
|
|
|
lights[DECAY_LIGHT].value = (gated && decaying && !sustaining) ? 1.f : 0.f; |
|
|
|
|
|
lights[SUSTAIN_LIGHT].value = (gated && decaying && sustaining) ? 1.f : 0.f; |
|
|
|
|
|
lights[RELEASE_LIGHT].value = (!gated && !resting) ? 1.f : 0.f; |
|
|
|
|
|
|
|
|
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); |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
} |
|
|
} |
|
|
}; |
|
|
}; |
|
|
|
|
|
|
|
@@ -111,9 +154,9 @@ struct ADSRWidget : ModuleWidget { |
|
|
setPanel(APP->window->loadSvg(asset::plugin(pluginInstance, "res/ADSR.svg"))); |
|
|
setPanel(APP->window->loadSvg(asset::plugin(pluginInstance, "res/ADSR.svg"))); |
|
|
|
|
|
|
|
|
addChild(createWidget<ScrewSilver>(Vec(15, 0))); |
|
|
addChild(createWidget<ScrewSilver>(Vec(15, 0))); |
|
|
addChild(createWidget<ScrewSilver>(Vec(box.size.x-30, 0))); |
|
|
|
|
|
|
|
|
addChild(createWidget<ScrewSilver>(Vec(box.size.x - 30, 0))); |
|
|
addChild(createWidget<ScrewSilver>(Vec(15, 365))); |
|
|
addChild(createWidget<ScrewSilver>(Vec(15, 365))); |
|
|
addChild(createWidget<ScrewSilver>(Vec(box.size.x-30, 365))); |
|
|
|
|
|
|
|
|
addChild(createWidget<ScrewSilver>(Vec(box.size.x - 30, 365))); |
|
|
|
|
|
|
|
|
addParam(createParam<RoundLargeBlackKnob>(Vec(62, 57), module, ADSR::ATTACK_PARAM)); |
|
|
addParam(createParam<RoundLargeBlackKnob>(Vec(62, 57), module, ADSR::ATTACK_PARAM)); |
|
|
addParam(createParam<RoundLargeBlackKnob>(Vec(62, 124), module, ADSR::DECAY_PARAM)); |
|
|
addParam(createParam<RoundLargeBlackKnob>(Vec(62, 124), module, ADSR::DECAY_PARAM)); |
|
|