シンセサイザ(オシレータ)の基本波形として一般的なサイン波,矩形波,のこぎり波,三角波,白色雑音は,ファミコンのBGMのようなゲーム音楽(チップチューン)の作成や音信号処理のテストでよく利用されます.
サイン波は基本周波数(音高)をとすると,
として簡単に書けますが,矩形波・ のこぎり波・三角波は若干面倒です.
ただ,Pythonだと scipy.signal.sawtooth, scipy.signal.square を使えばサイン波と同様の形式でプログラミングできるので非常に楽です.
この記事では,Pythonでnumpy や scipy を使って基本波形5種を生成する方法を紹介します.
基本波形の生成
準備
まずはパラメタとして波形の音高,長さ,サンプリング周波数を決めます.
長さ,サンプリング周波数が決まれば各サンプル点の時刻t
が算出できます.
duration = 0.025 # 25 msec sr = 44100 # サンプリング周波数 fo = 261.63 # 基本周波数(C4) t = np.arange(0, sr*duration) / sr # サンプル点の時間
サイン波 (正弦波)
Pythonでは numpy.sin を使って1行で生成できます.
x = np.sin(2*np.pi*fo*t)
矩形波 (方形波)
矩形波は scipy.signal.square を使うことでサイン波と同様の形式で生成できます.
x = scipy.signal.square(2*np.pi*fo*t)
なおscipy.signal.squareは引数duty
でデューティー比(パルス幅)を変更できます.
x = scipy.signal.square(2*np.pi*fo*t, duty=duty)
のこぎり波(鋸歯状)
のこぎり波も scipy.signal.sawtooth を使うことでサイン波と同様の形式で生成できます.
x = scipy.signal.sawtooth(2*np.pi*fo*t)
なお,引数width
でのこぎり波の形状を変更することができます.
デフォルトはwidth=1.0
です.
x = scipy.signal.sawtooth(2*np.pi*fo*t, width=width)
三角波
三角波は,scipy.signal.sawtooth の width=0.5
に相当します.
x = scipy.signal.sawtooth(2*np.pi*fo*t, width=0.5)
白色雑音 (ホワイトノイズ)
白色雑音は正規分布を発生させます.Pythonの場合は np.random.normal
を使えば1行で書けます.
ただし,最大/最小値が制限されていないため,用途によっては正規化や値域の制限すべき場合があります.
正規化は平均パワー/RMSが所望の値になっているか,クリッピングする場合は波形が歪んで音質に影響していないかなどに注意が必要です.
# -1.0 ~ 1.0 に制限 # 方法1. 正規化 x = 2*(x-np.min(x))/(np.max(x)-np.min(x)) - 1.0 # 方法2. クリッピング x[x<-1.0] = -1.0 x[x>=1.0] = 1.0
x = np.random.normal(0.0, 1.0, len(t))
まとめ
Pythonでゲーム音楽や音信号処理で利用される基本波形(サイン波,矩形波,のこぎり波,三角波,白色雑音)を生成する方法を紹介しました.
なお,作成した音波形を聞いたりオーディオファイルとして書き出すのは,Pythonではpython-soundcard
やsoundfile
といったライブラリを使えば簡単にできます.
詳しくは以下の記事をご覧ください.
プロット用コード
import sys import matplotlib.pyplot as plt import numpy as np import scipy.signal def plot(x, c="c", label=""): plt.plot(x, c=c, label=label, alpha=0.8) plt.ylim(-1.5, 1.5) plt.legend(loc='upper right') plt.grid() duration = 0.025 # 25 msec sr = 44100 # サンプリング周波数 t = np.arange(0, sr*duration) / sr # サンプル点の時間 fo = 261.63 # 基本周波数(C4) # サイン波 x = np.sin(2*np.pi*fo*t) plt.subplot(5,1,1) plot(x, "c", "Sine wave") # 矩形波 x = scipy.signal.square(2*np.pi*fo*t) plt.subplot(5,1,2) plot(x, "m", "Square wave") # のこぎり波(鋸歯状) x = scipy.signal.sawtooth(2*np.pi*fo*t) plt.subplot(5,1,3) plot(x, "g", "Sawtooth wave") # 三角波 x = scipy.signal.sawtooth(2*np.pi*fo*t, width=0.5) plt.subplot(5,1,4) plot(x, "b", "Triangle wave") # 白色雑音 x = np.random.normal(0.0, 0.25, len(t)) x[x<-1.0] = -1.0 x[x>=1.0] = 1.0 plt.subplot(5,1,5) plot(x, "k", "Whitenoise") plt.tight_layout() plt.show() # 矩形波 デューティー比変更 cmap = plt.get_cmap("tab10") duties = np.arange(0.1, 1.0, 0.2) plt.clf() for k, duty in enumerate(duties): plt.subplot(len(duties), 1, k+1) x = scipy.signal.square(2*np.pi*fo*t, duty=duty) plot(x, cmap(k), f"Square wave (duty={duty:0.1f})") # 三角:鋸歯状波 width比変更 cmap = plt.get_cmap("tab10") width_list = np.arange(0.0, 1.1, 0.2) plt.clf() for k, width in enumerate(width_list): plt.subplot(len(width_list), 1, k+1) x = scipy.signal.sawtooth(2*np.pi*fo*t, width=width) plot(x, cmap(k), f"Sawtooth wave (width={width:0.1f})") plt.tight_layout() plt.show()