//+-------------------------------------------------------------------------
//
//  Microsoft Windows
//
//  Copyright (c) 1998-2001 Microsoft Corporation
//
//  File:       ptrntrk.cpp
//
//--------------------------------------------------------------------------

// PtrnTrk.cpp : Implementation of the Pattern Track info and state structs

#include "PtrnTrk.h"

#include "dmusici.h"
#include "dmusicf.h"

#include "debug.h"

/////////////////////////////////////////////////////////////////////////////
// PatternTrackState

PatternTrackState::PatternTrackState() :
    m_pStyle(NULL),
    m_pSegState(NULL),
    m_pPerformance(NULL),
    m_mtPerformanceOffset(0),
    m_dwVirtualTrackID(0),
    m_pPatternTrack(NULL),
    m_pTrack(NULL),
    m_mtCurrentChordTime(0),
    m_mtNextChordTime(0),
    m_mtLaterChordTime(0),
    m_pPattern(NULL),
    m_pdwPChannels(NULL),
    m_pVariations(NULL),
    m_pdwVariationMask(NULL),
    m_pdwRemoveVariations(NULL),
    m_pmtPartOffset(NULL),
    m_nInversionGroupCount(0),
    m_fNewPattern(TRUE),
    m_fStateActive(TRUE),
//  m_fStatePlay(TRUE),
    m_pMappings(NULL),
    m_ppEventSeek(NULL),
    m_dwGroupID(0xffffffff),
    m_plVariationSeeds(NULL),
    m_nTotalGenerators(0),
    m_mtPatternStart(0),
    m_dwValidate(0),
    m_hrPlayCode(S_OK),
    m_pfChangedVariation(NULL)
{
    ZeroMemory(&m_NextChord, sizeof(DMUS_CHORD_PARAM));
    wcscpy(m_CurrentChord.wszName, L"M7");
    m_CurrentChord.wMeasure = 0;
    m_CurrentChord.bBeat = 0;
    m_CurrentChord.bSubChordCount = 1;
    m_CurrentChord.bKey = 12;
    m_CurrentChord.dwScale = DEFAULT_SCALE_PATTERN;
    m_CurrentChord.bFlags = 0;
    for (int n = 0; n < DMUS_MAXSUBCHORD; n++)
    {
        m_CurrentChord.SubChordList[n].dwChordPattern = DEFAULT_CHORD_PATTERN;
        m_CurrentChord.SubChordList[n].dwScalePattern = DEFAULT_SCALE_PATTERN;
        m_CurrentChord.SubChordList[n].dwInversionPoints = 0xffffff;
        m_CurrentChord.SubChordList[n].dwLevels = 0xffffffff;
        m_CurrentChord.SubChordList[n].bChordRoot = 12; // 2C
        m_CurrentChord.SubChordList[n].bScaleRoot = 0;
    }
    for (int i = 0; i < INVERSIONGROUPLIMIT; i++)
        m_aInversionGroups[i].m_wGroupID = 0;
}


PatternTrackState::~PatternTrackState()
{
    if (m_pmtPartOffset)
        delete [] m_pmtPartOffset;
    if (m_pdwPChannels)
        delete [] m_pdwPChannels;
    if (m_pVariations)
        delete [] m_pVariations;
    if (m_pdwVariationMask)
        delete [] m_pdwVariationMask;
    if (m_pdwRemoveVariations)
        delete [] m_pdwRemoveVariations;
    if (m_pMappings) delete [] m_pMappings;
    if (m_ppEventSeek) delete [] m_ppEventSeek;
    if (m_plVariationSeeds) delete [] m_plVariationSeeds;
    if (m_pPattern) m_pPattern->Release();
    if (m_pfChangedVariation)
    {
        delete [] m_pfChangedVariation;
    }
}


HRESULT PatternTrackState::InitPattern(CDirectMusicPattern* pTargetPattern, MUSIC_TIME mtNow, CDirectMusicPattern* pOldPattern)
{
    m_fNewPattern = TRUE;
    m_mtPatternStart = mtNow;
    short nPartCount = (short) pTargetPattern->m_PartRefList.GetCount();
    // initialize an array to keep track of variations in parts.
    // if the current pattern is the same as the previous pattern,
    // use the existing array.
    if (m_pPattern != pTargetPattern || pOldPattern)
    {
        ////////////////// create an array of variation bools //////////////////
        if (m_pfChangedVariation) delete [] m_pfChangedVariation;
        m_pfChangedVariation = new bool[nPartCount];
        if (!m_pfChangedVariation)
        {
            return E_OUTOFMEMORY;
        }
        ////////////////// create an array of part offsets //////////////////
        if (m_pmtPartOffset != NULL) delete [] m_pmtPartOffset;
        m_pmtPartOffset = new MUSIC_TIME[nPartCount];
        if (!m_pmtPartOffset)
        {
            return E_OUTOFMEMORY;
        }
        ////////////////// create an array of seek pointers //////////////////
        if (m_ppEventSeek) delete [] m_ppEventSeek;
        m_ppEventSeek = new CDirectMusicEventItem*[nPartCount];
        if (!m_ppEventSeek)
        {
            return E_OUTOFMEMORY;
        }
        ////////////////// create and initialize PChannels //////////////////
        if (m_pdwPChannels != NULL) delete [] m_pdwPChannels;
        m_pdwPChannels = new DWORD[nPartCount];
        if (!m_pdwPChannels)
        {
            return E_OUTOFMEMORY;
        }
        TListItem<DirectMusicPartRef>* pPartRef = pTargetPattern->m_PartRefList.GetHead();
        for (int i = 0; pPartRef != NULL; pPartRef = pPartRef->GetNext(), i++)
        {
            m_pdwPChannels[i] = pPartRef->GetItemValue().m_dwLogicalPartID;
        }
        if (!pOldPattern ||
            pTargetPattern->m_strName != pOldPattern->m_strName ||
            nPartCount != pOldPattern->m_PartRefList.GetCount() )
        {
            ////////////////// create and initialize variations //////////////////
            if (m_pVariations != NULL) delete [] m_pVariations;
            m_pVariations = new BYTE[nPartCount];
            if (!m_pVariations)
            {
                return E_OUTOFMEMORY;
            }
            if (m_pdwVariationMask != NULL) delete [] m_pdwVariationMask;
            m_pdwVariationMask = new DWORD[nPartCount];
            if (!m_pdwVariationMask)
            {
                return E_OUTOFMEMORY;
            }
            if (m_pdwRemoveVariations != NULL) delete [] m_pdwRemoveVariations;
            m_pdwRemoveVariations = new DWORD[nPartCount];
            if (!m_pdwRemoveVariations)
            {
                return E_OUTOFMEMORY;
            }
            for (int i = 0; i < nPartCount; i++)
            {
                m_pdwVariationMask[i] = 0;
                if ( (pTargetPattern->m_dwFlags & DMUS_PATTERNF_PERSIST_CONTROL) &&
                     m_pPatternTrack &&
                     m_pPatternTrack->m_pVariations &&
                     m_pPatternTrack->m_pdwRemoveVariations )
                {
                    m_pVariations[i] = m_pPatternTrack->m_pVariations[i];
                    m_pdwRemoveVariations[i] = m_pPatternTrack->m_pdwRemoveVariations[i];
                }
                else
                {
                    m_pVariations[i] = -1;
                    m_pdwRemoveVariations[i] = 0;
                }
            }
        }
    }
    // initialize the part offset array and seek pointer array.
    for (int i = 0; i < nPartCount; i++)
    {
        m_pmtPartOffset[i] = 0;
        m_ppEventSeek[i] = NULL;
    }

    // Set up the new pattern.
    if (m_pPattern != pTargetPattern)
    {
        pTargetPattern->AddRef();
        if (m_pPattern) m_pPattern->Release();
        m_pPattern = pTargetPattern;
    }
    return S_OK;
}

// This assumes the time sig remains constant for the length of the segment.
// If time sigs change, we won't necessarily have one generator per beat, but
// this will still give consistent playback behavior under most circumstances;
// the exception is a controlling segment that interupts somewhere after the
// time signature changes.
HRESULT PatternTrackState::InitVariationSeeds(long lBaseSeed)
{
    // Get the Segment length
    MUSIC_TIME mtLength = 0;
    IDirectMusicSegment* pSegment = NULL;
    if (m_pSegState)
    {
        if (SUCCEEDED(m_pSegState->GetSegment(&pSegment)))
        {
            pSegment->GetLength(&mtLength);
            pSegment->Release();
        }
    }
    else
    {
        return E_POINTER;
    }

    // Get the current time sig and use it to get the number of beats in the segment
    DirectMusicTimeSig TimeSig = PatternTimeSig();
    int nBeats = TimeSig.ClocksToBeat(mtLength);

    // Create an array with the required number of beats, and use the Base Seed to
    //   seed a random number generator at each beat
    if (m_plVariationSeeds) delete [] m_plVariationSeeds;
    m_plVariationSeeds = new CRandomNumbers[nBeats];
    if (!m_plVariationSeeds)
    {
        m_nTotalGenerators = 0;
        return E_OUTOFMEMORY;
    }
    else
    {
        m_nTotalGenerators = nBeats;
        for (int i = 0; i < nBeats; i++)
        {
            m_plVariationSeeds[i].Seed(lBaseSeed);
            lBaseSeed = m_plVariationSeeds[i].Next();
        }
        return S_OK;
    }
}

HRESULT PatternTrackState::RemoveVariationSeeds()
{
    if (m_plVariationSeeds) delete [] m_plVariationSeeds;
    m_plVariationSeeds = NULL;
    m_nTotalGenerators = 0;
    return S_OK;
}

long PatternTrackState::RandomVariation(MUSIC_TIME mtTime, long lModulus)
{
    if (m_plVariationSeeds)
    {
        DirectMusicTimeSig TimeSig = PatternTimeSig();
        int nBeat = TimeSig.ClocksToBeat(mtTime);
        // In case time sigs change somehow, make sure we get a valid generator
        if (nBeat >= m_nTotalGenerators) nBeat = m_nTotalGenerators - 1;
        return m_plVariationSeeds[nBeat].Next(lModulus);
    }
    else
    {
        // regular old rand...
        return rand() % lModulus;
    }
}

void PatternTrackState::GetNextChord(MUSIC_TIME mtNow, MUSIC_TIME mtOffset, IDirectMusicPerformance* pPerformance, BOOL fStart, BOOL fSkipVariations)
{
    HRESULT hr = S_OK;
    {
        hr = pPerformance->GetParam(GUID_ChordParam, m_dwGroupID, DMUS_SEG_ANYTRACK, mtNow + mtOffset,
                                    &m_mtNextChordTime, (void*) &m_CurrentChord);
        if (SUCCEEDED(hr))
        {
            m_mtCurrentChordTime = mtNow;
            if (m_mtNextChordTime) m_mtNextChordTime += mtNow;
            TraceI(4, "[1] Offset: %d Next Chord: %d\n", mtOffset, m_mtNextChordTime);
#ifdef DBG
            if (!m_CurrentChord.bSubChordCount)
            {
                Trace(2, "Warning: Attempt to get a chord resulted in a chord with no subchords.\n");
            }
#endif
        }
    }
    // instead of failing here completely, I'll just give m_mtNextChordTime and m_CurrentChord
    // fallback values
    if (FAILED(hr))
    {
        m_mtCurrentChordTime = 0;
        m_mtNextChordTime = 0;
        if (!m_pStyle || !m_pStyle->UsingDX8()) // otherwise use current chord info
        {
            wcscpy(m_CurrentChord.wszName, L"M7");
            m_CurrentChord.wMeasure = 0;
            m_CurrentChord.bBeat = 0;
            m_CurrentChord.bSubChordCount = 1;
            m_CurrentChord.bKey = 12;
            m_CurrentChord.dwScale = DEFAULT_SCALE_PATTERN;
            m_CurrentChord.SubChordList[0].dwChordPattern = DEFAULT_CHORD_PATTERN;
            m_CurrentChord.SubChordList[0].dwScalePattern = DEFAULT_SCALE_PATTERN;
            m_CurrentChord.SubChordList[0].dwInversionPoints = 0xffffff;
            m_CurrentChord.SubChordList[0].dwLevels = 0xffffffff;
            m_CurrentChord.SubChordList[0].bChordRoot = 12; // 2C
            m_CurrentChord.SubChordList[0].bScaleRoot = 0;
        }
    }
    TraceI(3, "Current Chord: %d %s [%d] HRESULT: %x\n",
        m_CurrentChord.SubChordList[0].bChordRoot, m_CurrentChord.wszName, mtNow, hr);
    if (m_mtNextChordTime > 0)
    {
        hr = pPerformance->GetParam(GUID_ChordParam, m_dwGroupID, DMUS_SEG_ANYTRACK, m_mtNextChordTime + mtOffset,
                                    &m_mtLaterChordTime, (void*) &m_NextChord);
        if (SUCCEEDED(hr))
        {
            if (m_mtLaterChordTime) m_mtLaterChordTime += m_mtNextChordTime;
            TraceI(4, "[3] Offset: %d Later Chord: %d\n", mtOffset, m_mtLaterChordTime);
#ifdef DBG
            if (!m_NextChord.bSubChordCount)
            {
                Trace(2, "Warning: Attempt to get a chord resulted in a chord with no subchords.\n");
            }
#endif
        }
    }
    if (!fSkipVariations)
    {
        // Select a variation for each part in the pattern, based on the moaw and the
        // previous and next chords.
        DWORD dwFlags = 0;
        if (m_fNewPattern) dwFlags |= COMPUTE_VARIATIONSF_NEW_PATTERN;
        if (fStart) dwFlags |= COMPUTE_VARIATIONSF_START;
        if (m_pStyle && m_pStyle->UsingDX8()) dwFlags |= COMPUTE_VARIATIONSF_DX8;
        m_pPattern->ComputeVariations(dwFlags, m_CurrentChord, m_NextChord,
            m_abVariationGroups, m_pdwVariationMask, m_pdwRemoveVariations, m_pVariations, mtNow, m_mtNextChordTime, this);
        m_fNewPattern = FALSE;
        if ( (m_pPattern->m_dwFlags & DMUS_PATTERNF_PERSIST_CONTROL) &&
              m_pPatternTrack &&
              m_pPatternTrack->m_pVariations &&
              m_pPatternTrack->m_pdwRemoveVariations)
        {
            // update track's m_pVariations and m_pdwRemoveVariations (for each part)
            for (int i = 0; i < m_pPattern->m_PartRefList.GetCount(); i++)
            {
                m_pPatternTrack->m_pVariations[i] = m_pVariations[i];
                m_pPatternTrack->m_pdwRemoveVariations[i] = m_pdwRemoveVariations[i];
            }
        }
    }
}

DMStyleStruct* PatternTrackState::FindStyle(MUSIC_TIME mtTime, MUSIC_TIME& rmtTime)
{
    IDMStyle* pStyle = NULL;
    DMStyleStruct* pResult = NULL;
    if (m_pPatternTrack && m_pPatternTrack->m_pISList.GetHead())
    {
        TListItem<StylePair>* pScan = m_pPatternTrack->m_pISList.GetHead();
        for(; pScan; pScan = pScan->GetNext())
        {
            if (pScan->GetItemValue().m_pStyle) break;
        }
        if (pScan)
        {
            pStyle = pScan->GetItemValue().m_pStyle;
            for(pScan = pScan->GetNext(); pScan; pScan = pScan->GetNext())
            {
                StylePair& rScan = pScan->GetItemValue();
                if (rScan.m_pStyle)
                {
                    if ( mtTime < rScan.m_mtTime) break;
                    pStyle = rScan.m_pStyle;
                }
            }
            rmtTime = (pScan != NULL) ? pScan->GetItemValue().m_mtTime : 0;
            if (pStyle)
            {
                pStyle->GetStyleInfo((void**)&pResult);
            }
            else
            {
                return NULL;
            }
        }
    }
    return pResult;
}

DWORD PatternTrackState::Variations(DirectMusicPartRef&, int nPartIndex)
{
    return (m_pVariations[nPartIndex] == 0xff) ? 0 : (1 << m_pVariations[nPartIndex]);
}

BOOL PatternTrackState::PlayAsIs()
{
    return FALSE;
}

BOOL PatternTrackState::MapPChannel(DWORD dwPChannel, DWORD& dwMapPChannel)
{
    for (DWORD dw = 0; dw < m_pPatternTrack->m_dwPChannels; dw++)
    {
        if (m_pPatternTrack->m_pdwPChannels[dw] == dwPChannel)
        {
            dwMapPChannel = m_pMappings[dw].m_dwPChannelMap;
            return m_pMappings[dw].m_fMute;
        }
    }
    dwMapPChannel = 0;
    return FALSE;
}

inline int RandomExp(BYTE bRange)
{
    int nResult = 0;
    if (0 <= bRange && bRange <= 190)
    {
        nResult = bRange;
    }
    else if (191 <= bRange && bRange <= 212)
    {
        nResult = ((bRange - 190) * 5) + 190;
    }
    else if (213 <= bRange && bRange <= 232)
    {
        nResult = ((bRange - 212) * 10) + 300;
    }
    else // bRange > 232
    {
        nResult = ((bRange - 232) * 50) + 500;
    }
    return (rand() % nResult) - (nResult >> 1);
}

HRESULT PatternTrackState::PlayParts(MUSIC_TIME mtStart,
                                     MUSIC_TIME mtEnd,
                                     MUSIC_TIME mtOffset,
                                     REFERENCE_TIME rtOffset,
                                     MUSIC_TIME mtSection,
                                     IDirectMusicPerformance* pPerformance,
                                     DWORD dwPartFlags,
                                     DWORD dwPlayFlags,
                                     bool& rfReLoop)
{
    if (dwPlayFlags & DMUS_TRACKF_PLAY_OFF)
    {
        return S_OK;
    }
    if (!m_pPattern) // This shouldn't happen
    {
        return DMUS_E_NOT_INIT;
    }

    HRESULT hr = S_OK;
    bool fClockTime = (dwPartFlags & PLAYPARTSF_CLOCKTIME) ? true : false;
    bool fStart = (dwPartFlags & PLAYPARTSF_START) ? true : false;
    bool fGetChordStart = fStart;
    bool fFirstCall = (dwPartFlags & PLAYPARTSF_FIRST_CALL) ? true : false;
    bool fReloop = (dwPartFlags & PLAYPARTSF_RELOOP) ? true : false;
    bool fFlush = (dwPartFlags & PLAYPARTSF_FLUSH) ? true : false;
    MUSIC_TIME mtNewChord = mtStart;

    TListItem<DirectMusicPartRef>* pPartRef = m_pPattern->m_PartRefList.GetHead();
    for (short i = 0; pPartRef != NULL; pPartRef = pPartRef->GetNext(), i++)
    {
        m_pfChangedVariation[i] = false;
        MUSIC_TIME mtFinish = mtEnd;
        CurveSeek Curves;
        MUSIC_TIME mtNow = 0;
        DirectMusicPart* pPart = pPartRef->GetItemValue().m_pDMPart;
        DirectMusicTimeSig& TimeSig =
            (pPart->m_timeSig.m_bBeat != 0) ? pPart->m_timeSig : PatternTimeSig();
        MUSIC_TIME mtPartLength = TimeSig.ClocksPerMeasure() * pPart->m_wNumMeasures;
        if (fFirstCall)
        {
            if (fFlush)
            {
                GetNextMute(pPartRef->GetItemValue().m_dwLogicalPartID, 0, mtStart, mtOffset, pPerformance, fClockTime);
                m_ppEventSeek[i] = NULL;
            }
            if (fStart)
            {
                m_pmtPartOffset[i] = 0;
            }
            if (mtPartLength)
            {
                while (mtStart >= mtSection + m_pmtPartOffset[i] + mtPartLength)
                {
                    m_pmtPartOffset[i] += mtPartLength;
                }
            }
            if (mtFinish > mtSection + m_pmtPartOffset[i] + mtPartLength)
            {
                rfReLoop = TRUE;
                mtFinish = mtSection + m_pmtPartOffset[i] + mtPartLength;
            }
        }
        if (!fReloop || mtFinish > mtSection + m_pmtPartOffset[i] + mtPartLength)
        {
            if (fReloop)
            {
                m_pmtPartOffset[i] += mtPartLength;
            }
            CDirectMusicEventItem* pEvent = NULL;
            if (fFirstCall) pEvent = m_ppEventSeek[i];
            if (!pEvent) pEvent = pPart->EventList.GetHead();
            BumpTime(pEvent, TimeSig, mtSection + m_pmtPartOffset[i], mtNow);
            if (pEvent)
            {
                GetNextMute(pPartRef->GetItemValue().m_dwLogicalPartID, mtStart, mtNow, mtOffset, pPerformance, fClockTime);
            }
            while (pEvent != NULL && mtNow < mtFinish)
            {
                if (fFirstCall && fStart &&
                    mtNow < mtStart &&
                    pEvent->m_dwEventTag == DMUS_EVENT_CURVE)
                {
                    if (Variations(pPartRef->GetItemValue(), i) &
                        pEvent->m_dwVariation)
                    {
                        TraceI(4, "Found a curve\n");
                        Curves.AddCurve(pEvent, mtNow);
                    }
                }
                if (mtNow >= mtStart)
                {
                    if (mtNow < mtNewChord)
                    {
                        // Revert to the chord in effect at mtNow
                        TraceI(4, "WARNING: Reverting to chord at %d\n", mtNow);
                        GetNextChord(mtNow, mtOffset, pPerformance, (dwPartFlags & PLAYPARTSF_START) ? true : false);
                        mtNewChord = mtNow;
                    }
                    else if ((mtNow >= m_mtNextChordTime) || m_mtNextChordTime == 0)
                    {
                        TraceI(4, "Getting new chord.  Now: %d Next: %d\n", mtNow, m_mtNextChordTime);
                        GetNextChord(mtNow, mtOffset, pPerformance, fGetChordStart);
                        mtNewChord = mtNow;
                        fGetChordStart = false;
                    }
                    TraceI(4, "Play %d (%d + %d + %d)\n", mtNow, TimeSig.GridToClocks(pEvent->m_nGridStart), mtSection, m_pmtPartOffset[i]);
                    PlayPatternEvent(
                        mtNow,
                        pEvent,
                        TimeSig,
                        mtSection + m_pmtPartOffset[i],
                        mtOffset,
                        rtOffset,
                        pPerformance,
                        i,
                        pPartRef->GetItemValue(),
                        fClockTime,
                        0,
                        m_pfChangedVariation[i]);
                }
                pEvent = pEvent->GetNext();
                BumpTime(pEvent, TimeSig, mtSection + m_pmtPartOffset[i], mtNow);
                MUSIC_TIME mtMute = pEvent ? mtNow : mtFinish - 1;
                GetNextMute(pPartRef->GetItemValue().m_dwLogicalPartID, mtStart, mtMute, mtOffset, pPerformance, fClockTime);
            }
            m_ppEventSeek[i] = pEvent;
            // If we've got curve events, send them now
            if (fFirstCall && fStart)
            {
                TraceI(4, "Playing curves (after loop)\n");
                Curves.PlayCurves(this,
                        TimeSig,
                        mtSection + m_pmtPartOffset[i],
                        mtOffset,
                        rtOffset,
                        pPerformance,
                        i,
                        pPartRef->GetItemValue(),
                        fClockTime,
                        mtStart - (mtSection + m_pmtPartOffset[i]));
            }
        }
    }
    return hr;
}

// when creating a note event, both the passed in offset and the note's offset must
// be added to the note's time
void PatternTrackState::PlayPatternEvent(
        MUSIC_TIME mtNow,
        CDirectMusicEventItem* pEventItem,
        DirectMusicTimeSig& TimeSig,
        MUSIC_TIME mtPartOffset,
        MUSIC_TIME mtSegmentOffset,
        REFERENCE_TIME rtOffset,
        IDirectMusicPerformance* pPerformance,
        short nPart,
        DirectMusicPartRef& rPartRef,
        BOOL fClockTime,
        MUSIC_TIME mtPartStart,
        bool& rfChangedVariation)
{
    DMUS_NOTE_PMSG* pNote = NULL;
    DMUS_CURVE_PMSG* pCurve = NULL;
    DWORD dwMapPChannel = 0;
    BOOL fMute = MapPChannel(rPartRef.m_dwLogicalPartID, dwMapPChannel);
    if ( (!fMute) &&
         (Variations(rPartRef, nPart) & pEventItem->m_dwVariation) )
    {
        CDMStyleCurve* pCurveEvent = NULL;
        CDMStyleNote* pNoteEvent = NULL;
        CDMStyleMarker* pMarkerEvent = NULL;
        if (pEventItem->m_dwEventTag == DMUS_EVENT_MARKER) // we have a marker event
        {
            // If we're not ignoring marker events and we've hit a variation stop point that's
            // either not chord-aligned or on the chord, then get a new variation.
            pMarkerEvent = (CDMStyleMarker*)pEventItem;
            if ( (rPartRef.m_pDMPart && (rPartRef.m_pDMPart->m_dwFlags & DMUS_PARTF_USE_MARKERS)) &&
                 (pMarkerEvent->m_wFlags & DMUS_MARKERF_STOP) &&
                 (mtNow != m_mtPatternStart) &&
                 (!(pMarkerEvent->m_wFlags & DMUS_MARKERF_CHORD_ALIGN) ||
                   (mtNow == m_mtCurrentChordTime) ||
                   (mtNow == m_mtNextChordTime)) )
            {
                TraceI(3, "Computing variations at %d Pattern start: %d...\n", mtNow, m_mtPatternStart);
                DWORD dwFlags = COMPUTE_VARIATIONSF_NEW_PATTERN | COMPUTE_VARIATIONSF_MARKER;
                if ((pMarkerEvent->m_wFlags & DMUS_MARKERF_CHORD_ALIGN))
                {
                    dwFlags |= COMPUTE_VARIATIONSF_CHORD_ALIGN;
                }
                if (m_pStyle && m_pStyle->UsingDX8()) dwFlags |= COMPUTE_VARIATIONSF_DX8;
                if (rfChangedVariation) dwFlags |= COMPUTE_VARIATIONSF_CHANGED;
                m_pPattern->ComputeVariationGroup(
                    rPartRef,
                    nPart,
                    dwFlags,
                    m_CurrentChord,
                    m_NextChord,
                    m_abVariationGroups,
                    m_pdwVariationMask,
                    m_pdwRemoveVariations,
                    m_pVariations,
                    mtNow + pMarkerEvent->m_nTimeOffset,
                    m_mtNextChordTime,
                    this);
                rfChangedVariation = true;
                if ( (m_pPattern->m_dwFlags & DMUS_PATTERNF_PERSIST_CONTROL) &&
                     m_pPatternTrack &&
                     m_pPatternTrack->m_pVariations &&
                     m_pPatternTrack->m_pdwRemoveVariations )
                {
                    // update track's m_pVariations and m_pdwRemoveVariations (for this part)
                    m_pPatternTrack->m_pVariations[nPart] = m_pVariations[nPart];
                    m_pPatternTrack->m_pdwRemoveVariations[nPart] = m_pdwRemoveVariations[nPart];
                }
            }
            else
            {
                TraceI(3, "NOT computing variations at %d Pattern start: %d  Chord times: %d, %d Flags: %x\n",
                    mtNow, m_mtPatternStart, m_mtCurrentChordTime, m_mtNextChordTime, pMarkerEvent->m_wFlags);
            }
        }
        else if (pEventItem->m_dwEventTag == DMUS_EVENT_CURVE) // we have a curve event
        {
            pCurveEvent = (CDMStyleCurve*)pEventItem;
            if (SUCCEEDED(pPerformance->AllocPMsg( sizeof(DMUS_CURVE_PMSG),
                    (DMUS_PMSG**) &pCurve)))
            {
                MUSIC_TIME mtSegmentTime = TimeSig.GridToClocks(pCurveEvent->m_nGridStart) +
                    pCurveEvent->m_nTimeOffset + mtPartOffset;
                if (fClockTime)
                {
                    pCurve->wMeasure = 0;
                    pCurve->bBeat = 0;
                    pCurve->bGrid = 0;
                    pCurve->nOffset = pCurveEvent->m_nTimeOffset;
                    pCurve->rtTime = (mtSegmentTime * REF_PER_MIL) + rtOffset;
                    pCurve->dwFlags = DMUS_PMSGF_REFTIME | DMUS_PMSGF_LOCKTOREFTIME;
                }
                else
                {
                    pCurve->wMeasure = (WORD)TimeSig.GridsToMeasure(pCurveEvent->m_nGridStart);
                    pCurve->bBeat = (BYTE)TimeSig.GridsToBeat(pCurveEvent->m_nGridStart);
                    pCurve->bGrid = (BYTE)TimeSig.GridOffset(pCurveEvent->m_nGridStart);
                    pCurve->nOffset = pCurveEvent->m_nTimeOffset;
                    pCurve->mtTime = mtSegmentTime + mtSegmentOffset;
                    pCurve->dwFlags = DMUS_PMSGF_MUSICTIME;
                }
                pCurve->mtResetDuration = pCurveEvent->m_mtResetDuration;
                pCurve->mtDuration = pCurveEvent->m_mtDuration;
                pCurve->nResetValue = pCurveEvent->m_nResetValue;
                pCurve->bFlags = pCurveEvent->m_bFlags;
                pCurve->dwType = DMUS_PMSGT_CURVE;
                pCurve->dwPChannel = dwMapPChannel;
                pCurve->dwVirtualTrackID = m_dwVirtualTrackID; // ??
                pCurve->nStartValue = pCurveEvent->m_StartValue;    // curve's start value
                pCurve->nEndValue = pCurveEvent->m_EndValue;    // curve's end value
                pCurve->bType = pCurveEvent->m_bEventType;  // type of curve
                pCurve->bCurveShape = pCurveEvent->m_bCurveShape;   // shape of curve
                pCurve->bCCData = pCurveEvent->m_bCCData;       // CC# if this is a control change type
                pCurve->dwGroupID = m_dwGroupID;
                pCurve->wParamType = pCurveEvent->m_wParamType;
                pCurve->wMergeIndex = pCurveEvent->m_wMergeIndex;
                // Set the DX8 flag to indicate the wMergeIndex and wParamType fields are valid.
                pCurve->dwFlags |= DMUS_PMSGF_DX8;
                if (mtPartStart) // only set on invalidation
                {
                    MUSIC_TIME mtOffset = mtPartOffset + mtSegmentOffset;
                    if (pCurve->mtTime + pCurve->mtDuration >= mtPartStart + mtOffset)
                    {
                        pCurve->mtOriginalStart = pCurve->mtTime;
                        pCurve->mtTime = mtPartStart + mtOffset;
                    }
                    else
                    {
                        pCurve->mtResetDuration -= (mtPartStart + mtOffset - pCurve->mtTime);
                        if (pCurve->mtResetDuration < 0) pCurve->mtResetDuration = 0;
                        pCurve->mtTime = mtPartStart + mtOffset;
                        pCurve->bCurveShape = DMUS_CURVES_INSTANT;
                    }
                }
                IDirectMusicGraph* pGraph;
                if( SUCCEEDED( m_pSegState->QueryInterface( IID_IDirectMusicGraph,
                    (void**)&pGraph )))
                {
                    pGraph->StampPMsg( (DMUS_PMSG*)pCurve );
                    pGraph->Release();
                }
                if(FAILED(pPerformance->SendPMsg( (DMUS_PMSG*)pCurve)))
                {
                    pPerformance->FreePMsg( (DMUS_PMSG*)pCurve);
                }
            }
        }
        else if (pEventItem->m_dwEventTag == DMUS_EVENT_NOTE) // we have a note event
        {
            pNoteEvent = (CDMStyleNote*)pEventItem;
            BYTE bPlayModeFlags =
                (pNoteEvent->m_bPlayModeFlags & DMUS_PLAYMODE_NONE) ?
                    rPartRef.m_pDMPart->m_bPlayModeFlags :
                    pNoteEvent->m_bPlayModeFlags;
            BYTE bMidiValue = 0;
            short nMidiOffset = 0;
            HRESULT hr = rPartRef.ConvertMusicValue(pNoteEvent,
                                      m_CurrentChord,
                                      bPlayModeFlags,
                                      PlayAsIs(),
                                      m_aInversionGroups,
                                      pPerformance,
                                      bMidiValue,
                                      nMidiOffset);
            if (SUCCEEDED(hr) &&
                SUCCEEDED(pPerformance->AllocPMsg( sizeof(DMUS_NOTE_PMSG),
                    (DMUS_PMSG**) &pNote)))
            {
                pNote->bFlags = DMUS_NOTEF_NOTEON | pNoteEvent->m_bFlags;
                MUSIC_TIME mtSegmentTime = TimeSig.GridToClocks(pNoteEvent->m_nGridStart) +
                    pNoteEvent->m_nTimeOffset + mtPartOffset;
                if (fClockTime)
                {
                    pNote->wMeasure = 0;
                    pNote->bBeat = 0;
                    pNote->bGrid = 0;
                    pNote->nOffset = pNoteEvent->m_nTimeOffset;
                    pNote->rtTime = (mtSegmentTime * REF_PER_MIL) + rtOffset;
                    pNote->dwFlags = DMUS_PMSGF_REFTIME | DMUS_PMSGF_LOCKTOREFTIME;
                }
                else
                {
                    pNote->wMeasure = (WORD)TimeSig.GridsToMeasure(pNoteEvent->m_nGridStart);
                    pNote->bBeat = (BYTE)TimeSig.GridsToBeat(pNoteEvent->m_nGridStart);
                    pNote->bGrid = (BYTE)TimeSig.GridOffset(pNoteEvent->m_nGridStart);
                    pNote->nOffset = pNoteEvent->m_nTimeOffset;
                    pNote->mtTime = mtSegmentTime + mtSegmentOffset;
                    pNote->dwFlags = DMUS_PMSGF_MUSICTIME;
                }
                // time needs be jiggled by pNoteEvent->m_bTimeRange
                if (pNoteEvent->m_bTimeRange)
                    pNote->mtTime += RandomExp(pNoteEvent->m_bTimeRange);
                pNote->mtDuration = pNoteEvent->m_mtDuration;
                // duration needs be jiggled by pNoteEvent->m_bDurRange
                if (pNoteEvent->m_bDurRange)
                    pNote->mtDuration += RandomExp(pNoteEvent->m_bDurRange);
                    //  (rand() % pNoteEvent->m_bDurRange) - (pNoteEvent->m_bDurRange >> 1);
                pNote->bVelocity = pNoteEvent->m_bVelocity;
                // velocity needs be jiggled by pNoteEvent->m_bVelRange
                if (pNoteEvent->m_bVelRange)
                    pNote->bVelocity +=
                      (rand() % pNoteEvent->m_bVelRange) - (pNoteEvent->m_bVelRange >> 1);
                if (pNote->bVelocity < 1) pNote->bVelocity = 1;
                if (pNote->bVelocity > 127) pNote->bVelocity = 127;
                pNote->wMusicValue = pNoteEvent->m_wMusicValue;
                pNote->bMidiValue = bMidiValue;
                pNote->dwType = DMUS_PMSGT_NOTE;
                pNote->bPlayModeFlags = bPlayModeFlags;
                pNote->dwPChannel = dwMapPChannel;
                pNote->dwVirtualTrackID = m_dwVirtualTrackID; // ??
                pNote->bSubChordLevel = rPartRef.m_bSubChordLevel;
                pNote->dwGroupID = m_dwGroupID;
                pNote->bTimeRange = pNoteEvent->m_bTimeRange;
                pNote->bDurRange = pNoteEvent->m_bDurRange;
                pNote->bVelRange = pNoteEvent->m_bVelRange;
                pNote->cTranspose = (char) nMidiOffset;

                IDirectMusicGraph* pGraph;
                if( SUCCEEDED( m_pSegState->QueryInterface( IID_IDirectMusicGraph,
                    (void**)&pGraph )))
                {
                    pGraph->StampPMsg( (DMUS_PMSG*)pNote );
                    pGraph->Release();
                }

                if (pNote->dwFlags & DMUS_PMSGF_REFTIME)
                {
                    TraceI(5, "PLAY %d @%d\n", rPartRef.m_dwLogicalPartID,
                        (MUSIC_TIME) (pNote->rtTime/REF_PER_MIL));
                }
                else
                {
                    TraceI(5, "PLAY %d @%d: %x [%d]{%x}\n", rPartRef.m_dwLogicalPartID, pNote->mtTime,
                        pNote->wMusicValue, pNote->bMidiValue, Variations(rPartRef, nPart));
                }
                if(FAILED(pPerformance->SendPMsg( (DMUS_PMSG*)pNote) ))
                {
                    pPerformance->FreePMsg( (DMUS_PMSG*)pNote);
                }
            }
        }
    }
}


void PatternTrackState::SendTimeSigMessage(MUSIC_TIME mtNow, MUSIC_TIME mtOffset, MUSIC_TIME mtTime, IDirectMusicPerformance* pPerformance)
{
    if (!m_pStyle) return;
    IDirectMusicGraph* pGraph = NULL;
    DMUS_TIMESIG_PMSG* pTimeSig;
    if( FAILED( m_pSegState->QueryInterface( IID_IDirectMusicGraph,
        (void**)&pGraph )))
    {
        pGraph = NULL;
    }
    if( SUCCEEDED( pPerformance->AllocPMsg( sizeof(DMUS_TIMESIG_PMSG),
        (DMUS_PMSG**)&pTimeSig )))
    {
        if( mtTime < mtNow )
        {
            // this only happens in the case where we've puposefully seeked
            // and need to time stamp this event with the start time
            pTimeSig->mtTime = mtNow + mtOffset;
        }
        else
        {
            pTimeSig->mtTime = mtTime + mtOffset;
        }
        pTimeSig->bBeatsPerMeasure = m_pStyle->m_TimeSignature.m_bBeatsPerMeasure;
        pTimeSig->bBeat = m_pStyle->m_TimeSignature.m_bBeat;
        pTimeSig->wGridsPerBeat = m_pStyle->m_TimeSignature.m_wGridsPerBeat;
        pTimeSig->dwFlags |= DMUS_PMSGF_MUSICTIME;
        pTimeSig->dwVirtualTrackID = m_dwVirtualTrackID;
        pTimeSig->dwType = DMUS_PMSGT_TIMESIG;
        pTimeSig->dwGroupID = m_dwGroupID;

        if( pGraph )
        {
            pGraph->StampPMsg( (DMUS_PMSG*)pTimeSig );
            pGraph->Release();
        }
        TraceI(3, "TimeSigtrk: TimeSig event\n");
        if(FAILED(pPerformance->SendPMsg( (DMUS_PMSG*)pTimeSig )))
        {
            pPerformance->FreePMsg( (DMUS_PMSG*)pTimeSig );
        }
    }
}

// send measure and beat notifications
MUSIC_TIME PatternTrackState::NotifyMeasureBeat(
    MUSIC_TIME mtStart, MUSIC_TIME mtEnd, MUSIC_TIME mtOffset, IDirectMusicPerformance* pPerformance, DWORD dwFlags )
{
    if (dwFlags & DMUS_TRACKF_NOTIFY_OFF)
    {
        return S_OK;
    }

    DMUS_NOTIFICATION_PMSG* pEvent = NULL;
    BYTE bCurrentBeat;
    WORD wCurrentMeasure;
    DirectMusicTimeSig& rTimeSig = PatternTimeSig();

    // now actually generate the beat events.
    // Generate events that are on beat boundaries, from mtStart to mtEnd
    long lQuantize = ( DMUS_PPQ * 4 ) / rTimeSig.m_bBeat;
    long lAbsoluteBeat = mtStart / lQuantize;

    bCurrentBeat = (BYTE) (lAbsoluteBeat % rTimeSig.m_bBeatsPerMeasure);
    wCurrentMeasure = (WORD) (lAbsoluteBeat / rTimeSig.m_bBeatsPerMeasure);
    while( mtStart < mtEnd )
    {
        if( SUCCEEDED( pPerformance->AllocPMsg( sizeof(DMUS_NOTIFICATION_PMSG),
            (DMUS_PMSG**)&pEvent )))
        {
            pEvent->dwField1 = 0;
            pEvent->dwField2 = 0;
            pEvent->dwType = DMUS_PMSGT_NOTIFICATION;
            pEvent->mtTime = mtStart + mtOffset;
            pEvent->dwFlags = DMUS_PMSGF_MUSICTIME | DMUS_PMSGF_TOOL_ATTIME;
            m_pSegState->QueryInterface(IID_IUnknown, (void**)&pEvent->punkUser);

            pEvent->dwNotificationOption = DMUS_NOTIFICATION_MEASUREBEAT;
            pEvent->dwField1 = bCurrentBeat;
            pEvent->dwField2 = wCurrentMeasure;
            pEvent->guidNotificationType = GUID_NOTIFICATION_MEASUREANDBEAT;
            pEvent->dwGroupID = m_dwGroupID;

            IDirectMusicGraph* pGraph;
            if( SUCCEEDED( m_pSegState->QueryInterface( IID_IDirectMusicGraph,
                (void**)&pGraph )))
            {
                pGraph->StampPMsg((DMUS_PMSG*) pEvent );
                pGraph->Release();
            }
            if(FAILED(pPerformance->SendPMsg((DMUS_PMSG*) pEvent )))
            {
                pPerformance->FreePMsg( (DMUS_PMSG*)pEvent);;
            }
        }
        bCurrentBeat++;
        if( bCurrentBeat >= rTimeSig.m_bBeatsPerMeasure )
        {
            bCurrentBeat = 0;
            wCurrentMeasure++;
        }
        mtStart += lQuantize;
    }
    return mtEnd;
}

MUSIC_TIME PatternTrackState::PartOffset(int nPartIndex)
{
    return m_pmtPartOffset[nPartIndex];
}

/////////////////////////////////////////////////////////////////////////////
// PatternTrackInfo

PatternTrackInfo::PatternTrackInfo() :
    m_fNotifyMeasureBeat(FALSE), m_dwPChannels(0), m_pdwPChannels(NULL),
    m_fActive(TRUE),
//  m_fTrackPlay(TRUE),
    m_fStateSetBySetParam(FALSE),
//  m_fStatePlaySetBySetParam(FALSE),
    m_fChangeStateMappings(FALSE),
    m_lRandomNumberSeed(0),
    m_dwValidate(0),
    m_pVariations(NULL),
    m_pdwRemoveVariations(NULL)
{
}

PatternTrackInfo::PatternTrackInfo(
        const PatternTrackInfo* pInfo, MUSIC_TIME mtStart, MUSIC_TIME mtEnd) :
    m_dwPChannels(0), m_pdwPChannels(NULL), m_lRandomNumberSeed(0), m_dwValidate(0),
    m_pVariations(NULL), m_pdwRemoveVariations(NULL)
{
    if (pInfo)
    {
        m_fChangeStateMappings = pInfo->m_fChangeStateMappings;
        m_fNotifyMeasureBeat = pInfo->m_fNotifyMeasureBeat;
        m_fActive = pInfo->m_fActive;
//      m_fTrackPlay = pInfo->m_fTrackPlay;
        m_fStateSetBySetParam = pInfo->m_fStateSetBySetParam;
//      m_fStatePlaySetBySetParam = pInfo->m_fStatePlaySetBySetParam;
    }
    TListItem<StylePair>* pScan = pInfo->m_pISList.GetHead();
    //1////////////////////////////////////////
    TListItem<StylePair>* pPrevious = NULL;
    //1////////////////////////////////////////
    for(; pScan; pScan = pScan->GetNext())
    {
        StylePair& rScan = pScan->GetItemValue();
        //2////////////////////////////////////////
        if (rScan.m_mtTime < mtStart)
        {
            pPrevious = pScan;
        }
        //2////////////////////////////////////////
        else if (rScan.m_mtTime < mtEnd)
        {
            //3////////////////////////////////////////
            if (rScan.m_mtTime == mtStart)
            {
                pPrevious = NULL;
            }
            //3////////////////////////////////////////
            TListItem<StylePair>* pNew = new TListItem<StylePair>;
            if (pNew)
            {
                StylePair& rNew = pNew->GetItemValue();
                rNew.m_mtTime = rScan.m_mtTime - mtStart;
                rNew.m_pStyle = rScan.m_pStyle;
                if (rNew.m_pStyle) rNew.m_pStyle->AddRef();
                m_pISList.AddTail(pNew);
            }
        }
    }
    //4////////////////////////////////////////
    if (pPrevious)
    {
        TListItem<StylePair>* pNew = new TListItem<StylePair>;
        if (pNew)
        {
            StylePair& rNew = pNew->GetItemValue();
            rNew.m_mtTime = 0;
            rNew.m_pStyle = pPrevious->GetItemValue().m_pStyle;
            if (rNew.m_pStyle) rNew.m_pStyle->AddRef();
            m_pISList.AddHead(pNew);
        }
    }
    //4////////////////////////////////////////
}

PatternTrackInfo::~PatternTrackInfo()
{
    if (m_pdwPChannels) delete [] m_pdwPChannels;
    if (m_pVariations) delete [] m_pVariations;
    if (m_pdwRemoveVariations) delete [] m_pdwRemoveVariations;
}

PatternTrackState* PatternTrackInfo::FindState(IDirectMusicSegmentState* pSegState)
{
    TListItem<StatePair>* pPair = m_StateList.GetHead();
    for (; pPair; pPair = pPair->GetNext())
    {
        if (pPair->GetItemValue().m_pSegState == pSegState)
        {
            return pPair->GetItemValue().m_pStateData;
        }
    }
    return NULL;
}

HRESULT PatternTrackInfo::EndPlay(PatternTrackState* pStateData)
{
    if (!pStateData) return E_FAIL;
    for (TListItem<StatePair>* pScan = m_StateList.GetHead(); pScan; pScan = pScan->GetNext())
    {
        StatePair& rPair = pScan->GetItemValue();
        if (pStateData == rPair.m_pStateData)
        {
            rPair.m_pSegState = NULL;
            rPair.m_pStateData = NULL;
            break;
        }
    }
    delete pStateData;
    return S_OK;
}

HRESULT STDMETHODCALLTYPE PatternTrackInfo::AddNotificationType(
    /* [in] */  REFGUID rGuidNotify)
{
    if( rGuidNotify == GUID_NOTIFICATION_MEASUREANDBEAT )
    {
        m_fNotifyMeasureBeat = TRUE;
        return S_OK;
    }
    else
    {
        return S_FALSE;
    }
}

HRESULT STDMETHODCALLTYPE PatternTrackInfo::RemoveNotificationType(
    /* [in] */  REFGUID rGuidNotify)
{
    if( rGuidNotify == GUID_NOTIFICATION_MEASUREANDBEAT )
    {
        m_fNotifyMeasureBeat = FALSE;
        return S_OK;
    }
    else
    {
        return S_FALSE;
    }
}

HRESULT PatternTrackInfo::InitTrackVariations(CDirectMusicPattern* pPattern)
{
    HRESULT hr = S_OK;
    if ( pPattern && (pPattern->m_dwFlags & DMUS_PATTERNF_PERSIST_CONTROL) )
    {
        // delete the variation arrays if they exist;
        if (m_pVariations)
        {
            delete [] m_pVariations;
            m_pVariations = NULL;
        }
        if (m_pdwRemoveVariations)
        {
            delete [] m_pdwRemoveVariations;
            m_pdwRemoveVariations = NULL;
        }
        // init the variation arrays to the number of parts in the pattern
        int nPartCount = pPattern->m_PartRefList.GetCount();
        m_pVariations = new BYTE[nPartCount];
        if (!m_pVariations)
        {
            return E_OUTOFMEMORY;
        }
        m_pdwRemoveVariations = new DWORD[nPartCount];
        if (!m_pdwRemoveVariations)
        {
            return E_OUTOFMEMORY;
        }
        for (int i = 0; i < nPartCount; i++)
        {
            m_pVariations[i] = -1;
            m_pdwRemoveVariations[i] = 0;
        }
    }
    return hr;
}

HRESULT PatternTrackInfo::MergePChannels()
{
    TList<DWORD> PChannelList;
    DMStyleStruct* pStruct = NULL;
    HRESULT hr = S_OK;

    TListItem<StylePair>* pScan = m_pISList.GetHead();
    for( ; pScan; pScan = pScan->GetNext())
    {
        if (pScan->GetItemValue().m_pStyle)
        {
            pScan->GetItemValue().m_pStyle->GetStyleInfo((void**)&pStruct);
            TListItem<DWORD>* pChannel = pStruct->m_PChannelList.GetHead();
            for (; pChannel; pChannel = pChannel->GetNext() )
            {
                AdjoinPChannel(PChannelList, pChannel->GetItemValue() );
            }
        }
    }
    if (PChannelList.IsEmpty())
    {
        AdjoinPChannel(PChannelList, 0);
    }

    TListItem<DWORD>* pPChannel = PChannelList.GetHead();

    m_dwPChannels = pPChannel->GetCount();
    if (m_pdwPChannels) delete [] m_pdwPChannels;
    m_pdwPChannels = new DWORD[m_dwPChannels];
    if (!m_pdwPChannels)
    {
        hr = E_OUTOFMEMORY;
    }
    else
    {
        for (int i = 0; i < (int)m_dwPChannels; i++)
        {
            m_pdwPChannels[i] = pPChannel->GetItemValue();
            pPChannel = pPChannel->GetNext();
        }
        m_fChangeStateMappings = TRUE;
    }
    return hr;
}

inline int CurveIndex(CDirectMusicEventItem* pEvent)
{
    CDMStyleCurve* pCurve = NULL;
    if (pEvent->m_dwEventTag == DMUS_EVENT_CURVE)
    {
        pCurve = (CDMStyleCurve*)pEvent;
        switch (pCurve->m_bEventType)
        {
        case DMUS_CURVET_CCCURVE:
            return pCurve->m_bCCData & 0x7f;
        case DMUS_CURVET_PATCURVE:
            return (pCurve->m_bCCData & 0x7f) + 128;
        case DMUS_CURVET_PBCURVE:
            return 256;
        case DMUS_CURVET_MATCURVE:
            return 257;
        default:
            return -1;
        }
    }
    return -1;
}

CurveSeek::CurveSeek()
{
    for (int nType = 0; nType < CURVE_TYPES; nType++)
    {
        m_apCurves[nType] = NULL;
        m_amtTimeStamps[nType] = 0;
    }
    m_fFoundCurve = false;
}

void CurveSeek::AddCurve(CDirectMusicEventItem* pEvent, MUSIC_TIME mtTimeStamp)
{
    int nIndex = CurveIndex(pEvent);
    if (nIndex >= 0)
    {
        if (!m_apCurves[nIndex] ||
            m_amtTimeStamps[nIndex] < mtTimeStamp)
        {
            m_apCurves[nIndex] = pEvent;
            m_amtTimeStamps[nIndex] = mtTimeStamp;
            m_fFoundCurve = true;
        }
    }
}

void CurveSeek::PlayCurves(
        PatternTrackState* pStateData,
        DirectMusicTimeSig& TimeSig,
        MUSIC_TIME mtPatternOffset,
        MUSIC_TIME mtOffset,
        REFERENCE_TIME rtOffset,
        IDirectMusicPerformance* pPerformance,
        short nPart,
        DirectMusicPartRef& rPartRef,
        BOOL fClockTime,
        MUSIC_TIME mtPartStart)
{
    if (m_fFoundCurve)
    {
        for (int nType = 0; nType < CURVE_TYPES; nType++)
        {
            CDirectMusicEventItem* pScan = m_apCurves[nType];
            if (pScan)
            {
                int nGrid = pScan->m_nGridStart;
                CDirectMusicEventItem* pWinner = pScan;
                MUSIC_TIME mtBiggest = pWinner->m_nTimeOffset;
                for (; pScan && pScan->m_nGridStart == nGrid; pScan = pScan->GetNext())
                {
                    if (pScan->m_dwEventTag == DMUS_EVENT_CURVE &&
                        pScan->m_nTimeOffset > mtBiggest)
                    {
                        pWinner = pScan;
                        mtBiggest = pWinner->m_nTimeOffset;
                    }
                }
                MUSIC_TIME mtNow = 0;
                bool fChange = false;
                pStateData->BumpTime(pWinner, TimeSig, mtPatternOffset, mtNow);
                pStateData->PlayPatternEvent(
                    mtNow,
                    pWinner,
                    TimeSig,
                    mtPatternOffset,
                    mtOffset,
                    rtOffset,
                    pPerformance,
                    nPart,
                    rPartRef,
                    fClockTime,
                    mtPartStart,
                    fChange);
            }
        }
        m_fFoundCurve = false;
    }
}