Skip to content

Instantly share code, notes, and snippets.

@kode54
Last active January 19, 2020 03:08
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save kode54/68fed8663b13b5e9d1ef284de42e6f69 to your computer and use it in GitHub Desktop.
Save kode54/68fed8663b13b5e9d1ef284de42e6f69 to your computer and use it in GitHub Desktop.
diff -urN pulseaudio.orig/src/modules/meson.build pulseaudio/src/modules/meson.build
--- pulseaudio.orig/src/modules/meson.build 2020-01-18 00:13:15.288900614 -0800
+++ pulseaudio/src/modules/meson.build 2020-01-18 17:57:26.855466531 -0800
@@ -61,7 +61,6 @@
[ 'module-tunnel-source-new', 'module-tunnel-source-new.c' ],
[ 'module-virtual-sink', 'module-virtual-sink.c' ],
[ 'module-virtual-source', 'module-virtual-source.c' ],
- [ 'module-virtual-surround-sink', 'module-virtual-surround-sink.c' ],
[ 'module-volume-restore', 'module-volume-restore.c' ],
# [ 'module-waveout', 'module-waveout.c' ],
]
@@ -137,6 +136,12 @@
]
endif
+if fftw_dep.found()
+ all_modules += [
+ [ 'module-virtual-surround-sink', ['module-virtual-surround-sink.c', 'simple-convolver.c', 'simple-convolver.h'], [], [], [fftw_dep, libm_dep] ],
+ ]
+endif
+
if dbus_dep.found() and fftw_dep.found()
all_modules += [
[ 'module-equalizer-sink', 'module-equalizer-sink.c', [], [], [dbus_dep, fftw_dep, libm_dep] ],
diff -urN pulseaudio.orig/src/modules/module-virtual-surround-sink.c pulseaudio/src/modules/module-virtual-surround-sink.c
--- pulseaudio.orig/src/modules/module-virtual-surround-sink.c 2020-01-18 00:13:15.138234743 -0800
+++ pulseaudio/src/modules/module-virtual-surround-sink.c 2020-01-18 18:37:28.524891222 -0800
@@ -41,6 +41,8 @@
#include <math.h>
+#include "simple-convolver.h"
+
PA_MODULE_AUTHOR("Niels Ole Salscheider");
PA_MODULE_DESCRIPTION(_("Virtual surround sink"));
PA_MODULE_VERSION(PACKAGE_VERSION);
@@ -73,6 +75,8 @@
pa_sink_input *sink_input;
pa_memblockq *memblockq;
+
+ void *convolver;
bool auto_desc;
unsigned channels;
@@ -86,9 +90,6 @@
unsigned hrir_samples;
float *hrir_data;
- float *input_buffer;
- int input_buffer_offset;
-
bool autoloaded;
};
@@ -120,7 +121,8 @@
* make sure we don't access it in that time. Also, the
* sink input is first shut down, the sink second. */
if (!PA_SINK_IS_LINKED(u->sink->thread_info.state) ||
- !PA_SINK_INPUT_IS_LINKED(u->sink_input->thread_info.state)) {
+ !PA_SINK_INPUT_IS_LINKED(u->sink_input->thread_info.state) ||
+ !u->convolver) {
*((int64_t*) data) = 0;
return 0;
}
@@ -131,7 +133,10 @@
pa_sink_get_latency_within_thread(u->sink_input->sink, true) +
/* Add the latency internal to our sink input on top */
- pa_bytes_to_usec(pa_memblockq_get_length(u->sink_input->thread_info.render_memblockq), &u->sink_input->sink->sample_spec);
+ pa_bytes_to_usec(pa_memblockq_get_length(u->sink_input->thread_info.render_memblockq), &u->sink_input->sink->sample_spec) +
+
+ /* Add the latency of the convolver */
+ pa_bytes_to_usec((convolver_get_free_count(u->convolver) + convolver_ready(u->convolver)) * sizeof(float) * u->channels, &u->sink_input->sink->sample_spec);
return 0;
}
@@ -241,12 +246,12 @@
pa_memchunk tchunk;
unsigned j, k, l;
- float sum_right, sum_left;
- float current_sample;
pa_sink_input_assert_ref(i);
pa_assert(chunk);
pa_assert_se(u = i->userdata);
+
+ float samples[(u->sink_fs / sizeof(float)) + 1];
if (!PA_SINK_IS_LINKED(u->sink->thread_info.state))
return -1;
@@ -279,27 +284,14 @@
dst = pa_memblock_acquire(chunk->memblock);
for (l = 0; l < n; l++) {
- memcpy(((char*) u->input_buffer) + u->input_buffer_offset * u->sink_fs, ((char *) src) + l * u->sink_fs, u->sink_fs);
-
- sum_right = 0;
- sum_left = 0;
-
- /* fold the input buffer with the impulse response */
- for (j = 0; j < u->hrir_samples; j++) {
- for (k = 0; k < u->channels; k++) {
- current_sample = u->input_buffer[((u->input_buffer_offset + j) % u->hrir_samples) * u->channels + k];
-
- sum_left += current_sample * u->hrir_data[j * u->hrir_channels + u->mapping_left[k]];
- sum_right += current_sample * u->hrir_data[j * u->hrir_channels + u->mapping_right[k]];
- }
- }
+ memcpy(samples, ((char *) src) + l * u->sink_fs, u->sink_fs);
+
+ convolver_write(u->convolver, samples);
+
+ convolver_read(u->convolver, samples);
- dst[2 * l] = PA_CLAMP_UNLIKELY(sum_left, -1.0f, 1.0f);
- dst[2 * l + 1] = PA_CLAMP_UNLIKELY(sum_right, -1.0f, 1.0f);
-
- u->input_buffer_offset--;
- if (u->input_buffer_offset < 0)
- u->input_buffer_offset += u->hrir_samples;
+ dst[2 * l] = PA_CLAMP_UNLIKELY(samples[0], -1.0f, 1.0f);
+ dst[2 * l + 1] = PA_CLAMP_UNLIKELY(samples[1], -1.0f, 1.0f);
}
pa_memblock_release(tchunk.memblock);
@@ -331,10 +323,8 @@
if (amount > 0) {
pa_memblockq_seek(u->memblockq, - (int64_t) amount, PA_SEEK_RELATIVE, true);
-
- /* Reset the input buffer */
- memset(u->input_buffer, 0, u->hrir_samples * u->sink_fs);
- u->input_buffer_offset = 0;
+ if (u->convolver)
+ convolver_clear(u->convolver);
}
}
@@ -794,10 +784,6 @@
PA_RESAMPLER_SRC_SINC_BEST_QUALITY, PA_RESAMPLER_NO_REMAP);
u->hrir_samples = hrir_temp_chunk.length / pa_frame_size(&hrir_temp_ss) * hrir_ss.rate / hrir_temp_ss.rate;
- if (u->hrir_samples > 64) {
- u->hrir_samples = 64;
- pa_log("The (resampled) hrir contains more than 64 samples. Only the first 64 samples will be used to limit processor usage.");
- }
hrir_total_length = u->hrir_samples * pa_frame_size(&hrir_ss);
u->hrir_channels = hrir_ss.channels;
@@ -871,9 +857,33 @@
goto fail;
}
}
-
- u->input_buffer = pa_xmalloc0(u->hrir_samples * u->sink_fs);
- u->input_buffer_offset = 0;
+
+ float ** impulseptrs = (float **) pa_xmalloc(map.channels * 2 * sizeof(float *));
+
+ float * impulses = (float *) pa_xmalloc(hrir_total_length * sizeof(float));
+
+ for (i = 0; i < map.channels; i++) {
+ float * impulseleft = impulses + i * 2 * u->hrir_samples;
+ float * impulseright = impulseleft + u->hrir_samples;
+ int mapping_left = u->mapping_left[i];
+ int mapping_right = u->mapping_right[i];
+ for (j = 0; j < u->hrir_samples; j++) {
+ impulseleft[j] = u->hrir_data[j * map.channels + mapping_left];
+ impulseright[j] = u->hrir_data[j * map.channels + mapping_right];
+ }
+ impulseptrs[i * 2 + 0] = impulseleft;
+ impulseptrs[i * 2 + 1] = impulseright;
+ }
+
+ pa_assert_se(u->convolver = convolver_create((const float * const *)impulseptrs, u->hrir_samples, map.channels, 2, 3));
+
+ pa_xfree(impulses);
+ pa_xfree(impulseptrs);
+
+ /* This no longer needs to be kept, since the convolver keeps
+ * its own copy for the life of the module. */
+ pa_xfree(u->hrir_data);
+ u->hrir_data = NULL;
/* The order here is important. The input must be put first,
* otherwise streams might attach to the sink before the sink
@@ -940,8 +950,8 @@
if (u->hrir_data)
pa_xfree(u->hrir_data);
- if (u->input_buffer)
- pa_xfree(u->input_buffer);
+ if (u->convolver)
+ convolver_delete(u->convolver);
if (u->mapping_left)
pa_xfree(u->mapping_left);
diff -urN pulseaudio.orig/src/modules/simple-convolver.c pulseaudio/src/modules/simple-convolver.c
--- pulseaudio.orig/src/modules/simple-convolver.c 1969-12-31 16:00:00.000000000 -0800
+++ pulseaudio/src/modules/simple-convolver.c 2020-01-18 18:25:54.151586785 -0800
@@ -0,0 +1,451 @@
+/***
+ This file is part of PulseAudio.
+
+ This convolver is based off general documentation I found from searching
+ around the Internet. It was originally written for KissFFT, then later
+ FFTW3 and then Apple vDSP support were added. It was originally published
+ under the BSD three clause license. It has been trimmed down to just use
+ FFTW3, as that is already an optional dependency.
+ All new work is published under PulseAudio's original license.
+
+ Copyright 2020 Christopher Snowhill <kode54@gmail.com>
+
+ PulseAudio 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.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with PulseAudio; if not, see <http://www.gnu.org/licenses/>.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include "simple-convolver.h"
+
+#include <pulse/xmalloc.h>
+
+#include <pulsecore/macro.h>
+
+#include <string.h>
+#include <math.h>
+
+#include <fftw3.h>
+
+#define v_size 4
+
+static void * alloc(size_t x, size_t s) {
+ size_t f;
+ float *t;
+
+ f = PA_ROUND_UP(x*s, sizeof(float)*v_size);
+ pa_assert_se(t = fftwf_malloc(f));
+ pa_memzero(t, f);
+
+ return t;
+}
+
+/* Only the simplest of state information is necessary for this, and it's
+ * designed around a simple usage case. As many samples as you can generate,
+ * input one at a time, and output pulled one at a time as well. */
+
+typedef struct convolver_state
+{
+ int fftlen; /* size of FFT */
+ int stepsize; /* size of overlapping steps */
+ int buffered_in; /* how many input samples buffered */
+ int buffered_out; /* output samples buffered */
+ int inputs; /* Input channels */
+ int outputs; /* Output channels */
+ int mode; /* Mode */
+ fftwf_plan *p_fw, p_bw; /* forward and backwards plans */
+ fftwf_complex *f_in, *f_out, **f_ir; /* input, output, and impulse in frequency domain */
+ float *revspace, **outspace, **inspace; /* reverse, output, and input work space */
+} convolver_state;
+
+/* Fully opaque convolver state created and returned here, otherwise NULL on
+ * failure. Users are welcome to change this to pass in a const pointer to an
+ * impulse and its size, which will be copied and no longer needed upon return.
+ * It is assumed that there will be one impulse per input channel, and that
+ * each impulse will have one channel per output. */
+
+void * convolver_create(const float * const* impulse, int impulse_size, int input_channels, int output_channels, int mode) {
+ convolver_state * state;
+ int fftlen, total_channels, i, j, k;
+
+ if (mode < 0 || mode > 3)
+ return 0;
+
+ if ((mode == 0 || mode == 1) && input_channels != output_channels)
+ return 0;
+
+ state = (convolver_state *) alloc(1, sizeof(convolver_state));
+
+ if (!state)
+ return NULL;
+
+ state->mode = mode;
+ state->inputs = input_channels;
+ state->outputs = output_channels;
+ if (mode == 0)
+ total_channels = 1;
+ else if (mode == 1)
+ total_channels = input_channels;
+ else if (mode == 2 || mode == 3)
+ total_channels = input_channels * output_channels;
+
+ fftlen = (impulse_size * 11) / 8;
+ {
+ // round up to a power of two
+ int pow = 1;
+ while ( fftlen > 2 ) { pow++; fftlen /= 2; }
+ fftlen = 2 << pow;
+ }
+
+ state->fftlen = fftlen;
+ state->stepsize = fftlen - impulse_size;
+ state->buffered_in = 0;
+ state->buffered_out = 0;
+
+ /* Prepare arrays for multiple inputs */
+ /* And we use kissfft's aligned malloc functions/macros to allocate these things. */
+
+ if ((state->f_in = (fftwf_complex*) alloc(sizeof(fftwf_complex), (fftlen/2+1))) == NULL)
+ goto error;
+
+ if ((state->f_out = (fftwf_complex*) alloc(sizeof(fftwf_complex), (fftlen/2+1))) == NULL)
+ goto error;
+
+ if ((state->f_ir = (fftwf_complex**) alloc(sizeof(fftwf_complex*), total_channels)) == NULL)
+ goto error;
+ for (i = 0; i < total_channels; ++i) {
+ if ((state->f_ir[i] = (fftwf_complex*) alloc(sizeof(fftwf_complex), (fftlen/2+1))) == NULL)
+ goto error;
+ }
+
+ if ((state->revspace = (float *) alloc(sizeof(float), fftlen)) == NULL)
+ goto error;
+
+ if ((state->outspace = (float **) alloc(sizeof(float *), output_channels)) == NULL)
+ goto error;
+ for (i = 0; i < output_channels; ++i) {
+ if ((state->outspace[i] = (float *) alloc(sizeof(float), fftlen)) == NULL)
+ goto error;
+ }
+
+ if ((state->inspace = (float **) alloc(sizeof(float *), input_channels)) == NULL)
+ goto error;
+ for (i = 0; i < input_channels; ++i) {
+ if ((state->inspace[i] = (float *) alloc(sizeof(float), fftlen)) == NULL)
+ goto error;
+ }
+
+ if ((state->p_fw = (fftwf_plan *) alloc(sizeof(fftwf_plan), input_channels)) == NULL)
+ goto error;
+
+ for (i = 0; i < input_channels; ++i) {
+ if ((state->p_fw[i] = fftwf_plan_dft_r2c_1d(fftlen, state->inspace[i], state->f_in, FFTW_ESTIMATE)) == NULL)
+ goto error;
+ }
+ if ((state->p_bw = fftwf_plan_dft_c2r_1d(fftlen, state->f_out, state->revspace, FFTW_ESTIMATE)) == NULL)
+ goto error;
+
+ convolver_restage(state, impulse);
+
+ return state;
+
+error:
+ convolver_delete(state);
+ return NULL;
+}
+
+/* Restage the convolver with a new impulse set, same size/parameters */
+void convolver_restage(void * state_, const float * const * impulse) {
+ convolver_state * state = (convolver_state *) state_;
+
+ float * impulse_temp;
+
+ int impulse_count;
+ int channels_per_impulse;
+ int fftlen = state->fftlen;
+ int impulse_size = fftlen - state->stepsize - 10;
+ int i, j, k;
+
+ fftwf_plan p;
+ fftwf_complex ** f_ir = state->f_ir;
+
+ if (state->mode == 0 || state->mode == 1)
+ impulse_count = 1;
+ else if (state->mode == 2)
+ impulse_count = state->inputs;
+ else if (state->mode == 3)
+ impulse_count = state->inputs * state->outputs;
+
+ if (state->mode == 0 || state->mode == 3)
+ channels_per_impulse = 1;
+ else if (state->mode == 1 || state->mode == 2)
+ channels_per_impulse = state->outputs;
+
+ /* Since the FFT requires a full input for every transformaton, we allocate
+ * a temporary buffer, which we fill with the impulse, then pad with silence. */
+
+ if ((impulse_temp = (float*) alloc(sizeof(float), fftlen)) == NULL)
+ return;
+
+ /* Our alloc() function zeroes the memory for us. */
+ //memset(impulse_temp + impulse_size, 0, sizeof(float) * (fftlen - impulse_size));
+
+ for (i = 0; i < impulse_count; ++i) {
+ for (j = 0; j < channels_per_impulse; ++j) {
+ for (k = 0; k < impulse_size; ++k) {
+ impulse_temp[k] = impulse[i][j + k * channels_per_impulse];
+ }
+
+ /* Our first actual transformation, which is cached for the life of this convolver. */
+ p = fftwf_plan_dft_r2c_1d(fftlen, impulse_temp, f_ir[i * channels_per_impulse + j], FFTW_ESTIMATE);
+ if (p) {
+ fftwf_execute(p);
+ fftwf_destroy_plan(p);
+ }
+ }
+ }
+
+ fftwf_free(impulse_temp);
+}
+
+/* Delete our opaque state, by freeing all of its member structures, then the
+ * top level structure itself. */
+
+void convolver_delete(void * state_) {
+ if (state_) {
+ int i, input_channels, output_channels, total_channels;
+ convolver_state * state = (convolver_state *) state_;
+ input_channels = state->inputs;
+ output_channels = state->outputs;
+ if (state->mode == 0)
+ total_channels = 1;
+ else if (state->mode == 1)
+ total_channels = input_channels;
+ else if (state->mode == 2 || state->mode == 3)
+ total_channels = input_channels * output_channels;
+ if (state->p_fw) {
+ for (i = 0; i < input_channels; ++i) {
+ if (state->p_fw[i])
+ fftwf_destroy_plan(state->p_fw[i]);
+ }
+ fftwf_free(state->p_fw);
+ }
+ if (state->p_bw)
+ fftwf_destroy_plan(state->p_bw);
+ if (state->f_ir) {
+ for (i = 0; i < total_channels; ++i) {
+ if (state->f_ir[i])
+ fftwf_free(state->f_ir[i]);
+ }
+ fftwf_free(state->f_ir);
+ }
+ if (state->f_out)
+ fftwf_free(state->f_out);
+ if (state->f_in)
+ fftwf_free(state->f_in);
+ if (state->revspace)
+ fftwf_free(state->revspace);
+ if (state->outspace) {
+ for (i = 0; i < output_channels; ++i) {
+ if (state->outspace[i])
+ fftwf_free(state->outspace[i]);
+ }
+ fftwf_free(state->outspace);
+ }
+ if (state->inspace) {
+ for (i = 0; i < input_channels; ++i) {
+ if (state->inspace[i])
+ fftwf_free(state->inspace[i]);
+ }
+ fftwf_free(state->inspace);
+ }
+ fftwf_free(state);
+ }
+}
+
+/* This resets the state between uses, if you need to restart output on startup. */
+
+void convolver_clear(void * state_) {
+ if (state_) {
+ /* Clearing for a new use setup only requires resetting the input and
+ * output buffers, not actually changing any of the FFT state. */
+
+ int i, input_channels, output_channels, fftlen;
+ convolver_state * state = (convolver_state *) state_;
+ input_channels = state->inputs;
+ output_channels = state->outputs;
+ fftlen = state->fftlen;
+ state->buffered_in = 0;
+ state->buffered_out = 0;
+ for (i = 0; i < input_channels; ++i)
+ memset(state->inspace[i], 0, sizeof(float) * fftlen);
+ for (i = 0; i < output_channels; ++i)
+ memset(state->outspace[i], 0, sizeof(float) * fftlen);
+ }
+}
+
+/* This returns how many slots are available in the input buffer, before the
+ * user may pull output. */
+
+int convolver_get_free_count(void * state_) {
+ if (state_) {
+ convolver_state * state = (convolver_state *) state_;
+ return state->stepsize - state->buffered_in;
+ }
+ return 0;
+}
+
+/* This returns how many output samples are currently buffered, or zero if not
+ * enough sample data has been buffered. */
+
+int convolver_ready(void * state_) {
+ if (state_) {
+ convolver_state * state = (convolver_state *) state_;
+ return state->buffered_out;
+ }
+ return 0;
+}
+
+/* Input sample data is fed in here, one sample at a time. */
+
+void convolver_write(void * state_, const float * input_samples) {
+ if (state_) {
+ int i, j, k, input_channels;
+ convolver_state * state = (convolver_state *) state_;
+ input_channels = state->inputs;
+
+ for (i = 0; i < input_channels; ++i)
+ state->inspace[i][state->buffered_in] = input_samples[i];
+
+ ++state->buffered_in;
+
+ /* And every stepsize samples buffered, it convolves a new block of samples. */
+
+ if (state->buffered_in == state->stepsize) {
+ int output_channels = state->outputs;
+ int fftlen;
+ int index;
+ float fftlen_if;
+ fftwf_complex *f_in = state->f_in;
+ fftwf_complex *f_out = state->f_out;
+ float *revspace = state->revspace;
+
+ if (state->mode == 0 || state->mode == 1) {
+ for (i = 0; i < input_channels; ++i) {
+ int index = i * state->mode;
+ fftwf_complex *f_ir = state->f_ir[index];
+ float *outspace;
+
+ /* First the input samples are transformed to frequency domain, like
+ * the cached impulse was in the setup function. */
+
+ fftwf_execute(state->p_fw[i]);
+
+ /* Then we cross multiply the products of the frequency domain, the
+ * real and imaginary values, into output real and imaginary pairs. */
+
+ for (k = 0, fftlen = state->fftlen / 2 + 1; k < fftlen; ++k) {
+ float re = f_ir[k][0] * f_in[k][0] - f_ir[k][1] * f_in[k][1];
+ float im = f_ir[k][1] * f_in[k][0] + f_ir[k][0] * f_in[k][1];
+ f_out[k][0] = re;
+ f_out[k][1] = im;
+ }
+
+ /* Then we transform back from frequency to time domain. */
+
+ fftwf_execute(state->p_bw);
+
+ /* Then we add the entire revspace block onto our output, dividing
+ * each value by the total number of samples in the buffer. Remember,
+ * since there is some overlap, this addition step is important. */
+
+ outspace = state->outspace[i];
+ for (j = 0, fftlen = state->fftlen, fftlen_if = 1.0f / (float)fftlen; j < fftlen; ++j)
+ outspace[j] += revspace[j] * fftlen_if;
+ }
+ }
+ else if ( state->mode == 2 || state->mode == 3 ) {
+ /* We do the same thing here, except each input channel is convolved
+ * once per output channel, and summed onto the output. */
+
+ for (i = 0; i < input_channels; ++i) {
+ fftwf_execute(state->p_fw[i]);
+
+ for (j = 0; j < output_channels; ++j) {
+ int index = i * output_channels + j;
+ fftwf_complex *f_ir = state->f_ir[index];
+ float *outspace;
+
+ for (k = 0, fftlen = state->fftlen / 2 + 1; k < fftlen; ++k) {
+ float re = f_ir[k][0] * f_in[k][0] - f_ir[k][1] * f_in[k][1];
+ float im = f_ir[k][1] * f_in[k][0] + f_ir[k][0] * f_in[k][1];
+ f_out[k][0] = re;
+ f_out[k][1] = im;
+ }
+
+ fftwf_execute(state->p_bw);
+
+ outspace = state->outspace[j];
+ for (k = 0, fftlen = state->fftlen, fftlen_if = 1.0f / (float)fftlen; k < fftlen; ++k)
+ outspace[k] += revspace[k] * fftlen_if;
+ }
+ }
+ }
+
+ /* Output samples are now buffered and ready for retrieval. */
+
+ state->buffered_out = state->stepsize;
+ state->buffered_in = 0;
+ }
+ }
+}
+
+/* Call this to retrieve available output samples. */
+
+void convolver_read(void *state_, float * output_samples)
+{
+ if (state_) {
+ convolver_state * state = (convolver_state *) state_;
+
+ /* We only want to pass in here if we have buffered samples. */
+
+ if (state->buffered_out) {
+ int i, output_channels = state->outputs;
+ int sample_index = state->stepsize - state->buffered_out;
+
+ for (i = 0; i < output_channels; ++i) {
+ output_samples[i] = state->outspace[i][sample_index];
+ }
+
+ /* And if the buffer has run empty again, we want to slide back the buffer
+ * contents, eliminating the space used by the block of output samples, and
+ * filling in the space at the end with silence. */
+
+ if (--state->buffered_out == 0) {
+ int fftlen = state->fftlen;
+ int stepsize = state->stepsize;
+ int remainder = fftlen - stepsize;
+ for (i = 0; i < output_channels; ++i) {
+ float *outspace = state->outspace[i];
+ memmove(outspace, outspace + stepsize, remainder * sizeof(float));
+ memset(outspace + remainder, 0, stepsize * sizeof(float));
+ }
+ }
+ }
+ else {
+ memset( output_samples, 0, state->outputs * sizeof(float) );
+ }
+ }
+}
diff -urN pulseaudio.orig/src/modules/simple-convolver.h pulseaudio/src/modules/simple-convolver.h
--- pulseaudio.orig/src/modules/simple-convolver.h 1969-12-31 16:00:00.000000000 -0800
+++ pulseaudio/src/modules/simple-convolver.h 2020-01-18 18:03:55.201003287 -0800
@@ -0,0 +1,85 @@
+/***
+ This file is part of PulseAudio.
+
+ This convolver is based off general documentation I found from searching
+ around the Internet. It was originally written for KissFFT, then later
+ FFTW3 and then Apple vDSP support were added. It was originally published
+ under the BSD three clause license. It has been trimmed down to just use
+ FFTW3, as that is already an optional dependency.
+ All new work is published under PulseAudio's original license.
+
+ Copyright 2020 Christopher Snowhill <kode54@gmail.com>
+
+ PulseAudio 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.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with PulseAudio; if not, see <http://www.gnu.org/licenses/>.
+***/
+
+#ifndef _SIMPLE_CONVOLVER_H_
+#define _SIMPLE_CONVOLVER_H_
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/* Fully opaque convolver state created and returned here, otherwise NULL on
+ * failure. Users are welcome to change this to pass in a const pointer to an
+ * impulse and its size, which will be copied and no longer needed upon return.
+ * It is assumed that there will be one impulse per input channel, and that
+ * each impulse will have one channel per output.
+ *
+ * Mode may be one of the following:
+ * - 0: single 1 channel impulse, same number of input and output channels
+ * - 1: single multi channel impulse, individual channels mapped 1:1 from
+ * inputs to outputs, same number of input and output channels
+ * - 2: multiple multi-channel impulses, one impulse per input channel,
+ * containing one channel per output channel, and the results are
+ * summed together.
+ * - 3: multiple sets of single channel impulses, one impulse per output
+ * channel per set, one set per input channel, and the results for
+ * each set are summed together. */
+void * convolver_create(const float * const* impulses, int impulse_size, int input_channels, int output_channels, int mode);
+
+/* This function is for re-importing a modified impulse set into an existing
+ * instance, with the same number of channels per input and output, so the
+ * same number of impulses and channels per impulse. Useful if you are
+ * creating filter impulses, and wish to restage with a newly generated filter
+ * set. */
+void convolver_restage(void *, const float * const* impulses);
+
+/* Pass an instance of the convolver here to clean up when you're done with it */
+void convolver_delete(void *);
+
+/* This will clear the intermediate buffers of the convolver, useful for
+ * restarting a stream with the same filter parameters. */
+void convolver_clear(void *);
+
+/* This returns the number of samples necessary for the convolver to generate
+ * a block of output. When there is output buffered, it is a bad idea to fill
+ * more input samples in. */
+int convolver_get_free_count(void *);
+
+/* Write the samples here, one n-channel set at a time. */
+void convolver_write(void *, const float *);
+
+/* This will return how many samples are buffered for output. */
+int convolver_ready(void *);
+
+/* This will return one n-channel set of samples at a time, or else silence
+ * if the buffer is empty. */
+void convolver_read(void *, float *);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment