Pythonは科学計算のライブラリが充実している。それらのライブラリを使うことで、サイン波を手軽に生成できる。そうであれば、簡単なシンセサイザーを作ることもできるだろう。今回は、PythonのライブラリPyAudioとNumPyで音楽の生成に挑戦してみよう。

PyAudioのインストール

PyAudioは、Pythonのオーディオ関連ライブラリだ。音声の録音、再生、書き出しに対応している。今回は、このPyAudioとNumPyを利用して音楽を奏でてみようと思う。

今回は、Anaconda3に、PyAudioをインストールしてみる。Anacondaのインストールは、本連載の45回目を参考にしよう。

Windowsなら、スタートメニューからAnaconda Promptを起動しよう。macOSであれば、ターミナル.appを起動しよう。そして、以下のコマンドを実行する。

conda install pyaudio

なお、NumPyは最初からAnacondaにインストールされている。

簡単なビープ音を鳴らしてみよう

それでは、最も簡単に、ビープ音を再生するプログラムを紹介しよう。以下のプログラムを「beep.py」という名前で保存しよう。

import pyaudio
import numpy as np

# 音声を出力するためのストリームを開く --- (*1)
p = pyaudio.PyAudio()
stream = p.open(format=pyaudio.paFloat32,
                channels=1,
                rate=44100,
                frames_per_buffer=1024,
                output=True)

# 適当なサイン波を生成する --- (*2)
samples = np.sin(np.arange(50000) / 20)

# サイン波を再生する --- (*3)
print("play")
stream.write(samples.astype(np.float32).tostring())
stream.close()

コマンドラインより、以下のコマンドを実行すると、ポーンと言うシンプルなサイン波が再生される。

python beep.py

プログラムを見てみよう。(*1)の部分では、PyAudioの出力ストリームを開き、音声を再生できる状態にする。そして、(*2)の部分でサイン波を生成する。そして、プログラムの(*3)の部分でサイン波を再生する。

なお、ここで再生した波形はNumPyのsinメソッドで生成した波形で、最初の500個だけ取り出して見てみると、以下のような波形になっている。以下はJupyter Notebbokで波形を表示したところだ。

  • 再生したサイン波をグラフで表示したところ

    再生したサイン波をグラフで表示したところ

サイン波の生成に関してだが、NumPyを使うことで簡単に生成できる。上記のプログラム(*2)の部分を詳しくみてみよう。まず、「np.arange(10)」のように記述すると、[0,1,2,3,...9]のような連番の配列を作成する。「np.arange(10) / 10」と書くと、[0, 0.1, 0.2, 0.3 ... 0.9]のような値が生成される。そこで、「np.sin( np.arange(50000) / 20 )」のように書くと、各値にsin関数を適用したサイン波が生成される。

音程を再生してみよう

次にドレミの音程を再生してみよう。以下のプログラムを「doremi.py」という名前で保存する。

import pyaudio
import numpy as np

# サンプリングレートを定義 --- (*1)
RATE = 44100

# BPMや音長を定義 --- (*2)
BPM = 100
L1 = (60 / BPM * 4)
L2,L4,L8 = (L1/2,L1/4,L1/8)

# ドレミ...の周波数を定義 --- (*3)
C,D,E,F,G,A,B,C2 = (
        261.626, 293.665, 329.628, 
        349.228, 391.995, 440.000,
        493.883, 523.251)

# サイン波を生成 --- (*4)
def tone(freq, length, gain):
    slen = int(length * RATE)
    t = float(freq) * np.pi * 2 / RATE
    return np.sin(np.arange(slen) * t) * gain

# 再生 --- (*5)
def play_wave(stream, samples):
    stream.write(samples.astype(np.float32).tostring())


# 出力用のストリームを開く --- (*6)
p = pyaudio.PyAudio()
stream = p.open(format=pyaudio.paFloat32,
                channels=1,
                rate=RATE,
                frames_per_buffer=1024,
                output=True)

# ドレミを再生 --- (*7)
print("play")
play_wave(stream, tone(C, L8, 1.0)) 
play_wave(stream, tone(D, L8, 1.0)) 
play_wave(stream, tone(E, L4, 1.0)) 
stream.close()

このプログラムを実行するには、以下のコマンドを打ち込もう。

python doremi.py

プログラムを確認してみよう。(*1)の部分では、サンプリングレートを指定する。サンプリングレートというのは、1秒をいくつのデータで表すかというものだ。ここでは、44100(44.1kHz)を指定しているので、1秒間に44100個のデータを再生デバイスに送信するという意味になる。ちなみに、CDのサンプリングレートは44.1kHz、FMラジオは33kHz、電話は8kHzだ。

そして、(*2)の部分では、音楽のテンポを表すBPM(Beat Per Minute)や音の長さを定義する。ここでは、BPMを100に指定した。これは、1分間あたりに刻む拍数を100回にするという意味だ。そして、ここでは、音の長さを秒単位で、全分音符(L1)、二分音符(L2)、四分音符(L4)、八分音符(L8)を計算する。

次に、(*3)の部分では、ドレミの音の周波数を定義している。ドの音(C)は261.626Hz、レの音(D)は293.665Hz、ミの音(E)は329.628Hz ... のように周波数を指定した。

(*4)の部分ではサイン波を生成する関数toneを定義した。NumPyを利用することでサイン波を手軽に作成している。(*5)の部分では、生成したサイン波をPyAudioのストリームに書き込む関数play_waveを定義した。(*6)の部分は、PyAudioでストリームを開く処理で、(*7)の部分で実際にドレミーと再生を行う。

なお、音程によるサイン波の違いを確認してみよう。以下のように、ド・レ・ラ・シの四つの音程の波形を生成した。

tone_c = tone(C, L4, 1.0) # ド
tone_d = tone(D, L4, 1.0) # レ
tone_a = tone(A, L4, 1.0) # ラ
tone_b = tone(B, L4, 1.0) # シ

上記の波形データをJupyterで先ほどと同様の方法で描画してみた。音程ごとにサイン波の周期が異なることが分かるだろう。

  • 音程によるサイン波の違いを確認

    音程によるサイン波の違いを確認

周波数と十二平均律の関係

ところで、プログラムの(*3)の部分で、ドレミの周波数を実数で指定した。しかし、この値は計算によって求めることができる。数学と音楽の関係は面白い。せっかくなので計算してみよう。

まず、前提条件として、身近な音楽は、十二平均律でチューニングされている。これは、1オクターブを12等分して表したものである。つまり、12半音上が1オクターブ上となる。

そして、よくギターのチューニングで使われる440Hzはラの音だ。ここから、1オクターブ上の音を求めるには周波数に2を掛ければ良い。そのため、1オクターブ上のラの周波数は880Hzとなる。ここから、考えてみると、半音上の音の周波数を計算したい場合、元の周波数に2の12乗根を掛け合わせば良い。

分かりやすく、Pythonのプログラムで確認してみよう。ラ(440Hz)の半音上、ラ♯(またはシ♭)を求めるには、以下のようなにする。実行すると、466.1637615180899が表示される。

a = 440
a_sharp = a * (2 ** (1/12))
print(a_sharp)

これを利用して、128段階の周波数を計算するプログラムを作ってみよう。128段階とはMIDI楽器の鍵盤に対応する周波数だ。

base_a = 440
names = ("C","C#","D","D#","E","F","F#","G","G#","A","A#","B")
res = []
for n in range(0, 128):
    hz = 440 * 2 ** ((n-69) / 12)
    name = names[n % 12]
    o = int(n / 12)
    res.append([name, o, hz])

import pandas as pd
df = pd.DataFrame(res, columns=["Note", "Octave", "Freq"])
df
  • 128段階の音階ごとの周波数を表示したところ

    128段階の音階ごとの周波数を表示したところ

まとめ

以上、今回は、Pythonで音楽を再生する方法を紹介した。科学計算ライブラリのNumPyと音声ライブラリのPyAudioを使うことで、気軽に音を生成して鳴らすことができた。こうした波形合成を行うには、数学の知識も必要になるが、ドレミを鳴らすだけであれば、それほど難しい計算は必要ではない。今回のプログラムを叩き台にすることで、簡単なシンセサイザーをPythonで自作することもできるだろう。Pythonでオリジナル楽器を作成するのも楽しいだろう。

自由型プログラマー。くじらはんどにて、プログラミングの楽しさを伝える活動をしている。代表作に、日本語プログラミング言語「なでしこ」 、テキスト音楽「サクラ」など。2001年オンラインソフト大賞入賞、2004年度未踏ユース スーパークリエータ認定、2010年 OSS貢献者章受賞。技術書も多く執筆している。