Skip to content

スペクトログラムと STFT ​

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

波形から周波数へ: FFT ​

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

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

時間変化を追う: STFT ​

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

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

400 Hz から 1 kHz へ上昇するトーンに、4 つのクリックを重ねた信号。各列が1つの短時間スペクトルで、明るいほどエネルギーが大きい。nFft を切り替えると、256 ではクリックは細い線になり、トーンは幅約 170 Hz の帯になる。4096 ではトーンは幅約 20 Hz の線になり、クリックは約 280 ms にわたってにじむ。

nFft(窓長)

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

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

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

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

サンプル数から秒とヘルツへ ​

どちらのパラメータもサンプル数で数えるので、時間や周波数としての意味はサンプルレート(sr、1 秒あたりのサンプル数)によって変わります。

量式22050 Hz で nFft 2048、hopLength 51244100 Hz で同じ値
窓の長さnFft / sr92.9 ms46.4 ms
ホップの長さhopLength / sr23.2 ms11.6 ms
1 秒あたりのフレーム数sr / hopLength43.186.1
周波数ビンの間隔sr / nFft10.8 Hz21.5 Hz
周波数ビンの数nFft / 2 + 110251025
フレーム t の時刻(既定の中央合わせフレーミング)t × hopLength / srフレーム 100 ≈ 2.32 秒フレーム 100 ≈ 1.16 秒

同じサンプル数でも、44100 Hz では窓の長さが半分、ビン間隔が 2 倍になります。音楽アナライザーが nFft を入力レートに合わせて換算し直すのはこのためで(下の詳細を参照)、窓の秒数をそろえています。

窓掛け ​

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

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

音楽的な間隔: CQT と VQT ​

標準の STFT は周波数ビンを Hz で均等に並べます。しかし音名は対数的に並びます。オクターブごとに周波数が倍になるからです。

そのため均等な Hz のグリッドは、高域では分解能を浪費し、低域では足りなくなります。

同じ 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 だと窓が長くなりすぎて時間がにじむ低域だけ、フィルターの帯域幅を広げます。
変換周波数の並べ方使いどころ
STFTHz で均等一般的な時間-周波数の見取り図が欲しいとき
CQT(定 Q 変換)音楽的な音程ごと。通常は半音ごとに 1 組で、オクターブあたり同じ分解能になるピッチの関係が重要なとき
VQT(可変 Q 変換)ビンの中心周波数は CQT と同じ半音刻みのまま、低域のフィルターだけ帯域幅を広げる(Q を下げる)。帯域を広げたフィルターは時間的に短くなるため、低い音が時間方向ににじまないCQT のような音高を意識したビンを使いつつ、ベースや低域の打撃成分の時間挙動をきれいに保ちたいとき
libsonare が STFT をどう計算するか

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

窓長のサンプル数は、サンプルレートごとに異なる長さの時間を意味します。そのため、音声を本来のレートで受け取る解析器(ビート、テンポ、オンセット、キー、リズム、コードのビートグリッド、音色)は nFft を 22050 Hz でのサンプル数として読み、入力のレートに換算します。ホップは入力のサンプル数のままです。これにより、同じ素材は 22050、44100、48000 Hz のどれでも同じ長さの窓で解析されます。ストリーミング解析器は nFft を 44100 Hz でのサンプル数として読み(それより低いレートでは縮め、ホップより短くはしません)、ポリフォニー解析は nFft、winLength、hopLength を 44100 Hz で読みます。

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