[iOS]マイクアプリを作る

Hello, World! あーみーです．

この記事は[Life is Tech! Tokai Mentors Advent Calendar 2018](https://qiita.com/advent-calendar/2018/lit-tokai-mentors) 23日目の記事です．

忙しくてずっと下書きで眠っていたので，アドベントカレンダーの力を借りて記事を完成させました．

今回は僕が作ったアプリで恐縮ですが．[このアプリ](https://itunes.apple.com/us/app/shout-aloud/id1397428412?mt=8)の音声の入出力の部分を作ります．

## はじめに
僕は合唱をやっていたので，音楽に関係するアプリを作りたいなと思い，iPhoneがマイクになるアプリを作ろうと思いました．

iPhoneでの音声入力は調べたら結構実装できる程度には記事が見つかるんですが，マイクを作りたいので，入力だけではいけません．その入力した音声を同時にスピーカーから出力しないとマイクとは言えませんから．

音声を入力と同時に出力する方法は調べてもなかなか記事が見つからなかったため，記事にしました．


## 環境
僕の今回のPC環境を以下に示します．

- High Sierra 10.13
- XCode 10.1
- Swift 4
- iPhoneX（実機でのテストのため）

## マイク実装
### #1. とりあえず部品を配置する
音声入力と音声出力に必要なUI部品は特に何もないですが，スピーカーに繋げて出力した時にハウリングしたり，音が大きいとビックリするので，それを制御するために以下の部品を配置します．

- マイクON/OFF用```UISwitch```
- マイクボリューム用```UISlider```

![スクリーンショット 2018-12-22 17.02.52.png](https://qiita-image-store.s3.amazonaws.com/0/210077/dd13cca3-a196-2cb6-2ad8-76fecce5e756.png)


### #2. 音声入出力
音声の入出力自体はとても簡単で```ViewController```を以下のようにするだけで，音は出るようになります．


```swift
import UIKit
import AVFoundation

class ViewController: UIViewController {

    private var engine = AVAudioEngine()

    override func viewDidLoad() {
        super.viewDidLoad()
        
        let input = engine.inputNode
        let output = engine.mainMixerNode
        let format = engine.inputNode.inputFormat(forBus: 0)
        
        engine.connect(input, to: output, format: format)
        
        try! engine.start()
    }
}
```

簡単ですね！

ただ，このままではマイクに無許可でアクセスしようとして起動と同時にアプリが落ちてしまうので，```Info.plist```に以下を追加します．

|Key|Value|
|---|---|
|Privacy - Microphone Usage Description|[マイクを使用する理由]|

また，#1の時に配置した```UISwitch```と```UISlider```でマイクを制御するため，```ViewController```に以下も追記します．

```swift
    // マイクON/OFF用UISwitch
    @IBAction func changeMicState(sw: UISwitch){
        if sw.isOn {
            try! engine.start()
        }else{
            engine.stop()
        }
    }
    
    // マイクボリューム用UISlider
    @IBAction func micSlider(slider: UISlider){
        engine.inputNode.volume = slider.value
    }
```
どちらも値が変わった時に変更を加えたいので，```Value Changed```でアタッチします．

![スクリーンショット 2018-12-22 17.32.45.png](https://qiita-image-store.s3.amazonaws.com/0/210077/c5381c7c-7004-bdb6-2961-a13f10a10145.png)


### #3. Bluetoothスピーカーから出力
マイクのように声を増幅させたいので，スピーカーから出力することが目的となります．
今でも，iPhoneのイヤホンジャックとスピーカーをミニプラグで繋げれば可能ですが，最近はイヤホンジャックが廃止されていますし，有線だと，スマートじゃありません．

なので，Bluetoothでスピーカーに接続していきます．```viewDidLoad```内に以下を追記すればBluetoothスピーカーにも対応できます．

```swift
    override func viewDidLoad() {
        super.viewDidLoad()

        // Bluetooth接続を許可
        try! AVAudioSession.sharedInstance()
            .setCategory(.playAndRecord,
                         mode: .voiceChat,
                         options: .allowBluetoothA2DP)
    }
```

ただ，Bluetoothでスピーカーに繋ぐと音声が入力されてから出力されるまでにタイムラグがあります．
自分の声が遅れて聞こえてくるので喋りづらいかもしれません．しょうがない気もしますが．

タイムラグをなくすことができるかどうかはわかりません．もし，方法知ってるよって方がいればコメント等お願いします．

これでマイクアプリは完成です！

## エフェクト実装
せっかくのマイクアプリなので，多少のマイクパフォーマンスができるように変更していきます．
今回は以下の２つの機能をつけます．

- リバーブ
- ディレイ

### #1. とりあえず部品を配置する
マイク用の部品に加えてリバーブ用，ディレイ用にも```UISlider```を追加します．

![スクリーンショット 2018-12-22 19.42.39.png](https://qiita-image-store.s3.amazonaws.com/0/210077/7478c480-b695-6993-fe16-e70b5d39a3ee.png)

画像の上から順に

- マイクON/OFF用```UISwitch```
- マイクボリューム用```UISlider```
- リバーブレベル用```UISlider```
- ディレイタイム用```UISlider```

となります，

### #2. AVAudioEngineに繋げる
先程の```ViewController```を以下のように変更します．

```swift
import UIKit
import AVFoundation

class ViewController: UIViewController {
    
    private var engine = AVAudioEngine()
    
    private var reverb = AVAudioUnitReverb()
    private var delay = AVAudioUnitDelay()

    override func viewDidLoad() {
        super.viewDidLoad()
        
        // Bluetooth接続を許可
        try! AVAudioSession.sharedInstance()
            .setCategory(.playAndRecord,
                         mode: .voiceChat,
                         options: .allowBluetoothA2DP)
        
        let input = engine.inputNode
        let output = engine.mainMixerNode
        let format = engine.inputNode.inputFormat(forBus: 0)
        
        engine.attach(reverb)
        engine.attach(delay)
        
        reverb.wetDryMix = 0
        delay.delayTime = 0
        
        engine.connect(input, to: reverb, format: format)
        engine.connect(reverb, to: delay, format: format)
        engine.connect(delay, to: output, format: format)
        
        try! engine.start()

    }
}
```

音声にエフェクトをつけたい時は，ギターのエフェクターを繋げるみたいに，それぞれのエフェクトを```input```から```output```までの間に入れてあげればできます．

![minimax (41).png](https://qiita-image-store.s3.amazonaws.com/0/210077/49bc63cc-1efb-ac65-9ccf-3b3c8b262ef5.png)

図だとこんな感じですね．

そして，エフェクターのつまみを回すように，エフェクトレベルを変更する関数を用意します．

```swift
    // リバーブレベル用UISlider
    @IBAction func reverbSlider(slider: UISlider){
        reverb.wetDryMix = slider.value
    }
    
    // ディレイタイム用UISlider
    @IBAction func delaySlider(slider: UISlider){
        delay.delayTime = TimeInterval(slider.value)
    }
```

どちらも値が変わった時に変更を加えたいので，```Value Changed```でアタッチします．

![スクリーンショット 2018-12-22 19.53.14.png](https://qiita-image-store.s3.amazonaws.com/0/210077/f2898d8a-4732-d5c2-881a-c13a77d3ff69.png)



## リポジトリ
サンプルのプロジェクトがあるリポジトリは[こちら](https://github.com/raicos/MicrophoneSample)

## 参考にしたサイト
- [Ariel Elkin: Real-Time Audio using Swift](https://vimeo.com/125703971)

## さいごに
今回はiPhoneをマイクにするため，入力した音声をリアルタイムで出力するアプリを作成しました．また，マイクパフォーマンスができるようにエフェクトも追加しました．

忘年会，新年会の季節ですが，人数が多いと声が後ろまで届きませんよね．
是非，幹事の人は使ってみてはいかがでしょうか．

それでは，Happy Hacking! :smile: