2.3.7. Prozesse
Prozesse sind in Common Music Abstraktionen zur Steuerung zeitlicher Abläufe, vergleichbar mit einem Midi-Sequencer der Midi- bzw. Klangereignisse im Zeitverlauf steuert.
In Common Music ist das process Makro der zentrale Baustein, um zeitliche Ereignisfolgen zu definieren. Dieses Makro hat eine sehr ähnliche Struktur und Syntax, wie das loop Makro von Common Lisp. Beide Makros definieren Iterationen, d.h. Wiederholungen, deren Parameter (Anzahl der Wiederholungen, Variablen, Rückgabewerte, Abbruchbedingungen, etc.) über spezielle Symbole, sogenannte 'Klauseln' definiert werden können. Diese Klauseln ermöglichen sehr mächtige und flexible Steuerungsmechanismen und bilden eine eigene, in Common Lisp eingebettete Sprache zur Beschreibung von Iterationen. Es erfordert einige Zeit, mit den Feinheiten vertraut zu werden, lohnt aber die Mühe und den Zeitaufwand des Lernens. Neben dem oben verlinkten Kapitel zu loop
aus dem Sprachstandard (cltl2) gibt es in dem Kapitel Loop for Black Belts in Peter Seibel’s Buch 'Practical Common Lisp' eine sehr empfehlenswerte Einführung in das loop
Makro 12:
Im process
Makro von Common Music sind über die von loop
bekannten Klauseln hinaus vor allem die Schlüsselwörter output
und wait
wichtig, da sie die Voraussetzung für die zeitlich strukturierte Ausgabe von Ereignissen bilden. output
erzeugt die Ausgabe eines Ereignisses und wait
bezeichnet die Zeitdifferenz zwischen den Iterationen. Die Klausel repeat
gibt die Anzahl der Iterationen an.
;; Definition eines Prozesses mit zwei Midiereignissen mit 1 Sekunde Zeitabstand (process repeat 2 output (new midi) wait 1)
Das process
Makro 'definiert' dabei lediglich den Prozess, ohne irgendwelche Ereignisse zu erzeugen. Um die Ereignisse eines Prozesses tatsächlich auszulösen, werden die Funktionen sprout
oder events
verwendet:
;; Verschiedene Ausgabemöglichkeiten eines Prozesses mit zwei ;; Midiereignissen mit 1 Sekunde Zeitabstand ;;; Echtzeit (rts muss zuvor gestartet worden sein): (sprout (process repeat 2 output (new midi) wait 1)) ;;; Echtzeit unter Verwendung der events funktion: (events (process repeat 2 output (new midi) wait 1) *rts-out*) ;;; Ausgabe in eine Midi-Datei: (events (process repeat 2 output (new midi) wait 1) "/tmp/test.midi") ;;; Ausgabe in eine Lilypond Datei: (events (process repeat 2 output (new midi) wait 1) "/tmp/test.ly")
Die Klausel output
hat eine ähnliche Funktionsweise, wie die oben beschriebene Funktion output, allerdings eine andere Syntax: Hinter der Klausel output
muss ein Ausdruck stehen, dessen Wert ein Ereignis ist. Im Unterschied hierzu steht die Funktion output
in Klammern und erwartet als 'Argument' einen Ausdruck, dessen Wert ein Ereignis ist, das ausgegeben werden kann.
Die Funktion output
kann allerdings in Verbindung mit der do
Klausel auch innerhalb des process
Makros verwendet werden. Das nachfolgende Beispiel beschreibt den gleichen Prozess wie zuvor mit Hilfe der Klausel do
und der Funktion output
.
(sprout (process repeat 2 do (output (new midi)) wait 1))
Auch innerhalb eines process
ist es möglich, die Ausgabe von output
auf einen speziellen Stream/Port umzuleiten. Hierzu dient die Klausel to
:
;;; Erzeugung eines incudine-streams mit 4-Kanal channel-tuning (make-mt-stream *mt-out01* *midi-out1* '(4 0)) ;;; Umleitung von output in einem process mit der Klausel "to" (sprout (process repeat 2 output (new midi :keynum 60.5) to *mt-out01* wait 1)) ;;; Alternative Lösung mit der Funktion output, der Klausel "do" und ;;; dem Keywort :to: (sprout (process repeat 2 do (output (new midi :keynum 60.5) :to *mt-out01*) wait 1))
2.3.7.1. Prozesse als Funktionen
Die Definition von Prozessen als Funktionen ermöglicht die Abstraktion eines bestimmten formalen Ablaufs mit Hilfe eines Namens. Über Funktionsargumente lassen sich diese Abläufe parametrisieren.
Die nachfolgende Funktion definiert ein Arpeggio. Funktionsargumente sind der arpeggierte Akkord, die Anzahl von Durchläufen und die Geschwindigkeit des Arpeggios.
(defun arpeggio (chord dtime) (process for keynum in chord output (new midi :keynum keynum) wait dtime)) ;; Abspielen dieses Prozesses mit sprout bzw. events: (sprout (arpeggio '(60 66 71 77) 0.1))
2.3.7.2. Verschachtelte Prozesse
Besonders hervorzuheben ist die Möglichkeit, Prozesse als Bestandteile anderer Prozesse zu verwenden.
Das oben definierte Arpeggio lässt sich beispielsweise als Bestandteil eines Prozesses definieren, der eine wiederholte Repetition von Arpeggios erzeugt.
(defun arpeggio-repeat (chord numrepeats dtime) (process repeat numrepeats sprout (arpeggio chord dtime) wait (* (length chord) dtime))) ;; Abspielen dieses Prozesses mit sprout bzw. events: (sprout (arpeggio-repeat '(60 66 71 77) 4 0.2))
Diese Funktion wiederum kann in einer Funktion verwendet werden, die eine rhythmisierte Akkordfolge arpeggiert.
(defun arpeggio-akkordfolge (akkordfolge wiederholungen dtime) (process for chord in akkordfolge for repeats in wiederholungen sprout (arpeggio-repeat chord repeats dtime) wait (* (length chord) repeats dtime))) ;; Abspielen dieses Prozesses: (sprout (arpeggio-akkordfolge '((60 66 71 77) (60 65 73) (62 68 73 81) (61 69 71 74 77 83) (55 61 66 72 67 63 57) (41 43 47 51 57 63 65 71 74 80 82)) '(4 2 3 1 1 1) 0.1)) (sprout (arpeggio-akkordfolge '((60 64 67 72 76 67 72 76) (60 62 69 74 77 69 74 77) (59 62 67 74 77 67 74 77) (60 64 67 72 76 67 72 76)) '(2 2 2 2) 0.12))
12: Es sollte an dieser Stelle allerdings erwähnt werden, dassprocess
nicht den vollen Umfang vonloop
implementiert. So fehlen beispielsweise die Iteration über Hash-Tables, argument-destructuring und anderes mehr. ↩