#include "snd.h"
#include "clm2xen.h"
#include "clm-strings.h"

/* collect syncd chans */
typedef struct {
  sync_info *si;
  snd_fd **sfs;
  mus_long_t dur;
} sync_state;


static void free_sync_state(sync_state *sc)
{
  if (sc)
    {
      if (sc->si) 
	sc->si = free_sync_info(sc->si);
      if (sc->sfs) 
	free(sc->sfs);
      free(sc);
    }
}


int to_c_edit_position(chan_info *cp, Xen edpos, const char *caller, int arg_pos)
{
  int pos = AT_CURRENT_EDIT_POSITION;
  /* need to allow #f here for optargs */
  /* also remember that there might be no extension language */

#if (!HAVE_EXTENSION_LANGUAGE)
  return(cp->edit_ctr);
#endif

  Xen_check_type(!Xen_is_bound(edpos) || Xen_is_integer(edpos) || Xen_is_false(edpos), edpos, arg_pos, caller, "an integer, or " PROC_FALSE);

  if (Xen_is_integer(edpos))
    pos = Xen_integer_to_C_int(edpos);

  if (pos == AT_CURRENT_EDIT_POSITION)
    return(cp->edit_ctr);

  if ((pos < 0) || 
      (pos >= cp->edit_size) ||
      (!cp->edits[pos]))
    Xen_error(Xen_make_error_type("no-such-edit"),
	      Xen_list_8(C_string_to_Xen_string("~A: no such edpos: ~A (from ~A), sound index: ~A (~S), chan: ~A, current edit: ~A"),
			 C_string_to_Xen_string(caller),
			 C_int_to_Xen_integer(pos),
			 edpos,
			 C_int_to_Xen_sound(cp->sound->index),
			 C_string_to_Xen_string(cp->sound->short_filename),
			 C_int_to_Xen_integer(cp->chan),
			 C_int_to_Xen_integer(cp->edit_ctr)));
  return(pos);
}


mus_long_t to_c_edit_samples(chan_info *cp, Xen edpos, const char *caller, int arg_pos)
{
  return(cp->edits[to_c_edit_position(cp, edpos, caller, arg_pos)]->samples);
}


mus_long_t beg_to_sample(Xen beg, const char *caller)
{
  if (Xen_is_integer(beg))
    {
      mus_long_t start;
      start = Xen_llong_to_C_llong(beg);
      if (start < 0) 
	Xen_error(Xen_make_error_type("no-such-sample"), 
		  Xen_list_3(C_string_to_Xen_string("~A: no such sample: ~A"), C_string_to_Xen_string(caller), beg));
      if (start > (1LL << 34))
	Xen_out_of_range_error(caller, 1, beg, "too large");
      return(start);
    }
  return(0);
}


mus_long_t dur_to_samples(Xen dur, mus_long_t beg, chan_info *cp, int edpos, int argn, const char *caller)
{
  if (Xen_is_integer(dur))
    {
      mus_long_t samps;
      samps = Xen_llong_to_C_llong(dur);
      if (samps < 0)
	Xen_wrong_type_arg_error(caller, argn, dur, "a positive integer");
      if (samps > (1LL << 34))
	Xen_out_of_range_error(caller, argn, dur, "too large");
      return(samps);
    }
  return(cp->edits[edpos]->samples - beg);
}


static mus_long_t end_to_sample(Xen end, chan_info *cp, int edpos, const char *caller)
{
  if (Xen_is_integer(end))
    {
      mus_long_t last;
      last = Xen_llong_to_C_llong(end);
      if (last < 0) 
	Xen_error(Xen_make_error_type("no-such-sample"), 
		  Xen_list_3(C_string_to_Xen_string("~A: no such sample: ~A"), C_string_to_Xen_string(caller), end));
      if (last > (1LL << 34))
	Xen_out_of_range_error(caller, 2, end, "too large");
      return(last);
    }
  return(cp->edits[edpos]->samples - 1);
}


static sync_state *get_sync_state_1(snd_info *sp, chan_info *cp, mus_long_t beg, bool over_selection, 
				    read_direction_t forwards, mus_long_t prebeg, Xen edpos, const char *caller, int arg_pos)
{
  /* can return NULL if over_selection and no current selection */
  sync_info *si = NULL;
  snd_fd **sfs = NULL;
  chan_info *ncp;
  int i, pos;
  mus_long_t dur = 0, pbeg;
  sync_state *sc;

  if ((!over_selection) && (!sp)) return(NULL);

  if ((!over_selection) && (sp->sync != 0))
    {
      si = snd_sync(sp->sync);
      sfs = (snd_fd **)malloc(si->chans * sizeof(snd_fd *));
      for (i = 0; i < si->chans; i++) 
	{
	  ncp = si->cps[i];
	  si->begs[i] = beg;
	  pos = to_c_edit_position(ncp, edpos, caller, arg_pos);
	  if (forwards == READ_FORWARD)
	    sfs[i] = init_sample_read_any(beg, ncp, READ_FORWARD, pos);
	  else sfs[i] = init_sample_read_any(ncp->edits[pos]->samples - 1, ncp, READ_BACKWARD, pos);
	}
    }
  else
    {
      if (over_selection)
	{
	  if (selection_is_active())
	    {
	      si = selection_sync();
	      dur = selection_len();
	      sfs = (snd_fd **)malloc(si->chans * sizeof(snd_fd *));
	      for (i = 0; i < si->chans; i++) 
		{
		  ncp = si->cps[i];
		  pos = to_c_edit_position(ncp, edpos, caller, arg_pos);
		  if (forwards == READ_FORWARD)
		    {
		      pbeg = si->begs[i] - prebeg;
		      if (pbeg < 0) pbeg = 0;
		      sfs[i] = init_sample_read_any(pbeg, ncp, READ_FORWARD, pos);
		    }
		  else sfs[i] = init_sample_read_any(si->begs[i] + dur - 1, ncp, READ_BACKWARD, pos);
		}
	    }
	  else 
	    {
	      snd_warning_without_format("no selection");
	      return(NULL);
	    }
	}
    }
  if (!si) 
    {
      snd_fd *sf = NULL;
      pos = to_c_edit_position(cp, edpos, caller, arg_pos);
      if (forwards == READ_FORWARD)
	sf = init_sample_read_any(beg, cp, READ_FORWARD, pos);
      else sf = init_sample_read_any(cp->edits[pos]->samples - 1, cp, READ_BACKWARD, pos);
      if (sf)
	{
	  si = make_simple_sync(cp, beg);
	  sfs = (snd_fd **)malloc(sizeof(snd_fd *));
	  sfs[0] = sf;
	}
      else return(NULL);
    }
  sc = (sync_state *)calloc(1, sizeof(sync_state));
  sc->dur = dur;
  sc->sfs = sfs;
  sc->si = si;
  return(sc);
}


static sync_state *get_sync_state(snd_info *sp, chan_info *cp, mus_long_t beg, bool over_selection, 
				  read_direction_t forwards, Xen edpos, const char *caller, int arg_pos)
{
  return(get_sync_state_1(sp, cp, beg, over_selection, forwards, 0, edpos, caller, arg_pos));
}


static sync_state *get_sync_state_without_snd_fds(snd_info *sp, chan_info *cp, mus_long_t beg, bool over_selection)
{
  sync_info *si = NULL;
  mus_long_t dur = 0;
  sync_state *sc;
  if ((sp->sync != 0) && (!over_selection))
    {
      int i;
      si = snd_sync(sp->sync);
      for (i = 0; i < si->chans; i++) 
	si->begs[i] = beg;
    }
  else
    {
      if (over_selection)
	{
	  if (selection_is_active())
	    {
	      si = selection_sync();
	      dur = selection_len();
	    }
	  else
	    {
	      snd_warning_without_format("no selection");
	      return(NULL);
	    }
	}
    }
  if (!si) 
    si = make_simple_sync(cp, beg);
  sc = (sync_state *)calloc(1, sizeof(sync_state));
  sc->dur = dur;
  sc->si = si;
  sc->sfs = NULL;
  return(sc);
}


static char *convolve_with_or_error(char *filename, mus_float_t amp, chan_info *cp, Xen edpos, int arg_pos)
{
  /* if string returned, needs to be freed */
  /* amp == 0.0 means unnormalized, cp == NULL means current selection */
  sync_state *sc;
  sync_info *si;
  snd_info *sp = NULL;
  int ip, stop_point = 0, filter_chans;
  mus_sample_t dataformat;
  mus_long_t filtersize = 0, dataloc;
  chan_info *ncp, *ucp;
  char *origin;

  if (cp) 
    {
      sp = cp->sound; 
      ncp = cp;
    }
  else
    {
      sp = any_selected_sound();
      if (!sp) return(NULL);
      ncp = any_selected_channel(sp);
    }

  filter_chans = mus_sound_chans(filename);
  if (filter_chans <= 0)
    return(mus_format("convolve: impulse response file %s chans: %d", filename, filter_chans));

  filtersize = mus_sound_samples(filename) / filter_chans;
  if (filtersize <= 0) 
    return(mus_format("convolve: impulse response file %s is empty", filename));
  /* if impulse response is stereo, we need to use both its channels */

  dataloc = mus_sound_data_location(filename);
  dataformat = mus_sound_sample_type(filename);

  sc = get_sync_state_without_snd_fds(sp, ncp, 0, (!cp));
  if (!sc) return(NULL);
  si = sc->si;

#if HAVE_FORTH
  origin = mus_format("\"%s\" %.3f %s", 
		      filename, amp,  (!cp) ? S_convolve_selection_with : S_convolve_with);
#else
  origin = mus_format("%s" PROC_OPEN "\"%s\"" PROC_SEP "%.3f", 
	       to_proc_name((!cp) ? S_convolve_selection_with : S_convolve_with), 
	       filename, amp);
#endif
  if (!(ss->stopped_explicitly))
    {
      snd_info *gsp = NULL;
      int impulse_chan = 0;
      for (ip = 0; ip < si->chans; ip++)
	{
	  char *ofile, *saved_chan_file;
	  io_error_t io_err;
	  bool ok = false;
	  mus_long_t filesize;

      	  ucp = si->cps[ip];
	  if (!(is_editable(ucp))) continue;
	  sp = ucp->sound;
	  if (!(sp->active)) continue;
	  if ((ip == 0) || (sp != gsp)) 
	    gsp = ucp->sound; 

	  /* ofile here = new convolved data */
	  ofile = snd_tempnam();

	  saved_chan_file = snd_tempnam();
	  io_err = save_channel_edits(ucp, saved_chan_file, to_c_edit_position(ucp, edpos, S_convolve_with, arg_pos));
	  if (io_err != IO_NO_ERROR)
	    {
	      if (ofile) free(ofile);
	      free_sync_state(sc);
	      return(mus_format("convolve: save chan (%s[%d]) in %s hit error: %s\n",
				sp->short_filename, ucp->chan, 
				saved_chan_file, snd_open_strerror()));
	    }
	  else
	    {
	      int scfd;
	      scfd = mus_file_open_read(saved_chan_file);
	      if (scfd == -1) 
		{
		  if (ofile) free(ofile);
		  free_sync_state(sc);
		  return(mus_format("convolve: open saved chan (%s[%d]) file %s hit error: %s\n",
				    sp->short_filename, ucp->chan, 
				    saved_chan_file, snd_open_strerror()));
		}
	      else
		{
		  file_info *hdr;
		  int fltfd;
		  hdr = sp->hdr;
		  snd_file_open_descriptors(scfd,
					    saved_chan_file,
					    hdr->sample_type,
					    hdr->data_location,
					    1, hdr->type); /* ??? */
		  fltfd = mus_file_open_read(filename);
		  if (fltfd == -1) 
		    {
		      if (ofile) free(ofile);
		      free_sync_state(sc);
		      return(mus_format("convolve: open filter file %s hit error: %s\n", 
					filename, snd_open_strerror()));
		    }
		  else
		    {
		      snd_file_open_descriptors(fltfd,
						filename,
						dataformat,
						dataloc,
						filter_chans,
						mus_sound_header_type(filename));
		      if (!cp)
			filesize = selection_len();
		      else filesize = to_c_edit_samples(ucp, edpos, S_convolve_with, arg_pos);
		      if (filesize > 0)
			{
			  int ipow;
			  mus_long_t fftsize;
			  ipow = (int)(ceil(log(filtersize + filesize) / log(2.0))) + 1;
			  fftsize = snd_mus_long_t_pow2(ipow);
			  ok = true;
			  c_convolve(ofile, amp, scfd,
				     mus_sound_data_location(saved_chan_file),
				     fltfd, dataloc, filtersize, fftsize, filter_chans, impulse_chan,
				     filtersize + filesize + 1,
				     gsp);
			  impulse_chan++;
			  if (impulse_chan >= filter_chans) 
			    impulse_chan = 0;
			}
		      if (mus_file_close(fltfd) != 0)
			{
			  if (ofile) free(ofile);
			  free_sync_state(sc);
			  return(mus_format("convolve: close filter file %s hit error: %s\n", 
					    filename, snd_io_strerror()));
			}
		    }
		}
	      if (mus_file_close(scfd) != 0)
		{
		  if (ofile) free(ofile);
		  free_sync_state(sc);
		  return(mus_format("convolve: close saved chan (%s[%d]) file %s hit error: %s\n",
				    sp->short_filename, ucp->chan, 
				    saved_chan_file, snd_io_strerror()));
		}
	    }
	  snd_remove(saved_chan_file, REMOVE_FROM_CACHE);
	  free(saved_chan_file);

	  if (ok)
	    {
	      if (!cp)
		{
		  delete_samples(si->begs[ip], sc->dur, ucp, ucp->edit_ctr);
		  if ((filtersize + filesize) > 0)
		    {
		      file_insert_samples(si->begs[ip], filtersize + filesize, ofile, ucp, 0, DELETE_ME, origin, ucp->edit_ctr);
		      reactivate_selection(ucp, si->begs[ip], si->begs[ip] + filtersize + filesize);
		      backup_edit_list(ucp); 
		      ripple_trailing_marks(ucp, si->begs[ip], sc->dur, filtersize + filesize);
		    }
		  else snd_remove(ofile, REMOVE_FROM_CACHE);
		  update_graph(ucp); 
		}
	      else file_override_samples(filtersize + filesize, ofile, ucp, 0, DELETE_ME, origin);
	    }
	  if (ofile) free(ofile);
	  check_for_event();
	  if (ss->stopped_explicitly) 
	    {
	      stop_point = ip;
	      break;
	    }
	}
    }
  if (origin) 
    {
      free(origin); 
      origin = NULL;
    }
  if (ss->stopped_explicitly)
    {
      /* clean up and undo all edits up to stop_point */
      ss->stopped_explicitly = false;
      for (ip = 0; ip <= stop_point; ip++)
	{
	  ucp = si->cps[ip];
	  undo_edit(ucp, 1);
	}
    }
  free_sync_state(sc);
  return(NULL);
}


/* amplitude scaling */

void scale_by(chan_info *cp, mus_float_t *ur_scalers, int len, bool over_selection)
{
  /* if over_selection, sync to current selection, else sync to current sound */
  /* 3-Oct-00: the scale factors are now embedded in the edit fragments  */
  sync_info *si;
  int i, j;

  if (over_selection)
    si = selection_sync();
  else si = sync_to_chan(cp);
  if (!si) return;

  for (i = 0, j = 0; i < si->chans; i++)
    {
      mus_long_t beg, framples;
      chan_info *ncp;
      ncp = si->cps[i];
      if (over_selection)
	{
	  beg = selection_beg(ncp);
	  framples = selection_end(ncp) - beg + 1;
	}
      else
	{
	  beg = 0;
	  framples = current_samples(ncp);
	}
      scale_channel(ncp, ur_scalers[j], beg, framples, ncp->edit_ctr, NOT_IN_AS_ONE_EDIT);
      j++;
      if (j >= len) j = 0;
    }
  free_sync_info(si);
}


bool scale_to(snd_info *sp, chan_info *cp, mus_float_t *ur_scalers, int len, bool over_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 */
  bool scaled = false;
  int i, chans, nlen, datum_size;
  sync_info *si = NULL;
  chan_info *ncp;
  mus_float_t maxamp = -1.0, val;
  mus_float_t *scalers;

  if ((!over_selection) && (!cp)) return(false);
  if (over_selection) 
    {
      if (!(selection_is_active())) 
	return(false);
      si = selection_sync();
      sp = si->cps[0]->sound;
    }
  else si = sync_to_chan(cp);

  datum_size = mus_bytes_per_sample((sp->hdr)->sample_type);
  chans = si->chans;
  scalers = (mus_float_t *)calloc(chans, sizeof(mus_float_t));
  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)
    {
      if (scalers[0] != 0.0)
	{
	  for (i = 0; i < chans; i++)
	    {
	      ncp = si->cps[i];
	      if (over_selection)
		val = selection_maxamp(ncp);
	      else val = channel_maxamp(ncp, AT_CURRENT_EDIT_POSITION);
	      if (val > maxamp) maxamp = val;
	    }
	  if ((!(clipping(ss))) && 
	      (scalers[0] == 1.0) &&
	      (datum_size <= 2))
	    {
	      if (datum_size == 2)
		scalers[0] = 32767.0 / 32768.0;
	      else scalers[0] = 127.0 / 128.0;
	    }
	  if (maxamp != 0.0)
	    val = scalers[0] / maxamp;
	  else val = 0.0;
	}
      else val = 0.0;
      for (i = 0; i < chans; i++) 
	scalers[i] = val;
    }
  else
    {
      for (i = 0; i < chans; i++)
	{
	  ncp = si->cps[i];
	  if (scalers[i] != 0.0)
	    {
	      if (over_selection)
		val = selection_maxamp(ncp);
	      else val = channel_maxamp(ncp, AT_CURRENT_EDIT_POSITION);
	      if (val > maxamp) maxamp = val;
	      if (val != 0.0)
		{
		  if ((!(clipping(ss))) && 
		      (scalers[i] == 1.0) && 
		      (datum_size <= 2))
		    {
		      if (datum_size == 2)
			scalers[i] = 32767.0 / 32768.0;
		      else scalers[i] = 127.0 / 128.0;
		    }
		  scalers[i] /= val;
		}
	      else scalers[i] = 0.0;
	    }
	  else maxamp = 1.0; /* turn off the maxamp check */
	}
    }

  if (maxamp != 0.0)
    {
      for (i = 0; i < si->chans; i++)
	{
	  mus_long_t beg, framples;
	  char *origin = NULL;
	  mus_float_t norm = 1.0;
	  ncp = si->cps[i];
	  if (nlen > i) norm = ur_scalers[i]; else norm = ur_scalers[0];
	  if (over_selection)
	    {
	      beg = selection_beg(ncp);
	      framples = selection_end(ncp) - beg + 1;
#if HAVE_FORTH
	      origin = mus_format("%.3f" PROC_SEP "%" print_mus_long PROC_SEP "%" print_mus_long " %s", norm, beg, framples, S_normalize_channel);
#else
	      origin = mus_format("%s" PROC_OPEN "%.3f" PROC_SEP "%" print_mus_long PROC_SEP "%" print_mus_long, to_proc_name(S_normalize_channel), norm, beg, framples);
#endif
	    }
	  else
	    {
	      beg = 0;
	      framples = current_samples(ncp);
#if HAVE_FORTH
	      origin = mus_format("%.3f 0 " PROC_FALSE " %s", norm, S_normalize_channel);
#else
	      origin = mus_format("%s" PROC_OPEN "%.3f" PROC_SEP "0" PROC_SEP PROC_FALSE, to_proc_name(S_normalize_channel), norm);
#endif
	    }
	  scale_channel_with_origin(ncp, scalers[i], beg, framples, ncp->edit_ctr, NOT_IN_AS_ONE_EDIT, origin);
	  if (origin) free(origin);
	  origin = NULL;
	}
      scaled = true;
    }

  free(scalers);
  free_sync_info(si);
  return(scaled);
}


static void swap_channels(chan_info *cp0, chan_info *cp1, mus_long_t beg, mus_long_t dur, int pos0, int pos1)
{
  snd_fd *c0, *c1;
  snd_info *sp0;
  file_info *hdr0 = NULL, *hdr1 = NULL;
  int ofd0 = 0, ofd1 = 0, datumb = 0;
  bool temp_file;
  mus_long_t alloc_len;
  mus_float_t **data0, **data1;
  mus_float_t *idata0, *idata1;
  bool reporting = false;
  char *ofile0 = NULL, *ofile1 = NULL;
  io_error_t io_err = IO_NO_ERROR;

  if (dur <= 0) return;
  if ((!(is_editable(cp0))) || (!(is_editable(cp1)))) return;
  sp0 = cp0->sound;
  reporting = ((sp0) && (dur > REPORTING_SIZE) && (!(cp0->squelch_update)));
  if (reporting) start_progress_report(cp0);

  if (dur > REPORTING_SIZE)
    {
      alloc_len = REPORTING_SIZE;
      temp_file = true; 
      ofile0 = snd_tempnam();
      hdr0 = make_temp_header(ofile0, snd_srate(sp0), 1, dur, (char *)S_swap_channels);
      ofd0 = open_temp_file(ofile0, 1, hdr0, &io_err);
      if (ofd0 == -1)
	{
	  free_file_info(hdr0);
	  snd_error("%s " S_swap_channels " temp file %s: %s\n", 
		    (io_err != IO_NO_ERROR) ? io_error_name(io_err) : "can't open",
		    ofile0, 
		    snd_open_strerror());
	  return;
	}
      datumb = mus_bytes_per_sample(hdr0->sample_type);
      ofile1 = snd_tempnam();
      hdr1 = make_temp_header(ofile1, snd_srate(sp0), 1, dur, (char *)S_swap_channels);
      ofd1 = open_temp_file(ofile1, 1, hdr1, &io_err);
      if (ofd1 == -1)
	{
	  close_temp_file(ofile0, ofd0, hdr0->type, 0);
	  free_file_info(hdr0);
	  free_file_info(hdr1);
	  if (ofile0) free(ofile0);
	  snd_error("%s " S_swap_channels " temp file %s: %s\n", 
		    (io_err != IO_NO_ERROR) ? io_error_name(io_err) : "can't open",
		    ofile1, 
		    snd_open_strerror());
	  return;
	}
    }
  else 
    {
      temp_file = false;
      alloc_len = dur;
    }

  data0 = (mus_float_t **)malloc(sizeof(mus_float_t *));
  data0[0] = (mus_float_t *)calloc(alloc_len, sizeof(mus_float_t)); 
  data1 = (mus_float_t **)malloc(sizeof(mus_float_t *));
  data1[0] = (mus_float_t *)calloc(alloc_len, sizeof(mus_float_t)); 
  idata0 = data0[0];
  idata1 = data1[0];
  c0 = init_sample_read_any_with_bufsize(beg, cp0, READ_FORWARD, pos0, alloc_len);
  c1 = init_sample_read_any_with_bufsize(beg, cp1, READ_FORWARD, pos1, alloc_len);

  if (temp_file)
    {
      mus_long_t k;
      ss->stopped_explicitly = false;
      sampler_set_safe(c0, dur);
      sampler_set_safe(c1, dur);
      for (k = 0; k < dur; k += alloc_len)
	{
	  int j, n, err;
	  j = dur - k;
	  if (j > alloc_len) j = alloc_len;

	  for (n = 0; n < j; n++)
	    {
	      idata0[j] = read_sample(c1);
	      idata1[j] = read_sample(c0);
	    }
	  mus_file_write(ofd0, 0, j - 1, 1, data0);
	  err = mus_file_write(ofd1, 0, j - 1, 1, data1);

	  if (err != MUS_NO_ERROR) break;
	  if (reporting) 
	    {
	      progress_report(cp0, (mus_float_t)((double)k / (double)dur));
	      if (ss->stopped_explicitly) break;
	      if ((cp0->active < CHANNEL_HAS_EDIT_LIST) || 
		  (cp1->active < CHANNEL_HAS_EDIT_LIST))
		{
		  ss->stopped_explicitly = true;
		  break;
		}
	    }
	}
      close_temp_file(ofile0, ofd0, hdr0->type, dur * datumb);
      close_temp_file(ofile1, ofd1, hdr1->type, dur * datumb);
      free_file_info(hdr0);
      free_file_info(hdr1);
      if (!(ss->stopped_explicitly))
	{
	  file_change_samples(beg, dur, ofile0, cp0, 0, DELETE_ME, S_swap_channels, cp0->edit_ctr);
	  file_change_samples(beg, dur, ofile1, cp1, 0, DELETE_ME, S_swap_channels, cp1->edit_ctr);
	}
      else
	{
	  set_status(sp0, "swap interrupted", false);
	  ss->stopped_explicitly = false;
	}
      if (ofile0) {free(ofile0); ofile0 = NULL;}
      if (ofile1) {free(ofile1); ofile1 = NULL;}
      if (reporting) finish_progress_report(cp0);
    }
  else 
    {
      samples_to_vct_with_reader(dur, idata0, c1);
      samples_to_vct_with_reader(dur, idata1, c0);

      change_samples(beg, dur, data0[0], cp0, S_swap_channels, cp0->edit_ctr, -1.0);
      change_samples(beg, dur, data1[0], cp1, S_swap_channels, cp1->edit_ctr, -1.0);
    }
  swap_marks(cp0, cp1);
  update_graph(cp0);
  update_graph(cp1);
  if (ofile0) free(ofile0);
  if (ofile1) free(ofile1);
  free(data0[0]);
  free(data0);
  free(data1[0]);
  free(data1);
  free_snd_fd(c0);
  free_snd_fd(c1);
}


/* -------- reverse-channel -------- */

mus_float_t previous_sample_value_unscaled_and_unchecked(snd_fd *sf);
mus_float_t previous_sample_value_unscaled(snd_fd *sf);
mus_float_t previous_sample_value_unchecked(snd_fd *sf);

static char *reverse_channel(chan_info *cp, snd_fd *sf, mus_long_t beg, mus_long_t dur, Xen edp, const char *caller, int arg_pos)
{
  snd_info *sp;
  peak_env_info *ep = NULL;
  file_info *hdr = NULL;
  int ofd = 0, datumb = 0, edpos = 0;
  bool section = false, temp_file;
  mus_long_t k, alloc_len;
  char *origin = NULL;
  mus_float_t **data;
  mus_float_t *idata;
  char *ofile = NULL;
  io_error_t io_err = IO_NO_ERROR;

  if ((beg < 0) || (dur <= 0)) return(NULL);
  if (!(is_editable(cp))) return(NULL);
  /* if last was reverse and start/end match, we could just copy preceding edlist entry, or undo/redo etc --
   *   how to tell that this is happening?
   */
  sp = cp->sound;
  edpos = to_c_edit_position(cp, edp, caller, arg_pos);

  if (dur > cp->edits[edpos]->samples) dur = cp->edits[edpos]->samples;
  if (dur > REPORTING_SIZE)
    {
      temp_file = true; 
      alloc_len = REPORTING_SIZE;
      ofile = snd_tempnam();
      hdr = make_temp_header(ofile, snd_srate(sp), 1, dur, caller);
      ofd = open_temp_file(ofile, 1, hdr, &io_err);
      if (ofd == -1)
	{
	  char *str;
	  str = mus_format("%s %s temp file %s: %s\n", 
			    (io_err != IO_NO_ERROR) ? io_error_name(io_err) : "can't open",
			    caller, ofile, 
			    snd_open_strerror());
	  if (ofile) free(ofile);
	  return(str);
	}
      datumb = mus_bytes_per_sample(hdr->sample_type);
    }
  else 
    {
      temp_file = false;
      alloc_len = dur;
    }

  if ((beg == 0) && (dur == cp->edits[edpos]->samples))
    ep = peak_env_copy(cp, true, edpos); /* true -> reversed */
  else 
    {
      ep = peak_env_copy(cp, false, edpos);
      if (ep) 
	{
	  int i, j, sbin, ebin;
	  /* now reverse the selection */
	  sbin = (int)ceil(beg / ep->samps_per_bin);
	  ebin = (int)floor((beg + dur) / ep->samps_per_bin);
	  if (ebin > ep->peak_env_size) ebin = ep->peak_env_size;
	  for (i = sbin, j = ebin - 1; i < j; i++, j--)
	    {
	      mus_float_t min1, max1;
	      min1 = ep->data_min[i];
	      max1 = ep->data_max[i];
	      ep->data_min[i] = ep->data_min[j];
	      ep->data_max[i] = ep->data_max[j];
	      ep->data_min[j] = min1;
	      ep->data_max[j] = max1;
	    }
	  if (sbin > 0) pick_one_bin(ep, sbin - 1, ep->samps_per_bin * (sbin - 1), cp, edpos);
	  if (ebin < ep->peak_env_size) pick_one_bin(ep, ebin, ep->samps_per_bin * ebin, cp, edpos);
	}
      section = true; /* only for reverse_marks below */
    }

  sampler_set_safe(sf, dur);
  data = (mus_float_t **)malloc(sizeof(mus_float_t *));
  data[0] = (mus_float_t *)malloc(alloc_len * sizeof(mus_float_t)); 
  idata = data[0];

#if HAVE_FORTH
  if (dur == cp->edits[edpos]->samples)
    origin = mus_format("%" print_mus_long PROC_SEP PROC_FALSE " %s", beg, S_reverse_channel);
  else origin = mus_format("%" print_mus_long PROC_SEP "%" print_mus_long " %s", beg, dur, S_reverse_channel);
#else
  if (dur == cp->edits[edpos]->samples)
    origin = mus_format("%s" PROC_OPEN "%" print_mus_long PROC_SEP PROC_FALSE, to_proc_name(S_reverse_channel), beg);
  else origin = mus_format("%s" PROC_OPEN "%" print_mus_long PROC_SEP "%" print_mus_long, to_proc_name(S_reverse_channel), beg, dur);
#endif

  if (temp_file)
    {
      for (k = 0; k < dur; k += alloc_len)
	{ 
	  int err;
	  mus_long_t kp, kdur;
	  kdur = dur - k;
	  if (kdur > alloc_len) kdur = alloc_len;
	  for (kp = 0; kp < kdur; kp++)
	    idata[kp] = read_sample(sf);
	  err = mus_file_write(ofd, 0, kdur - 1, 1, data);
	  if (err != MUS_NO_ERROR) break;
	}
      close_temp_file(ofile, ofd, hdr->type, dur * datumb);
      free_file_info(hdr);
      file_change_samples(beg, dur, ofile, cp, 0, DELETE_ME, origin, edpos);
      if (ofile) 
	{
	  free(ofile); 
	  ofile = NULL;
	}
    }
  else 
    {
      mus_long_t n;
      if ((sf->runf == previous_sample_value_unscaled_and_unchecked) ||
	  ((sf->runf == previous_sample_value_unscaled) &&
	   (sf->loc - sf->first >= (dur - 1))))
	{
	  for (n = sf->loc, k = 0; k < dur; k++, n--)
	    idata[k] = sf->data[n];
	}
      else
	{
	  if (sf->runf == previous_sample_value_unchecked)
	    {
	      for (n = sf->loc, k = 0; k < dur; k++, n--)
		idata[k] = sf->data[n] * sf->fscaler;
	    }
	  else
	    {
	      /* beg is begin time of the edit, not where the reverse read starts */
	      /* independent of sf, if edpos is 0, and saved_data is available, and beg+dur < saved_data length, just use it.
	       */
	      mus_float_t **d;
	      if ((sf->runf == previous_sample_value_unscaled) &&
		  (edpos == 0) &&
		  (beg + dur <= cp->edits[0]->samples) &&
		  (d = mus_sound_saved_data(sp->filename)))
		{
		  mus_long_t d2;
		  mus_float_t *dc;

		  dc = d[cp->chan];
		  if (dur & 1) d2 = dur - 1; else d2 = dur;
		  for (n = beg + dur, k = 0; k < d2; )
		    {
		      idata[k++] = dc[n--];
		      idata[k++] = dc[n--];
		    }
		  if (k < dur) idata[k] = dc[n];
		}
	      else
		{
		  for (k = 0; k < dur; k++)
		    idata[k] = read_sample(sf);
		}
	    }
	}
      change_samples(beg, dur, idata, cp, origin, edpos, -1.0);
    }
  if (ep) cp->edits[cp->edit_ctr]->peak_env = ep;
  reverse_marks(cp, (section) ? beg : -1, dur);
  update_graph(cp); 
  free(data[0]);
  free(data);
  if (origin) free(origin);
  return(NULL);
}


void reverse_sound(chan_info *ncp, bool over_selection, Xen edpos, int arg_pos)
{
  sync_state *sc;
  sync_info *si;
  int i, stop_point = 0;
  snd_fd **sfs;
  char *caller;
  snd_info *sp;
  chan_info *cp;

  sp = ncp->sound;
  caller = (char *)((over_selection) ? S_reverse_selection : S_reverse_sound);
  sc = get_sync_state(sp, ncp, 0, over_selection, READ_BACKWARD, edpos, (const char *)caller, arg_pos);
  if (!sc) return;
  si = sc->si;
  sfs = sc->sfs;

  if (!(ss->stopped_explicitly))
    {
      for (i = 0; i < si->chans; i++)
	{
	  char *errmsg = NULL;
	  mus_long_t dur;
	  cp = si->cps[i];
	  sp = cp->sound;
	  if (over_selection)
	    dur = sc->dur;
	  else dur = to_c_edit_samples(cp, edpos, caller, arg_pos);
	  if (dur == 0) 
	    {
	      sfs[i] = free_snd_fd(sfs[i]); 
	      continue;
	    }
	  
	  errmsg = reverse_channel(cp, sfs[i], si->begs[i], dur, edpos, caller, arg_pos);

	  sfs[i] = free_snd_fd(sfs[i]);
	  if (errmsg)
	    {
	      snd_error_without_format(errmsg);
	      free(errmsg);
	      break;
	    }
	  if (ss->stopped_explicitly) 
	    {
	      stop_point = i;
	      break;
	    }
	}
    }

  if (ss->stopped_explicitly)
    {
      set_status(sp, "reverse stopped", false);
      ss->stopped_explicitly = false;
      for (i = 0; i <= stop_point; i++)
	{
	  cp = si->cps[i];
	  undo_edit(cp, 1);
	}
    }
  free_sync_state(sc);
}




/* -------- src -------- */

typedef struct {
  mus_any *gen;
  snd_fd *sf;
  mus_long_t sample;
  int dir;
} src_state;

static mus_float_t src_input_as_needed(void *arg, int direction) 
{
  src_state *sr = (src_state *)arg;
  snd_fd *sf;
  sf = sr->sf;
  sr->sample++;
  if (direction != sr->dir)
    {
      read_sample_change_direction(sf, (direction == 1) ? READ_FORWARD : READ_BACKWARD);
      sr->dir = direction;
    }
  return(read_sample(sf));
}


static mus_float_t src_input_as_needed_unchanged(void *arg, int direction) 
{
  src_state *sr = (src_state *)arg;
  sr->sample++;
  return(read_sample(sr->sf));
}


static mus_float_t read_sample_input(void *arg, int direction) 
{
  src_state *sr = (src_state *)arg;
  return(read_sample(sr->sf));
}


static src_state *make_src(mus_float_t srate, snd_fd *sf, bool src_change)
{
  src_state *sr;
  if ((sinc_width(ss) > MUS_MAX_CLM_SINC_WIDTH) ||
      (sinc_width(ss) < 0) ||
      (fabs(srate) > MUS_MAX_CLM_SRC))
    return(NULL);
  sr = (src_state *)calloc(1, sizeof(src_state));
  sr->sf = sf;
  if (srate >= 0.0) sr->dir = 1; else sr->dir = -1;          /* if env on src, this will be 0.0 even if env vals are < 0.0 */
  if (src_change)
    sr->gen = mus_make_src(&src_input_as_needed, srate, sinc_width(ss), (void *)sr);
  else sr->gen = mus_make_src(&src_input_as_needed_unchanged, srate, sinc_width(ss), (void *)sr);
  mus_set_increment(sr->gen, srate);
  sr->sample = 0;
  return(sr);
}


static src_state *free_src(src_state *sr)
{
  mus_free(sr->gen);
  free(sr);
  return(NULL);
}


static int mus_long_t_compare(const void *a, const void *b)
{
  mus_long_t *m1, *m2;
  m1 = (mus_long_t *)a;
  m2 = (mus_long_t *)b;
  if (*m1 < *m2) return(-1);
  if (*m1 == *m2) return(0);
  return(1);
}


mus_float_t next_sample_value_unscaled_and_unchecked(snd_fd *sf);

static char *src_channel_with_error(chan_info *cp, snd_fd *sf, mus_long_t beg, mus_long_t dur, mus_float_t ratio, mus_any *egen, 
				    const char *origin, bool over_selection,
				    bool *clm_err)
{
  snd_info *sp = NULL;
  bool reporting = false;
  bool full_chan;
  mus_float_t **data;
  file_info *hdr = NULL;
  int j, ofd = 0, datumb = 0, err = 0;
  mus_long_t *old_marks = NULL, *new_marks = NULL;
  int cur_marks = 0;
  mus_long_t k;
  char *ofile = NULL;
  mus_float_t *idata;
  io_error_t io_err = IO_NO_ERROR;
  src_state *sr;

  /* fprintf(stderr, "src: %" print_mus_long " %f %s\n", dur, ratio, origin); */

  if ((!egen) && (sf->edit_ctr == cp->edit_ctr))
    {
      if (ratio == 1.0) 
	return(NULL);
      if (ratio == -1.0)
	return(reverse_channel(cp, sf, beg, dur, C_int_to_Xen_integer(sf->edit_ctr), origin, 0));
    }

  sp = cp->sound;
  if (!(is_editable(cp))) return(NULL); /* edit hook result perhaps */

  sr = make_src(ratio, sf, egen);  /* ratio is 0.0 if egen because the envelope is the srate, but it's passed as the "sr-change" arg */
  if (!sr) 
    {
      (*clm_err) = true;
      return(mus_format("invalid src ratio: %f\n", ratio));
    }
  if ((egen) &&
      (mus_phase(egen) < 0.0))
    sr->dir = -1;

  full_chan = ((beg == 0) && (dur == cp->edits[sf->edit_ctr]->samples)); /* not current_samples here! */

  reporting = ((sp) && (dur > REPORTING_SIZE) && (!(cp->squelch_update)));
  if (reporting) start_progress_report(cp);

  ofile = snd_tempnam();
  hdr = make_temp_header(ofile, snd_srate(sp), 1, dur, (char *)origin);
  ofd = open_temp_file(ofile, 1, hdr, &io_err);
  if (ofd == -1)
    {
      return(mus_format("%s %s temp file %s: %s\n", 
			(io_err != IO_NO_ERROR) ? io_error_name(io_err) : "can't open",
			origin, ofile, 
			snd_open_strerror()));
    }

  data = (mus_float_t **)malloc(sizeof(mus_float_t *));
  datumb = mus_bytes_per_sample(hdr->sample_type);

  j = 0;
  ss->stopped_explicitly = false;

  if (!egen)
    {
      if ((ratio == 0.5) && (dur < (1 << 22)))
	{
	  mus_long_t in_dur, swid2;
	  mus_float_t *in_data;

	  swid2 = 2 * sinc_width(ss);
	  in_dur = dur + 4 + swid2;
	  in_data = (mus_float_t *)calloc(in_dur, sizeof(mus_float_t));
	  samples_to_vct_with_reader(in_dur - swid2, (mus_float_t *)(in_data + swid2), sf);
	  data[0] = mus_src_05(sr->gen, in_data, dur);
	  k = dur * 2 + 1;
	  j = k;
	  free(in_data);
	}
      else
	{
	  if ((ratio == 2.0) && (dur < (1 << 23)))
	    {
	      /* make and fill input data, make output data, pass mus_src_20 the input data array, new dur, and sr->gen
	       */
	      mus_long_t in_dur, swid2;
	      mus_float_t *in_data;

	      swid2 = 2 * sinc_width(ss);
	      in_dur = dur + 4 + swid2;
	      in_data = (mus_float_t *)calloc(in_dur, sizeof(mus_float_t));
	      samples_to_vct_with_reader(in_dur - swid2, (mus_float_t *)(in_data + swid2), sf);
	      data[0] = mus_src_20(sr->gen, in_data, dur);
	      k = dur / 2 + 1;
	      if ((dur & 1) != 0) k++; /* ?? */
	      j = k;
	      free(in_data);
	    }
	  else
	    {
	      mus_long_t out_dur;

	      data[0] = (mus_float_t *)malloc(MAX_BUFFER_SIZE * sizeof(mus_float_t)); 
	      idata = data[0];
	      out_dur = ceil(dur / fabs(ratio)) + 1;

	      for (k = 0; k < out_dur; k += MAX_BUFFER_SIZE)
		{
		  mus_long_t kdur;
		  kdur = out_dur - k;
		  if (kdur > MAX_BUFFER_SIZE) kdur = MAX_BUFFER_SIZE;
		  mus_src_to_buffer(sr->gen, &read_sample_input, idata, kdur);

		  err = mus_file_write(ofd, 0, kdur - 1, 1, data);
		  if (err != MUS_NO_ERROR) break;
		  if (reporting) 
		    {
		      progress_report(cp, (mus_float_t)((double)(sr->sample) / (double)dur));
		      if (ss->stopped_explicitly) break;
		      if (!(sp->active))
			{
			  ss->stopped_explicitly = true;
			  break;
			}
		    }
		}
	      j = 0;
	      k = out_dur;
	    }
	}
    }
  else
    {
      mus_long_t next_pass;
      mus_long_t cur_mark_sample;
      int cur_mark = 0, cur_new_mark = 0;
      mus_float_t env_val;

      data[0] = (mus_float_t *)malloc(MAX_BUFFER_SIZE * sizeof(mus_float_t)); 
      idata = data[0];

      cur_mark_sample = -1;
      env_val = mus_env(egen); 
      /* envelope case -- have to go by sr->sample, not output sample counter, also check marks */

      if ((cp->edits[cp->edit_ctr]->marks) && 
	  (cp->edits[cp->edit_ctr]->mark_ctr >= 0))
	{
	  int m;
	  mark **mps;
	  mps = cp->edits[cp->edit_ctr]->marks;
	  cur_marks = cp->edits[cp->edit_ctr]->mark_ctr + 1;
	  new_marks = (mus_long_t *)malloc(cur_marks * sizeof(mus_long_t));
	  old_marks = (mus_long_t *)malloc(cur_marks * sizeof(mus_long_t));
	  for (m = 0; m < cur_marks; m++)
	    {
	      mus_long_t pos;
	      pos = mark_sample(mps[m]);
	      new_marks[m] = -1;
	      if ((env_val >= 0.0) ||
		  (pos < beg) ||
		  (pos > (beg + dur)))
		old_marks[m] = pos;
	      else 
		{
		  old_marks[m] = (dur - pos - 1) + beg; /* moving backwards, so flip marks */
		  cur_new_mark = m;
		}
	    }
	  if ((env_val < 0.0) && (cur_marks > 1))
	    qsort((void *)old_marks, cur_marks, sizeof(mus_long_t), mus_long_t_compare);
	  for (m = 0; m < cur_marks; m++)
	    if (old_marks[m] > beg)
	      {
		cur_mark_sample = old_marks[m];
		cur_mark = m;
		if ((env_val >= 0.0) || (cur_marks <= 1))
		  cur_new_mark = m;
		break;
	      }
	}
      next_pass = sr->sample;
      for (k = 0; sr->sample < dur; k++)
	{
	  idata[j] = ((mus_src(sr->gen, env_val, &src_input_as_needed)));
	  j++;
	  if (j == MAX_BUFFER_SIZE)
	    {
	      err = mus_file_write(ofd, 0, j - 1, 1, data);
	      j = 0;
	      if (err != MUS_NO_ERROR) break;
	      if (reporting) 
		{
		  progress_report(cp, (mus_float_t)((double)(sr->sample) / (double)dur));
		  if (ss->stopped_explicitly) break;
		  if (!(sp->active))
		    {
		      ss->stopped_explicitly = true;
		      break;
		    }
		}
	    }
	  if (next_pass != sr->sample)             /* tick env forward dependent on sr->sample */
	    {
	      mus_long_t jj, idiff;
	      idiff = sr->sample - next_pass;
	      next_pass = sr->sample;
	      if ((new_marks) && 
		  (cur_mark_sample != -1) && 
		  (next_pass >= (cur_mark_sample - beg))) 
		{
		  /* not '==' because sr->sample can be incremented by more than 1 */
		  new_marks[cur_new_mark] = k + beg;
		  cur_mark++;
		  if (env_val >= 0.0) cur_new_mark++; else cur_new_mark--;
		  if (cur_mark < cur_marks)
		    cur_mark_sample = old_marks[cur_mark];
		  else cur_mark_sample = -1;
		}
	      env_val = mus_env(egen);
	      for (jj = 1; jj < idiff; jj++) 
		env_val = mus_env(egen);
	    }
	}
    }

  if (reporting) finish_progress_report(cp);
  free_src(sr);
  if ((!(ss->stopped_explicitly)) && (j > 0)) 
    mus_file_write(ofd, 0, j - 1, 1, data);

  close_temp_file(ofile, ofd, hdr->type, k * datumb);
  free_file_info(hdr);

  if (!(ss->stopped_explicitly))
    {
      char *new_origin = NULL;
      /* egen null -> use ratio, else env, if dur=samples #f */
      if (!egen)
	{

#if HAVE_FORTH
	  if (dur == cp->edits[sf->edit_ctr]->samples)
	    new_origin = mus_format("%.4f" PROC_SEP "%" print_mus_long PROC_SEP PROC_FALSE " %s", ratio, beg, S_src_channel);
	  else new_origin = mus_format("%.4f" PROC_SEP "%" print_mus_long PROC_SEP "%" print_mus_long " %s", ratio, beg, dur, S_src_channel);
#else
	  if (dur == cp->edits[sf->edit_ctr]->samples)
	    new_origin = mus_format("%s" PROC_OPEN "%.4f" PROC_SEP "%" print_mus_long PROC_SEP PROC_FALSE, to_proc_name(S_src_channel), ratio, beg);
	  else new_origin = mus_format("%s" PROC_OPEN "%.4f" PROC_SEP "%" print_mus_long PROC_SEP "%" print_mus_long, to_proc_name(S_src_channel), ratio, beg, dur);
#endif
	}
      else
	{
	  mus_float_t base;
	  char *envstr;
	  env *newe;
	  base = mus_increment(egen);
	  newe = make_envelope_with_offset_and_scaler(mus_data(egen), mus_env_breakpoints(egen) * 2, mus_offset(egen), mus_scaler(egen));
	  envstr = env_to_string(newe);

#if HAVE_FORTH
	  if (base == 1.0)
	    {
	      if (dur == cp->edits[sf->edit_ctr]->samples)
		new_origin = mus_format("%s" PROC_SEP "%" print_mus_long PROC_SEP PROC_FALSE " %s", envstr, beg, S_src_channel);
	      else new_origin = mus_format("%s" PROC_SEP "%" print_mus_long PROC_SEP "%" print_mus_long " %s", envstr, beg, dur, S_src_channel);
	    }
	  else new_origin = mus_format("%s :base %.4f :end %" print_mus_long " %s %" print_mus_long PROC_SEP "%" print_mus_long " %s", envstr, base, dur, S_make_env, beg, dur, S_src_channel);
#else
	  if (base == 1.0)
	    {
	      if (dur == cp->edits[sf->edit_ctr]->samples)
		new_origin = mus_format("%s" PROC_OPEN "%s" PROC_SEP "%" print_mus_long PROC_SEP PROC_FALSE, to_proc_name(S_src_channel), envstr, beg);
	      else new_origin = mus_format("%s" PROC_OPEN "%s" PROC_SEP "%" print_mus_long PROC_SEP "%" print_mus_long, to_proc_name(S_src_channel), envstr, beg, dur);
	    }
	  else new_origin = mus_format("%s" PROC_OPEN BPAREN "%s" PROC_OPEN "%s" PROC_SEP ":base" PROC_SEP "%.4f" PROC_SEP ":end" PROC_SEP "%" print_mus_long ")" PROC_SEP "%" print_mus_long PROC_SEP "%" print_mus_long, 
				       to_proc_name(S_make_env), to_proc_name(S_src_channel), envstr, base, dur, beg, dur);
#endif
	  if (envstr) free(envstr);
	  free_env(newe);
	}

      if (!full_chan)
	{
	  /* here we need delete followed by insert since dur is probably different */
	  if (k == dur)
	    file_change_samples(beg, dur, ofile, cp, 0, DELETE_ME, new_origin, sf->edit_ctr);
	  else
	    {
	      delete_samples(beg, dur, cp, sf->edit_ctr);
	      file_insert_samples(beg, k, ofile, cp, 0, DELETE_ME, new_origin, cp->edit_ctr);
	      if (over_selection)
		reactivate_selection(cp, beg, beg + k); /* backwards compatibility */
	      backup_edit_list(cp);
	      ripple_marks(cp, 0, 0);
	    }
	  update_graph(cp);
	}
      else file_override_samples(k, ofile, cp, 0, DELETE_ME, new_origin);

      if (new_origin) free(new_origin);

      /* not file_change_samples because that would not necessarily change the current file length */
      if (cp->edits[cp->edit_ctr]->marks)
	{
	  if (!egen)
	    src_marks(cp, ratio, dur, k, beg, full_chan);
	  else
	    {
	      if (new_marks) 
		reset_marks(cp, cur_marks, new_marks, beg + dur, (k - dur), full_chan);
	    }
	}

      /* if possible, copy the previous amp env and change the samps_per_bin to reflect ratio */
      if ((full_chan) && (!egen) &&          /* just ratio -- egen is freed by caller */
	  (!(cp->edits[cp->edit_ctr]->peak_env)))   /* can this happen? */
	{
	  peak_env_info *ep;
	  ep = cp->edits[sf->edit_ctr]->peak_env; /* previous peak env (sf is freed by caller) */
	  if (ep)
	    {
	      mus_float_t bratio;
	      int iratio;
	      bratio = ep->samps_per_bin / fabs(ratio);
	      iratio = (int)bratio;
	      if ((bratio - iratio) < .001)
		{
		  peak_env_info *new_ep;
		  new_ep = copy_peak_env_info(ep, (ratio < 0.0)); /* might return NULL if ep but not ep->completed */
		  if (new_ep)
		    {
		      new_ep->samps_per_bin = iratio;
		      cp->edits[cp->edit_ctr]->peak_env = new_ep;
		    }
		}
	    }
	}

      update_graph(cp);
    }
  else
    {
      set_status(sp, "src interrupted", false);
      /* should we remove the temp file here? */
      ss->stopped_explicitly = false;
    }

  if (old_marks) free(old_marks);
  old_marks = NULL;
  if (new_marks) free(new_marks);
  new_marks = NULL;
  free(ofile); 
  ofile = NULL;
  free(data[0]);
  free(data);
  return(NULL);
}


void src_env_or_num(chan_info *cp, env *e, mus_float_t ratio, bool just_num, 
		    const char *origin, bool over_selection, mus_any *gen, Xen edpos, int arg_pos)
{
  snd_info *sp = NULL;
  sync_state *sc;
  sync_info *si;
  snd_fd **sfs;
  int i;
  mus_long_t scdur;
  int stop_point = 0;
  char *errmsg = NULL;

  if ((!just_num) && (!e) && (!gen)) return;
  if ((just_num) && (ratio == 0.0)) return;

  /* get envelope or src ratio */
  sp = cp->sound;
  /* get current syncd chans */
  sc = get_sync_state(sp, cp, 0, over_selection, 
		      (ratio < 0.0) ? READ_BACKWARD : READ_FORWARD, /* 0->beg, 0->over_selection (ratio = 0.0 if from enved) */ 
		      edpos,
		      origin, arg_pos);      
  if (!sc) return;
  si = sc->si;
  sfs = sc->sfs;
  scdur = sc->dur;

  if (!(ss->stopped_explicitly))
    {
      for (i = 0; i < si->chans; i++)
	{
	  mus_long_t dur;
	  mus_any *egen = NULL;
	  bool clm_err = false;
	  cp = si->cps[i];
	  if (scdur == 0) 
	    dur = to_c_edit_samples(cp, edpos, origin, arg_pos); 
	  else dur = scdur;
	  if (dur == 0) 
	    {
	      sfs[i] = free_snd_fd(sfs[i]); 
	      continue;
	    }
	  if (!just_num)
	    {
	      if (e)
		egen = mus_make_env(e->data, e->pts, 1.0, 0.0, e->base, 0.0, dur - 1, NULL);
	      else egen = gen;
	      if (egen) ratio = 0.0;            /* added 14-Mar-01 otherwise the envelope is an offset? */
	    }
	  errmsg = src_channel_with_error(cp, sfs[i], si->begs[i], dur, ratio, egen, origin, over_selection, &clm_err);
	  if (egen)
	    {
	      if (e) 
		mus_free(egen);
	      else mus_reset(gen);
	    }
	  if (errmsg) break;
	  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 = false;
      for (i = 0; i <= stop_point; i++)
	{
	  cp = si->cps[i];
	  undo_edit(cp, 1);
	}
    }
  for (i = 0; i < si->chans; i++) 
    free_snd_fd(sfs[i]);
  free_sync_state(sc);
  if (errmsg)
    snd_error_without_format(errmsg);
}


static mus_float_t input_as_needed(void *arg, int dir) 
{
  return(mus_readin((mus_any *)arg));
}


void src_file(const char *file, double ratio)
{
  mus_any **rds, **srcs;
  char *temp_out;
  const char *comment;
  int k, chan, chans, width, out_fd, buffer_size;
  mus_sample_t sample_type;
  mus_header_t header_type;
  mus_long_t samp, old_samps, new_samps;
  mus_float_t old_srate, new_srate;
  mus_float_t **obufs;

  old_srate = mus_srate();
  new_srate = mus_sound_srate(file); /* need have no connection with previous CLM srate setting */
  mus_set_srate(new_srate);

  chans = mus_sound_chans(file);
  sample_type = mus_sound_sample_type(file);
  header_type = mus_sound_header_type(file);
  comment = mus_sound_comment(file);
  buffer_size = mus_file_buffer_size();
  old_samps = mus_sound_framples(file);
  new_samps = old_samps / ratio;  /* old-srate/new-srate in-coming */

  width = sinc_width(ss);
  if (width < 32) width = 32;

  temp_out = snd_tempnam();
  out_fd = mus_sound_open_output(temp_out, new_srate, chans, sample_type, header_type, comment);

  srcs = (mus_any **)malloc(chans * sizeof(mus_any *));
  rds = (mus_any **)malloc(chans * sizeof(mus_any *));
  obufs = (mus_float_t **)malloc(chans * sizeof(mus_float_t *));

  for (chan = 0; chan < chans; chan++)
    {
      rds[chan] = mus_make_readin(file, chan, 0, 1);
      srcs[chan] = mus_make_src(NULL, ratio, width, (void *)rds[chan]);
      obufs[chan] = (mus_float_t *)malloc(buffer_size * sizeof(mus_float_t));
    }

  for (k = 0, samp = 0; samp < new_samps; samp++)
    {
      for (chan = 0; chan < chans; chan++)
	obufs[chan][k] = (mus_src(srcs[chan], 0.0, &input_as_needed));
      k++;
      if (k == buffer_size)
	{
	  mus_sound_write(out_fd, 0, buffer_size - 1, chans, obufs);
	  k = 0;
	}
    }
  if (k > 0) 
    mus_sound_write(out_fd, 0, k - 1, chans, obufs);

  mus_sound_close_output(out_fd, new_samps * chans * mus_bytes_per_sample(sample_type));
  mus_sound_forget(file);

  for (chan = 0; chan < chans; chan++)
    {
      free(obufs[chan]);
      mus_free(srcs[chan]);
      mus_free(rds[chan]);
    }
  free(obufs);
  free(srcs);
  free(rds);

  move_file(temp_out, file);
  free(temp_out);
  mus_set_srate(old_srate);
}


/* FIR filtering */

static mus_float_t *get_filter_coeffs(int order, env *e)
{
  /* interpret e as frequency response */
  mus_float_t *a = NULL, *fdata;
  if (!e) return(NULL);

  /* get the frequency envelope and design the FIR filter */
  fdata = sample_linear_env(e, order);
  if (!fdata) return(NULL);
  a = (mus_float_t *)calloc(order + 1, sizeof(mus_float_t));

  mus_make_fir_coeffs(order, fdata, a);

  free(fdata);
  return(a);
}


void display_frequency_response(env *e, axis_info *ap, graphics_context *gax, int order, bool dBing)
{
  /* not cp->min_dB here -- this is sound panel related which refers to ss->min_dB */
  mus_float_t *coeffs = NULL;
  int height, width, i, pts, x1, y1;
  mus_float_t samps_per_pixel, invpts, resp, pix;
  int fsize, j;
  mus_float_t step, fx;
  mus_float_t *rl, *im;

  if (order & 1) order++;

  height = (ap->y_axis_y1 - ap->y_axis_y0);
  width = (ap->x_axis_x1 - ap->x_axis_x0);
  pts = order * 4;
  if (pts > width) pts = width;
  if (pts <= 0) pts = 1;
  invpts = 1.0 / (mus_float_t)pts;
  samps_per_pixel = (mus_float_t)(ap->x_axis_x1 - ap->x_axis_x0) * invpts;

  coeffs = get_filter_coeffs(order, e);
  if (!coeffs) return;

  fsize = 2 * snd_to_int_pow2((pts > order) ? pts : order); /* *2 for 1/2 frqs */
  rl = (mus_float_t *)calloc(fsize, sizeof(mus_float_t));
  im = (mus_float_t *)calloc(fsize, sizeof(mus_float_t));
  for (i = 0, j = order - 1; i < order / 2; i++, j -= 2) rl[j] = coeffs[i]; /* by 2 from 1 for 1/2 bins */

  mus_fft(rl, im, fsize, -1);

  resp = 2 * rl[0];
  if (dBing)
    y1 = (int)(ap->y_axis_y0 + (min_dB(ss) - in_dB(min_dB(ss), ss->lin_dB, resp)) * height / min_dB(ss));
  else y1 = (int)(ap->y_axis_y0 + resp * height);
  x1 = ap->x_axis_x0;
  step = (mus_float_t)(fsize - 1) / (4 * (mus_float_t)pts); /* fsize-1 since we got 1 already, *4 due to double size fft */
  for (i = 1, pix = x1, fx = step; 
       i < pts; 
       i++, pix += samps_per_pixel, fx += step)
    {
      int fxi, x0, y0;
      x0 = x1;
      y0 = y1;
      x1 = (int)(pix);
      fxi = (int)fx;
      resp = 2 * (rl[fxi] + (fx - fxi) * (rl[fxi + 1] - rl[fxi]));
      if (resp < 0.0) resp = -resp;
      if (dBing)
	y1 = (int)(ap->y_axis_y0 + (min_dB(ss) - in_dB(min_dB(ss), ss->lin_dB, resp)) * height / min_dB(ss));
      else y1 = (int)(ap->y_axis_y0 + resp * height);
      draw_line(gax, x0, y0, x1, y1);
    }

  free(rl);
  free(im);
  free(coeffs);
}


static char *clm_channel(chan_info *cp, mus_any *gen, mus_long_t beg, mus_long_t dur, int edpos, mus_long_t overlap, const char *origin)
{
  /* calls gen over cp[beg for dur] data, replacing. */
  snd_info *sp;
  file_info *hdr = NULL;
  int j = 0, ofd = 0, datumb = 0;
  bool temp_file;
  mus_long_t k, alloc_len;
  mus_float_t **data;
  mus_float_t *idata;
  char *ofile = NULL;
  snd_fd *sf;
  mus_float_t (*runf)(mus_any *gen, mus_float_t arg1, mus_float_t arg2);

  if ((beg < 0) || ((dur + overlap) <= 0)) return(NULL);
  sp = cp->sound;
  if (!(is_editable(cp))) return(NULL);

  sf = init_sample_read_any(beg, cp, READ_FORWARD, edpos);
  if (!sf)
    return(mus_format("%s can't read %s[%d] channel data!", S_clm_channel, sp->short_filename, cp->chan));
  runf = mus_run_function(gen);

  if ((dur + overlap) > REPORTING_SIZE)
    {
      io_error_t io_err = IO_NO_ERROR;
      alloc_len = REPORTING_SIZE;
      temp_file = true; 
      ofile = snd_tempnam();
      hdr = make_temp_header(ofile, snd_srate(sp), 1, dur + overlap, S_clm_channel);
      ofd = open_temp_file(ofile, 1, hdr, &io_err);
      if (ofd == -1)
	{
	  free_snd_fd(sf); 
	  return(mus_format("%s %s temp file %s: %s\n", 
			    (io_err != IO_NO_ERROR) ? io_error_name(io_err) : "can't open",
			    S_clm_channel, ofile, 
			    snd_open_strerror()));
	}
      datumb = mus_bytes_per_sample(hdr->sample_type);
    }
  else 
    {
      temp_file = false;
      alloc_len = dur + overlap;
    }

  data = (mus_float_t **)malloc(sizeof(mus_float_t *));
  data[0] = (mus_float_t *)calloc(alloc_len, sizeof(mus_float_t)); 
  idata = data[0];

  if (temp_file)
    {
      sampler_set_safe(sf, dur);
      for (k = 0; k < dur; k += alloc_len)
	{
	  int n, err;
	  j = dur - k;
	  if (j > alloc_len) j = alloc_len;

	  for (n = 0; n < j; n++)
	    idata[n] = runf(gen, read_sample(sf), 0.0);

	  err = mus_file_write(ofd, 0, j - 1, 1, data);
	  j = 0;
	  if (err != MUS_NO_ERROR) break;
	}
    }
  else
    {
      mus_float_t (*runf1)(mus_any *gen, mus_float_t arg);
      samples_to_vct_with_reader(dur, idata, sf);

      runf1 = mus_run1_function(gen);
      if (runf1)
	{
	  for (k = 0; k < dur; k++)
	    idata[k] = runf1(gen, idata[k]);
	}
      else
	{
	  for (k = 0; k < dur; k++)
	    idata[k] = runf(gen, idata[k], 0.0);
	}
      j = (int)dur;
    }

  if (overlap > 0)
    {
      snd_fd *fd;
      fd = init_sample_read_any_with_bufsize(beg + dur, cp, READ_FORWARD, edpos, overlap);
      for (k = 0; k < overlap; k++)
	idata[j++] = runf(gen, 0.0, 0.0) + read_sample(fd);
      free_snd_fd(fd);
      dur += overlap;
    }

  free_snd_fd(sf);

  if (temp_file)
    {
      if (j > 0) mus_file_write(ofd, 0, j - 1, 1, data);
      close_temp_file(ofile, ofd, hdr->type, dur * datumb);
      free_file_info(hdr);
      file_change_samples(beg, dur, ofile, cp, 0, DELETE_ME, origin, edpos);
      if (ofile) 
	{
	  free(ofile); 
	  ofile = NULL;
	}
    }
  else 
    {
      if (dur > 0) 
	change_samples(beg, dur, idata, cp, origin, edpos, -1.0);
    }
  update_graph(cp); 
  free(data[0]);
  free(data);
  return(NULL);
}

#define TWO_30 1073741824
#define MAX_SINGLE_FFT_SIZE 1048576

static mus_float_t convolve_next_sample(void *ptr, int dir)
{
  return(read_sample(((snd_fd *)ptr)));
}



static char *convolution_filter(chan_info *cp, int order, env *e, snd_fd *sf, mus_long_t beg, mus_long_t dur, 
				const char *origin, mus_float_t *precalculated_coeffs)
{
  snd_info *sp;
  file_info *hdr = NULL;
  int ofd = 0, datumb = 0;
  char *ofile = NULL;
  int fsize;
  mus_float_t *fltdat = NULL;
  io_error_t io_err = IO_NO_ERROR;

  if (!(is_editable(cp))) return(NULL);
  sp = cp->sound;

  dur += order;
  if (dur < TWO_30)
    fsize = snd_to_int_pow2(dur);
  else fsize = TWO_30;

  ofile = snd_tempnam();
  hdr = make_temp_header(ofile, snd_srate(sp), 1, dur, (char *)origin);

#if MUS_LITTLE_ENDIAN
  if (sizeof(mus_float_t) == 4)
    hdr->sample_type = MUS_LFLOAT;
  else hdr->sample_type = MUS_LDOUBLE;
#else
  if (sizeof(mus_float_t) == 4)
    hdr->sample_type = MUS_BFLOAT;
  else hdr->sample_type = MUS_BDOUBLE;
#endif

  ofd = open_temp_file(ofile, 1, hdr, &io_err);
  if (ofd == -1)
    {
      return(mus_format("%s %s temp file %s: %s\n", 
			(io_err != IO_NO_ERROR) ? io_error_name(io_err) : "can't open",
			origin, ofile, 
			snd_open_strerror()));
    }

  if (fsize > MAX_SINGLE_FFT_SIZE)
    {
      /* set up convolution generator and run overlap-add in order-sized blocks */
      bool reporting;
      mus_any *gen;
      mus_float_t **data;
      mus_float_t *idata;
      mus_long_t alloc_len;
      int j;

      reporting = ((sp) && (dur > REPORTING_SIZE) && (!(cp->squelch_update)));
      if (order == 0) order = 65536; /* presumably fsize is enormous here, so no MIN needed */
      if (!(is_power_of_2(order)))
	order = snd_to_int_pow2(order);
      fsize = 2 * order; /* need room for convolution */
      if (precalculated_coeffs)
	fltdat = precalculated_coeffs;
      else fltdat = get_filter_coeffs(order, e);

      gen = mus_make_convolve(convolve_next_sample, fltdat, fsize, order, (void *)sf);

      if (dur > MAX_BUFFER_SIZE)
	alloc_len = MAX_BUFFER_SIZE;
      else alloc_len = dur;

      data = (mus_float_t **)malloc(sizeof(mus_float_t *));
      data[0] = (mus_float_t *)malloc(alloc_len * sizeof(mus_float_t)); 
      idata = data[0];

      if (reporting) start_progress_report(cp);
      ss->stopped_explicitly = false;

      if (alloc_len == dur)
	{
	  for (j = 0; j < dur; j++)
	    idata[j] = mus_convolve(gen, NULL);
	  mus_file_write(ofd, 0, dur - 1, 1, data);
	}
      else
	{
	  mus_long_t offk;
	  for (offk = 0; offk < dur; offk += alloc_len)
	    {
	      mus_long_t kdur;
	      kdur = dur - offk;
	      if (kdur > alloc_len) kdur = alloc_len;
	      for (j = 0; j < kdur; j++)
		idata[j] = mus_convolve(gen, NULL);
	      mus_file_write(ofd, 0, kdur - 1, 1, data);
	      if (reporting)
		{
		  progress_report(cp, (mus_float_t)((double)offk / (double)dur));
		  if (ss->stopped_explicitly) break;
		  if (!(sp->active))
		    {
		      ss->stopped_explicitly = true;
		      break;
		    }
		}
	    }
	}
      if (reporting) finish_progress_report(cp);
      close_temp_file(ofile, ofd, hdr->type, dur * datumb);
      if (!(ss->stopped_explicitly))
	file_change_samples(beg, dur, ofile, cp, 0, DELETE_ME, origin, sf->edit_ctr);
      else 
	{
	  set_status(sp, "filter interrupted", false);
	  ss->stopped_explicitly = false;
	}
      mus_free(gen);
      free(data[0]);
      free(data);
    }
  else
    {
      /* we think there's enough memory to do the entire thing in one pass */
      if (precalculated_coeffs)
	fltdat = precalculated_coeffs;
      else fltdat = sample_linear_env(e, fsize);
      if (fltdat)
	{
	  mus_float_t *sndrdat, *sndidat;
	  mus_float_t scale;
	  mus_long_t k;
	  ssize_t bytes;

	  sndrdat = (mus_float_t *)calloc(fsize, sizeof(mus_float_t));
	  sndidat = (mus_float_t *)calloc(fsize, sizeof(mus_float_t));

	  samples_to_vct_with_reader(dur, sndrdat, sf);
	  mus_fft(sndrdat, sndidat, fsize, 1);
	  scale = 1.0 / (mus_float_t)fsize;
	  for (k = 0; k < fsize; k++)
	    {
	      double scl;
	      scl = scale * fltdat[k];
	      sndrdat[k] *= scl;         /* fltdat is already reflected around midpoint */
	      sndidat[k] *= scl;
	    }
	  mus_fft(sndrdat, sndidat, fsize, -1);

	  bytes = write(ofd, sndrdat, fsize * sizeof(mus_float_t));
	  close_temp_file(ofile, ofd, hdr->type, fsize * sizeof(mus_float_t));
	  if (bytes != 0)
	    file_change_samples(beg, dur + order, ofile, cp, 0, DELETE_ME, origin, sf->edit_ctr);
	  else set_status(sp, "can't write data?", false);

	  free(sndrdat);
	  free(sndidat);
	}
      else 
	{
	  close_temp_file(ofile, ofd, hdr->type, 0);
	  snd_remove(ofile, REMOVE_FROM_CACHE);
	}
    }
  if (ofile) {free(ofile); ofile = NULL;}
  free_file_info(hdr);
  if ((fltdat) && (!precalculated_coeffs))  free(fltdat);
  update_graph(cp);
  return(NULL);
}

static char *direct_filter(chan_info *cp, int order, env *e, snd_fd *sf, mus_long_t beg, mus_long_t dur, 
			   const char *origin, bool truncate,
			   bool over_selection, mus_any *gen, mus_float_t *precalculated_coeffs)
{
  mus_float_t *fcoeffs = NULL;
  snd_info *sp;
  bool reporting = false;
  mus_long_t offk;
  file_info *hdr = NULL;
  int j = 0, ofd = 0, datumb = 0, err = 0;
  bool temp_file;
  char *new_origin = NULL;
  mus_float_t **data;
  mus_float_t *idata;
  char *ofile = NULL;
  io_error_t io_err = IO_NO_ERROR;
  mus_any *g = NULL;
  mus_float_t (*runf)(mus_any *gen, mus_float_t arg1, mus_float_t arg2);

  if (!(is_editable(cp))) return(NULL);
  sp = cp->sound;
  if ((!over_selection) || (!truncate))
    dur += order;
  /* if over-selection this causes it to clobber samples beyond the selection end -- maybe mix? */
  reporting = ((sp) && (dur > REPORTING_SIZE) && (!(cp->squelch_update)));
  if (reporting) start_progress_report(cp);

  if (!gen)
    {
      if (precalculated_coeffs)
	fcoeffs = precalculated_coeffs;
      else 
	{
	  if (order & 1) order++;
	  fcoeffs = get_filter_coeffs(order, e);
	  if (!fcoeffs) return(NULL);
	}
    }

  if (dur > MAX_BUFFER_SIZE)
    {
      temp_file = true; 
      ofile = snd_tempnam();
      hdr = make_temp_header(ofile, snd_srate(sp), 1, dur, (char *)origin);
      ofd = open_temp_file(ofile, 1, hdr, &io_err);
      if (ofd == -1)
	{
	  return(mus_format("%s %s temp file %s: %s\n", 
			    (io_err != IO_NO_ERROR) ? io_error_name(io_err) : "can't open",
			    origin, ofile, 
			    snd_open_strerror()));
	}
      datumb = mus_bytes_per_sample(hdr->sample_type);
    }
  else temp_file = false;

  data = (mus_float_t **)malloc(sizeof(mus_float_t *));
  data[0] = (mus_float_t *)malloc(MAX_BUFFER_SIZE * sizeof(mus_float_t)); 
  idata = data[0];

  sampler_set_safe(sf, dur);

  if (gen)
    {
      mus_reset(gen);
      g = gen;
    }
  else
    {
      g = mus_make_fir_filter(order, fcoeffs, NULL);
      if (over_selection)
	{
	  int m;
	  mus_long_t prebeg = 0;
	  /* see if there's data to pre-load the filter */
	  if (beg >= order)
	    prebeg = order - 1;
	  else prebeg = beg;
	  if (prebeg > 0)
	    for (m = (int)prebeg; m > 0; m--)
	      mus_fir_filter(g, read_sample(sf));
	}
    }
  if ((over_selection) && (!truncate))
    dur -= order;

  runf = mus_run_function(g);
  if (!temp_file)
    {
      for (j = 0; j < dur; j++)
	idata[j] = runf(g, read_sample(sf), 0.0);
    }
  else
    {
      for (offk = 0; offk < dur; offk += MAX_BUFFER_SIZE)
	{
	  mus_long_t kdur;
	  kdur = dur - offk;
	  if (kdur > MAX_BUFFER_SIZE) kdur = MAX_BUFFER_SIZE;

	  for (j = 0; j < kdur; j++)
	    idata[j] = runf(g, read_sample(sf), 0.0);

	  err = mus_file_write(ofd, 0, j - 1, 1, data);
	  if (err != MUS_NO_ERROR) break;
	  if (reporting) 
	    {
	      progress_report(cp, (mus_float_t)((double)offk / (double)dur));
	      if (ss->stopped_explicitly) return(NULL);
	      if (!(sp->active))
		{
		  ss->stopped_explicitly = true;
		  break;
		}
	    }
	}
    }

  if ((over_selection) && (!truncate))
    {
      snd_fd *sfold;
      sfold = init_sample_read_any(beg + dur, cp, READ_FORWARD, sf->edit_ctr);
      for (offk = 0; offk < order; offk++)
	{
	  idata[j] = runf(g, read_sample(sf), 0.0) + read_sample(sfold);
	  j++;
	  if ((temp_file) && (j == MAX_BUFFER_SIZE))
	    {
	      err = mus_file_write(ofd, 0, j - 1, 1, data);
	      j = 0;
	      if (err != MUS_NO_ERROR) break;
	    }
	}
      dur += order;
      free_snd_fd(sfold);
    }

  if (reporting) finish_progress_report(cp);
  if ((origin) && (!mus_strcmp(origin, S_filter_channel)))
    new_origin = mus_strdup(origin);
  else
    {
      if (precalculated_coeffs)
	{
	  vct *v;
	  char *vstr;

	  v = mus_vct_wrap(order, precalculated_coeffs);
	  vstr = mus_vct_to_readable_string(v);

#if HAVE_FORTH
	  if (dur == (order + cp->edits[sf->edit_ctr]->samples))
	    new_origin = mus_format("%s %d %" print_mus_long PROC_SEP PROC_FALSE " %s", vstr, order, beg, S_filter_channel);
	  else new_origin = mus_format("%s %d %" print_mus_long PROC_SEP "%" print_mus_long " %s", vstr, order, beg, dur, S_filter_channel);
#else
	  if (dur == (order + cp->edits[sf->edit_ctr]->samples))
	    new_origin = mus_format("%s" PROC_OPEN "%s" PROC_SEP "%d" PROC_SEP "%" print_mus_long PROC_SEP PROC_FALSE, 
				    to_proc_name(S_filter_channel), vstr, order, beg);
	  else new_origin = mus_format("%s" PROC_OPEN "%s" PROC_SEP "%d" PROC_SEP "%" print_mus_long PROC_SEP "%" print_mus_long, 
				       to_proc_name(S_filter_channel), vstr, order, beg, dur);
#endif

	  if (vstr) free(vstr);
#if (!HAVE_SCHEME)
	  mus_vct_free(v);
#endif
	}
      else
	{
	  /* new_origin = filter-channel + envelope */
	  char *envstr;
	  envstr = env_to_string(e);

#if HAVE_FORTH
	  if (dur == (order + cp->edits[sf->edit_ctr]->samples))
	    new_origin = mus_format("%s %d %" print_mus_long PROC_SEP PROC_FALSE " %s", envstr, order, beg, S_filter_channel);
	  else new_origin = mus_format("%s %d %" print_mus_long PROC_SEP "%" print_mus_long " %s", envstr, order, beg, dur, S_filter_channel);
#else
	  if (dur == (order + cp->edits[sf->edit_ctr]->samples))
	    new_origin = mus_format("%s" PROC_OPEN "%s" PROC_SEP "%d" PROC_SEP "%" print_mus_long PROC_SEP PROC_FALSE, 
				    to_proc_name(S_filter_channel), envstr, order, beg);
	  else new_origin = mus_format("%s" PROC_OPEN "%s" PROC_SEP "%d" PROC_SEP "%" print_mus_long PROC_SEP "%" print_mus_long, 
				       to_proc_name(S_filter_channel), envstr, order, beg, dur);
#endif
	  if (envstr) free(envstr);
	}
    }

  if (temp_file)
    {
      if (j > 0) mus_file_write(ofd, 0, j - 1, 1, data);
      close_temp_file(ofile, ofd, hdr->type, dur * datumb);
      free_file_info(hdr);
      file_change_samples(beg, dur, ofile, cp, 0, DELETE_ME, new_origin, sf->edit_ctr);
      if (ofile) {free(ofile); ofile = NULL;}
    }
  else change_samples(beg, dur, data[0], cp, new_origin, sf->edit_ctr, -1.0);
  if (new_origin) free(new_origin);

  update_graph(cp); 

  free(data[0]);
  free(data);
  if (!gen) mus_free(g);
  if ((fcoeffs) && (!precalculated_coeffs)) free(fcoeffs);
  return(NULL);
}


static char *filter_channel(chan_info *cp, int order, env *e, mus_long_t beg, mus_long_t dur, int edpos, const char *origin, bool truncate, mus_float_t *coeffs)
{
  bool over_selection;
  snd_fd *sf;
  char *errstr = NULL;
  if ((order == 1) && (coeffs) && (!e))
    {
      /* a silly optimization... */
      if ((coeffs[0] != 1.0) || (edpos != cp->edit_ctr))
	scale_channel(cp, coeffs[0], beg, dur, edpos, NOT_IN_AS_ONE_EDIT);
      return(NULL);
    }
  over_selection = ((beg != 0) || (dur < cp->edits[edpos]->samples));
  sf = init_sample_read_any(beg, cp, READ_FORWARD, edpos);

  if ((!over_selection) &&
      ((order == 0) || 
       (order >= 128)))
    errstr = convolution_filter(cp, order, e, sf, beg, dur, origin, coeffs);
  else  errstr = direct_filter(cp, order, e, sf, beg, dur, origin, truncate, over_selection, NULL, coeffs);

  free_snd_fd(sf);
  return(errstr);
}


static char *apply_filter_or_error(chan_info *ncp, int order, env *e, 
				   const char *caller, const char *origin, bool over_selection, mus_float_t *ur_a, 
				   mus_any *gen, Xen edpos, int arg_pos, bool truncate, bool *clm_error)
{
  /* if string returned, needs to be freed */
  /* interpret e as frequency response and apply as filter to all sync'd chans */
  sync_state *sc;
  sync_info *si;
  snd_info *sp;
  int i, stop_point = 0;
  mus_long_t scdur, dur;
  snd_fd **sfs;
  chan_info *cp;
  char *errstr = NULL;

  if ((!e) && (!ur_a) && (!gen)) 
    return(NULL);

  if ((gen) && (!(mus_run_function(gen))))
    {
      (*clm_error) = true;
      return(mus_format("%s: can't handle %s generators",
			caller,
			mus_name(gen)));
    }

  sp = ncp->sound;
  sc = get_sync_state_1(sp, ncp, 0, over_selection, 
			READ_FORWARD, (over_selection) ? (order - 1) : 0, 
			edpos,
			caller, arg_pos);
  if (!sc) return(NULL);

  si = sc->si;
  sfs = sc->sfs;
  scdur = sc->dur;
  ss->stopped_explicitly = false;

  if ((!ur_a) && 
      (!gen) && 
      (!over_selection) &&
      ((order == 0) || (order >= 128)))
    {
      /* use convolution if order is large and not over_selection */
      for (i = 0; i < si->chans; i++)
	{
	  cp = si->cps[i];
	  sp = cp->sound;
	  if (!(sp->active)) continue;
	  if (scdur == 0) 
	    dur = to_c_edit_samples(cp, edpos, caller, arg_pos);
	  else dur = scdur;
	  if (dur == 0) 
	    {
	      sfs[i] = free_snd_fd(sfs[i]);
	      continue;
	    }

	  errstr = convolution_filter(cp, order, e, sfs[i], si->begs[i], dur, (origin) ? origin : caller, NULL);

	  sfs[i] = free_snd_fd(sfs[i]);
	  check_for_event();
	  if ((errstr) || (ss->stopped_explicitly))
	    {
	      stop_point = i;
	      break;
	    }
	}
    }
  else
    {
      /* use FIR filter */
      mus_float_t *a = NULL;
      if (order == 0) order = enved_filter_order(ss);
      if (!gen)
	{
	  if (ur_a)
	    a = ur_a;
	  else 
	    {
	      if (order & 1) order++;
	      a = get_filter_coeffs(order, e);
	    }
	  if (!a) return(NULL);
	}
      /* now filter all currently sync'd chans (one by one) */
      /* for each decide whether a file or internal array is needed, scale, update edit tree */
      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];
	      if (scdur == 0) 
		dur = to_c_edit_samples(cp, edpos, caller, arg_pos);
	      else dur = scdur;
	      if (dur == 0) 
		{
		  sfs[i] = free_snd_fd(sfs[i]);
		  continue;
		}

	      errstr = direct_filter(cp, order, e, sfs[i], si->begs[i], dur,
				     (origin) ? origin : caller, truncate, over_selection,
				     gen, a);

	      sfs[i] = free_snd_fd(sfs[i]);
	      if ((errstr) || (ss->stopped_explicitly))
		{
		  stop_point = i;
		  break;
		}
	    }
	}
      if ((a) && (!ur_a)) free(a);
    }

  if (ss->stopped_explicitly)
    {
      /* clean up and undo all edits up to stop_point */
      set_status(sp, "filter stopped", false);
      ss->stopped_explicitly = false;
      for (i = 0; i <= stop_point; i++)
	{
	  cp = si->cps[i];
	  undo_edit(cp, 1);
	}
    }
  free_sync_state(sc);
  return(errstr);
}


void apply_filter(chan_info *ncp, int order, env *e,
		  const char *caller, const char *origin, bool over_selection, mus_float_t *ur_a, mus_any *gen, 
		  Xen edpos, int arg_pos, bool truncate)
{
  char *error;
  bool err_type; /* ignored in this context */
  error = apply_filter_or_error(ncp, order, e, caller, origin, over_selection, ur_a, gen, edpos, arg_pos, truncate, &err_type);
  if (error)
    {
      snd_error_without_format(error);
      free(error);
    }
}

static char *edit_list_envelope(mus_any *egen, mus_long_t beg, mus_long_t env_dur, mus_long_t called_dur, mus_long_t chan_dur, mus_float_t base)
{
  char *new_origin, *envstr;
  env *newe;

  newe = make_envelope_with_offset_and_scaler(mus_data(egen), mus_env_breakpoints(egen) * 2, mus_offset(egen), mus_scaler(egen));
  /* mus_env_offset|scaler are the fixed up versions, the originals are mus_offset|scaler.  mus_data is the original data */

  envstr = env_to_string(newe);
  if (((env_dur == chan_dur) || (env_dur == (chan_dur - 1))) &&
      (called_dur == chan_dur))
    {
#if HAVE_FORTH
      if (base == 1.0)
	new_origin = mus_format("%s %" print_mus_long PROC_SEP PROC_FALSE " %s", envstr, beg, S_env_channel);
      else new_origin = mus_format("%s %.4f %" print_mus_long PROC_SEP PROC_FALSE " %s", 
				   envstr, base, beg, S_env_channel_with_base);
#else
      if (base == 1.0)
	new_origin = mus_format("%s" PROC_OPEN "%s" PROC_SEP "%" print_mus_long PROC_SEP PROC_FALSE, 
				to_proc_name(S_env_channel), envstr, beg);
      else new_origin = mus_format("%s" PROC_OPEN "%s" PROC_SEP "%.4f" PROC_SEP "%" print_mus_long PROC_SEP PROC_FALSE, 
				   to_proc_name(S_env_channel_with_base), envstr, base, beg);
#endif
    }
  else 
    {
      /* env dur was apparently not chan dur, or called dur was not full sound? */
#if HAVE_FORTH
      new_origin = mus_format("%s :base %.4f :end %" print_mus_long " %s %" print_mus_long PROC_SEP "%" print_mus_long " %s",
			      envstr, base, env_dur, S_make_env, beg, called_dur, S_env_channel);
#else
      new_origin = mus_format("%s" PROC_OPEN BPAREN "%s" PROC_OPEN "%s" PROC_SEP ":base" PROC_SEP "%.4f" PROC_SEP ":end" PROC_SEP "%" print_mus_long ")" PROC_SEP "%" print_mus_long PROC_SEP "%" print_mus_long,
			      to_proc_name(S_env_channel), to_proc_name(S_make_env), envstr, base, env_dur, beg, called_dur);
#endif
    }
  if (envstr) free(envstr);
  free_env(newe);
  return(new_origin);
}


void apply_env(chan_info *cp, env *e, mus_long_t beg, mus_long_t dur, bool over_selection, 
	       const char *origin, mus_any *gen, Xen edpos, int arg_pos)
{
  /* basic cases: if env has 1 y value, use scale-channel,
   *              if step env (base == 0.0), use sequence of scale-channels,
   *              if not optimizable (via virtual edits), call mus_env on each sample
   *              if optimizable, use sequence of (x)ramp-channels
   */
  /* e can be NULL => use gen */
  snd_info *sp;
  sync_info *si;
  sync_state *sc = NULL;
  int i, j, k, len;
  bool scalable = true, rampable = true, is_xramp = false;
  mus_float_t val[1];
  mus_any *egen;
  mus_long_t *passes;
  mus_float_t *rates;
  mus_float_t base;
  mus_float_t scaler, offset;

  if ((!e) && (!gen)) return;
  if (over_selection) dur = selection_len();
  if (dur <= 0) return;
  if (e)
    {
      if (e->pts == 0) return;
      val[0] = e->data[1];                            /* ok because no possibility of scaler/offset here */
      for (i = 1, j = 2; i < e->pts; i++, j += 2)
	if (e->data[j + 1] != val[0]) 
	  {
	    scalable = false; 
	    break;
	  }
      if ((scalable) && 
	  (beg == 0))
	{
	  int pos;
	  pos = to_c_edit_position(cp, edpos, origin, arg_pos);
	  if ((cp->edit_ctr == pos) &&
	      (dur >= cp->edits[pos]->samples))
	    {
	      scale_by(cp, val, 1, over_selection);
	      return;
	    }
	}
    }
  else scalable = false;

  si = NULL;
  sp = cp->sound;
  if (scalable) /* only true if e (not gen) and all vals are equal and not full chan (latter case handled above) */
    {
      /* ---------------- use scale-channel ---------------- */
      sc = get_sync_state_without_snd_fds(sp, cp, beg, over_selection);
      if (!sc) return;
      si = sc->si;
      for (i = 0; i < si->chans; i++) 
	{
	  if (over_selection)
	    scale_channel(si->cps[i], 
			  val[0], 
			  si->begs[i], 
			  selection_end(si->cps[i]) - si->begs[i] + 1, 
			  to_c_edit_position(si->cps[i], edpos, origin, arg_pos),
			  NOT_IN_AS_ONE_EDIT);
	  else scale_channel(si->cps[i], val[0], si->begs[i], dur, 
			     to_c_edit_position(si->cps[i], edpos, origin, arg_pos),
			     NOT_IN_AS_ONE_EDIT);
	}
      free_sync_state(sc);
      return;
    }

  if (e)
    egen = mus_make_env(e->data, e->pts, 1.0, 0.0, e->base, 0.0, dur - 1, NULL);
  else egen = gen;
  len = mus_env_breakpoints(egen);
  passes = mus_env_passes(egen);
  rates = mus_env_rates(egen);
  scaler = mus_env_scaler(egen); /* fixed-up versions if base != 1.0 */
  offset = mus_env_offset(egen);
  base = mus_increment(egen);

  if (base == 0.0) 
    {
      /* ---------------- step env -- handled as sequence of scalings ---------------- */
      int local_edpos, pos;
      mus_long_t segbeg, segnum, segend;
      /* base == 0 originally, so it's a step env */
      sc = get_sync_state_without_snd_fds(sp, cp, beg, over_selection);
      if (!sc) 
	{
	  if (e) mus_free(egen);
	  return;
	}
      si = sc->si;
      for (i = 0; i < si->chans; i++) 
	{
	  bool edited = false;
	  if (!(is_editable(si->cps[i]))) continue;
	  segbeg = si->begs[i];
	  segend = si->begs[i] + dur;
	  segnum = passes[0] + 1;
	  local_edpos = si->cps[i]->edit_ctr; /* for as_one_edit backup */
	  pos = to_c_edit_position(si->cps[i], edpos, origin, arg_pos);
	  for (k = 0; k < len; k++)
	    {
	      if ((segbeg + segnum) > segend) 
		segnum = segend - segbeg;
	      else
		if ((k == (len - 1)) && 
		    ((segbeg + segnum) < segend))
		  segnum = segend - segbeg; /* last value is sticky in envs */
	      if (segnum > 0)
		{
		  if (scale_channel(si->cps[i], (mus_float_t)(offset + scaler * rates[k]), segbeg, segnum, pos, IN_AS_ONE_EDIT))
		    edited = true;
		  pos = si->cps[i]->edit_ctr;
		}
	      segbeg += segnum;
	      if (segbeg >= segend) break;
	      segnum = passes[k + 1] - passes[k];
	    }
	  if (edited)
	    {
	      as_one_edit(si->cps[i], local_edpos + 1);
	      if (cp->edits[cp->edit_ctr]->origin) free(cp->edits[cp->edit_ctr]->origin);
	      cp->edits[cp->edit_ctr]->origin = edit_list_envelope(egen, si->begs[i], (len > 1) ? (passes[len - 2]) : dur, dur, current_samples(si->cps[i]), base);
	      after_edit(cp);
	      update_graph(si->cps[i]);
	      reflect_edit_history_change(si->cps[i]);
	    }
	}
      free_sync_state(sc);
      if (e) mus_free(egen);
      return;
    }

  /* step env, special env, and degenerate cases are out of the way */
  /* need to use the same sync/selection choice as will be used below! */
  sc = get_sync_state_without_snd_fds(sp, cp, beg, over_selection);
  si = sc->si;
  if (base != 1.0) is_xramp = true;
  for (i = 0; i < si->chans; i++)
    if (unrampable(si->cps[i], si->begs[i], dur, to_c_edit_position(si->cps[i], edpos, origin, arg_pos), is_xramp))
      {
	rampable = false;
	break;
      }
  free_sync_state(sc);

  if (!rampable)
    {
      /* ---------------- not optimizable, so call mus_env on each sample ---------------- */
      mus_long_t ioff, alloc_len;
      mus_float_t **data;
      bool reporting = false, temp_file = false;
      int ofd = 0, datumb = 0;
      file_info *hdr = NULL;
      char *ofile = NULL;
      snd_fd **sfs;

      /* run env over samples */
      sc = get_sync_state(sp, cp, beg, over_selection, READ_FORWARD, edpos, origin, arg_pos);
      if (!sc) 
	{
	  if (e) mus_free(egen);
	  return;
	}
      si = sc->si;
      sfs = sc->sfs;
      if (dur > MAX_BUFFER_SIZE)
	{
	  io_error_t io_err = IO_NO_ERROR;

	  alloc_len = MAX_BUFFER_SIZE;
	  temp_file = true; 
	  ofile = snd_tempnam(); 
	  hdr = make_temp_header(ofile, snd_srate(sp), si->chans, dur, (char *)origin);
	  ofd = open_temp_file(ofile, si->chans, hdr, &io_err);
	  if (ofd == -1)
	    {
	      if (e) mus_free(egen);
	      for (i = 0; i < si->chans; i++) 
		sfs[i] = free_snd_fd(sfs[i]);
	      free_sync_state(sc);
	      if (e) mus_free(egen);
	      snd_error("%s %s temp file %s: %s\n", 
			(io_err != IO_NO_ERROR) ? io_error_name(io_err) : "can't open",
			origin, ofile, 
			snd_open_strerror());
	      free(ofile);
	      return;
	    }
	  datumb = mus_bytes_per_sample(hdr->sample_type);
	}
      else 
	{
	  temp_file = false;
	  alloc_len = dur;
	}

      data = (mus_float_t **)malloc(si->chans * sizeof(mus_float_t *));
      for (i = 0; i < si->chans; i++) 
	data[i] = (mus_float_t *)calloc(alloc_len, sizeof(mus_float_t)); 

      j = 0;
      reporting = ((dur > REPORTING_SIZE) && (!(cp->squelch_update)));
      if (reporting) start_progress_report(cp);
      if (si->chans > 1)
	{
	  ss->stopped_explicitly = false;
	  if (temp_file)
	    {
	      for (ioff = 0; ioff < dur; ioff++)
		{
		  mus_float_t egen_val;
		  egen_val = mus_env(egen);
		  for (k = 0; k < si->chans; k++)
		    data[k][j] = (read_sample(sfs[k]) * egen_val);
		  j++;
		  if (j == alloc_len)
		    {
		      int err;
		      if (reporting)
			{
			  progress_report(cp, (mus_float_t)((double)ioff / ((double)dur)));
			  if (ss->stopped_explicitly) break;
			  if (!(sp->active))
			    {
			      ss->stopped_explicitly = true;
			      break;
			    }
			}
		      err = mus_file_write(ofd, 0, j - 1, si->chans, data);
		      j = 0;
		      if (err != MUS_NO_ERROR) break;
		    }
		}
	    }
	  else
	    {
	      for (k = 0; k < si->chans; k++)
		samples_to_vct_with_reader(dur, data[k], sfs[k]); 
	      for (j = 0; j < dur; j++)
		{
		  mus_float_t egen_val;
		  egen_val = mus_env(egen);
		  for (k = 0; k < si->chans; k++)
		    data[k][j] *= egen_val;
		}
	    }
	}
      else
	{
	  mus_float_t *idata;
	  snd_fd *sf;
	  sf = sfs[0];
	  idata = data[0];
	  if (temp_file)
	    {
	      ss->stopped_explicitly = false;
	      for (ioff = 0; ioff < dur; ioff++)
		{
		  idata[j] = (read_sample(sf) * mus_env(egen));
		  j++;
		  if (j == alloc_len)
		    {
		      int err;
		      if (reporting)
			{
			  progress_report(cp, (mus_float_t)((double)ioff / ((double)dur)));
			  if (ss->stopped_explicitly) break;
			  if (!(sp->active))
			    {
			      ss->stopped_explicitly = true;
			      break;
			    }
			}
		      err = mus_file_write(ofd, 0, j - 1, 1, data);
		      j = 0;
		      if (err != MUS_NO_ERROR) break;
		    }
		}
	    }
	  else
	    {
	      samples_to_vct_with_reader(dur, idata, sf);
	      for (j = 0; j < dur; j++)
		idata[j] *= mus_env(egen);
	    }
	}

      if (temp_file)
	{
	  if (j > 0) mus_file_write(ofd, 0, j - 1, si->chans, data);
	  close_temp_file(ofile, ofd, hdr->type, dur * si->chans * datumb);
	  free_file_info(hdr);
	}

      if (reporting) finish_progress_report(cp);
      if (ss->stopped_explicitly)
	{
	  ss->stopped_explicitly = false;
	  if (temp_file) 
	    snd_remove(ofile, REMOVE_FROM_CACHE);
	}
      else
	{
	  if ((temp_file) && 
	      (si->chans > 1)) 
	    remember_temp(ofile, si->chans);
	  for (i = 0; i < si->chans; i++)
	    {
	      char *new_origin;
	      int pos;
	      pos = to_c_edit_position(si->cps[i], edpos, origin, arg_pos);
	      new_origin = edit_list_envelope(egen, si->begs[i], (len > 1) ? (passes[len - 2]) : dur, dur, current_samples(si->cps[i]), base);
	      if (temp_file)
		{
		  file_change_samples(si->begs[i], dur, ofile, si->cps[i], i, 
				      (si->chans > 1) ? MULTICHANNEL_DELETION : DELETE_ME, 
				      new_origin, pos);
		  if ((si->begs[i] == 0) && (dur == si->cps[i]->edits[pos]->samples))
		    amp_env_env(si->cps[i], mus_data(egen), len, pos, base, scaler, offset);
		  else 
		    {
		      if ((len < 2) || (snd_abs_mus_long_t(dur - passes[len - 2]) < 2))
			amp_env_env_selection_by(si->cps[i], egen, si->begs[i], dur, pos);
		    }

		}
	      else change_samples(si->begs[i], dur, data[i], si->cps[i], new_origin, pos, -1.0);
	      free(new_origin);
	      update_graph(si->cps[i]);
	    }
	}
      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;}
      if (data) free(data);
    }
  else 
    {
      /* ---------------- optimizable -- treat env as a sequence of virtual (x)ramps and scalings (if slope=0) ---------------- */
      int local_edpos, m, pos, env_pos;
      bool need_xramp = false;
      mus_long_t segbeg, segnum, segend;
      double power = 0.0;
      mus_float_t *data;

      data = mus_data(egen);
      if (base != 1.0) need_xramp = true;
      sc = get_sync_state_without_snd_fds(sp, cp, beg, over_selection);
      if (!sc) 
	{
	  if (e) mus_free(egen);
	  return;
	}
      si = sc->si;
      /* in snd-test.scm, one sync_state pointer is lost here because env-channel requests edpos 2 (or is it 123?), but only 1 exists */

      for (i = 0; i < si->chans; i++) 
	{
	  bool edited = false;
	  if (!(is_editable(si->cps[i]))) continue;
	  segbeg = si->begs[i];
	  segend = si->begs[i] + dur;
	  segnum = passes[0];
	  local_edpos = si->cps[i]->edit_ctr; /* for as_one_edit backup */
	  pos = to_c_edit_position(si->cps[i], edpos, origin, arg_pos);
	  env_pos = pos;
	  for (k = 0, m = 1; k < len; k++, m += 2)
	    {
	      bool applied_ramp = false;

	      if ((segbeg + segnum) > segend) 
		segnum = segend - segbeg;
	      else
		if ((k >= (len - 2)) && 
		    ((segbeg + segnum) < segend))
		  segnum = segend - segbeg; /* last value is sticky in envs */

	      if (segnum > 0)
		{
		  if (k == 0) 
		    {
		      if (need_xramp)
			{
			  power = mus_env_initial_power(egen);
			  applied_ramp = xramp_channel(si->cps[i], 
						       power,
						       rates[0],
						       scaler, offset, segbeg, segnum, pos, IN_AS_ONE_EDIT, egen, 0);
			  power *= exp(log(rates[0]) * segnum);
			}
		      else applied_ramp = ramp_channel(si->cps[i], 
						       offset + scaler * data[m],
						       rates[0],
						       segbeg, segnum, pos, IN_AS_ONE_EDIT);
		    }
		  else 
		    {
		      if (need_xramp)
			/* divide by segnum since we end at the break point and don't want to repeat it, so go to next position in env */
			{
			  applied_ramp = xramp_channel(si->cps[i], 
						       power, 
						       rates[k],
						       scaler, offset, segbeg, segnum, pos, IN_AS_ONE_EDIT, egen, k);
			  power *= exp(log(rates[k]) * segnum);
			}
		      else 
			{
			  if (k == (len - 1)) /* oops -- must have sticky end in play here? this doesn't work if a clm env passed */
			    applied_ramp = scale_channel(si->cps[i], 
							 (mus_float_t)(offset + scaler * data[m]), 
							 segbeg, segnum, pos, IN_AS_ONE_EDIT);
			  else applied_ramp = ramp_channel(si->cps[i], 
							   offset + scaler * data[m],
							   rates[k],
							   segbeg, segnum, pos, IN_AS_ONE_EDIT);
			}
		    }
		  pos = si->cps[i]->edit_ctr;
		}

	      if (!edited) edited = applied_ramp;

	      segbeg += segnum;
	      if (segbeg >= segend) break;
	      segnum = passes[k + 1] - passes[k];
	    }

	  if (edited)
	    {
	      if ((si->begs[i] == 0) && (dur == si->cps[i]->edits[env_pos]->samples))
		amp_env_env(si->cps[i], mus_data(egen), len, env_pos, base, scaler, offset);
	      else 
		{
		  if ((len < 2) || (snd_abs_mus_long_t(dur - passes[len - 2]) < 2))
		    amp_env_env_selection_by(si->cps[i], egen, si->begs[i], dur, env_pos);
		}

	      as_one_edit(si->cps[i], local_edpos + 1);
	      if (si->cps[i]->edits[si->cps[i]->edit_ctr]->origin) 
		free(si->cps[i]->edits[si->cps[i]->edit_ctr]->origin);
	      si->cps[i]->edits[si->cps[i]->edit_ctr]->origin = edit_list_envelope(egen, 
										   si->begs[i], (len > 1) ? (passes[len - 2]) : dur, 
										   dur, 
										   current_samples(si->cps[i]), 
										   base);
	      after_edit(cp);
	      update_graph(si->cps[i]);
	      reflect_edit_history_change(si->cps[i]);
	    }
	}
    }
  if (e) mus_free(egen);
  free_sync_state(sc);
}


void cursor_delete(chan_info *cp, mus_long_t count)
{
  mus_long_t beg;
  snd_info *sp;
  if (count == 0) return;
  if (count > 0)
    beg = cursor_sample(cp);
  else
    {
      count = -count;
      beg = cursor_sample(cp) - count;
      if (beg < 0)
	{
	  count += beg;
	  beg = 0;
	  if (count <= 0) return;
	}
    }
  sp = cp->sound;
  if (sp->sync != 0)
    {
      int i;
      sync_info *si;
      chan_info **cps;
      si = snd_sync(sp->sync);
      cps = si->cps;
      for (i = 0; i < si->chans; i++)
	{
	  if (delete_samples(beg, count, cps[i], cps[i]->edit_ctr))
	    {
	      cursor_sample(cps[i]) = beg;
	      update_graph(si->cps[i]);
	    }
	}
      free_sync_info(si);
    }
  else
    {
      if (delete_samples(beg, count, cp, cp->edit_ctr))
	{
	  cursor_sample(cp) = beg;
	  update_graph(cp);
	}
    }
}


void cursor_insert(chan_info *cp, mus_long_t beg, mus_long_t count)
{
  snd_info *sp;
  sp = cp->sound;
  if (count < 0) 
    {
      count = -count;
      if (count > beg) count = beg;
      beg -= count;
    }
  if (sp->sync != 0)
    {
      int i;
      sync_info *si;
      chan_info **cps;
      si = snd_sync(sp->sync);
      cps = si->cps;
      for (i = 0; i < si->chans; i++)
	{
	  if ((count > 0) &&
	      (extend_with_zeros(cps[i], 
				 mus_oclamp(0, beg, current_samples(si->cps[i])), 
				 count, 
				 cps[i]->edit_ctr,
				 "cursor insert")))
	    update_graph(cps[i]);
	}
      free_sync_info(si);
    }
  else 
    {
      if ((count > 0) &&
	  (extend_with_zeros(cp, 
			     mus_oclamp(0, beg, current_samples(cp)), 
			     count, 
			     cp->edit_ctr,
			     "cursor insert")))
	update_graph(cp);
    }
}


void cursor_zeros(chan_info *cp, mus_long_t count, bool over_selection)
{
  int i;
  mus_long_t beg, num;
  snd_info *sp;
  sync_info *si = NULL;
  chan_info *ncp;

  if (count == 0) return;
  if (count < 0) num = -count; else num = count;
  sp = cp->sound;

  if ((sp->sync != 0) && (!over_selection))
    {
      si = snd_sync(sp->sync);
      for (i = 0; i < si->chans; i++) 
	si->begs[i] = cursor_sample(cp);
    }
  else
    {
      if ((over_selection) && (selection_is_active()))
	{
	  si = selection_sync();
	  num = selection_len();
	}
    }

  if (!si) si = make_simple_sync(cp, cursor_sample(cp));

  for (i = 0; i < si->chans; i++)
    {
      /* if zeroing entire sound, set scalers and remake peak_env */
      ncp = si->cps[i];
      if ((si->begs[i] == 0) && 
	  (num >= current_samples(ncp)))
	{
	  mus_float_t scaler[1];
	  snd_info *nsp;
	  int old_sync;
	  nsp = ncp->sound;
	  old_sync = nsp->sync;
	  nsp->sync = 0;
	  scaler[0] = 0.0;
	  scale_by(ncp, scaler, 1, OVER_SOUND);
	  nsp->sync = old_sync;
	}
      else
	{
	  if (count > 0) 
	    beg = si->begs[i];
	  else beg = si->begs[i] + count;
	  /* special case 1 sample -- if already 0, treat as no-op */

	  if ((count != 1) || 
	      (beg >= current_samples(ncp)) || 
	      (chn_sample(beg, ncp, ncp->edit_ctr) != 0.0))
	    scale_channel(ncp, 0.0, beg, num, ncp->edit_ctr, NOT_IN_AS_ONE_EDIT);
	}
    }
  free_sync_info(si);
}


/* smooth-channel could be a built-in virtual op, but the smoothed section is never long, so it doesn't save anything */

static void smooth_channel(chan_info *cp, mus_long_t beg, mus_long_t dur, int edpos)
{
  mus_float_t *data = NULL;
  mus_long_t k;
  char *origin = NULL;
  mus_float_t y0, y1;

  if ((beg < 0) || (dur <= 0)) return;
  if (!(is_editable(cp))) return;
  if ((beg + dur) > cp->edits[edpos]->samples) 
    {
      dur = cp->edits[edpos]->samples - beg;
      if (dur <= 0) return;
    }
  y0 = chn_sample(beg, cp, edpos);
  y1 = chn_sample(beg + dur, cp, edpos); /* one past end -- this is a debatable choice */

#if HAVE_FORTH
  origin = mus_format("%" print_mus_long PROC_SEP "%" print_mus_long " %s", beg, dur, S_smooth_channel);
#else
  origin = mus_format("%s" PROC_OPEN "%" print_mus_long PROC_SEP "%" print_mus_long, to_proc_name(S_smooth_channel), beg, dur);
#endif

  data = (mus_float_t *)malloc(dur * sizeof(mus_float_t));
  if (y0 == y1)
    {
      for (k = 0; k < dur; k++) 
	data[k] = y0;
      change_samples(beg, dur, data, cp, origin, edpos, fabs(y0));
    }
  else
    {
      mus_float_t angle, incr, off, scale;
      if (y1 > y0) angle = M_PI; else angle = 0.0;
      incr = M_PI / (double)dur;
      off = 0.5 * (y1 + y0);
      scale = 0.5 * fabs(y0 - y1);
      /* if scale is very small, it might work here to just use linear interpolation, but that case appears to be very uncommon.
       */
      for (k = 0; k < dur; k++, angle += incr) 
	data[k] = (off + scale * cos(angle));
      change_samples(beg, dur, data, cp, origin, edpos, ((y0 > y1) && (y1 >= 0.0)) ? y0 : -1.0);
    }
  if (origin) free(origin);
  update_graph(cp);
  free(data);
}


void cos_smooth(chan_info *cp, mus_long_t beg, mus_long_t num, bool over_selection)
{
  /* verbatim, so to speak from Dpysnd */
  /* start at beg, apply a cosine for num samples, matching endpoints */
  sync_state *sc;
  int i;
  snd_info *sp;
  sync_info *si;

  sp = cp->sound;
  sc = get_sync_state_without_snd_fds(sp, cp, beg, over_selection);
  if (!sc) return;
  si = sc->si;
  if (over_selection) num = sc->dur;

  for (i = 0; i < si->chans; i++)
    smooth_channel(si->cps[i], si->begs[i], num, si->cps[i]->edit_ctr);

  free_sync_state(sc);
}


#if USE_MOTIF
/* this is used by the view-files dialog */

typedef struct {
  snd_fd **fds;
  int len;
} scale_and_src_data;


static mus_float_t scale_and_src_input(void *data, int direction)
{
  scale_and_src_data *sd = (scale_and_src_data *)data;
  int i;
  mus_float_t sum;
  sum = 0.0;
  for (i = 0; i < sd->len; i++)
    if (sd->fds[i])
      sum += read_sample(sd->fds[i]);
  return(sum);
}


char *scale_and_src(char **files, int len, int max_chans, mus_float_t amp, mus_float_t speed, env *amp_env, bool *temp_file_err)
{
  /* view files mix and insert possible src change */
  char *tempfile;
  snd_fd ***fds = NULL;
  snd_info **sps = NULL;
  int i, chan, chans = 0;
  mus_long_t k, new_dur = 0, dur = 0;
  mus_float_t **data;
  file_info *hdr = NULL;
  int j, ofd = 0, datumb = 0, err = 0, srate = 0, olen;
  io_error_t io_err = IO_NO_ERROR;
  mus_float_t sum;
  mus_any *e = NULL;
  mus_any **sgens = NULL;
  scale_and_src_data **sdata = NULL;

  (*temp_file_err) = false;
  tempfile = snd_tempnam();

  for (i = 0; i < len; i++)
    {
      int fchans, fsrate;
      mus_long_t flen;
      fchans = mus_sound_chans(files[i]);
      flen = mus_sound_framples(files[i]);
      fsrate = mus_sound_srate(files[i]);
      if (chans < fchans) chans = fchans;
      if (srate < fsrate) srate = fsrate;
      if (dur < flen) dur = flen;
    }

  /* open output sound file */
  hdr = make_temp_header(tempfile, srate, chans, dur, "scale-and-src temp");
  ofd = open_temp_file(tempfile, chans, hdr, &io_err);
  if (ofd == -1)
    {
      (*temp_file_err) = true;
      free_file_info(hdr);
      free(tempfile);
      return(mus_format("%s temp file %s: %s\n", 
			(io_err != IO_NO_ERROR) ? io_error_name(io_err) : "can't open",
			tempfile, 
			snd_open_strerror()));
    }

  olen = len * sizeof(snd_fd **); /* try to turn off gcc's alloc-size-larger-than error message */
  fds = (snd_fd ***)calloc(olen, 1);
  sps = (snd_info **)calloc(olen, 1);
  for (i = 0; i < len; i++)
    {
      fds[i] = (snd_fd **)calloc(max_chans, sizeof(snd_fd *));
      sps[i] = make_sound_readable(files[i], false);
      sps[i]->short_filename = filename_without_directory(files[i]);
      sps[i]->filename = NULL; /* why? squelch graphics perhaps? */
      for (chan = 0; chan < (int)sps[i]->nchans; chan++)
	fds[i][chan] = init_sample_read(0, sps[i]->chans[chan], READ_FORWARD);
    }

  /* now we have readers set up for all chans of all sounds about to be mixed/scaled/enveloped/resampled... */

  datumb = mus_bytes_per_sample(hdr->sample_type);
  data = (mus_float_t **)calloc(chans, sizeof(mus_float_t *));
  for (i = 0; i < chans; i++)
    data[i] = (mus_float_t *)malloc(MAX_BUFFER_SIZE * sizeof(mus_float_t)); 

  if (!(snd_feq(speed, 1.0)))
    {
      new_dur = (mus_long_t)((double)dur / (double)speed);
      sgens = (mus_any **)calloc(chans, sizeof(mus_any *));
      sdata = (scale_and_src_data **)calloc(chans, sizeof(scale_and_src_data *));
      for (chan = 0; chan < chans; chan++)
	{
	  int m;
	  sdata[chan] = (scale_and_src_data *)calloc(1, sizeof(scale_and_src_data));
	  sdata[chan]->len = len;
	  sdata[chan]->fds = (snd_fd **)calloc(len, sizeof(snd_fd *));
	  for (m = 0; m < len; m++)
	    sdata[chan]->fds[m] = fds[m][chan];

	  sgens[chan] = mus_make_src(scale_and_src_input, speed, 0, (void *)(sdata[chan])); /* width=0 -> use current default */
	}
    }
  else  new_dur = dur;

  if (!(is_default_env(amp_env)))
    e = mus_make_env(amp_env->data, amp_env->pts, amp, 0.0, 1.0, 0.0, new_dur - 1, NULL);

  j = 0;
  if (!sgens)
    {
      for (k = 0; k < dur; k++)
	{
	  if (e) amp = mus_env(e);
	  for (chan = 0; chan < chans; chan++)
	    {
	      sum = 0.0;
	      for (i = 0; i < len; i++)
		if (fds[i][chan])
		  sum += read_sample(fds[i][chan]);
	      sum *= amp;
	      data[chan][j] = (sum);
	    }
	  j++;
	  if (j == MAX_BUFFER_SIZE)
	    {
	      err = mus_file_write(ofd, 0, j - 1, chans, data);
	      j = 0;
	      if (err != MUS_NO_ERROR) break;
	    }
	}
    }
  else
    {
      for (k = 0; k < new_dur; k++)
	{
	  if (e) amp = mus_env(e);
	  for (chan = 0; chan < chans; chan++)
	    data[chan][j] = (amp * mus_src(sgens[chan], 0.0, &scale_and_src_input));
	  j++;
	  if (j == MAX_BUFFER_SIZE)
	    {
	      err = mus_file_write(ofd, 0, j - 1, chans, data);
	      j = 0;
	      if (err != MUS_NO_ERROR) break;
	    }
	}
    }

  if (j > 0) 
    mus_file_write(ofd, 0, j - 1, chans, data);

  /* close and free everything */
  close_temp_file(tempfile, ofd, hdr->type, new_dur * datumb);
  hdr = free_file_info(hdr);
  if (e) mus_free(e);

  for (i = 0; i < len; i++)
    {
      for (chan = 0; chan < (int)sps[i]->nchans; chan++)
	free_snd_fd(fds[i][chan]);
      free(fds[i]);
      sps[i] = completely_free_snd_info(sps[i]);
    }
  free(fds);
  free(sps);

  for (i = 0; i < chans; i++)
    free(data[i]);
  free(data);

  if (sgens)
    {
      for (chan = 0; chan < chans; chan++)
	{
	  free(sdata[chan]->fds);
	  free(sdata[chan]);
	  mus_free(sgens[chan]);
	}
      free(sdata);
      free(sgens);
    }

  return(tempfile);
}
#endif


static Xen map_channel_to_temp_file(chan_info *cp, snd_fd *sf, Xen proc, mus_long_t beg, mus_long_t num, int pos, const char *caller)
{
  snd_info *sp;
  int rpt4, ofd, datumb;
  char *filename;
  file_info *hdr;
  bool reporting;
  io_error_t io_err = IO_NO_ERROR;
  Xen res = Xen_false;

  sampler_set_safe(sf, num);
  sp = cp->sound;
  reporting = ((num > REPORTING_SIZE) && (!(cp->squelch_update)));
  if (reporting) start_progress_report(cp);
  rpt4 = MAX_BUFFER_SIZE / 4;
  
  filename = snd_tempnam();
  hdr = make_temp_header(filename, snd_srate(cp->sound), 1, 0, S_map_channel);
  datumb = mus_bytes_per_sample(hdr->sample_type);

  ofd = open_temp_file(filename, 1, hdr, &io_err);
  if (ofd == -1)
    snd_error("%s: %s (temp file) %s: %s", 
	      S_map_channel,
	      (io_err != IO_NO_ERROR) ? io_error_name(io_err) : "can't open",
	      filename, 
	      snd_open_strerror());
  else
    {
      int err = MUS_NO_ERROR, i, j = 0, rpt = 0;
      mus_float_t **data;
      mus_long_t kp, samps = 0;
      
      data = (mus_float_t **)malloc(1 * sizeof(mus_float_t *));
      data[0] = (mus_float_t *)malloc(MAX_BUFFER_SIZE * sizeof(mus_float_t));
      ss->stopped_explicitly = false;
      
      /* fprintf(stderr, "tempfile %d, %" print_mus_long " %s\n", __LINE__, num, DISPLAY(body)); */
      for (kp = 0; kp < num; kp++)
	{
	  /* changed here to remove catch 24-Mar-02 */
	  res = Xen_unprotected_call_with_1_arg(proc, C_double_to_Xen_real((double)read_sample(sf)));
	  if (Xen_is_number(res))                         /* one number -> replace current sample */
	    {
	      samps++;
	      data[0][j++] = Xen_real_to_C_double(res);
	      if (j == MAX_BUFFER_SIZE)
		{
		  err = mus_file_write(ofd, 0, j - 1, 1, data);
		  j = 0;
		  if (err != MUS_NO_ERROR) break;
		}
	    }
	  else
	    {
	      if (!Xen_is_false(res))                  /* if #f, no output on this pass */
		{
		  if (Xen_is_true(res))                   /* if #t we halt the entire map */
		    break;
		  else
		    {
		      if (mus_is_vct(res))
			{
			  vct *v;
			  mus_long_t vlen;
			  mus_float_t *vdata;

			  v = Xen_to_vct(res);
			  vlen = mus_vct_length(v);
			  vdata = mus_vct_data(v);

			  for (i = 0; i < vlen; i++) 
			    {
			      data[0][j++] = vdata[i];
			      if (j == MAX_BUFFER_SIZE)
				{
				  err = mus_file_write(ofd, 0, j - 1, 1, data);
				  j = 0;
				  if (err != MUS_NO_ERROR) break;
				}
			    }
			  samps += vlen - 1;
			}
		      else
			{
			  close_temp_file(filename, ofd, hdr->type, samps * datumb);
			  sf = free_snd_fd(sf);
			  if (reporting) finish_progress_report(cp);
			  snd_remove(filename, REMOVE_FROM_CACHE);
			  free(filename);
			  free(data[0]);
			  free(data);
			  
			  Xen_error(BAD_TYPE,
				    Xen_list_3(C_string_to_Xen_string("~A: result of procedure must be a (non-complex) number, boolean, or vct: ~A"),
					       C_string_to_Xen_string(caller),
					       res));
			}
		    }
		}
	    }
	  if (reporting) 
	    {
	      rpt++;
	      if (rpt > rpt4)
		{
		  progress_report(cp, (mus_float_t)((double)kp / (double)num));
		  if (!(sp->active))
		    {
		      ss->stopped_explicitly = true;
		      break;
		    }
		  rpt = 0;		    
		}
	    }
	  if (ss->stopped_explicitly) break;
	}
      
      if (j > 0) 
	mus_file_write(ofd, 0, j - 1, 1, data);

      close_temp_file(filename, ofd, hdr->type, samps * datumb);
      free_file_info(hdr);
      free(data[0]);
      free(data);
      free_snd_fd(sf);
      
      if (reporting) finish_progress_report(cp);
      if (ss->stopped_explicitly) 
	ss->stopped_explicitly = false;
      else
	{
	  if (cp->active < CHANNEL_HAS_EDIT_LIST)
	    {
	      snd_remove(filename, REMOVE_FROM_CACHE);
	      free(filename);
	      Xen_error(NO_SUCH_CHANNEL,
			Xen_list_2(C_string_to_Xen_string("~A: can't edit closed channel!"),
				   C_string_to_Xen_string(caller)));
	      return(Xen_false);
	    }
	  
	  if (samps == num)
	    file_change_samples(beg, samps, filename, cp, 0, DELETE_ME, caller, pos);
	  else
	    {
	      delete_samples(beg, num, cp, pos);
	      if (samps > 0)
		{
		  int cured;
		  cured = cp->edit_ctr;
		  file_insert_samples(beg, samps, filename, cp, 0, DELETE_ME, caller, cp->edit_ctr);
		  backup_edit_list(cp);
		  if (cp->edit_ctr > cured)
		    backup_edit_list(cp);
		  ripple_trailing_marks(cp, beg, num, samps);
		}
	      else snd_remove(filename, REMOVE_FROM_CACHE);
	    }
	}
    }
  free(filename);
  return(res);
}

#if HAVE_SCHEME
static bool tree_memq(s7_scheme *sc, s7_pointer symbol, s7_pointer tree)
{
  if (symbol == tree)
    return(true);
  if (s7_is_pair(tree))
    return((tree_memq(sc, symbol, s7_car(tree))) ||
	   (tree_memq(sc, symbol, s7_cdr(tree))));
  return(false);
}

static s7_pointer gc_vect;
enum {GC_BODY, GC_ARGS, GC_FUNC, GC_LET};
#endif

static Xen map_channel_to_buffer(chan_info *cp, snd_fd *sf, Xen proc, mus_long_t beg, mus_long_t num, int pos, const char *caller)
{
  /* not temp_file -- use resizable buffer */
  int i, data_pos = 0, kp;
  mus_long_t cur_size;
  mus_float_t *data = NULL;
  Xen res = Xen_false;

#if HAVE_SCHEME
  mus_float_t *in_data;
  bool use_apply;
  s7_pointer arg_list, body, e, slot;

  arg_list = xen_nil;
  e = xen_nil;
  slot = xen_nil;

  body = s7_closure_body(s7, proc);
  if ((s7_is_pair(body)) &&
      (s7_is_pair(s7_closure_args(s7, proc))))
    /* (!s7_tree_memq(s7, s7_make_symbol(s7, "set!"), body))) why this? */
    {
      s7_pointer arg;
      if (s7_is_null(s7, s7_cdr(body)))
	{
	  res = s7_car(body);
	  arg = s7_car(s7_closure_args(s7, proc));
	  if (s7_is_pair(arg)) arg = s7_car(arg); /* lambda* + default */

	  if ((s7_is_boolean(res)) ||
	      (res == arg))
	    {
	      /* #f = delete all samples in the range, #t = no-op, (lambda (y) y) a no-op */
	      free_snd_fd(sf);
	      if (res == s7_f(s7))
		delete_samples(beg, num, cp, pos);
	      else 
		if (res == arg) /* this change and two below courtesy of Tito Latini (to avoid returning a pointer into the procedure source) */
		  return(Xen_true);
	      return(res);
	    }
	  
	  if (!s7_is_pair(res))
	    {
	      s7_double x;
	      if (s7_is_symbol(res))
		{
		  s7_pointer old_e;
		  e = s7_sublet(s7, s7_closure_let(s7, proc), s7_nil(s7)); 
		  old_e = s7_set_curlet(s7, e);                  /* new env for map lambda */
		  res = s7_symbol_value(s7, res);
		  s7_set_curlet(s7, old_e);
		}
	      x = s7_number_to_real_with_caller(s7, res, "map-channel");
	      data = (mus_float_t *)malloc(num * sizeof(mus_float_t));
	      for (kp = 0; kp < num; kp++)
		data[kp] = x;
	      /* since we're not calling eval or the event checker, the channel can't be closed during the loop (??) */
	      change_samples(beg, num, data, cp, caller, pos, fabs(x));
	      free(data);
	      free_snd_fd(sf);
	      return(res);
	    }

	  /* look first for the common scaling case */
	  if ((s7_list_length(s7, res) == 3) &&
	      (s7_cadr(res) != s7_caddr(res)) &&
	      ((s7_car(res) == s7_make_symbol(s7, "*")) || (s7_car(res) == s7_make_symbol(s7, "+"))) &&
	      (((s7_cadr(res) == arg) && (!s7_is_pair(s7_caddr(res)))) ||
	       ((s7_caddr(res) == arg) && (!s7_is_pair(s7_cadr(res))))))
	    {
	      double x;
	      s7_pointer fx;
	      if (s7_cadr(res) == arg) fx = s7_caddr(res); else fx = s7_cadr(res);
	      if (s7_is_symbol(fx))
		{
		  s7_pointer old_e;
		  e = s7_sublet(s7, s7_closure_let(s7, proc), s7_nil(s7)); 
		  old_e = s7_set_curlet(s7, e);                  /* new env for map lambda */
		  fx = s7_symbol_value(s7, fx);
		  s7_set_curlet(s7, old_e);
		}
	      x = s7_number_to_real_with_caller(s7, fx, "map-channel");
	      if (s7_car(res) == s7_make_symbol(s7, "*"))
		{
		  scale_channel(cp, x, beg, num, pos, NOT_IN_AS_ONE_EDIT);
		  res = C_double_to_Xen_real(chn_sample(num - 1, cp, cp->edit_ctr));
		}
	      else
		{
		  data = (mus_float_t *)calloc(num, sizeof(mus_float_t));
		  samples_to_vct_with_reader(num, data, sf);
		  for (kp = 0; kp < num; kp++) data[kp] += x;
		  change_samples(beg, num, data, cp, caller, pos, -1.0);
		  res = C_double_to_Xen_real(data[num - 1]);
		  free(data);
		}
	      free_snd_fd(sf);
	      return(res);
	    }

	  {
	    /* try s7_float_optimize */
	    s7_pointer e, yp, old_e;
	    s7_float_function opt_func;
	    e = s7_sublet(s7, s7_closure_let(s7, proc), s7_nil(s7));
	    old_e = s7_set_curlet(s7, e);
	    yp = s7_make_slot(s7, e, arg, s7_make_mutable_real(s7, 1.5));
			
	    opt_func = s7_float_optimize(s7, body);
	    if (opt_func)
	      {
		data = (mus_float_t *)calloc(num, sizeof(mus_float_t));
		if (tree_memq(s7, arg, res))
		  {
		    samples_to_vct_with_reader(num, data, sf);
		    for (kp = 0; kp < num; kp++)
		      {
			s7_slot_set_real_value(s7, yp, data[kp]);
			data[kp] = opt_func(s7);
		      }
		  }
		else
		  {
		    for (kp = 0; kp < num; kp++)
		      data[kp] = opt_func(s7);
		  }
		free_snd_fd(sf);
		change_samples(beg, num, data, cp, caller, pos, -1.0);
		res = C_double_to_Xen_real(data[num - 1]);
		free(data);
		s7_set_curlet(s7, old_e);
		return(res);
	      }
	  }

	  if ((s7_list_length(s7, res) == 2) &&
	      (s7_is_symbol(s7_cadr(res))))
	    {
	      if (s7_car(res) == s7_make_symbol(s7, "granulate"))
		{
		  s7_pointer gp;
		  mus_any *g;
		  gp = s7_symbol_value(s7, s7_cadr(res));
		  if ((mus_is_xen(gp)) &&
		      (mus_is_granulate(g = Xen_to_mus_any(gp))))
		    {
		      data = (mus_float_t *)calloc(num, sizeof(mus_float_t));
		      for (kp = 0; kp < num; kp++)
			data[kp] = mus_granulate_with_editor(g, NULL, NULL);
		      free_snd_fd(sf);
		      change_samples(beg, num, data, cp, caller, pos, -1.0);
		      free(data);
		      return(res);
		    }
		}
	      if (s7_car(res) == s7_make_symbol(s7, "phase-vocoder"))
		{
		  s7_pointer gp;
		  mus_any *g;
		  gp = s7_symbol_value(s7, s7_cadr(res));
		  if ((mus_is_xen(gp)) &&
		      (mus_is_phase_vocoder(g = Xen_to_mus_any(gp))))
		    {
		      data = (mus_float_t *)calloc(num, sizeof(mus_float_t));
		      for (kp = 0; kp < num; kp++)
			data[kp] = mus_phase_vocoder(g, NULL);
		      free_snd_fd(sf);
		      change_samples(beg, num, data, cp, caller, pos, -1.0);
		      free(data);
		      return(res);
		    }
		}
	    }
	} /* is one expr body */

      arg = s7_car(s7_closure_args(s7, proc));
      e = s7_sublet(s7, s7_closure_let(s7, proc), s7_nil(s7));
      s7_vector_set(s7, gc_vect, GC_LET, e);
      slot = s7_make_slot(s7, e, arg, s7_make_real(s7, 0.0));
      use_apply = false;
      if (s7_is_null(s7, s7_cdr(body)))
	{
	  body = s7_car(body);
	}
      else 
	{
	  body = s7_cons(s7, s7_make_symbol(s7, "begin"), body);
	  s7_vector_set(s7, gc_vect, GC_BODY, body);
	}
      /* fprintf(stderr, "eval %s\n", DISPLAY(body)); */
    }
  else
    {
      arg_list = Xen_list_1(Xen_false);
      s7_vector_set(s7, gc_vect, GC_ARGS, arg_list);
      use_apply = true;
    }
  s7_vector_set(s7, gc_vect, GC_FUNC, proc);
#endif

  /* fprintf(stderr, "map %" print_mus_long ": body: %s\n", num, s7_object_to_c_string(s7, body)); */

  data = (mus_float_t *)calloc(num, sizeof(mus_float_t));
#if HAVE_SCHEME
  in_data = (mus_float_t *)calloc(num, sizeof(mus_float_t));
  samples_to_vct_with_reader(num, in_data, sf);
#endif
  cur_size = num;
  for (kp = 0; kp < num; kp++)
    {
#if HAVE_SCHEME
      if (use_apply)
	{
	  s7_set_car(arg_list, s7_make_real(s7, in_data[kp]));
	  if (kp == 0)
	    res = s7_call_with_location(s7, proc, arg_list, __func__, __FILE__, __LINE__);
	  else res = s7_apply_function(s7, proc, arg_list);
	}
      else
	{
	  s7_slot_set_value(s7, slot, s7_make_real(s7, in_data[kp]));
	  res = s7_eval(s7, body, e);
	}
#else
      res = Xen_unprotected_call_with_1_arg(proc, C_double_to_Xen_real((double)read_sample(sf)));
#endif

      if (Xen_is_number(res))                         /* one number -> replace current sample */
	{
	  if (data_pos >= cur_size)
	    {
	      cur_size *= 2;
	      data = (mus_float_t *)realloc(data, cur_size * sizeof(mus_float_t));
	    }
	  data[data_pos++] = Xen_real_to_C_double(res);
	}
      else
	{
	  if (!Xen_is_false(res))                  /* if #f, no output on this pass */
	    {
	      if (Xen_is_true(res))                   /* if #t we halt the entire map */
		break;
	      else
		{
		  if (mus_is_vct(res))
		    {
		      vct *v;
		      mus_long_t vlen;
		      mus_float_t *vdata;

		      v = Xen_to_vct(res);
		      vlen = mus_vct_length(v);
		      vdata = mus_vct_data(v);

		      for (i = 0; i < vlen; i++)
			{
			  if (data_pos >= cur_size)
			    {
			      cur_size *= 2;
			      data = (mus_float_t *)realloc(data, cur_size * sizeof(mus_float_t));
			    }
			  data[data_pos++] = vdata[i];
			}
		    }
		  else
		    {
		      if (data) {free(data); data = NULL;}
		      sf = free_snd_fd(sf);
#if HAVE_SCHEME
		      free(in_data);
#endif
		      Xen_error(BAD_TYPE,
				Xen_list_3(C_string_to_Xen_string("~A: result of procedure must be a number, boolean, or vct: ~A"),
					   C_string_to_Xen_string(caller),
					   res));
		    }
		}
	    }
	}
    }

  free_snd_fd(sf);
#if HAVE_SCHEME
  free(in_data);
#endif

  if (cp->active < CHANNEL_HAS_EDIT_LIST)
    {
      if (data) {free(data); data = NULL;} 
      Xen_error(NO_SUCH_CHANNEL,
		Xen_list_2(C_string_to_Xen_string("~A: can't edit closed channel!"),
			   C_string_to_Xen_string(caller)));
      return(Xen_false);
    }
  if (data_pos == num)
    change_samples(beg, data_pos, data, cp, caller, pos, -1.0);
  else
    {
      /* the version above truncates to the new length... */
      delete_samples(beg, num, cp, pos);
      if (data_pos > 0)
	{
	  int cured;
	  cured = cp->edit_ctr;
	  insert_samples(beg, data_pos, data, cp, caller, cp->edit_ctr);
	  backup_edit_list(cp);
	  if (cp->edit_ctr > cured)
	    backup_edit_list(cp);
	  ripple_trailing_marks(cp, beg, num, data_pos);
	}
    }
  if (data) {free(data); data = NULL;}
  return(res);
}


static Xen g_map_chan_1(Xen proc_and_list, Xen s_beg, Xen s_end, Xen org, Xen snd, Xen chn, Xen edpos, Xen s_dur, const char *fallback_caller) 
{ 
  chan_info *cp;
  const char *caller;
  mus_long_t beg = 0, end = 0, dur = 0;
  mus_long_t num;
  int pos;
  Xen res = Xen_false;
  Xen proc;

  proc = proc_and_list;

  if (Xen_is_string(org)) 
    caller = Xen_string_to_C_string(org);
  else caller = fallback_caller;

  Xen_check_type((Xen_is_procedure(proc)) || (mus_is_xen(proc)), proc, 1, caller, "a procedure");
  Snd_assert_sample_type(caller, s_beg, 2);
  Snd_assert_sample_type(caller, s_end, 3);
  Snd_assert_sample_type(caller, s_dur, 3);
  Snd_assert_channel(caller, snd, chn, 5); 

  cp = get_cp(snd, chn, caller);
  if (!cp) return(Xen_false);
  if (!(is_editable(cp))) return(Xen_false);

  pos = to_c_edit_position(cp, edpos, caller, 7);
  beg = beg_to_sample(s_beg, caller);
  if (Xen_is_false(s_dur))
    end = end_to_sample(s_end, cp, pos, caller);
  else dur = dur_to_samples(s_dur, beg, cp, pos, 3, caller); /* 3 is arg num from caller's point of view */
  if (end == 0) 
    {
      if (dur != 0) 
	end = beg + dur - 1;
      else end = cp->edits[pos]->samples - 1;
    }
  num = end - beg + 1;
  if (num > 0)
    {
      snd_fd *sf = NULL;
      char *errmsg;
      bool temp_file, backup = false;

      errmsg = procedure_ok(proc, 1, caller, "", 1);
      if (errmsg)
	{
	  Xen errstr;
	  errstr = C_string_to_Xen_string(errmsg);
	  free(errmsg);
	  return(snd_bad_arity_error(caller, errstr, proc));
	}

      /* added 27-Oct-06 -- can't see why map-channel should be that different from insert-samples et al */
      if (beg > cp->edits[pos]->samples)
	{
	  if (!(extend_with_zeros(cp, cp->edits[pos]->samples, beg - cp->edits[pos]->samples, pos, "extend for " S_map_channel))) 
	    return(Xen_false);
	  backup = true;
	  pos = cp->edit_ctr;
	}

      sf = init_sample_read_any_with_bufsize(beg, cp, READ_FORWARD, pos, (num > REPORTING_SIZE) ? REPORTING_SIZE : num);
      if (!sf) 
	return(Xen_true);

      temp_file = (num > REPORTING_SIZE);
      if (temp_file)
	res = map_channel_to_temp_file(cp, sf, proc, beg, num, pos, caller);
      else res = map_channel_to_buffer(cp, sf, proc, beg, num, pos, caller);

      if (backup)
	backup_edit_list(cp);

      update_graph(cp);
    }
  return(res);
}


static Xen g_sp_scan(Xen proc_and_list, Xen s_beg, Xen s_end, Xen snd, Xen chn, const char *caller, bool counting, Xen edpos, int arg_pos, Xen s_dur)
{
  chan_info *cp;
  mus_long_t beg = 0, end = 0, dur = 0;
  snd_info *sp;
  snd_fd *sf;
  mus_long_t kp, num;
  int rpt = 0, rpt4 = 0;
  bool reporting = false;
  int counts = 0, pos;
  char *errmsg;
  Xen proc;

  proc = proc_and_list;

  Xen_check_type((Xen_is_procedure(proc)), proc, 1, caller, "a procedure");
  Snd_assert_sample_type(caller, s_beg, 2);
  Snd_assert_sample_type(caller, s_end, 3);
  Snd_assert_sample_type(caller, s_dur, 3);
  Snd_assert_channel(caller, snd, chn, 4);

  cp = get_cp(snd, chn, caller);
  if (!cp) return(Xen_false);

  pos = to_c_edit_position(cp, edpos, caller, arg_pos);

  beg = beg_to_sample(s_beg, caller);
  if (beg > cp->edits[pos]->samples) return(Xen_false);
  if (Xen_is_false(s_dur))
    end = end_to_sample(s_end, cp, pos, caller);
  else dur = dur_to_samples(s_dur, beg, cp, pos, 3, caller);

  errmsg = procedure_ok(proc, 1, caller, "", 1);
  if (errmsg)
    {
      Xen errstr;
      errstr = C_string_to_Xen_string(errmsg);
      free(errmsg);
      return(snd_bad_arity_error(caller, errstr, proc));
    }
#if HAVE_SCHEME
  {
    Xen arity;
    arity = Xen_arity(proc);
    if (Xen_integer_to_C_int(Xen_car(arity)) != Xen_integer_to_C_int(Xen_cdr(arity)))
      return(snd_bad_arity_error(caller, C_string_to_Xen_string("function should not accept optional arguments"), proc));
  }
#endif

  sp = cp->sound;
  if (end == 0) 
    {
      if (dur != 0)
	end = beg + dur - 1;
      else end = cp->edits[pos]->samples - 1;
    }
  num = end - beg + 1;
  if (num <= 0) return(Xen_false);
  sf = init_sample_read_any_with_bufsize(beg, cp, READ_FORWARD, pos, (num < REPORTING_SIZE) ? REPORTING_SIZE : num);
  if (!sf) return(Xen_true);
  sampler_set_safe(sf, num);

#if HAVE_SCHEME
  {
  s7_pointer arg_list;
  /* s7_int gc_loc; */
  bool use_apply;
  s7_pointer body, e, slot;

  arg_list = xen_nil;
  e = xen_nil;
  slot = xen_nil;

  body = s7_closure_body(s7, proc);
  if ((s7_is_pair(body)) &&
      (s7_is_pair(s7_closure_args(s7, proc))))
      /* (!s7_tree_memq(s7, s7_make_symbol(s7, "set!"), body))) */
    {
      s7_pointer arg, expr;

      arg = s7_car(s7_closure_args(s7, proc));
      if (s7_is_pair(arg)) arg = s7_car(arg);
      expr = s7_car(body);

      if (expr == xen_false)		       
	{
	  free_snd_fd(sf);
	  return(xen_false);
	}
      if (!s7_is_pair(expr))
	{
	  free_snd_fd(sf);
	  return(s_beg);
	}

      e = s7_sublet(s7, s7_closure_let(s7, proc), s7_nil(s7));

	{
	  s7_pointer yp, old_e, y, val;
	  s7_pfunc func;

	  old_e = s7_set_curlet(s7, e);                  /* new env for scan lambda */
	  y = s7_make_mutable_real(s7, 1.5);             /* slot for the scan lambda arg */
	  yp = s7_make_slot(s7, e, arg, y);
	  val = y;

	  if (s7_is_null(s7, s7_cdr(body)))
	    func = s7_optimize(s7, body);
	  else func = s7_optimize(s7, s7_cons(s7, s7_cons(s7, s7_make_symbol(s7, "begin"), body), s7_nil(s7)));
	  if (func)
	    {
	      for (kp = 0; kp < num; kp++)
		{
		  s7_slot_set_real_value(s7, yp, read_sample(sf));
		  val = func(s7);
		  
		  if (val != s7_f(s7))
		    {
		      if (counting)	
			counts++; 
		      else
			{
			  if (reporting) finish_progress_report(cp);
			  free_snd_fd(sf);
			  s7_set_curlet(s7, old_e);
			  return(C_llong_to_Xen_llong(kp + beg));
			}
		    }
		}
	      free_snd_fd(sf);
	      s7_set_curlet(s7, old_e);
	      if (counting)
		return(C_int_to_Xen_integer(counts));
	      return(val);
	    }
	  s7_set_curlet(s7, old_e);
	}
      
	s7_vector_set(s7, gc_vect, GC_LET, e);
	slot = s7_make_slot(s7, e, arg, s7_make_real(s7, 0.0));
	use_apply = false;
	
	if (s7_is_null(s7, s7_cdr(body)))
	  body = s7_car(body);
	else
	  {
	    body = s7_cons(s7, s7_make_symbol(s7, "begin"), body);
	    s7_vector_set(s7, gc_vect, GC_BODY, body);
	  }
	/* fprintf(stderr, "eval %s\n", DISPLAY(body)); */
    }
  else
    {
      /* is this for built-in funcs? */
      arg_list = Xen_list_1(Xen_false);
      s7_vector_set(s7, gc_vect, GC_ARGS, arg_list);
      use_apply = true;
    }

  /* fprintf(stderr, "scan %" print_mus_long ": body: %s\n", num, s7_object_to_c_string(s7, body)); */

  reporting = ((num > REPORTING_SIZE) && (!(cp->squelch_update)));
  if (reporting) start_progress_report(cp);
  rpt4 = MAX_BUFFER_SIZE / 4;
  ss->stopped_explicitly = false;

  for (kp = 0; kp < num; kp++)
    {
      Xen res;
      if (use_apply)
	{
	  s7_set_car(arg_list, s7_make_real(s7, read_sample(sf)));
	  if (kp == 0)
	    res = s7_call_with_location(s7, proc, arg_list, __func__, __FILE__, __LINE__);
	  else res = s7_apply_function(s7, proc, arg_list);
	}
      else
	{
	  s7_slot_set_value(s7, slot, s7_make_real(s7, read_sample(sf)));
	  res = s7_eval(s7, body, e);
	}

#else
  for (kp = 0; kp < num; kp++)
    {
      Xen res;
      res = Xen_unprotected_call_with_1_arg(proc, C_double_to_Xen_real((double)read_sample(sf)));
#endif
      /* leak here -- if reader active and error occurs, we jump out without cleanup */
      /* see dynamic_wind above */
      if (!Xen_is_false(res))
	{
	  if ((counting) &&
	      (Xen_is_true(res)))
	    counts++;
	  else
	    {
	      free_snd_fd(sf);
	      if (reporting) 
		finish_progress_report(cp);
	      return(C_llong_to_Xen_llong(kp + beg));
	    }
	}
      if (reporting) 
	{
	  rpt++;
	  if (rpt > rpt4)
	    {
	      progress_report(cp, (mus_float_t)((double)kp / (double)num));
	      if (!(sp->active))
		{
		  ss->stopped_explicitly = true;
		  break;
		}
	      rpt = 0;
	    }
	}
      if (ss->stopped_explicitly)
	{
	  ss->stopped_explicitly = false;
	  status_report(sp, "%s stopped at sample %" print_mus_long, caller, kp + beg);
	  break;
	}
    }
#if HAVE_SCHEME
  }
#endif
  if (reporting) finish_progress_report(cp);
  free_snd_fd(sf);
  if (counting)
    return(C_int_to_Xen_integer(counts));

  return(Xen_false);
}


mus_long_t scan_channel(chan_info *cp, mus_long_t start, mus_long_t end, Xen proc)
{
  Xen result;
  result = g_sp_scan(proc, C_llong_to_Xen_llong(start), C_llong_to_Xen_llong(end),
		     Xen_false, Xen_false, "search procedure", false, C_int_to_Xen_integer(AT_CURRENT_EDIT_POSITION), 0, Xen_false);
  if (Xen_is_llong(result))
    return(Xen_llong_to_C_llong(result));
  return(-1);
}

#if (!HAVE_SCHEME)
static Xen g_scan_chan(Xen proc, Xen beg, Xen end, Xen snd, Xen chn, Xen edpos) 
{ 
  #if HAVE_SCHEME
    #define scan_chan_example "(scan-chan (lambda (y) (> y .1)))"
  #endif
  #if HAVE_RUBY
    #define scan_chan_example "scan_chan(lambda do |y| y > 0.1 end)"
  #endif
  #if HAVE_FORTH
    #define scan_chan_example "lambda: <{ y }> y 0.1 f> ; scan-chan"
  #endif

  #define H_scan_chan "(" S_scan_chan " func :optional (start 0) (end len) snd chn edpos): \
apply 'func' to samples in current channel (or the specified channel). \
'func' is a function of one argument, the current sample. \
if 'func' returns non-" PROC_FALSE ", the scan stops, and the current sample number is returned.\n  " scan_chan_example

  Snd_assert_channel(S_scan_chan, snd, chn, 4); 

#if HAVE_SCHEME
  {
    s7_pointer result;
    s7_vector_set(s7, gc_vect, GC_FUNC, proc);
    result = g_sp_scan(proc, beg, end, snd, chn, S_scan_chan, false, edpos, 6, Xen_false);
    return(result);
  }
#else
  return(g_sp_scan(proc, beg, end, snd, chn, S_scan_chan, false, edpos, 6, Xen_false));
#endif
}
#endif

static Xen g_scan_channel(Xen proc, Xen beg, Xen dur, Xen snd, Xen chn, Xen edpos) 
{ 
  #if HAVE_SCHEME
    #define scan_channel_example "(scan-channel (lambda (y) (> y .1)))"
  #endif
  #if HAVE_RUBY
    #define scan_channel_example "scan_channel(lambda do |y| y > 0.1 end)"
  #endif
  #if HAVE_FORTH
    #define scan_channel_example "lambda: <{ y }> y 0.1 f> ; scan-channel"
  #endif

  #define H_scan_channel "(" S_scan_channel " func :optional (start 0) (dur len) snd chn edpos): \
apply func to samples in current channel (or the specified channel). \
func is a function of one argument, the current sample. \
if func returns non-" PROC_FALSE ", the scan stops, and the current sample number is returned. \n  " scan_channel_example

  Snd_assert_channel(S_scan_channel, snd, chn, 4); 

#if HAVE_SCHEME
  {
    s7_pointer result;
    s7_vector_set(s7, gc_vect, GC_FUNC, proc);
    result = g_sp_scan(proc, beg, Xen_false, snd, chn, S_scan_channel, false, edpos, 6, (Xen_is_bound(dur)) ? dur : Xen_false);
    return(result);
  }
#else
  return(g_sp_scan(proc, beg, Xen_false, snd, chn, S_scan_channel, false, edpos, 6, (Xen_is_bound(dur)) ? dur : Xen_false));
#endif
}


#if (!HAVE_SCHEME)
static Xen g_map_chan(Xen proc, Xen s_beg, Xen s_end, Xen org, Xen snd, Xen chn, Xen edpos) 
{
  #if HAVE_SCHEME
    #define map_chan_example "(map-chan (lambda (y) (* y 2.0)))"
  #endif
  #if HAVE_RUBY
    #define map_chan_example "map_chan(lambda do |y| y * 2.0 end)"
  #endif
  #if HAVE_FORTH
    #define map_chan_example "lambda: <{ y }> y 2.0 f* ; map-chan"
  #endif

  #define H_map_chan "(" S_map_chan " func :optional (start 0) (end len) edname snd chn edpos): \
apply func to samples in current channel; edname is the edit history name for this editing operation.\n  " map_chan_example

#if HAVE_SCHEME
  {
    s7_pointer result;
    s7_vector_set(s7, gc_vect, GC_FUNC, proc);
    result = g_map_chan_1(proc, s_beg, s_end, org, snd, chn, edpos, Xen_false, S_map_chan);
    return(result);
  }
#else
  return(g_map_chan_1(proc, s_beg, s_end, org, snd, chn, edpos, Xen_false, S_map_chan));
#endif
}
#endif


static Xen g_map_channel(Xen proc, Xen s_beg, Xen s_dur, Xen snd, Xen chn, Xen edpos, Xen org) 
{
  #if HAVE_SCHEME
    #define map_channel_example "(map-channel (lambda (y) (* y 2.0)))"
  #endif
  #if HAVE_RUBY
    #define map_channel_example "map_channel(lambda do |y| y * 2.0 end)"
  #endif
  #if HAVE_FORTH
    #define map_channel_example "lambda: <{ y }> y 2.0 f* ; map-channel"
  #endif

  #define H_map_channel "(" S_map_channel " func :optional (start 0) (dur len) snd chn edpos edname): \
apply func to samples in current channel; edname is the edit history name for this editing operation.\n  " map_channel_example

#if HAVE_SCHEME
  {
    s7_pointer result;
    s7_vector_set(s7, gc_vect, GC_FUNC, proc);
    result = g_map_chan_1(proc, s_beg, Xen_false, org, snd, chn, edpos, (Xen_is_bound(s_dur)) ? s_dur : Xen_false, S_map_channel);
    return(result);
  }
#else
  return(g_map_chan_1(proc, s_beg, Xen_false, org, snd, chn, edpos, (Xen_is_bound(s_dur)) ? s_dur : Xen_false, S_map_channel));
#endif
}


#if (!HAVE_SCHEME)
static Xen g_find_channel(Xen expr, Xen sample, Xen snd, Xen chn_n, Xen edpos)
{
  #if HAVE_SCHEME
    #define find_channel_example "(find-channel (lambda (y) (> y .1)))"
  #endif
  #if HAVE_RUBY
    #define find_channel_example "find_channel(lambda do |y| y > 0.1 end)"
  #endif
  #if HAVE_FORTH
    #define find_channel_example "lambda: <{ y }> y 0.1 f> ; find-channel"
  #endif

  #define H_find_channel "(" S_find_channel " func :optional (start-samp 0) snd chn edpos): apply func, a function of one argument, \
the current sample, to each sample in snd's channel chn, starting at 'start-samp' until func returns something other than " PROC_FALSE ": \n  " find_channel_example

  /* no free here -- it's handled as ss->search_expr in snd-find.c */
  Snd_assert_channel(S_find_channel, snd, chn_n, 3);
  return(g_sp_scan(expr, sample, Xen_false, snd, chn_n, S_find_channel, false, edpos, 5, Xen_false));
}
#endif


static Xen g_count_matches(Xen expr, Xen sample, Xen snd, Xen chn_n, Xen edpos)
{
  #if HAVE_SCHEME
    #define count_matches_example "(count-matches (lambda (y) (> y .1)))"
  #endif
  #if HAVE_RUBY
    #define count_matches_example "count_matches(lambda do |y| y > 0.1 end)"
  #endif
  #if HAVE_FORTH
    #define count_matches_example "lambda: <{ y }> y 0.1 f> ; count-matches"
  #endif

  #define H_count_matches "(" S_count_matches " func :optional (start-samp 0) snd chn edpos): return how many \
samples satisfy func (a function of one argument, the current sample, returning " PROC_TRUE " upon match):\n  " count_matches_example

  Snd_assert_channel(S_count_matches, snd, chn_n, 3);
  return(g_sp_scan(expr, sample, Xen_false, snd, chn_n, S_count_matches, true, edpos, 5, Xen_false));
}


static Xen g_smooth_sound(Xen beg, Xen num, Xen snd, Xen chn_n)
{
  #define H_smooth_sound "(" S_smooth_sound " :optional (start-samp 0) (samps len) snd chn): smooth \
data from start-samp for samps in snd's channel chn"
  chan_info *cp;
  mus_long_t start, samps;

  Snd_assert_sample_type(S_smooth_sound, beg, 1);
  Snd_assert_sample_type(S_smooth_sound, num, 2);
  Snd_assert_channel(S_smooth_sound, snd, chn_n, 3);

  cp = get_cp(snd, chn_n, S_smooth_sound);
  if (!cp) return(Xen_false);
  start = beg_to_sample(beg, S_smooth_sound);
  samps = dur_to_samples(num, start, cp, cp->edit_ctr, 2, S_smooth_sound);

  cos_smooth(cp, start, samps, OVER_SOUND); 

  return(beg);
}


static Xen g_smooth_channel(Xen beg, Xen dur, Xen snd, Xen chn_n, Xen edpos)
{
  #define H_smooth_channel "(" S_smooth_channel " :optional (beg 0) (dur len) snd chn edpos): \
smooth data from beg for dur in snd's channel chn"
  chan_info *cp;
  mus_long_t start, num;
  int pos;

  Snd_assert_sample_type(S_smooth_channel, beg, 1);
  Snd_assert_sample_type(S_smooth_channel, dur, 2);
  Snd_assert_channel(S_smooth_channel, snd, chn_n, 3);

  cp = get_cp(snd, chn_n, S_smooth_channel);
  if (!cp) return(Xen_false);
  pos = to_c_edit_position(cp, edpos, S_smooth_channel, 5);
  start = beg_to_sample(beg, S_smooth_channel);
  num = dur_to_samples(dur, start, cp, pos, 2, S_smooth_channel);

  if ((start < cp->edits[pos]->samples) &&
      (num > 0))
    smooth_channel(cp, start, num, pos);

  return(beg);
}


static Xen g_smooth_selection(void)
{
  #define H_smooth_selection "(" S_smooth_selection "): smooth the data in the currently selected portion"
  chan_info *cp;

  if (!(selection_is_active())) 
    return(snd_no_active_selection_error(S_smooth_selection));
  cp = get_cp(Xen_false, Xen_false, S_smooth_selection);
  if (!cp) return(Xen_false);

  cos_smooth(cp, 0, 0, OVER_SELECTION);

  return(Xen_true);
}


static void cut_and_smooth_1(chan_info *cp, mus_long_t beg, mus_long_t end, bool over_selection, int pos)
{
  #define SPLICE_LEN 32
  /* making this 128 is not a big improvement */
  mus_long_t start;
  mus_float_t splice[2 * SPLICE_LEN];
  double ramp, incr;
  int i;
  snd_fd *sf, *sf_end;
  
  incr = 0.5 / SPLICE_LEN;
  if (end < SPLICE_LEN)
    start = 0;
  else start = end - SPLICE_LEN;
  
  sf_end = init_sample_read_any_with_bufsize(start, cp, READ_FORWARD, pos, 2 * SPLICE_LEN);
  
  if (beg < SPLICE_LEN)
    start = 0;
  else start = beg - SPLICE_LEN;
  
  sf = init_sample_read_any_with_bufsize(start, cp, READ_FORWARD, pos, 2 * SPLICE_LEN);
  for (i = 0, ramp = 1.0; i < 2 * SPLICE_LEN; i++, ramp -= incr)
    {
      mus_float_t x, y;
      x = read_sample(sf);
      y = read_sample(sf_end);
      splice[i] = (x * ramp) + (y * (1.0 - ramp));
    }
  free_snd_fd(sf);
  free_snd_fd(sf_end);
  
  if (over_selection)
    cp_delete_selection(cp);
  else delete_samples(beg, end - beg + 1, cp, pos);
  
  change_samples(start, 2 * SPLICE_LEN, splice, cp, 
		 (over_selection) ? S_delete_selection_and_smooth : S_delete_samples_and_smooth, 
		 cp->edit_ctr, -1.0);
}


void cut_and_smooth(chan_info *cp)
{
  if (selection_is_active_in_channel(cp))
    cut_and_smooth_1(cp, selection_beg(cp), selection_end(cp), true, cp->edit_ctr);
}


static Xen g_delete_selection_and_smooth(void)
{
  #define H_delete_selection_and_smooth "(" S_delete_selection_and_smooth ") deletes the current selection, and tries to \
make the splice-point smooth."

  if (!(selection_is_active())) 
    return(snd_no_active_selection_error(S_delete_selection_and_smooth));
  for_each_chan(cut_and_smooth);
  return(Xen_false);
}



static Xen g_delete_samples_and_smooth(Xen samp_n, Xen samps, Xen snd, Xen chn_n, Xen edpos)
{
  #define H_delete_samples_and_smooth "(" S_delete_samples_and_smooth " start-samp samps :optional snd chn edpos): \
delete 'samps' samples from snd's channel chn starting at 'start-samp', then try to smooth-over the splice"

  chan_info *cp;
  int pos;
  mus_long_t samp, len;

  Xen_check_type(Xen_is_integer(samp_n), samp_n, 1, S_delete_samples_and_smooth, "an integer");
  Xen_check_type(Xen_is_llong(samps), samps, 2, S_delete_samples_and_smooth, "an integer");

  Snd_assert_channel(S_delete_samples_and_smooth, snd, chn_n, 3);
  cp = get_cp(snd, chn_n, S_delete_samples_and_smooth);
  if (!cp) return(Xen_false);

  pos = to_c_edit_position(cp, edpos, S_delete_samples_and_smooth, 5);
  samp = beg_to_sample(samp_n, S_delete_samples_and_smooth);
  if (samp > cp->edits[pos]->samples)
    Xen_out_of_range_error(S_delete_samples_and_smooth, 1, samp_n, "beyond end of sound");

  len = Xen_llong_to_C_llong(samps);
  if (len <= 0) return(Xen_false);
  if (len > cp->edits[pos]->samples)
    len = cp->edits[pos]->samples;

  cut_and_smooth_1(cp, samp, samp + len - 1, false, pos);
  update_graph(cp);
  return(samp_n);
}



static Xen g_reverse_sound(Xen snd, Xen chn_n, Xen edpos)
{
  #define H_reverse_sound "(" S_reverse_sound " :optional snd chn edpos): reverse snd's channel chn"
  chan_info *cp;

  Snd_assert_channel(S_reverse_sound, snd, chn_n, 1);
  cp = get_cp(snd, chn_n, S_reverse_sound);
  if (!cp) return(Xen_false);

  reverse_sound(cp, OVER_SOUND, edpos, 3);

  return(Xen_false);
}


static Xen g_reverse_selection(void)
{
  #define H_reverse_selection "(" S_reverse_selection "): reverse the data in the currently selected portion"
  chan_info *cp;

  if (!(selection_is_active())) 
    return(snd_no_active_selection_error(S_reverse_selection));

  cp = get_cp(Xen_false, Xen_false, S_reverse_selection);
  if (!cp) return(Xen_false);

  reverse_sound(cp, OVER_SELECTION, C_int_to_Xen_integer(AT_CURRENT_EDIT_POSITION), 0);

  return(Xen_false);
}


static Xen g_reverse_channel(Xen s_beg, Xen s_dur, Xen snd, Xen chn_n, Xen edpos)
{
  #define H_reverse_channel "(" S_reverse_channel " :optional (beg 0) (dur len) snd chn edpos): reverse a portion of snd's channel chn"
  chan_info *cp;
  char *errmsg;
  mus_long_t beg = 0, dur = 0, end;
  int pos;
  snd_fd *sf;

  Snd_assert_sample_type(S_reverse_channel, s_beg, 1);
  Snd_assert_sample_type(S_reverse_channel, s_dur, 2);
  Snd_assert_channel(S_reverse_channel, snd, chn_n, 3);

  cp = get_cp(snd, chn_n, S_reverse_channel);
  if (!cp) return(Xen_false);
  beg = beg_to_sample(s_beg, S_reverse_channel);
  pos = to_c_edit_position(cp, edpos, S_reverse_channel, 5);
  dur = dur_to_samples(s_dur, beg, cp, pos, 2, S_reverse_channel);
  if ((beg > cp->edits[pos]->samples) || (dur == 0)) return(Xen_false);
  end = beg + dur;
  if (end > cp->edits[pos]->samples)
    end = cp->edits[pos]->samples;

  sf = init_sample_read_any(end - 1, cp, READ_BACKWARD, pos);
  errmsg = reverse_channel(cp, sf, beg, end - beg, edpos, S_reverse_channel, 5);

  free_snd_fd(sf);
  if (errmsg)
    {
      Xen str;
      str = C_string_to_Xen_string(errmsg);
      free(errmsg);
      Xen_error(Xen_make_error_type("IO-error"),
		Xen_list_2(C_string_to_Xen_string(S_reverse_channel ": IO error ~A"),
			   str));
    }
  return(s_beg);
}


static Xen g_insert_silence(Xen beg, Xen num, Xen snd, Xen chn)
{
  #define H_insert_silence "(" S_insert_silence " beg num :optional snd chn): insert num zeros at beg in snd's chn"
  chan_info *cp; /* follows sync */
  mus_long_t start = 0, len = 0;

  Xen_check_type(Xen_is_integer(beg), beg, 1, S_insert_silence, "an integer");
  Xen_check_type(Xen_is_integer(num), num, 2, S_insert_silence, "an integer");
  Snd_assert_channel(S_insert_silence, snd, chn, 3);

  cp = get_cp(snd, chn, S_insert_silence);
  if (!cp) return(Xen_false);

  start = beg_to_sample(beg, S_insert_silence);
  len = Xen_llong_to_C_llong(num);
  if (len <= 0) return(Xen_false);
  if (len > (1LL << 34))
    Xen_out_of_range_error(S_insert_silence, 2, num, "too large");

  cursor_insert(cp, start, len);

  return(beg);
}


static Xen g_pad_channel(Xen beg, Xen num, Xen snd, Xen chn, Xen edpos)
{
  #define H_pad_channel "(" S_pad_channel " beg dur :optional snd chn edpos): insert dur zeros at beg in snd's chn"
  chan_info *cp;
  mus_long_t bg, len;
  int pos;

  Xen_check_type(Xen_is_integer(beg), beg, 1, S_pad_channel, "an integer");
  Xen_check_type(Xen_is_integer(num), num, 2, S_pad_channel, "an integer");
  Snd_assert_channel(S_pad_channel, snd, chn, 3);

  cp = get_cp(snd, chn, S_pad_channel);
  if (!cp) return(Xen_false);

  bg = beg_to_sample(beg, S_pad_channel);

  len = Xen_llong_to_C_llong(num);
  if (len <= 0) return(Xen_false); /* to parallel insert-silence above -- maybe better would be an out of range error in both cases */
  if (len > (1LL << 34))
    Xen_out_of_range_error(S_pad_channel, 2, num, "too large");

  pos = to_c_edit_position(cp, edpos, S_pad_channel, 5);

  if ((len > 0) &&
      (extend_with_zeros(cp, bg, len, pos, S_pad_channel)))
    update_graph(cp);
  return(beg);
}


static Xen g_swap_channels(Xen snd0, Xen chn0, Xen snd1, Xen chn1, Xen beg, Xen dur, Xen edpos0, Xen edpos1)
{
  #define H_swap_channels "(" S_swap_channels " :optional snd0 chn0 snd1 chn1 (beg 0) (dur len) edpos0 edpos1): \
swap the indicated channels"
  chan_info *cp0 = NULL, *cp1 = NULL;
  snd_info *sp = NULL;

  Snd_assert_channel(S_swap_channels, snd0, chn0, 1);

  cp0 = get_cp(snd0, chn0, S_swap_channels);
  if (!cp0) return(Xen_false);
  if (!(cp0->editable)) return(Xen_false);

  if (Xen_is_integer(chn1))
    {
      Snd_assert_channel(S_swap_channels, snd1, chn1, 3);
      cp1 = get_cp(snd1, chn1, S_swap_channels);
    }
  else
    {
      if (Xen_is_integer(snd1) || xen_is_sound(snd1))
	sp = get_sp(snd1);
      else sp = cp0->sound;
      if (!sp) 
	return(snd_no_such_sound_error(S_swap_channels, snd1));
      if (cp0->sound == sp)
	{
	  if ((cp0->chan + 1) < (int)sp->nchans)
	    cp1 = sp->chans[cp0->chan + 1];
	  else cp1 = sp->chans[0];
	}
      else cp1 = sp->chans[0];
    }

  if (cp0 == cp1) return(Xen_false);
  if (!(cp1->editable)) return(Xen_false);
  if ((cp0) && (cp1))
    {
      int pos0, pos1;
      mus_long_t dur0, dur1, beg0 = 0, num;

      if (Xen_is_integer(beg)) 
	beg0 = Xen_llong_to_C_llong(beg);

      pos0 = to_c_edit_position(cp0, edpos0, S_swap_channels, 7);
      pos1 = to_c_edit_position(cp1, edpos1, S_swap_channels, 8);

      dur0 = cp0->edits[pos0]->samples;
      dur1 = cp1->edits[pos1]->samples;

      if (Xen_is_integer(dur)) 
	num = Xen_llong_to_C_llong(dur);
      else
	{
	  if (dur0 > dur1) 
	    num = dur0; 
	  else num = dur1; /* was min here 13-Dec-02 */
	}

      if ((beg0 != 0) ||
	  ((num != dur0) && (num != dur1))) /* if just a section being swapped, use readers */
	swap_channels(cp0, cp1, beg0, num, pos0, pos1);

      else
	{
	  if ((pos0 == 0) &&
	      (pos1 == 0))
	    {
	      /* common special case -- just setup a new ed-list entry with the channels/sounds swapped */
	      if ((dur0 == 0) && (dur1 == 0)) return(Xen_false);
	      if ((is_editable(cp0)) && (is_editable(cp1)))
		{
		  peak_env_info *e0, *e1;
		  e0 = peak_env_copy(cp0, false, cp0->edit_ctr);
		  e1 = peak_env_copy(cp1, false, cp1->edit_ctr);
		  file_override_samples(dur1, cp1->sound->filename, cp0, cp1->chan, DONT_DELETE_ME, S_swap_channels);
		  file_override_samples(dur0, cp0->sound->filename, cp1, cp0->chan, DONT_DELETE_ME, S_swap_channels);
		  cp0->edits[cp0->edit_ctr]->peak_env = e1; /* can be NULL */
		  cp1->edits[cp1->edit_ctr]->peak_env = e0;
		  swap_marks(cp0, cp1);
		  update_graph(cp0);
		  update_graph(cp1);
		}
	    }
	  else
	    {
	      /* look for simple cases where copying the current edit tree entry is not too hard */
	      if ((num < FILE_BUFFER_SIZE) ||
		  (sound_fragments_in_use(cp0, pos0)) ||
		  (sound_fragments_in_use(cp1, pos1)))
		swap_channels(cp0, cp1, beg0, num, pos0, pos1);
	      else copy_then_swap_channels(cp0, cp1, pos0, pos1); /* snd-edits.c */
	    }
	}
    }
  return(Xen_false);
}


static mus_float_t *load_mus_float_ts(Xen scalers, int *result_len, const char *caller)
{
  uint32_t len = 0, i;
  mus_float_t *scls;
  vct *v = NULL;
  if (Xen_is_number(scalers))
    len = 1;
  else
    {
      if (mus_is_vct(scalers))
	{
	  v = Xen_to_vct(scalers);
	  len = (uint32_t)mus_vct_length(v);
	}
      else
	{
	  if (Xen_is_list(scalers))
	    {
	      int lst_len;
	      lst_len = Xen_list_length(scalers);
	      if (lst_len < 0)
		Xen_wrong_type_arg_error(caller, 1, scalers, "a proper list");
	      len = (uint32_t)lst_len;
	    }
	  else Xen_wrong_type_arg_error(caller, 1, scalers, "a number, list, or " S_vct);
	}

      if (len == 0) 
	Xen_error(NO_DATA,
		  Xen_list_2(C_string_to_Xen_string("~A: scalers data is empty?"), 
			     C_string_to_Xen_string(caller)));
    }
  
  scls = (mus_float_t *)calloc(len, sizeof(mus_float_t));
  if (v)
    mus_copy_floats(scls, mus_vct_data(v), len);
  else
    {
      if (Xen_is_list(scalers))
	{
	  Xen lst;
	  for (i = 0, lst = Xen_copy_arg(scalers); i < len; i++, lst = Xen_cdr(lst)) 
	    scls[i] = (mus_float_t)Xen_real_to_C_double(Xen_car(lst));
	}
      else scls[0] = (mus_float_t)Xen_real_to_C_double(scalers);
    }
  result_len[0] = len;
  return(scls);
}


static Xen g_scale_to(Xen scalers, Xen snd, Xen chn_n)
{
  #define H_scale_to "(" S_scale_to " :optional (norms 1.0) snd chn): \
normalize snd to norms (following sync); norms can be a float or a " S_vct "/list of floats"

  /* chn_n irrelevant if sync */
  chan_info *cp;
  bool happy;
  int len[1];
  mus_float_t *scls;

  Snd_assert_channel(S_scale_to, snd, chn_n, 2);
  cp = get_cp(snd, chn_n, S_scale_to);
  if (!cp) return(Xen_false);

  scls = load_mus_float_ts(scalers, len, S_scale_to);
  happy = scale_to(cp->sound, cp, scls, len[0], OVER_SOUND);

  free(scls);
  if (happy)
    return(scalers);
  return(Xen_false);
}


static Xen g_scale_by(Xen scalers, Xen snd, Xen chn_n)
{
  #define H_scale_by "(" S_scale_by " scalers :optional snd chn): \
scale snd by scalers (following sync); scalers can be a float or a " S_vct "/list of floats"

  /* chn_n irrelevant if sync */
  chan_info *cp;
  int len[1];
  mus_float_t *scls;

  Snd_assert_channel(S_scale_by, snd, chn_n, 2);
  cp = get_cp(snd, chn_n, S_scale_by);
  if (!cp) return(Xen_false);
  len[0] = 0;

  /* fprintf(stderr, "(scale-by %s %s %s)\n", Xen_object_to_C_string(scalers), Xen_object_to_C_string(snd), Xen_object_to_C_string(chn_n)); */

  scls = load_mus_float_ts(scalers, len, S_scale_by);
  if (len[0] == 0)
    {
      /* fprintf(stderr, "len is 0\n"); */
      return(Xen_false);
    }
  scale_by(cp, scls, len[0], OVER_SOUND);

  free(scls);
  return(scalers);
}


static Xen g_scale_selection_to(Xen scalers)
{
  #define H_scale_selection_to "(" S_scale_selection_to " norms): normalize selected portion to norms"
  if (selection_is_active())
    {
      int len[1];
      bool happy;
      mus_float_t *scls;

      scls = load_mus_float_ts(scalers, len, S_scale_selection_to);
      happy = scale_to(NULL, NULL, scls, len[0], OVER_SELECTION);

      free(scls);
      if (happy)
	return(scalers);
      return(Xen_false);
    }
  return(snd_no_active_selection_error(S_scale_selection_to));
}


Xen g_scale_selection_by(Xen scalers)
{
  #define H_scale_selection_by "(" S_scale_selection_by " scalers): scale selected portion by scalers"
  if (selection_is_active())
    {
      int len[1];
      mus_float_t *scls;

      scls = load_mus_float_ts(scalers, len, S_scale_selection_by);
      scale_by(NULL, scls, len[0], OVER_SELECTION);

      free(scls);
      return(scalers);
    }
  return(snd_no_active_selection_error(S_scale_selection_by));
}


static Xen g_clm_channel(Xen gen, Xen samp_n, Xen samps, Xen snd, Xen chn_n, Xen edpos, Xen overlap, Xen origin)
{
  #define H_clm_channel "(" S_clm_channel " gen :optional (beg 0) (dur len) snd chn edpos (overlap 0) origin): \
apply gen to snd's channel chn starting at beg for dur samples. overlap is the 'ring' time, if any."

  chan_info *cp;
  mus_long_t beg = 0, dur = 0;
  int pos;
  mus_any *egen;
  char *errmsg = NULL, *caller = NULL;

  Snd_assert_sample_type(S_clm_channel, samp_n, 2);
  Snd_assert_sample_type(S_clm_channel, samps, 3);
  Xen_check_type(Xen_is_integer(overlap) || Xen_is_false(overlap) || !Xen_is_bound(overlap), overlap, 7, S_clm_channel, "an integer or " PROC_FALSE);
  Xen_check_type(Xen_is_string_or_unbound(origin), origin, 8, S_clm_channel, "a string");
  Snd_assert_channel(S_clm_channel, snd, chn_n, 4);

  cp = get_cp(snd, chn_n, S_clm_channel);
  if (!cp) return(Xen_false);
  pos = to_c_edit_position(cp, edpos, S_clm_channel, 6);
  beg = beg_to_sample(samp_n, S_clm_channel);
  dur = dur_to_samples(samps, beg, cp, pos, 3, S_clm_channel);
  if (dur == 0) return(Xen_false);
  Xen_check_type(mus_is_xen(gen), gen, 1, S_clm_channel, "a clm generator");
  egen = Xen_to_mus_any(gen);
  if (Xen_is_string(origin)) caller = mus_strdup(Xen_string_to_C_string(origin)); else caller = mus_strdup(S_clm_channel);

  errmsg = clm_channel(cp, egen, beg, dur, pos, (Xen_is_llong(overlap)) ? Xen_llong_to_C_llong(overlap) : 0, caller);

  free(caller);
  if (errmsg)
    {
      Xen str;
      str = C_string_to_Xen_string(errmsg);
      free(errmsg);
      Xen_error(Xen_make_error_type("IO-error"),
		Xen_list_2(C_string_to_Xen_string(S_clm_channel ": IO error ~A"),
			   str));
    }
  return(gen);
}


static Xen g_apply_env_1(Xen edata, mus_long_t beg, mus_long_t dur, Xen ebase, chan_info *cp, Xen edpos, const char *caller, bool over_selection)
{
  if (Xen_is_list(edata))
    {
      env *e;
      e = get_env(edata, caller);
      if (e)
	{
	  if (Xen_is_number(ebase))
	    {
	      /* env 'e' is a temp here, so we can clobber its base, etc */
	      e->base = Xen_real_to_C_double(ebase);
	      if (e->base < 0.0)
		{
		  free_env(e);
		  Xen_out_of_range_error(caller, 4, ebase, "base < 0.0?");
		}
	    }
	  apply_env(cp, e, beg, dur, over_selection, caller, NULL, edpos, 7);
	  free_env(e);
	  return(edata);
	}
    }
  else
    {
      mus_any *egen = NULL;
      Xen_check_type((mus_is_xen(edata)) && (mus_is_env(egen = Xen_to_mus_any(edata))), edata, 1, caller, "an env generator or a list");
      apply_env(cp, NULL, beg, dur, over_selection, caller, egen, edpos, 7);
      return(edata);
    }
  return(Xen_false);
}


static Xen g_env_selection(Xen edata, Xen base)
{
  #define H_env_selection "(" S_env_selection " env :optional (env-base 1.0)): \
apply envelope to the selection using env-base to determine how breakpoints are connected"

  if (!(selection_is_active())) 
    return(snd_no_active_selection_error(S_env_selection));
  return(g_apply_env_1(edata, 0, 0, base, 
		 get_cp(Xen_false, Xen_false, S_env_selection), 
		 C_int_to_Xen_integer(AT_CURRENT_EDIT_POSITION), 
		 S_env_selection, OVER_SELECTION));
}


static Xen g_env_sound(Xen edata, Xen samp_n, Xen samps, Xen base, Xen snd, Xen chn_n, Xen edpos)
{
  #define H_env_sound "(" S_env_sound " env :optional (start-samp 0) (samps len) (env-base 1.0) snd chn edpos): \
apply amplitude envelope (a list of breakpoints or a CLM env) to snd's channel chn starting at start-samp, going \
either to the end of the sound or for samps samples, with segments interpolating according to env-base"

  mus_long_t beg = 0, dur = 0;
  int pos;
  chan_info *cp;

  Snd_assert_sample_type(S_env_sound, samp_n, 2);
  Snd_assert_sample_type(S_env_sound, samps, 3);
  Snd_assert_channel(S_env_sound, snd, chn_n, 5);

  cp = get_cp(snd, chn_n, S_env_sound);
  if (!cp) return(Xen_false);
  pos = to_c_edit_position(cp, edpos, S_env_sound, 7);
  beg = beg_to_sample(samp_n, S_env_sound);
  dur = dur_to_samples(samps, beg, cp, pos, 3, S_env_sound);

  return(g_apply_env_1(edata, beg, dur, base, cp, edpos, S_env_sound, OVER_SOUND));
}


static Xen g_env_channel(Xen gen, Xen samp_n, Xen samps, Xen snd, Xen chn_n, Xen edpos)
{
  #define H_env_channel "(" S_env_channel " clm-env-gen-or-envelope :optional (beg 0) (dur len) snd chn edpos): \
apply amplitude envelope to snd's channel chn starting at beg for dur samples."

  chan_info *cp;
  snd_info *sp;
  mus_long_t beg = 0, dur;
  int old_sync = 0, pos;
  Xen val;

  Snd_assert_sample_type(S_env_channel, samp_n, 2);
  Snd_assert_sample_type(S_env_channel, samps, 3);
  Snd_assert_channel(S_env_channel, snd, chn_n, 4);

  cp = get_cp(snd, chn_n, S_env_channel);
  if (!cp) return(Xen_false);
  beg = beg_to_sample(samp_n, S_env_channel);
  pos = to_c_edit_position(cp, edpos, S_env_channel, 6);
  dur = dur_to_samples(samps, beg, cp, pos, 3, S_env_channel);
  if (dur == 0) return(Xen_false);
  if (beg > cp->edits[pos]->samples) return(Xen_false); /* not redundant */
  sp = cp->sound;
  old_sync = sp->sync;
  sp->sync = 0;

  val = g_apply_env_1(gen, beg, dur, Xen_false, cp, edpos, S_env_channel, OVER_SOUND);

  sp->sync = old_sync;
  return(val);
}


static Xen g_env_channel_with_base(Xen gen, Xen base, Xen samp_n, Xen samps, Xen snd, Xen chn_n, Xen edpos)
{
  #define H_env_channel_with_base "(" S_env_channel_with_base " clm-env-gen-or-envelope :optional (base 1.0) (beg 0) (dur len) snd chn edpos): \
apply amplitude envelope to snd's channel chn starting at beg for dur samples."

  chan_info *cp;
  snd_info *sp;
  mus_long_t beg = 0, dur;
  int old_sync = 0, pos;
  Xen val;

  Snd_assert_sample_type(S_env_channel, samp_n, 2);
  Snd_assert_sample_type(S_env_channel, samps, 3);
  Snd_assert_channel(S_env_channel, snd, chn_n, 4);

  cp = get_cp(snd, chn_n, S_env_channel);
  if (!cp) return(Xen_false);
  beg = beg_to_sample(samp_n, S_env_channel);
  pos = to_c_edit_position(cp, edpos, S_env_channel, 6);
  dur = dur_to_samples(samps, beg, cp, pos, 3, S_env_channel);
  if (dur == 0) return(Xen_false);
  if (beg > cp->edits[pos]->samples) return(Xen_false); /* not redundant */
  sp = cp->sound;
  old_sync = sp->sync;
  sp->sync = 0;

  val = g_apply_env_1(gen, beg, dur, base, cp, edpos, S_env_channel, OVER_SOUND);

  sp->sync = old_sync;
  return(val);
}


static Xen g_ramp_channel(Xen rmp0, Xen rmp1, Xen beg, Xen num, Xen snd, Xen chn, Xen edpos)
{
  #define H_ramp_channel "(" S_ramp_channel " rmp0 rmp1 :optional (beg 0) (dur len) snd chn edpos): \
scale samples in the given sound/channel between beg and beg + num by a ramp going from rmp0 to rmp1."

  chan_info *cp;
  mus_long_t samp, samps;
  int pos;
  double seg0, seg1;

  Xen_check_type(Xen_is_number(rmp0), rmp0, 1, S_ramp_channel, "a number");
  Xen_check_type(Xen_is_number(rmp1), rmp1, 2, S_ramp_channel, "a number");
  Snd_assert_sample_type(S_ramp_channel, beg, 3);
  Snd_assert_sample_type(S_ramp_channel, num, 4);
  Snd_assert_sound(S_ramp_channel, snd, 5);

  samp = beg_to_sample(beg, S_ramp_channel);
  cp = get_cp(snd, chn, S_ramp_channel);
  if (!cp) return(Xen_false);
  pos = to_c_edit_position(cp, edpos, S_ramp_channel, 7);
  samps = dur_to_samples(num, samp, cp, pos, 4, S_ramp_channel);

  if (unrampable(cp, samp, samps, pos, false)) /* false - not xramp */
    {
      snd_info *sp;
      int old_sync;
      Xen val;

      sp = cp->sound;
      old_sync = sp->sync;
      sp->sync = 0;
      val = g_apply_env_1(Xen_list_4(C_double_to_Xen_real(0.0), rmp0, C_double_to_Xen_real(1.0), rmp1),
		    samp, samps, C_double_to_Xen_real(1.0), cp, edpos, S_ramp_channel, false);
      sp->sync = old_sync;
      return(val);
    }

  seg0 = Xen_real_to_C_double(rmp0);
  seg1 = Xen_real_to_C_double(rmp1);

  if (ramp_channel(cp, seg0, (seg1 - seg0) / (double)(samps - 1),
		   samp, samps, pos, NOT_IN_AS_ONE_EDIT))
    {
      if (cp->edits[pos]->peak_env)
	{
	  mus_float_t data[4];
	  data[0] = 0.0;
	  data[1] = seg0;
	  data[2] = 1.0;
	  data[3] = seg1;
	  if ((samp == 0) && 
	      (samps >= cp->edits[pos]->samples))
	    amp_env_env(cp, data, 2, pos, 1.0, 1.0, 0.0);
	  else 
	    {
	      mus_any *egen;
	      egen = mus_make_env(data, 2, 1.0, 0.0, 1.0, 0.0, samps - 1, NULL);
	      amp_env_env_selection_by(cp, egen, samp, samps, pos);
	      mus_free(egen);
	    }
	}
      update_graph(cp);
    }
  return(rmp0);
}			  


static Xen g_xramp_channel(Xen rmp0, Xen rmp1, Xen base, Xen beg, Xen num, Xen snd, Xen chn, Xen edpos)
{
  #define H_xramp_channel "(" S_xramp_channel " rmp0 rmp1 base :optional (beg 0) (dur len) snd chn edpos): \
scale samples in the given sound/channel between beg and beg + num by an exponential ramp going from rmp0 to rmp1 with curvature set by base."

  chan_info *cp;
  mus_long_t samp, samps;
  int pos;
  mus_float_t ebase = 1.0;

  Xen_check_type(Xen_is_number(rmp0), rmp0, 1, S_xramp_channel, "a number");
  Xen_check_type(Xen_is_number(rmp1), rmp1, 2, S_xramp_channel, "a number");
  Xen_check_type(Xen_is_number(base), base, 3, S_xramp_channel, "a number");
  Snd_assert_sample_type(S_xramp_channel, beg, 4);
  Snd_assert_sample_type(S_xramp_channel, num, 5);
  Snd_assert_sound(S_xramp_channel, snd, 6);

  samp = beg_to_sample(beg, S_xramp_channel);
  cp = get_cp(snd, chn, S_xramp_channel);
  if (!cp) return(Xen_false);
  pos = to_c_edit_position(cp, edpos, S_xramp_channel, 8);
  samps = dur_to_samples(num, samp, cp, pos, 4, S_xramp_channel);

  ebase = Xen_real_to_C_double(base);
  if (ebase < 0.0) 
    Xen_out_of_range_error(S_xramp_channel, 3, base, "base < 0.0?");
  if (ebase > 1.0e10)
    Xen_out_of_range_error(S_xramp_channel, 3, base, "base too large");

  if (unrampable(cp, samp, samps, pos, (ebase != 1.0)))
    {
      snd_info *sp;
      int old_sync;
      Xen val;
      sp = cp->sound;
      old_sync = sp->sync;
      sp->sync = 0;
      val = g_apply_env_1(Xen_list_4(C_double_to_Xen_real(0.0), rmp0, C_double_to_Xen_real(1.0), rmp1),
		    samp, samps, base, cp, edpos, S_xramp_channel, false);
      sp->sync = old_sync;
      return(val);
    }
  else
    {
      double seg0;

      seg0 = Xen_real_to_C_double(rmp0);

      if (ebase == 0.0)
	scale_channel(cp, seg0, samp, samps, pos, NOT_IN_AS_ONE_EDIT);
      else
	{
	  double seg1;
	  seg1 = Xen_real_to_C_double(rmp1);
	  if (ebase == 1.0)
	    ramp_channel(cp, seg0, (seg1 - seg0) / (double)(samps - 1), samp, samps, pos, NOT_IN_AS_ONE_EDIT);
	  else
	    {
	      mus_any *e;
	      mus_float_t *data;
	      mus_float_t *rates;
	      data = (mus_float_t *)malloc(4 * sizeof(mus_float_t));
	      data[0] = 0.0;
	      data[1] = seg0;
	      data[2] = 1.0;
	      data[3] = seg1;
	      e = mus_make_env(data, 2, 1.0, 0.0, ebase, 0.0, samps - 1, NULL);

	      rates = mus_env_rates(e);
	      if (xramp_channel(cp, mus_env_initial_power(e), rates[0], mus_env_scaler(e), mus_env_offset(e), samp, samps, pos, NOT_IN_AS_ONE_EDIT, e, 0))
		{
		  if (cp->edits[pos]->peak_env)
		    {
		      if ((samp == 0) && 
			  (samps >= cp->edits[pos]->samples))
			amp_env_env(cp, data, 2, pos, ebase, mus_env_scaler(e), mus_env_offset(e));
		      else 
			{
			  mus_any *egen;
			  egen = mus_make_env(data, 2, 1.0, 0.0, ebase, 0.0, samps - 1, NULL);
			  amp_env_env_selection_by(cp, egen, samp, samps, pos);
			  mus_free(egen);
			}
		    }
		}
	      free(data);
	      mus_free(e);
	      update_graph(cp);
	    }
	}
    }
  return(rmp0);
}			  


static Xen g_fft(Xen reals, Xen imag, Xen sign)
{
  #define H_fft "(" S_fft " reals imags :optional (sign 1)): fft the data returning the result in reals. \
If sign is -1, perform inverse fft.  Incoming data is in " S_vct "s."

  vct *v1 = NULL, *v2 = NULL;
  int n = 0, n2 = 0, isign = 1;
  bool need_free = false;
  mus_float_t *rl = NULL, *im = NULL;

  Xen_check_type(Xen_is_integer_or_unbound(sign), sign, 3, S_fft, "an integer");
  Xen_check_type(mus_is_vct(reals), reals, 1, S_fft, S_vct);
  Xen_check_type(mus_is_vct(imag), imag, 2, S_fft, S_vct);

  isign = (Xen_is_integer(sign)) ? Xen_integer_to_C_int(sign) : 1;
  v1 = Xen_to_vct(reals);
  v2 = Xen_to_vct(imag);

  n = mus_vct_length(v1);
  if (mus_vct_length(v2) < n) n = mus_vct_length(v2);
  if (n == 0) return(Xen_integer_zero);
  if (is_power_of_2(n))
    {
      n2 = n;
      rl = mus_vct_data(v1);
      im = mus_vct_data(v2);
    }
  else
    {
      int ipow;
      ipow = (int)ceil(log(n + 1) / log(2.0)); /* ceil because we're assuming below that n2 >= n */
      n2 = snd_int_pow2(ipow);
      rl = (mus_float_t *)calloc(n2, sizeof(mus_float_t));
      im = (mus_float_t *)calloc(n2, sizeof(mus_float_t));
      need_free = true;
      mus_copy_floats(rl, mus_vct_data(v1), n);
      mus_copy_floats(im, mus_vct_data(v2), n);
    }

  mus_fft(rl, im, n2, isign);

  if (need_free)
    {
      mus_copy_floats(mus_vct_data(v1), rl, n);
      mus_copy_floats(mus_vct_data(v2), im, n);
      free(rl);
      free(im);
    }
  return(reals);
}


static Xen g_snd_spectrum(Xen data, Xen win, Xen len, Xen linear_or_dB, Xen beta, Xen in_place, Xen normalized)
{
  #define H_snd_spectrum "(" S_snd_spectrum " data :optional (window " S_rectangular_window ") (len data-len) (linear " PROC_TRUE ") (beta 0.0) in-place (normalized " PROC_TRUE ")): \
magnitude spectrum of data (a " S_vct "), in data if in-place, using fft-window win and fft length len."

  bool linear = true, in_data = false, normed = true;
  int i, n, n2, wtype;
  mus_float_t maxa, lowest, b = 0.0;
  mus_float_t *rdat;
  vct *v;

  Xen_check_type((mus_is_vct(data)), data, 1, S_snd_spectrum, "a " S_vct);
  Xen_check_type(Xen_is_integer_or_unbound(win), win, 2, S_snd_spectrum, "an integer");
  Xen_check_type(Xen_is_integer_or_unbound(len), len, 3, S_snd_spectrum, "an integer");
  Xen_check_type(Xen_is_boolean_or_unbound(linear_or_dB), linear_or_dB, 4, S_snd_spectrum, "a boolean");
  Xen_check_type(Xen_is_number_or_unbound(beta), beta, 5, S_snd_spectrum, "a number");
  Xen_check_type(Xen_is_boolean_or_unbound(in_place), in_place, 6, S_snd_spectrum, "a boolean");
  Xen_check_type(Xen_is_boolean_or_unbound(normalized), normalized, 7, S_snd_spectrum, "a boolean");

  v = Xen_to_vct(data);
  n = (Xen_is_integer(len)) ? Xen_integer_to_C_int(len) : mus_vct_length(v);
  if (n > mus_vct_length(v)) n = mus_vct_length(v);
  if (n <= 0)
    Xen_out_of_range_error(S_snd_spectrum, 3, len, "length <= 0 or " S_vct " length == 0?");

  if (Xen_is_boolean(linear_or_dB)) linear = Xen_boolean_to_C_bool(linear_or_dB);
  if (Xen_is_boolean(in_place)) in_data = Xen_boolean_to_C_bool(in_place);
  if (Xen_is_boolean(normalized)) normed = Xen_boolean_to_C_bool(normalized);

  wtype = (Xen_is_integer(win)) ? Xen_integer_to_C_int(win) : (int)MUS_RECTANGULAR_WINDOW;
  if (!(mus_is_fft_window(wtype)))
    Xen_out_of_range_error(S_snd_spectrum, 2, win, "unknown fft window");

  if (Xen_is_number(beta)) b = Xen_real_to_C_double(beta);
  if (b < 0.0) b = 0.0; else if (b > 1.0) b = 1.0;

  if (!in_data)
    {
      mus_float_t *vdata;
      vdata = mus_vct_data(v);
      rdat = (mus_float_t *)malloc(n * sizeof(mus_float_t));
      if (n < mus_vct_length(v))
	for (i = 0; i < n; i++) rdat[i] = vdata[i];
      else mus_copy_floats(rdat, vdata, mus_vct_length(v));
    }
  else rdat = mus_vct_data(v);

  if (wtype != (int)MUS_RECTANGULAR_WINDOW)
    {
      mus_float_t *window;
      window = (mus_float_t *)calloc(n, sizeof(mus_float_t));
      mus_make_fft_window_with_window((mus_fft_window_t)wtype, n, b * fft_beta_max((mus_fft_window_t)wtype), 0.0, window);
      for (i = 0; i < n; i++) rdat[i] *= window[i];
      free(window);
    }
    
    n2 = n / 2;
  {
    int j;
    mus_float_t *idat;
    idat = (mus_float_t *)calloc(n, sizeof(mus_float_t));
    mus_fft(rdat, idat, n, 1);
    rdat[0] *= rdat[0];
    rdat[n2] *= rdat[n2];
    for (i = 1, j = n - 1; i < n2; i++, j--)
      {
	rdat[i] = rdat[i] * rdat[i] + idat[i] * idat[i];
	rdat[j] = rdat[i];
      }
    free(idat);
  }

  lowest = 0.000001;
  maxa = 0.0;
  n = n / 2;
  for (i = 0; i < n; i++)
    {
      mus_float_t val;
      val = rdat[i];
      if (val < lowest)
	rdat[i] = 0.0;
      else 
	{
	  rdat[i] = sqrt(val);
	  if (rdat[i] > maxa) maxa = rdat[i];
	}
    }
  if (maxa > 0.0)
    {
      if (normed)
	maxa = 1.0 / maxa;
      else maxa = 1.0;
      if (!linear) /* dB */
	{
	  mus_float_t todb;
	  todb = 20.0 / log(10.0);
	  for (i = 0; i < n; i++) 
	    if (rdat[i] > 0.0)
	      rdat[i] = todb * log(rdat[i] * maxa);
	    else rdat[i] = -90.0; /* min_dB(ss)? or could channel case be less? */
	}
      else 
	{
	  if (normed)
	    for (i = 0; i < n; i++) 
	      rdat[i] *= maxa;
	}
    }
  if (in_data)
    return(data);
  return(xen_make_vct(n, rdat)); /* xen_make_vct uses the data array directly (frees upon gc) */
}


static Xen g_convolve_with_1(Xen file, Xen new_amp, chan_info *cp, Xen edpos, const char *caller)
{
  /* cp NULL -> selection (see above) */
  mus_float_t amp;
  static char *fname = NULL;

  Xen_check_type(Xen_is_string(file), file, 1, caller, "a string");

  if (Xen_is_number(new_amp)) 
    amp = Xen_real_to_C_double(new_amp);
  else
    {
      if (Xen_is_false(new_amp))
	amp = 0.0;
      else amp = 1.0;
    }
  if (fname) free(fname);
  fname = mus_expand_filename(Xen_string_to_C_string(file));

  if (mus_file_probe(fname))
    {
      char *error;
      error = convolve_with_or_error(fname, amp, cp, edpos, 5);
      if (error)
	{
	  Xen errstr;
	  errstr = C_string_to_Xen_string(error);
	  free(error);
	  Xen_error(Xen_make_error_type("IO-error"),
		    Xen_list_3(C_string_to_Xen_string("~A: IO error ~A"),
			       C_string_to_Xen_string(caller),
			       errstr));
	}
    }
  else return(snd_no_such_file_error(caller, file));
  return(file);
}


static Xen g_convolve_with(Xen file, Xen new_amp, Xen snd, Xen chn_n, Xen edpos)
{
  #define H_convolve_with "(" S_convolve_with " file :optional (amp 1.0) snd chn edpos): \
convolve file with snd's channel chn (or the currently sync'd channels); amp is the resultant peak amp"

  chan_info *cp;
  Snd_assert_channel(S_convolve_with, snd, chn_n, 3);
  cp = get_cp(snd, chn_n, S_convolve_with);
  if (!cp) return(Xen_false);
  return(g_convolve_with_1(file, new_amp, cp, edpos, S_convolve_with));
}


static Xen g_convolve_selection_with(Xen file, Xen new_amp)
{
  #define H_convolve_selection_with "(" S_convolve_selection_with " file :optional (amp 1.0)): \
convolve the selection with file; amp is the resultant peak amp"

  if (!(selection_is_active())) 
    return(snd_no_active_selection_error(S_convolve_selection_with));
  return(g_convolve_with_1(file, new_amp, NULL, C_int_to_Xen_integer(AT_CURRENT_EDIT_POSITION), S_convolve_selection_with));
}


enum {SRC_ENV_NO_ERROR, SRC_ENV_HIT_ZERO, SRC_ENV_THROUGH_ZERO};

static mus_float_t check_src_envelope(int pts, mus_float_t *data, int *error)
{
  /* can't go through zero here, and if negative need to return 1.0 */
  int i;
  mus_float_t res = 0.0;
  (*error) = SRC_ENV_NO_ERROR;
  for (i = 0; i < (2 * pts); i += 2)
    if (data[i + 1] == 0.0)
      {
	(*error) = SRC_ENV_HIT_ZERO;
	return(res);
      }
    else
      {
	if (data[i + 1] < 0.0)
	  {
	    if (res <= 0.0)
	      res = -1.0;
	    else
	      {
		(*error) = SRC_ENV_THROUGH_ZERO;
		return(res);
	      }
	  }
	else
	  {
	    if (res >= 0)
	      res = 1.0;
	    else
	      {
		(*error) = SRC_ENV_THROUGH_ZERO;
		return(res);
	      }
	  }
      }
  return(res);
}


static bool is_NaN(double x) {return(x != x);}

static Xen g_src_channel(Xen ratio_or_env, Xen beg_n, Xen dur_n, Xen snd, Xen chn_n, Xen edpos)
{
  #define H_src_channel "(" S_src_channel " ratio-or-env :optional (beg 0) (dur len) snd chn edpos): \
sampling-rate convert snd's channel chn by ratio, or following an envelope (a list or a CLM env generator)."

  chan_info *cp;
  char *errmsg;
  snd_fd *sf;
  mus_long_t beg, dur;
  int pos;
  bool clm_err = false;
  mus_any *egen = NULL;
  bool need_free = false;
  mus_float_t ratio = 0.0; /* not 1.0 here! -- the zero is significant */
  env *e = NULL;

  Xen_check_type((Xen_is_number(ratio_or_env)) || 
		  (Xen_is_list(ratio_or_env)) ||
		  ((mus_is_xen(ratio_or_env)) && 
		   (mus_is_env(egen = Xen_to_mus_any(ratio_or_env)))),
		  ratio_or_env, 1, S_src_channel, "a number, an envelope, or a CLM env generator");
  Snd_assert_sample_type(S_src_channel, beg_n, 2);
  Snd_assert_sample_type(S_src_channel, dur_n, 3);
  Snd_assert_channel(S_src_channel, snd, chn_n, 4);

  cp = get_cp(snd, chn_n, S_src_channel);
  if (!cp) return(Xen_false);
  beg = beg_to_sample(beg_n, S_src_channel);
  pos = to_c_edit_position(cp, edpos, S_src_channel, 6);
  dur = dur_to_samples(dur_n, beg, cp, pos, 3, S_src_channel);
  if (dur == 0) return(Xen_false);
  if (beg > cp->edits[pos]->samples) return(Xen_false);

  if (Xen_is_number(ratio_or_env))
    {
      ratio = Xen_real_to_C_double(ratio_or_env);
      if ((pos == cp->edit_ctr) &&
	  ((ratio == 0.0) || (ratio == 1.0)))
	return(Xen_false);
      if ((is_NaN(ratio)) ||
	  (fabs(ratio) < 1.0e-10)) /* dur > 0 here */
	Xen_out_of_range_error(S_src_channel, 1, ratio_or_env, "too small (resultant sound will be too large)");
    }
  else 
    {
      int error = SRC_ENV_NO_ERROR;
      if (!egen)
	{
	  e = get_env(ratio_or_env, S_src_channel);
	  egen = mus_make_env(e->data, e->pts, 1.0, 0.0, e->base, 0.0, dur - 1, NULL);
	  need_free = true;
	}
      check_src_envelope(mus_env_breakpoints(egen), mus_data(egen), &error);
      if (error != SRC_ENV_NO_ERROR)
	{
	  Xen data;
	  if (e) free_env(e);
	  data = mus_array_to_list(mus_data(egen), 0, mus_env_breakpoints(egen) * 2);
	  if (need_free) 
	    mus_free(egen); 

	  if (error == SRC_ENV_HIT_ZERO)
	    Xen_out_of_range_error(S_src_channel, 1, data, "envelope hits 0.0");
	  else Xen_out_of_range_error(S_src_channel, 1, data, "envelope passes through 0.0");

	  return(Xen_false); /* just for clarity... */
	}
    }

  if (((egen) && (mus_phase(egen) >= 0.0)) ||
      ((!egen) && (ratio >= 0.0))) /* ratio == 0.0 if env in use because env is the srate (as change arg) */
    sf = init_sample_read_any_with_bufsize(beg, cp, READ_FORWARD, pos, (!egen) ? MAX_BUFFER_SIZE : FILE_BUFFER_SIZE);
  else sf = init_sample_read_any(beg + dur - 1, cp, READ_BACKWARD, pos);

  errmsg = src_channel_with_error(cp, sf, beg, dur, ratio, egen, S_src_channel, OVER_SOUND, &clm_err);
  free_snd_fd(sf);
  if (need_free) mus_free(egen);
  if (e) free_env(e);
  if (errmsg)
    {
      Xen err;
      err = C_string_to_Xen_string(errmsg);
      free(errmsg);
      Xen_error(Xen_make_error_type((clm_err) ? "mus-error" : "IO-error"),
		Xen_list_2(C_string_to_Xen_string(S_src_channel ": ~A"),
			   err));
    }
  return(ratio_or_env);
}


#if (defined(__sun) && defined(__SVR4))
  static bool is_inf(s7_double x) {return((x == x) && (is_NaN(x - x)));} /* there's no isinf in Solaris */
#else
  #define is_inf(x) isinf(x)
#endif

static Xen g_src_1(Xen ratio_or_env, Xen ebase, Xen snd, Xen chn_n, Xen edpos, const char *caller, bool over_selection)
{
  chan_info *cp;

  Snd_assert_channel(caller, snd, chn_n, 3);
  cp = get_cp(snd, chn_n, caller);
  if (!cp) return(Xen_false);

  if (Xen_is_number(ratio_or_env))
    {
      mus_float_t ratio;

      ratio = Xen_real_to_C_double(ratio_or_env);
      if ((is_NaN(ratio)) || (is_inf(ratio)))
	Xen_out_of_range_error(caller, 1, ratio_or_env, "src ratio must be a normal number");

      if (ratio != 1.0)
	src_env_or_num(cp, NULL, ratio,
		       true, caller,
		       over_selection, NULL, edpos, 5);
    }
  else 
    {
      int error = SRC_ENV_NO_ERROR;
      if (Xen_is_list(ratio_or_env))
	{
	  env *e;
	  mus_float_t e_ratio = 1.0;

	  /* env 'e' is a temp here, so we can clobber its base, etc */
	  e = get_env(ratio_or_env, caller);
	  if (Xen_is_number(ebase))
	    e->base = Xen_real_to_C_double(ebase);

	  e_ratio = check_src_envelope(e->pts, e->data, &error);
	  if (error != SRC_ENV_NO_ERROR)
	    {
	      Xen data;
	      data = mus_array_to_list(e->data, 0, e->pts * 2);
	      free_env(e);
	      if (error == SRC_ENV_HIT_ZERO)
		Xen_out_of_range_error(caller, 1, data, "envelope hits 0.0");
	      else Xen_out_of_range_error(caller, 1, data, "envelope passes through 0.0");
	    }
	  else
	    {
	      src_env_or_num(cp, e, e_ratio, false, caller, over_selection, NULL, edpos, 5);
	      free_env(e);
	    }
	}
      else
	{
	  mus_any *egen;
	  Xen_check_type(mus_is_xen(ratio_or_env), ratio_or_env, 1, caller, "a number, list, or env generator");
	  egen = Xen_to_mus_any(ratio_or_env);
	  Xen_check_type(mus_is_env(egen), ratio_or_env, 1, caller, "a number, list, or env generator");

	  check_src_envelope(mus_env_breakpoints(egen), mus_data(egen), &error);
	  if (error != SRC_ENV_NO_ERROR)
	    {
	      Xen data;
	      data = mus_array_to_list(mus_data(egen), 0, mus_env_breakpoints(egen) * 2);
	      if (error == SRC_ENV_HIT_ZERO)
		Xen_out_of_range_error(S_src_channel, 1, data, "envelope hits 0.0");
	      else Xen_out_of_range_error(S_src_channel, 1, data, "envelope passes through 0.0");
	    }
	  else
	    src_env_or_num(cp, NULL, 
			   (mus_phase(egen) >= 0.0) ? 1.0 : -1.0, /* mus_phase of env apparently = current_value(!) */
			   false, caller, 
			   over_selection, egen, edpos, 5);
	}
    }
  return(ratio_or_env);
}


static Xen g_src_sound(Xen ratio_or_env, Xen base, Xen snd, Xen chn_n, Xen edpos)
{
  #define H_src_sound "(" S_src_sound " ratio-or-env :optional (base 1.0) snd chn edpos): \
sampling-rate convert snd's channel chn by ratio, or following an envelope. A negative ratio reverses the sound"

  return(g_src_1(ratio_or_env, base, snd, chn_n, edpos, S_src_sound, OVER_SOUND));
}


static Xen g_src_selection(Xen ratio_or_env, Xen base)
{
  #define H_src_selection "(" S_src_selection " ratio-or-env :optional (base 1.0)): \
sampling-rate convert the currently selected data by ratio (which can be an envelope)"

  if (!(selection_is_active())) 
    return(snd_no_active_selection_error(S_src_selection));
  return(g_src_1(ratio_or_env, base, Xen_false, Xen_false, C_int_to_Xen_integer(AT_CURRENT_EDIT_POSITION), S_src_selection, OVER_SELECTION));
}


static Xen g_filter_channel(Xen e, Xen order, Xen beg, Xen dur, Xen snd, Xen chn_n, Xen edpos, Xen truncate, Xen origin)
{
  #define H_filter_channel "(" S_filter_channel " env :optional order beg dur snd chn edpos (truncate " PROC_TRUE ") origin): \
applies an FIR filter to snd's channel chn. 'env' is the frequency response envelope, or a " S_vct " with the coefficients."

  chan_info *cp;
  char *errstr = NULL;
  const char *caller = NULL;
  bool truncate_1 = true;
  int order_1 = 0, edpos_1 = AT_CURRENT_EDIT_POSITION;
  mus_long_t beg_1 = 0, dur_1 = 0;
  env *e_1 = NULL;
  mus_float_t *coeffs = NULL;

  Xen_check_type(Xen_is_list(e) || mus_is_vct(e), e, 1, S_filter_channel, "an envelope or a " S_vct);
  Xen_check_type(Xen_is_integer_or_unbound(order), order, 2, S_filter_channel, "an integer");
  Xen_check_type(Xen_is_string_or_unbound(origin), origin, 9, S_filter_channel, "a string");

  if (Xen_is_integer(order)) 
    {
      order_1 = Xen_integer_to_C_int(order);
      if (order_1 < 0) 
	Xen_out_of_range_error(S_filter_channel, 2, order, "order should not be negative");
    }
  Snd_assert_channel(S_filter_channel, snd, chn_n, 5);
  cp = get_cp(snd, chn_n, S_filter_channel);
  if (!cp) return(Xen_false);

  Xen_check_type(Xen_is_boolean_or_unbound(truncate), truncate, 8, S_filter_channel, "boolean");
  if (Xen_is_boolean(truncate)) truncate_1 = Xen_boolean_to_C_bool(truncate);

  Snd_assert_sample_type(S_filter_channel, beg, 3);
  Snd_assert_sample_type(S_filter_channel, dur, 4);

  beg_1 = beg_to_sample(beg, S_filter_channel);
  edpos_1 = to_c_edit_position(cp, edpos, S_filter_channel, 7);
  dur_1 = dur_to_samples(dur, beg_1, cp, edpos_1, 4, S_filter_channel);

  if (Xen_is_list(e))
    {
      e_1 = get_env(e, S_filter_channel);
      if (!e_1) return(Xen_false);
      if (order_1 == 0) order_1 = e_1->pts * 4;
    }
  else 
    {
      vct *v;
      int len;
      v = Xen_to_vct(e);
      len = mus_vct_length(v);
      if (len == 0)
	Xen_out_of_range_error(S_filter_channel, 1, e, "filter coeffs array is empty");
      coeffs = mus_vct_data(v);
      if (order_1 == 0) order_1 = len;
    }
  if (Xen_is_string(origin))
    caller = Xen_string_to_C_string(origin);
  else caller = S_filter_channel;
  /* if origin is NULL, direct_filter fills out the necessary parameters so that edit-list->function can re-call it,
   *   so if we set it to S_filter_channel here, we need to ignore it in direct_filter.
   *   Actually, the origin calculation in direct_filter should probably be moved here --
   *   otherwise convolution_filter will not work with edit-list->function -- I haven't tested this.
   */

  errstr = filter_channel(cp, order_1, e_1, beg_1, dur_1, edpos_1, caller, truncate_1, coeffs);

  if (e_1) free_env(e_1);
  if (errstr)
    {
      Xen str;
      str = C_string_to_Xen_string(errstr);
      free(errstr);
      Xen_error(Xen_make_error_type("IO-error"),
		Xen_list_2(C_string_to_Xen_string(S_filter_channel ": IO error ~A"),
			   str));
    }
  return(e);
}


static Xen g_filter_1(Xen e, Xen order, Xen snd, Xen chn_n, Xen edpos, const char *caller, const char *origin, bool over_selection, bool truncate)
{
  chan_info *cp;
  Snd_assert_channel(caller, snd, chn_n, 3);
  cp = get_cp(snd, chn_n, caller);
  if (!cp) return(Xen_false);
  if (mus_is_xen(e))
    {
      char *error;
      bool clm_err = false;
      error = apply_filter_or_error(cp, 0, NULL, caller, origin, over_selection, NULL, Xen_to_mus_any(e), edpos, 5, truncate, &clm_err);
      if (error)
	{
	  Xen errstr;
	  errstr = C_string_to_Xen_string(error);
	  free(error);
	  Xen_error(Xen_make_error_type((clm_err) ? "mus-error" : "IO-error"),
		    Xen_list_3(C_string_to_Xen_string("~A: ~A"),
			       C_string_to_Xen_string(caller),
			       errstr));
	}
    }
  else
    {
      int len = 0;
      Xen_check_type(Xen_is_integer_or_unbound(order), order, 2, caller, "an integer");
      if (Xen_is_integer(order)) 
	{
	  len = Xen_integer_to_C_int(order);
	  if (len <= 0) 
	    Xen_out_of_range_error(caller, 2, order, "order should be positive");
	}
      if (mus_is_vct(e)) /* the filter coefficients direct */
	{
	  vct *v;
	  char *new_origin = NULL, *estr = NULL;
	  v = Xen_to_vct(e);
	  if (len > mus_vct_length(v)) 
	    Xen_out_of_range_error(caller, 2, order, "order > length coeffs");
	  else
	    {
	      if (len == 0) len = mus_vct_length(v);
	    }
	  if ((!origin) && (mus_vct_length(v) < 16))
	    {
	      estr = mus_vct_to_readable_string(v);
#if HAVE_FORTH
	      new_origin = mus_format("%s %d%s %s", 
				      estr, len, 
				      (over_selection) ? "" : PROC_SEP "0" PROC_SEP PROC_FALSE,
				      caller);
#else
	      new_origin = mus_format("%s" PROC_OPEN "%s" PROC_SEP "%d%s", 
				      to_proc_name(caller), estr, len, 
				      (over_selection) ? "" : PROC_SEP "0" PROC_SEP PROC_FALSE);
#endif
	    }
	  else new_origin = mus_strdup(origin);
	  apply_filter(cp, len, NULL, caller, new_origin, over_selection, mus_vct_data(v), NULL, edpos, 5, truncate);
	  if (estr) free(estr);
	  if (new_origin) free(new_origin);
	}
      else 
	{
	  env *ne = NULL;
	  char *new_origin = NULL, *estr = NULL;
	  Xen_check_type(Xen_is_list(e), e, 1, caller, "a list, " S_vct ", or env generator");
	  ne = get_env(e, caller); /* arg here must be a list */
	  if (!origin)
	    {
	      estr = env_to_string(ne);
#if HAVE_FORTH
	      new_origin = mus_format("%s %d%s %s", 
				      estr, len,
				      (over_selection) ? "" : " 0 " PROC_FALSE,
				      caller);
#else
	      new_origin = mus_format("%s" PROC_OPEN "%s" PROC_SEP "%d%s", 
				      to_proc_name(caller), estr, len, (over_selection) ? "" : PROC_SEP "0" PROC_SEP PROC_FALSE);
#endif
	    }
	  else new_origin = mus_strdup(origin);
	  if (len == 0) len = ne->pts * 4;
	  apply_filter(cp, len, ne, caller, new_origin, over_selection, NULL, NULL, edpos, 5, truncate);
	  if (ne) free_env(ne); 
	  if (estr) free(estr);
	  if (new_origin) free(new_origin);
	}
    }
  return(Xen_true);
}


static Xen g_filter_sound(Xen e, Xen order, Xen snd, Xen chn_n, Xen edpos, Xen origin)
{
  #define H_filter_sound "(" S_filter_sound " filter :optional order snd chn edpos origin): \
applies FIR filter to snd's channel chn. 'filter' is either the frequency response envelope, a CLM filter, or a " S_vct " with the actual coefficients"

  Xen_check_type(Xen_is_string_or_unbound(origin), origin, 6, S_filter_sound, "a string");
  return(g_filter_1(e, order, snd, chn_n, edpos, 
		    S_filter_sound, (Xen_is_string(origin)) ? Xen_string_to_C_string(origin) : NULL, 
		    OVER_SOUND, false));
}


static Xen g_filter_selection(Xen e, Xen order, Xen truncate)
{
  #define H_filter_selection "(" S_filter_selection " filter :optional order (truncate " PROC_TRUE ")): apply filter to selection. If truncate, \
cut off filter output at end of selection, else mix"

  Xen_check_type(Xen_is_boolean_or_unbound(truncate), truncate, 3, S_filter_selection, "boolean");
  if (!(selection_is_active())) 
    return(snd_no_active_selection_error(S_filter_selection));
  return(g_filter_1(e, order, Xen_false, Xen_false, 
		    C_int_to_Xen_integer(AT_CURRENT_EDIT_POSITION), 
		    S_filter_selection, NULL, 
		    OVER_SELECTION, Xen_boolean_to_C_bool(truncate)));
}


static Xen g_sinc_width(void) {return(C_int_to_Xen_integer(sinc_width(ss)));}

static Xen g_set_sinc_width(Xen val) 
{
  #define H_sinc_width "(" S_sinc_width "): sampling rate conversion sinc width (10). \
The higher this number, the better the src low-pass filter, but the slower \
src runs.  If you use too low a setting, you can sometimes hear high \
frequency whistles leaking through."

  int len;
  Xen_check_type(Xen_is_integer(val), val, 1, S_set S_sinc_width, "an integer"); 
  len = Xen_integer_to_C_int(val);
  if ((len >= 0) && (len <= MUS_MAX_CLM_SINC_WIDTH))
    set_sinc_width(len);
  return(C_int_to_Xen_integer(sinc_width(ss)));
}


#ifndef _MSC_VER
  #include <sys/time.h>
#endif

#if 0
  static int primes[129] = {1, 2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67, 71, 73, 79, 83, 
			    89, 97, 101, 103, 107, 109, 113, 127, 131, 137, 139, 149, 151, 157, 163, 167, 173, 179, 181, 
			    191, 193, 197, 199, 211, 223, 227, 229, 233, 239, 241, 251, 257, 263, 269, 271, 277, 281, 
			    283, 293, 307, 311, 313, 317, 331, 337, 347, 349, 353, 359, 367, 373, 379, 383, 389, 397, 
			    401, 409, 419, 421, 431, 433, 439, 443, 449, 457, 461, 463, 467, 479, 487, 491, 499, 503, 
			    509, 521, 523, 541, 547, 557, 563, 569, 571, 577, 587, 593, 599, 601, 607, 613, 617, 619, 
			    631, 641, 643, 647, 653, 659, 661, 673, 677, 683, 691, 701, 709, 719};
#else
  static int primes[2049] = 
    {1, 2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67, 71, 73, 79, 83, 
     89, 97, 101, 103, 107, 109, 113, 127, 131, 137, 139, 149, 151, 157, 163, 167, 173, 179, 181, 
     191, 193, 197, 199, 211, 223, 227, 229, 233, 239, 241, 251, 257, 263, 269, 271, 277, 281, 
     283, 293, 307, 311, 313, 317, 331, 337, 347, 349, 353, 359, 367, 373, 379, 383, 389, 397, 
     401, 409, 419, 421, 431, 433, 439, 443, 449, 457, 461, 463, 467, 479, 487, 491, 499, 503, 
     509, 521, 523, 541, 547, 557, 563, 569, 571, 577, 587, 593, 599, 601, 607, 613, 617, 619, 
     631, 641, 643, 647, 653, 659, 661, 673, 677, 683, 691, 701, 709, 719, 727, 733, 739, 743, 751, 757, 761, 
     769, 773, 787, 797, 809, 811, 821, 823, 827, 829, 839, 853, 857, 859, 863, 877, 881, 883, 
     887, 907, 911, 919, 929, 937, 941, 947, 953, 967, 971, 977, 983, 991, 997, 1009, 1013, 1019, 
     1021, 1031, 1033, 1039, 1049, 1051, 1061, 1063, 1069, 1087, 1091, 1093, 1097, 1103, 1109, 1117, 1123, 1129, 
     1151, 1153, 1163, 1171, 1181, 1187, 1193, 1201, 1213, 1217, 1223, 1229, 1231, 1237, 1249, 1259, 1277, 1279, 
     1283, 1289, 1291, 1297, 1301, 1303, 1307, 1319, 1321, 1327, 1361, 1367, 1373, 1381, 1399, 1409, 1423, 1427, 
     1429, 1433, 1439, 1447, 1451, 1453, 1459, 1471, 1481, 1483, 1487, 1489, 1493, 1499, 1511, 1523, 1531, 1543, 
     1549, 1553, 1559, 1567, 1571, 1579, 1583, 1597, 1601, 1607, 1609, 1613, 1619, 1621, 1627, 1637, 1657, 1663, 
     1667, 1669, 1693, 1697, 1699, 1709, 1721, 1723, 1733, 1741, 1747, 1753, 1759, 1777, 1783, 1787, 1789, 1801, 
     1811, 1823, 1831, 1847, 1861, 1867, 1871, 1873, 1877, 1879, 1889, 1901, 1907, 1913, 1931, 1933, 1949, 1951, 
     1973, 1979, 1987, 1993, 1997, 1999, 2003, 2011, 2017, 2027, 2029, 2039, 2053, 2063, 2069, 2081, 2083, 2087, 
     2089, 2099, 2111, 2113, 2129, 2131, 2137, 2141, 2143, 2153, 2161, 2179, 2203, 2207, 2213, 2221, 2237, 2239, 
     2243, 2251, 2267, 2269, 2273, 2281, 2287, 2293, 2297, 2309, 2311, 2333, 2339, 2341, 2347, 2351, 2357, 2371, 
     2377, 2381, 2383, 2389, 2393, 2399, 2411, 2417, 2423, 2437, 2441, 2447, 2459, 2467, 2473, 2477, 2503, 2521, 
     2531, 2539, 2543, 2549, 2551, 2557, 2579, 2591, 2593, 2609, 2617, 2621, 2633, 2647, 2657, 2659, 2663, 2671, 
     2677, 2683, 2687, 2689, 2693, 2699, 2707, 2711, 2713, 2719, 2729, 2731, 2741, 2749, 2753, 2767, 2777, 2789, 
     2791, 2797, 2801, 2803, 2819, 2833, 2837, 2843, 2851, 2857, 2861, 2879, 2887, 2897, 2903, 2909, 2917, 2927, 
     2939, 2953, 2957, 2963, 2969, 2971, 2999, 3001, 3011, 3019, 3023, 3037, 3041, 3049, 3061, 3067, 3079, 3083, 
     3089, 3109, 3119, 3121, 3137, 3163, 3167, 3169, 3181, 3187, 3191, 3203, 3209, 3217, 3221, 3229, 3251, 3253, 
     3257, 3259, 3271, 3299, 3301, 3307, 3313, 3319, 3323, 3329, 3331, 3343, 3347, 3359, 3361, 3371, 3373, 3389, 
     3391, 3407, 3413, 3433, 3449, 3457, 3461, 3463, 3467, 3469, 3491, 3499, 3511, 3517, 3527, 3529, 3533, 3539, 
     3541, 3547, 3557, 3559, 3571, 3581, 3583, 3593, 3607, 3613, 3617, 3623, 3631, 3637, 3643, 3659, 3671, 3673, 
     3677, 3691, 3697, 3701, 3709, 3719, 3727, 3733, 3739, 3761, 3767, 3769, 3779, 3793, 3797, 3803, 3821, 3823, 
     3833, 3847, 3851, 3853, 3863, 3877, 3881, 3889, 3907, 3911, 3917, 3919, 3923, 3929, 3931, 3943, 3947, 3967, 
     3989, 4001, 4003, 4007, 4013, 4019, 4021, 4027, 4049, 4051, 4057, 4073, 4079, 4091, 4093, 4099, 4111, 4127, 
     4129, 4133, 4139, 4153, 4157, 4159, 4177, 4201, 4211, 4217, 4219, 4229, 4231, 4241, 4243, 4253, 4259, 4261, 
     4271, 4273, 4283, 4289, 4297, 4327, 4337, 4339, 4349, 4357, 4363, 4373, 4391, 4397, 4409, 4421, 4423, 4441, 
     4447, 4451, 4457, 4463, 4481, 4483, 4493, 4507, 4513, 4517, 4519, 4523, 4547, 4549, 4561, 4567, 4583, 4591, 
     4597, 4603, 4621, 4637, 4639, 4643, 4649, 4651, 4657, 4663, 4673, 4679, 4691, 4703, 4721, 4723, 4729, 4733, 
     4751, 4759, 4783, 4787, 4789, 4793, 4799, 4801, 4813, 4817, 4831, 4861, 4871, 4877, 4889, 4903, 4909, 4919, 
     4931, 4933, 4937, 4943, 4951, 4957, 4967, 4969, 4973, 4987, 4993, 4999, 5003, 5009, 5011, 5021, 5023, 5039, 
     5051, 5059, 5077, 5081, 5087, 5099, 5101, 5107, 5113, 5119, 5147, 5153, 5167, 5171, 5179, 5189, 5197, 5209, 
     5227, 5231, 5233, 5237, 5261, 5273, 5279, 5281, 5297, 5303, 5309, 5323, 5333, 5347, 5351, 5381, 5387, 5393, 
     5399, 5407, 5413, 5417, 5419, 5431, 5437, 5441, 5443, 5449, 5471, 5477, 5479, 5483, 5501, 5503, 5507, 5519, 
     5521, 5527, 5531, 5557, 5563, 5569, 5573, 5581, 5591, 5623, 5639, 5641, 5647, 5651, 5653, 5657, 5659, 5669, 
     5683, 5689, 5693, 5701, 5711, 5717, 5737, 5741, 5743, 5749, 5779, 5783, 5791, 5801, 5807, 5813, 5821, 5827, 
     5839, 5843, 5849, 5851, 5857, 5861, 5867, 5869, 5879, 5881, 5897, 5903, 5923, 5927, 5939, 5953, 5981, 5987, 
     6007, 6011, 6029, 6037, 6043, 6047, 6053, 6067, 6073, 6079, 6089, 6091, 6101, 6113, 6121, 6131, 6133, 6143, 
     6151, 6163, 6173, 6197, 6199, 6203, 6211, 6217, 6221, 6229, 6247, 6257, 6263, 6269, 6271, 6277, 6287, 6299, 
     6301, 6311, 6317, 6323, 6329, 6337, 6343, 6353, 6359, 6361, 6367, 6373, 6379, 6389, 6397, 6421, 6427, 6449, 
     6451, 6469, 6473, 6481, 6491, 6521, 6529, 6547, 6551, 6553, 6563, 6569, 6571, 6577, 6581, 6599, 6607, 6619, 
     6637, 6653, 6659, 6661, 6673, 6679, 6689, 6691, 6701, 6703, 6709, 6719, 6733, 6737, 6761, 6763, 6779, 6781, 
     6791, 6793, 6803, 6823, 6827, 6829, 6833, 6841, 6857, 6863, 6869, 6871, 6883, 6899, 6907, 6911, 6917, 6947, 
     6949, 6959, 6961, 6967, 6971, 6977, 6983, 6991, 6997, 7001, 7013, 7019, 7027, 7039, 7043, 7057, 7069, 7079, 
     7103, 7109, 7121, 7127, 7129, 7151, 7159, 7177, 7187, 7193, 7207, 7211, 7213, 7219, 7229, 7237, 7243, 7247, 
     7253, 7283, 7297, 7307, 7309, 7321, 7331, 7333, 7349, 7351, 7369, 7393, 7411, 7417, 7433, 7451, 7457, 7459, 
     7477, 7481, 7487, 7489, 7499, 7507, 7517, 7523, 7529, 7537, 7541, 7547, 7549, 7559, 7561, 7573, 7577, 7583, 
     7589, 7591, 7603, 7607, 7621, 7639, 7643, 7649, 7669, 7673, 7681, 7687, 7691, 7699, 7703, 7717, 7723, 7727, 
     7741, 7753, 7757, 7759, 7789, 7793, 7817, 7823, 7829, 7841, 7853, 7867, 7873, 7877, 7879, 7883, 7901, 7907, 
     7919, 7927, 7933, 7937, 7949, 7951, 7963, 7993, 8009, 8011, 8017, 8039, 8053, 8059, 8069, 8081, 8087, 8089, 
     8093, 8101, 8111, 8117, 8123, 8147, 8161, 8167, 8171, 8179, 8191, 8209, 8219, 8221, 8231, 8233, 8237, 8243, 
     8263, 8269, 8273, 8287, 8291, 8293, 8297, 8311, 8317, 8329, 8353, 8363, 8369, 8377, 8387, 8389, 8419, 8423, 
     8429, 8431, 8443, 8447, 8461, 8467, 8501, 8513, 8521, 8527, 8537, 8539, 8543, 8563, 8573, 8581, 8597, 8599, 
     8609, 8623, 8627, 8629, 8641, 8647, 8663, 8669, 8677, 8681, 8689, 8693, 8699, 8707, 8713, 8719, 8731, 8737, 
     8741, 8747, 8753, 8761, 8779, 8783, 8803, 8807, 8819, 8821, 8831, 8837, 8839, 8849, 8861, 8863, 8867, 8887, 
     8893, 8923, 8929, 8933, 8941, 8951, 8963, 8969, 8971, 8999, 9001, 9007, 9011, 9013, 9029, 9041, 9043, 9049, 
     9059, 9067, 9091, 9103, 9109, 9127, 9133, 9137, 9151, 9157, 9161, 9173, 9181, 9187, 9199, 9203, 9209, 9221, 
     9227, 9239, 9241, 9257, 9277, 9281, 9283, 9293, 9311, 9319, 9323, 9337, 9341, 9343, 9349, 9371, 9377, 9391, 
     9397, 9403, 9413, 9419, 9421, 9431, 9433, 9437, 9439, 9461, 9463, 9467, 9473, 9479, 9491, 9497, 9511, 9521, 
     9533, 9539, 9547, 9551, 9587, 9601, 9613, 9619, 9623, 9629, 9631, 9643, 9649, 9661, 9677, 9679, 9689, 9697, 
     9719, 9721, 9733, 9739, 9743, 9749, 9767, 9769, 9781, 9787, 9791, 9803, 9811, 9817, 9829, 9833, 9839, 9851, 
     9857, 9859, 9871, 9883, 9887, 9901, 9907, 9923, 9929, 9931, 9941, 9949, 9967, 9973, 10007, 10009, 10037, 10039, 
     10061, 10067, 10069, 10079, 10091, 10093, 10099, 10103, 10111, 10133, 10139, 10141, 10151, 10159, 10163, 10169, 10177, 10181, 
     10193, 10211, 10223, 10243, 10247, 10253, 10259, 10267, 10271, 10273, 10289, 10301, 10303, 10313, 10321, 10331, 10333, 10337, 
     10343, 10357, 10369, 10391, 10399, 10427, 10429, 10433, 10453, 10457, 10459, 10463, 10477, 10487, 10499, 10501, 10513, 10529, 
     10531, 10559, 10567, 10589, 10597, 10601, 10607, 10613, 10627, 10631, 10639, 10651, 10657, 10663, 10667, 10687, 10691, 10709, 
     10711, 10723, 10729, 10733, 10739, 10753, 10771, 10781, 10789, 10799, 10831, 10837, 10847, 10853, 10859, 10861, 10867, 10883, 
     10889, 10891, 10903, 10909, 10937, 10939, 10949, 10957, 10973, 10979, 10987, 10993, 11003, 11027, 11047, 11057, 11059, 11069, 
     11071, 11083, 11087, 11093, 11113, 11117, 11119, 11131, 11149, 11159, 11161, 11171, 11173, 11177, 11197, 11213, 11239, 11243, 
     11251, 11257, 11261, 11273, 11279, 11287, 11299, 11311, 11317, 11321, 11329, 11351, 11353, 11369, 11383, 11393, 11399, 11411, 
     11423, 11437, 11443, 11447, 11467, 11471, 11483, 11489, 11491, 11497, 11503, 11519, 11527, 11549, 11551, 11579, 11587, 11593, 
     11597, 11617, 11621, 11633, 11657, 11677, 11681, 11689, 11699, 11701, 11717, 11719, 11731, 11743, 11777, 11779, 11783, 11789, 
     11801, 11807, 11813, 11821, 11827, 11831, 11833, 11839, 11863, 11867, 11887, 11897, 11903, 11909, 11923, 11927, 11933, 11939, 
     11941, 11953, 11959, 11969, 11971, 11981, 11987, 12007, 12011, 12037, 12041, 12043, 12049, 12071, 12073, 12097, 12101, 12107, 
     12109, 12113, 12119, 12143, 12149, 12157, 12161, 12163, 12197, 12203, 12211, 12227, 12239, 12241, 12251, 12253, 12263, 12269, 
     12277, 12281, 12289, 12301, 12323, 12329, 12343, 12347, 12373, 12377, 12379, 12391, 12401, 12409, 12413, 12421, 12433, 12437, 
     12451, 12457, 12473, 12479, 12487, 12491, 12497, 12503, 12511, 12517, 12527, 12539, 12541, 12547, 12553, 12569, 12577, 12583, 
     12589, 12601, 12611, 12613, 12619, 12637, 12641, 12647, 12653, 12659, 12671, 12689, 12697, 12703, 12713, 12721, 12739, 12743, 
     12757, 12763, 12781, 12791, 12799, 12809, 12821, 12823, 12829, 12841, 12853, 12889, 12893, 12899, 12907, 12911, 12917, 12919, 
     12923, 12941, 12953, 12959, 12967, 12973, 12979, 12983, 13001, 13003, 13007, 13009, 13033, 13037, 13043, 13049, 13063, 13093, 
     13099, 13103, 13109, 13121, 13127, 13147, 13151, 13159, 13163, 13171, 13177, 13183, 13187, 13217, 13219, 13229, 13241, 13249, 
     13259, 13267, 13291, 13297, 13309, 13313, 13327, 13331, 13337, 13339, 13367, 13381, 13397, 13399, 13411, 13417, 13421, 13441, 
     13451, 13457, 13463, 13469, 13477, 13487, 13499, 13513, 13523, 13537, 13553, 13567, 13577, 13591, 13597, 13613, 13619, 13627, 
     13633, 13649, 13669, 13679, 13681, 13687, 13691, 13693, 13697, 13709, 13711, 13721, 13723, 13729, 13751, 13757, 13759, 13763, 
     13781, 13789, 13799, 13807, 13829, 13831, 13841, 13859, 13873, 13877, 13879, 13883, 13901, 13903, 13907, 13913, 13921, 13931, 
     13933, 13963, 13967, 13997, 13999, 14009, 14011, 14029, 14033, 14051, 14057, 14071, 14081, 14083, 14087, 14107, 14143, 14149, 
     14153, 14159, 14173, 14177, 14197, 14207, 14221, 14243, 14249, 14251, 14281, 14293, 14303, 14321, 14323, 14327, 14341, 14347, 
     14369, 14387, 14389, 14401, 14407, 14411, 14419, 14423, 14431, 14437, 14447, 14449, 14461, 14479, 14489, 14503, 14519, 14533, 
     14537, 14543, 14549, 14551, 14557, 14561, 14563, 14591, 14593, 14621, 14627, 14629, 14633, 14639, 14653, 14657, 14669, 14683, 
     14699, 14713, 14717, 14723, 14731, 14737, 14741, 14747, 14753, 14759, 14767, 14771, 14779, 14783, 14797, 14813, 14821, 14827, 
     14831, 14843, 14851, 14867, 14869, 14879, 14887, 14891, 14897, 14923, 14929, 14939, 14947, 14951, 14957, 14969, 14983, 15013, 
     15017, 15031, 15053, 15061, 15073, 15077, 15083, 15091, 15101, 15107, 15121, 15131, 15137, 15139, 15149, 15161, 15173, 15187, 
     15193, 15199, 15217, 15227, 15233, 15241, 15259, 15263, 15269, 15271, 15277, 15287, 15289, 15299, 15307, 15313, 15319, 15329, 
     15331, 15349, 15359, 15361, 15373, 15377, 15383, 15391, 15401, 15413, 15427, 15439, 15443, 15451, 15461, 15467, 15473, 15493, 
     15497, 15511, 15527, 15541, 15551, 15559, 15569, 15581, 15583, 15601, 15607, 15619, 15629, 15641, 15643, 15647, 15649, 15661, 
     15667, 15671, 15679, 15683, 15727, 15731, 15733, 15737, 15739, 15749, 15761, 15767, 15773, 15787, 15791, 15797, 15803, 15809, 
     15817, 15823, 15859, 15877, 15881, 15887, 15889, 15901, 15907, 15913, 15919, 15923, 15937, 15959, 15971, 15973, 15991, 16001, 
     16007, 16033, 16057, 16061, 16063, 16067, 16069, 16073, 16087, 16091, 16097, 16103, 16111, 16127, 16139, 16141, 16183, 16187, 
     16189, 16193, 16217, 16223, 16229, 16231, 16249, 16253, 16267, 16273, 16301, 16319, 16333, 16339, 16349, 16361, 16363, 16369, 
     16381, 16411, 16417, 16421, 16427, 16433, 16447, 16451, 16453, 16477, 16481, 16487, 16493, 16519, 16529, 16547, 16553, 16561, 
     16567, 16573, 16603, 16607, 16619, 16631, 16633, 16649, 16651, 16657, 16661, 16673, 16691, 16693, 16699, 16703, 16729, 16741, 
     16747, 16759, 16763, 16787, 16811, 16823, 16829, 16831, 16843, 16871, 16879, 16883, 16889, 16901, 16903, 16921, 16927, 16931, 
     16937, 16943, 16963, 16979, 16981, 16987, 16993, 17011, 17021, 17027, 17029, 17033, 17041, 17047, 17053, 17077, 17093, 17099, 
     17107, 17117, 17123, 17137, 17159, 17167, 17183, 17189, 17191, 17203, 17207, 17209, 17231, 17239, 17257, 17291, 17293, 17299, 
     17317, 17321, 17327, 17333, 17341, 17351, 17359, 17377, 17383, 17387, 17389, 17393, 17401, 17417, 17419, 17431, 17443, 17449, 
     17467, 17471, 17477, 17483, 17489, 17491, 17497, 17509, 17519, 17539, 17551, 17569, 17573, 17579, 17581, 17597, 17599, 17609, 
     17623, 17627, 17657, 17659, 17669, 17681, 17683, 17707, 17713, 17729, 17737, 17747, 17749, 17761, 17783, 17789, 17791, 17807, 
     17827, 17837, 17839, 17851, 17863};
#endif

  static mus_float_t all_mins[128] = {1.0000, 1.7600, 1.9797, 2.0390, 2.3435, 2.5493, 2.6394, 2.7946, 2.9617, 3.1023, 3.2180, 3.3887, 3.5241, 3.6122, 3.7680, 3.8738, 3.9802, 4.1397, 4.2182, 4.2880, 4.4425, 4.5399, 4.6037, 4.7280, 4.8529, 4.9819, 5.0640, 5.1567, 5.2413, 5.3613, 5.4531, 5.5256, 5.6085, 5.7151, 5.7615, 5.8717, 5.9181, 6.0558, 6.1236, 6.2239, 6.2785, 6.3741, 6.4746, 6.5441, 6.6127, 6.6910, 6.7566, 6.8041, 6.8716, 6.9660, 6.9718, 7.1018, 7.1684, 7.2456, 7.3000, 7.3494, 7.4415, 7.4714, 7.4694, 7.5887, 7.7185, 7.7918, 7.7927, 7.8504, 7.9354, 8.0115, 8.0440, 8.1314, 8.1403, 8.1770, 8.2913, 8.3678, 8.3715, 8.4685, 8.5124, 8.5666, 8.6559, 8.7132, 8.7674, 8.8316, 8.7973, 8.8502, 8.9350, 9.0232, 9.0499, 9.1449, 9.1879, 9.2434, 9.3155, 9.3693, 9.4066, 9.4199, 9.4126, 9.5437, 9.5949, 9.6988, 9.7336, 9.7670, 9.8274, 9.8281, 9.9219, 9.9997, 9.9365, 10.0175, 10.0636, 10.1795, 10.2507, 10.3130, 10.3161, 10.3845, 10.4435, 10.4596, 10.5332, 10.5179, 10.6217, 10.6669, 10.7403, 10.7900, 10.8359, 10.8779, 10.9249, 10.9498, 11.0161, 11.0603, 11.1055, 11.1455, 11.1761, 11.2104};

  static mus_float_t odd_mins[128] = {1.0000, 1.5390, 1.7387, 2.0452, 2.3073, 2.5227, 2.6183, 2.7907, 2.8862, 3.0534, 3.1766, 3.3619, 3.4745, 3.5985, 3.7384, 3.8570, 3.9264, 4.0695, 4.1719, 4.3004, 4.3988, 4.5190, 4.6344, 4.7830, 4.8336, 4.9967, 5.0854, 5.0889, 5.2573, 5.3526, 5.4189, 5.5543, 5.5967, 5.7260, 5.8007, 5.9555, 5.9587, 6.0708, 6.1678, 6.2652, 6.3216, 6.4032, 6.4742, 6.5992, 6.6249, 6.7092, 6.7852, 6.8280, 6.9858, 6.9471, 7.0877, 7.0801, 7.2526, 7.3281, 7.3642, 7.4191, 7.4889, 7.5859, 7.6178, 7.6996, 7.7755, 7.8170, 7.9041, 7.9574, 8.0409, 8.0952, 8.1280, 8.2044, 8.2749, 8.3285, 8.3835, 8.3664, 8.5147, 8.4879, 8.6207, 8.6513, 8.7070, 8.7153, 8.8646, 8.8701, 8.9263, 8.8955, 9.0607, 9.1335, 9.1729, 9.2133, 9.3170, 9.3240, 9.3316, 9.4217, 9.4566, 9.5527, 9.6288, 9.6539, 9.7169, 9.7594, 9.8323, 9.8526, 9.9278, 9.9678, 9.9646, 10.0458, 10.1025, 10.1685, 10.1158, 10.1983, 10.2960, 10.3255, 10.4140, 10.4081, 10.4171, 10.5820, 10.5864, 10.6208, 10.6743, 10.7333, 10.7833, 10.8122, 10.8155, 10.9086, 10.9649, 11.0108, 11.0879, 11.1203, 11.1221, 11.2172, 11.2677, 11.2612};

  static mus_float_t prime_mins[128] = {1.0000, 1.7600, 1.9798, 2.1921, 2.4768, 2.8054, 3.0618, 3.2628, 3.3822, 3.6019, 3.7784, 3.9359, 4.1545, 4.3244, 4.4659, 4.6003, 4.7186, 4.8551, 4.9988, 5.1826, 5.3236, 5.4336, 5.5623, 5.6422, 5.8108, 6.0560, 6.1340, 6.1909, 6.3650, 6.4518, 6.6155, 6.7719, 6.8464, 6.9912, 7.1636, 7.1869, 7.2910, 7.3959, 7.4521, 7.5274, 7.7192, 7.8634, 7.9364, 8.0652, 8.1549, 8.2605, 8.2472, 8.4681, 8.5828, 8.6759, 8.6516, 8.8175, 8.9482, 8.9955, 9.1430, 9.1118, 9.2168, 9.4251, 9.4178, 9.5291, 9.5971, 9.6970, 9.6932, 9.9049, 10.0313, 10.0406, 10.2660, 10.2616, 10.3583, 10.3621, 10.4075, 10.4970, 10.4247, 10.6460, 10.7289, 10.7732, 10.7871, 10.9786, 10.9996, 11.1246, 11.3109, 11.3865, 11.4001, 11.4872, 11.5705, 11.4640, 11.5865, 11.7792, 11.7172, 11.9121, 12.0055, 11.8779, 11.9314, 12.0399, 12.0860, 12.1999, 12.2938, 12.3727, 12.5774, 12.5080, 12.3999, 12.5091, 12.6727, 12.8169, 12.9165, 12.7944, 12.9882, 12.9496, 13.0181, 13.0735, 13.3043, 13.4320, 13.3789, 13.4760, 13.4814, 13.6086, 13.6802, 13.8100, 13.7456, 13.7572, 13.8806, 13.9065, 13.9862, 14.1155, 14.1096, 14.1161, 14.0378, 14.2402};

  static mus_float_t even_mins[128] = {1.0000, 1.7602, 2.0215, 2.4306, 2.6048, 2.8370, 3.0470, 3.1975, 3.4540, 3.5587, 3.6561, 3.7869, 3.9726, 4.0967, 4.1921, 4.3250, 4.4630, 4.5694, 4.7415, 4.8395, 4.9197, 5.0552, 5.1479, 5.2532, 5.4032, 5.4523, 5.6204, 5.7317, 5.7663, 5.9070, 5.9878, 6.0611, 6.1619, 6.2228, 6.3617, 6.4316, 6.5302, 6.5360, 6.6802, 6.7476, 6.8804, 6.9400, 7.0517, 7.0391, 7.1643, 7.2740, 7.2898, 7.3923, 7.5290, 7.5471, 7.5686, 7.7050, 7.7478, 7.8429, 7.8878, 7.9747, 7.9655, 8.1007, 8.1919, 8.2950, 8.2268, 8.3868, 8.4105, 8.4885, 8.5672, 8.6324, 8.6805, 8.7377, 8.8613, 8.7383, 8.8715, 8.9661, 8.9989, 9.0897, 9.1223, 9.2670, 9.2587, 9.3283, 9.3569, 9.4514, 9.4842, 9.4971, 9.5274, 9.6148, 9.6764, 9.7580, 9.8338, 9.7183, 9.9973, 10.0054, 10.0428, 10.0938, 10.0656, 10.1228, 10.2691, 10.2410, 10.3316, 10.4691, 10.3878, 10.4419, 10.5743, 10.5388, 10.6377, 10.6556, 10.7237, 10.8126, 10.8718, 10.9019, 10.9585, 11.0599, 10.9622, 11.1279, 11.0422, 11.0983, 11.1109, 11.2778, 11.2957, 11.4305, 11.4631, 11.3135, 11.4986, 11.5297, 11.5885, 11.6136, 11.6968, 11.7032, 11.7900, 11.8302};

  static mus_float_t min_8[4] = {15.9975, 16.2419, 16.8959, 23.9548};
  static mus_float_t min_9[4] = {23.1900, 23.5159, 24.5104, 38.6029};
  static mus_float_t min_10[4] = {33.0298, 33.7318, 34.4867, 65.3493};
  static mus_float_t min_11[4] = {48.9126, 48.2271, 50.8873, 95.9043};

#define USE_CLM_RANDOM (!HAVE_SCHEME)

static mus_float_t local_random(mus_float_t val)
{
#if USE_CLM_RANDOM
  return(mus_random(val));
#else
  return(val * (1.0  - (s7_random(s7, NULL) * 2.0)));
#endif
}


static mus_float_t local_frandom(mus_float_t val)
{
#if USE_CLM_RANDOM
  return(mus_frandom(val));
#else
  return(val * s7_random(s7, NULL));
#endif
}


typedef struct {
  mus_float_t pk;
  mus_float_t *phases;
} pk_data;


/* -------------------------------------------------------------------------------- */

#define ALL 0
#define ODD 1
#define EVEN 2
#define PRIME 3

#define FFT_MULT 160
  /* if 64, errors or .005 are common 
   * if 128, which works in 99% of the cases, errors can be as much as .002
   */

#define S_fpsap "fpsap"

static mus_float_t saved_min(int ch, int nn)
{
  if (nn <= 128)
    {
      switch (ch)
	{
	case ALL:   return(all_mins[nn - 1]);
	case ODD:   return(odd_mins[nn - 1]);
	case EVEN:  return(even_mins[nn - 1]);
	case PRIME: return(prime_mins[nn - 1]);
	}
    }
  if (nn == 256) return(min_8[ch]);
  if (nn == 512) return(min_9[ch]);
  if (nn == 1024) return(min_10[ch]);
  if (nn == 2048) return(min_11[ch]);
  return((mus_float_t)nn);
}

static mus_float_t set_saved_min(int ch, int nn, mus_float_t new_val)
{
  if (nn <= 128)
    {
      switch (ch)
	{
	case ALL:   all_mins[nn - 1] = new_val; break;
	case ODD:   odd_mins[nn - 1] = new_val; break;
	case EVEN:  even_mins[nn - 1] = new_val; break;
	case PRIME: prime_mins[nn - 1] = new_val; break;
	}
    }
  if (nn == 256) min_8[ch] = new_val;
  if (nn == 512) min_9[ch] = new_val;
  if (nn == 1024) min_10[ch] = new_val;
  if (nn == 2048) min_11[ch] = new_val;
  return((mus_float_t)nn);
}


static mus_float_t get_peak(int choice, int fft_size, int n, mus_float_t *phases, mus_float_t *rl, mus_float_t *im)
{
  int i, m;
  mus_float_t pi2, mx_sin;
  
  pi2 = M_PI / 2.0;
  mus_clear_floats(rl, fft_size);
  mus_clear_floats(im, fft_size);
  
  for (m = 0; m < n; m++)
    {
      int bin;
      mus_float_t phi;
      phi = (M_PI * phases[m]) + pi2;
      if (choice == ALL)
	bin = m + 1;
      else
	{
	  if (choice == ODD)
	    bin = (m * 2) + 1;
	  else 
	    {
	      if (choice == EVEN)
		{
		  bin = m * 2;
		  if (bin == 0) bin = 1;
		}
	      else bin = primes[m];
	    }
	}
      rl[bin] = cos(phi);
      im[bin] = sin(phi);
    }
  
  mus_fft(rl, im, fft_size, -1);
  /* real part is sine reconstruction, imaginary part is cosine, we were originally interested in both */
  /*   we could also add and subtract the 2 to get 2 more cases "for free", amp sqrt(2), phase asin(cos(0)/sqrt(2)) */
  /*   and repeat this with a shift (rotation from i) for 2n other cases */
  /*   resultant amp is between 0 and 2 (cosine) */
  
  mus_abs_floats(rl, fft_size);
  mx_sin = rl[0];
  for (i = 1; i < fft_size; i++)
    mx_sin = (rl[i] > mx_sin) ? rl[i] : mx_sin;

  return(mx_sin);
}
  

static Xen g_fpsap(Xen x_choice, Xen x_n, Xen start_phases, Xen x_size, Xen x_increment)
{
  #define H_fpsap "(" S_fpsap " choice n phases (size 6000) (increment 0.06)) searches \
for a peak-amp minimum using a simulated annealing form of the genetic algorithm.  choice: 0=all, 1=odd, 2=even, 3=prime."

  #define INCR_DOWN 0.92
  #define INCR_MAX 1.0
  #define INCR_MIN 0.00005
  #define RETRIES 10
  #define RETRY_MULT 2
  #define INIT_TRIES 5000

  int choice, n, size, counts = 0;
  mus_float_t increment = INCR_MAX, orig_incr, local_best = 1000.0, incr_mult = INCR_DOWN, overall_min;
  mus_float_t *min_phases = NULL, *temp_phases = NULL, *diff_phases = NULL, *initial_phases = NULL;
  const char *choice_name[4] = {"all", "odd", "even", "prime"};
  pk_data **choices = NULL, **free_choices = NULL;
  mus_float_t *rl, *im;
  const char *file = NULL;
  bool just_best;

#ifndef _MSC_VER
  {
    struct timeval tm;
    gettimeofday(&tm, NULL);
    mus_set_rand_seed((unsigned long)(tm.tv_sec * 1000 + tm.tv_usec / 1000));
  }
#endif

  choice = Xen_integer_to_C_int(x_choice);
  if ((choice < ALL) || (choice > PRIME))
    choice = ALL;

  n = Xen_integer_to_C_int(x_n);

  if (Xen_is_integer(x_size))
    size = Xen_integer_to_C_int(x_size);
  else size = 3000; 

  if (Xen_is_double(x_increment))
    increment = Xen_real_to_C_double(x_increment);
  else increment = 0.06; /* was .03 */

  counts = 50; /* 100? */
  orig_incr = increment;
  incr_mult = INCR_DOWN;
  file = "test.data";
  just_best = false;

  if (Xen_is_vector(start_phases))
    {
      int i;
      initial_phases = (mus_float_t *)malloc(n * sizeof(mus_float_t));
      for (i = 0; i < n; i++)
	initial_phases[i] = (mus_float_t)Xen_real_to_C_double(Xen_vector_ref(start_phases, i));
    }

  min_phases = (mus_float_t *)calloc(n, sizeof(mus_float_t));

  overall_min = saved_min(choice, n);
#if 0
  if (overall_min < sqrt((double)n)) overall_min = sqrt((double)n);
  overall_min += .5;
#endif
  temp_phases = (mus_float_t *)calloc(n, sizeof(mus_float_t));
  diff_phases = (mus_float_t *)calloc(n, sizeof(mus_float_t));

  {
    int start, n1, day_counter, free_top, fft_size;

    if (choice == ALL)
      n1 = n;
    else
      {
	if (choice != PRIME)
	  n1 = n * 2;
	else n1 = primes[n];
      }
    fft_size = (int)pow(2.0, (int)ceil(log(FFT_MULT * n1) / log(2.0)));
    rl = (mus_float_t *)calloc(fft_size, sizeof(mus_float_t));
    im = (mus_float_t *)calloc(fft_size, sizeof(mus_float_t));

    choices = (pk_data **)calloc(size, sizeof(pk_data *));
    free_choices = (pk_data **)calloc(size, sizeof(pk_data *));

    for (start = 0; start < size; start++)
      {
	choices[start] = (pk_data *)calloc(1, sizeof(pk_data));
	choices[start]->phases = (mus_float_t *)calloc(n, sizeof(mus_float_t));
      }

    free_top = 0;
    day_counter = 0;
    local_best = (mus_float_t)n;
    increment = orig_incr;

    /* here to stay focussed,
     *     for (k = 0; k < n; k++) choices[0]->phases[k] = initial_phases[k];
     *     choices[0]->pk = get_peak(choice, fft_size, n, initial_phases, rl, im);
     *     for (start = 1; start < size; start++)
     *     etc
     * but this is not an improvement
     */
    for (start = 0; start < size; start++)
      {
	mus_float_t pk, local_pk = 100000.0;
	int k, init_try;
	
	for (init_try = 0;  init_try < INIT_TRIES; init_try++)
	  {
	    if (initial_phases)
	      {
		for (k = 1; k < n; k++) 
		  temp_phases[k] = initial_phases[k] + local_random(increment) + local_random(increment);
	      }
	    else
	      {
		for (k = 1; k < n; k++) 
		  temp_phases[k] = local_frandom(2.0);
	      }
	    pk = get_peak(choice, fft_size, n, temp_phases, rl, im);
	    
	    if (pk < local_best)
	      {
		local_best = pk;
		if ((!just_best) ||
		    (pk < overall_min))
		  {
		    for (k = 1; k < n; k++) min_phases[k] = temp_phases[k];
		    if (pk < overall_min)
		      {
			FILE *ofile;
			if (file)
			  ofile = fopen(file, "a");
			else ofile = stderr;
			fprintf(ofile, "%s, %d %f #r(", choice_name[choice], n, pk);
			for (k = 0; k < n - 1; k++) fprintf(ofile, "%f ", min_phases[k]);
			fprintf(ofile, "%f)\n", min_phases[n - 1]);
			if (file) fclose(ofile);
			overall_min = pk;
			set_saved_min(choice, n, pk);
		      }
		  }
	      }
	    
	    if (pk < local_pk)
	      {
		for (k = 1; k < n; k++) choices[start]->phases[k] = temp_phases[k];
		choices[start]->pk = pk;
		local_pk = pk;
	      }
	  }
      }

    while (true)
      {
	int i, j = 0, k, len;
	mus_float_t sum = 0.0, avg;

	len = size;
	day_counter++;
	for (i = 0; i < len; i++) sum += choices[i]->pk;
	avg = sum / len;
	
	for (i = 0; i < len; i++)
	  {
	    pk_data *datum;
	    datum = choices[i];
	    choices[i] = NULL;
	    if (datum->pk < avg)
	      choices[j++] = datum;
	    else free_choices[free_top++] = datum;
	  }
	
	for (i = 0, k = j; k < len; i++, k++)
	  {
	    pk_data *data;
	    mus_float_t *phases;
	    mus_float_t cur_min, temp_min = 100000.0, pk = 100000.0;
	    int llen, local_try, ii, kk, local_tries;
	    pk_data *new_pk;
	    
	    if (i == j)
	      i = 0;
	    
	    data = choices[i];
	    new_pk = free_choices[--free_top];
	    cur_min = data->pk;
	    phases = data->phases;
	    llen = n;
	    local_tries = RETRIES + day_counter * RETRY_MULT;
	    
	    /* try to find a point nearby that is better */
	    for (local_try = 0; (local_try < local_tries) && (pk >= cur_min); local_try++)
	      {
		for (ii = 1; ii < llen; ii++)
		  temp_phases[ii] = fmod(phases[ii] + local_random(increment) + local_random(increment), 2.0); /* not mus_frandom! */
		pk = get_peak(choice, fft_size, n, temp_phases, rl, im);
		
		if (pk < temp_min)
		  {
		    temp_min = pk;
		    new_pk->pk = pk;
		    for (kk = 1; kk < llen; kk++) new_pk->phases[kk] = temp_phases[kk];	    
		  }
	      }
	    
	    /* if a better point is found, try to follow the slopes */
	    if (new_pk->pk < data->pk)
	      {
		int happy = 3;
		for (kk = 1; kk < llen; kk++)
		  diff_phases[kk] = new_pk->phases[kk] - data->phases[kk];
		
		while (happy > 0)
		  {
		    for (kk = 1; kk < llen; kk++)
		      temp_phases[kk] = fmod(new_pk->phases[kk] + local_frandom(diff_phases[kk]), 2.0); /* use frandom 30-mar-11 */
		    pk = get_peak(choice, fft_size, n, temp_phases, rl, im);
		    
		    if (pk < new_pk->pk)
		      {
			new_pk->pk = pk;
			for (kk = 1; kk < llen; kk++) new_pk->phases[kk] = temp_phases[kk];
			happy = 3;
		      }
		    else happy--;
		  }
	      }
	    
	    pk = new_pk->pk;
	    
	    if (pk < local_best)
	      {
		local_best = pk;
		if ((!just_best) ||
		    (pk < overall_min))
		  {
		    for (kk = 1; kk < llen; kk++) min_phases[kk] = new_pk->phases[kk];
		    if (pk < overall_min)
		      {
			FILE *ofile;
			if (file)
			  ofile = fopen(file, "a");
			else ofile = stderr;
			fprintf(ofile, "%s, %d %f #r(", choice_name[choice], n, pk);
			for (kk = 0; kk < llen - 1; kk++) fprintf(ofile, "%f ", min_phases[kk]);
			fprintf(ofile, "%f)\n", min_phases[llen - 1]);
			if (file) fclose(ofile);
			overall_min = pk;
			set_saved_min(choice, llen, pk);
		      }
		  }
		
		day_counter = 0;
	      }
	    choices[k] = new_pk;
	  }
	
	if (day_counter < counts)
	  {
	    /* .9^50 = .005, so starting at .1 bottoms out at .0005
	     *   perhaps the counts variable should be (ceiling (log INCR_MIN incr_mult)) = 90 or so in the current case
	     *   incr_mult is currently always INCR_DOWN = .9
	     */
	    increment *= incr_mult;
	    if (increment < INCR_MIN) 
	      {
		increment = INCR_MIN;
	      }
	    if (increment > INCR_MAX)
	      {
		increment = INCR_MAX;
		incr_mult = INCR_DOWN;
	      }
	  }
	else break;
      }
  }
  free(temp_phases);
  free(diff_phases);
  free(rl);
  free(im);
  free(free_choices);
  if (initial_phases) free(initial_phases);

  {
    int i;
    for (i = 0; i < size; i++)
      {
	free(choices[i]->phases);
	free(choices[i]);
      }
    free(choices);
  }

  return(Xen_list_2(C_double_to_Xen_real(local_best), 
		    xen_make_vct(n, min_phases)));
}



#if HAVE_SCHEME
static s7_pointer g_phases_get_peak(s7_scheme *sc, s7_pointer args)
{
  s7_int choice, i, m, n;
  int fft_size, fft_mult = 128, n1;
  s7_pointer phases;
  s7_pointer *elements;
  double pi2, mx;
  mus_float_t *rl, *im;

  pi2 = M_PI / 2.0;

  choice = s7_integer(s7_car(args));
  n = s7_integer(s7_cadr(args));
  phases = s7_caddr(args);
  elements = s7_vector_elements(phases);

  if (choice == 0)
    n1 = n;
  else 
    {
      if (choice == 3)
	n1 = primes[n];
      else n1 = 2 * n;
    }

  fft_size = (int)pow(2.0, (int)ceil(log(fft_mult * n1) / log(2.0)));
  rl = (mus_float_t *)calloc(fft_size, sizeof(mus_float_t));
  im = (mus_float_t *)calloc(fft_size, sizeof(mus_float_t));

  for (m = 0; m < n; m++)
    {
      double phi;
      int bin;
      phi = pi2 + M_PI * (s7_real(elements[m]));
      if (choice == 0)
	bin = m + 1;
      else 
	{
	  if (choice == 1)
	    bin = 1 + (m * 2);
	  else
	    {
	      if (choice == 2)
		{
		  bin = m * 2;
		  if (bin == 0) bin = 1;
		}
	      else bin = primes[m];
	    }
	}
      rl[bin] = cos(phi);
      im[bin] = sin(phi);
    }

  mus_fft(rl, im, fft_size, -1);
  
  mx = fabs(rl[0]);
  for (i = 1; i < fft_size; i++)
    {
      double tmp;
      tmp = fabs(rl[i]);
      if (tmp > mx)
	mx = tmp;
    }

  free(rl);
  free(im);

  return(s7_make_real(sc, mx));
}
#endif



Xen_wrap_6_optional_args(g_scan_channel_w, g_scan_channel)
Xen_wrap_7_optional_args(g_map_channel_w, g_map_channel)
#if (!HAVE_SCHEME)
Xen_wrap_7_optional_args(g_map_chan_w, g_map_chan)
Xen_wrap_6_optional_args(g_scan_chan_w, g_scan_chan)
Xen_wrap_5_optional_args(g_find_channel_w, g_find_channel)
#endif
Xen_wrap_5_optional_args(g_count_matches_w, g_count_matches)
Xen_wrap_4_optional_args(g_smooth_sound_w, g_smooth_sound)
Xen_wrap_5_optional_args(g_smooth_channel_w, g_smooth_channel)
Xen_wrap_no_args(g_smooth_selection_w, g_smooth_selection)
Xen_wrap_no_args(g_delete_selection_and_smooth_w, g_delete_selection_and_smooth)
Xen_wrap_5_optional_args(g_delete_samples_and_smooth_w, g_delete_samples_and_smooth)
Xen_wrap_3_optional_args(g_reverse_sound_w, g_reverse_sound)
Xen_wrap_5_optional_args(g_reverse_channel_w, g_reverse_channel)
Xen_wrap_no_args(g_reverse_selection_w, g_reverse_selection)
Xen_wrap_8_optional_args(g_swap_channels_w, g_swap_channels)
Xen_wrap_4_optional_args(g_insert_silence_w, g_insert_silence)
Xen_wrap_1_optional_arg(g_scale_selection_to_w, g_scale_selection_to)
Xen_wrap_1_arg(g_scale_selection_by_w, g_scale_selection_by)
Xen_wrap_3_optional_args(g_scale_to_w, g_scale_to)
Xen_wrap_3_optional_args(g_scale_by_w, g_scale_by)
Xen_wrap_2_optional_args(g_env_selection_w, g_env_selection)
Xen_wrap_7_optional_args(g_env_sound_w, g_env_sound)
Xen_wrap_6_optional_args(g_env_channel_w, g_env_channel)
Xen_wrap_7_optional_args(g_env_channel_with_base_w, g_env_channel_with_base)
Xen_wrap_7_optional_args(g_ramp_channel_w, g_ramp_channel)
Xen_wrap_8_optional_args(g_xramp_channel_w, g_xramp_channel)
Xen_wrap_3_optional_args(g_fft_w, g_fft)
Xen_wrap_7_optional_args(g_snd_spectrum_w, g_snd_spectrum)
Xen_wrap_5_optional_args(g_convolve_with_w, g_convolve_with)
Xen_wrap_2_optional_args(g_convolve_selection_with_w, g_convolve_selection_with)
Xen_wrap_5_optional_args(g_src_sound_w, g_src_sound)
Xen_wrap_2_optional_args(g_src_selection_w, g_src_selection)
Xen_wrap_6_optional_args(g_src_channel_w, g_src_channel)
Xen_wrap_5_optional_args(g_pad_channel_w, g_pad_channel)
Xen_wrap_9_optional_args(g_filter_channel_w, g_filter_channel)
Xen_wrap_6_optional_args(g_filter_sound_w, g_filter_sound)
Xen_wrap_3_optional_args(g_filter_selection_w, g_filter_selection)
Xen_wrap_8_optional_args(g_clm_channel_w, g_clm_channel)
Xen_wrap_no_args(g_sinc_width_w, g_sinc_width)
Xen_wrap_1_arg(g_set_sinc_width_w, g_set_sinc_width)

Xen_wrap_5_optional_args(g_fpsap_w, g_fpsap)

#if HAVE_SCHEME
static s7_pointer acc_sinc_width(s7_scheme *sc, s7_pointer args) {return(g_set_sinc_width(s7_cadr(args)));}
#endif

void g_init_sig(void)
{
#if HAVE_SCHEME
  s7_pointer i, pcl_t;
  i = s7_make_symbol(s7, "integer?");
  pcl_t = s7_make_circular_signature(s7, 0, 1, s7_t(s7));
  /* asssert_sample means all positions are '(b i) and assert_channel (or edpos) means the snd/chn/edpos args are #t
   *   so the signatures are almost useless here -- this should be changed!  snd=<sound>, chn=int, edpos=int
   */
#endif

  Xen_define_typed_procedure(S_scan_channel,       g_scan_channel_w,       1, 5, 0, H_scan_channel,       pcl_t);
  Xen_define_typed_procedure(S_count_matches,      g_count_matches_w,      1, 4, 0, H_count_matches,      pcl_t);
  Xen_define_typed_procedure(S_map_channel,        g_map_channel_w,        1, 6, 0, H_map_channel,        pcl_t);

  Xen_define_typed_procedure(S_smooth_sound,       g_smooth_sound_w,       0, 4, 0, H_smooth_sound,       pcl_t);
  Xen_define_typed_procedure(S_smooth_selection,   g_smooth_selection_w,   0, 0, 0, H_smooth_selection,   pcl_t);
  Xen_define_typed_procedure(S_delete_selection_and_smooth, g_delete_selection_and_smooth_w, 0, 0, 0, H_delete_selection_and_smooth, pcl_t);
  Xen_define_typed_procedure(S_delete_samples_and_smooth, g_delete_samples_and_smooth_w, 2, 3, 0, H_delete_samples_and_smooth, pcl_t);
  Xen_define_typed_procedure(S_reverse_sound,      g_reverse_sound_w,      0, 3, 0, H_reverse_sound,      pcl_t);
  Xen_define_typed_procedure(S_reverse_selection,  g_reverse_selection_w,  0, 0, 0, H_reverse_selection,  pcl_t);
  Xen_define_typed_procedure(S_swap_channels,      g_swap_channels_w,      0, 8, 0, H_swap_channels,      pcl_t);
  Xen_define_typed_procedure(S_insert_silence,     g_insert_silence_w,     2, 2, 0, H_insert_silence,     pcl_t);

  Xen_define_typed_procedure(S_scale_selection_to, g_scale_selection_to_w, 0, 1, 0, H_scale_selection_to, pcl_t);
  Xen_define_typed_procedure(S_scale_selection_by, g_scale_selection_by_w, 1, 0, 0, H_scale_selection_by, pcl_t);
  Xen_define_typed_procedure(S_scale_to,           g_scale_to_w,           0, 3, 0, H_scale_to,           pcl_t);
  Xen_define_typed_procedure(S_scale_by,           g_scale_by_w,           1, 2, 0, H_scale_by,           pcl_t);
  Xen_define_typed_procedure(S_env_selection,      g_env_selection_w,      1, 1, 0, H_env_selection,      pcl_t);
  Xen_define_typed_procedure(S_env_sound,          g_env_sound_w,          1, 6, 0, H_env_sound,          pcl_t);
  Xen_define_typed_procedure(S_fft,                g_fft_w,                2, 1, 0, H_fft,                pcl_t);
  Xen_define_typed_procedure(S_snd_spectrum,       g_snd_spectrum_w,       1, 6, 0, H_snd_spectrum,       pcl_t);
  Xen_define_typed_procedure(S_convolve_with,      g_convolve_with_w,      1, 4, 0, H_convolve_with,      pcl_t);
  Xen_define_typed_procedure(S_convolve_selection_with, g_convolve_selection_with_w, 1, 1, 0, H_convolve_selection_with, pcl_t);
  Xen_define_typed_procedure(S_src_sound,          g_src_sound_w,          1, 4, 0, H_src_sound,          pcl_t);
  Xen_define_typed_procedure(S_src_selection,      g_src_selection_w,      1, 1, 0, H_src_selection,      pcl_t);
  Xen_define_typed_procedure(S_filter_channel,     g_filter_channel_w,     1, 8, 0, H_filter_channel,     pcl_t);
  Xen_define_typed_procedure(S_filter_sound,       g_filter_sound_w,       1, 5, 0, H_filter_sound,       pcl_t);
  Xen_define_typed_procedure(S_filter_selection,   g_filter_selection_w,   1, 2, 0, H_filter_selection,   pcl_t);

  Xen_define_typed_procedure(S_reverse_channel,    g_reverse_channel_w,    0, 5, 0, H_reverse_channel,    pcl_t);
  Xen_define_typed_procedure(S_clm_channel,        g_clm_channel_w,        1, 7, 0, H_clm_channel,        pcl_t);
  Xen_define_typed_procedure(S_env_channel,        g_env_channel_w,        1, 5, 0, H_env_channel,        pcl_t);
  Xen_define_typed_procedure(S_env_channel_with_base, g_env_channel_with_base_w, 1, 6, 0, H_env_channel_with_base, pcl_t);
  Xen_define_typed_procedure(S_ramp_channel,       g_ramp_channel_w,       2, 5, 0, H_ramp_channel,       pcl_t);
  Xen_define_typed_procedure(S_xramp_channel,      g_xramp_channel_w,      2, 6, 0, H_xramp_channel,      pcl_t);
  Xen_define_typed_procedure(S_smooth_channel,     g_smooth_channel_w,     0, 5, 0, H_smooth_channel,     pcl_t);
  Xen_define_typed_procedure(S_src_channel,        g_src_channel_w,        1, 5, 0, H_src_channel,        pcl_t);
  Xen_define_typed_procedure(S_pad_channel,        g_pad_channel_w,        2, 3, 0, H_pad_channel,        pcl_t);

  Xen_define_typed_dilambda(S_sinc_width, g_sinc_width_w, H_sinc_width, 
			    S_set S_sinc_width, g_set_sinc_width_w, 0, 0, 1, 0,
			    s7_make_signature(s7, 1, i), s7_make_signature(s7, 2, i, i));

  Xen_define_typed_procedure(S_fpsap, g_fpsap_w, 3, 2, 0, H_fpsap, pcl_t);

#if HAVE_SCHEME
  Xen_define_typed_procedure("phases-get-peak", g_phases_get_peak, 3, 0, 0, "", pcl_t);

  s7_set_setter(s7, ss->sinc_width_symbol, s7_make_function(s7, "[acc-" S_sinc_width "]", acc_sinc_width, 2, 0, false, "accessor"));
  s7_set_documentation(s7, ss->sinc_width_symbol, "*sinc-width*: sampling rate conversion sinc width (10).");

  gc_vect = s7_make_vector(s7, 4);
  s7_gc_protect(s7, gc_vect);
#endif

#if (!HAVE_SCHEME)
  Xen_define_procedure(S_scan_chan,    g_scan_chan_w,    1, 5, 0, H_scan_chan);
  Xen_define_procedure(S_find_channel, g_find_channel_w, 1, 4, 0, H_find_channel);
  Xen_define_procedure(S_map_chan,     g_map_chan_w,     1, 6, 0, H_map_chan);
#endif
}

#if 0
/* these work in snd-test, but are not faster */

(define* (scan-channel func (beg 0) dur snd chn edpos)
  (let ((end (if dur (min (+ beg dur) (framples snd chn)) (framples snd chn)))
	(rd (make-sampler beg snd chn 1 edpos)))
    (do ((pos beg (+ pos 1)))
        ((or (>= pos end)
	     (func (next-sample rd)))
         (and (< pos end)
	      pos)))))

(define* (count-matches func (beg 0) snd chn edpos)
  (let ((end (framples snd chn edpos))
	(matches 0)
	(reader (make-sampler beg snd chn 1 edpos)))
    (do ((i beg (+ i 1)))
	((>= i end) matches)
      (if (func (next-sample reader))
	  (set! matches (+ matches 1))))))


(define-macro* (scan-channel-1 func (beg 0) dur snd chn edpos)
  (let ((end (if dur (min (+ beg dur) (framples snd chn)) (framples snd chn))))
    `(let ((rd (make-sampler ,beg ,snd ,chn 1 ,edpos))
	   (f ,func))
       (define (call)
	 (do ((pos ,beg (+ pos 1)))
	     ((or (>= pos ,end)
		  (f (next-sample rd)))
	      (and (< pos ,end)
		   pos))))
       (call))))

(define* (scan-channel-2 func (beg 0) dur snd chn edpos)
  (define* (uncons lst (res ()))
    (if (null? lst) res (uncons (cdr lst) (cons (list (caar lst) (cdar lst)) res))))
  (let ((end (if dur (min (+ beg dur) (framples snd chn)) (framples snd chn)))
	(source (procedure-source func))
	(e (funclet func)))
    (let ((arg (caadr source))
	  (body (cddr source))
	  (new-e (if (eq? e (rootlet)) () (uncons (let->list e))))
	  (rd (make-sampler beg snd chn 1 edpos)))
      (define call (apply let new-e 
			  `((lambda ()
			     (do ((pos ,beg (+ pos 1))
				(,arg (next-sample ,rd) (next-sample ,rd)))
			       ((or (>= pos ,end)
				    (begin ,@body))
				(and (< pos ,end)
				     pos)))))))
      (call))))


*/
#endif



