/*		DirectMusic
 *
 * Copyright 2002-2003 TransGaming Technologies Inc. 
 *
 */

#include "config.h"
#include <assert.h>
#include <string.h>
#include <unistd.h>

#include "wine/debug.h"
#include "winbase.h"
#include "winerror.h"
#include "windef.h"
#include "wingdi.h"
#include "winuser.h"
#include "winnls.h"
#include "mmsystem.h"
#include "ole2.h"
#include "strmif.h"
#include "control.h"
#include "uuids.h"
#include "errors.h"
#include "dsound.h"
#include "dmusici.h"
#include "dmusicf.h"
#include "dmime_private.h"

WINE_DEFAULT_DEBUG_CHANNEL(dmusic);

/* Message queue stuff */
typedef struct DMPerf_PMsgQueue DMPerf_PMsgQueue;
typedef struct DMPerf_PMsgQueueItem DMPerf_PMsgQueueItem;
struct DMPerf_PMsgQueueItem
{
	DMPerf_PMsgQueueItem *pPrev;
	DMPerf_PMsgQueueItem *pNext;
	REFERENCE_TIME rtTime; /* reference time of message.
                            * if message is supplied in reference time its
                            * just copied over, otherwise converted */
	BOOL bInQueue; /* is it currently in a queue? */

	DMPerf_PMsgQueue *pQueue; /* must contain a pointer to the queue
                                 so we can lock and remove from
                                 queue when freeing */
	DMUS_PMSG pmsg;
	/* pPMSG expands, must be last element. */
};

#define DM_PMSGQUEUEITEM_FROM_PMSG(pPMSG) \
	(DMPerf_PMsgQueueItem *)(((char*)pPMSG) - (sizeof(DMPerf_PMsgQueueItem) - sizeof(DMUS_PMSG)))
#define DM_PMSG_FROM_PMSGQUEUEITEM(pPMSGQueueItem) \
	(&(pPMSGQueueItem->pmsg))

struct DMPerf_PMsgQueue
/* items added to the list sorted. items at the head are taken first.
 * items are doubly linked so they can be removed.
 */
{
	DMPerf_PMsgQueueItem *pHead;
	CRITICAL_SECTION cs_queue;
};

typedef struct TempoChange {
	struct TempoChange* prev;
	struct TempoChange* next;
	REFERENCE_TIME rtTime;
	MUSIC_TIME mtTime;
	double dblTempo;
} TempoChange;

typedef struct SegmentChange {
	struct SegmentChange* next;
	REFERENCE_TIME rtTime;
	DWORD dwFlags;
	IDirectMusicSegmentState8* pSegState;
	BOOL stop;
} SegmentChange;

/*****************************************************************************
 * IDirectMusicPerformance implementation structure
 */
typedef struct IDirectMusicPerformanceImpl IDirectMusicPerformanceImpl;
struct IDirectMusicPerformanceImpl
{
    /* IUnknown fields */
    ICOM_VFIELD(IDirectMusicPerformance8);
    DWORD ref;
    /* IDirectMusicPerformance fields */
    IDirectMusic* pDirectMusic;
    IDirectSound* pDirectSound;
    DMUS_AUDIOPARAMS params;
    IDirectMusicAudioPath* pDefaultAudioPath;
    HANDLE hNotification;
    REFERENCE_TIME rtNotification;
    DWORD dwPrepareTime;
    DWORD dwLatencyTime;
    DWORD dwBumperTime;
    HANDLE hPlayer;

    BOOL started;
    REFERENCE_TIME rtStart;
    CRITICAL_SECTION cs_tempo;
    /*TempoChange* first_tempo;*/
    TempoChange* last_tempo;
    CRITICAL_SECTION cs_segstate;
    DWORD segstates;
    IDirectMusicSegmentState8** segstate;
    CRITICAL_SECTION cs_seg;
    IDirectMusicSegment* primary_seg;
    IDirectMusicSegmentState* primary_segstate;
    IDirectMusicSegment* control_seg;
    SegmentChange* primary_queue;
    SegmentChange* seg_change;

    DMPerf_PMsgQueue pPMsgTimeQueue;
    DMPerf_PMsgQueue pPMsgImmediateQueue;
    
    CRITICAL_SECTION thread_blocker;
};

static DWORD CALLBACK PerformancePlayback(LPVOID arg);

static HRESULT WINAPI IDirectMusicPerformanceImpl_QueryInterface(
	IDirectMusicPerformance8* iface,REFIID riid,LPVOID *ppobj
) {
	ICOM_THIS(IDirectMusicPerformanceImpl,iface);

	TRACE("(%p,%s,%p)\n",This,debugstr_guid(riid),ppobj);
	if (IsEqualGUID(riid, &IID_IUnknown) ||
	    IsEqualGUID(riid, &IID_IDirectMusicPerformance) ||
	    IsEqualGUID(riid, &IID_IDirectMusicPerformance8)) {
	    *ppobj = This;
	    This->ref++;
	    return S_OK;
	}
	FIXME("(%p): no interface for %s\n", This, debugstr_guid(riid));
	return E_NOINTERFACE;
}

static ULONG WINAPI IDirectMusicPerformanceImpl_AddRef(IDirectMusicPerformance8* iface) {
	ICOM_THIS(IDirectMusicPerformanceImpl,iface);
	return ++(This->ref);
}

static ULONG WINAPI IDirectMusicPerformanceImpl_Release(IDirectMusicPerformance8* iface) {
	ICOM_THIS(IDirectMusicPerformanceImpl,iface);
	TRACE("(%p), ref was %ld\n",This,This->ref);
	if (!--(This->ref)) {
		IDirectMusicPerformance8_CloseDown(iface);
		DeleteCriticalSection(&This->pPMsgTimeQueue.cs_queue);
		DeleteCriticalSection(&This->pPMsgImmediateQueue.cs_queue);
		DeleteCriticalSection(&This->cs_seg);
		DeleteCriticalSection(&This->cs_segstate);
		DeleteCriticalSection(&This->cs_tempo);
		HeapFree(GetProcessHeap(),0,This);
		return 0;
	}
	return This->ref;
}

static HRESULT WINAPI IDirectMusicPerformanceImpl_InitAudio(
	IDirectMusicPerformance8* iface,
	IDirectMusic** ppDirectMusic,
	IDirectSound** ppDirectSound,
	HWND hWnd,
	DWORD dwDefaultPathType,
	DWORD dwPChannelCount,
	DWORD dwFlags,
	DMUS_AUDIOPARAMS* pParams)
{
	ICOM_THIS(IDirectMusicPerformanceImpl,iface);
	HRESULT hres = S_OK;

	TRACE("(%p)->(%p,%p,0x%08x,%ld,%ld,0x%lx,%p)\n",
	      This,ppDirectMusic,ppDirectSound,hWnd,
	      dwDefaultPathType,dwPChannelCount,dwFlags,
	      pParams);

	if (!hWnd) hWnd = GetForegroundWindow();

	if (ppDirectMusic && *ppDirectMusic) {
		IDirectMusic_AddRef(*ppDirectMusic);
		This->pDirectMusic = *ppDirectMusic;
	} else {
		hres = CoCreateInstance(&CLSID_DirectMusic, NULL,
					CLSCTX_INPROC, &IID_IDirectMusic,
					(void**)&This->pDirectMusic);
		if (FAILED(hres)) return hres;
		if (ppDirectMusic) {
			IDirectMusic_AddRef(This->pDirectMusic);
			*ppDirectMusic = This->pDirectMusic;
		}
	}

	if (ppDirectSound && *ppDirectSound) {
		IDirectSound_AddRef(*ppDirectSound);
		This->pDirectSound = *ppDirectSound;
	} else {
		hres = CoCreateInstance(&CLSID_DirectSound8, NULL,
					CLSCTX_INPROC, &IID_IDirectSound,
					(void**)&This->pDirectSound);
		if (FAILED(hres)) return hres;
		if (This->pDirectSound) {
			hres = IDirectSound_Initialize(This->pDirectSound, NULL);
			if (FAILED(hres)) return hres;
			IDirectSound_SetCooperativeLevel(This->pDirectSound, hWnd, DSSCL_PRIORITY);
		}
		if (ppDirectSound) {
			if (This->pDirectSound) IDirectSound_AddRef(This->pDirectSound);
			*ppDirectSound = This->pDirectSound;
		}
	}

	This->params.dwSize = sizeof(This->params);
	This->params.dwVoices = 64;
	This->params.dwSampleRate = 22050;
	if (pParams) {
		This->params.dwValidData = pParams->dwValidData;
		This->params.fInitNow = pParams->fInitNow;
		if (pParams->dwValidData & DMUS_AUDIOPARAMS_FEATURES)
			This->params.dwFeatures = pParams->dwFeatures;
		if (pParams->dwValidData & DMUS_AUDIOPARAMS_VOICES)
			This->params.dwVoices = pParams->dwVoices;
		if (pParams->dwValidData & DMUS_AUDIOPARAMS_SAMPLERATE)
			This->params.dwSampleRate = pParams->dwSampleRate;
		if (pParams->dwValidData & DMUS_AUDIOPARAMS_DEFAULTSYNTH)
			This->params.clsidDefaultSynth = pParams->clsidDefaultSynth;
	} else {
		This->params.dwValidData = DMUS_AUDIOPARAMS_FEATURES;
		This->params.dwFeatures = dwFlags;
	}
	TRACE("init now: %d\n", This->params.fInitNow);
	TRACE("valid data: 0x%lx\n", This->params.dwValidData);
	TRACE("features: 0x%lx\n", This->params.dwFeatures);
	TRACE("voices: %ld\n", This->params.dwVoices);
	TRACE("samplerate: %ld\n", This->params.dwSampleRate);
	TRACE("default synth: %s\n", debugstr_guid(&This->params.clsidDefaultSynth));

	/* create default audiopath */
	if (dwDefaultPathType) {
		IDirectMusicAudioPath *path;
		hres = IDirectMusicPerformance8_CreateStandardAudioPath(iface,
			dwDefaultPathType, dwPChannelCount, TRUE, &path);
		if (FAILED(hres)) return hres;
		hres = IDirectMusicPerformance8_SetDefaultAudioPath(iface, path);
		if (FAILED(hres)) return hres;
	}

	/* start player thread */
	if (!This->hPlayer) {
	    /* Since there is a problem making a suspended thread, use critical
	       sections to block PerformancePlayback from running until
	       CreateThread is done. */
	    InitializeCriticalSection(&This->thread_blocker);
	    
	    EnterCriticalSection(&This->thread_blocker);
	    This->hPlayer = CreateThread(NULL, 0, PerformancePlayback, iface, 0, NULL);
	    LeaveCriticalSection(&This->thread_blocker);
	}

	TRACE("complete\n");

	return hres;
}

static HRESULT WINAPI IDirectMusicPerformanceImpl_CloseDown(
	IDirectMusicPerformance8* iface)
{
	ICOM_THIS(IDirectMusicPerformanceImpl,iface);
	TRACE("(%p)->()\n",This);

	if (This->hPlayer) {
		HANDLE hPlayer = This->hPlayer;
		/* close down player thread */
		This->hPlayer = 0;
		WaitForSingleObject(hPlayer, INFINITE);
		CloseHandle(hPlayer);
	}
	if (This->pDefaultAudioPath) {
		IDirectMusicAudioPath_Activate(This->pDefaultAudioPath, FALSE);
		IDirectMusicAudioPath_Release(This->pDefaultAudioPath);
		This->pDefaultAudioPath = NULL;
	}
	if (This->pDirectSound) {
		IDirectSound_Release(This->pDirectSound);
		This->pDirectSound = NULL;
	}
	if (This->pDirectMusic) {
		IDirectMusic_Release(This->pDirectMusic);
		This->pDirectMusic = NULL;
	}
	return S_OK;
}

static HRESULT WINAPI IDirectMusicPerformanceImpl_CreateStandardAudioPath(
	IDirectMusicPerformance8* iface,
	DWORD dwType, DWORD dwPChannelCount,
	BOOL fActivate, IDirectMusicAudioPath** ppNewPath)
{
	ICOM_THIS(IDirectMusicPerformanceImpl,iface);
	HRESULT hr;
	TRACE("(%p)->(%ld,%ld,%d,%p)\n", This,
	      dwType,dwPChannelCount,fActivate,ppNewPath);
	hr = DirectMusicCreateStandardAudioPath(iface, This->pDirectSound, &This->params,
						dwType, dwPChannelCount, ppNewPath);
	if (FAILED(hr)) return hr;
	return IDirectMusicAudioPath_Activate((*ppNewPath), fActivate);
}

static HRESULT WINAPI IDirectMusicPerformanceImpl_SetDefaultAudioPath(
	IDirectMusicPerformance8* iface,
	IDirectMusicAudioPath* pAudioPath)
{
	ICOM_THIS(IDirectMusicPerformanceImpl,iface);
	TRACE("(%p)->(%p)\n", This, pAudioPath);
	if (pAudioPath) IDirectMusicAudioPath_AddRef(pAudioPath);
	if (This->pDefaultAudioPath) IDirectMusicAudioPath_Release(This->pDefaultAudioPath);
	This->pDefaultAudioPath = pAudioPath;
	return S_OK;
}

static HRESULT WINAPI IDirectMusicPerformanceImpl_GetDefaultAudioPath(
	IDirectMusicPerformance8* iface,
	IDirectMusicAudioPath** ppAudioPath)
{
	ICOM_THIS(IDirectMusicPerformanceImpl,iface);
	TRACE("(%p)->(%p)\n", This, ppAudioPath);
	if (This->pDefaultAudioPath) IDirectMusicAudioPath_AddRef(This->pDefaultAudioPath);
	*ppAudioPath = This->pDefaultAudioPath;
	return S_OK;
}

static HRESULT WINAPI IDirectMusicPerformanceImpl_SetNotificationHandle(
	IDirectMusicPerformance8* iface,
	HANDLE hNotification,
	REFERENCE_TIME rtMinimum)
{
	ICOM_THIS(IDirectMusicPerformanceImpl,iface);
	TRACE("(%p)->(%d,%Ld)\n", This, hNotification, rtMinimum);
	This->hNotification = hNotification;
	if (rtMinimum) This->rtNotification = rtMinimum;
	return S_OK;
}

static HRESULT WINAPI IDirectMusicPerformanceImpl_AddNotificationType(
	IDirectMusicPerformance8* iface,
	REFGUID rguidNotificationType)
{
	ICOM_THIS(IDirectMusicPerformanceImpl,iface);
	FIXME("(%p)->(%s): stub\n", This, debugstr_guid(rguidNotificationType));
	return S_OK;
}

static HRESULT WINAPI IDirectMusicPerformanceImpl_RemoveNotificationType(
	IDirectMusicPerformance8* iface,
	REFGUID rguidNotificationType)
{
	ICOM_THIS(IDirectMusicPerformanceImpl,iface);
	FIXME("(%p)->(%s): stub\n", This, debugstr_guid(rguidNotificationType));
	return S_OK;
}

static HRESULT WINAPI IDirectMusicPerformanceImpl_SetGlobalParam(
	IDirectMusicPerformance8* iface,
	REFGUID rguidType,
	void* pParam,
	DWORD dwSize)
{
	ICOM_THIS(IDirectMusicPerformanceImpl,iface);

	if (IsEqualGUID (rguidType, &GUID_PerfMasterVolume))
	{
	    /* get the audio path */
	    IDirectMusicAudioPathImpl* ippDM=(IDirectMusicAudioPathImpl*)This->pDefaultAudioPath;
	 
	    if ((ippDM)->dsbuffers > 0)
	    {
		int     volumeValue = *(long*)pParam;
	    
		/* get the sound buffer */
		IDirectSoundBuffer8 *theBuffer = *((ippDM)->dsbuffer);
	    
		/* set the volume for this buffer */
		IDirectSoundBuffer_SetVolume(theBuffer, volumeValue);
	    }
	}
	else
	{
	    FIXME("(%p)->(%s,%p,%ld): stub\n", This, debugstr_guid(rguidType), pParam, dwSize);
	}
	
	return S_OK;
}

static HRESULT WINAPI IDirectMusicPerformanceImpl_PlaySegment(
	IDirectMusicPerformance8* iface,
	IDirectMusicSegment* pSegment,
	DWORD dwFlags,
	__int64 i64StartTime,
	IDirectMusicSegmentState** ppSegmentState)
{
	ICOM_THIS(IDirectMusicPerformanceImpl,iface);
	static const DWORD defaults = DMUS_SEGF_MEASURE|DMUS_SEGF_BEAT|DMUS_SEGF_GRID;
	IDirectMusicSegment8* pSeg = (IDirectMusicSegment8*)pSegment;
	IDirectMusicSegmentState8* pSegmentState;
	REFERENCE_TIME rtStart;
	SegmentChange** queue;
	SegmentChange* new_seg;
	DWORD c;
	HRESULT hr;

	TRACE("(%p)->(%p,0x%lx,%Ld,%p)\n", This,
	      pSegment, dwFlags, i64StartTime, ppSegmentState);

	if (dwFlags & DMUS_SEGF_DEFAULT) {
		DWORD dwDefault;
		hr = IDirectMusicSegment8_GetDefaultResolution(pSeg, &dwDefault);
		if (SUCCEEDED(hr)) {
			dwFlags &= ~defaults;
			dwFlags |= dwDefault & defaults;
		}
		/* FIXME: GetAudioPathConfig too? */
	}

	TRACE("flags=0x%08lx\n", dwFlags);

	/* find free slot in segstate array */
	EnterCriticalSection(&This->cs_segstate);
	for (c=0; c<This->segstates; c++) {
		if (!This->segstate[c]) break;
	}
	if (c == This->segstates) {
		DWORD n = c ? c*2 : 1;
		IDirectMusicSegmentState8** new_segstate;
		new_segstate = HeapReAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, This->segstate,
					   n*sizeof(IDirectMusicSegmentState8*));
		if (new_segstate) {
			This->segstate = new_segstate;
			This->segstates = n;
			This->segstate[c] = (IDirectMusicSegmentState8*)-1;
		} else {
			ERR("memory allocation failure\n");
		}
	}
	else This->segstate[c] = (IDirectMusicSegmentState8*)-1;
	LeaveCriticalSection(&This->cs_segstate);

	/* create segstate */
	hr = DirectMusicCreateSegmentState(iface, pSeg, dwFlags, c << 16, &pSegmentState);
	if (FAILED(hr)) {
		EnterCriticalSection(&This->cs_segstate);
		This->segstate[c] = NULL;
		LeaveCriticalSection(&This->cs_segstate);
		return hr;
	}
	EnterCriticalSection(&This->cs_segstate);
	IDirectMusicSegmentState8_AddRef(pSegmentState);
	This->segstate[c] = pSegmentState;
	LeaveCriticalSection(&This->cs_segstate);

	/* compute segment start time */
	if (dwFlags & DMUS_SEGF_QUEUE) {
		rtStart = 0;
	}
	else if (dwFlags & DMUS_SEGF_REFTIME) {
		rtStart = i64StartTime;
	}
	else if (i64StartTime) {
		hr = IDirectMusicPerformance8_MusicToReferenceTime(iface, i64StartTime, &rtStart);
		if (FAILED(hr)) {
			ERR("couldn't convert start time %Ld to reference time\n", i64StartTime);
			rtStart = 0;
		}
	}
	else {
		rtStart = 0;
	}

	if (rtStart || (dwFlags & DMUS_SEGF_SEGMENTEND)) {
		hr = IDirectMusicPerformance8_GetResolvedTime(iface, rtStart, &rtStart, dwFlags);
		if (FAILED(hr)) {
			ERR("couldn't resolve start time\n");
		}
	}

	/* add segment to playback queue */
	if ((dwFlags & DMUS_SEGF_QUEUE) &&
	    !(dwFlags & DMUS_SEGF_SECONDARY))
		queue = &This->primary_queue;
	else
		queue = &This->seg_change;
	new_seg = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(SegmentChange));
	new_seg->rtTime = rtStart;
	new_seg->dwFlags = dwFlags;
	IDirectMusicSegmentState8_AddRef(pSegmentState);
	new_seg->pSegState = pSegmentState;
	new_seg->stop = FALSE;
	EnterCriticalSection(&This->cs_seg);
	{
		SegmentChange* last = NULL;
		SegmentChange* cur = *queue;
		while (cur && new_seg->rtTime > cur->rtTime) {
			last = cur;
			cur = cur->next;
		}
		new_seg->next = cur;
		if (last) last->next = new_seg;
		else *queue = new_seg;
	}
	LeaveCriticalSection(&This->cs_seg);

	/* the rest is handled by the player thread, let's return */
	if (ppSegmentState) *ppSegmentState = (IDirectMusicSegmentState*)pSegmentState;
	else IDirectMusicSegmentState8_Release(pSegmentState);
	return hr;
}

static HRESULT WINAPI IDirectMusicPerformanceImpl_Stop(
	IDirectMusicPerformance8* iface,
	IDirectMusicSegment* pSegment,
	IDirectMusicSegmentState* pSegmentState,
	MUSIC_TIME mtTime,
	DWORD dwFlags)
{
	ICOM_THIS(IDirectMusicPerformanceImpl,iface);
	REFERENCE_TIME rtStop;
	HRESULT hr;

	TRACE("(%p)->(%p,%p,%ld,0x%lx)\n", This,
	      pSegment, pSegmentState, mtTime, dwFlags);

	/* compute stop time */
	if (mtTime) {
		hr = IDirectMusicPerformance8_MusicToReferenceTime(iface, mtTime, &rtStop);
		if (FAILED(hr)) {
			ERR("couldn't convert stop time %ld to reference time\n", mtTime);
			rtStop = 0;
		}
	}
	else {
		rtStop = 0;
	}

	if (rtStop) {
		hr = IDirectMusicPerformance8_GetResolvedTime(iface, rtStop, &rtStop, dwFlags);
		if (FAILED(hr)) {
			ERR("couldn't resolve start time\n");
		}
	}

	if (pSegmentState) {
		SegmentChange* new_seg;
		/* add segment to playback queue */
		new_seg = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(SegmentChange));
		new_seg->rtTime = rtStop;
		new_seg->dwFlags = dwFlags;
		IDirectMusicSegmentState8_AddRef(pSegmentState);
		new_seg->pSegState = (IDirectMusicSegmentState8*)pSegmentState;
		new_seg->stop = TRUE;
		EnterCriticalSection(&This->cs_seg);
		{
			SegmentChange* last = NULL;
			SegmentChange* cur = This->seg_change;
			while (cur && new_seg->rtTime > cur->rtTime) {
				last = cur;
				cur = cur->next;
			}
			new_seg->next = cur;
			if (last) last->next = new_seg;
			else This->seg_change = new_seg;
			/* remove any conflicting play requests from queue */
			last = NULL;
			cur = This->seg_change;
			while (cur) {
				SegmentChange* next = cur->next;
				if (cur->pSegState == new_seg->pSegState &&
				    !cur->stop && cur->rtTime >= new_seg->rtTime) {
					if (last) last->next = next;
					else This->seg_change = next;
					IDirectMusicSegmentState8_Release(pSegmentState);
					HeapFree(GetProcessHeap(), 0, cur);
				}
				else last = cur;
				cur = next;
			}
		}
		LeaveCriticalSection(&This->cs_seg);

	}
	if (pSegment) {
		FIXME("stop segment without state\n");
	}
	if (!(pSegment || pSegmentState)) {
		DMUS_PMSG* pMsg;
		IDirectMusicSegmentState* pState;
		hr = IDirectMusicPerformance8_AllocPMsg(iface, sizeof(DMUS_PMSG), &pMsg);
		if (FAILED(hr)) return hr;
		pMsg->rtTime = rtStop;
		pMsg->dwFlags = DMUS_PMSGF_REFTIME | DMUS_PMSGF_TOOL_QUEUE;
		pMsg->dwType = DMUS_PMSGT_STOP;
		hr = IDirectMusicPerformance8_SendPMsg(iface, pMsg);
		if (FAILED(hr)) {
			IDirectMusicPerformance8_FreePMsg(iface, pMsg);
			return hr;
		}
		/* HACK: call recursively to stop primary segment */
		pState = This->primary_segstate;
		if (pState) {
			IDirectMusicSegmentState_AddRef(pState);
			hr = IDirectMusicPerformance8_Stop(iface, NULL, pState, mtTime, dwFlags);
			IDirectMusicSegmentState_Release(pState);
		}
	}
	return S_OK;
}

static HRESULT WINAPI IDirectMusicPerformanceImpl_IsPlaying(
	IDirectMusicPerformance8* iface,
	IDirectMusicSegment* pSegment,
	IDirectMusicSegmentState* pSegmentState)
{
	ICOM_THIS(IDirectMusicPerformanceImpl,iface);
	FIXME("(%p)->(%p,%p): stub\n", This,
	      pSegment, pSegmentState);
	return S_FALSE;
}


static HRESULT WINAPI IDirectMusicPerformanceImpl_GetTime( 
        IDirectMusicPerformance8* iface,
        REFERENCE_TIME* prtNow, 
        MUSIC_TIME* pmtNow)
{
	ICOM_THIS(IDirectMusicPerformanceImpl,iface);
	REFERENCE_TIME now;
	TRACE("(%p)->(%p,%p)\n", This, prtNow, pmtNow);

	if (This->started) {
		/* we should really ask the IReferenceClock,
		 * which we could ask IDirectMusic8 for,
		 * but we won't bother quite yet */
		now = ((REFERENCE_TIME)GetTickCount()) * 10000;
		now -= This->rtStart;
	}
	else now = 0;

	if (prtNow) *prtNow = now;
	if (pmtNow)
		return IDirectMusicPerformance8_ReferenceToMusicTime(iface, now, pmtNow);
	return S_OK;
}

static HRESULT WINAPI IDirectMusicPerformanceImpl_MusicToReferenceTime(
        IDirectMusicPerformance8* iface,
        MUSIC_TIME mtTime,
        REFERENCE_TIME* prtTime)
{
	ICOM_THIS(IDirectMusicPerformanceImpl,iface);
	TempoChange* change;
	DMUS_TEMPO_PARAM param;
	MUSIC_TIME mtLast;
	REFERENCE_TIME rtLast;
	BOOL ask_track = FALSE;
	HRESULT hr;

	TRACE("(%p)->(%ld,%p)\n", This, mtTime, prtTime);

	param.mtTime = 0;
	param.dblTempo = 0.0;
	mtLast = 0;
	rtLast = 0;

	/* find time among recorded tempo changes */
	EnterCriticalSection(&This->cs_tempo);
	change = This->last_tempo;
	if (!change) ask_track = TRUE;
	else if (change->mtTime <= mtTime) ask_track = TRUE;
	else {
		while (change && change->mtTime > mtTime)
			change = change->prev;
	}
	if (change) {
		param.mtTime = 0;
		param.dblTempo = change->dblTempo;
		mtLast = change->mtTime;
		rtLast = change->rtTime;
	}
	LeaveCriticalSection(&This->cs_tempo);

	/* find time among future tempo changes from tempo track */
	if (ask_track && This->primary_seg) {
		MUSIC_TIME mtStart = 0;
		MUSIC_TIME mtPrev = mtLast;
		MUSIC_TIME mtNext = 0;
		IDirectMusicSegment* control = This->control_seg;
		if (!control) control = This->primary_seg;
		IDirectMusicSegmentState_GetStartTime(This->primary_segstate, &mtStart);
		mtNext = mtStart;

		while (TRUE) {
			hr = IDirectMusicSegment_GetParam(control, &GUID_TempoParam,
							  (DWORD)-1, DMUS_SEG_ANYTRACK,
							  mtLast - mtStart, &mtNext, &param);
			if (FAILED(hr) && (mtLast >= mtStart)) break;
			if ((mtNext >= mtTime) || !mtNext) break;
			rtLast += (REFERENCE_TIME)(mtNext - mtPrev) * 60 * 100000000 / (param.dblTempo * DMUS_PPQ);
			mtPrev = mtLast;
			mtLast = mtNext;
		}
	}

	TRACE("tempo is %.3f at mt=%ld rt=%Ld\n", param.dblTempo, mtLast, rtLast);
	if (param.dblTempo == 0.0) {
		/* time not found, this must be before anything was played */
		TRACE("no tempo found\n");
	} else {
		rtLast += (REFERENCE_TIME)(mtTime - mtLast) * 60 * 10000000 / (param.dblTempo * DMUS_PPQ);
	}

	TRACE("returning %Ld\n", rtLast);
	*prtTime = rtLast;

	return S_OK;
}

static HRESULT WINAPI IDirectMusicPerformanceImpl_ReferenceToMusicTime(
        IDirectMusicPerformance8* iface,
        REFERENCE_TIME rtTime,
        MUSIC_TIME* pmtTime)
{
	ICOM_THIS(IDirectMusicPerformanceImpl,iface);
	TempoChange* change;
	DMUS_TEMPO_PARAM param;
	MUSIC_TIME mtLast;
	REFERENCE_TIME rtLast;
	BOOL ask_track = FALSE;
	HRESULT hr;

	TRACE("(%p)->(%Ld,%p)\n", This, rtTime, pmtTime);

	param.mtTime = 0;
	param.dblTempo = 0.0;
	mtLast = 0;
	rtLast = 0;

	/* find time among recorded tempo changes */
	EnterCriticalSection(&This->cs_tempo);
	change = This->last_tempo;
	if (!change) ask_track = TRUE;
	else if (change->rtTime <= rtTime) ask_track = TRUE;
	else {
		while (change && change->rtTime > rtTime)
			change = change->prev;
	}
	if (change) {
		param.mtTime = 0;
		param.dblTempo = change->dblTempo;
		mtLast = change->mtTime;
		rtLast = change->rtTime;
	}
	LeaveCriticalSection(&This->cs_tempo);

	/* find time among future tempo changes from tempo track */
	if (ask_track && This->primary_seg) {
		MUSIC_TIME mtStart = 0;
		MUSIC_TIME mtPrev = mtLast;
		MUSIC_TIME mtNext = 0;
		IDirectMusicSegment* control = This->control_seg;
		if (!control) control = This->primary_seg;
		IDirectMusicSegmentState_GetStartTime(This->primary_segstate, &mtStart);
		mtNext = mtStart;

		while (TRUE) {
			REFERENCE_TIME rtNext;
			hr = IDirectMusicSegment_GetParam(control, &GUID_TempoParam,
							  (DWORD)-1, DMUS_SEG_ANYTRACK,
							  mtLast - mtStart, &mtNext, &param);
			if (FAILED(hr) && (mtLast >= mtStart)) break;
			if (!mtNext) break;
			rtNext = rtLast + (REFERENCE_TIME)(mtNext - mtPrev) * 60 * 100000000 / (param.dblTempo * DMUS_PPQ);
			if (rtLast >= rtTime) break;
			rtLast = rtNext;
			mtPrev = mtLast;
			mtLast = mtNext;
		}
	}

	TRACE("tempo is %.3f at mt=%ld rt=%Ld\n", param.dblTempo, mtLast, rtLast);
	if (param.dblTempo == 0.0) {
		/* time not found, this must be before anything was played */
		TRACE("no tempo found\n");
	} else {
		mtLast += (MUSIC_TIME)(((rtTime - rtLast) * (param.dblTempo * DMUS_PPQ) + 5000000) / (60 * 10000000));
	}

	TRACE("returning %ld\n", mtLast);
	*pmtTime = mtLast;

	return S_OK;
}

static HRESULT WINAPI IDirectMusicPerformanceImpl_GetResolvedTime(
	IDirectMusicPerformance8* iface,
	REFERENCE_TIME rtTime,
	REFERENCE_TIME* prtResolved,
	DWORD dwTimeResolveFlags)
{
	ICOM_THIS(IDirectMusicPerformanceImpl,iface);
	MUSIC_TIME mtTime;
	TRACE("(%p)->(%Ld,%p,0x%lx)\n", This,
	      rtTime, prtResolved, dwTimeResolveFlags);

	if (dwTimeResolveFlags & DMUS_TIME_RESOLVE_SEGMENTEND) {
		MUSIC_TIME mtStart, mtLen;
		if (This->primary_seg) {
			IDirectMusicSegmentState_GetStartTime(This->primary_segstate, &mtStart);
			IDirectMusicSegment_GetLength(This->primary_seg, &mtLen);
			mtTime = mtStart + mtLen;
		}
		else {
			IDirectMusicPerformance8_GetTime(iface, NULL, &mtTime);
		}
		dwTimeResolveFlags &= ~DMUS_TIME_RESOLVE_SEGMENTEND;
	}
	else {
		IDirectMusicPerformance8_ReferenceToMusicTime(iface, rtTime, &mtTime);
	}

	if (dwTimeResolveFlags) {
		FIXME("unhandled flags: 0x%lx\n", dwTimeResolveFlags);
	}

	return IDirectMusicPerformance8_MusicToReferenceTime(iface, mtTime, prtResolved);
}
     
static void DMPerf_LockQueue(DMPerf_PMsgQueue *queue)
{
	EnterCriticalSection(&queue->cs_queue);
}

static void DMPerf_UnlockQueue(DMPerf_PMsgQueue *queue)
{
	LeaveCriticalSection(&queue->cs_queue);
}

/* ok regarding messages.. here is some documentation on the internal API
 * Allocated messages have a bit of space at the front of them allocated for our
 *   private usage (ie, to keep them in the queue and stuff).
 *   I'm not sure if this is the best way to go, but we need some private info, and
 *   since they let us alocate it and free it ourselves - why not?
 * Messages are placed into the message queue by the COM API SendPMsg.
 *   Messages are then placed into one of two message queues. Immediate (unsorted)
 *   or Time (sorted, lowest rtTime at head. (FIXME james is working on music->rt time).
 * Messages can then be retrived internally by the player thread to be sent to the
 *   tool chain by DMPerf_GetRestrictedPMsg. You can add restrictions on the
 *   type of message to search for, if you like. It will grab any immediate messages
 *   first. Then grab any ones who's time has elapsed in the time queue (FIXME)
 *   (or who's time is about to elapse in the time queue if its delivery method
 *   is of type QUEUE - [FIXME]).
 *    Messages retrived from this will be removed from the queue and can be freed
 *    with either FreePMsg (API) or DMPerf_RealFreePMsg (internal, also removes from
 *    queue if its still in there).
 *    If you want to put it back in the queue (ie, after the tool is done with it
 *    and says to go back to the queue.. you can just re-Send it.. or pass it onto
 *    the next tool.. whatever is supposed to happen (I think if they modify the
 *    timestamp of it we need to put it back in the queue...).
 *    You don't need to lock the queue when sending a message to a tool, since its removed
 *    from the queue
 *  However, if you want top lock the queue, use: DMPerf_LockQueue and DMPerf_UnlockQueue.
 */

static HRESULT WINAPI IDirectMusicPerformanceImpl_SendPMsg(
	IDirectMusicPerformance8* iface,
	DMUS_PMSG *pPMSG)
{
	ICOM_THIS(IDirectMusicPerformanceImpl,iface);
	DMPerf_PMsgQueueItem *pPMSGQueueItem = DM_PMSGQUEUEITEM_FROM_PMSG(pPMSG);
	DMPerf_PMsgQueue *workingQueue;
	DMPerf_PMsgQueueItem *cur;
	DMPerf_PMsgQueueItem *prev = NULL;

	TRACE("(%p)->(%p)\n", This, pPMSG);
	if (!pPMSG) return E_POINTER;
	if (pPMSGQueueItem->bInQueue) return DMUS_E_ALREADY_SENT;
	/* if (REFTIME thing isn't created FIXME) return DMUS_E_NO_MASTER_CLOCK; */
	if (0 == (pPMSG->dwFlags & (DMUS_PMSGF_MUSICTIME | DMUS_PMSGF_REFTIME)))
		return E_INVALIDARG;
	if ((pPMSG->dwFlags & DMUS_PMSGF_MUSICTIME) &&
		(pPMSG->dwFlags & DMUS_PMSGF_REFTIME))
		return E_INVALIDARG;
	if (0 == (pPMSG->dwFlags & (DMUS_PMSGF_TOOL_QUEUE |
				    DMUS_PMSGF_TOOL_ATTIME |
				    DMUS_PMSGF_TOOL_IMMEDIATE)))
		pPMSG->dwFlags |= DMUS_PMSGF_TOOL_IMMEDIATE;
	if ((pPMSG->dwFlags & DMUS_PMSGF_REFTIME) && (pPMSG->rtTime == 0))
	{
		pPMSG->dwFlags &= ~(DMUS_PMSGF_TOOL_QUEUE |
				    DMUS_PMSGF_TOOL_ATTIME |
				    DMUS_PMSGF_TOOL_IMMEDIATE);
		pPMSG->dwFlags |= DMUS_PMSGF_TOOL_IMMEDIATE;
	}

	if (pPMSG->dwFlags & DMUS_PMSGF_MUSICTIME)
	{
		HRESULT hr = IDirectMusicPerformanceImpl_MusicToReferenceTime(iface,
							pPMSG->mtTime, &pPMSGQueueItem->rtTime);
		if (FAILED(hr)) return hr;
	}
	else
	{
		pPMSGQueueItem->rtTime = pPMSG->rtTime;
	}

	workingQueue = (pPMSG->dwFlags & DMUS_PMSGF_TOOL_IMMEDIATE) ?
		&This->pPMsgImmediateQueue : &This->pPMsgTimeQueue;

	DMPerf_LockQueue(workingQueue);
	
	cur = workingQueue->pHead;
	while (cur && cur->rtTime < pPMSGQueueItem->rtTime)
	{
		prev = cur;
		cur = cur->pNext;
	}
	if (prev)
		prev->pNext = pPMSGQueueItem;
	if (cur)
		cur->pPrev = pPMSGQueueItem;
	pPMSGQueueItem->pPrev = prev;
	pPMSGQueueItem->pNext = cur;
	if (workingQueue->pHead == cur)
		workingQueue->pHead = pPMSGQueueItem;
	pPMSGQueueItem->bInQueue = TRUE;
	pPMSGQueueItem->pQueue = workingQueue;
	DMPerf_UnlockQueue(workingQueue);
	return S_OK;
}

static HRESULT WINAPI IDirectMusicPerformanceImpl_AllocPMsg(
	IDirectMusicPerformance8* iface,
	ULONG cb,
	DMUS_PMSG** ppPMSG)
{
	ICOM_THIS(IDirectMusicPerformanceImpl,iface);
	DMPerf_PMsgQueueItem *pPMSGQueueItem;

	TRACE("(%p)->(%lu, %p)\n", This, cb, ppPMSG);
	if (cb < sizeof(DMUS_PMSG)) return E_INVALIDARG;
	if (!ppPMSG) return E_POINTER;
	pPMSGQueueItem = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, cb +
			                   (sizeof(DMPerf_PMsgQueueItem) - sizeof(DMUS_PMSG)));
	if (!pPMSGQueueItem) return E_OUTOFMEMORY;
	*ppPMSG = DM_PMSG_FROM_PMSGQUEUEITEM(pPMSGQueueItem);
	(*ppPMSG)->dwSize = cb;
	return S_OK;
}

/* internal */
static HRESULT DMPerf_RealFreePMsg(
	IDirectMusicPerformance8* iface,
	DMUS_PMSG* pPMSG)
{
	ICOM_THIS(IDirectMusicPerformanceImpl,iface);
	DMPerf_PMsgQueueItem *pPMSGQueueItem = DM_PMSGQUEUEITEM_FROM_PMSG(pPMSG);

	TRACE("(%p)->(%p)\n", This, pPMSG);

	if (pPMSGQueueItem->bInQueue)
	{
		DMPerf_PMsgQueue *queue = pPMSGQueueItem->pQueue;
		DMPerf_LockQueue(queue);

		if (pPMSGQueueItem->pPrev) pPMSGQueueItem->pPrev->pNext = pPMSGQueueItem->pNext;
		if (pPMSGQueueItem->pNext) pPMSGQueueItem->pNext->pPrev = pPMSGQueueItem->pPrev;
		if (queue->pHead == pPMSGQueueItem) queue->pHead = pPMSGQueueItem->pNext;

		DMPerf_UnlockQueue(queue);
	}

	if (pPMSGQueueItem->pmsg.punkUser)
		IUnknown_Release(pPMSGQueueItem->pmsg.punkUser);

	HeapFree(GetProcessHeap(), 0, pPMSGQueueItem);
	return S_OK;
}

static HRESULT WINAPI IDirectMusicPerformanceImpl_FreePMsg(
	IDirectMusicPerformance8* iface,
	DMUS_PMSG* pPMSG)
{
	ICOM_THIS(IDirectMusicPerformanceImpl,iface);
	DMPerf_PMsgQueueItem *pPMSGQueueItem = DM_PMSGQUEUEITEM_FROM_PMSG(pPMSG);

	TRACE("(%p)->(%p)\n", This, pPMSG);
	if (!pPMSG) return E_POINTER;
	if (pPMSGQueueItem->bInQueue) return DMUS_E_CANNOT_FREE;
	return DMPerf_RealFreePMsg(iface, pPMSG);
}

/*FIXME - code duplication with this and below method.
 * they do subtably different things. should be possible to combine.
 * but later.
 * so if you change this... change the below function!
 * set *sendNotification to TRUE if you want this function to signal
 * the apps event thing. in which case it will change sendNotification to
 * false, so if you continuely call this, use the same sendNotification, and
 * it will only send the notification on the first message..
 */
/* retrives the first message (that isn't a notifications) and removes it from its queue */
#define IS_MESSAGE_TIME_READY(msg) ((cur->rtTime - (dwWithinTimeMS * 10000) - \
			((cur->pmsg.dwFlags & DMUS_PMSGF_TOOL_QUEUE) ? (This->dwBumperTime + This->dwLatencyTime) : 0)) \
			< curTime)
static HRESULT DMPerf_GetNextPMsg(
	IDirectMusicPerformance8* iface,
	DMUS_PMSG** ppPMsg,
	DWORD dwWithinTimeMS, BOOL *sendNotification)
{
	ICOM_THIS(IDirectMusicPerformanceImpl,iface);
	DMPerf_PMsgQueueItem *cur;
	REFERENCE_TIME curTime;

	if (!ppPMsg) return E_POINTER;

	/* first look at the immediate queue */
	DMPerf_LockQueue(&This->pPMsgImmediateQueue);
	cur = This->pPMsgImmediateQueue.pHead;
	while (cur)
	{
		if (cur->pmsg.dwType == DMUS_PMSGT_NOTIFICATION)
		{
			if (*sendNotification) SetEvent(This->hNotification);
			*sendNotification = FALSE;
		}
		else
		{
			if (cur->pPrev) cur->pPrev->pNext = cur->pNext;
			if (cur->pNext) cur->pNext->pPrev = cur->pPrev;
			if (This->pPMsgImmediateQueue.pHead == cur)
				This->pPMsgImmediateQueue.pHead = cur->pNext;
			cur->bInQueue = FALSE;
			cur->pQueue = NULL;
			*ppPMsg = DM_PMSG_FROM_PMSGQUEUEITEM(cur);
			DMPerf_UnlockQueue(&This->pPMsgImmediateQueue);
			return S_OK;
		}
		cur = cur->pNext;
	}
	DMPerf_UnlockQueue(&This->pPMsgImmediateQueue);

	/* now look at the timed queue... */
	DMPerf_LockQueue(&This->pPMsgTimeQueue);
	cur = This->pPMsgTimeQueue.pHead;
	IDirectMusicPerformanceImpl_GetTime(iface, &curTime, NULL);

	while (cur && IS_MESSAGE_TIME_READY(cur))
	{
		if (cur->pmsg.dwType == DMUS_PMSGT_NOTIFICATION)
		{
			if (*sendNotification) SetEvent(This->hNotification);
			*sendNotification = FALSE;
		}
		else
		{
			if (cur->pPrev) cur->pPrev->pNext = cur->pNext;
			if (cur->pNext) cur->pNext->pPrev = cur->pPrev;
			if (This->pPMsgTimeQueue.pHead == cur)
				This->pPMsgTimeQueue.pHead = cur->pNext;
			cur->bInQueue = FALSE;
			cur->pQueue = NULL;
			*ppPMsg = DM_PMSG_FROM_PMSGQUEUEITEM(cur);
			DMPerf_UnlockQueue(&This->pPMsgTimeQueue);
			return S_OK;
		}
		cur = cur->pNext;
	}
	DMPerf_UnlockQueue(&This->pPMsgTimeQueue);

	return S_FALSE;
}

static HRESULT WINAPI IDirectMusicPerformanceImpl_GetNotificationPMsg(
	IDirectMusicPerformance8* iface,
	DMUS_NOTIFICATION_PMSG** ppNotificationPMsg)
{
	ICOM_THIS(IDirectMusicPerformanceImpl,iface);
	DMPerf_PMsgQueueItem *cur;
	REFERENCE_TIME curTime;
	DWORD dwWithinTimeMS = 0;

	TRACE("(%p)->(%p)\n", This, ppNotificationPMsg);
	if (!ppNotificationPMsg) return E_POINTER;

	/* first look at the immediate queue */
	DMPerf_LockQueue(&This->pPMsgImmediateQueue);
	cur = This->pPMsgImmediateQueue.pHead;
	while (cur)
	{
		if (cur->pmsg.dwType == DMUS_PMSGT_NOTIFICATION)
		{
			if (cur->pPrev) cur->pPrev->pNext = cur->pNext;
			if (cur->pNext) cur->pNext->pPrev = cur->pPrev;
			if (This->pPMsgImmediateQueue.pHead == cur)
				This->pPMsgImmediateQueue.pHead = cur->pNext;
			cur->bInQueue = FALSE;
			cur->pQueue = NULL;
			*ppNotificationPMsg = (DMUS_NOTIFICATION_PMSG *)DM_PMSG_FROM_PMSGQUEUEITEM(cur);
			DMPerf_UnlockQueue(&This->pPMsgImmediateQueue);
			TRACE("guid=%s, option=%ld\n",
			      debugstr_guid(&(*ppNotificationPMsg)->guidNotificationType),
			      (*ppNotificationPMsg)->dwNotificationOption);
			return S_OK;
		}
		cur = cur->pNext;
	}
	DMPerf_UnlockQueue(&This->pPMsgImmediateQueue);

	/* now look at the timed queue... */
	DMPerf_LockQueue(&This->pPMsgTimeQueue);
	cur = This->pPMsgTimeQueue.pHead;
	IDirectMusicPerformanceImpl_GetTime(iface, &curTime, NULL);

	while (cur && IS_MESSAGE_TIME_READY(cur))
	{
		if (cur->pmsg.dwType == DMUS_PMSGT_NOTIFICATION)
		{
			if (cur->pPrev) cur->pPrev->pNext = cur->pNext;
			if (cur->pNext) cur->pNext->pPrev = cur->pPrev;
			if (This->pPMsgTimeQueue.pHead == cur)
				This->pPMsgTimeQueue.pHead = cur->pNext;
			cur->bInQueue = FALSE;
			cur->pQueue = NULL;
			*ppNotificationPMsg = (DMUS_NOTIFICATION_PMSG *)DM_PMSG_FROM_PMSGQUEUEITEM(cur);
			DMPerf_UnlockQueue(&This->pPMsgTimeQueue);
			TRACE("guid=%s, option=%ld\n",
			      debugstr_guid(&(*ppNotificationPMsg)->guidNotificationType),
			      (*ppNotificationPMsg)->dwNotificationOption);
			return S_OK;
		}
		cur = cur->pNext;
	}
	DMPerf_UnlockQueue(&This->pPMsgTimeQueue);

	TRACE("no messages\n");
	return S_FALSE;
}
#undef IS_MESSAGE_TIME_READY

static ICOM_VTABLE(IDirectMusicPerformance8) dmpvt = 
{
	ICOM_MSVTABLE_COMPAT_DummyRTTIVALUE
	IDirectMusicPerformanceImpl_QueryInterface,
	IDirectMusicPerformanceImpl_AddRef,
	IDirectMusicPerformanceImpl_Release,
	(void*)0xdeaf3005, /* Init */
	IDirectMusicPerformanceImpl_PlaySegment,
	IDirectMusicPerformanceImpl_Stop,
	(void*)0xdeaf3008, /* GetSegmentState */
	(void*)0xdeaf3009, /* SetPrepareTime */
	(void*)0xdeaf300a, /* GetPrepareTime */
	(void*)0xdeaf300b, /* SetBumperLength */
	(void*)0xdeaf300c, /* GetBumperLength */
	IDirectMusicPerformanceImpl_SendPMsg,
	IDirectMusicPerformanceImpl_MusicToReferenceTime, 
	IDirectMusicPerformanceImpl_ReferenceToMusicTime,
	IDirectMusicPerformanceImpl_IsPlaying,
	IDirectMusicPerformanceImpl_GetTime,
	IDirectMusicPerformanceImpl_AllocPMsg,
	IDirectMusicPerformanceImpl_FreePMsg,
	(void*)0xdeaf3014, /* GetGraph */
	(void*)0xdeaf3015, /* SetGraph */
	IDirectMusicPerformanceImpl_SetNotificationHandle,
	IDirectMusicPerformanceImpl_GetNotificationPMsg,
	IDirectMusicPerformanceImpl_AddNotificationType,
	IDirectMusicPerformanceImpl_RemoveNotificationType,
	(void*)0xdeaf301a, /* AddPort */
	(void*)0xdeaf301b, /* RemovePort */
	(void*)0xdeaf301c, /* AssignPChannelBlock */
	(void*)0xdeaf301d, /* AssignPChannel */
	(void*)0xdeaf301e, /* PChannelInfo */
	(void*)0xdeaf301f, /* DownloadInstrument */
	(void*)0xdeaf3020, /* Invalidate */
	(void*)0xdeaf3021, /* GetParam */
	(void*)0xdeaf3022, /* SetParam */
	(void*)0xdeaf3023, /* GetGlobalParam */
	IDirectMusicPerformanceImpl_SetGlobalParam,
	(void*)0xdeaf3025, /* GetLatencyTime */
	(void*)0xdeaf3026, /* GetQueueTime */
	(void*)0xdeaf3027, /* AdjustTime */
	IDirectMusicPerformanceImpl_CloseDown,
	IDirectMusicPerformanceImpl_GetResolvedTime,
	(void*)0xdeaf302a, /* MIDIToMusic */
	(void*)0xdeaf302b, /* MusicToMIDI */
	(void*)0xdeaf302c, /* TimeToRhythm */
	(void*)0xdeaf302d, /* RhythmToTime */
	IDirectMusicPerformanceImpl_InitAudio,
	(void*)0xdeaf302f, /* PlaySegmentEx */
	(void*)0xdeaf3030, /* StopEx */
	(void*)0xdeaf3031, /* ClonePMsg */
	(void*)0xdeaf3032, /* CreateAudioPath */
	IDirectMusicPerformanceImpl_CreateStandardAudioPath,
	IDirectMusicPerformanceImpl_SetDefaultAudioPath,
	IDirectMusicPerformanceImpl_GetDefaultAudioPath,
	(void*)0xdeaf3036  /* GetParamEx */
};


/* poll for stuff to do every 100ms */
#define POLL_INTERVAL 100

/***********************************************************************
 *		Player thread
 */

static void ProcessSegmentChange(IDirectMusicPerformanceImpl* This, SegmentChange* segchange, REFERENCE_TIME rtNow)
{
	REFERENCE_TIME rtTime = segchange->rtTime;
	if (!rtTime) rtTime = rtNow;
	if (segchange->stop) {
		DWORD c;
		TRACE("stopping segment, state=%p\n", segchange->pSegState);
		/* maybe check if we need to invalidate etc */
		DirectMusicMarkSegmentStatePlaying(segchange->pSegState, FALSE,
						   rtTime, segchange->dwFlags);

		/* remove from segstate array (perhaps this is not the best place) */
		EnterCriticalSection(&This->cs_segstate);
		for (c=0; c<This->segstates; c++) {
			if (This->segstate[c] == segchange->pSegState) {
				IDirectMusicSegmentState8_Release(This->segstate[c]);
				This->segstate[c] = NULL;
			}
		}
		LeaveCriticalSection(&This->cs_segstate);
	} else {
		TRACE("starting segment, state=%p\n", segchange->pSegState);
		if (!(segchange->dwFlags & DMUS_SEGF_SECONDARY)) {
			IDirectMusicSegment* pSeg = NULL;
			/* new primary segment */
			IDirectMusicSegmentState8_GetSegment(segchange->pSegState, &pSeg);
			if (!pSeg) return;
			if (This->primary_segstate) {
				DirectMusicMarkSegmentStatePlaying((IDirectMusicSegmentState8*)This->primary_segstate,
								   FALSE, rtTime, segchange->dwFlags);
				IDirectMusicSegmentState_Release(This->primary_segstate);
				/* FIXME: remove from segstate array? */
			}
			if (This->primary_seg) IDirectMusicSegment_Release(This->primary_seg);
			This->primary_seg = pSeg; /* already AddRef-ed in GetSegment */
			IDirectMusicSegmentState8_AddRef(segchange->pSegState);
			This->primary_segstate = (IDirectMusicSegmentState*)segchange->pSegState;
			/* make sure clock is turned on */
			if (!This->started) {
				This->rtStart = 0;
				This->started = TRUE;
				IDirectMusicPerformance8_GetTime((IDirectMusicPerformance8*)This, &This->rtStart, NULL);
				TRACE("clock started at %Ld\n", This->rtStart);
			}
		}
		else {
			FIXME("start secondary segment\n");
		}
		DirectMusicMarkSegmentStatePlaying(segchange->pSegState, TRUE,
						   rtTime, segchange->dwFlags);
	}
}

static DWORD CALLBACK PerformancePlayback(LPVOID arg)
{
	IDirectMusicPerformance8* iface = (IDirectMusicPerformance8 *)arg;
	ICOM_THIS(IDirectMusicPerformanceImpl,iface);
	DWORD ltime = GetTickCount();
	DWORD ctime = ltime;
	SegmentChange* segchange;
	REFERENCE_TIME rtNow = 0;
	REFERENCE_TIME rtLast = 0;
	REFERENCE_TIME rtUntil = 0;
	MUSIC_TIME mtUntil = 0;
	DWORD extra, c;
	HRESULT hr;
	BOOL sendNotification = TRUE;
	DMUS_PMSG *pPMsg = NULL;

	/* Block this function until CreateThread is done executing */
	EnterCriticalSection(&This->thread_blocker);
	LeaveCriticalSection(&This->thread_blocker);
	
	DeleteCriticalSection(&This->thread_blocker);

	while (TRUE) {
		while (ltime < ctime) ltime += POLL_INTERVAL;
		/* check for closedown request */
		if (!This->hPlayer) return 0;

		/* get current time */
		IDirectMusicPerformance8_GetTime(iface, &rtNow, NULL);
		rtLast = rtUntil;
		rtUntil = rtNow + POLL_INTERVAL * 10000;
		TRACE("now=%Ld\n", rtNow);

		/* check for segment starts/stops */
		extra = This->dwLatencyTime + This->dwBumperTime;
		EnterCriticalSection(&This->cs_seg);
		segchange = This->seg_change;
		while (segchange && ((segchange->rtTime < (rtUntil + extra)) || !This->started)) {
			SegmentChange* next = segchange->next;
			This->seg_change = next;
			ProcessSegmentChange(This, segchange, rtNow);
			IDirectMusicSegmentState8_Release(segchange->pSegState);
			HeapFree(GetProcessHeap(), 0, segchange);
			segchange = next;
		}
		segchange = This->primary_queue;
		if (!This->primary_seg && segchange) {
			SegmentChange* next = segchange->next;
			This->seg_change = next;
			ProcessSegmentChange(This, segchange, rtNow);
			IDirectMusicSegmentState8_Release(segchange->pSegState);
			HeapFree(GetProcessHeap(), 0, segchange);
			segchange = next;
		}
		LeaveCriticalSection(&This->cs_seg);

		/* calculate music time with prepare time added */
		IDirectMusicPerformance8_ReferenceToMusicTime(iface, rtUntil + This->dwPrepareTime * 10000, &mtUntil);

		/* run through all playing segments */
		c = 0;
		EnterCriticalSection(&This->cs_segstate);
		while (c < This->segstates) {
			IDirectMusicSegmentState8* segstate;
			while (c < This->segstates) {
				segstate = This->segstate[c];
				if (segstate != NULL &&
				    segstate != (IDirectMusicSegmentState8*)-1)
					break;
				c++;
			}
			if (c >= This->segstates) break;
			IDirectMusicSegmentState8_AddRef(segstate);
			LeaveCriticalSection(&This->cs_segstate);
			DirectMusicPlaySegmentState(segstate, mtUntil);
			IDirectMusicSegmentState8_Release(segstate);
			EnterCriticalSection(&This->cs_segstate);
			c++;
		}
		LeaveCriticalSection(&This->cs_segstate);

		/* check for queued messages */
		sendNotification = TRUE; /* send notification to the app */
		while ((hr = DMPerf_GetNextPMsg(iface, &pPMsg,
						POLL_INTERVAL, &sendNotification)) == S_OK)
		{
			DMPerf_PMsgQueueItem *pQueueItem = DM_PMSGQUEUEITEM_FROM_PMSG(pPMsg);
			sendNotification = TRUE;
			     /* always send, they may have processed the messages in this time */
			switch (pPMsg->dwType)
			{
			case DMUS_PMSGT_STOP:
				DirectMusicStopAllInAudioPath(This->pDefaultAudioPath);
				break;
			case DMUS_PMSGT_TEMPO:
				{
					DMUS_TEMPO_PMSG* pTempoMsg = (DMUS_TEMPO_PMSG *)pPMsg;
					TempoChange *cur, *previous = NULL;

					TempoChange *newTempo = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY,
							sizeof(TempoChange));
					newTempo->rtTime = pQueueItem->rtTime;
					IDirectMusicPerformance8_ReferenceToMusicTime(iface,
							newTempo->rtTime, &newTempo->mtTime);
					newTempo->dblTempo = pTempoMsg->dblTempo;
					TRACE("inserting tempo event: rt=%Ld, mt=%ld, tempo=%.3f\n",
					      newTempo->rtTime, newTempo->mtTime, newTempo->dblTempo);
					EnterCriticalSection(&This->cs_tempo);
					cur = This->last_tempo;
					while (cur && cur->rtTime > newTempo->rtTime)
					{
						previous = cur;
						cur = cur->prev;
					}
					if (cur) cur->next = newTempo;
					if (previous) previous->prev = newTempo;
					newTempo->prev = cur;
					newTempo->next = previous;
					if (cur == This->last_tempo) This->last_tempo = newTempo;
					LeaveCriticalSection(&This->cs_tempo);
				}
				break;
			case DMUS_PMSGT_TIMESIG:
				FIXME("don't support TIMESIG messages yet.\n");
				break;
			case DMUS_PMSGT_WAVE:
				DirectMusicPlayInAudioPath(This->pDefaultAudioPath, pPMsg,
							   This->dwLatencyTime);
				break;
			default:
				FIXME("unhandled message type: %08lx\n", pPMsg->dwType);
				break;
			}
			IDirectMusicPerformance8_FreePMsg(iface, pPMsg);
		}

		/* run audio path */
		DirectMusicRunAudioPath(This->pDefaultAudioPath, This->dwLatencyTime);

		/* wait remainder of poll interval */
		ctime = GetTickCount();
		if (ctime < (ltime + POLL_INTERVAL))
			usleep(((ltime + POLL_INTERVAL) - ctime) * 1000);
	}
	return 0;
}

/***********************************************************************
 *		DirectMusicPerformanceCreate
 */
HRESULT DirectMusicPerformanceCreate(REFGUID lpGUID, IUnknown **ppObj, IUnknown *pUnkOuter)
{
	IDirectMusicPerformanceImpl** ippDM=(IDirectMusicPerformanceImpl**)ppObj;

	if (lpGUID)
		TRACE("(%p,%p,%p)\n",lpGUID,ippDM,pUnkOuter);
	else
		TRACE("DirectMusicPerformanceCreate (%p)\n", ippDM);

	if (ippDM == NULL)
		return E_INVALIDARG;

	/* Allocate memory */
	*ippDM = (IDirectMusicPerformanceImpl*)HeapAlloc(GetProcessHeap(),HEAP_ZERO_MEMORY,sizeof(IDirectMusicPerformanceImpl));
	if (*ippDM == NULL)
		return E_OUTOFMEMORY;

	ICOM_VTBL(*ippDM)	= &dmpvt;
	(*ippDM)->ref		= 1;
	(*ippDM)->rtNotification = 20000000; /* 2s */
	(*ippDM)->dwPrepareTime	= 1000; /* 1s */
	(*ippDM)->dwLatencyTime	= 400; /* 400ms */
	(*ippDM)->dwBumperTime	= 50; /* 50ms */

	InitializeCriticalSection(&(*ippDM)->pPMsgTimeQueue.cs_queue);
	InitializeCriticalSection(&(*ippDM)->pPMsgImmediateQueue.cs_queue);

	InitializeCriticalSection(&(*ippDM)->cs_tempo);
	InitializeCriticalSection(&(*ippDM)->cs_segstate);
	InitializeCriticalSection(&(*ippDM)->cs_seg);

	return DS_OK;
}
