/* GSL - Generic Sound Layer
 * Copyright (C) 2001-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 "gslosctable.h"

#include <string.h>
#include "gbsearcharray.h"
#include "gslcommon.h"
#include "gslmath.h"
#include "gslfft.h"


#define OSC_DEBUG		GSL_DEBUG_FUNCTION (GSL_MSG_OSC, G_STRLOC)

#define	OSC_FREQ_EPSILON	(1e-3)	/* range within which frequencies are "equal" */

/* compare mfreqs against each other, use an arbitrary sample rate
 * for which OSC_FREQ_EPSILON makes sense
 */
#define	CACHE_MATCH_FREQ(usr_mfreq, cache_mfreq) \
  (fabs ((cache_mfreq) * 44107 - (usr_mfreq) * 44107) < OSC_FREQ_EPSILON)


/* --- structures --- */
typedef struct
{
  /* main key (osc and cache tables) */
  gfloat         mfreq;			/* [0..0.5], mix_freq relative */
  /* secondary key (cache tables) */
  GslOscWaveForm wave_form;
  guint8	*filter_func;		/* just here for key indexing */
  /* data */
  guint		 ref_count;
  guint		 min_pos, max_pos;	/* pulse extension */
  guint          n_values;
  const gfloat   values[1];		/* flexible array */
} OscTableEntry;


/* --- prototypes --- */
static gint	cache_table_entry_locs_cmp	(gconstpointer	bsearch_node1, /* key */
						 gconstpointer	bsearch_node2);
static gint	osc_table_entry_locs_cmp	(gconstpointer	bsearch_node1, /* key */
						 gconstpointer	bsearch_node2);
static void	osc_wave_extrema_pos		(guint		n_values,
						 const gfloat *values,
						 guint        *minp_p,
						 guint        *maxp_p);
void		gsl_osc_cache_debug_dump	(void);


/* --- variables --- */
static GBSearchArray       *cache_entries = NULL;
static const GBSearchConfig cache_taconfig = {
  sizeof (OscTableEntry*),
  cache_table_entry_locs_cmp,
  0
};
static const GBSearchConfig osc_taconfig = {
  sizeof (OscTableEntry*),
  osc_table_entry_locs_cmp,
  0
};


/* --- functions --- */
static gint
cache_table_entry_locs_cmp (gconstpointer bsearch_node1, /* key */
			    gconstpointer bsearch_node2)
{
  const OscTableEntry * const *ep1 = bsearch_node1;
  const OscTableEntry * const *ep2 = bsearch_node2;
  const OscTableEntry *e1 = *ep1;
  const OscTableEntry *e2 = *ep2;
  
  if (e1->wave_form == e2->wave_form)
    {
      if (e1->filter_func == e2->filter_func)
	return G_BSEARCH_ARRAY_CMP (e1->mfreq, e2->mfreq);
      else
	return e1->filter_func > e2->filter_func ? 1 : -1;
    }
  else
    return e1->wave_form > e2->wave_form ? 1 : -1;
}

static gint
osc_table_entry_locs_cmp (gconstpointer bsearch_node1, /* key */
			  gconstpointer bsearch_node2)
{
  const OscTableEntry * const *ep1 = bsearch_node1;
  const OscTableEntry * const *ep2 = bsearch_node2;
  const OscTableEntry *e1 = *ep1;
  const OscTableEntry *e2 = *ep2;
  
  return G_BSEARCH_ARRAY_CMP (e1->mfreq, e2->mfreq);
}

static OscTableEntry*
cache_table_entry_lookup_best (GslOscWaveForm wave_form,
			       guint8*        filter_func,
			       gfloat         mfreq)
{
  OscTableEntry key, *k = &key, **ep1 = NULL, **ep2, **ep3 = NULL;
  
  key.mfreq = mfreq;
  key.wave_form = wave_form;
  key.filter_func = filter_func;
  
  /* get exact match or a match which is one off into either direction */
  ep2 = g_bsearch_array_lookup_sibling (cache_entries, &cache_taconfig, &k);
  if (ep2)
    {
      guint i = g_bsearch_array_get_index (cache_entries, &cache_taconfig, ep2);
      
      /* get siblings */
      if (i > 0)
	ep1 = g_bsearch_array_get_nth (cache_entries, &cache_taconfig, i - 1);
      if (i + 1 < g_bsearch_array_get_n_nodes (cache_entries))
	ep3 = g_bsearch_array_get_nth (cache_entries, &cache_taconfig, i + 1);
      
      /* get rid of invalid matches, i.e. ones with:
       * - a different wave
       * - a different filter
       * - a filter wider than required
       */
      if (ep1 && ((*ep1)->wave_form != wave_form ||
		  (*ep1)->filter_func != filter_func ||
		  (*ep1)->mfreq < mfreq))
	ep1 = NULL;
      if (ep3 && ((*ep3)->wave_form != wave_form ||
		  (*ep3)->filter_func != filter_func ||
		  (*ep3)->mfreq < mfreq))
	ep3 = NULL;
      if ((*ep2)->wave_form != wave_form ||
	  (*ep2)->filter_func != filter_func ||
	  (*ep2)->mfreq < mfreq)
	{
	  /* collapse siblings, so that, if we have valid matches, ep2 is amongst them */
	  if (ep1)
	    {
	      ep2 = ep1;
	      ep1 = NULL;
	    }
	  else if (ep3)
	    {
	      ep2 = ep3;
	      ep3 = NULL;
	    }
	  else
	    ep2 = NULL;	/* no valid match at all */
	}
    }
  
  /* now figure best out of valid siblings */
  if (ep2)
    {
      if (ep1 && fabs ((*ep1)->mfreq - mfreq) < fabs ((*ep2)->mfreq - mfreq))
	ep2 = ep1;
      if (ep3 && fabs ((*ep3)->mfreq - mfreq) < fabs ((*ep2)->mfreq - mfreq))
	ep2 = ep3;
    }
  return ep2 ? *ep2 : NULL;
}

static OscTableEntry*
osc_table_entry_lookup_best (const GslOscTable *table,
			     gfloat             mfreq,
			     gfloat	       *min_mfreq)
{
  OscTableEntry key, *k = &key, **ep;
  guint i;
  
  /* get exact match or a match which is one off into either direction */
  key.mfreq = mfreq;
  ep = g_bsearch_array_lookup_sibling (table->entry_array, &osc_taconfig, &k);
  if_reject (!ep)
    return NULL;	/* ugh, bad */
  
  if (mfreq > (*ep)->mfreq)	/* need better filter */
    {
      i = g_bsearch_array_get_index (table->entry_array, &osc_taconfig, ep);
      if (i + 1 < g_bsearch_array_get_n_nodes (table->entry_array))
	ep = g_bsearch_array_get_nth (table->entry_array, &osc_taconfig, i + 1);
      else	/* bad, might cause aliasing */
	OSC_DEBUG ("lookup mismatch, aliasing possible: want_freq=%f got_freq=%f",
		   mfreq * table->mix_freq, (*ep)->mfreq * table->mix_freq);
    }
  
  if (min_mfreq)
    {
      /* fetch mfreq from previous */
      i = g_bsearch_array_get_index (table->entry_array, &osc_taconfig, ep);
      if (i > 0)
	{
	  OscTableEntry **tp = g_bsearch_array_get_nth (table->entry_array, &osc_taconfig, i - 1);
	  
	  *min_mfreq = (*tp)->mfreq;
	}
      else
	*min_mfreq = 0;
    }
  
  return *ep;
}

static guint
wave_table_size (GslOscWaveForm wave_form,
		 gfloat         mfreq)
{
  /* have to return power of 2, and honour 8 <= size */
  
  /* FIXME: decide on other table sizes
  10000: 256
    5000: 512
    2500: 1024
    1250: 2048
    GSL_OSC_WAVE_SAW_FALL always huge buffers to guarantee pulse width stepping granularity
  */

  if (wave_form == GSL_OSC_WAVE_SAW_FALL)
    return 8192;

  return 2048;
}

static void
fft_filter (guint    n_values,
	    gfloat  *values,	/* [0..n_values], n_values/2 complex values */
	    gdouble  scale_window,
	    double (*window) (double))
{
  guint i;

  n_values >>= 1;
  scale_window /= (gdouble) n_values;
  for (i = 0; i <= n_values; i++)
    {
      gdouble w = window (i * scale_window);
      values[i * 2] *= w;
      values[i * 2 + 1] *= w;
    }
}

static OscTableEntry*
cache_table_ref_entry (GslOscWaveForm wave_form,
		       double       (*filter_func) (double),
		       gfloat         mfreq)
{
  OscTableEntry *e = cache_table_entry_lookup_best (wave_form, (guint8*) filter_func, mfreq);
  
  if (e && !CACHE_MATCH_FREQ (mfreq, e->mfreq))
    e = NULL;
  if (!e)
    {
      guint size = wave_table_size (wave_form, mfreq);
      gfloat *values, *fft, step, min, max;

      /* size:
       * - OscTableEntry already contains the first float values
       * - we need n_values+1 adressable floats to provide values[0] == values[n_values]
       */
      e = g_malloc (sizeof (OscTableEntry) + sizeof (gfloat) * size);
      values = (gfloat*) &e->values[0];
      e->wave_form = wave_form;
      e->filter_func = (guint8*) filter_func;
      e->mfreq = mfreq;
      e->ref_count = 1;
      e->n_values = size;
      gsl_osc_wave_fill_buffer (e->wave_form, e->n_values, values);
      
      /* filter wave accordingly */
      gsl_osc_wave_extrema (e->n_values, values, &min, &max);
      fft = g_new (gfloat, e->n_values + 2);	/* [0..n_values] for n_values/2 complex freqs */
      gsl_power2_fftar_simple (e->n_values, values, fft);
      step = e->mfreq * (gdouble) e->n_values;
      fft_filter (e->n_values, fft, step, filter_func);
      gsl_power2_fftsr_simple (e->n_values, fft, values);
      g_free (fft);
      gsl_osc_wave_normalize (e->n_values, values, (min + max) / 2, max);

      /* provide values[0]==values[n_values] */
      values[e->n_values] = values[0];

      /* pulse min/max pos extension */
      osc_wave_extrema_pos (e->n_values, values, &e->min_pos, &e->max_pos);

      /* insert into cache */
      cache_entries = g_bsearch_array_insert (cache_entries, &cache_taconfig, &e);
    }
  else
    e->ref_count++;
  return e;
}

static void
cache_table_unref_entry (OscTableEntry *e)
{
  g_return_if_fail (e->ref_count > 0);
  
  e->ref_count -= 1;
  if (e->ref_count == 0)
    {
      OscTableEntry **ep;
      guint i;
      
      ep = g_bsearch_array_lookup (cache_entries, &cache_taconfig, &e);
      i = g_bsearch_array_get_index (cache_entries, &cache_taconfig, ep);
      cache_entries = g_bsearch_array_remove (cache_entries, &cache_taconfig, i);
    }
}

GslOscTable*
gsl_osc_table_create (gfloat         mix_freq,
		      GslOscWaveForm wave_form,
		      double       (*filter_func) (double),
		      guint          n_freqs,
		      const gfloat  *freqs)
{
  GslOscTable *table;
  gfloat nyquist;
  guint i;
  
  g_return_val_if_fail (mix_freq > 0, NULL);
  g_return_val_if_fail (n_freqs > 0, NULL);
  g_return_val_if_fail (freqs != NULL, NULL);
  
  if (!cache_entries)
    cache_entries = g_bsearch_array_create (&cache_taconfig);
  
  table = gsl_new_struct (GslOscTable, 1);
  table->mix_freq = mix_freq;
  table->wave_form = wave_form;
  table->entry_array = g_bsearch_array_create (&osc_taconfig);
  nyquist = table->mix_freq * 0.5;
  if (wave_form == GSL_OSC_WAVE_PULSE_SAW)
    wave_form = GSL_OSC_WAVE_SAW_FALL;
  for (i = 0; i < n_freqs; i++)
    {
      OscTableEntry *e;
      gdouble mfreq = MIN (nyquist, freqs[i]);
      
      mfreq /= table->mix_freq;
      e = osc_table_entry_lookup_best (table, mfreq, NULL);
      if (!e || fabs (e->mfreq * table->mix_freq - mfreq * table->mix_freq) > OSC_FREQ_EPSILON)
	{
	  e = cache_table_ref_entry (wave_form, filter_func, mfreq);
	  table->entry_array = g_bsearch_array_insert (table->entry_array, &osc_taconfig, &e);
	}
      else if (e)
	OSC_DEBUG ("not inserting existing entry (freq=%f) for freq %f (nyquist=%f)",
		   e->mfreq * table->mix_freq, mfreq * table->mix_freq, nyquist);
    }
  
  return table;
}

void
gsl_osc_table_lookup (const GslOscTable	*table,
		      gfloat		 freq,
		      GslOscWave	*wave)
{
  OscTableEntry *e;
  gfloat mfreq, min_mfreq;

  g_return_if_fail (table != NULL);
  g_return_if_fail (wave != NULL);
  
  mfreq = freq / table->mix_freq;
  e = osc_table_entry_lookup_best (table, mfreq, &min_mfreq);
  if (e)
    {
      guint32 int_one;
      gfloat float_one;

      wave->min_freq = min_mfreq * table->mix_freq;
      wave->max_freq = e->mfreq * table->mix_freq;
      wave->n_values = e->n_values;
      wave->values = e->values;
      wave->n_frac_bits = g_bit_storage (wave->n_values - 1);
      wave->n_frac_bits = 32 - wave->n_frac_bits;
      int_one = 1 << wave->n_frac_bits;
      wave->frac_bitmask = int_one - 1;
      float_one = int_one;
      wave->freq_to_step = float_one * wave->n_values / table->mix_freq;
      wave->phase_to_pos = wave->n_values * float_one;
      wave->ifrac_to_float = 1.0 / float_one;
      /* pulse min/max pos extension */
      wave->min_pos = e->min_pos;
      wave->max_pos = e->max_pos;
    }
  else
    {
      /* shouldn't happen */
      OSC_DEBUG ("table lookup revealed NULL, empty table?");
      memset (wave, 0, sizeof (*wave));
    }
}

void
gsl_osc_table_free (GslOscTable *table)
{
  guint n;
  
  g_return_if_fail (table != NULL);
  
  n = g_bsearch_array_get_n_nodes (table->entry_array);
  while (n--)
    {
      OscTableEntry **ep;
      
      ep = g_bsearch_array_get_nth (table->entry_array, &osc_taconfig, n);
      cache_table_unref_entry (*ep);
      table->entry_array = g_bsearch_array_remove (table->entry_array, &osc_taconfig, n);
    }
  g_bsearch_array_free (table->entry_array, &osc_taconfig);
  gsl_delete_struct (GslOscTable, table);
}

void
gsl_osc_cache_debug_dump (void)
{
  OSC_DEBUG ("left in cache: %u", g_bsearch_array_get_n_nodes (cache_entries));
}

void
gsl_osc_wave_fill_buffer (GslOscWaveForm type,
			  guint	         n_values,
			  gfloat	*values)
{
  gdouble max = n_values, hmax = max * 0.5, qmax = n_values * 0.25;
  gint i, half = n_values / 2, quarter = half / 2;
  
  switch (type)
    {
      gdouble frac, pos;
    case GSL_OSC_WAVE_SINE:
      for (i = 0; i < n_values; i++)
	{
	  frac = ((gdouble) i) / max; /* [0..1[ */
	  pos = frac * 2. * GSL_PI;
	  values[i] = sin (pos);
	}
      break;
    case GSL_OSC_WAVE_SAW_RISE:
      for (i = 0; i < n_values; i++)
	{
	  frac = ((gdouble) i) / max; /* [0..1[ */
	  values[i] = 2.0 * frac - 1.0;
	}
      break;
    case GSL_OSC_WAVE_SAW_FALL:
      for (i = 0; i < n_values; i++)
	{
	  frac = ((gdouble) i) / max; /* [0..1[ */
	  values[i] = 1.0 - 2.0 * frac;
	}
      break;
    case GSL_OSC_WAVE_PEAK_RISE:	/* spaced saw */
      for (i = 0; i < half; i++)
	{
	  frac = ((gdouble) i) / hmax;
          values[i] = 2.0 * frac - 1.0;
	}
      for (; i < n_values; i++)
	values[i] = -1.0;
      break;
    case GSL_OSC_WAVE_PEAK_FALL:	/* spaced saw */
      for (i = 0; i < half; i++)
	{
	  frac = ((gdouble) i) / hmax;
          values[i] = 1.0 - 2.0 * frac;
	}
      for (; i < n_values; i++)
	values[i] = -1.0;
      break;
    case GSL_OSC_WAVE_TRIANGLE:
      for (i = 0; i < quarter; i++)
	{
	  frac = ((gdouble) i) / qmax;
	  values[i] = frac;
	}
      for (; i < half + quarter; i++)
	{
	  frac = ((gdouble) i - quarter) / hmax;
          values[i] = 1.0 - 2.0 * frac;
	}
      for (; i < n_values; i++)
	{
	  frac = ((gdouble) i - half - quarter) / qmax;
	  values[i] = frac - 1.0;
	}
      break;
    case GSL_OSC_WAVE_MOOG_SAW:
      for (i = 0; i < half; i++)
	{
	  frac = ((gdouble) i) / hmax;
          values[i] = 2.0 * frac - 1.0;
	}
      for (; i < n_values; i++)
	{
	  frac = ((gdouble) i) / max;
          values[i] = 1.0 - 2.0 * frac;
	}
      break;
    case GSL_OSC_WAVE_SQUARE:
      for (i = 0; i < half; i++)
	values[i] = 1.0;
      for (; i < n_values; i++)
	values[i] = -1.0;
      break;
    default:
      g_critical ("%s: invalid wave form id (%u)", G_STRLOC, type);
    case GSL_OSC_WAVE_NONE:
      for (i = 0; i < n_values; i++)
	values[i] = 0;
      break;
    }
}

static void
osc_wave_extrema_pos (guint          n_values,
		      const gfloat *values,
		      guint        *minp_p,
		      guint        *maxp_p)
{
  guint i, minp = 0, maxp = 0;
  gfloat min = values[0], max = min;

  for (i = 1; i < n_values; i++)
    {
      if (values[i] > max)
	{
	  max = values[i];
	  maxp = i;
	}
      else if (values[i] < min)
	{
	  min = values[i];
	  minp = i;
	}
    }
  *minp_p = minp;
  *maxp_p = maxp;
}

void
gsl_osc_wave_extrema (guint         n_values,
		      const gfloat *values,
		      gfloat       *min_p,
		      gfloat       *max_p)
{
  guint minp, maxp;

  g_return_if_fail (n_values > 0 && values != NULL && min_p != NULL && max_p != NULL);

  osc_wave_extrema_pos (n_values, values, &minp, &maxp);
  *min_p = values[minp];
  *max_p = values[maxp];
}

void
gsl_osc_wave_adjust_range (guint   n_values,
			   gfloat *values,
			   gfloat  min,
			   gfloat  max,
			   gfloat  new_center,
			   gfloat  new_max)
{
  gfloat center;
  guint i;
  
  g_return_if_fail (n_values > 0 && values != NULL);

  center = (min + max) / 2;
  center = new_center - center;
  min = fabs (min + center);
  max = fabs (max + center);
  if (min > max)
    max = min;
  if (max > GSL_FLOAT_MIN_NORMAL)
    max = new_max / max;
  else
    max = 0;
  for (i = 0; i < n_values; i++)
    values[i] = (values[i] + center) * max;
}

void
gsl_osc_wave_normalize (guint   n_values,
			gfloat *values,
			gfloat  new_center,
			gfloat  new_max)
{
  gfloat min, max;
  guint i;

  g_return_if_fail (n_values > 0 && values != NULL);

  min = values[0];
  max = min;
  for (i = 1; i < n_values; i++)
    {
      gfloat v = values[i];

      max = MAX (max, v);
      min = MIN (min, v);
    }

  gsl_osc_wave_adjust_range (n_values, values, min, max, new_center, new_max);
}
