Click here to Skip to main content
15,892,746 members
Articles / Desktop Programming / Win32

Play Audio Files with DirectSound and Display its Spectrum in Real Time - Part 3

Rate me:
Please Sign up or sign in to vote.
4.93/5 (21 votes)
24 Dec 2008CPOL3 min read 274.3K   17.7K   86  
An article to show how to play audio file with DirectSound and display its spectrum in real time accurately
#include "Utils.h"

#define _LITTLE_ENDIAN

#if USE_DAUDIO == TRUE

#pragma warning(disable : 4996)

/* half a minute to wait before device list is re-read */
#define WAIT_BETWEEN_CACHE_REFRESH_MILLIS 30000

/* maximum number of supported devices, playback+capture */
#define MAX_DS_DEVICES 60

static DS_AudioDeviceCache g_audioDeviceCache[MAX_DS_DEVICES];
static INT32 g_cacheCount = 0;
static UINT64 g_lastCacheRefreshTime = 0;
static INT32 g_mixerCount = 0;

BOOL DS_lockCache() {
	/* dummy implementation for now, Java does locking */
	return TRUE;
}

void DS_unlockCache() {
	/* dummy implementation for now */
}

static GUID CLSID_DAUDIO_Zero = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};

BOOL isEqualGUID(LPGUID lpGuid1, LPGUID lpGuid2)
{
	if (lpGuid1 == NULL || lpGuid2 == NULL)
	{
		if (lpGuid1 == lpGuid2)
		{
			return TRUE;
		}

		if (lpGuid1 == NULL)
		{
			lpGuid1 = (LPGUID) (&CLSID_DAUDIO_Zero);
		}
		else
		{
			lpGuid2 = (LPGUID) (&CLSID_DAUDIO_Zero);
		}
	}

	return memcmp(lpGuid1, lpGuid2, sizeof(GUID)) == 0;
}

INT32 findCacheItemByGUID(LPGUID lpGuid, BOOL isSource)
{
	int i;
	for (i = 0; i < g_cacheCount; i++)
	{
		if (isSource == g_audioDeviceCache[i].isSource
			&& isEqualGUID(lpGuid, &(g_audioDeviceCache[i].guid)))
		{
				return i;
		}
	}

	return -1;
}

INT32 findCacheItemByMixerIndex(INT32 mixerIndex)
{
	int i;
	for (i = 0; i < g_cacheCount; i++)
	{
		if (g_audioDeviceCache[i].mixerIndex == mixerIndex)
		{
			return i;
		}
	}

	return -1;
}

BOOL CALLBACK DS_RefreshCacheEnum(LPGUID lpGuid, LPCSTR lpstrDescription,
								  LPCSTR lpstrModule, DS_RefreshCacheStruct* rs)
{
	INT32 cacheIndex = findCacheItemByGUID(lpGuid, rs->isSource);
	/*TRACE3("Enumerating %d: %s (%s)\n", cacheIndex, lpstrDescription, lpstrModule);*/
	if (cacheIndex == -1)
	{
		/* add this device */
		if (g_cacheCount < MAX_DS_DEVICES-1)
		{
			g_audioDeviceCache[g_cacheCount].mixerIndex = rs->currMixerIndex;
			g_audioDeviceCache[g_cacheCount].isSource = rs->isSource;
			g_audioDeviceCache[g_cacheCount].dev = NULL;
			g_audioDeviceCache[g_cacheCount].refCount = 0;

			if (lpGuid == NULL)
			{
				memset(&(g_audioDeviceCache[g_cacheCount].guid), 0, sizeof(GUID));
			}
			else
			{
				memcpy(&(g_audioDeviceCache[g_cacheCount].guid), lpGuid, sizeof(GUID));
			}

				g_cacheCount++;
				rs->currMixerIndex++;
		}
		else
		{
			/* failure case: more than MAX_DS_DEVICES available... */
		}
	}
	else
	{
		/* device already exists in cache... update mixer number */
		g_audioDeviceCache[cacheIndex].mixerIndex = rs->currMixerIndex;
		rs->currMixerIndex++;
	}
	
	/* continue enumeration */
	return TRUE;
}

///// implemented functions of DirectAudio.h

INT32 DAUDIO_GetDirectAudioDeviceCount() {
	DS_RefreshCacheStruct rs;
	INT32 oldCount;
	INT32 cacheIndex;

	if (!DS_lockCache()) {
		return 0;
	}

	if (g_lastCacheRefreshTime == 0
		|| (UINT64) timeGetTime() > (UINT64) (g_lastCacheRefreshTime + WAIT_BETWEEN_CACHE_REFRESH_MILLIS)) {
			/* first, initialize any old cache items */
			for (cacheIndex = 0; cacheIndex < g_cacheCount; cacheIndex++) {
				g_audioDeviceCache[cacheIndex].mixerIndex = -1;
			}

			/* enumerate all devices and either add them to the device cache,
			* or refresh the mixer number
			*/
			rs.currMixerIndex = 0;
			rs.isSource = TRUE;
			DirectSoundEnumerate((LPDSENUMCALLBACK) DS_RefreshCacheEnum, &rs);
			/* if we only got the Primary Sound Driver (GUID=NULL),
			* then there aren't any playback devices installed */
			if (rs.currMixerIndex == 1) {
				cacheIndex = findCacheItemByGUID(NULL, TRUE);
				if (cacheIndex == 0) {
					rs.currMixerIndex = 0;
					g_audioDeviceCache[0].mixerIndex = -1;
					TRACE0("Removing stale Primary Sound Driver from list.\n");
				}
			}
			oldCount = rs.currMixerIndex;
			rs.isSource = FALSE;
			DirectSoundCaptureEnumerate((LPDSENUMCALLBACK) DS_RefreshCacheEnum, &rs);
			/* if we only got the Primary Sound Capture Driver (GUID=NULL),
			* then there aren't any capture devices installed */
			if ((rs.currMixerIndex - oldCount) == 1) {
				cacheIndex = findCacheItemByGUID(NULL, FALSE);
				if (cacheIndex != -1) {
					rs.currMixerIndex = oldCount;
					g_audioDeviceCache[cacheIndex].mixerIndex = -1;
					TRACE0("Removing stale Primary Sound Capture Driver from list.\n");
				}
			}
			g_mixerCount = rs.currMixerIndex;

			g_lastCacheRefreshTime = (UINT64) timeGetTime();
	}

	DS_unlockCache();
	return g_mixerCount;
}

BOOL CALLBACK DS_GetDescEnum(LPGUID lpGuid, LPCTSTR lpstrDescription,
							 LPCTSTR lpstrModule, DirectAudioDeviceDescription* desc)
{

	INT32 cacheIndex = findCacheItemByGUID(lpGuid, g_audioDeviceCache[desc->deviceID].isSource);
	if (cacheIndex == desc->deviceID) {
		StringCchCopy(desc->name, DAUDIO_STRING_LENGTH, lpstrDescription);
		//strncpy(desc->name, lpstrDescription, DAUDIO_STRING_LENGTH);
		//strncpy(desc->description, lpstrModule, DAUDIO_STRING_LENGTH);
		desc->maxSimulLines = -1;
		/* do not continue enumeration */
		return FALSE;
	}
	return TRUE;
}


INT32 DAUDIO_GetDirectAudioDeviceDescription(INT32 mixerIndex, DirectAudioDeviceDescription* desc) {

	if (!DS_lockCache()) {
		return FALSE;
	}

	/* set the deviceID field to the cache index */
	desc->deviceID = findCacheItemByMixerIndex(mixerIndex);
	if (desc->deviceID < 0) {
		DS_unlockCache();
		return FALSE;
	}
	desc->maxSimulLines = 0;
	if (g_audioDeviceCache[desc->deviceID].isSource) {
		DirectSoundEnumerate((LPDSENUMCALLBACK) DS_GetDescEnum, desc);
		StringCchCopy(desc->description, DAUDIO_STRING_LENGTH, _TEXT("DirectSound Playback"));
		//strncpy(desc->description, "DirectSound Playback", DAUDIO_STRING_LENGTH);
	} else {
		DirectSoundCaptureEnumerate((LPDSENUMCALLBACK) DS_GetDescEnum, desc);
		StringCchCopy(desc->description, DAUDIO_STRING_LENGTH, _TEXT("DirectSound Capture"));
		//strncpy(desc->description, "DirectSound Capture", DAUDIO_STRING_LENGTH);
	}

	/*desc->vendor;
	desc->version;*/

	DS_unlockCache();
	return (desc->maxSimulLines == -1)?TRUE:FALSE;
}

/* multi-channel info: http://www.microsoft.com/whdc/hwdev/tech/audio/multichaud.mspx */

//static UINT32 sampleRateArray[] = { 8000, 11025, 16000, 22050, 32000, 44100, 48000, 56000, 88000, 96000, 172000, 192000 };
static INT32 sampleRateArray[] = { -1 };
static INT32 channelsArray[] = { 1, 2};
static INT32 bitsArray[] = { 8, 16};

#define SAMPLERATE_COUNT sizeof(sampleRateArray)/sizeof(INT32)
#define CHANNELS_COUNT sizeof(channelsArray)/sizeof(INT32)
#define BITS_COUNT sizeof(bitsArray)/sizeof(INT32)

void DAUDIO_GetFormats(INT32 mixerIndex, INT32 deviceID, int isSource, void* creator) {

	int rateIndex, channelIndex, bitIndex;

	/* no need to lock, since deviceID identifies the device sufficiently */

	/* sanity */
	if (deviceID >= g_cacheCount) {
		return;
	}
	if ((g_audioDeviceCache[deviceID].isSource && !isSource)
		|| (!g_audioDeviceCache[deviceID].isSource && isSource)) {
			/* only support Playback or Capture */
			return;
	}

	for (rateIndex = 0; rateIndex < SAMPLERATE_COUNT; rateIndex++) {
		for (channelIndex = 0; channelIndex < CHANNELS_COUNT; channelIndex++) {
			for (bitIndex = 0; bitIndex < BITS_COUNT; bitIndex++) {
				DAUDIO_AddAudioFormat(creator, bitsArray[bitIndex],
					((bitsArray[bitIndex] + 7) / 8) * channelsArray[channelIndex],
					channelsArray[channelIndex],
					(float) sampleRateArray[rateIndex],
					DAUDIO_PCM,
					(bitsArray[bitIndex]==8)?FALSE:TRUE,  /* signed */
					(bitsArray[bitIndex]==8)?FALSE:
#ifndef _LITTLE_ENDIAN
					TRUE /* big endian */
#else
					FALSE /* little endian */
#endif
					);
			}
		}
	}
}

LPSTR TranslateDSError(HRESULT hr) {
	switch(hr) {
		case DSERR_ALLOCATED:
			return "DSERR_ALLOCATED";

		case DSERR_CONTROLUNAVAIL:
			return "DSERR_CONTROLUNAVAIL";

		case DSERR_INVALIDPARAM:
			return "DSERR_INVALIDPARAM";

		case DSERR_INVALIDCALL:
			return "DSERR_INVALIDCALL";

		case DSERR_GENERIC:
			return "DSERR_GENERIC";

		case DSERR_PRIOLEVELNEEDED:
			return "DSERR_PRIOLEVELNEEDED";

		case DSERR_OUTOFMEMORY:
			return "DSERR_OUTOFMEMORY";

		case DSERR_BADFORMAT:
			return "DSERR_BADFORMAT";

		case DSERR_UNSUPPORTED:
			return "DSERR_UNSUPPORTED";

		case DSERR_NODRIVER:
			return "DSERR_NODRIVER";

		case DSERR_ALREADYINITIALIZED:
			return "DSERR_ALREADYINITIALIZED";

		case DSERR_NOAGGREGATION:
			return "DSERR_NOAGGREGATION";

		case DSERR_BUFFERLOST:
			return "DSERR_BUFFERLOST";

		case DSERR_OTHERAPPHASPRIO:
			return "DSERR_OTHERAPPHASPRIO";

		case DSERR_UNINITIALIZED:
			return "DSERR_UNINITIALIZED";

		default:
			return "Unknown HRESULT";
	}
}

/*
** data/routines for starting DS buffers by separate thread
** (joint into DS_StartBufferHelper class)
** see cr6372428: playback fails after exiting from thread that has started it
** due IDirectSoundBuffer8::Play() description:
** http://msdn.microsoft.com/archive/default.asp?url=/archive/en-us/directx9_c
**       /directx/htm/idirectsoundbuffer8play.asp
** (remark section): If the application is multithreaded, the thread that plays
** the buffer must continue to exist as long as the buffer is playing.
** Buffers created on WDM drivers stop playing when the thread is terminated.
** IDirectSoundCaptureBuffer8::Start() has the same remark:
** http://msdn.microsoft.com/archive/default.asp?url=/archive/en-us/directx9_c
**       /directx/htm/idirectsoundcapturebuffer8start.asp
*/
class DS_StartBufferHelper {
public:
	/* starts DirectSound buffer (playback or capture) */
	static HRESULT StartBuffer(DS_Info* info);
	/* checks for initialization success */
	static inline BOOL isInitialized() { return data.threadHandle != NULL; }
protected:
	DS_StartBufferHelper() {}  // no need to create an instance

	/* data class */
	class Data {
	public:
		Data();
		~Data();
		// public data to access from parent class
		CRITICAL_SECTION crit_sect;
		volatile HANDLE threadHandle;
		volatile HANDLE startEvent;
		volatile HANDLE startedEvent;
		volatile DS_Info* line2Start;
		volatile HRESULT startResult;
	} static data;

	/* StartThread function */
	static DWORD WINAPI __stdcall ThreadProc(void *param);
};

/* StartBufferHelper class implementation
*/
DS_StartBufferHelper::Data DS_StartBufferHelper::data;

DS_StartBufferHelper::Data::Data() {
	threadHandle = NULL;
	::InitializeCriticalSection(&crit_sect);
	startEvent = ::CreateEvent(NULL, FALSE, FALSE, NULL);
	startedEvent = ::CreateEvent(NULL, FALSE, FALSE, NULL);
	if (startEvent != NULL && startedEvent != NULL)
		threadHandle = ::CreateThread(NULL, 0, ThreadProc, NULL, 0, NULL);
}

DS_StartBufferHelper::Data::~Data() {
	::EnterCriticalSection(&crit_sect);
	if (threadHandle != NULL) {
		// terminate thread
		line2Start = NULL;
		::SetEvent(startEvent);
		::CloseHandle(threadHandle);
		threadHandle = NULL;
	}
	::LeaveCriticalSection(&crit_sect);
	// won't delete startEvent/startedEvent/crit_sect
	// - Windows will do during process shutdown
}

DWORD WINAPI __stdcall DS_StartBufferHelper::ThreadProc(void *param)
{
	while (1) {
		// wait for something to do
		::WaitForSingleObject(data.startEvent, INFINITE);
		if (data.line2Start == NULL) {
			// (data.line2Start == NULL) is a signal to terminate thread
			break;
		}
		if (data.line2Start->isSource) {
			data.startResult =
				data.line2Start->playBuffer->Play(0, 0, DSCBSTART_LOOPING);
		} else {
			data.startResult =
				data.line2Start->captureBuffer->Start(DSCBSTART_LOOPING);
		}
		::SetEvent(data.startedEvent);
	}
	return 0;
}

HRESULT DS_StartBufferHelper::StartBuffer(DS_Info* info) {
	HRESULT hr;
	::EnterCriticalSection(&data.crit_sect);
	if (!isInitialized()) {
		::LeaveCriticalSection(&data.crit_sect);
		return E_FAIL;
	}
	data.line2Start = info;
	::SetEvent(data.startEvent);
	::WaitForSingleObject(data.startedEvent, INFINITE);
	hr = data.startResult;
	::LeaveCriticalSection(&data.crit_sect);
	return hr;
}


/* helper routines for DS buffer positions */
/* returns distance from pos1 to pos2
*/
inline int DS_getDistance(DS_Info* info, int pos1, int pos2) {
	int distance = pos2 - pos1;
	while (distance < 0)
		distance += info->dsBufferSizeInBytes;
	return distance;
}

/* adds 2 positions
*/
inline int DS_addPos(DS_Info* info, int pos1, int pos2) {
	int result = pos1 + pos2;
	while (result >= info->dsBufferSizeInBytes)
		result -= info->dsBufferSizeInBytes;
	return result;
}


BOOL DS_addDeviceRef(INT32 deviceID) {
	HWND ownerWindow;
	HRESULT res = DS_OK;
	LPDIRECTSOUND devPlay;
	LPDIRECTSOUNDCAPTURE devCapture;
	LPGUID lpGuid = NULL;


	if (g_audioDeviceCache[deviceID].dev == NULL) {
		/* Create DirectSound */
		TRACE1("Creating DirectSound object for device %d\n", deviceID);
		lpGuid = &(g_audioDeviceCache[deviceID].guid);
		if (isEqualGUID(lpGuid, NULL)) {
			lpGuid = NULL;
		}
		if (g_audioDeviceCache[deviceID].isSource) {
			res = DirectSoundCreate(lpGuid, &devPlay, NULL);
			g_audioDeviceCache[deviceID].dev = (void*) devPlay;
		} else {
			res = DirectSoundCaptureCreate(lpGuid, &devCapture, NULL);
			g_audioDeviceCache[deviceID].dev = (void*) devCapture;
		}
		g_audioDeviceCache[deviceID].refCount = 0;
		if (FAILED(res)) {
			ERROR1("DS_addDeviceRef: ERROR: Failed to create DirectSound: %s", TranslateDSError(res));
			g_audioDeviceCache[deviceID].dev = NULL;
			return FALSE;
		}
		if (g_audioDeviceCache[deviceID].isSource) {
			ownerWindow = GetForegroundWindow();
			if (ownerWindow == NULL) {
				ownerWindow = GetDesktopWindow();
			}
			TRACE0("DS_addDeviceRef: Setting cooperative level\n");
			res = devPlay->SetCooperativeLevel(ownerWindow, DSSCL_NORMAL);
			if (FAILED(res)) {
				ERROR1("DS_addDeviceRef: ERROR: Failed to set cooperative level: %s", TranslateDSError(res));
				return FALSE;
			}
		}
	}
	g_audioDeviceCache[deviceID].refCount++;
	return TRUE;
}

#define DEV_PLAY(devID)    ((LPDIRECTSOUND) g_audioDeviceCache[devID].dev)
#define DEV_CAPTURE(devID) ((LPDIRECTSOUNDCAPTURE) g_audioDeviceCache[devID].dev)

void DS_removeDeviceRef(INT32 deviceID) {

	if (g_audioDeviceCache[deviceID].refCount) {
		g_audioDeviceCache[deviceID].refCount--;
	}
	if (g_audioDeviceCache[deviceID].refCount == 0) {
		if (g_audioDeviceCache[deviceID].dev != NULL) {
			if (g_audioDeviceCache[deviceID].isSource) {
				DEV_PLAY(deviceID)->Release();
			} else {
				DEV_CAPTURE(deviceID)->Release();
			}
			g_audioDeviceCache[deviceID].dev = NULL;
		}
	}
}

void createWaveFormat(WAVEFORMATEXTENSIBLE* format, int sampleRate,
					  int channels, int bits, int significantBits) {
	GUID subtypePCM = {STATIC_KSDATAFORMAT_SUBTYPE_PCM};
	format->Format.nSamplesPerSec = (DWORD)sampleRate;
	format->Format.nChannels = (WORD) channels;
	/* do not support useless padding, like 24-bit samples stored in 32-bit containers */
	format->Format.wBitsPerSample = (WORD) ((bits + 7) & 0xFFF8);

	if (channels <= 2 && bits <= 16) {
		format->Format.wFormatTag = WAVE_FORMAT_PCM;
		format->Format.cbSize = 0;
	} else {
		format->Format.wFormatTag = WAVE_FORMAT_EXTENSIBLE;
		format->Format.cbSize = 22;
		format->Samples.wValidBitsPerSample = bits;
		/* no way to specify speaker locations */
		format->dwChannelMask = 0xFFFFFFFF;
		format->SubFormat = subtypePCM;
	}
	format->Format.nBlockAlign = (WORD)((format->Format.wBitsPerSample * format->Format.nChannels) / 8);
	format->Format.nAvgBytesPerSec = format->Format.nSamplesPerSec * format->Format.nBlockAlign;
}

/* fill buffer with silence
*/
void DS_clearBuffer(DS_Info* info, BOOL fromWritePos) {
	UBYTE* pb1=NULL, *pb2=NULL;
	DWORD  cb1=0, cb2=0;
	DWORD flags = 0;
	int start, count;
	TRACE1("> DS_clearBuffer for device %d\n", info->deviceID);
	if (info->isSource)  {
		if (fromWritePos) {
			DWORD playCursor, writeCursor;
			int end;
			if (FAILED(info->playBuffer->GetCurrentPosition(&playCursor, &writeCursor))) {
				ERROR0("  DS_clearBuffer: ERROR: Failed to get current position.");
				TRACE0("< DS_clearbuffer\n");
				return;
			}
			TRACE2("  DS_clearBuffer: DS playPos=%d  myWritePos=%d", (int) playCursor, (int) info->writePos);
			if (info->writePos >= 0) {
				start = info->writePos + info->silencedBytes;
			} else {
				start = writeCursor + info->silencedBytes;
				//flags |= DSBLOCK_FROMWRITECURSOR;
			}
			while (start >= info->dsBufferSizeInBytes) {
				start -= info->dsBufferSizeInBytes;
			}

			// fix for bug 6251460 (REGRESSION: short sounds do not play)
			// for unknown reason with hardware DS buffer playCursor sometimes
			// jumps back for little interval (mostly 2-8 bytes) (writeCursor moves forward as usual)
			// The issue happens right after start playing and for short sounds only (less then DS buffer,
			// when whole sound written into the buffer and remaining space filled by silence)
			// the case doesn't produce any audible aftifacts so just catch it to prevent filling
			// whole buffer by silence.
			if (((int)playCursor <= start && start < (int)writeCursor)
				|| (writeCursor < playCursor    // buffer bound is between playCursor & writeCursor
				&& (start < (int)writeCursor || (int)playCursor <= start))) {
					return;
			}

			count = info->dsBufferSizeInBytes - info->silencedBytes;
			// why / 4?
			//if (count > info->dsBufferSizeInBytes / 4) {
			//    count = info->dsBufferSizeInBytes / 4;
			//}
			end = start + count;
			if ((int) playCursor < start) {
				playCursor += (DWORD) info->dsBufferSizeInBytes;
			}
			if (start <= (int) playCursor && end > (int) playCursor) {
				/* at maximum, silence until play cursor */
				count = (int) playCursor - start;
#ifdef USE_TRACE
				if ((int) playCursor >= info->dsBufferSizeInBytes) playCursor -= (DWORD) info->dsBufferSizeInBytes;
				TRACE3("\n  DS_clearBuffer: Start Writing from %d, "
					"would overwrite playCursor=%d, so reduce count to %d\n",
					start, playCursor, count);
#endif
			}
			TRACE2("  clearing buffer from %d, count=%d. ", (int)start, (int) count);
			if (count <= 0) {
				TRACE0("\n");
				TRACE1("< DS_clearBuffer: no need to clear, silencedBytes=%d\n", info->silencedBytes);
				return;
			}
		} else {
			start = 0;
			count = info->dsBufferSizeInBytes;
			flags |= DSBLOCK_ENTIREBUFFER;
		}
		if (FAILED(info->playBuffer->Lock(start,
			count,
			(LPVOID*) &pb1, &cb1,
			(LPVOID*) &pb2, &cb2, flags))) {
				ERROR0("\n  DS_clearBuffer: ERROR: Failed to lock sound buffer.\n");
				TRACE0("< DS_clearbuffer\n");
				return;
		}
	} else {
		if (FAILED(info->captureBuffer->Lock(0,
			info->dsBufferSizeInBytes,
			(LPVOID*) &pb1, &cb1,
			(LPVOID*) &pb2, &cb2, DSCBLOCK_ENTIREBUFFER))) {
				ERROR0("  DS_clearBuffer: ERROR: Failed to lock sound buffer.\n");
				TRACE0("< DS_clearbuffer\n");
				return;
		}
	}
	if (pb1!=NULL) {
		memset(pb1, (info->bitsPerSample == 8)?128:0, cb1);
	}
	if (pb2!=NULL) {
		memset(pb2, (info->bitsPerSample == 8)?128:0, cb2);
	}
	if (info->isSource)  {
		info->playBuffer->Unlock( pb1, cb1, pb2, cb2 );
		if (!fromWritePos) {
			/* doesn't matter where to start writing next time */
			info->writePos = -1;
			info->silencedBytes = info->dsBufferSizeInBytes;
		} else {
			info->silencedBytes += (cb1+cb2);
			if (info->silencedBytes > info->dsBufferSizeInBytes) {
				ERROR1("  DS_clearbuffer: ERROR: silencedBytes=%d exceeds buffer size!\n",
					info->silencedBytes);
				info->silencedBytes = info->dsBufferSizeInBytes;
			}
		}
		TRACE2("  silencedBytes=%d, my writePos=%d\n", (int)info->silencedBytes, (int)info->writePos);
	} else {
		info->captureBuffer->Unlock( pb1, cb1, pb2, cb2 );
	}
	TRACE0("< DS_clearbuffer\n");
}

/* returns pointer to buffer */
void* DS_createSoundBuffer(DS_Info* info,
						   float sampleRate, /* 44100 */
						   int sampleSizeInBits, /* 16 */
						   int channels /* 2 */,
						   int bufferSizeInBytes/* 88200 0.5 sec */)
{
	DSBUFFERDESC dsbdesc;
	DSCBUFFERDESC dscbdesc;
	HRESULT res;
	WAVEFORMATEXTENSIBLE format;
	void* buffer;

	TRACE1("Creating secondary buffer for device %d\n", info->deviceID);
	createWaveFormat(&format,
		(int) sampleRate,
		channels,
		info->frameSize / channels * 8,
		sampleSizeInBits);

	/* 2 second secondary buffer */
	info->dsBufferSizeInBytes = 2 * ((int) sampleRate) * info->frameSize;

	if (bufferSizeInBytes > info->dsBufferSizeInBytes / 2) {
		bufferSizeInBytes = info->dsBufferSizeInBytes / 2;
	}

	bufferSizeInBytes = (bufferSizeInBytes / info->frameSize) * info->frameSize;
	info->bufferSizeInBytes = bufferSizeInBytes;

	if (info->isSource) {
		memset(&dsbdesc, 0, sizeof(DSBUFFERDESC));
		dsbdesc.dwSize = sizeof(DSBUFFERDESC);
		dsbdesc.dwFlags = DSBCAPS_GETCURRENTPOSITION2 | DSBCAPS_GLOBALFOCUS;
		dsbdesc.dwBufferBytes = info->dsBufferSizeInBytes;
		dsbdesc.lpwfxFormat = (WAVEFORMATEX*) &format;
		res = DEV_PLAY(info->deviceID)->CreateSoundBuffer
			   (&dsbdesc, (LPDIRECTSOUNDBUFFER*) &buffer, NULL);
	} else {
		memset(&dscbdesc, 0, sizeof(DSCBUFFERDESC));
		dscbdesc.dwSize = sizeof(DSCBUFFERDESC);
		dscbdesc.dwFlags = 0;
		dscbdesc.dwBufferBytes = info->dsBufferSizeInBytes;
		dscbdesc.lpwfxFormat = (WAVEFORMATEX*) &format;
		res = DEV_CAPTURE(info->deviceID)->CreateCaptureBuffer
			   (&dscbdesc, (LPDIRECTSOUNDCAPTUREBUFFER*) &buffer, NULL);
	}

	if (FAILED(res)) {
		ERROR1("DS_createSoundBuffer: ERROR: Failed to create sound buffer: %s", TranslateDSError(res));
		return NULL;
	}
	return buffer;
}

void DS_destroySoundBuffer(DS_Info* info) {
	if (info->playBuffer != NULL) {
		info->playBuffer->Release();
		info->playBuffer = NULL;
	}
	if (info->captureBuffer != NULL) {
		info->captureBuffer->Release();
		info->captureBuffer = NULL;
	}
}


void* DAUDIO_Open(INT32 mixerIndex, INT32 deviceID, int isSource,
				  int encoding, float sampleRate /*44100*/, int sampleSizeInBits/*16*/,
				  int frameSize/*4*/, int channels/*2*/,
				  int isSigned, int isBigEndian, int bufferSizeInBytes)
{
	DS_Info* info;
	void* buffer;

	TRACE0("> DAUDIO_Open\n");

	/* some sanity checks */
	if (deviceID >= g_cacheCount) {
		ERROR1("DAUDIO_Open: ERROR: cannot open the device with deviceID=%d!\n", deviceID);
		return NULL;
	}
	if ((g_audioDeviceCache[deviceID].isSource && !isSource)
		|| (!g_audioDeviceCache[deviceID].isSource && isSource)) {
			/* only support Playback or Capture */
			ERROR0("DAUDIO_Open: ERROR: Cache is corrupt: cannot open the device in specified isSource mode!\n");
			return NULL;
	}
	if (encoding != DAUDIO_PCM) {
		ERROR1("DAUDIO_Open: ERROR: cannot open the device with encoding=%d!\n", encoding);
		return NULL;
	}
	if (sampleSizeInBits > 8 &&
#ifdef _LITTLE_ENDIAN
		isBigEndian
#else
		!isBigEndian
#endif
		) {
			ERROR1("DAUDIO_Open: ERROR: wrong endianness: isBigEndian==%d!\n", isBigEndian);
			return NULL;
	}
	if (sampleSizeInBits == 8 && isSigned) {
		ERROR0("DAUDIO_Open: ERROR: wrong signed'ness: with 8 bits, data must be unsigned!\n");
		return NULL;
	}
	if (!DS_StartBufferHelper::isInitialized()) {
		ERROR0("DAUDIO_Open: ERROR: StartBufferHelper initialization was failed!\n");
		return NULL;
	}

	info = (DS_Info*) malloc(sizeof(DS_Info));
	if (!info) {
		ERROR0("DAUDIO_Open: ERROR: Out of memory\n");
		return NULL;
	}
	memset(info, 0, sizeof(DS_Info));

	info->deviceID = deviceID;
	info->isSource = isSource;
	info->bitsPerSample = sampleSizeInBits;
	info->frameSize = frameSize;
	info->framePos = 0;
	info->started = FALSE;
	info->underrun = FALSE;

	if (!DS_addDeviceRef(deviceID)) {
		DS_removeDeviceRef(deviceID);
		free(info);
		return NULL;
	}

	buffer = DS_createSoundBuffer(info,
		sampleRate,
		sampleSizeInBits,
		channels,
		bufferSizeInBytes);
	if (!buffer) {
		DS_removeDeviceRef(deviceID);
		free(info);
		return NULL;
	}

	if (info->isSource) {
		info->playBuffer = (LPDIRECTSOUNDBUFFER) buffer;
	} else {
		info->captureBuffer = (LPDIRECTSOUNDCAPTUREBUFFER) buffer;
	}
	DS_clearBuffer(info, FALSE /* entire buffer */);

	/* use writepos of device */
	if (info->isSource) {
		info->writePos = -1;
	} else {
		info->writePos = 0;
	}

	TRACE0("< DAUDIO_Open: Opened device successfully.\n");
	return (void*) info;
}

int DAUDIO_Start(void* id, int isSource) {
	DS_Info* info = (DS_Info*) id;
	HRESULT res = DS_OK;
	DWORD status;

	TRACE0("> DAUDIO_Start\n");

	if (info->isSource)  {
		res = info->playBuffer->GetStatus(&status);
		if (res == DS_OK) {
			if (status & DSBSTATUS_LOOPING) {
				ERROR0("DAUDIO_Start: ERROR: Already started!");
				return TRUE;
			}

			/* only start buffer if already something written to it */
			if (info->writePos >= 0) {
				res = DS_StartBufferHelper::StartBuffer(info);
				if (res == DSERR_BUFFERLOST) {
					res = info->playBuffer->Restore();
					if (res == DS_OK) {
						DS_clearBuffer(info, FALSE /* entire buffer */);
						/* write() will trigger actual device start */
					}
				} else {
					/* make sure that we will have silence after
					the currently valid audio data */
					DS_clearBuffer(info, TRUE /* from write position */);
				}
			}
		}
	} else {
		if (info->captureBuffer->GetStatus(&status) == DS_OK) {
			if (status & DSCBSTATUS_LOOPING) {
				ERROR0("DAUDIO_Start: ERROR: Already started!");
				return TRUE;
			}
		}
		res = DS_StartBufferHelper::StartBuffer(info);
	}
	if (FAILED(res)) {
		ERROR1("DAUDIO_Start: ERROR: Failed to start: %s", TranslateDSError(res));
		return FALSE;
	}
	info->started = TRUE;
	return TRUE;
}

int DAUDIO_Stop(void* id, int isSource) {
	DS_Info* info = (DS_Info*) id;

	TRACE0("> DAUDIO_Stop\n");

	info->started = FALSE;
	if (info->isSource)  {
		info->playBuffer->Stop();
	} else {
		info->captureBuffer->Stop();
	}

	TRACE0("< DAUDIO_Stop\n");
	return TRUE;
}


void DAUDIO_Close(void* id, int isSource) {
	DS_Info* info = (DS_Info*) id;

	TRACE0("DAUDIO_Close\n");

	if (info != NULL) {
		DS_destroySoundBuffer(info);
		DS_removeDeviceRef(info->deviceID);
		free(info);
	}
}

/* Check buffer for underrun
* This method is only meaningful for Output devices (write devices).
*/
void DS_CheckUnderrun(DS_Info* info, DWORD playCursor, DWORD writeCursor) {
	TRACE5("DS_CheckUnderrun: playCursor=%d, writeCursor=%d, "
		"info->writePos=%d  silencedBytes=%d  dsBufferSizeInBytes=%d\n",
		(int) playCursor, (int) writeCursor, (int) info->writePos,
		(int) info->silencedBytes, (int) info->dsBufferSizeInBytes);
	if (info->underrun || info->writePos < 0) return;
	int writeAhead = DS_getDistance(info, writeCursor, info->writePos);
	if (writeAhead > info->bufferSizeInBytes) {
		// this may occur after Stop(), when writeCursor decreases (real valid data size > bufferSizeInBytes)
		// But the case can occur only when we have more then info->bufferSizeInBytes valid bytes
		// (and less then (info->dsBufferSizeInBytes - info->bufferSizeInBytes) silenced bytes)
		// If we already have a lot of silencedBytes after valid data (written by
		// DAUDIO_StillDraining() or DAUDIO_Service()) then it's underrun
		if (info->silencedBytes >= info->dsBufferSizeInBytes - info->bufferSizeInBytes) {
			// underrun!
			ERROR0("DS_CheckUnderrun: ERROR: underrun detected!\n");
			info->underrun = TRUE;
		}
	}
}

/* For source (playback) line:
*   (a) if (fromPlayCursor == FALSE), returns number of bytes available
*     for writing: bufferSize - (info->writePos - writeCursor);
*   (b) if (fromPlayCursor == TRUE), playCursor is used instead writeCursor
*     and returned value can be used for play position calculation (see also
*     note about bufferSize)
* For destination (capture) line:
*   (c) if (fromPlayCursor == FALSE), returns number of bytes available
*     for reading from the buffer: readCursor - info->writePos;
*   (d) if (fromPlayCursor == TRUE), captureCursor is used instead readCursor
*     and returned value can be used for capture position calculation (see
*     note about bufferSize)
* bufferSize parameter are filled by "actual" buffer size:
*   if (fromPlayCursor == FALSE), bufferSize = info->bufferSizeInBytes
*   otherwise it increase by number of bytes currently processed by DirectSound
*     (writeCursor - playCursor) or (captureCursor - readCursor)
*/
int DS_GetAvailable(DS_Info* info, DWORD* playCursor, DWORD* writeCursor,
					int* bufferSize, BOOL fromPlayCursor) {
	int available;
	int newReadPos;

	TRACE2("DS_GetAvailable: fromPlayCursor=%d,  deviceID=%d\n", fromPlayCursor, info->deviceID);
	if (!info->playBuffer && !info->captureBuffer) {
		ERROR0("DS_GetAvailable: ERROR: buffer not yet created");
		return 0;
	}

	if (info->isSource)  {
		if (FAILED(info->playBuffer->GetCurrentPosition(playCursor, writeCursor))) {
			ERROR0("DS_GetAvailable: ERROR: Failed to get current position.\n");
			return 0;
		}
		int processing = DS_getDistance(info, (int)*playCursor, (int)*writeCursor);
		// workaround: sometimes DirectSound report writeCursor is less (for several bytes) then playCursor
		if (processing > info->dsBufferSizeInBytes / 2) {
			*writeCursor = *playCursor;
			processing = 0;
		}
		TRACE3("   playCursor=%d, writeCursor=%d, info->writePos=%d\n",
			*playCursor, *writeCursor, info->writePos);
		*bufferSize = info->bufferSizeInBytes;
		if (fromPlayCursor) {
			*bufferSize += processing;
		}
		DS_CheckUnderrun(info, *playCursor, *writeCursor);
		if (info->writePos == -1 || (info->underrun && !fromPlayCursor)) {
			/* always full buffer if at beginning */
			available = *bufferSize;
		} else {
			int currWriteAhead = DS_getDistance(info, fromPlayCursor ? (int)*playCursor : (int)*writeCursor, info->writePos);
			if (currWriteAhead > *bufferSize) {
				if (info->underrun) {
					// playCursor surpassed writePos - no valid data, whole buffer available
					available = *bufferSize;
				} else {
					// the case may occur after stop(), when writeCursor jumps back to playCursor
					// so "actual" buffer size has grown
					*bufferSize = currWriteAhead;
					available = 0;
				}
			} else {
				available = *bufferSize - currWriteAhead;
			}
		}
	} else {
		if (FAILED(info->captureBuffer->GetCurrentPosition(playCursor, writeCursor))) {
			ERROR0("DS_GetAvailable: ERROR: Failed to get current position.\n");
			return 0;
		}
		*bufferSize = info->bufferSizeInBytes;
		if (fromPlayCursor) {
			*bufferSize += DS_getDistance(info, (int)*playCursor, (int)*writeCursor);
		}
		TRACE4("   captureCursor=%d, readCursor=%d, info->readPos=%d  refBufferSize=%d\n",
			*playCursor, *writeCursor, info->writePos, *bufferSize);
		if (info->writePos == -1) {
			/* always empty buffer if at beginning */
			info->writePos = (int) (*writeCursor);
		}
		if (fromPlayCursor) {
			available = ((int) (*playCursor) - info->writePos);
		} else {
			available = ((int) (*writeCursor) - info->writePos);
		}
		if (available < 0) {
			available += info->dsBufferSizeInBytes;
		}
		if (!fromPlayCursor && available > info->bufferSizeInBytes) {
			/* overflow */
			ERROR2("DS_GetAvailable: ERROR: overflow detected: "
				"DirectSoundBufferSize=%d, bufferSize=%d, ",
				info->dsBufferSizeInBytes, info->bufferSizeInBytes);
			ERROR3("captureCursor=%d, readCursor=%d, info->readPos=%d\n",
				*playCursor, *writeCursor, info->writePos);
			/* advance read position, to allow exactly one buffer worth of data */
			newReadPos = (int) (*writeCursor) - info->bufferSizeInBytes;
			if (newReadPos < 0) {
				newReadPos += info->dsBufferSizeInBytes;
			}
			info->writePos = newReadPos;
			available = info->bufferSizeInBytes;
		}
	}
	available = (available / info->frameSize) * info->frameSize;

	TRACE1("DS_available: Returning %d available bytes\n", (int) available);
	return available;
}

// returns -1 on error, otherwise bytes written
int DAUDIO_Write(void* id, char* data, int byteSize) {
	DS_Info* info = (DS_Info*) id;
	int available;
	int thisWritePos;
	DWORD playCursor, writeCursor;
	HRESULT res;
	void* buffer1, *buffer2;
	DWORD buffer1len, buffer2len;
	BOOL needRestart = FALSE;
	int bufferLostTrials = 2;
	int bufferSize;

	TRACE1("> DAUDIO_Write %d bytes\n", byteSize);

	while (--bufferLostTrials > 0) {
		available = DS_GetAvailable(info, &playCursor, &writeCursor, &bufferSize, FALSE /* fromPlayCursor */);
		if (byteSize > available) byteSize = available;
		if (byteSize == 0) break;
		thisWritePos = info->writePos;
		if (thisWritePos == -1 || info->underrun) {
			// play from current write cursor after flush, etc.
			needRestart = TRUE;
			thisWritePos = writeCursor;
			info->underrun = FALSE;
		}

		TRACE2("DAUDIO_Write: writing from %d, count=%d\n", (int) thisWritePos, (int) byteSize);
		res = info->playBuffer->Lock(thisWritePos, byteSize,
			(LPVOID *) &buffer1, &buffer1len,
			(LPVOID *) &buffer2, &buffer2len,
			0);
		if (res != DS_OK) {
			/* some DS failure */
			if (res == DSERR_BUFFERLOST) {
				ERROR0("DAUDIO_write: ERROR: Restoring lost Buffer.");
				if (info->playBuffer->Restore() == DS_OK) {
					DS_clearBuffer(info, FALSE /* entire buffer */);
					info->writePos = -1;
					/* try again */
					continue;
				}
			}
			/* can't recover from error */
			byteSize = 0;
			break;
		}
		/* buffer could be locked successfully */
		/* first fill first buffer */
		if (buffer1) {
			memcpy(buffer1, data, buffer1len);
			data = (char*) (((UINT_PTR) data) + buffer1len);
		} else buffer1len = 0;
		if (buffer2) {
			memcpy(buffer2, data, buffer2len);
		} else buffer2len = 0;
		byteSize = buffer1len + buffer2len;

		/* update next write pos */
		thisWritePos += byteSize;
		while (thisWritePos >= info->dsBufferSizeInBytes) {
			thisWritePos -= info->dsBufferSizeInBytes;
		}
		/* commit data to directsound */
		info->playBuffer->Unlock(buffer1, buffer1len, buffer2, buffer2len);

		info->writePos = thisWritePos;

		/* update position
		* must be AFTER updating writePos,
		* so that getSvailable doesn't return too little,
		* so that getFramePos doesn't jump
		*/
		info->framePos += (byteSize / info->frameSize);

		/* decrease silenced bytes */
		if (info->silencedBytes > byteSize) {
			info->silencedBytes -= byteSize;
		} else {
			info->silencedBytes = 0;
		}
		break;
	} /* while */

	/* start the device, if necessary */
	if (info->started && needRestart && (info->writePos >= 0)) {
		DS_StartBufferHelper::StartBuffer(info);
	}

	TRACE1("< DAUDIO_Write: returning %d bytes.\n", byteSize);
	return byteSize;
}

// returns -1 on error
int DAUDIO_Read(void* id, char* data, int byteSize) {
	DS_Info* info = (DS_Info*) id;
	int available;
	int thisReadPos;
	DWORD captureCursor, readCursor;
	HRESULT res;
	void* buffer1, *buffer2;
	DWORD buffer1len, buffer2len;
	int bufferSize;

	TRACE1("> DAUDIO_Read %d bytes\n", byteSize);

	available = DS_GetAvailable(info, &captureCursor, &readCursor, &bufferSize, FALSE /* fromCaptureCursor? */);
	if (byteSize > available) byteSize = available;
	if (byteSize > 0) {
		thisReadPos = info->writePos;
		if (thisReadPos == -1) {
			/* from beginning */
			thisReadPos = 0;
		}
		res = info->captureBuffer->Lock(thisReadPos, byteSize,
			(LPVOID *) &buffer1, &buffer1len,
			(LPVOID *) &buffer2, &buffer2len,
			0);
		if (res != DS_OK) {
			/* can't recover from error */
			byteSize = 0;
		} else {
			/* buffer could be locked successfully */
			/* first fill first buffer */
			if (buffer1) {
				memcpy(data, buffer1, buffer1len);
				data = (char*) (((UINT_PTR) data) + buffer1len);
			} else buffer1len = 0;
			if (buffer2) {
				memcpy(data, buffer2, buffer2len);
			} else buffer2len = 0;
			byteSize = buffer1len + buffer2len;

			/* update next read pos */
			thisReadPos = DS_addPos(info, thisReadPos, byteSize);
			/* commit data to directsound */
			info->captureBuffer->Unlock(buffer1, buffer1len, buffer2, buffer2len);

			/* update position
			* must be BEFORE updating readPos,
			* so that getAvailable doesn't return too much,
			* so that getFramePos doesn't jump
			*/
			info->framePos += (byteSize / info->frameSize);

			info->writePos = thisReadPos;
		}
	}

	TRACE1("< DAUDIO_Read: returning %d bytes.\n", byteSize);
	return byteSize;
}


int DAUDIO_GetBufferSize(void* id, int isSource) {
	DS_Info* info = (DS_Info*) id;
	return info->bufferSizeInBytes;
}

int DAUDIO_StillDraining(void* id, int isSource) {
	DS_Info* info = (DS_Info*) id;
	BOOL draining = FALSE;
	int available, bufferSize;
	DWORD playCursor, writeCursor;

	DS_clearBuffer(info, TRUE /* from write position */);
	available = DS_GetAvailable(info, &playCursor, &writeCursor, &bufferSize, TRUE /* fromPlayCursor */);
	draining = (available < bufferSize);

	TRACE3("DAUDIO_StillDraining: available=%d  silencedBytes=%d  Still draining: %s\n",
		available, info->silencedBytes, draining?"TRUE":"FALSE");
	return draining;
}


int DAUDIO_Flush(void* id, int isSource) {
	DS_Info* info = (DS_Info*) id;

	TRACE0("DAUDIO_Flush\n");

	if (info->isSource)  {
		info->playBuffer->Stop();
		DS_clearBuffer(info, FALSE /* entire buffer */);
	} else {
		DWORD captureCursor, readCursor;
		/* set the read pointer to the current read position */
		if (FAILED(info->captureBuffer->GetCurrentPosition(&captureCursor, &readCursor))) {
			ERROR0("DAUDIO_Flush: ERROR: Failed to get current position.");
			return FALSE;
		}
		DS_clearBuffer(info, FALSE /* entire buffer */);
		/* SHOULD set to *captureCursor*,
		* but that would be detected as overflow
		* in a subsequent GetAvailable() call.
		*/
		info->writePos = (int) readCursor;
	}
	return TRUE;
}

int DAUDIO_GetAvailable(void* id, int isSource) {
	DS_Info* info = (DS_Info*) id;
	DWORD playCursor, writeCursor;
	int ret, bufferSize;

	ret = DS_GetAvailable(info, &playCursor, &writeCursor, &bufferSize, /*fromPlayCursor?*/ FALSE);

	TRACE1("DAUDIO_GetAvailable returns %d bytes\n", ret);
	return ret;
}

INT64 estimatePositionFromAvail(DS_Info* info, INT64 javaBytePos, int bufferSize, int availInBytes) {
	// estimate the current position with the buffer size and
	// the available bytes to read or write in the buffer.
	// not an elegant solution - bytePos will stop on xruns,
	// and in race conditions it may jump backwards
	// Advantage is that it is indeed based on the samples that go through
	// the system (rather than time-based methods)
	if (info->isSource) {
		// javaBytePos is the position that is reached when the current
		// buffer is played completely
		return (INT64) (javaBytePos - bufferSize + availInBytes);
	} else {
		// javaBytePos is the position that was when the current buffer was empty
		return (INT64) (javaBytePos + availInBytes);
	}
}

INT64 DAUDIO_GetBytePosition(void* id, int isSource, INT64 javaBytePos) {
	DS_Info* info = (DS_Info*) id;
	int available, bufferSize;
	DWORD playCursor, writeCursor;
	INT64 result = javaBytePos;

	available = DS_GetAvailable(info, &playCursor, &writeCursor, &bufferSize, /*fromPlayCursor?*/ TRUE);
	result = estimatePositionFromAvail(info, javaBytePos, bufferSize, available);
	return result;
}


void DAUDIO_SetBytePosition(void* id, int isSource, INT64 javaBytePos) {
	/* save to ignore, since GetBytePosition
	* takes the javaBytePos param into account
	*/
}

int DAUDIO_RequiresServicing(void* id, int isSource) {
	// need servicing on for SourceDataLines
	return isSource?TRUE:FALSE;
}

void DAUDIO_Service(void* id, int isSource) {
	DS_Info* info = (DS_Info*) id;
	if (isSource) {
		if (info->silencedBytes < info->dsBufferSizeInBytes) {
			// clear buffer
			TRACE0("DAUDIO_Service\n");
			DS_clearBuffer(info, TRUE /* from write position */);
		}
		if (info->writePos >= 0
			&& info->started
			&& !info->underrun
			&& info->silencedBytes >= info->dsBufferSizeInBytes) {
				// if we're currently playing, and the entire buffer is silenced...
				// then we are underrunning!
				info->underrun = TRUE;
				ERROR0("DAUDIO_Service: ERROR: DirectSound: underrun detected!\n");
		}
	}
}

void DAUDIO_AddAudioFormat(void* creatorV, int significantBits, int frameSizeInBytes,
						   int channels, float sampleRate,
						   int encoding, int isSigned,
						   int bigEndian)
{
   if (frameSizeInBytes <= 0) {
	   if (channels > 0) {
		   frameSizeInBytes = ((significantBits + 7) / 8) * channels;
	   } else {
		   frameSizeInBytes = -1;
	   }
   }

   TRACE4("AddAudioFormat with sigBits=%d bits, frameSize=%d bytes, channels=%d, sampleRate=%d ",
	   significantBits, frameSizeInBytes, channels, (int) sampleRate);
   TRACE3("enc=%d, signed=%d, bigEndian=%d\n\n", encoding, isSigned, bigEndian);
}

BOOL DrawGradient(HDC hdc, CONST RECT* pRect, CONST DWORD* cl, int Num, DWORD dwMode)
{
	int Width;
	int Height;
	TRIVERTEX *pvert;
	GRADIENT_RECT    *pgRect;

	if (cl == NULL || Num < 1 || pRect == NULL || dwMode == GRADIENT_FILL_TRIANGLE)
	{
		::SetLastError(ERROR_INVALID_PARAMETER);
		return TRUE;
	}

	if (Num == 1)
	{
		HBRUSH hBrush = CreateSolidBrush(cl[0]);
		SelectObject(hdc, hBrush);
		FillRect(hdc, pRect, hBrush);
		DeleteObject(hBrush);
		return TRUE;
	}

	pvert = new TRIVERTEX[Num * 2 - 2];
	pgRect = new GRADIENT_RECT[Num];

	Width = pRect->right - pRect->left;
	Height = pRect->bottom - pRect->top;
	for (int i = 0; i < Num - 1; i++)
	{
		if (dwMode == GRADIENT_FILL_RECT_V)
		{
			pvert[i * 2].x = pRect->left;
			pvert[i * 2].y = pRect->top + Height / (Num - 1) * i;

			pvert[i * 2 + 1].x = pRect->right;
			pvert[i * 2 + 1].y = pRect->top + Height / (Num - 1) * (i + 1);
		}
		else if (dwMode == GRADIENT_FILL_RECT_H)
		{
			pvert[i * 2].x = pRect->left + Width / (Num - 1) * i;
			pvert[i * 2].y = pRect->top;

			pvert[i * 2 + 1].x = pRect->left + Width / (Num - 1) * (i + 1);
			pvert[i * 2 + 1].y = pRect->bottom;
		}

		pvert[i * 2] .Red    = (WORD)GetRValue((cl[i])) << 8;
		pvert[i * 2] .Green  = (WORD)GetGValue((cl[i])) << 8;
		pvert[i * 2] .Blue   = (WORD)GetBValue((cl[i])) << 8;
		pvert[i * 2] .Alpha  = 0x0000;

		pvert[i * 2 + 1] .Red    = (WORD)GetRValue((cl[i + 1])) << 8;
		pvert[i * 2 + 1] .Green  = (WORD)GetGValue((cl[i + 1])) << 8;
		pvert[i * 2 + 1] .Blue   = (WORD)GetBValue((cl[i + 1])) << 8;
		pvert[i * 2 + 1] .Alpha  = 0x0000;

		pgRect[i].UpperLeft  = i * 2;
		pgRect[i].LowerRight = i * 2 + 1;
	}

	BOOL bRet = ::GradientFill(hdc, pvert, Num * 2, pgRect, Num - 1, dwMode);

	delete []pvert;
	delete []pgRect;

	return bRet;
}

#endif

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer none
China China
To be, or not to be, this is question. That's are all depend on your decision. What do you think?

Comments and Discussions