#include "snd.h"


/* -------------------------------- DATA STRUCTURES -------------------------------- 
 *
 * s_type used for run-time type checks (generic functions)
 * 
 * axis_info: axis data
 * file_info: header data describing sound data in file
 * fft_info: data relating to one fft display
 * chan_info: state info for one channel
 * snd_info: state info for one sound
 * snd_state: overall state of program (or perhaps someday one complete XSnd widget)
 *
 * these are passed to the related callback functions as the clientData (as XtPointers)
 */

static char *type_names[8] = {"axis_info","file_info","fft_info","chan_info","snd_info","snd_state","region_info","unknown"};

#if defined(SGI) || defined(NEXT)
  static const char I_SND[4] = {'s','n','d','\0'};
#else
  static const char I_SND[4] = {'\0','\0','\0','\0'};
#endif

unsigned int make_snd_pointer_type (int type)
{
  return((*((unsigned int *)I_SND)) | type);
}

int snd_pointer_type(void *w)
{
  if ((w) && ( ((unsigned int)((((snd_any *)w)->s_type) & 0xfffffff0)) == (*((unsigned int *)I_SND))))
    return((((snd_any *)w)->s_type) & 0xf);
  if (w) 
    fprintf(stderr,"type: %d (in %x) but %x != %x (this upper portion of the type should be 'snd0')\n",
	    ((((snd_any *)w)->s_type) & 0xf),
	    (((snd_any *)w)->s_type),
	    (((snd_any *)w)->s_type) & 0xfffffff0,
	    (*((unsigned int *)I_SND))); 
  return(UNKNOWN_TYPE);
}

void snd_pointer_error (char *msg, int good, void *w)
{ 
  if (w)
    {
      if (((int)w) > 1000)
	{
	    fprintf(stderr,"\n%s: unknown pointer for %s\n",msg,type_names[good]);
	}
      else fprintf(stderr,"\n%s: integer offset of some sort passed as %s ?!?\n",msg,type_names[good]);
    }
  else fprintf(stderr,"\n%s: null pointer for %s\n",msg,type_names[good]);
}

static lisp_grf *free_lisp_info(chan_info *cp)
{
  lisp_grf *lg;
  if (cp)
    {
      lg = cp->lisp_info;
      if (lg)
	{
	  if (lg->axis) free_axis_info(lg->axis);
	  if (lg->data) free(lg->data);
	  free(lg);
	}
    }
  return(NULL);
}

chan_info *make_chan_info(chan_info *cip, int chan, snd_info *sound, snd_state *state)
{
  chan_info *ci; /* may be re-use */
  if ((!cip) || (snd_pointer_type(cip) != CHAN_INFO))
    {
      ci = (chan_info *)calloc(1,sizeof(chan_info)); 
      ci->s_type = make_snd_pointer_type(CHAN_INFO);
      ci->cgx = (chan_context *)calloc(1,sizeof(chan_context));
      (ci->cgx)->ax = (axis_context *)calloc(1,sizeof(axis_context));
      ci->mixes = NULL;
      ci->last_sonogram = NULL;
    }
  else ci = cip;
  ci->tcgx = NULL;
  ci->chan = chan;
  ci->sound = sound;
  ci->sound_ctr = -1;
  ci->edit_ctr = -1;
  ci->sound_size = 0;
  ci->edit_size = 0;
  ci->cursor_on = 0;
  ci->cursor_visible = 0;
  ci->cursor = 0;
  ci->border_visible = 0;
  ci->waving = 1; /* the default state (button is set when we start) */
  ci->ffting = 0;
  ci->printing = 0;
  ci->state = state;
  ci->amp_env = NULL;
  ci->original_env = NULL;
  ci->sonogram_data = NULL;
  ci->lisp_info = NULL;
  ci->drawing = 1;
  ci->hookable = 1;
  ci->gzy = 1.0;
  ci->gsy = 1.0;
  if (ci->last_sonogram) {free(ci->last_sonogram); ci->last_sonogram = NULL;}
  return(ci);
}

static chan_info *free_chan_info(chan_info *cp)
{
  /* this does not free the associated widgets -- they are merely unmanaged */
  if (snd_pointer_type(cp) != CHAN_INFO) snd_pointer_error("free_chan_info",CHAN_INFO,(void *)cp);
  chan_info_cleanup(cp);
  cp->tcgx = NULL;
  cp->axis = free_axis_info(cp->axis);
  if (cp->fft) cp->fft = free_fft_info(cp->fft);
  if (cp->fft_data) {free(cp->fft_data); cp->fft_data = NULL;}
  /* this may leave ->wp window unfreed? -- see snd-fft.c free_fft_state */
  cp->ffting = 0;
  cp->printing = 0;
  cp->waving = 1;
  if (cp->edits) free_edit_list(cp);
  if (cp->sounds) free_sound_list(cp);
  if (cp->marks) free_mark_list(cp,-1);
  if (cp->sounds) {free(cp->sounds); cp->sounds = NULL;}
  if (cp->mixes) free_mixes(cp);
  cp->sound = NULL;  /* a backpointer */
  cp->state = NULL;
  cp->cursor_on = 0;
  cp->cursor_visible = 0;
  cp->border_visible = 0;
  cp->cursor = 0;
  cp->drawing = 1;
  if ((cp->amp_env) || (cp->original_env)) free_chan_env(cp);
  if (cp->sonogram_data) free_sono_info(cp);
  if (cp->last_sonogram) {free(cp->last_sonogram); cp->last_sonogram = NULL;}
  if (cp->lisp_info) cp->lisp_info = free_lisp_info(cp);
  cp->lisp_graphing = 0;
  return(cp);  /* pointer is left for possible future re-use */
}

static char close_token[3]={')',' ','\0'};

static int digits(float f)
{
  int i,j;
  for (j=0;j<4;j++)
    {
      i = f;
      f -= i;
      if (f == 0.0) return(j);
      f *= 10;
    }
  return(5);
}

snd_info *make_snd_info(snd_info *sip, snd_state *state, char *filename, file_info *hdr, int snd_slot)
{
  snd_info *si = NULL;
  int chans,i,len;
  float secs;
  char *sp,*dot,*buf = NULL,*cstr = NULL;
  int *changes = NULL;
  snd_state *ss = (snd_state *)state;
  /* assume file has been found and header read before reaching us */
  /* if a reused pointer, may need to extend current chans array */
  if (snd_pointer_type(hdr) != FILE_INFO) snd_pointer_error("make_snd_info file_info",FILE_INFO,(void *)hdr);
  chans = hdr->chans;
  if ((!sip) || (snd_pointer_type(sip) != SND_INFO))
    {
      si = (snd_info *)calloc(1,sizeof(snd_info));
      si->s_type = make_snd_pointer_type(SND_INFO);
      si->chans = (chan_info **)calloc(chans,sizeof(chan_info *));
      si->allocated_chans = chans;
      si->sgx = (snd_context *)calloc(1,sizeof(snd_context));
    }
  else 
    {
      si = sip;
      if (si->allocated_chans < chans) 
	{
	  si->chans = (chan_info **)realloc(si->chans,chans*sizeof(chan_info *));
	  for (i=si->allocated_chans;i<chans;i++) si->chans[i] = NULL;
	  si->allocated_chans = chans;
	}
    }
  si->index = snd_slot;
  si->nchans = chans;
  si->hdr = hdr;
  si->inuse = 1;
  si->fullname = copy_string(filename);
  si->shortname = filename_without_home_directory(si->fullname); /* a pointer into fullname, not a new string */
  si->state = ss;
  si->expand = default_expand(ss);
  si->last_expand = 450;
  si->expanding = default_expanding(ss);
  secs = (float)hdr->samples/(float)(hdr->chans*hdr->srate);
  if (secs < 1.0) 
    si->sx_scroll_max = 100;
  else si->sx_scroll_max = (int)pow(10,(ceil(log10(secs)) + 2));
  si->amp = default_amp(ss);
  si->last_amp = default_amp(ss) * 50;
  si->srate = fabs(default_speed(ss));
  si->last_srate = 450;
  if (default_speed(ss) > 0.0) si->play_direction = 1; else si->play_direction = -1;
  si->contrasting = default_contrasting(ss);
  si->contrast = default_contrast(ss);
  si->last_contrast = 0;
  si->reverbing = default_reverbing(ss);
  si->filtering = default_filtering(ss);
  si->searching = 0;
  if (chans > 1)
    si->combining = channel_style(ss);
  else si->combining = CHANNELS_SEPARATE;
  si->loading = 0;
  si->evaling = 0;
  si->marking = 0;
  si->filing = 0;
  si->read_only = 0;
  si->minibuffer_on = 0;
  si->local_explen = default_expand_length(ss);
  si->local_exprmp = default_expand_ramp(ss);
  si->local_exphop = default_expand_hop(ss);
  si->local_revfb = default_reverb_feedback(ss);
  si->local_revlp = default_reverb_lowpass(ss);
  si->revscl = default_reverb_scale(ss);
  si->last_revscl = 20;
  si->revlen = default_reverb_length(ss);
  si->last_revlen = 0;
  si->filter_order = default_filter_order(ss);
  si->selected_channel = NO_SELECTION;
  si->recording = 0;
  si->replaying = 0;
  si->applying = 0;
  si->lisp_graphing = 0;
  si->initial_controls = NULL;
  si->env_anew = 0;
  si->contrast_amp = default_contrast_amp(ss);
  si->debug_names = NULL;
  si->debug_amps = NULL;
  if (fit_data_on_open(ss) != 0) 
    {
      if ((fit_data_on_open(ss) == 1) || (hdr->format == snd_32_linear))
	{
	  changes = (int *)calloc(si->nchans,sizeof(int));
	  si->debug_amps = (float *)calloc(si->nchans,sizeof(float));
	  file_maxamps(state,si->fullname,si->debug_amps,changes);
	  /* can't use snd-clm.c get_maxamp here because the file edit tree is not yet set up */
	}
    }
  if (hdr->format == snd_32_linear)
    {
      buf = NULL;
      dot = NULL;
      cstr = NULL;
      for (sp=si->shortname;(*sp) != '\0';sp++) if ((*sp) == '.') dot=sp;
      if ((dot) && (strcmp(dot,".debugdata") == 0))
	{
	  cstr = sound_comment(si->fullname);
	  dot = cstr;
	  len = snd_strlen(dot);
	  sp = NULL;
	  for (i=0;i<len-10;i++)
	    {
	      if ((dot[i] == '(') && 
		  (dot[i+1] == 'r') && (dot[i+2] == 'u') && (dot[i+3] == 'n') && (dot[i+4] == '-') &&
 		  (dot[i+5] == 'v') && (dot[i+6] == 'a') && (dot[i+7] == 'r') && (dot[i+8] == 's'))
		{
		  sp = (char *)(dot+i+10);
		  break;
		}
	    }
	  /* search for "(run-vars ... )" and fill debug data with variable names and maxamps */
	  /* don't make debug data unless everything is present */
	  if (sp)
	    {
	      si->debug_names = (char **)calloc(si->nchans,sizeof(char *));
	      dot = strtok(sp,close_token);
	      for (i=0;i<si->nchans;i++)
		{
		  if (changes[i])
		    si->debug_names[i] = copy_string(dot);
		  else 
		    {
		      if (!buf) buf = (char *)calloc(128,sizeof(char));
		      if (si->debug_amps[i] == cl_false)
			sprintf(buf,"%s (%s nil)",dot,snd_string_always);
		      else
			{
			  if (si->debug_amps[i] == cl_true)
			    sprintf(buf,"%s (%s t)",dot,snd_string_always);
			  else sprintf(buf,"%s (%s %.*f)",dot,snd_string_always,digits(si->debug_amps[i]),si->debug_amps[i]);
			}
		      si->debug_names[i] = copy_string(buf);
		    }
		  dot = strtok(NULL,close_token);
		}
	    }
	  if (cstr) free(cstr);
	}
      if (buf) free(buf);
    }
  return(si);
}

snd_info *free_snd_info(snd_info *sp)
{
  int i;
  if (snd_pointer_type(sp) != SND_INFO) snd_pointer_error("free_snd_info",SND_INFO,(void *)sp);
  /* leave most for reuse as in free_chan_info */
  snd_info_cleanup(sp);
  sp->syncing = 0;
  for (i=0;i<sp->nchans;i++)
    {
      if (sp->chans[i]) sp->chans[i]=free_chan_info(sp->chans[i]);
    }
  if (sp->sgx)
    {
      if ((sp->sgx)->env_data) {remove_amp_env(sp); free_sound_env(sp,1);}
      if ((sp->sgx)->apply_in_progress) remove_apply(sp);
    }
  sp->state = NULL;
  sp->inuse = 0;
  sp->amp = 1.0;
  sp->srate = 1.0;
  sp->expand = 0.0;
  sp->expanding = 0;
  sp->contrasting = 0;
  sp->reverbing = 0;
  sp->filtering = 0;
  sp->play_direction = 1;
  sp->playing_mark = NULL;
  sp->searching = 0;
  sp->loading = 0;
  sp->evaling = 0;
  sp->marking = 0;
  sp->filing = 0;
  sp->recording = 0;
  sp->replaying = 0;
  sp->applying = 0;
  sp->combining = CHANNELS_SEPARATE;
  sp->read_only = 0;
  sp->lisp_graphing = 0;
  sp->minibuffer_on = 0;   /* if it's on, should we clear it first ?? */
  if (sp->search_expr) {free(sp->search_expr); sp->search_expr = NULL;}
  if (sp->search_tree) {free_sop(sp->search_tree); sp->search_tree = NULL;}
  if (sp->eval_expr) {free(sp->eval_expr); sp->eval_expr = NULL;}
  if (sp->eval_tree) {free_sop(sp->eval_tree); sp->eval_tree = NULL;}
  sp->selected_channel = NO_SELECTION;
  sp->shortname = NULL; /* was a pointer into fullname */
  if (sp->fullname) free(sp->fullname);
  sp->fullname = NULL;
  if (sp->filter_env) sp->filter_env = free_env(sp->filter_env);
  if (sp->actions) flush_actions(sp);
  if (sp->initial_controls) free_controls(sp);
  sp->env_anew = 0;
  sp->contrast_amp = 1.0;
  if (sp->debug_names) 
    {
      for (i=0;i<sp->nchans;i++) 
	{
	  if (sp->debug_names[i]) {free(sp->debug_names[i]); sp->debug_names[i] = NULL;}
	}
      free(sp->debug_names);
      sp->debug_names = NULL;
    }
  if (sp->debug_amps)
    {
      free(sp->debug_amps);
      sp->debug_amps = NULL;
    }
  if (sp->hdr) sp->hdr = free_file_info(sp->hdr);
  return(sp);  /* pointer is left for possible future re-use */
}


int map_over_chans (snd_state *ss, int (*func)(chan_info *,void *), void *userptr)
{
  /* argument to func is chan_info pointer+void pointer of user spec, return non-zero = abort map, skips inactive sounds */
  int i,j,val;
  snd_info *sp;
  chan_info *cp;
  val = 0;
  if (ss)
    {
      for (i=0;i<ss->max_sounds;i++)
	{
	  if ((sp=((snd_info *)(ss->sounds[i]))))
	    {
	      if (sp->inuse)
		{
		  for (j=0;j<(sp->nchans);j++)
		    {
		      if ((cp=((chan_info *)(sp->chans[j]))))
			{
			  val = (*func)(cp,userptr);
			  if (val) return(val);}}}}}} /* I wish I were a'Lispin... */
  return(val);
}

int map_over_sound_chans (snd_info *sp, int (*func)(chan_info *,void *), void *userptr)
{
  /* argument to func is chan_info pointer+void pointer of user spec, return non-zero = abort map, skips inactive sounds */
  int j,val;
  chan_info *cp;
  val = 0;
  for (j=0;j<(sp->nchans);j++)
    {
      if ((cp=sp->chans[j]))
	{
	  val = (*func)(cp,userptr);
	  if (val) return(val);
	}
    }
  return(val);
}

int map_over_sounds (snd_state *ss, int (*func)(snd_info *,void *), void *userptr)
{
  /* argument to func is snd_info pointer, return non-zero = abort map, skips inactive sounds */
  int i,val;
  snd_info *sp;
  val = 0;
  if (ss)
    {
      for (i=0;i<ss->max_sounds;i++)
	{
	  if ((sp=((snd_info *)(ss->sounds[i]))))
	    {
	      if (sp->inuse)
		{
		  val = (*func)(sp,userptr);
		  if (val) return(val);}}}}
  return(val);
}

int map_over_separate_chans(snd_state *ss, int (*func)(chan_info *,void *), void *userptr)
{
  int i,val;
  snd_info *sp;
  val = 0;
  if (ss)
    {
      for (i=0;i<ss->max_sounds;i++)
	{
	  if ((sp=((snd_info *)(ss->sounds[i]))))
	    {
	      if (sp->inuse)
		{
		  if (sp->combining != CHANNELS_SEPARATE)
		    val = (*func)(sp->chans[0],userptr);
		  else val = map_over_sound_chans(sp,func,userptr);
		  if (val) return(val);
		}
	    }
	}
    }
  return(val);
}

snd_state *main_STATE(void *w)
{
  switch (snd_pointer_type(w))
    {
    case SND_STATE: return((snd_state *)w); break;
    case SND_INFO: return(((snd_info *)w)->state); break;
    case CHAN_INFO: return(((chan_info *)w)->state); break;
    case FFT_INFO: if (((fft_info *)w)->chan) return(main_STATE((chan_info *)(((fft_info *)w)->chan))); break;
    case AXIS_INFO: return(((axis_info *)w)->ss); break;
    default: snd_pointer_error("main_STATE",SND_STATE,(void *)w); break;
    }
  return(NULL);
}

char *snd_NAME(void *w)
{
  switch (snd_pointer_type(w))
    {
    case SND_INFO: return(((snd_info *)w)->fullname); break;
    case CHAN_INFO: return(((snd_info *)(((chan_info *)w)->sound))->fullname); break;
    case FFT_INFO: if (((fft_info *)w)->chan) return(snd_NAME((chan_info *)(((fft_info *)w)->chan))); break;
    case AXIS_INFO: if (((axis_info *)w)->cp) return(snd_NAME((chan_info *)(((axis_info *)w)->cp))); break;
    case SND_STATE: return(snd_NAME(any_selected_sound((snd_state *)w))); break;
    default: snd_pointer_error("snd_NAME",SND_INFO,(void *)w); break;
    }
  return(NULL);
}

static file_info *snd_HEADER(void *w)
{
  switch (snd_pointer_type(w))
    {
    case FILE_INFO: return((file_info *)w); break;
    case SND_INFO: return(((snd_info *)w)->hdr); break;
    case CHAN_INFO: return( ((snd_info *) (((chan_info *)w)->sound))->hdr); break;
    case FFT_INFO: if (((fft_info *)w)->chan) return(snd_HEADER((chan_info *)(((fft_info *)w)->chan))); break;
    case AXIS_INFO: if (((axis_info *)w)->cp) return(snd_HEADER((chan_info *)(((axis_info *)w)->cp))); break;
    case SND_STATE: return(snd_HEADER(any_selected_sound((snd_state *)w))); break;
    default: snd_pointer_error("snd_HEADER",FILE_INFO,(void *)w); break;
    }
  return(NULL);
}

int snd_SRATE (void *ptr)
{
  return(((file_info *)(snd_HEADER(ptr)))->srate);
}

int snd_TYPE (void *ptr)
{
  return(((file_info *)(snd_HEADER(ptr)))->type);
}

int graph_low_SAMPLE (void *ptr)
{
  switch (snd_pointer_type(ptr))
    {
    case CHAN_INFO: return(graph_low_SAMPLE(((chan_info *)ptr)->axis)); break;
    case AXIS_INFO: return(((axis_info *)ptr)->losamp); break;
    default: snd_pointer_error("graph_low_SAMPLE",AXIS_INFO,ptr); break;
    }
  return(0);
}

int graph_high_SAMPLE (void *ptr)
{
  switch (snd_pointer_type(ptr))
    {
    case CHAN_INFO: return(graph_high_SAMPLE(((chan_info *)ptr)->axis)); break;
    case AXIS_INFO: return(((axis_info *)ptr)->hisamp); break;
    default: snd_pointer_error("graph_high_SAMPLE",AXIS_INFO,ptr); break;
    }
  return(0);
}

int snd_ok (snd_info *sp) {return((sp) && (sp->inuse));}

int active_channels (snd_state *ss,int count_virtual_channels)
{
  int chans,i;
  snd_info *sp;
  chans = 0;
  for (i=0;i<ss->max_sounds;i++)
    {
      if (snd_ok(sp = (ss->sounds[i])))
	{
	  if ((count_virtual_channels) || (sp->combining == CHANNELS_SEPARATE))
	    chans += sp->nchans;
	  else chans++;
	}
    }
  return(chans);
}

int find_free_sound_slot (snd_state *state)
{
  int i,j;
  for (i=0;i<state->max_sounds;i++)
    {
      if (state->sounds[i] == NULL) return(i);
      if ((state->sounds[i])->inuse == 0) return(i);
    }
  /* need to realloc sounds to make space */
  j = state->max_sounds;
  state->max_sounds += 4;
  state->sounds = (snd_info **)realloc(state->sounds,state->max_sounds*sizeof(snd_info *));
  for (i=j;i<state->max_sounds;i++) state->sounds[i] = NULL;
  return(j);
}

static snd_info *any_active_sound(snd_state *ss)
{
  snd_info *sp;
  int i;
  for (i=0;i<ss->max_sounds;i++)
    {
      if ((sp=(ss->sounds[i])) && (sp->inuse)) return(sp);
    }
  return(NULL);
}

snd_info *selected_sound(snd_state *ss)
{
  if (ss->selected_sound != NO_SELECTION) return(ss->sounds[ss->selected_sound]);
  return(NULL);
}

snd_info *any_selected_sound (snd_state *ss)
{
  snd_info *sp;
  sp = selected_sound(ss);
  if (!sp) sp = any_active_sound(ss);
  return(sp);
}

chan_info *any_selected_channel(snd_info *sp)
{
  if (sp->selected_channel != NO_SELECTION) return(sp->chans[sp->selected_channel]);
  return(sp->chans[0]);
}

chan_info *selected_channel (void *ptr)
{
  snd_state *ss;
  snd_info *sp;
  ss = main_STATE(ptr);
  if (ss->selected_sound != NO_SELECTION)
    {
      sp = ss->sounds[ss->selected_sound];
      if ((sp->inuse) && (sp->selected_channel != NO_SELECTION))
	return(sp->chans[sp->selected_channel]);
    }
  return(NULL);
}

void select_sound (snd_state *ss, snd_info *sp)
{
  snd_info *osp = NULL;
  if (ss->selected_sound != sp->index)
    {
      if (!ss->using_schemes)
	{
	  if (ss->selected_sound != NO_SELECTION) osp = ss->sounds[ss->selected_sound];
	  if ((osp) && (sp != osp) && (osp->inuse)) high_color(ss,snd_widget(osp,W_snd_name));
	  if (sp->selected_channel != NO_SELECTION) white_color(ss,snd_widget(sp,W_snd_name));
	}
      ss->selected_sound = sp->index;
      highlight_selected_sound(ss);
      reflect_undo_or_redo_in_menu(any_selected_channel(sp));
      new_active_channel_alert(ss);
    }
}

void select_channel(snd_info *sp, int chan)
{
  snd_state *ss = sp->state;
  chan_info *cp;
  cp = selected_channel(sp);
  if (cp) erase_graph_border(cp);
  sp->selected_channel = chan;
  select_sound(ss,sp);
  reflect_undo_or_redo_in_menu(sp->chans[chan]);
  display_graph_border(sp->chans[chan]);
}

void unselect_channel (void *ptr)
{
  snd_info *sp = NULL;
  snd_state *ss = NULL;
  switch (snd_pointer_type(ptr))
    {
    case CHAN_INFO: 
      sp=((chan_info *)ptr)->sound; 
      ss=sp->state; 
      break;
    case SND_INFO: 
      sp=(snd_info *)ptr; 
      ss=sp->state; 
      break;
    case SND_STATE: 
      ss=(snd_state *)ptr; 
      if (ss->selected_sound != NO_SELECTION) sp=ss->sounds[sp->selected_channel];
      break;
    default: snd_pointer_error("unselect_channel",CHAN_INFO,ptr); break;
    }
  if ((sp) && (ss) && (sp->selected_channel != NO_SELECTION)) erase_graph_border(sp->chans[sp->selected_channel]);
  if ((sp) && (ss)) high_color(ss,snd_widget(sp,W_snd_name));
  if (sp) sp->selected_channel = NO_SELECTION;
  if (ss) ss->selected_sound = NO_SELECTION;
}

chan_info *current_channel(void *ptr)
{
  snd_info *sp;
  snd_state *ss;
  switch (snd_pointer_type(ptr))
    {
    case CHAN_INFO: 
      return((chan_info *)ptr); 
      break;
    case SND_INFO: 
      sp = (snd_info *)ptr;
      if (sp->selected_channel != NO_SELECTION)
	return(sp->chans[sp->selected_channel]);
      else return(sp->chans[0]);
      break;
    case SND_STATE:
      ss = (snd_state *)ptr;
      if (ss->selected_sound != NO_SELECTION)
	return(current_channel(ss->sounds[ss->selected_sound]));
      else return(current_channel(any_active_sound(ss)));
      break;
    default: 
      if (ptr == NULL) /* this can happen when Snd has only the menu bar */
	return(NULL);
      else snd_pointer_error("current_channel",CHAN_INFO,ptr); 
      break;
    }
  return(NULL);
}

int syncd_chans(snd_state *ss)
{
  int chans,i;
  snd_info *sp;
  chans = 0;
  for (i=0;i<ss->max_sounds;i++)
    {
      sp = ss->sounds[i];
      if ((sp) && (sp->inuse) && (sp->syncing)) chans += sp->nchans;
    }
  return(chans);
}

void free_sync_info (sync_info *si)
{
  if (si)
    {
      if (si->begs) free(si->begs);
      si->begs = NULL;
      if (si->cps) free(si->cps);
      si->cps = NULL;
      free(si);
    }
}

sync_info *snd_sync(snd_state *ss)
{
  int i,j,k,chans;
  snd_info *sp;
  sync_info *si;
  chans = syncd_chans(ss);
  if (chans > 0)
    {
      si = (sync_info *)calloc(1,sizeof(sync_info));
      si->begs = (int *)calloc(chans,sizeof(int));
      si->cps = (chan_info **)calloc(chans,sizeof(chan_info *));
      si->chans = chans;
      j=0;
      for (i=0;i<ss->max_sounds;i++)
	{
	  sp = ss->sounds[i];
	  if ((sp) && (sp->inuse) && (sp->syncing))
	    {
	      for (k=0;k<sp->nchans;k++,j++)
		{
		  si->cps[j] = sp->chans[k];
		}
	    }
	}
      return(si);
    }
  return(NULL);
}

sync_info *make_simple_sync (chan_info *cp, int beg)
{
  sync_info *si;
  si = (sync_info *)calloc(1,sizeof(sync_info));
  si->chans = 1;
  si->cps = (chan_info **)calloc(1,sizeof(chan_info *));
  si->cps[0] = cp;
  si->begs = (int *)calloc(1,sizeof(int));
  si->begs[0] = beg;
  return(si);
}

snd_info *find_sound(snd_state *ss, char *name)
{
  snd_info *sp;
  int i;
  for (i=0;i<ss->max_sounds;i++)
    {
      sp = ss->sounds[i];
      if ((sp) && (sp->inuse))
	{
	  if ((strcmp(name,sp->shortname) == 0) || (strcmp(name,sp->fullname) == 0)) return(sp);
	}
    }
  return(NULL);
}

static char timestr[64];
#define INFO_BUFFER_SIZE 1024

void display_info(snd_info *sp)
{
  char *buffer = NULL;
  file_info *hdr;
  char *comment,*cstr;
  if (sp)
    {
      hdr = sp->hdr;
      if (hdr)
	{
	  buffer = (char *)snd_calloc(sp->state,INFO_BUFFER_SIZE,sizeof(char));
	  cstr = sound_comment(sp->fullname);
	  comment = cstr;
	  while ((comment) && (*comment) && 
		 (((*comment) == '\n') || ((*comment) == '\t') || 
		  ((*comment) == ' ') || ((*comment) == '\xd')))
	    comment++;
#if (!defined(BEOS)) && (!defined(HAVE_CONFIG_H)) || defined(HAVE_STRFTIME)
	  strftime(timestr,64,"%a %d-%b-%y %H:%M %Z",localtime(&(sp->write_date)));
#endif
	  sprintf(buffer,snd_string_display_info,
		  hdr->srate,
		  hdr->chans,
		  (float)(hdr->samples)/(float)(hdr->chans * hdr->srate),
		  sound_type_name(hdr->type),
		  sound_format_name(hdr->format),
		  timestr,
		  (comment) ? comment : "");
	  ssnd_help(sp->state,
		    sp->shortname,
		    buffer,
		    NULL);
	  if (cstr) free(cstr);
	  free(buffer);
	}
    }
}

