saturn-examples/midi/simulate.py
Zack Buhman 86d12c37ed midi: add generator
This adds both a midi generator and a midi type 1 simulator.

While I would have preferred to use an existing tool for this, I found
that timidity++ does not emit pitch wheel events correctly, and I
don't know of another widely-distributed tool that does midi-to-midi
format conversion.

The c++ and python versions were co-developed. I wrote one to test the
other. There is more cleanup to do, but `roundtrip.cpp` produces a
valid type 0 midi file given a type 1 or type 0 midi file as input.
2023-07-01 00:15:53 +00:00

75 lines
2.0 KiB
Python

from itertools import chain
from parser import *
"""
_slots = set()
def simulate_note(ix, ev):
if type(ev.event) is NoteOn:
print(repr(ev.event))
_slots.add((ev.event.channel, ev.event.note))
assert len(_slots) <= 32, (hex(ix))
if type(ev.event) is NoteOff:
print(repr(ev.event))
try:
_slots.remove((ev.event.channel, ev.event.note))
except:
print("ix", hex(ix))
raise
"""
def linearize_track(track):
time = 0
for i, event in enumerate(track.events):
time += event.delta_time
yield time, i, event
l = {
NoteOff: 0,
NoteOn: 1,
PolyphonicKeyPressure: 2,
ControlChange: 3,
ProgramChange: 4,
ChannelPressure: 5,
PitchBendChange: 6,
ChannelMode: 7,
}
def sort_key(global_time, i, event):
channel = event.event.event.channel if type(event.event) is MIDIEvent else 99
priority = -l[type(event.event.event)] if type(event.event) is MIDIEvent else -99
return global_time, priority, channel
def linearize_events(tracks):
global_time = 0
linearized = list(chain.from_iterable(map(linearize_track, tracks)))
linear_sort = sorted(linearized, key=lambda args: sort_key(*args))
for abs_time, i, event in linear_sort:
if type(event.event) is MetaEvent and event.event.type is MetaType.EndOfTrack:
continue
inner_event = event.event
delta_time = abs_time - global_time
print(Event(delta_time, inner_event))
global_time = abs_time
print(Event(delta_time=0, event=MetaEvent(type=MetaType.EndOfTrack, value=None)))
def dump_file(buf):
buf, header = parse_header(buf)
print(header)
assert header.ntrks > 0
tracks = []
for track_num in range(header.ntrks):
buf, track = parse_track(buf)
tracks.append(track)
print("remaining data:", len(buf))
assert len(buf) == 0, bytes(buf)
linearize_events(tracks)
import sys
with open(sys.argv[1], 'rb') as f:
b = memoryview(f.read())
dump_file(b)