iPhoneでも固定小数点演算が必要

ラッパ、鼻歌、街の音。そこから音を取り出して譜面にする。

あるキーとあるビートだとどういう音符が出てくるのか、それを眺めて楽しむ。

ただそれだけだが、かなり大変である。

音を拾って、そこから基本周波数(ピッチ、音の高さ)を割り出すには、最も効率が良いと考えられるアルゴリズムでも、次のようなステップが必要だ。

  1. ウィンドウウィング(波形調整)
  2. 高速フーリエ変換
  3. その結果とそれぞれの複素共役との二乗和、それの二乗根算出
  4. 高速フーリエ変換

結論から言うと、floatやdoubleを使ってこれらを実行すると、iPhoneでまともに動かない。これらの計算を省いて簡単なサンプルを返すようにするとスムーズに動くので、明らかにこれらの計算がボトルネックになっていた。

そこで、予想した通り、やっぱり固定小数点演算に戻ってきた。iPhoneでは、8.24という固定小数点サンプルデータをマイクから拾って送ってくる。これは例えば、

0x11FFFFFF

のようなデータで、11という上位8ビットが整数部、FFFFFFの下位24ビットが小数点部になっている。さらにこれは符号付きなので、この中でマイナスも表現する。

そこで、SFixedInt32というクラスを定義して、floatの代わりに実行してみたら、空一面に広がる星のように譜面上に点が出てきてしまった。

追跡してみると、逆フーリエ変換する時に、符号付き8ビットの上限である127を簡単にぶち破って、オーバーフローしていた。そのため、値がでらためになって、てんでばらばらな出力となってしまった。さて、じゃ、どうするか?

値を見てみると、逆フーリエ変換した時、3桁のデータも出てきている。となると、符号付きで考えると上下1000ぐらいは欲しい。そうすると11ビットだが、切りのいいところで12ビット、12.20というのを、SFixedInt32の内部表現にしてみた。

すると予想通り逆フーリエ変換した値が、オーバーフローすることなく入り、きちんとピッチが画面に出てきた。

ようやくできたとiPhoneにつないでみると、依然としてまともに動かない。

9月中になんとかしようと思ったのに、そうして10月1日となってしまった。

今朝もう一度よくSFixedInt32のコードを見てみると、2の24乗であるpow(2, 24)とかがたくさん入っている。これはdoubleなので、SFixedInt32の外では浮動小数点演算が無くなったが、なに、ただ中へ移っただけのことだった。これじゃ速くなるはずがない。

そこで、2の1乗は2、2乗は4、というように紙に書き出し、内部で使われる乗数を事前に計算しておくことにした。

static const SInt32 p220 = 1024 * 1024;

といった具合である。そしてそれを、例えばかけ算のところで、

SFixedInt32 operator* (const SFixedInt32& other) const
{
// -20 = 20 - (20 + 20)
SInt64 m = (SInt64)value * (SInt64)other.value;
return SFixedInt32((SInt32) (m / p220), true);
}


といったようにして、すべて整数の四則演算になるようにした。

するとようやく。ほんとようやく、iPhone上で動くようになった。

まだまだ音符の判別ときれいなグラフィックスの描画をしなきゃいけないのに、やれやれである。