今回は、楽器アプリを作るための、3つめのフレームワークを紹介しよう。OpenALだ。
OpenALとは
OpenALは、オープンな標準に基づくオーディオライブラリだ。名前から想像できると思うが、3Dグラフィックの分野で標準の地位を築いたOpenGLのようなものを、オーディオの分野でも作ろうとしているものだ。そのような動機であるため、単なるオーディオの再生だけではなく、ドップラー効果や3D音響などゲームの役に立つAPIが色々と定義されている。
OpenALプログラミングは、3つの要素から構成される。Buffer、Source、Listenerだ。Bufferは、オーディオデータを管理するものだ。Sourceは、Bufferのデータを使い、音を再生するものになる。OpenALの面白いところは、このSourceを3D空間中に自由に配置できる事だ。たとえば、右前方、左後方などにSourceを置く事ができる。これにより、3D音響の様々な効果を期待できる。最後に、それらの音を聞くものがListenerとなる。
OpenALはオーディオの再生に特化したライブラリであり、汎用的なオーディオライブラリとしてみると機能は少ない。たとえば、mp3やaacといったオーディオフォーマットのデコードには対応していない。OpenALで再生を行うときは、データをリニアPCMの形で渡してやる必要がある。
となると、アプリケーション側ではなんらかの対応が必要になる。対応の1つは、再生するオーディオをすべてリニアPCMの形で持っておく事だ。だが、これではアプリケーションのファイルサイズが非常に大きいものになってしまう。そこで考えられるもう1つの対応は、OpenALを使う前に、オーディオファイルのデコードをプログラム中で行う事だ。この手順について説明しよう。
オーディオファイルのデコード
iPhoneでは様々な種類のオーディオフレームワークを使う事ができるが、最も機能の種類が豊富なのは、Audio Toolboxフレームワークだ。前回紹介した、Audio QueueやAudio Fileなどもこれに含まれる。
このフレームワークを使えば、mp3やaacといったオーディオファイルを、リニアPCMの形に変換する事ができる。オーディオフォーマットの変換にはAudio Converterというライブラリを使う事ができる。ここでは、この変換を直接オーディオファイルから行う事ができる、Extended Audio Fileライブラリを使う事にしよう。
このライブラリの使い方は、iPhone SDKに付属するサンプルである、oalTouchを参考にするのがいい。このサンプルには、MyGetOpenALAudioDataという関数が実装されている。この関数でオーディオファイルを読み込み、リニアPCMに変換しているのだ。
少し長くなるが、この関数を見やすくしたものをここに掲載しておこう。
List 1.
void* GetOpenALAudioData(
CFURLRef fileURL, ALsizei* dataSize, ALenum* dataFormat, ALsizei *sampleRate)
{
OSStatus err;
UInt32 size;
// オーディオファイルを開く
ExtAudioFileRef audioFile;
err = ExtAudioFileOpenURL(fileURL, &audioFile);
if (err) {
goto Exit;
}
// オーディオデータフォーマットを取得する
AudioStreamBasicDescription fileFormat;
size = sizeof(fileFormat);
err = ExtAudioFileGetProperty(
audioFile, kExtAudioFileProperty_FileDataFormat, &size, &fileFormat);
if (err) {
goto Exit;
}
// アウトプットフォーマットを設定する
AudioStreamBasicDescription outputFormat;
outputFormat.mSampleRate = fileFormat.mSampleRate;
outputFormat.mChannelsPerFrame = fileFormat.mChannelsPerFrame;
outputFormat.mFormatID = kAudioFormatLinearPCM;
outputFormat.mBytesPerPacket = 2 * outputFormat.mChannelsPerFrame;
outputFormat.mFramesPerPacket = 1;
outputFormat.mBytesPerFrame = 2 * outputFormat.mChannelsPerFrame;
outputFormat.mBitsPerChannel = 16;
outputFormat.mFormatFlags = kAudioFormatFlagsNativeEndian | kAudioFormatFlagIsPacked | kAudioFormatFlagIsSignedInteger;
err = ExtAudioFileSetProperty(
audioFile, kExtAudioFileProperty_ClientDataFormat, sizeof(outputFormat), &outputFormat);
if (err) {
goto Exit;
}
// フレーム数を取得する
SInt64 fileLengthFrames = 0;
size = sizeof(fileLengthFrames);
err = ExtAudioFileGetProperty(
audioFile, kExtAudioFileProperty_FileLengthFrames, &size, &fileLengthFrames);
if (err) {
goto Exit;
}
// バッファを用意する
UInt32 bufferSize;
void* data;
AudioBufferList dataBuffer;
bufferSize = fileLengthFrames * outputFormat.mBytesPerFrame;;
data = malloc(bufferSize);
dataBuffer.mNumberBuffers = 1;
dataBuffer.mBuffers[0].mDataByteSize = bufferSize;
dataBuffer.mBuffers[0].mNumberChannels = outputFormat.mChannelsPerFrame;
dataBuffer.mBuffers[0].mData = data;
// バッファにデータを読み込む
err = ExtAudioFileRead(audioFile, (UInt32*)&fileLengthFrames, &dataB uffer);
if (err) {
free(data);
goto Exit;
}
// 出力値を設定する
*dataSize = (ALsizei)bufferSize;
*dataFormat = (outputFormat.mChannelsPerFrame > 1) ? AL_FORMAT_STEREO16 : AL_FORMAT_MONO16;
*sampleRate = (ALsizei)outputFormat.mSampleRate;
Exit:
// オーディオファイルを破棄する
if (audioFile) {
ExtAudioFileDispose(audioFile);
}
return data;
}
基本的な流れを紹介しておく。まず、オーディオファイルをオープンする。これには、ExtAudioFileOpenURLを使う。そして、オーディオファイルに対して、アウトプットフォーマットを指定する事になる。このフォーマットに応じた形に変換が行われる事になる。ここでは、リニアPCMを表すkAudioFormatLinearPCMを指定している。そして変換したデータを格納するためのバッファを用意して、ExtAudioFileReadを使ってデータを読み込めばいい。
これで、mp3やaacといったオーディオフォーマットを、プログラム中で変換する事ができるようになる。この方式の利点は、オーディオリソースのファイルサイズを抑えられる事。それに対して欠点は、変換のための時間がかかる事になる。変換しなくてはいけないデータが多いときは、できるだけ分散して変換を行い、ユーザを待たせないように気をつける必要があるだろう。
OpenALの使い方
では、実際にOpenALを使って楽器アプリを作ってみよう。今回作成するのは、ドラムだ。とりあえず、このような画面を用意した。
OpenALを使うには、まず初期化を行う必要がある。これは、まずOpenALデバイスを開き、そこにOpenALコンテキストを作成する、という手順で行う。
List 2.
// OpneALデバイスを開く
ALCdevice* device;
device = alcOpenDevice(NULL);
// OpenALコンテキスを作成して、カレントにする
ALCcontext* alContext;
alContext = alcCreateContext(device, NULL);
alcMakeContextCurrent(alContext);
次に、Bufferを作成する。これには、alGenBuffersという関数を使う。そして、これにオーディオデータを設定する。
List 3.
// バッファを作成する
alGenBuffers(1, _buffers);
// サウンドファイルパスを取得する
NSString* fileName = @"BD";
path = [[NSBundle mainBundle] pathForResource:fileName ofType:@"m4a"];
// オーディオデータを取得する
void* audioData;
ALsizei dataSize;
ALenum dataFormat;
ALsizei sampleRate;
audioData = GetOpenALAudioData(
(CFURLRef)[NSURL fileURLWithPath:path], &dataSize, &dataFormat, &sampleRate);
// データをバッファに設定する
alBufferData(_buffers[0], dataFormat, audioData, dataSize, sampleRate);
ここでは、先ほど紹介したGetOpenALAudioData関数を使って、オーディオファイルをリニアPCMに変換している。そして、alBufferData関数を使ってデータをBufferに設定しているのだ。
そして、Sourceを作成する。作成したら、Bufferを関連付けてやる。
List 4.
// ソースを作成する
alGenSources(1, _sources);
// バッファをソースに設定する
alSourcei(_sources[0], AL_BUFFER, _buffers[0]);
これで準備完了だ。あとは、オーディオを再生してやればいい。これには、alSourcePlayを使う。
List 5.
// オーディオを再生する
alSourcePlay(_sources[0]);
これがOpenALを使ったオーディオの再生だ。あとは、画面上のそれぞれのボタンで、バスドラムやスネアドラムの音を鳴らすようにしてやれば、楽器アプリの完成だ。
ここまでのソースコード: Drums-1.zip