// // 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(this); } else if (iid == IID_CSong) { *ppv = static_cast(this); } else if (iid == IID_IPersistStream) { *ppv = static_cast(this); } else if(iid == IID_IDirectMusicObject) { *ppv = static_cast(this); } else if (iid == IID_IDirectMusicObjectP) { *ppv = static_cast(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(this), CLSCTX_INPROC_SERVER, IID_IUnknown, reinterpret_cast(&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(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 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* 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* pTrackItem = new TListItem; 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* 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* 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(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(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 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* 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* pNew = new TListItem; 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* 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* pComponent = new TListItem; 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* 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; }