23 Spectral Composition

Example 23-1. Spectral snapshot of Clarinet tone in Figure 1.

(define clarinet
  '(1.00 .0842  2.01 .0017  3.00 .0420  4.01 .0029  
    5.00 .0251  6.02 .0041  6.99 .0115  7.96 .0006
    8.99 .0032  9.98 .0003  11.00 .0018 11.96 .0003
    12.96 .0006 14.00 .0001 14.97 .0001 15.97 .0005
    17.95 .0002 18.96 .0001 19.07 .0001 22.06 .0001))

Example 23-2. Spectral processing tools.

(define (spectrum-maxfreq spec)
  ;; return freq of last harmonic in spec
  (list-ref spec (- (length spec) 2)))

(define (spectrum-maxamp spec)
  ;; iterate successive tails of spec and find
  ;; the max amplitude
  (loop for tail = spec then (rest (rest tail))
        until (null? tail)
        maximize (second tail)))

(define (spectrum-freqs spec)
  ;; return only the freq components of spectrum
  (loop for tail = spec then (rest (rest tail))
        until (null? tail)
        collect (car tail)))

(define (spectrum-length spec)
  ;; return number of partials in spec
  (/ (length spec) 2))

Example 23-3. Rescaling spectral data.

(define (rescale-spectrum spec a1 a2 k1 k2)
  ;; rescale spectrum to lie between keynums k1 and k2
  ;; and amplitudes a1 to a2
  (let* ((fund (if k1 (* (hertz k1) (first spec)) 1.0))
         (key1 (keynum fund :hz))
         (key2 (keynum (* fund (spectrum-maxfreq spec)) :hz))
         (soft 0)
         (loud (spectrum-maxamp spec)))
    (loop for tail = spec then (rest (rest tail))
          until (null? tail)
          collect
          (list
           (rescale (keynum (* fund (first tail))
                            :hz)
                    key1 key2 (or k1 key1) (or k2 key2))
           (rescale (second tail) soft loud a1 a2)))))

Example 23-4. Spectral gestures.

(define (strum-it play rate dur amp up?)
  ;; play ascending or descending spectrum  
  (process for s in (if up? play (reverse play))
           output
           (new midi :time (now)
                :keynum (first s)
                :amplitude amp
                :duration (vary dur .1))
           wait rate))

(define (pluck-it play rate dur amp len)
  ;; play spectrum in random order
  (let ((pat (new heap :of play)))
    (process while (< (now) len)
             for s = (next pat)
             output
             (new midi :time (now)
                  :keynum (first s)
                  :amplitude (interp (now) 0 amp len (/ amp 2))
                  :duration (odds .2 (* dur 4)
                                  (vary dur .1)))
             wait rate)))

Example 23-5. The spectral zither process.

(define (spectral-zither spectrum key1 key2 tim1 tim2)
  ;; transpose tonal gestures to successively higher
  ;; positions in spectrum. duration of each gesture is
  ;; proportional to amplitude to the partial it is
  ;; based on
  (let ((spec (rescale-spectrum spectrum
                                tim1 tim2 key1 key2)))
    (process for x in spec
             for k = (first x)
             for a = (second x)
             for topk = (between 100 113)
             for play = (rescale-spectrum spectrum .4 .9 k topk)
             for up? = true then (odds .5)
             sprout (strum-it play .075 2.3 .9 up?)
             sprout (pluck-it play (vary .075 .2) .6 .9 a)
             wait a)))

Interaction 23-1. Playing the zither.

cm> (events (spectral-zither clarinet 10 80 2 6)
            "zither.mid")
"zither-1.mid"
cm> (events (spectral-zither clarinet 100 20 4 3)
            "zither.mid" :channel-tuning 9)
"zither-2.mid"
cm>

Example 23-6. The tam-tam FFT data.

(define tam-tam-data
  '(() () () () ()
    (156.77036815728735 90.637619 259.9496481697027 82.718292
     286.574546692396 100.874054 306.25563026238274 93.336433
     324.7524389568296 100.055481 337.44764076690535 102.149223
     364.6634271878465 230.778137 415.7531705386655 119.252495
     606.0946243520343 86.700752 693.632505316461 99.007416
     793.6624934018535 122.259544 884.1881093270954 157.20726
     933.8675278134929 145.184113 1088.9671367780802 89.725365
     1488.6904580535806 101.671417 1511.527861059107 104.7155
     1618.8507000903057 84.252243 1713.9968634651475 112.454514
     1765.292105410349 89.296089 1890.2215478184416 99.125633
     1908.0848686296388 92.404488 1999.0901910667542 99.439217
     2041.0190392835411 105.710556 2179.3234227680086 88.835609)
    (156.9226656365204 84.869324 260.82045952862836 90.014053
     285.1896751834164 111.457977 307.17019597206075 99.64637
     336.09609235859693 120.298607 362.75665648480026 225.252136
     415.73593078208086 115.339218 655.5235086291844 87.095612
     692.9686610934529 82.759056 793.4209536257428 108.587471
     850.8079688652781 95.086945 883.5759843738408 150.945786
     932.8432789368185 150.02179 1088.3966109501614 85.69706
     1490.71509379789 112.47464 1512.3888337014348 92.084824
     1619.7171692738077 82.596962 1712.6721745280083 102.734337
     1890.1368493016446 97.024925 1908.251766584189 94.600105
     1998.8849421756013 106.950005 2009.4111265591894 83.189438
     2041.6380351691066 102.523079 2402.793452296438 84.39566)))

Example 23-7. Two sample chord events.

(define (event40)
  (let ((data (rescale-spectrum
               (list-ref tam-tam-data 5) 0.5 1.0 #f #f)))
    (process for delta in (append (explsegs 3 0.5 4)
                                  (explsegs 4 0.6 1/2)
                                  (explsegs (- (length data) 7)
                                            2 1/8))
             for s in data
             for count from 0
             each kt in '(0 12 19 24)
             output (new midi :time (now)
                         :keynum (+ (first s) kt)
                         :amplitude (second s)
                         :duration 1.5)
             wait delta)))

(define (event41)
  (let ((data (rescale-spectrum
               (list-ref tam-tam-data 6) 0.7 1.0 #f #f)))
    (process for s in data
             output (new midi :time (now)
                         :keynum (first s)
                         :amplitude (second s)
                         :duration (between 8.0 10.0))
             wait 0)))

Interaction 23-2. Generating sample chords from 1706F.

cm> (events (list (event40) (event41))
            "1706Example.mid" 
            '(0 3.75))
"1706Example.mid"
cm>

Example 23-8. Computing sum and difference notes.

(define (combo-notes n1 n2)
  (if (scale= n1 n2)
    '()
    (let ((f1 (hertz n1 ))
          (f2 (hertz n2)))
      (list (note (abs (- f1 f2)) :hz)
            (note (+ f1 f2) :hz)))))

Interaction 23-3. Calculating sum and difference sets.

cm> (combo-notes 'd5 'e5)
(d2 ef6)
cm> (combo-notes 'a4 'ds5)
(fs3 c6)
cm>

Example 23-9. Synthesizing note sets by ring modulation.

(define (ringmod set1 set2)
  (scale-order (loop for n1 in set1
                     append 
                     (loop for n2 in set2 
                           append
                           (combo-notes n1 n2)))))

(define (play-rings notes rate)
  (process repeat 25
           for k = 70 then (drunk k 3)
           for r = (ringmod (list k) notes)
           for l = (length r)
           output
           (new midi :time (now)
                :duration (* rate l)
                :keynum k :amplitude .6)
           each s in r as i from 1
           output (new midi
                    :time (+ (now) (* rate i))
                    :keynum s
                    :amplitude .3)
           wait (* rate l)))

Interaction 23-4. Ring modulated melodic line.

cm> (events (play-rings '(c4 e4 g4) .1)
            "rings.mid")
"rings-1.mid"
cm>

Interaction 23-5. Generating note sets.

cm> (fm-spectrum 220 1 1)
(220.0 440.0 660.0)
cm> (fm-spectrum 220 1 5 :note)
(a3 a4 e5 a5 cs6 e6 g6)
cm> (fm-spectrum (hertz 'c3) 2 5 :note)
(c3 g4 e5 bf5 d6 fs6 fs6 af6)
cm> (fm-spectrum 220 1 2 :keynum :invert t)
(33.0 37.980453 45.0 57.0)
cm> (fm-spectrum 220 1 9 '(a3 a5))
(a3 cs4 ef4 e4 g4 a4 b4)
cm> (fm-spectrum (hertz 'c3) 1.618 6 '(c4 g5) )
(c4 d4 e4 f4 g4 bf4 b4 cs5 d5 e5 f5 g5)
cm>

Example 23-10. Playing FM spectra.

(define (fm-harmony note )
  (let ((mrat (between 1.0 2.0))
        (indx 1))
    (fm-spectrum (hertz note) mrat indx
                 (list (transpose note -12)
                       (transpose note 12)))))

(define (fm-piano )
  (let* ((rhy (new random
                :of `((q weight 2)
                      (tq :min 3)
                      (,(new cycle :of '(e q e)))))))
    (process for n = 60 then (drunk n 3 :low 40 :high 80
                                    :mode :jump)
             repeat 40 
             for s = (fm-harmony (note n))
             for r = (rhythm (next rhy) 70)
             output (new midi :time (now)
                         :keynum n
                         :amplitude .75
                         :duration (* r .9))
             output
             (new midi :time (+ (now) (rhythm 'e 70))
                  :keynum (list-ref s (pick 0 (1- (length s))))
                  :amplitude .7
                  :duration (* r .9))
             if (odds .6)
             each k in s
             output
             (new midi :time (now)
                  :keynum k
                  :duration (* r .9)
                  :amplitude (between .4 .55))
             wait r )))

Interaction 23-6. Listening to fm-piano.

cm> (events (fm-piano ) "fm-piano.mid")
"fm-piano-1.mid"
cm>

Example 23-11. FM Bell harmony.

(define (fm-bell-notes tone )
  (let ((cen (hertz tone))
        (rat (between 1.1 1.6))
        (ind 2.4))
    (fm-spectrum cen rat ind 
                 (list (transpose tone -12)
                       (transpose tone 12)))))

(defun fm-bell (reps rate)
  (let ((tones (new random
                 :of '((a4 :weight 3)
                       (a3 :max 1)))))
    (process repeat reps
             for tone = (next tones)
             for bell = (fm-bell-notes tone) 
             output (new midi :time (now)
                         :keynum tone
                         :duration 3
                         :amplitude .6)
             when (odds .65)
             
             each k in  bell
             output (new midi :time (now)
                         :keynum k
                         :duration 3
                         :amplitude .4)
             wait rate )))

Interaction 23-7. Listening to fm bells.

cm> (events (fm-bell 20 1.25) "fm-bell.mid")
"fm-bell-1.mid"
cm>

Example 23-12. FM melodic generator.

(define (gen-mel cen rat ind low high)
  (fm-spectrum (hertz cen)
               rat ind
               (list (transpose cen low)
                     (transpose cen high))
               :scale-order nil
               :invert true :unique nil))

(define (fm-melody measure ratlow rathigh 
                   indlow indhigh blow bhigh)
  (let ((starts '(a4 a4 a4 a3 a4 a4 a5 a4)))
    (process for cen in starts
             for rat = (between ratlow rathigh)
             for ind = (between indlow indhigh)
             for mel = (gen-mel cen rat ind blow bhigh)
             for rhy = (/ measure (+ 1 (length mel)))
             output (new midi :time (now)
                         :keynum cen
                         :amplitude .6
                         :duration .4)
             each n in mel as r from rhy by rhy
             output (new midi :time (+ (now) r)
                         :keynum n
                         :amplitude .4
                         :duration 2)
             wait measure)))

Interaction 23-8. Listening to fm-melody

cm> (events (fm-melody 1.8 1.1 pi 2.0 2.5 -24 12)
            "fm-melody.mid")
"fm-melody-1.mid"
cm>

Chapter Source Code

The source code to all of the examples and interactions in this chapter can be found in the file spectral.cm located in the same directory as the HTML file for this chapter. The source file can be edited in a text editor or evaluated inside the Common Music application.