/* $Id: saol_tables.c,v 1.6 1997/11/11 22:26:05 eds Exp $ */
/* $Log: saol_tables.c,v $
 * Revision 1.6  1997/11/11  22:26:05  eds
 * Fixed bug in harm_phase.c
 *
 * Revision 1.5  1997/11/10  23:01:44  eds
 * Changed directory separators a bit.
 *
 * Revision 1.4  1997/11/05  20:45:19  eds
 * Fixed typo from check-in.
 *
 * Revision 1.3  1997/11/05  20:09:20  eds
 * Fixed size bugs in step, data, lineseg.
 * */
/*********************************************************************
  
  This software module was originally developed by
  
  Eric D. Scheirer (MIT Media Laboratory)
  
  in the course of development of the MPEG-2 NBC/MPEG-4 Audio standard
  ISO/IEC 13818-7, 14496-1,2 and 3. This software module is an
  implementation of a part of one or more MPEG-2 NBC/MPEG-4 Audio tools
  as specified by the MPEG-2 NBC/MPEG-4 Audio standard.  ISO/IEC gives
  users of the MPEG-2 NBC/MPEG-4 Audio standards free license to this
  software module or modifications thereof for use in hardware or
  software products claiming conformance to the MPEG-2 NBC/ MPEG-4 Audio
  standards. Those intending to use this software module in hardware or
  software products are advised that this use may infringe existing
  patents. The original developer of this software module and his/her
  company, the subsequent editors and their companies, and ISO/IEC have
  no liability for use of this software module or modifications thereof
  in an implementation.
  
  This software module is hereby released into the public domain.
  
  ***********************************************************************/

/* saol_tables.c: Table utilities and core table generators */

#include <string.h>
#include "saol_interp.h"
#include <math.h>
#include "aifif.h"
#include "saol.h"

#ifdef _WIN32
#define DIRSEP '\\'
#else
#define DIRSEP '/'
#endif

#define NUM_CORE_TABLEGEN 18

extern double drand48();
extern struct cmdinfo cmd; /* access to cmdline parameters */

double i0(double x);
struct core_tg_struct {
  char *name;			/* the name of the generator */
  table_storage *(*func)(actparam *, int); /* the code which implements it */
};

table_storage *ctg_soundfile(actparam *pf, int);
table_storage *ctg_data(actparam *pf, int);
table_storage *ctg_random(actparam *pf, int);
table_storage *ctg_step(actparam *pf, int);
table_storage *ctg_lineseg(actparam *pf, int);
table_storage *ctg_expseg(actparam *pf, int);
table_storage *ctg_cubicseg(actparam *pf, int);
table_storage *ctg_spline(actparam *pf, int);
table_storage *ctg_polynomial(actparam *pf, int);
table_storage *ctg_window(actparam *pf, int);
table_storage *ctg_harm(actparam *pf, int);
table_storage *ctg_harm_phase(actparam *pf, int);
table_storage *ctg_harm_dc(actparam *pf, int);
table_storage *ctg_buzz(actparam *pf, int);
table_storage *ctg_logbessel(actparam *pf, int);
table_storage *ctg_cheby_poly(actparam *pf, int);
table_storage *ctg_concat(actparam *pf, int);
table_storage *ctg_empty(actparam *pf, int);

struct core_tg_struct core_tablegens[NUM_CORE_TABLEGEN] = {
  {"soundfile",ctg_soundfile},
  {"data",ctg_data},
  {"random",ctg_random},
  {"step",ctg_step},
  {"lineseg",ctg_lineseg},
  {"expseg",ctg_expseg},
  {"cubicseg",ctg_cubicseg},
  {"spline",ctg_spline},
  {"polynomial",ctg_polynomial},
  {"window",ctg_window},
  {"harm",ctg_harm},
  {"harm_phase",ctg_harm_phase},
  {"harm_dc",ctg_harm_dc},
  {"buzz",ctg_buzz},
  {"logbessel",ctg_logbessel},
  {"cheby_poly",ctg_cheby_poly},
  {"concat",ctg_concat},
  {"empty",ctg_empty}};

int get_table_size(table_storage *t) {
  return(t->size);
}

double get_table_value(table_storage *t, int idx) {
  char s[234];
  if (idx >= t->size || idx < 0) {
    sprintf(s,"Table index out of bounds: '%s', index %d, max %d.",
	    t->name,idx,t->size);
    runtime(s);
  }
  return(t->d[idx]);
}

void set_table_value(table_storage *t, int idx, double val) {
  char s[234];
  
  if (idx >= t->size || idx < 0) {
    sprintf(s,"Table index out of bounds: '%s', index %d, max %d.",
	    t->name,idx,t->size);
    runtime(s);
  }
  t->d[idx] = val;
}



table_storage *gen_table(char *gen, actparam *pf, int pf_ct) {
  int i;
  char s[854];

  for (i=0;i!=NUM_CORE_TABLEGEN;i++)
    if (!strcmp(gen,core_tablegens[i].name))
      return(core_tablegens[i].func(pf,pf_ct));

  sprintf(s,"Unknown table generator '%s'",gen);
  runtime(s);
  return NULL;
}

table_storage *new_table(int size) {
  table_storage *t;

  PROT_MAL(t,table_storage,new_table);
  
  if (!(t->d=(double *)calloc(sizeof(double),size))) 
    runtime("Couldn't allocate space in new_table().");

  t->size = size;
  t->srate = 0;
  t->loop = 0;
  t->loopend = 0;
  t->base = 0;

  return(t);
}

/* return the value from the byte buffer for 8/16/24/32-bit files */
/* fractional bit widths not supported */

double bufval(char *buf,int frame,int chan,int nchan,int nbits) {

  if (nbits == 16)
    return((double)*(short *)&buf[frame * nchan * 2 + chan * 2]);

  if (nbits == 32)
    return((double)*(int *)&buf[frame * nchan * 2 + chan * 2]);

  if (nbits == 8)
    return((double)*(char *)&buf[frame * nchan *2 + chan * 2]);

  return 0;
}

table_storage *ctg_soundfile(actparam *pf, int pf_ct) {
  int size;
  char fn[800],s[800];
  AIF_STRUCT *aifin = aifNew();
  table_storage *t;
  int skip=0, chan=0, nchan, nbits, ct, done = 0, retct, i, j;
  double samp,scale;
  char *buf;
  long param[24];

  if (pf_ct < 2) {
    runtime("Bad call to 'soundfile' tablegen.\n");
  }
  
  sprintf(fn,"%s%c%s",cmd.temp,DIRSEP,pf[1].ref);
  if (aifOpenRead(aifin,fn)) {
    sprintf(s,"Couldn't open '%s' for table read.",fn);
    runtime(s);
  }

  if (pf_ct > 2) {
    skip = ((int)(pf[2].val + 0.5));
    if (pf_ct > 3)
      chan = ((int)(pf[3].val + 0.5));
  }
  
  if (pf[0].val == -1) {
    param[0] = AIF_P_NFRAMES;
    aifGetParams(aifin,param,2);
    size = param[1] - skip;
  }
  else
    size = ((int)(pf[0].val +0.5));

  t = new_table(size);
  t->size = size;

  param[0] = AIF_P_CHANNELS;
  param[2] = AIF_P_SAMPSIZE;
  param[4] = AIF_P_SAMPRATE;
  aifGetParams(aifin,param,6);
  nchan = param[1];
  nbits = param[3];
  t->srate = (int)*(float *)&param[5];

  scale = (double)(2 << (nbits-2));
  
  buf = (char *)malloc(nchan * nbits/8 * 1024); /* 1K frame buffer */

  aifFrameSeek(aifin, skip);

  ct = 0;
  while (!done) {
    retct = aifReadFrames(aifin,buf,1024); /* number actually read */
    if (retct < 1024 || ct+retct >= size) {
      done = 1;
    }
    for (i=0;i!=retct && ct < size;i++,ct++) {
      if (!chan) {		/* average all channels */
	samp = 0;
	for (j=0;j!=nchan;j++)
	  samp += bufval(buf,i,j,nchan,nbits);
	samp = samp/nchan;
      }
      else
	samp = bufval(buf,i,chan,nchan,nbits);

      set_table_value(t,ct,samp/scale);
    }
  }

  aifClose(aifin);
  aifFree(aifin);

  printf("Read %d samples from soundfile '%s'\n",ct,fn);
  return(t);
}

table_storage *ctg_data(actparam *pf, int pf_ct) {
  table_storage *t;
  int i = 0;

  if (!pf[0].val) 
    runtime("Invalid size for generator 'data'.");

  if (pf[0].val == -1)
    pf[0].val = pf_ct;

  t = new_table((int)(pf[0].val + 0.5));

  for(i=1;i!=pf[0].val;i++)
    set_table_value(t,i-1,pf[i].val);

  return(t);
}

table_storage *ctg_random(actparam *pf, int pf_ct) {
  table_storage *t;
  int i = 0;
  int which = 0,ct;
  double x, y, p1, p2;
  
  if (!pf[0].val)
    runtime("Invalid size for generator 'random'.");

  t = new_table((int)(pf[0].val + 0.5));
  which = (int)pf[1].val;
  switch (which) {
  case 1:			/* uniform */
    if (pf_ct < 4)
      runtime("Need min and max for uniform random tablegen.");
    p1 = pf[2].val;
    p2 = pf[3].val;

    for (i=0;i!=t->size;i++) {
      x = drand48();

      set_table_value(t,i,x * (p2-p1)+p1);
    }

    break;
  case 2:			/* linear */
    if (pf_ct < 4)
      runtime("Need min and max for linear random tablegen.");
    p1 = pf[2].val;
    p2 = pf[3].val;

    for (i=0;i!=t->size;i++) {
      x = drand48();
      y = drand48();

      set_table_value(t,i,MAX(x,y) * (p2-p1) + p1);
    }
    break;
  case 3:			/* exp */
    if (pf_ct < 3)
      runtime("Need mean for exponential random tablegen.");
    p1 = pf[2].val;
    
    for (i=0;i!=t->size;i++) {
      x = drand48();

      set_table_value(t,i,-log(x)*p1);
    }
    break;
  case 4:
    if (pf_ct < 4)
      runtime("Need mean and var for Gaussian random tablegen.");
    p1 = pf[2].val;
    p2 = pf[3].val;

    for (i=0;i!=t->size;i++) {
      x = drand48();
      y = drand48();

      set_table_value(t,i,sqrt(-2 * log(x)) * cos(2 * PI * y) * sqrt(p1) + p2);
    }
 
    break;
  case 5:
    if (pf_ct < 3)
      runtime("Need mean for exponential random tablegen.");
    p1 = pf[2].val;
    
    for (i=0;i<t->size;) {
      x = drand48();

      for (ct=0;i<t->size && ct < floor(-log(drand48()) * p1);ct++,i++)
	set_table_value(t,i,0);
      set_table_value(t,i++,1);
    }
    break;
  default:
    runtime("Unknown random tablegen.");
  }

  return t;
}

table_storage *ctg_step(actparam *pf, int pf_ct) {
  table_storage *t;
  int ct = 0,i;
  int end,lastxval,xval;
  actparam *p;
  double yval;

  if (!pf[0].val)
    runtime("Invalid size for generator 'step'.");

  if (pf[0].val == -1)
    pf[0].val = pf[pf_ct-1].val+1;
  
  t = new_table((int)(pf[0].val + 0.5));
  
  end = (int)(floor(pf[pf_ct-1].val+0.5));
  lastxval = 0;

  if (pf_ct < 3)
    runtime("Not enough y-values for generator 'step'.");
  
  for (i=2;i!=pf_ct;i++) { /* start with first y-value */
    yval = pf[i].val;
    
    if (++i == pf_ct) 
      runtime("Not enough x-values for generator 'step'.");
    xval = (int)(pf[i].val + 0.5);
    if (xval >= get_table_size(t))
      runtime("Index out-of-bounds for generator 'step'.");
    if (xval < lastxval)
      runtime("'step' x-values must be non-decreasing.");
    for (ct=lastxval;ct!=xval;ct++)
      set_table_value(t,ct,yval);
    lastxval = xval;
  }
      
  return(t);
}

table_storage *ctg_lineseg(actparam *pf, int pf_ct) {

  table_storage *t;
  int i,ct = 0;
  int end,lastxval,xval;
  actparam *p;
  double yval,lastyval;

  if (!pf[0].val)
    runtime("Invalid size for generator 'lineseg'.");

  if (pf[0].val == -1)
    pf[0].val = pf[pf_ct-1].val+1;
  
  t = new_table((int)(pf[0].val + 0.5));
  
  end = (int)floor(pf[pf_ct-1].val+0.5);
  if (pf_ct < 4)
    runtime("Need at least two x-y pairs for generator 'lineseg'");
  
  lastxval = 0;
  lastyval = pf[2].val;
  
  for (i=3;i!=pf_ct;i++) { /* start with second x-value */
    xval = (int)(pf[i].val + 0.5);
    if (++i == pf_ct)
      runtime("Not enough y-values for generator 'lineseg'");
    yval = pf[i].val;
    if (xval >= get_table_size(t))
      runtime("Index out-of-bounds for generator 'lineseg'.");
    if (xval < lastxval)
      runtime("'lineseg' x-values must be non-decreasing.");
    for (ct=lastxval;ct!=xval;ct++)
      set_table_value(t,ct,((yval-lastyval)/(xval-lastxval) * (ct-lastxval) +
			    lastyval));
    lastxval = xval;
    lastyval = yval;
  }
      
  return(t);

}

table_storage *ctg_expseg(actparam *pf, int pf_ct) {
  interror("Table generator not implemented.\n");
  return 0;
}

table_storage *ctg_cubicseg(actparam *pf, int pf_ct) {
  interror("Table generator not implemented.\n");
  return 0;
}

table_storage *ctg_spline(actparam *pf, int pf_ct) {
  interror("Table generator not implemented.\n");
  return 0;
}

table_storage *ctg_polynomial(actparam *pf, int pf_ct) {
  interror("Table generator not implemented.\n");
  return 0;
}

table_storage *ctg_window(actparam *pf, int pf_ct) {
  table_storage *t;
  actparam *p;
  int i,n;
  double p1, p2,alpha,std,var,sum;

  if (!pf[0].val)
    runtime("Invalid size for generator 'window'.");
  t = new_table((int)(pf[0].val + 0.5));

  if (pf_ct < 2)
    runtime("Must provide window type for generator 'window'.");

  switch ((int)pf[1].val) {
  case 1:			/* hamming */
    for (i=0;i!=t->size;i++) 
      set_table_value(t,i,.52 - .46*cos(2*PI*i/(t->size-1)));
    break;

  case 2:			/* hanning */
    for (i=0;i!=t->size;i++)
      set_table_value(t,i,.5*(1-cos(2*PI*(i+1)/(t->size+1))));
    break;

  case 3:			/* bartlett */
    if (t->size == (t->size/2)*2) { /* even length */
      for (i=0;i!=t->size/2;i++)
	set_table_value(t,i,2.0*i/(t->size-1.0));
      for (;i!=t->size;i++)
	set_table_value(t,i,2.0*((double)t->size-i)/(t->size-1.0));
    }
    else {
      for (i=0;i!=(int)ceil(t->size/2.0);i++)
	set_table_value(t,i,2*i/(t->size-1));
      for (;i!=t->size;i++)
	set_table_value(t,i,2.0*(t->size-i-1.0)/(t->size-1.0));
    }
    break;

  case 4:			/* gaussian */
    std = t->size/6.0;
    var = std*std;
    for (i=0;i!=t->size;i++)
      set_table_value(t,i,exp(-(i-t->size/2.0)*(i-t->size/2.0) / 2.0 / var));
    break;

  case 5:			/* kaiser */
    if (pf_ct < 3)
      runtime("Must provide parameter for Kaiser window.");
    p1 = pf[2].val;	/* == "beta" parameter */
    alpha = t->size/2;
    for (i=0;i!=t->size;i++)
      set_table_value(t,i,i0(p1 * pow(1-pow((i-alpha)/alpha,2.0),0.5)) / i0(p1));
    break;


  case 6:			/* rectangular */
    for (i=0;i!=t->size;i++)
      set_table_value(t,i,1);
    break;
  }

	
  return(t);
}

table_storage *ctg_harm(actparam *pf, int pf_ct) {
  table_storage *t;
  actparam *p;
  int harm,i;
  double val;
  
  if (!pf[0].val)
    runtime("Invalid size for generator 'harm'.");

  t = new_table((int)(pf[0].val +0.5));

  for (i=0;i!=get_table_size(t);i++) {
    val = 0;
    for (harm=1;harm < pf_ct;harm++)
      val += sin((double)i/get_table_size(t) * 2 * PI * harm) * pf[harm].val;
    set_table_value(t,i,val);
  }
  return t;
}

table_storage *ctg_harm_phase(actparam *pf, int pf_ct) {
  table_storage *t;
  actparam *p;
  double val,wt,ph;
  int harm,i,ct;
  
  if (!pf[0].val)
    runtime("Invalid size for generator 'harm_phase'.");

  t = new_table((int)(pf[0].val +0.5));

  for (i=0;i!=get_table_size(t);i++) {
    val = 0;
    for (harm=1,ct=1;ct < pf_ct;harm++,ct++) {
      wt = pf[ct].val;
      if (++ct == pf_ct)
	runtime("Not enough phase values for generator 'harm_phase'.");
      ph = pf[ct].val;
      val += sin(((double)i/get_table_size(t)+(ph/2/PI)) * 2 * PI * harm) * wt;
    }
    set_table_value(t,i,val);
  }
  return t;
}

table_storage *ctg_harm_dc(actparam *pf, int pf_ct) {
  table_storage *t;
  actparam *p;
  double val,harm,wt,ph,dc;
  int i,ct;
  
  if (!pf[0].val)
    runtime("Invalid size for generator 'harm_dc'.");

  t = new_table((int)(pf[0].val +0.5));

  for (i=0;i!=get_table_size(t);i++) {
    val = 0;
    for (ct=1;ct < pf_ct;ct++) {
      harm = pf[ct].val;
      if (++ct == pf_ct )
	runtime("Not enough values (4 per partial) for generator 'harm_dc'.");
      wt = pf[ct].val;

      if (++ct == pf_ct)
	runtime("Not enough values (4 per partial) for generator 'harm_dc'.");
      ph = pf[ct].val;

      if (++ct == pf_ct)
	runtime("Not enough values (4 per partial) for generator 'harm_dc'.");
      dc = pf[ct].val;
      
      val += sin(((double)i/get_table_size(t)+ph/2/PI) * 2 * PI * harm) * wt + dc;
    }
    set_table_value(t,i,val);
  }
  return t;
}

table_storage *ctg_buzz(actparam *pf, int pf_ct) {
  interror("Table generator not implemented.\n");
  return 0;
}

table_storage *ctg_logbessel(actparam *pf, int pf_ct) {
  interror("Table generator not implemented.\n");
  return 0;
}

table_storage *ctg_cheby_poly(actparam *pf, int pf_ct) {
  interror("Table generator not implemented.\n");
  return 0;
}

table_storage *ctg_concat(actparam *pf, int pf_ct) {
  interror("Table generator not implemented.\n");
  return 0;
}

table_storage *ctg_empty(actparam *pf, int pf_ct) {
  table_storage *t;
  actparam *p;
  double val,harm,wt,ph,dc;
  int k,i;
  
  if (pf[0].val <= 0)
    runtime("Invalid size for generator 'empty'.");

  k = (int)(pf[0].val + 0.5);
  t = new_table(k);
  for (i=0;i<k;i++)
    set_table_value(t,i,0);

  return t;
}

double i0(double x) {
  /* Opp/Schaefer p. 457 */

  double ds = 1, d = 0, s = 1;

  while (fabs(ds)> 1e-12) {
    d += 2;
    ds *= x*x/d/d;
    s += ds;
  }
  return s;
}
