Sobald du ausreichend vertraut damit bist, mit mehreren gleichzeitig ablaufenden Funktionen und Threads live zu programmieren, wirst du bemerken, dass es ziemlich leicht ist in einem einzelnen Thread einen Fehler zu machen, der ihn zum Absturz bringt. Das ist nicht weiter schlimm, da du den Thread ja mit einem Klick auf Ausführen
einfach neu starten kannst. Wenn du den Thread aber neu startest, dann läuft er nicht mehr im Takt mit den anderen Threads.
Wie wir bereits vorher gesehen haben, erben neue Threads die mit in_thread
erzeugt werden alle ihre Einstellungen von einem Eltern-Thread. Das schließt auch die aktuelle Zeit mit ein. Das bedeutet, dass Threads immer miteinander im Takt sind, wenn sie gleichzeitig gestartet werden.
Wenn du aber einen Thread für sich alleine startest, spielt dieser in seinem eigenen Takt, der wahrscheinlich nicht mit irgendeinem der anderen gerade laufenden Threads synchron ist.
Sonic Pi bietet mit den Funktionen cue
und sync
eine Lösung für dieses Problem.
cue
erlaubt es uns, mit einem Taktgeber regelmäßig Signale an alle anderen Threads zu versenden. Normalerweise zeigen die anderen Threads an solchen Takt-Signalen kein Interesse und ignorieren sie. Mit der sync
-Funktion kann du jedoch erreichen, dass ein anderer Thread Interesse zeigt.
Wichtig ist dabei sich darüber bewusst zu sein, dass sync
ähnlich wie sleep
funktioniert, indem es den aktuellen Thread für eine bestimmte Dauer anhält. Allerdings legst du bei sleep
fest, wie lange du warten willst, während du bei sync
nicht weißt, wie lange gewartet werden wird - da sync
auf den nächsten cue
eines anderen Threads wartet - was eine kürzere oder längere Dauer sein kann.
Sehen wir uns das im Detail an:
in_thread do
loop do
cue :tick
sleep 1
end
end
in_thread do
loop do
sync :tick
sample :drum_heavy_kick
end
end
Hier haben wir zwei Threads - einer arbeitet wie ein Metronom, er spielt nichts, aber sendet bei jedem Schlag das Taktgeber-Signal :tick
. Der zweite Thread synchronisiert sich mit den tick
-Signalen, und wenn er ein Signal erhält, erbt er dadurch den Takt vom cue
-Thread und läuft weiter.
Im Ergebnis hören wir das :drum_heavy_kick
-Sample genau dann, wenn der andere Thread das :tick
-Signal sendet, auch dann wenn die Ausführung beider Threads gar nicht zur selben Zeit gestartet war:
in_thread do
loop do
cue :tick
sleep 1
end
end
sleep(0.3)
in_thread do
loop do
sync :tick
sample :drum_heavy_kick
end
end
Der dreiste Aufruf von sleep
würde normalerweise den zweiten Thread gegenüber dem ersten aus dem Takt bringen. Da wir jedoch cue
und sync
verwenden, synchronisieren wir beide Threads automatisch und umgehen dabei ungewollte Abweichungen in der Zeit.
Du kannst deine cue
-Signale benennen, wie du willst - nicht nur mit :tick
. Du musst nur sicherstellen, dass jegliche anderen Threads, die sich mit sync
synchronisieren, auch diesen Namen verwenden - ansonsten werden sie endlos warten (oder zumindest so lange bis du auf Stopp
klickst).
Lass uns mit ein paar Namen für cue
spielen:
in_thread do
loop do
cue [:foo, :bar, :baz].choose
sleep 0.5
end
end
in_thread do
loop do
sync :foo
sample :elec_beep
end
end
in_thread do
loop do
sync :bar
sample :elec_flip
end
end
in_thread do
loop do
sync :baz
sample :elec_blup
end
end
Hier haben wir eine taktgebende cue
-Schleife, die auf Zufallsbasis einen der drei Taktgeber-Namen :foo
, :bar
oder :baz
aussendet. Wir haben außerdem drei Schleifen-Threads, die sich unabhängig voneinander, jeder für sich, mit einem der Namen synchronisieren und dann jeweils ein anderes Sample spielen. Im Endeffekt hören wir jeden halben Schlag einen Klang, da jeder der sync
-Threads mit dem cue
-Thread auf Zufallsbasis synchronisiert ist und entsprechend sein Sample abspielt.
Das funktioniert natürlich auch, wenn du die Reihenfolge der Threads umkehrst, da die sync
-Threads einfach dasitzen und auf den nächsten cue
warten.