/* 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 "Engine/StdH.h"

#include <Engine/Sound/SoundProfile.h>
#include <Engine/Sound/SoundDecoder.h>
#include <Engine/Sound/SoundLibrary.h>
#include <Engine/Sound/SoundData.h>
#include <Engine/Sound/SoundObject.h>
#include <Engine/Base/Statistics_Internal.h>
#include <Engine/Base/Console.h>

// asm shortcuts
#define O offset
#define Q qword ptr
#define D dword ptr
#define W  word ptr
#define B  byte ptr

// console variables for volume
extern FLOAT snd_fSoundVolume;
extern FLOAT snd_fMusicVolume;
extern INDEX snd_bMono;


// a bunch of local vars coming up

static SLONG slMixerBufferSampleRate;  // quality of destination buffer
static CSoundData *psd;

// nasm on MacOS X is getting wrong addresses of external globals, so I have
//  to define them in the .asm file...lame.
#ifdef __GNU_INLINE__
#ifdef USE_PORTABLE_C
#define INASM 
#else
#define INASM extern
#endif
#else
#define INASM static
static __int64 mmInvFactor   = 0x00007FFF00007FFF;
static FLOAT f65536 = 65536.0f;
static FLOAT f4G    = 4294967296.0f;
#endif

INASM SLONG slMixerBufferSize;        // size in samples per channel of the destination buffers
INASM void *pvMixerBuffer;            // pointer to the start of the destination buffers
INASM SWORD *pswSrcBuffer;
INASM SLONG slLeftVolume,  slRightVolume, slLeftFilter, slRightFilter;
INASM SLONG slLastLeftSample, slLastRightSample, slSoundBufferSize;
INASM FLOAT fSoundSampleRate, fPhase;
INASM FLOAT fOfsDelta, fStep, fLeftStep, fRightStep, fLeftOfs, fRightOfs;
INASM __int64 fixLeftOfs, fixRightOfs; // fixed integers 32:32
INASM __int64 mmSurroundFactor, mmLeftStep, mmRightStep, mmVolumeGain;
INASM BOOL bNotLoop, bEndOfSound;

// reset mixer buffer (wipes it with zeroes and remembers pointers in static mixer variables)
void ResetMixer( const SLONG *pslBuffer, const SLONG slBufferSize)
{
  // clamp master volumes
  snd_fSoundVolume = Clamp(snd_fSoundVolume, 0.0f, 1.0f);
  snd_fMusicVolume = Clamp(snd_fMusicVolume, 0.0f, 1.0f);

  // cache local variables
  ASSERT( slBufferSize%4==0);
  pvMixerBuffer     = (void*)pslBuffer;
  slMixerBufferSize = slBufferSize /2/2; // because it's stereo and 16-bit dst format
  slMixerBufferSampleRate = _pSound->sl_SwfeFormat.nSamplesPerSec;

  // wipe destination mixer buffer
  // (Mac OS X uses this path because Apple's memset() is customized for each CPU they support and way faster than this inline asm. --ryan.)
  #if ((defined USE_PORTABLE_C) || (PLATFORM_MACOSX))
  memset(pvMixerBuffer, 0, slMixerBufferSize * 8);

  #elif (defined __MSVC_INLINE__)
  __asm {
    cld
    xor     eax,eax
    mov     edi,D [pvMixerBuffer]
    mov     ecx,D [slMixerBufferSize]
    shl     ecx,1 // *2 because of 32-bit src format
    rep     stosd
  }
  #elif (defined __GNU_INLINE__)
  // !!! FIXME : rcg12172001 Is this REALLY any faster than memset()?
  __asm__ __volatile__ (
    "cld                  \n\t"
    "rep                  \n\t"
    "stosl                \n\t"
        : // no outputs.
        : "a" (0), "D" (pvMixerBuffer), "c" (slMixerBufferSize*2)
        : "cc", "memory"
  );
  #else
    #error please write inline asm for your platform.
  #endif
}


// copy mixer buffer to the output buffer(s)
void CopyMixerBuffer_stereo( const SLONG slSrcOffset, void *pDstBuffer, const SLONG slBytes)
{
  ASSERT( pDstBuffer!=NULL);
  ASSERT( slBytes%4==0);
  if( slBytes<4) return;

  #if ((defined USE_PORTABLE_C) || (PLATFORM_MACOSX))
  // (Mac OS X uses this path because Apple's memset() is customized for each CPU they support and way faster than this inline asm. --ryan.)
  memcpy(pDstBuffer, ((const char *)pvMixerBuffer) + slSrcOffset, slBytes);
  #elif (defined __MSVC_INLINE__)
  __asm {
    cld
    mov     esi,D [slSrcOffset]
    add     esi,D [pvMixerBuffer]
    mov     edi,D [pDstBuffer]
    mov     ecx,D [slBytes]
    shr     ecx,2   // bytes to samples per channel
    rep     movsd
  }
  #elif (defined __GNU_INLINE__)
  // !!! FIXME : rcg12172001 Is this REALLY any faster than memcpy()?
  __asm__ __volatile__ (
    "cld                 \n\t"
    "rep                 \n\t"
    "movsl               \n\t"
      : // no outputs.
      : "S" (((char *)pvMixerBuffer) + slSrcOffset),
        "D" (pDstBuffer),
        "c" (slBytes >> 2)
      : "cc", "memory"
  );
  #else
  #error please write inline asm for your platform.
  #endif
}


// copy one channel from mixer buffer to the output buffer(s)
void CopyMixerBuffer_mono( const SLONG slSrcOffset, void *pDstBuffer, const SLONG slBytes)
{
  ASSERT( pDstBuffer!=NULL);
  ASSERT( slBytes%2==0);
  if( slBytes<4) return;

  #if (defined USE_PORTABLE_C)
  // (This is untested, currently. --ryan.)
  WORD *dest = (WORD *) pDstBuffer;
  WORD *src = (WORD *) ( ((char *) pvMixerBuffer) + slSrcOffset );
  SLONG max = slBytes / 4;
  for (SLONG i = 0; i < max; i++) {
      *dest = *src;
      dest++;    // move 16 bits.
      src+=2;    // move 32 bits.
  }

  #elif (defined __MSVC_INLINE__)
  __asm {
    mov     esi,D [slSrcOffset]
    add     esi,D [pvMixerBuffer]
    mov     edi,D [pDstBuffer]
    mov     ecx,D [slBytes]
    shr     ecx,2   // bytes to samples
copyLoop:
    movzx   eax,W [esi]
    mov     W [edi],ax
    add     esi,4
    add     edi,2
    dec     ecx
    jnz     copyLoop
  }

  #elif (defined __GNU_INLINE__)
  __asm__ __volatile__ (
    "0:                                       \n\t" // copyLoop
    "movzwl   (%%esi), %%eax                  \n\t"
    "movw     %%ax, (%%edi)                   \n\t"
    "addl     $4, %%esi                       \n\t"
    "addl     $2, %%edi                       \n\t"
    "decl     %%ecx                           \n\t"
    "jnz      0b                              \n\t" // copyLoop
      : // no outputs.
      : "S" (((char *)pvMixerBuffer) + slSrcOffset),
        "D" (pDstBuffer),
        "c" (slBytes >> 2)
      : "cc", "memory", "eax"
  );

  #else
  #error please write inline asm for your platform.
  #endif
}


// plain conversion of mixer buffer from 32-bit to 16-bit clamped
static void ConvertMixerBuffer( const SLONG slBytes)
{
  ASSERT( slBytes%4==0);
  if( slBytes<4) return;

  #if (defined USE_PORTABLE_C)
  //STUBBED("ConvertMixerBuffer");
  SWORD *dest = (SWORD *) pvMixerBuffer;
  SLONG *src = (SLONG *) pvMixerBuffer;
  SLONG max = slBytes / 2;
  int tmp;
  for (SLONG i = 0; i < max; i++) {
      tmp = *src;
      if (tmp>32767) tmp=32767;
      if (tmp<-32767) tmp=-32767;
      *dest=tmp;
      dest++;    // move 16 bits.
      src++;     // move 32 bits.
  }



  #elif (defined __MSVC_INLINE__)
  __asm {
    cld
    mov     esi,D [pvMixerBuffer]
    mov     edi,D [pvMixerBuffer]
    mov     ecx,D [slBytes]
    shr     ecx,2 // bytes to samples (2 channels)
copyLoop:
    movq    mm0,Q [esi]
    packssdw mm0,mm0
    movd    D [edi],mm0
    add     esi,8
    add     edi,4
    dec     ecx
    jnz     copyLoop
    emms
  }

  #elif (defined __GNU_INLINE__)
  __asm__ __volatile__ (
    "cld                                   \n\t"
    "0:                                    \n\t" // copyLoop
    "movq     (%%esi), %%mm0               \n\t"
    "packssdw %%mm0, %%mm0                 \n\t"
    "movd     %%mm0, (%%edi)               \n\t"
    "addl     $8, %%esi                    \n\t"
    "addl     $4, %%edi                    \n\t"
    "decl     %%ecx                        \n\t"
    "jnz      0b                           \n\t" // copyLoop
    "emms                                  \n\t"
      : // no outputs.
      : "S" (pvMixerBuffer), "D" (pvMixerBuffer), "c" (slBytes >> 2)
      : "cc", "memory"
  );

  #else
  #error please write inline asm for your platform.
  #endif
}


// normalize mixer buffer
void NormalizeMixerBuffer( const FLOAT fNormStrength, const SLONG slBytes, FLOAT &fLastNormValue)
{
  // just convert to 16-bit if normalization isn't required
  ASSERT( slBytes%4==0);
  if( slBytes<8) return;
  if( fNormStrength<0.01f) {
    ConvertMixerBuffer(slBytes);
    return;
  }

  // well, I guess we'll might need to normalize a bit, so first - find maximum
  INDEX i;
  SLONG slPeak = 0;
  SLONG *pslSrc = (SLONG*)pvMixerBuffer;
  const INDEX iSamples = slBytes/2; // 16-bit was assumed -> samples (treat as mono)
  for( i=0; i<iSamples; i++) slPeak = Max( Abs(pslSrc[i]), slPeak);

  // determine normalize value and skip normalization if maximize is required (do not increase volume!)
  FLOAT fNormValue = 32767.0f / (FLOAT)slPeak;
  if( fNormValue>0.99f && fLastNormValue>0.99f) { // should be enough to tolerate
    fLastNormValue = 1.0f;
    ConvertMixerBuffer(slBytes);
    return;
  }

  // adjust normalize value by strength
  ASSERT( fNormStrength>=0 && fNormStrength<=1);
  fNormValue = Lerp( 1.0f, fNormValue, fNormStrength);
  const FLOAT fNormAdd = (fNormValue-fLastNormValue) / (iSamples/4); 
  
  // normalize (and convert to 16-bit)
  SWORD *pswDst = (SWORD*)pvMixerBuffer;
  FLOAT fCurrentNormValue = fLastNormValue;
  for( i=0; i<iSamples; i++) {
    SLONG slSample = FloatToInt(pslSrc[i]*fCurrentNormValue);
    pswDst[i] = (SWORD)Clamp( slSample, -32767L, +32767L);
    fCurrentNormValue = fCurrentNormValue+fNormAdd; // interpolate normalizer
         if( fCurrentNormValue<fNormValue && fNormAdd<0) fCurrentNormValue = fNormValue; // clamp interpolated value
    else if( fCurrentNormValue>fNormValue && fNormAdd>0) fCurrentNormValue = fNormValue;
  }
  // CPrintF( "%.5f -> %.5f (%.5f) @ %.9f / %d\n", fLastNormValue, fCurrentNormValue, fNormValue, fNormAdd, iSamples);
  // remember normalization value
  fLastNormValue = fCurrentNormValue;
}
 

#ifdef __GNU_INLINE__
// These are implemented in an external NASM file.
extern "C" {
    void MixStereo_asm(CSoundObject *pso);
    void MixMono_asm(CSoundObject *pso);
}
#endif


// mixes one mono 16-bit signed sound to destination buffer
inline void MixMono( CSoundObject *pso)
{
  _pfSoundProfile.StartTimer(CSoundProfile::PTI_RAWMIXER);

 #if (defined USE_PORTABLE_C)
  // initialize some local vars
  SLONG slLeftSample, slRightSample, slNextSample;
  SLONG *pslDstBuffer = (SLONG*)pvMixerBuffer;
  fixLeftOfs   = (__int64)(fLeftOfs   * 65536.0);
  fixRightOfs  = (__int64)(fRightOfs  * 65536.0);
  __int64 fixLeftStep  = (__int64)(fLeftStep  * 65536.0);
  __int64 fixRightStep = (__int64)(fRightStep * 65536.0);
  __int64 fixSoundBufferSize = ((__int64)slSoundBufferSize)<<16;
  mmSurroundFactor = (__int64)(SWORD)mmSurroundFactor;

  SLONG slLeftVolume_ = slLeftVolume >> 16;
  SLONG slRightVolume_ = slRightVolume >> 16;

  // loop thru source buffer
  INDEX iCt = slMixerBufferSize;
  FOREVER
  {
    // if left channel source sample came to end of sample buffer
    if( fixLeftOfs >= fixSoundBufferSize) {
      fixLeftOfs -= fixSoundBufferSize;
      // if has no loop, end it
      bEndOfSound = bNotLoop;
    }
    // if right channel source sample came to end of sample buffer
    if( fixRightOfs >= fixSoundBufferSize) {
      fixRightOfs -= fixSoundBufferSize;
      // if has no loop, end it
      bEndOfSound = bNotLoop;
    }
    // end of buffer?
    if( iCt<=0 || bEndOfSound) break;

    // fetch one lineary interpolated sample on left channel
    slLeftSample = pswSrcBuffer[(fixLeftOfs>>16)+0];
    slNextSample = pswSrcBuffer[(fixLeftOfs>>16)+1];
    slLeftSample = (slLeftSample*(65535-(fixLeftOfs&65535)) + slNextSample*(fixLeftOfs&65535)) >>16;
    // fetch one lineary interpolated sample on right channel
    slRightSample = pswSrcBuffer[(fixRightOfs>>16)+0];
    slNextSample  = pswSrcBuffer[(fixRightOfs>>16)+1];
    slRightSample = (slRightSample*(65535-(fixRightOfs&65535)) + slNextSample*(fixRightOfs&65535)) >>16;

    // filter samples
    slLastLeftSample  += ((slLeftSample -slLastLeftSample) *slLeftFilter) >>15;
    slLastRightSample += ((slRightSample-slLastRightSample)*slRightFilter)>>15;

    // apply stereo volume to current sample
    slLeftSample  = (slLastLeftSample  * slLeftVolume_) >>15;
    slRightSample = (slLastRightSample * slRightVolume_)>>15;

    slLeftSample  ^= (SLONG)((mmSurroundFactor>> 0)&0xFFFFFFFF);
    slRightSample ^= (SLONG)((mmSurroundFactor>>32)&0xFFFFFFFF);

    // mix in current sample
    slLeftSample  += pslDstBuffer[0];
    slRightSample += pslDstBuffer[1];
    // upper clamp
    if( slLeftSample  > MAX_SWORD) slLeftSample  = MAX_SWORD;
    if( slRightSample > MAX_SWORD) slRightSample = MAX_SWORD;
    // lower clamp
    if( slLeftSample  < MIN_SWORD) slLeftSample  = MIN_SWORD;
    if( slRightSample < MIN_SWORD) slRightSample = MIN_SWORD;

    // store samples (both channels)
    pslDstBuffer[0] = slLeftSample;
    pslDstBuffer[1] = slRightSample;

    // modify volume  `
    slLeftVolume  += (SWORD)((mmVolumeGain>> 0)&0xFFFF);
    slRightVolume += (SWORD)((mmVolumeGain>>16)&0xFFFF);

    // advance to next sample
    fixLeftOfs   += fixLeftStep;
    fixRightOfs  += fixRightStep;
    pslDstBuffer += 2;
    iCt--;
  }

 #elif (defined __MSVC_INLINE__)
  __asm {
    // convert from floats to fixints 32:16
    fld     D [fLeftOfs]
    fmul    D [f65536]
    fld     D [fRightOfs]
    fmul    D [f65536]
    fld     D [fLeftStep]
    fmul    D [f65536]
    fld     D [fRightStep]
    fmul    D [f4G]
    fistp   Q [mmRightStep] // fixint 32:32
    fistp   Q [mmLeftStep]  // fixint 32:16
    fistp   Q [fixRightOfs] // fixint 32:16
    fistp   Q [fixLeftOfs]  // fixint 32:16

    // get last played sample (for filtering purposes)
    movzx   eax,W [slLastRightSample]
    movzx   edx,W [slLastLeftSample]
    shl     eax,16
    or      eax,edx
    movd    mm6,eax                       // MM6 = 0 | 0 || lastRightSample | lastLeftSample

    // get volume
    movd    mm5,D [slRightVolume]
    movd    mm0,D [slLeftVolume]
    psllq   mm5,32
    por     mm5,mm0                       // MM5 = rightVolume || leftVolume

    // get filter
    mov     eax,D [slRightFilter]
    mov     edx,D [slLeftFilter]
    shl     eax,16
    or      eax,edx
    movd    mm7,eax                       // MM7 = 0 | 0 || rightFilter | leftFilter

    // get offset of each channel inside sound and loop thru destination buffer
    mov     W [mmRightStep],0
    movzx   eax,W [fixLeftOfs]
    movzx   edx,W [fixRightOfs]
    shl     edx,16
    or      eax,edx                       // EAX = right ofs frac | left ofs frac
    mov     ebx,D [fixLeftOfs+2]          // EBX = left ofs int
    mov     edx,D [fixRightOfs+2]         // EDX = right ofs int
    mov     esi,D [pswSrcBuffer]          // ESI = source sound buffer start ptr
    mov     edi,D [pvMixerBuffer]         // EDI = mixer buffer ptr
    mov     ecx,D [slMixerBufferSize]     // ECX = samples counter

sampleLoop:
    // check if source offsets came to the end of source sound buffer
    cmp     ebx,D [slSoundBufferSize]
    jl      lNotEnd
    sub     ebx,D [slSoundBufferSize]
    push    D [bNotLoop]
    pop     D [bEndOfSound]
lNotEnd:
    // same for right channel
    cmp     edx,D [slSoundBufferSize]
    jl      rNotEnd
    sub     edx,D [slSoundBufferSize]
    push    D [bNotLoop]
    pop     D [bEndOfSound]
rNotEnd:

    // check end of sample
    cmp     ecx,0
    jle     loopEnd
    cmp     D [bEndOfSound],TRUE
    je      loopEnd

    // get sound samples
    movd    mm1,D [esi+ ebx*2]    // MM1 = 0 | 0 || nextLeftSample  | leftSample
    movd    mm2,D [esi+ edx*2]    // MM2 = 0 | 0 || nextRightSample | RightSample
    psllq   mm2,32
    por     mm1,mm2   // MM1 = nextRightSample | rightSample || nextLeftSample | leftSample

    // calc linear interpolation factor (strength)
    movd    mm3,eax   // MM3 = 0 | 0 || right frac | left frac
    punpcklwd mm3,mm3
    psrlw   mm3,1     // MM3 = rightFrac | rightFrac || leftFrac | leftFrac
    pxor    mm3,Q [mmInvFactor] // MM3 = rightFrac | 1-rightFrac || leftFrac | 1-leftFrac
    // apply linear interpolation
    pmaddwd mm1,mm3
    psrad   mm1,15
    packssdw mm1,mm1  // MM1 = ? | ? || linearRightSample | linearLeftSample

    // apply filter
    psubsw  mm1,mm6
    pmulhw  mm1,mm7
    psllw   mm1,1
    paddsw  mm1,mm6
    movq    mm6,mm1

    // apply volume adjustment
    movq    mm0,mm5
    psrad   mm0,16
    packssdw mm0,mm0
    pmulhw  mm1,mm0
    psllw   mm1,1
    pxor    mm1,Q [mmSurroundFactor]
    paddd   mm5,Q [mmVolumeGain]   // modify volume

    // unpack to 32bit and mix it into destination buffer
    punpcklwd mm1,mm1
    psrad   mm1,16              // MM1 = finalRightSample || finalLeftSample
    paddd   mm1,Q [edi]
    movq    Q [edi],mm1

    // advance to next samples in source sound
    add     eax,D [mmRightStep+0]
    adc     edx,D [mmRightStep+4]
    add      ax,W [mmLeftStep +0]
    adc     ebx,D [mmLeftStep +2]
    add     edi,8
    dec     ecx
    jmp     sampleLoop

loopEnd:
    // store modified asm local vars
    mov     D [fixLeftOfs +0],eax
    shr     eax,16
    mov     D [fixRightOfs+0],eax
    mov     D [fixLeftOfs +2],ebx
    mov     D [fixRightOfs+2],edx
    movd    eax,mm6
    mov     edx,eax
    and     eax,0x0000FFFF
    shr     edx,16
    mov     D [slLastLeftSample],eax
    mov     D [slLastRightSample],edx
    emms
  }

 #elif (defined __GNU_INLINE__)
   // This is implemented in an external NASM file.
   MixMono_asm(pso);

 #else
   #error please write inline asm for your platform.
 #endif

  _pfSoundProfile.StopTimer(CSoundProfile::PTI_RAWMIXER);
}


// mixes one stereo 16-bit signed sound to destination buffer
inline void MixStereo( CSoundObject *pso)
{
  _pfSoundProfile.StartTimer(CSoundProfile::PTI_RAWMIXER);

 #if (defined USE_PORTABLE_C)
  // initialize some local vars
  SLONG slLeftSample, slRightSample, slNextSample;
  SLONG *pslDstBuffer = (SLONG*)pvMixerBuffer;
  fixLeftOfs   = (__int64)(fLeftOfs   * 65536.0);
  fixRightOfs  = (__int64)(fRightOfs  * 65536.0);
  __int64 fixLeftStep  = (__int64)(fLeftStep  * 65536.0);
  __int64 fixRightStep = (__int64)(fRightStep * 65536.0);
  __int64 fixSoundBufferSize = ((__int64)slSoundBufferSize)<<16;
  mmSurroundFactor = (__int64)(SWORD)mmSurroundFactor;

  SLONG slLeftVolume_ = slLeftVolume >> 16;
  SLONG slRightVolume_ = slRightVolume >> 16;

  // loop thru source buffer
  INDEX iCt = slMixerBufferSize;
  FOREVER
  {
    // if left channel source sample came to end of sample buffer
    if( fixLeftOfs >= fixSoundBufferSize) {
      fixLeftOfs -= fixSoundBufferSize;
      // if has no loop, end it
      bEndOfSound = bNotLoop;
    }
    // if right channel source sample came to end of sample buffer
    if( fixRightOfs >= fixSoundBufferSize) {
      fixRightOfs -= fixSoundBufferSize;
      // if has no loop, end it
      bEndOfSound = bNotLoop;
    }
    // end of buffer?
    if( iCt<=0 || bEndOfSound) break;

    // fetch one lineary interpolated sample on left channel
    slLeftSample = pswSrcBuffer[(fixLeftOfs>>15)+0];
    slNextSample = pswSrcBuffer[(fixLeftOfs>>15)+2];
    slLeftSample = (slLeftSample*(65535-(fixLeftOfs&65535)) + slNextSample*(fixLeftOfs&65535)) >>16;
    // fetch one lineary interpolated sample on right channel
    slRightSample = pswSrcBuffer[(fixRightOfs>>15)+0];
    slNextSample  = pswSrcBuffer[(fixRightOfs>>15)+2];
    slRightSample = (slRightSample*(65535-(fixRightOfs&65535)) + slNextSample*(fixRightOfs&65535)) >>16;

    // filter samples
    slLastLeftSample  += ((slLeftSample -slLastLeftSample) *slLeftFilter) >>15;
    slLastRightSample += ((slRightSample-slLastRightSample)*slRightFilter)>>15;

    // apply stereo volume to current sample
    slLeftSample  = (slLastLeftSample  * slLeftVolume_) >>15;
    slRightSample = (slLastRightSample * slRightVolume_)>>15;

    slLeftSample  ^= (SLONG)((mmSurroundFactor>> 0)&0xFFFFFFFF);
    slRightSample ^= (SLONG)((mmSurroundFactor>>32)&0xFFFFFFFF);

    // mix in current sample
    slLeftSample  += pslDstBuffer[0];
    slRightSample += pslDstBuffer[1];
    // upper clamp
    if( slLeftSample  > MAX_SWORD) slLeftSample  = MAX_SWORD;
    if( slRightSample > MAX_SWORD) slRightSample = MAX_SWORD;
    // lower clamp
    if( slLeftSample  < MIN_SWORD) slLeftSample  = MIN_SWORD;
    if( slRightSample < MIN_SWORD) slRightSample = MIN_SWORD;

    // store samples (both channels)
    pslDstBuffer[0] = slLeftSample;
    pslDstBuffer[1] = slRightSample;

    // modify volume  `
    slLeftVolume  += (SWORD)((mmVolumeGain>> 0)&0xFFFF);
    slRightVolume += (SWORD)((mmVolumeGain>>16)&0xFFFF);

    // advance to next sample
    fixLeftOfs   += fixLeftStep;
    fixRightOfs  += fixRightStep;
    pslDstBuffer += 2;
    iCt--;
  }

 #elif (defined __MSVC_INLINE__)
  __asm {
    // convert from floats to fixints 32:16
    fld     D [fLeftOfs]
    fmul    D [f65536]
    fld     D [fRightOfs]
    fmul    D [f65536]
    fld     D [fLeftStep]
    fmul    D [f65536]
    fld     D [fRightStep]
    fmul    D [f4G]
    fistp   Q [mmRightStep] // fixint 32:32
    fistp   Q [mmLeftStep]  // fixint 32:16
    fistp   Q [fixRightOfs] // fixint 32:16
    fistp   Q [fixLeftOfs]  // fixint 32:16

    // get last played sample (for filtering purposes)
    movzx   eax,W [slLastRightSample]
    movzx   edx,W [slLastLeftSample]
    shl     eax,16
    or      eax,edx
    movd    mm6,eax                       // MM6 = 0 | 0 || lastRightSample | lastLeftSample

    // get volume
    movd    mm5,D [slRightVolume]
    movd    mm0,D [slLeftVolume]
    psllq   mm5,32
    por     mm5,mm0                       // MM5 = rightVolume || leftVolume

    // get filter
    mov     eax,D [slRightFilter]
    mov     edx,D [slLeftFilter]
    shl     eax,16
    or      eax,edx
    movd    mm7,eax                       // MM7 = 0 | 0 || rightFilter | leftFilter

    // get offset of each channel inside sound and loop thru destination buffer
    mov     W [mmRightStep],0
    movzx   eax,W [fixLeftOfs]
    movzx   edx,W [fixRightOfs]
    shl     edx,16
    or      eax,edx                       // EAX = right ofs frac | left ofs frac
    mov     ebx,D [fixLeftOfs+2]          // EBX = left ofs int
    mov     edx,D [fixRightOfs+2]         // EDX = right ofs int
    mov     esi,D [pswSrcBuffer]          // ESI = source sound buffer start ptr
    mov     edi,D [pvMixerBuffer]         // EDI = mixer buffer ptr
    mov     ecx,D [slMixerBufferSize]     // ECX = samples counter

sampleLoop:
    // check if source offsets came to the end of source sound buffer
    cmp     ebx,D [slSoundBufferSize]
    jl      lNotEnd
    sub     ebx,D [slSoundBufferSize]
    push    D [bNotLoop]
    pop     D [bEndOfSound]
lNotEnd:
    // same for right channel
    cmp     edx,D [slSoundBufferSize]
    jl      rNotEnd
    sub     edx,D [slSoundBufferSize]
    push    D [bNotLoop]
    pop     D [bEndOfSound]
rNotEnd:

    // check end of sample
    cmp     ecx,0
    jle     loopEnd
    cmp     D [bEndOfSound],TRUE
    je      loopEnd

    // get sound samples
    movq    mm1,Q [esi+ ebx*4]
    movq    mm2,Q [esi+ edx*4]
    pslld   mm1,16
    psrad   mm1,16              // MM1 = 0 | nextLeftSample  || 0 | leftSample
    psrad   mm2,16              // MM2 = 0 | nextRightSample || 0 | rightSample
    packssdw mm1,mm2  // MM1 = nextRightSample | rightSample || nextLeftSample | leftSample

    // calc linear interpolation factor (strength)
    movd    mm3,eax   // MM3 = 0 | 0 || right frac | left frac
    punpcklwd mm3,mm3
    psrlw   mm3,1     // MM3 = rightFrac | rightFrac || leftFrac | leftFrac
    pxor    mm3,Q [mmInvFactor] // MM3 = rightFrac | 1-rightFrac || leftFrac | 1-leftFrac
    // apply linear interpolation
    pmaddwd mm1,mm3
    psrad   mm1,15
    packssdw mm1,mm1  // MM1 = ? | ? || linearRightSample | linearLeftSample

    // apply filter
    psubsw  mm1,mm6
    pmulhw  mm1,mm7
    psllw   mm1,1
    paddsw  mm1,mm6
    movq    mm6,mm1

    // apply volume adjustment
    movq    mm0,mm5
    psrad   mm0,16
    packssdw mm0,mm0
    pmulhw  mm1,mm0
    psllw   mm1,1
    pxor    mm1,Q [mmSurroundFactor]
    paddd   mm5,Q [mmVolumeGain]   // modify volume

    // unpack to 32bit and mix it into destination buffer
    punpcklwd mm1,mm1
    psrad   mm1,16              // MM1 = finalRightSample || finalLeftSample
    paddd   mm1,Q [edi]
    movq    Q [edi],mm1

    // advance to next samples in source sound
    add     eax,D [mmRightStep+0]
    adc     edx,D [mmRightStep+4]
    add      ax,W [mmLeftStep +0]
    adc     ebx,D [mmLeftStep +2]
    add     edi,8
    dec     ecx
    jmp     sampleLoop

loopEnd:
    // store modified asm local vars
    mov     D [fixLeftOfs +0],eax
    shr     eax,16
    mov     D [fixRightOfs+0],eax
    mov     D [fixLeftOfs +2],ebx
    mov     D [fixRightOfs+2],edx
    movd    eax,mm6
    mov     edx,eax
    and     eax,0x0000FFFF
    shr     edx,16
    mov     D [slLastLeftSample],eax
    mov     D [slLastRightSample],edx
    emms
  }

 #elif (defined __GNU_INLINE__)
   // This is implemented in an external NASM file.
   MixStereo_asm(pso);

 #else
   #error please write inline asm for your platform.
 #endif

  _pfSoundProfile.StopTimer(CSoundProfile::PTI_RAWMIXER);
}


// mixes one sound to destination buffer
void MixSound( CSoundObject *pso)
{
  psd = pso->so_pCsdLink;

  // if don't mix encoded sounds if they are not opened properly
  if((psd->sd_ulFlags&SDF_ENCODED) && 
    (pso->so_psdcDecoder==NULL || !pso->so_psdcDecoder->IsOpen()) ) {
    return;
  }

  // check for supported sound formats
  const SLONG slChannels = pso->so_pCsdLink->sd_wfeFormat.nChannels;
  const SLONG slBytes    = pso->so_pCsdLink->sd_wfeFormat.wBitsPerSample/8;
  // unsupported sound formats will be ignored
  if( (slChannels!=1 && slChannels!=2) || slBytes!=2) return;

  // check for delay
  const FLOAT f1oMixerBufferSampleRate = 1.0f / slMixerBufferSampleRate;
  const FLOAT fSecondsToMix = (FLOAT)slMixerBufferSize * f1oMixerBufferSampleRate;
  pso->so_fDelayed += fSecondsToMix;
  if( pso->so_fDelayed < pso->so_sp.sp_fDelay) {
    _pfSoundProfile.IncrementCounter(CSoundProfile::PCI_SOUNDSDELAYED, 1);
    return;
  }
  // playing started, so skip further delays
  pso->so_fDelayed = 9999.9999f;

  // reach sound data and determine sound step, sound buffer and buffer size
  pswSrcBuffer = psd->sd_pswBuffer;
  fSoundSampleRate = psd->sd_wfeFormat.nSamplesPerSec * pso->so_sp.sp_fPitchShift;
  fStep      = fSoundSampleRate * f1oMixerBufferSampleRate;
  fLeftStep  = fStep;
  fRightStep = fStep;
  slSoundBufferSize  = psd->sd_slBufferSampleSize;
  // eliminate potentional "puck" at the of sample that hasn't loop
  if( !(pso->so_slFlags&SOF_LOOP) && slSoundBufferSize>1) slSoundBufferSize--;

  // get old and new volumes
  FLOAT fLeftVolume     = ClampDn( pso->so_fLastLeftVolume,  0.0f);
  FLOAT fRightVolume    = ClampDn( pso->so_fLastRightVolume, 0.0f);
  FLOAT fNewLeftVolume  = ClampDn( pso->so_sp.sp_fLeftVolume,  0.0f);
  FLOAT fNewRightVolume = ClampDn( pso->so_sp.sp_fRightVolume, 0.0f);

  // adjust for master volume
  if(pso->so_slFlags&SOF_MUSIC) {
    fNewLeftVolume  *= snd_fMusicVolume;
    fNewRightVolume *= snd_fMusicVolume;
  } else {
    fNewLeftVolume  *= snd_fSoundVolume;
    fNewRightVolume *= snd_fSoundVolume;
  }

  // if both channel volumes are too low
  if( fLeftVolume<0.001f && fRightVolume<0.001f && fNewLeftVolume<0.001f && fNewRightVolume<0.001f)
  {
    // if this is not an encoded sound
    if( !(psd->sd_ulFlags&SDF_ENCODED) ) {
      // skip mixing of this sample segment
      fOfsDelta = fStep*slMixerBufferSampleRate*fSecondsToMix;
      pso->so_fLeftOffset  += fOfsDelta;
      pso->so_fRightOffset += fOfsDelta;
      const FLOAT fMinOfs = Min( pso->so_fLeftOffset, pso->so_fRightOffset);
      ASSERT( fMinOfs>=0);
      if( fMinOfs<0) CPrintF( "BUG: negative offset (%.2g) encountered in sound: '%s' !\n", fMinOfs, (const char *) (CTString&)psd->GetName());
      // if looping
      if (pso->so_slFlags & SOF_LOOP) {
        // adjust offset ptrs inside sound
        while( pso->so_fLeftOffset  < 0) pso->so_fLeftOffset  += slSoundBufferSize;
        while( pso->so_fRightOffset < 0) pso->so_fRightOffset += slSoundBufferSize;
        while( pso->so_fLeftOffset  >= slSoundBufferSize) pso->so_fLeftOffset  -= slSoundBufferSize;
        while( pso->so_fRightOffset >= slSoundBufferSize) pso->so_fRightOffset -= slSoundBufferSize;
      // if not looping
      } else {
        // no more playing
        pso->so_slFlags  &= ~SOF_PLAY;
        pso->so_fDelayed     = 0.0f;
        pso->so_sp.sp_fDelay = 0.0f;
      }
    }
    // reset last samples
    pso->so_swLastLeftSample  = 0;
    pso->so_swLastRightSample = 0;
    // update volume
    pso->so_fLastLeftVolume  = fNewLeftVolume;
    pso->so_fLastRightVolume = fNewRightVolume;

    _pfSoundProfile.IncrementCounter(CSoundProfile::PCI_SOUNDSSKIPPED, 1);
    return;
  }
  _sfStats.IncrementCounter(CStatForm::SCI_SOUNDSMIXING);

  // cache sound object vars
  fPhase    = pso->so_sp.sp_fPhaseShift;
  fLeftOfs  = pso->so_fLeftOffset;
  fRightOfs = pso->so_fRightOffset;
  fOfsDelta = pso->so_fOffsetDelta;
  slLeftVolume  = FloatToInt(fLeftVolume  * 65536*32767.0f);
  slRightVolume = FloatToInt(fRightVolume * 65536*32767.0f);
  const FLOAT fMixBufSize = 65536*32767.0f / slMixerBufferSize;
  const SLONG slLeftGain  = FloatToInt( (fNewLeftVolume -fLeftVolume)  *fMixBufSize);
  const SLONG slRightGain = FloatToInt( (fNewRightVolume-fRightVolume) *fMixBufSize);
  mmVolumeGain  = ((__int64)(slRightGain)<<32) | ((__int64)(slLeftGain)&0xFFFFFFFF);
  // extrapolate back new volumes because of not enough precision in interpolation!
  // (otherwise we might hear occasional pucks)
  if( fNewLeftVolume >0.001f) fNewLeftVolume  = (slLeftVolume  + slLeftGain *slMixerBufferSize) /(65536*32767.0f);
  if( fNewRightVolume>0.001f) fNewRightVolume = (slRightVolume + slRightGain*slMixerBufferSize) /(65536*32767.0f);
  //ASSERT( fNewLeftVolume>=0 && fNewRightVolume>=0);
  //CPrintF( "NV: %.4f / %.4f, GV: %.4f / %.4f\n", fNewLeftVolume,fNewRightVolume, fLeftGainedVolume,fRightGainedVolume); 

  // determine filtering and surround 
  slLeftFilter  = pso->so_sp.sp_slLeftFilter;
  slRightFilter = pso->so_sp.sp_slRightFilter;
  bNotLoop = !(pso->so_slFlags & SOF_LOOP);
  mmSurroundFactor = 0;
  if( pso->so_slFlags & SOF_SURROUND) mmSurroundFactor = 0x0000FFFF;

  // if this is an encoded sound
  BOOL bDecodingFinished = FALSE;
  if( psd->sd_ulFlags&SDF_ENCODED) {
    _pfSoundProfile.StartTimer(CSoundProfile::PTI_DECODESOUND);
    // decode some samples from it
    SLONG slWantedBytes  = FloatToInt(slMixerBufferSize*fStep*pso->so_pCsdLink->sd_wfeFormat.nChannels) *2;
    void *pvDecodeBuffer = _pSound->sl_pswDecodeBuffer;
    ASSERT(slWantedBytes<=_pSound->sl_slDecodeBufferSize);
    SLONG slDecodedBytes = pso->so_psdcDecoder->Decode( pvDecodeBuffer, slWantedBytes);
    ASSERT(slDecodedBytes<=slWantedBytes);
    // if it has a loop
    if (!bNotLoop) {
      // if sound is shorter than buffer
      while(slDecodedBytes<slWantedBytes) {
        // decode it again and again
        pso->so_psdcDecoder->Reset();
        slDecodedBytes += pso->so_psdcDecoder->Decode( ((UBYTE*)pvDecodeBuffer) +
                                                       slDecodedBytes, slWantedBytes-slDecodedBytes);
      }
    // if it doesn't have a loop
    } else {
      // if sound is shorter than buffer
      if(slDecodedBytes<slWantedBytes) {
        // mark that it is finished
        bDecodingFinished = TRUE;
      }
    }
    // copy first sample to the last one (this is needed for linear interpolation)
    (ULONG&)(((UBYTE*)pvDecodeBuffer)[slDecodedBytes]) = *(ULONG*)pvDecodeBuffer;
    // fix some mixer variables to play temporary decode buffer instead of real sound
    pswSrcBuffer = (SWORD*)pvDecodeBuffer;
    slSoundBufferSize = slDecodedBytes>>2;  // convert to samples
    fLeftOfs  = 0.0f;
    fRightOfs = 0.0f;
    fPhase = 0.0f;

    _pfSoundProfile.StopTimer(CSoundProfile::PTI_DECODESOUND);
  }

  _pfSoundProfile.IncrementCounter(CSoundProfile::PCI_SOUNDSMIXED, 1);
  _pfSoundProfile.IncrementCounter(CSoundProfile::PCI_SAMPLES, slMixerBufferSize);

  _pfSoundProfile.StartTimer(CSoundProfile::PTI_MIXSOUND);

  slLastLeftSample  = pso->so_swLastLeftSample;
  slLastRightSample = pso->so_swLastRightSample;

  // calculate eventual new offsets from phase shift
  FLOAT fLastPhase  = fOfsDelta / fSoundSampleRate;
  FLOAT fPhaseDelta = fPhase - fLastPhase;
  FLOAT fStepDelta  = Abs( fPhaseDelta*fSoundSampleRate / slMixerBufferSize);

  FLOAT fStepDeltaL, fStepDeltaR;
  if( fPhaseDelta>0) {
    fStepDeltaL = fStepDelta/2;
    if( fStepDeltaL>fLeftStep/2) fStepDeltaL = fLeftStep/2;
    fStepDeltaL = -fStepDeltaL;
    fStepDeltaR =  fStepDelta + fStepDeltaL;
  } else {
    fStepDeltaR = fStepDelta/2;
    if( fStepDeltaR>fLeftStep/2) fStepDeltaR = fLeftStep/2;
    fStepDeltaR = -fStepDeltaR;
    fStepDeltaL =  fStepDelta + fStepDeltaR;
  }
  fLeftStep  += fStepDeltaL;
  fRightStep += fStepDeltaR;
  fStepDelta  = fStepDeltaR-fStepDeltaL;

  // if there is anything to mix (could be nothing when encoded file just finished)
  if( slSoundBufferSize>0) {
    // safety check (needed because of bad-bug!)
    FLOAT fMinOfs = Min( fLeftOfs, fRightOfs);
    ASSERT( fMinOfs>=0);
    if( fMinOfs<0) CPrintF( "BUG: negative offset (%.2g) encountered in sound: '%s' !\n", fMinOfs, (const char *) (CTString&)psd->GetName());
    // adjust offset ptrs inside sound to match those of phase shift
    while( fLeftOfs  < 0) fLeftOfs  += slSoundBufferSize;
    while( fRightOfs < 0) fRightOfs += slSoundBufferSize;
    while( fLeftOfs  >= slSoundBufferSize) fLeftOfs  -= slSoundBufferSize;
    while( fRightOfs >= slSoundBufferSize) fRightOfs -= slSoundBufferSize;

    // if mono output is required
    if( snd_bMono) {
      // monomize channels (cool word:)
      fLeftOfs  = (fLeftOfs+fRightOfs)/2;
      fRightOfs = fLeftOfs;
      fLeftStep  = (fLeftStep+fRightStep)/2;
      fRightStep = fLeftStep;
      slLeftVolume  = (slLeftVolume+slRightVolume)/2;
      slRightVolume = slLeftVolume;
      slLeftFilter  = (slLeftFilter+slRightFilter)/2;
      slRightFilter = slLeftFilter;
    }

    // call corresponding mixer routine for current sound format
    bEndOfSound = FALSE;
    if( slChannels==2) {
      // mix as 16-bit stereo
      MixStereo( pso);
    } else {
      // mix as 16-bit mono
      MixMono( pso);
    }
  }

  // if encoded sound
  if( psd->sd_ulFlags&SDF_ENCODED) {
    // ignore mixing finished flag, but use decoding finished flag
    bEndOfSound = bDecodingFinished;
  }

  // if sound ended, not buffer
  if( bEndOfSound) {
    // reset some sound vars
    slLastLeftSample  = 0;
    slLastRightSample = 0;
    pso->so_slFlags  &= ~SOF_PLAY;
    pso->so_fDelayed     = 0.0f;
    pso->so_sp.sp_fDelay = 0.0f;
  }

  // rememer last samples for the next mix in
  pso->so_swLastLeftSample  = (SWORD)slLastLeftSample;
  pso->so_swLastRightSample = (SWORD)slLastRightSample;
  // determine new phase shift offset
  pso->so_fOffsetDelta += fStepDelta*slMixerBufferSize;
  // update play offset for the next mix iteration
  pso->so_fLeftOffset  = fixLeftOfs  * (1.0f/65536.0f);
  pso->so_fRightOffset = fixRightOfs * (1.0f/65536.0f);
  // update volume
  pso->so_fLastLeftVolume  = fNewLeftVolume;
  pso->so_fLastRightVolume = fNewRightVolume;

  //if( pso->so_fLastLeftVolume>0 || pso->so_fLastRightVolume>0 || fNewLeftVolume>0 || fNewRightVolume>0) {
  //  CPrintF( "SO: 0x%8X; OV: %.4f / %.4f, NV: %.4f / %.4f\n", pso,
  //            pso->so_fLastLeftVolume,pso->so_fLastRightVolume, fNewLeftVolume,fNewRightVolume);
  //}
  _pfSoundProfile.StopTimer(CSoundProfile::PTI_MIXSOUND);
}

