音を鳴らす

いろいろ方法がありますが、簡単でどこでも使えるのが print("\x07") です。ASCII の 07 は音を鳴らすための文字です。いわゆるビープ音ですが、Mac はシステム設定の「サウンド」の「サウンドエフェクト」で音が選べます。

440Hzの正弦波を鳴らす方法はいろいろありますが、ここでは python-sounddevice というライブラリを使います。pip install sounddevice または conda install -c conda-forge python-sounddevice でインストールできます。音のデータは ±1 の範囲に収めます。

import numpy as np
import sounddevice as sd

x = 0.8 * np.sin(2 * np.pi * 440 * np.linspace(0, 1, 44101))
sd.play(x, 44100)

WAVファイルの読み書きは、Python標準ライブラリ wave や、次に説明する SciPy も使えますが、ここでは python-sounddevice と同じ開発者による soundfile ライブラリを使ってみます。pip install soundfile でインストールして、次のようにします:

import soundfile as sf
import sounddevice as sd

data, fs = sf.read("filename.wav")
sd.play(data, fs)

WAVファイルの作成

以下では SciPy を使ってみます。

880Hzの音と1320Hzの音を混ぜると、あるはずのない440Hzの音も聞こえるという話を聞いたので確かめてみましょう。NumPy と SciPy はインストールしておきます(pip install numpy scipy)。

import numpy as np
from scipy.io import wavfile

samplerate = 44100 # 44100Hzサンプリング
duration = 5       # 5秒
t = np.linspace(0, 2 * np.pi * duration, samplerate * duration + 1)

data = 10000 * (np.sin(880 * t) + np.sin(1320 * t))
wavfile.write("test.wav", samplerate, np.round(data).astype("int16"))

こうしてできた test.wav を再生してみましょう。Macであればターミナルに afplay test.wav と打ち込めば再生できます。

880Hzを左耳、1320Hzを右耳に出すには、data = ... の行を次のようにします:

data = 10000 * np.c_[np.sin(880 * t), np.sin(1320 * t)]

1320Hzのほうを少し小さい音にして、1秒遅れて出します:

w = np.append(np.zeros(samplerate), np.ones(samplerate * (duration - 1) + 1))
data = 10000 * np.sin(880 * t) + 5000 * w * np.sin(1320 * t)

いろいろ試してください。聞こえるのは差 (1320-880)Hz だという説もありますが、それなら高い方を例えば 1374 にすれば (1374-880)Hz でほぼレの音が聞こえるはずですね。

ついでに聴力検査用の4000Hzの音を左に5秒、右に5秒出してみましょう。

data1 = 10000 * np.c_[np.sin(4000 * t), 0 * t]
data2 = 10000 * np.c_[0 * t, np.sin(4000 * t)]
data = np.vstack((data1, data2))
wavfile.write("4000.wav", samplerate, np.round(data).astype("int16"))

ボリュームを十分小さくして聞いてみてください。

音階にしてみましょう。中央のラ(A4)は440Hzで、半音上がるごとに周波数は 2**(1/12) 倍になりますので、上のド(C5)は 440 * 2**(3/12) Hzです。ここから初めてドーレミファソラシドーレミファソラシドーレミファソラシドーレミファソラシドーレミファソラシドーと5オクターブ上げていって、どこまで聞こえるか試してください。

samplerate = 44100 # 44100Hzサンプリング
t1 = np.linspace(0, np.pi, samplerate // 2 + 1) # 0.5sec
t2 = np.linspace(0, 2 * np.pi, samplerate + 1) # 1sec
onkai = [2**(3/12), 2**(5/12), 2**(7/12), 2**(8/12), 2**(10/12), 2**(12/12), 2**(14/12)]
tempo = [t2, t1, t1, t1, t1, t1, t1]

data = np.array([])
for k in [440, 880, 1760, 3520, 7040]:
    for o, t in zip(onkai, tempo):
        data = np.concatenate((data, np.sin(k * o * t)))
data = np.concatenate((data, np.sin(14080 * 2**(3/12) * t2)))
wavfile.write("mono.wav", samplerate, np.round(10000 * data).astype("int16"))

left = 10000 * np.c_[data, np.zeros(data.shape[0])]
right = 10000 * np.c_[np.zeros(data.shape[0]), data]
wavfile.write("left.wav", samplerate, np.round(left).astype("int16"))
wavfile.write("right.wav", samplerate, np.round(right).astype("int16"))

各音の周波数(Hz)は次の通りです。ピアノの最高音はC8です。

5678910
C523.31046.52093.04186.08372.016744.0
D587.31174.72349.34698.69397.3
E659.31318.52637.05274.010548.1
F698.51396.92793.85587.711175.3
G784.01568.03136.06271.912543.9
A880.01760.03520.07040.014080.0
B987.81975.53951.17902.115804.3

私はE9くらいまでしか聞こえませんでした。昔はテレビのブラウン管(CRT)の水平同期周波数15750Hzが聞こえたのですが・・・。

C10を超える17000Hz以上の音(モスキート音)は若者にしか聞こえません。若者を寄せ付けないために大音量のモスキート音を使うことがあるようです。

音楽を作る

musicpy が楽しそうです(そのうち書きます)。