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_swap.h
|
||||
memio.c memio.h
|
||||
midifallback.c midifallback.h
|
||||
midifile.c midifile.h
|
||||
mus2mid.c mus2mid.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
|
||||
#include <windows.h>
|
||||
#include <mmsystem.h>
|
||||
#include <mmreg.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <math.h>
|
||||
@ -30,11 +31,21 @@
|
||||
#include "memio.h"
|
||||
#include "mus2mid.h"
|
||||
#include "midifile.h"
|
||||
#include "midifallback.h"
|
||||
|
||||
int winmm_reset_type = 2;
|
||||
int winmm_reset_delay = 100;
|
||||
int winmm_reverb_level = 40;
|
||||
int winmm_chorus_level = 0;
|
||||
int winmm_reset_type = -1;
|
||||
int winmm_reset_delay = 0;
|
||||
int winmm_reverb_level = -1;
|
||||
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[] = {
|
||||
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
|
||||
};
|
||||
|
||||
static byte master_volume_msg[] = {
|
||||
0xF0, 0x7F, 0x7F, 0x04, 0x01, 0x7F, 0x7F, 0xF7
|
||||
};
|
||||
static boolean use_fallback;
|
||||
|
||||
#define DEFAULT_MASTER_VOLUME 16383
|
||||
static unsigned int master_volume = DEFAULT_MASTER_VOLUME;
|
||||
#define DEFAULT_VOLUME 100
|
||||
static int channel_volume[MIDI_CHANNELS_PER_TRACK];
|
||||
static int last_volume = -1;
|
||||
static float volume_factor = 1.0f;
|
||||
static float volume_factor = 0.0f;
|
||||
static boolean update_volume = false;
|
||||
|
||||
static DWORD timediv;
|
||||
@ -72,7 +81,7 @@ static HANDLE hBufferReturnEvent;
|
||||
static HANDLE hExitEvent;
|
||||
static HANDLE hPlayerThread;
|
||||
|
||||
// MS GS Wavetable Syhth id
|
||||
// MS GS Wavetable Synth Device ID.
|
||||
static int ms_gs_synth = MIDI_MAPPER;
|
||||
|
||||
// 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));
|
||||
}
|
||||
|
||||
static void SendDelayMsg(void)
|
||||
static void SendDelayMsg(int time_ms)
|
||||
{
|
||||
// Convert ms to ticks (see "Standard MIDI Files 1.0" page 14).
|
||||
int ticks = (float)winmm_reset_delay * 1000 * timediv / tempo + 0.5f;
|
||||
|
||||
SendNOPMsg(ticks);
|
||||
int time_ticks = (float)time_ms * 1000 * timediv / tempo + 0.5f;
|
||||
SendNOPMsg(time_ticks);
|
||||
}
|
||||
|
||||
static void UpdateTempo(int time)
|
||||
@ -261,78 +269,209 @@ static void UpdateTempo(int time)
|
||||
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;
|
||||
master_volume_msg[5] = volume & 0x7F;
|
||||
master_volume_msg[6] = (volume >> 7) & 0x7F;
|
||||
SendLongMsg(time, master_volume_msg, sizeof(master_volume_msg));
|
||||
int volume = channel_volume[channel] * volume_factor + 0.5f;
|
||||
SendShortMsg(time, MIDI_EVENT_CONTROLLER, channel,
|
||||
MIDI_CONTROLLER_MAIN_VOLUME, volume);
|
||||
}
|
||||
|
||||
static void ResetDevice(void)
|
||||
static void UpdateVolume(void)
|
||||
{
|
||||
int i;
|
||||
|
||||
for (i = 0; i < MIDI_CHANNELS_PER_TRACK; ++i)
|
||||
{
|
||||
// midiOutReset() sends "all notes off" and "reset all controllers."
|
||||
SendShortMsg(0, MIDI_EVENT_CONTROLLER, i, MIDI_CONTROLLER_ALL_SOUND_OFF, 0);
|
||||
SendVolumeMsg(0, i);
|
||||
}
|
||||
}
|
||||
|
||||
// Reset pitch bend sensitivity to +/- 2 semitones and 0 cents.
|
||||
SendShortMsg(0, MIDI_EVENT_CONTROLLER, i, MIDI_CONTROLLER_RPN_MSB, 0);
|
||||
SendShortMsg(0, MIDI_EVENT_CONTROLLER, i, MIDI_CONTROLLER_RPN_LSB, 0);
|
||||
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);
|
||||
static void ResetVolume(void)
|
||||
{
|
||||
int i;
|
||||
|
||||
// Reset channel volume and pan.
|
||||
SendShortMsg(0, MIDI_EVENT_CONTROLLER, i, MIDI_CONTROLLER_MAIN_VOLUME, 100);
|
||||
for (i = 0; i < MIDI_CHANNELS_PER_TRACK; ++i)
|
||||
{
|
||||
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);
|
||||
|
||||
// Reset instrument.
|
||||
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_PROGRAM_CHANGE, i, 0, 0);
|
||||
}
|
||||
}
|
||||
|
||||
if (MidiDevice != ms_gs_synth)
|
||||
{
|
||||
// Send SysEx reset message.
|
||||
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();
|
||||
}
|
||||
}
|
||||
static void ResetPitchBendSensitivity(void)
|
||||
{
|
||||
int i;
|
||||
|
||||
for (i = 0; i < MIDI_CHANNELS_PER_TRACK; ++i)
|
||||
{
|
||||
// Reset reverb and chorus.
|
||||
SendShortMsg(0, MIDI_EVENT_CONTROLLER, i, MIDI_CONTROLLER_REVERB, winmm_reverb_level);
|
||||
SendShortMsg(0, MIDI_EVENT_CONTROLLER, i, MIDI_CONTROLLER_CHORUS, winmm_chorus_level);
|
||||
// Set RPN MSB/LSB to pitch bend sensitivity.
|
||||
SendShortMsg(0, MIDI_EVENT_CONTROLLER, i, MIDI_CONTROLLER_RPN_LSB, 0);
|
||||
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;
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
native_event_t native_event;
|
||||
boolean is_sysex_reset;
|
||||
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
|
||||
// value and scale it by the user's volume slider.
|
||||
UpdateVolume(time);
|
||||
// Ignore SysEx reset from MIDI file for MS GS Wavetable Synth.
|
||||
SendNOPMsg(time);
|
||||
return;
|
||||
}
|
||||
|
||||
@ -509,12 +610,17 @@ static void SendSysExMsg(int time, const byte *data, int length)
|
||||
WriteBuffer(data, length);
|
||||
WriteBufferPad();
|
||||
|
||||
if (IsSysExReset(data, length))
|
||||
if (is_sysex_reset)
|
||||
{
|
||||
// SysEx reset also resets master volume. Take the default master
|
||||
// volume and scale it by the user's volume slider.
|
||||
master_volume = DEFAULT_MASTER_VOLUME;
|
||||
UpdateVolume(0);
|
||||
// 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.
|
||||
MIDI_ResetFallback();
|
||||
use_fallback = false;
|
||||
}
|
||||
}
|
||||
|
||||
@ -527,11 +633,10 @@ static void FillBuffer(void)
|
||||
|
||||
if (initial_playback)
|
||||
{
|
||||
initial_playback = false;
|
||||
|
||||
master_volume = DEFAULT_MASTER_VOLUME;
|
||||
ResetDevice();
|
||||
StreamOut();
|
||||
|
||||
initial_playback = false;
|
||||
return;
|
||||
}
|
||||
|
||||
@ -539,7 +644,7 @@ static void FillBuffer(void)
|
||||
{
|
||||
update_volume = false;
|
||||
|
||||
UpdateVolume(0);
|
||||
UpdateVolume();
|
||||
StreamOut();
|
||||
return;
|
||||
}
|
||||
@ -550,6 +655,7 @@ static void FillBuffer(void)
|
||||
int min_time = INT_MAX;
|
||||
int idx = -1;
|
||||
int delta_time;
|
||||
midi_fallback_t fallback = {FALLBACK_NONE, 0};
|
||||
|
||||
// Look for an event with a minimal delta time.
|
||||
for (i = 0; i < MIDI_NumTracks(song.file); ++i)
|
||||
@ -600,6 +706,11 @@ static void FillBuffer(void)
|
||||
|
||||
delta_time = min_time - song.current_time;
|
||||
|
||||
if (use_fallback)
|
||||
{
|
||||
MIDI_CheckFallback(event, &fallback);
|
||||
}
|
||||
|
||||
switch ((int)event->event_type)
|
||||
{
|
||||
case MIDI_EVENT_META:
|
||||
@ -607,7 +718,8 @@ static void FillBuffer(void)
|
||||
{
|
||||
case MIDI_META_SET_TEMPO:
|
||||
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);
|
||||
break;
|
||||
|
||||
@ -621,22 +733,63 @@ static void FillBuffer(void)
|
||||
break;
|
||||
|
||||
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_ON:
|
||||
case MIDI_EVENT_AFTERTOUCH:
|
||||
case MIDI_EVENT_PITCH_BEND:
|
||||
SendShortMsg(delta_time, event->event_type, event->data.channel.channel,
|
||||
event->data.channel.param1, event->data.channel.param2);
|
||||
SendShortMsg(delta_time, event->event_type,
|
||||
event->data.channel.channel,
|
||||
event->data.channel.param1,
|
||||
event->data.channel.param2);
|
||||
break;
|
||||
|
||||
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:
|
||||
SendShortMsg(delta_time, event->event_type, event->data.channel.channel,
|
||||
event->data.channel.param1, 0);
|
||||
SendShortMsg(delta_time, event->event_type,
|
||||
event->data.channel.channel,
|
||||
event->data.channel.param1, 0);
|
||||
break;
|
||||
|
||||
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;
|
||||
StreamOut();
|
||||
return;
|
||||
@ -698,6 +851,8 @@ static boolean I_WIN_InitMusic(int device)
|
||||
hBufferReturnEvent = CreateEvent(NULL, FALSE, FALSE, NULL);
|
||||
hExitEvent = CreateEvent(NULL, FALSE, FALSE, NULL);
|
||||
|
||||
MIDI_InitFallback();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -713,32 +868,28 @@ static void I_WIN_SetMusicVolume(int volume)
|
||||
|
||||
volume_factor = sqrtf((float)volume / 15);
|
||||
|
||||
update_volume = true;
|
||||
update_volume = (song.file != NULL);
|
||||
}
|
||||
|
||||
static void I_WIN_StopSong(void *handle)
|
||||
{
|
||||
MMRESULT mmr;
|
||||
|
||||
if (hPlayerThread)
|
||||
if (!hPlayerThread)
|
||||
{
|
||||
SetEvent(hExitEvent);
|
||||
WaitForSingleObject(hPlayerThread, INFINITE);
|
||||
|
||||
CloseHandle(hPlayerThread);
|
||||
hPlayerThread = NULL;
|
||||
return;
|
||||
}
|
||||
|
||||
SetEvent(hExitEvent);
|
||||
WaitForSingleObject(hPlayerThread, INFINITE);
|
||||
CloseHandle(hPlayerThread);
|
||||
hPlayerThread = NULL;
|
||||
|
||||
mmr = midiStreamStop(hMidiStream);
|
||||
if (mmr != MMSYSERR_NOERROR)
|
||||
{
|
||||
MidiError("midiStreamStop", mmr);
|
||||
}
|
||||
mmr = midiOutReset((HMIDIOUT)hMidiStream);
|
||||
if (mmr != MMSYSERR_NOERROR)
|
||||
{
|
||||
MidiError("midiOutReset", mmr);
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
update_volume = true;
|
||||
|
||||
SetEvent(hBufferReturnEvent);
|
||||
|
||||
mmr = midiStreamRestart(hMidiStream);
|
||||
@ -915,28 +1064,37 @@ static void I_WIN_ShutdownMusic(void)
|
||||
}
|
||||
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);
|
||||
if (mmr != MMSYSERR_NOERROR)
|
||||
{
|
||||
MidiError("midiStreamClose", mmr);
|
||||
}
|
||||
|
||||
hMidiStream = NULL;
|
||||
|
||||
if (buffer.data)
|
||||
{
|
||||
free(buffer.data);
|
||||
buffer.data = NULL;
|
||||
}
|
||||
buffer.size = 0;
|
||||
buffer.position = 0;
|
||||
|
||||
CloseHandle(hBufferReturnEvent);
|
||||
CloseHandle(hExitEvent);
|
||||
}
|
||||
|
||||
#include <mmreg.h>
|
||||
|
||||
int I_WIN_DeviceList(const char* devices[], int size)
|
||||
{
|
||||
int i;
|
||||
|
24
src/m_misc.c
24
src/m_misc.c
@ -2325,29 +2325,29 @@ default_t defaults[] = {
|
||||
{
|
||||
"winmm_reset_type",
|
||||
(config_t *) &winmm_reset_type, NULL,
|
||||
{2}, {0, 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"
|
||||
{-1}, {-1, 4}, number, ss_none, wad_no,
|
||||
"SysEx reset for native MIDI (-1 = Default, 0 = None, 1 = GS, 2 = GM, 3 = GM2, 4 = XG)"
|
||||
},
|
||||
|
||||
{
|
||||
"winmm_reset_delay",
|
||||
(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)"
|
||||
},
|
||||
|
||||
{
|
||||
"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",
|
||||
(config_t *) &winmm_reverb_level, NULL,
|
||||
{40}, {0, 127}, number, ss_none, wad_no,
|
||||
"fine tune default reverb level for native MIDI"
|
||||
{-1}, {-1, 127}, number, ss_none, wad_no,
|
||||
"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
|
||||
|
||||
|
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