CCRMA

Substractive Synthesis, Digital Filters, Common Music Patterns


Lecture Slides

A series of gif images of the lecture slides... (only accesible from within Stanford University)


Common Music Patterns

Introduction to Common Music patterns. See the Common Music Manual, in particular the Common Music reference and the section on patterns

Pattern Classes

During the class we took a look at the simplest classes or types of patterns. Each class of patterns has a different behavior in how elements are selected:

cycle
circular selection of elements in the pattern
line
go from first to last, repeat last
palindrome
first to last and back
heap
random selection without replacement
random
random selection with replacement

Patterns are objects created with the "new" macro. The following line stores a cyclic pattern (cyclic patterns are the default type of pattern) that contains five elements in the global variable "pat":

  (set pat (new pattern 1 2 3 4 5))

We can access elements through the "next" function, which can either return one element at a time or a whole period of selected elements:

  (next pat)
  1
  nil
  (next pat)
  2
  nil

or if we want a whole "period" returned in a list we add an optional argument with the value "t":

  (next pat t)
  (1 2 3 4 5)

by default the period of a pattern is equal to the number of elements it contains. We can create a specific kind of pattern by naming its type in two equivalent ways:

  (setf pat (new pattern 1 2 3 4 5 in cycle))
  (setf pat (new cycle 1 2 3 4 5))
  (setf pat (new pattern 1 2 3 4 5 in random))
  (setf pat (new random 1 2 3 4 5))

Patterns can be nested to arbitrary depths, the following pattern contains a random sub-pattern as element four:

  (setf pat (new cycle 1 2 3 (new random 10 20 30) 5))
  (next pat t)
  (1 2 3 10 30 20 5)

Note that the included pattern completes its own period before returning control to the enclosing pattern. This behavior can be changed by altering the default period of the pattern using the for keyword:

  (setf pat (new cycle 1 2 3 (new random 10 20 30 for 1) 5))
  (next pat t)
  (1 2 3 10 5)
  (next pat t)
  (1 2 3 30 5)

The period itself could be a pattern!:

  (setf pat (new cycle 1 2 3 (new random 10 20 30 for (new heap 1 2)) 5))
  (next pat t)
  (1 2 3 20 10 5)
  (next pat t)
  (1 2 3 10 5)

If you want to supply a prebuilt list of elements to the new macro just use the of keyword followed by a list of elements:

  (setf elements '(1 2 3 4))
  (setf pat (new heap of elements for 3))

Some tools included in Common Music

See the tools section in the Common Music reference manual for more details. There are some interesting functions that let you translate between different representation of musically interesting magnitudes (pitch/frequency, tempo/seconds, loudness/amplitude).

note
translates to note names from note names, frequencies or key numbers
keynum
translates to key numbers from note names or frequencies
hertz
translates to frequencies either note numbers or key numbers

The last one is particularly interesting for controlling CLM instruments as most of them can only deal with frequency and not the most common note numbers or key numbers that represent the popular twelve tone tempered scale (Common Music can also work with other scales...). For example, we could be controlling our fm-violin using a heap pattern of note numbers that we translate into frequencies:

(with-sound()
  (let* ((notes (new pattern 64 66 68 61 60 in heap)))
    (loop
      for time from 0 by 0.1 repeat 10 do
      (fm-violin time 0.1 (hertz (next notes)) 0.1))))

Common Music patterns can be told to use a function to parse the elements selected before returning them by using the "parsing" keyword:

(with-sound()
  (let* ((notes (new pattern 64 66 68 61 60 in heap parsing #'hertz)))
    (loop
      for time from 0 by 0.1 repeat 10 do
      (fm-violin time 0.1 (next notes) 0.1))))

The translation is now handled by the pattern itself. Of course our elements could be also mixed note names and key numbers and hertz will correctly translate both:

(with-sound()
  (let* ((notes (new pattern 64 66 68 cs6 cf6 60 in heap parsing #'hertz)))
    (loop
      for time from 0 by 0.1 repeat 10 do
      (fm-violin time 0.1 (next notes) 0.1))))

Functions also exist that deal with rhythmic time and logical amplitudes

CLM instruments and Common Music objects

When you compile a CLM instrument Common Music also creates a Common Music class that knows the parameter structure of the instrument itself. So, instruments can be called as functions within a with-sound macro or can be instantiated from the corresponding Common Music instrument class and then rendered from within the context of Common Music. See Reading and Writing Events in the IO section of the Common Music reference manual.

In the following example we create a single fm-violin object and render it using the "events" Common Music function:

(setf a-note (new fm-violin time 0 dur 1 frequency 440 amplitude 0.1))
(events a-note "/zap/test.snd")

Compare it with doing the same thing in pure clm:

(with-sound()(fm-violin 0 1 440 0.1))

The new macro creates a new fm-violin object with the named values for its parameters. The events function takes an object or a container of objects and renders it to the specified destination (in this case the "/zap/test.snd" soundfile). Note that events does not play the resulting file, you will have to use (dac "/zap/test.snd") to play it.

Both ways of sounding clm notes are equivalent. The Common Music object oriented abstraction will be more useful latter on. We could obviously create a container full of fm-violin objects and render it with events:

(setf some-notes
      (loop repeat 8
	with notes = (new heap 64 65 69 54 parsing #'hertz)
	with rhythms = (new heap q q e e.. s s parsing #'rhythm)
	with amplitudes = (new random 0.1 0.01 0.2)
	for time from 0 by (rhythm 'e)
	collect (new fm-violin
		     time time
		     dur (next rhythms)
		     frequency (next notes)
		     amplitude (next amplitudes))))
(events some-notes "/zap/test.snd")

The Common Music object representation of CLM instruments has a potential advantage when dealing with algorithmic composition. Instantiated CLM instrument objects are persistent and can be manipulated in several ways before being rendered. For example we could loop through a container full of objects and change one of the parameters before sending the objects to events to be rendered. In the following (nonsensical, just to illustrate the point) example we create a contained (list) full of fm-violin notes with time = 0 (that is, a gigantic "chord" if rendered as is). Then we iterate through the list a assign starting times to the notes:

(setf some-notes
      (loop repeat 8
	with notes = (new heap 64 65 69 54 parsing #'hertz)
	with rhythms = (new heap q q e e.. s s parsing #'rhythm)
	with amplitudes = (new random 0.1 0.01 0.2)
	collect (new fm-violin
		     ;; we don't define a time at this point
		     ;; we just set it to 0
		     time 0
		     dur (next rhythms)
		     frequency (next notes)
		     amplitude (next amplitudes))))

;; "in" iterates in a list
(loop for note in some-notes
  ;; the whole thing starts at 0
  with time = 0
  ;; create a pattern for start rhythms
  with start = (new random q q e e s s s s parsing #'rhythm)
  do
  (sv note time time)
  (incf time (next start)))

(events some-notes "/zap/test.snd")

"sv" sets a particular parameter of a Common Music object to a value.

Substractive Synthesis

Basic concepts.


Filters

Definition
input -> operation -> output; anything is a filter... usually applied to devices that boost or attenuate regions of the spectrum.
Characterization
amplitude versus frequency response curve
  • lowpass
  • highpass
  • bandpass
  • bandreject (notch)
  • shelving
Cutoff Frequency
half power point (0.707 or -3dB)
Center Frequency
[maximum|minimum] amplitude in a [bandpass|bandreject]
Stopband vs Passband
Bandwidth
Slope
Q and Gain
Comb and Allpass Filters

Examples

Here is a bunch of very simple instruments that use the stock filters that come with the clm distribution ("/usr/ccrma/lisp/src/clm").


onepole.ins

A simple One Pole filter (filtering white noise)...

(definstrument onepole(start-time duration amplitude
				  &key
				  (b1 '(0 0.5 1 0.5)))
  (multiple-value-bind (beg end) (times->samples start-time duration)
    (let* ((noise (make-rand :frequency (* 0.49 *srate*) 
			      :amplitude amplitude))
	   (b1-env (make-env :envelope b1
			     :duration duration))
	   (opfilt (make-one-pole :a0 1.0 :b1 0.5)))
      (run
       (loop for i from beg to end do
	     (setf (mus-b1 opfilt) (env b1-env))
	     (outa i (one-pole opfilt (rand noise))))))))

onezero.ins

A simple One Zero filter (filtering white noise)...

(definstrument onezero(start-time duration amplitude
				  &key
				  (a1 '(0 0.5 1 0.5)))
  (multiple-value-bind (beg end) (times->samples start-time duration)
    (let* ((noise (make-rand :frequency (* 0.49 *srate*) 
			      :amplitude amplitude))
	   (a1-env (make-env :envelope a1
			     :duration duration))
	   (ozfilt (make-one-zero :a0 1.0 :a1 0.5)))
      (run
       (loop for i from beg to end do
	     (setf (mus-a1 ozfilt) (env a1-env))
	     (outa i (one-zero ozfilt (rand noise))))))))

ppolar.ins

A simple Two Pole filter with resonance based on the ppolar clm ug (filtering white noise)...

(defmacro b1-from-r-freq (r freq) 
   `(- (* 2.0 ,r (cos (hz->radians ,freq)))))
(defmacro b2-from-r (r) `(* ,r ,r))

(definstrument twopole(start-time duration amplitude
				  &key
				  (freq '(0 20 1 10000))
				  (r '(0 0.5 1 0.5)))
  (multiple-value-bind (beg end) 
      (times->samples start-time duration)
    (let* ((noise (make-rand :frequency (* 0.49 *srate*) 
			     :amplitude amplitude))
	   (freq-env (make-env :envelope freq
			       :duration duration))
	   (r-env (make-env :envelope r
			    :duration duration))
	   (ppfilt (make-ppolar :r 0.5 :frequency 440.0)))
      (run
       (loop for i from beg to end do
	     (let* ((freq0 (env freq-env))
		    (r0 (env r-env)))
	       (setf (mus-b1 ppfilt) (b1-from-r-freq r0 freq0))
	       (setf (mus-b2 ppfilt) (b2-from-r r0))
	       (outa i (two-pole ppfilt (rand noise)))))))))

formnt.ins

A simple Two Pole / Two Zero formant filter (filtering white noise)...

(defmacro set-formnt(filter freq r)
  `(let* ((freq ,freq)
	  (r ,r))
     (setf (mus-a2 (identity ,filter)) (- r)
	   (mus-b1 (identity ,filter)) (- (* 2.0 r (cos (hz->radians freq))))
	   (mus-b2 (identity ,filter)) (* r r))))

(definstrument simp-formnt(start-time duration amplitude
				  &key
				  (freq '(0 20 1 10000))
				  (r '(0 0.707 1 0.707)))
  (multiple-value-bind (beg end) 
      (times->samples start-time duration)
    (let* ((noise (make-rand :frequency (* 0.5 *srate*) :amplitude amplitude))
	   (freq-env (make-env :envelope freq
			       :duration duration))
	   (r-env (make-env :envelope r
			    :duration duration))
	   (fmfilt (make-formant :frequency 440 :radius 0.99)))
      (run
       (loop for i from beg to end do
	     (set-formnt fmfilt (env freq-env) (env r-env))
	     (outa i (formant fmfilt (rand noise))))))))

All of these example instrument do some internal contortions to move through envelopes the center frequency, resonance and or assorted internal coefficients. Hope you can figure things out. Believe it or not everything that's being done is documented in the clm manual... :-)

butterworth.cl

A pair of quite good low pass, high pass, band pass and reject filters from the clm distribution. Just copy the file butterworth.cl. Take a look at the docs. Here's an example instrument (butter.ins), courtesy of Juan Pampin.

moog.lisp

Tim Stilson's implementation of a 24db/octave Moog low pass filter, with variable resonance... back to that warm analog sound! Find the source in moog.lisp. There's also a simple example instrument (moog-filter.ins) that can filter a soundfile (and also defines a very handy with-moog that uses sound-let to do its magic). If you really want to read all the gory details on how the magic works go to Tim Stilson's Home Page and follow the link that points to his papers.

addflt.ins

Take a look at a more sophisticated example that is included in the clm distribution, a multiple resonante filter instrument called addflt.ins

Adding filtering to instruments

Two approaches to adding filtering to instruments.

In the first one we modified the code of the original v.ins (Bill's fm-violin) and added a moog filter based of Tim Stilson's moog filter. Needless to say, for this to work we have to first compile and load the moog filter code moog.lisp. After that, compiling and loading the modified vmoog.ins fm violin file will generate a mutant fm-violin that understands two additional parameters, freq-env: the frequency envelope for the cutoff frequency of the moog filter and res-env: an envelope that controls the resonance of the filter.

We can also create a general purpose moog filter instrument that processes a soundfile. The code can be found in moog-filter.ins. One way to use this instrument to process arbitrary clm instrument output is to use a sound-let to create a temporary soundfile that holds the notes that are to be filtered. Better still, we can create a custom macro that adds some syntactic sugar and makes things look better. See the "with-moog" macro in the previous file plus an example on how to use it.


Assignment #3 [due on January 30th]

Please send this assignment to Michael Gurevich (gurevich@ccrma)

The example streams.lisp demonstrates an illusion resulting from the ear's tendency to group a rapid note sequence according to register. The "streaming effect" is encountered when a musical line of alternating registers is played slowly and then faster. You can hear multiple voices in the rapid part but only one voice with changing register in the slow one (the first one to play). If you play the example with-sound you will hear the same melody repeated twice at different tempos. The first time through it is (hopefully) perceived as just one melody. The second time through the same notes are played faster and perceptually the higher pitch notes are grouped together in a separate melody (from the other lower pitched notes). The effect is more marked for higher tempos. Like in any illusion there is a gray range of borderline tempos in which the perception can be "forced" to be just one melody or two separate melodies. Above that range (which obviously is subjective and depends on the listener) streaming is said to occur as the melodies start to distinctly separate.

Streaming also occurs for other musical reasons, pitch is not the only musical magnitude that can cause it. Starting with streams.lisp, create a version with a similar repeated sequence using 4 pitches but which are within one octave so that register streaming is less likely to occur (ie: we try to make sure that pitch is not the main perceptual magnitude that causes streaming). Instead of register, create the sequence with an alternation of 2 (easy) or 3 (more difficult) distinct timbres, for example, bright-sustained, dull-inharmonic-sustained, and bright-transient (these are just subjective descriptions of arbitrary timbres - the point is: the same note with the same pitch should sound very different). Use envelopes and FM parameters of the fm-violin instrument to create the differences. Example parameters you might want to play with include (but are not restricted to):

fm-index
global fm index (amount of frequency modulation) applied to the carrier oscillator. Higher numbers produce higher number and strength of harmonics (ie: a brighter sound). There are three modulators in the fm-violin, this parameter affects all of them at the same time. There are additional parameters that let you tweak each modulator's fm index individually.
amp-env
global note-level amplitude envelope. Can be used to generate, for example, sudden attacks and thus percussive sounds
fm1-env, fm2-env, fm3-env
individual envelopes for all three modulation oscillators, can be used to change the modulation index as a function of time. Particularly effective for percussive sounds is setting the fm-env[n] to also be percussive (in naturally occuring percussive sounds the higher partials decay much faster than the fundamental)
fm1-rat, fm2-rat, fm3-rat
carrier to modulation ratios for all three modulation oscillators. The default values (1, 3, 4) are integers that create overtone or partial components in the resulting sound that are all integer multiples of the fundamental frequency. Integer fm[n]-rat's always do that. Non-integer ratios generate non-harmonic overtones or partials. Non-harmonic partials are normally associated with bells or drums, so non-integer ratios are good for emulation of percussive sounds of that nature.

A timbre streaming effect should be possible.

The goal of the assignment is to create a repeating melody that uses only one instrument (the fm-violin) but where some of the notes in the repeating sequence (always the same ones) have distinctly different timbral qualities. The resulting sequence should be played twice, once at a slow tempo and again at a faster tempo. The slow tempo version should be perceived as a single melody with changes in timber, the faster playback should stream the notes that share the same timbre together.


©1998, 2001-2002 Fernando Lopez-Lezcano. All Rights Reserved.
nando@ccrma.stanford.edu