/*		DirectSound Wave
 *
 * Copyright 2002-2003 TransGaming Technologies Inc. 
 *
 */

#include "config.h"
#include <assert.h>
#include <string.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 "mmreg.h"
#include "msacm.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"

WINE_DEFAULT_DEBUG_CHANNEL(dmusic);

/*****************************************************************************
 * IDirectSoundWave implementation structure
 */
typedef struct IDirectSoundWaveImpl IDirectSoundWaveImpl;
struct IDirectSoundWaveImpl
{
    /* IUnknown fields */
    ICOM_VFIELD(IUnknown);
    ICOM_VTABLE(IDirectMusicObject) *lpOVtbl;
    ICOM_VTABLE(IPersistStream) *lpPSVtbl;
    DWORD ref;
    /* IDirectSoundWave fields */
    DMUS_OBJECTDESC desc;
    DMUS_IO_WAVE_HEADER whdr;
    WAVEFORMATEX *wfx;
    DWORD datalen;
    LPBYTE *data;
};

static HRESULT WINAPI IDirectSoundWaveImpl_ParseStream(
	IDirectSoundWaveImpl* This,
	IStream* pStream,
	LPDMUS_OBJECTDESC pDesc);
static HRESULT DirectSoundWaveStreamCreate(IUnknown* pWave, void** ppStream);

static HRESULT WINAPI IDirectSoundWaveImpl_QueryInterface(
	IUnknown* iface,REFIID riid,LPVOID *ppobj
) {
	ICOM_THIS(IDirectSoundWaveImpl,iface);

	TRACE("(%p,%s,%p)\n",This,debugstr_guid(riid),ppobj);
	if (IsEqualGUID(riid, &IID_IUnknown)) {
	    *ppobj = (LPVOID)This;
	    This->ref++;
	    return S_OK;
	}
	if (IsEqualGUID(riid, &IID_IDirectMusicObject)) {
	    *ppobj = (LPVOID)&This->lpOVtbl;
	    This->ref++;
	    return S_OK;
	}
	if (IsEqualGUID(riid, &IID_IPersist) ||
	    IsEqualGUID(riid, &IID_IPersistStream)) {
	    *ppobj = (LPVOID)&This->lpPSVtbl;
	    This->ref++;
	    return S_OK;
	}
	if (IsEqualGUID(riid, &CLSID_DirectSoundWave)) {
	    /* grab custom stream interface (this is probably not what MS does,
	     * but they work in undocumented ways, so shame on them) */
	    return DirectSoundWaveStreamCreate(iface, ppobj);
	}
	FIXME("(%p): no interface for %s\n", This, debugstr_guid(riid));
	return E_NOINTERFACE;
}

static ULONG WINAPI IDirectSoundWaveImpl_AddRef(IUnknown* iface) {
	ICOM_THIS(IDirectSoundWaveImpl,iface);
	return ++(This->ref);
}

static ULONG WINAPI IDirectSoundWaveImpl_Release(IUnknown* iface) {
	ICOM_THIS(IDirectSoundWaveImpl,iface);
	TRACE("(%p), ref was %ld\n",This,This->ref);
	if (!--(This->ref)) {
		if (This->data) HeapFree(GetProcessHeap(),0,This->data);
		if (This->wfx) HeapFree(GetProcessHeap(),0,This->wfx);
		HeapFree(GetProcessHeap(),0,This);
		return 0;
	}
	return This->ref;
}

static ICOM_VTABLE(IUnknown) dswvt =
{
	ICOM_MSVTABLE_COMPAT_DummyRTTIVALUE
	IDirectSoundWaveImpl_QueryInterface,
	IDirectSoundWaveImpl_AddRef,
	IDirectSoundWaveImpl_Release
};

static HRESULT WINAPI IDirectSoundWaveObject_QueryInterface(
	IDirectMusicObject* iface,REFIID riid,LPVOID *ppobj
) {
	ICOM_THIS_MULTI(IDirectSoundWaveImpl,lpOVtbl,iface);
	return IUnknown_QueryInterface((IUnknown*)This, riid, ppobj);
}

static ULONG WINAPI IDirectSoundWaveObject_AddRef(IDirectMusicObject* iface) {
	ICOM_THIS_MULTI(IDirectSoundWaveImpl,lpOVtbl,iface);
	return IUnknown_AddRef((IUnknown*)This);
}

static ULONG WINAPI IDirectSoundWaveObject_Release(IDirectMusicObject* iface) {
	ICOM_THIS_MULTI(IDirectSoundWaveImpl,lpOVtbl,iface);
	return IUnknown_Release((IUnknown*)This);
}

static HRESULT WINAPI IDirectSoundWaveObject_GetDescriptor(
	IDirectMusicObject* iface,
	LPDMUS_OBJECTDESC pDesc)
{
	ICOM_THIS_MULTI(IDirectSoundWaveImpl,lpOVtbl,iface);
	TRACE("(%p)->(%p)\n", This, pDesc);
	*pDesc = This->desc;
	return S_OK;
}

static HRESULT WINAPI IDirectSoundWaveObject_SetDescriptor(
	IDirectMusicObject* iface,
	LPDMUS_OBJECTDESC pDesc)
{
	ICOM_THIS_MULTI(IDirectSoundWaveImpl,lpOVtbl,iface);
	DWORD dwValid = pDesc->dwValidData;
	DWORD dwUsed = 0;
	TRACE("(%p)->(%p)\n", This, pDesc);
	if (dwValid & DMUS_OBJ_OBJECT) {
		memcpy(&This->desc.guidObject, &pDesc->guidObject, sizeof(This->desc.guidObject));
		dwUsed |= DMUS_OBJ_OBJECT;
	}
	if (dwValid & DMUS_OBJ_CLASS) {
		memcpy(&This->desc.guidClass, &pDesc->guidClass, sizeof(This->desc.guidClass));
		dwUsed |= DMUS_OBJ_CLASS;
	}
	if (dwValid & DMUS_OBJ_NAME) {
		memcpy(This->desc.wszName, pDesc->wszName, sizeof(This->desc.wszName));
		dwUsed |= DMUS_OBJ_NAME;
	}
	if (dwValid & DMUS_OBJ_CATEGORY) {
		memcpy(This->desc.wszCategory, pDesc->wszCategory, sizeof(This->desc.wszCategory));
		dwUsed |= DMUS_OBJ_CATEGORY;
	}
	if (dwValid & (DMUS_OBJ_FILENAME | DMUS_OBJ_FULLPATH)) {
		memcpy(This->desc.wszFileName, pDesc->wszFileName, sizeof(This->desc.wszFileName));
		dwUsed |= DMUS_OBJ_FILENAME;
		if (dwValid & DMUS_OBJ_FULLPATH)
			dwUsed |= DMUS_OBJ_FULLPATH;
	}
	if (dwValid & DMUS_OBJ_VERSION) {
		memcpy(&This->desc.vVersion, &pDesc->vVersion, sizeof(This->desc.vVersion));
		dwUsed |= DMUS_OBJ_VERSION;
	}
	if (dwValid & DMUS_OBJ_DATE) {
		memcpy(&This->desc.ftDate, &pDesc->ftDate, sizeof(This->desc.ftDate));
		dwUsed |= DMUS_OBJ_DATE;
	}
	This->desc.dwValidData |= dwUsed;
	dwValid &= ~dwUsed;
	if (dwValid) {
		FIXME("unhandled data: %08lx\n", dwValid);
		pDesc->dwValidData = dwValid;
		return S_FALSE;
	}
	return S_OK;
}

static HRESULT WINAPI IDirectSoundWaveObject_ParseDescriptor(
	IDirectMusicObject* iface,
	IStream* pStream,
	LPDMUS_OBJECTDESC pDesc)
{
	ICOM_THIS_MULTI(IDirectSoundWaveImpl,lpOVtbl,iface);
	TRACE("(%p)->(%p)\n", This, pDesc);
	if (!pDesc) return E_POINTER;
	return IDirectSoundWaveImpl_ParseStream(This, pStream, pDesc);
}

static ICOM_VTABLE(IDirectMusicObject) dmovt =
{
	ICOM_MSVTABLE_COMPAT_DummyRTTIVALUE
	IDirectSoundWaveObject_QueryInterface,
	IDirectSoundWaveObject_AddRef,
	IDirectSoundWaveObject_Release,
	IDirectSoundWaveObject_GetDescriptor,
	IDirectSoundWaveObject_SetDescriptor,
	IDirectSoundWaveObject_ParseDescriptor
};

static HRESULT WINAPI IDirectSoundWaveStream_QueryInterface(
	IPersistStream* iface,REFIID riid,LPVOID *ppobj
) {
	ICOM_THIS_MULTI(IDirectSoundWaveImpl,lpPSVtbl,iface);
	return IUnknown_QueryInterface((IUnknown*)This, riid, ppobj);
}

static ULONG WINAPI IDirectSoundWaveStream_AddRef(IPersistStream* iface) {
	ICOM_THIS_MULTI(IDirectSoundWaveImpl,lpPSVtbl,iface);
	return IUnknown_AddRef((IUnknown*)This);
}

static ULONG WINAPI IDirectSoundWaveStream_Release(IPersistStream* iface) {
	ICOM_THIS_MULTI(IDirectSoundWaveImpl,lpPSVtbl,iface);
	return IUnknown_Release((IUnknown*)This);
}

static HRESULT WINAPI IDirectSoundWaveStream_GetClassID(
	IPersistStream* iface,
	CLSID* pClassID)
{
	ICOM_THIS_MULTI(IDirectSoundWaveImpl,lpPSVtbl,iface);
	TRACE("(%p)->(%p)\n", This, pClassID);
	memcpy(pClassID, &CLSID_DirectSoundWave, sizeof(CLSID));
	return S_OK;
}

static HRESULT WINAPI IDirectSoundWaveStream_IsDirty(
	IPersistStream* iface)
{
	ICOM_THIS_MULTI(IDirectSoundWaveImpl,lpPSVtbl,iface);
	TRACE("(%p)->()\n", This);
	return S_FALSE; /* who cares */
}

static HRESULT WINAPI IDirectSoundWaveStream_Load(
	IPersistStream* iface,
	IStream* pStream)
{
	ICOM_THIS_MULTI(IDirectSoundWaveImpl,lpPSVtbl,iface);
	TRACE("(%p)->(%p)\n", This, pStream);
	return IDirectSoundWaveImpl_ParseStream(This, pStream, NULL);
}

static HRESULT WINAPI IDirectSoundWaveStream_Save(
	IPersistStream* iface,
	IStream* pStream,
	BOOL fClearDirty)
{
	ICOM_THIS_MULTI(IDirectSoundWaveImpl,lpPSVtbl,iface);
	FIXME("(%p)->(%p,%d): stub\n", This, pStream, fClearDirty);
	return E_NOTIMPL;
}

static HRESULT WINAPI IDirectSoundWaveStream_GetSizeMax(
	IPersistStream* iface,
	ULARGE_INTEGER* pcbSize)
{
	ICOM_THIS_MULTI(IDirectSoundWaveImpl,lpPSVtbl,iface);
	FIXME("(%p)->(%p): stub\n", This, pcbSize);
	pcbSize->QuadPart = 0;
	return E_NOTIMPL;
}

static ICOM_VTABLE(IPersistStream) dmpsvt =
{
	ICOM_MSVTABLE_COMPAT_DummyRTTIVALUE
	IDirectSoundWaveStream_QueryInterface,
	IDirectSoundWaveStream_AddRef,
	IDirectSoundWaveStream_Release,
	IDirectSoundWaveStream_GetClassID,
	IDirectSoundWaveStream_IsDirty,
	IDirectSoundWaveStream_Load,
	IDirectSoundWaveStream_Save,
	IDirectSoundWaveStream_GetSizeMax
};

static inline HRESULT Stream_Read(IStream* pStream, LPVOID buf, ULONG cb)
{
	ULONG res;
	HRESULT hr = IStream_Read(pStream, buf, cb, &res);
	if (FAILED(hr)) return hr;
	if (res < cb) return E_FAIL;
	return S_OK;
}

#define FIELD_READ(buf, fld) do { \
    IStream_Read(pStream, (LPVOID)&(buf).fld, sizeof((buf).fld), NULL); \
    /* add error checking here? */ \
  } while (0)

#define STRUCT_READ_START(buf) { unsigned _srcnt = 0;
#define STRUCT_READ(buf, fld) \
  if (cksize >= (_srcnt + sizeof((buf).fld))) { \
    FIELD_READ(buf, fld); \
    _srcnt += sizeof((buf).fld); \
  }
#define STRUCT_READ_END(buf) }

static HRESULT WINAPI IDirectSoundWaveImpl_ParseStream(
	IDirectSoundWaveImpl* This,
	IStream* pStream,
	LPDMUS_OBJECTDESC pDesc)
{
	HRESULT hr;
	LARGE_INTEGER ofs, eofs;
	ULARGE_INTEGER pos, endpos;
	DWORD ckid, fcctype;
	DWORD fsize, cksize;

	TRACE("(%p,%p,%p)\n", This, pStream, pDesc);

	/* since the .wav format is simple, we won't bother
	 * to try to wrap the mmio API in this DLL, just
	 * read the stream directly. */

	/* read WAVE form */
	hr = Stream_Read(pStream, &ckid, sizeof(ckid));
	if (FAILED(hr)) return hr;
	if (ckid != FOURCC_RIFF) {
		ERR("RIFF chunk not found\n");
		return E_FAIL;
	}
	hr = Stream_Read(pStream, &fsize, sizeof(fsize));
	if (FAILED(hr)) return hr;
	hr = Stream_Read(pStream, &fcctype, sizeof(fcctype));
	if (FAILED(hr)) return hr;
	if (fcctype != mmioFOURCC('W','A','V','E')) {
		ERR("WAVE form not found\n");
		return E_FAIL;
	}

	/* calculate size of form */
	ofs.QuadPart = 0;
	IStream_Seek(pStream, ofs, STREAM_SEEK_CUR, &pos);
	endpos.QuadPart = pos.QuadPart + fsize;

	/* read all chunks within */
	while (pos.QuadPart < endpos.QuadPart) {
		hr = Stream_Read(pStream, &ckid, sizeof(ckid));
		if (FAILED(hr)) return hr;
		hr = Stream_Read(pStream, &cksize, sizeof(cksize));
		if (FAILED(hr)) return hr;
		eofs.QuadPart = pos.QuadPart + sizeof(ckid) + sizeof(cksize) + cksize;
		if (ckid == FOURCC_RIFF || ckid == FOURCC_LIST) {
			hr = Stream_Read(pStream, &fcctype, sizeof(fcctype));
			if (FAILED(hr)) return hr;
			eofs.QuadPart += sizeof(fcctype);
		}
		switch (ckid) {
		case DMUS_FOURCC_WAVEHEADER_CHUNK:
			if (pDesc) break;
			STRUCT_READ_START(This->whdr)
			STRUCT_READ(This->whdr, rtReadAhead)
			STRUCT_READ(This->whdr, dwFlags)
			STRUCT_READ_END(This->whdr)
			TRACE("Streaming readahead: %Ld\n", This->whdr.rtReadAhead);
			TRACE("Streaming flags: 0x%lx\n", This->whdr.dwFlags);
			break;
		case mmioFOURCC('f','m','t',' '):
			if (pDesc) break;
			if (This->wfx) HeapFree(GetProcessHeap(), 0, This->wfx);
			This->wfx = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, cksize);
			STRUCT_READ_START(*This->wfx)
			STRUCT_READ(*This->wfx, wFormatTag)
			STRUCT_READ(*This->wfx, nChannels)
			STRUCT_READ(*This->wfx, nSamplesPerSec)
			STRUCT_READ(*This->wfx, nAvgBytesPerSec)
			STRUCT_READ(*This->wfx, nBlockAlign)
			STRUCT_READ(*This->wfx, wBitsPerSample)
			STRUCT_READ(*This->wfx, cbSize)
			switch (This->wfx->wFormatTag) {
			case WAVE_FORMAT_PCM: {
				WAVEFORMATEX* ffx = This->wfx;
				TRACE("Format: PCM, %ldHz, %d-bit, %s\n",
				      ffx->nSamplesPerSec, ffx->wBitsPerSample,
				      (ffx->nChannels>1)?"stereo":"mono");
				break;
			}
			case WAVE_FORMAT_MPEGLAYER3: {
				MPEGLAYER3WAVEFORMAT* ffx = (MPEGLAYER3WAVEFORMAT*)This->wfx;
				STRUCT_READ(*ffx, wID)
				STRUCT_READ(*ffx, fdwFlags)
				STRUCT_READ(*ffx, nBlockSize)
				STRUCT_READ(*ffx, nFramesPerBlock)
				STRUCT_READ(*ffx, nCodecDelay)
				TRACE("Format: MPEG layer 3, %ldHz, %ldbps, %s\n",
				      ffx->wfx.nSamplesPerSec, ffx->wfx.nAvgBytesPerSec*8,
				      (ffx->wfx.nChannels>1)?"stereo":"mono");
				break;
			}
			default:
				FIXME("unhandled wave format %d\n", This->wfx->wFormatTag);
				break;
			}
			STRUCT_READ_END(*This->wfx)
			break;
		case mmioFOURCC('d','a','t','a'):
			if (pDesc) break;
			if (This->data) HeapFree(GetProcessHeap(), 0, This->data);
			This->data = HeapAlloc(GetProcessHeap(), 0, cksize);
			This->datalen = cksize;
			if (This->wfx->nAvgBytesPerSec) {
				TRACE("Data: %ld bytes, %.3f seconds\n", cksize,
				      ((double)cksize)/This->wfx->nAvgBytesPerSec);
			} else {
				TRACE("Data: %ld bytes\n", cksize);
			}
			hr = Stream_Read(pStream, This->data, cksize);
			if (FAILED(hr)) return hr;
			break;
		default:
			FIXME("unknown chunk %08lx\n", ckid);
		}
		IStream_Seek(pStream, eofs, STREAM_SEEK_SET, &pos);
	}

	return S_OK;
}

/*****************************************************************************
 * IDirectSoundWaveStream implementation structure
 */
typedef struct IDirectSoundWaveStreamImpl IDirectSoundWaveStreamImpl;
struct IDirectSoundWaveStreamImpl
{
    /* IUnknown fields */
    ICOM_VFIELD(IStream);
    DWORD ref;
    /* IDirectSoundWaveStream fields */
    IDirectSoundWaveImpl* pWave;
    WAVEFORMATEX wfx, mwfx;
    BOOL open, mopen, is_eof;
    DWORD bufsize, bufptr, buflen;
    DWORD mbufsize, mbufptr, mbuflen;
    LPBYTE buf, mbuf;
    HACMSTREAM hacm, hmacm;
    DWORD spos;
    LARGE_INTEGER dpos;
};

static HRESULT WINAPI IDirectSoundWaveStreamImpl_QueryInterface(
	IStream* iface,REFIID riid,LPVOID *ppobj
) {
	ICOM_THIS(IDirectSoundWaveStreamImpl,iface);

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

static ULONG WINAPI IDirectSoundWaveStreamImpl_AddRef(IStream* iface) {
	ICOM_THIS(IDirectSoundWaveStreamImpl,iface);
	return ++(This->ref);
}

static ULONG WINAPI IDirectSoundWaveStreamImpl_Release(IStream* iface) {
	ICOM_THIS(IDirectSoundWaveStreamImpl,iface);
	TRACE("(%p), ref was %ld\n",This,This->ref);
	if (!--(This->ref)) {
		if (This->open) acmStreamClose(This->hacm, 0);
		if (This->mopen) acmStreamClose(This->hmacm, 0);
		if (This->buf) HeapFree(GetProcessHeap(), 0, This->buf);
		if (This->mbuf) HeapFree(GetProcessHeap(), 0, This->mbuf);
		IUnknown_Release((IUnknown*)This->pWave);
		HeapFree(GetProcessHeap(),0,This);
		return 0;
	}
	return This->ref;
}

static void mix_in(LPVOID dst, LPVOID src, DWORD len)
{
	SHORT* sw = (SHORT*)src;
	SHORT* dw = (SHORT*)dst;
	DWORD cnt = len/sizeof(SHORT);
	while (cnt--) *dw++ += *sw++;
}

static HRESULT WINAPI IDirectSoundWaveStreamImpl_Read(
	IStream* iface,
	void* pv,
	ULONG cb,
	ULONG* pcbRead)
{
	ICOM_THIS(IDirectSoundWaveStreamImpl,iface);
	ACMSTREAMHEADER hdr;
	DWORD fdwConvert = 0;
	DWORD dwRead = 0;
	MMRESULT mmres;

	TRACE("(%p)->(%p,%ld,%p)\n", This, pv, cb, pcbRead);
	if (!This->open) {
		mmres = acmStreamOpen(&This->hacm, 0, This->pWave->wfx,
				      &This->wfx, NULL, 0, 0, 0);
		if (!mmres) {
			This->open = TRUE;
			This->bufsize = This->wfx.nAvgBytesPerSec / 5;
		}
		if (mmres == ACMERR_NOTPOSSIBLE) {
			/* try with another format conversion in between */
			This->mwfx = This->wfx;
			This->mwfx.wBitsPerSample = 16;
			This->mwfx.nSamplesPerSec = This->pWave->wfx->nSamplesPerSec;
			This->mwfx.nChannels = This->pWave->wfx->nChannels;
			This->mwfx.nBlockAlign = This->mwfx.nChannels * 2;
			This->mwfx.nAvgBytesPerSec = This->mwfx.nSamplesPerSec * This->mwfx.nBlockAlign;
			mmres = acmStreamOpen(&This->hacm, 0, This->pWave->wfx,
					      &This->mwfx, NULL, 0, 0, 0);
			if (!mmres) {
				This->open = TRUE;
				This->bufsize = This->mwfx.nAvgBytesPerSec / 5;
				mmres = acmStreamOpen(&This->hmacm, 0, &This->mwfx,
						      &This->wfx, NULL, 0, 0, 0);
				if (!mmres) {
					This->mopen = TRUE;
					This->mbufsize = This->wfx.nAvgBytesPerSec / 5;
				}
			}
		}
		if (mmres) {
			ERR("ACM stream could not be opened, res=%d\n", mmres);
			return E_FAIL;
		}
		This->is_eof = FALSE;
		if (This->open)  This->buf  = HeapAlloc(GetProcessHeap(), 0, This->bufsize);
		This->bufptr = 0;
		This->buflen = 0;
		if (This->mopen) This->mbuf = HeapAlloc(GetProcessHeap(), 0, This->mbufsize);
		This->mbufptr = 0;
		This->mbuflen = 0;
		fdwConvert |= ACM_STREAMCONVERTF_START;
	}
	while (dwRead < cb) {
		/* read from format conversion buffer if available */
		if (This->mopen && This->mbufptr < This->mbuflen) {
			DWORD len = This->mbuflen - This->mbufptr;
			if (len > (cb - dwRead)) len = cb - dwRead;
			mix_in(((char*)pv) + dwRead, This->mbuf + This->mbufptr, len);
			This->mbufptr += len;
			dwRead += len;
			This->dpos.QuadPart += len;
			if (dwRead >= cb) break;
		}
		This->mbufptr = 0;
		This->mbuflen = 0;
		/* read from decompression buffer if available */
		if (This->bufptr < This->buflen) {
			if (This->mopen) {
				/* convert format */
				memset(&hdr, 0, sizeof(hdr));
				hdr.cbStruct = sizeof(hdr);
				hdr.pbSrc = This->buf + This->bufptr;
				hdr.cbSrcLength = This->buflen - This->bufptr;
				hdr.pbDst = This->mbuf;
				hdr.cbDstLength = This->mbufsize;
				mmres = acmStreamPrepareHeader(This->hmacm, &hdr, 0);
				if (mmres) {
					ERR("ACM stream header could not be prepared, res=%d\n", mmres);
					return E_FAIL;
				}
				mmres = acmStreamConvert(This->hmacm, &hdr, 0);
				This->bufptr += hdr.cbSrcLengthUsed;
				This->mbuflen = hdr.cbDstLengthUsed;
				acmStreamUnprepareHeader(This->hmacm, &hdr, 0);
				if (mmres) {
					ERR("ACM conversion failed, res=%d\n", mmres);
					return E_FAIL;
				}
				continue;
			} else {
				DWORD len = This->buflen - This->bufptr;
				if (len > (cb - dwRead)) len = cb - dwRead;
				mix_in(((char*)pv) + dwRead, This->buf + This->bufptr, len);
				This->bufptr += len;
				dwRead += len;
				This->dpos.QuadPart += len;
				if (dwRead >= cb) break;
			}
		}
		This->bufptr = 0;
		This->buflen = 0;
		if (This->is_eof) break;
		memset(&hdr, 0, sizeof(hdr));
		hdr.cbStruct = sizeof(hdr);
		hdr.pbSrc = ((char*)This->pWave->data) + This->spos;
		hdr.cbSrcLength = This->pWave->datalen - This->spos;
		hdr.pbDst = This->buf;
		hdr.cbDstLength = This->bufsize;
		if (!hdr.cbSrcLength) fdwConvert |= ACM_STREAMCONVERTF_END;
		mmres = acmStreamPrepareHeader(This->hacm, &hdr, 0);
		if (mmres) {
			ERR("ACM stream header could not be prepared, res=%d\n", mmres);
			This->is_eof = TRUE;
			return E_FAIL;
		}
		mmres = acmStreamConvert(This->hacm, &hdr, fdwConvert);
		TRACE("converted src=%ld/%ld, dst=%ld/%ld\n",
		      hdr.cbSrcLengthUsed, hdr.cbSrcLength,
		      hdr.cbDstLengthUsed, hdr.cbDstLength);
		This->spos += hdr.cbSrcLengthUsed;
		This->buflen = hdr.cbDstLengthUsed;
		acmStreamUnprepareHeader(This->hacm, &hdr, 0);
		if (mmres) {
			ERR("ACM conversion failed, res=%d\n", mmres);
			This->is_eof = TRUE;
			return E_FAIL;
		}
		fdwConvert &= ~ACM_STREAMCONVERTF_START;
		if ((fdwConvert & ACM_STREAMCONVERTF_END) && !hdr.cbDstLengthUsed) {
			TRACE("EOF encountered\n");
			This->is_eof = TRUE;
			break;
		}
	}
	return S_OK;
}

static HRESULT WINAPI IDirectSoundWaveStreamImpl_Write(
	IStream* iface,
	const void* pv,
	ULONG cb,
	ULONG* pcbWritten)
{
	ICOM_THIS(IDirectSoundWaveStreamImpl,iface);
	TRACE("(%p)->(%p,%ld,%p)\n", This, pv, cb, pcbWritten);
	if (pcbWritten) *pcbWritten = 0;
	if (cb != sizeof(WAVEFORMATEX)) return S_FALSE;
	memcpy(&This->wfx, pv, cb);
	return S_OK;
}

static HRESULT WINAPI IDirectSoundWaveStreamImpl_Seek(
	IStream* iface,
	LARGE_INTEGER dlibMove,
	DWORD dwOrigin,
	ULARGE_INTEGER* plibNewPosition)
{
	ICOM_THIS(IDirectSoundWaveStreamImpl,iface);
	FIXME("(%p)->(%Ld,%ld,%p): stub\n", This, dlibMove.QuadPart, dwOrigin, plibNewPosition);
	return E_NOTIMPL;
}

static ICOM_VTABLE(IStream) dmwsvt =
{
	ICOM_MSVTABLE_COMPAT_DummyRTTIVALUE
	IDirectSoundWaveStreamImpl_QueryInterface,
	IDirectSoundWaveStreamImpl_AddRef,
	IDirectSoundWaveStreamImpl_Release,
	IDirectSoundWaveStreamImpl_Read,
	IDirectSoundWaveStreamImpl_Write,
	IDirectSoundWaveStreamImpl_Seek,
	(void*)0xdeaf2208, /* SetSize */
	(void*)0xdeaf2209, /* CopyTo */
	(void*)0xdeaf220a, /* Commit */
	(void*)0xdeaf220b, /* Revert */
	(void*)0xdeaf220c, /* LockRegion */
	(void*)0xdeaf220d, /* UnlockRegion */
	(void*)0xdeaf220e, /* Stat */
	(void*)0xdeaf220f  /* Clone */
};

static HRESULT DirectSoundWaveStreamCreate(IUnknown* pWave, void** ppStream)
{
	IDirectSoundWaveStreamImpl* stream;
	stream = HeapAlloc(GetProcessHeap(),HEAP_ZERO_MEMORY,sizeof(IDirectSoundWaveStreamImpl));
	if (!stream) return E_OUTOFMEMORY;

	ICOM_VTBL(stream)	= &dmwsvt;
	stream->ref		= 1;
	IUnknown_AddRef(pWave);
	stream->pWave		= (IDirectSoundWaveImpl*)pWave;

	*ppStream = (void*)stream;

	return S_OK;
}

/***********************************************************************
 *		DirectSoundWaveCreate 
 */
static HRESULT DirectSoundWaveCreate(IUnknown **ppDM, IUnknown *pUnkOuter )
{
	IDirectSoundWaveImpl** ippDM=(IDirectSoundWaveImpl**)ppDM;

	TRACE("(%p,%p)\n",ippDM,pUnkOuter);

	if (ippDM == NULL)
		return E_INVALIDARG;

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

	ICOM_VTBL(*ippDM)	= &dswvt;
	(*ippDM)->lpOVtbl	= &dmovt;
	(*ippDM)->lpPSVtbl	= &dmpsvt;
	(*ippDM)->ref		= 1;

	return DS_OK;
}


/***********************************************************************
 *		DllCanUnloadNow (DSWAVE.@)
 */
HRESULT WINAPI DSWAVE_DllCanUnloadNow(void)
{
    FIXME("(void): stub\n");

    return S_FALSE;
}


/*******************************************************************************
 * DirectMusic ClassFactory
 */
typedef struct
{
    /* IUnknown fields */
    ICOM_VFIELD(IClassFactory);
    DWORD                       ref;
} IClassFactoryImpl;

static HRESULT WINAPI 
DMCF_QueryInterface(LPCLASSFACTORY iface,REFIID riid,LPVOID *ppobj) {
	ICOM_THIS(IClassFactoryImpl,iface);

	FIXME("(%p)->(%s,%p),stub!\n",This,debugstr_guid(riid),ppobj);
	return E_NOINTERFACE;
}

static ULONG WINAPI
DMCF_AddRef(LPCLASSFACTORY iface) {
	ICOM_THIS(IClassFactoryImpl,iface);
	return ++(This->ref);
}

static ULONG WINAPI DMCF_Release(LPCLASSFACTORY iface) {
	ICOM_THIS(IClassFactoryImpl,iface);
	/* static class, won't be  freed */
	return --(This->ref);
}

static HRESULT WINAPI DMCF_CreateInstance(
	LPCLASSFACTORY iface,LPUNKNOWN pOuter,REFIID riid,LPVOID *ppobj
) {
	ICOM_THIS(IClassFactoryImpl,iface);
	IUnknown *obj = NULL;
	HRESULT hres;

	TRACE("(%p)->(%p,%s,%p)\n",This,pOuter,debugstr_guid(riid),ppobj);
	hres = DirectSoundWaveCreate(&obj,pOuter);
	if (FAILED(hres)) return hres;
	hres = IUnknown_QueryInterface(obj, riid, ppobj);
	IUnknown_Release(obj);
	return hres;
}

static HRESULT WINAPI DMCF_LockServer(LPCLASSFACTORY iface,BOOL dolock) {
	ICOM_THIS(IClassFactoryImpl,iface);
	FIXME("(%p)->(%d),stub!\n",This,dolock);
	return S_OK;
}

static ICOM_VTABLE(IClassFactory) DMCF_Vtbl = {
	ICOM_MSVTABLE_COMPAT_DummyRTTIVALUE
	DMCF_QueryInterface,
	DMCF_AddRef,
	DMCF_Release,
	DMCF_CreateInstance,
	DMCF_LockServer
};
static IClassFactoryImpl DSWAVE_CF = {&DMCF_Vtbl, 1 };

/*******************************************************************************
 * DllGetClassObject [DSWAVE.@]
 * Retrieves class object from a DLL object
 *
 * PARAMS
 *    rclsid [I] CLSID for the class object
 *    riid   [I] Reference to identifier of interface for class object
 *    ppv    [O] Address of variable to receive interface pointer for riid
 *
 * RETURNS
 *    Success: S_OK
 *    Failure: CLASS_E_CLASSNOTAVAILABLE, E_OUTOFMEMORY, E_INVALIDARG,
 *             E_UNEXPECTED
 */
DWORD WINAPI DSWAVE_DllGetClassObject(REFCLSID rclsid,REFIID riid,LPVOID *ppv)
{
    TRACE("(%p,%p,%p)\n", debugstr_guid(rclsid), debugstr_guid(riid), ppv);
    if ( IsEqualCLSID( &IID_IClassFactory, riid ) ) {
    	*ppv = (LPVOID)&DSWAVE_CF;
        IClassFactory_AddRef((IClassFactory*)*ppv);
        return S_OK;
    }

    FIXME("(%p,%p,%p): no interface found.\n", debugstr_guid(rclsid), debugstr_guid(riid), ppv);
    return CLASS_E_CLASSNOTAVAILABLE;
}



/***********************************************************************
 *		DllRegisterServer (DSWAVE.@)
 */
HRESULT WINAPI DSWAVE_DllRegisterServer(void)
{
    FIXME("(void): stub\n");

    return S_OK;
}

/***********************************************************************
 *		DllUnregisterServer (DSWAVE.@)
 */
HRESULT WINAPI DSWAVE_DllUnregisterServer(void)
{
    FIXME("(void): stub\n");

    return S_OK;
}
