Browse Source

ADSR: Redesign display to match new VCV envelope modules.

tags/v2.5.0
Andrew Belt 2 years ago
parent
commit
2dfa78ffda
1 changed files with 157 additions and 43 deletions
  1. +157
    -43
      src/ADSR.cpp

+ 157
- 43
src/ADSR.cpp View File

@@ -43,7 +43,10 @@ struct ADSR : Module {
static constexpr float MIN_TIME = 1e-3f; static constexpr float MIN_TIME = 1e-3f;
static constexpr float MAX_TIME = 10.f; static constexpr float MAX_TIME = 10.f;
static constexpr float LAMBDA_BASE = MAX_TIME / MIN_TIME; static constexpr float LAMBDA_BASE = MAX_TIME / MIN_TIME;
static constexpr float ATT_TARGET = 1.2f;


int channels = 1;
float_4 gate[4] = {};
float_4 attacking[4] = {}; float_4 attacking[4] = {};
float_4 env[4] = {}; float_4 env[4] = {};
dsp::TSchmittTrigger<float_4> trigger[4]; dsp::TSchmittTrigger<float_4> trigger[4];
@@ -86,7 +89,7 @@ struct ADSR : Module {
// 0.23 us serial with all lambdas computed // 0.23 us serial with all lambdas computed
// 0.15-0.18 us serial with all lambdas computed with SSE // 0.15-0.18 us serial with all lambdas computed with SSE


int channels = std::max(1, inputs[GATE_INPUT].getChannels());
channels = std::max(1, inputs[GATE_INPUT].getChannels());


// Compute lambdas // Compute lambdas
if (cvDivider.process()) { if (cvDivider.process()) {
@@ -119,34 +122,35 @@ struct ADSR : Module {
} }
} }


float_4 gate[4] = {};
bool push = (params[PUSH_PARAM].getValue() > 0.f); bool push = (params[PUSH_PARAM].getValue() > 0.f);


for (int c = 0; c < channels; c += 4) { for (int c = 0; c < channels; c += 4) {
// Gate // Gate
gate[c / 4] = inputs[GATE_INPUT].getVoltageSimd<float_4>(c) >= 1.f;

float_4 oldGate = gate[c / 4];
if (push) { if (push) {
gate[c / 4] = float_4::mask(); gate[c / 4] = float_4::mask();
} }
else {
gate[c / 4] = inputs[GATE_INPUT].getVoltageSimd<float_4>(c) >= 1.f;
}
attacking[c / 4] |= (gate[c / 4] & ~oldGate);


// Retrigger // Retrigger
float_4 triggered = trigger[c / 4].process(inputs[RETRIG_INPUT].getPolyVoltageSimd<float_4>(c)); float_4 triggered = trigger[c / 4].process(inputs[RETRIG_INPUT].getPolyVoltageSimd<float_4>(c));
attacking[c / 4] = simd::ifelse(triggered, float_4::mask(), attacking[c / 4]);
attacking[c / 4] |= triggered;

// Turn off attacking state if gate is LOW
attacking[c / 4] &= gate[c / 4];


// Get target and lambda for exponential decay // 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]);
float_4 target = simd::ifelse(attacking[c / 4], ATT_TARGET, simd::ifelse(gate[c / 4], sustain[c / 4], 0.f));
float_4 lambda = simd::ifelse(attacking[c / 4], attackLambda[c / 4], simd::ifelse(gate[c / 4], decayLambda[c / 4], releaseLambda[c / 4]));


// Adjust env // Adjust env
env[c / 4] += (target - env[c / 4]) * lambda * args.sampleTime; env[c / 4] += (target - env[c / 4]) * lambda * args.sampleTime;


// Turn off attacking state if envelope is HIGH // 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());
attacking[c / 4] &= (env[c / 4] < 1.f);


// Set output // Set output
outputs[ENVELOPE_OUTPUT].setVoltageSimd(10.f * env[c / 4], c); outputs[ENVELOPE_OUTPUT].setVoltageSimd(10.f * env[c / 4], c);
@@ -201,43 +205,153 @@ struct ADSRDisplay : LedDisplay {


void drawLayer(const DrawArgs& args, int layer) override { void drawLayer(const DrawArgs& args, int layer) override {
if (layer == 1) { if (layer == 1) {
// Module parameters
float attackLambda = module ? module->attackLambda[0][0] : 1.f;
float decayLambda = module ? module->decayLambda[0][0] : 1.f;
float releaseLambda = module ? module->releaseLambda[0][0] : 1.f;
float sustain = module ? module->sustain[0][0] : 0.5f;

// Scale lambdas
const float power = 0.5f;
float attack = std::pow(attackLambda, -power);
float decay = std::pow(decayLambda, -power);
float release = std::pow(releaseLambda, -power);
float totalLambda = attack + decay + release;
if (totalLambda == 0.f)
return;

Rect r = box.zeroPos().shrink(Vec(4, 5));
Vec p0 = r.getBottomLeft();
Vec p1 = r.interpolate(Vec(attack / totalLambda, 0));
Vec p2 = r.interpolate(Vec((attack + decay) / totalLambda, 1 - sustain));
Vec p3 = r.getBottomRight();
Vec attackHandle = Vec(p0.x, crossfade(p0.y, p1.y, 0.8f));
Vec decayHandle = Vec(p1.x, crossfade(p1.y, p2.y, 0.8f));
Vec releaseHandle = Vec(p2.x, crossfade(p2.y, p3.y, 0.8f));

nvgScissor(args.vg, RECT_ARGS(args.clipBox));

Rect gridBox = getBox().zeroPos().shrink(Vec(0, 6.5));
Rect r = gridBox;
r.pos.x += 4.5;
r.size.x -= 4.5;
Vec p;

// Get parameters
float attTime = module ? 1 / module->attackLambda[0][0] : 1.f;
float decTime = module ? 1 / module->decayLambda[0][0] : 1.f;
float relTime = module ? 1 / module->releaseLambda[0][0] : 1.f;
float totalTime = attTime + decTime + relTime;
attTime /= totalTime;
decTime /= totalTime;
relTime /= totalTime;
float sustain = module ? module->sustain[0][0] : 0.333f;
int channels = module ? module->channels : 1;

// Grid
nvgStrokeWidth(args.vg, 1.0);
nvgStrokeColor(args.vg, nvgRGBAf(1, 1, 1, 0.20));
nvgBeginPath(args.vg); nvgBeginPath(args.vg);
nvgMoveTo(args.vg, p0.x, p0.y);
nvgBezierTo(args.vg, p0.x, p0.y, attackHandle.x, attackHandle.y, p1.x, p1.y);
nvgBezierTo(args.vg, p1.x, p1.y, decayHandle.x, decayHandle.y, p2.x, p2.y);
nvgBezierTo(args.vg, p2.x, p2.y, releaseHandle.x, releaseHandle.y, p3.x, p3.y);
nvgLineCap(args.vg, NVG_ROUND);
nvgMiterLimit(args.vg, 2.f);
nvgStrokeWidth(args.vg, 1.5f);

// Left
p = r.getTopLeft();
nvgMoveTo(args.vg, VEC_ARGS(p));
p = r.getBottomLeft();
nvgLineTo(args.vg, VEC_ARGS(p));
// Top
p = gridBox.getTopLeft();
nvgMoveTo(args.vg, VEC_ARGS(p));
p = gridBox.getTopRight();
nvgLineTo(args.vg, VEC_ARGS(p));
// Bottom
p = gridBox.getBottomLeft();
nvgMoveTo(args.vg, VEC_ARGS(p));
p = gridBox.getBottomRight();
nvgLineTo(args.vg, VEC_ARGS(p));
// Attack
p = r.interpolate(Vec(attTime, 0));
nvgMoveTo(args.vg, VEC_ARGS(p));
p = r.interpolate(Vec(attTime, 1));
nvgLineTo(args.vg, VEC_ARGS(p));
// Decay
p = r.interpolate(Vec(attTime + decTime, 1 - sustain));
nvgMoveTo(args.vg, VEC_ARGS(p));
p = r.interpolate(Vec(attTime + decTime, 1));
nvgLineTo(args.vg, VEC_ARGS(p));
// Sustain
p = r.interpolate(Vec(attTime, 1 - sustain));
nvgMoveTo(args.vg, VEC_ARGS(p));
p = r.interpolate(Vec(1, 1 - sustain));
nvgLineTo(args.vg, VEC_ARGS(p));

nvgStroke(args.vg);

// Line
nvgStrokeColor(args.vg, SCHEME_YELLOW); nvgStrokeColor(args.vg, SCHEME_YELLOW);
nvgBeginPath(args.vg);
Vec c1, c2;

// Begin
p = r.getBottomLeft();
nvgMoveTo(args.vg, VEC_ARGS(p));
// Attack
const int I = 10;
for (int i = 1; i <= I; i++) {
float phase = float(i) / I;
// Distribute points more evenly to prevent artifacts
phase = std::pow(phase, 2);
float env = phaseToEnv(phase);
p = r.interpolate(Vec(attTime * phase, 1 - env));
nvgLineTo(args.vg, VEC_ARGS(p));
}
// Decay
for (int i = 1; i <= I; i++) {
float phase = float(i) / I;
phase = std::pow(phase, 2);
float env = 1 - phaseToEnv(phase) * (1 - sustain);
p = r.interpolate(Vec(attTime + decTime * phase, 1 - env));
nvgLineTo(args.vg, VEC_ARGS(p));
}
// Release
for (int i = 1; i <= I; i++) {
float phase = float(i) / I;
phase = std::pow(phase, 2);
float env = (1 - phaseToEnv(phase)) * sustain;
p = r.interpolate(Vec(attTime + decTime + relTime * phase, 1 - env));
nvgLineTo(args.vg, VEC_ARGS(p));
}

nvgStroke(args.vg); nvgStroke(args.vg);

// Sustain circle
{
nvgStrokeColor(args.vg, SCHEME_YELLOW);
nvgBeginPath(args.vg);
p = r.interpolate(Vec(attTime + decTime, 1 - sustain));
nvgCircle(args.vg, VEC_ARGS(p), 2.5);
nvgFillColor(args.vg, nvgRGB(0x22, 0x22, 0x22));
nvgFill(args.vg);
nvgStroke(args.vg);
}

// Position circle
for (int c = 0; c < channels; c++) {
float env = module ? module->env[c / 4][c % 4] : 0.f;
if (env > 0.01f) {
bool attacking = module ? (simd::movemask(module->attacking[c / 4]) & (1 << (c % 4))) : false;
bool gate = module ? module->gate[c / 4][c % 4] : false;

if (attacking) {
float phase = envToPhase(env);
p = r.interpolate(Vec(attTime * phase, 1 - env));
}
else if (gate) {
float phase = envToPhase(1 - (env - sustain) / (1 - sustain));
p = r.interpolate(Vec(attTime + decTime * phase, 1 - env));
}
else {
env = std::min(env, sustain);
float phase = envToPhase(1 - env / sustain);
p = r.interpolate(Vec(attTime + decTime + relTime * phase, 1 - env));
}
nvgBeginPath(args.vg);
nvgCircle(args.vg, VEC_ARGS(p), 2.5);
nvgFillColor(args.vg, nvgRGBAf(1, 1, 1, 0.66));
nvgFill(args.vg);
}
}

nvgResetScissor(args.vg);
} }

LedDisplay::drawLayer(args, layer); LedDisplay::drawLayer(args, layer);
} }

// Optimized for appearance, not accuracy to ADSR DSP.
static constexpr float TARGET = 1.1f;
static constexpr float LAMBDA = 2.3978952727983702f; // -std::log(1 - 1 / TARGET);
static float phaseToEnv(float phase) {
return (1 - std::exp(-LAMBDA * phase)) * TARGET;
}
static float envToPhase(float env) {
return -std::log(1 - env / TARGET) / LAMBDA;
}
}; };






Loading…
Cancel
Save