Threads

Mal angenommen, Du hast eine Killer-Basslinie und einen krassen Beat gebaut. Wie kannst Du beides zur selben Zeit spielen lassen? Eine Möglichkeit ist es, beide Sounds per Hand ineinander zu weben - erst spielt der Bass ein bisschen, dann das Schlagzeug, dann der Bass etwas mehr… Beides aufeinander zeitlich abzustimmen wird jedoch immer schwieriger, vor allem, wenn noch mehr Sounds dazukommen sollen.

Was, wenn Sonic Pi Sounds automatisch für Dich ineinander flechten könnte? Kann es auch, und zwar mit einem besonderen Ding, welches Thread heißt.

Unendliche Schleifen

Damit das nächste Beispiel nicht zu kompliziert wird, musst Du Dir einfach vorstellen, dass dies Deine Killer-Basslinie und Dein krasser Beat sind:

loop do
  sample :drum_heavy_kick
  sleep 1
end
loop do
  use_synth :fm
  play 40, release: 0.2
  sleep 0.5
end

Wir haben das früher schon besprochen, Endlos-Schleifen sind wie schwarze Löcher für ein Programm; läuft es einmal in die Schleife kommt es da nicht mehr raus, bis Du den Stopp-Button klickst. Wie also können wir beide Schleifen zur selben Zeit abspielen? Wir müssen Sonic Pi sagen, dass wir einen bestimmten Abschnitt gleichzeitig mit dem Rest des Codes starten möchten. Hierbei helfen uns Threads.

Threads kommen zur Hilfe

in_thread do
  loop do
    sample :drum_heavy_kick
    sleep 1
  end
end
loop do
  use_synth :fm
  play 40, release: 0.2
  sleep 0.5
end

Wenn wir die erste Schleife in einen in_thread-do/end-Block hinein packen, sagen wir Sonic Pi, es soll den Inhalt dieses do/end-Blocks genau zur selben Zeit wie die nachfolgende Anweisung ausführen. Und das ist in diesem Fall die zweite Schleife. Probier es aus, und Du wirst den Beat zusammen mit der Basslinie hören!

Angenommen, dass wir obendrauf noch einen Synth legen wollten. Ungefähr so:

in_thread do
  loop do
    sample :drum_heavy_kick
    sleep 1
  end
end
loop do
  use_synth :fm
  play 40, release: 0.2
  sleep 0.5
end
loop do
  use_synth :zawa
  play 52, release: 2.5, phase: 2, amp: 0.5
  sleep 2
end

Jetzt haben wir das gleiche Problem wie vorhin. Die erste Schleife wird durch das in_thread zur selben Zeit wie die zweite gespielt. Aber die dritte Schleife wird nie erreicht. Also brauchen wir einen weiteren Thread:

in_thread do
  loop do
    sample :drum_heavy_kick
    sleep 1
  end
end
in_thread do
  loop do
    use_synth :fm
    play 40, release: 0.2
    sleep 0.5
  end
end
loop do
  use_synth :zawa
  play 52, release: 2.5, phase: 2, amp: 0.5
  sleep 2
end

Ablauf in Threads

Was Dich vielleicht erstaunt: Wenn Du den Ausführen-Button klickst, erzeugst Du tatsächlich einen neuen Thread, innerhalb dessen der Code abläuft. Deshalb entstehen immer neue Soundschichten, wenn Du den Ausführen-Button wiederholt klickst. Weil die Abläufe Threads sind, werden sie automatisch die Sounds verflechten.

Geltungsbereich (Scope)

Wenn Du Dich besser mit Sonic Pi auskennst, wirst Du herausfinden, dass Threads die wichtigsten Bausteine für Deine Musik sind. Threads können die aktuellen Einstellungen, die für einen Thread gelten, von anderen Threads isolieren. Was genau bedeutet das? Wenn Du etwa einen Synths mit use_synth durch einen anderen austauschst, dann veränderst Du den Synth lediglich für den aktuellen Thread - kein anderer der laufenden Threads bekommt den neuen Synth. Sehen wir uns das mal in Aktion an:

play 50
sleep 1
in_thread do
  use_synth :tb303
  play 50
end
sleep 1
play 50

Hast Du gehört, dass sich der mittlere Klang von den anderen beiden unterschieden hat? Die use_synth-Anweisung hat sich nur auf den Thread ausgewirkt, in dem sie auch stand, aber nicht auf den äußeren Haupt-Thread.

Vererbung

Wenn Du einen neuen Thread mit in_thread erzeugst, wird dieser vom vorherigen alle Einstellungen automatisch erben. Sehen wir uns das an:

use_synth :tb303
play 50
sleep 1
in_thread do
  play 55
end

Hast Du bemerkt, dass der zweite Ton mit dem :tb303-Synth gespielt wird, obwohl er in einem anderen Thread läuft? Jede der Einstellungen, die mit den unterschiedlichen use_*-Ausdrücken vorgenommen wird, verhält sich genauso.

Wenn neue Threads starten, erben sie alle Einstellungen von ihren Eltern. Wenn Du aber Einstellungen innerhalb dieser neuen Threads änderst, haben diese keine Einfluss auf die Eltern-Threads.

Threads benennen

Schließlich kannst Du Deinen Threads Namen geben:

in_thread(name: :bass) do
  loop do
    use_synth :prophet
    play chord(:e2, :m7).choose, release: 0.6
    sleep 0.5
  end
end
in_thread(name: :drums) do
  loop do
    sample :elec_snare
    sleep 1
  end
end

Sieh Dir das Protokoll-Fenster an, wenn Du diesen Code laufen lässt. Siehst Du, wie die Namen der Threads ausgeben werden?

[Run 36, Time 4.0, Thread :bass]
 |- synth :prophet, {release: 0.6, note: 47}

Nur ein Name pro Thread erlaubt

Eine letzte Anmerkungen zu Threads mit Namen: Es können nicht zwei gleichzeitig laufende Threads den gleichen Namen haben. Probieren wir das aus. Sieh Dir den folgenden Code an:

in_thread do
  loop do
    sample :loop_amen
    sleep sample_duration :loop_amen
  end
end

Kopiere das einmal in einen Puffer und klicke den Ausführen-Button. Klick in noch ein paar mal. Hör Dir diese Kakophonie mehrerer Amen-Breaks an, die rhythmisch nicht unbedingt passend zueinander ablaufen. Ok, jetzt kannst Du auf Stopp klicken.

Dieses Verhalten konnten wir schon woanders sehen - wenn Du den Ausführen-Button klickst, legen sich die neuen Klänge über die schon laufenden. Wenn eine Schleife abläuft, und Du den Ausführen-Button dreimal klickst, bekommst Du drei Ebenen mit Schleifen, die gleichzeitig ablaufen.

Mit benannten Threads ist das jedoch anders:

in_thread(name: :amen) do
  loop do
    sample :loop_amen
    sleep sample_duration :loop_amen
  end
end

Versuche mit diesem Code den Ausführen-Button mehrmals zu klicken. Du wirst immer nur eine Amen-Break-Schleife hören. Das kannst Du auch im Protokoll sehen:

==> Skipping thread creation: thread with name :amen already exists.

Sonic Pi sagt Dir, dass ein Thread mit dem Namen :amen bereits läuft und es deshalb keinen weiteren startet.

Vielleicht erscheint Dir dieses Verhalten im Moment noch nicht sinnvoll - aber es wird sehr nützlich sein, wenn wir ins Live-Coding einsteigen…