LoginSignup
5
4

More than 5 years have passed since last update.

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

Last updated at Posted at 2018-11-11

やりたいこと

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つしかないデータです)。

参考

5
4
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
5
4