// ------------------------- // Mid2StreamRewindConverter // This little function is an adaptation of the ConverterInit() code which // resets the tracks without closing and opening the file, thus reducing the // time it takes to loop back to the beginning when looping. // ------------------------- static BOOL Mid2StreamRewindConverter(VOID) { DWORD iTrack; LPINTRACKSTATE pInTrack; tkCurrentTime = 0; // reset to start of midi file ifs.iBytesLeft = ifs.FileSize; ifs.pFilePointer = ifs.pFile; for (iTrack = 0, pInTrack = ifs.pTracks; iTrack < ifs.nTracks; ++iTrack, ++pInTrack) { pInTrack->iBytesLeft = pInTrack->iTrackLen; // Setup pointer to the current position in the track pInTrack->pTrackPointer = pInTrack->pTrackData; pInTrack->fdwTrack = 0; pInTrack->bRunningStatus = BAD_MIDI_FIX; pInTrack->tkNextEventDue = 0; // Handle bozo MIDI files which contain empty track chunks if (!pInTrack->iBytesLeft) { pInTrack->fdwTrack |= ITS_F_ENDOFTRK; continue; } // We always preread the time from each track so the mixer code can // determine which track has the next event with a minimum of work if (!GetTrackVDWord(pInTrack, &pInTrack->tkNextEventDue)) { I_OutputMsg("Read error while reading first delta time of track.\n"); return TRUE; } } // End of track initialization code return FALSE; }
// // GetTrackEvent // // Fills in the event struct with the next event from the track // // pteTemp->tkEvent will contain the absolute tick time of the event // pteTemp->byShortData[0] will contain // MIDI_META if the event is a meta event; // in this case pteTemp->byShortData[1] will contain the meta class // MIDI_SYSEX or MIDI_SYSEXEND if the event is a SysEx event // Otherwise, the event is a channel message and pteTemp->byShortData[1] // and pteTemp->byShortData[2] will contain the rest of the event. // // pteTemp->dwEventLength will contain // The total length of the channel message in pteTemp->byShortData if // the event is a channel message // The total length of the paramter data pointed to by // pteTemp->pLongData otherwise // // pteTemp->pLongData will point at any additional paramters if the // event is a SysEx or meta event with non-zero length; else // it will contain NULL // // Returns FALSE on success or TRUE on any kind of parse error // Prints its own error message ONLY in the debug version // // Maintains the state of the input track (i.e. ptsTrack->dwLeftInBuffer, // ptsTrack->pTrackPointers, and ptsTrack->byRunningStatus). // static BOOL GetTrackEvent( INTRACKSTATE *ptsTrack, PTEMPEVENT pteTemp ) { DWORD idx; BYTE byByte; UINT dwEventLength; // Clear out the temporary event structure to get rid of old data... memset( pteTemp, 0, sizeof(TEMPEVENT)); // Already at end of track? There's nothing to read. // if(( ptsTrack->fdwTrack & ITS_F_ENDOFTRK ) || ( !ptsTrack->dwLeftInBuffer && !ptsTrack->dwLeftOnDisk )) return( TRUE ); // Get the first byte, which determines the type of event. // if( GetTrackByte( ptsTrack, &byByte )) return( TRUE ); // If the high bit is not set, then this is a channel message // which uses the status byte from the last channel message // we saw. NOTE: We do not clear running status across SysEx or // meta events even though the spec says to because there are // actually files out there which contain that sequence of data. // if( !( byByte & 0x80 )) { // No previous status byte? We're hosed. if( !ptsTrack->byRunningStatus ) { TRACKERR(ptsTrack, gteBadRunStat); return( TRUE ); } pteTemp->byShortData[0] = ptsTrack->byRunningStatus; pteTemp->byShortData[1] = byByte; byByte = pteTemp->byShortData[0] & 0xF0; pteTemp->dwEventLength = 2; // Only program change and channel pressure events are 2 bytes long; // the rest are 3 and need another byte // if(( byByte != MIDI_PRGMCHANGE ) && ( byByte != MIDI_CHANPRESS )) { if( !ptsTrack->dwLeftInBuffer && !ptsTrack->dwLeftOnDisk ) { TRACKERR( ptsTrack, gteRunStatMsgTrunc ); ptsTrack->fdwTrack |= ITS_F_ENDOFTRK; return( TRUE ); } if( GetTrackByte( ptsTrack, &pteTemp->byShortData[2] )) return( TRUE ); ++pteTemp->dwEventLength; } } else if(( byByte & 0xF0 ) != MIDI_SYSEX ) { // Not running status, not in SysEx range - must be // normal channel message (0x80-0xEF) // pteTemp->byShortData[0] = byByte; ptsTrack->byRunningStatus = byByte; // Strip off channel and just keep message type // byByte &= 0xF0; dwEventLength = ( byByte == MIDI_PRGMCHANGE || byByte == MIDI_CHANPRESS ) ? 1 : 2; pteTemp->dwEventLength = dwEventLength + 1; if(( ptsTrack->dwLeftInBuffer + ptsTrack->dwLeftOnDisk ) < dwEventLength ) { TRACKERR( ptsTrack, gteChanMsgTrunc ); ptsTrack->fdwTrack |= ITS_F_ENDOFTRK; return( TRUE ); } if( GetTrackByte( ptsTrack, &pteTemp->byShortData[1] )) return( TRUE ); if( dwEventLength == 2 ) if( GetTrackByte( ptsTrack, &pteTemp->byShortData[2] )) return( TRUE ); } else if(( byByte == MIDI_SYSEX ) || ( byByte == MIDI_SYSEXEND )) { // One of the SysEx types. (They are the same as far as we're concerned; // there is only a semantic difference in how the data would actually // get sent when the file is played. We must take care to put the proper // event type back on the output track, however.) // // Parse the general format of: // BYTE bEvent (MIDI_SYSEX or MIDI_SYSEXEND) // VDWORD cbParms // BYTE abParms[cbParms] // pteTemp->byShortData[0] = byByte; if( GetTrackVDWord( ptsTrack, &pteTemp->dwEventLength )) { TRACKERR( ptsTrack, gteSysExLenTrunc ); return( TRUE ); } if(( ptsTrack->dwLeftInBuffer + ptsTrack->dwLeftOnDisk ) < pteTemp->dwEventLength ) { TRACKERR( ptsTrack, gteSysExTrunc ); ptsTrack->fdwTrack |= ITS_F_ENDOFTRK; return( TRUE ); } // Malloc a temporary memory block to hold the parameter data if(( pteTemp->pLongData = malloc( pteTemp->dwEventLength )) == NULL ) { TRACKERR( ptsTrack, gteNoMem ); return( TRUE ); } // Copy from the input buffer to the parameter data buffer for( idx = 0; idx < pteTemp->dwEventLength; idx++ ) if( GetTrackByte( ptsTrack, pteTemp->pLongData + idx )) { TRACKERR( ptsTrack, gteSysExTrunc ); return( TRUE ); } // Increment our counter, which tells the program to look around for // a malloc block to free, should it need to exit or reset before the // block would normally be freed dwMallocBlocks++; } else if( byByte == MIDI_META ) { // It's a meta event. Parse the general form: // BYTE bEvent (MIDI_META) // BYTE bClass // VDWORD cbParms // BYTE abParms[cbParms] // pteTemp->byShortData[0] = byByte; if( !ptsTrack->dwLeftInBuffer && !ptsTrack->dwLeftOnDisk ) { TRACKERR(ptsTrack, gteMetaNoClass ); ptsTrack->fdwTrack |= ITS_F_ENDOFTRK; return( TRUE ); } if( GetTrackByte( ptsTrack, &pteTemp->byShortData[1] )) return( TRUE ); if( GetTrackVDWord( ptsTrack, &pteTemp->dwEventLength )) { TRACKERR( ptsTrack, gteMetaLenTrunc ); return( TRUE ); } // NOTE: It's perfectly valid to have a meta with no data // In this case, dwEventLength == 0 and pLongData == NULL // if( pteTemp->dwEventLength ) { if(( ptsTrack->dwLeftInBuffer + ptsTrack->dwLeftOnDisk ) < pteTemp->dwEventLength ) { TRACKERR( ptsTrack, gteMetaTrunc ); ptsTrack->fdwTrack |= ITS_F_ENDOFTRK; return( TRUE ); } // Malloc a temporary memory block to hold the parameter data if(( pteTemp->pLongData = malloc( pteTemp->dwEventLength )) == NULL ) { TRACKERR( ptsTrack, gteNoMem ); return( TRUE ); } // Copy from the input buffer to the parameter data buffer for( idx = 0; idx < pteTemp->dwEventLength; idx++ ) if( GetTrackByte( ptsTrack, pteTemp->pLongData + idx )) { TRACKERR( ptsTrack, gteMetaTrunc ); return( TRUE ); } // Increment our counter, which tells the program to look around for // a malloc block to free, should it need to exit or reset before the // block would normally be freed dwMallocBlocks++; } if( pteTemp->byShortData[1] == MIDI_META_EOT ) ptsTrack->fdwTrack |= ITS_F_ENDOFTRK; } else { // Messages in this range are system messages and aren't supposed to // be in a normal MIDI file. If they are, we've either misparsed or the // authoring software is stupid. // return( TRUE ); } // Event time was already stored as the current track time // pteTemp->tkEvent = ptsTrack->tkNextEventDue; // Now update to the next event time. The code above MUST properly // maintain the end of track flag in case the end of track meta is // missing. NOTE: This code is a continuation of the track event // time pre-read which is done at the end of track initialization. // if( !( ptsTrack->fdwTrack & ITS_F_ENDOFTRK )) { DWORD tkDelta; if( GetTrackVDWord( ptsTrack, &tkDelta )) return( TRUE ); ptsTrack->tkNextEventDue += tkDelta; } return( FALSE ); }
static BOOL RewindConverter( void ) { DWORD dwToRead, cbRead, idx; BOOL fRet; PINTRACKSTATE ptsTrack; tkCurrentTime = 0; for( idx = 0, ptsTrack = ifs.pitsTracks; idx < ifs.dwTrackCount; ++idx, ++ptsTrack ) { /////////////////////////////////////////////////////////////////////////////// // Here we need to determine if all track data will fit into a single one of // our track buffers. If not, we need to read in a buffer full and come back // for more later, saving the file offset to continue from and the amount left // to read in the track structure. // SetFilePointer( hInFile, ptsTrack->foTrackStart, NULL, FILE_BEGIN ); SetFilePointer2( ptsTrack->foTrackStart, NULL, FILE_BEGIN ); if( ptsTrack->dwTrackLength > TRACK_BUFFER_SIZE ) dwToRead = TRACK_BUFFER_SIZE; else dwToRead = ptsTrack->dwTrackLength; /* if( !ReadFile( hInFile, ptsTrack->pTrackStart, dwToRead, &cbRead, NULL ) || ( cbRead != dwToRead )) { MessageBox( GetActiveWindow(), szInitErrInFile, "TEST", MB_OK | MB_ICONEXCLAMATION ); goto Rewind_Cleanup; }*/ if( !ReadFile2( ptsTrack->pTrackStart, dwToRead, &cbRead, NULL ) || ( cbRead != dwToRead )) { Con_Printf("MIDI: %s\n", szInitErrInFile); goto Rewind_Cleanup; } // Save the number of bytes that didn't make it into the buffer ptsTrack->dwLeftOnDisk = ptsTrack->dwTrackLength - cbRead; ptsTrack->dwLeftInBuffer = cbRead; // Save the current file offset so we can seek to it later /* ptsTrack->foNextReadStart = SetFilePointer( hInFile, 0, NULL, FILE_CURRENT );*/ ptsTrack->foNextReadStart = SetFilePointer2( 0, NULL, FILE_CURRENT ); // Setup pointer to the current position in the track ptsTrack->pTrackCurrent = ptsTrack->pTrackStart; ptsTrack->fdwTrack = 0; ptsTrack->byRunningStatus = 0; ptsTrack->tkNextEventDue = 0; // Handle bozo MIDI files which contain empty track chunks // if( !ptsTrack->dwLeftInBuffer && !ptsTrack->dwLeftOnDisk ) { ptsTrack->fdwTrack |= ITS_F_ENDOFTRK; continue; } // We always preread the time from each track so the mixer code can // determine which track has the next event with a minimum of work // if( GetTrackVDWord( ptsTrack, &ptsTrack->tkNextEventDue )) { Con_Printf("MIDI: %s\n", szInitErrInFile); goto Rewind_Cleanup; } // Step over any unread data, advancing to the beginning of the next // track's data /* SetFilePointer( hInFile, ptsTrack->foTrackStart + ptsTrack->dwTrackLength, NULL, FILE_BEGIN );*/ SetFilePointer2( ptsTrack->foTrackStart + ptsTrack->dwTrackLength, NULL, FILE_BEGIN ); } // End of track initialization code fRet = FALSE; Rewind_Cleanup: if( fRet ) return( TRUE ); return( FALSE ); }
// ConverterInit // // Open the input file // Allocate and read the entire input file into memory // Validate the input file structure // Allocate the input track structures and initialize them // Initialize the output track structures // // Return TRUE on success // Prints its own error message if something goes wrong // BOOL ConverterInit( LPSTR szInFile ) { BOOL fRet = TRUE; DWORD cbRead, dwTag, cbHeader, dwToRead; MIDIFILEHDR Header; PINTRACKSTATE ptsTrack; UINT idx; tkCurrentTime = 0; // Initialize things we'll try to free later if we fail // memset( &ifs, 0, sizeof(INFILESTATE)); ifs.cbFileLength = 0; ifs.pitsTracks = NULL; // Attempt to open the input and output files // MidiData = (byte *)COM_LoadHunkFile2((char *)szInFile, (int *)&ifs.cbFileLength); if (!MidiData) { goto Init_Cleanup; } MidiOffset = 0; MidiSize = ifs.cbFileLength; /* hInFile = CreateFile( szInFile, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); if( hInFile == INVALID_HANDLE_VALUE ) { wsprintf( szTemp, "Could not open \"%s\" for read.\n", szInFile ); MessageBox( GetActiveWindow(), szTemp, "TEST", MB_OK | MB_ICONEXCLAMATION ); goto Init_Cleanup; } */ // Figure out how big the input file is. /* if((( ifs.cbFileLength = GetFileSize( hInFile, NULL )) == (UINT)-1 )) { MessageBox( GetActiveWindow(), "File system error on input file.\n", "TEST", MB_OK | MB_ICONEXCLAMATION ); goto Init_Cleanup; }*/ // Set up to read from the memory buffer. Read and validate // - MThd header // - size of file header chunk // - file header itself // if( GetInFileData( &dwTag, sizeof(DWORD)) || ( dwTag != MThd ) || GetInFileData( &cbHeader, sizeof(DWORD)) || (( cbHeader = DWORDSWAP( cbHeader )) < sizeof(MIDIFILEHDR)) || GetInFileData( &Header, cbHeader ) ) { Con_Printf("MIDI: %s\n",szInitErrInFile); goto Init_Cleanup; } // File header is stored in hi-lo order. Swap this into Intel order and save // parameters in our native int size (32 bits) // ifs.dwFormat = (DWORD)WORDSWAP( Header.wFormat ); ifs.dwTrackCount = (DWORD)WORDSWAP( Header.wTrackCount ); ifs.dwTimeDivision = (DWORD)WORDSWAP( Header.wTimeDivision ); // We know how many tracks there are; allocate the structures for them and parse // them. The parse merely looks at the MTrk signature and track chunk length // in order to skip to the next track header. // ifs.pitsTracks = (PINTRACKSTATE)GlobalAllocPtr( GPTR, ifs.dwTrackCount * sizeof(INTRACKSTATE)); if( ifs.pitsTracks == NULL ) { Con_Printf("MIDI: %s\n",szInitErrMem); goto Init_Cleanup; } for( idx = 0, ptsTrack = ifs.pitsTracks; idx < ifs.dwTrackCount; ++idx, ++ptsTrack ) { if(( ptsTrack->pTrackStart = GlobalAllocPtr( GHND, TRACK_BUFFER_SIZE )) == NULL ) { Con_Printf("MIDI: %s\n", szNoTrackBuffMem); goto Init_Cleanup; } if( GetInFileData( &dwTag, sizeof(dwTag)) || ( dwTag != MTrk ) || GetInFileData( &cbHeader, sizeof(cbHeader))) { Con_Printf("MIDI: %s\n", szInitErrInFile); goto Init_Cleanup; } cbHeader = DWORDSWAP( cbHeader ); ptsTrack->dwTrackLength = cbHeader; // Total track length /////////////////////////////////////////////////////////////////////////////// // Here we need to determine if all track data will fit into a single one of // our track buffers. If not, we need to read in a buffer full and come back // for more later, saving the file offset to continue from and the amount left // to read in the track structure. // Save the file offset of the beginning of this track /* ptsTrack->foTrackStart = SetFilePointer( hInFile, 0, NULL, FILE_CURRENT );*/ ptsTrack->foTrackStart = SetFilePointer2( 0, NULL, FILE_CURRENT ); if( ptsTrack->dwTrackLength > TRACK_BUFFER_SIZE ) dwToRead = TRACK_BUFFER_SIZE; else dwToRead = ptsTrack->dwTrackLength; /* if( !ReadFile( hInFile, ptsTrack->pTrackStart, dwToRead, &cbRead, NULL ) || ( cbRead != dwToRead )) { MessageBox( GetActiveWindow(), szInitErrInFile, "TEST", MB_OK | MB_ICONEXCLAMATION ); goto Init_Cleanup; }*/ if( !ReadFile2( ptsTrack->pTrackStart, dwToRead, &cbRead, NULL ) || ( cbRead != dwToRead )) { Con_Printf("MIDI: %s\n", szInitErrInFile); goto Init_Cleanup; } // Save the number of bytes that didn't make it into the buffer ptsTrack->dwLeftOnDisk = ptsTrack->dwTrackLength - cbRead; ptsTrack->dwLeftInBuffer = cbRead; // Save the current file offset so we can seek to it later /* ptsTrack->foNextReadStart = SetFilePointer( hInFile, 0, NULL, FILE_CURRENT );*/ ptsTrack->foNextReadStart = SetFilePointer2( 0, NULL, FILE_CURRENT ); // Setup pointer to the current position in the track ptsTrack->pTrackCurrent = ptsTrack->pTrackStart; ptsTrack->fdwTrack = 0; ptsTrack->byRunningStatus = 0; ptsTrack->tkNextEventDue = 0; // Handle bozo MIDI files which contain empty track chunks // if( !ptsTrack->dwLeftInBuffer && !ptsTrack->dwLeftOnDisk ) { ptsTrack->fdwTrack |= ITS_F_ENDOFTRK; continue; } // We always preread the time from each track so the mixer code can // determine which track has the next event with a minimum of work // if( GetTrackVDWord( ptsTrack, &ptsTrack->tkNextEventDue )) { Con_Printf("MIDI: %s\n", szInitErrInFile); goto Init_Cleanup; } // Step over any unread data, advancing to the beginning of the next // track's data /* SetFilePointer( hInFile, ptsTrack->foTrackStart + ptsTrack->dwTrackLength, NULL, FILE_BEGIN );*/ SetFilePointer2( ptsTrack->foTrackStart + ptsTrack->dwTrackLength, NULL, FILE_BEGIN ); } // End of track initialization code fRet = FALSE; Init_Cleanup: if( fRet ) ConverterCleanup(); return( fRet ); }
/* GetTrackEvent * * Fills in the event struct with the next event from the track * * te->event_time will contain the absolute tick time of the event. * * te->shortdata[0] will contain: * - MIDI_META_EVENT if the event is a meta event; in this case * te->shortdata[1] will contain the meta class. * - MIDICMD_SYSEX or MIDICMD_SYSEX_END if the event is a SysEx event * - Otherwise, event is a channel message and te->shortdata[1] and * te->shortdata[2] will contain the rest of the event. * * te->event_len will contain: * - The total length of the channel message in te->shortdata if the * event is a channel message, * - The total length of the paramter data pointed to by te->longdata, * otherwise. * * te->longdata will point at any additional paramters if the event is * a SysEx or meta event with non-zero length; else it will be NULL. * * Returns zero on success or non-zero on any kind of parse error * Maintains the state of the input track (i.e. ts->left_in_buffer, * ts->pTrackPointers, and ts->running_status). */ static int GetTrackEvent (track_state_t *ts, temp_event_t *te) { DWORD idx; BYTE b; UINT event_len; /* Clear out the temporary event structure to get rid of old data */ memset(te, 0, sizeof(struct _temp_event_s)); /* Already at end of track? There's nothing to read. */ if (ts->status & ITS_F_ENDOFTRK) return 1; if (!ts->left_in_buffer && !ts->left_on_disk) return 1; /* Get the first byte, which determines the type of event. */ if (GetTrackByte(ts, &b)) return 1; /* If the high bit is not set, then this is a channel message * which uses the status byte from the last channel message * we saw. NOTE: We do not clear running status across SysEx or * meta events even though the spec says to because there are * actually files out there which contain that sequence of data. */ if ( !(b & 0x80)) { /* No previous status byte? We're hosed. */ if (!ts->running_status) { TRACKERR(ts, err_bad_runstat); return 1; } te->shortdata[0] = ts->running_status; te->shortdata[1] = b; b = te->shortdata[0] & 0xF0; te->event_len = 2; /* Only program change and channel pressure events are * 2 bytes long. the rest are 3 and need another byte. */ if (b != MIDICMD_PGM_CHANGE && b != MIDICMD_CHANNEL_PRESSURE) { if (!ts->left_in_buffer && !ts->left_on_disk) { TRACKERR(ts, err_trunc_runstat); ts->status |= ITS_F_ENDOFTRK; return 1; } if (GetTrackByte(ts, &te->shortdata[2])) return 1; ++te->event_len; } } else if ((b & 0xF0) != MIDICMD_SYSEX) { /* Not running status, not in SysEx range - must be * normal channel message (0x80-0xEF) */ te->shortdata[0] = b; ts->running_status = b; /* Strip off channel and just keep message type */ b &= 0xF0; event_len = (b == MIDICMD_PGM_CHANGE || b == MIDICMD_CHANNEL_PRESSURE) ? 1 : 2; te->event_len = event_len + 1; if ((ts->left_in_buffer + ts->left_on_disk) < event_len) { TRACKERR(ts, err_trunc_chan_msg); ts->status |= ITS_F_ENDOFTRK; return 1; } if (GetTrackByte(ts, &te->shortdata[1])) return 1; if (event_len == 2) { if (GetTrackByte(ts, &te->shortdata[2])) return 1; } } else if (b == MIDICMD_SYSEX || b == MIDICMD_SYSEX_END) { /* One of the SysEx types. (They are the same as far as we're * concerned; there is only a semantic difference in how the * data would actually get sent when the file is played. * We must take care to put the proper event type back on the * output track, however.) * * Parse the general format of: * BYTE event (MIDICMD_SYSEX or MIDICMD_SYSEX_END) * VDWORD num_parms * BYTE ab_parms[num_parms] */ te->shortdata[0] = b; if (GetTrackVDWord(ts, &te->event_len)) { TRACKERR(ts, err_trunc_sysex_len); return 1; } if ((ts->left_in_buffer + ts->left_on_disk) < te->event_len) { TRACKERR(ts, err_trunc_sysex); ts->status |= ITS_F_ENDOFTRK; return 1; } te->longdata = (BYTE *) Z_Malloc(te->event_len, Z_MAINZONE); for (idx = 0; idx < te->event_len; idx++) { if (GetTrackByte(ts, te->longdata + idx)) { Z_Free(te->longdata); te->longdata = NULL; TRACKERR(ts, err_trunc_sysex); return 1; } } } else if (b == MIDI_META_EVENT) { /* It's a meta event. Parse the general form: * BYTE event (MIDI_META_EVENT) * BYTE class * VDWORD num_parms * BYTE ab_parms[num_parms] */ te->shortdata[0] = b; if (!ts->left_in_buffer && !ts->left_on_disk) { TRACKERR(ts, err_meta_noclass); ts->status |= ITS_F_ENDOFTRK; return 1; } if (GetTrackByte(ts, &te->shortdata[1])) return 1; if (GetTrackVDWord(ts, &te->event_len)) { TRACKERR(ts, err_trunc_meta_len); return 1; } /* NOTE: It's perfectly valid to have a meta with no data * In this case, event_len == 0 and longdata == NULL */ if (te->event_len) { if ((ts->left_in_buffer + ts->left_on_disk) < te->event_len) { TRACKERR(ts, err_trunc_meta); ts->status |= ITS_F_ENDOFTRK; return 1; } te->longdata = (BYTE *) Z_Malloc(te->event_len, Z_MAINZONE); for (idx = 0; idx < te->event_len; idx++) { if (GetTrackByte(ts, te->longdata + idx)) { Z_Free(te->longdata); te->longdata = NULL; TRACKERR(ts, err_trunc_meta); return 1; } } } if (te->shortdata[1] == MIDI_META_EOT) ts->status |= ITS_F_ENDOFTRK; } else { /* Messages in this range are system messages and aren't * supposed to be in a normal MIDI file. If they are, we * have either misparsed or the authoring software is stupid. */ return 1; } /* Event time was already stored as the current track time */ te->event_time = ts->next_event_time; /* Now update to the next event time. The code above MUST properly * maintain the end of track flag in case the end of track meta is * missing. NOTE: This code is a continuation of the track event * time pre-read which is done at the end of track initialization. */ if ( !(ts->status & ITS_F_ENDOFTRK)) { DWORD delta_time; if (GetTrackVDWord(ts, &delta_time)) return 1; ts->next_event_time += delta_time; } return 0; }
/* RewindConverter * * An adaptation of the ConverterInit() code which resets the * tracks without closing and opening the file. */ static int RewindConverter (void) { DWORD bytes_wanted, bytes_read, idx; int err = 1; track_state_t *ts; currenttime = 0; for (idx = 0, ts = mfs.tracks; idx < mfs.numtracks; ++idx, ++ts) { /* Determine whether all track data will fit into a single one of our track * buffers. If not, we need to read in a buffer full and come back for more * later, saving the file offset to continue from and the amount left to read * in the track structure. */ MID2STREAM_seek(ts->start_ofs, SEEK_SET); if (ts->length > TRACK_BUFFER_SIZE) bytes_wanted = TRACK_BUFFER_SIZE; else bytes_wanted = ts->length; MID2STREAM_readfile(ts->start_ptr, bytes_wanted, &bytes_read); if (bytes_read != bytes_wanted) goto Rewind_Cleanup; /* Save the number of bytes that didn't make it into the buffer */ ts->left_on_disk = ts->length - bytes_read; ts->left_in_buffer = bytes_read; /* Save the current file offset so we can seek to it later */ ts->nextread_ofs = MID2STREAM_seek(0, SEEK_CUR); /* Setup pointer to the current position in the track */ ts->current_ptr = ts->start_ptr; ts->status = 0; ts->running_status = 0; ts->next_event_time = 0; /* Handle bozo MIDI files which contain empty track chunks */ if (!ts->left_in_buffer && !ts->left_on_disk) { ts->status |= ITS_F_ENDOFTRK; continue; } /* always preread the time from each track so the mixer code can * determine which track has the next event with minimum work. */ if (GetTrackVDWord(ts, &ts->next_event_time)) goto Rewind_Cleanup; /* Step over any unread data, advancing to the beginning of the next * track's data */ MID2STREAM_seek(ts->start_ofs + ts->length, SEEK_SET); } /* End of track initialization code */ err = 0; Rewind_Cleanup: if (err) Con_Printf("MIDI: %s\n", err_bad_midi_file); return err; }
/* ConverterInit * * Open the input file * Allocate and read the entire input file into memory * Validate the input file structure * Allocate the input track structures and initialize them * Initialize the output track structures * * Return zero on success */ int ConverterInit (const char *filename) { int err = 1; DWORD bytes_wanted, bytes_read, magic, bytes; UINT idx; midihdr_t header; track_state_t *ts; currenttime = 0; memset (&mfs, 0, sizeof(midi_filestate_t)); memset (&midi_fh, 0, sizeof(fshandle_t)); if (MID2STREAM_fileopen(filename) != 0) return 1; mfs.length = midi_fh.length; /* Read and validate MThd header, size of file header chunk * and the file header itself. */ if (GetInFileData(&magic, sizeof(DWORD))) goto Init_Cleanup; magic = (DWORD)BigLong(magic); if (magic == MIDI_MAGIC_RIFF) /* RMID ?? */ { if (GetInFileData(&bytes, sizeof(DWORD)) != 0 || /* size */ GetInFileData(&magic, sizeof(DWORD)) != 0 || MIDI_MAGIC_RMID != (DWORD)BigLong(magic) || GetInFileData(&magic, sizeof(DWORD)) != 0 || /* "data" */0x64617461 != (DWORD)BigLong(magic) || GetInFileData(&bytes, sizeof(DWORD)) != 0 || /* size */ /* SMF must begin from here onwards: */ GetInFileData(&magic, sizeof(DWORD)) != 0) { goto Init_Cleanup; } magic = (DWORD)BigLong(magic); } if (magic != MIDI_MAGIC_MTHD) goto Init_Cleanup; if (GetInFileData(&bytes, sizeof(DWORD))) goto Init_Cleanup; if ((bytes = (DWORD)BigLong(bytes)) < sizeof(midihdr_t)) goto Init_Cleanup; if (GetInFileData(&header, bytes)) goto Init_Cleanup; /* File header is stored in big endian (hi-lo) order. */ mfs.format = (DWORD) BigShort(header.format); mfs.numtracks = (DWORD) BigShort(header.numtracks); mfs.timediv = (DWORD) BigShort(header.timediv); if (mfs.format != 0 && mfs.format != 1) /* Type-2 not supported */ goto Init_Cleanup; if (mfs.numtracks == 0) goto Init_Cleanup; if (mfs.format == 0 && mfs.numtracks != 1) goto Init_Cleanup; /* We know how many tracks there are; allocate structures for them * and parse them. The parse merely looks at the MTrk signature and * track chunk length in order to skip to the next track header. */ mfs.tracks = (track_state_t *) Z_Malloc(mfs.numtracks * sizeof(track_state_t), Z_MAINZONE); for (idx = 0, ts = mfs.tracks; idx < mfs.numtracks; ++idx, ++ts) { ts->start_ptr = (BYTE *) Z_Malloc(TRACK_BUFFER_SIZE, Z_MAINZONE); if (GetInFileData(&magic, sizeof(magic))) goto Init_Cleanup; if ((magic = (DWORD)BigLong(magic)) != MIDI_MAGIC_MTRK) goto Init_Cleanup; if (GetInFileData(&bytes, sizeof(bytes))) goto Init_Cleanup; bytes = (DWORD)BigLong(bytes); ts->length = bytes; /* Total track length */ /* Determine whether all track data will fit into a single one of our track * buffers. If not, we need to read in a buffer full and come back for more * later, saving the file offset to continue from and the amount left to read * in the track structure. */ /* Save the file offset of the beginning of this track */ ts->start_ofs = MID2STREAM_seek(0, SEEK_CUR); if (ts->length > TRACK_BUFFER_SIZE) bytes_wanted = TRACK_BUFFER_SIZE; else bytes_wanted = ts->length; MID2STREAM_readfile(ts->start_ptr, bytes_wanted, &bytes_read); if (bytes_read != bytes_wanted) goto Init_Cleanup; /* Save the number of bytes that didn't make it into the buffer */ ts->left_on_disk = ts->length - bytes_read; ts->left_in_buffer = bytes_read; /* Save the current file offset so we can seek to it later */ ts->nextread_ofs = MID2STREAM_seek(0, SEEK_CUR); /* Setup pointer to the current position in the track */ ts->current_ptr = ts->start_ptr; ts->status = 0; ts->running_status = 0; ts->next_event_time = 0; /* Handle bozo MIDI files which contain empty track chunks */ if (!ts->left_in_buffer && !ts->left_on_disk) { ts->status |= ITS_F_ENDOFTRK; continue; } /* always preread the time from each track so the mixer code can * determine which track has the next event with minimum work. */ if (GetTrackVDWord(ts, &ts->next_event_time)) goto Init_Cleanup; /* Step over any unread data, advancing to the beginning of the next * track's data */ MID2STREAM_seek(ts->start_ofs + ts->length, SEEK_SET); } /* End of track initialization code */ err = 0; Init_Cleanup: if (err) { Con_Printf("MIDI: %s\n", err_bad_midi_file); ConverterCleanup(); } return err; }
// ------------- // GetTrackEvent // // Fills in the event struct with the next event from the track // // pMe->tkEvent will contain the absolute tick time of the event // pMe->abEvent[0] will contain // MIDI_META if the event is a meta event; // in this case pMe->abEvent[1] will contain the meta class // MIDI_SYSEX or MIDI_SYSEXEND if the event is a SysEx event // Otherwise, the event is a channel message and pMe->abEvent[1] // and pMe->abEvent[2] will contain the rest of the event. // // pMe->dwEventLength will contain // The total length of the channel message in pMe->abEvent if // the event is a channel message // The total length of the paramter data pointed to by // pMe->pEvent otherwise // // pMe->pEvent will point at any additional paramters if the // event is a SysEx or meta event with non-zero length; else // it will contain NULL // // Returns TRUE on success or FALSE on any kind of parse error // Prints its own error message ONLY in the debug version // // Maintains the state of the input track (i.e. pInTrack->iBytesLeft, // pInTrack->pTrackPointers, and pInTrack->bRunningStatus). // ------------- static BOOL GetTrackEvent(LPINTRACKSTATE pInTrack, LPTEMPEVENT pMe) { BYTE b; UINT32 dwEventLength; // Clear out the temporary event structure to get rid of old data... ZeroMemory(pMe, sizeof (TEMPEVENT)); // Already at end of track? There's nothing to read. // if ((pInTrack->fdwTrack & ITS_F_ENDOFTRK) || !pInTrack->iBytesLeft) return FALSE; // Get the first BYTE, which determines the type of event. // b = *pInTrack->pTrackPointer++; --pInTrack->iBytesLeft; // If the high bit is not set, then this is a channel message // which uses the status BYTE from the last channel message // we saw. NOTE: We do not clear running status across SysEx or // meta events even though the spec says to because there are // actually files out there which contain that sequence of data. // if (!(b & 0x80)) { // No previous status BYTE? We're hosed. // if (!pInTrack->bRunningStatus) { TRACKERR(pInTrack, gteBadRunStat); return FALSE; } //faB: the last midi command issued on that track pMe->abEvent[0] = pInTrack->bRunningStatus; pMe->abEvent[1] = b; // the data ! b = (BYTE)(pMe->abEvent[0] & 0xF0); pMe->dwEventLength = 2; //2 data bytes // Only program change and channel pressure events are 2 BYTEs long; // the rest are 3 and need another BYTE // if ((b != MIDI_PRGMCHANGE) && (b != MIDI_CHANPRESS)) { if (!pInTrack->iBytesLeft) { TRACKERR(pInTrack, gteRunStatMsgTrunc); pInTrack->fdwTrack |= ITS_F_ENDOFTRK; return FALSE; } pMe->abEvent[2] = *pInTrack->pTrackPointer++; --pInTrack->iBytesLeft; ++pMe->dwEventLength; } } else if ((b & 0xF0) != MIDI_SYSEX) { // Not running status, not in SysEx range - must be // normal channel message (0x80-0xEF) pMe->abEvent[0] = b; pInTrack->bRunningStatus = b; // Strip off channel and just keep message type // b &= 0xF0; dwEventLength = (b == MIDI_PRGMCHANGE || b == MIDI_CHANPRESS) ? 1 : 2; pMe->dwEventLength = dwEventLength + 1; if (pInTrack->iBytesLeft < dwEventLength) { TRACKERR(pInTrack, gteChanMsgTrunc); pInTrack->fdwTrack |= ITS_F_ENDOFTRK; return FALSE; } pMe->abEvent[1] = *pInTrack->pTrackPointer++; if (dwEventLength == 2) pMe->abEvent[2] = *pInTrack->pTrackPointer++; pInTrack->iBytesLeft -= dwEventLength; } else if (b == MIDI_SYSEX || b == MIDI_SYSEXEND) { // One of the SysEx types. (They are the same as far as we're concerned; // there is only a semantic difference in how the data would actually // get sent when the file is played. We must take care to put the correct // event type back on the output track, however.) // // Parse the general format of: // BYTE bEvent (MIDI_SYSEX or MIDI_SYSEXEND) // VLONG cbParms // BYTE abParms[cbParms] // pMe->abEvent[0] = b; if (!GetTrackVDWord(pInTrack, &pMe->dwEventLength)) { TRACKERR(pInTrack, gteSysExLenTrunc); return FALSE; } if (pInTrack->iBytesLeft < pMe->dwEventLength) { TRACKERR(pInTrack, gteSysExTrunc); pInTrack->fdwTrack |= ITS_F_ENDOFTRK; return FALSE; } pMe->pEvent = pInTrack->pTrackPointer; pInTrack->pTrackPointer += pMe->dwEventLength; pInTrack->iBytesLeft -= pMe->dwEventLength; } else if (b == MIDI_META) { // It's a meta event. Parse the general form: // BYTE bEvent (MIDI_META) // BYTE bClass // VLONG cbParms // BYTE abParms[cbParms] // pMe->abEvent[0] = b; if (!pInTrack->iBytesLeft) { TRACKERR(pInTrack, gteMetaNoClass); pInTrack->fdwTrack |= ITS_F_ENDOFTRK; return FALSE; } pMe->abEvent[1] = *pInTrack->pTrackPointer++; --pInTrack->iBytesLeft; if (!GetTrackVDWord(pInTrack, &pMe->dwEventLength)) { TRACKERR(pInTrack, gteMetaLenTrunc); return FALSE; } // NOTE: Perfectly valid to have a meta with no data // In this case, dwEventLength == 0 and pEvent == NULL // if (pMe->dwEventLength) { if (pInTrack->iBytesLeft < pMe->dwEventLength) { TRACKERR(pInTrack, gteMetaTrunc); pInTrack->fdwTrack |= ITS_F_ENDOFTRK; return FALSE; } pMe->pEvent = pInTrack->pTrackPointer; pInTrack->pTrackPointer += pMe->dwEventLength; pInTrack->iBytesLeft -= pMe->dwEventLength; } if (pMe->abEvent[1] == MIDI_META_EOT) pInTrack->fdwTrack |= ITS_F_ENDOFTRK; } else { // Messages in this range are system messages and aren't supposed to // be in a normal MIDI file. If they are, we've misparsed or the // authoring software is stpuid. #ifdef DEBUGMIDISTREAM I_OutputMsg("System message not supposed to be in MIDI file..\n"); #endif return FALSE; } // Event time was already stored as the current track time // pMe->tkEvent = pInTrack->tkNextEventDue; // Now update to the next event time. The code above MUST properly // maintain the end of track flag in case the end of track meta is // missing. if (!(pInTrack->fdwTrack & ITS_F_ENDOFTRK)) { DWORD tkDelta; if (!GetTrackVDWord(pInTrack, &tkDelta)) return FALSE; pInTrack->tkNextEventDue += tkDelta; } return TRUE; }
// ---- // Init (stand-alone version) // // Open the input and output files // Allocate and read the entire input file into memory // Validate the input file structure // Allocate the input track structures and initialize them // Initialize the output track structures // // Return TRUE on success // Prints its own error message if something goes wrong // // ---- static BOOL Init(LPSTR szInFile, LPSTR szOutFile) { BOOL fRet = FALSE; LONG cbRead; UINT32 *pChunkID; UINT32 *pChunkSize; LONG iChunkSize; LPMIDIFILEHDR pHeader; LPINTRACKSTATE pInTrack; UINT iTrack; // Initialize things we'll try to free later if we fail // ifs.FileSize = 0; ifs.pFile = NULL; //ifs.pTracks = NULL; // Attempt to open the input and output files // hInFile = CreateFileA(szInFile, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); if (INVALID_HANDLE_VALUE == hInFile) { I_OutputMsg("Could not open \"%s\" for read.\n", szInFile); goto Init_Cleanup; } hOutFile = CreateFileA(szOutFile, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL); if (INVALID_HANDLE_VALUE == hOutFile) { I_OutputMsg("Could not open \"%s\" for write.\n", szOutFile); goto Init_Cleanup; } // Figure out how big the input file is and allocate a chunk of memory big enough // to hold the whole thing. Read the whole file in at once. // if (INVALID_FILE_SIZE == (ifs.FileSize = GetFileSize(hInFile, NULL))) { I_OutputMsg("File system error on input file.\n"); goto Init_Cleanup; } if (NULL == (ifs.pFile = GlobalAllocPtr(GPTR, ifs.FileSize))) { I_OutputMsg("Out of memory.\n"); goto Init_Cleanup; } if ((!ReadFile(hInFile, ifs.pFile, ifs.FileSize, &cbRead, NULL)) || cbRead != ifs.FileSize) { I_OutputMsg("Read error on input file.\n"); goto Init_Cleanup; } // Set up to read from the memory buffer. Read and validate // - MThd header // - size of file header chunk // - file header itself // ifs.iBytesLeft = ifs.FileSize; ifs.pFilePointer = ifs.pFile; // note: midi header size should always be 6 if ((pChunkID = (UINT32*)GetInFileData(sizeof (*pChunkID))) == NULL || *pChunkID != MThd || (pChunkSize = (UINT32*)GetInFileData(sizeof (*pChunkSize))) == NULL || (iChunkSize = LONGSWAP(*pChunkSize)) < sizeof (MIDIFILEHDR) || (pHeader = (LPMIDIFILEHDR)GetInFileData(iChunkSize)) == NULL) { I_OutputMsg("Read error on MIDI header.\n"); goto Init_Cleanup; } // File header is stored in hi-lo order. Swap this into Intel order and save // parameters in our native int size (32 bits) // ifs.dwFormat = (LONG)WORDSWAP(pHeader->wFormat); ifs.nTracks = (LONG)WORDSWAP(pHeader->nTracks); ifs.dwTimeDivision = (LONG)WORDSWAP(pHeader->wTimeDivision); #ifdef DEBUGMIDISTREAM I_OutputMsg("MIDI Header:\n" "------------\n" "format: %d\n" "number of tracks: %d\n" "time division: %d\n", ifs.dwFormat, ifs.nTracks, ifs.dwTimeDivision); #endif // We know how many tracks there are; allocate the structures for them and parse // them. The parse merely looks at the MTrk signature and track chunk length // in order to skip to the next track header. // faB: now static // ifs.pTracks = (INTRACKSTATE *)GlobalAllocPtr(GPTR, ifs.nTracks*sizeof (INTRACKSTATE)); // if (ifs.pTracks == NULL) // { // I_OutputMsg("Out of memory.\n"); // goto Init_Cleanup; // } // faB: made it static, but don't quit if there are more tracks, just skip'em // (this isn't really a limit, since 32 tracks are really the maximum for MIDI files) if (ifs.nTracks > MAX_MIDI_IN_TRACKS) ifs.nTracks = MAX_MIDI_IN_TRACKS; for (iTrack = 0, pInTrack = ifs.pTracks; iTrack < ifs.nTracks; ++iTrack, ++pInTrack) { if ((pChunkID = (UINT32*)GetInFileData(sizeof (*pChunkID))) == NULL || *pChunkID!= MTrk || (pChunkSize = (UINT32*)GetInFileData(sizeof (*pChunkSize))) == NULL) { I_OutputMsg("Read error on track header.\n"); goto Init_Cleanup; } iChunkSize = LONGSWAP(*pChunkSize); pInTrack->iTrackLen = iChunkSize; pInTrack->iBytesLeft = iChunkSize; pInTrack->pTrackData = GetInFileData(iChunkSize); if (pInTrack->pTrackData == NULL) { I_OutputMsg("Read error while reading track data.\n"); goto Init_Cleanup; } #ifdef DEBUGMIDISTREAM I_OutputMsg("Track %d : length %d bytes\n", iTrack, iChunkSize); pInTrack->nTrack = iTrack; #endif pInTrack->pTrackPointer = pInTrack->pTrackData; pInTrack->fdwTrack = 0; pInTrack->bRunningStatus = 0; // Handle bozo MIDI files which contain empty track chunks // if (!pInTrack->iBytesLeft) { pInTrack->fdwTrack |= ITS_F_ENDOFTRK; continue; } // We always preread the time from each track so the mixer code can // determine which track has the next event with a minimum of work // if (!GetTrackVDWord(pInTrack, &pInTrack->tkNextEventDue)) { I_OutputMsg("Read error while reading first delta time of track.\n"); goto Init_Cleanup; } } ots.tkTrack = 0; ots.pFirst = NULL; ots.pLast = NULL; fRet = TRUE; Init_Cleanup: if (!fRet) Cleanup(); return fRet; }
// ----------------------- // Mid2StreamConverterInit // // Validate the input file structure // Allocate the input track structures and initialize them (faB: now STATIC) // Initialize the output track structures // // Return TRUE on success // ----------------------- BOOL Mid2StreamConverterInit(LPBYTE pMidiData, size_t iMidiSize) { BOOL fRet = TRUE; UINT32 *pChunkID; UINT32 *pChunkSize; UINT32 iChunkSize; LPMIDIFILEHDR pHeader; LPINTRACKSTATE pInTrack; UINT iTrack; tkCurrentTime = 0; // Initialize things we'll try to free later if we fail ZeroMemory(&ifs, sizeof (INFILESTATE)); //ifs.pTracks = NULL; //faB: now static // Set up to read from the memory buffer. Read and validate // - MThd header // - size of file header chunk // - file header itself // ifs.FileSize = iMidiSize; ifs.pFile = pMidiData; ifs.iBytesLeft = ifs.FileSize; ifs.pFilePointer = ifs.pFile; #ifdef DEBUGMIDISTREAM I_OutputMsg("Midi file size: %d\n", iMidiSize); #endif // note: midi header size should always be 6 if ((pChunkID = (UINT32*)GetInFileData(sizeof (*pChunkID))) == NULL || *pChunkID != MThd || (pChunkSize = (UINT32*)GetInFileData(sizeof (*pChunkSize))) == NULL || (iChunkSize = LONGSWAP(*pChunkSize)) < sizeof (MIDIFILEHDR) || (pHeader = (LPMIDIFILEHDR)GetInFileData(iChunkSize)) == NULL) { I_OutputMsg("Read error on MIDI header.\n"); goto Init_Cleanup; } ifs.dwFormat = (LONG)WORDSWAP(pHeader->wFormat); ifs.nTracks = (LONG)WORDSWAP(pHeader->nTracks); ifs.dwTimeDivision = (LONG)WORDSWAP(pHeader->wTimeDivision); #ifdef DEBUGMIDISTREAM I_OutputMsg("MIDI Header:\n" "------------\n" "format: %d\n" "number of tracks: %d\n" "time division: %d\n", ifs.dwFormat, ifs.nTracks, ifs.dwTimeDivision); #endif /* faB: made static ifs.pTracks = (INTRACKSTATE *)GlobalAllocPtr(GPTR, ifs.nTracks*sizeof (INTRACKSTATE)); if (ifs.pTracks == NULL) { I_OutputMsg("Out of memory.\n"); goto Init_Cleanup; } */ // faB: made it static, but don't quit if there are more tracks, just skip'em if (ifs.nTracks > MAX_MIDI_IN_TRACKS) ifs.nTracks = MAX_MIDI_IN_TRACKS; for (iTrack = 0, pInTrack = ifs.pTracks; iTrack < ifs.nTracks; ++iTrack, ++pInTrack) { if ((pChunkID = (UINT32*)GetInFileData(sizeof (*pChunkID))) == NULL || *pChunkID!= MTrk || (pChunkSize = (UINT32*)GetInFileData(sizeof (*pChunkSize))) == NULL) { I_OutputMsg("Read error on track header.\n"); goto Init_Cleanup; } iChunkSize = LONGSWAP(*pChunkSize); pInTrack->iTrackLen = iChunkSize; // Total track length pInTrack->iBytesLeft = iChunkSize; pInTrack->pTrackData = GetInFileData(iChunkSize); if (pInTrack->pTrackData == NULL) { I_OutputMsg("Read error while reading track data.\n"); goto Init_Cleanup; } #ifdef DEBUGMIDISTREAM I_OutputMsg("Track %d : length %d bytes\n", iTrack, iChunkSize); pInTrack->nTrack = iTrack; #endif // Setup pointer to the current position in the track pInTrack->pTrackPointer = pInTrack->pTrackData; pInTrack->fdwTrack = 0; pInTrack->bRunningStatus = BAD_MIDI_FIX; pInTrack->tkNextEventDue = 0; // Handle bozo MIDI files which contain empty track chunks if (!pInTrack->iBytesLeft) { pInTrack->fdwTrack |= ITS_F_ENDOFTRK; continue; } // We always preread the time from each track so the mixer code can // determine which track has the next event with a minimum of work if (!GetTrackVDWord(pInTrack, &pInTrack->tkNextEventDue)) { I_OutputMsg("Read error while reading first delta time of track.\n"); goto Init_Cleanup; } } // End of track initialization code fRet = FALSE; Init_Cleanup: if (fRet) Mid2StreamConverterCleanup(); return fRet; }