/*
 * Copyright (c) 2010-2022 Belledonne Communications SARL.
 *
 * This file is part of mediastreamer2
 * (see https://gitlab.linphone.org/BC/public/mediastreamer2).
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as
 * published by the Free Software Foundation, either version 3 of the
 * License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program. If not, see <http://www.gnu.org/licenses/>.
 */

#ifdef __DIRECTSOUND_ENABLED__

#define UNICODE

#include "mediastreamer2/msfilter.h"
#include "mediastreamer2/mssndcard.h"
#include "mediastreamer2/msticker.h"

#include <mmsystem.h>
#ifdef _MSC_VER
#include <mmreg.h>
#endif
#include <msacm.h>

#include <dsconf.h>
#include <dsound.h>

const GUID GUID_DSCFX_MS_AEC = {0xcdebb919, 0x379a, 0x488a, {0x87, 0x65, 0xf5, 0x3c, 0xfd, 0x36, 0xde, 0x40}};
const GUID GUID_DSCFX_CLASS_AEC = {0xBF963D80L, 0xC559, 0x11D0, {0x8A, 0x2B, 0x00, 0xA0, 0xC9, 0x25, 0x5A, 0xC1}};

const GUID CLSID_DirectSoundPrivate = {0x11ab3ec0, 0x25ec, 0x11d1, {0xa4, 0xd8, 0x0, 0xc0, 0x4f, 0xc2, 0x8a, 0xca}};
const GUID DSPROPSETID_DirectSoundDevice = {
    0x84624f82, 0x25ec, 0x11d1, {0xa4, 0xd8, 0x0, 0xc0, 0x4f, 0xc2, 0x8a, 0xca}};

#define WINSNDDS_MINIMUMBUFFER 5

static MSFilter *ms_winsndds_read_new(MSSndCard *card);
static MSFilter *ms_winsndds_write_new(MSSndCard *card);

static HMODULE ms_lib_instance = NULL;
static HRESULT(WINAPI *ms_DllGetClassObject)(REFCLSID, REFIID, LPVOID *);

static HRESULT(WINAPI *ms_DirectSoundCreate)(LPGUID, LPDIRECTSOUND *, LPUNKNOWN);
static HRESULT(WINAPI *ms_DirectSoundEnumerate)(LPDSENUMCALLBACKW, LPVOID);

static HRESULT(WINAPI *ms_DirectSoundCaptureCreate)(LPGUID, LPDIRECTSOUNDCAPTURE *, LPUNKNOWN);
static HRESULT(WINAPI *ms_DirectSoundCaptureEnumerate)(LPDSENUMCALLBACKW, LPVOID);

static HRESULT(WINAPI *ms_DirectSoundFullDuplexCreate)(LPCGUID,
                                                       LPCGUID,
                                                       LPCDSCBUFFERDESC,
                                                       LPCDSBUFFERDESC,
                                                       HWND,
                                                       DWORD,
                                                       LPDIRECTSOUNDFULLDUPLEX *,
                                                       LPDIRECTSOUNDCAPTUREBUFFER8 *,
                                                       LPDIRECTSOUNDBUFFER8 *,
                                                       LPUNKNOWN);

typedef struct WinSndDsCard {
	int in_devid;
	int out_devid;
	GUID in_guid;
	GUID out_guid;
	int removed;
} WinSndDsCard;

static BOOL GetWaveIdFromDSoundGUID(GUID i_sGUID, DWORD *dwWaveID) {
	LPKSPROPERTYSET pKsPropertySet = NULL;
	LPCLASSFACTORY pClassFactory = NULL;
	HRESULT hr;
	BOOL retval = FALSE;

	PDSPROPERTY_DIRECTSOUNDDEVICE_DESCRIPTION_DATA psDirectSoundDeviceDescription = NULL;
	DSPROPERTY_DIRECTSOUNDDEVICE_DESCRIPTION_DATA sDirectSoundDeviceDescription;

	memset(&sDirectSoundDeviceDescription, 0, sizeof(sDirectSoundDeviceDescription));

	hr = ms_DllGetClassObject(CLSID_DirectSoundPrivate, IID_IClassFactory, (LPVOID *)&pClassFactory);

	if (SUCCEEDED(hr)) {
		hr = pClassFactory->CreateInstance(NULL, IID_IKsPropertySet, (LPVOID *)&pKsPropertySet);
	}

	// Release the class factory
	if (pClassFactory) {
		pClassFactory->Release();
	}

	if (SUCCEEDED(hr)) {
		ULONG ulBytesReturned = 0;
		sDirectSoundDeviceDescription.DeviceId = i_sGUID;

		// On the first call the final size is unknown so pass the size of the struct in order to receive
		// "Type" and "DataFlow" values, ulBytesReturned will be populated with bytes required for struct+strings.
		hr = pKsPropertySet->Get(DSPROPSETID_DirectSoundDevice, DSPROPERTY_DIRECTSOUNDDEVICE_DESCRIPTION, NULL, 0,
		                         &sDirectSoundDeviceDescription, sizeof(sDirectSoundDeviceDescription),
		                         &ulBytesReturned);

		if (ulBytesReturned) {
			// On the first call it notifies us of the required amount of memory in order to receive the strings.
			// Allocate the required memory, the strings will be pointed to the memory space directly after the struct.
			psDirectSoundDeviceDescription = (PDSPROPERTY_DIRECTSOUNDDEVICE_DESCRIPTION_DATA) new BYTE[ulBytesReturned];
			*psDirectSoundDeviceDescription = sDirectSoundDeviceDescription;

			hr = pKsPropertySet->Get(DSPROPSETID_DirectSoundDevice, DSPROPERTY_DIRECTSOUNDDEVICE_DESCRIPTION, NULL, 0,
			                         psDirectSoundDeviceDescription, ulBytesReturned, &ulBytesReturned);

			*dwWaveID = psDirectSoundDeviceDescription->WaveDeviceId;
			delete[] psDirectSoundDeviceDescription;
			retval = TRUE;
		}

		pKsPropertySet->Release();
	}

	return retval;
}

static void winsnddscard_set_level(MSSndCard *card, MSSndCardMixerElem e, int percent) {
	WinSndDsCard *d = (WinSndDsCard *)card->data;
	DWORD waveout_devid = WAVE_MAPPER;
	DWORD wavein_devid = WAVE_MAPPER;

	UINT uMixerID;
	DWORD dwMixerHandle;
	MIXERLINE MixerLine;
	MIXERLINE Line;
	UINT uLineIndex;

	MIXERLINECONTROLS mlc = {0};
	MIXERCONTROL mc = {0};
	MIXERCONTROLDETAILS mcd = {0};
	MIXERCONTROLDETAILS_UNSIGNED mcdu = {0};

	MMRESULT mr = MMSYSERR_NOERROR;
	DWORD dwVolume = ((0xFFFF) * percent) / 100;

	WORD wLeftVol, wRightVol;
	DWORD dwNewVol;
	wLeftVol = LOWORD(dwVolume);  // get higher WORD
	wRightVol = LOWORD(dwVolume); // get lower WORD

	dwNewVol = MAKELONG(wLeftVol, wRightVol);

	GetWaveIdFromDSoundGUID(d->in_guid, &wavein_devid);
	GetWaveIdFromDSoundGUID(d->out_guid, &waveout_devid);

	switch (e) {
		case MS_SND_CARD_PLAYBACK:
		case MS_SND_CARD_MASTER: {

			mr = mixerGetID((HMIXEROBJ)waveout_devid, &uMixerID, MIXER_OBJECTF_WAVEOUT);
			if (mr != MMSYSERR_NOERROR) {
				ms_error("winsndcard_set_level: mixerGetID failed. (0x%x)", mr);
				return;
			}
			mr = mixerOpen((LPHMIXER)&dwMixerHandle, uMixerID, 0L, 0L, 0L);
			if (mr != MMSYSERR_NOERROR) {
				mixerClose((HMIXER)dwMixerHandle);
				ms_error("winsndcard_set_level: mixerOpen failed. (0x%x)", mr);
				return;
			}
			memset(&MixerLine, 0, sizeof(MIXERLINE));
			MixerLine.cbStruct = sizeof(MIXERLINE);
			if (MS_SND_CARD_MASTER == e) MixerLine.dwComponentType = MIXERLINE_COMPONENTTYPE_DST_SPEAKERS;
			else MixerLine.dwComponentType = MIXERLINE_COMPONENTTYPE_SRC_WAVEOUT;
			mr = mixerGetLineInfo((HMIXEROBJ)dwMixerHandle, &MixerLine, MIXER_GETLINEINFOF_COMPONENTTYPE);
			if (mr != MMSYSERR_NOERROR) {
				mixerClose((HMIXER)dwMixerHandle);
				ms_error("winsndcard_set_level: mixerGetLineInfo failed. (0x%x)", mr);
				return;
			}

			/* ms_message("Name: %s\n", MixerLine.szName); */
			/* ms_message("Source Line: %d\n", MixerLine.dwSource); */
			/* ms_message("ComponentType: %d\n", MixerLine.dwComponentType); */

			mlc.cbStruct = sizeof(MIXERLINECONTROLS);
			mlc.dwLineID = MixerLine.dwLineID;
			mlc.dwControlType = MIXERCONTROL_CONTROLTYPE_VOLUME;
			mlc.cControls = 1;
			mlc.pamxctrl = &mc;
			mlc.cbmxctrl = sizeof(MIXERCONTROL);
			mr = mixerGetLineControls((HMIXEROBJ)dwMixerHandle, &mlc, MIXER_GETLINECONTROLSF_ONEBYTYPE);

			if (mr != MMSYSERR_NOERROR) {
				ms_error("winsndcard_set_level: mixerGetLineControls failed. (0x%x)", mr);
				return;
			}

			mcdu.dwValue = 65535 * percent / 100; /* the volume is a number between 0 and 65535 */

			mcd.cbStruct = sizeof(MIXERCONTROLDETAILS);
			mcd.hwndOwner = 0;
			mcd.dwControlID = mc.dwControlID;
			mcd.paDetails = &mcdu;
			mcd.cbDetails = sizeof(MIXERCONTROLDETAILS_UNSIGNED);
			mcd.cChannels = 1;
			mr = mixerSetControlDetails((HMIXEROBJ)dwMixerHandle, &mcd, MIXER_SETCONTROLDETAILSF_VALUE);

			if (mr != MMSYSERR_NOERROR) {
				ms_error("winsndcard_set_level: mixerSetControlDetails failed. (0x%x)", mr);
				return;
			}
		} break;
		case MS_SND_CARD_CAPTURE:
			mr = mixerGetID((HMIXEROBJ)wavein_devid, &uMixerID, MIXER_OBJECTF_WAVEIN);
			if (mr != MMSYSERR_NOERROR) {
				ms_error("winsndcard_set_level: mixerGetID failed. (0x%x)", mr);
				return;
			}
			mr = mixerOpen((LPHMIXER)&dwMixerHandle, uMixerID, 0L, 0L, 0L);
			if (mr != MMSYSERR_NOERROR) {
				mixerClose((HMIXER)dwMixerHandle);
				ms_error("winsndcard_set_level: mixerGetLineInfo failed. (0x%x)", mr);
				return;
			}
			memset(&MixerLine, 0, sizeof(MIXERLINE));
			MixerLine.cbStruct = sizeof(MIXERLINE);
			MixerLine.dwComponentType = MIXERLINE_COMPONENTTYPE_DST_WAVEIN;
			mr = mixerGetLineInfo((HMIXEROBJ)dwMixerHandle, &MixerLine, MIXER_GETLINEINFOF_COMPONENTTYPE);
			if (mr != MMSYSERR_NOERROR) {
				mixerClose((HMIXER)dwMixerHandle);
				ms_error("winsndcard_set_level: mixerGetLineInfo failed. (0x%x)", mr);
				return;
			}

			/* ms_message("Name: %s\n", MixerLine.szName); */
			/* ms_message("Source Line: %d\n", MixerLine.dwSource); */
			/* ms_message("ComponentType: %d\n", MixerLine.dwComponentType); */

			mlc.cbStruct = sizeof(MIXERLINECONTROLS);
			mlc.dwLineID = MixerLine.dwLineID;
			mlc.dwControlType = MIXERCONTROL_CONTROLTYPE_VOLUME;
			mlc.cControls = 1;
			mlc.pamxctrl = &mc;
			mlc.cbmxctrl = sizeof(MIXERCONTROL);
			mr = mixerGetLineControls((HMIXEROBJ)dwMixerHandle, &mlc, MIXER_GETLINECONTROLSF_ONEBYTYPE);

			if (mr == MMSYSERR_NOERROR) {
				mcdu.dwValue = 65535 * percent / 100; /* the volume is a number between 0 and 65535 */

				mcd.cbStruct = sizeof(MIXERCONTROLDETAILS);
				mcd.hwndOwner = 0;
				mcd.dwControlID = mc.dwControlID;
				mcd.paDetails = &mcdu;
				mcd.cbDetails = sizeof(MIXERCONTROLDETAILS_UNSIGNED);
				mcd.cChannels = 1;
				mr = mixerSetControlDetails((HMIXEROBJ)dwMixerHandle, &mcd, MIXER_SETCONTROLDETAILSF_VALUE);

				if (mr == MMSYSERR_NOERROR) {
					return;
				}
				ms_error("winsndcard_set_level: mixerSetControlDetails failed. (0x%x)", mr);
				ms_warning("winsndcard_set_level: control the SRC_MICROPHONE instead");
			} else {
				ms_error("winsndcard_set_level: mixerGetLineControls failed. (0x%x)", mr);
				ms_warning("winsndcard_set_level: control the SRC_MICROPHONE instead");
			}

			/* In case capture doesn't work: use the SRC_MICROPHONE volume */

			for (uLineIndex = 0; uLineIndex < MixerLine.cConnections; uLineIndex++) {
				memset(&Line, 0, sizeof(MIXERLINE));
				Line.cbStruct = sizeof(MIXERLINE);
				Line.dwDestination = MixerLine.dwDestination;
				Line.dwSource = uLineIndex;
				mr = mixerGetLineInfo((HMIXEROBJ)dwMixerHandle, &Line, MIXER_GETLINEINFOF_LINEID);
				if (mr != MMSYSERR_NOERROR) {
					mixerClose((HMIXER)dwMixerHandle);
					ms_error("winsndcard_set_level: mixerGetLineInfo failed. (0x%x)", mr);
					return;
				}

				/* ms_message("Name: %s\n", MixerLine.szName); */
				/* ms_message("Source Line: %d\n", MixerLine.dwSource); */
				/* ms_message("LineID: %d\n", MixerLine.dwLineID); */
				/* ms_message("ComponentType: %d\n", MixerLine.dwComponentType); */

				memset(&Line, 0, sizeof(MIXERLINE));
				Line.cbStruct = sizeof(MIXERLINE);
				Line.dwDestination = MixerLine.dwDestination;
				Line.dwSource = uLineIndex;
				mr = mixerGetLineInfo((HMIXEROBJ)dwMixerHandle, &Line, MIXER_GETLINEINFOF_SOURCE);
				if (mr != MMSYSERR_NOERROR) {
					mixerClose((HMIXER)dwMixerHandle);
					ms_error("winsndcard_set_level: mixerGetLineInfo failed. (0x%x)", mr);
					return;
				}

				/* ms_message("Name: %s\n", MixerLine.szName); */
				/* ms_message("Source Line: %d\n", MixerLine.dwSource); */
				/* ms_message("LineID: %d\n", MixerLine.dwLineID); */
				/* ms_message("ComponentType: %d\n", MixerLine.dwComponentType); */

				if (MIXERLINE_COMPONENTTYPE_SRC_MICROPHONE == Line.dwComponentType) {
					LPMIXERCONTROL pmxctrl = (LPMIXERCONTROL)malloc(sizeof(MIXERCONTROL));
					MIXERLINECONTROLS mxlctrl = {
					    sizeof(mxlctrl),      Line.dwLineID, MIXERCONTROL_CONTROLTYPE_VOLUME, 1,
					    sizeof(MIXERCONTROL), pmxctrl};
					mr = mixerGetLineControls((HMIXEROBJ)dwMixerHandle, &mxlctrl, MIXER_GETLINECONTROLSF_ONEBYTYPE);
					if (!mr) {
						DWORD cChannels = Line.cChannels;
						LPMIXERCONTROLDETAILS_UNSIGNED pUnsigned;
						MIXERCONTROLDETAILS mxcd;
						if (MIXERCONTROL_CONTROLF_UNIFORM & pmxctrl->fdwControl) cChannels = 1;
						pUnsigned =
						    (LPMIXERCONTROLDETAILS_UNSIGNED)malloc(cChannels * sizeof(MIXERCONTROLDETAILS_UNSIGNED));

						mxcd.cbStruct = sizeof(mxcd);
						mxcd.dwControlID = pmxctrl->dwControlID;
						mxcd.cChannels = cChannels;
						mxcd.hwndOwner = (HWND)0;
						mxcd.cbDetails = sizeof(MIXERCONTROLDETAILS_UNSIGNED);
						mxcd.paDetails = (LPVOID)pUnsigned;

						mr = mixerGetControlDetails((HMIXEROBJ)dwMixerHandle, &mxcd, MIXER_SETCONTROLDETAILSF_VALUE);
						if (!mr) {
							pUnsigned[0].dwValue = pUnsigned[cChannels - 1].dwValue =
							    pmxctrl->Bounds.dwMaximum * percent / 100;
							mr =
							    mixerSetControlDetails((HMIXEROBJ)dwMixerHandle, &mxcd, MIXER_SETCONTROLDETAILSF_VALUE);
							if (mr) {
								ms_error("winsndcard_set_level: mixerSetControlDetails failed. (0x%x)", mr);
							}
						} else {
							ms_error("winsndcard_set_level: mixerGetControlDetails failed. (0x%x)", mr);
						}
						free(pmxctrl);
						free(pUnsigned);
					} else {
						ms_error("winsndcard_set_level: mixerGetLineControls failed. (0x%x)", mr);
						free(pmxctrl);
					}
				}
			}
			mixerClose((HMIXER)dwMixerHandle);
			if (mr != MMSYSERR_NOERROR) {
				ms_error("winsndcard_set_level: mixerClose failed. (0x%x)", mr);
				return;
			}
			break;
		default:
			ms_warning("winsnd_card_set_level: unsupported command.");
	}
}

static int winsnddscard_get_level(MSSndCard *card, MSSndCardMixerElem e) {
	WinSndDsCard *d = (WinSndDsCard *)card->data;
	DWORD waveout_devid = WAVE_MAPPER;
	DWORD wavein_devid = WAVE_MAPPER;

	UINT uMixerID;
	DWORD dwMixerHandle;
	MIXERLINE MixerLine;
	MIXERLINE Line;
	UINT uLineIndex;

	MIXERLINECONTROLS mlc = {0};
	MIXERCONTROL mc = {0};
	MIXERCONTROLDETAILS mcd = {0};
	MIXERCONTROLDETAILS_UNSIGNED mcdu = {0};

	MMRESULT mr = MMSYSERR_NOERROR;
	int percent;

	GetWaveIdFromDSoundGUID(d->in_guid, &wavein_devid);
	GetWaveIdFromDSoundGUID(d->out_guid, &waveout_devid);

	switch (e) {
		case MS_SND_CARD_MASTER:
		case MS_SND_CARD_PLAYBACK: {
			mr = mixerGetID((HMIXEROBJ)waveout_devid, &uMixerID, MIXER_OBJECTF_WAVEOUT);
			if (mr != MMSYSERR_NOERROR) {
				ms_error("winsndcard_get_level: mixerGetID failed. (0x%x)", mr);
				return -1;
			}
			mr = mixerOpen((LPHMIXER)&dwMixerHandle, uMixerID, 0L, 0L, 0L);
			if (mr != MMSYSERR_NOERROR) {
				mixerClose((HMIXER)dwMixerHandle);
				ms_error("winsndcard_get_level: mixerOpen failed. (0x%x)", mr);
				return -1;
			}
			memset(&MixerLine, 0, sizeof(MIXERLINE));
			MixerLine.cbStruct = sizeof(MIXERLINE);
			if (MS_SND_CARD_MASTER == e) MixerLine.dwComponentType = MIXERLINE_COMPONENTTYPE_DST_SPEAKERS;
			else MixerLine.dwComponentType = MIXERLINE_COMPONENTTYPE_SRC_WAVEOUT;
			mr = mixerGetLineInfo((HMIXEROBJ)dwMixerHandle, &MixerLine, MIXER_GETLINEINFOF_COMPONENTTYPE);
			if (mr != MMSYSERR_NOERROR) {
				mixerClose((HMIXER)dwMixerHandle);
				ms_error("winsndcard_get_level: mixerGetLineInfo failed. (0x%x)", mr);
				return -1;
			}

			/* ms_message("Name: %s\n", MixerLine.szName); */
			/* ms_message("Source Line: %d\n", MixerLine.dwSource); */
			/* ms_message("ComponentType: %d\n", MixerLine.dwComponentType); */

			mlc.cbStruct = sizeof(MIXERLINECONTROLS);
			mlc.dwLineID = MixerLine.dwLineID;
			mlc.dwControlType = MIXERCONTROL_CONTROLTYPE_VOLUME;
			mlc.cControls = 1;
			mlc.pamxctrl = &mc;
			mlc.cbmxctrl = sizeof(MIXERCONTROL);
			mr = mixerGetLineControls((HMIXEROBJ)dwMixerHandle, &mlc, MIXER_GETLINECONTROLSF_ONEBYTYPE);
			if (mr != MMSYSERR_NOERROR) {
				ms_error("winsndcard_get_level: mixerGetLineControls failed. (0x%x)", mr);
				return -1;
			}

			mcd.cbStruct = sizeof(MIXERCONTROLDETAILS);
			mcd.hwndOwner = 0;
			mcd.dwControlID = mc.dwControlID;
			mcd.paDetails = &mcdu;
			mcd.cbDetails = sizeof(MIXERCONTROLDETAILS_UNSIGNED);
			mcd.cChannels = 1;
			mr = mixerGetControlDetails((HMIXEROBJ)dwMixerHandle, &mcd, MIXER_SETCONTROLDETAILSF_VALUE);
			percent = (mcdu.dwValue * 100) / (mc.Bounds.dwMaximum);

			if (mr != MMSYSERR_NOERROR) {
				ms_error("winsndcard_get_level: mixerGetControlDetails failed. (0x%x)", mr);
				return -1;
			}
			return percent;
		} break;
		case MS_SND_CARD_CAPTURE:
			mr = mixerGetID((HMIXEROBJ)wavein_devid, &uMixerID, MIXER_OBJECTF_WAVEIN);
			if (mr != MMSYSERR_NOERROR) {
				ms_error("winsndcard_get_level: mixerGetID failed. (0x%x)", mr);
				return -1;
			}
			mr = mixerOpen((LPHMIXER)&dwMixerHandle, uMixerID, 0L, 0L, 0L);
			if (mr != MMSYSERR_NOERROR) {
				mixerClose((HMIXER)dwMixerHandle);
				ms_error("winsndcard_get_level: mixerOpen failed. (0x%x)", mr);
				return -1;
			}
			memset(&MixerLine, 0, sizeof(MIXERLINE));
			MixerLine.cbStruct = sizeof(MIXERLINE);
			MixerLine.dwComponentType = MIXERLINE_COMPONENTTYPE_DST_WAVEIN;
			mr = mixerGetLineInfo((HMIXEROBJ)dwMixerHandle, &MixerLine, MIXER_GETLINEINFOF_COMPONENTTYPE);
			if (mr != MMSYSERR_NOERROR) {
				mixerClose((HMIXER)dwMixerHandle);
				ms_error("winsndcard_get_level: mixerGetLineInfo failed. (0x%x)", mr);
				return -1;
			}

			/* ms_message("Name: %s\n", MixerLine.szName); */
			/* ms_message("Source Line: %d\n", MixerLine.dwSource); */
			/* ms_message("ComponentType: %d\n", MixerLine.dwComponentType); */

			mlc.cbStruct = sizeof(MIXERLINECONTROLS);
			mlc.dwLineID = MixerLine.dwLineID;
			mlc.dwControlType = MIXERCONTROL_CONTROLTYPE_VOLUME;
			mlc.cControls = 1;
			mlc.pamxctrl = &mc;
			mlc.cbmxctrl = sizeof(MIXERCONTROL);
			mr = mixerGetLineControls((HMIXEROBJ)dwMixerHandle, &mlc, MIXER_GETLINECONTROLSF_ONEBYTYPE);
			if (mr == MMSYSERR_NOERROR) {
				mcd.cbStruct = sizeof(MIXERCONTROLDETAILS);
				mcd.hwndOwner = 0;
				mcd.dwControlID = mc.dwControlID;
				mcd.paDetails = &mcdu;
				mcd.cbDetails = sizeof(MIXERCONTROLDETAILS_UNSIGNED);
				mcd.cChannels = 1;
				mr = mixerGetControlDetails((HMIXEROBJ)dwMixerHandle, &mcd, MIXER_SETCONTROLDETAILSF_VALUE);
				percent = (mcdu.dwValue * 100) / (mc.Bounds.dwMaximum);

				if (mr == MMSYSERR_NOERROR) {
					return percent;
				}
				ms_error("winsndcard_get_level: mixerGetControlDetails failed. (0x%x)", mr);
				ms_warning("winsndcard_get_level: control the SRC_MICROPHONE instead");
			} else {
				ms_error("winsndcard_get_level: mixerGetLineControls failed. (0x%x)", mr);
				ms_warning("winsndcard_get_level: control the SRC_MICROPHONE instead");
			}

			for (uLineIndex = 0; uLineIndex < MixerLine.cConnections; uLineIndex++) {
				memset(&Line, 0, sizeof(MIXERLINE));
				Line.cbStruct = sizeof(MIXERLINE);
				Line.dwDestination = MixerLine.dwDestination;
				Line.dwSource = uLineIndex;
				mr = mixerGetLineInfo((HMIXEROBJ)dwMixerHandle, &Line, MIXER_GETLINEINFOF_LINEID);
				if (mr != MMSYSERR_NOERROR) {
					mixerClose((HMIXER)dwMixerHandle);
					ms_error("winsndcard_get_level: mixerGetLineInfo failed. (0x%x)", mr);
					return -1;
				}

				/* ms_message("Name: %s\n", MixerLine.szName); */
				/* ms_message("Source Line: %d\n", MixerLine.dwSource); */
				/* ms_message("LineID: %d\n", MixerLine.dwLineID); */
				/* ms_message("ComponentType: %d\n", MixerLine.dwComponentType); */

				memset(&Line, 0, sizeof(MIXERLINE));
				Line.cbStruct = sizeof(MIXERLINE);
				Line.dwDestination = MixerLine.dwDestination;
				Line.dwSource = uLineIndex;
				mr = mixerGetLineInfo((HMIXEROBJ)dwMixerHandle, &Line, MIXER_GETLINEINFOF_SOURCE);
				if (mr != MMSYSERR_NOERROR) {
					mixerClose((HMIXER)dwMixerHandle);
					ms_error("winsndcard_get_level: mixerGetLineInfo failed. (0x%x)", mr);
					return -1;
				}

				/* ms_message("Name: %s\n", MixerLine.szName); */
				/* ms_message("Source Line: %d\n", MixerLine.dwSource); */
				/* ms_message("LineID: %d\n", MixerLine.dwLineID); */
				/* ms_message("ComponentType: %d\n", MixerLine.dwComponentType); */

				if (MIXERLINE_COMPONENTTYPE_SRC_MICROPHONE == Line.dwComponentType) {
					LPMIXERCONTROL pmxctrl = (LPMIXERCONTROL)malloc(sizeof(MIXERCONTROL));
					MIXERLINECONTROLS mxlctrl = {
					    sizeof(mxlctrl),      Line.dwLineID, MIXERCONTROL_CONTROLTYPE_VOLUME, 1,
					    sizeof(MIXERCONTROL), pmxctrl};
					if (!mixerGetLineControls((HMIXEROBJ)dwMixerHandle, &mxlctrl, MIXER_GETLINECONTROLSF_ONEBYTYPE)) {
						DWORD cChannels = Line.cChannels;
						LPMIXERCONTROLDETAILS_UNSIGNED pUnsigned;
						MIXERCONTROLDETAILS mxcd;
						if (MIXERCONTROL_CONTROLF_UNIFORM & pmxctrl->fdwControl) cChannels = 1;
						pUnsigned =
						    (LPMIXERCONTROLDETAILS_UNSIGNED)malloc(cChannels * sizeof(MIXERCONTROLDETAILS_UNSIGNED));

						mxcd.cbStruct = sizeof(mxcd);
						mxcd.dwControlID = pmxctrl->dwControlID;
						mxcd.cChannels = cChannels;
						mxcd.hwndOwner = (HWND)0;
						mxcd.cbDetails = sizeof(MIXERCONTROLDETAILS_UNSIGNED);
						mxcd.paDetails = (LPVOID)pUnsigned;

						mixerGetControlDetails((HMIXEROBJ)dwMixerHandle, &mxcd, MIXER_SETCONTROLDETAILSF_VALUE);
						percent = (pUnsigned[0].dwValue * 100) / (pmxctrl->Bounds.dwMaximum);
						free(pmxctrl);
						free(pUnsigned);
					} else free(pmxctrl);
				}
			}
			mixerClose((HMIXER)dwMixerHandle);
			if (mr != MMSYSERR_NOERROR) {
				ms_error("winsndcard_get_level: mixerClose failed. (0x%x)", mr);
				return -1;
			}
			return percent;
			break;
		default:
			ms_warning("winsndcard_get_level: unsupported command.");
			return -1;
	}
	return -1;
}

static void winsnddscard_set_source(MSSndCard *card, MSSndCardCapture source) {

	switch (source) {
		case MS_SND_CARD_MIC:
			break;
		case MS_SND_CARD_LINE:
			break;
	}
}

static int winsnddscard_set_control(MSSndCard *card, MSSndCardControlElem e, int val) {
	WinSndDsCard *d = (WinSndDsCard *)card->data;
	DWORD waveout_devid = WAVE_MAPPER;
	DWORD wavein_devid = WAVE_MAPPER;

	UINT uMixerID;
	DWORD dwMixerHandle;
	MIXERLINE MixerLine;
	MIXERLINE Line;
	UINT uLineIndex;

	MIXERLINECONTROLS mlc = {0};
	MIXERCONTROL mc = {0};
	MIXERCONTROLDETAILS mcd = {0};
	MIXERCONTROLDETAILS_BOOLEAN bMute;

	MMRESULT mr = MMSYSERR_NOERROR;

	GetWaveIdFromDSoundGUID(d->in_guid, &wavein_devid);
	GetWaveIdFromDSoundGUID(d->out_guid, &waveout_devid);

	switch (e) {
		case MS_SND_CARD_CAPTURE_MUTE:
			bMute.fValue = (val > 0);

			mr = mixerGetID((HMIXEROBJ)wavein_devid, &uMixerID, MIXER_OBJECTF_WAVEIN);
			if (mr != MMSYSERR_NOERROR) {
				ms_error("winsndcard_set_control: mixerGetID failed. (0x%x)", mr);
				return -1;
			}
			mr = mixerOpen((LPHMIXER)&dwMixerHandle, uMixerID, 0L, 0L, 0L);
			if (mr != MMSYSERR_NOERROR) {
				mixerClose((HMIXER)dwMixerHandle);
				ms_error("winsndcard_set_control: mixerOpen failed. (0x%x)", mr);
				return -1;
			}
			memset(&MixerLine, 0, sizeof(MIXERLINE));
			MixerLine.cbStruct = sizeof(MIXERLINE);
			MixerLine.dwComponentType = MIXERLINE_COMPONENTTYPE_DST_WAVEIN;
			mr = mixerGetLineInfo((HMIXEROBJ)dwMixerHandle, &MixerLine, MIXER_GETLINEINFOF_COMPONENTTYPE);
			if (mr != MMSYSERR_NOERROR) {
				mixerClose((HMIXER)dwMixerHandle);
				ms_error("winsndcard_set_control: mixerGetLineInfo failed. (0x%x)", mr);
				return -1;
			}
			/* ms_message("Name: %s\n", MixerLine.szName); */
			/* ms_message("Source Line: %d\n", MixerLine.dwSource); */
			/* ms_message("ComponentType: %d\n", MixerLine.dwComponentType); */

			mlc.cbStruct = sizeof(MIXERLINECONTROLS);
			mlc.dwLineID = MixerLine.dwLineID;
			mlc.dwControlType = MIXERCONTROL_CONTROLTYPE_MUTE; // MIXERCONTROL_CONTROLTYPE_VOLUME;
			mlc.cControls = 1;
			mlc.pamxctrl = &mc;
			mlc.cbmxctrl = sizeof(MIXERCONTROL);
			mr = mixerGetLineControls((HMIXEROBJ)dwMixerHandle, &mlc, MIXER_GETLINECONTROLSF_ONEBYTYPE);
			if (mr == MMSYSERR_NOERROR) {
				mcd.cbStruct = sizeof(MIXERCONTROLDETAILS);
				mcd.hwndOwner = 0;
				mcd.dwControlID = mc.dwControlID;
				mcd.paDetails = &bMute;
				mcd.cbDetails = sizeof(MIXERCONTROLDETAILS_BOOLEAN);
				mcd.cChannels = 1;
				mr = mixerSetControlDetails((HMIXEROBJ)dwMixerHandle, &mcd, MIXER_SETCONTROLDETAILSF_VALUE);

				if (mr == MMSYSERR_NOERROR) {
					return 0;
				}
				ms_error("winsndcard_set_control: mixerSetControlDetails failed. (0x%x)", mr);
				ms_warning("winsndcard_get_level: control the SRC_MICROPHONE instead");
			} else {
				ms_error("winsndcard_set_control: mixerGetLineControls failed. (0x%x)", mr);
				ms_warning("winsndcard_get_level: control the SRC_MICROPHONE instead");
			}

			for (uLineIndex = 0; uLineIndex < MixerLine.cConnections; uLineIndex++) {
				memset(&Line, 0, sizeof(MIXERLINE));
				Line.cbStruct = sizeof(MIXERLINE);
				Line.dwDestination = MixerLine.dwDestination;
				Line.dwSource = uLineIndex;
				mr = mixerGetLineInfo((HMIXEROBJ)dwMixerHandle, &Line, MIXER_GETLINEINFOF_LINEID);
				if (mr != MMSYSERR_NOERROR) {
					mixerClose((HMIXER)dwMixerHandle);
					ms_error("winsndcard_set_control: mixerGetLineInfo failed. (0x%x)", mr);
					return -1;
				}

				/* ms_message("Name: %s\n", MixerLine.szName); */
				/* ms_message("Source Line: %d\n", MixerLine.dwSource); */
				/* ms_message("LineID: %d\n", MixerLine.dwLineID); */
				/* ms_message("ComponentType: %d\n", MixerLine.dwComponentType); */

				memset(&Line, 0, sizeof(MIXERLINE));
				Line.cbStruct = sizeof(MIXERLINE);
				Line.dwDestination = MixerLine.dwDestination;
				Line.dwSource = uLineIndex;
				mr = mixerGetLineInfo((HMIXEROBJ)dwMixerHandle, &Line, MIXER_GETLINEINFOF_SOURCE);
				if (mr != MMSYSERR_NOERROR) {
					mixerClose((HMIXER)dwMixerHandle);
					ms_error("winsndcard_set_control: mixerGetLineInfo failed. (0x%x)", mr);
					return -1;
				}

				/* ms_message("Name: %s\n", MixerLine.szName); */
				/* ms_message("Source Line: %d\n", MixerLine.dwSource); */
				/* ms_message("LineID: %d\n", MixerLine.dwLineID); */
				/* ms_message("ComponentType: %d\n", MixerLine.dwComponentType); */

				if (MIXERLINE_COMPONENTTYPE_SRC_MICROPHONE == Line.dwComponentType) {
					/* unmute */
					/* Find a mute control, if any, of the microphone line  */

					LPMIXERCONTROL pmxctrl = (LPMIXERCONTROL)malloc(sizeof(MIXERCONTROL));
					MIXERLINECONTROLS mxlctrl = {sizeof(mxlctrl),      Line.dwLineID, MIXERCONTROL_CONTROLTYPE_MUTE, 1,
					                             sizeof(MIXERCONTROL), pmxctrl};
					if (!mixerGetLineControls((HMIXEROBJ)dwMixerHandle, &mxlctrl, MIXER_GETLINECONTROLSF_ONEBYTYPE)) {
						DWORD cChannels = Line.cChannels;
						LPMIXERCONTROLDETAILS_BOOLEAN pbool;
						MIXERCONTROLDETAILS mxcd;

						if (MIXERCONTROL_CONTROLF_UNIFORM & pmxctrl->fdwControl) cChannels = 1;
						pbool = (LPMIXERCONTROLDETAILS_BOOLEAN)malloc(cChannels * sizeof(MIXERCONTROLDETAILS_BOOLEAN));

						mxcd.cbStruct = sizeof(mxcd);
						mxcd.dwControlID = pmxctrl->dwControlID;
						mxcd.cChannels = cChannels;
						mxcd.hwndOwner = (HWND)0;
						mxcd.cbDetails = sizeof(MIXERCONTROLDETAILS_BOOLEAN);
						mxcd.paDetails = (LPVOID)pbool;

						mixerGetControlDetails((HMIXEROBJ)dwMixerHandle, &mxcd, MIXER_SETCONTROLDETAILSF_VALUE);
						/* Unmute the microphone line (for both channels) */
						pbool[0].fValue = pbool[cChannels - 1].fValue = val; /* 0 -> unmute; */
						mixerSetControlDetails((HMIXEROBJ)dwMixerHandle, &mxcd, MIXER_SETCONTROLDETAILSF_VALUE);
						free(pmxctrl);
						free(pbool);
					} else free(pmxctrl);
				}
			}
			mixerClose((HMIXER)dwMixerHandle);
			if (mr != MMSYSERR_NOERROR) {
				ms_error("winsndcard_set_control: mixerClose failed. (0x%x)", mr);
				return -1;
			}
			return 0;

		case MS_SND_CARD_MASTER_MUTE:
		case MS_SND_CARD_PLAYBACK_MUTE: {
			bMute.fValue = (val > 0);

			mr = mixerGetID((HMIXEROBJ)waveout_devid, &uMixerID, MIXER_OBJECTF_WAVEOUT);
			if (mr != MMSYSERR_NOERROR) {
				ms_error("winsndcard_set_control: mixerGetID failed. (0x%x)", mr);
				return -1;
			}
			mr = mixerOpen((LPHMIXER)&dwMixerHandle, uMixerID, 0L, 0L, 0L);
			if (mr != MMSYSERR_NOERROR) {
				mixerClose((HMIXER)dwMixerHandle);
				ms_error("winsndcard_set_control: mixerOpen failed. (0x%x)", mr);
				return -1;
			}
			memset(&MixerLine, 0, sizeof(MIXERLINE));
			MixerLine.cbStruct = sizeof(MIXERLINE);
			if (MS_SND_CARD_MASTER_MUTE == e) MixerLine.dwComponentType = MIXERLINE_COMPONENTTYPE_DST_SPEAKERS;
			else MixerLine.dwComponentType = MIXERLINE_COMPONENTTYPE_SRC_WAVEOUT;
			mr = mixerGetLineInfo((HMIXEROBJ)dwMixerHandle, &MixerLine, MIXER_GETLINEINFOF_COMPONENTTYPE);
			if (mr != MMSYSERR_NOERROR) {
				mixerClose((HMIXER)dwMixerHandle);
				ms_error("winsndcard_set_control: mixerSetControlDetails failed. (0x%x)", mr);
				return -1;
			}

			/* ms_message("Name: %s\n", MixerLine.szName); */
			/* ms_message("Source Line: %d\n", MixerLine.dwSource); */
			/* ms_message("ComponentType: %d\n", MixerLine.dwComponentType); */

			mlc.cbStruct = sizeof(MIXERLINECONTROLS);
			mlc.dwLineID = MixerLine.dwLineID;
			mlc.dwControlType = MIXERCONTROL_CONTROLTYPE_MUTE; // MIXERCONTROL_CONTROLTYPE_VOLUME;
			mlc.cControls = 1;
			mlc.pamxctrl = &mc;
			mlc.cbmxctrl = sizeof(MIXERCONTROL);
			mr = mixerGetLineControls((HMIXEROBJ)dwMixerHandle, &mlc, MIXER_GETLINECONTROLSF_ONEBYTYPE);

			mcd.cbStruct = sizeof(MIXERCONTROLDETAILS);
			mcd.hwndOwner = 0;
			mcd.dwControlID = mc.dwControlID;
			mcd.paDetails = &bMute;
			mcd.cbDetails = sizeof(MIXERCONTROLDETAILS_BOOLEAN);
			mcd.cChannels = 1;
			mr = mixerSetControlDetails((HMIXEROBJ)dwMixerHandle, &mcd, MIXER_SETCONTROLDETAILSF_VALUE);

			if (mr != MMSYSERR_NOERROR) {
				ms_error("winsndcard_set_control: mixerSetControlDetails failed. (0x%x)", mr);
				return -1;
			}
			return 0;
		} break;
		default:
			ms_warning("winsndcard_set_control: unsupported command.");
	}
	return -1;
}

static int winsnddscard_get_control(MSSndCard *card, MSSndCardControlElem e) {
	WinSndDsCard *d = (WinSndDsCard *)card->data;
	return -1;
}

static void winsnddscard_init(MSSndCard *card) {
	WinSndDsCard *c = (WinSndDsCard *)ms_new(WinSndDsCard, 1);
	c->removed = 0;
	card->data = c;
}

static void winsnddscard_uninit(MSSndCard *card) {
	ms_free(card->data);
}

static void winsnddscard_detect(MSSndCardManager *m);
static MSSndCard *winsnddscard_dup(MSSndCard *obj);
static void winsnddscard_unload(MSSndCardManager *m);

MSSndCardDesc winsndds_card_desc = {
    "DirectSound",          winsnddscard_detect,     winsnddscard_init,        winsnddscard_set_level,
    winsnddscard_get_level, winsnddscard_set_source, winsnddscard_set_control, NULL,
    ms_winsndds_read_new,   ms_winsndds_write_new,   winsnddscard_uninit,      winsnddscard_dup,
    winsnddscard_unload};

static MSSndCard *winsnddscard_dup(MSSndCard *obj) {
	MSSndCard *card = ms_snd_card_new(&winsndds_card_desc);
	card->name = ms_strdup(obj->name);
	card->data = ms_new(WinSndDsCard, 1);
	memcpy(card->data, obj->data, sizeof(WinSndDsCard));
	return card;
}

static MSSndCard *winsnddscard_new(const char *name, LPGUID lpguid, int in_dev, int out_dev, unsigned cap) {
	MSSndCard *card = ms_snd_card_new(&winsndds_card_desc);
	WinSndDsCard *d = (WinSndDsCard *)card->data;
	card->name = ms_strdup(name);
	d->in_devid = in_dev;
	d->out_devid = out_dev;
	card->capabilities = cap;
	memset(&d->out_guid, 0, sizeof(GUID));
	memset(&d->in_guid, 0, sizeof(GUID));
	if (out_dev != -1) {
		if (lpguid != NULL) memcpy(&d->out_guid, lpguid, sizeof(GUID));
	} else {
		if (lpguid != NULL) memcpy(&d->in_guid, lpguid, sizeof(GUID));
	}
	return card;
}

static void add_or_update_card(
    MSSndCardManager *m, const char *name, LPGUID lpguid, int indev, int outdev, unsigned int capability) {
	MSSndCard *card;
	const bctbx_list_t *elem = ms_snd_card_manager_get_list(m);
	for (; elem != NULL; elem = elem->next) {
		card = (MSSndCard *)elem->data;
		if (strcmp(card->desc->driver_type, winsndds_card_desc.driver_type) == 0 && strcmp(card->name, name) == 0) {
			/*update already entered card */
			WinSndDsCard *d = (WinSndDsCard *)card->data;
			card->capabilities |= capability;
			if (indev != -1) d->in_devid = indev;
			if (outdev != -1) d->out_devid = outdev;

			if (outdev != -1) {
				if (lpguid != NULL) memcpy(&d->out_guid, lpguid, sizeof(GUID));
				else memset(&d->out_guid, 0, sizeof(GUID));
			}
			if (indev != -1) {
				if (lpguid != NULL) memcpy(&d->in_guid, lpguid, sizeof(GUID));
				else memset(&d->in_guid, 0, sizeof(GUID));
			}
			/* if (d->in_devid!=-1 && d->out_devid!=-1) */
			/*	ms_message("DS: new full duplex card %s", name); */
			d->removed = 0;
			return;
		}
	}
	/* add this new card:*/
	ms_snd_card_manager_add_card(m, winsnddscard_new(name, lpguid, indev, outdev, capability));
}

static BOOL CALLBACK enumerate_capture_devices_callback(LPGUID lpGUID,
                                                        LPCWSTR lpszDesc,
                                                        LPCWSTR lpszDrvName,
                                                        LPVOID lpContext) {
	MSSndCardManager *m = (MSSndCardManager *)lpContext;
	static int dev_index = 0;

	if (lpGUID == NULL) /* primary device */
	{
		char szName[256];
		wchar_t snd_card_name[256];
		swprintf(snd_card_name, 256, L"%s", lpszDesc);
		WideCharToMultiByte(CP_UTF8, 0, snd_card_name, -1, szName, 256, 0, 0);

		add_or_update_card(m, szName, lpGUID, dev_index, -1, MS_SND_CARD_CAP_CAPTURE);
		dev_index++;
	} else {
		char szName[256];
		wchar_t snd_card_name[256];
		swprintf(snd_card_name, 256, L"%s", lpszDesc);
		WideCharToMultiByte(CP_UTF8, 0, snd_card_name, -1, szName, 256, 0, 0);

		add_or_update_card(m, szName, lpGUID, dev_index, -1, MS_SND_CARD_CAP_CAPTURE);
		dev_index++;
	}

	return true;
}

static BOOL CALLBACK enumerate_playback_devices_callback(LPGUID lpGUID,
                                                         LPCWSTR lpszDesc,
                                                         LPCWSTR lpszDrvName,
                                                         LPVOID lpContext) {
	MSSndCardManager *m = (MSSndCardManager *)lpContext;
	static int dev_index = 0;

	if (lpGUID == NULL) /* primary device */
	{
		char szName[256];
		wchar_t snd_card_name[256];
		swprintf(snd_card_name, 256, L"%s", lpszDesc);
		WideCharToMultiByte(CP_UTF8, 0, snd_card_name, -1, szName, 256, 0, 0);

		add_or_update_card(m, szName, lpGUID, -1, dev_index, MS_SND_CARD_CAP_PLAYBACK);
		dev_index++;
	} else {
		char szName[256];
		wchar_t snd_card_name[256];
		swprintf(snd_card_name, 256, L"%s", lpszDesc);
		WideCharToMultiByte(CP_UTF8, 0, snd_card_name, -1, szName, 256, 0, 0);

		add_or_update_card(m, szName, lpGUID, -1, dev_index, MS_SND_CARD_CAP_PLAYBACK);
		dev_index++;
	}

	return true;
}

static void _winsnddscard_detect(MSSndCardManager *m) {
	MMRESULT mr = NOERROR;

	if (ms_lib_instance == NULL) {
		ms_lib_instance = LoadLibrary(L"dsound.dll");
		if (ms_lib_instance == NULL) {
			/* error */
			ms_debug("winsnddscard_detect: no support for dsound (missing dsound.dll)\n");
			return;
		}

		ms_DllGetClassObject =
		    (HRESULT(WINAPI *)(REFCLSID, REFIID, LPVOID *))GetProcAddress(ms_lib_instance, "DllGetClassObject");

		ms_DirectSoundCreate =
		    (HRESULT(WINAPI *)(LPGUID, LPDIRECTSOUND *, LPUNKNOWN))GetProcAddress(ms_lib_instance, "DirectSoundCreate");

		ms_DirectSoundEnumerate =
		    (HRESULT(WINAPI *)(LPDSENUMCALLBACKW, LPVOID))GetProcAddress(ms_lib_instance, "DirectSoundEnumerateW");

		ms_DirectSoundCaptureCreate = (HRESULT(WINAPI *)(LPGUID, LPDIRECTSOUNDCAPTURE *, LPUNKNOWN))GetProcAddress(
		    ms_lib_instance, "DirectSoundCaptureCreate");

		ms_DirectSoundCaptureEnumerate = (HRESULT(WINAPI *)(LPDSENUMCALLBACKW, LPVOID))GetProcAddress(
		    ms_lib_instance, "DirectSoundCaptureEnumerateW");

		ms_DirectSoundFullDuplexCreate =
		    (HRESULT(WINAPI *)(LPCGUID, LPCGUID, LPCDSCBUFFERDESC, LPCDSBUFFERDESC, HWND, DWORD,
		                       LPDIRECTSOUNDFULLDUPLEX *, LPDIRECTSOUNDCAPTUREBUFFER8 *, LPDIRECTSOUNDBUFFER8 *,
		                       LPUNKNOWN))GetProcAddress(ms_lib_instance, "DirectSoundFullDuplexCreate");

		if (ms_DllGetClassObject == NULL || ms_DirectSoundCreate == NULL || ms_DirectSoundEnumerate == NULL ||
		    ms_DirectSoundCaptureEnumerate == NULL || ms_DirectSoundCaptureCreate == NULL) {
			/* error */
			ms_debug("winsnddscard_detect: no support for dsound\n");
			return;
		}
	}

	ms_DirectSoundCaptureEnumerate((LPDSENUMCALLBACK)enumerate_capture_devices_callback, (void *)m);
	ms_DirectSoundEnumerate((LPDSENUMCALLBACK)enumerate_playback_devices_callback, (void *)m);
}

static void deactivate_removed_cards(MSSndCardManager *m) {
	MSSndCard *card;
	const bctbx_list_t *elem = ms_snd_card_manager_get_list(m);
	for (; elem != NULL; elem = elem->next) {
		card = (MSSndCard *)elem->data;
		if (strcmp(card->desc->driver_type, winsndds_card_desc.driver_type) == 0) {
			/*mark all cards as potentially removed, detect will check them immediately after */
			WinSndDsCard *d = (WinSndDsCard *)card->data;
			if (d->removed) card->capabilities = 0;
		}
	}
}

static void mark_as_removed(MSSndCardManager *m) {
	MSSndCard *card;
	const bctbx_list_t *elem = ms_snd_card_manager_get_list(m);
	for (; elem != NULL; elem = elem->next) {
		card = (MSSndCard *)elem->data;
		if (strcmp(card->desc->driver_type, winsndds_card_desc.driver_type) == 0) {
			/*mark all cards as potentially removed, detect will check them immediately after */
			WinSndDsCard *d = (WinSndDsCard *)card->data;
			d->removed = 1;
		}
	}
}

static ms_thread_t poller_thread = NULL;
static bool_t poller_running = TRUE;

static void *new_device_polling_thread(void *ignore) {
	MSSndCardManager *m;
	/*check for new devices every 2 seconds*/
	while (poller_running) {
		ms_sleep(5);
		if (poller_running) {
			m = ms_snd_card_manager_get();
			if (!m) break;
			mark_as_removed(m);
			_winsnddscard_detect(m);
			deactivate_removed_cards(m);
		}
	}
	return NULL;
}

static void stop_poller() {
	poller_running = FALSE;
	ms_thread_join(poller_thread, NULL);
	poller_thread = NULL;
}

static void winsnddscard_unload(MSSndCardManager *m) {
	stop_poller();
}

static void winsnddscard_detect(MSSndCardManager *m) {
	_winsnddscard_detect(m);
	if (poller_thread == NULL) ms_thread_create(&poller_thread, NULL, new_device_polling_thread, NULL);
}

typedef struct WinSndDs {
	int dev_id;
	GUID in_guid;
	GUID out_guid;

	ms_thread_t thread;
	ms_mutex_t thread_lock;
	ms_cond_t thread_cond;
	bool_t thread_running;

	MSBufferizer output_buff;
	LPDIRECTSOUNDFULLDUPLEX lpDirectSoundFullDuplex;
	LPDIRECTSOUND lpDirectSound;
	LPDIRECTSOUNDBUFFER lpDirectSoundOutputBuffer;
	double dsw_framesWritten;
	UINT writeOffset; /* last read position */

	LPDIRECTSOUNDCAPTURE lpDirectSoundCapture;
	LPDIRECTSOUNDCAPTUREBUFFER lpDirectSoundInputBuffer;
	UINT readOffset; /* last read position */

	int framesPerDSBuffer;

	WAVEFORMATEX wfx;
	queue_t rq;
	ms_mutex_t mutex;
	uint64_t bytes_read;
	unsigned int nbufs_playing;

	int32_t stat_input;
	int32_t stat_output;
	int32_t stat_notplayed;

} WinSndDs;

void *winsndds_read_thread(void *arg) {
	WinSndDs *d = (WinSndDs *)arg;

	ms_mutex_lock(&d->thread_lock);
	ms_cond_signal(&d->thread_cond);
	ms_mutex_unlock(&d->thread_lock);

	while (d->thread_running) {
		HRESULT hr;
		DWORD capturePos;
		DWORD readPos;
		long filled = 0;
		long bytesFilled = 0;
		LPBYTE lpInBuf1 = NULL;
		LPBYTE lpInBuf2 = NULL;
		DWORD dwInSize1 = 0;
		DWORD dwInSize2 = 0;

		hr = IDirectSoundCaptureBuffer_GetCurrentPosition(d->lpDirectSoundInputBuffer, &capturePos, &readPos);
		if (hr != DS_OK) {
			continue;
		}

		filled = readPos - d->readOffset;
		if (filled < 0) filled += d->framesPerDSBuffer;
		bytesFilled = filled;

		hr = IDirectSoundCaptureBuffer_Lock(d->lpDirectSoundInputBuffer, d->readOffset, bytesFilled, (void **)&lpInBuf1,
		                                    &dwInSize1, (void **)&lpInBuf2, &dwInSize2, 0);
		if (hr != DS_OK) {
			Sleep(10);
			continue;
		}

		if (dwInSize1 == 0) {
			Sleep(10);
		} else if (dwInSize1 >= bytesFilled) {
			mblk_t *m = allocb(bytesFilled, 0);
			memcpy(m->b_rptr, lpInBuf1, bytesFilled);
			m->b_wptr += bytesFilled;
			ms_mutex_lock(&d->mutex);
			putq(&d->rq, m);
			ms_mutex_unlock(&d->mutex);
			d->bytes_read += bytesFilled;
			/* ms_message("bytesFilled=%i\n",bytesFilled); */
		} else {
			mblk_t *m = allocb(bytesFilled, 0);
			memcpy(m->b_rptr, lpInBuf1, dwInSize1);
			memcpy(m->b_rptr + dwInSize1, lpInBuf2, dwInSize2);
			m->b_wptr += bytesFilled;
			ms_mutex_lock(&d->mutex);
			putq(&d->rq, m);
			ms_mutex_unlock(&d->mutex);
			d->bytes_read += bytesFilled;
			/* ms_message("bytesFilled=%i\n",bytesFilled); */
		}

		d->readOffset = (d->readOffset + bytesFilled) % d->framesPerDSBuffer;

		IDirectSoundCaptureBuffer_Unlock(d->lpDirectSoundInputBuffer, lpInBuf1, dwInSize1, lpInBuf2, dwInSize2);
	}

	ms_mutex_lock(&d->thread_lock);
	ms_cond_signal(&d->thread_cond);
	ms_mutex_unlock(&d->thread_lock);
	ms_thread_exit(NULL);
	return NULL;
}

static void winsndds_apply_settings(WinSndDs *d) {
	d->wfx.nBlockAlign = d->wfx.nChannels * d->wfx.wBitsPerSample / 8;
	d->wfx.nAvgBytesPerSec = d->wfx.nSamplesPerSec * d->wfx.nBlockAlign;
}

static uint64_t winsndds_get_cur_time(void *data) {
	WinSndDs *d = (WinSndDs *)data;
	uint64_t curtime = (d->bytes_read * 1000) / (uint64_t)d->wfx.nAvgBytesPerSec;
	return curtime;
}

static void winsndds_init(MSFilter *f) {
	WinSndDs *d = (WinSndDs *)ms_new0(WinSndDs, 1);
	d->wfx.wFormatTag = WAVE_FORMAT_PCM;
	d->wfx.cbSize = 0;
	d->wfx.nAvgBytesPerSec = 16000;
	d->wfx.nBlockAlign = 2;
	d->wfx.nChannels = 1;
	d->wfx.nSamplesPerSec = 8000;
	d->wfx.wBitsPerSample = 16;
	qinit(&d->rq);
	ms_mutex_init(&d->mutex, NULL);
	f->data = d;

	d->stat_input = 0;
	d->stat_output = 0;
	d->stat_notplayed = 0;

	d->framesPerDSBuffer = 320 * (8000 / 1000);

	d->thread = NULL;
	ms_mutex_init(&d->thread_lock, NULL);
	ms_cond_init(&d->thread_cond, NULL);
	d->thread_running = FALSE;

	ms_bufferizer_init(&d->output_buff);
}

static void winsndds_uninit(MSFilter *f) {
	WinSndDs *d = (WinSndDs *)f->data;

	d->thread = NULL;
	d->thread_running = FALSE;
	ms_cond_destroy(&d->thread_cond);
	ms_mutex_destroy(&d->thread_lock);
	ms_bufferizer_uninit(&d->output_buff);

	flushq(&d->rq, 0);
	ms_mutex_destroy(&d->mutex);
	ms_free(f->data);
}

static void winsndds_read_preprocess(MSFilter *f) {
	WinSndDs *d = (WinSndDs *)f->data;
	DSCBUFFERDESC captureDesc;
	HRESULT hr;

	d->stat_input = 0;
	d->stat_output = 0;
	d->stat_notplayed = 0;

	d->framesPerDSBuffer = d->wfx.nAvgBytesPerSec / 4;
	winsndds_apply_settings(d);
	ms_message("full duplex and echo canceller! (%x)", d->lpDirectSound);
	ms_DirectSoundCaptureCreate(&d->in_guid, &d->lpDirectSoundCapture, NULL);

	if (d->lpDirectSoundCapture == NULL) return;

	ZeroMemory(&captureDesc, sizeof(DSCBUFFERDESC));
	captureDesc.dwSize = sizeof(DSCBUFFERDESC);
	captureDesc.dwFlags = 0;
	captureDesc.dwBufferBytes = d->framesPerDSBuffer;
	captureDesc.lpwfxFormat = &d->wfx;

	if ((hr = IDirectSoundCapture_CreateCaptureBuffer(d->lpDirectSoundCapture, &captureDesc,
	                                                  &d->lpDirectSoundInputBuffer, NULL)) != DS_OK) {
		return;
	}
	d->readOffset = 0;

	hr = IDirectSoundCaptureBuffer_Start(d->lpDirectSoundInputBuffer, DSCBSTART_LOOPING);

	ms_mutex_lock(&f->ticker->lock);
	ms_ticker_set_time_func(f->ticker, winsndds_get_cur_time, d);
	ms_mutex_unlock(&f->ticker->lock);

	d->thread_running = TRUE;
	ms_thread_create(&d->thread, NULL, winsndds_read_thread, d);
	ms_mutex_lock(&d->thread_lock);
	ms_cond_wait(&d->thread_cond, &d->thread_lock);
	ms_mutex_unlock(&d->thread_lock);

	return;
}

static void winsndds_read_postprocess(MSFilter *f) {
	WinSndDs *d = (WinSndDs *)f->data;

	if (d->thread_running == TRUE) {
		ms_mutex_lock(&d->thread_lock);
		d->thread_running = FALSE;
		ms_cond_wait(&d->thread_cond, &d->thread_lock);
		ms_mutex_unlock(&d->thread_lock);
		ms_thread_join(d->thread, NULL);

		ms_mutex_lock(&f->ticker->lock);
		ms_ticker_set_time_func(f->ticker, NULL, NULL);
		ms_mutex_unlock(&f->ticker->lock);
	}

	if (d->lpDirectSoundInputBuffer) {
		IDirectSoundCaptureBuffer_Stop(d->lpDirectSoundInputBuffer);
		IDirectSoundCaptureBuffer_Release(d->lpDirectSoundInputBuffer);
		d->lpDirectSoundInputBuffer = NULL;
	}

	if (d->lpDirectSoundCapture) {
		IDirectSoundCapture_Release(d->lpDirectSoundCapture);
		d->lpDirectSoundCapture = NULL;
	}

	ms_message("Shutting down sound device (playing: %i) (input-output: %i) (notplayed: %i)", d->nbufs_playing,
	           d->stat_input - d->stat_output, d->stat_notplayed);
	flushq(&d->rq, 0);
}

static void winsndds_read_process(MSFilter *f) {
	WinSndDs *d = (WinSndDs *)f->data;
	mblk_t *m;

	ms_mutex_lock(&d->mutex);
	while ((m = getq(&d->rq)) != NULL) {
		ms_queue_put(f->outputs[0], m);
	}
	ms_mutex_unlock(&d->mutex);
}

static void winsndds_write_preprocess(MSFilter *f) {
	WinSndDs *d = (WinSndDs *)f->data;

	DWORD dwDataLen;
	DWORD playCursor;
	HWND hWnd;
	HRESULT hr;
	LPDIRECTSOUNDBUFFER pPrimaryBuffer;
	DSBUFFERDESC primaryDesc;
	DSBUFFERDESC secondaryDesc;
	unsigned char *pDSBuffData;
	DWORD outputBufferWriteOffsetBytes;

	bctbx_list_t *filters = NULL;
	MSFilter *f_capture_filter = NULL;
	WinSndDs *d_capture_filter = NULL;

	filters = ms_filter_find_neighbours(f);
	if (filters != NULL) {
		bctbx_list_t *it;
		/* search for another winsndds filter */
		for (it = filters; it != NULL; it = it->next) {
			f_capture_filter = (MSFilter *)it->data;
			if (f_capture_filter->desc->id == MS_WINSNDDS_READ_ID) {
				/* found */
				d_capture_filter = (WinSndDs *)f_capture_filter->data;
				break;
			}
			f_capture_filter = NULL;
		}
		bctbx_list_free(filters);
	}

	d->stat_input = 0;
	d->stat_output = 0;
	d->stat_notplayed = 0;

	d->framesPerDSBuffer = d->wfx.nAvgBytesPerSec / 4;
	winsndds_apply_settings(d);

	if (d_capture_filter != NULL && d_capture_filter->lpDirectSoundCapture != NULL &&
	    IsEqualIID(d_capture_filter->in_guid, d->in_guid)) {
		DSCBUFFERDESC captureDesc;

		winsndds_read_postprocess(f_capture_filter);

		DSCEFFECTDESC dscfx[1];
		ZeroMemory(&dscfx[0], sizeof(DSCEFFECTDESC));
		dscfx[0].dwSize = sizeof(DSCEFFECTDESC);
		dscfx[0].dwFlags = DSCFX_LOCSOFTWARE;
		dscfx[0].guidDSCFXClass = GUID_DSCFX_CLASS_AEC;
		dscfx[0].guidDSCFXInstance = GUID_DSCFX_MS_AEC;
		dscfx[0].dwReserved1 = 0;
		dscfx[0].dwReserved2 = 0;

		d_capture_filter->framesPerDSBuffer = d_capture_filter->wfx.nAvgBytesPerSec / 4;
		winsndds_apply_settings(d_capture_filter);

		ZeroMemory(&captureDesc, sizeof(DSCBUFFERDESC));
		captureDesc.dwSize = sizeof(DSCBUFFERDESC);
		captureDesc.dwFlags = DSCBCAPS_CTRLFX;
		captureDesc.dwBufferBytes = d_capture_filter->framesPerDSBuffer;
		captureDesc.lpwfxFormat = &d_capture_filter->wfx;
		captureDesc.dwFXCount = 1;
		captureDesc.lpDSCFXDesc = dscfx;

		ZeroMemory(&secondaryDesc, sizeof(DSBUFFERDESC));
		secondaryDesc.dwSize = sizeof(DSBUFFERDESC);
		secondaryDesc.dwFlags = DSBCAPS_GLOBALFOCUS | DSBCAPS_GETCURRENTPOSITION2 | DSBCAPS_LOCSOFTWARE;
		secondaryDesc.dwBufferBytes = d->framesPerDSBuffer;
		secondaryDesc.lpwfxFormat = &d->wfx;

		hWnd = GetDesktopWindow();
		hr = ms_DirectSoundFullDuplexCreate(&d_capture_filter->in_guid, &d->out_guid, &captureDesc, &secondaryDesc,
		                                    hWnd, DSSCL_NORMAL, &d->lpDirectSoundFullDuplex,
		                                    (LPDIRECTSOUNDCAPTUREBUFFER8 *)&d_capture_filter->lpDirectSoundInputBuffer,
		                                    (LPDIRECTSOUNDBUFFER8 *)&d->lpDirectSoundOutputBuffer, NULL);

		if (hr != DS_OK) {
			ms_message("full duplex and echo canceller: disabled!");
			captureDesc.dwFlags = 0;
			captureDesc.dwFXCount = 0;
			captureDesc.lpDSCFXDesc = NULL;

			hr = ms_DirectSoundFullDuplexCreate(
			    &d_capture_filter->in_guid, &d->out_guid, &captureDesc, &secondaryDesc, hWnd, DSSCL_NORMAL,
			    &d->lpDirectSoundFullDuplex, (LPDIRECTSOUNDCAPTUREBUFFER8 *)&d_capture_filter->lpDirectSoundInputBuffer,
			    (LPDIRECTSOUNDBUFFER8 *)&d->lpDirectSoundOutputBuffer, NULL);
		}
		if (hr != DS_OK) {
			ms_message("full duplex and echo canceller: disabled!");
			return;
		}
		ms_message("full duplex and echo canceller: activated!");

		d_capture_filter->readOffset = 0;

		hr = IDirectSoundCaptureBuffer_Start(d_capture_filter->lpDirectSoundInputBuffer, DSCBSTART_LOOPING);

		ms_mutex_lock(&f->ticker->lock);
		ms_ticker_set_time_func(f_capture_filter->ticker, winsndds_get_cur_time, d_capture_filter);
		ms_mutex_unlock(&f->ticker->lock);

		d_capture_filter->thread_running = TRUE;
		ms_thread_create(&d_capture_filter->thread, NULL, winsndds_read_thread, d_capture_filter);
		ms_mutex_lock(&d_capture_filter->thread_lock);
		ms_cond_wait(&d_capture_filter->thread_cond, &d_capture_filter->thread_lock);
		ms_mutex_unlock(&d_capture_filter->thread_lock);
	} else {
		ms_DirectSoundCreate(&d->out_guid, &d->lpDirectSound, NULL);

		if (d->lpDirectSound == NULL) {
			return;
		}

		hWnd = GetDesktopWindow();
		if ((hr = IDirectSound_SetCooperativeLevel(d->lpDirectSound, hWnd, DSSCL_PRIORITY)) !=
		    DS_OK) // DSSCL_EXCLUSIVE)) != DS_OK)
		{
			return;
		}

		ZeroMemory(&primaryDesc, sizeof(DSBUFFERDESC));
		primaryDesc.dwSize = sizeof(DSBUFFERDESC);
		primaryDesc.dwFlags = DSBCAPS_GETCURRENTPOSITION2 | DSBCAPS_PRIMARYBUFFER;
		primaryDesc.dwBufferBytes = 0;
		primaryDesc.lpwfxFormat = NULL;
		if ((hr = IDirectSound_CreateSoundBuffer(d->lpDirectSound, &primaryDesc, &pPrimaryBuffer, NULL)) != DS_OK) {
			return;
		}

		if ((hr = IDirectSoundBuffer_SetFormat(pPrimaryBuffer, &d->wfx)) != DS_OK) {
			return;
		}
		IDirectSoundBuffer_Release(pPrimaryBuffer);

		ZeroMemory(&secondaryDesc, sizeof(DSBUFFERDESC));
		secondaryDesc.dwSize = sizeof(DSBUFFERDESC);
		secondaryDesc.dwFlags = DSBCAPS_GLOBALFOCUS | DSBCAPS_GETCURRENTPOSITION2;
		secondaryDesc.dwBufferBytes = d->framesPerDSBuffer;
		secondaryDesc.lpwfxFormat = &d->wfx;
		if ((hr = IDirectSound_CreateSoundBuffer(d->lpDirectSound, &secondaryDesc, &d->lpDirectSoundOutputBuffer,
		                                         NULL)) != DS_OK) {
			return;
		}
	}

	if ((hr = IDirectSoundBuffer_Lock(d->lpDirectSoundOutputBuffer, 0, d->framesPerDSBuffer, (LPVOID *)&pDSBuffData,
	                                  &dwDataLen, NULL, 0, 0)) != DS_OK) {
		return;
	}

	ZeroMemory(pDSBuffData, dwDataLen);
	if ((hr = IDirectSoundBuffer_Unlock(d->lpDirectSoundOutputBuffer, pDSBuffData, dwDataLen, NULL, 0)) != DS_OK) {
		return;
	}

	hr =
	    IDirectSoundBuffer_GetCurrentPosition(d->lpDirectSoundOutputBuffer, &playCursor, &outputBufferWriteOffsetBytes);
	if (hr != DS_OK) {
		return;
	}

	hr = IDirectSoundBuffer_SetCurrentPosition(d->lpDirectSoundOutputBuffer, 0);
	if (hr != DS_OK) {
		return;
	}

	hr = IDirectSoundBuffer_Play(d->lpDirectSoundOutputBuffer, 0, 0, DSBPLAY_LOOPING);
	if (hr != DS_OK) {
		return;
	}
	d->writeOffset = -1;

	return;
}

static void winsndds_write_postprocess(MSFilter *f) {
	WinSndDs *d = (WinSndDs *)f->data;

	if (d->lpDirectSoundOutputBuffer) {
		IDirectSoundBuffer_Stop(d->lpDirectSoundOutputBuffer);
		IDirectSoundBuffer_Release(d->lpDirectSoundOutputBuffer);
		d->lpDirectSoundOutputBuffer = NULL;
	}

	if (d->lpDirectSound) {
		IDirectSound_Release(d->lpDirectSound);
		d->lpDirectSound = NULL;
	}

	if (d->lpDirectSoundFullDuplex) {
		IDirectSoundFullDuplex_Release(d->lpDirectSoundFullDuplex);
		d->lpDirectSoundFullDuplex = NULL;
	}

	ms_message("Shutting down sound device (playing: %i) (input-output: %i) (notplayed: %i)", d->nbufs_playing,
	           d->stat_input - d->stat_output, d->stat_notplayed);
	d->writeOffset = -1;
}

static void winsndds_write_process(MSFilter *f) {
	WinSndDs *d = (WinSndDs *)f->data;
	int discarded = 0;
	DWORD dwStatus;
	HRESULT hr;

	if (d->lpDirectSoundOutputBuffer == NULL) {
		ms_queue_flush(f->inputs[0]);
		return;
	}

	ms_bufferizer_put_from_queue(&d->output_buff, f->inputs[0]);

	if (d->writeOffset == -1) {
		if (ms_bufferizer_get_avail(&d->output_buff) >= d->framesPerDSBuffer) {
			DWORD playCursor;
			DWORD outputBufferWriteOffsetBytes;
			IDirectSoundBuffer_GetCurrentPosition(d->lpDirectSoundOutputBuffer, &playCursor,
			                                      &outputBufferWriteOffsetBytes);
			d->writeOffset = outputBufferWriteOffsetBytes;
		} else return;
	}

	DWORD current_playOffset;
	long msize_max = 0;
	DWORD currentWriteOffset;
	IDirectSoundBuffer_GetCurrentPosition(d->lpDirectSoundOutputBuffer, &current_playOffset, &currentWriteOffset);

	msize_max = current_playOffset - currentWriteOffset;
	if (msize_max < 0) msize_max += d->framesPerDSBuffer;

	/* write from d->writeOffset up to current_playOffset */
	msize_max = current_playOffset - d->writeOffset;
	if (msize_max < 0) msize_max += d->framesPerDSBuffer;

	// ms_message("DS information: last_writeOffset=%i current_playOffset=%i current_writeOffset=%i max_writable=%i",
	//	d->writeOffset, current_playOffset, currentWriteOffset, msize_max);

	hr = IDirectSoundBuffer_GetStatus(d->lpDirectSoundOutputBuffer, &dwStatus);
	if (dwStatus & DSBSTATUS_BUFFERLOST) {
		hr = IDirectSoundBuffer_Restore(d->lpDirectSoundOutputBuffer);
		d->writeOffset = 0;
		ms_message("DSBSTATUS_BUFFERLOST: restoring buffer");
	}

	if (msize_max == 0) return;
	int msize = d->framesPerDSBuffer / 4;
	if (msize > msize_max) msize = msize_max;
	while (ms_bufferizer_get_avail(&d->output_buff) >= msize) {
		LPBYTE lpOutBuf1 = NULL;
		LPBYTE lpOutBuf2 = NULL;
		DWORD dwOutSize1 = 0;
		DWORD dwOutSize2 = 0;
		char input[15360];

		hr = IDirectSoundBuffer_Lock(d->lpDirectSoundOutputBuffer, d->writeOffset, msize, (void **)&lpOutBuf1,
		                             &dwOutSize1, (void **)&lpOutBuf2, &dwOutSize2, 0); /* DSBLOCK_FROMWRITECURSOR); */
		if (hr != DS_OK) {
			ms_error("DirectSound IDirectSoundBuffer_Lock failed, hresult = 0x%x\n", hr);
			break;
		}

		if (dwOutSize1 == 0) {
			ms_error("no free room to play sample\n");
		} else if (dwOutSize1 + dwOutSize2 != msize) {
			ms_bufferizer_read(&d->output_buff, (uint8_t *)input, dwOutSize1 + dwOutSize2);
			memcpy(lpOutBuf1, input, dwOutSize1);
			memcpy(lpOutBuf2, input + dwOutSize1, dwOutSize2);
		} else if (dwOutSize1 >= msize) {
			ms_bufferizer_read(&d->output_buff, (uint8_t *)input, msize);
			memcpy(lpOutBuf1, input, msize);
		} else {
			ms_bufferizer_read(&d->output_buff, (uint8_t *)input, msize);
			memcpy(lpOutBuf1, input, dwOutSize1);
			memcpy(lpOutBuf2, input + dwOutSize1, dwOutSize2);
		}

		d->writeOffset = (d->writeOffset + dwOutSize1 + dwOutSize2) % d->framesPerDSBuffer;
		msize_max = msize_max - (dwOutSize1 + dwOutSize2);
		if (msize > msize_max) msize = msize_max;
		IDirectSoundBuffer_Unlock(d->lpDirectSoundOutputBuffer, lpOutBuf1, dwOutSize1, lpOutBuf2, dwOutSize2);
		if (dwOutSize1 == 0) break;
		if (dwOutSize1 + dwOutSize2 != msize) break;
	}
	if (msize == 0) {
		if (ms_bufferizer_get_avail(&d->output_buff) >= 3 * d->wfx.nSamplesPerSec / 50) {
			ms_warning("Removing extra data for sound card %i", ms_bufferizer_get_avail(&d->output_buff));
			ms_bufferizer_uninit(&d->output_buff);
			ms_bufferizer_init(&d->output_buff);
		}
	}

	if (discarded > 0)
		ms_warning("Extra data for sound card removed (%i buf), (playing: %i) (input-output: %i)", discarded,
		           d->nbufs_playing, d->stat_input - d->stat_output);
}

static int get_rate(MSFilter *f, void *arg) {
	WinSndDs *d = (WinSndDs *)f->data;
	*((int *)arg) = d->wfx.nSamplesPerSec;
	return 0;
}

static int set_rate(MSFilter *f, void *arg) {
	WinSndDs *d = (WinSndDs *)f->data;
	d->wfx.nSamplesPerSec = *((int *)arg);
	return 0;
}

static int set_nchannels(MSFilter *f, void *arg) {
	WinSndDs *d = (WinSndDs *)f->data;
	d->wfx.nChannels = *((int *)arg);
	return 0;
}

static int winsndds_get_stat_input(MSFilter *f, void *arg) {
	WinSndDs *d = (WinSndDs *)f->data;
	return d->stat_input;
}

static int winsndds_get_stat_ouptut(MSFilter *f, void *arg) {
	WinSndDs *d = (WinSndDs *)f->data;

	return d->stat_output;
}

static int winsndds_get_stat_discarded(MSFilter *f, void *arg) {
	WinSndDs *d = (WinSndDs *)f->data;

	return d->stat_notplayed;
}

static MSFilterMethod winsndds_methods[] = {{MS_FILTER_GET_SAMPLE_RATE, get_rate},
                                            {MS_FILTER_SET_SAMPLE_RATE, set_rate},
                                            {MS_FILTER_SET_NCHANNELS, set_nchannels},
                                            {MS_FILTER_GET_STAT_INPUT, winsndds_get_stat_input},
                                            {MS_FILTER_GET_STAT_OUTPUT, winsndds_get_stat_ouptut},
                                            {MS_FILTER_GET_STAT_DISCARDED, winsndds_get_stat_discarded},
                                            {0, NULL}};

MSFilterDesc winsndds_read_desc = {MS_WINSNDDS_READ_ID,
                                   "DirecSoundRead",
                                   "DirectSound capture filter for Windows",
                                   MS_FILTER_OTHER,
                                   NULL,
                                   0,
                                   1,
                                   winsndds_init,
                                   winsndds_read_preprocess,
                                   winsndds_read_process,
                                   winsndds_read_postprocess,
                                   winsndds_uninit,
                                   winsndds_methods};

MSFilterDesc winsndds_write_desc = {MS_WINSNDDS_WRITE_ID,
                                    "DirecSoundWrite",
                                    "DirectSound playback filter for Windows",
                                    MS_FILTER_OTHER,
                                    NULL,
                                    1,
                                    0,
                                    winsndds_init,
                                    winsndds_write_preprocess,
                                    winsndds_write_process,
                                    winsndds_write_postprocess,
                                    winsndds_uninit,
                                    winsndds_methods};

MSFilter *ms_winsndds_read_new(MSSndCard *card) {
	MSFilter *f = ms_factory_create_filter_from_desc(ms_snd_card_get_factory(card), &winsndds_read_desc);
	WinSndDsCard *wc = (WinSndDsCard *)card->data;
	WinSndDs *d = (WinSndDs *)f->data;
	d->dev_id = wc->in_devid;
	memcpy(&d->in_guid, &wc->in_guid, sizeof(GUID));
	memcpy(&d->out_guid, &wc->out_guid, sizeof(GUID));
	return f;
}

MSFilter *ms_winsndds_write_new(MSSndCard *card) {
	MSFilter *f = ms_factory_create_filter_from_desc(ms_snd_card_get_factory(card), &winsndds_write_desc);
	WinSndDsCard *wc = (WinSndDsCard *)card->data;
	WinSndDs *d = (WinSndDs *)f->data;
	d->dev_id = wc->out_devid;
	memcpy(&d->in_guid, &wc->in_guid, sizeof(GUID));
	memcpy(&d->out_guid, &wc->out_guid, sizeof(GUID));
	return f;
}

MS_FILTER_DESC_EXPORT(winsndds_read_desc)
MS_FILTER_DESC_EXPORT(winsndds_write_desc)

#endif
