音声処理をするときに、基準となる音声データが欲しくなります。
自分の場合、"ラ"の音に該当する440 Hzの音を作成して、音声データを比較をすることが多いです。
その音を周波数領域などで可視化するだけでなく、耳で聞くと、体感しながら音声処理が可能になります。
今回は、Pythonで440 Hzの音をデータ配列上で作成するだけでなく、waveファイルを作成してみます。
実行環境は次の通りです。
Python 3.8.1
Ubuntu 20.04.4 LTS
sin波のデータ配列を作成
今回は上記でも書いたように"ラ"の基本周波数となる$ f$ = 440 Hzの音を1秒間作成します。
振幅を$A$、サンプリングレートを$f_o$、サンプルデータ番号を$n = 1,2,...,N$とすると、sin波の数列は、
A\sin\left(\frac{2\pi f}{f_o}n\right)
で与えられます。
プログラムでは次のようにしてデータ配列を作成します。
import numpy as np
import wave
import struct
A = 1 #振幅
fs = 44100 #サンプリングレート
f0 = 440 #基本周波数
sec = 1 #再生時間
nframe = np.arange(0, fs*sec)
sin_wave = A*np.sin(nframe*2*np.pi*f0/fs) #sin波を生成
sin_wave = np.array(sin_wave * (2**15-1)).astype(np.int16) #16bit符号付き整数に変換
インポートしている、wave, structのモジュールは次のwavファイルを作成するのに扱います。
再生時間は、サンプリングレート×時間で求められます。その値を最大値として、np.arangeでデータ番号の配列を作成します。
今回は16bit符号付き整数型でwaveファイルを作成するので、-1から1まで正規化されているsin波に2^15-1を掛けます。その後に、型をint16に変換します。
waveファイルを作成する
今回のwaveファイル名は"440Hz.wav"とします。
プログラムは次の通りです。
wave_fname = "440Hz.wav"
binary_sin_wave = struct.pack("h"*len(nframe), *sin_wave)
wave_write = wave.Wave_write(wave_fname)
params = (1, 2, fs, len(nframe), 'NONE', 'not compressed')
使うのは、組込みwaveモジュールと、バイナリを取り扱うことができるstrcutモジュールです。
初めての場合、バイナリの作成に苦労すると思います。
解説すると次の通りです。
・sin波のデータ配列をバイナリに変換する。
・今回は16bit符号付き整数型なので、struct.pack()の第1引数を"h"とする。また、データサンプルの数だけこの"h"の文字列の引数が必要なため、生成する配列の長さの分だけかけて文字列を生成する。
・第2引数以降は、sin波の配列をアンパックする。
次に、ファイル名を指定し、Wave_writeオブジェクトを作成します。
wave_write.setparams(params)
wave_write.writeframes(binary_sin_wave)
wave_write.close()
waveファイルを作成するために、ファイルパラメータを指定する必要があります。今回は次の通りです。
チャネル数:モノラル
1サンプルあたりのバイト数:2 byte
サンプリングレート:44100 Hz
サンプルサイズ:44100
圧縮形式:NONE #NONEしか取り扱いなし。
このファイルパラメータをsetparams()で設定します。
上記で作成したsin波のバイナリデータをwriteframes()で書き込んで作成されました。
最後にclose()してwaveファイルを閉じます。
こうして、440 Hzが収録されたwaveファイルの完成しました。Windows標準で再生が可能です。
周波数領域で440Hzのみが検出されるか確認する
自作した440Hzのwaveファイルだが、本当に440Hzの成分のみになっているか確認してみましょう。
次のコードで確認します。今回は簡易のためsoundfileライブラリで、音声データを抽出します。
※ここでの詳細は本記事の本質とは異なるので、割愛します。
import soundfile
import numpy as np
import matplotlib.pyplot as plt
fname = '440Hz.wav'
data, fs = soundfile.read(fname)
PowerDFT = np.abs(np.fft.fft(data))**2
fscale = np.fft.fftfreq(len(data), d=1.0/fs)
print("最大値を取る周波数成分: {0} [Hz]".format(np.argmax(PowerDFT[:int(fs/2)])))
plt.plot(fscale, PowerDFT)
plt.xlim(0, fs/2)
plt.xlabel("frequency [Hz]")
plt.show()
実行結果は次のようになる。
最大値を取る周波数成分: 440 [Hz]
プログラム上でも、グラフ上でも440Hzで音声データが構成されていることが確認できました。
自作したsin波を自分で聞いてみて、音声処理の基準として進めていっていただければ幸いです。