WhistleBloom / frontend /audio_features.js
ZhaoJingzi's picture
Upload 27 files
a1772f6 verified
Raw
History Blame Contribute Delete
1.35 kB
window.WhistleCoach = window.WhistleCoach || {};
window.WhistleCoach.AudioFeatures = (() => {
const clamp = (value, minimum = 0, maximum = 1) =>
Math.min(maximum, Math.max(minimum, value));
function toFeaturePayload(audio, stableDurationMs = 0) {
return {
rms_volume: Number(audio?.rms || 0),
airflow_score: Number(audio?.airflow || 0),
peak_frequency_hz: Number(audio?.peakFrequency || 0),
pitch_stability_score: Number(audio?.tone || 0),
stable_duration_ms: Math.round(stableDurationMs || 0),
};
}
function classifyAudio(features) {
if (features.airflow_score < 0.32) return "no_airflow";
if (features.pitch_stability_score < 0.58) return "airflow_no_tone";
if (features.stable_duration_ms >= 2000) return "stable_whistle";
return "tone_starting";
}
function pitchToMidi(frequencyHz) {
if (!frequencyHz || frequencyHz <= 0) return null;
return Math.round(69 + 12 * Math.log2(frequencyHz / 440));
}
function midiToName(midi) {
if (midi === null) return "";
const names = ["C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B"];
return `${names[((midi % 12) + 12) % 12]}${Math.floor(midi / 12) - 1}`;
}
return {
toFeaturePayload,
classifyAudio,
pitchToMidi,
midiToName,
};
})();