- cleaned up the interdependencies between the MIDIStreamer and the WinMIDIDevice classes.

A major part of this device's implementation details about how to handle the callback were not encapsulated by the device class at all, they were #ifdef'd into the streamer class.
This puts everything into the device class which now exposes a clean interface to the rest of the game with no special handling aside from calling two additional virtual functions that are empty for the other devices
This commit is contained in:
Christoph Oelckers 2017-03-10 19:03:58 +01:00
parent 8d6fe24945
commit 5374eed06d
8 changed files with 215 additions and 316 deletions

View file

@ -74,7 +74,8 @@ public:
virtual int UnprepareHeader(MidiHeader *data);
virtual bool FakeVolume();
virtual bool Pause(bool paused) = 0;
virtual bool NeedThreadedCallback();
virtual void InitPlayback();
virtual bool Update();
virtual void PrecacheInstruments(const uint16_t *instruments, int count);
virtual void TimidityVolumeChanged();
virtual void FluidSettingInt(const char *setting, int value);
@ -357,21 +358,20 @@ public:
void FluidSettingStr(const char *setting, const char *value);
void WildMidiSetOption(int opt, int set);
void CreateSMF(TArray<uint8_t> &file, int looplimit=0);
int ServiceEvent();
protected:
MIDIStreamer(const char *dumpname, EMidiDevice type);
bool CheckExitEvent();
void OutputVolume (uint32_t volume);
int FillBuffer(int buffer_num, int max_events, uint32_t max_time);
int FillStopBuffer(int buffer_num);
uint32_t *WriteStopNotes(uint32_t *events);
int ServiceEvent();
int VolumeControllerChange(int channel, int volume);
int ClampLoopCount(int loopcount);
void SetTempo(int new_tempo);
static EMidiDevice SelectMIDIDevice(EMidiDevice devtype);
MIDIDevice *CreateMIDIDevice(EMidiDevice devtype) const;
MIDIDevice *CreateMIDIDevice(EMidiDevice devtype);
static void Callback(void *userdata);

View file

@ -122,6 +122,8 @@ CUSTOM_CVAR (Float, snd_sfxvolume, 1.f, CVAR_ARCHIVE|CVAR_GLOBALCONFIG|CVAR_NOIN
}
}
class MIDIStreamer;
class NullSoundRenderer : public SoundRenderer
{
public:

View file

@ -131,10 +131,6 @@ extern char MIDI_CommonLengths[15];
HMISong::HMISong (FileReader &reader, EMidiDevice type, const char *args)
: MIDIStreamer(type, args), MusHeader(0), Tracks(0)
{
if (!CheckExitEvent())
{
return;
}
int len = reader.GetLength();
if (len < 0x100)
{ // Way too small to be HMI.

View file

@ -36,15 +36,6 @@
#include "i_midi_win32.h"
/*
#ifdef _WIN32
DWORD PlayerLoop();
HANDLE PlayerThread;
HANDLE ExitEvent;
HANDLE BufferDoneEvent;
#endif
*/
#include "i_musicinterns.h"
#include "templates.h"
@ -73,8 +64,6 @@ EXTERN_CVAR(Float, snd_musicvolume)
EXTERN_CVAR(Int, snd_mididevice)
#ifdef _WIN32
DWORD WINAPI PlayerProc(LPVOID lpParameter);
extern unsigned mididevice;
#endif
@ -105,27 +94,9 @@ static const uint8_t StaticMIDIhead[] =
MIDIStreamer::MIDIStreamer(EMidiDevice type, const char *args)
:
#ifdef _WIN32
//PlayerThread(0), ExitEvent(0), BufferDoneEvent(0),
#endif
MIDI(0), Division(0), InitialTempo(500000), DeviceType(type), Args(args)
{
memset(Buffer, 0, sizeof(Buffer));
/*
#ifdef _WIN32
BufferDoneEvent = CreateEvent(NULL, FALSE, FALSE, NULL);
if (BufferDoneEvent == NULL)
{
Printf(PRINT_BOLD, "Could not create buffer done event for MIDI playback\n");
}
ExitEvent = CreateEvent(NULL, FALSE, FALSE, NULL);
if (ExitEvent == NULL)
{
Printf(PRINT_BOLD, "Could not create exit event for MIDI playback\n");
return;
}
#endif
*/
}
//==========================================================================
@ -136,18 +107,9 @@ MIDIStreamer::MIDIStreamer(EMidiDevice type, const char *args)
MIDIStreamer::MIDIStreamer(const char *dumpname, EMidiDevice type)
:
#ifdef _WIN32
//PlayerThread(0), ExitEvent(0), BufferDoneEvent(0),
#endif
MIDI(0), Division(0), InitialTempo(500000), DeviceType(type), DumpFilename(dumpname)
{
memset(Buffer, 0, sizeof(Buffer));
/*
#ifdef _WIN32
BufferDoneEvent = NULL;
ExitEvent = NULL;
#endif
*/
}
//==========================================================================
@ -159,18 +121,6 @@ MIDIStreamer::MIDIStreamer(const char *dumpname, EMidiDevice type)
MIDIStreamer::~MIDIStreamer()
{
Stop();
/*
#ifdef _WIN32
if (ExitEvent != NULL)
{
CloseHandle(ExitEvent);
}
if (BufferDoneEvent != NULL)
{
CloseHandle(BufferDoneEvent);
}
#endif
*/
if (MIDI != NULL)
{
delete MIDI;
@ -198,11 +148,7 @@ bool MIDIStreamer::IsMIDI() const
bool MIDIStreamer::IsValid() const
{
#ifdef _WIN32
return /*ExitEvent != NULL &&*/ Division != 0;
#else
return Division != 0;
#endif
}
//==========================================================================
@ -278,7 +224,7 @@ EMidiDevice MIDIStreamer::SelectMIDIDevice(EMidiDevice device)
//
//==========================================================================
MIDIDevice *MIDIStreamer::CreateMIDIDevice(EMidiDevice devtype) const
MIDIDevice *MIDIStreamer::CreateMIDIDevice(EMidiDevice devtype)
{
switch (devtype)
{
@ -331,7 +277,6 @@ MIDIDevice *MIDIStreamer::CreateMIDIDevice(EMidiDevice devtype) const
void MIDIStreamer::Play(bool looping, int subsong)
{
DWORD tid;
EMidiDevice devtype;
m_Status = STATE_Stopped;
@ -359,10 +304,6 @@ void MIDIStreamer::Play(bool looping, int subsong)
MIDI = CreateMIDIDevice(devtype);
}
#ifndef _WIN32
assert(MIDI == NULL || MIDI->NeedThreadedCallback() == false);
#endif
if (MIDI == NULL || 0 != MIDI->Open(Callback, this))
{
Printf(PRINT_BOLD, "Could not open MIDI out device\n");
@ -393,27 +334,7 @@ void MIDIStreamer::Play(bool looping, int subsong)
}
else
{
/*
#ifdef _WIN32
if (MIDI->NeedThreadedCallback())
{
PlayerThread = CreateThread(NULL, 0, PlayerProc, this, 0, &tid);
if (PlayerThread == NULL)
{
Printf ("Creating MIDI thread failed\n");
Stop();
}
else
{
m_Status = STATE_Playing;
}
}
else
#endif
*/
{
m_Status = STATE_Playing;
}
m_Status = STATE_Playing;
}
}
@ -440,12 +361,7 @@ void MIDIStreamer::StartPlayback()
MusicVolumeChanged(); // set volume to current music's properties
OutputVolume(Volume);
/*
#ifdef _WIN32
ResetEvent(ExitEvent);
ResetEvent(BufferDoneEvent);
#endif
*/
MIDI->InitPlayback();
// Fill the initial buffers for the song.
BufferNum = 0;
@ -532,17 +448,6 @@ void MIDIStreamer::Stop()
{
EndQueued = 4;
/*
#ifdef _WIN32
if (PlayerThread != NULL)
{
SetEvent(ExitEvent);
WaitForSingleObject(PlayerThread, INFINITE);
CloseHandle(PlayerThread);
PlayerThread = NULL;
}
#endif
*/
if (MIDI != NULL && MIDI->IsOpen())
{
MIDI->Stop();
@ -714,10 +619,6 @@ int MIDIStreamer::VolumeControllerChange(int channel, int volume)
//
// MIDIStreamer :: Callback Static
//
// Signals the BufferDoneEvent to prepare the next buffer. The buffer is not
// prepared in the callback directly, because it's generally still in use by
// the MIDI streamer when this callback is executed.
//
//==========================================================================
void MIDIStreamer::Callback(void *userdata)
@ -728,18 +629,7 @@ void MIDIStreamer::Callback(void *userdata)
{
return;
}
/*
#ifdef _WIN32
if (self->PlayerThread != NULL)
{
SetEvent(self->BufferDoneEvent);
}
else
#endif
*/
{
self->ServiceEvent();
}
self->ServiceEvent();
}
//==========================================================================
@ -753,128 +643,9 @@ void MIDIStreamer::Callback(void *userdata)
void MIDIStreamer::Update()
{
/*
#ifdef _WIN32
// If the PlayerThread is signalled, then it's dead.
if (PlayerThread != NULL &&
WaitForSingleObject(PlayerThread, 0) == WAIT_OBJECT_0)
{
static const char *const MMErrorCodes[] =
{
"No error",
"Unspecified error",
"Device ID out of range",
"Driver failed enable",
"Device already allocated",
"Device handle is invalid",
"No device driver present",
"Memory allocation error",
"Function isn't supported",
"Error value out of range",
"Invalid flag passed",
"Invalid parameter passed",
"Handle being used simultaneously on another thread",
"Specified alias not found",
"Bad registry database",
"Registry key not found",
"Registry read error",
"Registry write error",
"Registry delete error",
"Registry value not found",
"Driver does not call DriverCallback",
"More data to be returned",
};
static const char *const MidiErrorCodes[] =
{
"MIDI header not prepared",
"MIDI still playing something",
"MIDI no configured instruments",
"MIDI hardware is still busy",
"MIDI port no longer connected",
"MIDI invalid MIF",
"MIDI operation unsupported with open mode",
"MIDI through device 'eating' a message",
};
DWORD code = 0xABADCAFE;
GetExitCodeThread(PlayerThread, &code);
CloseHandle(PlayerThread);
PlayerThread = NULL;
Printf ("MIDI playback failure: ");
if (code < countof(MMErrorCodes))
{
Printf("%s\n", MMErrorCodes[code]);
}
else if (code >= MIDIERR_BASE && code < MIDIERR_BASE + countof(MidiErrorCodes))
{
Printf("%s\n", MidiErrorCodes[code - MIDIERR_BASE]);
}
else
{
Printf("%08x\n", code);
}
Stop();
}
#endif
*/
if (!MIDI->Update()) Stop();
}
//==========================================================================
//
// MIDIStreamer :: PlayerProc Static
//
// Entry point for the player thread.
//
//==========================================================================
#ifdef _WIN32
DWORD WINAPI PlayerProc(LPVOID lpParameter)
{
// return ((MIDIStreamer *)lpParameter)->PlayerLoop();
return 0;
}
#endif
//==========================================================================
//
// MIDIStreamer :: PlayerLoop
//
// Services MIDI playback events.
//
//==========================================================================
/*
#ifdef _WIN32
DWORD MIDIStreamer::PlayerLoop()
{
HANDLE events[2] = { BufferDoneEvent, ExitEvent };
int res;
SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_TIME_CRITICAL);
for (;;)
{
switch (WaitForMultipleObjects(2, events, FALSE, INFINITE))
{
case WAIT_OBJECT_0:
if (0 != (res = ServiceEvent()))
{
return res;
}
break;
case WAIT_OBJECT_0 + 1:
return 0;
default:
// Should not happen.
return MMSYSERR_ERROR;
}
}
return 0;
}
#endif
*/
//==========================================================================
//
// MIDIStreamer :: ServiceEvent
@ -913,8 +684,8 @@ fill:
switch (res & 3)
{
case SONG_MORE:
if ((MIDI->NeedThreadedCallback() && 0 != (res = MIDI->StreamOutSync(&Buffer[BufferNum]))) ||
(!MIDI->NeedThreadedCallback() && 0 != (res = MIDI->StreamOut(&Buffer[BufferNum]))))
res = MIDI->StreamOut(&Buffer[BufferNum]);
if (res != 0)
{
return res;
}
@ -1433,27 +1204,6 @@ bool MIDIStreamer::SetMIDISubsong(int subsong)
return subsong == 0;
}
//==========================================================================
//
// MIDIStreamer :: CheckExitEvent
//
//
//
//==========================================================================
bool MIDIStreamer::CheckExitEvent()
{
/*
#ifdef _WIN32
if (ExitEvent == NULL)
{
return false;
}
#endif
*/
return true;
}
//==========================================================================
//
// MIDIDevice stubs.
@ -1545,16 +1295,23 @@ bool MIDIDevice::FakeVolume()
//==========================================================================
//
// MIDIDevice :: NeedThreadedCallabck
//
// Most implementations can service the callback directly rather than using
// a separate thread.
//
//==========================================================================
bool MIDIDevice::NeedThreadedCallback()
void MIDIDevice::InitPlayback()
{
return false;
}
//==========================================================================
//
//
//
//==========================================================================
bool MIDIDevice::Update()
{
return true;
}
//==========================================================================

View file

@ -96,11 +96,6 @@ static const uint8_t CtrlTranslate[15] =
MUSSong2::MUSSong2 (FileReader &reader, EMidiDevice type, const char *args)
: MIDIStreamer(type, args), MusHeader(0), MusBuffer(0)
{
if (!CheckExitEvent())
{
return;
}
uint8_t front[32];
int start;

View file

@ -108,10 +108,6 @@ MIDISong2::MIDISong2 (FileReader &reader, EMidiDevice type, const char *args)
int p;
int i;
if (!CheckExitEvent())
{
return;
}
SongLen = reader.GetLength();
MusHeader = new uint8_t[SongLen];
if (reader.Read(MusHeader, SongLen) != SongLen)

View file

@ -34,19 +34,7 @@
#ifdef _WIN32
#define WIN32_LEAN_AND_MEAN
#define USE_WINDOWS_DWORD
#if defined(_WIN32_WINNT) && _WIN32_WINNT < 0x0400
#undef _WIN32_WINNT
#endif
#ifndef _WIN32_WINNT
#define _WIN32_WINNT 0x0400
#endif
#ifndef USE_WINDOWS_DWORD
#define USE_WINDOWS_DWORD
#endif
#include <windows.h>
#include <mmsystem.h>
#include "i_midi_win32.h"
// HEADER FILES ------------------------------------------------------------
@ -92,13 +80,16 @@ public:
int PrepareHeader(MidiHeader *data);
int UnprepareHeader(MidiHeader *data);
bool FakeVolume();
bool NeedThreadedCallback();
bool Pause(bool paused);
void InitPlayback() override;
bool Update() override;
void PrecacheInstruments(const uint16_t *instruments, int count);
DWORD PlayerLoop();
protected:
//protected:
static void CALLBACK CallbackFunc(HMIDIOUT, UINT, DWORD_PTR, DWORD, DWORD);
MIDIStreamer *Streamer;
HMIDISTRM MidiOut;
UINT DeviceID;
DWORD SavedVolume;
@ -108,6 +99,12 @@ protected:
MidiCallback Callback;
void *CallbackData;
HANDLE BufferDoneEvent;
HANDLE ExitEvent;
HANDLE PlayerThread;
};
// PUBLIC DATA DEFINITIONS -------------------------------------------------
@ -128,6 +125,18 @@ WinMIDIDevice::WinMIDIDevice(int dev_id)
MidiOut = 0;
HeaderIndex = 0;
memset(WinMidiHeaders, 0, sizeof(WinMidiHeaders));
BufferDoneEvent = CreateEvent(nullptr, FALSE, FALSE, nullptr);
if (BufferDoneEvent == nullptr)
{
Printf(PRINT_BOLD, "Could not create buffer done event for MIDI playback\n");
}
ExitEvent = CreateEvent(nullptr, FALSE, FALSE, nullptr);
if (ExitEvent == nullptr)
{
Printf(PRINT_BOLD, "Could not create exit event for MIDI playback\n");
}
PlayerThread = nullptr;
}
//==========================================================================
@ -139,6 +148,15 @@ WinMIDIDevice::WinMIDIDevice(int dev_id)
WinMIDIDevice::~WinMIDIDevice()
{
Close();
if (ExitEvent != nullptr)
{
CloseHandle(ExitEvent);
}
if (BufferDoneEvent != nullptr)
{
CloseHandle(BufferDoneEvent);
}
}
//==========================================================================
@ -153,7 +171,7 @@ int WinMIDIDevice::Open(MidiCallback callback, void *userdata)
Callback = callback;
CallbackData = userdata;
if (MidiOut == NULL)
if (MidiOut == nullptr)
{
err = midiStreamOpen(&MidiOut, &DeviceID, 1, (DWORD_PTR)CallbackFunc, (DWORD_PTR)this, CALLBACK_FUNCTION);
@ -185,10 +203,10 @@ int WinMIDIDevice::Open(MidiCallback callback, void *userdata)
void WinMIDIDevice::Close()
{
if (MidiOut != NULL)
if (MidiOut != nullptr)
{
midiStreamClose(MidiOut);
MidiOut = NULL;
MidiOut = nullptr;
}
}
@ -200,7 +218,7 @@ void WinMIDIDevice::Close()
bool WinMIDIDevice::IsOpen() const
{
return MidiOut != NULL;
return MidiOut != nullptr;
}
//==========================================================================
@ -244,6 +262,19 @@ int WinMIDIDevice::SetTimeDiv(int timediv)
return midiStreamProperty(MidiOut, (LPBYTE)&data, MIDIPROP_SET | MIDIPROP_TIMEDIV);
}
//==========================================================================
//
// MIDIStreamer :: PlayerProc Static
//
// Entry point for the player thread.
//
//==========================================================================
DWORD WINAPI PlayerProc(LPVOID lpParameter)
{
return ((WinMIDIDevice *)lpParameter)->PlayerLoop();
}
//==========================================================================
//
// WinMIDIDevice :: Resume
@ -252,7 +283,31 @@ int WinMIDIDevice::SetTimeDiv(int timediv)
int WinMIDIDevice::Resume()
{
return midiStreamRestart(MidiOut);
DWORD tid;
int ret = midiStreamRestart(MidiOut);
if (ret == 0)
{
PlayerThread = CreateThread(nullptr, 0, PlayerProc, this, 0, &tid);
if (PlayerThread == nullptr)
{
Printf("Creating MIDI thread failed\n");
Stop();
return MMSYSERR_NOTSUPPORTED;
}
}
return ret;
}
//==========================================================================
//
// WinMIDIDevice :: InitPlayback
//
//==========================================================================
void WinMIDIDevice::InitPlayback()
{
ResetEvent(ExitEvent);
ResetEvent(BufferDoneEvent);
}
//==========================================================================
@ -263,6 +318,14 @@ int WinMIDIDevice::Resume()
void WinMIDIDevice::Stop()
{
if (PlayerThread != nullptr)
{
SetEvent(ExitEvent);
WaitForSingleObject(PlayerThread, INFINITE);
CloseHandle(PlayerThread);
PlayerThread = nullptr;
}
midiStreamStop(MidiOut);
midiOutReset((HMIDIOUT)MidiOut);
if (VolumeWorks)
@ -271,6 +334,40 @@ void WinMIDIDevice::Stop()
}
}
//==========================================================================
//
// MIDIStreamer :: PlayerLoop
//
// Services MIDI playback events.
//
//==========================================================================
DWORD WinMIDIDevice::PlayerLoop()
{
HANDLE events[2] = { BufferDoneEvent, ExitEvent };
SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_TIME_CRITICAL);
for (;;)
{
switch (WaitForMultipleObjects(2, events, FALSE, INFINITE))
{
case WAIT_OBJECT_0:
if (Callback != nullptr) Callback(CallbackData);
break;
case WAIT_OBJECT_0 + 1:
return 0;
default:
// Should not happen.
return MMSYSERR_ERROR;
}
}
return 0;
}
//==========================================================================
//
// WinMIDIDevice :: PrecacheInstruments
@ -391,9 +488,7 @@ int WinMIDIDevice::StreamOut(MidiHeader *header)
int WinMIDIDevice::StreamOutSync(MidiHeader *header)
{
auto syshdr = (MIDIHDR*)header->lpNext;
assert(syshdr == &WinMidiHeaders[0] || syshdr == &WinMidiHeaders[1]);
return midiStreamOut(MidiOut, syshdr, sizeof(MIDIHDR));
return StreamOut(header);
}
//==========================================================================
@ -454,15 +549,71 @@ bool WinMIDIDevice::FakeVolume()
//==========================================================================
//
// WinMIDIDevice :: NeedThreadedCallback
//
// When using the MM system, the callback can't yet touch the buffer, so
// the real processing needs to happen in a different thread.
// WinMIDIDevice :: Update
//
//==========================================================================
bool WinMIDIDevice::NeedThreadedCallback()
bool WinMIDIDevice::Update()
{
// If the PlayerThread is signalled, then it's dead.
if (PlayerThread != nullptr &&
WaitForSingleObject(PlayerThread, 0) == WAIT_OBJECT_0)
{
static const char *const MMErrorCodes[] =
{
"No error",
"Unspecified error",
"Device ID out of range",
"Driver failed enable",
"Device already allocated",
"Device handle is invalid",
"No device driver present",
"Memory allocation error",
"Function isn't supported",
"Error value out of range",
"Invalid flag passed",
"Invalid parameter passed",
"Handle being used simultaneously on another thread",
"Specified alias not found",
"Bad registry database",
"Registry key not found",
"Registry read error",
"Registry write error",
"Registry delete error",
"Registry value not found",
"Driver does not call DriverCallback",
"More data to be returned",
};
static const char *const MidiErrorCodes[] =
{
"MIDI header not prepared",
"MIDI still playing something",
"MIDI no configured instruments",
"MIDI hardware is still busy",
"MIDI port no longer connected",
"MIDI invalid MIF",
"MIDI operation unsupported with open mode",
"MIDI through device 'eating' a message",
};
DWORD code = 0xABADCAFE;
GetExitCodeThread(PlayerThread, &code);
CloseHandle(PlayerThread);
PlayerThread = nullptr;
Printf("MIDI playback failure: ");
if (code < countof(MMErrorCodes))
{
Printf("%s\n", MMErrorCodes[code]);
}
else if (code >= MIDIERR_BASE && code < MIDIERR_BASE + countof(MidiErrorCodes))
{
Printf("%s\n", MidiErrorCodes[code - MIDIERR_BASE]);
}
else
{
Printf("%08x\n", code);
}
return false;
}
return true;
}
@ -475,9 +626,9 @@ bool WinMIDIDevice::NeedThreadedCallback()
void CALLBACK WinMIDIDevice::CallbackFunc(HMIDIOUT hOut, UINT uMsg, DWORD_PTR dwInstance, DWORD dwParam1, DWORD dwParam2)
{
WinMIDIDevice *self = (WinMIDIDevice *)dwInstance;
if (self->Callback != NULL && uMsg == MOM_DONE)
if (uMsg == MOM_DONE)
{
self->Callback(self->CallbackData);
SetEvent(self->BufferDoneEvent);
}
}
@ -524,9 +675,9 @@ static bool IgnoreMIDIVolume(UINT id)
// Now try to create an IMMDeviceEnumerator interface. If it succeeds,
// we know we're using the new audio stack introduced with Vista and
// should ignore this MIDI device's volume control.
if (SUCCEEDED(CoCreateInstance(__uuidof(MMDeviceEnumerator), NULL, CLSCTX_ALL,
if (SUCCEEDED(CoCreateInstance(__uuidof(MMDeviceEnumerator), nullptr, CLSCTX_ALL,
__uuidof(IMMDeviceEnumerator), (void**)&enumerator))
&& enumerator != NULL)
&& enumerator != nullptr)
{
enumerator->Release();
return true;
@ -540,7 +691,13 @@ static bool IgnoreMIDIVolume(UINT id)
MIDIDevice *CreateWinMIDIDevice(int mididevice)
{
return new WinMIDIDevice(mididevice);
auto d = new WinMIDIDevice(mididevice);
if (d->BufferDoneEvent == nullptr || d->ExitEvent == nullptr)
{
delete d;
return nullptr;
}
return d;
}
#endif

View file

@ -111,10 +111,6 @@ extern char MIDI_CommonLengths[15];
XMISong::XMISong (FileReader &reader, EMidiDevice type, const char *args)
: MIDIStreamer(type, args), MusHeader(0), Songs(0)
{
if (!CheckExitEvent())
{
return;
}
SongLen = reader.GetLength();
MusHeader = new uint8_t[SongLen];
if (reader.Read(MusHeader, SongLen) != SongLen)