はじめに
音声信号の基本周波数を推定したあと、音楽的な解釈を与えたい場合はたくさんあります。例えば人の声であれば音域を調べたかったり、音楽であれば周波数成分をピアノロール的に表現して和音構造を確認したい等です。
物理的な周波数値から音楽的なセントへ
物理量としての音量の知覚が対数スケールであるのと同様に、物理量としての周波数の知覚もおよそ対数スケールに従っています。
例えば、1オクターブ差というのは、周波数比で考えると、基準となる周波数の2倍か\frac{1}{2}倍です。よって、何オクターブ離れているかは以下の式で求められます。
オクターブ差 = log_2(\frac{対象周波数}{基準周波数})
Pythonを使った具体的な計算は以下の通りです。
octave = numpy.log2(target_frequency/base_frequency)
そして、この1オクターブ比を1200分割した周波数比スケールが、セント(cent)です。100cent分の比が半音分の上げ下げに相当します。つまり、ピアノの鍵盤で隣り合う鍵盤の音高比が、この100cent差に相当します。
セントの具体的な計算式は以下の通りです。オクターブ比を1200倍のスケールにしただけです。
cent = 1200 \times log_2(\frac{対象周波数}{基準周波数})
ここで基準周波数をどのようにとるかですが、二つの周波数間の比、つまり相対的な音高差を見たいのであれば、基準側を分母、対象側を分子として計算すれば良いです。
「ドレミファソラシド」に変換したいのであれば、基準周波数は440Hzや442Hzを使います。この基準周波数は国際的な取り決めがあるとの事です。結局のところ、これらのスケールは差のスケールではなく比のスケールなので、対数に底が必要な様に、比の基準となる周波数値を必ず必要とします。
なお、440Hzは音楽的にはピアノの鍵盤の真ん中あたりの「ラ」の音に相当する周波数だそうです。そうなる様にピアノを調整し、合奏する他の楽器もそこに音程を合わせる様ですが、私は音楽に詳しくないので、これ以上の説明はできません。
負値の扱い
ところで、前述の式のままセントを算出すると、対象周波数が基準周波数より小さい場合にセントが負値になります。例えば基準周波数440Hz、対象周波数220Hzとして、丁寧に計算してみます。
1200 \times log_2(220/440) = 1200 \times log_2(2^{-1}) = 1200 \times -1 = -1200
負値になる事自体は問題ありませんが、負値が出てくると計算がややこしいだとか、物理的なピアノの鍵盤位置に合わせたいので負値だと困ると言った理由があれば、オフセットを足してゼロ以上になる様に調整してしまっても良いです。音名(ドレミファソラシドやABCDといった記号)と対応づける場合も、要は符号化なので、負値ではなく自然数の方が都合が良いでしょう。
DTMやシンセサイザー用に処理するのであれば、MIDIノートナンバーに合わせてしまうのが一つの手です。
MIDIノートナンバーは1オクターブ比を12分割し、基準周波数440Hzがナンバー69になる様にオフセットを足したスケールになっています。
よって、前述の式に6900を足してしまえば、実用範囲では負値が出現しない、MIDIを扱うDTMソフト等と互換性のあるピアノロール的なスケールに変換できます。
cent = 1200 \times log_2(\frac{対象周波数}{基準周波数}) + 6900
実装
Pythonであれば前述の式を素直に実装できます。
# 周波数からセントへ変換
def __freq_to_cent(freq):
return 1200 * numpy.log2(freq/440) + 6900
# セントから周波数へ変換
def __cent_to_freq(cent):
return numpy.power(2, (cent - 6900) / 1200) * 440
おわりに
周波数値からセント値へのスケール変換により、物理的な値から、より人間にとって直感的に解釈しやすい表現に変換できる様になりました。
この後の課題は、DFT周波数成分をセントスケール変換し、スペクトログラムをピアノロール化する事でしょうか。
スペクトログラムをピアノロール化できると、Waveファイルに保存された音楽信号を解析し、MIDI的な情報に分解してシンセサイザーで合成する、といったプログラムを作れる様になるかもしれませんね。