//**************************************************************************
//*                     This file is part of the                           *
//*                      Mpxplay - audio player.                           *
//*                  The source code of Mpxplay is                         *
//*        (C) copyright 1998-2012 by PDSoft (Attila Padar)                *
//*                http://mpxplay.sourceforge.net                          *
//*                  email: mpxplay@freemail.hu                            *
//**************************************************************************
//*  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.                  *
//*  Please contact with the author (with me) if you want to use           *
//*  or modify this source.                                                *
//**************************************************************************
//function: proligic surround effect (for 2 channels only!)
// based on libModPlug v0.8 (http://www.xmms-libmod.com)

#include <mem.h>
#include <float.h>
#include "mpxplay.h"

#define PDS_FINITE(x) _finite(x)

static int MIXER_var_surround;

#define MIXER_SURROUND_VERSION1  1

#define nDolbyHiFltMask		1

#define XBASSBUFFERSIZE		64		// 2 ms at 50KHz
#define FILTERBUFFERSIZE	64		// 1.25 ms
#define SURROUNDBUFFERSIZE	((PCM_MAX_FREQ * 100) / 1000)

typedef float surr_float_t; // sizeof(surr_float_t) must be match with sizeof(mpxp_int32_t)

static long nSurroundSize;
static long nSurroundPos;
static long nDolbyDepth;
static long nDolbyLoDlyPos;
static long nDolbyLoFltPos;
static surr_float_t nDolbyLoFltSum;
static long nDolbyHiFltPos;
static surr_float_t nDolbyHiFltSum;
static surr_float_t *DolbyLoFilterBuffer;
static surr_float_t *DolbyLoFilterDelay;
static surr_float_t *DolbyHiFilterBuffer;
static surr_float_t *SurroundBuffer;

static unsigned int m_nProLogicDelay;
static unsigned int plsur_initialized;

static void mixer_plsurr_init(void)
{
 DolbyLoFilterBuffer=(surr_float_t *)malloc(XBASSBUFFERSIZE*sizeof(surr_float_t));
 if(!DolbyLoFilterBuffer) return;
 DolbyLoFilterDelay =(surr_float_t *)malloc(XBASSBUFFERSIZE*sizeof(surr_float_t));
 if(!DolbyLoFilterDelay)  return;
 DolbyHiFilterBuffer=(surr_float_t *)malloc(FILTERBUFFERSIZE*sizeof(surr_float_t));
 if(!DolbyHiFilterBuffer) return;
 SurroundBuffer     =(surr_float_t *)malloc(SURROUNDBUFFERSIZE*sizeof(surr_float_t));
 if(!SurroundBuffer)      return;
 plsur_initialized=1;
}

static void mixer_plsurr_close(void)
{
 if(DolbyLoFilterBuffer) free(DolbyLoFilterBuffer);
 if(DolbyLoFilterDelay)  free(DolbyLoFilterDelay);
 if(DolbyHiFilterBuffer) free(DolbyHiFilterBuffer);
 if(SurroundBuffer)      free(SurroundBuffer);
 plsur_initialized=0;
}

static void mixer_surr_reset(struct mpxp_aumixer_passinfo_s *mpi)
{
 if(!plsur_initialized)
  return;
 nSurroundPos = 0;
 nDolbyLoFltPos = 0;
 nDolbyLoFltSum = 0;
 nDolbyLoDlyPos = nDolbyHiFltPos = 0;
 nDolbyHiFltSum = 0;
 memset(DolbyLoFilterBuffer, 0, XBASSBUFFERSIZE*sizeof(surr_float_t));
 memset(DolbyLoFilterDelay,  0, XBASSBUFFERSIZE*sizeof(surr_float_t));
 memset(DolbyHiFilterBuffer, 0, FILTERBUFFERSIZE*sizeof(surr_float_t));
 memset(SurroundBuffer,      0, SURROUNDBUFFERSIZE*sizeof(surr_float_t));
 nSurroundSize = (mpi->freq_song * m_nProLogicDelay) / 1000;
 if(nSurroundSize > SURROUNDBUFFERSIZE)
  nSurroundSize = SURROUNDBUFFERSIZE;
}

static int mixer_surround_init(struct mpxp_aumixer_passinfo_s *mpi,int inittype)
{
 switch(inittype){
  case MIXER_INITTYPE_INIT:
        mixer_plsurr_init();
        break;
  case MIXER_INITTYPE_REINIT:
        break;
  case MIXER_INITTYPE_START:
  case MIXER_INITTYPE_RESET:
        mixer_surr_reset(mpi);
        break;
  case MIXER_INITTYPE_CLOSE:
        mixer_plsurr_close();
        break;
 }
 return 1;
}

static void mixer_surround_process(struct mpxp_aumixer_passinfo_s *mpi)
{
 PCM_CV_TYPE_F *pcm=(PCM_CV_TYPE_F *)mpi->pcm_sample;
 unsigned int i=mpi->samplenum,step=mpi->chan_card;

 i/=step;
 if(MIXER_var_surround<=100){
  const float cfsur=(float)MIXER_var_surround/200;
  const float half=0.5;
  do{
   const PCM_CV_TYPE_F cadd=(pcm[0]+pcm[1])*half;
   const PCM_CV_TYPE_F csub=(pcm[0]-pcm[1])*cfsur;
   pcm[0]=(cadd+csub);
   pcm[1]=(cadd-csub);
   pcm+=step;
  }while(--i);
 }else{
  int n = nDolbyLoFltPos;
#ifdef MIXER_SURROUND_VERSION1
  const float cfsur=(float)MIXER_var_surround/400.0;
#else
  const float cfsur=(float)MIXER_var_surround/200.0;
#endif
  do{
   PCM_CV_TYPE_F v=pcm[0]+pcm[1];
   PCM_CV_TYPE_F secho,tmp;

   v*=cfsur;

   // Low-Pass Filter
   nDolbyHiFltSum -= DolbyHiFilterBuffer[nDolbyHiFltPos];
   DolbyHiFilterBuffer[nDolbyHiFltPos] = v;
   nDolbyHiFltSum += v;
   v -= nDolbyHiFltSum/(nDolbyHiFltMask+1);
   //v = nDolbyHiFltSum;
   if((++nDolbyHiFltPos)>nDolbyHiFltMask)
    nDolbyHiFltPos=0;
   // Surround
   secho = SurroundBuffer[nSurroundPos];
   SurroundBuffer[nSurroundPos] = v;
   if (++nSurroundPos >= nSurroundSize)
    nSurroundPos = 0;
#ifdef MIXER_SURROUND_VERSION1
   // Delay line and remove low frequencies
   v = DolbyLoFilterDelay[nDolbyLoDlyPos];	// v = delayed signal
   DolbyLoFilterDelay[nDolbyLoDlyPos] = secho;	// secho = signal
   nDolbyLoDlyPos++;
   nDolbyLoDlyPos &= 0x1F;
   nDolbyLoFltSum -= DolbyLoFilterBuffer[n];
   tmp = secho / 64;
   DolbyLoFilterBuffer[n] = tmp;
   nDolbyLoFltSum += tmp;
   v -= nDolbyLoFltSum;
   n++;
   n &= 0x3F;
   // Add echo
   pcm[0] += v;
   pcm[1] -= v;
#else
   pcm[0] += secho;
   pcm[1] -= secho;
#endif
   pcm += step;
  }while(--i);
  nDolbyLoFltPos = n;
 }

 // to avoid sound "freezing" at bad pcm data
 if(!PDS_FINITE(nDolbyLoFltSum) || !PDS_FINITE(nDolbyHiFltSum)){
  mixer_surr_reset(mpi);
  pds_memset(mpi->pcm_sample, 0, mpi->samplenum*sizeof(PCM_CV_TYPE_F));
 }
}

static int mixer_surround_checkvar(struct mpxp_aumixer_passinfo_s *mpi)
{
 if((MIXER_var_surround!=100) && (mpi->chan_card>=2)){
  if(MIXER_var_surround>100){
   unsigned int gain = ((MIXER_var_surround-100) * 10) / 100, nDelay=0;
   if (gain < 1) gain = 1;
   nDolbyDepth=gain;
   nDelay=((MIXER_var_surround-100) * 10) / 100 + 3;
   if (nDelay < 4) nDelay = 4;
   if (nDelay > 23) nDelay = 23;
   m_nProLogicDelay = nDelay;
   nSurroundSize = (mpi->freq_song * m_nProLogicDelay) / 1000;
   if(nSurroundSize > SURROUNDBUFFERSIZE)
    nSurroundSize = SURROUNDBUFFERSIZE;
  }
  return 1;
 }
 return 0;
}

one_mixerfunc_info MIXER_FUNCINFO_surround={
 "MIX_SURROUND",
 "mxsr",
 &MIXER_var_surround,
 MIXER_INFOBIT_EXTERNAL_DEPENDENCY, // mpi->chan_card
 10,500,100,10,
 &mixer_surround_init,
 NULL,
 &mixer_surround_process,
 &mixer_surround_checkvar,
 NULL
};
