Next: Crescendo und Decrescendo , Previous: Die Partitur mit errechneten Wiederholungen der Sektionen , Up: Steve Reich: Piano Phase , Home: Allgemeine Einführung

Algorithmische Komposition mit Common Lisp

Erste Version des Codes für das gesamte Stück

Aus diesen Informationen lässt sich nun der Code für das gesamte Stück erstellen.

Zunächst müssen wir aber noch Anpassungen für die veränderte Syntax der Partitur vornehmen

  • Pausen

    Sektionen, in denen ein Piano pausiert, sind sehr einfach zu erstellen, indem man überprüft, ob der Shiftwert des jeweiligen Pianos nil ist und in diesem Fall keine Ereignisse generiert.

    Hierzu wird die Funktion shift-collect-section-events entsprechend modifiziert und in rest-shift-collect-section-events umbenannt.

  • Unterschiedliche Patterns

    Zusätzlich zu der Anpassung für Pausen muss die Funktion auch bezüglich der Möglichkeit angepasst werden, dass in beiden Pianos unterschiedliche Patterns sein können.

    Wir ändern daher die Funktion rest-shift-collect-section-events so, dass immer eine Liste mit den zwei Patterns für Piano 1 und Piano 2 übergeben wird und der loop zusätzlich über diese Liste iteriert.

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

    Für die Pausen muss zusätzlich eine Anpassung in der von collect-part-events verwendeten Funktion calc-shift vorgenommen werden: Wenn die Shift Liste in einem Klavier den Wert nil annimmt, soll der Wert 0 für den neuen Shift eingesetzt werden. Dafür kann die Funktion or verwendet werden: Sie überprüft alle ihre Argumente und liefert den Wert des ersten Arguments zurück, das nicht nil ist, d.h. der Funktion wird der Shiftwert des Pianos als erstes Argument übergeben und wenn das nil ist, wird die Zahl 0 zurückgegeben.

    (or 1 0)  ; => 1
    
    (or nil 0) ; => 0
    

    Damit kann man rest-calc-shift folgendermaßen definieren:

    (defun rest-calc-shift (old new)
      (list (+ (first old) (or (first new) 0))
            (+ (second old) (or (second new) 0))))
    
    ;;; Alternative Definition mit mapcar
    
    (defun rest-calc-shift (old new)
      (mapcar (lambda (o n) (+ o (or n 0))) old new))
    
    (rest-calc-shift '(0 1) '(0 2)) ; => (0 3)
    
    (rest-calc-shift '(0 1) '(0 nil)) ; => (0 1)
    

    Diese neue Funktion ersetzt in rest-collect-part-events die alte Funktion zur Errechnung der start-shifts. Zusätzlich wird in der let Bindung zu Beginn der Funktionsdefinitio von rest-collect-part-events sichergestellt, dass patterns immer eine Liste mit zwei Patterns ist: Wenn das erste Element von des im pattern Argument der Funktion übergebenen Werts eine Zahl ist, ist pattern nur ein Pattern und wird dupliziert in einer Liste an patterns gebunden. Andernfalls handelt es sich bei dem pattern bereits um eine Liste mit zwei Patterns und das Argument wird unverändert an patterns gebunden.

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

    Hier ein kurzer Test:

    (sprout
     (rest-collect-part-events
     '((4 (0 nil)) (5 (0 1)) (5 (0 0)))      ;;; Partitur des Teils mit Pause in Piano 2
     '(64 66 71 73 74 66 64 73 71 66 74 73)  ;;; Pattern des Teils
     0.139                                   ;;; Dauer von 1/16 Note
     0))
    
    (sprout
     (rest-collect-part-events
     '((4 (0 nil)) (5 (0 1)) (5 (0 0)))      ;;; Partitur des Teils mit Pause in Piano 2
     '((64 66 71 73 74 66 71 73)             ;;; Patterns des 2. Teils
       (64 76 69 71 74 76 69 71))
     0.139                                   ;;; Dauer von 1/16 Note
     0))
    
    

    Die Funktion zur Generierung der ganzen Partitur kann mit diesen Änderungen dann sehr einfach erstellt werden.

    Zunächst definieren wir eine Funktion part-duration, die die Gesamtdauer eines Teil errechnet:

    (defun part-duration (part pattern dtime)
      (loop for (num-repeats shift cresc) in part summing (section-duration num-repeats pattern dtime)))
    

    Die Funktion, die Piano Phase dann komplett spielt, ist sehr ähnlich zur Funktion rest-collect-part-events:

    (defun piano-phase (score patterns tempo)
      (loop
        with dtime = (get-duration 1/16 tempo)
        for start-time = 0 then (+ start-time part-duration)
        for part in score
        for pattern in patterns
        for part-duration = (part-duration part pattern dtime)
        append (rest-collect-part-events part pattern dtime start-time)))
    

    Mit dem folgenden Aufruf wird eine Version von Piano Phase komplett gespielt:

    (sprout
     (piano-phase
      (list (subseq (first *my-score*) 0 3))
      *piano-phase-patterns*
      '(3/8 72)))
    
    (defparameter *my-score* (get-real-score *piano-phase-score*))
    
    (piano-phase
     (list (subseq (first *my-score*) 0 3))
     *piano-phase-patterns*
     '(3/8 72))
    
    (subseq (first *my-score*) 0 3) ; => ((4 (0 nil)) (12 (0 0) (nil :cresc)) (11 (0 1)))
    (sprout
     (rest-collect-part-events
      '((2 (0 nil)) (12 (0 0) (nil :cresc)) (11 (0 1)))
      (caar *piano-phase-patterns*)
      0.139
      0))
    
    
    (sprout
     (rest-collect-part-events
     '((2 (0 nil)) (5 (0 1)) (5 (0 0)))      ;;; Partitur des Teils mit Pause in Piano 2
     '(64 66 71 73 74 66 64 73 71 66 74 73)  ;;; Pattern des Teils
     0.139                                   ;;; Dauer von 1/16 Note
     0))
    
    (sprout
     (rest-collect-part-events
     '((2 (0 nil)) (12 (0 0) (nil :cresc)) (11 (0 1)))
     '(64 66 71 73 74 66 64 73 71 66 74 73)
     0.139
     0))