#include "snd.h"

/* support for mixing operations */

/* connection to rest of Snd (leaving aside snd-xmix.c):
 * --------
 *  Chan_info cleanup and save:
 *    void free_mixes(chan_info *cp) 
 *    void free_mix_list(chan_info *cp)
 *
 *  Mix console update (channel display):
 *    void update_all_consoles(snd_state *ss)
 *
 *  Add mix
 *    void mix_array(int beg, int num, int **data, chan_info **out_cps, int in_chans, int out_chans, int from_ufun, int nominal_srate)
 *    void mix_file(int beg, int num, char *file, chan_info **cps, int out_chans, int from_ufun)
 *    void mix_complete_file(snd_info *sp, char *str, int source)
 *
 *  Display active consoles, undisplay others:
 *    void display_channel_mixes(chan_info *cp)
 *
 *  Release mixes that are clobbered by undo followed by edit
 *    void release_pending_mixes(chan_info *cp, int edit_ctr)
 *
 *  Lock out over-written mixes 
 *    void lock_affected_mixes(chan_info *cp, int beg, int end)
 *
 *  Ignore mix-drag related expose events
 *    int mix_dragging(void)
 *
 *  Save mix console state across file save
 *    reset_mix_list(chan_info *cp)
 *
 *  Load CLM with-sound explode mixes
 *    clm_add_mixes(snd_info *sp)
 * 
 *  Release mix consoles if time domain wave not currently displayed
 *    release_mixmarks(chan_info *cp)
 *
 * --------
 * not using snd-marks.c mark mechanism here to keep this connection as simple as possible.
 */


/* ---------------- MIX ENVELOPES ---------------- */

typedef struct {
  int size,pass,index;
  float y0,rate,value;
  float *data;
} mixenv;

static mixenv *copy_mixenv(mixenv *e)
{
  mixenv *newe = NULL;
  int i;
  if (e)
    {
      newe = (mixenv *)calloc(1,sizeof(mixenv));
      newe->size = e->size;
      newe->y0 = e->y0;
      newe->data = (float *)calloc(e->size,sizeof(float));
      for (i=0;i<e->size;i++) newe->data[i] = e->data[i];
    }
  return(newe);
}

static mixenv *free_mixenv(mixenv *e)
{
  if (e)
    {
      if (e->data) free(e->data);
      free(e);
    }
  return(NULL);
}

static void start_mixenv(mixenv *e)
{
  if (e)
    {
      e->value = e->y0;
      e->index = 0;
      e->rate = e->data[1];
      e->pass = e->data[0];
    }
}

static float env_val(mixenv *e)
{
  float val = 1.0;
  if (e)
    {
      val = e->value;
      e->value += e->rate;
      e->pass--;
      if (e->pass < 0)
	{
	  e->index+=2;
	  e->pass = e->data[e->index];
	  e->rate = e->data[e->index+1];
	}
    }
  return(val);
}

static void tick_env(mixenv *e, int passes)
{
  e->value += (e->rate * passes);
  e->pass -= passes;
  if (e->pass <= 0)
    {
      e->index+=2;
      e->pass = e->data[e->index];
      e->rate = e->data[e->index+1];
    }
}

#define infinity (1<<30)

static mixenv *meld_env(console_state *cs, env *ge, int beg, int dur)
{
  mixenv *e;
  int i,j;
  float *res;
  float xdur,xmag,nbeg,nend,x0,x1,y0,y1,inity = 0.0,xx0,xx1;
  res = (float *)calloc(ge->pts*2 + 2,sizeof(float));
  x1 = ge->data[0];
  xdur = ge->data[ge->pts*2 - 2] - x1;
  nbeg = x1 + xdur * (float)(cs->beg - beg)/(float)dur;
  nend = nbeg + xdur * (float)(cs->len)/(float)dur;
  xmag = (float)dur/xdur;
  y1 = ge->data[1];
  for (j=0,i=2;i<ge->pts*2;i+=2)
    {
      x0 = x1;
      x1 = ge->data[i];
      y0 = y1;
      y1 = ge->data[i+1];
      if ((x1 >= nbeg) && (x0 < nend))
	{
	  /* at least part of this segment counts -- this is a form of magify_env (snd-chn.c) */
	  if (x0 < nbeg) xx0 = nbeg; else xx0 = x0;
	  if (x1 > nend) xx1 = nend; else xx1 = x1;
	  res[j] = (xx1 - xx0) * xmag;
	  if (j == 0) inity = y0 + (y1-y0)*(nbeg-x0)/(x1-x0);
	  if (y0 == y1)
	    res[j+1] = 0.0;
	  else res[j+1] = (y1-y0)*(xx1-xx0)/(res[j]*(x1-x0));
	  j+=2;
	}
      else if (x0 >= nend) break;
    }
  res[j] = infinity; 
  res[j+1] = 0.0; /* in case we read past the end of the envelope */
  e = (mixenv *)calloc(1,sizeof(mixenv));
  e->size = j + 2;
  e->pass = 0;
  e->index = 0;
  e->data = res;
  e->y0 = inity;
  e->rate = 0.0;
  e->value = 0.0;
  /*
    fprintf(stderr,"got: global env '(");
    for (i=0;i<ge->pts*2;i++) fprintf(stderr,"%f ",ge->data[i]);
    fprintf(stderr,"), from %d for %d, cut to %d for %d, returned '(",begs[0],durs[0],cs->beg,cs->len);
    for (i=0;i<j+2;i++) fprintf(stderr,"%f ",res[i]);
    fprintf(stderr,") -> '(0.0 %f ",inity);
    for (x0=0.0,i=0;i<j;i+=2)
      {
       x0 += (float)(res[i])/(float)(cs->len);
       inity += (res[i+1]*res[i]);
       fprintf(stderr,"%f %f ",x0,inity);
      }
    fprintf(stderr,")\n");
    */
  return(e);
}

static mixenv *meld_envs(console_state *cs, env **ges, int *begs, int *durs, int num)
{
  /* make one env that reflects the contributions of all envs in the ges array */
  int i,size,j,k,passes,beg;
  float *res;
  float y1,y0;
  mixenv *newe;
  mixenv **mes;
  if (num == 0)
    return(NULL);  /* should not happen */
  else
    {
      if (num == 1)
	return(meld_env(cs,ges[0],begs[0],durs[0]));
      else
	{
	  /* make all look the same (x-axis), get our portion, meld into one, and magify_env as above */
	  mes = (mixenv **)calloc(num,sizeof(mixenv *));
	  size = 0;
	  for (i=0;i<num;i++)
	    {
	      mes[i] = meld_env(cs,ges[i],begs[i],durs[i]);
	      size += mes[i]->size; 
	    }
	  /* now all group contributions are normalized to be mixenvs of the same duration */
	  size *= 4;
	  res = (float *)calloc(size,sizeof(float));
	  /* now run through all envs in parallel making some plausible new envelope */
	  /* this uses short segments as is multiplying endpoints, but splits longer segments into 4 pieces */
	  /* the point being to come reasonably close to the non-linear curves we'd get if we multiplied on a sample-by-sample basis */
	  for (i=0;i<num;i++) start_mixenv(mes[i]);
	  newe = (mixenv *)calloc(1,sizeof(mixenv));
	  y0 = mes[0]->y0;
	  for (i=1;i<num;i++) y0 *= mes[i]->y0;
	  newe->y0 = y0;
	  j=0;
	  beg = 0;
	  while (beg < cs->len)
	    {
	      passes = cs->len;
	      for (i=0;i<num;i++)
		{
		  if (mes[i]->pass < passes) passes = mes[i]->pass;
		}
	      beg += passes;
	      if (passes > 2000)
		{
		  size = passes/4;
		  for (i=0;i<3;i++)
		    {
		      res[j] = (float)size; 
		      passes -= size;
		      y1 = 1.0;
		      for (k=0;k<num;k++) 
			{
			  tick_env(mes[k],size);
			  y1 *= mes[k]->value;
			}
		      res[j+1] = (y1 - y0) / (float)size;
		      y0 = y1;
		      j+=2;
		    }
		}
	      res[j] = (float)passes;
	      y1 = 1.0;
	      for (k=0;k<num;k++) 
		{
		  tick_env(mes[k],passes);
		  y1 *= mes[k]->value;
		}
	      res[j+1] = (y1 - y0) / (float)passes;
	      y0 = y1;
	      j+=2;
	    }
	  res[j] = infinity; 
	  res[j+1] = 0.0; /* in case we read past the end of the envelope */
	  newe->size = j + 2;
	  newe->pass = 0;
	  newe->index = 0;
	  newe->data = res;
	  newe->rate = 0.0;
	  newe->value = 0.0;
	  for (i=0;i<num;i++) free_mixenv(mes[i]);
	  free(mes);
	  return(newe);
	}
    }
}


/* ---------------- MIX POOL ---------------- */

#define MPOOL_INCREMENT 16

static console_state *make_console_state(int chans, int edit_ctr, int beg, int end)
{
  console_state *cs;
  cs = (console_state *)calloc(1,sizeof(console_state));
  cs->chans = chans;
  cs->edit_ctr = edit_ctr;
  cs->orig = beg;
  cs->beg = beg;
  cs->end = end;
  cs->len = end-beg+1;
  cs->locked = 0;
  cs->syncable = 1;
  cs->groups = 0;
  cs->scalers = (float *)calloc(chans,sizeof(float));
  cs->old_scalers = (float *)calloc(chans,sizeof(float));
  cs->scl_scalers = (float *)calloc(chans,sizeof(float));
  /* scalers hold the current globally determined state of the amp scalers,
     scl_scalers hold the local (scale widget relative) value
     old_scalers hold the last local value set explicitly by the user
     */
  cs->gs_amp = 1.0;
  cs->gs_speed = 1.0;
  cs->gs_tempo = 1.0;
  cs->gs_amp_env = NULL; 
  return(cs);
}

static console_state *copy_console(console_state *cs)
{
  console_state *ncs;
  int i;
  ncs = make_console_state(cs->chans,cs->edit_ctr,cs->orig,cs->end);
  if (!(cs->locked))
    {
      for (i=0;i<cs->chans;i++)
	{
	  ncs->scalers[i] = cs->scalers[i];
	  ncs->old_scalers[i] = cs->old_scalers[i];
	  ncs->scl_scalers[i] = cs->scl_scalers[i];
	}
    }
  ncs->locked = cs->locked;
  ncs->syncable = cs->syncable;
  ncs->groups = cs->groups;
  ncs->speed = cs->speed;
  ncs->scl_speed = cs->scl_speed;
  ncs->old_speed = cs->old_speed;
  ncs->gs_amp = cs->gs_amp;
  ncs->gs_speed = cs->gs_speed;
  ncs->gs_tempo = cs->gs_tempo;
  ncs->gs_amp_env = copy_mixenv((mixenv *)(cs->gs_amp_env)); 
  ncs->len = cs->len;
  return(ncs);
}

static void make_current_console(mixdata *md)
{
  int i;
  console_state *cs,*cur;
  cs = md->states[md->curcons];
  cur = md->current_cs;
  cur->chans = cs->chans;
  cur->edit_ctr = cs->edit_ctr;
  cur->orig = cs->beg;
  cur->beg = cs->beg;
  cur->end = cs->end;
  cur->len = cs->len;
  cur->locked = cs->locked;
  cur->syncable = cs->syncable;
  cur->groups = cs->groups;
  if (!(cs->locked))
    {
      for (i=0;i<cs->chans;i++)
	{
	  cur->scalers[i] = cs->scalers[i];
	  cur->old_scalers[i] = cs->old_scalers[i];
	  cur->scl_scalers[i] = cs->scl_scalers[i];
	}
    }
  cur->speed = cs->speed;
  cur->scl_speed = cs->scl_speed;
  cur->old_speed = cs->old_speed;
  cur->gs_amp = cs->gs_amp;
  cur->gs_speed = cs->gs_speed;
  cur->gs_tempo = cs->gs_tempo;
  free_mixenv((mixenv *)(cur->gs_amp_env));
  cur->gs_amp_env = copy_mixenv((mixenv *)(cs->gs_amp_env)); 
}

static console_state *free_console_state(console_state *cs)
{
  if (cs)
    {
      if (cs->scalers) {free(cs->scalers); cs->scalers = NULL;}
      if (cs->old_scalers) {free(cs->old_scalers); cs->old_scalers = NULL;}
      if (cs->scl_scalers) {free(cs->scl_scalers); cs->scl_scalers = NULL;}
      free_mixenv((mixenv *)(cs->gs_amp_env));
      free(cs);
    }
  return(NULL);
}


static void release_mixmark(mixmark *m)
{
  mixdata *md;
  m->inuse = 0;
  md = (mixdata *)(m->owner);      /* tell owner his widgets have been taken */
  if (md) {md->mixer = NULL; m->owner = NULL;}
  release_mixmark_widgets(m);
}

static void remove_from_sync_chain(mixdata *md)
{
  mixdata *tmp;
  if (md)
    {
      if ((tmp = (mixdata *)(md->sync_out))) tmp->sync_back = md->sync_back;
      if ((tmp = (mixdata *)(md->sync_back))) tmp->sync_out = md->sync_out;
      md->sync_out = NULL;
      md->sync_back = NULL;
    }
}

static void remove_mix_from_all_groups(mixdata *md);

mixdata *free_mixdata(mixdata *m)
{
  int i;
  mix_context *ms;
  if (m)
    {
      remove_from_sync_chain(m); /* keep chain otherwise intact -- might be 'x' of console in midst of chain */
      if (m->wg)
	{
	  ms = m->wg;
	  if (ms->p0) {free(ms->p0); ms->p0 = NULL;}
	  if (ms->p1) {free(ms->p1); ms->p1 = NULL;}
	}
      if (m->temporary == DELETE_ME) remove(m->in_filename);
      if (m->out_temp) remove(m->out_filename);
      if (m->mixer) {release_mixmark(m->mixer); m->mixer = NULL;}
      if (m->name) {free(m->name); m->name = NULL;}
      if (m->in_filename) {free(m->in_filename); m->in_filename = NULL;}
      if (m->out_filename) {free(m->out_filename); m->out_filename = NULL;}
      if (m->add_snd) {free_snd_info(m->add_snd); m->add_snd = NULL;}
      if (m->sub_snd) {free_snd_info(m->sub_snd); m->sub_snd = NULL;}
      if (m->states)
	{
	  for (i=0;i<m->console_state_size;i++) {if (m->states[i]) m->states[i] = free_console_state(m->states[i]);}
	  free(m->states);
	  m->states = NULL;
	}
      remove_mix_from_all_groups(m);
      free(m);
    }
  return(NULL);
}

chan_info *m_to_cp(mixmark *m)
{
  return(((mixdata *)(m->owner))->cp);
}

static void release_pending_consoles(mixdata *md)
{
  int i;
  if (md->states)
    {
      for (i=md->curcons+1;i<md->console_state_size;i++) 
	{
	  if (md->states[i]) 
	    md->states[i] = free_console_state(md->states[i]);
	}
    }
}


/* ---------------- MIX READ ---------------- */

#define C_STRAIGHT 0
#define C_AMP 1
#define C_SPEED 2
#define C_ZERO 3

typedef struct {
  mixdata *md;
  console_state *cs;
  snd_fd **sfs;
  int chans;                           /* chans of input */
  int calc,base;
  float x,sr;
  int *lst,*nxt;
  mixenv *amp_env;
} mix_fd;

static int next_mix_sample(mix_fd *mf)
{
  int i,j,sum = 0,val,move;
  float spd;
  console_state *cs;
  switch (mf->calc)
    {
    case C_STRAIGHT:
      NEXT_SAMPLE(sum,mf->sfs[mf->base]);
      break;
    case C_ZERO: 
      return(0);
      break;
    case C_AMP:
      sum = 0;
      cs = mf->cs;
      for (i=0;i<mf->chans;i++)
	{
	  NEXT_SAMPLE(val,mf->sfs[i]);
	  sum += ((int)(val * cs->scalers[i]));
	}
      if (mf->amp_env) sum *= env_val(mf->amp_env);
      /* it would be more efficient to hold the amp env in the group, and read it just once
       * in the group read or remix.  Unfortunately, this can't work in general if we allow
       * mixes to participate in more than one group (which I think is worth keeping if possible).
       */
      break;
    case C_SPEED:
      sum = 0;
      cs = mf->cs;
      spd = mf->sr;
      for (i=0;i<mf->chans;i++)
	{
	  if (cs->scalers[i] > 0.0)
	    sum += (cs->scalers[i] * (mf->lst[i] + mf->x * (mf->nxt[i] - mf->lst[i])));
	}
      if (mf->amp_env) sum *= env_val(mf->amp_env);
      mf->x += spd;
      move = (int)(mf->x);
      if (move != 0)
	{
	  mf->x -= move;
	  for (j=0;j<move;j++)
	    {
	      for (i=0;i<mf->chans;i++)
		{
		  mf->lst[i] = mf->nxt[i];
		  NEXT_SAMPLE(mf->nxt[i],mf->sfs[i]);
		}
	    }
	}
      break;
    }
  return(sum);
}

static mix_fd *init_mix_read(mixdata *md, int old)
{
  mix_fd *mf;
  snd_info *add_sp,*sub_sp;
  chan_info *cp;
  console_state *cs;
  int i,chans;
  mf = (mix_fd *)calloc(1,sizeof(mix_fd));
  if (old) 
    cs = md->states[md->curcons];
  else cs = md->current_cs;
  cp = md->cp;
  chans = md->in_chans;
  mf->calc = C_ZERO;
  for (i=0;i<chans;i++)
    {
      if (cs->scalers[i] != 0.0) {mf->calc = C_STRAIGHT; break;}
    }
  if (mf->calc == C_ZERO) return(mf);
  if (!(md->add_snd)) md->add_snd = make_mix_readable(md);
  if (!(md->sub_snd)) md->sub_snd = make_mix_readable(md);
  add_sp = md->add_snd;
  sub_sp = md->sub_snd;
  mf->sr = cs->speed;
  mf->md = md;
  mf->cs = cs;
  mf->chans = chans;
  mf->sfs = (snd_fd **)calloc(chans,sizeof(snd_fd *));
  mf->base = cp->chan;
  mf->amp_env = copy_mixenv((mixenv *)(cs->gs_amp_env));
  if (mf->amp_env) start_mixenv(mf->amp_env);
  if (mf->base >= chans) mf->base = 0; /* mono file mixed into sync'd stereo file for example */
  if (mf->sr != 1.0)
    mf->calc = C_SPEED;
  else
    {
      if (mf->amp_env)
	mf->calc = C_AMP;
      else
	{
	  mf->calc = C_STRAIGHT;
	  for (i=0;i<chans;i++)
	    {
	      if (((i == mf->base) && (cs->scalers[i] != 1.0)) ||
		  ((i != mf->base) && (cs->scalers[i] != 0.0)))
		{
		  mf->calc = C_AMP;
		  break;
		}
	    }
	}
    }
  if (mf->calc == C_STRAIGHT)
    {
      if (old)
	mf->sfs[mf->base] = init_sample_read(0,sub_sp->chans[mf->base],READ_FORWARD);
      else mf->sfs[mf->base] = init_sample_read(0,add_sp->chans[mf->base],READ_FORWARD);
    }
  else
    {
      for (i=0;i<chans;i++)
	{
	  if (old)
	    mf->sfs[i] = init_sample_read(0,sub_sp->chans[i],READ_FORWARD);
	  else mf->sfs[i] = init_sample_read(0,add_sp->chans[i],READ_FORWARD);
	}
    }
  if (mf->calc == C_SPEED)
    {
      /* initialize interpolator */
      mf->lst = (int *)calloc(chans,sizeof(int));
      mf->nxt = (int *)calloc(chans,sizeof(int));
      for (i=0;i<chans;i++)
	{
	  NEXT_SAMPLE(mf->lst[i],mf->sfs[i]);
	  NEXT_SAMPLE(mf->nxt[i],mf->sfs[i]);
	}
    }
  else
    {
      mf->lst = NULL;
      mf->nxt = NULL;
    }
  return(mf);
}

static mix_fd *free_mix_fd(mix_fd *mf)
{
  int i;
  if (mf)
    {
      if (mf->lst) free(mf->lst);
      if (mf->nxt) free(mf->nxt);
      if (mf->sfs)
	{
	  for (i=0;i<mf->chans;i++)
	    {
	      if (mf->sfs[i]) mf->sfs[i] = free_snd_fd(mf->sfs[i]);
	    }
	  free(mf->sfs);
	  free_mixenv(mf->amp_env);
	  mf->sfs = NULL;
	}
      free(mf);
    }
  return(NULL);
}


/* ---------------- MIX STATE (chan_info mixes) ---------------- */

static mixmark *ur_get_mixmark(chan_info *ncp, int in_chans)
{
  int i,next;
  mix_state *ms;
  mixmark *m;

  chan_info *cp;
  snd_info *sp;
  sp = ncp->sound;
  if ((ncp->chan == 0) || (sp->combining == CHANNELS_SEPARATE))
    cp = ncp;
  else cp = sp->chans[0];
  if (!cp->mixes) cp->mixes = (mix_state *)calloc(1,sizeof(mix_state));
  ms = (mix_state *)(cp->mixes);
  if (ms->widget_size == 0) 
    {
      ms->widget_size = MPOOL_INCREMENT;
      ms->widget_pool = (mixmark **)calloc(ms->widget_size,sizeof(mixmark *));
      m = (mixmark *)calloc(1,sizeof(mixmark));
      ms->widget_pool[0] = m; 
      return(m);
    }
  for (i=0;i<ms->widget_size;i++)
    {
      m = ms->widget_pool[i];
      if (!m)
	{
	  ms->widget_pool[i] = (mixmark *)calloc(1,sizeof(mixmark));
	  return(ms->widget_pool[i]);
	}
      if ((!(m->inuse)) && (m->chans_allocated == in_chans)) return(m);
    }
  /* no free mixmark slots found */
  next = ms->widget_size;
  ms->widget_size += MPOOL_INCREMENT;
  ms->widget_pool = (mixmark **)realloc(ms->widget_pool,sizeof(mixmark *)*ms->widget_size);
  for (i=next;i<ms->widget_size;i++) ms->widget_pool[i] = NULL;
  m = (mixmark *)calloc(1,sizeof(mixmark));
  ms->widget_pool[next] = m; 
  return(ms->widget_pool[next]);
}

static mixmark *get_mixmark(chan_info *ncp, int in_chans)
{
  mixmark *m;
  m = ur_get_mixmark(ncp,in_chans);
  m->inuse = 1;
  m->owner = NULL;
  return(m);
}

int map_over_channel_mixes(chan_info *cp, int (*func)(mixdata *,void *), void *ptr)
{
  int i,val;
  mixdata **mxd;
  mix_state *ms;
  mixdata *md;
  if (cp->mixes)
    {
      ms = (mix_state *)(cp->mixes);
      if (ms->mix_size > 0)
	{
	  mxd = (mixdata **)(ms->mix_pool);
	  for (i=0;i<ms->mix_size;i++) 
	    {
	      md = mxd[i];
	      if (md)
		{
		  val = (*func)(md,ptr);
		  if (val) return(val);}}}}
  return(0);
}

static int remove_temporary_mix_file(mixdata *md, void *ptr)
{
  if (md->temporary == DELETE_ME) remove(md->in_filename);
  if (md->out_temp) remove(md->out_filename);
  return(0);
}

void free_mix_list(chan_info *cp)
{
  map_over_channel_mixes(cp,remove_temporary_mix_file,NULL);
}

void free_mixes(chan_info *cp)
{
  /* called in snd-data.c during chan_info cleanup */
  int i;
  mixdata **mxd;
  mixmark **mxm;
  mix_state *ms;
  if (cp->mixes)
    {
      ms = (mix_state *)(cp->mixes);
      if (ms->mix_size > 0)
	{
	  mxd = (mixdata **)(ms->mix_pool);
	  for (i=0;i<ms->mix_size;i++) {if (mxd[i]) mxd[i] = free_mixdata(mxd[i]);}
	  ms->mix_size = 0;
	  free(ms->mix_pool);
	  ms->mix_pool = NULL;
	}
      if (ms->widget_size > 0)
	{
	  mxm = (mixmark **)(ms->widget_pool);
	  for (i=0;i<ms->widget_size;i++) {if (mxm[i]) release_mixmark(mxm[i]);}
	}
      free(ms);
      cp->mixes = NULL;
    }
}

#define MXD_INCREMENT 16

static mixdata *make_mixdata(chan_info *cp)
{
  int i,next;
  mixdata **mxd;
  mixdata *md;
  mix_state *ms;
  snd_state *ss;
  ss = cp->state;
  if (!cp->mixes) cp->mixes = (mix_state *)calloc(1,sizeof(mix_state));
  ms = (mix_state *)(cp->mixes);
  next=-1;
  if (ms->mix_size == 0)
    {
      ms->mix_size = MXD_INCREMENT;
      ms->mix_pool = (mixdata **)calloc(ms->mix_size,sizeof(mixdata *));
      mxd = (mixdata **)(ms->mix_pool);
      next=0;
    }
  else
    {
      mxd = (mixdata **)(ms->mix_pool);
      for (i=0;i<ms->mix_size;i++)
	{
	  if (!(mxd[i])) {next=i; break;}
	}
      if (next == -1)
	{
	  /* now make more space */
	  next = ms->mix_size;
	  ms->mix_size += MXD_INCREMENT;
	  ms->mix_pool = (mixdata **)realloc(ms->mix_pool,ms->mix_size * sizeof(mixdata *));
	  mxd = (mixdata **)(ms->mix_pool);
	  for (i=next;i<ms->mix_size;i++) mxd[i] = NULL;
	}
    }
  md = (mixdata *)calloc(1,sizeof(mixdata));
  mxd[next] = md;
  md->loc = next;
  md->cp = cp;
  md->ss = cp->state;
  md->mixer = NULL;
  md->add_snd = NULL;
  md->sub_snd = NULL;
  md->temporary = 0;
  md->wg = set_mixdata_context(cp);
  md->anchor = 0;
  md->changed = 0;
  md->width = 25;
  md->state = MD_TITLE;
  md->rows = (int *)calloc(ss->ngroups,sizeof(int));
  return(md);
}

snd_info *make_mix_readable(mixdata *md)
{
  chan_info *cp;
  snd_info *add_sp,*sub_sp;
  char *str;
  cp = md->cp;
  if (!md->add_snd) 
    {
      md->add_snd = make_sound_readable(cp->state,md->in_filename,TRUE);
      if (!(md->add_snd)) 
	{
	  str = (char *)calloc(256,sizeof(char));
	  sprintf(str,snd_string_cant_find,md->in_filename);
	  snd_printf(cp->state,str);
	  free(str);
	}
    }
  if (!(md->sub_snd)) md->sub_snd = make_sound_readable(cp->state,md->in_filename,TRUE);
  add_sp = md->add_snd;
  add_sp->fullname = copy_string(md->in_filename);
  add_sp->shortname = filename_without_home_directory(add_sp->fullname);
  sub_sp = md->sub_snd;
  sub_sp->fullname = copy_string(md->in_filename);
  sub_sp->shortname = filename_without_home_directory(sub_sp->fullname);
  return(add_sp);
}

static char *save_as_temp_file(snd_state *ss, int **raw_data, int chans, int len, int nominal_srate)
{
  char *newname;
  int format,ofd,no_space;
#ifdef CLM_LITTLE_ENDIAN
  format = snd_32_linear_little_endian;
#else
  format = snd_32_linear;
#endif
  /* newname = tempnam(ss->temp_dir,"snd_"); */
  newname = shorter_tempnam(ss->temp_dir,"snd_");
  /* we're writing our own private version of this thing, so we can use our own formats */
  snd_write_header(ss,newname,NeXT_sound_file,nominal_srate,chans,28,len*chans,format,NULL,0);
  ofd = snd_reopen_write(ss,newname);
  open_clm_file_descriptors(ofd,format,4,28);
  lseek(ofd,28,0);
  no_space = disk_space_p(any_selected_sound(ss),ofd,len*chans*4,0);
  if (no_space == GIVE_UP)
    {
      snd_close(ofd);
      return(newname);
    }
  clm_write(ofd,0,len-1,chans,raw_data);
  snd_close(ofd);
  return(newname);
}

#define offset_from_top 0
/* axis top border width is 10 (snd-axis.c) */

static mixdata *add_mix(chan_info *cp, int chan, int beg, int num, 
			char *mixed_chan_file, char *full_original_file, 
			int past_end, int input_chans, int temp, int clm, int out_chans)
{ /* temp -> delete original file also */
  mixdata *md;
  mixmark *m = NULL;
  axis_info *ap;
  console_state *cs;
  int xspot;
  initialize_mix(cp->state);
  ap = cp->axis;
  md = make_mixdata(cp);     /* add active mix to chan_info list */
  md->in_chans = input_chans;
  md->out_chan = chan;
  md->orig_beg = beg;
  if (chan < input_chans)
    {
      if (out_chans > 1) 
	md->main_chan = chan;
      else md->main_chan = 0;
    }
  else md->main_chan = -1;
  md->in_samps = num;
  md->name = just_filename(full_original_file);
  md->temporary = temp;
  if (!clm)
    {
      m = get_mixmark(cp,input_chans);
      md->mixer = m; 
    }
  else 
    {
      md->mixer = NULL;
      md->state = MD_M;
    }
  md->console_state_size = 1;
  md->states = (console_state **)calloc(md->console_state_size,sizeof(console_state *));
  cs = make_console_state(input_chans,cp->edit_ctr,beg,beg+num-1);
  md->current_cs = make_console_state(input_chans,cp->edit_ctr,beg,beg+num-1);
  md->states[0] = cs;
  if (chan < input_chans)
    {
      cs->scalers[chan] = 1.0;
      cs->old_scalers[chan] = 1.0;
      cs->scl_scalers[chan] = 1.0;
    }
  cs->speed = 1.0;
  cs->scl_speed = 1.0;
  cs->old_speed = 50;
  md->curcons = 0;
  make_current_console(md);
  md->out_filename = copy_string(mixed_chan_file);
  md->in_filename = copy_string(full_original_file);
  if (!clm)
    {
      md->out_temp = 0;
      xspot = grf_x((double)beg/(double)snd_SRATE(cp),ap);
      if ((xspot+16*5 + mark_name_width(md->ss,md->name))>ap->x_axis_x1) md->state = MD_M;
      use_mixmark(md,xspot,offset_from_top);
      clobber_amp_env(cp);
      update_graph(cp,NULL);
      check_for_first_edit(cp);
    }
  else md->out_temp = 1;
  return(md);
}

static mixdata *file_mix_samples(int beg, int num, char *tempfile, chan_info *cp, int chan, int temp, int clm, int out_chans)
{
  /* open tempfile, current data, write to new temp file mixed, close others, open and use new as change case */
  /* used for clip-region/ufun temp file incoming and C-q in snd-chn.c (i.e. mix in file) so sync not relevant */
  snd_fd *csf;
  snd_state *ss;
  snd_info *sp;
  int ofd,ifd;
  char *ofile;
  int **data;
  float scaler;
  int *chandata;
  int i,size,j,val,cursamps,in_chans,base,no_space;
  file_info *ihdr,*ohdr;
  sp = cp->sound;
  ss = cp->state;
  ihdr = make_file_info(tempfile,ss);
  in_chans = ihdr->chans;
  if (in_chans <= chan) {base = 0; scaler = 0.0;} else {base = chan; scaler = 1.0;}
  ofile = tempnam(ss->temp_dir,"snd_");
  ohdr = make_temp_header(ofile,sp->hdr,0);
  ohdr->chans = 1;
#ifdef CLM_LITTLE_ENDIAN
  ohdr->format = snd_32_linear_little_endian;
#else
  ohdr->format = snd_32_linear;
#endif
  ofd = open_temp_file(ofile,1,ohdr,ss);
  no_space = disk_space_p(cp->sound,ofd,num*4,0);
  if (no_space == GIVE_UP)
    {
      snd_close(ofd);
      remove(ofile);
      return(NULL);
    }
  csf = init_sample_read(beg,cp,READ_FORWARD);
  ifd = snd_open_read(ss,tempfile);
  open_clm_file_descriptors(ifd,ihdr->format,c_snd_datum_size(ihdr->format),ihdr->data_location);
  if (num < MAX_BUFFER_SIZE) size = num; else size = MAX_BUFFER_SIZE;
  data = (int **)calloc(in_chans,sizeof(int *));
  data[base] = (int *)calloc(size,sizeof(int));
  chandata = data[base];
  clm_seek(ofd,ohdr->data_location,0);
  clm_seek(ifd,ihdr->data_location,0);
  /* why not use fasmix? because we're reading edited data here in most cases */
  if (scaler == 0.0)
    {
      for (i=0;i<num;i+=MAX_BUFFER_SIZE)
	{
	  cursamps = num-i;
	  if (cursamps > MAX_BUFFER_SIZE) cursamps = MAX_BUFFER_SIZE;
	  for (j=0;j<cursamps;j++)
	    {
	      NEXT_SAMPLE(chandata[j],csf);
	    }
	  clm_write(ofd,0,cursamps-1,1,&chandata);
	}
    }
  else
    {
      clm_read_chans(ifd,0,size-1,in_chans,data,(int *)data);
      for (i=0,j=0;i<num;i++)
	{
	  if (j == size)
	    {
	      clm_write(ofd,0,size-1,1,&chandata);
	      clm_read_chans(ifd,0,size-1,in_chans,data,(int *)data);
	      j = 0;
	    }
	  NEXT_SAMPLE(val,csf);
	  chandata[j] += val;
	  j++;
	}
      if (j > 1) clm_write(ofd,0,j-1,1,&chandata);
    }
  close_temp_file(ofd,ohdr,num*c_snd_datum_size(ohdr->format),sp);
  snd_close(ifd);
  free_snd_fd(csf);
  free(data[base]);
  free(data);
  free_file_info(ihdr);
  free_file_info(ohdr);
  if (!clm) file_change_samples(beg,num,ofile,cp,0,DELETE_ME,DONT_LOCK_MIXES);
  return(add_mix(cp,chan,beg,num,ofile,tempfile,0,in_chans,temp,clm,out_chans));
}

/* next three functions canonicalize mixer input -- 
 *   all end up with a filename containing the original to-be-mixed input
 *                     length (per channel samples in input)
 *                     begin sample in output for mix start
 *                     an array of cps for mixing into
 *                     a notion of initial scalers (1.0 or arg to mix_array)
 *                     
 * need to set up the edits per out chan (as temp files via file_mix_samples with initial scalers)
 *         check_for_first_edit, and update_graph, creating mixer consoles where needed
 */

#define NO_GROUP -1
#define NOT_FROM_CLM 0
#define FROM_CLM 1
static void clm_notify_group(mixdata *md, int g, int on);

void mix(int beg, int num, int chans, chan_info **cps, char *mixinfile, int temp, int clm, int group)
{
  /* loop through out_chans cps writing the new mixed temp files and fixing up the edit trees */
  /* if syncing multi-chan output, set up the two way list of syncd mixes */
  int i;
  mixdata *md,*last_md;
  snd_info *sp;
  console_state *cs;
  last_md = NULL;
  for (i=0;i<chans;i++)
    {
      if (!clm)
	md = file_mix_samples(beg,num,mixinfile,cps[i],i,temp,FALSE,chans); /* explode in this file, or mix in snd-clm.c */
      else
	{
	  sp = cps[i]->sound;
	  /* CLM always creates output file with the same number of channels as the overall output */
	  md = add_mix(cps[i],i,beg,num,sp->fullname,mixinfile,0,chans,temp,FROM_CLM,chans);
	  if (group >= 0) 
	    {
	      cs = md->current_cs;
	      cs->groups = (1<<group);
	      clm_notify_group(md,group,TRUE); /* skip remix and other junk in toggle_group */
	    }
	}
      md->sync_back = last_md;
      if (last_md) last_md->sync_out = (void *)md;
      last_md = md;
      if ((!clm) && (group >= 0)) toggle_group(md,TRUE,group);
    }
}

void mix_array(int beg, int num, int **data, chan_info **out_cps, int in_chans, int out_chans, int from_ufun, int nominal_srate)
{
  /* always write to tempfile */
  char *newname;
  snd_state *ss;
  ss = out_cps[0]->state;
  newname = save_as_temp_file(ss,data,in_chans,num,nominal_srate);
  mix(beg,num,out_chans,out_cps,newname,DELETE_ME,NOT_FROM_CLM,NO_GROUP);
}

void mix_file(int beg, int num, char *file, chan_info **cps, int out_chans, int from_ufun)
{
  /* always write to tempfile (protect section/ufun temps from possible overwrites) */
  char *newname;
  snd_state *ss;
  ss = cps[0]->state;
  newname = shorter_tempnam(ss->temp_dir,"snd_");
  copy_file(file,newname,cps[0]->sound); /* sound used for disk full error message */
  mix(beg,num,out_chans,cps,newname,DELETE_ME,NOT_FROM_CLM,NO_GROUP);
}

void mix_complete_file(snd_info *sp, char *str, int source)
{
  /* no need to save as temp here, but we do need sync info (from menu and keyboard) */
  chan_info *cp;
  chan_info **cps;
  int nc,len,err,chans;
  sync_info *si;
  char *str1,*fullname;
  if ((sp) && (str) && (*str))
    {
      str1 = copy_string(str);
      fullname = copy_string(complete_filename(str1));
      free(str1);
      err = c_read_header(fullname);
      if (err == 0)
	{
	  nc = c_snd_header_chans();
	  len = c_snd_header_data_size()/nc;
	  cp = any_selected_channel(sp);
	  if (sp->syncing)
	    {
	      si = snd_sync(sp->state); 
	      cps = si->cps;
	      chans = si->chans;
	    }
	  else
	    {
	      cps = (chan_info **)calloc(1,sizeof(chan_info *));
	      cps[0] = cp;
	      chans = 1;
	    }
	  mix(cp->cursor,len,chans,cps,fullname,DONT_DELETE_ME,NOT_FROM_CLM,NO_GROUP);
	}
      else 
	{
	  str1 = (char *)calloc(512,sizeof(char));
	  sprintf(str1,"%s: %s, %s ",snd_string_cant_open_file,fullname,strerror(errno));
	  report_in_minibuffer(sp,str1);
	  free(str1);
	}
    }
}

#define CONSOLE_INCREMENT 8

static void extend_console_list(mixdata *md)
{
  int i,lim;
  md->curcons++;
  if (md->curcons >= md->console_state_size)
    {
      lim = md->console_state_size;
      md->console_state_size += CONSOLE_INCREMENT;
      md->states = (console_state **)realloc(md->states,md->console_state_size * sizeof(console_state *));
      for (i=lim;i<md->console_state_size;i++) md->states[i] = NULL;
    }
}

static void mix_change_inform_groups(mixdata *md); /* uses groups variable whihc is declare below */

void remix_file(mixdata *md)
{
  int beg,end,i,j,ofd = 0,size,num,val,maxy,miny,no_space,use_temp_file;
  float fmax,fmin;
  snd_info *cursp;
  mix_fd *add,*sub;
  snd_fd *cur,*sfb,*afb;
  snd_state *ss;
  char *ofile = NULL;
  int **data;
  int *chandata;
  file_info *ohdr = NULL;
  axis_info *ap;
  chan_info *cp;
  int old_beg,old_end,new_beg,new_end;
  console_state *cs;
  release_pending_consoles(md);

  cs = md->current_cs;
  cp = md->cp;
  ap = cp->axis;
  old_beg = cs->orig;
  old_end = cs->end;
  new_beg = cs->beg;
  new_end = cs->beg + cs->len - 1;

  ss = cp->state;
  cursp = cp->sound;

  beg = (old_beg < new_beg) ? old_beg : new_beg;
  end = (old_end > new_end) ? old_end : new_end;
  num = end-beg+1;
  use_temp_file = (num >= MAX_BUFFER_SIZE);

  add = init_mix_read(md,0);
  sub = init_mix_read(md,1);
  cur = init_sample_read(beg,cp,READ_FORWARD);

  if (use_temp_file)
    {
      ofile = tempnam(ss->temp_dir,"snd_");
      ohdr = make_temp_header(ofile,cursp->hdr,0);
      /* old header used for srate -- actually not needed here (make_temp_header assumes NeXT/16) */
      ohdr->chans = 1;

      /* we can't (normally) write 16-bit files here because the current mix state may overflow -1.0..1.0 */
#ifdef CLM_LITTLE_ENDIAN
      ohdr->format = snd_32_linear_little_endian;
#else
      ohdr->format = snd_32_linear;
#endif
      ofd = open_temp_file(ofile,1,ohdr,ss);
      no_space = disk_space_p(cursp,ofd,num*4,num*2);
      switch (no_space)
	{
	case GIVE_UP:
	  close_temp_file(ofd,ohdr,0,cursp);
	  free_snd_fd(cur);
	  free_mix_fd(add);
	  free_mix_fd(sub);
	  free_file_info(ohdr);
	  remove(ofile);
	  return;
	  break;
	case HUNKER_DOWN:
	  close_temp_file(ofd,ohdr,0,cursp);
#ifdef CLM_LITTLE_ENDIAN
	  ohdr->format = snd_16_linear_little_endian;
#else
	  ohdr->format = snd_16_linear;
#endif
	  ofd = open_temp_file(ofile,1,ohdr,ss);
	  break;
	case NO_PROBLEM: case BLIND_LEAP: break;
	}
    }
  if (num < MAX_BUFFER_SIZE) size = num; else size = MAX_BUFFER_SIZE;
  data = (int **)calloc(1,sizeof(int *));
  data[0] = (int *)calloc(size,sizeof(int));
  chandata = data[0];
  if (use_temp_file) clm_seek(ofd,ohdr->data_location,0);

  old_beg -= beg;
  old_end -= beg;
  new_beg -= beg;
  new_end -= beg;

  maxy = 32767;
  miny = -32768;

  /* split out special simple cases */
  if ((add->calc == C_ZERO) && (sub->calc == C_ZERO))
    {
      for (i=0,j=0;i<num;i++)
	{
	  if (j == size)
	    {
	      if (use_temp_file) clm_write(ofd,0,size-1,1,&chandata);
	      j = 0;
	    }
	  NEXT_SAMPLE(chandata[j],cur);
	  j++;
	}
    }
  else
    {
      if ((add->calc == C_STRAIGHT) && (sub->calc == C_STRAIGHT))
	{
	  sfb = sub->sfs[sub->base];
	  afb = add->sfs[add->base];
	  for (i=0,j=0;i<num;i++)
	    {
	      if (j == size)
		{
		  if (use_temp_file) clm_write(ofd,0,size-1,1,&chandata);
		  j = 0;
		}
	      NEXT_SAMPLE(chandata[j],cur);
	      if ((i>=old_beg) && (i<=old_end))
		{
		  NEXT_SAMPLE(val,sfb);
		  chandata[j] -= val;
		}
	      if ((i>=new_beg) && (i<=new_end))
		{
		  NEXT_SAMPLE(val,afb);
		  chandata[j] += val;
		}
	      if (chandata[j] > maxy) maxy = chandata[j];
	      else if (chandata[j] < miny) miny = chandata[j];
	      j++;
	    }
	}
      else
	{
	  for (i=0,j=0;i<num;i++)
	    {
	      if (j == size)
		{
		  if (use_temp_file) clm_write(ofd,0,size-1,1,&chandata);
		  j = 0;
		}
	      NEXT_SAMPLE(chandata[j],cur);
	      if ((i>=old_beg) && (i<=old_end))
		{
		  val = next_mix_sample(sub);
		  chandata[j] -= val;
		}
	      if ((i>=new_beg) && (i<=new_end))
		{
		  val = next_mix_sample(add);
		  chandata[j] += val;
		}
	      if (chandata[j] > maxy) maxy = chandata[j];
	      else if (chandata[j] < miny) miny = chandata[j];
	      j++;
	    }
	}
    }

  if (use_temp_file)
    {
      if (j > 0) clm_write(ofd,0,j-1,1,&chandata);
      close_temp_file(ofd,ohdr,num*c_snd_datum_size(ohdr->format),cursp);
      free_file_info(ohdr);
    }
  free_snd_fd(cur);
  free_mix_fd(add);
  free_mix_fd(sub);

  if (use_temp_file)
    {
      file_change_samples(beg,num,ofile,cp,0,DELETE_ME,DONT_LOCK_MIXES);
      free(data[0]);
    }
  else
    {
      /* free_snd_data will free the actual the data */
      change_samples(beg,num,data[0],cp,DONT_LOCK_MIXES);
    }
  free(data);
 
  extend_console_list(md);
  cs = copy_console(cs);
  cs->edit_ctr = cp->edit_ctr;
  cs->orig = new_beg+beg;
  cs->beg = cs->orig;
  cs->end = cs->beg + cs->len - 1;

  md->states[md->curcons] = cs;
  make_current_console(md);

  /* fix up graph if we overflowed during mix */
  fmax = maxy*clm_sndflt;
  fmin = miny*clm_sndflt;
  if ((fmax > ap->ymax) || (fmin < ap->ymin)) 
    {
      if (fmax < -fmin) fmax = -fmin; 
      set_y_limits(cp,ap,fmax);
    }
  update_graph(cp,NULL);
  check_for_first_edit(cp);
  mix_change_inform_groups(md);
}

void make_temporary_graph(chan_info *cp, mixdata *md, console_state *cs)
{
  int oldbeg,newbeg,oldend,newend;
  int i,j,samps,xi,val;
  int widely_spaced;
  axis_info *ap;
  snd_info *sp;
  mix_context *ms;
  snd_state *ss;
  float samples_per_pixel,xf;
  double x,incr,initial_x;
  int ina,ymin,ymax,lo,hi;
  snd_fd *sf,*sfb,*afb;
  mix_fd *add,*sub;
  int x_start,x_end;
  double start_time,cur_srate;
  ss = cp->state;
  if (!(ss->movies)) return;
  ms = md->wg;
  oldbeg = cs->orig;
  newbeg = cs->beg;
  oldend = cs->end;
  newend = newbeg + cs->len;
  sf = NULL; add = NULL; sub = NULL;
  sp = cp->sound;
  ap = cp->axis;
  cur_srate = (double)snd_SRATE(sp);
  start_time = (double)(ap->losamp)/cur_srate;
  x_start = grf_x(start_time,ap);
  x_end = grf_x((double)(ap->hisamp)/cur_srate,ap);
  lo = ap->losamp;
  hi = ap->hisamp;
  sf = init_sample_read(ap->losamp,cp,READ_FORWARD);
  add = init_mix_read(md,0);
  sub = init_mix_read(md,1);
  if ((oldbeg < lo) && (lo < oldend)) {for (i=oldbeg;i<lo;i++) next_mix_sample(sub);}
  samps = ap->hisamp-ap->losamp+1;
  samples_per_pixel = (float)(samps-1)/(float)(x_end-x_start);
  if ((samples_per_pixel < 5.0) && (samps < POINT_BUFFER_SIZE))
    {
      if (samples_per_pixel < 1.0)
	{
	  incr = 1.0 / samples_per_pixel;
	  initial_x = x_start;
	  widely_spaced = 1;
	}
      else
	{
	  incr = (double)1.0 /cur_srate;
	  initial_x = start_time;
	  widely_spaced = 0;
	}
      for (j=0,i=lo,x=initial_x;i<=hi;i++,j++,x+=incr)
	{
	  NEXT_SAMPLE(ina,sf);
	  if ((i >= oldbeg) && (i <= oldend)) ina -= next_mix_sample(sub);
	  if ((i >= newbeg) && (i <= newend)) ina += next_mix_sample(add);
	  if (widely_spaced)
	    set_grf_point(x,j,grf_y(clm_sndflt * ina,ap));
	  else set_grf_point(grf_x(x,ap),j,grf_y(clm_sndflt * ina,ap));
	}
      erase_and_draw_grf_points(ss,md->wg,cp,j);
    }
  else
    {
      /* can't use subsampled graphics or amplitude envelopes here because the cancellation is way off */
      j = 0;      /* graph point counter */
      x=ap->x0;
      xi=grf_x(x,ap);
      i=lo;
      xf=0.0;     /* samples per pixel counter */
      ymin=3276800;
      ymax=-3276800;

      if ((add->calc == C_ZERO) && (sub->calc == C_ZERO))
	{
	  while (i<=hi)
	    {
	      NEXT_SAMPLE(ina,sf);
	      if (ina > ymax) ymax = ina;
	      if (ina < ymin) ymin = ina;
	      xf+=1.0;
	      i++;
	      if (xf>samples_per_pixel)
		{
		  set_grf_points(xi,j,grf_y(clm_sndflt * ymin,ap),grf_y(clm_sndflt * ymax,ap));
		  xi++;
		  j++;
		  xf -= samples_per_pixel;
		  ymin=3276800;
		  ymax=-3276800;
		}
	    }
	}
      else
	{
	  if ((add->calc == C_STRAIGHT) && (sub->calc == C_STRAIGHT))
	    {
	      sfb = sub->sfs[sub->base];
	      afb = add->sfs[add->base];
	      while (i<=hi)
		{
		  NEXT_SAMPLE(ina,sf);
		  if ((i >= oldbeg) && (i <= oldend)) {NEXT_SAMPLE(val,sfb); ina -= val;}
		  if ((i >= newbeg) && (i <= newend)) {NEXT_SAMPLE(val,afb); ina += val;}
		  if (ina > ymax) ymax = ina;
		  if (ina < ymin) ymin = ina;
		  xf+=1.0;
		  i++;
		  if (xf>samples_per_pixel)
		    {
		      set_grf_points(xi,j,grf_y(clm_sndflt * ymin,ap),grf_y(clm_sndflt * ymax,ap));
		      xi++;
		      j++;
		      xf -= samples_per_pixel;
		      ymin=3276800;
		      ymax=-3276800;
		    }
		}
	    }
	  else
	    {
	      while (i<=hi)
		{
		  NEXT_SAMPLE(ina,sf);
		  if ((i >= oldbeg) && (i <= oldend)) ina -= next_mix_sample(sub);
		  if ((i >= newbeg) && (i <= newend)) ina += next_mix_sample(add);
		  if (ina > ymax) ymax = ina;
		  if (ina < ymin) ymin = ina;
		  xf+=1.0;
		  i++;
		  if (xf>samples_per_pixel)
		    {
		      set_grf_points(xi,j,grf_y(clm_sndflt * ymin,ap),grf_y(clm_sndflt * ymax,ap));
		      xi++;
		      j++;
		      xf -= samples_per_pixel;
		      ymin=3276800;
		      ymax=-3276800;
		    }
		}
	    }
	}
      erase_and_draw_both_grf_points(ss,md->wg,cp,j);
    }
  if (sf) free_snd_fd(sf);
  if (add) free_mix_fd(add);
  if (sub) free_mix_fd(sub);
  ms->lastpj = j;
}



/* ---------------- MIX ACTIONS ---------------- */

static int turn_off_mix(mixdata *md, void *ptr)
{
  if (md->mixer) {release_mixmark(md->mixer); md->mixer = NULL;}
  return(0);
}

static int turn_off_mixes(chan_info *cp, void *ptr)
{
  map_over_channel_mixes(cp,turn_off_mix,NULL);
  return(0);
}

void update_all_consoles(snd_state *ss)
{
  /* called to turn console display on/off in snd-xmenu (show_consoles) */
  if (!(ss->consoling)) map_over_chans(ss,turn_off_mixes,NULL);
}

mixdata *active_mix(chan_info *cp)
{
  mix_state *ms;
  mixdata *md,*curmd;
  console_state *cs;
  axis_info *ap;
  int lo,hi,spot,i,curmaxctr;
  ms = (mix_state *)(cp->mixes);
  curmd = NULL;
  if (ms)
    {
      curmaxctr = -1;
      ap = cp->axis;
      lo = ap->losamp;
      hi = ap->hisamp;
      for (i=0;i<ms->mix_size;i++)
	{
	  md = ms->mix_pool[i];
	  if (md)
	    {
	      cs = md->current_cs;
	      spot = cs->orig + md->anchor;
	      if ((spot >= lo) && (spot <= hi))
		{
		  /* this mix is within current graph window -- look for last edited mix */
		  if (cs->edit_ctr > curmaxctr)
		    {
		      curmaxctr = cs->edit_ctr;
		      curmd = md;
		    }}}}}
  return(curmd);
}

int mix_beg(chan_info *cp)
{
  mixdata *md;
  console_state *cs;
  md = active_mix(cp);
  if (md) 
    {
      cs = md->current_cs;
      if (cs) return(cs->orig + md->anchor);
    }
  return(-1);
}

static console_state *backup_console(chan_info *cp, mixdata *md)
{
  console_state *cs;
  /* undo -- look for first (last in list) cs with cs->edit_ctr<=cp->edit_ctr console */
  cs = md->states[md->curcons];
  while ((md->curcons > 0) && (cs->edit_ctr > cp->edit_ctr)) 
    {
      md->curcons--; 
      cs = md->states[md->curcons];
    }
  if (cs->edit_ctr > cp->edit_ctr) return(NULL);
  make_current_console(md);
  md->changed = 1;
  return(md->current_cs);
}

static console_state *restore_console(chan_info *cp, mixdata *md)
{
  console_state *cs;
  /* redo -- looking for the console that brackets cp->edit_ctr */
  cs = md->states[md->curcons];
  while ((md->curcons < (md->console_state_size-1)) && (cs->edit_ctr < cp->edit_ctr) && (md->states[md->curcons+1]))
    {
      md->curcons++; 
      cs = md->states[md->curcons];
    }
  if ((md->curcons > 0) && (cs->edit_ctr > cp->edit_ctr)) md->curcons--;
  make_current_console(md);
  md->changed = 1;
  return(md->current_cs);
}

void release_mixes(chan_info *cp)
{
  mix_state *ms;
  mixdata *md;
  mixmark *m;
  int i;
  ms = (mix_state *)(cp->mixes);
  if (ms)
    {
      for (i=0;i<ms->mix_size;i++)
	{
	  md = ms->mix_pool[i];
	  if (md)
	    {
	      m = md->mixer;
	      if (m) release_mixmark(m);
	    }
	}
    }
}

int ready_mix(mixdata *md)
{
  console_state *cs;
  chan_info *cp;
  if (!md) return(0);
  cp = md->cp;
  cs = md->states[md->curcons];
  return(((cs) && (!(cs->locked)) && (cs->edit_ctr <= cp->edit_ctr)));
}

void regraph_all_mixmarks(chan_info *cp)
{
  mix_state *ms;
  mixdata *md;
  mixmark *m;
  int i;
  if ((cp) && (ms = (mix_state *)(cp->mixes)))
    {
      for (i=0;i<ms->mix_size;i++)
	{
	  md = ms->mix_pool[i];
	  if (md)
	    {
	      m = md->mixer;
	      if (m) release_mixmark(m);
	      if (md->wg) free(md->wg);
	      md->wg = set_mixdata_context(cp);
	    }
	}
    }
}

#define MIX_Y_OFFSET 10
#define MIX_Y_INCR 10
/* may need fancier decision here -- leave moved mix at its current height and adjust others */


void display_channel_mixes(chan_info *cp)
{
  /* called in display_channel_data if ss->consoling and cp->mixes
   * this needs to spin through the mixes, 
   *   un-manage those whose mix has wandered off screen, and release the associated widgets,
   *   and re-manage those that are now active, grabbing widgets if necessary.
   */
  snd_state *ss;
  mix_state *ms;
  mixdata *md;
  mixmark *m;
  console_state *cs;
  axis_info *ap;
  int lo,hi,spot,i,xspot,turnover,y,yspot,combined;
  ms = (mix_state *)(cp->mixes);
  combined = (((snd_info *)(cp->sound))->combining != CHANNELS_SEPARATE);
  if (ms)
    {
      ss = cp->state;
      ap = cp->axis;
      lo = ap->losamp;
      hi = ap->hisamp;
      turnover = ap->height * 0.5;
      y = MIX_Y_OFFSET;
      for (i=0;i<ms->mix_size;i++)
	{
	  md = ms->mix_pool[i];
	  if (md)
	    {
	      m = md->mixer;
	      if ((!m) || (!(m->moving)))
		{
		  cs = md->current_cs;
		  if (cs->edit_ctr > cp->edit_ctr) 
		    cs = backup_console(cp,md);
		  else
		    if (cs->edit_ctr < cp->edit_ctr)
		      cs = restore_console(cp,md);
		  if ((cs) && (!cs->locked))
		    {
		      spot = cs->orig + md->anchor;
		      if ((cs->edit_ctr <= cp->edit_ctr) && (spot >= lo) && (spot <= hi))
			{
			  xspot = grf_x((double)spot/(double)snd_SRATE(cp),ap);
			  yspot = offset_from_top+y+ap->y_offset;
			  if (((xspot + md->width) <= ap->x_axis_x1) &&      /* not cut off on right */
			      ((!combined) || 
			       ((ap->y_axis_y0 > md->height) &&              /* not cut off on top */
				((ap->y_axis_y1+md->height) < cp->height)))) /* not cut off on bottom */
			    {
			      if (!m)
				{
				  m = get_mixmark(cp,md->in_chans);
				  md->mixer = m;
				  use_mixmark(md,xspot,yspot);
				  md->changed = 0;
				}
			      move_mixmark(m,xspot,yspot);
			      if (ss->mix_duration_brackets) draw_mix_duration_bracket(md,xspot,yspot);
			      y += MIX_Y_INCR;
			      if (y >= turnover) y=0;
			      if (md->changed) 
				{
				  if (md->state == MD_CS)
				    fixup_mixmark(md);
				  else
				    if (md->state == MD_TITLE)
				      set_mix_title_beg(md,m);
				}
			    }
			  else if (m) release_mixmark(m);
			}
		      else
			{
			  if ((m) && (!(m->moving)))
			    release_mixmark(m); /* if we have widgets deactivate and release them */
			}
		    }
		  else
		    if (m) release_mixmark(m);
		}}}}
}

static int lt_beg,lt_end;

static int lock_mixes(mixdata *md, void *ptr)
{
  console_state *cs;
  chan_info *cp;
  /* find affected console, extend and lock */
  cs = md->states[md->curcons];
  if (!(cs->locked))
    {
      if (((cs->orig >= lt_beg) && (cs->orig <= lt_end)) ||
	  ((cs->end >= lt_beg) && (cs->end <= lt_end)) ||
	  ((cs->orig < lt_beg) && (cs->end > lt_end)))
	{
	  extend_console_list(md);
	  md->states[md->curcons] = (console_state *)calloc(1,sizeof(console_state));
	  cs = md->states[md->curcons];
	  cs->locked = 1;
	  cp = md->cp;
	  cs->edit_ctr = cp->edit_ctr;
	  cs = md->current_cs;
	  cs->locked = 1;
	}
    }
  return(0);
}

void lock_affected_mixes(chan_info *cp, int beg, int end)
{
  /* search through cp->mixes for any active mixes that overlap beg..end 
   * and lock them down until possible undo.
   *
   * We need to lock down any active mixes that overlap the current
   * edit because any number of subsequent edits can take place, 
   * so to unmix (e.g. to move the mix) we'd either need to keep track 
   * of the complete path throughout and be able to unravel it, or 
   * backup the tree, unmix, remix, then re-run the subsequent edits --
   * this requires the edit-history mechanism which is not yet implemented, and 
   * would become a nightmare upon a subsequent undo.  Also there
   * are ambiguities in how we interpret this action: for example,
   * if the user mixes, then deletes a portion of the mixed portion,
   * then moves the original mix, how should it behave in the deleted
   * portion?  
   */
  lt_beg = beg;
  lt_end = end;
  map_over_channel_mixes(cp,lock_mixes,NULL);
}

void release_pending_mixes(chan_info *cp, int edit_ctr)
{
  /* look for mixes that have been cancelled by undo followed by unrelated edit */
  mix_state *ms;
  mixdata *md;
  console_state *cs;
  int i;
  ms = (mix_state *)(cp->mixes);
  if (ms)
    {
      for (i=0;i<ms->mix_size;i++)
	{
	  md = ms->mix_pool[i];
	  if (md)
	    {
	      cs = md->states[0];
	      if (cs->edit_ctr >= edit_ctr)
		{
		  ms->mix_pool[i] = free_mixdata(md);
		}
	    }
	}
    }
}

static int update_mix(mixdata *md, void *ptr)
{
  console_state *cur;
  int i,lim;
  lim = md->curcons;
  cur = md->states[lim];
  /* make states[0] and [1] both look like cur, collapse rest */
  if (lim>0)
    {
      for (i=0;i<lim;i++)
	md->states[i] = free_console_state(md->states[i]);
      md->states[0] = cur;
      md->states[lim] = NULL;
      md->curcons = 0;
    }
  cur->edit_ctr = 0;
  md->states[0]->edit_ctr = 0;
  make_current_console(md);
  return(0);
}

void reset_mix_list(chan_info *cp)
{
  map_over_channel_mixes(cp,update_mix,NULL);
}


/* ---------------- CLM WITH-SOUND EXPLODE ---------------- */

static char *explode_file_name(snd_info *sp)
{
  char *newname;
  int len;
  len = strlen(sp->fullname);
  newname = (char *)calloc(len+9,sizeof(char));
  strcpy(newname,sp->fullname);
  newname[len]='.'; newname[len+1]='e'; newname[len+2]='x'; newname[len+3]='p'; newname[len+4]='l'; 
  newname[len+5]='o'; newname[len+6]='d'; newname[len+7]='e'; newname[len+8]='\0'; 
  return(newname);
}

void clm_add_mixes(snd_info *sp)
{
  /* sp->fullname + ".marks" = possible mark file */
  char *newname;
  snd_state *ss;
  char str[256];
  FILE *fd;
  time_t snd_time,explode_time;
  int happy,len,delete_explode;
  float reverb;
  int place,length,deletable,group;
  snd_time = file_write_date(sp->fullname);
  newname = explode_file_name(sp);
  fd = fopen(newname,"r");
  delete_explode = 1;
  if (fd)
    {
      explode_time = file_write_date(newname);
      if (explode_time >= snd_time)
	{
	  /* file contains "EXPLODE " followed by lists: (name place len delete-temp group reverb) */
	  fscanf(fd,"%s ",str);
	  if (strcmp(str,"EXPLODE") == 0)
	    {
	      happy = 1;
	      ss = sp->state;
	      while (happy)
		{
		  len = fscanf(fd,"(%s %d %d %d %d %f) ",str,&place,&length,&deletable,&group,&reverb);
		  happy = ((len != EOF) && (len != 0));
		  if (happy)
		    {
		      if (!deletable) delete_explode = 0;
		      if (group >= ss->ngroups) group = -1;
		      mix(place,length,sp->nchans,sp->chans,str,(deletable) ? DELETE_ME : DONT_DELETE_ME,FROM_CLM,group);
		    }
		}
	    }
	}
      fclose(fd);
      /* if (delete_explode) fprintf(stderr,"removing %s ",newname); */
      if (delete_explode) remove(newname);
    }
  free(newname);
}




/* ---------------- SYNC'D MIXERS ---------------- */

static void sync_moves(mixdata *md, int val)
{
  mixdata *nxt_md,*lst_md;
  mixmark *m;
  chan_info *cp;
  nxt_md = (mixdata *)(md->sync_out);
  lst_md = (mixdata *)(md->sync_back);
  while (nxt_md)
    {
      m = nxt_md->mixer;
      if (m) m->moving = val;
      cp = nxt_md->cp;
      if (val) cp->mix_dragging = (void *)nxt_md; else cp->mix_dragging = NULL;
      nxt_md = (mixdata *)(nxt_md->sync_out);
    }
  while (lst_md)
    {
      m = lst_md->mixer;
      if (m) m->moving = val;
      cp = lst_md->cp;
      if (val) cp->mix_dragging = (void *)lst_md; else cp->mix_dragging = NULL;
      lst_md = (mixdata *)(lst_md->sync_back);
    }
}

void clear_sync_moves(mixdata *md) {sync_moves(md,0);}
void set_sync_moves(mixdata *md) {sync_moves(md,1);}

static void move_next_mix(mixdata *md, int beg, int nx)
{
  snd_state *ss;
  chan_info *cp;
  snd_info *sp;
  console_state *cs;
  mixmark *m;
  mix_context *ms;
  cs = md->current_cs;
  ms = md->wg;
  m = md->mixer;
  cp = md->cp;
  sp = cp->sound;
  ss = cp->state;
  if ((m) && (ss->mix_duration_brackets)) erase_mix_duration_bracket(md,m->x,m->y);
  cs->beg = beg;
  if (!(ms->lastpj)) 
    {
      ms->lastpj = make_graph(cp,sp,cp->state); 
      mix_save_graph(md->ss,md->wg,ms->lastpj);
    }
  set_mixer_location(m,nx);
  make_temporary_graph(cp,md,cs);
  set_mix_title_beg(md,md->mixer);
  if ((m) && (ss->mix_duration_brackets)) draw_mix_duration_bracket(md,m->x,m->y);
}

static void speed_next_mix(mixdata *md, float spd, int ival)
{
  chan_info *cp;
  snd_info *sp;
  console_state *cs;
  mix_context *ms;
  cs = md->current_cs;
  cs->scl_speed = spd;
  cs->speed = cs->scl_speed * cs->gs_speed;
  ms = md->wg;
  cp = md->cp;
  sp = cp->sound;
  if (!(ms->lastpj)) 
    {
      ms->lastpj = make_graph(cp,sp,cp->state); 
      mix_save_graph(md->ss,md->wg,ms->lastpj);
    }
  respeed(md,spd);
  make_temporary_graph(cp,md,cs);
  set_mix_title_beg(md,md->mixer);
}

#define DONT_CLEAR_SYNC 0
#define CLEAR_SYNC 1

#define MOVE_NEXT_MIX 0
#define SPEED_NEXT_MIX 1
#define REMIX_FILE 2
#define RESPEED 3
#define TOGGLE_GROUP 4
#define RESET_ORIG 5
#define ANCHOR_NEXT_MIX 6

static void map_across_syncd_mixes(snd_info *sp, mixdata *md, int func, int beg, int x, float spd, int clear)
{
  mixdata *nxt_md,*lst_md;
  console_state *cs;
  cs = md->current_cs;
  if ((sp->syncing) && (cs->syncable))
    {
      nxt_md = (mixdata *)(md->sync_out);
      lst_md = (mixdata *)(md->sync_back);
      while (nxt_md)
	{
	  cs = nxt_md->current_cs;
	  if ((cs) && (cs->syncable)) 
	    {
	      switch (func)
		{
		case MOVE_NEXT_MIX: move_next_mix(nxt_md,beg,x); break;
		case SPEED_NEXT_MIX: speed_next_mix(nxt_md,spd,x); break;
		case REMIX_FILE: remix_file(nxt_md); break;
		case RESPEED: respeed(nxt_md,spd); break;
		case TOGGLE_GROUP: toggle_group(nxt_md,beg,x); break;
		case RESET_ORIG: nxt_md->orig_beg += beg; if (nxt_md->orig_beg < 0) nxt_md->orig_beg = 0; break;
		case ANCHOR_NEXT_MIX: nxt_md->anchor = beg; update_graph(nxt_md->cp,NULL); break;
		}
	    }
	  nxt_md = (mixdata *)(nxt_md->sync_out);
	}
      while (lst_md)
	{
	  cs = lst_md->current_cs;
	  if ((cs) && (cs->syncable)) 
	    {
	      switch (func)
		{
		case MOVE_NEXT_MIX: move_next_mix(lst_md,beg,x); break;
		case SPEED_NEXT_MIX: speed_next_mix(lst_md,spd,x); break;
		case REMIX_FILE: remix_file(lst_md); break;
		case RESPEED: respeed(lst_md,spd); break;
		case TOGGLE_GROUP: toggle_group(lst_md,beg,x); break;
		case RESET_ORIG: lst_md->orig_beg += beg; if (lst_md->orig_beg < 0) lst_md->orig_beg = 0; break;
		case ANCHOR_NEXT_MIX: lst_md->anchor = beg; update_graph(lst_md->cp,NULL); break;
		}
	    }  

	  lst_md = (mixdata *)(lst_md->sync_back);
	}
    }
  else 
    {
      if (clear)
	{
	  cs = md->current_cs;
	  cs->syncable = 0;
	}
    }
}

void move_or_disable_sync_chain(snd_info *sp, mixdata *md, int beg, int nx)
{
  map_across_syncd_mixes(sp,md,MOVE_NEXT_MIX,beg,nx,0.0,CLEAR_SYNC);
}
      
void speed_or_disable_sync_chain(snd_info *sp, mixdata *md, float speed, int val)
{
  map_across_syncd_mixes(sp,md,SPEED_NEXT_MIX,0,val,speed,CLEAR_SYNC);
}

void remix_sync_chain(mixdata *md)
{ 
  map_across_syncd_mixes(((chan_info *)(md->cp))->sound,md,REMIX_FILE,0,0,0.0,DONT_CLEAR_SYNC);
}
      
void speed_sync_chain(mixdata *md, float spd, int ival)
{
  map_across_syncd_mixes(((chan_info *)(md->cp))->sound,md,RESPEED,0,ival,spd,DONT_CLEAR_SYNC);
}      

void toggle_syncd_group(mixdata *md, int on, int ival)
{
  map_across_syncd_mixes(((chan_info *)(md->cp))->sound,md,TOGGLE_GROUP,on,ival,0.0,DONT_CLEAR_SYNC);
}      

void reset_syncd_origs(mixdata *md, int amount)
{
  md->orig_beg += amount;
  if (md->orig_beg < 0) md->orig_beg = 0;
  map_across_syncd_mixes(((chan_info *)(md->cp))->sound,md,RESET_ORIG,amount,0,0.0,DONT_CLEAR_SYNC);
}      

void anchor_or_disable_sync_chain(snd_info *sp, mixdata *md, int anchor)
{
  map_across_syncd_mixes(sp,md,ANCHOR_NEXT_MIX,anchor,0,0.0,CLEAR_SYNC);
}
      



/* ---------------- GROUPED MIXES ---------------- */
/* 
 * two mappings: across channel or across current group
 * group browser might not exist when groups are in use (i.e. console buttons set for grouped movements)
 */

static grp_info *current_group = NULL;
static grp_info **groups = NULL;

grp_info *get_current_group(void) {return(current_group);}
void set_current_group(int i) {current_group = groups[i];}

static grp_info *make_grp_info (snd_state *ss, int group)
{
  grp_info *grp;
  int i;
  grp = (grp_info *)calloc(1,sizeof(grp_info));
  grp->amps = (float *)calloc(GROUP_MAX_OUT_CHANS,sizeof(float *));
  grp->old_amps = (float *)calloc(GROUP_MAX_OUT_CHANS,sizeof(float *));
  grp->speed_env = NULL;
  grp->tempo_env = NULL;
  grp->amp_envs = (env **)calloc(GROUP_MAX_OUT_CHANS,sizeof(env *));
  grp->cps = (chan_info **)calloc(GROUP_MAX_OUT_CHANS,sizeof(chan_info *));
  grp->old_cps = (chan_info **)calloc(GROUP_MAX_OUT_CHANS,sizeof(chan_info *));
  grp->wgs /* (c) 1996,1997 */ = (mix_context **)calloc(GROUP_MAX_OUT_CHANS,sizeof(mix_context *));
  grp->mds = NULL;
  grp->mixes = 0;
  grp->unreadies = 0;
  grp->active = GROUP_INITIALIZED;
  grp->speed = 1.0;
  grp->scl_speed = 1.0;
  grp->tempo = 1.0;
  grp->old_speed = 50;
  grp->old_tempo = 1.0;
  grp->tempfile = (char **)calloc(GROUP_MAX_OUT_CHANS,sizeof(char *));
  grp->play_sps = (snd_info **)calloc(GROUP_MAX_OUT_CHANS,sizeof(snd_info *));
  grp->group = group;
  for (i=0;i<GROUP_MAX_OUT_CHANS;i++) 
    {
      grp->amps[i] = 1.0; 
      grp->old_amps[i] = 1.0;
      grp->cps[i] = NULL;
      grp->amp_envs[i] = NULL;
      grp->tempfile[i] = NULL;
      grp->play_sps[i] = NULL;
    }
  grp->mix_size = 4;
  grp->mds = (mixdata **)calloc(grp->mix_size,sizeof(mixdata *));
  grp->env_strs = (char **)calloc(GROUP_MAX_OUT_CHANS+2,sizeof(char *));
  return(grp);
}

static void clear_grp_info(snd_state *ss, grp_info *grp)
{
  int i;
  grp->active = GROUP_CLEARED;
  grp->playing = 0;
  if (grp->amps) {for (i=0;i<GROUP_MAX_OUT_CHANS;i++) grp->amps[i] = 1.0;}
  grp->speed = 1.0;
  grp->scl_speed = 1.0;
  grp->old_speed = 50;
  grp->tempo = 1.0;
  if (grp->speed_env) grp->speed_env = free_env(grp->speed_env);
  if (grp->tempo_env) grp->tempo_env = free_env(grp->tempo_env);
  for (i=0;i<GROUP_MAX_OUT_CHANS;i++) 
    {
      if (grp->amp_envs[i]) grp->amp_envs[i] = free_env(grp->amp_envs[i]); 
      if (grp->env_strs[i]) {free(grp->env_strs[i]); grp->env_strs[i] = NULL;}
    } 
  if (grp->env_strs[GROUP_MAX_OUT_CHANS]) {free(grp->env_strs[GROUP_MAX_OUT_CHANS]); grp->env_strs[GROUP_MAX_OUT_CHANS] = NULL;}
  if (grp->env_strs[GROUP_MAX_OUT_CHANS+1]) {free(grp->env_strs[GROUP_MAX_OUT_CHANS+1]); grp->env_strs[GROUP_MAX_OUT_CHANS+1] = NULL;}
}

static int add_group_member(grp_info *g, mixdata *md)
{
  int i,lim,new_out;
  g->mds[g->mixes] = md;
  md->rows[g->group] = -1;
  new_out = 0;
  for (i=0;i<GROUP_MAX_OUT_CHANS;i++)
    {
      if (g->cps[i] == md->cp)
	{
	  md->rows[g->group] = i;
	  break;
	}
    }
  if (md->rows[g->group] == -1)
    {
      for (i=0;i<GROUP_MAX_OUT_CHANS;i++)
	{
	  if (g->cps[i] == NULL)
	    {
	      /* add this channel to the output set */
	      md->rows[g->group] = i;
	      if (g->chans <= i) g->chans = i+1; /* 0-based */
	      g->cps[i] = md->cp;
	      if (g->cps[i] == g->old_cps[i])
		{
		  g->amps[i] = g->old_amps[i]; /* toggling group button twice on mix console returns to previous state */
		}
	      else
		{
		  g->amps[i] = 1.0;
		  g->old_amps[i] = 1.0;
		}
	      g->old_cps[i] = g->cps[i];
	      g->wgs[i] = set_mixdata_context(md->cp);
	      new_out = 1;
	      break;
	    }
	}
    }
  g->mixes++;
  if (g->mixes == g->mix_size)
    {
      lim = g->mix_size;
      g->mix_size *= 2;
      g->mds = (mixdata **)realloc(g->mds,g->mix_size * sizeof(mixdata *));
      for (i=lim;i<g->mix_size;i++) g->mds[i] = NULL;
    }
  return(new_out);
}

static int remove_group_member(grp_info *g, mixdata *md)
{
  int i,n,new_out;
  mixdata *mdp;
  n=-1;
  new_out = 0;
  for (i=0;i<g->mixes;i++)
    {
      if (g->mds[i] == md)
	{
	  n=i;
	  break;
	}
    }
  if (n != -1)
    {
      for (i=n;i<g->mixes-1;i++) g->mds[i] = g->mds[i+1];
      g->mds[g->mixes-1] = NULL;
      g->mixes--;
      n = -1;
      for (i=0;i<g->mixes;i++)
	{
	  mdp = g->mds[i];
	  if ((mdp) && (mdp->cp == md->cp)) {n=i; break;}
	}
      if (n == -1)
	{
	  /* remove this channel from the output set */
	  for (i=0;i<GROUP_MAX_OUT_CHANS;i++)
	    {
	      if (g->cps[i] == md->cp)
		{
		  new_out = 1;
		  g->cps[i] = NULL;
		  g->old_amps[i] = g->amps[i];
		  g->amps[i] = 0.0;
		  if (g->chans >= i) g->chans = i; /* 0-based */
		  break;
		}
	    }
	}
    }
  return(new_out);
}

static void check_group_bounds(grp_info *g, mixdata *md)
{
  float tmp,sr;
  console_state *cs;
  if (md)
    {
      sr = (float)snd_SRATE(md->cp); /* output srate is what matters here */
      cs = md->current_cs;
      tmp = (float)(cs->beg)/sr;
      if ((g->mixes == 1) || (tmp < g->beg)) g->beg = tmp;
      tmp = (float)(cs->beg+cs->len)/sr;
      if ((g->mixes == 1) || (tmp > g->end)) g->end = tmp;
    }
}

void set_group_bounds(grp_info *g)
{
  int i;
  mixdata *md;
  g->unreadies = 0;
  if (g->mixes == 0)
    {
      g->beg = 0.0;
      g->end = 0.0;
    }
  else
    {
      g->beg = 100000.0;
      g->end = 0.0;
      for (i=0;i<g->mixes;i++) 
	{
	  md = g->mds[i];
	  if (ready_mix(md))
	    check_group_bounds(g,md);
	  else g->unreadies++;
	}
    }
}

#if 0
static int cp_member(chan_info *cp, chan_info **cps, int size)
{
  int i;
  for (i=0;i<size;i++)
    {
      if (cp == cps[i]) return(TRUE);
    }
  return(FALSE);
}
#endif

void set_group_out_names(grp_info *g, char **file_names, int size)
{
  int i;
  chan_info *cp = NULL;
  snd_info *sp = NULL;
  char *str = NULL;
  for (i=0;i<size;i++)
    {
      cp = g->cps[i];
      if (cp)
	{
	  sp = cp->sound;
	  file_names[i] = (char *)calloc(64,sizeof(char));
	  str = just_filename(sp->shortname);  /* this allocates, so we need free at this level */
	  if (sp->nchans == 1)
	    sprintf(file_names[i],"%s",str);
	  else sprintf(file_names[i],"%s #%d",str,(cp->chan + 1));
	  free(str); str = NULL;
	}
    }
}

static int move_group_member(mixdata *md, int change)
{
  int samp,samps;
  chan_info *cp;
  console_state *cs;
  cs = md->current_cs;
  cp = md->cp;
  samp = cs->orig + change;
  if (samp < 0) samp = 0;
  samps = current_ed_samples(cp);
  if (samp > samps) samp = samps;
  cs->beg = samp;
  return(0);
}

static int sweep_ctr = 0;

void move_associated_mixes(mixdata *md, int amount, int include_current)
{
  mixdata *tmp_md;
  mixdata **mds;
  mixmark *m;
  chan_info *cp;
  axis_info *ap;
  grp_info *g;
  console_state *cs,*tmp_cs;
  snd_state *ss;
  int i,j,k,gr,spot,xspot;
  ss = md->ss;
  sweep_ctr++;
  cs = md->current_cs;
  gr = cs->groups;
  if (!include_current) md->sweep_tag = sweep_ctr;

  for (i=0,j=1;i<ss->ngroups;i++,j*=2)
    {
      if (j & gr)
	{
	  g = groups[i];
	  if (g->mixes > 0)
	    {
	      mds = g->mds;
	      for (k=0;k<g->mixes;k++)
		{
		  tmp_md = mds[k];
		  if (ready_mix(tmp_md))
		    {
		      cs = tmp_md->current_cs;
		      if ((cs) && (!(cs->locked)) && (tmp_md->sweep_tag != sweep_ctr))
			{
			  tmp_md->sweep_tag = sweep_ctr;
			  m = tmp_md->mixer;
			  if ((m) && (ss->mix_duration_brackets)) erase_mix_duration_bracket(tmp_md,m->x,m->y);
			  move_group_member(tmp_md,amount);
			  if (m) 
			    {
			      tmp_cs = tmp_md->current_cs;
			      cp = tmp_md->cp;
			      ap = cp->axis;
			      spot = tmp_cs->beg + tmp_md->anchor;
			      xspot = grf_x((double)spot/(double)snd_SRATE(cp),ap);
			      if (((xspot + tmp_md->width) <= ap->x_axis_x1) && (xspot >= ap->x_axis_x0))
				{
				  move_mix_x(tmp_md->mixer,xspot);
				  set_mix_title_beg(tmp_md,tmp_md->mixer);
				  if (ss->mix_duration_brackets) draw_mix_duration_bracket(tmp_md,m->x,m->y);
				}
			      else
				release_mixmark(tmp_md->mixer);
			    }}}}}}}
}

void reset_associated_origs(mixdata *md, int amount)
{
  mixdata *tmp_md;
  mixdata **mds;
  grp_info *g;
  snd_state *ss;
  int i,j,k,gr;
  ss = md->ss;
  gr = (md->current_cs)->groups;
  sweep_ctr++;
  for (i=0,j=1;i<ss->ngroups;i++,j*=2)
    {
      if (j & gr)
	{
	  g = groups[i];
	  if (g->mixes > 0)
	    {
	      mds = g->mds;
	      for (k=0;k<g->mixes;k++)
		{
		  tmp_md = mds[k];
		  if ((ready_mix(tmp_md)) && (tmp_md->sweep_tag != sweep_ctr))
		    {
		      tmp_md->sweep_tag = sweep_ctr;
		      tmp_md->orig_beg += amount;
		      if (tmp_md->orig_beg < 0) tmp_md->orig_beg = 0;
		    }}}}}
}

static char *ur_remix_file_with_group(grp_info *g, int row, int with_original)
{
  /* if g->tempfile[row] then we can use it (else should get it first) */
  mixdata **mds;
  mixdata *md;
  console_state **css;
  int beg,end = 0,i,j,k,ofd = 0,size,num,val,maxy,miny,msize,no_space,use_temp_file,zeros,simples,insum;
  float fmax,fmin;
  snd_info *cursp;
  mix_fd **adds,**subs;
  snd_fd *cur;
  snd_fd **sfbs,**afbs;
  snd_state *ss;
  char *ofile = NULL;
  int **data;
  int *chandata;
  file_info *ohdr = NULL;
  axis_info *ap;
  chan_info *cp;
  int *old_begs,*old_ends,*new_begs,*new_ends;
  console_state *cs;
  old_ends=NULL; old_begs=NULL; new_ends=NULL; new_begs=NULL; cur=NULL; adds=NULL; subs=NULL; mds=NULL;
  cp = g->cps[row];
  ap = cp->axis;
  msize = 0;
  for (i=0;i<g->mixes;i++)
    {
      md = g->mds[i];
      if ((md) && (md->cp == cp) && (ready_mix(md))) msize++;
    }
  if (msize == 0) return(NULL);
  mds = (mixdata **)calloc(msize,sizeof(mixdata *));
  css = (console_state **)calloc(msize,sizeof(console_state *));
  old_begs = (int *)calloc(msize,sizeof(int));
  new_begs = (int *)calloc(msize,sizeof(int));
  old_ends = (int *)calloc(msize,sizeof(int));
  new_ends = (int *)calloc(msize,sizeof(int));
  adds = (mix_fd **)calloc(msize,sizeof(mix_fd *));
  if (with_original) subs = (mix_fd **)calloc(msize,sizeof(mix_fd *));
  j = 0;
  beg = -1;
  simples = 1;
  zeros = 1;
  for (i=0;i<g->mixes;i++)
    {
      md = g->mds[i];
      if ((md) && (md->cp == cp) && (ready_mix(md)))
	{
	  mds[j] = md;
	  if (with_original) release_pending_consoles(md);
	  css[j] = md->current_cs;
	  old_begs[j] = css[j]->orig;
	  new_begs[j] = css[j]->beg;
	  old_ends[j] = css[j]->end;
	  new_ends[j] = new_begs[j] + css[j]->len -1;
	  if (beg == -1) {beg = old_begs[j]; end = old_ends[j];}
	  adds[j] = init_mix_read(md,0);
	  if (adds[j]->calc != C_ZERO) zeros = 0;
	  if (adds[j]->calc != C_STRAIGHT) simples = 0;
	  if (with_original) 
	    {
	      subs[j] = init_mix_read(md,1);
	      if (subs[j]->calc != C_ZERO) zeros = 0;
	      if (subs[j]->calc != C_STRAIGHT) simples = 0;
	      if (old_begs[j] < beg) beg = old_begs[j];
	      if (new_begs[j] < beg) beg = new_begs[j];
	    }
	  if (old_ends[j] > end) end = old_ends[j];
	  if (new_ends[j] > end) end = new_ends[j];
	  j++;
	}
    }
  if (!with_original) beg = g->beg * snd_SRATE(cp); /* i.e. pad on left with 0's if necessary */
  num = end-beg+1;
  use_temp_file = ((!with_original) || (num >= MAX_BUFFER_SIZE));

  ss = cp->state;
  cursp = cp->sound;
  if (with_original) cur = init_sample_read(beg,cp,READ_FORWARD);

  if (use_temp_file)
    {
      ofile = tempnam(ss->temp_dir,"snd_");
      ohdr = make_temp_header(ofile,cursp->hdr,0);
      ohdr->chans = 1;
#ifdef CLM_LITTLE_ENDIAN
      ohdr->format = snd_32_linear_little_endian;
#else
      ohdr->format = snd_32_linear;
#endif
      ofd = open_temp_file(ofile,1,ohdr,ss);
      no_space = disk_space_p(cursp,ofd,num*4,0);
      if (no_space == GIVE_UP)
	{
	  snd_close(ofd);
	  remove(ofile);
	  free(old_begs);
	  free(old_ends);
	  free(new_begs);
	  free(new_ends);
	  free(css);
	  free(mds);
	  return(NULL);
	}
    }
  if (g->tempfile[row])
    {
      remove(g->tempfile[row]);
      g->tempfile[row] = NULL; /* gc it? */
    }
  if (num < MAX_BUFFER_SIZE) size = num; else size = MAX_BUFFER_SIZE;
  data = (int **)calloc(1,sizeof(int *));
  data[0] = (int *)calloc(size,sizeof(int));
  chandata = data[0];
  if (use_temp_file) clm_seek(ofd,ohdr->data_location,0);

  for (k=0;k<msize;k++)
    {
      old_begs[k] -= beg;
      old_ends[k] -= beg;
      new_begs[k] -= beg;
      new_ends[k] -= beg;
    }

  maxy = 32767;
  miny = -32768;

  if (with_original)
    {
      if (zeros)
	{
	  for (i=0,j=0;i<num;i++)
	    {
	      if (j == size)
		{
		  if (use_temp_file) clm_write(ofd,0,size-1,1,&chandata);
		  j = 0;
		}
	      NEXT_SAMPLE(chandata[j],cur);
	      j++;
	    }
	}
      else
	{
	  if (simples)
	    {
	      sfbs = (snd_fd **)calloc(msize,sizeof(snd_fd *));
	      afbs = (snd_fd **)calloc(msize,sizeof(snd_fd *));
	      for (i=0;i<msize;i++)
		{
		  sfbs[i] = (subs[i])->sfs[(subs[i])->base];
		  afbs[i] = (adds[i])->sfs[(adds[i])->base];
		}
	      for (i=0,j=0;i<num;i++)
		{
		  if (j == size)
		    {
		      if (use_temp_file) clm_write(ofd,0,size-1,1,&chandata);
		      j = 0;
		    }
		  NEXT_SAMPLE(insum,cur);
		  for (k=0;k<msize;k++)
		    {
		      if ((i>=old_begs[k]) && (i<=old_ends[k]))
			{
			  NEXT_SAMPLE(val,sfbs[k]);
			  insum -= val;
			}
		      if ((i>=new_begs[k]) && (i<=new_ends[k]))
			{
			  NEXT_SAMPLE(val,afbs[k]);
			  insum += val;
			}
		    }
		  if (insum > maxy) maxy = insum;
		  else if (insum < miny) miny = insum;
		  chandata[j] = insum;
		  j++;
		}
	      free(sfbs);
	      free(afbs);
	    }
	  else
	    {
	      for (i=0,j=0;i<num;i++)
		{
		  if (j == size)
		    {
		      if (use_temp_file) clm_write(ofd,0,size-1,1,&chandata);
		      j = 0;
		    }
		  NEXT_SAMPLE(chandata[j],cur);
		  for (k=0;k<msize;k++)
		    {
		      if ((i>=old_begs[k]) && (i<=old_ends[k]))
			{
			  val = next_mix_sample(subs[k]);
			  chandata[j] -= val;
			}
		      if ((i>=new_begs[k]) && (i<=new_ends[k]))
			{
			  val = next_mix_sample(adds[k]);
			  chandata[j] += val;
			}
		    }
		  if (chandata[j] > maxy) maxy = chandata[j];
		  else if (chandata[j] < miny) miny = chandata[j];
		  j++;
		}
	    }
	}
    }
  else
    {
      for (i=0,j=0;i<num;i++)
	{
	  if (j == size)
	    {
	      if (use_temp_file) clm_write(ofd,0,size-1,1,&chandata);
	      j = 0;
	    }
	  chandata[j] = 0;
	  for (k=0;k<msize;k++)
	    {
	      if ((i>=new_begs[k]) && (i<=new_ends[k]))
		{
		  val = next_mix_sample(adds[k]);
		  chandata[j] += val;
		}
	    }
	  j++;
	}
    }
  if (use_temp_file)
    {
      if (j > 0) clm_write(ofd,0,j-1,1,&chandata);
      close_temp_file(ofd,ohdr,num*c_snd_datum_size(ohdr->format),cursp);
      free_file_info(ohdr);
    }
  if (with_original) free_snd_fd(cur);
  if (adds)
    {
      for (k=0;k<msize;k++) if (adds[k]) free_mix_fd(adds[k]);
      free(adds);
    }
  if (subs)
    {
      for (k=0;k<msize;k++) if (subs[k]) free_mix_fd(subs[k]);
      free(subs);
    }

  if (with_original)
    {
      if (use_temp_file)
	file_change_samples(beg,num,ofile,cp,0,DELETE_ME,DONT_LOCK_MIXES);
      else change_samples(beg,num,data[0],cp,DONT_LOCK_MIXES);
      for (k=0;k<msize;k++)
	{
	  md = mds[k];
	  extend_console_list(md);
	  cs = copy_console(css[k]);
	  cs->edit_ctr = cp->edit_ctr;
	  cs->orig = new_begs[k]+beg;
	  cs->beg = cs->orig;
	  cs->end = cs->orig+cs->len-1;
	  md->states[md->curcons] = cs;
	  make_current_console(md);
	}
      /* fix up graph if we overflowed during mix */
      fmax = maxy*clm_sndflt;
      fmin = miny*clm_sndflt;
      if ((fmax > ap->ymax) || (fmin < ap->ymin)) 
	{
	  if (fmax < -fmin) fmax = -fmin; 
	  set_y_limits(cp,ap,fmax);
	}
      update_graph(cp,NULL);
      check_for_first_edit(cp);
    }
  if (use_temp_file) free(data[0]);
  free(data);
  if (old_begs) free(old_begs);
  if (old_ends) free(old_ends);
  if (new_begs) free(new_begs);
  if (new_ends) free(new_ends);
  if (css) free(css);
  if (mds) free(mds);
  return(ofile);
}

void remix_file_with_group(grp_info *g, int row)
{
  ur_remix_file_with_group(g,row,TRUE);
}

static void make_temporary_graph_with_group(grp_info *g, int row)
{
  /* find array of currently active visible mixes in the current row and run through all in parallel */
  /* if g->tempfile[row] then we can use it (else should get it first) */
  mixdata **mds;
  mixdata *md;
  console_state *cs;
  chan_info *cp;
  console_state **css;
  int *oldbegs,*newbegs,*oldends,*newends;
  int i,j,k,m,samps,xi,size,zeros,simples,val,ghi,glo;
  int widely_spaced;
  axis_info *ap;
  snd_info *sp;
  mix_context *ms;
  snd_state *ss;
  float samples_per_pixel,xf;
  double x,incr,initial_x;
  int ina,ymin,ymax,lo,hi;
  snd_fd *sf;
  snd_fd **sfbs,**afbs;
  mix_fd **adds,**subs;
  int x_start,x_end;
  double start_time,cur_srate;
  oldends=NULL; oldbegs=NULL; newends=NULL; newbegs=NULL; sf=NULL; adds=NULL; subs=NULL; mds=NULL; css=NULL;
  cp = g->cps[row];
  ss = cp->state;
  if (!(ss->movies)) return;
  ms = g->wgs[row];

  sp = cp->sound;
  ap = cp->axis;
  cur_srate = (double)snd_SRATE(sp);
  start_time = (double)(ap->losamp)/cur_srate;
  x_start = grf_x(start_time,ap);
  x_end = grf_x((double)(ap->hisamp)/cur_srate,ap);
  lo = ap->losamp;
  hi = ap->hisamp;
  ghi = lo;
  glo = hi;

  /* now collect the mixes that are visible and active */
  size = 0;
  for (i=0;i<g->mixes;i++)
    {
      md = g->mds[i];
      cs = md->current_cs;
      if ((md) && (md->cp == cp) && (!(cs->locked)) && (ready_mix(md)) &&
	  ((cs->orig < hi) || (cs->beg < hi)) &&
	  ((cs->end > lo) || ((cs->beg+cs->len) > lo)))
	size++;
    }
  if (size == 0) return;
  sf = init_sample_read(ap->losamp,cp,READ_FORWARD);
  samps = ap->hisamp-ap->losamp+1;
  samples_per_pixel = (float)(samps-1)/(float)(x_end-x_start);
  mds = (mixdata **)calloc(size,sizeof(mixdata *));
  css = (console_state **)calloc(size,sizeof(console_state *));
  oldbegs = (int *)calloc(size,sizeof(int));
  newbegs = (int *)calloc(size,sizeof(int));
  oldends = (int *)calloc(size,sizeof(int));
  newends = (int *)calloc(size,sizeof(int));
  adds = (mix_fd **)calloc(size,sizeof(mix_fd *));
  subs = (mix_fd **)calloc(size,sizeof(mix_fd *));
  j = 0;
  simples = 1;
  zeros = 1;
  for (i=0;i<g->mixes;i++)
    {
      md = g->mds[i];
      cs = md->current_cs;
      /* can't depend on pre-existing mix consoles here (via md->mixer) */
      if ((md) && (md->cp == cp) && (!(cs->locked)) && (ready_mix(md)) &&
	  ((cs->orig < hi) || (cs->beg < hi)) &&
	  ((cs->end > lo) || ((cs->beg+cs->len) > lo)))
	{
	  mds[j] = md;
	  css[j] = cs;
	  oldbegs[j] = css[j]->orig;
	  newbegs[j] = css[j]->beg;
	  oldends[j] = css[j]->end;
	  newends[j] = newbegs[j] + css[j]->len;
	  if (glo > oldbegs[j]) glo = oldbegs[j];
	  if (glo > newbegs[j]) glo = newbegs[j];
	  if (ghi < oldends[j]) ghi = oldends[j];
	  if (ghi < newends[j]) ghi = newends[j];
	  adds[j] = init_mix_read(md,0);
	  if (adds[j]->calc != C_ZERO) zeros = 0;
	  if (adds[j]->calc != C_STRAIGHT) simples = 0;
	  subs[j] = init_mix_read(md,1);
	  if (subs[j]->calc != C_ZERO) zeros = 0;
	  if (subs[j]->calc != C_STRAIGHT) simples = 0;
	  if ((oldbegs[j] < lo) && (lo < oldends[j])) 
	    {
	      for (k=oldbegs[j];k<lo;k++) 
		next_mix_sample(subs[j]);
	    }
	  j++;
	}
    }
  if ((samples_per_pixel < 5.0) && (samps < POINT_BUFFER_SIZE))
    {
      if (samples_per_pixel < 1.0)
	{
	  incr = 1.0 / samples_per_pixel;
	  initial_x = x_start;
	  widely_spaced = 1;
	}
      else
	{
	  incr = (double)1.0 /cur_srate;
	  initial_x = start_time;
	  widely_spaced = 0;
	}
      for (j=0,i=lo,x=initial_x;i<=hi;i++,j++,x+=incr)
	{
	  NEXT_SAMPLE(ina,sf);
	  if ((i>=glo) && (i<=ghi))
	    {
	      for (k=0;k<size;k++)
		{
		  if ((i >= oldbegs[k]) && (i <= oldends[k])) ina -= next_mix_sample(subs[k]);
		  if ((i >= newbegs[k]) && (i <= newends[k])) ina += next_mix_sample(adds[k]);
		}
	    }
	  if (widely_spaced)
	    set_grf_point(x,j,grf_y(clm_sndflt * ina,ap));
	  else set_grf_point(grf_x(x,ap),j,grf_y(clm_sndflt * ina,ap));
	}
      erase_and_draw_grf_points(ss,ms,cp,j);
    }
  else
    {
      /* can't use subsampled graphics or amplitude envelopes here because the cancellation is way off */
      j = 0;      /* graph point counter */
      x=ap->x0;
      xi=grf_x(x,ap);
      i=lo;
      xf=0.0;     /* samples per pixel counter */
      ymin=3276800;
      ymax=-3276800;

      if (zeros)
	{
	  while (i<=hi)
	    {
	      NEXT_SAMPLE(ina,sf);
	      if (ina > ymax) ymax = ina;
	      if (ina < ymin) ymin = ina;
	      xf+=1.0;
	      i++;
	      if (xf>samples_per_pixel)
		{
		  set_grf_points(xi,j,grf_y(clm_sndflt * ymin,ap),grf_y(clm_sndflt * ymax,ap));
		  xi++;
		  j++;
		  xf -= samples_per_pixel;
		  ymin=3276800;
		  ymax=-3276800;
		}
	    }
	}
      else
	{
	  if (simples)
	    {
	      sfbs = (snd_fd **)calloc(size,sizeof(snd_fd *));
	      afbs = (snd_fd **)calloc(size,sizeof(snd_fd *));
	      for (m=0;m<size;m++) /* i is global counter at this point (i=lo above) */
		{
		  sfbs[m] = (subs[m])->sfs[(subs[m])->base];
		  afbs[m] = (adds[m])->sfs[(adds[m])->base];
		}
	      while (i<=hi)
		{
		  NEXT_SAMPLE(ina,sf);
		  if ((i>=glo) && (i<=ghi))
		    {
		      for (k=0;k<size;k++)
			{
			  if ((i>=oldbegs[k]) && (i<=oldends[k]))
			    {
			      NEXT_SAMPLE(val,sfbs[k]);
			      ina -= val;
			    }
			  if ((i>=newbegs[k]) && (i<=newends[k]))
			    {
			      NEXT_SAMPLE(val,afbs[k]);
			      ina += val;
			    }
			}
		    }
		  if (ina > ymax) ymax = ina;
		  if (ina < ymin) ymin = ina;
		  xf+=1.0;
		  i++;
		  if (xf>samples_per_pixel)
		    {
		      set_grf_points(xi,j,grf_y(clm_sndflt * ymin,ap),grf_y(clm_sndflt * ymax,ap));
		      xi++;
		      j++;
		      xf -= samples_per_pixel;
		      ymin=3276800;
		      ymax=-3276800;
		    }
		}
	      free(sfbs);
	      free(afbs);
	    }
	  else
	    {
	      while (i<=hi)
		{
		  NEXT_SAMPLE(ina,sf);
		  if ((i>=glo) && (i<=ghi))
		    {
		      for (k=0;k<size;k++)
			{
			  if ((i >= oldbegs[k]) && (i <= oldends[k])) ina -= next_mix_sample(subs[k]);
			  if ((i >= newbegs[k]) && (i <= newends[k])) ina += next_mix_sample(adds[k]);
			}
		    }
		  if (ina > ymax) ymax = ina;
		  if (ina < ymin) ymin = ina;
		  xf+=1.0;
		  i++;
		  if (xf>samples_per_pixel)
		    {
		      set_grf_points(xi,j,grf_y(clm_sndflt * ymin,ap),grf_y(clm_sndflt * ymax,ap));
		      xi++;
		      j++;
		      xf -= samples_per_pixel;
		      ymin=3276800;
		      ymax=-3276800;
		    }
		}
	    }
	}
      erase_and_draw_both_grf_points(ss,ms,cp,j);
    }
  ms->lastpj = j;
  if (sf) free_snd_fd(sf);
  if (oldbegs) free(oldbegs);
  if (oldends) free(oldends);
  if (newbegs) free(newbegs);
  if (newends) free(newends);
  if (adds)
    {
      for (k=0;k<size;k++) if (adds[k]) free_mix_fd(adds[k]);
      free(adds);
    }
  if (subs)
    {
      for (k=0;k<size;k++) if (subs[k]) free_mix_fd(subs[k]);
      free(subs);
    }
  if (mds) free(mds);
  if (css) free(css);
}

void make_temporary_graph_over_group(snd_state *ss, grp_info *g)
{
  int i;
  chan_info *cp;
  mix_context *ms;
  for (i=0;i<g->chans;i++)
    {
      ms = g->wgs[i];
      cp = g->cps[i];
      if (!(ms->lastpj)) 
	{
	  ms->lastpj = make_graph(cp,cp->sound,ss); 
	  mix_save_graph(ss,ms,ms->lastpj);
	}
      make_temporary_graph_with_group(g,i);
    }
}

void make_temporary_graph_over_groups(snd_state *ss, int gr)
{
  int i,j;
  for (i=0,j=1;i<ss->ngroups;i++,j*=2)
    {
      if (j & gr) make_temporary_graph_over_group(ss,groups[i]);
    }
}

void remix_file_over_group(grp_info *g)
{
  int i;
  for (i=0;i<g->chans;i++) remix_file_with_group(g,i);
}

static char *group_to_temp_file(grp_info *g, int i)
{
  return(ur_remix_file_with_group(g,i,FALSE));
}

void remix_file_over_groups(snd_state *ss, int gr)
{
  int i,j;
  for (i=0,j=1;i<ss->ngroups;i++,j*=2)
    {
      if (j & gr) remix_file_over_group(groups[i]);
    }
}


/* ---------------- group editor actions ---------------- */

static void refigure_amp_env(snd_state *ss, mixdata *md, int cp_samps, int srate)
{
  /* reset cs->gs_amp_env in md->current_cs based on all participating group amp_envs (via md->rows) */
  /* each console-local amp_env is only that portion of the overall group env that happens to affect that mix */
  /* magify_env in snd-chn.c builds float arr for mixenv reader */
  /* make_mixenv(env *e, int overall_beg, int overall_dur, int local_beg, int local_dur) */
  int i,j,ge_ctr;
  console_state *cs;
  grp_info *g;
  env **ges;
  env *ge;
  int *begs,*durs;
  cs = md->current_cs;
  if (!(cs->locked))
    {
      if (cs->gs_amp_env) free_mixenv((mixenv *)(cs->gs_amp_env));
      if (cs->groups)
	{
	  ges = (env **)calloc(ss->ngroups,sizeof(env *));
	  begs = (int *)calloc(ss->ngroups,sizeof(int));
	  durs = (int *)calloc(ss->ngroups,sizeof(int));
	  ge_ctr = 0;
	  for (i=0,j=1;i<ss->ngroups;i++,j*=2)
	    {
	      if (j & (cs->groups))
		{
		  g = groups[i];
		  ge = g->amp_envs[md->rows[i]];
		  if (ge)
		    {
		      ges[ge_ctr] = ge;
		      if (g->env_entire)
			{
			  begs[ge_ctr] = 0;
			  durs[ge_ctr] = cp_samps;
			}
		      else
			{
			  begs[ge_ctr] = g->beg * srate + 0.5;
			  durs[ge_ctr] = (g->end - g->beg) * srate + 0.5;
			}
		      ge_ctr++;
		    }
		}
	    }
	  cs->gs_amp_env = meld_envs(cs,ges,begs,durs,ge_ctr);
	  free(ges);
	  free(begs);
	  free(durs);
	}
      else cs->gs_amp_env = NULL;
    }
}

void update_group_browser_labels(snd_state *ss, mixdata *md, console_state *cs)
{

  /* TODO: if one group browser changes speed of shared mix console, we need a check of the shared groups bounds */

  int i,j;
  grp_info *g;
  if (cs->groups)
    {
      for (i=0,j=1;i<ss->ngroups;i++,j*=2)
	{
	  if (j & (cs->groups))
	    {
	      g = groups[i];
	      check_group_bounds(g,md);
	      if ((g == current_group) && (gb_is_active()))
		remake_group_browser_label();
	    }
	}
    }
}

void update_groups(void)
{
  int i;
  if ((groups) && (current_group) && (gb_is_active()))
    {
      current_group->unreadies = 0;
      for (i=0;i<current_group->mixes;i++) 
	if (!(ready_mix(current_group->mds[i]))) current_group->unreadies++;
      remake_group_browser_label();
    }
}

void refigure_amp(snd_state *ss, mixdata *md)
{
  int i,j;
  float ratio = 1.0;
  console_state *cs;
  grp_info *g;
  cs = md->current_cs;
  if (!(cs->locked))
    {
      for (i=0,j=1;i<ss->ngroups;i++,j*=2)
	{
	  if (j & (cs->groups))
	    {
	      g = groups[i];
	      ratio *= g->amps[md->rows[i]];
	    }
	}
      cs->gs_amp = ratio;
      reflect_mixer_amp(md);
    }
}

void refigure_amps(snd_state *ss, int row)
{
  int i;
  mixdata *md;
  chan_info *cp;
  cp = current_group->cps[row];
  for (i=0;i<current_group->mixes;i++)
    {
      md = current_group->mds[i];
      if ((md) && (md->cp == cp) && (ready_mix(md))) refigure_amp(ss,md);
    }
}

void refigure_amp_envs(snd_state *ss, int row)
{
  /* current_group->amp_envs[row] has changed -- need to run through all associated mixes and fixup envs */
  mixdata *md;
  chan_info *cp;
  int cp_samps,i,srate;
  cp = current_group->cps[row]; /* amp env is supposed to affect only this channel of output */
  cp_samps = current_ed_samples(cp);
  srate = snd_SRATE(cp);
  for (i=0;i<current_group->mixes;i++)
    {
      md = current_group->mds[i];
      if ((md) && (md->cp == cp) && (ready_mix(md))) 
	refigure_amp_env(ss,md,cp_samps,srate);
    }
}

void refigure_one_amp_env(snd_state *ss, mixdata *md)
{
  chan_info *cp;
  cp = md->cp;
  refigure_amp_env(ss,md,current_ed_samples(cp),snd_SRATE(cp));
}

static float group_env_val(grp_info *g, env *te, float beg, float chan_end)
{
  int i;
  float tbeg,xend,gbeg,gend;
  if (g->env_entire)
    {
      gbeg = 0;
      gend = chan_end;
    }
  else
    {
      gbeg = g->beg;
      gend = g->end;
    }
  xend = te->data[te->pts*2 - 2];
  tbeg = te->data[0] + (beg - gbeg) * (xend - te->data[0]) / (gend - gbeg); 
  for (i=0;i<te->pts*2;i+=2)
    {
      if (te->data[i] == tbeg) return(te->data[i+1]);
      if (te->data[i] > tbeg) 
	return(te->data[i-1] + (tbeg - te->data[i-2]) * (te->data[i+1] - te->data[i-1]) / (te->data[i] - te->data[i-2]));
    }
  return(te->data[te->pts*2 - 1]);
}

static float tempo_env_val(grp_info *g, float beg, float chan_end) {return(group_env_val(g,g->tempo_env,beg,chan_end));}
static float speed_env_val(grp_info *g, float beg, float chan_end) {return(group_env_val(g,g->speed_env,beg,chan_end));} 

void refigure_speed(snd_state *ss, mixdata *md)
{
  int i,j;
  float ratio = 1.0;
  float srate;
  console_state *cs;
  mixmark *m;
  cs = md->current_cs;
  if (!(cs->locked))
    {
      for (i=0,j=1;i<ss->ngroups;i++,j*=2)
	{
	  if (j & (cs->groups)) 
	    {
	      ratio *= (groups[i]->speed);
	      if (groups[i]->speed_env)
		{
		  srate = (float)snd_SRATE(md->cp);
		  ratio *= speed_env_val(groups[i],(float)(cs->beg)/srate,(float)current_ed_samples(md->cp)/srate);
		}
	    }
	}
      cs->gs_speed = ratio;
      set_mix_speed(md,cs);
      m = md->mixer;
      if ((m) && (ss->mix_duration_brackets)) erase_mix_duration_bracket(md,m->x,m->y);      
      cs->len = ceil(md->in_samps / cs->speed);
      if ((m) && (ss->mix_duration_brackets)) draw_mix_duration_bracket(md,m->x,m->y);      
      if (cs->gs_amp_env) refigure_one_amp_env(ss,md);
    }
}

static void refigure_speeds(snd_state *ss, grp_info *g)
{
  int i;
  mixdata *md;
  for (i=0;i<g->mixes;i++) 
    {
      md = g->mds[i];
      if (ready_mix(md)) refigure_speed(ss,md);
    }
}

void update_group_speed(snd_state *ss, grp_info *g)
{
  if (g->mixes > 0)
    {
      refigure_speeds(ss,g);
      make_temporary_graph_over_group(ss,g);
      set_group_bounds(g);
      if ((g == current_group) && (gb_is_active())) remake_group_browser_label();
    }
}

/* group tempo must be based on inter-mix begin time differences (else envelope causes time reversal) */

static int compare_mixes(const void *umx1, const void *umx2)
{
  mixdata *mx1,*mx2;
  mx1 = (mixdata *)(*((mixdata **)umx1));
  mx2 = (mixdata *)(*((mixdata **)umx2));
  if (mx1->orig_beg > mx2->orig_beg) return(1);
  if (mx1->orig_beg == mx2->orig_beg) return(0);
  return(-1);
}

void refigure_tempo(snd_state *ss, grp_info *g)
{
  int i,j,k,spot,xspot,mixes;
  int *begs = NULL;
  float srate;
  mixdata **mds = NULL;
  float ratio;
  console_state *cs;
  mixmark *m;
  mixdata *md,*old_md;
  chan_info *cp;
  axis_info *ap;
  mds = (mixdata **)calloc(g->mixes,sizeof(mixdata *));
  for (j=0,i=0;i<g->mixes;i++) 
    {
      md = g->mds[i]; 
      if ((md) && (ready_mix(md))) mds[j++] = md;
    }
  mixes = j;
  /*  sort by orig_beg */
  qsort((void *)mds,mixes,sizeof(mixdata *),compare_mixes);
  begs = (int *)calloc(mixes,sizeof(int));
  begs[0] = mds[0]->orig_beg;
  old_md = mds[0];
  for (k=1;k<mixes;k++)
    {
      md = mds[k];
      cs = md->current_cs;
      ratio = 1.0;
      for (i=0,j=1;i<ss->ngroups;i++,j*=2)
	{
	  if (j & (cs->groups)) 
	    {
	      ratio *= (groups[i]->tempo);
	      if (groups[i]->tempo_env)
		{
		  srate = (float)snd_SRATE(md->cp);
		  ratio *= tempo_env_val(groups[i],(float)(cs->beg)/srate,(float)current_ed_samples(md->cp)/srate);
		}
	    }
	}
      cs->gs_tempo = ratio;
      m = md->mixer;
      if ((m) && (ss->mix_duration_brackets)) erase_mix_duration_bracket(md,m->x,m->y);
      cs->beg = begs[k-1] + (md->orig_beg - old_md->orig_beg)*cs->gs_tempo;
      begs[k] = cs->beg;
      /* can't use cs->orig because it changes upon release of tempo drag, making next motion highly confusing */
      /* however, explicit move (group or otherwise) should reset this (?) */
      /* move mixer or release it as per move_mix */
      if (m)
	{
	  cp = md->cp;
	  ap = cp->axis;
	  spot = cs->beg + md->anchor;
	  xspot = grf_x((double)spot/(double)snd_SRATE(cp),ap);
	  if (((xspot + md->width) <= ap->x_axis_x1) && (xspot >= ap->x_axis_x0))
	    {
	      move_mix_x(m,xspot); /* mix_dragged better be TRUE here */
	      set_mix_title_beg(md,m);
	      if (ss->mix_duration_brackets) draw_mix_duration_bracket(md,m->x,m->y);
	    }
	  else
	    release_mixmark(m);
	}
      old_md = md;
    }
  if (begs) free(begs);
  if (mds) free(mds);
}


static void ur_notify_group(mixdata *md, int g, int on, int clm)
{
  snd_state *ss;
  int i,new_out;
  grp_info *grp;
  ss = md->ss;
  new_out = 0;
  if (!groups) 
    {
      groups = (grp_info **)calloc(ss->ngroups,sizeof(grp_info *));
      for (i=0;i<ss->ngroups;i++) groups[i] = make_grp_info(ss,i);
    }
  grp = groups[g];
  if (on) 
    {
      new_out = add_group_member(grp,md); 
      check_group_bounds(grp,md);
    }
  else 
    {
      new_out = remove_group_member(grp,md);
      set_group_bounds(grp);
    }
  if (clm) return;
  if ((current_group) && (gb_is_active()) && (grp == current_group)) 
    {
      remake_group_browser_label();
      if (new_out) remake_group_amp_labels(ss);
      reflect_group_size_in_scales(ss,current_group);
    }
  if (!current_group) current_group = grp;
  for (i=0;i<grp->chans;i++)
    {
      if (grp->tempfile[i])
	{
	  remove(grp->tempfile[i]);
	  grp->tempfile[i] = NULL; 
	}
    }
  refigure_amp(ss,md);
  refigure_speed(ss,md);
  refigure_one_amp_env(ss,md);
  /* if group bounds change, do something */
}

void notify_group(mixdata *md, int g, int on) {ur_notify_group(md,g,on,FALSE);}
static void clm_notify_group(mixdata *md, int g, int on) {ur_notify_group(md,g,on,TRUE);}

void clear_groups(snd_state *ss)
{
  int i;
  mixdata *md;
  if (current_group) 
    {
      clear_grp_info(ss,current_group);
      update_group_browser(ss);
      if (current_group->mixes > 0) 
	{
	  for (i=0;i<current_group->mixes;i++) 
	    {
	      md = current_group->mds[i];
	      if (ready_mix(md))
		{
		  refigure_speed(ss,md);
		  refigure_amp(ss,md);
		  refigure_one_amp_env(ss,md);
		}
	    }
	  if (current_group->mixes > 1) 
	    refigure_tempo(ss,current_group);
	  remix_file_over_group(current_group);
	}
    }
}

void remix_group_speeds(snd_state *ss, grp_info *g)
{
  if (g->mixes > 0) 
    {
      refigure_speeds(ss,g);
      remix_file_over_group(g);
      set_group_bounds(g);
      if ((g == current_group) && (gb_is_active())) remake_group_browser_label();
    }
}

void update_group_tempo(snd_state *ss, grp_info *g)
{
  if (current_group->mixes > 1)
    {
      refigure_tempo(ss,g);
      make_temporary_graph_over_group(ss,g);
      set_group_bounds(g);
      if ((g == current_group) && (gb_is_active())) remake_group_browser_label();
    }
  /* if move mix, make sure we don't follow any chains */
}

void remix_group_tempo(snd_state *ss, grp_info *g)
{
  if (g->mixes > 1)
    {
      refigure_tempo(ss,g);
      remix_file_over_group(g);
      set_group_bounds(g);
      if ((g == current_group) && (gb_is_active())) remake_group_browser_label();
    }
}

void cleanup_group_temp_files(snd_state *ss)
{
  int i,j;
  grp_info *g;
  if (groups)
    {
      for (i=0;i<ss->ngroups;i++)
	{
	  g = groups[i];
	  if ((g) && (g->tempfile))
	    {
	      for (j=0;j<GROUP_MAX_OUT_CHANS;j++)
		{
		  if (g->tempfile[j]) remove(g->tempfile[j]);
		}
	    }
	}
    }
}

int update_group_positions(snd_state *ss, mixdata *md)
{
  console_state *cs;
  int i,j,found_one;
  float old_len;
  grp_info *g;
  cs = md->current_cs;
  found_one = 0;
  if (cs->groups) /* need to move entire set of group-associated mixes */
    {
      /* for now update the title */
      /* we've moved by cs->beg - cs->orig */
      for (i=0,j=1;i<ss->ngroups;i++,j*=2)
	{
	  if (j & (cs->groups))
	    {
	      g = groups[i];
	      old_len = (g->end - g->beg);
	      set_group_bounds(g);
	      if ((g == current_group) && gb_is_active()) remake_group_browser_label();
	      if ((g->end - g->beg) != old_len) found_one = 1;
	    }
	}
    }
  return(found_one);
}

void update_groups_and_envs(mixdata *md) 
{
  int changed,i,j,k,n,found_env;
  snd_state *ss;
  console_state *cs;
  mixdata *tmp_md;
  float *glens;
  grp_info *g;
  ss = md->ss;
  cs = md->current_cs;
  glens = (float *)calloc(ss->ngroups,sizeof(float));
  for (i=0;i<ss->ngroups;i++) glens[i] = (groups[i]->end - groups[i]->beg);
  changed = update_group_positions(ss,md);
  /* first fixup the local amp env (gs_amp_env) */
  sweep_ctr++;
  md->sweep_tag = sweep_ctr;
  if (cs->gs_amp_env) refigure_one_amp_env(ss,md);
  /* if length of group changed, and there are envelopes, we need to fix them up and refigure the mixes */
  if (changed)
    {
      for (i=0,j=1;i<ss->ngroups;i++,j*=2)
	{
	  if (j & (cs->groups))
	    {
	      g = groups[i];
	      
	      if ((!(g->env_entire)) && (glens[i] != (g->end - g->beg)))
		{
		  found_env = FALSE;
		  for (k=0;k<GROUP_MAX_OUT_CHANS;k++)
		    {
		      if (g->amp_envs[k]) {found_env = TRUE; break;}
		    }
		  if (found_env)
		    {
		      /* this group's length has changed and it has amp envelopes and they depend on its length */
		      /* the group envs themselves are in env form -- independent of actual length, but the */
		      /* individual members of the group use the magified form which needs to be recalculated */
		      /* However, the original mix that caused all this has already recalculated its envs (above) */
		      for (n=0;n<g->mixes;n++)
			{
			  tmp_md = g->mds[n];
			  if ((tmp_md) && (tmp_md->sweep_tag != sweep_ctr) && ready_mix(tmp_md))
			    {
			      refigure_one_amp_env(ss,tmp_md);
			    }
			  tmp_md->sweep_tag = sweep_ctr;
			}
		    }
		}
	    }
	}
    }
  free(glens);
}

static int update_group_tempfiles(grp_info *g)
{
  int i,chans;
  chans = 0;
  for (i=0;i<g->chans;i++)
    {
      if (!(g->tempfile[i])) 
	{
	  if (g->play_sps[i])
	    {
	      free_snd_info(g->play_sps[i]);
	      g->play_sps[i] = NULL;
	    }
	  g->tempfile[i] = group_to_temp_file(g,i);
	}
      if (((g->cps[i])->chan) > chans) chans = (g->cps[i])->chan;
    }
  return(chans);
}

void start_playing_group(snd_state *ss, grp_info *g)
{
  int i,chans;
  snd_info *play_sp;
  chans = update_group_tempfiles(g);
  for (i=0;i<g->chans;i++)
    {
      if (!(g->play_sps[i]))
	g->play_sps[i] = make_sound_readable(ss,g->tempfile[i],FALSE);
      play_sp = g->play_sps[i];
      play_sp->shortname = g->tempfile[i];
      play_sp->fullname = NULL;
      play_sp->amp = 1.0; /* these are already taken into account by the remix */
      play_sp->srate = 1.0;
      play_sp->grouping = (g->cps[i])->chan + ((chans+1) << 8);
      start_playing(play_sp,0);
    }
}

void stop_playing_group(snd_state *s,grp_info *g)
{
  int i;
  snd_info *play_sp;
  for (i=0;i<g->chans;i++)
    {
      play_sp = g->play_sps[i];
      if ((play_sp) && (play_sp->playing)) 
	{
	  stop_playing(play_sp->playing); 
	  play_sp->playing = 0;
	}
    }
}

void check_and_allocate_groups(snd_state *ss)
{
  int i;
  if (!groups)
    {
      groups = (grp_info **)calloc(ss->ngroups,sizeof(grp_info *));
      for (i=0;i<ss->ngroups;i++) groups[i] = make_grp_info(ss,i);
      current_group = groups[0];
    }
}

static void remove_mix_from_all_groups(mixdata *md)
{
  /* md is being freed, so make sure it is not in some group's list */
  int i;
  snd_state *ss;
  grp_info *g;
  if (groups)
    {
      ss = md->ss;
      for (i=0;i<ss->ngroups;i++) 
	{
	  g = groups[i];
	  if (g->mixes > 0)
	    remove_group_member(g,md);
	}
      update_groups();
    }
}

static void mix_change_inform_groups(mixdata *md)
{
  int i,j,gr,row;
  grp_info *g;
  snd_state *ss;
  if (groups)
    {
      gr = (md->current_cs)->groups;
      if (gr)
	{
	  ss = md->ss;
	  for (i=0,j=1;i<ss->ngroups;i++,j*=2)
	    {
	      if (j & gr)
		{
		  g = groups[i];
		  row = md->rows[i];
		  if ((g) && (row>=0) && (g->tempfile[row]))
		    {
		      remove(g->tempfile[row]);
		      g->tempfile[row] = NULL;
		    }
		}}}}
}


/* ---------------- paste/delete move affected mixes ---------------- */

typedef struct {
  int beg,change;
  chan_info *cp;
  axis_info *ap;
} mixrip;

static int ripple_mixes_1(mixdata *md, void *ptr)
{
  console_state *cs,*ncs;
  mixmark *m;
  int xspot;
  snd_state *ss;
  mixrip *data;
  chan_info *cp;
  snd_info *sp;
  data = (mixrip *)ptr;
  cp = data->cp;
  sp = cp->sound;
  cs = md->current_cs;
  if ((cs) && (!(cs->locked)) && (cs->beg > data->beg) && (ready_mix(md)))
    {
      ss = md->ss;
      ncs = copy_console(cs);
      ncs->edit_ctr = cp->edit_ctr;
      ncs->orig = cs->beg + data->change;
      ncs->beg = ncs->orig;
      ncs->end = ncs->beg + cs->len - 1;
      if (!(sp->syncing)) ncs->syncable = 0;
      m = md->mixer;
      if ((m) && (ss->mix_duration_brackets)) erase_mix_duration_bracket(md,m->x,m->y);
      extend_console_list(md);
      md->states[md->curcons] = ncs;
      make_current_console(md);
      if ((m) && (m->inuse))
	{
	  xspot = grf_x((double)(ncs->beg)/(double)snd_SRATE(cp),data->ap);
	  move_mix_x(m,xspot);
	  set_mix_title_beg(md,m);
	  if (ss->mix_duration_brackets) draw_mix_duration_bracket(md,m->x,m->y);
	}
    }
  return(0);
}

void ripple_mixes(chan_info *cp, int beg, int change)
{
  /* look for active mixes in cp, fixup pointers and move along with edit_ctr */
  /* if they have a mixer, move it too */
  mixrip *mp;
  if ((cp) && (cp->mixes))
    {
      mp = (mixrip *)calloc(1,sizeof(mixrip));
      mp->beg = beg;
      mp->change = change;
      mp->cp = cp;
      mp->ap = cp->axis;
      map_over_channel_mixes(cp,ripple_mixes_1,(void *)mp);
    }
}


/* ---------------- C-x C-j (go to mix) ---------------- */

static int compare_consoles(const void *umx1, const void *umx2)
{
  int mx1,mx2;
  mx1 = (*((int *)umx1));
  mx2 = (*((int *)umx2));
  if (mx1 > mx2) return(1);
  if (mx1 == mx2) return(0);
  return(-1);
}

int goto_mix(chan_info *cp,int count)
{
  int i,j,k,samp;
  mix_state *ms;
  mixdata **mxd;
  mixdata *md;
  int *css;
  console_state *cs;
  if ((!cp) || (!cp->mixes)) return(CURSOR_IN_VIEW);
  ms = (mix_state *)(cp->mixes);
  if (ms)
    {
      css = (int *)calloc(ms->mix_size,sizeof(int));
      mxd = (mixdata **)(ms->mix_pool);
      for (i=0,j=0;i<ms->mix_size;i++) 
	{
	  md = mxd[i];
	  if (md)
	    {
	      cs = md->current_cs;
	      css[j] = cs->orig;
	      j++;
	    }
	}
      qsort((void *)css,j,sizeof(int),compare_consoles);
      /* now find where we are via cp->cursor and go forward or back as per count */
      samp = cp->cursor;
      k = j-1;
      for (i=0;i<j;i++)	{if (css[i] > samp) {k=i; break;}}
      if (css[k] != samp) {if (count < 0) k++; else k--;}
      k+=count;
      if (k<0) k=0;
      if (k>=j) k=j-1;
      samp = css[k];
      cp->cursor = samp;
      free(css);
      if ((count > 0) && (samp > graph_high_SAMPLE(cp))) return(CURSOR_IN_MIDDLE);
      if ((count < 0) && (samp < graph_low_SAMPLE(cp))) return(CURSOR_IN_MIDDLE);
      return(CURSOR_IN_VIEW);
    }
  return(CURSOR_IN_VIEW);
}

/* ---------------- snd-clm connections ---------------- */

float mx_get_group_amp(snd_state *ss, int n, int row) {if (groups) return((groups[n])->amps[row]); else return(0.0);}
float mx_get_group_tempo(snd_state *ss, int n) {if (groups) return((groups[n])->tempo); else return(1.0);}
float mx_get_group_speed(snd_state *ss, int n) {if (groups) return((groups[n])->speed); else return(1.0);}
float mx_get_group_beg(snd_state *ss, int n) {if (groups) return((groups[n])->beg); else return(0.0);}
float mx_get_group_end(snd_state *ss, int n) {if (groups) return((groups[n])->end); else return(0.0);}

void mx_set_group_amp(snd_state *ss, int n, int row, float val)
{
  grp_info *g;
  if (groups)
    {
      g = groups[n];
      g->amps[row] = val;
      if ((g == current_group) && gb_is_active()) reflect_group_amp_row_change(ss,g,row);
      if (g->mixes > 0) 
	{
	  refigure_amps(ss,row);
	  remix_file_with_group(g,row);
	}
    }
}

static mixdata *find_active_mix(grp_info *g)
{
  mixdata *md;
  int i;
  for (i=0;i<g->mixes;i++) 
    {
      md = g->mds[i];
      if (ready_mix(md)) return(md);
    }
  return(NULL);
}

void mx_set_group_beg(snd_state *ss, int n, float val)
{
  grp_info *g;
  mixdata *md;
  int amount;
  if (groups)
    {
      g = groups[n];
      if ((g->mixes > 0) && (md = find_active_mix(g)))
	{
	  amount = (val - g->beg) * snd_SRATE(md->cp);
	  move_associated_mixes(md,amount,TRUE);
	  remix_file_over_groups(ss,(1<<n));
	  reset_associated_origs(md,amount);
	}
      g->end += (val - g->beg);
      g->beg = val;
      if ((g == current_group) && gb_is_active()) remake_group_browser_label();
    }
}

void mx_set_group_end(snd_state *ss, int n, float val) 
{
  grp_info *g;
  if (groups)
    {
      g = groups[n];
      mx_set_group_beg(ss,n,g->beg + val - g->end);
    }
}

void mx_set_group_speed(snd_state *ss, int n, float val) 
{
  grp_info *g;
  if (groups)
    {
      g = groups[n];
      reflect_group_speed_change(ss,g,val);
      remix_group_speeds(ss,g);
    }
}

void mx_set_group_tempo(snd_state *ss, int n, float val) 
{
  grp_info *g;
  if (groups)
    {
      g = groups[n];
      reflect_group_tempo_change(g,val);
      remix_group_tempo(ss,g);
    }
}
