Last active
January 19, 2020 03:08
-
-
Save kode54/68fed8663b13b5e9d1ef284de42e6f69 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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