Sound in ProcessingEinführung in den Umgang mit Audio-Daten in Processing
Fokus der Lesson ist der praktische Einstieg in die Arbeit mit Sound in Processing unter Verwendung der Minim-Bibliothek. Dabei steht das Abspielen und die einfache Analyse von Pegel und Frequenzen aus Audio-Dateien und Mikrofon-Signalen im Mittelpunkt.
Die minim Bibliothek
- Minim(this) Eine Instanz der Minim Klasse dient als Schnittstelle zur Soundkarte. Nach dem Importieren kann diese mit new erzeugt werden. Dabei muss jeder Minim-Instanz eine Referenz auf den Processing-Sketch in Form eines Parameters übergeben werden.
Minim m = new Minim (this);
- loadFile() läd ein Audiodokument und gibt dieses als Datentyp AudioPlayer zurück.
AudioPlayer player = m.loadFile ("myTrack.wav"); - loadSample() läd ein Audiodokument und gibt dieses als Datentyp AudioSample zurück. Ein AudioSample ist eine primitivere Form eines AudioPlayers, mit Fokus auf loopende Abspielweise.
AudioSample sample = m.loadSample ("mySample.wav"); - loadSnippet() läd ein Audiodokument und gibt dieses als Datentyp AudioSnippet zurück. Bei diesem Datentyp handelt es sich um die anspruchloseste Form Audiodaten innerhalb von Processing zu speichern. Vor allem spiegelt sich dies im geringem Funktionsumfang wieder.
AudioSnippet snippet = m.loadSnippet ("mySnippet.wav");
AudioPlayer
Drei Objekte bietet minim für das Laden/Speichern von Audiodateien. Der AudioPlayer bietet im Vergleich mit dem AudioSample und dem AudioSnippet den größten Funktionsumfang. Als Quelle können alle drei mit den Formaten mp3, ogg, wav und aif arbeiten. Wie beim Laden von Bildern und Schriften muss sich die zu ladende Datei im /data Ordner des Sketches befinden. Ebenfalls sollte der Aufruf der minim-Funktionen zum Auslesen der Audiodatei innerhalb des setup()-Block statt finden.- cue(int millis) verschiebt die Abspielposition zu einem bestimmten Punkt, gemessen in Millisekunden vom Anfang der Audiodatei. Genaue Informationen befinden sich im JavaDoc.
player.cue (0); // An den Anfang player.cue (player.length () / 2); // In die Mitte
- getMetaData () erlaubt den Zugriff auf ID3 Tag-Informationen, welche im Audiodokument abgelegt sind (z.B. in MP3s oder Dateien vom OGG-Format).
println (player.getMetaData ().title()); println (player.getMataData ().author ());
- isLooping() gibt true zurück wenn die Audiodatei in einer Schleife abgespielt wird.
- isPlaying() gibt true zurück wenn die Audiodatei momentan abgespielt wird.
- length() liefert die Länge der Audiodatei in Millisekunden zurück.
- loop() veranlasst das Abspielen der Audiodatei in einer Schleife. Wenn kein Parameter übergeben wird, läuft diese Schleife endlos. Optional kann die Anzahl der Wiederholungen durch einen Wert vom Typ int angegeben werden.
player.loop (); // Endlosschleife player.loop (7); // Spielt die Datei 7mal in Folge ab
- loopCount() liefert die Anzahl der verbleibenden Schleifendurchläufe.
- pause() pausiert den Abspielvorgang.
- play() startet den Abspielvorgang an der aktuellen Position.
- play(int millis) startet den Abspielvorgang an einer gegebenen Position.
- position() gibt die aktuelle Abspielposition in Millisekunden zurück.
// Einbeziehen des minim-Audio-Toolkits
import ddf.minim.*;
// Instanz der minim Bibliothek
Minim minim;
// Instanz die das geladene Audiodokument repräsentiert
AudioPlayer player;
void setup () {
// Audiotoolkit erstellen
minim = new Minim (this);
// Audiodatei aus dem data-Ordner laden
player = minim.loadFile ("track.aif");
// Audiodatei abspielen
player.play ();
}
void draw () {
// Mögliches Auswerden des aktuellen Spektrums
// bzw. von anderen Audioeigenschaften.
}Beispiele
Alle folgenden Processing Sketche sollen einen kurzen und flächendeckenden Einstieg in das Arbeiten mit der minim Library aufzeigen. Vom simplen Laden bis zur Auswertung von Frequenzbereichen ist jeder erdenklicher Umgang mit Audioinformationen möglich - jedoch, wie im letzten Beispiel, mit vermehrter Schreibarbeit verbunden. minim ist eine der best-dokumentiertesten Werkzeuge für Processing. Neben einer umfangreichen Beschreibung werden außerdem Beispiele online angeboten.Abspielen eines AudioPlayers in Processing
Zum Einstieg zeigt dieses Beispiel das rudimentären Laden und Abspielen einer Sounddatei. In diesem Fall greifen wir über die minim Instanz auf die Datei track.aif zu (sie muss sich im 'data'-Ordner befinden). Nach dem Laden im setup() startet der Aufruf von play() das Abspielen. Innerhalb des draw()s wird die aktuelle Position im Stück ermittelt und durch einen Kreis im Sketchfenster dargestellt. Die Breite der Zeichenfläche entspricht dabei der Tracklänge.
import ddf.minim.*;
Minim minim;
AudioPlayer player;
void setup () {
// Sketcheinstellungen
size (320, 170);
smooth ();
stroke (100);
// Audiotoolkit erzeugen
minim = new Minim (this);
// Audiodatei in das Object 'player' laden
player = minim.loadFile ("track.aif");
// Audiodatei abspielen
player.play ();
}
void draw () {
background (0);
// Relative Abspielposition im Stück ermitteln.
// Dieser Wert bewegt sich zwischen 0 und 1.
float playPos = player.position ();
float playLen = player.length ();
float xpos = (playPos / playLen) * width;
// Zeichnen der Ellipse als
ellipse (xpos, height / 2, 20, 20);
}void mousePressed () {
// Relative Position der Maus auf der x-Achse, bezogen
// auf die Breite des Fensters (ebenfalls zw. 0 und 1).
// Multipliziert mit der Länge der Audiodatei.
float pos = ((float) mouseX / width) * player.length ();
// Setzt neue Abspielposition.
player.cue ((int) pos);
}
void keyPressed () {
// Pausieren und Abspielen auf Tastendruck
if (player.isPlaying ()) {
player.pause ();
}else{
player.play ();
}
}Lautstärke ermittlen in Processing
Neben der Position im Audiodokument und dessen Steuerung, erlaubt die minim Bibliothek das Auslesen von Eigenschaften. Eine bezeichnendes Kriterium ist die wiedergegebene Lautsträke zu einem bestimmten Zeitpunkt. Während des Abspielens durch den play() Aufruf, kann im draw() das sogenannte 'level' für jedes Einzelbild erfragt werden. Die mix.level()-Funktion beschreibt die Auslastung dieses Merkmals in einem Wertebereich von 0 und 1.
Das Objekt mix repräsentiert das Sterosignal, welches sich aus dem linken (left) und dem rechten (right) Kanal zusammen setzt.
import ddf.minim.*;
Minim minim;
AudioPlayer player;
void setup () {
// Sketch einrichten
size (320, 240);
noStroke ();
// Audiodatei laden und abspielen
minim = new Minim (this);
player = minim.loadFile ("track.aif");
player.play ();
}
void draw () {
// Schwarzes semitransparentes Recht
// über die gesamte Bühne zeichnen
fill (0,30);
rectMode (CORNER);
rect (0, 0, width, height);
// Größe des Quadrats in Abhänigkeit von
// der Lautstärke berechnen
float dimension = player.mix.level () * 250;
// Rechteck zeichnen
fill (255);
rectMode (CENTER);
rect (width/2, height/2, dimension, dimension);
}Mikrofoneingang nutzen in Processing
Angewendet auf ein Mikrofon-Input visualisiert der folgende Sketch die chronologische Entwicklung der Umgebungsgeräusche. Je lauter, desto größer und separierter wird ein Kreis auf der Zeichenfläche abgebildet. Dabei verschiebt sich die Position auf der x-Achse von links nach rechts, bis es zu einem Umbruch in die nächste Zeile kommt. Mittels der globalen Variablen x und y wird die aktuelle Position gespeichert und durch die if-Bedingung am Ende des draw()-Blocks kontrolliert.
import ddf.minim.*;
float x;
float y;
Minim minim;
AudioInput input;
void setup () {
// Sketch einstellen
size (320, 240);
smooth();
stroke (255, 25);
noFill ();
// Startposition festlegen
x = 0;
y = 20;
// Audiotoolkit anlegen
minim = new Minim (this);
input = minim.getLineIn (Minim.STEREO, 512);
background (0);
}
void draw () {
// Kreisgröße Abhängig von Lautstärke
float dim = input.mix.level () * width;
// Kreis x-Position verschieben
x += input.mix.level () * 20;
// Kreis zeichnen
ellipse (x, y, dim, dim);
if (x > width) {
x = 0;
y += 20;
}
}Simples Spektrum (Wellenform) in Processing
Eine der bekanntesten Darstellungen von Ton und Klang ist das klassische Spekturm. In minim als AudioBuffer bezeichnet, beinhaltet es ein float Array mit einem Wert pro Kanal. Die Anzal der Kanäle wird bei dem Erzeugen des AudioInputs festgelegt. In diesem Fall unterteilen wir das Spektrum zu Beginn im setup-Blocks in 96 Bereiche.
Mit jedem Einzelbild wird eine Kopie des Buffers (toArray()) erzeugt um beim Durchlaufen des Arrays alle Einträge im Spektrum abzubilden. In der dafür benutzen for-Schleife verbindet die Oberseite eines Rechtecks jeweils zwei Teile des Spektrums. Neben der y-Position gibt der Mittelwert beider Kanäle die HSB-Füllfarbe an.
import ddf.minim.*;
Minim minim;
AudioInput input;
float yStart = 100;
float yScale = 140;
void setup () {
// Sketch einstellen
size (320, 240);
smooth();
noStroke ();
colorMode (HSB);
// Audiotoolkit anlegen
minim = new Minim (this);
input = minim.getLineIn (Minim.STEREO, 96);
}
void draw () {
background (0);
// Auslesen und speichern des Spektrums
float[] buffer = input.mix.toArray ();
// Breite der Rechtecke berechnen
float step = ceil ((float) width / buffer.length);
// Für jeden einzelnen Eintrag im Buffer wird ein
// Rechteck gezeichnet. Es entsteht durch das verbinden
// zweier Buffer-Einträge. Die Schleife beginnt bei 1,
// jeder Eintrag wird mit seinem Vorgänger verbunden.
for (int i=1; i < buffer.length; i++) {
// Positionen für alle 4 Punkt bestimmen
float x1 = (i-1) * step;
float x2 = i * step;
float y1 = yStart + buffer[i-1] * yScale;
float y2 = yStart + buffer[i] * yScale;
// Füllfarbe definieren
float h = (buffer[i-1] + buffer[i]) / 2;
fill (h * 255, 255, 255);
// Rechteck zeichnen
beginShape (QUADS);
vertex (x1, y1);
vertex (x2, y2);
vertex (x2, height);
vertex (x1, height);
endShape ();
}
}Frequenz Gruppen ermitteln in Processing
Die minim Bibliothek bietet neben dem Laden/Abspielen von Dokumenten, dem Nutzen von Audiohardware ein Paket von Werkzeugen um Klange zu erzeugen bzw. bestehendes Material auszuwerten. Eine bekannte Mehthode ist die sog. FFT (Fast Fourier Transform), bei der das Frequenzspektrum in eine feste Anzahl von Frequnzbändern unterteilt wird. Durch das Auslesen dieser Bänder erhält man Aufschluss über die Verteilung von Höhen, Mitten und Tiefen in der Audiodatei.
Da es bei hohen Auflösungen in den Bereichen von 1024, 512 oder 256 Bändern (wie in diesem Fall) nicht zu einer Abfrage jedes Frequenzbandes kommen muss, werden diese in den meisten Fällen zu Gruppen zusammengefasst. Im folgenden Beispiel liefert die Funktion getGroup() diese verkürzte Fassung mit einer Auflösung von 16 Bereichen, welche als Parameter angegeben wird. Die Anzahl der Bereich muss dabei immer ein Vielfaches von 2 sein (2, 4, 8, 16, 32, 64, 128, …). Innerhalb der Funktion wird ein Array angelegt und der Mittelwert einer jeden Frequenzgruppe berechnet, final in einem der Arrayfelder abgelegt. Nach dem Aufruf im draw(), kommt es zu einer simplen Abbildung der Audiodaten um die Funktionsweise zu verdeutlichen.
import ddf.minim.*;
import ddf.minim.analysis.*;
Minim minim;
AudioInput input;
FFT fft;
float maxSpec = 0;
void setup () {
size(320, 240);
noStroke ();
// Audiotool-Box und Mikrofoneingang erstellen
// und einrichten. Die '256' bestimmen dabei die
// spätere Auflösung des Spektrums.
minim = new Minim (this);
input = minim.getLineIn (Minim.STEREO, 256);
// FFT-Instanz für die Spektrumsanalyse
fft = new FFT (input.bufferSize (),
input.sampleRate ());
}
void draw () {
background (0);
float g = 0; // Grünwert der Füllfarbe
float h = 0; // Höhe von Rechteck und Linie
float specStep; // Breite einer horiz. Linie
float specScale = (float) width / (fft.specSize () - 1);
// Erzeugen der 'Frequenz-Gruppen' (16 Bereich)
// mögliche Schritte: 2-4-8-16-32-64-128
float[] group = getGroup (16);
// Zeichnen des detailierten Frequenzspektrums
noStroke ();
for (int i = 0; i < fft.specSize (); i++) {
g = map (fft.getBand (i), 0, maxSpec, 50, 255);
h = map (fft.getBand (i), 0, maxSpec, 2, height);
fill (0, g, 0);
rect (i * specScale, height - h, specScale, h);
}
// Zeichnen der Gruppen (Linien)
stroke (255, 255, 0, 200);
specStep = width / group.length;
for (int i=0; i < group.length; i++) {
h = height - map (group[i], 0, maxSpec, 0, height);
line (i * specStep, h, (i+1) * specStep, h);
}
}
/**
* Funktion fasst das vorliegende FFT-Spektrum
* in eine durch den Parameter 'theGroupNum'
* festgelegte Anzahl von gleichgroßen Bereichen
* zusammen – und gibt deren Mittelwert zurück.
*/
float[] getGroup (int theGroupNum) {
fft.forward (input.mix);
// Leeres Array für die Gruppen erstellen
float[] group = new float[theGroupNum];
// Das FFT-Spekturm hat eine Stelle mehr
// als beim Input definiert. (256->257).
// Diese wird ignoriert.
int specLimit = fft.specSize () - 1;
// Anzahl der Frequenzbänder pro Gruppe
int groupSize = specLimit / theGroupNum;
// Alle Gruppen mit einem Startwert füllen
for (int i=0; i < group.length; i++) {
group[i] = 0;
}
// Für jedes FFT-Frequenz-Band
for (int i=0; i < specLimit; i++) {
// Maximum?
if (fft.getBand (i) > maxSpec) {
maxSpec = fft.getBand (i);
}
// Jedes Band einer Gruppe zuweisen
int index = (int) Math.floor (i / groupSize);
group[index] += fft.getBand (i);
}
// Der Wert jeder Gruppe durch die Anzahl
// der enthaltenen Bänder Teilen - >Mittelwert
for (int i=0; i < group.length; i++) {
group[i] /= groupSize;
}
// Gruppe zurück geben.
return group;
}



