986 lines
28 KiB
C++
986 lines
28 KiB
C++
// Copyright (c) 1998-1999 Microsoft Corporation
|
|
// READ THIS!!!!!!!!!!!!!!!!!!!!!!!!!!!
|
|
//
|
|
// 4530: C++ exception handler used, but unwind semantics are not enabled. Specify -GX
|
|
//
|
|
// We disable this because we use exceptions and do *not* specify -GX (USE_NATIVE_EH in
|
|
// sources).
|
|
//
|
|
// The one place we use exceptions is around construction of objects that call
|
|
// InitializeCriticalSection. We guarantee that it is safe to use in this case with
|
|
// the restriction given by not using -GX (automatic objects in the call chain between
|
|
// throw and handler are not destructed). Turning on -GX buys us nothing but +10% to code
|
|
// size because of the unwind code.
|
|
//
|
|
// Any other use of exceptions must follow these restrictions or -GX must be turned on.
|
|
//
|
|
// READ THIS!!!!!!!!!!!!!!!!!!!!!!!!!!!
|
|
//
|
|
#pragma warning(disable:4530)
|
|
|
|
// TempoTrk.cpp : Implementation of CTempoTrack
|
|
#include "dmime.h"
|
|
#include "TempoTrk.h"
|
|
#include "dmusici.h"
|
|
#include "dmusicf.h"
|
|
#include "debug.h"
|
|
#include "dmperf.h"
|
|
#include "..\shared\Validate.h"
|
|
#include "debug.h"
|
|
#define ASSERT assert
|
|
|
|
/////////////////////////////////////////////////////////////////////////////
|
|
// CTempoTrack
|
|
|
|
void CTempoTrack::Construct()
|
|
{
|
|
InterlockedIncrement(&g_cComponent);
|
|
|
|
m_cRef = 1;
|
|
m_dwValidate = 0;
|
|
m_fCSInitialized = FALSE;
|
|
InitializeCriticalSection(&m_CrSec);
|
|
m_fCSInitialized = TRUE;
|
|
}
|
|
|
|
CTempoTrack::CTempoTrack()
|
|
{
|
|
Construct();
|
|
m_fActive = TRUE;
|
|
m_fStateSetBySetParam = FALSE;
|
|
}
|
|
|
|
CTempoTrack::CTempoTrack(
|
|
const CTempoTrack& rTrack, MUSIC_TIME mtStart, MUSIC_TIME mtEnd)
|
|
{
|
|
Construct();
|
|
m_fActive = rTrack.m_fActive;
|
|
m_fStateSetBySetParam = rTrack.m_fStateSetBySetParam;
|
|
TListItem<DMUS_IO_TEMPO_ITEM>* pScan = rTrack.m_TempoEventList.GetHead();
|
|
//1////////////////////////////////////////
|
|
TListItem<DMUS_IO_TEMPO_ITEM>* pPrevious = NULL;
|
|
//1////////////////////////////////////////
|
|
for(; pScan; pScan = pScan->GetNext())
|
|
{
|
|
DMUS_IO_TEMPO_ITEM& rScan = pScan->GetItemValue();
|
|
//2////////////////////////////////////////
|
|
if (rScan.lTime < mtStart)
|
|
{
|
|
pPrevious = pScan;
|
|
}
|
|
//2////////////////////////////////////////
|
|
else if (rScan.lTime < mtEnd)
|
|
{
|
|
//3////////////////////////////////////////
|
|
if (rScan.lTime == mtStart)
|
|
{
|
|
pPrevious = NULL;
|
|
}
|
|
//3////////////////////////////////////////
|
|
TListItem<DMUS_IO_TEMPO_ITEM>* pNew = new TListItem<DMUS_IO_TEMPO_ITEM>;
|
|
if (pNew)
|
|
{
|
|
DMUS_IO_TEMPO_ITEM& rNew = pNew->GetItemValue();
|
|
memcpy( &rNew, &rScan, sizeof(DMUS_IO_TEMPO_ITEM) );
|
|
rNew.lTime = rScan.lTime - mtStart;
|
|
m_TempoEventList.AddHead(pNew); // instead of AddTail, which is n^2. We reverse below.
|
|
}
|
|
}
|
|
else break;
|
|
}
|
|
m_TempoEventList.Reverse(); // for above AddHead.
|
|
//4////////////////////////////////////////
|
|
if (pPrevious)
|
|
{
|
|
DMUS_IO_TEMPO_ITEM& rPrevious = pPrevious->GetItemValue();
|
|
TListItem<DMUS_IO_TEMPO_ITEM>* pNew = new TListItem<DMUS_IO_TEMPO_ITEM>;
|
|
if (pNew)
|
|
{
|
|
DMUS_IO_TEMPO_ITEM& rNew = pNew->GetItemValue();
|
|
memcpy( &rNew, &rPrevious, sizeof(DMUS_IO_TEMPO_ITEM) );
|
|
rNew.lTime = 0;
|
|
m_TempoEventList.AddHead(pNew);
|
|
}
|
|
}
|
|
//4////////////////////////////////////////
|
|
}
|
|
|
|
CTempoTrack::~CTempoTrack()
|
|
{
|
|
if (m_fCSInitialized)
|
|
{
|
|
DeleteCriticalSection(&m_CrSec);
|
|
}
|
|
InterlockedDecrement(&g_cComponent);
|
|
}
|
|
|
|
// @method:(INTERNAL) HRESULT | IDirectMusicTempoTrack | QueryInterface | Standard QueryInterface implementation for <i IDirectMusicTempoTrack>
|
|
//
|
|
// @parm const IID & | iid | Interface to query for
|
|
// @parm void ** | ppv | The requested interface will be returned here
|
|
//
|
|
// @rdesc Returns one of the following:
|
|
//
|
|
// @flag S_OK | If the interface is supported and was returned
|
|
// @flag E_NOINTERFACE | If the object does not support the given interface.
|
|
//
|
|
// @mfunc:(INTERNAL)
|
|
//
|
|
//
|
|
STDMETHODIMP CTempoTrack::QueryInterface(
|
|
const IID &iid, // @parm Interface to query for
|
|
void **ppv) // @parm The requested interface will be returned here
|
|
{
|
|
V_INAME(CTempoTrack::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
|
|
{
|
|
*ppv = NULL;
|
|
Trace(4,"Warning: Request to query unknown interface on Tempo Track\n");
|
|
return E_NOINTERFACE;
|
|
}
|
|
|
|
reinterpret_cast<IUnknown*>(this)->AddRef();
|
|
return S_OK;
|
|
}
|
|
|
|
|
|
// @method:(INTERNAL) HRESULT | IDirectMusicTempoTrack | AddRef | Standard AddRef implementation for <i IDirectMusicTempoTrack>
|
|
//
|
|
// @rdesc Returns the new reference count for this object.
|
|
//
|
|
// @mfunc:(INTERNAL)
|
|
//
|
|
//
|
|
STDMETHODIMP_(ULONG) CTempoTrack::AddRef()
|
|
{
|
|
return InterlockedIncrement(&m_cRef);
|
|
}
|
|
|
|
|
|
// @method:(INTERNAL) HRESULT | IDirectMusicTempoTrack | Release | Standard Release implementation for <i IDirectMusicTempoTrack>
|
|
//
|
|
// @rdesc Returns the new reference count for this object.
|
|
//
|
|
// @mfunc:(INTERNAL)
|
|
//
|
|
//
|
|
STDMETHODIMP_(ULONG) CTempoTrack::Release()
|
|
{
|
|
if (!InterlockedDecrement(&m_cRef))
|
|
{
|
|
delete this;
|
|
return 0;
|
|
}
|
|
|
|
return m_cRef;
|
|
}
|
|
|
|
/////////////////////////////////////////////////////////////////////////////
|
|
// IPersist
|
|
|
|
HRESULT CTempoTrack::GetClassID( CLSID* pClassID )
|
|
{
|
|
V_INAME(CTempoTrack::GetClassID);
|
|
V_PTR_WRITE(pClassID, CLSID);
|
|
*pClassID = CLSID_DirectMusicTempoTrack;
|
|
return S_OK;
|
|
}
|
|
|
|
/////////////////////////////////////////////////////////////////////////////
|
|
// IPersistStream functions
|
|
|
|
HRESULT CTempoTrack::IsDirty()
|
|
{
|
|
return S_FALSE;
|
|
}
|
|
|
|
/*
|
|
|
|
@method HRESULT | ITempoTrack | Load |
|
|
Call this with an IStream filled with DMUS_IO_TEMPO_ITEM's, sorted in time order.
|
|
@parm IStream* | pIStream |
|
|
A stream of DMUS_IO_TEMPO_ITEM's, sorted in time order. The seek pointer should be
|
|
set to the first event. The stream should only contain Tempo events and
|
|
nothing more.
|
|
@rvalue E_INVALIDARG | If pIStream == NULL
|
|
@rvalue S_OK
|
|
@comm The <p pIStream> will be AddRef'd inside this function and held
|
|
until the TempoTrack is released.
|
|
*/
|
|
HRESULT CTempoTrack::Load( IStream* pIStream )
|
|
{
|
|
V_INAME(CTempoTrack::Load);
|
|
V_INTERFACE(pIStream);
|
|
HRESULT hr = S_OK;
|
|
|
|
EnterCriticalSection(&m_CrSec);
|
|
m_dwValidate++; // used to validate state data that's out there
|
|
if( m_TempoEventList.GetHead() )
|
|
{
|
|
TListItem<DMUS_IO_TEMPO_ITEM>* pItem;
|
|
while( pItem = m_TempoEventList.RemoveHead() )
|
|
{
|
|
delete pItem;
|
|
}
|
|
}
|
|
|
|
// copy contents of the stream into the list.
|
|
LARGE_INTEGER li;
|
|
DMUS_IO_TEMPO_ITEM tempoEvent;
|
|
// read in the chunk id
|
|
DWORD dwChunk, dwSubSize;
|
|
long lSize;
|
|
pIStream->Read( &dwChunk, sizeof(DWORD), NULL );
|
|
if( dwChunk != DMUS_FOURCC_TEMPO_TRACK )
|
|
{
|
|
Trace(1,"Error: Invalid data in tempo track.\n");
|
|
LeaveCriticalSection(&m_CrSec);
|
|
return DMUS_E_CHUNKNOTFOUND;
|
|
}
|
|
// read in the overall size
|
|
pIStream->Read( &lSize, sizeof(long), NULL );
|
|
// read in the size of the data structures
|
|
if( FAILED( pIStream->Read( &dwSubSize, sizeof(DWORD), NULL )))
|
|
{
|
|
// Check to make sure our reads are succeeding (we can safely
|
|
// assume the previous reads worked if we got this far.)
|
|
Trace(1,"Error: Unable to read tempo track.\n");
|
|
LeaveCriticalSection(&m_CrSec);
|
|
return DMUS_E_CANNOTREAD;
|
|
}
|
|
lSize -= sizeof(DWORD);
|
|
|
|
DWORD dwRead, dwSeek;
|
|
if( dwSubSize > sizeof(DMUS_IO_TEMPO_ITEM) )
|
|
{
|
|
dwRead = sizeof(DMUS_IO_TEMPO_ITEM);
|
|
dwSeek = dwSubSize - dwRead;
|
|
li.HighPart = 0;
|
|
li.LowPart = dwSeek;
|
|
}
|
|
else
|
|
{
|
|
dwRead = dwSubSize;
|
|
dwSeek = 0;
|
|
}
|
|
if( dwRead )
|
|
{
|
|
while( lSize > 0 )
|
|
{
|
|
if( FAILED( pIStream->Read( &tempoEvent, dwRead, NULL )))
|
|
{
|
|
Trace(1,"Error: Failure reading tempo track.\n");
|
|
hr = DMUS_E_CANNOTREAD;
|
|
break;
|
|
}
|
|
lSize -= dwRead;
|
|
if( dwSeek )
|
|
{
|
|
if( FAILED( pIStream->Seek( li, STREAM_SEEK_CUR, NULL )))
|
|
{
|
|
Trace(1,"Error: Failure reading tempo track.\n");
|
|
hr = DMUS_E_CANNOTSEEK;
|
|
break;
|
|
}
|
|
lSize -= dwSeek;
|
|
}
|
|
TListItem<DMUS_IO_TEMPO_ITEM>* pNew =
|
|
new TListItem<DMUS_IO_TEMPO_ITEM>(tempoEvent);
|
|
if (pNew)
|
|
{
|
|
m_TempoEventList.AddHead(pNew); // instead of AddTail, which is n^2. We reverse below.
|
|
}
|
|
}
|
|
m_TempoEventList.Reverse();
|
|
}
|
|
else
|
|
{
|
|
Trace(1,"Error: Failure reading tempo track.\n");
|
|
hr = DMUS_E_CANNOTREAD;
|
|
}
|
|
LeaveCriticalSection(&m_CrSec);
|
|
return hr;
|
|
}
|
|
|
|
HRESULT CTempoTrack::Save( IStream* pIStream, BOOL fClearDirty )
|
|
{
|
|
return E_NOTIMPL;
|
|
}
|
|
|
|
HRESULT CTempoTrack::GetSizeMax( ULARGE_INTEGER FAR* pcbSize )
|
|
{
|
|
return E_NOTIMPL;
|
|
}
|
|
|
|
// IDirectMusicTrack
|
|
HRESULT STDMETHODCALLTYPE CTempoTrack::IsParamSupported(
|
|
/* [in] */ REFGUID rguid)
|
|
{
|
|
V_INAME(IDirectMusicTrack::IsParamSupported);
|
|
V_REFGUID(rguid);
|
|
|
|
if (m_fStateSetBySetParam)
|
|
{
|
|
if( m_fActive )
|
|
{
|
|
if( rguid == GUID_DisableTempo ) return S_OK;
|
|
if( rguid == GUID_TempoParam ) return S_OK;
|
|
if( rguid == GUID_PrivateTempoParam ) return S_OK;
|
|
if( rguid == GUID_EnableTempo ) return DMUS_E_TYPE_DISABLED;
|
|
}
|
|
else
|
|
{
|
|
if( rguid == GUID_EnableTempo ) return S_OK;
|
|
if( rguid == GUID_DisableTempo ) return DMUS_E_TYPE_DISABLED;
|
|
if( rguid == GUID_PrivateTempoParam ) return DMUS_E_TYPE_DISABLED;
|
|
if( rguid == GUID_TempoParam ) return DMUS_E_TYPE_DISABLED;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if(( rguid == GUID_DisableTempo ) ||
|
|
( rguid == GUID_TempoParam ) ||
|
|
( rguid == GUID_PrivateTempoParam ) ||
|
|
( rguid == GUID_EnableTempo )) return S_OK;
|
|
}
|
|
|
|
return DMUS_E_TYPE_UNSUPPORTED;
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////
|
|
// IDirectMusicTrack::Init
|
|
HRESULT CTempoTrack::Init(
|
|
/* [in] */ IDirectMusicSegment *pSegment)
|
|
{
|
|
return S_OK;
|
|
}
|
|
|
|
HRESULT CTempoTrack::InitPlay(
|
|
/* [in] */ IDirectMusicSegmentState *pSegmentState,
|
|
/* [in] */ IDirectMusicPerformance *pPerformance,
|
|
/* [out] */ void **ppStateData,
|
|
/* [in] */ DWORD dwTrackID,
|
|
/* [in] */ DWORD dwFlags)
|
|
{
|
|
V_INAME(IDirectMusicTrack::InitPlay);
|
|
V_PTRPTR_WRITE(ppStateData);
|
|
V_INTERFACE(pSegmentState);
|
|
V_INTERFACE(pPerformance);
|
|
|
|
TempoStateData* pStateData;
|
|
pStateData = new TempoStateData;
|
|
if( NULL == pStateData )
|
|
return E_OUTOFMEMORY;
|
|
*ppStateData = pStateData;
|
|
if (m_fStateSetBySetParam)
|
|
{
|
|
pStateData->fActive = m_fActive;
|
|
}
|
|
else
|
|
{
|
|
pStateData->fActive = ((dwFlags & DMUS_SEGF_CONTROL) ||
|
|
!(dwFlags & DMUS_SEGF_SECONDARY));
|
|
}
|
|
pStateData->dwVirtualTrackID = dwTrackID;
|
|
pStateData->pPerformance = pPerformance; // weak reference, no addref.
|
|
pStateData->pSegState = pSegmentState; // weak reference, no addref.
|
|
pStateData->pCurrentTempo = m_TempoEventList.GetHead();
|
|
pStateData->dwValidate = m_dwValidate;
|
|
return S_OK;
|
|
}
|
|
|
|
HRESULT CTempoTrack::EndPlay(
|
|
/* [in] */ void *pStateData)
|
|
{
|
|
ASSERT( pStateData );
|
|
if( pStateData )
|
|
{
|
|
V_INAME(IDirectMusicTrack::EndPlay);
|
|
V_BUFPTR_WRITE(pStateData, sizeof(TempoStateData));
|
|
TempoStateData* pSD = (TempoStateData*)pStateData;
|
|
delete pSD;
|
|
}
|
|
return S_OK;
|
|
}
|
|
|
|
STDMETHODIMP CTempoTrack::PlayEx(void* pStateData,REFERENCE_TIME rtStart,
|
|
REFERENCE_TIME rtEnd,REFERENCE_TIME rtOffset,
|
|
DWORD dwFlags,IDirectMusicPerformance* pPerf,
|
|
IDirectMusicSegmentState* pSegSt,DWORD dwVirtualID)
|
|
{
|
|
V_INAME(IDirectMusicTrack::PlayEx);
|
|
V_BUFPTR_WRITE( pStateData, sizeof(TempoStateData));
|
|
V_INTERFACE(pPerf);
|
|
V_INTERFACE(pSegSt);
|
|
|
|
HRESULT hr;
|
|
EnterCriticalSection(&m_CrSec);
|
|
if (dwFlags & DMUS_TRACKF_CLOCK)
|
|
{
|
|
// Convert all reference times to millisecond times. Then, just use same MUSIC_TIME
|
|
// variables.
|
|
hr = Play(pStateData,(MUSIC_TIME)(rtStart / REF_PER_MIL),(MUSIC_TIME)(rtEnd / REF_PER_MIL),
|
|
(MUSIC_TIME)(rtOffset / REF_PER_MIL),rtOffset,dwFlags,pPerf,pSegSt,dwVirtualID,TRUE);
|
|
}
|
|
else
|
|
{
|
|
hr = Play(pStateData,(MUSIC_TIME)rtStart,(MUSIC_TIME)rtEnd,
|
|
(MUSIC_TIME)rtOffset,0,dwFlags,pPerf,pSegSt,dwVirtualID,FALSE);
|
|
}
|
|
LeaveCriticalSection(&m_CrSec);
|
|
return hr;
|
|
}
|
|
|
|
STDMETHODIMP CTempoTrack::Play(
|
|
void *pStateData,
|
|
MUSIC_TIME mtStart,
|
|
MUSIC_TIME mtEnd,
|
|
MUSIC_TIME mtOffset,
|
|
DWORD dwFlags,
|
|
IDirectMusicPerformance* pPerf,
|
|
IDirectMusicSegmentState* pSegSt,
|
|
DWORD dwVirtualID)
|
|
{
|
|
V_INAME(IDirectMusicTrack::Play);
|
|
V_BUFPTR_WRITE( pStateData, sizeof(TempoStateData));
|
|
V_INTERFACE(pPerf);
|
|
V_INTERFACE(pSegSt);
|
|
|
|
EnterCriticalSection(&m_CrSec);
|
|
HRESULT hr = Play(pStateData,mtStart,mtEnd,mtOffset,0,dwFlags,pPerf,pSegSt,dwVirtualID,FALSE);
|
|
LeaveCriticalSection(&m_CrSec);
|
|
return hr;
|
|
}
|
|
|
|
HRESULT CTempoTrack::Play(
|
|
void *pStateData,
|
|
MUSIC_TIME mtStart,
|
|
MUSIC_TIME mtEnd,
|
|
MUSIC_TIME mtOffset,
|
|
REFERENCE_TIME rtOffset,
|
|
DWORD dwFlags,
|
|
IDirectMusicPerformance* pPerf,
|
|
IDirectMusicSegmentState* pSegSt,
|
|
DWORD dwVirtualID,
|
|
BOOL fClockTime)
|
|
{
|
|
if (dwFlags & DMUS_TRACKF_PLAY_OFF)
|
|
{
|
|
return S_OK;
|
|
}
|
|
HRESULT hr = DMUS_S_END;
|
|
IDirectMusicGraph* pGraph = NULL;
|
|
TempoStateData* pSD = (TempoStateData*)pStateData;
|
|
BOOL fSeek = (dwFlags & DMUS_TRACKF_SEEK) ? TRUE : FALSE;
|
|
|
|
// if mtStart is 0 and dwFlags contains DMUS_TRACKF_START, we want to be sure to
|
|
// send out any negative time events. So, we'll set mtStart to -768.
|
|
if( (mtStart == 0) && ( dwFlags & DMUS_TRACKF_START ))
|
|
{
|
|
mtStart = -768;
|
|
}
|
|
|
|
// if pSD->pCurrentTempo is NULL, and we're in a normal Play call (dwFlags is 0)
|
|
// this means that we either have no events, or we got to the end of the event
|
|
// list previously. So, it's safe to just return.
|
|
if( (pSD->pCurrentTempo == NULL) && (dwFlags == 0) )
|
|
{
|
|
return S_FALSE;
|
|
}
|
|
|
|
if( pSD->dwValidate != m_dwValidate )
|
|
{
|
|
pSD->dwValidate = m_dwValidate;
|
|
pSD->pCurrentTempo = NULL;
|
|
}
|
|
if (!pSD->pCurrentTempo)
|
|
{
|
|
pSD->pCurrentTempo = m_TempoEventList.GetHead();
|
|
}
|
|
if (!pSD->pCurrentTempo)
|
|
{
|
|
return DMUS_S_END;
|
|
}
|
|
// if the previous end time isn't the same as the current start time,
|
|
// we need to seek to the right position.
|
|
if( fSeek || ( pSD->mtPrevEnd != mtStart ))
|
|
{
|
|
TempoStateData tempData;
|
|
BOOL fFlag = TRUE;
|
|
tempData = *pSD; // put this in so we can use Seek in other functions such as GetParam
|
|
if( !fSeek && (dwFlags & DMUS_TRACKF_DIRTY ))
|
|
{
|
|
fFlag = FALSE;
|
|
}
|
|
Seek( &tempData, mtStart, fFlag );
|
|
*pSD = tempData;
|
|
}
|
|
pSD->mtPrevEnd = mtEnd;
|
|
|
|
if( FAILED( pSD->pSegState->QueryInterface( IID_IDirectMusicGraph,
|
|
(void**)&pGraph )))
|
|
{
|
|
pGraph = NULL;
|
|
}
|
|
|
|
for (; pSD->pCurrentTempo; pSD->pCurrentTempo = pSD->pCurrentTempo->GetNext())
|
|
{
|
|
DMUS_IO_TEMPO_ITEM& rTempoEvent = pSD->pCurrentTempo->GetItemValue();
|
|
if( rTempoEvent.lTime >= mtEnd )
|
|
{
|
|
// this time is in the future. Return now to retain the same
|
|
// seek pointers for next time.
|
|
hr = S_OK;
|
|
break;
|
|
}
|
|
if( rTempoEvent.lTime < mtStart )
|
|
{
|
|
if( dwFlags & DMUS_TRACKF_FLUSH )
|
|
{
|
|
// this time is in the past, and this call to Play is in response to an
|
|
// invalidate. We don't want to replay stuff before the start time.
|
|
continue;
|
|
}
|
|
else if( !( dwFlags & DMUS_TRACKF_START) && !(dwFlags & DMUS_TRACKF_SEEK) )
|
|
{
|
|
// we really only want to play events earlier than mtStart on account
|
|
// of a START or SEEK (that isn't a FLUSH.)
|
|
continue;
|
|
}
|
|
}
|
|
if( pSD->fActive )
|
|
{
|
|
DMUS_TEMPO_PMSG* pTempo;
|
|
if( SUCCEEDED( pSD->pPerformance->AllocPMsg( sizeof(DMUS_TEMPO_PMSG),
|
|
(DMUS_PMSG**)&pTempo )))
|
|
{
|
|
if( rTempoEvent.lTime < mtStart )
|
|
{
|
|
// this only happens in the case where we've puposefully seeked
|
|
// and need to time stamp this event with the start time
|
|
if (fClockTime)
|
|
{
|
|
pTempo->rtTime = (mtStart * REF_PER_MIL) + rtOffset;
|
|
pTempo->dwFlags = DMUS_PMSGF_REFTIME | DMUS_PMSGF_LOCKTOREFTIME;
|
|
}
|
|
else
|
|
{
|
|
pTempo->mtTime = mtStart + mtOffset;
|
|
pTempo->dwFlags = DMUS_PMSGF_MUSICTIME;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (fClockTime)
|
|
{
|
|
pTempo->rtTime = (rTempoEvent.lTime * REF_PER_MIL) + rtOffset;
|
|
pTempo->dwFlags = DMUS_PMSGF_REFTIME | DMUS_PMSGF_LOCKTOREFTIME;
|
|
}
|
|
else
|
|
{
|
|
pTempo->mtTime = rTempoEvent.lTime + mtOffset;
|
|
pTempo->dwFlags = DMUS_PMSGF_MUSICTIME;
|
|
}
|
|
}
|
|
pTempo->dblTempo = rTempoEvent.dblTempo;
|
|
pTempo->dwVirtualTrackID = pSD->dwVirtualTrackID;
|
|
pTempo->dwType = DMUS_PMSGT_TEMPO;
|
|
pTempo->dwGroupID = 0xffffffff;
|
|
if( pGraph )
|
|
{
|
|
pGraph->StampPMsg( (DMUS_PMSG*)pTempo );
|
|
}
|
|
if(FAILED(pSD->pPerformance->SendPMsg( (DMUS_PMSG*)pTempo )))
|
|
{
|
|
pSD->pPerformance->FreePMsg( (DMUS_PMSG*)pTempo );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if( pGraph )
|
|
{
|
|
pGraph->Release();
|
|
}
|
|
return hr;
|
|
}
|
|
|
|
// if fGetPrevious is TRUE, seek to the event prior to mtTime. Otherwise, seek to
|
|
// the event on or after mtTime
|
|
HRESULT CTempoTrack::Seek(
|
|
/* [in] */ TempoStateData *pSD,
|
|
/* [in] */ MUSIC_TIME mtTime, BOOL fGetPrevious)
|
|
{
|
|
TListItem<DMUS_IO_TEMPO_ITEM>* pScan = pSD->pCurrentTempo;
|
|
if (!pScan)
|
|
{
|
|
pScan = m_TempoEventList.GetHead();
|
|
}
|
|
if (!pScan)
|
|
{
|
|
return S_FALSE;
|
|
}
|
|
// if the event's time is on or past mtTime, we need to go to the beginning
|
|
if (pScan->GetItemValue().lTime >= mtTime)
|
|
{
|
|
pScan = m_TempoEventList.GetHead();
|
|
}
|
|
pSD->pCurrentTempo = pScan;
|
|
for (; pScan; pScan = pScan->GetNext())
|
|
{
|
|
if (pScan->GetItemValue().lTime >= mtTime)
|
|
{
|
|
if (!fGetPrevious)
|
|
{
|
|
pSD->pCurrentTempo = pScan;
|
|
}
|
|
break;
|
|
}
|
|
pSD->pCurrentTempo = pScan;
|
|
}
|
|
return S_OK;
|
|
}
|
|
|
|
STDMETHODIMP CTempoTrack::GetParamEx(REFGUID rguidType,REFERENCE_TIME rtTime,
|
|
REFERENCE_TIME* prtNext,void* pParam,void * pStateData, DWORD dwFlags)
|
|
{
|
|
HRESULT hr;
|
|
MUSIC_TIME mtNext;
|
|
if (dwFlags & DMUS_TRACK_PARAMF_CLOCK)
|
|
{
|
|
hr = GetParam(rguidType,(MUSIC_TIME) (rtTime / REF_PER_MIL), &mtNext, pParam);
|
|
if (prtNext)
|
|
{
|
|
*prtNext = mtNext * REF_PER_MIL;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
hr = GetParam(rguidType,(MUSIC_TIME) rtTime, &mtNext, pParam);
|
|
if (prtNext)
|
|
{
|
|
*prtNext = mtNext;
|
|
}
|
|
}
|
|
return hr;
|
|
}
|
|
|
|
STDMETHODIMP CTempoTrack::SetParamEx(REFGUID rguidType,REFERENCE_TIME rtTime,
|
|
void* pParam, void * pStateData, DWORD dwFlags)
|
|
{
|
|
if (dwFlags & DMUS_TRACK_PARAMF_CLOCK)
|
|
{
|
|
rtTime /= REF_PER_MIL;
|
|
}
|
|
return SetParam(rguidType, (MUSIC_TIME) rtTime , pParam);
|
|
}
|
|
|
|
|
|
HRESULT CTempoTrack::GetParam(
|
|
REFGUID rguid,
|
|
MUSIC_TIME mtTime,
|
|
MUSIC_TIME* pmtNext,
|
|
void *pData)
|
|
{
|
|
V_INAME(IDirectMusicTrack::GetParam);
|
|
V_PTR_WRITE_OPT(pmtNext,MUSIC_TIME);
|
|
V_REFGUID(rguid);
|
|
|
|
HRESULT hr = DMUS_E_GET_UNSUPPORTED;
|
|
if( NULL == pData )
|
|
{
|
|
return E_POINTER;
|
|
}
|
|
if( rguid == GUID_PrivateTempoParam )
|
|
{
|
|
DMUS_TEMPO_PARAM TempoData;
|
|
PrivateTempo* pPrivateTempoData = (PrivateTempo*)pData;
|
|
hr = GetParam(GUID_TempoParam, mtTime, pmtNext, (void*)&TempoData);
|
|
if (hr == S_OK)
|
|
{
|
|
pPrivateTempoData->dblTempo = TempoData.dblTempo;
|
|
pPrivateTempoData->mtTime = 0; // must be set by the caller
|
|
pPrivateTempoData->mtDelta = TempoData.mtTime;
|
|
pPrivateTempoData->fLast = (pmtNext && !*pmtNext);
|
|
}
|
|
else if (hr == DMUS_E_NOT_FOUND) // the tempo track was empty
|
|
{
|
|
pPrivateTempoData->fLast = true;
|
|
}
|
|
}
|
|
else if( rguid == GUID_TempoParam )
|
|
{
|
|
if( !m_fActive )
|
|
{
|
|
return DMUS_E_TYPE_DISABLED;
|
|
}
|
|
DMUS_TEMPO_PARAM* pTempoData = (DMUS_TEMPO_PARAM*)pData;
|
|
TListItem<DMUS_IO_TEMPO_ITEM>* pScan = m_TempoEventList.GetHead();
|
|
TListItem<DMUS_IO_TEMPO_ITEM>* pPrevious = pScan;
|
|
if (!pScan)
|
|
{
|
|
return DMUS_E_NOT_FOUND;
|
|
}
|
|
for (; pScan; pScan = pScan->GetNext())
|
|
{
|
|
if (pScan->GetItemValue().lTime > mtTime)
|
|
{
|
|
break;
|
|
}
|
|
pPrevious = pScan;
|
|
}
|
|
DMUS_IO_TEMPO_ITEM& rTempoEvent = pPrevious->GetItemValue();
|
|
pTempoData->dblTempo = rTempoEvent.dblTempo;
|
|
pTempoData->mtTime = rTempoEvent.lTime - mtTime;
|
|
if (pmtNext)
|
|
{
|
|
*pmtNext = 0;
|
|
}
|
|
if (pScan)
|
|
{
|
|
DMUS_IO_TEMPO_ITEM& rNextTempoEvent = pScan->GetItemValue();
|
|
if (pmtNext)
|
|
{
|
|
*pmtNext = rNextTempoEvent.lTime - mtTime;
|
|
}
|
|
}
|
|
hr = S_OK;
|
|
}
|
|
return hr;
|
|
}
|
|
|
|
// Q: if all tracks are time-stamped, why do we need mtTime?
|
|
HRESULT CTempoTrack::SetParam(
|
|
REFGUID rguid,
|
|
MUSIC_TIME mtTime,
|
|
void *pData)
|
|
{
|
|
V_INAME(IDirectMusicTrack::SetParam);
|
|
V_REFGUID(rguid);
|
|
|
|
EnterCriticalSection(&m_CrSec);
|
|
|
|
HRESULT hr = DMUS_E_SET_UNSUPPORTED;
|
|
|
|
if( rguid == GUID_DisableTempo )
|
|
{
|
|
if (m_fStateSetBySetParam && !m_fActive)
|
|
{ // Already been disabled.
|
|
hr = DMUS_E_TYPE_DISABLED;
|
|
}
|
|
else
|
|
{
|
|
m_fStateSetBySetParam = TRUE;
|
|
m_fActive = FALSE;
|
|
hr = S_OK;
|
|
}
|
|
}
|
|
else if( rguid == GUID_EnableTempo )
|
|
{
|
|
if (m_fStateSetBySetParam && m_fActive)
|
|
{ // Already been enabled.
|
|
hr = DMUS_E_TYPE_DISABLED;
|
|
}
|
|
else
|
|
{
|
|
m_fStateSetBySetParam = TRUE;
|
|
m_fActive = TRUE;
|
|
hr = S_OK;
|
|
}
|
|
}
|
|
else if( rguid == GUID_TempoParam )
|
|
{
|
|
if (!m_fActive)
|
|
{ // Oops, app intentionally disabled tempo.
|
|
hr = DMUS_E_TYPE_DISABLED;
|
|
}
|
|
else
|
|
{
|
|
if( NULL == pData )
|
|
{
|
|
LeaveCriticalSection(&m_CrSec);
|
|
return E_POINTER;
|
|
}
|
|
DMUS_TEMPO_PARAM* pTempoData = (DMUS_TEMPO_PARAM*)pData;
|
|
TListItem<DMUS_IO_TEMPO_ITEM>* pScan = m_TempoEventList.GetHead();
|
|
TListItem<DMUS_IO_TEMPO_ITEM>* pPrevious = NULL;
|
|
for (; pScan; pScan = pScan->GetNext())
|
|
{
|
|
if (pScan->GetItemValue().lTime >= mtTime)
|
|
{
|
|
break;
|
|
}
|
|
pPrevious = pScan;
|
|
}
|
|
// Make a new DMUS_IO_TEMPO_ITEM and insert it after pPrevious
|
|
TListItem<DMUS_IO_TEMPO_ITEM>* pNew = new TListItem<DMUS_IO_TEMPO_ITEM>;
|
|
if (!pNew)
|
|
{
|
|
LeaveCriticalSection(&m_CrSec);
|
|
return E_OUTOFMEMORY;
|
|
}
|
|
DMUS_IO_TEMPO_ITEM& rTempoEvent = pNew->GetItemValue();
|
|
rTempoEvent.dblTempo = pTempoData->dblTempo;
|
|
/*
|
|
// I believe the fix for 204160 was supposed to change this line to what
|
|
// follows the comment. RSW
|
|
rTempoEvent.lTime = pTempoData->mtTime;
|
|
*/
|
|
rTempoEvent.lTime = mtTime;
|
|
if (pPrevious)
|
|
{
|
|
pNew->SetNext(pScan);
|
|
pPrevious->SetNext(pNew);
|
|
}
|
|
else
|
|
{
|
|
m_TempoEventList.AddHead(pNew);
|
|
}
|
|
if (pScan && pScan->GetItemValue().lTime == mtTime)
|
|
{
|
|
// remove it
|
|
pNew->SetNext(pScan->GetNext());
|
|
pScan->SetNext(NULL);
|
|
delete pScan;
|
|
}
|
|
m_dwValidate++;
|
|
hr = S_OK;
|
|
}
|
|
}
|
|
LeaveCriticalSection(&m_CrSec);
|
|
return hr;
|
|
}
|
|
|
|
HRESULT STDMETHODCALLTYPE CTempoTrack::AddNotificationType(
|
|
/* [in] */ REFGUID rguidNotification)
|
|
{
|
|
return E_NOTIMPL;
|
|
}
|
|
|
|
HRESULT STDMETHODCALLTYPE CTempoTrack::RemoveNotificationType(
|
|
/* [in] */ REFGUID rguidNotification)
|
|
{
|
|
return E_NOTIMPL;
|
|
}
|
|
|
|
HRESULT STDMETHODCALLTYPE CTempoTrack::Clone(
|
|
MUSIC_TIME mtStart,
|
|
MUSIC_TIME mtEnd,
|
|
IDirectMusicTrack** ppTrack)
|
|
{
|
|
V_INAME(IDirectMusicTrack::Clone);
|
|
V_PTRPTR_WRITE(ppTrack);
|
|
|
|
HRESULT hr = S_OK;
|
|
|
|
if(mtStart < 0 )
|
|
{
|
|
return E_INVALIDARG;
|
|
}
|
|
if(mtStart > mtEnd)
|
|
{
|
|
return E_INVALIDARG;
|
|
}
|
|
|
|
EnterCriticalSection(&m_CrSec);
|
|
|
|
CTempoTrack *pDM;
|
|
|
|
try
|
|
{
|
|
pDM = new CTempoTrack(*this, mtStart, mtEnd);
|
|
}
|
|
catch( ... )
|
|
{
|
|
pDM = NULL;
|
|
}
|
|
|
|
LeaveCriticalSection(&m_CrSec);
|
|
if (pDM == NULL) {
|
|
return E_OUTOFMEMORY;
|
|
}
|
|
|
|
|
|
hr = pDM->QueryInterface(IID_IDirectMusicTrack, (void**)ppTrack);
|
|
pDM->Release();
|
|
|
|
return hr;
|
|
}
|
|
|
|
STDMETHODIMP CTempoTrack::Compose(
|
|
IUnknown* pContext,
|
|
DWORD dwTrackGroup,
|
|
IDirectMusicTrack** ppResultTrack)
|
|
{
|
|
return E_NOTIMPL;
|
|
}
|
|
|
|
STDMETHODIMP CTempoTrack::Join(
|
|
IDirectMusicTrack* pNewTrack,
|
|
MUSIC_TIME mtJoin,
|
|
IUnknown* pContext,
|
|
DWORD dwTrackGroup,
|
|
IDirectMusicTrack** ppResultTrack)
|
|
{
|
|
V_INAME(IDirectMusicTrack::Join);
|
|
V_INTERFACE(pNewTrack);
|
|
V_INTERFACE_OPT(pContext);
|
|
V_PTRPTR_WRITE_OPT(ppResultTrack);
|
|
|
|
HRESULT hr = S_OK;
|
|
EnterCriticalSection(&m_CrSec);
|
|
|
|
if (ppResultTrack)
|
|
{
|
|
hr = Clone(0, mtJoin, ppResultTrack);
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
hr = ((CTempoTrack*)*ppResultTrack)->JoinInternal(pNewTrack, mtJoin, dwTrackGroup);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
hr = JoinInternal(pNewTrack, mtJoin, dwTrackGroup);
|
|
}
|
|
|
|
LeaveCriticalSection(&m_CrSec);
|
|
return hr;
|
|
}
|
|
|
|
HRESULT CTempoTrack::JoinInternal(
|
|
IDirectMusicTrack* pNewTrack,
|
|
MUSIC_TIME mtJoin,
|
|
DWORD dwTrackGroup)
|
|
{
|
|
HRESULT hr = S_OK;
|
|
CTempoTrack* pOtherTrack = (CTempoTrack*)pNewTrack;
|
|
TListItem<DMUS_IO_TEMPO_ITEM>* pScan = pOtherTrack->m_TempoEventList.GetHead();
|
|
for (; pScan; pScan = pScan->GetNext())
|
|
{
|
|
DMUS_IO_TEMPO_ITEM& rScan = pScan->GetItemValue();
|
|
TListItem<DMUS_IO_TEMPO_ITEM>* pNew = new TListItem<DMUS_IO_TEMPO_ITEM>;
|
|
if (pNew)
|
|
{
|
|
DMUS_IO_TEMPO_ITEM& rNew = pNew->GetItemValue();
|
|
rNew.lTime = rScan.lTime + mtJoin;
|
|
rNew.dblTempo = rScan.dblTempo;
|
|
m_TempoEventList.AddTail(pNew);
|
|
}
|
|
else
|
|
{
|
|
hr = E_OUTOFMEMORY;
|
|
break;
|
|
}
|
|
}
|
|
return hr;
|
|
}
|