今回思い切って、iOS Advent Calendar2011 に参加してみました。
しかも最終日自分でいいのか?と思いましたが・・・最終回のネタは「MusicPlayer」です。
そして25日ということで、
クリスマスソングを再生させていただきます
MusicPlayer は OSX には既に存在していますが iOS5 から AudioToolbox.framework に追加された機能で、簡単に言うと音楽シーケンサーです。
ドキュメントを見る限り、OSX の機能はフルには使えないようですが、基本的な機能は利用できます。
個人的に作ったアプリ「Chordlead」で、OpenALを利用した簡単な演奏機能をつけました。 が、シーケンス機能は完全に独自で実装してかなり苦労した部分で、もう少し楽にならないか?互換性を保ちたいと考えるようになりました。
MusicPlayer を使うメリットとして、以下の事項があげられると思います。
(1) SMF(Standard MIDI File)をサポートしている (2) MusicSequence (シーケンサー)が利用できる (3) AUGraph によるルーティングで内部音源が再生できる
ただし、MusicPlayer を利用するにしても AVAudioPlayer やSystem Sound を再生するのとは違い、下準備がそれなりに大変です。
今回は MusicPlayer 以外に以下のクラスが頻繁に登場します。
(1) AudioUnit オーディオを生成してアプリケーションに提供する重要なプラグインです。 (2) AUNode オーディオは入力(マイク、音源)、エフェクト、出力と言った経路をたどって音が出ますが、AUNodeはそららを接続する単位です。 (3) AUGraph AUNode をまとめたものが AUGraph です。 AUGraph 同士を接続することもできます。
抽象的ですが、音楽機材を用意してケーブルで繋ぎ合わせるようなイメージでしょうか? と言う事で、実際にコーディングしてみました。
まず、AVAudioSession の設定と、ハードウェアのサンプリングレートを設定します。
AVAudioSession setCategory では AVAudioSessionCategoryPlayback(iPhoneロック時でも再生) を設定し、サンプリングレートは 44.1kHz としました。 また、AVAudioSession では delegate を設定する事で、beginInterruption()、endInterruptionWithFlags () も利用する事ができます。
MusicPlayer を利用する前に、音源設定やオーディオのルーティングをしなければいけません。
まず、NewAUGraph で AUGraph を初期化します。
次に、AudioComponentDescription で AUNode の入力側の設定詳細を設定します。
componentType は、kAudioUnitType_MusicDevice(MIDI受信)componentSubType は kAudioUnitSubType_Sampler (内部音源 = サンプラー)を指します。 作成した AudioComponentDescription の情報を用いて、 AUGraph(_processingGraph) に 音源側の AUNode(_samplerNode1 )を追加します。
同様に、出力用の AUNode(_multiChannelMixerNode) を追加します。
各AUNode に対して AUGraphNodeInfo() を用いて AudioUnit と関連づけます。
AUGraphConnectNodeInput() で出力と入力の AUNode を接続し、AUGraph を設定していきます。
各 AudioUnit の再生、入出力設定し、最終的に AUGraph をスタートします。
AudioUnit でオーディオを扱う場合、AudioUnitSetProperty() で設定し、サンプリングレートやオーディオスライスの調整をしますが、定義は以下の通りです。
AudioUnitPropertyID は AudioUnit の種別 AudioUnitScope は、入出力関連の数値 AudioUnitElement は複数のBUS を利用する際に設定しますが、今回は1つだけなので 0です。 残りの引数は、データサイズを取得する為のものです。
今回の楽器の音源方式は 「aupreset」ファイルを利用して、PCM音源でサウンド再生します。
図のようにサンプリング音のパスは、 aupreset の URL で記述します。
ちなみに、このファイルを作らなくも MusicPlayer ではデフォルトで簡易音源(FM音源)が再生されます。
作成したaupreset ファイルを元に、AudioUnitSetProperty() で kAudioUnitProperty_ClassInfo で設定します。
サンプリング音のデータフォーマットは 今回 caf ファイルを利用しますが、OSX のターミナルでコマンドでコンバートする事ができます。
/usr/bin/afconvert -f caff -d aac piano_Bb.m4a piano_Bb.caf
ちなみにピアノのサンプリング音は、自宅のデジタルピアノから5音サンプリングしました。
さて、ようやく本題です。 まず、MusicSequence のインスタンスを NewMusicSequence() で作成します。
次にリソース内の mid ファイルを MusicSequenceFileLoad() で読み込みます。
MusicSequenceFileTypeID は MIDIファイルなので kMusicSequenceFile_MIDIType MusicSequenceLoadFlags は 現在 kMusicSequenceLoadSMF_ChannelsToTracks しかありません。
NewMusicPlayer() で MusicPlayer インスタンスを生成し、MusicPlayerSetSequence() で先ほど作成した MusicSequence を設定します。
最後にMusicSequence と AUGraph を接続し、シーケンサーを再生します。
と、長々と実装してきましたが、再生はできるものの色々と問題がでてきました。
(1) マルチチャンネルで楽器を再生できない 単にピアノ、ギターなどの単体楽器アプリなら今回のように問題なくMIDI再生できますが、現状バンド形式のように
複数の楽器を演奏する方法がわからず実装できませんでした
AUGraph で音源とBUS 設定でごにょごにょしてやれば可能かも知れませんが、そもそもMusicPlayer側でMIDIチャンネルをちゃんと認識してルーティングできるかが不明です。
(2) MIDI のコントロールチェンジ(CC)は反映されている? 今回、検証用として「HappyXmas」を自分で MIDI データ作ってみたのですが、PC(QuickTime)の再生 と iOS の再生が如実に違います。
特に CC64(サスティン)が効かず(?)ところどころブツブツ切れて不自然です
iOS上では何らかの理由で細かなニュアンスが再現できないようです。 設定が悪いのか?SMFファイルの作りが悪いのか?はたまたCC自体有効にならないのかが不明です。
という訳で、何とか「MusicPlayer」でクリスマスソングを再生することができました。