はじめに
pythonでmidiを扱う際に,(秒数ではなく)テンポを考慮し,16分音符単位で区切ったピアノロール(音高と時間で区切って,どの音がどの時間に鳴っているのかというデータ)を取得する方法です.
前にpretty_midiのget_piano_rollを使ってみる記事を書きました.
このget_piano_rollは時間(秒数)に基づいたピアノロールの取得をサポートしていました.
しかし,私は秒数の指定ではなく16分音符単位で区切ったものが欲しかったので,いろいろ組み合わせてやってみました.
ピアノロールとは?
ここで言うピアノロールは,
...
0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 1
0 0 0 0 0 0 1 0
1 1 1 0 0 1 0 0
0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0
0 0 0 1 1 0 0 1
0 0 0 0 0 0 0 0
...
みたいな感じです!
縦は音高,横は時間ですね.
ちなみに今は,1の部分が音が鳴っている,0の部分が音の鳴ってない部分となっています.
この後紹介する方法だと,1ではなく,音量を表現することもできます.
コード概観
import numpy as np
import pretty_midi
def get_tatum_pianoroll(midi_file):
"""midiファイルの読み込み"""
p_midi = pretty_midi.PrettyMIDI(midi_file)
"""テンポを取得"""
change_time, tempo = p_midi.get_tempo_changes()
"""曲終わりの秒数を最後に追加"""
change_time = np.append(change_time, p_midi.get_end_time())
"""サンプリング周波数の計算"""
fs = tempo * 4 / 60
"""区切る秒数の計算"""
times = [
np.arange(change_time[i], change_time[i+1], 1./fs[i])
for i in range(len(fs))
]
"""ピアノロールの計算"""
piano_roll = [
p_midi.get_piano_roll(times=times[i], fs=fs[i])
for i in range(len(fs))
]
"""音量なしの2値に変換"""
piano_roll = [
(i > 0).astype(np.bool)
for i in piano_roll
]
"""繋げる"""
piano_roll = np.concatenate(piano_roll, 1)
説明
まず,初めにpretty_midiでmidiファイルを読み込みます.
次にget_tempo_changes()でテンポの変化を取得します.
ここで,change_timeは,テンポが変わる秒数(曲はじめの0秒を含む)を表しています.
つまり,テンポがずっと変わらない曲であれば,初めの0秒のみ(array([0.])
)が返ってきます.
tempoの方はテンポが変わるタイミングでの実際のテンポを表しています.
曲終わりの秒数を追加というところは,この後の区切る秒数の計算のところで秒数の区間が必要になってくるので,一番最後を表すために追加しています.
次にサンプリング周波数の計算です.
pretty_midiのget_piano_rollを使ってみるの記事の方に詳しく書いています.
曲のテンポと,秒数をつなぐ大事な役どころです.
tempo * 4
の4
は16分音符4つ分で4分音符を表すというところから,4になっています.
もし8分音符単位にしたければ,tempo * 2
にすることでできます.
区切る秒数の計算は,
pretty_midiのget_piano_rollを使ってみるの記事の方に詳しく書いています.
ざっくり言うと,ピアノロールの横軸の各データが,曲の何秒のタイミングなのかを表現しています.
ピアノロールの計算も
pretty_midiのget_piano_rollを使ってみるの記事の方に詳しく書いています.
この操作が終わった段階では,数値の大きさが音量を表しています.
さっきの例で言うと,
...
0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 90
0 0 0 0 0 0 85 0
80 80 80 0 0 80 0 0
0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0
0 0 0 80 80 0 0 90
0 0 0 0 0 0 0 0
...
みたいな感じになります.(音量の数値は適当です.)
音量なしの2値に変換の部分は,音量情報を無くして,音の有無の情報だけを2値で表します.
コード内ではbooleanで表現しているので音があればTrue,なければFalseとなっていますが,
イメージ的には最初に示したピアノロールのようになるわけです.
最後は途中でテンポの変わる曲だった場合に,concatをしてつなげています.