チューナーにギターの弦の音を聴かせると、即座に「A、ちょっと低い」と針が振れます。カラオケの採点も、スマホの楽器チューナーアプリも、やっていることは同じです。マイクに入ってくるのはただの空気の振動なのに、機械はどうやって「いま鳴っている音の高さ」を言い当てているのでしょうか。
種を明かすと、中身は驚くほど単純です。波形を自分自身と少しずつずらして重ね、いちばんよく一致する「ずれ幅」を探す。それだけです。この記事では、自己相関と呼ばれるこの一つの発想から、ブラウザで動くリアルタイム・チューナーを作るところまでをたどります。
ここで少し宣伝を。筆者は個人で音感トレーニングアプリ Harmonize を開発しています。機械が音程を言い当てる仕組みが今回の話だとすれば、あちらは同じことを自分の耳でできるようになるための練習アプリで、ちょうど裏表の関係にあたります。
音の高さの正体は「くり返しの速さ」
出発点は、この連載の最初の記事 で見た「音は数の列」という事実です。マイクは1秒あたり44100個のペースで「いまの空気の圧力」を数として拾っていきます。プログラムから見た音とは、この数の列がすべてです。
そして、高さのある音——声や楽器の音——の波形には、共通のはっきりした特徴があります。同じ形が周期的にくり返されるのです。1秒に440回くり返せばラ(A4)、262回ならド(C4)。この「1回のくり返しにかかる時間」を基本周期と呼び、くり返しの回数が周波数、つまり音の高さです。逆に「サーッ」というノイズのような音にはくり返しがなく、だから高さも感じられません。
つまり「音の高さを当てる」という問題は、「数の列から、くり返しの間隔を見つける」という問題に化けます。ではその間隔を、どうやって測ればよいのでしょうか。
ずらして重ねると、周期が浮かび上がる
ここが今回の核心です。波形のコピーをもう一枚つくり、元の波形の上で少しずつ横にずらしながら、どれだけ重なっているか(一致度)を測っていきます。
中途半端なずれ幅では、山と山が食い違ってバラバラです。ちょうど半周期ずらすと、山と谷が正面衝突して「逆向きに一致」します。そして、ずれ幅がちょうど1周期ぶんに達した瞬間——波形はぴたりと自分自身に重なります。同じ形がくり返されているのだから、くり返しの間隔ぶんずらせば元どおりに見える、というわけです。
この「ずれ幅を横軸、一致度を縦軸」にしたグラフを描くと、次のようになります。
一致度のカーブに、くっきりした山が立ちます。最初の大きな山の位置が、探していた基本周期です。ずれ幅0の場所にも山がありますが、これは「ずらしていない=自分自身とくらべた」だけなので除外します。山の位置が T サンプルなら、1秒=44100サンプルの中にくり返しが $44100 \div T$ 回入る計算なので、それがそのまま周波数になります。図の例では T=100 で441Hz。いちばん近い音名はラ(A4=440Hz)で、ぴったりではなく少しだけ高い、というところまで分かります。
この「少しだけ高い」を数字にする単位がセントです。半音1個ぶんをさらに100等分した音程のものさしで、441Hzはラより約4セント高い。チューナーの針が示しているのはこのセント値で、±数セントに収まれば実用上「合っている」と扱われます。ちなみに半音の幅が周波数の何倍にあたるかは平均律の回 でやったとおり $2^{1/12}$ 倍。セントはそれを100分割しているので、1セント=$2^{1/1200}$ 倍という比の単位です。
こうして道具立てが出そろいました。ずらして重ねて山を探すと基本周期が出る。周期を周波数に直し、いちばん近い音名とのずれをセントで示す。チューナーの中身は、これで全部です。
触ってみる
実際に動くチューナーを用意しました。入力は2系統あって、マイクのボタンを押せば自分の声や楽器で、テスト音源のボタンを押せば内蔵オシレータの音で試せます。マイクはブラウザの決まりで https か localhost のページでしか使えないため、使えない環境では自動でテスト音源に切り替わります(このページ上ならそのまま使えるはずです。マイクの音がスピーカーから鳴ることはありません)。
うまく表示されないときは、デモを別タブで開く 。
いちばん下のグラフが、さきほどの「ずれ幅ごとの一致度」を毎瞬描き直しているところです。テスト音源の周波数スライダーを動かすと、山の位置が左右にすべっていき、針とグラフが連動して動くのが見えます。スライダーの右側には鳴らしている音の理論値を出してあるので、検出結果との答え合わせもできます。マイクで声を「あー」と出しながら、ゆっくり高さを上げ下げしてみるのも面白いところです。声のような複雑な波形でも、くり返してさえいれば山はちゃんと立ちます。
ひとつ試してほしいのが、波形をノコギリ波や矩形波に切り替えることです。倍音の回 で見たとおり、波形が変わると音色はまるで違いますが、くり返しの間隔そのものは変わりません。だから一致度の山の形は変わっても、山の位置は同じ——音色が違っても音程は同じ、という当たり前の事実を、アルゴリズムの側から眺め直せます。
どう実現するか
一致度の計算は、正体を見るとただの掛け算と足し算です。波形の数の列を $x[n]$ とすると、ずれ幅 $\tau$(タウ)での自己相関は
$$r(\tau) = \sum_{n} x[n] \cdot x[n+\tau]$$
と書けます。各時点の値と、$\tau$ サンプル先の値を掛けて、ぜんぶ足すだけ。波形が重なっていれば「プラス×プラス」と「マイナス×マイナス」ばかりになって合計は大きくなり、ずれていればプラスとマイナスが打ち消し合って小さくなります。「掛けて足す」が一致度の測定器になっているわけです。コードにしてもこれだけです。
/* 自己相関でピッチ検出(C風の擬似コード) */
double best = 0; int bestLag = 0;
for (int lag = minLag; lag <= maxLag; lag++) { /* 試すずれ幅の範囲 */
double r = 0;
for (int n = 0; n + lag < N; n++)
r += x[n] * x[n + lag]; /* ずらして、掛けて、足す */
if (r > best) { best = r; bestLag = lag; }
}
double f0 = fs / (double)bestLag; /* 山の位置 → 周波数 */
実用のチューナーにするには、これに手当てを少し足します。上のデモでは、一致度を −1〜+1 に正規化する、無音のときは判定しない、2周期・3周期の位置に立つ同じくらい高い山(オクターブ下と間違える原因になります)を避けて「最大値の9割を超える最初の山」を選ぶ、山の頂点を前後の点から放物線で補間して小数サンプルの精度まで詰める、という4つを入れています。最後の補間は地味に重要で、これがないと44100分の1秒刻みでしか周期を測れず、高い音ほどセント単位の精度が出ません。
それ、計算量は大丈夫なのか
ここまで読んで、気になった方がいるはずです。1サンプルずつずらして、そのたびに何千個も掛けて足すのだとしたら、とんでもない計算量になるのではないか。しかも音が少しでも変われば、また最初からやり直しになるのではないか、と。
実際の数字を並べてみます。このデモは1秒間に44100回の標本化で音を取り込み、そのうち2048サンプル、時間にして約46ミリ秒ぶんを窓として切り出して相関をとります。ずらし幅は本当に1サンプル刻み、つまり2万分の1秒ずつです。探す範囲は50Hzから1000Hzに対応する44〜882サンプル。したがって1回の判定に必要な計算は、883通りのずれ幅×2048サンプルで、およそ181万回の掛け算と足し算になります。画面の更新に合わせて毎秒60回やれば、毎秒1億回ほどです。
数字だけ見ると気が遠くなりますが、これは現代の計算機にとって軽い部類です。掛けて足すだけの単純なくり返しは、CPUがいちばん得意とする形で、毎秒数十億回のペースで流せます。1回の判定はおよそ1ミリ秒。60分の1秒=16.7ミリ秒の持ち時間に対して、十分すぎるほど余っています。スマホのブラウザで、ほかの描画と同時に動いても平気なのはこのためです。
そして「音が変わったらやり直し」も、そのとおりで、このデモは毎フレーム完全にゼロから計算し直しています。前の結果は一切引き継ぎません。ただ、それで困らない理由があります。46ミリ秒という窓は、人間が音程を感じ取るのに必要な時間とだいたい同じで、これより短く刻んでも音の高さという概念自体が定まりません。つまり「毎回作り直す」のが、そもそも自然な単位なのです。音が変化した瞬間に前の周期を引きずらないという意味でも、やり直しは正しい振る舞いです。
とはいえ、もっと速くする道はあります。自己相関は数学的には畳み込みの一種で、周波数の世界へ移してから計算すると、総当たりの $O(N^2)$ が $O(N \log N)$ に落ちます。今回の条件だと、素朴な総当たりの181万回に対して十数万回、ざっと10倍以上の高速化です。プロ用のチューナーや音楽制作ソフトはこの方式で、その主役が高速フーリエ変換(FFT)と呼ばれる道具です。ここでは原理の見通しを優先してあえて総当たりのままにしていますが、FFTがなぜ速いのかは、この連載の別の回で正面から扱います。
実際の製品やライブラリでよく使われる YIN という手法も、根っこはこの自己相関です。掛け算の代わりに「ずらした自分との差の2乗」を使い、山ではなく谷を探す形に整理して、低いずれ幅を優先しやすくする正規化を加えたもの——つまり、今回の発想の改良版です。核となる「ずらして自分と比べる」は、まったく変わりません。
参考文献
- A. de Cheveigné, H. Kawahara, “YIN, a fundamental frequency estimator for speech and music,” The Journal of the Acoustical Society of America, vol. 111, no. 4 (2002), pp. 1917–1930. DOI: 10.1121/1.1458024 。自己相関系ピッチ検出の実用的な定番となった手法の原論文。
- 青木直史『サウンドプログラミング入門――音響合成の基本とC言語による実装 』技術評論社(2013)。周期と周波数、波形の扱いなど本記事の土台をC言語の実装つきで学べる定番書。
- デモの実装には Web Audio API を使用。AnalyserNode で波形を取り出し、JavaScript で自己相関を計算しています。マイク入力は getUserMedia です。
ずらして、掛けて、足す。それだけでチューナーができてしまいます。まずはデモのマイクに、声でも楽器でも聴かせてみてください。
連載「サウンドプログラミング」
← 前回 無限に上がり続ける音階
