Created
October 10, 2017 22:50
-
-
Save wecsam/d3c08295ae94c605d83d962609ca2025 to your computer and use it in GitHub Desktop.
Generates touch tones like on a telephone
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env python3 | |
# DTMF = Dual Tone Multi-Frequency | |
import itertools, math, pyaudio, random, sys | |
BITRATE = 8000 | |
TONE_DURATION = 0.1 # seconds | |
KEY_FREQUENCIES = { | |
'1': (697, 1209), | |
'2': (697, 1336), | |
'3': (697, 1477), | |
'A': (697, 1633), | |
'4': (770, 1209), | |
'5': (770, 1336), | |
'6': (770, 1477), | |
'B': (770, 1633), | |
'7': (852, 1209), | |
'8': (852, 1336), | |
'9': (852, 1477), | |
'C': (852, 1633), | |
'*': (941, 1209), | |
'0': (941, 1336), | |
'#': (941, 1477), | |
'D': (941, 1633) | |
} | |
def sine_wave(frequency, frame=0): | |
# Yield frames forever. | |
while True: | |
yield int(math.sin(frequency * frame * 2.0 * math.pi / BITRATE) * 63.0 + 128.0) | |
frame += 1 | |
def mix(*waves): | |
# Add the waves. | |
wave_iterators = [iter(wave) for wave in waves] | |
while True: | |
yield max(0, min(255, sum(next(wave) - 128 for wave in wave_iterators) + 128)) | |
def mix_sine_waves(*frequencies): | |
# Return the sum of sine waves at these frequencies. | |
return mix(*[sine_wave(frequency) for frequency in frequencies]) | |
def key_tone(key): | |
# Return the wave that would play when this key is pressed. | |
return mix_sine_waves(*KEY_FREQUENCIES[key]) | |
def num_frames(seconds): | |
# Return the number of frames in this number of seconds. | |
return int(seconds * BITRATE) | |
def tone_for_duration(tone_generator, seconds): | |
return bytes(itertools.islice(tone_generator, num_frames(seconds))) | |
def silence(seconds): | |
return bytes(128 for i in range(num_frames(seconds))) | |
if __name__ == "__main__": | |
random.seed() | |
# Set up the audio output. | |
p = pyaudio.PyAudio() | |
try: | |
stream = p.open( | |
format=pyaudio.paUInt8, | |
channels=1, | |
rate=BITRATE, | |
output=True | |
) | |
# Loop until the user enters a blank line. | |
try: | |
print("Enter a blank line to quit.") | |
while True: | |
user_input = input("Type a sequence of keys on a telephone keypad: ").strip().upper() | |
if user_input: | |
# First, make sure that all the characters have tones defined. | |
valid = True | |
for c in user_input: | |
if c not in KEY_FREQUENCIES: | |
print(" > Error:", repr(c), "is not on the telephone keypad.") | |
valid = False | |
if valid: | |
# Play the dial tone. | |
sys.stdout.write(" > Off-hook...") | |
sys.stdout.flush() | |
stream.write(tone_for_duration(mix_sine_waves(350, 440), 1.5)) | |
# Dial the numbers. | |
sys.stdout.write(" dialing ") | |
sys.stdout.flush() | |
stream.write(silence(0.1)) | |
for c in user_input: | |
sys.stdout.write(c) | |
sys.stdout.flush() | |
# Write the dual-tone to the stream. | |
stream.write(tone_for_duration(key_tone(c), TONE_DURATION)) | |
# Write a little bit of silence. | |
stream.write(silence(0.05)) | |
# Write some additional silence. | |
sys.stdout.write("...") | |
sys.stdout.flush() | |
stream.write(silence(2.0)) | |
# Play the ringback tone or busy signal. | |
if random.random() < 0.9: | |
sys.stdout.write(" ringing...") | |
sys.stdout.flush() | |
for i in range(4): | |
stream.write(tone_for_duration(mix_sine_waves(440, 480), 2.0)) | |
stream.write(silence(4.0)) | |
else: | |
sys.stdout.write(" busy signal...") | |
sys.stdout.flush() | |
for i in range(4): | |
stream.write(tone_for_duration(mix_sine_waves(480, 620), 0.5)) | |
stream.write(silence(0.5)) | |
print(" sequence complete.") | |
else: | |
break | |
finally: | |
# Clean up. | |
stream.stop_stream() | |
stream.close() | |
finally: | |
p.terminate() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment