/**********************************************************************
augen -- generate audio signals

$Id: augen.c,v 1.14 2003/01/19 23:17:09 purnhage Exp $

Source file: augen.c

(c) Heiko Purnhagen 1998-2003
e-mail: purnhage@tnt.uni-hannover.de
WWW:    http://www.tnt.uni-hannover.de/~purnhage/

This program is available under the terms of the GNU General Public
License. See file COPYING for further details. All usual disclaimers
apply!

Authors:
HP    Heiko Purnhagen, Uni Hannover <purnhage@tnt.uni-hannover.de>

Changes:
26-may-98   HP      based on gen-sweep.c
10-jun-98   HP/NM   double
06-nov-98   HP/NM   fixed bug in rnd gen
30-dec-98   HP      ...
28-jan-99   HP      debug output on stderr
29-jan-99   HP      generator parameter from stdin, permit empty para. sets
18-feb-99   HP      progress in seconds
01-Nov-2001 HP      fixed duration (proper rounding)
17-Apr-2002 HP      now event-driven -> much faster ...
**********************************************************************/


#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <ctype.h>
#include <math.h>

#include "common.h"
#include "cmdline.h"
#include "audio.h"


/* ---------- declarations ---------- */

#define PROGVER "augen V2.0 17-Apr-2002"
#define CVSID "$Id: augen.c,v 1.14 2003/01/19 23:17:09 purnhage Exp $"
#ifndef DISTVER		/* set in Makefile */
#define DISTVER "<unknown>"
#endif

#ifndef MSVC
#define min(a,b) ((a) < (b) ? (a) : (b))
#define max(a,b) ((a) > (b) ? (a) : (b))
#endif

#define STRLEN 255

#define MAXCHAN 10	/* also hardwired in AddPara() */
#ifndef PI
#define PI 3.14159265358979323846264338327950288
#endif


/* ---------- variables ---------- */

static int *varArgIdx;
static double sampFreq;
static int numChan;
static double amplFact;
static double start,duration;
static char *genName,*auName,*genExt,*auExt;
static int durationUsed,genNameUsed,auNameUsed;
static int sampFreqUsed,numChanUsed,amplFactUsed;
static float bufTime;
static int debugLevel;
static int audioDebugLevel;
 
static CmdLinePara paraList[] = {
  {&varArgIdx,NULL,"<generator parameter set>"},
  {NULL,NULL,NULL}
};

static CmdLineSwitch switchList[] = {
  {"h",NULL,NULL,NULL,NULL,"print help"},
  {"s",&sampFreq,"%lf","8000",&sampFreqUsed,"sampling frequency [Hz]"},
  {"c",&numChan,"%d","1",&numChanUsed,"number of channels"},
  {"a",&amplFact,"%lf","32767",&amplFactUsed,"amplitude factor"},
  {"ts",&start,"%lf","0",NULL,"signal start time [sec]"},
  {"td",&duration,"%lf",NULL,&durationUsed,"signal duration [sec]\n"
   "(dflt: complete signal)"},
  {"g",&genName,"%s",NULL,&genNameUsed,
   "generator parameter file name or - for stdin"},
  {"o",&auName,"%s",NULL,&auNameUsed,
   "audio file name or - for stdout\n"
   "(dflt: as generator parameter file)"},
  {"eg",&genExt,"%s",".gen",NULL,
   "generator parameter file extension\n"},
  {"ea",&auExt,"%s",".au",NULL,
   "audio file extension\n"
   "supported file formats: .au, .snd, .wav, .aif, .raw\n\b"},
  {"b",&bufTime,"%f","0.2",NULL,
   "buffer time"},
  {"d",&debugLevel,"%d","0",NULL,"debug level"},
  {"da",&audioDebugLevel,"%i","0",NULL,"audio file debug level"},
  {NULL,NULL,NULL,NULL,NULL,NULL}
};


typedef struct GenParaStruct GenPara;

struct GenParaStruct {
  char wave;
  double ts,td;
  double fs,fe,p;
  double as[MAXCHAN],ae[MAXCHAN];
  double phi;
  GenPara *next;
  GenPara *active;
};



/* ---------- functions ---------- */

void AddPara (GenPara **root, char *para)
{
  GenPara *tmp;
  char *c;
  int i;

  if ((c=strchr(para,'\n')))
    *c = '\0';
  if (*para=='\0')
    return;
  if (!strchr("sutqpSUTQPnfca%",para[0]))
    CommonExit(1,"parameter set error: unknown type in line\n\"%s\"",para);
  if (para[0]=='%')
    return;
  if (para[0]=='f') {
    if (!sampFreqUsed)
      if (sscanf(para,"%*c %lf",&sampFreq) != 1)
	CommonExit(1,"parameter set error: wrong format in line\n\"%s\"",para);
    return;
  }
  if (para[0]=='c') {
    if (!numChanUsed)
      if (sscanf(para,"%*c %d",&numChan) != 1)
	CommonExit(1,"parameter set error: wrong format in line\n\"%s\"",para);
    return;
  }
  if (para[0]=='a') {
    if (!amplFactUsed)
      if (sscanf(para,"%*c %lf",&amplFact) != 1)
	CommonExit(1,"parameter set error: wrong format in line\n\"%s\"",para);
    return;
  }
  
  if (debugLevel >= 2)
    fprintf(stderr,"AddPara: %s\n",para);

  if (!(tmp=(GenPara*)malloc(sizeof(GenPara))))
    CommonExit(1,"error allocating memory");
  for (i=0; i<MAXCHAN; i++)
    tmp->as[i] = tmp->ae[i] = 0;
  if (para[0]=='n') {
    tmp->fs = tmp->fe = tmp->p = 0;
    if (sscanf(para,"%c %lf %lf"
	       " %lf %lf %lf %lf %lf %lf %lf %lf %lf %lf"
	       " %lf %lf %lf %lf %lf %lf %lf %lf %lf %lf",
	       &tmp->wave,&tmp->ts,&tmp->td,
	       &tmp->as[0],&tmp->ae[0],&tmp->as[1],&tmp->ae[1],
	       &tmp->as[2],&tmp->ae[2],&tmp->as[3],&tmp->ae[3],
	       &tmp->as[4],&tmp->ae[4],&tmp->as[5],&tmp->ae[5],
	       &tmp->as[6],&tmp->ae[6],&tmp->as[7],&tmp->ae[7],
	       &tmp->as[8],&tmp->ae[8],&tmp->as[9],&tmp->ae[9]) < 5)
      CommonExit(1,"parameter set error: wrong format in line\n\"%s\"",para);
  }
  else {
    if (sscanf(para,"%c %lf %lf %lf %lf %lf"
	       " %lf %lf %lf %lf %lf %lf %lf %lf %lf %lf"
	       " %lf %lf %lf %lf %lf %lf %lf %lf %lf %lf",
	       &tmp->wave,&tmp->ts,&tmp->td,&tmp->fs,&tmp->fe,&tmp->p,
	       &tmp->as[0],&tmp->ae[0],&tmp->as[1],&tmp->ae[1],
	       &tmp->as[2],&tmp->ae[2],&tmp->as[3],&tmp->ae[3],
	       &tmp->as[4],&tmp->ae[4],&tmp->as[5],&tmp->ae[5],
	       &tmp->as[6],&tmp->ae[6],&tmp->as[7],&tmp->ae[7],
	       &tmp->as[8],&tmp->ae[8],&tmp->as[9],&tmp->ae[9]) < 8)
      CommonExit(1,"parameter set error: wrong format in line\n\"%s\"",para);
    if (strchr("SUTQP",tmp->wave)) {
      tmp->fs = exp(log(2.)*(tmp->fs-69)/12)*440.;
      tmp->fe = exp(log(2.)*(tmp->fe-69)/12)*440.;
      tmp->wave = tolower(tmp->wave);
    }
  }
  if (!durationUsed && start+duration<tmp->ts+tmp->td)
    duration = tmp->ts+tmp->td-start;
  tmp->p /= 360.;
  tmp->phi = -1;
  tmp->next = *root;
  *root = tmp;
}


double GenUpdate (GenPara *tmp, GenPara **act, double t)
{
  double tNext;

  tNext = t;
  *act = NULL;
  while (tmp) {
    if ((tNext == t || tNext > tmp->ts+tmp->td ) && tmp->ts+tmp->td > t)
      tNext = tmp->ts+tmp->td;
    if (tNext > tmp->ts && tmp->ts > t)
      tNext = tmp->ts;
    if (t >= tmp->ts && t < tmp->ts+tmp->td) {
      tmp->active = *act;
      *act = tmp;
    }
    tmp = tmp->next;
  }
  return tNext;
}

void GenSample (GenPara *tmp, double t, float **buf, int n)
{
  int c;
  double phiNew,a,x;

  for (c=0; c<numChan; c++)
    buf[c][n] = 0;

  while (tmp) {
    if (t >= tmp->ts && t < tmp->ts+tmp->td) {
      phiNew = tmp->p+
	tmp->fs*(t-tmp->ts)+
	(tmp->fe-tmp->fs)/tmp->td/2*(t-tmp->ts)*(t-tmp->ts);
      phiNew -= (int)phiNew;
      if (phiNew < 0)
	phiNew += 1;
      x = 0;
      switch (tmp->wave) {
      case 's': 
	x = sin(2*PI*phiNew);
	break;
      case 'u': 
	x = -1+2*phiNew;
	break;
      case 't': 
	x = (phiNew>=0.5) ? (3-4*phiNew) : (-1+4*phiNew);
	break;
      case 'q': 
	x = (phiNew>=0.5) ? -1 : 1;
	break;
      case 'p': 
	x = ((tmp->phi >= 0) &&
	     (tmp->phi-phiNew >= 0.5 || tmp->phi-phiNew <= -0.5)) ? 1 : 0;
	break;
      case 'n': 
	x = (random1()/(double)RND_MAX-0.5)*2;
	break;
      default:
	CommonExit(1,"internal error");
      }
      for (c=0; c<numChan; c++) {
	a = tmp->as[c]+(tmp->ae[c]-tmp->as[c])/tmp->td*(t-tmp->ts);
	buf[c][n] += (float)(amplFact*a*x);
	if (debugLevel >= 3)
	  fprintf(stderr,"t=%f n=%d c=%d a=%f x=%f phi=%f\n",t,n,c,a,x,phiNew);
      }
      tmp->phi = phiNew;
    }
    else
      CommonExit(2,"internal error");
    tmp = tmp->active;
  }
}


/* ---------- main ---------- */

int main (int argc, char *argv[])
{
  char *progName = "<no program name>";
  int result;
  char genFileName[STRLEN], auFileName[STRLEN];
  char *auFormat;
  int paraIdx;
  FILE *genFile;  
  AudioFile *auFile;
  char str[STRLEN];
  int i;
  GenPara *genParaRoot;
  GenPara *act;
  double tUpdt;
  GenPara *p;
  float **buf;
  int n;
  unsigned long nn;
  double t;
  int bufLen;

  /* evaluate command line  */
  CmdLineInit(0);
  result = CmdLineEval(argc,argv,paraList,switchList,1,&progName);
  if (result) {
    if (result==1) {
      fprintf(stderr,
	      "%s: %s\n"
	      "%s\n",
	      progName,PROGVER "   " DISTVER,CVSID);
      CmdLineHelp(progName,paraList,switchList,stderr);
      fprintf(stderr,
	      "\n"
	      "generator parameter set format:\n"
	      "\n"
	      "  <wave> <ts> <td> <fs> <fe> <p> <a1s> <a1e> [<a2s> <a2e> ...]\n"
	      "    wave: s=sine u=saw-up t=triangle q=square p=pulse\n"
	      "    ts: start time [sec]\n"
	      "    td: duration [sec]\n"
	      "    fs: start frequency [Hz]\n"
	      "    fe: end frequency [Hz]\n"
	      "    p: start phase [deg]\n"
	      "    a1s: channel 1 start amplitude [FS]\n"
	      "    a1e: channel 1 end amplitude [FS]\n"
	      "    a2s: channel 2 start amplitude [FS]\n"
	      "    a2e: ...\n"
	      "  if wave = S, U, T, Q, P then frequencies in MIDI notes:\n"
	      "     24 = C1 =   32.70 Hz\n"
	      "     60 = c1 =  261.63 Hz\n"
	      "     69 = a1 =  440.00 Hz\n"
	      "    108 = c5 = 4186.01 Hz\n"
	      "\n"
	      "  n <ts> <td> <a1s> <a1e> [<a2s> <a2e> ...]\n"
	      "    noise (full BW, uniform distribution [-a,a])\n"
	      "\n"
	      "additional control parameters:\n"
	      "  f <sampling frequency [Hz]>\n"
	      "  c <number of channels>\n"
	      "  a <amplitude factor>\n"
	      "  %% comment\n"
	      "\n");
      exit (1);
    }
    else
      CommonExit(1,"command line error (\"-h\" for help)");
  }

  if (debugLevel)
    fprintf(stderr,"%s: %s\n",progName,PROGVER "   " DISTVER);
  if (debugLevel>1)
    fprintf(stderr,"%s\n",CVSID);
  
  /* check command line options */
  if (!durationUsed)
    duration = -1;
  if (sampFreq < 0)
    CommonExit(1,"sampling frequency < 0");
  if (numChan < 0)
    CommonExit(1,"number of channels < 0");
  if (durationUsed && duration < 0)
    CommonExit(1,"duration < 0");
  if (!auNameUsed && !genNameUsed)
    CommonExit(1,"audio file name not specified");
  
  /* init data structures */
  genParaRoot = NULL;

  /* process all parameter sets on command line */
  paraIdx = 0;
  while (varArgIdx[paraIdx] >= 0) {
    AddPara(&genParaRoot,argv[varArgIdx[paraIdx]]);
    paraIdx++;
  }

  /* process parameter file */
  if (genNameUsed) {
    if (ComposeFileName(genName,0,NULL,genExt,genFileName,STRLEN))
      CommonExit(1,"composed file name too long");

    if (debugLevel)
      fprintf(stderr,"%s: reading %s\n",progName,genFileName);

    if (strcmp(genFileName,"-"))
      genFile = fopen(genFileName,"r");
    else
      genFile = stdin;
    if (!genFile)
      CommonExit(1,"error opening generator parameter file %s",genFileName);
    i = 1;
    do {
      if (!fgets(str,STRLEN,genFile)) {
	if ((i=!feof(genFile)))
	  CommonExit(1,"error reading generator parameter file %s",
		     genFileName);
      }
      else
	AddPara(&genParaRoot,str);
    } while (i);
    if (genFile!=stdin)
      fclose(genFile);
  }
  
  i = 0;
  p = genParaRoot;
  while (p) {
    p = p->next;
    i++;
  }
  if (debugLevel >= 2) {
    fprintf(stderr,"%s: sampFreq=%4.2f  numChan=%d  numPara=%d\n",
	    progName,sampFreq,numChan,i);
    fprintf(stderr,"%s: start=%8.6f  duration=%8.6f amplFact=%4.2f\n",
	    progName,start,duration,amplFact);
  }

  /* open audio file */
  if (auNameUsed) {
    if (ComposeFileName(auName,0,NULL,auExt,auFileName,STRLEN))
      CommonExit(1,"composed file name too long");
  }
  else
    if (ComposeFileName(genFileName,1,NULL,auExt,auFileName,STRLEN))
      CommonExit(1,"composed file name too long");

  if (strcmp(auFileName,"-") == 0)
    auFormat = auExt;
  else {
    auFormat = auFileName+strlen(auFileName);
      do
        auFormat--;
      while (auFormat>auFileName && *auFormat!='.');
    }

  if (debugLevel)
    fprintf(stderr,"%s: generating %s\n",progName,auFileName);
  
  AudioInit(NULL,0,audioDebugLevel);
  if (!(auFile=AudioOpenWrite(auFileName,auFormat,numChan,(float)sampFreq)))
    CommonExit(1,"error opening audio file %s",auFileName);

  /* generate audio file */
  bufLen = (long)(sampFreq*bufTime+.5);
  bufLen = max(bufLen,1);
 if (!(buf=(float**)malloc(sizeof(float*)*numChan)))
    CommonExit(1,"error allocating memory");
  for (i=0; i<numChan; i++)
    if (!(buf[i]=(float*)malloc(sizeof(float)*bufLen)))
      CommonExit(1,"error allocating memory");
  n = 0;
  nn = 0;
  t = 0;
  tUpdt = GenUpdate(genParaRoot,&act,t+0.5/sampFreq)-0.5/sampFreq;
  if (debugLevel) {
    fprintf(stderr,(debugLevel<=2)?"\r%8.3f ":"%8.3f\n",
	    start+t);
    fflush(stderr);
  }
  while (t < duration-0.5/sampFreq) {
    if (t >= tUpdt)
      tUpdt = GenUpdate(genParaRoot,&act,t+0.5/sampFreq)-0.5/sampFreq;
    GenSample(act,start+t,buf,n);
    n++;
    nn++;
    t = ((double)nn)/sampFreq;
    if (n==bufLen) {
      AudioWriteData(auFile,buf,n);
      n = 0;
      if (debugLevel) {
	fprintf(stderr,(debugLevel<=2)?"\r%8.3f ":"%8.3f\n",
		start+t);
	fflush(stderr);
      }
    }
  }
  AudioWriteData(auFile,buf,n);
  if (debugLevel && debugLevel<=2) {
    fprintf(stderr," \n");
    fflush(stderr);
  }

  /* clean up ... */
  AudioClose(auFile);

  if (debugLevel >= 1)
    fprintf(stderr,"%s: finished\n",progName);

  return 0;
}

/* end of augen.c */
