Midoを使ってMIDIトラック内の音数を取得する
PythonのライブラリMidoを使って
MIDIトラック内の音数を取得するのが今回の目的です。
(音数が取得できると、歌詞を考えるツールとか作るのに役立ちそうかなと)
任意のトラック内に含まれる音数を取得
忙しい人向け
「理屈は良いから、音数を取得するコードが欲しい」という人向けに
今回作成した関数を載せます。(Midoはきちんとインポートしてね)
※動作保証とかはしないので、自己責任で使ってください。
def count_note(midi_file, track_num=0): """ 指定トラックの音数を取得する。\n なお、指定トラックが存在しない場合はNoneを返す。\n Args: midi_file : MIDIファイル track_num : トラック番号(デフォルト値 0) Returns: count : 音数 """ # MIDIの読み込み midi = mido.MidiFile(midi_file) # カウンターの初期化 count = 0 # 引数の確認 if type(track_num) is int and track_num >= 0 and track_num < len(midi.tracks): # 指定トラック内の音を数える for msg in midi.tracks[track_num]: if msg.type == "note_on" and msg.dict()["velocity"] != 0: count += 1 else: return None return count
以下の様に使うことができます。
# MIDIファイル midi_file = "MIDI_test.mid" # トラック番号 track_No = 1 # 音数の取得 notes = count_note(midi_file, track_No) print(notes)
処理内容を知りたい人向け
処理内容を理解してから使いたい人向けに
count_note関数の処理内容を説明します。
MIDI内での音の定義
MIDIファイルは"Messageの集合体"のようなもので、
楽曲情報はMessageとして管理されています。
MIDIファイル内で「音を発する」というのは以下のようなMessageになります。
Message('note_on', channel=0, note=74, velocity=102, time=0)
Messageの意味は以下(らしい)です。
- 'note_on' => このMessageは「音を発する」指令ですよ、という宣言
- channel => どのチャンネルで鳴らすかを指定(0~15のチャンネルに音色を登録できるらしい)
- note => 音の高さ(国際表記の場合、note=60 => C4)
- velocity => 音の強弱(DAWでもvelocityって言うよね)
- time => 直前のMessageから何tick後に鳴らし始めるか
正直、自分は理解しきってないのですが(音数を数えるだけなら不要だし)
以下の記事で詳しく解説されています。
まぁ、ここでは「'note_on'というMessageがあったら、音を発しているんだな」ってことが
分かっていれば、大体OKです。
音数を数える
ここで勘の良い人は、「じゃあ、音数を知りたい時は'note_on'のMessageを数えれば良いんだな」と気づくと思います。
実際その通りなのですが、一点だけ注意すべきことがあります。
それは、消音(ミュート)を'note_on'のvelocity=0としている場合があることです。
音を発する Message('note_on', channel=0, note=74, velocity=102, time=0) 消音(普通は'note_off'で宣言する) Message('note_off', channel=0, note=74, velocity=102, time=480) まさかの消音 Message('note_on', channel=0, note=74, velocity=0, time=0)
そのため、音数を数える場合はvelocityが0でない'note_on'を数える必要があります。
では、velocity=0の'note_on'に注意しつつ、トラック番号0の音数を
Pythonで取得してみます。
# カウンターの初期化 count = 0 # トラック番号0内のMessage毎にループ for msg in midi.tracks[0]: # 'note_on'でかつvelocity=0でないMessageを判定 if msg.type == "note_on" and msg.dict()["velocity"] != 0: # 音を発するMessageの場合、カウンターを+1する count += 1
上記でしれっと使っているけど、Messageに.typeを付けるとメッセージのタイプ、
.dict()をつけると、Messageを辞書型に変換したものが取得できますよ。
トラックが存在しない場合の対処
上記までで、「音数を取得する」機能は完成なんですが、
関数化するにあたって、「存在しないトラック番号を指定された場合」の対応を入れておきます。
# track_numが、INT型で、かつ0 <= track_num < MIDIファイル内のトラック数となっているか判定 if type(track_num) is int and track_num >= 0 and track_num < len(midi.tracks): # 指定トラック番号が問題ない場合の処理 else: # 指定トラック番号が不適な場合の処理
ということで、これまでの処理を合わせると以下のようになります。
(忙しい人向けで書いたやつと一緒です)
def count_note(midi_file, track_num=0): """ 指定トラックの音数を取得する。\n なお、指定トラックが存在しない場合はNoneを返す。\n Args: midi_file : MIDIファイル track_num : トラック番号(デフォルト値 0) Returns: count : 音数 """ # MIDIの読み込み midi = mido.MidiFile(midi_file) # カウンターの初期化 count = 0 # 引数の確認 if type(track_num) is int and track_num >= 0 and track_num < len(midi.tracks): # 指定トラック内の音を数える for msg in midi.tracks[track_num]: if msg.type == "note_on" and msg.dict()["velocity"] != 0: count += 1 else: return None return count
重ねて書いておきますが、上記関数はご自由にお使い頂いて問題ないですが、
動作保証はしないので、自己責任でお願いします。
今回はこの辺で。