Web Audio APIで音程検出する仕組み: AnalyserNode・自己相関・FFT・セント計算
MDN Web Docs仕様に基づき、ブラウザだけで動くチューナーの内部構造を実装観点で解説します。
音声処理の全体パイプライン
getUserMedia({audio: true})
→ MediaStreamSource (AudioContext内のノード)
→ AnalyserNode (FFTサイズ4096・時間軸バッファ取得)
→ getFloatTimeDomainData(Float32Array)
→ 自己相関アルゴリズム (周期検出)
→ 放物線補間 (山の頂点を高精度推定)
→ 周波数(Hz) → MIDIノート番号 → 音名 + セント差AnalyserNodeの主要仕様
MDNのAnalyserNodeページに基づくと、主要プロパティは以下:
fftSize: FFTサイズ。2^5〜2^15(32〜32768)。本ツールは4096で時間/周波数分解能をバランスfrequencyBinCount: fftSize/2。周波数領域データのビン数smoothingTimeConstant: 0〜1。前フレームとのブレンド比。0で生データgetFloatTimeDomainData(array): 時間軸の波形をFloat32Arrayに書き込む
チューナー用途では時間軸データを使うためgetFloatTimeDomainDataが中心。FFTスペクトル(getFloatFrequencyData)は倍音と基本音の判別が苦手で、楽器の音程検出には向きません。
自己相関による周期検出
自己相関(autocorrelation)は、波形を時間方向にシフトした自分自身と掛け合わせて、ピークが出る位置を見つける手法です。基本周波数の周期分シフトしたとき相関値が最大になります。
function detectPitch(buf, sampleRate) {
const SIZE = buf.length;
let rms = 0;
for (let i = 0; i < SIZE; i++) rms += buf[i] * buf[i];
rms = Math.sqrt(rms / SIZE);
if (rms < 0.01) return -1; // 無音判定
const MAX_SAMPLES = Math.floor(SIZE / 2);
let bestOffset = -1, bestCorrelation = 0;
for (let offset = 8; offset < MAX_SAMPLES; offset++) {
let correlation = 0;
for (let i = 0; i < MAX_SAMPLES; i++) {
correlation += buf[i] * buf[i + offset];
}
if (correlation > bestCorrelation) {
bestCorrelation = correlation;
bestOffset = offset;
}
}
return sampleRate / bestOffset;
}サンプリングレートが48000Hzなら、bestOffset=120のとき検出周波数は48000/120=400Hz、つまり概ねG4(392Hz)〜G♯4(415Hz)の間と判定できます。
放物線補間で精度を上げる
自己相関ピークはサンプル単位の解像度しか持たないため、48000Hz/120サンプル=400Hzのような離散値しか取れません。 隣接サンプルの相関値y0, y1, y2を放物線フィッティングし、頂点位置x = (y0 - y2) / (2 × (y0 - 2y1 + y2))を使って小数オフセットを求めると、精度が10倍以上向上しセント表示に必要な分解能が得られます。
MIDIノート番号とセント計算
検出した周波数 f を音名に変換する標準式:
const ref = 440; // A4基準 const midiFloat = 69 + 12 * Math.log2(f / ref); const midi = Math.round(midiFloat); const noteFreq = ref * Math.pow(2, (midi - 69) / 12); const cents = Math.round(1200 * Math.log2(f / noteFreq)); const noteIndex = midi % 12; // 0=C, 1=C#, ..., 9=A, ..., 11=B const octave = Math.floor(midi / 12) - 1;
MIDI 69はA4=440Hz。1セントは半音(100セント)の1/100で、1200セント=1オクターブ。これは等分平均律(12-TET)の標準式で、世界中のチューナーで使われています。
プライバシー: なぜサーバ送信が不要か
上記のパイプラインは全てブラウザ内のJavaScriptで完結します。MediaStreamSourceで取り込んだマイクデータはAudioContextのオーディオグラフ内でAnalyserNodeに流れ、波形データがJS側のFloat32Arrayにコピーされて自己相関処理されるだけ。 ネットワーク送信のためのfetchやWebSocket呼び出しは一切ありません。