#include "snd.h"

static int even_channels(snd_info *sp, void *ptr)
{
  int val,height,chans,i;
  chan_info *cp;
  chans = sp->nchans;
  if (chans > 1)
    {
      height = (*((int *)ptr));
      val = height/chans - 16;
      if (val < 6) val = 6;
      for (i=0;i<chans;i++)
	{
	  cp = sp->chans[i];
	  XtUnmanageChild(chan_widget(cp,W_chn_main_window));
	  XtVaSetValues(chan_widget(cp,W_chn_main_window),XmNpaneMinimum,val-5,XmNpaneMaximum,val+5,NULL);
	}
    }
  return(0);
}

static int even_sounds(snd_info *sp,void *ptr)
{
  int width;
  width = (*((int *)ptr));
  XtVaSetValues(snd_widget(sp,W_snd_pane),XmNwidth,width,NULL);
  return(0);
}

void normalize_all_sounds(snd_state *ss)
{
  /* normalize: get size, #chans, #snds, set pane minima, force remanage(?), unlock */
  int sounds = 0,chans,chan_y,height,width,screen_y,i;
  int wid[1];
  for (i=0;i<ss->max_sounds;i++) if (snd_ok(ss->sounds[i])) sounds++;
  if (sound_style(ss) == SOUNDS_VERTICAL)
    {
      height = get_window_height(sound_PANE(ss)) - lisp_listener_height();
      /* if lisp listener, remove it from this calculation */
      /* all are lined up vertically, so we can just make all chans the same size */
      if (auto_resize(ss))
	{
	  screen_y = DisplayHeight(main_DISPLAY(ss),main_SCREEN(ss));
	  if (height > screen_y) height = screen_y;
	}
      else XtVaSetValues(main_SHELL(ss),XmNallowShellResize,TRUE,NULL); /* need temporary resize to change pane sizes below */
      chans = active_channels(ss,0);
      if (chans > 1)
	{
	  /* now we try to make room for the sound ctrl bar, each channel, perhaps the menu */
	  chan_y = (height-(sounds*ss->ctrls_height))/chans - 16;
	  /* probably can be 14 or 12 -- seems to be margin related or something */
	  wid[0] = chan_y;
	  map_over_sounds(ss,sound_lock_ctrls,NULL);
	  map_over_separate_chans(ss,channel_lock_pane,(void *)wid);
	  map_over_separate_chans(ss,channel_open_pane,NULL);
	  map_over_separate_chans(ss,channel_unlock_pane,NULL);
	  map_over_sounds(ss,sound_unlock_ctrls,NULL);
	}
      if (!(auto_resize(ss))) XtVaSetValues(main_SHELL(ss),XmNallowShellResize,FALSE,NULL);
    }
  else
    {
      height = get_window_height(sound_PANE(ss));
      /* each sound has the same vertical space, so here we are just normalizing chans per sound */
      if (sounds > 0) 
	{
	  width = get_window_width(main_PANE(ss));
	  if (ss->listening == 1) 
	    {
	      set_listener_width(width/4);
	      width *= .75;
	    }
	  width /= sounds;
	  map_over_sounds(ss,even_sounds,&width);
	}
      map_over_sounds(ss,sound_lock_ctrls,NULL);
      map_over_sounds(ss,even_channels,&height);
      map_over_separate_chans(ss,channel_open_pane,NULL);   /* manage the channel widgets */
      map_over_separate_chans(ss,channel_unlock_pane,NULL); /* allow pane to be resized */
      map_over_sounds(ss,sound_unlock_ctrls,NULL);
    }
}

static char sname[128];
char *shortname(snd_info *sp)
{
  if (is_link(sp->fullname))
    {
      sprintf(sname,"(%s)",sp->shortname);
      return(sname);
    }
  else return(sp->shortname);
}

void add_sound_data(char *filename, snd_info *sp, snd_state *ss)
{
  int i;
  for (i=0;i<sp->nchans;i++) add_channel_data(filename,sp->chans[i],sp->hdr,ss);
}

typedef struct {
  int amp_env_size;
  int slice; 
  int loc; 
  int samp; 
  int true_samp; 
  int samps_per_bin; 
  int samples;  
  env_info **eps; 
  int chans; 
  int fd; 
  int **ibufs;
  snd_fd **sfs;
} env_state;

#define AMP_BUFFER_SIZE 1024

static void free_env_info(env_info *ep)
{
  /* can be either during channel close, or premature work proc removal */
  if (ep)
    {
      /* fprintf(stderr,"free ep: %p %p %p ",(void *)ep,(void *)(ep->data_min),(void *)(ep->data_max)); */
      if (ep->data_max) free(ep->data_max);
      if (ep->data_min) free(ep->data_min);
      free(ep);
    }
}

static void free_env_state(snd_info *sp, int in_progress)
{ /* if in_progress, we're quitting prematurely, so everything must go */
  env_state *ep;
  env_info *es;
  int *buf;
  int i;
  if ((sp) && (sp->sgx) && ((sp->sgx)->env_data))
    {
      /* assume someone else is removing the work proc via remove_amp_env(sp) */
      ep = (env_state *)((sp->sgx)->env_data);
      if (ep->ibufs)
	{
	  for (i=0;i<ep->chans;i++)
	    {
	      buf = ep->ibufs[i];
	      if (buf) free(buf);
	      ep->ibufs[i] = NULL;
	    }
	  free(ep->ibufs);
	  ep->ibufs = NULL;
	}
      if (ep->eps)
	{
	  if (in_progress)
	    {
	      for (i=0;i<ep->chans;i++)
		{
		  es = ep->eps[i];
		  if (es) free_env_info(es);
		  ep->eps[i] = NULL;
		}
	    }
	  free(ep->eps);
	  ep->eps = NULL;
	}
      if (in_progress)
	{
	  if (ep->fd != -1)
	    {
	      snd_close(ep->fd);
	    }
	}
      if (ep->sfs)
	{
	  for (i=0;i<ep->chans;i++)
	    {
	      if (ep->sfs[i]) free_snd_fd(ep->sfs[i]);
	    }
	  free(ep->sfs);
	  ep->sfs = NULL;
	}
      free(ep);
      (sp->sgx)->env_data = NULL;
      if (!in_progress) (sp->sgx)->env_in_progress = 0; /* assume someone else does this in the premature-quit case */
    }
}

void free_chan_env(chan_info *cp)
{
  if (cp->amp_env) 
    {
      if (cp->amp_env == cp->original_env) cp->original_env = NULL;
      free_env_info((env_info *)(cp->amp_env));
      cp->amp_env = NULL;
    }
  if (cp->original_env) 
    {
      free_env_info((env_info *)(cp->original_env));
      cp->original_env = NULL;
    }
}

void free_sound_env(snd_info *sp, int in_progress)
{
  free_env_state(sp,in_progress);
}


static void allocate_env_state(snd_info *sp, snd_context *sgx, int samples, int file)
{
  int i,val;
  env_info *ep;
  env_state *es;
  env_info **e;
  int **bufs = NULL;
  snd_fd **sfs = NULL;
  sgx->env_data = (env_state *)calloc(1,sizeof(env_state));
  es = (env_state *)(sgx->env_data);
  es->samples = samples/sp->nchans;
  /* we want samps_per_bin to be useful over a wide range of file sizes */
  val = (float)(es->samples)/100000.0;
  if (val < 5) es->amp_env_size = 2048;
  else if (val < 10) es->amp_env_size = 4096;
  else if (val < 40) es->amp_env_size = 8192;
  else if (val < 100) es->amp_env_size = 16384;
  else if (val < 1000) es->amp_env_size = 32768;
  else es->amp_env_size = 65536; /* i.e. more than 10^8 samples/chan */
  es->samps_per_bin = ceil((float)(es->samples)/(float)(es->amp_env_size));
  es->chans = sp->nchans;
  es->slice = 0;
  es->loc = -1;
  es->samp = es->samps_per_bin;
  es->true_samp = 0;
  es->fd = -1;
  if (file) 
    {
      es->ibufs = (int **)calloc(sp->nchans,sizeof(int *));
      bufs = es->ibufs;
      es->sfs = NULL;
    }
  else
    {
      sfs = (snd_fd **)calloc(sp->nchans,sizeof(snd_fd *));
      es->sfs = sfs;
      es->ibufs = NULL;
    }
  es->eps = (env_info **)calloc(sp->nchans,sizeof(env_info *));
  e = es->eps;
  for (i=0;i<sp->nchans;i++)
    {
      e[i] = (env_info *)calloc(1,sizeof(env_info));
      ep = e[i];
      ep->amp_env_size = es->amp_env_size;
      ep->data_max = (int *)snd_calloc(sp->state,es->amp_env_size,sizeof(int));
      ep->data_min = (int *)snd_calloc(sp->state,es->amp_env_size,sizeof(int));
      ep->samps_per_bin = es->samps_per_bin;
      if (file)
	bufs[i] = (int *)snd_calloc(sp->state,AMP_BUFFER_SIZE,sizeof(int));
      else 
	{
	  sfs[i] = init_sample_read(0,sp->chans[i],READ_FORWARD);
	}
    }
}

	
/* using file reads here for optimization (slightly faster than edit-tree traversal below) */
/* in a one hour 44KHz stereo file, this takes about 1.5 mins to get the overall amp env */

static int run_original_env(snd_info *sp)
{
  /* calculate the amp env of each channel */
  /* this is an optimization for large sound display */
  int i,j,k,loc0,loc1,ymin,ymax,samp0,samp1,val;
  env_info *ep;
  env_state *es;
  int *buf;
  file_info *hdr;
  chan_info *cp;
  snd_context *sgx;
  sgx = sp->sgx;
  if (!(sgx->env_data)) 
    {
      hdr = sp->hdr; 
      allocate_env_state(sp,sgx,hdr->samples,1);
      return(FALSE);
    }
  es = (env_state *)(sgx->env_data);
  switch (es->slice)
    {
    case 0:
      /* open the file, set up all pointers, grab first buffer */
      /* start_time(); */
      es->fd = snd_open_read(sp->state,sp->fullname);
      hdr = sp->hdr;
      open_clm_file_descriptors(es->fd,hdr->format,c_snd_datum_size(hdr->format),hdr->data_location);
      clm_seek(es->fd,hdr->data_location,0);
      clm_read(es->fd,0,AMP_BUFFER_SIZE-1,es->chans,es->ibufs);
      es->slice++;
      return(FALSE);
      break;
    case 1: 
      /* spin down n points doing the cur min/max shuffle */
      /* when file end reached, increment slice */
      /* es->loc is the bin we're working on now */
      /* es->samp is the current sample within that bin (-1 = time to reset) */
      /* es->true_samp is where we are in the input file */
      /* es->samples is how many there are (per channel) */
      loc0 = es->loc;
      loc1 = loc0;
      samp0 = es->samp;
      samp1 = samp0;
      for (j=0;j<es->chans;j++)
	{
	  samp1 = samp0;
	  loc1 = loc0;
	  ep = es->eps[j];
	  buf = es->ibufs[j];
	  ymin = ep->data_min[loc1];
	  ymax = ep->data_max[loc1];
	  for (i=0;i<AMP_BUFFER_SIZE;i++)
	    {
	      if (samp1 == es->samps_per_bin)
		{
		  if (loc1>=0)
		    {
		      ep->data_max[loc1] = ymax;
		      ep->data_min[loc1] = ymin;
		    }
		  loc1++;
		  ymin = buf[i];
		  ymax = buf[i];
		  samp1 = 1;
		}
	      else
		{
		  val = buf[i];
		  if (ymin > val) ymin = val;
		  if (ymax < val) ymax = val;
		  samp1++;
		}
	    }
	  ep->data_max[loc1] = ymax; /* save for next invocation */
	  ep->data_min[loc1] = ymin;
	}
      es->loc = loc1;
      es->samp = samp1;
      es->true_samp += AMP_BUFFER_SIZE;
      if ((es->true_samp) >= (es->samples))
	{
	  es->slice++;
	  /* check for round-off errors */
	  if (es->loc < (es->amp_env_size-1))
	    {
	      for (j=0;j<es->chans;j++)
		{
		  ep = es->eps[j];
		  for (k=(es->loc + 1);k<es->amp_env_size;k++)
		    {
		      ep->data_max[k] = 0;
		      ep->data_min[k] = 0;
		    }
		}
	    }
	}
      else clm_read(es->fd,0,AMP_BUFFER_SIZE-1,es->chans,es->ibufs);
      return(FALSE);
      break;
    case 2:
      /* assign env_infos to the respective chan_info pointers */
      /* free and nullify all the rest of the env_state */
      /* return true to remove work proc */
      /* fprintf(stderr,"done in %d",run_time()); */
      snd_close(es->fd);
      for (i=0;i<es->chans;i++)
	{
	  cp = sp->chans[i];
	  cp->amp_env = es->eps[i];
	  cp->original_env = cp->amp_env;
	  ((env_info *)(cp->amp_env))->amp_ctr = 0; /* edit_ctr can change while we're working on the original envelope */
	  es->eps[i] = NULL;
	}
      free_env_state(sp,0);
      signal_amp_env_done(sp);
      return(TRUE);
      break;
    }
  return(TRUE);
}

static int run_another_env(snd_info *sp)
{
  /* calculate the amp env of each channel using edit-tree, not stored file data */
  int i,j,k,loc0,loc1,ymin,ymax,samp0,samp1,val;
  env_info *ep;
  env_state *es;
  chan_info *cp;
  snd_context *sgx;
  sgx = sp->sgx;
  if (!(sgx->env_data))
    {
      allocate_env_state(sp,sgx,(current_ed_samples(sp->chans[0]))*sp->nchans,0);
      return(FALSE);
    }
  es = (env_state *)(sgx->env_data);
  switch (es->slice)
    {
    case 0:
      loc0 = es->loc;
      loc1 = loc0;
      samp0 = es->samp;
      samp1 = samp0;
      for (j=0;j<es->chans;j++)
	{
	  samp1 = samp0;
	  loc1 = loc0;
	  ep = es->eps[j];
	  ymin = ep->data_min[loc1];
	  ymax = ep->data_max[loc1];
	  for (i=0;i<AMP_BUFFER_SIZE;i++)
	    {
	      if (samp1 == es->samps_per_bin)
		{
		  if (loc1>=0)
		    {
		      ep->data_max[loc1] = ymax;
		      ep->data_min[loc1] = ymin;
		    }
		  loc1++;
		  NEXT_SAMPLE(val,es->sfs[j]);
		  ymin = val;
		  ymax = val;
		  samp1 = 1;
		}
	      else
		{
		  NEXT_SAMPLE(val,es->sfs[j]);
		  if (ymin > val) ymin = val;
		  if (ymax < val) ymax = val;
		  samp1++;
		}
	    }
	  ep->data_max[loc1] = ymax; /* save for next invocation */
	  ep->data_min[loc1] = ymin;
	}
      es->loc = loc1;
      es->samp = samp1;
      es->true_samp += AMP_BUFFER_SIZE;
      if ((es->true_samp) >= (es->samples))
	{
	  es->slice++;
	  /* check for round-off errors */
	  if (es->loc < (es->amp_env_size-1))
	    {
	      for (j=0;j<es->chans;j++)
		{
		  ep = es->eps[j];
		  for (k=(es->loc + 1);k<es->amp_env_size;k++)
		    {
		      ep->data_max[k] = 0;
		      ep->data_min[k] = 0;
		    }
		}
	    }
	}
      return(FALSE);
      break;
    case 1:
      for (i=0;i<es->chans;i++)
	{
	  cp = sp->chans[i];
	  cp->amp_env = es->eps[i];
	  if (cp->edit_ctr == 0) cp->original_env = cp->amp_env;
	  ((env_info *)(cp->amp_env))->amp_ctr = cp->edit_ctr;
	  es->eps[i] = NULL;
	}
      free_env_state(sp,0);
      signal_amp_env_done(sp);
      sp->env_anew = 0;
      return(TRUE);
      break;
    }
  return(TRUE);
}

int run_amp_env(snd_info *sp)
{
  if (sp->env_anew)
    return(run_another_env(sp));
  else return(run_original_env(sp));
}

void clobber_amp_env(chan_info *cp)
{
  env_info *ep;
  snd_info *sp;
  /* if (!(cp->original_env)) return; */
  sp = cp->sound;
  remove_amp_env(sp); /* if work proc in progress, it's no longer needed (or trustworthy) */
  free_env_state(sp,0);
  ep = (env_info *)(cp->amp_env);
  if ((ep) && (ep != cp->original_env)) free_env_info(ep);
  cp->amp_env = NULL;
}

int amp_env_maxamp_ok(chan_info *cp)
{
  env_info *ep;
  ep = (env_info *)(cp->amp_env);
  if ((ep) && (cp->edit_ctr == ep->amp_ctr)) return(1);
  if ((cp->edit_ctr == 0) && (ep = (env_info *)(cp->original_env)))
    {
      if ((cp->amp_env) && (cp->amp_env != ep)) clobber_amp_env(cp);
      cp->amp_env = ep;
      return(1);
    }
  return(0);
}

float amp_env_maxamp(chan_info *cp)
{
  env_info *ep;
  int i,ymax;
  ymax = 0;
  ep = (env_info *)(cp->amp_env);
  for (i=0;i<ep->amp_env_size;i++)
    {
      if (ymax < ep->data_max[i]) ymax = ep->data_max[i];
      if (ymax < -ep->data_min[i]) ymax = -ep->data_min[i];
    }
  return(ymax*clm_sndflt);
}

int amp_env_usable(chan_info *cp,float samples_per_pixel) 
{
  env_info *ep;
  snd_info *sp;
  chan_info *ncp;
  int i;
  snd_context *sgx;
  ep = (env_info *)(cp->amp_env);
  if ((ep) && (cp->edit_ctr == ep->amp_ctr))
    return(samples_per_pixel >= ep->samps_per_bin);
  if ((cp->edit_ctr == 0) && (ep = (env_info *)(cp->original_env)))
    {
      if ((cp->amp_env) && (cp->amp_env != ep)) clobber_amp_env(cp);
      cp->amp_env = ep;
      return(samples_per_pixel >= ep->samps_per_bin);
    }
  sp = cp->sound;
  sgx = sp->sgx;
  if ((!sgx) || (sgx->env_in_progress)) return(0); /* i.e. not ready yet or not a normal channel (region data) */
  /* either there is no original env, or we haven't scanned at the current edit_ctr */
  if (current_ed_samples(cp) > 100000)
    {
      for (i=0;i<sp->nchans;i++)
	{
	  ncp = sp->chans[i];
	  if ((ep = (env_info *)(ncp->amp_env)) && (ep != ncp->original_env)) 
	    {
	      free_env_info(ep); 
	      ncp->amp_env = NULL;
	    }
	}
      start_amp_env(sp,1);
    }
  return(0);
}

int amp_env_graph(chan_info *cp, axis_info *ap, float samples_per_pixel, int srate) 
{
  float step,x,xf,xk,pinc = 0.0;
  int ymin,ymax,i,j,xi,k,kk;
  env_info *ep;
  ep = (env_info *)(cp->amp_env);
  step = samples_per_pixel/(float)(ep->samps_per_bin);
  xf = (float)(ap->losamp)/(float)(ep->samps_per_bin);
  j=0;
  x=ap->x0;
  xi=grf_x(x,ap);
  i = ap->losamp;
  xk = i;
  ymin=3276800; /* maybe we have a dc offset of 100.0 (sigh) */
  ymax=-3276800;
  if (cp->printing) pinc = (float)samples_per_pixel/(float)srate;
  while (i<=ap->hisamp)
    {
      k=xf;
      xf+=step;
      kk=xf;
      if (kk>=ep->amp_env_size) kk=ep->amp_env_size-1;
      for (;k<=kk;k++)
	{
	  if (ep->data_min[k] < ymin) ymin=ep->data_min[k];
	  if (ep->data_max[k] > ymax) ymax=ep->data_max[k];
	}
      xk += samples_per_pixel;
      i = xk;
      set_grf_points(xi,j,grf_y(clm_sndflt * ymin,ap),grf_y(clm_sndflt * ymax,ap));
      if (cp->printing) {x+=pinc; ps_set_grf_points(x,j,ymin*clm_sndflt,ymax*clm_sndflt);}
      xi++;
      j++;
      ymin=3276800;
      ymax=-3276800;
    }
  return(j);
}

static char src_txt_buf[8];

#define TOTAL_RATS 77

static char *rat_names[] = {
  "1/20", "5/96", "1/16", "3/40", "5/64", "1/12", "3/32", "1/10", "5/48", "7/64", " 1/8", "9/64", "3/20", "5/32", 
  " 1/6", "3/16", " 1/5", "5/24", "7/32", " 1/4", "9/32", "3/10", "5/16", " 1/3", " 3/8", " 2/5", "5/12", "7/16", 
  " 1/2", "9/16", " 3/5", " 5/8", " 2/3", " 3/4", " 4/5", " 5/6", " 7/8", " 1/1", " 9/8", " 6/5", " 5/4", " 4/3", 
  " 3/2", " 8/5", " 5/3", " 7/4", " 2/1", " 9/4", "12/5", " 5/2", " 8/3", " 3/1", "16/5", "10/3", " 7/2", " 4/1", 
  " 9/2", "24/5", " 5/1", "16/3", " 6/1", "32/5", "20/3", " 7/1", " 8/1", " 9/1", "48/5", "10/1", "32/3", "12/1", 
  "64/5", "40/3", "14/1", "16/1", "18/1", "96/5", "20/1"};

static float rat_values[] = {
  0.05, 0.052083332, 0.0625, 0.075, 0.078125, 0.083333336, 0.09375, 0.1, 0.104166664, 0.109375, 0.125, 0.140625, 
  0.15, 0.15625, 0.16666667, 0.1875, 0.2, 0.20833333, 0.21875, 0.25, 0.28125, 0.3, 0.3125, 0.33333334, 0.375, 
  0.4, 0.41666666, 0.4375, 0.5, 0.5625, 0.6, 0.625, 0.6666667, 0.75, 0.8, 0.8333333, 0.875, 1.0, 1.125, 1.2, 
  1.25, 1.3333334, 1.5, 1.6, 1.6666666, 1.75, 2.0, 2.25, 2.4, 2.5, 2.6666667, 3.0, 3.2, 3.3333333, 3.5, 4.0, 
  4.5, 4.8, 5.0, 5.3333335, 6.0, 6.4, 6.6666665, 7.0, 8.0, 9.0, 9.6, 10.0, 10.666667, 12.0, 12.8, 13.333333, 
  14.0, 16.0, 18.0, 19.2, 20.0};

float ur_srate_changed(float val, char *srcbuf, int style, int tones)
{
  char *sfs;
  int semi,i,j;
  switch (style)
    {
    case SPEED_AS_RATIO: 
      for (i=1;i<TOTAL_RATS;i++)
	{
	  if (rat_values[i]>val) break;
	}
      sprintf(srcbuf,"%s",rat_names[i-1]);
      return(rat_values[i-1]);
      break;
    case SPEED_AS_SEMITONE: 
      /* find closest semitone to val */
      semi = round(log(val)*((float)tones/log(2.0)));
      /* space until (-) num (-52 to 52 is its range if 12-tone) */
      for (i=0;i<3;i++) srcbuf[i] = ' '; 
      sprintf(src_txt_buf,"%d",semi);
      j = strlen(src_txt_buf) - 1;
      for (i=3;(i>=0) && (j>=0);i--,j--) srcbuf[i] = src_txt_buf[j];
      return(pow(2.0,((float)semi/(float)tones)));
      break;
    default: 
      sfs=prettyf(val,2);
      fill_number(sfs,srcbuf);
      if (sfs) free(sfs);
      return(val);
      break;
    }
}

float srate_changed(int ival, char *srcbuf, int style, int tones)
{
  return(ur_srate_changed(exp((float)(ival-450)/150.0),srcbuf,style,tones));
}

