www.pudn.com > LSJ_MIDIPLYR.rar > 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 (C) 1993 - 1997 Microsoft Corporation. All Rights Reserved. 
* 
****************************************************************************** 
* 
* SMF.C 
* 
* MIDI File access routines. 
* 
*****************************************************************************/ 
#include  
#include  
#include  
#include  
#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  
*****************************************************************************/ 
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   */ 
}