#include "snd.h"

/* TODO: save actions as CLM list */

/* support for the record/replay/apply/s/r buttons in the control pane */

/* record watches the control panel state and makes a timed event list of all actions/settings */
/* replay re-runs the previous record.  If record is also on, new events are merged, but */
/*   all subsequent same-control changes are flushed (that is, change to a control erases */
/*   the rest of the recorded changes for that control) */
/* apply is like replay except that the data becomes the newly edited state of the current channels */

#define ACTION_SIZE 128
#define LONGEST_LABEL 16

typedef struct {
  int time;
  int ui_val;
  float snd_val;
} action;

typedef struct {
  int action_size;
  action **amp;
  int amp_ctr,amp_max,amp_init;
  action **speed;
  int speed_ctr,speed_max,speed_init;
  action **direction;
  int direction_ctr,direction_max,direction_init;
  action **expand;
  int expand_ctr,expand_max,expand_init;
  action **contrast;
  int contrast_ctr,contrast_max,contrast_init;
  action **reverb;
  int reverb_ctr,reverb_max,reverb_init;
  int revlen;
  int filter_order;
  env *filter;
  snd_state *ss;
  snd_info *sp;
} apply_info;

/* X times appear to be rounded to .01 secs or worse, but in this context that is probably ok */
/* we're not firing up notes by pushing keys, or anything that the user will perceive as rhythmic */
 
static int playing = 0;

static int allocate_actions(snd_info *sp, int size)
{
  apply_info *sap;
  int i;
  sap = (apply_info *)(sp->actions);
  if (!sap)
    {
      sap = (apply_info *)calloc(1,sizeof(apply_info));
      sap->amp = (action **)calloc(size,sizeof(action *));
      sap->speed = (action **)calloc(size,sizeof(action *));
      sap->direction = (action **)calloc(size,sizeof(action *));
      sap->expand = (action **)calloc(size,sizeof(action *));
      sap->contrast = (action **)calloc(size,sizeof(action *));
      sap->reverb = (action **)calloc(size,sizeof(action *));
      sap->action_size = size;
      sap->ss = sp->state;
      sap->sp = sp;
      sp->actions = sap;
    }
  else
    {
      if (size > sap->action_size)
	{
	  sap->amp = (action **)realloc(sap->amp,size*sizeof(action *));
	  sap->speed = (action **)realloc(sap->speed,size*sizeof(action *));
	  sap->direction = (action **)realloc(sap->direction,size*sizeof(action *));
	  sap->expand = (action **)realloc(sap->expand,size*sizeof(action *));
	  sap->contrast = (action **)realloc(sap->contrast,size*sizeof(action *));
	  sap->reverb = (action **)realloc(sap->reverb,size*sizeof(action *));
	  for (i=sap->action_size;i<size;i++) 
	    {
	      sap->amp[i] = NULL;
	      sap->speed[i] = NULL;
	      sap->direction[i] = NULL;
	      sap->expand[i] = NULL;
	      sap->contrast[i] = NULL;
	      sap->reverb[i] = NULL;
	    }
	  sap->action_size = size;
	}
    }
  return(0);
}

void flush_actions(snd_info *sp)
{
  apply_info *sap;
  if ((sp) && (sap = (apply_info*)(sp->actions)))
    {
      /* no need to deallocate -- just make sure we ignore current setup next time around */
      sap->amp_max = 0;
      sap->speed_max = 0;
      sap->direction_max = 0;
      sap->expand_max = 0;
      sap->contrast_max = 0;
      sap->reverb_max = 0;
    }
}

static void restart_actions(apply_info *sap)
{
  sap->amp_ctr = 0;
  sap->speed_ctr = 0;
  sap->direction_ctr = 0;
  sap->expand_ctr = 0;
  sap->contrast_ctr = 0;
  sap->reverb_ctr = 0;
}


/* ---------------- REPLAY ---------------- */

void initialize_replay(snd_info *sp)
{
  allocate_actions(sp,ACTION_SIZE);
  restart_actions((apply_info *)(sp->actions));
}

static int replay_ok(apply_info *ap)
{
  snd_info *sp;
  sp = ap->sp;
  return((int)(sp->playing));
}

static void replay_amp_change(snd_info *sp, int val)
{
  int old_rec;
  old_rec = sp->recording;
  sp->recording  = 0;
  snd_amp_changed(sp,val);
  set_raw_value(snd_widget(sp,W_snd_amp),val);
  sp->recording = old_rec;
}

void replay_amp(void *ap1)
{
  apply_info *ap = (apply_info *)ap1;
  int time;
  action *an_amp,*next_amp;
  if (replay_ok(ap))
    {
      an_amp = ap->amp[ap->amp_ctr];
      replay_amp_change(ap->sp,an_amp->ui_val);
      ap->amp_ctr++;
      if (ap->amp_ctr <= ap->amp_max) 
	{
	  next_amp = ap->amp[ap->amp_ctr];
	  time = (next_amp->time - an_amp->time);
	  if (time > 0) call_replay_amp(ap->ss,ap,time);
	}
    }
}

static void replay_speed_change(snd_info *sp, int val)
{
  int old_rec;
  old_rec = sp->recording;
  sp->recording  = 0;
  snd_srate_changed(sp,val);
  set_raw_value(snd_widget(sp,W_snd_srate),val);
  sp->recording = old_rec;
}

void replay_speed(void *ap1)
{
  apply_info *ap = (apply_info *)ap1;
  int time;
  action *a_speed,*next_speed;
  if (replay_ok(ap))
    {
      a_speed = ap->speed[ap->speed_ctr];
      replay_speed_change(ap->sp,a_speed->ui_val);
      ap->speed_ctr++;
      if (ap->speed_ctr <= ap->speed_max) 
	{
	  next_speed = ap->speed[ap->speed_ctr];
	  time = (next_speed->time - a_speed->time);
	  if (time > 0) call_replay_speed(ap->ss,ap,time);
	}
    }
}

static void replay_contrast_change(snd_info *sp, int val)
{
  int old_rec;
  old_rec = sp->recording;
  sp->recording  = 0;
  snd_contrast_changed(sp,val);
  set_raw_value(snd_widget(sp,W_snd_contrast),val);
  sp->recording = old_rec;
}

void replay_contrast(void *ap1)
{
  apply_info *ap = (apply_info *)ap1;
  int time;
  action *a_contrast,*next_contrast;
  if (replay_ok(ap))
    {
      a_contrast = ap->contrast[ap->contrast_ctr];
      replay_contrast_change(ap->sp,a_contrast->ui_val);
      ap->contrast_ctr++;
      if (ap->contrast_ctr <= ap->contrast_max) 
	{
	  next_contrast = ap->contrast[ap->contrast_ctr];
	  time = (next_contrast->time - a_contrast->time);
	  if (time > 0) call_replay_contrast(ap->ss,ap,time);
	}
    }
}

static void replay_expand_change(snd_info *sp, int val)
{
  int old_rec;
  old_rec = sp->recording;
  sp->recording  = 0;
  snd_expand_changed(sp,val);
  set_raw_value(snd_widget(sp,W_snd_expand),val);
  sp->recording = old_rec;
}

void replay_expand(void *ap1)
{
  apply_info *ap = (apply_info *)ap1;
  int time;
  action *an_expand,*next_expand;
  if (replay_ok(ap))
    {
      an_expand = ap->expand[ap->expand_ctr];
      replay_expand_change(ap->sp,an_expand->ui_val);
      ap->expand_ctr++;
      if (ap->expand_ctr <= ap->expand_max) 
	{
	  next_expand = ap->expand[ap->expand_ctr];
	  time = (next_expand->time - an_expand->time);
	  if (time > 0) call_replay_expand(ap->ss,ap,time);
	}
    }
}

static void replay_reverb_change(snd_info *sp, int val)
{
  int old_rec;
  old_rec = sp->recording;
  sp->recording  = 0;
  snd_revscl_changed(sp,val);
  set_raw_value(snd_widget(sp,W_snd_revscl),val);
  sp->recording = old_rec;
}

void replay_reverb(void *ap1)
{
  apply_info *ap = (apply_info *)ap1;
  int time;
  action *a_reverb,*next_reverb;
  if (replay_ok(ap))
    {
      a_reverb = ap->reverb[ap->reverb_ctr];
      replay_reverb_change(ap->sp,a_reverb->ui_val);
      ap->reverb_ctr++;
      if (ap->reverb_ctr <= ap->reverb_max) 
	{
	  next_reverb = ap->reverb[ap->reverb_ctr];
	  time = (next_reverb->time - a_reverb->time);
	  if (time > 0) call_replay_reverb(ap->ss,ap,time);
	}
    }
}

static void replay_revlen_change(snd_info *sp, int val)
{
  int old_rec;
  old_rec = sp->recording;
  sp->recording  = 0;
  snd_revlen_changed(sp,val);
  set_raw_value(snd_widget(sp,W_snd_revlen),val);
  sp->recording = old_rec;
}

static void replay_direction_change(snd_info *sp, int val)
{
  int old_rec;
  old_rec = sp->recording;
  sp->recording  = 0;
  toggle_direction_arrow(sp,val);
  sp->recording = old_rec;
}

void replay_direction(void *ap1)
{
  apply_info *ap = (apply_info *)ap1;
  int time;
  action *a_direction,*next_direction;
  if (replay_ok(ap))
    {
      a_direction = ap->direction[ap->direction_ctr];
      replay_direction_change(ap->sp,a_direction->ui_val);
      ap->direction_ctr++;
      if (ap->direction_ctr <= ap->direction_max) 
	{
	  next_direction = ap->direction[ap->direction_ctr];
	  time = (next_direction->time - a_direction->time);
	  if (time > 0) call_replay_direction(ap->ss,ap,time);
	}
    }
}


/* ---------------- RECORD ---------------- */

void initialize_record(snd_info *sp)
{
  allocate_actions(sp,ACTION_SIZE);
  restart_actions((apply_info*)(sp->actions));
}

void record_amp_change(snd_info *sp,int scroll_val,float amp_val)
{
  apply_info *sap;
  action *amp;
  action **amps;
  if (!playing) return;
  sap = (apply_info *)(sp->actions);
  if (sap->action_size <= sap->amp_ctr) allocate_actions(sp,ACTION_SIZE+sap->action_size);
  amps = sap->amp;
  if (!(amps[sap->amp_ctr])) amps[sap->amp_ctr] = (action *)calloc(1,sizeof(action));
  amp = amps[sap->amp_ctr];
  amp->ui_val = scroll_val;
  amp->snd_val = amp_val;
  amp->time = main_TIME(sp->state);
  if ((sp->replaying) || (sap->amp_ctr > sap->amp_max)) sap->amp_max = sap->amp_ctr;
  sap->amp_ctr++;
}


void record_speed_change(snd_info *sp,int scroll_val,float speed_val)
{
  apply_info *sap;
  action *speed;
  action **speeds;
  if (!playing) return;
  sap = (apply_info *)(sp->actions);
  if (sap->action_size <= sap->speed_ctr) allocate_actions(sp,ACTION_SIZE+sap->action_size);
  speeds = sap->speed;
  if (!(speeds[sap->speed_ctr])) speeds[sap->speed_ctr] = (action *)calloc(1,sizeof(action));
  speed = speeds[sap->speed_ctr];
  speed->ui_val = scroll_val;
  speed->snd_val = speed_val;
  speed->time = main_TIME(sp->state);
  if ((sp->replaying) || (sap->speed_ctr > sap->speed_max)) sap->speed_max = sap->speed_ctr;
  sap->speed_ctr++;
}

void record_expand_change(snd_info *sp,int scroll_val,float expand_val)
{
  apply_info *sap;
  action *expand;
  action **expands;
  if (!playing) return;
  sap = (apply_info *)(sp->actions);
  if (sap->action_size <= sap->expand_ctr) allocate_actions(sp,ACTION_SIZE+sap->action_size);
  expands = sap->expand;
  if (!(expands[sap->expand_ctr])) expands[sap->expand_ctr] = (action *)calloc(1,sizeof(action));
  expand = expands[sap->expand_ctr];
  expand->ui_val = scroll_val;
  expand->snd_val = expand_val;
  expand->time = main_TIME(sp->state);
  if ((sp->replaying) || (sap->expand_ctr > sap->expand_max)) sap->expand_max = sap->expand_ctr;
  sap->expand_ctr++;
}

void record_reverb_change(snd_info *sp,int scroll_val,float reverb_val)
{
  apply_info *sap;
  action *reverb;
  action **reverbs;
  if (!playing) return;
  sap = (apply_info *)(sp->actions);
  if (sap->action_size <= sap->reverb_ctr) allocate_actions(sp,ACTION_SIZE+sap->action_size);
  reverbs = sap->reverb;
  if (!(reverbs[sap->reverb_ctr])) reverbs[sap->reverb_ctr] = (action *)calloc(1,sizeof(action));
  reverb = reverbs[sap->reverb_ctr];
  reverb->ui_val = scroll_val;
  reverb->snd_val = reverb_val;
  reverb->time = main_TIME(sp->state);
  if ((sp->replaying) || (sap->reverb_ctr > sap->reverb_max)) sap->reverb_max = sap->reverb_ctr;
  sap->reverb_ctr++;
}

void record_contrast_change(snd_info *sp,int scroll_val,float contrast_val)
{
  apply_info *sap;
  action *contrast;
  action **contrasts;
  if (!playing) return;
  sap = (apply_info *)(sp->actions);
  if (sap->action_size <= sap->contrast_ctr) allocate_actions(sp,ACTION_SIZE+sap->action_size);
  contrasts = sap->contrast;
  if (!(contrasts[sap->contrast_ctr])) contrasts[sap->contrast_ctr] = (action *)calloc(1,sizeof(action));
  contrast = contrasts[sap->contrast_ctr];
  contrast->ui_val = scroll_val;
  contrast->snd_val = contrast_val;
  contrast->time = main_TIME(sp->state);
  if ((sp->replaying) || (sap->contrast_ctr > sap->contrast_max)) sap->contrast_max = sap->contrast_ctr;
  sap->contrast_ctr++;
}

void record_direction_change(snd_info *sp,int scroll_val,float direction_val)
{
  apply_info *sap;
  action *direction;
  action **directions;
  if (!playing) return;
  sap = (apply_info *)(sp->actions);
  if (sap->action_size <= sap->direction_ctr) allocate_actions(sp,ACTION_SIZE+sap->action_size);
  directions = sap->direction;
  if (!(directions[sap->direction_ctr])) directions[sap->direction_ctr] = (action *)calloc(1,sizeof(action));
  direction = directions[sap->direction_ctr];
  direction->ui_val = scroll_val;
  direction->snd_val = direction_val;
  direction->time = main_TIME(sp->state);
  if ((sp->replaying) || (sap->direction_ctr > sap->direction_max)) sap->direction_max = sap->direction_ctr;
  sap->direction_ctr++;
}

void finish_record_and_replay(void)
{
  playing = 0;
}

void start_record_and_replay(snd_state *ss, snd_info *sp)
{
  /* called when the dac is actually started -- none of these buttons may be down when we're called */
  /* at startup save initial state (at record time?) */
  apply_info *sap;
  action **amps,**speeds,**contrasts,**expands,**reverbs,**directions;
  int old_rec;
  playing = 1;
  sap = (apply_info *)(sp->actions);
  if (sap)
    {
      restart_actions(sap);
      if ((sp->recording) && (!(sp->replaying)))
	{
	  flush_actions(sp); /* if record on, but not replay, assume start from scratch */
	  sap->amp_init = get_raw_value(snd_widget(sp,W_snd_amp));
	  sap->speed_init = get_raw_value(snd_widget(sp,W_snd_srate));
	  sap->direction_init = get_raw_value(snd_widget(sp,W_snd_srate_arrow));
	  sap->expand_init = get_raw_value(snd_widget(sp,W_snd_expand));
	  sap->contrast_init = get_raw_value(snd_widget(sp,W_snd_contrast));
	  sap->reverb_init = get_raw_value(snd_widget(sp,W_snd_revscl));
	  sap->filter_order = sp->filter_order;
	  if (sp->filter_env) sap->filter = copy_env(sp->filter_env);
	}
      if (sp->replaying)
	{
	  /* for each sync'd sound, we need timeout procs running down the associated action lists */
	  old_rec = sp->recording;
	  sp->recording = 0;
	  if ((amps = sap->amp) && (sap->amp_max > 0))
	    {
	      snd_amp_changed(sp,sap->amp_init);
	      set_raw_value(snd_widget(sp,W_snd_amp),sap->amp_init);
	      call_replay_amp(ss,sap,amps[0]->time);
	    }
	  if ((sp->contrasting) && (contrasts = sap->contrast) && (sap->contrast_max > 0))
	    {
	      snd_contrast_changed(sp,sap->contrast_init);
	      set_raw_value(snd_widget(sp,W_snd_contrast),sap->contrast_init);
	      call_replay_contrast(ss,sap,contrasts[0]->time);
	    }
	  if ((sp->expanding) && (expands = sap->expand) && (sap->expand_max > 0))
	    {
	      snd_expand_changed(sp,sap->expand_init);
	      set_raw_value(snd_widget(sp,W_snd_expand),sap->expand_init);
	      call_replay_expand(ss,sap,expands[0]->time);
	    }
	  if ((sp->reverbing) && (reverbs = sap->reverb) && (sap->reverb_max > 0))
	    {
	      snd_revscl_changed(sp,sap->reverb_init);
	      set_raw_value(snd_widget(sp,W_snd_revscl),sap->reverb_init);
	      call_replay_reverb(ss,sap,reverbs[0]->time);
	    }
	  if ((speeds = sap->speed) && (sap->speed_max > 0))
	    {
	      snd_srate_changed(sp,sap->speed_init);
	      set_raw_value(snd_widget(sp,W_snd_srate),sap->speed_init);
	      call_replay_speed(ss,sap,speeds[0]->time);
	    }
	  if ((directions = sap->direction) && (sap->direction_max > 0))
	    {
	      toggle_direction_arrow(sp,sap->direction_init);
	      call_replay_direction(ss,sap,directions[0]->time);
	    }
	  sp->filter_order = sap->filter_order;
	  if (sp->filter_env) sp->filter_env = free_env(sp->filter_env);
	  if (sap->filter) sp->filter_env = copy_env(sap->filter);
	  sp->recording = old_rec;
	}
    }
}



/* ---------------- SAVE and RESTORE buttons ----------------*/

typedef struct {
  int amp,srate,contrast,expand,revscl,revlen;
  env *filter;
  int expand_on,contrast_on,reverb_on,filter_on,direction,filter_order;
} ctrl_state;

void free_controls(snd_info *sp)
{
  ctrl_state *cs;
  cs = (ctrl_state *)(sp->initial_controls);
  if (cs)
    {
      if (cs->filter) free_env(cs->filter);
      free(cs);
      sp->initial_controls = NULL;
    }
}

void save_control_state(snd_info *sp) 
{
  ctrl_state *cs;
  cs = (ctrl_state *)(sp->initial_controls);
  if (!cs)
    {
      sp->initial_controls = (ctrl_state *)calloc(1,sizeof(ctrl_state));
      cs = (ctrl_state *)(sp->initial_controls);
    }
  cs->amp = get_raw_value(snd_widget(sp,W_snd_amp));
  cs->srate = get_raw_value(snd_widget(sp,W_snd_srate));
  cs->expand = get_raw_value(snd_widget(sp,W_snd_expand));
  cs->revscl = get_raw_value(snd_widget(sp,W_snd_revscl));
  cs->revlen = get_raw_value(snd_widget(sp,W_snd_revlen));
  cs->contrast = get_raw_value(snd_widget(sp,W_snd_contrast));
  cs->expand_on = sp->expanding;
  cs->reverb_on = sp->reverbing;
  cs->contrast_on = sp->contrasting;
  cs->filter_on = sp->filtering;
  cs->filter_order = sp->filter_order;
  if (sp->filter_env) cs->filter = copy_env(sp->filter_env);
  if (sp->play_direction == 1) cs->direction = 0; else cs->direction = 1;
}

void restore_control_state(snd_info *sp) 
{
  ctrl_state *cs;
  cs = (ctrl_state *)(sp->initial_controls);
  if (!cs) 
    {
      sp->initial_controls = (ctrl_state *)calloc(1,sizeof(ctrl_state));
      cs = (ctrl_state *)(sp->initial_controls);
      cs->amp = SCROLLBAR_MID;
      cs->srate = 450;
      cs->expand = 450;
      cs->revscl = 0;
      cs->revlen = 20;
      cs->contrast = 0;
      cs->expand_on = 0;
      cs->reverb_on = 0;
      cs->contrast_on = 0;
      cs->filter_on = 0;
      cs->filter_order = 2;
      cs->filter = NULL;
      cs->direction = 0; /* 0 = forward, 1 = backward (this is the button's view) */
    }
  toggle_expand_button(sp,cs->expand_on);
  toggle_contrast_button(sp,cs->contrast_on);
  toggle_reverb_button(sp,cs->reverb_on);
  toggle_filter_button(sp,cs->filter_on);
  toggle_direction_arrow(sp,cs->direction);
  replay_amp_change(sp,cs->amp);
  replay_speed_change(sp,cs->srate);
  replay_contrast_change(sp,cs->contrast);
  replay_reverb_change(sp,cs->revscl);
  replay_expand_change(sp,cs->expand);
  replay_revlen_change(sp,cs->revlen);
  if (sp->filter_env) free_env(sp->filter_env);
  sp->filter_env = copy_env(cs->filter);
  sp->filter_order = cs->filter_order;
}




/* ---------------- APPLY ---------------- */

static void initialize_queues(snd_info *sp)
{
  apply_info *sap;
  sap = (apply_info *)(sp->actions);
  if (sap)
    {
      restart_actions(sap);
      if ((sap->amp) && (sap->amp_max > 0))  snd_amp_changed(sp,sap->amp_init);
      if ((sp->contrasting) && (sap->contrast) && (sap->contrast_max > 0)) snd_contrast_changed(sp,sap->contrast_init);
      if ((sp->expanding) && (sap->expand) && (sap->expand_max > 0)) snd_expand_changed(sp,sap->expand_init);
      if ((sp->reverbing) && (sap->reverb) && (sap->reverb_max > 0)) snd_revscl_changed(sp,sap->reverb_init);
      if ((sap->speed) && (sap->speed_max > 0)) snd_srate_changed(sp,sap->speed_init);
      if ((sap->direction) && (sap->direction_max > 0)) toggle_direction_arrow(sp,sap->direction_init);
      sp->filter_order = sap->filter_order;
      if (sp->filter_env) sp->filter_env = free_env(sp->filter_env);
      if (sap->filter) sp->filter_env = copy_env(sap->filter);
    }
}

static void finalize_queues(snd_info *sp)
{
  apply_info *sap;
  sap = (apply_info *)(sp->actions);
  if (sap) restart_actions(sap);  /* not really needed */
}

static void run_queues(snd_info *sp, int samp)
{
  apply_info *sap;
  action **amps,**speeds,**contrasts,**expands,**reverbs,**directions;
  float srscl;
  srscl = snd_SRATE(sp)/1000.0;
  sap = (apply_info *)(sp->actions);
  if (sap)
    {
      if ((amps = sap->amp) && (sap->amp_max > 0) && (sap->amp_ctr <= sap->amp_max))
	{
	  if (samp >= (srscl*(amps[sap->amp_ctr]->time)))
	    {
	      snd_amp_changed(sp,amps[sap->amp_ctr]->ui_val);
	      sap->amp_ctr++;
	    }
	}
      if ((sp->contrasting) && (contrasts = sap->contrast) && (sap->contrast_max > 0) && (sap->contrast_ctr <= sap->contrast_max))
	{
	  if (samp >= (srscl*(contrasts[sap->contrast_ctr]->time)))
	    {
	      snd_contrast_changed(sp,contrasts[sap->contrast_ctr]->ui_val);
	      sap->contrast_ctr++;
	    }
	}
      if ((sp->expanding) && (expands = sap->expand) && (sap->expand_max > 0) && (sap->expand_ctr <= sap->expand_max))
	{
	  if (samp >= (srscl*(expands[sap->expand_ctr]->time)))
	    {
	      snd_expand_changed(sp,expands[sap->expand_ctr]->ui_val);
	      sap->expand_ctr++;
	    }
	}
      if ((sp->reverbing) && (reverbs = sap->reverb) && (sap->reverb_max > 0) && (sap->reverb_ctr <= sap->reverb_max))
	{
	  if (samp >= (srscl*(reverbs[sap->reverb_ctr]->time)))
	    {
	      snd_revscl_changed(sp,reverbs[sap->reverb_ctr]->ui_val);
	      sap->reverb_ctr++;
	    }
	}
      if ((speeds = sap->speed) && (sap->speed_max > 0) && (sap->speed_ctr <= sap->speed_max))
	{
	  if (samp >= (srscl*(speeds[sap->speed_ctr]->time)))
	    {
	      snd_srate_changed(sp,speeds[sap->speed_ctr]->ui_val);
	      sap->speed_ctr++;
	    }
	}
      if ((directions = sap->direction) && (sap->direction_max > 0) && (sap->direction_ctr <= sap->direction_max))
	{
	  if (samp >= (srscl*(directions[sap->direction_ctr]->time)))
	    {
	      toggle_direction_arrow(sp,directions[sap->direction_ctr]->ui_val);
	      sap->direction_ctr++;
	    }
	}
    }
}

void stop_applying(snd_info *sp)
{
  /* called if user unset the apply button during the apply process */
  sp->apply_ok = 0;
}

typedef struct {
  int slice;
  snd_info *sp;
  int i;
  int ofd;
  char *ofile;
  ctrl_state *cs;
  file_info *hdr;
} apply_manager;

void *make_apply_state(void *xp)
{
  /* set up initial state for apply_controls */
  apply_manager *ap;
  ap = (apply_manager *)calloc(1,sizeof(apply_manager));
  ap->slice = 0;
  ap->sp = (snd_info *)xp;
  return((void *)ap);
}

int apply_controls(void *xp)
{
  /* a background procedure so that it's interruptible */
  apply_manager *ap = (apply_manager *)xp;
  snd_state *ss;
  snd_context *sgx;
  snd_info *sp;
  int i,len;
  sp = ap->sp;
  switch (ap->slice)
    {
    case 0: 
      ap->ofile = NULL;
      ss = sp->state;
      lock_apply(ss,sp);
      lock_play_and_record(ss,sp);
      finish_keyboard_selection();
      deactivate_selection(); /* any edit will change the data within the selection highlight region */
      report_in_minibuffer(sp,snd_string_applying);
      ap->cs = (ctrl_state *)(sp->initial_controls);
      sp->initial_controls = NULL;
      save_control_state(sp);
      ap->ofile = tempnam(ss->temp_dir,"snd_");
      ap->hdr = make_temp_header(ap->ofile,sp->hdr,0); /* assumes NeXT 16-bit */
      ap->ofd = open_temp_file(ap->ofile,sp->nchans,ap->hdr,ss);
      sp->apply_ok = 1;
      initialize_queues(sp);
      initialize_apply(sp);
      ap->i = 0;
      ap->slice++;
      return(FALSE);
      break;

    case 1:
      if (!(sp->apply_ok))
	ap->slice++;
      else
	{
	  len = run_apply(sp,ap->ofd); /* returns samps in chan (dac_buffer_size/chans) */
	  if (len > 0)
	    {
	      run_queues(sp,ap->i);
	      ap->i += len;
	    }
	  else 
	    {
	      ap->slice++;
	      if (len < 0) ap->i -= len;
	    }
	}
      return(FALSE);
      break;

    case 2:
      finalize_apply(sp);
      finalize_queues(sp);
      close_temp_file(ap->ofd,ap->hdr,ap->i*sp->nchans*2,sp); /* 2 because we're using dac procs to write sound = 16-bit samps */
      if (sp->apply_ok)
	{
	  for (i=0;i<sp->nchans;i++)
	    {
	      file_override_samples(ap->i,ap->ofile,sp->chans[i],i,(i == 0) ? DELETE_ME : DONT_DELETE_ME,LOCK_MIXES);
	    }
	  report_in_minibuffer(sp,"");
	  set_apply_button(sp,0);
	  sp->apply_ok = 0;
	}
      else
	{
	  remove(ap->ofile);
	  report_in_minibuffer(sp,snd_string_apply_flushed);
	}
      free(ap->ofile);                                              /* safe only if tempnam, not tmpnam used */
      ap->ofile=NULL;
      restore_control_state(sp);
      free_controls(sp);
      sp->initial_controls = ap->cs;
      sp->applying = 0;
      sgx = sp->sgx;
      if (sgx->apply_in_progress) sgx->apply_in_progress = 0;
      free(ap);
      unlock_apply(sp->state,sp);
      unlock_play_and_record(sp->state,sp);
      return(TRUE);
      break;
    }
  return(TRUE);
}
