Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Open
cifkao opened this issue Dec 30, 2020 · 7 comments
Open

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

cifkao opened this issue Dec 30, 2020 · 7 comments
Labels
enhancement New feature or request

Comments

@cifkao
Copy link
Contributor

cifkao commented Dec 30, 2020

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
Copy link
Contributor Author

cifkao commented Dec 30, 2020

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
Copy link
Owner

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.

@salu133445 salu133445 added the enhancement New feature or request label Jan 1, 2021
@cifkao
Copy link
Contributor Author

cifkao commented Jan 1, 2021

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
Copy link
Owner

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
Copy link
Contributor Author

cifkao commented Jan 3, 2021

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
Copy link
Owner

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
Copy link
Owner

Some alternatives:

  • Keep track of the channel-program mapping to reuse a channel for the same program.
  • Synthesize for every 16 tracks and mix them up at the end.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request
Projects
None yet
Development

No branches or pull requests

2 participants