Pythonで公衆電話とか昔の電話にあった「ピッ」「ポッ」「パッ」って感じの音を鳴らしみました。
環境
- MacBookAir(mid 2013)
- Python 2.7(pyenvでインストールしたanaconda-2.1.0)
- pyaudio
pyaudioのインストールはこちらの記事を参考にさせていただきました。
[MacでPyAudioを使ってみる. | miettalの日記]途中、ライブラリのシンボリックリンクを作成する説明(sudo ln -s うんぬん)があるのですが、こちらを行わなくても利用できたので、実行しませんでした。
DTMF(ピッポッパの音)について
通信の際にそれぞれの数字や記号に2つの周波数の合成はを対応させたものです。それぞれの対応は下記の表のようになります。
高群[Hz] | ||||
1209 | 1336 | 1477 | ||
低 群 [Hz] |
697 | 1 | 2 | 3 |
770 | 4 | 5 | 6 | |
852 | 7 | 8 | 9 | |
941 | * | 0 | # |
つまり、それぞれの数値に対応した2つの周波数を足し合わせたsin波を出力することによてあの「ピッポッパッ」という音を鳴らすことができます。例えば、”5″の場合は1336[Hz]と770[Hz]の合成波で表現されます。
実装
pythonでsin波を出力する方法については、以下の記事を参考にさせていただきました。とてもわかりやすいソースコードだったので、これらについての解説は省略したいと思います。
[正弦波の合成 | 人工知能に関する断創録]
こちらのソースコードのうち、一番上にあるソースコードを参考にさせていただきました。
その他、自分で行った変更とか
数値に対応した周波数のペアを返す関数
# 数値に対応したdtmfの周波数ペアを返す # 引数は、文字を受け取る # A,B,C,Dには未対応 def dtmf(number): freq_row = ( 697, 770, 852, 941) freq_col = ( 1209, 1336, 1477, 1633) if(number == '0'): row = 3 col = 1 elif(number == '#'): row = 3 col = 2 elif(number == '*'): row = 3 col = 0 else: num = int(number)-1 row = int(num/3) col = int(num%3) return ( freq_row[row], freq_col[col])
DTMFは0~9の数字と#,*,A~Dの16種類に対応した周波数が用意されていますが、今回はA-Dの文字は使わないので省きました。一つ目の戻り値が低群の周波数、2つめが高群の周波数となっています。
次にsin波を作成する関数を編集します。(最終的に作成したソースは後ほど↓↓)
createSinWave関数の引数の周波数を2つにして、さらにsin波を作成する際に二つの波の合成波を用います。また、合成波にした際に振幅が大きいときにその成分が切られてしまうことを避けるためにcreateSinWave関数に渡す際の引数Aの値を1.0->0.5に変更しました。
また、使用に合うようにmain関数を変更しました。
#7行目 def createSineWave (A, f0, fs, length): #変更後 def createSinWave (A, f0, f1, fs, length): #13行目 s = A * np.sin(2 * np.pi * f0 * n / fs) #変更後 s = A * (np.sin(2 * np.pi * f0 * n / fs) + np.sin(2* np.pi * f1 * n / fs))
これで、createSineWave関数は2つの波の合成から出力されるようになりました。
完成したソースコードはこちら。
#coding: utf-8 # dtmf.py # 電話のピッポッパッって音を鳴らしてみる。 import wave import struct import numpy as np from pylab import * def createSineWave (A, f0, f1, fs, length): """振幅A、基本周波数f0、サンプリング周波数 fs、 長さlength秒の正弦波を作成して返す""" data = [] # [-1.0, 1.0]の小数値が入った波を作成 for n in arange(length * fs): # nはサンプルインデックス s = A * (np.sin(2 * np.pi * f0 * n / fs) + np.sin(2* np.pi * f1 * n / fs)) # 振幅が大きい時はクリッピング if s > 1.0: s = 1.0 if s < -1.0: s = -1.0 data.append(s) # [-32768, 32767]の整数値に変換 data = [int(x * 32767.0) for x in data] # plot(data[0:100]); show() # バイナリに変換 data = struct.pack("h" * len(data), *data) # listに*をつけると引数展開される return data def play (data, fs, bit): import pyaudio # ストリームを開く p = pyaudio.PyAudio() stream = p.open(format=pyaudio.paInt16, channels=1, rate=int(fs), output= True) # チャンク単位でストリームに出力し音声を再生 chunk = 1024 sp = 0 # 再生位置ポインタ buffer = data[sp:sp+chunk] while buffer != '': stream.write(buffer) sp = sp + chunk buffer = data[sp:sp+chunk] stream.close() p.terminate() # 数値に対応したdtmfの周波数ペアを返す # 引数は、文字を受け取る # A,B,C,Dには未対応 def dtmf(number): freq_row = ( 697, 770, 852, 941) freq_col = ( 1209, 1336, 1477, 1633) if(number == '0'): row = 3 col = 1 elif(number == '#'): row = 3 col = 2 elif(number == '*'): row = 3 col = 0 else: num = int(number)-1 row = int(num/3) col = int(num%3) return ( freq_row[row], freq_col[col]) if __name__ == "__main__" : # numberList = ( 1, 2, 3, 4, 5, 6, 7, 8, 9, '*', 0, '#') number = raw_input("number or * or # :") numberList = list(number) for num in numberList: freq = dtmf(num) data = createSineWave(0.5, freq[0], freq[1], 8000.0, 0.5) print("%c %d[Hz]+%d[Hz]" % (str(num), freq[0], freq[1])) play(data, 8000, 16)
実行例(177#を入力した場合)
shell% python dtmf.py --> number or * or # :177# --> 1 697[Hz]+1209[Hz] # ♪ --> 7 852[Hz]+1209[Hz] # ♪ --> 7 852[Hz]+1209[Hz] # ♪ --> # 941[Hz]+1477[Hz] # ♪
それぞれの音が0.5秒ずつ鳴ります。
どうやら、正しく出力されていそうです。音も、それっぽい音…なのかな。。。
まとめ
今回は、 Pythonでsin波を作成して音を出力してみました。学校の授業で最近pythonを用いたfft(高速フーリエ変換)などを利用したので馴染みやすい分野でした。
この電話のピッポッパッとい音は2つの周波数から作成されているということは、数年前にみた名探偵コナンの映画で知りました。どこで得た知識が役に立つのかわりませんねぇ〜w
映画だと、受話器を外してこの周波数の音を鳴らせば電話がかけられるみたいな描写があったのですが、実際どうなんでしょう?公衆電話を使って177あたりで試してみたいですね。
どうでもいい話ですが、自分は「ピッポッパッ」だと思っていたのですが、Wikipediaでは「ピ、ポ、パ」と表現されていましたね。これが普通なのでしょうか?w
以上、Pythonでsin波を出力するでした。