はじめに
Web Audio APIで音声ビジュアライザーを作ると、AnalyserNode に以下のようなメソッドが出てきます。
analyser.getByteFrequencyData(frequencyData)
analyser.getByteTimeDomainData(timeDomainData)
どちらも音声データを取得するためのものですが、取れる内容は違います。
最初は名前だけ見ても分かりづらく、
frequency dataって何?
time domain dataって何?
どっちを使えばいいの?
となりました。
この記事では、音声ビジュアライザーを作る目線で、この2つの違いを整理します。
ざっくり結論
先にまとめると、こうです。
getByteFrequencyData:
周波数ごとの強さを取る
bass / mids / highs の解析に使いやすい
getByteTimeDomainData:
波形の形を取る
volume / waveform の表示に使いやすい
ビジュアライザーでいうと、
低音に反応させたい → getByteFrequencyData
波形を描きたい → getByteTimeDomainData
全体の音量を取りたい → getByteTimeDomainData
という感覚です。
getByteFrequencyDataとは
getByteFrequencyData は、音声を周波数成分に分解したデータを取得します。
const frequencyData = new Uint8Array(analyser.frequencyBinCount)
analyser.getByteFrequencyData(frequencyData)
配列の中には、低い周波数から高い周波数までの強さが入ります。
値は 0〜255 です。
frequencyData[0] 低い周波数
frequencyData[100] もう少し高い周波数
frequencyData[500] さらに高い周波数
つまり、周波数ごとの音の強さを見るためのデータです。
frequency dataでできること
周波数データを使うと、低域・中域・高域のように音を分けられます。
bass: 20Hz - 250Hz
mids: 250Hz - 2000Hz
highs: 2000Hz - 8000Hz
たとえば、低域だけを取り出して、キックやベースに反応する値として使えます。
const bass = getBandEnergy(
frequencyData,
audioContext.sampleRate,
analyser.fftSize,
20,
250
)
この値を使えば、
キックで球体が膨らむ
ベースで画面が歪む
高域で粒子が光る
のような表現ができます。
getByteTimeDomainDataとは
getByteTimeDomainData は、時間軸上の波形データを取得します。
const timeDomainData = new Uint8Array(analyser.fftSize)
analyser.getByteTimeDomainData(timeDomainData)
こちらも値は 0〜255 です。
中心がだいたい 128 で、そこから上下に揺れます。
128を中心に上下する値
音の波形そのものに近いデータです。
time domain dataでできること
時間領域データは、波形表示に使いやすいです。
function createWaveform(timeDomainData: Uint8Array): Float32Array {
const waveform = new Float32Array(timeDomainData.length)
for (let i = 0; i < timeDomainData.length; i++) {
waveform[i] = ((timeDomainData[i] ?? 128) - 128) / 128
}
return waveform
}
0〜255の値を、-1〜1くらいの値に変換しています。
この waveform を使うと、
- 波形をCanvasに描く
- パーティクルを上下に揺らす
- 線を音に合わせて変形する
といった表現ができます。
volumeを取るならtime domain data
全体の音量を取る場合も、時間領域データが使いやすいです。
RMSを使って音量のような値を計算できます。
function calculateVolume(timeDomainData: Uint8Array): number {
let sum = 0
for (const value of timeDomainData) {
const normalized = (value - 128) / 128
sum += normalized * normalized
}
return Math.sqrt(sum / timeDomainData.length)
}
この volume を使うと、音全体の大きさに合わせてビジュアルを動かせます。
const scale = 1 + volume * 0.5
周波数データと波形データの使い分け
ビジュアライザーでは、どちらか片方だけではなく、両方使うのが扱いやすいです。
frequency data:
音の成分を見る
bass / mids / highs に分ける
time domain data:
波形を見る
volume / waveform を作る
表現にすると、以下のような使い分けができます。
| データ | 使い道 |
|---|---|
volume |
全体の拡大、明るさ |
bass |
大きな膨張、低域の衝撃 |
mids |
回転、流れ、うねり |
highs |
細かい揺れ、粒子、ちらつき |
waveform |
線、波、パーティクルの質感 |
音量だけだと単調になる
最初は volume だけでビジュアライザーを動かしがちです。
const scale = 1 + volume * 0.5
これは分かりやすいですが、全体が同じように大きくなったり小さくなったりするだけになりやすいです。
そこで、周波数データも使います。
const scale = 1 + volume * 0.2 + bass * 0.4
const rotation = mids * 0.01
const jitter = highs * 0.1
音の成分ごとに役割を分けると、見た目に音楽的な動きが出やすくなります。
まとめ
getByteFrequencyData と getByteTimeDomainData は、どちらも音声ビジュアライザーで重要です。
違いは以下です。
getByteFrequencyData:
周波数ごとの強さを取得する
bass / mids / highs に分けるのに向いている
getByteTimeDomainData:
波形の形を取得する
volume / waveform に向いている
音に反応する表現を作るなら、両方を組み合わせると扱いやすいです。
音量で全体を動かす
低域で大きく反応させる
高域で細かい揺れを足す
波形で質感を出す
このように分けると、音量だけで動かすよりも、音楽の動きが見えやすいビジュアライザーになります。