[Swift]音声ファイルの位相を反転させる

#はじめに
ノイズキャンセリングやボーカル抽出には音声の位相反転を用います。
今回はSwiftを用いて位相反転を行う方法を紹介します。

逆位相に関する記事は[こちら](https://qiita.com/p_x9/items/ef19951eed23c9ccdb3b)


#本編
使用するフレームワークは`AVFoundation`と`Accelerate`です。
AVFoundationで音声ファイルを読み込み、Accelerateで位相反転処理を行う、といった流れです。
今回は簡単にPlaygroundを使用して実装していきます。

##1. 位相反転させたい音声ファイルを用意
位相反転させたい音声ファイルを用意します。今回はwav形式で用意しました。

##2. 音声ファイルの読み込み
音声ファイルを読み込みます。
AVFoundationを使用するのでimportしてください。
Urlは各自読み替えてください。

```swift
import AVFoundation
let inputUrl = URL(fileURLWithPath: NSHomeDirectory()+"/Desktop/"+"input.wav")
let outputUrl = URL(fileURLWithPath: NSHomeDirectory()+"/Desktop/"+"output.wav")

let input = try! AVAudioFile(forReading: inputUrl,commonFormat: .pcmFormatFloat32, interleaved: false)
```
##3. 音声データをバッファに読み込む
読み込んだ音声ファイルの中身を読み込むためのバッファと、処理を行った後に出力するためのバッファを用意します。
バッファを定義した後、実際にinputBufferには、先ほど読み込んだ音声ファイルから中身の音声データを読み込みます。

```swift
guard let inputBuffer = AVAudioPCMBuffer(pcmFormat: input.processingFormat, frameCapacity: AVAudioFrameCount(input.length)),
      let outputBuffer = AVAudioPCMBuffer(pcmFormat: input.processingFormat, frameCapacity: AVAudioFrameCount(input.length)) else{
    fatalError()
}

do{
    try input.read(into: inputBuffer)
}catch{
    print(error.localizedDescription)
}
```

##4.バッファから音声データ配列([[Float32]])を取り出す。
取り出します。(正確には`UnsafePointer<UnsafeMutablePointer<Float>>`型です)
配列はDouble型の二次元配列となっており、チャンネルごとに音声データの振幅値が入っています。

```swift 
let inputFloat32ChannelData = inputBuffer.floatChannelData!
let outputFloat32ChannelData = outputBuffer.floatChannelData!
```

##5.音声データを逆位相に変換
4で取り出した二次元配列に対し、チャンネルごとに音声データの配列のそれぞれの要素に-1をかければ、逆位相になります。
for文などで配列を走査してもできないことはありませんが、計算量が多く時間がかかりすぎるので、Accerelateフレームワークの関数を使うこととします。
vDSP_vsmulはFloat32型の配列に対してスカラー倍を行う関数です。
この関数の第４引数が出力したい配列ポインタを表すので、outputを指定します。

```swift 
for channel in 0 ..< Int(inputBuffer.format.channelCount) {
    let input: UnsafeMutablePointer<Float32> = inputFloat32ChannelData[channel]
    let output: UnsafeMutablePointer<Float32> = outputFloat32ChannelData[channel]
    
    var scalar:Float = -1.0
    vDSP_vsmul(input, 1, &scalar, output, 1, vDSP_Length(inputBuffer.frameLength))
}
outputBuffer.frameLength = inputBuffer.frameLength
```

##6.出力のフォーマットの設定

```swift
let settings: [String: Any] = [
    AVFormatIDKey: outputBuffer.format.settings[AVFormatIDKey] ?? kAudioFormatLinearPCM,
    AVNumberOfChannelsKey: outputBuffer.format.settings[AVNumberOfChannelsKey] ?? 2,
    AVSampleRateKey: outputBuffer.format.settings[AVSampleRateKey] ?? 44100,
    AVLinearPCMBitDepthKey: outputBuffer.format.settings[AVLinearPCMBitDepthKey] ?? 16
]
```

##7.出力
先程のフォーマット設定に従って出力をおこないます。
すると、1で指定したoutputUrlの場所にファイルが生成されます。

```swift
do{
    let output = try AVAudioFile(forWriting: outputUrl, settings: settings, commonFormat: .pcmFormatFloat32, interleaved: false)
    try output.write(from: outputBuffer)
}
catch{
    print(error.localizedDescription)
}
```

#本当に逆位相になっているか確認
[前の記事](https://qiita.com/p_x9/items/ef19951eed23c9ccdb3b)でも使用した、Audacityというソフトを使います。(winでもmacでも使えます)
Audacityにinputファイルと生成されたoutputファイルをインポートして再生します。
きちんと反転できていれば、音が打ち消しあい、何も聞こえません。

![スクリーンショット 2021-06-11 21.01.54.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/377767/b6f5357d-4362-086c-b251-b7225c0c7451.png)

#さいごに
読んでいただきありがとうございました。

今回作成したコードは、以下のリポジトリにあります。
https://github.com/p-x9/InvertAudioPhase