SEQUENCE.C

/***************************************************************************** 
*
* THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF
* ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED
* TO THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR
* A PARTICULAR PURPOSE.
*
* Copyright 1993 - 1998 Microsoft Corporation. All Rights Reserved.
*
******************************************************************************
*
* Sequence.C
*
* Sequencer engine for MIDI player app
*
*****************************************************************************/

#include <windows.h>
#include <windowsx.h>
#include <mmsystem.h>
#include <limits.h>

#include "debug.h"
#include "seq.h"

PRIVATE void FAR PASCAL seqMIDICallback(HMIDISTRM hms, UINT uMsg, DWORD dwUser, DWORD dw1, DWORD dw2);
PRIVATE MMRESULT FNLOCAL XlatSMFErr(SMFRESULT smfrc);

/***************************************************************************
*
* seqAllocBuffers
*
* Allocate buffers for this instance.
*
* pSeq - The sequencer instance to allocate buffers for.
*
* Returns
* MMSYSERR_NOERROR If the operation was successful.
*
* MCIERR_OUT_OF_MEMORY If there is insufficient memory for
* the requested number and size of buffers.
*
* seqAllocBuffers allocates playback buffers based on the
* cbBuffer and cBuffer fields of pSeq. cbBuffer specifies the
* number of bytes in each buffer, and cBuffer specifies the
* number of buffers to allocate.
*
* seqAllocBuffers must be called before any other sequencer call
* on a newly allocted SEQUENCE structure. It must be paired with
* a call to seqFreeBuffers, which should be the last call made
* before the SEQUENCE structure is discarded.
*
***************************************************************************/
MMRESULT FNLOCAL seqAllocBuffers(
PSEQ pSeq)
{
DWORD dwEachBufferSize;
DWORD dwAlloc;
UINT i;
LPBYTE lpbWork;

assert(pSeq != NULL);

pSeq->uState = SEQ_S_NOFILE;
pSeq->lpmhFree = NULL;
pSeq->lpbAlloc = NULL;
pSeq->hSmf = (HSMF)NULL;

/* First make sure we can allocate the buffers they asked for
*/
dwEachBufferSize = sizeof(MIDIHDR) + (DWORD)(pSeq->cbBuffer);
dwAlloc = dwEachBufferSize * (DWORD)(pSeq->cBuffer);

pSeq->lpbAlloc = GlobalAllocPtr(GMEM_MOVEABLE|GMEM_SHARE, dwAlloc);
if (NULL == pSeq->lpbAlloc)
return MCIERR_OUT_OF_MEMORY;

/* Initialize all MIDIHDR's and throw them into a free list
*/
pSeq->lpmhFree = NULL;

lpbWork = pSeq->lpbAlloc;
for (i=0; i < pSeq->cBuffer; i++)
{
((LPMIDIHDR)lpbWork)->lpNext = pSeq->lpmhFree;

((LPMIDIHDR)lpbWork)->lpData = lpbWork + sizeof(MIDIHDR);
((LPMIDIHDR)lpbWork)->dwBufferLength = pSeq->cbBuffer;
((LPMIDIHDR)lpbWork)->dwBytesRecorded = 0;
((LPMIDIHDR)lpbWork)->dwUser = (DWORD)(UINT)pSeq;
((LPMIDIHDR)lpbWork)->dwFlags = 0;

pSeq->lpmhFree = (LPMIDIHDR)lpbWork;

lpbWork += dwEachBufferSize;
}

return MMSYSERR_NOERROR;
}

/***************************************************************************
*
* seqFreeBuffers
*
* Free buffers for this instance.
*
* pSeq - The sequencer instance to free buffers for.
*
* seqFreeBuffers frees all allocated memory belonging to the
* given sequencer instance pSeq. It must be the last call
* performed on the instance before it is destroyed.
*
****************************************************************************/
VOID FNLOCAL seqFreeBuffers(
PSEQ pSeq)
{
LPMIDIHDR lpmh;

assert(pSeq != NULL);

if (NULL != pSeq->lpbAlloc)
{
lpmh = (LPMIDIHDR)pSeq->lpbAlloc;
assert(!(lpmh->dwFlags & MHDR_PREPARED));

GlobalFreePtr(pSeq->lpbAlloc);
}
}

/***************************************************************************
*
* seqOpenFile
*
* Associates a MIDI file with the given sequencer instance.
*
* pSeq - The sequencer instance.
*
* Returns
* MMSYSERR_NOERROR If the operation is successful.
*
* MCIERR_UNSUPPORTED_FUNCTION If there is already a file open
* on this instance.
*
* MCIERR_OUT_OF_MEMORY If there was insufficient memory to
* allocate internal buffers on the file.
*
* MCIERR_INVALID_FILE If initial attempts to parse the file
* failed (such as the file is not a MIDI or RMI file).
*
* seqOpenFile may only be called if there is no currently open file
* on the instance. It must be paired with a call to seqCloseFile
* when operations on this file are complete.
*
* The pstrFile field of pSeq contains the name of the file
* to open. This name will be passed directly to mmioOpen; it may
* contain a specifcation for a custom MMIO file handler. The task
* context used for all I/O will be the task which calls seqOpenFile.
*
***************************************************************************/
MMRESULT FNLOCAL seqOpenFile(
PSEQ pSeq)
{
MMRESULT rc = MMSYSERR_NOERROR;
SMFOPENFILESTRUCT sofs;
SMFFILEINFO sfi;
SMFRESULT smfrc;
DWORD cbBuffer;

assert(pSeq != NULL);

if (pSeq->uState != SEQ_S_NOFILE)
{
return MCIERR_UNSUPPORTED_FUNCTION;
}

assert(pSeq->pstrFile != NULL);

sofs.pstrName = pSeq->pstrFile;

smfrc = smfOpenFile(&sofs);
if (SMF_SUCCESS != smfrc)
{
rc = XlatSMFErr(smfrc);
goto Seq_Open_File_Cleanup;
}

pSeq->hSmf = sofs.hSmf;
smfGetFileInfo(pSeq->hSmf, &sfi);

pSeq->dwTimeDivision = sfi.dwTimeDivision;
pSeq->tkLength = sfi.tkLength;
pSeq->cTrk = sfi.dwTracks;

/* Track buffers must be big enough to hold the state data returned
** by smfSeek()
*/
cbBuffer = min(pSeq->cbBuffer, smfGetStateMaxSize());

Seq_Open_File_Cleanup:
if (MMSYSERR_NOERROR != rc)
seqCloseFile(pSeq);
else
pSeq->uState = SEQ_S_OPENED;

return rc;
}

/***************************************************************************
*
* seqCloseFile
*
* Deassociates a MIDI file with the given sequencer instance.
*
* pSeq - The sequencer instance.
*
* Returns
* MMSYSERR_NOERROR If the operation is successful.
*
* MCIERR_UNSUPPORTED_FUNCTION If the sequencer instance is not
* stopped.
*
* A call to seqCloseFile must be paired with a prior call to
* seqOpenFile. All buffers associated with the file will be
* freed and the file will be closed. The sequencer must be
* stopped before this call will be accepted.
*
***************************************************************************/
MMRESULT FNLOCAL seqCloseFile(
PSEQ pSeq)
{
LPMIDIHDR lpmh;

assert(pSeq != NULL);

if (SEQ_S_OPENED != pSeq->uState)
return MCIERR_UNSUPPORTED_FUNCTION;

if ((HSMF)NULL != pSeq->hSmf)
{
smfCloseFile(pSeq->hSmf);
pSeq->hSmf = (HSMF)NULL;
}

/* If we were prerolled, need to clean up -- have an open MIDI handle
** and buffers in the ready queue
*/

for (lpmh = pSeq->lpmhFree; lpmh; lpmh = lpmh->lpNext)
midiOutUnprepareHeader(pSeq->hmidi, lpmh, sizeof(*lpmh));

if (pSeq->lpmhPreroll)
midiOutUnprepareHeader(pSeq->hmidi, pSeq->lpmhPreroll, sizeof(*pSeq->lpmhPreroll));

if (pSeq->hmidi != NULL)
{
midiStreamClose((HMIDISTRM)(pSeq->hmidi));
pSeq->hmidi = NULL;
}

pSeq->uState = SEQ_S_NOFILE;

return MMSYSERR_NOERROR;
}

/***************************************************************************
*
* seqPreroll
*
* Prepares the file for playback at the given position.
*
* pSeq - The sequencer instance.
*
* lpPreroll - Specifies the starting and ending tick
* positions to play between.
*
* Returns
* MMSYSERR_NOERROR If the operation is successful.
*
* MCIERR_UNSUPPORTED_FUNCTION If the sequencer instance is not
* opened or prerolled.
*
* Open the device so we can initialize channels.
*
* Loop through the tracks. For each track, seek to the given position and
* send the init data SMF gives us to the handle.
*
* Wait for all init buffers to finish.
*
* Unprepare the buffers (they're only ever sent here; the sequencer
* engine merges them into a single stream during normal playback) and
* refill them with the first chunk of data from the track.
*
*
****************************************************************************/
MMRESULT FNLOCAL seqPreroll(
PSEQ pSeq,
LPPREROLL lpPreroll)
{
SMFRESULT smfrc;
MMRESULT mmrc = MMSYSERR_NOERROR;
MIDIPROPTIMEDIV mptd;
LPMIDIHDR lpmh = NULL;
LPMIDIHDR lpmhPreroll = NULL;
DWORD cbPrerollBuffer;
UINT uDeviceID;

assert(pSeq != NULL);

pSeq->mmrcLastErr = MMSYSERR_NOERROR;

if (pSeq->uState != SEQ_S_OPENED &&
pSeq->uState != SEQ_S_PREROLLED)
return MCIERR_UNSUPPORTED_FUNCTION;

pSeq->tkBase = lpPreroll->tkBase;
pSeq->tkEnd = lpPreroll->tkEnd;

if (pSeq->hmidi)
{
// Recollect buffers from MMSYSTEM back into free queue
//
pSeq->uState = SEQ_S_RESET;
midiOutReset(pSeq->hmidi);

while (pSeq->uBuffersInMMSYSTEM)
Sleep(0);
}

pSeq->uBuffersInMMSYSTEM = 0;
pSeq->uState = SEQ_S_PREROLLING;

//
// We've successfully opened the file and all of the tracks; now
// open the MIDI device and set the time division.
//
// NOTE: seqPreroll is equivalent to seek; device might already be open
//
if (NULL == pSeq->hmidi)
{
uDeviceID = pSeq->uDeviceID;
if ((mmrc = midiStreamOpen(&(HMIDISTRM)pSeq->hmidi,
&uDeviceID,
1,
(DWORD)seqMIDICallback,
0,
CALLBACK_FUNCTION)) != MMSYSERR_NOERROR)
{
pSeq->hmidi = NULL;
goto seq_Preroll_Cleanup;
}

mptd.cbStruct = sizeof(mptd);
mptd.dwTimeDiv = pSeq->dwTimeDivision;
if ((mmrc = midiStreamProperty(
(HMIDISTRM)pSeq->hmidi,
(LPBYTE)&mptd,
MIDIPROP_SET|MIDIPROP_TIMEDIV)) != MMSYSERR_NOERROR)
{
DPF(1, "midiStreamProperty() -> %04X", (WORD)mmrc);
midiStreamClose((HMIDISTRM)pSeq->hmidi);
pSeq->hmidi = NULL;
mmrc = MCIERR_DEVICE_NOT_READY;
goto seq_Preroll_Cleanup;
}
}

mmrc = MMSYSERR_NOERROR;

//
// Allocate a preroll buffer. Then if we don't have enough room for
// all the preroll info, we make the buffer larger.
//
if (!pSeq->lpmhPreroll)
{
cbPrerollBuffer = 4096;
lpmhPreroll = (LPMIDIHDR)GlobalAllocPtr(GMEM_MOVEABLE|GMEM_SHARE,
cbPrerollBuffer);
}
else
{
cbPrerollBuffer = pSeq->cbPreroll;
lpmhPreroll = pSeq->lpmhPreroll;
}

lpmhPreroll->lpNext = pSeq->lpmhFree;
lpmhPreroll->lpData = (LPBYTE)lpmhPreroll + sizeof(MIDIHDR);
lpmhPreroll->dwBufferLength = cbPrerollBuffer - sizeof(MIDIHDR);
lpmhPreroll->dwBytesRecorded = 0;
lpmhPreroll->dwUser = (DWORD)(UINT)pSeq;
lpmhPreroll->dwFlags = 0;

do
{
smfrc = smfSeek(pSeq->hSmf, pSeq->tkBase, lpmhPreroll);
if( SMF_SUCCESS != smfrc )
{
if( ( SMF_NO_MEMORY != smfrc ) ||
( cbPrerollBuffer >= 32768L ) )
{
DPF(1, "smfSeek() returned %lu", (DWORD)smfrc);

GlobalFreePtr(lpmhPreroll);
pSeq->lpmhPreroll = NULL;

mmrc = XlatSMFErr(smfrc);
goto seq_Preroll_Cleanup;
}
else // Try to grow buffer.
{
cbPrerollBuffer *= 2;
lpmh = (LPMIDIHDR)GlobalReAllocPtr( lpmhPreroll, cbPrerollBuffer, 0 );
if( NULL == lpmh )
{
DPF(2,"seqPreroll - realloc failed, aborting preroll.");
mmrc = MCIERR_OUT_OF_MEMORY;
goto seq_Preroll_Cleanup;
}

lpmhPreroll = lpmh;
lpmhPreroll->lpData = (LPBYTE)lpmhPreroll + sizeof(MIDIHDR);
lpmhPreroll->dwBufferLength = cbPrerollBuffer - sizeof(MIDIHDR);

pSeq->lpmhPreroll = lpmhPreroll;
pSeq->cbPreroll = cbPrerollBuffer;
}
}
} while( SMF_SUCCESS != smfrc );

if (MMSYSERR_NOERROR != (mmrc = midiOutPrepareHeader(pSeq->hmidi, lpmhPreroll, sizeof(MIDIHDR))))
{
DPF(1, "midiOutPrepare(preroll) -> %lu!", (DWORD)mmrc);

mmrc = MCIERR_DEVICE_NOT_READY;
goto seq_Preroll_Cleanup;
}

++pSeq->uBuffersInMMSYSTEM;

if (MMSYSERR_NOERROR != (mmrc = midiStreamOut((HMIDISTRM)pSeq->hmidi, lpmhPreroll, sizeof(MIDIHDR))))
{
DPF(1, "midiStreamOut(preroll) -> %lu!", (DWORD)mmrc);

mmrc = MCIERR_DEVICE_NOT_READY;
--pSeq->uBuffersInMMSYSTEM;
goto seq_Preroll_Cleanup;
}
DPF(3,"seqPreroll: midiStreamOut(0x%x,0x%lx,%u) returned %u.",pSeq->hmidi,lpmhPreroll,sizeof(MIDIHDR),mmrc);

pSeq->fdwSeq &= ~SEQ_F_EOF;
while (pSeq->lpmhFree)
{
lpmh = pSeq->lpmhFree;
pSeq->lpmhFree = lpmh->lpNext;

smfrc = smfReadEvents(pSeq->hSmf, lpmh, pSeq->tkEnd);
if (SMF_SUCCESS != smfrc && SMF_END_OF_FILE != smfrc)
{
DPF(1, "SFP: smfReadEvents() -> %u", (UINT)smfrc);
mmrc = XlatSMFErr(smfrc);
goto seq_Preroll_Cleanup;
}

if (MMSYSERR_NOERROR != (mmrc = midiOutPrepareHeader(pSeq->hmidi, lpmh, sizeof(*lpmh))))
{
DPF(1, "SFP: midiOutPrepareHeader failed");
goto seq_Preroll_Cleanup;
}

if (MMSYSERR_NOERROR != (mmrc = midiStreamOut((HMIDISTRM)pSeq->hmidi, lpmh, sizeof(*lpmh))))
{
DPF(1, "SFP: midiStreamOut failed");
goto seq_Preroll_Cleanup;
}

++pSeq->uBuffersInMMSYSTEM;

if (SMF_END_OF_FILE == smfrc)
{
pSeq->fdwSeq |= SEQ_F_EOF;
break;
}
}

seq_Preroll_Cleanup:
if (MMSYSERR_NOERROR != mmrc)
{
pSeq->uState = SEQ_S_OPENED;
pSeq->fdwSeq &= ~SEQ_F_WAITING;
}
else
{
pSeq->uState = SEQ_S_PREROLLED;
}

return mmrc;
}

/***************************************************************************
*
* seqStart
*
* Starts playback at the current position.
*
* pSeq - The sequencer instance.
*
* Returns
* MMSYSERR_NOERROR If the operation is successful.
*
* MCIERR_UNSUPPORTED_FUNCTION If the sequencer instance is not
* stopped.
*
* MCIERR_DEVICE_NOT_READY If the underlying MIDI device could
* not be opened or fails any call.
*
* The sequencer must be prerolled before seqStart may be called.
*
* Just feed everything in the ready queue to the device.
*
***************************************************************************/
MMRESULT FNLOCAL seqStart(
PSEQ pSeq)
{
assert(NULL != pSeq);

if (SEQ_S_PREROLLED != pSeq->uState)
{
DPF(1, "seqStart(): State is wrong! [%u]", pSeq->uState);
return MCIERR_UNSUPPORTED_FUNCTION;
}

pSeq->uState = SEQ_S_PLAYING;

return midiStreamRestart((HMIDISTRM)pSeq->hmidi);
}

/***************************************************************************
*
* seqPause
*
* Pauses playback of the instance.
*
* pSeq - The sequencer instance.
*
* Returns
* MMSYSERR_NOERROR If the operation is successful.
*
* MCIERR_UNSUPPORTED_FUNCTION If the sequencer instance is not
* playing.
*
* The sequencer must be playing before seqPause may be called.
* Pausing the sequencer will cause all currently on notes to be turned
* off. This may cause playback to be slightly inaccurate on restart
* due to missing notes.
*
***************************************************************************/
MMRESULT FNLOCAL seqPause(
PSEQ pSeq)
{
assert(NULL != pSeq);

if (SEQ_S_PLAYING != pSeq->uState)
return MCIERR_UNSUPPORTED_FUNCTION;

pSeq->uState = SEQ_S_PAUSED;
midiStreamPause((HMIDISTRM)pSeq->hmidi);

return MMSYSERR_NOERROR;
}

/***************************************************************************
*
* seqRestart
*
* Restarts playback of an instance after a pause.
*
* pSeq - The sequencer instance.
*
* Returns
* MMSYSERR_NOERROR If the operation is successful.
*
* MCIERR_UNSUPPORTED_FUNCTION If the sequencer instance is not
* paused.
*
* The sequencer must be paused before seqRestart may be called.
*
***************************************************************************/
MMRESULT FNLOCAL seqRestart(
PSEQ pSeq)
{
assert(NULL != pSeq);

if (SEQ_S_PAUSED != pSeq->uState)
return MCIERR_UNSUPPORTED_FUNCTION;

pSeq->uState = SEQ_S_PLAYING;
midiStreamRestart((HMIDISTRM)pSeq->hmidi);

return MMSYSERR_NOERROR;
}

/***************************************************************************
*
* seqStop
*
* Totally stops playback of an instance.
*
* pSeq - The sequencer instance.
*
* Returns
* MMSYSERR_NOERROR If the operation is successful.
*
* MCIERR_UNSUPPORTED_FUNCTION If the sequencer instance is not
* paused or playing.
*
* The sequencer must be paused or playing before seqStop may be called.
*
***************************************************************************/
MMRESULT FNLOCAL seqStop(
PSEQ pSeq)
{
assert(NULL != pSeq);

/* Automatic success if we're already stopped
*/
if (SEQ_S_PLAYING != pSeq->uState &&
SEQ_S_PAUSED != pSeq->uState)
{
pSeq->fdwSeq &= ~SEQ_F_WAITING;
return MMSYSERR_NOERROR;
}

pSeq->uState = SEQ_S_STOPPING;
pSeq->fdwSeq |= SEQ_F_WAITING;

if (MMSYSERR_NOERROR != (pSeq->mmrcLastErr = midiStreamStop((HMIDISTRM)pSeq->hmidi)))
{
DPF(1, "midiOutStop() returned %lu in seqStop()!", (DWORD)pSeq->mmrcLastErr);

pSeq->fdwSeq &= ~SEQ_F_WAITING;
return MCIERR_DEVICE_NOT_READY;
}

while (pSeq->uBuffersInMMSYSTEM)
Sleep(0);

return MMSYSERR_NOERROR;
}

/***************************************************************************
*
* seqTime
*
* Determine the current position in playback of an instance.
*
* pSeq - The sequencer instance.
*
* pTicks - A pointer to a DWORD where the current position
* in ticks will be returned.
*
* Returns
* MMSYSERR_NOERROR If the operation is successful.
*
* MCIERR_DEVICE_NOT_READY If the underlying device fails to report
* the position.
*
* MCIERR_UNSUPPORTED_FUNCTION If the sequencer instance is not
* paused or playing.
*
* The sequencer must be paused, playing or prerolled before seqTime
* may be called.
*
***************************************************************************/
MMRESULT FNLOCAL seqTime(
PSEQ pSeq,
PTICKS pTicks)
{
MMRESULT mmr;
MMTIME mmt;

assert(pSeq != NULL);

if (SEQ_S_PLAYING != pSeq->uState &&
SEQ_S_PAUSED != pSeq->uState &&
SEQ_S_PREROLLING != pSeq->uState &&
SEQ_S_PREROLLED != pSeq->uState &&
SEQ_S_OPENED != pSeq->uState)
{
DPF(1, "seqTime(): State wrong! [is %u]", pSeq->uState);
return MCIERR_UNSUPPORTED_FUNCTION;
}

*pTicks = 0;
if (SEQ_S_OPENED != pSeq->uState)
{
*pTicks = pSeq->tkBase;
if (SEQ_S_PREROLLED != pSeq->uState)
{
mmt.wType = TIME_TICKS;
mmr = midiStreamPosition((HMIDISTRM)pSeq->hmidi, &mmt, sizeof(mmt));
if (MMSYSERR_NOERROR != mmr)
{
DPF(1, "midiStreamPosition() returned %lu", (DWORD)mmr);
return MCIERR_DEVICE_NOT_READY;
}

*pTicks += mmt.u.ticks;
}
}

return MMSYSERR_NOERROR;
}

/***************************************************************************
*
* seqMillisecsToTicks
*
* Given a millisecond offset in the output stream, returns the associated
* tick position.
*
* pSeq - The sequencer instance.
*
* msOffset - The millisecond offset into the stream.
*
* Returns the number of ticks into the stream.
*
***************************************************************************/
TICKS FNLOCAL seqMillisecsToTicks(
PSEQ pSeq,
DWORD msOffset)
{
return smfMillisecsToTicks(pSeq->hSmf, msOffset);
}

/***************************************************************************
*
* seqTicksToMillisecs
*
* Given a tick offset in the output stream, returns the associated
* millisecond position.
*
* pSeq - The sequencer instance.
*
* tkOffset - The tick offset into the stream.
*
* Returns the number of milliseconds into the stream.
*
***************************************************************************/
DWORD FNLOCAL seqTicksToMillisecs(
PSEQ pSeq,
TICKS tkOffset)
{
return smfTicksToMillisecs(pSeq->hSmf, tkOffset);
}

/***************************************************************************
*
* seqMIDICallback
*
* Called by the system when a buffer is done
*
* dw1 - The buffer that has completed playback.
*
***************************************************************************/
PRIVATE void FAR PASCAL seqMIDICallback(HMIDISTRM hms, UINT uMsg, DWORD dwUser, DWORD dw1, DWORD dw2)
{
LPMIDIHDRlpmh= (LPMIDIHDR)dw1;
PSEQ pSeq;
MMRESULT mmrc;
SMFRESULT smfrc;

if (uMsg != MOM_DONE)
return;

assert(NULL != lpmh);

pSeq = (PSEQ)(lpmh->dwUser);

assert(pSeq != NULL);

--pSeq->uBuffersInMMSYSTEM;

if (SEQ_S_RESET == pSeq->uState)
{
// We're recollecting buffers from MMSYSTEM
//
if (lpmh != pSeq->lpmhPreroll)
{
lpmh->lpNext = pSeq->lpmhFree;
pSeq->lpmhFree = lpmh;
}

return;
}


if ((SEQ_S_STOPPING == pSeq->uState) || (pSeq->fdwSeq & SEQ_F_EOF))
{
/*
** Reached EOF, just put the buffer back on the free
** list
*/
if (lpmh != pSeq->lpmhPreroll)
{
lpmh->lpNext = pSeq->lpmhFree;
pSeq->lpmhFree = lpmh;
}

if (MMSYSERR_NOERROR != (mmrc = midiOutUnprepareHeader(pSeq->hmidi, lpmh, sizeof(*lpmh))))
{
DPF(1, "midiOutUnprepareHeader failed in seqBufferDone! (%lu)", (DWORD)mmrc);
}

if (0 == pSeq->uBuffersInMMSYSTEM)
{
DPF(1, "seqBufferDone: normal sequencer shutdown.");

/* Totally done! Free device and notify.
*/
midiStreamClose((HMIDISTRM)pSeq->hmidi);

pSeq->hmidi = NULL;
pSeq->uState = SEQ_S_OPENED;
pSeq->mmrcLastErr = MMSYSERR_NOERROR;
pSeq->fdwSeq &= ~SEQ_F_WAITING;

// lParam indicates whether or not to preroll again. Don't if we were explicitly
// stopped.
//
PostMessage(pSeq->hWnd, MMSG_DONE, (WPARAM)pSeq, (LPARAM)(SEQ_S_STOPPING != pSeq->uState));
}
}
else
{
/*
** Not EOF yet; attempt to fill another buffer
*/
smfrc = smfReadEvents(pSeq->hSmf, lpmh, pSeq->tkEnd);

switch(smfrc)
{
case SMF_SUCCESS:
break;

case SMF_END_OF_FILE:
pSeq->fdwSeq |= SEQ_F_EOF;
smfrc = SMF_SUCCESS;
break;

default:
DPF(1, "smfReadEvents returned %lu in callback!", (DWORD)smfrc);
pSeq->uState = SEQ_S_STOPPING;
break;
}

if (SMF_SUCCESS == smfrc)
{
++pSeq->uBuffersInMMSYSTEM;
mmrc = midiStreamOut((HMIDISTRM)pSeq->hmidi, lpmh, sizeof(*lpmh));
if (MMSYSERR_NOERROR != mmrc)
{
DPF(1, "seqBufferDone(): midiStreamOut() returned %lu!", (DWORD)mmrc);

--pSeq->uBuffersInMMSYSTEM;
pSeq->uState = SEQ_S_STOPPING;
}
}
}
}

/***************************************************************************
*
* XlatSMFErr
*
* Translates an error from the SMF layer into an appropriate MCI error.
*
* smfrc - The return code from any SMF function.
*
* Returns
* A parallel error from the MCI error codes.
*
***************************************************************************/
PRIVATE MMRESULT FNLOCAL XlatSMFErr(
SMFRESULT smfrc)
{
switch(smfrc)
{
case SMF_SUCCESS:
return MMSYSERR_NOERROR;

case SMF_NO_MEMORY:
return MCIERR_OUT_OF_MEMORY;

case SMF_INVALID_FILE:
case SMF_OPEN_FAILED:
case SMF_INVALID_TRACK:
return MCIERR_INVALID_FILE;

default:
return MCIERR_UNSUPPORTED_FUNCTION;
}
}