The actual work of the tool is performed in the method derived from IDirectMusicTool::ProcessPMsg. This method will be called for every message of the type listed in the array returned by IDirectMusicTool::GetMediaTypes.
The CEchoTool::ProcessPMsg method first performs some initialization of local variables:
HRESULT STDMETHODCALLTYPE CEchoTool::ProcessPMsg(
IDirectMusicPerformance* pPerf,
DMUS_PMSG* pMsg)
{
DMUS_NOTE_PMSG* pNote;
DWORD dwCount;
DWORD dwEchoNum;
MUSIC_TIME mtDelay;
// SetEchoNum() and SetDelay() use these member variables,
// so use a critical section to make them thread-safe.
EnterCriticalSection(&m_CrSec);
dwEchoNum = m_dwEchoNum;
mtDelay = m_mtDelay;
LeaveCriticalSection(&m_CrSec);
Next the method calls the IDirectMusicGraph::StampPMsg method on the message. If there is another tool to which this message must be directed after we're finished with it here, StampPMsg succeeds. (Note that DirectMusic provides a final output tool, so StampPMsg should succeed even if there are no other application-specific tools to which the message must be routed.) If it fails, our method returns S_FREE so that the message will automatically be discarded.
if ((NULL == pMsg->pGraph) ||
FAILED(pMsg->pGraph->StampPMsg(pMsg)))
{
return DMUS_S_FREE;
}
Now it's time for CEchoTool to perform its work on the message. Remember, it is set up to receive messages only of type DMUS_PMSGT_NOTE, DMUS_PMSGT_MIDI; or DMUS_PMSGT_PATCH. (These types are part of the DMUS_PMSGT_TYPES enumeration.)
For each successive echo to be created, the tool does the following:
Here's the sample code that deals with MIDI notes:
if( pPMsg->dwType == DMUS_PMSGT_MIDI )
{
// copy MIDI messages into the echo channels.
for( dwCount = 1; dwCount <= dwEchoNum; dwCount++ )
{
DMUS_MIDI_PMSG* pMidi;
if( SUCCEEDED( pPerf->AllocPMsg( sizeof(DMUS_MIDI_PMSG),
(DMUS_PMSG**)&pMidi )))
{
// Copy the original message into this message.
memcpy( pMidi, pPMsg, sizeof(DMUS_MIDI_PMSG) );
// Addref or clear out any fields that contain
// or may contain pointers to objects.
if( pMidi->pTool ) pMidi->pTool->AddRef();
if( pMidi->pGraph ) pMidi->pGraph->AddRef();
pMidi->punkUser = NULL;
// Set the PChannel so the message goes to the
// next higher group.
pMidi->dwPChannel = pMidi->dwPChannel +
(16*dwCount);
// Add to the time of the echoed message.
pMidi->mtTime += (dwCount * mtDelay);
// Set the message so only MUSIC_TIME is valid.
// REFERENCE_TIME will be recomputed inside
// SendPMsg().
pMidi->dwFlags = DMUS_PMSGF_MUSICTIME;
// Send the message
pPerf->SendPMsg( (DMUS_PMSG*)pMidi );
}
}
}
Patch changes are also copied and sent to all possible echo channels, even those not currently being used, so that if echoes are added later they will be played by the correct instruments. Note that in the sample Echotool application, MAX_ECHOES is defined in Echotool.h.
else if( pPMsg->dwType == DMUS_PMSGT_PATCH )
{
for( dwCount = 1; dwCount <= MAX_ECHOES; dwCount++ )
{
DMUS_PATCH_PMSG* pPatch;
if( SUCCEEDED( pPerf->AllocPMsg( sizeof(DMUS_PATCH_PMSG),
(DMUS_PMSG**)&pPatch )))
{
// Copy the original message into this message,
memcpy( pPatch, pPMsg, sizeof(DMUS_PATCH_PMSG) );
// Addref or clear out any fields that contain
// or may contain pointers to objects
if( pPatch->pTool ) pPatch->pTool->AddRef();
if( pPatch->pGraph ) pPatch->pGraph->AddRef();
pPatch->punkUser = NULL;
// Set the PChannel so the message goes to the
// next higher group.
pPatch->dwPChannel = pPatch->dwPChannel +
(16*dwCount);
// Add to the time of the echoed message.
pPatch->mtTime += (dwCount * mtDelay);
// Set the message so only MUSIC_TIME is valid.
// REFERENCE_TIME will be recomputed inside
// SendPMsg()
pPatch->dwFlags = DMUS_PMSGF_MUSICTIME;
// Send the message.
pPerf->SendPMsg( (DMUS_PMSG*)pPatch );
}
}
}
The method deals with music notes much as it did with MIDI notes, but taking the additional step of reducing the volume:
else if( pPMsg->dwType == DMUS_PMSGT_NOTE )
{
// Create a variable to track the next note's velocity
BYTE bVelocity;
pNote = (DMUS_NOTE_PMSG*)pPMsg;
bVelocity = pNote->bVelocity;
for( dwCount = 1; dwCount <= dwEchoNum; dwCount++ )
{
if( SUCCEEDED( pPerf->AllocPMsg( sizeof(DMUS_NOTE_PMSG),
(DMUS_PMSG**)&pNote )))
{
// Copy the original note into this message.
memcpy( pNote, pPMsg, sizeof(DMUS_NOTE_PMSG) );
// Addref or clear out any fields that contain
// or may contain pointers to objects.
if( pNote->pTool ) pNote->pTool->AddRef();
if( pNote->pGraph ) pNote->pGraph->AddRef();
pNote->punkUser = NULL;
// Add to the time of the echoed note.
pNote->mtTime += (dwCount * mtDelay);
// Reduce the volume of the echoed note.
bVelocity = (BYTE) (bVelocity -
((bVelocity * (dwCount * 15))/100));
pNote->bVelocity = bVelocity;
// Set the note so only MUSIC_TIME is valid.
// REFERENCE_TIME will be recomputed inside
// SendPMsg().
pNote->dwFlags = DMUS_PMSGF_MUSICTIME;
pNote->dwPChannel = pNote->dwPChannel +
(16*dwCount);
// Send the message.
pPerf->SendPMsg( (DMUS_PMSG*)pNote );
}
}
}
Finally, the ProcessPMsg method returns DMUS_S_REQUEUE so the original message will be put back in the pipeline.
return DMUS_S_REQUEUE;
} // End CEchoTool::ProcessPMsg()