// Velocity.cpp : Implementation of CVelocityTool
//
// Copyright (C) 2000 Microsoft Corporation.  All Rights Reserved
//

#include "dmusicc.h"
#include "dmusici.h"
#include "debug.h"
#include "velocity.h"
#include "toolhelp.h"

CVelocityTool::CVelocityTool()
{
    ParamInfo Params[DMUS_VELOCITY_PARAMCOUNT] = 
    {
        { DMUS_VELOCITY_STRENGTH, MPT_INT,MP_CAPS_ALL,0,100,100,
            L"Percent",L"Strength",NULL },            // Strength - 100% by default
        { DMUS_VELOCITY_LOWLIMIT, MPT_INT,MP_CAPS_ALL,1,127,1,
            L"Velocity",L"Lower Limit",NULL },        // Lower limit - 1 by default
        { DMUS_VELOCITY_HIGHLIMIT, MPT_INT,MP_CAPS_ALL,1,127,127,
            L"Velocity",L"Upper Limit",NULL },        // Upper limit - 127 by default
        { DMUS_VELOCITY_CURVESTART, MPT_INT,MP_CAPS_ALL,1,127,1,
            L"Velocity",L"Curve Start",NULL },        // Curve start - 1 by default
        { DMUS_VELOCITY_CURVEEND, MPT_INT,MP_CAPS_ALL,1,127,127,
            L"Velocity",L"Curve End",NULL },          // Curve End - 127 by default
    };
    InitParams(DMUS_VELOCITY_PARAMCOUNT,Params);
    m_fMusicTime = TRUE;        // override default setting.
}

STDMETHODIMP_(ULONG) CVelocityTool::AddRef()
{
    return InterlockedIncrement(&m_cRef);
}

STDMETHODIMP_(ULONG) CVelocityTool::Release()
{
    if( 0 == InterlockedDecrement(&m_cRef) )
    {
        delete this;
        return 0;
    }

    return m_cRef;
}

STDMETHODIMP CVelocityTool::QueryInterface(const IID &iid, void **ppv)
{
    if (iid == IID_IUnknown || iid == IID_IDirectMusicTool || iid == IID_IDirectMusicTool8)
    {
        *ppv = static_cast<IDirectMusicTool8*>(this);
    } 
	else if(iid == IID_IPersistStream)
	{
		*ppv = static_cast<IPersistStream*>(this);
	}
    else if(iid == IID_IDirectMusicVelocityTool)
	{
		*ppv = static_cast<IDirectMusicVelocityTool*>(this);
	}
    else if(iid == IID_IMediaParams)
	{
		*ppv = static_cast<IMediaParams*>(this);
	}
    else if(iid == IID_IMediaParamInfo)
	{
		*ppv = static_cast<IMediaParamInfo*>(this);
	}
    else if(iid == IID_ISpecifyPropertyPages)
	{
		*ppv = static_cast<ISpecifyPropertyPages*>(this);
	}
    else
    {
        *ppv = NULL;
        return E_NOINTERFACE;
    }
    
    AddRef();
    return S_OK;
}

//////////////////////////////////////////////////////////////////////
// IPersistStream

STDMETHODIMP CVelocityTool::GetClassID(CLSID* pClassID) 

{
    if (pClassID)
    {
	    *pClassID = CLSID_DirectMusicVelocityTool;
	    return S_OK;
    }
    return E_POINTER;
}


//////////////////////////////////////////////////////////////////////
// IPersistStream Methods:

STDMETHODIMP CVelocityTool::IsDirty() 

{
    if (m_fDirty) return S_OK;
    else return S_FALSE;
}


STDMETHODIMP CVelocityTool::Load(IStream* pStream)
{
	EnterCriticalSection(&m_CrSec);
	DWORD dwChunkID;
    DWORD dwSize;

	HRESULT hr = pStream->Read(&dwChunkID, sizeof(dwChunkID), NULL);
	hr = pStream->Read(&dwSize, sizeof(dwSize), NULL);

	if(SUCCEEDED(hr) && (dwChunkID == FOURCC_VELOCITY_CHUNK))
	{
        DMUS_IO_VELOCITY_HEADER Header;
        memset(&Header,0,sizeof(Header));
		hr = pStream->Read(&Header, min(sizeof(Header),dwSize), NULL);
        if (SUCCEEDED(hr))
        {
            SetParam(DMUS_VELOCITY_STRENGTH,(float) Header.lStrength);
            SetParam(DMUS_VELOCITY_LOWLIMIT,(float) Header.lLowLimit);
            SetParam(DMUS_VELOCITY_HIGHLIMIT,(float) Header.lHighLimit);
            SetParam(DMUS_VELOCITY_CURVESTART,(float) Header.lCurveStart);
            SetParam(DMUS_VELOCITY_CURVEEND,(float) Header.lCurveEnd);
        }
    }
    m_fDirty = FALSE;
	LeaveCriticalSection(&m_CrSec);

	return hr;
}

STDMETHODIMP CVelocityTool::Save(IStream* pStream, BOOL fClearDirty) 

{
    EnterCriticalSection(&m_CrSec);
	DWORD dwChunkID = FOURCC_VELOCITY_CHUNK;
    DWORD dwSize = sizeof(DMUS_IO_VELOCITY_HEADER);

	HRESULT hr = pStream->Write(&dwChunkID, sizeof(dwChunkID), NULL);
    if (SUCCEEDED(hr))
    {
	    hr = pStream->Write(&dwSize, sizeof(dwSize), NULL);
    }
    if (SUCCEEDED(hr))
    {
        DMUS_IO_VELOCITY_HEADER Header;
        GetParamInt(DMUS_VELOCITY_STRENGTH,MAX_REF_TIME,&Header.lStrength);
        GetParamInt(DMUS_VELOCITY_LOWLIMIT,MAX_REF_TIME,&Header.lLowLimit);
        GetParamInt(DMUS_VELOCITY_HIGHLIMIT,MAX_REF_TIME,&Header.lHighLimit);
        GetParamInt(DMUS_VELOCITY_CURVESTART,MAX_REF_TIME,&Header.lCurveStart);
        GetParamInt(DMUS_VELOCITY_CURVEEND,MAX_REF_TIME,&Header.lCurveEnd);
		hr = pStream->Write(&Header, sizeof(Header),NULL);
    }
    if (fClearDirty) m_fDirty = FALSE;
	LeaveCriticalSection(&m_CrSec);
    return hr;
}

STDMETHODIMP CVelocityTool::GetSizeMax(ULARGE_INTEGER* pcbSize) 

{
    if (pcbSize == NULL)
    {
        return E_POINTER;
    }
    pcbSize->QuadPart = sizeof(DMUS_IO_VELOCITY_HEADER) + 8; // Data plus RIFF header.
    return S_OK;
}

STDMETHODIMP CVelocityTool::GetPages(CAUUID * pPages)

{
	pPages->cElems = 1;
	pPages->pElems = (GUID *) CoTaskMemAlloc(sizeof(GUID));
	if (pPages->pElems == NULL)
	    return E_OUTOFMEMORY;

	*(pPages->pElems) = CLSID_VelocityPage;
	return NOERROR;
}


/////////////////////////////////////////////////////////////////
// IDirectMusicTool

STDMETHODIMP CVelocityTool::ProcessPMsg( IDirectMusicPerformance* pPerf, 
                                                  DMUS_PMSG* pPMsg )
{
    // returning S_FREE frees the message. If StampPMsg()
    // fails, there is no destination for this message so
    // free it.
    if(NULL == pPMsg->pGraph )
    {
        return DMUS_S_FREE;
    }
    if (FAILED(pPMsg->pGraph->StampPMsg(pPMsg))) 
    {
        return DMUS_S_FREE;
    }
    // We need to know the time format so we can call GetParamInt() to read control parameters.
    REFERENCE_TIME rtTime;
    if (m_fMusicTime) rtTime = pPMsg->mtTime;
    else rtTime = pPMsg->rtTime;
    if( pPMsg->dwType == DMUS_PMSGT_NOTE )
    {
        DMUS_NOTE_PMSG *pNote = (DMUS_NOTE_PMSG *) pPMsg;
        long lStrength;
        long lLowLimit, lHighLimit, lCurveStart, lCurveEnd;
        GetParamInt(DMUS_VELOCITY_STRENGTH,rtTime,&lStrength);
        GetParamInt(DMUS_VELOCITY_LOWLIMIT,rtTime,&lLowLimit);
        GetParamInt(DMUS_VELOCITY_HIGHLIMIT,rtTime,&lHighLimit);
        GetParamInt(DMUS_VELOCITY_CURVESTART,rtTime,&lCurveStart);
        GetParamInt(DMUS_VELOCITY_CURVEEND,rtTime,&lCurveEnd);
        if (lCurveStart <= lCurveEnd)
        {
            long lNewVelocity;
            if (pNote->bVelocity <= lCurveStart)
            {
                lNewVelocity = lLowLimit;
            }
            else if (pNote->bVelocity >= lCurveEnd)
            {
                lNewVelocity = lHighLimit;
            }
            else
            {
                // For this case, compute the point on the line between (lCurveStart, lLowLimit) and (lCurveEnd, lHighLimit)
                lNewVelocity = lLowLimit + ((lHighLimit - lLowLimit) * (pNote->bVelocity - lCurveStart)) / (lCurveEnd - lCurveStart);
            }
            // Now, calculate the change we want to apply.
            lNewVelocity -= pNote->bVelocity;
            // Scale it to the amount we'll actually do.
            lNewVelocity = (lNewVelocity * lStrength) / 100;
            lNewVelocity += pNote->bVelocity;
            if (lNewVelocity < 1) lNewVelocity = 1;
            if (lNewVelocity > 127) lNewVelocity = 127;
            pNote->bVelocity = (BYTE) lNewVelocity;
        }

    }
    return DMUS_S_REQUEUE;
}

STDMETHODIMP CVelocityTool::Clone( IDirectMusicTool ** ppTool)

{
    CVelocityTool *pNew = new CVelocityTool;
    if (pNew)
    {
        HRESULT hr = pNew->CopyParamsFromSource(this);
        if (SUCCEEDED(hr))
        {
            *ppTool = (IDirectMusicTool *) pNew;
        }
        else
        {
            delete pNew;
        }
        return hr;
    }
    else
    {
        return E_OUTOFMEMORY;
    }
}

STDMETHODIMP CVelocityTool::SetStrength(long lStrength) 
{
    return SetParam(DMUS_VELOCITY_STRENGTH,(float) lStrength);
}

STDMETHODIMP CVelocityTool::SetLowLimit(long lVelocityOut)
{
    return SetParam(DMUS_VELOCITY_LOWLIMIT,(float) lVelocityOut);
}

STDMETHODIMP CVelocityTool::SetHighLimit(long lVelocityOut)
{
    return SetParam(DMUS_VELOCITY_HIGHLIMIT,(float) lVelocityOut);
}

STDMETHODIMP CVelocityTool::SetCurveStart(long lVelocityIn)
{
    return SetParam(DMUS_VELOCITY_CURVESTART,(float) lVelocityIn);
}

STDMETHODIMP CVelocityTool::SetCurveEnd(long lVelocityIn)
{
    return SetParam(DMUS_VELOCITY_CURVEEND,(float) lVelocityIn);
}

STDMETHODIMP CVelocityTool::GetStrength(long * plStrength) 
{
    return GetParamInt(DMUS_VELOCITY_STRENGTH,MAX_REF_TIME,plStrength);
}

STDMETHODIMP CVelocityTool::GetLowLimit(long * plVelocityOut) 
{
    return GetParamInt(DMUS_VELOCITY_LOWLIMIT,MAX_REF_TIME,plVelocityOut);
}

STDMETHODIMP CVelocityTool::GetHighLimit(long * plVelocityOut) 
{
    return GetParamInt(DMUS_VELOCITY_HIGHLIMIT,MAX_REF_TIME,plVelocityOut);
}

STDMETHODIMP CVelocityTool::GetCurveStart(long * plVelocityIn) 
{
    return GetParamInt(DMUS_VELOCITY_CURVESTART,MAX_REF_TIME,plVelocityIn);
}

STDMETHODIMP CVelocityTool::GetCurveEnd(long * plVelocityIn) 
{
    return GetParamInt(DMUS_VELOCITY_CURVEEND,MAX_REF_TIME,plVelocityIn);
}