実践! iPhoneアプリ開発 楽器アプリの作り方

You might also like

Download as pdf or txt
Download as pdf or txt
You are on page 1of 21

今回からは、楽器アプリを取り上げてみる。iPhoneをギターやピアノといった楽器にするアプ

リケーションは、App Storeの売り上げでも人気を博しており、iPhoneアプリの代表格とも言え
るだろう。この連載としては、楽器アプリの作り方を通して、iPhoneのオーディオテクノロ
ジーを概観する事が目的となる。

iPhoneのオーディオフレームワーク
アプリの制作にとりかかる前に、iPhoneが持つオーディオフレームワークを紹介しよう。図示
すると、次のようになると思う。

iPhoneのオーディオテクノロジーは、Core Audioと総称される。その中でも重要なのは、Mac
OS Xに由来するオーディオフレームワークであるAudio ToolboxとAudio Unitだ。この2つのフ
レームワークに、多くの機能が備わっている。その一部を書き出すと、

オーディオファイルの再生、一時停止、ループ

オーディオの録音

様々なオーディオフォーマットの読み込み

オーディオのエフェクト処理

警告音やバイブレーションの再生(バイブレーションはiPod touchでは不可)

などがある。オーディオ関連の処理で必要なものは、ここにそろっている。

OpenALは、標準化されたオーディオAPIである。OpenGLのオーディオ版と考えてもいい。
OpenALは様々なプラットフォームで実装されているので、その使い方に慣れている方は、こ
こから始めるといいだろう。その成り立ちから、ゲームアプリケーションでよく使われている
ようだ。3次元空間での音源位置指定や、ドップラー効果といった、ゲームで便利そうな機能
を備えているのも特徴だ。

AVFoundationは、オーディオプレイヤーを簡単に作るために用意されたフレームワークだ。
オーディオファイルの再生、一時停止、シーク、ボリューム設定、再生時間の取得、などを簡
単に行える。これにユーザインタフェースを被せれば、自分のアプリケーションで簡単にオー
ディオプレイヤーを実現できる。他のフレームワークと比べると、高レベルに位置するものに
なる。

楽器アプリのためのライブラリ
このようなフレームワークがあることをふまえて、楽器アプリを作成する事を考えてみよう。
楽器アプリに求められる要件は、次のようなものである。

オーディオファイルの再生。ユーザが画面上に表示された楽器の一部をタップすると、
用意されたオーディオファイルを再生する。

複数オーディオの同時再生。ユーザが同時に二カ所以上タップした場合は、その数だけ
オーディオを再生する。

オーディオのループ再生。管楽器のように任意の長さの音を演奏できる楽器では、ルー
プ再生する事が必要だろう。

このような条件を考えると、Core Audioの中から楽器アプリ実現に適したライブラリとして、
次のものが考えられる。

System Sound Service

Audio Toolboxに含まれるサービスである。30秒以下のシステム警告音を鳴らすのに使われ
る。打楽器のようなものに対しては、十分な機能と言えるだろう。オーディオライブラリの中
でも、最も少ない手順でオーディオファイルを再生する事ができるのも魅力である。

Audio Queue Service

こちらもAudio Toolboxに含まれるサービスである。オーディオデータのためのバッファを作
成して、そこにデータを追加していく事で再生を行う。再生データを任意に変更する事ができ
るので、好きな箇所でループを再生を行うなど、自由度の高いオーディオ処理が可能となる。
また、自分で波形を作成して鳴らすという、シンセサイザーのようなこともできる。機能が高
い反面、取り扱い方は複雑になる。

OpenAL

OpenALはもともとゲームのために用意されたAPIなので、自由度の高い再生ができる。
OpenGLとの相性もいいので、画面の表示と同期させたいときには適切だろう。また、パ
フォーマンスの点でも有利な事が多い。

これらのライブラリを使って、楽器アプリを作成してみよう。

最後に、一つ重要な点を注意しておく。iPhoneは様々なオーディオフォーマットに対応してい
るが、ハードウェアの制限としてMP3とAACは、一度に一つのファイルしか再生する事ができ
ない。おそらく、フォーマットのデコードをハードウェアで処理しているためであろう。楽器
アプリのように複数オーディオ再生が必要な場合は、AIFFやWAVといったフォーマットで音
源を用意しておこう。
では実際に楽器アプリを作り始めてみる。楽器アプリ作成で大事なのは、オーディオフレーム
ワークの選定だ。前回解説したように、iPhoneは様々な種類のオーディオ技術を提供してい
る。そこで、いくつかの技術を取り上げて、個別の楽器アプリを作ってみよう。

まず使ってみるのは、System Sound Serviceだ。おそらく、これが最も簡単な楽器アプリになる


だろう。実現する楽器は、ベルにしてみる。つまり、鐘の音だ。この楽器を選んだ理由は、後
で説明しよう。

System Sound Serviceの使い方


はじめに、System Sound Serviceについて説明しよう。これは、Audio Toolboxフレームワーク
が提供するサービスだ。警告音のような、短いサウンドを鳴らすために使われるものだ。目安
としては、30秒以下のサウンドだ。iPhoneのバイブレーション機能も、このサービスからアク
セスする。

System Sound Serviceの使い方は、非常に簡単だ。まず、システムサウンドIDを作成する。これ


には、AudioServicesCreateSystemSoundIDという関数を使う。引数として、.aifや.wavといった
オーディオファイルを渡してやればいい。取得したシステムサウンドIDを鳴らすには、
AudioServicesPlaySystemSoundという関数を使う。

たとえば、次のようなソースコードで音を鳴らす事ができるだろう。

List 1.

// オーディオファイルのパスを取得する NSURL* url; url = [NSURL


fileURLWithPath: [[NSBundle mainBundle]
pathForResource:@"C4" ofType:@"aif"]]; // システムサウンドを作
成する AudioServicesCreateSystemSoundID((CFURLRef)url,
&_soundC4); // サウンドを鳴らす
AudioServicesPlayAlertSound(sound);

これだけだ。これで、プロジェクトに追加されているC4.aifというオーディオファイルを鳴ら
す事ができる。

このサービスを使って、楽器アプリを作る事を考えてみよう。まず、必要なオーディオファイ
ル(おそらく12音以上)を読み込み、システムサウンドIDを取得する。そして、画面上のボタン
などが押されたら、それを鳴らす。これだけで楽器アプリとして動作するはずだ。

盤のレイアウトと横回転
早速、アプリを作ってみよう。Xcodeを立ち上げて、新規プロジェクトを作成する。そして、
ユーザインタフェースのレイアウトを行おう。
今回は、ピアノの 盤のようにボタンを配置しておこう。iPhoneを横に持って使うと想定して
おく。

それぞれのボタンには、インスペクタウインドウを使って、tagの値を設定しておく。この値
を使って、どのボタンが押されたか判別する事にしよう。この値は、UIViewクラスのtagプロ
パティから取得できる。
ボタンからは、アクションを接続しておこう。今回は、playSound:という、送り手を引き数に
取るアクションを用意した。すべてのボタンからこのアクションを呼び出す事にする。レイア
ウトとアクションの接続が終わったら、Xcodeに戻ろう。

今回は横画面専用のインタフェースにしたので、アプリケーションの起動時に強制的に横画面
にしておくことにする。applicationDidFinishLaunching:メソッドの中で、この処理を行おう。

List 2.

- (void)applicationDidFinishLaunching:(UIApplication
*)application { // ステータスバーを上にする
[UIApplication sharedApplication].statusBarOrientation =
UIInterfaceOrientationLandscapeRight; // アプリケーション
の表示フレームを取得する CGRect frame; frame =
[UIScreen mainScreen].applicationFrame; // 中心位置を作
成 CGPoint center; center.x =
CGRectGetWidth(frame) * 0.5f; center.y =
CGRectGetHeight(frame) * 0.5f; // バウンズを作成
CGRect bounds; bounds.origin = CGPointZero;
bounds.size.width = CGRectGetHeight(frame);
bounds.size.height = CGRectGetWidth(frame); // アフィン
変換を作成 CGAffineTransform transform; transform
= CGAffineTransformMakeRotation(M_PI_2); // ビューを回転
させる viewController.view.center = center;
viewController.view.bounds = bounds;
viewController.view.transform = transform; // ウィンドウ
を表示する [window addSubview:viewController.view];
[window makeKeyAndVisible]; }

まず、ステータスバーの位置を変更しておく。そして回転を行うのだが、これはビューコント
ローラのビューに対して、transformプロパティを設定してやる事で実現できる。このプロパ
ティはアフィン変換を表すものなので、90度回転する変換を指定してやればいい。

ただし、このとき注意しておきたい事がある。UIViewの大きさや位置を表すプロパティに
frameがあるが、transformのプロパティとして単位行列以外を設定すると、この値は不定とな
る。代わりに、ビューの中心位置を表すcenterと、ビューの大きさを表すboundsといったプロ
パティを使用する必要が出てくるのだ。それらもあわせて設定行っている。

音を鳴らす
続いて、音を鳴らすための設定だ。まず、システムサウンドIDを作成する。これは、ビューコ
ントローラクラスの、viewDidLoadで行っている。

List 3.

- (void)viewDidLoad { // サウンドを読み込む NSURL*


url; url = [NSURL fileURLWithPath:[[NSBundle
mainBundle] pathForResource:@"C4" ofType:@"aif"]];
AudioServicesCreateSystemSoundID((CFURLRef)url,
&_soundC4); url = [NSURL fileURLWithPath:[[NSBundle
mainBundle] pathForResource:@"C#" ofType:@"aif"]];
AudioServicesCreateSystemSoundID((CFURLRef)url,
&_soundCS); ……

このようにして13個のオーディオファイルを読み込んでいる。

音を鳴らすのは、playSound:アクションメソッドだ。

List 4.

- (IBAction)playSound:(id)sender { // タグを取得して、サウ
ンドを決定する SystemSoundID sound =
(SystemSoundID)NULL; switch ([sender tag]) { case
100: sound = _soundC4; break; case 101: sound =
_soundCS; break; case 102: sound = _soundD; break;
case 103: sound = _soundDS; break; case 104: sound =
_soundE; break; case 105: sound = _soundF; break;
case 106: sound = _soundFS; break; case 107: sound =
_soundG; break; case 108: sound = _soundGS; break;
case 109: sound = _soundA; break; case 110: sound =
_soundAS; break; case 111: sound = _soundB; break;
case 112: sound = _soundC5; break; } // サウンドを鳴
らす if (sound) {
AudioServicesPlayAlertSound(sound); } }

このメソッドの引き数であるsenderとしてボタンが送られてくるので、そのタグの値からサウ
ンドを決定している。そして、AudioServicesPlayAlertSoundで音を鳴らせばいい。

これで、十分な機能を持つ楽器アプリの完成だ。だが実際に使ってみると、いくつか不満を感
じるだろう。まず、音量の調整ができていない。また、鳴らした音を途中で止める事ができな
い。System Sound Serviceは、これらの機能は提供していないのだ。

今回の説明で楽器としてベルを取り上げたのは、このようなAPIの制約があるためだ。ベルな
らば、音量や持続時間を変化できなくとも、それらしく聞こえると考えたのだ。

次回は、別のフレームワークを使って楽器アプリを作成して見よう。

ここまでのソースコード: ChurchBell-1.zip
今回は、Audio Queueを使ってみよう。Audio Queueは、Audio Toolboxフレームワークの中にお
いて、主役と呼べるライブラリだ。柔軟なオーディオの再生が可能になる。

楽器アプリを実現するためのライブラリとして、前回はSystem Sound Serviceを使った。今回の


Audio Queueがこれと大きく異なるのは、再生音の繰り返しの有無である。Audio Queueを使え
ば、任意のポイントで繰り返し再生を行う事ができるようになるのだ。これにより、楽器アプ
リの表現力は大きく広がる事になる。

Audio Queueとバッファ
Audio Queueの特徴は、名前の通り、音データをキューイングすることだ。音データを格納す
るためのバッファを複数用意して、そこにデータを格納してキューを作成する。バッファへの
データの格納は、アプリケーションへのコールバックで行われる。

Audio Queueの動作を理解するには、Appleが公開しているドキュメントを見るのが分かりやす
い。次の図が、Audio Queueを使って音を再生するときのプロセスだ。

Audio Queueの動作図(Apple Developer Connectionより)


最初に、アプリケーションはキューとバッファを作成し、初期データをバッファに格納する
(1)。バッファにデータが入ると、それはキューへ移動する。その状態で再生を行うと(2)、
キューにあるバッファを読み込みながら再生が始まる(3)。バッファのデータを読み込み終わ
ると次のバッファへと移動し(4)、空になったバッファがコールバックを使ってアプリケー
ションに通知される(5)。アプリケーションはバッファに必要なデータを満たして、再び
キューに追加するのだ(6)。

これがAudio Queueの再生プロセスだ。バッファへのデータ格納が、キューにあるデータを再
生しきる前に完了する事ができれば、スムーズに音を再生する事ができる。

キューとコールバックによる方式は、多くの利点がある。まず、事前にすべてのオーディオ
データを用意する必要がない。再生する直前に、必要な分だけデータを用意すればいいのだ。
これにより、使用メモリは少なくなる。また、バッファに任意のデータを格納できるとこも注
目だ。この仕組みのため、単純にオーディオファイルの先頭から最後まで再生するといったも
のだけでなく、任意のポイントでループさせたり、別のオーディオファイルのデータを組み合
わせたり、といったことが可能になる。または、プログラム中で波形を作成して再生すると
いった、シンセサイズ機能も実現できる。

欠点をあげるならば、データを格納してから実際に再生されるまでの時間がアプリケーション
から決定できない、ということがある。つまり、レイテンシが長いという事だ。レイテンシを
短くするには、バッファの数を少なくしたり、サイズを小さくすればいい。だがそうすると、
バッファへのデータ格納が再生時間に間に合わないという自体が発生する。これは、音飛びと
いう現象になって現れてしまう。

以上のことから、Audio Queueは長時間の音楽再生には向くが、ゲームのようにユーザの反応
にリアルタイムに即応するタイプのものには不向きと言えるだろう。

データフォーマットの決定とAudio Queueの作成
では、Audio Queueの使い方を説明しよう。先に言っておくが、なかなか面倒なところがあ
る。

Audio Queueとバッファを作成して、音を再生する準備を整えるには、以下のような手順が必
要になる。

1. 再生するオーディオのデータフォーマットを決定する

2. データフォーマット、コールバック関数などを指定して、Audio Queueを作成する

3. バッファの数と大きさを決定する

4. バッファを確保する
まず、Audio Queueに渡すデータのフォーマットを決定する必要がある。もしオーディオファ
イルを再生するのであれば、そのオーディオフォーマットを取得する事になる。データフォー
マットは、AudioStreamBasicDescription構造体で指定する。

List 1.

struct AudioStreamBasicDescription { Float64


mSampleRate; UInt32 mFormatID; UInt32
mFormatFlags; UInt32 mBytesPerPacket; UInt32
mFramesPerPacket; UInt32 mBytesPerFrame; UInt32
mChannelsPerFrame; UInt32 mBitsPerChannel;
UInt32 mReserved; };

この構造体で注目してほしいのは、mFormatIDフィールドだ。ここにオーディオフォーマット
を指定する事ができる。実はAudio Queueでは、MP3やAACの再生を行う事ができる。それら
のフォーマットはここに指定するのだ。

もしオーディオファイルからデータを読み込むのであれば、そのフォーマットは、Audio File
ライブラリのAPIである、AudioFileGetPropertyを使って取得する事ができる。次のようなソー
スコードになるだろう。

List 2.

OSStatus status; UInt32 size; // オー


ディオファイルを開く AudioFileID audioFileId; status =
AudioFileOpenURL( (CFURLRef)[NSURL
fileURLWithPath:path],
kAudioFileReadPermission, 0, &audioFileId); // オーディ
オデータフォーマットを取得する AudioStreamBasicDescription
audioBasicDesc; size = sizeof(audioBasicDesc);
status = AudioFileGetProperty( audioFileId,
kAudioFilePropertyDataFormat, &size, &audioBasicDesc);

まず、AudioFileOpenURLを使ってオーディオファイルをオープンする。そして、
AudioFileGetPropertyを、kAudioFilePropertyDataFormatを指定して呼び出す。これで、データ
フォーマットが取得できるのだ。

続いて、Audio Queueを作成しよう。Audio Queueには、再生用と録音用のものとがある。今回


使うのは、再生用だ。AudioQueueNewOutputを使って作成する。
List 3.

// オーディオキューを作成する AudioQueueRef
audioQueue; status = AudioQueueNewOutput(
&audioBasicDesc, audioQueueOutputCallback, self,
CFRunLoopGetCurrent(), kCFRunLoopCommonModes,
0, audioQueue);

第一引き数に先ほど取得したデータフォーマット、第二引き数にデータを核のするためのコー
ルバック関数を指定する。コールバック関数は、次のような型のものになる。

List 4.

typedef void (*AudioQueueOutputCallback) (


void *inUserData, AudioQueueRef
inAQ, AudioQueueBufferRef inBuffer );

第二引き数がAudio Queue、第三引き数がこの後に説明するバッファ型になっている。

バッファサイズの決定とバッファの作成
続いて、バッファを作成しよう。まず、バッファの数と大きさを決定する必要がある。

バッファの数は任意だ。多くすればその分再生が安定する。その代わりに、レイテンシが大き
くなる。ここは、アプリケーションの動作を見ながら調整する事になる。

問題は、バッファの大きさだ。詳しくは後述するが、実はバッファにオーディオデータを書き
込む際は、それが何パケット分のデータに相当するかを指定する必要がある。適当にデータを
書き込むだけではだめなのだ。従って、バッファに何パケットのデータを書き込み、そのサイ
ズがいくつなのか、を知る必要がある。

これは、非圧縮データであれば簡単だ。パケット毎のデータサイズは固定になる。問題はMP3
やAACといった、圧縮データの場合だ。パケットサイズが可変になる。こういったときには、
そのオーディオファイルにある最大パケットサイズを求めて、それをサポートできるようにし
よう。そして、任意の時間のデータを格納できるようにバッファサイズを決定する。

List 5.

// 最大パケットサイズを取得する UInt32 maxPacketSize;


size = sizeof(maxPacketSize); status =
AudioFileGetProperty( &audioFileId,
kAudioFilePropertyPacketSizeUpperBound, &size,
&maxPacketSize); // 単位時間あたりのパケット数を計算する
Float64 numPacketsPerTime; numPacketsPerTime =
audioBasicDesc.mSampleRate /
audioBasicDesc.mFramesPerPacket; // 0.5秒分のパケットデー
タサイズを計算する UInt32 bufferSize; bufferSize =
numPacketsPerTime * maxPacketSize * 0.5;

バッファサイズは決定できた。あとは、バッファを作成すれば、完了である。

List 6.

// オーディオキューバッファを作成する
AudioQueueBufferRef audioBuffer; status =
AudioQueueAllocateBuffer(audioQueues, bufferSize,
&audioBuffers);

これでキューとバッファの準備は整った。次回は、バッファにデータを格納するコールバック
について説明しよう。
前回はAudio Queueとバッファを作成するところまで説明を行った。今
回は、バッファにデータを格納して、音を再生する手順を説明しよう。

オーディオキューからのコールバック
Audio Queueにオーディオデータを渡すには、作成時に登録したコール
バック関数を使う事になる。コールバックの仕組みに関する大枠は、前
回も紹介したAppleのドキュメントを参考にしてほしい。

コールバック関数自体は、C関数として登録する事になる。だが、この
関数の中で様々な処理を行うのは都合が悪い。なぜなら、Audio Queue
の管理はObjective-Cのコントロールクラスで行っているからだ。そこ
で、コールバックであるC関数からは、すぐにObjective-Cメソッドを呼
び出してしまうようにしよう。

List 1.

static void audioQueueOutputCallback(


void* userData, AudioQueueRef audioQueue, AudioQueueBufferRef audioQ
{
[(OrganViewController*)userData
_audioQueueOutputWithQueue:audioQueue queueBuffer:audioQueueBuff
}

第一引き数はアプリケーションが任意に指定できる値なので、ここに
Objective-Cのインスタンスのポインタを入れておく。これを使えば、適
当なメソッドに処理をパススルーすることができるだろう。

パケットデータの読み込み
コールバックから続く処理では、まずオーディオデータを読み込みを行
う。これには、Audio FileライブラリにあるAudioFileReadPacketsを使う
事ができる。これを使えば、指定したパケットの大きさだけデータを読
み込む事ができる。さらに、読み込んだパケットデータの詳細情報を知
るために、AudioStreamPacketDescriptionという構造体が用意されてい
る。この構造体の定義は、次のようになっている。

List 2.
struct AudioStreamPacketDescription {
SInt64 mStartOffset;
UInt32 mVariableFramesInPacket;
UInt32 mDataByteSize;
};

実はAudio Queueにパケットデータを設定するときは、この情報もあわ
せて指定してやる必要がある。あらかじめインスタンス変数などにバッ
ファを確保しておこう。

パケットデータを読み込むための呼び出しは、次のようになるだろう。

List 3.

// パケットデータを読み込む
UInt32 numBytes;
UInt32 numPackets = _packetSizeToRead;
status = AudioFileReadPackets(
_audioFileId, NO, &numBytes, &_audioPacketDesc,
_startPacketNumber, &numPackets, audioQueueBuffer->mAudioData);

AudioFileReadPacketsには、7つの引き数がある。これらを説明しよう。
まず第一引き数は、オープンしているオーディオファイルを表すAudio
File ID。第に引き数は、データをキャッシュするかどうか指定するも
の。第三引き数は、読み込んだバイト数を出力するためのもの。第四引
き数は、パケットの詳細情報を表すAudioStreamPacketDescriptionを出力
するためのもの。第五引き数は、読み込むパケットの開始位置を表すも
の。先頭から読み込む場合は0になり、続きから読み込みたい場合は順
次インクリメントさせていく。第六引き数は、読み込むパケット数。こ
のパケット数は、前回単位時間あたりのパケット数を計算したので、そ
こから求める事ができるだろう。そして第七引き数は、読み込んだパ
ケットデータを格納するバッファになる。これには、AudioQueueBuffer
を直接している。

これでオーディオファイルからパケットデータを読み込む事ができる。
かなり複雑だ。これは、オーディオデータを単なるバイト列として扱う
のではなく、パケットごとに区切られたデータとして見る必要があるた
めだ。

データの読み込みに成功したら、これをAudio Queueに追加しよう。

List 4.

// 読み込みに成功した場合
if (numPackets > 0) {
// バッファの大きさを、読み込んだパケットデータのサイズに設定する
audioQueueBuffer->mAudioDataByteSize = numBytes;

// バッファをキューに追加する
status = AudioQueueEnqueueBuffer(
audioQueue, audioQueueBuffer, numPackets, _audioPacketDescs[

再生音のループ
これでデータの読み込みは行えたのだが、もう1つ考慮しなくてはいけ
ないものがある。それは、再生音のループだ。ここでは楽器アプリを
作っているのだが、ドラムやピアノのように、単純に用意した音を一度
だけ再生すれば十分なものもあるが、オルガンや管楽器のように、ボタ
ンを押している間は継続して音を鳴らす必要があるものもある。この場
合、音のループ再生を行わなくてはいけない。

しかも、楽器のシミュレートの場合は、単純なループではだめである。
なぜなら、楽器音は始まりの音(アタック音)と、伸ばしているときの音
(経過音)とで構成されているからだ。単にループするとアタック音が何
度も再生されてしまうので、適切な経過音のポイントに戻ってループを
行う必要がある。

Audio Queueでは、バッファに書き込むデータのパケット位置を管理す
る事で、これらを実現する事ができる。具体的には、読み込んだパケッ
ト位置を覚えておき、ループ終了点を超えたら、ループ開始点まで戻し
てやる事になる。次のようなソースコードで対応できるだろう。

List 5.

// パケット位置を動かす
_startPacketNumber += numPackets;
// ループの処理を行う
if (_startPacketNumber > 40) {
_startPacketNumber = 8;
}

ここでは、40パケット目がループの終了地点。8パケット目がループの
開始地点となっている。

気をつけなくてはいけないのは、一度音が止まったら、再び音を出すと
きは0パケット目から再生する必要がある事だ。8パケット目はループの
戻り地点なので、ここからスタートすることはない。

これらを組み込んだ、アプリケーションのソースコードを公開しよう。
楽器としては、オルガンをシミュレートしている。ボタンを押している
間、音が鳴り続けているのが分かると思う。ただし、ループの継ぎ目が
あまりきれいではない。違和感のないループ音を作成するには、そのた
めの技術が必要だ。今回はAudio Queueの説明に重きを置いたため、そ
こは省略させていただいた。

次回は、OpenALを使った再生について議論しよう。

ここまでのソースコード: RSS-3.zip
今回は、楽器アプリを作るための、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

You might also like