Algorithmic Composition

you are coming into us who cannot withstand you

Posted in composition documentation by Michael Edwards on July 1st, 2011

For Ensemble Aventure, Freiburg, Germany.

The title of this piece is taken from the poem “Final Notions” by Adrienne Rich
(1929-):

It will not be simple, it will not take long
It will take little time, it will take all your thought
It will take all your heart, it will take all your breath
It will be short, it will not be simple

It will touch through your ribs, it will take all your heart
It will not take long, it will occupy all your thought
As a city is occupied, as a bed is occupied
It will take your flesh, it will not be simple

You are coming into us who cannot withstand you
You are coming into us who never wanted to withstand you
You are taking parts of us into places never planned
You are going far away with pieces of our lives

It will be short, it will take all your breath
It will not be simple, it will become your will

The mood of the piece picks up on the simplicity and directness of language,
the repetitions, and the almost breathless speed (in my reading at least) of
the poetic meter.

Deceptively simple on the page, “you are coming into us who cannot withstand
you” gains its impetus from the combination of small, simple rhythmic units
which form larger, sometimes repeating sequences. These are usually placed in polymetric
opposition to similarly constructed contrapuntally combined sequences. The
tempi are quick, the energy level is high, and the perception of multiple
pattern streams moving at different rates is the main feature of the music.
The most common polymetric combination is four against three. The technique for
constructing the sequences is new; I call it rhythm chains.

In rhythm chains, there are an arbitrary number of one beat rhythmic units–in
this piece just eight. An example of one such unit is simply a quaver (1/8th
note) rest followed by a quaver note; another is even simpler: a single
crotchet (1/4 note) note. The order in which the units are combined into
sequences is determined either by my Fibonacci transition algorithm, in which
new elements are folded into existing repeating elements according to a number
of repetitions determined by Fibonacci numbers (e.g. 1 1 1 1 1 1 1 2 1 1 1 1 2
1 1 2 1 2 1 2 1 2 1 2 1 2 2 2 2 2 2 2 3 2 2 3 2 3 2 3 2 3 2 3 2 3 3 3 2 3 3 3 3
3 3 3 3 3 3 3); or by my procession algorithm: this moves through a list by
alternating adjacent elements, progressing through to the higher elements by
interspersion, with an algorithmic eye on equal statistical distribution also
(e.g. 1 2 1 2 3 1 3 1 1 4 2 3 2 4 3 4 3 4 5 2 4 2 2 5 1 3 1 5 4 5 4 5 6 3 5 3 3
6 1). “You are coming” makes use of both of these procedures for organising the
rhythmic units.

However many such units there are, there must be a matching number of opposing
units towards which we transition over the course of the whole piece. The
transition between the two unit collections is handled by the Fibonacci
transition algorithm. In “you are coming”, the transition is from exactly such
simple binary rhythms as described above to ternary rhythms i.e. those
characterised by triplets.

The simple one-beat units are intended to form faster moving parts
counterpointed by slower moving units of two or three beats. The technique is
therefore essentially two-part contrapuntal, though this can be scaled up to
any number of parts–in this piece, eight. The grouping of an arbitrary number
of slower moving units into a sequence is determined by a given harmonic
rhythm: this may change during the piece. The repetition of both unit and
sequence, with or without the repetition of their associated notes, creates
identifiably recurring material which, through the additional insertion of
rests and new contrapuntal combinations, constantly varies an essentially
consistent, flowing musical structure. Variation is provided by the algorithmic
control of rests in two forms: through the interaction of multiple cycles of
rests, and the definition of “activity levels”; together these determine the
relative mix of rests to notes, which can vary from very sparse to
unbroken. Both approaches are controlled through the use of curves which map
over the whole structure.

Another characteristic, new for me, is a general avoidance of long-term
crescendo and diminuendo–these do exist in the piece, but rarely. In order to
develop dynamically, I instead use a new technique called “popcorn dynamics”,
whereby most notes remain at the same dynamic level for a while until one note
is suddenly louder (perhaps only marginally) than the others. This begins a
chain reaction, the end result of which is that all notes are at the louder
dynamic. The analogy of course is popcorn kernels cooking away in a pan until
one explodes, this being followed a little later by another, then another, and
more and more until all are popped. When applied to dynamics, this process can
work in the reverse direction too, i.e. from loud to quiet, as well as within
every dynamic gradation, e.g. from pp to mp, ff to mf, p to mf etc.

Downloads

coming score

This is the final score, with some edits ‘by hand’. If you compare to the MIDI realisation below though you’ll hear that there’s little discrepancy between the two.

MIDI realisation

This was made with Kontakt 3 standard samples using the MIDI file generated before any interventions/edits. I didn’t do any fancy MIDI editing to distinguish between arco and pizzicato in the strings (they’re pizz. all the way through) so this really is quite a simple demo.

Common Lisp code

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;
;;; File:             coming.lsp
;;;
;;; Project:          you are coming into us who cannot withstand you
;;;                   for Ensemble Aventure, Freiburg
;;;
;;; Purpose:          Code for generation of score
;;;
;;; Author:           Michael Edwards: m@michael-edwards.org
;;;
;;; Creation date:    27th December 2010 (Wals, Austria)
;;;
;;; $$ Last modified: 15:24:42 Mon Nov  7 2011 GMT
;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;; 
;;; The title is taken from the poem, Final Notions, by Adrienne Rich (1929-):
;;; 
;;; It will not be simple, it will not take long
;;; It will take little time, it will take all your thought
;;; It will take all your heart, it will take all your breath
;;; It will be short, it will not be simple 
;;; 
;;; It will touch through your ribs, it will take all your heart
;;; It will not take long, it will occupy all your thought
;;; As a city is occupied, as a bed is occupied
;;; It will take your flesh, it will not be simple 
;;;
;;; You are coming into us who cannot withstand you
;;; You are coming into us who never wanted to withstand you
;;; You are taking parts of us into places never planned
;;; You are going far away with pieces of our lives
;;;
;;;
;;; It will be short, it will take all your breath
;;; It will not be simple, it will become your will
;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

(in-package :sc)

;;; The rthm-chains data and object
(defparameter +coming-rthm-chain-main+
  (let ((1-beat-rthms1                  ; 8 in total
         '(((e) e) 
           (- s (s) (s) s -) 
           ({ 3 (te) - te te - })
           ((e.) s) 
           (q)
           ((e) - s s -)
           ((s) s (e))
           ((s) s (s) s)))
        (1-beat-rthms2                  ; what we transition to
         '(({ 3 (te) te (te) })
           ({ 3 - te (te) te - })
           ({ 3 (te) - te te - })
           ({ 3 (tq) te })
           ({ 3 (te) - ts ts te - })
           (- s s s s -)
           ({ 3 - te te - (te) })
           ({ 3 - te te te - })))
        (slower-rthms1                  ; the 2/4 bars: 10 total
         '(((q q) 
            ((q) q) 
            ((q) q) 
            ((q) (s) e.)
            (- e e - (e) e)
            (- e. s - (e) e) 
            (- e. s - (q)) 
            ((e.) s (e) e)
            (- e. s - (e.) s)
            ((e.) s (q)))
           (({ 3 tq tq tq })            ; what we transition to
            (q - s e. -)
            (q (s) e.)
            (q (s) - s e -)
            ({ 3 tq tq - te te - })
            (- e. s - (e) - s s -) 
            ({ 3 tq te } { 3 (tq) te })
            ({ 3 - te te te - } { 3 (te) - te te - })
            (q { 3 (te) tq })
            (q { 3 (tq) te }))))
        (slower-rthms2                  ; the 3/4 bars: 5 total
         '((((e.) s (e) e (s) e.)
            (- e e - (e) e (q))
            (- e. s - - +e e - (q)) 
            ((q) - e. s - (q))
            (q (e.) s (q)))
           (({ 3 (tq) tq tq } (q))      ; what we transition to
            (- e. s - (q) (s) - s e -)
            ({ 3 tq te } (q) q)
            ({ 3 - te te te - } { 3 (te) - te te - } { 3 (tq) te })
            (q (q) { 3 (te) - te te - })))))
    (make-instance 'rthm-chain
                   :num-beats (nth-value 1 (fibonacci 400))
                   ;; so the flute voice will be the 'fast' player, clarinet
                   ;; the 'slow' 
                   :players '(flute clarinet) ; to start with
                   :1-beat-rthms (list 1-beat-rthms1 1-beat-rthms2)
                   :section-id 1
                   :rests '(q h q w) 
                   :1-beat-fibonacci nil
                   :slow-fibonacci t
                   :slower-rthms (list slower-rthms1 slower-rthms2)
                   :do-rests t
                   ;; the recurring-event object data: every 5 events 3x, then
                   ;; every 8 2x...
                   :rest-re '((5 3) (8 2) (13 1))
                   :activity-curve  '(0 7 50 10 100 10) 
                   :do-sticking t
                   :do-sticking-curve '(0 0 100 1)
                   :sticking-rthms '(e e. e s))))

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;; 5.2.11 now we've got two voices of counterpoint (flute and clarinet), add
;;; the rest based on their rthm-seqs and create the ensemble groupings for the
;;; different sections.

(let* ((flute-voices '(flute violin piano-lh viola))
       (clarinet-voices '(clarinet cello bassoon piano-rh marimba))
       ;; the sequence numbers (starting from 1) at which we'll change the
       ;; ensemble (and maybe have a longer pause, but that's for later).
       ;; Determined by ear. See coming.mm for details
       ;; when I decided these the piece was considerably longer than it turned
       ;; out to be so in calculating the actual seq to change at we have to
       ;; scale it: see csmult below
       (change-seqs '(1 19 29 67 79 91 129 143 160 210 211 218 252 274 309 332
                      ;; 422 is the last seq
                      343 353 355 363 387 410 422)))
  (flet ((add-voices (voices based-on)
           ;; from rthm-chain.lsp: The challenge here is that each rthm-seq has
           ;; potentially its own time signature structure: there could be a
           ;; 2/4 bar followed by 5/4 then 3/16; or any other combination of
           ;; any meter.  So first of all, we analyse the time-signature
           ;; structure of the existing rthm-seqs and save those with the same
           ;; bar/meter structure together in the order in which they occurred.
           ;; When creating the extra voice then, we actually start ahead of
           ;; the main voice, by choosing <offset> similar rthm-seqs in advance
           (loop for v in (rest voices) and offset from 1 do
                (add-voice +coming-rthm-chain-main+
                           based-on v offset)))
         ;; check that we've got a 'flute' and a 'clarinet' voice in every
         ;; ensemble (i.e. we should have the 1-beat and slower rthms always
         ;; present.)
         (both-types (ensemble)
           (let ((check '()))
             (loop for v in ensemble do
                  (cond ((member v flute-voices) (pushnew 'fl check))
                        ((member v clarinet-voices) (pushnew 'cl check))
                        (t (error "coming: huh?: ~a" v))))
             (= 2 (length check)))))
    ;; first of all, add the groups: this will set up the rthm-chain's
    ;; rthm-seq-map so that all voices are playing all the time--we deal with
    ;; this below.
    (add-voices flute-voices '(1 flute))
    (add-voices clarinet-voices '(1 clarinet))
    ;; from the rthm-seq-map class, this adds rthm-seq repeats (so rhythms only,
    ;; not notes) at recurring intervals
    (add-repeats +coming-rthm-chain-main+ 
                 ;; of course this means we have 1+ seq items of these numbers
                 ;; all together, as these are the number of _repeats_
                 '((3 3) (2 2) (1 1)) ; when to repeat
                 '((3 3) (5 2) (8 1) (13 1)) ; now many to repeat when repeating
                 :print nil)
    ;; we're now going to algorithmically generate the scoring, moving in
    ;; general from 4 instruments playing at the beginning to 8 by the end,
    ;; keeping track of who's played and how much and trying to balance this
    ;; out to maximize timbral variety and rests for the players.
                                        ; -1 because we have the last seq in
                                        ; the list too 
    (let* ((num-ins-procession (procession (1- (length change-seqs)) 
                                   '(4 5 6 7 8)))
           (total-entries (loop for num-ins in num-ins-procession sum num-ins))
           ;; we don't want to just append flute-voices and clarinet-voices as
           ;; we determine an ordering here
           (ins '(violin clarinet flute viola cello bassoon marimba piano-rh
                  piano-lh))
           (num-ins (length ins))
           ;; the hash table to keep track of how often each ins has played
           (hash (let ((ht (make-hash-table)))
                   (loop for i below num-ins do
                        (setf (gethash i ht) 0))
                   ht))
           (csmult (/ (first (last change-seqs))
                      (length (get-data-data '(1 flute)
                                             +coming-rthm-chain-main+))))
           ;; first of all we're going to spread out the instruments so
           (ins-order-procession (procession total-entries num-ins))
           ;; create the instrumental groupings (scoring) we'll want so that we
           ;; can make those not playing silent in the now full/everyone
           ;; constantly playing rthm-seq-map
           (groups (loop with ins-order-procession-cp =
                        (copy-list ins-order-procession)
                      with group with ngroup
                      for num-ins in num-ins-procession 
                      do (setf group '()   ; the instrument symbols
                               ngroup '()) ; their position in <ins>
                      collect
                      (loop repeat num-ins 
                                        ; procession min is 1
                         for ni = (1- (pop ins-order-procession-cp))
                         for i = (nth ni ins)
                         do
                         ;; this messes with the results of the procession
                         ;; algorithm but we really can't have the same voice
                         ;; twice in a single ensemble
                         (loop with happy = nil
                            with ignore = (copy-list ngroup) ; once
                            for count from 0 
                            for try = (cons i group)
                            until happy do
                            (when (= count 1000) ; no infinite loop
                              (error "woah!: 1000 tries already!"))
                            (if (member ni ngroup)
                                (progn 
                                  ;; so if this instrument is already in the
                                  ;; current scoring we have to get another
                                  (push ni ignore)
                                  ;; from the rthm-chain class: ignoring the
                                  ;; insuments in the ignore list, get the
                                  ;; least-used element--here the instrument
                                  ;; number--auto-incrementing its count
                                  (setf ni (hash-least-used hash :ignore ignore)
                                        i (nth ni ins)))
                                ;; else clause!
                                (setf happy t))
                            (when (and (= (length group) num-ins)
                                       happy)
                              ;; if we don't have at least one 1-beat and one
                              ;; slower-rthms group in there, keep going
                              (setf happy (both-types try))))
                         ;; keep tally
                         (incf (gethash (position i ins) hash))
                         (push ni ngroup)
                         (push i group)
                         finally (return (nreverse group))))))
      ;; just print the groups and the spread of instruments
      (print groups)
      (maphash #'(lambda (key val) (format t "~&~a: ~a" (nth key ins) val))
               hash)
      ;; now all instruments are playing all the time, so sculpt them out
      ;; according to the groupings we've just made
      (loop with all-players = (append clarinet-voices flute-voices)
         for start in change-seqs and end in (rest change-seqs)
         for group1 in groups and group2 in (rest groups)
         do
         ;; see change-seqs above for why we have to scale the change-seq
         ;; number 
         (setf end (floor end csmult)
               start (floor start csmult))
         (loop for not-playing in (remove-all group1 all-players) do
              (delete-from-to-in-map (list 1 not-playing)
                                     (max 0 (1- start))
                                     ;; we don't go up to end itself as
                                     ;; that change seq needs both groups
                                     ;; playing 
                                     (- end 2)
                                     +coming-rthm-chain-main+))
         ;; now the change-seq itself
         (loop for not-playing in
              (remove-all (append group1 group2 all-players) all-players) do
              (delete-nth-in-map (list 1 not-playing) (1- end)
                                 +coming-rthm-chain-main+))))))

;; some more repeats                         seq no. (determined by ear)
(add-repeats-simple +coming-rthm-chain-main+ 228 5 :print nil)

;; and now some instrumental rhythmic doublings
(defparameter +coming-doublings+
  '((11 16 violin viola) ; viola was silent but now doubles violin
    (72 76 violin clarinet) ; added 22.7.11 (Pula)
    (102 116 cello (violin viola))
    (151 162 violin  viola)
    (117 125 cello violin)
    (102 125 bassoon flute)
    (138 150 clarinet flute)
    (180 198 clarinet bassoon)
    (237 250 bassoon flute)
    (17 46 piano-rh piano-lh)
    (54 68 piano-lh piano-rh)
    (87 87 piano-rh piano-lh)
    (102 125 piano-lh piano-rh)
    (131 150 piano-rh piano-lh)
    (218 236 piano-lh piano-rh)))

(loop for double in +coming-doublings+ do
     (apply #'double (append (list +coming-rthm-chain-main+ 1) double)))

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

;;; modify our default piano chord function somewhat so that chords are 3rd
;;; instead of 2nd based
(let ((last-chord (make-chord '(d8 e8)))
      (cs2 (make-pitch 'cs2)))
  (defun coming-piano-chord (curve-num index pitch-list pitch-seq instrument
                             set) 
    (declare (ignore set instrument pitch-seq curve-num))
    (let* ((start (max 0 (- index 3))) 
           (at-start (nth start pitch-list))
           (nump (length pitch-list))
           (lots (> nump 4))
           (down-low (pitch< at-start cs2))
           (result (list at-start)))
      (loop 
         ;; try and get every other note
         for i from (if lots 
                        (if down-low
                            (+ 3 start) ;; 4ths if low 
                            (+ 2 start))
                        (1+ start))
         by (if lots 2 1) 
         ;; 26.2.11 don't get more than 2 notes if we're down low (remember
         ;; at-start is already in there, so we try for 4 notes if > c2
         ;; otherwise 2
         repeat (if (not down-low) 3 2)
         for p = (nth i pitch-list)
         for interval = (when p (pitch- p at-start))
         do
         (when (and p (<= interval 12)
                    (not (member p result :test #'note=))
                    ;; 24.7.11 no low M7ths
                    (and down-low (/= interval 11)))
           (push p result)))
      (if (> (length result) 1)
          (progn 
            (setf result (make-chord result))
            ;; 24.7.11 (Pula) don't have repeated chords or new chords with
            ;; more than 1 common note 
            (if (> (common-notes result last-chord) 1)
                (coming-piano-chord nil (1+ index) pitch-list nil nil nil)
                (setf last-chord result)))
          (first result)))))

;;; ditto for marimba: prefer thirds
(defun coming-marimba-chord (curve-num index pitch-list pitch-seq instrument
                             set)
  (declare (ignore set instrument pitch-seq curve-num))
  (let ((at-index (nth index pitch-list))
        p1 p2)
    (cond ((> index 1) (setf p1 (nth (- index 2) pitch-list)
                             p2 at-index))
          ((> index 0) (setf p1 (nth (1- index) pitch-list)
                            p2 at-index))
          ((> (length pitch-list) 2) (setf p1 at-index 
                                           p2 (nth (+ index 2) pitch-list)))
          ((= (length pitch-list) 2) (setf p1 at-index 
                                           p2 (nth (1+ index) pitch-list)))
          (t (setf p1 at-index
                   p2 nil)))
    ;; don't create diads > 8ve
    (when (and p2 (> (pitch- p2 p1) 12)) ; assuming pitch-list is sorted
      (setf p2 nil))
    (if p2
        (make-chord (list p1 p2))
        (make-chord (list p1)))))

(setf (chord-function
       (get-data 'piano-lh +slippery-chicken-standard-instrument-palette+))
      'coming-piano-chord
      (chord-function
       (get-data 'piano +slippery-chicken-standard-instrument-palette+))
      'coming-piano-chord
      (chord-function
       (get-data 'marimba +slippery-chicken-standard-instrument-palette+))
      'coming-marimba-chord)

;;; although we want sc to choose notes for marimba all the way through, we
;;; want it called percussion as we use cymbals too (hand-entered) 
(setf (staff-name 
       (get-data 'marimba +slippery-chicken-standard-instrument-palette+))
      "percussion"
      (staff-short-name 
       (get-data 'marimba +slippery-chicken-standard-instrument-palette+))
      "perc"
      (starting-clef
       (get-data 'cello +slippery-chicken-standard-instrument-palette+))
      'treble
      (starting-clef
       (get-data 'viola +slippery-chicken-standard-instrument-palette+))
      'treble)


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

;;; use the default 'melodic' lines, now we've modified them to include chords.
(create-psps (palette +coming-rthm-chain-main+))

(make-slippery-chicken
 '+coming-rthm-chain+
 :title "coming"
 ;; increase from 0.125 to become a little more angular
 :fast-leap-threshold 0.15
 :instrument-palette +slippery-chicken-standard-instrument-palette+
 :ensemble '(((flute ((flute piccolo)
                      :midi-channel 1 :microtones-midi-channel 2))
              (clarinet ((b-flat-clarinet bass-clarinet)
                         :midi-channel 3 :microtones-midi-channel 4))
              (bassoon (bassoon :midi-channel 5 :microtones-midi-channel 6))
              (marimba (marimba :midi-channel 7))
              (piano-rh (piano :midi-channel 8))
              (piano-lh (piano-lh :midi-channel 9))
              (violin (violin :midi-channel 10 :microtones-midi-channel 11))
              (viola (viola :midi-channel 12 :microtones-midi-channel 13))
              (cello (cello :midi-channel 14 :microtones-midi-channel 15))))
 ;; remember here that we have to use the bar numbers--if not using seq
 ;; numbers--before we deleted bars (see coming-no-delete-bars.pdf)
 :set-limits-high '((piano-rh (0 c4 40 c4  96 d7  97 c8 643 c8))
                    (piano-lh (0 g2 40 g2  96 c5  97 c4 643 c4))
                    (violin (0 c7 217 c7 218 b4 222 b4 223 c7 259 c7)) 
                    ;; hmm, though bar numbers should work, it's actually best
                    ;; to express x axis in seq numbers.
                    (flute (0 c9 129 c9 130 a5 131 a5 136 c9 259 c9))
                    (clarinet (0 g6 105 g6 109 bf5 121 bf5 127 g6 134 c5 153 c5
                               157 g6 232 g6 237 c6 242 c6 247 g6 259 g6))) 
 :set-limits-low '((piano-rh (0 gs2 40 gs2 96 cs5 97 cs4 643 c4))
                   (piano-lh (0 a0  40 a0  96 b3  97 a0 137 f2 170 c3 171 a0
                              280 a0 334 c3 335 a0 643 a0))
                   (bassoon (0 f2 35 f2 36 bf1 202 bf1 220 e3 230 bf1 280 bf1
                             285 b2 300 c4 330 e3 357 e3 385 bf1 500 bf1 510 e3
                             525 bf1 643 bf1))
                   (violin (0 g3 643 g3))
                   (clarinet (0 bf1 102 bf1 107 c5 115 c5 120 d3 162 bf1
                              232 bf1 237 d4 242 d3 255 c5 259 c5)))
 ;; remember we're entering sequence numbers here, not bars.
 :instrument-change-map '((1 ((clarinet ((1 bass-clarinet)
                                         (96 b-flat-clarinet)
                                         (164 bass-clarinet)
                                         (237 b-flat-clarinet)))
                              (flute ((1 flute) (164 piccolo))))))
 :rehearsal-letters '(22 40 96 117 137 153 164 202 231 256 280 333 343 385 421
                      ;; we've added 10 from 583
                      461 471 500 534 544 575 593 622 630) 
 :staff-groupings '(3 1 2 3) 
 ;; for the first time I'm using more of a scale than a chord but of course
 ;; this scale has harmonic properties (e.g. microtones and something a little
 ;; octophonic like).  I also only change chord once--yikes!
 :set-palette (make-set-palette 
               'coming-set-palette
               ;;       consider              fs4 gs4 aqs4 bqs4 cs5?
               (let* ((pitches '(c4 d4 ef4 f4 gf4 af4 bqf4 cqf5 df5))
                      (ipitches (invert-pitch-list pitches))
                      (s1 (make-stack 's1 pitches 3))
                      (s2 (transpose (make-stack 's2 ipitches 3) 13)))
                 (list s1 s2)))
 :set-map `((1 ,(append
                 (ml 's1 101)
                 ;; chord/scale change
                 (ml 's2 78)          ; K = seq102, p56 (before deleting bars)
                 (ml 's1 80))))       ; P = seq 180, p92 (before deleting bars)
 :bars-per-system-map '((1 5))
 :rthm-seq-palette (palette +coming-rthm-chain-main+)
 :rthm-seq-map +coming-rthm-chain-main+
 :tempo-map 
 ;; set tempi by hand.  we have a max tempo of start-tempo * 4/3 and as we have
 ;; max 4 steps from start to max tempo our inc is 1/4 of this
 (let* ((start-tempo 140)
        (inc (/ (- (* start-tempo 4/3) 140) 4))
        ;; bar number, inc steps NB bar nums before deleting!
        (changes '(96 1 117 -1 164 1 202 1 231 -1 280 1 385 -1 421 -1 461 1
                   500 1 544 -2 593 1 622 1 630 1 637 1)))
   (append
    (list (list 1 start-tempo))
    (loop with current-steps = 0
       for bar in changes by #'cddr
       for steps in (cdr changes) by #'cddr
       do (incf current-steps steps)
       collect (list bar
                     (list 'q (round (+ start-tempo
                                        (* current-steps inc)))
                           (if (< steps 0) "Meno Mosso" "Piu Mosso")))))))

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

(proclaim '(special +coming-rthm-chain+))

;;; using the doubling data, add a textual indication to the player in the
;;; score as to who they're playing with.
(labels ((write-double (ins with start-bar)
           (add-cmn-marks-to-note 
            +coming-rthm-chain+ start-bar 1 ins 
            (format nil "with ~a"
                    (string-downcase (list-to-string with ", ")))))
         (write-doubles (instruments start-bar)
           (loop for ins in instruments
              for others = (remove ins instruments)
              do
              (write-double ins others start-bar))))
  (loop for double in +coming-doublings+
     for start-bar = (get-bar-num-from-ref
                      +coming-rthm-chain+ 1 (first double) 1)
     for ins = (flatten (list (third double) (fourth double)))
     do
     (unless (or (equal ins '(piano-lh piano-rh))
                 (equal ins '(piano-rh piano-lh)))
       (write-doubles ins start-bar))))
     
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;; Now set the popcorn dynamics

(let (amp-list last)
  (defun add-popcorn-dynamic (event &optional amps)
    (if (and (not event) amps)
        (setf amp-list amps
              last 0.0)
        (when (needs-new-note event)
          (setf (amplitude event) (pop amp-list))))))

(let ((pc (make-popcorn '(0.02 0.03) :min-spike 2 :max-spike 2.4)))
  (heat pc)
  (flet ((set-amps (start-bar end-bar start-dynamic end-dynamic)
           ;; start and end bars are inclusive
           (let ((start-amp (dynamic-to-amplitude start-dynamic))
                 (end-amp (dynamic-to-amplitude end-dynamic))
                 (num-notes (count-notes +coming-rthm-chain+
                                         start-bar end-bar t))
                 (pc-clone (clone pc)))
             (fit-to-length pc-clone num-notes)
             (scale pc-clone (max start-amp end-amp) (min start-amp end-amp))
             (add-popcorn-dynamic nil (if (< end-amp start-amp)
                                          (reverse (kernels pc-clone))
                                          (kernels pc-clone)))
             (unless (= (numk pc-clone) num-notes)
               (error "numk = ~a, num-notes = ~a" (numk pc-clone) num-notes))
             (process-events-by-time +coming-rthm-chain+
                                     #'add-popcorn-dynamic
                                     :start-bar start-bar :end-bar end-bar))))
    (set-amps 1 117 'pp 'mp)
    (set-amps 118 163 'pp 'p)
    (set-amps 164 201 'mf 'pp)
    (set-amps 202 230 'mf 'ff)
    (set-amps 231 279 'pp 'p)
    (set-amps 280 342 'f 'p)
    (set-amps 343 384 'f 'ff)
    (set-amps 385 420 'p 'ff)
    (set-amps 421 460 'pp 'mf)
    (set-amps 461 470 'ff 'mf)
    (set-amps 471 499 'p 'f)
    (set-amps 500 524 'f 'ff)
    (set-amps 525 533 'p 'mf)
    (set-amps 534 543 'f 'ff)
    (set-amps 544 620 'pp 'f)
    (set-amps 621 643 'f 'fff)))
                      
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

(midi-play +coming-rthm-chain+ 1 
           :midi-file "/Users/medward2/Dropbox/coming/coming.mid")

(load "/Users/medward2/Dropbox/coming/edits.lsp")
  
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

;;; EOF coming.lsp

don’t flinch

Posted in composition documentation by Michael Edwards on June 30th, 2011

Essentially, “don’t flinch” for guitar and computer is a three-part mensural
canon, but similar to late Mediaeval and Renaissance isorhythmic techniques,
melodic material is repeated along with a rhythmic sequence of differing
length. This is one of the simplest algorithms for a piece I’ve ever written,
so I thought it would make a good candidate for my first blog post on the
topic.

A basic 13-note scale ranging over an octave and a minor 6th (i.e. not an
octave repeating scale) is mapped over the full MIDI range starting from C0
(middle C being C4). With the guitar beginning in the third octave, an (also)
13-note melodic pattern is repeated 18 times, each time starting on a different
note. The transposition is determined by an offset curve mapped over the
duration of the piece and generally, but not linearly, moving from the lowest
to the highest registers of the guitar.

The same process runs in two other voices: a higher keyboard type voice, two
octaves higher, and moving four times as fast; and a bass line, moving at the
same speed as the guitar but in a lower octave and with accompanying chords.

Each voice also selects or rejects notes from the sequence according to a
fixed-seed random procedure controlled by a curve. Each voice uses the same
curve, which begins with 100% note inclusion, moves non-linearly down to 40% at
70% through the piece, and back up to 100% at the end. This, along with the
additive and subtractive rhythmic techniques (see below), accounts for the
transition from unbroken continuity to rather long stretches of short
punctuations or phrases separated by silence.

A voice consists of single notes or two-note chords (diads). The frequency of
the diads is controlled by a double cycle of lengths 17 and 19. This means that
if the note count is a multiple of either of these two numbers, a diad will be
created instead of a single note. The choice of note is determined by the same
pitch selection algorithm as for the guitar, but with the degree pattern
reversed and wrapped by one element e.g. wrap 1 2 3 4 5 6 7 8 9 10 by 1 –> 2 3
4 5 6 7 8 9 10 1.

Rhythms

Unlike a lot of contemporary music, including my own earlier work, I’m working
more and more these days with extremely simple rhythms, often involving nothing
more complex than a triplet. The general aim in doing this is to invest my
music with a renewed sense of pulse and perhaps even meter, though meter plays
no significant role in this piece. A driving pulse is perhaps also not so
evident here but the techniques used lead to the much more metrically and pulse
driven music found in, for example, “you are coming into us who cannot
withstand you”, for eight piece ensemble, from 2011.

The rhythms are derived from a six-element sequence repeated as many times as
necessary to accommodate the number of melodic sequences. Rhythmic values are
modified by an arithmetic procedure which adds or subtracts a 1/16th note to
various rhythms in the list depending on which part of the piece we’re in; this
contributes to the transition from unbroken to broken rhythmic flow as
described above. In addition, the three voices generated here use the rhythms
1) unaltered (guitar); 2) reversed (bass and chords); 3) wrapped by 4 (high
keyboard).

Chords

The bass line is rhythmically doubled by mainly three- to four-note harmonies,
extending to a six notes by the end. Chords are selected according to a simple
Lindenmayer sequence of eight rules/chords. In keeping with my obsession with
transition, each chord is presented in two variations, the second of which is
more cluster-like than the first. Over the course of the piece there is a
move from more open to more closed, cluster-based harmonies.

Post-generation modifications

After generating the piece and reading the MIDI file into Sibelius to create
the score, the normal guitar pitches were modified according to my goal of
using a bottleneck and other extended techniques. There is an overall
transition, though again not linear, from using the bottleneck continuously to
not using it at all. This is accomplished on a note-by-note basis using my
Fibonacci transition algorithm; this essentially splices in and swaps new
event types for old. A similar procedure was used to move from non-tremolo to
tremolo plucking techniques.

In addition, extended techniques for the guitar were added ad libitum,
including different plucking positions, from near the bridge to on the
fingerboard; tremolo with and without a plectrum; glissandi; exaggerated
vibrato with and without the bottleneck; various single and double harmonics;
pitch bends; and string rattles created by delicately touching a vibrating open
string with either the fingernail or the bottleneck.

The bottleneck sound was utmost in my mind from the very beginning of working
on this piece. I have a very strong and fond memory of watching Ry Cooder play
the guitar with a bottleneck on the now defunct UK TV music show “The Old Grey
Whistle Test” when I was about three or four years old. The sound of this has
remained with me my whole life and is strongly associated with the guitar for
me personally. Its connection to old blues recordings was further strengthened
during the composition of this piece when Yvonne Zehner, for whom it was
written, sent me ad hoc recordings she’d made of these techniques on a standard
PC. The poor quality of the PC’s soundcard, with all its crackles and band
limited spectral content, led to a recording that sounded not unlike blues
records from the 1930s, despite the fact that they were made in 2011 (no
criticism of Yvonne intended: it’s the sound card that’s at fault).

Use of the computer in performance

Viewed historically, this is essentially an instrument-plus-tape piece. The
computer is used only to trigger stereo sound files, sometimes at the push
of a pedal, other times once the onset of a guitar note is detected (but after
a gate is opened with another pedal, in order to avoid false triggerings).

The piece is definitely out of the ordinary in having what is essentially a
conventionally notatable computer part. Most electronic accompaniments in
pieces of this kind consist mainly of sounds that could only be made– perhaps
especially rhythmically–with computers or other electronic equipment. I was
attracted in this piece to the idea of creating an almost acoustic instrumental
trio, but having the luxury of continuously modifying, refining, and spectrally
shaping two of the voices through digital production techniques.

I will be curious to see how chamber music-like Yvonne’s approach to the piece
is. Ideally I would imagine her playing with the computer much in the same way
she would with other musicians. Naturally, I’m aware that the computer here
offers no sophisticated score following techniques which would allow tempo
fluctuations etc. I did investigate score following, but in order to take real
advantage of this I would have had to use MIDI sequences driving a sampler or
synthesiser. I know that I wouldn’t have been content with the output of these
without severe modifications and the cross fading of many different sound banks
per contrapuntal voice. So I elected instead to generate as many different
versions of each voice as I needed and then process them much as I would any
other audio data. The cutting of the final mix into shorter sound files that
are then triggered by Yvonne during the performance seemed to be the best
solution given my aims, and the desire for temporal flexibility at particular
points.

The two main voices in the computer then i.e. the high keyboard and the bass
line with chords, were generated with standard samples and software
synthesisers from Native Instruments, and the Ultra Analog VA-1 software
synthesiser by Applied Acoustics Systems. In addition, several sound files were
mixed in, including a recording of myself improvising on tenor saxophone;
myself reciting Rich’s poem; recordings of sheep; and Artaud’s “Pour en finir
avec le jugement de dieu”. The latter was used purely for its sonic and not its
semantic content.

dont flinch: score
(Recording to follow)

Common Lisp code

If you’re fluent in Lisp the following might be useful. To be run it requires
modules and classes from my algorithmic composition package “slippery chicken”
(to be released soon, I hope) but reading the comments and code should give a
good idea of how the piece was made.

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;
;;; File:             yvonne.lsp
;;;
;;; Project:          don't flinch: composition for guitar and computer
;;;
;;; Purpose:          Code for generation of score
;;;
;;; Author:           Michael Edwards: m@michael-edwards.org
;;;
;;; Creation date:    August 2010 (Edinburgh)
;;;
;;; $$ Last modified: 15:48:30 Thu Jun 30 2011 BST
;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

;;; The title is lifted from a poem by Adrienne Rich:
;;; Don't Flinch
;;; Adrienne Rich
;;; Granta 111, 2010
;;; 
;;; Lichen-green lines of shingle pulsate and waver
;;; when you lift your eyes. It's the glare.  Don't flinch
;;; The news you were reading
;;; (who tramples whom) is antique
;;; and on the death pages you've seen already
;;; worms doing their normal work
;;; on the life that was: the chewers chewing
;;; at a sensuality that wrestled doom
;;; an anger steeped in love they can't
;;; even taste. How could this still
;;; shock or sicken you?  Friends go missing, mute
;;; nameless.. Toss
;;; the paper.  Reach again
;;; for the Iliad.  The lines
;;; pulse into sense.  Turn up the music
;;; Now do you hear it? can you smell smoke
;;; under the near shingles?

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;; the slippery-chicken guitar object
(defparameter +yvonne-guitar+
  (make-instrument 
   'guitar :staff-name "guitar" :lowest-written 'e3 
   :highest-written 'b6 :chords t :midi-program 28 :starting-clef 'treble 
   :subset-id 'guitar :transposition-semitones -12 :microtones nil
   :chord-function 'guitar-chord-selection-fun :largest-fast-leap 31))

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;; Use a Lindenmayer System to create the intervallic structure (in semitones)
;;; of chords above a given bass note.
(defun get-yvonne-chord-seq (num)
  (let ((lfl (make-l-for-lookup 
              'yvonne-chord-seq
              ;; what the rules keys will actually translate to in terms of
              ;; interval structure. We progress from one list to another over
              ;; the course of the lookup procedure, so we tend towards more
              ;; cluster-like material.
              '((1 (((1 3 4)) ((1 2 3 4))))
                (2 (((1 2 4)) ((1 2 4 5))))
                (3 (((2 3 7)) ((2 3 5 6 7))))
                (4 (((3 4 6)) ((3 4 5 6))))
                (5 (((2 5 6)) ((2 4 5 6))))
                (6 (((3 5 6 8)) ((3 4 5 6 7 8))))
                (7 (((1 2 4 6)) ((1 2 3 4 5 6))))
                (8 (((1 3 4 6)) ((1 2 3 4 5 6)))))
              ;; the l-sys rules
              '((1 (4 5))
                (2 (3 6))
                (3 (4 2 6))
                (4 (1 8))
                (5 (7 4))
                (6 (8))
                (7 (3))
                (8 (5 2))))))
    (do-lookup lfl 1 (round num))))

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;; The main function.  This generates a MIDI file with three voices and four
;;; channels: the guitar (channel 1), the keyboard (channel 3), and the bass
;;; (channel 2) with the optional (but selected for don't flinch) chords
;;; (channel 4).  See the let at the very bottom of the function where the
;;; voice local function is called).
;;; 
;;; <num-seqs> is the number of times we loop through the degree-pattern 'tune'
;;; -- 18 in don't flinch
;;;
;;; <scale-structure> has a default set of pitches but we actually use
;;; +yvonne-scale+ (defined below) for don't flinch.
;;;
;;; <degree-pattern> is the melodic structure that is repeated num-seqs times;
;;; numbers here refer to step-by-step moves up and down the scale, using
;;; <start-octave> and <offset-curve>.  See sc-scale.lsp::sc-scale-note for
;;; details, but basically we start with the C in the <start-octave> and move
;;; up/down from there.  We start with C no matter what the 'tonic' of the
;;; scale is because it's the octave that's important in determining tessitura.
;;; <rhythms>.  We don't change this 13-note sequence for don't-flinch.
;;;
;;; <rhythms> is the 6-element sequence we cycle around (also not changed in
;;; don't flinch) as many times as necessary to accommodate <num-seqs> of
;;; <degree-patterns>.  These are modified however by an additive procedure
;;; which adds or subtracts a 1/16th note depending on which part of the
;;; 'piece' we're in (see the portion algorithm below).  In addition, the three
;;; voices generated here use the rhythms 1) unaltered 2) reversed 3) wrapped
;;; by 4 e.g. (wrap-list '(1 2 3 4 5 6 7 8 9 10) 4) -> (5 6 7 8 9 10 1 2 3
;;; 4). NB The latter's wrapping is independent of the <wrap> key argument.
;;;
;;; <offset-curve> (which is modified in the call which generates don't flinch)
;;; specifies in degrees the transposition of the degree-pattern.  This offset
;;; is calculated once per sequence, not on a note-by-note basis.  So we can
;;; create tessitura curves easily.
;;;
;;; <inclusion-curve> determines the percentage chance of a note being included
;;; or not. 100 means will be included, 0 not.  So this introduces randomness
;;; into the equation, but in order to get repeatable results we use a fixed
;;; seed random generator.
;;;
;;; <start-octave> is the pitch octave in which the pitch selection algorithm
;;; will begin.
;;;
;;; <min-amp> and <max-amp> are values between which random amplitudes will be
;;; selected (merely to liven up the playback of the MIDI file).
;;;
;;; <chord-intervals> selects the cycle of notes before a 2-note chord is
;;; selected in a generated part.  The default is changed in don't flinch.  If
;;; the note count is a cycle of one of these numbers (mod count x) == 0 then a
;;; chord will be generated.  NB After 2/3 of the piece we increase the
;;; number of 2-note chords by decrementing the numbers of this list until we
;;; hit <chord-int-min>.
;;;
;;; <wrap> After generating the complete part, should we start somewhere in the
;;; middle, loop back to the beginning and up to the start point?  If so, give
;;; a portion here e.g. 1/3.  NB This was experimented with but not actually
;;; used in don't flinch.
;;; 
;;; <chord-int-min> we'll reduce the chord-intervals as we progress but don't
;;; go less than this number (see <chord-intervals> above).

(defun yvonne (num-seqs midi-file &key 
               (tempo 60)
               (scale-structure '(d e gs as d ef g a bf cs d ef gf))
               ;; the repeating 'melodic' pattern: 13 notes
               (degree-pattern 
                '(-1 2 4    3 6 -2 -1 2 6    7 3 6 2))
               (rhythms '(q w+e q. h. h+s e.+w.))
               (offset-curve '(0 0 30 6 50 10 60 8 80 3 90 16 
                               100 17))
               (inclusion-curve '(0 100 100 100))
               (start-octave 3)
               (min-amp 0.5)
               (max-amp 0.65)
               ;; play a 2-note chord in cycles of these numbers
               (chord-intervals '(14 8))
               (wrap nil)
               (print-chords nil)
               (chord-int-min 4))
  ;; reset the fixed-seed random generator
  (random-rep 100 t)
  (let* ((scale (make-sc-scale 'c0 scale-structure))
         (lowest-degree (loop for deg in degree-pattern minimize deg))
         (rthms nil)
         (offset-curve-stretched (new-lastx offset-curve num-seqs))
         (highest-pitch nil)
         ;; get the transposition we'll need to fit the pattern into the lowest
         ;; notes possible 
         (initial-offset 
          (loop for offset from 0 
             for note = (sc-scale-note scale (+ offset lowest-degree)
                                       start-octave)
             do
             (when (in-range +yvonne-guitar+ note)
               ;; so we can get higher notes below
               (setf highest-pitch note)
               (format t "~&Lowest note will be ~a" (data note))
               (return offset))))
         ;; this will hold the number of pitches the get-pitches function
         ;; generated
         (num-pitches 0)
         ;; the basic unit used for additive/subtractive rhythms
         (add-rthm (make-rhythm 's)))
    (labels 
        ;; get all the pitches for a complete part with a sub-list for each
        ;; sequence. The interp-scaler stretches the offset so that the pitch
        ;; offset curve for the whole piece becomes more exaggerated.
        ((get-pitches (pattern &optional (offset 0) (interp-scaler 1.0)
                               (num-passes 1))
           ;; floating point so arithmetic is good in voice call
           (setf num-pitches 0.0)
           (loop for i below num-seqs by (/ num-passes)
              for coffset = (round (interpolate i offset-curve-stretched
                                                :scaler interp-scaler))
              collect
              (loop for degree in pattern and count from 0 
                 do (incf num-pitches)
                 collect
                 ;; so the offset is degrees, not semitones, and is a
                 ;; combination, in order, of the overall offset for this
                 ;; voice, the offset derived from the main offset curve, the
                 ;; offset calculated to make the lowest sequence fit onto the
                 ;; guitar, and the current degree.
                 (sc-scale-note scale (+ offset coffset initial-offset degree)
                                start-octave))))
         ;; depending on 2nd arg, this adds or subtracts <add-rthm> (1/16th) to
         ;; one of the rhythms in our list 
         (additive (index add &optional print)
           (let ((rthm (get-nth index rthms)))
             ;; min dur of add-rthm (1/16th)
             (when (or add (> (duration rthm) (duration add-rthm)))
               (let ((new (arithmetic rthm add-rthm (if add #'+ #'-) t)))
                 (if new
                     (progn 
                       (set-nth index new rthms)
                       (when print 
                         (format t "~&~a ~a"
                                 (if add "+" "-") (duration new))))
                     (error "can't do additive (dur ~a, add ~a)" 
                            (duration rthm) add))))))
         ;; return t if seq is a certain proportion through the total
         ;; i.e. between start and stop fractions (0->1) NB determines where we
         ;; are in the piece according to sequence count, not note count.
         (portion (seq start num-passes &optional (stop 1) print)
           (let* ((fraction (/ seq (* num-passes num-seqs)))
                  (result (and (>= fraction start) (<= fraction stop))))
             (when print
               (format t "~&seq ~a start ~a stop ~a fraction ~a result ~a"
                       seq start stop fraction result))
             result))
         ;; check whether a pitch is in the guitar range.  if we pass the
         ;; sequence-number, we assume note and time and issue a warning if out
         ;; of range.
         (check-range (pitch &optional seq note time)
           (let ((result (in-range +yvonne-guitar+ pitch)))
             (when (and seq (not result))
               (warn "out of range @ ~a (~a:~a): ~a" 
                     (secs-to-mins-secs time)
                     seq note (data pitch)))
             result))
         ;; from the bass note and intervals returned by get-yvonne-chord-seq,
         ;; get a chord object
         (extra-chord (bass-pitch intervals midi-channel
                                  &optional (octaves-up 1))
           (let* ((bass-up (sc-scale-degree 
                            scale bass-pitch (+ octaves-up (octave bass-pitch))
                            t)))
             (make-chord 
              (loop for interval in intervals 
                 ;; there will be some strangely high chords generated here
                 ;; but that's because the bass-note might not actually
                 ;; re-occur until quite high up in the scale. but they're a
                 ;; nice structural feature :) 
                 for pitch = (nth (+ interval bass-up) (scale-pitches scale))
                 when pitch collect pitch)
              :midi-channel midi-channel :force-midi-channel t)))
         ;; generate one part for the whole piece.
         (voice (midi-channel the-rhythms &key (pitch-offset 0) 
                              (interp-scaler 1.0) (time-scaler 1.0)
                              ;; basically a scaler for num-seqs 
                              (num-passes 1) (wrap-em wrap)
                              ;; produce another midi channel of chords to be
                              ;; played with this voice; either nil or the
                              ;; midi-channel for the chords
                              (extra-chords-channel nil) 
                              (do-additive t) (check-range t))
           ;; rthms is declared above so we can use them in additive
           (setf rthms (rhythm-list the-rhythms t))
           (let* ((pitches (get-pitches degree-pattern pitch-offset 
                                        interp-scaler num-passes))
                  (pitches-rev (get-pitches 
                                ;; start with the second element and
                                ;; wrap-around to the first at the end
                                (wrap-list (reverse degree-pattern) 1)
                                pitch-offset interp-scaler num-passes))
                  (incl-curve (new-lastx inclusion-curve (1- num-pitches)))
                  (chord-seq (when extra-chords-channel
                               (get-yvonne-chord-seq num-pitches)))
                  (events '()))
             (loop for seq in pitches and seq-rev in pitches-rev
                and seq-count from 1 
                with event-count = 0
                with time = 0 
                for print-first = t
                do
                ;; double bar at end of each top-level repeat?
                (loop 
                   for p in seq
                   for p2 in seq-rev
                   for p-highest = (pitch-max p p2)
                   for rhythm = (scale (get-next rthms) time-scaler)
                   for count from 0
                   for chord-possible = (or (not check-range) 
                                            (and (check-range p)
                                                 (check-range p2)))
                   for chord = (and chord-possible 
                                    (mods (1+ event-count) chord-intervals))
                   for include = (<= (random-rep 100.0)
                                     (interpolate event-count incl-curve))
                   do
                   (when include
                     (push (make-event p rhythm :start-time time
                                       :midi-channel midi-channel
                                       :amplitude (between min-amp max-amp))
                           events)
                     (when extra-chords-channel
                       (let* ((chord (extra-chord
                                      p (pop chord-seq) extra-chords-channel))
                              (echord (make-event 
                                       chord rhythm :start-time time
                                       :amplitude (between min-amp max-amp))))
                         (unless (has-notes chord)
                           (warn "no notes in extra chord"))
                         (push echord events)))
                     ;; main voice chord time?
                     (when chord
                       (when print-chords
                         (format t "~&chord (~a, ~a) @ ~a:~a"
                                 (data p) (data p2) seq-count count))
                       ;; start increasing chord regularity after a certain
                       ;; time  
                       (when (portion seq-count 2/3 num-passes)
                         (loop for int in chord-intervals and i from 0 do
                              (when (> int chord-int-min)
                                (decf (nth i chord-intervals))
                                (return))))
                       (push
                        ;; we use time no matter what the tempo as this will
                        ;; end up expressing time in 1/4 notes, which we want.
                        ;; we then write the tempo in the midi-file (below) so
                        ;; this takes care of everything
                        (make-event p2 rhythm :start-time time
                                    :midi-channel midi-channel
                                    :amplitude (between min-amp max-amp))
                        events))
                     (when print-first
                       (setf print-first nil)
                       (format t "~&~a: ~a ~a" (secs-to-mins-secs time)
                               (data p) (if chord (data p2) "")))
                     ;; uncomment to print every pitch in pattern
                     ;; (print (data p))
                     (when (pitch> p-highest highest-pitch)
                       (setf highest-pitch p-highest))
                     (when check-range
                       (check-range p seq-count count time)))
                   (when (mods count 13)
                     (incf time 5))
                   (incf event-count)
                   (incf time (compound-duration rhythm)))
                do
                ;; additive/subtractive rhythms during different parts of
                ;; the piece but always +/- 16th.
                (when do-additive
                  ;; from 1/4 to 2/3 of piece, subtractive on first rhythm,
                  ;; additive on second
                  (when (portion seq-count 1/4 num-passes 2/3)
                    (additive 0 nil)
                    (additive 1 t))
                  ;; from 1/4 to end, additive on 6th (so we get some quite
                  ;; long rests by the end)
                  (when (portion seq-count 1/4 num-passes)
                    (additive 5 nil))
                  ;; from 2/3 subtractive on the 2nd and 4th, additive on the
                  ;; first 
                  (when (portion seq-count 2/3 num-passes)
                    (additive 1 nil)
                    (additive 3 nil)
                    (additive 0 t))))
             (setf events (nreverse events))
             ;; just check which note classes we've used in this voice
             (loop for e in events with used-notes = '() do
                  (when (is-single-pitch e)
                    (pushnew (no-8ve (pitch-or-chord e)) used-notes))
                  finally (format t "~&~a used notes: ~a" 
                                  (length used-notes) used-notes))
             (format t "~&Duration: ~a, Highest Note: ~a"
                     (secs-to-mins-secs (start-time (first (last events))))
                     (data highest-pitch))
             (if wrap-em
                 (wrap-events-list events (floor (* wrap-em num-pitches)))
                 events))))
      ;; although we've been explicit about do-additive in the keyboard voice
      ;; (bg3) it defaults to t anyway in the other voices.
      (let ((bg1 (voice 1 rhythms)) ;; guitar
            ;; bass & organ chords: at the mo high/low 118/48 bf8/c3
            (bg2 (voice 2 (reverse rhythms) :check-range nil 
                        ;; bass: at the mo high/low 70/43 bf4/bf1
                        :extra-chords-channel 4 
                        :pitch-offset -12))
            ;; keyboard: at the mo high/low 120/72 c9/c5
            (bg3 (voice 3 (wrap-list rhythms 4) :pitch-offset 12 
                        ;; high keyboard voice has an exaggerated pitch offset
                        ;; curve applied to the sequences
                        :interp-scaler 1.2 :do-additive t
                        ;; rhythms are scaled by 1/4 so we'll need 4x as many
                        ;; passes 
                        :num-passes 4
                        :check-range nil :time-scaler 1/4)))
        (cm::event-list-to-midi-file 
         (append bg1 bg2 bg3) midi-file tempo 0)))))

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;; Generation of piece.

;;; NB The 13 notes here are only there to represent the interval structure of
;;; the scale.  The actual 'tonic' is give in the sc-make-scale call above
;;; (c0).
(defparameter +yvonne-scale+ '(d e f g af bf c d ef f gf af bf))

(yvonne 18 "/Users/medward2/Dropbox/dont-flinch/yvonne.mid" :tempo 52
        :chord-int-min 4 ;;:min-amp .2 :max-amp .35
        :chord-intervals '(17 19)
        :print-chords nil
        ;; this means missing B through whole piece (has BF6 as highest): use
        ;; that at end against final bass notes?  or as tremolo at various
        ;; points in piece?  D# missing in bass btw
        :offset-curve '(0 0 1 5 2 8 3 3 4 6 7 2 11 10 12 11 13 4 14 2 15 13 
                        16 3 17 10 19 1 20 16 21 17)
        ;; so this means some notes won't be used i.e. there might be < 13
        ;; notes per sequence 
        :inclusion-curve '(0 100 20 100 40 90 50 80 55 95 60 60 65 100 
                           70 40 75 70 90 100 100 100)
        :scale-structure +yvonne-scale+)

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;; Some post-generation fiddling/interference/quibbling.

(defun yvonne-transition (event1 event2 fib-trans-data)
  (let* ((midi-events
          ;; only count chords once
          (remove-duplicates 
           (cm::midi-file-to-events-list 
            "/Users/medward2/Dropbox/dont-flinch/score/yvonne-sib.mid" 1)
           :test #'(lambda (x y) (= (cm::object-time x) (cm::object-time y)))))
         (num-events (length midi-events))
         (fib-trans (fibonacci-transitions num-events fib-trans-data)))
    (loop for ft in fib-trans and me in midi-events do
         (when (typep me 'cm::midi)
           (format t "~&~a: ~a ~a" 
                   (if (zerop ft) event1 event2)
                   (secs-to-mins-secs (cm::object-time me))
                   (midi-to-note (cm::midi-keynum me)))))))

;;; transition between bottle-neck and ord.
(yvonne-transition 
 "NO" "YES"
 ;; the point of '(1 0 1 0) is we have 3 transitions: 1->0 0->1 and 1->0
 '(1 0 1 0))

;;; transition between no tremolo and tremolo
(yvonne-transition 
 "ord" "trem"
 '(0 1 0))

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

;;; EOF yvonne.lsp

University of Edinburgh Privacy Policy and cookie information