SMF.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.
*
******************************************************************************
*
* SMF.C
*
* MIDI File access routines.
*
*****************************************************************************/
#include <windows.h>
#include <windowsx.h>
#include <mmsystem.h>
#include <memory.h>
#include "muldiv32.h"
#include "smf.h"
#include "smfi.h"
#include "debug.h"

PRIVATE SMFRESULT FNLOCAL smfInsertParmData(
PSMF pSmf,
TICKS tkDelta,
LPMIDIHDR lpmh);

/*****************************************************************************
*
* smfOpenFile
*
* This function opens a MIDI file for access.
*
* psofs - Specifies the file to open and associated
* parameters. Contains a valid HSMF handle
* on success.
*
* Returns
* SMF_SUCCESS The specified file was opened.
*
* SMF_OPEN_FAILED The specified file could not be opened because it
* did not exist or could not be created on the disk.
*
* SMF_INVALID_FILE The specified file was corrupt or not a MIDI file.
*
* SMF_NO_MEMORY There was insufficient memory to open the file.
*
* SMF_INVALID_PARM The given flags or time division in the
* SMFOPENFILESTRUCT were invalid.
*
*****************************************************************************/
SMFRESULT FNLOCAL smfOpenFile(
PSMFOPENFILESTRUCT psofs)
{
HMMIO hmmio = (HMMIO)NULL;
PSMF pSmf;
SMFRESULT smfrc = SMF_SUCCESS;
MMIOINFO mmioinfo;
MMCKINFO ckRIFF;
MMCKINFO ckDATA;

assert(psofs != NULL);
assert(psofs->pstrName != NULL);

/* Verify that the file can be opened or created
*/
_fmemset(&mmioinfo, 0, sizeof(mmioinfo));

hmmio = mmioOpen(psofs->pstrName, &mmioinfo, MMIO_READ|MMIO_ALLOCBUF);
if ((HMMIO)NULL == hmmio)
{
DPF(1, "smfOpenFile: mmioOpen failed!");
return SMF_OPEN_FAILED;
}

/* Now see if we can create the handle structure
*/
pSmf = (PSMF)LocalAlloc(LPTR, sizeof(SMF));
if (NULL == pSmf)
{
DPF(1, "smfOpenFile: LocalAlloc failed!");
smfrc = SMF_NO_MEMORY;
goto smf_Open_File_Cleanup;
}

lstrcpy(pSmf->szName, psofs->pstrName);
pSmf->fdwSMF = 0;
pSmf->pTempoMap = NULL;

/* Pull the entire file into a block of memory.
*/
_fmemset(&ckRIFF, 0, sizeof(ckRIFF));

if (0 == mmioDescend(hmmio, &ckRIFF, NULL, MMIO_FINDRIFF) &&
ckRIFF.fccType == FOURCC_RMID)
{
ckDATA.ckid = FOURCC_data;

if (0 == mmioDescend(hmmio, &ckDATA, &ckRIFF, MMIO_FINDCHUNK))
{
pSmf->cbImage = ckDATA.cksize;
}
else
{
DPF(1, "smfOpenFile: Could not descend into RIFF DATA chunk!");
smfrc = SMF_INVALID_FILE;
goto smf_Open_File_Cleanup;
}
}
else
{
mmioSeek(hmmio, 0L, SEEK_SET);

pSmf->cbImage = mmioSeek(hmmio, 0L, SEEK_END);
mmioSeek(hmmio, 0L, SEEK_SET);
}

if (NULL == (pSmf->hpbImage = GlobalAllocPtr(GMEM_MOVEABLE|GMEM_SHARE, pSmf->cbImage)))
{
DPF(1, "smfOpenFile: No memory for image! [%08lX]", pSmf->cbImage);
smfrc = SMF_NO_MEMORY;
goto smf_Open_File_Cleanup;
}

if (pSmf->cbImage != (DWORD)mmioRead(hmmio, pSmf->hpbImage, pSmf->cbImage))
{
DPF(1, "smfOpenFile: Read error on image!");
smfrc = SMF_INVALID_FILE;
goto smf_Open_File_Cleanup;
}

/* If the file exists, parse it just enough to pull out the header and
** build a track index.
*/
smfrc = smfBuildFileIndex((PSMF BSTACK *)&pSmf);
if (MMSYSERR_NOERROR != smfrc)
{
DPF(1, "smfOpenFile: smfBuildFileIndex failed! [%lu]", (DWORD)smfrc);
}

smf_Open_File_Cleanup:

mmioClose(hmmio, 0);

if (SMF_SUCCESS != smfrc)
{
if (NULL != pSmf)
{
if (NULL != pSmf->hpbImage)
{
GlobalFreePtr(pSmf->hpbImage);
}

LocalFree((HLOCAL)pSmf);
}
}
else
{
psofs->hSmf = (HSMF)pSmf;
}

return smfrc;
}

/*****************************************************************************
*
* smfCloseFile
*
* This function closes an open MIDI file.
*
* hSmf - The handle of the open file to close.
*
* Returns
* SMF_SUCCESS The specified file was closed.
* SMF_INVALID_PARM The given handle was not valid.
*
* Any track handles opened from this file handle are invalid after this
* call.
*
*****************************************************************************/
SMFRESULT FNLOCAL smfCloseFile(
HSMF hSmf)
{
PSMF pSmf = (PSMF)hSmf;

assert(pSmf != NULL);

/*
** Free up handle memory
*/

if (NULL != pSmf->hpbImage)
GlobalFreePtr(pSmf->hpbImage);

LocalFree((HLOCAL)pSmf);

return SMF_SUCCESS;
}

/******************************************************************************
*
* smfGetFileInfo This function gets information about the MIDI file.
*
* hSmf - Specifies the open MIDI file to inquire about.
*
* psfi - A structure which will be filled in with
* information about the file.
*
* Returns
* SMF_SUCCESS Information was gotten about the file.
* SMF_INVALID_PARM The given handle was invalid.
*
*****************************************************************************/
SMFRESULT FNLOCAL smfGetFileInfo(
HSMF hSmf,
PSMFFILEINFO psfi)
{
PSMF pSmf = (PSMF)hSmf;

assert(pSmf != NULL);
assert(psfi != NULL);

/*
** Just fill in the structure with useful information.
*/
psfi->dwTracks = pSmf->dwTracks;
psfi->dwFormat = pSmf->dwFormat;
psfi->dwTimeDivision= pSmf->dwTimeDivision;
psfi->tkLength = pSmf->tkLength;

return SMF_SUCCESS;
}

/******************************************************************************
*
* smfTicksToMillisecs
*
* This function returns the millisecond offset into the file given the
* tick offset.
*
* hSmf - Specifies the open MIDI file to perform
* the conversion on.
*
* tkOffset - Specifies the tick offset into the stream
* to convert.
*
* Returns the number of milliseconds from the start of the stream.
*
* The conversion is performed taking into account the file's time division and
* tempo map from the first track. Note that the same millisecond value
* might not be valid at a later time if the tempo track is rewritten.
*
*****************************************************************************/
DWORD FNLOCAL smfTicksToMillisecs(
HSMF hSmf,
TICKS tkOffset)
{
PSMF pSmf = (PSMF)hSmf;
PTEMPOMAPENTRY pTempo;
UINT idx;
UINT uSMPTE;
DWORD dwTicksPerSec;

assert(pSmf != NULL);

if (tkOffset > pSmf->tkLength)
{
DPF(1, "sTTM: Clipping ticks to file length!");
tkOffset = pSmf->tkLength;
}

/* SMPTE time is easy -- no tempo map, just linear conversion
** Note that 30-Drop means nothing to us here since we're not
** converting to a colonized format, which is where dropping
** happens.
*/
if (pSmf->dwTimeDivision & 0x8000)
{
uSMPTE = -(int)(char)((pSmf->dwTimeDivision >> 8)&0xFF);
if (29 == uSMPTE)
uSMPTE = 30;

dwTicksPerSec = (DWORD)uSMPTE *
(DWORD)(BYTE)(pSmf->dwTimeDivision & 0xFF);

return (DWORD)muldiv32(tkOffset, 1000L, dwTicksPerSec);
}

/* Walk the tempo map and find the nearest tick position. Linearly
** calculate the rest (using MATH.ASM)
*/

pTempo = pSmf->pTempoMap;
assert(pTempo != NULL);

for (idx = 0; idx < pSmf->cTempoMap; idx++, pTempo++)
if (tkOffset < pTempo->tkTempo)
break;
pTempo--;

/* pTempo is the tempo map entry preceding the requested tick offset.
*/

return pTempo->msBase + muldiv32(tkOffset-pTempo->tkTempo,
pTempo->dwTempo,
1000L*pSmf->dwTimeDivision);

}


/******************************************************************************
*
* smfMillisecsToTicks
*
* This function returns the nearest tick offset into the file given the
* millisecond offset.
*
* hSmf - Specifies the open MIDI file to perform the
* conversion on.
*
* msOffset - Specifies the millisecond offset into the stream
* to convert.
*
* Returns the number of ticks from the start of the stream.
*
* The conversion is performed taking into account the file's time division and
* tempo map from the first track. Note that the same tick value
* might not be valid at a later time if the tempo track is rewritten.
* If the millisecond value does not exactly map to a tick value, then
* the tick value will be rounded down.
*
*****************************************************************************/
TICKS FNLOCAL smfMillisecsToTicks(
HSMF hSmf,
DWORD msOffset)
{
PSMF pSmf = (PSMF)hSmf;
PTEMPOMAPENTRY pTempo;
UINT idx;
UINT uSMPTE;
DWORD dwTicksPerSec;
TICKS tkOffset;

assert(pSmf != NULL);

/* SMPTE time is easy -- no tempo map, just linear conversion
** Note that 30-Drop means nothing to us here since we're not
** converting to a colonized format, which is where dropping
** happens.
*/
if (pSmf->dwTimeDivision & 0x8000)
{
uSMPTE = -(int)(char)((pSmf->dwTimeDivision >> 8)&0xFF);
if (29 == uSMPTE)
uSMPTE = 30;

dwTicksPerSec = (DWORD)uSMPTE *
(DWORD)(BYTE)(pSmf->dwTimeDivision & 0xFF);

return (DWORD)muldiv32(msOffset, dwTicksPerSec, 1000L);
}

/* Walk the tempo map and find the nearest millisecond position. Linearly
** calculate the rest (using MATH.ASM)
*/
pTempo = pSmf->pTempoMap;
assert(pTempo != NULL);

for (idx = 0; idx < pSmf->cTempoMap; idx++, pTempo++)
if (msOffset < pTempo->msBase)
break;
pTempo--;

/* pTempo is the tempo map entry preceding the requested tick offset.
*/

tkOffset = pTempo->tkTempo + muldiv32(msOffset-pTempo->msBase,
1000L*pSmf->dwTimeDivision,
pTempo->dwTempo);

if (tkOffset > pSmf->tkLength)
{
DPF(1, "sMTT: Clipping ticks to file length!");
tkOffset = pSmf->tkLength;
}

return tkOffset;
}

/******************************************************************************
*
* smfReadEvents
*
* This function reads events from a track.
*
* hSmf - Specifies the file to read data from.
*
* lpmh - Contains information about the buffer to fill.
*
* tkMax - Specifies a cutoff point in the stream
* beyond which events will not be read.
*
* Return@rdes
* SMF_SUCCESS The events were successfully read.
* SMF_END_OF_TRACK There are no more events to read in this track.
* SMF_INVALID_FILE A disk error occured on the file.
*
* @xref <f smfWriteEvents>
*****************************************************************************/
SMFRESULT FNLOCAL smfReadEvents(
HSMF hSmf,
LPMIDIHDR lpmh,
TICKS tkMax)
{
PSMF pSmf = (PSMF)hSmf;
SMFRESULT smfrc;
EVENT event;
LPDWORD lpdw;
DWORD dwTempo;

assert(pSmf != NULL);
assert(lpmh != NULL);

/*
** Read events from the track and pack them into the buffer in polymsg
** format.
**
** If a SysEx or meta would go over a buffer boundry, split it.
*/
lpmh->dwBytesRecorded = 0;
if (pSmf->dwPendingUserEvent)
{
smfrc = smfInsertParmData(pSmf, (TICKS)0, lpmh);
if (SMF_SUCCESS != smfrc)
{
DPF(1, "smfInsertParmData() -> %u", (UINT)smfrc);
return smfrc;
}
}

lpdw = (LPDWORD)(lpmh->lpData + lpmh->dwBytesRecorded);

if (pSmf->fdwSMF & SMF_F_EOF)
{
return SMF_END_OF_FILE;
}

while(TRUE)
{
assert(lpmh->dwBytesRecorded <= lpmh->dwBufferLength);

/* If we know ahead of time we won't have room for the
** event, just break out now. We need 2 DWORD's for the
** terminator event and at least 2 DWORD's for any
** event we might store - this will allow us a full
** short event or the delta time and stub for a long
** event to be split.
*/
if (lpmh->dwBufferLength - lpmh->dwBytesRecorded < 4*sizeof(DWORD))
{
break;
}

smfrc = smfGetNextEvent(pSmf, (SPEVENT)&event, tkMax);
if (SMF_SUCCESS != smfrc)
{
/* smfGetNextEvent doesn't set this because smfSeek uses it
** as well and needs to distinguish between reaching the
** seek point and reaching end-of-file.
**
** To the user, however, we present the selection between
** their given tkBase and tkEnd as the entire file, therefore
** we want to translate this into EOF.
*/
if (SMF_REACHED_TKMAX == smfrc)
{
pSmf->fdwSMF |= SMF_F_EOF;
}

DPF(1, "smfReadEvents: smfGetNextEvent() -> %u", (UINT)smfrc);
break;
}


if (MIDI_SYSEX > EVENT_TYPE(event))
{
*lpdw++ = (DWORD)event.tkDelta;
*lpdw++ = 0;
*lpdw++ = (((DWORD)MEVT_SHORTMSG)<<24) |
((DWORD)EVENT_TYPE(event)) |
(((DWORD)EVENT_CH_B1(event)) << 8) |
(((DWORD)EVENT_CH_B2(event)) << 16);

lpmh->dwBytesRecorded += 3*sizeof(DWORD);
}
else if (MIDI_META == EVENT_TYPE(event) &&
MIDI_META_EOT == EVENT_META_TYPE(event))
{
/* These are ignoreable since smfReadNextEvent()
** takes care of track merging
*/
}
else if (MIDI_META == EVENT_TYPE(event) &&
MIDI_META_TEMPO == EVENT_META_TYPE(event))
{
if (event.cbParm != 3)
{
DPF(1, "smfReadEvents: Corrupt tempo event");
return SMF_INVALID_FILE;
}

dwTempo = (((DWORD)MEVT_TEMPO)<<24)|
(((DWORD)event.hpbParm[0])<<16)|
(((DWORD)event.hpbParm[1])<<8)|
((DWORD)event.hpbParm[2]);

*lpdw++ = (DWORD)event.tkDelta;
*lpdw++ = 0;
*lpdw++ = dwTempo;

lpmh->dwBytesRecorded += 3*sizeof(DWORD);
}
else if (MIDI_META != EVENT_TYPE(event))
{
/* Must be F0 or F7 system exclusive or FF meta
** that we didn't recognize
*/
pSmf->cbPendingUserEvent = event.cbParm;
pSmf->hpbPendingUserEvent = event.hpbParm;
pSmf->fdwSMF &= ~SMF_F_INSERTSYSEX;

switch(EVENT_TYPE(event))
{
case MIDI_SYSEX:
pSmf->fdwSMF |= SMF_F_INSERTSYSEX;

++pSmf->cbPendingUserEvent;

/* Falling through...
*/

case MIDI_SYSEXEND:
pSmf->dwPendingUserEvent = ((DWORD)MEVT_LONGMSG) << 24;
break;
}

smfrc = smfInsertParmData(pSmf, event.tkDelta, lpmh);
if (SMF_SUCCESS != smfrc)
{
DPF(1, "smfInsertParmData[2] %u", (UINT)smfrc);
return smfrc;
}

lpdw = (LPDWORD)(lpmh->lpData + lpmh->dwBytesRecorded);
}
}

return (pSmf->fdwSMF & SMF_F_EOF) ? SMF_END_OF_FILE : SMF_SUCCESS;
}

/******************************************************************************
*
* smfInsertParmData
*
* Inserts pending long data from a track into the given buffer.
*
* pSmf - Specifies the file to read data from.
*
* tkDelta - Specfices the tick delta for the data.
*
* lpmh - Contains information about the buffer to fill.
*
* Returns
* SMF_SUCCESS The events were successfully read.
* SMF_INVALID_FILE A disk error occured on the file.
*
* Fills as much data as will fit while leaving room for the buffer
* terminator.
*
* If the long data is depleted, resets pSmf->dwPendingUserEvent so
* that the next event may be read.
*
*****************************************************************************/
PRIVATE SMFRESULT FNLOCAL smfInsertParmData(
PSMF pSmf,
TICKS tkDelta,
LPMIDIHDR lpmh)
{
DWORD dwLength;
DWORD dwRounded;
LPDWORD lpdw;

assert(pSmf != NULL);
assert(lpmh != NULL);

/* Can't fit 4 DWORD's? (tkDelta + stream-id + event + some data)
** Can't do anything.
*/
assert(lpmh->dwBufferLength >= lpmh->dwBytesRecorded);

if (lpmh->dwBufferLength - lpmh->dwBytesRecorded < 4*sizeof(DWORD))
{
if (0 == tkDelta)
return SMF_SUCCESS;

/* If we got here with a real delta, that means smfReadEvents screwed
** up calculating left space and we should flag it somehow.
*/
DPF(1, "Can't fit initial piece of SysEx into buffer!");
return SMF_INVALID_FILE;
}

lpdw = (LPDWORD)(lpmh->lpData + lpmh->dwBytesRecorded);

dwLength = lpmh->dwBufferLength - lpmh->dwBytesRecorded - 3*sizeof(DWORD);
dwLength = min(dwLength, pSmf->cbPendingUserEvent);

*lpdw++ = (DWORD)tkDelta;
*lpdw++ = 0L;
*lpdw++ = (pSmf->dwPendingUserEvent & 0xFF000000L) | (dwLength & 0x00FFFFFFL);

dwRounded = (dwLength + 3) & (~3L);

if (pSmf->fdwSMF & SMF_F_INSERTSYSEX)
{
*((LPBYTE)lpdw)++ = MIDI_SYSEX;
pSmf->fdwSMF &= ~SMF_F_INSERTSYSEX;
--dwLength;
--pSmf->cbPendingUserEvent;
}

if (dwLength & 0x80000000L)
{
DPF(1, "dwLength %08lX dwBytesRecorded %08lX dwBufferLength %08lX", dwLength, lpmh->dwBytesRecorded, lpmh->dwBufferLength);
DPF(1, "cbPendingUserEvent %08lX dwPendingUserEvent %08lX dwRounded %08lX", pSmf->cbPendingUserEvent, pSmf->dwPendingUserEvent, dwRounded);
DPF(1, "Offset into MIDI image %08lX", (DWORD)(pSmf->hpbPendingUserEvent - pSmf->hpbImage));
DPF(1, "!hmemcpy is about to fault");
}

hmemcpy(lpdw, pSmf->hpbPendingUserEvent, dwLength);
if (0 == (pSmf->cbPendingUserEvent -= dwLength))
pSmf->dwPendingUserEvent = 0;

lpmh->dwBytesRecorded += 3*sizeof(DWORD) + dwRounded;

return SMF_SUCCESS;
}

/******************************************************************************
*
* smfSeek
*
* This function moves the file pointer within a track
* and gets the state of the track at the new position. It returns a buffer of
* state information which can be used to set up to play from the new position.
*
* hSmf - Handle of file to seek within
*
* tkPosition - The position to seek to in the track.
*
* lpmh - A buffer to contain the state information.
*
* Returns
* SMF_SUCCESS | The state was successfully read.
* SMF_END_OF_TRACK | The pointer was moved to end of track and no state
* information was returned.
* SMF_INVALID_PARM | The given handle or buffer was invalid.
* SMF_NO_MEMORY | There was insufficient memory in the given buffer to
* contain all of the state data.
*
* The state information in the buffer includes patch changes, tempo changes,
* time signature, key signature,
* and controller information. Only the most recent of these paramters before
* the current position will be stored. The state buffer will be returned
* in polymsg format so that it may be directly transmitted over the MIDI
* bus to bring the state up to date.
*
* The buffer is mean to be sent as a streaming buffer; i.e. immediately
* followed by the first data buffer. If the requested tick position
* does not exist in the file, the last event in the buffer
* will be a MEVT_NOP with a delta time calculated to make sure that
* the next stream event plays at the proper time.
*
* The meta events (tempo, time signature, key signature) will be the
* first events in the buffer if they exist.
*
* Use smfGetStateMaxSize to determine the maximum size of the state
* information buffer. State information that will not fit into the given
* buffer will be lost.
*
* On return, the dwBytesRecorded field of lpmh will contain the
* actual number of bytes stored in the buffer.
*
*****************************************************************************/

typedef struct tag_keyframe
{
/*
** Meta events. All FF's indicates never seen.
*/
BYTE rbTempo[3];

/*
** MIDI channel messages. FF indicates never seen.
*/
BYTE rbProgram[16];
BYTE rbControl[16*120];
} KEYFRAME,
FAR *PKEYFRAME;

#define KF_EMPTY ((BYTE)0xFF)

SMFRESULT FNLOCAL smfSeek(
HSMF hSmf,
TICKS tkPosition,
LPMIDIHDR lpmh)
{
PSMF pSmf = (PSMF)hSmf;
PTRACK ptrk;
DWORD idxTrack;
SMFRESULT smfrc;
EVENT event;
LPDWORD lpdw;
BYTE bEvent;
UINT idx;
UINT idxChannel;
UINT idxController;

static KEYFRAME kf;

_fmemset(&kf, 0xFF, sizeof(kf));

pSmf->tkPosition = 0;
pSmf->fdwSMF &= ~SMF_F_EOF;

for (ptrk = pSmf->rTracks, idxTrack = pSmf->dwTracks; idxTrack--; ptrk++)
{
ptrk->pSmf = pSmf;
ptrk->tkPosition = 0;
ptrk->cbLeft = ptrk->smti.cbLength;
ptrk->hpbImage = pSmf->hpbImage + ptrk->idxTrack;
ptrk->bRunningStatus = 0;
ptrk->fdwTrack = 0;
}

while (SMF_SUCCESS == (smfrc = smfGetNextEvent(pSmf, (SPEVENT)&event, tkPosition)))
{
if (MIDI_META == (bEvent = EVENT_TYPE(event)))
{
if (EVENT_META_TYPE(event) == MIDI_META_TEMPO)
{
if (event.cbParm != sizeof(kf.rbTempo))
return SMF_INVALID_FILE;

hmemcpy((HPBYTE)kf.rbTempo, event.hpbParm, event.cbParm);
}
}
else switch(bEvent & 0xF0)
{
case MIDI_PROGRAMCHANGE:
kf.rbProgram[bEvent & 0x0F] = EVENT_CH_B1(event);
break;

case MIDI_CONTROLCHANGE:
kf.rbControl[(((WORD)bEvent & 0x0F)*120) + EVENT_CH_B1(event)] =
EVENT_CH_B2(event);
break;
}
}

if (SMF_REACHED_TKMAX != smfrc)
{
return smfrc;
}

/* Build lpmh from keyframe
*/
lpmh->dwBytesRecorded = 0;
lpdw = (LPDWORD)lpmh->lpData;

/* Tempo change event?
*/
if (KF_EMPTY != kf.rbTempo[0] ||
KF_EMPTY != kf.rbTempo[1] ||
KF_EMPTY != kf.rbTempo[2])
{
if (lpmh->dwBufferLength - lpmh->dwBytesRecorded < 3*sizeof(DWORD))
return SMF_NO_MEMORY;

*lpdw++ = 0;
*lpdw++ = 0;
*lpdw++ = (((DWORD)kf.rbTempo[0])<<16)|
(((DWORD)kf.rbTempo[1])<<8)|
((DWORD)kf.rbTempo[2])|
(((DWORD)MEVT_TEMPO) << 24);

lpmh->dwBytesRecorded += 3*sizeof(DWORD);
}

/* Program change events?
*/
for (idx = 0; idx < 16; idx++)
{
if (KF_EMPTY != kf.rbProgram[idx])
{
if (lpmh->dwBufferLength - lpmh->dwBytesRecorded < 3*sizeof(DWORD))
return SMF_NO_MEMORY;

*lpdw++ = 0;
*lpdw++ = 0;
*lpdw++ = (((DWORD)MEVT_SHORTMSG) << 24) |
((DWORD)MIDI_PROGRAMCHANGE) |
((DWORD)idx) |
(((DWORD)kf.rbProgram[idx]) << 8);

lpmh->dwBytesRecorded += 3*sizeof(DWORD);
}
}

/* Controller events?
*/
idx = 0;
for (idxChannel = 0; idxChannel < 16; idxChannel++)
{
for (idxController = 0; idxController < 120; idxController++)
{
if (KF_EMPTY != kf.rbControl[idx])
{
if (lpmh->dwBufferLength - lpmh->dwBytesRecorded < 3*sizeof(DWORD))
return SMF_NO_MEMORY;

*lpdw++ = 0;
*lpdw++ = 0;
*lpdw++ = (((DWORD)MEVT_SHORTMSG << 24) |
((DWORD)MIDI_CONTROLCHANGE) |
((DWORD)idxChannel) |
(((DWORD)idxController) << 8) |
(((DWORD)kf.rbControl[idx]) << 16));


lpmh->dwBytesRecorded += 3*sizeof(DWORD);
}

idx++;
}
}

/* Force all tracks to be at tkPosition. We are guaranteed that
** all tracks will be past the event immediately preceding tkPosition;
** this will force correct delta-ticks to be generated so that events
** on all tracks will line up properly on a seek into the middle of the
** file.
*/
for (ptrk = pSmf->rTracks, idxTrack = pSmf->dwTracks; idxTrack--; ptrk++)
{
ptrk->tkPosition = tkPosition;
}

return SMF_SUCCESS;
}

/******************************************************************************
*
* smfGetStateMaxSize
*
* This function returns the maximum sizeof buffer that is needed to
* hold the state information returned by f smfSeek.
*
* pdwSize - Gets the size in bytes that should be allocated
* for the state buffer.
*
* Returns the state size in bytes.
*
*****************************************************************************/
DWORD FNLOCAL smfGetStateMaxSize(
VOID)
{
return 3*sizeof(DWORD) + /* Tempo */
3*16*sizeof(DWORD) + /* Patch changes */
3*16*120*sizeof(DWORD) + /* Controller changes */
3*sizeof(DWORD); /* Time alignment NOP */
}