Sonic Pi verfügt über einen globalen Speicherbereich, der Time-State genannt wird. Die beiden wesentliche damit möglichen Dinge sind das Setzen von Informationen (Set) und das Holen von Information (Get). Lass uns das genauer ansehen …
Um Informationen in den Time-State zu speichern benötigen wir zwei Dinge:
Zum Beispiel wollen wir vielleicht die Zahl 3000
unter dem Schlüsselnamen :intensity
speichern. Das ist möglich mit der set
-Funktion:
set :intensity, 3000
Wir können für den Schlüssel einen beliebigen Namen verwenden. Wurde unter diesem Schlüsselnamen bereits Information gespeichert, wird unser neuer set
-Befehl diese überschreiben:
set :intensity, 1000
set :intensity, 3000
In dem obigen Beispiel haben wir beide Werte unter dem gleichen Schlüsselnamen abgespeichert, und der letzte set
-Befehl ‘gewinnt’, so dass die Zahl 3000 mit :intensity
assoziiert wird, also unser erster set
-Befehl damit überschrieben wurde.
Um Informationen aus dem Time-State wiederzuholen, benötigen wir nur den Schlüssel, den wir bei set
verwendet haben, also in unserem Fall :intensity
. Wir müssen jetzt nur get[:intensity]
aufrufen, und das Ergebnis wird uns im Protokoll angezeigt:
print get[:intensity] #=> prints 3000
Beachte, dass Aufrufe von get
Informationen zurückgeben können, die bei einem vorherigem Programm-Lauf gesetzt wurden. Wurde eine Information einmal mit Hilfe von set
gesetzt, bleibt sie solange verfügbar, bis sie entweder überschrieben wird (so wie wir den Wert 1000
von intensity
mit 3000
vertauscht haben) oder Sonic Pi beendet wird.
Der wesentliche Vorteil des Time-State-Systems ist, dass es auf sichere Art und Weise zwischen Threads und zwischen Live-Loops verwendet werden kann. Beispielsweise könnte ein Live-Loop Informationen setzen und ein anderer diese auslesen:
live_loop :setter do
set :foo, rrand(70, 130)
sleep 1
end
live_loop :getter do
puts get[:foo]
sleep 0.5
end
Das Schöne an der Verwendung von get
und set
über Threads hinweg ist, dass immer das gleiche Ergebnis ausgegeben wird, wenn der Programm-Code gestartet wird. Los, probiere es selbst. Sieh nach, ob dir das Folgende im Protokoll ausgegeben wird:
{run: 0, time: 0.0}
└─ 125.72265625
{run: 0, time: 0.5}
└─ 125.72265625
{run: 0, time: 1.0}
└─ 76.26220703125
{run: 0, time: 1.5}
└─ 76.26220703125
{run: 0, time: 2.0}
└─ 114.93408203125
{run: 0, time: 2.5}
└─ 114.93408203125
{run: 0, time: 3.0}
└─ 75.6048583984375
{run: 0, time: 3.5}
└─ 75.6048583984375
Starte das Programm einfach mehrmals hintereinander - du siehst, dass das Ergebnis jedes Mal das Gleiche ist. Wir bezeichnen dies als deterministisches (vorhersagbares) Verhalten, und es ist sehr wichtig, wenn wir unsere Musik als Programm-Code weitergeben, zu wissen, dass die Person, die den Code ausführt, genau das hören wird, was wir sie hören lassen wollten (ähnlich wie beim Abspiel einer MP3 oder beim Streamen für alle Hörer dasselbe zu hören ist).
In Abschnitt 5.6 haben wir darüber gesprochen, wie die Nutzung von Variablen über verschiedenen Threads hinweg zu zufälligen Resultaten führen kann. Das hindert uns daran Programm-Code wie diesen zuverlässig zu reproduzieren:
## Ein Beispiel für ein nicht-deterministisches Verhalten
## (aufgrund von Race Conditions, die durch
## mehrfache Live-Loops ausgelöst werden, die den Wert
## der selben Variable verändern).
##
## Wenn du den Code startest, siehst du,
## dass die Liste, die ausgegeben wird,
## nicht immer sortiert ist!
a = (ring 6, 5, 4, 3, 2, 1)
live_loop :shuffled do
a = a.shuffle
sleep 0.5
end
live_loop :sorted do
a = a.sort
sleep 0.5
puts "sorted: ", a
end
Lass uns sehen, wie wir hier get
und set
einsetzen könnten:
## Ein Beispiel für deterministisches Verhalten
## (trotz gleichzeitigem Zugriff auf einen geteilten Zustand)
## mit Hilfe des Sonic Pi Time State Systems.
##
## Bei Ausführung dieses Codes,
## wird die Liste immer sortiert ausgegeben!
set :a, (ring 6, 5, 4, 3, 2, 1)
live_loop :shuffled do
set :a, get[:a].shuffle
sleep 0.5
end
live_loop :sorted do
set :a, get[:a].sort
sleep 0.5
puts "sorted: ", get[:a]
end
Beachte, dass dieser Code fast identisch ist mit dem Code, der eine Variable zum Teilen der Information verwendet hat. Wenn du ihn ausführst verhält er sich allerdings so, wie du es bei jedem typischen Sonic Pi Code erwarten würdest - er verhält sich immer gleich, in diesem Fall dank des Time-State-Systems.
Deshalb verwende für ein vorhersagbares und reproduzierbares Verhalten anstelle von Variablen immer get
und set
, wenn du Daten zwischen Live-Loops und zwischen Threads teilst.