schellingb / TinySoundFont

SoundFont2 synthesizer library in a single C/C++ file
MIT License
647 stars 76 forks source link

Trouble playing general midi file percussion #13

Closed kavika13 closed 6 years ago

kavika13 commented 6 years ago

Two problems I've run into so far:

  1. The example3 app mapped all channels to 0, whereas GM seems (from my limited understanding) to map channel 10 (index 9 in this library's case) to a special percussion sound set. My understanding is coming from this page: https://www.midi.org/specifications/item/gm-level-1-sound-set
  2. When I manually mapped channel 10 to the right bank/program for my sound font, it seems it wasn't playing just the percussion sound that it should have been. As far as I can tell, it was hitting multiple notes at once - both what sounded like the right crash cymbal (49) and some sort of cowbell sound. This might partly be an artifact of the sound font I was using for testing, which is called GeneralUser GS MuseScore v1.442.sf2

I can provide a sample midi file if that helps. I looked at the binary of the midi in a hex editor with midi template support, and ran the sample code through the debugger. There's no program specification on that channel at all, and no bank switching. It seems the midi file is relying on mapping to the GM standard.

I can just manually map to solve problem 1, but problem 2 I don't fully get how to work around. I tried playing just "note 49" by hard coding it in the sample program, and it still played the extra note.

kavika13 commented 6 years ago

I tried hacking the region matching code to only match ranges where the top/bottom note exactly matched the played note for channel 10, and it seemed to play the correct instrument without the extra sounds. However, this only worked well for one midi file I had.

I am not sure how other programs do their mapping, but a program that I use to play midis that can support soundfonts ("MidiPlayer5") doesn't seem to have the same mapping problem.

kavika13 commented 6 years ago

I added more special case code for the program switch event, and my second midi track mapped the drums correctly this time. Basically if the channel is 10 (== 9), try loading from bank 128. If it can't find 128 + program num, fall back on looking up 128 + program 0.

It still needs the special case code to only search for a range that maps the key to the key range exactly, but now I've gotten two midi files to seemingly map correctly. Still not sure if this is a correct or foolproof way to do this, just hacking around.

schellingb commented 6 years ago

Hi there!

Thank you for the report and the detailed testing. Turns out the code marked with "that may not be correct" was indeed not correct. Commit d21c521c2f7fc85b9d490a5852fa05f2994171eb now correctly filters zone regions (aka instrument splits) by key/velocity ranges of parent preset region (aka preset layer). And then I also committed an update to example3 2e4021b25f023a79c9b53811289c3e975f903533 which should handle the percussion of the 10th MIDI channel (at least most cases I hope).

Though... both changes were done in kind of a hurry so I hope you can test and confirm them a bit more thorough than I just did.

Thanks again!

kavika13 commented 6 years ago

This is much closer! :) 👍

The percussion channel for one of the midi files I'm using isn't explicitly sending a program change message before it starts playing notes. The sample code seems to assume that would be the case. I'm not sure if this is a thing that should be automatically done or not.

After these commits, the only thing I had to do to work around the issue was add this after the sound font loading code to get the instruments to be assigned the way the midi seems to expect: g_MidiChannelPreset[9] = tsf_get_presetindex(g_TinySoundFont, 128, 0);

schellingb commented 6 years ago

Yay, good to hear! Yeah the initial preset setting for the 10th channel is indeed required, I'll add that to the example3 as well.

chriscoomber commented 3 years ago

@kavika13 @schellingb

Sorry this is many years later, but I'm just learning about the GM specs (1 and 2) and trying to figure out how I should make a GM2-compliant synthesizer (well, my aspirations are far less lofty - really I just want to play midi files that I generate which are written in a GM2-compliant way so they play well on other MIDI players tpp - I so have some control over the MIDI files). Maybe I would be happy with GM1-compliant instead. I'm not sure.

Anyway, I was wondering where you got 128==0x80 from? In reading the GM2 spec, it looks like all we need to do is default channel 10 (==9) to the bank 0x7800 and default to preset 0 (where the key->drum mapping is given in the GM1 spec), and all other channels to the bank 0x7900 (where the presets are given by the GM1 spec). Having loaded an allegedly GM2 .sf2 file, I can't see any mention of bank 0x7900 or 0x7800, but I do see drums in bank 0x80, so there must be something about 128==0x80.

I also found this interesting comment about some synthesizers sneakily mapping bank 0x80 from the soundfont to bank 0x7800, so that they remain GM2 compliant.

Perhaps we're not trying to be GM2 compliant, but instead GM1 compliant. The GM1 spec doesn't talk about banks, so I reckon it doesn't expect them to be configurable. I didn't see anywhere where mentioned the bank 0x80==128 though. Do you know where this standard came from?

Reading a little more, it sounds like maybe this is an .sf2 convention? Which means that .sf2 is not compatible with GM2 since 0x80 != 0x7800 (or at least it requires the synthesizer to do the mapping from the comment I linked above, so that any bank select messages from a GM2 MIDI file talking about 0x7800 get mapped to 0x80).

This is all very confusing, sorry for not being very coherent here. This is just me trying to figure something out by asking as many people as possible!

Edit: I've done some more research. It would appear that the convention is as follows:

So I think either my synthesizer code (which calls into TSF), or TSF itself should do this guessing. Currently, TSF doesn't quite do this guessing. Most of the time it works by defaulting to bank 0 if you don't pass flag_mididrums, and bank 128 if you do. This is actually fine for 99% of cases, so possibly I don't need to do anything here (for full GM2 compliance, would need to handle banks 0x7901 to 0x7904 properly too). Note though that my code that calls TSF needs to know whether this is a melody channel or a rhythm channel and pass the flag appropriately. Technically in GM2 you can configure channel 10 to be a melody channel by giving it a bank 0x79nm, and you can configure channel 11 to a rhythm channel by giving it a bank 0x78nm (n and m are hex numbers), so I would need to keep track of that when deciding whether to pass the flag or not.

I think no response required from either of you. Sorry that you were victim to me thinking out loud.

kode54 commented 3 years ago

SoundFont banks only support an 8 bit bank field, with banks of 0-127 being instrument banks, and bank 128 being the drum bank. Anything else is non-standard.

chriscoomber commented 3 years ago

Ok, that makes sense, thanks. I find it odd that in none of the specs is there a well defined mapping between the Midi version of a bank (0x0000 to 0xFFFF) and the soundfont version of the bank (0-128). It's just left to the synthesiser to guess.