Set und Get

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 …

Set

Um Informationen in den Time-State zu speichern benötigen wir zwei Dinge:

  1. die Information, die wir speichern wollen,
  2. einen eindeutigen Namen (Schlüssel) für die Information.

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.

Get

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.

Mehrere Threads

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).

Ein einfaches deterministisches State-System

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.