Bald wirst Du ausreichend vertraut damit sein, live mit einer Anzahl von
gleichzeitig ablaufenden Funktionen und Threads zu coden. Dann wirst Du
bemerken, dass es ziemlich leicht ist, einen einzelnen Thread mit einem
Fehler zum Abbruch zu bringen. Das ist nicht weiter schlimm, da
Du den Thread mit einem Klick auf Ausführen
ja einfach neu starten
kannst. Wenn Du den Thread aber neu startest, dann läuft er nicht mehr
zusammen mit den anderen so wie vorher, er ist nun aus dem Takt mit
den ursprünglichen Threads.
Wir haben zuvor erwähnt, dass neue Threads beim Start mit
in_thread
alle ihre Einstellungen vom Eltern-Thread erben.
Das schließt auch die aktuelle Zeit mit ein. Das bedeutet, dass
Threads bei gleichzeitigem Start immer miteinander im Takt sind.
Wenn Du aber einen Thread alleine startest, spielt er in seinem eigenen Takt, der höchstwahrscheinlich nicht mit irgendeinem der anderen laufenden Threads zusammenpasst.
Sonic Pi hat mit den Funktionen cue
und sync
eine Lösung für dieses
Problem.
cue
erlaubt es uns, regelmäßige Signale an alle anderen Threads mit
einem Taktgeber zu versenden. Standardmäßig sind die anderen
Threads an solchen Signale nicht interessiert und werden sie
ignorieren. Mit der sync
-Funktion kann man jedoch erreichen, dass ein
anderer Thread auf diesen Taktgeber hört.
Eine wichtige Sache, derer man sich bewusst sein sollte: sync
ist so
ähnlich sleep
, weil es den aktuellen Thread für eine bestimmte Zeit
anhält und dieser so lange nichts tut. Allerdings sagt man mit sleep
,
wie lange man warten möchte, während man bei sync
nicht weiss, wie
lange gewartet wird - denn sync
wartet auf das nächste cue
eines
anderen Threads; das kann kürzer oder länger dauern.
Sehen wir uns das etwas genauer 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 davon arbeitet wie ein Metronom; er
spielt nichts, aber sendet bei jedem Schlag ein :tick
-Taktgebersignal.
Der zweite Thread synchronisiert sich mit den tick
-Signalen, und wenn
er eins davon erhält, übernimmt er die Zeit vom cue
-Thread und läuft
weiter.
Als Ergebnis hören wir den :drum_heavy_kick
Sample genau dann, wenn
der andere Thread ein :tick
-Signal sendet, auch dann, wenn beide
Threads gar nicht zur selben Zeit gestartet sind.
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
Dieser freche (zweite) Aufruf von sleep
würde eigentlich den zweiten
Thread gegenüber dem ersten aus dem Takt bringen. Weil wir aber cue
und sync
verwenden, synchronisieren wir beide Threads automatisch und
umgehen dabei, dass beide in der Zeit auseinanderfallen.
Du kannst cue
-Signale benennen, wie Du willst - nicht nur mit
:tick
. Du musst nur sicherstellen, dass irgendein anderer Thread sich
mit eben diesem Namen synchronisiert - ansonsten wartet der zweite
Thread dann für immer (oder jedenfalls bis Du den Stopp
-Button
klickst).
Lasst uns ein paar Namen für cue
ausprobieren:
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 cue
-Hauptschleife, die auf Zufallsbasis einen von
drei Namen des Taktgebers :foo
, :bar
oder :baz
aussendet. Wir
haben auch drei Schleifen-Threads, die jeder für sich mit einem der
Namen synchronisiert werden und dann jeweils ein anderes Sample spielen.
Im Endeffekt hören wir bei jedem halben Schlag einen Klang, da jeder
der sync
-Threads auf Zufallsbasis mit dem cue
-Thread
synchronisiert ist, und sein Sample spielt, wenn er dran ist.
Das funktioniert natürlich auch, wenn Du die Reihenfolge der Threads
umkehrst: Die die sync
-Threads sitzen einfach da und warten auf ihren
nächsten cue
.