波形処理 第4回コード演奏
2021.01.16
YouTube でも紹介しています。画像をクリックすると再生できます。
波形処理第4回では、深層学習第3回コード進行解析 で得られたコード進行を例として、音を合成する仕組みを考察してみました。
まだ、試行錯誤の状況なので、未完成な内容です。
今回使用するコード進行パターンです。
B→E→A→EonG#→F#m
詳細は後回しにして、このコード進行を分散和音、続けてアルペジオにして鳴らしてみます。
※1オクターブの範囲内での処理を行っているので、正しい分散和音にはなっていません。
■サイン波をベースに合成したコード演奏
HTML5のaudioに対応していないブラウザのためサンプルは表示されません。
ここで使用している波形は単純なサイン波です。
サイン波を音源とする場合、その周波数の値を変更すれば簡単に別の音を作成することができます。しかし、いかにも電子音といった感じです。
■ピアノ・サンプリング音源ドの音をベースに合成したコード演奏
こちらはネットからダウンロードできるフリーのピアノ音源を用いた演奏です。
Ref.魔王魂 フリー効果音素材
公開されている音源はドの音1つだけなので、これを利用して別の音を作成して演奏させてみました。
HTML5のaudioに対応していないブラウザのためサンプルは表示されません。
ピアノ音源のドの音の波形です。
それでは解説にはいります。
■開発環境
パソコンから、TeraTeamでラズパイにSSH接続、python によるコーディングを行います。
DACとの接続設定は、
I2S通信によるハイレゾ音源再生
を参照してください。
以前はOSに、Python3.5系を実装している Raspbian Stretch を使用していましたが、3.5系はすでにサポート切れとなっています。
Python 3.7, 3.8, 3.9系のソースコードをビルドしてみましたが安定しません。
そこで今回は無駄を省くため、Python3.7系の Raspberry Pi OSを使用します。
OSのインストール・環境設定に関しては、深層学習 第1回環境整備 を参考にしてください。
ここでは手っ取り早く、Raspberry Pi 3 model B に実装したものを、SD Card Copier を使って複製、Raspberry Pi Zero W に挿して、起動後にIPアドレスを書き換えています。
深層学習第1回環境整備において、インストールされている各種パッケージのバージョンは下記の通りです。
$ pip3 list
Package Version
-------------------- -----------
Keras 2.4.3
matplotlib 3.3.2
numpy 1.16.2
pip 18.1
scikit-learn 0.23.2
scipy 1.5.4
tensorflow 1.14.0
$ python3 --version
Python 3.7.3
さらに、pyaudio と pychord をインストールします。
$ sudo pip3 install pyaudio
Successfully installed pyaudio-0.2.11
$ sudo pip3 install pychord
Successfully installed pychord-0.5.1
Pythonのサンプルコードはあとで説明しますが、実行しようとするとエラーが発生します。
$ python3 sample.py
ImportError: libportaudio.so.2: cannot open shared object file: No such file or directory
python-pyaudioの有無を確認します
$ apt list --installed | grep python-pyaudio
存在する場合は削除します
$ sudo apt-get purge --remove python-pyaudio
他のパッケージも確認します
$ apt list --installed | grep portaudio19-dev
$ apt list --installed | grep python-all-dev
python-all-dev/stable,now 2.7.16-1 armhf [インストール済み、自動]
portaudio をインストールします
$ sudo apt install portaudio19-dev
さらに、libatlas-base-dev も追加します
$ python3 sample.py
ImportError: libf77blas.so.3: cannot open shared object file: No such file or directory
$ sudo apt install libatlas-base-dev
■基準となる音の作成
サイン波であれば、言うまでもなく簡単です。1秒間のド(C4)の音は下記の通りです。
import numpy as np
C4 = 261.626
samplingRate = 44100
range = 2 * np.pi * C4
slice = range / samplingRate
radian = np.arange(0, range, slice)
wavC4 = np.sin(radian)
周波数を変更するだけで、新たに別の音が作れます。
次にピアノ・サンプリング音源の場合です。
from scipy.io import wavfile
samplingRate = 44100
rate, data = wavfile.read(filename)
wavC4 = data[0:samplingRate]
ここでは、サンプリング音源ドのファイルを読み込んで、先頭の1秒間を抜き出しています。
本来であれば、波形解析を行って、波を重ね合わせ、類似の波形を作りあげたいところですが、サンプリング音源の波形をフーリエ変換により周波数解析してみると以下のようになります。
黒線グラフからわかるように、ドの音の倍音付近にも小さなピークが存在しています。
赤線のグラフは、横軸のレンジを絞って表示したものです。強度のピークが260Hz付近を指していますが、このフーリエ変換結果からは、ピアノの音色を決定付ける要素を検出することはできません。
この解析には、HK29さんのpython ソースコードを用いています。多少の変更で簡単に動きます。
Ref.Python 高速フーリエ変換(FFT)による周波数解析「SciPy」
ピアノ音源の合成は簡単にはできそうにないので、かなり手抜きの処理を考えました。
ソースコードは試作中なので、かなり雑です。
stack = []
shape = []
breakKey = 0
cnt = 0
rate = 1.122
tmpWav = wavC4.tolist()
for key, amplitude in enumerate(tmpWav):
matchKey = int(key/rate+0.5)
if breakKey != matchKey:
pos = len(stack)//2
#print("pos=",pos)
shape.append(stack[pos])
cnt += 1
breakKey = matchKey
stack = [amplitude]
else:
stack.append(amplitude)
この処理では波形を縮めています。つまり、配列データを間引いています。
レの音の周波数は、ドの音の1.122倍ですので、1/1.122に縮小します。
そうすると、再生時間が1秒未満のデータになってしまうので、この縮めたデータを再活用して、不足分をデータ末尾に追加します。
freqWav = shape
rest = samplingRate - cnt
while rest > 0:
if rest > cnt:
range = cnt
else:
range = rest
freqWav.extend(shape[0:range])
rest -= cnt
wavD4 = np.array(freqWav).astype(np.int16)
この要領で1オクターブ分の音を作成します。さらに1オクターブ上の音も同様に生成可能です。
また、1オクターブ下げる場合は、音を引き延ばすことになります。
ある音の1オクターブ下を作る場合、配列データに1個ずつ隙間ができるので、この隙間を隔てた両側の振幅の平均値で埋め合わせます。
■分散和音を作る
Cメジャーであれば、構成音はド・ミ・ソなので、この3つの音を足し合わせます。numpy配列であれば、簡単に操作できます。
この際、振幅幅が大きくなりますので、足し合わせた構成音の数で振幅を割っています。
broken = wavC4/3
broken += wavE4/3
broken += wavG4/3
Cmajor = broken.astype(np.int16)
■コードを鳴らす
import pyaudio
p = pyaudio.PyAudio()
stream = p.open(format=pyaudio.paInt16,
channels=1,
rate=samplingRate,
output=True,
output_device_index=0)
stream.write(Cmajor)
stream.stop_stream()
stream.close()
p.terminate()
strem.write() 部分をコード進行を読み込んでループさせるように変更することで、演奏させることができます。
■補足
先ほどのフーリエ変換で得られた倍音に現れるピークをその強度とともに足し合わせてみます。
C4 = 261.626
range = 2 * np.pi * C4
slice = range / samplingRate
# Oscillator-1: サイン波
radian = np.arange(0, range, slice)
wav = np.sin(radian)
# Oscillator-2: サイン波
intensity = 0.45 # 強度
overtone = 2.0 # 倍音
wav += np.sin(radian * overtone) * intensity
# Oscillator-3: サイン波
intensity = 0.1 # 強度
overtone = 3.0 # 倍音
wav += np.sin(radian * overtone) * intensity
# Oscillator-4: サイン波
intensity = 0.25 # 強度
overtone = 4.0 # 倍音
wav += np.sin(radian * overtone) * intensity
# Oscillator-5: サイン波
intensity = 0.12 # 強度
overtone = 5.0 # 倍音
wav += np.sin(radian * overtone) * intensity
max = np.max(wav)
wav /= max
wavC4 = (wav * float(2**14)).astype(np.int16)
重ね合わされた波形は下図のようになります。
サンプリング音源とはまだまだかけ離れた波形ですが、1つのサイン波よりは穏やかな音質になっています。
HTML5のaudioに対応していないブラウザのためサンプルは表示されません。
これより先の詳しい内容については、FM音源YMF825+micro:bit編 で若干触れています。
単純なサイン波の合成だけではなく、さまざまな波形
アルゴリズムとオペレータが深く絡んできます。
いつもでしたら、使用した全ソースコードを掲載するのですが、もうちょっと考察が進んでから公開する予定です。
■参考文献
・Python で音楽を作って楽しもう
・yuma-m/pychord
・Pythonでwavファイルを読み込む
・Pythonで音声解析 – 音声データの周波数特性を調べる方法
・Python ねこふんじゃったを演奏する「PyAudio」