AT's Blog

趣味のプログラミングに関する技術メモがメインです。

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による波形生成

qiita.com

aidiary.hatenablog.com

scipy.io.wavefile.write()の仕様

https://docs.scipy.org/doc/scipy/reference/generated/scipy.io.wavfile.write.html