//**************************************************************************
//*                     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: auto volume correction (low sensitivy)

#include "au_mixer.h"
#include "mix_func.h"
#include "newfunc\newfunc.h"
#include "display\display.h"

extern unsigned int crossfadepart;
extern unsigned int refdisp;
extern unsigned int MIXER_controlbits;
extern int MIXER_var_volume;
extern struct mainvars mvps;

int MIXER_var_autovolume;

#define AVOL_UP_WAIT       8 // in frames

#define CAVOL_MIN_VOLUME 100
#define CAVOL_MAX_VOLUME 400
#define CAVOL_DOWN_SPEED  10
#define CAVOL_CLIPS_LIMIT  5
#define CAVOL_CCHK_COMPRESS 5  // >>5 == /32
#define CAVOL_CCHK_SIZE ((MIXER_SCALE_MAX+1)>>CAVOL_CCHK_COMPRESS)

static unsigned long clipcounts[CAVOL_CCHK_SIZE+32];

static void mixer_autovolume_getclipcounts(PCM_CV_TYPE_S *pcm_sample,unsigned int samplenum,unsigned int type_float)
{
 unsigned long *cc;

 pds_memset(clipcounts,0,sizeof(clipcounts));

 cc=&clipcounts[0];

 if(!type_float){ // pcm16
  do{
   register short sign=PDS_GETB_LE16(pcm_sample++);
   if(sign<0)
    sign=-sign;
   cc[sign>>CAVOL_CCHK_COMPRESS]++;
  }while(--samplenum);
 }else{           // float32
  PCM_CV_TYPE_F *pcmf=(PCM_CV_TYPE_F *)pcm_sample;
  do{
   long tmp,sign;
   pds_ftoi(*pcmf++,&tmp);
   sign=tmp;
   if(sign<0)
    sign=-sign;
   if(sign>MIXER_SCALE_MAX)
    sign=MIXER_SCALE_MAX;
   cc[sign>>CAVOL_CCHK_COMPRESS]++;
  }while(--samplenum);
 }
}

static void mixer_autovolume_volumecalc(void)
{
 static unsigned long counter1,pluswait,signlimit;
 unsigned int currlimit,new_volume;
 unsigned int clips=0, k=CAVOL_CCHK_SIZE;
 unsigned long *cc=&clipcounts[CAVOL_CCHK_SIZE];
 //char sout[100];

 do{
  clips+=*cc;
  if(clips>CAVOL_CLIPS_LIMIT)
   break;
  cc--;
 }while(--k);

 currlimit=100*CAVOL_CCHK_SIZE;
 if(k)
  currlimit/=k;

 //if(MIXER_controlbits&MIXER_CONTROLBIT_LIMITER)
 // currlimit+=currlimit>>3;

 //sprintf(sout,"vv:%d cl:%d sl:%d k:%d c1:%d c:%d %d",MIXER_var_volume,currlimit,signlimit,k,counter1,clipcounts[1023],clipcounts[1022]);
 //display_message(0,0,sout);

 new_volume=MIXER_var_volume;
 if((currlimit>MIXER_var_volume) && (MIXER_var_volume<CAVOL_MAX_VOLUME)
     && (!crossfadepart || ((crossfadepart==CROSS_IN) && !(mvps.cfi->crossfadetype&CFT_FADEIN)))){
  signlimit=((7*signlimit)+currlimit)>>3;
  if(!counter1){
   new_volume++;
   counter1=signlimit-new_volume;
   if(counter1<100)
    new_volume--;
   if((counter1<40) && (new_volume>CAVOL_MIN_VOLUME))
    new_volume--;
   counter1>>=5;
   if(counter1>25)
    counter1=25;
   counter1=25-counter1;
   if(counter1>pluswait)
    pluswait=counter1;
   else if(pluswait)
    pluswait--;
   counter1=AVOL_UP_WAIT+pluswait;
  }else
   counter1--;
 }else{
  if(currlimit<MIXER_var_volume){
   if(currlimit>=CAVOL_MIN_VOLUME){
    pluswait=30;
    new_volume--;
   }else
    new_volume=CAVOL_MIN_VOLUME;
  }
 }
 if(new_volume!=MIXER_var_volume){
  MIXER_setfunction("MIX_VOLUME",MIXER_SETMODE_ABSOLUTE,new_volume);
  refdisp|=RDT_VOL;
 }
}

static void mixer_autovolume_lq(struct mpxplay_audioout_info_s *aui)
{
 if(!aui->pcm_sample || !aui->samplenum)
  return;
 mixer_autovolume_getclipcounts((PCM_CV_TYPE_S *)aui->pcm_sample,aui->samplenum,0);
 mixer_autovolume_volumecalc();
}

static void mixer_autovolume_hq(struct mpxplay_audioout_info_s *aui)
{
 if(!aui->pcm_sample || !aui->samplenum)
  return;
 mixer_autovolume_getclipcounts((PCM_CV_TYPE_S *)aui->pcm_sample,aui->samplenum,1);
 mixer_autovolume_volumecalc();
}

struct one_mixerfunc_info MIXER_FUNCINFO_autovolume={
 "MIX_AUTOVOLUME",
 "sva",
 &MIXER_var_autovolume,
 MIXER_INFOBIT_SWITCH,
 0,1,0,0,
 NULL,
 &mixer_autovolume_lq,
 &mixer_autovolume_hq,
 NULL,
 NULL
};

#if 0
//-----------------------------------------------------------------------
// limiter based auto volume control

#define LAVOL_LIMITER_AREA 20 // don't reduce the volume in this area (limiter will handle the overflow)
#define LAVOL_VOLUME_ADD    5 // volume is always higher a little bit than detected
#define LAVOL_MIN_VOLUME (100+LAVOL_VOLUME_ADD)
#define LAVOL_MAX_VOLUME (300-LAVOL_VOLUME_ADD)

static void mixer_autovolume_set_by_limiter(void)
{
 static unsigned long counter1,pluswait,avol_min_volume=LAVOL_MIN_VOLUME;
 //static long last_volume;
 long rvol=mpxplay_aumixer_mxvolum_getrealvolume_fast();
 long vval=MIXER_var_volume,change=0;

 //if(last_volume!=MIXER_var_volume){ // an idea to control the avol area manually (by user)
 // avol_min_volume+=(MIXER_var_volume-last_volume);
 // if(avol_min_volume<100)
 //  avol_min_volume=100;
 //}

 if(vval>=avol_min_volume){
  vval-=avol_min_volume;
  vval+=100;
 }else{
  vval=100;
  change=1;
 }

 if(rvol>vval){
  if((vval<LAVOL_MAX_VOLUME) && (!crossfadepart || ((crossfadepart==CROSS_IN) && !(mvps.cfi->crossfadetype&CFT_FADEIN))) ){
   if(!counter1){
    if(pluswait)
     pluswait--;
    counter1=AVOL_UP_WAIT+pluswait;
    vval++;
    change=1;
   }else
    counter1--;
  }
 }else{
  long diff=vval-rvol;
  if(diff>=LAVOL_LIMITER_AREA){
   vval--;
   pluswait=30;
   counter1=pluswait+AVOL_UP_WAIT;
   change=1;
  }
 }

 if(change){
  if(vval>=100){
   vval-=100;
   vval+=avol_min_volume;
  }else
   vval=avol_min_volume;
  //last_volume=vval;

  if(vval!=MIXER_var_volume){
   MIXER_setfunction("MIX_VOLUME",MIXER_SETMODE_ABSOLUTE,vval);
   refdisp|=RDT_VOL;
  }
 }
}

/*void mixer_autovolume_set_by_limiter(void)
{
 static unsigned int counter1, avol_min_volume=LAVOL_MIN_VOLUME;
 static long last_volume;
 long rvol=mpxplay_aumixer_mxvolum_getrealvolume_fast();
 long vval=MIXER_var_volume,change=0,diff;

 //if(last_volume!=MIXER_var_volume){ // an idea to control the avol area manually (by user)
 // avol_min_volume+=(MIXER_var_volume-last_volume);
 // if(avol_min_volume<100)
 //  avol_min_volume=100;
 //}

 if(vval>=avol_min_volume){
  vval-=avol_min_volume;
  //vval>>=1;
  vval+=100;
 }else{
  vval=100;
  change=1;
 }

 if(rvol>vval){
  if((++counter1>5) && (vval<AVOL_MAX_VOLUME)){
   vval++;
   counter1=0;
   change=1;
  }
 }else{
  diff=vval-rvol;
  if(diff>=AVOL_LIMITER_AREA){
   vval--;
   counter1=0;
   change=1;
  }
 }

 if(change){
  if(vval>=100){
   vval-=100;
   //vval<<=1;
   vval+=avol_min_volume;
  }else
   vval=avol_min_volume;
  last_volume=vval;

  if(vval!=MIXER_var_volume){
   MIXER_setfunction("MIX_VOLUME",MIXER_SETMODE_ABSOLUTE,vval);
   refdisp|=RDT_VOL;
  }
 }
}*/
#endif // 0
