AVAudioEngineでiOSボイスチェンジャーをつくる

ボイチェンみたいに音声を高くしたり低くしたり、速くしたり遅くしたりできます。
変換した音声をファイルに書き込むこともできます。


#手順

音声ファイルから読み込んだ音声を変換します。


###1.AVAudioEngine を初期化

```swift
var audioEngine = AVAudioEngine()
```

###2.AVAudioEngine に音声ファイルを流し込む PlayerNode を用意

```swift
let playerNode = AVAudioPlayerNode()
let audioFile = try! AVAudioFile(forReading: audioFileURL) // URLからファイル読み込み
playerNode.scheduleFile(audioFile, at:nil) // playerNodeにファイルをセット
```

###3.音声を変換する AVAudioUnitTimeEffect を用意

```swift
let audioUnitTimePitch = AVAudioUnitTimePitch()
audioUnitTimePitch.pitch = 1200 // 1200で１オクターブ高くなる。可能な値は -2400 ~ 2400
```

###4.AudioEngine に PlayerNodeとAudioUnitTimeEffect を取り付けて接続

|==================**AudioEngine**================|
　**PlayerNode** → **AudioUnitTimePitch** → **OutputNode**
|============================================|

```swift
// 取り付け
audioEngine.attach(playerNode)
audioEngine.attach(audioUnitTimePitch)

// 接続
audioEngine.connect(playerNode, to: audioUnitTimePitch, format: audioFile.processingFormat)
audioEngine.connect(audioUnitTimePitch, to: audioEngine.outputNode, format: audioFile.processingFormat)
```
###5.音声を流し込む

変換された音声が再生されます。
匿名インタビューみたいな声になります。

```swift
try! audioEngine.start()
playerNode.play()
```

###6.変換した音声をファイルとしてエクスポート

AudioUnitTimePitch にタップ（蛇口）というのを取り付けて、音声バッファを取り出してファイルに書き込みます。
|==================**AudioEngine**================|
　PlayerNode → **AudioUnitTimePitch** → OutputNode
　　　　　　　　　　　　　　　　↓ **Tap（蛇口）** 
|============================================|

Tap をつけるのは AudioUnitTimePitch ですが、その後の OutputNode も一応つけておかないと動かないようです。

```swift
// 書き込み先ファイルをつくる
let outputFile = try! AVAudioFile(forWriting: outputURL, settings: audioUnitTimePitch.outputFormat(forBus: 0).settings)

// タップ（蛇口）をつけてバッファを取り出してファイルに書き込み
audioUnitTimePitch.installTap(onBus: 0, bufferSize: AVAudioFrameCount(audioUnitTimePitch.outputFormat(forBus: 0).sampleRate), format: audioUnitTimePitch.outputFormat(forBus: 0)) { (buffer, when) in
    do {
        try outputFile.write(from: buffer)
       } catch let error {
        print(error)
       }
}
```

書き込みを終了するにはタップ（蛇口）を取り除く必要があります。
PlayerNode が再生し終わった時に書き込み終了したいので、上記手順2.の PlayerNode に音声ファイルをセットするときに、完了した時に呼ばれる完了ハンドラーを仕込んでおきます。録音するときは以下のよう書き換えます。

```swift
playerNode.scheduleFile(audioFile, at:nil, completionCallbackType: .dataPlayedBack) { [self] _ in // 再生完了時に呼ばれる
    audioUnitTimePitch.removeTap(onBus: 0) // これで書き込み終了の合図を送る。書き込みには音声の長さによって数秒かかります。
    }
```

###エフェクトの種類

それぞれ使い方は上記のピッチ変更の例と同じです（AudioEngine にアタッチして、接続）。
ディレイとディストーションとリバーブとイコイライザーはマイクからのリアルタイム音声に適用できます。
AVAudioUnitTimePitch と AVAudioUnitVarispeed はファイル音声しか無理みたいです。

**AVAudioUnitTimePitch**

|プロパティ |効果  |値  |
|---|---|---|
|overlap|入力オーディオ信号のセグメント間のオーバーラップの量  |Float  |
|pitch  |高さ  |Float  |
|rate   |速さ  |Float |

**AVAudioUnitVarispeed**

|プロパティ |効果  |値  |
|---|---|---|
|rate|高さの変化を伴った速さの変化 |Float  |

**AVAudioUnitEQ**

|プロパティ |効果  |値  |
|---|---|---|
| bands | 周波数帯  | [AVAudioUnitEQFilterParameters]  |
| globalGain  |増幅|Float  |

**AVAudioUnitDistortion**

|プロパティ |効果  |値  |
|---|---|---|
| loadFactoryPreset |プリセットのディストーション（エイリアンの声とか）|AVAudioUnitDistortionPreset|
|preGain  |増幅  |Float  |
|wetDryMix   |歪み具合  |Float |

**AVAudioUnitDelay**

|プロパティ |効果  |値  |
|---|---|---|
| delayTime |遅延時間  |Float  |
|feedback  |フィードバック  |Float  |
| lowPassCutoff   |これ以上の周波数をカット |Float |
| wetDryMix   |ウェット信号とドライ信号のブレンド具合 |Float |

**AVAudioUnitReverb**

|プロパティ |効果  |値  |
|---|---|---|
| loadFactoryPreset |プリセットの音の広がり（大きなホールなど）  | AVAudioUnitReverbPreset  |
| wetDryMix   |ウェット信号とドライ信号のブレンド具合 |Float |

🐣
****
フリーランスエンジニアです。
お仕事のご相談こちらまで
rockyshikoku@gmail.com

Core MLを使ったアプリを作っています。
機械学習関連の情報を発信しています。

[Twitter](https://twitter.com/JackdeS11)
[Medium](https://medium.com/@rockyshikoku)