読者です 読者をやめる 読者になる 読者になる

すこしふしぎ.

VR/HI系院生による技術ブログ.まったりいきましょ.(友人ズとブログリレー中.さぼったら焼肉おごらなきゃいけない)

pythonで和音を鳴らそう

pyaudio python

一時期はコーラスに入っていた僕です. 最近はあまり歌ってないですが,それでもたまにカラオケとか行きたくなります. 普通に歌うのは飽きてきたので,歌いなれた曲だとコーラスパートを歌いたくなったりします.

とはいえ何度も歌ったり聞いたりしている曲はまだしも, 初見曲でコーラスパートを聞き取る耳とかセンスとか有りません. ましてアレンジつけるなんて夢の先です.

そこで”コード進行のパターン覚えたら3度/6度ハモ付けやりやすくなるのでは”と思いました.

となればまずはやってみようということで,コード進行のパターン本を買ってきました. それを見ながらキーボードを叩くのですが..

4和音が弾けません.

3和音なら引けるのですが4和音だと手がいたいです. こんなん何指で弾けばいいんですか.ピアニストってすごいなぁと思います. しかしこれでは勉強しようにも先に手が音を上げてしまいます.

そこで思いました.

自分で弾けないなら,プログラムに弾かせればいいじゃない

エンジニアなら誰もがこう思うはずですね.面倒ならばプログラムにやらせればいいんです.これなら手が痛くなることも有りません.

というわけで,弾きたい和音をぽちっと打つだけでそれっぽく弾いてくれるプログラムを作ります. ちょうどpyaudioつかって信号処理の勉強をしようと思っていた所なので,軽い勉強になりそうです.

では,codeではなくchordの勉強,もといpyaudioのお勉強と行きましょう. 正直誰得感が溢れ出ていますが,作りたいものを作るのがクリエータです.

pyaudioで音を鳴らす

単音を鳴らす

音楽的にhello worldといえばA(440hz)の音を鳴らすことではないでしょうか.いわゆる音叉です. NHKの"ポーン"ってなるやつです. まずはシンプルに単音をならすプログラムをつくってみます.

公式サイトには既存ファイルを読み込んだり録音したり録音を出力したりのサンプルは有るのですが, 自分で波形を生成して再生するサンプルが有りません. ググったところこの辺が参考になりました.

正弦波の合成:人工知能に関する断想録

Making noise in Python:milkandtang

サンプルコピペして動かなかったので読み込んだwaveファイルがbufferとしてどう利用されているのかprintしたりいろいろ追調査した結果,framefrata=8000をframerate=44100にしたら動いたのはいい思い出です.1日悩みました

以下単音鳴らすコードの紹介.numpyとpyaudioを利用します.入ってない場合はpipでごにょごにょしてください.python2.7で動作確認済みです. MIDI?しらn

#coding:utf-8
import math
import numpy
import pyaudio

#指定周波数でサイン波を生成する
def sine(frequency, length, rate):
    length = int(length * rate)
    factor = float(frequency) * (math.pi * 2) / rate
    return numpy.sin(numpy.arange(length) * factor)

#オーディオ鳴らす
def play_tone(stream, frequency=440, length=1, rate=44100):
    chunks = []
    chunks.append(sine(frequency, length, rate))
    chunk = numpy.concatenate(chunks) * 0.25
    stream.write(chunk.astype(numpy.float32).tostring())

#main
if __name__ == '__main__':
    p = pyaudio.PyAudio()
    stream = p.open(format=pyaudio.paFloat32,
                    channels=1, rate=44100, output=1)
    play_tone(stream)
    stream.close()
    p.terminate()

2つ目のサイトのコピペです. 基本的には

  • sin波つくる関数:sine()

  • pyaudioのstreamにdataを流し込む(再生する)関数:play_tone()

の二つが用意されているようなイメージでしょうか.

さくっと鳴らすには良さそうです.

和音を鳴らす

単音を生成するモジュールができてしまえば,これを複数用意したのちにlistにぶっこんで加算してクリッピングすれば和音に鳴るんじゃないでしょうか.何とかなりそうです. こちらを再び参考にしつつこんなコードを書きました.

#coding:utf-8
#Last Change: 2013_11_15_12:42:13.

import math
import numpy
import pyaudio

# 定数の生成
key_name = ["C","D","E","F","G","A","B","C+"]
key_diff = [-9,-7,-5,-4,-2,0,2,3]
key_frequency = {}
for key,diff in zip(key_name,key_diff):
  key_frequency[key] = 440 * math.pow(2,diff * (1/12.0))

def sine(frequency, length, rate):
  length = int(length * rate)
  factor = float(frequency) * (math.pi * 2) / rate
  return numpy.sin(numpy.arange(length) * factor)

def chord(frequency, length, rate):
  # 音源生成
  src = []
  src.append(sine(frequency,length,rate))
  src.append(sine(frequency * math.pow(2,(4/12.0)),length,rate))
  src.append(sine(frequency * math.pow(2,(7/12.0)),length,rate))
  res = numpy.array([0] * len(src[0])) #ダミーの空配列
 
  #加算&クリッピング
  for s in src:
    res = res + s
  res *= 0.5
  
  return res

def play_chord(stream, frequency=440, length=1, rate=44100):
  chunks = []
  chunks.append(chord(frequency, length, rate))
  chunk = numpy.concatenate(chunks) * 0.25
  stream.write(chunk.astype(numpy.float32).tostring())

if __name__ == '__main__':
  p = pyaudio.PyAudio()
  stream = p.open(format=pyaudio.paFloat32,
      channels=1, rate=44100, output=1)

  for key in key_name:
    play_chord(stream,frequency=key_frequency[key])

  stream.close()
  p.terminate()

ちゃんと和音が鳴りました.ちょっと解説.

冒頭

key_frequancyとして各音の周波数を無理矢理生成しています. 今回は12平均律をつかい,Aの440hzを基準として半音につき2の1/12乗倍するようにしました.ここの部分ですね.

key_frequency[key] = 440 * math.pow(2,diff * (1/12.0))

なかなか無理矢理ですがまぁいいでしょう.

和音生成

とりあえずmajorにすりゃいんじゃね?ってことで,指定された周波数を根音とする3和音を生成します.根音の周波数をもとに,長三度と五度の周波数を生成して加算&クリップします.ここの部分です.

 src.append(sine(frequency,length,rate)) #根音
 src.append(sine(frequency * math.pow(2,(4/12.0)),length,rate)) #長三度(半音4つ上)
 src.append(sine(frequency * math.pow(2,(7/12.0)),length,rate)) #五度(半音7つ上)
 res = numpy.array([0] * len(src[0])) #ダミーの空配列

 # 3つのsin波を合成
 for s in src:
   res = res + s 

 res *= 0.5 #面倒なので全体を半分に.

sin波の合成がアホみたいに楽でいいですね.なお通常のlistの和算では

a = [1,2,3]
b = [4,5]
print a+b
[1, 2, 3, 4, 5]

てな感じになってしまいます.numpyに感謝です.

ただ若干ノイズが聞こえる気がするのですがこれはなんなのでしょう...

とりあえず和音を鳴らすところまでできましたので,今回はいったんここまで. そのうち,好きなminorやらsus4やらを自由にながせるようにしたいです.