Variablen

Beim Coden ist es sehr nützlich, Namen für Dinge zu vergeben. Sonic Pi macht es Dir hier sehr leicht: Schreib einfach den Namen, den Du gerne verwenden möchtest, dann ein Gleichheitszeichen (=) und dann das Ding, welches Du Dir merken möchtest:

sample_name = :loop_amen

Hier haben wir uns das Symbol :loop_amen mit der Variable sample_name gemerkt. Wir können nun sample_name überall da benutzen, wo wir auch loop_amen benutzen könnten. Zum Beispiel:

sample_name = :loop_amen
sample sample_name

Es gibt drei Hauptgründe, Variablen in Sonic Pi zu nutzen: Bedeutung vermitteln, Wiederholung steuern und Ergebnisse speichern.

Bedeutung vermitteln

Wenn Du Code schreibst, könntest Du denken, dass Du einfach nur dem Computer sagst, was er tun soll - solange der Computer das versteht, ist es okay. Vergiss aber nicht, dass nicht nur ein Computer Deinen Code lesen will. Andere Leute könnten den Code auch lesen und wollen verstehen, was da vor sich geht. Wahrscheinlich wirst Du den Code auch später selbst wieder lesen und möchtest verstehen, was er bedeutet. Obwohl Dir jetzt vielleicht alles offensichtlich erscheint - wahrscheinlich ist es für andere nicht ganz so offensichtlich und vielleicht nicht einmal für Dich in der Zukunft!

Mit Kommentaren hilfst Du anderen, Deinen Code zu verstehen. Und Du kannst sinnvolle Namen für Deine Variablen verwenden. Sie Dir diesen Code an:

sleep 1.7533

Warum steht hier die Zahl 1.7533. Woher kommt diese Zahl? Was bedeutet sie? Sieh Dir zum Vergleich diesen Code an:

loop_amen_duration = 1.7533
sleep loop_amen_duration

Aha, jetzt ist viel klarer, was 1.7533 bedeutet: Es ist die Dauer des Sample :loop_amen! Natürlich kannst Du jetzt sagen, warum nicht einfach schreiben:

sleep sample_duration(:loop_amen)

Das ist natürlich auch ein sehr guter Weg, die Absicht hinter dem Code mitzuteilen.

Wiederholungen steuern

In Deinem Code wirst Du oft Dinge wiederholen, und wenn Du irgendwo etwas ändern willst, musst Du das an vielen Stellen tun. Schau Dir mal diesen Code an:

sample :loop_amen
sleep sample_duration(:loop_amen)
sample :loop_amen, rate: 0.5
sleep sample_duration(:loop_amen, rate: 0.5)
sample :loop_amen
sleep sample_duration(:loop_amen)

Hier machen wir eine ganze Menge mit dem :loop_amen! Was wäre, wenn Du das Ganze mit einem anderen Loop-Sample wie zum Beispiel :loop_garzul hören wollten? Wir müssten alle :loop_amen suchen und mit :loop_garzul ersetzen. Das mag in Ordnung sein, wenn Du viel Zeit hast - aber was, wenn Du gerade auf einer Bühne stehst? Manchmal hast Du nicht den Luxus, Zeit zu haben - vor allem dann nicht, wenn Du willst, dass die Leute weiter tanzen.

Was wäre, wenn Du den Code so geschrieben hättest:

sample_name = :loop_amen
sample sample_name
sleep sample_duration(sample_name)
sample sample_name, rate: 0.5
sleep sample_duration(sample_name, rate: 0.5)
sample sample_name
sleep sample_duration(sample_name)

Das tut genau dasselbe wie der Code weiter oben (probier es aus). Außerdem bekommen wir die Möglichkeit, durch die Änderung der einen Zeile sample_name = :loop_amen in sample_name = :loop_garzul die vielen anderen Stellen durch die Magie der Variablen zu verändern.

Ergebnisse von Dingen speichern

Wenn man schließlich die Ergebnisse von irgendwelchen Dingen speichern möchte, dann ist das ebenfalls ein guter Grund dafür, Variablen zu verwenden. Du möchtest vielleicht irgendetwas mit der Dauer eines Sample anstellen:

sd = sample_duration(:loop_amen)

Wir können nun sd überall da einsetzen, wo wir die Länge von :loop_amen brauchen.

Noch wichtiger vielleicht: Eine Variable erlaubt es uns, das Ergebnis eines Aufrufs von play oder sample zu speichern:

s = play 50, release: 8

Jetzt haben wir s als Variable eingefangen und gemerkt. Und das erlaubt es uns, einen Synth zu steuern, während er läuft:

s = play 50, release: 8
sleep 2
control s, note: 62

Das nur als kleine Vorschau, wie man Synths steuert. Das schauen wir uns später noch genauer an.

Warnung: Variablen und Threads (Prozessreihenfolge)

Während Variablen sehr gut dazu geeignet sind, Dingen Namen zu geben oder ein Ergebnis festzuhalten, so sollten sie aber typischerweise nur im lokalen Kontext eines Threads verwendet werden. Das Folgende sollte man daher nicht tun:

a = (ring 6, 5, 4, 3, 2, 1)
live_loop :sorted do
  a = a.sort
  sleep 0.5
  puts "sorted: ", a
end
live_loop :shuffled do
  a = a.shuffle
  sleep 0.5
end

In dem Beispiel oben weisen wir einem “Ring” aus Zahlen einer Variable ‘a’ zu und verwenden sie dann in zwei separaten live_loops. Innerhalb der ersten live-loop sortieren wir alle 5 Sekunden den Ring (to (ring 1, 2, 3, 4, 5, 6)) und geben in es dann im log aus. Wenn du nun den Code startest, wirst du bemerken, dass die ausgegebene Liste nicht immer sortiert ist!. Das könnte dich verwundern - insbesondere, da manchmal die Liste sortiert ist und manchmal eben nicht. Das bezeichnet man als nicht-deterministisches Verhalten (nicht vorhersagbar) und ist das Ergebnis eines eher üblen Problems, das man als ‘Race-Condition’ bezeichnet. Das Problem resultiert aus der Tatsache, dass die zweite live-loop parallel zur ersten live-loop die Liste verändert (sie mischt sie durch) und zum Zeitpunkt der Ausgabe die Liste manchmal sortiert und manchmal nicht sortiert ist. Da beide live-loops im Wettlauf miteinander sind, die Variable zu verändern, ‘gewinnt’ eben manchmal die eine live-loop oder manchmal die andere.

Hierzu gibt es zwei Lösungen: Verwende nie die gleiche Variable in verschiedenen live-loops oder threads. Beispielsweise gibt der folgende Code die Liste immer richtig sortiert aus, da jede live-loop ihre eigene Variable verwendet:

live_loop :shuffled do
  a = (ring 6, 5, 4, 3, 2, 1)
  a = a.shuffle
  sleep 0.5
end
live_loop :sorted do
  a = (ring 6, 5, 4, 3, 2, 1)
  a = a.sort
  sleep 0.5
  puts "sorted: ", a
end

Allerdings möchte man manchmal Werte zwischen verschiedene Threads teilen: zum Beispiel die aktuelle Tonart, das aktuelle oder den aktuellen Synth. In einem solchen Fall verwendet man in Sonic Pi spezielle System-Kommandos mit Hilfe der Funktionen get und `set, die wir später in Kapitel besprechen.