Algorithmische Komposition mit Common Lisp
Realisation des Spectral Canon mit Clamps
Bezüglich der Einsatzabstände ist in der Partitur vermerkt, dass der erste Einsatzabstand 4 Sekunden betragen soll.
Wir berechnen aus diesen Angaben einen Faktor, den wir verwenden können, um den Startzeitpunkt für jeden "Partialton" errechnen zu können. Da der erste Ton der ersten Stimme dem 8. Partialton entspricht, ist der Faktor die Zahl, mit der die Differenz zwischen dem 9. und dem 8. Partialton multipliziert werden muss, um die Zahl 4 zu erhalten:
(defvar *faktor* nil) (setf *faktor* (/ 4 (- (log 9 2) (log 8 2)))) ; => 23.5398 (* *faktor* (- (log 9 2) (log 8 2))) ; => 4.0
Legt man diesen Faktor zu Grunde und beschriftet die Zeitachse entsprechend, erhält man Abb. 18
Wie aus Abb. 18 ersichtlich ist, liegt der Startzeitpunkt des 8. Partialtons in Stimme 1 nicht bei 0 (0 ist der Startzeitpunkt des ersten Partialtons), sondern bei (* *faktor* (log 3 2)) = 70.62 Sekunden. Dieser Wert muss von allen berechneten Zeiten für die Töne abgezogen werden, um deren absoluten Zeitpunkt zu berechnen.
Der Spectral Canon weist eine weitere Besonderheit auf: Die erste Stimme beschleunigt bis zum Einsatz der 24. Stimme. Anschließend werden die Einsatzabstände bis zu dieser Stelle umgekehrt (im Krebs gespielt), so dass die erste Stimme in der zweiten Hälfte der Komposition ein Ritardando macht, bis am Schluss wieder ein Einsatzabstand von 4 Sekunden erreicht wird. An dieser Stelle endet das Stück, d.h. die 24. Stimme hat zu diesem Zeitpunkt rhthmisch lediglich die erste Hälfte mit dem Accelerando gespielt.1
Die Funktion get-time fasst all diese Dinge zusammen: Sie bekommt als Argumente einen Index (mit 0 beginnend) und eine Nummer der Stimme (mit 1 beginnend) und errechnet daraus den Startzeitpunkt des Tons an dieser Stelle in Sekunden.
(let* ((factor (/ 4 (- (log 9 2) (log 8 2)))) ; => 23.5398 (neg-offset-voice-one (* -1 factor (log 8 2))) ; => -70.6194 (section-length (* 24 8)) ; => 192 (section-duration (+ neg-offset-voice-one (* factor (log (+ section-length 8) 2))))) ; => 109.315445 (defun get-time (idx voice) (let ((section (floor idx section-length)) (start-offset (+ (* factor (log (* voice 8) 2)) neg-offset-voice-one))) (if (evenp section) (+ start-offset (* section section-duration) (* factor (log (+ (mod idx section-length) 8) 2)) neg-offset-voice-one) (+ start-offset (* (1+ section) section-duration) (* -1 (+ (* factor (log (+ (- 192 (mod idx section-length)) 8) 2)) neg-offset-voice-one))))))) #| ;;; Beispielaufrufe: (get-time 0 1) ; => 0.0 (get-time 1 1) ; => 4.0 (get-time 0 2) ; => 23.539803 (get-time 1 2) ; => 27.539803 |#
- faktor ist der oben beschriebene Skalierungsfaktor, um zwischen dem Zweierlogarithmus des 8. und dem Zweierlogarithmus des 9. "Partialtons" 4 Sekunden zu erhalten.
- neg-offset-voice-one ist der Einsatzzeitpunkt des 8. Partialtons, multiplizert mit -1, den man zu allen Einsatzzeitpunkten aller Töne addieren muss, um sie um diesen Einsatzzeitpunkt vorzuverlegen ("nach links zu schieben").
- section-length ist die Anzahl der Töne eines vollständigen Accelerandos. Da das Accelerando beim Stimmeneinsatz der 24. Stimme endet und jeder neue Stimmeinsatz nach 8 Tönen der ersten Stimme erfolgt, ist dies (* 24 8) = 192.
- section-offset ist die Dauer einer Sektion. Diese Dauer ist die Differenz zwischen dem Einsatz der letzten Stimme und dem Einsatz der ersten Stimme. Die letzte Stimme setzt bei Index (+ 8 (* 24 8)) (bzw. (* 25 8)) ein und die erste Stimme bei Index 8, d.h. die Dauer einer Sektion ist (* faktor (- (log (* 25 8) 2) (log 8 2))).
Innerhalb der Funktion get-time wird zunächst der Sektionsindex für idx als (floor idx section-length) berechnet und an das Symbol section gebunden.
start-offset des Stimmeinsatzes ist die Summe des Zeitpunktes des Stimmeinsatzes und dem negativen Zeitpunkt des Einsatzes der ersten Stimme, also (+ (* faktor (log (* voice 8) 2)) offset-voice-one).
Die if clause (zerop (mod section 2)) prüft, ob es sich um ein accelerando, oder ein ritardando handelt. Im ersten Fall errechnet sich der Zeitpunkt des Tons mit dem Index idx aus der Summe des Startzeitpunkts der Sektion (* section section-duration), dem start-offset des Stimmeinsatzes, dem Zeitpunkt des Tons bei Index idx und dem negativen Einsatzzeitpunkt der ersten Stimme neg-offset-voice-one.
Im Falle des Ritardandos in Sektionen mit ungeradem Index wird die Indexreihenfolge umgekehrt (mit (- 192 idx)) und dessen Startzeitpunkt dann mit -1 multipliziert und vom Ende der Sektion ((* (1+ section) section-duration)) subtrahiert.
Damit lässt sich die Startzeitpunkt für alle Töne der ersten Stimme folgendermaßen berechnen:
(loop for idx from 0 with voice = 1 with end-time = (get-time 384 1) for time = (get-time idx voice) while (<= time end-time) collect time) #| => (0.0 4.0 7.578125 10.814934 13.769905 16.488213 19.004974 21.34803 23.539803 25.598663 27.53981 29.375969 31.117928 32.77487 34.35473 35.86435 37.3097 38.696045 40.028015 41.309708 42.544777 43.73651 44.887825 46.001396 47.079605 ...) |#
Die Funktion spectral-canon schließlich erzeugt sämtliche sfz Instanzen für den gesamten Spectral Canon. Wenn das optionale Argument dur nicht gesetzt ist, wird als Dauer eines Tons der Einsatzabstand zum nächsten Ton multipliziert mit 1.2 verwendet, um der Partiturvorgabe, dass bei dem Klavier das rechte Pedal immer gedrückt sein soll, zu entsprechen. Für die grafische Darstellung ist es besser, eine Dauer, die kürzer, als der kürzeste Einsatzabstand ist (also z.B. 0.1) zu verwenden, da dadurch die rhythmische Struktur besser bzw. überhaupt zu erkennen ist.
(defun spectral-canon (&optional dur) (loop for voice from 1 to 24 for keynum = (keynum (* voice (hertz 45)) :hertz) append (loop for idx from 0 for last = -4 then time for time = (get-time idx voice) for duration = (or dur (* 1.2 (- time last))) while (<= time (get-time 384 1)) collect (new sfz :time time :keynum keynum :duration duration :preset :yamaha-grand-piano)))) #| ;;; Beispielaufrufe: (sprout (spectral-canon)) (sprout (spectral-canon) :to (svg-gui-path "spectral-canon.svg")) (svg->browser "spectral-canon-short.svg") (set-val clamps.svgd:seq (import-events (svg-gui-path "spectral-canon.svg") :x-scale 1)) |#
Fußnoten:
Es ist auffallend, dass die erste (tiefste) und 24. (höchste) Stimme einen rhythmischen "Crossfade" ausführen, der an den berühmten Canon X für PlayerPiano von Conlon Nancarrow erinnert. Dies ist sicher kein Zufall, sondern eine bewusste Referenz, da der Spectral Canon Conlon Nancarrow gewidmet ist und auf dessen PlayerPiano uraufgeführt wurde.