iOSでWAVファイルを書き出す場合、
以前はAudioToolKitにあるAudioFileCreateWithURLを使っていましたが、
ここでは試しにiOS8,macOS10.10で追加されたAVFoundationにあるAVAudioFileを使ってみます。
まず、AVAudioFileのクラス定義をみてみます。
open class AVAudioFile : NSObject { public init(forReading fileURL: URL) throws public init(forReading fileURL: URL, commonFormat format: AVAudioCommonFormat, interleaved: Bool) throws public init(forWriting fileURL: URL, settings: [String : Any]) throws public init(forWriting fileURL: URL, settings: [String : Any], commonFormat format: AVAudioCommonFormat, interleaved: Bool) throws open func read(into buffer: AVAudioPCMBuffer) throws open func read(into buffer: AVAudioPCMBuffer, frameCount frames: AVAudioFrameCount) throws open func write(from buffer: AVAudioPCMBuffer) throws open var url: URL { get } open var fileFormat: AVAudioFormat { get } open var processingFormat: AVAudioFormat { get } open var length: AVAudioFramePosition { get } open var framePosition: AVAudioFramePosition }
forWritingというのを使いwriteを呼び出してクラスのメモリを解放すれば良さそうです。
AVAudioPCMBufferの定義を見て見ます。
open class AVAudioPCMBuffer : AVAudioBuffer { public init(pcmFormat format: AVAudioFormat, frameCapacity: AVAudioFrameCount) open var frameCapacity: AVAudioFrameCount { get } open var frameLength: AVAudioFrameCount open var stride: Int { get } open var floatChannelData: UnsafePointer<UnsafeMutablePointer<Float>>? { get } open var int16ChannelData: UnsafePointer<UnsafeMutablePointer<Int16>>? { get } open var int32ChannelData: UnsafePointer<UnsafeMutablePointer<Int32>>? { get } }
フォーマットとキャパシティ(=サンプル数)を指定してインスタンスを作り、データを詰めれば良さそうです。
書き込むフォーマットはWindowsで標準的なInt16、サンプルレート44100,チャネル数2,インターリーブであるWAVファイル形式とします。
書き込むデータはLには440Hzのサイン波、Rには523Hzのサイン波を1024サンプルだけ出力することにします。
音の長さは約0.023秒となります。
以下、実装です。
if let documentsPath = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true).first { let path = documentsPath.appending("/test.wav") let format:AVAudioFormat = AVAudioFormat(commonFormat: AVAudioCommonFormat.pcmFormatInt16, sampleRate: 44100, channels: 2, interleaved: true) if let url = URL(string:path) { do { // WAVファイルのフォーマットを指定する let file:AVAudioFile = try AVAudioFile(forWriting: url, settings: format.settings, commonFormat: format.commonFormat, interleaved: true) // データ書き込みフォーマットを指定する // ここではWAVファイルと同じフォーマットを使う let buffer:AVAudioPCMBuffer = AVAudioPCMBuffer(pcmFormat: format, frameCapacity: 1024) if let p:UnsafePointer<UnsafeMutablePointer<Int16>> = buffer.int16ChannelData { let q:UnsafeMutablePointer<Int16> = p.pointee var x:Float = 0 var y:Float = 0 let delta_x:Float = Float(440 * 2 * M_PI / 44100) // ラ(440Hz) let delta_y:Float = Float(523 * 2 * M_PI / 44100) // ド(523Hz) for i:Int in stride(from:0, to:1024*2, by: 2) { q[i] = Int16(sin(x) * 32767.0) // L q[i+1] = Int16(sin(y) * 32767.0) // R x += delta_x y += delta_y } } else { assert(false) } // メモリに書き込んだサンプル数をセット buffer.frameLength = 1024 // WAVファイルへ書き込み try file.write(from: buffer) } catch { assert(false) } } }
ここでは、WAVファイルのフォーマットとデータを書き込む時のフォーマットを同一にしています。
同じにしておくとコンバートが走らないので効率が良いですが、異なるフォーマットでもある程度の範囲内なら
AVAudioFileが勝手にコンバートしてくれたりします。
経験的にはInterleavedやチャネル数が同じでないと動かないです。
interleaved:trueにした場合AVAudioPCMBufferのInt16の領域が1つになりますのでint16ChannelDataから
サンプル数分LRの順序で詰めていけば良いです。
buffer.frameLengthに書き込む値を毎回セットする必要があります。
AudioToolKitで書くよりソースコードが長くなっている気もしますが、
swiftは型安全性を何より優先しますので、こんな感じになります。
ついでに波形画像も載せておきます。