mirror of
https://github.com/ZDoom/gzdoom-gles.git
synced 2024-11-10 23:01:59 +00:00
- Changed MUS playback to use MIDI streams, like it did during the early days
of ZDoom, except now the entire song isn't prebuffered in large chunks, so I can insert MIDI events into the playback with fairly low latency. This should offer more precise timing than the combination of low-level MIDI and WaitForSingleObject timeouts. SVN r783 (trunk)
This commit is contained in:
parent
ef99d9c057
commit
69cebb7e57
6 changed files with 357 additions and 187 deletions
|
@ -1,4 +1,9 @@
|
||||||
March 3, 2008
|
March 3, 2008
|
||||||
|
- Changed MUS playback to use MIDI streams, like it did during the early days
|
||||||
|
of ZDoom, except now the entire song isn't prebuffered in large chunks, so
|
||||||
|
I can insert MIDI events into the playback with fairly low latency. This
|
||||||
|
should offer more precise timing than the combination of low-level MIDI and
|
||||||
|
WaitForSingleObject timeouts.
|
||||||
- Fixed: PTR_BounceTraverse only checked for projectiles that were too
|
- Fixed: PTR_BounceTraverse only checked for projectiles that were too
|
||||||
high to pass through two-sided lines, but not ones that were too low.
|
high to pass through two-sided lines, but not ones that were too low.
|
||||||
- Fixed: SBARINFO couldn't detect the extreme death damage type for the
|
- Fixed: SBARINFO couldn't detect the extreme death damage type for the
|
||||||
|
|
|
@ -112,6 +112,10 @@ bool MusInfo::SetPosition (int order)
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void MusInfo::ServiceEvent ()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
void I_InitMusic (void)
|
void I_InitMusic (void)
|
||||||
{
|
{
|
||||||
static bool setatterm = false;
|
static bool setatterm = false;
|
||||||
|
|
|
@ -42,6 +42,7 @@ public:
|
||||||
virtual bool IsMIDI () const = 0;
|
virtual bool IsMIDI () const = 0;
|
||||||
virtual bool IsValid () const = 0;
|
virtual bool IsValid () const = 0;
|
||||||
virtual bool SetPosition (int order);
|
virtual bool SetPosition (int order);
|
||||||
|
virtual void ServiceEvent ();
|
||||||
|
|
||||||
enum EState
|
enum EState
|
||||||
{
|
{
|
||||||
|
@ -55,6 +56,18 @@ public:
|
||||||
// MUS file played with MIDI output messages --------------------------------
|
// MUS file played with MIDI output messages --------------------------------
|
||||||
|
|
||||||
#ifdef _WIN32
|
#ifdef _WIN32
|
||||||
|
struct SHORTMIDIEVENT
|
||||||
|
{
|
||||||
|
DWORD dwDeltaTime;
|
||||||
|
DWORD dwStreamID;
|
||||||
|
DWORD dwEvent;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct VOLSYSEXEVENT : SHORTMIDIEVENT
|
||||||
|
{
|
||||||
|
BYTE SysEx[8];
|
||||||
|
};
|
||||||
|
|
||||||
class MUSSong2 : public MusInfo
|
class MUSSong2 : public MusInfo
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
|
@ -74,26 +87,41 @@ protected:
|
||||||
static DWORD WINAPI PlayerProc (LPVOID lpParameter);
|
static DWORD WINAPI PlayerProc (LPVOID lpParameter);
|
||||||
void OutputVolume (DWORD volume);
|
void OutputVolume (DWORD volume);
|
||||||
int SendCommand ();
|
int SendCommand ();
|
||||||
|
bool TranslateSong(const BYTE *buffer, size_t len);
|
||||||
|
int CountEvents(const BYTE *buffer, size_t len);
|
||||||
|
int FillBuffer(int buffer_num, int max_events, DWORD max_time);
|
||||||
|
void ServiceEvent();
|
||||||
|
static void CALLBACK Callback(HMIDIOUT handle, UINT uMsg, DWORD_PTR dwInstance, DWORD dwParam1, DWORD dwParam2);
|
||||||
|
|
||||||
enum
|
enum
|
||||||
{
|
{
|
||||||
SEND_DONE,
|
MAX_EVENTS = 128
|
||||||
SEND_WAIT
|
|
||||||
};
|
};
|
||||||
|
|
||||||
HMIDIOUT MidiOut;
|
enum
|
||||||
HANDLE PlayerThread;
|
{
|
||||||
HANDLE PauseEvent;
|
SONG_MORE,
|
||||||
HANDLE ExitEvent;
|
SONG_DONE,
|
||||||
HANDLE VolumeChangeEvent;
|
SONG_ERROR
|
||||||
|
};
|
||||||
|
|
||||||
|
HMIDISTRM MidiOut;
|
||||||
DWORD SavedVolume;
|
DWORD SavedVolume;
|
||||||
bool VolumeWorks;
|
bool VolumeWorks;
|
||||||
|
|
||||||
BYTE *MusBuffer;
|
|
||||||
MUSHeader *MusHeader;
|
MUSHeader *MusHeader;
|
||||||
|
BYTE *MusBuffer;
|
||||||
BYTE LastVelocity[16];
|
BYTE LastVelocity[16];
|
||||||
BYTE ChannelVolumes[16];
|
BYTE ChannelVolumes[16];
|
||||||
size_t MusP, MaxMusP;
|
size_t MusP, MaxMusP;
|
||||||
|
VOLSYSEXEVENT FullVolEvent;
|
||||||
|
SHORTMIDIEVENT Events[2][MAX_EVENTS];
|
||||||
|
MIDIHDR Buffer[2];
|
||||||
|
int BufferNum;
|
||||||
|
int EndQueued;
|
||||||
|
bool VolumeChanged;
|
||||||
|
bool Restarting;
|
||||||
|
DWORD NewVolume;
|
||||||
};
|
};
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
|
|
@ -6,6 +6,9 @@
|
||||||
|
|
||||||
extern DWORD midivolume;
|
extern DWORD midivolume;
|
||||||
extern UINT mididevice;
|
extern UINT mididevice;
|
||||||
|
extern HANDLE MusicEvent;
|
||||||
|
|
||||||
|
#define MAX_TIME (10)
|
||||||
|
|
||||||
EXTERN_CVAR (Float, snd_midivolume)
|
EXTERN_CVAR (Float, snd_midivolume)
|
||||||
|
|
||||||
|
@ -28,17 +31,17 @@ static const BYTE CtrlTranslate[15] =
|
||||||
121, // reset all controllers
|
121, // reset all controllers
|
||||||
};
|
};
|
||||||
|
|
||||||
MUSSong2::MUSSong2 (FILE *file, char * musiccache, int len)
|
MUSSong2::MUSSong2 (FILE *file, char *musiccache, int len)
|
||||||
: MidiOut (0), PlayerThread (0),
|
: MidiOut(0), MusHeader(0), MusBuffer(0)
|
||||||
PauseEvent (0), ExitEvent (0), VolumeChangeEvent (0),
|
|
||||||
MusBuffer (0), MusHeader (0)
|
|
||||||
{
|
{
|
||||||
MusHeader = (MUSHeader *)new BYTE[len];
|
MusHeader = (MUSHeader *)new BYTE[len];
|
||||||
|
|
||||||
if (file != NULL)
|
if (file != NULL)
|
||||||
{
|
{
|
||||||
if (fread (MusHeader, 1, len, file) != (size_t)len)
|
if (fread(MusHeader, 1, len, file) != (size_t)len)
|
||||||
|
{
|
||||||
return;
|
return;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
@ -47,28 +50,26 @@ MUSSong2::MUSSong2 (FILE *file, char * musiccache, int len)
|
||||||
|
|
||||||
// Do some validation of the MUS file
|
// Do some validation of the MUS file
|
||||||
if (MusHeader->Magic != MAKE_ID('M','U','S','\x1a'))
|
if (MusHeader->Magic != MAKE_ID('M','U','S','\x1a'))
|
||||||
|
{
|
||||||
return;
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (LittleShort(MusHeader->NumChans) > 15)
|
if (LittleShort(MusHeader->NumChans) > 15)
|
||||||
|
{
|
||||||
return;
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
ExitEvent = CreateEvent (NULL, FALSE, FALSE, NULL);
|
FullVolEvent.dwDeltaTime = 0;
|
||||||
if (ExitEvent == NULL)
|
FullVolEvent.dwStreamID = 0;
|
||||||
{
|
FullVolEvent.dwEvent = MEVT_LONGMSG | 8;
|
||||||
Printf (PRINT_BOLD, "Could not create exit event for MIDI playback\n");
|
FullVolEvent.SysEx[0] = 0xf0;
|
||||||
return;
|
FullVolEvent.SysEx[1] = 0x7f;
|
||||||
}
|
FullVolEvent.SysEx[2] = 0x7f;
|
||||||
VolumeChangeEvent = CreateEvent (NULL, FALSE, FALSE, NULL);
|
FullVolEvent.SysEx[3] = 0x04;
|
||||||
if (VolumeChangeEvent == NULL)
|
FullVolEvent.SysEx[4] = 0x01;
|
||||||
{
|
FullVolEvent.SysEx[5] = 0x7f;
|
||||||
Printf (PRINT_BOLD, "Could not create volume event for MIDI playback\n");
|
FullVolEvent.SysEx[6] = 0x7f;
|
||||||
return;
|
FullVolEvent.SysEx[7] = 0xf7;
|
||||||
}
|
|
||||||
PauseEvent = CreateEvent (NULL, FALSE, FALSE, NULL);
|
|
||||||
if (PauseEvent == NULL)
|
|
||||||
{
|
|
||||||
Printf (PRINT_BOLD, "Could not create pause event for MIDI playback\n");
|
|
||||||
}
|
|
||||||
|
|
||||||
MusBuffer = (BYTE *)MusHeader + LittleShort(MusHeader->SongStart);
|
MusBuffer = (BYTE *)MusHeader + LittleShort(MusHeader->SongStart);
|
||||||
MaxMusP = MIN<int> (LittleShort(MusHeader->SongLen), len - LittleShort(MusHeader->SongStart));
|
MaxMusP = MIN<int> (LittleShort(MusHeader->SongLen), len - LittleShort(MusHeader->SongStart));
|
||||||
|
@ -78,23 +79,6 @@ MUSSong2::MUSSong2 (FILE *file, char * musiccache, int len)
|
||||||
MUSSong2::~MUSSong2 ()
|
MUSSong2::~MUSSong2 ()
|
||||||
{
|
{
|
||||||
Stop ();
|
Stop ();
|
||||||
|
|
||||||
if (PauseEvent != NULL)
|
|
||||||
{
|
|
||||||
CloseHandle (PauseEvent);
|
|
||||||
}
|
|
||||||
if (ExitEvent != NULL)
|
|
||||||
{
|
|
||||||
CloseHandle (ExitEvent);
|
|
||||||
}
|
|
||||||
if (VolumeChangeEvent != NULL)
|
|
||||||
{
|
|
||||||
CloseHandle (VolumeChangeEvent);
|
|
||||||
}
|
|
||||||
if (MusHeader != 0)
|
|
||||||
{
|
|
||||||
delete[] ((BYTE *)MusHeader);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool MUSSong2::IsMIDI () const
|
bool MUSSong2::IsMIDI () const
|
||||||
|
@ -109,14 +93,31 @@ bool MUSSong2::IsValid () const
|
||||||
|
|
||||||
void MUSSong2::Play (bool looping)
|
void MUSSong2::Play (bool looping)
|
||||||
{
|
{
|
||||||
DWORD tid;
|
UINT dev_id;
|
||||||
|
|
||||||
m_Status = STATE_Stopped;
|
m_Status = STATE_Stopped;
|
||||||
m_Looping = looping;
|
m_Looping = looping;
|
||||||
|
EndQueued = false;
|
||||||
|
VolumeChanged = false;
|
||||||
|
Restarting = false;
|
||||||
|
|
||||||
if (MMSYSERR_NOERROR != midiOutOpen (&MidiOut, mididevice<0? MIDI_MAPPER:mididevice, 0, 0, CALLBACK_NULL))
|
dev_id = MAX(mididevice, 0u);
|
||||||
|
if (MMSYSERR_NOERROR != midiStreamOpen(&MidiOut, &dev_id, 1, (DWORD_PTR)Callback, (DWORD_PTR)this, CALLBACK_FUNCTION))
|
||||||
{
|
{
|
||||||
Printf (PRINT_BOLD, "Could not open MIDI out device\n");
|
Printf(PRINT_BOLD, "Could not open MIDI out device\n");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set time division and tempo.
|
||||||
|
MIDIPROPTIMEDIV timediv = { sizeof(MIDIPROPTIMEDIV), 140 };
|
||||||
|
MIDIPROPTEMPO tempo = { sizeof(MIDIPROPTEMPO), 1000000 };
|
||||||
|
|
||||||
|
if (MMSYSERR_NOERROR != midiStreamProperty(MidiOut, (LPBYTE)&timediv, MIDIPROP_SET | MIDIPROP_TIMEDIV) ||
|
||||||
|
MMSYSERR_NOERROR != midiStreamProperty(MidiOut, (LPBYTE)&tempo, MIDIPROP_SET | MIDIPROP_TEMPO))
|
||||||
|
{
|
||||||
|
Printf(PRINT_BOLD, "Setting MIDI stream speed failed\n");
|
||||||
|
midiStreamClose(MidiOut);
|
||||||
|
MidiOut = NULL;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -126,48 +127,98 @@ void MUSSong2::Play (bool looping)
|
||||||
// each channel. Because every General MIDI-compliant device must support
|
// each channel. Because every General MIDI-compliant device must support
|
||||||
// this controller, it is the most reliable means of setting the volume.
|
// this controller, it is the most reliable means of setting the volume.
|
||||||
|
|
||||||
VolumeWorks = (MMSYSERR_NOERROR == midiOutGetVolume (MidiOut, &SavedVolume));
|
VolumeWorks = (MMSYSERR_NOERROR == midiOutGetVolume((HMIDIOUT)MidiOut, &SavedVolume));
|
||||||
if (VolumeWorks)
|
if (VolumeWorks)
|
||||||
{
|
{
|
||||||
VolumeWorks &= (MMSYSERR_NOERROR == midiOutSetVolume (MidiOut, 0xffffffff));
|
VolumeWorks &= (MMSYSERR_NOERROR == midiOutSetVolume((HMIDIOUT)MidiOut, 0xffffffff));
|
||||||
|
}
|
||||||
|
if (!VolumeWorks)
|
||||||
|
{ // Send the standard SysEx message for full master volume
|
||||||
|
memset(&Buffer[0], 0, sizeof(Buffer[0]));
|
||||||
|
Buffer[0].lpData = (LPSTR)&FullVolEvent;
|
||||||
|
Buffer[0].dwBufferLength = sizeof(FullVolEvent);
|
||||||
|
Buffer[0].dwBytesRecorded = sizeof(FullVolEvent);
|
||||||
|
|
||||||
|
if (MMSYSERR_NOERROR == midiOutPrepareHeader((HMIDIOUT)MidiOut, &Buffer[0], sizeof(Buffer[0])))
|
||||||
|
{
|
||||||
|
midiStreamOut(MidiOut, &Buffer[0], sizeof(Buffer[0]));
|
||||||
|
}
|
||||||
|
BufferNum = 1;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// Send the standard SysEx message for full master volume
|
BufferNum = 0;
|
||||||
BYTE volmess[] = { 0xf0, 0x7f, 0x7f, 0x04, 0x01, 0x7f, 0x7f, 0xf7 };
|
|
||||||
MIDIHDR hdr = { (LPSTR)volmess, sizeof(volmess), };
|
|
||||||
|
|
||||||
if (MMSYSERR_NOERROR == midiOutPrepareHeader (MidiOut, &hdr, sizeof(hdr)))
|
|
||||||
{
|
|
||||||
midiOutLongMsg (MidiOut, &hdr, sizeof(hdr));
|
|
||||||
while (MIDIERR_STILLPLAYING == midiOutUnprepareHeader (MidiOut, &hdr, sizeof(hdr)))
|
|
||||||
{
|
|
||||||
Sleep (10);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
snd_midivolume.Callback(); // set volume to current music's properties
|
snd_midivolume.Callback(); // set volume to current music's properties
|
||||||
PlayerThread = CreateThread (NULL, 0, PlayerProc, this, 0, &tid);
|
for (int i = 0; i < 16; ++i)
|
||||||
if (PlayerThread == NULL)
|
|
||||||
{
|
{
|
||||||
if (VolumeWorks)
|
LastVelocity[i] = 64;
|
||||||
{
|
ChannelVolumes[i] = 127;
|
||||||
midiOutSetVolume (MidiOut, SavedVolume);
|
|
||||||
}
|
|
||||||
midiOutClose (MidiOut);
|
|
||||||
MidiOut = NULL;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
m_Status = STATE_Playing;
|
// Fill the initial buffers for the song.
|
||||||
|
do
|
||||||
|
{
|
||||||
|
int res = FillBuffer(BufferNum, MAX_EVENTS, MAX_TIME);
|
||||||
|
if (res == SONG_MORE)
|
||||||
|
{
|
||||||
|
if (MMSYSERR_NOERROR != midiStreamOut(MidiOut, &Buffer[BufferNum], sizeof(Buffer[0])))
|
||||||
|
{
|
||||||
|
Stop();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
BufferNum ^= 1;
|
||||||
|
}
|
||||||
|
else if (res == SONG_DONE)
|
||||||
|
{
|
||||||
|
if (looping)
|
||||||
|
{
|
||||||
|
MusP = 0;
|
||||||
|
if (SONG_MORE == FillBuffer(BufferNum, MAX_EVENTS, MAX_TIME))
|
||||||
|
{
|
||||||
|
if (MMSYSERR_NOERROR != midiStreamOut(MidiOut, &Buffer[BufferNum], sizeof(MIDIHDR)))
|
||||||
|
{
|
||||||
|
Stop();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
BufferNum ^= 1;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Stop();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
EndQueued = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Stop();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
while (BufferNum != 0);
|
||||||
|
|
||||||
|
if (MMSYSERR_NOERROR != midiStreamRestart(MidiOut))
|
||||||
|
{
|
||||||
|
Stop();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
m_Status = STATE_Playing;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void MUSSong2::Pause ()
|
void MUSSong2::Pause ()
|
||||||
{
|
{
|
||||||
if (m_Status == STATE_Playing)
|
if (m_Status == STATE_Playing)
|
||||||
{
|
{
|
||||||
SetEvent (PauseEvent);
|
|
||||||
m_Status = STATE_Paused;
|
m_Status = STATE_Paused;
|
||||||
|
OutputVolume(0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -175,30 +226,28 @@ void MUSSong2::Resume ()
|
||||||
{
|
{
|
||||||
if (m_Status == STATE_Paused)
|
if (m_Status == STATE_Paused)
|
||||||
{
|
{
|
||||||
SetEvent (PauseEvent);
|
OutputVolume(midivolume & 0xffff);
|
||||||
m_Status = STATE_Playing;
|
m_Status = STATE_Playing;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void MUSSong2::Stop ()
|
void MUSSong2::Stop ()
|
||||||
{
|
{
|
||||||
if (PlayerThread)
|
EndQueued = 2;
|
||||||
{
|
|
||||||
SetEvent (ExitEvent);
|
|
||||||
WaitForSingleObject (PlayerThread, INFINITE);
|
|
||||||
CloseHandle (PlayerThread);
|
|
||||||
PlayerThread = NULL;
|
|
||||||
}
|
|
||||||
if (MidiOut)
|
if (MidiOut)
|
||||||
{
|
{
|
||||||
midiOutReset (MidiOut);
|
midiStreamStop(MidiOut);
|
||||||
|
midiOutReset((HMIDIOUT)MidiOut);
|
||||||
if (VolumeWorks)
|
if (VolumeWorks)
|
||||||
{
|
{
|
||||||
midiOutSetVolume (MidiOut, SavedVolume);
|
midiOutSetVolume((HMIDIOUT)MidiOut, SavedVolume);
|
||||||
}
|
}
|
||||||
midiOutClose (MidiOut);
|
midiOutUnprepareHeader((HMIDIOUT)MidiOut, &Buffer[0], sizeof(MIDIHDR));
|
||||||
|
midiOutUnprepareHeader((HMIDIOUT)MidiOut, &Buffer[1], sizeof(MIDIHDR));
|
||||||
|
midiStreamClose(MidiOut);
|
||||||
MidiOut = NULL;
|
MidiOut = NULL;
|
||||||
}
|
}
|
||||||
|
m_Status = STATE_Stopped;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool MUSSong2::IsPlaying ()
|
bool MUSSong2::IsPlaying ()
|
||||||
|
@ -208,99 +257,129 @@ bool MUSSong2::IsPlaying ()
|
||||||
|
|
||||||
void MUSSong2::SetVolume (float volume)
|
void MUSSong2::SetVolume (float volume)
|
||||||
{
|
{
|
||||||
SetEvent (VolumeChangeEvent);
|
OutputVolume(midivolume & 0xffff);
|
||||||
}
|
|
||||||
|
|
||||||
DWORD WINAPI MUSSong2::PlayerProc (LPVOID lpParameter)
|
|
||||||
{
|
|
||||||
MUSSong2 *song = (MUSSong2 *)lpParameter;
|
|
||||||
HANDLE events[2] = { song->ExitEvent, song->PauseEvent };
|
|
||||||
bool waited;
|
|
||||||
int i;
|
|
||||||
|
|
||||||
SetThreadPriority (GetCurrentThread (), THREAD_PRIORITY_TIME_CRITICAL);
|
|
||||||
|
|
||||||
for (i = 0; i < 16; ++i)
|
|
||||||
{
|
|
||||||
song->LastVelocity[i] = 64;
|
|
||||||
song->ChannelVolumes[i] = 127;
|
|
||||||
}
|
|
||||||
|
|
||||||
song->OutputVolume (midivolume & 0xffff);
|
|
||||||
|
|
||||||
do
|
|
||||||
{
|
|
||||||
waited = false;
|
|
||||||
song->MusP = 0;
|
|
||||||
|
|
||||||
while (song->SendCommand () == SEND_WAIT)
|
|
||||||
{
|
|
||||||
DWORD time = 0;
|
|
||||||
BYTE t;
|
|
||||||
do
|
|
||||||
{
|
|
||||||
t = song->MusBuffer[song->MusP++];
|
|
||||||
time = (time << 7) | (t & 127);
|
|
||||||
}
|
|
||||||
while (t & 128);
|
|
||||||
|
|
||||||
waited = true;
|
|
||||||
|
|
||||||
// Wait for the exit or pause event or the next note
|
|
||||||
switch (WaitForMultipleObjects (2, events, FALSE, time * 1000 / 140))
|
|
||||||
{
|
|
||||||
case WAIT_OBJECT_0:
|
|
||||||
song->m_Status = STATE_Stopped;
|
|
||||||
return 0;
|
|
||||||
|
|
||||||
case WAIT_OBJECT_0+1:
|
|
||||||
// Go paused
|
|
||||||
song->OutputVolume (0);
|
|
||||||
// Wait for the exit or pause event
|
|
||||||
if (WAIT_OBJECT_0 == WaitForMultipleObjects (2, events, FALSE, INFINITE))
|
|
||||||
{
|
|
||||||
song->m_Status = STATE_Stopped;
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
song->OutputVolume (midivolume & 0xffff);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if the volume needs changing
|
|
||||||
if (WAIT_OBJECT_0 == WaitForSingleObject (song->VolumeChangeEvent, 0))
|
|
||||||
{
|
|
||||||
song->OutputVolume (midivolume & 0xffff);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
while (waited && song->m_Looping);
|
|
||||||
|
|
||||||
song->m_Status = STATE_Stopped;
|
|
||||||
return 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void MUSSong2::OutputVolume (DWORD volume)
|
void MUSSong2::OutputVolume (DWORD volume)
|
||||||
{
|
{
|
||||||
for (int i = 0; i < 16; ++i)
|
NewVolume = volume;
|
||||||
|
VolumeChanged = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void CALLBACK MUSSong2::Callback(HMIDIOUT hOut, UINT uMsg, DWORD_PTR dwInstance, DWORD dwParam1, DWORD dwParam2)
|
||||||
|
{
|
||||||
|
MUSSong2 *self = (MUSSong2 *)dwInstance;
|
||||||
|
|
||||||
|
if (self->EndQueued > 1)
|
||||||
{
|
{
|
||||||
BYTE courseVol = (BYTE)(((ChannelVolumes[i]+1) * volume) >> 16);
|
return;
|
||||||
midiOutShortMsg (MidiOut, i | MIDI_CTRLCHANGE | (7<<8) | (courseVol<<16));
|
}
|
||||||
|
if (uMsg == MOM_DONE)
|
||||||
|
{
|
||||||
|
SetEvent(MusicEvent);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
int MUSSong2::SendCommand ()
|
void MUSSong2::ServiceEvent()
|
||||||
{
|
{
|
||||||
BYTE event = 0;
|
if (EndQueued == 1)
|
||||||
|
{
|
||||||
|
Stop();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (MMSYSERR_NOERROR != midiOutUnprepareHeader((HMIDIOUT)MidiOut, &Buffer[BufferNum], sizeof(MIDIHDR)))
|
||||||
|
{
|
||||||
|
Printf ("Failed unpreparing MIDI header.\n");
|
||||||
|
Stop();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
fill:
|
||||||
|
switch (FillBuffer(BufferNum, MAX_EVENTS, MAX_TIME))
|
||||||
|
{
|
||||||
|
case SONG_MORE:
|
||||||
|
if (MMSYSERR_NOERROR != midiStreamOut(MidiOut, &Buffer[BufferNum], sizeof(MIDIHDR)))
|
||||||
|
{
|
||||||
|
Printf ("Failed streaming MIDI buffer.\n");
|
||||||
|
Stop();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
BufferNum ^= 1;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case SONG_DONE:
|
||||||
|
if (m_Looping)
|
||||||
|
{
|
||||||
|
MusP = 0;
|
||||||
|
Restarting = true;
|
||||||
|
goto fill;
|
||||||
|
}
|
||||||
|
EndQueued = 1;
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
Stop();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns SONG_MORE if the buffer was prepared with data.
|
||||||
|
// Returns SONG_DONE if the song's end was reached. The buffer will never have data in this case.
|
||||||
|
// Returns SONG_ERROR if there was a problem preparing the buffer.
|
||||||
|
int MUSSong2::FillBuffer(int buffer_num, int max_events, DWORD max_time)
|
||||||
|
{
|
||||||
if (MusP >= MaxMusP)
|
if (MusP >= MaxMusP)
|
||||||
return SEND_DONE;
|
{
|
||||||
|
return SONG_DONE;
|
||||||
|
}
|
||||||
|
|
||||||
while (MusP < MaxMusP && (event & 0x70) != MUS_SCOREEND)
|
int i = 0;
|
||||||
|
SHORTMIDIEVENT *events = Events[buffer_num];
|
||||||
|
DWORD tot_time = 0;
|
||||||
|
DWORD time = 0;
|
||||||
|
|
||||||
|
// If the volume has changed, stick those events at the start of this buffer.
|
||||||
|
if (VolumeChanged)
|
||||||
|
{
|
||||||
|
VolumeChanged = false;
|
||||||
|
for (; i < 16; ++i)
|
||||||
|
{
|
||||||
|
BYTE courseVol = (BYTE)(((ChannelVolumes[i]+1) * NewVolume) >> 16);
|
||||||
|
events[i].dwDeltaTime = 0;
|
||||||
|
events[i].dwStreamID = 0;
|
||||||
|
events[i].dwEvent = MEVT_SHORTMSG | MIDI_CTRLCHANGE | i | (7<<8) | (courseVol<<16);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the song is starting over, stop all notes in case any were left hanging.
|
||||||
|
if (Restarting)
|
||||||
|
{
|
||||||
|
Restarting = false;
|
||||||
|
for (int j = 0; j < 16; ++i, ++j)
|
||||||
|
{
|
||||||
|
events[i].dwDeltaTime = 0;
|
||||||
|
events[i].dwStreamID = 0;
|
||||||
|
events[i].dwEvent = MEVT_SHORTMSG | MIDI_NOTEOFF | i | (60 << 8) | (64<<16);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Play nothing while paused.
|
||||||
|
if (m_Status == STATE_Paused)
|
||||||
|
{
|
||||||
|
time = max_time;
|
||||||
|
goto end;
|
||||||
|
}
|
||||||
|
|
||||||
|
// The final event is for a NOP to hold the delay from the last event.
|
||||||
|
max_events--;
|
||||||
|
|
||||||
|
for (; i < max_events && tot_time <= max_time; ++i)
|
||||||
{
|
{
|
||||||
BYTE mid1, mid2;
|
BYTE mid1, mid2;
|
||||||
BYTE channel;
|
BYTE channel;
|
||||||
BYTE t = 0, status;
|
BYTE t = 0, status;
|
||||||
|
BYTE event = MusBuffer[MusP++];
|
||||||
event = MusBuffer[MusP++];
|
|
||||||
|
|
||||||
if ((event & 0x70) != MUS_SCOREEND)
|
if ((event & 0x70) != MUS_SCOREEND)
|
||||||
{
|
{
|
||||||
|
@ -333,7 +412,7 @@ int MUSSong2::SendCommand ()
|
||||||
mid1 = t & 127;
|
mid1 = t & 127;
|
||||||
if (t & 128)
|
if (t & 128)
|
||||||
{
|
{
|
||||||
LastVelocity[channel] = MusBuffer[MusP++];;
|
LastVelocity[channel] = MusBuffer[MusP++];
|
||||||
}
|
}
|
||||||
mid2 = LastVelocity[channel];
|
mid2 = LastVelocity[channel];
|
||||||
break;
|
break;
|
||||||
|
@ -376,20 +455,43 @@ int MUSSong2::SendCommand ()
|
||||||
|
|
||||||
case MUS_SCOREEND:
|
case MUS_SCOREEND:
|
||||||
default:
|
default:
|
||||||
return SEND_DONE;
|
MusP = MaxMusP;
|
||||||
break;
|
goto end;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (MMSYSERR_NOERROR != midiOutShortMsg (MidiOut, status | (mid1 << 8) | (mid2 << 16)))
|
events[i].dwDeltaTime = time;
|
||||||
{
|
events[i].dwStreamID = 0;
|
||||||
return SEND_DONE;
|
events[i].dwEvent = MEVT_SHORTMSG | status | (mid1 << 8) | (mid2 << 16);
|
||||||
}
|
|
||||||
|
time = 0;
|
||||||
if (event & 128)
|
if (event & 128)
|
||||||
{
|
{
|
||||||
return SEND_WAIT;
|
do
|
||||||
|
{
|
||||||
|
t = MusBuffer[MusP++];
|
||||||
|
time = (time << 7) | (t & 127);
|
||||||
|
}
|
||||||
|
while (t & 128);
|
||||||
}
|
}
|
||||||
|
tot_time += time;
|
||||||
}
|
}
|
||||||
|
end:
|
||||||
return SEND_DONE;
|
if (time != 0)
|
||||||
|
{
|
||||||
|
events[i].dwDeltaTime = time;
|
||||||
|
events[i].dwStreamID = 0;
|
||||||
|
events[i].dwEvent = MEVT_NOP;
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
memset(&Buffer[buffer_num], 0, sizeof(MIDIHDR));
|
||||||
|
Buffer[buffer_num].lpData = (LPSTR)events;
|
||||||
|
Buffer[buffer_num].dwBufferLength = sizeof(events[0]) * i;
|
||||||
|
Buffer[buffer_num].dwBytesRecorded = sizeof(events[0]) * i;
|
||||||
|
if (MMSYSERR_NOERROR != midiOutPrepareHeader((HMIDIOUT)MidiOut, &Buffer[buffer_num], sizeof(MIDIHDR)))
|
||||||
|
{
|
||||||
|
Printf ("Preparing MIDI header failed.\n");
|
||||||
|
return SONG_ERROR;
|
||||||
|
}
|
||||||
|
return SONG_MORE;
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
|
@ -95,6 +95,7 @@
|
||||||
#include "gameconfigfile.h"
|
#include "gameconfigfile.h"
|
||||||
#include "win32iface.h"
|
#include "win32iface.h"
|
||||||
#include "templates.h"
|
#include "templates.h"
|
||||||
|
#include "i_musicinterns.h"
|
||||||
|
|
||||||
#define DINPUT_BUFFERSIZE 32
|
#define DINPUT_BUFFERSIZE 32
|
||||||
|
|
||||||
|
@ -109,6 +110,7 @@ BOOL DI_InitJoy (void);
|
||||||
|
|
||||||
extern HINSTANCE g_hInst;
|
extern HINSTANCE g_hInst;
|
||||||
extern DWORD SessionID;
|
extern DWORD SessionID;
|
||||||
|
extern HANDLE MusicEvent;
|
||||||
|
|
||||||
extern void ShowEAXEditor ();
|
extern void ShowEAXEditor ();
|
||||||
extern bool SpawnEAXWindow;
|
extern bool SpawnEAXWindow;
|
||||||
|
@ -1929,22 +1931,34 @@ void I_GetEvent ()
|
||||||
|
|
||||||
// Briefly enter an alertable state so that if a secondary thread
|
// Briefly enter an alertable state so that if a secondary thread
|
||||||
// crashed, we will execute the APC it sent now.
|
// crashed, we will execute the APC it sent now.
|
||||||
SleepEx (0, TRUE);
|
if (MusicEvent != NULL)
|
||||||
|
{
|
||||||
// for (;;) {
|
DWORD res;
|
||||||
while (PeekMessage (&mess, NULL, 0, 0, PM_REMOVE))
|
do
|
||||||
{
|
{
|
||||||
if (mess.message == WM_QUIT)
|
res = WaitForSingleObjectEx(MusicEvent, 0, TRUE);
|
||||||
exit (mess.wParam);
|
|
||||||
if (EAXEditWindow == 0 || !IsDialogMessage (EAXEditWindow, &mess))
|
|
||||||
{
|
|
||||||
TranslateMessage (&mess);
|
|
||||||
DispatchMessage (&mess);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
// if (havefocus || netgame || gamestate != GS_LEVEL)
|
while (res == WAIT_IO_COMPLETION);
|
||||||
// break;
|
if (res == WAIT_OBJECT_0 && currSong != NULL)
|
||||||
// }
|
{
|
||||||
|
currSong->ServiceEvent();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
SleepEx (0, TRUE);
|
||||||
|
}
|
||||||
|
|
||||||
|
while (PeekMessage (&mess, NULL, 0, 0, PM_REMOVE))
|
||||||
|
{
|
||||||
|
if (mess.message == WM_QUIT)
|
||||||
|
exit (mess.wParam);
|
||||||
|
if (EAXEditWindow == 0 || !IsDialogMessage (EAXEditWindow, &mess))
|
||||||
|
{
|
||||||
|
TranslateMessage (&mess);
|
||||||
|
DispatchMessage (&mess);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
KeyRead ();
|
KeyRead ();
|
||||||
|
|
||||||
|
|
|
@ -62,6 +62,7 @@
|
||||||
#include "templates.h"
|
#include "templates.h"
|
||||||
#include "gameconfigfile.h"
|
#include "gameconfigfile.h"
|
||||||
#include "v_font.h"
|
#include "v_font.h"
|
||||||
|
#include "i_musicinterns.h"
|
||||||
|
|
||||||
#include "stats.h"
|
#include "stats.h"
|
||||||
|
|
||||||
|
@ -85,6 +86,7 @@ UINT TimerPeriod;
|
||||||
UINT TimerEventID;
|
UINT TimerEventID;
|
||||||
UINT MillisecondsPerTic;
|
UINT MillisecondsPerTic;
|
||||||
HANDLE NewTicArrived;
|
HANDLE NewTicArrived;
|
||||||
|
HANDLE MusicEvent;
|
||||||
uint32 LanguageIDs[4];
|
uint32 LanguageIDs[4];
|
||||||
void CalculateCPUSpeed ();
|
void CalculateCPUSpeed ();
|
||||||
|
|
||||||
|
@ -173,7 +175,15 @@ int I_WaitForTicEvent (int prevtic)
|
||||||
{
|
{
|
||||||
while (prevtic >= tics)
|
while (prevtic >= tics)
|
||||||
{
|
{
|
||||||
WaitForSingleObject (NewTicArrived, 1000/TICRATE);
|
HANDLE handles[2] = { NewTicArrived, MusicEvent };
|
||||||
|
switch(WaitForMultipleObjects(1 + (MusicEvent != NULL), handles, FALSE, 1000/TICRATE))
|
||||||
|
{
|
||||||
|
case WAIT_OBJECT_0 + 1:
|
||||||
|
if (currSong != NULL)
|
||||||
|
{
|
||||||
|
currSong->ServiceEvent();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return tics;
|
return tics;
|
||||||
|
@ -435,6 +445,11 @@ void I_Init (void)
|
||||||
I_WaitForTic = I_WaitForTicPolled;
|
I_WaitForTic = I_WaitForTicPolled;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ((MusicEvent = CreateEvent(NULL, FALSE, FALSE, NULL)) == NULL)
|
||||||
|
{
|
||||||
|
Printf ("Creation of music event failed.");
|
||||||
|
}
|
||||||
|
|
||||||
atterm (I_ShutdownSound);
|
atterm (I_ShutdownSound);
|
||||||
I_InitSound ();
|
I_InitSound ();
|
||||||
}
|
}
|
||||||
|
@ -500,6 +515,8 @@ void I_Quit (void)
|
||||||
timeKillEvent (TimerEventID);
|
timeKillEvent (TimerEventID);
|
||||||
if (NewTicArrived)
|
if (NewTicArrived)
|
||||||
CloseHandle (NewTicArrived);
|
CloseHandle (NewTicArrived);
|
||||||
|
if (MusicEvent)
|
||||||
|
CloseHandle (MusicEvent);
|
||||||
|
|
||||||
timeEndPeriod (TimerPeriod);
|
timeEndPeriod (TimerPeriod);
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue