// 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) // TimeSigTrk.cpp : Implementation of CTimeSigTrack #include "dmime.h" #include "TSigTrk.h" #include "dmusici.h" #include "dmusicf.h" #include "debug.h" #include "..\shared\dmstrm.h" #include "..\shared\Validate.h" #include "debug.h" #define ASSERT assert CTimeSigItem::CTimeSigItem() { m_TimeSig.lTime = 0; m_TimeSig.bBeatsPerMeasure = 0; m_TimeSig.bBeat = 0; m_TimeSig.wGridsPerBeat = 0; } ///////////////////////////////////////////////////////////////////////////// // CTimeSigTrack void CTimeSigTrack::Construct() { InterlockedIncrement(&g_cComponent); m_cRef = 1; m_fCSInitialized = FALSE; InitializeCriticalSection(&m_CrSec); m_fCSInitialized = TRUE; m_dwValidate = 0; m_fNotificationMeasureBeat = FALSE; } CTimeSigTrack::CTimeSigTrack() { Construct(); m_fActive = TRUE; m_fStateSetBySetParam = FALSE; } CTimeSigTrack::CTimeSigTrack( CTimeSigTrack *pSourceTrack, MUSIC_TIME mtStart, MUSIC_TIME mtEnd) { Construct(); m_fActive = pSourceTrack->m_fActive; m_fStateSetBySetParam = pSourceTrack->m_fStateSetBySetParam; // Clone the time signature list. CTimeSigItem* pScan = pSourceTrack->m_TSigEventList.GetHead(); CTimeSigItem* pPrevious = NULL; for(; pScan; pScan = pScan->GetNext()) { if (pScan->m_TimeSig.lTime < mtStart) { pPrevious = pScan; } else if (pScan->m_TimeSig.lTime < mtEnd) { if (pScan->m_TimeSig.lTime == mtStart) { pPrevious = NULL; } CTimeSigItem* pNew = new CTimeSigItem; if (pNew) { pNew->m_TimeSig = pScan->m_TimeSig; pNew->m_TimeSig.lTime = pScan->m_TimeSig.lTime - mtStart; m_TSigEventList.AddHead(pNew); // instead of AddTail, which is n^2. We reverse below. } } else break; } m_TSigEventList.Reverse(); // Now, put list in order. // Then, install the time signature that precedes the clone. if (pPrevious) { CTimeSigItem* pNew = new CTimeSigItem; if (pNew) { pNew->m_TimeSig = pPrevious->m_TimeSig; pNew->m_TimeSig.lTime = 0; m_TSigEventList.AddHead(pNew); } } } void CTimeSigTrack::Clear() { CTimeSigItem* pItem; while( pItem = m_TSigEventList.RemoveHead() ) { delete pItem; } } CTimeSigTrack::~CTimeSigTrack() { Clear(); if (m_fCSInitialized) { DeleteCriticalSection(&m_CrSec); } InterlockedDecrement(&g_cComponent); } // @method:(EXTERNAL) HRESULT | IDirectMusicTimeSigTrack | QueryInterface | Standard QueryInterface implementation for // // @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 CTimeSigTrack::QueryInterface( const IID &iid, // @parm Interface to query for void **ppv) // @parm The requested interface will be returned here { V_INAME(CTimeSigTrack::QueryInterface); V_PTRPTR_WRITE(ppv); V_REFGUID(iid); if (iid == IID_IUnknown || iid == IID_IDirectMusicTrack) { *ppv = static_cast(this); } else if (iid == IID_IPersistStream) { *ppv = static_cast(this); } else { *ppv = NULL; Trace(4,"Warning: Request to query unknown interface on Time Signature Track\n"); return E_NOINTERFACE; } reinterpret_cast(this)->AddRef(); return S_OK; } // @method:(EXTERNAL) HRESULT | IDirectMusicTimeSigTrack | AddRef | Standard AddRef implementation for // // @rdesc Returns the new reference count for this object. // // @mfunc:(INTERNAL) // // STDMETHODIMP_(ULONG) CTimeSigTrack::AddRef() { return InterlockedIncrement(&m_cRef); } // @method:(EXTERNAL) HRESULT | IDirectMusicTimeSigTrack | Release | Standard Release implementation for // // @rdesc Returns the new reference count for this object. // // @mfunc:(INTERNAL) // // STDMETHODIMP_(ULONG) CTimeSigTrack::Release() { if (!InterlockedDecrement(&m_cRef)) { delete this; return 0; } return m_cRef; } ///////////////////////////////////////////////////////////////////////////// // IPersist HRESULT CTimeSigTrack::GetClassID( CLSID* pClassID ) { V_INAME(CTimeSigTrack::GetClassID); V_PTR_WRITE(pClassID, CLSID); *pClassID = CLSID_DirectMusicTimeSigTrack; return S_OK; } ///////////////////////////////////////////////////////////////////////////// // IPersistStream functions HRESULT CTimeSigTrack::IsDirty() { return S_FALSE; } /* @method HRESULT | ITimeSigTrack | Load | Call this with an IStream filled with DMUS_IO_TIMESIGNATURE_ITEM's, sorted in time order. @parm IStream* | pIStream | A stream of DMUS_IO_TIMESIGNATURE_ITEM's, sorted in time order. The seek pointer should be set to the first event. The stream should only contain TimeSig events and nothing more. @rvalue E_INVALIDARG | If pIStream == NULL @rvalue S_OK @comm The

will be AddRef'd inside this function and held until the TimeSigTrack is released. */ HRESULT CTimeSigTrack::Load( IStream* pIStream ) { V_INAME(CTimeSigTrack::Load); V_INTERFACE(pIStream); CRiffParser Parser(pIStream); EnterCriticalSection(&m_CrSec); m_dwValidate++; // used to validate state data that's out there RIFFIO ckMain; HRESULT hr = S_OK; Parser.EnterList(&ckMain); if (Parser.NextChunk(&hr)) { if (ckMain.ckid == DMUS_FOURCC_TIMESIG_CHUNK) { hr = LoadTimeSigList(&Parser,ckMain.cksize); } else if ((ckMain.ckid == FOURCC_LIST) && (ckMain.fccType == DMUS_FOURCC_TIMESIGTRACK_LIST)) { Clear(); RIFFIO ckNext; // Descends into the children chunks. Parser.EnterList(&ckNext); while (Parser.NextChunk(&hr)) { switch(ckNext.ckid) { case DMUS_FOURCC_TIMESIG_CHUNK : hr = LoadTimeSigList(&Parser,ckNext.cksize); break; } } Parser.LeaveList(); } else { Trace(1,"Error: Failure reading bad data in time signature track.\n"); hr = DMUS_E_CHUNKNOTFOUND; } } LeaveCriticalSection(&m_CrSec); return hr; } HRESULT CTimeSigTrack::LoadTimeSigList( CRiffParser *pParser, long lChunkSize ) { HRESULT hr; // copy contents of the stream into the list. DWORD dwSubSize; // read in the size of the data structures hr = pParser->Read( &dwSubSize, sizeof(DWORD)); if (SUCCEEDED(hr)) { lChunkSize -= sizeof(DWORD); DWORD dwRead, dwSeek; if( dwSubSize > sizeof(DMUS_IO_TIMESIGNATURE_ITEM) ) { dwRead = sizeof(DMUS_IO_TIMESIGNATURE_ITEM); dwSeek = dwSubSize - dwRead; } else { dwRead = dwSubSize; dwSeek = 0; } if( 0 == dwRead ) { Trace(1,"Error: Failure reading time signature track.\n"); hr = DMUS_E_CANNOTREAD; } else { while( lChunkSize > 0 ) { CTimeSigItem *pNew = new CTimeSigItem; if (pNew) { if( FAILED( pParser->Read( &pNew->m_TimeSig, dwRead ))) { delete pNew; hr = DMUS_E_CANNOTREAD; break; } // make sure this time sig is OK if (!pNew->m_TimeSig.bBeatsPerMeasure) { Trace(1, "Warning: invalid content: DMUS_IO_TIMESIGNATURE_ITEM.bBeatsPerMeasure\n"); pNew->m_TimeSig.bBeatsPerMeasure = 4; } if (!pNew->m_TimeSig.bBeat) { Trace(1, "Warning: invalid content: DMUS_IO_TIMESIGNATURE_ITEM.bBeat\n"); pNew->m_TimeSig.bBeat = 4; } if (!pNew->m_TimeSig.wGridsPerBeat) { Trace(1, "Warning: invalid content: DMUS_IO_TIMESIGNATURE_ITEM.wGridsPerBeat\n"); pNew->m_TimeSig.wGridsPerBeat = 4; } m_TSigEventList.AddHead(pNew); lChunkSize -= dwRead; if( dwSeek ) { if( FAILED( pParser->Skip(dwSeek))) { hr = DMUS_E_CANNOTSEEK; break; } lChunkSize -= dwSeek; } } } m_TSigEventList.Reverse(); // If there is no time signature at the start, make a copy of the // first time signature and stick it there. This resolves a bug in 6.1 // where notification messages and GetParam() were inconsistent // in their behavior under this circumstance. This ensures they behave // the same. CTimeSigItem *pTop = m_TSigEventList.GetHead(); if (pTop && (pTop->m_TimeSig.lTime > 0)) { CTimeSigItem *pCopy = new CTimeSigItem; if (pCopy) { *pCopy = *pTop; pCopy->m_TimeSig.lTime = 0; m_TSigEventList.AddHead(pCopy); } } } } return hr; } HRESULT CTimeSigTrack::Save( IStream* pIStream, BOOL fClearDirty ) { return E_NOTIMPL; } HRESULT CTimeSigTrack::GetSizeMax( ULARGE_INTEGER FAR* pcbSize ) { return E_NOTIMPL; } // IDirectMusicTrack HRESULT STDMETHODCALLTYPE CTimeSigTrack::IsParamSupported( /* [in] */ REFGUID rguid) { V_INAME(CTimeSigTrack::IsParamSupported); V_REFGUID(rguid); if (m_fStateSetBySetParam) { if( m_fActive ) { if( rguid == GUID_DisableTimeSig ) return S_OK; if( rguid == GUID_TimeSignature ) return S_OK; if( rguid == GUID_EnableTimeSig ) return DMUS_E_TYPE_DISABLED; } else { if( rguid == GUID_EnableTimeSig ) return S_OK; if( rguid == GUID_DisableTimeSig ) return DMUS_E_TYPE_DISABLED; if( rguid == GUID_TimeSignature ) return DMUS_E_TYPE_DISABLED; } } else { if(( rguid == GUID_DisableTimeSig ) || ( rguid == GUID_TimeSignature ) || ( rguid == GUID_EnableTimeSig )) return S_OK; } return DMUS_E_TYPE_UNSUPPORTED; } ////////////////////////////////////////////////////////////////////// // IDirectMusicTrack::Init HRESULT CTimeSigTrack::Init( /* [in] */ IDirectMusicSegment *pSegment) { return S_OK; } HRESULT CTimeSigTrack::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); EnterCriticalSection(&m_CrSec); CTimeSigStateData* pStateData; pStateData = new CTimeSigStateData; if( NULL == pStateData ) return E_OUTOFMEMORY; *ppStateData = pStateData; if (m_fStateSetBySetParam) { pStateData->m_fActive = m_fActive; } else { pStateData->m_fActive = !(dwFlags & (DMUS_SEGF_CONTROL | DMUS_SEGF_SECONDARY)); } pStateData->m_dwVirtualTrackID = dwTrackID; pStateData->m_pPerformance = pPerformance; // weak reference, no addref. pStateData->m_pSegState = pSegmentState; // weak reference, no addref. pStateData->m_pCurrentTSig = m_TSigEventList.GetHead(); pStateData->m_dwValidate = m_dwValidate; LeaveCriticalSection(&m_CrSec); return S_OK; } HRESULT CTimeSigTrack::EndPlay( /* [in] */ void *pStateData) { ASSERT( pStateData ); if( pStateData ) { V_INAME(CTimeSigTrack::EndPlay); V_BUFPTR_WRITE(pStateData, sizeof(CTimeSigStateData)); CTimeSigStateData* pSD = (CTimeSigStateData*)pStateData; delete pSD; } return S_OK; } HRESULT CTimeSigTrack::Play( /* [in] */ void *pStateData, /* [in] */ MUSIC_TIME mtStart, /* [in] */ MUSIC_TIME mtEnd, /* [in] */ MUSIC_TIME mtOffset, DWORD dwFlags, IDirectMusicPerformance* pPerf, IDirectMusicSegmentState* pSegSt, DWORD dwVirtualID ) { V_INAME(IDirectMusicTrack::Play); V_BUFPTR_WRITE( pStateData, sizeof(CTimeSigStateData)); V_INTERFACE(pPerf); V_INTERFACE(pSegSt); EnterCriticalSection(&m_CrSec); HRESULT hr = S_OK; IDirectMusicGraph* pGraph = NULL; DMUS_TIMESIG_PMSG* pTimeSig; CTimeSigStateData* pSD = (CTimeSigStateData*)pStateData; MUSIC_TIME mtNotification = mtStart; 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->m_dwValidate != m_dwValidate ) { pSD->m_dwValidate = m_dwValidate; pSD->m_pCurrentTSig = NULL; } // 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->m_mtPrevEnd != mtStart )) { if( dwFlags & (DMUS_TRACKF_START | DMUS_TRACKF_LOOP) ) { Seek( pStateData, mtStart, TRUE ); } else { Seek( pStateData, mtStart, FALSE ); } } pSD->m_mtPrevEnd = mtEnd; if( NULL == pSD->m_pCurrentTSig ) { pSD->m_pCurrentTSig = m_TSigEventList.GetHead(); } if( FAILED( pSD->m_pSegState->QueryInterface( IID_IDirectMusicGraph, (void**)&pGraph ))) { pGraph = NULL; } for( ; pSD->m_pCurrentTSig; pSD->m_pCurrentTSig = pSD->m_pCurrentTSig->GetNext() ) { DMUS_IO_TIMESIGNATURE_ITEM *pItem = &pSD->m_pCurrentTSig->m_TimeSig; if( pItem->lTime >= mtEnd ) { break; } if( (pItem->lTime < mtStart) && !fSeek ) { break; } if( pSD->m_fActive && !(dwFlags & DMUS_TRACKF_PLAY_OFF) && SUCCEEDED( pSD->m_pPerformance->AllocPMsg( sizeof(DMUS_TIMESIG_PMSG), (DMUS_PMSG**)&pTimeSig ))) { if( pItem->lTime < mtStart ) { // this only happens in the case where we've puposefully seeked // and need to time stamp this event with the start time pTimeSig->mtTime = mtStart + mtOffset; } else { pTimeSig->mtTime = pItem->lTime + mtOffset; } pTimeSig->bBeatsPerMeasure = pItem->bBeatsPerMeasure; pTimeSig->bBeat = pItem->bBeat; pTimeSig->wGridsPerBeat = pItem->wGridsPerBeat; pTimeSig->dwFlags |= DMUS_PMSGF_MUSICTIME; pTimeSig->dwVirtualTrackID = pSD->m_dwVirtualTrackID; pTimeSig->dwType = DMUS_PMSGT_TIMESIG; pTimeSig->dwGroupID = 0xffffffff; if( pGraph ) { pGraph->StampPMsg( (DMUS_PMSG*)pTimeSig ); } TraceI(3, "TimeSigtrk: TimeSig event\n"); if(FAILED(pSD->m_pPerformance->SendPMsg( (DMUS_PMSG*)pTimeSig ))) { pSD->m_pPerformance->FreePMsg( (DMUS_PMSG*)pTimeSig ); } } if( pSD->m_fActive && m_fNotificationMeasureBeat && !(dwFlags & DMUS_TRACKF_NOTIFY_OFF)) { // create beat and measure notifications for up to this time if (mtNotification < pItem->lTime) { mtNotification = NotificationMeasureBeat( mtNotification, pItem->lTime, pSD, mtOffset ); } } // set the state data to the new beat and beats per measure, and time pSD->m_bBeat = pItem->bBeat; pSD->m_bBeatsPerMeasure = pItem->bBeatsPerMeasure; pSD->m_mtTimeSig = pItem->lTime; } if( pSD->m_fActive && m_fNotificationMeasureBeat && ( mtNotification < mtEnd ) && !(dwFlags & DMUS_TRACKF_NOTIFY_OFF)) { NotificationMeasureBeat( mtNotification, mtEnd, pSD, mtOffset ); } if( pGraph ) { pGraph->Release(); } LeaveCriticalSection(&m_CrSec); return hr; } // seeks to the time sig. just before mtTime. HRESULT CTimeSigTrack::Seek( /* [in] */ void *pStateData, /* [in] */ MUSIC_TIME mtTime, BOOL fGetPrevious) { CTimeSigStateData* pSD = (CTimeSigStateData*)pStateData; if( m_TSigEventList.IsEmpty() ) { return S_FALSE; } if( NULL == pSD->m_pCurrentTSig ) { pSD->m_pCurrentTSig = m_TSigEventList.GetHead(); } // if the current event's time is on or past mtTime, we need to rewind to the beginning if( pSD->m_pCurrentTSig->m_TimeSig.lTime >= mtTime ) { pSD->m_pCurrentTSig = m_TSigEventList.GetHead(); } // now start seeking until we find an event with time on or past mtTime CTimeSigItem* pTSig; for( pTSig = pSD->m_pCurrentTSig; pTSig ; pTSig = pTSig->GetNext() ) { if( pTSig->m_TimeSig.lTime >= mtTime ) { break; } pSD->m_pCurrentTSig = pTSig; } if( !fGetPrevious && pSD->m_pCurrentTSig ) { pSD->m_pCurrentTSig = pSD->m_pCurrentTSig->GetNext(); } return S_OK; } HRESULT CTimeSigTrack::GetParam( REFGUID rguid, MUSIC_TIME mtTime, MUSIC_TIME* pmtNext, void *pData) { V_INAME(CTimeSigTrack::GetParam); V_PTR_WRITE_OPT(pmtNext,MUSIC_TIME); V_REFGUID(rguid); HRESULT hr = DMUS_E_GET_UNSUPPORTED; EnterCriticalSection(&m_CrSec); if( NULL == pData ) { hr = E_POINTER; } else if( GUID_TimeSignature == rguid ) { if( !m_fActive ) { hr = DMUS_E_TYPE_DISABLED; } else { DMUS_TIMESIGNATURE* pTSigData = (DMUS_TIMESIGNATURE*)pData; CTimeSigItem* pScan = m_TSigEventList.GetHead(); CTimeSigItem* pPrevious = pScan; if (pScan) { for (; pScan; pScan = pScan->GetNext()) { if (pScan->m_TimeSig.lTime > mtTime) { break; } pPrevious = pScan; } pTSigData->mtTime = pPrevious->m_TimeSig.lTime - mtTime; pTSigData->bBeatsPerMeasure = pPrevious->m_TimeSig.bBeatsPerMeasure; pTSigData->bBeat = pPrevious->m_TimeSig.bBeat; pTSigData->wGridsPerBeat = pPrevious->m_TimeSig.wGridsPerBeat; if (pmtNext) { *pmtNext = 0; } if (pScan) { if (pmtNext) { *pmtNext = pScan->m_TimeSig.lTime - mtTime; } } hr = S_OK; } else { hr = DMUS_E_NOT_FOUND; } } } LeaveCriticalSection(&m_CrSec); return hr; } HRESULT CTimeSigTrack::SetParam( REFGUID rguid, MUSIC_TIME mtTime, void *pData) { V_INAME(CTimeSigTrack::SetParam); V_REFGUID(rguid); HRESULT hr = DMUS_E_SET_UNSUPPORTED; if( rguid == GUID_EnableTimeSig ) { 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_DisableTimeSig ) { if (m_fStateSetBySetParam && !m_fActive) { // Already been disabled. hr = DMUS_E_TYPE_DISABLED; } else { m_fStateSetBySetParam = TRUE; m_fActive = FALSE; hr = S_OK; } } return hr; } HRESULT STDMETHODCALLTYPE CTimeSigTrack::AddNotificationType( /* [in] */ REFGUID rguidNotification) { V_INAME(IDirectMusicTrack::AddNotificationType); V_REFGUID(rguidNotification); HRESULT hr = S_FALSE; if( rguidNotification == GUID_NOTIFICATION_MEASUREANDBEAT ) { m_fNotificationMeasureBeat = TRUE; hr = S_OK; } return hr; } HRESULT STDMETHODCALLTYPE CTimeSigTrack::RemoveNotificationType( /* [in] */ REFGUID rguidNotification) { V_INAME(IDirectMusicTrack::RemoveNotificationType); V_REFGUID(rguidNotification); HRESULT hr = S_FALSE; if( rguidNotification == GUID_NOTIFICATION_MEASUREANDBEAT ) { m_fNotificationMeasureBeat = FALSE; hr = S_OK; } return hr; } // send measure and beat notifications MUSIC_TIME CTimeSigTrack::NotificationMeasureBeat( MUSIC_TIME mtStart, MUSIC_TIME mtEnd, CTimeSigStateData* pSD, MUSIC_TIME mtOffset ) { DMUS_NOTIFICATION_PMSG* pEvent = NULL; MUSIC_TIME mtTime; DWORD dwMeasure; BYTE bCurrentBeat; if( pSD->m_mtTimeSig >= mtEnd ) return mtStart; if( pSD->m_mtTimeSig > mtStart ) { mtStart = pSD->m_mtTimeSig; } // now actually generate the beat events. // Generate events that are on beat boundaries, from mtStart to mtEnd long lQuantize = ( DMUS_PPQ * 4 ) / pSD->m_bBeat; mtTime = mtStart - pSD->m_mtTimeSig; if( mtTime ) // 0 stays 0 { // quantize to next boundary mtTime = ((( mtTime - 1 ) / lQuantize ) + 1 ) * lQuantize; } mtStart += mtTime - ( mtStart - pSD->m_mtTimeSig ); bCurrentBeat = (BYTE)(( ( mtStart - pSD->m_mtTimeSig ) / lQuantize ) % pSD->m_bBeatsPerMeasure); dwMeasure = mtStart / (pSD->m_bBeatsPerMeasure * lQuantize ); while( mtStart < mtEnd ) { if( SUCCEEDED( pSD->m_pPerformance->AllocPMsg( sizeof(DMUS_NOTIFICATION_PMSG), (DMUS_PMSG**)&pEvent ))) { pEvent->dwType = DMUS_PMSGT_NOTIFICATION; pEvent->mtTime = mtStart + mtOffset; pEvent->dwFlags = DMUS_PMSGF_MUSICTIME | DMUS_PMSGF_TOOL_ATTIME; pEvent->dwPChannel = 0; pSD->m_pSegState->QueryInterface(IID_IUnknown, (void**)&pEvent->punkUser); pEvent->dwNotificationOption = DMUS_NOTIFICATION_MEASUREBEAT; pEvent->dwField1 = bCurrentBeat; pEvent->dwField2 = dwMeasure; pEvent->guidNotificationType = GUID_NOTIFICATION_MEASUREANDBEAT; pEvent->dwGroupID = 0xffffffff; IDirectMusicGraph* pGraph; if( SUCCEEDED( pSD->m_pSegState->QueryInterface( IID_IDirectMusicGraph, (void**)&pGraph ))) { pGraph->StampPMsg((DMUS_PMSG*) pEvent ); pGraph->Release(); } if(FAILED(pSD->m_pPerformance->SendPMsg((DMUS_PMSG*) pEvent ))) { pSD->m_pPerformance->FreePMsg( (DMUS_PMSG*)pEvent ); } } bCurrentBeat++; if( bCurrentBeat >= pSD->m_bBeatsPerMeasure ) { bCurrentBeat = 0; dwMeasure += 1; } mtStart += lQuantize; } return mtEnd; } HRESULT STDMETHODCALLTYPE CTimeSigTrack::Clone( MUSIC_TIME mtStart, MUSIC_TIME mtEnd, IDirectMusicTrack** ppTrack) { V_INAME(IDirectMusicTrack::Clone); V_PTRPTR_WRITE(ppTrack); HRESULT hr = S_OK; if((mtStart < 0 ) ||(mtStart > mtEnd)) { Trace(1,"Error: Clone failed on time signature track because of invalid start or end time.\n"); return E_INVALIDARG; } EnterCriticalSection(&m_CrSec); CTimeSigTrack *pDM; try { pDM = new CTimeSigTrack(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; }