829 lines
30 KiB
C++
829 lines
30 KiB
C++
|
// Copyright (c) 1999 Microsoft Corporation. All rights reserved.
|
|||
|
//
|
|||
|
// Declaration of CParamControlTrack.
|
|||
|
//
|
|||
|
|
|||
|
#include "dmime.h"
|
|||
|
#include "ParamTrk.h"
|
|||
|
#include "..\shared\Validate.h"
|
|||
|
#include "miscutil.h"
|
|||
|
#include "limits.h"
|
|||
|
#include "math.h"
|
|||
|
|
|||
|
|
|||
|
STDMETHODIMP
|
|||
|
CParamControlTrack::QueryInterface(const IID &iid, void **ppv)
|
|||
|
{
|
|||
|
V_INAME(CParamControlTrack::QueryInterface);
|
|||
|
V_PTRPTR_WRITE(ppv);
|
|||
|
V_REFGUID(iid);
|
|||
|
|
|||
|
if (iid == IID_IUnknown || iid == IID_IDirectMusicTrack || iid == IID_IDirectMusicTrack8)
|
|||
|
*ppv = static_cast<IDirectMusicTrack8*>(this);
|
|||
|
else if (iid == IID_IPersistStream)
|
|||
|
*ppv = static_cast<IPersistStream*>(this);
|
|||
|
else if (iid == IID_IPersist)
|
|||
|
*ppv = static_cast<IPersist*>(this);
|
|||
|
else if (iid == IID_CParamControlTrack)
|
|||
|
{
|
|||
|
*ppv = static_cast<CParamControlTrack*>(this);
|
|||
|
}
|
|||
|
else
|
|||
|
{
|
|||
|
*ppv = NULL;
|
|||
|
Trace(4,"Warning: Request to query unknown interface on Track\n");
|
|||
|
return E_NOINTERFACE;
|
|||
|
}
|
|||
|
|
|||
|
reinterpret_cast<IUnknown*>(this)->AddRef();
|
|||
|
return S_OK;
|
|||
|
}
|
|||
|
|
|||
|
STDMETHODIMP
|
|||
|
CParamControlTrack::Init(IDirectMusicSegment *pSegment)
|
|||
|
{
|
|||
|
V_INAME(CBasicTrack::Init);
|
|||
|
V_INTERFACE(pSegment);
|
|||
|
|
|||
|
return S_OK;
|
|||
|
}
|
|||
|
|
|||
|
STDMETHODIMP
|
|||
|
CParamControlTrack::Load(IStream* pIStream)
|
|||
|
{
|
|||
|
V_INAME(CPlayingTrack::Load);
|
|||
|
V_INTERFACE(pIStream);
|
|||
|
HRESULT hr = S_OK;
|
|||
|
|
|||
|
SmartRef::CritSec CS(&m_CriticalSection);
|
|||
|
|
|||
|
// Increment counter so the next play will update state data with the new list.
|
|||
|
++m_dwValidate;
|
|||
|
// Clear the objects/params/curves in case we're being reloaded.
|
|||
|
m_listObjects.CleanUp();
|
|||
|
m_cObjects = 0;
|
|||
|
m_cParams = 0;
|
|||
|
|
|||
|
SmartRef::RiffIter ri(pIStream);
|
|||
|
if (!ri)
|
|||
|
return ri.hr();
|
|||
|
|
|||
|
// find <prmt>
|
|||
|
hr = ri.FindRequired(SmartRef::RiffIter::List, DMUS_FOURCC_PARAMCONTROLTRACK_TRACK_LIST, DMUS_E_INVALID_PARAMCONTROLTRACK);
|
|||
|
if (FAILED(hr))
|
|||
|
{
|
|||
|
#ifdef DBG
|
|||
|
if (hr == DMUS_E_INVALID_PARAMCONTROLTRACK)
|
|||
|
{
|
|||
|
Trace(1, "Error: Unable to load parameter control track: List 'prmt' not found.\n");
|
|||
|
}
|
|||
|
#endif
|
|||
|
return hr;
|
|||
|
}
|
|||
|
SmartRef::RiffIter riTrack = ri.Descend();
|
|||
|
|
|||
|
// for each <prol>
|
|||
|
while (riTrack && riTrack.Find(SmartRef::RiffIter::List, DMUS_FOURCC_PARAMCONTROLTRACK_OBJECT_LIST))
|
|||
|
{
|
|||
|
hr = this->LoadObject(riTrack.Descend());
|
|||
|
if (FAILED(hr))
|
|||
|
return hr;
|
|||
|
++riTrack;
|
|||
|
}
|
|||
|
hr = riTrack.hr();
|
|||
|
|
|||
|
return hr;
|
|||
|
}
|
|||
|
|
|||
|
STDMETHODIMP
|
|||
|
CParamControlTrack::InitPlay(
|
|||
|
IDirectMusicSegmentState *pSegmentState,
|
|||
|
IDirectMusicPerformance *pPerformance,
|
|||
|
void **ppStateData,
|
|||
|
DWORD dwTrackID,
|
|||
|
DWORD dwFlags)
|
|||
|
{
|
|||
|
V_INAME(CParamControlTrack::InitPlay);
|
|||
|
V_PTRPTR_WRITE(ppStateData);
|
|||
|
V_INTERFACE(pSegmentState);
|
|||
|
V_INTERFACE(pPerformance);
|
|||
|
|
|||
|
SmartRef::CritSec CS(&m_CriticalSection);
|
|||
|
|
|||
|
// Set up state data
|
|||
|
StateData *pStateData = new StateData;
|
|||
|
if (!pStateData)
|
|||
|
return E_OUTOFMEMORY;
|
|||
|
|
|||
|
HRESULT hr = InitStateData(pStateData, pSegmentState);
|
|||
|
if (FAILED(hr))
|
|||
|
{
|
|||
|
delete pStateData;
|
|||
|
}
|
|||
|
else
|
|||
|
{
|
|||
|
pStateData->dwValidate = m_dwValidate;
|
|||
|
*ppStateData = pStateData;
|
|||
|
}
|
|||
|
|
|||
|
return hr;
|
|||
|
}
|
|||
|
|
|||
|
STDMETHODIMP
|
|||
|
CParamControlTrack::EndPlay(void *pStateData)
|
|||
|
{
|
|||
|
V_INAME(CParamControlTrack::EndPlay);
|
|||
|
V_BUFPTR_WRITE(pStateData, sizeof(StateData));
|
|||
|
|
|||
|
SmartRef::CritSec CS(&m_CriticalSection);
|
|||
|
|
|||
|
StateData *pSD = static_cast<StateData *>(pStateData);
|
|||
|
|
|||
|
if (!pSD->fFlushInAbort)
|
|||
|
{
|
|||
|
// For each object, flush all curves on each parameter up to the start time of the last one we sent.
|
|||
|
// (This allows the DMO being controlled to free up memory associated with any previous curves
|
|||
|
// while still keeping the last one around so that the next thing played picks up that parameter
|
|||
|
// value how it was left.)
|
|||
|
// Then release the object's params interface.
|
|||
|
int iObj = 0;
|
|||
|
for (TListItem<ObjectInfo> *pObject = m_listObjects.GetHead();
|
|||
|
pObject && iObj < m_cObjects;
|
|||
|
pObject = pObject->GetNext(), ++iObj)
|
|||
|
{
|
|||
|
IMediaParams *pIMediaParams = pSD->prgpIMediaParams[iObj];
|
|||
|
if (pIMediaParams)
|
|||
|
{
|
|||
|
ObjectInfo &obj = pObject->GetItemValue();
|
|||
|
int iParam = 0;
|
|||
|
for (TListItem<ParamInfo> *pParam = obj.listParams.GetHead();
|
|||
|
pParam && iParam < m_cParams;
|
|||
|
pParam = pParam->GetNext(), ++iParam)
|
|||
|
{
|
|||
|
ParamInfo ¶m = pParam->GetItemValue();
|
|||
|
ParamState ¶mstate = pSD->prgParam[iParam];
|
|||
|
if (paramstate.fLast)
|
|||
|
{
|
|||
|
HRESULT hrFlush = pIMediaParams->FlushEnvelope(param.header.dwIndex, _I64_MIN, paramstate.rtStartPointOfLastCurve);
|
|||
|
if (FAILED(hrFlush))
|
|||
|
{
|
|||
|
assert(false);
|
|||
|
TraceI(1, "Unable to flush envelope information from an audio path object in parameter control track, HRESULT 0x%08x.\n", hrFlush);
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
SafeRelease(pIMediaParams);
|
|||
|
}
|
|||
|
}
|
|||
|
delete[] pSD->prgpIMediaParams;
|
|||
|
delete[] pSD->prgParam;
|
|||
|
delete pSD;
|
|||
|
|
|||
|
return S_OK;
|
|||
|
}
|
|||
|
|
|||
|
HRESULT CParamControlTrack::OnSegmentEnd(REFERENCE_TIME rtEnd, void *pStateData)
|
|||
|
{
|
|||
|
SmartRef::CritSec CS(&m_CriticalSection);
|
|||
|
|
|||
|
StateData *pSD = static_cast<StateData *>(pStateData);
|
|||
|
|
|||
|
// For each object, flush all curves on each parameter up to the start time of the last one we sent
|
|||
|
// (if that started before segment end) or flush everything up to the last one to start before
|
|||
|
// segment end, and flush everything after segment end (if the start time was after segment end).
|
|||
|
// (This allows the DMO being controlled to free up memory associated with any previous curves
|
|||
|
// while still keeping the last one around so that the next thing played picks up that parameter
|
|||
|
// value how it was left.)
|
|||
|
// Then release the object's params interface.
|
|||
|
int iObj = 0;
|
|||
|
for (TListItem<ObjectInfo> *pObject = m_listObjects.GetHead();
|
|||
|
pObject && iObj < m_cObjects;
|
|||
|
pObject = pObject->GetNext(), ++iObj)
|
|||
|
{
|
|||
|
IMediaParams *pIMediaParams = pSD->prgpIMediaParams[iObj];
|
|||
|
if (pIMediaParams)
|
|||
|
{
|
|||
|
ObjectInfo &obj = pObject->GetItemValue();
|
|||
|
int iParam = 0;
|
|||
|
for (TListItem<ParamInfo> *pParam = obj.listParams.GetHead();
|
|||
|
pParam && iParam < m_cParams;
|
|||
|
pParam = pParam->GetNext(), ++iParam)
|
|||
|
{
|
|||
|
ParamInfo ¶m = pParam->GetItemValue();
|
|||
|
ParamState ¶mstate = pSD->prgParam[iParam];
|
|||
|
if (paramstate.fLast)
|
|||
|
{
|
|||
|
HRESULT hrFlush = S_OK;
|
|||
|
if (paramstate.rtStartPointOfLastCurve < rtEnd)
|
|||
|
{
|
|||
|
hrFlush = pIMediaParams->FlushEnvelope(param.header.dwIndex, _I64_MIN, paramstate.rtStartPointOfLastCurve);
|
|||
|
}
|
|||
|
else
|
|||
|
{
|
|||
|
// first, look for the largest start time less than rtEnd and
|
|||
|
// flush up to there. The loop assumes the list is ordered largest to smallest.
|
|||
|
TListItem<REFERENCE_TIME>* pStartTime = paramstate.listStartTimes.GetHead();
|
|||
|
for (; pStartTime; pStartTime = pStartTime->GetNext())
|
|||
|
{
|
|||
|
if (pStartTime->GetItemValue() < rtEnd)
|
|||
|
{
|
|||
|
hrFlush = pIMediaParams->FlushEnvelope(param.header.dwIndex, _I64_MIN, pStartTime->GetItemValue());
|
|||
|
break;
|
|||
|
}
|
|||
|
}
|
|||
|
// Then, flush from rtEnd on.
|
|||
|
if (SUCCEEDED(hrFlush))
|
|||
|
{
|
|||
|
hrFlush = pIMediaParams->FlushEnvelope(param.header.dwIndex, rtEnd, _I64_MAX);
|
|||
|
}
|
|||
|
}
|
|||
|
if (FAILED(hrFlush))
|
|||
|
{
|
|||
|
assert(false);
|
|||
|
TraceI(1, "Unable to flush envelope information from an audio path object in parameter control track, HRESULT 0x%08x.\n", hrFlush);
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
SafeRelease(pIMediaParams);
|
|||
|
pSD->prgpIMediaParams[iObj] = NULL;
|
|||
|
}
|
|||
|
|
|||
|
pSD->fFlushInAbort = true;
|
|||
|
|
|||
|
return S_OK;
|
|||
|
}
|
|||
|
|
|||
|
STDMETHODIMP
|
|||
|
CParamControlTrack::Clone(MUSIC_TIME mtStart, MUSIC_TIME mtEnd, IDirectMusicTrack** ppTrack)
|
|||
|
{
|
|||
|
// <20><> Test more thoroughly when we have multiple working params/objects.
|
|||
|
|
|||
|
V_INAME(CParamControlTrack::Clone);
|
|||
|
V_PTRPTR_WRITE(ppTrack);
|
|||
|
|
|||
|
SmartRef::CritSec CS(&m_CriticalSection);
|
|||
|
|
|||
|
HRESULT hr = S_OK;
|
|||
|
|
|||
|
SmartRef::ComPtr<CParamControlTrack> scomTrack = new CParamControlTrack(&hr);
|
|||
|
if (FAILED(hr))
|
|||
|
return hr;
|
|||
|
if (!scomTrack)
|
|||
|
return E_OUTOFMEMORY;
|
|||
|
scomTrack->AddRef();
|
|||
|
|
|||
|
// Copy each object
|
|||
|
for (TListItem<ObjectInfo> *pObject = m_listObjects.GetHead();
|
|||
|
pObject;
|
|||
|
pObject = pObject->GetNext())
|
|||
|
{
|
|||
|
ObjectInfo &obj = pObject->GetItemValue();
|
|||
|
TListItem<ObjectInfo> *pNewObject = new TListItem<ObjectInfo>;
|
|||
|
if (!pNewObject)
|
|||
|
return E_OUTOFMEMORY;
|
|||
|
ObjectInfo &newobj = pNewObject->GetItemValue();
|
|||
|
newobj.header = obj.header;
|
|||
|
|
|||
|
// Copy each parameter
|
|||
|
for (TListItem<ParamInfo> *pParam = obj.listParams.GetHead();
|
|||
|
pParam;
|
|||
|
pParam = pParam->GetNext())
|
|||
|
{
|
|||
|
ParamInfo ¶m = pParam->GetItemValue();
|
|||
|
TListItem<ParamInfo> *pNewParam = new TListItem<ParamInfo>;
|
|||
|
if (!pNewParam)
|
|||
|
return E_OUTOFMEMORY;
|
|||
|
ParamInfo &newparam = pNewParam->GetItemValue();
|
|||
|
newparam.header = param.header;
|
|||
|
|
|||
|
// Copy the curves from mtStart to mtEnd
|
|||
|
// These should include curves that overlap the start and end, though this
|
|||
|
// leave some issues we still need to work out (what happens with overlapping curves?)
|
|||
|
// So, first find the first curve whose end time is at or after mtStart...
|
|||
|
for (DMUS_IO_PARAMCONTROLTRACK_CURVEINFO *pCurveStart = param.curves;
|
|||
|
(pCurveStart < param.curvesEnd) && (pCurveStart->mtEndTime < mtStart);
|
|||
|
++pCurveStart)
|
|||
|
{}
|
|||
|
// Then, find the curve whose start time is after mtEnd.
|
|||
|
for (DMUS_IO_PARAMCONTROLTRACK_CURVEINFO *pCurveEnd = pCurveStart;
|
|||
|
(pCurveEnd < param.curvesEnd) && (pCurveEnd->mtStartTime < mtEnd);
|
|||
|
++pCurveEnd)
|
|||
|
{}
|
|||
|
int cCurves = (int)(pCurveEnd - pCurveStart);
|
|||
|
newparam.curves = new DMUS_IO_PARAMCONTROLTRACK_CURVEINFO[cCurves];
|
|||
|
if (!newparam.curves)
|
|||
|
return E_OUTOFMEMORY;
|
|||
|
memcpy(newparam.curves, pCurveStart, cCurves * sizeof(DMUS_IO_PARAMCONTROLTRACK_CURVEINFO));
|
|||
|
newparam.curvesEnd = newparam.curves + cCurves;
|
|||
|
// Now, scan through the new curve array and adjust the times by subtracting mtStart from everything.
|
|||
|
for (pCurveStart = newparam.curves; pCurveStart < newparam.curvesEnd; pCurveStart++)
|
|||
|
{
|
|||
|
pCurveStart->mtStartTime -= mtStart;
|
|||
|
pCurveStart->mtEndTime -= mtStart;
|
|||
|
}
|
|||
|
|
|||
|
newobj.listParams.AddHead(pNewParam);
|
|||
|
}
|
|||
|
|
|||
|
newobj.listParams.Reverse(); // Technically, the order shouldn't matter. But this ensures that the cloned track will send curves to different parameters in the exact same order just in case.
|
|||
|
scomTrack->m_listObjects.AddHead(pNewObject);
|
|||
|
}
|
|||
|
scomTrack->m_listObjects.Reverse(); // Technically, the order shouldn't matter. But this ensures that the cloned track will send curves to different objects in the exact same order just in case.
|
|||
|
++scomTrack->m_dwValidate;
|
|||
|
|
|||
|
scomTrack->m_cObjects = m_cObjects;
|
|||
|
scomTrack->m_cParams = m_cParams;
|
|||
|
|
|||
|
*ppTrack = scomTrack.disown();
|
|||
|
return hr;
|
|||
|
}
|
|||
|
|
|||
|
HRESULT
|
|||
|
CParamControlTrack::PlayMusicOrClock(
|
|||
|
void *pStateData,
|
|||
|
MUSIC_TIME mtStart,
|
|||
|
MUSIC_TIME mtEnd,
|
|||
|
MUSIC_TIME mtOffset,
|
|||
|
REFERENCE_TIME rtOffset,
|
|||
|
DWORD dwFlags,
|
|||
|
IDirectMusicPerformance* pPerf,
|
|||
|
IDirectMusicSegmentState* pSegSt,
|
|||
|
DWORD dwVirtualID,
|
|||
|
bool fClockTime)
|
|||
|
{
|
|||
|
V_INAME(CParamControlTrack::Play);
|
|||
|
V_BUFPTR_WRITE( pStateData, sizeof(StateData));
|
|||
|
V_INTERFACE(pPerf);
|
|||
|
V_INTERFACE(pSegSt);
|
|||
|
|
|||
|
if (dwFlags & DMUS_TRACKF_PLAY_OFF)
|
|||
|
return S_OK;
|
|||
|
|
|||
|
SmartRef::CritSec CS(&m_CriticalSection);
|
|||
|
|
|||
|
StateData *pSD = static_cast<StateData *>(pStateData);
|
|||
|
|
|||
|
if (m_dwValidate != pSD->dwValidate)
|
|||
|
{
|
|||
|
HRESULT hr = InitStateData(pSD, pSegSt);
|
|||
|
if (FAILED(hr))
|
|||
|
{
|
|||
|
return hr;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
// envelope structure we'll fill for sending each envelope segment.
|
|||
|
MP_ENVELOPE_SEGMENT envCurve;
|
|||
|
Zero(&envCurve);
|
|||
|
MP_ENVELOPE_SEGMENT *const penvCurve = &envCurve;
|
|||
|
|
|||
|
bool fMoreCurves = false; // set to true by any parameter that has more curves to play
|
|||
|
|
|||
|
// for each parameter...
|
|||
|
int iParam = 0;
|
|||
|
int iObject = 0;
|
|||
|
for (TListItem<ObjectInfo> *pObject = m_listObjects.GetHead();
|
|||
|
pObject && iObject < m_cObjects;
|
|||
|
pObject = pObject->GetNext(), ++iObject)
|
|||
|
{
|
|||
|
ObjectInfo &obj = pObject->GetItemValue();
|
|||
|
IMediaParams *pIMediaParams = pSD->prgpIMediaParams[iObject];
|
|||
|
|
|||
|
bool fObjClockTime = !!(obj.header.guidTimeFormat == GUID_TIME_REFERENCE);
|
|||
|
if (!fObjClockTime && obj.header.guidTimeFormat != GUID_TIME_MUSIC)
|
|||
|
{
|
|||
|
// track can only handle music and clock time
|
|||
|
assert(false);
|
|||
|
// Only log this once at warning level one. Rest go to warning level three to avoid tons of identical trace messages during playback).
|
|||
|
TraceI(
|
|||
|
obj.fAlreadyTracedPlaybackError ? 3 : 1,
|
|||
|
"Parameter control track unable to control object -- unknown time format (must be GUID_TIME_MUSIC or GUID_TIME_REFERENCE).\n");
|
|||
|
obj.fAlreadyTracedPlaybackError = true;
|
|||
|
continue;
|
|||
|
}
|
|||
|
|
|||
|
for (TListItem<ParamInfo> *pParam = obj.listParams.GetHead();
|
|||
|
pParam && iParam < m_cParams;
|
|||
|
pParam = pParam->GetNext(), ++iParam)
|
|||
|
{
|
|||
|
ParamInfo ¶m = pParam->GetItemValue();
|
|||
|
ParamState ¶mstate = pSD->prgParam[iParam];
|
|||
|
|
|||
|
DMUS_IO_PARAMCONTROLTRACK_CURVEINFO *&pCurrentCurve = paramstate.pCurrentCurve;
|
|||
|
|
|||
|
// We're going to seek through the event list to find the proper next control curve for each parameter if
|
|||
|
// the track's data has been reloaded or if playback has made a jump to a different position in the track.
|
|||
|
if (m_dwValidate != pSD->dwValidate || dwFlags & (DMUS_TRACKF_SEEK | DMUS_TRACKF_LOOP | DMUS_TRACKF_FLUSH | DMUS_TRACKF_START))
|
|||
|
{
|
|||
|
assert(m_dwValidate != pSD->dwValidate || dwFlags & DMUS_TRACKF_SEEK); // by contract SEEK should be set whenever the other dwFlags are
|
|||
|
|
|||
|
// find first curve that begins at or after the start time we're currently playing
|
|||
|
for (pCurrentCurve = param.curves; pCurrentCurve < param.curvesEnd && pCurrentCurve->mtStartTime < mtStart; ++pCurrentCurve)
|
|||
|
{}
|
|||
|
|
|||
|
if (pIMediaParams && pCurrentCurve > param.curves)
|
|||
|
{
|
|||
|
// check the previous curve to see if we ended up in the middle of it
|
|||
|
DMUS_IO_PARAMCONTROLTRACK_CURVEINFO *pPrevCurve = pCurrentCurve - 1;
|
|||
|
// Send a curve chopped off at the start time we're currently playing.
|
|||
|
// We can't send the whole curve because it would take effect too early.
|
|||
|
HRESULT hrEnv = this->PlayTruncatedEnvelope(mtStart, pIMediaParams, penvCurve, pPrevCurve, obj, param, paramstate, mtOffset, rtOffset, pPerf, fClockTime, fObjClockTime, dwFlags);
|
|||
|
if (FAILED(hrEnv))
|
|||
|
{
|
|||
|
// Can't fail from Play. Just assert and print trace information.
|
|||
|
assert(false);
|
|||
|
// Only log this once at warning level one. Rest go to warning level three to avoid tons of identical trace messages during playback).
|
|||
|
TraceI(
|
|||
|
param.fAlreadyTracedPlaybackError ? 3 : 1,
|
|||
|
"Unable to send envelope information to an audio path object in parameter control track, HRESULT 0x%08x.\n", hrEnv);
|
|||
|
param.fAlreadyTracedPlaybackError = true;
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
// Send curves until the next curve is after mtEnd
|
|||
|
for ( ; pCurrentCurve < param.curvesEnd; ++pCurrentCurve )
|
|||
|
{
|
|||
|
if (pCurrentCurve->mtStartTime < mtStart) // this can happen if DMUS_TRACKF_PLAY_OFF was set and the seek pointer remains at events from the past
|
|||
|
continue;
|
|||
|
if (pCurrentCurve->mtStartTime >= mtEnd)
|
|||
|
break;
|
|||
|
|
|||
|
// send this curve
|
|||
|
if (pIMediaParams)
|
|||
|
{
|
|||
|
HRESULT hrEnv = this->PlayEnvelope(pIMediaParams, penvCurve, pCurrentCurve, obj, param, paramstate, mtOffset, rtOffset, pPerf, fClockTime, fObjClockTime);
|
|||
|
if (FAILED(hrEnv))
|
|||
|
{
|
|||
|
// Can't fail from Play. Just assert and print trace information.
|
|||
|
assert(false);
|
|||
|
// Only log this once at warning level one. Rest go to warning level three to avoid tons of identical trace messages during playback).
|
|||
|
TraceI(
|
|||
|
param.fAlreadyTracedPlaybackError ? 3 : 1,
|
|||
|
"Unable to send envelope information to an audio path object in parameter control track, HRESULT 0x%08x.\n", hrEnv);
|
|||
|
param.fAlreadyTracedPlaybackError = true;
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
if (pCurrentCurve < param.curvesEnd)
|
|||
|
fMoreCurves = true;
|
|||
|
}
|
|||
|
assert(!pParam); // we should have gotten all the way through this param list
|
|||
|
}
|
|||
|
assert(!pObject && iParam == m_cParams && iObject == m_cObjects); // we should have gotten all the way through the object list and done the expected number of objects and parameters
|
|||
|
|
|||
|
pSD->dwValidate = m_dwValidate; // if we weren't in sync with new track data before, we are now
|
|||
|
return fMoreCurves ? S_OK : DMUS_S_END;
|
|||
|
}
|
|||
|
|
|||
|
HRESULT CParamControlTrack::LoadObject(SmartRef::RiffIter ri)
|
|||
|
{
|
|||
|
if (!ri)
|
|||
|
return ri.hr();
|
|||
|
|
|||
|
HRESULT hr = S_OK;
|
|||
|
|
|||
|
SmartRef::Ptr<TListItem<ObjectInfo> > spItem = new TListItem<ObjectInfo>;
|
|||
|
if (!spItem)
|
|||
|
return E_OUTOFMEMORY;
|
|||
|
ObjectInfo &ritem = spItem->GetItemValue();
|
|||
|
|
|||
|
// find <proh>
|
|||
|
hr = ri.FindRequired(SmartRef::RiffIter::Chunk, DMUS_FOURCC_PARAMCONTROLTRACK_OBJECT_CHUNK, DMUS_E_INVALID_PARAMCONTROLTRACK);
|
|||
|
if (FAILED(hr))
|
|||
|
{
|
|||
|
#ifdef DBG
|
|||
|
if (hr == DMUS_E_INVALID_PARAMCONTROLTRACK)
|
|||
|
{
|
|||
|
Trace(1, "Error: Unable to load parameter control track: Chunk 'proh' not found.\n");
|
|||
|
}
|
|||
|
#endif
|
|||
|
return hr;
|
|||
|
}
|
|||
|
|
|||
|
hr = SmartRef::RiffIterReadChunk(ri, &ritem.header);
|
|||
|
if (FAILED(hr))
|
|||
|
return hr;
|
|||
|
if (!(ritem.header.guidTimeFormat == GUID_TIME_MUSIC || ritem.header.guidTimeFormat == GUID_TIME_REFERENCE))
|
|||
|
{
|
|||
|
Trace(1, "Error: Unable to load parameter control track: guidTimeFormat in chunk 'proh' must be either GUID_TIME_MUSIC or GUID_TIME_REFERENCE.\n");
|
|||
|
return DMUS_E_INVALID_PARAMCONTROLTRACK;
|
|||
|
}
|
|||
|
|
|||
|
// for each <prpl>
|
|||
|
while (ri && ri.Find(SmartRef::RiffIter::List, DMUS_FOURCC_PARAMCONTROLTRACK_PARAM_LIST))
|
|||
|
{
|
|||
|
hr = this->LoadParam(ri.Descend(), ritem.listParams);
|
|||
|
if (FAILED(hr))
|
|||
|
return hr;
|
|||
|
++ri;
|
|||
|
}
|
|||
|
hr = ri.hr();
|
|||
|
|
|||
|
if (SUCCEEDED(hr))
|
|||
|
{
|
|||
|
m_listObjects.AddHead(spItem.disown());
|
|||
|
++m_cObjects;
|
|||
|
}
|
|||
|
return hr;
|
|||
|
}
|
|||
|
|
|||
|
HRESULT CParamControlTrack::LoadParam(SmartRef::RiffIter ri, TList<ParamInfo> &listParams)
|
|||
|
{
|
|||
|
if (!ri)
|
|||
|
return ri.hr();
|
|||
|
|
|||
|
HRESULT hr = S_OK;
|
|||
|
|
|||
|
SmartRef::Ptr<TListItem<ParamInfo> > spItem = new TListItem<ParamInfo>;
|
|||
|
if (!spItem)
|
|||
|
return E_OUTOFMEMORY;
|
|||
|
ParamInfo &ritem = spItem->GetItemValue();
|
|||
|
|
|||
|
// find <prph>
|
|||
|
hr = ri.FindRequired(SmartRef::RiffIter::Chunk, DMUS_FOURCC_PARAMCONTROLTRACK_PARAM_CHUNK, DMUS_E_INVALID_PARAMCONTROLTRACK);
|
|||
|
if (FAILED(hr))
|
|||
|
{
|
|||
|
#ifdef DBG
|
|||
|
if (hr == DMUS_E_INVALID_PARAMCONTROLTRACK)
|
|||
|
{
|
|||
|
Trace(1, "Error: Unable to load parameter control track: Chunk 'prph' not found.\n");
|
|||
|
}
|
|||
|
#endif
|
|||
|
return hr;
|
|||
|
}
|
|||
|
|
|||
|
hr = SmartRef::RiffIterReadChunk(ri, &ritem.header);
|
|||
|
if (FAILED(hr))
|
|||
|
return hr;
|
|||
|
|
|||
|
// find <prcc>
|
|||
|
if (!ri.Find(SmartRef::RiffIter::Chunk, DMUS_FOURCC_PARAMCONTROLTRACK_CURVES_CHUNK))
|
|||
|
{
|
|||
|
// It is OK if we read to the end without finding the chunk--we succeed without finding any curves.
|
|||
|
// Or it could be a failure because there was a problem reading from the stream.
|
|||
|
// The RiffIter's hr method reflects this.
|
|||
|
return ri.hr();
|
|||
|
}
|
|||
|
|
|||
|
// read the array of control curves
|
|||
|
int cRecords;
|
|||
|
hr = SmartRef::RiffIterReadArrayChunk(ri, &ritem.curves, &cRecords);
|
|||
|
if (FAILED(hr))
|
|||
|
return hr;
|
|||
|
ritem.curvesEnd = ritem.curves + cRecords;
|
|||
|
|
|||
|
listParams.AddHead(spItem.disown());
|
|||
|
++m_cParams;
|
|||
|
return hr;
|
|||
|
}
|
|||
|
|
|||
|
HRESULT CParamControlTrack::TrackToObjectTime(
|
|||
|
MUSIC_TIME mtOffset,
|
|||
|
REFERENCE_TIME rtOffset,
|
|||
|
IDirectMusicPerformance* pPerf,
|
|||
|
bool fTrkClockTime,
|
|||
|
bool fObjClockTime,
|
|||
|
MUSIC_TIME mt,
|
|||
|
REFERENCE_TIME *rt)
|
|||
|
{
|
|||
|
HRESULT hr = S_OK;
|
|||
|
|
|||
|
// set the time (reference time variable is used to hold either music or reference time in different contexts)
|
|||
|
REFERENCE_TIME rtEnv = mt;
|
|||
|
|
|||
|
// add the correct offset and if necessary convert from millisecond time
|
|||
|
rtEnv = fTrkClockTime
|
|||
|
? rtEnv * gc_RefPerMil + rtOffset
|
|||
|
: rtEnv = rtEnv + mtOffset;
|
|||
|
|
|||
|
if (fTrkClockTime != fObjClockTime)
|
|||
|
{
|
|||
|
// need to convert between out track's time format and the audio object's time format
|
|||
|
if (fObjClockTime)
|
|||
|
{
|
|||
|
MUSIC_TIME mtEnv = static_cast<MUSIC_TIME>(rtEnv);
|
|||
|
hr = pPerf->MusicToReferenceTime(mtEnv, &rtEnv);
|
|||
|
if (FAILED(hr))
|
|||
|
return hr;
|
|||
|
}
|
|||
|
else
|
|||
|
{
|
|||
|
MUSIC_TIME mtEnv = 0;
|
|||
|
hr = pPerf->ReferenceToMusicTime(rtEnv, &mtEnv);
|
|||
|
rtEnv = mtEnv;
|
|||
|
if (FAILED(hr))
|
|||
|
return hr;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
*rt = rtEnv;
|
|||
|
return hr;
|
|||
|
}
|
|||
|
|
|||
|
HRESULT
|
|||
|
CParamControlTrack::PlayEnvelope(
|
|||
|
IMediaParams *pIMediaParams,
|
|||
|
MP_ENVELOPE_SEGMENT *pEnv,
|
|||
|
DMUS_IO_PARAMCONTROLTRACK_CURVEINFO *pPt,
|
|||
|
const ObjectInfo &obj,
|
|||
|
const ParamInfo ¶m,
|
|||
|
ParamState ¶mstate,
|
|||
|
MUSIC_TIME mtOffset,
|
|||
|
REFERENCE_TIME rtOffset,
|
|||
|
IDirectMusicPerformance* pPerf,
|
|||
|
bool fTrkClockTime,
|
|||
|
bool fObjClockTime)
|
|||
|
{
|
|||
|
HRESULT hr = S_OK;
|
|||
|
|
|||
|
// set the curve type and flags
|
|||
|
pEnv->iCurve = static_cast<MP_CURVE_TYPE>(pPt->dwCurveType);
|
|||
|
pEnv->flags = pPt->dwFlags;
|
|||
|
|
|||
|
pEnv->valEnd = pPt->fltEndValue;
|
|||
|
pEnv->valStart = pPt->fltStartValue;
|
|||
|
|
|||
|
// set the time (used to hold either music or reference time in different contexts)
|
|||
|
|
|||
|
REFERENCE_TIME &rtEnvStart = pEnv->rtStart;
|
|||
|
hr = this->TrackToObjectTime(mtOffset, rtOffset, pPerf, fTrkClockTime, fObjClockTime, pPt->mtStartTime, &rtEnvStart);
|
|||
|
if (FAILED(hr))
|
|||
|
return hr;
|
|||
|
|
|||
|
REFERENCE_TIME &rtEnvEnd = pEnv->rtEnd;
|
|||
|
hr = this->TrackToObjectTime(mtOffset, rtOffset, pPerf, fTrkClockTime, fObjClockTime, pPt->mtEndTime, &rtEnvEnd);
|
|||
|
if (FAILED(hr))
|
|||
|
return hr;
|
|||
|
|
|||
|
hr = pIMediaParams->AddEnvelope(param.header.dwIndex, 1, pEnv);
|
|||
|
if (SUCCEEDED(hr))
|
|||
|
{
|
|||
|
paramstate.rtStartPointOfLastCurve = rtEnvStart;
|
|||
|
TListItem<REFERENCE_TIME>* pStartTime = new TListItem<REFERENCE_TIME>;
|
|||
|
if (pStartTime)
|
|||
|
{
|
|||
|
pStartTime->GetItemValue() = rtEnvStart;
|
|||
|
// Adding to the head maintains a largest-to-smallest ordering.
|
|||
|
paramstate.listStartTimes.AddHead(pStartTime);
|
|||
|
}
|
|||
|
paramstate.fLast = true;
|
|||
|
}
|
|||
|
|
|||
|
return hr;
|
|||
|
}
|
|||
|
|
|||
|
HRESULT
|
|||
|
CParamControlTrack::PlayTruncatedEnvelope(
|
|||
|
MUSIC_TIME mtTruncStart,
|
|||
|
IMediaParams *pIMediaParams,
|
|||
|
MP_ENVELOPE_SEGMENT *pEnv,
|
|||
|
DMUS_IO_PARAMCONTROLTRACK_CURVEINFO *pPt,
|
|||
|
const ObjectInfo &obj,
|
|||
|
const ParamInfo ¶m,
|
|||
|
ParamState ¶mstate,
|
|||
|
MUSIC_TIME mtOffset,
|
|||
|
REFERENCE_TIME rtOffset,
|
|||
|
IDirectMusicPerformance* pPerf,
|
|||
|
bool fTrkClockTime,
|
|||
|
bool fObjClockTime,
|
|||
|
DWORD dwFlags)
|
|||
|
{
|
|||
|
// Copy info from the curve
|
|||
|
DMUS_IO_PARAMCONTROLTRACK_CURVEINFO curveinfo = *pPt;
|
|||
|
// Cut the start to the designated time
|
|||
|
curveinfo.mtStartTime = mtTruncStart;
|
|||
|
bool fSkip = false;
|
|||
|
|
|||
|
if (mtTruncStart >= curveinfo.mtEndTime)
|
|||
|
{
|
|||
|
// Curve happened in the past. Send a jump curve right at the current (truncate) time picking up with
|
|||
|
// that value.
|
|||
|
// if we're looping and we passed the end of this curve, just skip it.
|
|||
|
if ( (dwFlags & DMUS_TRACKF_LOOP) )
|
|||
|
{
|
|||
|
fSkip = true;
|
|||
|
}
|
|||
|
else
|
|||
|
{
|
|||
|
curveinfo.mtEndTime = mtTruncStart;
|
|||
|
curveinfo.dwCurveType = MP_CURVE_JUMP;
|
|||
|
}
|
|||
|
}
|
|||
|
else if (pPt->dwCurveType != MP_CURVE_JUMP)
|
|||
|
{
|
|||
|
// Find the point at that time and pick up with a linear curve from there.
|
|||
|
// (For the nonlinear curves, there's no way to pick them up part-way along.)
|
|||
|
curveinfo.dwCurveType = MP_CURVE_LINEAR;
|
|||
|
|
|||
|
MUSIC_TIME mtTimeChange = pPt->mtEndTime - pPt->mtStartTime;
|
|||
|
MUSIC_TIME mtTimeIntermediate = mtTruncStart - pPt->mtStartTime;
|
|||
|
|
|||
|
float fltScalingX = static_cast<float>(mtTimeIntermediate) / mtTimeChange; // horizontal distance along curve between 0 and 1
|
|||
|
float fltScalingY; // height of curve at that point between 0 and 1 based on curve function
|
|||
|
switch (pPt->dwCurveType)
|
|||
|
{
|
|||
|
case MP_CURVE_SQUARE:
|
|||
|
fltScalingY = fltScalingX * fltScalingX;
|
|||
|
break;
|
|||
|
case MP_CURVE_INVSQUARE:
|
|||
|
fltScalingY = (float) sqrt(fltScalingX);
|
|||
|
break;
|
|||
|
case MP_CURVE_SINE:
|
|||
|
// <20><> Maybe we should have a lookup table here?
|
|||
|
fltScalingY = (float) (sin(fltScalingX * 3.1415926535 - (3.1415926535/2)) + 1) / 2;
|
|||
|
break;
|
|||
|
case MP_CURVE_LINEAR:
|
|||
|
default:
|
|||
|
fltScalingY = fltScalingX;
|
|||
|
}
|
|||
|
|
|||
|
// Apply that scaling to the range of the actual points
|
|||
|
curveinfo.fltStartValue = (pPt->fltEndValue - pPt->fltStartValue) * fltScalingY + pPt->fltStartValue;
|
|||
|
}
|
|||
|
|
|||
|
if (fSkip) return S_OK;
|
|||
|
|
|||
|
return this->PlayEnvelope(pIMediaParams, pEnv, &curveinfo, obj, param, paramstate, mtOffset, rtOffset, pPerf, fTrkClockTime, fObjClockTime);
|
|||
|
}
|
|||
|
|
|||
|
HRESULT CParamControlTrack::InitStateData(StateData *pStateData,
|
|||
|
IDirectMusicSegmentState *pSegmentState)
|
|||
|
{
|
|||
|
if (pStateData->prgpIMediaParams)
|
|||
|
{
|
|||
|
delete [] pStateData->prgpIMediaParams;
|
|||
|
pStateData->prgpIMediaParams = NULL;
|
|||
|
}
|
|||
|
if (pStateData->prgParam)
|
|||
|
{
|
|||
|
delete [] pStateData->prgParam;
|
|||
|
pStateData->prgParam = NULL;
|
|||
|
}
|
|||
|
|
|||
|
pStateData->prgpIMediaParams = new IMediaParams *[m_cObjects];
|
|||
|
if (!pStateData->prgpIMediaParams)
|
|||
|
{
|
|||
|
return E_OUTOFMEMORY;
|
|||
|
}
|
|||
|
pStateData->prgParam = new ParamState[m_cParams];
|
|||
|
if (!pStateData->prgParam)
|
|||
|
{
|
|||
|
delete [] pStateData->prgpIMediaParams;
|
|||
|
return E_OUTOFMEMORY;
|
|||
|
}
|
|||
|
|
|||
|
// Get the IMediaParams interface for each object
|
|||
|
SmartRef::ComPtr<IDirectMusicSegmentState8> scomSegSt8;
|
|||
|
HRESULT hr = pSegmentState->QueryInterface(IID_IDirectMusicSegmentState8, reinterpret_cast<void**>(&scomSegSt8));
|
|||
|
if (FAILED(hr))
|
|||
|
{
|
|||
|
delete [] pStateData->prgParam;
|
|||
|
delete [] pStateData->prgpIMediaParams;
|
|||
|
return hr;
|
|||
|
}
|
|||
|
|
|||
|
int iObject = 0;
|
|||
|
for (TListItem<ObjectInfo> *pObject = m_listObjects.GetHead();
|
|||
|
pObject;
|
|||
|
pObject = pObject->GetNext(), ++iObject)
|
|||
|
{
|
|||
|
IMediaParams *pIMediaParams = NULL;
|
|||
|
ObjectInfo &rinfo = pObject->GetItemValue();
|
|||
|
HRESULT hrObject = scomSegSt8->GetObjectInPath(
|
|||
|
rinfo.header.dwPChannel,
|
|||
|
rinfo.header.dwStage,
|
|||
|
rinfo.header.dwBuffer,
|
|||
|
rinfo.header.guidObject,
|
|||
|
rinfo.header.dwIndex,
|
|||
|
IID_IMediaParams,
|
|||
|
reinterpret_cast<void**>(&pIMediaParams));
|
|||
|
if (FAILED(hrObject))
|
|||
|
{
|
|||
|
// Can't fail from InitPlay (and this is called from there).
|
|||
|
// Just print trace information.
|
|||
|
TraceI(1, "Parameter control track was unable to find audio path object, HRESULT 0x%08x.\n", hrObject);
|
|||
|
}
|
|||
|
else
|
|||
|
{
|
|||
|
hrObject = pIMediaParams->SetTimeFormat(rinfo.header.guidTimeFormat, rinfo.header.guidTimeFormat == GUID_TIME_MUSIC ? 768 : 0);
|
|||
|
}
|
|||
|
if (FAILED(hrObject))
|
|||
|
{
|
|||
|
// Can't fail from InitPlay (and this is called from there).
|
|||
|
// Just print trace information.
|
|||
|
Trace(1, "Unable to set time format of object in parameter control track, HRESULT 0x%08x.\n", hrObject);
|
|||
|
}
|
|||
|
if (FAILED(hrObject))
|
|||
|
{
|
|||
|
SafeRelease(pIMediaParams);
|
|||
|
}
|
|||
|
pStateData->prgpIMediaParams[iObject] = pIMediaParams;
|
|||
|
}
|
|||
|
|
|||
|
return S_OK;
|
|||
|
}
|