#include "snd.h"

/* to handle undo/redo cleanly, we keep the mark list as an array (indexed to edit_ctr)
 * of arrays of pointers to marks.  Any mark-related operation follows cp->edit_ctr's
 * array within the outer array.  This way, marks come and go in the intuitively clean
 * manner without endless list operations in this file.  Each mark is very small,
 * so the extra data should not be a problem.
 */

typedef mark *mark_map_func(chan_info *cp, mark *mp, mark *m);

static mark *map_over_marks(chan_info *cp, mark_map_func *func, mark *m, int direction)
{
  int i,marks,pos;
  mark *mp;
  mark **mps;
  if (cp->marks)
    {
      pos = cp->edit_ctr;
      mps = cp->marks[pos];
      marks = cp->mark_ctr[pos];
      if (mps)
	{
	  if (direction == READ_FORWARD)
	    {
	      for (i=0;i<=marks;i++) 
		{
		  mp = (*func)(cp,mps[i],m);
		  if (mp) return(mp);
		}
	    }
	  else
	    {
	      for (i=marks;i>=0;i--) 
		{
		  mp = (*func)(cp,mps[i],m);
		  if (mp) return(mp);
		}
	    }
	}
    }
  return(NULL);
}

mark *make_mark(int samp, char *name)
{
  mark *mp;
  mp = (mark *)calloc(1,sizeof(mark));
  if (name) mp->name = copy_string(name); else mp->name = NULL;
  mp->samp = samp;
  mp->visible = 0;
  return(mp);
}

static mark *free_mark (mark *mp)
{
  if (mp)
    {
      if (mp->name) free(mp->name);
      free(mp);
    }
  return(NULL);
}

static mark *find_named_mark_1(chan_info *cp, mark *mp, mark *m)
{
  if ((mp->name) && (strcmp(mp->name,m->name) == 0)) return(mp);
  else return(NULL);
}

static mark *find_named_mark(chan_info *cp, char *name)
{
  mark *m,*mp;
  m=make_mark(0,name);
  mp = map_over_marks(cp,find_named_mark_1,m,READ_FORWARD);
  free_mark(m);
  return(mp);
}

static mark *find_previous_mark_1(chan_info *cp, mark *mp, mark *m)
{
  if (mp->samp < m->samp) return(mp);
  else return(NULL);
}

static mark *find_previous_mark (int current_sample, chan_info *cp)
{
  mark *m,*mp;
  m=make_mark(current_sample,NULL);
  mp = map_over_marks(cp,find_previous_mark_1,m,READ_BACKWARD);
  free_mark(m);
  return(mp);
}

static mark *find_next_mark_1(chan_info *cp, mark *mp, mark *m)
{
  if (mp->samp > m->samp) return(mp);
  else return(NULL);
}

static mark *find_next_mark (int current_sample, chan_info *cp)
{
  mark *m,*mp;
  m=make_mark(current_sample,NULL);
  mp = map_over_marks(cp,find_next_mark_1,m,READ_FORWARD);
  free_mark(m);
  return(mp);
}

static mark* marks_off_1(chan_info *cp, mark *mp, mark *m)
{
  mp->visible = 0;
  return(NULL);
}

void marks_off(chan_info *cp)
{
  map_over_marks(cp,marks_off_1,NULL,READ_FORWARD);
}


#define PLAY_ARROW_SIZE 10
#define MARK_TAB_WIDTH 10
#define MARK_TAB_HEIGHT 4

static void draw_mark_1(chan_info *cp, axis_info *ap, mark *mp, int show)
{
  /* fields are samp and name */
  int top,len,cx,y0,y1;
  axis_context *ax;
  ax = cursor_context(cp);
  top = ap->y_axis_y1;
  y1 = top;
  y0 = ap->y_axis_y0;
  if (mp->name) top += 10;
  cx = grf_x((double)(mp->samp)/(double)snd_SRATE(cp),ap);
  if (mp->name)
    {
      set_button_font(ax,cp->state);
      len = mark_name_width(cp->state,mp->name);
      draw_string(ax,cx-0.5*len,y1+6,mp->name,strlen(mp->name));
    }
  fill_rectangle(ax,
		 cx - MARK_TAB_WIDTH,top,
		 2*MARK_TAB_WIDTH,MARK_TAB_HEIGHT);
  draw_line(ax,cx,top+4,cx,y0);
  fill_polygon(ax,4,
	       cx,                y0,
	       cx+PLAY_ARROW_SIZE,y0 +   PLAY_ARROW_SIZE,
	       cx,                y0 + 2*PLAY_ARROW_SIZE,
	       cx,                y0);
  mp->visible = show;
}

static void draw_play_triangle(chan_info *cp, int x)
{
  int y0;
  y0 = ((axis_info *)(cp->axis))->y_axis_y0;
  draw_polygon(cursor_context(cp),4,
	       x,                y0,
	       x+PLAY_ARROW_SIZE,y0 +   PLAY_ARROW_SIZE,
	       x,                y0 + 2*PLAY_ARROW_SIZE,
	       x,                y0);
}

void draw_mark(chan_info *cp, axis_info *ap, mark *mp)
{
  if (!(mp->visible)) draw_mark_1(cp,ap,mp,1);
}

static void erase_mark(chan_info *cp, axis_info *ap, mark *mp)
{
  if (mp->visible) draw_mark_1(cp,ap,mp,0);
}

static mark *hit_mark_1(chan_info *cp, mark *mp, mark *m)
{
  /* m->samp = raw x mouse position */
  /* we're going left to right, so after passing x, give up */
  int mx,y;
  axis_info *ap;
  mx = grf_x((double)(mp->samp)/(double)snd_SRATE(cp),cp->axis);
  if (mx > (m->samp+MARK_TAB_WIDTH)) return(m);    /* past it */
  if (mx < (m->samp-MARK_TAB_WIDTH)) return(NULL); /* before it */
  if (mp->name == NULL)                            /* check y if unnamed */
    {
      ap = cp->axis;
      y = m->visible;
      if ((y >= ap->y_axis_y1) && (y <= (ap->y_axis_y1+MARK_TAB_HEIGHT))) 
	return(mp); 
      else return(NULL);
    }
  else return(mp);
}

mark *hit_mark(chan_info *cp, int x, int y)
{
  mark *m,*mp;
  axis_info *ap;
  ap = cp->axis;
  if (cp->marks)
    {
      /* first check that we're in the top portion of the graph where the mark tabs are */
      if ((y >= ap->y_axis_y1) && (y <= (ap->y_axis_y1+MARK_TAB_HEIGHT+10))) /* +10 for named marks -- checked again later */
	{
	  m = make_mark(x,NULL);
	  m->visible = y;         /* kludge upon kludge... */
	  mp = map_over_marks(cp,hit_mark_1,m,READ_FORWARD);
	  if (mp == m) mp = NULL; /* premature exit flag */
	  free_mark(m);
	  return(mp);
	}
    }
  return(NULL);
}

static mark *hit_triangle_1(chan_info *cp, mark *mp, mark *m)
{
  /* m->samp = raw x mouse position */
  /* we're going left to right, so after passing x, give up */
  int mx,y;
  axis_info *ap;
  mx = grf_x((double)(mp->samp)/(double)snd_SRATE(cp),cp->axis);
  if (mx > m->samp) return(m);
  if ((mx+PLAY_ARROW_SIZE) < m->samp) return(NULL);
  ap = cp->axis;
  y = m->visible - ap->y_axis_y0 - PLAY_ARROW_SIZE;
  if (y < 0) y = -y;
  if ((mx+PLAY_ARROW_SIZE-y) >= m->samp) return(mp);
  /* the last is assuming the triangle shape for hit detection */
  return(NULL);
}

mark *hit_triangle(chan_info *cp, int x, int y)
{
  mark *m,*mp;
  axis_info *ap;
  ap = cp->axis;
  if (cp->marks)
    {
      /* first check that we're in the bottom portion of the graph where the mark triangles are */
      if ((y >= ap->y_axis_y0) && (y <= (ap->y_axis_y0+2*PLAY_ARROW_SIZE)))
	{
	  m = make_mark(x,NULL);
	  m->visible = y;         /* kludge upon kludge... */
	  mp = map_over_marks(cp,hit_triangle_1,m,READ_FORWARD);
	  if (mp == m) mp = NULL; /* premature exit flag */
	  free_mark(m);
	  return(mp);
	}
    }
  return(NULL);
}


static int watching_mouse = 0;
static int last_mouse_x = 0;
static mark *moving_mark = NULL;

static void start_mark_watching(chan_info *cp, mark *mp)
{
  moving_mark = mp;
  StartMarkWatch(cp);
  watching_mouse = 1;
}

static void cancel_mark_watch(chan_info *cp)
{
  CancelMarkWatch();
  watching_mouse = 0;
  moving_mark = NULL;
}

static void move_mark_1(chan_info *cp, mark *mp, int x)
{
  axis_info *ap;
  int nx,samps;
  ap = cp->axis;
  erase_mark(cp,ap,mp);
  if ((x > ap->x_axis_x1) || (x < ap->x_axis_x0)) 
    {
      if (watching_mouse)
	{
	  if ((x < ap->x_axis_x0) && (ap->x0 == ap->xmin)) return;
	  if ((x > ap->x_axis_x1) && (ap->x1 == ap->xmax)) return;
	}
      nx = move_axis(cp,ap,x);
      if (!watching_mouse) start_mark_watching(cp,mp);
    }
  else 
    {
      nx = x;
      if (watching_mouse) cancel_mark_watch(cp);
    }
  mp->samp = ungrf_x(ap,nx) * snd_SRATE(cp);
  if (mp->samp < 0) mp->samp = 0;
  samps = current_ed_samples(cp);
  if (mp->samp > samps) mp->samp = samps;
  if (!watching_mouse) draw_mark(cp,ap,mp);
}

void move_mark_2(chan_info *cp)
{
  move_mark_1(cp,moving_mark,last_mouse_x);
}

void move_mark(chan_info *cp, mark *mp, int x, int y)
{
  last_mouse_x = x;
  move_mark_1(cp,mp,x);
}


static int prev_cx = -1;

int move_play_mark(chan_info *cp, int *mc, int cx)
{
  /* mc = mouse loc sampwise return samps updating mc */
  int cur_mc;
  axis_info *ap;
  ap = cp->axis;
  if (prev_cx > 0) draw_play_triangle(cp,prev_cx);
  prev_cx = cx;
  draw_play_triangle(cp,cx);
  cur_mc = (*mc);
  (*mc) = ungrf_x(ap,cx) * snd_SRATE(cp);
  return((*mc) - cur_mc);
}

void finish_moving_play_mark(chan_info *cp)
{
  snd_info *sp;
  sp = cp->sound;
  draw_play_triangle(cp,prev_cx);
  prev_cx = -1;
  sp->srate = 1.0;
}

static int compare_mark_samps(const void *mp1, const void *mp2)
{
  mark *m1,*m2;
  m1 = (mark *)(*((mark **)mp1));
  m2 = (mark *)(*((mark **)mp2));
  if (m1->samp < m2->samp) return(-1);
  if (m1->samp == m2->samp) return(0);
  return(1);
}

void finish_moving_mark(chan_info *cp, mark *mp)
{
  /* make sure marks are in order still */
  mark **mps;
  int ed;
  if (watching_mouse) cancel_mark_watch(cp);
  ed = cp->edit_ctr;
  mps = cp->marks[ed];
  qsort((void *)mps,cp->mark_ctr[ed]+1,sizeof(mark *),compare_mark_samps);
}


static int ignore_redundant_marks = 1;

static void allocate_marks(chan_info *cp, int edit_ctr)
{
  int i;
  cp->marks_size = edit_ctr+16;
  cp->marks = (mark ***)calloc(cp->marks_size,sizeof(mark **));
  cp->mark_size = (int *)calloc(cp->marks_size,sizeof(int));
  cp->mark_ctr = (int *)calloc(cp->marks_size,sizeof(int));
  for (i=0;i<cp->marks_size;i++) cp->mark_ctr[i] = -1;
}

mark *add_mark(int samp, char *name, chan_info *cp)
{
  int i,j,ed;
  mark **mps;
  mark *mp;
  if (!(cp->marks)) allocate_marks(cp,cp->edit_ctr);
  /* our current mark list is cp->edit_ctr (it starts at 0 -- see snd-chn.c and snd-edits.c) */
  ed = cp->edit_ctr;
  cp->mark_ctr[ed]++;
  if (cp->mark_ctr[ed] >= cp->mark_size[ed])
    {
      cp->mark_size[ed] += 16;
      if (!cp->marks[ed]) cp->marks[ed] = (mark **)calloc(cp->mark_size[ed],sizeof(mark *));
      else 
	{
	  cp->marks[ed] = (mark **)realloc(cp->marks[ed],cp->mark_size[ed] * sizeof(mark *));
	  for (i=cp->mark_size[ed]-16;i<cp->mark_size[ed];i++) cp->marks[ed][i] = NULL;
	}
    }
  mps = cp->marks[ed];
  if (cp->mark_ctr[ed] == 0)
    {
      mps[0] = make_mark(samp,name);
      return(mps[0]);
    }
  else
    {
      for (i=0;i<cp->mark_ctr[ed];i++)
	{
	  mp = mps[i];
	  if ((ignore_redundant_marks) && (samp == mp->samp))
	    {
	      cp->mark_ctr[ed]--;
	      return(NULL);
	    }
	  if (samp < mp->samp)
	    {
	      for (j=cp->mark_ctr[ed];j>i;j--)
		{
		  mps[j] = mps[j-1];
		}
	      mps[i] = make_mark(samp,name);
	      return(mps[i]);
	    }
	}
      /* insert at end */
      mps[cp->mark_ctr[ed]] = make_mark(samp,name);
      return(mps[cp->mark_ctr[ed]]);
    }
}

void delete_mark(int samp, chan_info *cp)
{
  int i,j,ed;
  mark *mp;
  mark **mps;
  axis_info *ap;
  if ((cp) && (cp->marks))
    {
      ed = cp->edit_ctr;
      mps = cp->marks[ed];
      if (mps)
	{
	  for (i=0;i<=cp->mark_ctr[ed];i++)
	    {
	      mp = mps[i];
	      if (mp->samp == samp)
		{
		  ap = cp->axis;
		  if ((mp->samp >= ap->losamp) && (mp->samp <= ap->hisamp)) erase_mark(cp,cp->axis,mp); 
		  free_mark(mp);
		  mps[i] = NULL;
		  for (j=i;j<=cp->mark_ctr[ed];j++)
		    {
		      mps[j] = mps[j+1];
		    }
		  cp->mark_ctr[ed]--;
		  break;
		}
	    }
	}
    }
}

void free_mark_list(chan_info *cp, int ignore)
{
  int i,j;
  mark **mps;
  mark *mp;
  if (cp->marks)
    {
      for (i=0;i<cp->marks_size;i++) 
	{
	  if (i != ignore)
	    {
	      mps = cp->marks[i];
	      if (mps)
		{
		  for (j=0;j<=cp->mark_ctr[i];j++)
		    {
		      mp = mps[j];
		      if (mp) free_mark(mp);
		    }
		  free(mps);
		}
	    }
	}
      free(cp->marks);
      cp->marks = NULL;
      free(cp->mark_ctr);
      cp->mark_ctr = NULL;
      free(cp->mark_size);
      cp->mark_size = NULL;
    }
}

void collapse_marks (snd_info *sp)
{
  /* in all channels, move current edit_ctr mark list to 0, freeing all the rest */
  int i,ed,len;
  chan_info *cp;
  mark **mps;
  for (i=0;i<sp->nchans;i++)
    {
      cp = sp->chans[i];
      if ((cp) && (cp->marks))
	{
	  ed = cp->edit_ctr;
	  mps = cp->marks[ed];
	  /* this is the one to save */
	  if (mps)
	    {
	      len = cp->mark_ctr[ed];
	      free_mark_list(cp,ed);
	      allocate_marks(cp,0);
	      cp->marks[0] = mps;
	      cp->mark_ctr[0] = len;
	    }
	}
    }
}

int goto_mark(chan_info *cp, int count)
{
  int i,c,samp;
  mark *mp;
  if ((!cp) || (!cp->marks)) 
    {
      report_in_minibuffer(cp->sound,"no marks");
      return(CURSOR_IN_VIEW);
    }
  if (count > 0) c=count; else c=-count;
  samp = cp->cursor;
  for (i=0;i<c;i++)
    {
      if (count > 0) mp = find_next_mark(samp,cp);
      else mp = find_previous_mark(samp,cp);
      if (!mp) break;
      samp = mp->samp;
    }
  return(cursor_moveto(cp,samp));
}

int goto_named_mark(chan_info *cp, char *name)
{
  mark *mp;
  mp = find_named_mark(cp,name);
  if (mp) return(cursor_moveto(cp,mp->samp));
  return(CURSOR_IN_VIEW);
}

static mark *active_mark_1(chan_info *cp, mark *mp, mark *m)
{
  axis_info *ap;
  ap = cp->axis;
  if ((mp->samp >= ap->losamp) && (mp->samp <= ap->hisamp)) return(mp); 
  return(NULL);
}

mark *active_mark(chan_info *cp)
{
  return(map_over_marks(cp,active_mark_1,NULL,READ_FORWARD));
}

int mark_beg(chan_info *cp)
{
  mark *mp;
  mp = active_mark(cp);
  if (mp) return(mp->samp);
  return(-1);
}

static mark *display_channel_marks_1(chan_info *cp,  mark *mp, mark *m)
{
  axis_info *ap;
  ap = cp->axis;
  if (mp->samp > ap->hisamp) return(mp); /* terminates loop */
  if ((mp->samp >= ap->losamp) && (mp->samp <= ap->hisamp) && (mp != moving_mark)) draw_mark(cp,ap,mp);
  return(NULL);
}

void display_channel_marks(chan_info *cp)
{
  map_over_marks(cp,display_channel_marks_1,NULL,READ_FORWARD);
}

int add_named_mark(chan_info *cp) 
{
  /* prompt for name in W_snd_info */
  snd_info *sp = cp->sound;
  make_button_label(snd_widget(sp,W_snd_info_label),snd_string_mark_p);
  sp->minibuffer_on = 1;
  clear_minibuffer(sp);
  goto_minibuffer(sp);
  sp->marking = cp->cursor+1;
  return(CURSOR_IN_VIEW);
}


void release_pending_marks(chan_info *cp, int edit_ctr)
{
  /* free the mark list at edit_ctr */
  mark **mps;
  mark *mp;
  int j;
  if ((cp) && (cp->marks))
    {
      mps = cp->marks[edit_ctr];
      if (mps)
	{
	  for (j=0;j<=cp->mark_ctr[edit_ctr];j++)
	    {
	      mp = mps[j];
	      if (mp) free_mark(mp);
	    }
	  cp->mark_ctr[edit_ctr] = -1;
	}
    }
}

void ripple_marks(chan_info *cp, int beg, int change)
{
  /* if change = 0, just set ptr, else copy and fixup with deletions */
  /* this is called after the tree has been pushed forward, so edit_ctr is ahead of us */
  /* but we don't do anything if no marks */
  int old,noo,end,i;
  mark **mps,**mpo;
  mark *mp;
  if ((cp) && (cp->marks))
    {
      old = cp->edit_ctr-1;
      noo = cp->edit_ctr;
      if (noo>=cp->marks_size) /* groan -- we have to realloc the base array of array of pointers! */
	{
	  cp->marks_size += 16;
	  cp->marks = (mark ***)realloc(cp->marks,cp->marks_size * sizeof(mark **));
	  cp->mark_size = (int *)realloc(cp->mark_size,cp->marks_size * sizeof(int));
	  cp->mark_ctr = (int *)realloc(cp->mark_ctr,cp->marks_size * sizeof(int));
	  for (i=noo;i<cp->marks_size;i++) 
	    {
	      cp->mark_ctr[i] = -1;
	      cp->mark_size[i] = 0;
	      cp->marks[i] = NULL;
	    }
	}
      cp->mark_size[noo] = cp->mark_size[old];
      cp->mark_ctr[noo] = cp->mark_ctr[old];
      cp->marks[noo] = (mark **)calloc(cp->mark_size[noo],sizeof(mark *));
      mps = cp->marks[noo];
      mpo = cp->marks[old];
      for (i=0;i<=cp->mark_ctr[noo];i++)
	{
	  mps[i] = make_mark(mpo[i]->samp,mpo[i]->name);
	}
      if (change < 0)
	{
	  /* if (change<0) and any marks are between beg and beg+change, they must be deleted */
	  end = beg-change-1;
	  i=0;
	  while (i<=cp->mark_ctr[noo])
	    {
	      mp = mps[i];
	      if ((mp->samp >= beg) && (mp->samp <= end)) /* was mp->samp > beg, ditto end, beg can = end */
		delete_mark(mp->samp,cp); /* changes cp->mark_ctr, hence the while loop */
	      else 
		{
		  if (mp->samp > beg)   /* don't change marks that precede the point of the change */
		    mp->samp+=change;
		  i++;
		}
	    }
	}
      else
	{
	  if (change > 0)
	    {
	      for (i=0;i<=cp->mark_ctr[noo];i++)
		{
		  mp = mps[i];
		  if (mp->samp > beg) mp->samp+=change;
		}
	    }
	}
    }
}

void mark_define_region(chan_info *cp,int count)
{
  int beg,end,temp;
  beg = cp->cursor;
  goto_mark(cp,count);
  end = cp->cursor;
  cp->cursor = beg;
  if (end < beg) {temp = end; end = beg; beg = temp;}
  define_region(cp,beg,end);
}

static void add_chan_marks (snd_info *sp, char *name, int samp)
{
  int j;
  chan_info *cp;
  if (strcmp(name,"NIL") == 0) name = NULL;
  for (j=0;j<(sp->nchans);j++)
    {
      cp=((chan_info *)(sp->chans[j]));
      add_mark(samp,name,cp);
    }
}

static void add_chan_mark (snd_info *sp, char *name, int samp, int chan)
{
  if (strcmp(name,"NIL") == 0) name = NULL;
  if (chan < sp->nchans)
    add_mark(samp,name,(chan_info *)(sp->chans[chan]));
}

static char *mark_file_name(snd_info *sp)
{
  char *newname;
  int len,i;
  len = strlen(sp->fullname);
  newname = (char *)calloc(len+7,sizeof(char));
  strcpy(newname,sp->fullname);
  for (i=len-1;i>0;i--) if (newname[i] == '.') break;
  if (i>0) len=i;
  newname[len]='.'; newname[len+1]='m'; newname[len+2]='a'; newname[len+3]='r'; newname[len+4]='k'; newname[len+5]='s'; newname[len+6]='\0';
  return(newname);
}

void clm_add_marks(snd_info *sp)
{
  /* sp->fullname + ".marks" = possible mark file */
  char *newname;
  int beg,end,mark_style,happy,chan,len;
  time_t snd_time,marks_time;
  char str[256];
  FILE *fd;
  len = 0;
  snd_time = file_write_date(sp->fullname);
  newname = mark_file_name(sp);
  fd = fopen(newname,"r");
  if (fd)
    {
      /* check for "MARK n " and decode the n */
      /* the "n" is decoded as 4 bits: 8=chan, 4=named, 2=begin, 1=end, each mark enclosed in parens */
      /* order is ([name] [beg] [end] [chan]) */
      marks_time = file_write_date(newname);
      if (marks_time >= snd_time)
	{
	  fscanf(fd,"%s %d ",str,&mark_style);
	  if (strcmp(str,"MARK") == 0)
	    {
	      happy = 1;
	      if ((mark_style <= 0) || (mark_style == 4) || (mark_style == 12) || (mark_style == 8) || (mark_style > 15)) happy = 0;
	      ignore_redundant_marks = 0;
	      while (happy)
		{
		  switch (mark_style)
		    {
		    case 1:  len = fscanf(fd,"(%d) ",&end); break;
		    case 2:  len = fscanf(fd,"(%d) ",&beg); break;
		    case 3:  len = fscanf(fd,"(%d %d) ",&beg,&end); break;
		    case 5:  len = fscanf(fd,"(%s %d) ",str,&end); break;
		    case 6:  len = fscanf(fd,"(%s %d) ",str,&beg); break;
		    case 7:  len = fscanf(fd,"(%s %d %d) ",str,&beg,&end); break;
		    case 9:  len = fscanf(fd,"(%d %d) ",&end,&chan); break;
		    case 10: len = fscanf(fd,"(%d %d) ",&beg,&chan); break;
		    case 11: len = fscanf(fd,"(%d %d %d) ",&beg,&end,&chan); break;
		    case 13: len = fscanf(fd,"(%s %d %d) ",str,&end,&chan); break;
		    case 14: len = fscanf(fd,"(%s %d %d) ",str,&beg,&chan); break;
		    case 15: len = fscanf(fd,"(%s %d %d %d) ",str,&beg,&end,&chan); break;
		    }
		  happy = ((len != EOF) && (len != 0));
		  /* "len" here is how many fields were matched during fscanf -- keep the trailing space!!  &%#$!@ C */
		  /* whenever I use a C library function, I end up wishing I had done it by hand. */
		  if (happy)
		    {
		      switch (mark_style)
			{
			case 1:  add_chan_marks(sp,NULL,end); break;
			case 2:  add_chan_marks(sp,NULL,beg); break;
			case 3:  add_chan_marks(sp,NULL,beg); add_chan_marks(sp,NULL,end); break;
			case 5:  add_chan_marks(sp,str,end); break;
			case 6:  add_chan_marks(sp,str,beg); break;
			case 7:  add_chan_marks(sp,str,beg); add_chan_marks(sp,str,end); break;
			case 9:  add_chan_mark(sp,NULL,end,chan); break;
			case 10: add_chan_mark(sp,NULL,beg,chan); break;
			case 11: add_chan_mark(sp,NULL,beg,chan); add_chan_mark(sp,NULL,end,chan); break;
			case 13: add_chan_mark(sp,str,end,chan); break;
			case 14: add_chan_mark(sp,str,beg,chan); break;
			case 15: add_chan_mark(sp,str,beg,chan); add_chan_mark(sp,str,end,chan); break;
			}
		    }
		}
	    }
	  ignore_redundant_marks = 1;
	}
      fclose(fd);
    }
  free(newname);
}

static int find_any_marks (chan_info *cp, void *ptr)
{
  if (cp->marks) return(cp->mark_ctr[cp->edit_ctr]+1); /* initialized to -1 -- 0 is first mark */
  return(0);
}

static int report_any_marks(chan_info *cp, void *ptr)
{
  int i,marks,pos;
  mark **mps;
  mark *m;
  FILE *fd = (FILE *)ptr;
  if (cp->marks)
    {
      pos = cp->edit_ctr;
      mps = cp->marks[pos];
      marks = cp->mark_ctr[pos];
      if (mps)
	{
	  for (i=0;i<=marks;i++) 
	    {
	      m = mps[i];
	      fprintf(fd," (%s %d %d)",(m->name) ? m->name : "NIL",m->samp,cp->chan);
	    }
	}
    }
  return(0);
}

void save_marks(snd_info *sp)
{
  char *newname;
  char str[256];
  FILE *fd;
  if ((sp) && (map_over_sound_chans(sp,find_any_marks,NULL)))
    {
      newname = mark_file_name(sp);
      fd = fopen(newname,"w");
      if (fd)
	{
	  fprintf(fd,"MARK 14");
	  map_over_sound_chans(sp,report_any_marks,(void *)fd);
	  fclose(fd);
	}
      else 
	{
	  sprintf(str,"%s %s ",newname,strerror(errno));
	  report_in_minibuffer(sp,str);
	}
      free(newname);
    }
}

void save_mark_list(FILE *fd, chan_info *cp)
{
  /* assumes we're calling from the edit history list maker in snd-edits.c */
  /* as with edit-tree, graft these lists onto the end of the current mark list */
  /*   if none, pad out to current cp->edit_ctr? */
  /*   since the edits ripple the marks, we'll have to assume we're called later and hope */
  int i,j,marks;
  mark **mps;
  mark *m;
  if (cp->marks)
    {
      fprintf(fd,"      (restore-marks %d sfile %d '(",cp->marks_size,cp->chan);
      for (i=0;i<cp->marks_size;i++)
	{
	  fprintf(fd,"\n        (%d %d (",cp->mark_size[i],cp->mark_ctr[i]);
	  mps = cp->marks[i];
	  if (mps)
	    {
	      marks = cp->mark_ctr[i];
	      for (j=0;j<=marks;j++)
		{
		  m = mps[j];
		  if (m)
		    {
		      if (m->name)
			fprintf(fd,"(%s %d) ",m->name,m->samp);
		      else fprintf(fd,"%d ",m->samp);
		    }
		  else fprintf(fd,"-1 ");
		}
	    }
	  fprintf(fd,"))");
	}
      fprintf(fd,"))\n");
    }
}
