#include "snd.h"

#ifndef VMS
#if defined(NEXT) || defined(HAVE_SYS_DIR_H)
  #include <sys/dir.h>
  #include <sys/dirent.h>
#else
  #ifdef WINDOZE
    #include <direct.h>
  #else
    #include <dirent.h>
  #endif
#endif
#else
#include "vms_dirent.h"
#endif

int set_dot_size(snd_state *ss, int val)
{
  if (val>0)  /* -1 here can crash X! */
    {
      in_set_dot_size(ss,val);
      if (graph_style(ss) == GRAPH_DOTS) map_over_chans(ss,update_graph,NULL);
    }
  return(val);
}

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]);
}

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;
  if (count == 0) return;
  sp=cp->sound;
  ss=cp->state;
  osp = sp;
  vcp = virtual_selected_channel(cp);
  chan = vcp->chan;
  ncp = NULL;
  if (count < 0) k = -count; else {goto_next_graph(cp,count); return;}
  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);
}

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;
  if (count == 0) return;
  sp=cp->sound;
  osp = sp;
  ss=cp->state;
  vcp = virtual_selected_channel(cp);
  chan = vcp->chan;
  ncp = NULL;
  if (count < 0) {goto_previous_graph(cp,count); return;}
  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 (ss->listening) {goto_listener(); k--; if (k == 0) return;} */
      /* not really right because C-x in listener already exits listener (so C-x O in one chan case bounces back to self) */
      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 lisp 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);
  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_SY_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 ((sp->nchans > 1) && (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 = ymax(ss);
  ymin = ymin(ss);
  xmax = xmax(ss);
  xmin = xmin(ss);
  x0 = initial_x0(ss);
  x1 = initial_x1(ss);
  y0 = initial_y0(ss);
  y1 = initial_y1(ss);
  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) && (fit_data_on_open(ss) != 0)) /* -1 = default: fit only 32 linear */
    {
      if ((fit_data_on_open(ss) == 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;
  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 */
  if (chdr)
    {
      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,ss);
#if LONG_INT_P
	  cp->sounds[0] = make_snd_data_file(filename,datai,delist_ptr(datai[io_dats+6+cp->chan]),chdr,0,cp->edit_ctr,cp->chan);
#else
	  cp->sounds[0] = make_snd_data_file(filename,datai,(int *)(datai[io_dats+6+cp->chan]),chdr,0,cp->edit_ctr,cp->chan);
#endif
	}
    }
}

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_SY_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);
}

static 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 low, float high)
{
  ap->y0 = low;
  ap->y1 = high;
  ap->ymin = low;
  ap->ymax = high;
  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;
}

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=0;i<sp->nchans;i++)
	{
	  ncp = sp->chans[i];
	  if (ncp != cp)
	    {
	      nap = ncp->axis;
	      if (nap)
		{
		  nap->zy = zy;
		  nap->sy = sy;
		  set_y_bounds(nap);
		  update_graph(ncp,NULL);
		}
	    }
	}
    }
}

void clm_x_axis (chan_info *cp, float x0, float x1) 
{
  axis_info *ap;
  ap = cp->axis;
  if (x0 >= 0.0) ap->x0 = x0; else ap->x0 = 0.0;
  ap->x1 = x1;
  if (x1 > ap->xmax) ap->xmax = x1;
  ap->zx = (x1-x0)/(ap->xmax-ap->xmin);
  ap->sx = (x0-ap->xmin)/(ap->xmax-ap->xmin);
  resize_sx(cp);
  resize_zx(cp);
  apply_x_axis_change(ap,cp,cp->sound); /* this checks sync */
}

void clm_left_axis(chan_info *cp, int left)
{
  float x1x0;
  axis_info *ap;
  if (cp)
    {
      ap = cp->axis; 
      if (ap) 
	{
	  x1x0 = ap->x1 - ap->x0; 
	  ap->x0 = (double)left / (double)snd_SRATE(cp); 
	  clm_x_axis(cp,ap->x0,ap->x0 + x1x0);
	}
    }
}

void clm_right_axis(chan_info *cp, int right)
{
  float x1x0;
  axis_info *ap;
  if (cp)
    {
      ap = cp->axis; 
      if (ap) 
	{
	  x1x0 = ap->x1 - ap->x0; 
	  ap->x1 = (double)right / (double)snd_SRATE(cp); 
	  clm_x_axis(cp,ap->x1 - x1x0,ap->x1);
	}
    }
}

void clm_y_axis (chan_info *cp, float y0, float y1) 
{
  axis_info *ap;
  ap = cp->axis;
  if (y0 < ap->ymin) ap->ymin = y0;
  if (y1 > ap->ymax) ap->ymax = y1;
  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);
  apply_y_axis_change(ap,cp);
}

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_SY_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 int visible_syncd_cursor(chan_info *cp)
{
  snd_info *sp;
  snd_state *ss;
  chan_info *ncp;
  int i,j;
  sp = cp->sound;
  if (sp->syncing)
    {
      for (i=0;i<sp->nchans;i++)
	{
	  ncp = sp->chans[i];
	  if (ncp->cursor_visible) return(ncp->cursor);
	}
      /* geez, maybe it's in a separate syncd sound */
      ss = cp->state;
      for (j=0;j<ss->max_sounds;j++)
	{
	  sp = ss->sounds[j];
	  if ((sp) && (sp->inuse) && (sp->syncing) && (sp != cp->sound))
	    {
	      for (i=0;i<sp->nchans;i++)
		{
		  ncp = sp->chans[i];
		  if (ncp->cursor_visible) return(ncp->cursor);
		}
	    }
	}
    }
  return(-1);
}

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
	    {
	      /* perhaps user has syncd chans (sounds), has cursor in only one, is zooming using some other channel's (sound's) slider */
	      newf = visible_syncd_cursor(cp);
	      if (newf == -1)
		{
		  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 */
  if (ap->sy < 0) ap->sy = 0;
  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,zoom_focus_style(ss));
  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 (wavo(ss)) {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.
	   * lisp 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,ap);
      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,ap);
	  if (cp->printing) ps_draw_grf_points(ss,cp,ap,j);
	}
      else
	{
	  /* take min, max */
	  if ((subsampling(ss)) && (amp_env_usable(cp,samples_per_pixel))) 
	    j = amp_env_graph(cp,ap,samples_per_pixel,snd_SRATE(cp));
	  else
	    {
	      if ((subsampling(ss)) && (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 (fft_log_frequency(ss)) 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 (fft_log_magnitude(ss)) py = dB(py);
  return(grf_y(py,fap));
}

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

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

static char ampstr[8];
#define AMP_ROOM 35
#define AMP_ROOM_CUTOFF 3.0

#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)
{
  snd_state *ss;
  int num_peaks,row,col,tens,i,with_amps;
  float amp0,px;
  char *fstr;
  ss = cp->state;
  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;
  with_amps = (fap->width > ((30+5*tens+AMP_ROOM)*AMP_ROOM_CUTOFF));
  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; 
      if (with_amps) col -= AMP_ROOM;
      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;
	      if (with_amps)
		{
		  if (fft_log_magnitude(ss))
		    sprintf(ampstr,"%.1f",dB(peak_freqs[i].amp));
		  else sprintf(ampstr,"%.3f",peak_freqs[i].amp);
		  draw_string(copy_context(cp),fap->x_axis_x1-AMP_ROOM+15,row,ampstr,strlen(ampstr));
		  if (cp->printing) ps_draw_string(cp,fap->x_axis_x1-AMP_ROOM+15,row,ampstr);
		}
	    }
	  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;
  if (with_amps) col -= AMP_ROOM;
  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;
	  if (with_amps)
	    {
	      if (fft_log_magnitude(ss))
		sprintf(ampstr,"%.1f",dB(peak_freqs[i].amp));
	      else sprintf(ampstr,"%.3f",peak_freqs[i].amp);
	      draw_string(copy_context(cp),fap->x_axis_x1 - AMP_ROOM + 15,row,ampstr,strlen(ampstr));
	      if (cp->printing) ps_draw_string(cp,fap->x_axis_x1 - AMP_ROOM + 15,row,ampstr);
	    }
	}
      row+=15;
    }
}

static float 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(0.0);
  fap = fp->axis;
  if (!fap->graph_active) return(0.0);
  data = fp->data;
  if (transform_type(ss) == FOURIER)
    {
      samps = fp->current_size*spectro_cutoff(ss)/2;
      incr = (float)snd_SRATE(sp)/(float)(fp->current_size);
    }
  else
    {
      samps = fp->current_size * spectro_cutoff(ss);
      incr = 1.0;
    }
  data_max = 0.0;
  if ((!(normalize_fft(ss))) && (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 (transform_type(ss) == 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 (normalize_fft(ss))
    scale = 1.0/data_max;
  else 
    {
      if (transform_type(ss) == 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;
    }
  fp->scale = scale;
  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 ((!fft_log_magnitude(ss)) && (!fft_log_frequency(ss)))
	{
	  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 (fft_log_frequency(ss)) 
	    {
	      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,fap);
      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 (fft_log_frequency(ss)) 
	{
	  ymax = LOG_FACTOR;
	  incr = ymax/(float)samps;
	  scaler = 1.0/log(ymax+1.0);
	}
      else scaler = 0.0;
      ymax=-1.0;
      if ((!fft_log_magnitude(ss)) && (!fft_log_frequency(ss)))
	{
	  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,fap);
      if (cp->printing) ps_draw_grf_points(ss,cp,fap,j);
    }
  if (show_fft_peaks(ss)) 
    {
      if (transform_type(ss) == FOURIER)
	display_peaks(cp,fap,data,snd_SRATE(sp)*spectro_cutoff(ss)/2,samps,samples_per_pixel,1);
      else display_peaks(cp,fap,data,fp->current_size*spectro_cutoff(ss),samps,samples_per_pixel,1);
    }
  return(scale);
}


void display_fft_peaks(chan_info *cp)
{
  /* put unsorted peak info in help window */
  char *buffer,*str;
  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((num_peaks+1) * 32,sizeof(char));
  str = (char *)calloc(32,sizeof(char));
#if HAVE_XmHTML
  sprintf(buffer,"<pre>\n");
#endif
  for (i=0;i<num_peaks;i++)
    {
      sprintf(str,"%.*f  %.3f\n",tens,peak_freqs[i].freq * srate2,peak_freqs[i].amp);      
      strcat(buffer,str);
    }
#if HAVE_XmHTML
  sprintf(str,"</pre>\n");
  strcat(buffer,str);
#endif
  snd_help((snd_state *)(cp->state),"fft peaks",buffer);
  free(buffer);
  free(str);
}

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 float make_sonogram(chan_info *cp, snd_info *sp, snd_state *ss)
{ /* colormap allocated in snd-fft via allocate_sono_rects in snd-xchn */
  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,xscl,scl;
  float *hfdata;
  int *hidata;
  if (chan_fft_in_progress(cp)) return(0.0);
  si = (sono_info *)(cp->sonogram_data);
  bins = si->target_bins * spectro_cutoff(ss);
  allocate_grf_points();
  if (cp->printing) ps_allocate_grf_points();
  if ((si) && (si->scale > 0.0))
    {
      if (fft_log_frequency(ss)) 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;
      hfdata = (float *)calloc(bins+1,sizeof(float));
      hidata = (int *)calloc(bins+1,sizeof(int));
      if (transform_type(ss) == FOURIER)
	{
	  if (fft_log_frequency(ss))
	      yfincr = (spectro_cutoff(ss) * LOG_FACTOR)/(float)bins;
	  else yfincr = spectro_cutoff(ss) * (float)snd_SRATE(cp) * 0.5 / (float)bins;
	}
      else yfincr = 1.0;
      if (fft_log_frequency(ss))
	{
	  for (yf=0.0,i=0;i<=bins;i++,yf+=yfincr)
	    {
	      hfdata[i] = log(yf+1.0) * scaler;
	      hidata[i] = grf_y(hfdata[i],fap);
	    }
	}
      else
	{
	  for (yf=0.0,i=0;i<=bins;i++,yf+=yfincr)
	    {
	      hfdata[i] = yf;
	      hidata[i] = grf_y(yf,fap);
	    }
	}
      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 (i=0;i<bins;i++)
	    {
	      /* above is fdata[i-1], left is si->data[slice-1][i] */
	      binval = fdata[i]/scl;
	      if (fft_log_magnitude(ss)) binval = 1.0 + (dB(binval))/60.0;
	      if (binval >= color_cutoff(ss))
		{
		  if (color_inverted(ss)) 
		    j = skew_color((1.0 - binval),color_scale(ss))*GRAY_SCALES; 
		  else j = skew_color(binval,color_scale(ss))*GRAY_SCALES;
		  if (j>0) j--;
		  if (fft_log_frequency(ss))
		    set_sono_rectangle(js[j],j,xf,hidata[i+1],rectw,hidata[i]-hidata[i+1]);
		  else set_sono_rectangle(js[j],j,xf,hidata[i+1],rectw,recth);
		  if (cp->printing)
		    {
		      if (fft_log_frequency(ss)) 
			ps_draw_sono_rectangle(cp,fap,j,fap->x0 + xscl*slice,hfdata[i+1],frectw,hfdata[i]-hfdata[i+1]);
		      else ps_draw_sono_rectangle(cp,fap,j,fap->x0 + xscl*slice,hfdata[i+1],frectw,frecth);
		    }
		  js[j]++;
		}
	    }
	  for (i=0;i<GRAY_SCALES;i++)
	    {
	      if (js[i] > 0) draw_sono_rectangles(cp,i,js[i]);
	    }
	  if (cp->printing)
	    {
	      check_for_event(ss);
	      if (ss->stopped_explicitly)
		{
		  ss->stopped_explicitly = 0;
		  report_in_minibuffer(sp,"stopped");
		  break;
		}
	    }
	}
      reset_color(cp);
      if (cp->printing) ps_reset_color(cp);
      free(hfdata);
      free(hidata);
    }
  return(1.0/scl);
}

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)
{
  set_spectro_cutoff(state,1.0);
  set_spectro_hop(state,4);
  set_spectro_x_scale(state,1.0);
  set_spectro_y_scale(state,1.0);
  set_spectro_z_scale(state,0.1);
  set_spectro_z_angle(state,-2.0);
  set_spectro_x_angle(state,90.0);
  set_spectro_y_angle(state,0.0);
  set_spectro_color(state,-1);
  set_color_cutoff(state,.003);
  set_color_scale(state,1.0);
  set_color_inverted(state,1);
  set_wavo_hop(state,3);
  set_wavo_trace(state,64);
}

static float 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(0.0);
  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 * spectro_cutoff(ss);
      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 (!(fft_log_magnitude(ss)))
	zscl = -(spectro_z_scale(ss)*fheight/scl);
      else zscl = -(spectro_z_scale(ss)*fheight);
      rotate_matrix(spectro_x_angle(ss),spectro_y_angle(ss),spectro_z_angle(ss),
		    spectro_x_scale(ss),spectro_y_scale(ss),zscl,
		    matrix);
      if (spectro_color(ss) == -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 (!(fft_log_magnitude(ss))) 
		    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,fap);
	      if (cp->printing) 
		{
		  ps_draw_grf_points(ss,cp,fap,bins);
		  check_for_event(ss);
		  if (ss->stopped_explicitly)
		    {
		      ss->stopped_explicitly = 0;
		      report_in_minibuffer(sp,"stopped");
		      break;
		    }
		}
	    }
	}
      else
	{
	  /* spectrogram in various colors */
	  allocate_color_map(ss,spectro_color(ss));
	  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 (!(fft_log_magnitude(ss))) 		  
		    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 >= color_cutoff(ss))
		    {
		      if (color_inverted(ss)) 
			j = skew_color((1.0 - binval),color_scale(ss))*GRAY_SCALES; 
		      else j = skew_color(binval,color_scale(ss))*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;
		}
	      if (cp->printing) 
		{
		  check_for_event(ss);
		  if (ss->stopped_explicitly)
		    {
		      ss->stopped_explicitly = 0;
		      report_in_minibuffer(sp,"stopped");
		      break;
		    }
		}
	    }
	  reset_color(cp);
	  if (cp->printing) ps_reset_color(cp);
	}
    }
  return(1.0/scl);
}

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)wavo_trace(ss);
  yincr = -(wavo_hop(ss));
  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 = -(spectro_z_scale(ss)*height);
  rotate_matrix(spectro_x_angle(ss),spectro_y_angle(ss),spectro_z_angle(ss),
		spectro_x_scale(ss),spectro_y_scale(ss),zscl,
		matrix);
  if (spectro_color(ss) == -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<wavo_trace(ss);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,wavo_trace(ss),ap);
	  if (cp->printing) ps_draw_grf_points(ss,cp,ap,wavo_trace(ss));
	}
    }
  else
    {
      allocate_color_map(ss,spectro_color(ss));
      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<wavo_trace(ss);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 >= color_cutoff(ss)) && (xx != -1))
		{
		  if (color_inverted(ss)) 
		    j = skew_color((1.0 - binval),color_scale(ss))*GRAY_SCALES; 
		  else j = skew_color(binval,color_scale(ss))*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_lisp_graph(chan_info *cp, snd_info *sp, snd_state *ss)
{
  lisp_grf *up;
  axis_info *uap = NULL;
  int i,j,grf_len;
  float x,y,samples_per_pixel,xinc,start_x,xf,ymin,ymax,xi,pinc;
  up = (lisp_grf *)(cp->lisp_info);
  if (up) uap = up->axis; else return;
  if ((!uap) || (!uap->graph_active) || (up->length <= 0)) 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;
      start_x = uap->x0;
      if (grf_len <= 1) 
	xinc = 1.0;
      else xinc = (uap->x1 - uap->x0) / (float)(grf_len-1);
      for (i=0,x=start_x;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,uap);
      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 ((show_fft_peaks(ss)) && (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_lisp,displays,points;
  float fft_scale;
  axis_info *ap = NULL;
  axis_info *fap = NULL;
  axis_info *uap = NULL;
  fft_info *fp = NULL;
  lisp_grf *up = NULL;
  if ((cp->hookable) && (dont_graph(ss,cp))) return;
  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->lisp_graphing) && (up = (lisp_grf *)(cp->lisp_info)))
    {
      with_lisp = 1;
      displays++;
      uap = up->axis;
      uap->height = height;
      uap->y_offset = offset;
      uap->window_width = width;
    }
  else with_lisp = 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_lisp) 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_lisp) uap->graph_x0 = uap->width*(displays - 1);

  if ((!with_fft) && (!(cp->waving)) && (!(with_lisp)))
    clear_window(ap->ax);
  else 
    {
      if (sp->combining != CHANNELS_SUPERIMPOSED)
	cp->clear = 1; /* deferred clear (can be wave, fft, lisp 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 (wavo(ss))
	{
	  if (ap->y_axis_y0 == ap->y_axis_y1) make_axes(cp,ap,x_axis_style(ss)); /* first time needs setup */
	  ap->y0 = ap->x0;
	  ap->y1 = ap->y0+(float)(wavo_trace(ss) * (ap->y_axis_y0 - ap->y_axis_y1)) / ((float)(wavo_hop(ss))*snd_SRATE(sp));
	  ap->x1 = ap->x0 + (float)(wavo_trace(ss))/(float)snd_SRATE(sp);
	}
      make_axes(cp,ap,x_axis_style(ss));
      cp->cursor_visible = 0;
      points = make_graph(cp,sp,ss);
      if ((show_mix_consoles(ss)) && (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 (fft_style(ss) == SPECTROGRAM) cp->drawing = 0;
      make_axes(cp,fap,(x_axis_style(ss) == X_IN_SAMPLES) ? X_IN_SECONDS : (x_axis_style(ss)));
      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 (fft_style(ss))
	{
	case NORMAL_FFT: fft_scale = make_fft_graph(cp,sp,ss); break;
	case SONOGRAM: fft_scale = make_sonogram(cp,sp,ss); break;
	case SPECTROGRAM: fft_scale = make_spectrogram(cp,sp,ss); break;
	default: snd_printf(ss,"unknown fft style"); break;
	}
      if (cp->hookable) points = after_fft(ss,cp,fft_scale); /* dummy assignment -- no return value here */
    }
  if (with_lisp)
    {
      make_axes(cp,uap,X_IN_LENGTH);
      if ((!(cp->waving)) && (!(with_fft)))
	{
	  ap->losamp = ap->x0*(double)snd_SRATE(sp);
	  ap->hisamp = ap->x1*(double)snd_SRATE(sp);
	}
      make_lisp_graph(cp,sp,ss);
    }
  if (cp->waving)
    {
      display_selection(cp);
      if ((cp->marks) && (show_marks(ss))) display_channel_marks(cp);
      if (cp == selected_channel(ss)) display_graph_border(cp);
      if (show_y_zero(ss)) display_zero(cp);
      if ((show_mix_consoles(ss)) && (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;
  len = current_ed_samples(cp);
  if (cp->cursor < 0) cp->cursor = 0;
  if (cp->cursor > len) cp->cursor = len;
  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);
}

void handle_cursor_with_sync(chan_info *cp,int decision)
{
  snd_info *sp;
  sync_info *si;
  int i;
  sp = cp->sound;
  if ((sp) && (sp->syncing))
    {
      si = snd_sync(cp->state);
      for (i=0;i<si->chans;i++)
	handle_cursor(si->cps[i],decision);
      free_sync_info(si);
    }
  else handle_cursor(cp,decision);
}

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));
}

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);
}

void clear_minibuffer_prompt(snd_info *sp)
{
  make_button_label(snd_widget(sp,W_snd_info_label),"     ");
}

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;}

void show_cursor_info(chan_info *cp)
{
  snd_info *sp;
  chan_info *ncp;
  float y,absy;
  int digits,i,samp;
  char *s1,*s2;
  sp = cp->sound;
  if ((sp->syncing) && (cp->chan != 0)) return;
  samp = cp->cursor;
  y = sample(samp,cp);
  absy = fabs(y);
  if (absy < .0001) digits=4;
  else if (absy<.001) digits=3;
  else digits=2;
  if (sp->nchans == 1)
    sprintf(expr_str,snd_string_cursor_at_sample,
	    s1 = prettyf((double)samp/(double)snd_SRATE(cp),2),
	    samp,
	    s2 = prettyf(y,digits));
  else
    {
      if (!(sp->syncing))
	sprintf(expr_str,snd_string_chan_cursor_at_sample,
		cp->chan + 1,
		s1 = prettyf((double)samp/(double)snd_SRATE(cp),2),
		samp,
		s2 = prettyf(y,digits));
      else
	{
	  /* in this case, assume we show all on chan 0 and ignore the call otherwise (see above) */
	  /* "cursor at..." then list of values */
	  sprintf(expr_str,snd_string_cursor_at_sample_p,
		  s1 = prettyf((double)samp/(double)snd_SRATE(cp),2),
		  samp,
		  s2 = prettyf(y,digits));
	  for (i=1;i<sp->nchans;i++)
	    {
	      strcat(expr_str,", ");
	      free(s2);
	      ncp = sp->chans[i];
	      y = sample(samp,ncp);
	      absy = fabs(y);
	      if (absy < .0001) digits=4;
	      else if (absy<.001) digits=3;
	      else digits=2;
	      s2 = prettyf(y,digits);
	      strcat(expr_str,s2);
	    }
	}
    }
  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)
{
  clear_minibuffer_prompt(sp);
  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->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))
    {
      if (verbose_cursor(cp->state)) {show_cursor_info(cp); (cp->sound)->minibuffer_on = 0;} 
      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 ((!regexpr) && (sp->syncing))
    {
      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) && (region_ok(0)))
	{
	  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);
}

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


/* amplitude scaling */

static void scale_with(snd_state *ss, sync_state *sc, float *scalers, char *origin)
{
  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 *)snd_calloc(ss,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 = snd_tempnam(temp_dir(ss),"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,origin);
	  if ((temp_file) && (ofile)) {free(ofile); ofile=NULL;}
	}
      else change_samples(si->begs[i],dur,data[0],cp,LOCK_MIXES,origin);
      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 *ur_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,nlen;
  float *scalers = NULL;
  sc = get_sync_state(ss,sp,cp,0,selection);
  si = sc->si;
  chans = si->chans;
  scalers = (float *)calloc(chans,sizeof(float));
  if (chans<len) nlen=chans; else nlen=len;
  for (i=0;i<nlen;i++) scalers[i] = ur_scalers[i];
  if (chans > len)
    for (i=len;i<chans;i++) scalers[i] = ur_scalers[len-1];
  scale_with(ss,sc,scalers,"(scale-by)");
  free(scalers);
  free(sc);
}

float get_maxamp(snd_state *ss, snd_info *sp, chan_info *cp)
{
  snd_fd *sf;
  int ymax,i,val;
  if (!sp) return(0.0);
  if (!cp) cp = sp->chans[0];
  if ((subsampling(ss)) && (amp_env_maxamp_ok(cp))) return(amp_env_maxamp(cp));
  sf = init_sample_read(0,cp,READ_FORWARD);
  ymax = 0;
  for (i=0;i<current_ed_samples(cp);i++)
    {
      NEXT_SAMPLE(val,sf);
      if (val < 0) val = -val;
      if (val > ymax) ymax = val;
    }
  free_snd_fd(sf);
  return(ymax*clm_sndflt);
}

void scale_to(snd_state *ss, snd_info *sp, chan_info *cp, float *ur_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,nlen;
  sync_state *sc;
  sync_info *si;
  chan_info *ncp;
  float *scalers;
  float maxamp,val;
  sc = get_sync_state(ss,sp,cp,0,selection);
  si = sc->si;
  chans = si->chans;
  scalers = (float *)calloc(chans,sizeof(float));
  if (chans<len) nlen=chans; else nlen=len;
  for (i=0;i<nlen;i++) scalers[i] = ur_scalers[i];
  if (chans > len)
    for (i=len;i<chans;i++) scalers[i] = ur_scalers[len-1];
  /* 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,"(scale-to)");
  free(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 ((sinc_width(ss) != current_sinc_width) && (sinc_width(ss) > 0))
    {
      if (sinc_table) {free(sinc_table); sinc_table = NULL;}
      current_sinc_width = sinc_width(ss);
      size = current_sinc_width * SRC_SINC_DENSITY;
      sinc_table = (float *)snd_calloc(ss,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 = sinc_width(ss);
  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);
}

#if 0
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;
  int err = 0;
  env *e = NULL;
  int just_num;
  char *str;
  float ratio = 0.0;
  /* get envelope or src ratio */
  if (env_or_num == NULL) return;
  sp = any_selected_sound(ss); 
  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;
    }
  src_env_or_num(ss,e,ratio,just_num,FALSE,NULL);
}
#endif

void src_env_or_num(snd_state *ss, env *e, float ratio, int just_num, int from_enved, char *origin)
{
  snd_info *sp = NULL;
  chan_info *cp = NULL;
  int reporting = 0;
  sync_state *sc;
  sync_info *si;
  snd_fd **sfs;
  snd_fd *sf;
  int 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,xoffset,xscaler,logbase,step_val;
  int ef_ctr,pass,next_pass,diff,stop_point,need_step,need_exponential;

  if ((!just_num) && (e == NULL)) return;

  /* get envelope or src ratio */
  sp = any_selected_sound(ss); 
  if (sp) cp = any_selected_channel(sp); 

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

  if (from_enved)
    {
      expr_str[0]='\0';
      display_enved_progress(ss,expr_str);
    }
  if (!just_num)
    {
      need_step = (e->base <= 0.0);
      need_exponential = ((!need_step) && (e->base != 1.0));
      if (need_exponential)
	{
	  xoffset = 0.0;
	  xscaler = 1.0;
	  logbase = log(e->base);
	  ef = fixup_exp_env(e,&xoffset,&xscaler,e->base);
	  if (ef == NULL) return;
	  e = make_envelope(ef,e->pts*2);
	  free(ef);
	}
    }
  
  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 = snd_tempnam(temp_dir(ss),"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];
	      step_val = env_val;
	      env_incr = ef[1];
	      pass = ef[0];
	      next_pass = sr->sample;
	      for (k=0;sr->sample<dur;k++)
		{
		  if (need_step)
		    idata[j] = (int)run_src(sr,step_val);
		  else
		    if (need_exponential)
		      idata[j] = (int)run_src(sr,xoffset + xscaler * (exp(logbase*env_val) - 1.0));
		    else 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];
			  step_val = env_val;
			  /* 1 pass segments are special in this case */
			  if (pass == 1)
			    {
			      env_val += env_incr;
			      ef_ctr += 2;
			      pass = ef[ef_ctr];
			      env_incr = ef[ef_ctr+1];
			      step_val = env_val;
			    }
			}
		    }
		}
	      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,origin);
	  /* 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, char *origin)
{
  /* interpret e as frequency response and apply as filter to all sync'd chans */
  float *fdata,*a,*d,*ef;
  float x;
  sync_state *sc;
  sync_info *si;
  snd_state *ss;
  snd_info *sp;
  int reporting = 0;
  int i,m,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,cur_pt,ef_ctr,pass;
  int **data;
  int *idata;
  char *ofile = NULL;
  chan_info *cp;
  float last_x,step,xoffset,xscaler,logbase,env_pow,env_incr;

  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);
  if (e->base == 1.0)
    {
      for (i=0,x=0.0;i<order;i++,x+=step) 
	fdata[i] = list_interp(x,e->data,e->pts);
    }
  else
    {
      if (e->base == 0.0)
	{
	  cur_pt = 2;
	  for (i=0,x=0.0;i<order;i++,x+=step) 
	    {
	      while ((cur_pt < (e->pts*2)) && (x > e->data[cur_pt])) cur_pt+=2;
	      fdata[i] = e->data[cur_pt - 1];
	    }
	}
      else
	{
	  /* exponential segments */
	  xoffset = 0.0;
	  xscaler = 1.0;
	  logbase = log(e->base);
	  ef = fixup_exp_env(e,&xoffset,&xscaler,e->base);
	  if (ef == NULL) return;
	  e = make_envelope(ef,e->pts*2);
	  free(ef);
	  ef = magify_env(e,order,1.0);
	  env_pow = e->data[1];
	  env_incr = ef[1];
	  pass = ef[0];
	  ef_ctr = 0;
	  for (i=0;i<order;i++)
	    {
	      fdata[i] = xoffset + xscaler * (exp(logbase * env_pow) - 1.0);
	      env_pow += env_incr;
	      pass--;
	      if (pass <= 0) 
		{
		  ef_ctr += 2;
		  pass = ef[ef_ctr];
		  env_incr = ef[ef_ctr+1];
		}
	    }
	  free(ef);
	  free(e);
	}
    }
  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;
  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 *)snd_calloc(ss,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 = snd_tempnam(temp_dir(ss),"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,origin);
	      if ((temp_file) && (ofile)) {free(ofile); ofile=NULL;}
	    }
	  else change_samples(si->begs[i],dur,data[0],cp,LOCK_MIXES,origin);
	  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, char *origin)
{
  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,step_val;
  double env_vald,env_incrd;
  int need_doubles,need_exponential,need_step;
  char *ofile = NULL;
  snd_state *ss;
  float xoffset,xscaler,logbase,env_powd;

  if (!e) return;
  need_step = (e->base <= 0.0);
  need_exponential = ((!need_step) && (e->base != 1.0) && (fabs(e->base-1.0) > 0.00001));
  need_doubles = (((dur > 5000000) && (!need_step)) || (need_exponential));
  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 = snd_tempnam(temp_dir(ss),"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_exponential)
    {
      xoffset = 0.0;
      xscaler = 1.0;
      logbase = log(e->base);
      ef = fixup_exp_env(e,&xoffset,&xscaler,e->base);
      if (ef == NULL) 
	need_exponential = 0;
      else 
	{
	  e = make_envelope(ef,e->pts*2);
	  scaler = 1.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 *)snd_calloc(ss,FILE_BUFFER_SIZE,sizeof(int)); 
      else data[i] = (int *)snd_calloc(ss,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_exponential)
    {
      if (!need_step)
	{
	  /* LINEAR SEGMENTS */
	  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];
	      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];
			}
		      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;
			    }
			}
		    }
		}
	    }
	}
      else
	{
	  /* STEP SEGMENTS -- I doubt this is actually ever useful */
	  env_val = e->data[1];
	  step_val = env_val;
	  env_incr = ef[1];
	  pass = ef[0];
	  for (i=0;i<dur;i++)
	    {
	      for (k=0;k<si->chans;k++)
		{
		  NEXT_SAMPLE(val,sfs[k]);
		  data[k][j] = val*step_val;
		}
	      env_val += env_incr;
	      pass--;
	      if (pass <= 0) 
		{
		  ef_ctr += 2;
		  pass = ef[ef_ctr];
		  env_incr = ef[ef_ctr+1];
		  step_val = env_val;
		}
	      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
    {
      /* EXPONENTIAL SEGMENTS */
      env_powd = e->data[1];
      env_incrd = efd[1];
      pass = efd[0];
      for (i=0;i<dur;i++)
	{
	  if (env_incrd != 0.0) 
	    {
	      env_vald = xoffset + xscaler * (exp(logbase * env_powd) - 1.0);
	      env_powd += env_incrd;
	    }
	  for (k=0;k<si->chans;k++)
	    {
	      NEXT_SAMPLE(val,sfs[k]);
	      data[k][j] = val*env_vald;
	    }
	  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;
		}
	    }
	}
    }
  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,origin);
	  else change_samples(si->begs[i],dur,data[i],si->cps[i],LOCK_MIXES,origin);
	  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 snd_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,char *origin)
{
  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],origin); 
	  else delete_samples(beg+count,-count,cps[i],origin);
	  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,origin);
      else delete_samples(beg+count,-count,cp,origin);
      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 *)snd_calloc(cp->state,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],"C-o");
	  check_for_first_edit(cps[i]);
	  update_graph(si->cps[i],NULL);
	}
      free_sync_info(si);
    }
  else
    {
      insert_samples(beg,count,zeros,cp,"C-o");
      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) && (region_ok(0)))
	{
	  si = region_sync(0);
	  num = region_len(0);
	}
    }
  if (!si) si = make_simple_sync(cp,cp->cursor);
  zeros = (int *)snd_calloc(cp->state,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,"C-z"); 
      else change_samples(si->begs[i],num,zeros,si->cps[i],LOCK_MIXES,"C-z");
      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 */
  int *data = NULL;
  chan_info *ncp;
  sync_state *sc;
  int i,k;
  float y0,y1,angle,incr,off,scale;
  snd_info *sp;
  sync_info *si;
  sp = cp->sound;
  sc = get_simple_sync_state(cp->state,sp,cp,beg,regexpr);
  si = sc->si;
  if (regexpr) num = sc->dur;
  for (i=0;i<si->chans;i++)
    {
      ncp = si->cps[i];
      y0 = sample(si->begs[i],ncp);
      y1 = sample(si->begs[i]+num,ncp);
      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 *)snd_calloc(cp->state,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,ncp,LOCK_MIXES,"C-x C-z");
      check_for_first_edit(ncp);
      update_graph(ncp,NULL);
      free(data);
    }
  free_sync_info(si);
  free(sc);
}

/* 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 **)snd_calloc(ss,dur,sizeof(assign_info *));
	      /* TODO: what if this is too large? (dur*assign_info_size > mem+swap) */
	      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 *)snd_calloc(ss,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,"C-x C-x");
	      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(snd_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;
    }
}

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

void save_macro_state (snd_state *ss, FILE *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 */
  FILE *fd;
  fd = open_snd_init_file(ss);
  save_macro_state(ss,fd);
  fclose(fd);
}

static int execute_named_macro_1(chan_info *cp, char *name, int count)
{
  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,FALSE);
		    }
		}
	    }
	  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...*/
    clm_doit(cp->state,name,FALSE);
}

typedef struct {char *code; int key; int state;} key_entry;
static key_entry *user_keymap = NULL;
static int keymap_size = 0;
static int keymap_top = 0;

static int in_user_keymap(int key, int state)
{
  int i;
  if (keymap_top == 0) return(-1);
  for (i=0;i<keymap_top;i++)
    {
      if ((user_keymap[i].key == key) && (user_keymap[i].state == state) && (user_keymap[i].code)) return(i);
    }
  return(-1);
}

void set_keymap_entry(int key, int state, char *val)
{
  int i;
  i = in_user_keymap(key,state);
  if (i == -1)
    {
      if (keymap_size == keymap_top)
	{
	  keymap_size += 16;
	  if (keymap_top == 0)
	    user_keymap = (key_entry *)calloc(keymap_size,sizeof(key_entry));
	  else 
	    {
	      user_keymap = (key_entry *)realloc(user_keymap,keymap_size * sizeof(key_entry));
	      for (i=keymap_top;i<keymap_size;i++) {user_keymap[i].code=0; user_keymap[i].state=0; user_keymap[i].code=NULL;}
	    }
	}
      user_keymap[keymap_top].key = key;
      user_keymap[keymap_top].state = state;
      user_keymap[keymap_top].code = copy_string(val);
      keymap_top++;
    }
  else
    {
      if (user_keymap[i].code) free(user_keymap[i].code);
      user_keymap[i].code = copy_string(val);
    }
}

static int call_user_keymap(int hashedsym, int count, chan_info *cp)
{
  int i,res = 0;
  /* if guile call the associated scheme code, else see if basic string parser can handle it */
#if HAVE_GUILE
  for (i=0;i<count;i++) res = handle_keymap(cp,user_keymap[hashedsym].code);
#else
  for (i=0;i<count;i++) clm_doit(cp->state,user_keymap[hashedsym].code,FALSE);
#endif
  return(res);
}

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

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,*newdir,*str1;
  env *e;
  mark *m;
  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_with_sync(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_with_sync(active_chan,goto_named_mark(active_chan,str));
	  sp->finding_mark = 0;
	}
      return;
    }
  if (snd_strlen(str) != 0)
    {
      if (sp->printing)
	{
	  snd_print(ss,str,(sp->printing != 1));
	  sp->printing = 0;
	  clear_minibuffer(sp);
	  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);
		  set_temp_dir(ss,newdir);
		}
	      else 
		{
		  tok = dir_from_tempnam(temp_dir(ss));
		  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,"C-x C-q");
	      break;
	    case INSERT_FILING:
	      str1 = complete_filename(str);
	      nc = sound_chans(str1);
	      if (nc != -1)
		{
		  len = sound_samples(str1)/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,"C-x C-i");
		      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,(sp->reging) ? "C-x a" : "C-x C-a");
	      else apply_env(active_chan,e,0,current_ed_samples(active_chan),1.0,sp->reging,FALSE,(sp->reging) ? "C-x a" : "C-x C-a");
	    }
	  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);
	  /* if this is a close command from the current minibuffer, the sound may not exist when we return */
	  ss->mx_sp = NULL;
	  if (sp->state == NULL) return;
	  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_with_sync(active_chan,nc);
	  return;
	}
      if ((sp->evaling) || (clm_doit(sp->state,str,FALSE) == -1))
	{
	  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);
	      clear_minibuffer_prompt(sp);
	    }
	}
      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);
}

static void no_selection_error(snd_info *sp)
{
  report_in_minibuffer(sp,"no active selection");
}

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 */
  /* keysym has Shift taken into account already (see snd-xchn.c XKeycodeToKeysym, and same snd-xsnd.c) */
  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,hashloc,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;
  if (!cp) return(KEYBOARD_NO_ACTION);
  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); 
  /* this happens when the user presses Control or Shift etc prior to hitting the actual (modified) key */
  if (defining_macro) continue_macro(keysym,state);
  if (!m) count = 1; else m=0;
  if (u_count == 0) set_prefix_arg(ss,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;
      set_prefix_arg(ss,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);
    }

  hashloc = in_user_keymap(keysym,state);
  if (hashloc != -1) {return(call_user_keymap(hashloc,count,cp));}
  if (state & snd_ControlMask)
    {
      if (!extended_mode)
	{
	  switch (keysym)
	    {
	    case snd_K_A: case snd_K_a: 
	      cp->cursor_on = 1; 
	      loc = ap->x0*snd_SRATE(cp); 
	      if ((loc+1) == ap->losamp) loc=ap->losamp; /* handle dumb rounding problem */
	      cursor_moveto(cp,loc); 
	      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,"C-d"); break;
	    case snd_K_E: case snd_K_e:
	      cp->cursor_on = 1; 
	      loc = ap->x1*(double)snd_SRATE(cp);
	      if ((loc+1) == ap->hisamp) loc=ap->hisamp;
	      cursor_moveto(cp,loc); 
	      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; 
	      if (sp->playing) stop_playing(sp->playing);
	      /* this tries to break out of long filter/src computations (and perhaps others) */
	      break;
	    case snd_K_H: case snd_K_h: 
	      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*line_size(ss),"C-k"); 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;
		  set_show_marks(ss,1);
		  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*line_size(ss)); 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*line_size(ss)); 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("C-x C-w"); 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,"C-y"); 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_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: counting = 1; number_buffer[number_ctr]='.'; number_ctr++; dot_seen = 1; 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_minus: counting = 1; number_ctr = 1; number_buffer[0]='-'; break;
	    case snd_K_underscore: undo_EDIT(cp,count); redisplay = CURSOR_UPDATE_DISPLAY; break;
#if !defined(NEXT) && !defined(UW2)
	    case snd_keypad_Left: case snd_keypad_4: 
	      set_spectro_y_angle(ss,spectro_y_angle(ss)-1.0);
	      redisplay = CURSOR_UPDATE_DISPLAY; 
	      reflect_spectro(ss); 
	      break;
	    case snd_keypad_Right: case snd_keypad_6: 
	      set_spectro_y_angle(ss,spectro_y_angle(ss)+1.0);
	      redisplay = CURSOR_UPDATE_DISPLAY; 
	      reflect_spectro(ss); 
	      break;
	    case snd_keypad_Down: case snd_keypad_2: 
	      set_spectro_x_angle(ss,spectro_x_angle(ss)-1.0);
	      redisplay = CURSOR_UPDATE_DISPLAY; 
	      reflect_spectro(ss); 
	      break;
	    case snd_keypad_Up: case snd_keypad_8: 
	      set_spectro_x_angle(ss,spectro_x_angle(ss)+1.0);
	      redisplay = CURSOR_UPDATE_DISPLAY; 
	      reflect_spectro(ss); 
	      break;
#else
	    case snd_keypad_4: 
	      set_spectro_y_angle(ss,spectro_y_angle(ss)-1.0);
	      redisplay = CURSOR_UPDATE_DISPLAY; 
	      reflect_spectro(ss); 
	      break;
	    case snd_keypad_6: 
	      set_spectro_y_angle(ss,spectro_y_angle(ss)+1.0);
	      redisplay = CURSOR_UPDATE_DISPLAY; 
	      reflect_spectro(ss); 
	      break;
	    case snd_keypad_2:
	      set_spectro_x_angle(ss,spectro_x_angle(ss)-1.0);
	      redisplay = CURSOR_UPDATE_DISPLAY; 
	      reflect_spectro(ss); 
	      break;
	    case snd_keypad_8: 
	      set_spectro_x_angle(ss,spectro_x_angle(ss)+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: 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); 
	      set_show_marks(ss,1); 
	      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: 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 sent to listener if possible */
	  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: counting=1; number_buffer[number_ctr]='.'; number_ctr++; dot_seen = 1; 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_minus: 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: 
	      set_spectro_z_scale(ss,spectro_z_scale(ss)+.01);
	      redisplay = CURSOR_UPDATE_DISPLAY;
	      reflect_spectro(ss);
	      break;
	    case snd_keypad_Down: case snd_keypad_2: 
	      set_spectro_z_scale(ss,spectro_z_scale(ss)-.01);
	      redisplay = CURSOR_UPDATE_DISPLAY;
	      reflect_spectro(ss);
	      break;
	    case snd_keypad_Left: case snd_keypad_4: 
	      set_spectro_z_angle(ss,spectro_z_angle(ss)-1.0);
	      redisplay = CURSOR_UPDATE_DISPLAY;
	      reflect_spectro(ss);
	      break;
	    case snd_keypad_Right: case snd_keypad_6: 
	      set_spectro_z_angle(ss,spectro_z_angle(ss)+1.0);
	      redisplay = CURSOR_UPDATE_DISPLAY; 
	      reflect_spectro(ss); 
	      break;
#else
	    case snd_keypad_8: 
	      set_spectro_z_scale(ss,spectro_z_scale(ss)+.01);
	      redisplay = CURSOR_UPDATE_DISPLAY;
	      reflect_spectro(ss);
	      break;
	    case snd_keypad_2: 
	      set_spectro_z_scale(ss,spectro_z_scale(ss)-.01);
	      redisplay = CURSOR_UPDATE_DISPLAY; 
	      reflect_spectro(ss); 
	      break;
	    case snd_keypad_4: 
	      set_spectro_z_angle(ss,spectro_z_angle(ss)-1.0);
	      redisplay = CURSOR_UPDATE_DISPLAY;
	      reflect_spectro(ss);
	      break;
	    case snd_keypad_6: 
	      set_spectro_z_angle(ss,spectro_z_angle(ss)+1.0);
	      redisplay = CURSOR_UPDATE_DISPLAY;
	      reflect_spectro(ss); 
	      break;
#endif
	    case snd_keypad_Add:
	      if (wavo(ss)) 
		set_wavo_trace(ss,wavo_trace(ss)+1); 
	      else set_spectro_hop(ss,spectro_hop(ss)+1);
	      redisplay = CURSOR_UPDATE_DISPLAY; 
	      reflect_spectro(ss); 
	      break;
	    case snd_keypad_Subtract: 
	      if (wavo(ss)) 
		{
		  if (wavo_trace(ss)>1) 
		    set_wavo_trace(ss,wavo_trace(ss)-1);
		} 
	      else 
		{
		  if (spectro_hop(ss)>1) 
		    set_spectro_hop(ss,spectro_hop(ss)-1);
		}
	      redisplay = CURSOR_UPDATE_DISPLAY; 
	      reflect_spectro(ss); 
	      break;
	    case snd_keypad_Multiply: set_fft_size(ss,fft_size(ss) * 2); redisplay = CURSOR_UPDATE_DISPLAY; break;
	    case snd_keypad_Divide: 
	      if (fft_size(ss) > 4) set_fft_size(ss,fft_size(ss) / 2); 
	      redisplay = CURSOR_UPDATE_DISPLAY; 
	      break;
#if !defined(NEXT) && !defined(UW2)
	    case snd_keypad_Delete: case snd_keypad_Decimal: set_dot_size(ss,dot_size(ss)+1); redisplay = KEYBOARD_NO_ACTION; break;
	    case snd_keypad_Insert: case snd_keypad_0: if (dot_size(ss) > 1) set_dot_size(ss,dot_size(ss)-1); redisplay = KEYBOARD_NO_ACTION; break;
	    case snd_keypad_PageDown: case snd_keypad_3: 
	      set_spectro_cutoff(ss,spectro_cutoff(ss)*.95); redisplay = CURSOR_UPDATE_DISPLAY; reflect_spectro(ss); break;
	    case snd_keypad_PageUp: case snd_keypad_9: 
	      if (spectro_cutoff(ss) < 1.0) set_spectro_cutoff(ss,spectro_cutoff(ss)/.95); 
	      redisplay = CURSOR_UPDATE_DISPLAY; reflect_spectro(ss); 
	      break;
#else
	    case snd_keypad_Decimal: set_dot_size(ss,dot_size(ss)+1); redisplay = KEYBOARD_NO_ACTION; break;
	    case_snd_keypad_0: if (dot_size(ss) > 1) set_dot_size(ss,dot_size(ss)-1); redisplay = KEYBOARD_NO_ACTION; break;
	    case snd_keypad_3: 
	      set_spectro_cutoff(ss,spectro_cutoff(ss)*.95); redisplay = CURSOR_UPDATE_DISPLAY; reflect_spectro(ss); break;
	    case snd_keypad_9: 
	      if (spectro_cutoff(ss) < 1.0) set_spectro_cutoff(ss,spectro_cutoff(ss)/.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:
	      /* try to send unbuckified random keyboard input to the lisp listener */
	      ss = cp->state;
	      if ((ss->listening == 1) || ((ss->listening == 2) && (lisp_listener_height() > 5)))
		{
		  char buf[2];
		  goto_listener();
		  buf[0]=keysym; buf[1]=0;
		  snd_append_char(ss,buf);
		}
	      /* should we open the minibuffer? */
	      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: 
	      if (selection_active(cp)) 
		{
		  get_amp_expression(sp,ext_count,1); 
		  searching = 1; 
		  redisplay = CURSOR_IN_VIEW;
		} 
	      else no_selection_error(sp); 
	      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: 
	      break;
	    case snd_K_I: case snd_K_i: paste_region(ext_explicit_count,cp,"C-x i"); 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_active(cp))
		cursor_moveto(cp,selection_beg(cp)+0.5*region_len(0));
	      else no_selection_error(sp); 
	      redisplay = CURSOR_IN_MIDDLE;
	      break;
	    case snd_K_N: case snd_K_n: 
	      if (selection_active(cp))
		eval_expression(cp,sp,ext_explicit_count,1); 
	      else no_selection_error(sp); 
	      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,"C-x q"); 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: 
	      if (selection_active(cp))
		{
		  window_frames_selection(cp); 
		  redisplay = CURSOR_UPDATE_DISPLAY; 
		}
	      else no_selection_error(sp); 
	      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: 
	      if (selection_active(cp))
		{
		  get_eval_expression(sp,ext_count,1); 
		  searching = 1; 
		  redisplay = CURSOR_IN_VIEW;
		}
	      else no_selection_error(sp); 
	      break;
	    case snd_K_Z: case snd_K_z: 
	      if (selection_active(cp))
		{
		  cos_smooth(cp,cp->cursor,ext_explicit_count,1); 
		  redisplay = CURSOR_UPDATE_DISPLAY; 
		}
	      else no_selection_error(sp); 
	      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_less: cp->cursor_on = 1; redisplay = cursor_moveto_beginning(cp); break;
	    case snd_K_greater: cp->cursor_on = 1; redisplay = cursor_moveto_end(cp); break;
	    case snd_K_openparen:
	      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_closeparen: 
	      if (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); 
	      set_show_marks(ss,1); 
	      searching = 1; 
	      break;
	    }
	}
    }
  if ((sp->minibuffer_on) && (!searching)) 
    clear_minibuffer(sp);
  else if (!cursor_searching) 
    sp->searching = 0;
  return(redisplay);
}
