/* GSL - Generic Sound Layer
 * Copyright (C) 2001-2002 Tim Janik and Stefan Westerfeld
 *
 * This library is free software; you can redistribute it and/or modify
 * it under the terms of the GNU Library 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 General Public License for more details.
 *
 * You should have received a copy of the GNU Library 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.
 */


#define CHECK_SYNC		(WOSC_MIX_VARIANT & WOSC_MIX_WITH_SYNC)
#define CHECK_FREQ		(WOSC_MIX_VARIANT & WOSC_MIX_WITH_FREQ)
#define CHECK_MOD		(WOSC_MIX_VARIANT & WOSC_MIX_WITH_MOD)
#define EXPONENTIAL_FM		(WOSC_MIX_VARIANT & WOSC_MIX_WITH_EXP_FM)
#define DIRSTRIDE(b)		(b->dirstride)	 /* (1) change for n_channel stepping */


static void
WOSC_MIX_VARIANT_NAME (GslWaveOscData *wosc,
		       guint           n_values,
		       const gfloat   *freq_in,
		       const gfloat   *mod_in,
		       const gfloat   *sync_in,
		       gfloat         *wave_out)
{
  gfloat *wave_boundary;
  gfloat last_sync_level = wosc->last_sync_level;
  gfloat last_freq_level = wosc->last_freq_level;
  gfloat last_mod_level = wosc->last_mod_level;
  GslWaveChunkBlock *block = &wosc->block;
  gdouble *a = wosc->a, *b = wosc->b, *y = wosc->y;
  gfloat *boundary = block->end;
  guint wosc_j = wosc->j;

  /* do the mixing */
  wave_boundary = wave_out + n_values;
  do
    {
      gfloat ffrac;

      if (CHECK_SYNC)
	{
	  gfloat sync_level = *sync_in++;
	  if_reject (GSL_SIGNAL_RAISING_EDGE (last_sync_level, sync_level))
	    {
	      wosc->j = wosc_j;
	      gsl_wave_osc_retrigger (wosc, CHECK_FREQ ? GSL_SIGNAL_TO_FREQ (*freq_in) : wosc->config.cfreq);
	      /* retrigger alters last_freq and last_mod */
	      last_freq_level = wosc->last_freq_level;
	      last_mod_level = wosc->last_mod_level;
	      wosc_j = wosc->j;
	      boundary = block->end;
	      /* FIXME: g_assert (ABS (block->dirstride) == 1); */
	      last_sync_level = sync_level;
	    }
	}
      if (CHECK_MOD && CHECK_FREQ)
	{
	  gfloat mod_level = *mod_in++, freq_level = *freq_in++;
	  if_reject (GSL_SIGNAL_FREQ_CHANGED (last_freq_level, freq_level))
	    {
	      last_freq_level = freq_level;
	      if (GSL_SIGNAL_MOD_CHANGED (last_mod_level, mod_level))
		last_mod_level = mod_level;
	      goto UPDATE_FREQ;
	    }
          else if_reject (GSL_SIGNAL_MOD_CHANGED (last_mod_level, mod_level))
	    {
	      gfloat new_freq;
	      last_mod_level = mod_level;
	    UPDATE_FREQ:
	      new_freq = GSL_SIGNAL_TO_FREQ (freq_level);
	      if (EXPONENTIAL_FM)
		new_freq *= gsl_signal_exp2 (wosc->config.fm_strength * mod_level);
	      else /* LINEAR_FM */
		new_freq *= 1.0 + wosc->config.fm_strength * mod_level;
	      wave_osc_transform_filter (wosc, new_freq);
	    }
	}
      else if (CHECK_MOD)
	{
	  gfloat mod_level = *mod_in++;
	  if (GSL_SIGNAL_MOD_CHANGED (last_mod_level, mod_level))
	    {
	      gfloat new_freq = wosc->config.cfreq;
	      if (EXPONENTIAL_FM)
		new_freq *= gsl_signal_exp2 (wosc->config.fm_strength * mod_level);
	      else /* LINEAR_FM */
		new_freq *= 1.0 + wosc->config.fm_strength * mod_level;
	      last_mod_level = mod_level;
	      wave_osc_transform_filter (wosc, new_freq);
	    }
	}
      else if (CHECK_FREQ)
	{
	  gfloat freq_level = *freq_in++;
	  if (GSL_SIGNAL_FREQ_CHANGED (last_freq_level, freq_level))
	    {
	      last_freq_level = freq_level;
	      wave_osc_transform_filter (wosc, GSL_SIGNAL_TO_FREQ (freq_level));
	    }
	}

      /* process filter while necesary */
      while (wosc->cur_pos >= (FRAC_MASK + 1) << 1)
	{
	  gfloat c, c0, c1, c2, c3, c4, c5, c6, c7, c8;
	  gfloat d, d0, d1, d2, d3, d4, d5, d6, d7;
	  gfloat *x;

	  if_reject (wosc->x >= boundary)       /* wchunk block boundary */
	    {
	      GslLong next_offset = block->next_offset;

	      gsl_wave_chunk_unuse_block (wosc->wchunk, block);
	      block->play_dir = wosc->config.play_dir;
	      block->offset = next_offset;
	      gsl_wave_chunk_use_block (wosc->wchunk, block);
	      wosc->x = block->start + wosc->config.channel;
	      boundary = block->end;
	      /* FIXME: g_assert (ABS (block->dirstride) == 1); */
	    }

	  if_expect (block->dirstride > 0)
	    {
	      x = wosc->x;
	      d0 = b[0] * y[wosc_j]; wosc_j++; wosc_j &= 0x7;
	      d1 = b[1] * y[wosc_j]; wosc_j++; wosc_j &= 0x7;
	      d2 = b[2] * y[wosc_j]; wosc_j++; wosc_j &= 0x7;
	      d3 = b[3] * y[wosc_j]; wosc_j++; wosc_j &= 0x7;
	      d4 = b[4] * y[wosc_j]; wosc_j++; wosc_j &= 0x7;
	      d5 = b[5] * y[wosc_j]; wosc_j++; wosc_j &= 0x7;
	      d6 = b[6] * y[wosc_j]; wosc_j++; wosc_j &= 0x7;
	      d7 = b[7] * y[wosc_j]; wosc_j++; wosc_j &= 0x7;
	      c8 = a[8] * x[-4 * DIRSTRIDE (block)];
	      c6 = a[6] * x[-3 * DIRSTRIDE (block)];
	      c4 = a[4] * x[-2 * DIRSTRIDE (block)];
	      c2 = a[2] * x[-1 * DIRSTRIDE (block)];
	      c0 = a[0] * x[0 * DIRSTRIDE (block)];
	      d = d0 + d1 + d2 + d3 + d4 + d5 + d6 + d7;
	      c = c0 + c2 + c4 + c6 + c8;
	      y[wosc_j] = c - d; wosc_j++; wosc_j &= 0x7;
	      d0 = b[0] * y[wosc_j]; wosc_j++; wosc_j &= 0x7;
	      d1 = b[1] * y[wosc_j]; wosc_j++; wosc_j &= 0x7;
	      d2 = b[2] * y[wosc_j]; wosc_j++; wosc_j &= 0x7;
	      d3 = b[3] * y[wosc_j]; wosc_j++; wosc_j &= 0x7;
	      d4 = b[4] * y[wosc_j]; wosc_j++; wosc_j &= 0x7;
	      d5 = b[5] * y[wosc_j]; wosc_j++; wosc_j &= 0x7;
	      d6 = b[6] * y[wosc_j]; wosc_j++; wosc_j &= 0x7;
	      d7 = b[7] * y[wosc_j]; wosc_j++; wosc_j &= 0x7;
	      c7 = a[7] * x[-3 * DIRSTRIDE (block)];
	      c5 = a[5] * x[-2 * DIRSTRIDE (block)];
	      c3 = a[3] * x[-1 * DIRSTRIDE (block)];
	      c1 = a[1] * x[0 * DIRSTRIDE (block)];
	      d = d0 + d1 + d2 + d3 + d4 + d5 + d6 + d7;
	      c = c1 + c3 + c5 + c7;
	      y[wosc_j] = c - d; wosc_j++; wosc_j &= 0x7;
	      wosc->x += DIRSTRIDE (block);
	    }
          else /* dirstride < 0 */
	    {
	      x = wosc->x;
	      d0 = b[0] * y[wosc_j]; wosc_j++; wosc_j &= 0x7;
	      d1 = b[1] * y[wosc_j]; wosc_j++; wosc_j &= 0x7;
	      d2 = b[2] * y[wosc_j]; wosc_j++; wosc_j &= 0x7;
	      d3 = b[3] * y[wosc_j]; wosc_j++; wosc_j &= 0x7;
	      d4 = b[4] * y[wosc_j]; wosc_j++; wosc_j &= 0x7;
	      d5 = b[5] * y[wosc_j]; wosc_j++; wosc_j &= 0x7;
	      d6 = b[6] * y[wosc_j]; wosc_j++; wosc_j &= 0x7;
	      d7 = b[7] * y[wosc_j]; wosc_j++; wosc_j &= 0x7;
	      c8 = a[8] * x[-4 * -DIRSTRIDE (block)];
	      c6 = a[6] * x[-3 * -DIRSTRIDE (block)];
	      c4 = a[4] * x[-2 * -DIRSTRIDE (block)];
	      c2 = a[2] * x[-1 * -DIRSTRIDE (block)];
	      c0 = a[0] * x[0 * -DIRSTRIDE (block)];
	      d = d0 + d1 + d2 + d3 + d4 + d5 + d6 + d7;
	      c = c0 + c2 + c4 + c6 + c8;
	      y[wosc_j] = c - d; wosc_j++; wosc_j &= 0x7;
	      d0 = b[0] * y[wosc_j]; wosc_j++; wosc_j &= 0x7;
	      d1 = b[1] * y[wosc_j]; wosc_j++; wosc_j &= 0x7;
	      d2 = b[2] * y[wosc_j]; wosc_j++; wosc_j &= 0x7;
	      d3 = b[3] * y[wosc_j]; wosc_j++; wosc_j &= 0x7;
	      d4 = b[4] * y[wosc_j]; wosc_j++; wosc_j &= 0x7;
	      d5 = b[5] * y[wosc_j]; wosc_j++; wosc_j &= 0x7;
	      d6 = b[6] * y[wosc_j]; wosc_j++; wosc_j &= 0x7;
	      d7 = b[7] * y[wosc_j]; wosc_j++; wosc_j &= 0x7;
	      c7 = a[7] * x[-3 * -DIRSTRIDE (block)];
	      c5 = a[5] * x[-2 * -DIRSTRIDE (block)];
	      c3 = a[3] * x[-1 * -DIRSTRIDE (block)];
	      c1 = a[1] * x[0 * -DIRSTRIDE (block)];
	      d = d0 + d1 + d2 + d3 + d4 + d5 + d6 + d7;
	      c = c1 + c3 + c5 + c7;
	      y[wosc_j] = c - d; wosc_j++; wosc_j &= 0x7;
	      wosc->x += -DIRSTRIDE (block);
	    }

	  wosc->cur_pos -= (FRAC_MASK + 1) << 1;
	}

      /* interpolate filter output from current pos
       * wosc->cur_pos >> FRAC_SHIFT is 1 or 0;
       */
      if (wosc->cur_pos >> FRAC_SHIFT)
	{
	  guint k = wosc_j - 2;

	  ffrac = wosc->cur_pos & FRAC_MASK;    /* int -> float */
	  ffrac *= 1. / (FRAC_MASK + 1.);
	  *wave_out++ = y[k & 0x7] * (1.0 - ffrac) + y[(k + 1) & 0x7] * ffrac;
	}
      else
	{
	  guint k = wosc_j - 3;

	  ffrac = wosc->cur_pos;                /* int -> float */
	  ffrac *= 1. / (FRAC_MASK + 1.);
	  *wave_out++ = y[k & 0x7] * (1.0 - ffrac) + y[(k + 1) & 0x7] * ffrac;
	}

      /* increment */
      wosc->cur_pos += wosc->istep;
    }
  while (wave_out < wave_boundary);
  wosc->j = wosc_j;
  wosc->last_sync_level = last_sync_level;
  wosc->last_freq_level = last_freq_level;
  wosc->last_mod_level = last_mod_level;
}

#undef CHECK_SYNC
#undef CHECK_FREQ
#undef CHECK_MOD
#undef EXPONENTIAL_FM
#undef DIRSTRIDE

#undef WOSC_MIX_VARIANT
#undef WOSC_MIX_VARIANT_NAME
