"Tempus Perfectum" — Composing with L-Systems

+ Associated files

close

NB: An exercise relating to the material covered in this tutorial can be found on the Exercises page.

This demo piece, Tempus Perfectum, and its accompanying tutorial, will explore the use of L-systems to generate material for slippery chicken compositions. It also provides a concrete example of the use of slippery chicken with Common Lisp Music (CLM).

More detail is available for both of these slippery chicken features on the L-systems and slippery chicken and CLM pages of the manual.

+ The code

The code is first presented on its own here, then explained point by point below.

NB: It is strongly recommended that the user not copy and paste code from the web browser into the Lisp listener, as this can occasionally lead to errors. The code below can be downloaded in complete form under the Associated files section above.

(let* ((num-seqs 71)
       (src-width 50)
       (sndfile-dir
        (concatenate 'string
                     cl-user::+slippery-chicken-home-dir+
                     "doc/manual/resources/"))
       (ens '(((ob (oboe :midi-channel 1))
               (cl (b-flat-clarinet :midi-channel 2))
               (bn (bassoon :midi-channel 3))
               (hn (french-horn :midi-channel 4))
               (tp (c-trumpet :midi-channel 5))
               (tb (tenor-trombone :midi-channel 6))
               (pr (piano :midi-channel 7))
               (pl (piano-lh :midi-channel 8))
               (vn (violin :midi-channel 9))
               (va (viola :midi-channel 11))
               (vc (cello :midi-channel 12))
               (c1 (computer :midi-channel 13))
               (c2 (computer :midi-channel 14)))))
       (seqs-rules (loop repeat (length (first ens))
                      with l = '(1 2 3 4 5 6 7 8)
                      for p from 0
                      with po = 1
                      with s = nil
                      do (setf s (list (nth (mod p 8) l)
                                       (nth (mod (+ p po) 8) l)
                                       (nth (mod (+ p (* 2 po)) 8) l)
                                       (nth (mod (+ p (* 3 po)) 8) l)))
                      collect (list (1+ p) s)
                      when (= 8 (first s))
                      do (setf po (1+ po))))
       (seqs (make-l-for-lookup 'l-seqs
                                '((1 ((1)))
                                  (2 ((2)))
                                  (3 ((3)))
                                  (4 ((4)))
                                  (5 ((5)))
                                  (6 ((6)))
                                  (7 ((7)))
                                  (8 ((8))))
                                seqs-rules))
       (rsm-lists 
        (loop for s from 1 to (length (first ens))
           for p in (loop for i in (first ens)
                       collect (first i))
           collect (list p (flatten (do-simple-lookup seqs s num-seqs)))))
       (sp '((1 ((cs2 f2 a2 ds2 c3 cs3 d3 gs3 c4 cs4 fs4 g4 gs4 a4 bf4 b4 d5 ds5
                      e5 f5 fs5 a5)))))
       (harms-list (loop repeat (length (second (first rsm-lists)))
                      collect 1))
       (set-lims-progs
        (loop for pchs in '((gs ds5) (g4 fs5) (cs4 fs5) (c4 fs5) (d3 e5) 
                            (d3 e5) (d3 b4) (cs3 b4) (cs3 ds5) (cs3 ds5) 
                            (d3 b4) (cs3 a4) (cs3 e5) (d3 e5) (d3 a5) (c4 a5) 
                            (c4 e5) (c4 d5) (c4 ds5) (c4 f5) (g4 e5) (f2 ds5) 
                            (c4 e5) (a2 f5) (cs2 e5) (cs2 fs5))
           for n from 1
           collect (list n pchs)))
       (set-lims-low (loop for s in set-lims-progs 
                        collect (first s)
                        collect (first (second s))))
       (set-lims-high (loop for s in set-lims-progs 
                         collect (first s)
                         collect (second (second s))))
       (tempus-perfectum
        (make-slippery-chicken
         '+tempus-perfectum+
         :title "Tempus Perfectum"
         :ensemble ens
         :staff-groupings '(3 3 2 3 2)
         :tempo-map '((1 (q 112)))
         :set-palette sp
         :set-map (list (list 1 harms-list))
         :set-limits-high `((all ,set-lims-high)
                            (ob (0 a5 100 a5))
                            (bn (0 g4 100 g4))
                            (hn (0 c5 100 c5))
                            (tb (0 g4 100 g4))
                            (va (0 ds5 100 ds5))
                            (vc (0 g4 100 g4)))
         :set-limits-low `((all ,set-lims-low)
                           (ob (0 g4 100 g4))
                           (hn (0 c3 100 c3))
                           (tp (0 c4 100 c4))
                           (tb (0 a2 100 a2))
                           (vn (0 d4 100 d4)))
         :rthm-seq-palette '((1 ((((3 4) - e (s) s - +q { 3 (te) - te te - })
                                  ((h.))
                                  ((h.))
                                  ((h.))
                                  ((h.))
                                  ((h.)))
                                 :pitch-seq-palette ((1 3 2 3))
                                 :marks (ppp 1 pp 2 ppp 3 cresc-beg 3 cresc-end
                                             4 )))
                             (2 ((((3 4) (h.))
                                  ((h.))
                                  (- e.. 32 - { 3 - +te te te } - - +s s - (e))
                                  (- s. 32 +e - - s s - (e) (q))
                                  ((s) - s (s) s - (q) - e e -)
                                  ((h.)))
                                 :pitch-seq-palette ((5 7 9 7 6 7 6 9 5 6 3 1
                                                        2))
                                 :marks (mf 1 s 1 2 p 3 mf 7 s 7 8 ppp 9 s 12
                                            13)))
                             (3 ((((3 4) (h.))
                                  ((q) q { 3 (ts) ts (ts) } e )
                                  (- e s s - +q q)
                                  ({ 3 (ts) - ts ts - } (e) { 3 (ts) te } 
                                     { 3 - +te ts - } q) 
                                  (h.)
                                  (+q - +e s - (s) q))
                                 :pitch-seq-palette ((10 5 7 1 3 4 10 14 8 6 7
                                                         6 10 12 7))
                                 :marks (pp 1 cresc-beg 1 cresc-end 2 f 2 p 3
                                            mf 4 dim-beg 4 dim-end 9 pp 9 mf 11
                                            dim-beg 11 dim-end 15 ppp 15
                                            cresc-beg 15 cresc-end 16 f 16 s
                                            17 mf 18 te 18)))
                             (4 ((((3 4) { 5 - fe fs fs fs - } { 3 te tq } 
                                   - +e e -)
                                  ((h.))
                                  (q (q) (e) e)
                                  ((e) e (h)) 
                                  (- s s - (e) - e (s) s - - e e -)
                                  ((e.) s +h))
                                 :pitch-seq-palette ((9 7 8 6 3 1 9 9 8 8 7 8 3
                                                        8 2 1 (9)))
                                 :marks (mf 1 dim-beg 1 s 4 ppp 6 dim-end 6 mf
                                            8 at 8 cresc-beg 8 cresc-end 11 f
                                            11 s 12 p 13 a 15 s 16 at 17 mf
                                            17)))
                             (5 ((((3 4) (e) q. - +s e. -)
                                  (e (e) (s) - e s - +e (e))
                                  (e (e) (e.) s - +e. s -)
                                  (- +s e. - (e.) s (q)) 
                                  (e (e) (h))
                                  (e (e) (h)))
                                 :pitch-seq-palette ((1 2 (5) 1 3 2 3 2 1 1 1 
                                                        (5)))
                                 :marks (fff 1 dim-beg 1 mf 3 dim-end 3 ppp 4
                                             cresc-beg 8 cresc-end 9 cresc-beg 
                                             11 cresc-end 13 f 15)))
                             (6 ((((3 4) (h.))
                                  ((e) e (h))
                                  ((h.))
                                  ((h.)) 
                                  ((h.))
                                  ((q) e (e) (q)))
                                 :pitch-seq-palette ((1 (1)))
                                 :marks (p 1 f 2)))
                             (7 ((((3 4) e (e) { 3 - ts ts ts - } (e) (q))
                                  ((h.))
                                  ((h.))
                                  ((h.)) 
                                  ((e..) 32 - e e - (q))
                                  ((h.)))
                                 :pitch-seq-palette ((2 5 4 3 1 6 7))
                                 :marks (ppp 1)))
                             (8 ((((3 4) (h.))
                                  ((h.))
                                  ((h) (e..) 32)
                                  (e (e) (h)) 
                                  ((h.))
                                  (h (q)))
                                 :pitch-seq-palette ((4 1 (2)))
                                 :marks (ppp 1 ff 3))))
         :rthm-seq-map (list (list 1 rsm-lists))
         :snd-output-dir "/tmp"
         :sndfile-palette `(((vocal-sounds
                              ((voice-womanKP-18 :frequency 1028)
                               (voice-womanKP-20 :frequency 456)
                               (voice-womanKP-21 :frequency 484)
                               (voice-womanKP-22 :frequency 591)
                               (voice-womanKP-23 :frequency 662)
                               (voice-womanKP-26 :frequency 516)
                               (voice-womanKP-29 :frequency 629)))
                             (mouth-pops-clicks
                              ((mouth_pop_2 :frequency 375)
                               (mouth_pop_2a :frequency 798)
                               (mouthnoises2 :frequency 703))))
                            ,(list sndfile-dir)
                            ("wav")))))
  (clm-play tempus-perfectum 1 '(c1) 'vocal-sounds
            :rev-amt 0.07
            :reset-snds-each-rs nil
            :pitch-synchronous t
            :src-width src-width
            :srate 44100
            :header-type clm::mus-aiff
            :data-format clm::mus-bshort
            :sndfile-extension ".aiff")
  (clm-play tempus-perfectum 1 '(c2) 'mouth-pops-clicks
            :rev-amt 0.07
            :reset-snds-each-rs nil
            :pitch-synchronous t
            :src-width src-width
            :srate 44100
            :header-type clm::mus-aiff
            :data-format clm::mus-bshort
            :sndfile-extension ".aiff")
  (setf (staff-name (get-data-data 'pl (ensemble tempus-perfectum))) " ")
  (midi-play tempus-perfectum 
             :midi-file "/tmp/tempus-perfectum.mid"
             :voices '(ob cl bn hn tp tb pr pl vn va vc))
  (cmn-display tempus-perfectum
               :file "/tmp/tempus-perfectum.eps"
               :size 11 
               :players '(ob cl bn hn tp tb pr pl vn va vc)
               :in-c t)
  (write-lp-data-for-all tempus-perfectum
                         :players '(ob cl bn hn tp tb pr pl vn va vc)
                         :in-c t))

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

;;; EOF tempus-perfectum.lsp

close

The code explained

+ The concept behind the piece

Tempus perfectum was a term that designated triple meter in the mensural notation systems of the Renaissance. A triple meter based on three duple beats (tempus perfectum prolatio minor) was the equivalent of today's 3/4 time, and was indicated in the score by an empty full circle.[1]

This piece is constructed from self-similar patterns of eight rthm-seq objects that are all in 3/4 time. The specific rhythms and melodic contours of these rthm-seqs are loosely based on a 6-bar passage from Luciano Berio's Circles. In keeping with the cyclical concept and with the source composer, the pitch set used and the sculpting of the ensemble's overall tessitura are loosely based on a passage from Berio's Ritorno Degli Snovidenia.

The sound files chosen for the computer part are samples of female vocalise and mouth sounds (all from the Open Path Music Collection V5 and the Berklee College of Music Sampling Archive V5 sample collections at the One Laptop Per Child (OLPC) Free Sound Samples website), as a further aspect of homage to Berio's landmark work.

Notes

[1] Apel, W (1972). Harvard Dictionary of Music, 2nd Edition, 22nd Printing. Entry on Mensural Notation, p.520. Cambridge, MA: Harvard University Press

close

+ The opening variables

As with Nouveau Reich, the code for this composition begins with a number of fundamental variables, including num-seqs, src-width, and sndfile-dir, which determine the length of the piece, the quality of the sound file sample transposition, and the directory where the sound files used in the computer part are stored.

The sndfile-dir variable uses Lisp's concatenate function together with slippery-chicken's built-in global variable for its home directory to create the string for the path to the sc/doc/manual/resources/ directory.

The ensemble is also predefined here and assigned to the variable ens. This enables the subsequent loops that generate the L-system rules and the rthm-seq-map to be based on the number of players in the ensemble and their IDs, allowing the user to change the instrumentation and have the rules and rthm-seq-map automatically changed accordingly.

(let* ((num-seqs 71)
       (src-width 50)
       (sndfile-dir
        (concatenate 'string
                     cl-user::+slippery-chicken-home-dir+
                     "doc/manual/resources/"))
       (ens '(((ob (oboe :midi-channel 1))
               (cl (b-flat-clarinet :midi-channel 2))
               (bn (bassoon :midi-channel 3))
               (hn (french-horn :midi-channel 4))
               (tp (c-trumpet :midi-channel 5))
               (tb (tenor-trombone :midi-channel 6))
               (pr (piano :midi-channel 7))
               (pl (piano-lh :midi-channel 8))
               (vn (violin :midi-channel 9))
               (va (viola :midi-channel 11))
               (vc (cello :midi-channel 12))
               (c1 (computer :midi-channel 13))
               (c2 (computer :midi-channel 14)))))

close

Using L-systems to generate the rthm-seq-map

+ The elements

The order in which the rthm-seq objects occur in each player's part in Tempus Perfectum is determined using an L-system. The piece has eight separate 6-bar rthm-seqs, each with a numeric ID, so the list of elements in the call to make-l-for-lookup contains references to all 8 of these IDs.

       (seqs (make-l-for-lookup 'l-seqs
                                '((1 ((1)))
                                  (2 ((2)))
                                  (3 ((3)))
                                  (4 ((4)))
                                  (5 ((5)))
                                  (6 ((6)))
                                  (7 ((7)))
                                  (8 ((8))))
                                seqs-rules))

close

+ The rules

The rules for this l-for-lookup object are defined immediately prior to calling make-l-for-lookup and are passed to the function through the variable seqs-rules. They are constructed using a loop to collect a different 4-element set from the numbers 1 to 8 for each of the players in the piece. Having a separate rule for each player ensures that each player will have a different sequence of rthm-seqs in the course of the piece.

       (seqs-rules (loop repeat (length (first ens))
                      with l = '(1 2 3 4 5 6 7 8)
                      for p from 0
                      with po = 1
                      with s = nil
                      do (setf s (list (nth (mod p 8) l)
                                       (nth (mod (+ p po) 8) l)
                                       (nth (mod (+ p (* 2 po)) 8) l)
                                       (nth (mod (+ p (* 3 po)) 8) l)))
                      collect (list (1+ p) s)
                      when (= 8 (first s))
                      do (setf po (1+ po))))

This loop produces the following rules:

=> 
((1 (1 2 3 4)) (2 (2 3 4 5)) (3 (3 4 5 6)) (4 (4 5 6 7)) (5 (5 6 7 8))
 (6 (6 7 8 1)) (7 (7 8 1 2)) (8 (8 1 2 3)) (9 (1 3 5 7)) (10 (2 4 6 8))
 (11 (3 5 7 1)) (12 (4 6 8 2)) (13 (5 7 1 3)))

close

+ Constructing the rthm-seq-map using do-simple-lookup

The inner portions of the rthm-seq-map are then constructed and assigned to the variable rsm-lists. They are assembled from the previously created l-for-lookup object using a loop that pairs the IDs of the players, as listed in the variable ens, with the flattened results of the do-simple-lookup method. (See the source code documentation for the flatten function for more detail.)

The do-simple-lookup method is called using the seqs variable as its first argument to create separate lists that are num-seqs long for each of the players assigned to p, including the two computer parts. It creates these lists by initiating each L-system iteration with a different axiom, consisting of consecutive numbers from 1 to the length of the list of players in the ens variable (13, one for each rule/player).

       (rsm-lists 
        (loop for s from 1 to (length (first ens))
           for p in (loop for i in (first ens)
                       collect (first i))
           collect (list p (flatten (do-simple-lookup seqs s num-seqs)))))

This loop ensures that each player's part in the rthm-seq-map begins with a differently ordered sequence. After this initial difference, all subsequent sequences will only be created using rules 1 through 8, as these are the only elements used in each rule. The combination of the rules and this particular loop produces a rthm-seq-map in which all players play all rthm-seqs of the rthm-seq-palette at least once during the course of the piece.

close

+ Self-similarity in the results

The resulting rthm-seq-map would look like this if written out in full, with a few of the repeating sequences color-coded to highlight the self-similarity:

((OB
  (1 2 3 4 2 3 4 5 3 4 5 6 4 5 6 7 2 3 4 5 3 4 5 6 4 5 6 7 5 6 7 8 3 4 5 6
   4 5 6 7 5 6 7 8 6 7 8 1 4 5 6 7 5 6 7 8 6 7 8 1 7 8 1 2 2 3 4 5 3 4 5))
 (CL
  (2 3 4 5 3 4 5 6 4 5 6 7 5 6 7 8 3 4 5 6 4 5 6 7 5 6 7 8 6 7 8 1 4 5 6 7
   5 6 7 8 6 7 8 1 7 8 1 2 5 6 7 8 6 7 8 1 7 8 1 2 8 1 2 3 3 4 5 6 4 5 6))
 (BN
  (3 4 5 6 4 5 6 7 5 6 7 8 6 7 8 1 4 5 6 7 5 6 7 8 6 7 8 1 7 8 1 2 5 6 7 8
   6 7 8 1 7 8 1 2 8 1 2 3 6 7 8 1 7 8 1 2 8 1 2 3 1 2 3 4 4 5 6 7 5 6 7))
 (HN
  (4 5 6 7 5 6 7 8 6 7 8 1 7 8 1 2 5 6 7 8 6 7 8 1 7 8 1 2 8 1 2 3 6 7 8 1
   7 8 1 2 8 1 2 3 1 2 3 4 7 8 1 2 8 1 2 3 1 2 3 4 2 3 4 5 5 6 7 8 6 7 8))
 (TP
  (5 6 7 8 6 7 8 1 7 8 1 2 8 1 2 3 6 7 8 1 7 8 1 2 8 1 2 3 1 2 3 4 7 8 1 2
   8 1 2 3 1 2 3 4 2 3 4 5 8 1 2 3 1 2 3 4 2 3 4 5 3 4 5 6 6 7 8 1 7 8 1))
 (TB
  (6 7 8 1 7 8 1 2 8 1 2 3 1 2 3 4 7 8 1 2 8 1 2 3 1 2 3 4 2 3 4 5 8 1 2 3
   1 2 3 4 2 3 4 5 3 4 5 6 1 2 3 4 2 3 4 5 3 4 5 6 4 5 6 7 7 8 1 2 8 1 2))
 (PR
  (7 8 1 2 8 1 2 3 1 2 3 4 2 3 4 5 8 1 2 3 1 2 3 4 2 3 4 5 3 4 5 6 1 2 3 4
   2 3 4 5 3 4 5 6 4 5 6 7 2 3 4 5 3 4 5 6 4 5 6 7 5 6 7 8 8 1 2 3 1 2 3))
 (PL
  (8 1 2 3 1 2 3 4 2 3 4 5 3 4 5 6 1 2 3 4 2 3 4 5 3 4 5 6 4 5 6 7 2 3 4 5
   3 4 5 6 4 5 6 7 5 6 7 8 3 4 5 6 4 5 6 7 5 6 7 8 6 7 8 1 1 2 3 4 2 3 4))
 (VN
  (1 2 3 4 2 3 4 5 3 4 5 6 4 5 6 7 2 3 4 5 3 4 5 6 4 5 6 7 5 6 7 8 3 4 5 6
   4 5 6 7 5 6 7 8 6 7 8 1 4 5 6 7 5 6 7 8 6 7 8 1 7 8 1 2 3 4 5 6 4 5 6))
 (VA
  (2 3 4 5 3 4 5 6 4 5 6 7 5 6 7 8 3 4 5 6 4 5 6 7 5 6 7 8 6 7 8 1 4 5 6 7
   5 6 7 8 6 7 8 1 7 8 1 2 5 6 7 8 6 7 8 1 7 8 1 2 8 1 2 3 4 5 6 7 5 6 7))
 (VC
  (3 4 5 6 4 5 6 7 5 6 7 8 6 7 8 1 4 5 6 7 5 6 7 8 6 7 8 1 7 8 1 2 5 6 7 8
   6 7 8 1 7 8 1 2 8 1 2 3 6 7 8 1 7 8 1 2 8 1 2 3 1 2 3 4 5 6 7 8 6 7 8))
 (C1
  (4 5 6 7 5 6 7 8 6 7 8 1 7 8 1 2 5 6 7 8 6 7 8 1 7 8 1 2 8 1 2 3 6 7 8 1
   7 8 1 2 8 1 2 3 1 2 3 4 7 8 1 2 8 1 2 3 1 2 3 4 2 3 4 5 6 7 8 1 7 8 1))
 (C2
  (5 6 7 8 6 7 8 1 7 8 1 2 8 1 2 3 6 7 8 1 7 8 1 2 8 1 2 3 1 2 3 4 7 8 1 2
   8 1 2 3 1 2 3 4 2 3 4 5 8 1 2 3 1 2 3 4 2 3 4 5 3 4 5 6 7 8 1 2 8 1 2)))
Occasional rhythmic unison in multiple parts

As can be seen in the code example above, the use of different axioms for each call to do-simple-lookup still produces a number of instances where the same sequence segment appears in more than one part simultaneously. This will produce a nice effect of coupling (non-unison rhythmic doubling) in various passages of the instrumental parts.

close

+ Shaping the ensemble's tessitura

One set for the entire piece

This piece uses a different approach to controlling the progression of pitch collections (sets) over the course of a slippery chicken composition than that of the first three demo compositions. Instead of defining multiple sets and assigning their IDs to individual sequences in the set-map, one set of pitches is defined for the entire piece, and the subsets of pitches available to the ensemble as the piece progresses are governed using the set-limits-high and -low slots in conjunction with their all option.

The set of pitches is given the ID 1 and assigned to the variable sp, and a simple loop is used to collect a list of 1s that is equal in length to the list of rthm-seqs and assigned to the variable harms-list.

       (sp '((1 ((cs2 f2 a2 ds2 c3 cs3 d3 gs3 c4 cs4 fs4 g4 gs4 a4 bf4 b4 d5 ds5
                      e5 f5 fs5 a5)))))
       (harms-list (loop repeat (length (second (first rsm-lists)))
                      collect 1))
Collecting the high and low set limits

The gradually changing upper and lower pitch limits within this set are then determined by first collecting a list of consecutive integers from 1 and pairing them with sublists consisting of the lowest and highest pitches available to the ensemble at consecutive points within the piece. The results of this loop are assigned to the variable set-lims-progs.

       (set-lims-progs
        (loop for pchs in '((g4 ds5) (g4 fs5) (cs4 fs5) (c4 fs5) (d3 e5) 
                            (d3 e5) (d3 b4) (cs3 b4) (cs3 ds5) (cs3 ds5) 
                            (d3 b4) (cs3 a4) (cs3 e5) (d3 e5) (d3 a5) (c4 a5) 
                            (c4 e5) (c4 d5) (c4 ds5) (c4 f5) (g4 e5) (f2 ds5) 
                            (c4 e5) (a2 f5) (cs2 e5) (cs2 fs5))
           for n from 1
           collect (list n pchs)))

The set-lims-progs list is then further broken down into the -high and -low lists with two more loops. The first of these collects each consecutive number and the first pitch of the sublist it is paired with (the low pitch), and the second collects the same number with the second pitch of the sublist (the high pitch).

The resulting lists are assigned to the variables set-lims-low and set-lims-high, which are later paired with the word all (to control the tessitura of the entire ensemble rather than individual instruments) and passed to the set-limits-high and -low keywords of the make-slippery-chicken function.

       (set-lims-low (loop for s in set-lims-progs 
                        collect (first s)
                        collect (first (second s))))
       (set-lims-high (loop for s in set-lims-progs 
                         collect (first s)
                         collect (second (second s))))

close

+ The call to make-slippery-chicken

As with Nouveau Reich, this composition assigns the results of the make-slippery-chicken function to a variable, in this case tempus-perfectum. The first few keyword arguments of the function are either passed straightforward values directly (:title, :staff-groupings, and :tempo-map), or are passed one of the previously declared variables (:ensemble, :set-palette, and :set-map).

       (tempus-perfectum
        (make-slippery-chicken
         '+tempus-perfectum+
         :title "Tempus Perfectum"
         :ensemble ens
         :staff-groupings '(3 3 2 3 2)
         :tempo-map '((1 (q 112)))
         :set-palette sp
         :set-map (list (list 1 harms-list))

close

+ Additional set-limits- for individual players

In addition to the overall tessitura defined above, the code for Tempus Perfectum also sets high and low pitch limits for a number of the players in the ensemble. These limits are static and assigned for the entire piece using x-values of 0 and 100 for the break-point pairs. For example, the code specifies that the oboe will be given no pitches below G4, and that the bassoon, trombone, and cello will not have any pitches above G4.

         :set-limits-high `((all ,set-lims-high)
                            (ob (0 a5 100 a5))
                            (bn (0 g4 100 g4))
                            (hn (0 c5 100 c5))
                            (tb (0 g4 100 g4))
                            (va (0 ds5 100 ds5))
                            (vc (0 g4 100 g4)))
         :set-limits-low `((all ,set-lims-low)
                           (ob (0 g4 100 g4))
                           (hn (0 c3 100 c3))
                           (tp (0 c4 100 c4))
                           (tb (0 a2 100 a2))
                           (vn (0 d4 100 d4)))

close

+ Multi-bar rthm-seqs with lots of rests

In defining the rthm-seq-palette, much attention is given in Tempus Perfectum to ensuring that there are a considerable number of full-bar rests in the multi-bar rthm-seqs. This is done to allow for more differentiated orchestration and more discernible combinations of motifs when they are superimposed.

Only one pitch-seq curve is defined for each rthm-seq, but the rthm-seqs in this piece are assigned specific marks that will be present in the score each time the corresponding rthm-seq appears.

         :rthm-seq-palette '((1 ((((3 4) - e (s) s - +q { 3 (te) - te te - })
                                  ((h.))
                                  ((h.))
                                  ((h.))
                                  ((h.))
                                  ((h.)))
                                 :pitch-seq-palette ((1 3 2 3))
                                 :marks (ppp 1 pp 2 ppp 3 cresc-beg 3 cresc-end
                                             4 )))
                             (2 ((((3 4) (h.))
                                  ((h.))
                                  (- e.. 32 - { 3 - +te te te } - - +s s - (e))
                                  (- s. 32 +e - - s s - (e) (q))
                                  ((s) - s (s) s - (q) - e e -)
                                  ((h.)))
                                 :pitch-seq-palette ((5 7 9 7 6 7 6 9 5 6 3 1
                                                        2))
                                 :marks (mf 1 s 1 2 p 3 mf 7 s 7 8 ppp 9 s 12
                                            13)))
                             (3 ((((3 4) (h.))
                                  ((q) q { 3 (ts) ts (ts) } e )
                                  (- e s s - +q q)
                                  ({ 3 (ts) - ts ts - } (e) { 3 (ts) te } 
                                     { 3 - +te ts - } q) 
                                  (h.)
                                  (+q - +e s - (s) q))
                                 :pitch-seq-palette ((10 5 7 1 3 4 10 14 8 6 7
                                                         6 10 12 7))
                                 :marks (pp 1 cresc-beg 1 cresc-end 2 f 2 p 3
                                            mf 4 dim-beg 4 dim-end 9 pp 9 mf 11
                                            dim-beg 11 dim-end 15 ppp 15
                                            cresc-beg 15 cresc-end 16 f 16 s
                                            17 mf 18 te 18)))
                             (4 ((((3 4) { 5 - fe fs fs fs - } { 3 te tq } 
                                   - +e e -)
                                  ((h.))
                                  (q (q) (e) e)
                                  ((e) e (h)) 
                                  (- s s - (e) - e (s) s - - e e -)
                                  ((e.) s +h))
                                 :pitch-seq-palette ((9 7 8 6 3 1 9 9 8 8 7 8 3
                                                        8 2 1 (9)))
                                 :marks (mf 1 dim-beg 1 s 4 ppp 6 dim-end 6 mf
                                            8 at 8 cresc-beg 8 cresc-end 11 f
                                            11 s 12 p 13 a 15 s 16 at 17 mf
                                            17)))
                             (5 ((((3 4) (e) q. - +s e. -)
                                  (e (e) (s) - e s - +e (e))
                                  (e (e) (e.) s - +e. s -)
                                  (- +s e. - (e.) s (q)) 
                                  (e (e) (h))
                                  (e (e) (h)))
                                 :pitch-seq-palette ((1 2 (5) 1 3 2 3 2 1 1 1 
                                                        (5)))
                                 :marks (fff 1 dim-beg 1 mf 3 dim-end 3 ppp 4
                                             cresc-beg 8 cresc-end 9 cresc-beg 
                                             11 cresc-end 13 f 15)))
                             (6 ((((3 4) (h.))
                                  ((e) e (h))
                                  ((h.))
                                  ((h.)) 
                                  ((h.))
                                  ((q) e (e) (q)))
                                 :pitch-seq-palette ((1 (1)))
                                 :marks (p 1 f 2)))
                             (7 ((((3 4) e (e) { 3 - ts ts ts - } (e) (q))
                                  ((h.))
                                  ((h.))
                                  ((h.)) 
                                  ((e..) 32 - e e - (q))
                                  ((h.)))
                                 :pitch-seq-palette ((2 5 4 3 1 6 7))
                                 :marks (ppp 1)))
                             (8 ((((3 4) (h.))
                                  ((h.))
                                  ((h) (e..) 32)
                                  (e (e) (h)) 
                                  ((h.))
                                  (h (q)))
                                 :pitch-seq-palette ((4 1 (2)))
                                 :marks (ppp 1 ff 3))))
A cmn-display of the rthm-seq-palette

A cmn-display processing of the above palette produces the following graphic:

tempus-perfectum-rsp.png
NB: This graphic was created by applying the cmn-display method directly to a set-palette object. See the page on output for more detail on this feature.

close

Incorporating CLM and sound files into the composition

+ Adding separate computer players to the ensemble

In order to generate rhythmically and melodically independent computer parts for the piece, two separate computer players are added to the ensemble. These are given the IDs c1 and c2, and assigned the computer instrument from the +slippery-chicken-standard-instrument-palette:

            '(((ob (oboe :midi-channel 1))
               (cl (b-flat-clarinet :midi-channel 2))
               (bn (bassoon :midi-channel 3))
               (hn (french-horn :midi-channel 4))
               (tp (c-trumpet :midi-channel 5))
               (tb (tenor-trombone :midi-channel 6))
               (pr (piano :midi-channel 7))
               (pl (piano-lh :midi-channel 8))
               (vn (violin :midi-channel 9))
               (va (viola :midi-channel 11))
               (vc (cello :midi-channel 12))
               (c1 (computer :midi-channel 13))
               (c2 (computer :midi-channel 14))))

These two parts are also included in the loop that generates the rthm-seq-map, as described above.

NB: The phrase "computer player" used here does not mean a live performer sitting at a computer and playing it in real-time. Instead, it refers to a player object within the ensemble of the slippery-chicken object that is assigned to the computer instrument of the slippery-chicken-standard-instrument-palette. Each of the two players using the computer instrument in this piece will merely be separate parts (separate rhythmic and pitch structures) that will eventually be used to create fixed-media (non-real-time) "tape" parts, which will most likely be played back using audio software on a computer.

close

+ Setting up the output directory and sndfile-palette

Next, the output directory for the CLM output and the source sound files used to generate that output are specified using the :snd-output-dir and :sndfile-palette keywords of the make-slippery-chicken function.

The sndfile-palette uses the previously defined sndfile-dir variable to specify the path to the location of the source sound files. Since all of the files are .wav files, the string "wav" is added to the extensions slot of the sndfile-palette, with the extension omitted from the file names.

       (sndfile-dir
        (concatenate 'string
                     cl-user::+slippery-chicken-home-dir+
                     "doc/manual/resources/"))

       [...]

         :snd-output-dir "/tmp"
         :sndfile-palette `(((vocal-sounds
                              ((voice-womanKP-18 :frequency 1028)
                               (voice-womanKP-20 :frequency 456)
                               (voice-womanKP-21 :frequency 484)
                               (voice-womanKP-22 :frequency 591)
                               (voice-womanKP-23 :frequency 662)
                               (voice-womanKP-26 :frequency 516)
                               (voice-womanKP-29 :frequency 629)))
                             (mouth-pops-clicks
                              ((mouth_pop_2 :frequency 375)
                               (mouth_pop_2a :frequency 798)
                               (mouthnoises2 :frequency 703))))
                            ,(list sndfile-dir)
                            ("wav")))))
One group for each call to clm-play

The sndfile-palette is constructed to consist of two groups of source sound files, the first being vocalized sounds, the second being non-vocalized mouth sounds. These groups are given the names vocal-sounds and mouth-pops-clicks, which will be used in the two calls to clm-play below.

Preparing pitch-synchronous transposition of the source files

Fundamental frequencies are specified for each of the sound files, as the calls to clm-play will employ the pitch-synchronous option.

close

+ Generating the computer parts

The two computer parts are then produced by using separate calls to clm-play for each sound file group and each computer player, specifying values for a number of the method's keyword arguments. These calls also make use of the previously defined variable src-width, which is set to 50 to produce a slightly higher quality of sample interpolation when transposing the sound files.

       (src-width 50)

       [...]

  (clm-play tempus-perfectum 1 '(c1) 'vocal-sounds
            :rev-amt 0.07
            :reset-snds-each-rs nil
            :pitch-synchronous t
            :src-width src-width
            :srate 44100
            :header-type clm::mus-aiff
            :data-format clm::mus-bshort
            :sndfile-extension ".aiff")
  (clm-play tempus-perfectum 1 '(c2) 'mouth-pops-clicks
            :rev-amt 0.07
            :reset-snds-each-rs nil
            :pitch-synchronous t
            :src-width src-width
            :srate 44100
            :header-type clm::mus-aiff
            :data-format clm::mus-bshort
            :sndfile-extension ".aiff")
Required arguments

The first required argument to clm-play is the slippery-chicken object itself, and the second is the section within the piece from which to start. Since Tempus Perfectum has only one section, the number 1 is specified here. The third required argument is the ID of the player (or players) whose part is to serve as the basis for the resulting CLM output. This is specified as c1 in the first case and c2 in the second. The final required argument is the ID of the group of sound files specified in the sndfile-palette from which the CLM output is to be generated. The c1 part is to be based on the vocal-sounds group, and the second on the mouth-pops-clicks group.

Optional keyword arguments

Each of the calls to clm-play uses the same optional arguments. The same amount of reverb is specified for each output file using the rev-amt keyword. Setting the reset-snds-each-rs argument to NIL causes the method to cycle through the sound files of the given group for the whole duration of the piece, without beginning at the head of the group with each new rthm-seq.

Setting the pitch-synchronous argument to T causes the method to transpose the source sound files to match the specific pitches of the part on which the output is based, using the frequency value specified in the sndfile-palette as the basis for this transposition. The src-width argument determines the quality of the interpolation during transposition, as mentioned above.

The last four arguments determine the output format of the files that CLM will generate. The srate and sndfile-extension arguments determine the sample rate and file suffix for the files produced. The header-type and data-format arguments are CLM arguments and must be preceded by the clm:: package qualifier. The values given here will result in stereo .aiff audio files, with the extension .aiff (default is otherwise .aif), and with a format of 16-bit integer (big endian). More on these arguments can be found on the slippery chicken and CLM page of the manual.

close

+ Generating the MIDI and printable score output

Changing the staff name of the piano left-hand

Before generating the printable score output, one last change is made to the slippery-chicken object. The piano-lh instrument object of the +slippery-chicken-standard-instrument-palette+ has no entry for the staff-name and staff-short-name slots, meaning that those slots contain NIL. LilyPond handles this by printing blank space for the corresponding staff names in the score. CMN, however, will print NIL into the score.

To work around this, the code for Tempus Perfectum sets the staff-name slot of the corresponding instrument object stored within the ensemble slot of the slippery-chicken object itself, rather than changing the value in the instrument-palette. The setf approach to changing slot values is used here rather than the set-slot method, though that method could be used as well (see the page on instruments and instrument-palettes for more detail.)

  (setf (staff-name (get-data-data 'pl (ensemble tempus-perfectum))) " ")
Excluding the computer parts from MIDI and score output

The calls to midi-play, cmn-display, and write-lp-data-for-all then use the :voices and :player keywords to exclude the computer players from the MIDI and printable score output. This is done by specifying in list form the IDs of all players that are to be included in the output. Additionally, the :in-c argument is set to T to produce C-scores, and the :size argument of clm-play is set to 11 to ensure that the full score fits on the page.

  (midi-play tempus-perfectum 
             :midi-file "/tmp/tempus-perfectum.mid"
             :voices '(ob cl bn hn tp tb pr pl vn va vc))
  (cmn-display tempus-perfectum
               :file "/tmp/tempus-perfectum.eps"
               :size 11 
               :players '(ob cl bn hn tp tb pr pl vn va vc)
               :in-c t)
  (write-lp-data-for-all tempus-perfectum
                         :players '(ob cl bn hn tp tb pr pl vn va vc)
                         :in-c t))
Adding the \midi { } command to the LilyPond score file

A final step was undertaken to produce the MP3 of the MIDI mock-up for this tutorial. The midi-play method of the slippery-chicken class only reflects static dynamics in the score, without implementing crescendos/diminuendos or articulation. The LilyPond application is capable of producing a MIDI file that reflects articulation and gradual dynamic changes slightly more accurately (though the authors of that program too explicitly state that LilyPond is not made for MIDI).

In order to have LilyPond produce a second MIDI file, the resulting _tempus-perfectum-score.ly file produced by write-lp-data-for-all was edited prior to having LilyPond render the PDF of the score, by inserting one simple line, namely \midi { }, as seen below.

The MIDI file produced by LilyPond (_tempus-perfectum-score.midi) will have separate tracks for each player, but all program numbers (patches) will be set to 1 (Acoustic Grand Piano) by default. This was not an issue when producing the MIDI mock-up for Tempus Perfectum, as the resulting MIDI file was then imported into a MIDI sequencer and the tracks were manually assigned to instrument tracks that used sample-based VST plug-ins.

\version "2.14.2"
\include "tempus-perfectum-def.ly"
\header {
  title ="Tempus Perfectum" 
  tagline = ##f
  composer = ##f
}
\score {
  \keepWithTag #'score \music
  \midi { }
  \layout { }
}

close