Music 220b: Winter 2001
Fernando Lopez-Lezcano, instructor
Christopher Burns, teaching assistant
Tamara Smyth, teaching assistant
Week 7: the delay unit generator
Delay is the building block of a number of computer music techniques: we've seen very short delays applied in digital filters, and longer delays used for reverberation. There are even more applications: the dlocsig unit generator uses delay to simulate doppler shift, and delays are extremely useful in physical modeling synthesis. Delays can be used to achieve effects in either the time or the frequency domain, or in both simultaneously.
So -- how does delay work in CLM?
simple delay: one echo
The "echo-file" instrument (contained in echo-file.ins) demonstrates what is perhaps the simplest use of delay -- creating a single echo. The instrument reads and outputs samples from a soundfile, mixing that output with a delayed copy of the samples.
Delay is provided by a unit generator pair: (make-delay) and (delay). As with other unit generators, we use (make-delay) to create a delay structure during the initialization phase of the instrument, and then access that structure in the run loop using the (delay) function.
(definstrument echo-file (source-filename &key (source-srate 1.0) (start 0.0) (duration nil) (echo-time 0.5)) (let* ((source-file (open-input* source-filename)) (source-file-duration (/ (sound-duration source-filename) (abs source-srate))) (minimum-duration (if (null duration) (+ echo-time source-file-duration) (+ echo-time (min duration source-file-duration)))) (source-src (make-src :input source-file :srate source-srate)) (echo (make-delay :size (* echo-time *srate*))) (current-sample 0.0)) (multiple-value-bind (beg end) (times->samples start minimum-duration) (run (loop for i from beg to end do (setf current-sample (src source-src)) (outa i (* 0.5 (+ current-sample (delay echo current-sample))))))) (close-input source-file)))
Note that (delay echo current-sample) is feeding samples into the delay; they won't appear at the output until (* echo-time *srate*) samples later. If we need to access the current value of the delay generator we can use the (tap) function. Which leads us to the next example....
delay with feedback: an echo-train
If we feed the output of a delay back into its input, we can get a train of decaying echoes. (Or nasty clipping and explosions, if we're not careful). The key is to scale the input so that it decreases with each cycle through the delay line. In the following example, echo-train.ins, the feedback parameter must be set below 1.0 to insure that the delay input and output eventually fades away. The (tap) function is used to apply the delay output to the input.
(definstrument echo-train (source-filename &key (source-srate 1.0) (start 0.0) (duration nil) (echo-time 0.5); controls echo spacing (feedback 0.7) ; should be < 1.0 (gain 0.8) ; scales output (decay-time 10.0)) ; lengthens output file (let* ((source-file (open-input* source-filename)) (source-file-duration (/ (sound-duration source-filename) (abs source-srate))) (minimum-duration (if (null duration) (+ decay-time source-file-duration) (+ decay-time (min duration source-file-duration)))) (source-src (make-src :input source-file :srate source-srate)) (echo (make-delay :size (* echo-time *srate*))) (current-sample 0.0)) (multiple-value-bind (beg end) (times->samples start minimum-duration) (run (loop for i from beg to end do (setf current-sample (src source-src)) (outa i (* gain (+ current-sample (delay echo (+ current-sample (* feedback (tap echo)))))))))) (close-input source-file)))
The (tap) function also enables us to use a single delay line to generate multiple echoes that aren't evenly spaced -- for instance, we could tap a one-second delay line at 0.3, 0.55, and 0.633 seconds to create rhythmic effects. (tap delay offset) produces the sample which is offset samples away from the current output.
Note that we have to use the (decay-time) parameter to lengthen the output file -- otherwise the echo-train will be cut off when the source soundfile ends, probably with an unpleasant click. (Admittedly, an amplitude envelope would help....)
the spectral effect of delay: comb filtering
Short delays can have pronounced spectral effects -- something we need to plan for carefully if we're primarily interested in producing echoes (for instance, in Schroeder reverberator designs). On the other hand, we might be interested primarily in those frequency-domain effects (as when designing digital filters). For example, if we use the echo-train instrument with a much shorter delay time, we've got a classic comb filter. Here's comb-filter.ins: nearly identical to the echo-train instrument but with shorter delays (calculated in samples) by default.
(definstrument comb-filter (source-filename &key (source-srate 1.0) (start 0.0) (duration nil) (delay-samples 799) ; specified in samples (feedback 0.7) ; should be < 1.0 (gain 0.8) ; scales output (decay-time 0.1)) ; lengthens output file (let* ((source-file (open-input* source-filename)) (source-file-duration (/ (sound-duration source-filename) (abs source-srate))) (minimum-duration (if (null duration) (+ decay-time source-file-duration) (+ decay-time (min duration source-file-duration)))) (source-src (make-src :input source-file :srate source-srate)) (echo (make-delay :size delay-samples)) (current-sample 0.0)) (multiple-value-bind (beg end) (times->samples start minimum-duration) (run (loop for i from beg to end do (setf current-sample (src source-src)) (outa i (* gain (+ current-sample (delay echo (+ current-sample (* feedback (tap echo)))))))))) (close-input source-file)))
time-varying delay: flanging
One of the nice things about the delay unit generators in CLM is that they are capable of fractional or interpolating delay -- that is, we can have a delay length of 70.2 samples just as easily as we can have 70.0 or 71.0 samples. The delay interpolates between the actual samples to produce the fractional amounts. This makes time-varying delay effects easy to achieve.
For instance, a classic application of the comb filter is flanging. (If you don't know what flanging sounds like, listen to the Eagles' "Life in the Fast Lane" or practically any guitar track by Veruca Salt). To create a flanger, we sweep the length of a comb filter with an oscillator, so that the delay time changes sinusoidally between a specified maximum and minimum. Easy to do in CLM -- just patch an (oscil) and a (delay) together....
(definstrument flanger (source-filename &key (source-srate 1.0) (start 0.0) (duration nil) (min-delay-samples 599) (max-delay-samples 699) (frequency 0.75) (feedback 0.9) ; should still be < 1.0 (gain 0.5) ; scales output (decay-time 0.1)) ; lengthens output file (let* ((source-file (open-input* source-filename)) (source-file-duration (/ (sound-duration source-filename) (abs source-srate))) (minimum-duration (if (null duration) (+ decay-time source-file-duration) (+ decay-time (min duration source-file-duration)))) (source-src (make-src :input source-file :srate source-srate)) (oscil-width (/ (- max-delay-samples min-delay-samples) 2)) (oscil-center (/ (+ max-delay-samples min-delay-samples) 2)) (oscillator (make-oscil :frequency frequency)) ; oscillator is used to drive the delay length ; to put it another way, the delay length varies sinusoidally (echo (make-delay :size oscil-center :max-size max-delay-samples))) ; using the :max-size variable automatically forces (make-delay) to create an ; interpolating / fractional delay line (multiple-value-bind (beg end) (times->samples start minimum-duration) (run (loop for i from beg to end do (outa i (* gain (delay echo (+ (src source-src) (* feedback (tap echo))) (* oscil-width (oscil oscillator)))))))) (close-input source-file)))
The new wrinkle here is that we're using three parameters for the (delay) function. The first names the delay structure: in this case, echo. The second specifies the current sample going into the delay: (+ (src source-src) (* feedback (tap echo))). The new parameter is the third: (* oscil-width (oscil oscillator)). This specifies the deviation of the delay length from the default (specified in the initialization phase with oscil-center).
Whenever you want an interpolating delay line, be sure to set the :max-size parameter of (make-delay) -- that's CLM's cue to create a fractional delay instead of using a more efficient but less flexible integer delay line. :max-size should be set to the longest delay line (in samples) that you might request during the run loop.
Delays are at the core of many digital signal processing techniques: check 'em out....