P5 und P5.sound - Sonagramm



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 über ide Funktion preload() größere Dateien vorausgeladen:

function preload(){
sound = loadSound('klang/fagott_toene.mp3');
}

Dann werden in der Funktion setup() die Grundkomponenten festgelegt:

function setup(){
var container = createCanvas(800,400); // Canvas erstellen
container.parent('p5container'); // an DIV-Container anhängen
container.mouseClicked(togglePlay); // auf Klick auf Canvas reagieren
fft = new p5.FFT(); // Fouriertransformation des Signals ermöglichen
background(255,255,255); // der Hintergrund wird ganz zu Anfang auf weiss festgelegt, da
// er in der Funktion draw() pro Fram erneut überschrieben wird (anders als sonst wird er diesmal
// nicht jedesmal erneut in draw() auf weiß gesetzt).

}

Außerhalb der setup()-Funktion und der darauffolgenden draw()-Funktion wird eine Variable als Startpunkt für den Offset auf der X-Achse definiert, d.h. von diesem Punkt ausgehend wird der Offset mit jeder FFT erneut immer weiter nach rechts verschoben, bis alle Frames des Sonagramms gezeichnet werden, also:

var xoffset = 0;

Daraufhin wird in der Funktion draw() beschrieben, was im Canvas dargestellt werden soll:

function draw(){
cursor(HAND); // zeige den Cursor als Hand
noStroke(); // keine Striche farblich ausfüllen (keine Strichfarbe)
var bins = fft.analyze(); // mache eine Spektralanalyse in der Länge des aktuellen
// Frames und schreibe sie ins Array bins.

for (var i = 0; i < bins.length; i++){ // hole in einer Schleife in der Länge
// des Arrays bin jeden einzelnen Wert i aus dem Array (= jeden Frequenzbereich)

var drawY = map(bins.length-i, 0, bins.length, 0, height*2); // skaliere die
// Zahlen i (von 0 bis zur Länge des bins-Arrays) so, dass sie von 0 bis zur zweifachen Höhe des
// Canvas dargestellt werden (da in den höheren Frequenzanteilen so gut wie keine Amplituden mehr
// sind) und weise sie der Variablen drawY zu (Frequenz auf der Y-Achse).

var rectHeight = height / bins.length; // Errechne die Höhe des zu zeichnenden
// Rechtecks (pro bin-Wert) aus dem Quotienten zwischen der Höhe des Canvas und der Anzahl der bins
// im Array (= der Länge des bins-Arrays).
var rectWidth = width/sound.duration()/getFrameRate(); // Errechne die Weite
// des zu zeichnenden Rechtecks (pro bin-Wert) aus dem Quotienten von Weite des Canvas geteilt
// durch Länge des Klangbeispiels geteilt durch Frame-Rate des Canvas.

fill(255-bins[i], 255-bins[i], 255-bins[i]);
// Ziehe den jeweiligen bins-Wert von
// den RGB-Werten für die Füllfarbe pro zu zeichnendes Rechteck ab (= Färbung der einzelnen Punkte
// des Sonagramms)

rect(xoffset, drawY-height, rectWidth, rectHeight); // zeichne pro bins-Wert ein
// Rechteck mit dem xoffset-Wert als X-Koordinate und dem von der Canvas-Höhe abgezogene
drawY-Wert
// als Y-Koordinate, sowie mit der vorher definierten Weite (rectWidth) und Höhe (rectHeight).
// Jedes Rechteck stellt einen Punkt des Sonagramms dar.

}
if (sound.isPlaying()) { // sobald der Klang abgespielt wird:
xoffset += width/sound.duration()/getFrameRate(); // erhöhe den Offset um einen
// Faktor, der sich aus dem Quotienten von Weite des Canvas geteilt durch Länge des Klangbeispiels
// geteilt durch Frame-Rate des Canvas ergibt.

}
}

Schließlich wird mit der Funktion togglePlay() beschrieben, was passieren soll, wenn auf den Canvas geklickt wird

function togglePlay() {
if (sound.isPlaying()) { // wenn der Klang "sound" gespielt wird
sound.pause(); // stoppe ihn
} else { // in allen anderen Fällen
sound.play(); // spiele ihn ab.
}
}

Von dieser Grundstruktur ausgehend ist es nicht mehr schwer z.B. den Spectral Centroid oder den jeweils stärksten Teilton im im Sonagramm zu markieren (wie z.B. oben auf der Seite in der Anwendung).

Insgesamt sieht das Script dann folgendermaßen aus:

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

<script>
function preload(){
sound = loadSound('klang/fagott_toene.mp3');
}

function setup(){
var container = createCanvas(800,400);
container.parent('p5container');
container.mouseClicked(togglePlay);
fft = new p5.FFT();
background(255,255,255);
}

var xoffset = 0;

function draw(){
cursor(HAND);
noStroke();
var bins = fft.analyze();
for (var i = 0; i < bins.length; i++) {
var drawY = map(bins.length-i, 0, bins.length, 0, height*2);
var rectHeight = height / bins.length;
var rectWidth = width/sound.duration()/getFrameRate();
fill(255-bins[i], 255-bins[i], 255-bins[i]);
rect(xoffset, drawY-height, rectWidth, rectHeight);
}
if (sound.isPlaying()) {
xoffset += width/sound.duration()/getFrameRate();
}
}

function togglePlay() {
if (sound.isPlaying()) {
sound.pause();
} else {
sound.play();
}
}
</script>

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