Audio
Features ermitteln mit Essentia.js
Seit 2021
stehen die Möglichkeiten von Essentia auch für Javascript
zur Verfügung. Mit mehr als 200 Feature-Algorithmen ist es mit
Abstand die größte Audio Feature Library, die in Javascript
zu finden ist.
Anders
als andere Javascript Libraries ist sie weniger für den Echtzeit-Betrieb
ausgelegt, sondern eher für die Berechnung der Audio Features im
Hintergrund. Für ihre Verwendung benötigt man mindestens 3
Libraries, die in den Header der Seite eingebunden werden müssen:
Auf der
Seite selbst sollte im Body ein Audio-Player eingebunden sein, mit dem
man sich das zu analysierende Klangbeispiel auch anhören kann:
<audio id="audioPlayer"
controls style="width:100px">
<source id="audio-source" src="" type="audio/mp3"
/>
</audio>
Darüber
hinaus benötigt man noch einen Button, mit dem man den Analysevorgang
über die Funktion featureExtractor();
starten kann, z.B.
<button type="button"
name="Signal Analysis" onclick="featureExtractor();">
Start Signal Analysis</button>
sowie einen
Bereich für die Ausgabe des Ergebnisses, z.B.
<span id="datenausgabe">
</span>
und natürlich
ein Klangbeispiel zum Analysieren, z.B. eine Fagott-Tonleiter
.
Danach
legt man mit <script defer>
.. </script> einen Scriptbereich an, in dem die Analyse
ausgeführt wird (das defer
bedeutet, dass das Script mit der Ausführung wartet, bis alle Teile
der Seite geladen und geparst sind).
In diesem
Bereich bestimmt man zunächst die auf jeden Fall benötigten
Variablen und legt einen Audio-Context fest:
<script defer>
//--------------------------------------------------------------------------
// grundlegende Variablen definieren
//--------------------------------------------------------------------------
let essentia; // Variable, über die alles
abgewickelt wird
let audioURL = "klang/fagott_toene.mp3";
// Klangbeispiel
let audioData; //Variable für die Wellenform
des Klangbeispiels
//--------------------------------------------------------------------------
// Audio-Context
für alle Browsertypen erzeugen
//--------------------------------------------------------------------------
const AudioContext = window.AudioContext || window.webkitAudioContext;
let audioCtx = new AudioContext();
</script>
Als nächsten
Schritt bestimmt man die Funktion featureExtractor()
{...}.
Innerhalb
dieser werden alle gewünschten Audio Features extrahiert.
In dieser Funktion wird zunächst via essentia.getAudioChannelDataFromURL(...)
die Wellenform aus der Audiodatei an die Variable audioData
übergeben:
<script defer>
//--------------------------------------------------------------------------
// grundlegende Variablen definieren
//--------------------------------------------------------------------------
let essentia; // Variable, über die alles
abgewickelt wird
let audioURL = "klang/fagott_toene.mp3";
// Klangbeispiel
let audioData; //Variable für die Wellenform
des Klangbeispiels
//--------------------------------------------------------------------------
// Audio-Context
für alle Browsertypen erzeugen
//--------------------------------------------------------------------------
const AudioContext = window.AudioContext || window.webkitAudioContext;
let audioCtx = new AudioContext();
//--------------------------------------------------------------------------
// featureExtractor Funktion starten
//--------------------------------------------------------------------------
async function featureExtractor() {
audioData = await essentia.getAudioChannelDataFromURL(audioURL,
audioCtx, 0); // übergebe die Daten von der
Audio-Datei beschrieben in
// audioURL innerhalb des Audio-Context audioCtx an die Variable
// AudioData und verwende dabei den linken Kanal (0)
}
</script>
Danach
hat man zwei Möglichkeiten zur Feature Extraktion:
Man kann
Audio Features mit Hilfe eines Vectors der Audiodaten essentia.arrayToVector(audioData)
aus der Datei extrahieren oder über essentia.FrameGenerator(...)
eine frame-weise Extraktion der Audiodaten ausformulieren:
Audio
Features mit Hilfe von essentia.arrayToVector(audioData)
extrahieren
Die meisten
(eindimensionalen) Features lassen sich sehr komfortabel mit Hilfe von
essentia.arrayToVector(audioData)
extrahieren. Hier gibt es zum einen Einzelwerte für ein ganzes
Stück, die in einzelnen Variablen extrahiert werden können,
wie z.B. Dauer, Einschwingzeit, RMS oder Danceability.
Zum anderen
gibt es Serien von aufeinanderfolgenden Werten, die in Arrays zurückgegeben
werden, wie z.B. Tonhöhen oder spektrale Features.
Darüber
hinaus gibt es auch Algorithmen-Sammlungen, mit deren Hilfe man auf
einen Schlag gleich ganz viele Features extrahieren kann. Vor allem
fünf Algorithmen-Sammlungen sind hier sehr spannend:
essentia.LowLevelSpectralEqloudExtractor();
// spektrale Eigenschaften extrahieren
essentia.LowLevelSpectralExtractor();
// spektrale Eigenschaften extrahieren
essentia.RhythmDescriptors(); // Rhythmische Eigenschaften
extrahieren
essentia.TonalExtractor(); //Tonart,
Tongeschlecht etc. extrahieren
essentia.KeyExtractor();
// Tonart, Tongeschlecht etc. extrahieren
Um die
eindimensionalen Features zu extrahieren, müssen die Audiodaten
in der Funktion featureExtractor(){...}
via essentia.arrayToVector(audioData);
in einen Vektor umgewandelt werden, über den dann die Variablen
erhoben werden können.
Die Funktion bis zur Umwandlung in einen Vektor sieht dann folgendermaßen
aus:
<script defer>
//--------------------------------------------------------------------------
// grundlegende Variablen definieren
//--------------------------------------------------------------------------
let essentia; // Variable, über die alles
abgewickelt wird
let audioURL = "klang/fagott_toene.mp3";
// Klangbeispiel
let audioData; //Variable für die Wellenform
des Klangbeispiels
//--------------------------------------------------------------------------
// Audio-Context
für alle Browsertypen erzeugen
//--------------------------------------------------------------------------
const AudioContext = window.AudioContext || window.webkitAudioContext;
let audioCtx = new AudioContext();
//--------------------------------------------------------------------------
// featureExtractor Funktion starten
//--------------------------------------------------------------------------
async function featureExtractor() {
audioData = await essentia.getAudioChannelDataFromURL(audioURL,
audioCtx, 0); // übergebe die Daten von der
Audio-Datei beschrieben in
// audioURL innerhalb des Audio-Context audioCtx an die Variable
// AudioData und verwende dabei den linken Kanal (0)
//--------------------------------------------------------------------
// Umwandlung der Audio-Daten in einen Vektor
//--------------------------------------------------------------------
const audioVector
= essentia.arrayToVector(audioData); // in der
Variablen audioVector
// ist die Wellenform als Vektor hinterlegt. Hieraus können nun
alle möglichen
// Informationen ermittelt werden.
//
[...] hier würde dann die Auswertung der einzelnen Audio-Features
stehen, z.B.
// let audio_duration = essentia.Duration(audioVector).duration;
// let onsetRate = essentia.OnsetRate(audioVector).onsetRate;
// let rms = essentia.RMS(audioVector).rms;
// etc.
}
</script>
Auf der
Grundlage des audioVector
lassen sich einzelne (grundlegende) Variablen sehr schnell ermitteln,
z.B.
let audio_duration
= essentia.Duration(audioVector).duration;
// Dauer des Beispiels wird an die Variable audio_duration
übergeben
let onsetRate = essentia.OnsetRate(audioVector).onsetRate;
// Onset-Rate des Beispiels wird an die Variable
onsetRate übergeben
let rms = essentia.RMS(audioVector).rms;
// RMS-Pegel des Beispiels wird an die Variable
rms übergeben
let leq = essentia.Leq(audioVector).leq;
// Leq (equivalent Sound Level in dB)
des Beispiels wird an die Variable leg übergeben
let loudness = essentia.Loudness(audioVector).loudness;
// Lautheit des Beispiels wird an die Variable
loudness
übergeben
let loudnessVickers = essentia.LoudnessVickers(audioVector).loudness;
// Lautheit nach Vickers des Beispiels wird an
die Variable loudnessVickers
übergeben
let gain = essentia.ReplayGain(audioVector, 44100).replayGain;
// Replay Gain [distance to suitable replay level
(~-31dB)] wird an die Variable gain
// übergeben
Bei anderen
Variablen müssen noch zusätzliche Informationen über
die Samplerate, den Abstand der Frames (hopSize) oder die Breite der
Frames (frameSize) hinzugefügt werden,
wie z.B. bei BPM oder High Level Features wie Danceability o.ä.,
z.B.:
let bpm =
essentia.PercivalBpmEstimator(audioVector,
1024, //frame size for the analysis of the input
signal
2048, //frame size for the analysis of the Onset
Strength Signal
128, //hop size for the analysis of the input
signal
128, //hop size for the analysis of the Onset
Strength Signal
210, //maximum BPM to detect
50, //minimum BPM to detect
44100 //the sampling rate of the audio signal
[Hz]
).bpm; //BPM des Beispiels wird an die Variable
bpm übergeben
let danceability
= essentia.Danceability( audioVector,
8800, //maxTau, maximum segment length to consider
[ms]
310, //minTau, minimum segment length to consider
[ms]
44100, //SampleRate [Hz]
1.1 //tauMultiplier, multiplier to increment from
min to max tau
).danceability; // Danceability
des Beispiels wird an die Variable danceability
// übergeben
let intensity
= essentia.Intensity(audioVector,
44100 //SampleRate [Hz]
).intensity;
//
Intensität des Beispiels wird an die Variable intensity
übergeben
Arrays
mit Audio Features werden auf eine ähnliche Art und Weise erhoben,
nur muss der ermittelte Vektor wieder in ein Array zurückverwandelt
werden, wie z.B. bei der Ermittlung des Tonhöhenverlaufs oder des
Vibratos:
let pitchMelodia =
essentia.PitchMelodia(audioVector,
// Tonhöhe ermitteln über den Melodia-Algorithmus
10, // binResolution, Genauigkeit der Tonhöheneinschätzung
in Cents
3, // filterIterations, Anzahl der Filterungen,
um Oktavverwechslungen auszuschließen
2048, // frameSize
false, // guessUnvoiced,
// ob bei nicht-erkennbarem Pitch dennoch einer erkannt werden soll
0.8, // harmonicWeight, Gewichtung zwischen zwei
aufeinanderfolgenden Teiltönen
128, // hopSize
);
let pitchMelodia_array = essentia.vectorToArray(pitchMelodia.pitch);
// Tonhöhen-Vektor in ein Array umwandeln.
Über
das Tonhöhen-Ergebnis pitchMelodia.pitch
lässt sich z.B. das Vibrato ermitteln:
let vibratoOut = essentia.Vibrato(pitchMelodia.pitch,
500, // maxExtend, stärkster erwarteter Umfang
des Vibratos in Cents
8, // maxFrequency, größte erwartete
Vibrato-Frequenz in Hz
50, // minExtend, kleinster erwarteter Umfang
des Vibratos in Cents
1, // minFrequency, kleinste erwartete Vibrato-Frequenz
in Hz
344.53125 // Entdeckungs-Samplerate für das
Vibrato: 44100 Hz / 128 hopSize = 344,53125
); //Einstellungen möglichst nicht verändern!!!
vibratoHz_array=essentia.vectorToArray(vibratoOut.vibratoFrequency);
// Vibrato-Frequenzen in ein Array umwandeln
vibratoDepth_array=essentia.vectorToArray(vibratoOut.vibratoExtend);
// Vibrato-Tiefe in ein Array umwandeln
oder die
Tonhöhen-Kontur:
let pitchContour=
essentia.PitchContourSegmentation(pitchMelodia.pitch, audioVector,
128, // hopSize
0.1, // minDuration, kürzeste Notendauer
in s
60, // pitchDistanceThreshold, ab wann ein Tonhöhenunterschied
erkannt wird (in Cents)
-2, // rmsThreshold, ab welcher Schwelle ein Tonwechsel
erkannt wird
44100, //Samplerate
440 // Stimmtonhöhe
);
let pitchContourOnset_array = essentia.vectorToArray(pitchContour.onset);
// die gefundenen Onsets in ein Array umwandeln
let pitchContourDuration_array = essentia.vectorToArray(pitchContour.duration);
// die gefundenen Tondauern in ein Array umwandeln
let pitchContourMIDIpitch_array = essentia.vectorToArray(pitchContour.MIDIpitch);
// die gefundenen MIDI-Tonhöhen in ein Array
umwandeln
In diesem
Zusammenhang ist auch das Feature AfterMaxToBeforeMaxEnergyRatio()
interessant, das anzeigt, ob eine Melodielinie eher absteigend oder
eher aufsteigend verläuft:
let ascending_or_falling_pitch=
essentia.AfterMaxToBeforeMaxEnergyRatio(pitchMelodia.pitch).afterMaxToBeforeMaxEnergyRatio;
// 0 bis 1 bedeutet,
die Melodie ist eher aufsteigend.
// Ein Wert >1 bedeutet, die Melodie ist eher absteigend.
Spektrale
Eigenschaften und Eigenschaften von Akkorden und Tonalität lassen
sich am schnellsten über die "Sammelpackungen" LowLevelSpectralEqloudExtractor();
LowLevelSpectralExtractor();
und KeyExtractor(); ermitteln:
Grundlegende
spektrale Features wie Spectral Centroid, Dissonance, Spectral Kurtosis
etc. erhält man in zwei Schritten als Arrays über den LowLevelSpectralEqloudExtractor():
Erst wird über
LowLevelSpectralEqloudExtractor() die
spektrale Information aus dem audioVector
in ein Objekt geschrieben
(d.h. eine Art Sammelvariable, die alle möglichen Variablen-Typen
wie Arrays, Strings, Floats etc. beinhalten kann).
Dann werden aus diesem die Vektoren der einzelnen Features ermittelt,
die dann via vectorToArray()
jeweils in ein Array umgewandelt werden:
//
1. Ermitteln der spektralen Informationen pro Frame
LowLevelSpectralEq_object = essentia.LowLevelSpectralEqloudExtractor(
audioVector,
2048, // frameSize
128, // hopSize
44100, // SampleRate [Hz]
);
//
2. Arrays zu den einzelnen Audio Features aus den spektralen Informationen
ermitteln
let dissonance_array
= essentia.vectorToArray(LowLevelSpectralEq_object.dissonance);
let spectral_centroid_array = essentia.vectorToArray(LowLevelSpectralEq_object.spectral_centroid);
let spectral_kurtosis_array = essentia.vectorToArray(LowLevelSpectralEq_object.spectral_kurtosis);
let spectral_skewness_array = essentia.vectorToArray(LowLevelSpectralEq_object.spectral_skewness);
let spectral_spread_array = essentia.vectorToArray(LowLevelSpectralEq_object.spectral_spread);
Dies geht
noch viel ausgeprägter mit dem LowLevelSpectralExtractor():
//
1. Ermitteln der spektralen Informationen pro Frame
LowLevelSpectralEx_object
= essentia.LowLevelSpectralExtractor( audioVector,
2048, // frameSize
128, // hopSize
44100, //Samplerate [[Hz]
);
//
2. Arrays zu den einzelnen Audio Features aus den spektralen Informationen
ermitteln
let barkbands_kurtosis_array
= essentia.vectorToArray(LowLevelSpectralEx_object.barkbands_kurtosis);
let barkbands_skewness_array = essentia.vectorToArray(LowLevelSpectralEx_object.barkbands_skewness);
let barkbands_spread_array = essentia.vectorToArray(LowLevelSpectralEx_object.barkbands_spread);
let hfc_array = essentia.vectorToArray(LowLevelSpectralEx_object.hfc);
let pitch_array = essentia.vectorToArray(LowLevelSpectralEx_object.pitch);
let pitch_instantaneous_confidence_array = essentia.vectorToArray(LowLevelSpectralEx_object.pitch_instantaneous_confidence);
let pitch_salience_array = essentia.vectorToArray(LowLevelSpectralEx_object.pitch_salience);
let silence_rate_20dB_array = essentia.vectorToArray(LowLevelSpectralEx_object.silence_rate_20dB);
let silence_rate_30dB_array = essentia.vectorToArray(LowLevelSpectralEx_object.silence_rate_30dB);
let silence_rate_60dB_array = essentia.vectorToArray(LowLevelSpectralEx_object.silence_rate_60dB);
let spectral_complexity_array = essentia.vectorToArray(LowLevelSpectralEx_object.spectral_complexity);
let spectral_crest_array = essentia.vectorToArray(LowLevelSpectralEx_object.spectral_crest);
let spectral_decrease_array = essentia.vectorToArray(LowLevelSpectralEx_object.spectral_decrease);
let spectral_energy_array = essentia.vectorToArray(LowLevelSpectralEx_object.spectral_energy);
let spectral_energyband_low_array = essentia.vectorToArray(LowLevelSpectralEx_object.spectral_energyband_low);
let spectral_energyband_middle_low_array = essentia.vectorToArray(LowLevelSpectralEx_object.spectral_energyband_middle_low);
let spectral_energyband_middle_high_array = essentia.vectorToArray(LowLevelSpectralEx_object.spectral_energyband_middle_high);
let spectral_energyband_high_array = essentia.vectorToArray(LowLevelSpectralEx_object.spectral_energyband_high);
let spectral_flatness_db_array = essentia.vectorToArray(LowLevelSpectralEx_object.spectral_flatness_db);
let spectral_flux_array = essentia.vectorToArray(LowLevelSpectralEx_object.spectral_flux);
let spectral_rms_array = essentia.vectorToArray(LowLevelSpectralEx_object.spectral_rms);
let spectral_rolloff_array = essentia.vectorToArray(LowLevelSpectralEx_object.spectral_rolloff);
let spectral_strongpeak_array = essentia.vectorToArray(LowLevelSpectralEx_object.spectral_strongpeak);
let zerocrossingrate_array = essentia.vectorToArray(LowLevelSpectralEx_object.zerocrossingrate);
let inharmonicity_array = essentia.vectorToArray(LowLevelSpectralEx_object.inharmonicity);
let oddtoevenharmonicenergyratio_array = essentia.vectorToArray(LowLevelSpectralEx_object.oddtoevenharmonicenergyratio);
Oder auch
mit vielen Voreinstellungen (am besten so lassen) über den KeyExtractor(),
der anstelle von Arrays einzelne Variablen ausgibt:
let key_object = essentia.KeyExtractor(audioVector,
// Tonart ermitteln
true, // shifts a pcp to the nearest tempered
bin
4096, // the framesize for computing tonal features
4096, // the hopsize for computing tonal features
12, // the size of the output HPCP (must be a
positive nonzero multiple of 12)
3500, // max frequency to apply whitening to [Hz]
60, // the maximum number of spectral peaks
25, // min frequency to apply whitening to [Hz]
0.2, // pcp bins below this value are set to 0
'bgate', // the type of polyphic profile to use
for correlation calculation
44100, // the sampling rate of the audio signal
[Hz]
0.0001, // the threshold for the spectral peaks
440, // the tuning frequency of the input signal
'cosine', //type of weighting function for determining
frequency contribution
'hann' //the window type, which can be 'hamming',
'hann', 'triangular', 'square' or
// 'blackmanharrisXX'
);
tonart = key_object.key;
tongeschlecht= key_object.scale;
tonart_confidence = key_object.strength;
Am Ende
dieses Vorgangs liegen die Features als einzelne Variablen oder Arrays
vor, so dass man die Einzelwerte in den Arrays über eine for-Schleife
aus dem jeweiligen Array herausholen und in der Datenausgabe auf der
Seite darstellen kann, z.B. für die Tonhöhen oder fürs
Vibrato:
let pitchMelodia_daten
= ""; // Variable für die Sammlung
der Daten definieren
for (i=0;i<pitchMelodia_array.length; i++){ //
For-Schleife über das Pitch-Array starten
pitchMelodia_daten = pitchMelodia_daten + pitchMelodia_array[i].toFixed(1)+
", ";
} // für jeden Eintrag die Frequenz herausholen
und an pitchMelodia_daten anhängen
pitchMelodia_ausgabe="pitchMelodia_array=["+pitchMelodia_daten+"];";
// die gesammelten Daten an die Variable pitchMelodia_ausgabe
übergeben.
let vibratoHz_daten
= ""; // Variable für die Sammlung
der Daten definieren
for (i=0;i<vibratoHz_array.length; i++){ //
For-Schleife über das VibratoHz-Array starten
vibratoHz_daten = vibratoHz_daten + vibratoHz_array[i]+ ", ";
} // für jeden Eintrag die Frequenz herausholen
und an VibratoHz_daten anhängen
vibratoHz_ausgabe="VibratoHz_array=["+vibratoHz_daten+"];";
// die gesammelten Daten an die Variable VibratoHz_ausgabe
übergeben.
let vibratoDepth_daten
= ""; // Variable für die Sammlung
der Daten definieren
for (i=0;i<vibratoDepth_array.length; i++){ //
For-Schleife über das Vibrato-Array starten
vibratoDepth_daten = vibratoDepth_daten + vibratoDepth_array[i]+ ",
";
} // für jeden Eintrag die Modulationstiefe
herausholen und an VibratoDepth_daten anhängen
vibratoDepth_ausgabe="vibratoDepth_array=["+vibratoDepth_daten+"];";
// die gesammelten Daten an die Variable VibratoDepth_ausgabe
übergeben.
document.getElementById("datenausgabe").innerHTML=
pitchMelodia_ausgabe+"<br><br><br>"+vibratoHz_ausgabe+"<br><br><br>"+vibratoDepth_ausgabe;
// Den Inhalt der Variablen pitchMelodia_ausgabe,
vibratoHz_ausgabe
und vibratoDepth_ausgabe
im Feld Datenausgabe auf der Webseite ausgeben.
Nun
muss nur noch die featureExtractor()
{ ...}-Funktion mit essentia.algorithms.delete();
abgeschlossen werden und das
WASM-Modul hinzugefügt werden, welches gestartet wird, nachdem
die Seite vollständig geladen und geparst ist.
Das
gesamte Script für die Erfassung der Tonhöhen und das
Vibratos sähe dann z.B. folgendermaßen aus:
<!DOCTYPE html>
<html>
<head>
<script src="header/jquery.min.js"></script>
<!-- vereinfacht das Javascript Handling, hat
sonst nichts mit Essentia.js zu tun -->
<script src="https://cdn.jsdelivr.net/npm/essentia.js@0.1.0/dist/essentia-wasm.web.js"></script>
<!-- Web Assembly Modul (WASM) = Backend, um
die Standard C++ Algorithmen von Essentia verwenden zu können -->
<script src="header/essentia.js-core.js"></script>
<!-- die eigentliche Essentia Bibliothek-->
<script defer>
//--------------------------------------------------------------------------
// grundlegende Variablen definieren
//--------------------------------------------------------------------------
let essentia; // Variable, über die alles
abgewickelt wird
let audioURL = "klang/fagott_toene.mp3";
// Klangbeispiel
let audioData; //Variable für die Wellenform
des Klangbeispiels
//--------------------------------------------------------------------------
// Audio-Context
für alle Browsertypen erzeugen
//--------------------------------------------------------------------------
const AudioContext = window.AudioContext || window.webkitAudioContext;
let audioCtx = new AudioContext();
//--------------------------------------------------------------------------
// featureExtractor Funktion starten
//--------------------------------------------------------------------------
async function featureExtractor() {
audioData = await essentia.getAudioChannelDataFromURL(audioURL,
audioCtx, 0); // übergebe die Daten von der
Audio-Datei beschrieben in
// audioURL innerhalb des Audio-Context audioCtx an die Variable
// AudioData und verwende dabei den linken Kanal (0)
//--------------------------------------------------------------------
// Umwandlung der Audio-Daten in einen Vektor
//--------------------------------------------------------------------
const audioVector
= essentia.arrayToVector(audioData); // in der
Variablen audioVector
// ist die Wellenform als Vektor hinterlegt. Hieraus können nun
alle möglichen
// Informationen ermittelt werden.
//--------------------------------------------------------------------
// Ermitteln der Audio Features
//--------------------------------------------------------------------
let pitchMelodia =
essentia.PitchMelodia(audioVector,
// Tonhöhe ermitteln über den Melodia-Algorithmus
10, // binResolution, Genauigkeit der Tonhöheneinschätzung
in Cents
3, // filterIterations, Anzahl der Filterungen,
um Oktavverwechslungen auszuschließen
2048, // frameSize
false, // guessUnvoiced,
// ob bei nicht-erkennbarem Pitch dennoch einer erkannt werden soll
0.8, // harmonicWeight, Gewichtung zwischen zwei
aufeinanderfolgenden Teiltönen
128, // hopSize
);
let pitchMelodia_array = essentia.vectorToArray(pitchMelodia.pitch);
// Tonhöhen-Vektor in ein Array umwandeln.
let vibratoOut = essentia.Vibrato(pitchMelodia.pitch,
500, // maxExtend, stärkster erwarteter Umfang
des Vibratos in Cents
8, // maxFrequency, größte erwartete
Vibrato-Frequenz in Hz
50, // minExtend, kleinster erwarteter Umfang
des Vibratos in Cents
1, // minFrequency, kleinste erwartete Vibrato-Frequenz
in Hz
344.53125 // Entdeckungs-Samplerate für das
Vibrato: 44100 Hz / 128 hopSize = 344,53125
); //Einstellungen möglichst nicht verändern!!!
vibratoHz_array=essentia.vectorToArray(vibratoOut.vibratoFrequency);
// Vibrato-Frequenzen in ein Array umwandeln
vibratoDepth_array=essentia.vectorToArray(vibratoOut.vibratoExtend);
// Vibrato-Tiefe in ein Array umwandeln
//--------------------------------------------------------------------
// Ausgabe der Audio Features
//--------------------------------------------------------------------
let pitchMelodia_daten
= ""; // Variable für die Sammlung
der Daten definieren
for (i=0;i<pitchMelodia_array.length; i++){ //
For-Schleife über das Pitch-Array starten
pitchMelodia_daten = pitchMelodia_daten + pitchMelodia_array[i].toFixed(1)+
", ";
} // für jeden Eintrag die Frequenz herausholen
und an pitchMelodia_daten anhängen
pitchMelodia_ausgabe="pitchMelodia_array=["+pitchMelodia_daten+"];";
// die gesammelten Daten an die Variable pitchMelodia_ausgabe
übergeben.
let vibratoHz_daten
= ""; // Variable für die Sammlung
der Daten definieren
for (i=0;i<vibratoHz_array.length; i++){ //
For-Schleife über das VibratoHz-Array starten
vibratoHz_daten = vibratoHz_daten + vibratoHz_array[i]+ ", ";
} // für jeden Eintrag die Frequenz herausholen
und an VibratoHz_daten anhängen
vibratoHz_ausgabe="VibratoHz_array=["+vibratoHz_daten+"];";
// die gesammelten Daten an die Variable VibratoHz_ausgabe
übergeben.
let vibratoDepth_daten
= ""; // Variable für die Sammlung
der Daten definieren
for (i=0;i<vibratoDepth_array.length; i++){ //
For-Schleife über das Vibrato-Array starten
vibratoDepth_daten = vibratoDepth_daten + vibratoDepth_array[i]+ ",
";
} // für jeden Eintrag die Modulationstiefe
herausholen und an VibratoDepth_daten anhängen
vibratoDepth_ausgabe="vibratoDepth_array=["+vibratoDepth_daten+"];";
// die gesammelten Daten an die Variable VibratoDepth_ausgabe
übergeben.
document.getElementById("datenausgabe").innerHTML=
pitchMelodia_ausgabe+"<br><br><br>"+vibratoHz_ausgabe+"<br><br><br>"+vibratoDepth_ausgabe;
// Den Inhalt der Variablen pitchMelodia_ausgabe,
vibratoHz_ausgabe
und vibratoDepth_ausgabe
im Feld Datenausgabe auf der Webseite ausgeben.
//--------------------------------------------------------------------------
// featureExtractor Funktion beenden
//--------------------------------------------------------------------------
essentia.algorithms.delete();
}
//--------------------------------------------------------------------------
// WasmModul
einbinden
//--------------------------------------------------------------------------
$(document).ready(function()
{ // Funktion, die startet, sobald Seite vollständig
// geladen und alles geparst und ausgeführt ist:
EssentiaWASM().then(async
function(WasmModule) { // starte das WasmModule
let player = document.getElementById("audioPlayer"); //
spreche den Audio-Player an
player.src = audioURL; // weise dem Audio-Player
die Klangdabei zu
player.load(); // lade die Klangdatei in den Player
essentia = new Essentia(WasmModule); // weise
das WasmModule der Variablen essentia zu,
// so dass darüber alle Funktionen mit den C++-Algorithmen ausgeführt
werden können
});
});
</script>
</head>
<body>
<audio id="audioPlayer" controls style="width:100px">
<source id="audio-source" src="" type="audio/mp3"
/></audio><br>
<button type="button" name="Signal Analysis"
onclick="featureExtractor();">
Start Signal Analysis</button><br>
<span id="datenausgabe"> </span>
</body>
</html>
Ähnlich
sähe dann auch die Ermittung und Datenausgabe für die anderen
Features aus.
Audio
Features mit Hilfe des essentia.FrameGenerator(...)
extrahieren
Eine frame-weise
Extrahierung lohnt sich bei Audio Features mit mehrdimensionalen Arrays,
wie MFCCs, Barkbänder, Mel-Bänder, ERB-Bänder, Spectral
Peaks oder auch die stärkste Amplitude aus dem Spektrum ermitteln
(natürlich auch für FFTs).
Hier teilt
man über essentia.FrameGenerator(...)
das Audio zunächst in einzelne Zeitabschnitte auf, die dann via
essentia.Spectrum(essentia.Windowing(frames.get(i)).frame).spectrum
spektralanalysiert werden. Aus den Einzelspektren lassen sich dann pro
Frame die gewünschten MFCCs, Barkbänder, Mel-Bänder,
ERB-Bänder, Spectral Peaks ermitteln:
<script defer>
//--------------------------------------------------------------------------
// grundlegende Variablen definieren
//--------------------------------------------------------------------------
let essentia; // Variable, über die alles
abgewickelt wird
let audioURL = "klang/fagott_toene.mp3";
// Klangbeispiel
let audioData; //Variable für die Wellenform
des Klangbeispiels
//--------------------------------------------------------------------------
// Audio-Context
für alle Browsertypen erzeugen
//--------------------------------------------------------------------------
const AudioContext = window.AudioContext || window.webkitAudioContext;
let audioCtx = new AudioContext();
//--------------------------------------------------------------------------
// featureExtractor Funktion starten
//--------------------------------------------------------------------------
async function featureExtractor() {
audioData = await essentia.getAudioChannelDataFromURL(audioURL,
audioCtx, 0); // übergebe die Daten von der
Audio-Datei beschrieben in
// audioURL innerhalb des Audio-Context audioCtx an die Variable
// AudioData und verwende dabei den linken Kanal (0)
//--------------------------------------------------------------------
//Frameweise spetrale Extraction für zweidimensionale Daten
//--------------------------------------------------------------------
let mfccs_array
=[]; // Array für MFCCs anlegen
let barkbands_array =[]; // Array für Barkbänder
anlegen
let melbands_array =[]; // Array für Mel-Bänder
anlegen
let ERBbands_array =[]; // Array für ERB-Bänder
anlegen
let spectralPeaks_frequencies_array =[]; // Array
für Spektral Peaks (Hz) anlegen
let spectralPeaks_magnitudes_array =[]; // Array
für Spektral Peaks (Ampl.) anlegen
let strongestFrequency_array =[]; // Array für
stärkste Frequenz anlegen
const frames = essentia.FrameGenerator(audioData,
// FrameGenerator starten
1024, // frameSize = Größe der Frames
512 // hopSize = Abstand zwischen den Frames
);
for (var i=0; i<frames.size();
i++) { //Schleife über alle Frames laufen
lassen
spectrum_pro_frames_array = essentia.Spectrum(essentia.Windowing(frames.get(i)).frame).spectrum;
// Kernzeile, um
// über das jeweilige Frame mit einem bestimmten Fenster das Spektrum
pro Frame zu
// errechnen
barkbands = essentia.vectorToArray(essentia.BarkBands(spectrum_pro_frames_array).bands);
// Pro Frame die 27 Barkbänder in ein Array
schreiben. Die Bark Frequenzen sind: 0, 50,
// 100, 150, 200, 300, 400, 510, 630, 770, 920, 1080, 1270, 1480, 1720,
2000, 2320,
// 2700, 3150, 3700, 4400, 5300, 6400, 7700, 9500, 12000, 15500, 20500,
27000 Hz
barkbands_array.push(barkbands);
// die ermittelten Barkbänder dem barkband_array
//
hinzufügen.
mfccs = essentia.vectorToArray(essentia.MFCC(spectrum_pro_frames_array).mfcc);
// Pro Frame die 13 MFCCs in ein Array schreiben
mfccs_array.push(mfccs); // die ermittelten
MFCCs dem mfccs-array hinzufügen.
melbands = essentia.vectorToArray(essentia.MFCC(spectrum_pro_frames_array).bands);
// Pro Frame die 24 Mel-Bänder in ein Array
schreiben
melbands_array.push(melbands); // die ermittelten
24 Mel-Bänder dem melbands_array
// hinzufügen.
ERBbands = essentia.vectorToArray(essentia.ERBBands(spectrum_pro_frames_array).bands);
// Pro Frame die 40 ERB-Bänder in ein Array
schreiben
ERBbands_array.push(ERBbands); // die ermittelten
40 ERB-Bänder dem ERBbands_array
// hinzufügen.
spectralPeaks_frequencies = essentia.vectorToArray(essentia.SpectralPeaks(spectrum_pro_frames_array).frequencies);
// Pro Frame die Frequenzen von maximal 100 Spectral
Peaks in ein Array schreiben
spectralPeaks_frequencies_array.push(spectralPeaks_frequencies);
// die ermittelten
//
maxinal 100 stärksten Frequenzen
dem spectralPeaks_frequencies_array hinzufügen.
spectralPeaks_magnitudes = essentia.vectorToArray(essentia.SpectralPeaks(spectrum_pro_frames_array).magnitudes);
// Pro Frame die Amplituden von maximal 100 Spectral
Peaks in ein Array schreiben
spectralPeaks_magnitudes_array.push(spectralPeaks_magnitudes);
// die ermittelten
//
maxinal 100 stärksten Amplituden
dem spectralPeaks_magnitudes_array hinzufügen.
strongestFrequency = essentia.MaxMagFreq(spectrum_pro_frames_array).maxMagFreq;
// Pro Frame die stärkste Frequenz ermitteln
strongestFrequency_array.push(strongestFrequency);
// die ermittelte stärkste Frequenz dem strongestFrequency_array
hinzufügen
}
}
</script>
Am Ende
dieses Vorgangs liegen die Features als zweidimensionale Arrays vor,
so dass man die Einzelwerte über zwei for-Schleifen aus den Arrays
herausholen und in der Datenausgabe auf der Seite darstellen kann, z.B.
für die MFCCs:
//--------------------------------------------------------------------------
// Datenausgabe
am Beispiel der MFCCs
//--------------------------------------------------------------------------
mfccs_daten=""; // Variable für
die Textausgabe definieren
for (i=0; i<mfccs_array.length; i++){ // für
alle Frames des mfccs_array eine for-
// Schleife starten
mfccs_spalte=""; // Variable für
die Textausgabe pro Tabellenspalte definieren
for (j=0; j<mfccs_array[i].length; j++){ //
für jedes MFCC innerhalb eines Frames [i]
//
eine for-Schleife starten
mfccs_spalte=mfccs_spalte+mfccs_array[i][j].toFixed(8)+",";
// jedes MFCC [j] innerhalb eines Frames
[i] gerundet auf 8 Stellen ausgeben und mit einem Komma (",")
getrennt an die bisherigen Ergebnisse innerhalb dieser Zeile anhängen
}
mfccs_daten=mfccs_daten+mfccs_spalte+"<br>"; //
die vollständige Zeile mit allen Spalten mit einem Zeilenumbruch
an die vorangegangenen Zeilen anhängen.
}
mfccs_ausgabe="<b>MFCCs: </b><br>"+mfccs_daten;
// alle Daten an die Variable
// mfccs_ausgabe anhängen.
document.getElementById("datenausgabe").innerHTML=
mfccs_ausgabe; // Den Inhalt der Variablen mfccs_ausgabe
im Feld Datenausgabe auf der Webseite ausgeben.
Nun muss
nur noch die featureExtractor()
{ ...}-Funktion mit essentia.algorithms.delete();
abgeschlossen werden und das
WASM-Modul hinzugefügt werden, welches gestartet wird, nachdem
die Seite vollständig geladen und geparst ist.
Das gesamte
Script sähe für die frame-weise spetrale Extraction für
die MFCCs dann folgendermaßen aus:
<!DOCTYPE html>
<html>
<head>
<script src="header/jquery.min.js"></script>
<!-- vereinfacht das Javascript Handling, hat
sonst nichts mit Essentia.js zu tun -->
<script src="https://cdn.jsdelivr.net/npm/essentia.js@0.1.0/dist/essentia-wasm.web.js"></script>
<!-- Web Assembly Modul (WASM) = Backend, um
die Standard C++ Algorithmen von Essentia verwenden zu können -->
<script src="header/essentia.js-core.js"></script>
<!-- die eigentliche Essentia Bibliothek-->
<script defer>
//--------------------------------------------------------------------------
// grundlegende Variablen definieren
//--------------------------------------------------------------------------
let essentia; // Variable, über die alles
abgewickelt wird
let audioURL = "klang/fagott_toene.mp3";
// Klangbeispiel
let audioData; //Variable für die Wellenform
des Klangbeispiels
//--------------------------------------------------------------------------
// Audio-Context
für alle Browsertypen erzeugen
//--------------------------------------------------------------------------
const AudioContext
= window.AudioContext || window.webkitAudioContext;
let audioCtx = new AudioContext();
//--------------------------------------------------------------------------
// featureExtractor Funktion starten
//--------------------------------------------------------------------------
async function
featureExtractor() {
audioData = await essentia.getAudioChannelDataFromURL(audioURL,
audioCtx, 0);
// übergebe die Daten von der Audio-Datei beschrieben in
// audioURL innerhalb des Audio-Context audioCtx an die Variable
// AudioData und verwende dabei den linken Kanal (0)
//--------------------------------------------------------------------
//Frameweise spetrale Extraction starten
//--------------------------------------------------------------------
let mfccs_array
=[]; // Array für MFCCs anlegen
const frames = essentia.FrameGenerator(audioData,
// FrameGenerator starten
1024, // frameSize = Größe der Frames
512 // hopSize = Abstand zwischen den Frames
);
for (var i=0; i<frames.size();
i++) { //Schleife über alle Frames laufen
lassen
spectrum_pro_frames_array = essentia.Spectrum(essentia.Windowing(frames.get(i)).frame).spectrum;
// Kernzeile, um über das jeweilige Frame mit einem bestimmten
Fenster das Spektrum pro
// Frame zu errechnen
mfccs = essentia.vectorToArray(essentia.MFCC(spectrum_pro_frames_array).mfcc);
// Pro Frame die 13 MFCCs in ein Array schreiben
mfccs_array.push(mfccs); // die ermittelten
MFCCs dem mfccs-array hinzufügen.
}
//--------------------------------------------------------------------------
// Datenausgabe
am Beispiel der MFCCs
//--------------------------------------------------------------------------
mfccs_daten="";
// Variable für die Textausgabe definieren
for (i=0; i<mfccs_array.length; i++){ // für
alle Frames des mfccs_array eine for-
// Schleife starten
mfccs_spalte=""; // Variable für
die Textausgabe pro Tabellenspalte definieren
for (j=0; j<mfccs_array[i].length; j++){ //
für jedes MFCC innerhalb eines Frames [i]
//
eine for-Schleife starten
mfccs_spalte=mfccs_spalte+mfccs_array[i][j].toFixed(8)+",";
// jedes MFCC [j] innerhalb eines Frames
[i] gerundet auf 8 Stellen ausgeben und mit einem Komma (",")
getrennt an die bisherigen Ergebnisse innerhalb dieser Zeile anhängen
}
mfccs_daten=mfccs_daten+mfccs_spalte+"<br>"; //
die vollständige Zeile mit allen Spalten mit einem Zeilenumbruch
an die vorangegangenen Zeilen anhängen.
}
mfccs_ausgabe="<b>MFCCs: </b><br>"+mfccs_daten;
// alle Daten an die Variable
// mfccs_ausgabe anhängen.
document.getElementById("datenausgabe").innerHTML=
mfccs_ausgabe; // Den Inhalt der Variablen mfccs_ausgabe
im Feld Datenausgabe auf der Webseite ausgeben.
//--------------------------------------------------------------------------
// featureExtractor Funktion beenden
//--------------------------------------------------------------------------
essentia.algorithms.delete();
}
//--------------------------------------------------------------------------
// WasmModul
einbinden
//--------------------------------------------------------------------------
$(document).ready(function()
{ // Funktion, die startet, sobald Seite vollständig
// geladen und alles geparst und ausgeführt ist:
EssentiaWASM().then(async
function(WasmModule) { // starte das WasmModule
let player = document.getElementById("audioPlayer"); //
spreche den Audio-Player an
player.src = audioURL; // weise dem Audio-Player
die Klangdabei zu
player.load(); // lade die Klangdatei in den Player
essentia = new Essentia(WasmModule); // weise
das WasmModule der Variablen essentia zu,
// so dass darüber alle Funktionen mit den C++-Algorithmen ausgeführt
werden können
});
});
</script>
</head>
<body>
<audio id="audioPlayer" controls style="width:100px">
<source id="audio-source" src="" type="audio/mp3"
/></audio><br>
<button type="button" name="Signal Analysis"
onclick="featureExtractor();">
Start Signal Analysis</button><br>
<span id="datenausgabe"> </span>
</body>
</html>
Ähnlich
sähe dann auch die Ermittung und Datenausgabe für die anderen
Arrays wie
barkbands_array,
melbands_array,
ERBbands_array,
spectralPeaks_frequencies_array,
spectralPeaks_magnitudes_array,
strongestFrequency_array
etc. aus.
|