DTMFとは、Dual-Tone Multi-Frequency
の略で、プッシュホン回線で信号を送信する音声信号のこと。
高音と低音の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による実装については、次のサイトで詳しく説明されています。
・Swiftでの実装は、次のサイトに公開されています。これを参考に、次の DTMFクラス を作りました。
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
}
}
- 使い方
let dtmf = DTMF()
dtmf.play(for: "01-2345-6789")
Thread.sleep(forTimeInterval: 3) //for REPL
- ミッキーマウスのテーマ(に聞こえる?) 再生
dtmf.play(for: "66666666 96321 666 666 #6936")
Thread.sleep(forTimeInterval: 6) //for REPL
- SwiftUIでの使用例
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()
}
}
相手方の電話番号を入力して、電話の受話器に向かって音声信号を聞かせると、実際にダイヤルすることができるはずです。
以上