From 69cebb7e578e995328ef11ec7b1d080ba81a0e30 Mon Sep 17 00:00:00 2001 From: Randy Heit Date: Tue, 4 Mar 2008 06:36:23 +0000 Subject: [PATCH] - 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) --- docs/rh-log.txt | 5 + src/sound/i_music.cpp | 4 + src/sound/i_musicinterns.h | 44 +++- src/sound/music_mus_midiout.cpp | 430 ++++++++++++++++++++------------ src/win32/i_input.cpp | 42 ++-- src/win32/i_system.cpp | 19 +- 6 files changed, 357 insertions(+), 187 deletions(-) diff --git a/docs/rh-log.txt b/docs/rh-log.txt index da830282f..b9c4f5d40 100644 --- a/docs/rh-log.txt +++ b/docs/rh-log.txt @@ -1,4 +1,9 @@ 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 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 diff --git a/src/sound/i_music.cpp b/src/sound/i_music.cpp index aed44e993..7d7f92c6b 100644 --- a/src/sound/i_music.cpp +++ b/src/sound/i_music.cpp @@ -112,6 +112,10 @@ bool MusInfo::SetPosition (int order) return false; } +void MusInfo::ServiceEvent () +{ +} + void I_InitMusic (void) { static bool setatterm = false; diff --git a/src/sound/i_musicinterns.h b/src/sound/i_musicinterns.h index 835c6ab06..d2bebb508 100644 --- a/src/sound/i_musicinterns.h +++ b/src/sound/i_musicinterns.h @@ -42,6 +42,7 @@ public: virtual bool IsMIDI () const = 0; virtual bool IsValid () const = 0; virtual bool SetPosition (int order); + virtual void ServiceEvent (); enum EState { @@ -55,6 +56,18 @@ public: // MUS file played with MIDI output messages -------------------------------- #ifdef _WIN32 +struct SHORTMIDIEVENT +{ + DWORD dwDeltaTime; + DWORD dwStreamID; + DWORD dwEvent; +}; + +struct VOLSYSEXEVENT : SHORTMIDIEVENT +{ + BYTE SysEx[8]; +}; + class MUSSong2 : public MusInfo { public: @@ -74,26 +87,41 @@ protected: static DWORD WINAPI PlayerProc (LPVOID lpParameter); void OutputVolume (DWORD volume); 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 { - SEND_DONE, - SEND_WAIT + MAX_EVENTS = 128 }; - HMIDIOUT MidiOut; - HANDLE PlayerThread; - HANDLE PauseEvent; - HANDLE ExitEvent; - HANDLE VolumeChangeEvent; + enum + { + SONG_MORE, + SONG_DONE, + SONG_ERROR + }; + + HMIDISTRM MidiOut; DWORD SavedVolume; bool VolumeWorks; - BYTE *MusBuffer; MUSHeader *MusHeader; + BYTE *MusBuffer; BYTE LastVelocity[16]; BYTE ChannelVolumes[16]; 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 diff --git a/src/sound/music_mus_midiout.cpp b/src/sound/music_mus_midiout.cpp index 6cee79533..d35c21320 100644 --- a/src/sound/music_mus_midiout.cpp +++ b/src/sound/music_mus_midiout.cpp @@ -6,6 +6,9 @@ extern DWORD midivolume; extern UINT mididevice; +extern HANDLE MusicEvent; + +#define MAX_TIME (10) EXTERN_CVAR (Float, snd_midivolume) @@ -28,17 +31,17 @@ static const BYTE CtrlTranslate[15] = 121, // reset all controllers }; -MUSSong2::MUSSong2 (FILE *file, char * musiccache, int len) -: MidiOut (0), PlayerThread (0), - PauseEvent (0), ExitEvent (0), VolumeChangeEvent (0), - MusBuffer (0), MusHeader (0) +MUSSong2::MUSSong2 (FILE *file, char *musiccache, int len) +: MidiOut(0), MusHeader(0), MusBuffer(0) { MusHeader = (MUSHeader *)new BYTE[len]; if (file != NULL) { - if (fread (MusHeader, 1, len, file) != (size_t)len) + if (fread(MusHeader, 1, len, file) != (size_t)len) + { return; + } } else { @@ -47,28 +50,26 @@ MUSSong2::MUSSong2 (FILE *file, char * musiccache, int len) // Do some validation of the MUS file if (MusHeader->Magic != MAKE_ID('M','U','S','\x1a')) + { return; + } if (LittleShort(MusHeader->NumChans) > 15) + { return; + } - ExitEvent = CreateEvent (NULL, FALSE, FALSE, NULL); - if (ExitEvent == NULL) - { - Printf (PRINT_BOLD, "Could not create exit event for MIDI playback\n"); - return; - } - VolumeChangeEvent = CreateEvent (NULL, FALSE, FALSE, NULL); - if (VolumeChangeEvent == NULL) - { - Printf (PRINT_BOLD, "Could not create volume event for MIDI playback\n"); - return; - } - PauseEvent = CreateEvent (NULL, FALSE, FALSE, NULL); - if (PauseEvent == NULL) - { - Printf (PRINT_BOLD, "Could not create pause event for MIDI playback\n"); - } + FullVolEvent.dwDeltaTime = 0; + FullVolEvent.dwStreamID = 0; + FullVolEvent.dwEvent = MEVT_LONGMSG | 8; + FullVolEvent.SysEx[0] = 0xf0; + FullVolEvent.SysEx[1] = 0x7f; + FullVolEvent.SysEx[2] = 0x7f; + FullVolEvent.SysEx[3] = 0x04; + FullVolEvent.SysEx[4] = 0x01; + FullVolEvent.SysEx[5] = 0x7f; + FullVolEvent.SysEx[6] = 0x7f; + FullVolEvent.SysEx[7] = 0xf7; MusBuffer = (BYTE *)MusHeader + LittleShort(MusHeader->SongStart); MaxMusP = MIN (LittleShort(MusHeader->SongLen), len - LittleShort(MusHeader->SongStart)); @@ -78,23 +79,6 @@ MUSSong2::MUSSong2 (FILE *file, char * musiccache, int len) MUSSong2::~MUSSong2 () { 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 @@ -109,14 +93,31 @@ bool MUSSong2::IsValid () const void MUSSong2::Play (bool looping) { - DWORD tid; + UINT dev_id; m_Status = STATE_Stopped; 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; } @@ -126,48 +127,98 @@ void MUSSong2::Play (bool looping) // each channel. Because every General MIDI-compliant device must support // 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) { - 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 { - // Send the standard SysEx message for full master volume - 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); - } - } + BufferNum = 0; } snd_midivolume.Callback(); // set volume to current music's properties - PlayerThread = CreateThread (NULL, 0, PlayerProc, this, 0, &tid); - if (PlayerThread == NULL) + for (int i = 0; i < 16; ++i) { - if (VolumeWorks) - { - midiOutSetVolume (MidiOut, SavedVolume); - } - midiOutClose (MidiOut); - MidiOut = NULL; + LastVelocity[i] = 64; + ChannelVolumes[i] = 127; } - 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 () { if (m_Status == STATE_Playing) { - SetEvent (PauseEvent); m_Status = STATE_Paused; + OutputVolume(0); } } @@ -175,30 +226,28 @@ void MUSSong2::Resume () { if (m_Status == STATE_Paused) { - SetEvent (PauseEvent); + OutputVolume(midivolume & 0xffff); m_Status = STATE_Playing; } } void MUSSong2::Stop () { - if (PlayerThread) - { - SetEvent (ExitEvent); - WaitForSingleObject (PlayerThread, INFINITE); - CloseHandle (PlayerThread); - PlayerThread = NULL; - } + EndQueued = 2; if (MidiOut) { - midiOutReset (MidiOut); + midiStreamStop(MidiOut); + midiOutReset((HMIDIOUT)MidiOut); 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; } + m_Status = STATE_Stopped; } bool MUSSong2::IsPlaying () @@ -208,99 +257,129 @@ bool MUSSong2::IsPlaying () void MUSSong2::SetVolume (float volume) { - SetEvent (VolumeChangeEvent); -} - -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; + OutputVolume(midivolume & 0xffff); } 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); - midiOutShortMsg (MidiOut, i | MIDI_CTRLCHANGE | (7<<8) | (courseVol<<16)); + return; + } + 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) - 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 channel; BYTE t = 0, status; - - event = MusBuffer[MusP++]; + BYTE event = MusBuffer[MusP++]; if ((event & 0x70) != MUS_SCOREEND) { @@ -333,7 +412,7 @@ int MUSSong2::SendCommand () mid1 = t & 127; if (t & 128) { - LastVelocity[channel] = MusBuffer[MusP++];; + LastVelocity[channel] = MusBuffer[MusP++]; } mid2 = LastVelocity[channel]; break; @@ -376,20 +455,43 @@ int MUSSong2::SendCommand () case MUS_SCOREEND: default: - return SEND_DONE; - break; + MusP = MaxMusP; + goto end; } - if (MMSYSERR_NOERROR != midiOutShortMsg (MidiOut, status | (mid1 << 8) | (mid2 << 16))) - { - return SEND_DONE; - } + events[i].dwDeltaTime = time; + events[i].dwStreamID = 0; + events[i].dwEvent = MEVT_SHORTMSG | status | (mid1 << 8) | (mid2 << 16); + + time = 0; if (event & 128) { - return SEND_WAIT; + do + { + t = MusBuffer[MusP++]; + time = (time << 7) | (t & 127); + } + while (t & 128); } + tot_time += time; } - - return SEND_DONE; +end: + 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 diff --git a/src/win32/i_input.cpp b/src/win32/i_input.cpp index 61a0d45a3..1b6c59e42 100644 --- a/src/win32/i_input.cpp +++ b/src/win32/i_input.cpp @@ -95,6 +95,7 @@ #include "gameconfigfile.h" #include "win32iface.h" #include "templates.h" +#include "i_musicinterns.h" #define DINPUT_BUFFERSIZE 32 @@ -109,6 +110,7 @@ BOOL DI_InitJoy (void); extern HINSTANCE g_hInst; extern DWORD SessionID; +extern HANDLE MusicEvent; extern void ShowEAXEditor (); extern bool SpawnEAXWindow; @@ -1929,22 +1931,34 @@ void I_GetEvent () // Briefly enter an alertable state so that if a secondary thread // crashed, we will execute the APC it sent now. - SleepEx (0, TRUE); - -// for (;;) { - while (PeekMessage (&mess, NULL, 0, 0, PM_REMOVE)) + if (MusicEvent != NULL) + { + DWORD res; + do { - if (mess.message == WM_QUIT) - exit (mess.wParam); - if (EAXEditWindow == 0 || !IsDialogMessage (EAXEditWindow, &mess)) - { - TranslateMessage (&mess); - DispatchMessage (&mess); - } + res = WaitForSingleObjectEx(MusicEvent, 0, TRUE); } -// if (havefocus || netgame || gamestate != GS_LEVEL) -// break; -// } + while (res == WAIT_IO_COMPLETION); + 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 (); diff --git a/src/win32/i_system.cpp b/src/win32/i_system.cpp index bdb4fd0c4..e6ce2f9ba 100644 --- a/src/win32/i_system.cpp +++ b/src/win32/i_system.cpp @@ -62,6 +62,7 @@ #include "templates.h" #include "gameconfigfile.h" #include "v_font.h" +#include "i_musicinterns.h" #include "stats.h" @@ -85,6 +86,7 @@ UINT TimerPeriod; UINT TimerEventID; UINT MillisecondsPerTic; HANDLE NewTicArrived; +HANDLE MusicEvent; uint32 LanguageIDs[4]; void CalculateCPUSpeed (); @@ -173,7 +175,15 @@ int I_WaitForTicEvent (int prevtic) { 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; @@ -435,6 +445,11 @@ void I_Init (void) I_WaitForTic = I_WaitForTicPolled; } + if ((MusicEvent = CreateEvent(NULL, FALSE, FALSE, NULL)) == NULL) + { + Printf ("Creation of music event failed."); + } + atterm (I_ShutdownSound); I_InitSound (); } @@ -500,6 +515,8 @@ void I_Quit (void) timeKillEvent (TimerEventID); if (NewTicArrived) CloseHandle (NewTicArrived); + if (MusicEvent) + CloseHandle (MusicEvent); timeEndPeriod (TimerPeriod);