// Swing.cpp : Implementation of CSwingTool
//
// Copyright (C) 1999 Microsoft Corporation.  All Rights Reserved
//

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

CSwingTool::CSwingTool()
{
    ParamInfo Params[DMUS_SWING_PARAMCOUNT] = 
    {
        { DMUS_SWING_STRENGTH, MPT_INT,MP_CAPS_ALL,0,100,100,
            L"Percent",L"Strength",NULL },            // Strength - 100% by default
    };
    InitParams(DMUS_SWING_PARAMCOUNT,Params);
    m_fMusicTime = TRUE;        // override default setting.
}

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

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

    return m_cRef;
}

STDMETHODIMP CSwingTool::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_IDirectMusicSwingTool)
	{
		*ppv = static_cast<IDirectMusicSwingTool*>(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 CSwingTool::GetClassID(CLSID* pClassID) 

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


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

STDMETHODIMP CSwingTool::IsDirty() 

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


STDMETHODIMP CSwingTool::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_SWING_CHUNK))
	{
        DMUS_IO_SWING_HEADER Header;
        memset(&Header,0,sizeof(Header));
		hr = pStream->Read(&Header, min(sizeof(Header),dwSize), NULL);
        if (SUCCEEDED(hr))
        {
            SetParam(DMUS_SWING_STRENGTH,(float) Header.dwStrength);
        }
    }
    m_fDirty = FALSE;
	LeaveCriticalSection(&m_CrSec);

	return hr;
}

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

{
    EnterCriticalSection(&m_CrSec);
	DWORD dwChunkID = FOURCC_SWING_CHUNK;
    DWORD dwSize = sizeof(DMUS_IO_SWING_HEADER);

	HRESULT hr = pStream->Write(&dwChunkID, sizeof(dwChunkID), NULL);
    if (SUCCEEDED(hr))
    {
	    hr = pStream->Write(&dwSize, sizeof(dwSize), NULL);
    }
    if (SUCCEEDED(hr))
    {
        DMUS_IO_SWING_HEADER Header;
        GetParamInt(DMUS_SWING_STRENGTH,MAX_REF_TIME,(long *)&Header.dwStrength);
		hr = pStream->Write(&Header, sizeof(Header),NULL);
    }
    if (fClearDirty) m_fDirty = FALSE;
	LeaveCriticalSection(&m_CrSec);
    return hr;
}

STDMETHODIMP CSwingTool::GetSizeMax(ULARGE_INTEGER* pcbSize) 

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

STDMETHODIMP CSwingTool::GetPages(CAUUID * pPages)

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

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


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

STDMETHODIMP CSwingTool::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;
        IDirectMusicPerformance8 *pPerf8;   // We'll need the DX8 interface to access ClonePMsg.
        if (SUCCEEDED(pPerf->QueryInterface(IID_IDirectMusicPerformance8,(void **)&pPerf8)))
        {
            long lStrength;
            
            GetParamInt(DMUS_SWING_STRENGTH,rtTime,&lStrength);
            DMUS_TIMESIGNATURE TimeSig;
            if (SUCCEEDED(pPerf8->GetParamEx(GUID_TimeSignature,pNote->dwVirtualTrackID,pNote->dwGroupID,DMUS_SEG_ANYTRACK,pNote->mtTime,NULL,&TimeSig)))
            {
                long lGrid = ((4 * 768) / TimeSig.bBeat) / TimeSig.wGridsPerBeat;
                if ((TimeSig.wGridsPerBeat == 3) || (TimeSig.wGridsPerBeat == 6) || 
                    (TimeSig.wGridsPerBeat == 9) || (TimeSig.wGridsPerBeat == 12))
                {
                    // This is already in a triplet feel, so work in reverse.
                    // Adjust the timing, as set by the lStrength parameter.
                    // lStrength is a range from 0 for no swing to 100 for full swing.
                    // We are moving from grids 0,1,2,3,4,5... in triplet feel to grids 
                    // 0,1,2,4,5,6... in non-triplet feel.
                    // So, the notes need to be adjusted in time in either direction.
                    // When we change the time, we clear the DMUS_PMSGF_REFTIME flag, 
                    // telling the performance to recalculate the reference time stamp
                    // in the event when it is requeued.
                    static long lFromTriplet[12] = { 0,1,2,4,5,6,8,9,10,12,13,14 };
                    if (pNote->bGrid < 12)
                    {
                        // Calculate the position we are moving to.
                        long lTwoplet = ((lGrid * 3) / 4) * lFromTriplet[pNote->bGrid];
                        // Calculate the position we are moving from. 
                        lGrid *= pNote->bGrid;
                        // Calculate the new time. Note that we inverse strength since we are going from triplet.
                        pNote->mtTime += ((100 - lStrength) * (lTwoplet - lGrid)) / 100;
		                pNote->dwFlags &= ~DMUS_PMSGF_REFTIME;
                    }
                }
                else if (TimeSig.wGridsPerBeat <= 16)
                {
                    // Adjust the timing, as set by the lStrength parameter.
                    // lStrength is a range from 0 for no swing to 100 for full swing.
                    // We are moving from grids 0,1,2,3 in straight ahead feel to grids 
                    // 0,1,2 in triplet feel.
                    // So, the notes need to be adjusted in time in either direction.
                    // When we change the time, we clear the DMUS_PMSGF_REFTIME flag, 
                    // telling the performance to recalculate the reference time stamp
                    // in the event when it is requeued.
                    static long lToTriplet[16] = { 0,1,2,2,3,4,5,5,6,7,8,8,9,10,11,11 };
                    if (pNote->bGrid < 16)
                    {
                        // Calculate the position we are moving to.
                        long lTriplet = ((lGrid * 4) / 3) * lToTriplet[pNote->bGrid];
                        // Calculate the position we are moving from. 
                        Trace(0,"%ld,%ld,%ld,%ld\t%ld,%ld,%ld\t",
                            (long)TimeSig.bBeatsPerMeasure,(long)TimeSig.bBeat,(long)TimeSig.wGridsPerBeat,
                            lGrid,(long)pNote->bBeat,(long)pNote->bGrid,(long)pNote->nOffset);
                        lGrid *= pNote->bGrid;
                        Trace(0,"%ld,%ld,%ld\n",lStrength,lTriplet,lGrid);
                        pNote->mtTime += (lStrength * (lTriplet - lGrid)) / 100;
		                pNote->dwFlags &= ~DMUS_PMSGF_REFTIME;
                    }
                }
            }
            pPerf8->Release();
        }
    }
    return DMUS_S_REQUEUE;
}

STDMETHODIMP CSwingTool::Clone( IDirectMusicTool ** ppTool)

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

STDMETHODIMP CSwingTool::SetStrength(DWORD dwStrength) 
{
    return SetParam(DMUS_SWING_STRENGTH,(float) dwStrength);
}

STDMETHODIMP CSwingTool::GetStrength(DWORD * pdwStrength) 
{
    return GetParamInt(DMUS_SWING_STRENGTH,MAX_REF_TIME,(long *) pdwStrength);
}