886 lines
27 KiB
C
886 lines
27 KiB
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 (c) Microsoft Corporation. All rights reserved.
|
|
|
|
/*****************************************************************************
|
|
*
|
|
* SMF.C
|
|
*
|
|
* MIDI File access routines.
|
|
*
|
|
*****************************************************************************/
|
|
#include <windows.h>
|
|
#include <windowsx.h>
|
|
#include <mmsystem.h>
|
|
#include <memory.h>
|
|
#include <strsafe.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;
|
|
}
|
|
|
|
StringCchCopyA(pSmf->szName, 128, psofs->pstrName); // 128 comes from definition of SMF struct in SMFI.h
|
|
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 */
|
|
}
|
|
|