P5 und P5.sound - Filtern in Echtzeit



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 die 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
sound.amp(0.5); // Amplitude des vorausgeladenen Klangs "sound" auf die Hälfte reduzieren
filter = new p5.LowPass(); // Filter erstellen (auch HighPass oder BandPass möglich)
sound.disconnect(); // geladenen Klang (sound) von der direkten Audioausgabe trennen
sound.connect(filter); // geladenen Klang (sound) an Filter anschließen
}

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

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

cursor(HAND); // zeige den Cursor als Hand

filterFreq = map(mouseX, 0, width*8, 10, 22050); // skaliere die X-Koordinate
// der Maus zwischen 0 und 8* der Weite des Canvas auf einen Wertebereich zwischen 10 und 22050 und
// weise die Zahl der Variablen für die Filterfrequenz zu (filterFreq).

filterRes = map(mouseY, 0, height, 50, 1); // skaliere die Y-Koordinate der Maus
// zwischen 0 und der Höhe des Canvas auf einen Wertebereich zwischen 50 und 1 und weise die Zahl
// der Variablen für die Filterresonanz zu (filterRes).

filter.set(filterFreq, filterRes); // definiere die Filterfrequenz und die
// Filteresonanz des Filters.

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

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.

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

noStroke(); // keine Striche farblich ausfüllen (keine Strichfarbe)
fill(188, 48, 47); // Füllfarbe auf ein dunkles rot 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 (= jeden Frequenzbereich)

var x = map(i, 0, spectrum.length, 0, width*8); // skaliere die aufsteigend
// gezählten Zahlen i (von 0 bis Ende des spectrum Arrays) so, dass sie von 0 bis zur achtfachen
// 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). W
eise 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.

}

text('Frequency: ' + round(filterFreq)+'Hz', 20, 20, width - 20); //
// Ausgabe der Filterfrequenz

text('Resonance: ' + round(filterRes), 20, 40, width - 20); // Ausgabe der
// Filterresonanz

}

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.
}
}

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();
sound.amp(0.5);
filter = new p5.LowPass(); //auch HighPass oder BandPass möglich
sound.disconnect();
sound.connect(filter);
}

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

filterFreq = map(mouseX, 0, width*8, 10, 22050);
filterRes = map(mouseY, 0, height, 50, 1);
filter.set(filterFreq, filterRes);

var waveform = fft.waveform();
noFill();
stroke(225, 225, 225);
beginShape();
for (let 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();

var spectrum = fft.analyze();
noStroke();
fill(188, 48, 47);
for (var i = 0; i< spectrum.length; i++){
var x = map(i, 0, spectrum.length, 0, width*8);
var h = -height + map(spectrum[i], 0, 255, height, 0);
rect(x, height, width / spectrum.length, h );
}

text('Frequency: ' + round(filterFreq)+'Hz', 20, 20, width - 20);
text('Resonance: ' + round(filterRes), 20, 40, width - 20);
}

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>