Funktionen

Wenn du einmal damit angefangen hast größere Mengen Programm-Code zu schreiben, dann wirst du nach Wegen suchen, wie du die Dinge organisieren und strukturieren kannst, um sie sauberer und verständlicher zu machen. Funktionen sind ein sehr wirkungsvoller Weg das zu tun. Sie geben uns die Möglichkeit, einem Haufen Programm-Code 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 haupt_sektion oder hintergrund_akkorde.

Denke daran, bei der Definition einer Funktion ihrem Namen einen Doppelpunkt : voranzustellen.

Funktionen aufrufen

Wenn wir unsere Funktion definiert haben, können wir sie über die Eingabe ihres Namens 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 Blocks mit Iterationen verwenden - oder überall da, wo wir sonst auch play oder sample schreiben würden. Das gibt uns eine sehr weit gehende Möglichkeit uns auszudrücken, und können sinnvolle Worte einsetzen, um sie in unseren Kompositionen zu verwenden.

Funktionen bleiben in Erinnerung

Wenn du bisher auf Ausführen geklickt hast, ist Sonic Pi jedes Mal aufs Neue ohne irgendwelche Vorgaben gestartet. Es berücksichtigt dabei nichts, außer dem, was im jeweiligen Puffer steht. Du kannst dich nicht auf irgendwelchen Programm-Code beziehen, der in einem anderen Puffer oder in einem anderen Thread steht. Funktionen ändern das jedoch. Wenn du eine Funktion definierst, dann erinnert sich Sonic Pi daran. Probieren wir das aus. Lösche den gesamten Code in deinem Puffer und ersetze ihn durch:

foo

Klicke auf Ausführen - und höre deine Funktion spielen. Wo wurde dieser Code gespeichert? Woher wusste Sonic Pi, was es zu spielen hat? Sonic Pi hat sich deine Funktion einfach gemerkt - sogar, nachdem du den Programm-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) erzeugt hast.

Funktionen parametrisieren

Es wird dich vielleicht interessieren, dass so wie du rrand einen Minimal- und Maximalwert übergeben kannst, du auch deinen Funktionen beibringen kannst Argumente zu akzeptieren. Sehen wir uns das an:

define :my_player do |n|
  play n
end
my_player 80
sleep 0.5
my_player 90

Das ist 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 hinter dem do des define-do/end-Blocks stehen, umgeben von senkrechten Strichen (pipes) |und durch Kommas , getrennt. Du kannst beliebige Wörter als Parameternamen verwenden.

Die Magie passiert innerhalb des define-do/end-Blocks. 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 wenn der Programm-Code läuft, sie durch wirkliche Werte ersetzt werden. Du machst das, indem du der Funktion beim Aufruf einen Parameter mitgibst. Ich tue das hier mit my_player 80, um die Note 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 jetzt play n in play 90 verwandelt.

Sehen wir uns ein interessanteres Beispiel 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 in meinem Aufruf von play wäre.

Sieh dir an, wie wir hier etwas sehr Ausdrucksstarkes und leicht zu Lesendes schreiben konnten, indem wir vieles von unserer Programmlogik in Funktionen verschieben!