Pythonで和音を奏でてみる
概要
Python+Numpy+Scipyでsin波の合成波形(和音)を生成し、wav形式で出力する。
まず、前提知識として十二平均律について概説し、 その後合成波形生成に用いたPythonコードについて説明する。
十二平均律
まず前提として、周波数比が1:2となる音程をオクターブと呼ぶ。 仮に基準音となるA音を周波数442Hzとすると、1オクターブ上のA音の周波数は884Hzとなる。
十二平均律は、この1オクターブを周波数について等比で12音に分割した音律だ (等差ではない。ちなみに、僕はずっと等差だと勘違いしていた)。
定義に従い、基準となる周波数をf、比をxとして式を立てると、
f*x^12 = f*2
x = 2^(1/12) = 1.0594630943593
となり、比xを求めることができる。 この比を用いて、A=442Hzから1オクターブ上までの各音の周波数を求めると、下表のようになる。
音名 | 周波数 [Hz] |
---|---|
A | 442 |
A# | 468.2826877 |
B | 496.1282254 |
C | 525.6295448 |
C# | 556.8851041 |
D | 589.9992155 |
D# | 625.0823946 |
E | 662.251728 |
F | 701.631265 |
F# | 743.3524311 |
G | 787.5544668 |
G# | 834.3848924 |
A | 884 |
音声波形の生成
前項で作成した周波数表を基に、 Pythonで単純な合成音声波形(和音)を生成する。 今回は、C=525.6295448Hz, E=662.251728Hz, G=787.5544668Hzで構成される Cトライアドを生成する。
Pythonコード
import numpy as np from scipy.io import wavfile freq_C = 525.6295448 freq_E = 662.251728 freq_G = 787.5544668 seconds = 5.0 rate = 441000 def oscillator(frequency, seconds, rate): phases = np.cumsum(2.0 * np.pi * frequency / rate * np.ones(int(rate * seconds))) return np.sin(phases) wave_C = oscillator(freq_C, seconds, rate) wave_E = oscillator(freq_E, seconds, rate) wave_G = oscillator(freq_G, seconds, rate) wave = 0.1*(wave_C + wave_E + wave_G) wave = (wave * float(2 ** 15 - 1)).astype(np.int16) wavfile.write("chord_C.wav", rate, wave)
開発環境
- OS X Yosemite 10.10.5
- Python 3.6.1 | Anaconda 4.4.0 (x86_64)
- Numpy 1.12.1
- sin波の生成と合成に使用
- Scipy 0.19.0
- wavファイルの書き出しに使用
設定
音声期間secondsは5.0[s]、サンプリング周波数rateは441[kHz]とした(CD規格と同様)。
sin波形の生成
sin波形の生成は、oscillator()が行う。関数内で、位相のnumpy配列を生成し、それをnp.sin()に与えて戻り値としている。
def oscillator(frequency, seconds, rate): phases = np.cumsum(2.0 * np.pi * frequency / rate * np.ones(int(rate * seconds))) return np.sin(phases)
np.ones()はrate*seconds個、すなわち総サンプリング数個だけ要素を持つ1で初期化されたnumpy配列を生成する。
np.cumsum()は、与えられたnumpy配列の累積和を返すので、この場合はsin波形の位相の配列を生成する。
クリッピング対策
波形の振幅が[-1, 1]を超えた場合、wavファイル出力時にクリッピングされる。 そのため、クリッピング対策として、CEGの各sin波形を加算した後に、適当に振幅を0.1倍している。
wave = 0.1*(wave_C + wave_E + wave_G)
なお、クリッピング対策を忘れると、非常にノイジーな音声が生成される可能性がある。大音量のノイズで耳にダメージを受けるかもしれないので要注意(経験談)。
ビット変換とwavファイル出力
wavファイル出力はwavfile.write()で行っているが、その前にサンプルビット数16bitに収まるようデータ型を変換している。
wave = (wave * float(2 ** 15 - 1)).astype(np.int16) wavfile.write("chord_C.wav", rate, wave)
参考文献
Numpy+Scipyによる波形生成
scipy.io.wavefile.write()の仕様
https://docs.scipy.org/doc/scipy/reference/generated/scipy.io.wavfile.write.html