Handle GS SysEx messages for setting whether a channel is used for rhythm part. (#708)

Some MIDI files that uses the GS standard uses channels other than channel 10 as percussion channel. Currently FluidSynth ignores the messages setting that up, causing notes meant to be played with a drum instrument played with a melodic instrument or vice versa. This patch will partially fix the issue.

Currently the implementation in this patch doesn't cover a specific "quirk" in Roland GS Modules: they seem to remember the last used instrument in the selected mode. This patch simply sets the instrument number to 0 instead.

A test file is attached. If played correctly (with `-o synth.device-id=16`) no out of place drum or piano sounds should be heard.

[wikipedia_MIDI_sample_gstest.mid.gz](https://github.com/FluidSynth/fluidsynth/files/5610727/wikipedia_MIDI_sample_gstest.mid.gz)
This commit is contained in:
Chris Xiong 2020-11-29 07:20:04 +08:00 committed by GitHub
parent 33c147402b
commit 9b485fad7c
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
2 changed files with 77 additions and 0 deletions

View file

@ -187,6 +187,7 @@ enum midi_sysex_manuf
/* SYSEX sub-ID #1 which follows device ID */
#define MIDI_SYSEX_MIDI_TUNING_ID 0x08 /**< Sysex sub-ID #1 for MIDI tuning messages */
#define MIDI_SYSEX_GM_ID 0x09 /**< Sysex sub-ID #1 for General MIDI messages */
#define MIDI_SYSEX_GS_ID 0x42 /**< Model ID (GS) serving as sub-ID #1 for GS messages*/
/**
* SYSEX tuning message IDs.
@ -208,6 +209,7 @@ enum midi_sysex_tuning_msg_id
/* General MIDI sub-ID #2 */
#define MIDI_SYSEX_GM_ON 0x01 /**< Enable GM mode */
#define MIDI_SYSEX_GM_OFF 0x02 /**< Disable GM mode */
#define MIDI_SYSEX_GS_DT1 0x12 /**< GS DT1 command */
enum fluid_driver_status
{

View file

@ -66,6 +66,10 @@ static int fluid_synth_sysex_midi_tuning(fluid_synth_t *synth, const char *data,
int len, char *response,
int *response_len, int avail_response,
int *handled, int dryrun);
static int fluid_synth_sysex_gs_dt1(fluid_synth_t *synth, const char *data,
int len, char *response,
int *response_len, int avail_response,
int *handled, int dryrun);
int fluid_synth_all_notes_off_LOCAL(fluid_synth_t *synth, int chan);
static int fluid_synth_all_sounds_off_LOCAL(fluid_synth_t *synth, int chan);
static int fluid_synth_system_reset_LOCAL(fluid_synth_t *synth);
@ -1898,6 +1902,7 @@ fluid_synth_handle_device_id(void *data, const char *name, int value)
* Non-realtime: 0xF0 0x7E <DeviceId> [BODY] 0xF7
* Realtime: 0xF0 0x7F <DeviceId> [BODY] 0xF7
* Tuning messages: 0xF0 0x7E/0x7F <DeviceId> 0x08 <sub ID2> [BODY] <ChkSum> 0xF7
* GS DT1 messages: 0xF0 0x41 <DeviceId> 0x42 0x12 [ADDRESS (3 bytes)] [DATA] <ChkSum> 0xF7
*/
int
fluid_synth_sysex(fluid_synth_t *synth, const char *data, int len,
@ -1940,6 +1945,21 @@ fluid_synth_sysex(fluid_synth_t *synth, const char *data, int len,
FLUID_API_RETURN(result);
}
/* GS DT1 message */
if((synth->bank_select == FLUID_BANK_STYLE_GS)
&& data[0] == MIDI_SYSEX_MANUF_ROLAND
&& (data[1] == synth->device_id || data[1] == MIDI_SYSEX_DEVICE_ID_ALL)
&& data[2] == MIDI_SYSEX_GS_ID
&& data[3] == MIDI_SYSEX_GS_DT1)
{
int result;
fluid_synth_api_enter(synth);
result = fluid_synth_sysex_gs_dt1(synth, data, len, response,
response_len, avail_response,
handled, dryrun);
FLUID_API_RETURN(result);
}
return FLUID_OK;
}
@ -2235,6 +2255,61 @@ fluid_synth_sysex_midi_tuning(fluid_synth_t *synth, const char *data, int len,
return FLUID_OK;
}
/* Handler for GS DT1 messages */
static int
fluid_synth_sysex_gs_dt1(fluid_synth_t *synth, const char *data, int len,
char *response, int *response_len, int avail_response,
int *handled, int dryrun)
{
int addr;
int len_data;
int checksum = 0, i;
if(len < 9) // at least one byte of data should be transmitted
{
return FLUID_FAILED;
}
len_data = len - 8;
addr = (data[4] << 16) | (data[5] << 8) | data[6];
for (i = 4; i < len - 1; ++i)
{
checksum += data[i];
}
if (0x80 - (checksum & 0x7F) != data[len - 1])
{
return FLUID_FAILED;
}
if ((addr & 0xFFF0FF) == 0x401015) // Use for rhythm part
{
if (len_data > 1 || data[7] > 0x02)
{
return FLUID_FAILED;
}
if (handled)
{
*handled = TRUE;
}
if (!dryrun)
{
int chan = (addr >> 8) & 0x0F;
//See the Patch Part parameters section in SC-88Pro/8850 owner's manual
chan = chan >= 0x0a ? chan : (chan == 0 ? 9 : chan - 1);
synth->channel[chan]->channel_type =
data[7] == 0x00 ? CHANNEL_TYPE_MELODIC : CHANNEL_TYPE_DRUM;
//Roland synths seem to "remember" the last instrument a channel
//used in the selected mode. This behavior is not replicated here.
fluid_synth_program_change(synth, chan, 0);
}
return FLUID_OK;
}
//silently ignore
return FLUID_OK;
}
/**
* Turn off all voices that are playing on the given MIDI channel, by putting them into release phase.
* @param synth FluidSynth instance