#include "snd.h"


chan_info *make_chan_info(chan_info *cip, int chan, snd_info *sound)
{
  chan_info *cp; /* may be re-used */
  if (!cip)
    {
      cp = (chan_info *)calloc(1, sizeof(chan_info)); 
      cp->ax = (graphics_context *)calloc(1, sizeof(graphics_context));

      switch (chan % 4)
	{
	case 0: cp->combined_data_color = ss->black;      break;
	case 1: cp->combined_data_color = ss->red;        break;
	case 2: cp->combined_data_color = ss->green;      break;
	case 3: cp->combined_data_color = ss->blue;       break;
	}
      cp->last_sonogram = NULL;
      cp->last_wavogram = NULL;
      cp->temp_sonogram = NULL;
      cp->inset_graph = NULL;
      cp->fft = NULL;
#if HAVE_GL
      cp->gl_fft_list = NO_LIST;
      cp->gl_wavo_list = NO_LIST;
#endif
      cp->edit_hook = Xen_false;
      cp->edit_hook_loc = NOT_A_GC_LOC;
      cp->after_edit_hook = Xen_false;
      cp->after_edit_hook_loc = NOT_A_GC_LOC;
      cp->undo_hook = Xen_false;
      cp->undo_hook_loc = NOT_A_GC_LOC;
      cp->properties = Xen_false; /* will be a vector of 1 element if it's ever used */
      cp->properties_loc = NOT_A_GC_LOC;
      cp->active = CHANNEL_INACTIVE;
    }
  else cp = cip;
  cp->chan = chan;
  cp->sound = sound;
  cp->sound_ctr = NOT_A_SOUND;
  cp->edit_ctr = -1;
  cp->sound_size = 0;
  cp->edit_size = 0;
  cp->cursor_on = false;
  cp->cursor_visible = false;
  cp->fft_cursor_visible = false;
  cp->show_sonogram_cursor = show_sonogram_cursor(ss);
  cp->selection_visible = false;
  cp->editable = true; /* must be unset (region display, variable display, etc) */
  cp->cursor_style = cursor_style(ss);
  cp->tracking_cursor_style = tracking_cursor_style(ss);
  cp->cursor_size = cursor_size(ss);
  cp->cursor_proc = Xen_undefined;
  cp->cursor_proc_loc = NOT_A_GC_LOC;
  cp->squelch_update = false;
  cp->show_y_zero = show_y_zero(ss);
  cp->show_grid = show_grid(ss);
  cp->show_marks = show_marks(ss);
  cp->time_graph_type = time_graph_type(ss);
  cp->wavo_hop = wavo_hop(ss);
  cp->wavo_trace = wavo_trace(ss);
  cp->max_transform_peaks = max_transform_peaks(ss);
  cp->show_transform_peaks = show_transform_peaks(ss);
  cp->zero_pad = zero_pad(ss);
  cp->with_verbose_cursor = with_verbose_cursor(ss);
  cp->fft_log_frequency = fft_log_frequency(ss);
  cp->fft_log_magnitude = fft_log_magnitude(ss);
  cp->fft_with_phases = fft_with_phases(ss);
  cp->min_dB = min_dB(ss);
  cp->lin_dB = ss->lin_dB;
  cp->in_as_one_edit = 0;
  cp->wavelet_type = wavelet_type(ss);
  cp->spectro_x_angle = spectro_x_angle(ss);
  cp->spectro_y_angle = spectro_y_angle(ss);
  cp->spectro_z_angle = spectro_z_angle(ss);
  cp->spectro_x_scale = spectro_x_scale(ss);
  cp->spectro_y_scale = spectro_y_scale(ss);
  cp->spectro_z_scale = spectro_z_scale(ss);
  cp->spectrum_end = spectrum_end(ss);
  cp->spectrum_start = spectrum_start(ss);
  cp->spectro_hop = spectro_hop(ss);
  cp->fft_window_alpha = fft_window_alpha(ss);
  cp->fft_window_beta = fft_window_beta(ss);
  cp->transform_size = transform_size(ss);
  cp->transform_graph_type = transform_graph_type(ss);
  cp->fft_window = fft_window(ss);
  cp->transform_type = transform_type(ss);
  cp->transform_normalization = transform_normalization(ss);
  cp->show_mix_waveforms = show_mix_waveforms(ss);
  cp->time_graph_style = graph_style(ss);
  cp->lisp_graph_style = graph_style(ss);
  cp->transform_graph_style = graph_style(ss);
  cp->graphs_horizontal = graphs_horizontal(ss);
  cp->dot_size = dot_size(ss);
  cp->grid_density = grid_density(ss);
  cp->x_axis_style = x_axis_style(ss);
  cp->beats_per_minute = beats_per_minute(ss);
  cp->beats_per_measure = beats_per_measure(ss);
  cp->show_axes = show_axes(ss);
  cp->graph_time_on = true; /* the default state (button is set when we start) */
  cp->graph_transform_on = false;
  cp->printing = NOT_PRINTING;
  cp->waiting_to_make_graph = false;
  cp->new_peaks = false;
  cp->sonogram_data = NULL;
  cp->lisp_info = NULL;
  cp->amp_control = NULL;
  cp->hookable = WITH_HOOK;
  cp->cx = 0;
  cp->cy = 0;
  cp->fft_cx = 0;
  cp->selection_transform_size = 0;
  if (cp->last_sonogram) 
    {
      free(cp->last_sonogram); 
      cp->last_sonogram = NULL;
    }
  if (cp->last_wavogram) 
    {
      free(cp->last_wavogram); 
      cp->last_wavogram = NULL;
    }
  if (cp->inset_graph)
    clear_inset_graph(cp);
  cp->active = CHANNEL_INITIALIZED;
  return(cp);
}


static chan_info *free_chan_info(chan_info *cp)
{
  /* this does not free the associated widgets -- they are merely unmanaged */

  cp->active = CHANNEL_INACTIVE;
  /* need an indication right away that this channel is being deleted -- during free_snd_info (close-sound),
   *   an error may occur (an edit list temp file might have vanished for example), and normally Snd
   *   attempts to post an error message in the sound's status area.  To force this out, we have to
   *   call XmUpdate or equivalent, which can cause all the sound's channels to attempt to redisplay;
   *   since the one causing the error is half-deallocated, trouble can ensue.  So both the channel
   *   and the sound have "active" flags that are true only when everything is ship-shape.
   */
  chan_info_cleanup(cp);
  cp->squelch_update = true;
  cp->axis = free_axis_info(cp->axis);
  if (cp->fft) cp->fft = free_fft_info(cp->fft);
  cp_free_fft_state(cp);
  cp->graph_transform_on = false;
  cp->printing = NOT_PRINTING;
  cp->graph_time_on = true;
  if (cp->edits) free_edit_list(cp);
  if (cp->sounds) free_sound_list(cp);
  free_channel_mixes(cp);
  cp->sound = NULL;  /* a backpointer */
  cp->cursor_on = false;
  cp->cursor_visible = false;
  cp->fft_cursor_visible = false;
  cp->show_sonogram_cursor = false;
  cp->selection_visible = false;
  if (cp->amp_control)
    {
      /* not sure this is the right thing */
      free(cp->amp_control);
      cp->amp_control = NULL;
    }
  if (Xen_is_procedure(cp->cursor_proc))
    {
      snd_unprotect_at(cp->cursor_proc_loc);
      cp->cursor_proc = Xen_undefined;
      cp->cursor_proc_loc = NOT_A_GC_LOC;
    }
  if (Xen_is_vector(cp->properties)) /* using vector as node for GC */
    Xen_vector_set(cp->properties, 0, Xen_empty_list);
  cp->waiting_to_make_graph = false;
  if (cp->sonogram_data) free_sono_info(cp);
  if (cp->temp_sonogram) 
    {
      /* special case -- background fft process never got a chance to run */
      if (cp->temp_sonogram == cp->last_sonogram) cp->last_sonogram = NULL;
      free_sonogram_fft_state(cp->temp_sonogram);
      free(cp->temp_sonogram); 
      cp->temp_sonogram = NULL;
    } 
  if (cp->last_sonogram) 
    {
      free_sonogram_fft_state(cp->last_sonogram);
      free(cp->last_sonogram); 
      cp->last_sonogram = NULL;
    }
  if (cp->last_wavogram) 
    {
      free(cp->last_wavogram); 
      cp->last_wavogram = NULL;
    }
  if (cp->lisp_info) 
    {
      free_lisp_info(cp);
      cp->lisp_info = NULL;
    }
  cp->graph_lisp_on = false;
  cp->selection_transform_size = 0;

  if (cp->as_one_edit_positions)
    {
      free(cp->as_one_edit_positions);
      cp->as_one_edit_positions = NULL;
      cp->as_one_edit_positions_size = 0;
    }

  if (Xen_is_hook(cp->edit_hook))
    {
      Xen_clear_hook_list(cp->edit_hook);
      snd_unprotect_at(cp->edit_hook_loc);
      cp->edit_hook = Xen_false;
      cp->edit_hook_loc = NOT_A_GC_LOC;
    }
  if (Xen_is_hook(cp->after_edit_hook))
    {
      Xen_clear_hook_list(cp->after_edit_hook);
      snd_unprotect_at(cp->after_edit_hook_loc);
      cp->after_edit_hook = Xen_false;
      cp->after_edit_hook_loc = NOT_A_GC_LOC;
    }
  if (Xen_is_hook(cp->undo_hook))
    {
      Xen_clear_hook_list(cp->undo_hook);
      snd_unprotect_at(cp->undo_hook_loc);
      cp->undo_hook = Xen_false;
      cp->undo_hook_loc = NOT_A_GC_LOC;
    }
  if (cp->inset_graph)
    clear_inset_graph(cp);

  return(cp);  /* pointer is left for possible future re-use */
}


snd_info *make_basic_snd_info(int chans)
{
  snd_info *sp;
  sp = (snd_info *)calloc(1, sizeof(snd_info));
  sp->chans = (chan_info **)calloc(chans, sizeof(chan_info *));
  sp->allocated_chans = chans;
  sp->properties = Xen_false; /* will be a vector of 1 element if it's ever used */
  sp->properties_loc = NOT_A_GC_LOC;
#if USE_NO_GUI
  sp->widgets = false; /* it's a bool if no gui */
#endif
  return(sp);
}


void initialize_control_panel(snd_info *sp)
{
  sp->expand_control = DEFAULT_EXPAND_CONTROL;
  sp->expand_control_min = expand_control_min(ss);
  sp->expand_control_max = expand_control_max(ss);
  sp->last_expand_control = 0.0;
  sp->saved_expand_control = 0.0;
  sp->expand_control_on = DEFAULT_EXPAND_CONTROL_ON;
  sp->amp_control = DEFAULT_AMP_CONTROL;
  sp->amp_control_min = amp_control_min(ss);
  sp->amp_control_max = amp_control_max(ss);
  sp->last_amp_control = 1.0;
  sp->saved_amp_control = 1.0;
  sp->speed_control = fabs(DEFAULT_SPEED_CONTROL);
  sp->speed_control_min = speed_control_min(ss);
  sp->speed_control_max = speed_control_max(ss);
  sp->last_speed_control = 1.0;
  sp->saved_speed_control = 1.0;
  if (DEFAULT_SPEED_CONTROL > 0.0) sp->speed_control_direction = 1; else sp->speed_control_direction = -1;
  sp->contrast_control_on = DEFAULT_CONTRAST_CONTROL_ON;
  sp->contrast_control = DEFAULT_CONTRAST_CONTROL;
  sp->contrast_control_min = contrast_control_min(ss);
  sp->contrast_control_max = contrast_control_max(ss);
  sp->contrast_control_amp = contrast_control_amp(ss);
  sp->last_contrast_control = contrast_control_min(ss);
  sp->saved_contrast_control = contrast_control_min(ss);
  sp->reverb_control_on = DEFAULT_REVERB_CONTROL_ON;
  sp->filter_control_on = DEFAULT_FILTER_CONTROL_ON;
  sp->expand_control_length = expand_control_length(ss);
  sp->expand_control_ramp = expand_control_ramp(ss);
  sp->expand_control_hop = expand_control_hop(ss);
  sp->expand_control_jitter = expand_control_jitter(ss);
  sp->reverb_control_feedback = reverb_control_feedback(ss);
  sp->reverb_control_lowpass = reverb_control_lowpass(ss);
  sp->reverb_control_scale = DEFAULT_REVERB_CONTROL_SCALE;
  sp->reverb_control_scale_min = reverb_control_scale_min(ss);
  sp->reverb_control_scale_max = reverb_control_scale_max(ss);
  sp->reverb_control_decay = reverb_control_decay(ss);
  sp->speed_control_tones = speed_control_tones(ss);
  sp->speed_control_style = speed_control_style(ss);
  sp->speed_control_numerator = 1;
  sp->speed_control_denominator = 1;
  sp->last_reverb_control_scale = 0.0;
  sp->saved_reverb_control_scale = 0.0;
  sp->reverb_control_length = DEFAULT_REVERB_CONTROL_LENGTH;
  sp->reverb_control_length_min = reverb_control_length_min(ss);
  sp->reverb_control_length_max = reverb_control_length_max(ss);
  sp->last_reverb_control_length = 0.0;
  sp->saved_reverb_control_length = 0.0;
  sp->filter_control_order = filter_control_order(ss);
  sp->filter_control_in_dB = filter_control_in_dB(ss);
  sp->filter_control_in_hz = filter_control_in_hz(ss);
  if (sp->filter_control_in_hz)
    sp->filter_control_xmax = (mus_float_t)(snd_srate(sp) / 2);
  else sp->filter_control_xmax = 1.0;
  sp->filter_control_changed = false;
  sp->saved_controls = NULL;
}


snd_info *make_snd_info(snd_info *sip, const char *filename, file_info *hdr, int snd_slot, read_only_t read_only)
{
  snd_info *sp = NULL;
  int chans, i;
  /* assume file has been found and header read before reaching us */
  /* if a reused pointer, may need to extend current chans array */
  chans = hdr->chans;
  if (!sip)
    {
      sp = make_basic_snd_info(chans);
    }
  else 
    {
      sp = sip;
      if (sp->allocated_chans < chans) 
	{
	  sp->chans = (chan_info **)realloc(sp->chans, chans * sizeof(chan_info *));
	  for (i = sp->allocated_chans; i < chans; i++) sp->chans[i] = NULL;
	  sp->allocated_chans = chans;
	}
    }
  sp->user_read_only = read_only;  /* need to be sure this is set before any hooks run */
  sp->bomb_in_progress = false;
  sp->index = snd_slot;
  sp->nchans = chans;
  sp->hdr = hdr;
  sp->inuse = SOUND_NORMAL;
  sp->filename = mus_strdup(filename);
  sp->short_filename = filename_without_directory(sp->filename); /* a pointer into filename, not a new string */
  sp->sync = DEFAULT_SYNC;
  sp->previous_sync = sp->sync;
  initialize_control_panel(sp);
  sp->selectpos = -1;

  if (chans > 1)
    {
      if (ss->update_sound_channel_style != NOT_A_CHANNEL_STYLE)
	{
	  sp->channel_style = ss->update_sound_channel_style;
	  ss->update_sound_channel_style = NOT_A_CHANNEL_STYLE;
	}
      else sp->channel_style = channel_style(ss);
    }
  else sp->channel_style = CHANNELS_SEPARATE;

  sp->selected_channel = NO_SELECTION;
  sp->playing = 0;
  sp->applying = false;
  sp->lacp = NULL;
  sp->delete_me = NULL;
  sp->name_string = NULL;
  sp->active = true;
  return(sp);
}


void free_snd_info(snd_info *sp)
{
  uint32_t i;

#if (!USE_NO_GUI)
  env_editor *edp;

  /* make sure trough colors are ok upon reuse */
  if (sp->reverb_control_on != DEFAULT_REVERB_CONTROL_ON)
    toggle_reverb_button(sp, DEFAULT_REVERB_CONTROL_ON);
  if (sp->expand_control_on != DEFAULT_EXPAND_CONTROL_ON)
    toggle_expand_button(sp, DEFAULT_EXPAND_CONTROL_ON);
  if (sp->contrast_control_on != DEFAULT_CONTRAST_CONTROL_ON)
    toggle_contrast_button(sp, DEFAULT_CONTRAST_CONTROL_ON);

  edp = sp->flt;
  if (edp)
    {
      edp->edited = false;
      edp->env_dragged = false;
      edp->env_pos = 0;
      edp->click_to_delete = false;
    }
  set_filter_text(sp, "");
#endif

  /* leave most for reuse as in free_chan_info */
  sp->active = false;
  sp->selectpos = -1;
  sp->sync = 0;
  snd_info_cleanup(sp);
  sp->previous_sync = sp->sync;
  for (i = 0; i < sp->nchans; i++)
    if (sp->chans[i]) 
      sp->chans[i] = free_chan_info(sp->chans[i]);
  sp->inuse = SOUND_IDLE;
  sp->playing = 0;
  sp->bomb_in_progress = false;
  sp->applying = false;
  sp->channel_style = CHANNELS_SEPARATE;
  sp->user_read_only = FILE_READ_WRITE;
  sp->file_read_only = FILE_READ_WRITE;
  sp->need_update = false;
  sp->file_unreadable = false;
  if (Xen_is_vector(sp->properties)) /* using vector as node for GC */
    Xen_vector_set(sp->properties, 0, Xen_empty_list);
  sp->selected_channel = NO_SELECTION;
  sp->short_filename = NULL;                      /* was a pointer into filename */
  if (sp->filename) free(sp->filename);
  sp->filename = NULL;
  if (sp->filter_control_envelope) sp->filter_control_envelope = free_env(sp->filter_control_envelope);
  if (sp->saved_controls) free_controls(sp);
  sp->delete_me = NULL;
  if (sp->name_string) free(sp->name_string);
  sp->name_string = NULL;
  sp->lacp = NULL;
  sp->hdr = free_file_info(sp->hdr);
  if (sp->edited_region) clear_region_backpointer(sp);
  clear_players();
  reflect_mix_change(ANY_MIX_ID);

  if (Xen_hook_has_list(ss->effects_hook))
    run_hook(ss->effects_hook, Xen_empty_list, S_effects_hook);
}


snd_info *completely_free_snd_info(snd_info *sp)
{
  int i;

  free_snd_info(sp);
  for (i = 0; i < sp->allocated_chans; i++) 
    {
      chan_info *cp;
      cp = sp->chans[i];
      if (cp)
	{
	  if (Xen_is_vector(cp->properties))
	    {
	      snd_unprotect_at(cp->properties_loc);
	      cp->properties_loc = NOT_A_GC_LOC;
	    }
	  if (cp->inset_graph)
	    free_inset_graph(cp);

	  cp->active = CHANNEL_FREED;
	  free(cp);
	  /* it's possible to have dangling readers (snd_fd) with pointers to this channel (see note in snd-edits.c under unlist_reader) */
	}
    }
  free(sp->chans);
  if (Xen_is_vector(sp->properties))
    {
      snd_unprotect_at(sp->properties_loc);
      sp->properties_loc = NOT_A_GC_LOC;
    }
  free(sp);
  return(NULL);
}


void for_each_chan_with_int(void (*func)(chan_info *ncp, int val), int value)
{
  int i;
  for (i = 0; i < ss->max_sounds; i++)
    {
      uint32_t j;
      snd_info *sp;
      chan_info *cp;
      sp = ss->sounds[i];
      if ((sp) && ((sp->inuse == SOUND_NORMAL) || (sp->inuse == SOUND_WRAPPER)))
	for (j = 0; j < sp->nchans; j++)
	  if ((cp = ((chan_info *)(sp->chans[j]))))
	    (*func)(cp, value);
    }
}


void for_each_chan_with_mus_long_t(void (*func)(chan_info *ncp, mus_long_t val), mus_long_t value)
{
  int i;
  for (i = 0; i < ss->max_sounds; i++)
    {
      uint32_t j;
      snd_info *sp;
      chan_info *cp;
      sp = ss->sounds[i];
      if ((sp) && ((sp->inuse == SOUND_NORMAL) || (sp->inuse == SOUND_WRAPPER)))
	for (j = 0; j < sp->nchans; j++)
	  if ((cp = ((chan_info *)(sp->chans[j]))))
	    (*func)(cp, value);
    }
}


void for_each_chan_with_bool(void (*func)(chan_info *ncp, bool val), bool value)
{
  int i;
  for (i = 0; i < ss->max_sounds; i++)
    {
      uint32_t j;
      snd_info *sp;
      chan_info *cp;
      sp = ss->sounds[i];
      if ((sp) && ((sp->inuse == SOUND_NORMAL) || (sp->inuse == SOUND_WRAPPER)))
	for (j = 0; j < sp->nchans; j++)
	  if ((cp = ((chan_info *)(sp->chans[j]))))
	    (*func)(cp, value);
    }
}


void for_each_chan_with_float(void (*func)(chan_info *ncp, mus_float_t val), mus_float_t value)
{
  int i;
  for (i = 0; i < ss->max_sounds; i++)
    {
      uint32_t j;
      snd_info *sp;
      chan_info *cp;
      sp = ss->sounds[i];
      if ((sp) && ((sp->inuse == SOUND_NORMAL) || (sp->inuse == SOUND_WRAPPER)))
	for (j = 0; j < sp->nchans; j++)
	  if ((cp = ((chan_info *)(sp->chans[j]))))
	    (*func)(cp, value);
    }
}


void for_each_chan(void (*func)(chan_info *ncp))
{
  int i;
  for (i = 0; i < ss->max_sounds; i++)
    {
      uint32_t j;
      snd_info *sp;
      chan_info *cp;
      sp = ss->sounds[i];
      if ((sp) && ((sp->inuse == SOUND_NORMAL) || (sp->inuse == SOUND_WRAPPER)))
	for (j = 0; j < sp->nchans; j++)
	  if ((cp = ((chan_info *)(sp->chans[j]))))
	    (*func)(cp);
    }
}


void for_each_normal_chan_with_void(void (*func)(chan_info *ncp, void *ptr), void *userptr)
{
  int i;
  for (i = 0; i < ss->max_sounds; i++)
    {
      uint32_t j;
      snd_info *sp;
      chan_info *cp;
      sp = ss->sounds[i];
      if ((sp) && (sp->inuse == SOUND_NORMAL))
	for (j = 0; j < sp->nchans; j++)
	  if ((cp = ((chan_info *)(sp->chans[j]))))
	    (*func)(cp, userptr);
    }
}


void for_each_normal_chan_with_int(void (*func)(chan_info *ncp, int val), int value)
{
  int i;
  for (i = 0; i < ss->max_sounds; i++)
    {
      uint32_t j;
      snd_info *sp;
      chan_info *cp;
      sp = ss->sounds[i];
      if ((sp) && (sp->inuse == SOUND_NORMAL))
	for (j = 0; j < sp->nchans; j++)
	  if ((cp = ((chan_info *)(sp->chans[j]))))
	    (*func)(cp, value);
    }
}


void for_each_normal_chan_with_refint(void (*func)(chan_info *ncp, int *val), int *value)
{
  int i;
  for (i = 0; i < ss->max_sounds; i++)
    {
      uint32_t j;
      snd_info *sp;
      chan_info *cp;
      sp = ss->sounds[i];
      if ((sp) && (sp->inuse == SOUND_NORMAL))
	for (j = 0; j < sp->nchans; j++)
	  if ((cp = ((chan_info *)(sp->chans[j]))))
	    (*func)(cp, value);
    }
}


void for_each_normal_chan(void (*func)(chan_info *ncp))
{
  int i;
  for (i = 0; i < ss->max_sounds; i++)
    {
      uint32_t j;
      snd_info *sp;
      chan_info *cp;
      sp = ss->sounds[i];
      if ((sp) && (sp->inuse == SOUND_NORMAL))
	for (j = 0; j < sp->nchans; j++)
	  if ((cp = ((chan_info *)(sp->chans[j]))))
	    (*func)(cp);
    }
}


void for_each_sound_chan_with_int(snd_info *sp, void (*func)(chan_info *ncp, int val1), int value)
{
  uint32_t j;
  chan_info *cp;
  for (j = 0; j < sp->nchans; j++)
    if ((cp = sp->chans[j]))
      (*func)(cp, value);
}


bool map_over_sound_chans(snd_info *sp, bool (*func)(chan_info *ncp))
{
  uint32_t j;
  bool val = false;
  chan_info *cp;
  for (j = 0; j < sp->nchans; j++)
    if ((cp = sp->chans[j]))
      {
	val = (*func)(cp);
	if (val) return(val);
      }
  return(val);
}


void for_each_sound_chan(snd_info *sp, void (*func)(chan_info *ncp))
{
  uint32_t j;
  chan_info *cp;
  for (j = 0; j < sp->nchans; j++)
    if ((cp = sp->chans[j]))
      (*func)(cp);
}


void for_each_sound(void (*func)(snd_info *usp))
{
  int i;
  for (i = 0; i < ss->max_sounds; i++)
    {
      snd_info *sp;
      sp = ss->sounds[i];
      if ((sp) && (sp->inuse == SOUND_NORMAL))
	(*func)(sp);
    }
}


void for_each_sound_with_void(void (*func)(snd_info *usp, void *ptr), void *userptr)
{
  int i;
  for (i = 0; i < ss->max_sounds; i++)
    {
      snd_info *sp;
      sp = ss->sounds[i];
      if ((sp) && (sp->inuse == SOUND_NORMAL))
	(*func)(sp, userptr);
    }
}


void for_each_separate_chan(void (*func)(chan_info *ncp))
{
  /* used only to lock/unlock panes */
  int i;
  for (i = 0; i < ss->max_sounds; i++)
    {
      snd_info *sp;
      sp = ss->sounds[i];
      if ((sp) && (sp->inuse == SOUND_NORMAL))
	{
	  if (sp->channel_style != CHANNELS_SEPARATE)
	    (*func)(sp->chans[0]);
	  else for_each_sound_chan(sp, func);
	}
    }
}


bool snd_ok(snd_info *sp) 
{
  return((sp) && 
	 (sp->inuse != SOUND_IDLE) && 
	 (sp->active));
}


int active_channels(virtual_channels_t count_virtual_channels)
{
  int chans = 0, i;
  for (i = 0; i < ss->max_sounds; i++)
    {
      snd_info *sp;
      sp = ss->sounds[i];
      if (snd_ok(sp) && 
	  (sp->inuse != SOUND_WRAPPER))
	{
	  if ((count_virtual_channels == WITH_VIRTUAL_CHANNELS) ||
	      (sp->channel_style == CHANNELS_SEPARATE))
	    chans += sp->nchans;
	  else chans++;
	}
    }
  return(chans);
}


#define SOUNDS_ALLOC_SIZE 4

int find_free_sound_slot(int desired_chans)
{
  int i, j;
  snd_info *sp;
  /* first try to find an unused slot that can accommodate desired_chans (to reduce Widget creation) */
  if (ss->reloading_updated_file > 0)
    {
      /* snd_update should change the underlying slot only when it has to (user increased chans) */
      sp = ss->sounds[ss->reloading_updated_file - 1];
      if ((!sp) ||
	  ((sp->inuse == SOUND_IDLE) && (sp->allocated_chans >= desired_chans)))
	return(ss->reloading_updated_file - 1);
    }
  for (i = 0; i < ss->max_sounds; i++)
    {
      sp = ss->sounds[i];
      if ((sp) && (sp->inuse == SOUND_IDLE) && (sp->allocated_chans == desired_chans)) return(i);
    }
  for (i = 0; i < ss->max_sounds; i++)
    {
      sp = ss->sounds[i];
      if ((sp) && (sp->inuse == SOUND_IDLE) && (sp->allocated_chans > desired_chans)) return(i);
    }
  for (i = 0; i < ss->max_sounds; i++)
    {
      sp = ss->sounds[i];
      if (!sp) return(i);
      if (sp->inuse == SOUND_IDLE) return(i);
    }
  j = ss->max_sounds;
  ss->max_sounds += SOUNDS_ALLOC_SIZE;
  ss->sounds = (snd_info **)realloc(ss->sounds, ss->max_sounds * sizeof(snd_info *));
  for (i = j; i < ss->max_sounds; i++) ss->sounds[i] = NULL;
  return(j);
}


int find_free_sound_slot_for_channel_display(void)
{
  int i, j;
  for (i = 0; i < ss->max_sounds; i++)
    if (!ss->sounds[i]) return(i);
  j = ss->max_sounds;
  ss->max_sounds += SOUNDS_ALLOC_SIZE;
  ss->sounds = (snd_info **)realloc(ss->sounds, ss->max_sounds * sizeof(snd_info *));
  for (i = j; i < ss->max_sounds; i++) ss->sounds[i] = NULL;
  return(j);
}


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


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


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


chan_info *any_selected_channel(snd_info *sp)
{
  int chan = 0;
  if (sp->selected_channel != NO_SELECTION) 
    chan = sp->selected_channel;
  if ((sp->chans[chan]) && 
      (sp->chans[chan]->active > CHANNEL_INITIALIZED))
    return(sp->chans[chan]);
  return(NULL);
}


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


static Xen select_sound_hook;
static Xen select_channel_hook;
static int current_selectpos = 0;

static void select_sound(snd_info *sp)
{
  if ((!sp) || (sp->inuse != SOUND_NORMAL)) return;

  if (Xen_hook_has_list(select_sound_hook))
    run_hook(select_sound_hook,
	     Xen_list_1(C_int_to_Xen_sound(sp->index)),
	     S_select_sound_hook);

  if (ss->selected_sound != sp->index)
    {
      reflect_sound_selection(sp);
      ss->selected_sound = sp->index;
      new_active_channel_alert();
      reflect_save_as_sound_selection(sp->short_filename);
    }
  sp->selectpos = current_selectpos++;

  if (Xen_hook_has_list(ss->effects_hook))
    run_hook(ss->effects_hook, Xen_empty_list, S_effects_hook);
}


chan_info *color_selected_channel(snd_info *sp)
{
  chan_info *ncp;
  ncp = sp->chans[sp->selected_channel];
  recolor_graph(ncp, true);
  ncp->selected = true;
  update_graph(ncp); /* update any indication of change in selected channel (channel id for example) */
  return(ncp);
}


void select_channel(snd_info *sp, int chan)
{
  chan_info *cp;

  if ((!sp) || (sp->inuse != SOUND_NORMAL)) return;
  cp = selected_channel();
  if (cp != sp->chans[chan])
    {
      chan_info *ncp;
      sp->selected_channel = chan;
      select_sound(sp);
      if (cp) 
	{
	  recolor_graph(cp, false);
	  cp->selected = false;
	  if (sp != cp->sound) cp->sound->selected_channel = NO_SELECTION;
	  update_graph(cp);
	}
      if (Xen_hook_has_list(select_channel_hook))
	run_hook(select_channel_hook,
		 Xen_list_2(C_int_to_Xen_sound(sp->index),
			    C_int_to_Xen_integer(chan)),
		 S_select_channel_hook);
      ncp = color_selected_channel(sp);
      goto_graph(ncp);
    }
}


chan_info *current_channel(void)
{
  snd_info *sp = NULL;
  if (ss->selected_sound != NO_SELECTION)
    sp = ss->sounds[ss->selected_sound];
  else sp = any_active_sound();
  if (sp) return(any_selected_channel(sp));
  return(NULL);
}


sync_info *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);
    }
  return(NULL);
}


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


sync_info *snd_sync(int sync)
{
  int chans;
  snd_info *sp;
  chans = syncd_channels(sync);
  if (chans > 0)
    {
      int i, j;
      sync_info *si;
      si = (sync_info *)calloc(1, sizeof(sync_info));
      si->begs = (mus_long_t *)calloc(chans, sizeof(mus_long_t));
      si->cps = (chan_info **)calloc(chans, sizeof(chan_info *));
      si->chans = chans;
      for (i = 0, j = 0; i < ss->max_sounds; i++)
	{
	  sp = ss->sounds[i];
	  if ((sp) && 
	      (sp->inuse == SOUND_NORMAL) && 
	      (sp->sync == sync))
	    {
	      uint32_t k;
	      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, mus_long_t 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 = (mus_long_t *)calloc(1, sizeof(mus_long_t));
  si->begs[0] = beg;
  return(si);
}


sync_info *sync_to_chan(chan_info *cp)
{
  snd_info *sp;
  sp = cp->sound;
  if (sp->sync != 0)
    return(snd_sync(sp->sync));
  return(make_simple_sync(cp, 0));
}


snd_info *find_sound(const char *name, int nth)
{
  char *sname;
  int i, which = 0;
  if (!name) return(NULL);
  sname = filename_without_directory(name);
  for (i = 0; i < ss->max_sounds; i++)
    {
      snd_info *sp;
      sp = ss->sounds[i];
      if ((sp) && (sp->inuse == SOUND_NORMAL))
	if ((mus_strcmp(name, sp->short_filename)) || 
	    (mus_strcmp(name, sp->filename)) ||
	    (mus_strcmp(sname, sp->short_filename)))
	  {
	    if (which == nth) return(sp);
	    which++;
	  }
    }
  return(NULL);
}


void g_init_data(void)
{
  #define H_select_sound_hook S_select_sound_hook " (snd): called whenever a sound is selected."

  #define H_select_channel_hook S_select_channel_hook " (snd chn): called whenever a channel is selected. \
Its arguments are the sound index and the channel number."

  select_sound_hook =   Xen_define_hook(S_select_sound_hook,   "(make-hook 'snd)",      1, H_select_sound_hook);
  select_channel_hook = Xen_define_hook(S_select_channel_hook, "(make-hook 'snd 'chn)", 2, H_select_channel_hook);
}

