mirror of
https://github.com/fabiangreffrath/woof.git
synced 2025-09-24 04:29:34 -04:00
Misc. MIDI fixes and refactoring (#1657)
This commit is contained in:
parent
341f56d246
commit
8be4beea1c
@ -51,7 +51,7 @@ enum
|
||||
|
||||
enum
|
||||
{
|
||||
RESET_TYPE_NONE,
|
||||
RESET_TYPE_NO_SYSEX,
|
||||
RESET_TYPE_GM,
|
||||
RESET_TYPE_GS,
|
||||
RESET_TYPE_XG,
|
||||
@ -59,11 +59,7 @@ enum
|
||||
|
||||
int midi_complevel = COMP_STANDARD;
|
||||
int midi_reset_type = RESET_TYPE_GM;
|
||||
int midi_reset_delay = 0;
|
||||
|
||||
#define SYSEX_DEFAULT_BUFFER_SIZE 32
|
||||
static byte *sysex_buffer;
|
||||
static int sysex_buffer_size = SYSEX_DEFAULT_BUFFER_SIZE;
|
||||
int midi_reset_delay = -1;
|
||||
|
||||
static const byte gm_system_on[] =
|
||||
{
|
||||
@ -92,7 +88,6 @@ static const byte ff_loopEnd[] =
|
||||
|
||||
static boolean use_fallback;
|
||||
|
||||
#define DEFAULT_VOLUME 100
|
||||
static byte channel_volume[MIDI_CHANNELS_PER_TRACK];
|
||||
static float volume_factor = 0.0f;
|
||||
|
||||
@ -128,6 +123,8 @@ typedef struct
|
||||
|
||||
typedef struct
|
||||
{
|
||||
void *lump_data;
|
||||
int lump_length;
|
||||
midi_file_t *file;
|
||||
midi_track_t *tracks;
|
||||
unsigned int elapsed_time;
|
||||
@ -177,15 +174,14 @@ static uint64_t CurrentTime(void)
|
||||
return I_GetTimeUS() - start_time;
|
||||
}
|
||||
|
||||
static void SendShortMsg(byte status, byte channel, byte param1, byte param2)
|
||||
{
|
||||
const byte message[] = {status | channel, param1, param2};
|
||||
MIDI_SendShortMsg(message, sizeof(message));
|
||||
}
|
||||
// Sends a channel message with the second parameter overridden to zero. Only
|
||||
// use this function for events that require special handling.
|
||||
|
||||
static void SendLongMsg(const byte *message, unsigned int length)
|
||||
static void SendChannelMsgZero(const midi_event_t *event)
|
||||
{
|
||||
MIDI_SendLongMsg(message, length);
|
||||
const byte message[] = {event->event_type | event->data.channel.channel,
|
||||
event->data.channel.param1, 0};
|
||||
MIDI_SendShortMsg(message, sizeof(message));
|
||||
}
|
||||
|
||||
static void SendChannelMsg(const midi_event_t *event, boolean use_param2)
|
||||
@ -205,15 +201,48 @@ static void SendChannelMsg(const midi_event_t *event, boolean use_param2)
|
||||
}
|
||||
}
|
||||
|
||||
static void SendControlChange(byte channel, byte number, byte value)
|
||||
{
|
||||
const byte message[] = {MIDI_EVENT_CONTROLLER | channel, number, value};
|
||||
MIDI_SendShortMsg(message, sizeof(message));
|
||||
}
|
||||
|
||||
// Writes a MIDI program change message. If applicable, emulates capital tone
|
||||
// fallback (CTF) to fix invalid instruments.
|
||||
|
||||
static void SendProgramChange(byte channel, byte program)
|
||||
{
|
||||
const byte message[] = {MIDI_EVENT_PROGRAM_CHANGE | channel, program};
|
||||
MIDI_SendShortMsg(message, sizeof(message));
|
||||
}
|
||||
|
||||
static void SendProgramChangeCTF(byte channel, byte program,
|
||||
const midi_fallback_t *fallback)
|
||||
{
|
||||
switch (fallback->type)
|
||||
{
|
||||
case FALLBACK_DRUMS:
|
||||
SendProgramChange(channel, fallback->value);
|
||||
break;
|
||||
|
||||
case FALLBACK_BANK_MSB:
|
||||
SendControlChange(channel, MIDI_CONTROLLER_BANK_SELECT_MSB,
|
||||
fallback->value);
|
||||
// Fall through.
|
||||
|
||||
default:
|
||||
SendProgramChange(channel, program);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Writes an RPN message set to NULL (0x7F). Prevents accidental data entry.
|
||||
|
||||
static void SendNullRPN(const midi_event_t *event)
|
||||
{
|
||||
const byte channel = event->data.channel.channel;
|
||||
SendShortMsg(MIDI_EVENT_CONTROLLER, channel, MIDI_CONTROLLER_RPN_LSB,
|
||||
MIDI_RPN_NULL);
|
||||
SendShortMsg(MIDI_EVENT_CONTROLLER, channel, MIDI_CONTROLLER_RPN_MSB,
|
||||
MIDI_RPN_NULL);
|
||||
SendControlChange(channel, MIDI_CONTROLLER_RPN_LSB, MIDI_RPN_NULL);
|
||||
SendControlChange(channel, MIDI_CONTROLLER_RPN_MSB, MIDI_RPN_NULL);
|
||||
}
|
||||
|
||||
static void UpdateTempo(const midi_event_t *event)
|
||||
@ -229,15 +258,10 @@ static void SendManualVolumeMsg(byte channel, byte volume)
|
||||
{
|
||||
unsigned int scaled_volume;
|
||||
|
||||
scaled_volume = volume * volume_factor + 0.5f;
|
||||
scaled_volume = lroundf((float)volume * volume_factor);
|
||||
scaled_volume = MIN(scaled_volume, 127);
|
||||
|
||||
if (scaled_volume > 127)
|
||||
{
|
||||
scaled_volume = 127;
|
||||
}
|
||||
|
||||
SendShortMsg(MIDI_EVENT_CONTROLLER, channel, MIDI_CONTROLLER_VOLUME_MSB,
|
||||
scaled_volume);
|
||||
SendControlChange(channel, MIDI_CONTROLLER_VOLUME_MSB, scaled_volume);
|
||||
|
||||
channel_volume[channel] = volume;
|
||||
}
|
||||
@ -268,7 +292,7 @@ static void ResetVolume(void)
|
||||
|
||||
for (i = 0; i < MIDI_CHANNELS_PER_TRACK; ++i)
|
||||
{
|
||||
SendManualVolumeMsg(i, DEFAULT_VOLUME);
|
||||
SendManualVolumeMsg(i, MIDI_DEFAULT_VOLUME);
|
||||
}
|
||||
}
|
||||
|
||||
@ -282,28 +306,41 @@ static void SendNotesSoundOff(void)
|
||||
|
||||
for (i = 0; i < MIDI_CHANNELS_PER_TRACK; ++i)
|
||||
{
|
||||
SendShortMsg(MIDI_EVENT_CONTROLLER, i, MIDI_CONTROLLER_ALL_NOTES_OFF, 0);
|
||||
SendShortMsg(MIDI_EVENT_CONTROLLER, i, MIDI_CONTROLLER_ALL_SOUND_OFF, 0);
|
||||
SendControlChange(i, MIDI_CONTROLLER_ALL_NOTES_OFF, 0);
|
||||
SendControlChange(i, MIDI_CONTROLLER_ALL_SOUND_OFF, 0);
|
||||
}
|
||||
}
|
||||
|
||||
// Resets commonly used controllers. This is only for a reset type of "none" for
|
||||
// devices that don't support SysEx resets.
|
||||
// Writes "reset all controllers" message for each channel. Despite the name,
|
||||
// this only resets some controllers (see MIDI Recommended Practice RP-015).
|
||||
|
||||
static void ResetControllers(void)
|
||||
{
|
||||
int i;
|
||||
|
||||
for (i = 0; i < MIDI_CHANNELS_PER_TRACK; i++)
|
||||
{
|
||||
SendControlChange(i, MIDI_CONTROLLER_RESET_ALL_CTRLS, 0);
|
||||
}
|
||||
}
|
||||
|
||||
// Resets commonly used controllers. This is only for a reset type of "No SysEx"
|
||||
// for devices that don't support SysEx resets.
|
||||
|
||||
static void ResetNoSysEx(void)
|
||||
{
|
||||
int i;
|
||||
|
||||
for (i = 0; i < MIDI_CHANNELS_PER_TRACK; ++i)
|
||||
{
|
||||
// Reset commonly used controllers.
|
||||
SendShortMsg(MIDI_EVENT_CONTROLLER, i, MIDI_CONTROLLER_RESET_ALL_CTRLS, 0);
|
||||
SendShortMsg(MIDI_EVENT_CONTROLLER, i, MIDI_CONTROLLER_PAN, 64);
|
||||
SendShortMsg(MIDI_EVENT_CONTROLLER, i, MIDI_CONTROLLER_BANK_SELECT_MSB, 0);
|
||||
SendShortMsg(MIDI_EVENT_CONTROLLER, i, MIDI_CONTROLLER_BANK_SELECT_LSB, 0);
|
||||
SendShortMsg(MIDI_EVENT_PROGRAM_CHANGE, i, 0, 0);
|
||||
SendShortMsg(MIDI_EVENT_CONTROLLER, i, MIDI_CONTROLLER_REVERB, 40);
|
||||
SendShortMsg(MIDI_EVENT_CONTROLLER, i, MIDI_CONTROLLER_CHORUS, 0);
|
||||
SendControlChange(i, MIDI_CONTROLLER_RESET_ALL_CTRLS, 0);
|
||||
SendControlChange(i, MIDI_CONTROLLER_PAN, 64);
|
||||
SendControlChange(i, MIDI_CONTROLLER_BANK_SELECT_MSB, 0);
|
||||
SendControlChange(i, MIDI_CONTROLLER_BANK_SELECT_LSB, 0);
|
||||
SendProgramChange(i, 0);
|
||||
SendControlChange(i, MIDI_CONTROLLER_REVERB, 40);
|
||||
SendControlChange(i, MIDI_CONTROLLER_CHORUS, 0);
|
||||
}
|
||||
}
|
||||
|
||||
@ -317,16 +354,29 @@ static void ResetPitchBendSensitivity(void)
|
||||
for (i = 0; i < MIDI_CHANNELS_PER_TRACK; ++i)
|
||||
{
|
||||
// Set RPN MSB/LSB to pitch bend sensitivity.
|
||||
SendShortMsg(MIDI_EVENT_CONTROLLER, i, MIDI_CONTROLLER_RPN_LSB, 0);
|
||||
SendShortMsg(MIDI_EVENT_CONTROLLER, i, MIDI_CONTROLLER_RPN_MSB, 0);
|
||||
SendControlChange(i, MIDI_CONTROLLER_RPN_LSB, MIDI_RPN_PITCH_BEND_SENS_LSB);
|
||||
SendControlChange(i, MIDI_CONTROLLER_RPN_MSB, MIDI_RPN_MSB);
|
||||
|
||||
// Reset pitch bend sensitivity to +/- 2 semitones and 0 cents.
|
||||
SendShortMsg(MIDI_EVENT_CONTROLLER, i, MIDI_CONTROLLER_DATA_ENTRY_MSB, 2);
|
||||
SendShortMsg(MIDI_EVENT_CONTROLLER, i, MIDI_CONTROLLER_DATA_ENTRY_LSB, 0);
|
||||
SendControlChange(i, MIDI_CONTROLLER_DATA_ENTRY_MSB, 2);
|
||||
SendControlChange(i, MIDI_CONTROLLER_DATA_ENTRY_LSB, 0);
|
||||
|
||||
// Set RPN MSB/LSB to null value after data entry.
|
||||
SendShortMsg(MIDI_EVENT_CONTROLLER, i, MIDI_CONTROLLER_RPN_LSB, 127);
|
||||
SendShortMsg(MIDI_EVENT_CONTROLLER, i, MIDI_CONTROLLER_RPN_MSB, 127);
|
||||
SendControlChange(i, MIDI_CONTROLLER_RPN_LSB, MIDI_RPN_NULL);
|
||||
SendControlChange(i, MIDI_CONTROLLER_RPN_MSB, MIDI_RPN_NULL);
|
||||
}
|
||||
}
|
||||
|
||||
// Wait time based on bytes sent to the device. For compatibility with hardware
|
||||
// devices (e.g. SC-55).
|
||||
|
||||
static void ResetDelayBytes(uint32_t length)
|
||||
{
|
||||
if (midi_reset_delay == -1)
|
||||
{
|
||||
// MIDI transfer period is 320 us per byte (MIDI 1.0 Electrical Spec
|
||||
// Update CA-033, page 2).
|
||||
I_SleepUS(320 * length);
|
||||
}
|
||||
}
|
||||
|
||||
@ -335,42 +385,58 @@ static void ResetPitchBendSensitivity(void)
|
||||
|
||||
static void ResetDevice(void)
|
||||
{
|
||||
// For notes/sound off called prior to this function.
|
||||
ResetDelayBytes(96);
|
||||
|
||||
MIDI_ResetFallback();
|
||||
use_fallback = false;
|
||||
|
||||
switch (midi_reset_type)
|
||||
{
|
||||
case RESET_TYPE_NONE:
|
||||
ResetControllers();
|
||||
case RESET_TYPE_NO_SYSEX:
|
||||
ResetNoSysEx();
|
||||
ResetDelayBytes(320);
|
||||
break;
|
||||
|
||||
case RESET_TYPE_GS:
|
||||
SendLongMsg(gs_reset, sizeof(gs_reset));
|
||||
MIDI_SendLongMsg(gs_reset, sizeof(gs_reset));
|
||||
ResetDelayBytes(sizeof(gs_reset));
|
||||
use_fallback = (midi_complevel != COMP_VANILLA);
|
||||
break;
|
||||
|
||||
case RESET_TYPE_XG:
|
||||
SendLongMsg(xg_system_on, sizeof(xg_system_on));
|
||||
MIDI_SendLongMsg(xg_system_on, sizeof(xg_system_on));
|
||||
ResetDelayBytes(sizeof(xg_system_on));
|
||||
break;
|
||||
|
||||
default:
|
||||
SendLongMsg(gm_system_on, sizeof(gm_system_on));
|
||||
MIDI_SendLongMsg(gm_system_on, sizeof(gm_system_on));
|
||||
ResetDelayBytes(sizeof(gm_system_on));
|
||||
break;
|
||||
}
|
||||
|
||||
// Roland/Yamaha require an additional ~50 ms to execute a SysEx reset.
|
||||
if (midi_reset_delay == -1 && midi_reset_type != RESET_TYPE_NO_SYSEX)
|
||||
{
|
||||
I_Sleep(60);
|
||||
}
|
||||
|
||||
// MS GS Wavetable Synth doesn't reset pitch bend sensitivity.
|
||||
ResetPitchBendSensitivity();
|
||||
ResetDelayBytes(288);
|
||||
|
||||
// Reset volume (initial playback or on shutdown if no SysEx reset).
|
||||
// Scale by slider on initial playback, max on shutdown.
|
||||
if (midi_state == STATE_STARTUP)
|
||||
{
|
||||
ResetVolume();
|
||||
ResetDelayBytes(48);
|
||||
}
|
||||
else if (midi_reset_type == RESET_TYPE_NONE)
|
||||
else if (midi_reset_type == RESET_TYPE_NO_SYSEX)
|
||||
{
|
||||
volume_factor = 1.0f;
|
||||
ResetVolume();
|
||||
ResetDelayBytes(48);
|
||||
}
|
||||
|
||||
// Send delay after reset. This is for hardware devices only (e.g. SC-55).
|
||||
@ -380,221 +446,31 @@ static void ResetDevice(void)
|
||||
}
|
||||
}
|
||||
|
||||
// Normally, volume is controlled by channel volume messages. Roland defined a
|
||||
// special SysEx message called "part level" that is equivalent to this. MS GS
|
||||
// Wavetable Synth ignores these messages, but other MIDI devices support them.
|
||||
// Returns true if there is a match.
|
||||
|
||||
static boolean IsPartLevel(const byte *msg, unsigned int length)
|
||||
{
|
||||
if (length == 10 &&
|
||||
msg[0] == 0x41 && // Roland
|
||||
msg[2] == 0x42 && // GS
|
||||
msg[3] == 0x12 && // DT1
|
||||
msg[4] == 0x40 && // Address MSB
|
||||
msg[5] >= 0x10 && // Address
|
||||
msg[5] <= 0x1F && // Address
|
||||
msg[6] == 0x19 && // Address LSB
|
||||
msg[9] == 0xF7) // SysEx EOX
|
||||
{
|
||||
const byte checksum = 128 - ((int)msg[4] + msg[5] + msg[6] + msg[7]) % 128;
|
||||
|
||||
if (msg[8] == checksum)
|
||||
{
|
||||
// GS Part Level (aka Channel Volume)
|
||||
// 41 <dev> 42 12 40 <ch> 19 <vol> <sum> F7
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// Checks if the current SysEx message matches any known SysEx reset message.
|
||||
// Returns true if there is a match.
|
||||
|
||||
static boolean IsSysExReset(const byte *msg, unsigned int length)
|
||||
{
|
||||
if (length < 5)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
switch (msg[0])
|
||||
{
|
||||
case 0x41: // Roland
|
||||
switch (msg[2])
|
||||
{
|
||||
case 0x42: // GS
|
||||
switch (msg[3])
|
||||
{
|
||||
case 0x12: // DT1
|
||||
if (length == 10 &&
|
||||
msg[4] == 0x00 && // Address MSB
|
||||
msg[5] == 0x00 && // Address
|
||||
msg[6] == 0x7F && // Address LSB
|
||||
((msg[7] == 0x00 && // Data (MODE-1)
|
||||
msg[8] == 0x01)
|
||||
|| // Checksum (MODE-1)
|
||||
(msg[7] == 0x01 && // Data (MODE-2)
|
||||
msg[8] == 0x00))) // Checksum (MODE-2)
|
||||
{
|
||||
// SC-88 System Mode Set
|
||||
// 41 <dev> 42 12 00 00 7F 00 01 F7 (MODE-1)
|
||||
// 41 <dev> 42 12 00 00 7F 01 00 F7 (MODE-2)
|
||||
return true;
|
||||
}
|
||||
else if (length == 10 &&
|
||||
msg[4] == 0x40 && // Address MSB
|
||||
msg[5] == 0x00 && // Address
|
||||
msg[6] == 0x7F && // Address LSB
|
||||
msg[7] == 0x00 && // Data (GS Reset)
|
||||
msg[8] == 0x41) // Checksum
|
||||
{
|
||||
// GS Reset
|
||||
// 41 <dev> 42 12 40 00 7F 00 41 F7
|
||||
return true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
break;
|
||||
|
||||
case 0x43: // Yamaha
|
||||
switch (msg[2])
|
||||
{
|
||||
case 0x2B: // TG300
|
||||
if (length == 9 &&
|
||||
msg[3] == 0x00 && // Start Address b20 - b14
|
||||
msg[4] == 0x00 && // Start Address b13 - b7
|
||||
msg[5] == 0x7F && // Start Address b6 - b0
|
||||
msg[6] == 0x00 && // Data
|
||||
msg[7] == 0x01) // Checksum
|
||||
{
|
||||
// TG300 All Parameter Reset
|
||||
// 43 <dev> 2B 00 00 7F 00 01 F7
|
||||
return true;
|
||||
}
|
||||
break;
|
||||
|
||||
case 0x4C: // XG
|
||||
if (length == 8 &&
|
||||
msg[3] == 0x00 && // Address High
|
||||
msg[4] == 0x00 && // Address Mid
|
||||
(msg[5] == 0x7E || // Address Low (System On)
|
||||
msg[5] == 0x7F)
|
||||
&& // Address Low (All Parameter Reset)
|
||||
msg[6] == 0x00) // Data
|
||||
{
|
||||
// XG System On, XG All Parameter Reset
|
||||
// 43 <dev> 4C 00 00 7E 00 F7
|
||||
// 43 <dev> 4C 00 00 7F 00 F7
|
||||
return true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
break;
|
||||
|
||||
case 0x7E: // Universal Non-Real Time
|
||||
switch (msg[2])
|
||||
{
|
||||
case 0x09: // General Midi
|
||||
if (length == 5
|
||||
&& (msg[3] == 0x01 || // GM System On
|
||||
msg[3] == 0x02 || // GM System Off
|
||||
msg[3] == 0x03)) // GM2 System On
|
||||
{
|
||||
// GM System On/Off, GM2 System On
|
||||
// 7E <dev> 09 01 F7
|
||||
// 7E <dev> 09 02 F7
|
||||
// 7E <dev> 09 03 F7
|
||||
return true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static void SendSysExMsg(const midi_event_t *event)
|
||||
{
|
||||
const byte *data = event->data.sysex.data;
|
||||
const unsigned int length = event->data.sysex.length;
|
||||
|
||||
if (IsPartLevel(data, length))
|
||||
switch (event->data.sysex.type)
|
||||
{
|
||||
byte channel;
|
||||
|
||||
// Convert "block number" to a channel number.
|
||||
if (data[5] == 0x10) // Channel 10
|
||||
{
|
||||
channel = 9;
|
||||
}
|
||||
else if (data[5] < 0x1A) // Channels 1-9
|
||||
{
|
||||
channel = (data[5] & 0x0F) - 1;
|
||||
}
|
||||
else // Channels 11-16
|
||||
{
|
||||
channel = data[5] & 0x0F;
|
||||
}
|
||||
|
||||
// Replace SysEx part level message with channel volume message.
|
||||
SendManualVolumeMsg(channel, data[7]);
|
||||
return;
|
||||
}
|
||||
|
||||
// Send the SysEx message.
|
||||
if (length + 1 > sysex_buffer_size)
|
||||
{
|
||||
sysex_buffer_size = length + 1;
|
||||
free(sysex_buffer);
|
||||
sysex_buffer = malloc(sysex_buffer_size);
|
||||
sysex_buffer[0] = MIDI_EVENT_SYSEX;
|
||||
}
|
||||
|
||||
memcpy(sysex_buffer + 1, data, length);
|
||||
SendLongMsg(sysex_buffer, length + 1);
|
||||
|
||||
if (IsSysExReset(data, length))
|
||||
{
|
||||
// SysEx reset also resets volume. Take the default channel volumes
|
||||
// and scale them by the user's volume slider.
|
||||
ResetVolume();
|
||||
|
||||
// Disable instrument fallback and give priority to MIDI file. Fallback
|
||||
// assumes GS (SC-55 level) and the MIDI file could be GM, GM2, XG, or
|
||||
// GS (SC-88 or higher). Preserve the composer's intent.
|
||||
if (use_fallback)
|
||||
{
|
||||
MIDI_ResetFallback();
|
||||
use_fallback = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Writes a MIDI program change message. If applicable, emulates capital tone
|
||||
// fallback to fix invalid instruments.
|
||||
|
||||
static void SendProgramMsg(byte channel, byte program,
|
||||
const midi_fallback_t *fallback)
|
||||
{
|
||||
switch ((int)fallback->type)
|
||||
{
|
||||
case FALLBACK_BANK_MSB:
|
||||
SendShortMsg(MIDI_EVENT_CONTROLLER, channel,
|
||||
MIDI_CONTROLLER_BANK_SELECT_MSB, fallback->value);
|
||||
SendShortMsg(MIDI_EVENT_PROGRAM_CHANGE, channel, program, 0);
|
||||
case MIDI_SYSEX_RESET:
|
||||
if (use_fallback)
|
||||
{
|
||||
MIDI_ResetFallback();
|
||||
use_fallback = false;
|
||||
}
|
||||
MIDI_SendLongMsg(data, length);
|
||||
ResetVolume();
|
||||
break;
|
||||
|
||||
case FALLBACK_DRUMS:
|
||||
SendShortMsg(MIDI_EVENT_PROGRAM_CHANGE, channel, fallback->value, 0);
|
||||
case MIDI_SYSEX_RHYTHM_PART:
|
||||
case MIDI_SYSEX_OTHER:
|
||||
MIDI_SendLongMsg(data, length);
|
||||
break;
|
||||
|
||||
default:
|
||||
SendShortMsg(MIDI_EVENT_PROGRAM_CHANGE, channel, program, 0);
|
||||
case MIDI_SYSEX_PART_LEVEL:
|
||||
// Replace SysEx part level message with channel volume message.
|
||||
SendManualVolumeMsg(event->data.sysex.channel, data[8]);
|
||||
break;
|
||||
}
|
||||
}
|
||||
@ -684,8 +560,8 @@ static void SendEMIDI(const midi_event_t *event, midi_track_t *track,
|
||||
if (track->emidi_program || track->elapsed_time < ticks_per_beat)
|
||||
{
|
||||
track->emidi_program = true;
|
||||
SendProgramMsg(event->data.channel.channel,
|
||||
event->data.channel.param2, fallback);
|
||||
SendProgramChangeCTF(event->data.channel.channel,
|
||||
event->data.channel.param2, fallback);
|
||||
}
|
||||
break;
|
||||
|
||||
@ -783,13 +659,12 @@ static void SendMetaMsg(const midi_event_t *event, midi_track_t *track)
|
||||
// ProcessEvent function for vanilla (DMX MPU-401) compatibility level. Do not
|
||||
// call this function directly. See the ProcessEvent function pointer.
|
||||
|
||||
static boolean ProcessEvent_Vanilla(const midi_event_t *event,
|
||||
midi_track_t *track)
|
||||
static void ProcessEvent_Vanilla(const midi_event_t *event, midi_track_t *track)
|
||||
{
|
||||
switch ((int)event->event_type)
|
||||
switch (event->event_type)
|
||||
{
|
||||
case MIDI_EVENT_SYSEX:
|
||||
return false;
|
||||
break;
|
||||
|
||||
case MIDI_EVENT_META:
|
||||
SendMetaMsg(event, track);
|
||||
@ -801,7 +676,7 @@ static boolean ProcessEvent_Vanilla(const midi_event_t *event,
|
||||
case MIDI_CONTROLLER_BANK_SELECT_MSB:
|
||||
case MIDI_CONTROLLER_BANK_SELECT_LSB:
|
||||
// DMX has broken bank select support and runs in GM mode.
|
||||
SendChannelMsg(event, false);
|
||||
SendChannelMsgZero(event);
|
||||
break;
|
||||
|
||||
case MIDI_CONTROLLER_MODULATION:
|
||||
@ -811,8 +686,6 @@ static boolean ProcessEvent_Vanilla(const midi_event_t *event,
|
||||
case MIDI_CONTROLLER_SOFT_PEDAL:
|
||||
case MIDI_CONTROLLER_REVERB:
|
||||
case MIDI_CONTROLLER_CHORUS:
|
||||
case MIDI_CONTROLLER_ALL_SOUND_OFF:
|
||||
case MIDI_CONTROLLER_ALL_NOTES_OFF:
|
||||
SendChannelMsg(event, true);
|
||||
break;
|
||||
|
||||
@ -822,7 +695,14 @@ static boolean ProcessEvent_Vanilla(const midi_event_t *event,
|
||||
|
||||
case MIDI_CONTROLLER_RESET_ALL_CTRLS:
|
||||
// MS GS Wavetable Synth resets volume if param2 isn't zero.
|
||||
SendChannelMsg(event, false);
|
||||
// Per MIDI 1.0 Spec, param2 must be zero.
|
||||
SendChannelMsgZero(event);
|
||||
break;
|
||||
|
||||
case MIDI_CONTROLLER_ALL_SOUND_OFF:
|
||||
case MIDI_CONTROLLER_ALL_NOTES_OFF:
|
||||
// Per MIDI 1.0 Spec, param2 must be zero.
|
||||
SendChannelMsgZero(event);
|
||||
break;
|
||||
|
||||
default:
|
||||
@ -843,15 +723,13 @@ static boolean ProcessEvent_Vanilla(const midi_event_t *event,
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// ProcessEvent function for standard and full MIDI compatibility levels. Do not
|
||||
// call this function directly. See the ProcessEvent function pointer.
|
||||
|
||||
static boolean ProcessEvent_Standard(const midi_event_t *event,
|
||||
midi_track_t *track)
|
||||
static void ProcessEvent_Standard(const midi_event_t *event,
|
||||
midi_track_t *track)
|
||||
{
|
||||
midi_fallback_t fallback = {FALLBACK_NONE, 0};
|
||||
|
||||
@ -867,19 +745,19 @@ static boolean ProcessEvent_Standard(const midi_event_t *event,
|
||||
{
|
||||
SendSysExMsg(event);
|
||||
}
|
||||
return false;
|
||||
return;
|
||||
|
||||
case MIDI_EVENT_META:
|
||||
SendMetaMsg(event, track);
|
||||
return true;
|
||||
return;
|
||||
}
|
||||
|
||||
if (track->emidi_designated && (EMIDI_DEVICE & ~track->emidi_device_flags))
|
||||
{
|
||||
return true;
|
||||
return;
|
||||
}
|
||||
|
||||
switch ((int)event->event_type)
|
||||
switch (event->event_type)
|
||||
{
|
||||
case MIDI_EVENT_CONTROLLER:
|
||||
switch (event->data.channel.param1)
|
||||
@ -894,15 +772,12 @@ static boolean ProcessEvent_Standard(const midi_event_t *event,
|
||||
case MIDI_CONTROLLER_SOFT_PEDAL:
|
||||
case MIDI_CONTROLLER_REVERB:
|
||||
case MIDI_CONTROLLER_CHORUS:
|
||||
case MIDI_CONTROLLER_ALL_SOUND_OFF:
|
||||
case MIDI_CONTROLLER_ALL_NOTES_OFF:
|
||||
case MIDI_CONTROLLER_POLY_MODE_OFF:
|
||||
case MIDI_CONTROLLER_POLY_MODE_ON:
|
||||
SendChannelMsg(event, true);
|
||||
break;
|
||||
|
||||
case MIDI_CONTROLLER_VOLUME_MSB:
|
||||
if (track->emidi_volume == 0)
|
||||
if (!track->emidi_volume)
|
||||
{
|
||||
SendVolumeMsg(event);
|
||||
}
|
||||
@ -912,8 +787,14 @@ static boolean ProcessEvent_Standard(const midi_event_t *event,
|
||||
break;
|
||||
|
||||
case MIDI_CONTROLLER_BANK_SELECT_LSB:
|
||||
SendChannelMsg(event,
|
||||
fallback.type != FALLBACK_BANK_LSB);
|
||||
if (fallback.type == FALLBACK_BANK_LSB)
|
||||
{
|
||||
SendChannelMsgZero(event);
|
||||
}
|
||||
else
|
||||
{
|
||||
SendChannelMsg(event, true);
|
||||
}
|
||||
break;
|
||||
|
||||
case MIDI_CONTROLLER_NRPN_LSB:
|
||||
@ -988,7 +869,24 @@ static boolean ProcessEvent_Standard(const midi_event_t *event,
|
||||
|
||||
case MIDI_CONTROLLER_RESET_ALL_CTRLS:
|
||||
// MS GS Wavetable Synth resets volume if param2 isn't zero.
|
||||
SendChannelMsg(event, false);
|
||||
// Per MIDI 1.0 Spec, param2 must be zero.
|
||||
SendChannelMsgZero(event);
|
||||
break;
|
||||
|
||||
case MIDI_CONTROLLER_ALL_SOUND_OFF:
|
||||
case MIDI_CONTROLLER_ALL_NOTES_OFF:
|
||||
case MIDI_CONTROLLER_POLY_MODE_ON:
|
||||
// Per MIDI 1.0 Spec, param2 must be zero.
|
||||
SendChannelMsgZero(event);
|
||||
break;
|
||||
|
||||
case MIDI_CONTROLLER_OMNI_MODE_OFF:
|
||||
case MIDI_CONTROLLER_OMNI_MODE_ON:
|
||||
if (midi_complevel == COMP_FULL)
|
||||
{
|
||||
// Per MIDI 1.0 Spec, param2 must be zero.
|
||||
SendChannelMsgZero(event);
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
@ -1007,10 +905,10 @@ static boolean ProcessEvent_Standard(const midi_event_t *event,
|
||||
break;
|
||||
|
||||
case MIDI_EVENT_PROGRAM_CHANGE:
|
||||
if (track->emidi_program == 0)
|
||||
if (!track->emidi_program)
|
||||
{
|
||||
SendProgramMsg(event->data.channel.channel,
|
||||
event->data.channel.param1, &fallback);
|
||||
SendProgramChangeCTF(event->data.channel.channel,
|
||||
event->data.channel.param1, &fallback);
|
||||
}
|
||||
break;
|
||||
|
||||
@ -1031,16 +929,14 @@ static boolean ProcessEvent_Standard(const midi_event_t *event,
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// Function pointer determined by the desired MIDI compatibility level. Set
|
||||
// during initialization by the main thread, then called from the MIDI thread
|
||||
// only.
|
||||
|
||||
static boolean (*ProcessEvent)(const midi_event_t *event,
|
||||
midi_track_t *track) = ProcessEvent_Standard;
|
||||
static void (*ProcessEvent)(const midi_event_t *event,
|
||||
midi_track_t *track) = ProcessEvent_Standard;
|
||||
|
||||
// Restarts a song that uses a Final Fantasy or RPG Maker loop point.
|
||||
|
||||
@ -1079,49 +975,6 @@ static void RestartTracks(void)
|
||||
RestartTimer(0);
|
||||
}
|
||||
|
||||
// The controllers "EMIDI track exclusion" and "RPG Maker loop point" share the
|
||||
// same number (CC#111) and are not compatible with each other. As a workaround,
|
||||
// allow an RPG Maker loop point only if no other EMIDI events are present. Call
|
||||
// this function before the song starts.
|
||||
|
||||
static boolean IsRPGLoop(void)
|
||||
{
|
||||
unsigned int i;
|
||||
unsigned int num_rpg_events = 0;
|
||||
unsigned int num_emidi_events = 0;
|
||||
midi_event_t *event = NULL;
|
||||
|
||||
for (i = 0; i < song.num_tracks; ++i)
|
||||
{
|
||||
while (MIDI_GetNextEvent(song.tracks[i].iter, &event))
|
||||
{
|
||||
if (event->event_type == MIDI_EVENT_CONTROLLER)
|
||||
{
|
||||
switch (event->data.channel.param1)
|
||||
{
|
||||
case EMIDI_CONTROLLER_TRACK_EXCLUSION:
|
||||
num_rpg_events++;
|
||||
break;
|
||||
|
||||
case EMIDI_CONTROLLER_TRACK_DESIGNATION:
|
||||
case EMIDI_CONTROLLER_PROGRAM_CHANGE:
|
||||
case EMIDI_CONTROLLER_VOLUME:
|
||||
case EMIDI_CONTROLLER_LOOP_BEGIN:
|
||||
case EMIDI_CONTROLLER_LOOP_END:
|
||||
case EMIDI_CONTROLLER_GLOBAL_LOOP_BEGIN:
|
||||
case EMIDI_CONTROLLER_GLOBAL_LOOP_END:
|
||||
num_emidi_events++;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
MIDI_RestartIterator(song.tracks[i].iter);
|
||||
}
|
||||
|
||||
return (num_rpg_events == 1 && num_emidi_events == 0);
|
||||
}
|
||||
|
||||
// Get the next event from the MIDI file, process it or return if the delta
|
||||
// time is > 0.
|
||||
|
||||
@ -1160,11 +1013,7 @@ static midi_state_t NextEvent(midi_position_t *position)
|
||||
}
|
||||
else if (song.looping)
|
||||
{
|
||||
for (int i = 0; i < MIDI_CHANNELS_PER_TRACK; ++i)
|
||||
{
|
||||
SendShortMsg(MIDI_EVENT_CONTROLLER, i,
|
||||
MIDI_CONTROLLER_RESET_ALL_CTRLS, 0);
|
||||
}
|
||||
ResetControllers();
|
||||
RestartTracks();
|
||||
return STATE_PLAYING;
|
||||
}
|
||||
@ -1201,6 +1050,57 @@ static midi_state_t NextEvent(midi_position_t *position)
|
||||
return STATE_WAITING;
|
||||
}
|
||||
|
||||
static boolean RegisterSong(void)
|
||||
{
|
||||
if (IsMid(song.lump_data, song.lump_length))
|
||||
{
|
||||
song.file = MIDI_LoadFile(song.lump_data, song.lump_length);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Assume a MUS file and try to convert
|
||||
MEMFILE *instream;
|
||||
MEMFILE *outstream;
|
||||
void *outbuf;
|
||||
size_t outbuf_len;
|
||||
|
||||
instream = mem_fopen_read(song.lump_data, song.lump_length);
|
||||
outstream = mem_fopen_write();
|
||||
|
||||
if (mus2mid(instream, outstream) == 0)
|
||||
{
|
||||
mem_get_buf(outstream, &outbuf, &outbuf_len);
|
||||
song.file = MIDI_LoadFile(outbuf, outbuf_len);
|
||||
}
|
||||
else
|
||||
{
|
||||
song.file = NULL;
|
||||
}
|
||||
|
||||
mem_fclose(instream);
|
||||
mem_fclose(outstream);
|
||||
}
|
||||
|
||||
if (song.file == NULL)
|
||||
{
|
||||
I_Printf(VB_ERROR, "I_MID_RegisterSong: Failed to load MID.");
|
||||
return false;
|
||||
}
|
||||
|
||||
ticks_per_beat = MIDI_GetFileTimeDivision(song.file);
|
||||
us_per_beat = MIDI_DEFAULT_TEMPO;
|
||||
|
||||
song.num_tracks = MIDI_NumTracks(song.file);
|
||||
song.tracks = calloc(song.num_tracks, sizeof(midi_track_t));
|
||||
for (uint16_t i = 0; i < song.num_tracks; i++)
|
||||
{
|
||||
song.tracks[i].iter = MIDI_IterateTrack(song.file, i);
|
||||
}
|
||||
|
||||
song.rpg_loop = MIDI_RPGLoop(song.file);
|
||||
return true;
|
||||
}
|
||||
|
||||
static int PlayerThread(void *unused)
|
||||
{
|
||||
SDL_SetThreadPriority(SDL_THREAD_PRIORITY_TIME_CRITICAL);
|
||||
@ -1224,8 +1124,12 @@ static int PlayerThread(void *unused)
|
||||
switch (midi_state)
|
||||
{
|
||||
case STATE_STARTUP:
|
||||
if (!RegisterSong())
|
||||
{
|
||||
midi_state = STATE_STOPPED;
|
||||
break;
|
||||
}
|
||||
ResetDevice();
|
||||
song.rpg_loop = IsRPGLoop();
|
||||
midi_state = STATE_PLAYING;
|
||||
RestartTimer(0);
|
||||
break;
|
||||
@ -1316,9 +1220,6 @@ static boolean I_MID_InitMusic(int device)
|
||||
: ProcessEvent_Standard;
|
||||
MIDI_InitFallback();
|
||||
|
||||
sysex_buffer = malloc(sysex_buffer_size);
|
||||
sysex_buffer[0] = MIDI_EVENT_SYSEX;
|
||||
|
||||
music_initialized = true;
|
||||
|
||||
I_Printf(VB_INFO, "MIDI Init: Using '%s'.", midi_devices[device]);
|
||||
@ -1419,51 +1320,8 @@ static void *I_MID_RegisterSong(void *data, int len)
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (IsMid(data, len))
|
||||
{
|
||||
song.file = MIDI_LoadFile(data, len);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Assume a MUS file and try to convert
|
||||
MEMFILE *instream;
|
||||
MEMFILE *outstream;
|
||||
void *outbuf;
|
||||
size_t outbuf_len;
|
||||
|
||||
instream = mem_fopen_read(data, len);
|
||||
outstream = mem_fopen_write();
|
||||
|
||||
if (mus2mid(instream, outstream) == 0)
|
||||
{
|
||||
mem_get_buf(outstream, &outbuf, &outbuf_len);
|
||||
song.file = MIDI_LoadFile(outbuf, outbuf_len);
|
||||
}
|
||||
else
|
||||
{
|
||||
song.file = NULL;
|
||||
}
|
||||
|
||||
mem_fclose(instream);
|
||||
mem_fclose(outstream);
|
||||
}
|
||||
|
||||
if (song.file == NULL)
|
||||
{
|
||||
I_Printf(VB_ERROR, "I_MID_RegisterSong: Failed to load MID.");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
ticks_per_beat = MIDI_GetFileTimeDivision(song.file);
|
||||
|
||||
us_per_beat = 500 * 1000; // Default is 120 bpm.
|
||||
|
||||
song.num_tracks = MIDI_NumTracks(song.file);
|
||||
song.tracks = calloc(song.num_tracks, sizeof(midi_track_t));
|
||||
for (int i = 0; i < song.num_tracks; ++i)
|
||||
{
|
||||
song.tracks[i].iter = MIDI_IterateTrack(song.file, i);
|
||||
}
|
||||
song.lump_data = data;
|
||||
song.lump_length = len;
|
||||
|
||||
return (void *)1;
|
||||
}
|
||||
@ -1491,6 +1349,8 @@ static void I_MID_UnRegisterSong(void *handle)
|
||||
MIDI_FreeFile(song.file);
|
||||
song.file = NULL;
|
||||
}
|
||||
song.lump_data = NULL;
|
||||
song.lump_length = 0;
|
||||
song.elapsed_time = 0;
|
||||
song.saved_elapsed_time = 0;
|
||||
song.num_tracks = 0;
|
||||
@ -1514,8 +1374,6 @@ static void I_MID_ShutdownMusic(void)
|
||||
|
||||
MIDI_CloseDevice();
|
||||
|
||||
free(sysex_buffer);
|
||||
|
||||
music_initialized = false;
|
||||
}
|
||||
|
||||
|
@ -1417,7 +1417,7 @@ static void InitChannel(opl_channel_data_t *channel)
|
||||
|
||||
channel->instrument = &main_instrs[0];
|
||||
channel->volume = current_music_volume;
|
||||
channel->volume_base = 100;
|
||||
channel->volume_base = MIDI_DEFAULT_VOLUME;
|
||||
if (channel->volume > channel->volume_base)
|
||||
{
|
||||
channel->volume = channel->volume_base;
|
||||
@ -1582,7 +1582,7 @@ static void I_OPL_PlayStream(boolean looping)
|
||||
// Default is 120 bpm.
|
||||
// TODO: this is wrong
|
||||
|
||||
us_per_beat = 500 * 1000;
|
||||
us_per_beat = MIDI_DEFAULT_TEMPO;
|
||||
|
||||
start_music_volume = current_music_volume;
|
||||
|
||||
|
@ -520,14 +520,14 @@ default_t defaults[] = {
|
||||
"midi_reset_type",
|
||||
(config_t *) &midi_reset_type, NULL,
|
||||
{1}, {0, 3}, number, ss_none, wad_no,
|
||||
"SysEx reset for native MIDI (0 = None, 1 = GM, 2 = GS, 3 = XG)"
|
||||
"Reset type for native MIDI (0 = No SysEx, 1 = GM, 2 = GS, 3 = XG)"
|
||||
},
|
||||
|
||||
{
|
||||
"midi_reset_delay",
|
||||
(config_t *) &midi_reset_delay, NULL,
|
||||
{0}, {0, 2000}, number, ss_none, wad_no,
|
||||
"Delay after reset for native MIDI (milliseconds)"
|
||||
{-1}, {-1, 2000}, number, ss_none, wad_no,
|
||||
"Delay after reset for native MIDI (-1 = Auto, 0 = None, 1-2000 = Milliseconds)"
|
||||
},
|
||||
|
||||
//
|
||||
|
@ -40,53 +40,6 @@ static byte bank_msb[MIDI_CHANNELS_PER_TRACK];
|
||||
static byte drum_map[MIDI_CHANNELS_PER_TRACK];
|
||||
static boolean selected[MIDI_CHANNELS_PER_TRACK];
|
||||
|
||||
static void UpdateDrumMap(const byte *msg, unsigned int length)
|
||||
{
|
||||
byte idx;
|
||||
byte checksum;
|
||||
|
||||
// GS allows drums on any channel using SysEx messages.
|
||||
// The message format is F0 followed by:
|
||||
//
|
||||
// 41 10 42 12 40 <ch> 15 <map> <sum> F7
|
||||
//
|
||||
// <ch> is [11-19, 10, 1A-1F] for channels 1-16. Note the position of 10.
|
||||
// <map> is 00-02 for off (normal part), drum map 1, or drum map 2.
|
||||
// <sum> is checksum.
|
||||
|
||||
if (length == 10 &&
|
||||
msg[0] == 0x41 && // Roland
|
||||
msg[1] == 0x10 && // Device ID
|
||||
msg[2] == 0x42 && // GS
|
||||
msg[3] == 0x12 && // DT1
|
||||
msg[4] == 0x40 && // Address MSB
|
||||
msg[6] == 0x15 && // Address LSB
|
||||
msg[9] == 0xF7) // SysEx EOX
|
||||
{
|
||||
checksum = 128 - ((int)msg[4] + msg[5] + msg[6] + msg[7]) % 128;
|
||||
|
||||
if (msg[8] != checksum)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (msg[5] == 0x10) // Channel 10
|
||||
{
|
||||
idx = 9;
|
||||
}
|
||||
else if (msg[5] < 0x1A) // Channels 1-9
|
||||
{
|
||||
idx = (msg[5] & 0x0F) - 1;
|
||||
}
|
||||
else // Channels 11-16
|
||||
{
|
||||
idx = msg[5] & 0x0F;
|
||||
}
|
||||
|
||||
drum_map[idx] = msg[7];
|
||||
}
|
||||
}
|
||||
|
||||
static boolean GetProgramFallback(byte idx, byte program,
|
||||
midi_fallback_t *fallback)
|
||||
{
|
||||
@ -164,9 +117,15 @@ void MIDI_CheckFallback(const midi_event_t *event, midi_fallback_t *fallback,
|
||||
switch ((int)event->event_type)
|
||||
{
|
||||
case MIDI_EVENT_SYSEX:
|
||||
if (allow_sysex)
|
||||
if (allow_sysex && event->data.sysex.type == MIDI_SYSEX_RHYTHM_PART)
|
||||
{
|
||||
UpdateDrumMap(event->data.sysex.data, event->data.sysex.length);
|
||||
// GS allows drums on any channel using SysEx messages.
|
||||
// The message format is:
|
||||
// F0 41 10 42 12 40 <ch> 15 <map> <sum> F7
|
||||
// <ch> is [11-19, 10, 1A-1F] for channels 1-16.
|
||||
// <map> is 00-02 for off (normal part) or drum map 1/2.
|
||||
// <sum> is checksum.
|
||||
drum_map[event->data.sysex.channel] = event->data.sysex.data[8];
|
||||
}
|
||||
break;
|
||||
|
||||
|
216
src/midifile.c
216
src/midifile.c
@ -27,7 +27,6 @@
|
||||
|
||||
#define HEADER_CHUNK_ID "MThd"
|
||||
#define TRACK_CHUNK_ID "MTrk"
|
||||
#define MAX_BUFFER_SIZE 0x10000
|
||||
|
||||
// haleyjd 09/09/10: packing required
|
||||
#if defined(_MSC_VER)
|
||||
@ -81,9 +80,11 @@ struct midi_file_s
|
||||
midi_track_t *tracks;
|
||||
unsigned int num_tracks;
|
||||
|
||||
// Data buffer used to store data read for SysEx or meta events:
|
||||
byte *buffer;
|
||||
unsigned int buffer_size;
|
||||
// Number of RPG Maker loop events.
|
||||
unsigned int num_rpg_events;
|
||||
|
||||
// Number of EMIDI events, without track exclusion.
|
||||
unsigned int num_emidi_events;
|
||||
};
|
||||
|
||||
// Check the header of a chunk:
|
||||
@ -239,9 +240,13 @@ static boolean ReadChannelEvent(midi_event_t *event, byte event_type,
|
||||
|
||||
// Read sysex event:
|
||||
|
||||
static midi_sysex_type_t GetSysExType(midi_event_t *event);
|
||||
|
||||
static boolean ReadSysExEvent(midi_event_t *event, int event_type,
|
||||
MEMFILE *stream)
|
||||
{
|
||||
unsigned int i;
|
||||
|
||||
event->event_type = event_type;
|
||||
|
||||
if (!ReadVariableLength(&event->data.sysex.length, stream))
|
||||
@ -253,14 +258,28 @@ static boolean ReadSysExEvent(midi_event_t *event, int event_type,
|
||||
|
||||
// Read the byte sequence:
|
||||
|
||||
event->data.sysex.data = ReadByteSequence(event->data.sysex.length, stream);
|
||||
event->data.sysex.length++; // Extra byte for event type.
|
||||
event->data.sysex.data = malloc(event->data.sysex.length);
|
||||
|
||||
if (event->data.sysex.data == NULL)
|
||||
{
|
||||
I_Printf(VB_ERROR, "ReadSysExEvent: Failed while reading SysEx event");
|
||||
I_Printf(VB_ERROR, "ReadSysExEvent: Failed to allocate buffer");
|
||||
return false;
|
||||
}
|
||||
|
||||
event->data.sysex.data[0] = event->event_type;
|
||||
|
||||
for (i = 1; i < event->data.sysex.length; i++)
|
||||
{
|
||||
if (!ReadByte(&event->data.sysex.data[i], stream))
|
||||
{
|
||||
I_Printf(VB_ERROR, "ReadSysExEvent: Failed to read event");
|
||||
free(event->data.sysex.data);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
event->data.sysex.type = GetSysExType(event);
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -286,8 +305,8 @@ static boolean ReadMetaEvent(midi_event_t *event, MEMFILE *stream)
|
||||
|
||||
if (!ReadVariableLength(&event->data.meta.length, stream))
|
||||
{
|
||||
I_Printf(VB_ERROR, "ReadSysExEvent: Failed to read length of "
|
||||
"SysEx block");
|
||||
I_Printf(VB_ERROR, "ReadMetaEvent: Failed to read length of "
|
||||
"meta event block");
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -297,7 +316,7 @@ static boolean ReadMetaEvent(midi_event_t *event, MEMFILE *stream)
|
||||
|
||||
if (event->data.meta.data == NULL)
|
||||
{
|
||||
I_Printf(VB_ERROR, "ReadSysExEvent: Failed while reading SysEx event");
|
||||
I_Printf(VB_ERROR, "ReadMetaEvent: Failed while reading meta event");
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -431,7 +450,35 @@ static boolean ReadTrackHeader(midi_track_t *track, MEMFILE *stream)
|
||||
return true;
|
||||
}
|
||||
|
||||
static boolean ReadTrack(midi_track_t *track, MEMFILE *stream)
|
||||
// "EMIDI track exclusion" and "RPG Maker loop point" share the same controller
|
||||
// number (CC#111) and are not compatible with each other. Count these events
|
||||
// and allow an RPG Maker loop point only if no other EMIDI events are present.
|
||||
|
||||
static void CheckUndefinedEvent(midi_file_t *file, const midi_event_t *event)
|
||||
{
|
||||
if (event->event_type == MIDI_EVENT_CONTROLLER)
|
||||
{
|
||||
switch (event->data.channel.param1)
|
||||
{
|
||||
case EMIDI_CONTROLLER_TRACK_EXCLUSION:
|
||||
file->num_rpg_events++;
|
||||
break;
|
||||
|
||||
case EMIDI_CONTROLLER_TRACK_DESIGNATION:
|
||||
case EMIDI_CONTROLLER_PROGRAM_CHANGE:
|
||||
case EMIDI_CONTROLLER_VOLUME:
|
||||
case EMIDI_CONTROLLER_LOOP_BEGIN:
|
||||
case EMIDI_CONTROLLER_LOOP_END:
|
||||
case EMIDI_CONTROLLER_GLOBAL_LOOP_BEGIN:
|
||||
case EMIDI_CONTROLLER_GLOBAL_LOOP_END:
|
||||
file->num_emidi_events++;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static boolean ReadTrack(midi_file_t *file, midi_track_t *track,
|
||||
MEMFILE *stream)
|
||||
{
|
||||
midi_event_t *new_events = NULL;
|
||||
midi_event_t *event;
|
||||
@ -484,6 +531,7 @@ static boolean ReadTrack(midi_track_t *track, MEMFILE *stream)
|
||||
}
|
||||
|
||||
++track->num_events;
|
||||
CheckUndefinedEvent(file, event);
|
||||
|
||||
// End of track?
|
||||
|
||||
@ -530,7 +578,7 @@ static boolean ReadAllTracks(midi_file_t *file, MEMFILE *stream)
|
||||
|
||||
for (i = 0; i < file->num_tracks; ++i)
|
||||
{
|
||||
if (!ReadTrack(&file->tracks[i], stream))
|
||||
if (!ReadTrack(file, &file->tracks[i], stream))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
@ -607,8 +655,8 @@ midi_file_t *MIDI_LoadFile(void *buf, size_t buflen)
|
||||
|
||||
file->tracks = NULL;
|
||||
file->num_tracks = 0;
|
||||
file->buffer = NULL;
|
||||
file->buffer_size = 0;
|
||||
file->num_rpg_events = 0;
|
||||
file->num_emidi_events = 0;
|
||||
|
||||
// Open file
|
||||
|
||||
@ -736,3 +784,145 @@ void MIDI_RestartAtLoopPoint(midi_track_iter_t *iter)
|
||||
{
|
||||
iter->position = iter->loop_point;
|
||||
}
|
||||
|
||||
boolean MIDI_RPGLoop(const midi_file_t *file)
|
||||
{
|
||||
return (file->num_rpg_events == 1 && file->num_emidi_events == 0);
|
||||
}
|
||||
|
||||
static boolean RolandChecksum(const byte *data)
|
||||
{
|
||||
const byte checksum =
|
||||
128 - ((int)data[5] + data[6] + data[7] + data[8]) % 128;
|
||||
return (data[9] == checksum);
|
||||
}
|
||||
|
||||
static void RolandBlockToChannel(midi_event_t *event)
|
||||
{
|
||||
const byte *data = event->data.sysex.data;
|
||||
unsigned int *channel = &event->data.sysex.channel;
|
||||
|
||||
// Convert Roland "block number" to a channel number.
|
||||
// [11-19, 10, 1A-1F] for channels 1-16. Note the position of 10.
|
||||
|
||||
if (data[6] == 0x10) // Channel 10
|
||||
{
|
||||
*channel = 9;
|
||||
}
|
||||
else if (data[6] < 0x1A) // Channels 1-9
|
||||
{
|
||||
*channel = (data[6] & 0x0F) - 1;
|
||||
}
|
||||
else // Channels 11-16
|
||||
{
|
||||
*channel = data[6] & 0x0F;
|
||||
}
|
||||
}
|
||||
|
||||
static midi_sysex_type_t GetSysExType(midi_event_t *event)
|
||||
{
|
||||
const byte *data = event->data.sysex.data;
|
||||
const unsigned int length = event->data.sysex.length;
|
||||
unsigned int address;
|
||||
|
||||
if (length < 2 || data[0] != MIDI_EVENT_SYSEX
|
||||
|| data[length - 1] != MIDI_EVENT_SYSEX_SPLIT)
|
||||
{
|
||||
return MIDI_SYSEX_UNSUPPORTED;
|
||||
}
|
||||
|
||||
if (length < 6)
|
||||
{
|
||||
return MIDI_SYSEX_OTHER;
|
||||
}
|
||||
|
||||
switch (data[1])
|
||||
{
|
||||
case 0x7E: // Universal Non-Real Time
|
||||
if (length == 6 && data[3] == 0x09 // General Midi
|
||||
&& (data[4] == 0x01 || data[4] == 0x02 || data[4] == 0x03))
|
||||
{
|
||||
// GM System On/Off, GM2 System On
|
||||
// F0 7E <dev> 09 01 F7
|
||||
// F0 7E <dev> 09 02 F7
|
||||
// F0 7E <dev> 09 03 F7
|
||||
return MIDI_SYSEX_RESET;
|
||||
}
|
||||
break;
|
||||
|
||||
case 0x41: // Roland
|
||||
if (length == 11 && data[3] == 0x42 && data[4] == 0x12) // GS DT1
|
||||
{
|
||||
address = (data[5] << 16) | ((data[6] & 0xF0) << 8) | data[7];
|
||||
switch (address)
|
||||
{
|
||||
case 0x40007F: // Mode Set
|
||||
if (data[8] == 0x00 && data[9] == 0x41)
|
||||
{
|
||||
// GS Reset
|
||||
// F0 41 <dev> 42 12 40 00 7F 00 41 F7
|
||||
return MIDI_SYSEX_RESET;
|
||||
}
|
||||
break;
|
||||
|
||||
case 0x00007F: // System Mode Set
|
||||
if ((data[8] == 0x00 && data[9] == 0x01)
|
||||
|| (data[8] == 0x01 && data[9] == 0x00))
|
||||
{
|
||||
// System Mode Set MODE-1, MODE-2
|
||||
// F0 41 <dev> 42 12 00 00 7F 00 01 F7
|
||||
// F0 41 <dev> 42 12 00 00 7F 01 00 F7
|
||||
return MIDI_SYSEX_RESET;
|
||||
}
|
||||
break;
|
||||
|
||||
case 0x401015: // Use for Rhythm Part
|
||||
if (RolandChecksum(data))
|
||||
{
|
||||
// Use for Rhythm Part (Drum Map)
|
||||
// F0 41 <dev> 42 12 40 <ch> 15 <map> <sum> F7
|
||||
RolandBlockToChannel(event);
|
||||
return MIDI_SYSEX_RHYTHM_PART;
|
||||
}
|
||||
break;
|
||||
|
||||
case 0x401019: // Part Level
|
||||
if (RolandChecksum(data))
|
||||
{
|
||||
// Part Level (Channel Volume)
|
||||
// F0 41 <dev> 42 12 40 <ch> 19 <vol> <sum> F7
|
||||
RolandBlockToChannel(event);
|
||||
return MIDI_SYSEX_PART_LEVEL;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case 0x43: // Yamaha
|
||||
if (length == 9 && data[3] == 0x4C) // XG
|
||||
{
|
||||
address = (data[4] << 16) | (data[5] << 8) | data[6];
|
||||
if ((address == 0x7E || address == 0x7F) && data[7] == 0x00)
|
||||
{
|
||||
// XG System On, XG All Parameter Reset
|
||||
// F0 43 <dev> 4C 00 00 7E 00 F7
|
||||
// F0 43 <dev> 4C 00 00 7F 00 F7
|
||||
return MIDI_SYSEX_RESET;
|
||||
}
|
||||
}
|
||||
else if (length == 10 && data[3] == 0x2B) // TG300
|
||||
{
|
||||
address = (data[4] << 16) | (data[5] << 8) | data[6];
|
||||
if (address == 0x7F && data[7] == 0x00 && data[8] == 0x01)
|
||||
{
|
||||
// TG300 All Parameter Reset
|
||||
// F0 43 <dev> 2B 00 00 7F 00 01 F7
|
||||
return MIDI_SYSEX_RESET;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
return MIDI_SYSEX_OTHER;
|
||||
}
|
||||
|
@ -24,6 +24,8 @@ typedef struct midi_file_s midi_file_t;
|
||||
typedef struct midi_track_iter_s midi_track_iter_t;
|
||||
|
||||
#define MIDI_CHANNELS_PER_TRACK 16
|
||||
#define MIDI_DEFAULT_VOLUME 100
|
||||
#define MIDI_DEFAULT_TEMPO 500000 // 120 bpm
|
||||
|
||||
#define MIDI_RPN_MSB 0x00
|
||||
#define MIDI_RPN_PITCH_BEND_SENS_LSB 0x00
|
||||
@ -77,6 +79,8 @@ typedef enum
|
||||
MIDI_CONTROLLER_RESET_ALL_CTRLS = 0x79,
|
||||
MIDI_CONTROLLER_ALL_NOTES_OFF = 0x7B,
|
||||
|
||||
MIDI_CONTROLLER_OMNI_MODE_OFF = 0x7C,
|
||||
MIDI_CONTROLLER_OMNI_MODE_ON = 0x7D,
|
||||
MIDI_CONTROLLER_POLY_MODE_OFF = 0x7E,
|
||||
MIDI_CONTROLLER_POLY_MODE_ON = 0x7F,
|
||||
} midi_controller_t;
|
||||
@ -132,6 +136,15 @@ typedef enum
|
||||
EMIDI_CONTROLLER_GLOBAL_LOOP_END = 0x77,
|
||||
} emidi_controller_t;
|
||||
|
||||
typedef enum
|
||||
{
|
||||
MIDI_SYSEX_UNSUPPORTED,
|
||||
MIDI_SYSEX_RESET,
|
||||
MIDI_SYSEX_RHYTHM_PART,
|
||||
MIDI_SYSEX_PART_LEVEL,
|
||||
MIDI_SYSEX_OTHER,
|
||||
} midi_sysex_type_t;
|
||||
|
||||
typedef struct
|
||||
{
|
||||
// Meta event type:
|
||||
@ -149,6 +162,9 @@ typedef struct
|
||||
|
||||
typedef struct
|
||||
{
|
||||
unsigned int type;
|
||||
unsigned int channel;
|
||||
|
||||
// Length:
|
||||
|
||||
unsigned int length;
|
||||
@ -230,4 +246,8 @@ void MIDI_SetLoopPoint(midi_track_iter_t *iter);
|
||||
|
||||
void MIDI_RestartAtLoopPoint(midi_track_iter_t *iter);
|
||||
|
||||
// Check if this MIDI file contains a valid RPG Maker loop point.
|
||||
|
||||
boolean MIDI_RPGLoop(const midi_file_t *file);
|
||||
|
||||
#endif /* #ifndef MIDIFILE_H */
|
||||
|
Loading…
x
Reference in New Issue
Block a user