Continuous change of delay time causes a pitch shift

Image

 

The way we commonly avoid clicks when changing the amplitude of a sound is to interpolate smoothly sample-by-sample from one gain factor to another, using an object such as line~. Does that same technique work well for making a smooth change from one delay time to another? As it turns out, that’s not the best way to get a seamless unnoticeable change from one delay time to another, because changing the delay time gradually will actually cause a pitch shift in the sound.

This patch demonstrates that fact. When you provide a new delay time, it interpolates to the new value quickly; you’ll hear that as a quick swoop in pitch. You can get different types of swoop with different interpolation times, but this sort of gradual change in delay time always causes some amount of audible pitch change. Of course there are ways to use this pitch change for desired effects such as flanging, but what we seek here is a way to get from one fixed delay time to another without any extraneous audible artifacts.

Change of delay time may cause clicks

Image

The main ways to delay a sound in Max are demonstrated in the examples from the previous quarter that show the delay~ object and the tapin~ and tapout~ objects. You might want to take a look at those examples and read the associated text to review how they work, and what their pros and cons are.

Whenever you change the delay time, you risk causing a click by creating a discontinuity in the output waveform. (The amplitude at the new location in the ring buffer is likely to be different from the amplitude at the old location, so the output waveform will leap instantly from the old amplitude to the new amplitude.) This patch allows you to try that, to confirm that clicks can occur. You might sometimes get lucky and change the delay time at a moment of silence thus avoiding a click, but the odds are that a click will occur. So if you plan to change the delay time while listening, you probably want to try to solve that problem. The next few examples will address the topic.

Rhythmic delays in time with a musical tempo

Image

The tempo-relative timing capabilities in Max can be used to synchronize MSP processing in time with a musical beat. In this example, timings of delays are specified in tempo-relative time units so that they remain rhythmically correct for any tempo.

Click on the button above the click~ and you will hear a click followed by four rhythmically delayed versions of the click (with diminishing amplitudes). The timings of the delays have been set to a dotted eighth note, a dotted quarter note, a double-dotted quarter note, and a half-note-plus-a-sixteenth. The tapout~ object does not interpret tempo-relative time syntax on its own, so we use the translate object to convert time musical units such as 8nd or bars.beats.units such as 0.1.360 into milliseconds.

Now try changing the tempo of the transport to some other tempo such as 96 bpm, and then trigger the click again. Notice how the rate of the rhythm has changed commensurate with the change in the global tempo. Finally, start the transport, which will also start the metro, which will begin playing a different sound every quarter note. Since the delays are all set to happen on sixteenth note pulses somewhere between the quarter notes, you will hear a composite rhythm that is more complex than is actually being played by the metro. If you change the tempo, the delays will stay in the same rhythmic relationship to the quarter-note beat.

Delay with feedback

Image

 

The delay~ object does not permit its delayed signal to be fed back into its own left inlet. (You can probably imagine how that would make it impossible for MSP to calculate the correct signal, since you’d be asking it to perform infinite recursion: the signal plus the delayed signal plus the delayed sum of those two plus…). If you want to get delay with feedback, you need to use objects that are designed to handle that.

The tapin~ and tapout~ objects together work similarly to the delay~ object. The tapin~ object allows you to create a ring buffer containing the most recently received signal, and you specify the size of the buffer as the argument to tapin~ — in terms of milliseconds. (The size in samples will be calculated automatically based on the sample rate.) A tapout~ object that’s connected to the tapin~ object will refer to that ring buffer and will look a certain amount of time in the past for its output (whatever number of milliseconds you specify). In this case we typed in 1000 ms as the delay time for tapout~ to use, but that can be changed with a number in the inlet.

Unlike the delay~ object, which can have a delay time as small as one sample, tapout~ has a minimum delay time of one signal vector. (The signal vector size is specified in the Audio Status window.) This might be a disadvantage if you want extremely short delay times (in which case delay~ might be a better object to use), but it has the advantage of permitting its output to be fed back into the inlet of tapin~ (because MSP can calculate each vector’s samples without having to account for the delayed sound from the same vector). Feeding the delayed sound back into the delay buffer allows for echos of echos, giving the potential for a series of repeating echos using a single delay tap. But one must be careful not to create a situation in which the sum of the original signal and the delayed signal, plus a delayed version of that, etc., grows ever louder and causes clipping. For that reason, you almost invariably will want to multiply the delayed signal by some number between 0 and 1 to scale down its amplitude.

Ducking when changing delay time

Image

 

Whenever you change the delay time, you’re asking MSP to look at a new location in the delay buffer, which can cause a click in the output if the new sample value is very different from the previous one. One way to get around that is to quickly fade the output amplitude down to 0 whenever you make a change, then quickly fade it back up once the change has been made. In this example, whenever a new delay time comes out of the number box, it first sends a 0 to the pack object, which sends a ‘0 5’ message to line~, which fades line~’s signal down to 0 in 5 milliseconds (meaning the output signal of delay~ will get multiplied by 0, silencing it). The delay time meanwhile is held for 6 milliseconds by the pipe object, thus waiting for the fade-down to be completed by line~. Only then does the new delay time get passed on to the delay~ object, and then a 1 gets sent to pack, sending a ‘1 5’ message to line~, fading the sound back up in 5 ms. The net effect is that we do get a very quick (11-ms) fadedown/fadeup in the output of delay~, but at least it’s not a jarring click.

Delay with tempo-relative timing

Image

The delay attribute (the delay time) of a delay object can be specified with tempo-relative timing (such as notevalues) instead of samples. This example shows a delay time of a dotted eighth note rhythmic value, at whatever the current transport tempo is. The transport tempo is 120 bpm by default; a dotted eighth note at that tempo will last 375 ms which, at a sample rate of 44100 samples per second, is 16,537.5 samples. Specifying the delay time as a rhythmic value allows you not to have to worry about the math of exactly how many samples of delay you need (other than making sure that you create a big enough buffer for delay~). In this case, 88200 samples’ worth of memory space will be a great plenty for a dotted eighth note at any reasonable tempo.

This example shows the use of a gain~ object so that we can control the level of sound that is coming into MSP from the audio input (which allows us to turn the audio input down to 0 if we need to do so), and a meter~ object to show how much signal is being sent to delay~. There is also a subpatch that allows you to control the balance between direct input sound and delayed sound. The right inlet of the subpatch expects a value from 0 to 1, where 0 is all direct sound and no delayed sound, a value of 1 is all delayed sound and no direct sound, and a value of 0.5 will be a half-and-half mix of the two. In audio processing terminology this value is often referred to as the “wetness” or the “wet/dry mix”, meaning the balance between the effect and the original sound. (Double click on the patcher object to see its contents.) This kind of simple mixer is frequently useful when applying audio effects.

Simple delay of audio signal

Image

 

The delay~ object creates a “ring buffer” into which it constantly records the signal coming in its left inlet. The first typed-in argument specifies the size of the buffer, in samples. The second argument (or a number in the right inlet) specifies how many samples in the past delay~ should look for the signal it will send out its outlet. So, assuming a sample rate of 44100 Hz, this patch creates a one-second buffer and instructs delay~ to send out the sound it received 1/10 of a second ago.

Entering those values as typed-in arguments makes a rather unsafe assumption about the sample rate. (The sample rate could be 48000 or 96000 or whatever the user has set it to.) A better way to make sure you’re allotting the correct amount of memory and specifying the correct delay time is to use an [adstatus sr] object (which can be triggered by loadbang to report the sample rate) to calculate the size of the buffer in samples (in this case we know we want 1 second’s worth of memory, so we can use the sample rate directly), and use the mstosamps~ object (which will make its calculation based on the current sample rate) to convert the desired delay time from milliseconds to samples.

When the click~ object receives a bang it sends out a single sample with a value of 1; it sends out all 0 values otherwise. That’s a very clearly defined moment in time (known as an “impulse”) that lets you confirm that delay~ is doing what it’s supposed to do.