Edited at

(Windows) pythonでMIDIからWAVを出力


やりたいこと

Windows8.1でMIDIファイルからWAVファイルを出力


環境


  • Windows8.1

  • python3.5

  • pretty_midi 0.2.8


方針

こちらの記事PythonでMIDIをWAVにする ...に従います。


  1. pythonのpretty_midiパッケージでMIDI読み込み

  2. scipyでWAV出力(すぐできたので特に説明しません)


pretty_midiパッケージの依存関係をインストールする

上記記事のとおりですが、pretty_midiでの波形出力(numpyのndarray形式)は、ソフトウェアシンセサイザのFluidSynthおよびそれを呼び出すためのpythonパッケージpyFluidSynthに依存しています。これらに依存しない関数(synthesize())もありますが、サウンドフォントが適用されない模様です(期待した音にならない)。

FluidSynthのリポジトリwikiより、FluidSynthをwindowsに入れるには

a. MSYS2からビルド

b. Cygwinからビルド

c. MinGW+コマンドプロンプトからビルド

d. Visual Studioでビルド

があることがわかります。

私の試した範囲では、bとcはビルドに失敗、

aはFluidSynthのビルドはできましたが、pretty_midiをインストールできず中断しました。

結局dで動かすことができたので記載します。


FluidSynthのビルド

上記githubに従うだけです。

ソースコードをgithubのリリースからダウンロードして解凍します。この記事ではfluidsynth 2.0.1を使用します。

すでにVisual Studio Express Community 2017が入っているものとして、パッケージマネージャのvcpkgをインストールします。

インストール方法はvcpkgを使ってみたに従いました。

なお、Visual Studioに言語パック(英語)が入っていないとビルドに失敗します。私はおやぢチップス (65) : Visual Studio 2017 言語パックのインストール方法に従ってインストールしました。

下記をコマンドプロンプトに打って、FluidSynthが依存するglibをインストールします

vcpkg install glib

ビルドします。CMakeのGUI版(3.9.0)を使いました。


  • 入力:ダウンロードしたFluidSynthのディレクトリを指定

  • 出力:上記ディレクトリ内にbuildディレクトリを作成し、それを指定


  • Configureボタンを押してGeneratorはVisual Studio 15 2017 Win64を選択(私はpythonが64bit版なのでWin64を選びました。一致していないと後でpythonから読み込めません。)


  • Generateボタンを押す

出力に指定したディレクトリにFluidSynth.slnというファイルができるのでVisual Studioで開きます。

構成をreleaseにして(debugのままでもいいのかもしれません)、ビルドメニューからソリューションのビルド(B)をクリックし、ビルド終了を待ちます。

なお、FluidSynth 1.1.10にはvcpkgにパッケージがあるそうです。

vcpkg install fluidsynth

ビルド終了後、build/src/Releaseに目的のdllファイルlibfluidsynth-2.dllが生成されています。これをパスの通っている場所に置くか、後述のようにpyFluidSynthのパッケージ内に置きます。


pyFluidSynthをインストール

公式リポジトリから落としました。pipでも入りますがバージョンが古いという情報があったためです。

*先ほど確認したところpipでも同じバージョン(pyfluidsynth-1.2.5)だったのでそちらでも大丈夫そうです。

git clone https://github.com/nwhitehead/pyfluidsynth

cd pyfluidsynth
python setup.py install

pythonのsite-packages(私の環境ではユーザディレクトリ\AppData\Local\Programs\Python\Python35\Lib\site-packagesに)内にpyfluidsynth用ディレクトリが2つできることを想定したのですが、

pyFluidSynth-1.2.5-py3.5.eggというファイルが1つできました。

これを解凍すると

fluidsynth.pyファイルと

EGG-INFOディレクトリができます。

EGG-INFOはリネームしてpyFluidSynth-1.2.5.dist-infoに、

また、fluidsynthという新たなディレクトリを作成してその中にfluidsynth.pyと先ほどビルドしたdllファイルlibfluidsynth-2.dllを入れました。

さらに__init__.pyを作成してfluidsynth.pyと同じ場所に置きました。


__init__.py

from .fluidsynth import *

__version__ = '1.2.5'

これでファイルの配置は完了です。

ここからfluidsynth.pyのソースを編集していきます。


fluidsynth.py

lib = find_library('fluidsynth') or \

find_library('libfluidsynth') or \
find_library('libfluidsynth-2') #37行目くらいにあるlibfluidsynth-1をlibfluidsynth-2に


fluidsynth.py

_fl = WinDLL(lib) # 45行目 _fl = CDLL(lib) から変更。そのままでも大丈夫かも


179行目から237行目までコメントアウト(私の場合)。

2018/11/12 訂正。179~201, 220~226, 229~233をコメントアウト。

よく確認するとエラーが出ない関数も混じっていました。公式でもissueが上がっていて対応中の模様

DLL内の関数をpythonから呼び出すための関数を作成している箇所だと思いますが、

FluidSynthのビルド設定によって有効な関数が異なると思います。

55行目の関数定義にprint()を入れてどこまでならエラーを出さずに通るか調べました。


fluidsynth.py

    print(((name, _fl), tuple(aflags)))#55行目。これを追加して確認した

return CFUNCTYPE(result, *atypes)((name, _fl), tuple(aflags))


fluidsynth.py


#179行目から201行目までコメントアウト
'''
fluid_synth_get_channel_info = cfunc('fluid_synth_get_channel_info', c_int,
('synth', c_void_p, 1),
('chan', c_int, 1),
('info', POINTER(fluid_synth_channel_info_t), 1))

fluid_synth_set_reverb_full = cfunc('fluid_synth_set_reverb_full', c_int,
('synth', c_void_p, 1),
('set', c_int, 1),
('roomsize', c_double, 1),
('damping', c_double, 1),
('width', c_double, 1),
('level', c_double, 1))

fluid_synth_set_chorus_full = cfunc('fluid_synth_set_chorus_full', c_int,
('synth', c_void_p, 1),
('set', c_int, 1),
('nr', c_int, 1),
('level', c_double, 1),
('speed', c_double, 1),
('depth_ms', c_double, 1),
('type', c_int, 1))
'''



fluidsynth.py

#220~226コメントアウト

'''
fluid_synth_get_chorus_speed_Hz = cfunc('fluid_synth_get_chorus_speed_Hz', c_double,
('synth', c_void_p, 1))

fluid_synth_get_chorus_depth_ms = cfunc('fluid_synth_get_chorus_depth_ms', c_double,
('synth', c_void_p, 1))

'''



fluidsynth.py

#229~233コメントアウト

'''
fluid_synth_set_midi_router = cfunc('fluid_synth_set_midi_router', None,
('synth', c_void_p, 1),
('router', c_void_p, 1))
'''


これで編集完了です。


pythonで動作確認

MIDIからWAVを出力するスクリプトは下記となります


midi2wav.py


import pretty_midi
import argparse
import scipy
from scipy import io
from scipy.io import wavfile
import numpy as np

parser=argparse.ArgumentParser()
parser.add_argument("input")
parser.add_argument("output")
args=parser.parse_args()

in_file=args.input
out_file=args.output
midi=pretty_midi.PrettyMIDI(in_file)

sig=midi.fluidsynth()

m = np.max(np.abs(sig))
sigf32 = (sig/m).astype(np.float32)
scipy.io.wavfile.write(out_file, 44100, sigf32)


実行します

python midi2wav.py midiファイル.mid output.wav

問題がなければoutput.wavが出力され、midiファイルに定義された音色で音が鳴るはずです(ちなみに検証に使ったmidiファイルはトラックが1つしかないデータです)。


参考