Piano Roll Animation made by Matplotlib Artist blit.
You can read your MIDI file (hopefully any file), play and draw Piano Roll over time (notes shown when played over vertical piano keys).
Remark) Tempo change is not well shown now. I'll update to handle tempo change later.
You can see the example in my Youtube https://www.youtube.com/watch?v=yZ8H1D5n4KI.
Matplotlib was very slow before. But now, with Artist blit, it is barely fast enough to draw Piano Roll animations. But still need some sleep time adjustments.
---------------------------------------------------------------------------------------------
# blog 4 program
import mido
import time
from mido import Message, MidiFile, MidiTrack, MetaMessage
from mido_util import *
import matplotlib.pyplot as plt
# open MIDI port to play music
ports = mido.get_output_names()
port = mido.open_output(ports[0])
# import MIDI file
score_name = 'blog03'
score = MidiFile(score_name+'.mid', clip=True)
# MIDI play and show Piano Roll
def play_plot_MIDI(port, score):
#########################################################################
# lists for plot
pitch_list = []
reverse_pitch_list = [] # for note duration
on_time_list = []
off_time_list = []
draw_timing = []
play_time = 0
# note_on time and note_off time
score_tempo = 100 # initial tempo BPM
for msg in score:
if msg.type == "set_tempo":
score_tempo = msg.tempo/10000 # no tempo change assumed
for msg in score:
if not msg.is_meta:
play_time += msg.time / score_tempo * 100
if msg.type == "note_on":
pitch_list.append(msg.channel*1000+msg.note)
reverse_pitch_list = [msg.channel*1000+msg.note]+reverse_pitch_list
# note_on time
on_time_list.append(play_time)
off_time_list.append(0) # dummy
if msg.type == "note_off":
# find latest note_on
on_idx = len(pitch_list) - reverse_pitch_list.index(msg.channel*1000+msg.note) -1
# note_off time
off_time_list[on_idx] = play_time
# draw timing to show same timing notes together
for i in range(len(on_time_list)-1):
draw_timing.append(on_time_list[i] < on_time_list[i+1])
draw_timing.append(1)
#########################################################################
# chart
fig = plt.figure(figsize=(10,6))
ax = fig.add_subplot(111)
# piano roll background
x = [0,off_time_list[-1]]
color = "#bbbbbb" # grey
for i in range(50,88):
y = [i,i]
if i % 12 in [1,3,6,8,10]: # piano black keys
ax.plot(x,y,color,linewidth = 12, alpha=0.2)
else:
ax.plot(x,y,"w",linewidth = 12, alpha=1)
for i in [52.5, 59.5, 64.5, 71.5, 76.5, 83.5]: # line between piano white keys
ax.plot(x,[i,i],color = "#bbbbbb", linewidth = 1)
fig.canvas.draw()
fig.show()
fig.canvas.blit(ax.bbox)
fig.canvas.flush_events()
#########################################################################
# play and draw MIDI notes
i = 0
time1 = time.time() # to adjutst sleep time because matplotlib takes time
for msg in score:
if not msg.is_meta:
# sleep msg.time with adustment
if msg.time > 0:
time.sleep(max(0,msg.time - (time.time()-time1)))
time1 = time.time()
# send MIDI msg
port.send(msg)
# draw note on chart
if msg.type == "note_on":
if time1 == 0:
time1 = time.time()
# chart limited
if msg.channel < 5 and msg.note > 50:
x = [on_time_list[i], off_time_list[i]]
y = [msg.note, pitch_list[i]%1000]
color = ["r", "g", "b", "c", "m", "y", "k", "w"][msg.channel]
line, = ax.plot(x,y,color = color, linewidth = 4, alpha=0.5)
ax.draw_artist(line)
# draw bars
if draw_timing[i] == 1:
fig.canvas.blit(ax.bbox)
fig.canvas.flush_events()
i += 1
plt.show()
play_plot_MIDI(port, score)
Comments
Post a Comment