Designing a Variable Digital Delay Line in Pure Data
Ever tried to get that perfect echo on a synth patch and ended up with a static, robotic slap‑back? It’s a common frustration, especially when you need the delay time to move smoothly with a LFO or a MIDI controller. In this post I’ll walk you through building a truly variable digital delay line in Pure Data (Pd) – no external objects, just vanilla Pd objects and a bit of math. By the end you’ll have a delay that can glide from a few milliseconds to several seconds without clicks or glitches.
Why a Variable Delay Matters
A fixed‑length delay is fine for simple slap‑back or tape‑style echoes, but modern music production often calls for more. Think of a rising feedback swell, a rhythmic ping‑pong that follows the tempo, or a “space‑craft” effect where the delay time drifts like a ship in orbit. If the delay time changes abruptly, you’ll hear clicks because the read pointer jumps to a new location in the buffer. A smooth, sample‑accurate variable delay avoids that and keeps the sound organic.
The Core Idea: A Circular Buffer
At its heart a digital delay is just a circular buffer – a block of memory that you write incoming samples into and read from a certain distance behind the write head. In Pd we can emulate this with the [delwrite~] and [delread~] objects. The trick to making it variable is to feed a changing delay time into [delread~] while keeping the write pointer moving continuously.
Step 1: Set Up the Buffer
Create a new patch and add a [delwrite~] object. Give it a name (e.g., myDelay) and a maximum length that covers the longest delay you’ll need. If you think you’ll never go beyond 2 seconds at a 44.1kHz sample rate, set it to 88200 samples.
[delwrite~ myDelay 88200]
Connect your audio source (say, a [adc~] or a synth output) to the left inlet of [delwrite~]. This writes every incoming sample into the buffer.
Step 2: Create a Variable Read Position
Add a [delread~] object that points to the same buffer name:
[delread~ myDelay]
The right inlet of [delread~] expects a delay time in milliseconds. To make it variable, we’ll feed it a signal that can change smoothly.
Step 3: Generate a Smooth Control Signal
There are many ways to drive the delay time. For this tutorial I’ll use a simple [phasor~] to create a ramp that we can scale with [*~]. Let’s say we want the delay to sweep from 50 ms to 800 ms over a 10‑second period.
[phasor~ 0.1] ; 0.1 Hz = 10‑second cycle
[*~ 750] ; scale to 0‑750 ms range
[+~ 50] ; offset to start at 50 ms
Connect the output of the [+~] to the right inlet of [delread~]. Now the read head will glide smoothly between 50 ms and 800 ms.
Step 4: Keep the Delay Time in Bounds
If you ever push the control signal beyond the maximum buffer length, Pd will clamp it and you might get unwanted artifacts. A quick safety net is to use [clip~] to force the value between 0 and the maximum delay you set in [delwrite~].
[clip~ 0 800] ; assuming 800 ms is the max we’ll use
Place this just before the [delread~] inlet.
Step 5: Add Feedback (Optional)
A classic delay often includes feedback – feeding part of the delayed signal back into the input to create repeats. To do this, take the output of [delread~], scale it with [*~], and add it back to the original audio before it hits [delwrite~].
[delread~ myDelay] ; delayed signal
[*~ 0.4] ; feedback amount (40%)
[+~] ; sum with original
Make sure the sum goes into the left inlet of [delwrite~]. Adjust the feedback gain to taste; too high and you’ll get runaway oscillations.
Step 6: Mix Wet and Dry
Most of the time you’ll want to blend the original (dry) signal with the delayed (wet) signal. Use a simple [*~] for each path and a [+~] to combine.
[adc~] ; dry source
[*~ 0.7] ; dry level
|
[+~] ; sum with wet
[delread~ myDelay] ; wet source
[*~ 0.5] ; wet level
Feel free to replace the static gain values with [vsl~] sliders so you can tweak them live.
Step 7: Put It All Together
Your final patch should look something like this (text version):
[adc~] ; input
|
[delwrite~ myDelay 88200] ; write to buffer
|
[+~] ; add feedback (optional)
|
[delread~ myDelay] ; read variable delay
[*~ 0.5] ; wet level
|
[+~] ; mix with dry
[*~ 0.7] ; dry level
|
[dac~] ; output
Don’t forget the control chain that feeds the right inlet of [delread~]:
[phasor~ 0.1]
[*~ 750]
[+~ 50]
[clip~ 0 800]
|
[delread~ myDelay]
Step 8: Test and Tweak
Hit the DSP on button and listen. Turn the [phasor~] frequency up or down to change how fast the delay sweeps. Adjust the scaling factor (750) and offset (50) to set the range. Play with feedback and wet/dry levels until the echo sits nicely in the mix.
If you hear clicks when the sweep hits the extremes, double‑check that the [clip~] limits match the buffer size. You can also add a tiny [line~] smoothing block before the [delread~] inlet, but in most cases the phasor ramp is already smooth enough.
A Quick Anecdote
Back in my early days of building a live‑performance rig, I tried to modulate a delay with a cheap LFO pedal. The result was a jittery, almost robotic stutter that made the audience wince. After a few sleepless nights I realized the pedal was stepping in 10‑ms increments, causing the read head to jump. The solution? A true sample‑accurate ramp, just like the [phasor~] we used here. The difference was night‑and‑day – the delay now sang, it didn’t scream.
Going Further
- Tempo‑Sync: Replace the [phasor~] with a [metro]‑driven counter and convert beats to milliseconds using [expr]. This lets the delay lock to your DAW’s BPM.
- Modulation Sources: Try a [noise~], a [cycle~] (sine LFO), or even a [vsl~] knob for manual control.
- Stereo Spread: Duplicate the whole chain, give each side a slightly different delay range, and pan them left/right for a wide, moving space.
That’s it – a fully variable digital delay line built from scratch in Pure Data. The beauty of Pd is that you can see every step, tweak every number, and hear the change instantly. Keep experimenting, and you’ll find new ways to make time itself bend to your musical will.
#digitaldelay #puredata #dsp