Threads

Nun hast du also eine Killer-Basslinie und einen krassen Beat gebaut. Wie kannst du beide zur selben Zeit spielen lassen? Eine Möglichkeit ist, beide Sounds per Hand miteinander zu verweben - spiele erst den Bass ein bisschen, dann das Schlagzeug, dann den Bass etwas mehr… Beides zeitlich aufeinander abzustimmen wird jedoch gedanklich bald immer schwieriger, vor allem, wenn noch mehr Klänge dazukommen sollen.

Was, wenn Sonic Pi Klänge automatisch für dich miteinander verweben könnte? Nun, das kann es, und zwar erreichst du das mit einem besonderen Ding, welches Thread (Strang) genannt wird.

Unendliche Schleifen

Damit dieses 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, 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 auf Stopp 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 Reihenfolgen (threads).

Threads als Rettung

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

Indem 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 nächste Anweisung nach dem do/end-Block ausführen (und das ist in diesem Fall die zweite Schleife). Probiere es aus, und du wirst den Beat und die Basslinie miteinander verwoben hören!

Mal angenommen, wir wollten darüber noch einen Synth hinzufügen. 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

Ausführen als Thread

Was dich vielleicht erstaunt: Wenn du auf Ausführen klickst, erzeugst du eigentlich einen neuen Thread, innerhalb dessen der Programm-Code abläuft. Deshalb entstehen immer neue Klangschichten, wenn du wiederholt auf Ausführen klickst. Weil diese Abläufe jeweils für sich Threads sind, werden sie automatisch die Klänge für dich miteinander verweben.

Bereich

Während du Sonic Pi besser zu meistern lernst, wirst du auch herausfinden, dass Threads die wichtigsten Bausteine für deine Musik sind. Eine der wichtigen Aufgaben die sie ausführen, ist die aktuellen Einstellungen, die für einen Thread gelten, von anderen Threads zu isolieren. Was genau bedeutet das? Nun, wenn du etwa einen Synth mit use_synth durch einen anderen ersetzt, dann veränderst du den Synth lediglich für den aktuellen Thread - bei keinem anderen der laufenden Threads wird der Synth ersetzt. 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

Bemerke, wie 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 ausführenden Thread.

Vererbung

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

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

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

Wenn neue Threads erzeugt werden, erben sie alle Einstellungen von ihren Eltern. Aber Änderungen der Einstellungen innerhalb dieser neuen Threads haben umgekehrt keinen Einfluss auf die Eltern.

Threads benennen

Und schließlich können wir unseren Threads auch 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

Achte auf das Protokoll-Fenster, wenn du diesen Code laufen lässt. Siehst du, wie das Protokoll mit den Nachrichten auch die Namen der Threads ausgibt?

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

Nur ein Thread pro Name erlaubt

Eine letzte Anmerkung zu Threads mit Namen: Es kann nur ein Thread gleichen Namens zur selben Zeit laufen. Probieren wir das aus. Sieh dir 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 auf Ausführen. Klicke noch ein paar mal darauf. Hör dir diese Kakophonie mehrerer Amen-Breaks an, die rhythmisch nicht unbedingt passend zueinander ablaufen. Ok, du kannst jetzt Stopp klicken.

Dieses Verhalten haben wir bereits öfter gesehen - wenn du die Ausführen-Schaltfläche klickst, legen sich Klänge über alle bereits laufenden Klänge. Wenn du eine Schleife hast und dreimal auf Ausführen klickst, bekommst du drei Ebenen mit Schleifen, die gleichzeitig spielen.

Bei benannten Threads jedoch ist das anders:

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

Versuche bei diesem Code den Ausführen-Schalter 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 teilt dir mit, dass ein Thread mit dem Namen :amen bereits läuft und es deshalb keinen weiteren erzeugt.

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