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

## やりたいこと
Windows8.1でMIDIファイルからWAVファイルを出力

## 環境
* Windows8.1
* python3.5
* pretty_midi 0.2.8

## 方針
こちらの記事[PythonでMIDIをWAVにする ...](http://shingoushori.hatenablog.jp/entry/2017/08/06/155428)に従います。

1. pythonのpretty_midiパッケージでMIDI読み込み
2. scipyでWAV出力(すぐできたので特に説明しません)

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

[FluidSynthのリポジトリwiki](https://github.com/FluidSynth/fluidsynth/wiki/BuildingWithCMake)より、FluidSynthをwindowsに入れるには

a. MSYS2からビルド
b. Cygwinからビルド
c. MinGW+コマンドプロンプトからビルド
d. Visual Studioでビルド

があることがわかります。
私の試した範囲では、bとcはビルドに失敗、
aはFluidSynthのビルドはできましたが、pretty_midiをインストールできず中断しました。
結局dで動かすことができたので記載します。

#### FluidSynthのビルド
上記[github](https://github.com/FluidSynth/fluidsynth/wiki/BuildingWithCMake#building-with-visual-studio-on-windows)に従うだけです。
ソースコードを[githubのリリース](https://github.com/FluidSynth/fluidsynth/releases)からダウンロードして解凍します。この記事ではfluidsynth 2.0.1を使用します。
すでにVisual Studio Express Community 2017が入っているものとして、パッケージマネージャのvcpkgをインストールします。
インストール方法は[vcpkgを使ってみた](https://qiita.com/jugemjugemu/items/fcb72bdca13dc06b0f37)に従いました。
なお、Visual Studioに言語パック(英語)が入っていないとビルドに失敗します。私は[おやぢチップス (65) : Visual Studio 2017 言語パックのインストール方法](https://blog.goo.ne.jp/ashm314/e/e4d4e8ab26f6126e825352c50ee706d1)に従ってインストールしました。

下記をコマンドプロンプトに打って、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をインストール
[公式リポジトリ](https://github.com/nwhitehead/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**と同じ場所に置きました。

```python:__init__.py
from .fluidsynth import *
__version__ = '1.2.5'
```

これでファイルの配置は完了です。
ここから**fluidsynth.py**のソースを編集していきます。

```python:fluidsynth.py
lib = find_library('fluidsynth') or \
    find_library('libfluidsynth') or \
    find_library('libfluidsynth-2') #37行目くらいにあるlibfluidsynth-1をlibfluidsynth-2に
```

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

~~179行目から237行目までコメントアウト（私の場合）。~~
2018/11/12 訂正。179~201, 220~226, 229~233をコメントアウト。
よく確認するとエラーが出ない関数も混じっていました。公式でも[issue](https://github.com/nwhitehead/pyfluidsynth/issues/12)が上がっていて対応中の模様


DLL内の関数をpythonから呼び出すための関数を作成している箇所だと思いますが、
FluidSynthのビルド設定によって有効な関数が異なると思います。
55行目の関数定義にprint()を入れてどこまでならエラーを出さずに通るか調べました。

```python:fluidsynth.py
    print(((name, _fl), tuple(aflags)))#55行目。これを追加して確認した
    return CFUNCTYPE(result, *atypes)((name, _fl), tuple(aflags))
```


```python: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))
'''
```
```python: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))

'''
```

```python: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を出力するスクリプトは下記となります

```python: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つしかないデータです）。


## 参考

* [PythonでMIDIをWAVにする ...](http://shingoushori.hatenablog.jp/entry/2017/08/06/155428)
* [Building FluidSynth with CMake](https://github.com/FluidSynth/fluidsynth/wiki/BuildingWithCMake)
* [pyFluidSynth](https://github.com/nwhitehead/pyfluidsynth)
* [pretty-midi](https://github.com/craffel/pretty-midi)
* [vcpkgを使ってみた](https://qiita.com/jugemjugemu/items/fcb72bdca13dc06b0f37)
* [
おやぢチップス (65) : Visual Studio 2017 言語パックのインストール方法](https://blog.goo.ne.jp/ashm314/e/e4d4e8ab26f6126e825352c50ee706d1)
* [Pythonでwavを作成する](https://yukota.hatenablog.com/entry/2018/07/16/235020)
* [python synthesize midi with fluidsynth](https://stackoverflow.com/questions/5783913/python-synthesize-midi-with-fluidsynth)
* [インタラクティブミュージック作曲家にFluidSynthをざっくり紹介します](https://qiita.com/YosukeM/items/64df6b97acea3adb7f5b)