Set und Get

Sonic Pi hat einen global Speicherbereich, der “Time State” genannt wird. Die beiden wesentliche Dinge, die man auf ihn an anwendet sind das Setzen von Informationen (Set) und das Holen von Information (Get).

Set

Um Informationen innerhalb des Time States zu speichern, benötigen wir zwei Dinge:

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

Wir möchten beispielsweise die Zahl 3000 unter dem Schlüsselnamen :intensity speichern. Dies können wir mit der set function erreichen:

set :intensity, 3000

Für den Schlüssel können wir jeden Namen verwenden, den wir möchten. Haben wir einmal einen Wert unter diesem Schlüsselnamen gespeichert, wird ein neuer set -Befehl diesen überschreiben:

set :intensity, 1000
set :intensity, 3000

In dem obigen Beispiel haben wir beide Werte unter dem gleichen Schlüssel abgespeichert. Der letzte Befehl ‘gewinnt’, so dass die Zahl, die mit :intensity assoziert ist, die 3000 sein wird und somit die erste (1000) damit überschrieben wurde.

Get (Abfragen des Werts)

Um Informationen aus dem Time-State wieder zu holen, benötigen wir nun unseren Schlüssel, den wir bei set verwendet haben, also in diesem Fall :intensity. We müssen jetzt einfach get[:intensity] aufrufen und bekommen dies im log angezeigt:

print get[:intensity]  #=> prints 3000

Achte darauf, dass Aufrufe von get Informationen zurückgeben kann, die bei einem vorherigem Programm-Lauf gesetzt wurden. Wurde eine Information einmal mit Hilfe von set gespeichert, ist sie solange verfügbar bis sie entweder überschrieben wird (wie oben die 1000 mit 3000) oder Sonic Pi beendet wird.

Mehrere Threads

Der wesentliche Vorteil des Time State Systems ist, dass man auf sichere Art und Weise Informationen zwischen Threads und live-loops austauschen kann. Beispielsweise könnte eine live-loop Informationen bereitstellen, die die andere live-loop ausliest:

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 get und set bei Threads ist, dass sie immer das gleiche Ergebnis produzieren, wenn das Programm erneut gestartet wird. Versuch’s einfach mal selbst und prüfe nach, ob die folgende Ausgabe im Log erscheint:

{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 mal mehrere Male - du wirst sehen, dass das Ergebnis immer das Gleiche ist. Das bezeichnen wir als “deterministisches” Verhalten und das ist sehr wichtig, wenn wir unsere Musik an andere weitergeben möchten und sicher sein wollen, dass die Person, die die Musik anhört, genau das gleiche Ergebnis hören wird wie wir, die es erstellt haben (ähnlich wie das Spielen einer MP3 oder das Streamen von Musik).

Ein einfaches deterministisches State System

In Abschnitt 5.6 haben wir gezeigt, wie die Nutzung von Variablen in verschiedenen Threads zu zufälligen Resultaten führen kann. Das verhindert wiederum die sichere Reproduktion von bestimmten Code wie diesem:

## Ein Beispiel für ein nicht-deterministisches Verhalten
## (aufgrund von Race Conditions, die durch
## mehrfache live-loops ausgelöst werden, die auf 
## gleiche Variable zugreifen).
##  
## Wenn Du den Code startest, siehst Du,
## dass die Liste 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

Schauen wir mal, wie wir get und set einsetzen können:

## Ein Beispiel für ein deterministisches Verhalten
## (trotz des gleichzeitigen Zugriff auf einen gemeinsamen Zustand) 
## mit Hilfe des Sonic Pi Time State Systems
##
## Wenn dieser Code gestartet wird,
## 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 zu dem Code ist, der die Variable zum Teilen der Information verwendet hat. Allerdings verhält er sich so, wie man es sich bei einem typischen Sonic Pi Code erwartet - er verhält sich immer gleich in diesem Fall dank des Time State Systems.

Deshalb verwende immer get und set, wenn Du Daten zwischen live-loops und Threads teilen möchtest anstatt Variablen zu verwenden.