hexen2/H2MP/code/mstrconv.c
2000-11-10 00:00:00 +00:00

1194 lines
39 KiB
C

/*
* $Header: /H3/game/mstrconv.c 1 2/27/97 4:04p Rjohnson $
*/
#include <windows.h>
#include <windowsx.h>
#include <mmsystem.h>
#include <assert.h>
#include <stdio.h>
#include "midi.h"
#include "midstuff.h"
// Global stuff which is defined in the main module
//
BOOL bInsertTempo = FALSE;
// A few global variables used by this module only
//
//static HANDLE hInFile = INVALID_HANDLE_VALUE;
INFILESTATE ifs;
static DWORD tkCurrentTime;
byte *MidiData;
int MidiOffset,MidiSize;
// Tracks how many malloc blocks exist. If there are any and we decide to shut
// down, we must scan for them and free them. Malloc blocks are only created as
// temporary storgae blocks for extra parameter data associated with MIDI_SYSEX,
// MIDI_SYSEXEND, and MIDI_META events.
static DWORD dwMallocBlocks = 0;
extern DWORD dwBufferTickLength, dwTempoMultiplier, dwCurrentTempo;
extern DWORD dwProgressBytes, dwVolumePercent;
extern BOOL bLooped;
// Messages
//
static char szInitErrMem[] = "Out of memory.\n";
static char szInitErrInFile[] = "Read error on input file or file is corrupt.\n";
static char szNoTrackBuffMem[] = "Insufficient memory for track buffer allocation\n";
#ifdef DEBUG
static char gteBadRunStat[] = "Reference to missing running status.";
static char gteRunStatMsgTrunc[]= "Running status message truncated";
static char gteChanMsgTrunc[] = "Channel message truncated";
static char gteSysExLenTrunc[] = "SysEx event truncated (length)";
static char gteSysExTrunc[] = "SysEx event truncated";
static char gteMetaNoClass[] = "Meta event truncated (no class byte)";
static char gteMetaLenTrunc[] = "Meta event truncated (length)";
static char gteMetaTrunc[] = "Meta event truncated";
static char gteNoMem[] = "Out of memory during malloc call";
#endif
// Prototypes
//
static int AddEventToStreamBuffer( PTEMPEVENT pteTemp, LPCONVERTINFO );
static BOOL GetInFileData( LPVOID lpDest, DWORD cbToGet );
static BOOL GetTrackByte( PINTRACKSTATE ptsTrack, LPBYTE lpbyByte );
static BOOL GetTrackEvent( PINTRACKSTATE ptsTrack, PTEMPEVENT pteTemp );
static BOOL GetTrackVDWord( PINTRACKSTATE ptsTrack, LPDWORD lpdw );
static BOOL RefillTrackBuffer( PINTRACKSTATE ptsTrack );
static BOOL RewindConverter( void );
#ifdef DEBUG
static void ShowTrackError( PINTRACKSTATE ptsTrack, char* szErr );
#endif
int SetFilePointer2(LONG lDistanceToMove, PLONG lpDistanceToMoveHigh, DWORD dwMoveMethod)
{
int SaveMidi;
SaveMidi = MidiOffset;
if (dwMoveMethod == FILE_BEGIN)
{
MidiOffset = lDistanceToMove;
}
else MidiOffset += lDistanceToMove;
if (MidiOffset >= MidiSize)
{
MidiOffset = SaveMidi;
return -1;
}
return MidiOffset;
}
BOOL ReadFile2(LPVOID lpBuffer, DWORD nNumberOfBytesToRead, LPDWORD lpNumberOfBytesRead, LPOVERLAPPED lpOverlapped)
{
if (MidiOffset+nNumberOfBytesToRead > MidiSize)
{
// Con_Printf("Bad Read (%d+%d>=%d)\n",MidiOffset,nNumberOfBytesToRead,MidiSize);
return FALSE;
}
memcpy(lpBuffer,MidiData+MidiOffset,nNumberOfBytesToRead);
MidiOffset += nNumberOfBytesToRead;
*lpNumberOfBytesRead = nNumberOfBytesToRead;
return TRUE;
}
// 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 );
}
//
// GetInFileData
//
// Gets the requested number of bytes of data from the input file and returns
// a pointer to them.
//
// Returns a pointer to the data or NULL if we'd read more than is
// there.
//
static BOOL GetInFileData( LPVOID lpDest, DWORD cbToGet )
{
DWORD cbRead;
/* if( !ReadFile( hInFile, lpDest, cbToGet, &cbRead, NULL )
|| ( cbRead != cbToGet ))
{
return( TRUE );
}*/
if( !ReadFile2( lpDest, cbToGet, &cbRead, NULL )
|| ( cbRead != cbToGet ))
{
return( TRUE );
}
return( FALSE );
}
//
// ConverterCleanup
//
// Free anything we ever allocated
//
void ConverterCleanup( void )
{
DWORD idx;
/* if( hInFile != INVALID_HANDLE_VALUE )
{
CloseHandle( hInFile );
hInFile = INVALID_HANDLE_VALUE;
}*/
if( ifs.pitsTracks )
{
// De-allocate all our track buffers
for( idx = 0; idx < ifs.dwTrackCount; idx++ )
if( ifs.pitsTracks[idx].pTrackStart )
GlobalFreePtr( ifs.pitsTracks[idx].pTrackStart );
GlobalFreePtr( ifs.pitsTracks );
ifs.pitsTracks = NULL;
}
}
/*****************************************************************************/
/* RewindConverter() */
/* */
/* 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 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 );
}
/*****************************************************************************/
/* ConvertToBuffer() */
/* */
/* This function converts MIDI data from the track buffers setup by a */
/* previous call to ConverterInit(). It will convert data until an error is */
/* encountered or the output buffer has been filled with as much event data */
/* as possible, not to exceed dwMaxLength. This function can take a couple */
/* bit flags, passed through dwFlags. Information about the success/failure */
/* of this operation and the number of output bytes actually converted will */
/* be returned in the CONVERTINFO structure pointed at by lpciInfo. */
/* */
/*****************************************************************************/
int ConvertToBuffer( DWORD dwFlags, LPCONVERTINFO lpciInfo )
{
static INTRACKSTATE *ptsTrack, *ptsFound;
static DWORD dwStatus;
static DWORD tkNext;
static TEMPEVENT teTemp;
int nChkErr;
DWORD idx;
lpciInfo->dwBytesRecorded = 0;
if( dwFlags & CONVERTF_RESET )
{
dwProgressBytes = 0;
dwStatus = 0;
memset( &teTemp, 0, sizeof(TEMPEVENT));
ptsTrack = ptsFound = NULL;
}
// If we were already done, then return with a warning...
if( dwStatus & CONVERTF_STATUS_DONE )
{
if( bLooped )
{
RewindConverter();
dwProgressBytes = 0;
dwStatus = 0;
}
else
{
return( CONVERTERR_DONE );
}
}
// The caller is asking us to continue, but we're already hosed because we
// previously identified something as corrupt, so complain louder this time.
else if( dwStatus & CONVERTF_STATUS_STUCK )
{
return( CONVERTERR_STUCK );
}
else if( dwStatus & CONVERTF_STATUS_GOTEVENT )
{
// Turn off this bit flag
dwStatus ^= CONVERTF_STATUS_GOTEVENT;
/*
* The following code for this case is duplicated from below, and is
* designed to handle a "straggler" event, should we have one left over
* from previous processing the last time this function was called.
*/
// Don't add end of track event 'til we're done
//
if( teTemp.byShortData[0] == MIDI_META
&& teTemp.byShortData[1] == MIDI_META_EOT )
{
if( dwMallocBlocks )
{
free( teTemp.pLongData );
dwMallocBlocks--;
}
}
else if(( nChkErr = AddEventToStreamBuffer( &teTemp, lpciInfo ))
!= CONVERTERR_NOERROR )
{
if( nChkErr == CONVERTERR_BUFFERFULL )
{
// Do some processing and tell caller that this buffer's full
dwStatus |= CONVERTF_STATUS_GOTEVENT;
return( CONVERTERR_NOERROR );
}
else if( nChkErr == CONVERTERR_METASKIP )
{
// We skip by all meta events that aren't tempo changes...
}
else
{
DebugPrint( "Unable to add event to stream buffer." );
if( dwMallocBlocks )
{
free( teTemp.pLongData );
dwMallocBlocks--;
}
return( TRUE );
}
}
}
for( ; ; )
{
ptsFound = NULL;
tkNext = 0xFFFFFFFFL;
// Find nearest event due
//
for( idx = 0, ptsTrack = ifs.pitsTracks; idx < ifs.dwTrackCount;
++idx, ++ptsTrack )
{
if(( !( ptsTrack->fdwTrack & ITS_F_ENDOFTRK ))
&& ( ptsTrack->tkNextEventDue < tkNext ))
{
tkNext = ptsTrack->tkNextEventDue;
ptsFound = ptsTrack;
}
}
// None found? We must be done, so return to the caller with a smile.
//
if( !ptsFound )
{
dwStatus |= CONVERTF_STATUS_DONE;
// Need to set return buffer members properly
return( CONVERTERR_NOERROR );
}
// Ok, get the event header from that track
//
if( GetTrackEvent( ptsFound, &teTemp ))
{
// Warn future calls that this converter is stuck at a corrupt spot
// and can't continue
dwStatus |= CONVERTF_STATUS_STUCK;
return( CONVERTERR_CORRUPT );
}
// Don't add end of track event 'til we're done
//
if( teTemp.byShortData[0] == MIDI_META
&& teTemp.byShortData[1] == MIDI_META_EOT )
{
if( dwMallocBlocks )
{
free( teTemp.pLongData );
dwMallocBlocks--;
}
continue;
}
if(( nChkErr = AddEventToStreamBuffer( &teTemp, lpciInfo ))
!= CONVERTERR_NOERROR )
{
if( nChkErr == CONVERTERR_BUFFERFULL )
{
// Do some processing and tell somebody this buffer is full...
dwStatus |= CONVERTF_STATUS_GOTEVENT;
return( CONVERTERR_NOERROR );
}
else if( nChkErr == CONVERTERR_METASKIP )
{
// We skip by all meta events that aren't tempo changes...
}
else
{
DebugPrint( "Unable to add event to stream buffer." );
if( dwMallocBlocks )
{
free( teTemp.pLongData );
dwMallocBlocks--;
}
return( TRUE );
}
}
}
return( CONVERTERR_NOERROR );
}
//
// GetTrackVDWord
//
// Attempts to parse a variable length DWORD from the given track. A VDWord
// in a MIDI file
// (a) is in lo-hi format
// (b) has the high bit set on every byte except the last
//
// Returns the DWORD in *lpdw and TRUE on success; else
// FALSE if we hit end of track first. Sets ITS_F_ENDOFTRK
// if we hit end of track.
//
static BOOL GetTrackVDWord( PINTRACKSTATE ptsTrack, LPDWORD lpdw )
{
BYTE byByte;
DWORD dw = 0;
if( ptsTrack->fdwTrack & ITS_F_ENDOFTRK )
return( TRUE );
do
{
if( !ptsTrack->dwLeftInBuffer && !ptsTrack->dwLeftOnDisk )
{
ptsTrack->fdwTrack |= ITS_F_ENDOFTRK;
return( TRUE );
}
if( GetTrackByte( ptsTrack, &byByte ))
return( TRUE );
dw = ( dw << 7 ) | ( byByte & 0x7F );
} while( byByte & 0x80 );
*lpdw = dw;
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 );
}
//
// GetTrackByte
//
// Retrieve the next byte from the track buffer, refilling the buffer from
// disk if necessary.
//
static BOOL GetTrackByte( PINTRACKSTATE ptsTrack, LPBYTE lpbyByte )
{
if( !ptsTrack->dwLeftInBuffer )
{
if( RefillTrackBuffer( ptsTrack ))
return( TRUE );
}
*lpbyByte = *ptsTrack->pTrackCurrent++;
ptsTrack->dwLeftInBuffer--;
return( FALSE );
}
//
// RefillTrackBuffer()
//
// This function attempts to read in a buffer-full of data for a MIDI track.
//
BOOL RefillTrackBuffer( PINTRACKSTATE ptsTrack )
{
DWORD dwBytesRead, dwResult;
BOOL bResult;
if( ptsTrack->dwLeftOnDisk )
{
ptsTrack->pTrackCurrent = ptsTrack->pTrackStart;
// Seek to the proper place in the file, indicated by
// ptsTrack->foNextReadStart and read in the remaining data,
// up to a maximum of the buffer size.
/* if(( dwResult = SetFilePointer( hInFile,
(long)ptsTrack->foNextReadStart,
0L, FILE_BEGIN )) == 0xFFFFFFFF )
{
MessageBox( GetActiveWindow(),
"Unable to seek to track buffer location in RefillTrackBuffer()!!",
"TEST", MB_OK | MB_ICONEXCLAMATION );
return( TRUE );
}*/
if(( dwResult = SetFilePointer2(
(long)ptsTrack->foNextReadStart,
0L, FILE_BEGIN )) == 0xFFFFFFFF )
{
Con_Printf("MIDI: Unable to seek to track buffer location in RefillTrackBuffer()!!\n");
return( TRUE );
}
if( ptsTrack->dwLeftOnDisk > TRACK_BUFFER_SIZE )
ptsTrack->dwLeftInBuffer = TRACK_BUFFER_SIZE;
else
ptsTrack->dwLeftInBuffer = ptsTrack->dwLeftOnDisk;
/* bResult = ReadFile( hInFile, ptsTrack->pTrackStart,
ptsTrack->dwLeftInBuffer,
&dwBytesRead, NULL );*/
bResult = ReadFile2( ptsTrack->pTrackStart,
ptsTrack->dwLeftInBuffer,
&dwBytesRead, NULL );
ptsTrack->dwLeftOnDisk -= dwBytesRead;
ptsTrack->foNextReadStart = dwResult + dwBytesRead;
ptsTrack->dwLeftInBuffer = dwBytesRead;
if( !bResult || ( bResult && !dwBytesRead )
|| ( bResult && dwBytesRead != ptsTrack->dwLeftInBuffer ))
{
Con_Printf("MIDI: Read operation failed prematurely!!\n");
ptsTrack->dwLeftInBuffer = dwBytesRead;
return( TRUE );
}
else
return( FALSE );
}
return( TRUE );
}
//
// AddEventToStreamBuffer
//
// Put the given event into the given stream buffer at the given location
// pteTemp must point to an event filled out in accordance with the
// description given in GetTrackEvent
//
// Returns FALSE on sucess or TRUE on an error condition
// Handles its own error notification by displaying to the appropriate
// output device (either our debugging window, or the screen).
//
static int AddEventToStreamBuffer( PTEMPEVENT pteTemp, CONVERTINFO *lpciInfo )
{
DWORD tkNow, tkDelta;
MIDIEVENT *pmeEvent;
char szTemp[256];
pmeEvent = (MIDIEVENT *)( lpciInfo->mhBuffer.lpData
+ lpciInfo->dwStartOffset
+ lpciInfo->dwBytesRecorded );
// When we see a new, empty buffer, set the start time on it...
if( !lpciInfo->dwBytesRecorded )
lpciInfo->tkStart = tkCurrentTime;
// Use the above set start time to figure out how much longer we should fill
// this buffer before officially declaring it as "full"
if( tkCurrentTime - lpciInfo->tkStart > dwBufferTickLength )
if( lpciInfo->bTimesUp )
{
lpciInfo->bTimesUp = FALSE;
return( CONVERTERR_BUFFERFULL );
}
else
lpciInfo->bTimesUp = TRUE;
tkNow = tkCurrentTime;
// Delta time is absolute event time minus absolute time
// already gone by on this track
tkDelta = pteTemp->tkEvent - tkCurrentTime;
// Event time is now current time on this track
tkCurrentTime = pteTemp->tkEvent;
if( bInsertTempo )
{
bInsertTempo = FALSE;
if( lpciInfo->dwMaxLength-lpciInfo->dwBytesRecorded < 3*sizeof(DWORD))
{
// Cleanup from our write operation
return( CONVERTERR_BUFFERFULL );
}
if( dwCurrentTempo )
{
pmeEvent->dwDeltaTime = 0;
pmeEvent->dwStreamID = 0;
pmeEvent->dwEvent = ( dwCurrentTempo * 100 ) / dwTempoMultiplier;
pmeEvent->dwEvent |= (((DWORD)MEVT_TEMPO ) << 24 ) | MEVT_F_SHORT;
lpciInfo->dwBytesRecorded += 3 * sizeof(DWORD);
pmeEvent += 3 * sizeof(DWORD);
}
}
if( pteTemp->byShortData[0] < MIDI_SYSEX )
{
// Channel message. We know how long it is, just copy it.
// Need 3 DWORD's: delta-t, stream-ID, event
if( lpciInfo->dwMaxLength-lpciInfo->dwBytesRecorded < 3*sizeof(DWORD))
{
// Cleanup from our write operation
return( CONVERTERR_BUFFERFULL );
}
pmeEvent->dwDeltaTime = tkDelta;
pmeEvent->dwStreamID = 0;
pmeEvent->dwEvent = ( pteTemp->byShortData[0] )
| (((DWORD)pteTemp->byShortData[1] ) << 8 )
| (((DWORD)pteTemp->byShortData[2] ) << 16 )
| MEVT_F_SHORT;
if((( pteTemp->byShortData[0] & 0xF0) == MIDI_CTRLCHANGE )
&& ( pteTemp->byShortData[1] == MIDICTRL_VOLUME ))
{
// If this is a volume change, generate a callback so we can grab
// the new volume for our cache
pmeEvent->dwEvent |= MEVT_F_CALLBACK;
}
lpciInfo->dwBytesRecorded += 3 *sizeof(DWORD);
}
else if(( pteTemp->byShortData[0] == MIDI_SYSEX )
|| ( pteTemp->byShortData[0] == MIDI_SYSEXEND ))
{
DebugPrint( "AddEventToStreamBuffer: Ignoring SysEx event." );
if( dwMallocBlocks )
{
free( pteTemp->pLongData );
dwMallocBlocks--;
}
}
else
{
// Better be a meta event.
// BYTE byEvent
// BYTE byEventType
// VDWORD dwEventLength
// BYTE pLongEventData[dwEventLength]
//
assert( pteTemp->byShortData[0] == MIDI_META );
// The only meta-event we care about is change tempo
//
if( pteTemp->byShortData[1] != MIDI_META_TEMPO )
{
if( dwMallocBlocks )
{
free( pteTemp->pLongData );
dwMallocBlocks--;
}
return( CONVERTERR_METASKIP );
}
// We should have three bytes of parameter data...
assert( pteTemp->dwEventLength == 3 );
// Need 3 DWORD's: delta-t, stream-ID, event data
if( lpciInfo->dwMaxLength - lpciInfo->dwBytesRecorded < 3 *sizeof(DWORD))
{
// Cleanup the temporary event if necessary and return
if( dwMallocBlocks )
{
free( pteTemp->pLongData );
dwMallocBlocks--;
}
return( CONVERTERR_BUFFERFULL );
}
pmeEvent->dwDeltaTime = tkDelta;
pmeEvent->dwStreamID = 0;
// Note: this is backwards from above because we're converting a single
// data value from hi-lo to lo-hi format...
pmeEvent->dwEvent = ( pteTemp->pLongData[2] )
| (((DWORD)pteTemp->pLongData[1] ) << 8 )
| (((DWORD)pteTemp->pLongData[0] ) << 16 );
/* This next step has absolutely nothing to do with the conversion of a
* MIDI file to a stream, it's simply put here to add the functionality
* of the tempo slider. If you don't need this, be sure to remove the
* next two lines.
*/
dwCurrentTempo = pmeEvent->dwEvent;
pmeEvent->dwEvent = (pmeEvent->dwEvent * 100 ) / dwTempoMultiplier;
pmeEvent->dwEvent |= (((DWORD)MEVT_TEMPO ) << 24 ) | MEVT_F_SHORT;
dwBufferTickLength = ( ifs.dwTimeDivision * 1000 * BUFFER_TIME_LENGTH ) / dwCurrentTempo;
wsprintf( szTemp, "dwBufferTickLength = %lu", dwBufferTickLength );
DebugPrint( szTemp );
if( dwMallocBlocks )
{
free( pteTemp->pLongData );
dwMallocBlocks--;
}
lpciInfo->dwBytesRecorded += 3 *sizeof(DWORD);
}
return( FALSE );
}
#ifdef DEBUG
static void ShowTrackError( PINTRACKSTATE ptsTrack, LPSTR lpszErr )
{
wsprintf( szTemp, "Track buffer offset %lu",
(DWORD)(ptsTrack->pTrackCurrent - ptsTrack->pTrackStart));
DebugPrint( szTemp );
wsprintf( szTemp, "Track total %lu Track left %lu",
ptsTrack->dwTrackLength, ptsTrack->dwLeftInBuffer );
DebugPrint( szTemp );
}
#endif
/*
* $Log: /H3/game/mstrconv.c $
*
* 1 2/27/97 4:04p Rjohnson
* Initial Revision
*/