iPhone のマイクから拾った音の音程を判定する

環境: Xcode10.3、Swift 5.0.1

iPhone のマイクから拾ったオーディオ情報から音程を判定する方法について調べた。
ちゃんとやるには、離散フーリエ変換 (discrete Fourier transform) を使って周波数を算出するらしいのだけれども、今回はAudioKitというOSSライブラリを使うことで簡単に実現できたのでメモ。

github.com

やりたいことがそのまま公式Example(GitHubコードはこちら)として実装されていたのでこの通りに進めた。

audiokit.io

画面にはマイクから拾った音の周波数(Frequency)と音階(Note)、また波形がリアルタイムで表示される。

AudioKit インストール

プリコンパイル済みのFrameworkはこちらからダウンロードできる。
プロジェクトにダウンロードしたAudioKit.frameworkを追加、
そして TARGET の Build Settings > Linking > Other Linker Flags に -lc++ の追加する。

CocoaPods の場合

Podfile

pod 'AudioKit', '~> 4.0'

インストール

$ pod install

Carthage の場合

Cartfile

github "AudioKit/AudioKit"

ビルド

# iOS
$ carthage update --platform iOS --no-use-binaries --cache-builds --new-resolver
# Mac
$ carthage update --platform Mac --no-use-binaries --cache-builds --new-resolver

さらに TARGET の Build Settings > Linking > Other Linker Flags に -lc++ の追加が必要。

Swift コード

Exampleのコードそのまま。今回は波形表示は不要だったので省いた。
AKFrequencyTrackerクラスを利用することでピッチ検出することができる。
ただし、現状は検出できるのは単音(モノフォニック)のみで、和音のような複数音(ポリフォニック)には対応していないようだ。

import UIKit
import AudioKit

class ViewController: UIViewController {

    @IBOutlet weak var frequencyLabel: UILabel!
    @IBOutlet weak var amplitudeLabel: UILabel!
    @IBOutlet weak var noteNameWithSharpsLabel: UILabel!
    @IBOutlet weak var noteNameWithFlatsLabel: UILabel!
    
    var mic: AKMicrophone!
    var tracker: AKFrequencyTracker!
    var silence: AKBooster!
    
    let noteFrequencies = [16.35, 17.32, 18.35, 19.45, 20.6, 21.83, 23.12, 24.5, 25.96, 27.5, 29.14, 30.87]
    let noteNamesWithSharps = ["C", "C♯", "D", "D♯", "E", "F", "F♯", "G", "G♯", "A", "A♯", "B"]
    let noteNamesWithFlats = ["C", "D♭", "D", "E♭", "E", "F", "G♭", "G", "A♭", "A", "B♭", "B"]
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        AKSettings.audioInputEnabled = true
        mic = AKMicrophone()
        tracker = AKFrequencyTracker(mic)
        silence = AKBooster(tracker, gain: 0)
    }

    override func viewDidAppear(_ animated: Bool) {
        super.viewDidAppear(animated)
        
        AudioKit.output = silence
        do {
            try AudioKit.start()
        } catch {
            AKLog("AudioKit did not start!")
        }
        Timer.scheduledTimer(timeInterval: 0.1,
                             target: self,
                             selector: #selector(ViewController.updateUI),
                             userInfo: nil,
                             repeats: true)
    }
    
    @objc func updateUI() {
        if tracker.amplitude > 0.1 {
            frequencyLabel.text = String(format: "%0.1f", tracker.frequency)
            
            var frequency = Float(tracker.frequency)
            while frequency > Float(noteFrequencies[noteFrequencies.count - 1]) {
                frequency /= 2.0
            }
            while frequency < Float(noteFrequencies[0]) {
                frequency *= 2.0
            }
            
            var minDistance: Float = 10_000.0
            var index = 0
            
            for i in 0..<noteFrequencies.count {
                let distance = fabsf(Float(noteFrequencies[i]) - frequency)
                if distance < minDistance {
                    index = i
                    minDistance = distance
                }
            }
            let octave = Int(log2f(Float(tracker.frequency) / frequency))
            noteNameWithSharpsLabel.text = "\(noteNamesWithSharps[index])\(octave)"
            noteNameWithFlatsLabel.text = "\(noteNamesWithFlats[index])\(octave)"
        }
        amplitudeLabel.text = String(format: "%0.2f", tracker.amplitude)
    }
}

コード内にあるnoteFrequencies配列の数値の意味がわからなかったが、調べたところ音高(Pitch)の周波数らしい。
オクターブ 4 のラの音A4 = 440Hzを基準音とすると以下の表のようになる。
1オクターブ高いと周波数は2倍になる。

Frequency (Hz)
OctaveNote
C C# D Eb E F F# G G# A Bb B
0 16.35 17.32 18.35 19.45 20.60 21.83 23.12 24.50 25.96 27.50 29.14 30.87
1 32.70 34.65 36.71 38.89 41.20 43.65 46.25 49.00 51.91 55.00 58.27 61.74
2 65.41 69.30 73.42 77.78 82.41 87.31 92.50 98.00 103.8 110.0 116.5 123.5
3 130.8 138.6 146.8 155.6 164.8 174.6 185.0 196.0 207.7 220.0 233.1 246.9
4 261.6 277.2 293.7 311.1 329.6 349.2 370.0 392.0 415.3 440.0 466.2 493.9
5 523.3 554.4 587.3 622.3 659.3 698.5 740.0 784.0 830.6 880.0 932.3 987.8
6 1047 1109 1175 1245 1319 1397 1480 1568 1661 1760 1865 1976
7 2093 2217 2349 2489 2637 2794 2960 3136 3322 3520 3729 3951
8 4186 4435 4699 4978 5274 5588 5920 6272 6645 7040 7459 7902

ピアノの鍵盤数は88で7オクターブ、この表での範囲はA0 = 27.50HzからC8 = 4186Hzになる。
ちなみに日本のピアノはA4 = 442Hzで調律されることが多いとのこと。

https://upload.wikimedia.org/wikipedia/commons/thumb/2/2e/Piano_Frequencies.svg/520px-Piano_Frequencies.svg.png

水色が真ん中のド (C4)、英語では middle C、黄色がラ (A4)

参考:

A440 - Wikipedia