/* Copyright (c) 2002-2012 Croteam Ltd. 
This program is free software; you can redistribute it and/or modify
it under the terms of version 2 of the GNU General Public License as published by
the Free Software Foundation


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.

You should have received a copy of the GNU General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */

#include "stdh.h"
#include "initguid.h"

#include <Engine/Sound/SoundLibrary.h>
#include <Engine/Base/Translation.h>

#include <Engine/Base/Shell.h>
#include <Engine/Base/Memory.h>
#include <Engine/Base/ErrorReporting.h>
#include <Engine/Base/ListIterator.inl>
#include <Engine/Base/Console.h>
#include <Engine/Base/Console_internal.h>
#include <Engine/Base/Statistics_Internal.h>
#include <Engine/Base/IFeel.h>

#include <Engine/Sound/SoundProfile.h>
#include <Engine/Sound/SoundListener.h>
#include <Engine/Sound/SoundData.h>
#include <Engine/Sound/SoundObject.h>
#include <Engine/Sound/SoundDecoder.h>
#include <Engine/Network/Network.h>

#include <Engine/Templates/StaticArray.cpp>
#include <Engine/Templates/StaticStackArray.cpp>

template CStaticArray<CSoundListener>;

#pragma comment(lib, "winmm.lib")

// pointer to global sound library object
CSoundLibrary *_pSound = NULL;


// console variables
extern FLOAT snd_tmMixAhead  = 0.2f; // mix-ahead in seconds
extern FLOAT snd_fSoundVolume = 1.0f;   // master volume for sound playing [0..1]
extern FLOAT snd_fMusicVolume = 1.0f;   // master volume for music playing [0..1]
// NOTES: 
// - these 3d sound parameters have been set carefully, take extreme if changing !
// - ears distance of 20cm causes phase shift of up to 0.6ms which is very noticable
//   and is more than enough, too large values cause too much distorsions in other effects
// - pan strength needs not to be very strong, since lrfilter has panning-like influence also
// - if down filter is too large, it makes too much influence even on small elevation changes
//   and messes the situation completely
extern FLOAT snd_fDelaySoundSpeed   = 1E10;   // sound speed used for delay [m/s]
extern FLOAT snd_fDopplerSoundSpeed = 330.0f; // sound speed used for doppler [m/s]
extern FLOAT snd_fEarsDistance = 0.2f;   // distance between listener's ears
extern FLOAT snd_fPanStrength  = 0.1f;   // panning modifier (0=none, 1= full)
extern FLOAT snd_fLRFilter = 3.0f;   // filter for left-right
extern FLOAT snd_fBFilter  = 5.0f;   // filter for back
extern FLOAT snd_fUFilter  = 1.0f;   // filter for up
extern FLOAT snd_fDFilter  = 3.0f;   // filter for down

ENGINE_API extern INDEX snd_iFormat = 3;
extern INDEX snd_bMono = FALSE;
static INDEX snd_iDevice = -1;
static INDEX snd_iInterface = 2;   // 0=WaveOut, 1=DirectSound, 2=EAX
static INDEX snd_iMaxOpenRetries = 3;
static INDEX snd_iMaxExtraChannels = 32;
static FLOAT snd_tmOpenFailDelay = 0.5f;
static FLOAT snd_fEAXPanning = 0.0f;

static FLOAT snd_fNormalizer = 0.9f;
static FLOAT _fLastNormalizeValue = 1;

extern HWND  _hwndMain; // global handle for application window
static HWND  _hwndCurrent = NULL;
static HINSTANCE _hInstDS = NULL;
static INDEX _iWriteOffset  = 0;
static INDEX _iWriteOffset2 = 0;
static BOOL  _bMuted  = FALSE;
static INDEX _iLastEnvType = 1234;
static FLOAT _fLastEnvSize = 1234;
static FLOAT _fLastPanning = 1234;


// TEMP! - for writing mixer buffer to file
static FILE *_filMixerBuffer;
static BOOL _bOpened = FALSE;


#define WAVEOUTBLOCKSIZE 1024
#define MINPAN (1.0f)
#define MAXPAN (9.0f)



/**
 * ----------------------------
 *    Sound Library functions
 * ----------------------------
**/

/*
 *  Construct uninitialized sound library.
 */
CSoundLibrary::CSoundLibrary(void)
{
  sl_csSound.cs_iIndex = 3000;

  // access to the list of handlers must be locked
  CTSingleLock slHooks(&_pTimer->tm_csHooks, TRUE);
  // synchronize access to sounds
  CTSingleLock slSounds(&sl_csSound, TRUE);

  // clear sound format
  memset( &sl_SwfeFormat, 0, sizeof(WAVEFORMATEX));
  sl_EsfFormat = SF_NONE;

  // reset buffer ptrs
  sl_pslMixerBuffer   = NULL;
  sl_pswDecodeBuffer  = NULL;
  sl_pubBuffersMemory = NULL;
  // clear wave out data
  sl_hwoWaveOut = NULL;

  // clear direct sound data
  _hInstDS = NULL;
  sl_pDS   = NULL;
  sl_pKSProperty = NULL;
  sl_pDSPrimary    = NULL;
  sl_pDSSecondary  = NULL;
  sl_pDSSecondary2 = NULL;
  sl_pDSListener    = NULL;
  sl_pDSSourceLeft  = NULL;
  sl_pDSSourceRight = NULL;
  sl_bUsingDirectSound = FALSE;
  sl_bUsingEAX = FALSE;
}


/*
 *  Destruct (and clean up).
 */
CSoundLibrary::~CSoundLibrary(void)
{
  // access to the list of handlers must be locked
  CTSingleLock slHooks(&_pTimer->tm_csHooks, TRUE);
  // synchronize access to sounds
  CTSingleLock slSounds(&sl_csSound, TRUE);

  // clear sound enviroment
  Clear();

  // clear any installed sound decoders
  CSoundDecoder::EndPlugins();
}



// post sound console variables' functions

static FLOAT _tmLastMixAhead = 1234;
static INDEX _iLastFormat = 1234;
static INDEX _iLastDevice = 1234;
static INDEX _iLastAPI = 1234;

static void SndPostFunc(void *pArgs)
{
  // clamp variables
  snd_tmMixAhead = Clamp( snd_tmMixAhead, 0.1f, 0.9f);
  snd_iFormat    = Clamp( snd_iFormat, (INDEX)CSoundLibrary::SF_NONE, (INDEX)CSoundLibrary::SF_44100_16);
  snd_iDevice    = Clamp( snd_iDevice, -1L, 15L);
  snd_iInterface = Clamp( snd_iInterface, 0L, 2L);
  // if any variable has been changed
  if( _tmLastMixAhead!=snd_tmMixAhead || _iLastFormat!=snd_iFormat
   || _iLastDevice!=snd_iDevice || _iLastAPI!=snd_iInterface) {
    // reinit sound format
    _pSound->SetFormat( (enum CSoundLibrary::SoundFormat)snd_iFormat, TRUE);
  }
}



/*
 *  some internal functions
 */


// DirectSound shutdown procedure
static void ShutDown_dsound( CSoundLibrary &sl)
{
  // free direct sound buffer(s)
  sl.sl_bUsingDirectSound = FALSE;
  sl.sl_bUsingEAX = FALSE;

  if( sl.sl_pDSSourceRight!=NULL) {
    sl.sl_pDSSourceRight->Release();
    sl.sl_pDSSourceRight = NULL;
  }
  if( sl.sl_pDSSourceLeft != NULL) {
    sl.sl_pDSSourceLeft->Release();
    sl.sl_pDSSourceLeft = NULL;
  }
  if( sl.sl_pDSListener != NULL) {
    sl.sl_pDSListener->Release();
    sl.sl_pDSListener = NULL;
  }

  if( sl.sl_pDSSecondary2 != NULL) {
    sl.sl_pDSSecondary2->Stop();
    sl.sl_pDSSecondary2->Release();
    sl.sl_pDSSecondary2 = NULL;
  }
  if( sl.sl_pDSSecondary != NULL) {
    sl.sl_pDSSecondary->Stop();
    sl.sl_pDSSecondary->Release();
    sl.sl_pDSSecondary = NULL;
  }
  if( sl.sl_pDSPrimary!=NULL) {
    sl.sl_pDSPrimary->Stop();
    sl.sl_pDSPrimary->Release();
    sl.sl_pDSPrimary = NULL;
  }

  if( sl.sl_pKSProperty != NULL) {
    sl.sl_pKSProperty->Release();
    sl.sl_pKSProperty = NULL;
  }

  // free direct sound object
  if( sl.sl_pDS!=NULL) {
    // reset cooperative level
    if( _hwndCurrent!=NULL) sl.sl_pDS->SetCooperativeLevel( _hwndCurrent, DSSCL_NORMAL);
    sl.sl_pDS->Release();
    sl.sl_pDS = NULL;
  }
  // free direct sound library
  if( _hInstDS != NULL) {
    FreeLibrary(_hInstDS);
    _hInstDS = NULL;
  }
  // free memory
  if( sl.sl_pslMixerBuffer!=NULL) {
    FreeMemory( sl.sl_pslMixerBuffer);
    sl.sl_pslMixerBuffer = NULL;
  }
  if( sl.sl_pswDecodeBuffer!=NULL) {
    FreeMemory( sl.sl_pswDecodeBuffer);
    sl.sl_pswDecodeBuffer = NULL;
  }
}


/*
 *  Set wave format from library format
 */
static void SetWaveFormat( CSoundLibrary::SoundFormat EsfFormat, WAVEFORMATEX &wfeFormat)
{
  // change Library Wave Format
  memset( &wfeFormat, 0, sizeof(WAVEFORMATEX));
  wfeFormat.wFormatTag = WAVE_FORMAT_PCM;
  wfeFormat.nChannels = 2;
  wfeFormat.wBitsPerSample = 16;
  switch( EsfFormat) {
    case CSoundLibrary::SF_11025_16: wfeFormat.nSamplesPerSec = 11025;  break;
    case CSoundLibrary::SF_22050_16: wfeFormat.nSamplesPerSec = 22050;  break;
    case CSoundLibrary::SF_44100_16: wfeFormat.nSamplesPerSec = 44100;  break;
    case CSoundLibrary::SF_NONE: ASSERTALWAYS( "Can't set to NONE format"); break;
    default:                     ASSERTALWAYS( "Unknown Sound format");     break;
  }
  wfeFormat.nBlockAlign     = (wfeFormat.wBitsPerSample / 8) * wfeFormat.nChannels;
  wfeFormat.nAvgBytesPerSec = wfeFormat.nSamplesPerSec * wfeFormat.nBlockAlign;
}


/*
 *  Set library format from wave format
 */
static void SetLibraryFormat( CSoundLibrary &sl)
{
  // if library format is none return
  if( sl.sl_EsfFormat == CSoundLibrary::SF_NONE) return;

  // else check wave format to determine library format
  ULONG ulFormat = sl.sl_SwfeFormat.nSamplesPerSec;
  // find format
  switch( ulFormat) {
    case 11025: sl.sl_EsfFormat = CSoundLibrary::SF_11025_16; break;
    case 22050: sl.sl_EsfFormat = CSoundLibrary::SF_22050_16; break;
    case 44100: sl.sl_EsfFormat = CSoundLibrary::SF_44100_16; break;
    // unknown format
    default:
      ASSERTALWAYS( "Unknown sound format");
      FatalError( TRANS("Unknown sound format"));
      sl.sl_EsfFormat = CSoundLibrary::SF_ILLEGAL;
  }
}



static BOOL DSFail( CSoundLibrary &sl, char *strError) 
{
  CPrintF(strError);
  ShutDown_dsound(sl);
  snd_iInterface=1; // if EAX failed -> try DirectSound
  return FALSE;
}


// some helper functions for DirectSound
static BOOL DSInitSecondary( CSoundLibrary &sl, LPDIRECTSOUNDBUFFER &pBuffer, SLONG slSize)
{
  // eventuallt adjust for EAX
  DWORD dwFlag3D = NONE;
  if( snd_iInterface==2) {
    dwFlag3D = DSBCAPS_CTRL3D;
    sl.sl_SwfeFormat.nChannels=1;  // mono output
    sl.sl_SwfeFormat.nBlockAlign/=2;
    sl.sl_SwfeFormat.nAvgBytesPerSec/=2;
    slSize/=2;
  }
  DSBUFFERDESC dsBuffer;
  memset( &dsBuffer, 0, sizeof(dsBuffer));
  dsBuffer.dwSize  = sizeof(DSBUFFERDESC);
  dsBuffer.dwFlags = DSBCAPS_GETCURRENTPOSITION2 | dwFlag3D;
  dsBuffer.dwBufferBytes = slSize; 
  dsBuffer.lpwfxFormat = &sl.sl_SwfeFormat;
  HRESULT hResult = sl.sl_pDS->CreateSoundBuffer( &dsBuffer, &pBuffer, NULL);
  if( snd_iInterface==2) {
      // revert back to original wave format (stereo)
    sl.sl_SwfeFormat.nChannels=2;
    sl.sl_SwfeFormat.nBlockAlign*=2;
    sl.sl_SwfeFormat.nAvgBytesPerSec*=2;
  }
  if( hResult != DS_OK) return DSFail( sl, TRANS("  ! DirectSound error: Cannot create secondary buffer.\n"));
  return TRUE;
}


static BOOL DSLockBuffer( CSoundLibrary &sl, LPDIRECTSOUNDBUFFER pBuffer, SLONG slSize, LPVOID &lpData, DWORD &dwSize)
{
  INDEX ctRetries = 1000;  // too much?
  if( sl.sl_bUsingEAX) slSize/=2; // buffer is mono in case of EAX
  FOREVER {
    HRESULT hResult = pBuffer->Lock( 0, slSize, &lpData, &dwSize, NULL, NULL, 0);
    if( hResult==DS_OK && slSize==dwSize) return TRUE;
    if( hResult!=DSERR_BUFFERLOST) return DSFail( sl, TRANS("  ! DirectSound error: Cannot lock sound buffer.\n"));
    if( ctRetries-- == 0) return DSFail( sl, TRANS("  ! DirectSound error: Couldn't restore sound buffer.\n"));
    pBuffer->Restore();
  }
}
 

static void DSPlayBuffers( CSoundLibrary &sl)
{
  DWORD dw;
  BOOL bInitiatePlay = FALSE;
  ASSERT( sl.sl_pDSSecondary!=NULL && sl.sl_pDSPrimary!=NULL);
  if( sl.sl_bUsingEAX && sl.sl_pDSSecondary2->GetStatus(&dw)==DS_OK && !(dw&DSBSTATUS_PLAYING)) bInitiatePlay = TRUE;
  if( sl.sl_pDSSecondary->GetStatus(&dw)==DS_OK && !(dw&DSBSTATUS_PLAYING)) bInitiatePlay = TRUE;
  if( sl.sl_pDSPrimary->GetStatus(&dw)==DS_OK && !(dw&DSBSTATUS_PLAYING))  bInitiatePlay = TRUE;

  // done if all buffers are already playing
  if( !bInitiatePlay) return;

  // stop buffers (in case some buffers are playing
  sl.sl_pDSPrimary->Stop();
  sl.sl_pDSSecondary->Stop();
  if( sl.sl_bUsingEAX) sl.sl_pDSSecondary2->Stop();
  
  // check sound buffer lock and clear sound buffer(s) 
  LPVOID lpData;
  DWORD	 dwSize;
  if( !DSLockBuffer( sl, sl.sl_pDSSecondary, sl.sl_slMixerBufferSize, lpData, dwSize)) return;
  memset( lpData, 0, dwSize);
  sl.sl_pDSSecondary->Unlock( lpData, dwSize, NULL, 0);
  if( sl.sl_bUsingEAX) { 
    if( !DSLockBuffer( sl, sl.sl_pDSSecondary2, sl.sl_slMixerBufferSize, lpData, dwSize)) return;
    memset( lpData, 0, dwSize); 
    sl.sl_pDSSecondary2->Unlock( lpData, dwSize, NULL, 0);
    // start playing EAX additional buffer
    sl.sl_pDSSecondary2->Play( 0, 0, DSBPLAY_LOOPING);
  }
  // start playing standard DirectSound buffers
  sl.sl_pDSPrimary->Play(   0, 0, DSBPLAY_LOOPING);
  sl.sl_pDSSecondary->Play( 0, 0, DSBPLAY_LOOPING);
  _iWriteOffset  = 0;
  _iWriteOffset2 = 0;

  // adjust starting offsets for EAX
  if( sl.sl_bUsingEAX) {
    DWORD dwCursor1, dwCursor2;
    SLONG slMinDelta = MAX_SLONG;
    for( INDEX i=0; i<10; i++) { // shoud be enough to screw interrupts
      sl.sl_pDSSecondary->GetCurrentPosition(  &dwCursor1, NULL);
      sl.sl_pDSSecondary2->GetCurrentPosition( &dwCursor2, NULL);
      SLONG slDelta1 = dwCursor2-dwCursor1;
      sl.sl_pDSSecondary2->GetCurrentPosition( &dwCursor2, NULL);
      sl.sl_pDSSecondary->GetCurrentPosition(  &dwCursor1, NULL);
      SLONG slDelta2 = dwCursor2-dwCursor1;
      SLONG slDelta  = (slDelta1+slDelta2) /2;
      if( slDelta<slMinDelta) slMinDelta = slDelta;
      //CPrintF( "D1=%5d,  D2=%5d,  AD=%5d,  MD=%5d\n", slDelta1, slDelta2, slDelta, slMinDelta);
    }
    if( slMinDelta<0) _iWriteOffset  = -slMinDelta*2; // 2 because of offset is stereo
    if( slMinDelta>0) _iWriteOffset2 = +slMinDelta*2; 
    _iWriteOffset  += _iWriteOffset  & 3;  // round to 4 bytes
    _iWriteOffset2 += _iWriteOffset2 & 3;

    // assure that first writing offsets are inside buffers
    if( _iWriteOffset >=sl.sl_slMixerBufferSize) _iWriteOffset  -= sl.sl_slMixerBufferSize;
    if( _iWriteOffset2>=sl.sl_slMixerBufferSize) _iWriteOffset2 -= sl.sl_slMixerBufferSize;
    ASSERT( _iWriteOffset >=0 && _iWriteOffset <sl.sl_slMixerBufferSize);
    ASSERT( _iWriteOffset2>=0 && _iWriteOffset2<sl.sl_slMixerBufferSize);
  }
}


// init and set DirectSound format (internal)

static BOOL StartUp_dsound( CSoundLibrary &sl, BOOL bReport=TRUE)
{
  // startup
  sl.sl_bUsingDirectSound = FALSE;
  ASSERT( _hInstDS==NULL && sl.sl_pDS==NULL);
  ASSERT( sl.sl_pDSSecondary==NULL && sl.sl_pDSPrimary==NULL);
  // update window handle (just in case)
  HRESULT (WINAPI *pDirectSoundCreate)(GUID FAR *lpGUID, LPDIRECTSOUND FAR *lplpDS, IUnknown FAR *pUnkOuter);
  
  if( bReport) CPrintF(TRANS("Direct Sound initialization ...\n"));
  ASSERT( _hInstDS==NULL);
  _hInstDS = LoadLibraryA( "dsound.dll");
  if( _hInstDS==NULL) {
    CPrintF( TRANS("  ! DirectSound error: Cannot load 'DSOUND.DLL'.\n"));
    return FALSE;
  }
  // get main procedure address
  pDirectSoundCreate = (HRESULT(WINAPI *)(GUID FAR *, LPDIRECTSOUND FAR *, IUnknown FAR *))GetProcAddress( _hInstDS, "DirectSoundCreate");
  if( pDirectSoundCreate==NULL) return DSFail( sl, TRANS("  ! DirectSound error: Cannot get procedure address.\n"));

  // init dsound
  HRESULT	hResult;
  hResult = pDirectSoundCreate( NULL, &sl.sl_pDS, NULL);
  if( hResult != DS_OK) return DSFail( sl, TRANS("  ! DirectSound error: Cannot create object.\n"));

  // get capabilities
  DSCAPS dsCaps;
  dsCaps.dwSize = sizeof(dsCaps);
  hResult = sl.sl_pDS->GetCaps( &dsCaps);
  if( hResult != DS_OK) return DSFail( sl, TRANS("  ! DirectSound error: Cannot determine capabilites.\n"));

  // fail if in emulation mode
  if( dsCaps.dwFlags & DSCAPS_EMULDRIVER) {
    CPrintF( TRANS("  ! DirectSound error: No driver installed.\n"));
    ShutDown_dsound(sl);
    return FALSE;
  }

  // set cooperative level to priority
  _hwndCurrent = _hwndMain;
  hResult = sl.sl_pDS->SetCooperativeLevel( _hwndCurrent, DSSCL_PRIORITY);
  if( hResult != DS_OK) return DSFail( sl, TRANS("  ! DirectSound error: Cannot set cooperative level.\n"));

  // prepare 3D flag if EAX
  DWORD dwFlag3D = NONE;
  if( snd_iInterface==2) dwFlag3D = DSBCAPS_CTRL3D;

  // create primary sound buffer (must have one)
  DSBUFFERDESC dsBuffer;
  memset( &dsBuffer, 0, sizeof(dsBuffer));
  dsBuffer.dwSize  = sizeof(dsBuffer);
  dsBuffer.dwFlags = DSBCAPS_PRIMARYBUFFER | dwFlag3D;
  dsBuffer.dwBufferBytes = 0;
  dsBuffer.lpwfxFormat = NULL;
  hResult = sl.sl_pDS->CreateSoundBuffer( &dsBuffer, &sl.sl_pDSPrimary, NULL);
  if( hResult != DS_OK) return DSFail( sl, TRANS("  ! DirectSound error: Cannot create primary sound buffer.\n"));

  // set primary buffer format
  WAVEFORMATEX wfx = sl.sl_SwfeFormat;
  hResult = sl.sl_pDSPrimary->SetFormat(&wfx);
  if( hResult != DS_OK) return DSFail( sl, TRANS("  ! DirectSound error: Cannot set primary sound buffer format.\n"));

  // startup secondary sound buffer(s)
  SLONG slBufferSize = (SLONG)(ceil(snd_tmMixAhead*sl.sl_SwfeFormat.nSamplesPerSec) *
                       sl.sl_SwfeFormat.wBitsPerSample/8 * sl.sl_SwfeFormat.nChannels);
  if( !DSInitSecondary( sl, sl.sl_pDSSecondary, slBufferSize)) return FALSE;

  // set some additionals for EAX
  if( snd_iInterface==2)
  {
    // 2nd secondary buffer
    if( !DSInitSecondary( sl, sl.sl_pDSSecondary2, slBufferSize)) return FALSE;
    // set 3D for all buffers
    HRESULT hr1,hr2,hr3,hr4;
    hr1 = sl.sl_pDSPrimary->QueryInterface(  IID_IDirectSound3DListener, (LPVOID*)&sl.sl_pDSListener);
    hr2 = sl.sl_pDSSecondary->QueryInterface(  IID_IDirectSound3DBuffer, (LPVOID*)&sl.sl_pDSSourceLeft);
    hr3 = sl.sl_pDSSecondary2->QueryInterface( IID_IDirectSound3DBuffer, (LPVOID*)&sl.sl_pDSSourceRight);
    if( hr1!=DS_OK || hr2!=DS_OK || hr3!=DS_OK) return DSFail( sl, TRANS("  ! DirectSound3D error: Cannot set 3D sound buffer.\n"));

    hr1 = sl.sl_pDSListener->SetPosition( 0,0,0, DS3D_DEFERRED);
    hr2 = sl.sl_pDSListener->SetOrientation( 0,0,1, 0,1,0, DS3D_DEFERRED);
    hr3 = sl.sl_pDSListener->SetRolloffFactor( 1, DS3D_DEFERRED);
    if( hr1!=DS_OK || hr2!=DS_OK || hr3!=DS_OK) return DSFail( sl, TRANS("  ! DirectSound3D error: Cannot set 3D parameters for listener.\n"));
    hr1 = sl.sl_pDSSourceLeft->SetMinDistance(  MINPAN, DS3D_DEFERRED);
    hr2 = sl.sl_pDSSourceLeft->SetMaxDistance(  MAXPAN, DS3D_DEFERRED);
    hr3 = sl.sl_pDSSourceRight->SetMinDistance( MINPAN, DS3D_DEFERRED);
    hr4 = sl.sl_pDSSourceRight->SetMaxDistance( MAXPAN, DS3D_DEFERRED);
    if( hr1!=DS_OK || hr2!=DS_OK || hr3!=DS_OK || hr4!=DS_OK) {
      return DSFail( sl, TRANS("  ! DirectSound3D error: Cannot set 3D parameters for sound source.\n"));
    }
    // apply
    hResult = sl.sl_pDSListener->CommitDeferredSettings();
    if( hResult!=DS_OK) return DSFail( sl, TRANS("  ! DirectSound3D error: Cannot apply 3D parameters.\n"));
    // reset EAX parameters
    _fLastPanning = 1234; 
    _iLastEnvType = 1234;
    _fLastEnvSize = 1234;

    // query property interface to EAX
    hResult = sl.sl_pDSSourceLeft->QueryInterface( IID_IKsPropertySet, (LPVOID*)&sl.sl_pKSProperty);
    if( hResult != DS_OK) return DSFail( sl, TRANS("  ! EAX error: Cannot set property interface.\n"));
    // query support
    ULONG ulSupport = 0;
    hResult = sl.sl_pKSProperty->QuerySupport( DSPROPSETID_EAX_ListenerProperties, DSPROPERTY_EAXLISTENER_ENVIRONMENT, &ulSupport);
    if( hResult != DS_OK || !(ulSupport&KSPROPERTY_SUPPORT_SET)) return DSFail( sl, TRANS("  ! EAX error: Cannot query property support.\n"));
    hResult = sl.sl_pKSProperty->QuerySupport( DSPROPSETID_EAX_ListenerProperties, DSPROPERTY_EAXLISTENER_ENVIRONMENTSIZE, &ulSupport);
    if( hResult != DS_OK || !(ulSupport&KSPROPERTY_SUPPORT_SET)) return DSFail( sl, TRANS("  ! EAX error: Cannot query property support.\n"));
    // made it - EAX's on!
    sl.sl_bUsingEAX = TRUE;
  }

  // mark that dsound is operative and set mixer buffer size (decoder buffer always works at 44khz)
  _iWriteOffset  = 0;
  _iWriteOffset2 = 0;
  sl.sl_bUsingDirectSound  = TRUE;
  sl.sl_slMixerBufferSize  = slBufferSize;
  sl.sl_slDecodeBufferSize = sl.sl_slMixerBufferSize *
                           ((44100+sl.sl_SwfeFormat.nSamplesPerSec-1) /sl.sl_SwfeFormat.nSamplesPerSec);
  // allocate mixing and decoding buffers
  sl.sl_pslMixerBuffer  = (SLONG*)AllocMemory( sl.sl_slMixerBufferSize *2); // (*2 because of 32-bit buffer)
  sl.sl_pswDecodeBuffer = (SWORD*)AllocMemory( sl.sl_slDecodeBufferSize+4); // (+4 because of linear interpolation of last samples)

  // report success
  if( bReport) {
    CTString strDevice = TRANS("default device");
    if( snd_iDevice>=0) strDevice.PrintF( TRANS("device %d"), snd_iDevice); 
    CPrintF( TRANS("  %dHz, %dbit, %s, mix-ahead: %gs\n"), 
             sl.sl_SwfeFormat.nSamplesPerSec, sl.sl_SwfeFormat.wBitsPerSample, strDevice, snd_tmMixAhead); 
    CPrintF(TRANS("  mixer buffer size:  %d KB\n"), sl.sl_slMixerBufferSize /1024);
    CPrintF(TRANS("  decode buffer size: %d KB\n"), sl.sl_slDecodeBufferSize/1024);
    // EAX?
    CTString strEAX = TRANS("Disabled");
    if( sl.sl_bUsingEAX) strEAX = TRANS("Enabled");
    CPrintF( TRANS("  EAX: %s\n"), strEAX);
  } 
  // done
  return TRUE;
}


// set WaveOut format (internal)

static INDEX _ctChannelsOpened = 0;
static BOOL StartUp_waveout( CSoundLibrary &sl, BOOL bReport=TRUE)
{
  // not using DirectSound (obviously)
  sl.sl_bUsingDirectSound = FALSE;
  sl.sl_bUsingEAX = FALSE;
  if( bReport) CPrintF(TRANS("WaveOut initialization ...\n"));
  // set maximum total number of retries for device opening
  INDEX ctMaxRetries = snd_iMaxOpenRetries;
  _ctChannelsOpened = 0;
  MMRESULT res;
  // repeat
  FOREVER {
    // try to open wave device
    HWAVEOUT hwo;
    res = waveOutOpen( &hwo, (snd_iDevice<0)?WAVE_MAPPER:snd_iDevice, &sl.sl_SwfeFormat, NULL, NULL, NONE);
    // if opened
    if( res == MMSYSERR_NOERROR) {
      _ctChannelsOpened++;
      // if first one
      if (_ctChannelsOpened==1) {
        // remember as used waveout
        sl.sl_hwoWaveOut = hwo;
      // if extra channel
      } else {
        // remember under extra
        sl.sl_ahwoExtra.Push() = hwo;
      }
      // if no extra channels should be taken
      if (_ctChannelsOpened>=snd_iMaxExtraChannels+1) {
        // no more tries
        break;
      }    
    // if cannot open
    } else {
      // decrement retry counter
      ctMaxRetries--;
      // if no more retries
      if (ctMaxRetries<0) {
        // quit trying
        break;
      // if more retries left
      } else {
        // wait a bit (probably sound-scheme is playing)
        Sleep(int(snd_tmOpenFailDelay*1000));
      }
    }
  }

  // if couldn't set format
  if( _ctChannelsOpened==0 && res != MMSYSERR_NOERROR) {
    // report error
    CTString strError;
    switch (res) {
    case MMSYSERR_ALLOCATED:    strError = TRANS("Device already in use.");     break;
    case MMSYSERR_BADDEVICEID:  strError = TRANS("Bad device number.");         break;
    case MMSYSERR_NODRIVER:     strError = TRANS("No driver installed.");       break;
    case MMSYSERR_NOMEM:        strError = TRANS("Memory allocation problem."); break;
    case WAVERR_BADFORMAT:      strError = TRANS("Unsupported data format.");   break;
    case WAVERR_SYNC:           strError = TRANS("Wrong flag?");                break;
    default: strError.PrintF( "%d", res);
    };
    CPrintF( TRANS("  ! WaveOut error: %s\n"), strError);
    return FALSE;
  }

  // get waveout capabilities
  WAVEOUTCAPS woc;
  memset( &woc, 0, sizeof(woc));
  res = waveOutGetDevCaps((int)sl.sl_hwoWaveOut, &woc, sizeof(woc));
  // report success
  if( bReport) {
    CTString strDevice = TRANS("default device");
    if( snd_iDevice>=0) strDevice.PrintF( TRANS("device %d"), snd_iDevice); 
    CPrintF( TRANS("  opened device: %s\n"), woc.szPname);
    CPrintF( TRANS("  %dHz, %dbit, %s\n"), 
             sl.sl_SwfeFormat.nSamplesPerSec, sl.sl_SwfeFormat.wBitsPerSample, strDevice);
  }

  // determine whole mixer buffer size from mixahead console variable
  sl.sl_slMixerBufferSize = (SLONG)(ceil(snd_tmMixAhead*sl.sl_SwfeFormat.nSamplesPerSec) *
                            sl.sl_SwfeFormat.wBitsPerSample/8 * sl.sl_SwfeFormat.nChannels);
  // align size to be next multiply of WAVEOUTBLOCKSIZE
  sl.sl_slMixerBufferSize += WAVEOUTBLOCKSIZE - (sl.sl_slMixerBufferSize % WAVEOUTBLOCKSIZE);
  // determine number of WaveOut buffers
  const INDEX ctWOBuffers = sl.sl_slMixerBufferSize / WAVEOUTBLOCKSIZE;
  // decoder buffer always works at 44khz
  sl.sl_slDecodeBufferSize = sl.sl_slMixerBufferSize *
                           ((44100+sl.sl_SwfeFormat.nSamplesPerSec-1)/sl.sl_SwfeFormat.nSamplesPerSec);
  if( bReport) {
    CPrintF(TRANS("  parameters: %d Hz, %d bit, stereo, mix-ahead: %gs\n"),
            sl.sl_SwfeFormat.nSamplesPerSec, sl.sl_SwfeFormat.wBitsPerSample, snd_tmMixAhead);
    CPrintF(TRANS("  output buffers: %d x %d bytes\n"), ctWOBuffers, WAVEOUTBLOCKSIZE),
    CPrintF(TRANS("  mpx decode: %d bytes\n"), sl.sl_slDecodeBufferSize),
    CPrintF(TRANS("  extra sound channels taken: %d\n"), _ctChannelsOpened-1);
  }

  // initialise waveout sound buffers
  sl.sl_pubBuffersMemory = (UBYTE*)AllocMemory( sl.sl_slMixerBufferSize);
  memset( sl.sl_pubBuffersMemory, 0, sl.sl_slMixerBufferSize);
  sl.sl_awhWOBuffers.New(ctWOBuffers); 
  for( INDEX iBuffer = 0; iBuffer<sl.sl_awhWOBuffers.Count(); iBuffer++) {
    WAVEHDR &wh = sl.sl_awhWOBuffers[iBuffer];
    wh.lpData = (char*)(sl.sl_pubBuffersMemory + iBuffer*WAVEOUTBLOCKSIZE);
    wh.dwBufferLength = WAVEOUTBLOCKSIZE;
    wh.dwFlags = 0;
  }
  // initialize mixing and decoding buffer
  sl.sl_pslMixerBuffer  = (SLONG*)AllocMemory( sl.sl_slMixerBufferSize *2); // (*2 because of 32-bit buffer)
  sl.sl_pswDecodeBuffer = (SWORD*)AllocMemory( sl.sl_slDecodeBufferSize+4); // (+4 because of linear interpolation of last samples)

  // done
  return TRUE;
}




/*
 *  set sound format
 */
static void SetFormat_internal( CSoundLibrary &sl, CSoundLibrary::SoundFormat EsfNew, BOOL bReport)
{
  // access to the list of handlers must be locked
  CTSingleLock slHooks(&_pTimer->tm_csHooks, TRUE);
  // synchronize access to sounds
  CTSingleLock slSounds(&sl.sl_csSound, TRUE);

  // remember library format
  sl.sl_EsfFormat = EsfNew;
  // release library
  sl.ClearLibrary();

  // if none skip initialization
  _fLastNormalizeValue = 1;
  if( bReport) CPrintF(TRANS("Setting sound format ...\n"));
  if( sl.sl_EsfFormat == CSoundLibrary::SF_NONE) {
    if( bReport) CPrintF(TRANS("  (no sound)\n"));
    return;
  }

  // set wave format from library format
  SetWaveFormat( EsfNew, sl.sl_SwfeFormat);
  snd_iDevice    = Clamp( snd_iDevice, -1L, (INDEX)(sl.sl_ctWaveDevices-1));
  snd_tmMixAhead = Clamp( snd_tmMixAhead, 0.1f, 0.9f);
  snd_iInterface = Clamp( snd_iInterface, 0L, 2L);

  BOOL bSoundOK = FALSE;
  if( snd_iInterface==2) {
    // if wanted, 1st try to set EAX
    bSoundOK = StartUp_dsound( sl, bReport);  
  }
  if( !bSoundOK && snd_iInterface==1) {
    // if wanted, 2nd try to set DirectSound
    bSoundOK = StartUp_dsound( sl, bReport);  
  }
  // if DirectSound failed or not wanted
  if( !bSoundOK) { 
    // try waveout
    bSoundOK = StartUp_waveout( sl, bReport); 
    snd_iInterface = 0; // mark that DirectSound didn't make it
  }

  // if didn't make it by now
  if( bReport) CPrintF("\n");
  if( !bSoundOK) {
    // revert to none in case sound init was unsuccessful
    sl.sl_EsfFormat = CSoundLibrary::SF_NONE;
    return;
  } 
  // set library format from wave format
  SetLibraryFormat(sl);

  // add timer handler
  _pTimer->AddHandler(&sl.sl_thTimerHandler);
}


/*
 *  Initialization
 */
void CSoundLibrary::Init(void)
{
  // access to the list of handlers must be locked
  CTSingleLock slHooks(&_pTimer->tm_csHooks, TRUE);
  // synchronize access to sounds
  CTSingleLock slSounds(&sl_csSound, TRUE);

  _pShell->DeclareSymbol( "void SndPostFunc(INDEX);", &SndPostFunc);

  _pShell->DeclareSymbol( "           user INDEX snd_bMono;", &snd_bMono);
  _pShell->DeclareSymbol( "persistent user FLOAT snd_fEarsDistance;",      &snd_fEarsDistance);
  _pShell->DeclareSymbol( "persistent user FLOAT snd_fDelaySoundSpeed;",   &snd_fDelaySoundSpeed);
  _pShell->DeclareSymbol( "persistent user FLOAT snd_fDopplerSoundSpeed;", &snd_fDopplerSoundSpeed);
  _pShell->DeclareSymbol( "persistent user FLOAT snd_fPanStrength;", &snd_fPanStrength);
  _pShell->DeclareSymbol( "persistent user FLOAT snd_fLRFilter;",    &snd_fLRFilter);
  _pShell->DeclareSymbol( "persistent user FLOAT snd_fBFilter;",     &snd_fBFilter);
  _pShell->DeclareSymbol( "persistent user FLOAT snd_fUFilter;",     &snd_fUFilter);
  _pShell->DeclareSymbol( "persistent user FLOAT snd_fDFilter;",     &snd_fDFilter);
  _pShell->DeclareSymbol( "persistent user FLOAT snd_fSoundVolume;", &snd_fSoundVolume);
  _pShell->DeclareSymbol( "persistent user FLOAT snd_fMusicVolume;", &snd_fMusicVolume);
  _pShell->DeclareSymbol( "persistent user FLOAT snd_fNormalizer;",  &snd_fNormalizer);
  _pShell->DeclareSymbol( "persistent user FLOAT snd_tmMixAhead post:SndPostFunc;", &snd_tmMixAhead);
  _pShell->DeclareSymbol( "persistent user INDEX snd_iInterface post:SndPostFunc;", &snd_iInterface);
  _pShell->DeclareSymbol( "persistent user INDEX snd_iDevice post:SndPostFunc;", &snd_iDevice);
  _pShell->DeclareSymbol( "persistent user INDEX snd_iFormat post:SndPostFunc;", &snd_iFormat);
  _pShell->DeclareSymbol( "persistent user INDEX snd_iMaxExtraChannels;", &snd_iMaxExtraChannels);
  _pShell->DeclareSymbol( "persistent user INDEX snd_iMaxOpenRetries;",   &snd_iMaxOpenRetries);
  _pShell->DeclareSymbol( "persistent user FLOAT snd_tmOpenFailDelay;",   &snd_tmOpenFailDelay);
  _pShell->DeclareSymbol( "persistent user FLOAT snd_fEAXPanning;", &snd_fEAXPanning);
  
  // print header
  CPrintF(TRANS("Initializing sound...\n"));

  // initialize sound library and set no-sound format
  SetFormat(SF_NONE);

  // initialize any installed sound decoders
  CSoundDecoder::InitPlugins();

  // get number of devices
  INDEX ctDevices = waveOutGetNumDevs();
  CPrintF(TRANS("  Detected devices: %d\n"), ctDevices);
  sl_ctWaveDevices = ctDevices;
  
  // for each device
  for(INDEX iDevice=0; iDevice<ctDevices; iDevice++) {
    // get description
    WAVEOUTCAPS woc;
    memset( &woc, 0, sizeof(woc));
    MMRESULT res = waveOutGetDevCaps(iDevice, &woc, sizeof(woc));
    CPrintF(TRANS("    device %d: %s\n"), 
      iDevice, woc.szPname);
    CPrintF(TRANS("      ver: %d, id: %d.%d\n"), 
      woc.vDriverVersion, woc.wMid, woc.wPid);
    CPrintF(TRANS("      form: 0x%08x, ch: %d, support: 0x%08x\n"), 
      woc.dwFormats, woc.wChannels, woc.dwSupport);
  }
  // done
  CPrintF("\n");
}


/*
 *  Clear Sound Library
 */
void CSoundLibrary::Clear(void) {
  // access to the list of handlers must be locked
  CTSingleLock slHooks(&_pTimer->tm_csHooks, TRUE);
  // synchronize access to sounds
  CTSingleLock slSounds(&sl_csSound, TRUE);

  // clear all sounds and datas buffers
  {FOREACHINLIST(CSoundData, sd_Node, sl_ClhAwareList, itCsdStop) {
    FOREACHINLIST(CSoundObject, so_Node, (itCsdStop->sd_ClhLinkList), itCsoStop) {
      itCsoStop->Stop();
    }
    itCsdStop->ClearBuffer();
  }}

  // clear wave out data
  ClearLibrary();
  _fLastNormalizeValue = 1;
}


/* Clear Library WaveOut */
void CSoundLibrary::ClearLibrary(void)
{
  // access to the list of handlers must be locked
  CTSingleLock slHooks(&_pTimer->tm_csHooks, TRUE);

  // synchronize access to sounds
  CTSingleLock slSounds(&sl_csSound, TRUE);

  // remove timer handler if added
  if (sl_thTimerHandler.th_Node.IsLinked()) {
    _pTimer->RemHandler(&sl_thTimerHandler);
  }

  // shut down direct sound buffers (if needed)
  ShutDown_dsound(*this);

  // shut down wave out player buffers (if needed)
  if( sl_hwoWaveOut!=NULL)
  { // reset wave out play buffers (stop playing)
    MMRESULT res;
    res = waveOutReset(sl_hwoWaveOut);
    ASSERT(res == MMSYSERR_NOERROR);
     // clear buffers
    for( INDEX iBuffer = 0; iBuffer<sl_awhWOBuffers.Count(); iBuffer++) {
      res = waveOutUnprepareHeader( sl_hwoWaveOut, &sl_awhWOBuffers[iBuffer],
                                    sizeof(sl_awhWOBuffers[iBuffer]));
      ASSERT(res == MMSYSERR_NOERROR);
    }
    sl_awhWOBuffers.Clear();
    // close waveout device
    res = waveOutClose( sl_hwoWaveOut);
    ASSERT(res == MMSYSERR_NOERROR);
    sl_hwoWaveOut = NULL;
  }

  // for each extra taken channel
  for(INDEX iChannel=0; iChannel<sl_ahwoExtra.Count(); iChannel++) {
    // close its device
    MMRESULT res = waveOutClose( sl_ahwoExtra[iChannel]);
    ASSERT(res == MMSYSERR_NOERROR);
  }
  // free extra channel handles
  sl_ahwoExtra.PopAll();

  // free memory
  if( sl_pslMixerBuffer!=NULL) {
    FreeMemory( sl_pslMixerBuffer);
    sl_pslMixerBuffer = NULL;
  }
  if( sl_pswDecodeBuffer!=NULL) {
    FreeMemory( sl_pswDecodeBuffer);
    sl_pswDecodeBuffer = NULL;
  }
  if( sl_pubBuffersMemory!=NULL) {
    FreeMemory( sl_pubBuffersMemory);
    sl_pubBuffersMemory = NULL;
  }
}


// set listener enviroment properties (EAX)
BOOL CSoundLibrary::SetEnvironment( INDEX iEnvNo, FLOAT fEnvSize/*=0*/)
{
  if( !sl_bUsingEAX) return FALSE;
  // trim values
  if( iEnvNo<0   || iEnvNo>25)   iEnvNo=1;
  if( fEnvSize<1 || fEnvSize>99) fEnvSize=8;
  HRESULT hResult;
  hResult = sl_pKSProperty->Set( DSPROPSETID_EAX_ListenerProperties, DSPROPERTY_EAXLISTENER_ENVIRONMENT, NULL, 0, &iEnvNo, sizeof(DWORD));
  if( hResult != DS_OK) return DSFail( *this, TRANS("  ! EAX error: Cannot set environment.\n"));
  hResult = sl_pKSProperty->Set( DSPROPSETID_EAX_ListenerProperties, DSPROPERTY_EAXLISTENER_ENVIRONMENTSIZE, NULL, 0, &fEnvSize, sizeof(FLOAT));
  if( hResult != DS_OK) return DSFail( *this, TRANS("  ! EAX error: Cannot set environment size.\n"));
  return TRUE;
}


// mute all sounds (erase playing buffer(s) and supress mixer)
void CSoundLibrary::Mute(void)
{
  // stop all IFeel effects
  IFeel_StopEffect(NULL);

  // erase direct sound buffer (waveout will shut-up by itself), but skip if there's no more sound library
  if( this==NULL || !sl_bUsingDirectSound) return;

  // synchronize access to sounds
  CTSingleLock slSounds(&sl_csSound, TRUE);

  // supress future mixing and erase sound buffer
  _bMuted = TRUE;
  static LPVOID lpData;
  static DWORD  dwSize;

  // flush one secondary buffer
  if( !DSLockBuffer( *this, sl_pDSSecondary, sl_slMixerBufferSize, lpData, dwSize)) return;
  memset( lpData, 0, dwSize);
  sl_pDSSecondary->Unlock( lpData, dwSize, NULL, 0);
  // if EAX is in use
  if( sl_bUsingEAX) {
    // flush right buffer, too
    if( !DSLockBuffer( *this, sl_pDSSecondary2, sl_slMixerBufferSize, lpData, dwSize)) return;
    memset( lpData, 0, dwSize);
    sl_pDSSecondary2->Unlock( lpData, dwSize, NULL, 0);
  } 
}





/*
 * set sound format
 */
CSoundLibrary::SoundFormat CSoundLibrary::SetFormat( CSoundLibrary::SoundFormat EsfNew, BOOL bReport/*=FALSE*/)
{
  // access to the list of handlers must be locked
  CTSingleLock slHooks(&_pTimer->tm_csHooks, TRUE);
  // synchronize access to sounds
  CTSingleLock slSounds(&sl_csSound, TRUE);

  // pause playing all sounds
  {FOREACHINLIST( CSoundData, sd_Node, sl_ClhAwareList, itCsdStop) {
    itCsdStop->PausePlayingObjects();
  }}

  // change format and keep console variable states
  SetFormat_internal( *this, EsfNew, bReport);
  _tmLastMixAhead = snd_tmMixAhead;
  _iLastFormat = snd_iFormat;
  _iLastDevice = snd_iDevice;
  _iLastAPI = snd_iInterface;

  // continue playing all sounds
  CListHead lhToReload;
  lhToReload.MoveList(sl_ClhAwareList);
  {FORDELETELIST( CSoundData, sd_Node, lhToReload, itCsdContinue) {
    CSoundData &sd = *itCsdContinue;
    if( !(sd.sd_ulFlags&SDF_ENCODED)) {
      sd.Reload();
    } else {
      sd.sd_Node.Remove();
      sl_ClhAwareList.AddTail(sd.sd_Node);
    }
    sd.ResumePlayingObjects();
  }}

  // done
  return sl_EsfFormat;
}



/* Update all 3d effects and copy internal data. */
void CSoundLibrary::UpdateSounds(void)
{
  // see if we have valid handle for direct sound and eventually reinit sound
  if( sl_bUsingDirectSound && _hwndCurrent!=_hwndMain) {
    _hwndCurrent = _hwndMain;
    SetFormat( sl_EsfFormat);
  }
  _bMuted = FALSE; // enable mixer
  _sfStats.StartTimer(CStatForm::STI_SOUNDUPDATE);
  _pfSoundProfile.StartTimer(CSoundProfile::PTI_UPDATESOUNDS);

  // synchronize access to sounds
  CTSingleLock slSounds( &sl_csSound, TRUE);

  // make sure that the buffers are playing
  if( sl_bUsingDirectSound) DSPlayBuffers(*this);

  // determine number of listeners and get listener
  INDEX ctListeners=0;
  CSoundListener *sli;
  {FOREACHINLIST( CSoundListener, sli_lnInActiveListeners, _pSound->sl_lhActiveListeners, itsli) {
    sli = itsli;
    ctListeners++;
  }}
  // if there's only one listener environment properties have been changed (in split-screen EAX is not supported)
  if( ctListeners==1 && (_iLastEnvType!=sli->sli_iEnvironmentType || _fLastEnvSize!=sli->sli_fEnvironmentSize)) {
    // keep new properties and eventually update environment (EAX)
    _iLastEnvType = sli->sli_iEnvironmentType;
    _fLastEnvSize = sli->sli_fEnvironmentSize;
    SetEnvironment( _iLastEnvType, _fLastEnvSize);
  }
  // if there are no listeners - reset environment properties
  if( ctListeners<1 && (_iLastEnvType!=1 || _fLastEnvSize!=1.4f)) {
    // keep new properties and update environment
    _iLastEnvType = 1;
    _fLastEnvSize = 1.4f;
    SetEnvironment( _iLastEnvType, _fLastEnvSize);
  }

  // adjust panning if needed
  snd_fEAXPanning = Clamp( snd_fEAXPanning, -1.0f, +1.0f);
  if( sl_bUsingEAX && _fLastPanning!=snd_fEAXPanning)
  { // determine new panning
    _fLastPanning = snd_fEAXPanning;
    FLOAT fPanLeft  = -1.0f;
    FLOAT fPanRight = +1.0f;
    if( snd_fEAXPanning<0) fPanRight = MINPAN + Abs(snd_fEAXPanning)*MAXPAN;  // pan left
    if( snd_fEAXPanning>0) fPanLeft  = MINPAN + Abs(snd_fEAXPanning)*MAXPAN;  // pan right
    // set and apply
    HRESULT hr1,hr2,hr3;
    hr1 = sl_pDSSourceLeft->SetPosition(  fPanLeft, 0,0, DS3D_DEFERRED);
    hr2 = sl_pDSSourceRight->SetPosition( fPanRight,0,0, DS3D_DEFERRED);
    hr3 = sl_pDSListener->CommitDeferredSettings();
    if( hr1!=DS_OK || hr2!=DS_OK || hr3!=DS_OK) DSFail( *this, TRANS("  ! DirectSound3D error: Cannot set 3D position.\n"));
  }

  // for each sound
  {FOREACHINLIST( CSoundData, sd_Node, sl_ClhAwareList, itCsdSoundData) {
    FORDELETELIST( CSoundObject, so_Node, itCsdSoundData->sd_ClhLinkList, itCsoSoundObject) {
      _sfStats.IncrementCounter(CStatForm::SCI_SOUNDSACTIVE);
      itCsoSoundObject->Update3DEffects();
    }
  }}

  // for each sound
  {FOREACHINLIST( CSoundData, sd_Node, sl_ClhAwareList, itCsdSoundData) {
    FORDELETELIST( CSoundObject, so_Node, itCsdSoundData->sd_ClhLinkList, itCsoSoundObject) {
      CSoundObject &so = *itCsoSoundObject;
      // if sound is playing
      if( so.so_slFlags&SOF_PLAY) {
        // copy parameters
        so.so_sp = so.so_spNew;
        // prepare sound if not prepared already
        if ( !(so.so_slFlags&SOF_PREPARE)) {
          so.PrepareSound();
          so.so_slFlags |= SOF_PREPARE;
        }
      // if it is not playing
      } else {
        // remove it from list
        so.so_Node.Remove();
      }
    }
  }}

  // remove all listeners
  {FORDELETELIST( CSoundListener, sli_lnInActiveListeners, sl_lhActiveListeners, itsli) {
    itsli->sli_lnInActiveListeners.Remove();
  }}

  _pfSoundProfile.StopTimer(CSoundProfile::PTI_UPDATESOUNDS);
  _sfStats.StopTimer(CStatForm::STI_SOUNDUPDATE);
}


/*
 * This is called every TickQuantum seconds.
 */
void CSoundTimerHandler::HandleTimer(void)
{
  /* memory leak checking routines
  ASSERT( _CrtCheckMemory());
  ASSERT( _CrtIsMemoryBlock( (void*)_pSound->sl_pswDecodeBuffer,
                             (ULONG)_pSound->sl_slDecodeBufferSize, NULL, NULL, NULL));
  ASSERT( _CrtIsValidPointer( (void*)_pSound->sl_pswDecodeBuffer,
                              (ULONG)_pSound->sl_slDecodeBufferSize, TRUE)); */
  // mix all needed sounds
  _pSound->MixSounds();
}



/*
 *  MIXER helper functions
 */


// copying of mixer buffer to sound buffer(s)

static LPVOID _lpData, _lpData2;
static DWORD  _dwSize, _dwSize2;

static void CopyMixerBuffer_dsound( CSoundLibrary &sl, SLONG slMixedSize)
{
  LPVOID lpData;
  DWORD dwSize;
  SLONG slPart1Size, slPart2Size;

  // if EAX is in use
  if( sl.sl_bUsingEAX)
  {
    // lock left buffer and copy first part of 1st mono block
    if( !DSLockBuffer( sl, sl.sl_pDSSecondary, sl.sl_slMixerBufferSize, lpData, dwSize)) return;
    slPart1Size = Min( sl.sl_slMixerBufferSize-_iWriteOffset, slMixedSize);
    CopyMixerBuffer_mono( 0, ((UBYTE*)lpData)+_iWriteOffset/2, slPart1Size);
    // copy second part of 1st mono block
    slPart2Size = slMixedSize - slPart1Size;
    CopyMixerBuffer_mono( slPart1Size, lpData, slPart2Size);
    _iWriteOffset += slMixedSize;
    if( _iWriteOffset>=sl.sl_slMixerBufferSize) _iWriteOffset -= sl.sl_slMixerBufferSize;
    ASSERT( _iWriteOffset>=0 && _iWriteOffset<sl.sl_slMixerBufferSize);
    sl.sl_pDSSecondary->Unlock( lpData, dwSize, NULL, 0); 

    // lock right buffer and copy first part of 2nd mono block
    if( !DSLockBuffer( sl, sl.sl_pDSSecondary2, sl.sl_slMixerBufferSize, lpData, dwSize)) return;
    slPart1Size = Min( sl.sl_slMixerBufferSize-_iWriteOffset2, slMixedSize);
    CopyMixerBuffer_mono( 2, ((UBYTE*)lpData)+_iWriteOffset2/2, slPart1Size);
    // copy second part of 2nd mono block
    slPart2Size = slMixedSize - slPart1Size;
    CopyMixerBuffer_mono( slPart1Size+2, lpData, slPart2Size);
    _iWriteOffset2 += slMixedSize;
    if( _iWriteOffset2>=sl.sl_slMixerBufferSize) _iWriteOffset2 -= sl.sl_slMixerBufferSize;
    ASSERT( _iWriteOffset2>=0 && _iWriteOffset2<sl.sl_slMixerBufferSize);
    sl.sl_pDSSecondary2->Unlock( lpData, dwSize, NULL, 0);
  }
  // if only standard DSound (no EAX)
  else
  {
    // lock stereo buffer and copy first part of block
    if( !DSLockBuffer( sl, sl.sl_pDSSecondary, sl.sl_slMixerBufferSize, lpData, dwSize)) return;
    slPart1Size = Min( sl.sl_slMixerBufferSize-_iWriteOffset, slMixedSize);
    CopyMixerBuffer_stereo( 0, ((UBYTE*)lpData)+_iWriteOffset, slPart1Size);
    // copy second part of block
    slPart2Size = slMixedSize - slPart1Size;
    CopyMixerBuffer_stereo( slPart1Size, lpData, slPart2Size);
    _iWriteOffset += slMixedSize;
    if( _iWriteOffset>=sl.sl_slMixerBufferSize) _iWriteOffset -= sl.sl_slMixerBufferSize;
    ASSERT( _iWriteOffset>=0 && _iWriteOffset<sl.sl_slMixerBufferSize);
    sl.sl_pDSSecondary->Unlock( lpData, dwSize, NULL, 0);
  }
}


static void CopyMixerBuffer_waveout( CSoundLibrary &sl)
{
  MMRESULT res;
  SLONG slOffset = 0;
  for( INDEX iBuffer = 0; iBuffer<sl.sl_awhWOBuffers.Count(); iBuffer++)
  { // skip prepared buffer
    WAVEHDR &wh = sl.sl_awhWOBuffers[iBuffer];
    if( wh.dwFlags&WHDR_PREPARED) continue;
    // copy part of a mixer buffer to wave buffer
    CopyMixerBuffer_stereo( slOffset, wh.lpData, WAVEOUTBLOCKSIZE);
    slOffset += WAVEOUTBLOCKSIZE;
    // write wave buffer (ready for playing)
    res = waveOutPrepareHeader( sl.sl_hwoWaveOut, &wh, sizeof(wh));
    ASSERT( res==MMSYSERR_NOERROR);
    res = waveOutWrite( sl.sl_hwoWaveOut, &wh, sizeof(wh));
    ASSERT( res==MMSYSERR_NOERROR);
  }
}


// finds room in sound buffer to copy in next crop of samples
static SLONG PrepareSoundBuffer_dsound( CSoundLibrary &sl)
{
  // determine writable block size (difference between write and play pointers)
  HRESULT hr1,hr2;
  DWORD dwCurrentCursor, dwCurrentCursor2;
  SLONG slDataToMix;
  ASSERT( sl.sl_pDSSecondary!=NULL && sl.sl_pDSPrimary!=NULL);

  // if EAX is in use
  if( sl.sl_bUsingEAX)
  {
    hr1 = sl.sl_pDSSecondary->GetCurrentPosition(  &dwCurrentCursor,  NULL);
    hr2 = sl.sl_pDSSecondary2->GetCurrentPosition( &dwCurrentCursor2, NULL);
    if( hr1!=DS_OK || hr2!=DS_OK) return DSFail( sl, TRANS("  ! DirectSound error: Cannot obtain sound buffer write position.\n"));
    dwCurrentCursor *=2; // stereo mixer
    dwCurrentCursor2*=2; // stereo mixer
    // store pointers and wrapped block sizes
    SLONG slDataToMix1 = dwCurrentCursor - _iWriteOffset;
    if( slDataToMix1<0) slDataToMix1 += sl.sl_slMixerBufferSize;
    ASSERT( slDataToMix1>=0 && slDataToMix1<=sl.sl_slMixerBufferSize);
    slDataToMix1 = Min( slDataToMix1, sl.sl_slMixerBufferSize); 
    SLONG slDataToMix2 = dwCurrentCursor2 - _iWriteOffset2;
    if( slDataToMix2<0) slDataToMix2 += sl.sl_slMixerBufferSize;
    ASSERT( slDataToMix2>=0 && slDataToMix2<=sl.sl_slMixerBufferSize);
    slDataToMix = Min( slDataToMix1, slDataToMix2);
  }
  // if only standard DSound (no EAX)
  else
  {
    hr1 = sl.sl_pDSSecondary->GetCurrentPosition( &dwCurrentCursor, NULL);
    if( hr1!=DS_OK) return DSFail( sl, TRANS("  ! DirectSound error: Cannot obtain sound buffer write position.\n"));
    // store pointer and wrapped block size
    slDataToMix = dwCurrentCursor - _iWriteOffset;
    if( slDataToMix<0) slDataToMix += sl.sl_slMixerBufferSize;
    ASSERT( slDataToMix>=0 && slDataToMix<=sl.sl_slMixerBufferSize);
    slDataToMix = Min( slDataToMix, sl.sl_slMixerBufferSize); 
  }

  // done
  //CPrintF( "LP/LW: %5d / %5d,   RP/RW: %5d / %5d,    MIX: %5d\n", dwCurrentCursor, _iWriteOffset, dwCurrentCursor2, _iWriteOffset2, slDataToMix); // grgr
  return slDataToMix;
}


static SLONG PrepareSoundBuffer_waveout( CSoundLibrary &sl)
{
  // scan waveout buffers to find all that are ready to receive sound data (i.e. not playing)
  SLONG slDataToMix=0;
  for( INDEX iBuffer=0; iBuffer<sl.sl_awhWOBuffers.Count(); iBuffer++)
  { // if done playing
    WAVEHDR &wh = sl.sl_awhWOBuffers[iBuffer];
    if( wh.dwFlags&WHDR_DONE) {
      // unprepare buffer
      MMRESULT res = waveOutUnprepareHeader( sl.sl_hwoWaveOut, &wh, sizeof(wh));
      ASSERT( res == MMSYSERR_NOERROR);
    }
    // if unprepared
    if( !(wh.dwFlags&WHDR_PREPARED)) {
      // increase mix-in data size
      slDataToMix += WAVEOUTBLOCKSIZE;
    }
  }
  // done
  ASSERT( slDataToMix <= sl.sl_slMixerBufferSize);
  return slDataToMix;
}

  
/* Update Mixer */
void CSoundLibrary::MixSounds(void)
{
  // synchronize access to sounds
  CTSingleLock slSounds( &sl_csSound, TRUE);

  // do nothing if no sound
  if( sl_EsfFormat==SF_NONE || _bMuted) return;

  _sfStats.StartTimer(CStatForm::STI_SOUNDMIXING);
  _pfSoundProfile.IncrementAveragingCounter();
  _pfSoundProfile.StartTimer(CSoundProfile::PTI_MIXSOUNDS);

  // seek available buffer(s) for next crop of samples
  SLONG slDataToMix;
  if( sl_bUsingDirectSound) { // using direct sound
    slDataToMix = PrepareSoundBuffer_dsound( *this);
  } else { // using wave out 
    slDataToMix = PrepareSoundBuffer_waveout(*this);
  }

  // skip mixing if all sound buffers are still busy playing
  ASSERT( slDataToMix>=0);
  if( slDataToMix<=0) {
    _pfSoundProfile.StopTimer(CSoundProfile::PTI_MIXSOUNDS);
    _sfStats.StopTimer(CStatForm::STI_SOUNDMIXING);
    return;
  }

  // prepare mixer buffer
  _pfSoundProfile.IncrementCounter(CSoundProfile::PCI_MIXINGS, 1);
  ResetMixer( sl_pslMixerBuffer, slDataToMix);

  BOOL bGamePaused = _pNetwork->IsPaused() || _pNetwork->IsServer() && _pNetwork->GetLocalPause();

  // for each sound
  FOREACHINLIST( CSoundData, sd_Node, sl_ClhAwareList, itCsdSoundData) {
    FORDELETELIST( CSoundObject, so_Node, itCsdSoundData->sd_ClhLinkList, itCsoSoundObject) {
      CSoundObject &so = *itCsoSoundObject;
      // if the sound is in-game sound, and the game paused
      if (!(so.so_slFlags&SOF_NONGAME) && bGamePaused) {
        // don't mix it it
        continue;
      }
      // if sound is prepared and playing
      if( so.so_slFlags&SOF_PLAY && 
          so.so_slFlags&SOF_PREPARE &&
        !(so.so_slFlags&SOF_PAUSED)) {
        // mix it
        MixSound(&so);
      }
    }
  }

  // eventually normalize mixed sounds
  snd_fNormalizer = Clamp( snd_fNormalizer, 0.0f, 1.0f);
  NormalizeMixerBuffer( snd_fNormalizer, slDataToMix, _fLastNormalizeValue);

  // write mixer buffer to file
  // if( !_bOpened) _filMixerBuffer = fopen( "d:\\MixerBufferDump.raw", "wb");
  // fwrite( (void*)sl_pslMixerBuffer, 1, slDataToMix, _filMixerBuffer);
  // _bOpened = TRUE;

  // copy mixer buffer to buffers buffer(s)
  if( sl_bUsingDirectSound) { // using direct sound
    CopyMixerBuffer_dsound( *this, slDataToMix);
  } else { // using wave out 
    CopyMixerBuffer_waveout(*this);
  }

  // all done
  _pfSoundProfile.StopTimer(CSoundProfile::PTI_MIXSOUNDS);
  _sfStats.StopTimer(CStatForm::STI_SOUNDMIXING);
}


//
//  Sound mode awareness functions
//

/*
 *  Add sound in sound aware list
 */
void CSoundLibrary::AddSoundAware(CSoundData &CsdAdd) {
  // add sound to list tail
  sl_ClhAwareList.AddTail(CsdAdd.sd_Node);
};

/*
 *  Remove a display mode aware object.
 */
void CSoundLibrary::RemoveSoundAware(CSoundData &CsdRemove) {
  // remove it from list
  CsdRemove.sd_Node.Remove();
};

// listen from this listener this frame
void CSoundLibrary::Listen(CSoundListener &sl)
{
  // just add it to list
  if (sl.sli_lnInActiveListeners.IsLinked()) {
    sl.sli_lnInActiveListeners.Remove();
  }
  sl_lhActiveListeners.AddTail(sl.sli_lnInActiveListeners);
}
