Wenn Du einmal damit angefangen hast, mehr Code zu schreiben, dann wirst Du nach Wegen suchen, wie Du die Dinge organisieren und strukturieren kannst. Damit wird alles ordentlicher und einfacher zu verstehen. Funktionen sind sehr praktisch, um das zu erreichen. Sie geben uns die Möglichkeit, einem Bündel von Codezeilen einen Namen zu geben. Sehen wir uns das an.
define :foo do
play 50
sleep 1
play 55
sleep 2
end
Hier haben wir eine neue Funktion mit dem Namen foo
definiert. Wir
machen das mit unserem alten Freund, dem do/end-Block und dem
Zauberwort define
gefolgt von dem Namen, den wir unserer Funktion
geben möchten. Wir müssen die Funktion nicht unbedingt foo
nennen,
wir können sie auch irgendwie anders nennen; zum Beispiel bar
, baz
oder idealerweise einen für Dich bedeutsamen Namen wie main_section
(etwa: Hauptteil) oder lead_riff
(etwa: Leitfigur).
Denk daran, bei der Definition einer Funktion einen Doppelpunkt :
vor ihren Namen zu stellen.
Wenn wir unsere Funktion definiert haben, können wir sie einfach über ihren Namen aufrufen:
define :foo do
play 50
sleep 1
play 55
sleep 0.5
end
foo
sleep 1
2.times do
foo
end
Wir können foo
sogar in Iterations-Blocks aufrufen und überall da, wo
wir auch play
oder sample
hätten benutzen können. Das gibt uns eine
große Ausdrucksfreiheit und wir können sinnvolle Worte bilden, die wir
in unseren Kompositionen verwenden.
Wenn Du bislang den Ausführen
-Button geklickt hast, startete Sonic Pi
jedesmal aufs Neue ohne irgendwelche Vorgaben. Es berücksichtigt nichts,
außer dem, was im jeweiligen Arbeitsfenster steht. Du kannst Dich nicht
auf irgendwelchen Code beziehen, der in einem anderen Arbeitsfenster
oder einem anderen Thread steht. Funktionen ändern das jedoch. Wenn Du
eine Funktion definierst, dann erinnert sich Sonic Pi daran. Probieren
wir das aus. Ersetze den den gesamten Code in Deinem Arbeitsfenster
durch:
foo
Klick den Ausführen
-Button und höre, wie Deine Funktion spielt. Wo
wurde dieser Code gespeichert? Woher weiss Sonic Pi, was es zu spielen
hat? Sonic Pi hat sich Deine Funktion einfach gemerkt. Also sogar,
nachdem Du den Code aus dem Arbeitsfenster gelöscht hast, wusste
Sonic Pi noch, was Du geschrieben hattest. Dies funktioniert nur mit
Funktionen, die Du mit define
(und defonce
) definiert hast.
Du kannst Deinen Funktionen beibringen, Argumente zu übernehmen,
genau so, wie Du z.B. rrand
einen Minimal- und einen Maximalwert
übergeben kannst. Sehen wir uns das an:
define :my_player do |n|
play n
end
my_player 80
sleep 0.5
my_player 90
Das ist jetzt nicht besonders aufregend, zeigt aber, worum es hier
geht. Wir haben unsere eigene Version von play
mit dem Namen
my_player
erschaffen. Diese ist parametrisiert - sie akzeptiert also
Argumente.
Die Parameter müssen nach dem do
stehen, welches zum define
des
do/end-Blocks gehört; sie werden von senkrechten Strichen (auch:
Verkettungszeichen) umgeben und durch Kommata getrennt. Du kannst
beliebige Worte als Parameternamen verwenden.
Die Zauberei findet innerhalb des define
-do/end-Blocks statt. Du
kannst die Parameternamen so benutzen, als wären sie wirkliche Werte.
In diesem Beispiel spiele ich den Ton n
. Du kannst die Parameter als
eine Art Versprechen ansehen, dass sie durch wirkliche Werte ersetzt
werden, wenn der Code läuft. Das machst Du, indem Du der Funktion
beim Aufruf einen Parameter mitgibst. Ich tue das hier mit
my_player 80
, um den Ton 80 zu spielen. Innerhalb der
Funktionsdefinition wird n
nun durch 80 ersetzt, sodass play n
sich
in play 80
verwandelt. Wenn ich die Funktion erneut mit my_player
90
aufrufe, wird n
durch 90 ersetzt, sodass sich play n
in play
90
verwandelt.
Sehen wir uns interessantere Beispiele an:
define :chord_player do |root, repeats|
repeats.times do
play chord(root, :minor), release: 0.3
sleep 0.5
end
end
chord_player :e3, 2
sleep 0.5
chord_player :a3, 3
chord_player :g3, 4
sleep 0.5
chord_player :e3, 3
Hier habe ich repeats
so benutzt, als ob es eine Zahl in der Zeile
repeats.times do
wäre. Zusätzlich habe ich roots
so verwendet, als
ob es ein Notenname im Aufruf play
wäre.
Siehst Du? Unser Code wird sehr aussagekräftig und leichter lesbar, wenn wir eine Menge der Programmlogik in Funktionen verschieben.