Add delay/reverb dry-wet, granular randomize toggles, rework accent DSP

- Add delay and reverb Dry/Wet parameters and knobs, wired into presets
- Add Accent/Slide/Fine/Rate toggles to the Randomize panel with new
  per-aspect pattern randomizers
- Soften accent: modest volume lift plus small cutoff and filter-attack
  modulation instead of the heavy 5x gain boost
- Halve reverb comb/allpass delays so the wet speaks sooner
- Regenerate a fresh build stamp on every build
This commit is contained in:
Armin 2026-08-06 18:38:32 +02:00
commit 662a1f7383
11 changed files with 310 additions and 97 deletions

View file

@ -137,6 +137,10 @@ private:
fenvStage = Stage::attack;
}
// RC-style (exponential) filter envelope: the cutoff glides smoothly instead
// of stepping linearly, so fast filter sweeps sound analog rather than
// zippered. Each stage still reaches ~95% within the labelled time, so the
// knob values keep their musical meaning.
void updateFilterEnvelope()
{
const float sr = sampleRate;
@ -144,23 +148,37 @@ private:
switch (fenvStage)
{
case Stage::attack:
fenv += 1.0f / (params.fAttack * sr);
if (fenv >= 1.0f) { fenv = 1.0f; fenvStage = Stage::decay; }
{
// Accented notes attack the filter ever so slightly faster.
const float acc = accentSmooth - 1.0f;
const float t = juce::jmax (0.0005f, params.fAttack / (1.0f + acc * 0.5f));
const float a = 1.0f - std::exp (-3.0f / (t * sr));
fenv += (1.0f - fenv) * a;
if (fenv >= 0.999f) { fenv = 1.0f; fenvStage = Stage::decay; }
break;
}
case Stage::decay:
fenv -= (1.0f - params.fSustain) / (params.fDecay * sr);
if (fenv <= params.fSustain) { fenv = params.fSustain; fenvStage = Stage::sustain; }
{
const float t = juce::jmax (0.0005f, params.fDecay);
const float a = 1.0f - std::exp (-3.0f / (t * sr));
fenv += (params.fSustain - fenv) * a;
if (std::fabs (fenv - params.fSustain) < 0.001f) { fenv = params.fSustain; fenvStage = Stage::sustain; }
break;
}
case Stage::sustain:
fenv = params.fSustain;
break;
case Stage::release:
fenv -= 1.0f / (params.fRelease * sr);
if (fenv <= 0.0f) { fenv = 0.0f; fenvStage = Stage::idle; }
{
const float t = juce::jmax (0.0005f, params.fRelease);
const float a = 1.0f - std::exp (-3.0f / (t * sr));
fenv -= fenv * a;
if (fenv <= 0.001f) { fenv = 0.0f; fenvStage = Stage::idle; }
break;
}
case Stage::idle:
break;
@ -278,15 +296,18 @@ private:
float renderSample()
{
updateEnvelope();
updateFilterEnvelope();
updateGlide();
// Accent gains fast attack / slow release, so the boost fades out smoothly
// at the tail of an accented note instead of cutting off hard-edged.
accentSmooth += (accentLevel - accentSmooth)
* (accentLevel > accentSmooth ? accentAttackCoef : accentReleaseCoef);
// 0 for unaccented steps, rising with the accent knob on accented ones.
const float accBoost = accentSmooth - 1.0f;
updateEnvelope();
updateFilterEnvelope();
updateGlide();
const float incA = (currentFreq * params.detuneRatioA) / sampleRate;
const float incB = (currentFreq * params.detuneRatio) / sampleRate;
@ -302,14 +323,17 @@ private:
out = (1.0f - params.ringMod) * out + params.ringMod * (a * b);
const float preFilter = out;
// Accented steps open the filter a tiny bit, so they sparkle without
// relying on a big volume jump.
const float envCutoff = juce::jlimit (20.0f, 20000.0f,
params.cutoff * std::pow (2.0f, params.fAmount * fenv));
params.cutoff * std::pow (2.0f, params.fAmount * fenv + accBoost * 1.5f));
out = processFilter (out, envCutoff);
// Accented steps also get a touch of the unfiltered signal so they cut
// through the mix (presence), not just a volume bump.
if (accentSmooth > 1.0f)
out += (accentSmooth - 1.0f) * 0.2f * preFilter;
if (accBoost > 0.0f)
out += accBoost * 0.2f * preFilter;
if (params.drive > 0.0f)
{