P5
und ML5: Tonhöhenerkennung
in komplexen Klängen (Mikrophon)
Über ML5 kann P5 auf Machine-Learning-Modelle zugeifen, die im Bereich
der Mustererkennung Dinge ermöglichen, die vor einigen Jahren für
einfache Scriptsprachen wie Javascript oder P5 noch undenkbar waren. So
ist es auch möglich eine der komplexesten Aufgaben in der musikalischen
Akustik die Tonhöhenerkennung mit Hilfe von trainierten Modellen
zu bewältigen:
Um auf diese
Möglichkeit zugreifen zu können, muss im Header der HTML-Seite
neben der P5-Library
und der p5.sound-Library
auch die ML5-Library
eingebunden werden, so
dass es im Header der Seite folgendermaßen heisst:
<script src="header/ml5.min_0.12.2.js"></script>
Im Script
werden nach der Deklarierung der für die Verbindung mit der Tonhöhenerkennung
via facemesh und die Bilddarstellung notwendigen Variablen ...
var audioContext; //
Variable für den Inhalt des Audio-Kontexts
var mic; // Variable für den angeschlossenen
Mikrofoneingang
var pitch; // Array für das Tonhöhenerkennungs-Modell
var tonhoehe = 0; // Variable für
die erkannte Frequenz
var modell_geladen = 0; // Zustand des
Tonhöhenerkennungsmodells
var pitch_array=[]; // Array für alle
erkannten Tonhöhen
wird die
setup-Funktion gestartet,
in der sowohl der Canvas zur Darstellung der Pitch-Visualisierung erstellt
wird als auch der Mikrofoneingang und ein Tongenerator zur Kontrolle angelegt
und gestartet werden:
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(startPitch); // Mikrofon starten
und Funktion zum Laden des Tonhöhenmodells
// aufrufen
sinuston = new p5.SinOsc(); // Sinusgenerator
anlegen
sinuston.start(); // Sinusgenerator starten
sampleRate(60); // Bildwiederholfrequenz
einstellen
}
Sobald das
Mikrofon gestartet ist, wird mit der Funktion
startPitch() die Tonhöhenerkennung geladen:
function startPitch()
{
pitch = ml5.pitchDetection('./model_pitch/', audioContext , mic.stream,
modelLoaded); // Lade das Modell für
die Tonhöhenerkennung, das sich auf dem Server im
// gleichen Ordner unter model_pitch befindet, beziehe es auf die aktuelle
Audio-Umgebung
// (audioContext) und darin auf den oben angelegten Mikrofoneingang (mic).
Sobald es geladen ist,
// starte die Funktion modelLoaded.
}
Sobald das
Tonhöhenerkennungsmodell geladen ist, kann mit der Funktion getPitch(),
die aktuelle Tonhöhe ermittelt werden:
function modelLoaded()
{
modell_geladen = 1; // sobald das Tonhöhenerkennungsmodell
geladen ist ...
getPitch(); // ermittle die aktuelle Tonhöhe
}
Sobald das
Tonhöhenmodell geladen ist, starte mit der Funktion getPitch()
die Tonhöhenerkennung:
function getPitch()
{
pitch.getPitch(function(err, frequency) { //
starte mit dem Modell pitch die Tonhöhenerkennung und gebe sowohl
Fehlermeldungen (err) als auch Frequenzen aus (frequency)
if (frequency) { // wenn eine Frequenz
ausgegeben wird...
tonhoehe = round(frequency*10)/10; // weise
sie mit einer Stelle hinter dem Komma der
// Variablen tonhoehe zu.
} else { // in allen anderen Fällen
...
tonhoehe = 0; // setze den Wert der Frequenz
in tonhoehe auf 0 (Hz)
}
})
}
In der draw-Funktion
werden dann die erfassten Frequenzen ausgegeben und können dann beliebig
weiterverarbeitet werden, z.B.:
function draw(){
background(255,255,255); // weißer
Hintergrund einstellen
stroke(150,150,150); strokeWeight(1); //
graue Strichfarbe mit Liniendicke 1
// einstellen
line(0,0,0,400);line(0,400,800,400); //
X- und Y-Achse für Koordinatensystem anlegen.
for(i=0; i<400; i=i+20){ // in 20er
Schritten waagerechte Linien hochzählen
line(0,i,10,i); line(70,i,800,i); // und
erstellen
text(2000-(i*5) + " Hz", 14,i+4); //
Achsen beschriften
}
if (modell_geladen ==1){ // sobald das
Modell geladen ist
getPitch(); // rufe mit jedem Durchlauf
die Tonhöhenerkennung auf
text("aktuelle Frequenz: " + tonhoehe + " Hz",600,10);
// gebe die in der
// Variablen tonhoehe erfasste Frequenz aus
} else {
text("Pitch-Detection-Modell lädt noch ...",600,10); //
gebe aus, dass das
// Tonhöhenerkennungsmodell noch geladen wird
}
if (pitch_array.length<800){ // erzeuge
ein fließendes Array mit 800 Einträgen: Solange // weniger
als 800 Einträge vorhanden sind:
pitch_array.push(tonhoehe); // füge
die erkannte Frequenz am Ende des Arrays hinzu
} else { // sonst (sobald 800 Einträge
vorhanden sind):
pitch_array.push(tonhoehe); // füge
die erkannte Frequenz am Ende des Arrays hinzu
pitch_array.shift(); // und entferne die
erste Frequenz am Anfang des Arrays
}
stroke(150,50,50); strokeWeight(2); //
wähle dunkelrot und eine Liniendicke von 2
// Pixeln
beginShape(POINTS); // beginne eine Kurve
zu zeichnen ...
for(i=0; i<pitch_array.length; i++){ //
über alle Punkte des Arrays
pitch_angepasst = map(pitch_array[i],0,2000,400,0); //
passe die erhobenen
// Frequenzen an die Maße des Canvas an
vertex(i,pitch_angepasst); // setze für
jede erhobene Frequenz einen Punkt
}
endShape(); // beende die Kurve
sinuston.freq(tonhoehe); // gebe zur Kontrolle
einen Sinuston in gleicher Höhe aus.
}
Insgesamt
sieht das Script dann folgendermaßen aus:
<script src="header/ml5.min_0.12.2.js"></script>
<script src="header/p5.js"></script>
<script>
var audioContext;
var mic;
var pitch;
var tonhoehe = 0;
var modell_geladen = 0;
var pitch_array=[];
function setup() {
var container = createCanvas(800,400);
container.parent('p5container');
audioContext = getAudioContext();
mic = new p5.AudioIn();
mic.start(startPitch);
sinuston = new p5.SinOsc();
sinuston.start();
sampleRate(60);
}
function startPitch()
{
pitch = ml5.pitchDetection('./model_pitch/', audioContext , mic.stream,
modelLoaded);
}
function modelLoaded()
{
modell_geladen = 1;
getPitch();
}
function getPitch()
{
pitch.getPitch(function(err, frequency) {
if (frequency) {
tonhoehe = round(frequency*10)/10;
} else {
tonhoehe = 0;
}
})
}
function draw(){
background(255,255,255);
stroke(150,150,150); strokeWeight(1);
line(0,0,0,400);line(0,400,800,400);
for(i=0; i<400; i=i+20){
line(0,i,10,i); line(70,i,800,i);
text(2000-(i*5) + " Hz", 14,i+4);
}
if (modell_geladen ==1){
getPitch();
text("aktuelle Frequenz: " + tonhoehe + " Hz",600,10);
} else {
text("Pitch-Detection-Modell lädt noch ...",600,10);
}
if (pitch_array.length<800){
pitch_array.push(tonhoehe);
} else {
pitch_array.push(tonhoehe);
pitch_array.shift();
}
stroke(150,50,50); strokeWeight(2);
beginShape(POINTS);
for(i=0; i<pitch_array.length; i++){
pitch_angepasst = map(pitch_array[i],0,2000,400,0);
vertex(i,pitch_angepasst);
}
endShape();
sinuston.freq(tonhoehe);
}
</script>
<DIV id="p5container"
style="width:800;border: 1px solid #333;box-shadow: 8px 8px 5px
#444;padding: 8px 12px;background-color:ffffff"></DIV>
|