P5 und P5.sound - Zeit-Frequenz Darstellungen via Mikrofon
mit Spectral Centroid und maximale Amplitude



Zunächst werden im <head> der Seite die beiden Scripte für P5 und P5.sound eingebunden:

<script src="header/p5.js"></script>
<script src="header/p5.sound.js"></script>

Dann wird im <body> der Seite ein Container mit einer eindeutigen id mithilfe von DIV-Tags angelegt, in dem alles angezeigt werden soll. Dies geschieht via

<DIV id="p5container"></DIV>

über Style-Eigenschaften kann man diesen noch weiter im Aussehen verändern, z.B.

<DIV id="p5container" style="width:800;border: 1px solid #333;box-shadow: 8px 8px 5px #444;padding: 8px 12px;background-color:ffffff"></DIV>

Das dazugehörende Script ist wie fast alle P5 Scripte aufgebaut:

Erst werden in der Funktion setup() die Grundkomponenten festgelegt (die neu hinzugekommenen Scriptteile sind rot markiert):

function setup() {
var container = createCanvas(800,400); // Canvas erstellen
container.parent('p5container'); // an DIV-Container anhängen
mic = new p5.AudioIn(); // Mikrofoneingang anlegen
mic.start(); // Mikrofon starten
fft = new p5.FFT(); // Fouriertransformation des Signals ermöglichen
fft.setInput(mic);
// Mikrofoneingang mit der FFT-Analye verbinden
aufloesung = sampleRate()/fft.bins/2; // Abstand zwischen den einzelnen Frequenzen
// der FFT berechnen

}

Daraufhin wird in der Funktion draw() beschrieben, was im Canvas dargestellt werden soll (die neu hinzugekommenen Scriptteile sind rot markiert):

function draw() {
background(255,255,255); //zeichne weißen Hintergrund


var waveform = fft.waveform(); // hole aus dem Klang alle Pegelwerte und schreibe sie
// ins Array waveform

//Oszillogramm zeichnen
noFill(); // keine Formen farblich ausfüllen (keine Füllfarbe)
stroke(225, 225, 225); // Strichfarbe auf ein helles grau setzen
beginShape(); // das Zeichnen einer Form beginnen
for (var i = 0; i < waveform.length; i++){ // hole in einer Schleife in der Länge
// des Arrays waveform jeden einzelnen Wert i aus dem Array

var x = map( i, 0, waveform.length, 0, width); // skaliere die aufsteigend
// gezählten Zahlen i (von 0 bis Ende des waveform Arrays) so, dass sie von 0 bis zur Weite des
// Canvas dargestellt werden und weise sie der Variablen x zu (Zeit auf der X-Achse).

var y = map( waveform[i], -1, 1, 0, height);// skaliere jeden Wert aus dem
// Array waveform, der zwischen -1 und 1 liegt, so, dass er zwischen 0 und der Höhe des Canvas
// dargestellt wird, und weise ihn dem Wert y zu (Amplitude auf der Y-Achse)

vertex(x,y-100); // weise x und y als Koordinaten eines Punkts zu, der einen Eckpunkt der
// dazustellenden Gesamtform (via beginshape(); s.o.) beschreibt.

}
endShape(); // Schließe die Gesamtform ab, in ihr ist dann ein Ausschnitt der Wellenform pro
// Frame dargestellt.

//Spektrale Hüllkurve zeichnen
var spectrum = fft.analyze(); // mache eine Spektralanalyse in der Länge des aktuellen
// Frames und schreibe sie ins Array spectrum

noFill(); // keine Formen farblich ausfüllen (keine Füllfarbe)
stroke(188, 48, 47); // Strichfarbe auf ein dunkles rot setzen
beginShape(); // das Zeichnen einer Form beginnen
for (i = 0; i < spectrum.length; i++) { // hole in einer Schleife in der Länge
// des Arrays spectrum jeden einzelnen Wert i aus dem Array (= jede Frequenz)

var x = map(i, 0, spectrum.length, 0, width*4); // skaliere die aufsteigend
// gezählten Zahlen i (von 0 bis Ende des spectrum Arrays) so, dass sie von 0 bis zur vierfachen
// Weite des Canvas dargestellt werden (da in den höheren Frequenzanteilen so gut wie keine
// Amplituden sind) und weise sie der Variablen x zu (Frequenz auf der X-Achse).

var y = map(spectrum[i], 0, 255, height, 0); // skaliere jeden Wert
// aus dem Array spectrum, der zwischen 0 und 255 liegt, so, dass er zwischen der Höhe des Canvas
// und 0 dargestellt wird. Weise ihn dann dem Wert y zu (Amplituden der einzelnen Frequenzen auf
// der Y-Achse)

vertex(x,y); // weise x und y als Koordinaten eines Punkts zu, der einen Eckpunkt der
// dazustellenden Gesamtform (via beginshape(); s.o.) beschreibt.

}
endShape(); // Schließe die Gesamtform ab, in ihr ist dann ein Ausschnitt des Spektrums pro
// Frame dargestellt.

// Finde die stärkste Frequenz im Spektrum
noFill(); // keine Formen farblich ausfüllen (keine Füllfarbe)
stroke(188, 48, 47);
// Strichfarbe auf ein dunkles rot setzen
maxfrequenz_index= spectrum.indexOf(Math.max.apply({}, spectrum)); //
// ermittle, an welcher Stelle (= indexnummer)im Array spectrum die Frequenz mit der höchsten
// Amplitude ist und schreibe die Position ins die Variable maxfrequenz_index.

maxfrequenz = round(maxfrequenz_index*aufloesung); // Multipliziere die
// gefundene indexzahl
maxfrequenz_index mit der spektralen Auflösung, um die Frequenz der höchsten
// Amplitude im Spektrum zu ermitteln

text("Max. Frequenz: "+maxfrequenz + " Hz", 600, 40); // schreibe den
// gefundenen Wert in den Canvas


// Finde den Spectral Centroid
noFill();
// keine Formen farblich ausfüllen (keine Füllfarbe)
stroke(48, 188, 47);
// Strichfarbe auf ein dunkles grün setzen
spectralCentroid = round(fft.getCentroid()); // ermittle den Spectral Centroid
text("Spectral Centroid: "+spectralCentroid + " Hz", 600, 60);
// schreibe
// den gefundenen Wert in den Canvas

rect(spectralCentroid/8, 100, width / spectrum.length, height );
//
// zeichne ein Rechteck mit den Ausgangskoordinaten
spectralCentroid/8 (eigentlich das Spekturm /
// 2, da dieses für die Darstellung aber mal 4 genommen wurde, s.u., muss durch 8 geteilt werden)

// und der Höhe von 100 Pixeln unter der oberen Canvas-Kante, einer Breite, die unabhängig von der
// Größe des spektralen Arrays (spectrum) immer die richtige ist, um insgesamt den Canvas in der
// Breite zu füllen, und der Höhe des Canvas.

// ermittle Frequenz und Amplitude an der Mausposition
noFill();
// keine Formen farblich ausfüllen (keine Füllfarbe)
stroke(225, 225, 225);
// Strichfarbe auf ein helles grau setzen
aktuelle_frequenz = round(map(mouseX, 0, spectrum.length, 0, width*4)*aufloesung/10); // ermittle die Frequenz, die unter der Maus ist
// (aktuelle_frequenz), indem die X-Koordinate der Maus zwischen 0 und dem maximalen Frequenzband

//
der Spektralanalyse gemappt wird auf den Wertebereich zwischen 0 und der vierfachen Canvasbreite
// mal einem 10tel der spektralen Auflösung und runde das ganze zu einer ganzen Zahl

aktueller_pegel=round(((map(mouseY, 0, 255, height, 0))+200)/6); //
// ermittle dem Pegel, der unter der Maus ist (aktueller Pegel), indem die Y-Koordinate der Maus
// zwischen 0 und 255 gemappt wird auf den Wertebereich zwischen der Höhe des Canvas (= untere
// Kante) und 0 (=obere Kante).

if (aktuelle_frequenz>=0){ // wenn die aktuelle Frequenz >= 0 ist
text(aktuelle_frequenz + " Hz", mouseX+15, mouseY); // gebe die aktuelle
// Frequenz an der Position der Maus aus.

}
text(aktueller_pegel + " %", mouseX+15, mouseY+12); // gebe die aktuelle
// Amplitude an der Position der Maus aus

rect(mouseX, 100, width / spectrum.length, height ); // zeichne die X-Achse
// eines Koordinatenkreuzes mit der Maus im Mittelpunkt

rect(0, mouseY, width, width / spectrum.length ); // zeichne die Y-Achse
// eines Koordinatenkreuzes mit der Maus im Mittelpunkt

//Spektrum zeichnen
noFill(); // keine Formen farblich ausfüllen (keine Füllfarbe)

stroke(225, 225, 225); // Strichfarbe auf ein helles grau setzen
for (var i = 0; i< spectrum.length; i++){ // hole in einer Schleife in der Länge
// des Arrays spectrum jeden einzelnen Wert i aus dem Array (= jede Frequenz)
if (i == maxfrequenz_index){stroke(188, 48, 47);} else {stroke(225, 225, 225);}
// wenn i dem Index der Frequenz mit der maximalen Amplitude entspricht, dann wähle
// dunkelrot als Strichfarbe, sonst hellgrau.

var x = map(i, 0, spectrum.length, 0, width*4); // skaliere die aufsteigend
// gezählten Zahlen i (von 0 bis Ende des spectrum Arrays) so, dass sie von 0 bis zur vierfachen
// Weite des Canvas dargestellt werden (da in den höheren Frequenzanteilen so gut wie keine
// Amplituden sind) und weise sie der Variablen x zu (Frequenz auf der X-Achse).

var h = -height + map(spectrum[i], 0, 255, height, 0); // skaliere jeden Wert
// aus dem Array spectrum, der zwischen 0 und 255 liegt, so, dass er zwischen der Höhe des Canvas
// und 0 dargestellt wird, und ziehe ihn von der Höhe des Canvas ab (damit die Amplitudenwerte für
// die einzelnen Frequenzen nicht auf dem Kopf dargestellt werden). Weise ihn dann dem Wert y zu
// (Amplituden der einzelnen Frequenzen auf der Y-Achse)

rect(x, height, width / spectrum.length, h ); // zeichne pro Frequenz ein Rechteck
// mit den Ausgangskoordinaten x und der Höhe des Canvas (= an der untere Kante), einer Breite, die
// unabhängig von der Größe des spektralen Arrays (spectrum) immer die richtige ist, um insgesamt
// den Canvas in der Breite zu füllen, und einer Höhe h.
}

}

Insgesamt sieht das Script dann folgendermaßen aus:

<script src="header/p5.js"></script>
<script src="header/p5.sound.js"></script>

<script>
function setup() {

var container = createCanvas(800,400);
container.parent('p5container');
mic = new p5.AudioIn();
mic.start();
fft = new p5.FFT();
fft.setInput(mic);
aufloesung = sampleRate()/fft.bins/2;
}

function draw() {
background(255,255,255);

//Oszillogramm zeichnen
var waveform = fft.waveform();

noFill();
stroke(225, 225, 225);
beginShape();
for (var i = 0; i < waveform.length; i++){
var x = map(i, 0, waveform.length, 0, width);
var y = map( waveform[i], -1, 1, 0, height);
vertex(x,y-100);
}
endShape();

//Spektrale Hüllkurve zeichnen
var spectrum = fft.analyze();

noFill();
stroke(188, 48, 47);
beginShape();
for (i = 0; i < spectrum.length; i++) {
var x = map(i, 0, spectrum.length, 0, width*4);
var y = map(spectrum[i], 0, 255, height, 0);
vertex(x,y);
}
endShape();

// Finde die stärkste Frequenz im Spektrum
no
Fill();
stroke(188, 48, 47);
maxfrequenz_index= spectrum.indexOf(Math.max.apply({}, spectrum));
maxfrequenz = round(maxfrequenz_index*aufloesung);
text("Max. Frequenz: "+maxfrequenz + " Hz", 600, 40);

// Finde den Spectral Centroid
noFill();
stroke(48, 188, 47);
spectralCentroid = round(fft.getCentroid());
text("Spectral Centroid: "+spectralCentroid + " Hz", 600, 60);
rect(spectralCentroid/8, 100, width / spectrum.length, height );

// ermittle Frequenz und Amplitude an der Mausposition
noFill();
stroke(225, 225, 225);
aktuelle_frequenz = round(map(mouseX, 0, spectrum.length, 0, width*4)*aufloesung/10);
aktueller_pegel=round(((map(mouseY, 0, 255, height, 0))+200)/6);
if (aktuelle_frequenz>=0){text(aktuelle_frequenz + " Hz", mouseX+15, mouseY);}
text(aktueller_pegel + " %", mouseX+15, mouseY+12);
rect(mouseX, 100, width / spectrum.length, height );
rect(0, mouseY, width, width / spectrum.length );

//Spektrum zeichnen
noFill();
stroke(225, 225, 225);
for (var i = 0; i< spectrum.length; i++){
if (i == maxfrequenz_index){stroke(188, 48, 47);} else {stroke(225, 225, 225);}
var x = map(i, 0, spectrum.length, 0, width*4);
var h = -height + map(spectrum[i], 0, 255, height, 0);
rect(x, height, width / spectrum.length, h );
}

}
</script>

<DIV id="p5container" style="width:800;border: 1px solid #333;box-shadow: 8px 8px 5px #444;padding: 8px 12px;background-color:ffffff"></DIV>