You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
627 lines
17 KiB
627 lines
17 KiB
/* 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 tqcontains 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_bittqmask = 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++)
|
|
{
|
|
register 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);
|
|
}
|