音楽に合わせてバーが動く、スペクトラムアナライザー(またの名をグラフィックイコライザー、通称グライコ)をずっとやってみたかった。 WebAudioを使えば音楽の再生やスペクトラム解析に必要なFFT計算も簡単に利用できるのでやってみた (ハードウェアではなくソフトウェアで)。
デモ
もっとも重要なこと:横軸を対数でプロットする
WebAudioを使ってスペアナを作ろうとしてググった時に出てくるサンプルサイトなどを参考に作ってみても「なんかそれっぽくない…」となってしまう。
問題はAnalyserNode.getByteFrequencyData()
で取得した周波数ごとのデータをそのまま横軸にプロットしていくところ。
フーリエ変換した結果を配列に受け取れるが、各要素はサンプリング周波数の半分を等間隔に区切った周波数になる。 しかし人が音を聞き取る感覚としては周波数を対数で感じる。 可聴範囲が20Hz〜20,000Hzとのことでそれを線形にプロットすると低音部分が潰れてしまい、ほとんど見えなくなってしまう。
なので横軸(周波数)を対数となるようにプロットする必要がある。
具体的には、最低20Hzと最高20000Hzをそれぞれ対数log10を取って、横軸に沿って線形補間したものを指数にして周波数に戻して、それに応じたFFT内のデータを取得する:
let analyserNode = null |
LED式の表示のように横をブロックに分けて表示したい場合には対応する範囲の最大値を用いるとよい。
AnalyserNodeの問題:遅延回避にsmoothingTimeConstantを下げる
上の対策だけでそれっぽく動くのだけど、よく見ると少し音より遅れているように見えた。 音と同期してないと気持ちよくないのでなんとかしたかった。
しかし理由が分からなくて悩んだ。
getByteFrequencyData
で取得できる値が少し遅れてしまっているのか、
AudioSource
から context.destination
と AnalyserNode
に分けて入れてるのが悪いのか、
requestAnimationFrame
に問題があるのか、
そもそもfftSize
分だけ遅れた状態になってしまうからまずいのか、
などとあれこれ推測したが分からなかった。
ひょんな拍子に解決法がわかった。
AnalyserNode.smoothingTimeConstant
を下げてやれば解決する。
これは結果があまりブレて見えないように、前回の取得結果との補間をしているっぽい。
デフォルトが0.8
で、「ほとんどのケースで十分」などと書かれているが、音のスペアナとして利用する場合にはレスポンスが悪く見えてしまうので下げてやる必要がある(体感0.3以下、実際にはブラックマン窓がどうとかこうとか…)。
細かなこと
デシベル範囲の調整
AnalyserNode
にmaxDecibels
とminDecibels
プロパティがあって、得られる値はその範囲に影響する。普通高周波部分は振幅が低いので、見栄え的には別途持ち上げたほうがいいかもしれない
直流成分
- 普通は問題はないと思うが、場合によっては
AnalyserNode
の前に直流成分を取り除いたほうがいいかもしれない。FFTする場合、直流成分が含まれていると結果が劣化するとのことなので、防止のため- 「DCオフセット除去」で検索
- WebAudioでやる場合には
AudioWorklet
で移動平均を求めて引いてやればよいでしょう
AudioBufferSourceNode
は不便
- WebAudioで曲を鳴らすのに最初
AudioBufferSourceNode
を使ってみたが、再生中の位置の取得やシークができないので、htmlのaudio
メディアタグを使った方がいい- WebAudioにつなぐには
AudioContext.createMediaElementSource()
を使う
- WebAudioにつなぐには
audio
タグにcontrols
を指定すると自動的にUIも表示されるのでこういう用途に便利
リンク
- ソース:tyfkda/speana: WebAudioを使ったスペクトラムアナライザー
- Visualizations with Web Audio API - Web API | MDN 横軸が線形なところだけなんとかして
- audioMotion.js いろんなタイプのスペアナ表示ができる。ライブラリとして簡単に利用できるようなのでこちらを使わせてもらうといいかも
- 高速フーリエ変換の実装 実際のところ自分でFFTを計算して動かしたことがないので、完全に理解したとは言いがたい…
- ハード的に実装する場合にはバンドパスフィルターがどうとかで、FFTせずにやってるのかしらん