Kombination von Meyda und P5 - MFCCs (-> Mikrophon-Version)

Meyda arbeitet sehr gut mit P5 und P5.sound zusammen, z.B. um MFCCs aus einem Klangbeispiel automatisch zu extrahieren und zu visualisieren:

1. Einbettung der Meyda- und der Plotly-Library

Zunächst werden im <head> der Seite die Meyda- und P5-Javascript-Bibliotheken eingebettet:

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

 

2. Script zur Steuerung eines Audioplayers

Dann wird im <head> der Seite ein Script zur Steuerung des Audioplayers angelegt:

<script>
function spielAudio() { // Funktion zum Abspielen des Audioplayers
var meinplayer = document.getElementById('audio'); // finde über
// getElementById() den Audioplayer mit der id "audio" und übergebe ihn an die Variable meinPlayer.

meinplayer.play(); // starte den so gefundenen Audioplayer
}

function stopAudio() { // Funktion zum Stoppen des Audioplayers
var meinplayer = document.getElementById('audio'); // finde über
// getElementById() den Audioplayer mit der id "audio" und übergebe ihn an die Variable meinPlayer.

meinplayer.pause(); // pausiere den Player
meinplayer.currentTime = 0; // setze ihn auf den Anfang des Stücks
}
</script>

 

3. Einbettung eines Audioplayers mit id und Steuerungsmöglichkeit

Im <body> der Seite wird der Player angelegt:

<audio id="audio" src="klang/fagott_toene.mp3"> </audio>

 

4. Einbettung eines Bereichs, in dem die Daten visualisiert werden sollen

Unter dem Audioplayer wird innerhalb eines <DIV>-Tag ein Bereich (hier mit der id "p5container") festgelegt, in dem die Audiodaten mit Hilfe von P5 visualisiert werden sollen:

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

 

5. Starten des WebAudioApi und Audio-Analyse mit Meyda

Darunter wird das Script für die Analyse eingebaut.
<script defer>
bedeutet, dass das Script nicht seriell im Seitenaufbau eingebaut ist, sondern parallel zu den anderen Scripten und Funktionen auf der Seite passiert:

<script defer>
const audioContext = new AudioContext(); // erzeuge einen Audiokontext (ähnlich wie
// einen Canvas) für das WebAudioApi

const htmlAudioElement = document.getElementById("audio"); // suche den
// Audioplayer via getElementById() und weise ihn der Variablen htmlAudioElement zu.

const source = audioContext.createMediaElementSource(htmlAudioElement); // füge den Audioplayer über die Variable htmlAudioElement als Mediaquelle dem audioContext hinzu.
source.connect(audioContext.destination); // verbinde den audioContext mit der
// Soundausgabe des Browsers/Computers.

mfccfaktor = audioContext.sampleRate/512; // Faktor mit dem die Werte der MFCCs
// multipliziert werden müssen

if (typeof Meyda === "undefined") { // falls die Meyda-Library nicht erkannt wird:
alert("Meyda konnte nicht gefunden werden, wurde die Library nicht eingebunden?"); // gebe eine Fehlermeldung aus
} else { // in allen anderen Fällen:
const analyzer = Meyda.createMeydaAnalyzer({ // erstelle einen Analyzer mit
// folgenden Eigenschaften:

"audioContext": audioContext, // audioContext, auf den sich der Analyzer bezieht
"source": source, // Quelle auf die sich der Analyzer bezieht (= der Audioplayer)
"bufferSize": 512, // Buffergröße
"featureExtractors": ["mfcc",], // Array (Liste) von zu analysierenden Eigenschaften
// (mfcc ist ein Array mit den Zahlenwerten für die 13 Mel-Frequency Cepstral Coefficients)

"callback": features => { // Ausgabe der ermittelten Features
mfcc1 = Math.round(features.mfcc[0]/mfccfaktor * 1000)/1000; // runde den
// ersten Wert aus dem mfcc-Array auf 3 Stellen, multipliziere ihn mit dem mfccfaktor und übergebe
// ihn an die Variable mfcc1

mfcc2 = Math.round(features.mfcc[1]/mfccfaktor * 1000)/1000; // runde den
// zweiten Wert aus dem mfcc-Array auf 3 Stellen, multipliziere ihn mit dem mfccfaktor und übergebe
// ihn an die Variable mfcc2

mfcc3 = Math.round(features.mfcc[2]/mfccfaktor * 1000)/1000; // runde den
// dritten Wert aus dem mfcc-Array auf 3 Stellen, multipliziere ihn mit dem mfccfaktor und übergebe
// ihn an die Variable mfcc3

mfcc4 = Math.round(features.mfcc[3]/mfccfaktor * 1000)/1000; // runde den
// vierten Wert aus dem mfcc-Array auf 3 Stellen, multipliziere ihn mit dem mfccfaktor und übergebe
// ihn an die Variable mfcc4

mfcc5 = Math.round(features.mfcc[4]/mfccfaktor * 1000)/1000; // runde den
// fünften Wert aus dem mfcc-Array auf 3 Stellen, multipliziere ihn mit dem mfccfaktor und übergebe
// ihn an die Variable mfcc5

mfcc6 = Math.round(features.mfcc[5]/mfccfaktor * 1000)/1000; // runde den
// sechsten Wert aus dem mfcc-Array auf 3 Stellen, multipliziere ihn mit dem mfccfaktor und
// übergebe ihn an die Variable mfcc6

mfcc7 = Math.round(features.mfcc[6]/mfccfaktor * 1000)/1000; // runde den
// siebten Wert aus dem mfcc-Array auf 3 Stellen, multipliziere ihn mit dem mfccfaktor und übergebe
// ihn an die Variable mfcc7

mfcc8 = Math.round(features.mfcc[7]/mfccfaktor * 1000)/1000; // runde den
// achten Wert aus dem mfcc-Array auf 3 Stellen, multipliziere ihn mit dem mfccfaktor und übergebe
// ihn an die Variable mfcc8

mfcc9 = Math.round(features.mfcc[8]/mfccfaktor * 1000)/1000; // runde den
// neunten Wert aus dem mfcc-Array auf 3 Stellen, multipliziere ihn mit dem mfccfaktor und übergebe
// ihn an die Variable mfcc9

mfcc10 = Math.round(features.chroma[9]/mfccfaktor * 1000)/1000; // runde
// den zehnten Wert aus dem mfcc-Array auf 3 Stellen, multipliziere ihn mit dem mfccfaktor und
// übergebe ihn an die Variable mfcc10

mfcc11 = Math.round(features.mfcc[10]/mfccfaktor * 1000)/1000; // runde
// den elften Wert aus dem mfcc-Array auf 3 Stellen, multipliziere ihn mit dem mfccfaktor und
// übergebe ihn an die Variable mfcc11

mfcc12 = Math.round(features.mfcc[11]/mfccfaktor * 1000)/1000; // runde
// den zwölften Wert aus dem mfcc-Array auf 3 Stellen, multipliziere ihn mit dem mfccfaktor und
// übergebe ihn an die Variable mfcc12

mfcc13 = Math.round(features.mfcc[12]/mfccfaktor * 1000)/1000; // runde
// den dreizehnten Wert aus dem mfcc-Array auf 3 Stellen, multipliziere ihn mit dem mfccfaktor und
// übergebe ihn an die Variable mfcc13

}
});
analyzer.start(); // starte den Analyzer
}
</script>

 

6. Setup- und Draw-Funktion für P5 erstellen

Auf der Grundlage der mit Meyda ermittelten Chroma-Werte kann nun mit Hilfe von P5 mit der Visualisierung begonnen werden. Wie in den meisten P5-Anwendungen benötigt man hierzu eine setup()- und eine draw()-Funktion sowie am Ende eine Funktion, die das Starten und Stoppen des Audioplayers steuert (togglePlay()):

function setup(){
var container = createCanvas(800,400); // Canvas erstellen
container.parent('p5container'); // an DIV-Container anhängen
container.mouseClicked(togglePlay); // auf Klick auf Canvas mit Funktion togglePlay
// reagieren

}

function draw(){
background(255, 255, 255); // zeichne weißen Hintergrund
cursor(HAND); // zeige den Cursor als Hand
noStroke(); // keine Strichfarbe
fill(188, 188, 188); // Füllfarbe auf hellgrau setzen
textSize(12); // Textgröße auf 12 setzen
text("MFCC1: " + mfcc1, 20, 20); // Text für mfcc1-Wert erstellen
text("MFCC2: " + mfcc2, 20, 40); // Text für mfcc2-Wert erstellen
text("MFCC3: " + mfcc3, 20, 60); // Text für mfcc3-Wert erstellen
text("MFCC4: " + mfcc4, 20, 80); // Text für mfcc4-Wert erstellen
text("MFCC5: " + mfcc5, 20, 100); // Text für mfcc5-Wert erstellen
text("MFCC6: " + mfcc6, 20, 120); // Text für mfcc6-Wert erstellen
text("MFCC7: " + mfcc7, 20, 140); // Text für mfcc7-Wert erstellen
text("MFCC8: " + mfcc8, 20, 160); // Text für mfcc8-Wert erstellen
text("MFCC9: " + mfcc9, 20, 180); // Text für mfcc9-Wert erstellen
text("MFCC10: " + mfcc10, 20, 200); // Text für mfcc10-Wert erstellen
text("MFCC11: " + mfcc11, 20, 220); // Text für mfcc11-Wert erstellen
text("MFCC12: " + mfcc12, 20, 240); // Text für mfcc12-Wert erstellen
text("MFCC13: " + mfcc13, 20, 260); // Text für mfcc13-Wert erstellen
fill(188, 48, 47); // Füllfarbe auf dunkelrot setzen

c_mfcc1 = map(mfcc1, -1, 1, 0, height); // skaliere den Wert zwischen -1 und 1 von
// mfcc1 so, dass er zwischen 0 und der Höhe des Canvas ist und weise ihn der Variablen c_mfcc1 zu

rect(100, height/2, 30, height/2-c_mfcc1); // erstelle ein Rechteck, deren Höhe von
// der Variablen c_mfcc1 abhängig ist.

c_mfcc2 = map(mfcc2, -1, 1, 0, height); // skaliere den Wert zwischen -1 und 1 von
// mfcc2 so, dass er zwischen 0 und der Höhe des Canvas ist und weise ihn der Variablen c_mfcc2 zu

rect(150, height/2, 30, height/2-c_mfcc2); // erstelle ein Rechteck, deren Höhe von
// der Variablen c_mfcc2 abhängig ist.

c_mfcc3 = map(mfcc3, -1, 1, 0, height); // skaliere den Wert zwischen -1 und 1 von
// mfcc3 so, dass er zwischen 0 und der Höhe des Canvas ist und weise ihn der Variablen c_mfcc3 zu

rect(200, height/2, 30, height/2-c_mfcc3); // erstelle ein Rechteck, deren Höhe von
// der Variablen c_mfcc3 abhängig ist.

c_mfcc4 = map(mfcc4, -1, 1, 0, height); // skaliere den Wert zwischen -1 und 1 von
// mfcc4 so, dass er zwischen 0 und der Höhe des Canvas ist und weise ihn der Variablen c_mfcc4 zu

rect(250, height/2, 30, height/2-c_mfcc4); // erstelle ein Rechteck, deren Höhe von
// der Variablen c_mfcc4 abhängig ist.

c_mfcc5 = map(mfcc5, -1, 1, 0, height); // skaliere den Wert zwischen -1 und 1 von
// mfcc5 so, dass er zwischen 0 und der Höhe des Canvas ist und weise ihn der Variablen c_mfcc5 zu

rect(300, height/2, 30, height/2-c_mfcc5); // erstelle ein Rechteck, deren Höhe von
// der Variablen c_mfcc5 abhängig ist.

c_mfcc6 = map(mfcc6, -1, 1, 0, height); // skaliere den Wert zwischen -1 und 1 von
// mfcc6 so, dass er zwischen 0 und der Höhe des Canvas ist und weise ihn der Variablen c_mfcc6 zu

rect(350, height/2, 30, height/2-c_mfcc6); // erstelle ein Rechteck, deren Höhe von
// der Variablen c_mfcc6 abhängig ist.

c_mfcc7 = map(mfcc7, -1, 1, 0, height); // skaliere den Wert zwischen -1 und 1 von
// mfcc7 so, dass er zwischen 0 und der Höhe des Canvas ist und weise ihn der Variablen c_mfcc7 zu

rect(400, height/2, 30, height/2-c_mfcc7); // erstelle ein Rechteck, deren Höhe von
// der Variablen c_mfcc7 abhängig ist.

c_mfcc8 = map(mfcc8, -1, 1, 0, height); // skaliere den Wert zwischen -1 und 1 von
// mfcc8 so, dass er zwischen 0 und der Höhe des Canvas ist und weise ihn der Variablen c_mfcc8 zu

rect(450, height/2, 30, height/2-c_mfcc8); // erstelle ein Rechteck, deren Höhe von
// der Variablen c_mfcc8 abhängig ist.

c_mfcc9 = map(mfcc9, -1, 1, 0, height); // skaliere den Wert zwischen -1 und 1 von
// mfcc9 so, dass er zwischen 0 und der Höhe des Canvas ist und weise ihn der Variablen c_mfcc9 zu

rect(500, height/2, 30, height/2-c_mfcc9); // erstelle ein Rechteck, deren Höhe von
// der Variablen c_mfcc9 abhängig ist.

c_mfcc10 = map(mfcc10, -1, 1, 0, height); // skaliere den Wert zwischen -1 und 1
// von mfcc10 so, dass er zwischen 0 und der Höhe des Canvas ist und weise ihn der Variablen
// c_mfcc10 zu

rect(550, height/2, 30, height/2-c_mfcc10); // erstelle ein Rechteck, deren Höhe
// von der Variablen c_mfcc10 abhängig ist.

c_mfcc11 = map(mfcc11, -1, 1, 0, height); // skaliere den Wert zwischen -1 und 1
// von mfcc11 so, dass er zwischen 0 und der Höhe des Canvas ist und weise ihn der Variablen
// c_mfcc11 zu

rect(600, height/2, 30, height/2-c_mfcc11); // erstelle ein Rechteck, deren Höhe
// von der Variablen c_mfcc11 abhängig ist.

c_mfcc12 = map(mfcc12, -1, 1, 0, height); // skaliere den Wert zwischen -1 und 1
// von mfcc12 so, dass er zwischen 0 und der Höhe des Canvas ist und weise ihn der Variablen
// c_mfcc12 zu

rect(650, height/2, 30, height/2-c_mfcc12); // erstelle ein Rechteck, deren Höhe
// von der Variablen c_mfcc12 abhängig ist.

c_mfcc13 = map(mfcc13, -1, 1, 0, height); // skaliere den Wert zwischen -1 und 1
// von mfcc13 so, dass er zwischen 0 und der Höhe des Canvas ist und weise ihn der Variablen
// c_mfcc13 zu

rect(700, height/2, 30, height/2-c_mfcc13); // erstelle ein Rechteck, deren Höhe
// von der Variablen c_mfcc13 abhängig ist.

fill(0, 0, 0); // Füllfarbe schwarz
text("MFCC1", 100, 210); text("MFCC2", 150, 210); text("MFCC3", 200, 210); text("MFCC4", 250, 210); text("MFCC5", 300, 210); text("MFCC6", 350, 210); text("MFCC7", 400, 210); text("MFCC8", 450, 210); text("MFCC9", 500, 210); text("MFCC10", 550, 210); text("MFCC11", 600, 210); text("MFCC12", 650, 210); text("MFCC13", 700, 210);
// Positioniere die Texte für die einzelnen MFCCs
noFill(); // keine Füllfarbe
}

function togglePlay() { // Steuerung des Audioplayers
meinplayer = document.getElementById('audio'); // finde über
// getElementById() den Audioplayer mit der id "audio" und übergebe ihn an die Variable meinplayer.

if (!meinplayer.paused) { // wenn der Audioplayer nicht pausiert
stopAudio(); // pausiere ihn
} else { // in allen anderen Fällen
spielAudio(); // starte ihn
}
}
</script>

 

 

Insgesamt sieht das Script dann folgendermaßen aus:

 

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

<script>
function spielAudio() {
var meinplayer = document.getElementById('audio');
meinplayer.play();
}


function stopAudio() {
var meinplayer = document.getElementById('audio');
meinplayer.pause();
meinplayer.currentTime = 0;
}

</script>
</head>
<body text="#000000" bgcolor="#FFFFFF">

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


<script defer>

const audioContext = new AudioContext();
const htmlAudioElement = document.getElementById('audio');
const source = audioContext.createMediaElementSource(htmlAudioElement);
source.connect(audioContext.destination);
mfccfaktor = audioContext.sampleRate/512;

if (typeof Meyda === "undefined") {
alert("Meyda konnte nicht gefunden werden, wurde die Library nicht eingebunden?");
} else {
const analyzer = Meyda.createMeydaAnalyzer({
"audioContext": audioContext,
"source": source,
"bufferSize": 512,
"featureExtractors": ["mfcc"],
"callback": features => {
mfcc1 = Math.round(features.mfcc[0]/mfccfaktor* 1000)/1000;
mfcc2 = Math.round(features.mfcc[1]/mfccfaktor* 1000)/1000;
mfcc3 = Math.round(features.mfcc[2]/mfccfaktor* 1000)/1000;
mfcc4 = Math.round(features.mfcc[3]/mfccfaktor* 1000)/1000;
mfcc5 = Math.round(features.mfcc[4]/mfccfaktor* 1000)/1000;
mfcc6 = Math.round(features.mfcc[5]/mfccfaktor* 1000)/1000;
mfcc7 = Math.round(features.mfcc[6]/mfccfaktor* 1000)/1000;
mfcc8 = Math.round(features.mfcc[7]/mfccfaktor* 1000)/1000;
mfcc9 = Math.round(features.mfcc[8]/mfccfaktor* 1000)/1000;
mfcc10 = Math.round(features.mfcc[9]/mfccfaktor* 1000)/1000;
mfcc11 = Math.round(features.mfcc[10]/mfccfaktor* 1000)/1000;
mfcc12 = Math.round(features.mfcc[11]/mfccfaktor* 1000)/1000;
mfcc13 = Math.round(features.mfcc[12]/mfccfaktor* 1000)/1000;
}
});
analyzer.start();
}

function setup(){
var container = createCanvas(800,400);
container.parent('p5container');
container.mouseClicked(togglePlay);
}

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

noStroke();
fill(188, 188, 188); textSize(12);
text("MFCC1: " + mfcc1, 20, 20);
text("MFCC2: " + mfcc2, 20, 40);
text("MFCC3: " + mfcc3, 20, 60);
text("MFCC4: " + mfcc4, 20, 80);
text("MFCC5: " + mfcc5, 20, 100);
text("MFCC6: " + mfcc6, 20, 120);
text("MFCC7: " + mfcc7, 20, 140);
text("MFCC8: " + mfcc8, 20, 160);
text("MFCC9: " + mfcc9, 20, 180);
text("MFCC10: " + mfcc10, 20, 200);
text("MFCC11: " + mfcc11, 20, 220);
text("MFCC12: " + mfcc12, 20, 240);
text("MFCC13: " + mfcc13, 20, 260);
fill(188, 48, 47);
c_mfcc1 = map(mfcc1, -1, 1, 0, height);
rect(100, height/2, 30, height/2-c_mfcc1);
c_mfcc2 = map(mfcc2, -1, 1, 0, height);
rect(150, height/2, 30, height/2-c_mfcc2);
c_mfcc3 = map(mfcc3, -1, 1, 0, height);
rect(200, height/2, 30, height/2-c_mfcc3);
c_mfcc4 = map(mfcc4, -1, 1, 0, height);
rect(250, height/2, 30, height/2-c_mfcc4);
c_mfcc5 = map(mfcc5, -1, 1, 0, height);
rect(300, height/2, 30, height/2-c_mfcc5);
c_mfcc6 = map(mfcc6, -1, 1, 0, height);
rect(350, height/2, 30, height/2-c_mfcc6);
c_mfcc7 = map(mfcc7, -1, 1, 0, height);
rect(400, height/2, 30, height/2-c_mfcc7);
c_mfcc8 = map(mfcc8, -1, 1, 0, height);
rect(450, height/2, 30, height/2-c_mfcc8);
c_mfcc9 = map(mfcc9, -1, 1, 0, height);
rect(500, height/2, 30, height/2-c_mfcc9);
c_mfcc10 = map(mfcc10, -1, 1, 0, height);
rect(550, height/2, 30, height/2-c_mfcc10);
c_mfcc11 = map(mfcc11, -1, 1, 0, height);
rect(600, height/2, 30, height/2-c_mfcc11);
c_mfcc12 = map(mfcc12, -1, 1, 0, height);
rect(650, height/2, 30, height/2-c_mfcc12);
c_mfcc13 = map(mfcc13, -1, 1, 0, height);
rect(700, height/2, 30, height/2-c_mfcc13);

fill(0, 0, 0);
text("MFCC1", 100, 210); text("MFCC2", 150, 210); text("MFCC3", 200, 210); text("MFCC4", 250, 210); text("MFCC5", 300, 210); text("MFCC6", 350, 210); text("MFCC7", 400, 210); text("MFCC8", 450, 210); text("MFCC9", 500, 210); text("MFCC10", 550, 210); text("MFCC11", 600, 210); text("MFCC12", 650, 210) ;text("MFCC13", 700, 210);
noFill();

}

function togglePlay() {
meinplayer = document.getElementById('audio');
if (!meinplayer.paused) {
stopAudio();
} else {
spielAudio();
}
}
</script>
</body>