You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
102 lines
2.9 KiB
102 lines
2.9 KiB
import mido
|
|
from mido import MidiFile, MidiTrack, MetaMessage
|
|
|
|
|
|
class MIDIMessage:
|
|
def __init__(self, message, track, abs_tick, abs_time):
|
|
self.message = message
|
|
self.track = track
|
|
self.abs_tick = abs_tick
|
|
self.abs_time = abs_time
|
|
self.new_tick = None
|
|
|
|
|
|
def compute_absolute_times(mid):
|
|
ticks_per_beat = mid.ticks_per_beat
|
|
DEFAULT_TEMPO = 500000
|
|
|
|
all_msgs = []
|
|
for track_index, track in enumerate(mid.tracks):
|
|
abs_tick = 0
|
|
for msg in track:
|
|
abs_tick += msg.time
|
|
all_msgs.append(MIDIMessage(msg, track_index, abs_tick, 0.0))
|
|
|
|
all_msgs.sort(key=lambda m: m.abs_tick)
|
|
|
|
current_tempo = DEFAULT_TEMPO
|
|
abs_time = 0.0
|
|
prev_tick = 0
|
|
|
|
for msg in all_msgs:
|
|
delta_ticks = msg.abs_tick - prev_tick
|
|
delta_time = mido.tick2second(delta_ticks, ticks_per_beat, current_tempo)
|
|
abs_time += delta_time
|
|
msg.abs_time = abs_time
|
|
|
|
if msg.message.type == 'set_tempo':
|
|
current_tempo = msg.message.tempo
|
|
prev_tick = msg.abs_tick
|
|
|
|
return all_msgs
|
|
|
|
|
|
def bake_tempo(all_msgs, ticks_per_beat, constant_tempo):
|
|
for msg in all_msgs:
|
|
seconds_per_tick = constant_tempo / ticks_per_beat / 1e6
|
|
msg.new_tick = int(round(msg.abs_time / seconds_per_tick))
|
|
return all_msgs
|
|
|
|
|
|
def assign_ticks_to_tracks(all_msgs, mid, ticks_per_beat):
|
|
new_tracks = [[] for _ in mid.tracks]
|
|
|
|
for msg in all_msgs:
|
|
if msg.message.type == 'set_tempo':
|
|
continue
|
|
new_tracks[msg.track].append(msg)
|
|
|
|
for track_index, track_msgs in enumerate(new_tracks):
|
|
track_msgs.sort(key=lambda m: m.new_tick)
|
|
|
|
prev_tick = 0
|
|
new_track = []
|
|
for msg in track_msgs:
|
|
delta_tick = msg.new_tick - prev_tick
|
|
prev_tick = msg.new_tick
|
|
new_msg = msg.message.copy(time=delta_tick)
|
|
new_track.append(new_msg)
|
|
|
|
if not new_track or new_track[-1].type != 'end_of_track':
|
|
new_track.append(MetaMessage('end_of_track', time=0))
|
|
|
|
new_tracks[track_index] = new_track
|
|
|
|
return new_tracks
|
|
|
|
|
|
def get_initial_tempo(all_msgs, default_tempo=500000):
|
|
for msg in all_msgs:
|
|
if msg.message.type == 'set_tempo':
|
|
return msg.message.tempo
|
|
return default_tempo
|
|
|
|
|
|
def process(mid: MidiFile) -> MidiFile:
|
|
ticks_per_beat = mid.ticks_per_beat
|
|
|
|
all_msgs = compute_absolute_times(mid)
|
|
initial_tempo = get_initial_tempo(all_msgs)
|
|
all_msgs = bake_tempo(all_msgs, ticks_per_beat, initial_tempo)
|
|
new_tracks = assign_ticks_to_tracks(all_msgs, mid, ticks_per_beat)
|
|
|
|
new_mid = MidiFile(ticks_per_beat=ticks_per_beat)
|
|
|
|
tempo_track = MidiTrack()
|
|
tempo_track.append(MetaMessage('set_tempo', tempo=initial_tempo, time=0))
|
|
new_mid.tracks.append(tempo_track)
|
|
|
|
for track in new_tracks:
|
|
new_mid.tracks.append(track)
|
|
|
|
return new_mid
|