📄 midi.cpp
字号:
}
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];
}
void CMIDI :: SetTempo(DWORD dwPercent) {
m_dwTempoMultiplier = dwPercent ? dwPercent : 1;
m_bInsertTempo = TRUE;
}
DWORD CMIDI :: GetTempo() const {
return m_dwTempoMultiplier;
}
void CMIDI :: SetInfinitePlay(BOOL bSet) {
m_bLooped = bSet;
}
//////////////////////////////////////////////////////////////////////
// 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
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -