Does [[delay]] use variable sample rates when changing delay times?

pascalargon's icon

Hi all,
I've been trying to port a small gen~ patch to the Teensy audio format to process live audio on a Teensy 4.1 --- specifically trying to recreate the pitch modulation that happens during interpolated changing of the delay rate.
What I don't understand is what causes this pitch shift within the delay object. Does a smooth change prompt a difference in the sample rate of the queued audio information?

For reference, here's the patch I'm trying to bring over:

Max Patch
Copy patch and select New From Clipboard in Max.

Many thanks!
PA

Roman Thilenius's icon


unlike delay~ in max, [[delay]] in gen is a tapping buffer.

if you dont want it to pitch when you change the time value you have to work your way around this using logic gates, polyphony and/or windowing - or clear the buffer somehow.

pascalargon's icon

Hi Roman,
Totally --- it's the synchronous environment that draws me to gen~ in the first place (wasn't aware of the difference in formatting [[delay]]!). It's exactly how it handles the tapping buffer in a way that results in pitch modulation that I'm curious about.
I'm trying to recreate this pitched delay effect in C, using the Teensy audio library, to run on a powerful embedded system. What specifically causes that change in pitch?

Graham Wakefield's icon

@Roman is exactly right.

To explain in more detail: [delay] in gen~ is like a digital equivalent of a tape delay. The tape loop is always moving at samplerate, every new input fills the next sample-slot in the tape. The delay time sets how far back in this tape the "read head" looks to get the next output sample.

If you change the delay time instantaneously, e.g. by changing a [param] value by sending a message to gen~, then the read-head instantanously jumps to a new location. It's likely to cause a click because suddely the waveform is being read at a different location. If you scroll the [param] it will sound distorted because of so many such jumps.

On the other hand, if you smooth the [param], or drive the delay time with a slowly-changing audio-rate signal, then you're gradually moving the read head back & forth around the tape. Each jump is now very small and doesn't cause glitches. But if you move the read head away from the write head, it's exactly like playing the tape more slowly, and vice versa -- or like playing a buffer~ faster or slower. In fact it's exactly like the Doppler shift of a fast-moving object in space: moving away will effectively stretch the wavefronts out, lowering the pitch, and moving closer will compress the wavefronts, which raises the pitch.

If you want to change delay times without pitch effects or glitches, you probably want to use a ducking or crossfading method. This can be done using a delay with 2 taps, e.g. [delay 44100 2]. Feed the two taps (A and B) into a [mix] to crossfade them. Normally only A or B would be heard (xfade value of 0 or 1). When changing delay time (instantaneously), set the new delay time on which ever tap is currently unheard, then trigger a crossfade. E.g. if xfade is 0, we are only hearing tap A. Set the new delay time to tap B. Ramp xfade from 0 to 1 (so now we are hearing tap A with the new delay time). The crossfade can be just a handful of milliseconds long, enough to mask the sudden waveform change.

Graham

pascalargon's icon

Thanks, Graham. Got it.
I guess I needed to be reminded that gen does not have a variable sample rate because the whole thing is synchronous. Pitch is not inherent in any one sample --- it has to do with the relation of groups of samples.

If I understand it correctly --- if you have a fixed sample rate, stepping through the numbers 0-9 should take 10 samples. Only playing the even numbers should take half the time to step through the entire set, meaning it will be much “higher pitch”. The way to recreate this pitch modulation might be to effectively "skip" samples when shortening delay time / speeding up, and "loop" samples when lengthening / slowing down. The number of samples to skip or loop would depend on the rate of change of [param], akin to pulling on a piece of tape.

Much appreciated!

Graham Wakefield's icon

"Pitch is not inherent in any one sample --- it has to do with the relation of groups of samples" -- a very nice observation that has a lot of implications!

Here's an example of quick crossfading between two taps to achieve delay time changes without pitch shift:

Max Patch
Copy patch and select New From Clipboard in Max.

Roman Thilenius's icon


"What specifically causes that change in pitch?"

i guess the built-in interpolation seemed more appropiate for gen~?

it is the only alternative to producing clicks. pest and cholera and you have to choose one. :)

but i see where you are coming from: yea, it is different in tapout~.

Graham Wakefield's icon

It's not the built-in interpolation, just the nature of reading from a buffer-based delay. By the way, tapout~ and delay~ aren't any different, they are both also buffer-tapping delays, and have exactly the same situation.

Open the tapout~ helpfile, play an oscillator tone into it. If you drag around the number box that sets delay time, you'll hear clicks. Hit the message box going to the [line~] object and you'll hear pitch shift. These are both for exactly the same reasons as mentioned in my post above.

Delay~ (the MSP object) applies an internal ramp to smooth the delay time changes, also like I described above. If you look at the delay~.maxhelp file, the object there defines a default ramp-time of 4410 samples. Hook a number box up to the delay attrui and wiggle it, you'll hear the tiny wobble of that ramp. Switch to the "signal-rate" tab and turn on the audio. You'll hear the "Doppler" pitch shift effect of a continuously varying delay time.

If you *don't* want the pitch shifting effects, and don't want clicks, then you need to use a crossfading method like in the patcher I shared above. (This applies to delay~ and tapout~ too.)

Jean-Francois Charles's icon

To second what Graham said, looking into how a tape delay works might also help: to change the delay time, you have to change the distance between record head and playback head (or change the tape speed): while you're doing that, you get pitch shift. Some guitar pedals model the delay exactly like this (with the Boss DD-7, you get an expression pedal input to "move the playhead", a way to get pitch shifting effects).

pascalargon's icon

Absolutely. My goal is entirely to recreate the doppler effect of moving a play/record head. I'm specifically interested in how the sample line of queued audio information is processed within the gen~ delay object --- how does it read this information in a non-linear chronological way that gives this modulated pitch?
I guess this is a tricky question because the object's source code isn't openly available (as far as I know), so I thought I'd ask for pointers. From what I gather, skipping or looping samples (or grains) depending on rate of change is a good start.

Roman Thilenius's icon


@ graham, no, wait, when you change the time in a tapout~ it doesnt pitch, it jumps.

Graham Wakefield's icon

@Pascalargon: The object's source code *is* available. Send 'exportcode' message to the gen~ object, and take a look at the files it generates. The gen_dsp folder that is also generated contains a genlib_ops.h file in which many of the object implementations can be found.

I tried to explain exactly what makes the pitch effect in the post above. It's because as you move the read head (change the delay time) you are moving through the delay buffer's samples faster or slower than the rate at which they were written (which was real-time samplerate).

@Roman please check my post above about the tapout helpfile. It's the same as [delay] in gen~. If you change delay time of [tapout~] instantaneously it will jump (often with a click). If you change the delay time *gradually* e.g. using [line~] you will get a pitch shift.

tl;dr: if you want pitch shift in a delay, change the delay time *gradually*. If you want instantaneous jumps and don't care about clicks, change the delay time *instantaneously*. If you want jump without pitch changes or clicks, use the crossfading trick in the patch I posted above.

Roman Thilenius's icon

of course you can do both with both. my point was that it is different by default - i didnt interpret his gen patcher correctly and didnt see the interpolation he put in. :)