DAW悪戦苦闘記

DAWやMIDIを通じてDTMを楽しむ記録。MIDI検定試験にもチャレンジするなり。

PythonでSMFを操作する (1) 下準備と読み込み

今回より、少々マニアックにはなるが、PythonによるSMFの加工編集処理について何度かに分けて(五月雨式に)書いていく。なんでそんなことするの?、という根拠については以下に記す。なお最初に断っておくが、以下の話はあくまでMIDI検定実技対策上の要請であり、自主制作では一切必要ない。

今回は、使用するパッケージの紹介と、それを使ったSMFの中身の確認までをやってみる。

Pythonで自動処理する目的

Studio One から書き出したSMFは、そのままではSysExメッセージとセットアップ・データが書かれていないので*1、DominoなどのMIDI専用エディター兼シーケンサーなどを使って追加編集する必要がある。もちろん、ここまで機能対応しているDAWであれば、わざわざDominoに取り込まずとも自己完結することは言うまでもない。

問題は、1級の場合、トラック(楽器パート)の数が多いので、Domino上での手動によるコピーや編集が非常に面倒くさい、ということである。また繰り返し作業が多いということは、ミスを誘発する可能性もそれだけ高まる懸念がある。

なので、セットアップ・データの追加部分については、Pythonによる定型バッチ処理で対応して効率化を図りたい。そうすると、Dominoでの追加編集はボリュームやパンの微調整ぐらいで済む。

使用するパッケージ

PythonMIDIデータを読み書きするためのパッケージとしては、今現在は 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  上での実行結果):

f:id:daw_jones:20170704144229p:plain

ほとんど自明で逐一説明を要しないと思うが、上図はメタイベント・トラックと、オーボエの楽器パートを割り当てているトラック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)

これを実行すると、以下のような感じに表示される(メタイベントは上とほぼ同じなので省略):

f:id:daw_jones:20170704153536p:plain

上の Studio One から吐き出したSMFと見比べてみると色々と興味深い。上図で示したように、2級実技用のテンプレでお馴染みのセットアップ・データが付加されている。また、トラック1については冒頭にSysExメッセージも付く(データは10進表記)。目標としては、これら定型の付加データを全トラックについてPythonで追加処理したい、ということである。

ヘッダー情報はどこに

以上はすべてトラック・チャンクに関わるデータであるが、ヘッダー・チャンクの主要データを確認したい場合は、SMFを取り込んだオブジェクトの属性一覧を見ればよい。

# SMFオブジェクトの属性には何が含まれるか確認する
mid.__dict__

すると、以下のように辞書形式で属性一覧が表示される:

f:id:daw_jones:20170704162943p:plain

上図で明らかなように、Studio One から書き出したSMFの分解能は TPQN = 480 であることがわかる。

次は何をやるか

初回なので思ってた以上に長くなってしまったが、次回からはワンポイント・メモのような形でもう少し手短にまとめていきたい。

次はまず一番簡単なところから着手するとして、SysExメッセージの追記をやってみるとする。

*1:私が知らないだけであって、セットアップ・データ等を付加する方法はあるのかもしれない。しかし、たとえ書けたとしても、Studio One はMIDIのイベントリスト表示機能がないため、検証チェックと確認が難しいと思う。

*2:詳しくは、メタイベント一覧表などを参照。

*3:変拍子については、次の過去記事参照: 譜面解釈とMIDI表現 (2) 変拍子の対応 (Studio One)