今回は、楽器アプリを作るための、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