[Swift] DTMFクラス（プッシュ信号音再生）

DTMFとは、<b>`Dual-Tone Multi-Frequency`</b>の略で、プッシュホン回線で信号を送信する音声信号のこと。

高音と低音の4種類ずつの周波数の音を組み合わせて、下表に示す16種類の符号を表現する。

| 低音 \ 高音 | 1209Hz | 1336Hz | 1477Hz | 1633Hz |
|:-:|:-:|:-:|:-:|:-:|
| 697Hz  | 1 | 2 | 3 | A |
| 770Hz  | 4 | 5 | 6 | B |
| 852Hz  | 7 | 8 | 9 | C |
| 941Hz  | * | 0 | # | D |


・Pythonによる実装については、次のサイトで詳しく説明されています。

https://github.com/lambda-tech-club/DTMF-generator

<br><br>

・Swiftでの実装は、次のサイトに公開されています。これを参考に、次の DTMFクラス を作りました。

https://github.com/palmerc/DTMFSounds

```swift: DTMFクラス 
import AVFoundation

class DTMF {
    private let sampleRate = Float32(48000)
    private let engine: AVAudioEngine!
    private let player: AVAudioPlayerNode!
    private let mixer: AVAudioMixerNode!

    private typealias MarkSpaceType = (Float32, Float32)
    private typealias DTMFType = (Float32, Float32)

    init() {
        engine = AVAudioEngine()
        player = AVAudioPlayerNode()
        mixer = engine.mainMixerNode
    }
    
    func play(for phoneNumber: String) {
        if player.isPlaying { player.stop() }
        
        if let tones = tonesFor(string: phoneNumber) {
            let audioFormat = AVAudioFormat(commonFormat: .pcmFormatFloat32, sampleRate: Double(sampleRate), channels: 2, interleaved: false)!
            
            // fill up the buffer with some samples
            var allSamples = [Float32]()
            for tone in tones {
                let samples = generateDTMF(tone)
                allSamples.append(contentsOf: samples)
            }
            
            let frameCount = AVAudioFrameCount(allSamples.count)
            guard let buffer = AVAudioPCMBuffer(pcmFormat: audioFormat, frameCapacity: frameCount) else { return }
            
            buffer.frameLength = frameCount
            let channelMemory = buffer.floatChannelData!
            for channelIndex in 0 ..< Int(audioFormat.channelCount) {
                let frameMemory = channelMemory[channelIndex]
                memcpy(frameMemory, allSamples, Int(frameCount) * MemoryLayout<Float32>.size)
            }
            
            engine.attach(player)
            engine.connect(player, to: mixer, format: audioFormat)
            mixer.outputVolume = 1.0
            
            do {
                try engine.start()
            } catch let error as NSError {
                print("Engine start failed - \(error)")
            }
            
            player.scheduleBuffer(buffer)
            player.play()
        }
    }

    /**
     Generates a series of Float32 samples representing a DTMF tone with a given mark and space.
     
     - parameter DTMF: takes a DTMFType comprised of two floats that represent the desired tone frequencies in Hz.
     - parameter markSpace: takes a MarkSpaceType comprised of two floats representing the duration of each in milliseconds. The mark represents the length of the tone and space the silence.
     - parameter sampleRate: the number of samples per second (Hz) desired.
     - returns: An array of Float32 that contains the Linear PCM samples that can be fed to AVAudio.
     */
    private func generateDTMF(_ dtmfType: DTMFType, markSpace: MarkSpaceType = MarkSpaceType(100.0, 100.0), sampleRate: Float32 = 48000) -> [Float32] {
        let toneLengthInSamples = 10e-4 * markSpace.0 * sampleRate
        let silenceLengthInSamples = 10e-4 * markSpace.1 * sampleRate
        
        var sound = [Float32](repeating: 0, count: Int(toneLengthInSamples + silenceLengthInSamples))
        let twoPI = Float32.pi * 2
        
        for n in 0 ..< Int(toneLengthInSamples) {
            let sample1 = 0.5 * sin(Float32(n) * twoPI / (sampleRate / dtmfType.0));
            let sample2 = 0.5 * sin(Float32(n) * twoPI / (sampleRate / dtmfType.1));
            sound[n] = sample1 + sample2
        }
        
        return sound
    }
    
    private func tonesFor(string dial: String) -> [DTMFType]? {
        let toneDict: [Character: DTMFType] = [
            "1": (697, 1209),
            "2": (697, 1336),
            "3": (697, 1477),
            "4": (770, 1209),
            "5": (770, 1336),
            "6": (770, 1477),
            "7": (852, 1209),
            "8": (852, 1336),
            "9": (852, 1477),
            "*": (941, 1209),
            "0": (941, 1336),
            "#": (941, 1477),
            "A": (697, 1633),
            "B": (770, 1633),
            "C": (852, 1633),
            "D": (941, 1633),
            " ": (  0,    0), //本来は不要
        ]
        let tones = dial.uppercased().compactMap { toneDict[$0] }
        return tones.count > 0 ? tones : nil
    }
}
```

- 使い方

```swift
let dtmf = DTMF()
dtmf.play(for: "01-2345-6789")
Thread.sleep(forTimeInterval: 3) //for REPL
```

- ミッキーマウスのテーマ（に聞こえる？） [再生](http://www.nak435.com/qiita/66666666963216666666936.wav)

```swift
dtmf.play(for: "66666666 96321 666 666 #6936")
Thread.sleep(forTimeInterval: 6) //for REPL
```



<br><br>

- SwiftUIでの使用例

```swift
import SwiftUI

struct ContentView: View {
    let dtmf = DTMF()
    @State var phoneNumber: String = "01-2345-6789"
    var body: some View {
        VStack {
            TextField(text: $phoneNumber, label: { Text("Phone number") })
            
            Button("Tone", action: {
                if phoneNumber.isEmpty { return }
                dtmf.play(for: phoneNumber)
            })
        }
        .padding()
    }
}
```

相手方の電話番号を入力して、電話の受話器に向かって音声信号を聞かせると、実際にダイヤルすることができるはずです。<br><br>　


**以上**