【Swift】iOS Speechフレームワークに歌姫を聞かせてみた

## はじめに
Life is Tech ! Advent Calender 10日目ですね。
Life is Tech !でiPhoneメンターをやってますしのきんと申します。
普段はLife is Tech ! SCHOOLで10人ほどの中高生にiPhoneアプリの制作を通して、プログラミングなどを教えています。
大学は東京大学の法学部なので、大学は情報系と全く関わりがないので、いわゆる文系エンジニアです。

初日担当のちゃんちーと4日目担当のへむへむに最初アドベントカレンダーの誘いが来た時は、普通にswiftのenumとかextensionをやろうと思ってました。

しかし、4日目のへむへむの記事の[
メンターとパンサーを人工知能に区別させてみた](http://qiita.com/rikky0611/items/a2783252f4452ab8733b)では、Life is Tech !のメンターという大学生スタッフを活用していて、笑い取ってくる感じと、5日目のありたくの記事、[目指せヘルシープログラマー: 成分比較から考えるプログラマ-にとって最適なエナジードリンク](http://qiita.com/aritaku/items/309b58e991e2d6f06f02)での、突然swiftでベンゼン環を書くという流れなど、普通の記事書く流れじゃない感じ泣

ということで、(やっと本題ですが、)今日はiPhoneをつかって音声認識をしたいと思います。認識させるのは、ライフイズテックではおなじみの歌姫メンターのちゃーはんのオリジナルソングを使ってやってみたいと思いまーーす。

## 実装
`Speech`フレームワークと、`AVFoundation`の`AVAudioEngine`を使用しています。
`SFSpeechRecognizer`というクラスを使って、マイクから取得した音声データを認識します。

`AVAudioEngine`から取得した音声データを`SFSpeechRecognitionRequest`に渡して実装します。

では、まずマイクなどを使用するために、ユーザーの許可をもらいます。
`Speech`フレームワーク自体をまずはインポートしましょう。

```swift:ViewController.swift
import Speech
```

そして、音声認識の許可をもらうメソッドが以下の通りです。

```swift:ViewController.swift
    private func request() {
        
        SFSpeechRecognizer.requestAuthorization { (status) in
            
            OperationQueue.main.addOperation {
                
                switch status {
                    
                case .authorized: print("authorized")
                case .denied: print("denied")
                case .restricted: print("restricted")
                case .notDetermined: print("notDetermined")
                }
            }
        }
    }
```

この`request`メソッドを`viewDidAppear`で呼び出します。
また`info.plist`にマイクを使用する目的などを記載しておきましょう。以下をinfo.plistに追加します。

```xml:info.plist
    <key>NSMicrophoneUsageDescription</key>
    <string>歌詞判定のために使用します</string>
    <key>NSSpeechRecognitionUsageDescription</key>
    <string>歌詞判定のために使用します</string>
```

これで、音声認識するための準備は整いました。
では、音声認識を行うメソッドを準備をします。


```swift:ViewController.swift
    private func startRecording() throws {
        
        refreshTask()
        
        let audioSession = AVAudioSession.sharedInstance()
        try audioSession.setCategory(AVAudioSessionCategoryRecord)
        try audioSession.setMode(AVAudioSessionModeMeasurement)
        try audioSession.setActive(true, with: .notifyOthersOnDeactivation)
        
        recognitionRequest = SFSpeechAudioBufferRecognitionRequest()
        guard let inputNode = audioEngine.inputNode else {
            
            fatalError("Audio Engine has no inputNode")
        }
        
        guard let recognitionRequest = recognitionRequest else {
            
            fatalError("Unable to create a SFSpeechAudioBufferRecognition object")
        }
        
        recognitionRequest.shouldReportPartialResults = true
        
        recognitionTask = speechRecognizer?.recognitionTask(with: recognitionRequest, resultHandler: { [weak self] result, error in
            
            guard let `self` = self else { return }
            
            var isFinal = false
            if let result = result {
                
                print(result.bestTranscription.formattedString)
                isFinal = result.isFinal
            }
            
            if error != nil || isFinal {
                
                self.audioEngine.stop()
                inputNode.removeTap(onBus: 0)
                
                self.recognitionRequest = nil
                self.recognitionTask = nil
                
            }
        })
        
        let recordingFormat = inputNode.outputFormat(forBus: 0)
        inputNode.installTap(onBus: 0, bufferSize: 1024, format: recordingFormat, block: { (buffer: AVAudioPCMBuffer, when: AVAudioTime) in
            
            self.recognitionRequest?.append(buffer)
        })
        
        try startAudioEngine()
    }
    
    private func refreshTask() {
        
        if let recognitionTask = recognitionTask {
            
            recognitionTask.cancel()
            self.recognitionTask = nil
        }
    }
    
    private func startAudioEngine() throws {
        
        audioEngine.prepare()
        
        try audioEngine.start()
    }
```

ここまでで、言葉を発するとしっかりとコンソールにプリントされました。

さて、これが歌姫メンターの歌の場合どうなるのでしょうか？

## 歌姫の歌を音声認識させてみた
では、さっそく歌姫メンターちゃーはんのオリジナルソングを認識させてみましょう。

結果ですが、全く認識されない:sob:
何度か試しましたし、周りの雑音もない状態でしたが、歌詞を出してくれませんでした。
音楽として流しているために、認識する音声とは区別しているのかもしれません。あるいは、別人の声だからなのか、伴奏のせいなのか分かり次第記事を更新したいと思います。

一番下のサンプルコードの中に、音源もいれてますので、ぜひ聞いてみてください。


## 番外編

### 1. Swiftで書いてみたのだから、Taylor Swiftも認識させよう
Swiftで検索するとちょくちょく出て来るTaylor Swiftの歌ならどうか認識させてみました。こっちも歌姫ですね
認識させたのは、Taylor SwiftのNew Romanticsです。テラスハウスでおなじみですね。

#### 結果

こちらも残念ながら全く認識されない結果となってしまいました。
そもそも話し言葉を認識するための機能に音楽を聞かせてしまうのが間違いですね。Appleさんごめんなさい。


### 2. Google Cloud Speech APIの実装方法
Google Cloud Speech APIでも認識させようと思い、実装に取り掛かりました。こちらは残念ながらAdvent Calendarの日程の関係で実装を終えることができませんでした。初めてのAdvent Calendarで、Advent Calendarを舐めていました:bow:。(日程決まってるの難しい)

しかし、良記事がたくさんありましたので、そちらの共有だけでもしておきます。

[Google Cloud Speech API Beta でwavファイルの音声認識](http://qiita.com/sn6/items/a227cc92453817eeab11)
セットアップはこちらの記事にほとんど頼りきりになりました。

[Google Cloud Speech API | Quickstart](https://cloud.google.com/speech/docs/getting-started)


[GoogleCloudPlatform/ios-docs-samples](https://github.com/GoogleCloudPlatform/ios-docs-samples)(こちらはGooleのサンプルコードです。)



## 参考URL
[【iOS 10】Speech Frameworkで音声認識](http://atamo-dev.hatenablog.com/entry/2016/07/20/200650)
[SpeakToMe: Using Speech Recognition with AVAudioEngine](https://developer.apple.com/library/prerelease/content/samplecode/SpeakToMe/Introduction/Intro.html#//apple_ref/doc/uid/TP40017110)(本記事のサンプルコードは、Appleのサンプルコードとほぼおなじです。)
[【iOS】Speech Frameworkの実装](http://qiita.com/KentaKudo/items/8473146d5d596069b857)


## サンプルコード
https://github.com/ShinokiRyosei/Speech-Recognizer


## さいごに
### 結果
さて、結果としては、歌姫メンターもTaylor Swiftも認識してくれませんでした。

### 所感
本題の歌はまったく認識してくれませんでしたが、普通に話す分にはしっかりと認識されますので、いいフレームワークですね。また非常に簡単に実装できるのもとてもいいですね。(小並感)

では、明日はライフイズテックが誇るトップiPhoneメンターのとみーさんです。LTとかもよくされてますね。
タイトルが「より多くの人にアプリを使ってもらうために」とのことなので、アプリ制作者やデザイナーの方必見です。
では、よいクリスマスを。