//**************************************************************************
//*                     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: set volume/limiter

#include "mpxplay.h"
#include "mix_func.h"

#define MIXER_LIMITER_CORRECT_BOUNDARY 1

#define VOLUME_MUTE_VOLDIV_DEFAULT  8    // 1/8 volume reduction at mute
#define VOLUME_SCALE_DEFAULT 8192 // don't set it higher (13 + 3 + 16 = 32 bits)

static unsigned int mixer_volume_calcvol(struct mpxp_aumixer_passinfo_s *mpi);
static void mixer_volumes_calc(struct mpxp_aumixer_passinfo_s *mpi);

extern unsigned int MIXER_controlbits,MIXER_channels,crossfadepart;

int MIXER_var_limiter_overflow;
int MIXER_var_volume;
int MIXER_var_mutelen,MIXER_var_mute_voldiv=VOLUME_MUTE_VOLDIV_DEFAULT;
int MIXER_var_balance;

one_mixerfunc_info MIXER_FUNCINFO_volume;

static long mxvolum_mute_voldiv=1,mxvolum_mute_keycounter;

//------------------------------------------------------------------------
// mute function

#define MUTE_SWITCH_SIGN 65535 // on/off switch instead of 'push'

//this is just a timer, not a transformation (volume does it)
static void mixer_volume_mute_process(struct mpxp_aumixer_passinfo_s *mpi)
{
 if(MIXER_var_mutelen!=MUTE_SWITCH_SIGN){
  if(MIXER_var_mutelen)
   funcbit_smp_value_decrement(MIXER_var_mutelen);
  if(!MIXER_var_mutelen){
   MIXER_checkfunc_setflags(mpi,"MIX_MUTE");
   funcbit_smp_value_put(mxvolum_mute_voldiv,1);
   funcbit_smp_value_put(mxvolum_mute_keycounter,0);
  }
 }
}

static void mixer_volume_mute_setvar(struct mpxp_aumixer_passinfo_s *mpi,unsigned int setmode,int value)
{
 int muteval=MIXER_var_mutelen;
 switch(setmode){
  case MIXER_SETMODE_ABSOLUTE:
        muteval=value;
        break;
  case MIXER_SETMODE_RELATIVE:
        if(value==MUTE_SWITCH_SIGN){ // on/off switch
         if(muteval==MUTE_SWITCH_SIGN)
          muteval=0;
         else
          muteval=MUTE_SWITCH_SIGN;
        }else{                       // mute while push
#ifdef MPXPLAY_WIN32
         if(mxvolum_mute_keycounter<15)
          muteval=21-mxvolum_mute_keycounter;
         else
          muteval=6;
         mxvolum_mute_keycounter++;
#else
         if(muteval)
          muteval=5;
         else
          muteval=15;
#endif
        }
        break;
  case MIXER_SETMODE_RESET:
        mxvolum_mute_keycounter=0;
        muteval=0;
        break;
 }
 if(MIXER_var_mute_voldiv<1)
  MIXER_var_mute_voldiv=1;
 if(muteval)
  funcbit_smp_value_put(mxvolum_mute_voldiv,MIXER_var_mute_voldiv);
 else
  funcbit_smp_value_put(mxvolum_mute_voldiv,1);

 funcbit_smp_value_put(MIXER_var_mutelen,muteval);
}

one_mixerfunc_info MIXER_FUNCINFO_mute={
 "MIX_MUTE",
 NULL,
 &MIXER_var_mutelen,
 MIXER_INFOBIT_SWITCH,
 0,0,0,0,
 NULL,
 NULL,
 &mixer_volume_mute_process,
 NULL,
 &mixer_volume_mute_setvar
};

//-------------------------------------------------------------------
//volume balance
static unsigned int mixer_volume_balance_calcvol(unsigned int ch,unsigned int vol)
{
 switch(ch){
  case 0:if(MIXER_var_balance>0)
	  vol=vol*(100-MIXER_var_balance)/100;
	 break;
  case 1:if(MIXER_var_balance<0)
	  vol=vol*(100+MIXER_var_balance)/100;
	 break;
 }
 return vol;
}

static int mixer_volume_balance_checkvar(struct mpxp_aumixer_passinfo_s *mpi)
{
 if(MIXER_var_balance!=0)
  return 1;
 return 0;
}

one_mixerfunc_info MIXER_FUNCINFO_balance={
 "MIX_BALANCE",
 "mxbl",
 &MIXER_var_balance,
 0,
 -99,+99,0,3,
 NULL,
 NULL,
 NULL,
 &mixer_volume_balance_checkvar,
 NULL
};

//--------------------------------------------------------------------------
//limiter
#define LIMITER_VOLUP_DELAY  15         // in frames

typedef struct wave_area_s{
 unsigned long begin;
 long maxsign;
 unsigned long value;
}wave_area_s;

static wave_area_s **wa;
static unsigned int wa_max_areas,wa_alloc_chans;

static unsigned int wac[PCM_MAX_CHANNELS];
static unsigned int VOL_volumes[PCM_MAX_CHANNELS];
static long savemul[PCM_MAX_CHANNELS],slowvol=100;
static float mxvolum_limiter_overflow=1.0;
static long mxvolum_volume_scale=VOLUME_SCALE_DEFAULT; // controlled by limiter_overflow

#ifdef MIXER_LIMITER_CORRECT_BOUNDARY
static long lastvalue[PCM_MAX_CHANNELS];
static long firstmaxsignpos[PCM_MAX_CHANNELS];
#endif

// returns 100-700
unsigned int mpxplay_aumixer_mxvolum_getrealvolume_fast(void)
{
 long realvol=(savemul[0]+savemul[1])*50/mxvolum_volume_scale;
 //long realvol=(savemul[0]+savemul[1])*50/VOLUME_SCALE_DEFAULT;
 /*char sout[100];
 sprintf(sout,"rv:%4d",realvol);
 display_message(1,0,sout);*/
 return realvol;
}

unsigned int mpxplay_aumixer_mxvolum_getrealvolume_slow(void)
{
 long rvol=mpxplay_aumixer_mxvolum_getrealvolume_fast();
 //char sout[100];
 if(rvol>slowvol)
  slowvol++;
 else if(rvol<slowvol)
  slowvol-=max(1,((slowvol-rvol)>>2));
 //sprintf(sout,"rv:%4d sv:%4d",rvol,slowvol);
 //display_message(1,0,sout);
 return slowvol;
}

static void get_wave_areas_hq_long(PCM_CV_TYPE_F *pcm_sample,unsigned int samplenum,unsigned int channels)
{
 long i,ch,ctmp,c1,begin,maxsign,sign,ac;

 for(ch=0;ch<channels;ch++){
  ac=0;
  begin=ch;
  maxsign=wa[ch][wac[ch]-1].maxsign;
  if(!maxsign)
   pds_ftoi(pcm_sample[ch],&maxsign);
#ifdef MIXER_LIMITER_CORRECT_BOUNDARY
  firstmaxsignpos[ch]=ch;
#endif
  if(maxsign<0)
   sign=1;
  else
   sign=0;
  for(i=begin;(i<samplenum) && (ac<wa_max_areas);i+=channels){
   pds_ftoi(pcm_sample[i],&ctmp);
   c1=ctmp;
   if(c1!=0){
    if(sign){
     if(c1<0){
      if(c1<maxsign){
       maxsign=c1;
#ifdef MIXER_LIMITER_CORRECT_BOUNDARY
       if(!ac)
        firstmaxsignpos[ch]=i;
#endif
      }
     }else{
      sign=!sign;
      wa[ch][ac].begin=begin;
      wa[ch][ac].maxsign=maxsign;
      begin=i;
      maxsign=c1;
      ac++;
     }
    }else{
     if(c1>0){
      if(c1>maxsign){
       maxsign=c1;
#ifdef MIXER_LIMITER_CORRECT_BOUNDARY
       if(!ac)
        firstmaxsignpos[ch]=i;
#endif
      }
     }else{
      sign=!sign;
      wa[ch][ac].begin=begin;
      wa[ch][ac].maxsign=maxsign;
      begin=i;
      maxsign=c1;
      ac++;
     }
    }
   }
  }
  wa[ch][ac].begin=begin;
  wa[ch][ac].maxsign=maxsign;
  ac++;
  wa[ch][ac].begin=0x7fffffff;
  wa[ch][ac].maxsign=MIXER_SCALE_MAX;
  wac[ch]=ac;
 }
}

void cmwv1(void);

static void calculate_max_wave_values(unsigned int samplenum,unsigned int channels)
{
 //struct crossfade_info *cfi=mpi->mvp->cfi;
 unsigned int volup_delay=LIMITER_VOLUP_DELAY;
 long ch,ca,amp,newamp,volmul,cmul,delaj;
 long volume_limit_max,volume_limit_min,volume_scale_max,volume_scale_min;
 //char sout[100];

 volume_limit_max=(long)((float)MIXER_SCALE_MAX*mxvolum_limiter_overflow);
 volume_limit_min=(long)((float)MIXER_SCALE_MIN*mxvolum_limiter_overflow);
 volume_scale_max=MIXER_SCALE_MAX*mxvolum_volume_scale;
 volume_scale_min=MIXER_SCALE_MIN*mxvolum_volume_scale;

 /*if(crossfadepart){
  if((crossfadepart&CFT_FADEOUT) && (cfi->crossfadetype&CFT_FADEOUT))
   volup_delay=1024;//0x7ffffff/wa_max_areas; // ???
  if((crossfadepart&CFT_FADEIN) && (cfi->crossfadetype&CFT_FADEIN))
   volup_delay=1024;//cfi->crossfade_in_len;
 }*/
 //sprintf(sout,"wac0: %5d wac1: %5d d0:%d",wac[0],wac[1],(wac[0]*volup_delay));
 //display_message(1,0,sout);

 for(ch=0;ch<channels;ch++){
  volmul=VOL_volumes[ch]*mxvolum_volume_scale/100;
  if(savemul[ch]<volmul)
   cmul=savemul[ch];
  else
   cmul=volmul;

  delaj=wac[ch]*volup_delay;
  ca=0;
  do{
   amp=wa[ch][ca].maxsign;
   pds_ftoi((float)amp*(float)cmul/(float)mxvolum_volume_scale,&newamp); // hq-mode requires float calculation
   if(newamp>volume_limit_max)
    cmul=volume_scale_max/amp; // new cmul (lower)
   else
    if(newamp<volume_limit_min)
     cmul=volume_scale_min/amp;// new cmul (lower)
   wa[ch][ca].value=cmul;
   ca++;
   if(ca>=wac[ch])
    break;
   pds_ftoi(((float)cmul*(float)(delaj-1)+(float)volmul)/(float)delaj,&cmul); // large delaj -> overflow in 32-bit integer
  }while(1);
  if(cmul<volmul)
   savemul[ch]=cmul;
  else
   savemul[ch]=volmul;
 }
}

static void modify_volume_wa_hq(PCM_CV_TYPE_F *pcm_sample,unsigned int samplenum,unsigned int channels)
{
 const float sdiv=(float)mxvolum_volume_scale*(float)mxvolum_mute_voldiv;
 float fmul;
 unsigned int i,ch,ac;

 //fprintf(stdout,"vol0:%1.4f vol1:%1.4f \n",(float)savemul[0]/sdiv,(float)savemul[1]/sdiv);
 for(ch=0;ch<channels;ch++){
  i=ch;
#ifdef MIXER_LIMITER_CORRECT_BOUNDARY
  if((firstmaxsignpos[ch]>(channels+ch)) && (wa[ch][0].value<lastvalue[ch])){
   unsigned int len=firstmaxsignpos[ch];
   float begincorr=(lastvalue[ch]-wa[ch][0].value)/sdiv/(float)(len-ch);

   fmul=(float)wa[ch][0].value/sdiv;
   //fprintf(stdout,"ch:%d len:%3d bc:%1.4f fmul:%1.4f cf:%1.5f d:%1.5f e:%1.5f\n",
   //ch,len,begincorr*(float)(len-i),fmul,(fmul+begincorr*(float)(len-i)),(float)lastvalue[ch]/sdiv,((fmul+begincorr*(float)(len-i))-(float)lastvalue[ch]/sdiv)*100.0);
   for(;i<firstmaxsignpos[ch];i+=channels)
    pcm_sample[i]=pcm_sample[i]*(fmul+begincorr*(float)(len-i));
  }
#endif
  ac=0;                              // wave counter
  for(;i<samplenum;i+=channels){
   if(i>=wa[ch][ac].begin){          // new wave
    fmul=(float)wa[ch][ac].value/sdiv;
    ac++;
   }
   pcm_sample[i]*=fmul;
  }
#ifdef MIXER_LIMITER_CORRECT_BOUNDARY
  lastvalue[ch]=wa[ch][wac[ch]-1].value;
#endif
 }
}

static void limiter_main_hq(struct mpxp_aumixer_passinfo_s *mpi)
{
 get_wave_areas_hq_long((PCM_CV_TYPE_F *)mpi->pcm_sample,mpi->samplenum,mpi->chan_card);
 calculate_max_wave_values(mpi->samplenum,mpi->chan_card);
 modify_volume_wa_hq((PCM_CV_TYPE_F *)mpi->pcm_sample,mpi->samplenum,mpi->chan_card);
}

static unsigned int limiter_alloc_wa(struct mpxp_aumixer_passinfo_s *mpi,unsigned int samplenum)
{
 unsigned int ch;
 mpxp_uint32_t chancfgnum=0;

 mpi->control_cb(mpi->ccb_data,MPXPLAY_CFGFUNCNUM_AUMIXER_GET_CARD_CHANCFGNUM,&chancfgnum,NULL);
 if(!chancfgnum)
  return 0;

 //samplenum=samplenum*2/2;
 if((samplenum<=wa_max_areas) && (chancfgnum<=wa_alloc_chans))
  return 1;
 wa_max_areas=0;
 if(!wa)
  wa=pds_calloc(PCM_MAX_CHANNELS,sizeof(*wa));
 if(!wa)
  return 0;
 for(ch=0;ch<chancfgnum;ch++){
  if(wa[ch])
   pds_free(wa[ch]);
  wa[ch]=pds_calloc((samplenum+4),sizeof(struct wave_area_s));
  if(!wa[ch])
   return 0;
  savemul[ch]=mxvolum_volume_scale;
 }
 wa_max_areas=samplenum;
 wa_alloc_chans=chancfgnum;
 return 1;
}

static void limiter_dealloc(void)
{
 unsigned int ch;
 if(wa){
  for(ch=0;ch<PCM_MAX_CHANNELS;ch++)
   if(wa[ch])
    pds_free(wa[ch]);
  pds_free(wa);
  wa=NULL;
 }
 wa_max_areas=0;
}

static int mxvolum_limiter_init(struct mpxp_aumixer_passinfo_s *mpi,int inittype)
{
 struct mpxplay_audioout_info_s *aui=mpi->aui;
 long ch,samplenum,save_mutelen;

 if(!(MIXER_controlbits&MIXER_CONTROLBIT_LIMITER))
  return 0;

 switch(inittype){
  case MIXER_INITTYPE_INIT:
   samplenum=PCM_BUFFER_SIZE/(PCM_MAX_BITS/8);
   if(!limiter_alloc_wa(mpi,samplenum/PCM_CHANNELS_CFG))
    return 0;
   break;
  case MIXER_INITTYPE_START:
  case MIXER_INITTYPE_REINIT:
   if((mpi->freq_song>=PCM_MIN_FREQ) && mpi->freq_card && mpi->chan_song){
    samplenum=0;
    mpi->control_cb(mpi->ccb_data,MPXPLAY_CFGFUNCNUM_AUMIXER_GET_PCMOUTBLOCKSIZE,&samplenum,NULL);
    samplenum/=mpi->chan_song;
    if(samplenum<PCM_OUTSAMPLES)
     samplenum=mpxplay_infile_get_samplenum_per_frame(mpi->freq_song);
    if(samplenum<PCM_OUTSAMPLES)
     samplenum=PCM_OUTSAMPLES;
    samplenum=(long)((float)(2*samplenum+128)*(float)mpi->freq_card/(float)mpi->freq_song); // *2 : speed_control max expansion
    if(!limiter_alloc_wa(mpi,samplenum))
     return 0;
   }
   if(inittype==MIXER_INITTYPE_REINIT)
    break;
  case MIXER_INITTYPE_RESET:
   if(!wa_max_areas)
    return 0;

#ifdef MIXER_LIMITER_CORRECT_BOUNDARY
   for(ch=0;ch<mpi->chan_card;ch++)
    lastvalue[ch]=0;
#endif

   save_mutelen=MIXER_var_mutelen;
   MIXER_var_mutelen=0;        // to avoid the effect of mute
   mixer_volumes_calc(mpi);
   MIXER_var_mutelen=save_mutelen;
   for(ch=0;ch<mpi->chan_card;ch++){
    //if(crossfadepart)
    // savemul[ch]=mxvolum_volume_scale;
    //else
    if(!crossfadepart)
     savemul[ch]=VOL_volumes[ch]*mxvolum_volume_scale/100;
    wac[ch]=1;
    wa[ch][0].begin=ch;
    wa[ch][0].maxsign=0;
   }
   slowvol=mpxplay_aumixer_mxvolum_getrealvolume_fast();
   break;

  case MIXER_INITTYPE_CLOSE:
   limiter_dealloc();
   break;
 }
 return 1;
}

static int mxvolum_limiter_checkvar(struct mpxp_aumixer_passinfo_s *mpi)
{
 unsigned int ch;
 if(MIXER_controlbits&MIXER_CONTROLBIT_LIMITER){
  if( ( (mixer_volume_calcvol(mpi)>100)
       || MIXER_getstatus("MIX_CROSSFADER") // ???
       || MIXER_getstatus("MIX_SURROUND")
       || MIXER_getstatus("MIX_TONE_BASS")
       || ((mpi->chan_card<mpi->chan_song) && (mpi->chan_card<=2)) // ??? downmix
      )
   && wa_max_areas
  ){
   mxvolum_limiter_overflow=pow(10.0,(double)MIXER_var_limiter_overflow/20.0);
   mxvolum_volume_scale=VOLUME_SCALE_DEFAULT/mxvolum_limiter_overflow;
   return 1;
  }
 }
 mxvolum_volume_scale=VOLUME_SCALE_DEFAULT;
 for(ch=0;ch<mpi->chan_card;ch++)
  savemul[ch]=mxvolum_volume_scale;
 slowvol=100;
 return 0;
}

one_mixerfunc_info MIXER_FUNCINFO_limiter={
 "MIX_LIMITER",
 "mxlo",
 &MIXER_var_limiter_overflow,
 MIXER_INFOBIT_PARALLEL_DEPENDENCY,
 0,18,0,1,
 &mxvolum_limiter_init,
 NULL,
 NULL,
 &mxvolum_limiter_checkvar,
 NULL
};

//--------------------------------------------------------------------------
//volume
static unsigned int mixer_volume_calcvol(struct mpxp_aumixer_passinfo_s *mpi)
{
 unsigned int loc_volume=MIXER_var_volume;
 /*if(mpi->mvp && mpi->mvp->frp0 && mpi->mvp->frp0->infile_infos && mpi->mvp->frp0->infile_infos->audio_decoder_infos){
  float rg=(mpi->mvp->frp0)->infile_infos->audio_decoder_infos->replaygain;
  //fprintf(stdout,"rg:%2.2f \n",rg);
  if(rg!=1.0)
   loc_volume*=rg;
 }*/
 return loc_volume;
}

static void mixer_volumes_calc(struct mpxp_aumixer_passinfo_s *mpi)
{
 unsigned int ch,loc_volume=mixer_volume_calcvol(mpi);
 for(ch=0;ch<mpi->chan_card;ch++)
  VOL_volumes[ch]=mixer_volume_balance_calcvol(ch,loc_volume);
}

static void mixer_volume_manual_hq(struct mpxp_aumixer_passinfo_s *mpi)
{
 PCM_CV_TYPE_F *pcm_s=(PCM_CV_TYPE_F *)mpi->pcm_sample;
 unsigned int ch,samplenum=mpi->samplenum,channels=mpi->chan_card;

 samplenum/=channels;

 for(ch=0;ch<channels;ch++){
  const float cfvol=(float)VOL_volumes[ch]/100.0f/(float)mxvolum_mute_voldiv;
  PCM_CV_TYPE_F *pcm=pcm_s+ch;
  unsigned int sn=samplenum;
  do{
   pcm[0]*=cfvol;
   pcm+=channels;
  }while(--sn);
 }
}

static void mixer_volume_process(struct mpxp_aumixer_passinfo_s *mpi)
{
 if(MIXER_FUNCINFO_limiter.infobits&MIXER_INFOBIT_ENABLED)
  limiter_main_hq(mpi);
 else
  mixer_volume_manual_hq(mpi);
}

static int mixer_volume_checkvar(struct mpxp_aumixer_passinfo_s *mpi)
{
 mixer_volumes_calc(mpi);
 if( MIXER_var_mutelen
  || mixer_volume_calcvol(mpi)!=MIXER_FUNCINFO_volume.var_center
  || mixer_volume_balance_checkvar(mpi)
  || mxvolum_limiter_checkvar(mpi)){
  return 1;
 }
 return 0;
}

static void mixer_volume_manual_setvar(struct mpxp_aumixer_passinfo_s *mpi,unsigned int setmode,int value)
{
 one_mixerfunc_info *infop=&MIXER_FUNCINFO_volume;
 int step=value*infop->var_step;
 int newvol=infop->var_center;
 int currvol=*(infop->variablep);

 switch(setmode){
  case MIXER_SETMODE_RELATIVE:
   if((currvol<14) || (currvol+step)<14)
    newvol=currvol+value;
   else
    if(currvol<=100)
     newvol=currvol+step;
    else
     newvol=(value>0)? ((currvol*1059+500)/1000):((currvol*944+500)/1000); // +0.5:-0.5 dB
   if((currvol<infop->var_center && newvol>infop->var_center) || (currvol>infop->var_center && newvol<infop->var_center))
    newvol=infop->var_center;
   break;
  case MIXER_SETMODE_ABSOLUTE:newvol=value;break;
  case MIXER_SETMODE_RESET   :newvol=infop->var_center;break;
 }
 if(newvol<infop->var_min)
  newvol=infop->var_min;
 else
  if(newvol>infop->var_max)
   newvol=infop->var_max;
 MIXER_var_volume=newvol;
}

one_mixerfunc_info MIXER_FUNCINFO_volume={
 "MIX_VOLUME",
 "mxv",
 &MIXER_var_volume,
 MIXER_INFOBIT_PARALLEL_DEPENDENCY, // mute,balance,limiter
 0,700,100,6,
 NULL,
 NULL,
 &mixer_volume_process,
 &mixer_volume_checkvar,
 &mixer_volume_manual_setvar
};
