/* GSL - Generic Sound Layer
 * Copyright (C) 1999, 2000-2002 Tim Janik
 *
 * 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	OSC_FLAGS		(GSL_INCLUDER_CASE | OSC_INCLUDER_FLAGS)
#define ISYNC1_OSYNC0		((OSC_FLAGS & OSC_FLAG_ISYNC) && !(OSC_FLAGS & OSC_FLAG_OSYNC))
#define ISYNC1_OSYNC1		((OSC_FLAGS & OSC_FLAG_ISYNC) && (OSC_FLAGS & OSC_FLAG_OSYNC))
#define ISYNC0_OSYNC1		((OSC_FLAGS & OSC_FLAG_OSYNC) && !(OSC_FLAGS & OSC_FLAG_ISYNC))
#define WITH_OSYNC		(OSC_FLAGS & OSC_FLAG_OSYNC)
#define WITH_FREQ		(OSC_FLAGS & OSC_FLAG_FREQ)
#define WITH_SMOD		(OSC_FLAGS & OSC_FLAG_SELF_MOD)
#define WITH_LMOD		(OSC_FLAGS & OSC_FLAG_LINEAR_MOD)
#define WITH_EMOD		(OSC_FLAGS & OSC_FLAG_EXP_MOD)
#define WITH_PWM_MOD		(OSC_FLAGS & OSC_FLAG_PWM_MOD)
#define PULSE_OSC		(OSC_FLAGS & OSC_FLAG_PULSE_OSC)


static void
GSL_INCLUDER_FUNC (GslOscData   *osc,
		   guint         n_values,
		   const gfloat *ifreq,
		   const gfloat *mod_in,
		   const gfloat *sync_in,
		   const gfloat *pwm_in,
		   gfloat       *mono_out,
		   gfloat       *sync_out)
{
  gfloat last_sync_level = osc->last_sync_level;
  gfloat last_pwm_level = osc->last_pwm_level;
  gdouble last_freq_level = osc->last_freq_level;
  guint32 cur_pos = osc->cur_pos;
  guint32 last_pos = osc->last_pos;
  guint32 sync_pos, pos_inc;
  gfloat posm_strength, self_posm_strength;
  gfloat *boundary = mono_out + n_values;
  GslOscWave *wave = &osc->wave;
  
  /* FIXME: should we do gsl_fpu_setround() here? */
  
  pos_inc = gsl_dtoi (osc->last_freq_level * gsl_cent_factor (osc->config.fine_tune) * wave->freq_to_step);
  sync_pos = osc->config.phase * wave->phase_to_pos;
  posm_strength = pos_inc * osc->config.fm_strength;
  self_posm_strength = pos_inc * osc->config.self_fm_strength;
  
  /* do the mixing */
  do
    {
      gfloat v;
      
      /* handle syncs
       */
#if (ISYNC1_OSYNC0)		/* input sync only */
      {
	gfloat sync_level = *sync_in++;
	if_reject (GSL_SIGNAL_RAISING_EDGE (last_sync_level, sync_level))
	  cur_pos = sync_pos;
	last_sync_level = sync_level;
      }
#elif (ISYNC1_OSYNC1)		/* input and output sync */
      {
	gfloat sync_level = *sync_in++;
	if_reject (GSL_SIGNAL_RAISING_EDGE (last_sync_level, sync_level))
	  {
	    cur_pos = sync_pos;
	    *sync_out++ = 1.0;
	  }
	else /* figure output sync position */
	  {
	    guint is_sync = (sync_pos <= cur_pos) + (last_pos < sync_pos) + (cur_pos < last_pos);
	    *sync_out++ = is_sync >= 2 ? 1.0 : 0.0;
	  }
	last_sync_level = sync_level;
      }
#elif (ISYNC0_OSYNC1)		/* output sync only */
      {
	/* figure output sync position */
	guint is_sync = (sync_pos <= cur_pos) + (last_pos < sync_pos) + (cur_pos < last_pos);
	*sync_out++ = is_sync >= 2 ? 1.0 : 0.0;
      }
#endif
      
      /* track frequency changes
       */
#if (WITH_FREQ)
      {
	gdouble freq_level = *ifreq++;
	freq_level = GSL_SIGNAL_TO_FREQ (freq_level);
	if (GSL_SIGNAL_FREQ_CHANGED (last_freq_level, freq_level))
	  {
	    if_reject (freq_level <= wave->min_freq || freq_level > wave->max_freq)
	      {
		gdouble fcpos, flpos;
		const gfloat *orig_values = wave->values;

		fcpos = cur_pos * wave->ifrac_to_float;
		flpos = last_pos * wave->ifrac_to_float;
		gsl_osc_table_lookup (osc->config.table, freq_level, wave);
		if (orig_values != wave->values)	/* catch non-changes */
		  {
		    last_pos = flpos / wave->ifrac_to_float;
		    cur_pos = fcpos / wave->ifrac_to_float;
		    sync_pos = osc->config.phase * wave->phase_to_pos;
		    pos_inc = gsl_dtoi (freq_level * gsl_cent_factor (osc->config.fine_tune) * wave->freq_to_step);
#if (PULSE_OSC)
		    osc->last_pwm_level = 0;
		    osc_update_pwm_offset (osc, osc->last_pwm_level);
		    last_pwm_level = osc->last_pwm_level;
#endif
		  }
	      }
	    else
	      pos_inc = gsl_dtoi (freq_level * gsl_cent_factor (osc->config.fine_tune) * wave->freq_to_step);
	    posm_strength = pos_inc * osc->config.fm_strength;
	    self_posm_strength = pos_inc * osc->config.self_fm_strength;
	    last_freq_level = freq_level;
	  }
      }
#endif

      /* track pulse witdh modulation
       */
#if (WITH_PWM_MOD)
      {
	gfloat pwm_level = *pwm_in++;
	if (fabs (last_pwm_level - pwm_level) > 1.0 / 65536.0)
	  {
	    last_pwm_level = pwm_level;
	    osc_update_pwm_offset (osc, pwm_level);
	  }
      }
#endif

      /* output signal calculation
       */
#if (PULSE_OSC)		/* pulse width modulation oscillator */
      {
	guint32 tpos, ipos;
	tpos = cur_pos >> wave->n_frac_bits;
	ipos = (cur_pos - osc->pwm_offset) >> wave->n_frac_bits;
	v = wave->values[tpos] - wave->values[ipos];
	v = (v + osc->pwm_center) * osc->pwm_max;
      }
#else			/* table read out and linear ipol */
      {
	guint32 tpos, ifrac;
	gfloat ffrac, w;
	tpos = cur_pos >> wave->n_frac_bits;
	ifrac = cur_pos & wave->frac_bitmask;
	ffrac = ifrac * wave->ifrac_to_float;
	v = wave->values[tpos];
	w = wave->values[tpos + 1];
	v *= 1.0 - ffrac;
	w *= ffrac;
	v += w;
      }
#endif	/* v = value_out done */
      *mono_out++ = v;
      
      /* position increment
       */
#if (WITH_OSYNC)
      last_pos = cur_pos;
#endif
#if (WITH_SMOD)			/* self modulation */
      cur_pos += self_posm_strength * v;
#endif
#if (WITH_LMOD)			/* linear fm */
      {
	gfloat mod_level = *mod_in++;
	cur_pos += pos_inc + posm_strength * mod_level;
      }
#elif (WITH_EMOD)		/* exponential fm */
      {
	gfloat mod_level = *mod_in++;
	cur_pos += pos_inc * gsl_signal_exp2 (osc->config.fm_strength * mod_level);
      }
#else				/* no modulation */
      cur_pos += pos_inc;
#endif
    }
  while (mono_out < boundary);
  
  osc->last_pos = WITH_OSYNC ? last_pos : cur_pos;
  osc->cur_pos = cur_pos;
  osc->last_sync_level = last_sync_level;
  osc->last_freq_level = last_freq_level;
  osc->last_pwm_level = last_pwm_level;
}

#undef ISYNC1_OSYNC0
#undef ISYNC1_OSYNC1
#undef ISYNC0_OSYNC1
#undef WITH_OSYNC
#undef WITH_FREQ
#undef WITH_SMOD
#undef WITH_LMOD
#undef WITH_EMOD
#undef OSC_FLAGS