#include "snd.h"

#if HAVE_OSS
  #include <sys/ioctl.h>
#endif

/* programmatic access to Snd -- also used for init files, M-x, etc */

static char clm_white_space[3]={' ','\t','\n'};
static char clm_null[1]={'\0'};
static char clm_close_paren[2]={')','\0'};
static char clm_double_quote[1]={'\"'};
static char clm_white_space_or_close_paren[5]={' ','\t','\n',')',','};

#define SEXPR_SIZE 512
static char sexpr_buf[SEXPR_SIZE];

#define CLM_BUFFER_SIZE 16384
static char *clm_buffer = NULL;
static int clm_buffer_size = 0;

static int raw_buf[4];

snd_info *find_sound(snd_state *ss, char *name)
{
  snd_info *sp;
  int i;
  for (i=0;i<ss->max_sounds;i++)
    {
      sp = ss->sounds[i];
      if ((sp) && (sp->inuse))
	{
	  if ((strcmp(name,sp->shortname) == 0) || (strcmp(name,sp->fullname) == 0)) return(sp);
	}
    }
  return(NULL);
}

static void snd_fvalue(snd_state *ss, float val)
{
  sprintf(sexpr_buf,"%f",val);
  if (ss->mx_sp)
    report_in_minibuffer(ss->mx_sp,sexpr_buf);
  else write(ss->to_clm,sexpr_buf,strlen(sexpr_buf)+1);
}

static void snd_ivalue(snd_state *ss, int val)
{
  sprintf(sexpr_buf,"%d",val);
  if (ss->mx_sp)
    report_in_minibuffer(ss->mx_sp,sexpr_buf);
  else write(ss->to_clm,sexpr_buf,strlen(sexpr_buf)+1);
}

static void snd_svalue(snd_state *ss, char *val)
{
  if (ss->mx_sp)
    report_in_minibuffer(ss->mx_sp,val);
  else write(ss->to_clm,val,strlen(val)+1);
}

static void snd_lvalue(snd_state *ss, env *e)
{
  char *news;
  news = env_to_string(e);
  if (ss->mx_sp)
    report_in_minibuffer(ss->mx_sp,news);
  else write(ss->to_clm,news,strlen(news)+1);
  free(news);
}


typedef struct {
  int type;
  char *name;
  void *val;
} lv;

static lv *find_symbol(char *name);
static float symbol_fvalue (lv *v);

static float fvalue_snd(char *str)
{
  float val = 0.0;
  lv *v;
  if ((str) && (*str)) 
    {
      if ((v = find_symbol(str)))
	val = symbol_fvalue(v);
      else sscanf(str,"%f",&val);
    }
  return(val);
}

static int ivalue_snd(char *str)
{
  int val = 0;
  lv *v;
  if ((str) && (*str)) 
    {
      if ((v = find_symbol(str)))
	val = (int)(symbol_fvalue(v));
      else sscanf(str,"%d",&val);
    }
  return(val);
}

static void clm_snd_close(snd_state *ss, char *name)
{
  snd_info *sp;
  sp = find_sound(ss,complete_filename(name));
  if (sp) snd_close_file(sp,ss);
}

static void clm_cut(snd_state *ss)
{
  if (selection_is_ours())
    {
      CopyToClipboard(main_PANE(ss),ss);
      delete_selection();
    }
}

static void clm_copy(snd_state *ss)
{
  if (selection_is_ours()) CopyToClipboard(main_PANE(ss),ss);
}

static void clm_dialog(snd_state *ss, char *msg, char *beg, char *len)
{
  int start,num;
  sscanf(beg,"%d",&start);
  sscanf(len,"%d",&num);
  start_clm_dialog(ss,msg); /* this dialog should eventually return ":ok" or ":cancel" */
}

static void clm_eval(snd_state *ss, chan_info *cp, char *expr)
{
  sop *tree;
  if (cp) snd_search_fd = init_sample_read(cp->cursor,cp,READ_FORWARD);
  tree = sop_parse(expr);
  if (tree) {sop_eval(tree); free_sop(tree);}
  if (cp) snd_search_fd = free_snd_fd(snd_search_fd);
}

static void clm_revert(snd_state *ss,char *name)
{
  snd_info *sp;
  int i;
  sp=find_sound(ss,name);
  if (sp)
    {
      for (i=0;i<sp->nchans;i++) {revert_edits(sp->chans[i],NULL); update_graph(sp->chans[i],NULL);}
      reflect_file_revert_in_label(sp);
      reflect_file_revert_in_menu(ss);
    }
}

static void clm_save_as(snd_state *ss, char *file1, char *file2)
{
  snd_info *sp;
  file_info *hdr;
  sp=find_sound(ss,file1);
  if (sp)
    {
      hdr = sp->hdr;
      save_edits_2(sp,file2,hdr->type,hdr->format,hdr->srate);
    }
}

static void clm_view(snd_state *ss, char *name)
{
  ss->viewing = 1;
  snd_open_file(name,ss); /* I think this is copied in every case (make_file_info and make_snd_info) */
  ss->viewing = 0;
}


static int *clm_snd_data(snd_state *ss, int *length)
{
  int *arr;
  int i,j,beg,ints,blen,len;
  if (!clm_buffer) 
    {
      clm_buffer = (char *)calloc(CLM_BUFFER_SIZE,sizeof(char));
      clm_buffer_size = CLM_BUFFER_SIZE;
    }
  write(ss->to_clm,"(ok)",5);
  read(ss->from_clm,clm_buffer,4);
  len = big_endian_int(*((int *)clm_buffer));
  (*length) = len;
  blen = (clm_buffer_size/4);
  arr = (int *)calloc(len,sizeof(int));
  for (beg=0;beg<len;beg+=blen)
    {
      read(ss->from_clm,clm_buffer,clm_buffer_size);
      ints = len-beg;
      if (ints > blen) ints=blen;
      for (i=0,j=0;i<ints;i++,j+=4) arr[i+beg] = big_endian_int(*((int *)(clm_buffer+j)));
    }
  write(ss->to_clm,"(ok)",5);
  return(arr);
}

static void snd_clm_data (snd_state *ss, int len, int *data)
{
  int beg,ints,blen,val;
  val = big_endian_int(len);
  write(ss->to_clm,(char *)(&val),4);
  blen = (clm_buffer_size/4);
#ifdef CLM_LITTLE_ENDIAN
  for (beg=0;beg<len;beg++)
    data[beg] = big_endian_int(data[beg]);
#endif
  for (beg=0;beg<len;beg+=blen)
    {
      ints = len-beg;
      if (ints > blen) ints=blen;
      write(ss->to_clm,(char *)(data+beg),ints*4);
    }
}

typedef struct {
  int i,n;
  mixdata *md;
} mdint;

static int get_md_from_int_1(mixdata *md, void *mi)
{
  mdint *m = (mdint *)mi;
  m->i++;
  if (m->i == m->n)
    {
      m->md = md;
      return(1);
    }
  return(0);
}

static int get_md_from_int(chan_info *cp, void *mi) {return(map_over_channel_mixes(cp,get_md_from_int_1,mi));}

static mixdata *md_from_int(snd_state *ss, int n)
{
  mdint *m;
  mixdata *md;
  m = (mdint *)calloc(1,sizeof(mdint));
  m->n = n;
  m->i = 0;
  m->md = NULL;
  map_over_chans(ss,get_md_from_int,(void *)m);
  md = m->md;
  free(m);
  return(md);
}

static int get_int_from_md_1(mixdata *md, void *mi)
{
  mdint *m = (mdint *)mi;
  m->i++;
  return(m->md == md);
}

static int get_int_from_md(chan_info *cp, void *mi) {return(map_over_channel_mixes(cp,get_int_from_md_1,mi));}

int int_from_md(snd_state *ss, mixdata *md)
{
  mdint *m;
  int val;
  m = (mdint *)calloc(1,sizeof(mdint));
  m->i = 0;
  m->md = md;
  map_over_chans(ss,get_int_from_md,(void *)m);
  val = m->i;
  free(m);
  return(val);
}

static void clm_mix(snd_info *sp, char *tempfile, int beg, int num, int deletable, int group)
{
  mix(beg,(num == -1) ? (current_ed_samples(sp->chans[0])) : num,sp->nchans,sp->chans,tempfile,(deletable) ? DELETE_ME : DONT_DELETE_ME,0,group);
}

enum {MIX_POSITION,MIX_LENGTH,MIX_SPEED,MIX_AMP,MIX_GROUPS,MIX_ANCHOR,MIX_STATE};

static int get_mix_idata(snd_state *ss, int n, int type)
{
  mixdata *md;
  console_state *cs;
  md = md_from_int(ss,n);
  if (md) 
    {
      cs = md->current_cs;
      switch (type)
	{
	case MIX_POSITION: return(cs->beg); break;
	case MIX_LENGTH: return(cs->len); break;
	case MIX_ANCHOR: return(md->anchor); break;
	case MIX_GROUPS: return(cs->groups); break;
	case MIX_STATE: return(md->state); break;
	default: fprintf(stderr,"oops: get_mix_idata(ss,%d,%d);\n",n,type); break;
	}
    }
  return(-1);
}

static void set_mix_idata(snd_state *ss, int n, int val, int type)
{
  mixdata *md;
  console_state *cs;
  int i,j,old_gr;
  md = md_from_int(ss,n);
  if (md) 
    {
      cs = md->current_cs;
      switch (type)
	{
	case MIX_POSITION: 
	  cs->beg = val; 
	  if (md->mixer) set_mix_title_beg(md,md->mixer);
	  remix_file(md); 
	  break;
	case MIX_LENGTH:
	  /* weird!  we'll do it, but under protest */
	  cs->len = val;
	  if (md->mixer) set_mix_title_beg(md,md->mixer);
	  remix_file(md); 
	  break;
	case MIX_ANCHOR:
	  md->anchor = val;
	  update_graph(md->cp,NULL);
	  break;
	case MIX_GROUPS:
	  /* untoggle bits on now, and off in new, etc */
	  old_gr = cs->groups;
	  for (i=0,j=1;i<ss->ngroups;i++,j*=2)
	    {
	      if ((j & old_gr) && (!(j & val)))
		toggle_group(md,FALSE,i);  /* may need toggle here */
	      if ((!(j & old_gr)) && (j & val))
		toggle_group(md,TRUE,i);
	    }
	  cs->groups = val;
	  break;
	case MIX_STATE:
	  md->state = val;
	  fixup_mixmark(md);
	  break;
	}
    }
}

static float get_mix_fdata(snd_state *ss, int n, int chan, int type)
{
  mixdata *md;
  console_state *cs;
  md = md_from_int(ss,n);
  if (md) 
    {
      cs = md->current_cs;
      switch (type)
	{
	case MIX_SPEED: return(cs->speed); break;
	case MIX_AMP: return(cs->scalers[chan]); break;
	default: fprintf(stderr,"oops: get_mix_fdata(ss,%d,%d,%d);\n",n,chan,type); break;
	}
    }
  return(-1.0);
}

static void set_mix_fdata(snd_state *ss, int n, int chan, float val, int type)
{
  mixdata *md;
  md = md_from_int(ss,n);
  if (md) 
    {
      switch (type)
	{
	case MIX_SPEED: respeed(md,val); break;
	case MIX_AMP: reamp(md,chan,val); break;
	default: fprintf(stderr,"oops: set_mix_fdata(ss,%d,%d,%f,%d);\n",n,chan,val,type); break;
	}
      remix_file(md);
    }
}

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

char *sound_type(int type)
{
  switch (type)
    {
    case NeXT_sound_file: return("NeXT/Sun"); break;
    case AIFF_sound_file: return("AIFF"); break;
    case RIFF_sound_file: return("RIFF"); break;
    case BICSF_sound_file: return("BICSF"); break;
    case NIST_sound_file: return("NIST"); break;
    case INRS_sound_file: return("INRS"); break;
    case ESPS_sound_file: return("ESPS"); break;
    case SVX_sound_file: return("SVX8"); break;
    case VOC_sound_file: return("VOC"); break;
    case SNDT_sound_file: return("SNDT"); break;
    case raw_sound_file: return("raw (no header)"); break;
    case SMP_sound_file: return("SMP"); break;
    case SD2_sound_file: return("Sound Designer 2"); break;
    case AVR_sound_file: return("AVR"); break;
    case IRCAM_sound_file: return("IRCAM"); break;
    case SD1_sound_file: return("Sound Designer 1"); break;
    case SPPACK_sound_file: return("SPPACK"); break;
    case MUS10_sound_file: return("Mus10"); break;
    case HCOM_sound_file: return("HCOM"); break;
    case PSION_sound_file: return("PSION"); break;
    case MAUD_sound_file: return("MAUD"); break;
    case IEEE_sound_file: return("IEEE text"); break;
    case DeskMate_sound_file: return("DeskMate"); break;
    case DeskMate_2500_sound_file: return("DeskMate_2500"); break;
    case Matlab_sound_file: return("Matlab"); break;
    case ADC_sound_file: return("ADC/OGI"); break;
    case SoundEdit_sound_file: return("SoundEdit"); break;
    case SoundEdit_16_sound_file: return("SoundEdit 16"); break;
    case DVSM_sound_file: return("DVSM"); break;
    case MIDI_file: return("MIDI"); break;
    case Esignal_file: return("Esignal"); break;
    case soundfont_sound_file: return("SoundFont"); break;
    case gravis_sound_file: return("Gravis Ultrasound patch"); break;
    case comdisco_sound_file: return("Comdisco SPW signal"); break;
    case goldwave_sound_file: return("Goldwave sample"); break;
    case srfs_sound_file: return("SRFS"); break;
    case MIDI_sample_dump: return("MIDI sample dump"); break;
    case DiamondWare_sound_file: return("DiamondWare"); break;
    case RealAudio_sound_file: return("RealAudio"); break;
    case ADF_sound_file: return("CSRE adf"); break;
    case SBStudioII_sound_file: return("SBStudioII"); break;
    case Delusion_sound_file: return("Delusion"); break;
    case Farandole_sound_file: return("Farandole"); break;
    case Sample_dump_sound_file: return("Sample dump"); break;
    case Ultratracker_sound_file: return("Ultratracker"); break;
    case Yamaha_TX16_sound_file: return("TX-16"); break;
    case Yamaha_SY85_sound_file: return("Sy-85"); break;
    case digiplayer_sound_file: return("Digiplayer ST3"); break;
    case Covox_sound_file: return("Covox V8"); break;
    case SPL_sound_file: return("Digitracker SPL"); break;
    case AVI_sound_file: return("AVI"); break;
    default: return(snd_string_unknown); break;
    }
}

char *sound_format (int format)
{
  switch (format)
    {
    case snd_unsupported: return("unsupported"); break;
    case snd_no_snd: return("no_snd"); break;
    case snd_16_linear: return("16-bit big endian"); break;
    case snd_8_mulaw: return("mulaw"); break;
    case snd_8_linear: return("8-bit"); break;
    case snd_32_float: return("32-bit big endian float"); break;
    case snd_32_linear: return("32-bit big endian"); break;
    case snd_8_alaw: return("alaw"); break;
    case snd_8_unsigned: return("8-bit unsigned"); break;
    case snd_24_linear: return("24-bit big endian"); break;
    case snd_64_double: return("64-bit big endian double"); break;
    case snd_16_linear_little_endian: return("16-bit little endian"); break;
    case snd_32_linear_little_endian: return("32-bit little endian"); break;
    case snd_32_float_little_endian: return("32-bit little endian float"); break;
    case snd_64_double_little_endian: return("64-bit little endian double"); break;
    case snd_16_unsigned: return("16-bit big endian unsigned"); break;
    case snd_16_unsigned_little_endian: return("16-bit little endian unsigned"); break;
    case snd_12_unsigned: return("12-bit big endian unsigned"); break;
    case snd_12_unsigned_little_endian: return("12-bit little endian unsigned"); break;
    case snd_12_linear: return("12-bit big endian"); break;
    case snd_12_linear_little_endian: return("12-bit little endian"); break;
    case snd_24_linear_little_endian: return("24-bit little endian"); break;
    case snd_32_vax_float: return("vax float"); break;
    default: return(snd_string_unknown); break;
    }
}

static char *sc = NULL;

char *sound_comment(file_info *hdr)
{
  int start,end,fd;
  start = hdr->comment_start;
  end = hdr->comment_end;
  if (end > start)
    {
      /* open hdr->name and get the comment */
      if (sc) free(sc);
      sc = (char *)calloc(end-start+2,sizeof(char));
      fd = open(hdr->name,O_RDONLY,0);
      lseek(fd,start,0);
      read(fd,sc,end-start+1);
      close(fd);
      return(sc);
    }
  else return("");
}

static char timestr[64];

void display_info(snd_info *sp)
{
  file_info *hdr;
  if (sp)
    {
      hdr = sp->hdr;
      if (hdr)
	{
	  if (!clm_buffer) 
	    {
	      clm_buffer = (char *)calloc(CLM_BUFFER_SIZE,sizeof(char));
	      clm_buffer_size = CLM_BUFFER_SIZE;
	    }
#ifndef BEOS
	  strftime(timestr,64,"%a %d-%b-%y %H:%M %Z",localtime(&(sp->write_date)));
#endif
	  sprintf(clm_buffer,snd_string_display_info,
		  hdr->srate,
		  hdr->chans,
		  (float)(hdr->samples)/(float)(hdr->chans * hdr->srate),
		  sound_type(hdr->type),
		  sound_format(hdr->format),
		  timestr,
		  sound_comment(hdr));
	  ssnd_help(sp->state,
		    sp->shortname,
		    clm_buffer,
		    NULL);
	}
    }
}

static void clm_y_axis (chan_info *cp, float high) {set_y_limits(cp,cp->axis,high);}
static void clm_select_sound(snd_state *ss,int n) {if (ss->sounds[n]) select_sound(ss,ss->sounds[n]);}
static void clm_set_region (chan_info *cp, int low, int high) {define_region(cp,low,high);}
static void clm_goto_mark(chan_info *cp, char *name) {mark *mp; mp=find_named_mark(cp,name); if (mp) cursor_moveto(cp,mp->samp);}
static int clm_mark(chan_info *cp,char *name) {mark *mp; mp=find_named_mark(cp,name); if (mp) return(mp->samp); return(0);}
static void clm_set_mark(chan_info *cp, char *name, int samp) {add_mark(samp,name,cp);}

static void clm_select_channel(snd_state *ss,int n) 
{
  snd_info *sp;
  if (ss->selected_sound >= 0) 
    {
      sp = (snd_info *)(ss->sounds[ss->selected_sound]);
      if ((sp) && (sp->nchans > n))
	sp->selected_channel = n;
    }
}

static float clm_get_y(chan_info *cp, int n) 
{
  snd_fd *sf; 
  float val; 
  sf = init_sample_read(cp->cursor+n,cp,READ_FORWARD); 
  val=any_sample(sf,0);
  free_snd_fd(sf);
  return(val);
}

/* TODO: args to ufuns */

static void clm_get_filter(snd_info *sp) 
{
  env *e;
  int i;
  snd_state *ss = sp->state;
  if (!clm_buffer) 
    {
      clm_buffer = (char *)calloc(CLM_BUFFER_SIZE,sizeof(char));
      clm_buffer_size = CLM_BUFFER_SIZE;
    }
  sprintf(clm_buffer,"'(");
  if ((sp) && (e = sp->filter_env))
    {
      for (i=0;i<e->pts*2;i++)
	{
	  sprintf(sexpr_buf,"%f ",e->data[i]);
	  strcat(clm_buffer,sexpr_buf);
	}
    }
  strcat(clm_buffer,")");
  if (ss->mx_sp)
    report_in_minibuffer(ss->mx_sp,clm_buffer);
  else write(ss->to_clm,clm_buffer,strlen(clm_buffer)+1);
}

static void clm_set_filter(snd_info *sp, char *str, char *rest) 
{
  int err;
  sprintf(clm_buffer,"%s %s",str,rest);
  sp->filter_env = scan_envelope_and_report_error(sp,clm_buffer,&err);
} 


static int ybuf[1];

static void clm_set_y(chan_info *cp, int n, float val) 
{
  ybuf[0] = (int)(val*clm_sndfix);
  change_samples(cp->cursor+n,1,ybuf,cp,LOCK_MIXES);
}

static void clm_get_region (snd_state *ss,int n) 
{
  int len;
  int *data;
  len = 1+region_len(n)*region_chans(n);
  data = send_region(ss,n);
  if (data)
    {
      snd_clm_data(ss,len,data);
      free(data);
    }
}

static void clm_find(chan_info *cp, snd_info *sp, char *str)
{
  if (str)
    {
      if (sp->search_tree) {free_sop(sp->search_tree); sp->search_tree = NULL;}
      if (sp->search_expr) free(sp->search_expr);
      sp->search_expr = copy_string(str);
      sp->search_tree = sop_parse(str);
      if (!(sp->search_tree))
	{
	  sprintf(sexpr_buf,"%s: %s",str,sop_parse_error_name());
	  text_set_string(snd_widget(sp,W_snd_info),sexpr_buf);
	}
      else
	{
	  sp->searching = 1;
	  if (cp) handle_cursor(cp,cursor_search(cp,1));
	}
    }
}

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

static void snd_reopen_file(char *name, snd_state *ss)
{
  /* here we're being told that the data in the file has changed and we have to re-read it. */
  /* This is not considered an edit of the sound (i.e. no update to edit lists etc) */
  snd_info *sp;
  chan_info *cp;
  snd_data *sd;
  file_info *hdr;
  float len,maxd;
  int i,copied;
  sp = find_sound(ss,name);
  if (sp)
    {
      for (i=0;i<sp->nchans;i++)
	{
	  copied = 0;
	  cp = sp->chans[i];
	  sd = cp->sounds[0];
	  if (sd->inuse) {sd = copy_snd_data(sd,cp); copied = 1;}
	  sd->inuse = TRUE;
	  snd_file_reset(ss,sd,0);
	  maxd = get_maxamp(ss,sp,cp);
	  if (copied) {sd->inuse = FALSE; free_snd_data(sd); sd = NULL;}
	  if (maxd > 1.0) clm_y_axis(cp,maxd);
	  update_graph(cp,NULL);
	}
    }
  else 
    {
      sp = snd_open_file(name,ss);
      if (sp)
	{
	  hdr = sp->hdr;
	  len = (float)(hdr->samples-1)/(float)(hdr->chans * hdr->srate); /* samples-1 because we start at 0 */
	  for (i=0;i<sp->nchans;i++)
	    {
	      cp = sp->chans[i];
	      clm_x_axis(cp,0.0,len);
	      maxd = get_maxamp(ss,sp,cp);
	      if (maxd > 1.0) clm_y_axis(cp,maxd);
	    }
	}
    }
}

static void clm_disconnect(snd_state *ss)
{
  CLM_disconnect(ss);
  if (ss->to_clm) {close(ss->to_clm); ss->to_clm = 0;}
  if (ss->from_clm) {close(ss->from_clm); ss->from_clm = 0;}
}

static void clm_call_ufun(chan_info *cp, snd_info *sp, char *fun, int autocall)
{
  int i;
  chan_info *ncp;
  if (autocall < 0) 
    {
      sp->auto_ufun = 0;
      for (i=0;i<sp->nchans;i++) {ncp = sp->chans[i]; ncp->ufuning = 0; update_graph(ncp,NULL);} /* ??? */
    }
  else
    {
      sp->ufunp = find_ufun(sp,fun);
      if (sp->ufunp)
	{
	  sp->ufun_nargs = 0;
	  if (sp->ufun_args) free(sp->ufun_args);
	  /* TODO: args here */
	  sp->ufun_args = NULL;
	  invoke_ufun_with_ptr(cp,sp->ufunp,NULL,0,0,0);
	  sp->auto_ufun = autocall;
	}
    }
}

#if HAVE_OSS
  #ifdef LINUX
    #include <sys/soundcard.h>
  #else
    #include <soundcard.h>
  #endif

static void clear_soundcard_inputs(void)
{
  /* turn off inputs (they create an unbelievable amount of noise) and maximize outputs */
  int fd,amp,devmask;
  fd = open("/dev/dsp",O_WRONLY,0);
  if (fd == -1) return;
  amp = 0;
  ioctl(fd,SOUND_MIXER_READ_DEVMASK,&devmask);
  if (SOUND_MASK_MIC & devmask) ioctl(fd,MIXER_WRITE(SOUND_MIXER_MIC),&amp);
  if (SOUND_MASK_IGAIN & devmask) ioctl(fd,MIXER_WRITE(SOUND_MIXER_IGAIN),&amp);
  if (SOUND_MASK_LINE & devmask) ioctl(fd,MIXER_WRITE(SOUND_MIXER_LINE),&amp);
  amp = (99<<8) + 99;
  if (SOUND_MASK_VOLUME & devmask) ioctl(fd,MIXER_WRITE(SOUND_MIXER_VOLUME),&amp);
  if (SOUND_MASK_OGAIN & devmask) ioctl(fd,MIXER_WRITE(SOUND_MIXER_OGAIN),&amp);
  if (SOUND_MASK_PCM & devmask) ioctl(fd,MIXER_WRITE(SOUND_MIXER_PCM),&amp);
  close(fd);
}
#endif

/* TODO: to implement the edit-history feature, we need setf var file, channel, or region (as temp file or buffer) */

/* lisp defvar support for envelopes, floats, strings: defvar, set[f,q], symbol_[f,s,l]value */

enum {LV_FLOAT,LV_STRING,LV_LIST};

static int lv_next = 0;
static int lv_space = 0;
static lv **lvs = NULL;

static env *get_list(snd_info *sp, char *str) 
{
  int err;
  lv *v;
  if ((v = find_symbol(str)) && (v->type == LV_LIST)) return((env *)(v->val));
  return(scan_envelope_and_report_error(sp,str,&err));
}

static float *get_float(char *str) 
{
  float *val; 
  lv *v;
  if ((v = find_symbol(str)) && (v->type == LV_FLOAT)) 
    val = (float *)(v->val);
  else 
    {
      val = (float *)calloc(1,sizeof(float)); 
      sscanf(str,"%f",val); 
    }
  return(val);
}

static char *get_string(char *str)
{
  char *news;
  int len,i;
  lv *v;
  if ((v = find_symbol(str)) && (v->type == LV_STRING)) return(copy_string(v->val));
  len = strlen(str);
  news = (char *)calloc(len,sizeof(char));
  i = 1;
  while ((i<len) && (str[i] != '"'))
    {
      news[i-1] = str[i];
      i++;
    }
  news[i] = '\0';
  return(news);
}
 
static int parse_lval(snd_state *ss, snd_info *sp, lv *v, char *val)
{
  /* set type dependent on val (string, list, or number) */
  /* if " -> string, if ' or quote or list -> list, else number */
  char *str,*rest;
  str = val;
  while ((str) && (*str))
    {
      if ((*str) == '"') 
	{
	  v->type = LV_STRING;
	  v->val = (void *)get_string(str);
	  return(0);
	}
      else
	{
	  if ((isdigit(*str)) || ((*str) == '-') || ((*str) == '+') || ((*str) == '.'))
	    {
	      v->type = LV_FLOAT;
	      v->val = (void *)get_float(str);
	      return(0);
	    }
	  else
	    {
	      if ((*str) == '\'')
		{
		  v->type = LV_LIST;
		  rest = strtok(NULL,clm_close_paren);
		  if ((rest) && (*rest))
		    {
		      sprintf(sexpr_buf,"%s %s",str,rest);
		      v->val = (void *)get_list(sp,sexpr_buf);
		    }
		  else v->val = (void *)get_list(sp,str);
		  /* this and alert_lv_env below are the only places an envelope variable gets a (possibly new) value */
		  alert_envelope_editor(ss,v->name,(env *)(v->val));
		  return(0);
		}
	      else (str++);
	    }
	}
    }
  return(-1);
}

static lv *new_lv(void)
{
  if (lv_next == lv_space)
    {
      if (lv_space == 0)
	{
	  lv_space = 32;
	  lvs = (lv **)calloc(lv_space,sizeof(lv *));
	}
      else
	{
	  lv_space += 32;
	  lvs = (lv **)realloc(lvs,lv_space * sizeof(lv *));
	}
    }
  lvs[lv_next] = (lv *)calloc(1,sizeof(lv));
  lv_next++;
  return(lvs[lv_next-1]);
}

static void defvar (snd_state *ss, snd_info *sp, char *name, char *val)
{
  lv *v;
  v = new_lv();
  v->name = copy_string(name);
  parse_lval(ss,sp,v,val);
}

void def_float(char *name) {defvar(NULL,NULL,name,"0.0");}

static lv *find_symbol(char *name)
{
  int i;
  if (lvs)
    {
      for (i=0;i<lv_next;i++)
	{
	  if (strcmp(name,lvs[i]->name) == 0) return(lvs[i]);
	}
    }
  return(NULL);
}

static float symbol_fvalue (lv *v) {return(*((float *)(v->val)));}
static char *symbol_svalue (lv *v) {return((char *)(v->val));}
static env *symbol_lvalue (lv *v) {return((env *)(v->val));}

env *find_lv_env(char *str)
{
  lv *v;
  v = find_symbol(str);
  if ((v) && (v->type == LV_LIST))
    return((env *)(v->val));
  else return(NULL);
}

void alert_lv_env(char *name, env *val)
{
  lv *v;
  v = find_symbol(name);
  if (!v)
    {
      v = new_lv();
      v->type = LV_LIST;
      v->name = copy_string(name);
    }
  v->val = (void *)val;
}
    
/* next two are for snd-parser */
float *find_lv_float(char *str)
{
  lv *v;
  v = find_symbol(str);
  if ((v) && (v->type == LV_FLOAT))
    return((float *)(v->val));
  else return(NULL);
}

float set_lv_float(char *str, float val)
{
  lv *v;
  float *vf;
  v = find_symbol(str);
  if ((v) && (v->type == LV_FLOAT))
    {
      vf = (float *)calloc(1,sizeof(float));
      (*vf) = val;
      v->val = vf;
    }
  return(val);
}
    
void save_parser_state (snd_state *ss, int fd)
{
  char *pbuf;
  int i;
  lv *v;
  if (lvs)
    {
      pbuf = (char *)calloc(256,sizeof(char));
      for (i=0;i<lv_next;i++)
	{
	  v = lvs[i];
	  if (v)
	    {
	      switch (v->type)
		{
		case LV_FLOAT: sprintf(pbuf,"(defvar %s %.3f)\n",v->name,symbol_fvalue(v)); break;
		case LV_STRING: sprintf(pbuf,"(defvar %s \"%s\")\n",v->name,symbol_svalue(v)); break;
		case LV_LIST: sprintf(pbuf,"(defvar %s %s)\n",v->name,env_to_string(symbol_lvalue(v))); break;
		}
	      write(fd,pbuf,strlen(pbuf));
	    }
	}
      free(pbuf);
    }
}


void tickle_clm(snd_state *ss)
{
  /* try to unwedge the Snd/CLM interface */
  snd_ivalue(ss,0);
}

static char **prev_shortnames,**prev_longnames;
static int prev_names = 0;

static void clm_preload_fb(snd_state *ss, char **names, int len)
{
  int i,j;
  for (i=0;i<len;i++)
    {
      if (i == prev_names)
	{
	  if (i == 0)
	    {
	      prev_names = len;
	      prev_shortnames = (char **)calloc(prev_names,sizeof(char *));
	      prev_longnames = (char **)calloc(prev_names,sizeof(char *));
	    }
	  else
	    {
	      prev_names += 16;
	      prev_shortnames = (char **)realloc(prev_shortnames,prev_names*sizeof(char *));
	      prev_longnames = (char **)realloc(prev_longnames,prev_names*sizeof(char *));
	      for (j=i;j<prev_names;j++) {prev_shortnames[j] = NULL; prev_longnames[j] = NULL;}
	    }
	}
      /* name may be full or short -- if full we'll assume it starts with "/" */
      if (prev_longnames[i])
	{
	  free(prev_longnames[i]);
	  prev_longnames[i] = NULL;
	  prev_shortnames[i] = NULL;
	}
      prev_longnames[i] = copy_string(complete_filename(names[i]));
      prev_shortnames[i] = filename_without_home_directory(prev_longnames[i]); /* a pointer into longnames[i] */
    }
  add_files_to_prevlist(ss,prev_shortnames,prev_longnames,len);
}

static float fop (int op, float x, float incf) {if (op == 0) return(incf); else if (op == 1) return(x+incf); else return(x-incf);}
static int iop (int op, int x, int incf) {if (op == 0) return(incf); else if (op == 1) return(x+incf); else return(x-incf);}

static int clm_setf(int op, char *tok, char *str, snd_state *ss, snd_info *sp, chan_info *cp)
{
  int val,chan;
  lv *v;
  if ((v = find_symbol(tok)))
    {
      parse_lval(ss,sp,v,str);
      return(1);
    }
  switch (*tok)
    {
    case 'a': 
      if (strcmp(tok,"amp") == 0) 
	{
	  if (sp) snd_amp_changed(sp,set_raw_value(snd_widget(sp,W_snd_amp),snd_amp_to_int(fop(op,sp->amp,fvalue_snd(str))))); 
	  return(1);
	}
      if (strcmp(tok,"auto-open") == 0) {ss->auto_open = iop(op,ss->auto_open,ivalue_snd(str)); return(1);}
      if (strcmp(tok,"auto-resize") == 0) {ss->auto_resize = iop(op,ss->auto_resize,ivalue_snd(str)); reflect_resize(ss); return(1);}
      break;
    case 'c':
      if (strcmp(tok,"clm-selectable") == 0) {ss->clm_selectable = iop(op,ss->clm_selectable,ivalue_snd(str)); return(1);}
      if (strcmp(tok,"color-cutoff") == 0) {ss->color_cutoff = fop(op,ss->color_cutoff,fvalue_snd(str)); reflect_spectro(ss); return(1);}
      if (strcmp(tok,"color-inverted") == 0) {ss->color_inverted = iop(op,ss->color_inverted,ivalue_snd(str)); reflect_spectro(ss); return(1);}
      if (strcmp(tok,"color-scale") == 0) {ss->color_scale = fop(op,ss->color_scale,fvalue_snd(str)); reflect_spectro(ss); return(1);}
      if (strcmp(tok,"combine-channels") == 0) {set_global_combine(ss,ivalue_snd(str)); return(1);}
      if (strcmp(tok,"contrast") == 0) 
	{
	  if (sp) snd_contrast_changed(sp,set_raw_value(snd_widget(sp,W_snd_contrast),snd_contrast_to_int(fop(op,sp->contrast,fvalue_snd(str)))));
	  return(1);
	}
      if (strcmp(tok,"contrast-amp") == 0) {if (sp) sp->contrast_amp = fop(op,sp->contrast_amp,fvalue_snd(str)); return(1);}
      if (strcmp(tok,"contrast-button") == 0) {if (sp) toggle_contrast_button(sp,ivalue_snd(str)); return(1);}
      if (strcmp(tok,"cursor") == 0) {if (cp) handle_cursor(cp,cursor_moveto(cp,iop(op,cp->cursor,ivalue_snd(str)))); return(1);}
      break;
    case 'd':
      if (strcmp(tok,"dot-size") == 0) {set_dot_size(ss,iop(op,ss->dot_size,ivalue_snd(str))); return(1);}
      break;
    case 'e':
      if (strcmp(tok,"env-clipping") == 0) {set_env_clipping(iop(op,get_env_clipping(),ivalue_snd(str))); return(1);}
      if (strcmp(tok,"env-graphing") == 0) {set_env_graphing(ss,iop(op,ss->env_graphing,ivalue_snd(str))); return(1);}
      if (strcmp(tok,"env-style") == 0) {set_env_style(ss,iop(op,get_env_style(),ivalue_snd(str))); return(1);}
      if (strcmp(tok,"expand") == 0) 
	{
	  if (sp) snd_expand_changed(sp,set_raw_value(snd_widget(sp,W_snd_expand),snd_expand_to_int(fop(op,sp->expand,fvalue_snd(str)))));
	  return(1);
	}
      if (strcmp(tok,"expand-button") == 0) {if (sp) toggle_expand_button(sp,ivalue_snd(str)); return(1);}
      if (strcmp(tok,"exphop") == 0) {if (sp) sp->exp_hop = fop(op,sp->exp_hop,fvalue_snd(str)); return(1);}
      if (strcmp(tok,"explen") == 0) {if (sp) sp->exp_seglen = fop(op,sp->exp_seglen,fvalue_snd(str)); return(1);}
      if (strcmp(tok,"exprmp") == 0) {if (sp) sp->exp_ramp = fop(op,sp->exp_ramp,fvalue_snd(str)); return(1);}
      break;
    case 'f':
      if (strcmp(tok,"fft-beta") == 0) {fft_set_current_window_beta(fvalue_snd(str),ss->global_fft_window); return(1);}
      if (strcmp(tok,"fft-size") == 0) {set_global_fft_size(ss,iop(op,ss->global_fft_size,ivalue_snd(str))); return(1);} /* snd-snd.c */
      if (strcmp(tok,"filter") == 0) {if (sp) clm_set_filter(sp,str,strtok(NULL,clm_close_paren)); return(1);}
      if (strcmp(tok,"fft-window") == 0) {ss->global_fft_window = ivalue_snd(str); return(1);}
      if (strcmp(tok,"filter-button") == 0) {if (sp) toggle_filter_button(sp,ivalue_snd(str)); return(1);}
      if (strcmp(tok,"filter-env-order") == 0) {ss->filter_env_order = iop(op,ss->filter_env_order,ivalue_snd(str)); return(1);}
      if (strcmp(tok,"filter-order") == 0) {if (sp) sp->filter_order = iop(op,sp->filter_order,ivalue_snd(str)); return(1);}
      if (strcmp(tok,"follow-play") == 0) {if (sp) sp->cursor_follows_play = iop(op,sp->cursor_follows_play,ivalue_snd(str)); return(1);}
      break;
    case 'g':
      if (strcmp(tok,"global-explen") == 0) {ss->gexp_seglen = fop(op,ss->gexp_seglen,fvalue_snd(str)); return(1);}
      if (strcmp(tok,"global-exphop") == 0) {ss->gexp_hop = fop(op,ss->gexp_hop,fvalue_snd(str)); return(1);}
      if (strcmp(tok,"global-exprmp") == 0) {ss->gexp_ramp = fop(op,ss->gexp_ramp,fvalue_snd(str)); return(1);}
      if (strcmp(tok,"global-revfb") == 0) {ss->grevfb = fop(op,ss->grevfb,fvalue_snd(str)); return(1);}
      if (strcmp(tok,"global-revlp") == 0) {ss->grevlp = fop(op,ss->grevlp,fvalue_snd(str)); return(1);}
      if (strcmp(tok,"global-revdecay") == 0) {ss->reverb_decay = fop(op,ss->reverb_decay,fvalue_snd(str)); return(1);}
      break;
    case 'h':
      if (strcmp(tok,"header-type") == 0) {set_new_header_type(ivalue_snd(str)); return(1);}
      break;
    case 'i':
      if (strcmp(tok,"initial-x0") == 0) {ss->initx0 = fop(op,ss->initx0,fvalue_snd(str)); return(1);}
      if (strcmp(tok,"initial-x1") == 0) {ss->initx1 = fop(op,ss->initx1,fvalue_snd(str)); return(1);}
      if (strcmp(tok,"initial-y0") == 0) {ss->inity0 = fop(op,ss->inity0,fvalue_snd(str)); return(1);}
      if (strcmp(tok,"initial-y1") == 0) {ss->inity1 = fop(op,ss->inity1,fvalue_snd(str)); return(1);}
      break;
    case 'l':
      if (strcmp(tok,"line-size") == 0) {ss->editor_line_size = iop(op,ss->editor_line_size,ivalue_snd(str)); return(1);}
      break;
    case 'm':
      if (strcmp(tok,"mix-amp-scaler") == 0) {set_mix_amp_scaler(fop(op,get_mix_amp_scaler(),fvalue_snd(str))); return(1);}
      if (strcmp(tok,"mix-duration-brackets") == 0) 
	{
	  ss->mix_duration_brackets = iop(op,ss->mix_duration_brackets,ivalue_snd(str));
	  map_over_chans(ss,update_graph,NULL);
	  return(1);
	}
      if (strcmp(tok,"mix-speed-scaler") == 0) {set_mix_speed_scaler(fop(op,get_mix_speed_scaler(),fvalue_snd(str))); return(1);}
      if (strcmp(tok,"mix-tempo-scaler") == 0) {set_mix_tempo_scaler(fop(op,get_mix_tempo_scaler(),fvalue_snd(str))); return(1);}
      if (strcmp(tok,"movies") == 0) {ss->movies = iop(op,ss->movies,ivalue_snd(str)); return(1);}
      break;
    case 'n':
      if (strcmp(tok,"normalize-fft") == 0) {ss->normalize_fft = ivalue_snd(str); return(1);}
      break;
    case 'o':
      if (strcmp(tok,"overwrite-check") == 0) {ss->ask_before_overwrite = ivalue_snd(str); return(1);}
      break;
    case 'r':
      if (strcmp(tok,"raw-chans") == 0) {set_raw_data_defaults(-1,ivalue_snd(str),-1,-1); return(1);}
      if (strcmp(tok,"raw-defaults") == 0) {set_raw_data_defaults(-1,-1,-1,ivalue_snd(str)); return(1);}
      if (strcmp(tok,"raw-format") == 0) {set_raw_data_defaults(-1,-1,ivalue_snd(str),-1); return(1);}
      if (strcmp(tok,"raw-srate") == 0) {set_raw_data_defaults(ivalue_snd(str),-1,-1,-1); return(1);}
      if (strcmp(tok,"read-only") == 0) {if (sp) {sp->read_only = ivalue_snd(str); snd_file_lock_icon(sp,sp->read_only);} return(1);}
      if (strcmp(tok,"remember-state") == 0) {ss->remember_state = ivalue_snd(str); return(1);}
      if (strcmp(tok,"revdecay") == 0) {ss->reverb_decay = fop(op,ss->reverb_decay,fvalue_snd(str)); return(1);}
      if (strcmp(tok,"reverb-button") == 0) {if (sp) toggle_reverb_button(sp,ivalue_snd(str)); return(1);}
      if (strcmp(tok,"revfb") == 0) {if (sp) sp->revfb = fop(op,sp->revfb,fvalue_snd(str)); return(1);}
      if (strcmp(tok,"revlen") == 0) 
	{
	  if (sp) snd_revlen_changed(sp,set_raw_value(snd_widget(sp,W_snd_revlen),snd_revlen_to_int(fop(op,sp->revlen,fvalue_snd(str)))));
	  return(1);
	}
      if (strcmp(tok,"revlp") == 0) {if (sp) sp->revlp = fop(op,sp->revlp,fvalue_snd(str)); return(1);}
      if (strcmp(tok,"revscl") == 0) 
	{
	  if (sp) snd_revscl_changed(sp,set_raw_value(snd_widget(sp,W_snd_revscl),snd_revscl_to_int(fop(op,sp->revscl,fvalue_snd(str)))));
	  return(1);
	}

      /* now the recorder state variables */
      if (strcmp(tok,"record-autoload") == 0) {reflect_record(ss,7,ivalue_snd(str)); return(1);}
      if (strcmp(tok,"record-chans") == 0) {reflect_record(ss,0,iop(op,ss->record_chans,ivalue_snd(str))); return(1);}
      if (strcmp(tok,"record-digital-line-in") == 0) {reflect_record(ss,8,ivalue_snd(str)); return(1);}
      if (strcmp(tok,"record-format") == 0) {reflect_record(ss,3,iop(op,ss->record_format,ivalue_snd(str))); return(1);}
      if (strcmp(tok,"record-line-panel") == 0) {reflect_record(ss,4,ivalue_snd(str)); return(1);}
      if (strcmp(tok,"record-mic-panel") == 0) {reflect_record(ss,5,ivalue_snd(str)); return(1);}
      if (strcmp(tok,"record-out-panel") == 0) {reflect_record(ss,6,ivalue_snd(str)); return(1);}
      if (strcmp(tok,"record-srate") == 0) {reflect_record(ss,1,iop(op,ss->record_srate,ivalue_snd(str))); return(1);}
      if (strcmp(tok,"record-type") == 0) {reflect_record(ss,2,iop(op,ss->record_type,ivalue_snd(str))); return(1);}

      /* and the 16 sliders ... 6+6+4 */
      if (strcmp(tok,"record-line-a") == 0) {reflect_record_slider(ss,fvalue_snd(str),0,op,TRUE); return(1);}      
      if (strcmp(tok,"record-line-b") == 0) {reflect_record_slider(ss,fvalue_snd(str),1,op,TRUE); return(1);}      
      if (strcmp(tok,"record-line-aa") == 0) {reflect_record_slider(ss,fvalue_snd(str),2,op,TRUE); return(1);}      
      if (strcmp(tok,"record-line-bb") == 0) {reflect_record_slider(ss,fvalue_snd(str),3,op,TRUE); return(1);}      
      if (strcmp(tok,"record-line-ab") == 0) {reflect_record_slider(ss,fvalue_snd(str),4,op,TRUE); return(1);}      
      if (strcmp(tok,"record-line-ba") == 0) {reflect_record_slider(ss,fvalue_snd(str),5,op,TRUE); return(1);}   
   
      if (strcmp(tok,"record-mic-a") == 0) {reflect_record_slider(ss,fvalue_snd(str),6+0,op,TRUE); return(1);}      
      if (strcmp(tok,"record-mic-b") == 0) {reflect_record_slider(ss,fvalue_snd(str),6+1,op,TRUE); return(1);}      
      if (strcmp(tok,"record-mic-aa") == 0) {reflect_record_slider(ss,fvalue_snd(str),6+2,op,TRUE); return(1);}      
      if (strcmp(tok,"record-mic-bb") == 0) {reflect_record_slider(ss,fvalue_snd(str),6+3,op,TRUE); return(1);}      
      if (strcmp(tok,"record-mic-ab") == 0) {reflect_record_slider(ss,fvalue_snd(str),6+4,op,TRUE); return(1);}      
      if (strcmp(tok,"record-mic-ba") == 0) {reflect_record_slider(ss,fvalue_snd(str),6+5,op,TRUE); return(1);}   
   
      if (strcmp(tok,"record-out-a") == 0) {reflect_record_slider(ss,fvalue_snd(str),12+0,op,TRUE); return(1);}      
      if (strcmp(tok,"record-out-b") == 0) {reflect_record_slider(ss,fvalue_snd(str),12+1,op,TRUE); return(1);}      
      if (strcmp(tok,"record-out-aa") == 0) {reflect_record_slider(ss,fvalue_snd(str),12+2,op,TRUE); return(1);}      
      if (strcmp(tok,"record-out-bb") == 0) {reflect_record_slider(ss,fvalue_snd(str),12+3,op,TRUE); return(1);}      

      break;
    case 's':
      if (strcmp(tok,"sono-color") == 0) {ss->sonogram_color = iop(op,ss->sonogram_color,ivalue_snd(str)); reflect_spectro(ss); return(1);}
      if (strcmp(tok,"sono-max") == 0) {set_sonogram_cutoff(ss,fop(op,ss->sonogram_cutoff,fvalue_snd(str))); reflect_spectro(ss); return(1);}
      if (strcmp(tok,"spectro-xangle") == 0) {ss->spectro_xangle = fop(op,ss->spectro_xangle,fvalue_snd(str)); reflect_spectro(ss); return(1);}
      if (strcmp(tok,"spectro-yangle") == 0) {ss->spectro_yangle = fop(op,ss->spectro_yangle,fvalue_snd(str)); reflect_spectro(ss); return(1);}
      if (strcmp(tok,"spectro-zangle") == 0) {ss->spectro_zangle = fop(op,ss->spectro_zangle,fvalue_snd(str)); reflect_spectro(ss); return(1);}
      if (strcmp(tok,"spectro-hop") == 0) 
	{
	  ss->spectro_hop = iop(op,ss->spectro_hop,ivalue_snd(str)); 
	  if (ss->spectro_hop <= 0) ss->spectro_hop = 1;
	  reflect_spectro(ss); 
	  return(1);
	}
      if (strcmp(tok,"spectro-color") == 0) {ss->spectrogram_color = iop(op,ss->spectrogram_color,ivalue_snd(str)); reflect_spectro(ss); return(1);}
      if (strcmp(tok,"spectro-xscl") == 0) {ss->spectro_xscl = fop(op,ss->spectro_xscl,fvalue_snd(str)); reflect_spectro(ss); return(1);}
      if (strcmp(tok,"spectro-yscl") == 0) {ss->spectro_yscl = fop(op,ss->spectro_yscl,fvalue_snd(str)); reflect_spectro(ss); return(1);}
      if (strcmp(tok,"spectro-zscl") == 0) {ss->spectro_zscl = fop(op,ss->spectro_zscl,fvalue_snd(str)); reflect_spectro(ss); return(1);}
      if (strcmp(tok,"speed") == 0) 
	{
	  if (sp) snd_srate_changed(sp,set_raw_value(snd_widget(sp,W_snd_srate),snd_srate_to_int(fop(op,sp->srate,fvalue_snd(str))))); 
	  return(1);
	}
      if (strcmp(tok,"speed-style") == 0) {activate_speed_in_menu(ss,val = iop(op,ss->speed_style,ivalue_snd(str))); return(1);}
      if (strcmp(tok,"speed-tones") == 0) {ss->speed_tones = iop(op,ss->speed_tones,ivalue_snd(str)); return(1);}
      if (strcmp(tok,"src-width") == 0) {ss->sinc_width = iop(op,ss->sinc_width,ivalue_snd(str)); return(1);}
      break;
    case 'w':
      if (strcmp(tok,"wavelet-choice") == 0) {ss->wavelet_choice = iop(op,ss->wavelet_choice,ivalue_snd(str)); return(1);}
      if (strcmp(tok,"wavo-hop") == 0) 
	{
	  ss->wavo_hop = iop(op,ss->wavo_hop,ivalue_snd(str)); 
	  if (ss->wavo_hop <= 0) ss->wavo_hop = 1;
	  return(1);
	}
      if (strcmp(tok,"wavo-state") == 0) {ss->wavo = ivalue_snd(str); return(1);}
      if (strcmp(tok,"wavo-trace") == 0) {ss->wavo_trace = iop(op,ss->wavo_trace,ivalue_snd(str)); return(1);}
      if (strcmp(tok,"window-height") == 0) {set_snd_window_height(ss,iop(op,snd_window_height(ss),ivalue_snd(str))); return(1);}
      if (strcmp(tok,"window-width") == 0) {set_snd_window_width(ss,iop(op,snd_window_width(ss),ivalue_snd(str))); return(1);} /*snd-xmain.c */
      break;
    case 'x':
      if (strcmp(tok,"x") == 0) {if (cp) handle_cursor(cp,cursor_moveto(cp,iop(op,cp->cursor,ivalue_snd(str)))); return(1);}
      if (strcmp(tok,"x-axis-style") == 0) 
	{
	  val = iop(op,ss->x_axis_style,ivalue_snd(str)); 
	  reflect_x_axis_unit_change_in_menu(ss->x_axis_style,val);
	  ss->x_axis_style = val;
	  return(1);
	}
      if (strcmp(tok,"xmax") == 0) {ss->xmax = fop(op,ss->xmax,fvalue_snd(str)); return(1);}
      if (strcmp(tok,"xmin") == 0) {ss->xmin = fop(op,ss->xmin,fvalue_snd(str)); return(1);}
      break;
    case 'y':
      if (strcmp(tok,"y-max") == 0) {if (cp) set_y_limits(cp,cp->axis,fop(op,(cp->axis)->y1,fvalue_snd(str))); return(1);}
      if (strcmp(tok,"ymax") == 0) {ss->ymax = fop(op,ss->ymax,fvalue_snd(str)); return(1);}
      if (strcmp(tok,"ymin") == 0) {ss->ymin = fop(op,ss->ymin,fvalue_snd(str)); return(1);}
      break;
    case 'z':
      if (strcmp(tok,"zero-pad") == 0) {ss->zero_pad = iop(op,ss->zero_pad,ivalue_snd(str)); return(1);}
      break;
    case '(':
      /* (setf (mix-speed n) f) or (setf (group-amp n row) f) etc */
      /* these are harder than the corresponding get cases because we actually have to perform the associated action */
      tok++;
      sscanf(str,"%d)",&val);   /* mix or group number */     
      if (strcmp(tok,"mix-position") == 0) 
	{set_mix_idata(ss,val,iop(op,get_mix_idata(ss,val,MIX_POSITION),ivalue_snd(strtok(NULL,clm_white_space))),MIX_POSITION); return(1);}
      if (strcmp(tok,"mix-length") == 0) 
	{set_mix_idata(ss,val,iop(op,get_mix_idata(ss,val,MIX_LENGTH),ivalue_snd(strtok(NULL,clm_white_space))),MIX_LENGTH); return(1);}
      if (strcmp(tok,"mix-anchor") == 0) 
	{set_mix_idata(ss,val,iop(op,get_mix_idata(ss,val,MIX_ANCHOR),ivalue_snd(strtok(NULL,clm_white_space))),MIX_ANCHOR); return(1);}
      if (strcmp(tok,"mix-state") == 0) 
	{set_mix_idata(ss,val,iop(op,get_mix_idata(ss,val,MIX_STATE),ivalue_snd(strtok(NULL,clm_white_space))),MIX_STATE); return(1);}
      if (strcmp(tok,"mix-groups") == 0) 
	{set_mix_idata(ss,val,iop(op,get_mix_idata(ss,val,MIX_GROUPS),ivalue_snd(strtok(NULL,clm_white_space))),MIX_GROUPS); return(1);}
      if (strcmp(tok,"mix-speed") == 0) 
	{set_mix_fdata(ss,val,0,fop(op,get_mix_fdata(ss,val,0,MIX_SPEED),fvalue_snd(strtok(NULL,clm_white_space))),MIX_SPEED); return(1);}
      if (strcmp(tok,"mix-amp") == 0) 
	{
	  chan = ivalue_snd(strtok(NULL,clm_white_space));
	  set_mix_fdata(ss,val,chan,fop(op,get_mix_fdata(ss,val,chan,MIX_AMP),fvalue_snd(strtok(NULL,clm_white_space))),MIX_AMP); 
	  return(1);
	}
      if (strcmp(tok,"group-amp") == 0) 
	{
	  chan = ivalue_snd(strtok(NULL,clm_white_space));
	  mx_set_group_amp(ss,val,chan,fop(op,mx_get_group_amp(ss,val,chan),fvalue_snd(strtok(NULL,clm_white_space))));
	  return(1);
	}
      if (strcmp(tok,"group-beg") == 0) 
	{ 
	  mx_set_group_beg(ss,val,fop(op,mx_get_group_beg(ss,val),fvalue_snd(strtok(NULL,clm_white_space))));
	  return(1);
	}
      if (strcmp(tok,"group-end") == 0) 
	{ 
	  mx_set_group_end(ss,val,fop(op,mx_get_group_end(ss,val),fvalue_snd(strtok(NULL,clm_white_space))));
	  return(1);
	}
      if (strcmp(tok,"group-speed") == 0) 
	{ 
	  mx_set_group_speed(ss,val,fop(op,mx_get_group_speed(ss,val),fvalue_snd(strtok(NULL,clm_white_space))));
	  return(1);
	}
      if (strcmp(tok,"group-tempo") == 0) 
	{ 
	  mx_set_group_tempo(ss,val,fop(op,mx_get_group_tempo(ss,val),fvalue_snd(strtok(NULL,clm_white_space))));
	  return(1);
	}
      break;
    }
  /* if we get here, we don't recognize the thing being setf'd, so fall back on CLM if possible */
  return(0);
}

static int char_code(char *str)
{
  /* can be quoted => remove quotes */
  char c;
  if (str)
    {
      if (str[0] == '\"') c=str[1]; else c=str[0];
      return((int)c);
    }
  return(0);
}

static char *quoted_name(char *str)
{
  /* return copy of unquoted portion of str */
  int len,beg,end,i,j;
  char *newstr;
  if (str)
    {
      len = strlen(str);
      if ((str[0] == '\"') || (str[0] == '\'')) beg = 1; else beg = 0;
      if (str[len-1] == '\"') end = len-2; else end=len-1;
      newstr = (char *)calloc(len+1,sizeof(char));
      for (i=beg,j=0;i<=end;i++,j++) newstr[j] = str[i];
      newstr[j] = '\0';
      return(newstr);
    }
  return(NULL);
}

static char *doit_buf = NULL;
static int doit_buf_size = 0;
static char* strbuf[4]; /* needed by Linux to guarantee argument evaluation order is left-to-right */
#define MAX_SCALERS 64
static float amp_scalers[MAX_SCALERS];

void clm_doit(snd_state *ss, char *buf, int start, int end)
{
  int i,j,redisplay,beg,num,err,temp,len;
  int *data;
  char *tok,*str,*csp,*ofile;
  char **pn;
  snd_info *sp;
  chan_info *cp;
  lv *v;
  if ((end-start) > doit_buf_size)
    {
      doit_buf_size = end-start+512;
      if (!doit_buf) doit_buf = (char *)calloc(doit_buf_size,sizeof(char));
      else doit_buf = (char *)realloc(doit_buf,doit_buf_size * sizeof(char));
    }
  cp = NULL;
  sp = any_selected_sound(ss);
  if (sp) cp = any_selected_channel(sp);
  for (csp=(buf+start+1),str=doit_buf;csp<(buf+end);csp++,str++) (*str) = (*csp);
  (*str) = '\0';
  csp = doit_buf;
  tok = strtok(csp,clm_white_space);

  if ((v = find_symbol(tok)))
    {
      if (v->type == LV_FLOAT) {snd_fvalue(ss,symbol_fvalue(v)); return;}
      if (v->type == LV_STRING) {snd_svalue(ss,symbol_svalue(v)); return;}
      if (v->type == LV_LIST) {snd_lvalue(ss,symbol_lvalue(v)); return;}
    }

  switch (*tok)
    {
    case 'a':
      if (strcmp(tok,"all-files") == 0) {toggle_just_sounds(0); return;}
      if (strcmp(tok,"amp") == 0) {snd_fvalue(ss,(sp) ? sp->amp : 0.0); return;}
      if (strcmp(tok,"amp-env") == 0) 
	{
	  /* get env as string (amp-env beg dur scaler data as list of floats) */
	  if (cp)
	    {
	      strbuf[0] = strtok(NULL,clm_white_space);
	      strbuf[1] = strtok(NULL,clm_white_space);
	      strbuf[2] = strtok(NULL,clm_white_space);
	      strbuf[3] = strtok(NULL,clm_close_paren);
	      apply_env(cp,
			scan_envelope_and_report_error(sp,strbuf[3],&err),
			ivalue_snd(strbuf[0]),   /* beg in samples */
			ivalue_snd(strbuf[1]),   /* dur */
			fvalue_snd(strbuf[2]),   /* scaler */
			0,FALSE);                /* regexpr, from_enved */
	    }
	  return;
	}
      if (strcmp(tok,"amp-envelope") == 0) 
	{
	  if (cp) apply_env(cp,scan_envelope_and_report_error(sp,strtok(NULL,clm_close_paren),&err),0,current_ed_samples(cp),1.0,0,FALSE);
	  return;
	}
      if (strcmp(tok,"append-to-minibuffer") == 0) 
	{
	  if (sp) append_to_minibuffer(sp,strtok(NULL,clm_white_space));
	  return;
	}
      if (strcmp(tok,"auto-open") == 0) {snd_ivalue(ss,ss->auto_open); return;}
      if (strcmp(tok,"auto-resize") == 0) {snd_ivalue(ss,ss->auto_resize); return;}
      if ((strcmp(tok,"axes") == 0) && (cp))
	{
	  strbuf[0] = strtok(NULL,clm_white_space);
	  strbuf[1] = strtok(NULL,clm_white_space);
	  strbuf[2] = strtok(NULL,clm_white_space);
	  strbuf[3] = strtok(NULL,clm_white_space);
	  set_axes(cp,fvalue_snd(strbuf[0]),fvalue_snd(strbuf[1]),fvalue_snd(strbuf[2]),fvalue_snd(strbuf[3]));
	  return;
	}
      break;
    case 'c':
      if ((strcmp(tok,"change") == 0) && (cp))
	{
	  beg = ivalue_snd(strtok(NULL,clm_white_space));
	  num = ivalue_snd(strtok(NULL,clm_white_space));
	  data = clm_snd_data(ss,&i);
	  change_samples(beg,num,data,cp,LOCK_MIXES);
	  check_for_first_edit(cp);
	  update_graph(cp,NULL);
	  free(data);
	  return;
	}
      if (strcmp(tok,"chan-marks") == 0)
	{
	  while ((str=strtok(NULL,clm_white_space)))
	    {
	      tok=strtok(NULL,clm_white_space);
	      sscanf(tok,"%d",&num);
	      add_mark(num,(strcmp(str,"NIL") == 0) ? NULL : str,cp);
	    }
	  return;
	}
#if HAVE_OSS
      if (strcmp(tok,"clear-inputs") == 0) {clear_soundcard_inputs(); return;}
#endif
      if (strcmp(tok,"clm-dialog") == 0) 
	{
	  /* msg is in double quotes */
	  strbuf[0] = strtok(NULL,clm_double_quote);
	  strbuf[1] = strtok(NULL,clm_white_space);
	  strbuf[2] = strtok(NULL,clm_white_space);
	  clm_dialog(ss,strbuf[0],strbuf[1],strbuf[2]);
	  return;
	}
      if (strcmp(tok,"clm-selectable") == 0) {snd_ivalue(ss,ss->clm_selectable); return;}
      if (strcmp(tok,"close") == 0) {clm_snd_close(ss,complete_filename(strtok(NULL,clm_white_space))); return;}
      if (strcmp(tok,"color-cutoff") == 0) {snd_fvalue(ss,ss->color_cutoff); return;}
      if (strcmp(tok,"color-dialog") == 0) 
	{
	  strbuf[0] = strtok(NULL,clm_white_space);
	  strbuf[1] = strtok(NULL,clm_white_space);
	  start_color_dialog(ss,ivalue_snd(strbuf[0]),ivalue_snd(strbuf[1])); 
	  return;
	}
      if (strcmp(tok,"color-inverted") == 0) {snd_ivalue(ss,ss->color_inverted); return;}
      if (strcmp(tok,"color-scale") == 0) {snd_fvalue(ss,ss->color_scale); return;}
      if (strcmp(tok,"combine-channels") == 0) {snd_ivalue(ss,ss->global_combining); return;}
      if (strcmp(tok,"contrast") == 0) {snd_fvalue(ss,(sp) ? sp->contrast : 0.0); return;}
      if (strcmp(tok,"contrast-amp") == 0) {snd_fvalue(ss,(sp) ? sp->contrast_amp : 0.0); return;}
      if (strcmp(tok,"contrast-button") == 0) {snd_ivalue(ss,(sp) ? sp->contrasting : 0); return;}
      if (strcmp(tok,"copy") == 0) {clm_copy(ss); return;}
      if (strcmp(tok,"cursor") == 0) {snd_ivalue(ss,(cp) ? cp->cursor : 0); return;}
      if ((strcmp(tok,"command") == 0) && (cp))
	{
	  strbuf[0] = strtok(NULL,clm_white_space);
	  strbuf[1] = strtok(NULL,clm_white_space);
	  redisplay = keyboard_command(cp,ivalue_snd(strbuf[0]),ivalue_snd(strbuf[1]));
	  handle_cursor(cp,redisplay);
	  return;
	}
      if (strcmp(tok,"cut") == 0) {clm_cut(ss); return;}
      break;
    case 'd':
      if (strcmp(tok,"data") == 0)
	{
	  /* get a block of data from the current channel */
	  if (cp)
	    {
	      beg = ivalue_snd(strtok(NULL,clm_white_space));
	      num = ivalue_snd(strtok(NULL,clm_white_space));
	      data = load_samples(beg,num,cp);
	      snd_clm_data(ss,num,data);
	      free(data);
	    }
	  return;
	}
      if (strcmp(tok,"decf") == 0) 
	{
	  strbuf[0] = strtok(NULL,clm_white_space);
	  strbuf[1] = strtok(NULL,clm_white_space);
	  if (clm_setf(2,strbuf[0],strbuf[1],ss,sp,cp)) return; else break;
	}
      if (strcmp(tok,"default-output-type") == 0) {ss->default_output_type = ivalue_snd(strtok(NULL,clm_white_space)); return;}
      if (strcmp(tok,"defmacro") == 0) {load_macro(strtok(NULL,clm_white_space),buf,start,end); return;}
      if (strcmp(tok,"defvar") == 0) 
	{
	  strbuf[0] = strtok(NULL,clm_white_space);
	  strbuf[1] = strtok(NULL,clm_close_paren);
	  defvar(ss,sp,strbuf[0],strbuf[1]);
	  return;
	}
      if ((strcmp(tok,"delete") == 0) && (cp))
	{
	  beg = ivalue_snd(strtok(NULL,clm_white_space));
	  num = ivalue_snd(strtok(NULL,clm_white_space));
	  delete_samples(beg,num,cp);
	  check_for_first_edit(cp);
	  update_graph(cp,NULL);
	  return;
	}
      if (strcmp(tok,"delete-mark") == 0) {if (cp) delete_mark(cp->cursor,cp); return;}
      if (strcmp(tok,"disconnect") == 0) {clm_disconnect(ss); return;}
      if (strcmp(tok,"dont-fit-data") == 0) {ss->fit_data = 0; return;}
      if (strcmp(tok,"dont-override-data-location") == 0) {c_snd_header_dont_override_data_location(); return;}
      if (strcmp(tok,"dont-override-data-size") == 0) {c_snd_header_dont_override_data_size(); return;}
      if (strcmp(tok,"dont-override-sound-format") == 0) {c_snd_header_dont_override_sound_format(); return;}
      if (strcmp(tok,"dots") == 0) {view_dots(ss); return;}
      if (strcmp(tok,"dot-size") == 0) {snd_ivalue(ss,ss->dot_size); return;}
      break;
    case 'e':
      if (strcmp(tok,"edit-sound") == 0)
	{
	  num=0;
	  tok=strtok(NULL,clm_white_space); /* newname -- sequester this so repeated clm with-sounds don't walk on previous edits */
	  str=strtok(NULL,clm_white_space); /* optional oldname */
	  if ((str) && (*str)) sp = find_sound(ss,str);  /* otherwise use sp already set up via any_selected_sound */
	  if (sp)
	    {
	      ofile = tempnam(ss->temp_dir,"snd_"); num=1;
	      if ((err = (rename(tok,ofile))))
		{
		  if (errno == EXDEV) 
		    {
		      err = copy_file(tok,ofile,sp); 
		      if (!err) err = remove(tok);
		    }
		  else {free(ofile); num=0; ofile = tok;}
		}
	      for (i=0;i<sp->nchans;i++)
		{
		  /* samples as -1 means use current tempfile read_header result in this case */
		  file_override_samples(-1,ofile,sp->chans[i],i,(i == 0) ? DELETE_ME : DONT_DELETE_ME,LOCK_MIXES);
		}
	      if (num) free(ofile);
	    }
	  return;
	}
      if (strcmp(tok,"env-clipping") == 0) {snd_ivalue(ss,get_env_clipping()); return;}
      if (strcmp(tok,"env-graphing") == 0) {snd_ivalue(ss,ss->env_graphing); return;}
      if (strcmp(tok,"env-style") == 0) {snd_ivalue(ss,get_env_style()); return;}
      if (strcmp(tok,"eval") == 0) {clm_eval(ss,cp,strtok(NULL,clm_null)); return;}
      if (strcmp(tok,"exit") == 0) {snd_exit_cleanly(ss); exit(0);}
      if (strcmp(tok,"expand") == 0) {snd_fvalue(ss,(sp) ? sp->expand : 0.0); return;}
      if (strcmp(tok,"expand-button") == 0) {snd_ivalue(ss,(sp) ? sp->expanding : 0); return;}
      if (strcmp(tok,"exphop") == 0) {snd_fvalue(ss,(sp) ? sp->exp_hop : 0.0);return;}
      if (strcmp(tok,"explen") == 0) {snd_fvalue(ss,(sp) ? sp->exp_seglen : 0.0);return;}
      if (strcmp(tok,"exprmp") == 0) {snd_fvalue(ss,(sp) ? sp->exp_ramp : 0.0);return;}
      break;
    case 'f':
      if (strcmp(tok,"fft-beta") == 0) {snd_fvalue(ss,fft_window_beta(ss->global_fft_window)); return;}
      if (strcmp(tok,"fft-dB") == 0) {fft_dB(ss); return;}
      if (strcmp(tok,"fft-linear") == 0) {fft_linear(ss); return;}
      if (strcmp(tok,"fft-linear-freq") == 0) {fft_linear_freq(ss); return;}
      if (strcmp(tok,"fft-log-freq") == 0) {fft_log_freq(ss); return;}
      if (strcmp(tok,"fft-size") == 0) {snd_ivalue(ss,ss->global_fft_size); return;}
      if (strcmp(tok,"fft-style") == 0) {fft_style(ss,ivalue_snd(strtok(NULL,clm_white_space))); return;}
      if (strcmp(tok,"fft-window") == 0) {snd_ivalue(ss,ss->global_fft_window); return;}
      if (strcmp(tok,"file-dialog") == 0) 
	{
	  strbuf[0] = strtok(NULL,clm_white_space);
	  strbuf[1] = strtok(NULL,clm_white_space);
	  start_file_dialog(ss,ivalue_snd(strbuf[0]),ivalue_snd(strbuf[1]));
	  return;
	}
      if (strcmp(tok,"filter") == 0) {clm_get_filter(sp); return;}
      if (strcmp(tok,"filter-button") == 0) {snd_ivalue(ss,(sp) ? sp->filtering : 0); return;}
      if (strcmp(tok,"filter-env") == 0) 
	{
	  if (cp)
	    {
	      strbuf[0] = strtok(NULL,clm_white_space);
	      strbuf[1] = strtok(NULL,clm_close_paren);
	      apply_filter(cp,ivalue_snd(strbuf[0]),scan_envelope_and_report_error(sp,strbuf[1],&err),FALSE);
	    }
	  return;
	}
      if (strcmp(tok,"filter-env-order") == 0) {snd_ivalue(ss,ss->filter_env_order); return;}
      if (strcmp(tok,"filter-order") == 0) {snd_ivalue(ss,(sp) ? sp->filter_order : 0); return;}
      if (strcmp(tok,"find") == 0) {if (cp) clm_find(cp,sp,strtok(NULL,clm_white_space)); return;}
      if (strcmp(tok,"fit-data") == 0) {ss->fit_data = 1; return;}
      if (strcmp(tok,"float") == 0) {sop_add_id(strtok(NULL,clm_white_space)); return;}
      if (strcmp(tok,"focus-style") == 0) {ss->zoom_focus_anchor = ivalue_snd(strtok(NULL,clm_white_space)); return;}
      if (strcmp(tok,"follow-play") == 0) {snd_ivalue(ss,(sp) ? sp->cursor_follows_play : 0); return;}
      if (strcmp(tok,"function") == 0) 
	{
	  if ((sp) && (cp)) 
	    {
	      strbuf[0] = strtok(NULL,clm_white_space);
	      strbuf[1] = strtok(NULL,clm_white_space);
	      clm_call_ufun(cp,sp,strbuf[0],ivalue_snd(strbuf[1]));
	    }
	  return;
	}
      break;
    case 'g':
      if (strcmp(tok,"global-explen") == 0) {snd_fvalue(ss,ss->gexp_seglen); return;}
      if (strcmp(tok,"global-exphop") == 0) {snd_fvalue(ss,ss->gexp_hop); return;}
      if (strcmp(tok,"global-exprmp") == 0) {snd_fvalue(ss,ss->gexp_ramp); return;}
      if (strcmp(tok,"global-revfb") == 0) {snd_fvalue(ss,ss->grevfb); return;}
      if (strcmp(tok,"global-revlp") == 0) {snd_fvalue(ss,ss->grevlp); return;}
      if (strcmp(tok,"global-revdecay") == 0) {snd_fvalue(ss,ss->reverb_decay); return;}

      if (strcmp(tok,"global-set-key") == 0) 
	{
	  num = char_code(strtok(NULL,clm_white_space));
	  set_keymap_entry(num,quoted_name(strtok(NULL,clm_white_space))); 
	  return;
	}
      if (strcmp(tok,"global-unset-key") == 0) {set_keymap_entry(char_code(strtok(NULL,clm_white_space)),NULL); return;}

      if (strcmp(tok,"goto-mark") == 0) {if (cp) clm_goto_mark(cp,strtok(NULL,clm_white_space)); return;}
      if ((strcmp(tok,"goto-sound") == 0) || (strcmp(tok,"goto-channel") == 0))
	{
	  sp=find_sound(ss,str=strtok(NULL,clm_white_space));
	  if (sp) 
	    {
	      if (strcmp(tok,"goto-channel") == 0) 
		select_channel(sp,ivalue_snd(strtok(NULL,clm_white_space)));
	      else select_channel(sp,0);
	    }
	  return;
	}

      if (strcmp(tok,"group-amp") == 0) 
	{
	  num = ivalue_snd(strtok(NULL,clm_white_space));
	  temp = ivalue_snd(strtok(NULL,clm_white_space));
	  snd_fvalue(ss,mx_get_group_amp(ss,num,temp)); 
	  return;
	}
      if (strcmp(tok,"group-tempo") == 0) {snd_fvalue(ss,mx_get_group_tempo(ss,ivalue_snd(strtok(NULL,clm_white_space)))); return;}
      if (strcmp(tok,"group-speed") == 0) {snd_fvalue(ss,mx_get_group_speed(ss,ivalue_snd(strtok(NULL,clm_white_space)))); return;}
      if (strcmp(tok,"group-beg") == 0) {snd_fvalue(ss,mx_get_group_beg(ss,ivalue_snd(strtok(NULL,clm_white_space)))); return;}
      if (strcmp(tok,"group-end") == 0) {snd_fvalue(ss,mx_get_group_end(ss,ivalue_snd(strtok(NULL,clm_white_space)))); return;}

      break;
    case 'h':
      if (strcmp(tok,"header-type") == 0) {snd_ivalue(ss,get_new_header_type()); return;}
      if (strcmp(tok,"help") == 0) 
	{
	  if (sp) 
	    ssnd_help(ss,sp->shortname,strtok(NULL,clm_null),NULL); 
	  else ssnd_help(ss,"",strtok(NULL,clm_null),NULL); 
	  return;
	}
      if (strcmp(tok,"hide-consoles") == 0) {hide_consoles(ss); return;}
      if (strcmp(tok,"hide-controls") == 0) {hide_controls(ss); return;}
      if (strcmp(tok,"hide-fft") == 0) {if (cp) hide_fft(cp); return;}
      if (strcmp(tok,"hide-marks") == 0) {hide_marks(ss); return;}
      if (strcmp(tok,"hide-peaks") == 0) {hide_peaks(ss); return;}
      if (strcmp(tok,"hide-wave") == 0) {if (cp) hide_wave(cp); return;}
      if (strcmp(tok,"hide-y-zero") == 0) {hide_y_zero(ss); return;}
      break;
    case 'i':
      if (strcmp(tok,"incf") == 0) 
	{
	  strbuf[0] = strtok(NULL,clm_white_space);
	  strbuf[1] = strtok(NULL,clm_white_space);
	  if (clm_setf(1,strbuf[0],strbuf[1],ss,sp,cp)) return; else break;
	}
      if (strcmp(tok,"info") == 0) {if (sp) display_info(sp); return;}
      if (strcmp(tok,"initial-x0") == 0) {snd_fvalue(ss,ss->initx0); return;}
      if (strcmp(tok,"initial-x1") == 0) {snd_fvalue(ss,ss->initx1); return;}
      if (strcmp(tok,"initial-y0") == 0) {snd_fvalue(ss,ss->inity0); return;}
      if (strcmp(tok,"initial-y1") == 0) {snd_fvalue(ss,ss->inity1); return;}
      if ((strcmp(tok,"insert") == 0) && (cp))
	{
	  beg = ivalue_snd(strtok(NULL,clm_white_space));
	  num = ivalue_snd(strtok(NULL,clm_white_space));
	  data = clm_snd_data(ss,&i);
	  insert_samples(beg,num,data,cp);
	  check_for_first_edit(cp);
	  update_graph(cp,NULL);
	  free(data);
	  return;
	}
      break;
    case 'j':
      if (strcmp(tok,"just-sounds") == 0) {toggle_just_sounds(1); return;}
      break;
    case 'k':
      if ((strcmp(tok,"key") == 0) && (cp))
	{
	  int key,state;
	  char *keysym;
	  keysym = strtok(NULL,clm_white_space);
	  key = (int)(*keysym);
	  state = ivalue_snd(strtok(NULL,clm_white_space));
	  redisplay = keyboard_command(cp,key,state);
	  handle_cursor(cp,redisplay);
	  return;
	}
      break;
    case 'l':
      if (strcmp(tok,"lines") == 0) {view_lines(ss); return;}
      if (strcmp(tok,"line-size") == 0) {snd_ivalue(ss,ss->editor_line_size); return;}
      if (strcmp(tok,"load") == 0) {snd_load_file(ss,strtok(NULL,clm_white_space)); return;}
      if (strcmp(tok,"load-function") == 0) 
	{
	  strbuf[0] = strtok(NULL,clm_white_space);
	  strbuf[1] = strtok(NULL,clm_white_space);
	  snd_ivalue(ss,snd_load_ufun(complete_filename(strbuf[0]),strbuf[1])); 
	  return;
	}
      break;
    case 'm':
      if (strcmp(tok,"maxamp") == 0) {if ((cp) && (sp)) snd_fvalue(ss,get_maxamp(ss,sp,cp)); else snd_fvalue(ss,0.0); return;}
      if (strcmp(tok,"mark") == 0) {if (cp) snd_ivalue(ss,clm_mark(cp,strtok(NULL,clm_white_space))); else snd_ivalue(ss,0); return;}
      if (strcmp(tok,"mix") == 0)
	{
	  /* (mix temp-file true-beg len delete-me group) -> clm_mix(strbuf[0],beg,num,temp,group) */
	  if (sp)
	    {
	      strbuf[0] = strtok(NULL,clm_white_space);
	      beg = ivalue_snd(strtok(NULL,clm_white_space));
	      num = ivalue_snd(strtok(NULL,clm_white_space));
	      temp = ivalue_snd(strtok(NULL,clm_white_space)); /* a flag for temp file deletion */
	      len = ivalue_snd(strtok(NULL,clm_white_space));  /* group (-1 = none) */
	      clm_mix(sp,strbuf[0],beg,num,temp,len);
	    }
	  return;
	}
      if (strcmp(tok,"mix-amp-scaler") == 0) {snd_fvalue(ss,get_mix_amp_scaler()); return;}
      if (strcmp(tok,"mix-duration-brackets") == 0) {snd_ivalue(ss,ss->mix_duration_brackets); return;}
      if (strcmp(tok,"mix-speed-scaler") == 0) {snd_fvalue(ss,get_mix_speed_scaler()); return;}
      if (strcmp(tok,"mix-tempo-scaler") == 0) {snd_fvalue(ss,get_mix_tempo_scaler()); return;}

      if (strcmp(tok,"mix-position") == 0) {snd_ivalue(ss,get_mix_idata(ss,ivalue_snd(strtok(NULL,clm_white_space)),MIX_POSITION)); return;}
      if (strcmp(tok,"mix-length") == 0) {snd_ivalue(ss,get_mix_idata(ss,ivalue_snd(strtok(NULL,clm_white_space)),MIX_LENGTH)); return;}
      if (strcmp(tok,"mix-state") == 0) {snd_ivalue(ss,get_mix_idata(ss,ivalue_snd(strtok(NULL,clm_white_space)),MIX_STATE)); return;}
      if (strcmp(tok,"mix-anchor") == 0) {snd_ivalue(ss,get_mix_idata(ss,ivalue_snd(strtok(NULL,clm_white_space)),MIX_ANCHOR)); return;}
      if (strcmp(tok,"mix-groups") == 0) {snd_ivalue(ss,get_mix_idata(ss,ivalue_snd(strtok(NULL,clm_white_space)),MIX_GROUPS)); return;}
      if (strcmp(tok,"mix-speed") == 0) {snd_fvalue(ss,get_mix_fdata(ss,ivalue_snd(strtok(NULL,clm_white_space)),0,MIX_SPEED)); return;}
      if (strcmp(tok,"mix-amp") == 0) 
	{
	  num = ivalue_snd(strtok(NULL,clm_white_space));
	  temp = ivalue_snd(strtok(NULL,clm_white_space));
	  snd_fvalue(ss,get_mix_fdata(ss,num,temp,MIX_AMP)); 
	  return;
	}

      /* TODO: 
       *       group-amp-env[i] group-speed-env group-tempo-env group-mixes(?)
       *       file-name file-beg file-end file-len file-srate file-chans etc
       *       region-beg region-end region-len region-srate region-chans
       *       mark-name mark-position
       */

      if (strcmp(tok,"movies") == 0) {snd_ivalue(ss,ss->movies); return;}
      break;
    case 'n':
      if (strcmp(tok,"new") == 0) {snd_new_file(ss); return;} /* changed 19-June-97 to correspond to file menu new option per snd.html */
      if (strcmp(tok,"normalize") == 0) {normalize_all_sounds(ss); return;}
      if (strcmp(tok,"normalize-fft") == 0) {snd_ivalue(ss,ss->normalize_fft); return;}
      break;
    case 'o': 
      if (strcmp(tok,"open") == 0) {snd_open_file(strtok(NULL,clm_white_space),ss); return;}
      if (strcmp(tok,"orientation-dialog") == 0) 
	{
	  strbuf[0] = strtok(NULL,clm_white_space);
	  strbuf[1] = strtok(NULL,clm_white_space);
	  start_orientation_dialog(ss,ivalue_snd(strbuf[0]),ivalue_snd(strbuf[1])); 
	  return;
	}
      if (strcmp(tok,"override-data-location") == 0) {c_snd_header_override_data_location(ivalue_snd(strtok(NULL,clm_close_paren))); return;}
      if (strcmp(tok,"override-data-size") == 0) {c_snd_header_override_data_size(); return;}
      if (strcmp(tok,"override-sound-format") == 0) {c_snd_header_override_sound_format(ivalue_snd(strtok(NULL,clm_close_paren))); return;}
      if (strcmp(tok,"overwrite-check") == 0) {snd_ivalue(ss,ss->ask_before_overwrite); return;}
      break;
    case 'p':
      if ((strcmp(tok,"paste") == 0) && (cp))
	{
	  str = strtok(NULL,clm_white_space);
	  if ((str) && (*str))
	    paste_region(ivalue_snd(str),cp); /* uses n unchanged */
	  else paste_region(0,cp);
	  update_graph(cp,NULL);
	  return;
	}
      if (strcmp(tok,"peaks") == 0) {if (cp) display_fft_peaks(cp); return;}
      if (strcmp(tok,"play") == 0) {sp=find_sound(ss,strtok(NULL,clm_white_space)); if (sp) start_playing(sp,0); return;}
      if (strcmp(tok,"preload") == 0) {add_directory_to_prevlist(ss,strtok(NULL,clm_close_paren)); return;}
      if (strcmp(tok,"preload-file-browser") == 0) 
	{
	  num = 16;
	  pn = (char **)calloc(num,sizeof(char *));
	  for (i=0;;i++)
	    {
	      ofile = strtok(NULL,clm_white_space);
	      if ((ofile) && (*ofile))
		{
		  if (i == num)
		    {
		      num+=16;
		      pn = (char **)realloc(pn,num*sizeof(char *));
		    }
		  pn[i] = ofile;
		}
	      else break;
	    }
	  clm_preload_fb(ss,pn,i);
	  free(pn);
	  return;
	}
      if (strcmp(tok,"print") == 0) {snd_print(ss,ss->eps_file,1); return;}
      break;
    case 'r':
      if (strcmp(tok,"raw-chans") == 0) {get_raw_data_defaults(raw_buf); snd_ivalue(ss,raw_buf[1]); return;}
      if (strcmp(tok,"raw-defaults") == 0) {get_raw_data_defaults(raw_buf); snd_ivalue(ss,raw_buf[3]); return;}
      if (strcmp(tok,"raw-format") == 0) {get_raw_data_defaults(raw_buf); snd_ivalue(ss,raw_buf[2]); return;}
      if (strcmp(tok,"raw-sound") == 0) 
	{
	  strbuf[0] = strtok(NULL,clm_white_space);
	  strbuf[1] = strtok(NULL,clm_white_space);
	  strbuf[2] = strtok(NULL,clm_white_space);
	  strbuf[3] = strtok(NULL,clm_white_space);
	  set_raw_data_defaults(ivalue_snd(strbuf[0]),ivalue_snd(strbuf[1]),ivalue_snd(strbuf[2]),ivalue_snd(strbuf[3]));
	  return;
	}
      if (strcmp(tok,"raw-srate") == 0) {get_raw_data_defaults(raw_buf); snd_ivalue(ss,raw_buf[0]); return;}
      if (strcmp(tok,"read-only") == 0) {if (sp) snd_ivalue(ss,sp->read_only); return;}
      if ((strcmp(tok,"redo") == 0) && (cp))
	{
	  str = strtok(NULL,clm_white_space);
	  if ((str) && (*str))
	    redo_EDIT(cp,ivalue_snd(str));
	  else redo_EDIT(cp,1); 
	  update_graph(cp,NULL);
	  return;
	}
      if (strcmp(tok,"region") == 0) {clm_get_region(ss,ivalue_snd(strtok(NULL,clm_white_space))); return;}
      if (strcmp(tok,"remember-state") == 0) {snd_ivalue(ss,ss->remember_state); return;}
      if (strcmp(tok,"reopen") == 0) {snd_reopen_file(strtok(NULL,clm_white_space),ss); return;}
      if (strcmp(tok,"report-in-minibuffer") == 0) 
	{
	  if (sp) report_in_minibuffer(sp,strtok(NULL,clm_white_space));
	  return;
	}
      if (strcmp(tok,"restore-controls") == 0) {if (sp) restore_control_state(sp); return;}
      if (strcmp(tok,"restore-state") == 0) {restore_state(ss,strtok(NULL,clm_white_space)); return;}
      if (strcmp(tok,"revdecay") == 0) {snd_fvalue(ss,ss->reverb_decay); return;}
      if (strcmp(tok,"reverb-button") == 0) {snd_ivalue(ss,(sp) ? sp->reverbing : 0); return;}
      if (strcmp(tok,"revert") == 0) {clm_revert(ss,strtok(NULL,clm_white_space)); return;}
      if (strcmp(tok,"revfb") == 0) {snd_fvalue(ss,(sp) ? sp->revfb : 0.0); return;}
      if (strcmp(tok,"revlen") == 0) {snd_fvalue(ss,(sp) ? sp->revlen : 0.0); return;}
      if (strcmp(tok,"revlp") == 0) {snd_fvalue(ss,(sp) ? sp->revlp : 0.0); return;}
      if (strcmp(tok,"revscl") == 0) {snd_fvalue(ss,(sp) ? sp->revscl : 0.0); return;}

      if (strcmp(tok,"record-chans") == 0) {snd_ivalue(ss,ss->record_chans); return;}
      if (strcmp(tok,"record-srate") == 0) {snd_ivalue(ss,ss->record_srate); return;}
      if (strcmp(tok,"record-type") == 0) {snd_ivalue(ss,ss->record_type); return;}
      if (strcmp(tok,"record-format") == 0) {snd_ivalue(ss,ss->record_format); return;}
      if (strcmp(tok,"record-line-panel") == 0) {snd_ivalue(ss,ss->record_line_panel); return;}
      if (strcmp(tok,"record-mic-panel") == 0) {snd_ivalue(ss,ss->record_mic_panel); return;}
      if (strcmp(tok,"record-out-panel") == 0) {snd_ivalue(ss,ss->record_out_panel); return;}
      if (strcmp(tok,"record-autoload") == 0) {snd_ivalue(ss,ss->record_autoload); return;}
      if (strcmp(tok,"record-digital-line-in") == 0) {snd_ivalue(ss,ss->record_digital); return;}

      if (strcmp(tok,"record-line-a") == 0) {snd_fvalue(ss,reflect_record_slider(ss,0.0,0,0,FALSE)); return;}      
      if (strcmp(tok,"record-line-b") == 0) {snd_fvalue(ss,reflect_record_slider(ss,0.0,1,0,FALSE)); return;}      
      if (strcmp(tok,"record-line-aa") == 0) {snd_fvalue(ss,reflect_record_slider(ss,0.0,2,0,FALSE)); return;}      
      if (strcmp(tok,"record-line-bb") == 0) {snd_fvalue(ss,reflect_record_slider(ss,0.0,3,0,FALSE)); return;}      
      if (strcmp(tok,"record-line-ab") == 0) {snd_fvalue(ss,reflect_record_slider(ss,0.0,4,0,FALSE)); return;}      
      if (strcmp(tok,"record-line-ba") == 0) {snd_fvalue(ss,reflect_record_slider(ss,0.0,5,0,FALSE)); return;}   
   
      if (strcmp(tok,"record-mic-a") == 0) {snd_fvalue(ss,reflect_record_slider(ss,0.0,6+0,0,FALSE)); return;}      
      if (strcmp(tok,"record-mic-b") == 0) {snd_fvalue(ss,reflect_record_slider(ss,0.0,6+1,0,FALSE)); return;}      
      if (strcmp(tok,"record-mic-aa") == 0) {snd_fvalue(ss,reflect_record_slider(ss,0.0,6+2,0,FALSE)); return;}      
      if (strcmp(tok,"record-mic-bb") == 0) {snd_fvalue(ss,reflect_record_slider(ss,0.0,6+3,0,FALSE)); return;}      
      if (strcmp(tok,"record-mic-ab") == 0) {snd_fvalue(ss,reflect_record_slider(ss,0.0,6+4,0,FALSE)); return;}      
      if (strcmp(tok,"record-mic-ba") == 0) {snd_fvalue(ss,reflect_record_slider(ss,0.0,6+5,0,FALSE)); return;}   
   
      if (strcmp(tok,"record-out-a") == 0) {snd_fvalue(ss,reflect_record_slider(ss,0.0,12+0,0,FALSE)); return;}      
      if (strcmp(tok,"record-out-b") == 0) {snd_fvalue(ss,reflect_record_slider(ss,0.0,12+1,0,FALSE)); return;}      
      if (strcmp(tok,"record-out-aa") == 0) {snd_fvalue(ss,reflect_record_slider(ss,0.0,12+2,0,FALSE)); return;}      
      if (strcmp(tok,"record-out-bb") == 0) {snd_fvalue(ss,reflect_record_slider(ss,0.0,12+3,0,FALSE)); return;}      

      break;
    case 's':
      if (strcmp(tok,"samples") == 0) {snd_ivalue(ss,(sp) ? (current_ed_samples(sp->chans[0])) : 0); return;}
      if (strcmp(tok,"save") == 0) {save_edits(find_sound(ss,strtok(NULL,clm_white_space)),NULL); return;}
      if (strcmp(tok,"save-as") == 0) 
	{
	  strbuf[0] = strtok(NULL,clm_white_space);
	  strbuf[1] = strtok(NULL,clm_white_space);
	  clm_save_as(ss,strbuf[0],strbuf[1]); 
	  return;
	}
      if (strcmp(tok,"save-controls") == 0) {if (sp) save_control_state(sp); return;}
      if (strcmp(tok,"save-macros") == 0) {save_macros(ss); return;}
      if (strcmp(tok,"save-macro") == 0) {save_macro(ss,strtok(NULL,clm_white_space)); return;}
      if (strcmp(tok,"save-marks") == 0) {if (sp) save_marks(sp); return;}
      if (strcmp(tok,"save-state") == 0) {save_snd_state(ss); return;}
      if ((strcmp(tok,"scale-by") == 0) || (strcmp(tok,"scale-selection-by") == 0))
	{
	  /* mimic the var_args or &rests args for C/Lisp */
	  err = strcmp(tok,"scale-selection-by");
	  amp_scalers[0] = 1.0; /* in case of no-op (scale-by) ! */
	  for (i=0;((i<MAX_SCALERS) && (str=strtok(NULL,clm_white_space_or_close_paren)));i++)
	    {
	      amp_scalers[i] = fvalue_snd(str);
	    }
	  scale_by(ss,sp,cp,amp_scalers,i,(err == 0));
	  return;
	}
      if ((strcmp(tok,"scale-to") == 0) || (strcmp(tok,"scale-selection-to") == 0))
	{
	  /* mimic the var_args or &rests args for C/Lisp */
	  err = strcmp(tok,"scale-selection-to");
	  amp_scalers[0] = 1.0; /* i.e (scale-to) => normalize to 1.0 */
	  for (i=0;((i<MAX_SCALERS) && (str=strtok(NULL,clm_white_space_or_close_paren)));i++)
	    {
	      amp_scalers[i] = fvalue_snd(str);
	    }
	  scale_to(ss,sp,cp,amp_scalers,i,(err == 0));
	  return;
	}
      if (strcmp(tok,"select-channel") == 0) {clm_select_channel(ss,ivalue_snd(strtok(NULL,clm_white_space))); return;}
      if (strcmp(tok,"select-sound") == 0) {clm_select_sound(ss,ivalue_snd(strtok(NULL,clm_white_space))); return;}
      if (strcmp(tok,"selection-amp-envelope") == 0) 
	{
	  if (cp) apply_env(cp,scan_envelope_and_report_error(sp,strtok(NULL,clm_close_paren),&err),0,region_len(0),1.0,1,FALSE);
	  return;
	}
      if (strcmp(tok,"separate") == 0) {if (sp) combineb(sp,0); return;}
      if (strcmp(tok,"session") == 0) {save_state(ss,strtok(NULL,clm_close_paren)); return;}
      if ((strcmp(tok,"setf") == 0) || (strcmp(tok,"setq") == 0))
	{
	  strbuf[0] = strtok(NULL,clm_white_space);
	  strbuf[1] = strtok(NULL,clm_white_space);
	  if (clm_setf(0,strbuf[0],strbuf[1],ss,sp,cp)) return; else break;
	}
      if (strcmp(tok,"setf-mark") == 0) 
	{
	  if (cp) 
	    {
	      strbuf[0] = strtok(NULL,clm_white_space);
	      strbuf[1] = strtok(NULL,clm_white_space);
	      clm_set_mark(cp,strbuf[0],ivalue_snd(strbuf[1]));
	    }
	  return;
	}
      if (strcmp(tok,"setf-region") == 0) 
	{
	  if (cp) 
	    {
	      strbuf[0] = strtok(NULL,clm_white_space);
	      strbuf[1] = strtok(NULL,clm_white_space);
	      clm_set_region(cp,ivalue_snd(strbuf[0]),ivalue_snd(strbuf[1]));
	    }
	  return;
	}
      if (strcmp(tok,"setf-y") == 0) 
	{
	  if (cp) 
	    {
	      strbuf[0] = strtok(NULL,clm_white_space);
	      strbuf[1] = strtok(NULL,clm_white_space);
	      clm_set_y(cp,ivalue_snd(strbuf[0]),fvalue_snd(strbuf[1])); 
	      }
	  return;
	}
      if (strcmp(tok,"show-consoles") == 0) {show_consoles(ss); return;}
      if (strcmp(tok,"show-controls") == 0) {show_controls(ss); return;}
      if (strcmp(tok,"show-fft") == 0) {if (cp) show_fft(cp); return;}
      if (strcmp(tok,"show-marks") == 0) {show_marks(ss); return;}
      if (strcmp(tok,"show-peaks") == 0) {show_peaks(ss); return;}
      if (strcmp(tok,"show-wave") == 0) {if (cp) show_wave(cp); return;}
      if (strcmp(tok,"show-y-zero") == 0) {show_y_zero(ss); return;}
      if (strcmp(tok,"silent-cursor") == 0) {silent_cursor(ss); return;}
      if (strcmp(tok,"sono-color") == 0) {snd_ivalue(ss,ss->sonogram_color); return;}
      if (strcmp(tok,"sono-max") == 0) {snd_fvalue(ss,ss->sonogram_cutoff); return;}
      if (strcmp(tok,"sound") == 0)
	{
	  /* originally I wanted to use M-X (with-sound ...) from Snd to CLM, but there is no reasonable
	   * way to get lisp to listen to the Snd-clm pipe and interrupt the lisp listener if input is seen.
	   * set-sigio-handler and friends all deal only with sockets, tl's top level would require polling,
	   * sleep can't sleep for less than 1 second (which is forever), acl's ipc.cl is a mess, and so on -- 
	   * a whole afternoon wasted...
	   *
	   * (later) see data command above -- a dialog is fired up in Snd while CLM waits
	   */
	  tok=strtok(NULL,clm_white_space);
	  if ((tok) && (*tok)) sp = find_sound(ss,tok);  /* otherwise use sp already set up via any_selected_sound */
	  str = only_save_edits(sp,sp->hdr,&num);	 /* send temp file name to clm, let it decide whether to change sp */
	  write(ss->to_clm,str,strlen(str)+1);
	  free(str);                                     /* allocated by tempnam in snd-edits.c only_save_edits */
	  str = NULL;
	  return;
	}
      if (strcmp(tok,"spectro-color") == 0) {snd_ivalue(ss,ss->spectrogram_color); return;}
      if (strcmp(tok,"spectro-hop") == 0) {snd_ivalue(ss,ss->spectro_hop); return;}
      if (strcmp(tok,"spectro-xangle") == 0) {snd_fvalue(ss,ss->spectro_xangle); return;}
      if (strcmp(tok,"spectro-xscl") == 0) {snd_fvalue(ss,ss->spectro_xscl); return;}
      if (strcmp(tok,"spectro-yangle") == 0) {snd_fvalue(ss,ss->spectro_yangle); return;}
      if (strcmp(tok,"spectro-yscl") == 0) {snd_fvalue(ss,ss->spectro_yscl); return;}
      if (strcmp(tok,"spectro-zangle") == 0) {snd_fvalue(ss,ss->spectro_zangle); return;}
      if (strcmp(tok,"spectro-zscl") == 0) {snd_fvalue(ss,ss->spectro_zscl); return;}
      if (strcmp(tok,"speed") == 0) {snd_fvalue(ss,(sp) ? sp->srate : 0.0); return;}
      if (strcmp(tok,"speed-style") == 0) {snd_ivalue(ss,ss->speed_style); return;}
      if (strcmp(tok,"speed-tones") == 0) {snd_ivalue(ss,ss->speed_tones); return;}
      if (strcmp(tok,"srate") == 0) {snd_ivalue(ss,(sp) ? (snd_SRATE(sp)): 0); return;}
      if (strcmp(tok,"src") == 0) {src(ss,strtok(NULL,clm_close_paren),FALSE); return;}
      if (strcmp(tok,"src-width") == 0) {snd_ivalue(ss,ss->sinc_width); return;}
      if (strcmp(tok,"stop") == 0) {sp=find_sound(ss,strtok(NULL,clm_white_space)); if ((sp) && (sp->playing)) stop_playing(sp->playing); return;}
      if (strcmp(tok,"subsampling-on") == 0) {subsampling_on(ss); return;}
      if (strcmp(tok,"subsampling-off") == 0) {subsampling_off(ss); return;}
      if (strcmp(tok,"sync") == 0) {if (sp) syncb(sp); return;}
      break;
    case 't':
      if (strcmp(tok,"temp-dir") == 0) {if (ss->temp_dir) free(ss->temp_dir); ss->temp_dir = copy_string(strtok(NULL,clm_white_space)); return;}
      if (strcmp(tok,"tickle-clm") == 0) {write(ss->to_clm,"0",2); return;}
      if (strcmp(tok,"transform-type") == 0) {transform_type(ss,ivalue_snd(strtok(NULL,clm_white_space))); return;}
      break;
    case 'u':
      if ((strcmp(tok,"undo") == 0) && (cp))
	{
	  str = strtok(NULL,clm_white_space);
	  if ((str) && (*str))
	    undo_EDIT(cp,ivalue_snd(str));
	  else undo_EDIT(cp,1); 
	  update_graph(cp,NULL);
	  return;
	}
      if (strcmp(tok,"unite") == 0) {if (sp) combineb(sp,1); return;}
      if (strcmp(tok,"unsync") == 0) {if (sp) unsyncb(sp); return;}
      break;
    case 'v':
      if (strcmp(tok,"view") == 0) {clm_view(ss,strtok(NULL,clm_white_space)); return;}
      if (strcmp(tok,"verbose-cursor") == 0) {verbose_cursor(ss); return;}
      if (strcmp(tok,"version") == 0) {snd_svalue(ss,SND_VERSION); return;}
      break;
    case 'w':
      if (strcmp(tok,"wavelet-choice") == 0) {snd_ivalue(ss,ss->wavelet_choice); return;}
      if (strcmp(tok,"wavo") == 0) 
	{
	  if ((cp) && (ss->wavo)) set_xy_bounds(cp,cp->axis);
	  ss->wavo = !ss->wavo; 
	  if (cp) update_graph(cp,NULL); 
	  return;
	}
      if (strcmp(tok,"wavo-hop") == 0) {snd_ivalue(ss,ss->wavo_hop); return;}
      if (strcmp(tok,"wavo-trace") == 0) {snd_ivalue(ss,ss->wavo_trace); return;}
      if (strcmp(tok,"window-height") == 0) {snd_ivalue(ss,snd_window_height(ss)); return;}
      if (strcmp(tok,"window-width") == 0) {snd_ivalue(ss,snd_window_width(ss)); return;}
      break;
    case 'x':
      if (strcmp(tok,"x") == 0) {snd_ivalue(ss,(cp) ? cp->cursor : 0); return;}
      if (strcmp(tok,"x-axis") == 0) 
	{
	  if (cp) 
	    {
	      strbuf[0] = strtok(NULL,clm_white_space);
	      strbuf[1] = strtok(NULL,clm_white_space);
	      clm_x_axis(cp,fvalue_snd(strbuf[0]),fvalue_snd(strbuf[1]));
	    }
	  return;
	}
      if (strcmp(tok,"x-axis-style") == 0) {snd_ivalue(ss,ss->x_axis_style); return;}
      if (strcmp(tok,"x-max") == 0) {snd_fvalue(ss,(cp) ? (((axis_info *)(cp->axis))->xmax) : 0.0); return;}
      if (strcmp(tok,"xmax") == 0) {snd_fvalue(ss,ss->xmax); return;}
      if (strcmp(tok,"xmin") == 0) {snd_fvalue(ss,ss->xmin); return;}
      break;
    case 'y':
      if (strcmp(tok,"y") == 0) {if (cp) snd_fvalue(ss,clm_get_y(cp,ivalue_snd(strtok(NULL,clm_white_space)))); else snd_fvalue(ss,0.0); return;}
      if (strcmp(tok,"y-axis") == 0) {if (cp) clm_y_axis(cp,fvalue_snd(strtok(NULL,clm_white_space))); return;}
      if (strcmp(tok,"y-max") == 0) {if (cp) snd_fvalue(ss,((axis_info *)(cp->axis))->ymax); else snd_fvalue(ss,0.0); return;}
      if (strcmp(tok,"ymax") == 0) {snd_fvalue(ss,ss->ymax); return;}
      if (strcmp(tok,"ymin") == 0) {snd_fvalue(ss,ss->ymin); return;}
      break;
    case 'z':
      if (strcmp(tok,"zero-pad") == 0) {snd_ivalue(ss,ss->zero_pad); return;}
      break;
    default: break;
    }
  if (cp) 
    {
      err = execute_macro(cp,doit_buf,1);
      if (err == 0) /* means no such macro */
	{
	  for(i=0,j=start;j<=end;i++,j++) sexpr_buf[i] = buf[j];
	  sexpr_buf[j] = '\0';
	  /* send to clm if attached and selectable (i.e. input interrupt via select and mp) */
	  if ((ss->to_clm) && (ss->clm_selectable))
	    write(ss->to_clm,sexpr_buf,j+1);
	}
    }
}


int get_clm_string(snd_state *ss, int fd)
{
  /* communication is via lisp expressions except for the find exprs -- these are in the C subset used by find */
  int bytes,i,j,k,start,commenting,len,parens,quoting,loop_start;
  if (!clm_buffer)
    {
      clm_buffer = (char *)calloc(CLM_BUFFER_SIZE,sizeof(char));
      clm_buffer_size = CLM_BUFFER_SIZE;
    }
  bytes=read(fd,clm_buffer,clm_buffer_size);
  if (bytes == 0) return(0);
  clm_buffer[bytes] = '\0';
  commenting = 0;
  parens = 0;
  quoting = FALSE;
  start = 0;
  loop_start = 0;
READ_CLM:
  for (i=loop_start;i<bytes;i++)
    {
      if (!quoting)
	{
	  if ((commenting == 0) && (clm_buffer[i] == ';')) commenting = 1;
	  else if ((commenting == 1) && (clm_buffer[i] == '\n')) commenting = 0;
	  else if ((commenting == 0) && (clm_buffer[i] == '#') && (clm_buffer[i+1] == '|')) commenting = 2;
	  else if ((commenting == 2) && (clm_buffer[i] == '|') && (clm_buffer[i+1] == '#')) commenting = 0;
	}
      if (clm_buffer[i] == '\"') quoting = (!quoting);
      if (commenting == 0)
	{
	  if (clm_buffer[i] == '(') {if (parens == 0) start = i; parens++;}
	  else if (clm_buffer[i] == ')') {parens--; if (parens == 0) clm_doit(ss,clm_buffer,start,i);}
	}
    }
  if (parens)
    {
      if (start != 0)
	for (i=start,j=0;i<bytes;i++,j++) clm_buffer[j] = clm_buffer[i];
      len = bytes - start;
      if (len == clm_buffer_size)
	{
	  clm_buffer_size *= 2;
	  clm_buffer = (char *)realloc(clm_buffer,clm_buffer_size*sizeof(char));
	}
      clm_buffer[len] = '\0';
      loop_start = len;
      k = read(fd,(char *)(clm_buffer+len),clm_buffer_size - len);
      if (k <= 0) 
	{
	  fprintf(stderr,snd_string_error_reading_file_extra_open_paren);
	  fprintf(stderr,"\ncurrent expression started at byte %d\n",start);
	  return(0); /* this ends the read loops below */
	}
      bytes = len + k;
      goto READ_CLM;
    }
  return(bytes);
}

static int clm_buffer_in_progress = 0;
static char init_file_buffer[128];

void snd_load_init_file(snd_state *ss)
{
  /* look for ".snd" on the home directory, and load it using the lisp-like CLM syntax given above */
  int fd;
  char *str;
  if (ss->init_file)
    {
      str = ss->init_file;
      if ((*str) == '~')
	{
	  strcpy(init_file_buffer,getenv("HOME"));
	  strcat(init_file_buffer,++str);
	  fd = open(init_file_buffer,O_RDONLY,0);
	}
      else fd = open(ss->init_file,O_RDONLY,0);
      if (fd == -1) return;
      if (!clm_buffer) 
	{
	  clm_buffer = (char *)calloc(CLM_BUFFER_SIZE,sizeof(char));
	  clm_buffer_size = CLM_BUFFER_SIZE;
	}
      clm_buffer_in_progress = 1;
      while (get_clm_string(ss,fd));
      clm_buffer_in_progress = 0;
      close(fd);
    }
}

int snd_load_file(snd_state *ss,char *filename)
{
  int fd,old_progress;
  char *str,*saved_buf;
  str=filename;
  saved_buf = NULL;
  if ((*str) == '~')
    {
      strcpy(init_file_buffer,getenv("HOME"));
      strcat(init_file_buffer,++str);
      fd = open(init_file_buffer,O_RDONLY,0);
    }
  else fd = open(str,O_RDONLY,0);
  if (fd == -1) return(-1);
  if (!clm_buffer) 
    {
      clm_buffer = (char *)calloc(CLM_BUFFER_SIZE,sizeof(char));
      clm_buffer_size = CLM_BUFFER_SIZE;
    }
  old_progress = clm_buffer_in_progress;
  if (clm_buffer_in_progress)
    {
      saved_buf = (char *)calloc(clm_buffer_size,sizeof(char));
      strcpy(saved_buf,clm_buffer);
    }
  clm_buffer_in_progress = 1;
  while (get_clm_string(ss,fd));
  close(fd);
  clm_buffer_in_progress = old_progress;
  if (saved_buf)
    {
      strcpy(clm_buffer,saved_buf);
      free(saved_buf);
    }
  return(0);
}
