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

Meyda arbeitet sehr gut mit P5 und P5.sound zusammen, z.B. um die Tonklassen (Chroma) 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.

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": ["chroma",], // Array (Liste) von zu analysierenden Eigenschaften
// (chroma ist ein Array mit den Zahlenwerten für die 12 Tonklassen)

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

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

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

chroma_dis = Math.round(features.chroma[3]* 1000)/1000; // runde den vierten
// Wert aus dem chroma-Array auf 3 Stellen und übergebe ihn an die Variable chroma_dis

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

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

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

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

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

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

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

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

}
});
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, 48, 47); // Füllfarbe auf dunkelrot setzen
textSize(12); // Textgröße auf 12 setzen
text("C: " + chroma_c, 20, 20); // Text für die Ausgabe der Tonklasse c erstellen
text("Cis: " + chroma_cis, 20, 40); // Text für die Ausgabe der Tonklasse cis
// erstellen

text("D: " + chroma_d, 20, 60); // Text für die Ausgabe der Tonklasse d erstellen
text("Dis: " + chroma_dis, 20, 80); // Text für die Ausgabe der Tonklasse dis
// erstellen
text("E: " + chroma_e, 20, 100); // Text für die Ausgabe der Tonklasse e erstellen
text("F: " + chroma_f, 20, 120); // Text für die Ausgabe der Tonklasse f erstellen
text("Fis: " + chroma_fis, 20, 140); // Text für die Ausgabe der Tonklasse fis
// erstellen
text("G: " + chroma_g, 20, 160); // Text für die Ausgabe der Tonklasse g erstellen
text("Gis: " + chroma_gis, 20, 180); // Text für die Ausgabe der Tonklasse gis
// erstellen
text("A: " + chroma_a, 20, 200); // Text für die Ausgabe der Tonklasse a erstellen
text("Ais: " + chroma_ais, 20, 220); // Text für die Ausgabe der Tonklasse ais
// erstellen
text("H: " + chroma_h, 20, 240); // Text für die Ausgabe der Tonklasse h erstellen

if(chroma_c==1){ textSize(24); fill(188, 48, 47); text("C", 20, 300);} else {fill(188, 188, 188); } // wenn der Wert für chroma_c = 1 ist, dann färbe den
// Balken und die Schrift dunkelrot, sonst lasse sie grau

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

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

if(chroma_cis==1){ textSize(24); fill(188, 48, 47); text("Cis", 20, 300);} else {fill(188, 188, 188); } // wenn der Wert für chroma_cis = 1 ist, dann
// färbe den Balken und die Schrift dunkelrot, sonst lasse sie grau

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

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

if(chroma_d==1){ textSize(24); fill(188, 48, 47); text("D", 20, 300);} else {fill(188, 188, 188); } // wenn der Wert für chroma_d = 1 ist, dann färbe den
// Balken und die Schrift dunkelrot, sonst lasse sie grau

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

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

if(chroma_dis==1){ textSize(24); fill(188, 48, 47); text("Dis", 20, 300);} else {fill(188, 188, 188); } // wenn der Wert für chroma_dis = 1 ist, dann
// färbe den Balken und die Schrift dunkelrot, sonst lasse sie grau

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

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

if(chroma_e==1){ textSize(24); fill(188, 48, 47); text("E", 20, 300);} else {fill(188, 188, 188); } // wenn der Wert für chroma_e = 1 ist, dann färbe den
// Balken und die Schrift dunkelrot, sonst lasse sie grau

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

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

if(chroma_f==1){ textSize(24); fill(188, 48, 47); text("F", 20, 300);} else {fill(188, 188, 188); } // wenn der Wert für chroma_f = 1 ist, dann färbe den
// Balken und die Schrift dunkelrot, sonst lasse sie grau

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

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

if(chroma_fis==1){ textSize(24); fill(188, 48, 47); text("Fis", 20, 300);} else {fill(188, 188, 188); } // wenn der Wert für chroma_fis = 1 ist, dann
// färbe den Balken und die Schrift dunkelrot, sonst lasse sie grau

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

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

if(chroma_g==1){ textSize(24); fill(188, 48, 47); text("G", 20, 300);} else {fill(188, 188, 188); } // wenn der Wert für chroma_g = 1 ist, dann färbe den
// Balken und die Schrift dunkelrot, sonst lasse sie grau

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

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

if(chroma_gis==1){ textSize(24); fill(188, 48, 47); text("Gis", 20, 300);} else {fill(188, 188, 188); } // wenn der Wert für chroma_gis = 1 ist, dann
// färbe den Balken und die Schrift dunkelrot, sonst lasse sie grau

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

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

if(chroma_a==1){ textSize(24); fill(188, 48, 47); text("A", 20, 300);} else {fill(188, 188, 188); } // wenn der Wert für chroma_a = 1 ist, dann färbe den
// Balken und die Schrift dunkelrot, sonst lasse sie grau

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

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

if(chroma_ais==1){ textSize(24); fill(188, 48, 47); text("Ais", 20, 300);} else {fill(188, 188, 188); } // wenn der Wert für chroma_ais = 1 ist, dann
// färbe den Balken und die Schrift dunkelrot, sonst lasse sie grau

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

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

if(chroma_h==1){ textSize(24); fill(188, 48, 47); text("H", 20, 300);} else {fill(188, 188, 188); } // wenn der Wert für chroma_h = 1 ist, dann färbe den
// Balken und die Schrift dunkelrot, sonst lasse sie grau

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

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

textSize(24); // Setze die Textgröße auf 24
fill(0, 0, 0); // Füllfarbe schwarz
text("C", 100, 395);text("Cis", 150, 395);text("D", 200, 395);text("Dis", 250, 395);text("E", 300, 395);text("F", 350, 395); text("Fis", 400, 395);text("G", 450, 395);text("Gis", 500, 395);text("A", 550, 395);text("Ais", 600, 395);text("H", 650, 395);
// Positioniere die Texte für die einzelnen Tonklassennamen
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 = window.AudioContext || window.webkitAudioContext;
const audioContext = new AudioContext();
const htmlAudioElement = document.getElementById('audio');
const source = audioContext.createMediaElementSource(htmlAudioElement);
source.connect(audioContext.destination);

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": ["chroma",],
"callback": features => {
chroma_c = Math.round(features.chroma[0]* 1000)/1000;
chroma_cis = Math.round(features.chroma[1]* 1000)/1000;
chroma_d = Math.round(features.chroma[2]* 1000)/1000;
chroma_dis = Math.round(features.chroma[3]* 1000)/1000;
chroma_e = Math.round(features.chroma[4]* 1000)/1000;
chroma_f = Math.round(features.chroma[5]* 1000)/1000;
chroma_fis = Math.round(features.chroma[6]* 1000)/1000;
chroma_g = Math.round(features.chroma[7]* 1000)/1000;
chroma_gis = Math.round(features.chroma[8]* 1000)/1000;
chroma_a = Math.round(features.chroma[9]* 1000)/1000;
chroma_ais = Math.round(features.chroma[10]* 1000)/1000;
chroma_h = Math.round(features.chroma[11]* 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, 48, 47); textSize(12);
text("C: " + chroma_c, 20, 20);
text("Cis: " + chroma_cis, 20, 40);
text("D: " + chroma_d, 20, 60);
text("Dis: " + chroma_dis, 20, 80);
text("E: " + chroma_e, 20, 100);
text("F: " + chroma_f, 20, 120);
text("Fis: " + chroma_fis, 20, 140);
text("G: " + chroma_g, 20, 160);
text("Gis: " + chroma_gis, 20, 180);
text("A: " + chroma_a, 20, 200);
text("Ais: " + chroma_ais, 20, 220);
text("H: " + chroma_h, 20, 240);
if(chroma_c==1){ textSize(24); fill(188, 48, 47); text("C", 20, 300);} else {fill(188, 188, 188); }
c_r = map(chroma_c, 0, 1, 0, height);
rect(100, height-c_r+50, 30, height);
if(chroma_cis==1){ textSize(24); fill(188, 48, 47); text("Cis", 20, 300);} else {fill(188, 188, 188); }
cis_r = map(chroma_cis, 0, 1, 0, height);
rect(150, height-cis_r+50, 30, height);
if(chroma_d==1){ textSize(24); fill(188, 48, 47); text("D", 20, 300);} else {fill(188, 188, 188); }
d_r = map(chroma_d, 0, 1, 0, height);
rect(200, height-d_r+50, 30, height);
if(chroma_dis==1){ textSize(24); fill(188, 48, 47); text("Dis", 20, 300);} else {fill(188, 188, 188); }
dis_r = map(chroma_dis, 0, 1, 0, height);
rect(250, height-dis_r+50, 30, height);
if(chroma_e==1){ textSize(24); fill(188, 48, 47); text("E", 20, 300);} else {fill(188, 188, 188); }
e_r = map(chroma_e, 0, 1, 0, height);
rect(300, height-e_r+50, 30, height);
if(chroma_f==1){ textSize(24); fill(188, 48, 47); text("F", 20, 300);} else {fill(188, 188, 188); }
f_r = map(chroma_f, 0, 1, 0, height);
rect(350, height-f_r+50, 30, height);
if(chroma_fis==1){ textSize(24); fill(188, 48, 47); text("Fis", 20, 300);} else {fill(188, 188, 188); }
fis_r = map(chroma_fis, 0, 1, 0, height);
rect(400, height-fis_r+50, 30, height);
if(chroma_g==1){ textSize(24); fill(188, 48, 47); text("G", 20, 300);} else {fill(188, 188, 188); }
g_r = map(chroma_g, 0, 1, 0, height);
rect(450, height-g_r+50, 30, height);
if(chroma_gis==1){ textSize(24); fill(188, 48, 47); text("Gis", 20, 300);} else {fill(188, 188, 188); }
gis_r = map(chroma_gis, 0, 1, 0, height);
rect(500, height-gis_r+50, 30, height);
if(chroma_a==1){ textSize(24); fill(188, 48, 47); text("A", 20, 300);} else {fill(188, 188, 188); }
a_r = map(chroma_a, 0, 1, 0, height);
rect(550, height-a_r+50, 30, height);
if(chroma_ais==1){ textSize(24); fill(188, 48, 47); text("Ais", 20, 300);} else {fill(188, 188, 188); }
ais_r = map(chroma_ais, 0, 1, 0, height);
rect(600, height-ais_r+50, 30, height);
if(chroma_h==1){ textSize(24); fill(188, 48, 47); text("H", 20, 300);} else {fill(188, 188, 188); }
h_r = map(chroma_h, 0, 1, 0, height);
rect(650, height-h_r+50, 30, height);

textSize(24); fill(0, 0, 0);
text("C", 100, 395);text("Cis", 150, 395);text("D", 200, 395);text("Dis", 250, 395);text("E", 300, 395);text("F", 350, 395); text("Fis", 400, 395);text("G", 450, 395);text("Gis", 500, 395);text("A", 550, 395);text("Ais", 600, 395);text("H", 650, 395);
noFill();
}

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