Variablen

Beim Programmieren ist es sehr nützlich, Namen für Dinge zu vergeben. Sonic Pi vereinfacht dies sehr: Du schreibst den Namen, den du 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 Variablen sample_name gemerkt. Wir können nun sample_name überall da verwenden, wo wir auch loop_amen verwenden würden. Zum Beispiel:

sample_name = :loop_amen
sample sample_name

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

Bedeutung vermitteln

Wenn du Programm-Code schreibst, ist es leicht zu denken, dass du dem nur Computer sagst, wie er etwas tun soll - solange der Computer das versteht, ist das in Ordnung. Vergiss aber nicht, dass nicht nur der Computer deinen Code lesen wird. Andere Leute könnten den Code auch lesen und versuchen zu verstehen, was da vor sich geht. Es ist möglich, dass du den Code später auch selbst noch einmal lesen wirst und verstehen willst, was er bedeutet. Obwohl dir jetzt alles noch ganz offensichtlich erscheint - wahrscheinlich ist es für andere nicht ganz so offensichtlich und vielleicht auch nicht für dich in der Zukunft!

Ein Weg um andere darin zu unterstützen deinen Programm-Code zu verstehen, ist das Schreiben von Kommentaren (wie wir im vorherigen Abschnitt gesehen haben). Ein andererr besteht darin 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

Nun, es ist viel klarer, was 1.7533 bedeutet: Es ist die Dauer des Samples :loop_amen! Natürlich könntest du jetzt sagen, warum nicht einfach schreiben:

sleep sample_duration(:loop_amen)

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

Wiederholungen steuern

Oft wirst du in deinem Programm-Code Wiederholungen begegnen, und wenn du eine Sache ändern willst, musst du das an vielen Stellen tun. Schau dir 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)

Wir machen hier eine ganze Menge mit dem :loop_amen! Was wäre, wenn wir es mit einem anderen Loop-Sample hören wollten, wie zum Beispiel :loop_garzul? 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 der Bühne stehst und performst? Manchmal hast du nicht den Luxus der Zeit - 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 (probiere es aus). Und es gibt uns auch die Möglichkeit, dass wir nur eine Zeile von sample_name = :loop_amen in sample_name = :loop_garzul ändern - und damit zugleich an vielen anderen Stellen durch die Magie der Variablen.

Ergebnisse speichern

Schließlich, eine gute Motivation Variablen zu verwenden ist die Ergebnisse von etwas festzuhalten. Z. B. möchtest du vielleicht irgendetwas mit der Dauer eines Samples anstellen:

sd = sample_duration(:loop_amen)

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

Vielleicht noch wichtiger, erlaubt uns eine Variable das Ergebnis eines Aufrufs von play oder sample zu speichern:

s = play 50, release: 8

Jetzt haben wir s als Variable festgehalten und gespeichert, das erlaubt es uns einen Synth zu steuern, während er läuft:

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

Wir werden uns die Steuerung von Synths in einem späteren Abschnitt noch detaillierter ansehen.

Warnung: Variablen und Threads

Während Variablen sehr gut dazu geeignet sind, Dingen Namen zu geben oder ein Ergebnis festzuhalten, ist es wichtig zu wissen, dass sie typischerweise nur im lokalen Kontext eines Threads verwendet werden sollten. Zum Beispiel, tu das hier nicht:

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 einen Ring aus Zahlen einer Variablen a zu und verwenden sie dann in zwei separaten live_loop. Innerhalb der ersten Schleife sortieren wir alle 0.5 Sekunden den Ring (zu (ring 1, 2, 3, 4, 5, 6)) und geben dies im Protokoll aus. Wenn du nun den Code ausführst, wirst du bemerken, dass die ausgegebene Liste nicht immer sortiert ist!. Das könnte dich überraschen - insbesondere, da manchmal die Liste sortiert ist und manchmal nicht. Dies wird als nicht-vorhersagbares Verhalten bezeichnet, und ist das Ergebnis eines eher üblen Problems, ‘Race-Condition’ genannt. Das Problem resultiert aus der Tatsache, dass die zweite Schleife ebenfalls die Liste verändert (in diesem Fall wird sie gemischt) und zum Zeitpunkt der Ausgabe die Liste manchmal sortiert und manchmal gemischt ist. Beide Schleifen befinden sich im Wettlauf miteinander die selbe Variable zu verändern, und es gewinnt manchmal die eine und manchmal die andere.

Hierzu gibt es zwei Lösungen: Zunächst, verwende nie die gleiche Variable in mehreren Live-Loops oder Threads. Beispielsweise gibt der folgende Code die Liste immer richtig sortiert aus, da jeder Live-Loop eine 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 wollen wir manchmal auch Werte zwischen verschiedene Threads teilen, zum Beispiel die aktuelle Tonart, BPM , den aktuellen Synth etc. In diesen Fällen ist die beste Lösung Sonic Pi spezielles Thread-Safe-State-System mit Hilfe der Funktionen get und set zu verwenden. Dies werden wir etwas später in Abschnitt 10 behandeln.