1

Type 0 midi files (example) have all instruments crammed onto a single track.

Type 1 midi files have instruments separated out into distinct tracks.

Is there a good way to convert from type 0 to type 1? If there are any resources out there that can run this conversion, I'd love to hear about them!

duhaime
  • 25,611
  • 17
  • 169
  • 224
  • I'm guessing this would take ~100 LOC and some thought to do right, as midi time stamps are relative, so I'm hoping a library like `mido` contains some method like `unmerge_tracks` – duhaime Jun 05 '20 at 21:36
  • It looks you want a library. Maybe [this](https://stackoverflow.com/questions/569321/simple-cross-platform-midi-library-for-python) is useful.Cannot help much here. Maybe there are more modern libraries, search pypi. – progmatico Jun 05 '20 at 21:58

1 Answers1

0

Here's how I took care of this. The important insight was that channel in midi files designates the device port on which some info is sent (e.g. some midi keyboard input), while program is the instrument voice (e.g. sawtooth lead, cajun banjo...).

This means one can create a dictionary with one key per voice, and a list of values that contain notes played in that voice. Time should initially be stored in global coordinates (as in a type-0 file the relative time coordinates are expressed across all voices but we're now separating voices out into distinct lists of notes).

Then one can convert back to relative time units, store the bpm and time resolution values from the input type-0 track, and whoomp--there's your type 1 midi.

from collections import defaultdict
import mido, os

def subdivide_midi_tracks(path):
  '''Convert a type 0 midi file to a type 1 midi file'''
  m = mido.MidiFile(path) # load the original type-0 midi file
  messages = [] # a list of message dicts, one per track
  for track in m.tracks:
    time = 0 # store time in aggregate units
    track_messages = []
    for idx, i in enumerate(track):
      i = i.dict()
      if i.get('time', None): time += i.get('time')
      i['time'] = time
      track_messages.append(i)
    messages.append(track_messages)
  # build a dictionary of the events for each channel
  d = defaultdict(list) # d[channel_id] = [notes]
  for track_idx, track in enumerate(messages):
    for i in track:
      channel = i.get('channel', -1)
      d[channel].append(i)
  # covert time units in each program back to relative units
  for channel in d:
    total_time = 0
    for i in sorted(d[channel], key=lambda i: i['time']):
      t = i['time']
      i['time'] = t - total_time
      total_time = t
  # create a midi file and add a track for each channel
  m2 = mido.MidiFile()
  for channel in sorted(d.keys()):
    track = mido.midifiles.tracks.MidiTrack()
    # add the notes to this track
    for note in d[channel]:
      note_type = note['type']
      del note['type']
      # if this is a meta message, append a meta message else a messaege
      try:
        track.append(mido.MetaMessage(note_type, **note))
      except:
        track.append(mido.Message(note_type, **note))
    m2.tracks.append(track)
  # ensure the time quantization is the same in the new midi file
  m2.ticks_per_beat = m.ticks_per_beat
  return m2

m = midi.load("data/nes/'Bomb_Man's_Stage'_Mega_Man_I_by_Mark_Richardson.mid")
m2 = subdivide_midi_tracks(m.path)
m2.save('whoop.mid')
os.system('open whoop.mid')
duhaime
  • 25,611
  • 17
  • 169
  • 224