iOSでのオーディオ処理を担うCore Audioは準OS Xクラスの充実ぶりで優秀なんだけど、それなりに面倒な設定が沢山ある。そこでちょっとは楽ができそうなのがMoMu: A Mobile Music Toolkit。これはスタンフォード大学のCCRMAがSmuleの協力を得て開発し、公開しているモバイル・デバイス向けのライブラリ。
CCRMAは長年コンピュータ音楽の一翼を担ってきた研究所だし、SmuleはOcarinaなどのiOSアプリで有名な、資金調達もガンガンやってるアクティブなベンチャー企業。この2つがタグを組んだライブラリだから期待が高まる。ちなみに、CCRMAの有名な音響合成ライブラリSTK (Synthesize Toolkit) もMoMuから利用できる(とは言え…)。
さて、MoMuはオーディオ処理だけでなく、図のようにグラフィックス、マルチ・タッチ、加速度センサー、位置情報、コンパスなどの機能も提供している。これで結構なことができそうと思う人はいいんだけど、MoMuはC++インターフェースなのが個人的には難あり。そこで、MoMuのオーディオ処理だけを活用させていただくObjective-C++で参ります。以下、手順。
(1) プロジェクトにMoMuから以下のファイルを追加。
mo_audio.h
mo_audio.mm
mo_def.h
(2) ターゲットに以下のフレームワークを追加。
AudioToolbox.framework
(3) MoMuを利用するクラス(例えば〜ViewController)のソース・ファイルの拡張子を .m から .mm に変更。
(4) ソースコード(例えば〜ViewController.mm)で、ヘッダをインポートして定数を定義。
#import "mo_audio.h" #define SRATE 44100 #define FRAMESIZE 128 #define NUMCHANNELS 2
(5) 適切なメソッド(例えばviewDidLoad)でオーディオ処理を初期化し、処理の開始を記述。
MoAudio::init(SRATE, FRAMESIZE, NUMCHANNELS ); MoAudio::start(audioCallback, nil);
(6) オーディオ処理のコールバック関数を記述。これはC言語関数なので@implementationより前(または@endより後)に記述すること。
void audioCallback(Float32 *buffer, UInt32 framesize, void *userData) { }
以上でMoMuが利用できる。シミュレータでも実機でも動作するので、プロジェクトを実行して、グワ〜〜ンとかビィイイ〜とか爆音が響き渡れば成功。そうならない場合は、スピーカーのボリュームが下がっているか、マイクが無効になっているか、処理の記述が間違っているか、のどれかだろうね。
爆音大会になる理由は、MoMuはマイク入力をコールバック関数のbufferに渡し、コールバック関数が終わった時点のbufferをスピーカーから出力するから。つまり、コールバック関数に何も記述しないと、マイク入力がそのままスピーカー出力され、フィードバックが起こってハウリングするというワケ。
ハウリングしないように無音にするには、bufferの内容をすべて0にすれば良い。サイレンス音響合成ってのも、なんだかトキめく(?)が、コールバック関数の中身を次のように記述する。
Float32 *data = buffer; for (int i=0; i<framesize; i++) { *data++ = 0.0; // 左チャンネル *data++ = 0.0; // 右チャンネル }
bufferはFloat32型の配列で、その要素数はframesizeで与えられるが、実際にはチャンネル数だけインターリーブしている。例えば、2チャンネル(ステレオ)なら、最初の要素が左チャンネルの1番目のデータ、次の要素は右チャンネルの1番目のデータ、その次の要素は左チャンネルの2番目のデータ…といった具合。データの値の範囲は-1.0から1.0まで。さらに、startの2番目のパラメータがコールバック関数のuserDataに渡される(ここではnilで何も渡していない)。
ここまで理解できれば、後は勝手にイヂることができるね。ナンチャッテ音響処理をいくつか書いてみよう。繰り返します、ナンチャッテ、です。
まず、ホワイトノイズ。
Float32 *data = buffer; for (int i=0; i<framesize; i++) { Float32 noise = (Float32)rand() / (Float32)RAND_MAX; *data++ = noise; // 左チャンネル *data++ = noise; // 右チャンネル }
次で、サイン波。
Float32 *data = buffer; Float32 frequency = 440.0; Float32 phaseDelta = 2.0 * M_PI * frequency / SRATE; static Float32 phase; for (int i=0; i<framesize; i++) { phase = phase + phaseDelta; Float32 value = sin(phase); *data++ = value; // 左チャンネル *data++ = value; // 右チャンネル }
発展させて、マイク入力にトレモロ。
Float32 *data = buffer; Float32 frequency = 8.0; Float32 phaseDelta = 2.0 * M_PI * frequency / SRATE; static Float32 phase; for (int i=0; i<framesize; i++) { phase = phase + phaseDelta; Float32 value = sin(phase) * 0.5 + 0.5; *data++ = *data * value; // 左チャンネル *data++ = *data * value; // 右チャンネル }
最後に、サンプリング(1秒録音1秒再生の繰り返し)。
Float32 *data = buffer; static Float32 samples[SRATE]; static long index = 0; static bool isSampling = YES; for (int i=0; i<framesize; i++) { if (isSampling) { samples[index] = *data; *data++ = 0.0; // 左チャンネル *data++ = 0.0; // 右チャンネル } else { *data++ = samples[index]; // 左チャンネル *data++ = samples[index]; // 右チャンネル } if (++index >= SRATE) { index = 0; isSampling = !isSampling; } }
さらにイロイロしたい人はMusic DSP Code Archiveあたりを参考に。
はい、おしまい。
【追記】諸般の事情(笑)により、サイン波とトレモロのコードを変更しました。また、演算精度に少々問題がある箇所があります。意図的、または、ナンチャッテです。