#include "snd.h" 

/* snd selections/regions */

typedef struct { /* one for each 'channel' */
  int visible;
  int type;
  snd_info *sp;
  chan_info *cp;
  int first,last;
  int x0,y0,width,height;
  int *fixups;
  int fixup_size;
} region_context;  

static int samp0(region_context *rg)
{
  chan_info *cp;
  if (!rg->fixups) return(rg->first);
  cp = rg->cp;
  if (cp->edit_ctr < 0) return(rg->first);
  return(rg->first+rg->fixups[cp->edit_ctr]);
}

static int samp1(region_context *rg)
{
  chan_info *cp;
  if (!rg->fixups) return(rg->last);
  cp = rg->cp;
  if (cp->edit_ctr < 0) return(rg->last);
  return(rg->last+rg->fixups[cp->edit_ctr]);
}

#define REGION_ARRAY 0
#define REGION_FILE 1
/* region data can be stored either in-core (if less than MAX_BUFFER_SIZE ints), else in a temp file that */
/*    is deleted when the region is deleted (hence must be copied upon insert or mix) */

typedef struct {
  int **data;
  int chans;
  int len;
  int srate;        /* for file save (i.e. region->file) */
  int type;         /* for file save */
  int save;
  snd_info *rsp;
  region_context **rgx;
  char *name,*start,*end;
  char *filename;  /* if region data is stored in a temp file */
  int rtype;       /* REGION_ARRAY = data is in 'data' arrays, else in temp file 'filename' */
  float maxamp;
} region;

static void free_region_contexts(region *r)
{
  int i;
  region_context *rg;
  for (i=0;i<r->chans;i++)
    {
      if ((rg = r->rgx[i]))
	{
	  if (rg->fixups) free(rg->fixups);
	  free(rg);
	}
    }
}

static void free_region(region *r)
{
  int i;
  snd_info *sp;
  chan_info *cp;
  if (r)
    {
      if (r->rgx) free_region_contexts(r);
      if (r->data)  /* null if temp file */
	{
	  for (i=0;i<r->chans;i++) 
	    {
	      if (r->data[i]) free(r->data[i]);
	    }
	  free(r->data);
	}
      if (r->name) free(r->name);
      if (r->start) free(r->start);
      if (r->end) free(r->end);
      if (r->rtype == REGION_FILE) /* we can delete this temp file because all references copy first */
	{
	  if (r->filename)
	    {
	      remove(r->filename);
	      free(r->filename);   /* ok because tempnam used */
	    }
	  r->filename = NULL;
	}
      if (r->rsp) 
	{
	  sp = r->rsp;
	  sp->sgx = NULL;
	  if (sp->chans)
	    {
	      for (i=0;i<sp->nchans;i++)
		{
		  cp=sp->chans[i];
		  cp->cgx = NULL;
		}
	    }
	  free_snd_info(r->rsp);
	}
      free(r);
    }
}

static region *regions[REGIONS]; /* regions[0] => current global selection from X viewpoint */

void init_regions(void) {int i; for (i=0;i<REGIONS;i++) regions[i]=NULL;}
int region_ok(int n) {return((n<REGIONS) && (regions[n]));}
int region_len(int n) {if (region_ok(n)) return(regions[n]->len); else return(0);}
int region_chans(int n) {if (region_ok(n)) return(regions[n]->chans); else return(0);}
int region_srate(int n) {if (region_ok(n)) return(regions[n]->srate); else return(0);}
float region_maxamp(int n) {if (region_ok(n)) return(regions[n]->maxamp); else return(0.0);}

float region_sample(int reg, int chn, int samp)
{
  region *r;
  if (region_ok(reg))
    {
      r = regions[reg];
      if ((samp < r->len) && (chn < r->chans)) return(clm_sndflt * r->data[chn][samp]);
    }
  return(0.0);
}

void region_samples(int reg, int chn, int beg, int num, float *data)
{
  region *r;
  int i,j;
  if (region_ok(reg))
    {
      r = regions[reg];
      if ((beg < r->len) && (chn < r->chans))
	{
	  for (i=beg,j=0;(i<r->len) && (j<num);i++,j++) data[j] = clm_sndflt * r->data[chn][i];
	  if (j < num) for (;j<num;j++) data[j] = 0.0;
	}
    }
}

static int first_region_active(void)
{
  int i;
  for (i=0;i<REGIONS;i++)
    {
      if (regions[i]) return(i);
    }
  return(-1);
}
  
static int check_regions(void)
{
  int act;
  act = first_region_active();
  if (act == -1) reflect_no_regions_in_menu();
  return(act);
}


static void make_region_readable(region *r, snd_state *ss)
{
  snd_info *regsp;
  chan_info *cp;
  file_info *hdr;
  int *datai;
  int i,fd;
  if (r->rsp) return;
  regsp = (snd_info *)calloc(1,sizeof(snd_info));
  regsp->s_type = make_snd_pointer_type(SND_INFO);
  regsp->nchans = r->chans;
  regsp->chans = (chan_info **)calloc(r->chans,sizeof(chan_info *));
  regsp->hdr = (file_info *)calloc(1,sizeof(file_info));
  hdr = regsp->hdr;
  hdr->s_type = make_snd_pointer_type(FILE_INFO);
  hdr->samples = r->len * r->chans;
  hdr->srate = r->srate;
  hdr->chans = r->chans;
  hdr->comment = NULL;
  for (i=0;i<r->chans;i++)
    {
      cp = make_chan_info(NULL,i,regsp,ss);
      regsp->chans[i] = cp;
      add_channel_data_1(cp,regsp,ss,0);
      cp->edits[0] = initial_ed_list(0,r->len);
      /* cp->edit_size = 0; */
      /* cp->sound_size = 0; */
      cp->hookable = 0;
      if (r->rtype == REGION_ARRAY)
	cp->sounds[0] = make_snd_data_buffer(r->data[i],r->len,cp->edit_ctr);
      else
	{
	  hdr = make_file_info(r->filename,ss);
	  if (hdr)
	    {
	      fd = snd_open_read(ss,r->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,i,FILE_BUFFER_SIZE,ss);
#if LONG_INT_P
	      cp->sounds[0] = make_snd_data_file(r->filename,datai,
						 delist_ptr(datai[io_dats+6+i]),
						 copy_header(hdr->name,hdr),
						 0,cp->edit_ctr,i); /* don't auto-delete! */
#else
	      cp->sounds[0] = make_snd_data_file(r->filename,datai,
						 (int *)(datai[io_dats+6+i]),
						 copy_header(hdr->name,hdr),
						 0,cp->edit_ctr,i); /* don't auto-delete! */
#endif
	    }
	}
    }
  r->rsp = regsp;
}

file_info *fixup_region_data(chan_info *cp, int chan, int n)
{
  region *r;
  snd_info *nsp;
  chan_info *ncp;
  if (region_ok(n))
    {
      r = regions[n];
      if (chan < r->chans)
	{
	  make_region_readable(r,cp->state);
	  nsp = r->rsp;
	  ncp = nsp->chans[chan];
	  cp->sounds = ncp->sounds;
	  cp->sound_size = 0;
	  cp->edits = ncp->edits;
	  cp->edit_size = 0;
	  cp->edit_ctr = ncp->edit_ctr;
	  cp->axis = ncp->axis;
	  initialize_scrollbars(cp);
	  return(nsp->hdr);
	}
    }
  return(NULL);
}

region_state *region_report(void)
{
  region_state *rs;
  int i,len;
  char *reg_buf;
  region *r;
  finish_keyboard_selection();
  rs = (region_state *)calloc(1,sizeof(region_state));
  len = REGIONS;
  for (i=0;i<REGIONS;i++) {if (!(regions[i])) {len = i; break;}}
  rs->len = len;
  if (len == 0) return(rs);
  rs->save = (int *)calloc(len,sizeof(int));
  rs->name = (char **)calloc(len,sizeof(char *));
  for (i=0;i<len;i++)
    {
      r = regions[i];
      rs->save[i] = r->save;
      reg_buf = (char *)calloc(64,sizeof(char));
      sprintf(reg_buf,"%d: %s (%s:%s)",i,r->name,r->start,r->end);
      rs->name[i] = reg_buf;
    }
  return(rs);
}

void free_region_state (region_state *r)
{
  int i;
  if (r)
    {
      for (i=0;i<r->len;i++)
	{
	  if (r->name[i]) free(r->name[i]);
	}
      if (r->name) free(r->name);
      if (r->save) free(r->save);
      free(r);
    }
}

void select_region(int n)
{
  int i;
  region *r;
  if (region_ok(n))
    {
      r = regions[n];
      for (i=n;i>0;i--) regions[i]=regions[i-1]; 
      regions[0] = r;
    }
}

int delete_region(int n)
{
  int i;
  if (region_ok(n)) free_region(regions[n]);
  for (i=n;i<REGIONS-1;i++) regions[i]=regions[i+1]; 
  regions[REGIONS-1] = NULL;
  return(check_regions());
}

void protect_region(int n,int protect)
{
  region *r;
  if (region_ok(n))
    {
      r = regions[n];
      if (r) r->save = protect;
    }
}

snd_info *region_sound(int n) 
{
  region *r; 
  region_context **rgx;
  if (region_ok(n))
    {
      r = regions[n]; 
      rgx = r->rgx;
      if ((rgx) && (rgx[0])) return(rgx[0]->sp); else return(NULL);
    }
  return(NULL);
}

static void stack_region(region *r) 
{
  int i,okr = 0;
  /* leave protected regions alone -- search for highest unprotected region */
  for (i=REGIONS-1;i>0;i--) {if ((!(regions[i])) || (!((regions[i])->save))) {okr = i; break;}}
  if (regions[okr]) free_region(regions[okr]);
  for (i=okr;i>0;i--) regions[i]=regions[i-1]; 
  regions[0]=r;
  if (!r) check_regions();
}

static region_context *region_member(chan_info *cp, region* r)
{
  int i;
  region_context **rg;
  if ((r) && (r->rgx))
    {
      rg = r->rgx;
      for (i=0;i<r->chans;i++)
	{
	  if ((rg[i]) && (rg[i]->cp == cp)) return(rg[i]);
	}
    }
  return(NULL);
}

int selection_member(snd_info *sp)
{
  region *r;
  region_context **rg;
  int i;
  r = regions[0];
  if ((r) && (r->rgx))
    {
      rg = r->rgx;
      for (i=0;i<r->chans;i++)
	{
	  if ((rg[i]) && (rg[i]->sp == sp)) return(1);
	}
    }
  return(0);
}

int selection_len(void) {if (regions[0]) return(1 + region_len(0)*region_chans(0)); else return(0);}
int active_selection (chan_info *cp)
{
  region_context *rg;
  return((regions[0]) &&
	 (rg=(region_member(cp,regions[0]))) && 
	 (rg->visible));
}

int selection_beg(chan_info *cp) 
{
  region *r;
  region_context *rg;
  region_context **rgx;
  int i;
  if ((r = regions[0]))
    {
      rgx = r->rgx;
      if (rgx)
	{
	  for (i=0;i<r->chans;i++)
	    {
	      rg = rgx[i];
	      if ((rg) && (cp = rg->cp))
		{
		  return(samp0(rg));
		}
	    }
	}
    }
  return(0);
}
 
void selection_off(chan_info *cp)
{
  region *r;
  region_context *rg;
  if ((r = regions[0]) && (rg = region_member(cp,r))) rg->visible = 0;
}

static int save_selection_1(snd_state *ss, char *ofile,int type, int format, int srate, int reg, char *comment)
{
  int ofd,oloc,ifd,chans,i,frames,cursamples,iloc,comlen;
  int **bufs;
  region *r;
  comlen = snd_strlen(comment);
  if (region_ok(reg)) r = regions[reg]; else r=NULL;
  if (r)
    {
      if ((snd_write_header(ss,ofile,type,srate,r->chans,28,r->chans*r->len,format,comment,comlen)) == -1) return(snd_cannot_write_header);
      oloc = c_snd_header_data_location();
      if ((ofd = snd_reopen_write(ss,ofile)) == -1) return(snd_cannot_open_temp_file);
      open_clm_file_descriptors(ofd,format,c_snd_datum_size(format),oloc);
      clm_seek(ofd,oloc,0);
      if (r->rtype == REGION_ARRAY)
	{
	  clm_write(ofd,0,r->len-1,r->chans,r->data); /* was * r->chans --> clm_write wants per channel size */
	}
      else
	{
	  /* copy r->filename with possible header/data format changes */
	  if ((ifd = snd_open_read(ss,r->filename)) == -1) return(snd_cannot_open_temp_file);
	  chans = sound_chans(r->filename);
	  frames = sound_samples(r->filename) / chans;
	  iloc = sound_data_location(r->filename);
	  open_clm_file_descriptors(ifd,sound_data_format(r->filename),sound_datum_size(r->filename),iloc);
	  clm_seek(ifd,iloc,0);
	  bufs = (int **)calloc(chans,sizeof(int *));
	  for (i=0;i<chans;i++) bufs[i] = (int *)snd_calloc(ss,FILE_BUFFER_SIZE,sizeof(int));
	  for (i=0;i<frames;i+=FILE_BUFFER_SIZE)
	    {
	      if ((i+FILE_BUFFER_SIZE)<frames) cursamples = FILE_BUFFER_SIZE; else cursamples = (frames-i);
	      clm_read(ifd,0,cursamples-1,chans,bufs);
	      clm_write(ofd,0,cursamples-1,chans,bufs);
	    }
	  snd_close(ifd);
	  for (i=0;i<chans;i++) free(bufs[i]);
	  free(bufs);
	}
      snd_close(ofd);
      alert_new_file();
    }
  return(0);
}

int save_region(snd_state *ss, int n, char *ofile)
{
  region *r;
  r = regions[n];
  if (r)
    {
      if (!(output_type_and_format_ok(r->type,snd_16_linear))) r->type = NeXT_sound_file;
      return(save_selection_1(ss,ofile,r->type,snd_16_linear,r->srate,n,NULL));
    }
  return(0);
}

int save_selection(snd_state *ss, char *ofile,int type, int format, int srate, char *comment)
{
  return(save_selection_1(ss,ofile,type,format,srate,0,comment));
}

void delete_selection(char *origin)
{
  /* if we own the selection, delete it from the current sync'd channels, reset stippling, update displays */
  region *r;
  int i;
  chan_info **ncp;
  region_context *rg;
  finish_keyboard_selection();
  r = regions[0];
  if ((r) && (r->rgx))
    {
      ncp = (chan_info **)calloc(r->chans,sizeof(chan_info *));
      for (i=0;i<r->chans;i++)
	{
	  rg = r->rgx[i];
	  ncp[i] = rg->cp;
	  if (samp1(rg) > samp0(rg))
	    {
	      delete_samples(samp0(rg),samp1(rg) - samp0(rg) + 1,ncp[i],origin);
	      check_for_first_edit(ncp[i]);
	    }
	}
      free_region_contexts(r);
      r->rgx = NULL;
      for (i=0;i<r->chans;i++)
	{
	  update_graph(ncp[i],NULL);
	}
      free(ncp);
      reflect_edit_without_selection_in_menu();
    }
}

static void paste_region_1(int n, chan_info *cp, int add, int beg, float scaler, char *origin)
{
  region *r;
  region_context *rg;
  int chn,i,j,k;
  snd_info *sp;
  sync_info *si;
  chan_info *ncp;
  int *data;
  snd_state *ss;
  char *tempfile = NULL;
  ss = cp->state;
  sp = cp->sound;
  si = NULL;
  finish_keyboard_selection();
  /* 
   * two cases: if current selection, rg has the cp pointers, else traverse in parallel
   * so -- get syncd chans, if rg, loop looking for matches, else loop until either exhausted
   */
  if (region_ok(n)) r = regions[n]; else r=NULL;
  if (r)
    {
      /* get syncd chans relative to current (cp) */
      if (sp->syncing) si = snd_sync(ss);  
      else si = make_simple_sync(cp,beg);
      /* chans in si->cps[i], for si->chans */
      if (add)
	{
	  if (r->rtype == REGION_ARRAY)
	    mix_array(beg,r->len,r->data,si->cps,r->chans,si->chans,snd_SRATE(cp),origin);
	  else mix_file(beg,r->len,r->filename,si->cps,r->chans,origin);
	}
      else
	{
	  if (r->rtype == REGION_FILE)
	    {
	      tempfile = snd_tempnam(temp_dir(ss),"snd_");
	      copy_file(r->filename,tempfile,sp);
	    }
	  /* if current_selection and rg->cp == si->cps[i] for some rg or i, edit there, else go through each in order until either is done */
	  for (i=0;((i<r->chans) && (i<si->chans));i++)
	    {
	      ncp = si->cps[i]; /* currently syncd chan that we might paste to */
	      chn  = i;         /* default is parallel paste */
	      if ((n == 0) && (r->rgx))
		{
		  for (k=0;k<r->chans;k++)
		    {
		      rg = r->rgx[k];
		      if ((rg->cp) == ncp)
			{
			  chn = k;
			  break;
			}
		    }
		}
	      /* now chn = region chan, ncp = Snd chan */
	      if (r->rtype == REGION_ARRAY)
		{
		  data = (int *)snd_calloc(ss,r->len,sizeof(int));
		  if (scaler == 1.0)
		    {
		      for (j=0;j<r->len;j++) data[j] = r->data[chn][j];
		    }
		  else
		    {
		      for (j=0;j<r->len;j++) data[j] = scaler * r->data[chn][j];
		    }
		  insert_samples(beg,r->len,data,ncp,origin);
		  free(data);
		}
	      else
		file_insert_samples(beg,r->len,tempfile,ncp,chn,(i == 0) ? DELETE_ME : DONT_DELETE_ME,origin);
	      check_for_first_edit(ncp);
	      update_graph(ncp,NULL);
	    }
	}
    }
  if (si) free_sync_info(si);
}

static int paste_fix_region(int n) {if (n > REGIONS) return(0); return(n);}
static float paste_fix_scaler(int n, chan_info *cp) {if (n > REGIONS) return((float)n/(float)snd_SRATE(cp)); return(1.0);}

void paste_region(int n, chan_info *cp,char *origin) {paste_region_1(paste_fix_region(n),cp,0,cp->cursor,paste_fix_scaler(n,cp),origin);}
void add_region(int n, chan_info *cp, char *origin) {paste_region_1(paste_fix_region(n),cp,1,cp->cursor,paste_fix_scaler(n,cp),origin);}
void mix_region(int n, chan_info *cp, int beg, float scaler) {paste_region_1(n,cp,1,beg,scaler,"(mix-region)");}

/* we're drawing the selection in one channel, but others may be sync'd to it */

static double selbeg,selend;    /* true bounds (as the axes move etc) */
static int keyboard_selecting = 0;

void finish_keyboard_selection(void)
{
  region_context **rgx;
  if (keyboard_selecting)
    {
      rgx = regions[0]->rgx;
      define_selection(rgx[0]->cp);
      keyboard_selecting = 0;
    }
}

int cancel_keyboard_selection (void)
{
  region_context **rgx;
  snd_info *sp;
  int beg = -1;
  if (keyboard_selecting)
    {
      keyboard_selecting = 0;
      rgx = regions[0]->rgx;
      if ((rgx) && (sp = (rgx[0]->sp)))
	{
	 clear_minibuffer(sp);
	 beg = selbeg * snd_SRATE(sp); /* during kbd selection we have to use selbeg (samp0 not set yet) */
	}
      deactivate_selection();
      free_region(regions[0]);
      regions[0] = NULL;
    }
  return(beg);
}

void start_selection (chan_info *cp, int x)
{ /* only from mouse so we use ungrf here (very inaccurate if lots of data displayed) */
  if (keyboard_selecting) /* i.e. mouse button while c-space active = save and start again */
    {
      finish_keyboard_selection();
    }
  /* reflect_edit_with_selection_in_menu(); */ /* can be called when cursor first defined -- no selection intended */
  selbeg = ungrf_x(cp->axis,x);
  if (selbeg < 0.0) selbeg = 0.0;
  selend = selbeg;
}

void start_keyboard_selection(chan_info *cp, int x)
{
  if (keyboard_selecting) deactivate_selection(); /* i.e. c-space while c-space active = flush and start again */
  keyboard_selecting = 0;
  create_selection(cp);
  reflect_edit_with_selection_in_menu();
  if (cp->cursor_on)                           /* use exact sample if it's available */
    selbeg = (double)(cp->cursor)/(double)snd_SRATE(cp);
  else ungrf_x(cp->axis,x);
  if (selbeg < 0.0) selbeg = 0.0;
  selend = selbeg;
  keyboard_selecting = 1;
  cp->cursor_on = 1;
}

static void redraw_selection(chan_info *cp, int x);

void check_keyboard_selection(chan_info *cp, int x)
{
  if (keyboard_selecting) redraw_selection(cp,x);
}

static void draw_selection_portion(chan_info *cp, region_context *rg)
{
  fill_rectangle(selection_context(cp),rg->x0,rg->y0,rg->width,rg->height);
}

void deactivate_selection(void)
{
  region *r;
  region_context *rg;
  int i;
  chan_info *ncp;
  r = regions[0];
  if ((r) && (r->rgx))
    {
      for (i=0;i<r->chans;i++)
	{
	  rg = r->rgx[i];
	  ncp = rg->cp;
	  if (rg->visible) draw_selection_portion(ncp,rg);
	}
      free_region_contexts(r);
      r->rgx = NULL;
    }
  reflect_edit_without_selection_in_menu();
}

static void redraw_selection(chan_info *cp, int x)
{
  /* called as mouse is dragged, for example */
  region *r;
  region_context *rg;
  int i;
  float sx0,sx1;
  chan_info *ncp;
  axis_info *ap;
  if (keyboard_selecting)
    selend = (double)(cp->cursor)/(double)snd_SRATE(cp);
  else selend = ungrf_x(cp->axis,x);
  if (selend < 0.0) selend = 0.0;
  r=regions[0];
  if (r)
    {
      for (i=0;i<r->chans;i++)
	{
	  rg = r->rgx[i];
	  if (rg)
	    {
	      ncp = rg->cp;
	      ap = ncp->axis;
	      if (rg->visible) draw_selection_portion(ncp,rg);
	      if (selbeg < selend)
		{
		  if (selbeg >= ap->x0) sx0 = selbeg; else sx0 = ap->x0;
		  if (selend <= ap->x1) sx1 = selend; else sx1 = ap->x1;
		}
	      else
		{
		  if (selend >= ap->x0) sx0 = selend; else sx0 = ap->x0;
		  if (selbeg <= ap->x1) sx1 = selbeg; else sx1 = ap->x1;
		}
	      rg->x0 = grf_x(sx0,ap);
	      rg->y0 = ap->y_axis_y1;
	      rg->width = grf_x(sx1,ap) - rg->x0;
	      rg->height = (unsigned int)(ap->y_axis_y0-ap->y_axis_y1);
	      rg->visible = 1;
	      draw_selection_portion(ncp,rg);
	    }
	}
    }
}

int selection_active(chan_info *cp)
{
  return((regions[0]) && (region_member(cp,regions[0])));
}

void display_selection(chan_info *cp)
{ /* cp's graph was just cleared and redrawn -- now add selection, if relevant */
  int x0,x1,sx0,sx1;
  region_context *rg;
  axis_info *ap;
  snd_info *sp;
  if ((regions[0]) && (rg=(region_member(cp,regions[0]))))
    {
      /* we have the current selection -- should it be visible? */
      ap = cp->axis;
      sp = cp->sound;
      if (keyboard_selecting) /* selection definition is in progress, so rg->first and last are not set yet */
	{
	  x0 = selbeg * snd_SRATE(cp);
	  x1 = selend * snd_SRATE(cp);
	  if (x1 < x0) {sx0 = x0; x0 = x1; x1 = sx0;}
	}
      else
	{
	  x0 = samp0(rg);
	  x1 = samp1(rg);
	}
      if ((x0 <= ap->hisamp) && (x1 >= ap->losamp))
	{
	  if (ap->losamp > x0) x0 = ap->losamp;
	  if (ap->hisamp < x1) x1 = ap->hisamp;
	  sx0 = grf_x((double)x0/(double)snd_SRATE(sp),ap);
	  sx1 = grf_x((double)x1/(double)snd_SRATE(sp),ap);
	  rg->x0 = sx0;
	  rg->y0 = ap->y_axis_y1;
	  rg->width = sx1-sx0;
	  rg->height = (unsigned int)(ap->y_axis_y0-ap->y_axis_y1);
	  rg->visible = 1;
	  draw_selection_portion(cp,rg);
	}
    }
}

void ripple_selection(chan_info *cp, int beg, int num)
{
  /* if selection channel = cp, and selection samp > beg, fixup samp nums, etc */
  /* this has to be undo-able, so these fixups need to follow edit_ctr -- */
  /* region_context needs an integer array of num which we use before accessing samp0/samp1 */
  region_context *rg;
  int previous_fixup,i,old_size;
  if ((regions[0]) && (rg=region_member(cp,regions[0])))
    {
      if (samp0(rg) > beg)
	{
	  draw_selection_portion(cp,rg);
	  if (!rg->fixups)
	    {
	      rg->fixups = (int *)calloc(cp->edit_size,sizeof(int));
	      rg->fixup_size = cp->edit_size;
	      previous_fixup = 0;
	    }
	  else 
	    {
	      if (cp->edit_size > rg->fixup_size)
		{
		  /* must have reallocated on this edit */
		  old_size = rg->fixup_size;
		  rg->fixup_size = cp->edit_size;
		  rg->fixups = (int *)realloc(rg->fixups,rg->fixup_size * sizeof(int));
		  for (i=old_size;i<rg->fixup_size;i++) rg->fixups[i] = 0;
		}
	      if (cp->edit_ctr <= 0) /* can this happen? */
		previous_fixup = 0;
	      else previous_fixup = rg->fixups[cp->edit_ctr-1];
	    }

	  rg->fixups[cp->edit_ctr] = previous_fixup+num;
	  display_selection(cp);
	}
    }
}

void create_selection(chan_info *cp)
{
  /* called upon initial mouse drag notification and elsewhere */
  /* if we're sync'd collect all */
  snd_info *sp,*nsp;
  snd_state *ss;
  region *r;
  region_context *rg;
  int chans,i,j,k;
  r = (region *)calloc(1,sizeof(region));
  if (regions[0]) stack_region(r); else regions[0] = r;
  sp = cp->sound;
  ss = cp->state;
  report_in_minibuffer(sp,snd_string_defining_region);
  r->type = snd_TYPE(sp);
  r->srate = snd_SRATE(sp);
  r->maxamp = 0.0;
  r->name = (char *)calloc(strlen(sp->shortname)+1,sizeof(char));
  strcpy(r->name,sp->shortname);
  chans = 1;
  if (sp->syncing) chans = syncd_chans(ss);
  r->chans = chans;
  r->rgx = (region_context **)calloc(chans,sizeof(region_context *));
  r->data = (int **)calloc(chans,sizeof(int *));
  /* don't alloc these until definition time when we know the selection length */
  if (sp->syncing)
    {
      k = 0;
      for (i=0;i<ss->max_sounds;i++)
	{
	  nsp = ss->sounds[i];
	  if ((nsp) && (nsp->inuse) && (nsp->syncing))
	    {
	      for (j=0;j<nsp->nchans;j++)
		{
		  rg = (region_context *)calloc(1,sizeof(region_context));
		  r->rgx[k] = rg;
		  /* set cp sp fields while we know what we're doing */
		  rg->sp = nsp;
		  rg->cp = nsp->chans[j];
		  rg->visible = 0;
		  k++;
		}
	    }
	}
    }
  else 
    {
      rg = (region_context *)calloc(1,sizeof(region_context));
      r->rgx[0] = rg;
      rg->sp = sp;
      rg->cp = cp;
      rg->visible = 0;
    }
}

static void load_region(chan_info *cp, region *r, int beg, int end)
{
  /* now look at all sync'd channels, collect them into the current region */
  /* we created the necessary pointers in create_selection above */
  int i,j,len,k,ofd = 0,datumb = 0,val,mval,curval;
  snd_fd **sfs;
  snd_state *ss;
  snd_info *sp;
  file_info *hdr = NULL;
  region_context *rg;
  len = end-beg+1;
  r->len = len;
  val = 0; mval = 0;
  r->start = prettyf((float)beg/(float)(r->srate),2);
  r->end = prettyf((float)end/(float)(r->srate),2);
  sfs = (snd_fd **)calloc(r->chans,sizeof(snd_fd *));
  if (r->len >= MAX_BUFFER_SIZE)
    {
      ss = cp->state;
      sp = cp->sound;
      r->rtype = REGION_FILE;
      r->filename = snd_tempnam(temp_dir(ss),"snd_");
      hdr = make_temp_header(r->filename,sp->hdr,0);
      ofd = open_temp_file(r->filename,r->chans,hdr,ss);
      datumb = c_snd_datum_size(hdr->format);
    }
  else 
    {
      r->rtype = REGION_ARRAY;
      r->filename = NULL;
    }
  for (i=0;i<r->chans;i++)
    {
      rg = r->rgx[i];
      rg->first = beg;
      rg->last = end;
      sfs[i]=init_sample_read(beg,rg->cp,READ_FORWARD);
      if (r->rtype == REGION_ARRAY)
	r->data[i] = (int *)snd_calloc(cp->state,len,sizeof(int));
      else r->data[i] = (int *)snd_calloc(cp->state,MAX_BUFFER_SIZE,sizeof(int));
    }
  for (j=0,k=0;j<len;j++,k++) 
    {
      if (k == MAX_BUFFER_SIZE)
	{
	  clm_write(ofd,0,k-1,r->chans,r->data);
	  k = 0;
	}
      for (i=0;i<r->chans;i++)
	{
	  NEXT_SAMPLE(curval,sfs[i]);
	  r->data[i][k] = curval;
	  if (curval>val) val=curval;
	  if (curval<mval) mval=curval;
	}
    }
  if (r->rtype == REGION_FILE)
    {
      if (k > 0) clm_write(ofd,0,k-1,r->chans,r->data);
      close_temp_file(ofd,hdr,len*r->chans*datumb,cp->sound);
      for (i=0;i<r->chans;i++) free(r->data[i]);
      free(r->data);
      r->data = NULL; /* filename only access in this case */
    }
  if (val < (-mval)) val=-mval;
  r->maxamp = val*clm_sndflt;
  for (i=0;i<r->chans;i++) free_snd_fd(sfs[i]);
  free(sfs);
  reflect_regions_in_menu();
  if (region_browser_is_active()) update_region_browser(1);
}

static int watching_selection = 0;
static int last_selection_x = 0;

static void start_selection_watching(chan_info *cp)
{
  StartSelectionWatch(cp);
  watching_selection = 1;
}

static void cancel_selection_watch(chan_info *cp)
{
  CancelSelectionWatch();
  watching_selection = 0;
}

static void move_selection_1(chan_info *cp, int x)
{
  axis_info *ap;
  int nx;
  ap = cp->axis;
  if ((x > ap->x_axis_x1) || (x < ap->x_axis_x0)) 
    {
      if (((x > ap->x_axis_x1) && (ap->x1 == ap->xmax)) ||
	  ((x < ap->x_axis_x0) && (ap->x0 == ap->xmin)))
	return;
      nx = move_axis(cp,ap,x);
      if (!watching_selection) start_selection_watching(cp);
    }
  else 
    {
      nx = x;
      if (watching_selection) cancel_selection_watch(cp);
    }
  redraw_selection(cp,nx);
}

void move_selection_2(chan_info *cp)
{
  move_selection_1(cp,last_selection_x); /* called via watch work proc */
}

void move_selection(chan_info *cp, int x)
{
  last_selection_x = x; /* called in snd-xchn -- sets last_selection_x */
  move_selection_1(cp,x);
}


/* if the current view is small (i.e. widely spaced individual samples),
 * we need to get only those samples that fall within the selection bounds.
 * But this needs to be a little sloppy for convenience.
 */

static int round_up(double x)
{
  int xint;
  float xfrac;
  xint = (int)x;
  xfrac = x - xint;
  if (xfrac > .2) return(xint+1);
  return(xint);
}

static int round_down(double x)
{
  int xint;
  float xfrac;
  xint = (int)x;
  xfrac = x - xint;
  if (xfrac < .8) return(xint);
  return(xint+1);
}

void define_selection(chan_info *cp)
{
  /* called in snd-xchn.c upon mouse button release */
  region *r;
  region_context *rg;
  float tmp;
  if (watching_selection) cancel_selection_watch(cp);
  keyboard_selecting = 0; /* mouse click might have interrupted kbd definition */
  r = regions[0];
  rg = region_member(cp,r);
  if (rg->sp) clear_minibuffer(rg->sp);
  if (selbeg > selend) {tmp = selbeg; selbeg = selend; selend = tmp;}
  load_region(cp,r,round_up(selbeg * snd_SRATE(cp)),round_down(selend * snd_SRATE(cp)));
  reflect_edit_with_selection_in_menu();
}

void define_region(chan_info *cp, int beg, int end)
{
  deactivate_selection();
  create_selection(cp);
  load_region(cp,regions[0],beg,end);
  clear_minibuffer(cp->sound);
  reflect_edit_with_selection_in_menu();
}

snd_fd *init_region_read (snd_state *ss, int n, int chan, int direction)
{
  /* conjure up a reasonable looking ed list and sound list */
  region *r;
  snd_info *rsp;
  int beg;
  if (region_ok(n))
    {
      r = regions[n];
      make_region_readable(r,ss);
      if ((r) && (chan < r->chans))
	{
	  rsp = r->rsp;
	  if (direction == READ_FORWARD) beg=0; else beg=r->len-1;
	  return(init_sample_read(beg,rsp->chans[chan],direction));
	}
    }
  return(NULL);
}

void play_region(snd_state *ss, int n, void *rg)
{
  region_info *ri;
  finish_keyboard_selection();
  if (!(region_ok(n))) return;
  ri = (region_info *)calloc(1,sizeof(region_info));
  ri->s_type = make_snd_pointer_type(REGION_INFO);
  ri->r = (void *)regions[n];
  ri->n = n;
  ri->rg = rg;
  ri->ss = ss;
  start_playing(ri,0);
}

sync_info *region_sync(int n)
{
  region *r;
  region_context *rg;
  sync_info *si;
  int i;
  finish_keyboard_selection();
  if (region_ok(n)) r = regions[n]; else r=NULL;
  if (r)
    {
      si = (sync_info *)calloc(1,sizeof(sync_info));
      si->chans = r->chans;
      si->cps = (chan_info **)calloc(si->chans,sizeof(chan_info *));
      si->begs = (int *)calloc(si->chans,sizeof(int));
      for (i=0;i<r->chans;i++)
	{
	  rg = r->rgx[i];
	  if (rg)
	    {
	      si->begs[i] = samp0(rg);
	      si->cps[i] = rg->cp;
	    }
	}
      return(si);
    }
  return(NULL);
}

void cleanup_region_temp_files(void)
{ /* called upon exit to get rid of lingering region-related temp files */
  int i;
  region *r;
  for (i=0;i<REGIONS;i++)
    {
      r = regions[i];
      if ((r) && (r->rtype == REGION_FILE) && (r->filename))
	{
	  remove(r->filename);
	  r->filename = NULL;
	}
    }
}

int snd_regions(void)
{
  int i,num;
  num = 0;
  for (i=0;i<REGIONS;i++) if (regions[i]) num++;
  return(num);
}

/* (restore-region n chans len srate maxamp name start end #(chan-1 int-data ...)) */

void save_regions(FILE *fd)
{
  int i,j,k;
  region *r;
  for (i=0;i<REGIONS;i++)
    {
      r = regions[i];
      if (r)
	{
	  fprintf(fd,"(restore-region %d %d %d %d %.4f \"%s\" \"%s\" \"%s\"",
	          i,r->chans,r->len,r->srate,r->maxamp,r->name,r->start,r->end);
	  fprintf(fd,"\n  #(");
	  /* handled as one list for speed in guile -- "uniform vector"? */
	  if (r->rtype == REGION_ARRAY)
	    {
	      for (j=0;j<r->chans;j++)
		{
		  for (k=0;k<r->len;k++)
		    fprintf(fd,"%d ",r->data[j][k]);
		}
	    }
	  else /* file data */
	    {
	      /* read at very low level */
	      int ifd;
	      int **ibufs;
	      ifd = clm_open_read(r->filename);
	      open_clm_file_descriptors(ifd,sound_data_format(r->filename),sound_datum_size(r->filename),sound_data_location(r->filename));
	      clm_seek(ifd,sound_data_location(r->filename),0);
	      ibufs = (int **)calloc(r->chans,sizeof(int *));
	      for (j=0;j<r->chans;j++)
		ibufs[j] = (int *)calloc(r->len,sizeof(int));
	      clm_read(ifd,0,r->len-1,r->chans,ibufs);
	      for (j=0;j<r->chans;j++)
		{
		  for (k=0;k<r->len;k++) 
		    fprintf(fd,"%d ",ibufs[j][k]);
		}
	      clm_close(ifd);
	      for (j=0;j<r->chans;j++) free(ibufs[j]);
	      free(ibufs);
	    }
	  fprintf(fd,"))\n");
	}
    }
}

#if HAVE_GUILE
SCM g_restore_region(SCM n, SCM chans, SCM len, SCM srate, SCM maxamp, SCM name, SCM start, SCM end, SCM data)
{
  region *r;
  int i,j,k;
  r = (region *)calloc(1,sizeof(region));
  regions[gh_scm2int(n)] = r;
  r->rtype = REGION_ARRAY;
  r->maxamp = gh_scm2double(maxamp);
  r->chans = gh_scm2int(chans);
  r->rsp = NULL;
  r->rgx = NULL;
  r->filename = NULL;
  r->len = gh_scm2int(len);
  r->srate = gh_scm2int(srate);
  r->name = gh_scm2newstr(name,NULL);
  r->start = gh_scm2newstr(start,NULL);
  r->end = gh_scm2newstr(end,NULL);
  r->data = (int **)calloc(r->chans,sizeof(int *));
  k=0; 
  for (i=0;i<r->chans;i++)
    {
      r->data[i] = (int *)calloc(r->len,sizeof(int));
      for (j=0;j<r->len;j++,k++)
	r->data[i][j] = gh_scm2int(scm_vector_ref(data,gh_int2scm(k)));
    }
  reflect_regions_in_menu();
  return(SCM_BOOL_F);
}
#endif
