目次
pyaudioで3オクターブの音階を管理し和音(コード)を鳴らす
pyaudioをインストールしたので、もう少し遊んでみます。
今回は和音交じりのメロディを記述できるように拡張してみます。
こちらの記事で作成した「Audio」クラスを拡張する
前回、Windows10に、pyaudioをインストールして、単音で音階をならしました。
そこで作成した「Audio」クラスを、和音もならせるように拡張しようと思います。
改良1:使える音階の幅をひろげる
前回はお試しだったので、1オクターブだけしか音階を作っていません。
和音を使うからには、せめて3オクターブくらいは欲しいので、拡張します。
PyAudioで音階を表現するには、周波数を求める必要があって、A(ラ)の周波数が440Hzとして、これに2の十二乗根を掛け合わせていけば、半音ずつずらした音階の周波数が計算できるってのは、まったく、同じです。
ループを回す回数と、Aのところで0乗にするための数字だけ変更します。
def make_scale(self): res = {} octave = -1 for n in range(0, 37): hz = self.a_hz * 2 ** ((n - 21) / 12) if (n % 12) == 0: octave += 1 name = self.code_names[n % 12] + str(octave) print(name, ":", hz) res[name] = hz return res
確認のためにprint文をいれてます。
C0 : 130.8127826502993
C#0 : 138.59131548843604
D0 : 146.8323839587038
D#0 : 155.56349186104046
E0 : 164.81377845643496
F0 : 174.61411571650194
F#0 : 184.9972113558172
G0 : 195.99771799087463
G#0 : 207.65234878997256
A0 : 220.0
A#0 : 233.08188075904496
B0 : 246.94165062806206
C1 : 261.6255653005986
C#1 : 277.1826309768721
D1 : 293.6647679174076
D#1 : 311.1269837220809
E1 : 329.6275569128699
F1 : 349.2282314330039
F#1 : 369.9944227116344
G1 : 391.99543598174927
G#1 : 415.3046975799451
A1 : 440.0
A#1 : 466.1637615180899
B1 : 493.8833012561241
C2 : 523.2511306011972
C#2 : 554.3652619537442
D2 : 587.3295358348151
D#2 : 622.2539674441618
E2 : 659.2551138257398
F2 : 698.4564628660078
F#2 : 739.9888454232688
G2 : 783.9908719634985
G#2 : 830.6093951598903
A2 : 880.0
A#2 : 932.3275230361799
B2 : 987.7666025122483
C3 : 1046.5022612023945
とりあえず3オクターブ分いけてるっぽいので、次にいきます。
改良2:和音をならせる様にする
pyAudioで単音を鳴らすのに「np.sin(np.arange(slen) * t) * gain」みたいにして、Sin派を生成していました。
ということは。
普通に考えれば、和音にする=音を重ねる・・ということなので、単音のSin波を合成してやればいいということになります。
イメージとしては音の数だけ。
tone += np.sin(np.arange(slen) * t) * gain
みたいに足しこんでやって、最後に「tone」を再生すればいいという感じです。
なんですが・・。
実際にやってみると、それだけだと和音(コード)のところで、急に音がでかくなってノイズみたいな音になってしまいます。
なので、今回はtoneに(1 ÷ 重ねる音数)の結果を掛け合わせる方法をとりました。
これが正解なのか?
あんまり情報がないのでよくわかりません。
でも、いろいろ試して一番自然な音量になったのが、この方法なので、そうしてます。
修正したAudioクラスです。
import pyaudio as pa import numpy as np class Audio: def __init__(self, bpm=30): self.audio = pa.PyAudio() self.a_hz = 440 self.smpl_rate = 44100 self.bpm = bpm self.code_names = ( "C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B") self.stream = self.audio.open( format=pa.paFloat32, channels=1, rate=self.smpl_rate, frames_per_buffer=1024, output=True) def __tone(self, hz, note, gain=1.0): slen = int(note * self.smpl_rate) t = float(hz[0]) * np.pi * 2 / self.smpl_rate tone = np.sin(np.arange(slen) * t) * gain if (len(hz) > 1): for i in range(1, len(hz)): t = float(hz[i]) * np.pi * 2 / self.smpl_rate tone += np.sin(np.arange(slen) * t) * gain return tone * (1 / len(hz)) # 音符 def make_note(self): self.note1 = 60 / (self.bpm * 4) notes = [] notes.append(self.note1) notes.append(self.note1 / 2) notes.append(self.note1 / 4) notes.append(self.note1 / 8) return notes # 音階 def make_scale(self): res = {} octave = -1 for n in range(0, 37): hz = self.a_hz * 2 ** ((n - 21) / 12) if (n % 12) == 0: octave += 1 name = self.code_names[n % 12] + str(octave) res[name] = hz return res def play(self, hz, note, gain=1.0, repeat=1): for j in range(repeat): self.stream.write( self.__tone( hz, note, gain).astype( np.float32).tostring())
変更したのは「__tone」メソッドです。
短音と和音(コード)でメソッドを変えるようなことはしたくなかったので、音階の周波数(hz)をリストで受け取るようにして、単音か和音(長さが2以上)で処理を分岐するようにしました。
def __tone(self, hz, note, gain=1.0): slen = int(note * self.smpl_rate) t = float(hz[0]) * np.pi * 2 / self.smpl_rate tone = np.sin(np.arange(slen) * t) * gain if (len(hz) > 1): for i in range(1, len(hz)): t = float(hz[i]) * np.pi * 2 / self.smpl_rate tone += np.sin(np.arange(slen) * t) * gain return tone * (1 / len(hz))
これで、和音もならせるようになったはずです。
和音(コード)を鳴らすサンプルソース
キーCの王道コード進行である「FーGーEmーAm」をやってみます。
構成音はこちら。
- F::ファ・ラ・ド
- G:ソ・シ・レ
- Em:ミ:ソ・シ
- Am:ラ・ド・ミ
タン・タタ・タン・タタのシンプルなリズムにします。
せっかくなので、4小節くらい繰り返すようにして、和音を構成する1音だけオクターブを上げたり下げたりして変化をつける感じでやってみます。
ソースはこんな感じ。
a = Audio() scales = a.make_scale() notes = a.make_note() a.play([scales["G1"]], notes[1]) a.play([scales["B0"]], notes[1]) for i in range(4): if((i % 2) == 1): a.play([scales["F1"], scales["A1"], scales["C1"]], notes[0]) a.play([scales["F1"], scales["A1"], scales["C1"]], notes[1], repeat=2) a.play([scales["G1"], scales["B1"], scales["D1"]], notes[0]) a.play([scales["G1"], scales["B1"], scales["D1"]], notes[1], repeat=2) a.play([scales["E1"], scales["G1"], scales["B1"]], notes[0]) a.play([scales["E1"], scales["G1"], scales["B1"]], notes[1], repeat=2) a.play([scales["A1"], scales["C1"], scales["E1"]], notes[0]) a.play([scales["A1"], scales["C1"], scales["E1"]], notes[1], repeat=2) else: a.play([scales["F1"], scales["A1"], scales["C2"]], notes[0]) a.play([scales["F1"], scales["A1"], scales["C2"]], notes[1], repeat=2) a.play([scales["G1"], scales["B1"], scales["D2"]], notes[0]) a.play([scales["G1"], scales["B1"], scales["D2"]], notes[1], repeat=2) a.play([scales["E1"], scales["G1"], scales["B0"]], notes[0]) a.play([scales["E1"], scales["G1"], scales["B0"]], notes[1], repeat=2) a.play([scales["A1"], scales["C2"], scales["E1"]], notes[0]) a.play([scales["A1"], scales["C2"], scales["E1"]], notes[1], repeat=2
鳴らしてみると、結構いい感じです。
王道コード進行ですしね。
これだけでも、コードをいろいろ変えてみたら、結構遊べました(笑)
今回はこんなところで。
ではでは。