FYI, here is my code from my instrument changer to check for GS percussion assignment on a non-standard channel:
// GS Custom Percussion Assignment
// F0 0A 41 (10) 42 12 [40 1A 15] [02] 0F F7
// [.. 1x ..] x = part# / (probably) ch# 1-9, 0, A-F
// [0y] y = type: 0 = standard, 1 = drum map 1, 2 = drum map 2
else if ((buffer_peek(global.edit_buffer, cur_pos, buffer_u8) == 0x41) &&
/*(buffer_peek(global.edit_buffer, cur_pos + 1, buffer_u8) == 0x10) &&*/
(buffer_peek(global.edit_buffer, cur_pos + 2, buffer_u8) == 0x42) &&
(buffer_peek(global.edit_buffer, cur_pos + 3, buffer_u8) == 0x12) &&
(buffer_peek(global.edit_buffer, cur_pos + 4, buffer_u8) == 0x40) &&
/*(buffer_peek(global.edit_buffer, cur_pos + 5, buffer_u8) == 0x00) &&*/
(buffer_peek(global.edit_buffer, cur_pos + 6, buffer_u8) == 0x15) &&
/*(buffer_peek(global.edit_buffer, cur_pos + 7, buffer_u8) == 0x00) &&*/
(buffer_peek(global.edit_buffer, cur_pos + 8, buffer_u8) == 0x0F) &&
(buffer_peek(global.edit_buffer, cur_pos + 9, buffer_u8) == 0xF7))
{
part_num = buffer_peek(global.edit_buffer, cur_pos + 5, buffer_u8);
part_num -= 0xF;
part_type = buffer_peek(global.edit_buffer, cur_pos + 7, buffer_u8);
global.custom_drums[part_num - 1] = part_type;
}
buffer_seek(global.edit_buffer, buffer_seek_relative, v_length);
break;
I read the F0 to trigger a SYSEX scan (all values in HEX). If the next byte is 0A then check for 41 (skip device ID) 42 12 40 (skip part number) 15 (skip type) 0F F7. If those all match, I then go back and read the part number and type.
XG is easier to determine percussion channels, as the MSB for a channel will either be set to 126 or 127.
GM can also have percussion set with MSB 120.