/* GSL - Generic Sound Layer
 * Copyright (C) 2001 Tim Janik
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General
 * Public License along with this library; if not, write to the
 * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
 * Boston, MA 02110-1301, USA.
 */
#include "gslwavechunk.h"

#include "gslcommon.h"
#include "gsldatahandle.h"

#include <string.h>


/* --- macros --- */
#define	PRINT_DEBUG_INFO		(0)
#define	STATIC_ZERO_SIZE		(4096)
#define	PBLOCK_SIZE(pad, n_channels)	(MAX (2 * (pad), (n_channels) * gsl_get_config ()->wave_chunk_big_pad))

#define	PHASE_NORM(wchunk)		((GslWaveChunkMem*) (0))
#define	PHASE_NORM_BACKWARD(wchunk)	((GslWaveChunkMem*) (+1))
#define	PHASE_UNDEF(wchunk)		((GslWaveChunkMem*) (+2))
#define	PHASE_HEAD(wchunk)		(&(wchunk)->head)
#define	PHASE_ENTER(wchunk)		(&(wchunk)->enter)
#define	PHASE_WRAP(wchunk)		(&(wchunk)->wrap)
#define	PHASE_PPWRAP(wchunk)		(&(wchunk)->ppwrap)
#define	PHASE_LEAVE(wchunk)		(&(wchunk)->leave)
#define	PHASE_TAIL(wchunk)		(&(wchunk)->tail)


/* --- typedefs & structures --- */
typedef struct {
  GslLong pos;			/* input */
  GslLong rel_pos;
  GslLong lbound, ubound;	/* PHASE_NORM/_BACKWARD */
} Iter;
typedef struct {
  GslLong dir;
  GslLong pos;
  GslLong loop_count;
} WPos;


/* --- variables --- */
static gfloat static_zero_block[STATIC_ZERO_SIZE] = { 0, };	/* FIXME */


/* --- functions --- */
static inline void
wpos_step (GslWaveChunk *wchunk,
	   WPos         *wpos)
{
  wpos->pos += wpos->dir;
  if (wpos->loop_count)
    {
      if (wchunk->loop_type == GSL_WAVE_LOOP_PINGPONG)
	{
	  if (wpos->dir < 0 &&
	      wpos->pos == wchunk->loop_first + wpos->dir)
	    {
	      wpos->loop_count--;
	      wpos->dir = -wpos->dir;
	      wpos->pos = wchunk->loop_first + wpos->dir;
	    }
	  else if (wpos->pos == wchunk->loop_last + wpos->dir)
	    {
	      wpos->loop_count--;
	      wpos->dir = -wpos->dir;
	      wpos->pos = wchunk->loop_last + wpos->dir;
	    }
	}
      else
	{
	  if (wpos->pos == wchunk->loop_last + wpos->dir && wpos->loop_count)
	    {
	      wpos->loop_count--;
	      wpos->pos = wchunk->loop_first;
	    }
	}
    }
}

static void
fill_block (GslWaveChunk *wchunk,
	    gfloat	 *block,
	    GslLong	  offset,
	    guint	  length,
	    gboolean	  backward,
	    guint	  loop_count)
{
  GslLong dcache_length = gsl_data_handle_length (wchunk->dcache->dhandle);
  guint i, dnode_length = wchunk->dcache->node_size;
  GslDataCacheNode *dnode;
  WPos wpos;

  wpos.dir = wchunk->n_channels;
  if (backward)
    wpos.dir = -wpos.dir;
  wpos.pos = offset;
  wpos.loop_count = loop_count;
  dnode = gsl_data_cache_ref_node (wchunk->dcache, 0, TRUE);
  for (i = 0; i < length; i++)
    {
      GslLong offset = wpos.pos;

      if (offset < 0 || offset >= dcache_length)
	block[i] = 0;
      else
	{
	  if (offset < dnode->offset || offset >= dnode->offset + dnode_length)
	    {
	      gsl_data_cache_unref_node (wchunk->dcache, dnode);
	      dnode = gsl_data_cache_ref_node (wchunk->dcache, offset, TRUE);
	    }
	  block[i] = dnode->data[offset - dnode->offset];
	}
      wpos_step (wchunk, &wpos);
    }
  gsl_data_cache_unref_node (wchunk->dcache, dnode);
}

static gfloat*
create_block_for_offset (GslWaveChunk *wchunk,
			 GslLong       offset,
			 guint         length)
{
  GslLong padding = wchunk->n_pad_values;
  GslLong one = wchunk->n_channels;
  GslLong wave_last = wchunk->length - one;
  GslLong loop_width = wchunk->loop_last - wchunk->loop_first;
  gfloat *mem;
  GslLong l, j, k;

  if (wchunk->loop_type != GSL_WAVE_LOOP_PINGPONG)
    loop_width += one;

  l = length + 2 * padding;
  mem = gsl_new_struct (gfloat, l);
  offset -= padding;
  j = ((wchunk->wave_length - one - offset) -
       (wchunk->pploop_ends_backwards ? wchunk->loop_first : wave_last - wchunk->loop_last));
  if (j >= 0)
    {
      k = j / loop_width;
      /* g_print ("endoffset-setup: j=%ld %%=%ld, k=%ld, k&1=%ld\n", j, j % loop_width, k, k & 1); */
      j %= loop_width;
      if (wchunk->loop_type == GSL_WAVE_LOOP_PINGPONG)
	{
	  if (wchunk->pploop_ends_backwards && (k & 1))
	    fill_block (wchunk, mem, wchunk->loop_last - j, l, FALSE, k);
	  else if (wchunk->pploop_ends_backwards)
	    fill_block (wchunk, mem, wchunk->loop_first + j, l, TRUE, k);
	  else if (k & 1)
	    fill_block (wchunk, mem, wchunk->loop_first + j, l, TRUE, k);
	  else
	    fill_block (wchunk, mem, wchunk->loop_last - j, l, FALSE, k);
	}
      else
	fill_block (wchunk, mem, wchunk->loop_last - j, l, FALSE, k);
    }
  else if (wchunk->pploop_ends_backwards)
    fill_block (wchunk, mem, wchunk->loop_first + j, l, TRUE, 0);
  else
    fill_block (wchunk, mem, wchunk->loop_last - j, l, FALSE, 0);
  return mem + padding;
}

static void
setup_pblocks (GslWaveChunk *wchunk)
{
  GslLong padding = wchunk->n_pad_values;
  GslLong big_pad = PBLOCK_SIZE (wchunk->n_pad_values, wchunk->n_channels);
  GslLong loop_width = wchunk->loop_last - wchunk->loop_first;
  GslLong one = wchunk->n_channels;
  GslLong loop_duration, wave_last = wchunk->length - one;
  gfloat *mem;
  guint l;

  if (wchunk->loop_type != GSL_WAVE_LOOP_PINGPONG)
    loop_width += one;
  loop_duration = loop_width * wchunk->loop_count;

  wchunk->head.start = -padding;
  wchunk->head.end = big_pad;
  wchunk->head.length = wchunk->head.end - wchunk->head.start + one;
  wchunk->tail_start_norm = wave_last - big_pad;
  wchunk->tail.start = wchunk->tail_start_norm + loop_duration;
  wchunk->tail.end = wchunk->tail.start + big_pad + padding;
  wchunk->tail.length = wchunk->tail.end - wchunk->tail.start + one;
  if (wchunk->loop_type)
    {
      wchunk->enter.start = wchunk->loop_last - padding;
      wchunk->enter.end = wchunk->loop_last + one + big_pad;
      wchunk->wrap.start = loop_width - padding;
      wchunk->wrap.end = big_pad;
      if (wchunk->loop_type == GSL_WAVE_LOOP_PINGPONG)
	{
	  wchunk->enter.end -= one;
	  wchunk->wrap.end -= one;
	  wchunk->ppwrap.start = wchunk->wrap.start;
	  wchunk->ppwrap.end = wchunk->wrap.end + loop_width;
	  wchunk->ppwrap.length = wchunk->ppwrap.end - wchunk->ppwrap.start + one;
	  wchunk->wrap.length = loop_width - wchunk->wrap.start + wchunk->wrap.end + one;
	  wchunk->wrap.start += loop_width;
	}
      else
	wchunk->wrap.length = loop_width - wchunk->wrap.start + wchunk->wrap.end + one;
      wchunk->leave_end_norm = wchunk->loop_last + big_pad;
      wchunk->leave.start = wchunk->loop_last + loop_duration - padding;
      wchunk->leave.end = wchunk->leave_end_norm + loop_duration;
      if (wchunk->mini_loop)
	{
	  wchunk->leave.start -= wchunk->wrap.length + padding;
	  wchunk->enter.end += wchunk->wrap.length + padding;
	}
      wchunk->leave.length = wchunk->leave.end - wchunk->leave.start + one;
      wchunk->enter.length = wchunk->enter.end - wchunk->enter.start + one;
      if (wchunk->pploop_ends_backwards)
	{
	  wchunk->tail.start += wchunk->loop_last - wave_last + wchunk->loop_first;
	  wchunk->tail.end += wchunk->loop_last - wave_last + wchunk->loop_first;
	  wchunk->tail_start_norm = 0 + big_pad;
	  wchunk->leave_end_norm = wchunk->loop_first - big_pad;
	}
    }
  else
    {
      /*
      wchunk->enter.start = wchunk->head.end;
      wchunk->enter.end = wchunk->head.end;
      wchunk->enter.length = 0;
      */
      wchunk->enter.start = wchunk->tail.start;
      wchunk->enter.end = wchunk->head.end;
      wchunk->enter.length = 0;
      wchunk->wrap.start = wchunk->tail.end + 1;
      wchunk->wrap.end = wchunk->head.start - 1;
      wchunk->wrap.length = 0;
      wchunk->ppwrap.start = wchunk->tail.end + 1;
      wchunk->ppwrap.end = wchunk->head.start - 1;
      wchunk->ppwrap.length = 0;
      wchunk->leave.start = wchunk->tail.start;
      wchunk->leave.end = wchunk->tail.end;
      wchunk->leave_end_norm = 0;
      wchunk->leave.length = 0;
    }

  l = wchunk->head.length + 2 * padding;
  mem = gsl_new_struct (gfloat, l);
  fill_block (wchunk, mem, wchunk->head.start - padding, l, FALSE, wchunk->loop_count);
  wchunk->head.mem = mem + padding;
  if (wchunk->loop_type)
    {
      l = wchunk->enter.length + 2 * padding;
      mem = gsl_new_struct (gfloat, l);
      fill_block (wchunk, mem, wchunk->enter.start - padding, l, FALSE, wchunk->loop_count);
      wchunk->enter.mem = mem + padding;
      if (wchunk->loop_type == GSL_WAVE_LOOP_PINGPONG)
	{
	  wchunk->wrap.mem = create_block_for_offset (wchunk, wchunk->loop_last + one + wchunk->wrap.start, wchunk->wrap.length);
	  wchunk->ppwrap.mem = create_block_for_offset (wchunk, wchunk->loop_last + one + wchunk->ppwrap.start, wchunk->ppwrap.length);
	}
      else
	{
	  l = wchunk->wrap.length + 2 * padding;
	  mem = gsl_new_struct (gfloat, l);
	  fill_block (wchunk, mem, wchunk->loop_first + wchunk->wrap.start - padding, l, FALSE, wchunk->loop_count - 1);
	  wchunk->wrap.mem = mem + padding;
	}
      wchunk->leave.mem = create_block_for_offset (wchunk, wchunk->leave.start, wchunk->leave.length);
    }
  wchunk->tail.mem = create_block_for_offset (wchunk, wchunk->tail.start, wchunk->tail.length);
}

static inline GslWaveChunkMem*
wave_identify_offset (GslWaveChunk *wchunk,
		      Iter         *iter)
{
  GslLong pos = iter->pos;
  GslLong one = wchunk->n_channels;

  if (pos < wchunk->head.start)					/* outside wave boundaries */
    {
      iter->lbound = 0;
      iter->rel_pos = wchunk->n_pad_values;
      iter->ubound = iter->rel_pos + MIN (STATIC_ZERO_SIZE - 2 * wchunk->n_pad_values, wchunk->head.start - pos);
      if (PRINT_DEBUG_INFO)
	g_print ("PHASE_UNDEF, pre-head %ld %ld %ld\n", iter->lbound, iter->rel_pos, iter->ubound);
      return PHASE_UNDEF (wchunk);
    }
  if (pos > wchunk->tail.end)					/* outside wave boundaries */
    {
      iter->lbound = 0;
      iter->rel_pos = wchunk->n_pad_values;
      iter->ubound = iter->rel_pos + MIN (STATIC_ZERO_SIZE - 2 * wchunk->n_pad_values, pos - wchunk->tail.end);
      if (PRINT_DEBUG_INFO)
	g_print ("PHASE_UNDEF, post-tail %ld %ld %ld\n", iter->lbound, iter->rel_pos, iter->ubound);
      return PHASE_UNDEF (wchunk);
    }
  if (pos <= wchunk->head.end)
    {
      iter->rel_pos = pos - wchunk->head.start;
      if (PRINT_DEBUG_INFO)
	g_print ("PHASE_HEAD %ld %ld %ld\n", wchunk->head.start, iter->rel_pos, wchunk->head.end);
      return PHASE_HEAD (wchunk);
    }
  else if (pos <= wchunk->enter.end)					/* before loop */
    {
      if (pos >= wchunk->enter.start)
	{
	  iter->rel_pos = pos - wchunk->enter.start;
	  if (PRINT_DEBUG_INFO)
	    g_print ("PHASE_ENTER %ld %ld %ld\n", wchunk->enter.start, iter->rel_pos, wchunk->enter.end);
	  return PHASE_ENTER (wchunk);
	}
      iter->rel_pos = pos - wchunk->head.end;
      iter->lbound = wchunk->head.end;
      iter->ubound = wchunk->enter.start;
      if (PRINT_DEBUG_INFO)
	g_print ("PHASE_NORM, pre-enter %ld %ld %ld\n", iter->lbound, iter->rel_pos, iter->ubound);
      return PHASE_NORM (wchunk);
    }
  else if (pos >= wchunk->tail.start)
    {
      iter->rel_pos = pos - wchunk->tail.start;
      if (PRINT_DEBUG_INFO)
	g_print ("PHASE_TAIL %ld %ld %ld\n", wchunk->tail.start, iter->rel_pos, wchunk->tail.end);
      return PHASE_TAIL (wchunk);
    }
  else if (pos >= wchunk->leave.start)				/* after loop */
    {
      if (pos <= wchunk->leave.end)
	{
	  iter->rel_pos = pos - wchunk->leave.start;
	  if (PRINT_DEBUG_INFO)
	    g_print ("PHASE_LEAVE %ld %ld %ld\n", wchunk->leave.start, iter->rel_pos, wchunk->leave.end);
	  return PHASE_LEAVE (wchunk);
	}
      iter->rel_pos = pos - wchunk->leave.end;
      if (wchunk->pploop_ends_backwards)
	{
	  iter->lbound = wchunk->tail_start_norm;
	  iter->ubound = wchunk->leave_end_norm;
	  if (PRINT_DEBUG_INFO)
	    g_print ("PHASE_NORM_BACKWARD, post-leave %ld %ld %ld\n", iter->lbound, iter->rel_pos, iter->ubound);
	  return PHASE_NORM_BACKWARD (wchunk);
	}
      else
	{
	  iter->lbound = wchunk->leave_end_norm;
	  iter->ubound = wchunk->tail_start_norm;
	  if (PRINT_DEBUG_INFO)
	    g_print ("PHASE_NORM, post-leave %ld %ld %ld\n", iter->lbound, iter->rel_pos, iter->ubound);
	  return PHASE_NORM (wchunk);
	}
    }
  else if (wchunk->loop_type == GSL_WAVE_LOOP_PINGPONG)		/* in ping-pong loop */
    {
      guint loop_width = wchunk->loop_last - wchunk->loop_first;

      pos -= wchunk->loop_last + one;
      pos %= 2 * loop_width;
      if (pos <= wchunk->ppwrap.end)
	{
	  if (pos <= wchunk->wrap.end)
	    {
	      iter->rel_pos = wchunk->wrap.length - one - wchunk->wrap.end + pos;
	      if (PRINT_DEBUG_INFO)
		g_print ("PHASE_WRAP %ld %ld %ld\n", wchunk->wrap.start, iter->rel_pos, wchunk->wrap.end);
	      return PHASE_WRAP (wchunk);
	    }
	  if (pos >= wchunk->ppwrap.start)
	    {
	      iter->rel_pos = pos - wchunk->ppwrap.start;
	      if (PRINT_DEBUG_INFO)
		g_print ("PHASE_PPWRAP %ld %ld %ld\n", wchunk->ppwrap.start, iter->rel_pos, wchunk->ppwrap.end);
	      return PHASE_PPWRAP (wchunk);
	    }
	  iter->ubound = wchunk->loop_last - one - wchunk->wrap.end;
	  iter->lbound = wchunk->loop_last - one - wchunk->ppwrap.start;
	  iter->rel_pos = pos - wchunk->wrap.end;
	  if (PRINT_DEBUG_INFO)
	    g_print ("PHASE_NORM_BACKWARD, pploop %ld %ld %ld\n", iter->lbound, iter->rel_pos, iter->ubound);
	  return PHASE_NORM_BACKWARD (wchunk);
	}
      if (pos >= wchunk->wrap.start)
	{
	  iter->rel_pos = pos - wchunk->wrap.start;
	  if (PRINT_DEBUG_INFO)
	    g_print ("PHASE_WRAP %ld %ld %ld\n", wchunk->wrap.start, iter->rel_pos, wchunk->wrap.end);
	  return PHASE_WRAP (wchunk);
	}
      iter->rel_pos = pos - wchunk->ppwrap.end;
      iter->ubound = wchunk->loop_first + one + wchunk->wrap.start - loop_width;
      iter->lbound = wchunk->loop_first + one + wchunk->ppwrap.end - loop_width;
      if (PRINT_DEBUG_INFO)
	g_print ("PHASE_NORM, pploop %ld %ld %ld\n", iter->lbound, iter->rel_pos, iter->ubound);
      return PHASE_NORM (wchunk);
    }
  else if (wchunk->loop_type == GSL_WAVE_LOOP_JUMP)		/* in jump loop */
    {
      guint loop_width = wchunk->loop_last - wchunk->loop_first + one;

      pos -= wchunk->loop_last + one;
      pos %= loop_width;
      if (pos >= wchunk->wrap.start)
	{
	  iter->rel_pos = pos - wchunk->wrap.start;
	  if (PRINT_DEBUG_INFO)
	    g_print ("PHASE_WRAP %ld %ld %ld\n", wchunk->wrap.start, iter->rel_pos, wchunk->wrap.end);
	  return PHASE_WRAP (wchunk);
	}
      if (pos <= wchunk->wrap.end)
	{
	  iter->rel_pos = wchunk->wrap.length - one - wchunk->wrap.end + pos;
	  if (PRINT_DEBUG_INFO)
	    g_print ("PHASE_WRAP %ld %ld %ld\n", wchunk->wrap.start, iter->rel_pos, wchunk->wrap.end);
	  return PHASE_WRAP (wchunk);
	}
      iter->rel_pos = pos - wchunk->wrap.end;
      iter->lbound = wchunk->loop_first + wchunk->wrap.end;
      iter->ubound = wchunk->loop_first + wchunk->wrap.start;
      if (PRINT_DEBUG_INFO)
	g_print ("PHASE_NORM, jloop %ld %ld %ld\n", iter->lbound, iter->rel_pos, iter->ubound);
      return PHASE_NORM (wchunk);
    }
  iter->rel_pos = pos - wchunk->head.end;
  iter->lbound = wchunk->head.end;
  iter->ubound = wchunk->enter.start;
  if (PRINT_DEBUG_INFO)
    g_print ("PHASE_NORM, noloop %ld %ld %ld\n", iter->lbound, iter->rel_pos, iter->ubound);
  return PHASE_NORM (wchunk);
}

void
gsl_wave_chunk_use_block (GslWaveChunk      *wchunk,
			  GslWaveChunkBlock *block)
{
  GslWaveChunkMem *phase;
  GslLong one;
  Iter iter;
  gboolean reverse;

  g_return_if_fail (wchunk != NULL);
  g_return_if_fail (wchunk->open_count > 0);
  g_return_if_fail (block != NULL);
  g_return_if_fail (wchunk->dcache != NULL);
  g_return_if_fail (block->node == NULL);
  g_return_if_fail (block->play_dir == -1 || block->play_dir == +1);

  block->offset /= wchunk->n_channels;
  block->offset *= wchunk->n_channels;

  one = wchunk->n_channels;
  reverse = block->play_dir < 0;
  iter.pos = block->offset;
  phase = wave_identify_offset (wchunk, &iter);

  block->is_silent = FALSE;
  if (phase <= PHASE_UNDEF (wchunk))
    {
      GslDataCacheNode *dnode;
      guint offset;

      if (phase == PHASE_UNDEF (wchunk))
	{
	  block->is_silent = TRUE;
	  reverse = FALSE;
	  block->length = (iter.ubound - iter.rel_pos) / wchunk->n_channels;
	  block->length *= wchunk->n_channels;
	  g_assert (block->length <= STATIC_ZERO_SIZE - 2 * wchunk->n_pad_values);
	  block->start = static_zero_block + iter.rel_pos;
	}
      else
	{
	  GslLong max_length;

	  if (phase == PHASE_NORM_BACKWARD (wchunk))
	    {
	      offset = iter.ubound - iter.rel_pos;
	      reverse = !reverse;
	    }
	  else
	    offset = iter.lbound + iter.rel_pos;
	  max_length = reverse ? offset - iter.lbound : iter.ubound - offset;
	  dnode = gsl_data_cache_ref_node (wchunk->dcache, offset, TRUE); /* FIXME: demand_load */
	  offset -= dnode->offset;
	  block->start = dnode->data + offset;
	  if (reverse)
	    {
	      block->length = 1 + offset / wchunk->n_channels;
	      block->length *= wchunk->n_channels;
	    }
	  else
	    {
	      block->length = (wchunk->dcache->node_size - offset) / wchunk->n_channels;
	      block->length *= wchunk->n_channels;
	    }
	  block->length = MIN (block->length, max_length);
	  block->node = dnode;
	}
    }
  else
    {
      block->start = phase->mem + iter.rel_pos;
      if (reverse)
	block->length = one + iter.rel_pos;
      else
	block->length = phase->length - iter.rel_pos;
    }
  if (reverse)
    {
      block->dirstride = -wchunk->n_channels;
      block->end = block->start - block->length;
    }
  else
    {
      block->dirstride = +wchunk->n_channels;
      block->end = block->start + block->length;
    }
  g_assert (block->length > 0);
  /* we might want to partly reset this at some point to implement
   * truly infinite loops
   */
  block->next_offset = block->offset + (block->play_dir > 0 ? block->length : -block->length);
}

void
gsl_wave_chunk_unuse_block (GslWaveChunk      *wchunk,
			    GslWaveChunkBlock *block)
{
  g_return_if_fail (wchunk != NULL);
  g_return_if_fail (block != NULL);
  g_return_if_fail (wchunk->dcache != NULL);

  if (block->node)
    {
      gsl_data_cache_unref_node (wchunk->dcache, block->node);
      block->node = NULL;
    }
}

static void
wave_chunk_setup_loop (GslWaveChunk *wchunk)
{
  GslWaveLoopType loop_type = wchunk->requested_loop_type;
  GslLong loop_first = wchunk->requested_loop_first;
  GslLong loop_last = wchunk->requested_loop_last;
  guint loop_count = wchunk->requested_loop_count;
  GslLong one, padding, big_pad;

  g_return_if_fail (wchunk->open_count > 0);

  one = wchunk->n_channels;
  padding = wchunk->n_pad_values;
  big_pad = PBLOCK_SIZE (wchunk->n_pad_values, wchunk->n_channels);

  /* check validity */
  if (loop_count < 1 || loop_first < 0 || loop_last < 0 || wchunk->length < 1)
    loop_type = GSL_WAVE_LOOP_NONE;

  /* setup loop types */
  switch (loop_type)
    {
    case GSL_WAVE_LOOP_JUMP:
      loop_first /= wchunk->n_channels;
      loop_last /= wchunk->n_channels;
      if (loop_last >= wchunk->length ||
	  loop_first >= loop_last)
	goto CASE_DONT_LOOP;
      wchunk->loop_type = loop_type;
      wchunk->loop_first = loop_first * wchunk->n_channels;
      wchunk->loop_last = loop_last * wchunk->n_channels;
      wchunk->loop_count = (G_MAXINT - wchunk->length) / (wchunk->loop_last - wchunk->loop_first + one);
      wchunk->loop_count = MIN (wchunk->loop_count, loop_count);
      wchunk->wave_length = wchunk->length + (wchunk->loop_last - wchunk->loop_first + one) * wchunk->loop_count;
      break;
    case GSL_WAVE_LOOP_PINGPONG:
      loop_first /= wchunk->n_channels;
      loop_last /= wchunk->n_channels;
      if (loop_last >= wchunk->length ||
	  loop_first >= loop_last)
	goto CASE_DONT_LOOP;
      wchunk->loop_type = loop_type;
      wchunk->loop_first = loop_first * wchunk->n_channels;
      wchunk->loop_last = loop_last * wchunk->n_channels;
      wchunk->loop_count = (G_MAXINT - wchunk->loop_last - one) / (wchunk->loop_last - wchunk->loop_first);
      wchunk->loop_count = MIN (wchunk->loop_count, loop_count);
      wchunk->wave_length = wchunk->loop_last + one + (wchunk->loop_last - wchunk->loop_first) * wchunk->loop_count;
      if (wchunk->loop_count & 1)	/* FIXME */
	wchunk->wave_length += wchunk->loop_first;
      else
	wchunk->wave_length += wchunk->length - one - wchunk->loop_last;
      break;
    CASE_DONT_LOOP:
      loop_type = GSL_WAVE_LOOP_NONE;
    case GSL_WAVE_LOOP_NONE:
      wchunk->loop_type = loop_type;
      wchunk->loop_first = wchunk->length + 1;
      wchunk->loop_last = -1;
      wchunk->loop_count = 0;
      wchunk->wave_length = wchunk->length;
      break;
    }
  wchunk->pploop_ends_backwards = wchunk->loop_type == GSL_WAVE_LOOP_PINGPONG && (wchunk->loop_count & 1);
  wchunk->mini_loop = wchunk->loop_type && wchunk->loop_last - wchunk->loop_first < 2 * big_pad + padding;
}

GslWaveChunk*
gsl_wave_chunk_new (GslDataCache   *dcache,
		    gfloat          osc_freq,
		    gfloat          mix_freq,
		    GslWaveLoopType loop_type,
		    GslLong         loop_first,
		    GslLong         loop_last,
		    guint           loop_count)
{
  GslWaveChunk *wchunk;

  g_return_val_if_fail (dcache != NULL, NULL);
  g_return_val_if_fail (osc_freq < mix_freq / 2, NULL);
  g_return_val_if_fail (loop_type >= GSL_WAVE_LOOP_NONE && loop_type <= GSL_WAVE_LOOP_PINGPONG, NULL);

  wchunk = gsl_new_struct0 (GslWaveChunk, 1);
  wchunk->dcache = gsl_data_cache_ref (dcache);
  wchunk->length = 0;
  wchunk->n_channels = 0;
  wchunk->n_pad_values = 0;
  wchunk->wave_length = 0;
  wchunk->loop_type = GSL_WAVE_LOOP_NONE;
  wchunk->leave_end_norm = 0;
  wchunk->tail_start_norm = 0;
  wchunk->ref_count = 1;
  wchunk->open_count = 0;
  wchunk->mix_freq = mix_freq;
  wchunk->osc_freq = osc_freq;
  wchunk->requested_loop_type = loop_type;
  wchunk->requested_loop_first = loop_first;
  wchunk->requested_loop_last = loop_last;
  wchunk->requested_loop_count = loop_count;

  return wchunk;
}

GslWaveChunk*
gsl_wave_chunk_ref (GslWaveChunk *wchunk)
{
  g_return_val_if_fail (wchunk != NULL, NULL);
  g_return_val_if_fail (wchunk->ref_count > 0, NULL);

  wchunk->ref_count++;
  return wchunk;
}

void
gsl_wave_chunk_unref (GslWaveChunk *wchunk)
{
  g_return_if_fail (wchunk != NULL);
  g_return_if_fail (wchunk->ref_count > 0);

  wchunk->ref_count--;
  if (wchunk->ref_count == 0)
    {
      g_return_if_fail (wchunk->open_count == 0);
      gsl_data_cache_unref (wchunk->dcache);
      gsl_delete_struct (GslWaveChunk, wchunk);
    }
}

GslErrorType
gsl_wave_chunk_open (GslWaveChunk *wchunk)
{
  g_return_val_if_fail (wchunk != NULL, GSL_ERROR_INTERNAL);
  g_return_val_if_fail (wchunk->ref_count > 0, GSL_ERROR_INTERNAL);

  if (wchunk->open_count == 0)
    {
      GslErrorType error;

      error = gsl_data_handle_open (wchunk->dcache->dhandle);
      if (error != GSL_ERROR_NONE)
	return error;
      if (gsl_data_handle_n_values (wchunk->dcache->dhandle) < gsl_data_handle_n_channels (wchunk->dcache->dhandle))
	{
	  gsl_data_handle_close (wchunk->dcache->dhandle);
	  return GSL_ERROR_FILE_EMPTY;
	}
      wchunk->n_channels = gsl_data_handle_n_channels (wchunk->dcache->dhandle);
      wchunk->length = gsl_data_handle_n_values (wchunk->dcache->dhandle) / wchunk->n_channels;
      wchunk->length *= wchunk->n_channels;
      wchunk->n_pad_values = gsl_get_config ()->wave_chunk_padding * wchunk->n_channels;
      gsl_data_cache_open (wchunk->dcache);
      gsl_data_handle_close (wchunk->dcache->dhandle);
      g_return_val_if_fail (wchunk->dcache->padding >= wchunk->n_pad_values, GSL_ERROR_INTERNAL);
      wchunk->open_count++;
      wchunk->ref_count++;
      wave_chunk_setup_loop (wchunk);
      setup_pblocks (wchunk);
    }
  else
    wchunk->open_count++;
  return GSL_ERROR_NONE;
}

void
gsl_wave_chunk_close (GslWaveChunk *wchunk)
{
  GslLong padding;

  g_return_if_fail (wchunk != NULL);
  g_return_if_fail (wchunk->open_count > 0);
  g_return_if_fail (wchunk->ref_count > 0);

  wchunk->open_count--;
  if (wchunk->open_count)
    return;

  padding = wchunk->n_pad_values;
  gsl_data_cache_close (wchunk->dcache);
  if (wchunk->head.mem)
    gsl_delete_structs (gfloat, wchunk->head.length + 2 * padding, wchunk->head.mem - padding);
  memset (&wchunk->head, 0, sizeof (GslWaveChunkMem));
  if (wchunk->enter.mem)
    gsl_delete_structs (gfloat, wchunk->enter.length + 2 * padding, wchunk->enter.mem - padding);
  memset (&wchunk->enter, 0, sizeof (GslWaveChunkMem));
  if (wchunk->wrap.mem)
    gsl_delete_structs (gfloat, wchunk->wrap.length + 2 * padding, wchunk->wrap.mem - padding);
  memset (&wchunk->wrap, 0, sizeof (GslWaveChunkMem));
  if (wchunk->ppwrap.mem)
    gsl_delete_structs (gfloat, wchunk->ppwrap.length + 2 * padding, wchunk->ppwrap.mem - padding);
  memset (&wchunk->ppwrap, 0, sizeof (GslWaveChunkMem));
  if (wchunk->leave.mem)
    gsl_delete_structs (gfloat, wchunk->leave.length + 2 * padding, wchunk->leave.mem - padding);
  memset (&wchunk->leave, 0, sizeof (GslWaveChunkMem));
  if (wchunk->tail.mem)
    gsl_delete_structs (gfloat, wchunk->tail.length + 2 * padding, wchunk->tail.mem - padding);
  memset (&wchunk->tail, 0, sizeof (GslWaveChunkMem));
  wchunk->length = 0;
  wchunk->n_channels = 0;
  wchunk->n_pad_values = 0;
  wchunk->wave_length = 0;
  wchunk->loop_type = GSL_WAVE_LOOP_NONE;
  wchunk->leave_end_norm = 0;
  wchunk->tail_start_norm = 0;
  gsl_wave_chunk_unref (wchunk);
}

void
gsl_wave_chunk_debug_block (GslWaveChunk *wchunk,
			    GslLong       offset,
			    GslLong       length,
			    gfloat	 *block)
{
  g_return_if_fail (wchunk != NULL);

  fill_block (wchunk, block, offset, length, FALSE, wchunk->loop_count);
}

GslWaveChunk*
_gsl_wave_chunk_copy (GslWaveChunk *wchunk)
{
  g_return_val_if_fail (wchunk != NULL, NULL);
  g_return_val_if_fail (wchunk->ref_count > 0, NULL);

  return gsl_wave_chunk_new (wchunk->dcache,
			     wchunk->osc_freq,
			     wchunk->mix_freq,
			     wchunk->loop_type,
			     wchunk->loop_first,
			     wchunk->loop_last,
			     wchunk->loop_count);
}

const gchar*
gsl_wave_loop_type_to_string (GslWaveLoopType wave_loop)
{
  g_return_val_if_fail (wave_loop >= GSL_WAVE_LOOP_NONE && wave_loop <= GSL_WAVE_LOOP_PINGPONG, NULL);

  switch (wave_loop)
    {
    case GSL_WAVE_LOOP_NONE:		return "none";
    case GSL_WAVE_LOOP_JUMP:		return "jump";
    case GSL_WAVE_LOOP_PINGPONG:	return "pingpong";
    default:				return NULL;
    }
}

GslWaveLoopType
gsl_wave_loop_type_from_string (const gchar *string)
{
  g_return_val_if_fail (string != NULL, 0);

  while (*string == ' ')
    string++;
  if (strncasecmp (string, "jump", 4) == 0)
    return GSL_WAVE_LOOP_JUMP;
  if (strncasecmp (string, "pingpong", 8) == 0)
    return GSL_WAVE_LOOP_PINGPONG;
  return GSL_WAVE_LOOP_NONE;
}