いぬおさんのおもしろ数学実験室

おいしい紅茶でも飲みながら数学、物理、工学、プログラミング、そして読書を楽しみましょう

平均律、純正律のドミソの和音を聴き比べる

 音階の話です。音の高さを少しずつ変えて音階ができあがります。音の周波数を少しずつ増やしてだんだん高くなるようにするのです。ラの音を440Hzとしましょう。まずラの音を鳴らすwavファイルを作ってみます。コードを載せておきますが、作り方は前にブログで説明してありますので、そちらも参照してください。 
www.omoshiro-suugaku.com

#========================================================
#waveファイルを作る。ラの音(440Hz)。
#
import sys
import numpy as np
import wave
pi = np.pi
fs = 22050      #サンプリング周波数
s  = 20         #20秒の長さのwaveファイルを作る
#s秒分のサウンドデータを作るには、fs*sフレームが必要。
N  = fs * s     #フレーム数(サウンドデータ数。左右で1フレーム)
t = np.arange(0, s, 1/fs)   #横軸
#sin(2π*□*t) の周波数は□の部分。16ビットで録音するので、
#-32768~32767がデータの範囲であることに注意。
#この範囲の外に出ると音が潰れる。
#----------------------
ldata = 15000 * np.sin(2*pi*440*t)
rdata = 15000 * np.sin(2*pi*440*t)
#----------------------
sounddata = np.zeros(N * 2, dtype= "int16") #左右のデータ分が必要
sounddata[0::2] = ldata
sounddata[1::2] = rdata
writewave = wave.Wave_write("keshitene.wav") #保存先ファイル
#setparamの引数は
#(nchannels, sampwidth, framerate, nframes, comptype, compname)
#先頭から順にステレオ、サンプルのサイズ2バイト、
#サンプリング周波数fs、フレーム数N。
writewave.setparams((2, 2, fs, N, 'NONE', 'NONE'))
writewave.writeframes(sounddata) #編集したサウンドデータを保存
writewave.close()
sys.exit()
#========================================================

 さて、ギター、ピアノなどの楽器では平均律という音階が使われています。音の周波数は2倍にすると1オクターブ高くなります。例えば880Hzの周波数の音は440Hzのラの音の1オクターブ上の音になるのです。1オクターブは半音(ギターではフレット1個分、ピアノでは白鍵と黒鍵の間)12個から成り、
ラ,ラ#,シ,ド,ド#,レ,レ#,ミ,ファ,ファ#,ソ,ソ#,ラ
です。半音1個分高くなると周波数は[2の12乗根]≒1.059463094(=r とおきましょう)倍になります。それを12回繰り返して周波数は[2の12乗根]の12乗=2倍となって1オクターブ上の音が得られるのです。つまり、ラ=440Hz、ラ#=440Hz*r≒466.16Hz、シ=440Hz*r*r=493.88Hz、……、ラ(1オクターブ上)=440Hz*r*r*……*r(rを12回かけた)=880Hzとなります。あとでドミソの和音を鳴らしますが、ド=523.3Hz、ミ=659.358Hz、ソ=783.98Hzです。
 これに対し、純正律という音階もあります。人間は2つ以上の音を聴くとき、周波数の比が単純な整数比(小さな整数同士の比)であるとききれいに調和して聞こえます。例えば周波数の比が2:3だときれいに調和し、23:35とかだときれいには聞こえないのです。実際にどうやってそれぞれの音の周波数を決めるかについては色々なサイトもありますし、ご覧になってください。さて、この純正調では周波数の比がド:ミ:ソ=4:5:6です。ド=523.3Hzとすると、ミ=654.125Hz、ソ=784.95Hzです。
 平均律でドミソを鳴らす(Cというコードですね)のと、純正律で鳴らすのとでは純正律の方がきれいに調和するはず! ……というわけで実験です。コードでは2の12乗根を r12 という変数に入れて使っています。

#========================================================
#waveファイルを作る。ドミソの和音。
#
import sys
import numpy as np
import wave
pi = np.pi
fs = 22050      #サンプリング周波数
s  = 20         #20秒の長さのwaveファイルを作る
#s秒分のサウンドデータを作るには、fs*sフレームが必要。
N  = fs * s     #フレーム数(サウンドデータ数。左右で1フレーム)
t = np.arange(0, s, 1/fs)   #横軸
#sin(2π*□*t) の周波数は□の部分。16ビットで録音するので、
#-32768~32767がデータの範囲であることに注意。
#この範囲の外に出ると音が潰れる。
do = 523.3 #ドの音の周波数
r12 = 2 ** (1/12) #2の12乗根
#平均律でドミソ
print(do*5/4, do*r12**4)
#--------------
# A
#
#ldata = 15000 * np.sin(2*pi*do*5/4*t) #平均律でミ
#rdata = 15000 * np.sin(2*pi*do*(r12**4)*t) #純正律でミ
#--------------
# B
#
#純正律でドミソ
#ldata = 15000 * np.sin(2*pi*do*t) + 15000 * np.sin(2*pi*do*5/4*t)
#rdata = 15000 * np.sin(2*pi*do*3/2*t)
#--------------
# C
#
#平均律でドミソ
ldata = 15000 * np.sin(2*pi*do*t) + 15000 * np.sin(2*pi*do*(r12**4)*t)
rdata = 15000 * np.sin(2*pi*do*(r12**7)*t)
#--------------
sounddata = np.zeros(N * 2, dtype= "int16") #左右のデータ分が必要
sounddata[0::2] = ldata
sounddata[1::2] = rdata
writewave = wave.Wave_write("keshitene.wav") #保存先ファイル
#setparamの引数は
#(nchannels, sampwidth, framerate, nframes, comptype, compname)
#先頭から順にステレオ、サンプルのサイズ2バイト、
#サンプリング周波数fs、フレーム数N。
writewave.setparams((2, 2, fs, N, 'NONE', 'NONE'))
writewave.writeframes(sounddata) #編集したサウンドデータを保存
writewave.close()
sys.exit()
#========================================================

コード中の、A、B、Cの部分はどれかひとつだけを使います。使わない部分はコメントアウトします。上のコードではA、Bをコメントアウトしています。これを実行すると平均律のドミソを鳴らすwavファイルができあがります。
 実際に平均律純正律でファイルを2つ作って聴いてみました。……しかし、ぼくには違いが分かりませんでした。どちらもきれいに調和して聴こえます。聴く人が聴けば分かるのかも知れません。本当にずれた周波数の音が録音されているんだろうね……?と疑問に思い、念のためAの部分だけを生かして平均律のミ、純正律のミを鳴らすwavファイルを作って鳴らしてみると、確かにうなりが聞こえます。周波数の差がおよそ5Hz。だから高校物理の知識によるとうなりは1秒で5回くらい聞こえるはず。しっかり理屈通りにうなりが聴こえています。「やはり高さの違う音が録音されていた!」と一応確認できました。
 平均律だと明らかに濁った和音でした、とかいう結果ならさらに面白かったけれど、まあよしとしましょう。