/*====================================================================
 *
 * Copyright 1993, Silicon Graphics, Inc.
 * All Rights Reserved.
 *
 * This is UNPUBLISHED PROPRIETARY SOURCE CODE of Silicon Graphics,
 * Inc.; the contents of this file may not be disclosed to third
 * parties, copied or duplicated in any form, in whole or in part,
 * without the prior written permission of Silicon Graphics, Inc.
 *
 * RESTRICTED RIGHTS LEGEND:
 * Use, duplication or disclosure by the Government is subject to
 * restrictions as set forth in subdivision (c)(1)(ii) of the Rights
 * in Technical Data and Computer Software clause at DFARS
 * 252.227-7013, and/or in similar or successor clauses in the FAR,
 * DOD or NASA FAR Supplement. Unpublished - rights reserved under the
 * Copyright Laws of the United States.
 *====================================================================*/

#include "n_synthInternals.h"
#include <os.h>
#include <stdio.h>
#include <math.h>
#include <assert.h>

#ifdef AUD_PROFILE
extern u32 cnt_index, env_num, env_cnt, env_max, env_min, lastCnt[];
extern u32 rate_num, rate_cnt, rate_max, rate_min;
extern u32 vol_num, vol_cnt, vol_max, vol_min;
#endif

#define N_EQPOWER_LENGTH 128

s16 n_eqpower[ N_EQPOWER_LENGTH ] = {
    32767,  32764,  32757,  32744,  32727,  32704,
    32677,  32644,  32607,  32564,  32517,  32464,
    32407,  32344,  32277,  32205,  32127,  32045,
    31958,  31866,  31770,  31668,  31561,  31450,
    31334,  31213,  31087,  30957,  30822,  30682,
    30537,  30388,  30234,  30075,  29912,  29744,
    29572,  29395,  29214,  29028,  28838,  28643,
    28444,  28241,  28033,  27821,  27605,  27385,
    27160,  26931,  26698,  26461,  26220,  25975,
    25726,  25473,  25216,  24956,  24691,  24423,
    24151,  23875,  23596,  23313,  23026,  22736,
    22442,  22145,  21845,  21541,  21234,  20924,
    20610,  20294,  19974,  19651,  19325,  18997,
    18665,  18331,  17993,  17653,  17310,  16965,
    16617,  16266,  15913,  15558,  15200,  14840,
    14477,  14113,  13746,  13377,  13006,  12633,
    12258,  11881,  11503,  11122,  10740,  10357,
    9971,   9584,   9196,   8806,   8415,   8023,
    7630,   7235,   6839,   6442,   6044,   5646,
    5246,   4845,   4444,   4042,   3640,   3237,
    2833,   2429,   2025,   1620,   1216,    810,
    405,      0
};

#ifndef N_MICRO
extern	f64	__pow(f64, f64);
extern f64 _frexpf(f64 value, s32 *eptr);
extern f64 _ldexpf(f64 in, s32 ex);
#endif

/*
 * prototypes for private enveloper functions
 */
static  Acmd *_pullSubFrame(N_PVoice *pv, s16 *inp, s16 *outp, s32 outCount,
                             Acmd *p) ;

static  s16 _getRate(f64 vol, f64 tgt, s32 count, u16* ratel);
     
     
#ifndef N_MICRO
static  f32 _getVol(f32 ivol, s32 samples, s16 ratem, u16 ratel);
#else
static  s16 _getVol(s16 ivol, s32 samples, s16 ratem, u16 ratel);
#endif
     
/***********************************************************************
  * Enveloper filter public interfaces
 ***********************************************************************/
Acmd *n_alEnvmixerPull(N_PVoice *filter, s32 sampleOffset, Acmd *p) 
{
  Acmd        *ptr = p;

  
  N_PVoice	*e = (N_PVoice *)filter;
  
  s16         inp;
  s32         lastOffset;
  s32         thisOffset = sampleOffset;
  s32         samples;
  s16         loutp = 0;
  s32         fVol;
  ALParam     *thisParam;
  s32 outCount = FIXED_SAMPLE;
  
#ifdef AUD_PROFILE
  lastCnt[++cnt_index] = osGetCount();
#endif
  
  /*
   * Force the input to be the resampler output
   */
#ifndef N_MICRO
  inp = AL_RESAMPLER_OUT;
#else
  inp = N_AL_RESAMPLER_OUT;
#endif
  
  while (e->em_ctrlList != 0) {
    
    lastOffset = thisOffset;
    thisOffset = e->em_ctrlList->delta;

#ifdef FINAL_ROUND
    samples = SAMPLE184(thisOffset - lastOffset);
    if(!samples)
      thisOffset = lastOffset;
#else
    samples = thisOffset - lastOffset;
#endif

    if (samples > outCount)
      break;
    
#ifdef _DEBUG
    assert(samples >= 0);
#ifndef N_MICRO
    assert(samples <= AL_MAX_RSP_SAMPLES);
#else
    assert(samples <= FIXED_SAMPLE);
#endif
#endif

    switch (e->em_ctrlList->type) {
    case (AL_FILTER_START_VOICE_ALT):
      {                  
	ALStartParamAlt *param = (ALStartParamAlt *)e->em_ctrlList;
	ALFilter     *f = (ALFilter *) e;
	s32 tmp;
	
	if (param->unity) {
	  e->rs_upitch = 1;
	}
	
	n_alLoadParam(e, AL_FILTER_SET_WAVETABLE, param->wave);
	e->em_motion = AL_PLAYING;
	e->em_first  = 1;
	e->em_delta  = 0;

#ifdef FINAL_ROUND
	e->em_segEnd = SAMPLE184(param->samples);
#else
	e->em_segEnd = param->samples;
#endif

	tmp = ((s32)param->volume * (s32)param->volume) >> 15;


	e->em_volume = (s16) tmp;
	e->em_pan    = param->pan;
	e->em_dryamt = n_eqpower[param->fxMix];
	e->em_wetamt = n_eqpower[N_EQPOWER_LENGTH - param->fxMix - 1];
	
	if (param->samples) {
	  e->em_cvolL  = 1;
	  e->em_cvolR  = 1;
	} else {
	  /*
	   * Attack time is zero. Simply set the
	   * volume. We don't want an attack segment.
	   */
	  e->em_cvolL = (e->em_volume * n_eqpower[e->em_pan]) >> 15;
	  e->em_cvolR = (e->em_volume *
			 n_eqpower[N_EQPOWER_LENGTH - e->em_pan - 1]) >> 15;
	}
	e->rs_ratio = param->pitch;
      }
      
      break;
      
    case (AL_FILTER_SET_FXAMT):
    case (AL_FILTER_SET_PAN):
    case (AL_FILTER_SET_VOLUME):
      ptr = _pullSubFrame(e, &inp, &loutp, samples, ptr);
      
      if (e->em_delta >= e->em_segEnd){
	/*
	 * We should have reached our target, calculate
	 * target in case e->em_segEnd was 0
	 */
	e->em_ltgt = (e->em_volume * n_eqpower[e->em_pan]) >> 15;
	e->em_rtgt = (e->em_volume *
		      n_eqpower[N_EQPOWER_LENGTH - e->em_pan - 1]) >> 15;
	e->em_delta = e->em_segEnd;   /* To prevent overflow */
	e->em_cvolL = e->em_ltgt;
	e->em_cvolR = e->em_rtgt;
      } else {
	/* 
	 * Estimate the current volume
	 */
	e->em_cvolL = _getVol(e->em_cvolL, e->em_delta, e->em_lratm, e->em_lratl);
	e->em_cvolR = _getVol(e->em_cvolR, e->em_delta, e->em_rratm, e->em_rratl);
      }
      
      /*
       * We can't have volume of zero, because the envelope
       * would never go anywhere from there
       */
      if( e->em_cvolL == 0 ) e->em_cvolL = 1;
      if( e->em_cvolR == 0 ) e->em_cvolR = 1;
      
      if (e->em_ctrlList->type == AL_FILTER_SET_PAN)
	
	/*
	 * This should result in a change to the current
	 * segment rate and target
	 */
	e->em_pan = (s16) e->em_ctrlList->data.i;
      
      if (e->em_ctrlList->type == AL_FILTER_SET_VOLUME){
	
	/*
	 * Switching to a new segment
	 */
	e->em_delta = 0;
	
	/*
	 * Map volume non-linearly to give something close to
	 * loudness
	 */
	fVol = (e->em_ctrlList->data.i);
	fVol = (fVol*fVol)>>15;
	e->em_volume = (s16) fVol;
	
#ifdef FINAL_ROUND
	e->em_segEnd = SAMPLE184(e->em_ctrlList->moredata.i);
#else
	e->em_segEnd = e->em_ctrlList->moredata.i;
#endif
	
      }
      
      if (e->em_ctrlList->type == AL_FILTER_SET_FXAMT){
	e->em_dryamt = n_eqpower[e->em_ctrlList->data.i];
	e->em_wetamt = n_eqpower[N_EQPOWER_LENGTH - e->em_ctrlList->data.i - 1];
      }
      
      /*
       * Force a volume update
       */
      e->em_first = 1;
      break;
      
    case (AL_FILTER_START_VOICE):
      {
	ALStartParam *p = (ALStartParam *)e->em_ctrlList;
	
	/*
	 * Changing to PLAYING (since the previous state was
	 * persumable STOPPED, we'll just bump the output
	 * pointer rather than pull a subframe of zeros).
	 */
	if (p->unity) {
	  e->rs_upitch = 1;
	}
	
	n_alLoadParam(e, AL_FILTER_SET_WAVETABLE, p->wave);
	e->em_motion = AL_PLAYING;
      }
      break;
    case (AL_FILTER_STOP_VOICE):
      {
	/*
	 * Changing to STOPPED and reset the filter
	 */
	ptr = _pullSubFrame(e, &inp, &loutp, samples, ptr);
	n_alEnvmixerParam(e, AL_FILTER_RESET, 0);
      }
      break;
      
    case (AL_FILTER_FREE_VOICE):
      {                  
	N_ALFreeParam *param = (N_ALFreeParam *)e->em_ctrlList;
	param->pvoice->offset = 0;
	_n_freePVoice( (N_PVoice *)param->pvoice);
      }
      break;
#if 1
    case (AL_FILTER_SET_PITCH):
      ptr = _pullSubFrame(e, &inp, &loutp, samples, ptr);
      e->rs_ratio = e->em_ctrlList->data.f;
      break;
    case (AL_FILTER_SET_UNITY_PITCH):
      ptr = _pullSubFrame(e, &inp, &loutp, samples, ptr);
      e->rs_upitch = 1;
      break;
    case (AL_FILTER_SET_WAVETABLE):
      ptr = _pullSubFrame(e, &inp, &loutp, samples, ptr);
      n_alLoadParam(e, AL_FILTER_SET_WAVETABLE,
		       (void *)e->em_ctrlList->data.i);
      break;
#endif
    default:
      /*
       * Pull the reuired number of samples and then pass the message
       * on down the chain
       */
      ptr = _pullSubFrame(e, &inp, &loutp, samples, ptr);
      n_alEnvmixerParam(e, e->em_ctrlList->type,
		      (void *) e->em_ctrlList->data.i);
      break;
    }
    loutp  += (samples<<1);
    outCount -= samples;
    
    /*
     * put the param record back on the free list
     */
    thisParam = e->em_ctrlList;
    e->em_ctrlList = e->em_ctrlList->next;
    if (e->em_ctrlList == 0)
      e->em_ctrlTail = 0;
    
    _n_freeParam(thisParam);
    
  }
  
  ptr = _pullSubFrame(e, &inp, &loutp, outCount, ptr);
  
  /*
   * Prevent overflow in e->em_delta
   */
  if (e->em_delta > e->em_segEnd)
    e->em_delta = e->em_segEnd;
  
#ifdef AUD_PROFILE
  PROFILE_AUD(env_num, env_cnt, env_max, env_min);
#endif
  return ptr;
}

s32
  n_alEnvmixerParam(N_PVoice *filter, s32 paramID, void *param)
{
  N_PVoice	*e =  filter;
  
  switch (paramID) {
  case (AL_FILTER_ADD_UPDATE):
    if (e->em_ctrlTail) {
      e->em_ctrlTail->next = (ALParam *)param;
    } else {
      e->em_ctrlList = (ALParam *)param;
    }            
    e->em_ctrlTail = (ALParam *)param;
    break;
  case (AL_FILTER_RESET):
    e->em_first = 1;
    e->em_motion = AL_STOPPED;
    e->em_volume = 1;
    e->em_segEnd = 0;
    e->rs_delta  = 0.0;
    e->rs_first  = 1;
    e->rs_upitch = 0;
    n_alLoadParam(e, AL_FILTER_RESET, param);
    break;
  case (AL_FILTER_START):
    e->em_motion = AL_PLAYING;
    break;
  default:
#if 1
    n_alLoadParam(e, paramID, param);
#else
    n_alResampleParam(e, paramID, param);
#endif
    break;
  }
  return 0;
}

static
  Acmd* _pullSubFrame(N_PVoice *filter, s16 *inp, s16 *outp, s32 outCount,
		      Acmd *p) 
{
  Acmd        *ptr = p;
  N_PVoice	*e = filter;
#ifndef N_MICRO
  s16 *inpp = AL_RESAMPLER_OUT;
#else
  s16 *inpp = N_AL_RESAMPLER_OUT;
#endif

  /* filter must be playing and request non-zero output samples to pull. */
  if (e->em_motion != AL_PLAYING || !outCount)
    return ptr;

  /*
   * ask all filters upstream from us to build their command
   * lists.
   */
  
  ptr = n_alResamplePull(e,inp, p);
  
  /*
   * construct our portion of the command list
   */
#ifndef N_MICRO
  aSetBuffer(ptr++, A_MAIN, *inp, AL_MAIN_L_OUT, FIXED_SAMPLE<<1);
  aSetBuffer(ptr++, A_AUX, AL_MAIN_R_OUT , AL_AUX_L_OUT ,
	     AL_AUX_R_OUT );
#endif
  
  if (e->em_first){
    e->em_first = 0;
    
    /*
     * Calculate derived parameters
     */
    e->em_ltgt = (e->em_volume * n_eqpower[e->em_pan]) >> 15;
    e->em_lratm = _getRate((f64)e->em_cvolL, (f64)e->em_ltgt,
			   e->em_segEnd, &(e->em_lratl));
    e->em_rtgt = (e->em_volume *
		  n_eqpower[N_EQPOWER_LENGTH - e->em_pan - 1]) >> 15;
    e->em_rratm = _getRate((f64)e->em_cvolR, (f64)e->em_rtgt, e->em_segEnd,
			   &(e->em_rratl));

#ifndef N_MICRO
    aSetVolume(ptr++, A_LEFT | A_VOL, e->em_cvolL, 0, 0);
    aSetVolume(ptr++, A_RIGHT | A_VOL, e->em_cvolR, 0, 0);
    aSetVolume(ptr++, A_LEFT  | A_RATE, e->em_ltgt, e->em_lratm, e->em_lratl);
    aSetVolume(ptr++, A_RIGHT | A_RATE, e->em_rtgt, e->em_rratm, e->em_rratl);
    aSetVolume(ptr++, A_AUX, e->em_dryamt, 0, e->em_wetamt);
    aEnvMixer (ptr++, A_INIT | A_AUX, osVirtualToPhysical(e->em_state));
  }
  else
    aEnvMixer(ptr++, A_CONTINUE | A_AUX, osVirtualToPhysical(e->em_state));
#else
#include "n_env_add01.c"
#endif  
  /*
   * bump the input buffer pointer
   */
  
  *inp += (FIXED_SAMPLE<<1);
  e->em_delta += FIXED_SAMPLE;
  
  return ptr;
}

#ifndef N_MICRO
#define EXP_MASK  0x7f800000
#define MANT_MASK 0x807fffff

static
  s16 _getRate(f64 vol, f64 tgt, s32 count, u16* ratel)
{
  s16         s;
  
  f64         invn = 1.0/count, eps, a, fs, mant;
  s32         i_invn, ex, indx;
  
#ifdef AUD_PROFILE
  lastCnt[++cnt_index] = osGetCount();
#endif
  
  if (count == 0){
    if (tgt >= vol){
      *ratel = 0xffff;
      return 0x7fff;
    }
    else{
      *ratel = 0;
      return 0;
    }
  }
  
  if (tgt < 1.0)
    tgt = 1.0;
  if (vol <= 0) vol = 1;	/* zero and neg values not allowed */
  
#define NBITS (3)
#define NPOS  (1<<NBITS)
#define NFRACBITS (30)
#define M_LN2		0.69314718055994530942
  /*
   * rww's parametric pow()
   Goal: compute a = (tgt/vol)^(1/count)
   
   Approach:
   (tgt/vol)^(1/count) =
   ((tgt/vol)^(1/2^30))^(2^30*1/count)
   
   (tgt/vol)^(1/2^30) ~= 1 + eps
   
   where
   
   eps ~= ln(tgt/vol)/2^30
   
   ln(tgt/vol) = ln2(tgt/vol) * ln(2)
   
   ln2(tgt/vol) = fp_exponent( tgt/vol ) +
   ln2( fp_mantissa( tgt/vol ) )
   
   fp_mantissa() and fp_exponent() are
   calculated via tricky bit manipulations of
   the floating point number. ln2() is
   approximated by a look up table.
   
   Note that this final (1+eps) value needs
   to be raised to the 2^30/count power. This
   is done by operating on the binary representaion
   of this number in the final while loop.
   
   Enjoy!
   */
  {
    f64 logtab[] = { -0.912537, -0.752072, -0.607683, -0.476438,
		       -0.356144, -0.245112, -0.142019, -0.045804  };
    
    i_invn = (s32) _ldexpf( invn, NFRACBITS );
    mant = _frexpf( tgt/vol, &ex );
    indx = (s32) (_ldexpf( mant, NBITS+1 ) ); /* NPOS <= indx < 2*NPOS */
    eps = (logtab[indx - NPOS] + ex) * M_LN2;
    eps /= _ldexpf( 1, NFRACBITS ); /* eps / 2^NFRACBITS */
    fs = (1.0 + eps);
    a = 1.0;
    while( i_invn ) {
      if( i_invn & 1 )
	a = a * fs;
      fs *= fs;
      i_invn >>= 1;
    }
  }
  
  a *= (a *= (a *= a));
  s = (s16) a;
  *ratel = (s16)(0xffff * (a - (f32) s));
  
#ifdef AUD_PROFILE
  PROFILE_AUD(rate_num, rate_cnt, rate_max, rate_min);
#endif
  return (s16)a;
  
}
#else
static
s16 _getRate(f64 vol, f64 tgt, s32 count, u16* ratel)
{
    s16         s, tmp;
    f64         invn, a, f;

#ifdef AUD_PROFILE
    lastCnt[++cnt_index] = osGetCount();
#endif
    
    if (count == 0){
        if (tgt >= vol){
            *ratel = 0xffff;
            return 0x7fff;
        }
        else{
            *ratel = 0;
            return 0x8000;
        }
    }

    invn = 1.0 / count;

    if (tgt < 1.0)
        tgt = 1.0;
    if (vol <= 0.0) vol = 1.0;	/* zero and neg values not allowed */

    a = (tgt - vol) * invn * 8.0;
    s = (s16)a;
    f = a - (f64)s;
    s -= 1;
    f += 1.0;
    tmp = (s16)f;
    s += tmp;
    f -= (f64)tmp;

#ifdef AUD_PROFILE
	PROFILE_AUD( rate_num, rate_cnt, rate_max, rate_min);
#endif

    *ratel = (u16)(0xffff * f);
    return s;
}
#endif

#ifndef N_MICRO
static
  f32 _getVol(f32 ivol, s32 samples, s16 ratem, u16 ratel)
{
  f32	        r, a;
  s32	      	i;
  
#ifdef AUD_PROFILE
  lastCnt[++cnt_index] = osGetCount();
#endif
  
  /*
   * Rate values are actually rate^8
   */
  samples >>=3;
  if (samples == 0){
    return ivol;
  }
  r = ((f32) (ratem<<16) + (f32) ratel)/65536;
  
  a = 1.0;
  for (i=0; i<32; i++){
    if( samples & 1 )
      a *= r;
    samples >>= 1;
    if (samples == 0)
      break;
    r *= r;
  }
  ivol *= a;
#ifdef AUD_PROFILE
  PROFILE_AUD(vol_num, vol_cnt, vol_max, vol_min);
#endif
  return ivol;
}
#else
static
s16 _getVol(s16 ivol, s32 samples, s16 ratem, u16 ratel)
{
    s32 tmp1;
#ifdef AUD_PROFILE
    lastCnt[++cnt_index] = osGetCount();
#endif
    
    samples >>= 3;
    if (samples == 0){
        return ivol;
    }

    tmp1 = ratel * samples;
    tmp1 >>= 16;
    tmp1 += ratem * samples;
    ivol += tmp1;

#ifdef AUD_PROFILE
    PROFILE_AUD(vol_num, vol_cnt, vol_max, vol_min);
#endif
    return ivol;
}
#endif
