www.pudn.com > tetris_new.rar > MIDI.CPP


///////////////////////////////////////////////////////////////////////////// 
// Copyright (C) 1998 by Jörg König 
// All rights reserved 
// 
// This file is part of the completely free tetris clone "CGTetris". 
// 
// This is free software. 
// You may redistribute it by any means providing it is not sold for profit 
// without the authors written consent. 
// 
// No warrantee of any kind, expressed or implied, is included with this 
// software; use at your own risk, responsibility for damages (if any) to 
// anyone resulting from the use of this software rests entirely with the 
// user. 
// 
// Send bug reports, bug fixes, enhancements, requests, flames, etc., and 
// I'll try to keep a version up to date.  I can be reached as follows: 
//    J.Koenig@adg.de                 (company site) 
//    Joerg.Koenig@rhein-neckar.de    (private site) 
///////////////////////////////////////////////////////////////////////////// 
 
 
// Midi.cpp 
// 
 
 
// The CMIDI class is based on a sample in the DirectX SDK (mstream) 
 
#include "stdafx.h" 
#include "Midi.h" 
 
#ifdef _DEBUG 
#define new DEBUG_NEW 
#undef THIS_FILE 
static char THIS_FILE[] = __FILE__; 
#endif 
 
 
#define MThd		0x6468544D		// Start of file 
#define MTrk		0x6B72544D		// Start of track 
 
 
#define BUFFER_TIME_LENGTH		60   // Amount to fill in milliseconds 
 
 
// These structures are stored in MIDI files; they need to be byte aligned. 
// 
#pragma pack(1) 
 
// Contents of MThd chunk. 
struct MIDIFILEHDR 
{ 
    WORD	wFormat;			// Format (hi-lo) 
    WORD	wTrackCount;		// # tracks (hi-lo) 
    WORD	wTimeDivision;		// Time division (hi-lo) 
}; 
 
#pragma pack() // End of need for byte-aligned structures 
 
 
// Macros for swapping hi/lo-endian data 
// 
#define WORDSWAP(w)		(((w) >> 8) | \ 
						(((w) << 8) & 0xFF00)) 
 
#define DWORDSWAP(dw)	(((dw) >> 24) | \ 
						(((dw) >> 8) & 0x0000FF00) | \ 
						(((dw) << 8) & 0x00FF0000) | \ 
						(((dw) << 24) & 0xFF000000)) 
 
 
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"; 
 
 
////////////////////////////////////////////////////////////////////// 
// CMIDI -- Construction/Destruction 
////////////////////////////////////////////////////////////////////// 
 
CMIDI::CMIDI() 
	: m_dwSoundSize(0) 
	, m_pSoundData(0) 
	, m_dwFormat(0) 
	, m_dwTrackCount(0) 
	, m_dwTimeDivision(0) 
	, m_bPlaying(FALSE) 
	, m_hStream(0) 
	, m_dwProgressBytes(0) 
	, m_bLooped(FALSE) 
	, m_tkCurrentTime(0) 
	, m_dwBufferTickLength(0) 
	, m_dwCurrentTempo(0) 
	, m_dwTempoMultiplier(100) 
	, m_bInsertTempo(FALSE) 
	, m_bBuffersPrepared(FALSE) 
	, m_nCurrentBuffer(0) 
	, m_uMIDIDeviceID(MIDI_MAPPER) 
	, m_nEmptyBuffers(0) 
	, m_bPaused(FALSE) 
	, m_uCallbackStatus(0) 
	, m_hBufferReturnEvent(0) 
 
	, m_ptsTrack(0) 
	, m_ptsFound(0) 
	, m_dwStatus(0) 
	, m_tkNext(0) 
	, m_dwMallocBlocks(0) 
{ 
	m_hBufferReturnEvent = ::CreateEvent(0, FALSE, FALSE, TEXT("Wait For Buffer Return")); 
	ASSERT(m_hBufferReturnEvent != 0); 
} 
 
CMIDI::~CMIDI() 
{ 
	Stop(FALSE); 
 
	if(m_hBufferReturnEvent) 
		::CloseHandle(m_hBufferReturnEvent); 
} 
 
 
BOOL CMIDI::Create(UINT uResID, CWnd * pWndParent /* = NULL */) 
{ 
	return	Create(MAKEINTRESOURCE(uResID), pWndParent); 
} 
 
 
BOOL CMIDI::Create(LPCTSTR pszResID, CWnd * pWndParent /* = NULL */) 
{ 
	////////////////////////////////////////////////////////////////// 
	// load resource 
	HINSTANCE hApp = ::GetModuleHandle(0); 
	ASSERT(hApp); 
 
	HRSRC hResInfo = ::FindResource(hApp, pszResID, TEXT("MIDI")); 
	if(hResInfo == 0) 
		return FALSE; 
 
	HGLOBAL hRes = ::LoadResource(hApp, hResInfo); 
	if(hRes == 0) 
		return FALSE; 
 
	LPVOID pTheSound = ::LockResource(hRes); 
	if(pTheSound == 0) 
		return FALSE; 
 
	DWORD dwTheSound = ::SizeofResource(hApp, hResInfo); 
 
	return Create(pTheSound, dwTheSound, pWndParent); 
} 
 
 
BOOL CMIDI::Create(LPVOID pSoundData, DWORD dwSize, CWnd * pWndParent /* = NULL */) 
{ 
	if( m_pSoundData ) { 
		// already created 
		ASSERT(FALSE); 
		return FALSE; 
	} 
 
	ASSERT(pSoundData != 0); 
	ASSERT(dwSize > 0); 
 
	register LPBYTE p = LPBYTE(pSoundData); 
 
	// check header of MIDI 
	if(*(DWORD*)p != MThd) { 
		ASSERT(FALSE); 
		return FALSE; 
	} 
	p += sizeof(DWORD); 
 
	// check header size 
	DWORD dwHeaderSize = DWORDSWAP(*(DWORD*)p); 
	if( dwHeaderSize != sizeof(MIDIFILEHDR) ) { 
		ASSERT(FALSE); 
		return FALSE; 
	} 
	p += sizeof(DWORD); 
 
	// get header 
	MIDIFILEHDR hdr; 
	::CopyMemory(&hdr, p, dwHeaderSize); 
	m_dwFormat = DWORD(WORDSWAP(hdr.wFormat)); 
	m_dwTrackCount = DWORD(WORDSWAP(hdr.wTrackCount)); 
	m_dwTimeDivision = DWORD(WORDSWAP(hdr.wTimeDivision)); 
	p += dwHeaderSize; 
 
	// create the array of tracks 
	m_Tracks.resize(m_dwTrackCount); 
	for(register DWORD i = 0; i < m_dwTrackCount; ++i) { 
		// check header of track 
		if(*(DWORD*)p != MTrk) { 
			ASSERT(FALSE); 
			return FALSE; 
		} 
		p += sizeof(DWORD); 
 
		m_Tracks[i].dwTrackLength = DWORDSWAP(*(DWORD*)p); 
		p += sizeof(DWORD); 
 
		m_Tracks[i].pTrackStart = m_Tracks[i].pTrackCurrent = p; 
			p += m_Tracks[i].dwTrackLength; 
 
        // Handle bozo MIDI files which contain empty track chunks 
		if( !m_Tracks[i].dwTrackLength ) { 
			m_Tracks[i].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( &m_Tracks[i], &m_Tracks[i].tkNextEventDue )) { 
			TRACE0("Error in MIDI data\n"); 
			ASSERT(FALSE); 
			return FALSE; 
		} 
	} 
 
 
	m_pSoundData = pSoundData; 
	m_dwSoundSize = dwSize; 
	m_pWndParent = pWndParent; 
 
	// allocate volume channels and initialise them 
	m_Volumes.resize(NUM_CHANNELS, VOLUME_INIT); 
 
	if( ! StreamBufferSetup() ) { 
		ASSERT(FALSE); 
		return FALSE; 
	} 
 
	return TRUE; 
} 
 
 
BOOL CMIDI :: Play(BOOL bInfinite /* = FALSE */) { 
	if( IsPaused() ) { 
		Continue(); 
		return TRUE; 
	} 
 
	// calling Play() while it is already playing will restart from scratch 
	if( IsPlaying() ) 
		Stop(); 
 
	// Clear the status of our callback so it will handle 
	// MOM_DONE callbacks once more 
	m_uCallbackStatus = 0; 
 
	MMRESULT mmResult; 
	if( (mmResult = midiStreamRestart(m_hStream)) != MMSYSERR_NOERROR ) { 
		MidiError(mmResult); 
		return FALSE; 
	} 
 
	m_bPlaying = TRUE; 
	m_bLooped = bInfinite; 
 
	return m_bPlaying; 
} 
 
 
BOOL CMIDI :: Stop(BOOL bReOpen /*=TRUE*/) { 
	MMRESULT mmrRetVal; 
 
	if( IsPlaying()	|| (m_uCallbackStatus != STATUS_CALLBACKDEAD) ) { 
		m_bPlaying = m_bPaused = FALSE; 
		if( m_uCallbackStatus != STATUS_CALLBACKDEAD && m_uCallbackStatus != STATUS_WAITINGFOREND ) 
			m_uCallbackStatus = STATUS_KILLCALLBACK; 
 
		if( (mmrRetVal = midiStreamStop(m_hStream) ) != MMSYSERR_NOERROR ) { 
			MidiError(mmrRetVal); 
			return FALSE; 
		} 
		if( (mmrRetVal = midiOutReset((HMIDIOUT)m_hStream)) != MMSYSERR_NOERROR ) { 
			MidiError(mmrRetVal); 
			return FALSE; 
		} 
		// Wait for the callback thread to release this thread, which it will do by 
		// calling SetEvent() once all buffers are returned to it 
		if( WaitForSingleObject( m_hBufferReturnEvent, DEBUG_CALLBACK_TIMEOUT ) == WAIT_TIMEOUT ) { 
			// Note, this is a risky move because the callback may be genuinely busy, but 
			// when we're debugging, it's safer and faster than freezing the application, 
			// which leaves the MIDI device locked up and forces a system reset... 
			TRACE0("Timed out waiting for MIDI callback\n"); 
			m_uCallbackStatus = STATUS_CALLBACKDEAD; 
		} 
	} 
 
	if( m_uCallbackStatus == STATUS_CALLBACKDEAD ) { 
		m_uCallbackStatus = 0; 
		FreeBuffers(); 
		if( m_hStream ) { 
			if( (mmrRetVal = midiStreamClose(m_hStream) ) != MMSYSERR_NOERROR ) { 
				MidiError(mmrRetVal); 
			} 
			m_hStream = 0; 
		} 
 
		if( bReOpen ) { 
			if( !StreamBufferSetup() ) { 
				// Error setting up for MIDI file 
				// Notification is already taken care of... 
				return FALSE; 
			} 
		} 
	} 
	return TRUE; 
} 
 
 
BOOL CMIDI :: Pause() { 
	if( ! m_bPaused && m_bPlaying && m_pSoundData && m_hStream ) { 
		midiStreamPause(m_hStream); 
		m_bPaused = TRUE; 
	} 
	return FALSE; 
} 
 
 
BOOL CMIDI :: Continue() { 
	if( m_bPaused && m_bPlaying && m_pSoundData && m_hStream ) { 
		midiStreamRestart(m_hStream); 
		m_bPaused = FALSE; 
	} 
	return FALSE; 
} 
 
 
BOOL CMIDI :: Rewind() { 
	if( ! m_pSoundData ) 
		return FALSE; 
 
	for(register DWORD i = 0; i < m_dwTrackCount; ++i) { 
		m_Tracks[i].pTrackCurrent = m_Tracks[i].pTrackStart; 
		m_Tracks[i].byRunningStatus = 0; 
		m_Tracks[i].tkNextEventDue = 0; 
		m_Tracks[i].fdwTrack = 0; 
 
        // Handle bozo MIDI files which contain empty track chunks 
		if( !m_Tracks[i].dwTrackLength ) { 
			m_Tracks[i].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( &m_Tracks[i], &m_Tracks[i].tkNextEventDue )) { 
			TRACE0("Error in MIDI data\n"); 
			ASSERT(FALSE); 
			return FALSE; 
		} 
	} 
 
	return TRUE; 
} 
 
 
DWORD CMIDI :: GetChannelCount() const { 
	return m_Volumes.size(); 
} 
 
 
void CMIDI :: SetVolume(DWORD dwPercent) { 
	const DWORD dwSize = m_Volumes.size(); 
	for( register DWORD i = 0; i < dwSize; ++i ) 
		SetChannelVolume(i, dwPercent); 
} 
 
 
DWORD CMIDI :: GetVolume() const { 
	DWORD dwVolume = 0; 
	const DWORD dwSize = m_Volumes.size(); 
	for( register DWORD i = 0; i < dwSize; ++i ) 
		dwVolume += GetChannelVolume(i); 
 
	return dwVolume / GetChannelCount(); 
} 
 
 
void CMIDI :: SetChannelVolume(DWORD dwChannel, DWORD dwPercent) { 
	ASSERT(dwChannel < m_Volumes.size()); 
 
	if( !m_bPlaying ) 
		return; 
 
	m_Volumes[dwChannel] = (dwPercent > 100) ? 100 : dwPercent; 
	DWORD dwEvent = MIDI_CTRLCHANGE | dwChannel | ((DWORD)MIDICTRL_VOLUME << 8) | ((DWORD)(m_Volumes[dwChannel]*VOLUME_MAX/100) << 16); 
	MMRESULT mmrRetVal; 
	if(( mmrRetVal = midiOutShortMsg((HMIDIOUT)m_hStream, dwEvent)) != MMSYSERR_NOERROR ) { 
		MidiError(mmrRetVal); 
		return; 
	} 
} 
 
 
DWORD CMIDI :: GetChannelVolume(DWORD dwChannel) const { 
	ASSERT(dwChannel < GetChannelCount()); 
	return m_Volumes[dwChannel]; 
} 
 
////////////////////////////////////////////////////////////////////// 
// CMIDI -- implementation 
////////////////////////////////////////////////////////////////////// 
 
// 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 CMIDI :: ConvertToBuffer(DWORD dwFlags, CONVERTINFO * lpciInfo) { 
	int	    nChkErr; 
 
	lpciInfo->dwBytesRecorded = 0; 
 
	if( dwFlags & CONVERTF_RESET ) { 
		m_dwProgressBytes = 0; 
		m_dwStatus = 0; 
		memset( &m_teTemp, 0, sizeof(TEMPEVENT)); 
		m_ptsTrack = m_ptsFound = 0; 
	} 
 
	// If we were already done, then return with a warning... 
	if( m_dwStatus & CONVERTF_STATUS_DONE ) { 
		if( m_bLooped ) { 
			Rewind(); 
			m_dwProgressBytes = 0; 
			m_dwStatus = 0; 
		} else 
			return CONVERTERR_DONE; 
	} else if( m_dwStatus & CONVERTF_STATUS_STUCK ) { 
		// The caller is asking us to continue, but we're already hosed because we 
		// previously identified something as corrupt, so complain louder this time. 
		return( CONVERTERR_STUCK ); 
	} else if( m_dwStatus & CONVERTF_STATUS_GOTEVENT ) { 
		// Turn off this bit flag 
		m_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( m_teTemp.byShortData[0] == MIDI_META && m_teTemp.byShortData[1] == MIDI_META_EOT ) { 
			if( m_dwMallocBlocks ) { 
				delete [] m_teTemp.pLongData; 
				--m_dwMallocBlocks; 
			} 
		} else if(( nChkErr = AddEventToStreamBuffer( &m_teTemp, lpciInfo )) != CONVERTERR_NOERROR ) { 
			if( nChkErr == CONVERTERR_BUFFERFULL ) { 
				// Do some processing and tell caller that this buffer's full 
				m_dwStatus |= CONVERTF_STATUS_GOTEVENT; 
				return CONVERTERR_NOERROR; 
			} else if( nChkErr == CONVERTERR_METASKIP ) { 
				// We skip by all meta events that aren't tempo changes... 
			} else { 
				TRACE0("Unable to add event to stream buffer.\n"); 
				if( m_dwMallocBlocks ) { 
					delete [] m_teTemp.pLongData; 
					m_dwMallocBlocks--; 
				} 
				return( TRUE ); 
			} 
		} 
	} 
 
	for(;;) { 
		m_ptsFound = 0; 
		m_tkNext = 0xFFFFFFFFL; 
		// Find nearest event due 
		for( register DWORD idx = 0; idx < m_Tracks.size(); ++idx ) { 
			m_ptsTrack = &m_Tracks[idx]; 
			if( !(m_ptsTrack->fdwTrack & ITS_F_ENDOFTRK) && (m_ptsTrack->tkNextEventDue < m_tkNext) ) { 
				m_tkNext = m_ptsTrack->tkNextEventDue; 
				m_ptsFound = m_ptsTrack; 
			} 
		} 
 
		// None found?  We must be done, so return to the caller with a smile. 
		if( !m_ptsFound ) { 
			m_dwStatus |= CONVERTF_STATUS_DONE; 
			// Need to set return buffer members properly 
			return CONVERTERR_NOERROR; 
		} 
 
		// Ok, get the event header from that track 
		if( !GetTrackEvent( m_ptsFound, &m_teTemp )) { 
			// Warn future calls that this converter is stuck at a corrupt spot 
			// and can't continue 
			m_dwStatus |= CONVERTF_STATUS_STUCK; 
			return CONVERTERR_CORRUPT; 
		} 
 
		// Don't add end of track event 'til we're done 
		if( m_teTemp.byShortData[0] == MIDI_META && m_teTemp.byShortData[1] == MIDI_META_EOT ) { 
			if( m_dwMallocBlocks ) { 
				delete [] m_teTemp.pLongData; 
				--m_dwMallocBlocks; 
			} 
			continue; 
		} 
 
		if(( nChkErr = AddEventToStreamBuffer( &m_teTemp, lpciInfo )) != CONVERTERR_NOERROR ) { 
			if( nChkErr == CONVERTERR_BUFFERFULL ) { 
				// Do some processing and tell somebody this buffer is full... 
				m_dwStatus |= CONVERTF_STATUS_GOTEVENT; 
				return CONVERTERR_NOERROR; 
			} else if( nChkErr == CONVERTERR_METASKIP ) { 
				// We skip by all meta events that aren't tempo changes... 
			} else { 
				TRACE0("Unable to add event to stream buffer.\n"); 
				if( m_dwMallocBlocks ) { 
					delete [] m_teTemp.pLongData; 
					m_dwMallocBlocks--; 
				} 
				return TRUE; 
			} 
		} 
	}	 
 
	return CONVERTERR_NOERROR; 
} 
 
// 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 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.  
// ptsTrack->pTrackPointers, and ptsTrack->byRunningStatus). 
// 
BOOL CMIDI :: GetTrackEvent(TRACK * ptsTrack, TEMPEVENT * pteTemp) { 
	DWORD   idx; 
	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 ) 
		return FALSE; 
 
	// Get the first byte, which determines the type of event. 
	BYTE byByte; 
	if( !GetTrackByte(ptsTrack, &byByte) ) 
		return FALSE; 
 
	// 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 ) { 
			TrackError(ptsTrack, gteBadRunStat); 
			return FALSE; 
		} 
 
		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( !GetTrackByte( ptsTrack, &pteTemp->byShortData[2] )) 
				return FALSE; 
			++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( !GetTrackByte( ptsTrack, &pteTemp->byShortData[1] )) 
			return FALSE; 
		if( dwEventLength == 2 ) 
			if( !GetTrackByte( ptsTrack, &pteTemp->byShortData[2] )) 
				return FALSE; 
	} 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 )) { 
			TrackError( ptsTrack, gteSysExLenTrunc ); 
			return FALSE; 
		} 
 
		// Malloc a temporary memory block to hold the parameter data 
		pteTemp->pLongData = new BYTE [pteTemp->dwEventLength]; 
		if( pteTemp->pLongData == 0 ) { 
			TrackError( ptsTrack, gteNoMem ); 
			return FALSE; 
		} 
		// 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 
		++m_dwMallocBlocks; 
 
		// Copy from the input buffer to the parameter data buffer 
		for( idx = 0; idx < pteTemp->dwEventLength; idx++ ) 
			if( !GetTrackByte( ptsTrack, pteTemp->pLongData + idx )) { 
				TrackError( ptsTrack, gteSysExTrunc ); 
				return FALSE; 
			} 
	} 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( !GetTrackByte( ptsTrack, &pteTemp->byShortData[1] )) 
			return FALSE; 
 
		if( !GetTrackVDWord( ptsTrack, &pteTemp->dwEventLength )) {	 
			TrackError( ptsTrack, gteMetaLenTrunc ); 
			return FALSE; 
		} 
 
		// NOTE: It's perfectly valid to have a meta with no data 
		// In this case, dwEventLength == 0 and pLongData == NULL 
		if( pteTemp->dwEventLength ) {		 
			// Malloc a temporary memory block to hold the parameter data 
			pteTemp->pLongData = new BYTE [pteTemp->dwEventLength]; 
			if( pteTemp->pLongData == 0 ) { 
				TrackError( ptsTrack, gteNoMem ); 
				return FALSE; 
			} 
			// 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 
			++m_dwMallocBlocks; 
 
			// Copy from the input buffer to the parameter data buffer 
			for( idx = 0; idx < pteTemp->dwEventLength; idx++ ) 
				if( !GetTrackByte( ptsTrack, pteTemp->pLongData + idx )) { 
					TrackError( ptsTrack, gteMetaTrunc ); 
					return FALSE; 
				} 
		} 
 
		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 FALSE; 
	} 
 
	// 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 FALSE; 
 
		ptsTrack->tkNextEventDue += tkDelta; 
	} 
 
	return TRUE; 
} 
 
 
// 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. 
BOOL CMIDI :: GetTrackVDWord(TRACK * ptsTrack, LPDWORD lpdw) { 
	ASSERT(ptsTrack != 0); 
	ASSERT(lpdw != 0); 
 
	if( ptsTrack->fdwTrack & ITS_F_ENDOFTRK ) 
		return FALSE; 
 
	BYTE	byByte; 
	DWORD	dw = 0; 
 
	do { 
		if( !GetTrackByte( ptsTrack, &byByte )) 
			return FALSE; 
 
		dw = ( dw << 7 ) | ( byByte & 0x7F ); 
	} while( byByte & 0x80 ); 
 
	*lpdw = dw; 
 
	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 
// 
// Handles its own error notification by displaying to the appropriate 
// output device (either our debugging window, or the screen). 
int CMIDI :: AddEventToStreamBuffer( TEMPEVENT * pteTemp, CONVERTINFO *lpciInfo ) { 
	MIDIEVENT * 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 = m_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( m_tkCurrentTime - lpciInfo->tkStart > m_dwBufferTickLength ) 
		if( lpciInfo->bTimesUp ) { 
			lpciInfo->bTimesUp = FALSE; 
			return CONVERTERR_BUFFERFULL; 
		} else 
			lpciInfo->bTimesUp = TRUE; 
 
	DWORD tkNow = m_tkCurrentTime; 
 
	// Delta time is absolute event time minus absolute time 
	// already gone by on this track 
	DWORD tkDelta = pteTemp->tkEvent - m_tkCurrentTime; 
 
	// Event time is now current time on this track 
	m_tkCurrentTime = pteTemp->tkEvent; 
 
	if( m_bInsertTempo ) { 
		m_bInsertTempo = FALSE; 
 
		if( lpciInfo->dwMaxLength-lpciInfo->dwBytesRecorded < 3*sizeof(DWORD)) { 
			// Cleanup from our write operation 
			return CONVERTERR_BUFFERFULL; 
		} 
		if( m_dwCurrentTempo ) { 
			pmeEvent->dwDeltaTime = 0; 
			pmeEvent->dwStreamID = 0; 
			pmeEvent->dwEvent = ( m_dwCurrentTempo * 100 ) / m_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 )) { 
		TRACE0("AddEventToStreamBuffer: Ignoring SysEx event.\n"); 
		if( m_dwMallocBlocks ) { 
			delete [] pteTemp->pLongData; 
			--m_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( m_dwMallocBlocks ) { 
				delete [] pteTemp->pLongData; 
				--m_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( m_dwMallocBlocks ) { 
				delete [] pteTemp->pLongData; 
				--m_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. 
		m_dwCurrentTempo = pmeEvent->dwEvent; 
		pmeEvent->dwEvent = (pmeEvent->dwEvent * 100 ) / m_dwTempoMultiplier; 
 
		pmeEvent->dwEvent |= (((DWORD)MEVT_TEMPO ) << 24 ) | MEVT_F_SHORT; 
 
		m_dwBufferTickLength = (m_dwTimeDivision * 1000 * BUFFER_TIME_LENGTH) / m_dwCurrentTempo; 
		TRACE1("m_dwBufferTickLength = %lu\n", m_dwBufferTickLength); 
 
		if( m_dwMallocBlocks ) { 
			delete [] pteTemp->pLongData; 
			--m_dwMallocBlocks; 
		} 
		lpciInfo->dwBytesRecorded += 3 *sizeof(DWORD); 
	} 
 
	return CONVERTERR_NOERROR; 
} 
 
 
// StreamBufferSetup() 
// 
// Opens a MIDI stream. Then it goes about converting the data into a midiStream buffer for playback. 
BOOL CMIDI :: StreamBufferSetup() { 
	int		nChkErr; 
	BOOL	bFoundEnd = FALSE; 
 
	MMRESULT		mmrRetVal; 
 
	if( !m_hStream ) 
		if(( mmrRetVal = midiStreamOpen( &m_hStream, 
					&m_uMIDIDeviceID, 
					DWORD(1), DWORD(MidiProc), 
					DWORD(this), 
					CALLBACK_FUNCTION )) != MMSYSERR_NOERROR ) { 
		MidiError(mmrRetVal); 
		return FALSE; 
	} 
 
	// allocate stream buffers and initialise them 
	m_StreamBuffers.resize(NUM_STREAM_BUFFERS); 
 
	MIDIPROPTIMEDIV mptd; 
	mptd.cbStruct = sizeof(mptd); 
	mptd.dwTimeDiv = m_dwTimeDivision; 
	if(( mmrRetVal = midiStreamProperty( m_hStream, (LPBYTE)&mptd, 
					    MIDIPROP_SET | MIDIPROP_TIMEDIV )) != MMSYSERR_NOERROR ) { 
		MidiError( mmrRetVal ); 
		return FALSE; 
	} 
 
	m_nEmptyBuffers = 0; 
	DWORD dwConvertFlag = CONVERTF_RESET; 
 
	for( m_nCurrentBuffer = 0; m_nCurrentBuffer < NUM_STREAM_BUFFERS; m_nCurrentBuffer++ ) { 
		m_StreamBuffers[m_nCurrentBuffer].mhBuffer.dwBufferLength = OUT_BUFFER_SIZE; 
		m_StreamBuffers[m_nCurrentBuffer].mhBuffer.lpData = new char [OUT_BUFFER_SIZE]; 
		if( m_StreamBuffers[m_nCurrentBuffer].mhBuffer.lpData == 0 ) 
			return FALSE; 
 
		// Tell the converter to convert up to one entire buffer's length of output 
		// data. Also, set a flag so it knows to reset any saved state variables it 
		// may keep from call to call. 
		m_StreamBuffers[m_nCurrentBuffer].dwStartOffset = 0; 
		m_StreamBuffers[m_nCurrentBuffer].dwMaxLength = OUT_BUFFER_SIZE; 
		m_StreamBuffers[m_nCurrentBuffer].tkStart = 0; 
		m_StreamBuffers[m_nCurrentBuffer].bTimesUp = FALSE; 
 
		if(( nChkErr = ConvertToBuffer( dwConvertFlag, &m_StreamBuffers[m_nCurrentBuffer] )) != CONVERTERR_NOERROR ) { 
			if( nChkErr == CONVERTERR_DONE ) { 
				bFoundEnd = TRUE; 
			} else { 
				TRACE0("Initial conversion pass failed\n"); 
				return FALSE; 
			} 
		} 
		m_StreamBuffers[m_nCurrentBuffer].mhBuffer.dwBytesRecorded = m_StreamBuffers[m_nCurrentBuffer].dwBytesRecorded; 
 
		if( !m_bBuffersPrepared ) 
			if(( mmrRetVal = midiOutPrepareHeader( (HMIDIOUT)m_hStream, 
						&m_StreamBuffers[m_nCurrentBuffer].mhBuffer, 
						sizeof(MIDIHDR))) != MMSYSERR_NOERROR ) { 
				MidiError( mmrRetVal ); 
				return FALSE; 
			} 
 
		if(( mmrRetVal = midiStreamOut( m_hStream, 
						&m_StreamBuffers[m_nCurrentBuffer].mhBuffer, 
						sizeof(MIDIHDR))) != MMSYSERR_NOERROR ) { 
			MidiError(mmrRetVal); 
			break; 
		} 
		dwConvertFlag = 0; 
 
		if( bFoundEnd ) 
			break; 
	} 
 
	m_bBuffersPrepared = TRUE; 
	m_nCurrentBuffer = 0; 
	return TRUE; 
} 
 
// This function unprepares and frees all our buffers -- something we must 
// do to work around a bug in MMYSYSTEM that prevents a device from playing 
// back properly unless it is closed and reopened after each stop. 
void CMIDI :: FreeBuffers() { 
	DWORD	idx; 
	MMRESULT	mmrRetVal; 
 
	if( m_bBuffersPrepared ) { 
		for( idx = 0; idx < NUM_STREAM_BUFFERS; idx++ ) 
			if(( mmrRetVal = midiOutUnprepareHeader( (HMIDIOUT)m_hStream, 
								&m_StreamBuffers[idx].mhBuffer, 
								sizeof(MIDIHDR))) != MMSYSERR_NOERROR ) { 
				MidiError(mmrRetVal); 
			} 
		m_bBuffersPrepared = FALSE; 
	} 
	// Free our stream buffers... 
	for( idx = 0; idx < NUM_STREAM_BUFFERS; idx++ ) 
	if( m_StreamBuffers[idx].mhBuffer.lpData ) { 
		delete [] m_StreamBuffers[idx].mhBuffer.lpData; 
		m_StreamBuffers[idx].mhBuffer.lpData = 0; 
	} 
} 
 
////////////////////////////////////////////////////////////////////// 
// CMIDI -- error handling 
////////////////////////////////////////////////////////////////////// 
 
void CMIDI :: MidiError(MMRESULT mmResult) { 
	#ifdef _DEBUG 
		char chText[512]; 
		midiOutGetErrorText(mmResult, chText, sizeof(chText)); 
		TRACE1("Midi error: %hs\n", chText); 
	#endif 
} 
 
 
void CMIDI :: TrackError(TRACK * ptsTrack, LPSTR lpszErr ) { 
	TRACE1("Track buffer offset %lu\n", (DWORD)(ptsTrack->pTrackCurrent - ptsTrack->pTrackStart)); 
    TRACE1("Track total length %lu\n", ptsTrack->dwTrackLength); 
	TRACE1("%hs\n", lpszErr); 
} 
 
////////////////////////////////////////////////////////////////////// 
// CMIDI -- overridables 
////////////////////////////////////////////////////////////////////// 
 
void CMIDI :: OnMidiOutOpen() { 
} 
 
 
void CMIDI :: OnMidiOutDone(MIDIHDR & rHdr) { 
	if( m_uCallbackStatus == STATUS_CALLBACKDEAD ) 
		return; 
 
	++m_nEmptyBuffers; 
 
	if( m_uCallbackStatus == STATUS_WAITINGFOREND ) { 
		if( m_nEmptyBuffers < NUM_STREAM_BUFFERS ) 
			return; 
		else { 
			m_uCallbackStatus = STATUS_CALLBACKDEAD; 
			Stop(); 
			SetEvent(m_hBufferReturnEvent); 
			return; 
		} 
	} 
 
	// This flag is set whenever the callback is waiting for all buffers to 
	// come back. 
	if( m_uCallbackStatus == STATUS_KILLCALLBACK ) { 
		// Count NUM_STREAM_BUFFERS-1 being returned for the last time 
		if( m_nEmptyBuffers < NUM_STREAM_BUFFERS ) 
			return; 
		else { 
			// Change the status to callback dead 
			m_uCallbackStatus = STATUS_CALLBACKDEAD; 
			SetEvent(m_hBufferReturnEvent); 
			return; 
		} 
	} 
 
	m_dwProgressBytes += m_StreamBuffers[m_nCurrentBuffer].mhBuffer.dwBytesRecorded; 
 
	/////////////////////////////////////////////////////////////////////////////// 
	// Fill an available buffer with audio data again... 
 
	if( m_bPlaying && m_nEmptyBuffers ) { 
		m_StreamBuffers[m_nCurrentBuffer].dwStartOffset = 0; 
		m_StreamBuffers[m_nCurrentBuffer].dwMaxLength = OUT_BUFFER_SIZE; 
		m_StreamBuffers[m_nCurrentBuffer].tkStart = 0; 
		m_StreamBuffers[m_nCurrentBuffer].dwBytesRecorded = 0; 
		m_StreamBuffers[m_nCurrentBuffer].bTimesUp = FALSE; 
 
		int nChkErr; 
 
		if(( nChkErr = ConvertToBuffer( 0, &m_StreamBuffers[m_nCurrentBuffer] )) != CONVERTERR_NOERROR ) { 
			if( nChkErr == CONVERTERR_DONE ) { 
				m_uCallbackStatus = STATUS_WAITINGFOREND; 
				return; 
			} else { 
				TRACE0("MidiProc() conversion pass failed!\n"); 
				return; 
			} 
		} 
 
		m_StreamBuffers[m_nCurrentBuffer].mhBuffer.dwBytesRecorded = m_StreamBuffers[m_nCurrentBuffer].dwBytesRecorded; 
 
		MMRESULT mmrRetVal; 
		if( (mmrRetVal = midiStreamOut(m_hStream, &m_StreamBuffers[m_nCurrentBuffer].mhBuffer, sizeof(MIDIHDR))) != MMSYSERR_NOERROR ) { 
			MidiError(mmrRetVal); 
			return; 
		} 
		m_nCurrentBuffer = ( m_nCurrentBuffer + 1 ) % NUM_STREAM_BUFFERS; 
		m_nEmptyBuffers--; 
	} 
} 
 
 
void CMIDI :: OnMidiOutPositionCB(MIDIHDR & rHdr, MIDIEVENT & rEvent) { 
	if( MIDIEVENT_TYPE(rEvent.dwEvent) == MIDI_CTRLCHANGE ) 
	{ 
		if( MIDIEVENT_DATA1(rEvent.dwEvent) == MIDICTRL_VOLUME ) { 
			// Mask off the channel number and cache the volume data byte 
			m_Volumes[MIDIEVENT_CHANNEL(rEvent.dwEvent)] = DWORD(MIDIEVENT_VOLUME(rEvent.dwEvent)*100/VOLUME_MAX); 
			if( m_pWndParent && ::IsWindow(m_pWndParent->GetSafeHwnd()) ) 
				// Do not use SendMessage(), because a change of the midi stream has no effect 
				// during callback handling, so if the owner wants to adjust the volume, as a 
				// result of the windows message, (s)he will not hear that change. 
				m_pWndParent->PostMessage( 
					WM_MIDI_VOLUMECHANGED, 
					WPARAM(this), 
					LPARAM( 
						MAKELONG( 
							WORD(MIDIEVENT_CHANNEL(rEvent.dwEvent)), 
							WORD(MIDIEVENT_VOLUME(rEvent.dwEvent)*100/VOLUME_MAX) 
						) 
					) 
				); 
		} 
	} 
} 
 
 
void CMIDI :: OnMidiOutClose() { 
} 
 
////////////////////////////////////////////////////////////////////// 
// CMIDI -- static members 
////////////////////////////////////////////////////////////////////// 
 
void CMIDI :: MidiProc(HMIDIOUT hMidi, UINT uMsg, DWORD dwInstanceData, DWORD dwParam1, DWORD dwParam2) { 
	CMIDI * pMidi = (CMIDI *) dwInstanceData; 
	ASSERT(pMidi != 0); 
	MIDIHDR * pHdr = (MIDIHDR*) dwParam1; 
 
	switch(uMsg) { 
		case MOM_OPEN: 
			pMidi->OnMidiOutOpen(); 
			break; 
 
		case MOM_CLOSE: 
			pMidi->OnMidiOutClose(); 
			break; 
 
		case MOM_DONE: 
			ASSERT(pHdr != 0); 
			pMidi->OnMidiOutDone(*pHdr); 
			break; 
 
		case MOM_POSITIONCB: 
			ASSERT(pHdr != 0); 
			pMidi->OnMidiOutPositionCB(*pHdr, *((MIDIEVENT*)(pHdr->lpData + pHdr->dwOffset))); 
			break; 
 
		default: 
			break; 
	} 
}