Browse Source

Make ADSR polyphonic with SIMD. Increase ADSR attack target level to make the snappiness of the attack closer to average analog modules. Scope trigger refactoring.

tags/v1.0.1
Andrew Belt 5 years ago
parent
commit
04dc4d1330
4 changed files with 119 additions and 77 deletions
  1. +2
    -1
      plugin.json
  2. +104
    -61
      src/ADSR.cpp
  3. +1
    -1
      src/Delay.cpp
  4. +12
    -14
      src/Scope.cpp

+ 2
- 1
plugin.json View File

@@ -87,7 +87,8 @@
"name": "ADSR", "name": "ADSR",
"description": "Generates an envelope with Attack/Decay/Sustain/Release", "description": "Generates an envelope with Attack/Decay/Sustain/Release",
"tags": [ "tags": [
"Envelope Generator"
"Envelope Generator",
"Polyphonic"
] ]
}, },
{ {


+ 104
- 61
src/ADSR.cpp View File

@@ -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));


+ 1
- 1
src/Delay.cpp View File

@@ -36,7 +36,7 @@ struct Delay : Module {
config(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS); config(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS);
configParam(TIME_PARAM, 0.f, 1.f, 0.5f, "Time", " s", 10.f / 1e-3, 1e-3); configParam(TIME_PARAM, 0.f, 1.f, 0.5f, "Time", " s", 10.f / 1e-3, 1e-3);
configParam(FEEDBACK_PARAM, 0.f, 1.f, 0.5f, "Feedback", "%", 0, 100); configParam(FEEDBACK_PARAM, 0.f, 1.f, 0.5f, "Feedback", "%", 0, 100);
configParam(COLOR_PARAM, 0.f, 1.f, 0.5f, "Color");
configParam(COLOR_PARAM, 0.f, 1.f, 0.5f, "Color", "%", 0, 100);
configParam(MIX_PARAM, 0.f, 1.f, 0.5f, "Mix", "%", 0, 100); configParam(MIX_PARAM, 0.f, 1.f, 0.5f, "Mix", "%", 0, 100);


src = src_new(SRC_SINC_FASTEST, 1, NULL); src = src_new(SRC_SINC_FASTEST, 1, NULL);


+ 12
- 14
src/Scope.cpp View File

@@ -39,7 +39,7 @@ struct Scope : Module {
int channelsX = 0; int channelsX = 0;
int channelsY = 0; int channelsY = 0;
int bufferIndex = 0; int bufferIndex = 0;
float frameIndex = 0;
int frameIndex = 0;


dsp::BooleanTrigger sumTrigger; dsp::BooleanTrigger sumTrigger;
dsp::BooleanTrigger extTrigger; dsp::BooleanTrigger extTrigger;
@@ -120,19 +120,21 @@ struct Scope : Module {
return; return;
} }


// Reset the Schmitt trigger so we don't trigger immediately if the input is high
if (frameIndex == 0) {
resetTrigger.reset();
}
frameIndex++; frameIndex++;


// Must go below 0.1V to trigger
float gate = external ? inputs[TRIG_INPUT].getVoltage() : inputs[X_INPUT].getVoltage();

// Reset if triggered // Reset if triggered
float holdTime = 0.1f;
float trigValue = params[TRIG_PARAM].getValue(); float trigValue = params[TRIG_PARAM].getValue();
if (resetTrigger.process(rescale(gate, trigValue - 0.1f, trigValue, 0.f, 1.f)) || (frameIndex >= args.sampleRate * holdTime)) {
float gate = external ? inputs[TRIG_INPUT].getVoltage() : inputs[X_INPUT].getVoltage();

if (resetTrigger.process(rescale(gate, trigValue, trigValue + 0.001f, 0.f, 1.f))) {
bufferIndex = 0;
frameIndex = 0;
return;
}

// Reset if we've been waiting for `holdTime`
const float holdTime = 0.5f;
if (frameIndex * args.sampleTime >= holdTime) {
bufferIndex = 0; bufferIndex = 0;
frameIndex = 0; frameIndex = 0;
return; return;
@@ -165,22 +167,18 @@ struct ScopeDisplay : TransparentWidget {
std::shared_ptr<Font> font; std::shared_ptr<Font> font;


struct Stats { struct Stats {
// float vrms = 0.f;
float vpp = 0.f; float vpp = 0.f;
float vmin = 0.f; float vmin = 0.f;
float vmax = 0.f; float vmax = 0.f;


void calculate(float *buffer, int channels) { void calculate(float *buffer, int channels) {
// vrms = 0.f;
vmax = -INFINITY; vmax = -INFINITY;
vmin = INFINITY; vmin = INFINITY;
for (int i = 0; i < BUFFER_SIZE * channels; i++) { for (int i = 0; i < BUFFER_SIZE * channels; i++) {
float v = buffer[i]; float v = buffer[i];
// vrms += v*v;
vmax = std::fmax(vmax, v); vmax = std::fmax(vmax, v);
vmin = std::fmin(vmin, v); vmin = std::fmin(vmin, v);
} }
// vrms = std::sqrt(vrms / BUFFER_SIZE);
vpp = vmax - vmin; vpp = vmax - vmin;
} }
}; };


Loading…
Cancel
Save