/* 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.
 */
#include "gsloscillator.h"

#include "gslsignal.h"

#define	SIGNAL_LEVEL_INVAL	(-2.0)	/* trigger level-changed checks */


/* --- functions --- */
static inline void
osc_update_pwm_offset (GslOscData *osc,
		       gfloat	   pulse_mod) /* -1..+1 */
{
  guint32 maxp_offs, minp_offs, mpos, tpos;
  gfloat min, max, foffset;

  /* figure actual pulse width (0..1) */
  foffset = osc->config.pulse_width;	/* 0..1 */
  foffset += pulse_mod * osc->config.pulse_mod_strength;
  foffset = CLAMP (foffset, 0.0, 1.0);

  /* calculate pulse scaling range for this offset */
  osc->pwm_offset = foffset * osc->wave.n_values;
  osc->pwm_offset <<= osc->wave.n_frac_bits;

  maxp_offs = (osc->wave.min_pos + osc->wave.n_values + osc->wave.max_pos) << (osc->wave.n_frac_bits - 1);
  minp_offs = (osc->wave.max_pos + osc->wave.min_pos) << (osc->wave.n_frac_bits - 1);
  
  mpos = maxp_offs + (osc->pwm_offset >> 1);
  tpos = mpos >> osc->wave.n_frac_bits;
  max = osc->wave.values[tpos];
  mpos -= osc->pwm_offset;
  tpos = mpos >> osc->wave.n_frac_bits;
  max -= osc->wave.values[tpos];
  mpos = minp_offs + (osc->pwm_offset >> 1);
  tpos = mpos >> osc->wave.n_frac_bits;
  min = osc->wave.values[tpos];
  mpos -= osc->pwm_offset;
  tpos = mpos >> osc->wave.n_frac_bits;
  min -= osc->wave.values[tpos];
  osc->pwm_center = (min + max) / -2.0;
  min = fabs (min + osc->pwm_center);
  max = fabs (max + osc->pwm_center);
  max = MAX (max, min);
  if_reject (max < GSL_FLOAT_MIN_NORMAL)
    {
      osc->pwm_center = foffset < 0.5 ? -1.0 : +1.0;
      osc->pwm_max = 1.0;
    }
  else
    osc->pwm_max = 1.0 / max;
}


/* --- generate processing function variants --- */
#define OSC_FLAG_INVAL		(0xffffffff)
#define OSC_FLAG_ISYNC		(1)
#define OSC_FLAG_OSYNC		(2)
#define OSC_FLAG_FREQ		(4)
#define OSC_FLAG_SELF_MOD	(8)
#define	OSC_FLAG_LINEAR_MOD	(16)
#define	OSC_FLAG_EXP_MOD	(32)
#define	OSC_FLAG_PWM_MOD	(64)
#define	OSC_FLAG_PULSE_OSC	(128)

/* normal oscillator variants */
#define	OSC_INCLUDER_FLAGS	0
#define	GSL_INCLUDER_FIRST_CASE	0
#define	GSL_INCLUDER_LAST_CASE	63
#define	GSL_INCLUDER_NAME	oscillator_process_normal
#define	GSL_INCLUDER_TABLE	static void (*osc_process_table[]) (GslOscData*,guint, \
                                const gfloat*,const gfloat*,const gfloat*, \
		                const gfloat*,gfloat*,gfloat*)
#define	GSL_INCLUDER_FILE	"gsloscillator-aux.c"
#include "gslincluder.c"
#undef	OSC_INCLUDER_FLAGS

/* pulse width modulation oscillator variants */
#define	OSC_INCLUDER_FLAGS	OSC_FLAG_PULSE_OSC
#define	GSL_INCLUDER_FIRST_CASE	0
#define	GSL_INCLUDER_LAST_CASE	127
#define	GSL_INCLUDER_NAME	oscillator_process_pulse
#define	GSL_INCLUDER_TABLE	static void (*osc_process_pulse_table[]) (GslOscData*,guint, \
                                const gfloat*,const gfloat*,const gfloat*, \
		                const gfloat*,gfloat*,gfloat*)
#define	GSL_INCLUDER_FILE	"gsloscillator-aux.c"
#include "gslincluder.c"
#undef	OSC_INCLUDER_FLAGS


/* --- functions --- */
static inline void
osc_process (GslOscData   *osc,
	     guint         n_values,
	     guint	   mode,
	     const gfloat *ifreq,
	     const gfloat *imod,
	     const gfloat *isync,
	     const gfloat *ipwm,
	     gfloat       *mono_out,
	     gfloat       *sync_out)
{
  mode |= isync ? OSC_FLAG_ISYNC : 0;
  mode |= sync_out ? OSC_FLAG_OSYNC : 0;
  mode |= ifreq ? OSC_FLAG_FREQ : 0;
  if (osc->config.pulse_mod_strength > GSL_FLOAT_MIN_NORMAL)
    mode |= ipwm ? OSC_FLAG_PWM_MOD : 0;
  if (osc->config.self_fm_strength > GSL_FLOAT_MIN_NORMAL)
    mode |= OSC_FLAG_SELF_MOD;
  if (imod && osc->config.exponential_fm)
    mode |= OSC_FLAG_EXP_MOD;
  else if (imod)
    mode |= OSC_FLAG_LINEAR_MOD;
  
  if_reject (mode != osc->last_mode)
    {
      guint change_mask = osc->last_mode >= OSC_FLAG_INVAL ? OSC_FLAG_INVAL : osc->last_mode ^ mode;
      
      if (change_mask & OSC_FLAG_FREQ)
	{
	  gdouble fcpos, flpos;

	  fcpos = osc->cur_pos * osc->wave.ifrac_to_float;
	  flpos = osc->last_pos * osc->wave.ifrac_to_float;
	  osc->last_freq_level = osc->config.cfreq;
	  gsl_osc_table_lookup (osc->config.table, osc->last_freq_level, &osc->wave);
	  osc->last_pos = flpos / osc->wave.ifrac_to_float;
	  osc->cur_pos = fcpos / osc->wave.ifrac_to_float;
	}
      if (!(mode & OSC_FLAG_ISYNC))
	osc->last_sync_level = 0;
      if (mode & OSC_FLAG_PULSE_OSC)
	{
	  osc->last_pwm_level = 0;
	  osc_update_pwm_offset (osc, osc->last_pwm_level);
	}
      osc->last_mode = mode;
    }
  
  /* invoke generated function variant */
  if (mode & OSC_FLAG_PULSE_OSC)
    osc_process_pulse_table[mode & ~OSC_FLAG_PULSE_OSC] (osc, n_values,
							 ifreq, imod, isync, ipwm,
							 mono_out, sync_out);
  else
    osc_process_table[mode] (osc, n_values,
			     ifreq, imod, isync, NULL,
			     mono_out, sync_out);
}

void
gsl_osc_process (GslOscData   *osc,
		 guint         n_values,
		 const gfloat *ifreq,
		 const gfloat *imod,
		 const gfloat *isync,
		 gfloat       *mono_out,
		 gfloat       *sync_out)
{
  g_return_if_fail (osc != NULL);
  g_return_if_fail (n_values > 0);
  g_return_if_fail (mono_out != NULL);

  if (osc->last_mode & OSC_FLAG_PULSE_OSC)
    osc->last_mode = OSC_FLAG_INVAL;
  osc_process (osc, n_values, 0,
	       ifreq, imod, isync, NULL,
	       mono_out, sync_out);
}

void
gsl_osc_process_pulse (GslOscData   *osc,
		       guint         n_values,
		       const gfloat *ifreq,
		       const gfloat *imod,
		       const gfloat *isync,
		       const gfloat *ipwm,
		       gfloat       *mono_out,
		       gfloat       *sync_out)
{
  g_return_if_fail (osc != NULL);
  g_return_if_fail (n_values > 0);
  g_return_if_fail (mono_out != NULL);

  if (!(osc->last_mode & OSC_FLAG_PULSE_OSC))
    osc->last_mode = OSC_FLAG_INVAL;
  osc_process (osc, n_values, OSC_FLAG_PULSE_OSC,
	       ifreq, imod, isync, ipwm,
	       mono_out, sync_out);
}

void
gsl_osc_config (GslOscData   *osc,
		GslOscConfig *config)
{
  g_return_if_fail (osc != NULL);
  g_return_if_fail (config != NULL);
  g_return_if_fail (config->table != NULL);

  osc->config = *config;
  osc->last_mode = OSC_FLAG_INVAL;
}
