//**************************************************************************
//*                     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: speed/freq control

#include "mpxplay.h"

#define CVFREQ_USE_ASM 1

extern unsigned int crossfadepart;

static int MIXER_var_speed;
one_mixerfunc_info MIXER_FUNCINFO_speed;

typedef struct cvfreq_info_s{
 PCM_CV_TYPE_F *cv_freq_buffer;
 unsigned int cv_freq_bufsize, mx_sp_begin;
 float inpos;
}cvfreq_info_s;

//--------------------------------------------------------------------------
void asm_cv_freq_floor(void);
void asm_cv_freq_hq(void);

static void mixer_speed_process(struct mpxp_aumixer_passinfo_s *mpi)
{
 struct cvfreq_info_s *cfs=(struct cvfreq_info_s *)mpi->private_data;
 unsigned long samplenum=mpi->samplenum,channels=mpi->chan_card;
 const float instep=(float)MIXER_var_speed/(float)MIXER_FUNCINFO_speed.var_center*(float)mpi->freq_song/(float)mpi->freq_card;
 const float inend=samplenum/channels;
 PCM_CV_TYPE_F *pcm=(PCM_CV_TYPE_F *)mpi->pcm_sample,*intmp;
 unsigned long savesamplenum=channels,ipi,fpucontrolword_save;
 float inpos;

 if(!cfs)
  return;
 intmp = cfs->cv_freq_buffer;
 if(!intmp || (samplenum>cfs->cv_freq_bufsize))
  return;
 if((MIXER_var_speed==MIXER_FUNCINFO_speed.var_center) && (mpi->freq_song==mpi->freq_card))
  return;

 if(cfs->mx_sp_begin){ // to avoid a click at start
  pds_qmemcpy(intmp,pcm,savesamplenum);
  cfs->mx_sp_begin=0;
  if(instep<1)
   inpos=instep/2;
  else
   inpos=0;
 }else
  inpos=cfs->inpos;

 if( (crossfadepart==CROSS_FADE) // !!! hack
  && (mpi->mmi->mixer_infobits&AUINFOS_MIXERINFOBIT_CFFREQDIFF)
 ){
  if(instep<1)
   inpos=instep/2;
  else{
   float ief=1024*inend/instep;
   long iel=(long)ief;
   long ir=iel%1024;
   if(ir)
    inpos=0.25f;
   else
    inpos=0;
  }
 }

 pds_qmemcpy((intmp+savesamplenum),pcm,samplenum);

#if defined(CVFREQ_USE_ASM) && defined(__WATCOMC__)
 #pragma aux asm_cv_freq_hq=\
 "fstcw word ptr fpucontrolword_save"\
 "mov ax,word ptr fpucontrolword_save"\
 "or ax,0x0c00"\
 "mov word ptr ipi,ax"\
 "fldcw word ptr ipi"\
 "fld dword ptr inpos"\
 "mov ecx,dword ptr channels"\
 "mov esi,dword ptr pcm"\
 "back1:"\
  "fld st"\
  "frndint"\
  "fist dword ptr ipi"\
  "fsubr st,st(1)"\
  "mov eax,dword ptr ipi"\
  "imul ecx"\
  "fld1"\
  "fsub st,st(1)"\
  "shl eax,2"\
  "add eax,dword ptr intmp"\
  "lea ebx,dword ptr [eax+ecx*4]"\
  "mov edx,ecx"\
  "back2:"\
   "fld dword ptr [eax]"\
   "fmul st,st(1)"\
   "add eax,4"\
   "fld dword ptr [ebx]"\
   "fmul st,st(3)"\
   "add ebx,4"\
   "fadd"\
   "fstp dword ptr [esi]"\
   "add esi,4"\
   "dec edx"\
  "jnz back2"\
  "fstp st"\
  "fstp st"\
  "fadd dword ptr instep"\
  "fcom dword ptr inend"\
  "fnstsw ax"\
  "sahf"\
 "jb back1"\
 "fsub dword ptr inend"\
 "mov dword ptr pcm,esi"\
 "fstp dword ptr inpos"\
 "fldcw word ptr fpucontrolword_save"\
 modify[eax ebx ecx edx esi];
 asm_cv_freq_hq();

#else // !CVFREQ_USE_ASM

#ifdef __WATCOMC__
 pds_fpu_setround_chop(); // to asm_cv_freq_floor() !
#endif
 do{
  float m1,m2;
  unsigned int ipi,ch;
  PCM_CV_TYPE_F *intmp1,*intmp2;
#ifdef __WATCOMC__
 #pragma aux asm_cv_freq_floor=\
  "fld dword ptr inpos"\
  "fistp dword ptr ipi"\
  modify[];
  asm_cv_freq_floor();
#else
  ipi=(long)floor(inpos);
#endif
  m2=inpos-(float)ipi;
  m1=1.0f-m2;
  ch=channels;
  ipi*=ch;
  intmp1=intmp+ipi;
  intmp2=intmp1+ch;
  do{
   *pcm++=(*intmp1++)*m1+(*intmp2++)*m2;
  }while(--ch);
  inpos+=instep;
 }while(inpos<inend);
 inpos-=inend;
#ifdef __WATCOMC__
 pds_fpu_setround_near(); // restore default
#endif

#endif //CVFREQ_USE_ASM

 pds_qmemcpy(cfs->cv_freq_buffer,(cfs->cv_freq_buffer+mpi->samplenum),savesamplenum);
 mpi->samplenum=pcm-((PCM_CV_TYPE_F *)mpi->pcm_sample);
 cfs->inpos=inpos;
}

static unsigned int mixer_speed_alloc(struct mpxp_aumixer_passinfo_s *mpi,unsigned int samplenum)
{
 struct cvfreq_info_s *cfs=(struct cvfreq_info_s *)mpi->private_data;
 if(!cfs){
  cfs=(struct cvfreq_info_s *)pds_calloc(1,sizeof(struct cvfreq_info_s));
  if(!cfs)
   return 0;
  mpi->private_data=cfs;
 }
 if(cfs->cv_freq_bufsize<samplenum){
  cfs->cv_freq_bufsize=samplenum;
  if(cfs->cv_freq_buffer)
   pds_free(cfs->cv_freq_buffer);
  cfs->cv_freq_buffer=(PCM_CV_TYPE_F *)pds_malloc(cfs->cv_freq_bufsize*sizeof(PCM_CV_TYPE_F));
  if(!cfs->cv_freq_buffer){
   cfs->cv_freq_bufsize=0;
   return 0;
  }
  cfs->mx_sp_begin=1;
 }
 return 1;
}

static void mixer_speed_dealloc(struct mpxp_aumixer_passinfo_s *mpi)
{
 struct cvfreq_info_s *cfs=(struct cvfreq_info_s *)mpi->private_data;
 if(!cfs)
  return;
 if(cfs->cv_freq_buffer)
  pds_free(cfs->cv_freq_buffer);
 pds_memset(cfs,0,sizeof(*cfs));
 pds_free(cfs);
 mpi->private_data=NULL;
}

static void mixer_speed_chk_var_speed(struct mpxp_aumixer_passinfo_s *mpi)
{
 static unsigned int mx_sp_100sw1000_tested,mx_sp_1000_laststatus;
 mpxp_uint32_t infobits=0;
 mpi->control_cb(mpi->ccb_data,MPXPLAY_CFGFUNCNUM_AUMIXER_GET_INFOBITS,&infobits,NULL);
 if(mx_sp_1000_laststatus!=(infobits&AUINFOS_MIXERCTRLBIT_SPEED1000)){
  mx_sp_1000_laststatus=infobits&AUINFOS_MIXERCTRLBIT_SPEED1000;
  mx_sp_100sw1000_tested=0;
 }
 if(infobits&AUINFOS_MIXERCTRLBIT_SPEED1000){
  MIXER_FUNCINFO_speed.var_min=500;
  MIXER_FUNCINFO_speed.var_max=9999;
  MIXER_FUNCINFO_speed.var_center=1000;
  if(MIXER_var_speed<MIXER_FUNCINFO_speed.var_min)
   if(!mx_sp_100sw1000_tested)
    MIXER_var_speed*=10;
   else
    MIXER_var_speed=MIXER_FUNCINFO_speed.var_min;
  if(MIXER_var_speed>MIXER_FUNCINFO_speed.var_max)
   MIXER_var_speed=MIXER_FUNCINFO_speed.var_max;
  funcbit_enable(infobits,AUINFOS_MIXERINFOBIT_SPEED1000);
 }else{
  MIXER_FUNCINFO_speed.var_min=50;
  MIXER_FUNCINFO_speed.var_max=999;
  MIXER_FUNCINFO_speed.var_center=100;
  if(MIXER_var_speed>MIXER_FUNCINFO_speed.var_max)
   if(!mx_sp_100sw1000_tested)
    MIXER_var_speed/=10;
   else
    MIXER_var_speed=MIXER_FUNCINFO_speed.var_max;
  if(MIXER_var_speed<MIXER_FUNCINFO_speed.var_min)
   MIXER_var_speed=MIXER_FUNCINFO_speed.var_min;
  funcbit_disable(infobits,AUINFOS_MIXERINFOBIT_SPEED1000);
 }
 mpi->control_cb(mpi->ccb_data,MPXPLAY_CFGFUNCNUM_AUMIXER_SET_INFOBITS,&infobits,NULL);
 mx_sp_100sw1000_tested=1;
}

static int mixer_speed_init(struct mpxp_aumixer_passinfo_s *mpi,int inittype)
{
 struct mpxplay_audioout_info_s *aui=mpi->aui;
 struct cvfreq_info_s *cfs;
 switch(inittype){
  case MIXER_INITTYPE_INIT:
   if(!mixer_speed_alloc(mpi,PCM_BUFFER_SIZE/(PCM_MAX_BITS/8)+PCM_MAX_CHANNELS))
    return 0;
   break;
  case MIXER_INITTYPE_START:
  case MIXER_INITTYPE_REINIT:
   if(mpi->freq_song>=PCM_MIN_FREQ){
    one_mixerfunc_info *infop_speed=&MIXER_FUNCINFO_speed;
    float speed_expansion=0.0f;
    long samplenum;

    if(infop_speed)
     speed_expansion=(float)infop_speed->var_center/(float)infop_speed->var_min;
    if(speed_expansion<2.0)
     speed_expansion=2.0;

    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=speed_expansion*(float)((samplenum+128)*max(mpi->chan_song,mpi->chan_card))
             *(float)(max(mpi->freq_card,mpi->freq_song))/(float)mpi->freq_song;
    if(!mixer_speed_alloc(mpi,samplenum))
     return 0;
   }
   if(inittype==MIXER_INITTYPE_REINIT)
    break;
  case MIXER_INITTYPE_RESET:
   cfs=(struct cvfreq_info_s *)mpi->private_data;
   if(!cfs)
    return 0;
   cfs->mx_sp_begin=1;
   break;
  case MIXER_INITTYPE_CLOSE:
   mixer_speed_dealloc(mpi);
   break;
 }
 return 1;
}

static int mixer_speed_checkvar(struct mpxp_aumixer_passinfo_s *mpi)
{
 float freq=(mpi->freq_song<22050)? 22050.0:(float)mpi->freq_song;
 long  speed_center,speed_cur;
 mpxp_int32_t decoder_cycles;

 speed_center=MIXER_FUNCINFO_speed.var_center;
 speed_cur=MIXER_var_speed;
 if(speed_cur<speed_center)
  speed_cur=speed_center;

 decoder_cycles=(mpxp_int32_t)(((float)speed_cur+(float)(speed_center/2))/(float)speed_center*freq/(float)PCM_OUTSAMPLES)*(float)INT08_DIVISOR_NEW/(float)(INT08_CYCLES_DEFAULT*INT08_DIVISOR_DEFAULT)+1;
 mpi->control_cb(mpi->ccb_data,MPXPLAY_CFGFUNCNUM_AUMIXER_SET_CARD_INT08DECODERCYCLES,&decoder_cycles,NULL);

 if((MIXER_var_speed!=speed_center) || (mpi->freq_song!=mpi->freq_card))
  return 1;
 return 0;
}

static void mixer_speed_setvar(struct mpxp_aumixer_passinfo_s *mpi,unsigned int setmode,int value)
{
 switch(setmode){
  case MIXER_SETMODE_ABSOLUTE:MIXER_var_speed=value;break;
  case MIXER_SETMODE_RELATIVE:MIXER_var_speed+=value;break;
  case MIXER_SETMODE_RESET:MIXER_var_speed=MIXER_FUNCINFO_speed.var_center;break;
 }
 mixer_speed_chk_var_speed(mpi);
}

one_mixerfunc_info MIXER_FUNCINFO_speed={
 "MIX_SPEED",
 "mxsp",
 &MIXER_var_speed,
 MIXER_INFOBIT_EXTERNAL_DEPENDENCY, // mpi->freq_song
 50,999,100,1,
 &mixer_speed_init,
 NULL,
 &mixer_speed_process,
 &mixer_speed_checkvar,
 &mixer_speed_setvar
};

//---------------------------------------------------------------

one_mixerfunc_info MIXER_FUNCINFO_seekspeed;
static int seekspeed_base,seekspeed_extra,seekspeed_counter;
extern unsigned int refdisp;
#include "display\display.h"

static void mixer_speed_seekspeed_process(struct mpxp_aumixer_passinfo_s *mpi)
{
 if(seekspeed_counter)
  seekspeed_counter--;
 else{
  MIXER_setfunction(mpi,"MIX_SPEEDSEEK",MIXER_SETMODE_RELATIVE,-25);
  refdisp|=RDT_OPTIONS;
 }
}

static void mixer_speed_seekspeed_setvar(struct mpxp_aumixer_passinfo_s *mpi,unsigned int setmode,int value)
{
 mpxp_int32_t infobits;

 if(!mpi || !mpi->control_cb)
  return;

 infobits=0;
 mpi->control_cb(mpi->ccb_data,MPXPLAY_CFGFUNCNUM_AUMIXER_GET_CARD_INFOBITS,&infobits,NULL);

 if(!seekspeed_extra){
  seekspeed_base=MIXER_getvalue("MIX_SPEED");
  seekspeed_counter=35;
 }else if(value>0){
  if(funcbit_test(infobits,AUINFOS_CARDINFOBIT_DMAUNDERRUN))
   value=-10;
  else if(!funcbit_test(infobits,AUINFOS_CARDINFOBIT_DMAFULL))
   value=0;
  seekspeed_counter=20;
 }else
  seekspeed_counter=1;
 if(value<=0 || funcbit_test(infobits,AUINFOS_CARDINFOBIT_DMAFULL)){
  int spde=seekspeed_extra;
  switch(setmode){
   case MIXER_SETMODE_ABSOLUTE:spde=value;break;
   case MIXER_SETMODE_RELATIVE:spde+=value;break;
   case MIXER_SETMODE_RESET:spde=0;break;
  }
  if(spde<0)
   spde=0;
  if(spde>MIXER_FUNCINFO_seekspeed.var_max)
   spde=MIXER_FUNCINFO_seekspeed.var_max;
  seekspeed_extra=spde;
  infobits=0;
  mpi->control_cb(mpi->ccb_data,MPXPLAY_CFGFUNCNUM_AUMIXER_GET_INFOBITS,&infobits,NULL);
  if(infobits&AUINFOS_MIXERINFOBIT_SPEED1000)
   spde*=10;
  MIXER_setfunction(mpi,"MIX_SPEED",MIXER_SETMODE_ABSOLUTE,seekspeed_base+spde);
 }
 MIXER_setfunction(mpi,"MIX_MUTE",MIXER_SETMODE_ABSOLUTE,(seekspeed_extra)? 48:0);
}

one_mixerfunc_info MIXER_FUNCINFO_seekspeed={
 "MIX_SPEEDSEEK",
 NULL,
 &seekspeed_extra,
 0,
 0,890,0,10,
 NULL,
 NULL,
 &mixer_speed_seekspeed_process,
 NULL,
 &mixer_speed_seekspeed_setvar
};
