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あたりを参考に。
はい、おしまい。
【追記】諸般の事情(笑)により、サイン波とトレモロのコードを変更しました。また、演算精度に少々問題がある箇所があります。意図的、または、ナンチャッテです。