PythonでSMFを操作する (1) 下準備と読み込み
今回より、少々マニアックにはなるが、PythonによるSMFの加工編集処理について何度かに分けて(五月雨式に)書いていく。なんでそんなことするの?、という根拠については以下に記す。なお最初に断っておくが、以下の話はあくまでMIDI検定実技対策上の要請であり、自主制作では一切必要ない。
今回は、使用するパッケージの紹介と、それを使ったSMFの中身の確認までをやってみる。
Pythonで自動処理する目的
Studio One から書き出したSMFは、そのままではSysExメッセージとセットアップ・データが書かれていないので*1、DominoなどのMIDI専用エディター兼シーケンサーなどを使って追加編集する必要がある。もちろん、ここまで機能対応しているDAWであれば、わざわざDominoに取り込まずとも自己完結することは言うまでもない。
問題は、1級の場合、トラック(楽器パート)の数が多いので、Domino上での手動によるコピーや編集が非常に面倒くさい、ということである。また繰り返し作業が多いということは、ミスを誘発する可能性もそれだけ高まる懸念がある。
なので、セットアップ・データの追加部分については、Pythonによる定型バッチ処理で対応して効率化を図りたい。そうすると、Dominoでの追加編集はボリュームやパンの微調整ぐらいで済む。
使用するパッケージ
PythonでMIDIデータを読み書きするためのパッケージとしては、今現在は pretty_midi が主流であり、また一番使いやすいAPIだと思われる(3系対応)。
実はpretty_midiのベースはmidoパッケージらしく、SysExメッセージの追加挿入といったpretty_midiでは扱えない一部特殊な処理に関してはmidoを直接使わないと対応できない。
実際に試してみるとわかるが、midoパッケージ自体シンプルなデータ構造とAPIなので、MIDI検定3級以上の知識があれば難なく使いこなせる。なので、ここではすべてmidoパッケージを直接使ってみることにする。
さしあたりは、以下の2つのクラスしか使わないため、midoパッケージよりインポートしておく。
from mido import MidiFile, Message
SMFの中身を覗いてみる
試しにサンプルとして2級2次試験の2017年2月期練習曲No.1で制作したSMFを俎上に乗せる。
Studio One から書き出した直後のSMF
何度か繰り返し使うことになるので、各トラック内の全メッセージ内訳表示と、SMF全体の中身の表示させる関数を定義する。
# 各トラック毎の全メッセージを表示する def dump_track(track_obj): for msg in track_obj: print(msg) # 全トラックの全メッセージをトラック毎に表示する def dump_smf(midi_obj): for i, track in enumerate(midi_obj.tracks): print(f"Track {i}: {track.name}") dump_track(track)
トラック・オブジェクトは、実態としてはMIDIメッセージのリスト構造になっているので、Pythonの通常のリスト操作をそのまま使うことで簡単に編集加工できる。
上記の定義関数を呼び出して、まずは Studio One から書き出したSMFの中身を見てみるとする。
# Studio One から書き出したSMFを mid に読み込む mid = MidiFile('smf_studio1.mid') dump_smf(mid)
これを実行すると、以下のような感じに表示される(jupyter notebook 上での実行結果):
ほとんど自明で逐一説明を要しないと思うが、上図はメタイベント・トラックと、オーボエの楽器パートを割り当てているトラック1の冒頭9行までを表示した。注目すべき点としては、
- メタイベント*2のテンポ値は1拍の長さ(本例では4分音符一つ)をμs(マイクロ秒)単位で表示している。BPMに変換すると、60s/(638298μs/1000000) = 94 となり、BPM = 94 であることがわかる(実際にはこれとは逆方向に変換している)。
- メタイベントには拍子データ(Time Signature)も書かれている。本例では4/4である。曲中で何度か拍子を変えると、変化するタイミング(Time値)とともにそのデータもすべてここに書かれる*3。
- メタイベント以外の各トラック冒頭2行目には Studio One 固有のメタメッセージ1件が書かれているが、これは特に必要というわけではなく、Dominoに取り込む前に消去してしまっても構わない。
- トラックの冒頭部分には、SysExメッセージやコントロール・チェンジといったセットアップ・データがまだ何も付け足されていないことがわかる。
- ノートオン/オフの各メッセージ末尾に書かれているTime値はいわゆるデルタタイムであり、直前メッセージからの送信間隔を示す。実はここではティック単位になっている(ので確認しやすい)。MIDIメッセージが小節の概念を持っていないことがわかると思う。
Dominoで編集後に書き出したSMF
同様に、Dominoで編集した最終成果物としてのSMFの中身を確認してみる。
# Dominoから書き出したSMFを mid2 に読み込む mid2 = MidiFile('etude_2017-1.mid') dump_smf(mid2)
これを実行すると、以下のような感じに表示される(メタイベントは上とほぼ同じなので省略):
上の Studio One から吐き出したSMFと見比べてみると色々と興味深い。上図で示したように、2級実技用のテンプレでお馴染みのセットアップ・データが付加されている。また、トラック1については冒頭にSysExメッセージも付く(データは10進表記)。目標としては、これら定型の付加データを全トラックについてPythonで追加処理したい、ということである。
ヘッダー情報はどこに
以上はすべてトラック・チャンクに関わるデータであるが、ヘッダー・チャンクの主要データを確認したい場合は、SMFを取り込んだオブジェクトの属性一覧を見ればよい。
# SMFオブジェクトの属性には何が含まれるか確認する
mid.__dict__
すると、以下のように辞書形式で属性一覧が表示される:
上図で明らかなように、Studio One から書き出したSMFの分解能は TPQN = 480 であることがわかる。
次は何をやるか
初回なので思ってた以上に長くなってしまったが、次回からはワンポイント・メモのような形でもう少し手短にまとめていきたい。
次はまず一番簡単なところから着手するとして、SysExメッセージの追記をやってみるとする。