// MediaParams.cpp: implementation of the CMediaParams class.
//
//////////////////////////////////////////////////////////////////////

#include "stdafx.h"
#include "AudioPlugIn.h"
#include "MediaParams.h"
#include "ParamEnvelope.h"

#include "CakeMedParam_i.c"

#define DEFINE_PARAM_INFO
#include "Parameters.h"

//////////////////////////////////////////////////////////////////////
// Ctors

CMediaParams::CMediaParams( IUnknown* pUnkOuter ) : m_pUnkOuter(pUnkOuter)
{
	m_pCallback = NULL;
	m_cRef = 0;
	m_aEnv = NULL;
	m_dDecimationInterval = 20.0 / 1000.0; // 20 msec
	m_lFs = 44100;
}

CMediaParams::~CMediaParams()
{
	ASSERT( 0 == m_cRef );
	if (m_pCallback)
		m_pCallback->Release();
	m_pCallback = NULL;
	m_pUnkOuter = NULL;

	delete [] m_aEnv;
	m_aEnv = NULL;
}


//////////////////////////////////////////////////////////////////////
// Factory-style construction

HRESULT CMediaParams::Create( CMediaParams** ppObj, IUnknown* pUnkOuter )
{
	if (NULL == ppObj)
		return E_POINTER;
	if (NULL == pUnkOuter)
		return E_POINTER;

	// Construct the CMediaParams object
	CMediaParams* pNew = new CMediaParams( pUnkOuter );
	if (NULL == pNew)
		return E_OUTOFMEMORY;

	// Construct and initialize its parameters
	pNew->m_aEnv = new CParamEnvelope [ NUM_PARAMS ];
	if (NULL == pNew->m_aEnv)
		return E_OUTOFMEMORY;
	for (ULONG ix = 0; ix < NUM_PARAMS; ++ix)
		pNew->m_aEnv[ ix ].SetParamInfo( m_aParamInfo[ ix ] );

	pNew->AddRef();
	*ppObj = pNew;
	return S_OK;
}


////////////////////////////////////////////////////////////////////////////////
// Given a sample range, fills pTimes with sample positions where we need
// to recompute one or more automated parameter values.  Positions are always
// added periodically at the decimation interval; positions are also added
// for every segment boundary among all of the parameters.

HRESULT CMediaParams::GetDecimationTimes( LONGLONG llSampStart,
													   LONGLONG llSampEnd,
														std::vector<LONGLONG>* pTimes )
{
	LONGLONG const			llInterval = static_cast<LONGLONG>( GetDecimationInterval() * GetSampleRate() );
	double const			dSamplesPerRefTime = static_cast<double>( GetSampleRate() ) / UNITS;
	REFERENCE_TIME const	rtStart = REFERENCE_TIME( llSampStart / dSamplesPerRefTime + 0.5 );

	// Make an worst-case guess at how many decimation points we'll need
	ULONG uPoints = 0;
	for (DWORD dwParam = 0; dwParam < NUM_AUTOMATED_PARAMS; dwParam++)
	{
		const CParamEnvelope& env = m_aEnv[ dwParam ];
		uPoints += env.GetCount() * 2;
	}

	// If there is no automation, then there is no need to decimate
	if (0 == uPoints)
		return S_OK;

	// Account for points that are added due to periodic decimation
	uPoints += ULONG( ((llSampEnd - llSampStart) / llInterval) + 1 );

	// Reserve some memory for landmark points
	pTimes->reserve( uPoints );

	// Add periodic landmarks at the decimation interval
	LONGLONG llSamp = (llSampStart / llInterval) * llInterval;
	if (llSamp < llSampStart)
		llSamp += llInterval;
	while (llSamp < llSampEnd)
	{
		pTimes->push_back( llSamp );
		llSamp += llInterval;
	}

	// Add landmarks for each shape boundary
	for (dwParam = 0; dwParam < NUM_AUTOMATED_PARAMS; dwParam++)
	{
		const CParamEnvelope& env = m_aEnv[ dwParam ];
		unsigned const nCount = env.GetCount();

		// Add each shape endpoint that falls in our time range
		for (unsigned ix = 0; ix < nCount; ix++)
		{
			const MP_ENVELOPE_SEGMENT& seg = env.GetAt( ix );
			LONGLONG const llEnvStart = static_cast<LONGLONG>( seg.rtStart * dSamplesPerRefTime + 0.5 );
			LONGLONG const llEnvEnd = static_cast<LONGLONG>( seg.rtEnd * dSamplesPerRefTime + 0.5 );
			if (llSampStart <= llEnvStart && llEnvStart < llSampEnd)
				pTimes->push_back( llEnvStart );
			if (llSampStart <= llEnvEnd && llEnvEnd < llSampEnd)
				pTimes->push_back( llEnvEnd );
		}
	}

	// Sort result
	std::sort( pTimes->begin(), pTimes->end() );

	return S_OK;
}

////////////////////////////////////////////////////////////////////////////////
// Set the current position among all parameters, updating current envelope
// value and deltas.  This method is called repeatedly by the streaming code,
// to update parameter values as they evolve along the duration of the envelope.

HRESULT CMediaParams::UpdateValuesForSample( LONGLONG llSamp )
{
	double const			dSamplesPerRefTime = static_cast<double>( GetSampleRate() ) / UNITS;
	REFERENCE_TIME const	rt = REFERENCE_TIME( llSamp / dSamplesPerRefTime + 0.5 );

	HRESULT hr = S_OK;
	for (DWORD dwParam = 0; dwParam < NUM_AUTOMATED_PARAMS; dwParam++)
	{
		hr = m_aEnv[ dwParam ].UpdateValuesForRefTime( rt, GetSampleRate() );
		if (FAILED( hr ))
			break;
	}

	return hr;
}


////////////////////////////////////////////////////////////////////////////////
// IUnknown

HRESULT CMediaParams::QueryInterface( REFIID riid, void** ppv )
{
	if (NULL == ppv)
		return E_POINTER;

	if (riid == IID_IUnknown)
	{
		*ppv = static_cast<IUnknown*>( static_cast<IMediaParams*>( this ) );
		m_pUnkOuter->AddRef();
		return S_OK;
	}
	else
	{
		return m_pUnkOuter->QueryInterface( riid, ppv );
	}
}

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

ULONG CMediaParams::Release()
{
	ASSERT( m_cRef > 0 );
	ULONG ul = InterlockedDecrement( &m_cRef );
	if (0 == ul)
	{
		delete this;
		return 0;
	}
	else
		return ul;
}


////////////////////////////////////////////////////////////////////////////////
// IMediaParams

HRESULT CMediaParams::GetParam(ULONG dwParamIndex, FLOAT* pValue)
{
	if (dwParamIndex >= NUM_PARAMS)
		return E_INVALIDARG;
	if (NULL == pValue)
		return E_POINTER;

	return m_aEnv[ dwParamIndex ].GetParam( pValue );
}

////////////////////////////////////////////////////////////////////////////////

HRESULT CMediaParams::SetParam(ULONG dwParamIndex, FLOAT value)
{
	if (dwParamIndex >= NUM_PARAMS)
		return E_INVALIDARG;

	return m_aEnv[ dwParamIndex ].SetParam( value );
}

////////////////////////////////////////////////////////////////////////////////

HRESULT CMediaParams::AddEnvelope(ULONG dwParamIndex, ULONG cSegments, MP_ENVELOPE_SEGMENT* pmpes)
{
	if (dwParamIndex >= NUM_AUTOMATED_PARAMS && dwParamIndex != DWORD_ALLPARAMS)
		return E_INVALIDARG;
	if (0 == cSegments)
		return S_OK;
	if (IsBadReadPtr( pmpes, cSegments * sizeof(MP_ENVELOPE_SEGMENT) ))
		return E_POINTER;

	double const dSamplesPerRefTime = static_cast<double>( GetSampleRate() ) / UNITS;

	if (dwParamIndex == DWORD_ALLPARAMS)
	{
		for (ULONG ix = 0; ix < NUM_AUTOMATED_PARAMS; ix++)
		{
			HRESULT hr = m_aEnv[ ix ].AddEnvelope( cSegments, pmpes, dSamplesPerRefTime );
			if (FAILED( hr ))
				return hr;
		}
		return S_OK;
	}
	else
	{
		return m_aEnv[ dwParamIndex ].AddEnvelope( cSegments, pmpes, dSamplesPerRefTime );
	}
}

////////////////////////////////////////////////////////////////////////////////

HRESULT CMediaParams::FlushEnvelope(ULONG dwParamIndex, REFERENCE_TIME rtStart, REFERENCE_TIME rtEnd)
{
	if (dwParamIndex >= NUM_AUTOMATED_PARAMS && dwParamIndex != DWORD_ALLPARAMS)
		return E_INVALIDARG;
	if (rtStart > rtEnd)
		return E_INVALIDARG;

	double const dSamplesPerRefTime = static_cast<double>( GetSampleRate() ) / UNITS;

	if (dwParamIndex == DWORD_ALLPARAMS)
	{
		for (ULONG ix = 0; ix < NUM_AUTOMATED_PARAMS; ix++)
		{
			HRESULT hr = m_aEnv[ ix ].FlushEnvelope( rtStart, rtEnd, dSamplesPerRefTime );
			if (FAILED( hr ))
				return hr;
		}
		return S_OK;
	}
	else
	{
		HRESULT hr = m_aEnv[ dwParamIndex ].FlushEnvelope( rtStart, rtEnd, dSamplesPerRefTime );
		if (FAILED( hr ))
			return hr;
	}

	return S_OK;
}

////////////////////////////////////////////////////////////////////////////////

HRESULT CMediaParams::SetTimeFormat(GUID guidTimeFormat, ULONG mpTimeData)
{
	if (guidTimeFormat != GUID_TIME_REFERENCE)
		return E_INVALIDARG;
	return S_OK;
}


////////////////////////////////////////////////////////////////////////////////
// IMediaParamInfo

HRESULT CMediaParams::GetParamCount(ULONG* pdwParams)
{
	if (NULL == pdwParams)
		return E_POINTER;

	*pdwParams = NUM_AUTOMATED_PARAMS;

	return S_OK;
}

////////////////////////////////////////////////////////////////////////////////

HRESULT CMediaParams::GetParamInfo(ULONG dwParamIndex, MP_PARAMINFO* pInfo)
{
	if (dwParamIndex >= NUM_AUTOMATED_PARAMS)
		return E_INVALIDARG;
	if (IsBadWritePtr( pInfo, sizeof(MP_PARAMINFO) ))
		return E_POINTER;

	*pInfo = m_aParamInfo[ dwParamIndex ].mppi;

	return S_OK;
}

////////////////////////////////////////////////////////////////////////////////

HRESULT CMediaParams::GetParamText(ULONG dwParamIndex, WCHAR** ppwchText)
{
	if (dwParamIndex >= NUM_AUTOMATED_PARAMS)
		return E_INVALIDARG;
	if (NULL == ppwchText)
		return E_POINTER;

	const ParamInfo&		info = m_aParamInfo[ dwParamIndex ];
	const MP_PARAMINFO&	mppi = info.mppi;

	// Count up lengths of label and unit strings, plus null terminators
	int cch = wcslen(mppi.szLabel) + wcslen(mppi.szUnitText) + 3;

	// Add in length of the enum. text if any was supplied
	if (NULL != info.pwszEnumText)
		cch += wcslen(info.pwszEnumText) + 1;

	// Allocate memory for the returned string
	*ppwchText = (WCHAR*)CoTaskMemAlloc( sizeof(WCHAR) * cch );
	if (NULL == *ppwchText)
		return E_OUTOFMEMORY;
	
	// Text format is "Name\0Units\0Enum1\0Enum2\0...EnumN\0\0"
	WCHAR* pwsz = *ppwchText;

	// [1] Copy in the name
	wcscpy( pwsz, mppi.szLabel );
	pwsz += wcslen(mppi.szLabel) + 1;

	// [2] Copy in the units
	wcscpy( pwsz, mppi.szUnitText );
	pwsz += wcslen(mppi.szUnitText) + 1;

	// [3] Copy in the enum. text, if any was supplied
	if (NULL != info.pwszEnumText)
	{
		wcscpy( pwsz, info.pwszEnumText );

		// Replace commas with nulls, to conform to DX8 string format spec
		while (*pwsz)
		{
			if (*pwsz == L',')
				*pwsz = 0;
			pwsz++;
		}
	}

	return S_OK;
}

////////////////////////////////////////////////////////////////////////////////

HRESULT CMediaParams::GetNumTimeFormats(ULONG* pdwNumTimeFormats)
{
	if (NULL == pdwNumTimeFormats)
		return E_POINTER;
	*pdwNumTimeFormats = 1;
	return S_OK;
}

////////////////////////////////////////////////////////////////////////////////

HRESULT CMediaParams::GetSupportedTimeFormat(ULONG dwFormatIndex, GUID* pguidTimeFormat)
{
	if (NULL == pguidTimeFormat)
		return E_POINTER;
	if (0 != dwFormatIndex)
		return E_INVALIDARG;
	*pguidTimeFormat = GUID_TIME_REFERENCE;
	return S_OK;
}

////////////////////////////////////////////////////////////////////////////////

HRESULT CMediaParams::GetCurrentTimeFormat(GUID* pguidTimeFormat, ULONG*)
{
	if (NULL == pguidTimeFormat)
		return E_POINTER;
	*pguidTimeFormat = GUID_TIME_REFERENCE;
	return S_OK;
}


////////////////////////////////////////////////////////////////////////////////
// IMediaParamsSetUICallback

HRESULT CMediaParams::SetUICallback(IMediaParamsUICallback* pICallback)
{
	if (pICallback)
		pICallback->AddRef();
	if (m_pCallback)
		m_pCallback->Release();
	m_pCallback = pICallback;
	return S_OK;
}


////////////////////////////////////////////////////////////////////////////////
// IMediaParamsUICallback

HRESULT CMediaParams::ParamsBeginCapture(DWORD *aIndex, DWORD cPoints)
{
	HRESULT hr = S_OK;

	// Inform each parameter that capture has begun
	for (DWORD ix = 0; ix < cPoints; ix++)
		m_aEnv[ aIndex[ ix ] ].BeginCapture();

	if (m_pCallback)
		hr = m_pCallback->ParamsBeginCapture( aIndex, cPoints );

	return hr;
}

HRESULT CMediaParams::ParamsChanged(DWORD *aIndex, DWORD cPoints, MP_DATA *paData)
{
	HRESULT hr = S_OK;

	// Send the parameter change to each parameter
	for (DWORD ix = 0; ix < cPoints; ix++)
	{
		hr = SetParam( aIndex[ ix ], paData[ ix ] );
		if (FAILED( hr ))
			return hr;
	}

	// Send the parameter change to our callback
	if (m_pCallback)
		hr = m_pCallback->ParamsChanged( aIndex, cPoints, paData );

	return hr;
}

HRESULT CMediaParams::ParamsEndCapture(DWORD *aIndex, DWORD cPoints)
{
	HRESULT hr = S_OK;

	// Inform each parameter that capture has ended
	for (DWORD ix = 0; ix < cPoints; ix++)
		m_aEnv[ aIndex[ ix ] ].EndCapture();

	if (m_pCallback)
		hr = m_pCallback->ParamsEndCapture( aIndex, cPoints );

	return hr;
}

////////////////////////////////////////////////////////////////////////////////