sound, prepare FM filtering

This commit is contained in:
kub 2022-03-31 17:27:49 +00:00
parent 882f697ad4
commit e2e2b6ad1b
8 changed files with 1180 additions and 3 deletions

261
pico/sound/resampler.c Normal file
View file

@ -0,0 +1,261 @@
/* Configurable fixed point resampling SINC filter for mono and stereo audio.
*
* (C) 2022 kub
*
* This work is licensed under the terms of any of these licenses
* (at your option):
* - GNU GPL, version 2 or later.
* - MAME license.
* See COPYING file in the top-level directory.
*/
/* SINC filter generation taken from the blipper library, its license is:
*
* Copyright (C) 2013 - Hans-Kristian Arntzen
*
* Permission is hereby granted, free of charge,
* to any person obtaining a copy of this software and
* associated documentation files (the "Software"),
* to deal in the Software without restriction,
* including without limitation the rights to
* use, copy, modify, merge, publish, distribute, sublicense,
* and/or sell copies of the Software,
* and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included
* in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
* DAMAGES OR OTHER LIABILITY,
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
* IN THE SOFTWARE.
*/
#include <stdlib.h>
#include <stddef.h>
#include <string.h>
#include <math.h>
#include "../pico_types.h"
#include "resampler.h"
static double besseli0(double x)
{
unsigned i;
double sum = 0.0;
double factorial = 1.0;
double factorial_mult = 0.0;
double x_pow = 1.0;
double two_div_pow = 1.0;
double x_sqr = x * x;
/* Approximate. This is an infinite sum.
* Luckily, it converges rather fast. */
for (i = 0; i < 18; i++)
{
sum += x_pow * two_div_pow / (factorial * factorial);
factorial_mult += 1.0;
x_pow *= x_sqr;
two_div_pow *= 0.25;
factorial *= factorial_mult;
}
return sum;
}
static double sinc(double v)
{
if (fabs(v) < 0.00001)
return 1.0;
else
return sin(v) / v;
}
/* index range = [-1, 1) */
static double kaiser_window(double index, double beta)
{
return besseli0(beta * sqrt(1.0 - index * index));
}
/* Creates a polyphase SINC filter (:phases banks with :taps each)
* Interleaves the filter for cache coherency and possibilities for SIMD */
static s16 *create_sinc(unsigned phases, unsigned taps, double cutoff, double beta)
{
unsigned i, filter_len;
double sidelobes, window_mod, window_phase, sinc_phase;
s16 *filter;
double tap;
filter = (s16*)malloc(phases * taps * sizeof(*filter));
if (!filter)
return NULL;
sidelobes = taps / 2.0;
window_mod = 1.0 / kaiser_window(0.0, beta);
filter_len = phases * taps;
for (i = 0; i < filter_len; i++)
{
window_phase = (double)i / filter_len; /* [0, 1) */
window_phase = 2.0 * window_phase - 1.0; /* [-1, 1) */
sinc_phase = window_phase * sidelobes; /* [-taps / 2, taps / 2) */
tap = (cutoff * sinc(M_PI * sinc_phase * cutoff) *
kaiser_window(window_phase, beta) * window_mod);
/* assign taking filter bank interleaving into account:
* :phases banks of length :taps */
filter[(i%phases)*taps + (i/phases)] = tap * 0x7fff + 0.5;
}
return filter;
}
/* Public interface */
/* Release a resampler */
void resampler_free(resampler_t *rs)
{
if (rs)
{
free(rs->buffer);
free(rs->filter);
free(rs);
}
}
/* Create a resampler with upsampling factor :interpolation and downsampling
* factor :decimation, Kaiser windowed SINC polyphase FIR with bank size :taps.
* The created filter has a size of :taps*:interpolation for upsampling and
* :taps*:decimation for downsampling. :taps is limiting the cost per sample and
* should be big enough to avoid inaccuracy (>= 8, higher is more accurate).
* :cutoff is in [0..1] with 1 representing the Nyquist rate after decimation.
* :beta is the Kaiser window beta.
* :max_input is the maximum length in a resampler_update call */
resampler_t *resampler_new(unsigned taps, unsigned interpolation, unsigned decimation,
double cutoff, double beta, unsigned max_input, int stereo)
{
resampler_t *rs = NULL;
if (taps == 0 || interpolation == 0 || decimation == 0 || max_input == 0)
return NULL; /* invalid parameters */
rs = (resampler_t*)calloc(1, sizeof(*rs));
if (!rs)
return NULL; /* out of memory */
/* :cutoff is relative to the decimated frequency, but filtering is taking
* place at the interpolated frequency. It needs to be adapted if resampled
* rate is lower. Also needs more taps to keep the transistion band width */
if (decimation > interpolation) {
cutoff = cutoff * interpolation/decimation;
taps = taps * decimation/interpolation;
}
rs->interpolation = interpolation;
rs->decimation = decimation;
rs->taps = taps;
/* optimizers for resampler_update: */
rs->interp_inv = 0x100000000ULL / interpolation;
rs->ratio_int = decimation / interpolation;
rs->filter = create_sinc(interpolation, taps, cutoff, beta);
if (!rs->filter)
goto error;
rs->stereo = !!stereo;
rs->buffer_sz = (max_input * decimation/interpolation) + decimation + 1;
rs->buffer = calloc(1, rs->buffer_sz * (stereo ? 2:1) * sizeof(*rs->buffer));
if (!rs->buffer)
goto error;
return rs;
error:
if (rs->filter)
free(rs->filter);
if (rs->buffer)
free(rs->buffer);
free(rs);
return NULL;
}
/* Obtain :length resampled audio frames in :buffer. Use :get_samples to obtain
* the needed amount of input samples */
void resampler_update(resampler_t *rs, s32 *buffer, int length,
void (*get_samples)(s32 *buffer, int length, int stereo))
{
s16 *u;
s32 *p, *q = buffer;
int spf = (rs->stereo?2:1);
s32 inlen;
s32 l, r;
int n, i;
if (length <= 0) return;
/* compute samples needed on input side:
* inlen = (length*decimation + interpolation-phase) / interpolation */
n = length*rs->decimation + rs->interpolation-rs->phase;
inlen = ((u64)n * rs->interp_inv) >> 32; /* input samples, n/interpolation */
if (inlen * rs->interpolation < n - rs->interpolation) inlen++; /* rounding */
/* reset buffer to start if the input doesn't fit into the buffer */
if (rs->buffer_idx + inlen+rs->taps >= rs->buffer_sz) {
memcpy(rs->buffer, rs->buffer + rs->buffer_idx*spf, rs->taps*spf*sizeof(*rs->buffer));
rs->buffer_idx = 0;
}
p = rs->buffer + rs->buffer_idx*spf;
/* generate input samples */
if (inlen > 0)
get_samples(p + rs->taps*spf, inlen, rs->stereo);
if (rs->stereo) {
while (--length >= 0) {
/* compute filter output */
u = rs->filter + (rs->phase * rs->taps);
for (i = 0, l = r = 0; i < rs->taps-1; i += 2)
{ n = *u++; l += n * p[2*i ]; r += n * p[2*i+1];
n = *u++; l += n * p[2*i+2]; r += n * p[2*i+3]; }
if (i < rs->taps)
{ n = *u++; l += n * p[2*i ]; r += n * p[2*i+1]; }
*q++ = l >> 16, *q++ = r >> 16;
/* advance position to next sample */
rs->phase -= rs->decimation;
// if (rs->ratio_int) {
rs->phase += rs->ratio_int*rs->interpolation,
p += 2*rs->ratio_int, rs->buffer_idx += rs->ratio_int;
// }
if (rs->phase < 0)
{ rs->phase += rs->interpolation, p += 2, rs->buffer_idx ++; }
}
} else {
while (--length >= 0) {
/* compute filter output */
u = rs->filter + (rs->phase * rs->taps);
for (i = 0, l = r = 0; i < rs->taps-1; i += 2)
{ n = *u++; l += n * p[ i ];
n = *u++; l += n * p[ i+1]; }
if (i < rs->taps)
{ n = *u++; l += n * p[ i ]; }
*q++ = l >> 16;
/* advance position to next sample */
rs->phase -= rs->decimation;
// if (rs->ratio_int) {
rs->phase += rs->ratio_int*rs->interpolation,
p += rs->ratio_int, rs->buffer_idx += rs->ratio_int;
// }
if (rs->phase < 0)
{ rs->phase += rs->interpolation, p += 1, rs->buffer_idx ++; }
}
}
}