スペクトログラムと STFT
libsonare のほぼすべての MIR 機能 — クロマ、メル、MFCC、オンセット強度、さらには BPM やキー — は、音声の時間-周波数表現から計算されます。この 1 つの土台を理解すれば、なぜ多くの関数が同じ nFft と hopLength を共有するのかが分かります。本ページは MIR の全体像 の地図より 1 段深く掘り下げます。
波形から周波数へ: FFT
波形は時間に対する振幅であり、どの周波数が含まれるかは教えてくれません。フーリエ変換はそれに答えます — 信号を、それを合成する正弦波へ分解します。プリズムが光を色に分けるようなものです。FFT(高速フーリエ変換)は、それをサンプルのブロックに対して効率的に計算するアルゴリズムにすぎません。
問題は、1 回の FFT はブロック全体を、その周波数内容がまったく変化しないかのように記述する点です。音楽は絶えず変化するので、曲全体の単一 FFT はほとんど役に立ちません。
時間変化を追う: STFT
短時間フーリエ変換(STFT)は、音声を短く重なり合う窓に切り分け、それぞれに FFT をかけることでこれを解決します。結果は 2 次元のグリッド — 各時刻の周波数内容です。スペクトログラムはそのグリッドを画像として描いたもので、一方の軸が時間、もう一方が周波数、エネルギーが明るさです。
このグリッドを制御するため、2 つのパラメータがどこにでも現れます。
| パラメータ | 設定するもの | トレードオフ |
|---|---|---|
nFft | FFT の長さ(サンプル数。例 2048)。窓を別途短くしない限り、解析窓の長さも兼ねます | 窓が長いほど周波数が細かく、時間は粗くなります |
hopLength | 窓を進める量(例 512) | 小さいほど 1 秒あたりのフレームが増え(動きが滑らか)、CPU 負荷も増えます |
これが時間-周波数の分解能トレードオフです。完璧な周波数分解能と完璧な時間分解能を同時には得られません。これは物理であり、libsonare の制約ではありません。
厳密に言えば、このトレードオフを決めるのは変換の長さではなく窓の長さです。既定では両者は同じ値で、JS/WASM バインディング経由では常に一致します。C++ では StftConfig::win_length を n_fft より短く設定でき、その状態で n_fft を大きくしても各フレームのゼロ詰めが増えるだけです。ビンは増えて同じスペクトルを細かく補間しますが、実際の周波数分解能は上がらず、時間分解能も犠牲になりません。
窓掛け
各ブロックは FFT の前に、端をゼロへなめらかに落とす窓関数(Hann 窓など)と掛け合わされます。これがないと、ブロック境界の急な切れ目が周波数全体へエネルギーを漏らし(スペクトル漏れ)、結果がにじみます。窓掛けはフレームが重なる理由でもあります — テーパーした端は隣のフレームが補います。
- 窓掛けされたフレーム
- 同じサンプルを再び解析する範囲
- スペクトログラムの列
音楽的な間隔: CQT と VQT
標準の STFT は周波数ビンを Hz で均等に並べます。しかし音名は対数的に並びます — オクターブごとに周波数が倍になるからです。そのため均等なグリッドは、高域では分解能を浪費し、低域では足りなくなります。定 Q 変換(CQT)は音楽的な音程ごと(通常は半音ごと)にビンを並べ、オクターブあたり同じ分解能を与えます。可変 Q 変換(VQT)はビンの中心周波数を CQT と同じ半音刻みのまま保ち、代わりに低域のフィルター帯域幅を広げます(低域だけ Q を下げます)。帯域を広げたフィルターは時間的に短くなるため、低い音が時間方向ににじみません。CQT のような音高を意識したビンを使いつつ、ベースや低域の打撃成分の時間挙動をきれいに保ちたいときに使います。
- CQT のビン — 半音ごとに 1 本
- STFT のビン — Hz 間隔が一定
libsonare が STFT をどう計算するか
libsonare の STFT・フレーミングユーティリティは、窓(既定で Hann)を適用し、hopLength ずつ進め、フレームごとに実数 FFT を実行して、メル・クロマ・オンセット・テンポグラムの各段が再利用する振幅/パワースペクトルを生成します。ただしこの再利用には範囲があります。analyze()(MusicAnalyzer)の内部では中間のスペクトログラムを 1 回だけ計算し、クロマ段とメル段がそれを共有しますが、chroma() や melSpectrogram() などの単独ヘルパー、およびキー・コード・セクションの各アナライザーは、それぞれが自前で STFT を計算します。FFT のコストを 1 回に抑えたい場合は、analyze() からまとめて特徴量を取得してください。nFft/hopLength の既定値(2048/512)は一般的な librosa の使い方に合わせ、リファレンステストで出力を比較できるようにしています。CQT/VQT は同じフレーミング規約の上に対数周波数ビンを重ねます。