salu133445 / muspy

A toolkit for symbolic music generation
https://salu133445.github.io/muspy/
MIT License
427 stars 49 forks source link

MIDI output should not use a single channel for all tracks #40

Open cifkao opened 3 years ago

cifkao commented 3 years ago

While according to the MIDI standard it should be possible to put all instruments in a single channel as long as they are in different tracks, in practice, this is not true. Some softwares, including FluidSynth, seem to care more about the channel number than the track, so if MusPy puts notes from all tracks in channel 0, they will all end up using the same instrument.

Example test.json.gz containing the following tracks:

[Track(program=36, is_drum=False, name='BB Bass', ...), Track(program=0, is_drum=True, name='BB Drums', ...), Track(program=27, is_drum=False, name='BB Guitar', ...), Track(program=4, is_drum=False, name='BB Piano', ...)]

Output of muspy.write_midi: test.mid.gz Output of muspy.write_audio: test.mp3.gz Clearly all tracks sound like piano.

This is how pretty_midi solves the problem: https://github.com/craffel/pretty-midi/blob/5e3db4bfa6be0d6e87d7a9e8fcf5f4ed81e97a8d/pretty_midi/pretty_midi.py#L1380-L1397

And I guess this is also the reason why pretty_midi synthesizes each track separately and then mixes them together: https://github.com/craffel/pretty-midi/blob/5e3db4bfa6be0d6e87d7a9e8fcf5f4ed81e97a8d/pretty_midi/pretty_midi.py#L973-L982

cifkao commented 3 years ago

This behavior seems to be quite common. I also tested Rosegarden and LMMS and both behave like FluidSynth. On the other hand, MuseScore will load the file "correctly" (i.e. as different instruments).

salu133445 commented 3 years ago

It seems that FluidSynth allows more than 16 channels (FluidSynth/fluidsynth#326). I don't know how to store a MIDI file with more than 16 channels though.

For music that has less than 16 instruments, we could simply assign a channel for each instrument.

cifkao commented 3 years ago

Yes, pretty_midi simply increments the channel for each track, skipping track 9 and going back to 0 once it runs out of channels.

salu133445 commented 3 years ago

MIDI files with no more than 16 tracks are now supported. For MIDI files that have more than 16 tracks, we might need to rely on the midi_port meta messages (https://www.pgmusic.com/forums/ubbthreads.php?ubb=showflat&Number=14800).

cifkao commented 3 years ago

In my understanding, ports are like MIDI devices that can send/receive messages. I don't think this concept applies to MIDI files. So there's probably no good way to solve this, the best we can do is cycle through the 15 available (non-drum) channels.

salu133445 commented 3 years ago

I just checked the following code.

music = muspy.read("tests/data/midi/fur-elise.mid")

for i in range(16):
    t = music.tracks[0].deepcopy()
    t.program = i + 24  # Guitar or Bass
    music.tracks.insert(-1, t)
music[-1].program=127  # Sound effects - Gunshot

print([t.program for t in music])
# Output: [0, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 127]

music.write("test.wav")

The current implementation of write_audio clearly does not work for more than 16 tracks. Only the last track is for the left hand part and its program is set to gun shot effects. However, you can hear the gun shot effects for the right hand part. That's because the third track (program 25) and the last track (program 127) are merged into a single stream, and, as a result, the program of the third track will be overwritten into 127.

salu133445 commented 3 years ago

Some alternatives: