// Emacs style mode select   -*- C++ -*-
//-----------------------------------------------------------------------------
//
// Copyright (C) 2001 by DooM Legacy Team.
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License
// as published by the Free Software Foundation; either version 2
// 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 General Public License for more details.
//-----------------------------------------------------------------------------
/// \file
/// \brief General driver for 3D sound system
///
///	Implementend via DirectSound3D API

#define INITGUID
//#define WIN32_LEAN_AND_MEAN
#define RPC_NO_WINDOWS_H
#include <windows.h>
#include <cguid.h>
#define DIRECTSOUND_VERSION  0x0700       /* version 7.0 */

#ifdef __MINGW32__
#define NONAMELESSUNION
#endif
#include <dsound.h>

#if (defined (DIRECTSOUND_VERSION) && (DIRECTSOUND_VERSION >= 0x0700))
#define DX7 //Enable DX7 or up  only code
#undef DUMMYUNIONNAMEN
#endif

#ifdef __MINGW32__
//#undef DX7
#endif

#define  _CREATE_DLL_
#include "../hw3dsdrv.h"
#include "../../m_fixed.h"
#include "../../doomdef.h"
//#define VERSIONSTRING "v1.08"

#undef DEBUG_TO_FILE
#define DEBUG_TO_FILE

#if defined ( HAVE_SDL ) && !defined ( LOGMESSAGES )
#undef DEBUG_TO_FILE
#endif


// Internal sound stack
typedef struct stack_snd_s
{
	// Sound data
	LPDIRECTSOUNDBUFFER     dsbuffer;

	// 3D data of 3D source
	LPDIRECTSOUND3DBUFFER   dsbuffer3D;     // 3D data

	// Current parameters of 3D source
	// Valid only when source is 3D source
	// (dsbuffer3D is not NULL)
	DS3DBUFFER              parameters;

	// Currently unused
	INT32                     sfx_id;

	// Currently unused
	INT32                     LRU;

	// Flag of static source
	// Driver does not manage intrenally such sources
	INT32                     permanent;

} stack_t;

// Just for now...
#define SOUND_ALLOCATE_DELTA    16       // Amount of sources per stack incrementation
#define MAX_LRU                 16       // Maximum iterations to keep source in stack

static stack_t  *_stack;                 // Sound stack
static INT32      allocated_sounds;        // Size of stack

static INT32      srate;                   // Default sample rate


// output all debugging messages to this file
#if defined (DEBUG_TO_FILE)
#include <stdio.h>
FILE *logstream = NULL;
#endif

static LPDIRECTSOUND            DSnd            = NULL;  // Main DirectSound object
static LPDIRECTSOUNDBUFFER      PrimaryBuffer   = NULL;  //
static LPDIRECTSOUND3DLISTENER  Listener        = NULL;  //

static DS3DLISTENER             listener_parms;          // Listener papameters

#ifdef DX7
static BOOL                     virtualization;          // TRUE if HRTF virtualization enabled
#endif

static DWORD                    update_mode;             // Current update mode of listener
//static DWORD                    max_3d_buffers;

static I_Error_t I_ErrorDS3D = NULL;

//static stack_snd_t sound_stack[MAX_SOUNDS];

// Safe buffer release
#define RELEASE_BUFFER(buf)     {if (buf) { IDirectSoundBuffer_Release(buf); (buf) = NULL; }}
#define RELEASE_3DBUFFER(buf)   {if (buf) { IDirectSound3DBuffer_Release(buf); (buf) = NULL; }}

// Default flags for buffers
#define _2DSOURCE_FLAGS (DSBCAPS_CTRLPAN | DSBCAPS_CTRLVOLUME | DSBCAPS_STICKYFOCUS | DSBCAPS_STATIC | DSBCAPS_CTRLFREQUENCY)
#define _3DSOURCE_FLAGS (DSBCAPS_CTRL3D | DSBCAPS_STATIC | DSBCAPS_MUTE3DATMAXDISTANCE | DSBCAPS_CTRLVOLUME | DSBCAPS_CTRLFREQUENCY)


enum {IS_2DSOURCE = 0, IS_3DSOURCE = 1};

#define NEW_HANDLE  -1

#ifdef _WINDOWS
BOOL WINAPI DllMain(HINSTANCE hinstDLL, // handle to DLL module
                    DWORD fdwReason,    // reason for calling function
                    LPVOID lpvReserved) // reserved
{
	// Perform actions based on the reason for calling
	UNREFERENCED_PARAMETER(lpvReserved);
	switch ( fdwReason )
	{
		case DLL_PROCESS_ATTACH:
			// Initialize once for each new process.
			// Return FALSE to fail DLL load.
#ifdef DEBUG_TO_FILE
			logstream = fopen("s_ds3d.log", "wt");
			if (logstream == NULL)
				return FALSE;
#endif
			DisableThreadLibraryCalls(hinstDLL);
			break;

		case DLL_THREAD_ATTACH:
			// Do thread-specific initialization.
			break;

		case DLL_THREAD_DETACH:
			// Do thread-specific cleanup.
			break;

		case DLL_PROCESS_DETACH:
			// Perform any necessary cleanup.
#ifdef DEBUG_TO_FILE
			if (logstream)
			{
				fclose(logstream);
				logstream = NULL;
			}
#endif
			break;
	}
	return TRUE;  // Successful DLL_PROCESS_ATTACH.
}
#endif

// **************************************************************************
//                                                                  FUNCTIONS
// **************************************************************************

/***************************************************************
 *
 * DBG_Printf
 * Output error messages to debug log if DEBUG_TO_FILE is defined,
 * else do nothing
 *
 ***************************************************************
 */
// -----------------+
// DBG_Printf       : Output error messages to debug log if DEBUG_TO_FILE is defined,
//                  : else do nothing
// Returns          :
// -----------------+
void DBG_Printf(const char *lpFmt, ... )
{
#ifdef DEBUG_TO_FILE
	char    str[4096] = "";
	va_list arglist;

	va_start(arglist, lpFmt);
	vsnprintf(str, 4096, lpFmt, arglist);
	va_end(arglist);

	if (logstream)
		fwrite(str, strlen(str), 1, logstream);
#endif
}


/***************************************************************
 *
 * Grow intrenal sound stack by SOUND_ALLOCATE_DELTA amount
 *
 ***************************************************************
 */
static BOOL reallocate_stack(void)
{
	stack_t *new_stack;

	if (_stack)
		new_stack = realloc(_stack, sizeof (stack_t) * (allocated_sounds + SOUND_ALLOCATE_DELTA));
	else
		new_stack = malloc(sizeof (stack_t) * (allocated_sounds + SOUND_ALLOCATE_DELTA));
	if (new_stack)
	{
		_stack = new_stack;
		ZeroMemory(&_stack[allocated_sounds], SOUND_ALLOCATE_DELTA * sizeof (stack_t));
		allocated_sounds += SOUND_ALLOCATE_DELTA;
	}
	return (new_stack != NULL);
}


/***************************************************************
 *
 * Destroys source in stack
 *
 ***************************************************************
 */
static void kill_sound(stack_t *snd)
{
	//stack_t *snd = _stack + handle;

	if (snd->dsbuffer3D)
		RELEASE_3DBUFFER(snd->dsbuffer3D);

	if (snd->dsbuffer)
		RELEASE_BUFFER(snd->dsbuffer);

	ZeroMemory(snd, sizeof (stack_t));
}


/***************************************************************
 *
 * Returns TRUE if source currently playing
 *
 ***************************************************************
 */
static BOOL is_playing(stack_t *snd)
{
	DWORD               status;
	LPDIRECTSOUNDBUFFER dsbuffer = snd->dsbuffer;

	if (dsbuffer == NULL)
		return 0;

	IDirectSoundBuffer_GetStatus(dsbuffer, &status);

	return (status & (DSBSTATUS_PLAYING | DSBSTATUS_LOOPING));
}


/***************************************************************
 *
 * Creates DirectSound buffer and fills with sound data
 * NULL sound data pointer are valid (empty buffer
 * will be created)
 *
 ***************************************************************
 */
static LPDIRECTSOUNDBUFFER create_buffer (void *data, INT32 length, BOOL as3d)
{
	LPDIRECTSOUNDBUFFER dsbuffer;
	HRESULT             hr;
	WAVEFORMATEX        wfm;
	DSBUFFERDESC        dsbdesc;
	LPVOID              lpvAudio1; // receives address of lock start
	DWORD               dwBytes1;  // receives number of bytes locked
	LPVOID              lpvAudio2; // receives address of lock start
	DWORD               dwBytes2;  // receives number of bytes locked

	ZeroMemory(&wfm, sizeof (WAVEFORMATEX));
	wfm.wFormatTag = WAVE_FORMAT_PCM;
	wfm.nChannels = 1;
	wfm.nSamplesPerSec = data?*((UINT16 *)data+1):srate;      //mostly 11025, but some at 22050.
	wfm.wBitsPerSample = 8;
	wfm.nBlockAlign = (WORD)(wfm.wBitsPerSample / 8 * wfm.nChannels);
	wfm.nAvgBytesPerSec = wfm.nSamplesPerSec * wfm.nBlockAlign;

	// Set up DSBUFFERDESC structure.
	ZeroMemory (&dsbdesc, sizeof (DSBUFFERDESC) );
	dsbdesc.dwSize = sizeof (DSBUFFERDESC);
	dsbdesc.dwFlags = as3d?_3DSOURCE_FLAGS:_2DSOURCE_FLAGS;
	dsbdesc.dwBufferBytes = length;
	dsbdesc.lpwfxFormat = &wfm;

	// DirectX 7.0 and above!
	// Try to enable full HRTF virtualization algorithm for
	// two-speakers or headphones
#ifdef DX7
	if (as3d)
		dsbdesc.guid3DAlgorithm = (virtualization?DS3DALG_HRTF_FULL:DS3DALG_DEFAULT);
#endif

	hr = IDirectSound_CreateSoundBuffer (DSnd, &dsbdesc, &dsbuffer, NULL);

	// CreateSoundBuffer might return DS_NO_VIRTUALIZATION so uses FAILED
	// macro rather than check explictly for DS_OK value
	if (FAILED(hr))
	{
		DBG_Printf("CreateSoundBuffer FAILED. Code %d\n", hr);
		return NULL;
	}

	if (data)
	{
		hr = IDirectSoundBuffer_Lock(dsbuffer, 0, length, &lpvAudio1, &dwBytes1, &lpvAudio2, &dwBytes2, 0);

		// If DSERR_BUFFERLOST is returned, restore and retry lock.
		if (hr == DSERR_BUFFERLOST)
		{
			hr = IDirectSoundBuffer_Restore(dsbuffer);
			if (FAILED(hr))
				I_ErrorDS3D("Restore fail on %x, code %d\n",dsbuffer, hr);
			hr = IDirectSoundBuffer_Lock(dsbuffer, 0, length, &lpvAudio1, &dwBytes1, &lpvAudio2, &dwBytes2, 0);
			if (FAILED(hr))
				I_ErrorDS3D("Lock fail(2) on %x, code %d\n",dsbuffer, hr);
		}
		else
			if (FAILED(hr))
				I_ErrorDS3D("Lock fail(1) on %x, code %d\n",dsbuffer, hr);

		// copy wave data into the buffer (note: dwBytes1 should equal to dsbdesc->dwBufferBytes ...)
		CopyMemory (lpvAudio1, (LPBYTE)data+8, dwBytes1);

		if ( dwBytes2 && lpvAudio2)
			CopyMemory(lpvAudio2, ((LPBYTE)data + 8) + dwBytes1, dwBytes2);


		// finally, unlock the buffer
		hr = IDirectSoundBuffer_Unlock(dsbuffer, lpvAudio1, dwBytes1, lpvAudio2, dwBytes2);

		if (FAILED(hr))
			I_ErrorDS3D("Unlock fail on %x, code %d\n",dsbuffer, hr);
	}

	return dsbuffer;
}


/***************************************************************
 *
 * Creates 3D source data buffer
 *
 ***************************************************************
 */
static LPDIRECTSOUND3DBUFFER create_3dbuffer(LPDIRECTSOUNDBUFFER dsbuffer, LPDIRECTSOUND3DBUFFER source3d)
{
	HRESULT hr;

	hr = IDirectSoundBuffer_QueryInterface(dsbuffer,
	 &IID_IDirectSound3DBuffer, (void **)&source3d);

	if (FAILED(hr))
	{
		DBG_Printf("Couldn't obtain 3D Buffer interface. Code %d\n", hr);
		return NULL;
	}
#ifdef DX7
	if (hr == DS_NO_VIRTUALIZATION)
	{
		DBG_Printf("The 3D virtualization is not supported under this OS.\n");
			virtualization = FALSE;
	}
#endif
	return source3d;
}


/***************************************************************
 *
 * Returns free (unused) source stack slot
 * If none available sound stack will be increased
 *
 ***************************************************************
 */
static INT32 find_handle(INT32 new_sfx_id, INT32 new_is3d)
{
	INT32      free_sfx;
	stack_t *snd;

	// At first do look for sound with same sfx ID and reuse it
	new_sfx_id = new_is3d = 0;
	for (free_sfx = 0, snd = _stack; free_sfx < allocated_sounds; snd++, free_sfx++)
	{

		if (snd->permanent)
			continue;
/*
		if (!is_playing(snd))
		{
			if (same_sfx == 0 && snd->sfx_id == new_sfx_id && snd->dsbuffer)
			{
				same_sfx = i;
				continue;
			}

			if (snd->sfx_id && ++snd->LRU >= MAX_LRU)
				kill_sound(snd);
		}
*/
		if (snd->dsbuffer == 0)
			break;
			//free_sfx = i;
	}

	// No suitable resource found so increase sound stack
	if (free_sfx == allocated_sounds)
	{
		DBG_Printf("No free or same sfx found so increase stack (currently %d srcs)\n", allocated_sounds);
		free_sfx = reallocate_stack() ? free_sfx : -1;
	}

	return free_sfx;
}


/***************************************************************
 *
 * Recalculates volume from Doom scale to DirectSound scale
 *
 ***************************************************************
 */
static INT32 recalc_volume(INT32 base_vol, INT32 steps)
{
	return (base_vol * ((DSBVOLUME_MAX-DSBVOLUME_MIN)/4)) / steps
	       + (DSBVOLUME_MAX - ((DSBVOLUME_MAX-DSBVOLUME_MIN)/4));
}


/***************************************************************
 *
 * Updates sound volume of the DirectSound buffer
 *
 ***************************************************************
 */
static void UpdateSoundVolume (LPDIRECTSOUNDBUFFER lpSnd, INT32 volume)
{
	/*volume = (volume * ((DSBVOLUME_MAX-DSBVOLUME_MIN)/4)) / 256 +
	         (DSBVOLUME_MAX - ((DSBVOLUME_MAX-DSBVOLUME_MIN)/4));

	IDirectSoundBuffer_SetVolume (lpSnd, volume);*/
	IDirectSoundBuffer_SetVolume (lpSnd, recalc_volume(volume, 256));
}


// --------------------------------------------------------------------------
// Update the panning for a secondary buffer, make sure it was created with
// DSBCAPS_CTRLPAN
// --------------------------------------------------------------------------
#define DSBPAN_RANGE    (DSBPAN_RIGHT-(DSBPAN_LEFT))

//Doom sounds pan range 0-255 (128 is centre)
#define SEP_RANGE       256
static void Update2DSoundPanning (LPDIRECTSOUNDBUFFER lpSnd, INT32 sep)
{
	HRESULT hr;
	hr = IDirectSoundBuffer_SetPan (lpSnd, (sep * DSBPAN_RANGE)/SEP_RANGE - DSBPAN_RIGHT);
	//if (FAILED(hr))
	//	DEBFILE(va("SetPan FAILED for sep %d pan %d\n", sep, (sep * DSBPAN_RANGE)/SEP_RANGE - DSBPAN_RIGHT));
}



/******************************************************************************
 *
 * Initialise driver and listener
 *
 *****************************************************************************/
EXPORT BOOL HWRAPI( Startup ) (I_Error_t FatalErrorFunction, snddev_t *snd_dev)
{
	HRESULT      hr;
	DSBUFFERDESC desc;
	WAVEFORMATEX wfm;
	DSCAPS       dscaps;
	DWORD        speakers;
	DWORD        speaker_config;
	DWORD        speaker_geometry;

	I_ErrorDS3D = FatalErrorFunction;
	DBG_Printf ("S_DS3D Init(): DirectSound3D driver for SRB2 v1.09"); // Tails

	DBG_Printf("Initialising DirectSound3D...\n");
	hr = DirectSoundCreate( NULL, &DSnd, NULL);
	if (FAILED(hr))
	{
		DBG_Printf("Failed to obtain DirectSound\n");
		return FALSE;
	}

	hr = IDirectSound_SetCooperativeLevel(DSnd, snd_dev->hWnd, snd_dev->cooplevel);
	if (FAILED(hr))
	{
		DBG_Printf("Couldn't set coopertive level\n");
		return FALSE;
	}

	dscaps.dwSize = sizeof (DSCAPS);

	IDirectSound_GetCaps(DSnd, &dscaps);
	IDirectSound_GetSpeakerConfig(DSnd, &speakers);

	DBG_Printf("Sound hardware capabilities:\n");
	DBG_Printf("   Driver is %scertified.\n", (dscaps.dwFlags & DSCAPS_CERTIFIED) == DSCAPS_CERTIFIED?"":"not ");
	DBG_Printf("   Maximum hardware mixing buffers %d\n", dscaps.dwMaxHwMixingAllBuffers);
	DBG_Printf("   Maximum hardware 3D buffers %d\n", dscaps.dwFreeHw3DAllBuffers);

	speaker_config = DSSPEAKER_CONFIG(speakers);
	speaker_geometry = DSSPEAKER_GEOMETRY(speakers);

	DBG_Printf("Current speaker configuration: ");

	switch (speaker_config)
	{
#ifdef DX7
		case DSSPEAKER_5POINT1:
			DBG_Printf("5.1 (5 speakers with subwoofer).\n");
			break;
#endif
		case DSSPEAKER_HEADPHONE:
			DBG_Printf("headphone.\n");
			break;

		case DSSPEAKER_MONO:
			DBG_Printf("single speaker (mono).\n");
			break;

		case DSSPEAKER_QUAD:
			DBG_Printf("quadrophonic\n");
			break;

		case DSSPEAKER_SURROUND:
			DBG_Printf("surround.\n");
			break;

		case DSSPEAKER_STEREO:
			DBG_Printf("stereo with %s geometry ",
				speaker_geometry == DSSPEAKER_GEOMETRY_WIDE
					? "wide (arc of 20 deg.)"
					: speaker_geometry == DSSPEAKER_GEOMETRY_NARROW
						? "narrow (arc of 10 deg.)"
						: speaker_geometry == DSSPEAKER_GEOMETRY_MIN
							? "min (arc of 5 deg.)"
							: speaker_geometry == DSSPEAKER_GEOMETRY_MAX
								? "max (arc of 180 deg.)"
								: "unknown");
			break;
		default:
			DBG_Printf("undetectable.\n");

	}

	update_mode = DS3D_IMMEDIATE;

	// Create primary sound buffer
	ZeroMemory(&desc, sizeof (DSBUFFERDESC));
	desc.dwSize = sizeof (DSBUFFERDESC);
	desc.dwFlags = DSBCAPS_CTRL3D | DSBCAPS_PRIMARYBUFFER | DSBCAPS_CTRLVOLUME;
	desc.dwBufferBytes = 0;
	desc.lpwfxFormat = NULL;

	hr = IDirectSound_CreateSoundBuffer(DSnd, &desc, &PrimaryBuffer, NULL);
	if (FAILED(hr))
	{
		DBG_Printf("CreateSoundBuffer FAILED (ErrNo %d)\n", hr);
		return FALSE;
	}

	// Query for 3D Listener object
	hr = IDirectSoundBuffer_QueryInterface(PrimaryBuffer,
	 &IID_IDirectSound3DListener, (void **)&Listener);

	if (FAILED(hr))
	{
		DBG_Printf("Couldn't obtain 3D Listener interface (ErrNo %d)\n", hr);
		return FALSE;
	}

	// Set up initial listsner parameters
	IDirectSound3DListener_SetDistanceFactor(Listener, 1.0f/72.0f, DS3D_IMMEDIATE);
	IDirectSound3DListener_SetRolloffFactor(Listener, 1.6f, DS3D_IMMEDIATE);
	//IDirectSound3DListener_SetRolloffFactor(Listener, DS3D_MAXROLLOFFFACTOR, DS3D_IMMEDIATE);
	listener_parms.dwSize = sizeof (DS3DLISTENER);
	IDirectSound3DListener_GetAllParameters(Listener, &listener_parms);

	ZeroMemory (&wfm, sizeof (WAVEFORMATEX));
	wfm.wFormatTag = WAVE_FORMAT_PCM;
	wfm.nChannels = 2;
	wfm.nSamplesPerSec = srate = snd_dev->sample_rate;
	wfm.wBitsPerSample = (WORD)snd_dev->bps;
	wfm.nBlockAlign = (WORD)(wfm.wBitsPerSample / 8 * wfm.nChannels);
	wfm.nAvgBytesPerSec = wfm.nSamplesPerSec * wfm.nBlockAlign;

	if (snd_dev->cooplevel >= DSSCL_PRIORITY)
	{
		hr = IDirectSoundBuffer_SetFormat(PrimaryBuffer, &wfm);
		if (FAILED(hr))
			DBG_Printf("Couldn't set primary buffer format.\n");

		DBG_Printf(" Compacting onboard sound-memory...");
		hr = IDirectSound_Compact(DSnd);
		DBG_Printf(" %s\n", SUCCEEDED(hr) ? M_GetText("Done\n") : M_GetText("Failed\n"));
	}

#ifdef DX7
	// Initially enables HRTF virtualization (may be changed later)
	virtualization = TRUE;
#endif

	_stack = NULL;
	allocated_sounds = 0;
	IDirectSoundBuffer_Play(PrimaryBuffer, 0, 0, DSBPLAY_LOOPING);
	return reallocate_stack();
}



/***************************************************************
 *
 * Shutdown driver
 *
 ***************************************************************
 */
EXPORT void HWRAPI( Shutdown ) (void)
{
	INT32 i;

	DBG_Printf ("S_DS3D Shutdown()\n");

	for (i = 0; i < allocated_sounds; i++)
	{
		StopSource(i);
		kill_sound(_stack + i);
	}

	if (_stack)
		free(_stack);

	if (Listener)
	{
		IDirectSound3DListener_Release(Listener);
		Listener = NULL;
	}

	if (PrimaryBuffer)
		RELEASE_BUFFER(PrimaryBuffer);

	if (DSnd)
	{
		IDirectSound_Release(DSnd);
		DSnd = NULL;
	}

}



EXPORT INT32 HWRAPI (IsPlaying) (INT32 handle)
{
	if (handle < 0 || handle >= allocated_sounds)
		return FALSE;

	return is_playing(_stack + handle);
}


// Calculate sound pitching
static float recalc_pitch(INT32 doom_pitch)
{
	return doom_pitch < NORMAL_PITCH ?
	 (float)(doom_pitch + NORMAL_PITCH) / (NORMAL_PITCH * 2)
	 :(float)doom_pitch / (float)NORMAL_PITCH;
}


static stack_t *setup_source(INT32 handle, sfx_data_t *sfx, BOOL is_3dsource)
{
	stack_t               *snd;
	//INT32                  handle;
	INT32                    data_length;
	LPDIRECTSOUNDBUFFER    dsbuffer = NULL;
	LPDIRECTSOUND3DBUFFER  ds3dbuffer = NULL;

	if (handle == NEW_HANDLE)
		handle = find_handle(sfx?sfx->id:0, is_3dsource);

	snd = _stack + handle;

	// Check for reused source
	if (snd->dsbuffer)
		return snd;

	data_length = sfx?sfx->length - 8:DSBSIZE_MIN;

	dsbuffer = create_buffer(sfx ? sfx->data : NULL, data_length, is_3dsource);
	if (dsbuffer)
	{

		if (is_3dsource)
		{
			ds3dbuffer = create_3dbuffer(dsbuffer, ds3dbuffer);
			if (!ds3dbuffer)
			{
				RELEASE_BUFFER(dsbuffer);
				return NULL;
			}
			snd->parameters.dwSize = sizeof (DS3DBUFFER);
			IDirectSound3DBuffer_GetAllParameters(ds3dbuffer, &snd->parameters);
		}

		// judgecutor: sound pitching
		if (sfx)
		{
			DWORD freq;

			freq = (DWORD)((float)(*((UINT16 *)sfx->data+1)) * recalc_pitch(sfx->pitch));
			IDirectSoundBuffer_SetFrequency(dsbuffer, freq);
		}

		snd->dsbuffer = dsbuffer;
		snd->dsbuffer3D = ds3dbuffer;
		snd->LRU = 0;
		snd->sfx_id = sfx ? sfx->id : 0;
		if (!is_3dsource)
			snd->permanent = 0;
		return snd;
	}
	return NULL;

}

/******************************************************************************
 *
 * Creates 2D (stereo) source
 *
 ******************************************************************************/
EXPORT INT32 HWRAPI ( Add2DSource ) (sfx_data_t *sfx)
{
	stack_t *snd;

	if (!sfx)
		return -1;

	snd = setup_source(NEW_HANDLE, sfx, IS_2DSOURCE);
	if (snd)
	{
		UpdateSoundVolume(snd->dsbuffer, sfx->volume);
		Update2DSoundPanning(snd->dsbuffer, sfx->sep);
	}
	return snd - _stack;
}

/******************************************************************************
 *
 * Creates 3D source
 *
 ******************************************************************************/

EXPORT INT32 HWRAPI ( Add3DSource ) (source3D_data_t *src, sfx_data_t *sfx)
{
	stack_t *snd;

	snd = setup_source(NEW_HANDLE, sfx, IS_3DSOURCE);

	if (snd)
	{

		/*x = src->pos.x;
		y = src->pos.z;
		z = src->pos.y;

		IDirectSound3DBuffer_SetPosition(source, x, y, z, update_mode);
		IDirectSound3DBuffer_SetVelocity(source, src->pos.momx, src->pos.momz, src->pos.momy, update_mode);
		IDirectSound3DBuffer_SetMode(source, src->head_relative?DS3DMODE_HEADRELATIVE:DS3DMODE_NORMAL, update_mode);*/

#ifdef DUMMYUNIONNAMEN
		snd->parameters.vPosition.DUMMYUNIONNAMEN(9).x = src->pos.x;
		snd->parameters.vPosition.DUMMYUNIONNAMEN(10).y = src->pos.z;
		snd->parameters.vPosition.DUMMYUNIONNAMEN(11).z = src->pos.y;

		snd->parameters.vVelocity.DUMMYUNIONNAMEN(9).x = src->pos.momx;
		snd->parameters.vVelocity.DUMMYUNIONNAMEN(10).y = src->pos.momz;
		snd->parameters.vVelocity.DUMMYUNIONNAMEN(11).z = src->pos.momy;
#else
		snd->parameters.vPosition.x = src->pos.x;
		snd->parameters.vPosition.y = src->pos.z;
		snd->parameters.vPosition.z = src->pos.y;

		snd->parameters.vVelocity.x = src->pos.momx;
		snd->parameters.vVelocity.y = src->pos.momz;
		snd->parameters.vVelocity.z = src->pos.momy;
#endif
		snd->parameters.dwMode = src->head_relative ? DS3DMODE_HEADRELATIVE : DS3DMODE_NORMAL;

		snd->parameters.flMinDistance = src->min_distance;
		snd->parameters.flMaxDistance = src->max_distance;
		//snd->parameters.flMaxDistance = DS3D_DEFAULTMAXDISTANCE;

		snd->permanent = src->permanent;

		UpdateSoundVolume(snd->dsbuffer, sfx?sfx->volume:255);
		IDirectSound3DBuffer_SetAllParameters(snd->dsbuffer3D, &snd->parameters, DS3D_IMMEDIATE);
		IDirectSoundBuffer_SetCurrentPosition(snd->dsbuffer, 0);
	}

	return (snd - _stack);
}

/******************************************************************************
 *
 * Destroy source and remove it from stack if it is a 2D source.
 * Otherwise put source into cache
 *
 *****************************************************************************/
EXPORT void HWRAPI (KillSource) (INT32 handle)
{

	if (handle < 0 || handle >= allocated_sounds)
		return;
/*
		if (_stack[handle].dsbuffer3D)
	{
		// It's a 3D source so let him chance to be reused :-)
		_stack[handle].LRU = 1;
	}
	else
	{ */
		// No, it is a 2D source so kill him
		kill_sound(_stack + handle);
	//}
}


/******************************************************************************
 *
 * Update volume and separation (panning) of 2D source
 *
 *****************************************************************************/
EXPORT void HWRAPI (Update2DSoundParms) (INT32 handle, INT32 vol, INT32 sep)
{
	LPDIRECTSOUNDBUFFER dsbuffer;

	if (handle < 0 || handle >= allocated_sounds)
		return;

	if ((_stack+handle)->dsbuffer3D)
		return;
	dsbuffer = (_stack + handle)->dsbuffer;

	UpdateSoundVolume(dsbuffer, vol);
	Update2DSoundPanning(dsbuffer, sep);
}



// --------------------------------------------------------------------------
// Set the global volume for sound effects
// --------------------------------------------------------------------------
EXPORT void HWRAPI (SetGlobalSfxVolume) (INT32 volume)
{
	INT32     vol;
	HRESULT hr;

	// use the last quarter of volume range
	if (volume)
		vol = recalc_volume(volume, 31);
	else
		vol = DSBVOLUME_MIN;

	hr = IDirectSoundBuffer_SetVolume (PrimaryBuffer, vol);
}

EXPORT void HWRAPI ( StopSource) (INT32 handle)
{
	LPDIRECTSOUNDBUFFER dsbuffer;

	if (handle < 0 || handle >= allocated_sounds)
		return;

	dsbuffer = (_stack + handle)->dsbuffer;

	if (dsbuffer)
	{
		IDirectSoundBuffer_Stop(dsbuffer);
		IDirectSoundBuffer_SetCurrentPosition(dsbuffer, 0);
	}
}


EXPORT INT32 HWRAPI ( GetHW3DSVersion) (void)
{
	return VERSION;
}


EXPORT void HWRAPI (BeginFrameUpdate) (void)
{
	update_mode = DS3D_DEFERRED;
}


EXPORT void HWRAPI (EndFrameUpdate) (void)
{
	if (update_mode == DS3D_DEFERRED)
		IDirectSound3DListener_CommitDeferredSettings(Listener);

	update_mode = DS3D_IMMEDIATE;
}


/******************************************************************************
 * UpdateListener
 *
 * Set up main listener properties:
 * - position
 * - orientation
 * - velocity
 *****************************************************************************/
EXPORT void HWRAPI (UpdateListener) (listener_data_t *data)
{
	D3DVECTOR pos;
	D3DVECTOR face;
	D3DVECTOR head;
	D3DVECTOR velocity;
	double      f_angle;
	//double      h_angle, t_angle;
	//double      f_cos, f_sin, t_sin;

#ifdef DUMMYUNIONNAMEN
	pos.DUMMYUNIONNAMEN(9).x = (D3DVALUE)data->x;
	pos.DUMMYUNIONNAMEN(10).y = (D3DVALUE)data->z;
	pos.DUMMYUNIONNAMEN(11).z = (D3DVALUE)data->y;

	velocity.DUMMYUNIONNAMEN(9).x = (D3DVALUE)data->momx;
	velocity.DUMMYUNIONNAMEN(10).y = (D3DVALUE)data->momz;
	velocity.DUMMYUNIONNAMEN(11).z = (D3DVALUE)data->momy;
#else
	pos.x = (D3DVALUE)data->x;
	pos.y = (D3DVALUE)data->z;
	pos.z = (D3DVALUE)data->y;

	velocity.x = (D3DVALUE)data->momx;
	velocity.y = (D3DVALUE)data->momz;
	velocity.z = (D3DVALUE)data->momy;
#endif
	f_angle = (data->f_angle) / 180 * M_PI;
	//h_angle = data->h_angle / 180 * M_PI;
	//h_angle = 90 / 180 * M_PI;

	// Treat listener orientation angles as spherical coordinates.
	// x = sin h * cos f
	// y = sin h * sin f
	// z = cos h

	// Front vector
	//t_angle = (M_PI/2 - h_angle);
	//t_angle = M_PI/2;

	//face.x = (t_sin = sin(t_angle)) * (f_cos = cos(f_angle));
	//face.z = t_sin * (f_sin = sin(f_angle));
	//face.y = cos(t_angle);
#ifdef DUMMYUNIONNAMEN
	face.DUMMYUNIONNAMEN(9).x = (D3DVALUE)cos(f_angle);
	face.DUMMYUNIONNAMEN(11).z = (D3DVALUE)sin(f_angle);
	face.DUMMYUNIONNAMEN(10).y = 0.0f;

	head.DUMMYUNIONNAMEN(9).x = 0.0f;
	head.DUMMYUNIONNAMEN(10).y = 1.0f;
	head.DUMMYUNIONNAMEN(11).z = 0.0f;
#else
	face.x = (D3DVALUE)cos(f_angle);
	face.z = (D3DVALUE)sin(f_angle);
	face.y = 0.0f;

	head.x = 0.0f;
	head.y = 1.0f;
	head.z = 0.0f;
#endif
	// Top vector
	//h_angle = (-data->h_angle) / 180 * M_PI;

/*
	t_angle = -h_angle;

	head.z = (t_sin = sin(t_angle)) * f_cos;
	head.x = t_sin * f_sin;
	head.y = cos(t_angle);
*/

	// Update at once
	CopyMemory(&listener_parms.vPosition, &pos, sizeof (D3DVECTOR));
	CopyMemory(&listener_parms.vOrientFront, &face, sizeof (D3DVECTOR));
	CopyMemory(&listener_parms.vOrientTop, &head, sizeof (D3DVECTOR));
	CopyMemory(&listener_parms.vVelocity, &velocity, sizeof (D3DVECTOR));
	//IDirectSound3DListener_SetAllParameters(Listener, &listener_parms, DS3D_IMMEDIATE);
	IDirectSound3DListener_SetAllParameters(Listener, &listener_parms, update_mode);
}

EXPORT void HWRAPI (UpdateListener2) (listener_data_t *data)
{
	D3DVECTOR pos;
	D3DVECTOR face;
	D3DVECTOR head;
	D3DVECTOR velocity;
	double      f_angle;
	//double      h_angle, t_angle;
	//double      f_cos, f_sin, t_sin;

	if (!data) return;

#ifdef DUMMYUNIONNAMEN
	pos.DUMMYUNIONNAMEN(9).x = (D3DVALUE)data->x;
	pos.DUMMYUNIONNAMEN(10).y = (D3DVALUE)data->z;
	pos.DUMMYUNIONNAMEN(11).z = (D3DVALUE)data->y;

	velocity.DUMMYUNIONNAMEN(9).x = (D3DVALUE)data->momx;
	velocity.DUMMYUNIONNAMEN(10).y = (D3DVALUE)data->momz;
	velocity.DUMMYUNIONNAMEN(11).z = (D3DVALUE)data->momy;
#else
	pos.x = (D3DVALUE)data->x;
	pos.y = (D3DVALUE)data->z;
	pos.z = (D3DVALUE)data->y;

	velocity.x = (D3DVALUE)data->momx;
	velocity.y = (D3DVALUE)data->momz;
	velocity.z = (D3DVALUE)data->momy;
#endif
	f_angle = (data->f_angle) / 180 * M_PI;
	//h_angle = data->h_angle / 180 * M_PI;
	//h_angle = 90 / 180 * M_PI;

	// Treat listener orientation angles as spherical coordinates.
	// x = sin h * cos f
	// y = sin h * sin f
	// z = cos h

	// Front vector
	//t_angle = (M_PI/2 - h_angle);
	//t_angle = M_PI/2;

	//face.x = (t_sin = sin(t_angle)) * (f_cos = cos(f_angle));
	//face.z = t_sin * (f_sin = sin(f_angle));
	//face.y = cos(t_angle);
#ifdef DUMMYUNIONNAMEN
	face.DUMMYUNIONNAMEN(9).x = cos(f_angle);
	face.DUMMYUNIONNAMEN(11).z = sin(f_angle);
	face.DUMMYUNIONNAMEN(10).y = 0.0f;

	head.DUMMYUNIONNAMEN(9).x = 0.0f;
	head.DUMMYUNIONNAMEN(10).y = 1.0f;
	head.DUMMYUNIONNAMEN(11).z = 0.0f;
#else
	face.x = (D3DVALUE)cos(f_angle);
	face.z = (D3DVALUE)sin(f_angle);
	face.y = 0.0f;

	head.x = 0.0f;
	head.y = 1.0f;
	head.z = 0.0f;
#endif

	// Top vector
	//h_angle = (-data->h_angle) / 180 * M_PI;

/*
	t_angle = -h_angle;

	head.z = (t_sin = sin(t_angle)) * f_cos;
	head.x = t_sin * f_sin;
	head.y = cos(t_angle);
*/

	// Update at once
	CopyMemory(&listener_parms.vPosition, &pos, sizeof (D3DVECTOR));
	CopyMemory(&listener_parms.vOrientFront, &face, sizeof (D3DVECTOR));
	CopyMemory(&listener_parms.vOrientTop, &head, sizeof (D3DVECTOR));
	CopyMemory(&listener_parms.vVelocity, &velocity, sizeof (D3DVECTOR));
	//IDirectSound3DListener_SetAllParameters(Listener, &listener_parms, DS3D_IMMEDIATE);
	//Alam_GBC: 2 Listeners?
	//IDirectSound3DListener_SetAllParameters(Listener, &listener_parms, update_mode);
}

EXPORT INT32 HWRAPI (SetCone) (INT32 handle, cone_def_t *cone_def)
{
	stack_t     *snd;
	//DS3DBUFFER  parms;

	if (handle < 0 || handle >= allocated_sounds)
		return -1;

	snd = _stack + handle;

	if (snd->dsbuffer3D)
	{

		/*f_angle = cone_def->f_angle / 180 * M_PI;
		h_angle = (90 - cone_def->h_angle) / 180 * M_PI;
		parms.vConeOrientation.x = sin(h_angle) * cos(f_angle);
		parms.vConeOrientation.z = sin(h_angle) * sin(f_angle);
		parms.vConeOrientation.y = cos(h_angle);*/

		snd->parameters.dwInsideConeAngle = (DWORD)cone_def->inner;
		snd->parameters.dwOutsideConeAngle = (DWORD)cone_def->outer;
		snd->parameters.lConeOutsideVolume = recalc_volume(cone_def->outer_gain, 256);

		return IDirectSound3DBuffer_SetAllParameters(snd->dsbuffer3D, &snd->parameters, update_mode);
	}

	return -1;
}

EXPORT void HWRAPI (Update3DSource) (INT32 handle, source3D_pos_t *data)
{
	stack_t     *snd;

	if (handle < 0 || handle >= allocated_sounds)
		return;

	snd = _stack + handle;
	if (snd->dsbuffer3D)
	{

		/*parms.dwSize = sizeof (DS3DBUFFER);
		IDirectSound3DBuffer_GetAllParameters(snd, &parms);*/

		//angle = data->angle * 180 / M_PI;

#ifdef DUMMYUNIONNAMEN
		snd->parameters.vPosition.DUMMYUNIONNAMEN(9).x = data->x;
		snd->parameters.vPosition.DUMMYUNIONNAMEN(10).y = data->z;
		snd->parameters.vPosition.DUMMYUNIONNAMEN(11).z = data->y;
#else
		snd->parameters.vPosition.x = data->x;
		snd->parameters.vPosition.y = data->z;
		snd->parameters.vPosition.z = data->y;
#endif

		/*parms.vConeOrientation.x = cos(angle);
		parms.vConeOrientation.z = sin(angle);
		parms.vConeOrientation.y = 0;*/

#ifdef DUMMYUNIONNAMEN
		snd->parameters.vVelocity.x = data->momx;
		snd->parameters.vVelocity.y = data->momz;
		snd->parameters.vVelocity.z = data->momy;
#else
		snd->parameters.vVelocity.x = data->momx;
		snd->parameters.vVelocity.y = data->momz;
		snd->parameters.vVelocity.z = data->momy;
#endif

		//snd->parameters.flMinDistance = data->min_distance;
		//snd->parameters.flMaxDistance = data->max_distance;
		//snd->parameters.dwMode = pos->head_realtive?DS3DMODE_HEADREALTIVE:DS3DMODE_NORMAL;

		IDirectSound3DBuffer_SetAllParameters(snd->dsbuffer3D, &snd->parameters, update_mode);
	}
}


EXPORT INT32 HWRAPI (StartSource) (INT32 handle)
{
	LPDIRECTSOUNDBUFFER snd;

	if (handle < 0 || handle >= allocated_sounds)
		return -1;

	snd = (_stack + handle)->dsbuffer;
	IDirectSoundBuffer_SetCurrentPosition(snd, 0);
	return IDirectSoundBuffer_Play(snd, 0, 0, 0);

}

//-------------------------------------------------------------
// Load new sound data into source
//-------------------------------------------------------------
EXPORT INT32 HWRAPI (Reload3DSource) (INT32 handle, sfx_data_t *data)
{
	DS3DBUFFER  temp;
	stack_t    *snd;
	INT32         perm;

	// DirectX could not load new sound data into source
	// so recreate sound buffers
	if (handle < 0 || handle >= allocated_sounds)
		return -1;

	snd = _stack + handle;
	CopyMemory(&temp, &snd->parameters, sizeof (DS3DBUFFER));
	perm = snd->permanent;

	kill_sound(snd);

	snd = setup_source(handle, data, IS_3DSOURCE);
	/*snd->dsbuffer = create_buffer(data->data, data->length, true);
	if (snd->dsbuffer == NULL)
		return -1;
	snd->dsbuffer3D = create_3dbuffer(snd->dsbuffer, snd->dsbuffer3D);
	if (snd->dsbuffer3D == NULL)
	{
		RELEASE_BUFFER(snd->dsbuffer);
		return -1;
	}

	snd->sfx_id = data->id;
	snd->LRU = 0;*/
	if (snd)
	{
		snd->permanent = perm;
		IDirectSound3DBuffer_SetAllParameters(snd->dsbuffer3D, &temp, DS3D_IMMEDIATE);
		CopyMemory(&snd->parameters, &temp, sizeof (DS3DBUFFER));
	}

	return(snd - _stack);
}

EXPORT void HWRAPI (UpdateSourceVolume) (INT32 handle, INT32 volume)
{
	if (handle < 0 || handle >= allocated_sounds)
		return;
	UpdateSoundVolume((_stack + handle)->dsbuffer, volume);
}
