#include "snd.h"

#ifdef NEXT
  #include <sys/dir.h>
  #include <sys/dirent.h>
#else
  #include <dirent.h>
#endif

chan_info *virtual_selected_channel(chan_info *cp)
{
  snd_info *sp;
  sp = cp->sound;
  if ((sp->combining == CHANNELS_SEPARATE) || (sp->selected_channel == NO_SELECTION)) 
    return(cp);
  else return(sp->chans[sp->selected_channel]);
}

static void goto_previous_graph (chan_info *cp, int count)
{
  snd_info *sp,*osp;
  snd_state *ss;
  chan_info *ncp,*vcp;
  int i,k,j,chan;
  sp=cp->sound;
  ss=cp->state;
  osp = sp;
  vcp = virtual_selected_channel(cp);
  chan = vcp->chan;
  ncp = NULL;
  k = -count;
  if (chan > 0)
    {
      /* goto previous channel in current sound */
      k -= chan;
      if (k <= 0)
	ncp = sp->chans[chan+count];
    }
  while (k > 0)
    {
      /* look for previous sound, (wrap around) */
      /* goto channel n */
      for (i=(sp->index-1);i>=0;i--)
	{
	  if (snd_ok(ss->sounds[i]))
	    {
	      sp = (snd_info *)(ss->sounds[i]);
	      j = k;
	      k -= sp->nchans;
	      if (k <= 0)
		ncp = sp->chans[sp->nchans-j];
	      break;
	    }
	}
      if (k > 0)
	{
	  for (i=ss->max_sounds-1;i>=sp->index;i--)
	    {
	      if (snd_ok(ss->sounds[i]))
		{
		  sp = (snd_info *)(ss->sounds[i]);
		  j = k;
		  k -= sp->nchans;
		  if (k <= 0)
		    ncp = sp->chans[sp->nchans-j];
		  break;
		}
	    }
	}
    }
  if (ncp == vcp) return;
  if (!ncp) snd_printf(ss,"goto previous graph lost!");
  select_channel(ncp->sound,ncp->chan);
  normalize_sound(ss,ncp->sound,osp,ncp); /* snd-xsnd.c */
  goto_graph(ncp);
}

static void goto_next_graph (chan_info *cp, int count)
{
  snd_info *sp,*osp;
  snd_state *ss;
  chan_info *ncp,*vcp;
  int i,k,j,chan;
  sp=cp->sound;
  osp = sp;
  ss=cp->state;
  vcp = virtual_selected_channel(cp);
  chan = vcp->chan;
  ncp = NULL;
  k = count;
  if (chan < (sp->nchans-1))
    {
      /* goto next channel in current sound */
      k -= (sp->nchans-chan-1);
      if (k <= 0)
	ncp = sp->chans[chan+count];
    }
  while (k > 0)
    {
      /* look for next sound, (wrap around) */
      /* goto channel 0 */
      for (i=(sp->index+1);i<ss->max_sounds;i++)
	{
	  if (snd_ok(ss->sounds[i]))
	    {
	      sp = (snd_info *)(ss->sounds[i]);
	      j = k;
	      k -= sp->nchans;
	      if (k <= 0)
		ncp = sp->chans[j-1];
	      break;
	    }
	}
      if (k > 0)
	{
	  for (i=0;i<=sp->index;i++)
	    {
	      if (snd_ok(ss->sounds[i]))
		{
		  sp = (snd_info *)(ss->sounds[i]);
		  j = k;
		  k -= sp->nchans;
		  if (k <= 0)
		    ncp = sp->chans[j-1];
		  break;
		}
	    }
	}
    }
  if (ncp == vcp) return;
  if (!ncp) snd_printf(ss,"goto next graph lost!");
  select_channel(ncp->sound,ncp->chan);
  normalize_sound(ss,ncp->sound,osp,ncp);
  goto_graph(ncp);
}

static int updating = 0;

int update_graph(chan_info *cp, void *ptr)
{
  /* don't put display stuff here!  This is needed so that the fft display does not get caught in a loop */
  double cur_srate;
  snd_state *ss;
  snd_info *sp;
  axis_info *ap;
  if (updating) return(0);
  updating = 1;
  ss = cp->state;
  sp = cp->sound;
  /* next two are needed by fft and ufun displays, but if put off until make_graph cause
   * the display to happen twice in some cases 
   */
  ap = cp->axis;
  if (ap)
    {
      cur_srate = (double)(snd_SRATE(sp));
      ap->losamp = ap->x0*cur_srate; 
      if (ap->losamp < 0) ap->losamp = 0;
      ap->hisamp = ap->x1*cur_srate;
    }
  if ((cp->ffting) && (!(chan_fft_in_progress(cp)))) calculate_fft(cp,NULL);
  if ((sp->auto_ufun) && (sp->ufunp)) invoke_ufun_with_ptr(cp,sp->ufunp,sp->ufun_args,sp->ufun_nargs,0,0);
  display_channel_data(cp,sp,ss);
  updating = 0;
  return(0);
}

#define X_RANGE_CHANGEOVER 20.0

void initialize_scrollbars(chan_info *cp)
{
  axis_info *ap;
  snd_info *sp;
  ap = cp->axis;
  sp = cp->sound;
  set_scrollbar(chan_widget(cp,W_chn_sx),ap->sx,ap->zx,sp->sx_scroll_max);
  set_scrollbar(chan_widget(cp,W_chn_sy),ap->sy,ap->zy,SCROLLBAR_MAX);
  if ((ap->xmax-ap->xmin) < X_RANGE_CHANGEOVER)
    set_scrollbar(chan_widget(cp,W_chn_zx),sqrt(ap->zx),.1,SCROLLBAR_MAX);  /* assume size is 10% of scrollbar length */
  else set_scrollbar(chan_widget(cp,W_chn_zx),pow(ap->zx,.333),.1,SCROLLBAR_MAX);
  set_scrollbar(chan_widget(cp,W_chn_zy),ap->zy,.1,SCROLLBAR_MAX);          /* assume 1.0 here so sqrt/cube decision, if any, is not needed */
  if ((cp->chan == 0) && (chan_widget(cp,W_chn_gsy)))
    {
      set_scrollbar(chan_widget(cp,W_chn_gsy),cp->gsy,cp->gzy,SCROLLBAR_MAX);
      set_scrollbar(chan_widget(cp,W_chn_gzy),cp->gzy,1.0/(float)(sp->nchans),SCROLLBAR_MAX);
    }
}

#define INITIAL_EDIT_SIZE 8

void add_channel_data_1(chan_info *cp, snd_info *sp, snd_state *ss, int graphed)
{
  /* initialize channel, including edit/sound lists */
  axis_info *ap;
  float ymin,ymax,xmin,xmax,y0,y1,x0,x1,dur;
  char *label;
  file_info *hdr;
  int samples_per_channel;
  hdr = sp->hdr;
  samples_per_channel = hdr->samples/hdr->chans;
  ymax = ss->ymax;
  ymin = ss->ymin;
  xmax = ss->xmax;
  xmin = ss->xmin;
  x0 = ss->initx0;
  x1 = ss->initx1;
  y0 = ss->inity0;
  y1 = ss->inity1;
  label = snd_string_time;
  if (sp->debug_names) label = sp->debug_names[cp->chan];
  dur = (float)samples_per_channel/(float)hdr->srate;
  if (xmax == 0.0) xmax = dur;
  if (x1 == 0.0) x1 = dur;
  if ((sp->debug_amps) && (ss->fit_data != 0)) /* -1 = default: fit only 32 linear */
    {
      if ((ss->fit_data == 1) || (hdr->format == snd_32_linear))
	{
	  ymax = sp->debug_amps[cp->chan];
	  if ((ymax == 0.0) || (ymax == cl_false)) ymax = 1.0;
	  ymin = -ymax;
	  xmax = dur;
	  xmin = 0.0;
	  y0 = ymin; y1 = ymax; x0 = xmin; x1 = xmax;
	}
    }
  if (dur <= 0.0)
    {
      /* empty sound */
      label = snd_string_no_data;
      xmin = 0.0;
    }
  else
    {
      if (xmax > dur) xmax = dur;
      if (xmin >= xmax) xmin = xmax-.01;
      if (x0 >= x1) x0 = x1-.01;
    }
  if (ymin >= ymax) ymin = ymax-.01;
  if (y0 >= y1) y0 = y1-.01;
  ap = make_axis_info(cp,xmin,xmax,ymin,ymax,label,x0,x1,y0,y1,NULL);
  if (dur == 0)
    {
      ap->zx = 1.0;
      ap->sx = 1.0;
      ap->no_data = 1;
    }
  else
    {
      ap->zx = (ap->x1-ap->x0)/(ap->xmax-ap->xmin);
      ap->sx = (ap->x0-ap->xmin)/(ap->xmax-ap->xmin);
      ap->no_data = 0;
    }
  ap->zy = (ap->y1-ap->y0)/(ap->ymax-ap->ymin);
  ap->sy = (ap->y0-ap->ymin)/(ap->ymax-ap->ymin);
  cp->axis = ap;
  cp->active_axis = ap;
  if (graphed) initialize_scrollbars(cp);

  /* our initial edit_list size will be relatively small */
  cp->edit_size = INITIAL_EDIT_SIZE;
  cp->edit_ctr = 0;
  cp->edits = (ed_list **)calloc(cp->edit_size,sizeof(ed_list *));
  cp->samples = (int *)calloc(cp->edit_size,sizeof(int));
  cp->sound_size = INITIAL_EDIT_SIZE;
  cp->sound_ctr = 0;
  cp->sounds = (snd_data **)calloc(cp->sound_size,sizeof(snd_data *));
  cp->samples[0] = samples_per_channel;
}

void add_channel_data(char *filename, chan_info *cp, file_info *hdr, snd_state *ss)
{
  int fd;
  int *datai;
  snd_info *sp;
  file_info *chdr;
  sp = cp->sound;
  add_channel_data_1(cp,sp,ss,1);
  cp->edits[0] = initial_ed_list(0,hdr->samples/hdr->chans);
  chdr = make_file_info(filename,ss); /* need one separate from snd_info case */
  fd = snd_open_read(ss,filename);
  if (fd != -1)
    {
      open_clm_file_descriptors(fd,chdr->format,c_snd_datum_size(chdr->format),chdr->data_location);
      datai = make_file_state(fd,chdr,io_in_f,cp->chan,FILE_BUFFER_SIZE);
      cp->sounds[0] = make_snd_data_file(filename,datai,(int *)(datai[io_dats+6+cp->chan]),chdr,0,cp->edit_ctr);
    }
}

static void resize_sy(chan_info *cp)
{
  /* something changed the y axis view, so the scale scroller needs to reflect that change (in size and position) */
  axis_info *ap;
  ap = cp->axis;
  set_scrollbar(chan_widget(cp,W_chn_sy),(ap->y0-ap->ymin)/(ap->ymax-ap->ymin),(ap->y1-ap->y0)/(ap->ymax-ap->ymin),SCROLLBAR_MAX);
}

void resize_sx(chan_info *cp)
{
  axis_info *ap;
  snd_info *sp;
  ap = cp->axis;
  sp = cp->sound;
  set_scrollbar(chan_widget(cp,W_chn_sx),
		(ap->x0-ap->xmin)/(ap->xmax-ap->xmin),
		(ap->x1-ap->x0)/(ap->xmax-ap->xmin),
		sp->sx_scroll_max);
}

void resize_zx(chan_info *cp)
{
  axis_info *ap;
  ap = cp->axis;
  if ((ap->xmax-ap->xmin) < X_RANGE_CHANGEOVER)
    set_scrollbar(chan_widget(cp,W_chn_zx),sqrt(ap->zx)*.9,.1,SCROLLBAR_MAX);
  else set_scrollbar(chan_widget(cp,W_chn_zx),pow(ap->zx*.9,1.0/3.0),.1,SCROLLBAR_MAX);
}

static void resize_zy(chan_info *cp)
{
  axis_info *ap;
  ap = cp->axis;
  set_scrollbar(chan_widget(cp,W_chn_zy),sqrt(ap->zy)*.9,.1,SCROLLBAR_MAX);
}

static void set_y_bounds(axis_info *ap)
{
  float range;
  range = ap->zy*(ap->ymax - ap->ymin);
  ap->y0 = ap->ymin + ap->sy*(ap->ymax - ap->ymin);
  ap->y1 = (ap->y0 + range);
  if (ap->y1 > ap->ymax)
    {
      ap->y1 = ap->ymax;
      ap->y0 = ap->y1 - range;
    }
  if (ap->y0 < ap->ymin) ap->y0 = ap->ymin;
}

void set_y_limits(chan_info *cp, axis_info *ap, float val)
{
  ap->y0 = -val;
  ap->y1 = val;
  ap->ymin = -val;
  ap->ymax = val;
  update_graph(cp,NULL);
}

void set_x_bounds(axis_info *ap)
{
  float range;
  range = ap->zx*(ap->xmax - ap->xmin);
  ap->x0 = ap->xmin + ap->sx*(ap->xmax - ap->xmin);
  ap->x1 = (ap->x0 + range);
  if (ap->x1 > ap->xmax)
    {
      ap->x1 = ap->xmax;
      ap->x0 = ap->x1 - range;
    }
  if (ap->x0 < ap->xmin) ap->x0 = ap->xmin;
}

static void apply_y_axis_change (axis_info *ap, chan_info *cp)
{
  snd_info *sp;
  chan_info *ncp;
  axis_info *nap;
  int i;
  float zy,sy;
  set_y_bounds(ap);
  update_graph(cp,NULL);
  sp = cp->sound;
  if (sp->combining != CHANNELS_SEPARATE)
    {
      sy = ap->sy;
      zy = ap->zy;
      for (i=1;i<sp->nchans;i++)
	{
	  ncp = sp->chans[i];
	  nap = ncp->axis;
	  if (nap)
	    {
	      nap->zy = zy;
	      nap->sy = sy;
	      set_y_bounds(nap);
	      update_graph(ncp,NULL);
	    }
	}
    }
}

void sy_changed(int value,chan_info *cp)
{
  axis_info *ap;
  float low;
  ap = cp->axis;
  low = get_scrollbar(chan_widget(cp,W_chn_sy),value,SCROLLBAR_MAX);
  ap->sy = (1.0-ap->zy)*low;
  apply_y_axis_change(ap,cp);
}

static void reset_x_display(chan_info *cp, float sx, float zx)
{
  axis_info *ap;
  ap=cp->axis;
  ap->sx = sx;
  ap->zx = zx;
  set_x_bounds(ap);
  resize_sx(cp);
  resize_zx(cp);
  update_graph(cp,NULL);
}

static void update_xs(chan_info *ncp, axis_info *ap)
{
  float scl;
  axis_info *nap;
  nap = ncp->axis;
  if (nap->xmax > 0.0)
    {
      scl = ap->xmax/nap->xmax;
      reset_x_display(ncp,ap->sx*scl,ap->zx*scl);
    }
}

void apply_x_axis_change(axis_info *ap, chan_info *cp, snd_info *sp)
{
  int i;
  sync_info *si;
  si = NULL;
  set_x_bounds(ap);
  update_graph(cp,NULL);
  if (sp->syncing)
    {
      si = snd_sync(cp->state);
      for (i=0;i<si->chans;i++) 
	{
	  if (cp != si->cps[i]) update_xs(si->cps[i],ap);
	}
      free_sync_info(si);
    }
  else 
    {
      if (sp->combining != CHANNELS_SEPARATE)
	{
	  for (i=1;i<sp->nchans;i++) update_xs(sp->chans[i],ap);
	}
    }
}

static void focus_x_axis_change(axis_info *ap, chan_info *cp, snd_info *sp, int focus_style)
{
  /* prepare for set_x_bounds given desired focus point, then drop into default
   * we need to set ap->sx to reflect the new zx value and the focus type
   * if focus_left - go on (nothing to do)
   *    focus_right - get old right, set sx to keep it as is
   *    focus_middle - ditto mid window
   *    focus_active - find the currently active entity, if none use focus_middle 
   */
  chan_info *ncp;
  int newf;
  if (ap->xmax == 0.0) return;
  if (focus_style != FOCUS_LEFT)
    {
      switch (focus_style)
	{
	case FOCUS_RIGHT:   ap->x0 = ap->x1 - ap->zx*(ap->xmax - ap->xmin); break;
	case FOCUS_MIDDLE:  ap->x0 = 0.5 * ((ap->x1 + ap->x0) - ap->zx*(ap->xmax - ap->xmin)); break;
	case FOCUS_ACTIVE:
	  ncp = virtual_selected_channel(cp);
	  /* axes should be the same, since all move together in this mode */
	  if (ncp->cursor_visible) 
	    newf = ncp->cursor;
	  else
	    {
	      if (active_selection(ncp)) 
		newf = selection_beg(ncp);
	      else
		{
		  if (active_mix(ncp))
		    newf = mix_beg(ncp);
		  else
		    {
		      if (active_mark(ncp))
			newf = mark_beg(ncp);
		      else newf = -1;
		    }
		}
	    }
	  if (newf != -1)
	    ap->x0 = (double)newf/(double)snd_SRATE(ncp) - 0.5*ap->zx*(ap->xmax - ap->xmin);
	  break;
	}
      if (ap->x0 < 0.0) ap->x0 = 0.0;
      ap->sx = (float)(ap->x0 - ap->xmin) / (float)(ap->xmax - ap->xmin);
    }
  apply_x_axis_change(ap,cp,sp);
}

void sx_changed(int value,chan_info *cp)
{
  /* treat as centered with non-slider trough as defining current bounds */
  axis_info *ap;
  snd_info *sp;
  float low;
  ap = cp->axis;
  sp = cp->sound;
  low = get_scrollbar(chan_widget(cp,W_chn_sx),value,sp->sx_scroll_max);
  ap->sx = low*(1.0-ap->zx);
  apply_x_axis_change(ap,cp,sp);
}

void sx_incremented(chan_info *cp, float amount)
{
  axis_info *ap;
  ap = cp->axis;
  ap->sx += (ap->zx*amount);
  if (ap->sx < 0.0) ap->sx = 0.0;
  if (ap->sx > (1.0-ap->zx)) ap->sx = 1.0 - ap->zx;
  apply_x_axis_change(ap,cp,cp->sound);
  resize_sx(cp);
}

static void zx_incremented(chan_info *cp, float amount)
{ /* kbd arrows etc -- needs to be able to return to original */
  axis_info *ap;
  ap = cp->axis;
  ap->zx *= amount;
  apply_x_axis_change(ap,cp,cp->sound);
  resize_sx(cp);
}

void zy_changed(int value,chan_info *cp)
{ 
  axis_info *ap;
  float old_zy;
  ap = cp->axis;
  if (value < 1) value = 1;
  old_zy = ap->zy;
  ap->zy = sqr(get_scrollbar(chan_widget(cp,W_chn_zy),value,SCROLLBAR_MAX));
  ap->sy += (.5*(old_zy - ap->zy)); /* try to keep wave centered */
  apply_y_axis_change(ap,cp);
  resize_sy(cp);
}

void zx_changed(int value,chan_info *cp)
{ /* scrollbar change */
  axis_info *ap;
  snd_info *sp;
  snd_state *ss;
  sp = cp->sound;
  ss = cp->state;
  ap = cp->axis;
  if (ap->xmax == 0.0) return;
  if (value < 1) value = 1;
  if ((ap->xmax-ap->xmin) < X_RANGE_CHANGEOVER)
    ap->zx = sqr(get_scrollbar(chan_widget(cp,W_chn_zx),value,SCROLLBAR_MAX));
  else ap->zx = cube(get_scrollbar(chan_widget(cp,W_chn_zx),value,SCROLLBAR_MAX));
  /* if cursor visible, focus on that, else selection, else mark, else left side */
  focus_x_axis_change(ap,cp,sp,ss->zoom_focus_anchor);
  resize_sx(cp);
}

void gzy_changed(int value,chan_info *cp)
{
  float chan_frac,new_gsy,new_size;
  cp->gzy = get_scrollbar(chan_widget(cp,W_chn_gzy),value,SCROLLBAR_MAX);
  chan_frac = 1.0 / ((float)(((snd_info *)(cp->sound))->nchans));
  new_size = chan_frac + ((1.0-chan_frac)*cp->gzy);
  if ((cp->gsy+new_size) > 1.0) new_gsy = 1.0-new_size; else new_gsy = cp->gsy;
  if (new_gsy < 0.0) new_gsy = 0.0;
  set_scrollbar(chan_widget(cp,W_chn_gsy),new_gsy,new_size,SCROLLBAR_MAX);
  map_over_sound_chans(cp->sound,update_graph,NULL);
}

void gsy_changed(int value,chan_info *cp)
{
  float low;
  low = get_scrollbar(chan_widget(cp,W_chn_gsy),value,SCROLLBAR_MAX);
  cp->gsy = (1.0-cp->gzy)*low;
  map_over_sound_chans(cp->sound,update_graph,NULL);
}

int move_axis(chan_info *cp, axis_info *ap, int x)
{
  /* need to scroll axis forward or backward -- distance per call depends on x distance from end of axis */
  float off;
  int nx;
  if (x > ap->x_axis_x1)
    {
      off = x - ap->x_axis_x1;
      ap->sx += (off*ap->zx/1000.0);
      if (ap->sx > (1.0-ap->zx)) ap->sx = 1.0 - ap->zx;
      nx = ap->x_axis_x1;
    }
  else
    {
      off = ap->x_axis_x0 - x;
      ap->sx -= (off*ap->zx/1000.0);
      if (ap->sx < 0.0) ap->sx = 0.0;
      nx = ap->x_axis_x0;
    }
  apply_x_axis_change(ap,cp,cp->sound);
  resize_sx(cp);
  return(nx);
}

void set_axes(chan_info *cp,float x0,float x1,float y0,float y1)
{
  axis_info *ap;
  ap = cp->axis;
  ap->x0 = x0;
  ap->x1 = x1;
  ap->zx = (x1-x0)/(ap->xmax-ap->xmin);
  ap->sx = (x0-ap->xmin)/(ap->xmax-ap->xmin);
  resize_sx(cp);
  resize_zx(cp);
  ap->y0 = y0;
  ap->y1 = y1;
  ap->zy = (y1-y0)/(ap->ymax-ap->ymin);
  ap->sy = (y0-ap->ymin)/(ap->ymax-ap->ymin);
  resize_sy(cp);
  resize_zy(cp);
}

void set_xy_bounds(chan_info *cp,axis_info *ap)
{
  set_y_bounds(ap);
  resize_sy(cp);
  set_x_bounds(ap);
  resize_sx(cp);
}


/* ---------------- CHANNEL GRAPHICS ---------------- */

static void display_zero (chan_info *cp)
{
  axis_info *ap;
  short zero;
  ap = cp->axis;
  if ((ap->y0 < 0.0) && (ap->y1 > 0.0))
    {
      zero = grf_y(0.0,ap);
      draw_line(copy_context(cp),ap->x_axis_x0,zero,ap->x_axis_x1,zero);
      if (cp->printing) ps_draw_line(cp,ap->x_axis_x0,0,ap->x_axis_x1,0);
    }
}

static char chn_id_str[16];

static void display_channel_id (chan_info *cp, int height, int chans)
{
  int x0,y0;
  if ((chans>1) || (cp->edit_ctr > 0))
    {
      set_peak_numbers_font(cp);
      if (cp->printing) ps_set_peak_numbers_font(cp);
      x0 = 5;
      y0 = height - 5;
      if (chans > 1)
	{
	  if (cp->edit_ctr == 0)
	    sprintf(chn_id_str,"%s%d",snd_string_channel_id,(cp->chan+1)); /* cp chan numbers are 0 based to index sp->chans array */
	  else sprintf(chn_id_str,"%s%d:(%d)",snd_string_channel_id,(cp->chan+1),cp->edit_ctr);
	}
      else sprintf(chn_id_str,"(%d)",cp->edit_ctr);
      draw_string(copy_context(cp),x0,y0,chn_id_str,strlen(chn_id_str));
      if (cp->printing) ps_draw_string(cp,x0,y0,chn_id_str);
    }
}

static void make_wavogram(chan_info *cp, snd_info *sp, snd_state *ss);

int make_graph(chan_info *cp, snd_info *sp, snd_state *ss)
{
  /* axes are already set, determining the data we will show -- do we need explicit clipping ? */
  int i,j,samps,xi;
  axis_info *ap;
  float samples_per_pixel,xf,finc,pinc = 0.0;
  double x,incr;  
  /* in long files with small windows we can run into floating point errors that accumulate
   * in the loop (incrementing by a truncated amount each time), making the x amounts smaller
   * they should be (so the graph appears squeezed).
   *
   * There is a similar problem with exceedingly long files (say 1 hour at 44KHz), in which 
   * the x increment gets quantized making the graphs step-like, so the
   * axis_info fields x0, x1, and x_scale are doubles as well. The y axis is less problematic
   * since we are assuming sound files here.
   *
   * And if the data window itself is small (in the sense that we're looking at individual samples)
   * we need to make decisions about in-between sample mouse clicks, and the cursor placement
   * must match the sample placement (which means that the initial x-axis offset from the
   * first sample (similarly the last) must be taken into account).  Currently, the selection
   * definition (by mouse drag) has a sloppy rounding mechanism (snd-clip.c round_up) so that
   * within reason only samples within the selection-rectangle are in the final selection,
   * and cursor placement (mouse click) rounds to the nearest sample (snd-xchn.c cursor_round).
   *
   * And lastly, the axis x_scale double can be enormous whereas the between-sample choice can
   * be tiny, so we're pushing the arithmetic into dangerous realms.  This stuff has been tested
   * on some extreme cases (hour-long 44KHz stereo), but there's always another special case...
   */
  int ina,ymin,ymax,inc;
  snd_fd *sf;
  int x_start,x_end;
  double start_time,cur_srate;
  if (ss->wavo) {make_wavogram(cp,sp,ss); return(0);}
  ap = cp->axis;
  /* check for no graph */
  if ((!ap) || (!(ap->graph_active)) || (ap->x0 == ap->x1)) return(0);
  cur_srate = (double)snd_SRATE(sp);
  ap->losamp = ap->x0*cur_srate;
  if (ap->losamp < 0) ap->losamp = 0;
  if (ap->x0 != (ap->losamp/cur_srate)) ap->losamp++;
  start_time = (double)(ap->losamp)/cur_srate;
  x_start = grf_x(start_time,ap);
  ap->hisamp = ap->x1*cur_srate;
  if ((ap->losamp == 0) && (ap->hisamp == 0)) return(0);
  x_end = grf_x((double)(ap->hisamp)/cur_srate,ap);
  sf = init_sample_read(ap->losamp,cp,READ_FORWARD);
  samps = ap->hisamp - ap->losamp+1;
  if ((x_start == x_end) || (samps <= 1))
    samples_per_pixel = 0.01; /* any non-zero value < 1.0 should be ok here */
  else samples_per_pixel = (float)(samps-1)/(float)(x_end-x_start);
  allocate_grf_points();
  if (cp->printing) ps_allocate_grf_points();
  if (samples_per_pixel < 1.0)
    {
      /* i.e. individual samples are widely spaced */
      incr = 1.0 / samples_per_pixel;
      for (j=0,i=ap->losamp,x=x_start;i<=ap->hisamp;i++,j++,x+=incr)
	{
	  NEXT_SAMPLE(ina,sf);
	  set_grf_point(x,j,grf_y(clm_sndflt * ina,ap));

	  /* for colored lines (mix as green for example), we'd check sf->cb[ED_COLOR],
	   * if it has changed, send out the current points in the current color,
	   * change to the new color.
	   * mix groups by color could OR together base colors -- could be based
	   * on current color map.
	   * ufun edits could specifiy any color etc.
	   * need map from cb[ED_COLOR] to Pixel (or could it be the index itself?)
	   */

	  if (cp->printing) ps_set_grf_point((double)i/cur_srate,j,ina*clm_sndflt);
	}
      draw_grf_points(ss,cp,j);
      if (cp->printing) ps_draw_grf_points(ss,cp,ap,j);
    }
  else
    {
      if ((samples_per_pixel < 5.0) && (samps < POINT_BUFFER_SIZE))
	{
	  incr = (double)1.0 /cur_srate;
	  for (j=0,i=ap->losamp,x=start_time;i<=ap->hisamp;i++,j++,x+=incr)
	    {
	      NEXT_SAMPLE(ina,sf);
	      set_grf_point(grf_x(x,ap),j,grf_y(clm_sndflt * ina,ap));
	      if (cp->printing) ps_set_grf_point(x,j,ina*clm_sndflt);
	    }
	  draw_grf_points(ss,cp,j);
	  if (cp->printing) ps_draw_grf_points(ss,cp,ap,j);
	}
      else
	{
	  /* take min, max */
	  if ((ss->subsampling) && (amp_env_usable(cp,samples_per_pixel))) 
	    j = amp_env_graph(cp,ap,samples_per_pixel,snd_SRATE(cp));
	  else
	    {
	      if ((ss->subsampling) && (samples_per_pixel >= 40.0))
		{
		  inc = (int)floor(samples_per_pixel/20.0);
		  finc = (float)inc;
		}
	      else 
		{
		  inc = 1;
		  finc = 1.0;
		}
	      j = 0;      /* graph point counter */
	      x=ap->x0;
	      xi=grf_x(x,ap);
	      i=ap->losamp;   /* sample counter */
	      xf=0.0;     /* samples per pixel counter */
	      ymin=3276800;
	      ymax=-3276800;
	      if (cp->printing) pinc = samples_per_pixel/cur_srate;
	      while (i<=ap->hisamp)
		{
		  NEXT_SUB_SAMPLE(ina,sf,inc);
		  /* this only sees every inc-th sample, so it can introduce sampling artifacts */
		  if (ina > ymax) ymax = ina;
		  if (ina < ymin) ymin = ina;
		  xf+=finc;
		  i+=inc;
		  if (xf>samples_per_pixel)
		    {
		      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++;
		      xf -= samples_per_pixel;
		      ymin=3276800;
		      ymax=-3276800;
		    }
		}
	    }
	  draw_both_grf_points(ss,cp,j);
	  if (cp->printing) ps_draw_both_grf_points(ss,cp,ap,j);
	}
    }
  free_snd_fd(sf);
  return(j);
}

static fft_peak peak_freqs[MAX_NUM_PEAKS];
static fft_peak peak_amps[MAX_NUM_PEAKS];

static int compare_peak_amps(const void *pk1, const void *pk2)
{
  if (((fft_peak *)pk1)->amp > ((fft_peak *)pk2)->amp) return(-1);
  else if (((fft_peak *)pk1)->amp == ((fft_peak *)pk2)->amp) return(0);
  return(1);
}

static short linear_or_log_x(float x, float scaler, axis_info *fap, snd_state *ss)
{
  if (ss->logxing) return(grf_x(log(x+1.0)*scaler,fap)); else return(grf_x(x,fap));
}

#define dB(py) ((py <= .001) ? -60.0 : (20.0*(log10(py))))

static short linear_or_log_y(float py, axis_info *fap, snd_state *ss)
{
  if (ss->dBing) py = dB(py);
  return(grf_y(py,fap));
}

static float ps_linear_or_log_x(float x, float scaler, snd_state *ss)
{
  if (ss->logxing) return(log(x+1.0)*scaler); else return(x);
}

static float ps_linear_or_log_y(float py, snd_state *ss)
{
  if (ss->dBing) return(dB(py)); else return(py);
}

#define LOG_FACTOR 25.0
/* determines how we view the log */

static void display_peaks(chan_info *cp,axis_info *fap,float *data,int scaler,int samps,float samps_per_pixel,int fft_data)
{
  int num_peaks,row,col,tens,i;
  float amp0,px;
  char *fstr;
  if (samps > (scaler*10)) tens = 2; else if (samps > scaler) tens = 1; else if (samps > (scaler/10)) tens = 0; else tens = -1;
  num_peaks = (fap->y_axis_y0-fap->y_axis_y1) / 20;
  if (num_peaks > MAX_NUM_PEAKS) num_peaks = MAX_NUM_PEAKS;
  if (fft_data)
    num_peaks = find_and_sort_fft_peaks(data,peak_freqs,num_peaks,samps,1.0,samps_per_pixel); /* "srate" of 1.0 => "freqs" are between 0 and 1.0 */
  else num_peaks = find_and_sort_peaks(data,peak_freqs,num_peaks,samps);
  if ((num_peaks == 1) && (peak_freqs[0].freq == 0.0)) return;
  if (num_peaks > 6)
    {
      for (i=0;i<num_peaks;i++) peak_amps[i]=peak_freqs[i];
      qsort((void *)peak_amps,num_peaks,sizeof(fft_peak),compare_peak_amps);
      if (num_peaks < 12) amp0 = peak_amps[2].amp; else amp0 = peak_amps[5].amp;
      set_bold_peak_numbers_font(cp);
      if (cp->printing) ps_set_bold_peak_numbers_font(cp);
      col = fap->x_axis_x1 - 30 - tens*5;
      row = fap->y_axis_y1 + 15;
      for (i=0;i<num_peaks;i++)
	{
	  if (peak_freqs[i].amp >= amp0)
	    {
	      px = peak_freqs[i].freq;
	      fstr = prettyf(px*scaler,tens);
	      draw_string(copy_context(cp),col,row,fstr,strlen(fstr));
	      if (cp->printing) ps_draw_string(cp,col,row,fstr);
	      free(fstr);
	      fstr=NULL;
	    }
	  row+=15;
	}
    }
  else amp0 = 100.0;
  set_peak_numbers_font(cp);
  if (cp->printing) ps_set_peak_numbers_font(cp);
  /* choose a small font for these numbers */
  col = fap->x_axis_x1 - 30 - tens*5;
  row = fap->y_axis_y1 + 15;
  for (i=0;i<num_peaks;i++)
    {
      if (peak_freqs[i].amp < amp0)
	{
	  px = peak_freqs[i].freq;
	  fstr = prettyf(px*scaler,tens);
	  draw_string(copy_context(cp),col,row,fstr,strlen(fstr));
	  if (cp->printing) ps_draw_string(cp,col,row,fstr);
	  free(fstr);
	  fstr=NULL;
	}
      row+=15;
    }
}

static void make_fft_graph(chan_info *cp, snd_info *sp, snd_state *ss)
{
  /* axes are already set, data is in the fft_info struct */
  /* since the fft size menu callback can occur while we are calculating the next fft, we have to lock the current size until the graph goes out */
  fft_info *fp,*nfp;
  axis_info *fap;
  float *data,*tdata;
  chan_info *ncp;
  float incr,x,scale;
  int i,j,xi,samps,di;
  float samples_per_pixel,xf,ina,ymax,scaler,data_max;
  fp = cp->fft;
  if (chan_fft_in_progress(cp)) return;
  fap = fp->axis;
  if (!fap->graph_active) return;
  data = fp->data;
  if (ss->transform_type == fourier)
    {
      samps = fp->current_size*ss->sonogram_cutoff/2;
      incr = (float)snd_SRATE(sp)/(float)(fp->current_size);
    }
  else
    {
      samps = fp->current_size * ss->sonogram_cutoff;
      incr = 1.0;
    }
  data_max = 0.0;
  if ((!(ss->normalize_fft)) && (sp->nchans > 1) && (sp->combining == CHANNELS_SUPERIMPOSED))
    {
      for (j=0;j<sp->nchans;j++)
	{
	  ncp = sp->chans[j];
	  nfp = ncp->fft;
	  tdata = nfp->data;
	  for (i=0;i<samps;i++) if (tdata[i]>data_max) data_max=tdata[i];
	}
    }
  else
    {
      if (ss->transform_type == fourier)
	{
	  for (i=0;i<samps;i++) if (data[i]>data_max) data_max=data[i];
	}
      else
	{
	  for (i=0;i<samps;i++)
	    {
	      if (data[i]>data_max) 
		data_max=data[i];
	      else
		if (data[i]<-data_max) 
		  data_max=-data[i];
	    }
	}
    }
  if (data_max == 0.0) data_max = 1.0;
  if (ss->normalize_fft)
    scale = 1.0/data_max;
  else 
    {
      if (ss->transform_type == fourier)
	{
	  scale = 1.0/(float)samps;
	  di = 10*data_max*scale + 1;
	  if (di == 1)
	    {
	      di = 100*data_max*scale + 1;
	      data_max = (float)di/100.0;
	    }
	  else data_max = (float)di/10.0;
	}
      else 
	{
	  scale = 1.0;
	  fap->y0 = -data_max;
	  fap->ymin = -data_max;
	}
      fap->y1 = data_max;
      fap->ymax = data_max;
    }
  allocate_grf_points();
  if (cp->printing) ps_allocate_grf_points();
  samples_per_pixel = (float)samps/(float)(fap->x_axis_x1-fap->x_axis_x0);
  if (samples_per_pixel < 4.0)
    {
      if ((!ss->dBing) && (!ss->logxing))
	{
	  for (i=0,x=0.0;i<samps;i++,x+=incr)
	    {
	      set_grf_point(grf_x(x,fap),i,grf_y(data[i]*scale,fap));
	      if (cp->printing) ps_set_grf_point(x,i,data[i]*scale);
	    }
	}
      else
	{
	  if (ss->logxing) 
	    {
	      ymax = LOG_FACTOR;
	      incr = ymax/(float)samps;
	      scaler = 1.0/log(ymax+1.0);
	    }
	  else scaler = 0.0;
	  for (i=0,x=0.0;i<samps;i++,x+=incr)
	    {
	      set_grf_point(linear_or_log_x(x,scaler,fap,ss),i,linear_or_log_y(data[i]*scale,fap,ss));
	      if (cp->printing) ps_set_grf_point(ps_linear_or_log_x(x,scaler,ss),i,ps_linear_or_log_y(data[i]*scale,ss));
	    }
	}
      draw_grf_points(ss,cp,i);
      if (cp->printing) ps_draw_grf_points(ss,cp,fap,i);
    }
  else
    {
      /* take max -- min not interesting here */
      j = 0;      /* graph point counter */
      i=0;
      x=0.0;
      xi=grf_x(0.0,fap);
      xf=0.0;     /* samples per pixel counter */
      if (ss->logxing) 
	{
	  ymax = LOG_FACTOR;
	  incr = ymax/(float)samps;
	  scaler = 1.0/log(ymax+1.0);
	}
      else scaler = 0.0;
      ymax=-1.0;
      if ((!ss->dBing) && (!ss->logxing))
	{
	  while (i<samps)
	    {
	      ina = data[i];
	      if (ina > ymax) ymax = ina;
	      xf+=1.0;
	      i++;
	      if (xf>samples_per_pixel)
		{
		  set_grf_point(xi,j,grf_y(ymax*scale,fap));
		  if (cp->printing) {x+=(incr*samples_per_pixel); ps_set_grf_point(x,j,ymax*scale);}
		  xi++;
		  j++;
		  xf -= samples_per_pixel;
		  ymax=-1.0;
		}
	    }
	}
      else
	{
	  while (i<samps)
	    {
	      ina = data[i];
	      if (ina > ymax) ymax = ina;
	      xf+=1.0;
	      i++;
	      if (xf>samples_per_pixel)
		{
		  set_grf_point(linear_or_log_x(x,scaler,fap,ss),j,linear_or_log_y(ymax*scale,fap,ss));
		  if (cp->printing) ps_set_grf_point(ps_linear_or_log_x(x,scaler,ss),j,ps_linear_or_log_y(ymax*scale,ss));
		  x+=(incr*samples_per_pixel);
		  j++;
		  xf -= samples_per_pixel;
		  ymax=-1.0;
		}
	    }
	}
      draw_grf_points(ss,cp,j);
      if (cp->printing) ps_draw_grf_points(ss,cp,fap,j);
    }
  if (ss->peaking) 
    {
      if (ss->transform_type == fourier)
	display_peaks(cp,fap,data,snd_SRATE(sp)*ss->sonogram_cutoff/2,samps,samples_per_pixel,1);
      else display_peaks(cp,fap,data,fp->current_size*ss->sonogram_cutoff,samps,samples_per_pixel,1);
    }
}


void display_fft_peaks(chan_info *cp)
{
  /* put unsorted peak info in help window */
  char *buffer;
  fft_info *fp;
  axis_info *fap;
  float *data;
  float srate2;
  int i,samps,num_peaks,tens,srate;
  float samples_per_pixel;
  fp = cp->fft;
  if (!fp) return;
  fap = fp->axis;
  if ((!fap) || (!fap->graph_active)) return;
  data = fp->data;
  samps = fp->current_size/2;
  samples_per_pixel = (float)samps/(float)(fap->x_axis_x1-fap->x_axis_x0);
  srate = snd_SRATE(cp);
  srate2 = (float)srate * .5;
  if (samps > (5*srate)) tens = 2; else if (samps > (int)srate2) tens = 1; else if (samps > (srate/20)) tens = 0; else tens = -1;
  num_peaks = find_and_sort_fft_peaks(data,peak_freqs,MAX_NUM_PEAKS,samps,1.0,samples_per_pixel);
  if ((num_peaks == 1) && (peak_freqs[0].freq == 0.0)) return;
  buffer = (char *)calloc(64,sizeof(char));
  sprintf(buffer,"%.*f  %.3f\n",tens,peak_freqs[0].freq * srate2,peak_freqs[0].amp);
  snd_help((snd_state *)(cp->state),"fft peaks",buffer);
  for (i=1;i<num_peaks;i++)
    {
      sprintf(buffer,"%.*f  %.3f\n",tens,peak_freqs[i].freq * srate2,peak_freqs[i].amp);
      add_snd_help((snd_state *)(cp->state),buffer);
    }
  free(buffer);
}

static float skew_color(float x, float base)
{
  if ((base <= 0.0) || (base == 1.0)) return(x);
  return((pow(base,x)-1.0)/(base-1.0));
}

static int js[GRAY_SCALES];

static void make_sonogram(chan_info *cp, snd_info *sp, snd_state *ss)
{
  sono_info *si;
  int i,slice,fwidth,fheight,rectw,recth,j,bins;
  fft_info *fp;
  axis_info *fap;
  float *fdata;
  float binval,xf,xfincr,yf,yfincr,scaler = 0.0,frectw,frecth,yval,xscl,scl;
  if (chan_fft_in_progress(cp)) return;
  si = (sono_info *)(cp->sonogram_data);
  bins = si->target_bins * ss->sonogram_cutoff;
  allocate_grf_points();
  if (cp->printing) ps_allocate_grf_points();
  if ((si) && (si->scale > 0.0))
    {
      if (ss->logxing) scaler = 1.0/log(LOG_FACTOR+1.0);
      scl = si->scale; 
      fp = cp->fft;
      fap = fp->axis;
      fwidth = fap->x_axis_x1 - fap->x_axis_x0;
      fheight = fap->y_axis_y0 - fap->y_axis_y1;
      frectw = (float)fwidth/(float)(si->target_slices);
      frecth = (float)fheight/(float)bins;
      xscl = (float)(fap->x1 - fap->x0)/(float)(si->target_slices);
      rectw = ceil(frectw);
      recth = ceil(frecth);
      if (rectw==0) rectw=1;
      if (recth==0) recth=1;
      if (ss->transform_type == fourier)
	{
	  if (ss->logxing)
	    yfincr = (ss->sonogram_cutoff * LOG_FACTOR)/(float)bins;
	  else yfincr = ss->sonogram_cutoff * (float)snd_SRATE(cp) * 0.5 / (float)bins;
	}
      else yfincr = 1.0;
      xfincr = ((float)fwidth/(float)(si->target_slices));
      xf = 2+fap->x_axis_x0;
      for (slice=0;slice<si->active_slices;slice++,xf+=xfincr)
	{
	  for (i=0;i<GRAY_SCALES;i++) js[i] = 0;
	  fdata = si->data[slice];
	  for (yf=0.0,i=0;i<bins;i++,yf+=yfincr)
	    {
	      /* above is fdata[i-1], left is si->data[slice-1][i] */
	      binval = fdata[i]/scl;
	      if (ss->dBing) binval = 1.0 + (dB(binval))/60.0;
	      if (binval >= ss->color_cutoff)
		{
		  if (ss->color_inverted) 
		    j = skew_color((1.0 - binval),ss->color_scale)*GRAY_SCALES; 
		  else j = skew_color(binval,ss->color_scale)*GRAY_SCALES;
		  if (j>0) j--;
		  if (ss->logxing)
		    set_sono_rectangle(js[j],j,xf,grf_y(log(yf+1.0)*scaler,fap)-recth,rectw,recth);
		  else set_sono_rectangle(js[j],j,xf,grf_y(yf,fap)-recth,rectw,recth);
		  if (cp->printing)
		    {
		      if (ss->logxing) yval = log(yf+1.0)*scaler; else yval = yf;
		      if (j>=1)
			ps_draw_sono_rectangle(cp,fap,j,fap->x0 + xscl*slice,yval,frectw,frecth);
		    }
		  js[j]++;
		}
	    }
	  for (i=0;i<GRAY_SCALES;i++)
	    {
	      if (js[i] > 0) draw_sono_rectangles(cp,i,js[i]);
	    }
	}
      reset_color(cp);
      if (cp->printing) ps_reset_color(cp);
    }
}

static void rotate_matrix(float xangle, float yangle, float zangle, float xscl, float yscl, float zscl, float *mat)
{
  /* return rotation matrix for rotation through angles xangle, yangle, then zangle with scaling by xscl, yscl, zscl */
  float sinx,siny,sinz,cosx,cosy,cosz,deg;
  deg = two_pi / 360.0;
  sinx = sin(xangle*deg);
  siny = sin(yangle*deg);
  sinz = sin(zangle*deg);
  cosx = cos(xangle*deg);
  cosy = cos(yangle*deg);
  cosz = cos(zangle*deg);
  mat[0] = cosy*cosz*xscl;
  mat[1] = (sinx*siny*cosz - cosx*sinz)*yscl;
  mat[2] = (cosx*siny*cosz + sinx*sinz)*zscl;
  mat[3] = cosy*sinz*xscl;
  mat[4] = (sinx*siny*sinz + cosx*cosz)*yscl;
  mat[5] = (cosx*siny*sinz - sinx*cosz)*zscl;
  mat[6] = -siny*xscl;
  mat[7] = sinx*cosy*yscl;
  mat[8] = cosx*cosy*zscl;
}

static void rotate(float *xyz, float *mat)
{ /* use rotation/scaling matrix set up by rotate_matrix to rotate and scale the vector xyz */
  float x,y,z;
  x = xyz[0];
  y = xyz[1];
  z = xyz[2];
  xyz[0] = mat[0]*x + mat[1]*y + mat[2]*z;
  xyz[1] = mat[3]*x + mat[4]*y + mat[5]*z;
  xyz[2] = mat[6]*x + mat[7]*y + mat[8]*z;
}

void reset_spectro(snd_state *state)
{
  state->sonogram_cutoff = 1.0;
  state->spectro_hop = 4;
  state->spectro_xscl = 1.0;
  state->spectro_yscl = 1.0;
  state->spectro_zscl = 0.1;
  state->spectro_zangle = -2.0;
  state->spectro_xangle = 90.0;
  state->spectro_yangle = 0.0;
  state->spectrogram_color = -1;
  state->sonogram_color = 0;
  state->color_cutoff = .003;
  state->color_scale = 0.5;
  state->color_inverted = 1;
  state->wavo_hop = 3;
  state->wavo_trace = 64;
}

static void make_spectrogram(chan_info *cp, snd_info *sp, snd_state *ss)
{
  sono_info *si;
  fft_info *fp;
  axis_info *fap;
  float *fdata;
  float matrix[9];
  float xyz[3];
  float xoff,yoff,x,y,xincr,yincr,x0,y0,binval,scl;
  float fwidth,fheight,zscl,yval,xval;
  int bins,slice,i,j,xx,yy;
  if (chan_fft_in_progress(cp)) return;
  allocate_grf_points();
  if (cp->printing) ps_allocate_grf_points();
  si = (sono_info *)(cp->sonogram_data);
  if ((si) && (si->scale > 0.0))
    {
      scl = si->scale; /* unnormalized fft doesn't make much sense here (just washes out the graph) */
      fp = cp->fft;
      fap = fp->axis;
      bins = si->target_bins * ss->sonogram_cutoff;
      fwidth = (fap->x_axis_x1 - fap->x_axis_x0);
      fheight = (fap->y_axis_y1 - fap->y_axis_y0); /* negative! */
      xincr = fwidth/(float)bins;
      yincr = fheight/(float)si->active_slices;
      x0 = (fap->x_axis_x0+fap->x_axis_x1)*0.5;
      y0 = (fap->y_axis_y0+fap->y_axis_y1)*0.5;
      if (!(ss->dBing))
	zscl = -(ss->spectro_zscl*fheight/scl);
      else zscl = -(ss->spectro_zscl*fheight);
      rotate_matrix(ss->spectro_xangle,ss->spectro_yangle,ss->spectro_zangle,
		    ss->spectro_xscl,ss->spectro_yscl,zscl,
		    matrix);
      if (ss->spectrogram_color == -1)
	{
	  for (slice=0,xoff=fap->x_axis_x0,yoff=fap->y_axis_y0;slice<si->active_slices;slice++,yoff+=yincr)
	    {
	      fdata = si->data[slice];
	      x=xoff;
	      y=yoff;
	      for (i=0;i<bins;i++,x+=xincr)
		{
		  xyz[0]=x-x0; 
		  xyz[1]=y-y0; 
		  if (!(ss->dBing)) 
		    xyz[2]=fdata[i];
		  else {binval = fdata[i]/scl; xyz[2] = 1.0 + (dB(binval))/60.0;}
		  rotate(xyz,matrix);
		  yval = xyz[1]+xyz[2];
		  xval = xyz[0];
		  set_grf_point(xval+x0,i,yval+y0);
		  if (cp->printing) ps_set_grf_point(xval,i,yval);
		}
	      draw_grf_points(ss,cp,bins);
	      if (cp->printing) ps_draw_grf_points(ss,cp,fap,bins);
	    }
	}
      else
	{
	  /* spectrogram in various colors */
	  allocate_color_map(ss,ss->spectrogram_color);
	  for (slice=0,xoff=fap->x_axis_x0,yoff=fap->y_axis_y0;slice<si->active_slices;slice++,yoff+=yincr)
	    {
	      fdata = si->data[slice];
	      x=xoff;
	      y=yoff;
	      xyz[0]=x-x0; xyz[1]=y-y0; xyz[2]=fdata[0]; rotate(xyz,matrix); xx=xyz[0]+x0; yy=xyz[1]+xyz[2]+y0;
	      for (i=0;i<bins;i++,x+=xincr)
		{
		  xyz[0]=x-x0; xyz[1]=y-y0;
		  binval = fdata[i]/scl;
		  if (!(ss->dBing)) 		  
		    xyz[2]=fdata[i];
		  else {xyz[2] = 1.0 + (dB(binval))/60.0; binval = xyz[2];}
		  rotate(xyz,matrix);
		  yval = xyz[1]+xyz[2];
		  xval = xyz[0];
		  if (binval >= ss->color_cutoff)
		    {
		      if (ss->color_inverted) 
			j = skew_color((1.0 - binval),ss->color_scale)*GRAY_SCALES; 
		      else j = skew_color(binval,ss->color_scale)*GRAY_SCALES;
		      if (j>0) j--;
		      if (j >= GRAY_SCALES) j=GRAY_SCALES-1;
		      draw_spectro_line(cp,j,xx,yy,xval+x0,yval+y0);
		      if (cp->printing) ps_draw_spectro_line(cp,j,xx,yy,xval+x0,yval+y0);
		    }
		  xx = xval+x0; yy=yval+y0;
		}
	    }
	  reset_color(cp);
	  if (cp->printing) ps_reset_color(cp);
	}
    }
}

static void make_wavogram(chan_info *cp, snd_info *sp, snd_state *ss)
{
  float xoff,x,y,x0,y0,xincr;
  float width,height,zscl,yval,xval,binval;
  int i,j,ina,yincr,yoff,xx,yy;
  float matrix[9];
  float xyz[3];
  snd_fd *sf;
  axis_info *ap;
  ap = cp->axis;
  ap->losamp = ap->x0*snd_SRATE(sp);
  sf = init_sample_read(ap->losamp,cp,READ_FORWARD);
  allocate_grf_points();
  if (cp->printing) ps_allocate_grf_points();
  width = (ap->x_axis_x1 - ap->x_axis_x0);
  height = (ap->y_axis_y1 - ap->y_axis_y0); /* negative! */
  xincr = width/(float)(ss->wavo_trace);
  yincr = -ss->wavo_hop;
  if (yincr > 0) yincr = -yincr;
  if (yincr == 0) yincr = -1;
  x0 = (ap->x_axis_x0+ap->x_axis_x1)*0.5;
  y0 = (ap->y_axis_y0+ap->y_axis_y1)*0.5;
  zscl = -(ss->spectro_zscl*height);
  rotate_matrix(ss->spectro_xangle,ss->spectro_yangle,ss->spectro_zangle,
		ss->spectro_xscl,ss->spectro_yscl,zscl,
		matrix);
  if (ss->spectrogram_color == -1)
    {
      for (xoff=ap->x_axis_x0,yoff=ap->y_axis_y0;yoff>ap->y_axis_y1;yoff+=yincr)
	{
	  x=xoff;
	  y=yoff;
	  for (i=0;i<ss->wavo_trace;i++,x+=xincr)
	    {
	      NEXT_SAMPLE(ina,sf);
	      xyz[0]=x-x0; xyz[1]=y-y0; xyz[2]=ina*clm_sndflt;
	      rotate(xyz,matrix);
	      yval = xyz[1]+xyz[2];
	      xval = xyz[0];
	      set_grf_point(xval+x0,i,yval+y0);
	      if (cp->printing) ps_set_grf_point(xval,i,yval);
	    }
	  draw_grf_points(ss,cp,ss->wavo_trace);
	  if (cp->printing) ps_draw_grf_points(ss,cp,ap,ss->wavo_trace);
	}
    }
  else
    {
      allocate_color_map(ss,ss->spectrogram_color);
      for (xoff=ap->x_axis_x0,yoff=ap->y_axis_y0;yoff>ap->y_axis_y1;yoff+=yincr)
	{
	  xx=-1;
	  x=xoff;
	  y=yoff;
	  yy = y0; /* ? */
	  for (i=0;i<ss->wavo_trace;i++,x+=xincr)
	    {
	      NEXT_SAMPLE(ina,sf);
	      binval = ina * clm_sndflt;
	      xyz[0]=x-x0; xyz[1]=y-y0; xyz[2]=binval;
	      rotate(xyz,matrix);
	      yval = xyz[1]+xyz[2];
	      xval = xyz[0];
	      /* for color decision here we need absolute value of data */
	      if (binval < 0.0) binval = -binval;
	      if ((binval >= ss->color_cutoff) && (xx != -1))
		{
		  if (ss->color_inverted) 
		    j = skew_color((1.0 - binval),ss->color_scale)*GRAY_SCALES; 
		  else j = skew_color(binval,ss->color_scale)*GRAY_SCALES;
		  if (j>0) j--;
		  if (j >= GRAY_SCALES) j=GRAY_SCALES-1;
		  draw_spectro_line(cp,j,xx,yy,xval+x0,yval+y0);
		  if (cp->printing) ps_draw_spectro_line(cp,j,xx,yy,xval+x0,yval+y0);
		}
	      xx = xval+x0; yy=yval+y0;
	    }
	}
      reset_color(cp);
      if (cp->printing) ps_reset_color(cp);
    }
  free_snd_fd(sf);
}

static void make_ufun_graph(chan_info *cp, snd_info *sp, snd_state *ss)
{
  ufun_info *up;
  axis_info *uap = NULL;
  int i,j,grf_len;
  float x,y,samples_per_pixel,xinc,xf,ymin,ymax,xi,pinc;
  up = (ufun_info *)(cp->ui);
  if (up) uap = up->axis;
  if ((!uap) || (!uap->graph_active)) return;
  allocate_grf_points();
  if (cp->printing) ps_allocate_grf_points();
  /* check for up->length > pixels available and use ymin ymax if needed */
  samples_per_pixel = (float)(up->length)/(float)(uap->x_axis_x1 - uap->x_axis_x0);
  if (samples_per_pixel < 4.0)
    {
      grf_len = up->length;
      xinc = 1.0/(float)grf_len;
      for (i=0,x=0.0;i<grf_len;i++,x+=xinc)
	{
	  y = up->data[i];
	  set_grf_point(grf_x(x,uap),i,grf_y(y,uap));
	  if (cp->printing) ps_set_grf_point(x,i,y);
	}
      draw_grf_points(ss,cp,grf_len);
      if (cp->printing) ps_draw_grf_points(ss,cp,uap,grf_len);
    }
  else
    {
      j = 0;
      i = 0;
      xf = 0.0;
      x = 0.0;
      pinc = samples_per_pixel/(float)(up->length);
      xi = grf_x(0.0,uap);
      ymin = 32768.0;
      ymax = -32768.0;
      while (i < up->length)
	{
	  y = up->data[i];
	  if (y > ymax) ymax = y;
	  if (y < ymin) ymin = y;
	  xf += 1.0;
	  i++;
	  if (xf>samples_per_pixel)
	    {
	      set_grf_points(xi,j,grf_y(ymin,uap),grf_y(ymax,uap));
	      if (cp->printing) {x += pinc; ps_set_grf_points(x,j,ymin,ymax);}
	      xi++;
	      j++;
	      xf -= samples_per_pixel;
	      ymin=32767.0;
	      ymax=-32768.0;
	    }
	}
      draw_both_grf_points(ss,cp,j);
      if (cp->printing) ps_draw_both_grf_points(ss,cp,uap,j);
    }
  if ((ss->peaking) && (up->peaks_ok)) display_peaks(cp,uap,up->data,up->scaler,up->length-1,samples_per_pixel,0);
}

static void display_channel_data_with_size (chan_info *cp, snd_info *sp, snd_state *ss, int width, int height, int offset)
{
  int with_fft,with_ufun,displays,points;
  axis_info *ap = NULL;
  axis_info *fap = NULL;
  axis_info *uap = NULL;
  fft_info *fp = NULL;
  ufun_info *up = NULL;
  ap = cp->axis;
  /* now check for fft/wave/user-function decisions */
  ap->height = height;
  ap->window_width = width;
  ap->y_offset = offset;
  if (cp->waving) displays = 1; else displays = 0;

  if ((cp->ufuning) && (up = (ufun_info *)(cp->ui)))
    {
      with_ufun = 1;
      displays++;
      uap = up->axis;
      uap->height = height;
      uap->y_offset = offset;
      uap->window_width = width;
    }
  else with_ufun = 0;

  if ((cp->ffting) && (fp = cp->fft))
    {
      with_fft = 1;
      displays++;
      fap = fp->axis; 
      fap->height = height;
      fap->y_offset = offset;
      fap->window_width = width;
    }
  else with_fft = 0;

  if (cp->waving) ap->width = width/displays;
  if (with_fft) fap->width =  width/displays;
  if (with_ufun) uap->width = width/displays;

  /* now the x axis offsets for fap and uap */
  if (with_fft) {if (cp->waving) fap->graph_x0 = ap->width; else fap->graph_x0 = 0;}
  if (with_ufun) uap->graph_x0 = uap->width*(displays - 1);

  if ((!with_fft) && (!(cp->waving)) && (!(with_ufun)))
    clear_window(ap->ax);
  else 
    {
      if (sp->combining != CHANNELS_SUPERIMPOSED)
	cp->clear = 1; /* deferred clear (can be wave, fft, ufun triggered) in make_axes (snd-xutils.c) */
      else 
	{
	  if (cp->chan == 0)
	    cp->clear = updating; /* a horrible kludge... */
	  else cp->clear = 1;
	}
    }

  marks_off(cp);
  selection_off(cp);
  if (cp->waving)
    {
      if (ss->wavo)
	{
	  if (ap->y_axis_y0 == ap->y_axis_y1) make_axes(cp,ap,ss->x_axis_style); /* first time needs setup */
	  ap->y0 = ap->x0;
	  ap->y1 = ap->y0+(float)(ss->wavo_trace * (ap->y_axis_y0 - ap->y_axis_y1)) / ((float)(ss->wavo_hop)*snd_SRATE(sp));
	  ap->x1 = ap->x0 + (float)(ss->wavo_trace)/(float)snd_SRATE(sp);
	}
      make_axes(cp,ap,ss->x_axis_style);
      cp->cursor_visible = 0;
      points = make_graph(cp,sp,ss);
      if ((ss->consoling) && (cp->mixes) && (cp->mix_dragging)) mix_save_graph(ss,((mixdata *)(cp->mix_dragging))->wg,points);
      if (cp->cursor_on) draw_graph_cursor(cp);
    }
  if (with_fft)
    {
      if (ss->fft_style == spectrogram) cp->drawing = 0;
      make_axes(cp,fap,(ss->x_axis_style == X_IN_SAMPLES) ? X_IN_SECONDS : ss->x_axis_style);
      cp->drawing = 1;
      if (!cp->waving)
	{ /* make_graph does this -- sets graph_low_SAMPLE, needed by fft to find its starting point */
	  ap->losamp = ap->x0*(double)snd_SRATE(sp);
	  ap->hisamp = ap->x1*(double)snd_SRATE(sp);
	}
      switch (ss->fft_style)
	{
	case normal_fft: make_fft_graph(cp,sp,ss); break;
	case sonogram: make_sonogram(cp,sp,ss); break;
	case spectrogram: make_spectrogram(cp,sp,ss); break;
	default: snd_printf(ss,"unknown fft style"); break;
	}
    }
  if (with_ufun)
    {
      make_axes(cp,uap,ss->x_axis_style);
      if ((!(cp->waving)) && (!(with_fft)))
	{
	  ap->losamp = ap->x0*(double)snd_SRATE(sp);
	  ap->hisamp = ap->x1*(double)snd_SRATE(sp);
	}
      make_ufun_graph(cp,sp,ss);
    }
  if (cp->waving)
    {
      display_selection(cp);
      if ((cp->marks) && (ss->marks_visible)) display_channel_marks(cp);
      if (cp == selected_channel(ss)) display_graph_border(cp);
      if (ss->zero_visible) display_zero(cp);
      if ((ss->consoling) && (cp->mixes)) display_channel_mixes(cp);
    }
  else {if (cp->mixes) release_mixes(cp);}
  if ((displays > 0) && (sp->combining != CHANNELS_SUPERIMPOSED)) display_channel_id(cp,height+offset,sp->nchans);
}

void display_channel_data (chan_info *cp, snd_info *sp, snd_state *ss)
{
  int width,height,offset,full_height,chan_height,y0,y1,bottom,top;
  float val,size;
  axis_info *ap;
  if ((sp->combining == CHANNELS_SEPARATE) || (sp->nchans == 1))
    {
      width = get_window_width(chan_widget(cp,W_chn_graph));
      height = get_window_height(chan_widget(cp,W_chn_graph));
      display_channel_data_with_size(cp,sp,ss,width,height,0);
    }
  else
    {
      /* all chans in one graph widget, sy->scroll entire set, zy->zoom entire set etc */
      /* updates are asynchronous (dependent on background ffts etc), so we can't do the whole window at once */
      /* complication here is that we're growing down from the top, causing endless confusion */
      width = get_window_width(chan_widget(sp->chans[0],W_chn_graph)) - (2 * ss->place_scroll_size);
      height = get_window_height(chan_widget(sp->chans[0],W_chn_graph));
      cp->height = height;
      if (sp->combining == CHANNELS_SUPERIMPOSED)
	display_channel_data_with_size(cp,sp,ss,width,height,0);
      else
	{
	  val = (float)get_raw_value(chan_widget(sp->chans[0],W_chn_gsy))/(float)(SCROLLBAR_MAX);
	  size = (float)get_raw_size(chan_widget(sp->chans[0],W_chn_gsy))/(float)(SCROLLBAR_MAX);
	  full_height = (float)height / size;
	  chan_height = full_height / sp->nchans;
	  bottom = full_height * val;
	  top = full_height * (val+size);
	  y1 = (sp->nchans - cp->chan) * chan_height;
	  y0 = y1 - chan_height;
	  offset = top - y1;
	  if ((cp->chan == 0) && (offset > 0))
	    {
	      /* round off trouble can lead to garbage collecting at the top of the window (similarly at the bottom I suppose) */
	      chan_height += offset;
	      offset = 0;
	    }
	  if ((cp->chan == (sp->nchans - 1)) && ((offset+chan_height) < height))
	    chan_height = height - offset;
	  if (((y0 < top) && (y0 >= bottom)) || ((y1 > bottom) && (y1 <= top)))
	    display_channel_data_with_size(cp,sp,ss,width,chan_height,offset);
	  else 
	    {
	      ap = cp->axis;
	      ap->y_offset = offset; /* needed for mouse click channel determination */
	      if (cp->mixes) release_mixes(cp);
	    }
	}
    }
}



/* ---------------- CHANNEL CURSOR ---------------- */


#define cursor_size 15

void draw_graph_cursor(chan_info *cp)
{
  axis_info *ap;
  axis_context *ax;
  ap = cp->axis;
  if ((cp->cursor < ap->losamp) || (cp->cursor > ap->hisamp)) return;
  ax = cursor_context(cp);
  if (cp->cursor_visible)
    {
      draw_line(ax,cp->cx,cp->cy - cursor_size,cp->cx,cp->cy + cursor_size);
      draw_line(ax,cp->cx - cursor_size,cp->cy,cp->cx + cursor_size,cp->cy);
    }
  cp->cx = grf_x((double)(cp->cursor)/(double)snd_SRATE(cp),ap); /* not float -- this matters in very long files (i.e. > 40 minutes) */
  cp->cy = grf_y(sample(cp->cursor,cp),ap);
  draw_line(ax,cp->cx,cp->cy - cursor_size,cp->cx,cp->cy + cursor_size);
  draw_line(ax,cp->cx - cursor_size,cp->cy,cp->cx + cursor_size,cp->cy);
  cp->cursor_visible = 1;
}

static int cursor_decision(chan_info *cp)
{
  int len;
  snd_info *sp;
  snd_state *ss;
  sp = cp->sound;
  ss = cp->state;
  len = current_ed_samples(cp);
  if (cp->cursor < 0) cp->cursor = 0;
  if (cp->cursor > len) cp->cursor = len;
  if (ss->cursor_talks) {show_cursor_info(cp); sp->minibuffer_on = 0;}
  if (cp->cursor < graph_low_SAMPLE(cp)) 
    {
      if (cp->cursor == 0) return(CURSOR_ON_LEFT);
      else return(CURSOR_ON_RIGHT);
    }
  if (cp->cursor > graph_high_SAMPLE(cp)) 
    {
      if (cp->cursor == len) return(CURSOR_ON_RIGHT);
      else return(CURSOR_ON_LEFT);
    }
  return(CURSOR_IN_VIEW);
}

int cursor_moveto (chan_info *cp, int samp)
{
  snd_info *sp;
  chan_info *ncp;
  sync_info *si;
  int i;
  sp = cp->sound;
  if ((sp) && (sp->syncing))
    {
      si = snd_sync(cp->state);
      for (i=0;i<si->chans;i++)
	{
	  ncp = si->cps[i];
	  ncp->cursor = samp;
	  if (ncp != cp) handle_cursor(ncp,cursor_decision(ncp));
	}
      free_sync_info(si);
    }
  else cp->cursor = samp;
  return(cursor_decision(cp));
}

static int cursor_move (chan_info *cp,int samps)
{
  return(cursor_moveto(cp,cp->cursor+samps));
}

static int cursor_moveto_beginning(chan_info *cp) 
{
  return(cursor_moveto(cp,0)); /* is ap->xmin ever going to be non-zero? */
}

static int cursor_moveto_end(chan_info *cp)
{
  return(cursor_moveto(cp,current_ed_samples(cp)-1)); /* 5-june-97 -- this is 0-based! */
}


/* ---------------- CHANNEL EDITS ---------------- */

static char expr_str[128];

void report_in_minibuffer(snd_info *sp, char *message)
{
  text_set_string(snd_widget(sp,W_snd_info),message);
  /* leave sp->minibuffer off so that keyboard_command doesn't clear it */
}

void append_to_minibuffer(snd_info *sp, char *message)
{
  sprintf(expr_str,"%s%s",text_get_string(snd_widget(sp,W_snd_info)),message);
  text_set_string(snd_widget(sp,W_snd_info),expr_str);
}

static void prompt(snd_info *sp, char *msg)
{
  expr_str[0] = '\0';
  text_set_string(snd_widget(sp,W_snd_info),expr_str);
  make_button_label(snd_widget(sp,W_snd_info_label),msg);
  sp->minibuffer_on = 1;
  goto_minibuffer(sp);
}

#define NOT_FILING 0
#define INPUT_FILING 1
#define REGION_FILING 2
#define CHANNEL_FILING 3
#define TEMP_FILING 4
#define CHANGE_FILING 5
#define INSERT_FILING 6
#define MACRO_FILING 7

static int region_count = 0;
static void get_amp_expression(snd_info *sp,int count, int regexpr) {prompt(sp,snd_string_env_p); sp->amping = count; sp->reging = regexpr;}
static void get_eval_expression(snd_info *sp, int count, int regexpr) {prompt(sp,snd_string_eval_p); sp->evaling = count; sp->reging = regexpr;}
static void get_ufun(snd_info *sp, int count, int regexpr) {prompt(sp,snd_string_function_p); sp->ufuning = count; sp->reging = regexpr;}

static void get_auto_ufun(snd_info *sp, int count) 
{
  int i;
  chan_info *cp;
  if (count < 0) 
    {
      sp->auto_ufun = 0;
      for (i=0;i<sp->nchans;i++) {cp = sp->chans[i]; cp->ufuning = 0; update_graph(cp,NULL);} /* ??? */
    }
  else
    {
      sp->auto_ufun = 1;
      prompt(sp,snd_string_function_p);
      sp->ufuning = count;
    }
}

void show_cursor_info(chan_info *cp)
{
  snd_info *sp;
  char *s1,*s2;
  sp = cp->sound;
  if (sp->nchans > 1)
      sprintf(expr_str,snd_string_chan_cursor_at_sample,
	      cp->chan,
	      s1 = prettyf((double)(cp->cursor)/(double)snd_SRATE(cp),2),
	      cp->cursor,
	      s2 = prettyf(sample(cp->cursor,cp),2));
  else
    sprintf(expr_str,snd_string_cursor_at_sample,
	    s1 = prettyf((double)(cp->cursor)/(double)snd_SRATE(cp),2),
	    cp->cursor,
	    s2 = prettyf(sample(cp->cursor,cp),2));
  text_set_string(snd_widget(sp,W_snd_info),expr_str);
  sp->minibuffer_on = 1;
  free(s1);
  free(s2);
}

void clear_minibuffer(snd_info *sp)
{
  make_button_label(snd_widget(sp,W_snd_info_label),"     ");
  expr_str[0] = '\0';
  text_set_string(snd_widget(sp,W_snd_info),expr_str);
  sp->searching = 0;
  sp->evaling = 0;
  sp->marking = 0;
  sp->filing = NOT_FILING;
  sp->printing = 0;
  sp->minibuffer_on = 0;
  sp->loading = 0;
  sp->ufuning = 0;
  sp->amping = 0;
  sp->macroing = 0;
}

static int set_window_bounds(chan_info *cp,int count) 
{
  /* count = sample number to start at */
  axis_info *ap;
  float sx;
  ap = cp->axis;
  sx = (((float)count/(float)snd_SRATE(cp)) - ap->xmin) / (ap->xmax -ap->xmin);
  reset_x_display(cp,sx,ap->zx);
  return(CURSOR_IN_VIEW);
}

static int set_window_size(chan_info *cp, int count) 
{
  /* set samples within window */
  axis_info *ap;
  float zx;
  ap = cp->axis;
  zx = ((float)count/(((float)snd_SRATE(cp)) * (ap->xmax -ap->xmin)));
  reset_x_display(cp,ap->sx,zx);
  return(CURSOR_IN_VIEW);
}

static int set_window_percentage(chan_info *cp, int count) 
{
  /* set percentage of file within window */
  axis_info *ap;
  float zx;
  ap = cp->axis;
  zx = (float)count/(float)snd_SRATE(cp);
  reset_x_display(cp,ap->sx,zx);
  return(CURSOR_IN_VIEW);
}

static void window_frames_selection(chan_info *cp)
{
  axis_info *ap;
  float sx,zx;
  ap = cp->axis;
  sx = (((double)(selection_beg(cp)))/((double)snd_SRATE(cp)) - ap->xmin) / (ap->xmax - ap->xmin);
  zx = ((double)(region_len(0)))/(((double)(snd_SRATE(cp))) * (ap->xmax - ap->xmin));
  reset_x_display(cp,sx,zx);
}

void handle_cursor(chan_info *cp, int redisplay)
{
  axis_info *ap;
  axis_context *ax;
  float gx = 0.0;
  if ((redisplay != CURSOR_NO_ACTION) && (redisplay != KEYBOARD_NO_ACTION))
    {
      ap=cp->axis;
      if (redisplay == CURSOR_UPDATE_DISPLAY)
	{
	  update_graph(cp,NULL);
	}
      else
	{
	  if (redisplay != CURSOR_IN_VIEW)
	    {
	      if (cp->cursor_visible)
		{
		  ax = cursor_context(cp);
		  draw_line(ax,cp->cx,cp->cy - cursor_size,cp->cx,cp->cy + cursor_size);
		  draw_line(ax,cp->cx - cursor_size,cp->cy,cp->cx + cursor_size,cp->cy);
		  cp->cursor_visible = 0; /* don't redraw at old location */
		}
	      switch (redisplay)
		{
		case CURSOR_ON_LEFT: gx = (double)(cp->cursor)/(double)snd_SRATE(cp); break;
		case CURSOR_ON_RIGHT: gx = (double)(cp->cursor)/(double)snd_SRATE(cp) - ap->zx*(ap->xmax-ap->xmin); break;
		case CURSOR_IN_MIDDLE: gx = (double)(cp->cursor)/(double)snd_SRATE(cp) - ap->zx*0.5*(ap->xmax-ap->xmin); break;
		}
	      if (gx < 0.0) gx = 0.0;
	      reset_x_display(cp,(gx - ap->xmin) / (ap->xmax - ap->xmin),ap->zx);
	    }
	  else {if (cp->cursor_on) draw_graph_cursor(cp);}
	}
    }
  check_keyboard_selection(cp,cp->cx);
}

/* collect syncd chans */

typedef struct {
  sync_info *si;
  snd_fd **sfs;
  int dur;
} sync_state;


static sync_state *get_sync_state(snd_state *ss, snd_info *sp, chan_info *cp, int beg, int regexpr)
{
  sync_info *si = NULL;
  snd_fd **sfs = NULL;
  int dur,i;
  sync_state *sc;
  dur = 0;
  if ((sp->syncing) && (!regexpr))
    {
      si = snd_sync(ss);
      sfs = (snd_fd **)calloc(si->chans,sizeof(snd_fd *));
      for (i=0;i<si->chans;i++) 
	{
	  sfs[i] = init_sample_read(beg,si->cps[i],READ_FORWARD);
	  si->begs[i] = beg;
	}
    }
  else
    {
      if ((regexpr) && (selection_is_ours()))
	{
	  si = region_sync(0);
	  dur = region_len(0);
	  sfs = (snd_fd **)calloc(si->chans,sizeof(snd_fd *));
	  for (i=0;i<si->chans;i++) 
	    sfs[i] = init_sample_read(si->begs[i],si->cps[i],READ_FORWARD);
	}
    }
  if (!si) 
    {
      si = make_simple_sync(cp,beg);
      sfs = (snd_fd **)calloc(1,sizeof(snd_fd *));
      sfs[0] = init_sample_read(beg,cp,READ_FORWARD);
    }
  sc = (sync_state *)calloc(1,sizeof(sync_state));
  sc->dur = dur;
  sc->sfs = sfs;
  sc->si = si;
  return(sc);
}


/* amplitude scaling */

static void scale_with(snd_state *ss, sync_state *sc, float *scalers)
{
  sync_info *si;
  snd_fd **sfs;
  chan_info *cp;
  snd_info *sp;
  int i,scdur,dur,k;
  snd_fd *sf;
  file_info *hdr = NULL;
  int j,val,ofd = 0,datumb = 0,temp_file;
  int **data;
  int *idata;
  float env_val;
  char *ofile = NULL;
  si = sc->si;
  sfs = sc->sfs;
  scdur = sc->dur;
  /* for each decide whether a file or internal array is needed, scale, update edit tree */
  data = (int **)calloc(1,sizeof(int *));
  data[0] = (int *)calloc(MAX_BUFFER_SIZE,sizeof(int)); 
  for (i=0;i<si->chans;i++)
    {
      /* done channel at a time here, rather than in parallel as in apply_env because */
      /* in this case, the various sync'd channels may be different lengths */
      cp = si->cps[i];
      sp = cp->sound;
      if (scdur == 0) dur = current_ed_samples(cp); else dur = scdur;
      if (dur > MAX_BUFFER_SIZE)
	{
	  temp_file = 1; 
	  ofile = tempnam(ss->temp_dir,"snd_");
	  hdr = make_temp_header(ofile,sp->hdr,dur);
	  hdr->chans = 1;
	  ofd = open_temp_file(ofile,1,hdr,ss);
	  datumb = c_snd_datum_size(hdr->format);
	}
      else temp_file = 0;
      sf = sfs[i];
      idata = data[0];
      env_val = scalers[i];
      j = 0;
      for (k=0;k<dur;k++)
	{
	  NEXT_SAMPLE(val,sf);
	  idata[j] = val*env_val;
	  j++;
	  if (temp_file)
	    {
	      if (j == MAX_BUFFER_SIZE)
		{
		  clm_write(ofd,0,j-1,1,data);
		  j=0;
		}
	    }
	}
      if (temp_file)
	{
	  if (j > 0) clm_write(ofd,0,j-1,1,data);
	  close_temp_file(ofd,hdr,dur*datumb,sp);
	  file_change_samples(0,dur,ofile,cp,0,DELETE_ME,LOCK_MIXES);
	  if ((temp_file) && (ofile)) {free(ofile); ofile=NULL;}
	}
      else change_samples(si->begs[i],dur,data[0],cp,LOCK_MIXES);
      check_for_first_edit(cp);
      update_graph(cp,NULL); /* is this needed? */
      free_snd_fd(sfs[i]);
    }
  free(data[0]);
  free(data);
  free(sfs);
  free_sync_info(si);
}

void scale_by(snd_state *ss, snd_info *sp, chan_info *cp, float *scalers, int len, int selection)
{
  /* if selection, sync to current selection, else sync to current sound */
  sync_state *sc;
  sync_info *si;
  int i,chans;
  float last_scaler;
  sc = get_sync_state(ss,sp,cp,0,selection);
  si = sc->si;
  chans = si->chans;
  if (chans > len)
    {
      if (len == 0) {last_scaler = scalers[0]; len=1;} else last_scaler = scalers[len-1];
      for (i=len;i<chans;i++) scalers[i] = last_scaler;
    }
  scale_with(ss,sc,scalers);
  free(sc);
}

void scale_to(snd_state *ss, snd_info *sp, chan_info *cp, float *scalers, int len, int selection)
{
  /* essentially the same as scale-by, but first take into account current maxamps */
  /* here it matters if more than one arg is given -- if one, get overall maxamp */
  /*   if more than one, get successive maxamps */
  int i,chans;
  sync_state *sc;
  sync_info *si;
  chan_info *ncp;
  float maxamp,val,last_scaler;
  sc = get_sync_state(ss,sp,cp,0,selection);
  si = sc->si;
  chans = si->chans;
  if (chans > len)
    {
      if (len == 0) {last_scaler = scalers[0]; len=1;} else last_scaler = scalers[len-1];
      for (i=len;i<chans;i++) scalers[i] = last_scaler;
    }
  /* now find maxamps (special if len==1) and fixup the scalers */
  if (len == 1)
    {
      maxamp = 0.0;
      for (i=0;i<chans;i++)
	{
	  ncp = si->cps[i];
	  val = get_maxamp(ss,ncp->sound,ncp);
	  if (val > maxamp) maxamp = val;
	}
      val = scalers[0]/maxamp;
      for (i=0;i<chans;i++) scalers[i] = val;
    }
  else
    {
      for (i=0;i<chans;i++)
	{
	  ncp = si->cps[i];
	  val = get_maxamp(ss,ncp->sound,ncp);
	  scalers[i] /= val;
	}
    }
  scale_with(ss,sc,scalers);
  free(sc);
}


/* sampling rate conversion */
/* taken from sweep_srate.c of Perry Cook.  To quote Perry:
 *
 * 'The conversion is performed by sinc interpolation.
 *    J. O. Smith and P. Gossett, "A Flexible Sampling-Rate Conversion Method," 
 *    Proc. of the IEEE Conference on Acoustics, Speech, and Signal Processing, San Diego, CA, March, 1984.
 * There are essentially two cases, one where the conversion factor
 * is less than one, and the sinc table is used as is yielding a sound
 * which is band limited to the 1/2 the new sampling rate (we don't
 * want to create bandwidth where there was none).  The other case
 * is where the conversion factor is greater than one and we 'warp'
 * the sinc table to make the final cutoff equal to the original sampling
 * rate /2.  Warping the sinc table is based on the similarity theorem
 * of the time and frequency domain, stretching the time domain (sinc
 * table) causes shrinking in the frequency domain.'
 *
 * for other implementations see src in clm's mus.lisp, and c_src in cmus.c
 *
 * we also scale the amplitude if interpolating to take into account the broadened sinc 
 */

#define SRC_SINC_DENSITY 1000
/* make it big enough to be able to avoid interpolation */

static int current_sinc_width = 0;
static float *sinc_table = NULL;

static void init_sinc_table(snd_state *ss)
{
  int i,size;
  float sinc_freq,win_freq,sinc_phase,win_phase;
  if ((ss->sinc_width != current_sinc_width) && (ss->sinc_width > 0))
    {
      if (sinc_table) {free(sinc_table); sinc_table = NULL;}
      current_sinc_width = ss->sinc_width;
      size = current_sinc_width * SRC_SINC_DENSITY;
      sinc_table = (float *)calloc(size+1,sizeof(float));
      sinc_freq = PI / (float)SRC_SINC_DENSITY;
      win_freq = PI / (float)size;
      sinc_table[0] = 1.0;
      for (i=1,sinc_phase=sinc_freq,win_phase=win_freq;i<size;i++,sinc_phase+=sinc_freq,win_phase+=win_freq)
	sinc_table[i] = sin(sinc_phase) * (0.5+0.5*cos(win_phase)) / sinc_phase;
    }
}

src_state *make_src(snd_state *ss, float srate, snd_fd *sf)
{
  src_state *sr;
  int i,val,lim;
  init_sinc_table(ss);
  sr = (src_state *)calloc(1,sizeof(src_state));
  sr->x = 0.0;
  sr->incr = srate;
  sr->sf = sf;
  sr->width = ss->sinc_width;
  lim = 2*sr->width;
  sr->len = sr->width * SRC_SINC_DENSITY;
  sr->data = (float *)calloc(lim+1,sizeof(float));
  sr->sample = lim-1;
  if (srate >= 0.0)
    {
      for (i=0;i<lim;i++) 
	{
	  NEXT_SAMPLE(val,sr->sf);
	  sr->data[i] = (float)val;
	}
    }
  else
    {
      for (i=0;i<lim;i++) 
	{
	  PREVIOUS_SAMPLE(val,sr->sf);
	  sr->data[i] = (float)val;
	}
    }
  return(sr);
}

float run_src(src_state *sr, float sr_change)
{
  float sum,x,zf,srx,factor;
  int fsx,lim,i,j,k,loc,val;
  sum = 0.0;
  fsx = (int)(sr->x);
  lim = 2*sr->width;
  if (fsx >= 1)
    {
      /* realign data, reset sr->x */
      loc = 0;
      for (i=fsx,j=0;i<lim;i++,j++) {sr->data[j] = sr->data[i]; loc++;}
      if (sr_change >= 0.0)
	{
	  for (i=loc;i<lim;i++) 
	    {
	      NEXT_SAMPLE(val,sr->sf);
	      sr->data[i] = (float)val;
	      sr->sample++;
	    }
	}
      else
	{
	  for (i=loc;i<lim;i++) 
	    {
	      PREVIOUS_SAMPLE(val,sr->sf);
	      sr->data[i] = (float)val;
	      sr->sample++;
	    }
	}
      sr->x -= fsx;
    }
  if (sr_change < 0.0) sr_change = -sr_change;
  srx = sr->incr + sr_change;
  if (srx <= 0.0) srx=.01;
  if (srx>1.0) factor=1.0/srx; else factor=1.0;
  zf = factor * (float)SRC_SINC_DENSITY;
  for (i=0,x=zf*(1.0 - sr->x - sr->width);i<lim;i++,x+=zf)
    {
      /* we're moving backwards in the data array, so the sr->x field has to mimic that (hence the '1.0 - sr->x') */
      if (x<0) k = -x; else k = x;
      if (k < sr->len) sum += (sr->data[i] * sinc_table[k]);
    }
  sr->x += srx;
  return(sum*factor);
}

src_state *free_src(src_state *sr)
{
  if (sr)
    {
      if (sr->data) free(sr->data);
      free(sr);
    }
  return(NULL);
}

void src(snd_state *ss, char *env_or_num, int from_enved)
{
  /* env_or_num can be a number or an envelope name */
  snd_info *sp = NULL;
  chan_info *cp = NULL;
  int err = 0;
  int reporting = 0;
  env *e = NULL;
  int just_num;
  char *str;
  float ratio = 0.0;
  sync_state *sc;
  sync_info *si;
  snd_fd **sfs;
  snd_fd *sf;
  int chans,i;
  int **data;
  file_info *hdr = NULL;
  int j,k,ofd = 0,datumb = 0;
  char *ofile = NULL;
  int *idata;
  src_state *sr;
  int scdur,dur;

  float *ef = NULL;
  float env_val,env_incr;
  int ef_ctr,pass,next_pass,diff,stop_point;

  /* get envelope or src ratio */
  if (env_or_num == NULL) return;
  sp = any_selected_sound(ss); 
  if (sp) cp = any_selected_channel(sp); 
  str = env_or_num;
  while (isspace(*str)) str++;
  if ((isdigit(*str)) || ((*str) == '.'))
    {
      sscanf(str,"%f",&ratio);
      if (ratio <= 0.0) 
	{
	  str = (char *)calloc(64,sizeof(char));
	  sprintf(str,snd_string_invalid_srate,ratio);
	  report_in_minibuffer(sp,str);
	  free(str);
	  str = NULL;
	  return;
	}
      just_num = 1;
    }
  else 
    {
      e = scan_envelope_and_report_error(sp,env_or_num,&err);
      if ((!e) || (err)) return;
      just_num = 0;
    }

  /* get current syncd chans */
  sc = get_sync_state(ss,sp,cp,0,0); /* 0->beg, 0->regexpr */
  si = sc->si;
  chans = si->chans;
  sfs = sc->sfs;
  scdur = sc->dur;
  data = (int **)calloc(1,sizeof(int *));
  data[0] = (int *)calloc(MAX_BUFFER_SIZE,sizeof(int)); 

  if (from_enved)
    {
      expr_str[0]='\0';
      display_enved_progress(ss,expr_str);
    }
  
  if (!(ss->stopped_explicitly))
    {
      for (i=0;i<si->chans;i++)
	{
	  cp = si->cps[i];
	  sp = cp->sound;
	  if (scdur == 0) dur = current_ed_samples(cp); else dur = scdur;
	  reporting = ((sp) && (dur > (MAX_BUFFER_SIZE * 4)));
	  ofile = tempnam(ss->temp_dir,"snd_");
	  hdr = make_temp_header(ofile,sp->hdr,dur);
	  hdr->chans = 1;
	  ofd = open_temp_file(ofile,1,hdr,ss);
	  datumb = c_snd_datum_size(hdr->format);
	  sf = sfs[i];
	  idata = data[0];
	  sr = make_src(ss,ratio,sf);
	  j=0;
	  if (just_num)
	    {
	      for (k=0;sr->sample<dur;k++) /* sr->sample tracks input location -- produce output until input exhausted */
		{
		  idata[j]=(int)run_src(sr,0.0);
		  j++;
		  if (j == MAX_BUFFER_SIZE)
		    {
		      clm_write(ofd,0,j-1,1,data);
		      j=0;
		      if (reporting) 
			{
			  if (si->chans > 1)
			    sprintf(expr_str,"src: (%d of %d) %d%%",i+1,si->chans,(int)(100.0 * (float)(sr->sample) / (float)dur));
			  else sprintf(expr_str,"src: %d%%",(int)(100.0 * (float)(sr->sample) / (float)dur));
			  if (from_enved) display_enved_progress(ss,expr_str); else report_in_minibuffer(sp,expr_str);
			  if (ss->stopped_explicitly) break;
			}
		    }
		}
	    }
	  else
	    {
	      /* envelope case -- have to go by sr->sample, not output sample counter */
	      ef = magify_env(e,dur,1.0); /* dur=input samples (might change, so re-magify) */
	      ef_ctr = 0;
	      env_val = e->data[1];
	      env_incr = ef[1];
	      pass = ef[0];
	      next_pass = sr->sample;
	      for (k=0;sr->sample<dur;k++)
		{
		  idata[j] = (int)run_src(sr,env_val);
		  j++;
		  if (j == MAX_BUFFER_SIZE)
		    {
		      clm_write(ofd,0,j-1,1,data);
		      j=0;
		      if (reporting) 
			{
			  if (si->chans > 1)
			    sprintf(expr_str,"src: (%d of %d) %d%%",i+1,si->chans,(int)(100.0 * (float)(sr->sample) / (float)dur));
			  else sprintf(expr_str,"src: %d%%",(int)(100.0 * (float)(sr->sample) / (float)dur));
			  if (from_enved) display_enved_progress(ss,expr_str); else report_in_minibuffer(sp,expr_str);
			  if (ss->stopped_explicitly) break;
			}
		    }
		  if (next_pass != sr->sample)  /* tick env forward dependent on sr->sample */
		    {
		      diff = (sr->sample - next_pass);
		      next_pass = sr->sample;
		      env_val += (diff * env_incr);
		      pass -= diff;
		      if (pass < 0)
			{
			  ef_ctr += 2;
			  pass = ef[ef_ctr];
			  env_incr = ef[ef_ctr+1];
			}
		    }
		}
	      free(ef);
	    }
	  if (reporting)
	    {
	      if (ss->stopped_explicitly) sprintf(expr_str,"stopped"); else expr_str[0]='\0';
	      if (from_enved) display_enved_progress(ss,expr_str); else report_in_minibuffer(sp,expr_str);
	    }
	  sr = free_src(sr);
	  if (j > 0) clm_write(ofd,0,j-1,1,data);
	  close_temp_file(ofd,hdr,k*datumb,sp);
	  file_override_samples(k,ofile,cp,0,DELETE_ME,LOCK_MIXES);
	  /* not file_change_samples because that would not necessarily change the current file length */
	  free(ofile); 
	  ofile=NULL;
	  sfs[i] = free_snd_fd(sfs[i]);
	  if (ss->stopped_explicitly) 
	    {
	      stop_point = i;
	      break;
	    }
	}
    }
  if (ss->stopped_explicitly)
    {
      /* clean up and undo all edits up to stop_point */
      ss->stopped_explicitly = 0;
      for (i=0;i<=stop_point;i++)
	{
	  cp = si->cps[i];
	  undo_edit(cp,1);
	}
    }
  free(data[0]);
  free(data);
  free(sfs);
  free_sync_info(si);
  free(sc);
}


/* FIR filtering */

void apply_filter(chan_info *ncp, int order, env *e, int from_enved)
{
  /* interpret e as frequency response and apply as filter to all sync'd chans */
  float *fdata,*a,*d;
  float x;
  sync_state *sc;
  sync_info *si;
  snd_state *ss;
  snd_info *sp;
  int reporting = 0;
  int i,m,chans,scdur,dur,k,stop_point = 0;
  snd_fd **sfs;
  snd_fd *sf;
  file_info *hdr = NULL;
  int j,val,ofd = 0,datumb = 0,temp_file;
  int **data;
  int *idata;
  char *ofile = NULL;
  chan_info *cp;
  float last_x,step;

  if (!e) return;
  /* get the frequency envelope and design the FIR filter */
  fdata = (float *)calloc(order,sizeof(float));
  a = (float *)calloc(order,sizeof(float));
  d = (float *)calloc(order,sizeof(float));
  last_x = e->data[(e->pts-1)*2];
  step = last_x/((float)order-1);
  for (i=0,x=0.0;i<order;i++,x+=step) fdata[i] = list_interp(x,e->data,e->pts);
  make_filter(order,fdata,a);
  free(fdata);

  /* now filter all currently sync'd chans (one by one) */
  ss = ncp->state;
  sp = ncp->sound;
  sc = get_sync_state(ss,sp,ncp,0,0);
  si = sc->si;
  chans = si->chans;
  sfs = sc->sfs;
  scdur = sc->dur;
  /* for each decide whether a file or internal array is needed, scale, update edit tree */
  data = (int **)calloc(1,sizeof(int *));
  data[0] = (int *)calloc(MAX_BUFFER_SIZE,sizeof(int)); 

  if (from_enved)
    {
      expr_str[0]='\0';
      display_enved_progress(ss,expr_str);
    }

  if (!(ss->stopped_explicitly))
    {
      for (i=0;i<si->chans;i++)
	{
	  /* done channel at a time here, rather than in parallel as in apply_env because */
	  /* in this case, the various sync'd channels may be different lengths */
	  cp = si->cps[i];
	  sp = cp->sound;
	  if (scdur == 0) dur = current_ed_samples(cp); else dur = scdur;
	  reporting = ((sp) && (dur > (MAX_BUFFER_SIZE * 4)));
	  if (dur > MAX_BUFFER_SIZE)
	    {
	      temp_file = 1; 
	      ofile = tempnam(ss->temp_dir,"snd_");
	      hdr = make_temp_header(ofile,sp->hdr,dur);
	      hdr->chans = 1;
	      ofd = open_temp_file(ofile,1,hdr,ss);
	      datumb = c_snd_datum_size(hdr->format);
	    }
	  else temp_file = 0;
	  sf = sfs[i];
	  idata = data[0];
	  for (m=0;m<order;m++) d[m] = 0.0;
	  j = 0;
	  for (k=0;k<dur;k++)
	    {
	      NEXT_SAMPLE(val,sf);
	      x=0.0; 
	      d[0]=(float)val;
	      for (m=order-1;m>0;m--) 
		{
		  x+=d[m]*a[m]; 
		  d[m]=d[m-1];
		} 
	      x+=d[0]*a[0]; 
	      idata[j] = (int)x;
	      j++;
	      if (temp_file)
		{
		  if (j == MAX_BUFFER_SIZE)
		    {
		      clm_write(ofd,0,j-1,1,data);
		      j=0;
		      if (reporting) 
			{
			  if (si->chans > 1)
			    sprintf(expr_str,"filter: (%d of %d) %d%%",i+1,si->chans,(int)(100.0 * (float)k / (float)dur));
			  else sprintf(expr_str,"filter: %d%%",(int)(100.0 * (float)k / (float)dur));
			  if (from_enved) display_enved_progress(ss,expr_str); else report_in_minibuffer(sp,expr_str);
			  if (ss->stopped_explicitly) break;
			}
		    }
		}
	    }
	  if (reporting)
	    {
	      if (ss->stopped_explicitly) sprintf(expr_str,"stopped"); else expr_str[0]='\0';
	      if (from_enved) display_enved_progress(ss,expr_str); else report_in_minibuffer(sp,expr_str);
	    }
	  if (temp_file)
	    {
	      if (j > 0) clm_write(ofd,0,j-1,1,data);
	      close_temp_file(ofd,hdr,dur*datumb,sp);
	      file_change_samples(0,dur,ofile,cp,0,DELETE_ME,LOCK_MIXES);
	      if ((temp_file) && (ofile)) {free(ofile); ofile=NULL;}
	    }
	  else change_samples(si->begs[i],dur,data[0],cp,LOCK_MIXES);
	  check_for_first_edit(cp);
	  update_graph(cp,NULL); /* is this needed? */
	  sfs[i] = free_snd_fd(sfs[i]);
	  if (ss->stopped_explicitly) 
	    {
	      stop_point = i;
	      break;
	    }
	}
    }

  if (ss->stopped_explicitly)
    {
      /* clean up and undo all edits up to stop_point */
      ss->stopped_explicitly = 0;
      for (i=0;i<=stop_point;i++)
	{
	  cp = si->cps[i];
	  undo_edit(cp,1);
	}
    }

  free(data[0]);
  free(data);
  free(sfs);
  free_sync_info(si);
  free(a);
  free(d);
  free(sc);
}


/* amplitude envelopes */

static void report_env_progress(snd_info *sp, int i, int dur, int from_enved)
{
  sprintf(expr_str,"env: %d%%",(int)(100.0*((float)i)/((float)dur)));
  if (from_enved) 
    display_enved_progress(sp->state,expr_str);
  else report_in_minibuffer(sp,expr_str);
}

void apply_env(chan_info *cp, env *e, int beg, int dur, float scaler, int regexpr, int from_enved)
{
  snd_fd *sf;
  snd_info *sp;
  sync_info *si;
  sync_state *sc;
  snd_fd **sfs;
  file_info *hdr;
  float *ef = NULL;
  double *efd = NULL;
  int i,j,k,ef_ctr,val,pass,ofd = 0,datumb = 0,temp_file;
  int **data;
  int *idata;
  int reporting = 0;
  float env_val,env_incr;
  double env_vald,env_incrd;
  int need_doubles;
  char *ofile = NULL;
  snd_state *ss;

  if (!e) return;
  need_doubles = (dur > 5000000);
  si = NULL;
  sp = cp->sound;
  ss = cp->state;
  hdr = sp->hdr;
  sc = get_sync_state(ss,sp,cp,beg,regexpr);
  si = sc->si;
  sfs = sc->sfs;
  if (regexpr) dur = sc->dur;
  free(sc);
  
  if (dur > MAX_BUFFER_SIZE) /* if smaller than this, we don't gain anything by using a temp file (its buffers are this large) */
    {
      temp_file = 1; 
      ofile = tempnam(ss->temp_dir,"snd_"); /* see warning below -- don't use tmpnam without deleting free */
      ofd = open_temp_file(ofile,si->chans,hdr,ss);
      datumb = c_snd_datum_size(hdr->format);
    }
  else temp_file = 0;
  if (need_doubles)
    efd = dmagify_env(e,dur,scaler);
  else ef = magify_env(e,dur,scaler);
  data = (int **)calloc(si->chans,sizeof(int *));
  for (i=0;i<si->chans;i++) 
    {
      if (temp_file)
	data[i] = (int *)calloc(FILE_BUFFER_SIZE,sizeof(int)); 
      else data[i] = (int *)calloc(dur,sizeof(int)); 
    }
  ef_ctr = 0;
  j=0;
  reporting = (dur > (MAX_BUFFER_SIZE * 4));
  if ((reporting) && (from_enved))
    {
      expr_str[0]='\0';
      display_enved_progress(ss,expr_str);
    }

  if (need_doubles)
    { /* this case can take long enough that it probably should be a background process */
      env_vald = e->data[1];
      env_incrd = efd[1];
      pass = efd[0];
      if (si->chans > 1)
	{
	  for (i=0;i<dur;i++)
	    {
	      for (k=0;k<si->chans;k++)
		{
		  NEXT_SAMPLE(val,sfs[k]);
		  data[k][j] = val*env_vald;
		}
	      env_vald += env_incrd;
	      pass--;
	      if (pass <= 0) 
		{
		  ef_ctr += 2;
		  pass = efd[ef_ctr];
		  env_incrd = efd[ef_ctr+1];
		}
	      j++;
	      if (temp_file)
		{
		  if (j == FILE_BUFFER_SIZE)
		    {
		      report_env_progress(sp,i,dur,from_enved);
		      clm_write(ofd,0,j-1,si->chans,data);
		      j=0;
		      if (ss->stopped_explicitly) break;
		    }
		}
	    }
	}
      else
	{
	  sf = sfs[0];
	  idata = data[0];
	  for (i=0;i<dur;i++)
	    {
	      NEXT_SAMPLE(val,sf);
	      idata[j] = val*env_vald;
	      env_vald += env_incrd;
	      pass--;
	      if (pass <= 0) 
		{
		  ef_ctr += 2;
		  pass = efd[ef_ctr];
		  env_incrd = efd[ef_ctr+1];
		}
	      j++;
	      if (temp_file)
		{
		  if (j == FILE_BUFFER_SIZE)
		    {
		      report_env_progress(sp,i,dur,from_enved);
		      clm_write(ofd,0,j-1,1,data);
		      j=0;
		      if (ss->stopped_explicitly) break;
		    }
		}
	    }
	}
    }
  else /* float case */
    {
      env_val = e->data[1];
      env_incr = ef[1];
      pass = ef[0];
      /* fprintf(stderr,"{%.3f %.3f %.3f ",env_val,env_incr,(float)pass); */
      if (si->chans > 1)
	{
	  for (i=0;i<dur;i++)
	    {
	      for (k=0;k<si->chans;k++)
		{
		  NEXT_SAMPLE(val,sfs[k]);
		  data[k][j] = val*env_val;
		}
	      env_val += env_incr;
	      pass--;
	      if (pass <= 0) 
		{
		  ef_ctr += 2;
		  pass = ef[ef_ctr];
		  env_incr = ef[ef_ctr+1];
		}
	      j++;
	      if (temp_file)
		{
		  if (j == FILE_BUFFER_SIZE)
		    {
		      clm_write(ofd,0,j-1,si->chans,data);
		      j=0;
		      if (reporting) report_env_progress(sp,i,dur,from_enved);
		      if (ss->stopped_explicitly) break;
		    }
		}
	    }
	}
      else
	{
	  sf = sfs[0];
	  idata = data[0];
	  for (i=0;i<dur;i++)
	    {
	      NEXT_SAMPLE(val,sf);
	      idata[j] = val*env_val;
	      env_val += env_incr;
	      pass--;
	      if (pass <= 0) 
		{
		  ef_ctr += 2;
		  pass = ef[ef_ctr];
		  env_incr = ef[ef_ctr+1];
		  /* fprintf(stderr,"%.3f %.3f %.3f ",env_val,env_incr,(float)pass); */
		}
	      j++;
	      if (temp_file)
		{
		  if (j == FILE_BUFFER_SIZE)
		    {
		      clm_write(ofd,0,j-1,1,data);
		      j=0;
		      if (reporting) report_env_progress(sp,i,dur,from_enved);
		      if (ss->stopped_explicitly) break;
		    }
		}
	    }
	}
    }
  /* fprintf(stderr,"}\n"); */
  if (temp_file)
    {
      if (j>0) clm_write(ofd,0,j-1,si->chans,data);
      close_temp_file(ofd,hdr,dur*si->chans*datumb,sp);
    }
  if (reporting)
    {
      if (ss->stopped_explicitly) sprintf(expr_str,"stopped"); else expr_str[0]='\0';
      if (from_enved) display_enved_progress(ss,expr_str); else report_in_minibuffer(sp,expr_str);
    }
  if (ss->stopped_explicitly)
    {
      ss->stopped_explicitly = 0;
      if (temp_file) remove(ofile);
    }
  else
    {
      for (i=0;i<si->chans;i++)
	{
	  if (temp_file)
	    file_change_samples(si->begs[i],dur,ofile,si->cps[i],i,(i == 0) ? DELETE_ME : DONT_DELETE_ME,LOCK_MIXES);
	  else change_samples(si->begs[i],dur,data[i],si->cps[i],LOCK_MIXES);
	  check_for_first_edit(si->cps[i]);
	  update_graph(si->cps[i],NULL); /* is this needed? */
	}
    }
  for (i=0;i<si->chans;i++)
    {
      sfs[i] = free_snd_fd(sfs[i]);
      free(data[i]);
    }
  if ((temp_file) && (ofile)) {free(ofile); ofile=NULL;} /* safe only if tempnam, not tmpnam used */
  if (ef) free(ef);
  if (efd) free(efd);
  if (data) free(data);
  if (sfs) free(sfs);
  free_sync_info(si);
}


/* various simple editing ops */

static int cursor_delete(chan_info *cp,int count)
{
  int i,beg;
  snd_info *sp;
  sync_info *si;
  chan_info **cps;
  si = NULL;
  sp = cp->sound;
  beg = cp->cursor;
  if (sp->syncing)
    {
      si = snd_sync(cp->state);
      cps = si->cps;
      for (i=0;i<si->chans;i++)
	{
	  if (count > 0)
	    delete_samples(beg,count,cps[i]);
	  else delete_samples(beg+count,-count,cps[i]);
	  check_for_first_edit(cps[i]);
	  update_graph(si->cps[i],NULL);
	}
      free_sync_info(si);
    }
  else
    {
      if (count > 0)
	delete_samples(beg,count,cp);
      else delete_samples(beg+count,-count,cp);
      check_for_first_edit(cp);
    }
  return(CURSOR_UPDATE_DISPLAY);
}

static int cursor_insert(chan_info *cp, int count)
{
  int *zeros;
  int i,beg;
  snd_info *sp;
  sync_info *si;
  chan_info **cps;
  si = NULL;
  if (count < 0) count = -count;
  sp = cp->sound;
  beg = cp->cursor;
  zeros = (int *)calloc(count,sizeof(int));
  if (sp->syncing)
    {
      si = snd_sync(cp->state);
      cps = si->cps;
      for (i=0;i<si->chans;i++)
	{
	  insert_samples(beg,count,zeros,cps[i]);
	  check_for_first_edit(cps[i]);
	  update_graph(si->cps[i],NULL);
	}
      free_sync_info(si);
    }
  else
    {
      insert_samples(beg,count,zeros,cp);
      check_for_first_edit(cp);
    }
  free(zeros);
  return(CURSOR_UPDATE_DISPLAY);
}

static int cursor_zeros(chan_info *cp, int count, int regexpr)
{
  int *zeros;
  int i,num;
  snd_info *sp;
  sync_info *si;
  si = NULL;
  sp = cp->sound;
  if (count < 0) num = -count; else num = count;
  if ((sp->syncing) && (!regexpr))
    {
      si = snd_sync(cp->state);
      for (i=0;i<si->chans;i++) si->begs[i] = cp->cursor;
    }
  else
    {
      if ((regexpr) && (selection_is_ours()))
	{
	  si = region_sync(0);
	  num = region_len(0);
	}
    }
  if (!si) si = make_simple_sync(cp,cp->cursor);
  zeros = (int *)calloc(num,sizeof(int));
  for (i=0;i<si->chans;i++)
    {
      if (count < 0) 
	change_samples(si->begs[i]+count,num,zeros,si->cps[i],LOCK_MIXES); 
      else change_samples(si->begs[i],num,zeros,si->cps[i],LOCK_MIXES);
      check_for_first_edit(si->cps[i]);
      update_graph(si->cps[i],NULL);
    }
  free(zeros);
  free_sync_info(si);
  return(CURSOR_IN_VIEW);
}

static void cos_smooth(chan_info *cp, int beg, int num, int regexpr)
{
  /* verbatim, so to speak from Dpysnd */
  /* start at beg, apply a cosine for num samples, matching endpoints */
  snd_fd *sf;
  int *data;
  sync_state *sc;
  int i,k;
  float y0,y1,angle,incr,off,scale;
  snd_info *sp;
  sync_info *si;
  snd_fd **sfs;
  sp = cp->sound;
  sc = get_sync_state(cp->state,sp,cp,beg,regexpr);
  si = sc->si;
  sfs = sc->sfs;
  if (regexpr) num = sc->dur;
  free(sc);
  for (i=0;i<si->chans;i++)
    {
      sf = sfs[i];
      y0 = clm_sndflt * sf->current_value;
      y1 = any_sample(sf,num);
      free_snd_fd(sf);
      if (y1 > y0) angle=one_pi; else angle=0.0;
      incr = (float)one_pi/(float)num;
      off = 0.5*(y1+y0);
      scale = 0.5*fabs(y0-y1);
      data = (int *)calloc(num,sizeof(int));
      for (k=0;k<num;k++,angle+=incr) 
	{
	  data[k] = (int)((off + scale * cos(angle)) * clm_sndfix);
	}
      change_samples(si->begs[i],num,data,si->cps[i],LOCK_MIXES);
      check_for_first_edit(si->cps[i]);
      update_graph(si->cps[i],NULL);
      free(data);
    }
  free_sync_info(si);
  free(sfs);
}

/* another possibility is fft-based smoothing (see env.lisp etc) */



typedef struct {int off; float val; void *next;} assign_info;
snd_fd *snd_search_fd;
static assign_info *dot_changes = NULL;

float search_dot(int off) 
{
  return(any_sample(snd_search_fd,off));
}

float search_dot_assign(int off,float val) 
{
  assign_info *loc;
  loc = (assign_info *)calloc(1,sizeof(assign_info));
  loc->off = off;
  loc->val = val;
  loc->next = dot_changes;
  dot_changes = loc;
  return(val);
}

static void free_assign_info (assign_info *dot)
{
  if (dot)
    {
      if (dot->next) free_assign_info((assign_info *)(dot->next));
      free(dot);
    }
}

static void maxmin_off(assign_info *ai, int i, int *beg, int *end)
{
  if (ai)
    {
      maxmin_off((assign_info *)(ai->next),i,beg,end);
      if ((i+ai->off) < (*beg)) (*beg) = i+ai->off;
      if ((i+ai->off) > (*end)) (*end) = i+ai->off;
    }
}

static void assign_values(int *data,assign_info *change,int i,int offset)
{
  if (change)
    {
      assign_values(data,(assign_info *)(change->next),i,offset);
      data[i-offset+change->off] = (int)(clm_sndfix * change->val);
    }
}

static snd_fd **afs_list = NULL;

typedef struct {
  chan_info **cps;
  chan_info *skip_chan;
  int i,lim;
} afsget;

static int get_afs_chans(chan_info *cp, void *gaf)
{
  afsget *af = (afsget *)gaf;
  if (cp != af->skip_chan)
    {
      af->cps[af->i] = cp;
      af->i += 1;
    }
  return(af->i >= af->lim);
}

snd_fd **get_chans_needed_by_eval(snd_state *ss, chan_info *cp, sop *eval_tree, int start, int direction)
{
  snd_fd **sfs = NULL;
  afsget *af;
  int i;
  int lim;
  lim = scan_tree_for_yn(eval_tree);
  if (lim > 0)
    {
      sfs = (snd_fd **)calloc(lim+1,sizeof(snd_fd *)); /* end marker is final null entry */
      af = (afsget *)calloc(1,sizeof(afsget));
      af->cps = (chan_info **)calloc(lim,sizeof(chan_info *));
      af->lim = lim;
      af->i = 0;
      af->skip_chan = cp;
      map_over_chans(ss,get_afs_chans,(void *)af);
      for (i=0;i<lim;i++)
	{
	  if (af->cps[i])
	    sfs[i] = init_sample_read((start == -1) ? ((af->cps[i])->cursor) : start,af->cps[i],direction);
	  else break;
	}
      sfs[lim] = NULL; /* just to make sure it's there */
      free(af->cps);
      free(af);
    }
  afs_list = sfs;
  return(sfs);
}

void next_samples_for_eval(snd_fd **afs)
{
  int i = 0;
  while (afs[i])
    {
      next_sample_1(afs[i]);
      i++;
    }
}

void previous_samples_for_eval(snd_fd **afs)
{
  int i = 0;
  while (afs[i])
    {
      previous_sample_1(afs[i]);
      i++;
    }
}

void free_chans_for_eval(snd_fd **afs)
{
  int i = 0;
  while (afs[i])
    {
      free_snd_fd(afs[i]);
      i++;
    }
  afs_list = NULL;
}

float sop_yn(int chan, int off)
{
  if ((afs_list) && (afs_list[chan]))
    return(any_sample(afs_list[chan],off));
  else return(0.0);
}

static void eval_expression(chan_info *cp, snd_info *sp, int count, int regexpr)
{
  sync_state *sc; 
  sync_info *si;
  int i,dur,chan,end,size,cbeg,passes = 0;
  int beg = 0;
  snd_state *ss;
  snd_fd **sfs,**afs;
  static assign_info **changes;
  int *data;
  float val = 0.0;
  char *s1;
  if (sp->eval_tree)
    {
      ss = cp->state;
      if (!regexpr)
	{
	  beg = cp->cursor;
	  if (count < 0)
	    {
	      count = -count;
	      if ((beg-count) >= 0) 
		beg -= count;
	      else
		{
		  count = beg;
		  beg = 0;
		}
	    }
	}

      sc = get_sync_state(ss,sp,cp,beg,regexpr); /* beg ignored if regexpr (starts at region 0 = si->begs[]) */
      si = sc->si;
      sfs = sc->sfs;
      if (regexpr)
	dur = sc->dur;
      else dur = count;
      if (dur == 0) dur = 1;
      for (chan=0;chan<si->chans;chan++)
	{
	  afs = get_chans_needed_by_eval(ss,cp,sp->eval_tree,cp->cursor,READ_FORWARD);
	  snd_search_fd = sfs[chan];
	  changes = NULL;
	  for (i=0;i<dur;i++)
	    {
	      val = sop_eval(sp->eval_tree);
	      if ((dot_changes) && (!changes)) changes = (assign_info **)calloc(dur,sizeof(assign_info *));
	      if (changes) changes[i] = dot_changes; 
	      /* we save edits as we run down the data -- no actual changes occur until the loop is done */
	      /* this means y(2) = y(1) (for example) refers to current data, not the changing form */
	      dot_changes = NULL;
	      next_sample_1(snd_search_fd);
	      if (afs) next_samples_for_eval(afs);
	      passes++;
	      if (passes>=10000)
		{
		  check_for_event(ss);
		  if (ss->stopped_explicitly)
		    {
		      ss->stopped_explicitly = 0;
		      report_in_minibuffer(sp,"stopped");
		      break;
		    }
		  passes = 0;
		}
	    }
	  snd_search_fd = free_snd_fd(snd_search_fd);
	  if (afs) {free_chans_for_eval(afs); free(afs); afs = NULL;}
	  /* now run through the changes array looking for possible assignments,
	   * if any create an array and fill it with the values given (at the offsets desired!)
	   * then send the entire block to change_samples
	   */

	  if (changes)
	    {
	      cbeg = dur;
	      end = 0;
	      for (i=0;i<dur;i++) maxmin_off(changes[i],i,&cbeg,&end);
	      size = end-cbeg+1;
	      data = (int *)calloc(size,sizeof(int));
	      for (i=0;i<dur;i++)
		{
		  if (changes[i])
		    {
		      assign_values(data,changes[i],i,cbeg);
		      free_assign_info(changes[i]);
		      changes[i] = NULL;
		    }
		}
	      change_samples(si->begs[chan]+cbeg,size,data,si->cps[chan],LOCK_MIXES);
	      free(data);
	      free(changes);
	      changes = NULL;
	      check_for_first_edit(si->cps[chan]);
	      update_graph(si->cps[chan],NULL);
	    }
	}
      if ((!regexpr) && (dur == 1))
	{
	  sprintf(expr_str,"%s = %s",sp->eval_expr,s1 = prettyf(val,2));
	  text_set_string(snd_widget(sp,W_snd_info),expr_str);
	  free(s1);
	}
      free(sc);
      free_sync_info(si); /* no special stuff here, nothing done to sfs */
      free(sfs);
    }
}


/* -------- Keyboard Macros -------- */
/* optimized for the most common case (pure keyboard commands) */

static int defining_macro = 0;
static int macro_cmd_size = 0;
static int macro_size = 0;
typedef struct {int keysym; int state; char *sndop;} macro_cmd;
static macro_cmd **macro_cmds = NULL;
typedef struct {char *name; int macro_size; macro_cmd **cmds;} named_macro;
static named_macro **named_macros = NULL;
static int named_macro_ctr = 0;
static int named_macro_size = 0;

static void allocate_macro_cmds(void)
{
  int i,old_size;
  old_size = macro_cmd_size;
  macro_cmd_size += 16;
  if (!macro_cmds)
    macro_cmds = (macro_cmd **)calloc(macro_cmd_size,sizeof(macro_cmd *));
  else 
    {
      macro_cmds = (macro_cmd **)realloc(macro_cmds,macro_cmd_size * sizeof(macro_cmd *));
      for (i=old_size;i<macro_cmd_size;i++) macro_cmds[i] = NULL;
    }
}

static void start_defining_macro (void)
{
  macro_size = 0;
  defining_macro = 1;
  if ((!macro_cmds) || (macro_size == macro_cmd_size)) allocate_macro_cmds();
}

static void stop_defining_macro (void)
{
  /* the last C-x ) went into the macro before we noticed it should not have */
  macro_size -= 2;
  defining_macro = 0;
}

static void cancel_macro (void)
{
  defining_macro = 0;
  /* should we back up to previous here ? */
}

static void execute_last_macro (chan_info *cp, int count)
{
  int i,j;
  if (macro_cmds)
    {
      for (j=0;j<count;j++)
	{
	  for (i=0;i<macro_size;i++) 
	    {
	      keyboard_command(cp,macro_cmds[i]->keysym,macro_cmds[i]->state);
	    }
	}
    }
}

static void continue_macro (int keysym, int state)
{
  if (!(macro_cmds[macro_size])) macro_cmds[macro_size] = (macro_cmd *)calloc(1,sizeof(macro_cmd));
  macro_cmds[macro_size]->keysym = keysym;
  macro_cmds[macro_size]->state = state;
  macro_size++;
  if (macro_size == macro_cmd_size) allocate_macro_cmds();
}

static named_macro *name_macro(char *name)
{
  named_macro *nm;
  int i,old_size;
  if (named_macro_ctr == named_macro_size)
    {
      old_size = named_macro_size;
      named_macro_size += 16;
      if (!named_macros) named_macros = (named_macro **)calloc(named_macro_size,sizeof(named_macro *));
      else 
	{
	  named_macros = (named_macro **)realloc(named_macros,named_macro_size * sizeof(named_macro *));
	  for (i=old_size;i<named_macro_size;i++) named_macros[i] = NULL;
	}
    }
  if (!(named_macros[named_macro_ctr])) named_macros[named_macro_ctr] = (named_macro *)calloc(1,sizeof(named_macro));
  nm = named_macros[named_macro_ctr];
  nm->name = (char *)calloc(strlen(name)+1,sizeof(char));
  strcpy(nm->name,name);
  named_macro_ctr++;
  return(nm);
}

static void name_last_macro (char *name)
{
  named_macro *nm;
  macro_cmd *mc;
  int i;
  nm = name_macro(name);
  nm->macro_size = macro_size;
  nm->cmds = (macro_cmd **)calloc(macro_size,sizeof(macro_cmd *));
  for (i=0;i<macro_size;i++)
    {
      nm->cmds[i] = (macro_cmd *)calloc(1,sizeof(macro_cmd));
      mc = nm->cmds[i];
      mc->keysym = macro_cmds[i]->keysym;
      mc->state = macro_cmds[i]->state;
    }
}

void load_macro(char *name, char *clm_buffer, int start, int end)
{
  int size;
  macro_cmd *mc;
  named_macro *nm;
  char *command;
  int i,j,k,commenting,parens;
  int body = 0;
  commenting = 0;
  parens = 0;
  size = 0;
  for (i=start+1;i<end;i++)
    {
      if ((commenting == 0) && (clm_buffer[i] == ';')) commenting = 1;
      else if ((commenting == 1) && (clm_buffer[i] == '\n')) commenting = 0;
      else if ((commenting == 0) && (clm_buffer[i] == '#') && (clm_buffer[i+1] == '|')) commenting = 2;
      else if ((commenting == 2) && (clm_buffer[i] == '|') && (clm_buffer[i+1] == '#')) commenting = 0;
      if (commenting == 0)
	{
	  if (clm_buffer[i] == '(') parens++;
	  else if (clm_buffer[i] == ')') 
	    {
	      parens--; 
	      if (parens == 0) 
		{
		  if (size == 0) body = i+1;
		  size++;
		}
	    }
	}
    }
  size--; /* counted arg list above */
  nm = name_macro(name);
  nm->macro_size = size;
  nm->cmds = (macro_cmd **)calloc(size,sizeof(macro_cmd *));
  /* now run the loop again loading up the commands */
  j = 0;
  commenting = 0;
  parens = 0;
  for (i=body;i<end;i++)
    {
      if ((commenting == 0) && (clm_buffer[i] == ';')) commenting = 1;
      else if ((commenting == 1) && (clm_buffer[i] == '\n')) commenting = 0;
      else if ((commenting == 0) && (clm_buffer[i] == '#') && (clm_buffer[i+1] == '|')) commenting = 2;
      else if ((commenting == 2) && (clm_buffer[i] == '|') && (clm_buffer[i+1] == '#')) commenting = 0;
      if (commenting == 0)
	{
	  if (clm_buffer[i] == '(') {if (parens == 0) start = i; parens++;}
	  else if (clm_buffer[i] == ')') 
	    {
	      parens--; 
	      if (parens == 0) 
		{
		  command = (char *)calloc(i-start+2,sizeof(char *)); /* i-start+1 = strlen, +1 for null byte at end */
		  for (k=0;k<i-start+1;k++) command[k] = clm_buffer[k+start];
		  command[i-start+1] = '\0'; /* there may be trailing chars in the clm_buffer */
		  nm->cmds[j] = (macro_cmd *)calloc(1,sizeof(macro_cmd));
		  mc = nm->cmds[j];
		  j++;
		  mc->keysym = 0;
		  mc->sndop = command;
		}
	    }
	}
    }
}

static void save_macro_1(named_macro *nm, int fd)
{
  int i;
  macro_cmd *mc;
  sprintf(expr_str,"(defmacro %s ()\n",nm->name);
  write(fd,expr_str,strlen(expr_str));
  for (i=0;i<nm->macro_size;i++)
    {
      mc = nm->cmds[i];
      if (mc->keysym == 0)
	{
	  sprintf(expr_str,"  %s\n",mc->sndop);
	  write(fd,expr_str,strlen(expr_str));
	}
      else
	{
	  sprintf(expr_str,"  (key %c %d)\n",(char)(mc->keysym),mc->state);
	  write(fd,expr_str,strlen(expr_str));
	}
    }
  sprintf(expr_str,")\n");
  write(fd,expr_str,strlen(expr_str));
}

void save_macro(snd_state *ss, char *name)
{
  int fd,k;
  fd = open_snd_init_file(ss);
  for (k=0;k<named_macro_ctr;k++)
    {
      if (strcmp(name,named_macros[k]->name) == 0)
	{
	  save_macro_1(named_macros[k],fd);
	  return;
	}
    }
  close(fd);
}

void save_macro_state (snd_state *ss, int fd)
{
  int i;
  for (i=0;i<named_macro_ctr;i++) save_macro_1(named_macros[i],fd);
}

void save_macros(snd_state *ss)
{
  /* write out macro as quasi-lisp at end of current init file */
  int fd;
  fd = open_snd_init_file(ss);
  save_macro_state(ss,fd);
  close(fd);
}

static int execute_named_macro_1(chan_info *cp, char *name, int count)
{
  /* TODO: add more lisp-isms? (i.e. if progn when dotimes) */
  int i,j,k;
  named_macro *nm;
  macro_cmd *mc;
  for (k=0;k<named_macro_ctr;k++)
    {
      if (strcmp(name,named_macros[k]->name) == 0)
	{
	  nm = named_macros[k];
	  for (j=0;j<count;j++)
	    {
	      for (i=0;i<nm->macro_size;i++) 
		{
		  mc = nm->cmds[i];
		  if (mc->keysym != 0)
		    {
		      keyboard_command(cp,mc->keysym,mc->state);
		    }
		  else
		    {
		      clm_doit(cp->state,mc->sndop,0,strlen(mc->sndop)-1);
		    }
		}
	    }
	  return(1);
	}
    }
  return(0);
}

int execute_macro(chan_info *cp, char *name, int count)
{
  return(execute_named_macro_1(cp,name,count));
  /* if called from clm_doit, don't recurse! */
}

static void execute_named_macro(chan_info *cp, char *name, int count)
{
  if (!(execute_named_macro_1(cp,name,count)))
    {
      /* not a macro...*/
      if (name[0] == '(')
	clm_doit(cp->state,name,0,strlen(name)-1);
      else clm_doit(cp->state,name,-1,strlen(name)); /* no parens here (hence -1 etc) */
    }
}

static char **user_keymap = NULL;
#define SIZEOF_KEYMAP 256

void set_keymap_entry(int sym, char *val)
{
  if (!user_keymap) user_keymap = (char **)calloc(SIZEOF_KEYMAP,sizeof(char *));
  if (sym < SIZEOF_KEYMAP)
    {
      if (user_keymap[sym]) free(user_keymap[sym]);
      user_keymap[sym] = val;
    }
  else fprintf(stderr,"keymap[%d] beyond [%d]",sym,SIZEOF_KEYMAP);
}

static void call_user_keymap(int keysym, int count, chan_info *cp)
{
  if ((user_keymap) && (user_keymap[keysym])) execute_macro(cp,user_keymap[keysym],count);
}

static char *dir_from_tempnam(char *td)
{
  char *name;
  int i;
  name = tempnam(td,"snd_");
  i = strlen(name)-1;
  while ((name[i] != '/') && (i>0)) i--;
  if (i == 0) name[0]='.';
  name[i+1]='\0';
  return(name);
}

static char snd_load_dividers[3]={' ',',',';'};

void snd_info_activate(snd_info *sp, int keysym)
{
  snd_state *ss;
  snd_info *nsp;
  int s_or_r = 0;
  int err,nc,i,len;
  chan_info *active_chan;
  char *str = NULL;
  char *tok,*tok1,*newdir,*str1;
  env *e;
  mark *m;
  float *ufargs;
  int nufargs;
  DIR *dp;

  if ((keysym == snd_K_s) || (keysym == snd_K_r)) s_or_r = 1;
  ss = sp->state;
  select_sound(ss,sp);
  active_chan = current_channel(sp);
  if (active_chan)
    {
      goto_graph(active_chan);
    }
  if ((keysym == snd_K_g) || (keysym == snd_K_G)) /* c-g => abort whatever we're doing and return */
    {
      text_set_string(snd_widget(sp,W_snd_info),NULL);
      clear_minibuffer(sp);
      return;
    }

  if (sp->searching)
    {
      /* it's the search expr request */
      /* if not nil, replace previous */
      if (!s_or_r)
	{
	  str = text_get_string(snd_widget(sp,W_snd_info));
	  if ((str) && (*str))
	    {
	      if (sp->search_tree) free_sop(sp->search_tree);
	      if (sp->search_expr) free(sp->search_expr);
	      sp->search_expr = str;
	      sp->search_tree = sop_parse(str);
	      if (!sp->search_tree)
		{
		  sprintf(expr_str,"%s: %s",str,sop_parse_error_name());
		  text_set_string(snd_widget(sp,W_snd_info),expr_str);
		}
	    }
	}
      if (active_chan)
	handle_cursor(active_chan,cursor_search(active_chan,sp->searching));
      return;
    }
  
  str = text_get_string(snd_widget(sp,W_snd_info));
  if ((sp->marking) || (sp->finding_mark))
    {
      if (sp->marking) 
	{
	  m = add_mark(sp->marking-1,str,active_chan);
	  if (m)
	    {
	      sprintf(expr_str,snd_string_placed_at_sample,str,sp->marking-1);
	      draw_mark(active_chan,active_chan->axis,m);
	    }
	  else
	    {
	      sprintf(expr_str,snd_string_There_is_already_a_mark_at_sample,sp->marking-1);
	    }
	  text_set_string(snd_widget(sp,W_snd_info),expr_str);
	  sp->marking = 0;
	}	
      else 
	{
	  handle_cursor(active_chan,goto_named_mark(active_chan,str));
	  sp->finding_mark = 0;
	}
      return;
    }
  if (strlen(str) != 0)
    {
      if (sp->printing)
	{
	  snd_print(ss,str,(sp->printing != 1));
	  sp->printing = 0;
	  clear_minibuffer(sp);
	  return;
	}
      if (sp->loading)
	{
	  /* str should contain the library pathname and the function name */
	  tok = complete_filename(strtok(str,snd_load_dividers));
	  tok1 = strtok(NULL,snd_load_dividers);
	  err = snd_load_ufun(tok,tok1);
	  if (err != 0)
	    {
	      if (err == -1)
		sprintf(expr_str,"can't dlopen %s ",tok);
	      else sprintf(expr_str,snd_string_cant_load_from,tok1,tok);
	      text_set_string(snd_widget(sp,W_snd_info),expr_str);
	    }
	  else clear_minibuffer(sp);
	  sp->loading = 0;
	  return;
	}
      if (sp->filing)
	{
	  switch (sp->filing)
	    {
	    case NOT_FILING: fprintf(stderr,"oh good grief!"); break;
	    case INPUT_FILING:
	      nsp = snd_open_file(str,ss); /* will post error if any */
	      if (nsp) 
		{
		  select_channel(nsp,0);
		  clear_minibuffer(sp);
		}
	      break;
	    case REGION_FILING:
	      save_region(ss,region_count,complete_filename(str));
	      clear_minibuffer(sp);
	      break;
	    case CHANNEL_FILING:
	      chan_save_edits(active_chan,complete_filename(str));
	      clear_minibuffer(sp);
	      break;
	    case TEMP_FILING:
	      newdir = copy_string(str);
	      clear_minibuffer(sp);
	      dp = opendir(newdir);
	      if (dp) 
		{
		  closedir(dp);
		  ss->temp_dir = newdir;
		}
	      else 
		{
		  tok = dir_from_tempnam(ss->temp_dir);
		  sprintf(expr_str,snd_string_cant_access,newdir,tok);
		  text_set_string(snd_widget(sp,W_snd_info),expr_str);
		  if (newdir) free(newdir);
		  if (tok) free(tok);
		}
	      break;
	    case CHANGE_FILING:
	      mix_complete_file(sp,str,FROM_KEYBOARD);
	      break;
	    case INSERT_FILING:
	      str1 = complete_filename(str);
	      err = c_read_header(str1);
	      if (err == 0)
		{
		  nc = c_snd_header_chans();
		  len = c_snd_header_data_size()/nc;
		  if (nc > sp->nchans) nc = sp->nchans;
		  if (!active_chan) active_chan = sp->chans[0];
		  for (i=0;i<nc;i++)
		    {
		      file_insert_samples(active_chan->cursor,len,str1,sp->chans[i],i,DONT_DELETE_ME);
		      check_for_first_edit(sp->chans[i]);
		      update_graph(sp->chans[i],NULL);
		    }
		  clear_minibuffer(sp);
		}
	      else 
		{
		  sprintf(expr_str,snd_string_cant_read_his_header,str);
		  text_set_string(snd_widget(sp,W_snd_info),expr_str);
		}
	      break;
	    case MACRO_FILING: name_last_macro(str); clear_minibuffer(sp); break;
	    default: fprintf(stderr,"unknown filing option: %d",sp->filing); break;
	    }
	  sp->filing = NOT_FILING;
	  return;
	}
      if (sp->amping)
	{
	  if (!active_chan) active_chan = sp->chans[0];
	  err = 0;
	  while ((str) && ((*str) == ' ')) str++;
	  e = scan_envelope_and_report_error(sp,str,&err);
	  if ((e) && (!err))
	    {
	      if (sp->amping != 1)
		apply_env(active_chan,e,active_chan->cursor,sp->amping,1.0,sp->reging,FALSE);
	      else apply_env(active_chan,e,0,current_ed_samples(active_chan),1.0,sp->reging,FALSE);
	    }
	  sp->reging = 0;
	  sp->amping = 0;
	  if (!err) clear_minibuffer(sp);
	  return;
	}
      if (sp->macroing)
	{
	  len = active_chan->cursor;
	  execute_named_macro(active_chan,str,sp->macroing);
	  ss->mx_sp = NULL;
	  sp->macroing = 0;
	  if (active_chan->cursor != len)
	    {
	      nc = cursor_decision(active_chan);
	      if (nc == CURSOR_IN_VIEW) nc = CURSOR_UPDATE_DISPLAY; 
	    }
	  else nc = CURSOR_UPDATE_DISPLAY; 
	  handle_cursor(active_chan,nc);
	  return;
	}
      if (sp->ufuning)
	{
	  /* str contains a function name and possibly arg exprs func(arg,arg,arg...) */
	  /* we'll also accept just the function name, and func() */
	  tok = strtok(str,"("); /* look for args -- will flush the '(' */
	  nufargs = 0;
	  ufargs = (float *)calloc(16,sizeof(float));
	  sp->ufunp = find_ufun(sp,tok);
	  if (sp->ufunp)
	    {
	      while ((tok = strtok(NULL,",)"))) sscanf(tok,"%f",&(ufargs[nufargs++]));
	      sp->ufun_nargs = nufargs;
	      if (sp->ufun_args) free(sp->ufun_args);
	      sp->ufun_args = ufargs;
	      invoke_ufun_with_ptr(active_chan,sp->ufunp,ufargs,nufargs,sp->ufuning,sp->reging);
	      clear_minibuffer(sp);
	    }
	  else report_in_minibuffer(sp,snd_string_no_such_function_loaded);
	  sp->ufuning = 0;
	  sp->reging = 0;
	  return;
	}
      if (sp->eval_tree) free_sop(sp->eval_tree);
      if (sp->eval_expr) free(sp->eval_expr);
      sp->eval_expr = str;
      sp->eval_tree = sop_parse(str);
      if (!sp->eval_tree)
	{
	  if (sop_error()) /* null tree return is not necessarily an error */
	    {
	      sprintf(expr_str,"%s: %s",str,sop_parse_error_name());
	      text_set_string(snd_widget(sp,W_snd_info),expr_str);
	    }
	}
      else
	{
	  eval_expression(active_chan,sp,sp->evaling,sp->reging);
	  make_button_label(snd_widget(sp,W_snd_info_label),"     ");
	}
      sp->evaling = 0;
      sp->reging = 0;
    }
  else clear_minibuffer(sp);
}



static int get_count_1(char *number_buffer,int number_ctr, int dot_seen, chan_info *cp)
{
  /* allow floats here = secs */
  float f;
  int i;
  if (number_ctr == 0) return(1); /* c-u followed by nothing = 1 */
  number_buffer[number_ctr] = '\0';
  if (number_ctr == 1)
    { /* handle special cases of just - or + */
      if (number_buffer[0] == '-') return(-1);
      if (number_buffer[0] == '+') return(1);
    }
  if (dot_seen)
    {
      sscanf(number_buffer,"%f",&f);
      return(f*snd_SRATE(cp));
    }
  else
    {
      sscanf(number_buffer,"%d",&i);
      return(i);
    }
  return(1);
}

static int get_count(char *number_buffer,int number_ctr, int dot_seen, chan_info *cp, int mark_wise)
{
  int val,old_cursor;
  val = get_count_1(number_buffer,number_ctr,dot_seen,cp);
  if (!mark_wise) return(val);
  old_cursor = cp->cursor;
  goto_mark(cp,val);
  val = cp->cursor - old_cursor; /* will be 0 if no relevant marks */
  cp->cursor = old_cursor;
  return(val);
}

#define NUMBER_BUFFER_SIZE 12

static void dks(void) {finish_keyboard_selection();}
static int cks(void) {return(cancel_keyboard_selection());}

static float state_amount (int state)
{
  float amount;
  amount = 1.0;
  if (state & snd_ControlMask) amount *= 0.5;
  if (state & snd_MetaMask) amount *= 0.5;
  if (state & snd_ShiftMask) amount *= 0.5;
  return(amount);
}

int keyboard_command (chan_info *cp, int keysym, int state)
{
  /* we can't use the meta bit in most cases because this is trapped at a higher level for the Menu mnemonics */
  /* state here is the kbd bucky-bit state */
  static int number_ctr = 0;
  static int dot_seen = 0;
  static int counting = 0;
  static int u_count = 0;
  static char number_buffer[NUMBER_BUFFER_SIZE];
  static int extended_mode = 0;
  static int count = 1;
  static int m = 0;
  int redisplay,searching,cursor_searching,explicit_count,old_cursor_loc;
  static int ext_explicit_count; /* these are needed for region counts which are 0-based, unlike everything else */
  static int ext_count;
  snd_info *sp;
  axis_info *ap;
  snd_state *ss;
  mark *mk;
  redisplay = CURSOR_IN_VIEW;
  searching = 0;
  cursor_searching = 0;
  /* cp->cursor_on = 1; */
  sp = cp->sound;
  ss = cp->state;
  ap = cp->axis;
  if (keysym >= snd_K_Shift_L) return(KEYBOARD_NO_ACTION);
  if (defining_macro) continue_macro(keysym,state);
  if (!m) count = 1; else m=0;
  if ((counting) && (((keysym < snd_K_0) || (keysym > snd_K_9)) && (keysym != snd_K_minus) && (keysym != snd_K_period) && (keysym != snd_K_plus)))
    {
      m = ((u_count) && ((keysym == snd_K_M) || (keysym == snd_K_m)));
      count = get_count(number_buffer,number_ctr,dot_seen,cp,m);
      number_ctr = 0;
      counting = 0;
      dot_seen = 0;
      explicit_count = count;
      if (m) return(KEYBOARD_NO_ACTION);
    }
  else explicit_count = 0;
  u_count = 0;
  if (count == 0) return(KEYBOARD_NO_ACTION);
  if ((state & snd_MetaMask) && ((keysym == snd_K_X) || (keysym == snd_K_x)))
    {
      /* named macros invoked and saved here */
      ss->mx_sp = sp;
      prompt(sp,"M-x:");
      sp->macroing = count;
      return(KEYBOARD_NO_ACTION);
    }
  if (state & snd_ControlMask)
    {
      if (!extended_mode)
	{
	  switch (keysym)
	    {
	    case snd_K_A: case snd_K_a: cp->cursor_on = 1; cursor_moveto(cp,ap->x0*snd_SRATE(cp)); break;
	    case snd_K_B: case snd_K_b: cp->cursor_on = 1; redisplay = cursor_move(cp,-count); break;
	    case snd_K_D: case snd_K_d: cp->cursor_on = 1; cks(); redisplay = cursor_delete(cp,count); break;
	    case snd_K_E: case snd_K_e: cp->cursor_on = 1; cursor_moveto(cp,ap->x1*snd_SRATE(cp)); break;
	    case snd_K_F: case snd_K_f: cp->cursor_on = 1; redisplay = cursor_move(cp,count); break;
	    case snd_K_G: case snd_K_g: 
	      number_ctr = 0; counting = 0; dot_seen = 0; cks(); cancel_macro(); if (ss->checking_explicitly) ss->stopped_explicitly = 1; 
	      /* this tries to break out of long filter/src computations (and perhaps others) */
	      break;
	    case snd_K_H: case snd_K_h: 
	      if (sp->ufunp)
		/* should this be just like C-S? */
		invoke_ufun_with_ptr(cp,sp->ufunp,sp->ufun_args,sp->ufun_nargs,count,0);
	      else {get_ufun(sp,count,0); searching=1;}
	      break;
	    case snd_K_I: case snd_K_i: show_cursor_info(cp); searching = 1; break;
	    case snd_K_J: case snd_K_j: cp->cursor_on = 1; redisplay = goto_mark(cp,count); break;
	    case snd_K_K: case snd_K_k: cp->cursor_on = 1; cks(); redisplay = cursor_delete(cp,count*ss->editor_line_size); break;
	    case snd_K_L: case snd_K_l: cp->cursor_on = 1; redisplay = CURSOR_IN_MIDDLE; break;
	    case snd_K_M: case snd_K_m:
	      if (count > 0) 
		{
		  cp->cursor_on = 1;
		  show_marks(ss);
		  mk = add_mark(cp->cursor,NULL,cp);
		  if (mk) draw_mark(cp,cp->axis,mk);
		}
	      else delete_mark(cp->cursor,cp);
	      break;
	    case snd_K_N: case snd_K_n: cp->cursor_on = 1; redisplay = cursor_move(cp,count*ss->editor_line_size); break;
	    case snd_K_O: case snd_K_o: cp->cursor_on = 1; cks(); redisplay = cursor_insert(cp,count); break;
	    case snd_K_P: case snd_K_p: cp->cursor_on = 1; redisplay = cursor_move(cp,-count*ss->editor_line_size); break;
	    case snd_K_Q: case snd_K_q: 
	      start_playing(cp,cp->cursor); 
	      set_play_button(sp,1); 
	      return(KEYBOARD_NO_ACTION); 
	      break;
	    case snd_K_R: case snd_K_r: cp->cursor_on = 1; redisplay = cursor_search(cp,-count); searching = 1; cursor_searching = 1; break;
	    case snd_K_S: case snd_K_s: cp->cursor_on = 1; redisplay = cursor_search(cp,count); searching = 1; cursor_searching = 1; break;
	    case snd_K_T: case snd_K_t: 
	      stop_playing(sp->playing); 
	      set_play_button(sp,0);
	      return(KEYBOARD_NO_ACTION); 
	      break;
	    case snd_K_U: case snd_K_u: counting = 1; u_count = 1; number_ctr = 0; dot_seen = 0; break;
	    case snd_K_V: case snd_K_v:
	      cp->cursor_on = 1;
	      if (count > 0)
		redisplay = cursor_moveto(cp,ap->x1*snd_SRATE(cp)+1+(count-1)*snd_SRATE(cp)*(ap->x1 - ap->x0));
	      else redisplay = cursor_moveto(cp,ap->x0*snd_SRATE(cp)-1+(count+1)*snd_SRATE(cp)*(ap->x1 - ap->x0));
	      break;
	    case snd_K_W: case snd_K_w: dks(); delete_selection(); redisplay = CURSOR_UPDATE_DISPLAY; break;
	    case snd_K_X: case snd_K_x: dks(); extended_mode = 1; ext_count = count; ext_explicit_count = explicit_count; break;
	    case snd_K_Y: case snd_K_y: dks(); paste_region(explicit_count,cp); redisplay = CURSOR_UPDATE_DISPLAY; break;
	    case snd_K_Z: case snd_K_z: cks(); cp->cursor_on = 1; redisplay = cursor_zeros(cp,count,0); break;
	    case snd_K_greater: cp->cursor_on = 1; redisplay = cursor_moveto_end(cp); break;
	    case snd_K_less: cp->cursor_on = 1; redisplay = cursor_moveto_beginning(cp); break;
	    case snd_K_Right: sx_incremented(cp,state_amount(state)); break;
	    case snd_K_Left: sx_incremented(cp,-state_amount(state)); break;
	    case snd_K_Up: zx_incremented(cp,1.0+state_amount(state)); break;
	    case snd_K_Down: zx_incremented(cp,1.0/(1.0+state_amount(state))); break;
	    case snd_K_0: case snd_K_1: case snd_K_2: case snd_K_3: case snd_K_4:
	    case snd_K_5: case snd_K_6: case snd_K_7: case snd_K_8: case snd_K_9: 
	      counting = 1;
	      number_buffer[number_ctr]=(char)('0'+keysym-snd_K_0); 
	      if (number_ctr < (NUMBER_BUFFER_SIZE-2)) number_ctr++; 
	      /* there is also the bare-number case below */
	      break;
	    case snd_K_space: 
	      if (count > 0) {start_keyboard_selection(cp,cp->cx); return(KEYBOARD_NO_ACTION);}
	      else {cks(); deactivate_selection();}
	      break;
	    case snd_K_period: 
	      if (state & snd_ShiftMask) {cp->cursor_on = 1; redisplay = cursor_moveto_end(cp);}
	      else {counting = 1; number_buffer[number_ctr]='.'; number_ctr++; dot_seen = 1;}
	      break;
	    case snd_K_comma: if (state & snd_ShiftMask) redisplay = cursor_moveto_beginning(cp); break;
	    case snd_K_minus: 
	      if (state & snd_ShiftMask)
		{
		  undo_EDIT(cp,count); redisplay = CURSOR_UPDATE_DISPLAY;
		}
	      else
		{
		  counting = 1; number_ctr = 1; number_buffer[0]='-'; 
		}
	      break;
#if !defined(NEXT) && !defined(UW2)
	    case snd_keypad_Left: case snd_keypad_4: ss->spectro_yangle-=1.0; redisplay = CURSOR_UPDATE_DISPLAY; reflect_spectro(ss); break;
	    case snd_keypad_Right: case snd_keypad_6: ss->spectro_yangle+=1.0; redisplay = CURSOR_UPDATE_DISPLAY; reflect_spectro(ss); break;
	    case snd_keypad_Down: case snd_keypad_2: ss->spectro_xangle-=1.0; redisplay = CURSOR_UPDATE_DISPLAY; reflect_spectro(ss); break;
	    case snd_keypad_Up: case snd_keypad_8: ss->spectro_xangle+=1.0; redisplay = CURSOR_UPDATE_DISPLAY; reflect_spectro(ss); break;
#else
	    case snd_keypad_4: ss->spectro_yangle-=1.0; redisplay = CURSOR_UPDATE_DISPLAY; reflect_spectro(ss); break;
	    case snd_keypad_6: ss->spectro_yangle+=1.0; redisplay = CURSOR_UPDATE_DISPLAY; reflect_spectro(ss); break;
	    case snd_keypad_2: ss->spectro_xangle-=1.0; redisplay = CURSOR_UPDATE_DISPLAY; reflect_spectro(ss); break;
	    case snd_keypad_8: ss->spectro_xangle+=1.0; redisplay = CURSOR_UPDATE_DISPLAY; reflect_spectro(ss); break;
#endif
	    }
	}
      else /* extended mode with ctrl down */
	{
	  extended_mode = 0;
	  switch (keysym)
	    {
	    case snd_K_A: case snd_K_a: get_amp_expression(sp,ext_count,0); searching = 1; redisplay = CURSOR_IN_VIEW; break;
	    case snd_K_B: case snd_K_b: redisplay = set_window_bounds(cp,ext_count); break;
	    case snd_K_C: case snd_K_c: sound_hide_ctrls(sp); break;
	    case snd_K_D: case snd_K_d: prompt(sp,snd_string_eps_file_p); sp->printing = ext_count; searching = 1; break;
	    case snd_K_E: case snd_K_e: prompt(sp,snd_string_macro_name_p); sp->filing = MACRO_FILING; searching = 1; break;
	    case snd_K_F: case snd_K_f: prompt(sp,snd_string_file_p); sp->filing = INPUT_FILING; searching = 1; break;
	    case snd_K_G: case snd_K_g: 
	      number_ctr = 0; counting = 0; dot_seen = 0; cancel_macro(); if (ss->checking_explicitly) ss->stopped_explicitly = 1; break;
	    case snd_K_H: case snd_K_h: get_ufun(sp,ext_count,0); searching = 1; break;
	    case snd_K_I: case snd_K_i: prompt(sp,snd_string_insert_file_p); sp->filing = INSERT_FILING; searching = 1; break;
	    case snd_K_J: case snd_K_j: cp->cursor_on = 1; redisplay = goto_mix(cp,ext_count); break;
	    case snd_K_L: case snd_K_l: prompt(sp,snd_string_load_p); sp->loading = 1; searching = 1; break;
	    case snd_K_M: case snd_K_m:
	      cp->cursor_on = 1; 
	      redisplay = add_named_mark(cp); 
	      show_marks(ss); 
	      searching = 1; 
	      break;
	    case snd_K_N: case snd_K_n: eval_expression(cp,sp,ext_count,0); searching = 1; redisplay = CURSOR_IN_VIEW; break;
	    case snd_K_O: case snd_K_o: sound_show_ctrls(sp); break;
	    case snd_K_P: case snd_K_p: redisplay = set_window_size(cp,ext_count); break;
	    case snd_K_Q: case snd_K_q: prompt(sp,snd_string_mix_file_p); sp->filing = CHANGE_FILING; searching = 1; break;
	    case snd_K_R: case snd_K_r: redo_EDIT(cp,ext_count); redisplay = CURSOR_UPDATE_DISPLAY; break;
	    case snd_K_S: case snd_K_s: save_edits(sp,NULL); redisplay = CURSOR_IN_VIEW; break;
	    case snd_K_T: case snd_K_t: stop_playing(((snd_info *)(sp))->playing); return(KEYBOARD_NO_ACTION); break;
	    case snd_K_U: case snd_K_u: undo_EDIT(cp,ext_count); redisplay = CURSOR_UPDATE_DISPLAY; break;
	    case snd_K_V: case snd_K_v: redisplay = set_window_percentage(cp,ext_count); break;
	    case snd_K_W: case snd_K_w: prompt(sp,snd_string_file_p); sp->filing = CHANNEL_FILING; searching = 1; break;
	    case snd_K_X: case snd_K_x: get_eval_expression(sp,ext_count,0); searching = 1; redisplay = CURSOR_IN_VIEW; break;
	    case snd_K_Y: case snd_K_y: get_auto_ufun(sp,ext_count); searching = 1; break;
	    case snd_K_Z: case snd_K_z: cp->cursor_on = 1; cos_smooth(cp,cp->cursor,ext_count,0); redisplay = CURSOR_UPDATE_DISPLAY; break;
	    case snd_K_Right: sx_incremented(cp,state_amount(state)); break;
	    case snd_K_Left:  sx_incremented(cp,-state_amount(state)); break;
	    case snd_K_Up: zx_incremented(cp,1.0+state_amount(state)); break;
	    case snd_K_Down: zx_incremented(cp,1.0/(1.0+state_amount(state))); break;
	    }
	}
    }
  else
    {
      if (!extended_mode)
	{ /* no control/meta, not extended mode -- bare alpha chars mapped through user-table */
	  switch (keysym)
	    {
	    case snd_K_0: case snd_K_1: case snd_K_2: case snd_K_3: case snd_K_4:
	    case snd_K_5: case snd_K_6: case snd_K_7: case snd_K_8: case snd_K_9: 
	      counting = 1;
	      number_buffer[number_ctr]=(char)('0'+keysym-snd_K_0); 
	      if (number_ctr < (NUMBER_BUFFER_SIZE-2)) number_ctr++; 
	      break;
	    case snd_K_period: 
	      if (state & snd_ShiftMask) {cp->cursor_on = 1; redisplay = cursor_moveto_end(cp);}
	      else {counting=1; number_buffer[number_ctr]='.'; number_ctr++; dot_seen = 1;}
	      break;
	    case snd_K_comma: if (state & snd_ShiftMask) {cp->cursor_on = 1; redisplay = cursor_moveto_beginning(cp);} break;
	    case snd_K_minus: if (!(state & snd_ShiftMask)) {counting=1; number_buffer[0]='-'; number_ctr=1;} break;
	    case snd_K_Right: sx_incremented(cp,state_amount(state)); break;
	    case snd_K_Left:  sx_incremented(cp,-state_amount(state)); break;
	    case snd_K_Up: zx_incremented(cp,1.0+state_amount(state)); break;
	    case snd_K_Down: zx_incremented(cp,1.0/(1.0+state_amount(state))); break;
	    case snd_K_Home: snd_update(ss,sp); break;
	    case snd_K_space: 
	      old_cursor_loc = cks(); /* can be -1 => not actively selecting via kbd */
	      if (old_cursor_loc == -1) 
		{
		  deactivate_selection(); 
		  if (play_in_progress()) toggle_dac_pausing(ss);
		}
	      else /* all syncd chans need to reset cursor */
		cursor_moveto(cp,old_cursor_loc);
	      break;

	      /* fUn WiTh KeYpAd! */
#if !defined(NEXT) && !defined(UW2)
	    case snd_keypad_Up: case snd_keypad_8: ss->spectro_zscl+=.01; redisplay = CURSOR_UPDATE_DISPLAY; reflect_spectro(ss); break;
	    case snd_keypad_Down: case snd_keypad_2: ss->spectro_zscl-=.01; redisplay = CURSOR_UPDATE_DISPLAY; reflect_spectro(ss); break;
	    case snd_keypad_Left: case snd_keypad_4: ss->spectro_zangle-=1.0; redisplay = CURSOR_UPDATE_DISPLAY; reflect_spectro(ss); break;
	    case snd_keypad_Right: case snd_keypad_6: ss->spectro_zangle+=1.0; redisplay = CURSOR_UPDATE_DISPLAY; reflect_spectro(ss); break;
#else
	    case snd_keypad_8: ss->spectro_zscl+=.01; redisplay = CURSOR_UPDATE_DISPLAY; reflect_spectro(ss); break;
	    case snd_keypad_2: ss->spectro_zscl-=.01; redisplay = CURSOR_UPDATE_DISPLAY; reflect_spectro(ss); break;
	    case snd_keypad_4: ss->spectro_zangle-=1.0; redisplay = CURSOR_UPDATE_DISPLAY; reflect_spectro(ss); break;
	    case snd_keypad_6: ss->spectro_zangle+=1.0; redisplay = CURSOR_UPDATE_DISPLAY; reflect_spectro(ss); break;
#endif
	    case snd_keypad_Add: if (ss->wavo) ss->wavo_trace++; else ss->spectro_hop++; redisplay = CURSOR_UPDATE_DISPLAY; reflect_spectro(ss); break;
	    case snd_keypad_Subtract: 
	      if (ss->wavo) {if (ss->wavo_trace>1) ss->wavo_trace--;} else {if (ss->spectro_hop>1) ss->spectro_hop--;}
	      redisplay = CURSOR_UPDATE_DISPLAY; reflect_spectro(ss); 
	      break;
	    case snd_keypad_Multiply: set_global_fft_size(ss,ss->global_fft_size * 2); redisplay = CURSOR_UPDATE_DISPLAY; break;
	    case snd_keypad_Divide: 
	      if (ss->global_fft_size > 4) set_global_fft_size(ss,ss->global_fft_size / 2); 
	      redisplay = CURSOR_UPDATE_DISPLAY; 
	      break;
#if !defined(NEXT) && !defined(UW2)
	    case snd_keypad_Delete: case snd_keypad_Decimal: set_dot_size(ss,ss->dot_size+1); redisplay = KEYBOARD_NO_ACTION; break;
	    case snd_keypad_Insert: case snd_keypad_0: if (ss->dot_size > 1) set_dot_size(ss,ss->dot_size-1); redisplay = KEYBOARD_NO_ACTION; break;
	    case snd_keypad_PageDown: case snd_keypad_3: 
	      set_sonogram_cutoff(ss,ss->sonogram_cutoff*.95); redisplay = CURSOR_UPDATE_DISPLAY; reflect_spectro(ss); break;
	    case snd_keypad_PageUp: case snd_keypad_9: 
	      if (ss->sonogram_cutoff < 1.0) set_sonogram_cutoff(ss,ss->sonogram_cutoff/.95); 
	      redisplay = CURSOR_UPDATE_DISPLAY; reflect_spectro(ss); 
	      break;
#else
	    case snd_keypad_Decimal: set_dot_size(ss,ss->dot_size+1); redisplay = KEYBOARD_NO_ACTION; break;
	    case_snd_keypad_0: if (ss->dot_size > 1) set_dot_size(ss,ss->dot_size-1); redisplay = KEYBOARD_NO_ACTION; break;
	    case snd_keypad_3: 
	      set_sonogram_cutoff(ss,ss->sonogram_cutoff*.95); redisplay = CURSOR_UPDATE_DISPLAY; reflect_spectro(ss); break;
	    case snd_keypad_9: 
	      if (ss->sonogram_cutoff < 1.0) set_sonogram_cutoff(ss,ss->sonogram_cutoff/.95); 
	      redisplay = CURSOR_UPDATE_DISPLAY; reflect_spectro(ss); 
	      break;
#endif
	    case snd_keypad_Enter: reset_spectro(ss); redisplay = CURSOR_UPDATE_DISPLAY; reflect_spectro(ss); break;
	    default: call_user_keymap(keysym,count,cp); break;
	    }
	}
      else /* extended mode with ctrl up -- where not an emacs analogy, related to the current selection */
	{
	  extended_mode = 0;
	  switch (keysym)
	    {
	    case snd_K_A: case snd_K_a: get_amp_expression(sp,ext_count,1); searching = 1; redisplay = CURSOR_IN_VIEW; break;
	    case snd_K_B: case snd_K_b: cp->cursor_on = 1; redisplay = CURSOR_ON_LEFT; break;
	    case snd_K_C: case snd_K_c: mark_define_region(cp,ext_count); redisplay = CURSOR_CLAIM_SELECTION; break;
	    case snd_K_D: case snd_K_d: prompt(sp,snd_string_temp_dir_p); sp->filing = TEMP_FILING; searching = 1; break;
	    case snd_K_E: case snd_K_e: 
	      execute_last_macro(cp,ext_count); 
	      redisplay = cursor_decision(cp);
	      if (redisplay == CURSOR_IN_VIEW) redisplay = CURSOR_UPDATE_DISPLAY; 
	      break;
	    case snd_K_F: case snd_K_f: cp->cursor_on = 1; redisplay = CURSOR_ON_RIGHT; break;
	    case snd_K_H: case snd_K_h: get_ufun(sp,ext_explicit_count,1); searching = 1; break;
	    case snd_K_I: case snd_K_i: paste_region(ext_explicit_count,cp); redisplay = CURSOR_UPDATE_DISPLAY; break;
	    case snd_K_J: case snd_K_j: prompt(sp,snd_string_mark_p); sp->finding_mark = 1; searching = 1; break;
	    case snd_K_K: case snd_K_k: snd_close_file(sp,ss); return(CURSOR_NO_ACTION); break;
	    case snd_K_L: case snd_K_l: 
	      cp->cursor_on = 1;
	      if ((selection_is_ours()) && (selection_member(sp)))
		cursor_moveto(cp,selection_beg(cp)+0.5*region_len(0));
	      redisplay = CURSOR_IN_MIDDLE;
	      break;
	    case snd_K_N: case snd_K_n: eval_expression(cp,sp,ext_explicit_count,1); break;
	    case snd_K_O: case snd_K_o: 
	      if (ext_count > 0) goto_next_graph(cp,ext_count); else goto_previous_graph(cp,ext_count); return(CURSOR_NO_ACTION); break;
	    case snd_K_P: case snd_K_p: play_region(ss,ext_explicit_count,NULL); return(KEYBOARD_NO_ACTION); break;
	    case snd_K_Q: case snd_K_q: add_region(ext_explicit_count,cp); redisplay = CURSOR_UPDATE_DISPLAY; break;
	      /* if count is float, it becomes the scaler on the added data */
	    case snd_K_R: case snd_K_r: redo_EDIT(cp,ext_count); redisplay = CURSOR_UPDATE_DISPLAY; break;
	    case snd_K_U: case snd_K_u: undo_EDIT(cp,ext_count); redisplay = CURSOR_UPDATE_DISPLAY; break;
	    case snd_K_V: case snd_K_v: window_frames_selection(cp); redisplay = CURSOR_UPDATE_DISPLAY; break;
	    case snd_K_W: case snd_K_w: 
	      region_count = ext_explicit_count;
	      prompt(sp,snd_string_file_p); 
	      sp->filing = REGION_FILING; 
	      searching = 1;
	      break;
	    case snd_K_X: case snd_K_x: get_eval_expression(sp,ext_count,1); searching = 1; redisplay = CURSOR_IN_VIEW; break;
	    case snd_K_Z: case snd_K_z: cos_smooth(cp,cp->cursor,ext_explicit_count,1); redisplay = CURSOR_UPDATE_DISPLAY; break;
	    case snd_K_Right: sx_incremented(cp,state_amount(state)); break;
	    case snd_K_Left:  sx_incremented(cp,-state_amount(state)); break;
	    case snd_K_Up: zx_incremented(cp,1.0+state_amount(state)); break;
	    case snd_K_Down: zx_incremented(cp,1.0/(1.0+state_amount(state))); break;
	    case snd_K_comma: if (state & snd_ShiftMask) {cp->cursor_on = 1; redisplay = cursor_moveto_beginning(cp);} break;
	    case snd_K_period: if (state & snd_ShiftMask) {cp->cursor_on = 1; redisplay = cursor_moveto_end(cp);} break;
	    case snd_K_9: 
	      if (state & snd_ShiftMask)
		{
		  if (defining_macro) 
		    report_in_minibuffer(sp,snd_string_macro_definition_already_in_progress);
		  else
		    {
		      start_defining_macro(); 
		      report_in_minibuffer(sp,snd_string_defining_kbd_macro); 
		    }
		  return(KEYBOARD_NO_ACTION); 
		}
	      break;
	    case snd_K_0: 
	      if ((state & snd_ShiftMask) && (defining_macro))
		{
		  stop_defining_macro(); 
		  clear_minibuffer(sp); 
		}
	      return(KEYBOARD_NO_ACTION);
	      break;
	    case snd_K_slash: 
	      cp->cursor_on = 1;
	      redisplay = add_named_mark(cp); 
	      show_marks(ss); 
	      searching = 1; 
	      break;
	    }
	}
    }
  if ((sp->minibuffer_on) && (!searching)) 
    clear_minibuffer(sp);
  else if (!cursor_searching) 
    sp->searching = 0;
  return(redisplay);
}
