前回の最後に予想した通り、今回の目的にはAVAudioPlayerよりAVAudioUnitSamplerというクラスを使う方が良さそう。loadAudioFilesAtURLs()というメソッドがあり、これで複数のファイルを読み込むことができる。他に音声を扱うにはMedia Player framework、Audio Toolbox framework、Audio Unit framework、OpenAL frameworkがある。旧バージョンはOpenALを使用した。
AVAudioUnitSamplerはiOS 8から使えるAPIで、これを採用するということは動作環境がiOS 8以上である必要がある。2月2日時点のシェアは72%とのこと。このページで確認できる。
loadAudioFilesAtURLs(_ audioFiles: [AnyObject]!, error outError: NSErrorPointer)というメソッドで複数のファイルを読み込める。AVAudioUnitMIDIInstrumentクラスを継承しているので、音を鳴らすにはAVAudioUnitMIDIInstrumentクラスのメソッドを利用することになりそうだ。
…と思ったが全く使い方が分からなかった。いくつか検索して調べた結果、音声ファイルにメタデータを入れておくとかファイル名に音階を含むとMIDIと同様に扱えるなどと書いてある。任意のMIDIノート番号とチャンネルに、任意の音声ファイルをアサインできれば便利な気がするが、どうもそういう使い方はできないようだ。この方向は諦め、OpenALで実装しようかと思ったが、OpenALはCで書かれていて、Cで書かれたものをSwiftで使う方法がよく分からなかった。今後の課題とする。
同じくiOS 8から使えるAVAudioFileクラスのreadIntoBuffer(_ buffer: AVAudioPCMBuffer!, error outError: NSErrorPointer)メソッドを使うと、音声データをメモリに読み込めて遅延のない再生ができそうだが、全てのデータをメモリに入れられるのか、自分でうまく管理する必要があるのかどうかは検証が必要。
音関係の情報は相変わらず少ない。Objective-Cではそこそこ見つかるのだけど、Swiftで参考になるものがほとんどない(だからこそできたら良かったんだけど)。
ということで既にそういう課題を解決してそうなライブラリの力を借りることにする。ゲームなどでよく使われているCocos2Dも検討したが(内部ではOpenALを使っているらしい)、AudioKitが目的に合ってそう。サウンドエンジンはCsoundとのこと。学生時代に触って意味不明で挫折したけどアカデミックな電子音楽ではよく使われている(はず)。
CocoaPodsを入れる
ライブラリの管理はCocoaプロジェクト用のパッケージマネージャーであるCocoaPodsを使う。
pod setup
でローカルにリポジトリを作る。これがないとpod installしようとしてもエラーになる。
pod init
でPodfileを作成し、
target 'Dropophone' do pod 'AudioKit', '~> 1.2' end
と書いて
pod install
これでAudioKitを使う準備ができる。今後は.xcworkspaceからプロジェクトを開くようにする。
SwiftでObjective-Cのプログラムを使用する
AudioKitはObjective-Cで書かれているので、Swiftで使用するにはブリッジが必要。[プロジェクト名]-Bridging-Header.hというファイルに(なければ作る)、
#import "AKFoundation.h" #import "AKTools.h"
と書く。.xcodeprojファイルの「Build Settings」に「Swift Compiler」という項目があるので、「Objective-C Bridging Header」に先ほどのヘッダファイルを設定する。これでAudioKitのクラスが使えるようになる。
音声ファイルの追加
Finder上(Terminalでもいいけど)でsoundsというフォルダに全ての音声ファイルを格納し、XcodeのSupporting Filesの中にドロップする。ラジオボタンは「Create Groups」を選ぶ。これはFinder上の階層構造とは関係なく、MainBundleに対してフラットにリソースを格納することを意味する。「Create folder references」を選ぶと、finder上の階層構造を反映するようになるので、リソースを読み込む際にファイルまでのパスや、サブディレクトリを指定する必要がある。状況に応じて使い分けたい。
音声ファイルの形式はAIFFにしておく。
AudioKitでサウンドファイルを鳴らす
AudioKitはさすがにCsoundをベースにしているだけあって、サウンドファイルを読み込むクラスにplayしたら音が鳴るみたいな単純さではない。Expamleに音声ファイルを鳴らすプロジェクトがあるが、今のところObjective-Cで、かつクラス構成も少し複雑になっているので、シンプルに音を鳴らすための手順が分かりにくい。
色々と触った結果、
- 楽器(AKInstrument)を定義し、出力(AKAudioOutput)に繋ぐ。
- ノート(AKNote)のinstrumentプロパティに楽器を設定する。
- オーケストラ(AKOrchestra)に楽器を追加。
- オーケストラをstartし、ノートをplayすると音が鳴る。
というような流れがあるようだ。Csoundを知っていると分かりやすいのかな。
コードをgistに置いたので、見比べると大体分かると思う。
GUI
もうひとつ考慮したいのは、30個のボタンを配置する方法。前回は頑張って30本のアウトレットを繋いだが、今回は手作業ではなくプログラムで何とかしたい。
経験的に、プログラムから制御したいことがあると「programmatically」という語を含めて検索する。
button1.setTranslatesAutoresizingMaskIntoConstraints(false)
とすることでこれから定義するAuto Layoutを適用することができる。Auto Layoutに関してはまだまだ分かっていないことが多いのでひとまず適当にボタンが並べた。何故か最初のボタンがでかい。
UIButtonのインスタンスにイベントを設定。tagを利用して、どのボタンが押されたのかを識別する。
これでボタンに各音源がアサインできたので、応用すればシンプルなパッドのような楽器になる。次はAuto Layoutを掘り下げながら設計を考え直す。
実機でも動作確認済み。たまに音量がクリップするが、連打や同時発音など問題なさそう。