mirror of
https://github.com/fabiangreffrath/woof.git
synced 2025-09-24 04:29:34 -04:00
winmidi: Optimize reset messages, change volume control, add instrument fallback support (#834)
* Cosmetic changes. * Add volume safeguards. * Use device setting for reverb and chorus by default. * Clean up volume change behavior. * Move "all notes off" and "reset all controllers" to reset function. * Optimize set of reset messages depending on device and reset type. * Ignore SysEx resets from MIDI files for MS GS Wavetable Synth. * Check for Roland master volume combined with master tune, etc. * Check for Yamaha master volume combined with master tune, etc. * Don't replace master volume message; send a new one. * Change order of volume check. * Switch from master volume to channel volume control for better compatibility with MIDI devices. * Change default `winmm_reset_delay` to 0. * Change default winmm_reset_delay to 0. * Remove unused variable. * Add GS instrument fallback support. * Cast `event_type` as `int` * Fix switch/case. * Fix hot swapping. * Remove unneeded `return`. * Add instrument fallback support for all devices in GS mode. * Assign reset defaults to all devices. * Fix formatting.
This commit is contained in:
parent
61302e811f
commit
72ddbe8ffc
@ -53,6 +53,7 @@ set(WOOF_SOURCES
|
|||||||
m_snapshot.c m_snapshot.h
|
m_snapshot.c m_snapshot.h
|
||||||
m_swap.h
|
m_swap.h
|
||||||
memio.c memio.h
|
memio.c memio.h
|
||||||
|
midifallback.c midifallback.h
|
||||||
midifile.c midifile.h
|
midifile.c midifile.h
|
||||||
mus2mid.c mus2mid.h
|
mus2mid.c mus2mid.h
|
||||||
net_client.c net_client.h
|
net_client.c net_client.h
|
||||||
|
462
src/i_winmusic.c
462
src/i_winmusic.c
@ -19,6 +19,7 @@
|
|||||||
#define WIN32_LEAN_AND_MEAN
|
#define WIN32_LEAN_AND_MEAN
|
||||||
#include <windows.h>
|
#include <windows.h>
|
||||||
#include <mmsystem.h>
|
#include <mmsystem.h>
|
||||||
|
#include <mmreg.h>
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <math.h>
|
#include <math.h>
|
||||||
@ -30,11 +31,21 @@
|
|||||||
#include "memio.h"
|
#include "memio.h"
|
||||||
#include "mus2mid.h"
|
#include "mus2mid.h"
|
||||||
#include "midifile.h"
|
#include "midifile.h"
|
||||||
|
#include "midifallback.h"
|
||||||
|
|
||||||
int winmm_reset_type = 2;
|
int winmm_reset_type = -1;
|
||||||
int winmm_reset_delay = 100;
|
int winmm_reset_delay = 0;
|
||||||
int winmm_reverb_level = 40;
|
int winmm_reverb_level = -1;
|
||||||
int winmm_chorus_level = 0;
|
int winmm_chorus_level = -1;
|
||||||
|
|
||||||
|
enum
|
||||||
|
{
|
||||||
|
RESET_TYPE_NONE,
|
||||||
|
RESET_TYPE_GS,
|
||||||
|
RESET_TYPE_GM,
|
||||||
|
RESET_TYPE_GM2,
|
||||||
|
RESET_TYPE_XG,
|
||||||
|
};
|
||||||
|
|
||||||
static byte gs_reset[] = {
|
static byte gs_reset[] = {
|
||||||
0xF0, 0x41, 0x10, 0x42, 0x12, 0x40, 0x00, 0x7F, 0x00, 0x41, 0xF7
|
0xF0, 0x41, 0x10, 0x42, 0x12, 0x40, 0x00, 0x7F, 0x00, 0x41, 0xF7
|
||||||
@ -52,14 +63,12 @@ static byte xg_system_on[] = {
|
|||||||
0xF0, 0x43, 0x10, 0x4C, 0x00, 0x00, 0x7E, 0x00, 0xF7
|
0xF0, 0x43, 0x10, 0x4C, 0x00, 0x00, 0x7E, 0x00, 0xF7
|
||||||
};
|
};
|
||||||
|
|
||||||
static byte master_volume_msg[] = {
|
static boolean use_fallback;
|
||||||
0xF0, 0x7F, 0x7F, 0x04, 0x01, 0x7F, 0x7F, 0xF7
|
|
||||||
};
|
|
||||||
|
|
||||||
#define DEFAULT_MASTER_VOLUME 16383
|
#define DEFAULT_VOLUME 100
|
||||||
static unsigned int master_volume = DEFAULT_MASTER_VOLUME;
|
static int channel_volume[MIDI_CHANNELS_PER_TRACK];
|
||||||
static int last_volume = -1;
|
static int last_volume = -1;
|
||||||
static float volume_factor = 1.0f;
|
static float volume_factor = 0.0f;
|
||||||
static boolean update_volume = false;
|
static boolean update_volume = false;
|
||||||
|
|
||||||
static DWORD timediv;
|
static DWORD timediv;
|
||||||
@ -72,7 +81,7 @@ static HANDLE hBufferReturnEvent;
|
|||||||
static HANDLE hExitEvent;
|
static HANDLE hExitEvent;
|
||||||
static HANDLE hPlayerThread;
|
static HANDLE hPlayerThread;
|
||||||
|
|
||||||
// MS GS Wavetable Syhth id
|
// MS GS Wavetable Synth Device ID.
|
||||||
static int ms_gs_synth = MIDI_MAPPER;
|
static int ms_gs_synth = MIDI_MAPPER;
|
||||||
|
|
||||||
// This is a reduced Windows MIDIEVENT structure for MEVT_F_SHORT
|
// This is a reduced Windows MIDIEVENT structure for MEVT_F_SHORT
|
||||||
@ -244,12 +253,11 @@ static void SendNOPMsg(int time)
|
|||||||
WriteBuffer((byte *)&native_event, sizeof(native_event_t));
|
WriteBuffer((byte *)&native_event, sizeof(native_event_t));
|
||||||
}
|
}
|
||||||
|
|
||||||
static void SendDelayMsg(void)
|
static void SendDelayMsg(int time_ms)
|
||||||
{
|
{
|
||||||
// Convert ms to ticks (see "Standard MIDI Files 1.0" page 14).
|
// Convert ms to ticks (see "Standard MIDI Files 1.0" page 14).
|
||||||
int ticks = (float)winmm_reset_delay * 1000 * timediv / tempo + 0.5f;
|
int time_ticks = (float)time_ms * 1000 * timediv / tempo + 0.5f;
|
||||||
|
SendNOPMsg(time_ticks);
|
||||||
SendNOPMsg(ticks);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static void UpdateTempo(int time)
|
static void UpdateTempo(int time)
|
||||||
@ -261,78 +269,209 @@ static void UpdateTempo(int time)
|
|||||||
WriteBuffer((byte *)&native_event, sizeof(native_event_t));
|
WriteBuffer((byte *)&native_event, sizeof(native_event_t));
|
||||||
}
|
}
|
||||||
|
|
||||||
static void UpdateVolume(int time)
|
static void SendVolumeMsg(int time, int channel)
|
||||||
{
|
{
|
||||||
int volume = master_volume * volume_factor + 0.5f;
|
int volume = channel_volume[channel] * volume_factor + 0.5f;
|
||||||
master_volume_msg[5] = volume & 0x7F;
|
SendShortMsg(time, MIDI_EVENT_CONTROLLER, channel,
|
||||||
master_volume_msg[6] = (volume >> 7) & 0x7F;
|
MIDI_CONTROLLER_MAIN_VOLUME, volume);
|
||||||
SendLongMsg(time, master_volume_msg, sizeof(master_volume_msg));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static void ResetDevice(void)
|
static void UpdateVolume(void)
|
||||||
{
|
{
|
||||||
int i;
|
int i;
|
||||||
|
|
||||||
for (i = 0; i < MIDI_CHANNELS_PER_TRACK; ++i)
|
for (i = 0; i < MIDI_CHANNELS_PER_TRACK; ++i)
|
||||||
{
|
{
|
||||||
// midiOutReset() sends "all notes off" and "reset all controllers."
|
SendVolumeMsg(0, i);
|
||||||
SendShortMsg(0, MIDI_EVENT_CONTROLLER, i, MIDI_CONTROLLER_ALL_SOUND_OFF, 0);
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Reset pitch bend sensitivity to +/- 2 semitones and 0 cents.
|
static void ResetVolume(void)
|
||||||
SendShortMsg(0, MIDI_EVENT_CONTROLLER, i, MIDI_CONTROLLER_RPN_MSB, 0);
|
{
|
||||||
SendShortMsg(0, MIDI_EVENT_CONTROLLER, i, MIDI_CONTROLLER_RPN_LSB, 0);
|
int i;
|
||||||
SendShortMsg(0, MIDI_EVENT_CONTROLLER, i, MIDI_CONTROLLER_DATA_ENTRY_MSB, 2);
|
|
||||||
SendShortMsg(0, MIDI_EVENT_CONTROLLER, i, MIDI_CONTROLLER_DATA_ENTRY_LSB, 0);
|
|
||||||
SendShortMsg(0, MIDI_EVENT_CONTROLLER, i, MIDI_CONTROLLER_RPN_MSB, 127);
|
|
||||||
SendShortMsg(0, MIDI_EVENT_CONTROLLER, i, MIDI_CONTROLLER_RPN_LSB, 127);
|
|
||||||
|
|
||||||
// Reset channel volume and pan.
|
for (i = 0; i < MIDI_CHANNELS_PER_TRACK; ++i)
|
||||||
SendShortMsg(0, MIDI_EVENT_CONTROLLER, i, MIDI_CONTROLLER_MAIN_VOLUME, 100);
|
{
|
||||||
|
channel_volume[i] = DEFAULT_VOLUME;
|
||||||
|
SendVolumeMsg(0, i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void ResetReverb(int reset_type)
|
||||||
|
{
|
||||||
|
int i;
|
||||||
|
int reverb = winmm_reverb_level;
|
||||||
|
|
||||||
|
if (reverb == -1 && reset_type == RESET_TYPE_NONE)
|
||||||
|
{
|
||||||
|
// No reverb specified and no SysEx reset selected. Use GM default.
|
||||||
|
reverb = 40;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (reverb > -1)
|
||||||
|
{
|
||||||
|
for (i = 0; i < MIDI_CHANNELS_PER_TRACK; ++i)
|
||||||
|
{
|
||||||
|
SendShortMsg(0, MIDI_EVENT_CONTROLLER, i, MIDI_CONTROLLER_REVERB, reverb);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void ResetChorus(int reset_type)
|
||||||
|
{
|
||||||
|
int i;
|
||||||
|
int chorus = winmm_chorus_level;
|
||||||
|
|
||||||
|
if (chorus == -1 && reset_type == RESET_TYPE_NONE)
|
||||||
|
{
|
||||||
|
// No chorus specified and no SysEx reset selected. Use GM default.
|
||||||
|
chorus = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (chorus > -1)
|
||||||
|
{
|
||||||
|
for (i = 0; i < MIDI_CHANNELS_PER_TRACK; ++i)
|
||||||
|
{
|
||||||
|
SendShortMsg(0, MIDI_EVENT_CONTROLLER, i, MIDI_CONTROLLER_CHORUS, chorus);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void ResetControllers(void)
|
||||||
|
{
|
||||||
|
int i;
|
||||||
|
|
||||||
|
for (i = 0; i < MIDI_CHANNELS_PER_TRACK; ++i)
|
||||||
|
{
|
||||||
|
// Reset commonly used controllers.
|
||||||
|
SendShortMsg(0, MIDI_EVENT_CONTROLLER, i, MIDI_CONTROLLER_RESET_ALL_CTRLS, 0);
|
||||||
SendShortMsg(0, MIDI_EVENT_CONTROLLER, i, MIDI_CONTROLLER_PAN, 64);
|
SendShortMsg(0, MIDI_EVENT_CONTROLLER, i, MIDI_CONTROLLER_PAN, 64);
|
||||||
|
|
||||||
// Reset instrument.
|
|
||||||
SendShortMsg(0, MIDI_EVENT_CONTROLLER, i, MIDI_CONTROLLER_BANK_SELECT_MSB, 0);
|
SendShortMsg(0, MIDI_EVENT_CONTROLLER, i, MIDI_CONTROLLER_BANK_SELECT_MSB, 0);
|
||||||
SendShortMsg(0, MIDI_EVENT_CONTROLLER, i, MIDI_CONTROLLER_BANK_SELECT_LSB, 0);
|
SendShortMsg(0, MIDI_EVENT_CONTROLLER, i, MIDI_CONTROLLER_BANK_SELECT_LSB, 0);
|
||||||
SendShortMsg(0, MIDI_EVENT_PROGRAM_CHANGE, i, 0, 0);
|
SendShortMsg(0, MIDI_EVENT_PROGRAM_CHANGE, i, 0, 0);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (MidiDevice != ms_gs_synth)
|
static void ResetPitchBendSensitivity(void)
|
||||||
{
|
{
|
||||||
// Send SysEx reset message.
|
int i;
|
||||||
switch (winmm_reset_type)
|
|
||||||
{
|
|
||||||
case 1: // GS Reset
|
|
||||||
SendLongMsg(0, gs_reset, sizeof(gs_reset));
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 2: // GM System On
|
|
||||||
SendLongMsg(0, gm_system_on, sizeof(gm_system_on));
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 3: // GM2 System On
|
|
||||||
SendLongMsg(0, gm2_system_on, sizeof(gm2_system_on));
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 4: // XG System On
|
|
||||||
SendLongMsg(0, xg_system_on, sizeof(xg_system_on));
|
|
||||||
break;
|
|
||||||
|
|
||||||
default: // None
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Send delay after reset.
|
|
||||||
if (winmm_reset_delay > 0)
|
|
||||||
{
|
|
||||||
SendDelayMsg();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for (i = 0; i < MIDI_CHANNELS_PER_TRACK; ++i)
|
for (i = 0; i < MIDI_CHANNELS_PER_TRACK; ++i)
|
||||||
{
|
{
|
||||||
// Reset reverb and chorus.
|
// Set RPN MSB/LSB to pitch bend sensitivity.
|
||||||
SendShortMsg(0, MIDI_EVENT_CONTROLLER, i, MIDI_CONTROLLER_REVERB, winmm_reverb_level);
|
SendShortMsg(0, MIDI_EVENT_CONTROLLER, i, MIDI_CONTROLLER_RPN_LSB, 0);
|
||||||
SendShortMsg(0, MIDI_EVENT_CONTROLLER, i, MIDI_CONTROLLER_CHORUS, winmm_chorus_level);
|
SendShortMsg(0, MIDI_EVENT_CONTROLLER, i, MIDI_CONTROLLER_RPN_MSB, 0);
|
||||||
|
|
||||||
|
// Reset pitch bend sensitivity to +/- 2 semitones and 0 cents.
|
||||||
|
SendShortMsg(0, MIDI_EVENT_CONTROLLER, i, MIDI_CONTROLLER_DATA_ENTRY_MSB, 2);
|
||||||
|
SendShortMsg(0, MIDI_EVENT_CONTROLLER, i, MIDI_CONTROLLER_DATA_ENTRY_LSB, 0);
|
||||||
|
|
||||||
|
// Set RPN MSB/LSB to null value after data entry.
|
||||||
|
SendShortMsg(0, MIDI_EVENT_CONTROLLER, i, MIDI_CONTROLLER_RPN_LSB, 127);
|
||||||
|
SendShortMsg(0, MIDI_EVENT_CONTROLLER, i, MIDI_CONTROLLER_RPN_MSB, 127);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void ResetDevice(void)
|
||||||
|
{
|
||||||
|
int i;
|
||||||
|
int reset_type;
|
||||||
|
|
||||||
|
for (i = 0; i < MIDI_CHANNELS_PER_TRACK; ++i)
|
||||||
|
{
|
||||||
|
// Stop sound prior to reset to prevent volume spikes.
|
||||||
|
SendShortMsg(0, MIDI_EVENT_CONTROLLER, i, MIDI_CONTROLLER_ALL_NOTES_OFF, 0);
|
||||||
|
SendShortMsg(0, MIDI_EVENT_CONTROLLER, i, MIDI_CONTROLLER_ALL_SOUND_OFF, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (MidiDevice == ms_gs_synth)
|
||||||
|
{
|
||||||
|
// MS GS Wavetable Synth lacks instrument fallback in GS mode which can
|
||||||
|
// cause wrong or silent notes (MAYhem19.wad D_DM2TTL). It also responds
|
||||||
|
// to XG System On when it should ignore it.
|
||||||
|
switch (winmm_reset_type)
|
||||||
|
{
|
||||||
|
case RESET_TYPE_NONE:
|
||||||
|
reset_type = RESET_TYPE_NONE;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case RESET_TYPE_GS:
|
||||||
|
reset_type = RESET_TYPE_GS;
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
reset_type = RESET_TYPE_GM;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else // Unknown device
|
||||||
|
{
|
||||||
|
// Most devices support GS mode. Exceptions are some older hardware and
|
||||||
|
// a few older VSTis. Some devices lack instrument fallback in GS mode.
|
||||||
|
switch (winmm_reset_type)
|
||||||
|
{
|
||||||
|
case RESET_TYPE_NONE:
|
||||||
|
case RESET_TYPE_GM:
|
||||||
|
case RESET_TYPE_GM2:
|
||||||
|
case RESET_TYPE_XG:
|
||||||
|
reset_type = winmm_reset_type;
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
reset_type = RESET_TYPE_GS;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Use instrument fallback in GS mode.
|
||||||
|
MIDI_ResetFallback();
|
||||||
|
use_fallback = (reset_type == RESET_TYPE_GS);
|
||||||
|
|
||||||
|
switch (reset_type)
|
||||||
|
{
|
||||||
|
case RESET_TYPE_NONE:
|
||||||
|
ResetControllers();
|
||||||
|
break;
|
||||||
|
|
||||||
|
case RESET_TYPE_GS:
|
||||||
|
SendLongMsg(0, gs_reset, sizeof(gs_reset));
|
||||||
|
break;
|
||||||
|
|
||||||
|
case RESET_TYPE_GM:
|
||||||
|
SendLongMsg(0, gm_system_on, sizeof(gm_system_on));
|
||||||
|
break;
|
||||||
|
|
||||||
|
case RESET_TYPE_GM2:
|
||||||
|
SendLongMsg(0, gm2_system_on, sizeof(gm2_system_on));
|
||||||
|
break;
|
||||||
|
|
||||||
|
case RESET_TYPE_XG:
|
||||||
|
SendLongMsg(0, xg_system_on, sizeof(xg_system_on));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (reset_type == RESET_TYPE_NONE || MidiDevice == ms_gs_synth)
|
||||||
|
{
|
||||||
|
// MS GS Wavetable Synth doesn't reset pitch bend sensitivity, even
|
||||||
|
// when sending a GM/GS reset, so do it manually.
|
||||||
|
ResetPitchBendSensitivity();
|
||||||
|
}
|
||||||
|
|
||||||
|
ResetReverb(reset_type);
|
||||||
|
ResetChorus(reset_type);
|
||||||
|
|
||||||
|
// Reset volume (initial playback or on shutdown if no SysEx reset).
|
||||||
|
if (initial_playback || reset_type == RESET_TYPE_NONE)
|
||||||
|
{
|
||||||
|
// Scale by slider on initial playback, max on shutdown.
|
||||||
|
volume_factor = initial_playback ? volume_factor : 1.0f;
|
||||||
|
ResetVolume();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Send delay after reset. This is for hardware devices only (e.g. SC-55).
|
||||||
|
if (winmm_reset_delay > 0)
|
||||||
|
{
|
||||||
|
SendDelayMsg(winmm_reset_delay);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -447,56 +586,18 @@ static boolean IsSysExReset(const byte *msg, int length)
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
static boolean IsMasterVolume(const byte *msg, int length, unsigned int *volume)
|
|
||||||
{
|
|
||||||
// General Midi (7F <dev> 04 01 <lsb> <msb> F7)
|
|
||||||
if (length == 7 &&
|
|
||||||
msg[0] == 0x7F && // Universal Real Time
|
|
||||||
msg[2] == 0x04 && // Device Control
|
|
||||||
msg[3] == 0x01) // Master Volume
|
|
||||||
{
|
|
||||||
*volume = (msg[4] & 0x7F) | ((msg[5] & 0x7F) << 7);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Roland (41 <dev> 42 12 40 00 04 <vol> <sum> F7)
|
|
||||||
if (length == 10 &&
|
|
||||||
msg[0] == 0x41 && // Roland
|
|
||||||
msg[2] == 0x42 && // GS
|
|
||||||
msg[3] == 0x12 && // DT1
|
|
||||||
msg[4] == 0x40 && // Address MSB
|
|
||||||
msg[5] == 0x00 && // Address
|
|
||||||
msg[6] == 0x04) // Address LSB
|
|
||||||
{
|
|
||||||
*volume = DEFAULT_MASTER_VOLUME * (msg[7] & 0x7F) / 127;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Yamaha (43 <dev> 4C 00 00 04 <vol> F7)
|
|
||||||
if (length == 8 &&
|
|
||||||
msg[0] == 0x43 && // Yamaha
|
|
||||||
msg[2] == 0x4C && // XG
|
|
||||||
msg[3] == 0x00 && // Address High
|
|
||||||
msg[4] == 0x00 && // Address Mid
|
|
||||||
msg[5] == 0x04) // Address Low
|
|
||||||
{
|
|
||||||
*volume = DEFAULT_MASTER_VOLUME * (msg[6] & 0x7F) / 127;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void SendSysExMsg(int time, const byte *data, int length)
|
static void SendSysExMsg(int time, const byte *data, int length)
|
||||||
{
|
{
|
||||||
native_event_t native_event;
|
native_event_t native_event;
|
||||||
|
boolean is_sysex_reset;
|
||||||
const byte event_type = MIDI_EVENT_SYSEX;
|
const byte event_type = MIDI_EVENT_SYSEX;
|
||||||
|
|
||||||
if (IsMasterVolume(data, length, &master_volume))
|
is_sysex_reset = IsSysExReset(data, length);
|
||||||
|
|
||||||
|
if (is_sysex_reset && MidiDevice == ms_gs_synth)
|
||||||
{
|
{
|
||||||
// Found a master volume message in the MIDI file. Take this new
|
// Ignore SysEx reset from MIDI file for MS GS Wavetable Synth.
|
||||||
// value and scale it by the user's volume slider.
|
SendNOPMsg(time);
|
||||||
UpdateVolume(time);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -509,12 +610,17 @@ static void SendSysExMsg(int time, const byte *data, int length)
|
|||||||
WriteBuffer(data, length);
|
WriteBuffer(data, length);
|
||||||
WriteBufferPad();
|
WriteBufferPad();
|
||||||
|
|
||||||
if (IsSysExReset(data, length))
|
if (is_sysex_reset)
|
||||||
{
|
{
|
||||||
// SysEx reset also resets master volume. Take the default master
|
// SysEx reset also resets volume. Take the default channel volumes
|
||||||
// volume and scale it by the user's volume slider.
|
// and scale them by the user's volume slider.
|
||||||
master_volume = DEFAULT_MASTER_VOLUME;
|
ResetVolume();
|
||||||
UpdateVolume(0);
|
|
||||||
|
// 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.
|
||||||
|
MIDI_ResetFallback();
|
||||||
|
use_fallback = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -527,11 +633,10 @@ static void FillBuffer(void)
|
|||||||
|
|
||||||
if (initial_playback)
|
if (initial_playback)
|
||||||
{
|
{
|
||||||
initial_playback = false;
|
|
||||||
|
|
||||||
master_volume = DEFAULT_MASTER_VOLUME;
|
|
||||||
ResetDevice();
|
ResetDevice();
|
||||||
StreamOut();
|
StreamOut();
|
||||||
|
|
||||||
|
initial_playback = false;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -539,7 +644,7 @@ static void FillBuffer(void)
|
|||||||
{
|
{
|
||||||
update_volume = false;
|
update_volume = false;
|
||||||
|
|
||||||
UpdateVolume(0);
|
UpdateVolume();
|
||||||
StreamOut();
|
StreamOut();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -550,6 +655,7 @@ static void FillBuffer(void)
|
|||||||
int min_time = INT_MAX;
|
int min_time = INT_MAX;
|
||||||
int idx = -1;
|
int idx = -1;
|
||||||
int delta_time;
|
int delta_time;
|
||||||
|
midi_fallback_t fallback = {FALLBACK_NONE, 0};
|
||||||
|
|
||||||
// Look for an event with a minimal delta time.
|
// Look for an event with a minimal delta time.
|
||||||
for (i = 0; i < MIDI_NumTracks(song.file); ++i)
|
for (i = 0; i < MIDI_NumTracks(song.file); ++i)
|
||||||
@ -600,6 +706,11 @@ static void FillBuffer(void)
|
|||||||
|
|
||||||
delta_time = min_time - song.current_time;
|
delta_time = min_time - song.current_time;
|
||||||
|
|
||||||
|
if (use_fallback)
|
||||||
|
{
|
||||||
|
MIDI_CheckFallback(event, &fallback);
|
||||||
|
}
|
||||||
|
|
||||||
switch ((int)event->event_type)
|
switch ((int)event->event_type)
|
||||||
{
|
{
|
||||||
case MIDI_EVENT_META:
|
case MIDI_EVENT_META:
|
||||||
@ -607,7 +718,8 @@ static void FillBuffer(void)
|
|||||||
{
|
{
|
||||||
case MIDI_META_SET_TEMPO:
|
case MIDI_META_SET_TEMPO:
|
||||||
tempo = MAKE_EVT(event->data.meta.data[2],
|
tempo = MAKE_EVT(event->data.meta.data[2],
|
||||||
event->data.meta.data[1], event->data.meta.data[0], 0);
|
event->data.meta.data[1],
|
||||||
|
event->data.meta.data[0], 0);
|
||||||
UpdateTempo(delta_time);
|
UpdateTempo(delta_time);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
@ -621,22 +733,63 @@ static void FillBuffer(void)
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
case MIDI_EVENT_CONTROLLER:
|
case MIDI_EVENT_CONTROLLER:
|
||||||
|
if (event->data.channel.param1 == MIDI_CONTROLLER_MAIN_VOLUME)
|
||||||
|
{
|
||||||
|
// Adjust channel volume.
|
||||||
|
int volume = event->data.channel.param2;
|
||||||
|
channel_volume[event->data.channel.channel] = volume;
|
||||||
|
SendVolumeMsg(delta_time, event->data.channel.channel);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
else if (fallback.type == FALLBACK_BANK_LSB &&
|
||||||
|
event->data.channel.param1 == MIDI_CONTROLLER_BANK_SELECT_LSB)
|
||||||
|
{
|
||||||
|
SendShortMsg(delta_time, MIDI_EVENT_CONTROLLER,
|
||||||
|
event->data.channel.channel,
|
||||||
|
MIDI_CONTROLLER_BANK_SELECT_LSB,
|
||||||
|
fallback.value);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
// Fall through.
|
||||||
case MIDI_EVENT_NOTE_OFF:
|
case MIDI_EVENT_NOTE_OFF:
|
||||||
case MIDI_EVENT_NOTE_ON:
|
case MIDI_EVENT_NOTE_ON:
|
||||||
case MIDI_EVENT_AFTERTOUCH:
|
case MIDI_EVENT_AFTERTOUCH:
|
||||||
case MIDI_EVENT_PITCH_BEND:
|
case MIDI_EVENT_PITCH_BEND:
|
||||||
SendShortMsg(delta_time, event->event_type, event->data.channel.channel,
|
SendShortMsg(delta_time, event->event_type,
|
||||||
event->data.channel.param1, event->data.channel.param2);
|
event->data.channel.channel,
|
||||||
|
event->data.channel.param1,
|
||||||
|
event->data.channel.param2);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case MIDI_EVENT_PROGRAM_CHANGE:
|
case MIDI_EVENT_PROGRAM_CHANGE:
|
||||||
|
if (fallback.type == FALLBACK_BANK_MSB)
|
||||||
|
{
|
||||||
|
SendShortMsg(delta_time, MIDI_EVENT_CONTROLLER,
|
||||||
|
event->data.channel.channel,
|
||||||
|
MIDI_CONTROLLER_BANK_SELECT_MSB,
|
||||||
|
fallback.value);
|
||||||
|
SendShortMsg(0, MIDI_EVENT_PROGRAM_CHANGE,
|
||||||
|
event->data.channel.channel,
|
||||||
|
event->data.channel.param1, 0);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
else if (fallback.type == FALLBACK_DRUMS)
|
||||||
|
{
|
||||||
|
SendShortMsg(delta_time, MIDI_EVENT_PROGRAM_CHANGE,
|
||||||
|
event->data.channel.channel,
|
||||||
|
fallback.value, 0);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
// Fall through.
|
||||||
case MIDI_EVENT_CHAN_AFTERTOUCH:
|
case MIDI_EVENT_CHAN_AFTERTOUCH:
|
||||||
SendShortMsg(delta_time, event->event_type, event->data.channel.channel,
|
SendShortMsg(delta_time, event->event_type,
|
||||||
event->data.channel.param1, 0);
|
event->data.channel.channel,
|
||||||
|
event->data.channel.param1, 0);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case MIDI_EVENT_SYSEX:
|
case MIDI_EVENT_SYSEX:
|
||||||
SendSysExMsg(delta_time, event->data.sysex.data, event->data.sysex.length);
|
SendSysExMsg(delta_time, event->data.sysex.data,
|
||||||
|
event->data.sysex.length);
|
||||||
song.current_time = min_time;
|
song.current_time = min_time;
|
||||||
StreamOut();
|
StreamOut();
|
||||||
return;
|
return;
|
||||||
@ -698,6 +851,8 @@ static boolean I_WIN_InitMusic(int device)
|
|||||||
hBufferReturnEvent = CreateEvent(NULL, FALSE, FALSE, NULL);
|
hBufferReturnEvent = CreateEvent(NULL, FALSE, FALSE, NULL);
|
||||||
hExitEvent = CreateEvent(NULL, FALSE, FALSE, NULL);
|
hExitEvent = CreateEvent(NULL, FALSE, FALSE, NULL);
|
||||||
|
|
||||||
|
MIDI_InitFallback();
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -713,32 +868,28 @@ static void I_WIN_SetMusicVolume(int volume)
|
|||||||
|
|
||||||
volume_factor = sqrtf((float)volume / 15);
|
volume_factor = sqrtf((float)volume / 15);
|
||||||
|
|
||||||
update_volume = true;
|
update_volume = (song.file != NULL);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void I_WIN_StopSong(void *handle)
|
static void I_WIN_StopSong(void *handle)
|
||||||
{
|
{
|
||||||
MMRESULT mmr;
|
MMRESULT mmr;
|
||||||
|
|
||||||
if (hPlayerThread)
|
if (!hPlayerThread)
|
||||||
{
|
{
|
||||||
SetEvent(hExitEvent);
|
return;
|
||||||
WaitForSingleObject(hPlayerThread, INFINITE);
|
|
||||||
|
|
||||||
CloseHandle(hPlayerThread);
|
|
||||||
hPlayerThread = NULL;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
SetEvent(hExitEvent);
|
||||||
|
WaitForSingleObject(hPlayerThread, INFINITE);
|
||||||
|
CloseHandle(hPlayerThread);
|
||||||
|
hPlayerThread = NULL;
|
||||||
|
|
||||||
mmr = midiStreamStop(hMidiStream);
|
mmr = midiStreamStop(hMidiStream);
|
||||||
if (mmr != MMSYSERR_NOERROR)
|
if (mmr != MMSYSERR_NOERROR)
|
||||||
{
|
{
|
||||||
MidiError("midiStreamStop", mmr);
|
MidiError("midiStreamStop", mmr);
|
||||||
}
|
}
|
||||||
mmr = midiOutReset((HMIDIOUT)hMidiStream);
|
|
||||||
if (mmr != MMSYSERR_NOERROR)
|
|
||||||
{
|
|
||||||
MidiError("midiOutReset", mmr);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static void I_WIN_PlaySong(void *handle, boolean looping)
|
static void I_WIN_PlaySong(void *handle, boolean looping)
|
||||||
@ -753,8 +904,6 @@ static void I_WIN_PlaySong(void *handle, boolean looping)
|
|||||||
|
|
||||||
initial_playback = true;
|
initial_playback = true;
|
||||||
|
|
||||||
update_volume = true;
|
|
||||||
|
|
||||||
SetEvent(hBufferReturnEvent);
|
SetEvent(hBufferReturnEvent);
|
||||||
|
|
||||||
mmr = midiStreamRestart(hMidiStream);
|
mmr = midiStreamRestart(hMidiStream);
|
||||||
@ -915,28 +1064,37 @@ static void I_WIN_ShutdownMusic(void)
|
|||||||
}
|
}
|
||||||
WaitForSingleObject(hBufferReturnEvent, INFINITE);
|
WaitForSingleObject(hBufferReturnEvent, INFINITE);
|
||||||
|
|
||||||
|
mmr = midiStreamStop(hMidiStream);
|
||||||
|
if (mmr != MMSYSERR_NOERROR)
|
||||||
|
{
|
||||||
|
MidiError("midiStreamStop", mmr);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (buffer.data)
|
||||||
|
{
|
||||||
|
mmr = midiOutUnprepareHeader((HMIDIOUT)hMidiStream, &MidiStreamHdr,
|
||||||
|
sizeof(MIDIHDR));
|
||||||
|
if (mmr != MMSYSERR_NOERROR)
|
||||||
|
{
|
||||||
|
MidiError("midiOutUnprepareHeader", mmr);
|
||||||
|
}
|
||||||
|
free(buffer.data);
|
||||||
|
buffer.data = NULL;
|
||||||
|
buffer.size = 0;
|
||||||
|
buffer.position = 0;
|
||||||
|
}
|
||||||
|
|
||||||
mmr = midiStreamClose(hMidiStream);
|
mmr = midiStreamClose(hMidiStream);
|
||||||
if (mmr != MMSYSERR_NOERROR)
|
if (mmr != MMSYSERR_NOERROR)
|
||||||
{
|
{
|
||||||
MidiError("midiStreamClose", mmr);
|
MidiError("midiStreamClose", mmr);
|
||||||
}
|
}
|
||||||
|
|
||||||
hMidiStream = NULL;
|
hMidiStream = NULL;
|
||||||
|
|
||||||
if (buffer.data)
|
|
||||||
{
|
|
||||||
free(buffer.data);
|
|
||||||
buffer.data = NULL;
|
|
||||||
}
|
|
||||||
buffer.size = 0;
|
|
||||||
buffer.position = 0;
|
|
||||||
|
|
||||||
CloseHandle(hBufferReturnEvent);
|
CloseHandle(hBufferReturnEvent);
|
||||||
CloseHandle(hExitEvent);
|
CloseHandle(hExitEvent);
|
||||||
}
|
}
|
||||||
|
|
||||||
#include <mmreg.h>
|
|
||||||
|
|
||||||
int I_WIN_DeviceList(const char* devices[], int size)
|
int I_WIN_DeviceList(const char* devices[], int size)
|
||||||
{
|
{
|
||||||
int i;
|
int i;
|
||||||
|
24
src/m_misc.c
24
src/m_misc.c
@ -2325,29 +2325,29 @@ default_t defaults[] = {
|
|||||||
{
|
{
|
||||||
"winmm_reset_type",
|
"winmm_reset_type",
|
||||||
(config_t *) &winmm_reset_type, NULL,
|
(config_t *) &winmm_reset_type, NULL,
|
||||||
{2}, {0, 4}, number, ss_none, wad_no,
|
{-1}, {-1, 4}, number, ss_none, wad_no,
|
||||||
"SysEx reset for native MIDI (0 = None, 1 = GS Reset, 2 = GM System On (Default), 3 = GM2 System On, 4 = XG System On"
|
"SysEx reset for native MIDI (-1 = Default, 0 = None, 1 = GS, 2 = GM, 3 = GM2, 4 = XG)"
|
||||||
},
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
"winmm_reset_delay",
|
"winmm_reset_delay",
|
||||||
(config_t *) &winmm_reset_delay, NULL,
|
(config_t *) &winmm_reset_delay, NULL,
|
||||||
{100}, {0, 2000}, number, ss_none, wad_no,
|
{0}, {0, 2000}, number, ss_none, wad_no,
|
||||||
"Delay after reset for native MIDI (milliseconds)"
|
"Delay after reset for native MIDI (milliseconds)"
|
||||||
},
|
},
|
||||||
|
|
||||||
{
|
|
||||||
"winmm_chorus_level",
|
|
||||||
(config_t *) &winmm_chorus_level, NULL,
|
|
||||||
{0}, {0, 127}, number, ss_none, wad_no,
|
|
||||||
"fine tune default chorus level for native MIDI"
|
|
||||||
},
|
|
||||||
|
|
||||||
{
|
{
|
||||||
"winmm_reverb_level",
|
"winmm_reverb_level",
|
||||||
(config_t *) &winmm_reverb_level, NULL,
|
(config_t *) &winmm_reverb_level, NULL,
|
||||||
{40}, {0, 127}, number, ss_none, wad_no,
|
{-1}, {-1, 127}, number, ss_none, wad_no,
|
||||||
"fine tune default reverb level for native MIDI"
|
"Reverb send level for native MIDI (-1 = Default, 0 = Off, 127 = Max)"
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
"winmm_chorus_level",
|
||||||
|
(config_t *) &winmm_chorus_level, NULL,
|
||||||
|
{-1}, {-1, 127}, number, ss_none, wad_no,
|
||||||
|
"Chorus send level for native MIDI (-1 = Default, 0 = Off, 127 = Max)"
|
||||||
},
|
},
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
345
src/midifallback.c
Normal file
345
src/midifallback.c
Normal file
@ -0,0 +1,345 @@
|
|||||||
|
//
|
||||||
|
// Copyright(C) 2022 ceski
|
||||||
|
//
|
||||||
|
// This program is free software; you can redistribute it and/or
|
||||||
|
// modify it under the terms of the GNU General Public License
|
||||||
|
// as published by the Free Software Foundation; either version 2
|
||||||
|
// of the License, or (at your option) any later version.
|
||||||
|
//
|
||||||
|
// This program is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
//
|
||||||
|
// DESCRIPTION:
|
||||||
|
// MIDI instrument fallback support
|
||||||
|
//
|
||||||
|
|
||||||
|
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include "doomtype.h"
|
||||||
|
#include "midifile.h"
|
||||||
|
#include "midifallback.h"
|
||||||
|
|
||||||
|
static const byte drums_table[128] = {
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08,
|
||||||
|
0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10,
|
||||||
|
0x18, 0x19, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18,
|
||||||
|
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
|
||||||
|
0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28,
|
||||||
|
0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30,
|
||||||
|
0x38, 0x38, 0x38, 0x38, 0x38, 0x38, 0x38, 0x38,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7F
|
||||||
|
};
|
||||||
|
|
||||||
|
static byte variation[128][128];
|
||||||
|
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(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];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void MIDI_CheckFallback(midi_event_t *event, midi_fallback_t *fallback)
|
||||||
|
{
|
||||||
|
byte idx;
|
||||||
|
unsigned int *program;
|
||||||
|
|
||||||
|
switch ((int)event->event_type)
|
||||||
|
{
|
||||||
|
case MIDI_EVENT_SYSEX:
|
||||||
|
UpdateDrumMap(event->data.sysex.data, event->data.sysex.length);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case MIDI_EVENT_CONTROLLER:
|
||||||
|
idx = event->data.channel.channel;
|
||||||
|
switch (event->data.channel.param1)
|
||||||
|
{
|
||||||
|
case MIDI_CONTROLLER_BANK_SELECT_MSB:
|
||||||
|
bank_msb[idx] = event->data.channel.param2;
|
||||||
|
selected[idx] = false;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case MIDI_CONTROLLER_BANK_SELECT_LSB:
|
||||||
|
selected[idx] = false;
|
||||||
|
if (event->data.channel.param2 > 0)
|
||||||
|
{
|
||||||
|
// Bank select LSB > 0 not supported. This also
|
||||||
|
// preserves user's current SC-XX map.
|
||||||
|
fallback->type = FALLBACK_BANK_LSB;
|
||||||
|
fallback->value = 0;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case MIDI_EVENT_PROGRAM_CHANGE:
|
||||||
|
idx = event->data.channel.channel;
|
||||||
|
program = &event->data.channel.param1;
|
||||||
|
if (drum_map[idx] == 0) // Normal channel
|
||||||
|
{
|
||||||
|
if (bank_msb[idx] == 0 || variation[bank_msb[idx]][*program])
|
||||||
|
{
|
||||||
|
// Found a capital or variation for this bank select MSB.
|
||||||
|
selected[idx] = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
fallback->type = FALLBACK_BANK_MSB;
|
||||||
|
|
||||||
|
if (!selected[idx] || bank_msb[idx] > 63)
|
||||||
|
{
|
||||||
|
// Fall to capital when no instrument has (successfully)
|
||||||
|
// selected this variation or if the variation is above 63.
|
||||||
|
fallback->value = 0;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// A previous instrument used this variation but it's not
|
||||||
|
// valid for the current instrument. Fall to the next valid
|
||||||
|
// "sub-capital" (next variation that is a multiple of 8).
|
||||||
|
fallback->value = (bank_msb[idx] / 8) * 8;
|
||||||
|
while (fallback->value > 0)
|
||||||
|
{
|
||||||
|
if (variation[fallback->value][*program])
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
fallback->value -= 8;
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
else // Drums channel
|
||||||
|
{
|
||||||
|
if (*program != drums_table[*program])
|
||||||
|
{
|
||||||
|
// Use drum set from drums fallback table.
|
||||||
|
// Drums 0-63 and 127: same as original SC-55 (1.00 - 1.21).
|
||||||
|
// Drums 64-126: standard drum set (0).
|
||||||
|
fallback->type = FALLBACK_DRUMS;
|
||||||
|
fallback->value = drums_table[*program];
|
||||||
|
selected[idx] = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
fallback->type = FALLBACK_NONE;
|
||||||
|
fallback->value = 0xFF;
|
||||||
|
}
|
||||||
|
|
||||||
|
void MIDI_ResetFallback(void)
|
||||||
|
{
|
||||||
|
int i;
|
||||||
|
|
||||||
|
for (i = 0; i < MIDI_CHANNELS_PER_TRACK; i++)
|
||||||
|
{
|
||||||
|
bank_msb[i] = 0;
|
||||||
|
drum_map[i] = 0;
|
||||||
|
selected[i] = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Channel 10 (index 9) is set to drum map 1 by default.
|
||||||
|
drum_map[9] = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
void MIDI_InitFallback(void)
|
||||||
|
{
|
||||||
|
int program;
|
||||||
|
|
||||||
|
MIDI_ResetFallback();
|
||||||
|
|
||||||
|
// Capital
|
||||||
|
for (program = 0; program < 128; program++)
|
||||||
|
{
|
||||||
|
variation[0][program] = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Variation #1
|
||||||
|
variation[1][38] = 1;
|
||||||
|
variation[1][57] = 1;
|
||||||
|
variation[1][60] = 1;
|
||||||
|
variation[1][80] = 1;
|
||||||
|
variation[1][81] = 1;
|
||||||
|
variation[1][98] = 1;
|
||||||
|
variation[1][102] = 1;
|
||||||
|
variation[1][104] = 1;
|
||||||
|
variation[1][120] = 1;
|
||||||
|
variation[1][121] = 1;
|
||||||
|
variation[1][122] = 1;
|
||||||
|
variation[1][123] = 1;
|
||||||
|
variation[1][124] = 1;
|
||||||
|
variation[1][125] = 1;
|
||||||
|
variation[1][126] = 1;
|
||||||
|
variation[1][127] = 1;
|
||||||
|
|
||||||
|
// Variation #2
|
||||||
|
variation[2][102] = 1;
|
||||||
|
variation[2][120] = 1;
|
||||||
|
variation[2][122] = 1;
|
||||||
|
variation[2][123] = 1;
|
||||||
|
variation[2][124] = 1;
|
||||||
|
variation[2][125] = 1;
|
||||||
|
variation[2][126] = 1;
|
||||||
|
variation[2][127] = 1;
|
||||||
|
|
||||||
|
// Variation #3
|
||||||
|
variation[3][122] = 1;
|
||||||
|
variation[3][123] = 1;
|
||||||
|
variation[3][124] = 1;
|
||||||
|
variation[3][125] = 1;
|
||||||
|
variation[3][126] = 1;
|
||||||
|
variation[3][127] = 1;
|
||||||
|
|
||||||
|
// Variation #4
|
||||||
|
variation[4][122] = 1;
|
||||||
|
variation[4][124] = 1;
|
||||||
|
variation[4][125] = 1;
|
||||||
|
variation[4][126] = 1;
|
||||||
|
|
||||||
|
// Variation #5
|
||||||
|
variation[5][122] = 1;
|
||||||
|
variation[5][124] = 1;
|
||||||
|
variation[5][125] = 1;
|
||||||
|
variation[5][126] = 1;
|
||||||
|
|
||||||
|
// Variation #6
|
||||||
|
variation[6][125] = 1;
|
||||||
|
|
||||||
|
// Variation #7
|
||||||
|
variation[7][125] = 1;
|
||||||
|
|
||||||
|
// Variation #8
|
||||||
|
variation[8][0] = 1;
|
||||||
|
variation[8][1] = 1;
|
||||||
|
variation[8][2] = 1;
|
||||||
|
variation[8][3] = 1;
|
||||||
|
variation[8][4] = 1;
|
||||||
|
variation[8][5] = 1;
|
||||||
|
variation[8][6] = 1;
|
||||||
|
variation[8][11] = 1;
|
||||||
|
variation[8][12] = 1;
|
||||||
|
variation[8][14] = 1;
|
||||||
|
variation[8][16] = 1;
|
||||||
|
variation[8][17] = 1;
|
||||||
|
variation[8][19] = 1;
|
||||||
|
variation[8][21] = 1;
|
||||||
|
variation[8][24] = 1;
|
||||||
|
variation[8][25] = 1;
|
||||||
|
variation[8][26] = 1;
|
||||||
|
variation[8][27] = 1;
|
||||||
|
variation[8][28] = 1;
|
||||||
|
variation[8][30] = 1;
|
||||||
|
variation[8][31] = 1;
|
||||||
|
variation[8][38] = 1;
|
||||||
|
variation[8][39] = 1;
|
||||||
|
variation[8][40] = 1;
|
||||||
|
variation[8][48] = 1;
|
||||||
|
variation[8][50] = 1;
|
||||||
|
variation[8][61] = 1;
|
||||||
|
variation[8][62] = 1;
|
||||||
|
variation[8][63] = 1;
|
||||||
|
variation[8][80] = 1;
|
||||||
|
variation[8][81] = 1;
|
||||||
|
variation[8][107] = 1;
|
||||||
|
variation[8][115] = 1;
|
||||||
|
variation[8][116] = 1;
|
||||||
|
variation[8][117] = 1;
|
||||||
|
variation[8][118] = 1;
|
||||||
|
variation[8][125] = 1;
|
||||||
|
|
||||||
|
// Variation #9
|
||||||
|
variation[9][14] = 1;
|
||||||
|
variation[9][118] = 1;
|
||||||
|
variation[9][125] = 1;
|
||||||
|
|
||||||
|
// Variation #16
|
||||||
|
variation[16][0] = 1;
|
||||||
|
variation[16][4] = 1;
|
||||||
|
variation[16][5] = 1;
|
||||||
|
variation[16][6] = 1;
|
||||||
|
variation[16][16] = 1;
|
||||||
|
variation[16][19] = 1;
|
||||||
|
variation[16][24] = 1;
|
||||||
|
variation[16][25] = 1;
|
||||||
|
variation[16][28] = 1;
|
||||||
|
variation[16][39] = 1;
|
||||||
|
variation[16][62] = 1;
|
||||||
|
variation[16][63] = 1;
|
||||||
|
|
||||||
|
// Variation #24
|
||||||
|
variation[24][4] = 1;
|
||||||
|
variation[24][6] = 1;
|
||||||
|
|
||||||
|
// Variation #32
|
||||||
|
variation[32][16] = 1;
|
||||||
|
variation[32][17] = 1;
|
||||||
|
variation[32][24] = 1;
|
||||||
|
variation[32][52] = 1;
|
||||||
|
|
||||||
|
// CM-64 Map (PCM)
|
||||||
|
for (program = 0; program < 64; program++)
|
||||||
|
{
|
||||||
|
variation[126][program] = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// CM-64 Map (LA)
|
||||||
|
for (program = 0; program < 128; program++)
|
||||||
|
{
|
||||||
|
variation[127][program] = 1;
|
||||||
|
}
|
||||||
|
}
|
38
src/midifallback.h
Normal file
38
src/midifallback.h
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
//
|
||||||
|
// Copyright(C) 2022 ceski
|
||||||
|
//
|
||||||
|
// This program is free software; you can redistribute it and/or
|
||||||
|
// modify it under the terms of the GNU General Public License
|
||||||
|
// as published by the Free Software Foundation; either version 2
|
||||||
|
// of the License, or (at your option) any later version.
|
||||||
|
//
|
||||||
|
// This program is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
//
|
||||||
|
// DESCRIPTION:
|
||||||
|
// MIDI instrument fallback support
|
||||||
|
//
|
||||||
|
|
||||||
|
|
||||||
|
#include "doomtype.h"
|
||||||
|
#include "midifile.h"
|
||||||
|
|
||||||
|
typedef enum midi_fallback_type_t
|
||||||
|
{
|
||||||
|
FALLBACK_NONE,
|
||||||
|
FALLBACK_BANK_MSB,
|
||||||
|
FALLBACK_BANK_LSB,
|
||||||
|
FALLBACK_DRUMS,
|
||||||
|
} midi_fallback_type_t;
|
||||||
|
|
||||||
|
typedef struct midi_fallback_t
|
||||||
|
{
|
||||||
|
midi_fallback_type_t type;
|
||||||
|
byte value;
|
||||||
|
} midi_fallback_t;
|
||||||
|
|
||||||
|
void MIDI_CheckFallback(midi_event_t *event, midi_fallback_t *fallback);
|
||||||
|
void MIDI_ResetFallback(void);
|
||||||
|
void MIDI_InitFallback(void);
|
Loading…
x
Reference in New Issue
Block a user