Sonic Pi: Arrays und Ringe

Fast alle Programmiersprachen stellen sogenannte Arrays (zu deutsch: Felder oder Feldvariablen) zur Verfügung. Sie sind nützlich, wenn ganze Folgen von Werten verarbeitet werden sollen.

Angenommen, man möchte mit einem Ruby-Programm einen Tag lang jede Stunde die Temperatur messen, um anschließend die Durchschnittstemperatur zu berechnen. Auskunft über die Temperatur liefert in den folgenden beiden Beispielen die fiktive Funktion aktuelle_temperatur. (Wenn Sie den Code testen wollen, schreiben Sie einfach aktuelle_temperatur = 10 vorneweg, und ersetzen Sie sleep 3600 durch sleep 1, sonst dauert die Ausführung des Programms 24 Stunden!) Ohne die Verwendung eines Arrays würde das Programm so aussehen:

temp_0 = aktuelle_temperatur
sleep 3600
temp_1 = aktuelle_temperatur
sleep 3600
temp_2 = aktuelle_temperatur
sleep 3600
temp_3 = aktuelle_temperatur
sleep 3600
temp_4 = aktuelle_temperatur
sleep 3600
# hier haben wir 24 Zeilen ausgelassen....
temp_22 = aktuelle_temperatur
sleep 3600
temp_23 = aktuelle_temperatur

summe_temperaturen = temp_0 + temp_1 + temp_2 + temp_3 + temp_4  # + 19 weitere Variablen...

durchschnitt = summe_temperaturen / 24

puts "Durchschnittstemperatur: #{durchschnitt}"

Das ist ein ziemlich umständliches Vorgehen! Jedes Messergebnis braucht eine eigene Variable, und eine Umstellung des Programms auf eine Messung alle 10 Minuten oder auf einen größeren Zeitraum (eine Woche, ein Jahr!) würde noch sehr viel mehr Variablen und sehr viel Schreibarbeit erfordern.

Viel einfacher und übersichlicher geht es mit Arrays:

messungen = []

24.times do
  messungen << aktuelle_temperatur
  sleep 3600
end

summe_temperaturen = 0

messungen.each do |temperatur|
  summe_temperaturen += temperatur
end

durchschnitt = summe_temperaturen / 24

puts "Durchschnittstemperatur: #{durchschnitt}"

In Zeile 1 wird mit messungen = [] ein leeres Array mit dem Variablennamen messungen angelegt. In Zeile 3-6 wird mittels der Methode times vierundzwanzig mal eine Messung durchgeführt, das Ergebnis dem Array Messungen mit dem Operator << hinzugefügt, anschließend jeweils 3600 Sekunden gewartet. Nach der Ausführung dieses Codeabschnitts (und einem Tag Wartezeit!) enthält das Array messungen 24 Werte.

In Zeile 9 wird die Variable zur Speicherung der Summe aller Temperaturen definiert und auf den Wert 0 gesetzt. In den Zeilen 10-12 wird diese Summe berechnet. messungen.each sorgt dafür, dass der Code zwischen do und end (ein sogenannter Block) für jedes Element von messungen einmal durchgeführt wird. Das jeweilige Element des Arrays wird mit |temperatur| an den Block übergeben. (Anmerkung 1: summe_temperaturen += temperatur ist übrigens eine verkürzte Schreibweise für summe_temperaturen = summe_temperaturen + temperatur) (Anmerkung 2: Eleganter bildet man in Ruby Summen mit inject, aber so ist es für Einsteiger erst einmal leichter verständlich)

Zeile 14 berechnet dann schließlich die Durchschnittstemperatur, indem die Summe durch die Anzahl der Messungen geteilt wird. Damit man den Code an dieser Stelle nicht ändern muß, wenn die Anzahl der Messungen sich ändert, wird hier nicht durch 24 geteilt, sondern durch die Anzahl der Werte, die das Array enthält. Diesen Wert ermittelt man mit messungen.size.

Arrays in Sonic Pi

Ein häufiger Anwendungsfall für Arrays in Sonic Pi ist die Verwendung in Kombination mit choose. Im folgenden Beispiel wird aus dem Array [:bd_haus, :drum_snare_hard, :drum_cymbal_closed] bei jedem Schleifendurchgang eines der drei Samples zufällig ausgewählt und abgespielt:

live_loop :chooser do
  sample [:bd_haus, :drum_snare_hard, :drum_cymbal_closed].choose
  sleep 0.25
end

Ringe

Auch für Abfolgen Notenwerten, Pausen oder Effektparametern lassen sich Arrays verwenden. Sehr viel geeigneter sind aber Ringe. Ringe sind im Prinzip auch Arrays, sie haben aber einen entscheidenden Vorteil: Sie verhalten sich so, als seien sie endlos lang, sie sind (Überraschung!) ringförmig. Im folgenden Beispiel wird eine Tonfolge in einem Array gespeichert. tonfolge[0] liefert den ersten Wert des Arrays, tonfolge[1] den zweiten, tonfolge[2] den dritten. tonfolge[3] allerdings gibt nil, d.h. „Nichts“ zurück, dementsprechend spielt play tonfolge[3] auch keinen Ton.

tonfolge = [:c4, :d4, :e4]

play tonfolge[0]
sleep 1
play tonfolge[1]
sleep 1
play tonfolge[2]
sleep 1
play tonfolge[3]

Wenn man die Tonfolge mehrmals (im Prinzip endlos) wiederholen will, verwendet man in Sonic Pi besser einen Ring:

tonfolge = ring :c4, :d4, :e4

play tonfolge[0]
sleep 1
play tonfolge[1]
sleep 1
play tonfolge[2]
sleep 1
play tonfolge[3]
sleep 1
play tonfolge[4]
# und so weiter

In diesem Fall spielt Sonic Pi mit play tonfolge[3] wieder den ersten Wert des Rings ab, mit play tonfolge[4] den zweiten, und so weiter…

Folgender Code spielt die Tonfolge in Endlosschleife:

tonfolge = ring :c4, :d4, :e4

live_loop :ringring do
  play tonfolge[tick]
  sleep 1
end

Die Funktion tick arbeitet wie ein Zähler, die jedesmal – beginnend bei 0 – den aktuellen Wert ausgibt und ihn um 1 erhöht.

Es gibt zwei weitere Artikel zum Thema Ringe auf diesem Blog:
Ringe erzeugen Teil 1
Ringe erzeugen Teil 2

Hat Ihnen dieser Artikel gefallen? Haben Sie einen Fehler gefunden? Ist etwas unklar? Wir freuen uns über Rückmeldungen, Fragen und Anregungen im Kommentarbereich!