//||
//||	`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.
//||	

//
//	Render a sequence `seq' into an output file or stream.  Output
//	is generated in blocks of `blen' samples, or in default blocks
//	if `blen' is 0.  Output will be preceded by a WAV header if
//	`f_wav' is set.  Output will be mono if `f_mono' is set.
//
//	If `f_loop' is set, then the output sample will be made
//	perfectly loopable by duplicating events at the start and end
//	of the sample; otherwise the sample extends as long as
//	necessary to include the final notes.  Either way, an
//	introductory part exists for events that stray before time 0.
//
//	Length in samples of the introductory section and the main
//	section are returned in *pre_rvp and *len_rvp (if non-0);
//	

void 
renderSequence(Sequence *seq, int fd, int blen, int f_wav, int f_loop, int f_mono,
	       double vol, int *pre_rvp, int *len_rvp) {
  int n_ev;
  int len;
  int pre;
  Event *ev;

  printf("Rendering sequence `%s' ...\n", seq->nam);
  
  // Expand sequence into event list
  clearEvents();
  expandSeq(seq, 0);

  // Find the events that need to be duplicated to make the sequence
  // perfectly loopable, and duplicate them.  If no looping needs to
  // be done, instead extend the length of the output to include any
  // trailing events fully.
  len= seq->len;
  pre= 0;
  if (f_loop) {		// Duplicate events crossing loop boundary
    for (ev= event; ev; ev= ev->nxt) {
      if ((ev->stim < 0 && ev->etim > 0) ||
	  (ev->stim < len && ev->etim > len)) {
	Event *ev2= CAlloc(sizeof(*ev2));
	memcpy(ev2, ev, sizeof(*ev));
	ev2->nxt= event; event= ev2;
	if (ev2->stim > 0) { 
	  ev2->tim -= len;
	  ev2->stim -= len;
	  ev2->etim -= len;
	}	
	else {
	  ev2->tim += len;
	  ev2->stim += len;
	  ev2->etim += len;
	}
	if ((ev->stim < 0 && ev->etim > len) ||
	    (ev2->stim < 0 && ev2->etim > len))
	  warn("Warning: Note `%s' over-wraps the looped sequence", 
	       ev->not0 ? ev->not0->nam : ev->not1->nam);
      }
    }
  }
  else {		// Extend `len' according to the latest ->etim
    for (ev= event; ev; ev= ev->nxt) 
      if (ev->etim > len) len= ev->etim;
  }

  // Sort event list
  n_ev= sortEvents();
  if (event->stim < -pre) pre= -event->stim;
  if (f_loop) pre= 0;		// Drop everything before time 0
  if (o_dump == 2) { dumpEvents(); return; }

  // Scan thru to calculate automatic volume adjust if necessary
  if (vol > 0)
    mvol= vol;
  else {
    mvol= -vol/findMaxVol();
  }

  // Find the exact output length (for WAV output, etc)
  warn(" outputting %d events, length %d+%d samples, volume %.1f%%", n_ev, pre, len, mvol*100.0);
  if (pre_rvp) *pre_rvp= pre;
  if (len_rvp) *len_rvp= len;

  // Output WAV header
  if (f_wav) {
    char *p= writeWAV(pre+len, rate, f_mono ? 2 : 4, !f_mono);	// 16-bit == 2/4 bytes-per-sample
    writeData(fd, p, p+44);
  }

  // Render in chunks and output to file or device
  {
    int cnt;
    int m0= 0, m1= 0;
    initPlay(blen ? blen : 65536, pre, len);
    while (cnt= playChunk()) {
      short *sp= (short*)pbuf;
      int *ip= pbuf, val;
      char *cp0, *cp1;
      int a;
      if (f_mono)
	for (a= cnt; a>0; a--) {
	  val= *ip++ >> 16; val += *ip++ >> 16;
	  *sp++= val >>= 1;		//@@@ could do something more fancy here, like mixing noise in
	  if (val < m0) m0= val;
	  if (val > m1) m1= val;
	}
      else 
	for (a= cnt; a>0; a--) {
	  *sp++= val= *ip++ >> 16;	//@@@ could do something more fancy here, like mixing noise in
	  if (val < m0) m0= val;
	  if (val > m1) m1= val;
	  *sp++= val= *ip++ >> 16;
	  if (val < m0) m0= val;
	  if (val > m1) m1= val;
	}
      writeData(fd, (char*)pbuf, (char*)sp);
    }
    warn(" peak output level was %.1f%%", (m1>-m0 ? m1 : -m0) / 327.68);
  }

  // Close all open file descriptors for samples
  {
    Note *nn;
    for (nn= notelist; nn; nn= nn->nxt) 
      if (nn->typ == 'S' && ((NoteS*)nn)->fd) {
	NoteS *ns= (NoteS*)nn;
	if (ns->mmap) munmap(ns->mmap, (ns->len + ns->pre) << (1 + ns->styp));
	close(ns->fd);
	ns->fd= 0; ns->mmap= 0;
      }
  }
}

inline void 
writeData(int fd, char *p0, char *p1) {
  while (p0 < p1) {
    int len= write(fd, p0, p1-p0);
    if (len < 0) errorE("Can't write to output");
    p0 += len;
  }
}

void 
clearEvents() {
  while (event) {
    Event *ev= event;
    event= ev->nxt;
    Free(ev);
  }
}

int  
cmpEvents(Event **ep1, Event **ep2) {
  int t1= (*ep1)->stim;
  int t2= (*ep2)->stim;
  return (t1 < t2) ? -1 : (t1 > t2) ? 1 : 0;
}

int 
sortEvents() {
  int cnt= 0, rv;
  Event *ev= event;
  Event **arr;
  while (ev) { cnt++; ev= ev->nxt; }
  arr= CAlloc(sizeof(Event*) * cnt);
  cnt= 0; ev= event;
  while (ev) { arr[cnt++]= ev; ev= ev->nxt; }
  qsort(arr, cnt, sizeof(Event*), (int(*)(const void*,const void*))cmpEvents);
  event= 0; rv= cnt;
  while (cnt-- > 0) { ev= arr[cnt]; ev->nxt= event; event= ev; }
  Free(arr);
  return rv;
}

int  
expandSeq(Sequence *seq, int tim) {
  ParList *par= seq->par;
  while (par) { expandSeries(par, tim); par= par->nxt; }
  return tim + seq->len;
}

void 
expandPar(ParList *par, int tim) {
  while (par) { expandSeries(par, tim); par= par->nxt; }
}

void
addNoteS(NoteS *nn, int tim, double vol) {
  Event *ev;
  EnvList *en;
  ev= CAlloc(sizeof(*ev)); ev->nxt= event; event= ev;
  ev->tim= tim;
  ev->vol= vol;
  ev->stim= tim + nn->env->tim;
  for (en= nn->env; en; en= en->nxt)
    ev->etim= tim + en->tim + en->len;
  ev->not0= nn;
}

void
addNoteB(NoteB *nn, int tim, double vol) {
  Event *ev;
  BaList *ba;
  ev= CAlloc(sizeof(*ev)); ev->nxt= event; event= ev;
  ev->tim= tim;
  ev->vol= vol;
  ev->stim= tim + nn->bin->tim;
  for (ba= nn->bin; ba; ba= ba->nxt) 
    ev->etim= tim + ba->tim + ba->len;
  ev->not1= nn;
}

void 
addNote(Note *nn, int tim, double vol) {
  MixList *mix;
  switch (nn->typ) {
   case 'S': addNoteS((NoteS*)nn, tim, vol); return;
   case 'B': addNoteB((NoteB*)nn, tim, vol); return;
   case 'M':
     mix= ((NoteM*)nn)->mix;
     while (mix) {
       addNote(mix->note, tim + mix->tim, vol * mix->vol);
       mix= mix->nxt;
     }
     return;
  }
  error("Internal error - bad note type `%c'", nn->typ);
}

void
expandSeries(ParList *par, int tim) {
  SerList *ser= par->ser;
  
  while (ser) {
    int a;
    if (ser->note)
      addNote(ser->note, tim + ser->tim, 1);
    if (ser->seq)
      for (a= 0; a<ser->rep; a++)
	expandSeq(ser->seq, tim + ser->tim + ser->len * a);
    if (ser->par)
      for (a= 0; a<ser->rep; a++)
	expandPar(ser->par, tim + ser->tim + ser->len * a);
    ser= ser->nxt;
  }
}

double 
findMaxVol() {
  double max= 0;
  VolSeg *list= 0;
  int tim= event->stim;
  Event *ev= event;

  while (1) {
    // Add new Events to our VolSeg list
    while (ev && ev->stim <= tim) {
      if (ev->etim > tim) {
	VolSeg *vs= CAlloc(sizeof(*vs));
	vs->nxt= list; list= vs;
	vs->vol= ev->vol;
	vs->tim= ev->tim;
	if (ev->not0) vs->en= ev->not0->env;
	if (ev->not1) vs->ba= ev->not1->bin;
      }
      ev= ev->nxt;
    }

    // Shift forward past any finished segments
    {
      VolSeg **prvp= &list, *vs;
      while (vs= *prvp) {
	while (vs->ba && vs->tim + vs->ba->tim + vs->ba->len <= tim)
	  vs->ba= vs->ba->nxt;
	while (vs->en && vs->tim + vs->en->tim + vs->en->len <= tim)
	  vs->en= vs->en->nxt;
	if (!vs->ba && !vs->en) {
	  *prvp= vs->nxt; Free(vs);
	}
	else prvp= &vs->nxt;
      }
    }

    // See if there is anything left
    if (!list) {
      if (!ev) break;
      tim= ev->stim;
      continue;		// Move forward to next event
    }
    
    // Scan for max vol at this time-point and next time (end of
    // period, or start of next)
    {
      int ntim= 0x7fffffff;
      VolSeg *vs;
      double vol= 0;
      for (vs= list; vs; vs= vs->nxt) {
	int t0, t1;
	if (vs->ba) { 
	  t0= vs->tim + vs->ba->tim; t1= t0 + vs->ba->len;
	  if (tim < t0 || tim >= t1) error("Internal error checking volumes");
	  vol += vs->vol * vs->ba->vol[0] * 
	    exp_table[(int)(vs->ba->vol[1] + vs->ba->vol[2] * ((tim-t0) / vs->ba->cnt))];
	}
	else { 
	  t0= vs->tim + vs->en->tim; t1= t0 + vs->en->len; 
	  if (tim < t0 || tim >= t1) error("Internal error checking volumes");
	  vol += vs->vol * vs->en->vol[0] * 
	    exp_table[(int)(vs->en->vol[1] + vs->en->vol[2] * ((tim-t0) / vs->en->cnt))];
	}
	if (t1-1 < ntim) ntim= t1-1;
      }
      if (vol > max) max= vol;	
      tim= (ntim <= tim) ? tim+1 : ntim;
      if (ev && tim > ev->stim) tim= ev->stim;
    }
  }
  return max;
}

//
//      Generate a WAV header, and return a pointer to a static
//      44-byte buffer containing the header.  `tlen' is output length
//      in samples, `rate' is output sample rate in Hz, `bps' is bytes
//      per sample.
//

#define addU4(xx) { int a= xx; *p++= a; *p++= (a >>= 8); *p++= (a >>= 8); *p++= (a >>= 8); }
#define addStr(xx) { char *q= xx; *p++= *q++; *p++= *q++; *p++= *q++; *p++= *q++; }

char *
writeWAV(int tlen, int rate, int bps, int f_stereo) {
  static char buf[44];
  char *p= buf;
  addStr("RIFF");
  addU4(tlen*bps + 36);
  addStr("WAVE");
  addStr("fmt ");
  addU4(16);
  addU4(f_stereo ? 0x00020001 : 0x00010001);
  addU4(rate);
  addU4(rate * bps);
  addU4(0x0004 + 0x10000*(bps*4));  // 2,4 -> 8,16 - always assume stereo
  addStr("data");
  addU4(tlen*bps);
  return buf;
}

//
//	Dump out a list of events from `event'
//

void 
dumpEvents() {
  Event *ev;
  printf("   start logical     end vol-adj note\n");
  for (ev= event; ev; ev= ev->nxt) {
    printf("%8d%8d%8d%8g", ev->stim, ev->tim, ev->etim, ev->vol);
    if (ev->not0) printf(" `%s'", ev->not0->nam);
    if (ev->not1) printf(" `%s'", ev->not1->nam);
    printf("\n");
  }
  printf("\n");
}
      
// END //
