Skip to content

スペクトログラムと STFT

libsonare のほぼすべての MIR 機能 — クロマ、メル、MFCC、オンセット強度、さらには BPM やキー — は、音声の時間-周波数表現から計算されます。この 1 つの土台を理解すれば、なぜ多くの関数が同じ nFfthopLength を共有するのかが分かります。本ページは MIR の全体像 の地図より 1 段深く掘り下げます。

波形から周波数へ: FFT

波形は時間に対する振幅であり、どの周波数が含まれるかは教えてくれません。フーリエ変換はそれに答えます — 信号を、それを合成する正弦波へ分解します。プリズムが光を色に分けるようなものです。FFT(高速フーリエ変換)は、それをサンプルのブロックに対して効率的に計算するアルゴリズムにすぎません。

問題は、1 回の FFT はブロック全体を、その周波数内容がまったく変化しないかのように記述する点です。音楽は絶えず変化するので、曲全体の単一 FFT はほとんど役に立ちません。

時間変化を追う: STFT

短時間フーリエ変換(STFT)は、音声を短く重なり合う窓に切り分け、それぞれに FFT をかけることでこれを解決します。結果は 2 次元のグリッド — 各時刻の周波数内容です。スペクトログラムはそのグリッドを画像として描いたもので、一方の軸が時間、もう一方が周波数、エネルギーが明るさです。

STFT · SPECTRALIDLE
STFT — 時間と周波数を同時に見る

220 Hz から 4 kHz へ上昇するトーン。各列が1つの短時間スペクトルで、明るいほどその周波数のエネルギーが大きい。

このグリッドを制御するため、2 つのパラメータがどこにでも現れます。

パラメータ設定するものトレードオフ
nFftFFT の長さ(サンプル数。例 2048)。窓を別途短くしない限り、解析窓の長さも兼ねますが長いほど周波数が細かく、時間は粗くなります
hopLength窓を進める量(例 512小さいほど 1 秒あたりのフレームが増え(動きが滑らか)、CPU 負荷も増えます

これが時間-周波数の分解能トレードオフです。完璧な周波数分解能と完璧な時間分解能を同時には得られません。これは物理であり、libsonare の制約ではありません。

厳密に言えば、このトレードオフを決めるのは変換の長さではなくの長さです。既定では両者は同じ値で、JS/WASM バインディング経由では常に一致します。C++ では StftConfig::win_lengthn_fft より短く設定でき、その状態で n_fft を大きくしても各フレームのゼロ詰めが増えるだけです。ビンは増えて同じスペクトルを細かく補間しますが、実際の周波数分解能は上がらず、時間分解能も犠牲になりません。

窓掛け

各ブロックは FFT の前に、端をゼロへなめらかに落とす窓関数(Hann 窓など)と掛け合わされます。これがないと、ブロック境界の急な切れ目が周波数全体へエネルギーを漏らし(スペクトル漏れ)、結果がにじみます。窓掛けはフレームが重なる理由でもあります — テーパーした端は隣のフレームが補います。

nFft と hopLength を実寸で描く
入力サンプルnFft — 窓の長さ窓掛けされたフレームhopLengthスペクトログラムの列1 フレームにつき 1 回の FFT、1 列
  • 窓掛けされたフレーム
  • 同じサンプルを再び解析する範囲
  • スペクトログラムの列
既定値では、窓は自分の長さの 4 分の 1 ずつしか進みません。つまりどの窓もその大半が直前の窓と同じサンプルで、これがテーパーした端を補っています。1 つの窓が 1 回の FFT、そして 1 列になるので、1 秒あたり何列得られるかを決めているのは hopLength だけです。

音楽的な間隔: CQT と VQT

標準の STFT は周波数ビンを Hz で均等に並べます。しかし音名は対数的に並びます — オクターブごとに周波数が倍になるからです。そのため均等なグリッドは、高域では分解能を浪費し、低域では足りなくなります。定 Q 変換(CQT)は音楽的な音程ごと(通常は半音ごと)にビンを並べ、オクターブあたり同じ分解能を与えます。可変 Q 変換(VQT)はビンの中心周波数を CQT と同じ半音刻みのまま保ち、代わりに低域のフィルター帯域幅を広げます(低域だけ Q を下げます)。帯域を広げたフィルターは時間的に短くなるため、低い音が時間方向ににじみません。CQT のような音高を意識したビンを使いつつ、ベースや低域の打撃成分の時間挙動をきれいに保ちたいときに使います。

同じ 1 オクターブを 2 か所で
低いオクターブ · 110–220 Hz12 本5 本高いオクターブ · 3520–7040 Hz12 本163 本21.5 Hz · 44.1 kHz / 2048
  • CQT のビン — 半音ごとに 1 本
  • STFT のビン — Hz 間隔が一定
どちらのパネルも 1 オクターブ分で、同じ幅に描いてあります。音高を意識したビンはどちらにも 12 本を置きますが、STFT は Hz 間隔が一定なので、低いオクターブには数本しか入らず、高いオクターブには 160 本以上が詰め込まれます。VQT はこの音高基準の中心周波数をそのまま保ち、厳密な定 Q だと窓が長くなりすぎて時間がにじむ低域だけ、フィルターの帯域幅を広げます。
libsonare が STFT をどう計算するか

libsonare の STFT・フレーミングユーティリティは、窓(既定で Hann)を適用し、hopLength ずつ進め、フレームごとに実数 FFT を実行して、メル・クロマ・オンセット・テンポグラムの各段が再利用する振幅/パワースペクトルを生成します。ただしこの再利用には範囲があります。analyze()MusicAnalyzer)の内部では中間のスペクトログラムを 1 回だけ計算し、クロマ段とメル段がそれを共有しますが、chroma()melSpectrogram() などの単独ヘルパー、およびキー・コード・セクションの各アナライザーは、それぞれが自前で STFT を計算します。FFT のコストを 1 回に抑えたい場合は、analyze() からまとめて特徴量を取得してください。nFft/hopLength の既定値(2048/512)は一般的な librosa の使い方に合わせ、リファレンステストで出力を比較できるようにしています。CQT/VQT は同じフレーミング規約の上に対数周波数ビンを重ねます。

関連: MIR の全体像クロマ特徴量メル・MFCC・音色オーディオ基礎