Source code of Windows XP (NT5)
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

10907 lines
378 KiB

// Copyright (c) 1998-2001 Microsoft Corporation
// dmperf.cpp
#include <windows.h>
#include <mmsystem.h>
#include <time.h> // To seed random number generator
#include <dsoundp.h>
#include "debug.h"
#define ASSERT assert
#include "dmperf.h"
#include "dmime.h"
#include "dmgraph.h"
#include "dmsegobj.h"
#include "song.h"
#include "curve.h"
#include "math.h"
#include "..\shared\Validate.h"
#include "..\dmstyle\dmstylep.h"
#include <ks.h>
#include "dmksctrl.h"
#include <dsound.h>
#include "dmscriptautguids.h"
#include "..\shared\dmusiccp.h"
#include "wavtrack.h"
#include "tempotrk.h"
#include <strsafe.h>
#pragma warning(disable:4296)
#define PORT_CHANNEL 0
// @doc EXTERNAL
#define MIDI_NOTEOFF 0x80
#define MIDI_NOTEON 0x90
#define MIDI_PTOUCH 0xA0
#define MIDI_CCHANGE 0xB0
#define MIDI_PCHANGE 0xC0
#define MIDI_MTOUCH 0xD0
#define MIDI_PBEND 0xE0
#define MIDI_SYSX 0xF0
#define MIDI_MTC 0xF1
#define MIDI_SONGPP 0xF2
#define MIDI_SONGS 0xF3
#define MIDI_EOX 0xF7
#define MIDI_CLOCK 0xF8
#define MIDI_START 0xFA
#define MIDI_CONTINUE 0xFB
#define MIDI_STOP 0xFC
#define MIDI_SENSE 0xFE
#define MIDI_CC_BS_MSB 0x00
#define MIDI_CC_BS_LSB 0x20
#define MIDI_CC_DATAENTRYMSB 0x06
#define MIDI_CC_DATAENTRYLSB 0x26
#define MIDI_CC_NRPN_LSB 0x62
#define MIDI_CC_NRPN_MSB 0x63
#define MIDI_CC_RPN_LSB 0x64
#define MIDI_CC_RPN_MSB 0x65
#define MIDI_CC_MOD_WHEEL 0x01
#define MIDI_CC_VOLUME 0x07
#define MIDI_CC_PAN 0x0A
#define MIDI_CC_EXPRESSION 0x0B
#define MIDI_CC_FILTER 0x4A
#define MIDI_CC_REVERB 0x5B
#define MIDI_CC_CHORUS 0x5D
#define MIDI_CC_RESETALL 0x79
#define MIDI_CC_ALLSOUNDSOFF 0x78
#define CLEARTOOLGRAPH(x) { \
if( (x)->pTool ) \
{ \
(x)->pTool->Release(); \
(x)->pTool = NULL; \
} \
if( (x)->pGraph ) \
{ \
(x)->pGraph->Release(); \
(x)->pGraph = NULL; }}
#define GetLatencyWithPrePlay() ( GetLatency() + m_rtBumperLength )
void CChannelBlockList::Clear()
{
CChannelBlock* pCB;
while( pCB = RemoveHead() )
{
delete pCB;
}
}
void CChannelMap::Clear()
{
Reset(TRUE); // Clear all MIDI controllers
m_TransposeMerger.Clear(0); // No transpose.
nTranspose = 0;
wFlags = CMAP_FREE;
}
void CChannelMap::Reset(BOOL fVolumeAndPanToo)
{
if (fVolumeAndPanToo)
{
m_PanMerger.Clear(0); // Panned to center.
m_VolumeMerger.Clear(-415); // Equivalent to MIDI value 100.
}
m_PitchbendMerger.Clear(0); // No pitch bend.
m_ExpressionMerger.Clear(0);// Full volume for expression (MIDI 127.)
m_FilterMerger.Clear(0); // No filter change.
m_ReverbMerger.Clear(-87); // Start at default level (MIDI 40).
m_ChorusMerger.Clear(-127); // Start with no chorus.
m_ModWheelMerger.Clear(-127); // Start with no mod wheel.
}
void CParamMerger::Clear(long lInitValue )
{
CMergeParam *pParam;
while (pParam = RemoveHead())
{
delete pParam;
}
m_lZeroIndexData = lInitValue;
m_lMergeTotal = 0;
}
long CParamMerger::m_lMIDIToDB[128] = { // Global array used to convert MIDI to dB.
-9600, -8415, -7211, -6506, -6006, -5619, -5302, -5034,
-4802, -4598, -4415, -4249, -4098, -3959, -3830, -3710,
-3598, -3493, -3394, -3300, -3211, -3126, -3045, -2968,
-2894, -2823, -2755, -2689, -2626, -2565, -2506, -2449,
-2394, -2341, -2289, -2238, -2190, -2142, -2096, -2050,
-2006, -1964, -1922, -1881, -1841, -1802, -1764, -1726,
-1690, -1654, -1619, -1584, -1551, -1518, -1485, -1453,
-1422, -1391, -1361, -1331, -1302, -1273, -1245, -1217,
-1190, -1163, -1137, -1110, -1085, -1059, -1034, -1010,
-985, -961, -938, -914, -891, -869, -846, -824,
-802, -781, -759, -738, -718, -697, -677, -657,
-637, -617, -598, -579, -560, -541, -522, -504,
-486, -468, -450, -432, -415, -397, -380, -363,
-347, -330, -313, -297, -281, -265, -249, -233,
-218, -202, -187, -172, -157, -142, -127, -113,
-98, -84, -69, -55, -41, -27, -13, 0
};
long CParamMerger::m_lDBToMIDI[97] = { // Global array used to convert db to MIDI.
127, 119, 113, 106, 100, 95, 89, 84, 80, 75,
71, 67, 63, 60, 56, 53, 50, 47, 45, 42,
40, 37, 35, 33, 31, 30, 28, 26, 25, 23,
22, 21, 20, 19, 17, 16, 15, 15, 14, 13,
12, 11, 11, 10, 10, 9, 8, 8, 8, 7,
7, 6, 6, 6, 5, 5, 5, 4, 4, 4,
4, 3, 3, 3, 3, 3, 2, 2, 2, 2,
2, 2, 2, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0
};
CParamMerger::CParamMerger()
{
m_lMergeTotal = 0;
m_lZeroIndexData = 0;
}
BYTE CParamMerger::VolumeToMidi(long lVolume)
{
if (lVolume < -9600) lVolume = -9600;
if (lVolume > 0) lVolume = 0;
lVolume = -lVolume;
long lFraction = lVolume % 100;
lVolume = lVolume / 100;
long lResult = m_lDBToMIDI[lVolume];
lResult += ((m_lDBToMIDI[lVolume + 1] - lResult) * lFraction) / 100;
return (BYTE) lResult;
}
/* MergeMidiVolume() takes an incoming volume and updates the matching
MergeParam structure (determined by index.) If there is no such matching
structure, it creates one. Also, the volumes are totaled to create a new
total volume, which is converted back to MIDI volume and returned.
This mechanism allows us to introduce additional volume controllers
that are summed.
*/
BYTE CParamMerger::MergeMidiVolume(DWORD dwIndex, BYTE bMIDIVolume)
{
long lVolume = MergeData(dwIndex,m_lMIDIToDB[bMIDIVolume]);
if (m_lMergeTotal || dwIndex) // Optimization for simplest and most frequent case - there are no additional indexes.
{
return (BYTE) VolumeToMidi(lVolume);
}
return bMIDIVolume;
}
BYTE CParamMerger::GetVolumeStart(DWORD dwIndex)
{
if (dwIndex == 0)
{
return VolumeToMidi(m_lZeroIndexData);
}
return VolumeToMidi(GetIndexedValue(dwIndex));
}
/* MergeValue is used for all data types that have a plus and minus range
around a center bias. These include pitch bend, pan and filter.
MergeValue takes an incoming data value, adds the bias (in lRange),
calls MergeData to combine it with the other merged inputs,
adds the bias back in and checks for over or underflow.
*/
long CParamMerger::MergeValue(DWORD dwIndex, long lData, long lCenter, long lRange)
{
lData = MergeData(dwIndex,lData - lCenter) + lCenter;
if (lData < 0) lData = 0;
if (lData > lRange) lData = lRange;
return lData;
}
short CParamMerger::MergeTranspose(DWORD dwIndex, short nTranspose)
{
return (short) MergeData(dwIndex,nTranspose);
}
long CParamMerger::MergeData(DWORD dwIndex, long lData)
{
if (dwIndex)
{
// If this has an index, scan the indexes. Look
// for the matching index. If it is found, update it
// with the new data. Meanwhile, add up all the data fields.
// If it is not found, add an entry for it.
m_lMergeTotal = 0; // Recalculate
BOOL fNoEntry = TRUE;
CMergeParam *pParam = GetHead();
for (;pParam;pParam = pParam->GetNext())
{
if (pParam->m_dwIndex == dwIndex)
{
// Found the index. Store the new value.
pParam->m_lData = lData;
fNoEntry = FALSE;
}
// Sum all values to create the merged total.
m_lMergeTotal += pParam->m_lData;
}
if (fNoEntry)
{
// Didn't find the index. Create one and store the value.
pParam = new CMergeParam;
if (pParam)
{
pParam->m_dwIndex = dwIndex;
pParam->m_lData = lData;
m_lMergeTotal += lData;
AddHead(pParam);
}
}
// Add the initial value for merge index 0.
lData = m_lMergeTotal + m_lZeroIndexData;
}
else
{
m_lZeroIndexData = lData;
lData += m_lMergeTotal;
}
return lData;
}
long CParamMerger::GetIndexedValue(DWORD dwIndex)
{
if (dwIndex)
{
// If this has an index, scan the indexes. Look
// for the matching index. If it is found, return its data.
// If not, return the default 0.
BOOL fNoEntry = TRUE;
CMergeParam *pParam = GetHead();
for (;pParam;pParam = pParam->GetNext())
{
if (pParam->m_dwIndex == dwIndex)
{
return pParam->m_lData;
}
}
return 0;
}
return m_lZeroIndexData;
}
void CChannelBlock::Init(DWORD dwPChannelStart,
DWORD dwPortIndex, DWORD dwGroup,
WORD wFlags)
{
DWORD dwIndex;
m_dwPortIndex = dwPortIndex;
m_dwPChannelStart = ( dwPChannelStart / PCHANNEL_BLOCKSIZE ) * PCHANNEL_BLOCKSIZE;
for( dwIndex = 0; dwIndex < PCHANNEL_BLOCKSIZE; dwIndex++ )
{
m_aChannelMap[dwIndex].Clear();
m_aChannelMap[dwIndex].dwPortIndex = dwPortIndex;
m_aChannelMap[dwIndex].dwGroup = dwGroup;
m_aChannelMap[dwIndex].dwMChannel = dwIndex;
m_aChannelMap[dwIndex].nTranspose = 0;
m_aChannelMap[dwIndex].wFlags = wFlags;
}
if (wFlags == CMAP_FREE) m_dwFreeChannels = 16;
else m_dwFreeChannels = 0;
}
/////////////////////////////////////////////////////////////////////////////
// CPerformance
// Flags for which critical sections have been initialized
//
#define PERF_ICS_SEGMENT 0x0001
#define PERF_ICS_PIPELINE 0x0002
#define PERF_ICS_PCHANNEL 0x0004
#define PERF_ICS_GLOBAL 0x0010
#define PERF_ICS_REALTIME 0x0020
#define PERF_ICS_PORTTABLE 0x0040
#define PERF_ICS_MAIN 0x0100
#define PERF_ICS_PMSGCACHE 0x0200
CPerformance::CPerformance()
{
m_pGraph = NULL;
m_dwPrepareTime = 1000;
m_dwBumperLength = 50;
m_rtBumperLength = m_dwBumperLength * REF_PER_MIL;
m_pGlobalData = NULL;
m_fInTrackPlay = FALSE;
m_fPlaying = FALSE;
m_wRollOverCount = 0;
m_mtTransported = 0;
m_mtTempoCursor = 0;
m_pParamHook = NULL;
m_hNotification = 0;
m_rtNotificationDiscard = 20000000;
m_rtStart = 0;
m_rtAdjust = 0;
m_mtPlayTo = 0;
m_cRef = 1;
m_pUnkDispatch = NULL;
m_dwVersion = 6;
m_dwNumPorts = 0;
m_pPortTable = NULL;
m_fKillThread = 0;
m_fKillRealtimeThread = 0;
m_fInTransportThread = 0;
m_dwTransportThreadID = 0;
m_pDirectMusic = NULL;
m_pDirectSound = NULL;
m_pClock = NULL;
m_fReleasedInTransport = false;
m_fReleasedInRealtime = false;
InterlockedIncrement(&g_cComponent);
TraceI(3,"CPerformance %lx\n", this);
m_dwInitCS = 0;
InitializeCriticalSection(&m_SegmentCrSec); m_dwInitCS |= PERF_ICS_SEGMENT;
InitializeCriticalSection(&m_PipelineCrSec); m_dwInitCS |= PERF_ICS_PIPELINE;
InitializeCriticalSection(&m_PChannelInfoCrSec); m_dwInitCS |= PERF_ICS_PCHANNEL;
InitializeCriticalSection(&m_GlobalDataCrSec); m_dwInitCS |= PERF_ICS_GLOBAL;
InitializeCriticalSection(&m_RealtimeCrSec); m_dwInitCS |= PERF_ICS_REALTIME;
InitializeCriticalSection(&m_PMsgCacheCrSec); m_dwInitCS |= PERF_ICS_PMSGCACHE;
InitializeCriticalSection(&m_MainCrSec); m_dwInitCS |= PERF_ICS_MAIN;
memset( m_apPMsgCache, 0, sizeof(DMUS_PMSG*) * (PERF_PMSG_CB_MAX - PERF_PMSG_CB_MIN) );
DWORD dwCount;
for (dwCount = 0; dwCount < SQ_COUNT; dwCount++)
{
m_SegStateQueues[dwCount].SetID(dwCount);
}
Init();
}
void CPerformance::Init()
{
m_rtEarliestStartTime = 0;
m_lMasterVolume = 0;
if (m_dwVersion >= 8)
{
m_rtQueuePosition = 0;
m_dwPrepareTime = 1000;
m_dwBumperLength = 50;
m_rtBumperLength = m_dwBumperLength * REF_PER_MIL;
if (m_dwAudioPathMode)
{
CloseDown();
}
}
m_pDefaultAudioPath = NULL;
m_fltRelTempo = 1;
m_pGetParamSegmentState = NULL;
m_dwGetParamFlags = 0;
m_rtHighestPackedNoteOn = 0;
m_dwAudioPathMode = 0;
m_hTransport = 0;
m_hTransportThread = 0;
m_dwRealtimeThreadID = 0;
m_hRealtime = 0;
m_hRealtimeThread = 0;
m_fMusicStopped = TRUE;
BOOL fAuto = FALSE;
SetGlobalParam(GUID_PerfAutoDownload,&fAuto,sizeof(BOOL));
DMUS_TIMESIG_PMSG* pTimeSig;
if (SUCCEEDED(AllocPMsg(sizeof(DMUS_TIMESIG_PMSG),(DMUS_PMSG **) &pTimeSig)))
{
pTimeSig->wGridsPerBeat = 4;
pTimeSig->bBeatsPerMeasure = 4;
pTimeSig->bBeat = 4;
pTimeSig->dwFlags = DMUS_PMSGF_REFTIME;
pTimeSig->dwType = DMUS_PMSGT_TIMESIG;
EnterCriticalSection(&m_PipelineCrSec);
m_TimeSigQueue.Enqueue( DMUS_TO_PRIV(pTimeSig) );
LeaveCriticalSection(&m_PipelineCrSec);
}
}
CPerformance::~CPerformance()
{
TraceI(3,"~CPerformance %lx\n", this);
if (m_pParamHook)
{
m_pParamHook->Release();
}
CloseDown(); // this should have already been called, but just in case...
if (m_pUnkDispatch)
m_pUnkDispatch->Release(); // free IDispatch implementation we may have borrowed
if (m_dwInitCS & PERF_ICS_SEGMENT) DeleteCriticalSection(&m_SegmentCrSec);
if (m_dwInitCS & PERF_ICS_PIPELINE) DeleteCriticalSection(&m_PipelineCrSec);
if (m_dwInitCS & PERF_ICS_PCHANNEL) DeleteCriticalSection(&m_PChannelInfoCrSec);
if (m_dwInitCS & PERF_ICS_GLOBAL) DeleteCriticalSection(&m_GlobalDataCrSec);
if (m_dwInitCS & PERF_ICS_REALTIME) DeleteCriticalSection(&m_RealtimeCrSec);
if (m_dwInitCS & PERF_ICS_PMSGCACHE)DeleteCriticalSection(&m_PMsgCacheCrSec);
if (m_dwInitCS & PERF_ICS_MAIN) DeleteCriticalSection(&m_MainCrSec);
InterlockedDecrement(&g_cComponent);
}
STDMETHODIMP CPerformance::CloseDown(void)
{
V_INAME(CPerformance::CloseDown);
DWORD dwThreadID = GetCurrentThreadId();
if( m_dwAudioPathMode )
{
// kill the transport thread
m_fKillThread = 1;
m_fKillRealtimeThread = 1;
if (dwThreadID != m_dwTransportThreadID)
{
// signal the transport thread so we don't have to wait for it to wake up on its own
if( m_hTransport ) SetEvent( m_hTransport );
// wait until the transport thread quits
WaitForSingleObject(m_hTransportThread, INFINITE);
}
if (dwThreadID != m_dwRealtimeThreadID)
{
// signal the realtime thread so we don't have to wait for it to wake up on its own
if( m_hRealtime ) SetEvent( m_hRealtime );
// wait until the realtime thread quits
WaitForSingleObject(m_hRealtimeThread, INFINITE);
}
}
if (m_pGraph) SetGraph(NULL); // shut down the graph and release it (needs to happen before clearing audio path)
EnterCriticalSection(&m_SegmentCrSec);
EnterCriticalSection(&m_RealtimeCrSec);
m_fPlaying = FALSE; // prevents transport thread from doing anything more
IDirectMusicPerformance* pPerf = NULL;
if (SUCCEEDED(QueryInterface(IID_IDirectMusicPerformance, (void**)&pPerf)))
{
CWavTrack::UnloadAllWaves(pPerf);
pPerf->Release();
}
DequeueAllSegments();
if (m_pDefaultAudioPath)
{
m_pDefaultAudioPath->Release();
m_pDefaultAudioPath = NULL;
}
m_dwAudioPathMode = 0;
m_AudioPathList.Clear();
CNotificationItem* pItem = m_NotificationList.GetHead();
while( pItem )
{
CNotificationItem* pNext = pItem->GetNext();
m_NotificationList.Remove( pItem );
delete pItem;
pItem = pNext;
}
LeaveCriticalSection(&m_RealtimeCrSec);
LeaveCriticalSection(&m_SegmentCrSec);
EnterCriticalSection(&m_PipelineCrSec);
PRIV_PMSG* pPMsg;
while( pPMsg = m_EarlyQueue.Dequeue() )
{
FreePMsg(pPMsg);
}
while( pPMsg = m_NearTimeQueue.Dequeue() )
{
FreePMsg(pPMsg);
}
while( pPMsg = m_OnTimeQueue.Dequeue() )
{
FreePMsg(pPMsg);
}
while( pPMsg = m_TempoMap.Dequeue() )
{
FreePMsg(pPMsg);
}
while( pPMsg = m_OldTempoMap.Dequeue() )
{
FreePMsg(pPMsg);
}
while( pPMsg = m_NotificationQueue.Dequeue() )
{
FreePMsg(pPMsg);
}
while( pPMsg = m_TimeSigQueue.Dequeue() )
{
FreePMsg(pPMsg);
}
LeaveCriticalSection(&m_PipelineCrSec);
EnterCriticalSection(&m_GlobalDataCrSec);
GlobalData* pGD = m_pGlobalData;
while( pGD )
{
m_pGlobalData = pGD->pNext;
delete pGD;
pGD = m_pGlobalData;
}
LeaveCriticalSection(&m_GlobalDataCrSec);
EnterCriticalSection(&m_PChannelInfoCrSec);
// clear out ports, buffers, and pchannel maps
if( m_pPortTable )
{
DWORD dwIndex;
for( dwIndex = 0; dwIndex < m_dwNumPorts; dwIndex++ )
{
if( m_pPortTable[dwIndex].pPort )
{
m_pPortTable[dwIndex].pPort->Release();
}
if( m_pPortTable[dwIndex].pBuffer )
{
m_pPortTable[dwIndex].pBuffer->Release();
}
if( m_pPortTable[dwIndex].pLatencyClock )
{
m_pPortTable[dwIndex].pLatencyClock->Release();
}
}
delete [] m_pPortTable;
m_pPortTable = NULL;
m_dwNumPorts = 0;
}
m_ChannelBlockList.Clear();
LeaveCriticalSection(&m_PChannelInfoCrSec);
EnterCriticalSection(&m_MainCrSec);
if( m_pClock )
{
m_pClock->Release();
m_pClock = NULL;
}
m_BufferManager.Clear();
if( m_pDirectMusic )
{
m_pDirectMusic->Release();
m_pDirectMusic = NULL;
}
if (m_pDirectSound)
{
m_pDirectSound->Release();
m_pDirectSound = NULL;
}
m_hNotification = NULL;
LeaveCriticalSection(&m_MainCrSec);
EnterCriticalSection(&m_PMsgCacheCrSec);
for( int i = 0; i < (PERF_PMSG_CB_MAX - PERF_PMSG_CB_MIN); i++ )
{
while( m_apPMsgCache[i] )
{
PRIV_PMSG* pPriv = m_apPMsgCache[i];
m_apPMsgCache[i] = pPriv->pNext;
delete [] pPriv;
}
}
LeaveCriticalSection(&m_PMsgCacheCrSec);
DWORD dwExitCode = 0;
if (m_hTransportThread)
{
CloseHandle( m_hTransportThread );
m_hTransportThread = 0;
}
if( m_hTransport )
{
CloseHandle( m_hTransport );
m_hTransport = 0;
}
if (m_hRealtimeThread)
{
CloseHandle( m_hRealtimeThread );
m_hRealtimeThread = 0;
}
if( m_hRealtime )
{
CloseHandle( m_hRealtime );
m_hRealtime = 0;
}
m_mtPlayTo = 0;
return S_OK;
}
// @method:(INTERNAL) HRESULT | IDirectMusicPerformance | QueryInterface | Standard QueryInterface implementation for <i IDirectMusicPerformance>
//
// @rdesc Returns one of the following:
//
// @flag S_OK | If the interface is supported and was returned
// @flag E_NOINTERFACE | If the object does not support the given interface.
// @flag E_POINTER | <p ppv> is NULL or invalid.
//
STDMETHODIMP CPerformance::QueryInterface(
const IID &iid, // @parm Interface to query for
void **ppv) // @parm The requested interface will be returned here
{
V_INAME(CPerformance::QueryInterface);
V_PTRPTR_WRITE(ppv);
V_REFGUID(iid);
*ppv = NULL;
if (iid == IID_IUnknown || iid == IID_IDirectMusicPerformance)
{
*ppv = static_cast<IDirectMusicPerformance*>(this);
} else
if (iid == IID_IDirectMusicPerformance8)
{
m_dwVersion = 8;
*ppv = static_cast<IDirectMusicPerformance8*>(this);
} else
if (iid == IID_IDirectMusicPerformance2)
{
m_dwVersion = 7;
*ppv = static_cast<IDirectMusicPerformance*>(this);
} else
if( iid == IID_IDirectMusicPerformanceStats )
{
*ppv = static_cast<IDirectMusicPerformanceStats*>(this);
} else
if( iid == IID_IDirectMusicSetParamHook )
{
*ppv = static_cast<IDirectMusicSetParamHook*>(this);
} else
if (iid == IID_IDirectMusicTool)
{
*ppv = static_cast<IDirectMusicTool*>(this);
} else
if (iid == IID_CPerformance)
{
*ppv = static_cast<CPerformance*>(this);
}
if (iid == IID_IDirectMusicGraph)
{
*ppv = static_cast<IDirectMusicGraph*>(this);
}
if (iid == IID_IDirectMusicPerformanceP)
{
*ppv = static_cast<IDirectMusicPerformanceP*>(this);
} else
if (iid == IID_IDispatch)
{
// A helper scripting object implements IDispatch, which we expose from the
// Performance object via COM aggregation.
if (!m_pUnkDispatch)
{
// Create the helper object
::CoCreateInstance(
CLSID_AutDirectMusicPerformance,
static_cast<IDirectMusicPerformance*>(this),
CLSCTX_INPROC_SERVER,
IID_IUnknown,
reinterpret_cast<void**>(&m_pUnkDispatch));
}
if (m_pUnkDispatch)
{
return m_pUnkDispatch->QueryInterface(IID_IDispatch, ppv);
}
}
if (*ppv == NULL)
{
Trace(4,"Warning: Request to query unknown interface on Performance object\n");
return E_NOINTERFACE;
}
reinterpret_cast<IUnknown*>(this)->AddRef();
return S_OK;
}
// @method:(INTERNAL) HRESULT | IDirectMusicPerformance | AddRef | Standard AddRef implementation for <i IDirectMusicPerformance>
//
// @rdesc Returns the new reference count for this object.
//
STDMETHODIMP_(ULONG) CPerformance::AddRef()
{
return InterlockedIncrement(&m_cRef);
}
// @method:(INTERNAL) HRESULT | IDirectMusicPerformance | Release | Standard Release implementation for <i IDirectMusicPerformance>
//
// @rdesc Returns the new reference count for this object.
//
STDMETHODIMP_(ULONG) CPerformance::Release()
{
if (!InterlockedDecrement(&m_cRef))
{
DWORD dwThreadID = GetCurrentThreadId();
m_cRef = 100; // artificial reference count to prevent reentrency due to COM aggregation
if (dwThreadID == m_dwTransportThreadID)
{
m_fReleasedInTransport = true;
m_fKillThread = TRUE;
}
else if (dwThreadID == m_dwRealtimeThreadID)
{
m_fReleasedInRealtime = true;
m_fKillRealtimeThread = TRUE;
}
else
{
delete this;
}
return 0;
}
return m_cRef;
}
// call this only from within a m_SegmentCrSec critical section
// if fSendNotify, then send segment end notifications for segments that were
// playing
void CPerformance::DequeueAllSegments()
{
CSegState *pNode;
DWORD dwCount;
for( dwCount = 0; dwCount < SQ_COUNT; dwCount++ )
{
while( pNode = m_SegStateQueues[dwCount].RemoveHead())
{
pNode->ShutDown();
}
}
while( pNode = m_ShutDownQueue.RemoveHead())
{
pNode->ShutDown();
}
}
// IDirectMusicPerformanceStats
STDMETHODIMP CPerformance::TraceAllSegments()
{
CSegState *pNode;
DWORD dwCount;
for( dwCount = 0; dwCount < SQ_COUNT; dwCount++ )
{
EnterCriticalSection(&m_SegmentCrSec);
for ( pNode = m_SegStateQueues[dwCount].GetHead();pNode;pNode=pNode->GetNext())
{
TraceI(0,"%x %ld: Playing: %ld, Start: %ld, Seek: %ld, LastPlayed: %ld\n",
pNode,dwCount,pNode->m_fStartedPlay, pNode->m_mtResolvedStart,
pNode->m_mtSeek, pNode->m_mtLastPlayed);
}
LeaveCriticalSection(&m_SegmentCrSec);
}
return S_OK;
}
STDMETHODIMP CPerformance::CreateSegstateList(DMUS_SEGSTATEDATA ** ppList)
{
if (!ppList) return E_POINTER;
CSegState *pNode;
DWORD dwCount;
for( dwCount = 0; dwCount < SQ_COUNT; dwCount++ )
{
EnterCriticalSection(&m_SegmentCrSec);
for ( pNode = m_SegStateQueues[dwCount].GetHead();pNode;pNode=pNode->GetNext())
{
DMUS_SEGSTATEDATA *pData = new DMUS_SEGSTATEDATA;
if (pData)
{
CSegment *pSegment = pNode->m_pSegment;
if (pSegment && (pSegment->m_dwValidData & DMUS_OBJ_NAME))
{
StringCchCopyW(pData->wszName, DMUS_MAX_NAME, pSegment->m_wszName);
}
else
{
pData->wszName[0] = 0;
}
pData->dwQueue = dwCount;
pData->pSegState = (IDirectMusicSegmentState *) pNode;
pNode->AddRef();
pData->pNext = *ppList;
pData->mtLoopEnd = pNode->m_mtLoopEnd;
pData->mtLoopStart = pNode->m_mtLoopStart;
pData->dwRepeats = pNode->m_dwRepeats;
pData->dwPlayFlags = pNode->m_dwPlaySegFlags;
pData->mtLength = pNode->m_mtLength;
pData->rtGivenStart = pNode->m_rtGivenStart;
pData->mtResolvedStart = pNode->m_mtResolvedStart;
pData->mtOffset = pNode->m_mtOffset;
pData->mtLastPlayed = pNode->m_mtLastPlayed;
pData->mtPlayTo = pNode->m_mtStopTime;
pData->mtSeek = pNode->m_mtSeek;
pData->mtStartPoint = pNode->m_mtStartPoint;
pData->dwRepeatsLeft = pNode->m_dwRepeatsLeft;
pData->fStartedPlay = pNode->m_fStartedPlay;
*ppList = pData;
}
}
LeaveCriticalSection(&m_SegmentCrSec);
}
return S_OK;
}
STDMETHODIMP CPerformance::FreeSegstateList(DMUS_SEGSTATEDATA * pList)
{
DMUS_SEGSTATEDATA *pState;
while (pList)
{
pState = pList;
pList = pList->pNext;
pState->pSegState->Release();
delete pState;
}
return S_OK;
}
void CPerformance::SendBuffers()
{
DWORD dwIndex;
PortTable* pPortTable;
#ifdef DBG_PROFILE
DWORD dwDebugTime;
dwDebugTime = timeGetTime();
#endif
EnterCriticalSection(&m_PChannelInfoCrSec);
for( dwIndex = 0; dwIndex < m_dwNumPorts; dwIndex++ )
{
if( m_pPortTable[dwIndex].fBufferFilled && m_pPortTable[dwIndex].pBuffer )
{
pPortTable = &m_pPortTable[dwIndex];
pPortTable->fBufferFilled = FALSE;
ASSERT( pPortTable->pBuffer );
if( pPortTable->pPort )
{
pPortTable->pPort->PlayBuffer( pPortTable->pBuffer );
// TraceI(5, "SENT BUFFERS time=%ld latency=%ld\n", (long)(GetTime() / 10000),(long)(GetLatency()/10000));
}
pPortTable->pBuffer->Flush();
}
}
LeaveCriticalSection(&m_PChannelInfoCrSec);
#ifdef DBG_PROFILE
dwDebugTime = timeGetTime() - dwDebugTime;
if( dwDebugTime > 1 )
{
TraceI(5, "Hall, debugtime SendBuffers %u\n", dwDebugTime);
}
#endif
}
static DWORD WINAPI _Realtime(LPVOID lpParam)
{
if (SUCCEEDED(::CoInitialize(NULL)))
{
((CPerformance *)lpParam)->Realtime();
::CoUninitialize();
}
return 0;
}
void CPerformance::Realtime()
{
while (!m_fKillRealtimeThread)
{
EnterCriticalSection(&m_RealtimeCrSec);
PRIV_PMSG *pEvent;
HRESULT hr;
REFERENCE_TIME rtFirst = 0;
REFERENCE_TIME rtEnter = GetLatencyWithPrePlay();
DWORD dwTestTime;
DWORD dwBeginTime = timeGetTime();
DWORD dwLimitLoop = 0;
if( rtEnter > m_rtQueuePosition )
{
m_rtQueuePosition = rtEnter;
}
while (1)
{
// rtFirst equals the time that the first event was packed into a buffer.
// Once this time is greater than the latency clock (minus a delay) we need
// to queue the buffers so the events get down in time to be rendered.
// If rtFirst is 0 it means it hasn't been initialized yet.
dwTestTime = timeGetTime();
if( dwTestTime - dwBeginTime > REALTIME_RES )
{
if( ++dwLimitLoop > 10 )
{
TraceI(1,"Error! We've been in the realtime thread too long!!! Breaking out without completing.\n");
break;
}
SendBuffers();
dwBeginTime = dwTestTime;
}
pEvent = GetNextPMsg();
if( NULL == pEvent )
{
break;
}
ASSERT( pEvent->pNext == NULL );
if( !pEvent->pTool )
{
// this event doesn't have a Tool pointer, so stamp it with the
// final output Tool.
pEvent->pTool = (IDirectMusicTool*)this;
AddRef();
}
// before processing the event, set rtLast to the event's current time
pEvent->rtLast = pEvent->rtTime;
hr = pEvent->pTool->ProcessPMsg( this, PRIV_TO_DMUS(pEvent) );
if( hr != S_OK ) // S_OK means do nothing
{
if( hr == DMUS_S_REQUEUE )
{
if(FAILED(SendPMsg( PRIV_TO_DMUS(pEvent) )))
{
FreePMsg(pEvent);
}
}
else // e.g. DMUS_S_FREE or error code
{
FreePMsg( pEvent );
}
}
}
SendBuffers();
LeaveCriticalSection(&m_RealtimeCrSec);
if( m_hRealtime )
{
WaitForSingleObject( m_hRealtime, REALTIME_RES );
}
else
{
Sleep(REALTIME_RES);
}
}
m_fKillRealtimeThread = FALSE;
TraceI(2, "dmperf: LEAVE realtime\n");
if (m_fReleasedInRealtime)
{
delete this;
}
}
void CPerformance::GenerateNotification( DWORD dwNotification, MUSIC_TIME mtTime,
IDirectMusicSegmentState* pSegSt)
{
GUID guid;
guid = GUID_NOTIFICATION_PERFORMANCE;
if( FindNotification( guid ))
{
DMUS_NOTIFICATION_PMSG* pEvent = NULL;
if( SUCCEEDED( AllocPMsg( sizeof(DMUS_NOTIFICATION_PMSG),
(DMUS_PMSG**)&pEvent )))
{
pEvent->dwField1 = 0;
pEvent->dwField2 = 0;
pEvent->guidNotificationType = GUID_NOTIFICATION_PERFORMANCE;
pEvent->dwType = DMUS_PMSGT_NOTIFICATION;
pEvent->mtTime = mtTime;
pEvent->dwFlags = DMUS_PMSGF_MUSICTIME | DMUS_PMSGF_TOOL_ATTIME;
pEvent->dwGroupID = 0xffffffff;
pEvent->dwPChannel = 0;
pEvent->dwNotificationOption = dwNotification;
if( pSegSt )
{
pSegSt->QueryInterface(IID_IUnknown, (void**)&pEvent->punkUser);
}
StampPMsg((DMUS_PMSG*)pEvent);
if(FAILED(SendPMsg( (DMUS_PMSG*)pEvent )))
{
FreePMsg((DMUS_PMSG*)pEvent);
}
}
}
}
void CPerformance::PrepSegToPlay(CSegState *pSegState, bool fQueue)
/* Called when a segment is first queued, once the start time of the segment is known.
This calculates various fields that need to be initialized and also regenerates the
tempo map if the new segment has an active tempo map in it.
*/
{
if (!pSegState->m_fPrepped)
{
pSegState->m_fPrepped = TRUE;
pSegState->m_mtLastPlayed = pSegState->m_mtResolvedStart;
// if this is queued to play after the current segment ends, no need to recalc the tempo map;
// it will be updated as necessary by the transport thread.
if (!fQueue)
{
RecalcTempoMap(pSegState, pSegState->m_mtResolvedStart);
}
MusicToReferenceTime(pSegState->m_mtLastPlayed,&pSegState->m_rtLastPlayed);
// Calculate the total duration of the segment and store in m_mtEndTime.
pSegState->m_mtEndTime = pSegState->GetEndTime(pSegState->m_mtResolvedStart);
}
}
/*
void | CPerformance | PerformSegStNode |
Perform a Segment State contained in the CSegState.
Note that this ppSegStNode may be dequeued, so don't depend on it
staying around!
*/
void CPerformance::PerformSegStNode(
DWORD dwList, // The list the segmentstate comes from.
CSegState* pSegStNode) // The segmentstate node.
{
MUSIC_TIME mtMargin; // tracks how much of a segment to play
HRESULT hr;
CSegStateList *pList = &m_SegStateQueues[dwList];
CSegState *pNext;
if( !m_fPlaying || m_fInTrackPlay )
{
return;
}
if( pSegStNode )
{
m_fInTransportThread = TRUE; // Disable realtime processing of early queue messages.
hr = S_OK;
//Trace(0,"%ld: Performing %lx, Active: %ld, Start Time: %ld, End Time: %ld\n",m_mtPlayTo,
// pSegStNode->m_pSegment,pSegStNode->m_fStartedPlay,pSegStNode->m_mtResolvedStart,pSegStNode->m_mtEndTime);
if( !pSegStNode->m_fStartedPlay )
{
// check to see if this SegState should start playing.
ASSERT( !(pSegStNode->m_dwPlaySegFlags & DMUS_SEGF_REFTIME ));
if( pSegStNode->m_mtResolvedStart < m_mtPlayTo )
{
pSegStNode->m_fStartedPlay = TRUE;
PrepSegToPlay(pSegStNode);
// send a MUSICSTARTED notification if needed
if(m_fMusicStopped)
{
m_fMusicStopped = FALSE;
GenerateNotification( DMUS_NOTIFICATION_MUSICSTARTED, pSegStNode->m_mtResolvedStart, NULL );
}
// We don't want the music to start with a big BLURP in track
// order, so we send a little dribble out on each track.
mtMargin = m_mtPlayTo - pSegStNode->m_mtLastPlayed;
if( mtMargin >= 50 )
{
hr = pSegStNode->Play( 50 );
ProcessEarlyPMsgs();
// Once done processing all the early messages, make sure that the realtime
// thread wakes up and does whatever it needs to do. This ensures that the starting
// notes in a sequence get to the output port immediately.
if( m_hRealtime ) SetEvent( m_hRealtime );
mtMargin = m_mtPlayTo - pSegStNode->m_mtLastPlayed;
// Then, we send a larger chunk out on each track to catch up a little more...
if ((hr == S_OK) && ( mtMargin >= 200 ))
{
hr = pSegStNode->Play( 200 );
ProcessEarlyPMsgs();
}
}
}
else
{
MusicToReferenceTime(pSegStNode->m_mtLastPlayed,&pSegStNode->m_rtLastPlayed);
}
}
if( pSegStNode->m_fStartedPlay )
{
if( pSegStNode->m_mtStopTime && ( pSegStNode->m_mtStopTime < m_mtPlayTo ) )
{
mtMargin = pSegStNode->m_mtStopTime - pSegStNode->m_mtLastPlayed;
}
else
{
mtMargin = m_mtPlayTo - pSegStNode->m_mtLastPlayed;
}
while ((hr == S_OK) && (mtMargin > 0))
{
// Do not allow more than a quarter note's worth to be done at once.
MUSIC_TIME mtRange = mtMargin;
if (mtRange > DMUS_PPQ)
{
mtRange = DMUS_PPQ;
mtMargin -= mtRange;
}
else
{
mtMargin = 0;
}
hr = pSegStNode->Play( mtRange );
ProcessEarlyPMsgs();
}
}
if( (hr == DMUS_S_END) || ( pSegStNode->m_mtStopTime &&
( pSegStNode->m_mtStopTime <= pSegStNode->m_mtLastPlayed ) ) )
{
if( pSegStNode->m_mtStopTime && (pSegStNode->m_mtStopTime == pSegStNode->m_mtLastPlayed) )
{
pSegStNode->AbortPlay(pSegStNode->m_mtStopTime - 1, FALSE);
}
MUSIC_TIME mtEnd = pSegStNode->m_mtLastPlayed;
if( pList == &m_SegStateQueues[SQ_PRI_PLAY] )
{
// move primary segments to PriPastList
pList->Remove(pSegStNode);
m_SegStateQueues[SQ_PRI_DONE].Insert(pSegStNode);
pNext = pList->GetHead();
if( pNext )
{
if (!( pNext->m_dwPlaySegFlags & DMUS_SEGF_NOINVALIDATE ))
{
if (IsConQueue(dwList))
{
Invalidate( pNext->m_mtResolvedStart, 0 );
}
}
}
else // No more primary segments, send DMUS_NOTIFICATION_MUSICALMOSTEND
{
if (m_dwVersion >= 8)
{
MUSIC_TIME mtNow;
GetTime( NULL, &mtNow );
GenerateNotification( DMUS_NOTIFICATION_MUSICALMOSTEND, mtNow, pSegStNode );
}
}
ManageControllingTracks();
}
else if ( pList == &m_SegStateQueues[SQ_CON_PLAY] )
{
pList->Remove(pSegStNode );
if (pSegStNode->m_mtStopTime == pSegStNode->m_mtLastPlayed)
{
m_ShutDownQueue.Insert(pSegStNode);
}
else
{
m_SegStateQueues[SQ_CON_DONE].Insert(pSegStNode);
}
}
else
{
// move 2ndary segments to SecPastList
pList->Remove(pSegStNode);
m_SegStateQueues[SQ_SEC_DONE].Insert(pSegStNode);
}
// if there aren't any more segments to play, send a Music Stopped
// notification
if( (m_SegStateQueues[SQ_PRI_PLAY].IsEmpty() && m_SegStateQueues[SQ_SEC_PLAY].IsEmpty() &&
m_SegStateQueues[SQ_PRI_WAIT].IsEmpty() && m_SegStateQueues[SQ_SEC_WAIT].IsEmpty() &&
m_SegStateQueues[SQ_CON_PLAY].IsEmpty() && m_SegStateQueues[SQ_CON_WAIT].IsEmpty()))
{
m_fMusicStopped = TRUE;
GenerateNotification( DMUS_NOTIFICATION_MUSICSTOPPED, mtEnd, NULL );
}
}
m_fInTransportThread = FALSE;
}
}
static DWORD WINAPI _Transport(LPVOID lpParam)
{
if (SUCCEEDED(::CoInitialize(NULL)))
{
((CPerformance *)lpParam)->Transport();
::CoUninitialize();
}
return 0;
}
// call Segment's play code on a periodic basis. This routine is in its
// own thread.
void CPerformance::Transport()
{
srand((unsigned int)time(NULL));
while (!m_fKillThread)
{
DWORD dwCount;
CSegState* pNode;
CSegState* pNext;
CSegState* pTempQueue = NULL;
REFERENCE_TIME rtNow = GetTime();
EnterCriticalSection(&m_SegmentCrSec);
// Compute the time we should play all the segments to.
REFERENCE_TIME rtPlayTo = rtNow + PREPARE_TIME;
MUSIC_TIME mtAmount, mtResult, mtPlayTo;
mtPlayTo = 0;
ReferenceToMusicTime( rtPlayTo, &mtPlayTo );
if (m_fTempoChanged)
{
// If there has been a tempo change to slower, any clock time tracks could
// be delayed to long as the transport holds off sending out events. That's
// okay for music time tracks, but bad news for clock time tracks. This
// makes sure that the clock time tracks get a chance to spew.
if (m_mtPlayTo >= mtPlayTo)
{
mtPlayTo = m_mtPlayTo + 10;
}
m_fTempoChanged = FALSE;
}
IncrementTempoMap();
while (m_mtPlayTo < mtPlayTo)
{
BOOL fDirty = FALSE; // see below
m_mtPlayTo = mtPlayTo; // Start out optimistic
// We need to set play boundaries at the end of control segments.
// The beginnings of control segments are handled inside the segment state code.
pNode = m_SegStateQueues[SQ_PRI_PLAY].GetHead();
if( pNode && pNode->m_fStartedPlay )
{
mtAmount = m_mtPlayTo - pNode->m_mtLastPlayed;
pNode->CheckPlay( mtAmount, &mtResult );
if( mtResult < mtAmount )
{
m_mtPlayTo -= ( mtAmount - mtResult );
// don't need dirty flag when primary segment loops or ends normally (bug 30829)
// fDirty = TRUE; // see below
}
}
// if a control segment ended prematurely, mtPlayTo will have a value besides 0
// check for upcoming endings to control segments
for( pNode = m_SegStateQueues[SQ_CON_PLAY].GetHead(); pNode; pNode = pNode->GetNext() )
{
if( pNode->m_fStartedPlay )
{
if( pNode->m_mtStopTime && (m_mtPlayTo > pNode->m_mtStopTime) )
{
m_mtPlayTo = pNode->m_mtStopTime;
fDirty = TRUE; // see below
}
else
{
mtAmount = m_mtPlayTo - pNode->m_mtLastPlayed;
pNode->CheckPlay( mtAmount, &mtResult );
if( mtResult < mtAmount )
{
m_mtPlayTo -= ( mtAmount - mtResult );
fDirty = TRUE; // see below
}
}
}
}
// play the primary segment
PerformSegStNode( SQ_PRI_PLAY,m_SegStateQueues[SQ_PRI_PLAY].GetHead() );
// check to see if the next primary segment in the queue is ready to play
while( (pNode = m_SegStateQueues[SQ_PRI_PLAY].GetHead()) &&
(pNext = pNode->GetNext()) &&
( pNext->m_mtResolvedStart <= pNode->m_mtLastPlayed ) )
{
// the next primary segment is indeed ready to begin playing.
// save the old one in the primary past list so Tools can reference
// it if they're looking for chord progressions and such.
pNode->AbortPlay(pNext->m_mtResolvedStart-1,TRUE && (pNext->m_dwPlaySegFlags & DMUS_SEGF_NOINVALIDATE));
m_SegStateQueues[SQ_PRI_DONE].Insert(m_SegStateQueues[SQ_PRI_PLAY].RemoveHead());
ManageControllingTracks();
// we need to flush primary events after the new start time
if(!( m_SegStateQueues[SQ_PRI_PLAY].GetHead()->m_dwPlaySegFlags & (DMUS_SEGF_NOINVALIDATE | DMUS_SEGF_INVALIDATE_PRI) ))
{
Invalidate( m_SegStateQueues[SQ_PRI_PLAY].GetHead()->m_mtResolvedStart, 0 );
}
// and play the new segment
PerformSegStNode( SQ_PRI_PLAY,m_SegStateQueues[SQ_PRI_PLAY].GetHead());
}
// play the controlling segments
pNode = m_SegStateQueues[SQ_CON_PLAY].GetHead();
pNext = NULL;
for(; pNode != NULL; pNode = pNext)
{
pNext = pNode->GetNext();
PerformSegStNode(SQ_CON_PLAY,pNode );
}
// play the secondary segments
pNode = m_SegStateQueues[SQ_SEC_PLAY].GetHead();
pNext = NULL;
for(; pNode != NULL; pNode = pNext)
{
pNext = pNode->GetNext();
PerformSegStNode( SQ_SEC_PLAY,pNode );
}
// if we set fDirty above, it means that we truncated the playback of a control
// segment because of a loop or end condition. Therefore, we want all segments
// to set the DMUS_TRACKF_DIRTY flag on the next play cycle.
if( fDirty )
{
for (dwCount = SQ_PRI_PLAY; dwCount <= SQ_SEC_PLAY; dwCount++)
{
for( pNode = m_SegStateQueues[dwCount].GetHead(); pNode; pNode = pNode->GetNext() )
{
if( pNode->m_fStartedPlay )
{
pNode->m_dwPlayTrackFlags |= DMUS_TRACKF_DIRTY;
}
}
}
ManageControllingTracks();
}
m_mtTransported = m_mtPlayTo;
}
// check segments queued in ref-time to see if it's time for them to
// play. Add some extra time just in case. We'll bet that a tempo pmsg won't come
// in in the intervening 200 ms.
REFERENCE_TIME rtLatency = GetLatencyWithPrePlay();
for (dwCount = SQ_PRI_WAIT;dwCount <= SQ_SEC_WAIT; dwCount++)
{
while( m_SegStateQueues[dwCount].GetHead() )
{
if( m_SegStateQueues[dwCount].GetHead()->m_rtGivenStart > rtLatency + PREPARE_TIME + (200 * REF_PER_MIL) )
{
// it's not yet time to handle this one
break;
}
if (dwCount == SQ_PRI_WAIT)
{
QueuePrimarySegment( m_SegStateQueues[SQ_PRI_WAIT].RemoveHead());
}
else
{
QueueSecondarySegment( m_SegStateQueues[dwCount].RemoveHead());
}
}
}
// Check to see if Segments in the done queues
// can be released. They can be released if their
// final play times are older than the current time.
for (dwCount = SQ_PRI_DONE;dwCount <= SQ_SEC_DONE; dwCount++)
{
for (pNode = m_SegStateQueues[dwCount].GetHead();pNode;pNode = pNext)
{
pNext = pNode->GetNext();
if( pNode->m_rtLastPlayed < rtNow - 1000 * REF_PER_MIL ) // Let it last an additional second
{
m_SegStateQueues[dwCount].Remove(pNode);
pNode->ShutDown();
}
}
}
for (pNode = m_ShutDownQueue.GetHead();pNode;pNode = pNext)
{
pNext = pNode->GetNext();
if( pNode->m_rtLastPlayed < rtNow - 1000 * REF_PER_MIL ) // Let it last an additional second
{
m_ShutDownQueue.Remove(pNode);
pNode->ShutDown();
}
}
LeaveCriticalSection(&m_SegmentCrSec);
// check to see if there are old notifications that haven't been
// retrieved by the application and need to be removed.
EnterCriticalSection(&m_PipelineCrSec);
while( m_NotificationQueue.GetHead() )
{
if( m_NotificationQueue.GetHead()->rtTime <
(rtNow - m_rtNotificationDiscard) )
{
FreePMsg(m_NotificationQueue.Dequeue());
}
else
{
break;
}
}
LeaveCriticalSection(&m_PipelineCrSec);
if( m_hTransport )
{
WaitForSingleObject( m_hTransport, TRANSPORT_RES );
}
else
{
Sleep(TRANSPORT_RES);
}
}
m_fKillThread = FALSE;
if (m_fReleasedInTransport)
{
delete this;
}
}
//////////////////////////////////////////////////////////////////////
// CPerformance::GetNextPMsg
/*
HRESULT | CPerformance | GetNextPMsg |
Returns messages from the queues in priority order. Any message in the
OnTime queue that is scheduled to be played at the current time is
returned above any other. Secondly, any message in the NearTime queue
that is scheduled to be played within the next NEARTIME ms is returned.
Lastly, any message in the Early queue is returned.
rvalue PRIV_PMSG* | The message, or NULL if there are no messages.
*/
inline PRIV_PMSG *CPerformance::GetNextPMsg()
{
#ifdef DBG_PROFILE
DWORD dwDebugTime;
dwDebugTime = timeGetTime();
#endif
PRIV_PMSG* pEvent = NULL;
EnterCriticalSection(&m_PipelineCrSec);
if (m_OnTimeQueue.GetHead())
{
ASSERT( m_OnTimeQueue.GetHead()->dwFlags & DMUS_PMSGF_REFTIME );
if ( m_OnTimeQueue.GetHead()->rtTime - GetTime() <= 0 )
{
pEvent = m_OnTimeQueue.Dequeue();
}
}
if( !pEvent )
{
if (m_NearTimeQueue.GetHead())
{
ASSERT( m_NearTimeQueue.GetHead()->dwFlags & DMUS_PMSGF_REFTIME );
if ( m_NearTimeQueue.GetHead()->rtTime < (m_rtQueuePosition + (m_rtBumperLength >> 1)))
{
pEvent = m_NearTimeQueue.Dequeue();
}
}
if( !pEvent && !m_fInTransportThread)
{
if (m_EarlyQueue.GetHead())
{
pEvent = m_EarlyQueue.Dequeue();
}
}
}
LeaveCriticalSection(&m_PipelineCrSec);
#ifdef DBG_PROFILE
dwDebugTime = timeGetTime() - dwDebugTime;
if( dwDebugTime > 1 )
{
TraceI(5, "Hall, debugtime GetNextPMsg %u\n", dwDebugTime);
}
#endif
return pEvent;
}
/* This next function is used just by the transport thread
which can process messages in the early queue, but not
the other types. This allows all the tools that process
events right after they are generated by tracks to process
the events right after they were generated, and in sequential
order. This allows them to take a little longer, since it's
not as time critical, and it's much more likely to ensure
that they are in sequential order. If the realtime thread were
allowed to process these, it would preempt and process them
as soon as generated, so they would be processed in the order
of the tracks. The m_fInTransportThread is set by the
transport thread when it is generating and processing events
and this disallows the realtime thread from processing
early events (but not others.) At other times, the realtime
thread is welcome to process early events.
*/
void CPerformance::ProcessEarlyPMsgs()
{
PRIV_PMSG* pEvent;
// Exit if the thread is exiting. If we don't test here
// we can actually loop forever because tools and queue more
// early PMSGs (the Echo tool does this)
while (!m_fKillThread)
{
EnterCriticalSection(&m_PipelineCrSec);
pEvent = m_EarlyQueue.Dequeue();
LeaveCriticalSection(&m_PipelineCrSec);
if (!pEvent) break; // Done?
ASSERT( pEvent->pNext == NULL );
if( !pEvent->pTool )
{
// this event doesn't have a Tool pointer, so stamp it with the
// final output Tool.
pEvent->pTool = (IDirectMusicTool*)this;
AddRef();
// Don't process it. Instead, send to neartime queue so
// realtime thread will deal with it.
pEvent->dwFlags &= ~(DMUS_PMSGF_TOOL_IMMEDIATE | DMUS_PMSGF_TOOL_QUEUE | DMUS_PMSGF_TOOL_ATTIME);
pEvent->dwFlags |= DMUS_PMSGF_TOOL_QUEUE;
SendPMsg( PRIV_TO_DMUS(pEvent) );
}
else
{
// before processing the event, set rtLast to the event's current time
pEvent->rtLast = pEvent->rtTime;
HRESULT hr = pEvent->pTool->ProcessPMsg( this, PRIV_TO_DMUS(pEvent) );
if( hr != S_OK ) // S_OK means do nothing
{
if( hr == DMUS_S_REQUEUE )
{
if(FAILED(SendPMsg( PRIV_TO_DMUS(pEvent) )))
{
FreePMsg(pEvent);
}
}
else // e.g. DMUS_S_FREE or error code
{
FreePMsg( pEvent );
}
}
}
}
}
REFERENCE_TIME CPerformance::GetTime()
{
REFERENCE_TIME rtTime;
REFERENCE_TIME rtCurrent = 0;
WORD w;
HRESULT hr = S_OK;
EnterCriticalSection(&m_MainCrSec);
if (m_pClock) hr = m_pClock->GetTime( &rtCurrent );
if( !m_pClock || FAILED( hr ) || rtCurrent == 0 )
{
// this only gets called with machines that don't support m_pClock
rtTime = timeGetTime();
rtCurrent = rtTime * REF_PER_MIL; // 100 ns increments
// take care of timeGetTime rolling over every 49 days
if( rtCurrent < 0 )
{
m_wRollOverCount++;
}
for( w = 0; w < m_wRollOverCount; w++ )
{
rtCurrent += 4294967296;
}
// if rtCurrent is negative, it means we've rolled over rtCurrent. Ignore
// this case for now, as it will be quite uncommon.
}
LeaveCriticalSection(&m_MainCrSec);
return rtCurrent;
}
REFERENCE_TIME CPerformance::GetLatency(void)
{
DWORD dwIndex;
REFERENCE_TIME rtLatency = 0;
REFERENCE_TIME rtTemp;
#ifdef DBG_PROFILE
DWORD dwDebugTime;
dwDebugTime = timeGetTime();
#endif
EnterCriticalSection(&m_PChannelInfoCrSec);
if( m_pPortTable )
{
for( dwIndex = 0; dwIndex < m_dwNumPorts; dwIndex++ )
{
if( m_pPortTable[dwIndex].pLatencyClock )
{
if( SUCCEEDED( m_pPortTable[dwIndex].pLatencyClock->GetTime( &rtTemp )))
{
if( rtTemp > rtLatency )
rtLatency = rtTemp;
}
}
else if( m_pPortTable[dwIndex].pPort )
{
if( SUCCEEDED( m_pPortTable[dwIndex].pPort->GetLatencyClock( &m_pPortTable[dwIndex].pLatencyClock )))
{
if( SUCCEEDED( m_pPortTable[dwIndex].pLatencyClock->GetTime( &rtTemp )))
{
if( rtTemp > rtLatency )
rtLatency = rtTemp;
}
}
}
}
}
LeaveCriticalSection(&m_PChannelInfoCrSec);
if( 0 == rtLatency )
{
rtLatency = GetTime();
}
#ifdef DBG_PROFILE
dwDebugTime = timeGetTime() - dwDebugTime;
if( dwDebugTime > 1 )
{
TraceI(5, "Hall, debugtime GetLatency %u\n", dwDebugTime);
}
#endif
if (m_rtEarliestStartTime > rtLatency)
{
rtLatency = m_rtEarliestStartTime;
}
return rtLatency;
}
// return the most desireable Segment latency, based on which ports this
// segment plays on.
REFERENCE_TIME CPerformance::GetBestSegLatency( CSegState* pSeg )
{
// If we're using audiopaths, the code below doesn't work because it doesn't
// take converting pchannels into account. So, just use the worse case
// latency. 99% of the time, there is only one port, so this results
// in just a performance enhancement.
if (m_dwAudioPathMode == 2)
{
return GetLatency();
}
DWORD dwIndex;
REFERENCE_TIME rtLatency = 0;
REFERENCE_TIME rtTemp;
BOOL* pafIndexUsed = NULL;
DWORD dwCount;
if( m_dwNumPorts == 1 )
{
return GetLatency();
}
pafIndexUsed = new BOOL[m_dwNumPorts];
if( NULL == pafIndexUsed )
{
return GetLatency();
}
for( dwCount = 0; dwCount < m_dwNumPorts; dwCount++ )
{
pafIndexUsed[dwCount] = FALSE;
}
DWORD dwNumPChannels, dwGroup, dwMChannel;
DWORD* paPChannels;
pSeg->m_pSegment->GetPChannels( &dwNumPChannels, &paPChannels );
for( dwCount = 0; dwCount < dwNumPChannels; dwCount++ )
{
if( SUCCEEDED( PChannelIndex( paPChannels[dwCount],
&dwIndex, &dwGroup, &dwMChannel )))
{
pafIndexUsed[dwIndex] = TRUE;
}
}
for( dwCount = 0; dwCount < m_dwNumPorts; dwCount++ )
{
if( pafIndexUsed[dwCount] )
break;
}
if( dwCount >= m_dwNumPorts )
{
delete [] pafIndexUsed;
return GetLatency();
}
EnterCriticalSection(&m_PChannelInfoCrSec);
for( dwIndex = 0; dwIndex < m_dwNumPorts; dwIndex++ )
{
if( pafIndexUsed[dwIndex] )
{
if( m_pPortTable[dwIndex].pLatencyClock )
{
if( SUCCEEDED( m_pPortTable[dwIndex].pLatencyClock->GetTime( &rtTemp )))
{
if( rtTemp > rtLatency )
rtLatency = rtTemp;
}
}
else if( m_pPortTable[dwIndex].pPort )
{
if( SUCCEEDED( m_pPortTable[dwIndex].pPort->GetLatencyClock( &m_pPortTable[dwIndex].pLatencyClock )))
{
if( SUCCEEDED( m_pPortTable[dwIndex].pLatencyClock->GetTime( &rtTemp )))
{
if( rtTemp > rtLatency )
rtLatency = rtTemp;
}
}
}
}
}
LeaveCriticalSection(&m_PChannelInfoCrSec);
if( 0 == rtLatency )
{
rtLatency = GetLatency();
}
delete [] pafIndexUsed;
return rtLatency;
}
/* Called from either QueuePrimarySegment or QueueSecondarySegment,
this calculates the appropriate boundary time to start the segment
playback. Most of the logic takes care of the new DMUS_SEGF_ALIGN
capabilities.
*/
void CPerformance::CalculateSegmentStartTime( CSegState* pSeg )
{
BOOL fNoValidStart = TRUE;
if (pSeg->m_dwPlaySegFlags & DMUS_SEGF_ALIGN)
{
// If the ALIGN flag is set, see if we can align with the requested resolution,
// but switch to the new segment at an earlier point, as defined by
// a "valid start" point in the new segment.
DMUS_VALID_START_PARAM ValidStart; // Used to read start parameter from segment.
MUSIC_TIME mtIntervalSize = 0; // Quantization value.
MUSIC_TIME mtTimeNow = (MUSIC_TIME)pSeg->m_rtGivenStart; // The earliest time this can start.
// Call resolve time to get the last quantized interval that precedes mtTimeNow.
MUSIC_TIME mtStartTime = ResolveTime( mtTimeNow, pSeg->m_dwPlaySegFlags, &mtIntervalSize );
// StartTime actually shows the next time after now, so subtract the interval time to get the previous position.
mtStartTime -= mtIntervalSize;
// If the segment was supposed to start after the very beginning, quantize it.
if (mtIntervalSize && pSeg->m_mtStartPoint)
{
pSeg->m_mtStartPoint = ((pSeg->m_mtStartPoint + (mtIntervalSize >> 1))
/ mtIntervalSize) * mtIntervalSize;
// If this ends up being longer than the segment, do we need to drop back?
}
// Now, get the next start point after the point in the segment that
// corresponds with mtTimeNow, adjusted for the startpoint.
if (SUCCEEDED(pSeg->m_pSegment->GetParam( GUID_Valid_Start_Time,-1,0,
pSeg->m_mtStartPoint + mtTimeNow - mtStartTime,NULL,(void *) &ValidStart)))
{
// If the valid start point is within the range, we can cut in at the start point.
if ((mtTimeNow - mtStartTime + ValidStart.mtTime) < (mtIntervalSize + pSeg->m_mtStartPoint))
{
pSeg->m_mtResolvedStart = mtTimeNow + ValidStart.mtTime;
pSeg->m_mtStartPoint += mtTimeNow - mtStartTime + ValidStart.mtTime;
fNoValidStart = FALSE;
}
}
if (fNoValidStart)
{
// Couldn't find a valid start point. Was DMUS_SEGF_VALID_START_XXX set so we can override?
if (pSeg->m_dwPlaySegFlags &
(DMUS_SEGF_VALID_START_MEASURE | DMUS_SEGF_VALID_START_BEAT | DMUS_SEGF_VALID_START_GRID | DMUS_SEGF_VALID_START_TICK))
{
MUSIC_TIME mtOverrideTime;
// Depending on the flag, we need to get the appropriate interval resolution.
if (pSeg->m_dwPlaySegFlags & DMUS_SEGF_VALID_START_MEASURE)
{
mtOverrideTime = ResolveTime( mtTimeNow, DMUS_SEGF_MEASURE, 0 );
}
else if (pSeg->m_dwPlaySegFlags & DMUS_SEGF_VALID_START_BEAT)
{
mtOverrideTime = ResolveTime( mtTimeNow, DMUS_SEGF_BEAT, 0 );
}
else if (pSeg->m_dwPlaySegFlags & DMUS_SEGF_VALID_START_GRID)
{
mtOverrideTime = ResolveTime( mtTimeNow, DMUS_SEGF_GRID, 0 );
}
else
{
mtOverrideTime = mtTimeNow;
}
// If the valid start point is within the range, we can cut in at the start point.
if ((mtOverrideTime - mtTimeNow) < (mtIntervalSize + pSeg->m_mtStartPoint))
{
pSeg->m_mtResolvedStart = mtOverrideTime;
if ((mtOverrideTime - mtStartTime) >= mtIntervalSize)
{
mtOverrideTime -= mtIntervalSize;
}
/*Trace(0,"Startpoint %ld plus OverrideTime %ld - StartTime %ld = %ld\n",
pSeg->m_mtStartPoint, mtOverrideTime - mtSegmentTime, mtStartTime - mtSegmentTime,
pSeg->m_mtStartPoint + mtOverrideTime - mtStartTime);*/
pSeg->m_mtStartPoint += mtOverrideTime - mtStartTime;
fNoValidStart = FALSE;
}
}
}
}
if (fNoValidStart)
{
pSeg->m_mtResolvedStart = ResolveTime( (MUSIC_TIME)pSeg->m_rtGivenStart,
pSeg->m_dwPlaySegFlags, NULL );
}
else
{
// If we succeeded in finding a place to switch over, make sure it isn't deep inside
// a loop. This is specifically a problem when syncing to segment and switching inside
// or after a loop.
while (pSeg->m_dwRepeats && (pSeg->m_mtStartPoint >= pSeg->m_mtLoopEnd))
{
pSeg->m_dwRepeats--;
pSeg->m_mtStartPoint -= (pSeg->m_mtLoopEnd - pSeg->m_mtLoopStart);
}
// Since we were decrementing the repeats, we need to also decrement the repeats left.
pSeg->m_dwRepeatsLeft = pSeg->m_dwRepeats;
// Finally, if the startpoint is after the end of the segment, cut it back to the end of the
// segment. This will cause it to play for time 0 and, if this is a transition segment, whatever
// should play after will play immediately.
if (pSeg->m_mtStartPoint > pSeg->m_mtLength)
{
pSeg->m_mtStartPoint = pSeg->m_mtLength;
}
}
pSeg->m_mtOffset = pSeg->m_mtResolvedStart;
pSeg->m_mtLastPlayed = pSeg->m_mtResolvedStart;
}
// this function should only be called from within a SegmentCrSec
// critical section!
void CPerformance::QueuePrimarySegment( CSegState* pSeg )
{
CSegState* pTemp;
BOOL fInCrSec = TRUE;
BOOL fNotDone = TRUE;
EnterCriticalSection(&m_PipelineCrSec);
pSeg->m_dwPlayTrackFlags |= DMUS_TRACKF_DIRTY;
if( pSeg->m_dwPlaySegFlags & DMUS_SEGF_QUEUE )
{
MUSIC_TIME mtStart = 0;
pTemp = m_SegStateQueues[SQ_PRI_PLAY].GetTail();
if( pTemp )
{
mtStart = pTemp->GetEndTime( pTemp->m_mtResolvedStart );
}
else
{
pTemp = m_SegStateQueues[SQ_PRI_DONE].GetTail();
if( pTemp )
{
mtStart = pTemp->m_mtLastPlayed;
}
}
pSeg->m_dwPlaySegFlags &= ~DMUS_SEGF_QUEUE;
if( NULL == pTemp )
{
// if there's nothing in the queue, this means play it now
if( pSeg->m_dwPlaySegFlags & DMUS_SEGF_AFTERPREPARETIME )
{
// we want to queue this at the last transported time,
// so we don't need to do an invalidate
if( pSeg->m_dwPlaySegFlags & DMUS_SEGF_REFTIME )
{
REFERENCE_TIME rtTrans;
MusicToReferenceTime( m_mtTransported, &rtTrans );
if( pSeg->m_rtGivenStart < rtTrans )
{
pSeg->m_dwPlaySegFlags &= ~DMUS_SEGF_REFTIME;
pSeg->m_rtGivenStart = m_mtTransported;
}
}
else
{
if( pSeg->m_rtGivenStart < m_mtTransported )
{
pSeg->m_rtGivenStart = m_mtTransported;
}
}
}
else
{
// This will be changed to Queue time below
pSeg->m_rtGivenStart = 0;
}
}
else
{
REFERENCE_TIME rtQueue;
// otherwise, time stamp it with the time corresponding to
// the end time of all segments currently in the queue.
pSeg->m_mtResolvedStart = mtStart;
// make sure the resolved start time isn't before the latency
GetQueueTime(&rtQueue);
ReferenceToMusicTime( rtQueue, &mtStart );
if( pSeg->m_mtResolvedStart < mtStart )
{
pSeg->m_mtResolvedStart = 0; // below code will take care of this case
}
else
{
pSeg->m_dwPlaySegFlags &= ~DMUS_SEGF_REFTIME;
pSeg->m_mtOffset = pSeg->m_mtResolvedStart;
m_SegStateQueues[SQ_PRI_PLAY].Insert(pSeg);
TraceI(2, "dmperf: queueing primary seg/DMUS_SEGF_QUEUE. Prev time=%ld, this=%ld\n",
pTemp->m_mtResolvedStart, pSeg->m_mtResolvedStart);
fNotDone = FALSE;
PrepSegToPlay(pSeg, true);
}
}
}
if( fNotDone && (pSeg->m_rtGivenStart == 0) )
{
// if the given start time is 0, it means play now.
MUSIC_TIME mtStart;
REFERENCE_TIME rtStart;
GetQueueTime( &rtStart );
ReferenceToMusicTime( rtStart, &mtStart );
pSeg->m_dwPlaySegFlags &= ~DMUS_SEGF_REFTIME;
pSeg->m_rtGivenStart = mtStart;
// we definitely want to get rid of all segments following
// the currently playing segment
if( m_SegStateQueues[SQ_PRI_PLAY].GetHead() )
{
while( pTemp = m_SegStateQueues[SQ_PRI_PLAY].GetHead()->GetNext() )
{
m_SegStateQueues[SQ_PRI_PLAY].Remove(pTemp);
pTemp->AbortPlay(mtStart,FALSE);
m_ShutDownQueue.Insert(pTemp);
}
}
}
if( fNotDone && pSeg->m_dwPlaySegFlags & DMUS_SEGF_REFTIME )
{
// rtStartTime is in RefTime units.
// We can convert this to Music Time immediately if either there
// is no currently playing Primary Segment, or the conversion
// falls within the time that has already played. If the time
// falls within PREPARE_TIME, we need to get this Segment
// playing right away.
REFERENCE_TIME rtNow = m_rtQueuePosition;
MUSIC_TIME mtTime;
if( m_SegStateQueues[SQ_PRI_PLAY].IsEmpty() || ( pSeg->m_rtGivenStart <= rtNow ) )
{
ReferenceToMusicTime( pSeg->m_rtGivenStart, &mtTime );
pSeg->m_dwPlaySegFlags &= ~( DMUS_SEGF_REFTIME );
pSeg->m_rtGivenStart = mtTime;
// let the block of code below that handles music time
// deal with it from here on
}
else
{
// Otherwise, we must wait until rtStartTime
// has been performed in order to convert to music time, because
// we require the tempo map at that time to do the conversion.
// This will be handled by the Transport code.
m_SegStateQueues[SQ_PRI_WAIT].Insert(pSeg);
fNotDone = FALSE; // prevents the next block of code from operating on
// this Segment.
}
}
if( fNotDone ) // music time
{
// if we're in music time units, we can queue this segment in the
// main queue, in time order. If this segment's music time is less
// than the start time of other segments in the queue, all of those
// segments are removed and discarded. Also, segments that are in
// the wait queue as RefTime are discarded.
ASSERT( !(pSeg->m_dwPlaySegFlags & DMUS_SEGF_REFTIME )); // m_rtGivenStart must be in music time
CalculateSegmentStartTime( pSeg );
while( (pTemp = m_SegStateQueues[SQ_PRI_WAIT].RemoveHead()) )
{
pTemp->AbortPlay(pSeg->m_mtResolvedStart,FALSE);
m_ShutDownQueue.Insert(pTemp);
}
if( pTemp = m_SegStateQueues[SQ_PRI_PLAY].GetHead() )
{
if( pSeg->m_mtResolvedStart > pTemp->m_mtResolvedStart )
{
while( pTemp->GetNext() )
{
if( pTemp->GetNext()->m_mtResolvedStart >= pSeg->m_mtResolvedStart )
{
break;
}
pTemp = pTemp->GetNext();
}
pSeg->SetNext(pTemp->GetNext());
pTemp->SetNext(pSeg);
while( pTemp = pSeg->GetNext() )
{
// delete the remaining pSegs after this one
pSeg->SetNext(pTemp->GetNext());
pTemp->AbortPlay(pSeg->m_mtResolvedStart,FALSE);
m_ShutDownQueue.Insert(pTemp);
}
}
else
{
if( !pTemp->m_fStartedPlay )
{
// blow away the entire queue
while( m_SegStateQueues[SQ_PRI_PLAY].GetHead() )
{
pTemp = m_SegStateQueues[SQ_PRI_PLAY].RemoveHead();
pTemp->AbortPlay(pSeg->m_mtResolvedStart,FALSE);
m_ShutDownQueue.Insert(pTemp);
}
m_SegStateQueues[SQ_PRI_PLAY].AddHead(pSeg);
// give this a chance to start performing if it's near
// enough to time
if( fInCrSec )
{
LeaveCriticalSection(&m_PipelineCrSec);
fInCrSec = FALSE;
}
SyncTimeSig( pSeg );
ManageControllingTracks();
PerformSegStNode( SQ_PRI_PLAY,pSeg);
}
else
{
// else, place this segment after the current one
// and count on the routine below to take care of dequeing
// the current one, because in this case m_mtLastPlayed
// must be greater than m_mtResolvedStart.
if ( m_SegStateQueues[SQ_PRI_PLAY].GetHead()->m_mtLastPlayed <=
m_SegStateQueues[SQ_PRI_PLAY].GetHead()->m_mtResolvedStart )
{
TraceI(0,"Current Primary segment has not started playing.\n");
}
m_SegStateQueues[SQ_PRI_PLAY].AddHead(pSeg);
MUSIC_TIME mtTime = pSeg->m_mtResolvedStart;
while( pTemp = pSeg->GetNext() )
{
pTemp->AbortPlay( mtTime, TRUE && (pSeg->m_dwPlaySegFlags & DMUS_SEGF_NOINVALIDATE) );
// delete the remaining pSegs after this one
pSeg->SetNext(pTemp->GetNext());
m_ShutDownQueue.Insert(pTemp);
}
}
}
// m_pPriSegQueue could have become NULL from the PerformSegStNode call above.
if( m_SegStateQueues[SQ_PRI_PLAY].GetHead() && (pSeg != m_SegStateQueues[SQ_PRI_PLAY].GetHead()) )
{
CSegState *pCurrentSeg = m_SegStateQueues[SQ_PRI_PLAY].GetHead();
if( pCurrentSeg->m_fStartedPlay &&
( pSeg->m_mtResolvedStart <= pCurrentSeg->m_mtLastPlayed ))
{
// If Playsegment is recursively called by the end of a previous segment in a song, don't abort.
if (!pCurrentSeg->m_fInPlay || !pCurrentSeg->m_fSongMode)
{
// the new segment wants to play on top of stuff that's
// already been transported by the current primary segment.
pCurrentSeg->AbortPlay(pSeg->m_mtResolvedStart-1,TRUE && (pSeg->m_dwPlaySegFlags & DMUS_SEGF_NOINVALIDATE));
m_SegStateQueues[SQ_PRI_DONE].Insert(m_SegStateQueues[SQ_PRI_PLAY].RemoveHead());
// make sure none of the last played times in the past list
// are past the resolved start
for( CSegState* pSegTemp = m_SegStateQueues[SQ_PRI_DONE].GetHead();
pSegTemp; pSegTemp = pSegTemp->GetNext() )
{
if( pSegTemp->m_mtLastPlayed > pSeg->m_mtResolvedStart )
{
pSegTemp->m_mtLastPlayed = pSeg->m_mtResolvedStart;
}
}
if( !( pSeg->m_dwPlaySegFlags & (DMUS_SEGF_NOINVALIDATE | DMUS_SEGF_INVALIDATE_PRI) ) )
{
// if we set the PREPARE flag it means we specifically
// don't want to invalidate
Invalidate( pSeg->m_mtResolvedStart, pSeg->m_dwPlaySegFlags );
}
else if ( (pSeg->m_dwPlaySegFlags & DMUS_SEGF_INVALIDATE_PRI) &&
!(pSeg->m_dwPlaySegFlags & DMUS_SEGF_NOINVALIDATE) )
{
pCurrentSeg->Flush(pSeg->m_mtResolvedStart);
}
ASSERT( m_SegStateQueues[SQ_PRI_PLAY].GetHead() == pSeg ); // this should be the case
if( fInCrSec )
{
LeaveCriticalSection(&m_PipelineCrSec);
fInCrSec = FALSE;
}
SyncTimeSig( pSeg );
ManageControllingTracks();
PerformSegStNode( SQ_PRI_PLAY,m_SegStateQueues[SQ_PRI_PLAY].GetHead() );
}
}
else
{
if( !( pSeg->m_dwPlaySegFlags & (DMUS_SEGF_NOINVALIDATE | DMUS_SEGF_INVALIDATE_PRI) ))
{
Invalidate( pSeg->m_mtResolvedStart, pSeg->m_dwPlaySegFlags );
}
else if ( (pSeg->m_dwPlaySegFlags & DMUS_SEGF_INVALIDATE_PRI) &&
!(pSeg->m_dwPlaySegFlags & DMUS_SEGF_NOINVALIDATE) )
{
pCurrentSeg->Flush(pSeg->m_mtResolvedStart);
}
}
}
}
else
{
m_SegStateQueues[SQ_PRI_PLAY].AddHead(pSeg);
// give this a chance to start performing if it's near
// enough to time
if( fInCrSec )
{
LeaveCriticalSection(&m_PipelineCrSec);
fInCrSec = FALSE;
}
//DWORD dwDebugTime = timeGetTime();
SyncTimeSig( pSeg );
//DWORD dwDebugTime2 = timeGetTime();
//Trace(0, "perf, debugtime SyncTimeSig %u\n", dwDebugTime2 - dwDebugTime);
ManageControllingTracks();
//dwDebugTime = timeGetTime();
//Trace(0, "perf, debugtime ManageControllingTracks %u\n", dwDebugTime - dwDebugTime2);
PerformSegStNode( SQ_PRI_PLAY,pSeg );
//dwDebugTime2 = timeGetTime();
//Trace(0, "perf, debugtime PerformSegStNode %u\n", dwDebugTime2 - dwDebugTime);
}
}
if( fInCrSec )
{
LeaveCriticalSection(&m_PipelineCrSec);
}
}
// this function should only be called from within a SegmentCrSec
// critical section!
void CPerformance::QueueSecondarySegment( CSegState* pSeg)
{
BOOL fInCrSec = FALSE;
BOOL fNotDone = TRUE;
if( pSeg->m_dwPlaySegFlags & DMUS_SEGF_CONTROL )
{
EnterCriticalSection(&m_PipelineCrSec);
fInCrSec = TRUE;
}
pSeg->m_dwPlaySegFlags &= ~DMUS_SEGF_QUEUE; // not legal for 2ndary segs.
if( pSeg->m_rtGivenStart == 0 )
{
MUSIC_TIME mtStart;
if( pSeg->m_dwPlaySegFlags & DMUS_SEGF_CONTROL )
{
REFERENCE_TIME rtStart;
GetQueueTime( &rtStart ); // need queue time because control segments cause invalidations
ReferenceToMusicTime( rtStart, &mtStart );
}
else
{
ReferenceToMusicTime( GetBestSegLatency(pSeg), &mtStart );
}
pSeg->m_dwPlaySegFlags &= ~DMUS_SEGF_REFTIME;
pSeg->m_rtGivenStart = mtStart;
}
if( pSeg->m_dwPlaySegFlags & DMUS_SEGF_REFTIME )
{
// rtStartTime is in RefTime units.
// We can convert this to Music Time immediately if either there
// is no currently playing Primary Segment, or the conversion
// falls within the time that has already played. If the time
// falls within PREPARE_TIME, we need to get this Segment
// playing right away.
REFERENCE_TIME rtNow;
if( pSeg->m_dwPlaySegFlags & DMUS_SEGF_CONTROL )
{
GetQueueTime( &rtNow ); // need queue time because control segments cause invalidations
}
else
{
rtNow = GetBestSegLatency(pSeg);
}
MUSIC_TIME mtTime;
if( pSeg->m_rtGivenStart <= rtNow )
{
ReferenceToMusicTime( rtNow, &mtTime );
pSeg->m_dwPlaySegFlags &= ~( DMUS_SEGF_REFTIME );
pSeg->m_rtGivenStart = mtTime;
// let the block of code below that handles music time
// deal with it from here on
}
else if( m_SegStateQueues[SQ_PRI_PLAY].IsEmpty() )
{
ReferenceToMusicTime( pSeg->m_rtGivenStart, &mtTime );
pSeg->m_dwPlaySegFlags &= ~( DMUS_SEGF_REFTIME );
pSeg->m_rtGivenStart = mtTime;
}
else
{
// Otherwise, we must wait until rtStartTime
// has been performed in order to convert to music time, because
// we require the tempo map at that time to do the conversion.
// This will be handled by the Transport code.
m_SegStateQueues[SQ_SEC_WAIT].Insert(pSeg);
fNotDone = FALSE; // prevents the next block of code from operating on
// this Segment.
}
}
if( fNotDone ) // music time
{
// if we're in music time units, we can queue this segment in the
// main queue, in time order. If this segment's music time is less
// than the start time of other segments in the queue, all of those
// segments are removed and discarded.
ASSERT( !(pSeg->m_dwPlaySegFlags & DMUS_SEGF_REFTIME )); // m_m_rtGivenStart must be in music time
CalculateSegmentStartTime( pSeg );
TraceI(2,"Queuing 2ndary seg time %ld\n",pSeg->m_mtResolvedStart);
if( pSeg->m_dwPlaySegFlags & DMUS_SEGF_CONTROL)
{
m_SegStateQueues[SQ_CON_PLAY].Insert( pSeg );
// If this is a control segment, we need to do an invalidate.
if(!(pSeg->m_dwPlaySegFlags & DMUS_SEGF_NOINVALIDATE) )
{
ManageControllingTracks();
Invalidate( pSeg->m_mtResolvedStart, 0 );
}
}
else
{
m_SegStateQueues[SQ_SEC_PLAY].Insert( pSeg );
}
// give this a chance to start performing if it's near
// enough to time
if( fInCrSec )
{
LeaveCriticalSection(&m_PipelineCrSec);
fInCrSec = FALSE;
}
// play the secondary segments
CSegState *pNode = m_SegStateQueues[SQ_SEC_PLAY].GetHead();
CSegState *pNext;
for(; pNode != NULL; pNode = pNext)
{
pNext = pNode->GetNext();
PerformSegStNode( SQ_SEC_PLAY,pNode );
}
// play the controlling segments
pNode = m_SegStateQueues[SQ_CON_PLAY].GetHead();
for(; pNode != NULL; pNode = pNext)
{
pNext = pNode->GetNext();
PerformSegStNode( SQ_CON_PLAY,pNode );
}
}
if( fInCrSec )
{
LeaveCriticalSection(&m_PipelineCrSec);
}
}
/* If a segment is controlling, this establishes which tracks in the currently playing
primary segment are disabled.
We store temporary information in each playing track's m_dwInternalFlags, which is not used
otherwise in segmentstates.
Four scenarios, each for play and notify:
1) An officially enabled track is currently enabled and gets disabled.
2) An officially enabled track is currently disabled and continues to be disabled.
3) An officially enabled track is currently disabled and gets enabled.
4) An officially disabled track is left disabled. If none of the CONTROL_ flags are set and the track is disabled,
set the _WAS_DISABLED flag, which also indicates that this should be left alone.
This should get called every time a primary or secondary segment starts or stop, so it
can recalculate the behavior of all tracks in the primary segment.
*/
void CPerformance::ManageControllingTracks()
{
EnterCriticalSection(&m_SegmentCrSec);
CSegState* pSegNode;
// First, prepare all tracks in the primary segment, putting them back to normal.
// so they are ready to be reset by the controlling tracks.
// To do this, check for WAS_ENABLED or WAS_DISABLED and set the appropriate flags in m_dwFlags.
// Else, if these weren't set, then it's time to set them, since this is the first pass through this segment.
for( pSegNode = m_SegStateQueues[SQ_PRI_PLAY].GetHead(); pSegNode; pSegNode = pSegNode->GetNext() )
{
EnterCriticalSection(&pSegNode->m_CriticalSection);
CTrack *pTrack = pSegNode->m_TrackList.GetHead();
for (;pTrack;pTrack = pTrack->GetNext())
{
if (pTrack->m_dwInternalFlags) // This has been touched before.
{
// First transfer and reset the is disabled flags.
if (pTrack->m_dwInternalFlags & CONTROL_PLAY_IS_DISABLED)
{
pTrack->m_dwInternalFlags |= CONTROL_PLAY_WAS_DISABLED;
}
pTrack->m_dwInternalFlags &= ~(CONTROL_PLAY_IS_DISABLED | CONTROL_NTFY_IS_DISABLED);
// Then, set the play flags based on the original state.
if (pTrack->m_dwInternalFlags & CONTROL_PLAY_DEFAULT_ENABLED)
{
pTrack->m_dwFlags |= DMUS_TRACKCONFIG_PLAY_ENABLED;
}
if (pTrack->m_dwInternalFlags & CONTROL_NTFY_DEFAULT_ENABLED)
{
pTrack->m_dwFlags |= DMUS_TRACKCONFIG_NOTIFICATION_ENABLED;
}
}
else
{
// Since this has never been touched before, set the flags so we can know what to return to.
if (pTrack->m_dwFlags & DMUS_TRACKCONFIG_PLAY_ENABLED)
{
pTrack->m_dwInternalFlags = CONTROL_PLAY_DEFAULT_ENABLED;
}
else
{
pTrack->m_dwInternalFlags = CONTROL_PLAY_DEFAULT_DISABLED;
}
if (pTrack->m_dwFlags & DMUS_TRACKCONFIG_NOTIFICATION_ENABLED)
{
pTrack->m_dwInternalFlags |= CONTROL_NTFY_DEFAULT_ENABLED;
}
else
{
pTrack->m_dwInternalFlags |= CONTROL_NTFY_DEFAULT_DISABLED;
}
}
}
LeaveCriticalSection(&pSegNode->m_CriticalSection);
}
CSegState* pControlNode;
// Now, go through all the controlling segments and, for each controlling track that matches
// a primary segment track, clear the enable flags on the segment track.
for( pControlNode = m_SegStateQueues[SQ_CON_PLAY].GetHead(); pControlNode; pControlNode = pControlNode->GetNext() )
{
EnterCriticalSection(&pControlNode->m_CriticalSection);
CTrack *pTrack = pControlNode->m_TrackList.GetHead();
for (;pTrack;pTrack = pTrack->GetNext())
{
// If the track has never been overridden, the internal flags for IS_DISABLED should be clear.
// If the track is currently overridden, the internal flags should be CONTROL_PLAY_IS_DISABLED and/or
// CONTROL_NTFY_IS_DISABLED
if (pTrack->m_dwFlags & (DMUS_TRACKCONFIG_CONTROL_PLAY | DMUS_TRACKCONFIG_CONTROL_NOTIFICATION)) // This overrides playback and/or notification.
{
for( pSegNode = m_SegStateQueues[SQ_PRI_PLAY].GetHead(); pSegNode; pSegNode = pSegNode->GetNext() )
{
EnterCriticalSection(&pSegNode->m_CriticalSection);
CTrack *pPrimaryTrack = pSegNode->m_TrackList.GetHead();
for (;pPrimaryTrack;pPrimaryTrack = pPrimaryTrack->GetNext())
{
// A track matches if it has the same class id and overlapping group bits.
if ((pPrimaryTrack->m_guidClassID == pTrack->m_guidClassID) &&
(pPrimaryTrack->m_dwGroupBits & pTrack->m_dwGroupBits))
{
if ((pTrack->m_dwFlags & DMUS_TRACKCONFIG_CONTROL_PLAY) &&
(pPrimaryTrack->m_dwFlags & DMUS_TRACKCONFIG_PLAY_ENABLED))
{
pPrimaryTrack->m_dwFlags &= ~DMUS_TRACKCONFIG_PLAY_ENABLED;
pPrimaryTrack->m_dwInternalFlags |= CONTROL_PLAY_IS_DISABLED; // Mark so we can turn on later.
}
if ((pTrack->m_dwFlags & DMUS_TRACKCONFIG_CONTROL_NOTIFICATION) &&
(pPrimaryTrack->m_dwFlags & DMUS_TRACKCONFIG_NOTIFICATION_ENABLED))
{
pPrimaryTrack->m_dwFlags &= ~DMUS_TRACKCONFIG_NOTIFICATION_ENABLED;
pPrimaryTrack->m_dwInternalFlags |= CONTROL_NTFY_IS_DISABLED; // Mark so we can turn on later.
}
}
}
LeaveCriticalSection(&pSegNode->m_CriticalSection);
}
}
}
LeaveCriticalSection(&pControlNode->m_CriticalSection);
}
// Now, go back to the primary segment and find all tracks that have been reenabled
// and tag them so they will generate refresh data on the next play (by seeking, as if they
// were starting or looping playback.) We only do this for play, not notify, because no
// notifications have state.
for( pSegNode = m_SegStateQueues[SQ_PRI_PLAY].GetHead(); pSegNode; pSegNode = pSegNode->GetNext() )
{
EnterCriticalSection(&pSegNode->m_CriticalSection);
CTrack *pTrack = pSegNode->m_TrackList.GetHead();
for (;pTrack;pTrack = pTrack->GetNext())
{
if ((pTrack->m_dwInternalFlags & CONTROL_PLAY_DEFAULT_ENABLED) &&
(pTrack->m_dwInternalFlags & CONTROL_PLAY_WAS_DISABLED) &&
!(pTrack->m_dwInternalFlags & CONTROL_PLAY_IS_DISABLED))
{
pTrack->m_dwInternalFlags |= CONTROL_PLAY_REFRESH; // Mark so we can turn on later.
}
}
LeaveCriticalSection(&pSegNode->m_CriticalSection);
}
LeaveCriticalSection(&m_SegmentCrSec);
}
void CPerformance::GetTimeSig( MUSIC_TIME mtTime, DMUS_TIMESIG_PMSG* pTimeSig )
{
EnterCriticalSection(&m_PipelineCrSec);
PRIV_PMSG* pEvent = m_TimeSigQueue.GetHead();
for (;pEvent;pEvent = pEvent->pNext)
{
// If this is the last time sig, return it. Or, if the next time sig is after mtTime.
if (!pEvent->pNext || ( pEvent->pNext->mtTime > mtTime ))
{
DMUS_TIMESIG_PMSG* pNewTimeSig = (DMUS_TIMESIG_PMSG*)PRIV_TO_DMUS(pEvent);
memcpy( pTimeSig, pNewTimeSig, sizeof(DMUS_TIMESIG_PMSG) );
LeaveCriticalSection(&m_PipelineCrSec);
return;
}
}
// This should only happen if there is no timesig at all. Should only happen before any segments play.
memset( pTimeSig, 0, sizeof(DMUS_TIMESIG_PMSG ) );
pTimeSig->wGridsPerBeat = 4;
pTimeSig->bBeatsPerMeasure = 4;
pTimeSig->bBeat = 4;
LeaveCriticalSection(&m_PipelineCrSec);
}
void CPerformance::SyncTimeSig( CSegState *pSegState )
/* If a primary segment is played that does not have a time signature track,
this forces the current time signature to line up with the start of the
primary segment.
*/
{
// First, test to see if the segment has a time signature.
// If it doesn't then we need to do this.
DMUS_TIMESIGNATURE TimeSig;
if (FAILED(pSegState->GetParam(this,GUID_TimeSignature,-1,0,0,NULL,(void *)&TimeSig)))
{
MUSIC_TIME mtTime = pSegState->m_mtResolvedStart;
EnterCriticalSection(&m_PipelineCrSec);
PRIV_PMSG* pEvent = m_TimeSigQueue.GetHead();
// Scan through the time signatures until the most recent one is found.
for (;pEvent;pEvent = pEvent->pNext)
{
// If this is the last time sig, return it. Or, if the next time sig is after mtTime.
if (!pEvent->pNext || ( pEvent->pNext->mtTime > mtTime ))
{
pEvent->mtTime = mtTime;
MusicToReferenceTime(mtTime,&pEvent->rtTime);
break;
}
}
// Should never fall through to here without finding a time signature because Init() creates a timesig.
LeaveCriticalSection(&m_PipelineCrSec);
}
}
// Convert mtTime into the resolved time according to the resolution in
// dwResolution.
// This should only be called from within a segment critical section.
MUSIC_TIME CPerformance::ResolveTime( MUSIC_TIME mtTime, DWORD dwResolution, MUSIC_TIME *pmtIntervalSize )
{
if (pmtIntervalSize)
{
*pmtIntervalSize = 0;
}
if (dwResolution & DMUS_SEGF_MARKER)
{
DMUS_PLAY_MARKER_PARAM Marker;
MUSIC_TIME mtNext;
// First, get the time of the marker preceding this one.
if (SUCCEEDED (GetParam(GUID_Play_Marker,-1,0,mtTime,&mtNext,(void *) &Marker)))
{
BOOL fIsMarker = FALSE;
MUSIC_TIME mtInitialTime = mtTime;
MUSIC_TIME mtFirst = mtTime + Marker.mtTime; // This is the time of the preceding marker.
MUSIC_TIME mtSecond = mtTime + mtNext; // This might be the time of the next marker.
// Then, scan forward until a marker is found after or equal to this time.
// If a loop point or end of segment is encountered, the value in Marker.mtTime will
// continue to be negative. Once we hit the actual marker, it will become 0, since
// we are asking for the marker at that specific time.
while (mtNext)
{
mtTime += mtNext;
if (SUCCEEDED(GetParam(GUID_Play_Marker,-1,0,mtTime,&mtNext,(void *) &Marker)))
{
// If the marker time is 0, this means we are sitting right on the marker,
// so we are done.
if (fIsMarker = (Marker.mtTime == 0))
{
mtSecond = mtTime;
break;
}
// Otherwise, this was a loop boundary or segment end, so we should continue scanning forward.
}
else
{
// GetParam failed, must be nothing more to search.
break;
}
}
// If the caller wants the interval size, then we know they are interested in
// aligning to a previous marker as well as a future one. In that case,
// if we didn't find a marker in the future, it's okay because it will
// use the previous marker (mtFirst) anyway.
// For all other cases, we only return if the upcoming marker is legal.
// Otherwise, we drop through and try other resolutions.
if (pmtIntervalSize || fIsMarker)
{
if (pmtIntervalSize)
{
*pmtIntervalSize = mtSecond - mtFirst;
}
return mtSecond;
}
mtTime = mtInitialTime;
}
// If marker fails, we can drop down to the other types...
}
if( dwResolution & DMUS_SEGF_SEGMENTEND )
{
// In this mode, we don't actually get the time signature. Instead, we
// find out the time of the next segment start after the requested time.
CSegState *pSegNode = GetPrimarySegmentAtTime( mtTime );
if( pSegNode )
{
// First, calculate the end time of the segment.
// Include any starting offset so we see the full span of the segment.
mtTime = pSegNode->GetEndTime( pSegNode->m_mtStartPoint );
if (pmtIntervalSize)
{
// Interval would be the length of the primary segment!
*pmtIntervalSize = mtTime;
}
// Return the end of the segment.
LONGLONG llEnd = mtTime + (LONGLONG)(pSegNode->m_mtResolvedStart - pSegNode->m_mtStartPoint);
if(llEnd > 0x7fffffff) llEnd = 0x7fffffff;
mtTime = (MUSIC_TIME) llEnd;
return mtTime;
}
// If there was no segment, we should fail and try the other flags.
}
long lQuantize;
MUSIC_TIME mtNewTime;
MUSIC_TIME mtStartOfTimeSig = 0;
DMUS_TIMESIGNATURE timeSig;
if (!(dwResolution & DMUS_SEGF_TIMESIG_ALWAYS))
{
if (!GetPrimarySegmentAtTime(mtTime))
{
return mtTime;
}
}
GetParam(GUID_TimeSignature,-1,0,mtTime,NULL,(void *) &timeSig);
mtStartOfTimeSig = timeSig.mtTime + mtTime;
mtNewTime = mtTime - mtStartOfTimeSig;
if (dwResolution & DMUS_SEGF_MEASURE)
{
lQuantize = ( DMUS_PPQ * 4 * timeSig.bBeatsPerMeasure ) / timeSig.bBeat;
}
else if (dwResolution & DMUS_SEGF_BEAT)
{
lQuantize = ( DMUS_PPQ * 4 ) / timeSig.bBeat;
}
else if (dwResolution & DMUS_SEGF_GRID)
{
lQuantize = ( ( DMUS_PPQ * 4 ) / timeSig.bBeat ) / timeSig.wGridsPerBeat;
}
else
{
lQuantize = 1;
}
if (lQuantize == 0) // Avoid divide by 0 error.
{
lQuantize = 1;
}
if (pmtIntervalSize)
{
*pmtIntervalSize = lQuantize;
}
if( mtNewTime ) // if it's 0 it stays 0
{
// round up to next boundary
mtNewTime = ((mtNewTime-1) / lQuantize ) * lQuantize;
mtNewTime += lQuantize;
}
return (mtNewTime + mtStartOfTimeSig);
}
// returns:
// true if the note should be invalidated (any other return code will invalidate)
// false if the note should not be invalidated
inline bool GetInvalidationStatus(DMUS_PMSG* pPMsg)
{
bool fResult = true; // default: invalidate the note
if( pPMsg->dwType == DMUS_PMSGT_NOTE )
{
DMUS_NOTE_PMSG* pNote = (DMUS_NOTE_PMSG*)pPMsg;
if (pNote->bFlags & DMUS_NOTEF_NOINVALIDATE)
{
fResult = false;
}
}
else if( pPMsg->dwType == DMUS_PMSGT_WAVE )
{
DMUS_WAVE_PMSG* pWave = (DMUS_WAVE_PMSG*)pPMsg;
if(pWave->bFlags & DMUS_WAVEF_NOINVALIDATE)
{
fResult = false;
}
}
else if( pPMsg->dwType == DMUS_PMSGT_NOTIFICATION )
{
// Don't invalidate segment abort messages
DMUS_NOTIFICATION_PMSG* pNotification = (DMUS_NOTIFICATION_PMSG*) pPMsg;
if ((pNotification->guidNotificationType == GUID_NOTIFICATION_SEGMENT) &&
(pNotification->dwNotificationOption == DMUS_NOTIFICATION_SEGABORT))
{
fResult = false;
}
}
return fResult;
}
static inline long ComputeCurveTimeSlice(DMUS_CURVE_PMSG* pCurve)
{
long lTimeIncrement;
DWORD dwTotalDistance;
DWORD dwResolution;
if ((pCurve->bType == DMUS_CURVET_PBCURVE) ||
(pCurve->bType == DMUS_CURVET_RPNCURVE) ||
(pCurve->bType == DMUS_CURVET_NRPNCURVE))
{
dwResolution = 100;
}
else
{
dwResolution = 3;
}
if (pCurve->nEndValue > pCurve->nStartValue)
dwTotalDistance = pCurve->nEndValue - pCurve->nStartValue;
else
dwTotalDistance = pCurve->nStartValue - pCurve->nEndValue;
if (dwTotalDistance == 0) dwTotalDistance = 1;
lTimeIncrement = (pCurve->mtDuration * dwResolution) / dwTotalDistance;
// Force to no smaller than 192nd note (10ms at 120 bpm.)
if( lTimeIncrement < (DMUS_PPQ/48) ) lTimeIncrement = DMUS_PPQ/48;
return lTimeIncrement;
}
static DWORD ComputeCurve( DMUS_CURVE_PMSG* pCurve )
{
DWORD dwRet;
short *panTable;
MUSIC_TIME mtCurrent;
long lIndex;
switch( pCurve->bCurveShape )
{
case DMUS_CURVES_INSTANT:
default:
if( pCurve->dwFlags & DMUS_PMSGF_TOOL_FLUSH )
{
pCurve->rtTime = 0;
return (DWORD)pCurve->nResetValue;
}
if( ( pCurve->bFlags & DMUS_CURVE_RESET ) && ( pCurve->mtResetDuration > 0 ) )
{
pCurve->mtTime = pCurve->mtResetDuration + pCurve->mtOriginalStart;
pCurve->mtDuration = 0;
pCurve->dwFlags &= ~DMUS_PMSGF_REFTIME;
}
else
{
pCurve->rtTime = 0; // setting this to 0 will free the event upon return
}
return (DWORD)pCurve->nEndValue;
break;
case DMUS_CURVES_LINEAR:
panTable = &ganCT_Linear[ 0 ];
break;
case DMUS_CURVES_EXP:
panTable = &ganCT_Exp[ 0 ];
break;
case DMUS_CURVES_LOG:
panTable = &ganCT_Log[ 0 ];
break;
case DMUS_CURVES_SINE:
panTable = &ganCT_Sine[ 0 ];
break;
}
// compute index into table
// there are CT_MAX + 1 elements in the table.
mtCurrent = pCurve->mtTime - pCurve->mtOriginalStart;
// if we're flushing this event, send the reset value
if( pCurve->dwFlags & DMUS_PMSGF_TOOL_FLUSH )
{
// it will only get here if pCurve->bFlags & 1, because that is checked in
// the :Flush() routine.
pCurve->rtTime = 0;
return pCurve->nResetValue;
}
// this should now never happen, as a result of fixing 33987: Transition on a beat boundary invalidates CC's right away (doesn't wait for the beat)
if( (pCurve->bFlags & DMUS_CURVE_RESET) &&
(pCurve->mtResetDuration < 0 ) && // this can happen from flushing
(pCurve->mtTime >= pCurve->mtOriginalStart + pCurve->mtDuration + pCurve->mtResetDuration ))
{
pCurve->rtTime = 0;
return pCurve->nResetValue;
}
else if( (pCurve->mtDuration == 0) ||
(pCurve->mtTime - pCurve->mtOriginalStart >= pCurve->mtDuration ))
{
// if we're supposed to send the return value (m_bFlags & 1) then
// set it up to do so. Otherwise, free the event.
if( pCurve->bFlags & DMUS_CURVE_RESET )
{
pCurve->mtTime = pCurve->mtDuration + pCurve->mtResetDuration +
pCurve->mtOriginalStart;
pCurve->dwFlags &= ~DMUS_PMSGF_REFTIME;
}
else
{
pCurve->rtTime = 0; // time to free the event, we're done
}
dwRet = pCurve->nEndValue;
}
else
{
// Calculate how far into the table we should be.
lIndex = (mtCurrent * (CT_MAX + 1)) / pCurve->mtDuration;
// find an amount of time to add to the curve event such that there is at
// least a change by CT_FACTOR. This will be used as the time stamp
// for the next iteration of the curve.
// clamp lIndex
if( lIndex < 0 )
{
lIndex = 0;
}
if( lIndex >= CT_MAX )
{
lIndex = CT_MAX;
dwRet = pCurve->nEndValue;
}
else
{
// Okay, in the curve, so calculate the return value.
dwRet = ((panTable[lIndex] * (pCurve->nEndValue - pCurve->nStartValue)) /
CT_DIVFACTOR) + pCurve->nStartValue;
}
// this should now never happen, as a result of fixing 33987
if( (pCurve->bFlags & DMUS_CURVE_RESET) && (pCurve->mtResetDuration < 0) )
{
// this can happen as a result of flushing. We want to make sure the next
// time is the reset flush time.
pCurve->mtTime = pCurve->mtDuration + pCurve->mtResetDuration +
pCurve->mtOriginalStart;
}
else
{
// Within curve, so increment time.
if (!pCurve->wMeasure) // oops --- better compute this.
{
TraceI(2, "Warning: Computing curve time slice...\n");
pCurve->wMeasure = (WORD) ComputeCurveTimeSlice(pCurve); // Use this to store the time slice interval.
}
pCurve->mtTime += pCurve->wMeasure; // We are storing the time increment here.
}
if( pCurve->mtTime > pCurve->mtDuration + pCurve->mtOriginalStart )
{
pCurve->mtTime = pCurve->mtDuration + pCurve->mtOriginalStart;
}
pCurve->dwFlags &= ~DMUS_PMSGF_REFTIME;
}
return dwRet;
}
static int RecomputeCurveEnd( DMUS_CURVE_PMSG* pCurve, MUSIC_TIME mtCurrent )
{
int nRet = 0;
short *panTable;
switch( pCurve->bCurveShape )
{
case DMUS_CURVES_INSTANT:
default:
return pCurve->nEndValue;
break;
case DMUS_CURVES_LINEAR:
panTable = &ganCT_Linear[ 0 ];
break;
case DMUS_CURVES_EXP:
panTable = &ganCT_Exp[ 0 ];
break;
case DMUS_CURVES_LOG:
panTable = &ganCT_Log[ 0 ];
break;
case DMUS_CURVES_SINE:
panTable = &ganCT_Sine[ 0 ];
break;
}
if( (pCurve->mtDuration == 0) || (mtCurrent >= pCurve->mtDuration ))
{
return pCurve->nEndValue;
}
else
{
// Calculate how far into the table we should be.
long lIndex = (mtCurrent * (CT_MAX + 1)) / pCurve->mtDuration;
// find an amount of time to add to the curve event such that there is at
// least a change by CT_FACTOR. This will be used as the time stamp
// for the next iteration of the curve.
// clamp lIndex
if( lIndex < 0 )
{
lIndex = 0;
}
if( lIndex >= CT_MAX )
{
lIndex = CT_MAX;
nRet = pCurve->nEndValue;
}
else
{
// Okay, in the curve, so calculate the return value.
nRet = ((panTable[lIndex] * (pCurve->nEndValue - pCurve->nStartValue)) /
CT_DIVFACTOR) + pCurve->nStartValue;
}
}
return nRet;
}
void CPerformance::FlushEventQueue( DWORD dwId,
CPMsgQueue *pQueue, // Queue to flush events from.
REFERENCE_TIME rtFlush, // Time that flush occurs. This may be resolved to a timing resolution.
REFERENCE_TIME rtFlushUnresolved, // Queue time at time flush was requested. This is not resolved to the timing resolution.
// Instead, it is the actual time at which that the flush was requested. This is used only by curves.
BOOL fLeaveNotesOn) // If notes or waves are currently on, do not cut short their durations.
{
PRIV_PMSG* pEvent;
PRIV_PMSG* pNext;
HRESULT hr = S_OK;
REFERENCE_TIME rtTemp;
GetQueueTime(&rtTemp);
pNext = NULL;
for(pEvent = pQueue->GetHead(); pEvent; pEvent = pNext )
{
pNext = pEvent->pNext;
// Clear the remove bit. This will be set for each event that should be removed from the queue.
pEvent->dwPrivFlags &= ~PRIV_FLAG_REMOVE;
// Also clear the requeue bit, which will be set for each event that needs to be requeued.
pEvent->dwPrivFlags &= ~PRIV_FLAG_REQUEUE;
if( ( 0 == dwId ) || ( pEvent->dwVirtualTrackID == dwId ) )
{
// First, create the correct mtTime and rtTime for invalidation.
REFERENCE_TIME rtTime = pEvent->rtTime;
if( pEvent->dwType == DMUS_PMSGT_NOTE )
{
DMUS_NOTE_PMSG* pNote = (DMUS_NOTE_PMSG*)PRIV_TO_DMUS(pEvent);
if( pNote->bFlags & DMUS_NOTEF_NOTEON )
{
// If this is a note on, we want to take the offset into consideration for
// determining whether or not to invalidate.
MUSIC_TIME mtNote = pNote->mtTime - pNote->nOffset;
MusicToReferenceTime( mtNote, &rtTime );
}
// If note off and we want to leave notes playing, turn on the noinvalidate flag.
else if (fLeaveNotesOn)
{
pNote->bFlags |= DMUS_NOTEF_NOINVALIDATE;
}
}
else if( pEvent->dwType == DMUS_PMSGT_WAVE )
{
DMUS_WAVE_PMSG* pWave = (DMUS_WAVE_PMSG*)PRIV_TO_DMUS(pEvent);
if( !(pWave->bFlags & DMUS_WAVEF_OFF) )
{
if (pWave->dwFlags & DMUS_PMSGF_LOCKTOREFTIME)
{
rtTime = pWave->rtTime;
}
else
{
MusicToReferenceTime(pWave->mtTime, &rtTime);
}
}
// If wave off and we want to leave waves playing, turn on the noinvalidate flag.
else if (fLeaveNotesOn)
{
pWave->bFlags |= DMUS_WAVEF_NOINVALIDATE;
}
}
else if( pEvent->dwType == DMUS_PMSGT_CURVE )
{
if (fLeaveNotesOn)
{
rtTime = 0;
}
else
{
DMUS_CURVE_PMSG* pCurve = (DMUS_CURVE_PMSG*)PRIV_TO_DMUS(pEvent);
MUSIC_TIME mtCurve;
MUSIC_TIME mtStart;
mtStart = pCurve->mtOriginalStart ? pCurve->mtOriginalStart : pCurve->mtTime;
// if rtFlush is before the beginning of the curve minus the offset of
// the curve, we want to prevent the curve from playing
mtCurve = mtStart - pCurve->nOffset;
MusicToReferenceTime( mtCurve, &rtTime );
if( rtFlush > rtTime ) // if it isn't...
{
// if the curve has a reset value and has already begun,
// we may want to flush right away.
if( ( pCurve->bFlags & DMUS_CURVE_RESET) &&
pCurve->mtOriginalStart &&
rtFlush <= rtFlushUnresolved )
{
mtCurve = mtStart + pCurve->mtDuration;
MusicToReferenceTime( mtCurve, &rtTime );
if( rtTime >= rtFlush && !(pEvent->dwPrivFlags & PRIV_FLAG_FLUSH) )
{
MUSIC_TIME mt = 0;
ReferenceToMusicTime(rtFlush, &mt);
pCurve->mtDuration = (mt - mtStart) - 1;
pCurve->mtResetDuration = 1;
}
else
{
mtCurve = mtStart + pCurve->mtDuration + pCurve->mtResetDuration;
MusicToReferenceTime( mtCurve, &rtTime );
if ( rtTime >= rtFlush && !(pEvent->dwPrivFlags & PRIV_FLAG_FLUSH) )
{
MUSIC_TIME mt = 0;
ReferenceToMusicTime(rtFlush, &mt);
pCurve->mtResetDuration = mt - (mtStart + pCurve->mtDuration);
}
}
}
else
{
// Otherwise, we may cut the curve short in the code below.
rtTime = 0;
}
}
}
}
// now flush the event if needed
if( rtTime >= rtFlush )
{
if (!(pEvent->dwPrivFlags & PRIV_FLAG_FLUSH))
{
if( pEvent->pTool)
{
bool fFlush = false;
if (pEvent->dwType == DMUS_PMSGT_WAVE)
{
DMUS_WAVE_PMSG* pWave = (DMUS_WAVE_PMSG*)PRIV_TO_DMUS(pEvent);
if( !(pWave->bFlags & DMUS_WAVEF_OFF) )
{
// this wave on is due to start after the flush time.
// we never want to hear it.
fFlush = true;
}
else
{
// cut the duration short, but don't actually flush here,
// since it's possible to invalidate the same wave more
// than once, and the second invalidation might have a
// time prior to the first one (e.g., first is from a loop,
// second is from a transition)
if (GetInvalidationStatus(PRIV_TO_DMUS(pEvent)) &&
rtFlush < pWave->rtTime)
{
pEvent->dwPrivFlags |= PRIV_FLAG_REQUEUE;
MUSIC_TIME mtFlush = 0;
ReferenceToMusicTime(rtFlush, &mtFlush);
pWave->rtTime = rtFlush;
pWave->mtTime = mtFlush;
}
}
}
if (fFlush ||
(pEvent->dwType != DMUS_PMSGT_WAVE &&
GetInvalidationStatus(PRIV_TO_DMUS(pEvent))) )
{
pEvent->dwPrivFlags |= PRIV_FLAG_REMOVE;
pEvent->dwFlags |= DMUS_PMSGF_TOOL_FLUSH;
if( rtFlush <= pEvent->rtLast )
{
pEvent->pTool->Flush( this, PRIV_TO_DMUS(pEvent), pEvent->rtLast + REF_PER_MIL );
}
else
{
pEvent->pTool->Flush( this, PRIV_TO_DMUS(pEvent), rtFlush );
}
}
}
else
{
pEvent->dwPrivFlags |= PRIV_FLAG_REMOVE;
}
}
}
else // cut notes, waves, and curves short if needed
{
if( pEvent->dwType == DMUS_PMSGT_NOTE && !fLeaveNotesOn )
{
DMUS_NOTE_PMSG* pNote = (DMUS_NOTE_PMSG*)PRIV_TO_DMUS(pEvent);
if( pNote->bFlags & DMUS_NOTEF_NOTEON )
{
if (GetInvalidationStatus(PRIV_TO_DMUS(pEvent)))
{
// subtract 2 from the duration to guarantee the note cuts short
// 1 clock before the flush time.
MUSIC_TIME mtNoteOff = pNote->mtTime + pNote->mtDuration - 2;
REFERENCE_TIME rtNoteOff;
MusicToReferenceTime( mtNoteOff, &rtNoteOff );
if( rtNoteOff >= rtFlush )
{
ReferenceToMusicTime( rtFlush, &mtNoteOff );
mtNoteOff -= pNote->mtTime;
// Make any duration < 1 be 0; this will cause the note not to
// sound. Can happen if the note's logical time is well before
// its physical time.
if( mtNoteOff < 1 ) mtNoteOff = 0;
pNote->mtDuration = mtNoteOff;
}
}
}
}
else if( pEvent->dwType == DMUS_PMSGT_WAVE && !fLeaveNotesOn )
{
DMUS_WAVE_PMSG* pWave = (DMUS_WAVE_PMSG*)PRIV_TO_DMUS(pEvent);
if( !(pWave->bFlags & DMUS_WAVEF_OFF) &&
(GetInvalidationStatus(PRIV_TO_DMUS(pEvent))) )
{
if (pWave->dwFlags & DMUS_PMSGF_LOCKTOREFTIME)
{
// This is a clock time message.
// subtract 2 from the duration to guarantee the wave cuts short
// 1 clock before the flush time.
if ((rtTime + pWave->rtDuration - 2) >= rtFlush)
{
pWave->rtDuration = rtFlush - rtTime;
}
}
else
{
MUSIC_TIME mtTime = 0;
MUSIC_TIME mtFlush = 0;
ReferenceToMusicTime(rtTime, &mtTime);
ReferenceToMusicTime(rtFlush, &mtFlush);
// subtract 2 from the duration to guarantee the wave cuts short
// 1 clock before the flush time.
if ((mtTime + (MUSIC_TIME)pWave->rtDuration - 2) >= mtFlush)
{
pWave->rtDuration = mtFlush - mtTime;
}
}
if (pWave->rtDuration < 1) // disallow durations less than 1. This should never happen anyway.
{
pWave->rtDuration = 1;
}
}
}
else if( pEvent->dwType == DMUS_PMSGT_CURVE && !fLeaveNotesOn )
{
DMUS_CURVE_PMSG* pCurve = (DMUS_CURVE_PMSG*)PRIV_TO_DMUS(pEvent);
MUSIC_TIME mtEnd;
MUSIC_TIME mtStart = pCurve->mtOriginalStart ? pCurve->mtOriginalStart : pCurve->mtTime;
if( pCurve->bFlags & DMUS_CURVE_RESET )
{
mtEnd = mtStart + pCurve->mtResetDuration + pCurve->mtDuration;
}
else
{
mtEnd = mtStart + pCurve->mtDuration;
}
REFERENCE_TIME rtEnd;
MusicToReferenceTime( mtEnd, &rtEnd );
// Note: as a result of fixing 33987, the curve is no longer given
// a negative reset duration. Now, the curve's duration is recomputed
// and its time slice is recalculated.
if( rtEnd >= rtFlush )
{
// reset the curve's duration
ReferenceToMusicTime( rtFlush, &mtEnd );
mtEnd -= mtStart;
// get the curve value at the flush time, and make that the end value
pCurve->nEndValue = (short) RecomputeCurveEnd(pCurve, mtEnd);
// subtract 2 from the duration to guarantee the curve cuts short
// 1 clock before the flush time.
mtEnd -= 2;
if ( mtEnd < 1)
{
mtEnd = 1;
}
else if (pCurve->bFlags & DMUS_CURVE_RESET)
{
if (mtEnd > pCurve->mtDuration)
{
// curve ends in the reset duration; keep regular duration the
// same as it was and adjust reset duration
pEvent->dwPrivFlags |= PRIV_FLAG_FLUSH;
MUSIC_TIME mt = 0;
ReferenceToMusicTime(rtFlush, &mt);
pCurve->mtResetDuration = mt - (mtStart + pCurve->mtDuration);
mtEnd = pCurve->mtDuration;
if (pCurve->mtTime > mtEnd + pCurve->mtResetDuration + mtStart)
{
pCurve->mtTime = mtEnd + pCurve->mtResetDuration + mtStart;
MusicToReferenceTime(pCurve->mtTime, &pCurve->rtTime);
}
}
else
{
// curve ends in the regular duration; reduce it by 1 and
// give the reset duration a value of 1
mtEnd--;
pCurve->mtResetDuration = 1;
if (mtEnd < 1)
{
// this is unlikely, but the curve really should have
// a duration...
mtEnd = 1;
}
pEvent->dwPrivFlags |= PRIV_FLAG_FLUSH;
}
// If this is an instant curve that's already started, we
// don't want it to play again, so reset its start time
if ( pCurve->bCurveShape == DMUS_CURVES_INSTANT &&
pCurve->mtOriginalStart )
{
pCurve->mtTime = pCurve->mtResetDuration + pCurve->mtOriginalStart + mtEnd;
}
}
pCurve->mtDuration = mtEnd;
}
}
}
}
}
// remove (and unmark) all marked PMsgs from the current queue
for(pEvent = pQueue->GetHead(); pEvent; pEvent = pNext )
{
pNext = pEvent->pNext;
if (pEvent->dwPrivFlags & (PRIV_FLAG_REMOVE | PRIV_FLAG_REQUEUE))
{
pEvent->dwPrivFlags &= ~PRIV_FLAG_REMOVE;
if (pQueue->Dequeue(pEvent))
{
if (pEvent->dwPrivFlags & PRIV_FLAG_REQUEUE)
{
pEvent->dwPrivFlags &= ~PRIV_FLAG_REQUEUE;
pQueue->Enqueue(pEvent);
}
else
{
FreePMsg(pEvent);
}
}
else
{
TraceI(0,"Error dequeing event for flushing\n");
}
}
}
SendBuffers();
}
/*
Flushes all events in all queues from time <p mtFlush> on.
comm Only call this from withing a PipelineCrSec critical section!
*/
void CPerformance::FlushMainEventQueues(
DWORD dwId, // Virtual Track ID to flush, or zero for all.
MUSIC_TIME mtFlush, // Time to flush (resolved to timing resolution).
MUSIC_TIME mtFlushUnresolved, // Time to flush (unresolved).
BOOL fLeaveNotesOn) // If true, notes currently on are left to play through their duration.
{
REFERENCE_TIME rt;
if( mtFlush )
{
MusicToReferenceTime( mtFlush, &rt );
}
else
{
rt = 0;
}
REFERENCE_TIME rtUnresolved;
if( mtFlushUnresolved && mtFlushUnresolved != mtFlush)
{
MusicToReferenceTime( mtFlushUnresolved, &rtUnresolved );
}
else
{
rtUnresolved = rt;
}
FlushEventQueue( dwId, &m_OnTimeQueue, rt, rtUnresolved, fLeaveNotesOn );
FlushEventQueue( dwId, &m_NearTimeQueue, rt, rtUnresolved, fLeaveNotesOn );
FlushEventQueue( dwId, &m_EarlyQueue, rt, rtUnresolved, fLeaveNotesOn );
if (dwId == 0)
{
MUSIC_TIME mtTime;
ReferenceToMusicTime(rt,&mtTime);
FlushEventQueue( dwId, &m_TempoMap, rt, rtUnresolved, fLeaveNotesOn );
RecalcTempoMap(NULL, mtTime );
}
}
// the only kinds of events we care about are note events.
void CPerformance::OnChordUpdateEventQueue( DMUS_NOTIFICATION_PMSG* pNotify, CPMsgQueue *pQueue, REFERENCE_TIME rtFlush )
{
PRIV_PMSG* pEvent;
PRIV_PMSG* pNext;
HRESULT hr = S_OK;
DWORD dwId = pNotify->dwVirtualTrackID;
DWORD dwTrackGroup = pNotify->dwGroupID;
CPMsgQueue UpdateQueue; // List of PMsgs to be inserted into a queue during update.
REFERENCE_TIME rtTemp;
GetQueueTime(&rtTemp);
pNext = NULL;
for(pEvent = pQueue->GetHead(); pEvent; pEvent = pNext )
{
pNext = pEvent->pNext;
pEvent->dwPrivFlags &= ~PRIV_FLAG_REMOVE;
DMUS_PMSG* pNew = NULL;
if( ( 0 == dwId || pEvent->dwVirtualTrackID == dwId ) &&
(pEvent->dwType == DMUS_PMSGT_NOTE) )
{
REFERENCE_TIME rtTime = pEvent->rtTime;
DMUS_NOTE_PMSG* pNote = (DMUS_NOTE_PMSG*)PRIV_TO_DMUS(pEvent);
if( pNote->bFlags & DMUS_NOTEF_NOTEON )
{
MUSIC_TIME mtNote = pNote->mtTime - pNote->nOffset;
MusicToReferenceTime( mtNote, &rtTime );
}
// now flush the event if needed
if( rtTime >= rtFlush )
{
REFERENCE_TIME rtFlushTime = (rtFlush <= pEvent->rtLast) ? pEvent->rtLast + REF_PER_MIL : rtFlush;
if( pEvent->pTool &&
!(pNote->bFlags & DMUS_NOTEF_NOTEON) &&
S_OK == (hr = GetChordNotificationStatus(pNote, dwTrackGroup, rtFlushTime, &pNew)))
{
pEvent->dwPrivFlags |= PRIV_FLAG_REMOVE;
pEvent->dwFlags |= DMUS_PMSGF_TOOL_FLUSH;
pEvent->pTool->Flush( this, PRIV_TO_DMUS(pEvent), rtFlushTime );
}
if (SUCCEEDED(hr) && pNew) // add to temp queue for later insertion into regular queue
{
UpdateQueue.Enqueue( DMUS_TO_PRIV(pNew) );
}
}
else // cut notes short if needed
{
if( pNote->bFlags & DMUS_NOTEF_NOTEON )
{
if (S_OK == (hr = GetChordNotificationStatus(pNote, dwTrackGroup, rtFlush, &pNew)))
{
// subtract 2 from the duration to guarantee the note cuts short
// 1 clock before the flush time.
MUSIC_TIME mtNoteOff = pNote->mtTime + pNote->mtDuration - 2;
REFERENCE_TIME rtNoteOff;
MusicToReferenceTime( mtNoteOff, &rtNoteOff );
if( rtNoteOff >= rtFlush )
{
ReferenceToMusicTime( rtFlush, &mtNoteOff );
mtNoteOff -= pNote->mtTime;
if( mtNoteOff < 1 ) mtNoteOff = 1; // disallow durations less than 1. This should never happen anyway.
pNote->mtDuration = mtNoteOff;
}
}
if (SUCCEEDED(hr) && pNew) // add to temp queue for later insertion into regular queue
{
UpdateQueue.Enqueue( DMUS_TO_PRIV(pNew) );
}
}
}
}
}
// remove (and unmark) all marked PMsgs from the current queue
for(pEvent = pQueue->GetHead(); pEvent; pEvent = pNext )
{
pNext = pEvent->pNext;
if (pEvent->dwPrivFlags & PRIV_FLAG_REMOVE)
{
pEvent->dwPrivFlags &= ~PRIV_FLAG_REMOVE;
if (pQueue->Dequeue(pEvent))
{
FreePMsg(pEvent);
}
else
{
TraceI(0,"Error dequeing event for flushing\n");
}
}
}
// empty the Update queue into the current queue
while( pEvent = UpdateQueue.Dequeue() )
{
pQueue->Enqueue(pEvent);
}
SendBuffers();
}
/*
Only call this from withing a PipelineCrSec critical section!
*/
void CPerformance::OnChordUpdateEventQueues(
DMUS_NOTIFICATION_PMSG* pNotify) // notification PMsg that caused this to be called
{
IDirectMusicSegmentState* pSegState = NULL;
if (!pNotify || !pNotify->punkUser) return;
REFERENCE_TIME rt = 0;
if( pNotify->mtTime )
{
MusicToReferenceTime( pNotify->mtTime, &rt );
}
OnChordUpdateEventQueue( pNotify, &m_OnTimeQueue, rt );
OnChordUpdateEventQueue( pNotify, &m_NearTimeQueue, rt );
OnChordUpdateEventQueue( pNotify, &m_EarlyQueue, rt );
}
/////////////////////////////////////////////////////////////////////////////
// IDirectMusicPerformance
HRESULT CPerformance::CreateThreads()
{
// initialize the realtime thread
m_hRealtimeThread = CreateThread(NULL, 0, _Realtime, this, 0, &m_dwRealtimeThreadID);
if( m_hRealtimeThread )
{
m_hRealtime = CreateEvent(NULL,FALSE,FALSE,NULL);
SetThreadPriority( m_hRealtimeThread, THREAD_PRIORITY_TIME_CRITICAL );
}
else
{
TraceI(0, "Major error! Realtime thread not created.\n");
return E_OUTOFMEMORY;
}
// initialize the transport thread
m_hTransportThread = CreateThread(NULL, 0, _Transport, this, 0, &m_dwTransportThreadID);
if( m_hTransportThread )
{
m_hTransport = CreateEvent(NULL, FALSE, FALSE, NULL);
SetThreadPriority( m_hTransportThread, THREAD_PRIORITY_ABOVE_NORMAL );
}
else
{
TraceI(0, "Major error! Transport thread not created.\n");
m_fKillRealtimeThread = TRUE;
if( m_hRealtime ) SetEvent( m_hRealtime );
return E_OUTOFMEMORY;
}
m_pDirectMusic->GetMasterClock( NULL, &m_pClock );
m_rtStart = GetTime();
m_rtQueuePosition = m_rtStart;
return S_OK;
}
STDMETHODIMP CPerformance::InitAudio(IDirectMusic** ppDirectMusic,
IDirectSound** ppDirectSound,
HWND hWnd,
DWORD dwDefaultPathType,
DWORD dwPChannelCount,
DWORD dwFlags,
DMUS_AUDIOPARAMS *pParams)
{
V_INAME(IDirectMusicPerformance::InitAudio);
V_PTRPTR_WRITE_OPT(ppDirectMusic);
V_PTRPTR_WRITE_OPT(ppDirectSound);
V_HWND_OPT(hWnd);
HRESULT hr = S_OK;
// Further validate, checking for a pointer to a bad interface pointer...
if (ppDirectMusic)
{
V_INTERFACE_OPT(*ppDirectMusic);
}
if (ppDirectSound)
{
V_INTERFACE_OPT(*ppDirectSound);
}
if( m_dwAudioPathMode )
{
Trace(1,"Error: InitAudio called on an already initialized Performance.\n");
return DMUS_E_ALREADY_INITED;
}
if (dwFlags == 0)
{
dwFlags = DMUS_AUDIOF_ALL;
}
Init();
m_AudioParams.dwFeatures = dwFlags;
m_AudioParams.dwSampleRate = 22050;
m_AudioParams.dwSize = sizeof (m_AudioParams);
m_AudioParams.dwValidData = DMUS_AUDIOPARAMS_FEATURES | DMUS_AUDIOPARAMS_VOICES | DMUS_AUDIOPARAMS_SAMPLERATE | DMUS_AUDIOPARAMS_DEFAULTSYNTH;
m_AudioParams.dwVoices = 64;
m_AudioParams.fInitNow = TRUE;
m_AudioParams.clsidDefaultSynth = CLSID_DirectMusicSynth;
if (pParams)
{
if (pParams->dwValidData & DMUS_AUDIOPARAMS_FEATURES)
{
m_AudioParams.dwFeatures = pParams->dwFeatures;
}
if (pParams->dwValidData & DMUS_AUDIOPARAMS_VOICES)
{
m_AudioParams.dwVoices = pParams->dwVoices;
}
if (pParams->dwValidData & DMUS_AUDIOPARAMS_DEFAULTSYNTH)
{
// If they requested the DX7 default synth and yet also asked for audiopath
// features, force to DX8 default synth.
if ((pParams->clsidDefaultSynth != GUID_NULL) ||
!((m_AudioParams.dwValidData & DMUS_AUDIOPARAMS_FEATURES) &&
(m_AudioParams.dwFeatures & DMUS_AUDIOF_ALL)))
{
m_AudioParams.clsidDefaultSynth = pParams->clsidDefaultSynth;
}
}
if (pParams->dwValidData & DMUS_AUDIOPARAMS_SAMPLERATE)
{
if (pParams->dwSampleRate > 96000)
{
m_AudioParams.dwSampleRate = 96000;
}
else if (pParams->dwSampleRate < 11025)
{
m_AudioParams.dwSampleRate = 11025;
}
else
{
m_AudioParams.dwSampleRate = pParams->dwSampleRate;
}
}
}
m_dwAudioPathMode = 2;
EnterCriticalSection(&m_MainCrSec);
if (ppDirectMusic && *ppDirectMusic)
{
hr = (*ppDirectMusic)->QueryInterface(IID_IDirectMusic8,(void **) &m_pDirectMusic);
}
if (SUCCEEDED(hr))
{
if (ppDirectSound && *ppDirectSound)
{
hr = (*ppDirectSound)->QueryInterface(IID_IDirectSound8,(void **) &m_pDirectSound);
}
if (SUCCEEDED(hr))
{
if (!m_pDirectSound)
{
hr = DirectSoundCreate8(NULL,&m_pDirectSound,NULL);
if (SUCCEEDED(hr))
{
if (!hWnd)
{
hWnd = GetForegroundWindow();
if (!hWnd)
{
hWnd = GetDesktopWindow();
}
}
m_pDirectSound->SetCooperativeLevel(hWnd, DSSCL_PRIORITY);
}
}
if (SUCCEEDED(hr))
{
if (!m_pDirectMusic)
{
hr = CoCreateInstance(CLSID_DirectMusic,
NULL,
CLSCTX_INPROC,
IID_IDirectMusic8,
(LPVOID*)&m_pDirectMusic);
if (SUCCEEDED(hr))
{
hr = m_pDirectMusic->SetDirectSound(m_pDirectSound,hWnd);
}
}
}
}
}
if (SUCCEEDED(hr))
{
hr = m_BufferManager.Init(this,&m_AudioParams);
if (SUCCEEDED(hr))
{
// If we are going to be connecting the synth to Buffers,
// force the use of the dsound clock.
if (m_AudioParams.dwFeatures & DMUS_AUDIOF_BUFFERS)
{
DMUS_CLOCKINFO ClockInfo;
ClockInfo.dwSize = sizeof(ClockInfo);
DWORD dwIndex;
GUID guidMasterClock = GUID_NULL;
for (dwIndex = 0; ;dwIndex++)
{
if (S_OK == m_pDirectMusic->EnumMasterClock(dwIndex, &ClockInfo))
{
if (!wcscmp(ClockInfo.wszDescription, L"DirectSound Clock"))
{
guidMasterClock = ClockInfo.guidClock;
break;
}
}
else
{
break;
}
}
m_pDirectMusic->SetMasterClock(guidMasterClock);
}
hr = CreateThreads();
if (SUCCEEDED(hr))
{
if (dwDefaultPathType)
{
IDirectMusicAudioPath *pPath;
hr = CreateStandardAudioPath(dwDefaultPathType,dwPChannelCount,m_AudioParams.fInitNow,&pPath);
if (SUCCEEDED(hr))
{
hr = SetDefaultAudioPath(pPath);
pPath->Release();
}
}
}
}
}
if (SUCCEEDED(hr))
{
if (m_pDirectMusic && ppDirectMusic && !*ppDirectMusic)
{
*ppDirectMusic = m_pDirectMusic;
m_pDirectMusic->AddRef();
}
if (m_pDirectSound && ppDirectSound && !*ppDirectSound)
{
*ppDirectSound = m_pDirectSound;
m_pDirectSound->AddRef();
}
if (pParams && pParams->fInitNow)
{
if (pParams->clsidDefaultSynth != m_AudioParams.clsidDefaultSynth)
{
pParams->clsidDefaultSynth = m_AudioParams.clsidDefaultSynth;
if (pParams->dwValidData & DMUS_AUDIOPARAMS_DEFAULTSYNTH)
{
Trace(2,"Warning: Default synth choice has been changed.\n");
hr = S_FALSE;
}
}
if (pParams->dwFeatures != m_AudioParams.dwFeatures)
{
pParams->dwFeatures = m_AudioParams.dwFeatures;
if (pParams->dwValidData & DMUS_AUDIOPARAMS_FEATURES)
{
Trace(2,"Warning: Features flags has been changed to %lx.\n",pParams->dwFeatures);
hr = S_FALSE;
}
}
if (pParams->dwSampleRate != m_AudioParams.dwSampleRate)
{
pParams->dwSampleRate = m_AudioParams.dwSampleRate;
if (pParams->dwValidData & DMUS_AUDIOPARAMS_SAMPLERATE)
{
Trace(2,"Warning: Sample rate has been changed to %ld.\n",pParams->dwSampleRate);
hr = S_FALSE;
}
}
if (pParams->dwVoices != m_AudioParams.dwVoices)
{
pParams->dwVoices = m_AudioParams.dwVoices;
if (pParams->dwValidData & DMUS_AUDIOPARAMS_VOICES)
{
Trace(2,"Warning: Number of requested voices has been changed to %ld.\n",pParams->dwVoices);
hr = S_FALSE;
}
}
pParams->dwValidData = m_AudioParams.dwValidData;
}
LeaveCriticalSection(&m_MainCrSec);
}
else
{
LeaveCriticalSection(&m_MainCrSec);
CloseDown();
}
return hr;
}
HRESULT STDMETHODCALLTYPE CPerformance::Init(
IDirectMusic** ppDirectMusic, LPDIRECTSOUND pDirectSound,HWND hWnd)
{
V_INAME(IDirectMusicPerformance::Init);
V_PTRPTR_WRITE_OPT(ppDirectMusic);
V_INTERFACE_OPT(pDirectSound);
V_HWND_OPT(hWnd);
HRESULT hr = S_OK;
// Further validate, checking for a pointer to a bad interface pointer...
if (ppDirectMusic)
{
V_INTERFACE_OPT(*ppDirectMusic);
}
if( m_dwAudioPathMode )
{
Trace(1,"Error: Init called on an already initialized Performance.\n");
return DMUS_E_ALREADY_INITED;
}
Init();
m_dwAudioPathMode = 1;
EnterCriticalSection(&m_MainCrSec);
if(( NULL == ppDirectMusic ) || ( NULL == *ppDirectMusic ))
{
// intialize DirectMusic.
if( FAILED( CoCreateInstance(CLSID_DirectMusic,
NULL,
CLSCTX_INPROC,
IID_IDirectMusic,
(LPVOID*)&m_pDirectMusic)))
{
m_pDirectMusic = NULL;
LeaveCriticalSection(&m_MainCrSec);
return E_OUTOFMEMORY;
}
// If version2 was requested by the app (in the process of requesting the
// IDirectMusicPerformance2 interface), do the same for IDirectMusic.
if (m_dwVersion > 6)
{
IDirectMusic *pTemp = NULL;
if (SUCCEEDED(m_pDirectMusic->QueryInterface(
IID_IDirectMusic2,
(LPVOID*)&pTemp)))
{
// Succeeded in requesting DX7 and up behavior...
pTemp->Release();
}
}
hr = m_pDirectMusic->SetDirectSound(pDirectSound, hWnd);
if( FAILED( hr ) )
{
m_pDirectMusic->Release();
m_pDirectMusic = NULL;
LeaveCriticalSection(&m_MainCrSec);
return hr;
}
if( ppDirectMusic )
{
*ppDirectMusic = m_pDirectMusic;
m_pDirectMusic->AddRef();
}
}
else
{
m_pDirectMusic = (IDirectMusic8 *) *ppDirectMusic;
m_pDirectMusic->AddRef();
}
if (FAILED(hr = CreateThreads()))
{
if( m_pDirectMusic )
{
m_pDirectMusic->Release();
m_pDirectMusic = NULL;
}
}
LeaveCriticalSection(&m_MainCrSec);
return hr;
}
CSegState *CPerformance::GetSegmentForTransition(DWORD dwFlags,MUSIC_TIME mtTime, IUnknown *pFrom)
{
CSegState *pSegState = NULL;
// If the source segment was provided, use it.
if (pFrom)
{
if (SUCCEEDED(pFrom->QueryInterface(IID_CSegState,(void **) &pSegState)))
{
pSegState->Release();
}
}
// Else, if this is a primary segment, get the current primary segment.
if (!pSegState && !(dwFlags & DMUS_SEGF_SECONDARY))
{
pSegState = GetPrimarySegmentAtTime(mtTime);
}
return pSegState;
}
void CPerformance::ClearMusicStoppedNotification()
{
EnterCriticalSection(&m_PipelineCrSec);
PRIV_PMSG* pPMsg;
PRIV_PMSG* pNext;
DMUS_NOTIFICATION_PMSG* pNotification;
pPMsg = m_OnTimeQueue.GetHead(); // where notifications live normally
for (; pPMsg ; pPMsg = pNext)
{
pNext = pPMsg->pNext;
pNotification = (DMUS_NOTIFICATION_PMSG*)PRIV_TO_DMUS(pPMsg);
if( ( pPMsg->dwType == DMUS_PMSGT_NOTIFICATION ) &&
( pNotification->guidNotificationType == GUID_NOTIFICATION_PERFORMANCE ) &&
( pNotification->dwNotificationOption == DMUS_NOTIFICATION_MUSICSTOPPED ) )
{
pPMsg = m_OnTimeQueue.Dequeue(pPMsg);
if( pPMsg ) // Should always succeeed
{
FreePMsg(pPMsg);
}
m_fMusicStopped = FALSE;
}
}
LeaveCriticalSection(&m_PipelineCrSec);
}
HRESULT CPerformance::PlayOneSegment(
CSegment* pSegment,
DWORD dwFlags,
__int64 i64StartTime,
CSegState **ppSegState,
CAudioPath *pAudioPath)
{
HRESULT hr;
#ifdef DBG_PROFILE
DWORD dwDebugTime;
dwDebugTime = timeGetTime();
#endif
TraceI(0,"Play Segment %lx (%ls) at time %ld with flags %lx\n",pSegment,pSegment->m_wszName,(long)i64StartTime,dwFlags);
if( dwFlags & DMUS_SEGF_CONTROL )
{
dwFlags |= DMUS_SEGF_SECONDARY;
}
if( i64StartTime )
{
if(dwFlags & DMUS_SEGF_REFTIME)
{
// Give a grace period of 100ms.
if( i64StartTime < (GetLatency() - (100 * REF_PER_MIL)))
{
Trace(1,"Error: Unable to play segment, requested clock time %ld is past current time %ld\n",
(long)i64StartTime,(long)(GetLatency() - (100 * REF_PER_MIL)));
return DMUS_E_TIME_PAST;
}
}
else
{
MUSIC_TIME mtPrePlay;
// Give a grace period of 100ms.
ReferenceToMusicTime( (GetLatency() - (100 * REF_PER_MIL)), &mtPrePlay );
if( (MUSIC_TIME)i64StartTime < mtPrePlay )
{
Trace(1,"Error: Unable to play segment, requested music time %ld is past current time %ld\n",
(long)i64StartTime,(long)mtPrePlay);
return DMUS_E_TIME_PAST;
}
}
}
CSegState *pSegState = NULL;
hr = pSegment->CreateSegmentState( &pSegState, this, pAudioPath, dwFlags);
*ppSegState = pSegState;
if (FAILED(hr))
{
Trace(1,"Error: Unable to play segment because of failure creating segment state.\n");
return DMUS_E_SEGMENT_INIT_FAILED;
}
pSegState->m_rtGivenStart = i64StartTime;
pSegState->m_dwPlaySegFlags = dwFlags;
// add the pSegState to the appropriate queue
EnterCriticalSection(&m_SegmentCrSec);
m_fPlaying = 1; // turn on the transport
// add all notifications to the segment. First, clear it, in case old notifications
// are in effect.
pSegment->RemoveNotificationType(GUID_NULL,TRUE);
CNotificationItem* pItem;
pItem = m_NotificationList.GetHead();
while( pItem )
{
pSegment->AddNotificationType( pItem->guidNotificationType, TRUE );
pItem = pItem->GetNext();
}
if( pSegState->m_dwPlaySegFlags & DMUS_SEGF_AFTERPREPARETIME )
{
// we want to queue this at the last transported time,
// so we don't need to do an invalidate
if( pSegState->m_dwPlaySegFlags & DMUS_SEGF_REFTIME )
{
REFERENCE_TIME rtTrans;
MusicToReferenceTime( m_mtTransported, &rtTrans );
if( pSegState->m_rtGivenStart < rtTrans )
{
pSegState->m_dwPlaySegFlags &= ~DMUS_SEGF_REFTIME;
pSegState->m_rtGivenStart = m_mtTransported;
}
}
else
{
if( pSegState->m_rtGivenStart < m_mtTransported )
{
pSegState->m_rtGivenStart = m_mtTransported;
}
}
}
else if( pSegState->m_dwPlaySegFlags & DMUS_SEGF_AFTERQUEUETIME )
{
// we want to queue this at the queue time, as opposed to latency time,
// which is an option for secondary segments.
REFERENCE_TIME rtStart;
GetQueueTime( &rtStart ); // need queue time because control segments cause invalidations
if( pSegState->m_dwPlaySegFlags & DMUS_SEGF_REFTIME )
{
if( pSegState->m_rtGivenStart < rtStart )
{
pSegState->m_rtGivenStart = rtStart;
}
}
else
{
MUSIC_TIME mtStart;
ReferenceToMusicTime( rtStart, &mtStart );
if( pSegState->m_rtGivenStart < mtStart )
{
pSegState->m_rtGivenStart = mtStart;
}
}
}
// need to get rid of any pending musicstopped notifications
ClearMusicStoppedNotification();
pSegState->AddRef();
if( dwFlags & DMUS_SEGF_SECONDARY ) // queue a secondary segment
{
QueueSecondarySegment( pSegState );
}
else // queue a primary segment
{
QueuePrimarySegment( pSegState );
}
LeaveCriticalSection(&m_SegmentCrSec);
#ifdef DBG_PROFILE
dwDebugTime = timeGetTime() - dwDebugTime;
TraceI(5, "perf, debugtime PlaySegment %u\n", dwDebugTime);
#endif
// signal the transport thread so we don't have to wait for it to wake up on its own
if( m_hTransport ) SetEvent( m_hTransport );
return S_OK;
}
HRESULT CPerformance::PlaySegmentInternal(
CSegment* pSegment,
CSong * pSong,
WCHAR *pwzSegmentName,
CSegment* pTransition,
DWORD dwFlags,
__int64 i64StartTime,
IDirectMusicSegmentState** ppSegmentState,
IUnknown *pFrom,
CAudioPath *pAudioPath)
{
HRESULT hr;
CAudioPath *pInternalPath = NULL;
if( m_pClock == NULL )
{
Trace(1,"Error: Can not play segment because master clock has not been initialized.\n");
return DMUS_E_NO_MASTER_CLOCK;
}
if (pAudioPath && (pAudioPath->NoPorts()))
{
// This audiopath can't be used for playback since it doesn't have any ports.
Trace(1,"Error: Audiopath can't be used for playback because it doesn't have any ports.\n");
return DMUS_E_AUDIOPATH_NOPORT;
}
// Pointer to segment or song provided audio path config.
IUnknown *pConfig = NULL;
/* If this is a song, use the segment name to get the segment.
Then, it looks like a normal segment except the
existence of the pSong will let the segstate know
that it is a member of a song, so it should chain segments.
*/
if (pSong)
{
IDirectMusicSegment *pISegment = NULL;
hr = pSong->GetSegment(pwzSegmentName,&pISegment);
if (hr != S_OK)
{
return DMUS_E_NOT_FOUND;
}
pSegment = (CSegment *) pISegment;
// If the app wants an audiopath created dynamically from the song, find it and use it.
if (dwFlags & DMUS_SEGF_USE_AUDIOPATH)
{
pSong->GetAudioPathConfig(&pConfig);
}
}
else if (pSegment)
{
// Addref so we can release later.
pSegment->AddRef();
}
else
{
// No Segment!
Trace(1,"Error: No segment - nothing to play!\n");
return E_FAIL;
}
if (dwFlags & DMUS_SEGF_DEFAULT )
{
DWORD dwResTemp;
pSegment->GetDefaultResolution( &dwResTemp );
dwFlags &= ~DMUS_SEGF_DEFAULT;
dwFlags |= dwResTemp;
}
// If the app wants an audiopath created dynamically from the segment, find it and use it.
// Note that this overrides an audiopath created from the song.
if (dwFlags & DMUS_SEGF_USE_AUDIOPATH)
{
IUnknown *pSegConfig;
if (SUCCEEDED(pSegment->GetAudioPathConfig(&pSegConfig)))
{
if (pConfig)
{
pConfig->Release();
}
pConfig = pSegConfig;
}
}
// If we got an audiopath config from the segment or song, use it.
if (pConfig)
{
IDirectMusicAudioPath *pNewPath;
if (SUCCEEDED(CreateAudioPath(pConfig,TRUE,&pNewPath)))
{
// Now, get the CAudioPath structure.
if (SUCCEEDED(pNewPath->QueryInterface(IID_CAudioPath,(void **) &pInternalPath)))
{
pAudioPath = pInternalPath;
}
pNewPath->Release();
}
else
{
pConfig->Release();
Trace(1,"Error: Embedded audiopath failed to create, segment will not play.\n");
return DMUS_E_NO_AUDIOPATH;
}
pConfig->Release();
}
if (pTransition)
{
pTransition->AddRef();
}
if ((dwFlags & DMUS_SEGF_SECONDARY) && (dwFlags & DMUS_SEGF_QUEUE))
{
// Can only queue if there's a segment to queue after.
if (pFrom)
{
CSegState *pSegFrom = NULL;
if (SUCCEEDED(pFrom->QueryInterface(IID_CSegState,(void **) &pSegFrom)))
{
// Calculate the time at which the preceding segment will stop.
MUSIC_TIME mtStartTime = pSegFrom->GetEndTime( pSegFrom->m_mtResolvedStart );
i64StartTime = mtStartTime;
dwFlags &= ~DMUS_SEGF_REFTIME;
pSegFrom->Release();
}
}
}
// If auto-transition is requested,
// get the transition template, if it exists,
// and compose a segment with it.
CSegment *pPlayAfter = NULL; // This will hold the second segment, if we end up with a transition.
DWORD dwFlagsAfter = dwFlags & (DMUS_SEGF_SECONDARY | DMUS_SEGF_CONTROL);
if ( dwFlags & DMUS_SEGF_AUTOTRANSITION )
{
// First, calculate the time to start the transition.
// Note: this will be done again later. We really need to fold this all together.
REFERENCE_TIME rtTime;
if (i64StartTime == 0)
{
GetQueueTime( &rtTime );
}
else if (dwFlags & DMUS_SEGF_REFTIME)
{
rtTime = i64StartTime;
}
else
{
MusicToReferenceTime((MUSIC_TIME) i64StartTime,&rtTime);
}
REFERENCE_TIME rtResolved;
GetResolvedTime(rtTime, &rtResolved,dwFlags);
MUSIC_TIME mtTime; // Actual time to start transition.
ReferenceToMusicTime(rtResolved,&mtTime);
CSegment *pPriorSeg = NULL;
// Find the segment that is active at transition time.
CSegState *pPriorState = GetSegmentForTransition(dwFlags,mtTime,pFrom);
if (pPriorState)
{
pPriorSeg = pPriorState->m_pSegment;
}
// If this is a song, use the id to get the transition.
if (pSong && !pTransition)
{
DMUS_IO_TRANSITION_DEF Transition;
// Now, find out what sort of transition is expected.
if (SUCCEEDED(pSong->GetTransitionSegment(pPriorSeg,pSegment,&Transition)))
{
if (Transition.dwTransitionID != DMUS_SONG_NOSEG)
{
if (S_OK == pSong->GetPlaySegment(Transition.dwTransitionID,&pTransition))
{
dwFlags = Transition.dwPlayFlags;
}
}
else
{
dwFlags = Transition.dwPlayFlags;
}
}
}
if (pTransition)
{
IDirectMusicSegment *pITransSegment = NULL;
if (pPriorState)
{
pTransition->Compose(mtTime - pPriorState->m_mtOffset, pPriorSeg, pSegment, &pITransSegment);
}
else
{
pTransition->Compose(0,pPriorSeg,pSegment,&pITransSegment);
}
// Now, if we successfully composed a transition segment, set it up to be the one we
// will play first. Later, we fill call PlaySegment() with pPlayAfter, to queue it
// to play after the transition.
if (pITransSegment)
{
pPlayAfter = pSegment;
pSegment = (CSegment *) pITransSegment;
}
}
}
if (pSegment)
{
CSegState *pSegState;
if (!pAudioPath)
{
pAudioPath = m_pDefaultAudioPath;
}
if (pAudioPath && !pAudioPath->IsActive())
{
Trace(1,"Error: Can not play segment on inactive audiopath\n");
hr = DMUS_E_AUDIOPATH_INACTIVE;
}
else if ((m_dwAudioPathMode != 1) && !pAudioPath)
{
Trace(1,"Error: No audiopath to play segment on.\n");
hr = DMUS_E_NO_AUDIOPATH;
}
else
{
if (ppSegmentState)
{
*ppSegmentState = NULL;
}
hr = PlayOneSegment(
pSegment,
dwFlags,
i64StartTime,
&pSegState,
pAudioPath);
if (SUCCEEDED(hr))
{
if (pFrom)
{
pSegState->m_fCanStop = FALSE;
StopEx(pFrom, pSegState->m_mtResolvedStart, 0);
pSegState->m_fCanStop = TRUE;
}
// If this was actually a transition segment, now we need to play the original segment!
if (pPlayAfter)
{
MUSIC_TIME mtStartTime = pSegState->GetEndTime(pSegState->m_mtResolvedStart );
pSegState->Release();
hr = PlayOneSegment(pPlayAfter,dwFlagsAfter,mtStartTime,&pSegState,pAudioPath);
}
if (SUCCEEDED(hr))
{
if (pSong)
{
pSegState->m_fSongMode = TRUE;
}
if (ppSegmentState)
{
*ppSegmentState = pSegState;
}
else
{
pSegState->Release();
}
}
}
}
}
else
{
// There never was a segment to play, not even a transition.
Trace(1,"Error: No segment to play.\n");
hr = E_INVALIDARG;
}
// Before leaving, reduce the reference counts on variables that have been addref'd.
if (pSegment)
{
pSegment->Release();
}
if (pTransition)
{
pTransition->Release();
}
if (pPlayAfter)
{
pPlayAfter->Release();
}
if (pInternalPath)
{
pInternalPath->Release();
}
return hr;
}
HRESULT STDMETHODCALLTYPE CPerformance::PlaySegment(
IDirectMusicSegment *pSegment,
DWORD dwFlags,
__int64 i64StartTime,
IDirectMusicSegmentState **ppSegmentState)
{
V_INAME(IDirectMusicPerformance::PlaySegment);
V_INTERFACE(pSegment);
V_PTRPTR_WRITE_OPT(ppSegmentState);
CSegment *pCSourceSegment = NULL;
if (SUCCEEDED(pSegment->QueryInterface(IID_CSegment,(void **) &pCSourceSegment)))
{
pCSourceSegment->Release();
}
else
{
Trace(1,"Error: Invalid segment object passed to PlaySegment(). Segment must be created using CLSID_DirectMusicSegment object.\n");
return E_POINTER;
}
return PlaySegmentInternal(pCSourceSegment,NULL,0,NULL,dwFlags,i64StartTime,ppSegmentState,NULL,NULL);
}
HRESULT STDMETHODCALLTYPE CPerformance::PlaySegmentEx(
IUnknown* pSource,
WCHAR *pwzSegmentName,
IUnknown* pTransition,
DWORD dwFlags,
__int64 i64StartTime,
IDirectMusicSegmentState** ppSegmentState,
IUnknown *pFrom,
IUnknown *pAudioPath)
{
V_INAME(IDirectMusicPerformance::PlaySegmentEx);
V_INTERFACE_OPT(pSource);
V_INTERFACE_OPT(pTransition);
V_PTRPTR_WRITE_OPT(ppSegmentState);
V_INTERFACE_OPT(pFrom);
V_INTERFACE_OPT(pAudioPath);
CSegment *pCSourceSegment = NULL;
CSong *pCSourceSong = NULL;
CSegment *pCTransition = NULL;
CAudioPath *pCAudioPath = NULL;
// TraceI(0,"Playing %lx at time %ld, flags %lx, Transition %lx\n",pSource,(long)i64StartTime,dwFlags,pTransition);
// We may not have a source segment in the special case of transitioning from NULL.
if (!pSource && !pTransition)
{
Trace(1,"Error: Must pass either a segment or transition segment to PlaySegmentEx()\n");
return E_POINTER;
}
if (pSource)
{
// Figure out if we have a source song or segment and get the internal representations.
if (SUCCEEDED(pSource->QueryInterface(IID_CSegment,(void **) &pCSourceSegment)))
{
pCSourceSegment->Release();
}
else if (SUCCEEDED(pSource->QueryInterface(IID_CSong,(void **) &pCSourceSong)))
{
pCSourceSong->Release();
}
else
{
Trace(1,"Error: Invalid segment or song passed to PlaySegmentEx().\n");
return E_POINTER;
}
}
// If we have a transition segment, get the CSegment representation.
if (pTransition)
{
if (SUCCEEDED(pTransition->QueryInterface(IID_CSegment,(void **) &pCTransition)))
{
pCTransition->Release();
}
else
{
Trace(1,"Error: Invalid transition passed to PlaySegmentEx().\n");
return E_POINTER;
}
}
if (pAudioPath)
{
if (SUCCEEDED(pAudioPath->QueryInterface(IID_CAudioPath,(void **) &pCAudioPath)))
{
pCAudioPath->Release();
}
else
{
Trace(1,"Error: Invalid audiopath passed to PlaySegmentEx().\n");
return E_POINTER;
}
}
return PlaySegmentInternal(pCSourceSegment,pCSourceSong,pwzSegmentName,
pCTransition,dwFlags,i64StartTime,
ppSegmentState,pFrom,
pCAudioPath);
}
STDMETHODIMP CPerformance::SetDefaultAudioPath(IDirectMusicAudioPath *pAudioPath)
{
V_INAME(IDirectMusicPerformance::SetDefaultAudioPath);
V_INTERFACE_OPT(pAudioPath);
if (m_dwAudioPathMode == 0)
{
Trace(1,"Error: Performance not initialized.\n");
return DMUS_E_NOT_INIT;
}
if (m_dwAudioPathMode == 1)
{
Trace(1,"Error: Performance initialized not to support Audiopaths.\n");
return DMUS_E_AUDIOPATHS_NOT_VALID;
}
CAudioPath *pCPath = NULL;
if (pAudioPath)
{
if (SUCCEEDED(pAudioPath->QueryInterface(IID_CAudioPath,(void **) &pCPath)))
{
pCPath->Release();
if (!m_AudioPathList.IsMember(pCPath))
{
// This is not a legal audiopath, since it wasn't created by this performance.
Trace(1,"Error: Invalid audiopath - not created by this Performance.\n");
return E_INVALIDARG;
}
if (pCPath->NoPorts())
{
// This is an audiopath that doesn't have any port configurations.
// For example, it might be environmental reverb.
Trace(1,"Error: Failure setting default audiopath - does not have any ports, so can not be played on.\n");
return DMUS_E_AUDIOPATH_NOPORT;
}
}
else
{
// This is not a legal audiopath object at all.
Trace(1,"Error: Invalid audiopath - not created by call to Performance->CreateAudioPath().\n");
return E_INVALIDARG;
}
}
if (m_pDefaultAudioPath)
{
m_pDefaultAudioPath->Release();
m_pDefaultAudioPath = NULL;
}
m_pDefaultAudioPath = pCPath;
if (pCPath)
{
pCPath->AddRef();
pCPath->Activate(TRUE);
}
return S_OK;
}
STDMETHODIMP CPerformance::GetDefaultAudioPath(IDirectMusicAudioPath **ppAudioPath)
{
V_INAME(IDirectMusicPerformance::GetDefaultAudioPath);
V_PTRPTR_WRITE(ppAudioPath);
if (m_dwAudioPathMode == 0)
{
Trace(1,"Error: Performance not initialized.\n");
return DMUS_E_NOT_INIT;
}
if (m_dwAudioPathMode == 1)
{
Trace(1,"Error: Performance was initialized not to support audiopaths.\n");
return DMUS_E_AUDIOPATHS_NOT_VALID;
}
if (m_pDefaultAudioPath)
{
*ppAudioPath = (IDirectMusicAudioPath *) m_pDefaultAudioPath;
m_pDefaultAudioPath->AddRef();
return S_OK;
}
Trace(3,"Warning: No default audiopath\n");
return DMUS_E_NOT_FOUND;
}
HRESULT STDMETHODCALLTYPE CPerformance::CreateAudioPath( IUnknown *pSourceConfig,
BOOL fActivate,
IDirectMusicAudioPath **ppNewPath)
{
V_INAME(IDirectMusicPerformance::CreateAudioPath);
V_INTERFACE(pSourceConfig);
V_PTRPTR_WRITE_OPT(ppNewPath);
if (m_dwAudioPathMode == 0)
{
Trace(1,"Error: Performance not initialized.\n");
return DMUS_E_NOT_INIT;
}
if (m_dwAudioPathMode == 1)
{
Trace(1,"Error: Performance not initialized to support audiopaths (must use InitAudio.)\n");
return DMUS_E_AUDIOPATHS_NOT_VALID;
}
HRESULT hr = E_OUTOFMEMORY;
CAudioPath *pPath = new CAudioPath;
if (pPath)
{
hr = pPath->Init(pSourceConfig,this);
if (SUCCEEDED(hr) && fActivate)
{
hr = pPath->Activate(TRUE);
#ifdef DBG
if (FAILED(hr))
{
Trace(1,"Error: Audiopath creation failed because one or more buffers could not be activated.\n");
}
#endif
}
if (SUCCEEDED(hr))
{
hr = pPath->QueryInterface(IID_IDirectMusicAudioPath,(void **) ppNewPath);
}
else
{
delete pPath;
}
}
return hr;
}
STDMETHODIMP CPerformance::CreateStandardAudioPath(DWORD dwType,
DWORD dwPChannelCount,
BOOL fActivate,
IDirectMusicAudioPath **ppNewPath)
{
V_INAME(IDirectMusicPerformance::CreateStandardAudioPath);
V_PTRPTR_WRITE_OPT(ppNewPath);
HRESULT hr = S_OK;
if (m_dwAudioPathMode == 2)
{
if ((dwType <= DMUS_APATH_DYNAMIC_STEREO) && (dwType >= DMUS_APATH_DYNAMIC_3D)
|| (dwType == DMUS_APATH_SHARED_STEREOPLUSREVERB))
{
if (!(m_AudioParams.dwFeatures & DMUS_AUDIOF_BUFFERS))
{
Trace(4,"Warning: Creating a standard audiopath without buffers - InitAudio specified no buffer support.\n");
// If the default synth doesn't support buffers, then create a simple port with no buffers.
dwType = 0;
}
CAudioPathConfig *pConfig = CAudioPathConfig::CreateStandardConfig(dwType,dwPChannelCount,m_AudioParams.dwSampleRate);
if (pConfig)
{
hr = CreateAudioPath((IPersistStream *) pConfig,fActivate,ppNewPath);
pConfig->Release();
}
else
{
// CreateStandardConfig only returns NULL if we've run out of memory.
hr = E_OUTOFMEMORY;
}
}
else
{
Trace(1,"Error: %ld is not a valid predefined audiopath.\n",dwType);
hr = E_INVALIDARG;
}
}
else
{
Trace(1,"Error: Performance not initialized to support audiopaths.\n");
hr = DMUS_E_AUDIOPATHS_NOT_VALID;
}
return hr;
}
// Stop the segment state at mtTime. If NULL, stop all.
void CPerformance::DoStop( CSegState* pSegState, MUSIC_TIME mtTime,
BOOL fInvalidate)
{
HRESULT hrAbort = S_OK;
DWORD dwCount;
if( NULL == pSegState ) return;
EnterCriticalSection(&m_SegmentCrSec);
CSegStateList *pSourceList = NULL;
CSegStateList *pDestList = NULL;
CSegState *pNode = NULL;
// Mark the length of the segstate to be only as far as it played
// to keep GetParam() from accessing the unplayed portion.
if (pSegState)
{
if (mtTime < pSegState->m_mtEndTime)
{
pSegState->m_mtLength = mtTime - pSegState->m_mtResolvedStart +
pSegState->m_mtStartPoint;
if (pSegState->m_mtLength < 0)
{
pSegState->m_mtLength = 0;
}
// Make endtime one greater than mtTime so Abort notification will still happen.
pSegState->m_mtEndTime = mtTime + 1;
}
}
RecalcTempoMap(pSegState,mtTime);
// check each play queue
for (dwCount = SQ_PRI_PLAY; dwCount <= SQ_SEC_PLAY; dwCount++)
{
for( pNode = m_SegStateQueues[dwCount].GetHead(); pNode; pNode = pNode->GetNext())
{
if( pNode == pSegState )
{
// we want to move this to the approprate done queue
pDestList = &m_SegStateQueues[SQ_PRI_DONE - SQ_PRI_PLAY + dwCount];
pSourceList = &m_SegStateQueues[dwCount];
if ((dwCount == SQ_PRI_PLAY) && (m_SegStateQueues[SQ_PRI_PLAY].GetCount() == 1))
{
if (m_dwVersion >= 8)
{
MUSIC_TIME mtNow;
GetTime( NULL, &mtNow );
GenerateNotification( DMUS_NOTIFICATION_MUSICALMOSTEND, mtNow, pSegState );
}
}
dwCount = SQ_SEC_PLAY; // Force out of outer loop.
break;
}
}
}
if (!pNode)
{
// check each done queue
for (dwCount = SQ_PRI_DONE; dwCount <= SQ_SEC_DONE; dwCount++)
{
for( pNode = m_SegStateQueues[dwCount].GetHead(); pNode; pNode = pNode->GetNext())
{
if( pNode == pSegState )
{
pSourceList = &m_SegStateQueues[dwCount];
dwCount = SQ_SEC_DONE; // Force out of outer loop.
break;
}
}
}
}
if( pNode && pSourceList)
{
REFERENCE_TIME rtTime;
MusicToReferenceTime(mtTime,&rtTime);
if( pNode->m_mtLastPlayed >= mtTime )
{
pNode->Flush( mtTime );
pNode->m_mtLastPlayed = mtTime; // must set this to indicate it only played until then
pNode->m_rtLastPlayed = rtTime;
}
if( fInvalidate )
{
if( pNode->m_dwPlaySegFlags & DMUS_SEGF_CONTROL )
{
Invalidate( mtTime, 0 ); // must call Invalidate before AbortPlay so we don't
// invalidate the abort notification
}
else if ( !(pNode->m_dwPlaySegFlags & DMUS_SEGF_SECONDARY ))
{
// If this is a primary segment, kill the tempo map.
FlushEventQueue( 0, &m_TempoMap, rtTime, rtTime, FALSE );
}
}
hrAbort = pNode->AbortPlay( mtTime, FALSE );
if( pNode->m_dwPlaySegFlags & DMUS_SEGF_CONTROL )
{
pSourceList->Remove(pNode);
m_ShutDownQueue.Insert(pNode); // we're guaranteed to never need this again
// set dirty flags on all other segments
for (dwCount = SQ_PRI_PLAY; dwCount <= SQ_SEC_PLAY; dwCount++)
{
for( pNode = m_SegStateQueues[dwCount].GetHead(); pNode; pNode = pNode->GetNext() )
{
if( pNode->m_fStartedPlay )
{
pNode->m_dwPlayTrackFlags |= DMUS_TRACKF_DIRTY;
}
}
}
}
else if( pDestList )
{
pSourceList->Remove(pNode);
pDestList->Insert(pNode);
}
}
else
{
// check the wait lists.
for (dwCount = SQ_PRI_WAIT; dwCount <= SQ_SEC_WAIT; dwCount++)
{
for( pNode = m_SegStateQueues[dwCount].GetHead(); pNode; pNode = pNode->GetNext() )
{
if( pNode == pSegState )
{
hrAbort = pNode->AbortPlay( mtTime, FALSE );
m_SegStateQueues[dwCount].Remove(pNode);
RecalcTempoMap(pNode, mtTime);
m_ShutDownQueue.Insert(pNode);
break;
}
}
}
}
// if there aren't any more segments to play, send a Music Stopped
// notification
if( m_SegStateQueues[SQ_PRI_PLAY].IsEmpty() && m_SegStateQueues[SQ_SEC_PLAY].IsEmpty() &&
m_SegStateQueues[SQ_PRI_WAIT].IsEmpty() && m_SegStateQueues[SQ_SEC_WAIT].IsEmpty() &&
m_SegStateQueues[SQ_CON_PLAY].IsEmpty() && m_SegStateQueues[SQ_CON_WAIT].IsEmpty())
{
m_fMusicStopped = TRUE;
// S_FALSE means we tried to abort this segstate, but it's already been aborted
if (hrAbort != S_FALSE)
{
GenerateNotification( DMUS_NOTIFICATION_MUSICSTOPPED, mtTime, NULL );
}
}
LeaveCriticalSection(&m_SegmentCrSec);
}
// Stop all segment states based off of the segment.
void CPerformance::DoStop( CSegment* pSeg, MUSIC_TIME mtTime, BOOL fInvalidate )
{
DWORD dwCount;
CSegState* pNode;
CSegState* pNext;
EnterCriticalSection(&m_SegmentCrSec);
// find all seg pSegStates based off this segment that have played through time mtTime
// if pSeg is NULL, go through all of the segment lists. Flush any
// segment that played through time mtTime. Move any active segments
// into past lists.
if( pSeg )
{
for (dwCount = 0; dwCount < SQ_COUNT; dwCount++)
{
pNode = m_SegStateQueues[dwCount].GetHead();
while( pNode )
{
pNext = pNode->GetNext();
if( pNode->m_pSegment == pSeg )
{
if (IsDoneQueue(dwCount))
{
if (pNode->m_mtLastPlayed >= mtTime)
{
DoStop( pNode, mtTime, fInvalidate );
}
}
else
{
DoStop( pNode, mtTime, fInvalidate );
}
}
pNode = pNext;
}
}
}
else // pSeg is NULL, stop everything.
{
// go ahead and flush the event queues
EnterCriticalSection(&m_PipelineCrSec);
FlushMainEventQueues( 0, mtTime, mtTime, FALSE );
LeaveCriticalSection(&m_PipelineCrSec);
// clear out the wait lists
for (dwCount = SQ_PRI_WAIT; dwCount <= SQ_SEC_WAIT; dwCount++)
{
while (pNode = m_SegStateQueues[dwCount].GetHead())
{
pNode->AbortPlay( mtTime, FALSE );
m_SegStateQueues[dwCount].RemoveHead();
m_ShutDownQueue.Insert(pNode);
}
}
// stop any segment that is currently playing.
for (dwCount = SQ_PRI_DONE; dwCount <= SQ_SEC_DONE; dwCount++)
{
for( pNode = m_SegStateQueues[dwCount].GetHead(); pNode; pNode = pNode->GetNext() )
{
if( pNode->m_mtLastPlayed >= mtTime )
{
DoStop( pNode, mtTime, fInvalidate );
}
}
}
for (dwCount = SQ_PRI_PLAY; dwCount <= SQ_SEC_PLAY; dwCount++)
{
while( m_SegStateQueues[dwCount].GetHead() )
{
DoStop( m_SegStateQueues[dwCount].GetHead(), mtTime, fInvalidate );
}
}
// reset controllers and force all notes off.
ResetAllControllers( GetLatency() );
}
LeaveCriticalSection(&m_SegmentCrSec);
}
STDMETHODIMP CPerformance::StopEx(IUnknown *pObjectToStop,__int64 i64StopTime,DWORD dwFlags)
{
V_INAME(IDirectMusicPerformance::StopEx);
V_INTERFACE_OPT(pObjectToStop);
HRESULT hr = E_INVALIDARG;
IDirectMusicSegmentState *pState;
IDirectMusicSegment *pSegment;
CSong *pSong;
CAudioPath *pAudioPath;
if (m_dwAudioPathMode == 0)
{
Trace(1,"Error: Performance not initialized.\n");
return DMUS_E_NOT_INIT;
}
TraceI(0,"StopExing %lx at time %ld, flags %lx\n",pObjectToStop,(long)i64StopTime,dwFlags);
if (pObjectToStop == NULL)
{
return Stop(NULL,NULL,(MUSIC_TIME)i64StopTime,dwFlags);
}
if (dwFlags & DMUS_SEGF_AUTOTRANSITION)
{
// I this is an autotransition, it will only work if the currently playing segment in question
// is a member of a song. So, check the segstate, segment, song, and audiopath
// to find the segstate. And, if found, see if it is part of a song. If so,
// then go ahead and do the transition.
EnterCriticalSection(&m_SegmentCrSec);
BOOL fTransition = FALSE;
dwFlags &= ~DMUS_SEGF_AUTOTRANSITION;
CSegState *pCState = NULL;
// First, see if this is a segstate.
HRESULT hrTemp = pObjectToStop->QueryInterface(IID_CSegState,(void **)&pCState);
if (FAILED(hrTemp))
{
// Segstate failed. Is this a Song? If so, find the first correlating segstate.
CSong *pCSong = NULL;
CAudioPath *pCAudioPath = NULL;
CSegment *pCSegment = NULL;
hrTemp = pObjectToStop->QueryInterface(IID_CSong,(void **)&pCSong);
if (FAILED(hrTemp))
{
hrTemp = pObjectToStop->QueryInterface(IID_CSegment,(void **)&pCSegment);
}
if (FAILED(hrTemp))
{
hrTemp = pObjectToStop->QueryInterface(IID_CAudioPath,(void **)&pCAudioPath);
}
if (SUCCEEDED(hrTemp))
{
CSegState *pNode;
DWORD dwCount;
for (dwCount = SQ_PRI_WAIT; dwCount <= SQ_SEC_DONE; dwCount++)
{
for( pNode = m_SegStateQueues[dwCount].GetHead(); pNode; pNode = pNode->GetNext() )
{
if (pNode->m_fCanStop)
{
// Can only do this if the segstate ultimately points to a song.
if (pNode->m_pSegment && pNode->m_pSegment->m_pSong)
{
if ((pNode->m_pSegment == pCSegment) ||
(pNode->m_pSegment->m_pSong == pCSong) ||
(pCAudioPath && (pNode->m_pAudioPath == pCAudioPath)))
{
pCState = pNode;
pCState->AddRef();
break;
}
}
}
}
if (pCState) break;
}
}
if (pCSong) pCSong->Release();
else if (pCAudioPath) pCAudioPath->Release();
else if (pCSegment) pCSegment->Release();
}
if (pCState)
{
CSegment *pPriorSeg = pCState->m_pSegment;
if (pPriorSeg)
{
pSong = pPriorSeg->m_pSong;
if (pSong)
{
// If this is an autotransition, compose a transition segment from the
// current position in the song and play it.
// This will, in turn, call stop on the song, so we don't need to do it here.
// First, calculate the time to start the transition.
REFERENCE_TIME rtTime;
if (i64StopTime == 0)
{
GetQueueTime( &rtTime );
}
else if (dwFlags & DMUS_SEGF_REFTIME)
{
rtTime = i64StopTime;
}
else
{
MusicToReferenceTime((MUSIC_TIME) i64StopTime,&rtTime);
}
REFERENCE_TIME rtResolved;
GetResolvedTime(rtTime, &rtResolved,dwFlags);
MUSIC_TIME mtTime; // Actual time to start transition.
ReferenceToMusicTime(rtResolved,&mtTime);
CSegment *pTransition = NULL;
// Now, get the transition.
DMUS_IO_TRANSITION_DEF Transition;
if (SUCCEEDED(pSong->GetTransitionSegment(pPriorSeg,NULL,&Transition)))
{
if (Transition.dwTransitionID != DMUS_SONG_NOSEG)
{
if (S_OK == pSong->GetPlaySegment(Transition.dwTransitionID,&pTransition))
{
dwFlags = Transition.dwPlayFlags;
}
}
}
if (pTransition)
{
IDirectMusicSegment *pITransSegment = NULL;
pTransition->Compose(mtTime - pCState->m_mtOffset, pPriorSeg, NULL, &pITransSegment);
// Now, if we successfully composed a transition segment, set it up to be the one we
// will play first. Later, we fill call PlaySegment() with pPlayAfter, to queue it
// to play after the transition.
if (pITransSegment)
{
hr = PlaySegmentEx(pITransSegment,NULL,NULL,dwFlags,i64StopTime,NULL,(IDirectMusicSegmentState *)pCState,NULL);
pITransSegment->Release();
fTransition = TRUE;
}
pTransition->Release();
}
}
}
pCState->Release();
}
LeaveCriticalSection(&m_SegmentCrSec);
if (fTransition)
{
return hr;
}
}
if (SUCCEEDED(pObjectToStop->QueryInterface(IID_IDirectMusicSegmentState,(void **) &pState)))
{
hr = Stop(NULL,pState,(MUSIC_TIME)i64StopTime,dwFlags);
pState->Release();
}
else if (SUCCEEDED(pObjectToStop->QueryInterface(IID_IDirectMusicSegment,(void **) &pSegment)))
{
hr = Stop(pSegment,NULL,(MUSIC_TIME)i64StopTime,dwFlags);
pSegment->Release();
}
else if (SUCCEEDED(pObjectToStop->QueryInterface(IID_CAudioPath,(void **) &pAudioPath)))
{
pAudioPath->Release();
EnterCriticalSection(&m_SegmentCrSec);
CSegState *pNode;
DWORD dwCount;
for (dwCount = SQ_PRI_WAIT; dwCount <= SQ_SEC_DONE; dwCount++)
{
CSegState *pNext;
for( pNode = m_SegStateQueues[dwCount].GetHead(); pNode; pNode = pNext )
{
pNext = pNode->GetNext();
if (pNode->m_fCanStop && (pNode->m_pAudioPath == pAudioPath))
{
hr = Stop(NULL,(IDirectMusicSegmentState *)pNode,(MUSIC_TIME)i64StopTime,dwFlags);
}
}
}
LeaveCriticalSection(&m_SegmentCrSec);
}
else if (SUCCEEDED(pObjectToStop->QueryInterface(IID_CSong,(void **) &pSong)))
{
pSong->Release();
EnterCriticalSection(&m_SegmentCrSec);
CSegState *pNode;
DWORD dwCount;
for (dwCount = SQ_PRI_WAIT; dwCount <= SQ_SEC_DONE; dwCount++)
{
for( pNode = m_SegStateQueues[dwCount].GetHead(); pNode; pNode = pNode->GetNext() )
{
if (pNode->m_fCanStop && pNode->m_pSegment && (pNode->m_pSegment->m_pSong == pSong))
{
hr = Stop(NULL,(IDirectMusicSegmentState *)pNode,(MUSIC_TIME)i64StopTime,dwFlags);
}
}
}
LeaveCriticalSection(&m_SegmentCrSec);
}
return hr;
}
HRESULT STDMETHODCALLTYPE CPerformance::Stop(
IDirectMusicSegment *pISegment, // @parm The Segment to stop playing. All SegmentState's based upon this Segment are
// stopped playing at time <p mtTime>.
IDirectMusicSegmentState *pISegmentState, // @parm The SegmentState to stop playing.
MUSIC_TIME mtTime, // @parm The time at which to stop the Segments, Segment State, or everything. If
// this time is in the past, stop everything right away. Therefore, a value of
// 0 indicates stop everything NOW.
DWORD dwFlags) // @parm Flag that indicates whether we should stop immediately at time <p mtTime>,
// or on the grid, measure, or beat following <p mtTime>. This is only valid in
// relation to the currently playing primary segment. (For flag descriptions,
// see <t DMPLAYSEGFLAGS>.)
{
V_INAME(IDirectMusicPerformance::Stop);
V_INTERFACE_OPT(pISegment);
V_INTERFACE_OPT(pISegmentState);
EnterCriticalSection(&m_SegmentCrSec);
CSegment *pSegment = NULL;
CSegState *pSegmentState = NULL;
TraceI(0,"Stopping Segment %lx, SegState %lx at time %ld, flags %lx\n",pISegment,pISegmentState,mtTime,dwFlags);
if (pISegmentState)
{
if (SUCCEEDED(pISegmentState->QueryInterface(IID_CSegState,(void **)&pSegmentState)))
{
pISegmentState->Release();
}
else
{
Trace(1,"Error: Pointer in SegState parameter to Stop() is invalid.\n");
return E_INVALIDARG;
}
}
if (pISegment)
{
if (SUCCEEDED(pISegment->QueryInterface(IID_CSegment,(void **)&pSegment)))
{
pISegment->Release();
}
else
{
Trace(1,"Error: Pointer in Segment parameter to Stop() is invalid.\n");
return E_INVALIDARG;
}
}
if (pSegmentState)
{
// If this is the starting segstate from a playing song, find the
// current active segstate within that song.
// The current active segstate keeps a pointer to
// this segstate.
if (pSegmentState->m_fSongMode)
{
CSegState* pNode;
DWORD dwCount;
for (dwCount = 0; dwCount < SQ_COUNT; dwCount++)
{
for( pNode = m_SegStateQueues[dwCount].GetHead(); pNode; pNode = pNode->GetNext() )
{
if (pNode->m_pSongSegState == pSegmentState)
{
pSegmentState = pNode;
dwCount = SQ_COUNT;
break;
}
}
}
}
}
if( dwFlags & DMUS_SEGF_DEFAULT )
{
DWORD dwNewRes = 0;
if( pSegment )
{
pSegment->GetDefaultResolution( &dwNewRes );
}
else if( pSegmentState )
{
IDirectMusicSegment* pSegTemp;
if( SUCCEEDED( pSegmentState->GetSegment( &pSegTemp ) ) )
{
pSegTemp->GetDefaultResolution( &dwNewRes );
pSegTemp->Release();
}
else
{
dwNewRes = 0;
}
}
else
{
dwNewRes = 0;
}
dwFlags |= dwNewRes;
dwFlags &= ~DMUS_SEGF_DEFAULT;
}
// Make sure mtTime is greater or equal to QueueTime, which is the last time notes were
// queued down (or latency time, whichever is later) so we can stop everything after it.
MUSIC_TIME mtLatency;
REFERENCE_TIME rtQueueTime;
GetQueueTime( &rtQueueTime );
ReferenceToMusicTime( rtQueueTime, &mtLatency );
if( mtTime < mtLatency ) mtTime = mtLatency;
// Resolve the time according to the resolution
mtTime = ResolveTime( mtTime, dwFlags, NULL );
// if mtTime is less than the current transported time, we can take
// care of the Stop now. Otherwise, we need to cue a Stop PMsg and
// take care of it at QUEUE time.
if( mtTime <= m_mtTransported )
{
if( pSegmentState )
{
DoStop( pSegmentState, mtTime, TRUE );
if( pSegment )
{
DoStop( pSegment, mtTime, TRUE );
}
}
else
{
DoStop( pSegment, mtTime, TRUE );
}
}
else
{
// find and mark the segment and/or segment state to not play beyond
// the stop point.
CSegState* pNode;
DWORD dwCount;
for (dwCount = SQ_PRI_PLAY; dwCount <= SQ_SEC_PLAY; dwCount++)
{
for( pNode = m_SegStateQueues[dwCount].GetHead(); pNode; pNode = pNode->GetNext() )
{
if( (pNode->m_pSegment == pSegment) ||
(pNode == pSegmentState) )
{
if (pNode->m_fCanStop)
{
pNode->m_mtStopTime = mtTime;
// Make sure GetParams ignore the rest of the segment from now on.
if (mtTime < pNode->m_mtEndTime)
{
pNode->m_mtLength = mtTime - pNode->m_mtResolvedStart +
pNode->m_mtStartPoint;
if (pNode->m_mtLength < 0)
{
pNode->m_mtLength = 0;
}
// Make endtime one greater than mtTime so Abort notification will still happen.
pNode->m_mtEndTime = mtTime + 1;
}
// Force the tempo map to be recalculated IF this has a tempo track.
RecalcTempoMap(pNode,mtTime);
}
}
}
}
// create a Stop PMsg and cue it for QUEUE time
// I've removed this to fix bugs. A stop message at queue time,
// if in a controlling or primary segment, results in invalidation.
// This is particularily bad for controlling segments.
// Can't figure out why we even need the stop message...
/* DMUS_PMSG* pPMsg;
if( SUCCEEDED( AllocPMsg( sizeof(DMUS_PMSG), &pPMsg )))
{
pPMsg->dwType = DMUS_PMSGT_STOP;
pPMsg->mtTime = mtTime;
pPMsg->dwFlags = DMUS_PMSGF_MUSICTIME | DMUS_PMSGF_TOOL_QUEUE;
if( pSegment )
{
pSegment->QueryInterface( IID_IUnknown, (void**)&pPMsg->punkUser );
if( pSegmentState )
{
// if there is also a segment state pointer, we need to create two
// pmsg's
DMUS_PMSG* pPMsg2;
if( SUCCEEDED( AllocPMsg( sizeof(DMUS_PMSG), &pPMsg2 )))
{
pPMsg2->dwType = DMUS_PMSGT_STOP;
pPMsg2->mtTime = mtTime;
pPMsg2->dwFlags = DMUS_PMSGF_MUSICTIME | DMUS_PMSGF_TOOL_QUEUE;
pSegmentState->QueryInterface( IID_IUnknown, (void**)&pPMsg2->punkUser );
pPMsg2->pTool = this;
AddRef();
if(FAILED(SendPMsg( pPMsg2 )))
{
FreePMsg(pPMsg2);
}
}
}
}
else if( pSegmentState )
{
pSegmentState->QueryInterface( IID_IUnknown, (void**)&pPMsg->punkUser );
}
pPMsg->pTool = this;
AddRef();
if(FAILED(SendPMsg( pPMsg )))
{
FreePMsg(pPMsg);
}
}*/
}
LeaveCriticalSection(&m_SegmentCrSec);
return S_OK;
}
void CPerformance::ResetAllControllers(CChannelMap* pChannelMap, REFERENCE_TIME rtTime, bool fGMReset)
{
DWORD dwIndex = pChannelMap->dwPortIndex;
DWORD dwGroup = pChannelMap->dwGroup;
DWORD dwMChannel = pChannelMap->dwMChannel;
EnterCriticalSection(&m_PChannelInfoCrSec);
IDirectMusicPort* pPort = m_pPortTable[dwIndex].pPort;
IDirectMusicBuffer* pBuffer = m_pPortTable[dwIndex].pBuffer;
if( pPort && pBuffer )
{
m_pPortTable[dwIndex].fBufferFilled = TRUE;
if (!rtTime)
{
rtTime = m_pPortTable[dwIndex].rtLast + 1;
}
else
{
m_pPortTable[dwIndex].rtLast = rtTime;
}
pChannelMap->Reset(true);
DWORD dwMsg = dwMChannel | MIDI_CCHANGE | (MIDI_CC_ALLSOUNDSOFF << 8); // 0x78 is all sounds off.
if( FAILED( pBuffer->PackStructured( rtTime, dwGroup, dwMsg ) ) )
{
pPort->PlayBuffer( pBuffer );
pBuffer->Flush();
// try one more time
pBuffer->PackStructured( rtTime, dwGroup, dwMsg );
}
dwMsg = dwMChannel | MIDI_CCHANGE | (MIDI_CC_RESETALL << 8) | (1 << 16) ; // 0x79 is reset all controllers. Data byte set to indicate volume and pan too.
if( FAILED( pBuffer->PackStructured( rtTime + 30 * REF_PER_MIL, dwGroup, dwMsg ) ) )
{
pPort->PlayBuffer( pBuffer );
pBuffer->Flush();
// try one more time
pBuffer->PackStructured( rtTime + (30 * REF_PER_MIL), dwGroup, dwMsg );
}
// Send one GM Reset per channel group, but only under DX8 (and only if we need to).
if ((dwMChannel == 0) && (m_dwVersion >= 8) && fGMReset)
{
// create a buffer of the right size
DMUS_BUFFERDESC dmbd;
IDirectMusicBuffer *pLocalBuffer;
static BYTE abGMReset[6] = { (BYTE)MIDI_SYSX,0x7E,0x7F,9,1,(BYTE)MIDI_EOX };
memset( &dmbd, 0, sizeof(DMUS_BUFFERDESC) );
dmbd.dwSize = sizeof(DMUS_BUFFERDESC);
dmbd.cbBuffer = 50;
EnterCriticalSection(&m_MainCrSec);
if( SUCCEEDED( m_pDirectMusic->CreateMusicBuffer(&dmbd, &pLocalBuffer, NULL)))
{
if( SUCCEEDED( pLocalBuffer->PackUnstructured( rtTime + (30 * REF_PER_MIL), dwGroup,
6, abGMReset ) ) )
{
pPort->PlayBuffer(pLocalBuffer);
}
pLocalBuffer->Release();
}
LeaveCriticalSection(&m_MainCrSec);
}
m_rtEarliestStartTime = rtTime + (60 * REF_PER_MIL); // Give synth chance to stabilize
// before next start.
}
LeaveCriticalSection(&m_PChannelInfoCrSec);
}
void CPerformance::ResetAllControllers( REFERENCE_TIME rtTime )
{
EnterCriticalSection(&m_PChannelInfoCrSec);
CChannelBlock* pChannelBlock;
SendBuffers();
for( pChannelBlock = m_ChannelBlockList.GetHead(); pChannelBlock; pChannelBlock = pChannelBlock->GetNext() )
{
CChannelMap* pChannelMap;
for( DWORD dwPChannel = pChannelBlock->m_dwPChannelStart;
dwPChannel < pChannelBlock->m_dwPChannelStart + PCHANNEL_BLOCKSIZE;
dwPChannel++ )
{
pChannelMap = &pChannelBlock->m_aChannelMap[dwPChannel - pChannelBlock->m_dwPChannelStart];
if( pChannelMap->dwGroup ) // Valid group?
{
// Reset controllers and send a GM reset.
ResetAllControllers(pChannelMap, rtTime, true);
}
}
}
SendBuffers();
LeaveCriticalSection(&m_PChannelInfoCrSec);
}
// internal: return CSegState* at time mtTime
// only call this from within a segment critical section
CSegState* CPerformance::GetPrimarySegmentAtTime( MUSIC_TIME mtTime )
{
CSegState* pSegNode;
CSegState* pSegReturn = NULL;
BOOL fCheckedPri = FALSE;
for( pSegNode = m_SegStateQueues[SQ_PRI_DONE].GetHead(); pSegNode; pSegNode = pSegNode->GetNext() )
{
// if we're checking the past list, only check up until the last time played.
if( (mtTime >= pSegNode->m_mtResolvedStart) && (mtTime <= pSegNode->m_mtLastPlayed) )
{
pSegReturn = pSegNode;
break;
}
}
for( pSegNode = m_SegStateQueues[SQ_PRI_PLAY].GetHead(); pSegNode; pSegNode = pSegNode->GetNext() )
{
MUSIC_TIME mtTest = mtTime;
MUSIC_TIME mtOffset;
DWORD dwRepeat;
// if we're checking the current list, check the full segment time
if( S_OK == pSegNode->ConvertToSegTime( &mtTest, &mtOffset, &dwRepeat ))
{
pSegReturn = pSegNode;
break;
}
}
if (!pSegReturn)
{
for( pSegNode = m_SegStateQueues[SQ_PRI_WAIT].GetHead(); pSegNode; pSegNode = pSegNode->GetNext() )
{
MUSIC_TIME mtTest = mtTime;
MUSIC_TIME mtOffset;
DWORD dwRepeat;
// if we're checking the current list, check the full segment time
if( S_OK == pSegNode->ConvertToSegTime( &mtTest, &mtOffset, &dwRepeat ))
{
pSegReturn = pSegNode;
break;
}
}
}
return pSegReturn;
}
/*
@method HRESULT | IDirectMusicPerformance | GetSegmentState |
Returns the Primary SegmentState at time <p mtTime>.
@rvalue S_OK | Success.
@rvalue E_POINTER | ppSegmentState is NULL or invalid.
@rvalue DMUS_E_NOT_FOUND | There is no currently playing SegmentState or one at <p mtTime>.
@comm This function is intended for routines that need to access the currently
playing SegmentState, e.g. to obtain the chord or command track. "Currently
Playing" in this context means that it is being called into to perform messages.
I.e., this includes all latencies and doesn't imply that this
SegmentState is currenty being "heard" through the speakers.
*/
HRESULT STDMETHODCALLTYPE CPerformance::GetSegmentState(
IDirectMusicSegmentState **ppSegmentState, // @parm Returns the SegmentState pointer to the one currently playing.
// The caller is responsible for calling Release on this pointer.
MUSIC_TIME mtTime ) // @parm Return the SegmentState which played, is playing, or will
// be playing at mtTime. To get the currently playing segment, pass the
// mtTime retrieved from <om .GetTime>.
{
V_INAME(IDirectMusicPerformance::GetSegmentState);
V_PTRPTR_WRITE(ppSegmentState);
CSegState* pSegNode;
HRESULT hr;
EnterCriticalSection(&m_SegmentCrSec);
if( pSegNode = GetPrimarySegmentAtTime( mtTime ))
{
*ppSegmentState = pSegNode;
pSegNode->AddRef();
hr = S_OK;
}
else
{
Trace(3,"Unable to find a segment state at time %ld\n",mtTime);
hr = DMUS_E_NOT_FOUND;
}
LeaveCriticalSection(&m_SegmentCrSec);
return hr;
}
/*
@method HRESULT | IDirectMusicPerformance | SetPrepareTime |
Sets the prepare time. The prepare time is the amount of time ahead that
<om IDirectMusicTrack.Play> is called before the messages should actually
be heard through the loudspeaker. The midi messages from the tracks are placed in
the early queue, are processed by Tools, and then placed in the near-time
queue to await being sent to the midi ports.
@rvalue S_OK | Success.
@comm The default value is 1000 milliseconds.
*/
HRESULT STDMETHODCALLTYPE CPerformance::SetPrepareTime(
DWORD dwMilliSeconds) // @parm The amount of time.
{
m_dwPrepareTime = dwMilliSeconds;
return S_OK;
}
/*
@method HRESULT | IDirectMusicPerformance | GetPrepareTime |
Gets the prepare time. The prepare time is the amount of time ahead that
<om IDirectMusicTrack.Play> is called before the messages should actually
be heard through the loudspeaker. The midi messages from the tracks are placed in
the early queue, are processed by Tools, and then placed in the near-time
queue to await being sent to the midi ports.
@rvalue S_OK | Success.
@rvalue E_POINTER | pdwMilliSeconds is NULL or invalid.
@comm The default value is 1000 milliseconds.
*/
HRESULT STDMETHODCALLTYPE CPerformance::GetPrepareTime(
DWORD* pdwMilliSeconds) // @parm The amount of time.
{
V_INAME(IDirectMusicPerformance::GetPrepareTime);
V_PTR_WRITE(pdwMilliSeconds,DWORD);
*pdwMilliSeconds = m_dwPrepareTime;
return S_OK;
}
/*
@method HRESULT | IDirectMusicPerformance | SetBumperLength |
Sets the bumper length. The bumper length is the amount of time to buffer ahead
of the Port's latency for midi messages to be sent to the Port for rendering.
@rvalue S_OK | Success.
@comm The default value is 50 milliseconds.
*/
HRESULT STDMETHODCALLTYPE CPerformance::SetBumperLength(
DWORD dwMilliSeconds) // @parm The amount of time.
{
m_dwBumperLength = dwMilliSeconds;
m_rtBumperLength = m_dwBumperLength * REF_PER_MIL;
return S_OK;
}
/*
@method HRESULT | IDirectMusicPerformance | GetBumperLength |
Gets the bumper length. The bumper length is the amount of time to buffer ahead
of the Port's latency for midi messages to be sent to the Port for rendering.
@rvalue S_OK | Success.
@rvalue E_POINTER | pdwMilliSeconds is NULL or invalid.
@comm The default value is 50 milliseconds.
*/
HRESULT STDMETHODCALLTYPE CPerformance::GetBumperLength(
DWORD* pdwMilliSeconds) // @parm The amount of time.
{
V_INAME(IDirectMusicPerformance::GetBumperLength);
V_PTR_WRITE(pdwMilliSeconds,DWORD);
*pdwMilliSeconds = m_dwBumperLength;
return S_OK;
}
#define RESOLVE_FLAGS (DMUS_TIME_RESOLVE_AFTERPREPARETIME | \
DMUS_TIME_RESOLVE_AFTERLATENCYTIME | \
DMUS_TIME_RESOLVE_AFTERQUEUETIME | \
DMUS_TIME_RESOLVE_BEAT | \
DMUS_TIME_RESOLVE_MEASURE | \
DMUS_TIME_RESOLVE_GRID | \
DMUS_TIME_RESOLVE_MARKER | \
DMUS_TIME_RESOLVE_SEGMENTEND)
HRESULT STDMETHODCALLTYPE CPerformance::SendPMsg(
DMUS_PMSG *pDMUS_PMSG)
{
V_INAME(IDirectMusicPerformance::SendPMsg);
if( m_dwVersion < 8)
{
V_BUFPTR_WRITE(pDMUS_PMSG,sizeof(DMUS_PMSG));
}
else
{
#ifdef DBG
V_BUFPTR_WRITE(pDMUS_PMSG,sizeof(DMUS_PMSG));
#else
if (!pDMUS_PMSG)
{
return E_POINTER;
}
#endif
}
EnterCriticalSection(&m_MainCrSec);
if( m_pClock == NULL )
{
LeaveCriticalSection(&m_MainCrSec);
Trace(1,"Error: Unable to Send PMsg because performance not initialized.\n");
return DMUS_E_NO_MASTER_CLOCK;
}
LeaveCriticalSection(&m_MainCrSec);
if (pDMUS_PMSG->dwSize < sizeof(DMUS_PMSG))
{
TraceI(1,"Warning: PMsg size field has been cleared.\n");
}
// If this is a PMsg that was marked by STampPMsg as one that should be removed,
// do so now.
if (pDMUS_PMSG->dwPChannel == DMUS_PCHANNEL_KILL_ME)
{
FreePMsg(pDMUS_PMSG);
return S_OK;
}
EnterCriticalSection(&m_PipelineCrSec);
PRIV_PMSG* pPrivPMsg = DMUS_TO_PRIV(pDMUS_PMSG);
if( ( pPrivPMsg->dwPrivFlags & PRIV_FLAG_QUEUED ) ||
( ( pPrivPMsg->dwPrivFlags & PRIV_FLAG_ALLOC_MASK ) != PRIV_FLAG_ALLOC ) )
{
Trace(1, "Error: Attempt to send an improperly allocated PMsg, or trying to send it after it is already sent.\n" );
LeaveCriticalSection(&m_PipelineCrSec);
return DMUS_E_ALREADY_SENT;
}
if (m_dwVersion >= 8)
{
// If the music and ref times are both 0, set to latency time.
if ((pDMUS_PMSG->mtTime == 0) && ( pDMUS_PMSG->rtTime == 0 ))
{
// If this needs to resolve, use the worse case latency
// because this needs to sync with other pmsgs.
if (pDMUS_PMSG->dwFlags & RESOLVE_FLAGS)
{
GetLatencyTime(&pDMUS_PMSG->rtTime);
}
else
{
// Otherwise, we want to play as soon as possible.
pDMUS_PMSG->rtTime = GetTime();
}
pDMUS_PMSG->dwFlags |= DMUS_PMSGF_REFTIME;
pDMUS_PMSG->dwFlags &= ~DMUS_PMSGF_MUSICTIME;
}
}
// fill in missing time value
if (!(pDMUS_PMSG->dwFlags & DMUS_PMSGF_MUSICTIME))
{
if( !(pDMUS_PMSG->dwFlags & DMUS_PMSGF_REFTIME ) )
{
LeaveCriticalSection(&m_PipelineCrSec);
Trace(1,"Error: Unable to send PMsg because neither clock time (DMUS_PMSGF_REFTIME) nor music time (DMUS_PMSGF_MUSICTIME) has been set.\n");
return E_INVALIDARG; // one or the other MUST be set
}
// quantize to resolution boundaries
GetResolvedTime( pDMUS_PMSG->rtTime, &pDMUS_PMSG->rtTime, pDMUS_PMSG->dwFlags );
pDMUS_PMSG->dwFlags &= ~RESOLVE_FLAGS;
// if time is zero, set it to time now plus latency
if( pDMUS_PMSG->rtTime == 0 )
{
pDMUS_PMSG->rtTime = GetLatency();
}
ReferenceToMusicTime(pDMUS_PMSG->rtTime,
&pDMUS_PMSG->mtTime);
pDMUS_PMSG->dwFlags |= DMUS_PMSGF_MUSICTIME;
}
else if (!(pDMUS_PMSG->dwFlags & DMUS_PMSGF_REFTIME))
{
MusicToReferenceTime(pDMUS_PMSG->mtTime,
&pDMUS_PMSG->rtTime);
pDMUS_PMSG->dwFlags |= DMUS_PMSGF_REFTIME;
// quantize to resolution boundaries
REFERENCE_TIME rtNew;
GetResolvedTime( pDMUS_PMSG->rtTime, &rtNew, pDMUS_PMSG->dwFlags );
pDMUS_PMSG->dwFlags &= ~RESOLVE_FLAGS;
if( rtNew != pDMUS_PMSG->rtTime )
{
pDMUS_PMSG->rtTime = rtNew;
ReferenceToMusicTime( pDMUS_PMSG->rtTime, &pDMUS_PMSG->mtTime );
}
}
// insert into the proper queue by music value
if (pDMUS_PMSG->dwFlags & DMUS_PMSGF_TOOL_QUEUE)
{
m_NearTimeQueue.Enqueue(pPrivPMsg);
}
else if (pDMUS_PMSG->dwFlags & DMUS_PMSGF_TOOL_ATTIME)
{
m_OnTimeQueue.Enqueue(pPrivPMsg);
}
else // (pDMUS_PMSG->dwFlags & DMUS_PMSGF_TOOL_IMMEDIATE)
{
pDMUS_PMSG->dwFlags |= DMUS_PMSGF_TOOL_IMMEDIATE;
m_EarlyQueue.Enqueue(pPrivPMsg);
}
LeaveCriticalSection(&m_PipelineCrSec);
return S_OK;
}
/*
Call this only from within a PipelineCrSec.
*/
void CPerformance::RevalidateRefTimes( CPMsgQueue * pList, MUSIC_TIME mtTime )
{
PRIV_PMSG* pCheck;
BOOL fError = FALSE;
for( pCheck = pList->GetHead(); pCheck; pCheck = pCheck->pNext )
{
if (pCheck->mtTime > mtTime)
{
if (pCheck->dwFlags & DMUS_PMSGF_LOCKTOREFTIME)
{
ReferenceToMusicTime(pCheck->rtTime,&pCheck->mtTime);
}
else // if(pCheck->dwFlags & DMUS_PMSGF_MUSICTIME)
{
MusicToReferenceTime(pCheck->mtTime,&pCheck->rtTime);
}
}
}
// Make sure that we do not end up with out of order RTimes. This can happen with
// DMUS_PMSGF_LOCKTOREFTIME messages or very abrupt changes in tempo.
for( pCheck = pList->GetHead(); pCheck; pCheck = pCheck->pNext )
{
if (pCheck->pNext && ( pCheck->rtTime > pCheck->pNext->rtTime ))
{
fError = TRUE; // Need to sort the list.
}
}
if (fError)
{
TraceI(2,"Rearrangement of times in message list due to tempo change, resorting\n");
pList->Sort();
}
}
void CPerformance::AddToTempoMap( double dblTempo, MUSIC_TIME mtTime, REFERENCE_TIME rtTime )
{
DMInternalTempo* pITempo = NULL;
if( FAILED( AllocPMsg( sizeof(DMInternalTempo), (PRIV_PMSG**)&pITempo )))
{
return; // out of memory!
}
if( dblTempo > DMUS_TEMPO_MAX ) dblTempo = DMUS_TEMPO_MAX;
else if( dblTempo < DMUS_TEMPO_MIN ) dblTempo = DMUS_TEMPO_MIN;
pITempo->tempoPMsg.dblTempo = dblTempo;
pITempo->tempoPMsg.rtTime = rtTime;
pITempo->tempoPMsg.mtTime = mtTime;
pITempo->tempoPMsg.dwFlags = DMUS_PMSGF_MUSICTIME | DMUS_PMSGF_REFTIME;
pITempo->pNext = NULL;
// set the relative tempo field
EnterCriticalSection(&m_GlobalDataCrSec);
pITempo->fltRelTempo = m_fltRelTempo;
// add the tempo event to the tempo map and clear the tool and graph pointers
pITempo->tempoPMsg.pTool = NULL;
EnterCriticalSection(&m_PipelineCrSec);
// remove stale tempo events from the tempo map.
// as long as there is another tempo with a time stamp before the current
// time, get rid of the first in the list.
REFERENCE_TIME rtNow = GetTime() - (10000 * 1000); // keep around for a second.
PRIV_PMSG* pCheck;
while (pCheck = m_TempoMap.FlushOldest(rtNow))
{
m_OldTempoMap.Enqueue(pCheck);
}
// add the new tempo event to the queue
m_TempoMap.Enqueue( (PRIV_PMSG*) pITempo );
// now that it's been added, scan forward from it and change the relative tempo
// times of everyone after it
DMInternalTempo* pChange;
for( pChange = (DMInternalTempo*)pITempo->pNext; pChange;
pChange = (DMInternalTempo*)pChange->pNext )
{
pChange->fltRelTempo = pITempo->fltRelTempo;
}
// remove stale tempo events from the old tempo map.
// as long as there is another tempo with a time stamp before the current
// time, get rid of the first in the list.
rtNow = GetTime() - ((REFERENCE_TIME)10000 * 300000); // keep around for five minutes.
while (pCheck = m_OldTempoMap.FlushOldest(rtNow))
{
FreePMsg(pCheck);
}
m_fTempoChanged = TRUE;
LeaveCriticalSection(&m_PipelineCrSec);
LeaveCriticalSection(&m_GlobalDataCrSec);
}
void CPerformance::AddEventToTempoMap( PRIV_PMSG* pEvent )
{
PRIV_TEMPO_PMSG* pTempo = (PRIV_TEMPO_PMSG*)pEvent;
MUSIC_TIME mtTime = pTempo->tempoPMsg.mtTime;
AddToTempoMap( pTempo->tempoPMsg.dblTempo, mtTime, pTempo->tempoPMsg.rtTime );
pEvent->dwPrivFlags = PRIV_FLAG_ALLOC;
EnterCriticalSection(&m_GlobalDataCrSec);
EnterCriticalSection(&m_PipelineCrSec);
// revalidate the ref times of the events in the queues
RevalidateRefTimes( &m_TempoMap, mtTime );
RevalidateRefTimes( &m_OnTimeQueue, mtTime );
RevalidateRefTimes( &m_NearTimeQueue, mtTime );
RevalidateRefTimes( &m_EarlyQueue, mtTime );
m_fTempoChanged = TRUE;
LeaveCriticalSection(&m_PipelineCrSec);
LeaveCriticalSection(&m_GlobalDataCrSec);
RecalcTempoMap(NULL, mtTime+1, false);
}
#define TEMPO_AHEAD 768 * 4 * 10 // 10 measures ahead is plenty!
void CPerformance::IncrementTempoMap()
{
if (m_mtTempoCursor <= (m_mtTransported + TEMPO_AHEAD))
{
UpdateTempoMap(m_mtTempoCursor, false, NULL);
}
}
void CPerformance::RecalcTempoMap(CSegState *pSegState, MUSIC_TIME mtStart, bool fAllDeltas)
/* Called whenever a primary or controlling segment that has a tempo
track is played or stopped.
1) Convert the music time at transport time to ref time using the old
map.
2) Build a replacement tempo map starting at mtStart, by
calling GetParam() until there is no next time.
3) Install the new map.
4) Convert with the new map.
5) If the two numbers are not identical, recalculate all message times.
*/
{
if( mtStart > 0) // Don't do this for invalid values.
{
if (!pSegState || (pSegState->m_pSegment && pSegState->m_pSegment->IsTempoSource()))
{
REFERENCE_TIME rtCompareTime;
REFERENCE_TIME rtAfterTime;
MUSIC_TIME mtCompareTime = m_mtTransported;
MusicToReferenceTime(mtCompareTime,&rtCompareTime);
EnterCriticalSection(&m_PipelineCrSec);
FlushEventQueue( 0, &m_TempoMap, rtCompareTime, rtCompareTime, FALSE );
LeaveCriticalSection(&m_PipelineCrSec);
UpdateTempoMap(mtStart, true, pSegState, fAllDeltas);
MusicToReferenceTime(mtCompareTime,&rtAfterTime);
if (rtAfterTime != rtCompareTime)
{
EnterCriticalSection(&m_GlobalDataCrSec);
EnterCriticalSection(&m_PipelineCrSec);
// revalidate the ref times of the events in the queues
RevalidateRefTimes( &m_TempoMap, mtStart );
RevalidateRefTimes( &m_OnTimeQueue, mtStart );
RevalidateRefTimes( &m_NearTimeQueue, mtStart );
RevalidateRefTimes( &m_EarlyQueue, mtStart );
m_fTempoChanged = TRUE;
LeaveCriticalSection(&m_PipelineCrSec);
LeaveCriticalSection(&m_GlobalDataCrSec);
}
}
}
}
void CPerformance::UpdateTempoMap(MUSIC_TIME mtStart, bool fFirst, CSegState *pSegState, bool fAllDeltas)
{
HRESULT hr = S_OK;
DWORD dwIndex = 0;
PrivateTempo Tempo;
TList<PrivateTempo> TempoList;
TListItem<PrivateTempo>* pScan = NULL;
MUSIC_TIME mtNext = 0;
MUSIC_TIME mtTime = mtStart;
MUSIC_TIME mtCursor = mtStart;
REFERENCE_TIME rtTime;
do
{
hr = GetParam(GUID_PrivateTempoParam,-1,dwIndex,mtTime,&mtNext,(void *)&Tempo );
Tempo.mtTime = mtTime;
if (hr == S_OK && Tempo.mtDelta > 0)
{
mtTime += Tempo.mtDelta;
hr = GetParam(GUID_PrivateTempoParam,-1,dwIndex,mtTime,&mtNext,(void *)&Tempo );
Tempo.mtTime = mtTime;
}
if (hr == S_FALSE && fFirst && !pSegState)
{
// If this was the very first try, there might not be any tempo track, and
// so global tempo is called. If so, S_FALSE is returned. This is okay
// for the NULL segstate case where we are recomputing the tempo map in response
// to a change in global tempo, or stop of all segments.
if (fAllDeltas) // Never do this in response to adding a new event to the tempo map
{
MusicToReferenceTime(mtTime,&rtTime);
// the rtTime in the tempo map needs to be the non-adjusted value (305694)
AddToTempoMap( Tempo.dblTempo, mtTime, rtTime + m_rtAdjust );
}
break;
}
if (hr == S_OK)
{
TListItem<PrivateTempo>* pNew = new TListItem<PrivateTempo>(Tempo);
if (pNew)
{
// add to TempoList, replacing duplicate times with the most recent mtDelta
TListItem<PrivateTempo>* pNext = TempoList.GetHead();
if (!pNext || Tempo.mtTime < pNext->GetItemValue().mtTime)
{
TempoList.AddHead(pNew);
}
else for (pScan = TempoList.GetHead(); pScan; pScan = pNext)
{
pNext = pScan->GetNext();
if (Tempo.mtTime == pScan->GetItemValue().mtTime)
{
if (Tempo.mtDelta > pScan->GetItemValue().mtDelta)
{
pScan->GetItemValue() = Tempo;
}
delete pNew;
break;
}
else if (!pNext || Tempo.mtTime < pNext->GetItemValue().mtTime)
{
pScan->SetNext(pNew);
pNew->SetNext(pNext);
break;
}
}
}
mtTime += mtNext;
fFirst = false;
// If this was the last tempo in the track (that we care about),
// reset the time and bump the track index
if (Tempo.fLast || mtTime > (m_mtTransported + TEMPO_AHEAD))
{
dwIndex++;
mtCursor = mtTime;
mtTime = mtStart;
}
else if (!mtNext) break; // should never happen but if it does, infinite loop
}
else if (Tempo.fLast) // There was an empty tempo track
{
dwIndex++;
hr = S_OK;
}
Tempo.fLast = false;
} while (hr == S_OK);
if (TempoList.GetHead() && TempoList.GetHead()->GetItemValue().mtTime > mtStart)
{
// add a tempo of 120 at time mtStart
TListItem<PrivateTempo>* pNew = new TListItem<PrivateTempo>();
if (pNew)
{
PrivateTempo& rNew = pNew->GetItemValue();
rNew.dblTempo = 120.0;
rNew.mtTime = mtStart;
TempoList.AddHead(pNew);
}
else
{
#ifdef DBG
Trace(1, "Error: Out of memory; Tempo map is incomplete.\n");
#endif
TempoList.GetHead()->GetItemValue().mtTime = mtStart;
}
}
for (pScan = TempoList.GetHead(); pScan; pScan = pScan->GetNext())
{
PrivateTempo& rTempo = pScan->GetItemValue();
if (fAllDeltas || rTempo.mtTime + rTempo.mtDelta >= mtStart)
{
MusicToReferenceTime(rTempo.mtTime,&rtTime);
// the rtTime in the tempo map needs to be the non-adjusted value (305694)
AddToTempoMap( rTempo.dblTempo, rTempo.mtTime, rtTime + m_rtAdjust );
}
}
m_mtTempoCursor = mtCursor;
}
HRESULT STDMETHODCALLTYPE CPerformance::MusicToReferenceTime(
MUSIC_TIME mtTime, // @parm The time in MUSIC_TIME format to convert.
REFERENCE_TIME *prtTime) // @parm Returns the converted time in REFERENCE_TIME format.
{
V_INAME(IDirectMusicPerformance::MusicToReferenceTime);
V_PTR_WRITE(prtTime,REFERENCE_TIME);
EnterCriticalSection(&m_MainCrSec);
if( m_pClock == NULL )
{
LeaveCriticalSection(&m_MainCrSec);
Trace(1,"Error: Unable to convert music to reference time because the performance has not been initialized.\n");
return DMUS_E_NO_MASTER_CLOCK;
}
LeaveCriticalSection(&m_MainCrSec);
PRIV_PMSG* pEvent;
double dbl = 120;
MUSIC_TIME mtTempo = 0;
REFERENCE_TIME rtTempo = m_rtStart;
REFERENCE_TIME rtTemp;
EnterCriticalSection( &m_PipelineCrSec );
pEvent = m_TempoMap.GetHead();
if( pEvent )
{
if( mtTime >= pEvent->mtTime )
{
while( pEvent->pNext )
{
if( pEvent->pNext->mtTime > mtTime )
{
break;
}
pEvent = pEvent->pNext;
}
DMInternalTempo* pTempo = (DMInternalTempo*)pEvent;
dbl = pTempo->tempoPMsg.dblTempo * pTempo->fltRelTempo;
mtTempo = pTempo->tempoPMsg.mtTime;
rtTempo = pTempo->tempoPMsg.rtTime;
}
else
{
// If mtTime is less than everything in the tempo map, look in the old tempo map
// (which goes five minutes into the past). This keeps the regular tempo map
// small, but allows us to get a valid tempo in the cases where the regular tempo
// map no longer contains the tempo we need.
pEvent = m_OldTempoMap.GetHead();
if( pEvent )
{
if( mtTime >= pEvent->mtTime )
{
while( pEvent->pNext )
{
if( pEvent->pNext->mtTime > mtTime )
{
break;
}
pEvent = pEvent->pNext;
}
DMInternalTempo* pTempo = (DMInternalTempo*)pEvent;
dbl = pTempo->tempoPMsg.dblTempo * pTempo->fltRelTempo;
mtTempo = pTempo->tempoPMsg.mtTime;
rtTempo = pTempo->tempoPMsg.rtTime;
}
}
}
}
LeaveCriticalSection( &m_PipelineCrSec );
rtTempo -= m_rtAdjust;
rtTemp = ( mtTime - mtTempo );
rtTemp *= 600000000;
rtTemp += (DMUS_PPQ / 2);
rtTemp /= DMUS_PPQ;
rtTemp = (REFERENCE_TIME)(rtTemp / dbl);
*prtTime = rtTempo + rtTemp;
return S_OK;
}
HRESULT STDMETHODCALLTYPE CPerformance::ReferenceToMusicTime(
REFERENCE_TIME rtTime, // @parm The time in REFERENCE_TIME format to convert.
MUSIC_TIME *pmtTime) // @parm Returns the converted time in MUSIC_TIME format.
{
V_INAME(IDirectMusicPerformance::ReferenceToMusicTime);
V_PTR_WRITE(pmtTime,MUSIC_TIME);
EnterCriticalSection(&m_MainCrSec);
if( m_pClock == NULL )
{
LeaveCriticalSection(&m_MainCrSec);
Trace(1,"Error: Unable to convert reference to music time because the performance has not been initialized.\n");
return DMUS_E_NO_MASTER_CLOCK;
}
LeaveCriticalSection(&m_MainCrSec);
PRIV_PMSG* pEvent;
double dbl = 120;
MUSIC_TIME mtTempo = 0;
REFERENCE_TIME rtTempo = m_rtStart;
EnterCriticalSection( &m_PipelineCrSec );
pEvent = m_TempoMap.GetHead();
if( pEvent )
{
if( rtTime >= pEvent->rtTime )
{
while( pEvent->pNext )
{
if( pEvent->pNext->rtTime > rtTime )
{
break;
}
pEvent = pEvent->pNext;
}
DMInternalTempo* pTempo = (DMInternalTempo*)pEvent;
dbl = pTempo->tempoPMsg.dblTempo * pTempo->fltRelTempo;
mtTempo = pTempo->tempoPMsg.mtTime;
rtTempo = pTempo->tempoPMsg.rtTime;
}
else
{
// If mtTime is less than everything in the tempo map, look in the old tempo map
// (which goes five minutes into the past). This keeps the regular tempo map
// small, but allows us to get a valid tempo in the cases where the regular tempo
// map no longer contains the tempo we need.
pEvent = m_OldTempoMap.GetHead();
if( pEvent )
{
if( rtTime >= pEvent->rtTime )
{
while( pEvent->pNext )
{
if( pEvent->pNext->rtTime > rtTime )
{
break;
}
pEvent = pEvent->pNext;
}
DMInternalTempo* pTempo = (DMInternalTempo*)pEvent;
dbl = pTempo->tempoPMsg.dblTempo * pTempo->fltRelTempo;
mtTempo = pTempo->tempoPMsg.mtTime;
rtTempo = pTempo->tempoPMsg.rtTime;
}
}
}
}
LeaveCriticalSection( &m_PipelineCrSec );
rtTempo -= m_rtAdjust;
if( rtTime < rtTempo )
{
rtTime = rtTempo;
}
rtTime -= rtTempo;
rtTime *= DMUS_PPQ;
rtTime = (REFERENCE_TIME)(rtTime * dbl);
rtTime += 300000000;
rtTime /= 600000000;
#ifdef DBG
if ( rtTime & 0xFFFFFFFF00000000 )
{
Trace(1,"Error: Invalid Reference to Music time conversion resulted in overflow.\n");
}
#endif
*pmtTime = (long) (rtTime & 0xFFFFFFFF);
*pmtTime += mtTempo;
return S_OK;
}
/*
@method HRESULT | IDirectMusicPerformance | AdjustTime |
Adjust the internal Performance time forward or backward. This is mostly used to
compensate for drift when synchronizing to another source, such as SMPTE.
@rvalue S_OK | Success.
@rvalue E_INVALIDARG | rtAmount is too large or too small.
*/
HRESULT STDMETHODCALLTYPE CPerformance::AdjustTime(
REFERENCE_TIME rtAmount) // @parm The amount of time to adjust. This may be a
// number from -10000000 to 10000000 (-1 second to +1 second.)
{
if( ( rtAmount < -10000000 ) || ( rtAmount > 10000000 ) )
{
Trace(1,"Error: Time parameter passed to AdjustTime() is out of range.\n");
return E_INVALIDARG;
}
m_rtAdjust += rtAmount;
return S_OK;
}
/*
@method HRESULT | IDirectMusicPerformance | GetResolvedTime |
Quantize a time to a resolution boundary. Given a time, in REFERENCE_TIME,
return the next time on a given boundary after the time given.
@rvalue S_OK | Success.
@rvalue E_POINTER <prtResolved> is not valid.
*/
HRESULT STDMETHODCALLTYPE CPerformance::GetResolvedTime(
REFERENCE_TIME rtTime,
REFERENCE_TIME* prtResolved,
DWORD dwResolvedTimeFlags)
{
V_INAME(IDirectMusicPerformance::GetResolvedTime);
V_PTR_WRITE(prtResolved,REFERENCE_TIME);
if (rtTime == 0)
{
dwResolvedTimeFlags |= DMUS_TIME_RESOLVE_AFTERQUEUETIME ;
}
if( dwResolvedTimeFlags & DMUS_TIME_RESOLVE_AFTERPREPARETIME )
{
REFERENCE_TIME rtTrans;
MusicToReferenceTime( m_mtTransported, &rtTrans );
if( rtTime < rtTrans ) rtTime = rtTrans;
}
else if (dwResolvedTimeFlags & DMUS_TIME_RESOLVE_AFTERLATENCYTIME )
{
REFERENCE_TIME rtStart;
rtStart = GetLatency();
if( rtTime < rtStart ) rtTime = rtStart;
}
else if( dwResolvedTimeFlags & DMUS_TIME_RESOLVE_AFTERQUEUETIME )
{
REFERENCE_TIME rtStart;
GetQueueTime( &rtStart ); // need queue time because control segments cause invalidations
if( rtTime < rtStart ) rtTime = rtStart;
}
if( dwResolvedTimeFlags & ( DMUS_TIME_RESOLVE_BEAT | DMUS_TIME_RESOLVE_MEASURE |
DMUS_TIME_RESOLVE_GRID | DMUS_TIME_RESOLVE_MARKER | DMUS_TIME_RESOLVE_SEGMENTEND))
{
MUSIC_TIME mtTime; //, mtResolved;
ReferenceToMusicTime( rtTime, &mtTime );
EnterCriticalSection(&m_SegmentCrSec);
mtTime = ResolveTime( mtTime, dwResolvedTimeFlags, NULL);
LeaveCriticalSection(&m_SegmentCrSec);
MusicToReferenceTime( mtTime, prtResolved );
}
else
{
*prtResolved = rtTime;
}
return S_OK;
}
/*
@method HRESULT | IDirectMusicPerformance | IsPlaying |
Find out if a particular Segment or SegmentState is currently playing.
@rvalue E_POINTER | Both pSegment and pSegState are null, or one or both are invalid.
@rvalue DMUS_E_NO_MASTER_CLOCK | There is no master clock in the performance.
Make sure to call <om .Init> before calling this method.
@rvalue S_OK | Yes, it is playing.
@rvalue S_FALSE | No, it is not playing.
*/
HRESULT STDMETHODCALLTYPE CPerformance::IsPlaying(
IDirectMusicSegment *pSegment, // @parm The Segment to check. If NULL, check
// <p pSegState>.
IDirectMusicSegmentState *pSegState) // @parm The SegmentState to check. If NULL,
// check <p pSegment>.
{
CSegState* pNode;
DWORD dwCount;
V_INAME(IDirectMusicPerformance::IsPlaying);
V_INTERFACE_OPT(pSegment);
V_INTERFACE_OPT(pSegState);
EnterCriticalSection(&m_MainCrSec);
if( m_pClock == NULL )
{
LeaveCriticalSection(&m_MainCrSec);
Trace(1,"Error: IsPlaying() failed because the performance has not been initialized.\n");
return DMUS_E_NO_MASTER_CLOCK;
}
LeaveCriticalSection(&m_MainCrSec);
if( !pSegment && !pSegState )
{
Trace(1,"Error: IsPlaying() failed because segment and segment state are both NULL pointers.\n");
return E_POINTER;
}
MUSIC_TIME mtNow;
GetTime(NULL, &mtNow);
EnterCriticalSection(&m_SegmentCrSec);
for( dwCount = 0; dwCount < SQ_COUNT; dwCount++ )
{
for( pNode = m_SegStateQueues[dwCount].GetHead(); pNode; pNode = pNode->GetNext() )
{
if( !pNode->m_fStartedPlay )
{
continue;
}
if( mtNow >= pNode->m_mtResolvedStart )
{
if( mtNow < pNode->m_mtLastPlayed )
{
if(( pNode == (CSegState*) pSegState ) ||
( pNode->m_pSegment == (CSegment *) pSegment ))
{
LeaveCriticalSection(&m_SegmentCrSec);
return S_OK;
}
}
}
else
{
// if mtNow is before this pSegState's resolved start, it is before every
// pSegState after this too, so break now.
break;
}
}
}
LeaveCriticalSection(&m_SegmentCrSec);
return S_FALSE;
}
HRESULT STDMETHODCALLTYPE CPerformance::GetTime(
REFERENCE_TIME *prtNow, // @parm Returns the current time in REFERENCE_TIME
// format. May be NULL.
MUSIC_TIME *pmtNow) // @parm Returns the current time in MUSIC_TIME
// format. May be NULL.
{
V_INAME(IDirectMusicPerformance::GetTime);
V_PTR_WRITE_OPT(prtNow,REFERENCE_TIME);
V_PTR_WRITE_OPT(pmtNow,MUSIC_TIME);
EnterCriticalSection(&m_MainCrSec);
if( m_pClock == NULL )
{
LeaveCriticalSection(&m_MainCrSec);
Trace(1,"Error: GetTime() failed because the performance has not been initialized.\n");
return DMUS_E_NO_MASTER_CLOCK;
}
LeaveCriticalSection(&m_MainCrSec);
REFERENCE_TIME rtTime = GetTime();
if( prtNow )
{
*prtNow = rtTime;
}
if( pmtNow )
{
MUSIC_TIME mtTime;
ReferenceToMusicTime( rtTime, &mtTime );
*pmtNow = mtTime;
}
return S_OK;
}
HRESULT STDMETHODCALLTYPE CPerformance::GetLatencyTime(
REFERENCE_TIME *prtTime) // @parm Returns the current latency time.
{
V_INAME(IDirectMusicPerformance::GetLatencyTime);
V_PTR_WRITE(prtTime,REFERENCE_TIME);
EnterCriticalSection(&m_MainCrSec);
if( m_pClock == NULL )
{
LeaveCriticalSection(&m_MainCrSec);
Trace(1,"Error: GetLatencyTime() failed because the performance has not been initialized.\n");
return DMUS_E_NO_MASTER_CLOCK;
}
LeaveCriticalSection(&m_MainCrSec);
*prtTime = GetLatency();
return S_OK;
}
HRESULT STDMETHODCALLTYPE CPerformance::GetQueueTime(
REFERENCE_TIME *prtTime) // @parm Returns the current queue time.
{
V_INAME(IDirectMusicPerformance::GetQueueTime);
V_PTR_WRITE(prtTime,REFERENCE_TIME);
EnterCriticalSection(&m_MainCrSec);
if( m_pClock == NULL )
{
LeaveCriticalSection(&m_MainCrSec);
Trace(1,"Error: GetQueueTime() failed because the performance has not been initialized.\n");
return DMUS_E_NO_MASTER_CLOCK;
}
LeaveCriticalSection(&m_MainCrSec);
DWORD dw;
REFERENCE_TIME rtLatency;
*prtTime = 0;
EnterCriticalSection(&m_PChannelInfoCrSec);
for( dw = 0; dw < m_dwNumPorts; dw++ )
{
if( m_pPortTable[dw].rtLast > *prtTime )
*prtTime = m_pPortTable[dw].rtLast;
}
LeaveCriticalSection(&m_PChannelInfoCrSec);
rtLatency = GetLatency();
if( *prtTime < rtLatency )
{
*prtTime = rtLatency;
}
if (m_rtEarliestStartTime > rtLatency)
{
rtLatency = m_rtEarliestStartTime;
}
return S_OK;
}
// private version of AllocPMsg
HRESULT CPerformance::AllocPMsg(
ULONG cb,
PRIV_PMSG** ppPMSG)
{
ASSERT( cb >= sizeof(PRIV_PMSG) );
DMUS_PMSG* pDMUS_PMSG;
HRESULT hr;
hr = AllocPMsg( cb - PRIV_PART_SIZE, &pDMUS_PMSG );
if( SUCCEEDED(hr) )
{
*ppPMSG = DMUS_TO_PRIV(pDMUS_PMSG);
}
return hr;
}
HRESULT STDMETHODCALLTYPE CPerformance::ClonePMsg(DMUS_PMSG* pSourcePMSG,DMUS_PMSG** ppCopyPMSG)
{
V_INAME(IDirectMusicPerformance::ClonePMsg);
#ifdef DBG
V_PTRPTR_WRITE(ppCopyPMSG);
V_BUFPTR_READ(pSourcePMSG,sizeof(DMUS_PMSG));
#else
if (!ppCopyPMSG || !pSourcePMSG)
{
return E_POINTER;
}
#endif
HRESULT hr = AllocPMsg(pSourcePMSG->dwSize,ppCopyPMSG);
if (SUCCEEDED(hr))
{
memcpy(*ppCopyPMSG,pSourcePMSG,pSourcePMSG->dwSize);
if (pSourcePMSG->punkUser)
{
pSourcePMSG->punkUser->AddRef();
}
if (pSourcePMSG->pTool)
{
pSourcePMSG->pTool->AddRef();
}
if (pSourcePMSG->pGraph)
{
pSourcePMSG->pGraph->AddRef();
}
}
return hr;
}
//////////////////////////////////////////////////////////////////////
// CPerformance::AllocPMsg
/*
@method HRESULT | IDirectMusicPerformance | AllocPMsg |
Allocate a DMUS_PMSG.
@rvalue E_OUTOFMEMORY | Out of memory.
@rvalue S_OK | Success.
@rvalue E_INVALIDARG | <p cb> is smaller than sizeof(DMUS_PMSG)
@rvalue E_POINTER | <p ppPMSG> is NULL or invalid.
*/
HRESULT STDMETHODCALLTYPE CPerformance::AllocPMsg(
ULONG cb, // @parm Size of the <p ppPMSG>. Must be equal to or greater
// than sizeof(DMUS_PMSG).
DMUS_PMSG** ppPMSG // @parm Returns the pointer to the allocated message, which will
// be of size <p cb>. All fields are initialized to zero,
// except dwSize which is initialized to <p cb>.
)
{
V_INAME(IDirectMusicPerformance::AllocPMsg);
if( m_dwVersion < 8)
{
V_PTRPTR_WRITE(ppPMSG);
}
else
{
#ifdef DBG
V_PTRPTR_WRITE(ppPMSG);
#else
if (!ppPMSG)
{
return E_POINTER;
}
#endif
}
PRIV_PMSG* pPrivPMsg;
if( cb < sizeof(DMUS_PMSG) )
return E_INVALIDARG;
EnterCriticalSection(&m_PMsgCacheCrSec);
// cached pmsg's are stored in an array based on their public size.
// If a cached pmsg exists, return it. Otherwise, make a new one.
if( (cb >= PERF_PMSG_CB_MIN) && (cb < PERF_PMSG_CB_MAX) )
{
ULONG cbIndex = cb - PERF_PMSG_CB_MIN;
if( m_apPMsgCache[ cbIndex ] )
{
pPrivPMsg = m_apPMsgCache[ cbIndex ];
m_apPMsgCache[ cbIndex ] = pPrivPMsg->pNext;
pPrivPMsg->pNext = NULL;
if (pPrivPMsg->dwPrivFlags != PRIV_FLAG_FREE)
{
Trace(0,"Error - previously freed PMsg has been mangled.\n");
LeaveCriticalSection(&m_PMsgCacheCrSec);
return E_FAIL;
}
pPrivPMsg->dwPrivFlags = PRIV_FLAG_ALLOC;
if (m_fInTrackPlay) pPrivPMsg->dwPrivFlags |= PRIV_FLAG_TRACK;
*ppPMSG = PRIV_TO_DMUS(pPrivPMsg);
LeaveCriticalSection(&m_PMsgCacheCrSec);
return S_OK;
}
}
HRESULT hr = S_OK;
// no cached pmsg exists. Return a new one.
ULONG cbPriv = cb + PRIV_PART_SIZE;
pPrivPMsg = (PRIV_PMSG*)(new char[cbPriv]);
if( pPrivPMsg )
{
memset( pPrivPMsg, 0, cbPriv );
pPrivPMsg->dwSize = pPrivPMsg->dwPrivPubSize = cb; // size of public part only
pPrivPMsg->dwPrivFlags = PRIV_FLAG_ALLOC;
if (m_fInTrackPlay) pPrivPMsg->dwPrivFlags |= PRIV_FLAG_TRACK;
*ppPMSG = PRIV_TO_DMUS(pPrivPMsg);
hr = S_OK;
}
else
{
hr = E_OUTOFMEMORY;
}
LeaveCriticalSection(&m_PMsgCacheCrSec);
return hr;
}
// private version of FreePMsg
HRESULT CPerformance::FreePMsg(
PRIV_PMSG* pPMSG)
{
return FreePMsg( PRIV_TO_DMUS(pPMSG) );
}
HRESULT STDMETHODCALLTYPE CPerformance::FreePMsg(
DMUS_PMSG* pPMSG // @parm The message to free. This message must have been allocated
// using <om .AllocPMsg>.
)
{
V_INAME(IDirectMusicPerformance::FreePMsg);
if( m_dwVersion < 8)
{
V_BUFPTR_WRITE(pPMSG,sizeof(DMUS_PMSG));
}
else
{
#ifdef DBG
V_BUFPTR_WRITE(pPMSG,sizeof(DMUS_PMSG));
#else
if (!pPMSG)
{
return E_POINTER;
}
#endif
}
PRIV_PMSG* pPrivPMsg = DMUS_TO_PRIV(pPMSG);
if( (pPrivPMsg->dwPrivFlags & PRIV_FLAG_ALLOC_MASK) != PRIV_FLAG_ALLOC )
{
Trace(0, "Error --- Attempt to free a PMsg that is not allocated memory.\n");
// this isn't a msg allocated by AllocPMsg.
return DMUS_E_CANNOT_FREE;
}
if( pPrivPMsg->dwPrivFlags & PRIV_FLAG_QUEUED )
{
TraceI(1, "Attempt to free a PMsg that is currently in the Performance queue.\n");
return DMUS_E_CANNOT_FREE;
}
EnterCriticalSection(&m_PMsgCacheCrSec);
if( pPMSG->pTool )
{
pPMSG->pTool->Release();
}
if( pPMSG->pGraph )
{
pPMSG->pGraph->Release();
}
if( pPMSG->punkUser )
{
pPMSG->punkUser->Release();
}
ULONG cbSize = pPrivPMsg->dwPrivPubSize;
if( (cbSize >= PERF_PMSG_CB_MIN) && (cbSize < PERF_PMSG_CB_MAX) )
{
memset( pPrivPMsg, 0, cbSize + PRIV_PART_SIZE );
pPrivPMsg->dwPrivFlags = PRIV_FLAG_FREE; // Mark this as in the free queue.
pPrivPMsg->dwSize = pPrivPMsg->dwPrivPubSize = cbSize;
pPrivPMsg->pNext = m_apPMsgCache[ cbSize - PERF_PMSG_CB_MIN ];
m_apPMsgCache[ cbSize - PERF_PMSG_CB_MIN ] = pPrivPMsg;
}
else
{
delete [] pPrivPMsg;
}
LeaveCriticalSection(&m_PMsgCacheCrSec);
return S_OK;
}
HRESULT CPerformance::FlushVirtualTrack(
DWORD dwId,
MUSIC_TIME mtTime,
BOOL fLeaveNotesOn)
{
EnterCriticalSection(&m_PipelineCrSec);
FlushMainEventQueues( dwId, mtTime, mtTime, fLeaveNotesOn );
LeaveCriticalSection(&m_PipelineCrSec);
return S_OK;
}
/*
Given a time, mtTime, returns the time of the next control segment in pmtNextSeg.
Returns S_FALSE if none found, and sets pmtNextSeg to zero.
*/
HRESULT CPerformance::GetControlSegTime(
MUSIC_TIME mtTime,
MUSIC_TIME* pmtNextSeg)
{
HRESULT hr = S_FALSE;
*pmtNextSeg = 0;
EnterCriticalSection( &m_SegmentCrSec );
// search the secondary lists for a control segment
CSegState* pTemp;
for( pTemp = m_SegStateQueues[SQ_CON_DONE].GetHead(); pTemp; pTemp = pTemp->GetNext() )
{
if( pTemp->m_mtResolvedStart >= mtTime )
{
*pmtNextSeg = pTemp->m_mtResolvedStart;
hr = S_OK;
break;
}
}
if( S_FALSE == hr ) // if this is still zero, check the current queue
{
for( pTemp = m_SegStateQueues[SQ_CON_PLAY].GetHead(); pTemp; pTemp = pTemp->GetNext() )
{
if( pTemp->m_mtResolvedStart >= mtTime )
{
*pmtNextSeg = pTemp->m_mtResolvedStart;
hr = S_OK;
break;
}
}
}
LeaveCriticalSection( &m_SegmentCrSec );
return hr;
}
/*
Given a time, mtTime, returns the time of the next primary segment in pmtNextSeg.
Returns S_FALSE if none found, and sets pmtNextSeg to zero.
*/
HRESULT CPerformance::GetPriSegTime(
MUSIC_TIME mtTime,
MUSIC_TIME* pmtNextSeg)
{
HRESULT hr = S_FALSE;
*pmtNextSeg = 0;
EnterCriticalSection( &m_SegmentCrSec );
CSegState* pTemp;
for( pTemp = m_SegStateQueues[SQ_PRI_PLAY].GetHead(); pTemp; pTemp = pTemp->GetNext() )
{
if( pTemp->m_mtResolvedStart > mtTime )
{
*pmtNextSeg = pTemp->m_mtResolvedStart;
hr = S_OK;
break;
}
}
LeaveCriticalSection( &m_SegmentCrSec );
return hr;
}
/*
@method HRESULT | IDirectMusicPerformance | GetGraph |
Returns the performance's Tool Graph, AddRef'd.
@rvalue S_OK | Success.
@rvalue DMUS_E_NOT_FOUND | There is no graph in the performance, and therefore
one couldn't be returned.
@rvalue E_POINTER | <p ppGraph> is NULL or invalid.
*/
HRESULT STDMETHODCALLTYPE CPerformance::GetGraph(
IDirectMusicGraph** ppGraph // @parm Returns the tool graph pointer.
)
{
V_INAME(IDirectMusicPerformance::GetGraph);
V_PTRPTR_WRITE(ppGraph);
HRESULT hr;
if ((m_dwVersion >= 8) && (m_dwAudioPathMode == 0))
{
Trace(1,"Error: Performance not initialized.\n");
return DMUS_E_NOT_INIT;
}
EnterCriticalSection(&m_MainCrSec);
if( m_pGraph )
{
*ppGraph = m_pGraph;
m_pGraph->AddRef();
hr = S_OK;
}
else
{
Trace(1,"Error: Performance does not currently have a tool graph installed.\n");
hr = DMUS_E_NOT_FOUND;
}
LeaveCriticalSection(&m_MainCrSec);
return hr;
}
HRESULT CPerformance::GetGraphInternal(
IDirectMusicGraph** ppGraph )
{
EnterCriticalSection(&m_MainCrSec);
if( !m_pGraph )
{
m_pGraph = new CGraph;
}
LeaveCriticalSection(&m_MainCrSec);
return GetGraph(ppGraph);
}
/*
@method HRESULT | IDirectMusicPerformance | SetGraph |
Replaces the performance's Tool Graph. <p pGraph> is AddRef'd inside this
method. Any messages flowing through Tools in the current Tool Graph are deleted.
@rvalue S_OK | Success.
@rvalue E_POINTER | <p pGraph> is invalid.
*/
HRESULT STDMETHODCALLTYPE CPerformance::SetGraph(
IDirectMusicGraph* pGraph // @parm The tool graph pointer. May be NULL to clear
// the current graph out of the performance.
)
{
V_INAME(IDirectMusicPerformance::SetGraph);
V_INTERFACE_OPT(pGraph);
if ((m_dwVersion >= 8) && (m_dwAudioPathMode == 0))
{
Trace(1,"Error: Performance not initialized.\n");
return DMUS_E_NOT_INIT;
}
EnterCriticalSection(&m_MainCrSec);
if( m_pGraph )
{
m_pGraph->Release();
}
m_pGraph = pGraph;
if( pGraph )
{
pGraph->AddRef();
}
LeaveCriticalSection(&m_MainCrSec);
return S_OK;
}
HRESULT STDMETHODCALLTYPE CPerformance::SetNotificationHandle(
HANDLE hNotification, // @parm The event handle created by CreateEvent, or
// 0 to clear out an existing handle.
REFERENCE_TIME rtMinimum ) // @parm The minimum amount of time that the
// performance should hold notify messages before discarding them.
// 0 means to use the default minimum time of 20000000 reference time units,
// which is 2 seconds, or the previous value if this API has been called previously.
// If the application hasn't called <om .GetNotificationPMsg> by this time, the message is
// discarded to free the memory.
{
EnterCriticalSection(&m_MainCrSec);
m_hNotification = hNotification;
if( rtMinimum )
{
m_rtNotificationDiscard = rtMinimum;
}
LeaveCriticalSection(&m_MainCrSec);
return S_OK;
}
HRESULT STDMETHODCALLTYPE CPerformance::GetNotificationPMsg(
DMUS_NOTIFICATION_PMSG** ppNotificationPMsg )
{
V_INAME(IDirectMusicPerformance::GetNotificationPMsg);
V_PTRPTR_WRITE(ppNotificationPMsg);
HRESULT hr;
EnterCriticalSection(&m_PipelineCrSec);
if( m_NotificationQueue.GetHead() )
{
PRIV_PMSG* pPriv = m_NotificationQueue.Dequeue();
ASSERT(pPriv);
*ppNotificationPMsg = (DMUS_NOTIFICATION_PMSG*)PRIV_TO_DMUS(pPriv);
hr = S_OK;
}
else
{
*ppNotificationPMsg = NULL;
hr = S_FALSE;
}
LeaveCriticalSection(&m_PipelineCrSec);
return hr;
}
void CPerformance::AddNotificationTypeToAllSegments( REFGUID rguidNotification )
{
CSegState* pSegSt;
DWORD dwCount;
// Note: might be nice to optimize this so the same segment
// doesn't get called multiple times
EnterCriticalSection(&m_SegmentCrSec);
for (dwCount = 0; dwCount < SQ_COUNT; dwCount++)
{
for( pSegSt = m_SegStateQueues[dwCount].GetHead(); pSegSt; pSegSt = pSegSt->GetNext() )
{
pSegSt->m_pSegment->AddNotificationType( rguidNotification, TRUE );
}
}
LeaveCriticalSection(&m_SegmentCrSec);
}
void CPerformance::RemoveNotificationTypeFromAllSegments( REFGUID rguidNotification )
{
CSegState* pSegSt;
DWORD dwCount;
// Note: might be nice to optimize this so the same segment
// doesn't get called multiple times
EnterCriticalSection(&m_SegmentCrSec);
for (dwCount = 0; dwCount < SQ_COUNT; dwCount++)
{
for( pSegSt = m_SegStateQueues[dwCount].GetHead(); pSegSt; pSegSt = pSegSt->GetNext() )
{
pSegSt->m_pSegment->RemoveNotificationType( rguidNotification, TRUE );
}
}
LeaveCriticalSection(&m_SegmentCrSec);
}
/*
Check to see if this notification is already being tracked.
*/
CNotificationItem* CPerformance::FindNotification( REFGUID rguidNotification )
{
CNotificationItem* pItem;
pItem = m_NotificationList.GetHead();
while(pItem)
{
if( rguidNotification == pItem->guidNotificationType )
{
break;
}
pItem = pItem->GetNext();
}
return pItem;
}
/*
@method HRESULT | IDirectMusicPerformance | AddNotificationType |
Adds a notification type to the performance. Notifications are identified
by a guid. When a notification is added to the performance, notify messages
are sent to the application, which provides a message handle on which to
block through <om IDirectMusicPerformance.SetNotificationHandle>. All segments
and tracks are automatically updated with the new notification by calling
their AddNotificationType methods.
@rvalue S_OK | Success.
@rvalue S_FALSE | The requested notification is already on the performance.
@rvalue E_OUTOFMEMORY | Out of memory.
@xref <om .SetNotificationHandle>, <om .GetNotificationPMsg>, <om .RemoveNotificationType>
*/
HRESULT STDMETHODCALLTYPE CPerformance::AddNotificationType(
REFGUID rguidNotification) // @parm The guid of the notification message to add.
{
V_INAME(IDirectMusicPerformance::AddNotificationType);
V_REFGUID(rguidNotification);
CNotificationItem* pItem;
HRESULT hr = S_OK;
EnterCriticalSection(&m_SegmentCrSec);
if( NULL == FindNotification( rguidNotification ) )
{
pItem = new CNotificationItem;
if( NULL == pItem )
{
hr = E_OUTOFMEMORY;
}
else
{
pItem->guidNotificationType = rguidNotification;
m_NotificationList.Cat( pItem );
AddNotificationTypeToAllSegments( rguidNotification );
}
}
else
{
hr = S_FALSE;
}
LeaveCriticalSection(&m_SegmentCrSec);
return hr;
}
/*
@method HRESULT | IDirectMusicPerformance | RemoveNotificationType |
Removes a previously added notification type from the performance. All
segments and tracks are updated with the removed notification by calling
their RemoveNotificationType methods.
@rvalue S_OK | Success.
@rvalue S_FALSE | The requested notification isn't currently active.
@xref <om .SetNotificationHandle>, <om .GetNotificationPMsg>, <om .AddNotificationType>
*/
HRESULT STDMETHODCALLTYPE CPerformance::RemoveNotificationType(
REFGUID rguidNotification) // @parm The guid of the notification message to remove.
// If GUID_NULL, remove all notifications.
{
V_INAME(IDirectMusicPerformance::RemoveNotificationType);
V_REFGUID(rguidNotification);
HRESULT hr = S_OK;
CNotificationItem* pItem;
if( GUID_NULL == rguidNotification )
{
while (pItem = m_NotificationList.RemoveHead())
{
RemoveNotificationTypeFromAllSegments( pItem->guidNotificationType );
delete pItem;
}
}
else
{
if( pItem = FindNotification( rguidNotification ))
{
RemoveNotificationTypeFromAllSegments( pItem->guidNotificationType );
m_NotificationList.Remove( pItem );
delete pItem;
}
else
{
Trace(2,"Warning: Unable to remove requested notification because it is not currently installed.\n");
hr = S_FALSE;
}
}
return hr;
}
void CPerformance::RemoveUnusedPorts()
{
DWORD dwIndex;
EnterCriticalSection(&m_PChannelInfoCrSec);
for( dwIndex = 0; dwIndex < m_dwNumPorts; dwIndex++ )
{
if( m_pPortTable[dwIndex].pPort && !m_AudioPathList.UsesPort(m_pPortTable[dwIndex].pPort))
{
// release the port and buffer. NULL them in the table. PChannels
// that map will return an error code.
ASSERT( m_pPortTable[dwIndex].pBuffer );
m_pPortTable[dwIndex].pPort->Release();
m_pPortTable[dwIndex].pBuffer->Release();
if( m_pPortTable[dwIndex].pLatencyClock )
{
m_pPortTable[dwIndex].pLatencyClock->Release();
}
memset( &m_pPortTable[dwIndex], 0, sizeof( PortTable ));
CChannelBlock *pBlock = m_ChannelBlockList.GetHead();
CChannelBlock *pNext;
for(;pBlock;pBlock = pNext)
{
pNext = pBlock->GetNext();
if (pBlock->m_dwPortIndex == dwIndex)
{
m_ChannelBlockList.Remove(pBlock);
delete pBlock;
}
}
}
}
LeaveCriticalSection(&m_PChannelInfoCrSec);
}
HRESULT CPerformance::GetPathPort(CPortConfig *pConfig)
{
HRESULT hr = S_OK;
DWORD dwPort;
EnterCriticalSection(&m_PChannelInfoCrSec);
GUID &guidScan = pConfig->m_PortHeader.guidPort;
// If we are looking for the default synth, get the class id for the default synth.
BOOL fDefault = (pConfig->m_PortHeader.guidPort == GUID_Synth_Default);
if (fDefault)
{
guidScan = m_AudioParams.clsidDefaultSynth;
}
for (dwPort = 0;dwPort < m_dwNumPorts;dwPort++)
{
if ((m_pPortTable[dwPort].guidPortID == guidScan) && m_pPortTable[dwPort].pPort)
{
pConfig->m_dwPortID = dwPort;
pConfig->m_pPort = m_pPortTable[dwPort].pPort;
pConfig->m_PortParams = m_pPortTable[dwPort].PortParams;
ASSERT(pConfig->m_pPort);
pConfig->m_pPort->AddRef();
break;
}
}
LeaveCriticalSection(&m_PChannelInfoCrSec);
// Failed finding the port, so create it.
if (dwPort >= m_dwNumPorts)
{
BOOL fUseBuffers = FALSE;
pConfig->m_PortParams.dwSampleRate = m_AudioParams.dwSampleRate;
if (m_AudioParams.dwFeatures & DMUS_AUDIOF_STREAMING)
{
pConfig->m_PortParams.dwFeatures |= DMUS_PORT_FEATURE_STREAMING;
}
if (m_AudioParams.dwFeatures & DMUS_AUDIOF_BUFFERS)
{
fUseBuffers = TRUE;
pConfig->m_PortParams.dwFeatures |= DMUS_PORT_FEATURE_AUDIOPATH;
}
pConfig->m_PortParams.dwValidParams |= DMUS_PORTPARAMS_SAMPLERATE | DMUS_PORTPARAMS_FEATURES;
// If this wants a default synth, consult m_AudioParams and create that synth.
if (fDefault)
{
pConfig->m_PortParams.dwAudioChannels = 1;
pConfig->m_PortParams.dwVoices = m_AudioParams.dwVoices;
pConfig->m_PortParams.dwValidParams |= DMUS_PORTPARAMS_AUDIOCHANNELS | DMUS_PORTPARAMS_VOICES;
}
hr = m_pDirectMusic->CreatePort(guidScan,&pConfig->m_PortParams,&pConfig->m_pPort, NULL);
if (SUCCEEDED(hr))
{
if ((pConfig->m_PortParams.dwValidParams & DMUS_PORTPARAMS_FEATURES) && (pConfig->m_PortParams.dwFeatures & DMUS_PORT_FEATURE_AUDIOPATH))
{
IDirectMusicPortP* pPortP = NULL;
// QI for the private interface.
if (SUCCEEDED(pConfig->m_pPort->QueryInterface(IID_IDirectMusicPortP,(void **) &pPortP)))
{
// Connect the port to the sink.
hr = pPortP->SetSink(m_BufferManager.m_pSinkConnect);
pPortP->Release();
}
else
{
Trace(1,"Error: Attempt to create a port with audiopath buffer support failed because synth does not support buffers.\n");
hr = E_INVALIDARG;
}
}
else if (fUseBuffers && fDefault)
{
Trace(1,"Error: Attempt to create a port with audiopath buffer support failed because default synth does not support buffers.\n");
hr = E_INVALIDARG;
}
}
if (SUCCEEDED(hr))
{
// Now add the port to the performance.
hr = AddPort(pConfig->m_pPort,&pConfig->m_PortHeader.guidPort,
&pConfig->m_PortParams,&pConfig->m_dwPortID);
}
if (SUCCEEDED(hr))
{
// Activate the port.
hr = pConfig->m_pPort->Activate(TRUE);
// It's okay if the synth is already active.
if (hr == DMUS_E_SYNTHACTIVE)
{
hr = S_OK;
}
}
if (SUCCEEDED(hr))
{
DWORD dwPortID = GetPortID(pConfig->m_pPort);
// Then create matching channel blocks for all of the channel groups in the port.
for (DWORD dwGroup = 0;dwGroup < pConfig->m_PortParams.dwChannelGroups; dwGroup++)
{
AllocVChannelBlock(dwPortID,dwGroup+1);
}
}
}
return (hr);
}
HRESULT STDMETHODCALLTYPE CPerformance::AddPort(
IDirectMusicPort* pPort)
{
V_INAME(IDirectMusicPerformance::AddPort);
V_INTERFACE_OPT(pPort);
if (m_dwAudioPathMode == 2)
{
Trace(1,"Error: Can not call AddPort() when using AudioPaths.\n");
return DMUS_E_AUDIOPATHS_IN_USE;
}
m_dwAudioPathMode = 1;
return AddPort(pPort,NULL,NULL,NULL);
}
HRESULT CPerformance::AddPort(
IDirectMusicPort* pPort,
GUID *pguidPortID,
DMUS_PORTPARAMS8 *pParams,
DWORD *pdwPortID)
{
PortTable* pPortTable;
IDirectMusicBuffer* pBuffer;
BOOL fSetUpBlock = FALSE;
BOOL fBuiltNewTable = FALSE;
HRESULT hr = S_OK;
GUID guidPortID; // Class ID of port.
DWORD dwChannelGroups; // Number of channel groups at initialization.
DWORD dwNewPortIndex = 0; // Index into port array for new port.
EnterCriticalSection(&m_MainCrSec);
EnterCriticalSection(&m_PChannelInfoCrSec);
if( NULL == m_pDirectMusic )
{
Trace(1,"Error: Performance is not initialized, ports can not be added.\n");
hr = DMUS_E_NOT_INIT;
goto END;
}
for (;dwNewPortIndex < m_dwNumPorts; dwNewPortIndex++)
{
if (!m_pPortTable[dwNewPortIndex].pPort)
{
break;
}
}
if (dwNewPortIndex == m_dwNumPorts)
{
pPortTable = new PortTable[m_dwNumPorts + 1];
if( !pPortTable )
{
hr = E_OUTOFMEMORY;
goto END;
}
fBuiltNewTable = TRUE;
}
// if pPort is NULL, create a software synth port
DMUS_PORTPARAMS dmpp;
if( NULL == pPort )
{
pParams = &dmpp;
memset(&dmpp, 0, sizeof(DMUS_PORTPARAMS) );
dmpp.dwSize = sizeof(DMUS_PORTPARAMS);
dmpp.dwChannelGroups = dwChannelGroups = 1;
dmpp.dwValidParams = DMUS_PORTPARAMS_CHANNELGROUPS |
DMUS_PORTPARAMS_AUDIOCHANNELS;
dmpp.dwAudioChannels = 2;
guidPortID = GUID_NULL;
hr = m_pDirectMusic->CreatePort(GUID_NULL, &dmpp, &pPort, NULL);
if ( SUCCEEDED( hr ) )
{
hr = pPort->Activate(TRUE);
}
fSetUpBlock = TRUE;
}
else
{
if (pguidPortID)
{
guidPortID = *pguidPortID;
}
else
{
DMUS_PORTCAPS PortCaps;
PortCaps.dwSize = sizeof (PortCaps);
pPort->GetCaps(&PortCaps);
guidPortID = PortCaps.guidPort;
}
pPort->GetNumChannelGroups(&dwChannelGroups);
pPort->AddRef();
}
if( FAILED(hr) || ( pPort == NULL ) )
{
if (fBuiltNewTable) delete [] pPortTable;
Trace(1,"Error: Unable to open requested port.\n");
hr = DMUS_E_CANNOT_OPEN_PORT;
goto END;
}
// Create a buffer
DMUS_BUFFERDESC dmbd;
memset( &dmbd, 0, sizeof(DMUS_BUFFERDESC) );
dmbd.dwSize = sizeof(DMUS_BUFFERDESC);
dmbd.cbBuffer = DEFAULT_BUFFER_SIZE;
if( FAILED( m_pDirectMusic->CreateMusicBuffer(&dmbd, &pBuffer, NULL)))
{
if (fBuiltNewTable) delete [] pPortTable;
pPort->Release();
Trace(1,"Error: Unable to create MIDI buffer for port.\n");
hr = DMUS_E_CANNOT_OPEN_PORT;
goto END;
}
if (fBuiltNewTable)
{
// if there is an existing port table, copy its contents to the new, bigger, port table
if( m_pPortTable )
{
if( m_dwNumPorts > 0 )
{
memcpy( pPortTable, m_pPortTable, sizeof(PortTable) * ( m_dwNumPorts ) );
}
delete [] m_pPortTable;
}
m_pPortTable = pPortTable;
}
if (pdwPortID)
{
*pdwPortID = dwNewPortIndex;
}
pPortTable = &m_pPortTable[dwNewPortIndex];
pPortTable->pPort = pPort;
// If we have a passed params structure, copy it. This will be used for identifying the
// params as initialized by the synth.
if (pParams)
{
pPortTable->PortParams = *pParams;
}
pPortTable->dwGMFlags = 0;
//set master volume
IKsControl *pControl;
if(SUCCEEDED(pPort->QueryInterface(IID_IKsControl, (void**)&pControl)))
{
KSPROPERTY ksp;
ULONG cb;
memset(&ksp, 0, sizeof(ksp));
ksp.Set = GUID_DMUS_PROP_Volume;
ksp.Id = 0;
ksp.Flags = KSPROPERTY_TYPE_SET;
pControl->KsProperty(&ksp,
sizeof(ksp),
(LPVOID)&m_lMasterVolume,
sizeof(m_lMasterVolume),
&cb);
// Now, find out if it has a gm, gs, or xg sets in rom...
BOOL bIsSupported = FALSE;
ksp.Set = GUID_DMUS_PROP_GM_Hardware;
ksp.Flags = KSPROPERTY_TYPE_GET;
hr = pControl->KsProperty(&ksp,
sizeof(ksp),
(LPVOID)&bIsSupported,
sizeof(bIsSupported),
&cb);
if (SUCCEEDED(hr) && (bIsSupported))
{
pPortTable->dwGMFlags |= DM_PORTFLAGS_GM;
}
bIsSupported = FALSE;
ksp.Set = GUID_DMUS_PROP_GS_Hardware;
ksp.Flags = KSPROPERTY_TYPE_GET;
hr = pControl->KsProperty(&ksp,
sizeof(ksp),
(LPVOID)&bIsSupported,
sizeof(bIsSupported),
&cb);
if (SUCCEEDED(hr) && (bIsSupported))
{
pPortTable->dwGMFlags |= DM_PORTFLAGS_GS;
}
bIsSupported = FALSE;
ksp.Set = GUID_DMUS_PROP_XG_Hardware;
ksp.Flags = KSPROPERTY_TYPE_GET;
hr = pControl->KsProperty(&ksp,
sizeof(ksp),
(LPVOID)&bIsSupported,
sizeof(bIsSupported),
&cb);
if (SUCCEEDED(hr) && (bIsSupported))
{
pPortTable->dwGMFlags |= DM_PORTFLAGS_XG;
}
pControl->Release();
}
if( FAILED( pPort->GetLatencyClock( &pPortTable->pLatencyClock )))
{
pPortTable->pLatencyClock = NULL;
}
pPortTable->dwChannelGroups = dwChannelGroups;
pPortTable->guidPortID = guidPortID;
pPortTable->pBuffer = pBuffer;
pPortTable->fBufferFilled = FALSE;
pPortTable->rtLast = 0;
if (fBuiltNewTable) m_dwNumPorts++; // must do this before calling AssignPChannelBlock
if( fSetUpBlock && m_ChannelBlockList.IsEmpty() ) // set up default PChannel map if none already set
{
AssignPChannelBlock( 0, pPort, 1);
}
hr = S_OK;
END:
LeaveCriticalSection(&m_PChannelInfoCrSec);
LeaveCriticalSection(&m_MainCrSec);
return hr;
}
HRESULT STDMETHODCALLTYPE CPerformance::RemovePort(
IDirectMusicPort* pPort // @parm The port to remove.
)
{
V_INAME(IDirectMusicPerformance::RemovePort);
V_INTERFACE(pPort);
DWORD dwIndex;
HRESULT hr = E_INVALIDARG;
EnterCriticalSection(&m_PChannelInfoCrSec);
for( dwIndex = 0; dwIndex < m_dwNumPorts; dwIndex++ )
{
if( m_pPortTable[dwIndex].pPort == pPort )
{
// release the port and buffer. NULL them in the table. PChannels
// that map will return an error code.
ASSERT( m_pPortTable[dwIndex].pBuffer );
m_pPortTable[dwIndex].pPort->Release();
m_pPortTable[dwIndex].pBuffer->Release();
if( m_pPortTable[dwIndex].pLatencyClock )
{
m_pPortTable[dwIndex].pLatencyClock->Release();
}
memset( &m_pPortTable[dwIndex], 0, sizeof( PortTable ));
hr = S_OK;
break;
}
}
#ifdef DBG
if (hr == E_INVALIDARG)
{
Trace(1,"Error: Invalid port passed to RemovePort().\n");
}
#endif
LeaveCriticalSection(&m_PChannelInfoCrSec);
return hr;
}
// this must be called from within a PChannelCrSec critical section.
HRESULT CPerformance::AssignPChannelBlock(
DWORD dwBlockNum,
DWORD dwPortIndex,
DWORD dwGroup,
WORD wFlags)
{
// see if we've already allocated this block before
// blocknum is PChannel / 16, so search on that.
DWORD dwPChannel = dwBlockNum * 16;
CChannelBlock* pChannelBlock = m_ChannelBlockList.GetHead();
for( ; pChannelBlock; pChannelBlock = pChannelBlock->GetNext() )
{
if( pChannelBlock->m_dwPChannelStart == dwPChannel )
{
pChannelBlock->Init(dwPChannel,dwPortIndex,dwGroup,wFlags);
break;
}
}
if( !pChannelBlock )
{
pChannelBlock = new CChannelBlock;
if( !pChannelBlock )
{
return E_OUTOFMEMORY;
}
pChannelBlock->Init(dwPChannel,dwPortIndex,dwGroup,wFlags);
m_ChannelBlockList.AddHead(pChannelBlock);
pChannelBlock->m_dwPChannelStart = dwPChannel;
}
return S_OK;
}
// this must be called from within a PChannelCrSec critical section.
HRESULT CPerformance::AssignPChannel(
DWORD dwPChannel,
DWORD dwPortIndex,
DWORD dwGroup,
DWORD dwMChannel,
WORD wFlags)
{
DWORD dwIndex;
CChannelBlock* pChannelBlock = m_ChannelBlockList.GetHead();
for( ; pChannelBlock; pChannelBlock = pChannelBlock->GetNext() )
{
if( pChannelBlock->m_dwPChannelStart <= dwPChannel )
{
if( pChannelBlock->m_dwPChannelStart + PCHANNEL_BLOCKSIZE > dwPChannel )
{
break;
}
}
}
if( !pChannelBlock )
{
// there is no currently existing block that encompases dwPChannel.
// Create one.
pChannelBlock = new CChannelBlock;
if( !pChannelBlock )
{
return E_OUTOFMEMORY;
}
pChannelBlock->Init(dwPChannel,0,0,CMAP_FREE);
m_ChannelBlockList.AddHead(pChannelBlock);
}
dwIndex = dwPChannel - pChannelBlock->m_dwPChannelStart;
ASSERT( dwIndex < PCHANNEL_BLOCKSIZE );
CChannelMap *pMap = &pChannelBlock->m_aChannelMap[dwIndex];
pMap->dwPortIndex = dwPortIndex;
pMap->dwGroup = dwGroup;
pMap->dwMChannel = dwMChannel;
pMap->nTranspose = 0;
if ((pMap->wFlags & CMAP_FREE) && !(wFlags & CMAP_FREE))
pChannelBlock->m_dwFreeChannels--;
else if (!(pMap->wFlags & CMAP_FREE) && (wFlags & CMAP_FREE))
pChannelBlock->m_dwFreeChannels++;
pMap->wFlags = wFlags;
return S_OK;
}
HRESULT STDMETHODCALLTYPE CPerformance::AssignPChannelBlock(
DWORD dwBlockNum, // @parm The block number. Should be 0 or greater.
IDirectMusicPort* pPort, // @parm The port.
DWORD dwGroup // @parm The group on the port. Should be 1 or greater.
)
{
V_INAME(IDirectMusicPerformance::AssignPChannelBlock);
V_INTERFACE(pPort);
if (m_dwAudioPathMode == 2)
{
Trace(1,"Error: Can not call AssignPChannelBlock() when using AudioPaths.\n");
return DMUS_E_AUDIOPATHS_IN_USE;
}
m_dwAudioPathMode = 1;
DWORD dwIndex;
HRESULT hr = E_INVALIDARG;
EnterCriticalSection(&m_PChannelInfoCrSec);
for( dwIndex = 0; dwIndex < m_dwNumPorts; dwIndex++ )
{
if( m_pPortTable[dwIndex].pPort == pPort )
{
if( SUCCEEDED( hr = AssignPChannelBlock( dwBlockNum, dwIndex, dwGroup, CMAP_STATIC )))
{
if (m_pPortTable[dwIndex].dwChannelGroups < dwGroup)
{
hr = S_FALSE;
}
}
break;
}
}
#ifdef DBG
if (hr == E_INVALIDARG)
{
Trace(1,"Error: AssignPChannelBlock() called with invalid port.\n");
}
#endif
LeaveCriticalSection(&m_PChannelInfoCrSec);
return hr;
}
HRESULT STDMETHODCALLTYPE CPerformance::AssignPChannel(
DWORD dwPChannel, // @parm The PChannel.
IDirectMusicPort* pPort, // @parm The port.
DWORD dwGroup, // @parm The group on the port.
DWORD dwMChannel // @parm The channel on the group.
)
{
V_INAME(IDirectMusicPerformance::AssignPChannel);
V_INTERFACE(pPort);
if (m_dwAudioPathMode == 2)
{
Trace(1,"Error: Can not call AssignPChannel() when using AudioPaths.\n");
return DMUS_E_AUDIOPATHS_IN_USE;
}
m_dwAudioPathMode = 1;
DWORD dwIndex;
HRESULT hr = E_INVALIDARG;
if( (dwMChannel < 0) || (dwMChannel > 15))
{
Trace(1,"Error: AssignPChannel() called with invalid MIDI Channel %ld.\n",dwMChannel);
return E_INVALIDARG;
}
EnterCriticalSection(&m_PChannelInfoCrSec);
for( dwIndex = 0; dwIndex < m_dwNumPorts; dwIndex++ )
{
if( m_pPortTable[dwIndex].pPort == pPort )
{
if( SUCCEEDED( hr = AssignPChannel( dwPChannel, dwIndex, dwGroup, dwMChannel, CMAP_STATIC )))
{
if (m_pPortTable[dwIndex].dwChannelGroups < dwGroup)
{
hr = S_FALSE;
}
}
break;
}
}
LeaveCriticalSection(&m_PChannelInfoCrSec);
return hr;
}
/* ReleasePChannel finds the requested PChannel and makes it available
for reuse.
It also calls ResetAllControllers(), which sends MIDI CC 121 and 123,
reset all controllers and all notes off.
*/
HRESULT CPerformance::ReleasePChannel(DWORD dwPChannel)
{
HRESULT hr = E_INVALIDARG;
EnterCriticalSection(&m_PChannelInfoCrSec);
CChannelBlock* pChannelBlock = m_ChannelBlockList.GetHead();
for( ; pChannelBlock; pChannelBlock = pChannelBlock->GetNext() )
{
if( pChannelBlock->m_dwPChannelStart <= dwPChannel )
{
if( pChannelBlock->m_dwPChannelStart + PCHANNEL_BLOCKSIZE > dwPChannel )
{
break;
}
}
}
if( pChannelBlock )
{
// Only release if this is genuinely a virtual pchannel. Otherwise, leave alone.
CChannelMap *pMap = &pChannelBlock->m_aChannelMap[dwPChannel - pChannelBlock->m_dwPChannelStart];
if (pMap->wFlags & CMAP_VIRTUAL)
{
pChannelBlock->m_dwFreeChannels++;
// Clear out all the merge lists, etc.
pMap->Clear();
// Reset controllers, but don't send a GM reset.
ResetAllControllers(pMap,0, false);
}
hr = S_OK;
}
LeaveCriticalSection(&m_PChannelInfoCrSec);
return hr;
}
HRESULT CPerformance::GetPort(DWORD dwPortID, IDirectMusicPort **ppPort)
{
HRESULT hr;
EnterCriticalSection(&m_PChannelInfoCrSec);
if (dwPortID < m_dwNumPorts)
{
*ppPort = m_pPortTable[dwPortID].pPort;
(*ppPort)->AddRef();
hr = S_OK;
}
else
{
Trace(1,"Error: Unable to find requested port.\n");
hr = E_FAIL;
}
LeaveCriticalSection(&m_PChannelInfoCrSec);
return hr;
}
HRESULT CPerformance::AllocVChannel(DWORD dwPortID, DWORD dwDrumFlags, DWORD *pdwPChannel, DWORD *pdwGroup,DWORD *pdwMChannel)
{
// dwDrumsFlags:
// bit 0 determines whether this port separates out drums on channel 10.
// bit 1 determines whether this request is for a drum.
// First, figure out if we are scanning for drums on channel 10, melodic instruments
// on the other channels, or any on all channels.
static DWORD sdwSearchForDrums[1] = { 9 };
static DWORD sdwSearchForAll[16] = { 0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15 };
static DWORD sdwSearchForMelodic[15] = { 0,1,2,3,4,5,6,7,8,10,11,12,13,14,15 };
DWORD *pSearchArray = sdwSearchForAll;
DWORD dwSearchSize = 16;
if (dwDrumFlags & 1) // Do we handle drums as a special case for channel 10?
{
if (dwDrumFlags & 2) // And are we looking for drums on channel 10?
{
pSearchArray = sdwSearchForDrums;
dwSearchSize = 1;
}
else
{
pSearchArray = sdwSearchForMelodic;
dwSearchSize = 15;
}
}
HRESULT hr = E_INVALIDARG; // Return this if the vChannel is out of range.
EnterCriticalSection(&m_PChannelInfoCrSec);
CChannelBlock* pChannelBlock = m_ChannelBlockList.GetHead();
BOOL fNotFound = TRUE; // Use to indicate when we finally find a match.
DWORD dwHighestPChannel = 0; // Keep track of the highest PCHannel in use, this will be
// used to create a new PChannel block, if needed.
DWORD dwChannel;
for (;fNotFound && pChannelBlock;pChannelBlock = pChannelBlock->GetNext() )
{
if (dwHighestPChannel < pChannelBlock->m_dwPChannelStart)
{
dwHighestPChannel = pChannelBlock->m_dwPChannelStart;
}
if ((pChannelBlock->m_dwPortIndex == dwPortID) && (pChannelBlock->m_dwFreeChannels))
{
DWORD dwIndex;
for (dwIndex = 0; dwIndex < dwSearchSize; dwIndex++)
{
dwChannel = pSearchArray[dwIndex];
if (pChannelBlock->m_aChannelMap[dwChannel].wFlags & CMAP_FREE)
{
*pdwPChannel = pChannelBlock->m_dwPChannelStart + dwChannel;
pChannelBlock->m_dwFreeChannels--;
pChannelBlock->m_aChannelMap[dwChannel].wFlags = CMAP_VIRTUAL;
*pdwGroup = pChannelBlock->m_aChannelMap[dwChannel].dwGroup;
*pdwMChannel = pChannelBlock->m_aChannelMap[dwChannel].dwMChannel;
fNotFound = FALSE;
hr = S_OK;
break;
}
}
}
}
if( fNotFound )
{
// there is no currently existing block that has a free channel.
// Create one.
IDirectMusicPort *pPort = m_pPortTable[dwPortID].pPort;
DWORD dwChannelGroupCount;
pPort->GetNumChannelGroups(&dwChannelGroupCount);
dwChannelGroupCount++;
hr = pPort->SetNumChannelGroups(dwChannelGroupCount);
if (SUCCEEDED(hr))
{
m_pPortTable[dwPortID].dwChannelGroups = dwChannelGroupCount;
hr = E_OUTOFMEMORY;
dwHighestPChannel += PCHANNEL_BLOCKSIZE;
pChannelBlock = new CChannelBlock;
if (pChannelBlock)
{
pChannelBlock->Init(dwHighestPChannel,dwPortID,dwChannelGroupCount,CMAP_FREE);
m_ChannelBlockList.AddTail(pChannelBlock);
dwChannel = pSearchArray[0]; // Which channel should we use?
CChannelMap *pMap = &pChannelBlock->m_aChannelMap[dwChannel];
pMap->dwMChannel = dwChannel;
pMap->wFlags = CMAP_VIRTUAL;
pChannelBlock->m_dwFreeChannels--;
*pdwPChannel = dwChannel + dwHighestPChannel;
*pdwGroup = pMap->dwGroup;
*pdwMChannel = dwChannel;
hr = S_OK;
}
}
}
#ifdef DBG
if (hr == E_INVALIDARG)
{
Trace(1,"Error: Unable to allocated dynamic PChannel.\n");
}
#endif
LeaveCriticalSection(&m_PChannelInfoCrSec);
return hr;
}
HRESULT CPerformance::AllocVChannelBlock(DWORD dwPortID,DWORD dwGroup)
{
EnterCriticalSection(&m_PChannelInfoCrSec);
CChannelBlock* pChannelBlock = m_ChannelBlockList.GetHead();
long lHighestPChannel = -PCHANNEL_BLOCKSIZE;
for (;pChannelBlock;pChannelBlock = pChannelBlock->GetNext() )
{
if (lHighestPChannel < (long) pChannelBlock->m_dwPChannelStart)
{
lHighestPChannel = pChannelBlock->m_dwPChannelStart;
}
}
HRESULT hr = E_OUTOFMEMORY;
lHighestPChannel += PCHANNEL_BLOCKSIZE;
pChannelBlock = new CChannelBlock;
if (pChannelBlock)
{
pChannelBlock->Init((DWORD) lHighestPChannel,dwPortID,dwGroup,CMAP_FREE);
m_ChannelBlockList.AddTail(pChannelBlock);
hr = S_OK;
}
LeaveCriticalSection(&m_PChannelInfoCrSec);
return hr;
}
#ifdef DBG
void CPerformance::TraceAllChannelMaps()
{
EnterCriticalSection(&m_PChannelInfoCrSec);
CChannelBlock* pChannelBlock = m_ChannelBlockList.GetHead();
for( ; pChannelBlock; pChannelBlock = pChannelBlock->GetNext() )
{
TraceI(0,"ChannelBlock %lx, Free %ld\n",pChannelBlock->m_dwPChannelStart,pChannelBlock->m_dwFreeChannels);
DWORD dwIndex;
for (dwIndex = 0; dwIndex < PCHANNEL_BLOCKSIZE; dwIndex++)
{
CChannelMap *pMap = &pChannelBlock->m_aChannelMap[dwIndex];
TraceI(0,"\tPort %ld, Group: %ld, MIDI: %ld, Transpose: %ld, Flags: %ld\n",
pMap->dwPortIndex, pMap->dwGroup, pMap->dwMChannel, (long) pMap->nTranspose, (long) pMap->wFlags);
}
}
LeaveCriticalSection(&m_PChannelInfoCrSec);
}
#endif
/* Note that the following must be called from within a m_PChannelInfoCrSec
critical section and stay within that critical section for the duration
of using the returned CChannelMap.
*/
CChannelMap * CPerformance::GetPChannelMap( DWORD dwPChannel )
{
CChannelBlock* pChannelBlock = m_ChannelBlockList.GetHead();
for( ; pChannelBlock; pChannelBlock = pChannelBlock->GetNext() )
{
if( ( dwPChannel >= pChannelBlock->m_dwPChannelStart ) &&
( dwPChannel < pChannelBlock->m_dwPChannelStart + PCHANNEL_BLOCKSIZE ) )
{
CChannelMap* pChannelMap;
pChannelMap = &pChannelBlock->m_aChannelMap[ dwPChannel - pChannelBlock->m_dwPChannelStart ];
if( pChannelMap->dwGroup == 0 )
{
// this PChannel isn't on a valid group, therefore it hasn't
// been set.
// return NULL;
}
return pChannelMap;
}
}
return NULL;
}
/*
internal version
*/
HRESULT CPerformance::PChannelIndex( DWORD dwPChannel, DWORD* pdwIndex,
DWORD* pdwGroup, DWORD* pdwMChannel, short* pnTranspose )
{
if (m_dwAudioPathMode == 0)
{
Trace(1,"Error: Performance not initialized.\n");
return DMUS_E_NOT_INIT;
}
HRESULT hr;
EnterCriticalSection(&m_PChannelInfoCrSec);
CChannelMap *pChannelMap = GetPChannelMap(dwPChannel);
if (pChannelMap)
{
ASSERT( pdwIndex && pdwGroup && pdwMChannel );
*pdwIndex = pChannelMap->dwPortIndex;
*pdwGroup = pChannelMap->dwGroup;
*pdwMChannel = pChannelMap->dwMChannel;
if( pnTranspose )
{
*pnTranspose = pChannelMap->nTranspose;
}
hr = S_OK;
}
else
{
Trace(1,"Error: PChannel %ld has not been assigned to a port.\n",dwPChannel);
if (m_dwVersion < 8)
{
hr = E_INVALIDARG;
}
else
{
hr = DMUS_E_AUDIOPATH_NOPORT;
}
}
LeaveCriticalSection(&m_PChannelInfoCrSec);
return hr;
}
DWORD CPerformance::GetPortID(IDirectMusicPort * pPort)
{
EnterCriticalSection(&m_PChannelInfoCrSec);
DWORD dwID = 0;
for (;dwID < m_dwNumPorts; dwID++)
{
if (pPort == m_pPortTable[dwID].pPort)
{
break;
}
}
LeaveCriticalSection(&m_PChannelInfoCrSec);
if (dwID == m_dwNumPorts) dwID = 0;
return dwID;
}
STDMETHODIMP CPerformance::GetPortAndFlags(DWORD dwPChannel,IDirectMusicPort **ppPort,DWORD * pdwFlags)
{
EnterCriticalSection(&m_PChannelInfoCrSec);
DWORD dwIndex;
DWORD dwGroup;
DWORD dwMChannel;
HRESULT hr = PChannelIndex( dwPChannel, &dwIndex, &dwGroup, &dwMChannel, NULL );
if (SUCCEEDED(hr))
{
*ppPort = m_pPortTable[dwIndex].pPort;
if( *ppPort )
{
m_pPortTable[dwIndex].pPort->AddRef();
}
else
{
Trace(1,"Error: Performance does not have a port assigned to PChannel %ld.\n",dwPChannel);
hr = DMUS_E_NOT_INIT;
}
*pdwFlags = m_pPortTable[dwIndex].dwGMFlags;
}
LeaveCriticalSection(&m_PChannelInfoCrSec);
return hr;
}
STDMETHODIMP CPerformance::PChannelInfo(
DWORD dwPChannel, // @parm The PChannel to convert.
IDirectMusicPort** ppPort, // @parm Returns the port. May be NULL.
DWORD* pdwGroup, // @parm Returns the group on the port. May be NULL.
DWORD* pdwMChannel // @parm Returns the channel on the group. May be NULL.
)
{
V_INAME(IDirectMusicPerformance::PChannelInfo);
V_PTRPTR_WRITE_OPT(ppPort);
V_PTR_WRITE_OPT(pdwGroup,DWORD);
V_PTR_WRITE_OPT(pdwMChannel,DWORD);
DWORD dwIndex, dwGroup, dwMChannel;
HRESULT hr;
if (m_dwAudioPathMode == 0)
{
Trace(1,"Error: Performance not initialized.\n");
return DMUS_E_NOT_INIT;
}
EnterCriticalSection(&m_PChannelInfoCrSec);
if( SUCCEEDED( PChannelIndex( dwPChannel, &dwIndex, &dwGroup, &dwMChannel )))
{
if( ppPort )
{
*ppPort = m_pPortTable[dwIndex].pPort;
if( *ppPort )
{
m_pPortTable[dwIndex].pPort->AddRef();
}
}
if( pdwGroup )
{
*pdwGroup = dwGroup;
}
if( pdwMChannel )
{
*pdwMChannel = dwMChannel;
}
hr = S_OK;
}
else
{
// No need to print an error message because PChannelIndex() does it.
hr = E_INVALIDARG;
}
LeaveCriticalSection(&m_PChannelInfoCrSec);
return hr;
}
/*
@method HRESULT | IDirectMusicPerformance | DownloadInstrument |
Downloads an IDirectMusicInstrument to the IDirectMusicPort specified by
the selected PChannel.
@rvalue E_INVALIDARG | The PChannel isn't assigned to a Port, or the Port failed
to download the instrument. No return parameter is valid.
@rvalue S_OK | Success.
@rvalue E_POINTER | One of the pointers is invalid.
*/
HRESULT STDMETHODCALLTYPE CPerformance::DownloadInstrument(
IDirectMusicInstrument* pInst, // @parm The instrument to download.
DWORD dwPChannel, // @parm The PChannel to assign the instrument.
IDirectMusicDownloadedInstrument** ppDownInst, // @parm Returns the downloaded instrument.
DMUS_NOTERANGE* pNoteRanges, // @parm A pointer to an array of DMUS_NOTERANGE structures
DWORD dwNumNoteRanges, // @parm Number of DMUS_NOTERANGE structures in array pointed to by pNoteRanges
IDirectMusicPort** ppPort, // @parm Returns the port to which the instrument was downloaded.
DWORD* pdwGroup, // @parm Returns the group to which the instrument was assigned.
DWORD* pdwMChannel // @parm Returns the MChannel to which the instrument was assigned.
)
{
V_INAME(IDirectMusicPerformance::DownloadInstrument);
V_INTERFACE(pInst);
V_PTRPTR_WRITE(ppDownInst);
V_BUFPTR_READ_OPT(pNoteRanges, (sizeof(DMUS_NOTERANGE) * dwNumNoteRanges));
V_PTRPTR_WRITE(ppPort);
V_PTR_WRITE(pdwGroup,DWORD);
V_PTR_WRITE(pdwMChannel,DWORD);
DWORD dwIndex, dwGroup, dwMChannel;
IDirectMusicPort* pPort = NULL;
HRESULT hr = E_INVALIDARG;
if (m_dwAudioPathMode == 0)
{
Trace(1,"Error: Performance not initialized.\n");
return DMUS_E_NOT_INIT;
}
EnterCriticalSection(&m_PChannelInfoCrSec);
if( SUCCEEDED( PChannelIndex( dwPChannel, &dwIndex, &dwGroup, &dwMChannel )))
{
pPort = m_pPortTable[dwIndex].pPort;
if( pPort )
{
hr = pPort->DownloadInstrument( pInst, ppDownInst, pNoteRanges, dwNumNoteRanges );
pPort->AddRef();
}
}
else
{
Trace(1,"Error: Download attempted on unassigned PChannel %ld\n",dwPChannel);
}
LeaveCriticalSection(&m_PChannelInfoCrSec);
if( SUCCEEDED(hr) )
{
*ppPort = pPort;
pPort->AddRef();
*pdwGroup = dwGroup;
*pdwMChannel = dwMChannel;
}
if( pPort )
{
pPort->Release();
}
return hr;
}
/*
@method HRESULT | IDirectMusicPerformance | Invalidate |
Flushes all methods from <p mtTime> forward, and seeks all Segments back
to <p mtTime>, thereby calling all Tracks to resend their data.
@rvalue S_OK | Success.
@rvalue DMUS_E_NO_MASTER_CLOCK | There is no master clock in the performance.
Make sure to call <om .Init> before calling this method.
@comm If <p mtTime> is so long ago that it is impossible to invalidate that time,
the earliest possible time will be used.
*/
HRESULT STDMETHODCALLTYPE CPerformance::Invalidate(
MUSIC_TIME mtTime, // @parm The time to invalidate, adjusted by <p dwFlags>. 0 means now.
DWORD dwFlags) // @parm Adjusts <p mtTime> to align to measures, beats, etc. See
// <t DMPLAYSEGFLAGS>.
{
EnterCriticalSection(&m_MainCrSec);
if( m_pClock == NULL )
{
LeaveCriticalSection(&m_MainCrSec);
Trace(1,"Error: Invalidate() failed because the performance has not been initialized.\n");
return DMUS_E_NO_MASTER_CLOCK;
}
LeaveCriticalSection(&m_MainCrSec);
EnterCriticalSection( &m_SegmentCrSec );
EnterCriticalSection( &m_PipelineCrSec );
SendBuffers();
// make sure mtTime is greater than the current queue time
REFERENCE_TIME rtQueue;
MUSIC_TIME mtQueue;
MUSIC_TIME mtBumperLength;
GetQueueTime( &rtQueue );
ReferenceToMusicTime( rtQueue, &mtQueue );
ReferenceToMusicTime( m_rtBumperLength, &mtBumperLength );
if( mtTime < mtQueue + mtBumperLength )
{
mtTime = mtQueue + mtBumperLength;
}
// resolve mtTime to the boundary of dwFlags
mtTime = ResolveTime( mtTime, dwFlags, NULL );
// flush messages
FlushMainEventQueues( 0, mtTime, mtQueue, FALSE );
// move any segments in the past list that are affected into the current list
CSegState *pSegSt;
CSegState *pNext;
for (pSegSt = m_SegStateQueues[SQ_SEC_DONE].GetHead();pSegSt;pSegSt = pNext)
{
pNext = pSegSt->GetNext();
if( pSegSt->m_mtLastPlayed > mtTime )
{
m_SegStateQueues[SQ_SEC_DONE].Remove(pSegSt);
m_SegStateQueues[SQ_SEC_PLAY].Insert( pSegSt );
}
}
for (pSegSt = m_SegStateQueues[SQ_CON_DONE].GetHead();pSegSt;pSegSt = pNext)
{
pNext = pSegSt->GetNext();
if( pSegSt->m_mtLastPlayed > mtTime )
{
m_SegStateQueues[SQ_CON_DONE].Remove(pSegSt);
m_SegStateQueues[SQ_CON_PLAY].Insert( pSegSt );
}
}
pSegSt = m_SegStateQueues[SQ_PRI_DONE].GetTail();
if(pSegSt)
{
// only check the last one in this list
if( pSegSt->m_mtLastPlayed > mtTime )
{
m_SegStateQueues[SQ_PRI_DONE].Remove(pSegSt);
m_SegStateQueues[SQ_PRI_PLAY].Insert( pSegSt );
}
}
// seek back any affected segmentstates that were playing
DWORD dwCount;
for( dwCount = SQ_PRI_PLAY; dwCount <= SQ_SEC_PLAY; dwCount++ )
{
for( pSegSt = m_SegStateQueues[dwCount].GetHead(); pSegSt; pSegSt = pSegSt->GetNext() )
{
if( pSegSt->m_fStartedPlay )
{
if (SQ_PRI_PLAY == dwCount && pSegSt->m_mtResolvedStart >= mtTime)
{
// resend the segment start notification
pSegSt->GenerateNotification( DMUS_NOTIFICATION_SEGSTART, pSegSt->m_mtResolvedStart );
// if this is a primary or controlling segment, resend a DMUS_PMSGT_DIRTY message
if( !(pSegSt->m_dwPlaySegFlags & DMUS_SEGF_SECONDARY) || (pSegSt->m_dwPlaySegFlags & DMUS_SEGF_CONTROL) )
{
TraceI(4, "ReSend Dirty PMsg [3] %d (%d)\n", pSegSt->m_mtSeek, pSegSt->m_mtOffset + pSegSt->m_mtSeek);
pSegSt->SendDirtyPMsg( pSegSt->m_mtOffset + pSegSt->m_mtSeek );
}
}
if( pSegSt->m_mtLastPlayed > mtTime )
{
// if mtTime is after the actual start time of the segment,
// set it so the segment has never been played before and
// seek the segment to the beginning
if( pSegSt->m_mtResolvedStart > mtTime )
{
pSegSt->m_mtLastPlayed = pSegSt->m_mtResolvedStart;
pSegSt->m_fStartedPlay = FALSE;
}
else
{
pSegSt->m_mtLastPlayed = mtTime;
}
pSegSt->SetInvalidate( pSegSt->m_mtLastPlayed );
}
}
}
}
LeaveCriticalSection( &m_PipelineCrSec );
LeaveCriticalSection( &m_SegmentCrSec );
// signal the transport thread so we don't have to wait for it to wake up on its own
if( m_hTransport ) SetEvent( m_hTransport );
return S_OK;
}
STDMETHODIMP CPerformance::SetParamHook(IDirectMusicParamHook *pIHook)
{ V_INAME(IDirectMusicPerformance::SetParamHook);
V_INTERFACE_OPT(pIHook);
EnterCriticalSection(&m_MainCrSec);
if (m_pParamHook)
{
m_pParamHook->Release();
}
m_pParamHook = pIHook;
if (pIHook)
{
pIHook->AddRef();
}
LeaveCriticalSection(&m_MainCrSec);
return S_OK;
}
HRESULT STDMETHODCALLTYPE CPerformance::GetParamEx(
REFGUID rguidType,
DWORD dwTrackID,
DWORD dwGroupBits,
DWORD dwIndex,
MUSIC_TIME mtTime,
MUSIC_TIME* pmtNext,
void* pData)
{
V_INAME(IDirectMusicPerformance::GetParamEx);
V_PTR_WRITE_OPT(pmtNext,MUSIC_TIME);
V_PTR_WRITE_OPT(pData,1);
V_REFGUID(rguidType);
static DWORD dwSearchOrder[SQ_COUNT] = { SQ_PRI_PLAY, SQ_SEC_PLAY,
SQ_PRI_DONE, SQ_SEC_DONE,
SQ_PRI_WAIT, SQ_SEC_WAIT,
SQ_CON_PLAY, SQ_CON_DONE,
SQ_CON_WAIT };
DWORD dwIX;
HRESULT hr;
CSegState *pSegNode;
if (dwTrackID)
{
EnterCriticalSection(&m_SegmentCrSec);
for (dwIX = 0; dwIX < SQ_COUNT; dwIX++)
{
pSegNode = m_SegStateQueues[dwSearchOrder[dwIX]].GetHead();
for (;pSegNode;pSegNode = pSegNode->GetNext())
{
if ((pSegNode->m_dwFirstTrackID <= dwTrackID) &&
(pSegNode->m_dwLastTrackID >= dwTrackID))
{
CTrack* pCTrack;
for (pCTrack = pSegNode->m_TrackList.GetHead();pCTrack;pCTrack = pCTrack->GetNext())
{
if (pCTrack->m_dwVirtualID == dwTrackID)
{
m_dwGetParamFlags = pCTrack->m_dwFlags;
m_pGetParamSegmentState = pSegNode;
break;
}
}
break;
}
}
}
LeaveCriticalSection(&m_SegmentCrSec);
}
else
{
m_pGetParamSegmentState = NULL;
m_dwGetParamFlags = 0;
}
hr = GetParam(rguidType,dwGroupBits,dwIndex,mtTime,pmtNext,pData);
m_pGetParamSegmentState = NULL;
m_dwGetParamFlags = 0;
return hr;
}
HRESULT STDMETHODCALLTYPE CPerformance::GetParam(
REFGUID rguidType,
DWORD dwGroupBits,
DWORD dwIndex,
MUSIC_TIME mtTime,
MUSIC_TIME* pmtNext,
void* pData)
{
V_INAME(IDirectMusicPerformance::GetParam);
V_PTR_WRITE_OPT(pmtNext,MUSIC_TIME);
V_PTR_WRITE_OPT(pData,1);
V_REFGUID(rguidType);
EnterCriticalSection(&m_MainCrSec);
if( m_pClock == NULL )
{
LeaveCriticalSection(&m_MainCrSec);
Trace(1,"Error: GetParam() failed because the performance has not been initialized.\n");
return DMUS_E_NO_MASTER_CLOCK;
}
LeaveCriticalSection(&m_MainCrSec);
if( pmtNext )
{
*pmtNext = 0; // this will be replaced by calls to IDMSegment::GetParam
}
CSegState* pSegNode;
CSegState* pSegSource = (CSegState *) m_pGetParamSegmentState;
DWORD dwOverrideFlags;
HRESULT hr = DMUS_E_NOT_FOUND;
BOOL fCheckedPast = FALSE;
MUSIC_TIME mtOffset;
DWORD dwRepeat = 0;
MUSIC_TIME mtSegTime = 0;
MUSIC_TIME mtSegEnd = 0;
MUSIC_TIME mtLoopEnd = 0;
DWORD dwRepeatsLeft = 0;
if (pSegSource)
{
dwOverrideFlags = m_dwGetParamFlags & (DMUS_TRACKCONFIG_OVERRIDE_ALL | DMUS_TRACKCONFIG_OVERRIDE_PRIMARY | DMUS_TRACKCONFIG_FALLBACK);
}
else
{
dwOverrideFlags = 0;
}
if (dwOverrideFlags & DMUS_TRACKCONFIG_OVERRIDE_ALL)
{
// The calling track wants the controlling param to come from the segment itself
mtSegTime = mtTime;
if( S_OK == pSegSource->ConvertToSegTime( &mtSegTime, &mtOffset, &dwRepeat ) )
{
hr = pSegSource->GetParam( this, rguidType, dwGroupBits, dwIndex,
mtSegTime, pmtNext, pData );
if( SUCCEEDED(hr) )
{
dwRepeatsLeft = pSegSource->m_dwRepeats;
mtLoopEnd = pSegSource->m_mtLoopEnd;
mtSegEnd = pSegSource->m_mtLength;
dwRepeatsLeft -= dwRepeat;
}
}
}
if (FAILED(hr))
{
EnterCriticalSection(&m_SegmentCrSec);
// we only care about control segments
if( m_SegStateQueues[SQ_CON_DONE].GetHead() )
{
pSegNode = m_SegStateQueues[SQ_CON_DONE].GetHead();
}
else
{
pSegNode = m_SegStateQueues[SQ_CON_PLAY].GetHead();
fCheckedPast = TRUE;
}
while( pSegNode )
{
mtSegTime = mtTime;
if( S_OK == pSegNode->ConvertToSegTime( &mtSegTime, &mtOffset, &dwRepeat ) )
{
hr = pSegNode->GetParam( this, rguidType, dwGroupBits, dwIndex,
mtSegTime, pmtNext, pData );
if( SUCCEEDED(hr) )
{
dwRepeatsLeft = pSegNode->m_dwRepeats;
mtLoopEnd = pSegNode->m_mtLoopEnd;
mtSegEnd = pSegNode->m_mtLength;
dwRepeatsLeft -= dwRepeat;
break; // got the param we want. We're outta this loop with a success.
}
}
// we didn't find the param, so try the next segment.
pSegNode = pSegNode->GetNext();
// if we're the last segnode in the done queue, we need to
// check against the time of the first segnode in the control play queue
if (!pSegNode && !fCheckedPast )
{
pSegNode = m_SegStateQueues[SQ_CON_PLAY].GetHead();
fCheckedPast = TRUE;
}
}
LeaveCriticalSection(&m_SegmentCrSec);
}
if( FAILED(hr) && (dwOverrideFlags & DMUS_TRACKCONFIG_OVERRIDE_PRIMARY))
{
// The calling track wants the controlling param to come from the segment
// itself if there was no controlling segment.
mtSegTime = mtTime;
if( S_OK == pSegSource->ConvertToSegTime( &mtSegTime, &mtOffset, &dwRepeat ) )
{
hr = pSegSource->GetParam( this, rguidType, dwGroupBits, dwIndex,
mtSegTime, pmtNext, pData );
if( SUCCEEDED(hr) )
{
dwRepeatsLeft = pSegSource->m_dwRepeats;
mtLoopEnd = pSegSource->m_mtLoopEnd;
mtSegEnd = pSegSource->m_mtLength;
dwRepeatsLeft -= dwRepeat;
}
}
}
if( FAILED(hr) ) // didn't find one in the previous, so check for a primary segment
{
IDirectMusicSegment* pSegment = NULL;
mtSegTime = mtTime;
EnterCriticalSection(&m_SegmentCrSec);
pSegNode = GetPrimarySegmentAtTime( mtTime );
if( pSegNode )
{
pSegment = pSegNode->m_pSegment;
pSegment->AddRef();
pSegNode->ConvertToSegTime( &mtSegTime, &mtOffset, &dwRepeat );
dwRepeatsLeft = pSegNode->m_dwRepeats;
mtLoopEnd = pSegNode->m_mtLoopEnd;
mtSegEnd = pSegNode->m_mtLength;
dwRepeatsLeft -= dwRepeat;
}
else
{
Trace(4, "Couldn't find SegState in GetParam call.\n");
}
LeaveCriticalSection(&m_SegmentCrSec);
if( pSegment )
{
hr = pSegNode->GetParam( this, rguidType, dwGroupBits, dwIndex,
mtSegTime, pmtNext, pData );
pSegment->Release();
}
}
if( FAILED(hr) && (dwOverrideFlags & DMUS_TRACKCONFIG_FALLBACK))
{
// The calling track wants the controlling param to come from the segment itself
mtSegTime = mtTime;
if( S_OK == pSegSource->ConvertToSegTime( &mtSegTime, &mtOffset, &dwRepeat ) )
{
hr = pSegSource->GetParam( this, rguidType, dwGroupBits, dwIndex,
mtSegTime, pmtNext, pData );
if( SUCCEEDED(hr) )
{
dwRepeatsLeft = pSegSource->m_dwRepeats;
mtLoopEnd = pSegSource->m_mtLoopEnd;
mtSegEnd = pSegSource->m_mtLength;
dwRepeatsLeft -= dwRepeat;
}
}
}
if( FAILED(hr) )
{ // If we failed, fill in the end time of loop or segment anyway.
if (pmtNext)
{ // Check to see if the loop end is earlier than end of segment.
if (dwRepeatsLeft && (mtLoopEnd > mtSegTime))
{
*pmtNext = mtLoopEnd - mtSegTime;
}
else // Or, mark end of segment.
{
*pmtNext = mtSegEnd - mtSegTime;
}
}
// if we're looking for timesig, and didn't find it anywhere,
// return the Performance timesig
if( rguidType == GUID_TimeSignature )
{
if( NULL == pData )
{
Trace(1,"Error: Null pointer for time signature passed to GetParam().\n");
hr = E_POINTER;
}
else
{
DMUS_TIMESIGNATURE* pTSigData = (DMUS_TIMESIGNATURE*)pData;
DMUS_TIMESIG_PMSG timeSig;
GetTimeSig( mtTime, &timeSig );
pTSigData->bBeatsPerMeasure = timeSig.bBeatsPerMeasure;
pTSigData->bBeat = timeSig.bBeat;
pTSigData->wGridsPerBeat = timeSig.wGridsPerBeat;
pTSigData->mtTime = timeSig.mtTime - mtTime;
hr = S_OK;
}
}
// Likewise, if there was no tempo in a segment, we need to read directly from the tempo list.
else if ( rguidType == GUID_TempoParam || rguidType == GUID_PrivateTempoParam)
{
if( NULL == pData )
{
Trace(1,"Error: Null pointer for tempo passed to GetParam().\n");
hr = E_POINTER;
}
else
{
DMInternalTempo* pInternalTempo;
EnterCriticalSection( &m_PipelineCrSec );
pInternalTempo = (DMInternalTempo*)m_TempoMap.GetHead();
DMInternalTempo* pNextTempo = NULL;
for ( ;pInternalTempo;pInternalTempo = pNextTempo )
{
pNextTempo = (DMInternalTempo *) pInternalTempo->pNext;
if (pNextTempo && (pNextTempo->tempoPMsg.mtTime <= mtTime))
{
continue;
}
if (rguidType == GUID_TempoParam)
{
DMUS_TEMPO_PARAM* pTempoData = (DMUS_TEMPO_PARAM*)pData;
pTempoData->mtTime = pInternalTempo->tempoPMsg.mtTime - mtTime;
pTempoData->dblTempo = pInternalTempo->tempoPMsg.dblTempo;
}
else // rguidType == GUID_PrivateTempoParam
{
PrivateTempo* pTempoData = (PrivateTempo*)pData;
pTempoData->mtTime = pInternalTempo->tempoPMsg.mtTime - mtTime;
pTempoData->dblTempo = pInternalTempo->tempoPMsg.dblTempo;
}
if( pmtNext )
{
*pmtNext = 0;
}
break;
}
LeaveCriticalSection( &m_PipelineCrSec );
if (pInternalTempo)
{
hr = S_FALSE;
}
}
}
}
else // GetParam from a segment succeeded, so we need to clean up the next time parameter to account
// for loops and end of segment.
{
if (pmtNext) // Check to see if the loop end is earlier than *pmtNext.
{
if (dwRepeatsLeft && (*pmtNext > (mtLoopEnd - mtSegTime)))
{
if (mtLoopEnd >= mtSegTime) // This should always be true, but test anyway.
{
*pmtNext = mtLoopEnd - mtSegTime;
}
}
}
}
EnterCriticalSection(&m_MainCrSec);
if (m_pParamHook && SUCCEEDED(hr))
{
hr = m_pParamHook->GetParam(rguidType,dwGroupBits,dwIndex,mtTime,pmtNext,pData,
pSegSource,m_dwGetParamFlags,hr);
}
LeaveCriticalSection(&m_MainCrSec);
return hr;
}
/*
@method HRESULT | IDirectMusicPerformance | SetParam |
Sets data on a Track inside a Primary Segment in this Performance.
@rvalue S_OK | Success.
@rvalue DMUS_E_NO_MASTER_CLOCK | There is no master clock in the performance.
Make sure to call <om .Init> before calling this method.
*/
HRESULT STDMETHODCALLTYPE CPerformance::SetParam(
REFGUID rguidType, // @parm The type of data to set.
DWORD dwGroupBits, // @parm The group the desired track is in. Use 0xffffffff
// for all groups.
DWORD dwIndex, // @parm Identifies which track, by index, in the group
// identified by <p dwGroupBits> to set the data.
MUSIC_TIME mtTime, // @parm The time at which to set the data. Unlike
// <om IDirectMusicSegment.SetParam>, this time is in
// performance time. The start time of the segment is
// subtracted from this time, and <om IDirectMusicSegment.SetParam>
// is called.
void* pData) // @parm The struture containing the data to set. Each
// <p pGuidType> identifies a particular structure of a
// particular size. It is important that this field contain
// the correct structure of the correct size. Otherwise,
// fatal results can occur.
{
V_INAME(IDirectMusicPerformance::SetParam);
V_PTR_WRITE_OPT(pData,1);
V_REFGUID(rguidType);
EnterCriticalSection(&m_MainCrSec);
if( m_pClock == NULL )
{
LeaveCriticalSection(&m_MainCrSec);
Trace(1,"Error: SetParam() failed because the performance has not been initialized.\n");
return DMUS_E_NO_MASTER_CLOCK;
}
LeaveCriticalSection(&m_MainCrSec);
CSegState* pSegNode;
IDirectMusicSegment* pSegment = NULL;
HRESULT hr;
EnterCriticalSection(&m_SegmentCrSec);
pSegNode = GetPrimarySegmentAtTime( mtTime );
MUSIC_TIME mtOffset;
DWORD dwRepeat;
if( pSegNode )
{
pSegment = pSegNode->m_pSegment;
pSegment->AddRef();
pSegNode->ConvertToSegTime( &mtTime, &mtOffset, &dwRepeat );
}
LeaveCriticalSection(&m_SegmentCrSec);
if( pSegment )
{
hr = pSegment->SetParam( rguidType, dwGroupBits, dwIndex,
mtTime, pData );
pSegment->Release();
}
else
{
Trace(1,"Error: SetParam failed because there is no segment at requested time.\n");
hr = DMUS_E_NOT_FOUND;
}
return hr;
}
/*
@method HRESULT | IDirectMusicPerformance | GetGlobalParam |
Gets global values from the Performance.
@rvalue S_OK | Success.
@rvalue E_INVALIDARG | <p pGuidType> isn't in the list of global data being handled by this
Performance. Make sure to call <om IDirectMusicPerformance.SetGlobalParam> first. Or,
the value of <p pData> doesn't point to valid memory. Or, <p dwSize> isn't the size
originally given in <om .SetGlobalParam>
@rvalue E_POINTER | <p pData> is NULL or invalid.
@xref <om .SetGlobalParam>
*/
HRESULT STDMETHODCALLTYPE CPerformance::GetGlobalParam(
REFGUID rguidType, // @parm Identifies the type of data.
void* pData, // @parm Allocated memory to receive a copy of the data. This must be
// the correct size, which is constant for each <p pGuidType> type of
// data, and was also passed in to <om .SetGlobalParam>.
DWORD dwSize // @parm The size of the data in <p pData>. This should be constant for each
// <p pGuidType>. This parameter is needed because the Performance doesn't
// know about all types of data, allowing new ones to be created as needed.
)
{
V_INAME(IDirectMusicPerformance::GetGlobalParam);
V_REFGUID(rguidType);
if( dwSize )
{
V_BUFPTR_WRITE( pData, dwSize );
}
GlobalData* pGD;
HRESULT hr = S_OK;
EnterCriticalSection(&m_GlobalDataCrSec);
for( pGD = m_pGlobalData; pGD; pGD = pGD->pNext )
{
if( pGD->guidType == rguidType )
{
break;
}
}
if( pGD && ( dwSize == pGD->dwSize ) )
{
memcpy( pData, pGD->pData, pGD->dwSize );
}
else
{
#ifdef DBG
if (pGD && ( dwSize != pGD->dwSize ))
{
Trace(1,"Error: GetGlobalParam() failed because the passed data size %ld was inconsistent with %ld, set previously.\n",
dwSize, pGD->dwSize);
}
else
{
Trace(4,"Warning: GetGlobalParam() failed because the parameter had never been set.\n");
}
#endif
hr = E_INVALIDARG;
}
LeaveCriticalSection(&m_GlobalDataCrSec);
return hr;
}
/*
@method HRESULT | IDirectMusicPerformance | SetGlobalParam |
Set global values on the Performance.
@rvalue S_OK | Success.
@rvalue E_POINTER | <p pData> is NULL or invalid.
@rvalue E_OUTOFMEMORY | Ran out of memory.
@rvalue E_INVALIDARG | Other failure. pData or dwSize not correct?
@xref <om .GetGlobalParam>
*/
HRESULT STDMETHODCALLTYPE CPerformance::SetGlobalParam(
REFGUID rguidType, // @parm Identifies the type of data.
void* pData, // @parm The data itself, which will be copied and stored by the Performance.
DWORD dwSize // @parm The size of the data in <p pData>. This should be constant for each
// <p pGuidType>. This parameter is needed because the Performance doesn't
// know about all types of data, allowing new ones to be created as needed.
)
{
V_INAME(IDirectMusicPerformance::SetGlobalParam);
V_REFGUID(rguidType);
if( dwSize )
{
V_BUFPTR_READ( pData, dwSize );
}
GlobalData* pGD;
// see if this is one of our special Performance globals
if( rguidType == GUID_PerfMasterTempo )
{
if( dwSize == sizeof(float) )
{
float flt;
memcpy( &flt, pData, sizeof(float) );
if( (flt >= DMUS_MASTERTEMPO_MIN) && (flt <= DMUS_MASTERTEMPO_MAX) )
{
if( m_fltRelTempo != flt )
{
m_fltRelTempo = flt;
// It's only necessary to recalc the tempo map if something is playing
EnterCriticalSection(&m_SegmentCrSec);
if (GetPrimarySegmentAtTime(m_mtTransported))
{
RecalcTempoMap(NULL,m_mtTransported);
}
LeaveCriticalSection(&m_SegmentCrSec);
}
}
}
else
{
Trace(1,"Error: Attempt to set global tempo failed because dwSize is not size of float.\n");
return E_INVALIDARG;
}
}
else if( rguidType == GUID_PerfMasterVolume )
{
// master volume
if( dwSize == sizeof(long) )
{
memcpy( &m_lMasterVolume, pData, sizeof(long) );
}
else
{
Trace(1,"Error: Attempt to set global volume failed because dwSize is not size of long.\n");
return E_INVALIDARG;
}
// Go through all Ports and set the master volume.
// This is also done upon adding a Port.
IDirectMusicPort* pPort;
DWORD dw;
EnterCriticalSection(&m_PChannelInfoCrSec);
for( dw = 0; dw < m_dwNumPorts; dw++ )
{
pPort = m_pPortTable[dw].pPort;
if( pPort )
{
IKsControl *pControl;
if(SUCCEEDED(pPort->QueryInterface(IID_IKsControl, (void**)&pControl)))
{
KSPROPERTY ksp;
ULONG cb;
memset(&ksp, 0, sizeof(ksp));
ksp.Set = GUID_DMUS_PROP_Volume;
ksp.Id = 0;
ksp.Flags = KSPROPERTY_TYPE_SET;
pControl->KsProperty(&ksp,
sizeof(ksp),
(LPVOID)&m_lMasterVolume,
sizeof(m_lMasterVolume),
&cb);
pControl->Release();
}
}
}
LeaveCriticalSection(&m_PChannelInfoCrSec);
}
// see if this type is already there. If so, use it.
EnterCriticalSection(&m_GlobalDataCrSec);
for( pGD = m_pGlobalData; pGD; pGD = pGD->pNext )
{
if( pGD->guidType == rguidType )
{
break;
}
}
LeaveCriticalSection(&m_GlobalDataCrSec);
// if it already exists, just copy the new data into the
// existing memory block and return
if( pGD )
{
if( pGD->dwSize != dwSize )
{
Trace(1,"Error: Attempt to set global parameter failed because dwSize is not consistent with previous SetGlobalParam() call.\n");
return E_INVALIDARG;
}
if( dwSize )
{
memcpy( pGD->pData, pData, dwSize );
}
return S_OK;
}
// otherwise, create new memory
pGD = new GlobalData;
if( NULL == pGD )
{
return E_OUTOFMEMORY;
}
pGD->dwSize = dwSize;
if( dwSize )
{
pGD->pData = (void*)(new char[dwSize]);
if( NULL == pGD->pData )
{
delete pGD;
return E_OUTOFMEMORY;
}
memcpy( pGD->pData, pData, dwSize );
}
else
{
pGD->pData = NULL;
}
pGD->guidType = rguidType;
EnterCriticalSection(&m_GlobalDataCrSec); // just using this one since it's available and not used much
pGD->pNext = m_pGlobalData;
m_pGlobalData = pGD;
LeaveCriticalSection(&m_GlobalDataCrSec);
return S_OK;
}
// IDirectMusicTool
/*
@method HRESULT | IDirectMusicTool | Init |
Called when the Tool is inserted into the Graph, providing the Tool the opportunity
to initialize itself.
@rvalue S_OK | Success.
@rvalue E_NOTIMPL | Not implemented is a valid return for the method.
*/
HRESULT STDMETHODCALLTYPE CPerformance::Init(
IDirectMusicGraph* pGraph // @parm The calling graph.
)
{
return E_NOTIMPL;
}
inline bool CPerformance::SendShortMsg( IDirectMusicBuffer* pBuffer,
IDirectMusicPort* pPort,DWORD dwMsg,
REFERENCE_TIME rt, DWORD dwGroup)
{
if( FAILED( pBuffer->PackStructured( rt, dwGroup, dwMsg ) ) )
{
// ran out of room in the buffer
TraceI(2, "RAN OUT OF ROOM IN THE BUFFER!\n");
pPort->PlayBuffer( pBuffer );
pBuffer->Flush();
// try one more time
if( FAILED( pBuffer->PackStructured( rt, dwGroup, dwMsg ) ) )
{
TraceI(1, "MAJOR BUFFER PACKING FAILURE!\n");
// if it didn't work this time, free the event because something
// bad has happened.
return false;
}
}
return true;
}
//////////////////////////////////////////////////////////////////////
// CPerformance::PackNote
/*
HRESULT | CPerformance | PackNote |
Converts the message into a midiShortMsg, midiLongMsg, or user message
and packs it into the appropriate IDirectMusicBuffer in the PortTable,
setting the m_fBufferFilled flag.
DMUS_PMSG* | pPMsg |
[in] The message to pack into the buffer.
REFERENCE_TIME | mt |
[in] The time (in the Buffer's clock coordinates) at which to queue the message.
E_INVALIDARG | Either pPMsg or pBuffer is NULL.
E_OUTOFMEMORY | Failed to pack the buffer.
DMUS_S_REQUEUE | Tells the Pipeline to requeue this message.
DMUS_S_FREE | Tells the Pipeline to free this message.
*/
HRESULT CPerformance::PackNote(
DMUS_PMSG* pEvent,
REFERENCE_TIME rt )
{
DMUS_NOTE_PMSG* pNote = (DMUS_NOTE_PMSG*)pEvent;
PRIV_PMSG* pPriv = DMUS_TO_PRIV(pEvent);
REFERENCE_TIME rtLogical; // the time the note occurs in logical music time (subtract offset)
IDirectMusicBuffer* pBuffer = NULL;
IDirectMusicPort* pPort = NULL;
DWORD dwMsg;
DWORD dwGroup, dwMChannel, dwPortTableIndex;
short nTranspose = 0;
short nValue;
HRESULT hr = DMUS_S_FREE;
if( NULL == pEvent )
return E_INVALIDARG;
if( FAILED( PChannelIndex( pNote->dwPChannel, &dwPortTableIndex, &dwGroup, &dwMChannel,
&nTranspose )))
{
Trace(1,"Play note failed on unassigned PChannel %ld\n",pNote->dwPChannel);
return DMUS_S_FREE; // the PChannel map wasn't found. Just free the event.
}
EnterCriticalSection(&m_PChannelInfoCrSec);
if( dwPortTableIndex > m_dwNumPorts )
{
pPort = NULL; // the PChannel map is out of range of the number of ports
// so return outta here! (see after the LeaveCriticalSection)
}
else
{
pPort = m_pPortTable[dwPortTableIndex].pPort;
if( pPort ) pPort->AddRef();
pBuffer = m_pPortTable[dwPortTableIndex].pBuffer;
if( pBuffer ) pBuffer->AddRef();
}
LeaveCriticalSection(&m_PChannelInfoCrSec);
if(pPort && pBuffer )
{
dwMsg = 0;
if( pNote->bFlags & DMUS_NOTEF_NOTEON )
{
// transpose the note's bMidiValue, and store it in the note so the note off
// plays the correct pitch.
nValue = pNote->bMidiValue + nTranspose;
if( ( nValue > 127 ) || ( nValue < 0 )
|| pNote->mtDuration <= 0 )
{
// don't play this out-of-range or 0-duration note
pPort->Release();
pBuffer->Release();
return DMUS_S_FREE;
}
pNote->bMidiValue = (BYTE)nValue;
dwMsg |= pNote->bVelocity << 16;
}
else if( rt < pPriv->rtLast )
{
// the note off will play before the note on. Bad.
rt = pPriv->rtLast + REF_PER_MIL;
}
dwMsg |= pNote->bMidiValue << 8; // set note value
dwMsg |= dwMChannel; // MIDI Channel
if( pNote->bFlags & DMUS_NOTEF_NOTEON )
{
dwMsg |= MIDI_NOTEON;
}
else
{
dwMsg |= MIDI_NOTEOFF;
}
if (SendShortMsg(pBuffer,pPort,dwMsg,rt-2,dwGroup))
{
EnterCriticalSection(&m_PipelineCrSec); // to prevent deadlock in MusicToReferenceTime
EnterCriticalSection(&m_PChannelInfoCrSec);
m_pPortTable[dwPortTableIndex].fBufferFilled = TRUE; // so we send this in SendBuffers
rtLogical = rt;
// subtract the offset if needed, but only for a note on.
if( pNote->nOffset && (pNote->bFlags & DMUS_NOTEF_NOTEON))
{
MUSIC_TIME mtTemp = pNote->mtTime - pNote->nOffset + 1;
REFERENCE_TIME rtTemp;
MusicToReferenceTime( mtTemp, &rtTemp );
if( rtTemp > rtLogical )
{
rtLogical = rtTemp;
}
}
if( m_pPortTable[dwPortTableIndex].rtLast < rtLogical )
{
m_pPortTable[dwPortTableIndex].rtLast = rtLogical;
}
LeaveCriticalSection(&m_PChannelInfoCrSec);
LeaveCriticalSection(&m_PipelineCrSec);
if( pNote->bFlags & DMUS_NOTEF_NOTEON )
{
pPriv->rtLast = rt;
m_rtHighestPackedNoteOn = rt;
if (pNote->dwFlags & DMUS_PMSGF_LOCKTOREFTIME)
{
// This is a clock time message.
rt = pNote->rtTime;
pNote->rtTime += (pNote->mtDuration * REF_PER_MIL);
if (pNote->mtDuration > 1)
{
pNote->rtTime -= REF_PER_MIL;
}
// subtract 1 to guarantee that a note off at the same time as a note on doesn't
// stop the note on short. It's possible that rt == pNote->rtTime, if the duration
// was zero, so be sure to check that.
if( pNote->rtTime < rt + 1 )
{
pNote->rtTime = rt + 1;
}
pNote->bFlags &= ~DMUS_NOTEF_NOTEON; // make this a note off now
pNote->dwFlags &= ~DMUS_PMSGF_MUSICTIME;
hr = DMUS_S_REQUEUE;
}
else
{
pNote->mtTime += pNote->mtDuration;
if (pNote->mtDuration > 1)
{
pNote->mtTime--;
}
MusicToReferenceTime( pNote->mtTime, &rt );
// subtract 1 to guarantee that a note off at the same time as a note on doesn't
// stop the note on short. It's possible that rt == pNote->rtTime, if the duration
// was zero, so be sure to check that.
if( rt < pNote->rtTime + 2 )
{
rt = pNote->rtTime + 2;
}
pNote->rtTime = rt - 1;
}
pNote->bFlags &= ~DMUS_NOTEF_NOTEON; // make this a note off now
hr = DMUS_S_REQUEUE;
}
}
}
if( pPort ) pPort->Release();
if( pBuffer ) pBuffer->Release();
return hr;
}
//////////////////////////////////////////////////////////////////////
// CPerformance::PackCurve
HRESULT CPerformance::PackCurve(
DMUS_PMSG* pEvent,
REFERENCE_TIME rt )
{
DMUS_CURVE_PMSG* pCurve = (DMUS_CURVE_PMSG*)pEvent;
IDirectMusicBuffer* pBuffer = NULL;
IDirectMusicPort* pPort = NULL;
DWORD dwMsg;
HRESULT hr = DMUS_S_FREE;
BOOL fCalcStartValue = FALSE;
CChannelMap *pChannelMap = NULL;
if( NULL == pEvent )
return E_INVALIDARG;
// store the original start time so we know how far into the curve we are
if( pCurve->mtOriginalStart == 0 )
{
// if we're flushing and have never played this curve at all, just free
// it.
if( pCurve->dwFlags & DMUS_PMSGF_TOOL_FLUSH )
{
return DMUS_S_FREE;
}
if (pCurve->dwFlags & DMUS_PMSGF_LOCKTOREFTIME)
{
// This is a clock time message. Convert the duration into music time. It will act as
// a music time message from now on. This does have the downside that if a dramatic tempo
// change occurs in the middle of a lengthy curve, the end time can be distorted.
// But, given the purpose of curves, this is really an unlikely issue.
MUSIC_TIME mtTemp;
ReferenceToMusicTime(pCurve->rtTime + (pCurve->mtDuration * REF_PER_MIL),&mtTemp);
mtTemp -= pCurve->mtTime;
pCurve->mtDuration = mtTemp;
ReferenceToMusicTime(pCurve->rtTime + (pCurve->mtResetDuration * REF_PER_MIL),&mtTemp);
mtTemp -= pCurve->mtTime;
pCurve->mtResetDuration = mtTemp;
pCurve->dwFlags &= ~DMUS_PMSGF_LOCKTOREFTIME;
}
pCurve->mtOriginalStart = pCurve->mtTime;
// check the latency clock. Adjust pCurve->mtTime if needed. This can happen
// if the curve is time-stamped for the past. We only need do this for non-instant
// curve types.
if( pCurve->bCurveShape != DMUS_CURVES_INSTANT )
{
REFERENCE_TIME rtLatency = GetLatency();
MUSIC_TIME mtLatency;
ReferenceToMusicTime( rtLatency, &mtLatency );
if( pCurve->mtTime < mtLatency )
{
if( pCurve->mtTime + pCurve->mtDuration < mtLatency )
{
// If it is far enough in the past,
// we only need to send out the final value.
pCurve->mtTime += pCurve->mtDuration;
}
else
{
pCurve->mtTime = mtLatency;
}
}
// If this is the start of a curve and we are supposed to start with the current playing value...
if (pCurve->bFlags & DMUS_CURVE_START_FROM_CURRENT)
{
fCalcStartValue = TRUE;
}
else
{
pCurve->wMeasure = (WORD) ComputeCurveTimeSlice(pCurve); // Use this to store the time slice interval.
}
}
}
// it is necessary to check reset duration >= 0 because it could have been set
// to be negative by the flushing, and we don't want to toss it in that case.
// (should no longer be necessary to check, as a result of fixing 33987)
if( ( pCurve->bFlags & DMUS_CURVE_RESET ) && (pCurve->mtResetDuration >= 0) && ( pCurve->mtTime ==
pCurve->mtDuration + pCurve->mtResetDuration + pCurve->mtOriginalStart ))
{
if( !( pCurve->dwFlags & DMUS_PMSGF_TOOL_FLUSH ) )
{
PRIV_PMSG* pPrivPMsg = DMUS_TO_PRIV(pEvent);
if ( (pPrivPMsg->dwPrivFlags & PRIV_FLAG_FLUSH) )
{
pPrivPMsg->dwPrivFlags &= ~PRIV_FLAG_FLUSH;
pCurve->dwFlags |= DMUS_PMSGF_TOOL_FLUSH;
MUSIC_TIME mt = 0;
if( rt <= pPrivPMsg->rtLast )
{
return PackCurve( pEvent, pPrivPMsg->rtLast + REF_PER_MIL );
}
else
{
return PackCurve( pEvent, rt );
}
}
else
{
// the reset duration has expired, and we're not flushing, so expire the event.
return DMUS_S_FREE;
}
}
}
EnterCriticalSection(&m_PChannelInfoCrSec);
pChannelMap = GetPChannelMap(pCurve->dwPChannel);
if (!pChannelMap)
{
Trace(1,"Play curve failed on unassigned PChannel %ld\n",pCurve->dwPChannel);
LeaveCriticalSection(&m_PChannelInfoCrSec);
return DMUS_S_FREE; // the PChannel map wasn't found. Just free the event.
}
if( pChannelMap->dwPortIndex > m_dwNumPorts )
{
pPort = NULL; // the PChannel map is out of range of the number of ports
// so return outta here! (see after the LeaveCriticalSection)
}
else
{
pPort = m_pPortTable[pChannelMap->dwPortIndex].pPort;
if( pPort ) pPort->AddRef();
pBuffer = m_pPortTable[pChannelMap->dwPortIndex].pBuffer;
if( pBuffer ) pBuffer->AddRef();
}
if( pPort && pBuffer)
{
DWORD dwCurve;
DWORD dwMergeIndex = 0;
dwMsg = 0;
if (pCurve->dwFlags & DMUS_PMSGF_DX8)
{
dwMergeIndex = pCurve->wMergeIndex;
}
switch( pCurve->bType )
{
case DMUS_CURVET_PBCURVE:
if (fCalcStartValue)
{
pCurve->nStartValue =
(short) pChannelMap->m_PitchbendMerger.GetIndexedValue(dwMergeIndex) + 0x2000;
}
dwCurve = ComputeCurve( pCurve );
dwCurve = pChannelMap->m_PitchbendMerger.MergeValue(dwMergeIndex,(long)dwCurve,0x2000,0x3FFF);
dwMsg = MIDI_PBEND;
dwMsg |= ( (dwCurve & 0x7F) << 8);
dwCurve = dwCurve >> 7;
dwMsg |= ( (dwCurve & 0x7F) << 16);
break;
case DMUS_CURVET_CCCURVE:
switch (pCurve->bCCData)
{
case MIDI_CC_MOD_WHEEL:
if (fCalcStartValue)
{
pCurve->nStartValue =
(short) pChannelMap->m_ModWheelMerger.GetIndexedValue(dwMergeIndex) + 0x7F;
}
dwCurve = ComputeCurve( pCurve );
dwCurve = pChannelMap->m_ModWheelMerger.MergeValue(dwMergeIndex,(long)dwCurve,0x7F,0x7F);
break;
case MIDI_CC_VOLUME:
if (fCalcStartValue)
{
pCurve->nStartValue =
(short) pChannelMap->m_VolumeMerger.GetVolumeStart(dwMergeIndex);
}
dwCurve = ComputeCurve( pCurve );
dwCurve = pChannelMap->m_VolumeMerger.MergeMidiVolume(dwMergeIndex,(BYTE) dwCurve);
break;
case MIDI_CC_PAN:
if (fCalcStartValue)
{
pCurve->nStartValue =
(short) pChannelMap->m_PanMerger.GetIndexedValue(dwMergeIndex) + 0x40;
}
dwCurve = ComputeCurve( pCurve );
dwCurve = pChannelMap->m_PanMerger.MergeValue(dwMergeIndex,(long)dwCurve,0x40,0x7F);
break;
case MIDI_CC_EXPRESSION:
if (fCalcStartValue)
{
pCurve->nStartValue =
(short) pChannelMap->m_ExpressionMerger.GetVolumeStart(dwMergeIndex);
}
dwCurve = ComputeCurve( pCurve );
dwCurve = pChannelMap->m_ExpressionMerger.MergeMidiVolume(dwMergeIndex,(BYTE) dwCurve);
break;
case MIDI_CC_FILTER:
if (fCalcStartValue)
{
pCurve->nStartValue =
(short) pChannelMap->m_FilterMerger.GetIndexedValue(dwMergeIndex) + 0x40;
}
dwCurve = ComputeCurve( pCurve );
dwCurve = pChannelMap->m_FilterMerger.MergeValue(dwMergeIndex,(long)dwCurve,0x40,0x7F);
break;
case MIDI_CC_REVERB:
if (fCalcStartValue)
{
pCurve->nStartValue =
(short) pChannelMap->m_ReverbMerger.GetIndexedValue(dwMergeIndex) + 0x7F;
}
dwCurve = ComputeCurve( pCurve );
dwCurve = pChannelMap->m_ReverbMerger.MergeValue(dwMergeIndex,(long)dwCurve,0x7F,0x7F);
break;
case MIDI_CC_CHORUS:
if (fCalcStartValue)
{
pCurve->nStartValue =
(short) pChannelMap->m_ChorusMerger.GetIndexedValue(dwMergeIndex) + 0x7F;
}
dwCurve = ComputeCurve( pCurve );
dwCurve = pChannelMap->m_ChorusMerger.MergeValue(dwMergeIndex,(long)dwCurve,0x7F,0x7F);
break;
case MIDI_CC_RESETALL:
dwCurve = ComputeCurve( pCurve );
pChannelMap->Reset(pCurve->nEndValue);
break;
default:
dwCurve = ComputeCurve( pCurve );
break;
}
dwMsg = MIDI_CCHANGE;
dwMsg |= (pCurve->bCCData << 8);
dwMsg |= (dwCurve << 16);
break;
case DMUS_CURVET_MATCURVE:
dwCurve = ComputeCurve( pCurve );
dwMsg = MIDI_MTOUCH;
dwMsg |= (dwCurve << 8);
break;
case DMUS_CURVET_PATCURVE:
dwCurve = ComputeCurve( pCurve );
dwMsg = MIDI_PTOUCH;
dwMsg |= (pCurve->bCCData << 8);
dwMsg |= (dwCurve << 16);
break;
case DMUS_CURVET_RPNCURVE:
case DMUS_CURVET_NRPNCURVE:
if (pCurve->dwFlags & DMUS_PMSGF_DX8)
{
dwCurve = ComputeCurve( pCurve );
DWORD dwMsg2 = MIDI_CCHANGE;
dwMsg = MIDI_CCHANGE;
// First, send the two CC commands to select which RPN or NRPN event.
if (pCurve->bType == DMUS_CURVET_RPNCURVE)
{
dwMsg |= (MIDI_CC_RPN_MSB << 8);
dwMsg2 |= (MIDI_CC_RPN_LSB << 8);
}
else
{
dwMsg |= (MIDI_CC_NRPN_MSB << 8);
dwMsg2 |= (MIDI_CC_NRPN_LSB << 8);
}
dwMsg |= (pCurve->wParamType & 0x3F80) << 9; // Upper 8 bits of command #
dwMsg2 |= (pCurve->wParamType & 0x7F) << 16; // Lower 8 bits.
dwMsg |= pChannelMap->dwMChannel; // MIDI Channel
dwMsg2 |= pChannelMap->dwMChannel; // MIDI Channel
SendShortMsg(pBuffer,pPort,dwMsg,rt-3,pChannelMap->dwGroup); // Too bad if it fails!
SendShortMsg(pBuffer,pPort,dwMsg2,rt-2,pChannelMap->dwGroup);
// Then, send the two data CC commands.
dwMsg = MIDI_CCHANGE | (MIDI_CC_DATAENTRYMSB << 8);
dwMsg |= (dwCurve & 0x3F80) << 9; // Upper 8 bits of data
dwMsg |= pChannelMap->dwMChannel; // MIDI Channel
SendShortMsg(pBuffer,pPort,dwMsg,rt-1,pChannelMap->dwGroup);
dwMsg = MIDI_CCHANGE | (MIDI_CC_DATAENTRYLSB << 8);
dwMsg |= (dwCurve & 0x7F) << 16; // Lower 8 bits of data
}
}
if (dwMsg) // Make sure we successfully created a message.
{
dwMsg |= pChannelMap->dwMChannel; // MIDI Channel
if (SendShortMsg(pBuffer,pPort,dwMsg,rt,pChannelMap->dwGroup))
{
m_pPortTable[pChannelMap->dwPortIndex].fBufferFilled = TRUE; // so we send this in SendBuffers
m_pPortTable[pChannelMap->dwPortIndex].rtLast = rt;
// ComputeCurve() will set this to 0 if it's time to free the event. Otherwise, it
// will set it to the next time this event should be performed.
if( pCurve->rtTime )
{
// If we didn't calculate the time slice because we didn't know
// what the start value was, do it now.
if (fCalcStartValue)
{
pCurve->wMeasure = (WORD) ComputeCurveTimeSlice(pCurve); // Use this to store the time slice interval.
}
hr = DMUS_S_REQUEUE;
}
}
}
}
LeaveCriticalSection(&m_PChannelInfoCrSec);
if( pPort ) pPort->Release();
if( pBuffer ) pBuffer->Release();
return hr;
}
//////////////////////////////////////////////////////////////////////
// CPerformance::PackMidi
HRESULT CPerformance::PackMidi(
DMUS_PMSG* pEvent,
REFERENCE_TIME rt )
{
DMUS_MIDI_PMSG* pMidi = (DMUS_MIDI_PMSG*)pEvent;
IDirectMusicBuffer* pBuffer = NULL;
IDirectMusicPort* pPort = NULL;
DWORD dwMsg;
// DWORD dwGroup, dwMChannel, dwPortTableIndex;
HRESULT hr = DMUS_S_FREE;
CChannelMap *pChannelMap = NULL;
if( NULL == pMidi )
return E_INVALIDARG;
EnterCriticalSection(&m_PChannelInfoCrSec);
pChannelMap = GetPChannelMap(pMidi->dwPChannel);
if (!pChannelMap)
{
Trace(1,"Play MIDI failed on unassigned PChannel %ld\n",pMidi->dwPChannel);
LeaveCriticalSection(&m_PChannelInfoCrSec);
return DMUS_S_FREE; // the PChannel map wasn't found. Just free the event.
}
if( pChannelMap->dwPortIndex > m_dwNumPorts )
{
pPort = NULL; // the PChannel map is out of range of the number of ports
// so return outta here! (see after the LeaveCriticalSection)
}
else
{
pPort = m_pPortTable[pChannelMap->dwPortIndex].pPort;
if( pPort ) pPort->AddRef();
pBuffer = m_pPortTable[pChannelMap->dwPortIndex].pBuffer;
if( pBuffer ) pBuffer->AddRef();
}
if(pPort && pBuffer )
{
pMidi->bStatus &= 0xF0;
if (pMidi->bStatus == MIDI_CCHANGE)
{
switch (pMidi->bByte1)
{
case MIDI_CC_MOD_WHEEL:
pMidi->bByte2 = (BYTE) pChannelMap->m_ModWheelMerger.MergeValue(0,pMidi->bByte2,0x7F,0x7F);
break;
case MIDI_CC_VOLUME:
pMidi->bByte2 = pChannelMap->m_VolumeMerger.MergeMidiVolume(0,pMidi->bByte2);
break;
case MIDI_CC_PAN:
pMidi->bByte2 = (BYTE) pChannelMap->m_PanMerger.MergeValue(0,pMidi->bByte2,0x40,0x7F);
break;
case MIDI_CC_EXPRESSION:
pMidi->bByte2 = pChannelMap->m_ExpressionMerger.MergeMidiVolume(0,pMidi->bByte2);
break;
case MIDI_CC_FILTER:
pMidi->bByte2 = (BYTE) pChannelMap->m_FilterMerger.MergeValue(0,pMidi->bByte2,0x40,0x7F);
break;
case MIDI_CC_REVERB:
pMidi->bByte2 = (BYTE) pChannelMap->m_ReverbMerger.MergeValue(0,pMidi->bByte2,0x7F,0x7F);
break;
case MIDI_CC_CHORUS:
pMidi->bByte2 = (BYTE) pChannelMap->m_ChorusMerger.MergeValue(0,pMidi->bByte2,0x7F,0x7F);
break;
case MIDI_CC_RESETALL:
pChannelMap->Reset(pMidi->bByte2);
break;
}
}
else if (pMidi->bStatus == MIDI_PBEND)
{
WORD wBend = pMidi->bByte1 | (pMidi->bByte2 << 7);
wBend = (WORD) pChannelMap->m_PitchbendMerger.MergeValue(0,wBend,0x2000,0x3FFF);
pMidi->bByte1 = wBend & 0x7F;
pMidi->bByte2 = (wBend >> 7) & 0x7F;
}
dwMsg = pMidi->bByte1 << 8;
dwMsg |= pMidi->bByte2 << 16;
dwMsg |= pMidi->bStatus;
dwMsg |= pChannelMap->dwMChannel;
if (SendShortMsg(pBuffer,pPort,dwMsg,rt,pChannelMap->dwGroup))
{
m_pPortTable[pChannelMap->dwPortIndex].fBufferFilled = TRUE; // so we send this in SendBuffers
m_pPortTable[pChannelMap->dwPortIndex].rtLast = rt;
}
}
LeaveCriticalSection(&m_PChannelInfoCrSec);
if( pPort ) pPort->Release();
if( pBuffer ) pBuffer->Release();
return hr;
}
//////////////////////////////////////////////////////////////////////
// CPerformance::PackSysEx
HRESULT CPerformance::PackSysEx(
DMUS_PMSG* pEvent,
REFERENCE_TIME rt )
{
DMUS_SYSEX_PMSG* pSysEx = (DMUS_SYSEX_PMSG*)pEvent;
IDirectMusicBuffer* pBuffer = NULL;
IDirectMusicPort* pPort = NULL;
DWORD dwGroup, dwMChannel, dwPortTableIndex;
HRESULT hr = DMUS_S_FREE;
if( NULL == pEvent )
return E_INVALIDARG;
if( NULL == m_pDirectMusic )
return DMUS_E_NOT_INIT;
if( FAILED( PChannelIndex( pSysEx->dwPChannel, &dwPortTableIndex, &dwGroup, &dwMChannel)))
{
Trace(1,"Play SysEx failed on unassigned PChannel %ld\n",pSysEx->dwPChannel);
return DMUS_S_FREE; // the PChannel map wasn't found. Just free the event.
}
EnterCriticalSection(&m_PChannelInfoCrSec);
if( dwPortTableIndex > m_dwNumPorts )
{
pPort = NULL; // the PChannel map is out of range of the number of ports
// so return outta here! (see after the LeaveCriticalSection)
}
else
{
pPort = m_pPortTable[dwPortTableIndex].pPort;
if( pPort ) pPort->AddRef();
}
LeaveCriticalSection(&m_PChannelInfoCrSec);
if( pPort )
{
// create a buffer of the right size
DMUS_BUFFERDESC dmbd;
memset( &dmbd, 0, sizeof(DMUS_BUFFERDESC) );
dmbd.dwSize = sizeof(DMUS_BUFFERDESC);
dmbd.cbBuffer = pSysEx->dwLen + 48;
EnterCriticalSection(&m_MainCrSec);
if( SUCCEEDED( m_pDirectMusic->CreateMusicBuffer(&dmbd, &pBuffer, NULL)))
{
if( SUCCEEDED( pBuffer->PackUnstructured( rt - 4, dwGroup, pSysEx->dwLen, pSysEx->abData ) ) )
{
pPort->PlayBuffer(pBuffer);
}
pBuffer->Release();
}
LeaveCriticalSection(&m_MainCrSec);
}
if( pPort ) pPort->Release();
return hr;
}
//////////////////////////////////////////////////////////////////////
// CPerformance::PackPatch
HRESULT CPerformance::PackPatch(
DMUS_PMSG* pEvent,
REFERENCE_TIME rt )
{
DMUS_PATCH_PMSG* pPatch = (DMUS_PATCH_PMSG*)pEvent;
IDirectMusicBuffer* pBuffer = NULL;
IDirectMusicPort* pPort = NULL;
DWORD dwGroup, dwMChannel, dwPortTableIndex;
DWORD dwMsg;
HRESULT hr = DMUS_S_FREE;
if( NULL == pEvent )
return E_INVALIDARG;
if( FAILED( PChannelIndex( pPatch->dwPChannel, &dwPortTableIndex, &dwGroup, &dwMChannel)))
{
Trace(1,"Play Patch failed on unassigned PChannel %ld\n",pPatch->dwPChannel);
return DMUS_S_FREE; // the PChannel map wasn't found. Just free the event.
}
EnterCriticalSection(&m_PChannelInfoCrSec);
if( dwPortTableIndex > m_dwNumPorts )
{
pPort = NULL; // the PChannel map is out of range of the number of ports
// so return outta here! (see after the LeaveCriticalSection)
}
else
{
pPort = m_pPortTable[dwPortTableIndex].pPort;
if( pPort ) pPort->AddRef();
pBuffer = m_pPortTable[dwPortTableIndex].pBuffer;
if( pBuffer ) pBuffer->AddRef();
}
if( pPort && pBuffer)
{
// subtract 10 from rt to guarantee that patch events always go out earlier than
// notes with the same time stamp.
rt -= 10;
// send the bank select lsb
dwMsg = MIDI_CCHANGE;
dwMsg |= ( MIDI_CC_BS_LSB << 8 );
dwMsg |= (pPatch->byLSB << 16);
ASSERT( dwMChannel < 16 );
dwMsg |= dwMChannel;
SendShortMsg(pBuffer,pPort,dwMsg,rt-2,dwGroup);
// send the bank select msb
dwMsg = MIDI_CCHANGE;
dwMsg |= ( MIDI_CC_BS_MSB << 8 );
dwMsg |= (pPatch->byMSB << 16);
dwMsg |= dwMChannel;
SendShortMsg(pBuffer,pPort,dwMsg,rt-1,dwGroup);
// send the program change
dwMsg = MIDI_PCHANGE;
dwMsg |= (pPatch->byInstrument << 8);
dwMsg |= dwMChannel;
if (SendShortMsg(pBuffer,pPort,dwMsg,rt,dwGroup))
{
m_pPortTable[dwPortTableIndex].fBufferFilled = TRUE; // so we send this in SendBuffers
m_pPortTable[dwPortTableIndex].rtLast = rt;
}
}
LeaveCriticalSection(&m_PChannelInfoCrSec);
if( pPort ) pPort->Release();
if( pBuffer ) pBuffer->Release();
return hr;
}
HRESULT CPerformance::PackWave(DMUS_PMSG* pPMsg, REFERENCE_TIME rtTime)
{
DMUS_WAVE_PMSG* pWave = (DMUS_WAVE_PMSG*)pPMsg;
HRESULT hr = DMUS_S_FREE;
IDirectMusicVoiceP *pVoice = (IDirectMusicVoiceP *) pWave->punkUser;
if (pVoice)
{
if (pWave->bFlags & DMUS_WAVEF_OFF)
{
pVoice->Stop(rtTime);
EnterCriticalSection(&m_SegmentCrSec);
for (DWORD dwCount = 0; dwCount < SQ_COUNT; dwCount++)
{
for( CSegState* pSegSt = m_SegStateQueues[dwCount].GetHead(); pSegSt; pSegSt = pSegSt->GetNext() )
{
CTrack* pTrack = pSegSt->m_TrackList.GetHead();
while( pTrack )
{
if (pTrack->m_guidClassID == CLSID_DirectMusicWaveTrack)
{
IPrivateWaveTrack* pWaveTrack = NULL;
if (pTrack->m_pTrack &&
SUCCEEDED(pTrack->m_pTrack->QueryInterface(IID_IPrivateWaveTrack, (void**)&pWaveTrack)))
{
pWaveTrack->OnVoiceEnd(pVoice, pTrack->m_pTrackState);
pWaveTrack->Release();
}
}
pTrack = pTrack->GetNext();
}
}
}
LeaveCriticalSection(&m_SegmentCrSec);
}
else
{
if (SUCCEEDED(pVoice->Play(rtTime, pWave->lPitch, pWave->lVolume)))
{
if (pWave->dwFlags & DMUS_PMSGF_LOCKTOREFTIME)
{
// This is a clock time message.
pWave->rtTime += pWave->rtDuration ;
pWave->dwFlags &= ~DMUS_PMSGF_MUSICTIME;
}
else
{
pWave->mtTime += (MUSIC_TIME) pWave->rtDuration;
pWave->dwFlags &= ~DMUS_PMSGF_REFTIME;
}
pWave->bFlags |= DMUS_WAVEF_OFF; // Queue this back up as a wave off.
hr = DMUS_S_REQUEUE;
}
}
}
return hr;
}
HRESULT STDMETHODCALLTYPE CPerformance::ProcessPMsg(
IDirectMusicPerformance* pPerf, // @parm The performance pointer.
DMUS_PMSG* pPMsg // @parm The message to process.
)
{
V_INAME(IDirectMusicTool::ProcessPMsg);
V_INTERFACE(pPerf);
V_BUFPTR_WRITE(pPMsg,sizeof(DMUS_PMSG));
if (m_rtQueuePosition > pPMsg->rtTime + 50000000)
{
// pMSg is more than 5 seconds in the past; get rid of it unless it's signalling the
// end of something that's already been started.
if (pPMsg->dwType == DMUS_PMSGT_NOTIFICATION)
{
DMUS_NOTIFICATION_PMSG* pNotify = (DMUS_NOTIFICATION_PMSG*)pPMsg;
if ( (pNotify->guidNotificationType == GUID_NOTIFICATION_PERFORMANCE &&
pNotify->dwNotificationOption != DMUS_NOTIFICATION_MUSICSTOPPED) ||
(pNotify->guidNotificationType == GUID_NOTIFICATION_SEGMENT &&
pNotify->dwNotificationOption != DMUS_NOTIFICATION_SEGEND) )
{
return DMUS_S_FREE;
}
}
else if (pPMsg->dwType == DMUS_PMSGT_NOTE)
{
DMUS_NOTE_PMSG* pNote = (DMUS_NOTE_PMSG*)pPMsg;
if (pNote->bFlags & DMUS_NOTEF_NOTEON)
{
return DMUS_S_FREE;
}
}
else if (pPMsg->dwType == DMUS_PMSGT_WAVE)
{
DMUS_WAVE_PMSG* pWave = (DMUS_WAVE_PMSG*)pPMsg;
if (!(pWave->bFlags & DMUS_WAVEF_OFF))
{
return DMUS_S_FREE;
}
}
else
{
return DMUS_S_FREE;
}
}
HRESULT hr = DMUS_S_FREE;
ASSERT( pPerf == this );
if( pPMsg->dwType == DMUS_PMSGT_TEMPO )
{
PRIV_PMSG* pPrivPMsg = DMUS_TO_PRIV(pPMsg);
// If the pmsg was generated by a track, discard it
// because it was already placed in the tempo map.
if( pPrivPMsg->dwPrivFlags & PRIV_FLAG_TRACK )
{
return DMUS_S_FREE;
}
// Otherwise, this was generated by the application, so it's not already
// in the tempo map and we need to add it.
AddEventToTempoMap( DMUS_TO_PRIV(pPMsg));
return DMUS_S_FREE; // OK to free this event; not requeued
}
if ((pPMsg->dwPChannel == DMUS_PCHANNEL_BROADCAST_GROUPS) ||
(pPMsg->dwPChannel == DMUS_PCHANNEL_BROADCAST_PERFORMANCE))
{
// Scan through all the pchannels and make copies of the message for each pchannel.
// Then, release this one.
DWORD dwMax = PCHANNEL_BLOCKSIZE;
// If one per channel group (for sysex, for example,) do only one per block.
if (pPMsg->dwPChannel == DMUS_PCHANNEL_BROADCAST_GROUPS) dwMax = 1;
EnterCriticalSection(&m_PipelineCrSec); // Make sure we are in this so we don't deadlock in SendPMsg().
EnterCriticalSection(&m_PChannelInfoCrSec);
CChannelBlock* pChannelBlock = m_ChannelBlockList.GetHead();
for( ; pChannelBlock; pChannelBlock = pChannelBlock->GetNext() )
{
DWORD dwIndex;
for (dwIndex = 0; dwIndex < dwMax; dwIndex++)
{
CChannelMap* pChannelMap = &pChannelBlock->m_aChannelMap[ dwIndex ];
if( pChannelMap->dwGroup &&
(pChannelMap->wFlags & (CMAP_STATIC | CMAP_VIRTUAL)))
{
DWORD dwPChannel = dwIndex + pChannelBlock->m_dwPChannelStart;
// If this is a transpose on the drum channel, don't send it.
if ((pPMsg->dwType != DMUS_PMSGT_TRANSPOSE) || ((dwPChannel & 0xF) != 9))
{
DMUS_PMSG *pNewMsg;
if (SUCCEEDED(ClonePMsg(pPMsg,&pNewMsg)))
{
pNewMsg->dwPChannel = dwIndex + pChannelBlock->m_dwPChannelStart;
SendPMsg(pNewMsg);
}
}
}
}
}
LeaveCriticalSection(&m_PChannelInfoCrSec);
LeaveCriticalSection(&m_PipelineCrSec);
return DMUS_S_FREE;
}
if(pPMsg->dwType == DMUS_PMSGT_TRANSPOSE)
{
if( !( pPMsg->dwFlags & DMUS_PMSGF_TOOL_QUEUE ))
{
// requeue any tranpose event to be queue time
pPMsg->dwFlags |= DMUS_PMSGF_TOOL_QUEUE;
pPMsg->dwFlags &= ~( DMUS_PMSGF_TOOL_ATTIME | DMUS_PMSGF_TOOL_IMMEDIATE );
return DMUS_S_REQUEUE;
}
else
{
DMUS_TRANSPOSE_PMSG* pTrans = (DMUS_TRANSPOSE_PMSG*)pPMsg;
// set the PChannel for this transpose message
EnterCriticalSection(&m_PChannelInfoCrSec);
CChannelMap * pChannelMap = GetPChannelMap(pPMsg->dwPChannel);
if (pChannelMap)
{
WORD wMergeIndex = 0;
if (pPMsg->dwFlags & DMUS_PMSGF_DX8)
{
wMergeIndex = pTrans->wMergeIndex;
}
pChannelMap->nTranspose = pChannelMap->m_TransposeMerger.MergeTranspose(
wMergeIndex,pTrans->nTranspose);
}
LeaveCriticalSection(&m_PChannelInfoCrSec);
return DMUS_S_FREE;
}
}
if(pPMsg->dwType == DMUS_PMSGT_NOTIFICATION )
{
DMUS_NOTIFICATION_PMSG* pNotify = (DMUS_NOTIFICATION_PMSG*)pPMsg;
if (pNotify->guidNotificationType == GUID_NOTIFICATION_PRIVATE_CHORD)
{
// if we've got a GUID_NOTIFICATION_PRIVATE_CHORD,
// invalidate/regenerate queued note events as necessary
EnterCriticalSection(&m_PipelineCrSec);
OnChordUpdateEventQueues(pNotify);
LeaveCriticalSection(&m_PipelineCrSec);
return DMUS_S_FREE;
}
else if( !( pPMsg->dwFlags & DMUS_PMSGF_TOOL_ATTIME ))
{
// requeue any notification event to be ontime
pPMsg->dwFlags |= DMUS_PMSGF_TOOL_ATTIME;
pPMsg->dwFlags &= ~( DMUS_PMSGF_TOOL_QUEUE | DMUS_PMSGF_TOOL_IMMEDIATE );
return DMUS_S_REQUEUE;
}
else
{
// otherwise, fire the notification
// first, move the event into the notification queue.
// The app then calls GetNotificationPMsg to get the event.
CLEARTOOLGRAPH(pPMsg);
EnterCriticalSection(&m_PipelineCrSec);
m_NotificationQueue.Enqueue( DMUS_TO_PRIV(pPMsg) );
LeaveCriticalSection(&m_PipelineCrSec);
EnterCriticalSection(&m_MainCrSec);
if( m_hNotification )
{
SetEvent(m_hNotification);
}
LeaveCriticalSection(&m_MainCrSec);
return S_OK; // don't free since we've placed the event into the
// notification queue
}
}
// add time signature changes to the time sig queue
if(pPMsg->dwType == DMUS_PMSGT_TIMESIG )
{
CLEARTOOLGRAPH(pPMsg);
DMUS_TIMESIG_PMSG* pTimeSig = (DMUS_TIMESIG_PMSG*)pPMsg;
// check for a legal time signature, which may not have any
// members equal to 0, and bBeat must be evenly divisible by 2.
if( pTimeSig->wGridsPerBeat &&
pTimeSig->bBeatsPerMeasure &&
pTimeSig->bBeat &&
( 0 == ( pTimeSig->bBeat % 2 )))
{
EnterCriticalSection(&m_PipelineCrSec);
REFERENCE_TIME rtNow = GetTime() - (10000 * 1000); // keep around for a second.
PRIV_PMSG* pCheck;
while (pCheck = m_TimeSigQueue.FlushOldest(rtNow))
{
FreePMsg(pCheck);
}
m_TimeSigQueue.Enqueue( DMUS_TO_PRIV(pPMsg) );
LeaveCriticalSection(&m_PipelineCrSec);
return S_OK;
}
else
{
return DMUS_S_FREE;
}
}
// requeue anything else that's early to be neartime
if (pPMsg->dwFlags & DMUS_PMSGF_TOOL_IMMEDIATE)
{
// if this is a stop command, make sure the segment state doesn't keep going
if( pPMsg->dwType == DMUS_PMSGT_STOP )
{
IDirectMusicSegment* pSeg = NULL;
IDirectMusicSegmentState* pSegState = NULL;
if( pPMsg->punkUser )
{
if( FAILED( pPMsg->punkUser->QueryInterface( IID_IDirectMusicSegment,
(void**)&pSeg )))
{
pSeg = NULL;
}
else if( FAILED( pPMsg->punkUser->QueryInterface( IID_IDirectMusicSegmentState,
(void**)&pSegState )))
{
pSegState = NULL;
}
}
if( pSeg || pSegState )
{
EnterCriticalSection(&m_SegmentCrSec);
if( pPMsg->mtTime > m_mtTransported )
{
// find and mark the segment and/or segment state to not play beyond
// the stop point.
CSegState* pNode;
DWORD dwCount;
for (dwCount = 0; dwCount < SQ_COUNT; dwCount++)
{
for( pNode = m_SegStateQueues[dwCount].GetHead(); pNode; pNode = pNode->GetNext() )
{
if( (pNode->m_pSegment == pSeg) ||
(pNode == pSegState) )
{
pNode->m_mtStopTime = pPMsg->mtTime;
}
}
}
}
LeaveCriticalSection(&m_SegmentCrSec);
if( pSeg )
{
pSeg->Release();
}
if( pSegState )
{
pSegState->Release();
}
}
}
pPMsg->dwFlags |= DMUS_PMSGF_TOOL_QUEUE;
pPMsg->dwFlags &= ~( DMUS_PMSGF_TOOL_ATTIME | DMUS_PMSGF_TOOL_IMMEDIATE );
return DMUS_S_REQUEUE;
}
switch( pPMsg->dwType )
{
case DMUS_PMSGT_NOTE:
{
hr = PackNote( pPMsg, pPMsg->rtTime );
}
break;
case DMUS_PMSGT_CURVE:
{
hr = PackCurve( pPMsg, pPMsg->rtTime );
}
break;
case DMUS_PMSGT_SYSEX:
{
hr = PackSysEx( pPMsg, pPMsg->rtTime );
}
break;
case DMUS_PMSGT_MIDI:
{
hr = PackMidi( pPMsg, pPMsg->rtTime );
}
break;
case DMUS_PMSGT_PATCH:
{
hr = PackPatch( pPMsg, pPMsg->rtTime );
}
break;
case DMUS_PMSGT_CHANNEL_PRIORITY:
{
DMUS_CHANNEL_PRIORITY_PMSG* pPriPMsg = (DMUS_CHANNEL_PRIORITY_PMSG*)pPMsg;
DWORD dwPortTableIndex, dwGroup, dwMChannel;
IDirectMusicPort* pPort;
hr = DMUS_S_FREE;
if( SUCCEEDED( PChannelIndex( pPriPMsg->dwPChannel, &dwPortTableIndex, &dwGroup,
&dwMChannel )))
{
EnterCriticalSection(&m_PChannelInfoCrSec);
if( dwPortTableIndex <= m_dwNumPorts )
{
pPort = m_pPortTable[dwPortTableIndex].pPort;
if( pPort )
{
pPort->SetChannelPriority( dwGroup, dwMChannel,
pPriPMsg->dwChannelPriority );
}
}
LeaveCriticalSection(&m_PChannelInfoCrSec);
}
}
break;
case DMUS_PMSGT_WAVE:
{
hr = PackWave( pPMsg, pPMsg->rtTime );
}
default:
break;
}
return hr;
}
HRESULT STDMETHODCALLTYPE CPerformance::Flush(
IDirectMusicPerformance* pPerf, // @parm The Performance pointer.
DMUS_PMSG* pPMsg, // @parm The event to flush.
REFERENCE_TIME rtTime // @parm The time at which to flush.
)
{
V_INAME(IDirectMusicTool::Flush);
V_INTERFACE(pPerf);
V_BUFPTR_WRITE(pPMsg,sizeof(DMUS_PMSG));
HRESULT hr = S_OK;
ASSERT( pPerf == this );
switch( pPMsg->dwType )
{
case DMUS_PMSGT_NOTE:
{
DMUS_NOTE_PMSG* pNote = (DMUS_NOTE_PMSG*)pPMsg;
if( !(pNote->bFlags & DMUS_NOTEF_NOTEON) )
{
PackNote( pPMsg, rtTime );
}
}
break;
case DMUS_PMSGT_CURVE:
{
DMUS_CURVE_PMSG* pCurve = (DMUS_CURVE_PMSG*)pPMsg;
if( pCurve->bFlags & DMUS_CURVE_RESET )
{
PackCurve( pPMsg, rtTime );
}
}
break;
case DMUS_PMSGT_WAVE:
{
DMUS_WAVE_PMSG* pWave = (DMUS_WAVE_PMSG*)pPMsg;
if (pWave->bFlags & DMUS_WAVEF_OFF)
{
PackWave( pPMsg, rtTime );
}
}
default:
break;
}
return hr;
}
HRESULT STDMETHODCALLTYPE CPerformance::GetMsgDeliveryType(
DWORD* pdwDeliveryType) // @parm Should return either DMUS_PMSGF_TOOL_IMMEDIATE, DMUS_PMSGF_TOOL_QUEUE, or DMUS_PMSGF_TOOL_ATTIME.
// An illegal return value will be treated as DMUS_PMSGF_TOOL_IMMEDIATE by the <i IDirectMusicGraph>.
{
V_INAME(IDirectMusicTool::GetMsgDeliveryType);
V_PTR_WRITE(pdwDeliveryType,DWORD);
*pdwDeliveryType = DMUS_PMSGF_TOOL_IMMEDIATE;
return S_OK;
}
HRESULT STDMETHODCALLTYPE CPerformance::GetMediaTypeArraySize(
DWORD* pdwNumElements) // @parm Returns the number of media types, with 0 meaning all.
{
V_INAME(IDirectMusicTool::GetMediaTypeArraySize);
V_PTR_WRITE(pdwNumElements,DWORD);
*pdwNumElements = 0;
return S_OK;
}
HRESULT STDMETHODCALLTYPE CPerformance::GetMediaTypes(
DWORD** padwMediaTypes, // @parm This should be a DWORD array of size <p dwNumElements>.
// Upon return, the elements will be filled with the media types
// this Tool supports.
DWORD dwNumElements) // @parm Contains the number of elements, i.e. the size, of the
// array <p padwMediaTypes>. <p dwNumElements> should be equal
// to the number returned in
// <om IDirectMusicTool.GetMediaTypeArraySize>. If dwNumElements
// is less than this number, this method can't return all of the
// message types that are supported. If it is greater than this
// number, the element fields in the array will be set to zero.
{
return E_NOTIMPL;
}
// IDirectMusicGraph
HRESULT STDMETHODCALLTYPE CPerformance::Shutdown()
{
return E_NOTIMPL;
}
HRESULT STDMETHODCALLTYPE CPerformance::InsertTool(
IDirectMusicTool *pTool,
DWORD *pdwPChannels,
DWORD cPChannels,
LONG lIndex)
{
return E_NOTIMPL;
}
HRESULT STDMETHODCALLTYPE CPerformance::GetTool(
DWORD dwIndex,
IDirectMusicTool** ppTool)
{
return E_NOTIMPL;
}
HRESULT STDMETHODCALLTYPE CPerformance::RemoveTool(
IDirectMusicTool* pTool)
{
return E_NOTIMPL;
}
HRESULT STDMETHODCALLTYPE CPerformance::StampPMsg( DMUS_PMSG* pPMsg )
{
V_INAME(IDirectMusicGraph::StampPMsg);
if( m_dwVersion < 8)
{
V_PTR_WRITE(pPMsg,sizeof(DMUS_PMSG));
}
else
{
#ifdef DBG
V_PTR_WRITE(pPMsg,sizeof(DMUS_PMSG));
#else
if (!pPMsg)
{
return E_POINTER;
}
#endif
}
EnterCriticalSection(&m_MainCrSec);
if( m_pGraph && ( S_OK == m_pGraph->StampPMsg( pPMsg )))
{
if (pPMsg->pGraph != this)
{
if( pPMsg->pGraph )
{
pPMsg->pGraph->Release();
pPMsg->pGraph = NULL;
}
pPMsg->pGraph = this;
pPMsg->pGraph->AddRef();
}
LeaveCriticalSection(&m_MainCrSec);
return S_OK;
}
LeaveCriticalSection(&m_MainCrSec);
if( pPMsg->pGraph )
{
pPMsg->pGraph->Release();
pPMsg->pGraph = NULL;
}
if( pPMsg->pTool )
{
pPMsg->pTool->Release();
pPMsg->pTool = NULL;
}
//otherwise there is no graph: set it to the internal Performance Tool
pPMsg->dwFlags &= ~(DMUS_PMSGF_TOOL_IMMEDIATE | DMUS_PMSGF_TOOL_QUEUE | DMUS_PMSGF_TOOL_ATTIME);
pPMsg->dwFlags |= DMUS_PMSGF_TOOL_QUEUE;
pPMsg->pTool = this;
pPMsg->pTool->AddRef();
return S_OK;
}
// default scale is C Major
const DWORD DEFAULT_SCALE_PATTERN = 0xab5ab5;
inline DWORD BitCount(DWORD dwPattern)
{
DWORD dwCount = 0;
while (dwPattern)
{
dwPattern &= (dwPattern - 1);
dwCount++;
}
return dwCount;
}
inline bool InScale(BYTE bMIDI, BYTE bRoot, DWORD dwScale)
{
TraceI(3, "note: %d root: %d scale: %x\n", bMIDI, bRoot, dwScale);
// shift the note by the scale root, and put it in a one-octave range
bMIDI = ((bMIDI + 12) - (bRoot % 12)) % 12;
// merge two octaves of scale into one
dwScale = (dwScale & 0x0fff) | ((dwScale >> 12) & 0x0fff);
// note n is in scale if there's a bit in position n
TraceI(3, "shifted note: %d shifted scale: %x\n", bMIDI, dwScale);
return ((1 << bMIDI) & dwScale) ? true : false;
}
inline DWORD CleanupScale(DWORD dwPattern)
// Force scale to be exactly two octaves
{
dwPattern &= 0x0FFF; // Clear upper octave.
dwPattern |= (dwPattern << 12); // Copy lower octave to top.
return dwPattern;
}
inline DWORD PatternMatch(DWORD dwA, DWORD dwB)
{
DWORD dwHit = 0;
DWORD dwIndex = 0;
for (;dwIndex < 24; dwIndex++)
{
if ((dwA & (1 << dwIndex)) == (dwB & (1 << dwIndex)))
{
dwHit++;
}
}
return dwHit;
}
static DWORD dwFallbackScales[12] =
{
0xab5ab5,0x6ad6ad,
0x5ab5ab,0xad5ad5,
0x6b56b5,0x5ad5ad,
0x56b56b,0xd5ad5a,
0xb56b56,0xd6ad6a,
0xb5ab5a,0xad6ad6,
};
inline DWORD FixScale(DWORD dwScale)
{
if (BitCount(dwScale & 0xFFF) > 4)
{
return dwScale;
}
DWORD dwBest = 0;
DWORD dwBestPattern = DEFAULT_SCALE_PATTERN;
DWORD dwX;
for (dwX = 0;dwX < 12; dwX++)
{
DWORD dwTest = PatternMatch(dwScale,dwFallbackScales[dwX]);
if (dwTest > dwBest)
{
dwBestPattern = dwFallbackScales[dwX];
dwBest = dwTest;
}
}
return dwBestPattern;
}
inline DWORD ThreeOctave(DWORD dwScale)
{
DWORD dwResult = dwScale;
// don't change third octave if there's something there
if ( !(0xFFF000000 & dwScale) )
{
// copy second octave to third octave
dwResult |= (dwScale & 0xFFF000) << 12;
}
return dwResult;
}
inline DWORD AddRootToScale(BYTE bScaleRoot, DWORD dwScalePattern)
{
dwScalePattern = CleanupScale(dwScalePattern);
dwScalePattern >>= (12 - (bScaleRoot % 12));
dwScalePattern = CleanupScale(dwScalePattern);
return dwScalePattern;
}
inline DWORD SubtractRootFromScale(BYTE bScaleRoot, DWORD dwScalePattern)
{
dwScalePattern = CleanupScale(dwScalePattern);
dwScalePattern >>= (bScaleRoot % 12);
dwScalePattern = CleanupScale(dwScalePattern);
return dwScalePattern;
}
static DWORD ChordFromScale(BYTE bRoot, DWORD dwScalePattern)
{
DWORD dwChordPattern = CleanupScale(dwScalePattern >> (bRoot % 12));
DWORD dwX;
DWORD dwBitCount = 0;
for (dwX = 0; dwX < 24; dwX++)
{
DWORD dwBit = 1 << dwX;
if (dwChordPattern & dwBit)
{
if ((dwBitCount & 1) || (dwBitCount > 7))
{
dwChordPattern &= ~dwBit;
}
dwBitCount++;
}
}
return dwChordPattern;
}
static DWORD InvertChord(BYTE bKey, BYTE bChordRoot, DWORD dwChordPattern, bool& rfBelowRoot)
{
// rotate the chord by the difference between the key and chord root
rfBelowRoot = false;
bKey %= 12;
bChordRoot %= 12;
if (bKey < bChordRoot) bKey += 12;
BYTE bRotate = bKey - bChordRoot;
// first check if the whole chord fits into one octave
if ( !(dwChordPattern & 0xFFF000) )
{
dwChordPattern = ThreeOctave(CleanupScale(dwChordPattern));
dwChordPattern >>= bRotate;
dwChordPattern &= 0xFFF;
if (!(dwChordPattern & 0x7) && ((dwChordPattern & 0xc00)) ||
!(dwChordPattern & 0x3) && ((dwChordPattern & 0x800)))
{
dwChordPattern |= (dwChordPattern << 12);
dwChordPattern &= 0x3FFC00;
rfBelowRoot = true;
}
}
else
{
dwChordPattern &= 0xFFFFFF; // make sure there are only notes in the two-octave range
// do a circular shift in the closest direction
BYTE bRotate2 = (bChordRoot + 12) - bKey;
if (bRotate <= bRotate2)
{
dwChordPattern = (dwChordPattern << (24 - bRotate)) | (dwChordPattern >> bRotate);
}
else
{
dwChordPattern = (dwChordPattern >> (24 - bRotate2)) | (dwChordPattern << bRotate2);
}
dwChordPattern &= 0xFFFFFF;
if (!(dwChordPattern & 0x7) &&
(!(dwChordPattern & 0x7000) && ((dwChordPattern & 0xc00000)) ||
!(dwChordPattern & 0x3000) && ((dwChordPattern & 0x800000)) ||
!(dwChordPattern & 0x1000) && ((dwChordPattern & 0x1000000)) ||
!(dwChordPattern & 0x7) && (dwChordPattern & 0x7000) && ((dwChordPattern & 0xc00000))) )
{
dwChordPattern = (dwChordPattern << 12) | (dwChordPattern >> 12);
dwChordPattern &= 0xFFFFFF;
}
if (!(dwChordPattern & 0x7) && ((dwChordPattern & 0xc00)) ||
!(dwChordPattern & 0x3) && ((dwChordPattern & 0x800)) ||
!(dwChordPattern & 0x1) && ((dwChordPattern & 0x1000)) )
{
// put everything up to the G in the first octave two octaves up;
// put G# and A one octave up
dwChordPattern |= (((dwChordPattern & 0xFF) << 24) | ((dwChordPattern & 0x300) << 12));
// get rid of everything below A# in the first octave
dwChordPattern &= 0xFFFFFC00;
// If there are no notes lower than C2, shift everything back down an octave
if (!(dwChordPattern & 0xFFF))
{
dwChordPattern >>= 12;
}
else
{
rfBelowRoot = true;
}
}
}
return dwChordPattern;
}
/* This is SuperJAM! code */
static unsigned char OldMusicValueToNote(
unsigned short value, // Music value to convert.
char scalevalue, // Scale value if chord failes.
long keypattern, // Description of key as interval pattern.
char keyroot, // Root note of key.
long chordpattern, // Description of chord as interval pattern.
char chordroot) // Root note of chord.
{
unsigned char result ;
char octpart = (char)(value >> 12) ;
char chordpart = (char)((value >> 8) & 0xF) ;
char keypart = (char)((value >> 4) & 0xF) ;
char accpart = (char)(value & 0xF) ;
result = unsigned char(12 * octpart) ;
result += chordroot ;
if( accpart > 8 )
accpart -= 16 ;
for( ; chordpattern ; result++ ) {
if( chordpattern & 1L ) {
if( !chordpart )
break ;
chordpart-- ;
}
chordpattern = chordpattern >> 1L ;
if( !chordpattern ) {
if( !scalevalue )
return( 0 ) ;
result = unsigned char(12 * octpart) ;
result += chordroot ;
keypart = char(scalevalue >> 4) ;
accpart = char(scalevalue & 0x0F) ;
break ;
}
}
if( keypart ) {
keypattern = CleanupScale(keypattern) ;
keypattern = keypattern >> (LONG)((result - keyroot) % 12) ;
for( ; keypattern ; result++ ) {
if( keypattern & 1L ) {
if( !keypart )
break ;
keypart-- ;
}
keypattern = keypattern >> 1L ;
}
}
result += unsigned char(accpart) ;
return( result ) ;
}
/* This is SuperJAM! code */
static unsigned short OldNoteToMusicValue(
unsigned char note, // MIDI note to convert.
long keypattern, // Description of key as interval pattern.
char keyroot, // Root note of key.
long chordpattern, // Description of chord as interval pattern.
char chordroot) // Root note of chord.
{
unsigned char octpart = 0 ;
unsigned char chordpart = 0;
unsigned char keypart = (BYTE)-1 ;
unsigned char accpart = 0 ;
unsigned char scan, test, base, last ; // was char
long pattern ;
short testa, testb ;
scan = chordroot ;
// If we're trying to play a note below the bottom of our chord, forget it
if( note < scan)
{
return 0;
}
while( scan < (note - 24) )
{
scan += 12 ;
octpart++ ;
}
base = last = scan ;
for( ; base<=note ; base+=12 )
{
chordpart = (unsigned char)-1 ;
pattern = chordpattern ;
scan = last = base ;
if( scan == note )
{
accpart = 0;
while (!(pattern & 1) && pattern)
{
accpart--;
pattern >>= 1;
}
return( (unsigned short) (octpart << 12) + (accpart & 0xF)) ; // if octave, return.
}
for( ; pattern ; pattern=pattern >> 1 )
{
if( pattern & 1 ) // chord interval?
{
if( scan == note ) // note in chord?
{
chordpart++ ;
return((unsigned short) ((octpart << 12) | (chordpart << 8))) ; // yes, return.
}
else if (scan > note) // above note?
{
test = scan ;
break ; // go on to key.
}
chordpart++ ;
last = scan ;
}
scan++ ;
}
if( !pattern ) // end of chord.
{
test = unsigned char(base + 12) ; // set to next note.
}
octpart++ ;
if( test > note )
{
break ; // above our note?
}
}
octpart-- ;
// To get here, the note is not in the chord. Scan should show the last
// note in the chord. octpart and chordpart have their final values.
// Now, increment up the key to find the match.
scan = last ;
pattern = CleanupScale(keypattern);
pattern = pattern >> ((scan - keyroot) % 12) ;
for( ; pattern ; pattern=pattern >> 1 )
{
if( 1 & pattern )
{
keypart++ ;
accpart = 0 ;
}
else
{
accpart++ ;
}
if( scan == note )
break ;
scan++;
}
if( accpart && keypart )
{
testa = short((octpart << 12) + (chordpart << 8) + (keypart << 4) + accpart + 1);
testb = short((octpart << 12) + ((chordpart + 1) << 8) + 0);
testa = OldMusicValueToNote( testa, 0, keypattern, keyroot,
chordpattern, chordroot );
testb = OldMusicValueToNote( testb, 0, keypattern, keyroot,
chordpattern, chordroot );
if( testa == testb )
{
chordpart++ ;
keypart = 0 ;
accpart = -1 ;
}
}
// If the conversion didn't find an exact match, fudge accpart to make it work
testa = short((octpart << 12) + (chordpart << 8) + (keypart << 4) + (accpart & 0xF));
testa = OldMusicValueToNote( testa, 0, keypattern, keyroot,
chordpattern, chordroot );
if( testa != note )
{
accpart += note - testa;
}
return unsigned short((octpart << 12) + (chordpart << 8) + (keypart << 4) + (accpart & 0xF));
}
inline short MusicValueOctave(WORD wMusicValue)
{ return short((wMusicValue >> 12) & 0xf) * 12; }
inline short MusicValueAccidentals(WORD wMusicValue)
{
short acc = short(wMusicValue & 0xf);
return (acc > 8) ? acc - 16 : acc;
}
inline short BitsInChord(DWORD dwChordPattern)
{
for (short nResult = 0; dwChordPattern != 0; dwChordPattern >>= 1)
if (dwChordPattern & 1) nResult++;
return nResult;
}
#define S_OVER_CHORD 0x1000 // Success code to indicate the musicval could not be
// converted because the note is above the top of the chord.
short MusicValueIntervals(WORD wMusicValue, BYTE bPlayModes, DMUS_SUBCHORD *pSubChord, BYTE bRoot)
{
if ((bPlayModes & DMUS_PLAYMODE_CHORD_INTERVALS) || (bPlayModes & DMUS_PLAYMODE_SCALE_INTERVALS))
{
DWORD dwDefaultScale =
(pSubChord->dwScalePattern) ? (pSubChord->dwScalePattern) : DEFAULT_SCALE_PATTERN;
dwDefaultScale = AddRootToScale(pSubChord->bScaleRoot, dwDefaultScale);
dwDefaultScale = ThreeOctave(FixScale(dwDefaultScale));
DWORD dwChordPattern = pSubChord->dwChordPattern;
if (!dwChordPattern) dwChordPattern = 1;
bool fBelowRoot = false;
if ((bPlayModes & DMUS_PLAYMODE_KEY_ROOT) && bPlayModes != DMUS_PLAYMODE_PEDALPOINT)
{
dwChordPattern = InvertChord(bRoot, pSubChord->bChordRoot, dwChordPattern, fBelowRoot);
}
const short nChordPosition = (wMusicValue >> 8) & 0xf;
// const short nScalePosition = (wMusicValue >> 4) & 0xf;
// ensure that scale position is < 8
const short nScalePosition = (wMusicValue >> 4) & 0x7;
const short nChordBits = BitsInChord(dwChordPattern);
short nSemitones = 0;
// If the chord doesn't have a root or second, but does have a seventh, it's been inverted and
// we need to start below the root
short nTransposetones;
DWORD dwPattern;
short nPosition;
BYTE bOctRoot = bRoot % 12; // root in one octave
// if using chord intervals and the note is in the chord
if ((bPlayModes & DMUS_PLAYMODE_CHORD_INTERVALS) &&
!nScalePosition &&
(nChordPosition < nChordBits) )
{
nTransposetones = bRoot + MusicValueAccidentals(wMusicValue);
dwPattern = dwChordPattern;
nPosition = nChordPosition;
}
// if using chord intervals and note is inside the chord (including 6ths)
else if ((bPlayModes & DMUS_PLAYMODE_CHORD_INTERVALS) &&
(nChordPosition < nChordBits) )
{
dwPattern = dwChordPattern;
nPosition = nChordPosition;
if (dwPattern)
{
// skip to the first note in the chord
while (!(dwPattern & 1))
{
dwPattern >>= 1;
nSemitones++;
}
}
if (nPosition > 0)
{
do
{
dwPattern >>= 1; // this will ignore the first note in the chord
nSemitones++;
if (dwPattern & 1)
{
nPosition--;
}
if (!dwPattern)
{
nSemitones += nPosition;
// assert (0); // This shouldn't happen...
break;
}
} while (nPosition > 0);
}
nSemitones += bOctRoot;
nTransposetones = MusicValueAccidentals(wMusicValue) + bRoot - bOctRoot;
dwPattern = dwDefaultScale >> (nSemitones % 12); // start comparing partway through the pattern
nPosition = nScalePosition;
}
// if using scale intervals
else if (bPlayModes & DMUS_PLAYMODE_SCALE_INTERVALS)
{
fBelowRoot = false; // forget about chord inversions
nSemitones = bOctRoot;
nTransposetones = MusicValueAccidentals(wMusicValue) + bRoot - bOctRoot;
dwPattern = dwDefaultScale >> bOctRoot; // start comparing partway through the pattern
nPosition = nChordPosition * 2 + nScalePosition;
}
else
{
return S_OVER_CHORD; //
}
nPosition++; // Now nPosition corresponds to actual scale positions
for (; nPosition > 0; dwPattern >>= 1)
{
nSemitones++;
if (dwPattern & 1)
{
nPosition--;
}
if (!dwPattern)
{
nSemitones += nPosition;
// assert (0); // This shouldn't happen...
break;
}
}
nSemitones--; // the loop counts one too many semitones...
if (fBelowRoot)
{
nSemitones -=12;
}
return nSemitones + nTransposetones;
}
else
{
// should be impossible for 2.5 format
return bRoot + wMusicValue;
}
}
inline short MusicValueChord(WORD wMusicValue, BYTE bPlayModes, DMUS_SUBCHORD *pSubChord, BYTE bKey)
{
// first, get the root for transposition.
BYTE bRoot = 0;
if (bPlayModes & DMUS_PLAYMODE_CHORD_ROOT)
{
bRoot = pSubChord->bChordRoot;
}
else if (bPlayModes & DMUS_PLAYMODE_KEY_ROOT)
bRoot = bKey;
// Next, get an interval and combine it with the root.
return MusicValueIntervals(wMusicValue, bPlayModes, pSubChord, bRoot);
}
inline short MusicValueConvert(WORD wMV, BYTE bPlayModes, DMUS_SUBCHORD *pSubChord, BYTE bKey)
{
short nResult = 0;
// First, make sure the octave is not negative.
short nOffset = 0;
while (wMV >= 0xE000)
{
wMV += 0x1000;
nOffset -= 12;
}
// If the music value has a negative scale offset, convert to an equivalent
// music value with a positive offset (up an octave) and shift the whole thing
// down an octave
WORD wTemp = (wMV & 0x00f0) + 0x0070;
if (wTemp & 0x0f00)
{
wMV = (wMV & 0xff0f) | (wTemp & 0x00f0);
nOffset = -12;
}
short nChordValue = MusicValueChord(wMV, bPlayModes, pSubChord, bKey);
if (nChordValue != S_OVER_CHORD)
{
nChordValue += nOffset;
// If the chord root is < 12, take the result down an octave.
if ((bPlayModes & DMUS_PLAYMODE_CHORD_ROOT))
nResult = MusicValueOctave(wMV) + nChordValue - 12;
else
nResult = MusicValueOctave(wMV) + nChordValue;
}
else
nResult = S_OVER_CHORD;
return nResult;
}
HRESULT STDMETHODCALLTYPE CPerformance::MIDIToMusic(
BYTE bMIDIValue,
DMUS_CHORD_KEY* pChord,
BYTE bPlayMode,
BYTE bChordLevel,
WORD *pwMusicValue
)
{
V_INAME(IDirectMusicPerformance::MIDIToMusic);
V_BUFPTR_READ( pChord, sizeof(DMUS_CHORD_KEY) );
V_PTR_WRITE(pwMusicValue,WORD);
long lMusicValue;
HRESULT hr = S_OK;
#ifdef DBG
long lMIDIInTraceValue = bMIDIValue;
#endif
if ((bPlayMode & DMUS_PLAYMODE_NONE ) || (bMIDIValue & 0x80))
{
Trace(1,"Error: MIDIToMusic conversion failed either because there is no playmode or MIDI value %ld is out of range.\n",(long)bMIDIValue);
return E_INVALIDARG;
}
else if( bPlayMode == DMUS_PLAYMODE_FIXED )
{
*pwMusicValue = bMIDIValue & 0x7F;
return S_OK;
}
else if (bPlayMode == DMUS_PLAYMODE_FIXEDTOKEY) // fixed to key
{
lMusicValue = bMIDIValue - pChord->bKey;
while (lMusicValue < 0)
{
lMusicValue += 12;
Trace(2,"Warning: MIDIToMusic had to bump the music value up an octave for DMUS_PLAYMODE_FIXEDTOKEY note.\n");
hr = DMUS_S_UP_OCTAVE;
}
while (lMusicValue > 127)
{
lMusicValue -= 12;
Trace(2,"Warning: MIDIToMusic had to bump the music value up an octave for DMUS_PLAYMODE_FIXEDTOKEY note.\n");
hr = DMUS_S_DOWN_OCTAVE;
}
*pwMusicValue = (WORD) lMusicValue;
return hr;
}
else
{
DMUS_SUBCHORD *pSubChord;
DWORD dwLevel = 1 << bChordLevel;
bool fFoundLevel = false;
for (int i = 0; i < pChord->bSubChordCount; i++)
{
if (dwLevel & pChord->SubChordList[i].dwLevels)
{
pSubChord = &pChord->SubChordList[i];
fFoundLevel = true;
break;
}
}
if (!fFoundLevel) // No luck? Use first chord.
{
pSubChord = &pChord->SubChordList[0];
}
if (bPlayMode == DMUS_PLAYMODE_FIXEDTOCHORD) // fixed to chord
{
lMusicValue = bMIDIValue - (pSubChord->bChordRoot % 24);
while (lMusicValue < 0)
{
lMusicValue += 12;
Trace(2,"Warning: MIDIToMusic had to bump the music value up an octave for DMUS_PLAYMODE_FIXEDTOCHORD note.\n");
hr = DMUS_S_UP_OCTAVE;
}
while (lMusicValue > 127)
{
lMusicValue -= 12;
Trace(2,"Warning: MIDIToMusic had to bump the music value down an octave for DMUS_PLAYMODE_FIXEDTOCHORD note.\n");
hr = DMUS_S_DOWN_OCTAVE;
}
*pwMusicValue = (WORD) lMusicValue;
return hr;
}
bool fBelowRoot = false;
DWORD dwScalePattern = AddRootToScale(pSubChord->bScaleRoot, pSubChord->dwScalePattern);
DWORD dwChordPattern = pSubChord->dwChordPattern;
BYTE bKeyRoot = pChord->bKey;
BYTE bChordRoot = pSubChord->bChordRoot;
dwScalePattern = FixScale(dwScalePattern);
bPlayMode &= 0xF; // We only know about the bottom four flags, at this point.
// if (bPlayMode == DMUS_PLAYMODE_PEDALPOINT)
// Do this for any non-fixed key root mode (Pedalpoint, PedalpointChord, PedalpointAlways)
if (bPlayMode & DMUS_PLAYMODE_KEY_ROOT)
{
while (bKeyRoot > bMIDIValue)
{
hr = DMUS_S_UP_OCTAVE;
Trace(2,"Warning: MIDIToMusic had to bump the music value up an octave for DMUS_PLAYMODE_KEY_ROOT note.\n");
bMIDIValue += 12;
}
dwScalePattern = SubtractRootFromScale(bKeyRoot,dwScalePattern);
if (bPlayMode == DMUS_PLAYMODE_PEDALPOINT || !dwChordPattern)
{
bChordRoot = bKeyRoot;
dwChordPattern = ChordFromScale(0,dwScalePattern);
}
else
{
dwChordPattern = InvertChord(bKeyRoot, bChordRoot, dwChordPattern, fBelowRoot);
BYTE bNewChordRoot = 0;
if (dwChordPattern)
{
for (; !(dwChordPattern & (1 << bNewChordRoot)); bNewChordRoot++);
}
bChordRoot = bNewChordRoot + bKeyRoot;
dwChordPattern >>= bNewChordRoot;
}
}
else if (bPlayMode == DMUS_PLAYMODE_MELODIC)
{
bKeyRoot = 0;
dwChordPattern = ChordFromScale(bChordRoot,dwScalePattern);
}
else
{
bKeyRoot = 0;
if (!dwChordPattern)
{
dwChordPattern = ChordFromScale(bChordRoot,dwScalePattern);
}
}
BOOL fDropOctave = FALSE;
if (bMIDIValue < 24)
{
fDropOctave = TRUE;
bMIDIValue += 24;
}
WORD wNewMusicValue = OldNoteToMusicValue( bMIDIValue,
dwScalePattern,
bKeyRoot,
dwChordPattern,
bChordRoot );
if (fDropOctave)
{
wNewMusicValue -= 0x2000;
bMIDIValue -= 24;
}
// If DMUS_PLAYMODE_CHORD_ROOT is set, take the result up an octave.
// // also take the result up for the new pedalpoint chord modes.
if( (bPlayMode & DMUS_PLAYMODE_CHORD_ROOT) ||
fBelowRoot)
//((bPlayMode & DMUS_PLAYMODE_KEY_ROOT) && bPlayMode != DMUS_PLAYMODE_PEDALPOINT) )
{
wNewMusicValue += 0x1000;
}
short nTest =
MusicValueConvert(wNewMusicValue, bPlayMode,
pSubChord, pChord->bKey);
if (nTest == (short) bMIDIValue)
{
*pwMusicValue = wNewMusicValue;
}
else
{
if (nTest == S_OVER_CHORD)
{
if (BitCount(pSubChord->dwChordPattern) < 4)
{
DWORD dwOldChordPattern = pSubChord->dwChordPattern;
pSubChord->dwChordPattern = ChordFromScale(bChordRoot,dwScalePattern);
nTest =
MusicValueConvert(wNewMusicValue, bPlayMode,
pSubChord, pChord->bKey);
pSubChord->dwChordPattern = dwOldChordPattern;
if (nTest == (short) bMIDIValue)
{
*pwMusicValue = wNewMusicValue;
return hr;
}
}
}
*pwMusicValue = wNewMusicValue;
#ifdef DBG // Put in brackets just in case the compiler is using something different than DBG for turning on Trace.
Trace(1,"Error: Unable to convert MIDI value %ld to Music value. This usually means the DMUS_CHORD_KEY structure has an invalid chord or scale pattern.\n",
lMIDIInTraceValue);
#endif
return DMUS_E_CANNOT_CONVERT;
}
}
return hr;
}
HRESULT STDMETHODCALLTYPE CPerformance::MusicToMIDI(
WORD wMusicValue,
DMUS_CHORD_KEY* pChord,
BYTE bPlayMode,
BYTE bChordLevel,
BYTE *pbMIDIValue
)
{
V_INAME(IDirectMusicPerformance::MusicToMIDI);
V_BUFPTR_READ( pChord, sizeof(DMUS_CHORD_KEY) );
V_PTR_WRITE(pbMIDIValue,BYTE);
long lReturnVal = wMusicValue;
HRESULT hr = S_OK;
if (bPlayMode != DMUS_PLAYMODE_FIXED)
{
DMUS_SUBCHORD *pSubChord;
DWORD dwLevel = 1 << bChordLevel;
bool fFoundLevel = false;
for (int i = 0; i < pChord->bSubChordCount; i++)
{
if (dwLevel & pChord->SubChordList[i].dwLevels)
{
pSubChord = &pChord->SubChordList[i];
fFoundLevel = true;
break;
}
}
if (!fFoundLevel) // No luck? Use first chord.
{
pSubChord = &pChord->SubChordList[0];
}
if (bPlayMode & DMUS_PLAYMODE_NONE )
{
*pbMIDIValue = 0;
Trace(1,"Error: Unable to convert Music value to MIDI because the playmode is DMUS_PLAYMODE_NONE.\n");
return E_INVALIDARG;
}
if (bPlayMode == DMUS_PLAYMODE_FIXEDTOCHORD) // fixed to chord
{
lReturnVal += (pSubChord->bChordRoot % 24);
}
else if (bPlayMode == DMUS_PLAYMODE_FIXEDTOKEY) // fixed to scale
{
lReturnVal += pChord->bKey;
}
else
{
lReturnVal =
MusicValueConvert((WORD)lReturnVal, bPlayMode, pSubChord, pChord->bKey);
}
}
if (lReturnVal == S_OVER_CHORD)
{
Trace(5,"Warning: MIDIToMusic unable to convert because note out of chord range.\n");
return DMUS_S_OVER_CHORD;
}
while (lReturnVal < 0)
{
lReturnVal += 12;
Trace(2,"Warning: MusicToMIDI had to bump the music value up an octave to stay in MIDI range.\n");
hr = DMUS_S_UP_OCTAVE;
}
while (lReturnVal > 127)
{
lReturnVal -= 12;
Trace(2,"Warning: MusicToMIDI had to bump the music value down an octave to stay in MIDI range.\n");
hr = DMUS_S_DOWN_OCTAVE;
}
*pbMIDIValue = (BYTE) lReturnVal;
return hr;
}
// returns:
// S_OK if the note should be invalidated (any other return code will not invalidate)
// S_FALSE if processing otherwise succeeded, but the note should not be invalidated
// E_OUTOFMEMORY if allocation of a new note failed
HRESULT CPerformance::GetChordNotificationStatus(DMUS_NOTE_PMSG* pNote,
//IDirectMusicSegment* pSegment,
DWORD dwTrackGroup,
REFERENCE_TIME rtTime,
DMUS_PMSG** ppNew)
{
HRESULT hr = S_FALSE; // default: succeed, but don't invalidate the note
DMUS_CHORD_PARAM CurrentChord;
MUSIC_TIME mtTime;
ReferenceToMusicTime(rtTime, &mtTime);
if (pNote->bFlags & (DMUS_NOTEF_NOINVALIDATE_INSCALE | DMUS_NOTEF_NOINVALIDATE_INCHORD))
{
// If the note is inconsistent with the current scale/chord, invalidate it
if (SUCCEEDED(GetParam(GUID_ChordParam, dwTrackGroup, DMUS_SEG_ANYTRACK,
mtTime, NULL, (void*) &CurrentChord)))
{
if (CurrentChord.bSubChordCount > 0)
{
BYTE bRoot = CurrentChord.SubChordList[0].bChordRoot;
DWORD dwScale = CurrentChord.SubChordList[0].dwScalePattern;
if (pNote->bFlags & DMUS_NOTEF_NOINVALIDATE_INCHORD)
{
dwScale = CurrentChord.SubChordList[0].dwChordPattern;
}
else
{
dwScale = FixScale(SubtractRootFromScale(bRoot, dwScale));
}
if (!InScale(pNote->bMidiValue, bRoot, dwScale))
{
hr = S_OK;
}
}
}
}
else if (pNote->bFlags & DMUS_NOTEF_REGENERATE)
{
// this always causes an invalidation, and in addition generates a new note event,
// based on the Music Value of the current one, that starts at rtTime
// and continues until pNote->mtTime + pNote->Duration
// EXCEPTION: the newly generated note is the same as the currently playing one
if (SUCCEEDED(GetParam(GUID_ChordParam, dwTrackGroup, DMUS_SEG_ANYTRACK,
mtTime, NULL, (void*) &CurrentChord)))
{
BYTE bNewMidiValue = 0;
if (SUCCEEDED(MusicToMIDI(pNote->wMusicValue, &CurrentChord, pNote->bPlayModeFlags,
pNote->bSubChordLevel, &bNewMidiValue)) &&
bNewMidiValue != pNote->bMidiValue)
{
MUSIC_TIME mtDuration = (pNote->bFlags & DMUS_NOTEF_NOTEON) ? pNote->mtDuration - (mtTime - pNote->mtTime) : pNote->mtTime - mtTime;
// Make any duration < 1 be 0; this will cause the note not to
// sound. Can happen if the note's logical time is well before
// its physical time.
if( mtDuration < 1 ) mtDuration = 0;
DMUS_PMSG* pNewPMsg = NULL;
if( SUCCEEDED( AllocPMsg( sizeof(DMUS_NOTE_PMSG), &pNewPMsg )))
{
DMUS_NOTE_PMSG* pNewNote = (DMUS_NOTE_PMSG*)pNewPMsg;
// start by copying the current note into the new one
pNewNote->dwFlags = pNote->dwFlags;
pNewNote->dwPChannel = pNote->dwPChannel;
pNewNote->dwVirtualTrackID = pNote->dwVirtualTrackID;
pNewNote->pTool = pNote->pTool;
if (pNewNote->pTool) pNewNote->pTool->AddRef();
pNewNote->pGraph = pNote->pGraph;
if (pNewNote->pGraph) pNewNote->pGraph->AddRef();
pNewNote->dwType = pNote->dwType;
pNewNote->dwVoiceID = pNote->dwVoiceID;
pNewNote->dwGroupID = pNote->dwGroupID;
pNewNote->punkUser = pNote->punkUser;
if (pNewNote->punkUser) pNewNote->punkUser->AddRef();
pNewNote->wMusicValue = pNote->wMusicValue;
pNewNote->wMeasure = pNote->wMeasure;
pNewNote->nOffset = pNote->nOffset;
pNewNote->bBeat = pNote->bBeat;
pNewNote->bGrid = pNote->bGrid;
pNewNote->bVelocity = pNote->bVelocity;
pNewNote->bTimeRange = pNote->bTimeRange;
pNewNote->bDurRange = pNote->bDurRange;
pNewNote->bVelRange = pNote->bVelRange;
pNewNote->bPlayModeFlags = pNote->bPlayModeFlags;
pNewNote->bSubChordLevel = pNote->bSubChordLevel;
pNewNote->cTranspose = pNote->cTranspose;
// only things that need to change are flags, MIDI value, start time, and duration
pNewNote->mtTime = mtTime;
MusicToReferenceTime(pNewNote->mtTime, &pNewNote->rtTime);
pNewNote->mtDuration = mtDuration;
pNewNote->bMidiValue = bNewMidiValue;
pNewNote->bFlags = DMUS_NOTEF_NOTEON | DMUS_NOTEF_REGENERATE;
PackNote(pNewPMsg, rtTime + 1); // play the note on
*ppNew = pNewPMsg; // PackNote modifies event to be note-off; queue this
// invalidate the current note
hr = S_OK;
}
else hr = E_OUTOFMEMORY;
}
}
}
return hr;
}
HRESULT STDMETHODCALLTYPE CPerformance::TimeToRhythm(
MUSIC_TIME mtTime,
DMUS_TIMESIGNATURE *pTimeSig,
WORD *pwMeasure,
BYTE *pbBeat,
BYTE *pbGrid,
short *pnOffset
)
{
V_INAME(IDirectMusicPerformance::TimeToRhythm);
V_BUFPTR_READ( pTimeSig, sizeof(DMUS_TIMESIGNATURE) );
V_PTR_WRITE(pwMeasure,WORD);
V_PTR_WRITE(pbBeat,BYTE);
V_PTR_WRITE(pbGrid,BYTE);
V_PTR_WRITE(pnOffset,short);
long lMeasureLength;
long lBeatLength = DMUS_PPQ;
long lGridLength;
if( pTimeSig->bBeat )
{
lBeatLength = DMUS_PPQ * 4 / pTimeSig->bBeat;
}
lMeasureLength = lBeatLength * pTimeSig->bBeatsPerMeasure;
if( pTimeSig->wGridsPerBeat )
{
lGridLength = lBeatLength / pTimeSig->wGridsPerBeat;
}
else
{
lGridLength = lBeatLength / 256;
}
long lTemp = mtTime - pTimeSig->mtTime;
*pwMeasure = (WORD)((lTemp / lMeasureLength));
lTemp = lTemp % lMeasureLength;
*pbBeat = (BYTE)(lTemp / lBeatLength);
lTemp = lTemp % lBeatLength;
*pbGrid = (BYTE)(lTemp / lGridLength);
*pnOffset = (short)(lTemp % lGridLength);
if (*pnOffset > (lGridLength >> 1))
{
*pnOffset -= (short) lGridLength;
(*pbGrid)++;
if (*pbGrid == pTimeSig->wGridsPerBeat)
{
*pbGrid = 0;
(*pbBeat)++;
if (*pbBeat == pTimeSig->bBeatsPerMeasure)
{
*pbBeat = 0;
(*pwMeasure)++;
}
}
}
return S_OK;
}
HRESULT STDMETHODCALLTYPE CPerformance::RhythmToTime(
WORD wMeasure,
BYTE bBeat,
BYTE bGrid,
short nOffset,
DMUS_TIMESIGNATURE *pTimeSig,
MUSIC_TIME *pmtTime
)
{
V_INAME(IDirectMusicPerformance::RhythmToTime);
V_BUFPTR_READ( pTimeSig, sizeof(DMUS_TIMESIGNATURE) );
V_PTR_WRITE(pmtTime,MUSIC_TIME);
long lMeasureLength;
long lBeatLength = DMUS_PPQ;
long lGridLength;
if( pTimeSig->bBeat )
{
lBeatLength = DMUS_PPQ * 4 / pTimeSig->bBeat;
}
lMeasureLength = lBeatLength * pTimeSig->bBeatsPerMeasure;
if( pTimeSig->wGridsPerBeat )
{
lGridLength = lBeatLength / pTimeSig->wGridsPerBeat;
}
else
{
lGridLength = lBeatLength / 256;
}
long lTemp = nOffset + pTimeSig->mtTime;
lTemp += wMeasure * lMeasureLength;
lTemp += bBeat * lBeatLength;
lTemp += bGrid * lGridLength;
*pmtTime = lTemp;
return S_OK;
}