//-----------------------------------------------------------------------------
// File: EchoTool.cpp
//
// Desc: Implements an object based on IDirectMusicTool
//       that provides echoing effects.
//
// Copyright (c) 1998-2000 Microsoft Corporation. All rights reserved.
//-----------------------------------------------------------------------------
#include <dmusici.h>
#include "EchoTool.h"




//-----------------------------------------------------------------------------
// Name: CEchoTool::CEchoTool()
// Desc: 
//-----------------------------------------------------------------------------
CEchoTool::CEchoTool()
{
    m_cRef = 1;                 // Set to 1 so one call to Release() will free this
    m_dwEchoNum = 3;            // Default to 3 echoes per note
    m_mtDelay = DMUS_PPQ / 2;   // Default to 8th note echoes
    InitializeCriticalSection(&m_CrSec);
}




//-----------------------------------------------------------------------------
// Name: CEchoTool::~CEchoTool()
// Desc: 
//-----------------------------------------------------------------------------
CEchoTool::~CEchoTool()
{
    DeleteCriticalSection(&m_CrSec);
}




//-----------------------------------------------------------------------------
// Name: CEchoTool::QueryInterface()
// Desc: 
//-----------------------------------------------------------------------------
STDMETHODIMP CEchoTool::QueryInterface(const IID &iid, void **ppv)
{
    if (iid == IID_IUnknown || iid == IID_IDirectMusicTool)
    {
        *ppv = static_cast<IDirectMusicTool*>(this);
    } 
    else
    {
        *ppv = NULL;
        return E_NOINTERFACE;
    }
    
    reinterpret_cast<IUnknown*>(this)->AddRef();
    return S_OK;
}




//-----------------------------------------------------------------------------
// Name: CEchoTool::AddRef()
// Desc: 
//-----------------------------------------------------------------------------
STDMETHODIMP_(ULONG) CEchoTool::AddRef()
{
    return InterlockedIncrement(&m_cRef);
}




//-----------------------------------------------------------------------------
// Name: CEchoTool::Release()
// Desc: 
//-----------------------------------------------------------------------------
STDMETHODIMP_(ULONG) CEchoTool::Release()
{
    if( 0 == InterlockedDecrement(&m_cRef) )
    {
        delete this;
        return 0;
    }

    return m_cRef;
}




//-----------------------------------------------------------------------------
// Name: CEchoTool::Init()
// Desc: 
//-----------------------------------------------------------------------------
HRESULT STDMETHODCALLTYPE CEchoTool::Init( IDirectMusicGraph* pGraph )
{
    // This tool has no need to do any type of initialization.
    return E_NOTIMPL;
}




//-----------------------------------------------------------------------------
// Name: CEchoTool::GetMsgDeliveryType()
// Desc: 
//-----------------------------------------------------------------------------
HRESULT STDMETHODCALLTYPE CEchoTool::GetMsgDeliveryType( DWORD* pdwDeliveryType )
{
    // This tool wants messages immediately.
    // This is the default, so returning E_NOTIMPL
    // would work. The other method is to specifically
    // set *pdwDeliveryType to the delivery type, DMUS_PMSGF_TOOL_IMMEDIATE,
    // DMUS_PMSGF_TOOL_QUEUE, or DMUS_PMSGF_TOOL_ATTIME.
    
    *pdwDeliveryType = DMUS_PMSGF_TOOL_IMMEDIATE;
    return S_OK;
}




//-----------------------------------------------------------------------------
// Name: CEchoTool::GetMediaTypeArraySize()
// Desc: 
//-----------------------------------------------------------------------------
HRESULT STDMETHODCALLTYPE CEchoTool::GetMediaTypeArraySize( DWORD* pdwNumElements )
{
    // This tool only wants note messages, patch messages, sysex, and MIDI messages, so set
    // *pdwNumElements to 4.
    
    *pdwNumElements = 4;
    return S_OK;
}




//-----------------------------------------------------------------------------
// Name: CEchoTool::GetMediaTypes()
// Desc: 
//-----------------------------------------------------------------------------
HRESULT STDMETHODCALLTYPE CEchoTool::GetMediaTypes( DWORD** padwMediaTypes, 
                                                    DWORD dwNumElements )
{
    // Fill in the array padwMediaTypes with the type of
    // messages this tool wants to process. In this case,
    // dwNumElements will be 3, since that is what this
    // tool returns from GetMediaTypeArraySize().
    
    if( dwNumElements == 4 )
    {
        // Set the elements in the array to DMUS_PMSGT_NOTE,
        // DMUS_PMSGT_MIDI, and DMUS_PMSGT_PATCH
        (*padwMediaTypes)[0] = DMUS_PMSGT_NOTE;
        (*padwMediaTypes)[1] = DMUS_PMSGT_MIDI;
        (*padwMediaTypes)[2] = DMUS_PMSGT_PATCH;
        (*padwMediaTypes)[3] = DMUS_PMSGT_SYSEX;
        return S_OK;
    }
    else
    {
        // This should never happen
        return E_FAIL;
    }
}




//-----------------------------------------------------------------------------
// Name: CEchoTool::ProcessPMsg()
// Desc: 
//-----------------------------------------------------------------------------
HRESULT STDMETHODCALLTYPE CEchoTool::ProcessPMsg( IDirectMusicPerformance* pPerf, 
                                                  DMUS_PMSG* pPMsg )
{
    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);
    
    // Returning S_FREE frees the message. If StampPMsg()
    // fails, there is no destination for this message so
    // free it.
    if(( NULL == pPMsg->pGraph ) ||
        FAILED(pPMsg->pGraph->StampPMsg(pPMsg)))
    {
        return DMUS_S_FREE;
    }

  
    // The Tool is set up to only receive messages of types
    // DMUS_PMSGT_NOTE, DMUS_PMSGT_MIDI, DMUS_PMSGT_SYSEX, or DMUS_PMSGT_PATCH
    // We use the DX8 ClonePMsg method to make a copy of the pmsg and
    // send it to a pchannel in the next pchannel group. 
    // If it's a note, we also doctor the velocity.
    IDirectMusicPerformance8 *pPerf8;
    if (SUCCEEDED(pPerf->QueryInterface(IID_IDirectMusicPerformance8,(void **)&pPerf8)))
    {
        for( dwCount = 1; dwCount <= dwEchoNum; dwCount++ )
        {
            DMUS_PMSG *pClone;
            if( SUCCEEDED( pPerf8->ClonePMsg( pPMsg,&pClone)))
            {
                // Add to the time of the echoed note
                pClone->mtTime += (dwCount * mtDelay);
                if (pPMsg->dwType == DMUS_PMSGT_NOTE )
                {
                    DMUS_NOTE_PMSG *pNote = (DMUS_NOTE_PMSG*)pPMsg;
                    DMUS_NOTE_PMSG *pCloneNote = (DMUS_NOTE_PMSG*)pClone;
                    // Reduce the volume of the echoed note
                    // percentage of reduction in velocity increases with each echo
                    pCloneNote->bVelocity = (BYTE) (pNote->bVelocity - 
                        ((pNote->bVelocity * (dwCount * 15))/100));
                }
                // Set the note so only MUSIC_TIME is valid.
                // REFERENCE_TIME will be recomputed inside
                // SendPMsg()
                pClone->dwFlags = DMUS_PMSGF_MUSICTIME;
                pClone->dwPChannel = pPMsg->dwPChannel + 
                    (16*dwCount);
                // Queue the echoed PMsg
                pPerf->SendPMsg(pClone );
            }
        }
        pPerf8->Release();
    }


    // Return DMUS_S_REQUEUE so the original message is requeued
    return DMUS_S_REQUEUE;
}




//-----------------------------------------------------------------------------
// Name: CEchoTool::Flush()
// Desc: 
//-----------------------------------------------------------------------------
HRESULT STDMETHODCALLTYPE CEchoTool::Flush( IDirectMusicPerformance* pPerf, 
                                            DMUS_PMSG* pDMUS_PMSG,
                                            REFERENCE_TIME rt)
{
    // This tool does not need to flush.
    return E_NOTIMPL;
}




//-----------------------------------------------------------------------------
// Name: CEchoTool::SetEchoNum()
// Desc: 
//-----------------------------------------------------------------------------
void CEchoTool::SetEchoNum( DWORD dwEchoNum )
{
    // ProcessPMsg() uses m_dwEchoNum, so use a critical
    // section to make it thread-safe.
    if( dwEchoNum <= MAX_ECHOES )
    {
        EnterCriticalSection(&m_CrSec);
        m_dwEchoNum = dwEchoNum;
        LeaveCriticalSection(&m_CrSec);
    }
}




//-----------------------------------------------------------------------------
// Name: CEchoTool::SetDelay()
// Desc: 
//-----------------------------------------------------------------------------
void CEchoTool::SetDelay( MUSIC_TIME mtDelay )
{
    // ProcessPMsg() uses m_mtDelay, so use a critical
    // section to make it thread-safe.
    EnterCriticalSection(&m_CrSec);
    m_mtDelay = mtDelay;
    LeaveCriticalSection(&m_CrSec);
}
