Yikai-Liao / symusic

A cross platform note level midi decoding library with lightening speed, based on minimidi.
https://yikai-liao.github.io/symusic/
MIT License
108 stars 8 forks source link

Incorrect/inconsistent treatment of program change in dump_midi #22

Closed kirchhoff-hw closed 5 months ago

kirchhoff-hw commented 5 months ago

Hi there!

I have realized a strange behaviour when writing a Score containing multiple tracks to a MIDI file. General MIDI instruments (programs) don't seem to be interpreted correctly by different sequencers.

Here is a minimal example that should reproduce the issue:

from symusic import Score, Tempo, TimeSignature, Track

score = Score(16)
score.tempos.append(Tempo(time=0, qpm=120))
score.time_signatures.append(TimeSignature(0, 4, 4))
score.tracks.append(Track(name='piano', program=0, notes=[Note(0, 16, 60, 64), Note(16, 16, 64, 64), Note(32, 16, 67, 64), Note(48, 32, 72, 64)]))
score.tracks.append(Track(name='violin', program=40, notes=[Note(16, 16, 60, 64), Note(32, 16, 64, 64), Note(48, 32, 67, 64)]))
score.dump_midi("test.mid")

This should be synthesized by two different general MIDI sounds. However, in Reaper and Windows Media Player, only the first note is rendered with a piano sound and all subsequent notes (of both tracks) are rendered as violin. In Audacity on the other hand, only piano is used as a sound for all notes.

I assume this has to do with the way symusic writes the program change events. Unfortunately I don't have enough understanding of the code base to debug further.

Do you have any suggestion/idea what goes wrong here?

Yikai-Liao commented 5 months ago

Thanks for your report. I have reproduced this bug.

For the midi file dumped by symusic, MuseScore failed to get the right program of violin. But after I re-load it with miditoolkit and dump it again, MuseScore can get the right program. Here is the two midi files I created: midi.zip

It's a strange bug. I'll check it further.

Yikai-Liao commented 5 months ago

Well, we do find the difference here: symusic stores the two "track" into two different midi tracks with the same channel 0, while miditoolkit stores the two "track" into 2 midi tracks with 2 channels.

It seems MuseScore just merges the two midi track with the same channel into a single one although they have different program. And this behaviour is something not defined in midi standard.

kirchhoff-hw commented 5 months ago

Thanks for your quick reply!

Yes, I also tried reading the dumped file with pretty_midi and saving it again to a file, and it also resolved the issue. pretty_midi also writes it to two different tracks (at least 2 tracks are displayed in Reaper).

Yikai-Liao commented 5 months ago

It seems we need to follow such strategy. They just use something like channel = idx % 16

Yikai-Liao commented 5 months ago

@kirchhoff-hw I have pushed the commit to fix this bug. You could try it by:

git clone --recursive https://github.com/Yikai-Liao/symusic
pip install ./symusic

If it works on your more complex cases, I'll release this as a new version.

kirchhoff-hw commented 5 months ago

@Yikai-Liao Thanks! It seems to work for me! I also tested some other more complex files and playback seems fine and consistent across different sequencers, too.

kirchhoff-hw commented 5 months ago

I have some other files that cause unexpected results when loading and dumping with symusic. Those are, however, unrelated with this issue. I might report a different issue for this. But thanks for this fix already!

Yikai-Liao commented 5 months ago

Ok, I'll release this new version first. Looking forward to your other reports.

kirchhoff-hw commented 5 months ago

@Yikai-Liao I just checked the other files I had problems with earlier, and also they seem fine for the moment! So, nothing else to report here. If something comes up in the future, I will let you know.

So, thanks again!