windows-nt/Source/XPSP1/NT/multimedia/directx/dmusic/dmime/song.cpp
2020-09-26 16:20:57 +08:00

2325 lines
83 KiB
C++

//
// Copyright (c) 1998-2001 Microsoft Corporation
// song.cpp : Implementation of CSong
//
#include "dmime.h"
#include "song.h"
#include "..\shared\validp.h"
#include "..\shared\dmstrm.h"
#include "..\shared\Validate.h"
#include "debug.h"
CTrack::CTrack()
{
m_pTrack = NULL;
m_pTrack8 = NULL;
m_pTrackState = NULL;
m_bDone = FALSE;
m_dwPriority = 0;
m_dwPosition = 0;
m_dwFlags = DMUS_TRACKCONFIG_DEFAULT;
m_dwInternalFlags = 0;
m_dwGroupBits = 0xFFFFFFFF;
m_dwVirtualID = 0;
m_guidClassID = GUID_NULL;
}
CTrack::~CTrack()
{
assert( !( m_pTrackState && !m_pTrack ) ); // if we have state but no track, something's wrong
if( m_pTrack )
{
if( m_pTrackState )
{
m_pTrack->EndPlay( m_pTrackState ); // allow the track to delete its state data
}
m_pTrack->Release();
}
if ( m_pTrack8 )
{
m_pTrack8->Release();
}
}
HRESULT CTrackList::CreateCopyWithBlankState(CTrackList* pTrackList)
{
if( pTrackList )
{
CTrack* pTrack;
CTrack* pCopy;
pTrackList->Clear();
pTrack = (CTrack*)m_pHead;
while( pTrack )
{
pCopy = new CTrack;
if( pCopy )
{
// copy the IDirectMusicTrack pointer, but leave
// the track state blank.
*pCopy = *pTrack;
pCopy->SetNext(NULL);
pCopy->m_pTrackState = NULL;
assert( pCopy->m_pTrack );
pCopy->m_pTrack->AddRef();
if (pCopy->m_pTrack8)
{
pCopy->m_pTrack8->AddRef();
}
pTrackList->Cat( pCopy );
}
else
{
assert(FALSE); // out of memory
return E_OUTOFMEMORY;
}
pTrack = pTrack->GetNext();
}
}
else
{
assert(FALSE); // out of memory
return E_OUTOFMEMORY;
}
return S_OK;
}
CVirtualSegment::CVirtualSegment()
{
m_wszName[0] = 0;
m_pSourceSegment = NULL;
m_pPlaySegment = NULL;
m_pGraph = NULL;
m_dwFlags = 0;
m_dwID = 0;
m_dwNextPlayID = DMUS_SONG_NOSEG;
m_dwNextPlayFlags = 0;
m_mtTime = 0;
m_dwTransitionCount = 0;
m_pTransitions = NULL;
m_SegHeader.rtLength = 0;
m_SegHeader.dwFlags = 0;
m_SegHeader.dwRepeats = 0; /* Number of repeats. By default, 0. */
m_SegHeader.mtLength = 0xC00; /* Length, in music time. */
m_SegHeader.mtPlayStart = 0; /* Start of playback. By default, 0. */
m_SegHeader.mtLoopStart = 0; /* Start of looping portion. By default, 0. */
m_SegHeader.mtLoopEnd = 0; /* End of loop. Must be greater than dwPlayStart. By default equal to length. */
m_SegHeader.dwResolution = 0; /* Default resolution. */
}
CVirtualSegment::~CVirtualSegment()
{
if (m_pSourceSegment)
{
m_pSourceSegment->Release();
}
if (m_pPlaySegment)
{
m_pPlaySegment->Release();
}
if (m_pGraph)
{
m_pGraph->Release();
}
if (m_pTransitions)
{
delete [] m_pTransitions;
}
m_TrackList.Clear();
}
CTrack * CVirtualSegment::GetTrackByParam( CTrack * pCTrack,
REFGUID rguidType,DWORD dwGroupBits,DWORD dwIndex)
{
// If the caller was already part way through the list, it passes the current
// track. Otherwise, NULL to indicate start at the top.
if (pCTrack)
{
pCTrack = pCTrack->GetNext();
}
else
{
pCTrack = m_TrackList.GetHead();
}
while( pCTrack )
{
ASSERT(pCTrack->m_pTrack);
if( (pCTrack->m_dwGroupBits & dwGroupBits ) &&
(pCTrack->m_dwFlags & DMUS_TRACKCONFIG_CONTROL_ENABLED))
{
if( (GUID_NULL == rguidType) || (pCTrack->m_pTrack->IsParamSupported( rguidType ) == S_OK ))
{
if( 0 == dwIndex )
{
return pCTrack;
}
dwIndex--;
}
}
pCTrack = pCTrack->GetNext();
}
return NULL;
}
void CVirtualSegmentList::Clear()
{
CVirtualSegment *pVirtualSegment;
while (pVirtualSegment = RemoveHead())
{
delete pVirtualSegment;
}
}
CSongSegment::CSongSegment()
{
m_pSegment = NULL;
m_dwLoadID = 0;
}
CSongSegment::~CSongSegment()
{
if (m_pSegment)
{
m_pSegment->Release();
}
}
HRESULT CSongSegmentList::AddSegment(CSegment *pSegment, DWORD dwLoadID)
{
CSongSegment *pSeg = new CSongSegment;
if (pSeg)
{
pSeg->m_dwLoadID = dwLoadID;
pSeg->m_pSegment = pSegment;
pSegment->AddRef();
AddTail(pSeg);
return S_OK;
}
return E_OUTOFMEMORY;
}
void CSongSegmentList::Clear()
{
CSongSegment *pSongSegment;
while (pSongSegment = RemoveHead())
{
delete pSongSegment;
}
}
CSong::CSong()
{
InitializeCriticalSection(&m_CriticalSection);
m_dwStartSegID = DMUS_SONG_NOSEG;
m_pAudioPathConfig = NULL;
m_fPartialLoad = FALSE;
m_cRef = 1;
m_dwFlags = 0;
m_dwValidData = DMUS_OBJ_CLASS; // upon creation, only this data is valid
memset(&m_guidObject,0,sizeof(m_guidObject));
memset(&m_ftDate, 0,sizeof(m_ftDate));
memset(&m_vVersion, 0,sizeof(m_vVersion));
m_pUnkDispatch = NULL;
InterlockedIncrement(&g_cComponent);
m_fZombie = false;
TraceI(2, "Song %lx created\n", this );
}
CSong::~CSong()
{
Clear();
if (m_pUnkDispatch)
{
m_pUnkDispatch->Release(); // free IDispatch implementation we may have borrowed
}
DeleteCriticalSection(&m_CriticalSection);
InterlockedDecrement(&g_cComponent);
TraceI(2, "Song %lx destroyed\n", this );
}
void CSong::Clear()
{
if (m_pAudioPathConfig)
{
m_pAudioPathConfig->Release();
m_pAudioPathConfig = NULL;
}
m_GraphList.Clear();
m_PlayList.Clear();
m_SegmentList.Clear();
m_VirtualSegmentList.Clear();
m_dwStartSegID = DMUS_SONG_NOSEG;
m_fPartialLoad = FALSE;
m_dwFlags = 0;
m_dwValidData = DMUS_OBJ_CLASS; // upon creation, only this data is valid
}
STDMETHODIMP_(void) CSong::Zombie()
{
Clear();
m_fZombie = true;
}
STDMETHODIMP CSong::QueryInterface(
const IID &iid, // @parm Interface to query for
void **ppv) // @parm The requested interface will be returned here
{
V_INAME(CSong::QueryInterface);
V_PTRPTR_WRITE(ppv);
V_REFGUID(iid);
*ppv = NULL;
if (iid == IID_IUnknown || iid == IID_IDirectMusicSong)
{
*ppv = static_cast<IDirectMusicSong*>(this);
}
else if (iid == IID_CSong)
{
*ppv = static_cast<CSong*>(this);
}
else if (iid == IID_IPersistStream)
{
*ppv = static_cast<IPersistStream*>(this);
}
else if(iid == IID_IDirectMusicObject)
{
*ppv = static_cast<IDirectMusicObject*>(this);
}
else if (iid == IID_IDirectMusicObjectP)
{
*ppv = static_cast<IDirectMusicObjectP*>(this);
}
else if(iid == IID_IDispatch)
{
// A helper scripting object implements IDispatch, which we expose via COM aggregation.
if (!m_pUnkDispatch)
{
// Create the helper object
::CoCreateInstance(
CLSID_AutDirectMusicSong,
static_cast<IDirectMusicSong*>(this),
CLSCTX_INPROC_SERVER,
IID_IUnknown,
reinterpret_cast<void**>(&m_pUnkDispatch));
}
if (m_pUnkDispatch)
{
return m_pUnkDispatch->QueryInterface(IID_IDispatch, ppv);
}
}
if (*ppv == NULL)
{
Trace(4,"Warning: Request to query unknown interface on Song object\n");
return E_NOINTERFACE;
}
reinterpret_cast<IUnknown*>(this)->AddRef();
return S_OK;
}
STDMETHODIMP_(ULONG) CSong::AddRef()
{
return InterlockedIncrement(&m_cRef);
}
STDMETHODIMP_(ULONG) CSong::Release()
{
if (!InterlockedDecrement(&m_cRef))
{
m_cRef = 100; // artificial reference count to prevent reentrency due to COM aggregation
delete this;
return 0;
}
return m_cRef;
}
STDMETHODIMP CSong::Compose( )
{
if (m_fZombie)
{
Trace(1, "Error: Call of IDirectMusicSong::Compose after the song has been garbage collected. "
"It is invalid to continue using a song after releasing it from the loader (ReleaseObject/ReleaseObjectByUnknown) "
"and then calling CollectGarbage or Release on the loader.");
return DMUS_S_GARBAGE_COLLECTED;
}
HRESULT hr = S_OK;
EnterCriticalSection(&m_CriticalSection);
// Go through the seg ref list and create master composition tracks for each composing track.
TList<ComposingTrack> MasterTrackList;
CVirtualSegment* pVirtualSegment = m_VirtualSegmentList.GetHead();
for (; pVirtualSegment; pVirtualSegment = pVirtualSegment->GetNext())
{
if (!pVirtualSegment->m_pPlaySegment)
{
Trace(1,"Error: Corrupt song, one or more virtual segments do not resolve to real segments. Unable to compose.\n");
hr = E_POINTER;
break;
}
CSegment *pSegment = pVirtualSegment->m_pPlaySegment;
CTrack* pTrack = pSegment->m_TrackList.GetHead();
for (; pTrack; pTrack = pTrack->GetNext())
{
if (pTrack->m_dwFlags & DMUS_TRACKCONFIG_COMPOSING)
{
DWORD dwTrackGroup = pTrack->m_dwGroupBits;
// filter out any group bits already covered by other master tracks of same type
TListItem<ComposingTrack>* pMaster = MasterTrackList.GetHead();
for (; pMaster; pMaster = pMaster->GetNext())
{
ComposingTrack& rMaster = pMaster->GetItemValue();
if (rMaster.GetTrackID() == pTrack->m_guidClassID)
{
DWORD dwMaster = rMaster.GetTrackGroup();
if (dwMaster == dwTrackGroup)
{
// Exact match: put the track here.
hr = rMaster.AddTrack(pVirtualSegment, pTrack);
dwTrackGroup = 0;
break;
}
DWORD dwIntersection = dwMaster & dwTrackGroup;
if (dwIntersection)
{
dwTrackGroup |= ~dwIntersection;
}
}
}
// If we've still got any group bits left, add a new composing track
if (dwTrackGroup)
{
TListItem<ComposingTrack>* pTrackItem = new TListItem<ComposingTrack>;
if (!pTrackItem)
{
hr = E_OUTOFMEMORY;
}
else
{
ComposingTrack& rTrack = pTrackItem->GetItemValue();
rTrack.SetTrackGroup(dwTrackGroup);
rTrack.SetTrackID(pTrack->m_guidClassID);
rTrack.SetPriority(pTrack->m_dwPriority);
// Add tracks in priority order (higher priority first)
pMaster = MasterTrackList.GetHead();
TListItem<ComposingTrack>* pPrevious = NULL;
for (; pMaster; pMaster = pMaster->GetNext())
{
ComposingTrack& rMaster = pMaster->GetItemValue();
if (pTrack->m_dwPriority > rMaster.GetPriority()) break;
pPrevious = pMaster;
}
if (!pPrevious) // this has higher priority than anything in the list
{
MasterTrackList.AddHead(pTrackItem);
}
else // lower priority than pPrevious, higher than pMaster
{
pTrackItem->SetNext(pMaster);
pPrevious->SetNext(pTrackItem);
}
hr = pTrackItem->GetItemValue().AddTrack(pVirtualSegment, pTrack);
}
}
}
if (FAILED(hr)) break;
}
if (FAILED(hr)) break;
}
// Call compose on each master composition track
if (SUCCEEDED(hr))
{
TListItem<ComposingTrack>* pMaster = MasterTrackList.GetHead();
if (pMaster)
{
for (; pMaster; pMaster = pMaster->GetNext())
{
hr = pMaster->GetItemValue().Compose(this);
}
}
else hr = S_FALSE;
}
LeaveCriticalSection(&m_CriticalSection);
return hr;
}
STDMETHODIMP CSong::Download(IUnknown *pAudioPath)
{
V_INAME(IDirectMusicSong::Download);
V_INTERFACE(pAudioPath);
if (m_fZombie)
{
Trace(1, "Error: Call of IDirectMusicSong::Download after the song has been garbage collected. "
"It is invalid to continue using a song after releasing it from the loader (ReleaseObject/ReleaseObjectByUnknown) "
"and then calling CollectGarbage or Release on the loader.");
return DMUS_S_GARBAGE_COLLECTED;
}
DWORD dwSuccess = 0;
HRESULT hr = S_OK;
HRESULT hrFail = S_OK;
EnterCriticalSection(&m_CriticalSection);
CSegment *pSegment = m_PlayList.GetHead();
for (;pSegment;pSegment = pSegment->GetNext())
{
if (SUCCEEDED(hr = pSegment->Download(pAudioPath)))
{
// count partial successes, so that S_FALSE will be returned if we have, e.g.,
// one partial success followed by one failure
dwSuccess++;
}
if (hr != S_OK)
{
// keep track of partial successes so that they always percolate up
hrFail = hr;
}
}
LeaveCriticalSection(&m_CriticalSection);
if (hrFail != S_OK && dwSuccess)
{
Trace(1,"Warning: Only %ld of the total %ld segments successfully downloaded.\n",
dwSuccess,m_PlayList.GetCount());
hr = S_FALSE;
}
return hr;
}
STDMETHODIMP CSong::Unload(IUnknown *pAudioPath)
{
V_INAME(IDirectMusicSong::Unload);
V_INTERFACE(pAudioPath);
if (m_fZombie)
{
Trace(1, "Error: Call of IDirectMusicSong::Unload after the song has been garbage collected. "
"It is invalid to continue using a song after releasing it from the loader (ReleaseObject/ReleaseObjectByUnknown) "
"and then calling CollectGarbage or Release on the loader.");
return DMUS_S_GARBAGE_COLLECTED;
}
DWORD dwSuccess = 0;
HRESULT hr = S_OK;
HRESULT hrFail = S_OK;
EnterCriticalSection(&m_CriticalSection);
CSegment *pSegment = m_PlayList.GetHead();
for (;pSegment;pSegment = pSegment->GetNext())
{
if (SUCCEEDED(hr = pSegment->Unload(pAudioPath)))
{
dwSuccess++;
}
else
{
hrFail = hr;
}
}
LeaveCriticalSection(&m_CriticalSection);
if (FAILED(hrFail) && dwSuccess)
{
Trace(1,"Warning: Only %ld of the total %ld segments successfully unloaded.\n",
dwSuccess,m_PlayList.GetCount());
hr = S_FALSE;
}
return hr;
}
/*STDMETHODIMP CSong::Clone(IDirectMusicSong **ppSong)
{
V_INAME(IDirectMusicSong::Clone);
V_PTRPTR_WRITE_OPT(ppSong);
HRESULT hr = E_OUTOFMEMORY;
CSong *pSong = new CSong();
if (*ppSong)
{
*ppSong = pSong;
EnterCriticalSection(&m_CriticalSection);
CSegment *pSegment = m_PlayList.GetHead();
for (;pSegment;pSegment = pSegment->GetNext())
{
IDirectMusicSegment *pISeg;
hr = pSegment->Clone(0,pSegment->m_mtLength,&pISeg);
if (SUCCEEDED(hr))
{
CSegment *pCopy = (CSegment *) pISeg;
pSong->m_PlayList.AddTail(pCopy);
pCopy->m_pSong = pSong;
}
}
pSong->m_dwValidData = m_dwValidData;
pSong->m_guidObject = m_guidObject;
pSong->m_ftDate = m_ftDate;
pSong->m_vVersion = m_vVersion;
wcscpy(pSong->m_wszName,m_wszName);
wcscpy(pSong->m_wszCategory,m_wszCategory);
wcscpy(pSong->m_wszFileName,m_wszFileName);
pSong->m_dwVersion = m_dwVersion;
pSong->m_dwFlags = m_dwFlags;
pSong->m_pAudioPathConfig = m_pAudioPathConfig;
if (m_pAudioPathConfig)
m_pAudioPathConfig->AddRef();
LeaveCriticalSection(&m_CriticalSection);
}
return hr;
}
*/
STDMETHODIMP CSong::GetParam( REFGUID rguidType,
DWORD dwGroupBits,
DWORD dwIndex,
MUSIC_TIME mtTime,
MUSIC_TIME* pmtNext,
void* pParam)
{
V_INAME(IDirectMusiCSong::GetParam);
V_REFGUID(rguidType);
V_PTR_WRITE_OPT(pmtNext,MUSIC_TIME);
if (m_fZombie)
{
Trace(1, "Error: Call of IDirectMusicSong::GetParam after the song has been garbage collected. "
"It is invalid to continue using a song after releasing it from the loader (ReleaseObject/ReleaseObjectByUnknown) "
"and then calling CollectGarbage or Release on the loader.");
return DMUS_S_GARBAGE_COLLECTED;
}
HRESULT hr = DMUS_E_TRACK_NOT_FOUND;
/* BOOL fMultipleTry = FALSE;
if (dwIndex == DMUS_SEG_ANYTRACK)
{
dwIndex = 0;
fMultipleTry = TRUE;
}*/
EnterCriticalSection(&m_CriticalSection);
/*CSegment *pSegment = m_PlayList.GetHead();
for (;pSegment;pSegment = pSegment->GetNext())
{
if (pSegment->m_mtStart <= mtTime &&
mtTime < pSegment->m_mtStart + pSegment->m_mtLength)
{
hr = pSegment->GetParam(rguidType, dwGroupBits, dwIndex, mtTime - pSegment->m_mtStart, pmtNext, pParam);
if (SUCCEEDED(hr)) break;
}
}*/
CVirtualSegment *pVirtualSegment = m_VirtualSegmentList.GetHead();
for (;pVirtualSegment;pVirtualSegment = pVirtualSegment->GetNext())
{
if (pVirtualSegment->m_mtTime <= mtTime &&
pVirtualSegment->m_pPlaySegment &&
mtTime < pVirtualSegment->m_mtTime + pVirtualSegment->m_pPlaySegment->m_mtLength)
{
hr = pVirtualSegment->m_pPlaySegment->GetParam(rguidType, dwGroupBits, dwIndex, mtTime - pVirtualSegment->m_mtTime, pmtNext, pParam);
if (SUCCEEDED(hr)) break;
}
}
/* for (;pVirtualSegment;pVirtualSegment = pVirtualSegment->GetNext())
{
if (pVirtualSegment->m_mtTime <= mtTime)
{
CTrack* pCTrack;
pCTrack = pVirtualSegment->GetTrackByParam(NULL, rguidType,dwGroupBits, dwIndex);
while (pCTrack)
{
if (pCTrack->m_pTrack8)
{
REFERENCE_TIME rtNext, *prtNext;
// We need to store the next time in a 64 bit pointer. But, don't
// make 'em fill it in unless the caller requested it.
if (pmtNext)
{
prtNext = &rtNext;
}
else
{
prtNext = NULL;
}
hr = pCTrack->m_pTrack8->GetParamEx( rguidType, mtTime - pVirtualSegment->m_mtTime, prtNext, pParam,
NULL, 0 );
if (pmtNext)
{
*pmtNext = (MUSIC_TIME) rtNext;
}
}
else
{
hr = pCTrack->m_pTrack->GetParam( rguidType, mtTime - pVirtualSegment->m_mtTime, pmtNext, pParam );
/ * if( pmtNext && (( *pmtNext == 0 ) || (*pmtNext > (m_mtLength - mtTime))))
{
*pmtNext = m_mtLength - mtTime;
}* /
}
// If nothing was found and dwIndex was DMUS_SEG_ANYTRACK, try again...
if (fMultipleTry && (hr == DMUS_E_NOT_FOUND))
{
pCTrack = pVirtualSegment->GetTrackByParam(pCTrack, rguidType,dwGroupBits, dwIndex);
}
else
{
pCTrack = NULL;
}
}
}
}*/
if (FAILED(hr) && pmtNext)
{
// return the time of the first segment after mtTime (or 0 if there is no such segment)
pVirtualSegment = m_VirtualSegmentList.GetHead();
for (;pVirtualSegment;pVirtualSegment = pVirtualSegment->GetNext())
{
if (pVirtualSegment->m_mtTime > mtTime)
{
*pmtNext = pVirtualSegment->m_mtTime;
break;
}
}
}
LeaveCriticalSection(&m_CriticalSection);
return hr;
}
HRESULT CSong::Instantiate()
{
V_INAME(IDirectMusicSong::Instantiate);
EnterCriticalSection(&m_CriticalSection);
CVirtualSegment *pRef = m_VirtualSegmentList.GetHead();
m_PlayList.Clear();
for (;pRef;pRef = pRef->GetNext())
{
// the constructor below does an AddRef.
CSegment *pSegment = new CSegment(&pRef->m_SegHeader,pRef->m_pSourceSegment);
if (pSegment)
{
if (pRef->m_wszName[0])
{
wcscpy(pSegment->m_wszName,pRef->m_wszName);
pSegment->m_dwValidData |= DMUS_OBJ_NAME;
}
CTrack *pTrack;
for (pTrack = pRef->m_TrackList.GetHead();pTrack;pTrack = pTrack->GetNext())
{
CTrack *pCopy = new CTrack;
if( pCopy )
{
*pCopy = *pTrack;
pCopy->SetNext(NULL);
pCopy->m_pTrackState = NULL;
pCopy->m_pTrack->AddRef();
if (pCopy->m_pTrack8)
{
pCopy->m_pTrack8->AddRef();
}
// The tracks were in backwards order. This puts them back in order, and ahead of the segment tracks.
pSegment->m_TrackList.AddHead( pCopy );
}
}
pSegment->m_pSong = this;
pSegment->m_dwPlayID = pRef->m_dwID;
//Trace(0,"Intantiating PlaySegment %ls with ID %ld.\n",pRef->m_wszName,pRef->m_dwID);
pSegment->m_dwNextPlayFlags = pRef->m_dwNextPlayFlags;
pSegment->m_dwNextPlayID = pRef->m_dwNextPlayID;
m_PlayList.AddTail(pSegment);
if (pRef->m_pPlaySegment) pRef->m_pPlaySegment->Release();
pRef->m_pPlaySegment = pSegment;
pRef->m_pPlaySegment->AddRef();
}
}
LeaveCriticalSection(&m_CriticalSection);
return S_OK;
}
HRESULT CSong::EnumSegment( DWORD dwIndex,IDirectMusicSegment **ppSegment)
{
V_INAME(IDirectMusicSong::EnumSegment);
V_PTRPTR_WRITE (ppSegment);
if (m_fZombie)
{
Trace(1, "Error: Call of IDirectMusicSong::EnumSegment after the song has been garbage collected. "
"It is invalid to continue using a song after releasing it from the loader (ReleaseObject/ReleaseObjectByUnknown) "
"and then calling CollectGarbage or Release on the loader.");
return DMUS_S_GARBAGE_COLLECTED;
}
HRESULT hr = S_FALSE;
EnterCriticalSection(&m_CriticalSection);
CSegment *pSegment = m_PlayList.GetHead();
for (;pSegment && dwIndex;pSegment = pSegment->GetNext()) dwIndex--;
if (pSegment)
{
*ppSegment = static_cast<IDirectMusicSegment*>(pSegment);
pSegment->AddRef();
hr = S_OK;
}
LeaveCriticalSection(&m_CriticalSection);
return hr;
}
HRESULT CSong::GetPlaySegment( DWORD dwIndex,CSegment **ppSegment)
{
HRESULT hr = S_FALSE;
EnterCriticalSection(&m_CriticalSection);
CSegment *pSegment = m_PlayList.GetHead();
for (;pSegment;pSegment = pSegment->GetNext())
{
if (pSegment->m_dwPlayID == dwIndex)
{
*ppSegment = pSegment;
pSegment->AddRef();
hr = S_OK;
break;
}
}
LeaveCriticalSection(&m_CriticalSection);
return hr;
}
STDMETHODIMP CSong::GetSegment(WCHAR *wszName, IDirectMusicSegment **ppSegment)
{
V_INAME(IDirectMusicSong::GetSegment);
V_PTRPTR_WRITE(ppSegment);
if (m_fZombie)
{
Trace(1, "Error: Call of IDirectMusicSong::GetSegment after the song has been garbage collected. "
"It is invalid to continue using a song after releasing it from the loader (ReleaseObject/ReleaseObjectByUnknown) "
"and then calling CollectGarbage or Release on the loader.");
return DMUS_S_GARBAGE_COLLECTED;
}
HRESULT hr = S_FALSE;
CSegment *pSegment;
if (wszName)
{
V_BUFPTR_READ(wszName,2);
EnterCriticalSection(&m_CriticalSection);
pSegment = m_PlayList.GetHead();
for (;pSegment;pSegment = pSegment->GetNext())
{
if (_wcsicmp(pSegment->m_wszName, wszName) == 0)
{
pSegment->AddRef();
hr = S_OK;
break;
}
}
LeaveCriticalSection(&m_CriticalSection);
}
else
{
hr = GetPlaySegment( m_dwStartSegID,&pSegment);
}
if (hr == S_OK)
{
*ppSegment = static_cast<IDirectMusicSegment*>(pSegment);
}
else
{
#ifdef DBG
if (wszName)
{
Trace(1,"Error: Unable to find segment %ls in song.\n",wszName);
}
else
{
Trace(1,"Error: Unable to find starting segment in the song.\n");
}
#endif
}
return hr;
}
STDMETHODIMP CSong::GetAudioPathConfig(IUnknown ** ppAudioPathConfig)
{
V_INAME(IDirectMusicSegment::GetAudioPathConfig);
V_PTRPTR_WRITE(ppAudioPathConfig);
if (m_fZombie)
{
Trace(1, "Error: Call of IDirectMusicSong::GetAudioPathConfig after the song has been garbage collected. "
"It is invalid to continue using a song after releasing it from the loader (ReleaseObject/ReleaseObjectByUnknown) "
"and then calling CollectGarbage or Release on the loader.");
return DMUS_S_GARBAGE_COLLECTED;
}
HRESULT hr;
EnterCriticalSection(&m_CriticalSection);
if (m_pAudioPathConfig)
{
hr = m_pAudioPathConfig->QueryInterface(IID_IUnknown,(void **)ppAudioPathConfig);
}
else
{
Trace(2,"Warning: No embedded audiopath configuration in the song.\n");
hr = DMUS_E_NO_AUDIOPATH_CONFIG;
}
LeaveCriticalSection(&m_CriticalSection);
return hr;
}
/////////////////////////////////////////////////////////////////////////////
// IPersist
HRESULT CSong::GetClassID( CLSID* pClassID )
{
V_INAME(CSong::GetClassID);
V_PTR_WRITE(pClassID, CLSID);
if (m_fZombie)
{
Trace(1, "Error: Call of IDirectMusicSong::GetClassID after the song has been garbage collected. "
"It is invalid to continue using a song after releasing it from the loader (ReleaseObject/ReleaseObjectByUnknown) "
"and then calling CollectGarbage or Release on the loader.");
return DMUS_S_GARBAGE_COLLECTED;
}
*pClassID = CLSID_DirectMusicSong;
return S_OK;
}
/////////////////////////////////////////////////////////////////////////////
// IPersistStream functions
HRESULT CSong::IsDirty()
{
return E_NOTIMPL;
}
HRESULT CSong::Load( IStream* pIStream )
{
V_INAME(CSong::Load);
V_INTERFACE(pIStream);
// Song format temporarily turned off for DX8 release.
return E_NOTIMPL;
/*
if (m_fZombie)
{
Trace(1, "Error: Call of IDirectMusicSong::Load after the song has been garbage collected. "
"It is invalid to continue using a song after releasing it from the loader (ReleaseObject/ReleaseObjectByUnknown) "
"and then calling CollectGarbage or Release on the loader.");
return DMUS_S_GARBAGE_COLLECTED;
}
// Create RIFF parser.
CRiffParser Parser(pIStream);
RIFFIO ckMain;
HRESULT hr = S_OK;
// First, clear the song in case it is being read into a second time.
Clear();
Parser.EnterList(&ckMain);
if (Parser.NextChunk(&hr))
{
if (ckMain.fccType == DMUS_FOURCC_SONG_FORM)
{
EnterCriticalSection(&m_CriticalSection);
RIFFIO ckNext;
RIFFIO ckChild;
IDirectMusicContainer *pContainer = NULL; // For handling embedded container with linked objects.
Parser.EnterList(&ckNext);
while(Parser.NextChunk(&hr))
{
switch(ckNext.ckid)
{
case DMUS_FOURCC_SONG_CHUNK:
DMUS_IO_SONG_HEADER ioSongHdr;
ioSongHdr.dwFlags = 0;
hr = Parser.Read(&ioSongHdr, sizeof(DMUS_IO_SONG_HEADER));
if(SUCCEEDED(hr))
{
m_dwFlags = ioSongHdr.dwFlags;
m_dwStartSegID = ioSongHdr.dwStartSegID;
}
break;
case DMUS_FOURCC_GUID_CHUNK:
if( ckNext.cksize == sizeof(GUID) )
{
hr = Parser.Read(&m_guidObject, sizeof(GUID));
if( SUCCEEDED(hr) )
{
m_dwValidData |= DMUS_OBJ_OBJECT;
}
}
break;
case DMUS_FOURCC_VERSION_CHUNK:
hr = Parser.Read( &m_vVersion, sizeof(DMUS_VERSION) );
if( SUCCEEDED(hr) )
{
m_dwValidData |= DMUS_OBJ_VERSION;
}
break;
case DMUS_FOURCC_CATEGORY_CHUNK:
hr = Parser.Read( m_wszCategory, sizeof(WCHAR)*DMUS_MAX_CATEGORY );
if( SUCCEEDED(hr) )
{
m_dwValidData |= DMUS_OBJ_CATEGORY;
}
break;
case DMUS_FOURCC_DATE_CHUNK:
if( sizeof(FILETIME) == ckNext.cksize )
{
hr = Parser.Read( &m_ftDate, sizeof(FILETIME) );
if( SUCCEEDED(hr) )
{
m_dwValidData |= DMUS_OBJ_DATE;
}
}
break;
case FOURCC_LIST:
case FOURCC_RIFF:
switch(ckNext.fccType)
{
case DMUS_FOURCC_UNFO_LIST:
Parser.EnterList(&ckChild);
while(Parser.NextChunk(&hr))
{
switch( ckChild.ckid )
{
case DMUS_FOURCC_UNAM_CHUNK:
{
hr = Parser.Read(&m_wszName, sizeof(m_wszName));
if(SUCCEEDED(hr) )
{
m_dwValidData |= DMUS_OBJ_NAME;
}
break;
}
default:
break;
}
}
Parser.LeaveList();
break;
case DMUS_FOURCC_CONTAINER_FORM:
// An embedded container RIFF chunk which includes a bunch
// of objects referenced by the song. This should precede the
// segments and gets loaded prior to them. Loading this
// causes all of its objects to get SetObject'd in the loader,
// so they later get pulled in as requested by the tracks in the segments.
// After the tracks are loaded, the loader references are
// released by a call to release the IDirectMusicContainer.
{
DMUS_OBJECTDESC Desc;
IDirectMusicLoader *pLoader;
IDirectMusicGetLoader *pGetLoader;
HRESULT hr = pIStream->QueryInterface(IID_IDirectMusicGetLoader,(void **) &pGetLoader);
if (SUCCEEDED(hr))
{
if (SUCCEEDED(pGetLoader->GetLoader(&pLoader)))
{
// Move back stream's current position
Parser.SeekBack();
Desc.dwSize = sizeof(Desc);
Desc.dwValidData = DMUS_OBJ_CLASS | DMUS_OBJ_STREAM;
Desc.guidClass = CLSID_DirectMusicContainer;
Desc.pStream = pIStream;
pLoader->GetObject(&Desc,IID_IDirectMusicContainer,(void **) &pContainer);
if (pContainer)
{
// Don't cache the container object! We want it and the
// objects it references to go away when the segment is done loading.
IDirectMusicObject *pObject = NULL;
pContainer->QueryInterface(IID_IDirectMusicObject,(void **)&pObject);
if (pObject)
{
pLoader->ReleaseObject(pObject);
pObject->Release();
}
}
// Now, seek to the end of this chunk.
Parser.SeekForward();
pLoader->Release();
}
pGetLoader->Release();
}
}
break;
case DMUS_FOURCC_SONGSEGMENTS_LIST:
hr = LoadSegmentList(&Parser); //pIStream, pIDirectMusicStream, ckNext);
break;
case DMUS_FOURCC_SEGREFS_LIST:
hr = LoadVirtualSegmentList(&Parser);
break;
case DMUS_FOURCC_AUDIOPATH_FORM:
// Move back to start of this chunk.
Parser.SeekBack();
hr = LoadAudioPath(pIStream);
// Now, seek to the end of this chunk.
Parser.SeekForward();
break;
default:
break;
}
break;
default:
break;
}
}
Parser.LeaveList();
LeaveCriticalSection(&m_CriticalSection);
if (pContainer)
{
pContainer->Release();
}
if( SUCCEEDED(hr) )
{
if( m_fPartialLoad & PARTIALLOAD_E_FAIL )
{
if( m_fPartialLoad & PARTIALLOAD_S_OK )
{
Trace(1,"Error: Song load was incomplete, some components failed loading.\n");
hr = DMUS_S_PARTIALLOAD;
}
else
{
Trace(1,"Error: Song load failed because all components failed loading.\n");
hr = DMUS_E_ALL_TRACKS_FAILED;
}
}
}
}
else
{
// Couldn't find the chunk header for a song.
// But, maybe this is actually a segment, in which case see if
// the segment object will load it.
CSegment *pSegment = new CSegment;
if (pSegment)
{
pSegment->AddRef(); // Segment::Load (and possibly others) may need the refcount
// Force the version so audiopath functionality will be supported.
pSegment->m_dwVersion = 8;
Parser.SeekBack();
hr = pSegment->Load(pIStream);
if (SUCCEEDED(hr))
{
DMUS_OBJECTDESC Desc;
Desc.dwSize = sizeof (Desc);
pSegment->GetDescriptor(&Desc);
Desc.guidClass = CLSID_DirectMusicSong;
SetDescriptor(&Desc);
// AddSegment addref's by one.
m_SegmentList.AddSegment(pSegment,0);
pSegment->GetAudioPathConfig((IUnknown **) &m_pAudioPathConfig);
m_dwStartSegID = 0; // Points to this segment.
CVirtualSegment *pVirtual = new CVirtualSegment;
if (pVirtual)
{
pVirtual->m_pSourceSegment = pSegment;
pSegment->AddRef();
pVirtual->m_SegHeader.dwRepeats = pSegment->m_dwRepeats;
pVirtual->m_SegHeader.dwResolution = pSegment->m_dwResolution;
pVirtual->m_SegHeader.mtLength = pSegment->m_mtLength;
pVirtual->m_SegHeader.mtLoopEnd = pSegment->m_mtLoopEnd;
pVirtual->m_SegHeader.mtLoopStart = pSegment->m_mtLoopStart;
pVirtual->m_SegHeader.mtPlayStart = pSegment->m_mtStart;
pVirtual->m_SegHeader.rtLength = pSegment->m_rtLength;
pVirtual->m_SegHeader.dwFlags = pSegment->m_dwSegFlags;
if (pSegment->m_dwValidData & DMUS_OBJ_NAME)
{
wcscpy(pVirtual->m_wszName,pSegment->m_wszName);
}
m_VirtualSegmentList.AddHead(pVirtual);
}
else
{
hr = E_OUTOFMEMORY;
}
pSegment->Release(); // release the initial AddRef
}
if (FAILED(hr))
{
delete pSegment;
}
}
else
{
hr = E_OUTOFMEMORY;
}
}
}
// If there are no virtual segments, clear the song and fail the load
if ( !m_VirtualSegmentList.GetHead() )
{
Clear();
hr = DMUS_E_NOT_INIT;
}
if (SUCCEEDED(hr)) Instantiate();
return hr;*/
}
HRESULT CSong::LoadAudioPath(IStream *pStream)
{
assert(pStream);
CAudioPathConfig *pPath = new CAudioPathConfig;
if (pPath == NULL) {
return E_OUTOFMEMORY;
}
HRESULT hr = pPath->Load(pStream);
EnterCriticalSection(&m_CriticalSection);
if(m_pAudioPathConfig)
{
m_pAudioPathConfig->Release();
}
m_pAudioPathConfig = pPath;
LeaveCriticalSection(&m_CriticalSection);
return hr;
}
HRESULT CSong::LoadReferencedSegment(CSegment **ppSegment, CRiffParser *pParser)
{
IDirectMusicLoader* pLoader = NULL;
IDirectMusicGetLoader *pIGetLoader;
HRESULT hr = pParser->GetStream()->QueryInterface( IID_IDirectMusicGetLoader,(void **) &pIGetLoader );
if (FAILED(hr)) return hr;
hr = pIGetLoader->GetLoader(&pLoader);
pIGetLoader->Release();
if (FAILED(hr)) return hr;
DMUS_OBJECTDESC desc;
ZeroMemory(&desc, sizeof(desc));
RIFFIO ckNext;
pParser->EnterList(&ckNext);
while(pParser->NextChunk(&hr))
{
switch(ckNext.ckid)
{
case DMUS_FOURCC_REF_CHUNK:
DMUS_IO_REFERENCE ioDMRef;
hr = pParser->Read(&ioDMRef, sizeof(DMUS_IO_REFERENCE));
if(SUCCEEDED(hr))
{
if (ioDMRef.guidClassID != CLSID_DirectMusicSegment)
{
Trace(1,"Error: Invalid segment reference in song.\n");
hr = DMUS_E_CANNOTREAD;
}
else
{
desc.guidClass = ioDMRef.guidClassID;
desc.dwValidData |= ioDMRef.dwValidData;
desc.dwValidData |= DMUS_OBJ_CLASS;
}
}
break;
case DMUS_FOURCC_GUID_CHUNK:
hr = pParser->Read(&(desc.guidObject), sizeof(GUID));
if(SUCCEEDED(hr))
{
desc.dwValidData |= DMUS_OBJ_OBJECT;
}
break;
case DMUS_FOURCC_NAME_CHUNK:
hr = pParser->Read(desc.wszName, sizeof(desc.wszName));
if(SUCCEEDED(hr))
{
desc.dwValidData |= DMUS_OBJ_NAME;
}
break;
case DMUS_FOURCC_FILE_CHUNK:
hr = pParser->Read(desc.wszFileName, sizeof(desc.wszFileName));
if(SUCCEEDED(hr))
{
desc.dwValidData |= DMUS_OBJ_FILENAME;
}
break;
case DMUS_FOURCC_CATEGORY_CHUNK:
hr = pParser->Read(desc.wszCategory, sizeof(desc.wszCategory));
if(SUCCEEDED(hr))
{
desc.dwValidData |= DMUS_OBJ_CATEGORY;
}
break;
default:
break;
}
}
pParser->LeaveList();
if(SUCCEEDED(hr))
{
desc.dwSize = sizeof(DMUS_OBJECTDESC);
hr = pLoader->GetObject(&desc, IID_CSegment, (void**)ppSegment);
// Once we get the object, we need to ensure that the same object is never
// connected up to any other songs (or this one, too.)
// So, we ensure that the loader doesn't keep it around.
if (SUCCEEDED(hr))
{
IDirectMusicObject *pObject;
if (SUCCEEDED((*ppSegment)->QueryInterface(IID_IDirectMusicObject,(void **)&pObject)))
{
pLoader->ReleaseObject(pObject);
pObject->Release();
}
// If the segment has a next pointer, it still must be in another song. This
// should never happen, but being paranoid...
if ((*ppSegment)->GetNext())
{
*ppSegment = NULL;
hr = E_FAIL;
TraceI(0,"Error: Attempt to load song segment that is already referenced by another song. \n");
}
}
}
if (pLoader)
{
pLoader->Release();
}
return hr;
}
HRESULT CSong::LoadSegmentList(CRiffParser *pParser)
{
assert(pParser);
RIFFIO ckNext, ckChild;
DWORD dwSegmentCount = 0;
HRESULT hr = S_OK;
pParser->EnterList(&ckNext);
while(pParser->NextChunk(&hr))
{
switch(ckNext.ckid)
{
case FOURCC_LIST:
if (ckNext.fccType == DMUS_FOURCC_SONGSEGMENT_LIST)
{
pParser->EnterList(&ckChild);
while (pParser->NextChunk(&hr))
{
switch(ckChild.ckid)
{
case FOURCC_RIFF:
case FOURCC_LIST:
if ((ckChild.fccType == DMUS_FOURCC_SEGMENT_FORM) ||
(ckChild.fccType == DMUS_FOURCC_REF_LIST))
{
CSegment *pSegment = NULL;
if (ckChild.fccType == DMUS_FOURCC_SEGMENT_FORM)
{
pSegment = new CSegment;
if (pSegment)
{
pSegment->AddRef(); // Segment::Load may need a refcount
// Force the version so audiopath functionality will be supported.
pSegment->m_dwVersion = 8;
// Move back to start of this chunk.
pParser->SeekBack();
hr = pSegment->Load(pParser->GetStream());
pParser->SeekForward();
}
else
{
return E_OUTOFMEMORY;
}
}
else
{
// This will increment the refcount for the segment
hr = LoadReferencedSegment( &pSegment, pParser );
}
if (SUCCEEDED(hr))
{
// This increments the refcount.
m_SegmentList.AddSegment(pSegment,dwSegmentCount);
}
pSegment->Release(); // Release the extra AddRef
dwSegmentCount++;
if(SUCCEEDED(hr) && hr != DMUS_S_PARTIALLOAD)
{
m_fPartialLoad |= PARTIALLOAD_S_OK;
}
else
{
m_fPartialLoad |= PARTIALLOAD_E_FAIL;
hr = S_OK;
}
}
break;
}
}
pParser->LeaveList();
}
default:
break;
}
}
pParser->LeaveList();
return hr;
}
HRESULT CSong::LoadGraphList(CRiffParser *pParser)
{
RIFFIO ckNext;
DWORD dwGraphCount = 0;
HRESULT hr = S_OK;
pParser->EnterList(&ckNext);
while(pParser->NextChunk(&hr))
{
switch(ckNext.ckid)
{
case FOURCC_RIFF:
switch(ckNext.fccType)
{
CGraph *pGraph;
case DMUS_FOURCC_TOOLGRAPH_FORM :
// Move back to start of this chunk.
pParser->SeekBack();
pGraph = new CGraph;
if (pGraph)
{
hr = pGraph->Load(pParser->GetStream());
dwGraphCount++;
if (SUCCEEDED(hr))
{
m_GraphList.AddTail(pGraph);
pGraph->m_dwLoadID = dwGraphCount;
}
if(SUCCEEDED(hr) && hr != DMUS_S_PARTIALLOAD)
{
m_fPartialLoad |= PARTIALLOAD_S_OK;
}
else
{
m_fPartialLoad |= PARTIALLOAD_E_FAIL;
hr = S_OK;
}
}
else
{
return E_OUTOFMEMORY;
}
pParser->SeekForward();
break;
default:
break;
}
break;
default:
break;
}
}
pParser->LeaveList();
return hr;
}
HRESULT CSong::GetTransitionSegment(CSegment *pSource, CSegment *pDestination,
DMUS_IO_TRANSITION_DEF *pTransDef)
{
HRESULT hr = DMUS_E_NOT_FOUND;
// if (pSource) Trace(0,"Transitioning from %ls ",pSource->m_wszName);
// if (pDestination) Trace(0,"to %ls",pDestination->m_wszName);
// Trace(0,"\n");
EnterCriticalSection(&m_CriticalSection);
// Default values for other fields, in case we don't find a match.
pTransDef->dwPlayFlags = 0;
pTransDef->dwTransitionID = DMUS_SONG_NOSEG;
pTransDef->dwSegmentID = DMUS_SONG_NOSEG;
CVirtualSegment *pVSource = NULL;
// If there is a source segment, look to see if it's in this song
// and pull the matchin virtual segment.
if (pSource)
{
pVSource = m_VirtualSegmentList.GetHead();
for (;pVSource;pVSource = pVSource->GetNext())
{
if (pVSource->m_pPlaySegment == pSource)
{
// Trace(0,"Found match for source segment %ls in song\n",pSource->m_wszName);
break;
}
}
}
CVirtualSegment *pVDestination = NULL;
// If there is a destination segment, look to see if it's in this song
// and pull the matching virtual segment.
if (pDestination)
{
pVDestination = m_VirtualSegmentList.GetHead();
for (;pVDestination;pVDestination = pVDestination->GetNext())
{
if (pVDestination->m_pPlaySegment == pDestination)
{
// Trace(0,"Found match for destination segment %ls in song\n",pDestination->m_wszName);
break;
}
}
}
if (pVSource)
{
if (pVDestination)
{
pTransDef->dwSegmentID = pVDestination->m_dwID;
}
else
{
// If there is no destination, mark this to transition to nothing.
pTransDef->dwSegmentID = DMUS_SONG_NOSEG;
}
if (pVSource->m_dwTransitionCount)
{
ASSERT(pVSource->m_pTransitions);
DWORD dwIndex;
DWORD dwMatchCount = 0;
// First, find out how many transitions match the requirement.
// We'll randomly select from the matching ones.
for (dwIndex = 0; dwIndex < pVSource->m_dwTransitionCount; dwIndex++)
{
if (pVSource->m_pTransitions[dwIndex].dwSegmentID == pTransDef->dwSegmentID)
{
dwMatchCount++;
}
}
DWORD dwChoice;
if (dwMatchCount)
{
dwChoice = rand() % dwMatchCount;
}
for (dwIndex = 0; dwIndex < pVSource->m_dwTransitionCount; dwIndex++)
{
if (pVSource->m_pTransitions[dwIndex].dwSegmentID == pTransDef->dwSegmentID)
{
if (!dwChoice)
{
//Trace(0,"Chose transition from %lx with Transition %lx, flags %lx\n",pVSource->m_pTransitions[dwIndex].dwSegmentID,
// pVSource->m_pTransitions[dwIndex].dwTransitionID,pVSource->m_pTransitions[dwIndex].dwPlayFlags);
pTransDef->dwPlayFlags = pVSource->m_pTransitions[dwIndex].dwPlayFlags;
pTransDef->dwTransitionID = pVSource->m_pTransitions[dwIndex].dwTransitionID;
hr = S_OK;
break;
}
dwChoice--;
}
else if ((pVSource->m_pTransitions[dwIndex].dwSegmentID == DMUS_SONG_ANYSEG) && !dwMatchCount)
{
// Mark the segment and flags, but don't break because we might still have the matched segment in the list.
pTransDef->dwPlayFlags = pVSource->m_pTransitions[dwIndex].dwPlayFlags;
pTransDef->dwTransitionID = pVSource->m_pTransitions[dwIndex].dwTransitionID;
//Trace(0,"Found default transition from %lx with Transition %lx, flags %lx\n",pVSource->m_pTransitions[dwIndex].dwSegmentID,
// pVSource->m_pTransitions[dwIndex].dwTransitionID,pVSource->m_pTransitions[dwIndex].dwPlayFlags);
hr = S_OK;
break;
}
}
}
}
else if (pVDestination)
{
// This is the special case where there is no source segment, perhaps because we are starting
// playback or we are starting from a different song. In this case, look for a transition in the destination
// segment for the special case of DMUS_SONG_NOFROMSEG. Typically, this represents a transition
// segment that is an intro.
if (pVDestination->m_dwTransitionCount)
{
ASSERT(pVDestination->m_pTransitions);
DWORD dwIndex;
DWORD dwMatchCount = 0;
// First, find out how many transitions match the requirement.
// We'll randomly select from the matching ones.
for (dwIndex = 0; dwIndex < pVDestination->m_dwTransitionCount; dwIndex++)
{
if (pVDestination->m_pTransitions[dwIndex].dwSegmentID == DMUS_SONG_NOFROMSEG)
{
dwMatchCount++;
}
}
DWORD dwChoice;
if (dwMatchCount)
{
dwChoice = rand() % dwMatchCount;
}
for (dwIndex = 0; dwIndex < pVDestination->m_dwTransitionCount; dwIndex++)
{
if (pVDestination->m_pTransitions[dwIndex].dwSegmentID == DMUS_SONG_NOFROMSEG)
{
if (!dwChoice)
{
//Trace(0,"Chose transition from NONE with Transition %lx, flags %lx\n",
// pVDestination->m_pTransitions[dwIndex].dwTransitionID,pVDestination->m_pTransitions[dwIndex].dwPlayFlags);
pTransDef->dwPlayFlags = pVDestination->m_pTransitions[dwIndex].dwPlayFlags;
pTransDef->dwTransitionID = pVDestination->m_pTransitions[dwIndex].dwTransitionID;
hr = S_OK;
break;
}
dwChoice--;
}
}
}
}
LeaveCriticalSection(&m_CriticalSection);
#ifdef DBG
if (hr == DMUS_E_NOT_FOUND)
{
Trace(2,"Warning: No transition segment was found in song.\n");
}
#endif
return hr;
}
void CSong::GetSourceSegment(CSegment **ppSegment,DWORD dwSegmentID)
{
CSongSegment *pSongSegment = m_SegmentList.GetHead();
while (pSongSegment)
{
if (pSongSegment->m_dwLoadID == dwSegmentID)
{
if (pSongSegment->m_pSegment)
{
pSongSegment->m_pSegment->AddRef();
*ppSegment = pSongSegment->m_pSegment;
return;
}
}
pSongSegment = pSongSegment->GetNext();
}
}
void CSong::GetGraph(CGraph **ppGraph,DWORD dwGraphID)
{
CGraph *pGraph = m_GraphList.GetHead();
while (pGraph)
{
if (pGraph->m_dwLoadID == dwGraphID)
{
pGraph->AddRef();
*ppGraph = pGraph;
return;
}
pGraph = pGraph->GetNext();
}
}
BOOL CSong::GetSegmentTrack(IDirectMusicTrack **ppTrack,DWORD dwSegmentID,DWORD dwGroupBits,DWORD dwIndex,REFGUID guidClassID)
{
CSongSegment *pSongSegment = m_SegmentList.GetHead();
while (pSongSegment)
{
if (pSongSegment->m_dwLoadID == dwSegmentID)
{
if (pSongSegment->m_pSegment)
{
return (pSongSegment->m_pSegment->GetTrack(guidClassID,dwGroupBits,dwIndex,ppTrack) == S_OK);
}
}
pSongSegment = pSongSegment->GetNext();
}
return FALSE;
}
HRESULT CSong::LoadVirtualSegmentList(CRiffParser *pParser)
{
RIFFIO ckNext;
RIFFIO ckChild;
RIFFIO ckUNFO;
DWORD dwSegmentCount = 0;
CVirtualSegment *pVirtualSegment;
MUSIC_TIME mtTime = 0;
HRESULT hr = S_OK;
pParser->EnterList(&ckNext);
while(pParser->NextChunk(&hr))
{
switch(ckNext.ckid)
{
case FOURCC_RIFF:
case FOURCC_LIST:
switch(ckNext.fccType)
{
case DMUS_FOURCC_SEGREF_LIST:
pVirtualSegment = new CVirtualSegment;
if (pVirtualSegment)
{
BOOL fGotHeader = FALSE;
BOOL fGotSegmentHeader = FALSE;
pVirtualSegment->m_mtTime = mtTime; // Give the start time, an accumulation of all preceding segments.
pParser->EnterList(&ckChild);
while(pParser->NextChunk(&hr))
{
switch( ckChild.ckid )
{
case FOURCC_RIFF:
case FOURCC_LIST:
switch(ckChild.fccType)
{
case DMUS_FOURCC_TRACKREFS_LIST:
hr = LoadTrackRefList(pParser, pVirtualSegment);
break;
case DMUS_FOURCC_UNFO_LIST:
pParser->EnterList(&ckUNFO);
while(pParser->NextChunk(&hr))
{
switch( ckUNFO.ckid )
{
case DMUS_FOURCC_UNAM_CHUNK:
{
hr = pParser->Read(pVirtualSegment->m_wszName, sizeof(pVirtualSegment->m_wszName));
break;
}
default:
break;
}
}
pParser->LeaveList();
}
break;
case DMUS_FOURCC_SEGREF_CHUNK:
{
DMUS_IO_SEGREF_HEADER ioVirtualSegment;
hr = pParser->Read(&ioVirtualSegment,sizeof(ioVirtualSegment));
if(SUCCEEDED(hr) )
{
pVirtualSegment->m_dwFlags = ioVirtualSegment.dwFlags;
pVirtualSegment->m_dwID = ioVirtualSegment.dwID;
pVirtualSegment->m_dwNextPlayID = ioVirtualSegment.dwNextPlayID;
if (ioVirtualSegment.dwSegmentID != DMUS_SONG_NOSEG)
{
GetSourceSegment(&pVirtualSegment->m_pSourceSegment,ioVirtualSegment.dwSegmentID);
}
if (ioVirtualSegment.dwToolGraphID != DMUS_SONG_NOSEG)
{
GetGraph(&pVirtualSegment->m_pGraph,ioVirtualSegment.dwToolGraphID);
}
fGotHeader = TRUE;
}
break;
}
case DMUS_FOURCC_SEGTRANS_CHUNK:
{
DWORD dwTransCount;
dwTransCount = ckChild.cksize / sizeof(DMUS_IO_TRANSITION_DEF);
if (dwTransCount > 0)
{
pVirtualSegment->m_pTransitions = new DMUS_IO_TRANSITION_DEF[dwTransCount];
if (pVirtualSegment->m_pTransitions)
{
pVirtualSegment->m_dwTransitionCount = dwTransCount;
hr = pParser->Read(pVirtualSegment->m_pTransitions,sizeof(DMUS_IO_TRANSITION_DEF)*dwTransCount);
}
else
{
return E_OUTOFMEMORY;
}
}
}
break;
case DMUS_FOURCC_SEGMENT_CHUNK:
fGotSegmentHeader = TRUE;
hr = pParser->Read(&pVirtualSegment->m_SegHeader, sizeof(DMUS_IO_SEGMENT_HEADER));
mtTime += (pVirtualSegment->m_SegHeader.dwRepeats * (pVirtualSegment->m_SegHeader.mtLoopEnd - pVirtualSegment->m_SegHeader.mtLoopStart)) +
pVirtualSegment->m_SegHeader.mtLength - pVirtualSegment->m_SegHeader.mtPlayStart;
default:
break;
}
}
pParser->LeaveList();
if (fGotHeader && fGotSegmentHeader)
{
//Trace(0,"Adding VSegment %ls with ID %ld to song.\n",pVirtualSegment->m_wszName,pVirtualSegment->m_dwID);
m_VirtualSegmentList.AddTail(pVirtualSegment);
}
else
{
delete pVirtualSegment;
}
break;
}
else
{
return E_OUTOFMEMORY;
}
break;
default:
break;
}
break;
default:
break;
}
}
pParser->LeaveList();
return hr;
}
struct ClassGuidCounts
{
GUID guidClass;
DWORD dwCount;
};
HRESULT CSong::LoadTrackRefList(CRiffParser *pParser,CVirtualSegment *pVirtualSegment)
{
RIFFIO ckNext;
RIFFIO ckChild;
HRESULT hr = S_OK;
TList<ClassGuidCounts> GuidCountList;
pParser->EnterList(&ckNext);
while(pParser->NextChunk(&hr))
{
switch(ckNext.ckid)
{
case FOURCC_LIST:
switch(ckNext.fccType)
{
CTrack *pTrack;
case DMUS_FOURCC_TRACKREF_LIST :
pTrack = new CTrack;
if (pTrack)
{
TListItem<ClassGuidCounts>* pCountItem = NULL;
DMUS_IO_TRACKREF_HEADER ioTrackRef;
DMUS_IO_TRACK_HEADER ioTrackHdr;
DMUS_IO_TRACK_EXTRAS_HEADER ioTrackExtrasHdr;
ioTrackExtrasHdr.dwPriority = 0;
ioTrackExtrasHdr.dwFlags = DMUS_TRACKCONFIG_DEFAULT;
ioTrackHdr.dwPosition = 0;
BOOL fGotHeader = FALSE;
BOOL fGotRef = FALSE;
pParser->EnterList(&ckChild);
while(pParser->NextChunk(&hr))
{
switch( ckChild.ckid )
{
case DMUS_FOURCC_TRACKREF_CHUNK:
{
hr = pParser->Read(&ioTrackRef, sizeof(ioTrackRef));
fGotRef = SUCCEEDED(hr);
break;
}
case DMUS_FOURCC_TRACK_CHUNK:
{
hr = pParser->Read(&ioTrackHdr, sizeof(ioTrackHdr));
fGotHeader = SUCCEEDED(hr);
pTrack->m_guidClassID = ioTrackHdr.guidClassID;
pTrack->m_dwGroupBits = ioTrackHdr.dwGroup;
pTrack->m_dwPosition = ioTrackHdr.dwPosition;
break;
}
case DMUS_FOURCC_TRACK_EXTRAS_CHUNK:
{
hr = pParser->Read(&ioTrackExtrasHdr, sizeof(ioTrackExtrasHdr));
pTrack->m_dwPriority = ioTrackExtrasHdr.dwPriority;
pTrack->m_dwFlags = ioTrackExtrasHdr.dwFlags;
break;
}
default:
break;
}
}
pParser->LeaveList();
if (fGotHeader && fGotRef)
{
if (ioTrackRef.dwSegmentID != DMUS_SONG_NOSEG)
{
DWORD dwID = 0;
for (pCountItem = GuidCountList.GetHead(); pCountItem; pCountItem = pCountItem->GetNext())
{
if (pCountItem->GetItemValue().guidClass == pTrack->m_guidClassID)
{
break;
}
}
if (pCountItem)
{
dwID = pCountItem->GetItemValue().dwCount;
}
fGotHeader = GetSegmentTrack(&pTrack->m_pTrack,ioTrackRef.dwSegmentID,pTrack->m_dwGroupBits,dwID,pTrack->m_guidClassID);
}
}
if (fGotHeader && pTrack->m_pTrack)
{
pTrack->m_pTrack->QueryInterface(IID_IDirectMusicTrack8,(void **) &pTrack->m_pTrack8);
// Add the track based on position.
CTrack* pScan = pVirtualSegment->m_TrackList.GetHead();
CTrack* pPrevTrack = NULL;
for (; pScan; pScan = pScan->GetNext())
{
if (pTrack->Less(pScan))
{
break;
}
pPrevTrack = pScan;
}
if (pPrevTrack)
{
pPrevTrack->SetNext(pTrack);
pTrack->SetNext(pScan);
}
else
{
pVirtualSegment->m_TrackList.AddHead( pTrack );
}
if (pCountItem)
{
pCountItem->GetItemValue().dwCount++;
}
else
{
TListItem<ClassGuidCounts>* pNew = new TListItem<ClassGuidCounts>;
if (pNew)
{
pNew->GetItemValue().dwCount = 1;
pNew->GetItemValue().guidClass = pTrack->m_guidClassID;
GuidCountList.AddHead(pNew);
}
else return E_OUTOFMEMORY;
}
}
else
{
delete pTrack;
}
break;
}
else
{
return E_OUTOFMEMORY;
}
break;
default:
break;
}
break;
default:
break;
}
}
pParser->LeaveList();
return hr;
}
HRESULT CSong::Save( IStream* pIStream, BOOL fClearDirty )
{
return E_NOTIMPL;
}
HRESULT CSong::GetSizeMax( ULARGE_INTEGER FAR* pcbSize )
{
return E_NOTIMPL;
}
/////////////////////////////////////////////////////////////////////////////
// IDirectMusicObject
STDMETHODIMP CSong::GetDescriptor(LPDMUS_OBJECTDESC pDesc)
{
// Argument validation
V_INAME(CSong::GetDescriptor);
V_STRUCTPTR_WRITE(pDesc, DMUS_OBJECTDESC);
if (m_fZombie)
{
Trace(1, "Error: Call of IDirectMusicSong::GetDescriptor after the song has been garbage collected. "
"It is invalid to continue using a song after releasing it from the loader (ReleaseObject/ReleaseObjectByUnknown) "
"and then calling CollectGarbage or Release on the loader.");
return DMUS_S_GARBAGE_COLLECTED;
}
memset( pDesc, 0, sizeof(DMUS_OBJECTDESC));
pDesc->dwSize = sizeof(DMUS_OBJECTDESC);
pDesc->guidClass = CLSID_DirectMusicSong;
pDesc->guidObject = m_guidObject;
pDesc->ftDate = m_ftDate;
pDesc->vVersion = m_vVersion;
memcpy( pDesc->wszName, m_wszName, sizeof(m_wszName) );
memcpy( pDesc->wszCategory, m_wszCategory, sizeof(m_wszCategory) );
memcpy( pDesc->wszFileName, m_wszFileName, sizeof(m_wszFileName) );
pDesc->dwValidData = ( m_dwValidData | DMUS_OBJ_CLASS );
return S_OK;
}
STDMETHODIMP CSong::SetDescriptor(LPDMUS_OBJECTDESC pDesc)
{
// Argument validation
V_INAME(CSong::SetDescriptor);
V_STRUCTPTR_READ(pDesc, DMUS_OBJECTDESC);
if (m_fZombie)
{
Trace(1, "Error: Call of IDirectMusicSong::SetDescriptor after the song has been garbage collected. "
"It is invalid to continue using a song after releasing it from the loader (ReleaseObject/ReleaseObjectByUnknown) "
"and then calling CollectGarbage or Release on the loader.");
return DMUS_S_GARBAGE_COLLECTED;
}
HRESULT hr = E_INVALIDARG;
DWORD dw = 0;
if( pDesc->dwSize >= sizeof(DMUS_OBJECTDESC) )
{
if( pDesc->dwValidData & DMUS_OBJ_OBJECT )
{
m_guidObject = pDesc->guidObject;
dw |= DMUS_OBJ_OBJECT;
}
if( pDesc->dwValidData & DMUS_OBJ_NAME )
{
memcpy( m_wszName, pDesc->wszName, sizeof(WCHAR)*DMUS_MAX_NAME );
dw |= DMUS_OBJ_NAME;
}
if( pDesc->dwValidData & DMUS_OBJ_CATEGORY )
{
memcpy( m_wszCategory, pDesc->wszCategory, sizeof(WCHAR)*DMUS_MAX_CATEGORY );
dw |= DMUS_OBJ_CATEGORY;
}
if( ( pDesc->dwValidData & DMUS_OBJ_FILENAME ) ||
( pDesc->dwValidData & DMUS_OBJ_FULLPATH ) )
{
memcpy( m_wszFileName, pDesc->wszFileName, sizeof(WCHAR)*DMUS_MAX_FILENAME );
dw |= (pDesc->dwValidData & (DMUS_OBJ_FILENAME | DMUS_OBJ_FULLPATH));
}
if( pDesc->dwValidData & DMUS_OBJ_VERSION )
{
m_vVersion = pDesc->vVersion;
dw |= DMUS_OBJ_VERSION;
}
if( pDesc->dwValidData & DMUS_OBJ_DATE )
{
m_ftDate = pDesc->ftDate;
dw |= DMUS_OBJ_DATE;
}
m_dwValidData |= dw;
if( pDesc->dwValidData & (~dw) )
{
Trace(2,"Warning: Song::SetDescriptor was not able to handle all passed fields, dwValidData bits %lx.\n",pDesc->dwValidData & (~dw));
hr = S_FALSE; // there were extra fields we didn't parse;
pDesc->dwValidData = dw;
}
else
{
hr = S_OK;
}
}
return hr;
}
STDMETHODIMP CSong::ParseDescriptor(LPSTREAM pIStream, LPDMUS_OBJECTDESC pDesc)
{
V_INAME(CSong::ParseDescriptor);
V_INTERFACE(pIStream);
V_STRUCTPTR_WRITE(pDesc, DMUS_OBJECTDESC);
if (m_fZombie)
{
Trace(1, "Error: Call of IDirectMusicSong::ParseDescriptor after the song has been garbage collected. "
"It is invalid to continue using a song after releasing it from the loader (ReleaseObject/ReleaseObjectByUnknown) "
"and then calling CollectGarbage or Release on the loader.");
return DMUS_S_GARBAGE_COLLECTED;
}
CRiffParser Parser(pIStream);
RIFFIO ckMain;
RIFFIO ckNext;
RIFFIO ckUNFO;
HRESULT hr = S_OK;
Parser.EnterList(&ckMain);
if (Parser.NextChunk(&hr) && (ckMain.fccType == DMUS_FOURCC_SONG_FORM))
{
pDesc->dwValidData = DMUS_OBJ_CLASS;
pDesc->guidClass = CLSID_DirectMusicSong;
Parser.EnterList(&ckNext);
while(Parser.NextChunk(&hr))
{
switch(ckNext.ckid)
{
case DMUS_FOURCC_GUID_CHUNK:
hr = Parser.Read( &pDesc->guidObject, sizeof(GUID) );
if( SUCCEEDED(hr) )
{
pDesc->dwValidData |= DMUS_OBJ_OBJECT;
}
break;
case DMUS_FOURCC_VERSION_CHUNK:
hr = Parser.Read( &pDesc->vVersion, sizeof(DMUS_VERSION) );
if( SUCCEEDED(hr) )
{
pDesc->dwValidData |= DMUS_OBJ_VERSION;
}
break;
case DMUS_FOURCC_CATEGORY_CHUNK:
hr = Parser.Read( &pDesc->wszCategory, sizeof(pDesc->wszCategory) );
if( SUCCEEDED(hr) )
{
pDesc->dwValidData |= DMUS_OBJ_CATEGORY;
}
break;
case DMUS_FOURCC_DATE_CHUNK:
hr = Parser.Read( &pDesc->ftDate, sizeof(FILETIME) );
if( SUCCEEDED(hr))
{
pDesc->dwValidData |= DMUS_OBJ_DATE;
}
break;
case FOURCC_LIST:
switch(ckNext.fccType)
{
case DMUS_FOURCC_UNFO_LIST:
Parser.EnterList(&ckUNFO);
while (Parser.NextChunk(&hr))
{
switch( ckUNFO.ckid )
{
case DMUS_FOURCC_UNAM_CHUNK:
{
hr = Parser.Read(&pDesc->wszName, sizeof(pDesc->wszName));
if(SUCCEEDED(hr) )
{
pDesc->dwValidData |= DMUS_OBJ_NAME;
}
break;
}
default:
break;
}
}
Parser.LeaveList();
break;
}
break;
default:
break;
}
}
Parser.LeaveList();
}
else
{
// Couldn't find the chunk header for a song.
// But, maybe this is actually a segment, in which case see if
// the segment object will parse it.
CSegment *pSegment = new CSegment;
if (pSegment)
{
pSegment->AddRef(); // just to be safe...
// Force the version so audiopath functionality will be supported.
pSegment->m_dwVersion = 8;
Parser.SeekBack();
hr = pSegment->ParseDescriptor(pIStream,pDesc);
pDesc->guidClass = CLSID_DirectMusicSong;
// Done with the segment, say bye bye.
delete pSegment;
}
else
{
hr = E_OUTOFMEMORY;
}
}
return hr;
}
ComposingTrack::ComposingTrack() : m_dwTrackGroup(0), m_dwPriority(0)
{
memset((void*) &m_guidClassID, 0, sizeof(m_guidClassID));
}
ComposingTrack::~ComposingTrack()
{
TListItem<CompositionComponent>* pComponent = m_Components.GetHead();
for (; pComponent; pComponent = pComponent->GetNext())
{
CompositionComponent& rComponent = pComponent->GetItemValue();
if (rComponent.pVirtualSegment && rComponent.pVirtualSegment->m_pPlaySegment)
{
rComponent.pVirtualSegment->m_pPlaySegment->Release();
}
if (rComponent.pComposingTrack && rComponent.pComposingTrack->m_pTrack8)
{
rComponent.pComposingTrack->m_pTrack8->Release();
}
}
}
HRESULT ComposingTrack::AddTrack(CVirtualSegment* pVirtualSegment, CTrack* pTrack)
{
HRESULT hr = S_OK;
if (!pVirtualSegment || !pVirtualSegment->m_pPlaySegment || !pTrack || !pTrack->m_pTrack8)
{
Trace(1,"Error: Unable to compose song because of a required segment or track is missing.\n");
return E_INVALIDARG;
}
TListItem<CompositionComponent>* pComponent = new TListItem<CompositionComponent>;
if (!pComponent)
{
hr = E_OUTOFMEMORY;
}
else
{
pVirtualSegment->m_pPlaySegment->AddRef();
pTrack->m_pTrack8->AddRef();
CompositionComponent& rComponent = pComponent->GetItemValue();
rComponent.pVirtualSegment = pVirtualSegment;
rComponent.pComposingTrack = pTrack;
rComponent.mtTime = pVirtualSegment->m_mtTime;
m_Components.AddHead(pComponent);
}
return hr;
}
BOOL Less(CompositionComponent& Comp1, CompositionComponent& Comp2)
{
return Comp1.mtTime < Comp2.mtTime;
}
// Compose does the joining, composing, successive splitting, and adding to segments
HRESULT ComposingTrack::Compose(IDirectMusicSong* pSong)
{
HRESULT hr = S_OK;
IDirectMusicTrack8* pMasterTrack = NULL;
IDirectMusicTrack8* pComposedTrack = NULL;
m_Components.MergeSort(Less);
// Join the tracks together according to the ordering of their associated segments.
TListItem<CompositionComponent>* pComponent = m_Components.GetHead();
for (; pComponent; pComponent = pComponent->GetNext())
{
CompositionComponent& rComponent = pComponent->GetItemValue();
if (!pMasterTrack)
{
//MUSIC_TIME mtEnd = 0;
//if (pComponent->GetNext())
//{
// mtEnd = pComponent->GetNext()->GetItemValue().mtTime;
//}
//else
//{
// rComponent.pVirtualSegment->m_pPlaySegment->GetLength(&mtEnd);
//}
//hr = rComponent.pComposingTrack->m_pTrack8->Clone(0, mtEnd, (IDirectMusicTrack**)&pMasterTrack);
hr = rComponent.pComposingTrack->m_pTrack8->Clone(0, 0, (IDirectMusicTrack**)&pMasterTrack);
}
//else
if (SUCCEEDED(hr))
{
hr = pMasterTrack->Join(rComponent.pComposingTrack->m_pTrack8, rComponent.mtTime, pSong, m_dwTrackGroup, NULL);
}
if (FAILED(hr)) break;
}
// Call Compose on the joined track.
if (SUCCEEDED(hr))
{
hr = pMasterTrack->Compose(pSong, m_dwTrackGroup, (IDirectMusicTrack**)&pComposedTrack);
}
// Split the composed result according to the original segments.
if (SUCCEEDED(hr))
{
MUSIC_TIME mtStart = 0;
MUSIC_TIME mtEnd = 0;
pComponent = m_Components.GetHead();
for (; pComponent; pComponent = pComponent->GetNext())
{
CompositionComponent& rComponent = pComponent->GetItemValue();
mtStart = rComponent.mtTime;
// only split off a composed track if the original segment contained a composing track
IDirectMusicTrack* pOldTrack = NULL;
IPersistStream* pPersist = NULL;
GUID guidClassId;
memset(&guidClassId, 0, sizeof(guidClassId));
if (SUCCEEDED(pMasterTrack->QueryInterface(IID_IPersistStream, (void**)&pPersist)) &&
SUCCEEDED(pPersist->GetClassID(&guidClassId)) &&
SUCCEEDED( rComponent.pVirtualSegment->m_pPlaySegment->GetTrack( guidClassId, m_dwTrackGroup, 0, &pOldTrack ) ) )
{
pPersist->Release();
pOldTrack->Release();
if (pComponent->GetNext())
{
mtEnd = pComponent->GetNext()->GetItemValue().mtTime;
}
else
{
MUSIC_TIME mtLength = 0;
rComponent.pVirtualSegment->m_pPlaySegment->GetLength(&mtLength);
mtEnd = mtStart + mtLength;
}
IDirectMusicTrack8* pComposedFragment = NULL;
hr = pComposedTrack->Clone(mtStart, mtEnd, (IDirectMusicTrack**)&pComposedFragment);
if (SUCCEEDED(hr))
{
// Remove any tracks of this type (in the same group) from the segment.
pOldTrack = NULL;
pPersist = NULL;
memset(&guidClassId, 0, sizeof(guidClassId));
if (SUCCEEDED(pComposedFragment->QueryInterface(IID_IPersistStream, (void**)&pPersist)) )
{
if (SUCCEEDED(pPersist->GetClassID(&guidClassId)) &&
SUCCEEDED( rComponent.pVirtualSegment->m_pPlaySegment->GetTrack( guidClassId, m_dwTrackGroup, 0, &pOldTrack ) ) )
{
rComponent.pVirtualSegment->m_pPlaySegment->RemoveTrack( pOldTrack );
pOldTrack->Release();
}
pPersist->Release();
}
hr = rComponent.pVirtualSegment->m_pPlaySegment->InsertTrack(pComposedFragment, m_dwTrackGroup);
pComposedFragment->Release(); // release from the Clone
}
if (FAILED(hr)) break;
}
else // the QI to pPersist might have succeeded, so clean it up
{
if (pPersist) pPersist->Release();
}
}
if (pComposedTrack) pComposedTrack->Release();
}
if (pMasterTrack) pMasterTrack->Release();
return hr;
}