【Python】時間変化するサイン波を作る
サイン波
まずは一定なサイン波について考えてみます。周波数が F [Hz]、振幅が A のサイン波は下式で表現されます。
ここで t は時間インデクス、fs はサンプリング周波数、初期位相は 0 としています。これを実装すると、次のようになり
# -*- coding: utf-8 -*- from scipy import arange, sin from scipy import pi as mpi from matplotlib import pylab as pl # =============== # サイン波を作る # =============== def make_sine(F, A, fs, sec = 2.): ret = A * sin(2. * mpi * F * arange(int(fs * sec)) / fs) return ret # ======== # テスト # ======== F_default = 10 A_default = 1 fs_default = 16000 def test(): sine_wave = make_sine(F_default, A_default, fs_default) fig = pl.figure() fig.add_subplot(111) pl.plot(sine_wave) pl.xlim([0, len(sine_wave)]) pl.show() if __name__ == "__main__": test()
こんな感じでサイン波が作れます。
時間変化するサイン波
では、時間が経つにつれて音の高さが変わる、つまり周波数の変化するサイン波はどのように作れるでしょうか?ポイントは、周波数によって進む位相の大きさが変化することです。周波数が一定の場合では、時間インデクスが一つ進むごとに位相(角周波数)が ずつ一定に進むので、上のように簡単な式で書けましたが、周波数が変わる場合は、毎回、角周波数を計算してそれを位相に足していかなければなりません。
次のコードでは、100 ~ 1000 [Hz] まで線形的に周波数の変化するサイン波を作っています。for 文で角周波数を足していくのは不格好なので、cumsum 関数で一気に計算しているところに注意してください。
# -*- coding: utf-8 -*- from scipy import arange, cumsum, sin, linspace from scipy import pi as mpi # ========================== # 時間変化するサイン波を作る # ========================== def make_time_varying_sine(start_freq, end_freq, A, fs, sec = 5.): freqs = linspace(start_freq, end_freq, num = int(round(fs * sec))) ### 角周波数の変化量 phazes_diff = 2. * mpi * freqs / fs ### 位相 phazes = cumsum(phazes_diff) ### サイン波合成 ret = A * sin(phazes) return ret from scikits.audiolab import wavwrite A_default = 0.5 fs_default = 16000 def test2(): sine_wave = make_time_varying_sine(100, 1000, A_default, fs_default) wavwrite(sine_wave, "../wav/time_varying_sine_wave.wav", fs = fs_default) if __name__ == "__main__": test2()
こんな感じの音になります。
100~1000 [Hz]、5 [sec]
ちなみに 8000 [Hz] まで変化させてみた音がこんな感じです。かなり拷問に近いので気をつけてください。
100~8000 [Hz]、20 [sec]
最後らへんエイリアシング的な何か起こしてますね、なぜか。
今回は線形単調に周波数が変化するサイン波を合成しましたが、角周波数を足し込んでいくことさえ理解していれば、どんな風に変化するサイン波でも合成できるので、挑戦してみましょう。
(振幅(音量)を固定にしていますが、これは sin 関数部に任意の振幅を掛け合わせるだけなので大丈夫でしょう。)