#include "snd.h"

/* -------------------------------- EDIT LISTS -------------------------------- *
 *
 * each channel has a list of lists containing the current edit history and the associated sound temp files or buffers
 * undo: back up current list position
 * redo: push position foward
 * No actual changes are flushed out to the file system until the file is saved, so this implements unlimited undo.
 *
 * the three editing possibilities are insert, change, delete.  All input goes through these lists.
 */

static char edit_buf[128];

/* each block in an edit-list list describes one fragment of the current sound */
#define ED_OUT 0
#define ED_SND 1
#define ED_BEG 2
#define ED_END 3
#define ED_SIZE 4
#define EDIT_LIST_END_MARK -2
#define EDIT_ALLOC_SIZE 128

/* ED_SND is either an index into the cp->sounds array (snd_data structs) or EDIT_LIST_END_MARK */
/* ED_BEG and ED_END are indices into the associated data => start/end point of data used in current segment */
/* ED_OUT is running segment location within current overall edited data */
/* EDIT_ALLOC_SIZE is the allocation amount (pointers) each time cp->sounds is (re)allocated */

#define INSERTION_EDIT 0
#define DELETION_EDIT 1
#define CHANGE_EDIT 2
#define INITIALIZE_EDIT 3
#define PACK_EDIT(a,b) ((a)<<16 | (b))
#define EDIT_TYPE(a) (((a) >> 16) & 0xffff)
#define EDIT_LOCATION(a) ((a) & 0xffff)
/* edit history decoding info */

/* color could be added at this level ED_COLOR 4, ED_SIZE 5, then sf->cb[ED_COLOR] during edit tree read */

static char *edit_names[4] = {"insert","delete","set",""};

static void display_ed_list(int i, ed_list *ed)
{
  int len,j,type;
  len=ed->size; /* number of fragments in this list */
  type = EDIT_TYPE(ed->sfnum);
  switch (type)
    {
    case INSERTION_EDIT: fprintf(stderr,"\n (insert %d %d) ",ed->beg,ed->len); break;
    case DELETION_EDIT: fprintf(stderr,"\n (delete %d %d) ",ed->beg,ed->len); break;
    case CHANGE_EDIT: fprintf(stderr,"\n (set %d %d) ",ed->beg,ed->len); break;
    case INITIALIZE_EDIT: fprintf(stderr,"\n (begin) "); break;
    }
  if (ed->origin) fprintf(stderr,"; %s ",ed->origin);
  fprintf(stderr,"[%d:%d]:",i,len);
  for (j=0;j<len;j++)
    {
      fprintf(stderr,"\n   (at %d, cp->sounds[%d][%d:%d])",
	      ed->fragments[j*ED_SIZE+ED_OUT],
	      ed->fragments[j*ED_SIZE+ED_SND],
	      ed->fragments[j*ED_SIZE+ED_BEG],
	      ed->fragments[j*ED_SIZE+ED_END]);
    }
}

static char edbuf[128];

char *edit_to_string(ed_list *ed)
{
  /* only for edit list in snd-xchn.c */
  sprintf(edbuf,"%s : (%s %d %d)",ed->origin,edit_names[EDIT_TYPE(ed->sfnum)],ed->beg,ed->len);
  return(edbuf);
}

static void display_edits(chan_info *cp)
{
  int eds,i;
  ed_list *ed;
  eds = cp->edit_ctr;
  fprintf(stderr,"\nEDITS: %d\n",eds);
  for (i=0;i<=eds;i++)
    {
      ed = cp->edits[i];
      if (!ed) {fprintf(stderr,"ctr is %d, but [%d] is nil!",eds,i); abort();}
      display_ed_list(i,ed);
    }
}

static void edit_data_to_file(FILE *fd, ed_list *ed, chan_info *cp)
{
  snd_state *ss;
  snd_data *sf;
  int i,snd;
  snd = EDIT_LOCATION(ed->sfnum);
  if (snd < cp->sound_size)
    {
      sf = cp->sounds[snd];
      if (sf->type == SND_DATA_BUFFER)
	{
	  for (i=0;i<ed->len;i++) 
	    fprintf(fd,"%d ",sf->data[i]);
	}
      else
	{
	  /* read at very low level */
	  int ifd,idataloc,bufnum,n,loc,cursamples,samples,sample;
	  int *buffer;
	  int **ibufs;
	  ss = cp->state;
	  ifd = clm_open_read(sf->filename);
	  open_clm_file_descriptors(ifd,sound_data_format(sf->filename),sound_datum_size(sf->filename),sound_data_location(sf->filename));
	  idataloc = sound_data_location(sf->filename);
	  samples = sound_samples(sf->filename);
	  loc = clm_seek(ifd,idataloc,0);
	  ibufs = (int **)calloc(1,sizeof(int *));
	  ibufs[0] = (int *)snd_calloc(ss,FILE_BUFFER_SIZE,sizeof(int));
	  bufnum = (FILE_BUFFER_SIZE);
	  sample = 0;
	  for (n=0;n<samples;n+=bufnum)
	    {
	      if ((n+bufnum)<samples) cursamples = bufnum; else cursamples = (samples-n);
	      clm_read(ifd,0,cursamples-1,1,ibufs);
	      buffer = (int *)(ibufs[0]);
	      for (i=0;i<cursamples;i++) 
		{
		  fprintf(fd,"%d ",buffer[i]);
		  sample++;
		  if (sample == ed->len)
		    {
		      clm_close(ifd);
		      return;
		    }
		}
	    }
	  clm_close(ifd);
	}
    }
}

void edit_history_to_file(FILE *fd, chan_info *cp)
{
  /* write edit list as a snd-guile program to fd (open for writing) for subsequent load */
  /* this needs only 3 operations: delete-samples, insert-samples, and change-samples */
  /*   the actual user-operations that produced these are included as comments */
  /*   the data is included as a vector, so the file can be very large! */
  /*   the entire current list is written, then the edit_ctr is fixed up to reflect its current state */
  int i,edits;
  ed_list *ed;
  edits = cp->edit_ctr;
  while ((edits<(cp->edit_size-1)) && (cp->edits[edits+1])) edits++;
  /* 0 case = open-sound */
  for (i=1;i<=edits;i++)
    {
      ed = cp->edits[i];
      if (ed)
	{
	  switch (EDIT_TYPE(ed->sfnum))
	    {
	    case INSERTION_EDIT: 
	      /* samp data snd chn */
	      fprintf(fd,"      (insert-int-samples %d %d \"%s\" #(",ed->beg,ed->len,(ed->origin) ? ed->origin : "");
	      edit_data_to_file(fd,ed,cp);
	      fprintf(fd,") sfile %d)\n",cp->chan);
	      break;
	    case DELETION_EDIT:
	      /* samp samps snd chn */
	      fprintf(fd,"      (delete-int-samples %d %d \"%s\" sfile %d)\n",ed->beg,ed->len,(ed->origin) ? ed->origin : "",cp->chan);
	      break;
	    case CHANGE_EDIT:
	      fprintf(fd,"      (set-int-samples %d %d \"%s\" #(",ed->beg,ed->len,(ed->origin) ? ed->origin : "");
	      edit_data_to_file(fd,ed,cp);
	      fprintf(fd,") sfile %d)\n",cp->chan);
	      break;
	    }
	}
    }
  if (cp->edit_ctr < edits) fprintf(fd,"      (undo %d sfile %d)\n",edits-cp->edit_ctr,cp->chan);
  save_mark_list(fd,cp);
}

static void copy_ed_blocks(int *new_list, int *old_list, int new_beg, int old_beg, int num_lists)
{
  /* beg and num_lists here are in terms of blocks, not ints */
  int i,end,k;
  if (num_lists > 0)
    {
      end = (old_beg+num_lists)*ED_SIZE;
      for (k=new_beg*ED_SIZE,i=old_beg*ED_SIZE;i<end;i++,k++) {new_list[k] = old_list[i];}
    }
}

static ed_list *make_ed_list(int size)
{
  ed_list *ed;
  ed=(ed_list *)calloc(1,sizeof(ed_list));
  ed->size = size;
  ed->fragments = (int *)calloc(size*ED_SIZE,sizeof(int));
  ed->origin = NULL;
  return(ed);
}

static ed_list *free_ed_list(ed_list *ed)
{
  if (ed)
    {
      if (ed->fragments) free(ed->fragments);
      if (ed->origin) free(ed->origin);
      free(ed);
    }
  return(NULL);
}

void free_edit_list(chan_info *cp)
{
  int i;
  if (cp)
    {
      if (cp->edits)
	{
	  for (i=0;i<cp->edit_size;i++)
	    {
	      /* cp->edit_ctr follows current edit state (redo/undo) */
	      if (cp->edits[i]) free_ed_list(cp->edits[i]);
	    }
	  free(cp->edits);
	}
      cp->edits = NULL;
      cp->edit_ctr = -1;
      cp->edit_size = 0;
    }
}

ed_list *initial_ed_list(int beg, int end)
{
  ed_list *ed;
  ed = make_ed_list(2);
  ed->beg = beg;
  ed->len = end+1;
  ed->sfnum = PACK_EDIT(INITIALIZE_EDIT,0);
  /* origin (channel %s %d) desc channel should be obvious from context */
  ed->fragments[0*ED_SIZE + ED_BEG] = beg;
  ed->fragments[0*ED_SIZE + ED_END] = end;
  ed->fragments[0*ED_SIZE + ED_SND] = 0;
  ed->fragments[0*ED_SIZE + ED_OUT] = 0;
  /* second block is our end-of-tree marker */
  ed->fragments[1*ED_SIZE + ED_BEG] = 0;
  ed->fragments[1*ED_SIZE + ED_END] = 0;
  ed->fragments[1*ED_SIZE + ED_SND] = EDIT_LIST_END_MARK;
  ed->fragments[1*ED_SIZE + ED_OUT] = end+1;
  return(ed);
}

static int find_split_loc (chan_info *cp, int samp, ed_list *current_state)
{
  int i,k;
  if (!current_state) {fprintf(stderr,"current state missing!"); abort();}
  for (i=0,k=0;i<current_state->size;i++,k+=ED_SIZE)
    {
      if (current_state->fragments[k+ED_OUT] >= samp) return(i);
    }
  fprintf(stderr,"find failed at %d!",samp); 
  fprintf(stderr,"looking at: ");
  display_ed_list(-1,current_state);
  fprintf(stderr,"\n in: \n");
  display_edits(cp);
  abort();
}

snd_data *make_snd_data_file(char *name, int *io, int *data, file_info *hdr, int temp, int ctr, int temp_chan)
{
  snd_data *sf;
  sf = (snd_data *)calloc(1,sizeof(snd_data));
  sf->type = SND_DATA_FILE;
  sf->data = data;
  sf->io = io;
  sf->filename = (char *)calloc(strlen(name)+1,sizeof(char));
  strcpy(sf->filename,name);
  sf->hdr = hdr;
  sf->temporary = temp;
  sf->edit_ctr = ctr;
  sf->open = FD_OPEN;
  sf->inuse = FALSE;
  sf->copy = FALSE;
  sf->chan = temp_chan;
  return(sf);
}

static snd_data *copy_snd_data(snd_data *sd, chan_info *cp)
{
  /* see note under init_sample_read */
  snd_data *sf;
  int *datai;
  int fd;
  file_info *hdr;
  hdr = sd->hdr;
  fd = snd_open_read(cp->state,sd->filename);
  open_clm_file_descriptors(fd,hdr->format,c_snd_datum_size(hdr->format),hdr->data_location);
  datai = make_file_state(fd,hdr,io_in_f,sd->chan,MIX_FILE_BUFFER_SIZE,cp->state);
  sf = (snd_data *)calloc(1,sizeof(snd_data));
  sf->type = sd->type;
#if LONG_INT_P
  sf->data = delist_ptr(datai[io_dats+6+sd->chan]);
#else
  sf->data = (int *)(datai[io_dats+6+sd->chan]);
#endif
  sf->io = datai;
  sf->filename = (char *)calloc(strlen(sd->filename)+1,sizeof(char));
  strcpy(sf->filename,sd->filename);
  sf->hdr = hdr;
  sf->temporary = 0;
  sf->edit_ctr = sd->edit_ctr;
  sf->open = FD_OPEN;
  sf->inuse = FALSE;
  sf->copy = TRUE;
  return(sf);
}

snd_data *make_snd_data_buffer(int *data, int len, int ctr)
{
  snd_data *sf;
  int i;
  sf = (snd_data *)calloc(1,sizeof(snd_data));
  sf->type = SND_DATA_BUFFER;
  sf->data = (int *)calloc(len,sizeof(int));
  for (i=0;i<len;i++) sf->data[i] = data[i];
  sf->edit_ctr = ctr;
  sf->copy = FALSE;
  sf->inuse = FALSE;
  return(sf);
}

static snd_data *free_snd_data(snd_data *sf)
{
  /* in the snd file case, these pointers are dealt with elsewhere */
  if (sf)
    {
      if ((sf->type == SND_DATA_BUFFER) && (sf->data)) free(sf->data);
      sf->data = NULL;
      if ((!(sf->copy)) && (sf->hdr)) free_file_info(sf->hdr);
      sf->hdr = NULL;
      if (sf->io)
	{
	  if (sf->open == FD_OPEN) snd_close(sf->io[io_fd]);
	  sf->io = free_file_state(sf->io);
	  if (sf->temporary == DELETE_ME) remove(sf->filename);
	  free(sf->filename);
	}
      free(sf);
    }
  return(NULL);
}

void free_sound_list (chan_info *cp)
{
  int i;
  if (cp)
    {
      if (cp->sounds)
	{
	  for (i=0;i<cp->sound_size;i++)
	    {
	      if (cp->sounds[i]) cp->sounds[i] = free_snd_data(cp->sounds[i]);
	    }
	  free(cp->sounds);
	  cp->sounds = NULL;
	}
      cp->sound_ctr = -1;
      cp->sound_size = 0;
    }
}

static void release_pending_sounds(chan_info *cp, int edit_ctr)
{
  /* look for buffers or open temp files that are no longer reachable after pruning the edit tree */
  int i,j,del;
  snd_data *sf;
  if (cp)
    {
      if (cp->sounds)
	{
	  del= -1;
	  for (i=0;i<cp->sound_size;i++)
	    {
	      if ((sf = (cp->sounds[i])))
		{
		  if (sf->edit_ctr >= edit_ctr)
		    {
		      cp->sounds[i] = free_snd_data(sf);
		      if (del == -1) del = i;
		    }
		}
	    }
	  if (del != -1)
	    {
	      for (j=del,i=del;i<cp->sound_size;i++)
		{
		  if (cp->sounds[i]) 
		    {
		      if (j != i) 
			{
			  cp->sounds[j] = cp->sounds[i];
			  cp->sounds[i] = NULL;
			  j++;
			}
		    }
		}
	      cp->sound_ctr = j-1;
	    }
	}
    }
}
  
static void prepare_sound_list (chan_info *cp)
{
  int i;
  cp->sound_ctr++;
  if (cp->sound_ctr >= cp->sound_size)
    {
      cp->sound_size += EDIT_ALLOC_SIZE;
      cp->sounds = (snd_data **)realloc(cp->sounds,cp->sound_size * sizeof(snd_data *));
      for (i=cp->sound_ctr;i<cp->sound_size;i++) cp->sounds[i] = NULL;
    }
  if (cp->sounds[cp->sound_ctr]) cp->sounds[cp->sound_ctr] = free_snd_data(cp->sounds[cp->sound_ctr]);
}

static int add_sound_buffer_to_edit_list(chan_info *cp, int *data, int len)
{
  prepare_sound_list(cp);
  cp->sounds[cp->sound_ctr] = make_snd_data_buffer(data,len,cp->edit_ctr);
  return(cp->sound_ctr);
}

static int add_sound_file_to_edit_list(chan_info *cp, char *name, int *io, int *data, file_info *hdr, int temp, int chan)
{
  prepare_sound_list(cp);
  cp->sounds[cp->sound_ctr] = make_snd_data_file(name,io,data,hdr,temp,cp->edit_ctr,chan);
  return(cp->sound_ctr);
}

static void ripple_out(int *list,int beg,int num, int len)
{
  int i,k;
  for (i=beg,k=beg*ED_SIZE;i<len;i++,k+=ED_SIZE) list[k+ED_OUT] += num;
}

static void prepare_edit_list(chan_info *cp, int len)
{
  int i;
  snd_info *sp;
  sp = cp->sound;
  if ((sp) && (sp->playing)) stop_playing(sp->playing);
  cp->edit_ctr++;
  if (cp->edit_ctr >= cp->edit_size)
    {
      cp->edit_size += EDIT_ALLOC_SIZE;
      if (!cp->edits) cp->edits = (ed_list **)calloc(cp->edit_size,sizeof(ed_list *));
      else cp->edits = (ed_list **)realloc(cp->edits,cp->edit_size*sizeof(ed_list *));
      for (i=cp->edit_ctr;i<cp->edit_size;i++) cp->edits[i] = NULL;
      if (!cp->samples) cp->samples = (int *)calloc(cp->edit_size,sizeof(int));
      else cp->samples = (int *)realloc(cp->samples,cp->edit_size*sizeof(int));
    }
  if (cp->edits[cp->edit_ctr]) 
    {
      for (i=cp->edit_ctr;i<cp->edit_size;i++) cp->edits[i] = free_ed_list(cp->edits[i]);
      release_pending_marks(cp,cp->edit_ctr);
      release_pending_mixes(cp,cp->edit_ctr);
      release_pending_sounds(cp,cp->edit_ctr);
      reflect_no_more_redo_in_menu();
    }
  reflect_undo_ok_in_menu();
  cp->samples[cp->edit_ctr] = len;
}

int current_ed_samples(chan_info *cp)
{
  if (cp) 
    return(cp->samples[cp->edit_ctr]);
  else return(0);
}

static void reflect_sample_change_in_axis(chan_info *cp)
{
  axis_info *ap;
  int samps;
  ap = cp->axis;
  if (ap)
    {
      samps = current_ed_samples(cp);
      ap->xmax = (double)samps/(double)snd_SRATE(cp);
      if (ap->x1 > ap->xmax) ap->x1 = ap->xmax;
      if ((samps == 0) || (ap->no_data))
	{
	  ap->no_data = (samps == 0);
	  if (ap->xlabel) free(ap->xlabel);
	  if (samps == 0) ap->xlabel = copy_string(snd_string_no_data); else ap->xlabel = copy_string(snd_string_time);
	}
      set_x_bounds(ap);
    }
  clobber_amp_env(cp);
}

static ed_list *insert_samples_1 (int samp, int num, int* vals, ed_list *current_state, chan_info *cp, int **cb_back, char *origin)
{
  int len,k,old_beg,old_end,old_snd,old_out;
  ed_list *new_state;
  int *cb,*cbback,*ed;
  len = current_state->size;
  ed = current_state->fragments;
  k=find_split_loc(cp,samp,current_state);
  cb = (int *)(ed + k*ED_SIZE);
  if ((samp == cb[ED_OUT]) || (samp == (cb[ED_OUT] - 1)))
    {
      new_state = make_ed_list(len+1);
      copy_ed_blocks(new_state->fragments,ed,0,0,k);
      copy_ed_blocks(new_state->fragments,ed,k+1,k,len-k);
      len++;
    }
  else
    {
      cbback = (int *)(ed + (k-1)*ED_SIZE);
      old_beg = cbback[ED_BEG];
      old_end = cbback[ED_END];
      old_snd = cbback[ED_SND];
      old_out = cbback[ED_OUT];
      new_state = make_ed_list(len+2);
      copy_ed_blocks(new_state->fragments,ed,0,0,k);
      copy_ed_blocks(new_state->fragments,ed,k+2,k,len-k);
      cb = (int *)(new_state->fragments + (k+1)*ED_SIZE);  /* old after split */
      cbback = (int *)(new_state->fragments + (k-1)*ED_SIZE); /* old before split */
      cb[ED_SND] = old_snd;
      cb[ED_OUT] = samp;
      cb[ED_BEG] = old_beg+samp-old_out;
      cb[ED_END] = old_end;
      cbback[ED_END] = old_beg+samp-old_out-1;
      len += 2;
    }
  cb = (int *)(new_state->fragments + k*ED_SIZE); /* new */
  cb[ED_BEG] = 0;
  cb[ED_END] = num-1;
  cb[ED_OUT] = samp;
  if (vals) 
    {
      cb[ED_SND] = add_sound_buffer_to_edit_list(cp,vals,num); 
      new_state->sfnum = PACK_EDIT(INSERTION_EDIT,cb[ED_SND]);
    }
  else (*cb_back) = cb;
  /* vals is null if data is in a file -- handled later */
  new_state->beg = samp;
  new_state->len = num;
  if (origin) new_state->origin = copy_string(origin);
  ripple_out(new_state->fragments,k+1,num,len);
  ripple_marks(cp,samp,num);
  ripple_mixes(cp,samp,num);
  ripple_selection(cp,samp,num);
  reflect_sample_change_in_axis(cp);
  return(new_state);
}

void file_insert_samples(int beg, int num, char *inserted_file, chan_info *cp, int chan, int auto_delete, char *origin)
{
  int k;
  int *cb;
  int fd;
  int *datai;
  ed_list *ed;
  file_info *hdr;
  k=cp->edit_ctr;
  prepare_edit_list(cp,current_ed_samples(cp)+num);
  cp->edits[cp->edit_ctr] = insert_samples_1(beg,num,NULL,cp->edits[k],cp,&cb,origin);
  reflect_edit_history_change(cp);
  hdr = make_file_info(inserted_file,cp->state);
  if (hdr)
    {
      fd = snd_open_read(cp->state,inserted_file);
      open_clm_file_descriptors(fd,hdr->format,c_snd_datum_size(hdr->format),hdr->data_location);
      datai = make_file_state(fd,hdr,io_in_f,chan,FILE_BUFFER_SIZE,cp->state);
#if LONG_INT_P
      cb[ED_SND] = add_sound_file_to_edit_list(cp,inserted_file,datai,delist_ptr(datai[io_dats+6+chan]),hdr,auto_delete,chan);
#else
      cb[ED_SND] = add_sound_file_to_edit_list(cp,inserted_file,datai,(int *)(datai[io_dats+6+chan]),hdr,auto_delete,chan);
#endif
      ed = cp->edits[cp->edit_ctr];
      ed->sfnum = PACK_EDIT(INSERTION_EDIT,cb[ED_SND]);
      lock_affected_mixes(cp,beg,beg+num);
    }
}

void insert_samples(int beg, int num, int *vals, chan_info *cp, char *origin)
{
  int k,len;
  int *cb;
  len = current_ed_samples(cp);
  if (beg <= len)
    {
      k=cp->edit_ctr;
      prepare_edit_list(cp,len+num);
      cp->edits[cp->edit_ctr] = insert_samples_1(beg,num,vals,cp->edits[k],cp,&cb,origin);
      reflect_edit_history_change(cp);
      lock_affected_mixes(cp,beg,beg+num);
    }
}

static ed_list *delete_samples_1(int beg, int num, ed_list *current_state, chan_info *cp, char *origin)
{
  int len,k,need_to_delete,curbeg,old_out,cbi,start_del,len_fixup;
  int *cb,*temp_cb;
  ed_list *new_state;
  len=current_state->size;
  len_fixup = -1;
  k=find_split_loc(cp,beg,current_state);
  need_to_delete = num;
  start_del = k;
  curbeg = beg;
  cb = (int *)(current_state->fragments + k*ED_SIZE);
  if (cb[ED_OUT]>beg) start_del--;
  new_state = make_ed_list(len+1);
  copy_ed_blocks(new_state->fragments,current_state->fragments,0,0,start_del);
  cbi=start_del;
  temp_cb = (int *)(current_state->fragments + start_del*ED_SIZE);
  old_out = temp_cb[ED_OUT];
  if (beg>old_out)
    {
      cb = (int *)(new_state->fragments + start_del*ED_SIZE);
      cb[ED_OUT] = old_out;
      cb[ED_SND] = temp_cb[ED_SND];
      cb[ED_BEG] = temp_cb[ED_BEG];
      cb[ED_END] = temp_cb[ED_BEG]+beg-old_out-1;
      start_del++;
      len_fixup++;
    }
  while (need_to_delete > 0)
    {
      old_out = (current_state->fragments[(cbi+1)*ED_SIZE+ED_OUT]);
      need_to_delete -= (old_out-curbeg);
      if (need_to_delete > 0)
	{
	  cbi++;
	  curbeg = old_out;
	}
    }
  if (need_to_delete < 0)
    {
      temp_cb = (int *)(current_state->fragments+cbi*ED_SIZE);
      cb = (int *)(new_state->fragments+start_del*ED_SIZE);
      cb[ED_OUT] = beg;
      cb[ED_SND] = temp_cb[ED_SND];
      cb[ED_BEG] = temp_cb[ED_END]+1+need_to_delete;
      cb[ED_END] = temp_cb[ED_END];
      start_del++;
      len_fixup++;
    }
  cbi++;
  copy_ed_blocks(new_state->fragments,current_state->fragments,start_del,cbi,len-cbi); /* ??? */
  new_state->beg = beg;
  new_state->len = num;
  if (origin) new_state->origin = copy_string(origin);
  new_state->sfnum = PACK_EDIT(DELETION_EDIT,0);
  ripple_out(new_state->fragments,start_del,-num,len+len_fixup);
  ripple_marks(cp,beg,-num);
  ripple_mixes(cp,beg,-num);
  ripple_selection(cp,beg,-num);
  reflect_sample_change_in_axis(cp);
  new_state->size = len+len_fixup; /* don't propogate useless trailing blocks */
  return(new_state);
}    

void delete_samples(int beg, int num, chan_info *cp, char *origin)
{
  int k,len;
  len = current_ed_samples(cp);
  if (beg < len)
    {
      if ((beg+num) > len) num = len-beg;
      k=cp->edit_ctr;
      prepare_edit_list(cp,len-num);
      cp->edits[cp->edit_ctr] = delete_samples_1(beg,num,cp->edits[k],cp,origin);
      reflect_edit_history_change(cp);
      lock_affected_mixes(cp,beg,beg+num);
    }
}

/* TODO: if beg beyond end of current state, prepend 0's to fill out to beg */

static ed_list *change_samples_1(int beg, int num, int *vals, ed_list *current_state, chan_info *cp, int **cb_back, int lengthen, char *origin)
{
  /* changed 18-June-97 to incorporate lengthened file possibility */
  int len,k,start_del,cbi,curbeg,len_fixup,need_to_delete,old_out;
  ed_list *new_state;
  int *cb,*temp_cb;
  len = current_state->size;
  len_fixup = -1;
  k=find_split_loc(cp,beg,current_state);
  need_to_delete = num - lengthen;
  start_del = k;
  curbeg = beg;
  cbi=0;
  cb = (int *)(current_state->fragments + k*ED_SIZE);
  if (cb[ED_OUT]>beg) start_del--;
  new_state = make_ed_list(len+2);
  copy_ed_blocks(new_state->fragments,current_state->fragments,0,0,start_del);
  cbi=start_del;
  temp_cb = (int *)(current_state->fragments + start_del*ED_SIZE);  
  old_out = temp_cb[ED_OUT];
  if (beg>old_out)
    {
      cb = (int *)(new_state->fragments + start_del*ED_SIZE);
      cb[ED_OUT] = old_out;
      cb[ED_SND] = temp_cb[ED_SND];
      cb[ED_BEG] = temp_cb[ED_BEG];
      cb[ED_END] = temp_cb[ED_BEG]+beg-old_out-1;
      start_del++;
      len_fixup++;
    }
  while (need_to_delete > 0)
    {
      old_out = (current_state->fragments[(cbi+1)*ED_SIZE+ED_OUT]);
      need_to_delete -= (old_out-curbeg);
      if (need_to_delete > 0)
	{
	  cbi++;
	  curbeg = old_out;
	}
    }
  cb = (int *)(new_state->fragments+start_del*ED_SIZE);
  if (vals) 
    {
      cb[ED_SND] = add_sound_buffer_to_edit_list(cp,vals,num); 
      new_state->sfnum = PACK_EDIT(CHANGE_EDIT,cb[ED_SND]);
    }
  else (*cb_back) = cb;
  cb[ED_OUT] = beg;
  cb[ED_BEG] = 0;
  cb[ED_END] = num-1;
  start_del++;
  len_fixup++;
  if (need_to_delete < 0)
    {
      temp_cb = (int *)(current_state->fragments+cbi*ED_SIZE);
      cb = (int *)(new_state->fragments+start_del*ED_SIZE);
      cb[ED_OUT] = beg+num;
      cb[ED_SND] = temp_cb[ED_SND];
      cb[ED_BEG] = temp_cb[ED_END]+1+need_to_delete;
      cb[ED_END] = temp_cb[ED_END];
      start_del++;
      len_fixup++;
    }
  cbi++;
  copy_ed_blocks(new_state->fragments,current_state->fragments,start_del,cbi,len-cbi);
  new_state->beg = beg;
  new_state->len = num;
  if (origin) new_state->origin = copy_string(origin);
  if (lengthen)
    {
      ripple_out(new_state->fragments,k+1,lengthen,len+len_fixup);
      reflect_sample_change_in_axis(cp);
    }
  else clobber_amp_env(cp);
  new_state->size = len+len_fixup; /* don't propogate useless trailing blocks */
  ripple_marks(cp,0,0);
  return(new_state);
}    

void file_change_samples(int beg, int num, char *tempfile, chan_info *cp, int chan, int auto_delete, int lock, char *origin)
{
  int k,prev_len,new_len;
  int *cb;
  int fd;
  int *datai;
  ed_list *ed;
  file_info *hdr;
  prev_len = current_ed_samples(cp);
  new_len = beg+num-1;
  if (new_len < prev_len) new_len = prev_len;
  k=cp->edit_ctr;
  prepare_edit_list(cp,new_len);
  cp->edits[cp->edit_ctr] = change_samples_1(beg,num,NULL,cp->edits[k],cp,&cb,new_len - prev_len,origin);
  reflect_edit_history_change(cp);
  if (lock == LOCK_MIXES) lock_affected_mixes(cp,beg,beg+num);
  hdr = make_file_info(tempfile,cp->state);
  if (hdr)
    {
      fd = snd_open_read(cp->state,tempfile);
      open_clm_file_descriptors(fd,hdr->format,c_snd_datum_size(hdr->format),hdr->data_location);
      datai = make_file_state(fd,hdr,io_in_f,chan,FILE_BUFFER_SIZE,cp->state);
#if LONG_INT_P
      cb[ED_SND] = add_sound_file_to_edit_list(cp,tempfile,datai,delist_ptr(datai[io_dats+6+chan]),hdr,auto_delete,chan);
#else
      cb[ED_SND] = add_sound_file_to_edit_list(cp,tempfile,datai,(int *)(datai[io_dats+6+chan]),hdr,auto_delete,chan);
#endif
      ed = cp->edits[cp->edit_ctr];
      ed->sfnum = PACK_EDIT(CHANGE_EDIT,cb[ED_SND]);
    }
}

void file_override_samples(int num, char *tempfile, chan_info *cp, int chan, int auto_delete, int lock, char *origin)
{
  int fd;
  ed_list *e;
  int *datai;
  file_info *hdr;
  snd_state *ss;
  ss = cp->state;
  prepare_edit_list(cp,num);
  hdr = make_file_info(tempfile,ss);
  if (hdr)
    {
      if (num == -1) num = (hdr->samples/hdr->chans);
      fd = snd_open_read(ss,tempfile);
      open_clm_file_descriptors(fd,hdr->format,c_snd_datum_size(hdr->format),hdr->data_location);
      datai = make_file_state(fd,hdr,io_in_f,chan,FILE_BUFFER_SIZE,cp->state);
      e = initial_ed_list(0,num-1);
      if (origin) e->origin = copy_string(origin);
      cp->edits[cp->edit_ctr] = e;
      if (lock == LOCK_MIXES) lock_affected_mixes(cp,0,num);
#if LONG_INT_P
      e->fragments[0 + ED_SND] = add_sound_file_to_edit_list(cp,tempfile,datai,delist_ptr(datai[io_dats+6+chan]),hdr,auto_delete,chan);
#else
      e->fragments[0 + ED_SND] = add_sound_file_to_edit_list(cp,tempfile,datai,(int *)(datai[io_dats+6+chan]),hdr,auto_delete,chan);
#endif
      e->sfnum = PACK_EDIT(CHANGE_EDIT,e->fragments[0 + ED_SND]);
      reflect_edit_history_change(cp);
      reflect_sample_change_in_axis(cp);
      ripple_marks(cp,0,0);
      check_for_first_edit(cp);
      update_graph(cp,NULL);
    }
}

void change_samples(int beg, int num, int *vals, chan_info *cp, int lock, char *origin)
{
  int k,prev_len,new_len;
  prev_len = current_ed_samples(cp);
  new_len = beg+num-1;
  if (new_len < prev_len) new_len = prev_len;
  k=cp->edit_ctr;
  prepare_edit_list(cp,new_len);
  cp->edits[cp->edit_ctr] = change_samples_1(beg,num,vals,cp->edits[k],cp,NULL,new_len - prev_len,origin);
  reflect_edit_history_change(cp);
  if (lock == LOCK_MIXES) lock_affected_mixes(cp,beg,beg+num);
}

int *load_samples(int beg, int num, chan_info *cp)
{
  snd_fd *sf;
  int *data;
  int i;
  data = (int *)snd_calloc(cp->state,num,sizeof(int));
  sf = init_sample_read(beg,cp,READ_FORWARD);
  for (i=0;i<num;i++) {NEXT_SAMPLE(data[i],sf);}
  free_snd_fd(sf);
  return(data);
}

static int snd_file_read(snd_state *ss, snd_data *ur_sd, int index, chan_info *cp)
{
  int val,copied;
  snd_data *sd = NULL;
  copied = 0;
  /* first try to grab the sample without moving any buffers */
  if ((index >= ur_sd->io[io_beg]) && (index <= ur_sd->io[io_end])) 
    return(ur_sd->data[index - ur_sd->io[io_beg]]);
  /* not in current buffer, so create a new reader and go looking for it */
  if (ur_sd->inuse) 
    {
      sd = copy_snd_data(ur_sd,cp); 
      copied = 1;
    } 
  else sd = ur_sd;
  sd->inuse = TRUE;
  if ((index < sd->io[io_beg]) || (index > sd->io[io_end])) 
    snd_file_reset(ss,sd,index);
  val = sd->data[index - sd->io[io_beg]];
  if (copied) 
    {
      sd->inuse = FALSE; 
      free_snd_data(sd);
    }
  return(val); 
}

static int sample_1 (int samp, chan_info *cp)
{ /* slow access */
  ed_list *current_state;
  snd_data *sd;
  int len,i,cb,true_cb,index;
  int *data;
  current_state = cp->edits[cp->edit_ctr];
  data = current_state->fragments;
  len = current_state->size;
  for (i=0,cb=0;i<len;i++,cb+=ED_SIZE)
    {
      if (samp < data[cb+ED_OUT])
	{
	  true_cb = cb-ED_SIZE;
	  if (data[true_cb+ED_SND] == EDIT_LIST_END_MARK) return(0);
	  index = data[true_cb+ED_BEG]+samp-data[true_cb+ED_OUT];
	  sd = cp->sounds[data[true_cb+ED_SND]];
	  if (sd->type == SND_DATA_BUFFER)
	    return(sd->data[index]);
	  else return(snd_file_read(cp->state,sd,index,cp));
	}
    }
  return(0);
}

float sample (int samp, chan_info *cp) {return(clm_sndflt * sample_1(samp,cp));}

/* now for optimized sample access -- since everything goes through these lists, we want the access to be fast */

static snd_fd *make_snd_fd (void)
{
  snd_fd *sf;
  sf=(snd_fd *)calloc(1,sizeof(snd_fd));
  return(sf);
}

snd_fd *free_snd_fd(snd_fd *sf)
{
  snd_data *sd;
  if (sf) 
    {
      if ((sd = (sf->current_sound)))
	{
	  sd->inuse = FALSE;
	  if (sd->copy) free_snd_data(sd);
	}
      free(sf);
    }
  return(NULL);
}

static void massage_snd_file(int ind0, int ind1, int indx, snd_fd *sf, snd_data *cur_snd)
{
  /* need to track in-core buffer and file-relative index */
  if ((indx < cur_snd->io[io_beg]) || (indx > cur_snd->io[io_end])) 
    snd_file_reset((sf->cp)->state,cur_snd,indx);
  sf->data = (int *)(cur_snd->data + indx - cur_snd->io[io_beg]);
  /* only indx is guaranteed to be within the current in-core buffer */

  if (ind0 >= cur_snd->io[io_beg])
    sf->first = (int *)(cur_snd->data + ind0 - cur_snd->io[io_beg]);
  else sf->first = cur_snd->data;

  if (ind1 <= cur_snd->io[io_end]) 
    {
      sf->last = (int *)(cur_snd->data + ind1 - cur_snd->io[io_beg]);
      sf->eof = 1;
    }
  else 
    {
      sf->last = (int *)(cur_snd->data + cur_snd->io[io_bufsiz] -1);
      sf->eof = 0;
    }
  sf->beg = cur_snd->io[io_beg];
  sf->end = cur_snd->io[io_end];
}

static void massage_snd_file_back(int ind0, int ind1, int indx, snd_fd *sf, snd_data *cur_snd)
{
  if ((indx > cur_snd->io[io_end]) || (indx < cur_snd->io[io_beg])) 
    snd_file_reset((sf->cp)->state,cur_snd,indx - cur_snd->io[io_bufsiz] + 1);
  sf->data = (int *)(cur_snd->data + indx - cur_snd->io[io_beg]);

  if (ind1 <= cur_snd->io[io_end])
    sf->last = (int *)(cur_snd->data + ind1 - cur_snd->io[io_beg]);
  else sf->last = (int *)(cur_snd->data + cur_snd->io[io_bufsiz] -1);

  if (ind0 >= cur_snd->io[io_beg]) 
    {
      sf->first = (int *)(cur_snd->data + ind0 - cur_snd->io[io_beg]);
      sf->eof = 1;
    }
  else 
    {
      sf->first = cur_snd->data;
      sf->eof = 0;
    }
  sf->beg = cur_snd->io[io_beg];
  sf->end = cur_snd->io[io_end];
}

#if LONG_INT_P
  int current_location(snd_fd *sf) {return(sf->cb[ED_BEG] + sf->beg + (int)(((long)(sf->data) - (long)(sf->first))>>2));}
#else
  int current_location(snd_fd *sf) {return(sf->cb[ED_BEG] + sf->beg + (((int)(sf->data) - (int)(sf->first))>>2));}
#endif

snd_fd *init_sample_read (int samp, chan_info *cp, int direction)
{
  snd_fd *sf;
  ed_list *ed;
  int len,i,k,ind0,ind1,indx;
  int *cb;
  snd_data *first_snd;
  sf = make_snd_fd();
  sf->direction = 0;
  sf->cp = cp;
  ed = (ed_list *)(cp->edits[cp->edit_ctr]);
  sf->current_state = ed;
  len = ed->size;
  for (i=0,k=0;i<len;i++,k+=ED_SIZE)
    {
      cb = (int *)(ed->fragments+k);
      if ((cb[ED_OUT] > samp) || (cb[ED_SND] == EDIT_LIST_END_MARK))
	{
	  sf->cb = (int *)(ed->fragments+k-ED_SIZE);
	  sf->cbi = i-1;
	  ind0 = sf->cb[ED_BEG];
	  indx = sf->cb[ED_BEG]+samp-sf->cb[ED_OUT];
	  ind1 = sf->cb[ED_END];
	  sf->sounds = (snd_data **)(cp->sounds);
	  first_snd = sf->sounds[sf->cb[ED_SND]];
	  if (first_snd->type == SND_DATA_FILE)
	    {
	      /* since arbitrarily many work procs can be running in parallel, reading the same 
	       * data (edit tree sound file entries), we can't share the clm-style IO buffers since these contain
	       * a local notion of current position which is not accessed on every sample by the
	       * sample readers (they trust their snd_fd indices); we wouldn't want to be
	       * constantly jumping around and re-reading data buffers (in the worst case
	       * many times per sample) anyway, so we copy the IO buffer, allocate a relatively
	       * small data buffer, and then free all the copied snd_data stuff as soon as
	       * the current reader is done.
	       */
	      if (first_snd->inuse) first_snd = copy_snd_data(first_snd,cp);
	      first_snd->inuse = TRUE;
	      sf->current_sound = first_snd;
	      if (direction == READ_FORWARD)
		massage_snd_file(ind0,ind1,indx,sf,first_snd);
	      else massage_snd_file_back(ind0,ind1,indx,sf,first_snd);
	    }
	  else 
	    {
	      sf->current_sound = NULL;
	      sf->data = (int *)(first_snd->data+indx);
	      sf->first = (int *)(first_snd->data+ind0);
	      sf->last = (int *)(first_snd->data+ind1);
	      sf->eof = 1;
	    }
	  sf->current_value = (*sf->data);
	  return(sf);
	}
    }
  display_edits(cp);
  fprintf(stderr,"big trouble"); abort();
  return(NULL);
 }

int previous_sound (snd_fd *sf) 
{
  int ind0,ind1,indx;
  snd_data *prev_snd;
  if (sf->eof)
    {
      if (sf->current_sound) 
	{
	  prev_snd = sf->current_sound; 
	  prev_snd->inuse = FALSE; 
	  sf->current_sound = NULL;
	  if (prev_snd->copy) free_snd_data(prev_snd);
	}
      if (sf->cbi == 0) return(0); /* can't back up any further */
      sf->cbi--;
      /* now start in the final portion of this block (if a file) */
      sf->cb = (int *)((sf->current_state)->fragments+sf->cbi*ED_SIZE);
      ind0 = sf->cb[ED_BEG];
      ind1 = sf->cb[ED_END];
      prev_snd = sf->sounds[sf->cb[ED_SND]];
      if (prev_snd->type == SND_DATA_FILE)
	{
	  if (prev_snd->inuse) prev_snd = copy_snd_data(prev_snd,sf->cp);
	  prev_snd->inuse = TRUE;
	  sf->current_sound = prev_snd;
	  massage_snd_file_back(ind0,ind1,ind1,sf,prev_snd);
	}
      else 
	{
	  sf->data = (int *)(prev_snd->data+ind1);
	  sf->first = (int *)(prev_snd->data+ind0);
	  sf->last = sf->data;
	  sf->eof = 1;
	}
    }
  else
    {
      /* back up in current file */
      ind0 = sf->cb[ED_BEG];
      ind1 = sf->cb[ED_END];
      indx = sf->beg-1;
      massage_snd_file_back(ind0,ind1,indx,sf,sf->current_sound);
    }
  return(sf->current_value = *sf->data--);
}

int next_sound (snd_fd *sf)
{
  int ind0,ind1,indx;
  snd_data *nxt_snd;
  if (sf->eof) /* a convenience -- we could figure this out from various pointers */
    {
      if (sf->current_sound) 
	{
	  nxt_snd = sf->current_sound; 
	  nxt_snd->inuse = FALSE; 
	  sf->current_sound = NULL;
	  if (nxt_snd->copy) free_snd_data(nxt_snd);
	}
      if (sf->last == (int *)0) return(0);
      if (!(sf->cb)) return(0);
      sf->cbi++;
      sf->cb = (int *)((sf->current_state)->fragments+sf->cbi*ED_SIZE);
      if (sf->cb[ED_SND] == EDIT_LIST_END_MARK) 
	{
          sf->data = (int *)1;
	  sf->last = (int *)0; /* can I get away with this?? */
	  return(0);
	}
      ind0 = sf->cb[ED_BEG];
      ind1 = sf->cb[ED_END];
      nxt_snd = sf->sounds[sf->cb[ED_SND]];
      if (nxt_snd->type == SND_DATA_FILE)
	{
	  if (nxt_snd->inuse) nxt_snd = copy_snd_data(nxt_snd,sf->cp);
	  nxt_snd->inuse = TRUE;
	  sf->current_sound = nxt_snd;
	  massage_snd_file(ind0,ind1,ind0,sf,nxt_snd);
	}
      else 
	{
	  sf->data = (int *)(nxt_snd->data+ind0);
	  sf->first = sf->data;
	  sf->last = (int *)(nxt_snd->data+ind1);
	  sf->eof = 1;
	}
    }
  else
    { 
      ind0 = sf->cb[ED_BEG];
      ind1 = sf->cb[ED_END];
      indx = sf->end+1;
      massage_snd_file(ind0,ind1,indx,sf,sf->current_sound);
    }
  return(sf->current_value = *sf->data++);
}

int next_sample_1 (snd_fd *sf)
{
  if (sf->data > sf->last) return(next_sound(sf));
  return(sf->current_value = *sf->data++);
}

int previous_sample_1(snd_fd *sf)
{
  if (sf->data < sf->first) return(previous_sound(sf));
  return(sf->current_value = *sf->data--);
}

float any_sample(snd_fd *sf, int off)
{
  int true_off;
  int *sadr;
  /* for search routines, depends on current_value, direction etc -- not a general function! */
  if (off == 0) return(clm_sndflt * (sf->current_value));
  /* postincrement/decrement assumed here -- that is don't ask for offset value immediately after init_sample_read */
  true_off = off-sf->direction;
  /* now look for current block bounds -- maybe we'll luck out */
  sadr = (int *)(sf->data + true_off);
  if ((sadr >= sf->first) && (sadr <= sf->last))
    return(clm_sndflt * (*sadr));
  return(sample(current_location(sf) + off,sf->cp));
}

int next_sub_sound (snd_fd *sf, int inc)
{
  int i,val,val1;
  NEXT_SAMPLE(val1,sf);
  for (i=0;i<inc-1;i++) NEXT_SAMPLE(val,sf);
  return(val1);
}

float next_sample (snd_fd *sf) {return(clm_sndflt * next_sample_1(sf));}
float previous_sample (snd_fd *sf) {return(clm_sndflt * previous_sample_1(sf));}

#define NEXT_SAMPLES(data,num,sf) {int i; for (i=0;i<num;i++) NEXT_SAMPLE(data[i],sf);}

int read_sample_eof (snd_fd *sf)
{
  if (sf->cb)
    return((sf->cb[ED_SND] == EDIT_LIST_END_MARK) || ((sf->cbi == 0) && (sf->data < sf->first)));
  else return(sf->data >= sf->last);
}


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


int snd_IO_error;

static char *snd_error_names[] = {
  snd_string_no_error,snd_string_cant_write_header,snd_string_cant_open_file,
  snd_string_cant_allocate_IO_buffers,snd_string_cant_write_data,
  snd_string_cant_remove_file,snd_string_cant_rename_file,snd_string_cant_read_header,
  snd_string_unsupported_file_type,snd_string_cant_find_file,snd_string_unsupported_data_format};

char *snd_error_name(int i) {return(snd_error_names[i]);}

int open_temp_file(char *ofile, int chans, file_info *hdr, snd_state *ss)
{
  int ofd,len;
  len = snd_strlen(hdr->comment);
  if (!(output_type_and_format_ok(hdr->type,hdr->format)))
    {
      hdr->type = default_output_type(ss);
      if (hdr->type != RIFF_sound_file)
	hdr->format = snd_16_linear;
      else hdr->format = snd_16_linear_little_endian;
    }
  if ((snd_write_header(ss,ofile,hdr->type,hdr->srate,chans,0,0,hdr->format,hdr->comment,len)) == -1) return(-1);
  if ((ofd = snd_reopen_write(ss,ofile)) == -1) return(-1);
  hdr->data_location = c_snd_header_data_location(); /* header might have changed size (aiff extras) */
  open_clm_file_descriptors(ofd,hdr->format,c_snd_datum_size(hdr->format),hdr->data_location);
  clm_seek(ofd,hdr->data_location,0);
  return(ofd);
}

int close_temp_file(int ofd, file_info *hdr, long bytes, snd_info *sp)
{
  int kleft,kused;
  char *prtbuf;
  c_update_header_with_fd(ofd,hdr->type,bytes);
  kleft = disk_kspace(ofd);
  if (kleft < 0)
    report_in_minibuffer(sp,strerror(errno));
  else
    {
      kused = bytes>>10;
      if (kused > kleft) 
	{
	  prtbuf = (char *)calloc(64,sizeof(char));
	  sprintf(prtbuf,snd_string_were_getting_short_on_disk_space,kused,kleft);
	  report_in_minibuffer(sp,prtbuf);
	  free(prtbuf);
	}
    }
  snd_close(ofd);
  return(0);
}


static int make_file(char *ofile, int chans, file_info *hdr, snd_fd **sfs, int *length, snd_state *ss)
{
  /* create ofile, fill it by following sfs, use hdr for srate/type/format decisions */
  int ofd;
  int i,j,len,datumb;
  int **obufs;
  ofd = open_temp_file(ofile,chans,hdr,ss);
  if (ofd == -1) return(snd_cannot_open_temp_file);
  datumb = c_snd_datum_size(hdr->format);
  obufs = (int **)calloc(chans,sizeof(int *));
  for (i=0;i<chans;i++)
    {
      obufs[i] = (int *)snd_calloc(ss,FILE_BUFFER_SIZE,sizeof(int));
    }
  j=0;
  len = 0;
  while (!(read_sample_eof(sfs[0]))) /* assume all channels are same length (?) */
    {
      for (i=0;i<chans;i++)
	{
	  NEXT_SAMPLE(obufs[i][j],sfs[i]);
	}
      j++;
      len++;
      if (j == FILE_BUFFER_SIZE)
	{
	  clm_write(ofd,0,j-1,chans,obufs);
	  if (snd_IO_error) return(snd_cannot_write_data);
	  j=0;
	}
    }
  if (j > 0)
    {
      clm_write(ofd,0,j-1,chans,obufs);
      if (snd_IO_error) return(snd_cannot_write_data);
    }
  close_temp_file(ofd,hdr,len*chans*datumb,any_selected_sound(ss));
  alert_new_file();
  for (i=0;i<chans;i++) free(obufs[i]);
  free(obufs);
  (*length) = len;
  return(0);
}

static int only_save_edits(snd_info *sp, file_info *nhdr, char *ofile)
{
  snd_state *ss;
  int i,samples,err;
  snd_fd **sf;
  ss = sp->state;
  samples = 0;
  sf = (snd_fd **)calloc(sp->nchans,sizeof(snd_fd *));
  for (i=0;i<sp->nchans;i++) 
    {
      sf[i] = init_sample_read(0,sp->chans[i],READ_FORWARD);
    }
  err = make_file(ofile,sp->nchans,nhdr,sf,&samples,ss);
  for (i=0;i<sp->nchans;i++) free_snd_fd(sf[i]);
  free(sf);
  return(err);
}

static int save_edits_1(snd_info *sp)
{
  /* open temp, write current state, rename to old, reopen and clear all state */
  /* can't overwrite current because we may have cut/paste backpointers scattered around the current edit list */
  /* have to decide here what header/data type to write as well -- original? */
  /* if latter, must be able to write all headers! -- perhaps warn user and use snd/aiff/riff/ircam */
  char *ofile;
  int err,saved_errno = 0;
  snd_state *ss;
  int i,samples;
  chan_info *cp;
  axis_info *ap;
  snd_fd **sf;
  float *axis_data;
  int *ffts,*waves;

  ss = sp->state;
  samples = 0;
  snd_IO_error = 0;
  ofile = snd_tempnam(temp_dir(ss),"snd_"); 
  /* this will use user's TMPDIR if temp_dir(ss) is not set, else stdio.h's P_tmpdir else /tmp */
  axis_data = (float *)calloc(4*sp->nchans,sizeof(float));
  ffts = (int *)calloc(sp->nchans,sizeof(int));
  waves = (int *)calloc(sp->nchans,sizeof(int));
  for (i=0;i<sp->nchans;i++)
    {
      cp = sp->chans[i];
      ap = cp->axis;
      axis_data[(i*4)+0]=ap->x0;
      axis_data[(i*4)+1]=ap->x1;
      axis_data[(i*4)+2]=ap->y0;
      axis_data[(i*4)+3]=ap->y1;
      waves[i] = cp->waving;
      ffts[i] = cp->ffting;
    }
  sf = (snd_fd **)calloc(sp->nchans,sizeof(snd_fd *));
  for (i=0;i<sp->nchans;i++) 
    {
      sf[i] = init_sample_read(0,sp->chans[i],READ_FORWARD);
    }
  sprintf(edit_buf,snd_string_saving,sp->shortname);
  report_in_minibuffer(sp,edit_buf);
  snd_IO_error = make_file(ofile,sp->nchans,sp->hdr,sf,&samples,ss);
  if (snd_IO_error != snd_no_error) 
    {
      for (i=0;i<sp->nchans;i++) free_snd_fd(sf[i]);
      free(sf);
      free(ffts);
      free(waves);
      free(axis_data);
      return(snd_IO_error);
    }
  ((file_info *)(sp->hdr))->samples = samples*sp->nchans;
  collapse_marks(sp);
  for (i=0;i<sp->nchans;i++)
    {
      cp = sp->chans[i];
      if (cp->mixes) reset_mix_list(cp);
      if (cp->edits) free_edit_list(cp);
      if (cp->sounds) free_sound_list(cp);
      free_chan_env(cp);
      free_snd_fd(sf[i]);
    }
  free(sf);
  err = access(sp->fullname,W_OK);
  /* very weird -- in Linux we can write a write-protected file?? */
  if (err == 0)
    {
      if ((err = (rename(ofile,sp->fullname))))
	{
	  /* rename can't go across device boundaries, among other things, so it it fails
	   * here for some obvious reason, we'll try to copy before giving up
	   */
	  if (errno == EXDEV) /* cross device error */
	    {
	      err = copy_file(ofile,sp->fullname,sp);
	      if (!err) remove(ofile);
	    }
	  if (err) saved_errno = errno;
	  /* if (err) return(snd_cannot_rename_file); */
	}
    }
  else saved_errno = errno;
  sp->write_date = file_write_date(sp->fullname);

  add_sound_data(sp->fullname,sp,ss);

  for (i=0;i<sp->nchans;i++)
    {
      cp = sp->chans[i];
      set_axes(cp,axis_data[(i*4)+0],axis_data[(i*4)+1],axis_data[(i*4)+2],axis_data[(i*4)+3]);
      update_graph(cp,NULL); /* get normalized state before messing with it */
      if (ffts[i]) fftb(cp,TRUE);
      if (!(waves[i])) waveb(cp,FALSE);
    }
  free(axis_data);
  free(waves);
  free(ffts);

  reflect_file_revert_in_label(sp);
  reflect_file_save_in_menu(ss);
  if (err)
    sprintf(edit_buf,snd_string_write_failed_temp_saved,strerror(saved_errno),ofile);
  else sprintf(edit_buf,snd_string_wrote,sp->fullname); 
  report_in_minibuffer(sp,edit_buf);
  if (ofile) {free(ofile); ofile=NULL;}
  return(snd_no_error); /* don't erase our error message for the special write-permission problem */
}

int save_edits_2(snd_info *sp, char *new_name, int type, int format, int srate, char *comment)
{ /* file save as menu option -- changed 19-June-97 to retain current state after writing */
  file_info *hdr,*ohdr;
  ohdr = sp->hdr;
  hdr = copy_header(new_name,ohdr);
  hdr->format = format;
  hdr->srate = srate;
  hdr->type = type;
  hdr->comment = comment;
  hdr->data_location = 0; /* in case comment changes it */
  return(only_save_edits(sp,hdr,new_name));
}

int chan_save_edits(chan_info *cp, char *ofile)
{
  /* channel extraction -- does not cause reversion of edits, or change of in-window file, etc */
  snd_info *sp;
  snd_fd **sf;
  int samples;
  sp = cp->sound;
  snd_IO_error = 0;
  sf = (snd_fd **)calloc(1,sizeof(snd_fd *));
  sf[0] = init_sample_read(0,cp,READ_FORWARD);
  snd_IO_error = make_file(ofile,1,sp->hdr,sf,&samples,cp->state);
  free_snd_fd(sf[0]);
  free(sf);
  return(snd_IO_error);
}

int save_edits(snd_info *sp, void *ptr)
{
  int i,need_save,err;
  time_t current_write_date;
  chan_info *cp;
  if (!sp->read_only)
    {
      need_save = 0;
      for (i=0;i<sp->nchans;i++)
	{
	  cp = sp->chans[i];
	  if (cp->edit_ctr > 0) 
	    {
	      need_save = 1;
	      break;
	    }
	}
      if (need_save)
	{
	  errno = 0;
	  /* check for change to file while we were editing it */
	  current_write_date = file_write_date(sp->fullname);
	  if (current_write_date != sp->write_date)
	    {
	      sprintf(edit_buf,snd_string_changed_on_disk_p,sp->shortname);
	      err = snd_yes_or_no_p(sp->state,edit_buf);
	      if (err == 0) return(0);
	    }
	  err = save_edits_1(sp);
	  if (err)
	    {
	      sprintf(edit_buf,"%s: %s (%s)",sp->fullname,strerror(errno),snd_error_name(err));
	      report_in_minibuffer(sp,edit_buf);
	    }
	}
    }
  else
    {
      sprintf(edit_buf,snd_string_cant_write,sp->shortname);
      strcat(edit_buf," ("); strcat(edit_buf,snd_string_read_only); strcat(edit_buf,")");
      text_set_string(snd_widget(sp,W_snd_info),edit_buf);
    }
  return(0);
}

int revert_edits(chan_info *cp, void *ptr)
{
  snd_info *sp;
  sp = cp->sound;
  if (sp->playing) stop_playing(sp->playing);
  cp->edit_ctr = 0;
  reflect_edit_counter_change(cp);
  reflect_sample_change_in_axis(cp);
  update_graph(cp,NULL);
  update_groups();
  return(0);
}

void undo_edit(chan_info *cp, int count)
{
  snd_info *sp;
  if ((cp) && (cp->edit_ctr > 0) && (count != 0))
    {
      sp = cp->sound;
      if (sp->playing) stop_playing(sp->playing);
      cp->edit_ctr -= count; 
      if (cp->edit_ctr < 0) cp->edit_ctr = 0;
      reflect_edit_counter_change(cp);
      reflect_sample_change_in_axis(cp);
      reflect_undo_in_menu();
      if (cp->edit_ctr == 0)
	{
	  reflect_file_revert_in_label(sp);
	  reflect_file_revert_in_menu(cp->state);
	}
      update_graph(cp,NULL);
      update_groups();
    }
}

void undo_EDIT(void *ptr, int count)
{
  chan_info *cp;
  snd_info *sp;
  int i;
  sync_info *si;
  if (count < 0)
    redo_edit(ptr,-count);
  else
    {
      si = NULL;
      cp = current_channel(ptr);
      sp = cp->sound;
      if (sp->syncing) si = snd_sync(cp->state);
      if (si)
	{
	  for (i=0;i<si->chans;i++) undo_edit(si->cps[i],count);
	  free_sync_info(si);
	}
      else undo_edit(cp,count);
    }
}

static void reflect_file_change_in_label (chan_info *cp)
{
  snd_info *sp = cp->sound;
  char *starred_name;
  int len;
  len = strlen(shortname(sp)) + 2;
  if (sp->read_only) len+=2;
  starred_name = (char *)calloc(len,sizeof(char));
  strcpy(starred_name,shortname(sp));
  if (sp->read_only)
    {
      starred_name[len-4]='(';
      starred_name[len-3]='*';
      starred_name[len-2]=')';
    }
  else starred_name[len-2]='*';
  starred_name[len-1]='\0';
  make_name_label(snd_widget(sp,W_snd_name),starred_name);
  make_a_big_star_outa_me(sp->state,sp->shortname,1);
  free(starred_name);
}

void redo_edit(chan_info *cp, int count)
{
  snd_info *sp;
  if (cp)
    {
      if (cp->edit_ctr == 0) 
	{
	  reflect_file_change_in_label(cp);
	  reflect_redo_in_menu();
	}
      sp = cp->sound;
      if (sp->playing) stop_playing(sp->playing);
      cp->edit_ctr += count; 
      while ((cp->edit_ctr >= cp->edit_size) || (!(cp->edits[cp->edit_ctr]))) {cp->edit_ctr--;}
      if (((cp->edit_ctr+1) == cp->edit_size) || (!(cp->edits[cp->edit_ctr+1]))) 
	reflect_no_more_redo_in_menu();
      reflect_edit_counter_change(cp);
      reflect_sample_change_in_axis(cp);
      update_graph(cp,NULL);
      update_groups();
    }
}

void redo_EDIT(void *ptr, int count)
{
  chan_info *cp;
  snd_info *sp;
  int i;
  sync_info *si;
  if (count < 0)
    undo_EDIT(ptr,-count);
  else
    {
      si = NULL;
      cp = current_channel(ptr);
      sp = cp->sound;
      if (sp->syncing) si = snd_sync(cp->state);
      if (si)
	{
	  for (i=0;i<si->chans;i++) redo_edit(si->cps[i],count);
	  free_sync_info(si);
	}
      else redo_edit(cp,count);
    }
}


void check_for_first_edit(chan_info *cp)
{
  if (cp->edit_ctr == 1) /* first edit on this file (?) */
    {
      reflect_file_change_in_menu();
      reflect_file_change_in_label(cp);
    }
  /* display_edits(cp); */
}
