| @@ -43,7 +43,10 @@ struct ADSR : Module { | |||
| static constexpr float MIN_TIME = 1e-3f; | |||
| static constexpr float MAX_TIME = 10.f; | |||
| 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 env[4] = {}; | |||
| dsp::TSchmittTrigger<float_4> trigger[4]; | |||
| @@ -86,7 +89,7 @@ struct ADSR : Module { | |||
| // 0.23 us serial with all lambdas computed | |||
| // 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 | |||
| if (cvDivider.process()) { | |||
| @@ -119,34 +122,35 @@ struct ADSR : Module { | |||
| } | |||
| } | |||
| float_4 gate[4] = {}; | |||
| bool push = (params[PUSH_PARAM].getValue() > 0.f); | |||
| for (int c = 0; c < channels; c += 4) { | |||
| // Gate | |||
| gate[c / 4] = inputs[GATE_INPUT].getVoltageSimd<float_4>(c) >= 1.f; | |||
| float_4 oldGate = gate[c / 4]; | |||
| if (push) { | |||
| 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 | |||
| 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 | |||
| 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 | |||
| 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()); | |||
| attacking[c / 4] &= (env[c / 4] < 1.f); | |||
| // Set output | |||
| 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 { | |||
| 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); | |||
| 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); | |||
| 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); | |||
| // 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); | |||
| } | |||
| // 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; | |||
| } | |||
| }; | |||