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. Normalerweise 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 weiß, 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 endlos (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 sync
-Threads sitzen einfach da und warten auf ihren nächsten cue
.