Design a Custom Variable Digital Delay: A Step‑by‑Step DSP Tutorial for Music Producers
If you’ve ever tried to get a perfect echo on a vocal track and ended up with a robotic “wah‑wah” that sounds like a bad sci‑fi movie, you know why a flexible delay is worth its weight in gold. In today’s fast‑moving production world, a one‑size‑fits‑all delay just won’t cut it. That’s why I’m sharing a hands‑on guide to building a variable digital delay that you can tweak on the fly, right inside your DAW or as a tiny plug‑in.
Why Variable Delay Matters Right Now
Most stock delays lock you into a fixed time or a simple LFO‑based modulation. Those tools are fine for basic repeats, but they fall short when you need to sync a delay to a changing tempo, or when you want the delay time to follow a filter sweep, a side‑chain envelope, or even a MIDI controller. A custom variable delay gives you that freedom, and it can be built with just a few lines of code and a solid understanding of the math behind it.
The Core Ingredients
Before we dive into the code, let’s list the building blocks you’ll need:
- Sample buffer – a circular memory that stores incoming audio samples.
- Read/write pointers – indices that tell the algorithm where to write new samples and where to read them back.
- Delay time calculator – a function that converts a desired delay (in milliseconds) into a buffer offset (in samples).
- Interpolation – a method to smooth out fractional sample positions, because most delay times won’t line up exactly with whole samples.
- Modulation source – anything that can change the delay time: an LFO, an envelope follower, a MIDI CC, etc.
All of these are concepts I’ve played with for years at Digital Delay Lab, and they’re all you need to get a truly expressive delay.
Step 1: Set Up the Buffer
First, decide how long your maximum delay will be. A common choice is 2 seconds, which at a 48 kHz sample rate means a buffer of 96 000 samples.
const int maxDelaySec = 2;
const int sampleRate = 48000;
const int bufferSize = maxDelaySec * sampleRate;
float *delayBuffer = new float[bufferSize];
int writePos = 0;
The buffer lives in RAM, so keep an eye on memory if you’re targeting a low‑end device. For most desktop DAWs, 96 k samples is a drop in the ocean.
Step 2: Write Incoming Samples
Every time the audio callback runs, you push the new sample into the buffer and advance the write pointer. Wrap the pointer around when it reaches the end of the buffer.
void processSample(float in)
{
delayBuffer[writePos] = in;
writePos = (writePos + 1) % bufferSize;
}
That’s it – the buffer now holds a rolling history of your audio.
Step 3: Convert Desired Delay to a Read Position
Suppose you want a delay of 350 ms. First turn that into samples:
delaySamples = (delayMs / 1000.0) * sampleRate
If the result isn’t an integer, you’ll need interpolation (next step). The read position is simply the write position minus the delay in samples, wrapped around the buffer.
float getReadPos(float delayMs)
{
float delaySamples = (delayMs / 1000.0f) * sampleRate;
float readPos = (float)writePos - delaySamples;
if (readPos < 0) readPos += bufferSize;
return readPos;
}
Step 4: Linear Interpolation for Smooth Output
When the read position lands between two samples, linear interpolation gives a quick, decent result. Grab the two surrounding samples and blend them based on the fractional part.
float readSample(float readPos)
{
int idx0 = (int)readPos;
int idx1 = (idx0 + 1) % bufferSize;
float frac = readPos - idx0;
float s0 = delayBuffer[idx0];
float s1 = delayBuffer[idx1];
return s0 + frac * (s1 - s0);
}
If you need higher quality, replace this with a cubic or Hermite interpolator, but for most music work linear works fine.
Step 5: Add a Modulation Source
Now the fun part. Let’s say you want the delay time to follow a slow sine LFO that sweeps between 100 ms and 600 ms at 0.2 Hz.
float lfoPhase = 0.0f;
const float lfoRate = 0.2f; // Hz
const float minDelay = 100.0f;
const float maxDelay = 600.0f;
float getModulatedDelay()
{
float lfo = sinf(2.0f * M_PI * lfoPhase);
lfoPhase += lfoRate / sampleRate;
if (lfoPhase >= 1.0f) lfoPhase -= 1.0f;
// Map -1..1 to min..max
return minDelay + (lfo + 1.0f) * 0.5f * (maxDelay - minDelay);
}
You can replace the sine wave with any source: an envelope follower that reacts to the input level, a MIDI CC that you automate, or even a side‑chain signal from a kick drum.
Step 6: Put It All Together
Here’s the full per‑sample processing loop:
void process(float *in, float *out, int numSamples)
{
for (int i = 0; i < numSamples; ++i)
{
// 1. Write current sample
delayBuffer[writePos] = in[i];
// 2. Get current delay time from modulation source
float curDelayMs = getModulatedDelay();
// 3. Find read position
float readPos = getReadPos(curDelayMs);
// 4. Interpolate and output
out[i] = readSample(readPos);
// 5. Advance write pointer
writePos = (writePos + 1) % bufferSize;
}
}
That’s a complete variable delay! You can now add feedback, mix dry/wet, or even pan the delayed signal for a stereo spread. The core idea stays the same: a buffer, a moving read pointer, and a way to change that pointer over time.
Tips from the Lab
- Avoid clicks – When you change the delay time abruptly, the read pointer jumps and you can hear a click. Smooth the change with a tiny low‑pass filter on the delay value, or use a ramp that moves the pointer a few samples per block.
- Feedback safety – If you add feedback, keep an eye on the gain. A simple one‑pole limiter on the delayed signal can prevent runaway oscillations.
- CPU budget – Linear interpolation is cheap. Even on a modest laptop you can run dozens of these delays in parallel without breaking a sweat.
- Testing – Load a simple click track and sweep the LFO speed. You’ll see the delay time move smoothly across the waveform, and any bugs become obvious right away.
Where to Go From Here
Now that you have a working variable delay, you can experiment with more exotic modulation shapes: random jitter for a “lo‑fi” vibe, envelope‑controlled delay for a “gated echo,” or a per‑note MIDI‑driven delay that follows the melody. The DSP toolbox is full of possibilities, and the best way to learn is to try them out in a real session.
At Digital Delay Lab we love hearing how producers bend these tools to fit their sound. The next time you’re stuck on a mix, remember that a custom delay can be the missing link between a flat track and a moving, breathing piece of music.
#dsp #musicproduction #delay
- → How to Reduce Latency in Your DAW Using Simple DSP Techniques @signalcraft
- → How to Design a Low-Latency Digital Delay Line for Home Studios @signaldelaylab
- → Step-by-step guide: crafting club‑ready electro drops with free plugins @electropulse
- → Mixing Electro Beats in Ableton Live: A Step-by-Step Guide for Producers @electropulse
- → How to Choose the Right Audio Interface for Bedroom Producers: A Practical Guide @sonicforge