Startseite>Audio-Programmierung mit Sonic Pi, Teil 4
Aus Raspberry Pi Geek 06/2019

Audio-Programmierung mit Sonic Pi, Teil 4

© Teera Pittayanurak, 123RF

Verbindungsaufnahme

Pit Noack

Über die Funktion sync lassen Sie Sonic Pi auf MIDI- und OSC-Ereignisse sowie Audiosignale warten und diese verarbeiten.

Sonic Pi trat ursprünglich als Programmiersprache zum Live-Coding an, mit der sich aus Codezeilen Soundeffekte, Beats und ganze DJ-Sets bauen ließen. Mit der Version 3.0 kamen einige Fähigkeiten hinzu, die den Anwendungsbereich von Sonic Pi erheblich erweitern. Dazu gehören das Senden und Empfangen von MIDI- und Open-Sound-Control-Nachrichten, das Verarbeiten externer Audiosignale und nicht zuletzt das Resampling der erzeugten Klänge.

Um alle Beispiele aus diesem Artikel nachzuvollziehen, benötigen Sie neben dem Raspberry Pi noch einige externe Hardware: ein MIDI-Interface, einen MIDI-fähigen Klangerzeuger [1] sowie ein Audio-Interface mit Eingang. Abbildung 1 zeigt den verwendeten Aufbau und die für diesen Teil des Workshops eingesetzten Geräte.

Abbildung 1: So sieht das für die vorgestellten Tests verwendete Setup aus.

Abbildung 1: So sieht das für die vorgestellten Tests verwendete Setup aus.

Das Akronym MIDI steht für Musical Instrument Digital Interface, einen Standard zum Austauschen musikalischer Steuerinformationen zwischen Klangerzeugern und Controllern, zum Beispiel Synthesizern, Sequenzern, Keyboards und Fader-Boxen.

Einen wesentlichen Bestandteil der MIDI-Kommunikation bilden Noten- und Controller-Nachrichten: Eine Notennachricht repräsentiert etwa das Drücken oder Loslassen einer Keyboard-Taste und steuert musikalische Ereignisse. Eine Controller-Nachricht gibt Auskunft über den Zustand von Drehreglern und ähnlichen Kontrollelementen, sodass sich kontinuierliche musikalische Parameter wie Frequenzen oder Lautstärken steuern lassen.

Notennachrichten

Der Code aus Listing 1 zeigt, wie Sonic Pi Notennachrichten von einem Keyboard empfängt, um damit einen Synth zu spielen. In unserem Setup verbindet ein MIDI-Kabel das Keyboard mit dem Eingang des MIDI-Interfaces, das wiederum per USB am Raspberry Pi hängt. Verbundene MIDI-Geräte erkennt Sonic Pi automatisch und zeigt sie unter Preferences | IO an.

Listing 1

use_real_time
use_synth :piano
live_loop :midi_in do
  note, vel = sync "/midi/midisport_2x4_midi_1/*/*/note_on"
  play note if vel != 0
end

Entscheidend für die Verarbeitung der eingehenden Notennachrichten ist Zeile 5. Die Funktion sync – Sie kennen sie bereits aus dem dritten Teil dieser Serie [2] – hält den jeweiligen Thread an und wartet auf den Empfang einer Nachricht. Im konkreten Fall handelt es sich dabei um eine Notennachricht von einem MIDI-Port, den der Befehl mit dem String "/midi/midisport..." spezifiziert. Angeschlossene MIDI-Geräte bietet Sonic Pi hier per Autovervollständigung an.

Das erste Sternchen im String steht für die Nummer des Geräts (falls Sie mehrere gleichartige Controller verwenden), das zweite für den MIDI-Kanal, von denen 16 zur Verfügung stehen. Da wir uns in allen Beispielen mit einem Gerät und einem Kanal begnügen, genügen hier die als Wildcard fungierenden Sternchen.

Beim Empfang einer Notennachricht gibt sync ein Array mit zwei Ganzzahlen zwischen 0 und 127 zurück. Der erste Wert repräsentiert die Note, der zweite die Anschlagsstärke (englisch: velocity). Bei den Noten steht eine ganzzahlige Differenz für einen Halbtonschritt. Mit note, vel = ... entpacken Sie das Array und speichern die Werte in separaten Variablen (Zeile 5).

Zeile 6 übergibt den empfangenen Notenwert an die Funktion play, um die Note zu spielen. Die Bedingung if vel != 0 ist notwendig, weil das Loslassen der Keyboard-Taste eine Notennachricht mit der Anschlagsstärke 0 erzeugt. Ohne diese Abfrage würden Sie auch beim Zurückschnellen der Taste einen Ton hören.

Der Befehl use_real_time in Zeile 1 sorgt dafür, dass Sonic Pi mit einer möglichst niedrigen Audiolatenz arbeitet – andernfalls könnte man eine deutliche Verzögerung zwischen dem Tastendruck und dem Erklingen der Note wahrnehmen. Fügen Sie diesen Befehl also immer dann ein, wenn es auf eine verzögerungsfreie Ausführung ankommt. Das Erzielen einer niedrigen Latenz setzt zudem einen vorherigen manuellen Start des Audio-Servers Jack voraus (siehe Kasten “Den Audio-Server konfigurieren”). Wir haben für ihn eine Puffergröße von 512 Byte gewählt, was der internen Klangerzeugung von Sonic Pi entspricht.

Den Audio-Server konfigurieren

Raspbian enthält den Audio-Server Jack in der Variante Jack2. Das Programm lässt sich mit dem grafischen Tool Qjackctl einstellen und starten. Mehr Kontrolle über Jack bekommen Sie allerdings, indem Sie jackd in einem Terminal aufrufen und erst danach den Startknopf in Qjackctl drücken. Qjackctl erkennt dann automatisch, dass der Server bereits läuft, und zeigt für den laufenden Jack die Statusmeldungen und Verbindungsports an.

Die Kommandozeilenoptionen von Jack fallen reichlich komplex aus. Dabei sorgt die Tatsache, dass einige der Optionen für verschiedene Audiotreiber unterschiedliche Bedeutung haben, für zusätzliche Verwirrung. Letztlich zählt aber unter Linux nur der Umgang mit dem Alsa-Backend. Als ersten Schritt der Einrichtung werfen Sie einen Blick in die aktuell von Alsa erzeugte Liste der Audioschnittstellen. Dazu verwenden Sie folgendes Kommando:

$ cat /proc/asound/cards

Alsa nummeriert die verfügbaren Soundkarten, allerdings nicht immer in zuverlässiger Reihenfolge. Hier bleiben Sie auf der sicheren Seite, indem Sie das fragliche Gerät statt über die Nummer über seinen Alias (in der zweiten Spalte der Datei cards) ansprechen. Für das im Artikel verwendete Setup mit dem Behringer UMC22 lautet der entsprechende Name seltsamerweise CODEC. Im Setup hat sich der folgende Befehl für den Start des Audio-Servers bewährt und liefert ein sehr stabiles Laufzeitverhalten:

$ jackd -p32 -t2000 -dalsa -dhw:CODEC -r48000 -p512 -n2 -Xseq -s

Mit der Option -p32 erzeugt Jackd nur maximal 32 statt der standardmäßig bis zu 256 Ports. Durch -t2000 wartet Jackd 2 Sekunden statt nur eine halbe Sekunde auf Rückmeldungen von Anwendungen via Alsa. Der Parameter -dalsa wählt noch einmal ausdrücklich das Alsa-Backend aus, anschließend fordert -dhw:CODEC den Zugriff auf das USB-Gerät. Jackd interpretiert die erste Angabe via -d also als Angabe des Backend-Drivers, erst die zweite steht dann für “device” und wählt das Gerät aus.

Die danach folgenden Optionen -r, -p und -n bestimmen die Leistungskriterien, die Jack von Alsa erwartet: die Sampling-Rate sowie die Größe und Anzahl der Puffer. Kleinere Werte ermöglichen eine niedrigere Verzögerung, größere gewährleisten mehr Stabilität – je nach Anwendung müssen Sie hier etwas experimentieren. Die Werte im Beispiel funktionieren auf dem RasPi 3 mit einem einfachen USB-Interface der unteren Mittelklasse solide. Die resultierende Verzögerung von etwa 10 Millisekunden können nur Musiker mit einem geschulten Gehör noch wahrnehmen.

Die Option -Xseq schaltet den in Jack eingebauten MIDI-Server ein, der sich parallel zum Alsa-MIDI-Server nutzen lässt. Mit dem Kommandozeilenprogramm A2jmidi könnten Sie darüber hinaus bei Bedarf noch virtuelle Ports anlegen, die beide Systeme miteinander verbinden. Das kleine -s am Ende des Ausfrufs fügt noch etwas mehr Toleranz gegen Aussetzer im Audio-Stream des Backends hinzu.

Es gibt noch gut ein Dutzend weitere für Jackd relevante Optionen. Im Alltagseinsatz zeigt sich allerdings immer wieder, dass die hier genannten die wichtigsten sind. (Hartmut Noack/jlu)

Controller-Nachrichten empfangen

Das im Test verwendete Keyboard (Abbildung 2) verfügt über 12 Drehregler. Auch die von diesen erzeugten Controller-Nachrichten können Sie per sync empfangen und im Protokollfenster ausgeben, wie Listing 2 zeigt. Der im Argument übergebene String endet auf control_change. Als Rückgabe erhalten Sie wieder ein Array mit zwei Ganzzahlen. Die erste nennt die Nummer des jeweiligen Reglers, die zweite dessen Wert, der zwischen 0 und 127 liegt.

Abbildung 2: Das im Beispielaufbau verwendete Keyboard stellt nicht weniger als 12 Drehregler bereit.

Abbildung 2: Das im Beispielaufbau verwendete Keyboard stellt nicht weniger als 12 Drehregler bereit.

Listing 2

live_loop :ctl_monitor do
  ctl, val = sync ".../*/*/control_change"
  puts "#{ctl}, #{val}"
end

In vielen Situationen ist es besser, Controller-Nachrichten mit der Funktion get abzufragen. Anders als sync wartet get nicht auf ein Ereignis, sondern liefert die jeweils zuletzt empfangene Nachricht. Listing 3 zeigt ein entsprechendes Anwendungsbeispiel: Der erste Drehregler steuert die Lautstärke der Bassdrum (Zeile 6), der zweite die Lautstärke der Hi-Hat (Zeile 7).

DIESEN ARTIKEL ALS PDF KAUFEN
EXPRESS-KAUF ALS PDFUmfang: 8 HeftseitenPreis €0,99
(inkl. 19% MwSt.)
€0,99 – Kaufen
RASPBERRY PI GEEK KAUFEN
EINZELNE AUSGABE Print-Ausgaben Digitale Ausgaben
ABONNEMENTS Print-Abos Digitales Abo
TABLET & SMARTPHONE APPS
Deutschland