Funktionen

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 dafür sehr praktisch. Sie geben uns die Möglichkeit, einem Bündel von Codezeilen einen Namen zu geben. Sehen wir uns das an.

Funktionen definieren

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 erste_strophe oder hintergrund_akkorde.

Denk daran, bei der Definition einer Funktion einen Doppelpunkt : vor ihren Namen zu stellen.

Funktionen aufrufen

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.

Funktionen bleiben in Erinnerung

Wenn Du bislang den Ausführen-Button geklickt hast, startete Sonic Pi jedes mal aufs Neue ohne irgendwelche Vorgaben. Es berücksichtigt nichts, außer dem, was im jeweiligen Puffer steht. Du kannst Dich nicht auf irgendwelchen Code beziehen, der in einem anderen Puffer 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 gesamten Code in Deinem Puffer durch:

foo

Klick den Ausführen-Button und höre, wie Deine Funktion spielt. Wo wurde dieser Code gespeichert? Woher weiß Sonic Pi, was es zu spielen hat? Sonic Pi hat sich Deine Funktion einfach gemerkt. Also sogar, nachdem Du den Code aus dem Puffer gelöscht hast, wusste Sonic Pi noch, was Du geschrieben hattest. Dies funktioniert nur mit Funktionen, die Du mit define (und defonce) definiert hast.

Funktionen parametrisieren

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 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!