// 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. /***************************************************************************** * * SMFRead.C * * MIDI File access routines. * *****************************************************************************/ #include #include #include #include #include "muldiv32.h" #include "smf.h" #include "smfi.h" #include "debug.h" PRIVATE UINT grbChanMsgLen[] = { 0, /* 0x not a status byte */ 0, /* 1x not a status byte */ 0, /* 2x not a status byte */ 0, /* 3x not a status byte */ 0, /* 4x not a status byte */ 0, /* 5x not a status byte */ 0, /* 6x not a status byte */ 0, /* 7x not a status byte */ 3, /* 8x Note off */ 3, /* 9x Note on */ 3, /* Ax Poly pressure */ 3, /* Bx Control change */ 2, /* Cx Program change */ 2, /* Dx Chan pressure */ 3, /* Ex Pitch bend change */ 0, /* Fx SysEx (see below) */ } ; /****************************************************************************** * * smfBuildFileIndex * * Preliminary parsing of a MIDI file. * * ppSmf - Pointer to a returned SMF structure if the * file is successfully parsed. * * Returns * SMF_SUCCESS The events were successfully read. * SMF_NO_MEMORY Out of memory to build key frames. * SMF_INVALID_FILE A disk or parse error occured on the file. * * This function validates the format of and existing MIDI or RMI file * and builds the handle structure which will refer to it for the * lifetime of the instance. * * The file header information will be read and verified, and * smfBuildTrackIndices will be called on every existing track * to build keyframes and validate the track format. * *****************************************************************************/ SMFRESULT FNLOCAL smfBuildFileIndex( PSMF BSTACK * ppSmf) { SMFRESULT smfrc; UNALIGNED CHUNKHDR * pCh; FILEHDR FAR * pFh; DWORD idx; PSMF pSmf, pSmfTemp; PTRACK pTrk; WORD wMemory; DWORD dwLeft; HPBYTE hpbImage; DWORD idxTrack; EVENT event; BOOL fFirst; DWORD dwLength; HLOCAL hLocal; PTEMPOMAPENTRY pTempo; assert(ppSmf != NULL); pSmf = *ppSmf; assert(pSmf != NULL); /* MIDI data image is already in hpbImage (already extracted from ** RIFF header if necessary). */ /* Validate MIDI header */ dwLeft = pSmf->cbImage; hpbImage = pSmf->hpbImage; if (dwLeft < sizeof(CHUNKHDR)) return SMF_INVALID_FILE; pCh = (CHUNKHDR FAR *)hpbImage; dwLeft -= sizeof(CHUNKHDR); hpbImage += sizeof(CHUNKHDR); if (pCh->fourccType != FOURCC_MThd) return SMF_INVALID_FILE; dwLength = DWORDSWAP(pCh->dwLength); if (dwLength < sizeof(FILEHDR) || dwLength > dwLeft) return SMF_INVALID_FILE; pFh = (FILEHDR FAR *)hpbImage; dwLeft -= dwLength; hpbImage += dwLength; pSmf->dwFormat = (DWORD)(WORDSWAP(pFh->wFormat)); pSmf->dwTracks = (DWORD)(WORDSWAP(pFh->wTracks)); pSmf->dwTimeDivision = (DWORD)(WORDSWAP(pFh->wDivision)); /* ** We've successfully parsed the header. Now try to build the track ** index. ** ** We only check out the track header chunk here; the track will be ** preparsed after we do a quick integretiy check. */ wMemory = sizeof(SMF) + (WORD)(pSmf->dwTracks*sizeof(TRACK)); pSmfTemp = (PSMF)LocalReAlloc((HLOCAL)pSmf, wMemory, LMEM_MOVEABLE|LMEM_ZEROINIT); if (NULL == pSmfTemp) { DPF(1, "No memory for extended pSmf"); return SMF_NO_MEMORY; } pSmf = *ppSmf = pSmfTemp; pTrk = pSmf->rTracks; for (idx=0; idxdwTracks; idx++) { if (dwLeft < sizeof(CHUNKHDR)) return SMF_INVALID_FILE; pCh = (CHUNKHDR FAR *)hpbImage; dwLeft -= sizeof(CHUNKHDR); hpbImage += sizeof(CHUNKHDR); if (pCh->fourccType != FOURCC_MTrk) return SMF_INVALID_FILE; pTrk->idxTrack = (DWORD)(hpbImage - pSmf->hpbImage); pTrk->smti.cbLength = DWORDSWAP(pCh->dwLength); if (pTrk->smti.cbLength > dwLeft) { DPF(1, "Track longer than file!"); return SMF_INVALID_FILE; } dwLeft -= pTrk->smti.cbLength; hpbImage += pTrk->smti.cbLength; pTrk++; } /* File looks OK. Now preparse, doing the following: ** (1) Build tempo map so we can convert to/from ticks quickly ** (2) Determine actual tick length of file ** (3) Validate all events in all tracks */ 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, (EVENT BSTACK *)&event, MAX_TICKS))) { if (MIDI_META == event.abEvent[0] && MIDI_META_TEMPO == event.abEvent[1]) { if (3 != event.cbParm) { return SMF_INVALID_FILE; } if (pSmf->cTempoMap == pSmf->cTempoMapAlloc) { if (NULL != pSmf->hTempoMap) { LocalUnlock(pSmf->hTempoMap); } pSmf->cTempoMapAlloc += C_TEMPO_MAP_CHK; fFirst = FALSE; if (0 == pSmf->cTempoMap) { hLocal = LocalAlloc(LHND, (UINT)(pSmf->cTempoMapAlloc*sizeof(TEMPOMAPENTRY))); fFirst = TRUE; } else { hLocal = LocalReAlloc(pSmf->hTempoMap, (UINT)(pSmf->cTempoMapAlloc*sizeof(TEMPOMAPENTRY)), LHND); } if (NULL == hLocal) { return SMF_NO_MEMORY; } pSmf->pTempoMap = (PTEMPOMAPENTRY)LocalLock(pSmf->hTempoMap = hLocal); } if (fFirst && pSmf->tkPosition != 0) { /* Inserting first event and the absolute time is zero. ** Use defaults of 500,000 uSec/qn from MIDI spec */ pTempo = &pSmf->pTempoMap[pSmf->cTempoMap++]; pTempo->tkTempo = 0; pTempo->msBase = 0; pTempo->dwTempo = MIDI_DEFAULT_TEMPO; fFirst = FALSE; } pTempo = &pSmf->pTempoMap[pSmf->cTempoMap++]; pTempo->tkTempo = pSmf->tkPosition; if (fFirst) pTempo->msBase = 0; else { /* NOTE: Better not be here unless we're q/n format! */ pTempo->msBase = (pTempo-1)->msBase + muldiv32(pTempo->tkTempo-((pTempo-1)->tkTempo), (pTempo-1)->dwTempo, 1000L*pSmf->dwTimeDivision); } pTempo->dwTempo = (((DWORD)event.hpbParm[0])<<16)| (((DWORD)event.hpbParm[1])<<8)| ((DWORD)event.hpbParm[2]); } } if (0 == pSmf->cTempoMap) { DPF(1, "File contains no tempo map! Insert default tempo."); hLocal = LocalAlloc(LHND, sizeof(TEMPOMAPENTRY)); if (!hLocal) return SMF_NO_MEMORY; pSmf->pTempoMap = (PTEMPOMAPENTRY)LocalLock(pSmf->hTempoMap = hLocal); pSmf->cTempoMap = 1; pSmf->cTempoMapAlloc = 1; pSmf->pTempoMap->tkTempo = 0; pSmf->pTempoMap->msBase = 0; pSmf->pTempoMap->dwTempo = MIDI_DEFAULT_TEMPO; } if (SMF_END_OF_FILE == smfrc || SMF_SUCCESS == smfrc) { pSmf->tkLength = pSmf->tkPosition; smfrc = SMF_SUCCESS; } return smfrc; } /****************************************************************************** * * smfGetNextEvent * * Read the next event from the given file. * * pSmf - File to read the event from. * * pEvent - Pointer to an event structure which will receive * basic information about the event. * * tkMax - Tick destination. An attempt to read past this * position in the file will fail. * * Returns * SMF_SUCCESS The events were successfully read. * SMF_END_OF_FILE There are no more events to read in this track. * SMF_REACHED_TKMAX No event was read because

was reached. * SMF_INVALID_FILE A disk or parse error occured on the file. * * This is the lowest level of parsing for a raw MIDI stream. The basic * information about one event in the file will be returned in pEvent. * * Merging data from all tracks into one stream is performed here. * * pEvent->tkDelta will contain the tick delta for the event. * * pEvent->abEvent will contain a description of the event. * pevent->abEvent[0] will contain * F0 or F7 for a System Exclusive message. * FF for a MIDI file meta event. * The status byte of any other MIDI message. (Running status will * be tracked and expanded). * * pEvent->cbParm will contain the number of bytes of paramter data * which is still in the file behind the event header already read. * This data may be read with . Any unread * data will be skipped on the next call to . * * Channel messages (0x8? - 0xE?) will always be returned fully in * pevent->abEvent. * * Meta events will contain the meta type in pevent->abEvent[1]. * * System exclusive events will contain only an 0xF0 or 0xF7 in * pevent->abEvent[0]. * * The following fields in pTrk are used to maintain state and must * be updated if a seek-in-track is performed: * * bRunningStatus contains the last running status message or 0 if * there is no valid running status. * * hpbImage is a pointer into the file image of the first byte of * the event to follow the event just read. * * dwLeft contains the number of bytes from hpbImage to the end * of the track. * * * Get the next due event from all (in-use?) tracks * * For all tracks * If not end-of-track * decode event delta time without advancing through buffer * event_absolute_time = track_tick_time + track_event_delta_time * relative_time = event_absolute_time - last_stream_time * if relative_time is lowest so far * save this track as the next to pull from, along with times * * If we found a track with a due event * Advance track pointer past event, saving ptr to parm data if needed * track_tick_time += track_event_delta_time * last_stream_time = track_tick_time * Else * Mark and return end_of_file * *****************************************************************************/ SMFRESULT FNLOCAL smfGetNextEvent( PSMF pSmf, EVENT BSTACK * pEvent, TICKS tkMax) { PTRACK pTrk; PTRACK pTrkFound; DWORD idxTrack; TICKS tkEventDelta; TICKS tkRelTime; TICKS tkMinRelTime; BYTE bEvent; DWORD dwGotTotal; DWORD dwGot; DWORD cbEvent; assert(pSmf != NULL); assert(pEvent != NULL); if (pSmf->fdwSMF & SMF_F_EOF) { return SMF_END_OF_FILE; } pTrkFound = NULL; tkMinRelTime = MAX_TICKS; for (pTrk = pSmf->rTracks, idxTrack = pSmf->dwTracks; idxTrack--; pTrk++) { if (pTrk->fdwTrack & SMF_TF_EOT) continue; if (!smfGetVDword(pTrk->hpbImage, pTrk->cbLeft, (DWORD BSTACK *)&tkEventDelta)) { DPF(1, "Hit end of track w/o end marker!"); return SMF_INVALID_FILE; } tkRelTime = pTrk->tkPosition + tkEventDelta - pSmf->tkPosition; if (tkRelTime < tkMinRelTime) { tkMinRelTime = tkRelTime; pTrkFound = pTrk; } } if (!pTrkFound) { pSmf->fdwSMF |= SMF_F_EOF; return SMF_END_OF_FILE; } pTrk = pTrkFound; if (pSmf->tkPosition + tkMinRelTime >= tkMax) { return SMF_REACHED_TKMAX; } pTrk->hpbImage += (dwGot = smfGetVDword(pTrk->hpbImage, pTrk->cbLeft, (DWORD BSTACK *)&tkEventDelta)); pTrk->cbLeft -= dwGot; /* We MUST have at least three bytes here (cause we haven't hit ** the end-of-track meta yet, which is three bytes long). Checking ** against three means we don't have to check how much is left ** in the track again for any short event, which is most cases. */ if (pTrk->cbLeft < 3) { return SMF_INVALID_FILE; } pTrk->tkPosition += tkEventDelta; pEvent->tkDelta = pTrk->tkPosition - pSmf->tkPosition; pSmf->tkPosition = pTrk->tkPosition; bEvent = *pTrk->hpbImage++; if (MIDI_MSG > bEvent) { if (0 == pTrk->bRunningStatus) { return SMF_INVALID_FILE; } dwGotTotal = 1; pEvent->abEvent[0] = pTrk->bRunningStatus; pEvent->abEvent[1] = bEvent; if (3 == grbChanMsgLen[(pTrk->bRunningStatus >> 4) & 0x0F]) { pEvent->abEvent[2] = *pTrk->hpbImage++; dwGotTotal++; } } else if (MIDI_SYSEX > bEvent) { pTrk->bRunningStatus = bEvent; dwGotTotal = 2; pEvent->abEvent[0] = bEvent; pEvent->abEvent[1] = *pTrk->hpbImage++; if (3 == grbChanMsgLen[(bEvent >> 4) & 0x0F]) { pEvent->abEvent[2] = *pTrk->hpbImage++; dwGotTotal++; } } else { pTrk->bRunningStatus = 0; if (MIDI_META == bEvent) { pEvent->abEvent[0] = MIDI_META; if (MIDI_META_EOT == (pEvent->abEvent[1] = *pTrk->hpbImage++)) { pTrk->fdwTrack |= SMF_TF_EOT; } dwGotTotal = 2; } else if (MIDI_SYSEX == bEvent || MIDI_SYSEXEND == bEvent) { pEvent->abEvent[0] = bEvent; dwGotTotal = 1; } else { return SMF_INVALID_FILE; } if (0 == (dwGot = smfGetVDword(pTrk->hpbImage, pTrk->cbLeft - 2, (DWORD BSTACK *)&cbEvent))) { return SMF_INVALID_FILE; } pTrk->hpbImage += dwGot; dwGotTotal += dwGot; if (dwGotTotal + cbEvent > pTrk->cbLeft) { return SMF_INVALID_FILE; } pEvent->cbParm = cbEvent; pEvent->hpbParm = pTrk->hpbImage; pTrk->hpbImage += cbEvent; dwGotTotal += cbEvent; } assert(pTrk->cbLeft >= dwGotTotal); pTrk->cbLeft -= dwGotTotal; return SMF_SUCCESS; } /****************************************************************************** * * smfGetVDword * * Reads a variable length DWORD from the given file. * * hpbImage - Pointer to the first byte of the VDWORD. * * dwLeft - Bytes left in image * * pDw - Pointer to a DWORD to store the result in. * track. * * Returns the number of bytes consumed from the stream. * * A variable length DWORD stored in a MIDI file contains one or more * bytes. Each byte except the last has the high bit set; only the * low 7 bits are significant. * *****************************************************************************/ DWORD FNLOCAL smfGetVDword( HPBYTE hpbImage, DWORD dwLeft, DWORD BSTACK * pDw) { BYTE b; DWORD dwUsed = 0; assert(hpbImage != NULL); assert(pDw != NULL); *pDw = 0; do { if (!dwLeft) { return 0; } b = *hpbImage++; dwLeft--; dwUsed++; *pDw = (*pDw << 7) | (b & 0x7F); } while (b&0x80); return dwUsed; }