429 lines
10 KiB
C
429 lines
10 KiB
C
/*
|
|
waves: some oscillators, for fun
|
|
|
|
copyright 2017 by the mpg123 project, license: LGPL 2.1
|
|
|
|
This code does not care about efficiency constructing the period buffer,
|
|
as that is done only once at the beginning. Double precision
|
|
computations should be fine with software emulation.
|
|
|
|
There might be some slight pulsing from aliasing inside the buffer, where
|
|
there is a fractional number of samples per period and so some shidting
|
|
of sample point locations.
|
|
*/
|
|
|
|
#include "waves.h"
|
|
#include "debug.h"
|
|
|
|
/* Not depending on C99 math for these simple things, */
|
|
|
|
static const double freq_error = 1e-4;
|
|
static const double twopi = 2.0*3.14159265358979323846;
|
|
|
|
/* absolute value */
|
|
static double myabs(double a)
|
|
{
|
|
return a < 0 ? -a : a;
|
|
}
|
|
|
|
/* round floating point to size_t */
|
|
static size_t round2size(double a)
|
|
{
|
|
return a < 0 ? 0 : (size_t)(a+0.5);
|
|
}
|
|
|
|
/* fractional part, relating to frequencies (so long matches) */
|
|
static double myfrac(double a)
|
|
{
|
|
return a-(long)a;
|
|
}
|
|
|
|
/*
|
|
Given a set of wave frequencies, compute an approximate common
|
|
period for the combined signal. Invalid frequencies are set to
|
|
the error bound for some sanity.
|
|
*/
|
|
static double common_samples_per_period( long rate, size_t count
|
|
, double *freq, size_t size_limit )
|
|
{
|
|
double spp = 0;
|
|
size_t i;
|
|
for(i=0; i<count; ++i)
|
|
{
|
|
double sppi;
|
|
size_t periods = 1;
|
|
/* Limiting sensible frequency range. */
|
|
if(freq[i] < freq_error)
|
|
freq[i] = freq_error;
|
|
if(freq[i] > rate/2)
|
|
freq[i] = rate/2;
|
|
sppi = myabs((double)rate/freq[i]);
|
|
debug2("freq=%g sppi=%g", freq[i], sppi);
|
|
if(spp == 0)
|
|
spp = sppi;
|
|
while
|
|
(
|
|
periods*spp < size_limit &&
|
|
myabs( myfrac(periods*spp / sppi) ) > freq_error
|
|
)
|
|
periods++;
|
|
spp*=periods;
|
|
debug3( "samples_per_period + %f Hz = %g (%" SIZE_P " periods)"
|
|
, freq[i], spp, periods );
|
|
}
|
|
return spp;
|
|
}
|
|
|
|
/* Compute a good size of a table covering the common period for all waves. */
|
|
static size_t tablesize( long rate, size_t count
|
|
, double *freq, size_t size_limit )
|
|
{
|
|
size_t ts;
|
|
double samples_per_period;
|
|
size_t periods;
|
|
|
|
samples_per_period = common_samples_per_period( rate, count
|
|
, freq, size_limit );
|
|
periods = 1;
|
|
while
|
|
(
|
|
myabs( (double)(ts =
|
|
round2size(periods*samples_per_period))
|
|
- periods*samples_per_period
|
|
) / periods > freq_error*samples_per_period
|
|
&& periods*samples_per_period < size_limit
|
|
)
|
|
periods++;
|
|
/* Ensure that we got an even number of samples to accomodate the minimal */
|
|
/* sampling of a period. */
|
|
ts += ts % 2;
|
|
debug1("table size: %" SIZE_P, ts);
|
|
return ts;
|
|
}
|
|
|
|
/* The wave functions. Argument is the phase normalised to the period. */
|
|
/* The argument is guaranteed to be 0 <= p < 1. */
|
|
|
|
const char *wave_pattern_default = "sine";
|
|
const char *wave_pattern_list = "sine, square, triangle, sawtooth, gauss, pulse, shot";
|
|
|
|
/* _________ */
|
|
/* */
|
|
static double wave_none(double p)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
/* __ */
|
|
/* / \ */
|
|
/* \__/ */
|
|
static double wave_sine(double p)
|
|
{
|
|
return sin(twopi*p);
|
|
}
|
|
|
|
/* ___ */
|
|
/* ___| */
|
|
static double wave_square(double p)
|
|
{
|
|
return (myfrac(p) < 0.5 ? -1 : 1);
|
|
}
|
|
|
|
/* 1234 Avoid jump from zero at beginning. */
|
|
/* /\ */
|
|
/* \/ */
|
|
static double wave_triangle(double p)
|
|
{
|
|
return 4*p < 1
|
|
? 4*p /* 1 */
|
|
: ( 4*p < 3
|
|
? 2.-4*p /* 2 and 3 */
|
|
: -4+4*p /* 4 */
|
|
);
|
|
}
|
|
|
|
/* /| Avoid jump from zero ... */
|
|
/* |/ */
|
|
static double wave_sawtooth(double p)
|
|
{
|
|
return 2*p < 1 ? 2*p : -2+2*p;
|
|
}
|
|
|
|
/* _ */
|
|
/* __/ \__ */
|
|
/* */
|
|
static double wave_gauss(double p)
|
|
{
|
|
double v = p-0.5;
|
|
return exp(-30*v*v);
|
|
}
|
|
|
|
/* _ */
|
|
/* _/ -___ */
|
|
/* */
|
|
/* p**2*exp(-a*p**2) */
|
|
/* Scaling: maximum at sqrt(1/a), value 1/a*exp(-1). */
|
|
static double wave_pulse(double p)
|
|
{
|
|
return p*p*exp(-50*p*p)/0.00735758882342885;
|
|
}
|
|
|
|
/* _ */
|
|
/* / -___ */
|
|
/* */
|
|
/* p**2*exp(-a*p) */
|
|
/* Scaling: maximum at 4/a, value 4/a**2*exp(-2). */
|
|
static double wave_shot(double p)
|
|
{
|
|
return p*p*exp(-100*p)/5.41341132946451e-05;
|
|
}
|
|
|
|
/* Fill table with given periodic wave function. */
|
|
static void add_table( double *table, size_t samples
|
|
, long rate, double *freq, const char *wavetype, double phase)
|
|
{
|
|
size_t i, periods;
|
|
double spp, phaseoff;
|
|
double (*wave)(double);
|
|
debug3("add_table %" SIZE_P " %ld %g", samples, rate, *freq);
|
|
periods = round2size((double)samples*(*freq)/rate);
|
|
if(!periods)
|
|
periods = 1;
|
|
spp = (double)samples/periods;
|
|
/* 2 samples is absolute minimum. */
|
|
if(spp < 2)
|
|
spp = 2;
|
|
|
|
/* The actual frequency after the rounding of samples per period. */
|
|
*freq = (double)rate/spp;
|
|
|
|
/* Center samples in period, somewhat, to ensure getting extrema with very */
|
|
/* small sample counts (instead of zero at beginning and center). */
|
|
phaseoff = 1./(2*spp);
|
|
|
|
if(!wavetype)
|
|
wave = wave_none;
|
|
else if(!strcasecmp(wavetype, "sine"))
|
|
wave = wave_sine;
|
|
else if(!strcasecmp(wavetype, "square"))
|
|
wave = wave_square;
|
|
else if(!strcasecmp(wavetype, "triangle"))
|
|
wave = wave_triangle;
|
|
else if(!strcasecmp(wavetype, "sawtooth"))
|
|
wave = wave_sawtooth;
|
|
else if(!strcasecmp(wavetype, "gauss"))
|
|
wave = wave_gauss;
|
|
else if(!strcasecmp(wavetype, "pulse"))
|
|
wave = wave_pulse;
|
|
else if(!strcasecmp(wavetype, "shot"))
|
|
wave = wave_shot;
|
|
else
|
|
wave = wave_none;
|
|
|
|
debug3( "adding wave: %s @ %g Hz + %g"
|
|
, wavetype ? wavetype : "none", *freq, phase );
|
|
|
|
/*
|
|
compromise: smooth onset for low frequencies, but also good sampling
|
|
of high freqs extreme case: 2 samples per period should trigger
|
|
max/min amplitude, not both zero so, center the samples within one
|
|
period. That's achieved by adding 0.25 to the counter.
|
|
*/
|
|
if(phase >= 0)
|
|
for(i=0; i<samples; ++i)
|
|
table[i] *= wave(myfrac( ((double)i+phaseoff)/spp + phase));
|
|
else
|
|
for(i=0; i<samples; ++i)
|
|
table[i] *= wave(1.-myfrac( ((double)i+phaseoff)/spp - phase));
|
|
}
|
|
|
|
/* Construct the prototype table in double precision. */
|
|
static double* mytable( long rate, size_t count, double *freq
|
|
, const char** wavetype, double *phase
|
|
, size_t size_limit, size_t *samples)
|
|
{
|
|
size_t i;
|
|
double *table = NULL;
|
|
|
|
*samples = tablesize(rate, count, freq, size_limit);
|
|
debug1("computed table size: %" SIZE_P, *samples);
|
|
table = malloc(*samples*sizeof(double));
|
|
if(!table)
|
|
{
|
|
error("OOM!");
|
|
return NULL;
|
|
}
|
|
/* Initialise to zero. */
|
|
for(i=0; i<*samples; ++i)
|
|
table[i] = 1;
|
|
/* Add individual waves, with default parameters. */
|
|
for(i=0; i<count; ++i)
|
|
add_table(
|
|
table, *samples, rate, freq+i
|
|
, wavetype ? wavetype[i] : wave_pattern_default
|
|
, phase ? phase[i] : 0
|
|
);
|
|
/* Amplification could have caused clipping. */
|
|
for(i=0; i<*samples; ++i)
|
|
{
|
|
if(table[i] > 1.0)
|
|
table[i] = 1.0;
|
|
if(table[i] < -1.0)
|
|
table[i] = -1.0;
|
|
debug2("table[%" SIZE_P "]=%f", i, table[i]);
|
|
}
|
|
return table;
|
|
}
|
|
|
|
/* Some trivial conversion functions. No need for a library. */
|
|
|
|
/* All symmetric, +/- 2^n-1. */
|
|
#define CONV(name, type, maxval) \
|
|
static type name(double d) \
|
|
{ \
|
|
type imax = maxval; \
|
|
d *= imax; \
|
|
if(d>=0) \
|
|
{ \
|
|
d += 0.5; \
|
|
return d > imax \
|
|
? imax \
|
|
: (type)d; \
|
|
} \
|
|
else \
|
|
{ \
|
|
d -= 0.5; \
|
|
return d < -imax \
|
|
? -imax \
|
|
: (type)d; \
|
|
} \
|
|
}
|
|
|
|
CONV(d2s32, int32_t, 2147483647L)
|
|
CONV(d2s16, int16_t, 32767)
|
|
CONV(d2s8, char, 127)
|
|
|
|
#define HANDLE_OOM(p, h, p2) \
|
|
if(!(p)) \
|
|
{ \
|
|
error("OOM!"); \
|
|
if((p2)) free((p2)); \
|
|
return wave_table_del((h)); \
|
|
}
|
|
|
|
/* Build internal table, allocate external table, convert to that one, */
|
|
/* adjusting sample storage format and channel count. */
|
|
struct wave_table* wave_table_new(
|
|
long rate, int channels, int encoding
|
|
, size_t count, double *freq
|
|
, const char** wavetype, double *phase
|
|
, size_t size_limit
|
|
){
|
|
struct wave_table *handle;
|
|
double *dtable;
|
|
int c,i;
|
|
|
|
handle = malloc(sizeof(struct wave_table));
|
|
HANDLE_OOM(handle, handle, NULL)
|
|
|
|
handle->buf = NULL;
|
|
handle->fmt.rate = rate;
|
|
handle->fmt.channels = channels;
|
|
handle->fmt.encoding = encoding;
|
|
handle->samples = 0;
|
|
handle->offset = 0;
|
|
handle->count = count;
|
|
handle->freq = NULL;
|
|
|
|
handle->freq = malloc(sizeof(double)*count);
|
|
HANDLE_OOM(handle->freq, handle, NULL)
|
|
for(i=0; i<count; ++i)
|
|
handle->freq[i] = freq[i];
|
|
|
|
dtable = mytable( rate, count, handle->freq, wavetype, phase
|
|
, size_limit, &handle->samples );
|
|
HANDLE_OOM(dtable, handle, NULL)
|
|
handle->buf = malloc( handle->samples
|
|
* MPG123_SAMPLESIZE(encoding) * channels );
|
|
HANDLE_OOM(handle->buf, handle, dtable)
|
|
/* Now convert. */
|
|
for(c=0; c<channels; ++c)
|
|
{
|
|
switch(encoding)
|
|
{
|
|
/* Samples are clipped. Rounding a double below 1.0 to float cannot */
|
|
/* get larger than 1.0 since that is exact in single precision. */
|
|
case MPG123_ENC_FLOAT_64:
|
|
for(i=0; i<handle->samples; ++i)
|
|
((double*)handle->buf)[i*channels+c] = dtable[i];
|
|
break;
|
|
case MPG123_ENC_FLOAT_32:
|
|
for(i=0; i<handle->samples; ++i)
|
|
((float*)handle->buf)[i*channels+c] = dtable[i];
|
|
break;
|
|
case MPG123_ENC_SIGNED_32:
|
|
for(i=0; i<handle->samples; ++i)
|
|
((int32_t*)handle->buf)[i*channels+c] = d2s32(dtable[i]);
|
|
break;
|
|
case MPG123_ENC_SIGNED_16:
|
|
for(i=0; i<handle->samples; ++i)
|
|
((int16_t*)handle->buf)[i*channels+c] = d2s16(dtable[i]);
|
|
break;
|
|
case MPG123_ENC_SIGNED_8:
|
|
for(i=0; i<handle->samples; ++i)
|
|
((char*)handle->buf)[i*channels+c] = d2s8(dtable[i]);
|
|
break;
|
|
default:
|
|
error("unsupported encoding, choose f64, f32, s32, s16 or s8");
|
|
free(dtable);
|
|
return wave_table_del(handle);
|
|
}
|
|
}
|
|
free(dtable);
|
|
return handle;
|
|
}
|
|
|
|
void* wave_table_del(struct wave_table* handle)
|
|
{
|
|
if(handle)
|
|
{
|
|
if(handle->freq)
|
|
free(handle->freq);
|
|
if(handle->buf)
|
|
free(handle->buf);
|
|
free(handle);
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
/* Copy blocks from offset to end until desired amount is extracted. */
|
|
size_t wave_table_extract( struct wave_table *handle
|
|
, void *dest, size_t dest_samples )
|
|
{
|
|
char *cdest = (char*)dest; /* Want to do arithmetic. */
|
|
size_t framesize;
|
|
size_t extracted = 0;
|
|
|
|
if(!handle)
|
|
return 0;
|
|
|
|
framesize = MPG123_SAMPLESIZE(handle->fmt.encoding)
|
|
* handle->fmt.channels;
|
|
while(dest_samples)
|
|
{
|
|
size_t block = dest_samples > handle->samples - handle->offset
|
|
? handle->samples - handle->offset
|
|
: dest_samples;
|
|
debug1("block: %" SIZE_P, block);
|
|
memcpy( cdest, (char*)handle->buf+handle->offset*framesize
|
|
, framesize*block );
|
|
cdest += framesize*block;
|
|
handle->offset += block;
|
|
handle->offset %= handle->samples;
|
|
dest_samples -= block;
|
|
extracted += block;
|
|
}
|
|
debug1("extracted: %" SIZE_P, extracted);
|
|
return extracted;
|
|
}
|