sinshu / meltysynth

A SoundFont MIDI synthesizer for .NET
Other
136 stars 16 forks source link

GM2 Patch Change Overrides #31

Closed JimBayne closed 1 year ago

JimBayne commented 1 year ago

Many thanks for this wonderful product.

I am currently using it to render midi files to audio in a variety of ways.

I am stuck however, trying to override GM patch changes in the midi sequences with GM2 patch changes. I don't seem to be able to get the overrides to work.

I am using a SoundFont that claims to be GM2 compatible from https://musical-artifacts.com/artifacts/1346, and have the following code in place ...

` ... other stuff ...

    public const int PATCH_CHANGE = 0xC0;
    public const int BANK_CHANGE = 0xB0;
    public const int BANK_0 = 0;
    public const int BANK_32 = 32;

    ... other stuff ...

            // Create the synthesizer.
            synthesizer = new Synthesizer(SoundFontFile, sampleRate);

            midiFile = new MidiFile(MidiFile);
            sequencer = new MidiFileSequencer(synthesizer);

            sequencer.OnSendMessage = (synthesizer, channel, command, data1, data2) =>
            {
                if (command == PATCH_CHANGE && channel >= 0 && channel <= 15 && PatchOverRide[channel] != NO_OVERRIDE)
                {  //  sequencer encountered a patch change for a channel that we want to override the patch for. Override the patch ...
                    synthesizer.ProcessMidiMessage(channel, BANK_CHANGE, BANK_0, PatchOverRide_Bank_0_MSB[channel]);   // issue the Bank 0 change
                    synthesizer.ProcessMidiMessage(channel, BANK_CHANGE, BANK_32, PatchOverRide_Bank_32_MSB[channel]); // issue the Bank 32 change
                    synthesizer.ProcessMidiMessage(channel, command, PatchOverRide[channel], data2);  //                  issue the revised patch change  
                }
                else
                {  //  We're not interested in this message. Let the sequencer process it "as is".
                    synthesizer.ProcessMidiMessage(channel, command, data1, data2);
                }
            };

            sequencer.Play(midiFile, false);

            // setup the output buffer.
            left = new float[(int)(sampleRate * midiFile.Length.TotalSeconds)];
            right = new float[(int)(sampleRate * midiFile.Length.TotalSeconds)];

            // Render the waveform.
            sequencer.Render(left, right);

    ... other stuff ...`

The PatchOverRide's are working, however the BANK_0, and BANK_32 bank changes have no effect. For instance

Patch Bank32 Bank0 Name 063 121 001 Synth Brass 1 063 121 002 Synth Brass 3 063 121 003 Analog Brass 1 063 121 004 Jump Brass 1

all result in the same instrument being selected (063 121 001 Synth Brass 1) - i.e. no difference in the sound.

Do you have any suggestions as to how I can overcome this issue?

Regards Jim Bayne

sinshu commented 1 year ago

Could you share an example MIDI file that does not work well?

JimBayne commented 1 year ago

Thanks for the quick response. I have attached a sample midi file.

Try listening to / rendering the first 10 bars with the patch for channel 6 set to

          patch#  bank32  bank0
           063    121     001     Synth Brass 1

... 063 121 002 Synth Brass 3 ... 063 121 003 Analog Brass 1 ... 063 121 004 Jump Brass 1

I find they all sound identical, but ...

I don't thing MeltySynth is the problem. I have tried using Coolsoft's Virtual Midi Synth with the GM2 compatible SoundFount from https://musical-artifacts.com/artifacts/1346 and Virtual Midi Synth has the same problem. All patch variants for the same patch # from the different banks sound the same.

It's most likely that the SoundFont has the same audio samples for the different banks.

Do you know of a SoundFont that does have a full set of GM2 samples? SampeMidiFile.zip

sinshu commented 1 year ago

I checked the SF2 and found that it contains the different banks with the different samples. You can confirm this by opening the SF2 with some SF2 editor.

All the numbers below are zero-based indexing to avoid confusion.

The MIDI file you provided uses Synth-Brass 1 (patch 62, bank 0) in the track 10. To override that instrument with Jump-Brass 1 (patch 62, bank 3), I used the following code, and it seems work.

sequencer.OnSendMessage = (synthesizer, channel, command, data1, data2) =>
{
    if (channel == 10 && command == 0xC0 && data1 == 62)
    {
        synthesizer.ProcessMidiMessage(channel, 0xB0, 0, 3);
        synthesizer.ProcessMidiMessage(channel, 0xC0, 62, 0);
        Console.WriteLine("Overrided!");
        return;
    }

    synthesizer.ProcessMidiMessage(channel, command, data1, data2);
};

I believe that bank32 (bank change course) is unnecessary in this case, since all the bank numbers in the SF2 are less than 128. Bank32 will be necessary only if there are more than 128 banks for a patch.

JimBayne commented 1 year ago

Many thanks. Your response was extremely helpful, and solved the problem for me. I had the bank 32 LSB and bank 0 MSB values reversed when I interpreted the patch maps. Once I sorted that out, my MeltySynth patch overrides began to work. I couldn't really hear the difference with the Synth-Brass patches, but when I tested with the Sitar patches it became very evident that I was obtaining different voicings.