キノコの自省録

日々適当クリエイト

音(声)の高低を検出する方法を真面目に書く

Seraph Flightで使っている音程の検出について、
基本的な解析方法しか使っていないと書いたものの、
あまりこういう分野に明るくないけどやってみたいなあ、という人がいるかも知れないので、
ちょっと真面目に説明します。

Seraph Flight - 天使の歌声飛行を App Store で


録音

まず、音響データがないと始まりませんので、マイクから録音します。
この辺は実装系によって異なりますので頑張ってください。
マイク録音なんかSDK叩けば返って来るので難しくないと思いますが、
バッファ管理が意外と面倒です。


Linear PCM

録音した音響データについて。
無圧縮の生音響データをLinear PCM(LPCM)と言います。
有名どころはwav形式でしょう。wavはRIFFというフォーマットに基づいて、LPCMデータを格納しています。
LPCMの解釈には、3つのパラメータ「サンプリングレート、量子化ビット数、チャネル数」が必要です。
見たことあると思いますが、44100Hz, 16bit, stereoというやつです。
44100Hzなら、44100個で1秒という計算になります。
16bitはデータ1個当たりのビット数です。16bitなので2byte(-32768〜32767)で音を表現します。
stereoはわかりますよね。2チャンネルという意味です。
データ1個あたり16bit(=2byte) × 2chなので、4byteです。
このデータ1個を、信号処理の世界ではサンプルと言います。44100Hzというのは、1秒間に44100サンプルのデータを取ったということです。
8bit, monoralなら1サンプル=1バイトです。


高速フーリエ変換

高速フーリエ変換(FFT)とは、計算量を少なくした離散フーリエ変換(DFT)です。
ただしFFTには制限があって、入力信号のサンプル数は2の累乗でなければなりません。
録音した生データを、例えば1024サンプルだけ入力すると、
実部と虚部の位相が512個ずつ返ってきます。
Seraph Flightでは、4096サンプルが録音できたらFFT、という作業を繰り返しています。
録音のところで「バッファ管理が面倒」と書いたのは、
4096サンプル分を過不足なく渡す必要があるためです。


窓関数

4096サンプルずつ分析するというように、
短時間で区切って都度分析するフーリエ変換短時間フーリエ変換(STFT)と言います。
本来、フーリエ変換は無限区間を分析するのですが、
そんなことは無理な相談なので、音楽やリアルタイム録音データを分析する場合、
ほぼ確実にSTFTを行います。
しかし、本来なら連続して繋がっているデータを、
無理やり適当なサイズにばっさり区切って分析するわけなので、
データの端と端で不都合(=ニセの周波数成分など)が生じます。
その不都合を緩和するのが窓関数です。
一番メジャーなのがハミング窓で、
よくわからなかったらとりあえずハミング窓を適用しておくと良いと思います。


パワースペクトル

FFTをかけると実部(Re)と虚部(Im)の位相がそれぞれ(入力サンプル数/2)個返って来ると説明しました。
それらの位相を、以下の式で計算してみます。
sqrt(Re2 + Im2)
この結果がパワースペクトルです。
1024サンプルのデータをFFTにかけると512個分のパワースペクトルが取得できるというわけですが、
この内訳は、(サンプリング周波数 / 2)を512分割した周波数ごとのパワーとなります。
44100Hzの場合、22050Hzを512分割するということです。
22050/512=43なので、配列の若い順に、0〜43Hz, 43〜86Hz,・・・という感じでパワーが得られます。
なお、44100Hzを2で割った22050Hzの周波数をナイキスト周波数と言います。
表現できる周波数の上限を表しています。
22050Hzという中途半端感の否めない数値は、人間の可聴音の限界に基づいています。
といってもこんな高周波の音を聴くことのできる人は稀で、年齢とともに上限が落ちてきます。
普通の人ならせいぜい18000Hz聴こえればいいほうです。


ナイキスト周波数

少しだけ、ナイキスト周波数の話をしておきます。
これは、一般に標本化定理やサンプリング定理と呼ばれています。
ものすごくざっくり説明しますと、波はf/2で折り返してくるため、
f〜f/2の成分と、f/2〜0の成分がごちゃ混ぜになってしまいます。
例えば、20000Hzのサンプリング周波数で音声を録音したとします。
その音声の中に11000Hzの周波数成分が含まれていたとすると、
10000Hzで折り返して、9000Hzの成分と混ざってしまいます。
混ざってしまうと、残念ながら取り出せません。
しかも、これはノイズとして現れます。
このノイズを折り返し雑音と言います。
44100Hzでサンプリングする場合も、当然22050Hzを超える帯域の音は折り返してノイズとして現れます。
そのため、大抵は高周波成分をカットしてからサンプリングします。

そもそもなんで折り返すんだよ、という疑問もあるかと思いますが、
これは離散信号特有の悩みでもあります。
本来、信号というのは、ご存知の通り連続波形なのですが、
サンプリングすると、ただの「点」になります。
その「点」をどう波形として結ぶかという情報が欠落します。
例えば、20Hzというのは1秒間に20個の点をマス目に打つということです。
そのマス目上に15Hzのサイン波を書き込んだ時、マス目の交点のみが値として現れます。
この交点、実は5Hzのサイン波とぴったり一致します。
つまり、この条件下(20Hzでサンプリングした場合)では、5Hzと15Hzの区別がつかなくなるということです。


周波数分解能

1024サンプルを入力すると、43Hzに分解されるということは、
43Hz以上小さい周波数は識別できないという意味でもあります。
例えば、430Hz〜473Hzの区間に、オクターブ4のラ(A)=440Hzと、ラ#(A#)=466Hzが含まれますが、
これを識別することはできないということです。
もっと分解能を増やしたければ、入力する信号のサンプル数を単純に増やせば解決します。
例えば、Seraph Flightのように4096サンプルを入力すると、
22050/2048=10.77Hzが周波数分解能となります。
しかし、4096サンプルを録音するためには92.88msecかかります。
(1秒44100個なので、(1/44100)*4096≒92.88)
対して1024サンプルの場合は23msecですから、だいぶ「遅くなってしまった」ことがわかります。
先ほどの周波数の話と同様に、検出した周波数のパワーは、92.88msecのどこで鳴った音なのかはわかりません。
要するに、周波数分解能と時間方向の分解能はトレードオフの関係があるということです。
これは、ハイゼンベルク不確定性原理と根元は同じです。
ハイゼンベルク不確定性原理という名前が実に厨二臭くて格好いいということで紹介してみました。


人の声の周波数

人の声を分析にかけた場合、
割と大きい値を検出したパワースペクトルの中で、一番低い周波数を基本周波数と言います。
Seraph Flightでは、この基本周波数を使っています。
人の声の基本周波数は、大体200Hz〜3000Hzですので、
その辺りの帯域を中心に処理しているということです。


なお、「割と大きい」というのが曲者で、
単純に閾値を使う方法と、統計的に求める方法があります。
Seraph Flightでは統計量を求めています。

最後に

パワースペクトルを求めるところまでは、音響系の信号処理では大体一緒です。
そこから先どうするかは、完全に問題によりけりになります。