//+-------------------------------------------------------------------------
//
//  Microsoft Windows
//
//  Copyright (c) 1998-1999 Microsoft Corporation
//
//  File:       perstrk.cpp
//
//--------------------------------------------------------------------------

// READ THIS!!!!!!!!!!!!!!!!!!!!!!!!!!!
//
// 4530: C++ exception handler used, but unwind semantics are not enabled. Specify -GX
//
// We disable this because we use exceptions and do *not* specify -GX (USE_NATIVE_EH in
// sources).
//
// The one place we use exceptions is around construction of objects that call 
// InitializeCriticalSection. We guarantee that it is safe to use in this case with
// the restriction given by not using -GX (automatic objects in the call chain between
// throw and handler are not destructed). Turning on -GX buys us nothing but +10% to code
// size because of the unwind code.
//
// Any other use of exceptions must follow these restrictions or -GX must be turned on.
//
// READ THIS!!!!!!!!!!!!!!!!!!!!!!!!!!!
//
#pragma warning(disable:4530)

// PersTrk.cpp : Implementation of CPersonalityTrack
#include "DMPers.h"
#include "PersTrk.h"
#include "dmusicc.h"
#include "..\shared\Validate.h"

/////////////////////////////////////////////////////////////////////////////
// CPersonalityTrack


CPersonalityTrack::CPersonalityTrack() : 
    m_bRequiresSave(0), 
    m_cRef(1),
    m_fCSInitialized(FALSE)

{
    InterlockedIncrement(&g_cComponent);

    // Do this first since it might throw an exception
    //
    ::InitializeCriticalSection( &m_CriticalSection );
    m_fCSInitialized = TRUE;
}

CPersonalityTrack::CPersonalityTrack(const CPersonalityTrack& rTrack, MUSIC_TIME mtStart, MUSIC_TIME mtEnd)  : 
    m_bRequiresSave(0),
    m_cRef(1),
    m_fCSInitialized(FALSE)
{
    InterlockedIncrement(&g_cComponent);

    // Do this first since it might throw an exception
    //
    ::InitializeCriticalSection( &m_CriticalSection );
    m_fCSInitialized = TRUE;
    TListItem<StampedPersonality>* pScan = rTrack.m_PersonalityList.GetHead();
    TListItem<StampedPersonality>* pPrevious = NULL;
    for(; pScan; pScan = pScan->GetNext())
    {
        StampedPersonality& rScan = pScan->GetItemValue();
        if (rScan.m_mtTime < mtStart)
        {
            pPrevious = pScan;
        }
        else if (rScan.m_mtTime < mtEnd)
        {
            if (rScan.m_mtTime == mtStart)
            {
                pPrevious = NULL;
            }
            TListItem<StampedPersonality>* pNew = new TListItem<StampedPersonality>;
            if (pNew)
            {
                StampedPersonality& rNew = pNew->GetItemValue();
                rNew.m_mtTime = rScan.m_mtTime - mtStart;
                rNew.m_pPersonality = rScan.m_pPersonality;
                if (rNew.m_pPersonality) rNew.m_pPersonality->AddRef();
                m_PersonalityList.AddTail(pNew);
            }
        }
        else break;
    }
    if (pPrevious)
    {
        TListItem<StampedPersonality>* pNew = new TListItem<StampedPersonality>;
        if (pNew)
        {
            StampedPersonality& rNew = pNew->GetItemValue();
            rNew.m_mtTime = 0;
            rNew.m_pPersonality = pPrevious->GetItemValue().m_pPersonality;
            if (rNew.m_pPersonality) rNew.m_pPersonality->AddRef();
            m_PersonalityList.AddHead(pNew);
        }
    }
}

CPersonalityTrack::~CPersonalityTrack()
{
    if (m_fCSInitialized)
    {
        ::DeleteCriticalSection( &m_CriticalSection );
    }
    InterlockedDecrement(&g_cComponent);
}

STDMETHODIMP CPersonalityTrack::QueryInterface(
    const IID &iid, 
    void **ppv) 
{
    V_INAME(CPersonalityTrack::QueryInterface);
    V_PTRPTR_WRITE(ppv);
    V_REFGUID(iid);

    if (iid == IID_IUnknown || iid == IID_IDirectMusicTrack || iid == IID_IDirectMusicTrack8)
    {
        *ppv = static_cast<IDirectMusicTrack*>(this);
    }
    else if (iid == IID_IPersistStream)
    {
        *ppv = static_cast<IPersistStream*>(this);
    }
    else
    {
        *ppv = NULL;
        return E_NOINTERFACE;
    }

    reinterpret_cast<IUnknown*>(this)->AddRef();
    return S_OK;
}


STDMETHODIMP_(ULONG) CPersonalityTrack::AddRef()
{
    return InterlockedIncrement(&m_cRef);
}


STDMETHODIMP_(ULONG) CPersonalityTrack::Release()
{
    if (!InterlockedDecrement(&m_cRef))
    {
        delete this;
        return 0;
    }

    return m_cRef;
}


HRESULT CPersonalityTrack::Init(
                /*[in]*/  IDirectMusicSegment*      pSegment
            )
{
    return S_OK;
}

HRESULT CPersonalityTrack::InitPlay(
                /*[in]*/  IDirectMusicSegmentState* pSegmentState,
                /*[in]*/  IDirectMusicPerformance*  pPerformance,
                /*[out]*/ void**                    ppStateData,
                /*[in]*/  DWORD                     dwTrackID,
                /*[in]*/  DWORD                     dwFlags
            )
{
    return S_OK;
}

HRESULT CPersonalityTrack::EndPlay(
                /*[in]*/  void*                     pStateData
            )
{
    return S_OK;
}

HRESULT CPersonalityTrack::Play(
                /*[in]*/  void*                     pStateData, 
                /*[in]*/  MUSIC_TIME                mtStart, 
                /*[in]*/  MUSIC_TIME                mtEnd, 
                /*[in]*/  MUSIC_TIME                mtOffset,
                          DWORD                     dwFlags,
                          IDirectMusicPerformance*  pPerf,
                          IDirectMusicSegmentState* pSegState,
                          DWORD                     dwVirtualID
            )
{ 
    return S_OK;
}

HRESULT CPersonalityTrack::GetPriority( 
                /*[out]*/ DWORD*                    pPriority 
            )
    {
        return E_NOTIMPL;
    }

HRESULT CPersonalityTrack::GetParam(
                REFGUID                     rCommandGuid,
                MUSIC_TIME                  mtTime, 
                MUSIC_TIME*                 pmtNext,
                void*                       pData
            )
{
    V_INAME(CPersonalityTrack::GetParam);
    V_PTR_WRITE_OPT(pmtNext,MUSIC_TIME);
    V_REFGUID(rCommandGuid);

    if( NULL == pData )
    {
        Trace(1, "ERROR: GetParam (chord map track): pParam is NULL.\n");
        return E_POINTER;
    }

    HRESULT hr;
    EnterCriticalSection(&m_CriticalSection);
    if (rCommandGuid == GUID_IDirectMusicChordMap)
    {
        TListItem<StampedPersonality>* pScan = m_PersonalityList.GetHead();
        if (pScan)
        {
            IDirectMusicChordMap* pPersonality = pScan->GetItemValue().m_pPersonality;
            for(pScan = pScan->GetNext(); pScan; pScan = pScan->GetNext())
            {
                StampedPersonality& rScan = pScan->GetItemValue();
                if (mtTime < rScan.m_mtTime  && rScan.m_pPersonality) break;  // ignore if NULL
                if (rScan.m_pPersonality) pPersonality = rScan.m_pPersonality; // skip if NULL
            }
            if (pPersonality)
            {
                pPersonality->AddRef();
                *(IDirectMusicChordMap**)pData = pPersonality;
                if (pmtNext)
                {
                    *pmtNext = (pScan != NULL) ? pScan->GetItemValue().m_mtTime - mtTime : 0;
                }
                hr = S_OK;
            }
            else
            {
                Trace(1, "ERROR: GetParam (chord map track): Chord map list item is empty.\n");
                hr = E_POINTER;
            }

        }
        else
        {
            Trace(1, "ERROR: GetParam (chord map track): Chord map track is empty.\n");
            hr = DMUS_E_NOT_FOUND;
        }
    }
    else
    {
        Trace(1, "ERROR: GetParam (chord map track): Attempt to get an unsupported parameter.\n");
        hr = DMUS_E_GET_UNSUPPORTED;
    }
    LeaveCriticalSection(&m_CriticalSection);
    return hr;
} 

HRESULT CPersonalityTrack::SetParam( 
    REFGUID                     rCommandGuid,
    MUSIC_TIME mtTime,
    void __RPC_FAR *pData)
{
    V_INAME(CPersonalityTrack::SetParam);
    V_REFGUID(rCommandGuid);

    HRESULT hr;
    if (!pData)
    {
        Trace(1, "ERROR: SetParam (chord map track): pParam is NULL.\n");
        return E_POINTER;
    }

    EnterCriticalSection( &m_CriticalSection );
    if (rCommandGuid == GUID_IDirectMusicChordMap)
    {
        IDirectMusicChordMap* pPers = (IDirectMusicChordMap*)pData;
        TListItem<StampedPersonality>* pPersItem = m_PersonalityList.GetHead();
        TListItem<StampedPersonality>* pPrevious = NULL;
        TListItem<StampedPersonality>* pNew = new TListItem<StampedPersonality>;
        if (!pNew)
        {
            hr = E_OUTOFMEMORY;
        }
        else 
        {
            pNew->GetItemValue().m_mtTime = mtTime;
            pNew->GetItemValue().m_pPersonality = pPers;
            if (pPers) pPers->AddRef();
            for(; pPersItem != NULL; pPersItem = pPersItem->GetNext())
            {
                if (pPersItem->GetItemValue().m_mtTime >= mtTime) break;
                pPrevious = pPersItem;
            }
            if (pPrevious)
            {
                pPrevious->SetNext(pNew);
                pNew->SetNext(pPersItem);
            }
            else // pPersItem is current head of list
            {
                m_PersonalityList.AddHead(pNew);
            }
            if (pPersItem && pPersItem->GetItemValue().m_mtTime == mtTime)
            {
                // remove it
                if (pPersItem->GetItemValue().m_pPersonality)
                {
                    pPersItem->GetItemValue().m_pPersonality->Release();
                }
                pPersItem->GetItemValue().m_pPersonality = NULL;
                pNew->SetNext(pPersItem->GetNext());
                pPersItem->SetNext(NULL);
                delete pPersItem;
            }
            hr = S_OK;
        }
    }
    else
    {
        Trace(1, "ERROR: SetParam (chord map track): Attempt to set an unsupported parameter.\n");
        hr = DMUS_E_SET_UNSUPPORTED;
    }
    LeaveCriticalSection( &m_CriticalSection );
    return hr;
}

// IPersist methods
 HRESULT CPersonalityTrack::GetClassID( LPCLSID pClassID )
{
    V_INAME(CPersonalityTrack::GetClassID);
    V_PTR_WRITE(pClassID, CLSID); 
    *pClassID = CLSID_DirectMusicChordMapTrack;
    return S_OK;
}

// IDirectMusicCommon Methods
HRESULT CPersonalityTrack::GetName(
                /*[out]*/  BSTR*        pbstrName
            )
{
    return E_NOTIMPL;
}

HRESULT CPersonalityTrack::IsParamSupported(
                /*[in]*/ REFGUID                        rGuid
            )
{
    V_INAME(CPersonalityTrack::IsParamSupported);
    V_REFGUID(rGuid);

    if (rGuid == GUID_IDirectMusicChordMap)
        return S_OK;
    else 
    {
        Trace(2, "WARNING: IsParamSupported (chord map track): The parameter is not supported.\n");
        return DMUS_E_TYPE_UNSUPPORTED;
    }
}

// IPersistStream methods
 HRESULT CPersonalityTrack::IsDirty()
{
     return m_bRequiresSave ? S_OK : S_FALSE;
}

HRESULT CPersonalityTrack::Save( LPSTREAM pStream, BOOL fClearDirty )
{
    return E_NOTIMPL;
}

HRESULT CPersonalityTrack::GetSizeMax( ULARGE_INTEGER* /*pcbSize*/ )
{
    return E_NOTIMPL;
}


BOOL Less(StampedPersonality& SP1, StampedPersonality& SP2)
{ return SP1.m_mtTime < SP2.m_mtTime; }

HRESULT CPersonalityTrack::Load(LPSTREAM pStream )
{
    V_INAME(CPersonalityTrack::Load);
    V_INTERFACE(pStream);

    IAARIFFStream*  pIRiffStream;
    //MMCKINFO      ckMain;
    MMCKINFO        ck;
    HRESULT         hr = E_FAIL;

    if ( pStream == NULL ) return E_INVALIDARG;
    EnterCriticalSection( &m_CriticalSection );
    if (m_PersonalityList.GetHead())
    {
        delete m_PersonalityList.GetHead();
        m_PersonalityList.RemoveAll();
    }

    if( SUCCEEDED( AllocRIFFStream( pStream, &pIRiffStream ) ) )
    {
        if (pIRiffStream->Descend( &ck, NULL, 0 ) == 0)
        {
            if (ck.ckid == FOURCC_LIST && ck.fccType == DMUS_FOURCC_PERS_TRACK_LIST)
            {
                hr = LoadPersRefList(pIRiffStream, &ck);
            }
            pIRiffStream->Ascend( &ck, 0 );
        }
        pIRiffStream->Release();
    }
    m_PersonalityList.MergeSort(Less);

    LeaveCriticalSection( &m_CriticalSection );
    return hr;
}

HRESULT CPersonalityTrack::LoadPersRefList( IAARIFFStream* pIRiffStream, MMCKINFO* pckParent )
{
    HRESULT hr = S_OK;
    if (!pIRiffStream || !pckParent) return E_INVALIDARG;
    MMCKINFO ck;

    while ( pIRiffStream->Descend( &ck, pckParent, 0 ) == 0  )
    {
        if ( ck.ckid == FOURCC_LIST && ck.fccType == DMUS_FOURCC_PERS_REF_LIST )
        {
            hr = LoadPersRef(pIRiffStream, &ck);
            pIRiffStream->Ascend( &ck, 0 );
        }
        pIRiffStream->Ascend( &ck, 0 );
    }

    return hr;
}

HRESULT CPersonalityTrack::LoadPersRef( IAARIFFStream* pIRiffStream, MMCKINFO* pckParent )
{
    HRESULT hr = S_OK;
    if (!pIRiffStream || !pckParent) return E_INVALIDARG;
    MMCKINFO ck;
    IStream* pIStream = pIRiffStream->GetStream();
    if(!pIStream) return E_FAIL;
    IDirectMusicChordMap* pPersonality = NULL;
    TListItem<StampedPersonality>* pNew = new TListItem<StampedPersonality>;
    if (!pNew) return E_OUTOFMEMORY;
    StampedPersonality& rNew = pNew->GetItemValue();
    while (pIRiffStream->Descend( &ck, pckParent, 0 ) == 0)
    {
        switch (ck.ckid)
        {
        case DMUS_FOURCC_TIME_STAMP_CHUNK:
            {
                DWORD dwTime;
                DWORD cb;
                hr = pIStream->Read( &dwTime, sizeof( dwTime ), &cb );
                if (FAILED(hr) || cb != sizeof( dwTime ) ) 
                {
                    if (SUCCEEDED(hr)) hr = E_FAIL;
                    pIRiffStream->Ascend( &ck, 0 );
                    goto ON_END;
                }
                rNew.m_mtTime = dwTime;
            }
            break;
        case FOURCC_LIST:
            if (ck.fccType == DMUS_FOURCC_REF_LIST)
            {
                hr = LoadReference(pIStream, pIRiffStream, ck, &pPersonality);
                if (SUCCEEDED(hr))
                {
                    rNew.m_pPersonality = pPersonality;
                }
            }
            break;
        }
        pIRiffStream->Ascend( &ck, 0 );
    }
    if (SUCCEEDED(hr))
    {
        m_PersonalityList.AddTail(pNew);
    }
    else
    {
        delete pNew;
    }
ON_END:
    pIStream->Release();
    return hr;
}

//////////////////////////////////////////////////////////////////////
// CPersonalityTrack::LoadReference

HRESULT CPersonalityTrack::LoadReference(IStream *pStream,
                                         IAARIFFStream *pIRiffStream,
                                         MMCKINFO& ckParent,
                                         IDirectMusicChordMap** ppPersonality)
{
    if (!pStream || !pIRiffStream || !ppPersonality) return E_INVALIDARG;

    IDirectMusicLoader* pLoader = NULL;
    IDirectMusicGetLoader *pIGetLoader;  
    HRESULT hr = pStream->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));

    DWORD cbRead;
    
    MMCKINFO ckNext;
    ckNext.ckid = 0;
    ckNext.fccType = 0;
    DWORD dwSize = 0;
        
    while( pIRiffStream->Descend( &ckNext, &ckParent, 0 ) == 0 )
    {
        switch(ckNext.ckid)
        {
            case  DMUS_FOURCC_REF_CHUNK:
                DMUS_IO_REFERENCE ioDMRef;
                hr = pStream->Read(&ioDMRef, sizeof(DMUS_IO_REFERENCE), &cbRead);
                if(SUCCEEDED(hr) && cbRead == sizeof(DMUS_IO_REFERENCE))
                {
                    desc.guidClass = ioDMRef.guidClassID;
                    desc.dwValidData |= ioDMRef.dwValidData;
                    desc.dwValidData |= DMUS_OBJ_CLASS;
                }
                else if(SUCCEEDED(hr))
                {
                    hr = E_FAIL;
                }
                break;

            case DMUS_FOURCC_GUID_CHUNK:
                hr = pStream->Read(&(desc.guidObject), sizeof(GUID), &cbRead);
                if(SUCCEEDED(hr) && cbRead == sizeof(GUID))
                {
                    desc.dwValidData |=  DMUS_OBJ_OBJECT;
                }
                else if(SUCCEEDED(hr))
                {
                    hr = E_FAIL;
                }
                break;

            case DMUS_FOURCC_DATE_CHUNK:
                hr = pStream->Read(&(desc.ftDate), sizeof(FILETIME), &cbRead);
                if(SUCCEEDED(hr) && cbRead == sizeof(FILETIME))
                {
                    desc.dwValidData |=  DMUS_OBJ_DATE;
                }
                else if(SUCCEEDED(hr))
                {
                    hr = E_FAIL;
                }
                break;

            case DMUS_FOURCC_NAME_CHUNK:
                dwSize = min(sizeof(desc.wszName), ckNext.cksize);
                hr = pStream->Read(desc.wszName, dwSize, &cbRead);
                if(SUCCEEDED(hr) && cbRead == dwSize)
                {
                    desc.wszName[DMUS_MAX_NAME - 1] = L'\0';
                    desc.dwValidData |=  DMUS_OBJ_NAME;
                }
                else if(SUCCEEDED(hr))
                {
                    hr = E_FAIL;
                }
                break;
            
            case DMUS_FOURCC_FILE_CHUNK:
                dwSize = min(sizeof(desc.wszFileName), ckNext.cksize);
                hr = pStream->Read(desc.wszFileName, dwSize, &cbRead);
                if(SUCCEEDED(hr) && cbRead == dwSize)
                {
                    desc.wszFileName[DMUS_MAX_FILENAME - 1] = L'\0';
                    desc.dwValidData |=  DMUS_OBJ_FILENAME;
                }
                else if(SUCCEEDED(hr))
                {
                    hr = E_FAIL;
                }
                break;

            case DMUS_FOURCC_CATEGORY_CHUNK:
                dwSize = min(sizeof(desc.wszCategory), ckNext.cksize);
                hr = pStream->Read(desc.wszCategory, dwSize, &cbRead);
                if(SUCCEEDED(hr) && cbRead == dwSize)
                {
                    desc.wszCategory[DMUS_MAX_CATEGORY - 1] = L'\0';
                    desc.dwValidData |=  DMUS_OBJ_CATEGORY;
                }
                else if(SUCCEEDED(hr))
                {
                    hr = E_FAIL;
                }
                break;

            case DMUS_FOURCC_VERSION_CHUNK:
                DMUS_IO_VERSION ioDMObjVer;
                hr = pStream->Read(&ioDMObjVer, sizeof(DMUS_IO_VERSION), &cbRead);
                if(SUCCEEDED(hr) && cbRead == sizeof(DMUS_IO_VERSION))
                {
                    desc.vVersion.dwVersionMS = ioDMObjVer.dwVersionMS;
                    desc.vVersion.dwVersionLS = ioDMObjVer.dwVersionLS;
                    desc.dwValidData |= DMUS_OBJ_VERSION;
                }
                else if(SUCCEEDED(hr))
                {
                    hr = E_FAIL;
                }
                break;

            default:
                break;
        }
    
        if(SUCCEEDED(hr) && pIRiffStream->Ascend(&ckNext, 0) == 0)
        {
            ckNext.ckid = 0;
            ckNext.fccType = 0;
        }
        else if (SUCCEEDED(hr)) hr = E_FAIL;
    }
    if(SUCCEEDED(hr))
    {
        desc.dwSize = sizeof(DMUS_OBJECTDESC);
        hr = pLoader->GetObject(&desc, IID_IDirectMusicChordMap, (void**)ppPersonality);
    }

    if (pLoader)
    {
        pLoader->Release();
    }
    return hr;
}

HRESULT STDMETHODCALLTYPE CPersonalityTrack::AddNotificationType(
    /* [in] */  REFGUID                     rGuidNotify)
{
    return E_NOTIMPL;
}

HRESULT STDMETHODCALLTYPE CPersonalityTrack::RemoveNotificationType(
    /* [in] */  REFGUID                     rGuidNotify)
{
    return E_NOTIMPL;
}

HRESULT STDMETHODCALLTYPE CPersonalityTrack::Clone(
    MUSIC_TIME mtStart,
    MUSIC_TIME mtEnd,
    IDirectMusicTrack** ppTrack)
{
    V_INAME(CPersonalityTrack::Clone);
    V_PTRPTR_WRITE(ppTrack);

    HRESULT hr = S_OK;

    if(mtStart < 0 )
    {
        Trace(1, "ERROR: Clone (chord map track): Invalid  start time.\n");
        return E_INVALIDARG;
    }
    if(mtStart > mtEnd)
    {
        Trace(1, "ERROR: Clone (chord map track): Invalid  end time.\n");
        return E_INVALIDARG;
    }

    EnterCriticalSection( &m_CriticalSection );

    CPersonalityTrack *pDM;
    
    try
    {
        pDM = new CPersonalityTrack(*this, mtStart, mtEnd);
    }
    catch( ... )
    {
        pDM = NULL;
    }

    if (pDM == NULL) {
        LeaveCriticalSection( &m_CriticalSection );
        return E_OUTOFMEMORY;
    }

    hr = pDM->QueryInterface(IID_IDirectMusicTrack, (void**)ppTrack);
    pDM->Release();

    LeaveCriticalSection( &m_CriticalSection );
    return hr;
}

// IDirectMusicTrack8 Methods

// For consistency with other track types
STDMETHODIMP CPersonalityTrack::GetParamEx(REFGUID rguidType,REFERENCE_TIME rtTime, 
                REFERENCE_TIME* prtNext,void* pParam,void * pStateData, DWORD dwFlags) 
{
    HRESULT hr;
    MUSIC_TIME mtNext;
    hr = GetParam(rguidType,(MUSIC_TIME) rtTime, &mtNext, pParam);
    if (prtNext)
    {
        *prtNext = mtNext;
    }
    return hr;
}

// For consistency with other track types
STDMETHODIMP CPersonalityTrack::SetParamEx(REFGUID rguidType,REFERENCE_TIME rtTime,
                                      void* pParam, void * pStateData, DWORD dwFlags) 
{
    return SetParam(rguidType, (MUSIC_TIME) rtTime , pParam);
}

// For consistency with other track types
STDMETHODIMP CPersonalityTrack::PlayEx(void* pStateData,REFERENCE_TIME rtStart, 
                REFERENCE_TIME rtEnd,REFERENCE_TIME rtOffset,
                DWORD dwFlags,IDirectMusicPerformance* pPerf,
                IDirectMusicSegmentState* pSegSt,DWORD dwVirtualID) 
{
    V_INAME(IDirectMusicTrack::PlayEx);
    V_INTERFACE(pPerf);
    V_INTERFACE(pSegSt);

    HRESULT hr;
    EnterCriticalSection(&m_CriticalSection);
    hr = Play(pStateData, (MUSIC_TIME)rtStart, (MUSIC_TIME)rtEnd,
          (MUSIC_TIME)rtOffset, dwFlags, pPerf, pSegSt, dwVirtualID);
    LeaveCriticalSection(&m_CriticalSection);
    return hr;
}

STDMETHODIMP CPersonalityTrack::Compose(
        IUnknown* pContext, 
        DWORD dwTrackGroup,
        IDirectMusicTrack** ppResultTrack) 
{
    return E_NOTIMPL;
}

STDMETHODIMP CPersonalityTrack::Join(
        IDirectMusicTrack* pNewTrack,
        MUSIC_TIME mtJoin,
        IUnknown* pContext,
        DWORD dwTrackGroup,
        IDirectMusicTrack** ppResultTrack) 
{
    V_INAME(IDirectMusicTrack::Join);
    V_INTERFACE(pNewTrack);
    V_INTERFACE_OPT(pContext);
    V_PTRPTR_WRITE_OPT(ppResultTrack);

    HRESULT hr = S_OK;
    EnterCriticalSection(&m_CriticalSection);

    if (ppResultTrack)
    {
        hr = Clone(0, mtJoin, ppResultTrack);
        if (SUCCEEDED(hr))
        {
            hr = ((CPersonalityTrack*)*ppResultTrack)->JoinInternal(pNewTrack, mtJoin, dwTrackGroup);
        }
    }
    else
    {
        hr = JoinInternal(pNewTrack, mtJoin, dwTrackGroup);
    }

    LeaveCriticalSection(&m_CriticalSection);
    return hr;
}

HRESULT CPersonalityTrack::JoinInternal(
        IDirectMusicTrack* pNewTrack,
        MUSIC_TIME mtJoin,
        DWORD dwTrackGroup) 
{
    HRESULT hr = S_OK;
    CPersonalityTrack* pOtherTrack = (CPersonalityTrack*)pNewTrack;
    TListItem<StampedPersonality>* pScan = pOtherTrack->m_PersonalityList.GetHead();
    for (; pScan; pScan = pScan->GetNext())
    {
        StampedPersonality& rScan = pScan->GetItemValue();
        TListItem<StampedPersonality>* pNew = new TListItem<StampedPersonality>;
        if (pNew)
        {
            StampedPersonality& rNew = pNew->GetItemValue();
            rNew.m_mtTime = rScan.m_mtTime + mtJoin;
            rNew.m_pPersonality = rScan.m_pPersonality;
            if (rNew.m_pPersonality) rNew.m_pPersonality->AddRef();
            m_PersonalityList.AddTail(pNew);
        }
        else
        {
            hr = E_OUTOFMEMORY;
            break;
        }
    }
    return hr;
}