iOSでサイン波を鳴らす (Swift4 × AudioUnit)

# はじめに
この記事は [DMM.com Advent Calendar 17日目](https://qiita.com/advent-calendar/2018/dmm)の記事です。

iOSアプリ内で音を鳴らすには様々な方法があります。
今回は、Audio Unitフレームワークを用いた音の再生について紹介します。

# 使用環境
- Swift 4.1
- XCode 9.2
- iOS 11

# Audio Unitとは
Audio Unitとは、オーディオ処理を行うためのソフトウェアプラグイン規格を指します。

iOSおよびMacOS Xでは、オーディオを処理するためのライブラリやフレームワークの総称をCore Audio [^1] と呼んでおり、Audio UnitはCore Audio内で定義されているものの一つになります。

今回は、Audio Unitの中でも、オーディオの入出力を担当しているRemote IO Unitを使用して、音を再生します。

# 音を鳴らしてみる

## クラス作成
音を鳴らすための適当なクラスを作ります。
今回は`Audio Generator`という名前にしています。

`sampleRate`は、１秒間のフレーム数 (サイン波を計算し、値を書き込む回数) を表します。

```swift
import Foundation
import AVFoundation

/// Audio Unitを使って、音を再生するクラス
class AudioGenerator {
    
    var audioUnit: AudioUnit?
    static let sampleRate: Float = 44100.0 // サンプリングレート
    static let toneA: Float = 440.0 // 440Hz = ラの音
    static var frame: Float = 0 // フレーム数
    
    /// 初期化
    init () {
        prepareAudioUnit()
    }
}
```

## 初期化
音を再生するための初期化を行います。
初期化では、主に以下の3つが必要になります。

- Remote IO Unitのインスタンス化
- コールバック関数の設定
- Remote IO Unitのオーディオデータフォーマットの設定

```swift
/// Audio Unitを使用する準備をする
func prepareAudioUnit() {
    // この中に処理を書く
}
```

### Remote IO Unitのインスタンス化

Audio Unitでは、Audio Componentというプラグインの形式をとっています。
`AudioComponentDescription`で、どのようなAudio Componentを使用するかを定義し、インスタンス化した後、初期化を行います。

```swift
// RemoteIO AudioUnitのAudioComponentDescriptionを作成
var acd = AudioComponentDescription()
acd.componentType = kAudioUnitType_Output // カテゴリの指定
acd.componentSubType = kAudioUnitSubType_RemoteIO // 名前の指定
acd.componentManufacturer = kAudioUnitManufacturer_Apple // ベンダー名
acd.componentFlags = 0 // 使用しない
acd.componentFlagsMask = 0 // 使用しない
        
// Audio Componentの定義を取得
let audioComponent: AudioComponent = AudioComponentFindNext(nil, &acd)!
        
// インスタンス化
AudioComponentInstanceNew(audioComponent, &audioUnit)

// 初期化
AudioUnitInitialize(audioUnit!);
```

### コールバック関数の設定

コールバック関数は、Remote IO Unitの処理が開始された時、システムにより自動的に繰り返し呼ばれる関数です。
AURenderCallbackStruct構造体を作成し、`AudioUnitSetProperty()`で、コールバック関数の登録を行います。

```swift
// AURenderCallbackStruct構造体の作成
var callbackStruct: AURenderCallbackStruct = AURenderCallbackStruct(
    inputProc: renderCallback, // コールバック関数の名前
    inputProcRefCon: &audioUnit // コールバック関数内で参照するデータ
)
        
// コールバック関数の設定
AudioUnitSetProperty(
    audioUnit!, // 対象のAudio Unit
    kAudioUnitProperty_SetRenderCallback, // 設定するプロパティ
    kAudioUnitScope_Input, // 入力スコープ
    0, // バスの値(出力なので0)
    &callbackStruct, // プロパティに設定する値
    UInt32(MemoryLayout.size(ofValue: callbackStruct)) // 値のデータサイズ
)
```

### Remote IO Unitのオーディオデータフォーマットの設定

AudioStreamBasicDescription構造体を設定し、Audio Unitに設定します。

```swift
// ASBDの作成
var asbd = AudioStreamBasicDescription()

asbd.mSampleRate = Float64(AudioGenerator.sampleRate) // サンプリングレートの指定
asbd.mFormatID = kAudioFormatLinearPCM // フォーマットID (リニアPCMを指定)
asbd.mFormatFlags = kAudioFormatFlagIsFloat // フォーマットフラグの指定 (Float32形式)
asbd.mChannelsPerFrame = 1 // チャンネル指定 (モノラル)
asbd.mBytesPerPacket = UInt32(MemoryLayout<Float32>.size) // １パケットのバイト数
asbd.mBytesPerFrame = UInt32(MemoryLayout<Float32>.size) // 1フレームのバイト数
asbd.mFramesPerPacket = 1 // 1パケットのフレーム数
asbd.mBitsPerChannel = UInt32(8 * MemoryLayout<UInt32>.size) // 1チャンネルのビット数
asbd.mReserved = 0 // 使用しない
        
// AudioUnitにASBDを設定
AudioUnitSetProperty(
    audioUnit!, // 対象のAudio Unit
    kAudioUnitProperty_StreamFormat, // 設定するプロパティ
    kAudioUnitScope_Input, // 入力スコープ
    0, // 出力バス
    &asbd, // プロパティに設定する値
    UInt32(MemoryLayout.size(ofValue: asbd)) // 値のデータサイズ
)
```

以上で初期化処理は完了です。

## コールバック関数の作成
コールバック関数の中身をクラス内に定義します。
コールバック関数には、AURenderCallback型が適用されます。

サイン波を鳴らす設定は、この関数内で行います。
ここでは、`sin()`でサイン波の計算をし、その結果を`abl`に書き込んでいます。

```swift
// コールバック関数
let renderCallback: AURenderCallback = {(
    inRefCon: UnsafeMutableRawPointer,
    ioActionFlags: UnsafeMutablePointer<AudioUnitRenderActionFlags>,
    inTimeStamp: UnsafePointer<AudioTimeStamp>,
    inBusNumber: UInt32,
    inNumberFrames: UInt32,
    ioData: UnsafeMutablePointer<AudioBufferList>?
) -> OSStatus in
    // チャンネルの数分のAudioBuffer参照の取り出し
    let abl = UnsafeMutableAudioBufferListPointer(ioData)
    // フレーム数分のメモリキャパシティ
    let capacity = Int(abl![0].mDataByteSize) / MemoryLayout<Float>.size
    // バッファに値を書き込む
    if let buffer: UnsafeMutablePointer<Float> = abl![0].mData?.bindMemory(to: Float.self, capacity: capacity) {
        for i: Int in 0 ..< Int(inNumberFrames) {
            // サイン波を生成
            buffer[i] = sin(frame * toneA * 2.0 * Float(Double.pi) / sampleRate)
            frame += 1
        }
    }
        
    return noErr
}
```

1回の`renderCallback`内で計算される`inNumberFrames`の値が512であるとすると、このコールバック関数は、１秒間に 44100 / 512 = 約86回 呼ばれている計算になります。

## スタート・ストップ関数の設定

外から呼ぶ用の音声スタート・ストップ用関数をクラス内に作成します。
`AudioOutputUnitStart`を呼ぶと、コールバック関数`renderCallback`が繰り返し呼ばれるようになります。

```swift
/// 再生スタート
func start() {
    AudioGenerator.frame = 0
    AudioOutputUnitStart(audioUnit!) // Remote IO Unit 出力開始
    print("start")
}

/// 再生ストップ
func stop() {
    AudioOutputUnitStop(audioUnit!) // Remote IO Unit 出力停止
    print("stop")
}
```

あとは、`start()`と`stop()`を適宜外から呼んであげると、ラの音が鳴ったり止んだりするようになります。

## Audio Unitの破棄

音を鳴らす必要がなくなった際は、以下のような記述でAudio Unitの破棄を行います。

```swift
/// Audio Unitの破棄
func dispose() {
    AudioUnitUninitialize(audioUnit!)
    AudioComponentInstanceDispose(audioUnit!)
}
```

# おわり
iOS上で音の出力をする際の一例と、Swift4での記述例を紹介してみました。
参考になれば幸いです。

また、ご指摘があればコメント頂けるとありがたいです。

# 参考文献・サイト
- Apple Inc. "Core Audio | Apple Developer Documentation". Apple Developer. https://developer.apple.com/documentation/coreaudio, (参照: 2018-12-15).
- 永野 哲久. iPhone Core Audio プログラミング. 初版, ソフトバンククリエイティブ, 2009, p545.
- ペブ. "swift3でCoreAudioを使う 再生編". Pebble Coding. 2015-12-05. https://www.pebblewind.com/entry/2015/12/05/192914, (参照: 2018-12-15).

[^1]: Windows にも、Core Audioと呼ばれるものがあります https://docs.microsoft.com/en-us/windows/desktop/coreaudio/about-the-windows-core-audio-apis
[^2]: ここでは音を再生/録音するための動作を実行してから、実際に音がなるまでの遅延時間を指しています