filter-chain: move the filter-graph to plugins

This commit is contained in:
Wim Taymans 2024-11-13 11:10:49 +01:00
parent 2e157f7248
commit 567b484386
27 changed files with 122 additions and 132 deletions

View file

@ -71,122 +71,15 @@ pipewire_module_loopback = shared_library('pipewire-module-loopback',
dependencies : [spa_dep, mathlib, dl_lib, pipewire_dep],
)
plugin_dependencies = []
if get_option('spa-plugins').allowed()
plugin_dependencies += audioconvert_dep
endif
simd_cargs = []
simd_dependencies = []
if have_sse
filter_graph_sse = static_library('filter_graph_sse',
['module-filter-chain/pffft.c',
'module-filter-chain/audio-dsp-sse.c' ],
include_directories : [configinc],
c_args : [sse_args, '-O3', '-DHAVE_SSE'],
dependencies : [ spa_dep ],
install : false
)
simd_cargs += ['-DHAVE_SSE']
simd_dependencies += filter_graph_sse
endif
if have_avx
filter_graph_avx = static_library('filter_graph_avx',
['module-filter-chain/audio-dsp-avx.c' ],
include_directories : [configinc],
c_args : [avx_args, fma_args,'-O3', '-DHAVE_AVX'],
dependencies : [ spa_dep ],
install : false
)
simd_cargs += ['-DHAVE_AVX']
simd_dependencies += filter_graph_avx
endif
if have_neon
filter_graph_neon = static_library('filter_graph_neon',
['module-filter-chain/pffft.c' ],
c_args : [neon_args, '-O3', '-DHAVE_NEON'],
dependencies : [ spa_dep ],
install : false
)
simd_cargs += ['-DHAVE_NEON']
simd_dependencies += filter_graph_neon
endif
filter_graph_c = static_library('filter_graph_c',
['module-filter-chain/pffft.c',
'module-filter-chain/audio-dsp.c',
'module-filter-chain/audio-dsp-c.c' ],
include_directories : [configinc],
c_args : [simd_cargs, '-O3', '-DPFFFT_SIMD_DISABLE'],
dependencies : [ spa_dep, fftw_dep],
install : false
)
simd_dependencies += filter_graph_c
filter_graph = static_library('filter_graph',
['module-filter-chain/biquad.c',
'module-filter-chain/filter-graph.c' ],
include_directories : [configinc],
dependencies : [ spa_dep, sndfile_dep, plugin_dependencies ],
install : false
)
simd_dependencies += filter_graph
filter_graph_dependencies = [
spa_dep, mathlib, dl_lib, sndfile_dep, plugin_dependencies
]
pipewire_module_filter_chain = shared_library('pipewire-module-filter-chain',
[ 'module-filter-chain.c' ],
include_directories : [configinc],
install : true,
install_dir : modules_install_dir,
install_rpath: modules_install_dir,
link_with : simd_dependencies,
dependencies : [spa_dep, mathlib, dl_lib, pipewire_dep],
)
pipewire_module_filter_graph_builtin = shared_library('spa-filter-graph-plugin-builtin',
[ 'module-filter-chain/builtin_plugin.c',
'module-filter-chain/convolver.c' ],
include_directories : [configinc],
install : true,
install_dir : spa_plugindir / 'filter-graph',
dependencies : [ filter_graph_dependencies ]
)
pipewire_module_filter_graph_ladspa = shared_library('spa-filter-graph-plugin-ladspa',
[ 'module-filter-chain/ladspa_plugin.c' ],
include_directories : [configinc],
install : true,
install : true,
install_dir : spa_plugindir / 'filter-graph',
dependencies : [ filter_graph_dependencies ]
)
if libmysofa_dep.found()
pipewire_module_filter_graph_sofa = shared_library('spa-filter-graph-plugin-sofa',
[ 'module-filter-chain/sofa_plugin.c',
'module-filter-chain/convolver.c' ],
include_directories : [configinc],
install : true,
install_dir : spa_plugindir / 'filter-graph',
dependencies : [ filter_graph_dependencies, libmysofa_dep ]
)
endif
if lilv_lib.found()
pipewire_module_filter_graph_lv2 = shared_library('spa-filter-graph-plugin-lv2',
[ 'module-filter-chain/lv2_plugin.c' ],
include_directories : [configinc],
install : true,
install_dir : spa_plugindir / 'filter-graph',
dependencies : [ filter_graph_dependencies, lilv_lib ]
)
endif
pipewire_module_combine_stream = shared_library('pipewire-module-combine-stream',
[ 'module-combine-stream.c' ],
include_directories : [configinc],

View file

@ -17,11 +17,10 @@
#include <spa/param/tag-utils.h>
#include <spa/param/audio/raw-json.h>
#include <spa/pod/dynamic.h>
#include <spa/filter-graph/filter-graph.h>
#include <pipewire/impl.h>
#include "module-filter-chain/filter-graph.h"
#define NAME "filter-chain"
PW_LOG_TOPIC_STATIC(mod_topic, "mod." NAME);
@ -1168,8 +1167,7 @@ static void impl_destroy(struct impl *impl)
pw_core_disconnect(impl->core);
if (impl->handle)
spa_handle_clear(impl->handle);
free(impl->handle);
pw_unload_spa_handle(impl->handle);
pw_properties_free(impl->capture_props);
pw_properties_free(impl->playback_props);
@ -1222,8 +1220,6 @@ int pipewire__module_init(struct pw_impl_module *module, const char *args)
uint32_t pid = getpid();
const char *str;
int res;
const struct spa_support *support;
uint32_t n_support;
void *iface = NULL;
PW_LOG_TOPIC_INIT(mod_topic);
@ -1330,20 +1326,13 @@ int pipewire__module_init(struct pw_impl_module *module, const char *args)
pw_properties_setf(props, "filter-graph.n_inputs", "%d", impl->capture_info.channels);
pw_properties_setf(props, "filter-graph.n_outputs", "%d", impl->playback_info.channels);
impl->handle = calloc(1, spa_handle_factory_get_size(&spa_filter_graph_factory, &props->dict));
pw_properties_set(props, SPA_KEY_LIBRARY_NAME, "filter-graph/libspa-filter-graph");
impl->handle = pw_context_load_spa_handle(impl->context, "filter.graph", &props->dict);
if (impl->handle == NULL) {
res = -errno;
goto error;
}
support = pw_context_get_support(impl->context, &n_support);
if ((res = spa_handle_factory_init(&spa_filter_graph_factory,
impl->handle, &props->dict,
support, n_support)) < 0) {
pw_log_debug("can't make factory instance: %d (%s)",
res, spa_strerror(res));
goto error;
}
res = spa_handle_get_interface(impl->handle, SPA_TYPE_INTERFACE_FilterGraph, &iface);
if (res < 0 || iface == NULL)
goto error;

View file

@ -1,219 +0,0 @@
/* Spa */
/* SPDX-FileCopyrightText: Copyright © 2022 Wim Taymans */
/* SPDX-License-Identifier: MIT */
#include <string.h>
#include <stdio.h>
#include <math.h>
#include <spa/utils/defs.h>
#include "config.h"
#ifndef HAVE_FFTW
#include "pffft.h"
#endif
#include "audio-dsp-impl.h"
#include <immintrin.h>
void dsp_mix_gain_avx(void *obj,
void * SPA_RESTRICT dst,
const void * SPA_RESTRICT src[],
float gain[], uint32_t n_src, uint32_t n_samples)
{
if (n_src == 0) {
memset(dst, 0, n_samples * sizeof(float));
} else if (n_src == 1 && gain[0] == 1.0f) {
if (dst != src[0])
spa_memcpy(dst, src[0], n_samples * sizeof(float));
} else {
uint32_t n, i, unrolled;
__m256 in[4], g;
const float **s = (const float **)src;
float *d = dst;
if (SPA_LIKELY(SPA_IS_ALIGNED(dst, 32))) {
unrolled = n_samples & ~31;
for (i = 0; i < n_src; i++) {
if (SPA_UNLIKELY(!SPA_IS_ALIGNED(src[i], 32))) {
unrolled = 0;
break;
}
}
} else
unrolled = 0;
for (n = 0; n < unrolled; n += 32) {
g = _mm256_set1_ps(gain[0]);
in[0] = _mm256_mul_ps(g, _mm256_load_ps(&s[0][n+ 0]));
in[1] = _mm256_mul_ps(g, _mm256_load_ps(&s[0][n+ 8]));
in[2] = _mm256_mul_ps(g, _mm256_load_ps(&s[0][n+16]));
in[3] = _mm256_mul_ps(g, _mm256_load_ps(&s[0][n+24]));
for (i = 1; i < n_src; i++) {
g = _mm256_set1_ps(gain[i]);
in[0] = _mm256_add_ps(in[0], _mm256_mul_ps(g, _mm256_load_ps(&s[i][n+ 0])));
in[1] = _mm256_add_ps(in[1], _mm256_mul_ps(g, _mm256_load_ps(&s[i][n+ 8])));
in[2] = _mm256_add_ps(in[2], _mm256_mul_ps(g, _mm256_load_ps(&s[i][n+16])));
in[3] = _mm256_add_ps(in[3], _mm256_mul_ps(g, _mm256_load_ps(&s[i][n+24])));
}
_mm256_store_ps(&d[n+ 0], in[0]);
_mm256_store_ps(&d[n+ 8], in[1]);
_mm256_store_ps(&d[n+16], in[2]);
_mm256_store_ps(&d[n+24], in[3]);
}
for (; n < n_samples; n++) {
__m128 in[1], g;
g = _mm_set_ss(gain[0]);
in[0] = _mm_mul_ss(g, _mm_load_ss(&s[0][n]));
for (i = 1; i < n_src; i++) {
g = _mm_set_ss(gain[i]);
in[0] = _mm_add_ss(in[0], _mm_mul_ss(g, _mm_load_ss(&s[i][n])));
}
_mm_store_ss(&d[n], in[0]);
}
}
}
void dsp_sum_avx(void *obj, float *r, const float *a, const float *b, uint32_t n_samples)
{
uint32_t n, unrolled;
__m256 in[4];
unrolled = n_samples & ~31;
if (SPA_LIKELY(SPA_IS_ALIGNED(r, 32)) &&
SPA_LIKELY(SPA_IS_ALIGNED(a, 32)) &&
SPA_LIKELY(SPA_IS_ALIGNED(b, 32))) {
for (n = 0; n < unrolled; n += 32) {
in[0] = _mm256_load_ps(&a[n+ 0]);
in[1] = _mm256_load_ps(&a[n+ 8]);
in[2] = _mm256_load_ps(&a[n+16]);
in[3] = _mm256_load_ps(&a[n+24]);
in[0] = _mm256_add_ps(in[0], _mm256_load_ps(&b[n+ 0]));
in[1] = _mm256_add_ps(in[1], _mm256_load_ps(&b[n+ 8]));
in[2] = _mm256_add_ps(in[2], _mm256_load_ps(&b[n+16]));
in[3] = _mm256_add_ps(in[3], _mm256_load_ps(&b[n+24]));
_mm256_store_ps(&r[n+ 0], in[0]);
_mm256_store_ps(&r[n+ 8], in[1]);
_mm256_store_ps(&r[n+16], in[2]);
_mm256_store_ps(&r[n+24], in[3]);
}
} else {
for (n = 0; n < unrolled; n += 32) {
in[0] = _mm256_loadu_ps(&a[n+ 0]);
in[1] = _mm256_loadu_ps(&a[n+ 8]);
in[2] = _mm256_loadu_ps(&a[n+16]);
in[3] = _mm256_loadu_ps(&a[n+24]);
in[0] = _mm256_add_ps(in[0], _mm256_loadu_ps(&b[n+ 0]));
in[1] = _mm256_add_ps(in[1], _mm256_loadu_ps(&b[n+ 8]));
in[2] = _mm256_add_ps(in[2], _mm256_loadu_ps(&b[n+16]));
in[3] = _mm256_add_ps(in[3], _mm256_loadu_ps(&b[n+24]));
_mm256_storeu_ps(&r[n+ 0], in[0]);
_mm256_storeu_ps(&r[n+ 8], in[1]);
_mm256_storeu_ps(&r[n+16], in[2]);
_mm256_storeu_ps(&r[n+24], in[3]);
}
}
for (; n < n_samples; n++) {
__m128 in[1];
in[0] = _mm_load_ss(&a[n]);
in[0] = _mm_add_ss(in[0], _mm_load_ss(&b[n]));
_mm_store_ss(&r[n], in[0]);
}
}
inline static __m256 _mm256_mul_pz(__m256 ab, __m256 cd)
{
__m256 aa, bb, dc, x0, x1;
aa = _mm256_moveldup_ps(ab);
bb = _mm256_movehdup_ps(ab);
x0 = _mm256_mul_ps(aa, cd);
dc = _mm256_shuffle_ps(cd, cd, _MM_SHUFFLE(2,3,0,1));
x1 = _mm256_mul_ps(bb, dc);
return _mm256_addsub_ps(x0, x1);
}
void dsp_fft_cmul_avx(void *obj, void *fft,
float * SPA_RESTRICT dst, const float * SPA_RESTRICT a,
const float * SPA_RESTRICT b, uint32_t len, const float scale)
{
#ifdef HAVE_FFTW
__m256 s = _mm256_set1_ps(scale);
__m256 aa[2], bb[2], dd[2];
uint32_t i, unrolled;
if (SPA_IS_ALIGNED(a, 32) &&
SPA_IS_ALIGNED(b, 32) &&
SPA_IS_ALIGNED(dst, 32))
unrolled = len & ~7;
else
unrolled = 0;
for (i = 0; i < unrolled; i+=8) {
aa[0] = _mm256_load_ps(&a[2*i]); /* ar0 ai0 ar1 ai1 */
aa[1] = _mm256_load_ps(&a[2*i+8]); /* ar1 ai1 ar2 ai2 */
bb[0] = _mm256_load_ps(&b[2*i]); /* br0 bi0 br1 bi1 */
bb[1] = _mm256_load_ps(&b[2*i+8]); /* br2 bi2 br3 bi3 */
dd[0] = _mm256_mul_pz(aa[0], bb[0]);
dd[1] = _mm256_mul_pz(aa[1], bb[1]);
dd[0] = _mm256_mul_ps(dd[0], s);
dd[1] = _mm256_mul_ps(dd[1], s);
_mm256_store_ps(&dst[2*i], dd[0]);
_mm256_store_ps(&dst[2*i+8], dd[1]);
}
for (; i < len; i++) {
dst[2*i ] = (a[2*i] * b[2*i ] - a[2*i+1] * b[2*i+1]) * scale;
dst[2*i+1] = (a[2*i] * b[2*i+1] + a[2*i+1] * b[2*i ]) * scale;
}
#else
pffft_zconvolve(fft, a, b, dst, scale);
#endif
}
void dsp_fft_cmuladd_avx(void *obj, void *fft,
float * SPA_RESTRICT dst, const float * SPA_RESTRICT src,
const float * SPA_RESTRICT a, const float * SPA_RESTRICT b,
uint32_t len, const float scale)
{
#ifdef HAVE_FFTW
__m256 s = _mm256_set1_ps(scale);
__m256 aa[2], bb[2], dd[2], t[2];
uint32_t i, unrolled;
if (SPA_IS_ALIGNED(a, 32) &&
SPA_IS_ALIGNED(b, 32) &&
SPA_IS_ALIGNED(src, 32) &&
SPA_IS_ALIGNED(dst, 32))
unrolled = len & ~7;
else
unrolled = 0;
for (i = 0; i < unrolled; i+=8) {
aa[0] = _mm256_load_ps(&a[2*i]); /* ar0 ai0 ar1 ai1 */
aa[1] = _mm256_load_ps(&a[2*i+8]); /* ar1 ai1 ar2 ai2 */
bb[0] = _mm256_load_ps(&b[2*i]); /* br0 bi0 br1 bi1 */
bb[1] = _mm256_load_ps(&b[2*i+8]); /* br2 bi2 br3 bi3 */
dd[0] = _mm256_mul_pz(aa[0], bb[0]);
dd[1] = _mm256_mul_pz(aa[1], bb[1]);
dd[0] = _mm256_mul_ps(dd[0], s);
dd[1] = _mm256_mul_ps(dd[1], s);
t[0] = _mm256_load_ps(&src[2*i]);
t[1] = _mm256_load_ps(&src[2*i+8]);
t[0] = _mm256_add_ps(t[0], dd[0]);
t[1] = _mm256_add_ps(t[1], dd[1]);
_mm256_store_ps(&dst[2*i], t[0]);
_mm256_store_ps(&dst[2*i+8], t[1]);
}
for (; i < len; i++) {
dst[2*i ] = src[2*i ] + (a[2*i] * b[2*i ] - a[2*i+1] * b[2*i+1]) * scale;
dst[2*i+1] = src[2*i+1] + (a[2*i] * b[2*i+1] + a[2*i+1] * b[2*i ]) * scale;
}
#else
pffft_zconvolve_accumulate(fft, a, b, src, dst, scale);
#endif
}

View file

@ -1,337 +0,0 @@
/* Spa */
/* SPDX-FileCopyrightText: Copyright © 2022 Wim Taymans */
/* SPDX-License-Identifier: MIT */
#include <string.h>
#include <stdio.h>
#include <math.h>
#include <float.h>
#include <errno.h>
#include <spa/utils/defs.h>
#include "config.h"
#ifdef HAVE_FFTW
#include <fftw3.h>
#else
#include "pffft.h"
#endif
#include "audio-dsp-impl.h"
void dsp_clear_c(void *obj, void * SPA_RESTRICT dst, uint32_t n_samples)
{
memset(dst, 0, sizeof(float) * n_samples);
}
void dsp_copy_c(void *obj, void * SPA_RESTRICT dst,
const void * SPA_RESTRICT src, uint32_t n_samples)
{
if (dst != src)
spa_memcpy(dst, src, sizeof(float) * n_samples);
}
static inline void dsp_add_c(void *obj, void * SPA_RESTRICT dst,
const void * SPA_RESTRICT src, uint32_t n_samples)
{
uint32_t i;
const float *s = src;
float *d = dst;
for (i = 0; i < n_samples; i++)
d[i] += s[i];
}
static inline void dsp_gain_c(void *obj, void * SPA_RESTRICT dst,
const void * SPA_RESTRICT src, float gain, uint32_t n_samples)
{
uint32_t i;
const float *s = src;
float *d = dst;
if (gain == 0.0f)
dsp_clear_c(obj, dst, n_samples);
else if (gain == 1.0f)
dsp_copy_c(obj, dst, src, n_samples);
else {
for (i = 0; i < n_samples; i++)
d[i] = s[i] * gain;
}
}
static inline void dsp_gain_add_c(void *obj, void * SPA_RESTRICT dst,
const void * SPA_RESTRICT src, float gain, uint32_t n_samples)
{
uint32_t i;
const float *s = src;
float *d = dst;
if (gain == 0.0f)
return;
else if (gain == 1.0f)
dsp_add_c(obj, dst, src, n_samples);
else {
for (i = 0; i < n_samples; i++)
d[i] += s[i] * gain;
}
}
void dsp_mix_gain_c(void *obj,
void * SPA_RESTRICT dst,
const void * SPA_RESTRICT src[],
float gain[], uint32_t n_src, uint32_t n_samples)
{
uint32_t i;
if (n_src == 0) {
dsp_clear_c(obj, dst, n_samples);
} else {
dsp_gain_c(obj, dst, src[0], gain[0], n_samples);
for (i = 1; i < n_src; i++)
dsp_gain_add_c(obj, dst, src[i], gain[i], n_samples);
}
}
static inline void dsp_mult1_c(void *obj, void * SPA_RESTRICT dst,
const void * SPA_RESTRICT src, uint32_t n_samples)
{
uint32_t i;
const float *s = src;
float *d = dst;
for (i = 0; i < n_samples; i++)
d[i] *= s[i];
}
void dsp_mult_c(void *obj,
void * SPA_RESTRICT dst,
const void * SPA_RESTRICT src[],
uint32_t n_src, uint32_t n_samples)
{
uint32_t i;
if (n_src == 0) {
dsp_clear_c(obj, dst, n_samples);
} else {
dsp_copy_c(obj, dst, src[0], n_samples);
for (i = 1; i < n_src; i++)
dsp_mult1_c(obj, dst, src[i], n_samples);
}
}
static void biquad_run_c(void *obj, struct biquad *bq,
float *out, const float *in, uint32_t n_samples)
{
float x, y, x1, x2;
float b0, b1, b2, a1, a2;
uint32_t i;
if (bq->type == BQ_NONE) {
dsp_copy_c(obj, out, in, n_samples);
return;
}
x1 = bq->x1;
x2 = bq->x2;
b0 = bq->b0;
b1 = bq->b1;
b2 = bq->b2;
a1 = bq->a1;
a2 = bq->a2;
for (i = 0; i < n_samples; i++) {
x = in[i];
y = b0 * x + x1;
x1 = b1 * x - a1 * y + x2;
x2 = b2 * x - a2 * y;
out[i] = y;
}
#define F(x) (-FLT_MIN < (x) && (x) < FLT_MIN ? 0.0f : (x))
bq->x1 = F(x1);
bq->x2 = F(x2);
#undef F
}
void dsp_biquad_run_c(void *obj, struct biquad *bq, uint32_t n_bq, uint32_t bq_stride,
float * SPA_RESTRICT out[], const float * SPA_RESTRICT in[],
uint32_t n_src, uint32_t n_samples)
{
uint32_t i, j;
const float *s;
float *d;
for (i = 0; i < n_src; i++, bq+=bq_stride) {
s = in[i];
d = out[i];
if (s == NULL || d == NULL)
continue;
if (n_bq > 0)
biquad_run_c(obj, &bq[0], d, s, n_samples);
for (j = 1; j < n_bq; j++)
biquad_run_c(obj, &bq[j], d, d, n_samples);
}
}
void dsp_sum_c(void *obj, float * dst,
const float * SPA_RESTRICT a, const float * SPA_RESTRICT b, uint32_t n_samples)
{
uint32_t i;
for (i = 0; i < n_samples; i++)
dst[i] = a[i] + b[i];
}
void dsp_linear_c(void *obj, float * dst,
const float * SPA_RESTRICT src, const float mult,
const float add, uint32_t n_samples)
{
uint32_t i;
if (add == 0.0f) {
dsp_gain_c(obj, dst, src, mult, n_samples);
} else {
if (mult == 0.0f) {
for (i = 0; i < n_samples; i++)
dst[i] = add;
} else if (mult == 1.0f) {
for (i = 0; i < n_samples; i++)
dst[i] = src[i] + add;
} else {
for (i = 0; i < n_samples; i++)
dst[i] = mult * src[i] + add;
}
}
}
void dsp_delay_c(void *obj, float *buffer, uint32_t *pos, uint32_t n_buffer,
uint32_t delay, float *dst, const float *src, uint32_t n_samples)
{
if (delay == 0) {
dsp_copy_c(obj, dst, src, n_samples);
} else {
uint32_t w, o, i;
w = *pos;
o = n_buffer - delay;
for (i = 0; i < n_samples; i++) {
buffer[w] = buffer[w + n_buffer] = src[i];
dst[i] = buffer[w + o];
w = w + 1 > n_buffer ? 0 : w + 1;
}
*pos = w;
}
}
#ifdef HAVE_FFTW
struct fft_info {
fftwf_plan plan_r2c;
fftwf_plan plan_c2r;
};
#endif
void *dsp_fft_new_c(void *obj, uint32_t size, bool real)
{
#ifdef HAVE_FFTW
struct fft_info *info = calloc(1, sizeof(struct fft_info));
float *rdata;
fftwf_complex *cdata;
if (info == NULL)
return NULL;
rdata = fftwf_alloc_real(size * 2);
cdata = fftwf_alloc_complex(size + 1);
info->plan_r2c = fftwf_plan_dft_r2c_1d(size, rdata, cdata, FFTW_ESTIMATE);
info->plan_c2r = fftwf_plan_dft_c2r_1d(size, cdata, rdata, FFTW_ESTIMATE);
fftwf_free(rdata);
fftwf_free(cdata);
return info;
#else
return pffft_new_setup(size, real ? PFFFT_REAL : PFFFT_COMPLEX);
#endif
}
void dsp_fft_free_c(void *obj, void *fft)
{
#ifdef HAVE_FFTW
struct fft_info *info = fft;
fftwf_destroy_plan(info->plan_r2c);
fftwf_destroy_plan(info->plan_c2r);
free(info);
#else
pffft_destroy_setup(fft);
#endif
}
void *dsp_fft_memalloc_c(void *obj, uint32_t size, bool real)
{
#ifdef HAVE_FFTW
if (real)
return fftwf_alloc_real(size);
else
return fftwf_alloc_complex(size);
#else
if (real)
return pffft_aligned_malloc(size * sizeof(float));
else
return pffft_aligned_malloc(size * 2 * sizeof(float));
#endif
}
void dsp_fft_memfree_c(void *obj, void *data)
{
#ifdef HAVE_FFTW
fftwf_free(data);
#else
pffft_aligned_free(data);
#endif
}
void dsp_fft_memclear_c(void *obj, void *data, uint32_t size, bool real)
{
#ifdef HAVE_FFTW
spa_fga_dsp_clear(obj, data, real ? size : size * 2);
#else
spa_fga_dsp_clear(obj, data, real ? size : size * 2);
#endif
}
void dsp_fft_run_c(void *obj, void *fft, int direction,
const float * SPA_RESTRICT src, float * SPA_RESTRICT dst)
{
#ifdef HAVE_FFTW
struct fft_info *info = fft;
if (direction > 0)
fftwf_execute_dft_r2c (info->plan_r2c, (float*)src, (fftwf_complex*)dst);
else
fftwf_execute_dft_c2r (info->plan_c2r, (fftwf_complex*)src, dst);
#else
pffft_transform(fft, src, dst, NULL, direction < 0 ? PFFFT_BACKWARD : PFFFT_FORWARD);
#endif
}
void dsp_fft_cmul_c(void *obj, void *fft,
float * SPA_RESTRICT dst, const float * SPA_RESTRICT a,
const float * SPA_RESTRICT b, uint32_t len, const float scale)
{
#ifdef HAVE_FFTW
for (uint32_t i = 0; i < len; i++) {
dst[2*i ] = (a[2*i] * b[2*i ] - a[2*i+1] * b[2*i+1]) * scale;
dst[2*i+1] = (a[2*i] * b[2*i+1] + a[2*i+1] * b[2*i ]) * scale;
}
#else
pffft_zconvolve(fft, a, b, dst, scale);
#endif
}
void dsp_fft_cmuladd_c(void *obj, void *fft,
float * SPA_RESTRICT dst, const float * SPA_RESTRICT src,
const float * SPA_RESTRICT a, const float * SPA_RESTRICT b,
uint32_t len, const float scale)
{
#ifdef HAVE_FFTW
for (uint32_t i = 0; i < len; i++) {
dst[2*i ] = src[2*i ] + (a[2*i] * b[2*i ] - a[2*i+1] * b[2*i+1]) * scale;
dst[2*i+1] = src[2*i+1] + (a[2*i] * b[2*i+1] + a[2*i+1] * b[2*i ]) * scale;
}
#else
pffft_zconvolve_accumulate(fft, a, b, src, dst, scale);
#endif
}

View file

@ -1,94 +0,0 @@
/* Spa */
/* SPDX-FileCopyrightText: Copyright © 2022 Wim Taymans */
/* SPDX-License-Identifier: MIT */
#ifndef DSP_IMPL_H
#define DSP_IMPL_H
#include "audio-dsp.h"
struct spa_fga_dsp * spa_fga_dsp_new(uint32_t cpu_flags);
void spa_fga_dsp_free(struct spa_fga_dsp *dsp);
#define MAKE_CLEAR_FUNC(arch) \
void dsp_clear_##arch(void *obj, void * SPA_RESTRICT dst, uint32_t n_samples)
#define MAKE_COPY_FUNC(arch) \
void dsp_copy_##arch(void *obj, void * SPA_RESTRICT dst, \
const void * SPA_RESTRICT src, uint32_t n_samples)
#define MAKE_MIX_GAIN_FUNC(arch) \
void dsp_mix_gain_##arch(void *obj, void * SPA_RESTRICT dst, \
const void * SPA_RESTRICT src[], float gain[], uint32_t n_src, uint32_t n_samples)
#define MAKE_SUM_FUNC(arch) \
void dsp_sum_##arch (void *obj, float * SPA_RESTRICT dst, \
const float * SPA_RESTRICT a, const float * SPA_RESTRICT b, uint32_t n_samples)
#define MAKE_LINEAR_FUNC(arch) \
void dsp_linear_##arch (void *obj, float * SPA_RESTRICT dst, \
const float * SPA_RESTRICT src, const float mult, const float add, uint32_t n_samples)
#define MAKE_MULT_FUNC(arch) \
void dsp_mult_##arch(void *obj, void * SPA_RESTRICT dst, \
const void * SPA_RESTRICT src[], uint32_t n_src, uint32_t n_samples)
#define MAKE_BIQUAD_RUN_FUNC(arch) \
void dsp_biquad_run_##arch (void *obj, struct biquad *bq, uint32_t n_bq, uint32_t bq_stride, \
float * SPA_RESTRICT out[], const float * SPA_RESTRICT in[], uint32_t n_src, uint32_t n_samples)
#define MAKE_DELAY_FUNC(arch) \
void dsp_delay_##arch (void *obj, float *buffer, uint32_t *pos, uint32_t n_buffer, \
uint32_t delay, float *dst, const float *src, uint32_t n_samples)
#define MAKE_FFT_NEW_FUNC(arch) \
void *dsp_fft_new_##arch(void *obj, uint32_t size, bool real)
#define MAKE_FFT_FREE_FUNC(arch) \
void dsp_fft_free_##arch(void *obj, void *fft)
#define MAKE_FFT_MEMALLOC_FUNC(arch) \
void *dsp_fft_memalloc_##arch(void *obj, uint32_t size, bool real)
#define MAKE_FFT_MEMFREE_FUNC(arch) \
void dsp_fft_memfree_##arch(void *obj, void *mem)
#define MAKE_FFT_MEMCLEAR_FUNC(arch) \
void dsp_fft_memclear_##arch(void *obj, void *mem, uint32_t size, bool real)
#define MAKE_FFT_RUN_FUNC(arch) \
void dsp_fft_run_##arch(void *obj, void *fft, int direction, \
const float * SPA_RESTRICT src, float * SPA_RESTRICT dst)
#define MAKE_FFT_CMUL_FUNC(arch) \
void dsp_fft_cmul_##arch(void *obj, void *fft, \
float * SPA_RESTRICT dst, const float * SPA_RESTRICT a, \
const float * SPA_RESTRICT b, uint32_t len, const float scale)
#define MAKE_FFT_CMULADD_FUNC(arch) \
void dsp_fft_cmuladd_##arch(void *obj, void *fft, \
float * dst, const float * src, \
const float * SPA_RESTRICT a, const float * SPA_RESTRICT b, \
uint32_t len, const float scale)
MAKE_CLEAR_FUNC(c);
MAKE_COPY_FUNC(c);
MAKE_MIX_GAIN_FUNC(c);
MAKE_SUM_FUNC(c);
MAKE_LINEAR_FUNC(c);
MAKE_MULT_FUNC(c);
MAKE_BIQUAD_RUN_FUNC(c);
MAKE_DELAY_FUNC(c);
MAKE_FFT_NEW_FUNC(c);
MAKE_FFT_FREE_FUNC(c);
MAKE_FFT_MEMALLOC_FUNC(c);
MAKE_FFT_MEMFREE_FUNC(c);
MAKE_FFT_MEMCLEAR_FUNC(c);
MAKE_FFT_RUN_FUNC(c);
MAKE_FFT_CMUL_FUNC(c);
MAKE_FFT_CMULADD_FUNC(c);
#if defined (HAVE_SSE)
MAKE_MIX_GAIN_FUNC(sse);
MAKE_SUM_FUNC(sse);
MAKE_BIQUAD_RUN_FUNC(sse);
MAKE_DELAY_FUNC(sse);
MAKE_FFT_CMUL_FUNC(sse);
MAKE_FFT_CMULADD_FUNC(sse);
#endif
#if defined (HAVE_AVX)
MAKE_MIX_GAIN_FUNC(avx);
MAKE_SUM_FUNC(avx);
MAKE_FFT_CMUL_FUNC(avx);
MAKE_FFT_CMULADD_FUNC(avx);
#endif
#endif /* DSP_OPS_IMPL_H */

View file

@ -1,639 +0,0 @@
/* Spa */
/* SPDX-FileCopyrightText: Copyright © 2022 Wim Taymans */
/* SPDX-License-Identifier: MIT */
#include <string.h>
#include <stdio.h>
#include <math.h>
#include <float.h>
#include <complex.h>
#include <spa/utils/defs.h>
#include "config.h"
#ifndef HAVE_FFTW
#include "pffft.h"
#endif
#include "audio-dsp-impl.h"
#include <xmmintrin.h>
void dsp_mix_gain_sse(void *obj,
void * SPA_RESTRICT dst,
const void * SPA_RESTRICT src[],
float gain[], uint32_t n_src, uint32_t n_samples)
{
if (n_src == 0) {
memset(dst, 0, n_samples * sizeof(float));
} else if (n_src == 1 && gain[0] == 1.0f) {
if (dst != src[0])
spa_memcpy(dst, src[0], n_samples * sizeof(float));
} else {
uint32_t n, i, unrolled;
__m128 in[4], g;
const float **s = (const float **)src;
float *d = dst;
if (SPA_LIKELY(SPA_IS_ALIGNED(dst, 16))) {
unrolled = n_samples & ~15;
for (i = 0; i < n_src; i++) {
if (SPA_UNLIKELY(!SPA_IS_ALIGNED(src[i], 16))) {
unrolled = 0;
break;
}
}
} else
unrolled = 0;
for (n = 0; n < unrolled; n += 16) {
g = _mm_set1_ps(gain[0]);
in[0] = _mm_mul_ps(g, _mm_load_ps(&s[0][n+ 0]));
in[1] = _mm_mul_ps(g, _mm_load_ps(&s[0][n+ 4]));
in[2] = _mm_mul_ps(g, _mm_load_ps(&s[0][n+ 8]));
in[3] = _mm_mul_ps(g, _mm_load_ps(&s[0][n+12]));
for (i = 1; i < n_src; i++) {
g = _mm_set1_ps(gain[i]);
in[0] = _mm_add_ps(in[0], _mm_mul_ps(g, _mm_load_ps(&s[i][n+ 0])));
in[1] = _mm_add_ps(in[1], _mm_mul_ps(g, _mm_load_ps(&s[i][n+ 4])));
in[2] = _mm_add_ps(in[2], _mm_mul_ps(g, _mm_load_ps(&s[i][n+ 8])));
in[3] = _mm_add_ps(in[3], _mm_mul_ps(g, _mm_load_ps(&s[i][n+12])));
}
_mm_store_ps(&d[n+ 0], in[0]);
_mm_store_ps(&d[n+ 4], in[1]);
_mm_store_ps(&d[n+ 8], in[2]);
_mm_store_ps(&d[n+12], in[3]);
}
for (; n < n_samples; n++) {
g = _mm_set_ss(gain[0]);
in[0] = _mm_mul_ss(g, _mm_load_ss(&s[0][n]));
for (i = 1; i < n_src; i++) {
g = _mm_set_ss(gain[i]);
in[0] = _mm_add_ss(in[0], _mm_mul_ss(g, _mm_load_ss(&s[i][n])));
}
_mm_store_ss(&d[n], in[0]);
}
}
}
void dsp_sum_sse(void *obj, float *r, const float *a, const float *b, uint32_t n_samples)
{
uint32_t n, unrolled;
__m128 in[4];
unrolled = n_samples & ~15;
if (SPA_LIKELY(SPA_IS_ALIGNED(r, 16)) &&
SPA_LIKELY(SPA_IS_ALIGNED(a, 16)) &&
SPA_LIKELY(SPA_IS_ALIGNED(b, 16))) {
for (n = 0; n < unrolled; n += 16) {
in[0] = _mm_load_ps(&a[n+ 0]);
in[1] = _mm_load_ps(&a[n+ 4]);
in[2] = _mm_load_ps(&a[n+ 8]);
in[3] = _mm_load_ps(&a[n+12]);
in[0] = _mm_add_ps(in[0], _mm_load_ps(&b[n+ 0]));
in[1] = _mm_add_ps(in[1], _mm_load_ps(&b[n+ 4]));
in[2] = _mm_add_ps(in[2], _mm_load_ps(&b[n+ 8]));
in[3] = _mm_add_ps(in[3], _mm_load_ps(&b[n+12]));
_mm_store_ps(&r[n+ 0], in[0]);
_mm_store_ps(&r[n+ 4], in[1]);
_mm_store_ps(&r[n+ 8], in[2]);
_mm_store_ps(&r[n+12], in[3]);
}
} else {
for (n = 0; n < unrolled; n += 16) {
in[0] = _mm_loadu_ps(&a[n+ 0]);
in[1] = _mm_loadu_ps(&a[n+ 4]);
in[2] = _mm_loadu_ps(&a[n+ 8]);
in[3] = _mm_loadu_ps(&a[n+12]);
in[0] = _mm_add_ps(in[0], _mm_loadu_ps(&b[n+ 0]));
in[1] = _mm_add_ps(in[1], _mm_loadu_ps(&b[n+ 4]));
in[2] = _mm_add_ps(in[2], _mm_loadu_ps(&b[n+ 8]));
in[3] = _mm_add_ps(in[3], _mm_loadu_ps(&b[n+12]));
_mm_storeu_ps(&r[n+ 0], in[0]);
_mm_storeu_ps(&r[n+ 4], in[1]);
_mm_storeu_ps(&r[n+ 8], in[2]);
_mm_storeu_ps(&r[n+12], in[3]);
}
}
for (; n < n_samples; n++) {
in[0] = _mm_load_ss(&a[n]);
in[0] = _mm_add_ss(in[0], _mm_load_ss(&b[n]));
_mm_store_ss(&r[n], in[0]);
}
}
static void dsp_biquad_run1_sse(void *obj, struct biquad *bq,
float *out, const float *in, uint32_t n_samples)
{
__m128 x, y, z;
__m128 b012;
__m128 a12;
__m128 x12;
uint32_t i;
b012 = _mm_setr_ps(bq->b0, bq->b1, bq->b2, 0.0f); /* b0 b1 b2 0 */
a12 = _mm_setr_ps(0.0f, bq->a1, bq->a2, 0.0f); /* 0 a1 a2 0 */
x12 = _mm_setr_ps(bq->x1, bq->x2, 0.0f, 0.0f); /* x1 x2 0 0 */
for (i = 0; i < n_samples; i++) {
x = _mm_load1_ps(&in[i]); /* x x x x */
z = _mm_mul_ps(x, b012); /* b0*x b1*x b2*x 0 */
z = _mm_add_ps(z, x12); /* b0*x+x1 b1*x+x2 b2*x 0 */
_mm_store_ss(&out[i], z); /* out[i] = b0*x+x1 */
y = _mm_shuffle_ps(z, z, _MM_SHUFFLE(0,0,0,0)); /* b0*x+x1 b0*x+x1 b0*x+x1 b0*x+x1 = y*/
y = _mm_mul_ps(y, a12); /* 0 a1*y a2*y 0 */
y = _mm_sub_ps(z, y); /* y x1 x2 0 */
x12 = _mm_shuffle_ps(y, y, _MM_SHUFFLE(3,3,2,1)); /* x1 x2 0 0*/
}
#define F(x) (-FLT_MIN < (x) && (x) < FLT_MIN ? 0.0f : (x))
bq->x1 = F(x12[0]);
bq->x2 = F(x12[1]);
#undef F
}
static void dsp_biquad2_run_sse(void *obj, struct biquad *bq,
float *out, const float *in, uint32_t n_samples)
{
__m128 x, y, z;
__m128 b0, b1;
__m128 a0, a1;
__m128 x0, x1;
uint32_t i;
b0 = _mm_setr_ps(bq[0].b0, bq[0].b1, bq[0].b2, 0.0f); /* b0 b1 b2 0 */
a0 = _mm_setr_ps(0.0f, bq[0].a1, bq[0].a2, 0.0f); /* 0 a1 a2 0 */
x0 = _mm_setr_ps(bq[0].x1, bq[0].x2, 0.0f, 0.0f); /* x1 x2 0 0 */
b1 = _mm_setr_ps(bq[1].b0, bq[1].b1, bq[1].b2, 0.0f); /* b0 b1 b2 0 */
a1 = _mm_setr_ps(0.0f, bq[1].a1, bq[1].a2, 0.0f); /* 0 a1 a2 0 */
x1 = _mm_setr_ps(bq[1].x1, bq[1].x2, 0.0f, 0.0f); /* x1 x2 0 0 */
for (i = 0; i < n_samples; i++) {
x = _mm_load1_ps(&in[i]); /* x x x x */
z = _mm_mul_ps(x, b0); /* b0*x b1*x b2*x 0 */
z = _mm_add_ps(z, x0); /* b0*x+x1 b1*x+x2 b2*x 0 */
y = _mm_shuffle_ps(z, z, _MM_SHUFFLE(0,0,0,0)); /* b0*x+x1 b0*x+x1 b0*x+x1 b0*x+x1 = y*/
x = _mm_mul_ps(y, a0); /* 0 a1*y a2*y 0 */
x = _mm_sub_ps(z, x); /* y x1 x2 0 */
x0 = _mm_shuffle_ps(x, x, _MM_SHUFFLE(3,3,2,1)); /* x1 x2 0 0*/
z = _mm_mul_ps(y, b1); /* b0*x b1*x b2*x 0 */
z = _mm_add_ps(z, x1); /* b0*x+x1 b1*x+x2 b2*x 0 */
x = _mm_shuffle_ps(z, z, _MM_SHUFFLE(0,0,0,0)); /* b0*x+x1 b0*x+x1 b0*x+x1 b0*x+x1 = y*/
y = _mm_mul_ps(x, a1); /* 0 a1*y a2*y 0 */
y = _mm_sub_ps(z, y); /* y x1 x2 0 */
x1 = _mm_shuffle_ps(y, y, _MM_SHUFFLE(3,3,2,1)); /* x1 x2 0 0*/
_mm_store_ss(&out[i], x); /* out[i] = b0*x+x1 */
}
#define F(x) (-FLT_MIN < (x) && (x) < FLT_MIN ? 0.0f : (x))
bq[0].x1 = F(x0[0]);
bq[0].x2 = F(x0[1]);
bq[1].x1 = F(x1[0]);
bq[1].x2 = F(x1[1]);
#undef F
}
static void dsp_biquad_run2_sse(void *obj, struct biquad *bq, uint32_t bq_stride,
float **out, const float **in, uint32_t n_samples)
{
__m128 x, y, z;
__m128 b0, b1, b2;
__m128 a1, a2;
__m128 x1, x2;
uint32_t i;
b0 = _mm_setr_ps(bq[0].b0, bq[bq_stride].b0, 0.0f, 0.0f); /* b00 b10 0 0 */
b1 = _mm_setr_ps(bq[0].b1, bq[bq_stride].b1, 0.0f, 0.0f); /* b01 b11 0 0 */
b2 = _mm_setr_ps(bq[0].b2, bq[bq_stride].b2, 0.0f, 0.0f); /* b02 b12 0 0 */
a1 = _mm_setr_ps(bq[0].a1, bq[bq_stride].a1, 0.0f, 0.0f); /* b00 b10 0 0 */
a2 = _mm_setr_ps(bq[0].a2, bq[bq_stride].a2, 0.0f, 0.0f); /* b01 b11 0 0 */
x1 = _mm_setr_ps(bq[0].x1, bq[bq_stride].x1, 0.0f, 0.0f); /* b00 b10 0 0 */
x2 = _mm_setr_ps(bq[0].x2, bq[bq_stride].x2, 0.0f, 0.0f); /* b01 b11 0 0 */
for (i = 0; i < n_samples; i++) {
x = _mm_setr_ps(in[0][i], in[1][i], 0.0f, 0.0f);
y = _mm_mul_ps(x, b0); /* y = x * b0 */
y = _mm_add_ps(y, x1); /* y = x * b0 + x1*/
z = _mm_mul_ps(y, a1); /* z = a1 * y */
x1 = _mm_mul_ps(x, b1); /* x1 = x * b1 */
x1 = _mm_add_ps(x1, x2); /* x1 = x * b1 + x2*/
x1 = _mm_sub_ps(x1, z); /* x1 = x * b1 + x2 - a1 * y*/
z = _mm_mul_ps(y, a2); /* z = a2 * y */
x2 = _mm_mul_ps(x, b2); /* x2 = x * b2 */
x2 = _mm_sub_ps(x2, z); /* x2 = x * b2 - a2 * y*/
out[0][i] = y[0];
out[1][i] = y[1];
}
#define F(x) (-FLT_MIN < (x) && (x) < FLT_MIN ? 0.0f : (x))
bq[0*bq_stride].x1 = F(x1[0]);
bq[0*bq_stride].x2 = F(x2[0]);
bq[1*bq_stride].x1 = F(x1[1]);
bq[1*bq_stride].x2 = F(x2[1]);
#undef F
}
static void dsp_biquad2_run2_sse(void *obj, struct biquad *bq, uint32_t bq_stride,
float **out, const float **in, uint32_t n_samples)
{
__m128 x, y, z;
__m128 b00, b01, b02, b10, b11, b12;
__m128 a01, a02, a11, a12;
__m128 x01, x02, x11, x12;
uint32_t i;
b00 = _mm_setr_ps(bq[0].b0, bq[bq_stride].b0, 0.0f, 0.0f); /* b00 b10 0 0 */
b01 = _mm_setr_ps(bq[0].b1, bq[bq_stride].b1, 0.0f, 0.0f); /* b01 b11 0 0 */
b02 = _mm_setr_ps(bq[0].b2, bq[bq_stride].b2, 0.0f, 0.0f); /* b02 b12 0 0 */
a01 = _mm_setr_ps(bq[0].a1, bq[bq_stride].a1, 0.0f, 0.0f); /* b00 b10 0 0 */
a02 = _mm_setr_ps(bq[0].a2, bq[bq_stride].a2, 0.0f, 0.0f); /* b01 b11 0 0 */
x01 = _mm_setr_ps(bq[0].x1, bq[bq_stride].x1, 0.0f, 0.0f); /* b00 b10 0 0 */
x02 = _mm_setr_ps(bq[0].x2, bq[bq_stride].x2, 0.0f, 0.0f); /* b01 b11 0 0 */
b10 = _mm_setr_ps(bq[1].b0, bq[bq_stride+1].b0, 0.0f, 0.0f); /* b00 b10 0 0 */
b11 = _mm_setr_ps(bq[1].b1, bq[bq_stride+1].b1, 0.0f, 0.0f); /* b01 b11 0 0 */
b12 = _mm_setr_ps(bq[1].b2, bq[bq_stride+1].b2, 0.0f, 0.0f); /* b02 b12 0 0 */
a11 = _mm_setr_ps(bq[1].a1, bq[bq_stride+1].a1, 0.0f, 0.0f); /* b00 b10 0 0 */
a12 = _mm_setr_ps(bq[1].a2, bq[bq_stride+1].a2, 0.0f, 0.0f); /* b01 b11 0 0 */
x11 = _mm_setr_ps(bq[1].x1, bq[bq_stride+1].x1, 0.0f, 0.0f); /* b00 b10 0 0 */
x12 = _mm_setr_ps(bq[1].x2, bq[bq_stride+1].x2, 0.0f, 0.0f); /* b01 b11 0 0 */
for (i = 0; i < n_samples; i++) {
x = _mm_setr_ps(in[0][i], in[1][i], 0.0f, 0.0f);
y = _mm_mul_ps(x, b00); /* y = x * b0 */
y = _mm_add_ps(y, x01); /* y = x * b0 + x1*/
z = _mm_mul_ps(y, a01); /* z = a1 * y */
x01 = _mm_mul_ps(x, b01); /* x1 = x * b1 */
x01 = _mm_add_ps(x01, x02); /* x1 = x * b1 + x2*/
x01 = _mm_sub_ps(x01, z); /* x1 = x * b1 + x2 - a1 * y*/
z = _mm_mul_ps(y, a02); /* z = a2 * y */
x02 = _mm_mul_ps(x, b02); /* x2 = x * b2 */
x02 = _mm_sub_ps(x02, z); /* x2 = x * b2 - a2 * y*/
x = y;
y = _mm_mul_ps(x, b10); /* y = x * b0 */
y = _mm_add_ps(y, x11); /* y = x * b0 + x1*/
z = _mm_mul_ps(y, a11); /* z = a1 * y */
x11 = _mm_mul_ps(x, b11); /* x1 = x * b1 */
x11 = _mm_add_ps(x11, x12); /* x1 = x * b1 + x2*/
x11 = _mm_sub_ps(x11, z); /* x1 = x * b1 + x2 - a1 * y*/
z = _mm_mul_ps(y, a12); /* z = a2 * y*/
x12 = _mm_mul_ps(x, b12); /* x2 = x * b2 */
x12 = _mm_sub_ps(x12, z); /* x2 = x * b2 - a2 * y*/
out[0][i] = y[0];
out[1][i] = y[1];
}
#define F(x) (-FLT_MIN < (x) && (x) < FLT_MIN ? 0.0f : (x))
bq[0*bq_stride+0].x1 = F(x01[0]);
bq[0*bq_stride+0].x2 = F(x02[0]);
bq[1*bq_stride+0].x1 = F(x01[1]);
bq[1*bq_stride+0].x2 = F(x02[1]);
bq[0*bq_stride+1].x1 = F(x11[0]);
bq[0*bq_stride+1].x2 = F(x12[0]);
bq[1*bq_stride+1].x1 = F(x11[1]);
bq[1*bq_stride+1].x2 = F(x12[1]);
#undef F
}
static void dsp_biquad_run4_sse(void *obj, struct biquad *bq, uint32_t bq_stride,
float **out, const float **in, uint32_t n_samples)
{
__m128 x, y, z;
__m128 b0, b1, b2;
__m128 a1, a2;
__m128 x1, x2;
uint32_t i;
b0 = _mm_setr_ps(bq[0].b0, bq[bq_stride].b0, bq[2*bq_stride].b0, bq[3*bq_stride].b0);
b1 = _mm_setr_ps(bq[0].b1, bq[bq_stride].b1, bq[2*bq_stride].b1, bq[3*bq_stride].b1);
b2 = _mm_setr_ps(bq[0].b2, bq[bq_stride].b2, bq[2*bq_stride].b2, bq[3*bq_stride].b2);
a1 = _mm_setr_ps(bq[0].a1, bq[bq_stride].a1, bq[2*bq_stride].a1, bq[3*bq_stride].a1);
a2 = _mm_setr_ps(bq[0].a2, bq[bq_stride].a2, bq[2*bq_stride].a2, bq[3*bq_stride].a2);
x1 = _mm_setr_ps(bq[0].x1, bq[bq_stride].x1, bq[2*bq_stride].x1, bq[3*bq_stride].x1);
x2 = _mm_setr_ps(bq[0].x2, bq[bq_stride].x2, bq[2*bq_stride].x2, bq[3*bq_stride].x2);
for (i = 0; i < n_samples; i++) {
x = _mm_setr_ps(in[0][i], in[1][i], in[2][i], in[3][i]);
y = _mm_mul_ps(x, b0); /* y = x * b0 */
y = _mm_add_ps(y, x1); /* y = x * b0 + x1*/
z = _mm_mul_ps(y, a1); /* z = a1 * y */
x1 = _mm_mul_ps(x, b1); /* x1 = x * b1 */
x1 = _mm_add_ps(x1, x2); /* x1 = x * b1 + x2*/
x1 = _mm_sub_ps(x1, z); /* x1 = x * b1 + x2 - a1 * y*/
z = _mm_mul_ps(y, a2); /* z = a2 * y */
x2 = _mm_mul_ps(x, b2); /* x2 = x * b2 */
x2 = _mm_sub_ps(x2, z); /* x2 = x * b2 - a2 * y*/
out[0][i] = y[0];
out[1][i] = y[1];
out[2][i] = y[2];
out[3][i] = y[3];
}
#define F(x) (-FLT_MIN < (x) && (x) < FLT_MIN ? 0.0f : (x))
bq[0*bq_stride].x1 = F(x1[0]);
bq[0*bq_stride].x2 = F(x2[0]);
bq[1*bq_stride].x1 = F(x1[1]);
bq[1*bq_stride].x2 = F(x2[1]);
bq[2*bq_stride].x1 = F(x1[2]);
bq[2*bq_stride].x2 = F(x2[2]);
bq[3*bq_stride].x1 = F(x1[3]);
bq[3*bq_stride].x2 = F(x2[3]);
#undef F
}
static void dsp_biquad2_run4_sse(void *obj, struct biquad *bq, uint32_t bq_stride,
float **out, const float **in, uint32_t n_samples)
{
__m128 x, y, z;
__m128 b00, b01, b02, b10, b11, b12;
__m128 a01, a02, a11, a12;
__m128 x01, x02, x11, x12;
uint32_t i;
b00 = _mm_setr_ps(bq[0].b0, bq[bq_stride].b0, bq[2*bq_stride].b0, bq[3*bq_stride].b0);
b01 = _mm_setr_ps(bq[0].b1, bq[bq_stride].b1, bq[2*bq_stride].b1, bq[3*bq_stride].b1);
b02 = _mm_setr_ps(bq[0].b2, bq[bq_stride].b2, bq[2*bq_stride].b2, bq[3*bq_stride].b2);
a01 = _mm_setr_ps(bq[0].a1, bq[bq_stride].a1, bq[2*bq_stride].a1, bq[3*bq_stride].a1);
a02 = _mm_setr_ps(bq[0].a2, bq[bq_stride].a2, bq[2*bq_stride].a2, bq[3*bq_stride].a2);
x01 = _mm_setr_ps(bq[0].x1, bq[bq_stride].x1, bq[2*bq_stride].x1, bq[3*bq_stride].x1);
x02 = _mm_setr_ps(bq[0].x2, bq[bq_stride].x2, bq[2*bq_stride].x2, bq[3*bq_stride].x2);
b10 = _mm_setr_ps(bq[1].b0, bq[bq_stride+1].b0, bq[2*bq_stride+1].b0, bq[3*bq_stride+1].b0);
b11 = _mm_setr_ps(bq[1].b1, bq[bq_stride+1].b1, bq[2*bq_stride+1].b1, bq[3*bq_stride+1].b1);
b12 = _mm_setr_ps(bq[1].b2, bq[bq_stride+1].b2, bq[2*bq_stride+1].b2, bq[3*bq_stride+1].b2);
a11 = _mm_setr_ps(bq[1].a1, bq[bq_stride+1].a1, bq[2*bq_stride+1].a1, bq[3*bq_stride+1].a1);
a12 = _mm_setr_ps(bq[1].a2, bq[bq_stride+1].a2, bq[2*bq_stride+1].a2, bq[3*bq_stride+1].a2);
x11 = _mm_setr_ps(bq[1].x1, bq[bq_stride+1].x1, bq[2*bq_stride+1].x1, bq[3*bq_stride+1].x1);
x12 = _mm_setr_ps(bq[1].x2, bq[bq_stride+1].x2, bq[2*bq_stride+1].x2, bq[3*bq_stride+1].x2);
for (i = 0; i < n_samples; i++) {
x = _mm_setr_ps(in[0][i], in[1][i], in[2][i], in[3][i]);
y = _mm_mul_ps(x, b00); /* y = x * b0 */
y = _mm_add_ps(y, x01); /* y = x * b0 + x1*/
z = _mm_mul_ps(y, a01); /* z = a1 * y */
x01 = _mm_mul_ps(x, b01); /* x1 = x * b1 */
x01 = _mm_add_ps(x01, x02); /* x1 = x * b1 + x2*/
x01 = _mm_sub_ps(x01, z); /* x1 = x * b1 + x2 - a1 * y*/
z = _mm_mul_ps(y, a02); /* z = a2 * y */
x02 = _mm_mul_ps(x, b02); /* x2 = x * b2 */
x02 = _mm_sub_ps(x02, z); /* x2 = x * b2 - a2 * y*/
x = y;
y = _mm_mul_ps(x, b10); /* y = x * b0 */
y = _mm_add_ps(y, x11); /* y = x * b0 + x1*/
z = _mm_mul_ps(y, a11); /* z = a1 * y */
x11 = _mm_mul_ps(x, b11); /* x1 = x * b1 */
x11 = _mm_add_ps(x11, x12); /* x1 = x * b1 + x2*/
x11 = _mm_sub_ps(x11, z); /* x1 = x * b1 + x2 - a1 * y*/
z = _mm_mul_ps(y, a12); /* z = a2 * y*/
x12 = _mm_mul_ps(x, b12); /* x2 = x * b2 */
x12 = _mm_sub_ps(x12, z); /* x2 = x * b2 - a2 * y*/
out[0][i] = y[0];
out[1][i] = y[1];
out[2][i] = y[2];
out[3][i] = y[3];
}
#define F(x) (-FLT_MIN < (x) && (x) < FLT_MIN ? 0.0f : (x))
bq[0*bq_stride+0].x1 = F(x01[0]);
bq[0*bq_stride+0].x2 = F(x02[0]);
bq[1*bq_stride+0].x1 = F(x01[1]);
bq[1*bq_stride+0].x2 = F(x02[1]);
bq[2*bq_stride+0].x1 = F(x01[2]);
bq[2*bq_stride+0].x2 = F(x02[2]);
bq[3*bq_stride+0].x1 = F(x01[3]);
bq[3*bq_stride+0].x2 = F(x02[3]);
bq[0*bq_stride+1].x1 = F(x11[0]);
bq[0*bq_stride+1].x2 = F(x12[0]);
bq[1*bq_stride+1].x1 = F(x11[1]);
bq[1*bq_stride+1].x2 = F(x12[1]);
bq[2*bq_stride+1].x1 = F(x11[2]);
bq[2*bq_stride+1].x2 = F(x12[2]);
bq[3*bq_stride+1].x1 = F(x11[3]);
bq[3*bq_stride+1].x2 = F(x12[3]);
#undef F
}
void dsp_biquad_run_sse(void *obj, struct biquad *bq, uint32_t n_bq, uint32_t bq_stride,
float * SPA_RESTRICT out[], const float * SPA_RESTRICT in[],
uint32_t n_src, uint32_t n_samples)
{
uint32_t i, j, bqs2 = bq_stride*2, bqs4 = bqs2*2;
uint32_t iunrolled4 = n_src & ~3;
uint32_t iunrolled2 = n_src & ~1;
uint32_t junrolled2 = n_bq & ~1;
for (i = 0; i < iunrolled4; i+=4, bq+=bqs4) {
const float *s[4] = { in[i], in[i+1], in[i+2], in[i+3] };
float *d[4] = { out[i], out[i+1], out[i+2], out[i+3] };
if (s[0] == NULL || s[1] == NULL || s[2] == NULL || s[3] == NULL ||
d[0] == NULL || d[1] == NULL || d[2] == NULL || d[3] == NULL)
break;
j = 0;
if (j < junrolled2) {
dsp_biquad2_run4_sse(obj, &bq[j], bq_stride, d, s, n_samples);
s[0] = d[0];
s[1] = d[1];
s[2] = d[2];
s[3] = d[3];
j+=2;
}
for (; j < junrolled2; j+=2) {
dsp_biquad2_run4_sse(obj, &bq[j], bq_stride, d, s, n_samples);
}
if (j < n_bq) {
dsp_biquad_run4_sse(obj, &bq[j], bq_stride, d, s, n_samples);
}
}
for (; i < iunrolled2; i+=2, bq+=bqs2) {
const float *s[2] = { in[i], in[i+1] };
float *d[2] = { out[i], out[i+1] };
if (s[0] == NULL || s[1] == NULL || d[0] == NULL || d[1] == NULL)
break;
j = 0;
if (j < junrolled2) {
dsp_biquad2_run2_sse(obj, &bq[j], bq_stride, d, s, n_samples);
s[0] = d[0];
s[1] = d[1];
j+=2;
}
for (; j < junrolled2; j+=2) {
dsp_biquad2_run2_sse(obj, &bq[j], bq_stride, d, s, n_samples);
}
if (j < n_bq) {
dsp_biquad_run2_sse(obj, &bq[j], bq_stride, d, s, n_samples);
}
}
for (; i < n_src; i++, bq+=bq_stride) {
const float *s = in[i];
float *d = out[i];
if (s == NULL || d == NULL)
continue;
j = 0;
if (j < junrolled2) {
dsp_biquad2_run_sse(obj, &bq[j], d, s, n_samples);
s = d;
j+=2;
}
for (; j < junrolled2; j+=2) {
dsp_biquad2_run_sse(obj, &bq[j], d, s, n_samples);
}
if (j < n_bq) {
dsp_biquad_run1_sse(obj, &bq[j], d, s, n_samples);
}
}
}
void dsp_delay_sse(void *obj, float *buffer, uint32_t *pos, uint32_t n_buffer, uint32_t delay,
float *dst, const float *src, uint32_t n_samples)
{
__m128 t[1];
uint32_t w = *pos;
uint32_t o = n_buffer - delay;
uint32_t n, unrolled;
if (SPA_IS_ALIGNED(src, 16) &&
SPA_IS_ALIGNED(dst, 16))
unrolled = n_samples & ~3;
else
unrolled = 0;
for(n = 0; n < unrolled; n += 4) {
t[0] = _mm_load_ps(&src[n]);
_mm_storeu_ps(&buffer[w], t[0]);
_mm_storeu_ps(&buffer[w+n_buffer], t[0]);
t[0] = _mm_loadu_ps(&buffer[w+o]);
_mm_store_ps(&dst[n], t[0]);
w = w + 4 >= n_buffer ? 0 : w + 4;
}
for(; n < n_samples; n++) {
t[0] = _mm_load_ss(&src[n]);
_mm_store_ss(&buffer[w], t[0]);
_mm_store_ss(&buffer[w+n_buffer], t[0]);
t[0] = _mm_load_ss(&buffer[w+o]);
_mm_store_ss(&dst[n], t[0]);
w = w + 1 >= n_buffer ? 0 : w + 1;
}
*pos = w;
}
inline static void _mm_mul_pz(__m128 *a, __m128 *b, __m128 *d)
{
__m128 ar, ai, br, bi, arbr, arbi, aibi, aibr, dr, di;
ar = _mm_shuffle_ps(a[0], a[1], _MM_SHUFFLE(2,0,2,0)); /* ar0 ar1 ar2 ar3 */
ai = _mm_shuffle_ps(a[0], a[1], _MM_SHUFFLE(3,1,3,1)); /* ai0 ai1 ai2 ai3 */
br = _mm_shuffle_ps(b[0], b[1], _MM_SHUFFLE(2,0,2,0)); /* br0 br1 br2 br3 */
bi = _mm_shuffle_ps(b[0], b[1], _MM_SHUFFLE(3,1,3,1)) /* bi0 bi1 bi2 bi3 */;
arbr = _mm_mul_ps(ar, br); /* ar * br */
arbi = _mm_mul_ps(ar, bi); /* ar * bi */
aibi = _mm_mul_ps(ai, bi); /* ai * bi */
aibr = _mm_mul_ps(ai, br); /* ai * br */
dr = _mm_sub_ps(arbr, aibi); /* ar * br - ai * bi */
di = _mm_add_ps(arbi, aibr); /* ar * bi + ai * br */
d[0] = _mm_unpacklo_ps(dr, di);
d[1] = _mm_unpackhi_ps(dr, di);
}
void dsp_fft_cmul_sse(void *obj, void *fft,
float * SPA_RESTRICT dst, const float * SPA_RESTRICT a,
const float * SPA_RESTRICT b, uint32_t len, const float scale)
{
#ifdef HAVE_FFTW
__m128 s = _mm_set1_ps(scale);
__m128 aa[2], bb[2], dd[2];
uint32_t i, unrolled;
if (SPA_IS_ALIGNED(a, 16) &&
SPA_IS_ALIGNED(b, 16) &&
SPA_IS_ALIGNED(dst, 16))
unrolled = len & ~3;
else
unrolled = 0;
for (i = 0; i < unrolled; i+=4) {
aa[0] = _mm_load_ps(&a[2*i]); /* ar0 ai0 ar1 ai1 */
aa[1] = _mm_load_ps(&a[2*i+4]); /* ar1 ai1 ar2 ai2 */
bb[0] = _mm_load_ps(&b[2*i]); /* br0 bi0 br1 bi1 */
bb[1] = _mm_load_ps(&b[2*i+4]); /* br2 bi2 br3 bi3 */
_mm_mul_pz(aa, bb, dd);
dd[0] = _mm_mul_ps(dd[0], s);
dd[1] = _mm_mul_ps(dd[1], s);
_mm_store_ps(&dst[2*i], dd[0]);
_mm_store_ps(&dst[2*i+4], dd[1]);
}
for (; i < len; i++) {
dst[2*i ] = (a[2*i] * b[2*i ] - a[2*i+1] * b[2*i+1]) * scale;
dst[2*i+1] = (a[2*i] * b[2*i+1] + a[2*i+1] * b[2*i ]) * scale;
}
#else
pffft_zconvolve(fft, a, b, dst, scale);
#endif
}
void dsp_fft_cmuladd_sse(void *obj, void *fft,
float * SPA_RESTRICT dst, const float * SPA_RESTRICT src,
const float * SPA_RESTRICT a, const float * SPA_RESTRICT b,
uint32_t len, const float scale)
{
#ifdef HAVE_FFTW
__m128 s = _mm_set1_ps(scale);
__m128 aa[2], bb[2], dd[2], t[2];
uint32_t i, unrolled;
if (SPA_IS_ALIGNED(a, 16) &&
SPA_IS_ALIGNED(b, 16) &&
SPA_IS_ALIGNED(src, 16) &&
SPA_IS_ALIGNED(dst, 16))
unrolled = len & ~3;
else
unrolled = 0;
for (i = 0; i < unrolled; i+=4) {
aa[0] = _mm_load_ps(&a[2*i]); /* ar0 ai0 ar1 ai1 */
aa[1] = _mm_load_ps(&a[2*i+4]); /* ar1 ai1 ar2 ai2 */
bb[0] = _mm_load_ps(&b[2*i]); /* br0 bi0 br1 bi1 */
bb[1] = _mm_load_ps(&b[2*i+4]); /* br2 bi2 br3 bi3 */
_mm_mul_pz(aa, bb, dd);
dd[0] = _mm_mul_ps(dd[0], s);
dd[1] = _mm_mul_ps(dd[1], s);
t[0] = _mm_load_ps(&src[2*i]);
t[1] = _mm_load_ps(&src[2*i+4]);
t[0] = _mm_add_ps(t[0], dd[0]);
t[1] = _mm_add_ps(t[1], dd[1]);
_mm_store_ps(&dst[2*i], t[0]);
_mm_store_ps(&dst[2*i+4], t[1]);
}
for (; i < len; i++) {
dst[2*i ] = src[2*i ] + (a[2*i] * b[2*i ] - a[2*i+1] * b[2*i+1]) * scale;
dst[2*i+1] = src[2*i+1] + (a[2*i] * b[2*i+1] + a[2*i+1] * b[2*i ]) * scale;
}
#else
pffft_zconvolve_accumulate(fft, a, b, src, dst, scale);
#endif
}

View file

@ -1,124 +0,0 @@
/* Spa */
/* SPDX-FileCopyrightText: Copyright © 2022 Wim Taymans */
/* SPDX-License-Identifier: MIT */
#include <string.h>
#include <stdio.h>
#include <math.h>
#include <time.h>
#include <spa/support/cpu.h>
#include <spa/utils/defs.h>
#include <spa/param/audio/format-utils.h>
#include "pffft.h"
#include "audio-dsp-impl.h"
struct dsp_info {
uint32_t cpu_flags;
struct spa_fga_dsp_methods funcs;
};
static const struct dsp_info dsp_table[] =
{
#if defined (HAVE_AVX)
{ SPA_CPU_FLAG_AVX,
.funcs.clear = dsp_clear_c,
.funcs.copy = dsp_copy_c,
.funcs.mix_gain = dsp_mix_gain_avx,
.funcs.biquad_run = dsp_biquad_run_sse,
.funcs.sum = dsp_sum_avx,
.funcs.linear = dsp_linear_c,
.funcs.mult = dsp_mult_c,
.funcs.fft_new = dsp_fft_new_c,
.funcs.fft_free = dsp_fft_free_c,
.funcs.fft_memalloc = dsp_fft_memalloc_c,
.funcs.fft_memfree = dsp_fft_memfree_c,
.funcs.fft_memclear = dsp_fft_memclear_c,
.funcs.fft_run = dsp_fft_run_c,
.funcs.fft_cmul = dsp_fft_cmul_avx,
.funcs.fft_cmuladd = dsp_fft_cmuladd_avx,
.funcs.delay = dsp_delay_sse,
},
#endif
#if defined (HAVE_SSE)
{ SPA_CPU_FLAG_SSE,
.funcs.clear = dsp_clear_c,
.funcs.copy = dsp_copy_c,
.funcs.mix_gain = dsp_mix_gain_sse,
.funcs.biquad_run = dsp_biquad_run_sse,
.funcs.sum = dsp_sum_sse,
.funcs.linear = dsp_linear_c,
.funcs.mult = dsp_mult_c,
.funcs.fft_new = dsp_fft_new_c,
.funcs.fft_free = dsp_fft_free_c,
.funcs.fft_memalloc = dsp_fft_memalloc_c,
.funcs.fft_memfree = dsp_fft_memfree_c,
.funcs.fft_memclear = dsp_fft_memclear_c,
.funcs.fft_run = dsp_fft_run_c,
.funcs.fft_cmul = dsp_fft_cmul_sse,
.funcs.fft_cmuladd = dsp_fft_cmuladd_sse,
.funcs.delay = dsp_delay_sse,
},
#endif
{ 0,
.funcs.clear = dsp_clear_c,
.funcs.copy = dsp_copy_c,
.funcs.mix_gain = dsp_mix_gain_c,
.funcs.biquad_run = dsp_biquad_run_c,
.funcs.sum = dsp_sum_c,
.funcs.linear = dsp_linear_c,
.funcs.mult = dsp_mult_c,
.funcs.fft_new = dsp_fft_new_c,
.funcs.fft_free = dsp_fft_free_c,
.funcs.fft_memalloc = dsp_fft_memalloc_c,
.funcs.fft_memfree = dsp_fft_memfree_c,
.funcs.fft_memclear = dsp_fft_memclear_c,
.funcs.fft_run = dsp_fft_run_c,
.funcs.fft_cmul = dsp_fft_cmul_c,
.funcs.fft_cmuladd = dsp_fft_cmuladd_c,
.funcs.delay = dsp_delay_c,
},
};
#define MATCH_CPU_FLAGS(a,b) ((a) == 0 || ((a) & (b)) == a)
static const struct dsp_info *find_dsp_info(uint32_t cpu_flags)
{
SPA_FOR_EACH_ELEMENT_VAR(dsp_table, t) {
if (MATCH_CPU_FLAGS(t->cpu_flags, cpu_flags))
return t;
}
return NULL;
}
void spa_fga_dsp_free(struct spa_fga_dsp *dsp)
{
free(dsp);
}
struct spa_fga_dsp * spa_fga_dsp_new(uint32_t cpu_flags)
{
const struct dsp_info *info;
struct spa_fga_dsp *dsp;
info = find_dsp_info(cpu_flags);
if (info == NULL) {
errno = ENOTSUP;
return NULL;
}
dsp = calloc(1, sizeof(*dsp));
if (dsp == NULL)
return NULL;
pffft_select_cpu(cpu_flags);
dsp->cpu_flags = cpu_flags;
dsp->iface = SPA_INTERFACE_INIT(
SPA_TYPE_INTERFACE_FILTER_GRAPH_AudioDSP,
SPA_VERSION_FGA_DSP,
&info->funcs, dsp);
return dsp;
}

View file

@ -1,103 +0,0 @@
/* Spa */
/* SPDX-FileCopyrightText: Copyright © 2022 Wim Taymans */
/* SPDX-License-Identifier: MIT */
#ifndef SPA_FGA_DSP_H
#define SPA_FGA_DSP_H
#include <spa/utils/defs.h>
#include <spa/utils/hook.h>
#include "biquad.h"
#define SPA_TYPE_INTERFACE_FILTER_GRAPH_AudioDSP SPA_TYPE_INFO_INTERFACE_BASE "FilterGraph:AudioDSP"
#define SPA_VERSION_FGA_DSP 0
struct spa_fga_dsp {
struct spa_interface iface;
uint32_t cpu_flags;
};
struct spa_fga_dsp_methods {
#define SPA_VERSION_FGA_DSP_METHODS 0
uint32_t version;
void (*clear) (void *obj, void * SPA_RESTRICT dst, uint32_t n_samples);
void (*copy) (void *obj,
void * SPA_RESTRICT dst,
const void * SPA_RESTRICT src, uint32_t n_samples);
void (*mix_gain) (void *obj,
void * SPA_RESTRICT dst,
const void * SPA_RESTRICT src[],
float gain[], uint32_t n_src, uint32_t n_samples);
void (*sum) (void *obj,
float * dst, const float * SPA_RESTRICT a,
const float * SPA_RESTRICT b, uint32_t n_samples);
void *(*fft_new) (void *obj, uint32_t size, bool real);
void (*fft_free) (void *obj, void *fft);
void *(*fft_memalloc) (void *obj, uint32_t size, bool real);
void (*fft_memfree) (void *obj, void *mem);
void (*fft_memclear) (void *obj, void *mem, uint32_t size, bool real);
void (*fft_run) (void *obj, void *fft, int direction,
const float * SPA_RESTRICT src, float * SPA_RESTRICT dst);
void (*fft_cmul) (void *obj, void *fft,
float * SPA_RESTRICT dst, const float * SPA_RESTRICT a,
const float * SPA_RESTRICT b, uint32_t len, const float scale);
void (*fft_cmuladd) (void *obj, void *fft,
float * dst, const float * src,
const float * SPA_RESTRICT a, const float * SPA_RESTRICT b,
uint32_t len, const float scale);
void (*linear) (void *obj,
float * dst, const float * SPA_RESTRICT src,
const float mult, const float add, uint32_t n_samples);
void (*mult) (void *obj,
void * SPA_RESTRICT dst,
const void * SPA_RESTRICT src[], uint32_t n_src, uint32_t n_samples);
void (*biquad_run) (void *obj, struct biquad *bq, uint32_t n_bq, uint32_t bq_stride,
float * SPA_RESTRICT out[], const float * SPA_RESTRICT in[],
uint32_t n_src, uint32_t n_samples);
void (*delay) (void *obj, float *buffer, uint32_t *pos, uint32_t n_buffer, uint32_t delay,
float *dst, const float *src, uint32_t n_samples);
};
#define spa_fga_dsp_method_r(o,type,method,version,...) \
({ \
type _res = NULL; \
struct spa_fga_dsp *_o = o; \
spa_interface_call_fast_res(&_o->iface, \
struct spa_fga_dsp_methods, _res, \
method, version, ##__VA_ARGS__); \
_res; \
})
#define spa_fga_dsp_method(o,method,version,...) \
({ \
struct spa_fga_dsp *_o = o; \
spa_interface_call_fast(&_o->iface, \
struct spa_fga_dsp_methods, \
method, version, ##__VA_ARGS__); \
})
#define spa_fga_dsp_clear(o,...) spa_fga_dsp_method(o,clear,0,__VA_ARGS__)
#define spa_fga_dsp_copy(o,...) spa_fga_dsp_method(o,copy,0,__VA_ARGS__)
#define spa_fga_dsp_mix_gain(o,...) spa_fga_dsp_method(o,mix_gain,0,__VA_ARGS__)
#define spa_fga_dsp_biquad_run(o,...) spa_fga_dsp_method(o,biquad_run,0,__VA_ARGS__)
#define spa_fga_dsp_sum(o,...) spa_fga_dsp_method(o,sum,0,__VA_ARGS__)
#define spa_fga_dsp_linear(o,...) spa_fga_dsp_method(o,linear,0,__VA_ARGS__)
#define spa_fga_dsp_mult(o,...) spa_fga_dsp_method(o,mult,0,__VA_ARGS__)
#define spa_fga_dsp_delay(o,...) spa_fga_dsp_method(o,delay,0,__VA_ARGS__)
#define spa_fga_dsp_fft_new(o,...) spa_fga_dsp_method_r(o,void*,fft_new,0,__VA_ARGS__)
#define spa_fga_dsp_fft_free(o,...) spa_fga_dsp_method(o,fft_free,0,__VA_ARGS__)
#define spa_fga_dsp_fft_memalloc(o,...) spa_fga_dsp_method_r(o,void*,fft_memalloc,0,__VA_ARGS__)
#define spa_fga_dsp_fft_memfree(o,...) spa_fga_dsp_method(o,fft_memfree,0,__VA_ARGS__)
#define spa_fga_dsp_fft_memclear(o,...) spa_fga_dsp_method(o,fft_memclear,0,__VA_ARGS__)
#define spa_fga_dsp_fft_run(o,...) spa_fga_dsp_method(o,fft_run,0,__VA_ARGS__)
#define spa_fga_dsp_fft_cmul(o,...) spa_fga_dsp_method(o,fft_cmul,0,__VA_ARGS__)
#define spa_fga_dsp_fft_cmul(o,...) spa_fga_dsp_method(o,fft_cmul,0,__VA_ARGS__)
#define spa_fga_dsp_fft_cmuladd(o,...) spa_fga_dsp_method(o,fft_cmuladd,0,__VA_ARGS__)
#endif /* SPA_FGA_DSP_H */

View file

@ -1,109 +0,0 @@
/* PipeWire */
/* SPDX-FileCopyrightText: Copyright © 2021 Wim Taymans */
/* SPDX-License-Identifier: MIT */
#ifndef SPA_FGA_PLUGIN_H
#define SPA_FGA_PLUGIN_H
#include <stdint.h>
#include <stddef.h>
#include <spa/utils/defs.h>
#include <spa/utils/hook.h>
#include <spa/support/plugin.h>
#define SPA_TYPE_INTERFACE_FILTER_GRAPH_AudioPlugin SPA_TYPE_INFO_INTERFACE_BASE "FilterGraph:AudioPlugin"
#define SPA_VERSION_FGA_PLUGIN 0
struct spa_fga_plugin { struct spa_interface iface; };
struct spa_fga_plugin_methods {
#define SPA_VERSION_FGA_PLUGIN_METHODS 0
uint32_t version;
const struct spa_fga_descriptor *(*make_desc) (void *plugin, const char *name);
};
struct spa_fga_port {
uint32_t index;
const char *name;
#define SPA_FGA_PORT_INPUT (1ULL << 0)
#define SPA_FGA_PORT_OUTPUT (1ULL << 1)
#define SPA_FGA_PORT_CONTROL (1ULL << 2)
#define SPA_FGA_PORT_AUDIO (1ULL << 3)
uint64_t flags;
#define SPA_FGA_HINT_BOOLEAN (1ULL << 2)
#define SPA_FGA_HINT_SAMPLE_RATE (1ULL << 3)
#define SPA_FGA_HINT_INTEGER (1ULL << 5)
uint64_t hint;
float def;
float min;
float max;
};
#define SPA_FGA_IS_PORT_INPUT(x) ((x) & SPA_FGA_PORT_INPUT)
#define SPA_FGA_IS_PORT_OUTPUT(x) ((x) & SPA_FGA_PORT_OUTPUT)
#define SPA_FGA_IS_PORT_CONTROL(x) ((x) & SPA_FGA_PORT_CONTROL)
#define SPA_FGA_IS_PORT_AUDIO(x) ((x) & SPA_FGA_PORT_AUDIO)
struct spa_fga_descriptor {
const char *name;
#define SPA_FGA_DESCRIPTOR_SUPPORTS_NULL_DATA (1ULL << 0)
#define SPA_FGA_DESCRIPTOR_COPY (1ULL << 1)
uint64_t flags;
void (*free) (const struct spa_fga_descriptor *desc);
uint32_t n_ports;
struct spa_fga_port *ports;
void *(*instantiate) (const struct spa_fga_plugin *plugin, const struct spa_fga_descriptor *desc,
unsigned long SampleRate, int index, const char *config);
void (*cleanup) (void *instance);
void (*connect_port) (void *instance, unsigned long port, float *data);
void (*control_changed) (void *instance);
void (*activate) (void *instance);
void (*deactivate) (void *instance);
void (*run) (void *instance, unsigned long SampleCount);
};
static inline void spa_fga_descriptor_free(const struct spa_fga_descriptor *desc)
{
if (desc->free)
desc->free(desc);
}
#define spa_fga_plugin_method_r(o,method,version,...) \
({ \
const void * _res = NULL; \
struct spa_fga_plugin *_o = o; \
spa_interface_call_fast_res(&_o->iface, \
struct spa_fga_plugin_methods, _res, \
method, version, ##__VA_ARGS__); \
_res; \
})
#define spa_fga_plugin_method(o,method,version,...) \
({ \
struct spa_fga_plugin *_o = o; \
spa_interface_call_fast(&_o->iface, \
struct spa_fga_plugin_methods, \
method, version, ##__VA_ARGS__); \
})
#define spa_fga_plugin_make_desc(o,...) spa_fga_plugin_method_r(o,make_desc,0,__VA_ARGS__)
typedef struct spa_fga_plugin *(spa_filter_graph_audio_plugin_load_func_t)(const struct spa_support *support,
uint32_t n_support, const char *path, const struct spa_dict *info);
#define SPA_FILTER_GRAPH_AUDIO_PLUGIN_LOAD_FUNC_NAME "spa_filter_graph_audio_plugin_load"
#endif /* PLUGIN_H */

View file

@ -1,374 +0,0 @@
/* Copyright (c) 2013 The Chromium OS Authors. All rights reserved.
* Use of this source code is governed by a BSD-style license that can be
* found in the LICENSE file.
*/
/* Copyright (C) 2010 Google Inc. All rights reserved.
* Use of this source code is governed by a BSD-style license that can be
* found in the LICENSE.WEBKIT file.
*/
#include <math.h>
#include "biquad.h"
#ifndef M_PI
#define M_PI 3.14159265358979323846
#endif
/* S = 1 in Q */
#define BIQUAD_SHELVING_DEFAULT_Q 0.707106781186548
static void set_coefficient(struct biquad *bq, double b0, double b1, double b2,
double a0, double a1, double a2)
{
double a0_inv = 1 / a0;
bq->b0 = (float)(b0 * a0_inv);
bq->b1 = (float)(b1 * a0_inv);
bq->b2 = (float)(b2 * a0_inv);
bq->a1 = (float)(a1 * a0_inv);
bq->a2 = (float)(a2 * a0_inv);
}
static void biquad_lowpass(struct biquad *bq, double cutoff, double resonance)
{
/* Limit cutoff to 0 to 1. */
cutoff = fmax(0.0, fmin(cutoff, 1.0));
if (cutoff == 1 || cutoff == 0) {
/* When cutoff is 1, the z-transform is 1.
* When cutoff is zero, nothing gets through the filter, so set
* coefficients up correctly.
*/
set_coefficient(bq, cutoff, 0, 0, 1, 0, 0);
return;
}
/* Compute biquad coefficients for lowpass filter */
resonance = fmax(0.0, resonance); /* can't go negative */
double g = pow(10.0, 0.05 * resonance);
double d = sqrt((4 - sqrt(16 - 16 / (g * g))) / 2);
double theta = M_PI * cutoff;
double sn = 0.5 * d * sin(theta);
double beta = 0.5 * (1 - sn) / (1 + sn);
double gamma = (0.5 + beta) * cos(theta);
double alpha = 0.25 * (0.5 + beta - gamma);
double b0 = 2 * alpha;
double b1 = 2 * 2 * alpha;
double b2 = 2 * alpha;
double a1 = 2 * -gamma;
double a2 = 2 * beta;
set_coefficient(bq, b0, b1, b2, 1, a1, a2);
}
static void biquad_highpass(struct biquad *bq, double cutoff, double resonance)
{
/* Limit cutoff to 0 to 1. */
cutoff = fmax(0.0, fmin(cutoff, 1.0));
if (cutoff == 1 || cutoff == 0) {
/* When cutoff is one, the z-transform is 0. */
/* When cutoff is zero, we need to be careful because the above
* gives a quadratic divided by the same quadratic, with poles
* and zeros on the unit circle in the same place. When cutoff
* is zero, the z-transform is 1.
*/
set_coefficient(bq, 1 - cutoff, 0, 0, 1, 0, 0);
return;
}
/* Compute biquad coefficients for highpass filter */
resonance = fmax(0.0, resonance); /* can't go negative */
double g = pow(10.0, 0.05 * resonance);
double d = sqrt((4 - sqrt(16 - 16 / (g * g))) / 2);
double theta = M_PI * cutoff;
double sn = 0.5 * d * sin(theta);
double beta = 0.5 * (1 - sn) / (1 + sn);
double gamma = (0.5 + beta) * cos(theta);
double alpha = 0.25 * (0.5 + beta + gamma);
double b0 = 2 * alpha;
double b1 = 2 * -2 * alpha;
double b2 = 2 * alpha;
double a1 = 2 * -gamma;
double a2 = 2 * beta;
set_coefficient(bq, b0, b1, b2, 1, a1, a2);
}
static void biquad_bandpass(struct biquad *bq, double frequency, double Q)
{
/* No negative frequencies allowed. */
frequency = fmax(0.0, frequency);
/* Don't let Q go negative, which causes an unstable filter. */
Q = fmax(0.0, Q);
if (frequency <= 0 || frequency >= 1) {
/* When the cutoff is zero, the z-transform approaches 0, if Q
* > 0. When both Q and cutoff are zero, the z-transform is
* pretty much undefined. What should we do in this case?
* For now, just make the filter 0. When the cutoff is 1, the
* z-transform also approaches 0.
*/
set_coefficient(bq, 0, 0, 0, 1, 0, 0);
return;
}
if (Q <= 0) {
/* When Q = 0, the above formulas have problems. If we
* look at the z-transform, we can see that the limit
* as Q->0 is 1, so set the filter that way.
*/
set_coefficient(bq, 1, 0, 0, 1, 0, 0);
return;
}
double w0 = M_PI * frequency;
double alpha = sin(w0) / (2 * Q);
double k = cos(w0);
double b0 = alpha;
double b1 = 0;
double b2 = -alpha;
double a0 = 1 + alpha;
double a1 = -2 * k;
double a2 = 1 - alpha;
set_coefficient(bq, b0, b1, b2, a0, a1, a2);
}
static void biquad_lowshelf(struct biquad *bq, double frequency, double Q,
double db_gain)
{
/* Clip frequencies to between 0 and 1, inclusive. */
frequency = fmax(0.0, fmin(frequency, 1.0));
double A = pow(10.0, db_gain / 40);
if (frequency == 1) {
/* The z-transform is a constant gain. */
set_coefficient(bq, A * A, 0, 0, 1, 0, 0);
return;
}
if (frequency <= 0) {
/* When frequency is 0, the z-transform is 1. */
set_coefficient(bq, 1, 0, 0, 1, 0, 0);
return;
}
/* Set Q to an equivalent value to S = 1 if not specified */
if (Q <= 0)
Q = BIQUAD_SHELVING_DEFAULT_Q;
double w0 = M_PI * frequency;
double alpha = sin(w0) / (2 * Q);
double k = cos(w0);
double k2 = 2 * sqrt(A) * alpha;
double a_plus_one = A + 1;
double a_minus_one = A - 1;
double b0 = A * (a_plus_one - a_minus_one * k + k2);
double b1 = 2 * A * (a_minus_one - a_plus_one * k);
double b2 = A * (a_plus_one - a_minus_one * k - k2);
double a0 = a_plus_one + a_minus_one * k + k2;
double a1 = -2 * (a_minus_one + a_plus_one * k);
double a2 = a_plus_one + a_minus_one * k - k2;
set_coefficient(bq, b0, b1, b2, a0, a1, a2);
}
static void biquad_highshelf(struct biquad *bq, double frequency, double Q,
double db_gain)
{
/* Clip frequencies to between 0 and 1, inclusive. */
frequency = fmax(0.0, fmin(frequency, 1.0));
double A = pow(10.0, db_gain / 40);
if (frequency == 1) {
/* The z-transform is 1. */
set_coefficient(bq, 1, 0, 0, 1, 0, 0);
return;
}
if (frequency <= 0) {
/* When frequency = 0, the filter is just a gain, A^2. */
set_coefficient(bq, A * A, 0, 0, 1, 0, 0);
return;
}
/* Set Q to an equivalent value to S = 1 if not specified */
if (Q <= 0)
Q = BIQUAD_SHELVING_DEFAULT_Q;
double w0 = M_PI * frequency;
double alpha = sin(w0) / (2 * Q);
double k = cos(w0);
double k2 = 2 * sqrt(A) * alpha;
double a_plus_one = A + 1;
double a_minus_one = A - 1;
double b0 = A * (a_plus_one + a_minus_one * k + k2);
double b1 = -2 * A * (a_minus_one + a_plus_one * k);
double b2 = A * (a_plus_one + a_minus_one * k - k2);
double a0 = a_plus_one - a_minus_one * k + k2;
double a1 = 2 * (a_minus_one - a_plus_one * k);
double a2 = a_plus_one - a_minus_one * k - k2;
set_coefficient(bq, b0, b1, b2, a0, a1, a2);
}
static void biquad_peaking(struct biquad *bq, double frequency, double Q,
double db_gain)
{
/* Clip frequencies to between 0 and 1, inclusive. */
frequency = fmax(0.0, fmin(frequency, 1.0));
/* Don't let Q go negative, which causes an unstable filter. */
Q = fmax(0.0, Q);
double A = pow(10.0, db_gain / 40);
if (frequency <= 0 || frequency >= 1) {
/* When frequency is 0 or 1, the z-transform is 1. */
set_coefficient(bq, 1, 0, 0, 1, 0, 0);
return;
}
if (Q <= 0) {
/* When Q = 0, the above formulas have problems. If we
* look at the z-transform, we can see that the limit
* as Q->0 is A^2, so set the filter that way.
*/
set_coefficient(bq, A * A, 0, 0, 1, 0, 0);
return;
}
double w0 = M_PI * frequency;
double alpha = sin(w0) / (2 * Q);
double k = cos(w0);
double b0 = 1 + alpha * A;
double b1 = -2 * k;
double b2 = 1 - alpha * A;
double a0 = 1 + alpha / A;
double a1 = -2 * k;
double a2 = 1 - alpha / A;
set_coefficient(bq, b0, b1, b2, a0, a1, a2);
}
static void biquad_notch(struct biquad *bq, double frequency, double Q)
{
/* Clip frequencies to between 0 and 1, inclusive. */
frequency = fmax(0.0, fmin(frequency, 1.0));
/* Don't let Q go negative, which causes an unstable filter. */
Q = fmax(0.0, Q);
if (frequency <= 0 || frequency >= 1) {
/* When frequency is 0 or 1, the z-transform is 1. */
set_coefficient(bq, 1, 0, 0, 1, 0, 0);
return;
}
if (Q <= 0) {
/* When Q = 0, the above formulas have problems. If we
* look at the z-transform, we can see that the limit
* as Q->0 is 0, so set the filter that way.
*/
set_coefficient(bq, 0, 0, 0, 1, 0, 0);
return;
}
double w0 = M_PI * frequency;
double alpha = sin(w0) / (2 * Q);
double k = cos(w0);
double b0 = 1;
double b1 = -2 * k;
double b2 = 1;
double a0 = 1 + alpha;
double a1 = -2 * k;
double a2 = 1 - alpha;
set_coefficient(bq, b0, b1, b2, a0, a1, a2);
}
static void biquad_allpass(struct biquad *bq, double frequency, double Q)
{
/* Clip frequencies to between 0 and 1, inclusive. */
frequency = fmax(0.0, fmin(frequency, 1.0));
/* Don't let Q go negative, which causes an unstable filter. */
Q = fmax(0.0, Q);
if (frequency <= 0 || frequency >= 1) {
/* When frequency is 0 or 1, the z-transform is 1. */
set_coefficient(bq, 1, 0, 0, 1, 0, 0);
return;
}
if (Q <= 0) {
/* When Q = 0, the above formulas have problems. If we
* look at the z-transform, we can see that the limit
* as Q->0 is -1, so set the filter that way.
*/
set_coefficient(bq, -1, 0, 0, 1, 0, 0);
return;
}
double w0 = M_PI * frequency;
double alpha = sin(w0) / (2 * Q);
double k = cos(w0);
double b0 = 1 - alpha;
double b1 = -2 * k;
double b2 = 1 + alpha;
double a0 = 1 + alpha;
double a1 = -2 * k;
double a2 = 1 - alpha;
set_coefficient(bq, b0, b1, b2, a0, a1, a2);
}
void biquad_set(struct biquad *bq, enum biquad_type type, double freq, double Q,
double gain)
{
/* Clear history values. */
bq->type = type;
bq->x1 = 0;
bq->x2 = 0;
switch (type) {
case BQ_LOWPASS:
biquad_lowpass(bq, freq, Q);
break;
case BQ_HIGHPASS:
biquad_highpass(bq, freq, Q);
break;
case BQ_BANDPASS:
biquad_bandpass(bq, freq, Q);
break;
case BQ_LOWSHELF:
biquad_lowshelf(bq, freq, Q, gain);
break;
case BQ_HIGHSHELF:
biquad_highshelf(bq, freq, Q, gain);
break;
case BQ_PEAKING:
biquad_peaking(bq, freq, Q, gain);
break;
case BQ_NOTCH:
biquad_notch(bq, freq, Q);
break;
case BQ_ALLPASS:
biquad_allpass(bq, freq, Q);
break;
case BQ_NONE:
case BQ_RAW:
/* Default is an identity filter. */
set_coefficient(bq, 1, 0, 0, 1, 0, 0);
break;
}
}

View file

@ -1,58 +0,0 @@
/* Copyright (c) 2013 The Chromium OS Authors. All rights reserved.
* Use of this source code is governed by a BSD-style license that can be
* found in the LICENSE file.
*/
#ifndef BIQUAD_H_
#define BIQUAD_H_
#ifdef __cplusplus
extern "C" {
#endif
/* The type of the biquad filters */
enum biquad_type {
BQ_NONE,
BQ_LOWPASS,
BQ_HIGHPASS,
BQ_BANDPASS,
BQ_LOWSHELF,
BQ_HIGHSHELF,
BQ_PEAKING,
BQ_NOTCH,
BQ_ALLPASS,
BQ_RAW,
};
/* The biquad filter parameters. The transfer function H(z) is (b0 + b1 * z^(-1)
* + b2 * z^(-2)) / (1 + a1 * z^(-1) + a2 * z^(-2)). The previous two inputs
* are stored in x1 and x2, and the previous two outputs are stored in y1 and
* y2.
*
* We use double during the coefficients calculation for better accuracy, but
* float is used during the actual filtering for faster computation.
*/
struct biquad {
enum biquad_type type;
float b0, b1, b2;
float a1, a2;
float x1, x2;
};
/* Initialize a biquad filter parameters from its type and parameters.
* Args:
* bq - The biquad filter we want to set.
* type - The type of the biquad filter.
* frequency - The value should be in the range [0, 1]. It is relative to
* half of the sampling rate.
* Q - Quality factor. See Web Audio API for details.
* gain - The value is in dB. See Web Audio API for details.
*/
void biquad_set(struct biquad *bq, enum biquad_type type, double freq, double Q,
double gain);
#ifdef __cplusplus
} /* extern "C" */
#endif
#endif /* BIQUAD_H_ */

File diff suppressed because it is too large Load diff

View file

@ -1,392 +0,0 @@
/* PipeWire */
/* SPDX-FileCopyrightText: Copyright © 2017 HiFi-LoFi */
/* SPDX-FileCopyrightText: Copyright © 2021 Wim Taymans */
/* SPDX-License-Identifier: MIT */
/* Adapted from https://github.com/HiFi-LoFi/FFTConvolver */
#include "convolver.h"
#include <spa/utils/defs.h>
#include <math.h>
struct convolver1 {
struct spa_fga_dsp *dsp;
int blockSize;
int segSize;
int segCount;
int fftComplexSize;
float **segments;
float **segmentsIr;
float *fft_buffer;
void *fft;
void *ifft;
float *pre_mult;
float *conv;
float *overlap;
float *inputBuffer;
int inputBufferFill;
int current;
float scale;
};
static int next_power_of_two(int val)
{
int r = 1;
while (r < val)
r *= 2;
return r;
}
static void convolver1_reset(struct convolver1 *conv)
{
int i;
for (i = 0; i < conv->segCount; i++)
spa_fga_dsp_fft_memclear(conv->dsp, conv->segments[i], conv->fftComplexSize, false);
spa_fga_dsp_fft_memclear(conv->dsp, conv->overlap, conv->blockSize, true);
spa_fga_dsp_fft_memclear(conv->dsp, conv->inputBuffer, conv->segSize, true);
spa_fga_dsp_fft_memclear(conv->dsp, conv->pre_mult, conv->fftComplexSize, false);
spa_fga_dsp_fft_memclear(conv->dsp, conv->conv, conv->fftComplexSize, false);
conv->inputBufferFill = 0;
conv->current = 0;
}
static void convolver1_free(struct convolver1 *conv)
{
int i;
for (i = 0; i < conv->segCount; i++) {
if (conv->segments)
spa_fga_dsp_fft_memfree(conv->dsp, conv->segments[i]);
if (conv->segmentsIr)
spa_fga_dsp_fft_memfree(conv->dsp, conv->segmentsIr[i]);
}
if (conv->fft)
spa_fga_dsp_fft_free(conv->dsp, conv->fft);
if (conv->ifft)
spa_fga_dsp_fft_free(conv->dsp, conv->ifft);
if (conv->fft_buffer)
spa_fga_dsp_fft_memfree(conv->dsp, conv->fft_buffer);
free(conv->segments);
free(conv->segmentsIr);
spa_fga_dsp_fft_memfree(conv->dsp, conv->pre_mult);
spa_fga_dsp_fft_memfree(conv->dsp, conv->conv);
spa_fga_dsp_fft_memfree(conv->dsp, conv->overlap);
spa_fga_dsp_fft_memfree(conv->dsp, conv->inputBuffer);
free(conv);
}
static struct convolver1 *convolver1_new(struct spa_fga_dsp *dsp, int block, const float *ir, int irlen)
{
struct convolver1 *conv;
int i;
if (block == 0)
return NULL;
while (irlen > 0 && fabs(ir[irlen-1]) < 0.000001f)
irlen--;
conv = calloc(1, sizeof(*conv));
if (conv == NULL)
return NULL;
if (irlen == 0)
return conv;
conv->dsp = dsp;
conv->blockSize = next_power_of_two(block);
conv->segSize = 2 * conv->blockSize;
conv->segCount = (irlen + conv->blockSize-1) / conv->blockSize;
conv->fftComplexSize = (conv->segSize / 2) + 1;
conv->fft = spa_fga_dsp_fft_new(conv->dsp, conv->segSize, true);
if (conv->fft == NULL)
goto error;
conv->ifft = spa_fga_dsp_fft_new(conv->dsp, conv->segSize, true);
if (conv->ifft == NULL)
goto error;
conv->fft_buffer = spa_fga_dsp_fft_memalloc(conv->dsp, conv->segSize, true);
if (conv->fft_buffer == NULL)
goto error;
conv->segments = calloc(conv->segCount, sizeof(float*));
conv->segmentsIr = calloc(conv->segCount, sizeof(float*));
if (conv->segments == NULL || conv->segmentsIr == NULL)
goto error;
for (i = 0; i < conv->segCount; i++) {
int left = irlen - (i * conv->blockSize);
int copy = SPA_MIN(conv->blockSize, left);
conv->segments[i] = spa_fga_dsp_fft_memalloc(conv->dsp, conv->fftComplexSize, false);
conv->segmentsIr[i] = spa_fga_dsp_fft_memalloc(conv->dsp, conv->fftComplexSize, false);
if (conv->segments[i] == NULL || conv->segmentsIr[i] == NULL)
goto error;
spa_fga_dsp_copy(conv->dsp, conv->fft_buffer, &ir[i * conv->blockSize], copy);
if (copy < conv->segSize)
spa_fga_dsp_fft_memclear(conv->dsp, conv->fft_buffer + copy, conv->segSize - copy, true);
spa_fga_dsp_fft_run(conv->dsp, conv->fft, 1, conv->fft_buffer, conv->segmentsIr[i]);
}
conv->pre_mult = spa_fga_dsp_fft_memalloc(conv->dsp, conv->fftComplexSize, false);
conv->conv = spa_fga_dsp_fft_memalloc(conv->dsp, conv->fftComplexSize, false);
conv->overlap = spa_fga_dsp_fft_memalloc(conv->dsp, conv->blockSize, true);
conv->inputBuffer = spa_fga_dsp_fft_memalloc(conv->dsp, conv->segSize, true);
if (conv->pre_mult == NULL || conv->conv == NULL || conv->overlap == NULL ||
conv->inputBuffer == NULL)
goto error;
conv->scale = 1.0f / conv->segSize;
convolver1_reset(conv);
return conv;
error:
convolver1_free(conv);
return NULL;
}
static int convolver1_run(struct convolver1 *conv, const float *input, float *output, int len)
{
int i, processed = 0;
if (conv == NULL || conv->segCount == 0) {
spa_fga_dsp_fft_memclear(conv->dsp, output, len, true);
return len;
}
while (processed < len) {
const int processing = SPA_MIN(len - processed, conv->blockSize - conv->inputBufferFill);
const int inputBufferPos = conv->inputBufferFill;
spa_fga_dsp_copy(conv->dsp, conv->inputBuffer + inputBufferPos, input + processed, processing);
if (inputBufferPos == 0 && processing < conv->blockSize)
spa_fga_dsp_fft_memclear(conv->dsp, conv->inputBuffer + processing, conv->blockSize - processing, true);
spa_fga_dsp_fft_run(conv->dsp, conv->fft, 1, conv->inputBuffer, conv->segments[conv->current]);
if (conv->segCount > 1) {
if (conv->inputBufferFill == 0) {
int indexAudio = (conv->current + 1) % conv->segCount;
spa_fga_dsp_fft_cmul(conv->dsp, conv->fft, conv->pre_mult,
conv->segmentsIr[1],
conv->segments[indexAudio],
conv->fftComplexSize, conv->scale);
for (i = 2; i < conv->segCount; i++) {
indexAudio = (conv->current + i) % conv->segCount;
spa_fga_dsp_fft_cmuladd(conv->dsp, conv->fft,
conv->pre_mult,
conv->pre_mult,
conv->segmentsIr[i],
conv->segments[indexAudio],
conv->fftComplexSize, conv->scale);
}
}
spa_fga_dsp_fft_cmuladd(conv->dsp, conv->fft,
conv->conv,
conv->pre_mult,
conv->segments[conv->current],
conv->segmentsIr[0],
conv->fftComplexSize, conv->scale);
} else {
spa_fga_dsp_fft_cmul(conv->dsp, conv->fft,
conv->conv,
conv->segments[conv->current],
conv->segmentsIr[0],
conv->fftComplexSize, conv->scale);
}
spa_fga_dsp_fft_run(conv->dsp, conv->ifft, -1, conv->conv, conv->fft_buffer);
spa_fga_dsp_sum(conv->dsp, output + processed, conv->fft_buffer + inputBufferPos,
conv->overlap + inputBufferPos, processing);
conv->inputBufferFill += processing;
if (conv->inputBufferFill == conv->blockSize) {
conv->inputBufferFill = 0;
spa_fga_dsp_copy(conv->dsp, conv->overlap, conv->fft_buffer + conv->blockSize, conv->blockSize);
conv->current = (conv->current > 0) ? (conv->current - 1) : (conv->segCount - 1);
}
processed += processing;
}
return len;
}
struct convolver
{
struct spa_fga_dsp *dsp;
int headBlockSize;
int tailBlockSize;
struct convolver1 *headConvolver;
struct convolver1 *tailConvolver0;
float *tailOutput0;
float *tailPrecalculated0;
struct convolver1 *tailConvolver;
float *tailOutput;
float *tailPrecalculated;
float *tailInput;
int tailInputFill;
int precalculatedPos;
};
void convolver_reset(struct convolver *conv)
{
if (conv->headConvolver)
convolver1_reset(conv->headConvolver);
if (conv->tailConvolver0) {
convolver1_reset(conv->tailConvolver0);
spa_fga_dsp_fft_memclear(conv->dsp, conv->tailOutput0, conv->tailBlockSize, true);
spa_fga_dsp_fft_memclear(conv->dsp, conv->tailPrecalculated0, conv->tailBlockSize, true);
}
if (conv->tailConvolver) {
convolver1_reset(conv->tailConvolver);
spa_fga_dsp_fft_memclear(conv->dsp, conv->tailOutput, conv->tailBlockSize, true);
spa_fga_dsp_fft_memclear(conv->dsp, conv->tailPrecalculated, conv->tailBlockSize, true);
}
conv->tailInputFill = 0;
conv->precalculatedPos = 0;
}
struct convolver *convolver_new(struct spa_fga_dsp *dsp, int head_block, int tail_block, const float *ir, int irlen)
{
struct convolver *conv;
int head_ir_len;
if (head_block == 0 || tail_block == 0)
return NULL;
head_block = SPA_MAX(1, head_block);
if (head_block > tail_block)
SPA_SWAP(head_block, tail_block);
while (irlen > 0 && fabs(ir[irlen-1]) < 0.000001f)
irlen--;
conv = calloc(1, sizeof(*conv));
if (conv == NULL)
return NULL;
if (irlen == 0)
return conv;
conv->dsp = dsp;
conv->headBlockSize = next_power_of_two(head_block);
conv->tailBlockSize = next_power_of_two(tail_block);
head_ir_len = SPA_MIN(irlen, conv->tailBlockSize);
conv->headConvolver = convolver1_new(dsp, conv->headBlockSize, ir, head_ir_len);
if (conv->headConvolver == NULL)
goto error;
if (irlen > conv->tailBlockSize) {
int conv1IrLen = SPA_MIN(irlen - conv->tailBlockSize, conv->tailBlockSize);
conv->tailConvolver0 = convolver1_new(dsp, conv->headBlockSize, ir + conv->tailBlockSize, conv1IrLen);
conv->tailOutput0 = spa_fga_dsp_fft_memalloc(conv->dsp, conv->tailBlockSize, true);
conv->tailPrecalculated0 = spa_fga_dsp_fft_memalloc(conv->dsp, conv->tailBlockSize, true);
if (conv->tailConvolver0 == NULL || conv->tailOutput0 == NULL ||
conv->tailPrecalculated0 == NULL)
goto error;
}
if (irlen > 2 * conv->tailBlockSize) {
int tailIrLen = irlen - (2 * conv->tailBlockSize);
conv->tailConvolver = convolver1_new(dsp, conv->tailBlockSize, ir + (2 * conv->tailBlockSize), tailIrLen);
conv->tailOutput = spa_fga_dsp_fft_memalloc(conv->dsp, conv->tailBlockSize, true);
conv->tailPrecalculated = spa_fga_dsp_fft_memalloc(conv->dsp, conv->tailBlockSize, true);
if (conv->tailConvolver == NULL || conv->tailOutput == NULL ||
conv->tailPrecalculated == NULL)
goto error;
}
if (conv->tailConvolver0 || conv->tailConvolver) {
conv->tailInput = spa_fga_dsp_fft_memalloc(conv->dsp, conv->tailBlockSize, true);
if (conv->tailInput == NULL)
goto error;
}
convolver_reset(conv);
return conv;
error:
convolver_free(conv);
return NULL;
}
void convolver_free(struct convolver *conv)
{
if (conv->headConvolver)
convolver1_free(conv->headConvolver);
if (conv->tailConvolver0)
convolver1_free(conv->tailConvolver0);
if (conv->tailConvolver)
convolver1_free(conv->tailConvolver);
spa_fga_dsp_fft_memfree(conv->dsp, conv->tailOutput0);
spa_fga_dsp_fft_memfree(conv->dsp, conv->tailPrecalculated0);
spa_fga_dsp_fft_memfree(conv->dsp, conv->tailOutput);
spa_fga_dsp_fft_memfree(conv->dsp, conv->tailPrecalculated);
spa_fga_dsp_fft_memfree(conv->dsp, conv->tailInput);
free(conv);
}
int convolver_run(struct convolver *conv, const float *input, float *output, int length)
{
convolver1_run(conv->headConvolver, input, output, length);
if (conv->tailInput) {
int processed = 0;
while (processed < length) {
int remaining = length - processed;
int processing = SPA_MIN(remaining, conv->headBlockSize - (conv->tailInputFill % conv->headBlockSize));
if (conv->tailPrecalculated0)
spa_fga_dsp_sum(conv->dsp, &output[processed], &output[processed],
&conv->tailPrecalculated0[conv->precalculatedPos],
processing);
if (conv->tailPrecalculated)
spa_fga_dsp_sum(conv->dsp, &output[processed], &output[processed],
&conv->tailPrecalculated[conv->precalculatedPos],
processing);
conv->precalculatedPos += processing;
spa_fga_dsp_copy(conv->dsp, conv->tailInput + conv->tailInputFill, input + processed, processing);
conv->tailInputFill += processing;
if (conv->tailPrecalculated0 && (conv->tailInputFill % conv->headBlockSize == 0)) {
int blockOffset = conv->tailInputFill - conv->headBlockSize;
convolver1_run(conv->tailConvolver0,
conv->tailInput + blockOffset,
conv->tailOutput0 + blockOffset,
conv->headBlockSize);
if (conv->tailInputFill == conv->tailBlockSize)
SPA_SWAP(conv->tailPrecalculated0, conv->tailOutput0);
}
if (conv->tailPrecalculated &&
conv->tailInputFill == conv->tailBlockSize) {
SPA_SWAP(conv->tailPrecalculated, conv->tailOutput);
convolver1_run(conv->tailConvolver, conv->tailInput,
conv->tailOutput, conv->tailBlockSize);
}
if (conv->tailInputFill == conv->tailBlockSize) {
conv->tailInputFill = 0;
conv->precalculatedPos = 0;
}
processed += processing;
}
}
return 0;
}

View file

@ -1,14 +0,0 @@
/* PipeWire */
/* SPDX-FileCopyrightText: Copyright © 2021 Wim Taymans */
/* SPDX-License-Identifier: MIT */
#include <stdint.h>
#include <stddef.h>
#include "audio-dsp.h"
struct convolver *convolver_new(struct spa_fga_dsp *dsp, int block, int tail, const float *ir, int irlen);
void convolver_free(struct convolver *conv);
void convolver_reset(struct convolver *conv);
int convolver_run(struct convolver *conv, const float *input, float *output, int length);

File diff suppressed because it is too large Load diff

View file

@ -1,104 +0,0 @@
/* Simple Plugin API */
/* SPDX-FileCopyrightText: Copyright © 2024 Wim Taymans */
/* SPDX-License-Identifier: MIT */
#ifndef SPA_FILTER_GRAPH_H
#define SPA_FILTER_GRAPH_H
#ifdef __cplusplus
extern "C" {
#endif
#include <sys/types.h>
#include <spa/utils/defs.h>
#include <spa/utils/hook.h>
#include <spa/pod/builder.h>
/** \defgroup spa_filter_graph Filter Graph
* a graph of filters
*/
/**
* \addtogroup spa_filter_graph
* \{
*/
/**
* A graph of filters
*/
#define SPA_TYPE_INTERFACE_FilterGraph SPA_TYPE_INFO_INTERFACE_BASE "FilterGraph"
#define SPA_VERSION_FILTER_GRAPH 0
struct spa_filter_graph { struct spa_interface iface; };
struct spa_filter_graph_events {
#define SPA_VERSION_FILTER_GRAPH_EVENTS 0
uint32_t version;
void (*apply_props) (void *object, enum spa_direction direction, const struct spa_pod *props);
void (*props_changed) (void *object, enum spa_direction direction);
};
struct spa_filter_graph_chunk {
void *data;
size_t size;
};
struct spa_filter_graph_methods {
#define SPA_VERSION_FILTER_GRAPH_METHODS 0
uint32_t version;
int (*add_listener) (void *object,
struct spa_hook *listener,
const struct spa_filter_graph_events *events,
void *data);
int (*enum_prop_info) (void *object, uint32_t idx, struct spa_pod_builder *b);
int (*get_props) (void *object, struct spa_pod_builder *b, const struct spa_pod **props);
int (*set_props) (void *object, enum spa_direction direction, const struct spa_pod *props);
int (*activate) (void *object, const struct spa_fraction *rate);
int (*deactivate) (void *object);
int (*reset) (void *object);
int (*process) (void *object,
const struct spa_filter_graph_chunk in[], uint32_t n_in,
struct spa_filter_graph_chunk out[], uint32_t n_out);
};
#define spa_filter_graph_method_r(o,method,version,...) \
({ \
volatile int _res = -ENOTSUP; \
struct spa_filter_graph *_o = o; \
spa_interface_call_fast_res(&_o->iface, \
struct spa_filter_graph_methods, _res, \
method, version, ##__VA_ARGS__); \
_res; \
})
#define spa_filter_graph_add_listener(o,...) spa_filter_graph_method_r(o,add_listener,0,__VA_ARGS__)
#define spa_filter_graph_enum_prop_info(o,...) spa_filter_graph_method_r(o,enum_prop_info,0,__VA_ARGS__)
#define spa_filter_graph_get_props(o,...) spa_filter_graph_method_r(o,get_props,0,__VA_ARGS__)
#define spa_filter_graph_set_props(o,...) spa_filter_graph_method_r(o,set_props,0,__VA_ARGS__)
#define spa_filter_graph_activate(o,...) spa_filter_graph_method_r(o,activate,0,__VA_ARGS__)
#define spa_filter_graph_deactivate(o) spa_filter_graph_method_r(o,deactivate,0)
#define spa_filter_graph_reset(o) spa_filter_graph_method_r(o,reset,0)
#define spa_filter_graph_process(o,...) spa_filter_graph_method_r(o,process,0,__VA_ARGS__)
/**
* \}
*/
#ifdef __cplusplus
} /* extern "C" */
#endif
#endif /* SPA_FILTER_GRAPH_H */

View file

@ -1,603 +0,0 @@
/* ladspa.h
Linux Audio Developer's Simple Plugin API Version 1.1[LGPL].
Copyright (C) 2000-2002 Richard W.E. Furse, Paul Barton-Davis,
Stefan Westerfeld.
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public License
as published by the Free Software Foundation; either version 2.1 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
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
USA. */
#ifndef LADSPA_INCLUDED
#define LADSPA_INCLUDED
#define LADSPA_VERSION "1.1"
#define LADSPA_VERSION_MAJOR 1
#define LADSPA_VERSION_MINOR 1
#ifdef __cplusplus
extern "C" {
#endif
/*****************************************************************************/
/* Overview:
There is a large number of synthesis packages in use or development
on the Linux platform at this time. This API (`The Linux Audio
Developer's Simple Plugin API') attempts to give programmers the
ability to write simple `plugin' audio processors in C/C++ and link
them dynamically (`plug') into a range of these packages (`hosts').
It should be possible for any host and any plugin to communicate
completely through this interface.
This API is deliberately short and simple. To achieve compatibility
with a range of promising Linux sound synthesis packages it
attempts to find the `greatest common divisor' in their logical
behaviour. Having said this, certain limiting decisions are
implicit, notably the use of a fixed type (LADSPA_Data) for all
data transfer and absence of a parameterised `initialisation'
phase. See below for the LADSPA_Data typedef.
Plugins are expected to distinguish between control and audio
data. Plugins have `ports' that are inputs or outputs for audio or
control data and each plugin is `run' for a `block' corresponding
to a short time interval measured in samples. Audio data is
communicated using arrays of LADSPA_Data, allowing a block of audio
to be processed by the plugin in a single pass. Control data is
communicated using single LADSPA_Data values. Control data has a
single value at the start of a call to the `run()' or `run_adding()'
function, and may be considered to remain this value for its
duration. The plugin may assume that all its input and output ports
have been connected to the relevant data location (see the
`connect_port()' function below) before it is asked to run.
Plugins will reside in shared object files suitable for dynamic
linking by dlopen() and family. The file will provide a number of
`plugin types' that can be used to instantiate actual plugins
(sometimes known as `plugin instances') that can be connected
together to perform tasks.
This API contains very limited error-handling. */
/*****************************************************************************/
/* Fundamental data type passed in and out of plugin. This data type
is used to communicate audio samples and control values. It is
assumed that the plugin will work sensibly given any numeric input
value although it may have a preferred range (see hints below).
For audio it is generally assumed that 1.0f is the `0dB' reference
amplitude and is a `normal' signal level. */
typedef float LADSPA_Data;
/*****************************************************************************/
/* Special Plugin Properties:
Optional features of the plugin type are encapsulated in the
LADSPA_Properties type. This is assembled by ORing individual
properties together. */
typedef int LADSPA_Properties;
/* Property LADSPA_PROPERTY_REALTIME indicates that the plugin has a
real-time dependency (e.g. listens to a MIDI device) and so its
output must not be cached or subject to significant latency. */
#define LADSPA_PROPERTY_REALTIME 0x1
/* Property LADSPA_PROPERTY_INPLACE_BROKEN indicates that the plugin
may cease to work correctly if the host elects to use the same data
location for both input and output (see connect_port()). This
should be avoided as enabling this flag makes it impossible for
hosts to use the plugin to process audio `in-place.' */
#define LADSPA_PROPERTY_INPLACE_BROKEN 0x2
/* Property LADSPA_PROPERTY_HARD_RT_CAPABLE indicates that the plugin
is capable of running not only in a conventional host but also in a
`hard real-time' environment. To qualify for this the plugin must
satisfy all of the following:
(1) The plugin must not use malloc(), free() or other heap memory
management within its run() or run_adding() functions. All new
memory used in run() must be managed via the stack. These
restrictions only apply to the run() function.
(2) The plugin will not attempt to make use of any library
functions with the exceptions of functions in the ANSI standard C
and C maths libraries, which the host is expected to provide.
(3) The plugin will not access files, devices, pipes, sockets, IPC
or any other mechanism that might result in process or thread
blocking.
(4) The plugin will take an amount of time to execute a run() or
run_adding() call approximately of form (A+B*SampleCount) where A
and B depend on the machine and host in use. This amount of time
may not depend on input signals or plugin state. The host is left
the responsibility to perform timings to estimate upper bounds for
A and B. */
#define LADSPA_PROPERTY_HARD_RT_CAPABLE 0x4
#define LADSPA_IS_REALTIME(x) ((x) & LADSPA_PROPERTY_REALTIME)
#define LADSPA_IS_INPLACE_BROKEN(x) ((x) & LADSPA_PROPERTY_INPLACE_BROKEN)
#define LADSPA_IS_HARD_RT_CAPABLE(x) ((x) & LADSPA_PROPERTY_HARD_RT_CAPABLE)
/*****************************************************************************/
/* Plugin Ports:
Plugins have `ports' that are inputs or outputs for audio or
data. Ports can communicate arrays of LADSPA_Data (for audio
inputs/outputs) or single LADSPA_Data values (for control
input/outputs). This information is encapsulated in the
LADSPA_PortDescriptor type which is assembled by ORing individual
properties together.
Note that a port must be an input or an output port but not both
and that a port must be a control or audio port but not both. */
typedef int LADSPA_PortDescriptor;
/* Property LADSPA_PORT_INPUT indicates that the port is an input. */
#define LADSPA_PORT_INPUT 0x1
/* Property LADSPA_PORT_OUTPUT indicates that the port is an output. */
#define LADSPA_PORT_OUTPUT 0x2
/* Property LADSPA_PORT_CONTROL indicates that the port is a control
port. */
#define LADSPA_PORT_CONTROL 0x4
/* Property LADSPA_PORT_AUDIO indicates that the port is a audio
port. */
#define LADSPA_PORT_AUDIO 0x8
#define LADSPA_IS_PORT_INPUT(x) ((x) & LADSPA_PORT_INPUT)
#define LADSPA_IS_PORT_OUTPUT(x) ((x) & LADSPA_PORT_OUTPUT)
#define LADSPA_IS_PORT_CONTROL(x) ((x) & LADSPA_PORT_CONTROL)
#define LADSPA_IS_PORT_AUDIO(x) ((x) & LADSPA_PORT_AUDIO)
/*****************************************************************************/
/* Plugin Port Range Hints:
The host may wish to provide a representation of data entering or
leaving a plugin (e.g. to generate a GUI automatically). To make
this more meaningful, the plugin should provide `hints' to the host
describing the usual values taken by the data.
Note that these are only hints. The host may ignore them and the
plugin must not assume that data supplied to it is meaningful. If
the plugin receives invalid input data it is expected to continue
to run without failure and, where possible, produce a sensible
output (e.g. a high-pass filter given a negative cutoff frequency
might switch to an all-pass mode).
Hints are meaningful for all input and output ports but hints for
input control ports are expected to be particularly useful.
More hint information is encapsulated in the
LADSPA_PortRangeHintDescriptor type which is assembled by ORing
individual hint types together. Hints may require further
LowerBound and UpperBound information.
All the hint information for a particular port is aggregated in the
LADSPA_PortRangeHint structure. */
typedef int LADSPA_PortRangeHintDescriptor;
/* Hint LADSPA_HINT_BOUNDED_BELOW indicates that the LowerBound field
of the LADSPA_PortRangeHint should be considered meaningful. The
value in this field should be considered the (inclusive) lower
bound of the valid range. If LADSPA_HINT_SAMPLE_RATE is also
specified then the value of LowerBound should be multiplied by the
sample rate. */
#define LADSPA_HINT_BOUNDED_BELOW 0x1
/* Hint LADSPA_HINT_BOUNDED_ABOVE indicates that the UpperBound field
of the LADSPA_PortRangeHint should be considered meaningful. The
value in this field should be considered the (inclusive) upper
bound of the valid range. If LADSPA_HINT_SAMPLE_RATE is also
specified then the value of UpperBound should be multiplied by the
sample rate. */
#define LADSPA_HINT_BOUNDED_ABOVE 0x2
/* Hint LADSPA_HINT_TOGGLED indicates that the data item should be
considered a Boolean toggle. Data less than or equal to zero should
be considered `off' or `false,' and data above zero should be
considered `on' or `true.' LADSPA_HINT_TOGGLED may not be used in
conjunction with any other hint except LADSPA_HINT_DEFAULT_0 or
LADSPA_HINT_DEFAULT_1. */
#define LADSPA_HINT_TOGGLED 0x4
/* Hint LADSPA_HINT_SAMPLE_RATE indicates that any bounds specified
should be interpreted as multiples of the sample rate. For
instance, a frequency range from 0Hz to the Nyquist frequency (half
the sample rate) could be requested by this hint in conjunction
with LowerBound = 0 and UpperBound = 0.5. Hosts that support bounds
at all must support this hint to retain meaning. */
#define LADSPA_HINT_SAMPLE_RATE 0x8
/* Hint LADSPA_HINT_LOGARITHMIC indicates that it is likely that the
user will find it more intuitive to view values using a logarithmic
scale. This is particularly useful for frequencies and gains. */
#define LADSPA_HINT_LOGARITHMIC 0x10
/* Hint LADSPA_HINT_INTEGER indicates that a user interface would
probably wish to provide a stepped control taking only integer
values. Any bounds set should be slightly wider than the actual
integer range required to avoid floating point rounding errors. For
instance, the integer set {0,1,2,3} might be described as [-0.1,
3.1]. */
#define LADSPA_HINT_INTEGER 0x20
/* The various LADSPA_HINT_HAS_DEFAULT_* hints indicate a `normal'
value for the port that is sensible as a default. For instance,
this value is suitable for use as an initial value in a user
interface or as a value the host might assign to a control port
when the user has not provided one. Defaults are encoded using a
mask so only one default may be specified for a port. Some of the
hints make use of lower and upper bounds, in which case the
relevant bound or bounds must be available and
LADSPA_HINT_SAMPLE_RATE must be applied as usual. The resulting
default must be rounded if LADSPA_HINT_INTEGER is present. Default
values were introduced in LADSPA v1.1. */
#define LADSPA_HINT_DEFAULT_MASK 0x3C0
/* This default values indicates that no default is provided. */
#define LADSPA_HINT_DEFAULT_NONE 0x0
/* This default hint indicates that the suggested lower bound for the
port should be used. */
#define LADSPA_HINT_DEFAULT_MINIMUM 0x40
/* This default hint indicates that a low value between the suggested
lower and upper bounds should be chosen. For ports with
LADSPA_HINT_LOGARITHMIC, this should be exp(log(lower) * 0.75 +
log(upper) * 0.25). Otherwise, this should be (lower * 0.75 + upper
* 0.25). */
#define LADSPA_HINT_DEFAULT_LOW 0x80
/* This default hint indicates that a middle value between the
suggested lower and upper bounds should be chosen. For ports with
LADSPA_HINT_LOGARITHMIC, this should be exp(log(lower) * 0.5 +
log(upper) * 0.5). Otherwise, this should be (lower * 0.5 + upper *
0.5). */
#define LADSPA_HINT_DEFAULT_MIDDLE 0xC0
/* This default hint indicates that a high value between the suggested
lower and upper bounds should be chosen. For ports with
LADSPA_HINT_LOGARITHMIC, this should be exp(log(lower) * 0.25 +
log(upper) * 0.75). Otherwise, this should be (lower * 0.25 + upper
* 0.75). */
#define LADSPA_HINT_DEFAULT_HIGH 0x100
/* This default hint indicates that the suggested upper bound for the
port should be used. */
#define LADSPA_HINT_DEFAULT_MAXIMUM 0x140
/* This default hint indicates that the number 0 should be used. Note
that this default may be used in conjunction with
LADSPA_HINT_TOGGLED. */
#define LADSPA_HINT_DEFAULT_0 0x200
/* This default hint indicates that the number 1 should be used. Note
that this default may be used in conjunction with
LADSPA_HINT_TOGGLED. */
#define LADSPA_HINT_DEFAULT_1 0x240
/* This default hint indicates that the number 100 should be used. */
#define LADSPA_HINT_DEFAULT_100 0x280
/* This default hint indicates that the Hz frequency of `concert A'
should be used. This will be 440 unless the host uses an unusual
tuning convention, in which case it may be within a few Hz. */
#define LADSPA_HINT_DEFAULT_440 0x2C0
#define LADSPA_IS_HINT_BOUNDED_BELOW(x) ((x) & LADSPA_HINT_BOUNDED_BELOW)
#define LADSPA_IS_HINT_BOUNDED_ABOVE(x) ((x) & LADSPA_HINT_BOUNDED_ABOVE)
#define LADSPA_IS_HINT_TOGGLED(x) ((x) & LADSPA_HINT_TOGGLED)
#define LADSPA_IS_HINT_SAMPLE_RATE(x) ((x) & LADSPA_HINT_SAMPLE_RATE)
#define LADSPA_IS_HINT_LOGARITHMIC(x) ((x) & LADSPA_HINT_LOGARITHMIC)
#define LADSPA_IS_HINT_INTEGER(x) ((x) & LADSPA_HINT_INTEGER)
#define LADSPA_IS_HINT_HAS_DEFAULT(x) ((x) & LADSPA_HINT_DEFAULT_MASK)
#define LADSPA_IS_HINT_DEFAULT_MINIMUM(x) (((x) & LADSPA_HINT_DEFAULT_MASK) \
== LADSPA_HINT_DEFAULT_MINIMUM)
#define LADSPA_IS_HINT_DEFAULT_LOW(x) (((x) & LADSPA_HINT_DEFAULT_MASK) \
== LADSPA_HINT_DEFAULT_LOW)
#define LADSPA_IS_HINT_DEFAULT_MIDDLE(x) (((x) & LADSPA_HINT_DEFAULT_MASK) \
== LADSPA_HINT_DEFAULT_MIDDLE)
#define LADSPA_IS_HINT_DEFAULT_HIGH(x) (((x) & LADSPA_HINT_DEFAULT_MASK) \
== LADSPA_HINT_DEFAULT_HIGH)
#define LADSPA_IS_HINT_DEFAULT_MAXIMUM(x) (((x) & LADSPA_HINT_DEFAULT_MASK) \
== LADSPA_HINT_DEFAULT_MAXIMUM)
#define LADSPA_IS_HINT_DEFAULT_0(x) (((x) & LADSPA_HINT_DEFAULT_MASK) \
== LADSPA_HINT_DEFAULT_0)
#define LADSPA_IS_HINT_DEFAULT_1(x) (((x) & LADSPA_HINT_DEFAULT_MASK) \
== LADSPA_HINT_DEFAULT_1)
#define LADSPA_IS_HINT_DEFAULT_100(x) (((x) & LADSPA_HINT_DEFAULT_MASK) \
== LADSPA_HINT_DEFAULT_100)
#define LADSPA_IS_HINT_DEFAULT_440(x) (((x) & LADSPA_HINT_DEFAULT_MASK) \
== LADSPA_HINT_DEFAULT_440)
typedef struct _LADSPA_PortRangeHint {
/* Hints about the port. */
LADSPA_PortRangeHintDescriptor HintDescriptor;
/* Meaningful when hint LADSPA_HINT_BOUNDED_BELOW is active. When
LADSPA_HINT_SAMPLE_RATE is also active then this value should be
multiplied by the relevant sample rate. */
LADSPA_Data LowerBound;
/* Meaningful when hint LADSPA_HINT_BOUNDED_ABOVE is active. When
LADSPA_HINT_SAMPLE_RATE is also active then this value should be
multiplied by the relevant sample rate. */
LADSPA_Data UpperBound;
} LADSPA_PortRangeHint;
/*****************************************************************************/
/* Plugin Handles:
This plugin handle indicates a particular instance of the plugin
concerned. It is valid to compare this to NULL (0 for C++) but
otherwise the host should not attempt to interpret it. The plugin
may use it to reference internal instance data. */
typedef void * LADSPA_Handle;
/*****************************************************************************/
/* Descriptor for a Type of Plugin:
This structure is used to describe a plugin type. It provides a
number of functions to examine the type, instantiate it, link it to
buffers and workspaces and to run it. */
typedef struct _LADSPA_Descriptor {
/* This numeric identifier indicates the plugin type
uniquely. Plugin programmers may reserve ranges of IDs from a
central body to avoid clashes. Hosts may assume that IDs are
below 0x1000000. */
unsigned long UniqueID;
/* This identifier can be used as a unique, case-sensitive
identifier for the plugin type within the plugin file. Plugin
types should be identified by file and label rather than by index
or plugin name, which may be changed in new plugin
versions. Labels must not contain white-space characters. */
const char * Label;
/* This indicates a number of properties of the plugin. */
LADSPA_Properties Properties;
/* This member points to the null-terminated name of the plugin
(e.g. "Sine Oscillator"). */
const char * Name;
/* This member points to the null-terminated string indicating the
maker of the plugin. This can be an empty string but not NULL. */
const char * Maker;
/* This member points to the null-terminated string indicating any
copyright applying to the plugin. If no Copyright applies the
string "None" should be used. */
const char * Copyright;
/* This indicates the number of ports (input AND output) present on
the plugin. */
unsigned long PortCount;
/* This member indicates an array of port descriptors. Valid indices
vary from 0 to PortCount-1. */
const LADSPA_PortDescriptor * PortDescriptors;
/* This member indicates an array of null-terminated strings
describing ports (e.g. "Frequency (Hz)"). Valid indices vary from
0 to PortCount-1. */
const char * const * PortNames;
/* This member indicates an array of range hints for each port (see
above). Valid indices vary from 0 to PortCount-1. */
const LADSPA_PortRangeHint * PortRangeHints;
/* This may be used by the plugin developer to pass any custom
implementation data into an instantiate call. It must not be used
or interpreted by the host. It is expected that most plugin
writers will not use this facility as LADSPA_Handle should be
used to hold instance data. */
void * ImplementationData;
/* This member is a function pointer that instantiates a plugin. A
handle is returned indicating the new plugin instance. The
instantiation function accepts a sample rate as a parameter. The
plugin descriptor from which this instantiate function was found
must also be passed. This function must return NULL if
instantiation fails.
Note that instance initialisation should generally occur in
activate() rather than here. */
LADSPA_Handle (*instantiate)(const struct _LADSPA_Descriptor * Descriptor,
unsigned long SampleRate);
/* This member is a function pointer that connects a port on an
instantiated plugin to a memory location at which a block of data
for the port will be read/written. The data location is expected
to be an array of LADSPA_Data for audio ports or a single
LADSPA_Data value for control ports. Memory issues will be
managed by the host. The plugin must read/write the data at these
locations every time run() or run_adding() is called and the data
present at the time of this connection call should not be
considered meaningful.
connect_port() may be called more than once for a plugin instance
to allow the host to change the buffers that the plugin is
reading or writing. These calls may be made before or after
activate() or deactivate() calls.
connect_port() must be called at least once for each port before
run() or run_adding() is called. When working with blocks of
LADSPA_Data the plugin should pay careful attention to the block
size passed to the run function as the block allocated may only
just be large enough to contain the block of samples.
Plugin writers should be aware that the host may elect to use the
same buffer for more than one port and even use the same buffer
for both input and output (see LADSPA_PROPERTY_INPLACE_BROKEN).
However, overlapped buffers or use of a single buffer for both
audio and control data may result in unexpected behaviour. */
void (*connect_port)(LADSPA_Handle Instance,
unsigned long Port,
LADSPA_Data * DataLocation);
/* This member is a function pointer that initialises a plugin
instance and activates it for use. This is separated from
instantiate() to aid real-time support and so that hosts can
reinitialise a plugin instance by calling deactivate() and then
activate(). In this case the plugin instance must reset all state
information dependent on the history of the plugin instance
except for any data locations provided by connect_port() and any
gain set by set_run_adding_gain(). If there is nothing for
activate() to do then the plugin writer may provide a NULL rather
than an empty function.
When present, hosts must call this function once before run() (or
run_adding()) is called for the first time. This call should be
made as close to the run() call as possible and indicates to
real-time plugins that they are now live. Plugins should not rely
on a prompt call to run() after activate(). activate() may not be
called again unless deactivate() is called first. Note that
connect_port() may be called before or after a call to
activate(). */
void (*activate)(LADSPA_Handle Instance);
/* This method is a function pointer that runs an instance of a
plugin for a block. Two parameters are required: the first is a
handle to the particular instance to be run and the second
indicates the block size (in samples) for which the plugin
instance may run.
Note that if an activate() function exists then it must be called
before run() or run_adding(). If deactivate() is called for a
plugin instance then the plugin instance may not be reused until
activate() has been called again.
If the plugin has the property LADSPA_PROPERTY_HARD_RT_CAPABLE
then there are various things that the plugin should not do
within the run() or run_adding() functions (see above). */
void (*run)(LADSPA_Handle Instance,
unsigned long SampleCount);
/* This method is a function pointer that runs an instance of a
plugin for a block. This has identical behaviour to run() except
in the way data is output from the plugin. When run() is used,
values are written directly to the memory areas associated with
the output ports. However when run_adding() is called, values
must be added to the values already present in the memory
areas. Furthermore, output values written must be scaled by the
current gain set by set_run_adding_gain() (see below) before
addition.
run_adding() is optional. When it is not provided by a plugin,
this function pointer must be set to NULL. When it is provided,
the function set_run_adding_gain() must be provided also. */
void (*run_adding)(LADSPA_Handle Instance,
unsigned long SampleCount);
/* This method is a function pointer that sets the output gain for
use when run_adding() is called (see above). If this function is
never called the gain is assumed to default to 1. Gain
information should be retained when activate() or deactivate()
are called.
This function should be provided by the plugin if and only if the
run_adding() function is provided. When it is absent this
function pointer must be set to NULL. */
void (*set_run_adding_gain)(LADSPA_Handle Instance,
LADSPA_Data Gain);
/* This is the counterpart to activate() (see above). If there is
nothing for deactivate() to do then the plugin writer may provide
a NULL rather than an empty function.
Hosts must deactivate all activated units after they have been
run() (or run_adding()) for the last time. This call should be
made as close to the last run() call as possible and indicates to
real-time plugins that they are no longer live. Plugins should
not rely on prompt deactivation. Note that connect_port() may be
called before or after a call to deactivate().
Deactivation is not similar to pausing as the plugin instance
will be reinitialised when activate() is called to reuse it. */
void (*deactivate)(LADSPA_Handle Instance);
/* Once an instance of a plugin has been finished with it can be
deleted using the following function. The instance handle passed
ceases to be valid after this call.
If activate() was called for a plugin instance then a
corresponding call to deactivate() must be made before cleanup()
is called. */
void (*cleanup)(LADSPA_Handle Instance);
} LADSPA_Descriptor;
/**********************************************************************/
/* Accessing a Plugin: */
/* The exact mechanism by which plugins are loaded is host-dependent,
however all most hosts will need to know is the name of shared
object file containing the plugin types. To allow multiple hosts to
share plugin types, hosts may wish to check for environment
variable LADSPA_PATH. If present, this should contain a
colon-separated path indicating directories that should be searched
(in order) when loading plugin types.
A plugin programmer must include a function called
"ladspa_descriptor" with the following function prototype within
the shared object file. This function will have C-style linkage (if
you are using C++ this is taken care of by the `extern "C"' clause
at the top of the file).
A host will find the plugin shared object file by one means or
another, find the ladspa_descriptor() function, call it, and
proceed from there.
Plugin types are accessed by index (not ID) using values from 0
upwards. Out of range indexes must result in this function
returning NULL, so the plugin count can be determined by checking
for the least index that results in NULL being returned. */
const LADSPA_Descriptor * ladspa_descriptor(unsigned long Index);
/* Datatype corresponding to the ladspa_descriptor() function. */
typedef const LADSPA_Descriptor *
(*LADSPA_Descriptor_Function)(unsigned long Index);
/**********************************************************************/
#ifdef __cplusplus
}
#endif
#endif /* LADSPA_INCLUDED */
/* EOF */

View file

@ -1,385 +0,0 @@
/* PipeWire */
/* SPDX-FileCopyrightText: Copyright © 2021 Wim Taymans */
/* SPDX-License-Identifier: MIT */
#include "config.h"
#include <dlfcn.h>
#include <math.h>
#include <limits.h>
#include <spa/utils/result.h>
#include <spa/utils/defs.h>
#include <spa/utils/list.h>
#include <spa/utils/string.h>
#include <spa/support/log.h>
#include "audio-plugin.h"
#include "ladspa.h"
struct plugin {
struct spa_handle handle;
struct spa_fga_plugin plugin;
struct spa_log *log;
void *hndl;
LADSPA_Descriptor_Function desc_func;
};
struct descriptor {
struct spa_fga_descriptor desc;
const LADSPA_Descriptor *d;
};
static void *ladspa_instantiate(const struct spa_fga_plugin *plugin, const struct spa_fga_descriptor *desc,
unsigned long SampleRate, int index, const char *config)
{
struct descriptor *d = (struct descriptor *)desc;
return d->d->instantiate(d->d, SampleRate);
}
static const LADSPA_Descriptor *find_desc(LADSPA_Descriptor_Function desc_func, const char *name)
{
unsigned long i;
for (i = 0; ;i++) {
const LADSPA_Descriptor *d = desc_func(i);
if (d == NULL)
break;
if (spa_streq(d->Label, name))
return d;
}
return NULL;
}
static float get_default(struct spa_fga_port *port, LADSPA_PortRangeHintDescriptor hint,
LADSPA_Data lower, LADSPA_Data upper)
{
LADSPA_Data def;
switch (hint & LADSPA_HINT_DEFAULT_MASK) {
case LADSPA_HINT_DEFAULT_MINIMUM:
def = lower;
break;
case LADSPA_HINT_DEFAULT_MAXIMUM:
def = upper;
break;
case LADSPA_HINT_DEFAULT_LOW:
if (LADSPA_IS_HINT_LOGARITHMIC(hint))
def = (LADSPA_Data) expf(logf(lower) * 0.75f + logf(upper) * 0.25f);
else
def = (LADSPA_Data) (lower * 0.75f + upper * 0.25f);
break;
case LADSPA_HINT_DEFAULT_MIDDLE:
if (LADSPA_IS_HINT_LOGARITHMIC(hint))
def = (LADSPA_Data) expf(logf(lower) * 0.5f + logf(upper) * 0.5f);
else
def = (LADSPA_Data) (lower * 0.5f + upper * 0.5f);
break;
case LADSPA_HINT_DEFAULT_HIGH:
if (LADSPA_IS_HINT_LOGARITHMIC(hint))
def = (LADSPA_Data) expf(logf(lower) * 0.25f + logf(upper) * 0.75f);
else
def = (LADSPA_Data) (lower * 0.25f + upper * 0.75f);
break;
case LADSPA_HINT_DEFAULT_0:
def = 0.0f;
break;
case LADSPA_HINT_DEFAULT_1:
def = 1.0f;
break;
case LADSPA_HINT_DEFAULT_100:
def = 100.0f;
break;
case LADSPA_HINT_DEFAULT_440:
def = 440.0f;
break;
default:
if (upper == lower)
def = upper;
else
def = SPA_CLAMPF(0.5f * upper, lower, upper);
break;
}
if (LADSPA_IS_HINT_INTEGER(hint))
def = roundf(def);
return def;
}
static void ladspa_port_update_ranges(struct descriptor *dd, struct spa_fga_port *port)
{
const LADSPA_Descriptor *d = dd->d;
unsigned long p = port->index;
LADSPA_PortRangeHintDescriptor hint = d->PortRangeHints[p].HintDescriptor;
LADSPA_Data lower, upper;
lower = d->PortRangeHints[p].LowerBound;
upper = d->PortRangeHints[p].UpperBound;
port->hint = hint;
port->def = get_default(port, hint, lower, upper);
port->min = lower;
port->max = upper;
}
static void ladspa_free(const struct spa_fga_descriptor *desc)
{
struct descriptor *d = (struct descriptor*)desc;
free(d->desc.ports);
free(d);
}
static const struct spa_fga_descriptor *ladspa_plugin_make_desc(void *plugin, const char *name)
{
struct plugin *p = (struct plugin *)plugin;
struct descriptor *desc;
const LADSPA_Descriptor *d;
uint32_t i;
d = find_desc(p->desc_func, name);
if (d == NULL)
return NULL;
desc = calloc(1, sizeof(*desc));
desc->d = d;
desc->desc.instantiate = ladspa_instantiate;
desc->desc.cleanup = d->cleanup;
desc->desc.connect_port = d->connect_port;
desc->desc.activate = d->activate;
desc->desc.deactivate = d->deactivate;
desc->desc.run = d->run;
desc->desc.free = ladspa_free;
desc->desc.name = d->Label;
desc->desc.flags = 0;
desc->desc.n_ports = d->PortCount;
desc->desc.ports = calloc(desc->desc.n_ports, sizeof(struct spa_fga_port));
for (i = 0; i < desc->desc.n_ports; i++) {
desc->desc.ports[i].index = i;
desc->desc.ports[i].name = d->PortNames[i];
desc->desc.ports[i].flags = d->PortDescriptors[i];
ladspa_port_update_ranges(desc, &desc->desc.ports[i]);
}
return &desc->desc;
}
static struct spa_fga_plugin_methods impl_plugin = {
SPA_VERSION_FGA_PLUGIN_METHODS,
.make_desc = ladspa_plugin_make_desc,
};
static int ladspa_handle_load_by_path(struct plugin *impl, const char *path)
{
int res;
void *handle = NULL;
LADSPA_Descriptor_Function desc_func;
handle = dlopen(path, RTLD_NOW);
if (handle == NULL) {
spa_log_debug(impl->log, "failed to open '%s': %s", path, dlerror());
res = -ENOENT;
goto exit;
}
spa_log_info(impl->log, "successfully opened '%s'", path);
desc_func = (LADSPA_Descriptor_Function) dlsym(handle, "ladspa_descriptor");
if (desc_func == NULL) {
spa_log_warn(impl->log, "cannot find descriptor function in '%s': %s", path, dlerror());
res = -ENOSYS;
goto exit;
}
impl->hndl = handle;
impl->desc_func = desc_func;
return 0;
exit:
if (handle)
dlclose(handle);
return res;
}
static inline const char *split_walk(const char *str, const char *delimiter, size_t * len, const char **state)
{
const char *s = *state ? *state : str;
s += strspn(s, delimiter);
if (*s == '\0')
return NULL;
*len = strcspn(s, delimiter);
*state = s + *len;
return s;
}
static int load_ladspa_plugin(struct plugin *impl, const char *path)
{
int res = -ENOENT;
if (path[0] != '/') {
const char *search_dirs, *p, *state = NULL;
char filename[PATH_MAX];
size_t len;
search_dirs = getenv("LADSPA_PATH");
if (!search_dirs)
search_dirs = "/usr/lib64/ladspa:/usr/lib/ladspa:" LIBDIR;
/*
* set the errno for the case when `ladspa_handle_load_by_path()`
* is never called, which can only happen if the supplied
* LADSPA_PATH contains too long paths
*/
res = -ENAMETOOLONG;
while ((p = split_walk(search_dirs, ":", &len, &state))) {
int namelen;
if (len >= sizeof(filename))
continue;
namelen = snprintf(filename, sizeof(filename), "%.*s/%s.so", (int) len, p, path);
if (namelen < 0 || (size_t) namelen >= sizeof(filename))
continue;
res = ladspa_handle_load_by_path(impl, filename);
if (res >= 0)
break;
}
}
else {
res = ladspa_handle_load_by_path(impl, path);
}
return res;
}
static int impl_get_interface(struct spa_handle *handle, const char *type, void **interface)
{
struct plugin *impl;
spa_return_val_if_fail(handle != NULL, -EINVAL);
spa_return_val_if_fail(interface != NULL, -EINVAL);
impl = (struct plugin *) handle;
if (spa_streq(type, SPA_TYPE_INTERFACE_FILTER_GRAPH_AudioPlugin))
*interface = &impl->plugin;
else
return -ENOENT;
return 0;
}
static int impl_clear(struct spa_handle *handle)
{
struct plugin *impl = (struct plugin *)handle;
if (impl->hndl)
dlclose(impl->hndl);
impl->hndl = NULL;
return 0;
}
static size_t
impl_get_size(const struct spa_handle_factory *factory,
const struct spa_dict *params)
{
return sizeof(struct plugin);
}
static int
impl_init(const struct spa_handle_factory *factory,
struct spa_handle *handle,
const struct spa_dict *info,
const struct spa_support *support,
uint32_t n_support)
{
struct plugin *impl;
uint32_t i;
int res;
const char *path = NULL;
handle->get_interface = impl_get_interface;
handle->clear = impl_clear;
impl = (struct plugin *) handle;
impl->log = spa_support_find(support, n_support, SPA_TYPE_INTERFACE_Log);
for (i = 0; info && i < info->n_items; i++) {
const char *k = info->items[i].key;
const char *s = info->items[i].value;
if (spa_streq(k, "filter.graph.path"))
path = s;
}
if (path == NULL)
return -EINVAL;
if ((res = load_ladspa_plugin(impl, path)) < 0) {
spa_log_error(impl->log, "failed to load plugin '%s': %s",
path, spa_strerror(res));
return res;
}
impl->plugin.iface = SPA_INTERFACE_INIT(
SPA_TYPE_INTERFACE_FILTER_GRAPH_AudioPlugin,
SPA_VERSION_FGA_PLUGIN,
&impl_plugin, impl);
return 0;
}
static const struct spa_interface_info impl_interfaces[] = {
{ SPA_TYPE_INTERFACE_FILTER_GRAPH_AudioPlugin },
};
static int
impl_enum_interface_info(const struct spa_handle_factory *factory,
const struct spa_interface_info **info,
uint32_t *index)
{
spa_return_val_if_fail(factory != NULL, -EINVAL);
spa_return_val_if_fail(info != NULL, -EINVAL);
spa_return_val_if_fail(index != NULL, -EINVAL);
switch (*index) {
case 0:
*info = &impl_interfaces[*index];
break;
default:
return 0;
}
(*index)++;
return 1;
}
struct spa_handle_factory spa_fga_plugin_ladspa_factory = {
SPA_VERSION_HANDLE_FACTORY,
"filter.graph.plugin.ladspa",
NULL,
impl_get_size,
impl_init,
impl_enum_interface_info,
};
SPA_EXPORT
int spa_handle_factory_enum(const struct spa_handle_factory **factory, uint32_t *index)
{
spa_return_val_if_fail(factory != NULL, -EINVAL);
spa_return_val_if_fail(index != NULL, -EINVAL);
switch (*index) {
case 0:
*factory = &spa_fga_plugin_ladspa_factory;
break;
default:
return 0;
}
(*index)++;
return 1;
}

View file

@ -1,604 +0,0 @@
/* PipeWire */
/* SPDX-FileCopyrightText: Copyright © 2021 Wim Taymans */
/* SPDX-License-Identifier: MIT */
#include <dlfcn.h>
#include <math.h>
#include <lilv/lilv.h>
#include <spa/utils/defs.h>
#include <spa/utils/list.h>
#include <spa/utils/string.h>
#include <spa/support/loop.h>
#include <spa/support/log.h>
#if defined __has_include
# if __has_include (<lv2/atom/atom.h>)
#include <lv2/atom/atom.h>
#include <lv2/buf-size/buf-size.h>
#include <lv2/worker/worker.h>
#include <lv2/options/options.h>
#include <lv2/parameters/parameters.h>
# else
#include <lv2/lv2plug.in/ns/ext/atom/atom.h>
#include <lv2/lv2plug.in/ns/ext/buf-size/buf-size.h>
#include <lv2/lv2plug.in/ns/ext/worker/worker.h>
#include <lv2/lv2plug.in/ns/ext/options/options.h>
#include <lv2/lv2plug.in/ns/ext/parameters/parameters.h>
# endif
#endif
#include "audio-plugin.h"
static struct context *_context;
typedef struct URITable {
char **data;
size_t alloc;
size_t len;
} URITable;
static void uri_table_init(URITable *table)
{
table->data = NULL;
table->len = table->alloc = 0;
}
static void uri_table_destroy(URITable *table)
{
size_t i;
for (i = 0; i < table->len; i++)
free(table->data[i]);
free(table->data);
uri_table_init(table);
}
static LV2_URID uri_table_map(LV2_URID_Map_Handle handle, const char *uri)
{
URITable *table = (URITable*)handle;
size_t i;
for (i = 0; i < table->len; i++)
if (spa_streq(table->data[i], uri))
return i+1;
if (table->len == table->alloc) {
table->alloc += 64;
table->data = realloc(table->data, table->alloc * sizeof(char *));
}
table->data[table->len++] = strdup(uri);
return table->len;
}
static const char *uri_table_unmap(LV2_URID_Map_Handle handle, LV2_URID urid)
{
URITable *table = (URITable*)handle;
if (urid > 0 && urid <= table->len)
return table->data[urid-1];
return NULL;
}
struct context {
int ref;
LilvWorld *world;
LilvNode *lv2_InputPort;
LilvNode *lv2_OutputPort;
LilvNode *lv2_AudioPort;
LilvNode *lv2_ControlPort;
LilvNode *lv2_Optional;
LilvNode *atom_AtomPort;
LilvNode *atom_Sequence;
LilvNode *urid_map;
LilvNode *powerOf2BlockLength;
LilvNode *fixedBlockLength;
LilvNode *boundedBlockLength;
LilvNode* worker_schedule;
LilvNode* worker_iface;
URITable uri_table;
LV2_URID_Map map;
LV2_Feature map_feature;
LV2_URID_Unmap unmap;
LV2_Feature unmap_feature;
LV2_URID atom_Int;
LV2_URID atom_Float;
};
#define context_map(c,uri) ((c)->map.map((c)->map.handle,(uri)))
static void context_free(struct context *c)
{
if (c->world) {
lilv_node_free(c->worker_schedule);
lilv_node_free(c->powerOf2BlockLength);
lilv_node_free(c->fixedBlockLength);
lilv_node_free(c->boundedBlockLength);
lilv_node_free(c->urid_map);
lilv_node_free(c->atom_Sequence);
lilv_node_free(c->atom_AtomPort);
lilv_node_free(c->lv2_Optional);
lilv_node_free(c->lv2_ControlPort);
lilv_node_free(c->lv2_AudioPort);
lilv_node_free(c->lv2_OutputPort);
lilv_node_free(c->lv2_InputPort);
lilv_world_free(c->world);
}
uri_table_destroy(&c->uri_table);
free(c);
}
static const LV2_Feature buf_size_features[3] = {
{ LV2_BUF_SIZE__powerOf2BlockLength, NULL },
{ LV2_BUF_SIZE__fixedBlockLength, NULL },
{ LV2_BUF_SIZE__boundedBlockLength, NULL },
};
static struct context *context_new(void)
{
struct context *c;
c = calloc(1, sizeof(*c));
if (c == NULL)
return NULL;
uri_table_init(&c->uri_table);
c->world = lilv_world_new();
if (c->world == NULL)
goto error;
lilv_world_load_all(c->world);
c->lv2_InputPort = lilv_new_uri(c->world, LV2_CORE__InputPort);
c->lv2_OutputPort = lilv_new_uri(c->world, LV2_CORE__OutputPort);
c->lv2_AudioPort = lilv_new_uri(c->world, LV2_CORE__AudioPort);
c->lv2_ControlPort = lilv_new_uri(c->world, LV2_CORE__ControlPort);
c->lv2_Optional = lilv_new_uri(c->world, LV2_CORE__connectionOptional);
c->atom_AtomPort = lilv_new_uri(c->world, LV2_ATOM__AtomPort);
c->atom_Sequence = lilv_new_uri(c->world, LV2_ATOM__Sequence);
c->urid_map = lilv_new_uri(c->world, LV2_URID__map);
c->powerOf2BlockLength = lilv_new_uri(c->world, LV2_BUF_SIZE__powerOf2BlockLength);
c->fixedBlockLength = lilv_new_uri(c->world, LV2_BUF_SIZE__fixedBlockLength);
c->boundedBlockLength = lilv_new_uri(c->world, LV2_BUF_SIZE__boundedBlockLength);
c->worker_schedule = lilv_new_uri(c->world, LV2_WORKER__schedule);
c->worker_iface = lilv_new_uri(c->world, LV2_WORKER__interface);
c->map.handle = &c->uri_table;
c->map.map = uri_table_map;
c->map_feature.URI = LV2_URID_MAP_URI;
c->map_feature.data = &c->map;
c->unmap.handle = &c->uri_table;
c->unmap.unmap = uri_table_unmap;
c->unmap_feature.URI = LV2_URID_UNMAP_URI;
c->unmap_feature.data = &c->unmap;
c->atom_Int = context_map(c, LV2_ATOM__Int);
c->atom_Float = context_map(c, LV2_ATOM__Float);
return c;
error:
context_free(c);
return NULL;
}
static struct context *context_ref(void)
{
if (_context == NULL) {
_context = context_new();
if (_context == NULL)
return NULL;
}
_context->ref++;
return _context;
}
static void context_unref(struct context *context)
{
if (--_context->ref == 0) {
context_free(_context);
_context = NULL;
}
}
struct plugin {
struct spa_handle handle;
struct spa_fga_plugin plugin;
struct spa_log *log;
struct spa_loop *data_loop;
struct spa_loop *main_loop;
struct context *c;
const LilvPlugin *p;
};
struct descriptor {
struct spa_fga_descriptor desc;
struct plugin *p;
};
struct instance {
struct descriptor *desc;
struct plugin *p;
LilvInstance *instance;
LV2_Worker_Schedule work_schedule;
LV2_Feature work_schedule_feature;
LV2_Options_Option options[6];
LV2_Feature options_feature;
const LV2_Feature *features[7];
const LV2_Worker_Interface *work_iface;
int32_t block_length;
LV2_Atom empty_atom;
};
static int
do_respond(struct spa_loop *loop, bool async, uint32_t seq, const void *data,
size_t size, void *user_data)
{
struct instance *i = (struct instance*)user_data;
i->work_iface->work_response(i->instance->lv2_handle, size, data);
return 0;
}
/** Called by the plugin to respond to non-RT work. */
static LV2_Worker_Status
work_respond(LV2_Worker_Respond_Handle handle, uint32_t size, const void *data)
{
struct instance *i = (struct instance*)handle;
spa_loop_invoke(i->p->data_loop, do_respond, 1, data, size, false, i);
return LV2_WORKER_SUCCESS;
}
static int
do_schedule(struct spa_loop *loop, bool async, uint32_t seq, const void *data,
size_t size, void *user_data)
{
struct instance *i = (struct instance*)user_data;
i->work_iface->work(i->instance->lv2_handle, work_respond, i, size, data);
return 0;
}
/** Called by the plugin to schedule non-RT work. */
static LV2_Worker_Status
work_schedule(LV2_Worker_Schedule_Handle handle, uint32_t size, const void *data)
{
struct instance *i = (struct instance*)handle;
spa_loop_invoke(i->p->main_loop, do_schedule, 1, data, size, false, i);
return LV2_WORKER_SUCCESS;
}
static void *lv2_instantiate(const struct spa_fga_plugin *plugin, const struct spa_fga_descriptor *desc,
unsigned long SampleRate, int index, const char *config)
{
struct descriptor *d = (struct descriptor*)desc;
struct plugin *p = d->p;
struct context *c = p->c;
struct instance *i;
uint32_t n, n_features = 0;
static const int32_t min_block_length = 1;
static const int32_t max_block_length = 8192;
static const int32_t seq_size = 32768;
float fsample_rate = SampleRate;
i = calloc(1, sizeof(*i));
if (i == NULL)
return NULL;
i->block_length = 1024;
i->desc = d;
i->p = p;
i->features[n_features++] = &c->map_feature;
i->features[n_features++] = &c->unmap_feature;
i->features[n_features++] = &buf_size_features[0];
i->features[n_features++] = &buf_size_features[1];
i->features[n_features++] = &buf_size_features[2];
if (lilv_plugin_has_feature(p->p, c->worker_schedule)) {
i->work_schedule.handle = i;
i->work_schedule.schedule_work = work_schedule;
i->work_schedule_feature.URI = LV2_WORKER__schedule;
i->work_schedule_feature.data = &i->work_schedule;
i->features[n_features++] = &i->work_schedule_feature;
}
i->options[0] = (LV2_Options_Option) { LV2_OPTIONS_INSTANCE, 0,
context_map(c, LV2_BUF_SIZE__minBlockLength), sizeof(int32_t),
c->atom_Int, &min_block_length };
i->options[1] = (LV2_Options_Option) { LV2_OPTIONS_INSTANCE, 0,
context_map(c, LV2_BUF_SIZE__maxBlockLength), sizeof(int32_t),
c->atom_Int, &max_block_length };
i->options[2] = (LV2_Options_Option) { LV2_OPTIONS_INSTANCE, 0,
context_map(c, LV2_BUF_SIZE__sequenceSize), sizeof(int32_t),
c->atom_Int, &seq_size };
i->options[3] = (LV2_Options_Option) { LV2_OPTIONS_INSTANCE, 0,
context_map(c, "http://lv2plug.in/ns/ext/buf-size#nominalBlockLength"), sizeof(int32_t),
c->atom_Int, &i->block_length },
i->options[4] = (LV2_Options_Option) { LV2_OPTIONS_INSTANCE, 0,
context_map(c, LV2_PARAMETERS__sampleRate), sizeof(float),
c->atom_Float, &fsample_rate };
i->options[5] = (LV2_Options_Option) { LV2_OPTIONS_INSTANCE, 0, 0, 0, 0, NULL };
i->options_feature.URI = LV2_OPTIONS__options;
i->options_feature.data = i->options;
i->features[n_features++] = &i->options_feature;
i->instance = lilv_plugin_instantiate(p->p, SampleRate, i->features);
if (i->instance == NULL) {
free(i);
return NULL;
}
if (lilv_plugin_has_extension_data(p->p, c->worker_iface)) {
i->work_iface = (const LV2_Worker_Interface*)
lilv_instance_get_extension_data(i->instance, LV2_WORKER__interface);
}
for (n = 0; n < desc->n_ports; n++) {
const LilvPort *port = lilv_plugin_get_port_by_index(p->p, n);
if (lilv_port_is_a(p->p, port, c->atom_AtomPort)) {
lilv_instance_connect_port(i->instance, n, &i->empty_atom);
}
}
return i;
}
static void lv2_cleanup(void *instance)
{
struct instance *i = instance;
lilv_instance_free(i->instance);
free(i);
}
static void lv2_connect_port(void *instance, unsigned long port, float *data)
{
struct instance *i = instance;
lilv_instance_connect_port(i->instance, port, data);
}
static void lv2_activate(void *instance)
{
struct instance *i = instance;
lilv_instance_activate(i->instance);
}
static void lv2_deactivate(void *instance)
{
struct instance *i = instance;
lilv_instance_deactivate(i->instance);
}
static void lv2_run(void *instance, unsigned long SampleCount)
{
struct instance *i = instance;
lilv_instance_run(i->instance, SampleCount);
if (i->work_iface != NULL && i->work_iface->end_run != NULL)
i->work_iface->end_run(i->instance);
}
static void lv2_free(const struct spa_fga_descriptor *desc)
{
struct descriptor *d = (struct descriptor*)desc;
free((char*)d->desc.name);
free(d->desc.ports);
free(d);
}
static const struct spa_fga_descriptor *lv2_plugin_make_desc(void *plugin, const char *name)
{
struct plugin *p = (struct plugin *)plugin;
struct context *c = p->c;
struct descriptor *desc;
uint32_t i;
float *mins, *maxes, *controls;
desc = calloc(1, sizeof(*desc));
if (desc == NULL)
return NULL;
desc->p = p;
desc->desc.instantiate = lv2_instantiate;
desc->desc.cleanup = lv2_cleanup;
desc->desc.connect_port = lv2_connect_port;
desc->desc.activate = lv2_activate;
desc->desc.deactivate = lv2_deactivate;
desc->desc.run = lv2_run;
desc->desc.free = lv2_free;
desc->desc.name = strdup(name);
desc->desc.flags = 0;
desc->desc.n_ports = lilv_plugin_get_num_ports(p->p);
desc->desc.ports = calloc(desc->desc.n_ports, sizeof(struct spa_fga_port));
mins = alloca(desc->desc.n_ports * sizeof(float));
maxes = alloca(desc->desc.n_ports * sizeof(float));
controls = alloca(desc->desc.n_ports * sizeof(float));
lilv_plugin_get_port_ranges_float(p->p, mins, maxes, controls);
for (i = 0; i < desc->desc.n_ports; i++) {
const LilvPort *port = lilv_plugin_get_port_by_index(p->p, i);
const LilvNode *symbol = lilv_port_get_symbol(p->p, port);
struct spa_fga_port *fp = &desc->desc.ports[i];
fp->index = i;
fp->name = strdup(lilv_node_as_string(symbol));
fp->flags = 0;
if (lilv_port_is_a(p->p, port, c->lv2_InputPort))
fp->flags |= SPA_FGA_PORT_INPUT;
if (lilv_port_is_a(p->p, port, c->lv2_OutputPort))
fp->flags |= SPA_FGA_PORT_OUTPUT;
if (lilv_port_is_a(p->p, port, c->lv2_ControlPort))
fp->flags |= SPA_FGA_PORT_CONTROL;
if (lilv_port_is_a(p->p, port, c->lv2_AudioPort))
fp->flags |= SPA_FGA_PORT_AUDIO;
fp->hint = 0;
fp->min = mins[i];
fp->max = maxes[i];
fp->def = controls[i];
}
return &desc->desc;
}
static struct spa_fga_plugin_methods impl_plugin = {
SPA_VERSION_FGA_PLUGIN_METHODS,
.make_desc = lv2_plugin_make_desc,
};
static int impl_get_interface(struct spa_handle *handle, const char *type, void **interface)
{
struct plugin *impl;
spa_return_val_if_fail(handle != NULL, -EINVAL);
spa_return_val_if_fail(interface != NULL, -EINVAL);
impl = (struct plugin *) handle;
if (spa_streq(type, SPA_TYPE_INTERFACE_FILTER_GRAPH_AudioPlugin))
*interface = &impl->plugin;
else
return -ENOENT;
return 0;
}
static int impl_clear(struct spa_handle *handle)
{
struct plugin *p = (struct plugin *)handle;
context_unref(p->c);
return 0;
}
static size_t
impl_get_size(const struct spa_handle_factory *factory,
const struct spa_dict *params)
{
return sizeof(struct plugin);
}
static int
impl_init(const struct spa_handle_factory *factory,
struct spa_handle *handle,
const struct spa_dict *info,
const struct spa_support *support,
uint32_t n_support)
{
struct plugin *impl;
uint32_t i;
int res;
const char *path = NULL;
const LilvPlugins *plugins;
LilvNode *uri;
handle->get_interface = impl_get_interface;
handle->clear = impl_clear;
impl = (struct plugin *) handle;
impl->log = spa_support_find(support, n_support, SPA_TYPE_INTERFACE_Log);
impl->data_loop = spa_support_find(support, n_support, SPA_TYPE_INTERFACE_DataLoop);
impl->main_loop = spa_support_find(support, n_support, SPA_TYPE_INTERFACE_Loop);
for (i = 0; info && i < info->n_items; i++) {
const char *k = info->items[i].key;
const char *s = info->items[i].value;
if (spa_streq(k, "filter.graph.path"))
path = s;
}
if (path == NULL)
return -EINVAL;
impl->c = context_ref();
if (impl->c == NULL)
return -EINVAL;
uri = lilv_new_uri(impl->c->world, path);
if (uri == NULL) {
spa_log_warn(impl->log, "invalid URI %s", path);
res = -EINVAL;
goto error_cleanup;
}
plugins = lilv_world_get_all_plugins(impl->c->world);
impl->p = lilv_plugins_get_by_uri(plugins, uri);
lilv_node_free(uri);
if (impl->p == NULL) {
spa_log_warn(impl->log, "can't load plugin %s", path);
res = -EINVAL;
goto error_cleanup;
}
impl->plugin.iface = SPA_INTERFACE_INIT(
SPA_TYPE_INTERFACE_FILTER_GRAPH_AudioPlugin,
SPA_VERSION_FGA_PLUGIN,
&impl_plugin, impl);
return 0;
error_cleanup:
if (impl->c)
context_unref(impl->c);
return res;
}
static const struct spa_interface_info impl_interfaces[] = {
{ SPA_TYPE_INTERFACE_FILTER_GRAPH_AudioPlugin },
};
static int
impl_enum_interface_info(const struct spa_handle_factory *factory,
const struct spa_interface_info **info,
uint32_t *index)
{
spa_return_val_if_fail(factory != NULL, -EINVAL);
spa_return_val_if_fail(info != NULL, -EINVAL);
spa_return_val_if_fail(index != NULL, -EINVAL);
switch (*index) {
case 0:
*info = &impl_interfaces[*index];
break;
default:
return 0;
}
(*index)++;
return 1;
}
static struct spa_handle_factory spa_fga_plugin_lv2_factory = {
SPA_VERSION_HANDLE_FACTORY,
"filter.graph.plugin.lv2",
NULL,
impl_get_size,
impl_init,
impl_enum_interface_info,
};
SPA_EXPORT
int spa_handle_factory_enum(const struct spa_handle_factory **factory, uint32_t *index)
{
spa_return_val_if_fail(factory != NULL, -EINVAL);
spa_return_val_if_fail(index != NULL, -EINVAL);
switch (*index) {
case 0:
*factory = &spa_fga_plugin_lv2_factory;
break;
default:
return 0;
}
(*index)++;
return 1;
}

File diff suppressed because it is too large Load diff

View file

@ -1,181 +0,0 @@
/* Copyright (c) 2013 Julien Pommier ( pommier@modartt.com )
Based on original fortran 77 code from FFTPACKv4 from NETLIB,
authored by Dr Paul Swarztrauber of NCAR, in 1985.
As confirmed by the NCAR fftpack software curators, the following
FFTPACKv5 license applies to FFTPACKv4 sources. My changes are
released under the same terms.
FFTPACK license:
http://www.cisl.ucar.edu/css/software/fftpack5/ftpk.html
Copyright (c) 2004 the University Corporation for Atmospheric
Research ("UCAR"). All rights reserved. Developed by NCAR's
Computational and Information Systems Laboratory, UCAR,
www.cisl.ucar.edu.
Redistribution and use of the Software in source and binary forms,
with or without modification, is permitted provided that the
following conditions are met:
- Neither the names of NCAR's Computational and Information Systems
Laboratory, the University Corporation for Atmospheric Research,
nor the names of its sponsors or contributors may be used to
endorse or promote products derived from this Software without
specific prior written permission.
- Redistributions of source code must retain the above copyright
notices, this list of conditions, and the disclaimer below.
- Redistributions in binary form must reproduce the above copyright
notice, this list of conditions, and the disclaimer below in the
documentation and/or other materials provided with the
distribution.
THIS 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 CONTRIBUTORS OR COPYRIGHT
HOLDERS BE LIABLE FOR ANY CLAIM, INDIRECT, INCIDENTAL, SPECIAL,
EXEMPLARY, OR CONSEQUENTIAL 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 WITH THE
SOFTWARE.
*/
/*
PFFFT : a Pretty Fast FFT.
This is basically an adaptation of the single precision fftpack
(v4) as found on netlib taking advantage of SIMD instruction found
on cpus such as intel x86 (SSE1), powerpc (Altivec), and arm (NEON).
For architectures where no SIMD instruction is available, the code
falls back to a scalar version.
Restrictions:
- 1D transforms only, with 32-bit single precision.
- supports only transforms for inputs of length N of the form
N=(2^a)*(3^b)*(5^c), a >= 5, b >=0, c >= 0 (32, 48, 64, 96, 128,
144, 160, etc are all acceptable lengths). Performance is best for
128<=N<=8192.
- all (float*) pointers in the functions below are expected to
have an "simd-compatible" alignment, that is 16 bytes on x86 and
powerpc CPUs.
You can allocate such buffers with the functions
pffft_aligned_malloc / pffft_aligned_free (or with stuff like
posix_memalign..)
*/
#ifndef PFFFT_H
#define PFFFT_H
#include <stddef.h> // for size_t
#ifdef __cplusplus
extern "C" {
#endif
/* opaque struct holding internal stuff (precomputed twiddle factors)
this struct can be shared by many threads as it contains only
read-only data.
*/
typedef struct PFFFT_Setup PFFFT_Setup;
/* direction of the transform */
typedef enum { PFFFT_FORWARD, PFFFT_BACKWARD } pffft_direction_t;
/* type of transform */
typedef enum { PFFFT_REAL, PFFFT_COMPLEX } pffft_transform_t;
/*
prepare for performing transforms of size N -- the returned
PFFFT_Setup structure is read-only so it can safely be shared by
multiple concurrent threads.
*/
PFFFT_Setup *pffft_new_setup(int N, pffft_transform_t transform);
void pffft_destroy_setup(PFFFT_Setup *);
/*
Perform a Fourier transform , The z-domain data is stored in the
most efficient order for transforming it back, or using it for
convolution. If you need to have its content sorted in the
"usual" way, that is as an array of interleaved complex numbers,
either use pffft_transform_ordered , or call pffft_zreorder after
the forward fft, and before the backward fft.
Transforms are not scaled: PFFFT_BACKWARD(PFFFT_FORWARD(x)) = N*x.
Typically you will want to scale the backward transform by 1/N.
The 'work' pointer should point to an area of N (2*N for complex
fft) floats, properly aligned. If 'work' is NULL, then stack will
be used instead (this is probably the best strategy for small
FFTs, say for N < 16384).
input and output may alias.
*/
void pffft_transform(PFFFT_Setup *setup, const float *input, float *output, float *work, pffft_direction_t direction);
/*
Similar to pffft_transform, but makes sure that the output is
ordered as expected (interleaved complex numbers). This is
similar to calling pffft_transform and then pffft_zreorder.
input and output may alias.
*/
void pffft_transform_ordered(PFFFT_Setup *setup, const float *input, float *output, float *work, pffft_direction_t direction);
/*
call pffft_zreorder(.., PFFFT_FORWARD) after pffft_transform(...,
PFFFT_FORWARD) if you want to have the frequency components in
the correct "canonical" order, as interleaved complex numbers.
(for real transforms, both 0-frequency and half frequency
components, which are real, are assembled in the first entry as
F(0)+i*F(n/2+1). Note that the original fftpack did place
F(n/2+1) at the end of the arrays).
input and output should not alias.
*/
void pffft_zreorder(PFFFT_Setup *setup, const float *input, float *output, pffft_direction_t direction);
/*
Perform a multiplication of the frequency components of dft_a and
dft_b and accumulate them into dft_ab. The arrays should have
been obtained with pffft_transform(.., PFFFT_FORWARD) and should
*not* have been reordered with pffft_zreorder (otherwise just
perform the operation yourself as the dft coefs are stored as
interleaved complex numbers).
the operation performed is: dft_ab = dft_c + (dft_a * fdt_b)*scaling
The dft_a, dft_b and dft_ab pointers may alias.
*/
void pffft_zconvolve_accumulate(PFFFT_Setup *setup, const float *dft_a, const float *dft_b, const float *dft_c, float *dft_ab, float scaling);
void pffft_zconvolve(PFFFT_Setup *setup, const float *dft_a, const float *dft_b, float *dft_ab, float scaling);
/*
the float buffers must have the correct alignment (16-byte boundary
on intel and powerpc). This function may be used to obtain such
correctly aligned buffers.
*/
void *pffft_aligned_malloc(size_t nb_bytes);
void pffft_aligned_free(void *);
/* return 4 or 1 depending on whether support for SSE/Altivec instructions was enabled when building pffft.c */
int pffft_simd_size(void);
void pffft_select_cpu(int flags);
#ifdef __cplusplus
}
#endif
#endif // PFFFT_H

View file

@ -1,555 +0,0 @@
#include "config.h"
#include <limits.h>
#include <spa/utils/json.h>
#include <spa/support/loop.h>
#include <spa/support/log.h>
#include "audio-plugin.h"
#include "convolver.h"
#include "audio-dsp.h"
#include <mysofa.h>
struct plugin {
struct spa_handle handle;
struct spa_fga_plugin plugin;
struct spa_fga_dsp *dsp;
struct spa_log *log;
struct spa_loop *data_loop;
struct spa_loop *main_loop;
uint32_t quantum_limit;
};
struct spatializer_impl {
struct plugin *plugin;
struct spa_fga_dsp *dsp;
struct spa_log *log;
unsigned long rate;
float *port[6];
int n_samples, blocksize, tailsize;
float *tmp[2];
struct MYSOFA_EASY *sofa;
unsigned int interpolate:1;
struct convolver *l_conv[3];
struct convolver *r_conv[3];
};
static void * spatializer_instantiate(const struct spa_fga_plugin *plugin, const struct spa_fga_descriptor * Descriptor,
unsigned long SampleRate, int index, const char *config)
{
struct plugin *pl = SPA_CONTAINER_OF(plugin, struct plugin, plugin);
struct spatializer_impl *impl;
struct spa_json it[1];
const char *val;
char key[256];
char filename[PATH_MAX] = "";
int len;
errno = EINVAL;
if (config == NULL) {
spa_log_error(pl->log, "spatializer: no config was given");
return NULL;
}
if (spa_json_begin_object(&it[0], config, strlen(config)) <= 0) {
spa_log_error(pl->log, "spatializer: expected object in config");
return NULL;
}
impl = calloc(1, sizeof(*impl));
if (impl == NULL) {
errno = ENOMEM;
return NULL;
}
impl->plugin = pl;
impl->dsp = pl->dsp;
impl->log = pl->log;
while ((len = spa_json_object_next(&it[0], key, sizeof(key), &val)) > 0) {
if (spa_streq(key, "blocksize")) {
if (spa_json_parse_int(val, len, &impl->blocksize) <= 0) {
spa_log_error(impl->log, "spatializer:blocksize requires a number");
errno = EINVAL;
goto error;
}
}
else if (spa_streq(key, "tailsize")) {
if (spa_json_parse_int(val, len, &impl->tailsize) <= 0) {
spa_log_error(impl->log, "spatializer:tailsize requires a number");
errno = EINVAL;
goto error;
}
}
else if (spa_streq(key, "filename")) {
if (spa_json_parse_stringn(val, len, filename, sizeof(filename)) <= 0) {
spa_log_error(impl->log, "spatializer:filename requires a string");
errno = EINVAL;
goto error;
}
}
}
if (!filename[0]) {
spa_log_error(impl->log, "spatializer:filename was not given");
errno = EINVAL;
goto error;
}
int ret = MYSOFA_OK;
impl->sofa = mysofa_open_cached(filename, SampleRate, &impl->n_samples, &ret);
if (ret != MYSOFA_OK) {
const char *reason;
switch (ret) {
case MYSOFA_INVALID_FORMAT:
reason = "Invalid format";
errno = EINVAL;
break;
case MYSOFA_UNSUPPORTED_FORMAT:
reason = "Unsupported format";
errno = ENOTSUP;
break;
case MYSOFA_NO_MEMORY:
reason = "No memory";
errno = ENOMEM;
break;
case MYSOFA_READ_ERROR:
reason = "Read error";
errno = ENOENT;
break;
case MYSOFA_INVALID_ATTRIBUTES:
reason = "Invalid attributes";
errno = EINVAL;
break;
case MYSOFA_INVALID_DIMENSIONS:
reason = "Invalid dimensions";
errno = EINVAL;
break;
case MYSOFA_INVALID_DIMENSION_LIST:
reason = "Invalid dimension list";
errno = EINVAL;
break;
case MYSOFA_INVALID_COORDINATE_TYPE:
reason = "Invalid coordinate type";
errno = EINVAL;
break;
case MYSOFA_ONLY_EMITTER_WITH_ECI_SUPPORTED:
reason = "Only emitter with ECI supported";
errno = ENOTSUP;
break;
case MYSOFA_ONLY_DELAYS_WITH_IR_OR_MR_SUPPORTED:
reason = "Only delays with IR or MR supported";
errno = ENOTSUP;
break;
case MYSOFA_ONLY_THE_SAME_SAMPLING_RATE_SUPPORTED:
reason = "Only the same sampling rate supported";
errno = ENOTSUP;
break;
case MYSOFA_RECEIVERS_WITH_RCI_SUPPORTED:
reason = "Receivers with RCI supported";
errno = ENOTSUP;
break;
case MYSOFA_RECEIVERS_WITH_CARTESIAN_SUPPORTED:
reason = "Receivers with cartesian supported";
errno = ENOTSUP;
break;
case MYSOFA_INVALID_RECEIVER_POSITIONS:
reason = "Invalid receiver positions";
errno = EINVAL;
break;
case MYSOFA_ONLY_SOURCES_WITH_MC_SUPPORTED:
reason = "Only sources with MC supported";
errno = ENOTSUP;
break;
default:
case MYSOFA_INTERNAL_ERROR:
errno = EIO;
reason = "Internal error";
break;
}
spa_log_error(impl->log, "Unable to load HRTF from %s: %s (%d)", filename, reason, ret);
goto error;
}
if (impl->blocksize <= 0)
impl->blocksize = SPA_CLAMP(impl->n_samples, 64, 256);
if (impl->tailsize <= 0)
impl->tailsize = SPA_CLAMP(4096, impl->blocksize, 32768);
spa_log_info(impl->log, "using n_samples:%u %d:%d blocksize sofa:%s", impl->n_samples,
impl->blocksize, impl->tailsize, filename);
impl->tmp[0] = calloc(impl->plugin->quantum_limit, sizeof(float));
impl->tmp[1] = calloc(impl->plugin->quantum_limit, sizeof(float));
impl->rate = SampleRate;
return impl;
error:
if (impl->sofa)
mysofa_close_cached(impl->sofa);
free(impl);
return NULL;
}
static int
do_switch(struct spa_loop *loop, bool async, uint32_t seq, const void *data,
size_t size, void *user_data)
{
struct spatializer_impl *impl = user_data;
if (impl->l_conv[0] == NULL) {
SPA_SWAP(impl->l_conv[0], impl->l_conv[2]);
SPA_SWAP(impl->r_conv[0], impl->r_conv[2]);
} else {
SPA_SWAP(impl->l_conv[1], impl->l_conv[2]);
SPA_SWAP(impl->r_conv[1], impl->r_conv[2]);
}
impl->interpolate = impl->l_conv[0] && impl->l_conv[1];
return 0;
}
static void spatializer_reload(void * Instance)
{
struct spatializer_impl *impl = Instance;
float *left_ir = calloc(impl->n_samples, sizeof(float));
float *right_ir = calloc(impl->n_samples, sizeof(float));
float left_delay;
float right_delay;
float coords[3];
for (uint8_t i = 0; i < 3; i++)
coords[i] = impl->port[3 + i][0];
spa_log_info(impl->log, "making spatializer with %f %f %f", coords[0], coords[1], coords[2]);
mysofa_s2c(coords);
mysofa_getfilter_float(
impl->sofa,
coords[0],
coords[1],
coords[2],
left_ir,
right_ir,
&left_delay,
&right_delay
);
// TODO: make use of delay
if ((left_delay != 0.0f || right_delay != 0.0f) && (!isnan(left_delay) || !isnan(right_delay)))
spa_log_warn(impl->log, "delay dropped l: %f, r: %f", left_delay, right_delay);
if (impl->l_conv[2])
convolver_free(impl->l_conv[2]);
if (impl->r_conv[2])
convolver_free(impl->r_conv[2]);
impl->l_conv[2] = convolver_new(impl->dsp, impl->blocksize, impl->tailsize,
left_ir, impl->n_samples);
impl->r_conv[2] = convolver_new(impl->dsp, impl->blocksize, impl->tailsize,
right_ir, impl->n_samples);
free(left_ir);
free(right_ir);
if (impl->l_conv[2] == NULL || impl->r_conv[2] == NULL) {
spa_log_error(impl->log, "reloading left or right convolver failed");
return;
}
spa_loop_invoke(impl->plugin->data_loop, do_switch, 1, NULL, 0, true, impl);
}
struct free_data {
void *item[2];
};
static int
do_free(struct spa_loop *loop, bool async, uint32_t seq, const void *data,
size_t size, void *user_data)
{
const struct free_data *fd = data;
if (fd->item[0])
convolver_free(fd->item[0]);
if (fd->item[1])
convolver_free(fd->item[1]);
return 0;
}
static void spatializer_run(void * Instance, unsigned long SampleCount)
{
struct spatializer_impl *impl = Instance;
if (impl->interpolate) {
uint32_t len = SPA_MIN(SampleCount, impl->plugin->quantum_limit);
struct free_data free_data;
float *l = impl->tmp[0], *r = impl->tmp[1];
convolver_run(impl->l_conv[0], impl->port[2], impl->port[0], len);
convolver_run(impl->l_conv[1], impl->port[2], l, len);
convolver_run(impl->r_conv[0], impl->port[2], impl->port[1], len);
convolver_run(impl->r_conv[1], impl->port[2], r, len);
for (uint32_t i = 0; i < SampleCount; i++) {
float t = (float)i / SampleCount;
impl->port[0][i] = impl->port[0][i] * (1.0f - t) + l[i] * t;
impl->port[1][i] = impl->port[1][i] * (1.0f - t) + r[i] * t;
}
free_data.item[0] = impl->l_conv[0];
free_data.item[1] = impl->r_conv[0];
impl->l_conv[0] = impl->l_conv[1];
impl->r_conv[0] = impl->r_conv[1];
impl->l_conv[1] = impl->r_conv[1] = NULL;
impl->interpolate = false;
spa_loop_invoke(impl->plugin->main_loop, do_free, 1, &free_data, sizeof(free_data), false, impl);
} else if (impl->l_conv[0] && impl->r_conv[0]) {
convolver_run(impl->l_conv[0], impl->port[2], impl->port[0], SampleCount);
convolver_run(impl->r_conv[0], impl->port[2], impl->port[1], SampleCount);
}
}
static void spatializer_connect_port(void * Instance, unsigned long Port,
float * DataLocation)
{
struct spatializer_impl *impl = Instance;
if (Port > 5)
return;
impl->port[Port] = DataLocation;
}
static void spatializer_cleanup(void * Instance)
{
struct spatializer_impl *impl = Instance;
for (uint8_t i = 0; i < 3; i++) {
if (impl->l_conv[i])
convolver_free(impl->l_conv[i]);
if (impl->r_conv[i])
convolver_free(impl->r_conv[i]);
}
if (impl->sofa)
mysofa_close_cached(impl->sofa);
free(impl->tmp[0]);
free(impl->tmp[1]);
free(impl);
}
static void spatializer_control_changed(void * Instance)
{
spatializer_reload(Instance);
}
static void spatializer_deactivate(void * Instance)
{
struct spatializer_impl *impl = Instance;
if (impl->l_conv[0])
convolver_reset(impl->l_conv[0]);
if (impl->r_conv[0])
convolver_reset(impl->r_conv[0]);
impl->interpolate = false;
}
static struct spa_fga_port spatializer_ports[] = {
{ .index = 0,
.name = "Out L",
.flags = SPA_FGA_PORT_OUTPUT | SPA_FGA_PORT_AUDIO,
},
{ .index = 1,
.name = "Out R",
.flags = SPA_FGA_PORT_OUTPUT | SPA_FGA_PORT_AUDIO,
},
{ .index = 2,
.name = "In",
.flags = SPA_FGA_PORT_INPUT | SPA_FGA_PORT_AUDIO,
},
{ .index = 3,
.name = "Azimuth",
.flags = SPA_FGA_PORT_INPUT | SPA_FGA_PORT_CONTROL,
.def = 0.0f, .min = 0.0f, .max = 360.0f
},
{ .index = 4,
.name = "Elevation",
.flags = SPA_FGA_PORT_INPUT | SPA_FGA_PORT_CONTROL,
.def = 0.0f, .min = -90.0f, .max = 90.0f
},
{ .index = 5,
.name = "Radius",
.flags = SPA_FGA_PORT_INPUT | SPA_FGA_PORT_CONTROL,
.def = 1.0f, .min = 0.0f, .max = 100.0f
},
};
static const struct spa_fga_descriptor spatializer_desc = {
.name = "spatializer",
.n_ports = 6,
.ports = spatializer_ports,
.instantiate = spatializer_instantiate,
.connect_port = spatializer_connect_port,
.control_changed = spatializer_control_changed,
.deactivate = spatializer_deactivate,
.run = spatializer_run,
.cleanup = spatializer_cleanup,
};
static const struct spa_fga_descriptor * sofa_descriptor(unsigned long Index)
{
switch(Index) {
case 0:
return &spatializer_desc;
}
return NULL;
}
static const struct spa_fga_descriptor *sofa_plugin_make_desc(void *plugin, const char *name)
{
unsigned long i;
for (i = 0; ;i++) {
const struct spa_fga_descriptor *d = sofa_descriptor(i);
if (d == NULL)
break;
if (spa_streq(d->name, name))
return d;
}
return NULL;
}
static struct spa_fga_plugin_methods impl_plugin = {
SPA_VERSION_FGA_PLUGIN_METHODS,
.make_desc = sofa_plugin_make_desc,
};
static int impl_get_interface(struct spa_handle *handle, const char *type, void **interface)
{
struct plugin *impl;
spa_return_val_if_fail(handle != NULL, -EINVAL);
spa_return_val_if_fail(interface != NULL, -EINVAL);
impl = (struct plugin *) handle;
if (spa_streq(type, SPA_TYPE_INTERFACE_FILTER_GRAPH_AudioPlugin))
*interface = &impl->plugin;
else
return -ENOENT;
return 0;
}
static int impl_clear(struct spa_handle *handle)
{
return 0;
}
static size_t
impl_get_size(const struct spa_handle_factory *factory,
const struct spa_dict *params)
{
return sizeof(struct plugin);
}
static int
impl_init(const struct spa_handle_factory *factory,
struct spa_handle *handle,
const struct spa_dict *info,
const struct spa_support *support,
uint32_t n_support)
{
struct plugin *impl;
handle->get_interface = impl_get_interface;
handle->clear = impl_clear;
impl = (struct plugin *) handle;
impl->plugin.iface = SPA_INTERFACE_INIT(
SPA_TYPE_INTERFACE_FILTER_GRAPH_AudioPlugin,
SPA_VERSION_FGA_PLUGIN,
&impl_plugin, impl);
impl->quantum_limit = 8192u;
impl->log = spa_support_find(support, n_support, SPA_TYPE_INTERFACE_Log);
impl->data_loop = spa_support_find(support, n_support, SPA_TYPE_INTERFACE_DataLoop);
impl->main_loop = spa_support_find(support, n_support, SPA_TYPE_INTERFACE_Loop);
impl->dsp = spa_support_find(support, n_support, SPA_TYPE_INTERFACE_FILTER_GRAPH_AudioDSP);
for (uint32_t i = 0; info && i < info->n_items; i++) {
const char *k = info->items[i].key;
const char *s = info->items[i].value;
if (spa_streq(k, "clock.quantum-limit"))
spa_atou32(s, &impl->quantum_limit, 0);
if (spa_streq(k, "filter.graph.audio.dsp"))
sscanf(s, "pointer:%p", &impl->dsp);
}
if (impl->data_loop == NULL || impl->main_loop == NULL) {
spa_log_error(impl->log, "%p: could not find a data/main loop", impl);
return -EINVAL;
}
if (impl->dsp == NULL) {
spa_log_error(impl->log, "%p: could not find DSP functions", impl);
return -EINVAL;
}
return 0;
}
static const struct spa_interface_info impl_interfaces[] = {
{SPA_TYPE_INTERFACE_FILTER_GRAPH_AudioPlugin,},
};
static int
impl_enum_interface_info(const struct spa_handle_factory *factory,
const struct spa_interface_info **info,
uint32_t *index)
{
spa_return_val_if_fail(factory != NULL, -EINVAL);
spa_return_val_if_fail(info != NULL, -EINVAL);
spa_return_val_if_fail(index != NULL, -EINVAL);
switch (*index) {
case 0:
*info = &impl_interfaces[*index];
break;
default:
return 0;
}
(*index)++;
return 1;
}
struct spa_handle_factory spa_fga_sofa_plugin_factory = {
SPA_VERSION_HANDLE_FACTORY,
"filter.graph.plugin.sofa",
NULL,
impl_get_size,
impl_init,
impl_enum_interface_info,
};
SPA_EXPORT
int spa_handle_factory_enum(const struct spa_handle_factory **factory, uint32_t *index)
{
spa_return_val_if_fail(factory != NULL, -EINVAL);
spa_return_val_if_fail(index != NULL, -EINVAL);
switch (*index) {
case 0:
*factory = &spa_fga_sofa_plugin_factory;
break;
default:
return 0;
}
(*index)++;
return 1;
}