Next: Exkurs: Reihenfolge von Bindungen und destructuring-bind in loop Ausdrücken , Previous: Tempo und Dauer , Up: Steve Reich: Piano Phase , Home: Allgemeine Einführung

Algorithmische Komposition mit Common Lisp

Ein ganzer Teil

Ein ganzer Teil besteht aus einer wechselnden Abfolge von Sektionen, in denen beide Klaviere im gleichen Tempo spielen und Sektionen, in denen sich die Patterns durch ein schnelleres Tempo im zweiten Klavier allmählich um 1/16 Note verschieben. Die Anzahl der Wiederholungen innerhalb eines Teils ist dabei von Steve Reich mit einer Mindest- und einer Maximalzahl für jeden einzelnen Teil angegeben.

Um diese Abfolge möglichst übersichtlich zu halten, wird eine Partitur definiert, die die verschiedenen Sektionen enthält. In Clamps/Common Lisp ist es naheliegend, Partituren als verschachtelte Listen zu definieren.

Eine Sektion besteht dann aus zwei Elementen, der Anahl der Wiederholungen und einer Liste mit den Verschiebungen in den beiden Klavieren. Ein Teil ist dann einer Liste, deren Elemente die Parameter der einzelnen Sektionen sind:

Hier eine Übersicht:

;;; Sektion:

(anzahl-wiederholungen (shift-pno1 shift-pno2))

;;; Teil:

(sektion1 sektion2 sektion3 ...) = ((anzahl-wiederholungen1 (shift-pno1-1 shift-pno2-1))
                                    (anzahl-wiederholungen2 (shift-pno1-2 shift-pno2-2))
                                    (anzahl-wiederholungen3 (shift-pno1-3 shift-pno2-3))
                                    ...)

Die Partitur für die ersten 4 Sektionen von Piano Phase sieht dann folgendermaßen aus:

Hier eine Übersicht:

;;; Partitur des Beginns von Piano Phase:

'((10 (0 0)) (10 (0 0)) (10 (0 1)))

Es fehlen in der Partitur die Parameter pattern, dtime und pan der Funktion get-section-events. Der Grund dafür ist, dass dtime und pan sich im gesamten Stück nicht ändern und die Pattern in einem Teil konstant bleiben, so dass die Wiederholung dieser Werte innerhalb der Partitur unnötig ist und sie schlechter lesbar macht.

Auch wenn eine einzelne Sektion mit get-section-events gespielt werden kann, fehlen noch einige Informationen, um die Abfolge von Sektionen in der Partitur in einem Gesamablauf spielen zu können:

  • Es fehlt der Anfangszeitpunkt einer Sektion.
  • Es fehlt die Verschiebung des Patterns in beiden Klavieren zu Beginn einer Sektion, da die Patterns sich im Laufe eines Teils sukzessive verschieben.

Auch wenn man beide Informationen in der Partitur eintragen könnte, bleibt die Partitur lesbarer, wann man sich die Arbeit spart und die Zeiten aus den Informationen in der Partitur automatisch extrahiert.

Es geht aber nicht nur um die bessere Lesbarkeit, sondern insbesondere auch um folgende Vorteile:

  • Mögliche Fehler beim Eintragen in der Partitur werden auf diese Weise vermieden
  • Eine Änderung in der Mitte der Partitur (Anzahl Wiederholungen, Patternlänge, Verschiebungswerte, Tempoänderung etc.), zieht keine umfangreichen Änderungen im Rest der Partitur nach sich.

Zunächst erweitern wir die Funktion get-pno-events um die Parameter start-time und start-shift und passen auch die Funktion collect-section-events entsprechend an:

(defun shift-collect-pno-events (pattern num-repeats start-time dtime start-shift shift pan)
  (let* ((pattern-length (length pattern))
         (num-notes-no-shift (* pattern-length num-repeats))
         (num-notes-shift (+ shift num-notes-no-shift))
         (real-dtime (* dtime (/ num-notes-no-shift num-notes-shift))))
    (loop
      for i below num-notes-shift
      collect (new sfz
                :time (+ start-time (* i real-dtime))
                :keynum (elt pattern (mod (+ i start-shift) pattern-length))
                :duration dtime
                :amplitude -6
                :preset :yamaha-grand-piano
                :pan pan))))

(defun shift-collect-section-events (pattern num-repeats start-time dtime start-shifts shifts)
  (loop
    for start-shift in start-shifts
    for shift in shifts
    for pan from 0 by (/ 1 (1- (length shifts)))
    append (shift-collect-pno-events pattern num-repeats start-time dtime start-shift shift pan)))

Um einen ganzen Teil mit allen Sektionen ausgeben zu können, definieren wir zwei Hilfsfunktionen, section-duration und calc-shift, um die erweiterten Parameter für die Startzeit einer Sektion und deren Anfangsverschiebung zu errechnen.

Die Funktion section-duration erhält als Argumente die Anzahl der Wiederholungen, das Pattern und die Dauer einer 1/16 Note und errechnet daraus die Gesamtdauer der Sektion.

(defun section-duration (num-repeats pattern dtime)
  (let ((len (length (if (numberp (first pattern)) pattern (first pattern)))))
    (* num-repeats len dtime)))

(section-duration 10 '(64 66 71 73 74 66 64 73 71 66 74 73) (get-duration 1/16 '(3/8 72)))
 ; => 50/3 (16.666666)

Die Funktion calc-shift erhält als Argumente die aktuelle Position der Verschiebung der Pattern in den beiden Klavieren und die Verschiebung der Pattern innerhalb einer Sektion und errechnet daraus die resultierende Verschiebung der Pattern am Ende der Sektion.

(defun calc-shift (old new)
  (mapcar #'+ old new))

(calc-shift '(0 2) '(0 1))
 ; => (0 3)

Damit sind alle Voraussetzungen geschaffen, um einen gesamten Teil ausgeben zu können. Zum Errechnen der sfz Instanzen dient die Funktion collect-part-events (siehe den nächsten Abschnitt für eine Erläuterung der loop Form).

(defun collect-part-events (part pattern dtime start)
  "Collect all events of one part of the piece."
  (loop
    for start-time = start then (+ start-time section-duration)
    for start-shifts = '(0 0) then (calc-shift start-shifts shifts)
    for (num-repeats shifts) in part
    for section-duration = (section-duration num-repeats pattern dtime)
    append (shift-collect-section-events pattern num-repeats start-time dtime start-shifts shifts)))

Mit der Funktion kann nun ein gesamter Teil gespielt werden:

(sprout
 (collect-part-events
  '((4 (0 0)) (5 (0 1)) (5 (0 0)))        ;;; Partitur des Teils
  '(64 66 71 73 74 66 64 73 71 66 74 73)  ;;; Pattern des Teils
  0.139                                   ;;; Dauer von 1/16 Note
  0))                                     ;;; Anfangszeit des Teils