//||
//||	`rhybag' - Rhythmic BinAural Generator
//||
//||	(c) 1999 Jim Peters <jim@aguazul.demon.co.uk>.  All Rights Reserved.
//||	For latest version see <http://www.aguazul.demon.co.uk/bagen/>.
//||	Released under the GNU GPL.  Use at your own risk.
//||
//||	" This program is free software; you can redistribute it and/or modify
//||	  it under the terms of the GNU General Public License as published by
//||	  the Free Software Foundation, version 2.
//||
//||	  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.  See the
//||	  GNU General Public License for more details. "
//||
//||	If you really don't have a copy of the GNU GPL, I'll send you one.
//||	

//
//	Read and compile the sequence-description file, processing it
//	as we go.
//
//	The output filename may be 0 to output to STDOUT, or
//	"/dev/dsp" to output to sound card, or otherwise a plain
//	filename.  A WAV header is added if the filename ends with
//	.wav or .WAV.
//

extern char word[256];	// Word buffer
char *fil= 0;		// Currently loaded file
char *fp;		// Pointer into `fil' - current read position
int fp_lin;		// Line in which fp is

void 
process(char *fnam, char *main_seq, char *ofnam) {
  int ww;
  char **pp, **pp0;
  int opt_okay= 1;
  readFile(fnam);

  while (ww= getWord()) {
    if (ww != 'opt') opt_okay= 0;
    switch (ww) {
     case 'opt':	// Extra command-line options
       if (!opt_okay) error("`opt' directives must be at the top of the file, line %d", fp_lin);
       pp= pp0= (char **)scratch;
       while ((ww= getWord()) && ww != ';') *pp++= StrDup(word);
       if (!ww) error("Unexpected EOF in opt directive");
       *pp= 0;
       addOptions(pp-pp0, pp0);
       while (pp0 < pp) Free(*pp0++);
       break;
     case 'note': {
       char *nam= getName();
       if (findNote(nam)) error("Duplicate note definition `%s', line %d", nam, fp_lin);
       switch (getWord()) {
	case 'bin': decNoteB(nam); break;
	case 'samp': decNoteS(nam); break;
	case 'mix': decNoteM(nam); break;
	case 0: error("End of file in note definition"); break;
	default: error("Unknown note type `%s' at line %d", word, fp_lin); break;
       }
       break;
     }
     case 'seq': {
       char *nam= getName();
       Sequence *ss= CAlloc(sizeof(Sequence));
       ParList *par;
       int more;
       if (findSeq(nam)) error("Duplicate sequence definition `%s', line %d", nam, fp_lin);
       ss->nxt= seqlist; seqlist= ss;
       ss->nam= nam;
       ss->len= 0;
       do {		// Keep reading series sections to build up top-level ParList
	 more= decSerList(0, rate/4.0, &par);
	 if (par->len > ss->len) ss->len= par->len;
	 par->nxt= ss->par; ss->par= par;
       } while (more);
       break;
     }       
     default:
       error("Unknown directive `%s' at line %d", word, fp_lin); break;
    }
  }
  
  // Do note/seq dump if requested
  if (o_dump >= 1) { dumpAll(); if (o_dump == 1) return; }

  // Reached EOF, render the main sequence
  {
    int fd, f_wav, blen;
    Sequence *seq= findSeq(main_seq);
    if (!seq) error("Sequence `%s' is missing - nothing to output", main_seq);

    if (ofnam == 0) {				// Output to STDOUT
      fd= 1; f_wav= 0; 
      blen= 8192;		// Output short chunks - this may be real-time
      warn("Rendering `%s' sequence to standard output ...", main_seq);
    }
    if (0 != strcmp(ofnam, "/dev/dsp")) {	// Output to a file
      char *p= strchr(ofnam, 0);
      f_wav= p-4 >= ofnam && 0 == strcasecmp(p-4, ".wav");
      blen= 65536;		// Output big chunks (256K) for speed
      fd= creat(ofnam, 0666);
      if (fd < 0) errorE("Can't create output file `%s'", ofnam);
      warn("Rendering `%s' sequence to %s file ...", main_seq, f_wav ? "WAV" : "raw");
    }
    else {					// Output to /dev/dsp
      int a_ster, a_rate, a_fragsize, a_nfrag, a_enc;
      int afmt_req, afmt;
      int test= 1;
      audio_buf_info info;
      
      if (0 > (fd= open("/dev/dsp", O_WRONLY)))
	errorE("Can't open /dev/dsp");
      
      afmt= afmt_req= ((char*)&test)[0] ? AFMT_S16_LE : AFMT_S16_BE;
      a_ster= 1;
      a_rate= rate;
      a_fragsize= 14;
      a_nfrag= 4;
      a_enc= (a_nfrag<<16) | a_fragsize;
      
      if (0 > ioctl(fd, SNDCTL_DSP_SETFRAGMENT, &a_enc) ||
	  0 > ioctl(fd, SNDCTL_DSP_SAMPLESIZE, &afmt) ||
	  0 > ioctl(fd, SNDCTL_DSP_STEREO, &a_ster) ||
	  0 > ioctl(fd, SNDCTL_DSP_SPEED, &a_rate))
	errorE("Can't configure /dev/dsp");
      
      if (afmt != afmt_req)
	error("Can't open device in 16-bit mode");
      if (!a_ster)
	error("Can't open device in stereo");
      if (a_rate != rate)
	error("Can't open device at %dHz - closest available is %dHz", rate, a_rate);
      
      if (-1 == ioctl(fd, SNDCTL_DSP_GETOSPACE, &info))
	errorE("Can't get audio buffer info");
      
      f_wav= 0; blen= info.fragsize / 4;
      warn("Outputting `%s' sequence to /dev/dsp ...", main_seq);
    }

    renderSequence(seq, fd, blen, f_wav, 0, 0, o_mvol, 0, 0);
    close(fd);
  }

  // Delete temporary files
  {
    Note *nn; NoteS *ns;
    for (nn= notelist; nn; nn= nn->nxt)
      if (nn->typ == 'S' && (ns= (NoteS*)nn)->tmp_fil) {
	if (0 != unlink(ns->fnam))
	  errorE("Can't unlink temporary file `%s'", ns->fnam);
	ns->tmp_fil= 0;
      }
  }
}

//
//	Decode different note representations
//

void
decNoteB(char *nam) {
  NoteB *nn= CAlloc(sizeof(NoteB));
  BaList *prv= 0;
  nn->nxt= notelist;
  nn->typ= 'B';
  nn->nam= nam;
  notelist= (Note*)nn;
  while (1) {
    char *enc;
    BaList *ba;
    int ww;
    getWord(); enc= StrDup(word); 
    switch (ww= getWord()) {
     case ',': case '->': case ';': break;
     case 0: error("Unexpected EOF in note definition"); break;
     default: error("Bad syntax in note definition `%s %s', line %d",
		    enc, word, fp_lin); break;
    }
    ba= decBaSpec(&prv, enc, ww != ','); Free(enc);
    if (ww == ';') {
      if (ba->vol[0] != 0) {	// Add a final volume slide to zero if necessary
	prv= ba; ba= addBaSpec(&prv, ba->tim+fade_period, prv->carr[0], prv->freq[0], 0.0, 0);
      }
      prv->nxt= 0;		// The last one is only to set the length and slide
      Free(ba);			//  for the previous, so discard
      break;		
    }
    if (!prv) nn->bin= ba;
    prv= ba;
  }
  
  // Scan thru and delete zero-length segments (which may be there to
  // mark ends of slides or whatever)
  {
    BaList **prvp= &nn->bin;
    BaList *ba;
    while (ba= *prvp) {
      if (ba->len == 0) { *prvp= ba->nxt; Free(ba); }
      else { prvp= &ba->nxt; }
    }
  }
}

void 
decNoteS(char *nam) {
  NoteS *nn= CAlloc(sizeof(NoteS));
  double adjadj= 1.0;
  int ww;
  Sequence *seq= 0;

  nn->nxt= notelist;
  nn->typ= 'S';
  nn->nam= nam;
  nn->styp= 1;
  nn->pre= 0;
  nn->len= 0;
  notelist= (Note*)nn;

  getWord(); 
  if (word[0] == '[') {		// Sequence loop rendered, resampled as a note
    char *p= word + strlen(word);
    int fd;
    if (p[-1] != ']') error("Bad sequence-name `%s', line %d", word, fp_lin);
    p[-1]= 0; seq= findSeq(word+1);
    if (!seq) error("Sequence name not known `%s', line %d", word+1, fp_lin);
    nn->fnam= StrPrintF("tmp-rhybag-%d", tmpfil_cnt++);
    nn->tmp_fil= 1;
  }
  else
    nn->fnam= StrDup(word);
  
  // Handle modifiers
  while (1) {
    switch (getWord()) {
     case 'mono':
       nn->styp= 0;
       continue;
     case 'loop':
       nn->loop= 1;
       continue;
    }
    switch (word[0]) {
      double val; char *p;
     case '@': case 'p':
       p= word+1; 
       if (!getNum(&p, &val) || *p)
	 error("Bad value `%s' in `%s', line %d", word+1, word, fp_lin);
       switch (word[0]) {
	case '@': 
	  adjadj= val/rate; break;
	case 'p': 
	  if (seq) error("Invalid modifier `%s' for sequence-based note `%s', line %d",
			 word, nn->nam, fp_lin);
	  nn->pre= (int)val; break;
       }
       continue;
     default: break;
    }
    break;
  }
  ungetWord();

  // Render the sequence
  if (seq) {
    int fd;
    warn("Rendering sequence `%s' to temporary file ...", seq->nam);
    fd= creat(nn->fnam, 0666);
    if (fd < 0) errorE("Can't create temporary file `%s'", nn->fnam);
    renderSequence(seq, fd, 0, 0, nn->loop, nn->styp == 0, 	// No WAV, default block size,
		   -0.95, &nn->pre, &nn->len);			//  95% auto
    close(fd);
  }

  // Get the length if we don't have it already
  if (!seq) {
    int len, fd;
    fd= open(nn->fnam, O_RDONLY);
    if (fd < 0) error("Can't open file `%s' for note `%s', line %d", nn->fnam, nn->nam, fp_lin);
    len= lseek(fd, 0, SEEK_END);
    if (len < 0) error("Can't access file `%s' for note `%s', line %d", nn->fnam, nn->nam, fp_lin);
    close(fd);

    len >>= (1+nn->styp);
    if (nn->pre > len) nn->pre= len;
    nn->len= len - nn->pre;
  }

  // Calculate number of bits required
  {
    int bits= 0;
    uint tlen= nn->pre + nn->len; tlen += tlen / 2;
    while (tlen > 0) { bits++; tlen >>= 1; }
    nn->bits= bits;
  }
  adjadj *= (1<<(32-nn->bits));
  nn->off0= (1 << (32 - nn->bits)) >> 1;	// Centred on first sample, assuming rounding down

  // Handle envelope
  {
    EnvList *prv= 0;
    while (1) {
      char *enc;
      EnvList *env;
      getWord(); enc= StrDup(word); 
      switch (ww= getWord()) {
       case ',': case '->': case ';': break;
       case 0: error("Unexpected EOF in note definition"); break;
       default: error("Bad syntax in note definition `%s %s', line %d",
		      enc, word, fp_lin); break;
      }
      env= decEnvSpec(&prv, enc, ww == '->', adjadj); Free(enc);
      if (ww == ';') {
	if (env->vol[0] != 0) {    // Add a final volume slide to zero if necessary
	  prv= env; env= addEnvSpec(&prv, env->tim+fade_period, prv->adj[0], 0.0, 0);
	}
	prv->nxt= 0;               // The last one is only to set the length and slide
	Free(env);                 //  for the previous, so discard
	break;
      }
      if (!prv) nn->env= env;
      prv= env;
    }
  }

  // Scan thru and delete zero-length segments
  {
    EnvList **prvp= &nn->env;
    EnvList *ee;
    while (ee= *prvp) {
      if (ee->len == 0) { *prvp= ee->nxt; Free(ee); }
      else { prvp= &ee->nxt; }
    }
  }

  // Work out initial offset so that everything lines up at time 0
  if (nn->loop) {
    EnvList *en= nn->env;
    double off= 0;
    int frb= 32 - nn->bits;
    int olen= nn->len << frb;
    uint ioff;
    while (en && en->tim < 0) {
      off += envLength(en);
      en= en->nxt;
    }
    ioff= olen - (off - floor(off / olen) * olen);
    if (ioff == olen) ioff= 0;
    nn->off0 += ioff + (nn->pre << frb);
  }
  else
    warn("@@@ WARNING: Non-loopable sample start-time adjustments NYI");
}

void 
decNoteM(char *nam) {
  NoteM *nn= CAlloc(sizeof(NoteM));
  MixList *prv= 0;

  nn->nxt= notelist;
  nn->typ= 'M';
  nn->nam= nam;
  notelist= (Note*)nn;

  while (1) {
    MixList *mix;
    switch (getWord()) {
     case 0: error("Unexpected EOF in note definition"); break;
     case ';': break;
     default:
       mix= decMixSpec(word);
       if (prv) prv->nxt= mix; else nn->mix= mix;
       prv= mix; mix->nxt= 0;
       continue;
    }
    break;
  }
}

//
//	Fetch a serial list of a sequence from the input stream, up to
//	the next pipe, closing bracket or semicolon (dependent on the
//	current level), and put them into a ParList item to return.
//	Current base beat-length (samples) and brace level are also
//	passed.  Return value is true if there are more series at this
//	level.
//

int 
decSerList(int lev, double bblen, ParList **rvp) {
  ParList *par= CAlloc(sizeof(ParList));
  double tim= 0;
  double blen= bblen;
  int ww;
  SerList **prvp= &par->ser;
  SerList *ser;

  *rvp= par;

  while (1) {
    par->len= (int)tim;
    switch (ww= getWord()) {
     case 0: error("Unexpected EOF within sequence"); break;
     case ';':
       if (lev != 0) error("Unmatched closing braces before semicolon at line %d", fp_lin);
       return 0;
     case ')':
       if (lev == 0) error("Too many closing braces at line %d", fp_lin);
       return 0;
     case '|':
       return 1;
    }

    // Check for beat-length adjustments
    if (isdigit(word[0])) {
      double val;
      char *p= word;
      getNum(&p, &val);
      if (*p == '/') {
	double val2; p++;
	if (!getNum(&p, &val2) || *p) 
	  error("Bad NN/NN time adjustment item `%s', line %d", word, fp_lin);
	blen= bblen / val * val2;
      }
      else if (0 == strcmp(p, "ms"))
	blen= bblen= val * 0.001 * rate;
      else if (0 == strcmp(p, "bpm"))
	blen= bblen= rate * 60.0 / val;
      else error("Bad time adjustment item `%s', line %d", word, fp_lin);
      continue;
    }
    
    // Check for rests
    if (word[0] == '_') {
      double len= 0;
      double dlen= 0;
      char *p= word;
      while (*p == '_') { p++; len += blen; }
      dlen= len*2;
      while (*p == '.') { p++; len= (len+dlen) * 0.5; }
      while (*p == '\'') { p++; len *= 0.5; }

      if (*p) error("Extra rubbish following rest item \"%s\", line %d", word, fp_lin);
      tim += len;
      continue;
    }
    
    // Check for notes
    if (isalpha(word[0])) {
      double len= blen, dlen= blen*2;
      char *p= word;
      char *p0;
      Note *nn;
      while (isalnum(*p) || *p == '_') p++;
      p0= p;
      while (*p == '.') { p++; len= (len+dlen) * 0.5; }
      while (*p == '\'') { p++; len *= 0.5; }
      
      if (*p) error("Extra rubbish following note item \"%s\", line %d", word, fp_lin);
      *p0= 0;
      if (!(nn= findNote(word))) error("Note name `%s' not known at line %d", word, fp_lin);
      
      ser= CAlloc(sizeof(*ser));
      ser->tim= (int)tim; tim += len;
      ser->len= 0;
      ser->rep= 1;
      ser->note= nn;
      *prvp= ser; prvp= &ser->nxt;
      continue;
    }

    // Check for sequences
    if (word[0] == '[') {
      char *p= word+1, *p0;
      int rep= 0;
      Sequence *ss;

      while (isalnum(*p) || *p == '_') p++;
      p0= p;
      if (*p++ != ']') error("Bad sequence item - missing ']' - \"%s\", line %d", word, fp_lin);
      if (*p == 'x') {
	double val;
	p++; if (!getNum(&p, &val) || *p) 
	  error("Bad repeat count after sequence name `%s', line %d", word, fp_lin);
	rep= (int)val;
	if (rep <= 0) continue;		// use x0 to turn off a sequence
      }
      if (*p) error("Bad sequence item \"%s\", line %d", word, fp_lin);
      
      *p0= 0;
      if (!(ss= findSeq(word+1))) error("Sequence name `%s' not known at line %d", word+1, fp_lin);

      ser= CAlloc(sizeof(*ser));
      ser->tim= tim; tim += ss->len * rep;
      ser->len= ss->len;
      ser->rep= rep;
      *prvp= ser; prvp= &ser->nxt;
      ser->seq= ss;
      continue;
    }

    // Check for sub-series
    if (ww == '(') {
      int more;
      ParList *par;
      char *p;
      double val;

      ser= CAlloc(sizeof(*ser));
      ser->tim= tim;
      ser->len= 0;
      ser->rep= 1;
      *prvp= ser; prvp= &ser->nxt;
      
      do {		// Keep reading series sections to build up ParList
	more= decSerList(lev + 1, blen, &par);
	if (par->len > ser->len) ser->len= par->len;
	par->nxt= ser->par; ser->par= par;
      } while (more);
      
      getWord(); p= word+1;
      if (word[0] == 'x' && getNum(&p, &val) && !*p) {
	if (val < 1) { Free(ser); continue; }		// x0 to disable (@@memory leak here)
	ser->rep= (int)val;
      }
      else ungetWord();
      
      tim += ser->len * ser->rep;
      continue;
    }

    // Otherwise we don't know what it is
    error("Invalid item `%s' in sequence at line %d", word, fp_lin);
  }
  error("Internal error 1");
}

//
//	Decode the specification of a binaural tone
//

BaList *
decBaSpec(BaList **prvp, char *enc, int f_slide) {
  BaList *prv= *prvp;
  char *p= enc;
  double carr, freq, vol;
  int tim;

  if (!getTime(&p, prv ? prv->tim : 0, &tim))	
    error("Time-indication missing from binaural tone `%s', line %d", enc, fp_lin);
  if (prv && prv->tim >= tim)
    error("Time on this item `%s' must be later than the previous one, line %d", enc, fp_lin);
  if (*p == '=' && prv) {	// Duplicate previous value
    carr= prv->carr[0];
    freq= prv->freq[0];
    p++;
  }
  else {
    if (!getNum(&p, &carr))
      error("Carrier frequency missing from binaural tone `%s', line %d", enc, fp_lin);
    if (*p == 'c') {
      p++; carr= pow(2, carr / 1200) * bfreq;
    }
    freq= 0;
    if (*p == '+' || *p == '-') {
      if (*p == '+') p++;
      if (!getNum(&p, &freq))
	error("Binaural frequency invalid in binaural tone `%s', line %d", enc, fp_lin);
    }
  }
  if (*p++ != '/' || !getNum(&p, &vol) || *p)
    error("Volume level missing or invalid in binaural tone `%s', line %d", enc, fp_lin);
  vol *= 0.01;

  return addBaSpec(prvp, tim, carr, freq, vol, f_slide);
}

BaList *
addBaSpec(BaList **prvp, int tim, double carr, double freq, double vol, int f_slide) {
  BaList *ba= CAlloc(sizeof(BaList));
  BaList *prv= *prvp;

  // Extra parameters of slide arrays will be zero, so no need to set them
  ba->tim= tim;
  ba->carr[0]= carr;
  ba->freq[0]= freq;
  ba->vol[0]= vol;
  ba->cnt= 0x40000000;

  ba->len= f_slide ? -2 : -1;
  if (prv) {
    if (prv->len == -2) {			// Copy back slide targets
      setupSlide(prv->carr, prv->carr[0], ba->carr[0]);
      setupSlide(prv->freq, prv->freq[0], ba->freq[0]);
      setupSlide(prv->vol, prv->vol[0], ba->vol[0]);
    }
    else if (prv->vol[0] != ba->vol[0]) {	// Sudden change in volume (no slide)
      if (ba->tim - prv->tim <= fade_period) {	// Slide the volume over the whole period if too short
	setupSlide(prv->vol, prv->vol[0], ba->vol[0]);
      }
      else {					// Add a little slide on the end for just the volume
	*prvp= prv= addBaSpec(prvp, tim-fade_period, prv->carr[0], prv->freq[0], prv->vol[0], 1);
	setupSlide(prv->vol, prv->vol[0], ba->vol[0]);
      }
    }
    prv->len= ba->tim - prv->tim;
    prv->cnt= deltaCount(prv->len, prv->carr, prv->freq, prv->vol, 0);
    prv->voli= (exp_table[(int)(prv->vol[1] + prv->vol[2])] / 
		exp_table[(int)(prv->vol[1])] - 1) / prv->cnt;
    prv->nxt= ba;
  }
  ba->nxt= 0;
  return ba;
}

//
//	Decode a spec of a sample-adjust envelope
//

EnvList *
decEnvSpec(EnvList **prvp, char *enc, int f_slide, double adjadj) {
  EnvList *prv= *prvp;
  char *p= enc;
  double adj, vol;
  int tim;

  if (!getTime(&p, prv ? prv->tim : 0, &tim))
    error("Time-indication missing from sample adjustment `%s', line %d", enc, fp_lin);
  if (prv && prv->tim >= tim)
    error("Time on this item `%s' must be later than the previous one, line %d", enc, fp_lin);
  if (!getNum(&p, &adj))
    error("Pitch change missing from sample adjustment `%s', line %d", enc, fp_lin);
  adj= pow(2.0, adj/1200.0);
  if (*p++ != '/' || !getNum(&p, &vol) || *p)
    error("Volume level missing or invalid in sample adjustment `%s', line %d", enc, fp_lin);    
  vol *= 0.01;

  return addEnvSpec(prvp, tim, adj*adjadj, vol, f_slide);
}

EnvList *
addEnvSpec(EnvList **prvp, int tim, double adj, double vol, int f_slide) {
  EnvList *en= CAlloc(sizeof(EnvList));
  EnvList *prv= *prvp;

  // Extra parameters of slide arrays will be zero, so no need to set them
  en->adj[0]= adj;
  en->vol[0]= vol;
  en->cnt= 0x40000000;
  en->tim= tim;

  en->len= f_slide ? -2 : -1;
  if (prv) {
    if (prv->len == -2) {		// Copy back slide targets
      setupSlide(prv->adj, prv->adj[0], en->adj[0]);
      setupSlide(prv->vol, prv->vol[0], en->vol[0]);
    }
    else if (prv->vol[0] != en->vol[0]) {	// Sudden change in volume (no slide)
      if (en->tim - prv->tim <= fade_period) {	// Slide the volume over the whole period if too short
	setupSlide(prv->vol, prv->vol[0], en->vol[0]);
      }
      else {					// Add a little slide on the end for just the volume
	*prvp= prv= addEnvSpec(prvp, tim-fade_period, prv->adj[0], prv->vol[0], 1);
	setupSlide(prv->vol, prv->vol[0], en->vol[0]);
      }
    }

    prv->len= en->tim - prv->tim;
    prv->cnt= deltaCount(prv->len, prv->adj, prv->vol, 0);
    prv->voli= (exp_table[(int)(prv->vol[1] + prv->vol[2])] / 
		exp_table[(int)(prv->vol[1])] - 1) / prv->cnt;
    prv->nxt= en;
  }
  en->nxt= 0;
  return en;
}

//
//	Decode the spec of a note in a mix
//

MixList *
decMixSpec(char *enc) {
  MixList *mix= CAlloc(sizeof(MixList));
  char *p= enc;
  char *p0, *p1;

  getTime(&p, 0, &mix->tim);		// If there is one, read it
  p0= p;
  while (*p && *p != '/') p++;
  p1= p;

  mix->vol= 1.0;
  if (*p == '/') {
    p++; if (!getNum(&p, &mix->vol) || *p)
      error("Volume level invalid in mix note-spec `%s', line %d", enc, fp_lin);
    mix->vol *= 0.01;
  }
  
  *p1= 0;
  if (!(mix->note= findNote(p0)))
    error("Note `%s' not known in mix note-spec at line %d", p0, fp_lin);

  return mix;
}

//
//	Setup an exponential slide using the exp_table[]
//

void
setupSlide(double *arr, double val0, double val1) {
  double adj;
  int o0, o1, o2;

  if (val0 == 0) val0= ldexp(val1, -ET_OCT);
  if (val1 == 0) val1= ldexp(val0, -ET_OCT);

  if (val0 > val1) {
    arr[0]= ldexp(val0, -ET_OCT);
    arr[1]= ET_OCT*ET_PPO;
    adj= ldexp(val1 / val0, ET_OCT);
  }
  else {
    arr[0]= val0;
    arr[1]= 0;
    adj= val1 / val0;
  }

  // Binary search for target value
  o0= 0; o2= ET_OCT*ET_PPO;
  while (o0+1 < o2) {
    if (adj < exp_table[o1= (o0+o2)/2])
      o2= o1;
    else
      o0= o1;
  }
  if (arr[1] <= o0) arr[2]= o0-arr[1];
  else arr[2]= o2-arr[1];
}

int  
deltaCount(int len, ...) {
  va_list ap; 
  int steps= 0;
  double *dp;
  int cnt;
  double adj;

  // Scan to find the maximum number of steps
  va_start(ap, len);
  while (dp= va_arg(ap, double*)) {
    int s1= abs(dp[2]);
    if (s1 > steps) steps= s1;
  }
  va_end(ap);
  if (steps * delta_period > len) steps= len / delta_period;
  if (steps < 1) steps= 1;
  if (steps > 3600) steps= 3600;

  // Calculate delta count.  NB Need to have a whole number of steps
  // (i.e. not 32+8, rather 20+20, or just 40) especially for short
  // lengths so that linear-slide of amplitude within a step works
  // okay.
  cnt= len / steps;
  adj= cnt * 1.0 / len;
  
  // Adjust slides to this new value
  va_start(ap, len);
  while (dp= va_arg(ap, double*))
    dp[2] *= adj;
  va_end(ap);

  return cnt;
}

//
//	Gets the next word.  A word is any sequence of characters
//	between white space.  However, the characters ();, always
//	count as separate words, whether or not there is white space
//	around them.  The word is copied to the scratch buffer, and a
//	char-const integer is returned, or 0 if the end-of-file is
//	reached.
//
//	Comments start with a word starting with '#', and continue to
//	the end of the line.  This means that '#' embedded in words is
//	okay.
//
//	A single word can be ungot using ungetWord().	
//

char word[256];		// Word buffer
char *unget_fp;		// To unget a word
int unget_fp_lin;

int 
getWord() {
  char c, *p;
  unget_fp= fp; unget_fp_lin= fp_lin;
  while ((c= *fp) == '#' || isspace(c)) { 
    fp++;
    if (c == '\n') fp_lin++;
    if (c == '#') { while (*fp && *fp != '\n') fp++; }
  }
  if ((c= *fp) == ',' || c == ';' || c == '(' || c == ')') {
    word[0]= c; word[1]= 0; fp++; return c;
  }
  p= word;
  while ((c= *fp) && 
	 !(isspace(c) || c == ',' || c == ';' || c == '(' || c == ')') &&
	 p < &word[sizeof(word)-1]) 
    { *p++= c; fp++; }
  if (p >= &word[sizeof(word)-1]) 
    error("Word exceeded %d characters at line %d", sizeof(word)-1, fp_lin); 
  *p= 0;
  return packCC(word);
}

void 
ungetWord() {
  fp= unget_fp;
  fp_lin= unget_fp_lin;
}

//
//	Gets a name, and gives an error if it doesn't look like a
//	C-style identifier.  Returns a StrDup'd string.
//

char *
getName() {
  char *p;
  if (0 == getWord()) 
    error("Unexpected end-of-file - expecting a name");
  if (!isalpha(word[0]) && word[0] != '_')
    error("Expecting a name, found `%s', line %d", word, fp_lin);
  for (p= word+1; *p; p++) 
    if (!isalnum(*p) && *p != '_')
      error("Expecting a name, found `%s', line %d", word, fp_lin);
  return StrDup(word);
}

//
//	Get a (double) number out of a string, moving the pointer
//	forward, and returning indicating whether a valid number was
//	read.
//

int 
getNum(char **pp, double *rvp) {
  char *cp= *pp;
  *rvp= strtod(cp, pp);
  return *pp != cp;
}

//
//	Get a time from the front of a string, if there is one there.
//	Handles: relative and absolute times, NNh for hours, NNm for
//	minutes, NNs for seconds and NN for ms.
//

int 
getTime(char **pp, int prev, int *rvp) {
  double tim= 0;
  char *p0= *pp;
  char *p1= p0;
  char *p;
  while (*p1 && *p1 != ':') p1++;
  if (*p1 != ':') { *rvp= prev; return 0; }
  *pp= p1+1;
  if (*p0 == '+') { 		// Relative time
    p0++; tim= (double)prev;
  }

  p= p0;
  while (p < p1) {
    double val;
    if (!getNum(&p, &val)) error("Bad time-spec in item `%s', line %d", p0, fp_lin);
    switch (*p) {
     case 'h': p++; tim += val * rate * 3600; break;
     case 'm': p++; tim += val * rate * 60; break;
     case 's': p++; tim += val * rate; break;
     default: tim += val * rate * 0.001; break;
    }
  }
  *rvp= (int)tim;
  return 1;
}

//
//	Loads up the file into memory, inefficiently for very large
//	files, and sets everything up to start getting words with
//	getWord().
//
//	If there is a previously loaded file, it is replaced by the
//	new one.
//

void 
readFile(char *fnam) {
  FILE *in;
  int len;
  int flen= 0;

  in= (0 == strcmp(fnam, "-")) ? stdin : fopen(fnam, "r");
  if (!in) error("Can't open input file: %s", fnam);
  
  while (len= fread(scratch, 1, sizeof(scratch), in)) {
    if (fil == 0) {
      fil= Alloc(len+1);
      flen= len;
      memcpy(fil, scratch, len);
    }
    else {
      char *nfil= Alloc(flen+len+1);
      memcpy(nfil, fil, flen);
      memcpy(nfil+flen, scratch, len);
      Free(fil);
      fil= nfil; flen += len;
    }
  }
  if (ferror(in)) error("Error reading file: %s", fnam);
  fclose(in);
  fil[flen]= 0;

  fp= fil;
  fp_lin= 1;
}

//
//	Find a Note given the name, or return 0
//

Note *
findNote(char *nam) {
  Note *nn;
  for (nn= notelist; nn; nn= nn->nxt)
    if (0 == strcmp(nn->nam, nam)) return nn;
  return 0;
}

//
//	Find a Sequence given the name, or return 0
//

Sequence *
findSeq(char *nam) {
  Sequence *ss;
  for (ss= seqlist; ss; ss= ss->nxt) 
    if (0 == strcmp(ss->nam, nam)) return ss;
  return 0;
}

//
//	Dump out all notes and sequences
//

void 
dumpAll() {
  Note *nn;
  Sequence *seq;

  for (nn= notelist; nn; nn= nn->nxt)
    switch (nn->typ) {
     case 'S': dumpNoteS((NoteS*)nn); break;
     case 'B': dumpNoteB((NoteB*)nn); break;
     case 'M': dumpNoteM((NoteM*)nn); break;
     default: error("Invalid note type `%c' for `%s'", nn->typ, nn->nam);
    }
  for (seq= seqlist; seq; seq= seq->nxt) {
    printf("Sequence %s, length %d\n", seq->nam, seq->len);
    dumpPar(seq->par, 2);
    printf("\n");
  }
}

//
//	Dump notes
//

void
dumpNoteS(NoteS *nn) {
  EnvList *en;
  printf("NoteS %s %s%s%s,\n",
	 nn->nam, nn->fnam, nn->tmp_fil ? " (temp)" : "",
	 nn->styp ? "" : " mono");
  printf("  length %d+%d, %sloopable, bits %d:%d, off0 %08x\n",
	 nn->pre, nn->len, nn->loop ? "" : "non-", nn->bits, 32-nn->bits, nn->off0);
  for (en= nn->env; en; en= en->nxt)
    printf("  time %d, length %d, count %d\n"
	   "    adjust %g %g %g\n"
	   "    volume %g %g %g inc-adj %g\n",
	   en->tim, en->len, en->cnt, en->adj[0], en->adj[1], en->adj[2],
	   en->vol[0], en->vol[1], en->vol[2], en->voli);
  printf("\n");
}

void 
dumpNoteB(NoteB *nn) {
  BaList *ba;
  printf("NoteB %s\n", nn->nam);
  for (ba= nn->bin; ba; ba= ba->nxt)
    printf("  time %d, length %d, count %d\n"
	   "    carrier %g %g %g\n"
	   "    freq %g %g %g\n"
	   "    volume %g %g %g inc-adj %g\n",
	   ba->tim, ba->len, ba->cnt,
	   ba->carr[0], ba->carr[1], ba->carr[2], 
	   ba->freq[0], ba->freq[1], ba->freq[2], 
	   ba->vol[0], ba->vol[1], ba->vol[2], ba->voli);
  printf("\n");
}

void 
dumpNoteM(NoteM *nn) {
  MixList *mx;
  printf("NoteM %s\n", nn->nam);
  for (mx= nn->mix; mx; mx= mx->nxt)
    printf("  time %d, note `%s', volume %g\n",
	   mx->tim, mx->note->nam, mx->vol);
  printf("\n");
}

//
//	Dump a parallel series
//

void 
dumpPar(ParList *par, int off) {
  for (; par; par= par->nxt) {
    SerList *ser;
    printf("%*sSER: (length %d)\n", off, "", par->len);
    off += 2;
    for (ser= par->ser; ser; ser= ser->nxt) {
      printf("%*stime %d", off, "", ser->tim);
      if (ser->rep != 1) printf(", %d x %d", ser->len, ser->rep);
      if (ser->note) printf(", note `%s'", ser->note->nam);
      if (ser->seq) printf(", seq `%s'", ser->seq->nam);
      if (ser->par) printf(", parallel series:");
      printf("\n");
      if (ser->par) dumpPar(ser->par, off+2);
    }
    off -= 2;
  }
}

// END //
