やりたいこと
Windows8.1でMIDIファイルからWAVファイルを出力
環境
- Windows8.1
- python3.5
- pretty_midi 0.2.8
方針
こちらの記事PythonでMIDIをWAVにする ...に従います。
- pythonのpretty_midiパッケージでMIDI読み込み
- 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と同じ場所に置きました。
from .fluidsynth import *
__version__ = '1.2.5'
これでファイルの配置は完了です。
ここからfluidsynth.pyのソースを編集していきます。
lib = find_library('fluidsynth') or \
find_library('libfluidsynth') or \
find_library('libfluidsynth-2') #37行目くらいにあるlibfluidsynth-1をlibfluidsynth-2に
_fl = WinDLL(lib) # 45行目 _fl = CDLL(lib) から変更。そのままでも大丈夫かも
179行目から237行目までコメントアウト(私の場合)。
2018/11/12 訂正。179~201, 220~226, 229~233をコメントアウト。
よく確認するとエラーが出ない関数も混じっていました。公式でもissueが上がっていて対応中の模様
DLL内の関数をpythonから呼び出すための関数を作成している箇所だと思いますが、
FluidSynthのビルド設定によって有効な関数が異なると思います。
55行目の関数定義にprint()を入れてどこまでならエラーを出さずに通るか調べました。
print(((name, _fl), tuple(aflags)))#55行目。これを追加して確認した
return CFUNCTYPE(result, *atypes)((name, _fl), tuple(aflags))
# 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))
'''
# 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))
'''
# 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を出力するスクリプトは下記となります
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つしかないデータです)。