declare name "Drifting Echo"; declare subtitle "Delay Effect"; declare description "Warm analog echo with gently drifting modulated repeats and soft tape-style saturation."; declare author "Chaos Audio"; declare copyright "(c) Chaos Audio 2025"; import("stdfaust.lib"); // Controls (max 9 chars) timeKnob = hslider("Time[stratus:0]", 380, 40, 1000, 1) : si.smoo; // ms repeatsKnob = hslider("Repeats[stratus:1]", 45, 0, 100, 0.1) : si.smoo; // % toneKnob = hslider("Tone[stratus:2]", 50, 0, 100, 1) : si.smoo; // Warm/Bright depthKnob = hslider("Depth[stratus:3]", 30, 0, 100, 1) : si.smoo; // % rateKnob = hslider("Rate[stratus:4]", 1.2, 0.1, 6.0, 0.05) : si.smoo; // Hz mixKnob = hslider("Mix[stratus:5]", 35, 0, 100, 1) : si.smoo; // % driftToggle = hslider("Drift[stratus:6]", 1, 0, 1, 1) : si.smoo; // 0 or 1 // Maximum delay line allocation (1.2 seconds) maxDelaySamples = int(1.2 * 192000); // Base delay in samples baseDelaySamples = timeKnob * 0.001 * ma.SR; // Multi-speed drift LFO: subtle non-repeating movement when Drift is active // Combine primary LFO with two slow, inharmonic drift LFOs lfo1 = os.osc(rateKnob); driftLfo1 = os.osc(0.17) * 0.6; driftLfo2 = os.osc(0.073) * 0.4; driftFactor = (driftLfo1 + driftLfo2) * driftToggle; // Modulation depth mapped to sample excursion (up to ~3.5ms vibrato / drift) modAmount = (depthKnob * 0.01) * (0.0035 * ma.SR); totalMod = (lfo1 * 0.7 + driftFactor * 0.8) * modAmount; // Clamped total delay in samples targetDelay = max(10, min(maxDelaySamples - 10, baseDelaySamples + totalMod)); // Repeats feedback gain (0.0 to ~0.96 so it oscillates softly without runaway at 100%) fbGain = repeatsKnob * 0.0096; // Tone filter: warm lowpass + gentle highpass to avoid muddy sub-bass // Tone knob scales cutoff from warm/dark (1.2 kHz) to open (6.5 kHz) lpCutoff = 1200.0 + (toneKnob * 0.01) * 5300.0; hpCutoff = 80.0; // Soft saturation for warm repeats: tanh based saturator softSat(x) = ma.tanh(x * 1.25) * 0.8; // Delay loop with recursive feedback delayFeedbackLoop = (+ : de.fdelay(maxDelaySamples, targetDelay)) ~ ( fi.highpass(1, hpCutoff) : fi.lowpass(2, lpCutoff) : softSat : *(fbGain) ); // Mix staging: keep dry crisp and 100% transparent, mix repeats smoothly wetGain = mixKnob * 0.01; dryGain = 1.0; process(x) = (x * dryGain + wetSignal * wetGain) : fi.dcblocker : ma.tanh with { wetSignal = delayFeedbackLoop(x); };