DBraun / DawDreamer

Digital Audio Workstation with Python; VST instruments/effects, parameter automation, FAUST, JAX, Warp Markers, and JUCE processors
GNU General Public License v3.0
895 stars 65 forks source link

Handling multi-track MIDI files #8

Open ethman opened 4 years ago

ethman commented 4 years ago

Many MIDI files can contain more than one instrument track. I think there needs to be a plan for handling these types of files. Looking at this function, it looks like all of the MIDI tracks get rendered with the same graph, which might be problematic if the user doesn't know or expect there to be more than one track. For my use case, it's hard to know beforehand how many tracks are in the MIDI file.

Currently, I've been using pretty_midi and implementing the logic myself in python to do this. But the thing that sucks about using pretty_midi in this way is that it's a huge pain in the butt to use for multitracks if you just want one. Unless there's a better way, what I've been doings is:

  1. Load multitrack MIDI into a PrettyMIDI object
  2. Make a copy of the object with only the 1 track you want
  3. Save to disk
  4. Reload the MIDI with RenderMan (now DawDreamer)

Which is a very heavy and slow process. I would love a better solution to this.

One solution that would greatly improve my workflow would be if there was a way to "blow up" the MIDI file once it's loaded. Similar to pretty_midi, which gives you a nice list of Instrument objects, which can then be manipulated individually, that might be nice to expose in the API. So a solution I would like to see would be one that can give me a list of MIDI track objects in memory that I can send each through a graph individually.

DBraun commented 4 years ago

The "blow-up" idea is interesting but for simplicity I kinda want to avoid passing things from third party python APIs via pybind11 to dawdreamer. So PrettyMidi might have a very clean representation of MIDI, and this object can be mutated in useful ways, but then how does it get passed to dawdreamer? Possibly a big challenge. Maybe something simpler is to just work on load_midi with extra arguments for loading tracks.

tracks = None  # by default load all tracks
synth.load_midi("song.mid", tracks) # same as synth.load_midi("song.mid")
# We probably need to use JUCE to save MIDI channel info and then call new function:
print(synth.get_midi_instruments())
# would return a list of "instrument" dictionaries where each dict has key/values:
# "program": (int) The program number of this instrument.
# "name": (str) The name of this instrument.

# Suppose we got
[{"program": 0, "name": "violin"}, {"program": 1, "name": "cello"}]
# but we only want cello..

# then either
synth.load_midi("song.mid", "cello") # or synth.load_midi("song.mid", 1)

# This would be loading from disk a second time, but it's better than having to write to disk too right?

# Maybe there would be a way to "drop midi" too
synth.drop_midi_track("violin")

# But I think if MIDI got loaded once then it's already too piled into one track