[pulseaudio-commits] 9 commits - LICENSE man/pulse-daemon.conf.5.xml.in src/Makefile.am src/daemon src/modules src/pulsecore src/tests
David Henningsson
diwic at kemper.freedesktop.org
Mon Mar 30 01:54:10 PDT 2015
LICENSE | 3
man/pulse-daemon.conf.5.xml.in | 7 -
src/Makefile.am | 11 +
src/daemon/daemon-conf.c | 5
src/daemon/daemon-conf.h | 1
src/daemon/daemon.conf.in | 3
src/daemon/main.c | 1
src/modules/module-virtual-surround-sink.c | 2
src/pulsecore/core.c | 1
src/pulsecore/core.h | 1
src/pulsecore/filter/LICENSE.WEBKIT | 27 +++
src/pulsecore/filter/biquad.c | 117 +++++++++++++++++
src/pulsecore/filter/biquad.h | 45 ++++++
src/pulsecore/filter/crossover.c | 97 ++++++++++++++
src/pulsecore/filter/crossover.h | 29 ++++
src/pulsecore/filter/lfe-filter.c | 198 +++++++++++++++++++++++++++++
src/pulsecore/filter/lfe-filter.h | 39 +++++
src/pulsecore/memblock.h | 5
src/pulsecore/resampler.c | 53 +++++++
src/pulsecore/resampler.h | 7 +
src/pulsecore/sink-input.c | 6
src/pulsecore/source-output.c | 4
src/tests/lfe-filter-test.c | 194 ++++++++++++++++++++++++++++
src/tests/remix-test.c | 3
src/tests/resampler-test.c | 7 -
25 files changed, 848 insertions(+), 18 deletions(-)
New commits:
commit a9059be749b6043d6cbc5b79652e8a4adda8994e
Author: Hui Wang <hui.wang at canonical.com>
Date: Tue Mar 24 10:29:15 2015 +0100
daemon-conf: enable the lfe remixing by default
Since we have a workable lfe filter, it is time to enable the lfe
remixing by default.
Signed-off-by: Hui Wang <hui.wang at canonical.com>
diff --git a/man/pulse-daemon.conf.5.xml.in b/man/pulse-daemon.conf.5.xml.in
index 048f8f4..bc91a5a 100644
--- a/man/pulse-daemon.conf.5.xml.in
+++ b/man/pulse-daemon.conf.5.xml.in
@@ -125,7 +125,7 @@ License along with PulseAudio; if not, see <http://www.gnu.org/licenses/>.
channel is available as well. If no input LFE channel is
available the output LFE channel will always be 0. If no output
LFE channel is available the signal on the input LFE channel
- will be ignored. Defaults to <opt>no</opt>.</p>
+ will be ignored. Defaults to <opt>yes</opt>.</p>
</option>
<option>
diff --git a/src/daemon/daemon-conf.c b/src/daemon/daemon-conf.c
index 82df0ea..21a8edb 100644
--- a/src/daemon/daemon-conf.c
+++ b/src/daemon/daemon-conf.c
@@ -82,7 +82,7 @@ static const pa_daemon_conf default_conf = {
.log_time = false,
.resample_method = PA_RESAMPLER_AUTO,
.disable_remixing = false,
- .disable_lfe_remixing = true,
+ .disable_lfe_remixing = false,
.lfe_crossover_freq = 120,
.config_file = NULL,
.use_pid_file = true,
diff --git a/src/daemon/daemon.conf.in b/src/daemon/daemon.conf.in
index 61df20a..b48afa2 100644
--- a/src/daemon/daemon.conf.in
+++ b/src/daemon/daemon.conf.in
@@ -54,7 +54,7 @@ ifelse(@HAVE_DBUS@, 1, [dnl
; resample-method = speex-float-1
; enable-remixing = yes
-; enable-lfe-remixing = no
+; enable-lfe-remixing = yes
; lfe-crossover-freq = 120
; flat-volumes = yes
commit 98e01c8a9c0e62c9b0a733fc84409d1299392d5b
Author: Hui Wang <hui.wang at canonical.com>
Date: Tue Mar 24 10:29:19 2015 +0100
tests: adding lfe-filter-test
so far, this test only includes rewind test, it works as below:
let lfe-filter process 2 blocks mono lfe channel audio samples, the
sample format is PA_SAMPLE_S16LE, save the processed data to the temp
buffer, then rewind the lfe-filter back 1 block and 1.5 blocks
respectively, reprocess the audio samples from the rewind position,
then comparing the output data with previously saved data.
Signed-off-by: Hui Wang <hui.wang at canonical.com>
diff --git a/src/Makefile.am b/src/Makefile.am
index 302c532..d582e57 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -262,7 +262,8 @@ TESTS_default = \
cpu-sconv-test \
cpu-volume-test \
lock-autospawn-test \
- mult-s16-test
+ mult-s16-test \
+ lfe-filter-test
TESTS_norun = \
ipacl-test \
@@ -554,6 +555,11 @@ mult_s16_test_LDADD = $(AM_LDADD) libpulsecore- at PA_MAJORMINOR@.la libpulse.la li
mult_s16_test_CFLAGS = $(AM_CFLAGS) $(LIBCHECK_CFLAGS)
mult_s16_test_LDFLAGS = $(AM_LDFLAGS) $(BINLDFLAGS) $(LIBCHECK_LIBS)
+lfe_filter_test_SOURCES = tests/lfe-filter-test.c
+lfe_filter_test_LDADD = $(AM_LDADD) libpulsecore- at PA_MAJORMINOR@.la libpulse.la libpulsecommon- at PA_MAJORMINOR@.la
+lfe_filter_test_CFLAGS = $(AM_CFLAGS) $(LIBCHECK_CFLAGS)
+lfe_filter_test_LDFLAGS = $(AM_LDFLAGS) $(BINLDFLAGS) $(LIBCHECK_LIBS)
+
rtstutter_SOURCES = tests/rtstutter.c
rtstutter_LDADD = $(AM_LDADD) libpulsecore- at PA_MAJORMINOR@.la libpulse.la libpulsecommon- at PA_MAJORMINOR@.la
rtstutter_CFLAGS = $(AM_CFLAGS)
diff --git a/src/tests/lfe-filter-test.c b/src/tests/lfe-filter-test.c
new file mode 100644
index 0000000..2c6d597
--- /dev/null
+++ b/src/tests/lfe-filter-test.c
@@ -0,0 +1,194 @@
+/***
+ This file is part of PulseAudio.
+
+ 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 <check.h>
+
+#include <pulse/pulseaudio.h>
+#include <pulse/sample.h>
+#include <pulsecore/memblock.h>
+
+#include <pulsecore/filter/lfe-filter.h>
+
+struct lfe_filter_test {
+ pa_lfe_filter_t *lf;
+ pa_mempool *pool;
+ pa_sample_spec *ss;
+};
+
+static uint8_t *ori_sample_ptr;
+
+#define ONE_BLOCK_SAMPLES 4096
+#define TOTAL_SAMPLES 8192
+
+static void save_data_block(struct lfe_filter_test *lft, void *d, pa_memblock *blk) {
+ uint8_t *dst = d, *src;
+ size_t blk_size = pa_frame_size(lft->ss) * ONE_BLOCK_SAMPLES;
+
+ src = pa_memblock_acquire(blk);
+ memcpy(dst, src, blk_size);
+ pa_memblock_release(blk);
+}
+
+static pa_memblock* generate_data_block(struct lfe_filter_test *lft, int start) {
+ pa_memblock *r;
+ uint8_t *d, *s = ori_sample_ptr;
+ size_t blk_size = pa_frame_size(lft->ss) * ONE_BLOCK_SAMPLES;
+
+ pa_assert_se(r = pa_memblock_new(lft->pool, blk_size));
+ d = pa_memblock_acquire(r);
+ memcpy(d, s + start, blk_size);
+ pa_memblock_release(r);
+
+ return r;
+}
+
+static int compare_data_block(struct lfe_filter_test *lft, void *a, void *b) {
+ int ret = 0;
+ uint32_t i;
+ uint32_t fz = pa_frame_size(lft->ss);
+ uint8_t *r = a, *u = b;
+
+ for (i = 0; i < ONE_BLOCK_SAMPLES * fz; i++) {
+ if (*r++ != *u++) {
+ pa_log_error("lfe-filter-test: test failed, the output data in the position 0x%x of a block does not equal!\n", i);
+ ret = -1;
+ break;
+ }
+ }
+ return ret;
+}
+
+/* in this test case, we pass two blocks of sample data to lfe-filter, each
+ block contains 4096 samples, and don't let rewind_samples exceed TOTAL_SAMPLES */
+static int lfe_filter_rewind_test(struct lfe_filter_test *lft, int rewind_samples)
+{
+ int ret = -1, pos, i;
+ pa_memchunk mc;
+ uint8_t *outptr;
+ uint32_t fz = pa_frame_size(lft->ss);
+
+ if (rewind_samples > TOTAL_SAMPLES || rewind_samples < TOTAL_SAMPLES - ONE_BLOCK_SAMPLES) {
+ pa_log_error("lfe-filter-test: Please keep %d samples < rewind_samples < %d samples\n", TOTAL_SAMPLES - ONE_BLOCK_SAMPLES, TOTAL_SAMPLES);
+ return ret;
+ }
+
+ outptr = pa_xmalloc(fz * TOTAL_SAMPLES);
+
+ /* let lfe-filter process all samples first, and save the processed data to the temp buffer,
+ then rewind back to some position, reprocess some samples and compare the output data with
+ the processed data saved before. */
+ for (i = 0; i < TOTAL_SAMPLES / ONE_BLOCK_SAMPLES; i++) {
+ mc.memblock = generate_data_block(lft, i * ONE_BLOCK_SAMPLES * fz);
+ mc.length = pa_memblock_get_length(mc.memblock);
+ mc.index = 0;
+ pa_lfe_filter_process(lft->lf, &mc);
+ save_data_block(lft, outptr + i * ONE_BLOCK_SAMPLES * fz, mc.memblock);
+ pa_memblock_unref(mc.memblock);
+ }
+
+ pa_lfe_filter_rewind(lft->lf, rewind_samples * fz);
+ pos = (TOTAL_SAMPLES - rewind_samples) * fz;
+ mc.memblock = generate_data_block(lft, pos);
+ mc.length = pa_memblock_get_length(mc.memblock);
+ mc.index = 0;
+ pa_lfe_filter_process(lft->lf, &mc);
+ ret = compare_data_block(lft, outptr + pos, pa_memblock_acquire(mc.memblock));
+ pa_memblock_release(mc.memblock);
+ pa_memblock_unref(mc.memblock);
+
+ pa_xfree(outptr);
+
+ return ret;
+}
+
+START_TEST (lfe_filter_test) {
+ pa_sample_spec a;
+ int ret = -1;
+ unsigned i, crossover_freq = 120;
+ pa_channel_map chmapmono = {1, {PA_CHANNEL_POSITION_LFE}};
+ struct lfe_filter_test lft;
+ short *tmp_ptr;
+
+ pa_log_set_level(PA_LOG_DEBUG);
+
+ a.channels = 1;
+ a.rate = 44100;
+ a.format = PA_SAMPLE_S16LE;
+
+ lft.ss = &a;
+ pa_assert_se(lft.pool = pa_mempool_new(false, 0));
+
+ /* We prepare pseudo-random input audio samples for lfe-filter rewind testing*/
+ ori_sample_ptr = pa_xmalloc(pa_frame_size(lft.ss) * TOTAL_SAMPLES);
+ tmp_ptr = (short *) ori_sample_ptr;
+ for (i = 0; i < pa_frame_size(lft.ss) * TOTAL_SAMPLES / sizeof(short); i++)
+ *tmp_ptr++ = random();
+
+ /* we create a lfe-filter with cutoff frequency 120Hz and max rewind time 10 seconds */
+ pa_assert_se(lft.lf = pa_lfe_filter_new(&a, &chmapmono, crossover_freq, a.rate * 10));
+ /* rewind to a block boundary */
+ ret = lfe_filter_rewind_test(&lft, ONE_BLOCK_SAMPLES);
+ if (ret)
+ pa_log_error("lfe-filer-test: rewind to block boundary test failed!!!");
+ pa_lfe_filter_free(lft.lf);
+
+ /* we create a lfe-filter with cutoff frequency 120Hz and max rewind time 10 seconds */
+ pa_assert_se(lft.lf = pa_lfe_filter_new(&a, &chmapmono, crossover_freq, a.rate * 10));
+ /* rewind to the middle position of a block */
+ ret = lfe_filter_rewind_test(&lft, ONE_BLOCK_SAMPLES + ONE_BLOCK_SAMPLES / 2);
+ if (ret)
+ pa_log_error("lfe-filer-test: rewind to middle of block test failed!!!");
+
+ pa_xfree(ori_sample_ptr);
+
+ pa_lfe_filter_free(lft.lf);
+
+ pa_mempool_free(lft.pool);
+
+ if (!ret)
+ pa_log_debug("lfe-filter-test: tests for both rewind to block boundary and rewind to middle position of a block passed!");
+
+ fail_unless(ret == 0);
+}
+END_TEST
+
+int main(int argc, char *argv[]) {
+ int failed = 0;
+ Suite *s;
+ TCase *tc;
+ SRunner *sr;
+
+ if (!getenv("MAKE_CHECK"))
+ pa_log_set_level(PA_LOG_DEBUG);
+
+ s = suite_create("lfe-filter");
+ tc = tcase_create("lfe-filter");
+ tcase_add_test(tc, lfe_filter_test);
+ tcase_set_timeout(tc, 10);
+ suite_add_tcase(s, tc);
+
+ sr = srunner_create(s);
+ srunner_run_all(sr, CK_NORMAL);
+ failed = srunner_ntests_failed(sr);
+ srunner_free(sr);
+
+ return (failed == 0) ? EXIT_SUCCESS : EXIT_FAILURE;
+}
commit 7fb531d9369bb8a8edcdf84633e5e455b0fa7e40
Author: David Henningsson <david.henningsson at canonical.com>
Date: Tue Mar 24 10:29:18 2015 +0100
resampler: Make some basic functions for rewinding
The resampler framework just forwards the request to the lfe filter.
There are no resampler impl that can rewind yet, so just reset the
resampler impl instead of properly rewinding yet.
Signed-off-by: David Henningsson <david.henningsson at canonical.com>
diff --git a/src/pulsecore/resampler.c b/src/pulsecore/resampler.c
index db29698..f5a0e16 100644
--- a/src/pulsecore/resampler.c
+++ b/src/pulsecore/resampler.c
@@ -578,6 +578,20 @@ void pa_resampler_reset(pa_resampler *r) {
*r->have_leftover = false;
}
+void pa_resampler_rewind(pa_resampler *r, size_t out_frames) {
+ pa_assert(r);
+
+ /* For now, we don't have any rewindable resamplers, so we just
+ reset the resampler instead (and hope that nobody hears the difference). */
+ if (r->impl.reset)
+ r->impl.reset(r);
+
+ if (r->lfe_filter)
+ pa_lfe_filter_rewind(r->lfe_filter, out_frames);
+
+ *r->have_leftover = false;
+}
+
pa_resample_method_t pa_resampler_get_method(pa_resampler *r) {
pa_assert(r);
@@ -818,8 +832,8 @@ static void setup_remap(const pa_resampler *r, pa_remap_t *m, bool *lfe_filter_r
} else {
/* OK, we shall do the full monty: upmixing and downmixing. Our
- * algorithm is relatively simple, does not do spacialization, delay
- * elements or apply lowpass filters for LFE. Patches are always
+ * algorithm is relatively simple, does not do spacialization, or delay
+ * elements. LFE filters are done after the remap step. Patches are always
* welcome, though. Oh, and it doesn't do any matrix decoding. (Which
* probably wouldn't make any sense anyway.)
*
diff --git a/src/pulsecore/resampler.h b/src/pulsecore/resampler.h
index 3bc1054..4469022 100644
--- a/src/pulsecore/resampler.h
+++ b/src/pulsecore/resampler.h
@@ -145,6 +145,9 @@ void pa_resampler_set_output_rate(pa_resampler *r, uint32_t rate);
/* Reinitialize state of the resampler, possibly due to seeking or other discontinuities */
void pa_resampler_reset(pa_resampler *r);
+/* Rewind resampler */
+void pa_resampler_rewind(pa_resampler *r, size_t out_frames);
+
/* Return the resampling method of the resampler object */
pa_resample_method_t pa_resampler_get_method(pa_resampler *r);
diff --git a/src/pulsecore/sink-input.c b/src/pulsecore/sink-input.c
index 7d2202e..fe980c3 100644
--- a/src/pulsecore/sink-input.c
+++ b/src/pulsecore/sink-input.c
@@ -1106,9 +1106,9 @@ void pa_sink_input_process_rewind(pa_sink_input *i, size_t nbytes /* in sink sam
if (i->thread_info.rewrite_flush)
pa_memblockq_silence(i->thread_info.render_memblockq);
- /* And reset the resampler */
+ /* And rewind the resampler */
if (i->thread_info.resampler)
- pa_resampler_reset(i->thread_info.resampler);
+ pa_resampler_rewind(i->thread_info.resampler, amount);
}
}
diff --git a/src/pulsecore/source-output.c b/src/pulsecore/source-output.c
index 4cc1053..9000972 100644
--- a/src/pulsecore/source-output.c
+++ b/src/pulsecore/source-output.c
@@ -851,7 +851,7 @@ void pa_source_output_process_rewind(pa_source_output *o, size_t nbytes /* in so
o->process_rewind(o, nbytes);
if (o->thread_info.resampler)
- pa_resampler_reset(o->thread_info.resampler);
+ pa_resampler_rewind(o->thread_info.resampler, nbytes);
} else
pa_memblockq_rewind(o->thread_info.delay_memblockq, nbytes);
commit defc2b702bd7358634e70635a7614172836d632e
Author: David Henningsson <david.henningsson at canonical.com>
Date: Tue Mar 24 10:29:17 2015 +0100
lfe-filter: Add rewind support
Store current filter state at every normal block process.
When a rewind happens, rewind back to the nearest saved state,
then calculate forward to the actual sample position.
Signed-off-by: David Henningsson <david.henningsson at canonical.com>
diff --git a/src/pulsecore/filter/lfe-filter.c b/src/pulsecore/filter/lfe-filter.c
index e2bc329..ab81eb7 100644
--- a/src/pulsecore/filter/lfe-filter.c
+++ b/src/pulsecore/filter/lfe-filter.c
@@ -23,9 +23,20 @@
#include "lfe-filter.h"
#include <pulse/xmalloc.h>
+#include <pulsecore/flist.h>
+#include <pulsecore/llist.h>
#include <pulsecore/filter/biquad.h>
#include <pulsecore/filter/crossover.h>
+struct saved_state {
+ PA_LLIST_FIELDS(struct saved_state);
+ pa_memchunk chunk;
+ int64_t index;
+ struct lr4 lr4[PA_CHANNELS_MAX];
+};
+
+PA_STATIC_FLIST_DECLARE(lfe_state, 0, pa_xfree);
+
/* An LR4 filter, implemented as a chain of two Butterworth filters.
Currently the channel map is fixed so that a highpass filter is applied to all
@@ -35,24 +46,37 @@
*/
struct pa_lfe_filter {
+ int64_t index;
+ PA_LLIST_HEAD(struct saved_state, saved);
float crossover;
pa_channel_map cm;
pa_sample_spec ss;
+ size_t maxrewind;
bool active;
struct lr4 lr4[PA_CHANNELS_MAX];
};
-pa_lfe_filter_t * pa_lfe_filter_new(const pa_sample_spec* ss, const pa_channel_map* cm, float crossover_freq) {
+static void remove_state(pa_lfe_filter_t *f, struct saved_state *s) {
+ PA_LLIST_REMOVE(struct saved_state, f->saved, s);
+ pa_memblock_unref(s->chunk.memblock);
+ pa_xfree(s);
+}
+
+pa_lfe_filter_t * pa_lfe_filter_new(const pa_sample_spec* ss, const pa_channel_map* cm, float crossover_freq, size_t maxrewind) {
pa_lfe_filter_t *f = pa_xnew0(struct pa_lfe_filter, 1);
f->crossover = crossover_freq;
f->cm = *cm;
f->ss = *ss;
+ f->maxrewind = maxrewind;
pa_lfe_filter_update_rate(f, ss->rate);
return f;
}
void pa_lfe_filter_free(pa_lfe_filter_t *f) {
+ while (f->saved)
+ remove_state(f, f->saved);
+
pa_xfree(f);
}
@@ -60,26 +84,61 @@ void pa_lfe_filter_reset(pa_lfe_filter_t *f) {
pa_lfe_filter_update_rate(f, f->ss.rate);
}
-pa_memchunk * pa_lfe_filter_process(pa_lfe_filter_t *f, pa_memchunk *buf) {
+static void process_block(pa_lfe_filter_t *f, pa_memchunk *buf, bool store_result) {
int samples = buf->length / pa_frame_size(&f->ss);
- if (!f->active)
- return buf;
+ void *garbage = store_result ? NULL : pa_xmalloc(buf->length);
+
if (f->ss.format == PA_SAMPLE_FLOAT32NE) {
int i;
float *data = pa_memblock_acquire_chunk(buf);
for (i = 0; i < f->cm.channels; i++)
- lr4_process_float32(&f->lr4[i], samples, f->cm.channels, &data[i], &data[i]);
+ lr4_process_float32(&f->lr4[i], samples, f->cm.channels, &data[i], garbage ? garbage : &data[i]);
pa_memblock_release(buf->memblock);
}
else if (f->ss.format == PA_SAMPLE_S16NE) {
int i;
short *data = pa_memblock_acquire_chunk(buf);
for (i = 0; i < f->cm.channels; i++)
- lr4_process_s16(&f->lr4[i], samples, f->cm.channels, &data[i], &data[i]);
+ lr4_process_s16(&f->lr4[i], samples, f->cm.channels, &data[i], garbage ? garbage : &data[i]);
pa_memblock_release(buf->memblock);
}
else pa_assert_not_reached();
+
+ pa_xfree(garbage);
+ f->index += samples;
+}
+
+pa_memchunk * pa_lfe_filter_process(pa_lfe_filter_t *f, pa_memchunk *buf) {
+ struct saved_state *s, *s2;
+ void *data;
+
+ if (!f->active)
+ return buf;
+
+ /* Remove old states (FIXME: we could do better than searching the entire array here?) */
+ PA_LLIST_FOREACH_SAFE(s, s2, f->saved)
+ if (s->index + (int64_t) (s->chunk.length / pa_frame_size(&f->ss) + f->maxrewind) < f->index)
+ remove_state(f, s);
+
+ /* Insert our existing state into the flist */
+ if ((s = pa_flist_pop(PA_STATIC_FLIST_GET(lfe_state))) == NULL)
+ s = pa_xnew(struct saved_state, 1);
+ PA_LLIST_INIT(struct saved_state, s);
+
+ /* TODO: This actually memcpys the entire chunk into a new allocation, because we need to retain the original
+ in case of rewinding. Investigate whether this can be avoided. */
+ data = pa_memblock_acquire_chunk(buf);
+ s->chunk.memblock = pa_memblock_new_malloced(pa_memblock_get_pool(buf->memblock), pa_xmemdup(data, buf->length), buf->length);
+ s->chunk.length = buf->length;
+ s->chunk.index = 0;
+ pa_memblock_release(buf->memblock);
+
+ s->index = f->index;
+ memcpy(s->lr4, f->lr4, sizeof(struct lr4) * f->cm.channels);
+ PA_LLIST_PREPEND(struct saved_state, f->saved, s);
+
+ process_block(f, buf, true);
return buf;
}
@@ -87,6 +146,10 @@ void pa_lfe_filter_update_rate(pa_lfe_filter_t *f, uint32_t new_rate) {
int i;
float biquad_freq = f->crossover / (new_rate / 2);
+ while (f->saved)
+ remove_state(f, f->saved);
+
+ f->index = 0;
f->ss.rate = new_rate;
if (biquad_freq <= 0 || biquad_freq >= 1) {
pa_log_warn("Crossover frequency (%f) outside range for sample rate %d", f->crossover, new_rate);
@@ -99,3 +162,37 @@ void pa_lfe_filter_update_rate(pa_lfe_filter_t *f, uint32_t new_rate) {
f->active = true;
}
+
+void pa_lfe_filter_rewind(pa_lfe_filter_t *f, size_t amount) {
+ struct saved_state *i, *s = NULL;
+ size_t samples = amount / pa_frame_size(&f->ss);
+ f->index -= samples;
+
+ /* Find the closest saved position */
+ PA_LLIST_FOREACH(i, f->saved) {
+ if (i->index > f->index)
+ continue;
+ if (s == NULL || i->index > s->index)
+ s = i;
+ }
+ if (s == NULL) {
+ pa_log_debug("Rewinding LFE filter %lu samples to position %lli. No saved state found", samples, (long long) f->index);
+ pa_lfe_filter_update_rate(f, f->ss.rate);
+ return;
+ }
+ pa_log_debug("Rewinding LFE filter %lu samples to position %lli. Found saved state at position %lli",
+ samples, (long long) f->index, (long long) s->index);
+ memcpy(f->lr4, s->lr4, sizeof(struct lr4) * f->cm.channels);
+
+ /* now fast forward to the actual position */
+ if (f->index > s->index) {
+ pa_memchunk x = s->chunk;
+ x.length = (f->index - s->index) * pa_frame_size(&f->ss);
+ if (x.length > s->chunk.length) {
+ pa_log_error("Hole in stream, cannot fast forward LFE filter");
+ return;
+ }
+ f->index = s->index;
+ process_block(f, &x, false);
+ }
+}
diff --git a/src/pulsecore/filter/lfe-filter.h b/src/pulsecore/filter/lfe-filter.h
index 25db8a0..54d695b 100644
--- a/src/pulsecore/filter/lfe-filter.h
+++ b/src/pulsecore/filter/lfe-filter.h
@@ -25,13 +25,14 @@
#include <pulse/sample.h>
#include <pulse/channelmap.h>
#include <pulsecore/memchunk.h>
-
+#include <pulsecore/memblockq.h>
typedef struct pa_lfe_filter pa_lfe_filter_t;
-pa_lfe_filter_t * pa_lfe_filter_new(const pa_sample_spec* ss, const pa_channel_map* cm, float crossover_freq);
+pa_lfe_filter_t * pa_lfe_filter_new(const pa_sample_spec* ss, const pa_channel_map* cm, float crossover_freq, size_t maxrewind);
void pa_lfe_filter_free(pa_lfe_filter_t *);
void pa_lfe_filter_reset(pa_lfe_filter_t *);
+void pa_lfe_filter_rewind(pa_lfe_filter_t *, size_t amount);
pa_memchunk * pa_lfe_filter_process(pa_lfe_filter_t *filter, pa_memchunk *buf);
void pa_lfe_filter_update_rate(pa_lfe_filter_t *, uint32_t new_rate);
diff --git a/src/pulsecore/resampler.c b/src/pulsecore/resampler.c
index 13b272f..db29698 100644
--- a/src/pulsecore/resampler.c
+++ b/src/pulsecore/resampler.c
@@ -419,7 +419,8 @@ pa_resampler* pa_resampler_new(
if (lfe_filter_required) {
pa_sample_spec wss = r->o_ss;
wss.format = r->work_format;
- r->lfe_filter = pa_lfe_filter_new(&wss, &r->o_cm, (float)crossover_freq);
+ /* FIXME: For now just hardcode maxrewind to 3 seconds */
+ r->lfe_filter = pa_lfe_filter_new(&wss, &r->o_cm, (float)crossover_freq, b->rate * 3);
pa_log_debug(" lfe filter activated (LR4 type), the crossover_freq = %uHz", crossover_freq);
}
commit d0e8b0fe077b2d59e111c57ed5ed75b7a7d3e92d
Author: David Henningsson <david.henningsson at canonical.com>
Date: Wed Mar 25 10:13:13 2015 +0100
memblock: Change pa_memblock_new_malloced to an inline function
To avoid the macro trap: I call pa_memblock_new_malloced with
"pa_xmemdup" as data parameter, and that would expand to *two*
calls to pa_xmemdup in case that remains a macro, which is clearly
not intended.
Signed-off-by: David Henningsson <david.henningsson at canonical.com>
diff --git a/src/pulsecore/memblock.h b/src/pulsecore/memblock.h
index dbea213..4faef75 100644
--- a/src/pulsecore/memblock.h
+++ b/src/pulsecore/memblock.h
@@ -27,6 +27,7 @@ typedef struct pa_memblock pa_memblock;
#include <inttypes.h>
#include <pulse/def.h>
+#include <pulse/xmalloc.h>
#include <pulsecore/atomic.h>
#include <pulsecore/memchunk.h>
@@ -86,7 +87,9 @@ pa_memblock *pa_memblock_new_pool(pa_mempool *, size_t length);
pa_memblock *pa_memblock_new_user(pa_mempool *, void *data, size_t length, pa_free_cb_t free_cb, void *free_cb_data, bool read_only);
/* A special case of pa_memblock_new_user: take a memory buffer previously allocated with pa_xmalloc() */
-#define pa_memblock_new_malloced(p,data,length) pa_memblock_new_user(p, data, length, pa_xfree, data, 0)
+static inline pa_memblock *pa_memblock_new_malloced(pa_mempool *p, void *data, size_t length) {
+ return pa_memblock_new_user(p, data, length, pa_xfree, data, 0);
+}
/* Allocate a new memory block of type PA_MEMBLOCK_FIXED */
pa_memblock *pa_memblock_new_fixed(pa_mempool *, void *data, size_t length, bool read_only);
commit c36e191ce5d38173424c3db9ba06638fd6b8408e
Author: Hui Wang <hui.wang at canonical.com>
Date: Tue Mar 24 10:29:16 2015 +0100
lfe-filter: change the crossover frequency as a parameter
Add a user defined parameter lfe-crossover-freq for the lfe-filter,
to pass this parameter to the lfe-filter, we need to change the
pa_resampler_new() API as well.
Signed-off-by: Hui Wang <hui.wang at canonical.com>
diff --git a/man/pulse-daemon.conf.5.xml.in b/man/pulse-daemon.conf.5.xml.in
index 07be109..048f8f4 100644
--- a/man/pulse-daemon.conf.5.xml.in
+++ b/man/pulse-daemon.conf.5.xml.in
@@ -129,6 +129,11 @@ License along with PulseAudio; if not, see <http://www.gnu.org/licenses/>.
</option>
<option>
+ <p><opt>lfe-crossover-freq=</opt> The crossover frequency (in Hz) for the
+ LFE filter. Defaults to 120 Hz.</p>
+ </option>
+
+ <option>
<p><opt>use-pid-file=</opt> Create a PID file in the runtime directory
(<file>$XDG_RUNTIME_DIR/pulse/pid</file>). If this is enabled you may
use commands like <opt>--kill</opt> or <opt>--check</opt>. If
diff --git a/src/daemon/daemon-conf.c b/src/daemon/daemon-conf.c
index 6e9d377..82df0ea 100644
--- a/src/daemon/daemon-conf.c
+++ b/src/daemon/daemon-conf.c
@@ -83,6 +83,7 @@ static const pa_daemon_conf default_conf = {
.resample_method = PA_RESAMPLER_AUTO,
.disable_remixing = false,
.disable_lfe_remixing = true,
+ .lfe_crossover_freq = 120,
.config_file = NULL,
.use_pid_file = true,
.system_instance = false,
@@ -553,6 +554,7 @@ int pa_daemon_conf_load(pa_daemon_conf *c, const char *filename) {
{ "enable-remixing", pa_config_parse_not_bool, &c->disable_remixing, NULL },
{ "disable-lfe-remixing", pa_config_parse_bool, &c->disable_lfe_remixing, NULL },
{ "enable-lfe-remixing", pa_config_parse_not_bool, &c->disable_lfe_remixing, NULL },
+ { "lfe-crossover-freq", pa_config_parse_unsigned, &c->lfe_crossover_freq, NULL },
{ "load-default-script-file", pa_config_parse_bool, &c->load_default_script_file, NULL },
{ "shm-size-bytes", pa_config_parse_size, &c->shm_size, NULL },
{ "log-meta", pa_config_parse_bool, &c->log_meta, NULL },
@@ -745,6 +747,7 @@ char *pa_daemon_conf_dump(pa_daemon_conf *c) {
pa_strbuf_printf(s, "resample-method = %s\n", pa_resample_method_to_string(c->resample_method));
pa_strbuf_printf(s, "enable-remixing = %s\n", pa_yes_no(!c->disable_remixing));
pa_strbuf_printf(s, "enable-lfe-remixing = %s\n", pa_yes_no(!c->disable_lfe_remixing));
+ pa_strbuf_printf(s, "lfe-crossover-freq = %u\n", c->lfe_crossover_freq);
pa_strbuf_printf(s, "default-sample-format = %s\n", pa_sample_format_to_string(c->default_sample_spec.format));
pa_strbuf_printf(s, "default-sample-rate = %u\n", c->default_sample_spec.rate);
pa_strbuf_printf(s, "alternate-sample-rate = %u\n", c->alternate_sample_rate);
diff --git a/src/daemon/daemon-conf.h b/src/daemon/daemon-conf.h
index 62587b2..458784c 100644
--- a/src/daemon/daemon-conf.h
+++ b/src/daemon/daemon-conf.h
@@ -127,6 +127,7 @@ typedef struct pa_daemon_conf {
unsigned default_n_fragments, default_fragment_size_msec;
unsigned deferred_volume_safety_margin_usec;
int deferred_volume_extra_delay_usec;
+ unsigned lfe_crossover_freq;
pa_sample_spec default_sample_spec;
uint32_t alternate_sample_rate;
pa_channel_map default_channel_map;
diff --git a/src/daemon/daemon.conf.in b/src/daemon/daemon.conf.in
index 067738d..61df20a 100644
--- a/src/daemon/daemon.conf.in
+++ b/src/daemon/daemon.conf.in
@@ -55,6 +55,7 @@ ifelse(@HAVE_DBUS@, 1, [dnl
; resample-method = speex-float-1
; enable-remixing = yes
; enable-lfe-remixing = no
+; lfe-crossover-freq = 120
; flat-volumes = yes
diff --git a/src/daemon/main.c b/src/daemon/main.c
index 68ed383..deb1e43 100644
--- a/src/daemon/main.c
+++ b/src/daemon/main.c
@@ -1029,6 +1029,7 @@ int main(int argc, char *argv[]) {
c->default_fragment_size_msec = conf->default_fragment_size_msec;
c->deferred_volume_safety_margin_usec = conf->deferred_volume_safety_margin_usec;
c->deferred_volume_extra_delay_usec = conf->deferred_volume_extra_delay_usec;
+ c->lfe_crossover_freq = conf->lfe_crossover_freq;
c->exit_idle_time = conf->exit_idle_time;
c->scache_idle_time = conf->scache_idle_time;
c->resample_method = conf->resample_method;
diff --git a/src/modules/module-virtual-surround-sink.c b/src/modules/module-virtual-surround-sink.c
index 8f05f53..6c7120a 100644
--- a/src/modules/module-virtual-surround-sink.c
+++ b/src/modules/module-virtual-surround-sink.c
@@ -743,7 +743,7 @@ int pa__init(pa_module*m) {
pa_memblock_unref(silence.memblock);
/* resample hrir */
- resampler = pa_resampler_new(u->sink->core->mempool, &hrir_temp_ss, &hrir_map, &hrir_ss, &hrir_map,
+ resampler = pa_resampler_new(u->sink->core->mempool, &hrir_temp_ss, &hrir_map, &hrir_ss, &hrir_map, u->sink->core->lfe_crossover_freq,
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;
diff --git a/src/pulsecore/core.c b/src/pulsecore/core.c
index 0e67005..0e63bac 100644
--- a/src/pulsecore/core.c
+++ b/src/pulsecore/core.c
@@ -144,6 +144,7 @@ pa_core* pa_core_new(pa_mainloop_api *m, bool shared, size_t shm_size) {
c->realtime_priority = 5;
c->disable_remixing = false;
c->disable_lfe_remixing = false;
+ c->lfe_crossover_freq = 120;
c->deferred_volume = true;
c->resample_method = PA_RESAMPLER_SPEEX_FLOAT_BASE + 1;
diff --git a/src/pulsecore/core.h b/src/pulsecore/core.h
index 7d896bb..8570d47 100644
--- a/src/pulsecore/core.h
+++ b/src/pulsecore/core.h
@@ -160,6 +160,7 @@ struct pa_core {
unsigned default_n_fragments, default_fragment_size_msec;
unsigned deferred_volume_safety_margin_usec;
int deferred_volume_extra_delay_usec;
+ unsigned lfe_crossover_freq;
pa_defer_event *module_defer_unload_event;
pa_hashmap *modules_pending_unload; /* pa_module -> pa_module (hashmap-as-a-set) */
diff --git a/src/pulsecore/resampler.c b/src/pulsecore/resampler.c
index 157671d..13b272f 100644
--- a/src/pulsecore/resampler.c
+++ b/src/pulsecore/resampler.c
@@ -320,6 +320,7 @@ pa_resampler* pa_resampler_new(
const pa_channel_map *am,
const pa_sample_spec *b,
const pa_channel_map *bm,
+ unsigned crossover_freq,
pa_resample_method_t method,
pa_resample_flags_t flags) {
@@ -418,9 +419,8 @@ pa_resampler* pa_resampler_new(
if (lfe_filter_required) {
pa_sample_spec wss = r->o_ss;
wss.format = r->work_format;
- /* TODO: Temporary code that sets crossover freq to 120 Hz. This should be a parameter */
- r->lfe_filter = pa_lfe_filter_new(&wss, &r->o_cm, 120.0f);
- pa_log_debug(" lfe filter activated (LR4 type)");
+ r->lfe_filter = pa_lfe_filter_new(&wss, &r->o_cm, (float)crossover_freq);
+ pa_log_debug(" lfe filter activated (LR4 type), the crossover_freq = %uHz", crossover_freq);
}
/* initialize implementation */
diff --git a/src/pulsecore/resampler.h b/src/pulsecore/resampler.h
index 45ad75a..3bc1054 100644
--- a/src/pulsecore/resampler.h
+++ b/src/pulsecore/resampler.h
@@ -118,6 +118,7 @@ pa_resampler* pa_resampler_new(
const pa_channel_map *am,
const pa_sample_spec *b,
const pa_channel_map *bm,
+ unsigned crossover_freq,
pa_resample_method_t resample_method,
pa_resample_flags_t flags);
diff --git a/src/pulsecore/sink-input.c b/src/pulsecore/sink-input.c
index d95a93e..7d2202e 100644
--- a/src/pulsecore/sink-input.c
+++ b/src/pulsecore/sink-input.c
@@ -451,6 +451,7 @@ int pa_sink_input_new(
core->mempool,
&data->sample_spec, &data->channel_map,
&data->sink->sample_spec, &data->sink->channel_map,
+ core->lfe_crossover_freq,
data->resample_method,
((data->flags & PA_SINK_INPUT_VARIABLE_RATE) ? PA_RESAMPLER_VARIABLE_RATE : 0) |
((data->flags & PA_SINK_INPUT_NO_REMAP) ? PA_RESAMPLER_NO_REMAP : 0) |
@@ -2168,6 +2169,7 @@ int pa_sink_input_update_rate(pa_sink_input *i) {
new_resampler = pa_resampler_new(i->core->mempool,
&i->sample_spec, &i->channel_map,
&i->sink->sample_spec, &i->sink->channel_map,
+ i->core->lfe_crossover_freq,
i->requested_resample_method,
((i->flags & PA_SINK_INPUT_VARIABLE_RATE) ? PA_RESAMPLER_VARIABLE_RATE : 0) |
((i->flags & PA_SINK_INPUT_NO_REMAP) ? PA_RESAMPLER_NO_REMAP : 0) |
diff --git a/src/pulsecore/source-output.c b/src/pulsecore/source-output.c
index c8090f7..4cc1053 100644
--- a/src/pulsecore/source-output.c
+++ b/src/pulsecore/source-output.c
@@ -396,6 +396,7 @@ int pa_source_output_new(
core->mempool,
&data->source->sample_spec, &data->source->channel_map,
&data->sample_spec, &data->channel_map,
+ core->lfe_crossover_freq,
data->resample_method,
((data->flags & PA_SOURCE_OUTPUT_VARIABLE_RATE) ? PA_RESAMPLER_VARIABLE_RATE : 0) |
((data->flags & PA_SOURCE_OUTPUT_NO_REMAP) ? PA_RESAMPLER_NO_REMAP : 0) |
@@ -1625,6 +1626,7 @@ int pa_source_output_update_rate(pa_source_output *o) {
new_resampler = pa_resampler_new(o->core->mempool,
&o->source->sample_spec, &o->source->channel_map,
&o->sample_spec, &o->channel_map,
+ o->core->lfe_crossover_freq,
o->requested_resample_method,
((o->flags & PA_SOURCE_OUTPUT_VARIABLE_RATE) ? PA_RESAMPLER_VARIABLE_RATE : 0) |
((o->flags & PA_SOURCE_OUTPUT_NO_REMAP) ? PA_RESAMPLER_NO_REMAP : 0) |
diff --git a/src/tests/remix-test.c b/src/tests/remix-test.c
index 5193187..6feb8dc 100644
--- a/src/tests/remix-test.c
+++ b/src/tests/remix-test.c
@@ -47,6 +47,7 @@ int main(int argc, char *argv[]) {
unsigned i, j;
pa_mempool *pool;
+ unsigned crossover_freq = 120;
pa_log_set_level(PA_LOG_DEBUG);
@@ -66,7 +67,7 @@ int main(int argc, char *argv[]) {
ss1.rate = ss2.rate = 44100;
ss1.format = ss2.format = PA_SAMPLE_S16NE;
- r = pa_resampler_new(pool, &ss1, &maps[i], &ss2, &maps[j], PA_RESAMPLER_AUTO, 0);
+ r = pa_resampler_new(pool, &ss1, &maps[i], &ss2, &maps[j], crossover_freq, PA_RESAMPLER_AUTO, 0);
/* We don't really care for the resampler. We just want to
* see the remixing debug output. */
diff --git a/src/tests/resampler-test.c b/src/tests/resampler-test.c
index 96110fb..751d2dc 100644
--- a/src/tests/resampler-test.c
+++ b/src/tests/resampler-test.c
@@ -303,6 +303,7 @@ int main(int argc, char *argv[]) {
bool all_formats = true;
pa_resample_method_t method;
int seconds;
+ unsigned crossover_freq = 120;
static const struct option long_options[] = {
{"help", 0, NULL, 'h'},
@@ -419,7 +420,7 @@ int main(int argc, char *argv[]) {
b.rate, b.channels, pa_sample_format_to_string(b.format));
ts = pa_rtclock_now();
- pa_assert_se(resampler = pa_resampler_new(pool, &a, NULL, &b, NULL, method, 0));
+ pa_assert_se(resampler = pa_resampler_new(pool, &a, NULL, &b, NULL, crossover_freq, method, 0));
pa_log_info("init: %llu", (long long unsigned)(pa_rtclock_now() - ts));
i.memblock = pa_memblock_new(pool, pa_usec_to_bytes(1*PA_USEC_PER_SEC, &a));
@@ -450,8 +451,8 @@ int main(int argc, char *argv[]) {
pa_sample_format_to_string(b.format),
pa_sample_format_to_string(a.format));
- pa_assert_se(forth = pa_resampler_new(pool, &a, NULL, &b, NULL, method, 0));
- pa_assert_se(back = pa_resampler_new(pool, &b, NULL, &a, NULL, method, 0));
+ pa_assert_se(forth = pa_resampler_new(pool, &a, NULL, &b, NULL, crossover_freq, method, 0));
+ pa_assert_se(back = pa_resampler_new(pool, &b, NULL, &a, NULL, crossover_freq, method, 0));
i.memblock = generate_block(pool, &a);
i.length = pa_memblock_get_length(i.memblock);
commit 3538e6636edc1ba0b75e7409db618dbb7fe79d3e
Author: David Henningsson <david.henningsson at canonical.com>
Date: Tue Mar 24 10:29:14 2015 +0100
lfe-filter: Cleanup and refactor
- Remove imported dead code
- Fix compiler warnings
- Fix non-GCC compiler compilation (use more portable macros)
- Change lr4 struct to include a biquad struct
Thanks to Alexander Patrakov for suggesting many of these changes.
Signed-off-by: David Henningsson <david.henningsson at canonical.com>
diff --git a/src/pulsecore/filter/biquad.c b/src/pulsecore/filter/biquad.c
index b28256d..7c21a29 100644
--- a/src/pulsecore/filter/biquad.c
+++ b/src/pulsecore/filter/biquad.c
@@ -8,20 +8,15 @@
* found in the LICENSE.WEBKIT file.
*/
-#include <math.h>
-#include "biquad.h"
-#ifndef max
-#define max(a, b) ({ __typeof__(a) _a = (a); \
- __typeof__(b) _b = (b); \
- _a > _b ? _a : _b; })
+#ifdef HAVE_CONFIG_H
+#include <config.h>
#endif
-#ifndef min
-#define min(a, b) ({ __typeof__(a) _a = (a); \
- __typeof__(b) _b = (b); \
- _a < _b ? _a : _b; })
-#endif
+#include <pulsecore/macro.h>
+
+#include <math.h>
+#include "biquad.h"
#ifndef M_PI
#define M_PI 3.14159265358979323846
@@ -38,19 +33,18 @@ static void set_coefficient(struct biquad *bq, double b0, double b1, double b2,
bq->a2 = a2 * a0_inv;
}
-static void biquad_lowpass(struct biquad *bq, double cutoff, double resonance)
+static void biquad_lowpass(struct biquad *bq, double cutoff)
{
/* Limit cutoff to 0 to 1. */
- cutoff = max(0.0, min(cutoff, 1.0));
+ cutoff = PA_MIN(cutoff, 1.0);
+ cutoff = PA_MAX(0.0, cutoff);
- if (cutoff == 1) {
+ if (cutoff >= 1.0) {
/* When cutoff is 1, the z-transform is 1. */
set_coefficient(bq, 1, 0, 0, 1, 0, 0);
} else if (cutoff > 0) {
/* Compute biquad coefficients for lowpass filter */
- resonance = max(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 d = sqrt(2);
double theta = M_PI * cutoff;
double sn = 0.5 * d * sin(theta);
@@ -73,19 +67,18 @@ static void biquad_lowpass(struct biquad *bq, double cutoff, double resonance)
}
}
-static void biquad_highpass(struct biquad *bq, double cutoff, double resonance)
+static void biquad_highpass(struct biquad *bq, double cutoff)
{
/* Limit cutoff to 0 to 1. */
- cutoff = max(0.0, min(cutoff, 1.0));
+ cutoff = PA_MIN(cutoff, 1.0);
+ cutoff = PA_MAX(0.0, cutoff);
- if (cutoff == 1) {
+ if (cutoff >= 1.0) {
/* The z-transform is 0. */
set_coefficient(bq, 0, 0, 0, 1, 0, 0);
} else if (cutoff > 0) {
/* Compute biquad coefficients for highpass filter */
- resonance = max(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 d = sqrt(2);
double theta = M_PI * cutoff;
double sn = 0.5 * d * sin(theta);
@@ -110,259 +103,15 @@ static void biquad_highpass(struct biquad *bq, double cutoff, double resonance)
}
}
-static void biquad_bandpass(struct biquad *bq, double frequency, double Q)
-{
- /* No negative frequencies allowed. */
- frequency = max(0.0, frequency);
-
- /* Don't let Q go negative, which causes an unstable filter. */
- Q = max(0.0, Q);
-
- if (frequency > 0 && frequency < 1) {
- double w0 = M_PI * frequency;
- if (Q > 0) {
- 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);
- } else {
- /* 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);
- }
- } else {
- /* 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);
- }
-}
-
-static void biquad_lowshelf(struct biquad *bq, double frequency, double db_gain)
-{
- /* Clip frequencies to between 0 and 1, inclusive. */
- frequency = max(0.0, min(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);
- } else if (frequency > 0) {
- double w0 = M_PI * frequency;
- double S = 1; /* filter slope (1 is max value) */
- double alpha = 0.5 * sin(w0) *
- sqrt((A + 1 / A) * (1 / S - 1) + 2);
- 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);
- } else {
- /* When frequency is 0, the z-transform is 1. */
- set_coefficient(bq, 1, 0, 0, 1, 0, 0);
- }
-}
-
-static void biquad_highshelf(struct biquad *bq, double frequency,
- double db_gain)
+void biquad_set(struct biquad *bq, enum biquad_type type, double freq)
{
- /* Clip frequencies to between 0 and 1, inclusive. */
- frequency = max(0.0, min(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);
- } else if (frequency > 0) {
- double w0 = M_PI * frequency;
- double S = 1; /* filter slope (1 is max value) */
- double alpha = 0.5 * sin(w0) *
- sqrt((A + 1 / A) * (1 / S - 1) + 2);
- 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);
- } else {
- /* When frequency = 0, the filter is just a gain, A^2. */
- set_coefficient(bq, A * A, 0, 0, 1, 0, 0);
- }
-}
-
-static void biquad_peaking(struct biquad *bq, double frequency, double Q,
- double db_gain)
-{
- /* Clip frequencies to between 0 and 1, inclusive. */
- frequency = max(0.0, min(frequency, 1.0));
-
- /* Don't let Q go negative, which causes an unstable filter. */
- Q = max(0.0, Q);
-
- double A = pow(10.0, db_gain / 40);
-
- if (frequency > 0 && frequency < 1) {
- if (Q > 0) {
- 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);
- } else {
- /* 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);
- }
- } else {
- /* When frequency is 0 or 1, the z-transform is 1. */
- set_coefficient(bq, 1, 0, 0, 1, 0, 0);
- }
-}
-
-static void biquad_notch(struct biquad *bq, double frequency, double Q)
-{
- /* Clip frequencies to between 0 and 1, inclusive. */
- frequency = max(0.0, min(frequency, 1.0));
-
- /* Don't let Q go negative, which causes an unstable filter. */
- Q = max(0.0, Q);
-
- if (frequency > 0 && frequency < 1) {
- if (Q > 0) {
- 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);
- } else {
- /* 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);
- }
- } else {
- /* When frequency is 0 or 1, the z-transform is 1. */
- set_coefficient(bq, 1, 0, 0, 1, 0, 0);
- }
-}
-
-static void biquad_allpass(struct biquad *bq, double frequency, double Q)
-{
- /* Clip frequencies to between 0 and 1, inclusive. */
- frequency = max(0.0, min(frequency, 1.0));
-
- /* Don't let Q go negative, which causes an unstable filter. */
- Q = max(0.0, Q);
-
- if (frequency > 0 && frequency < 1) {
- if (Q > 0) {
- 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);
- } else {
- /* 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);
- }
- } else {
- /* When frequency is 0 or 1, the z-transform is 1. */
- set_coefficient(bq, 1, 0, 0, 1, 0, 0);
- }
-}
-
-void biquad_set(struct biquad *bq, enum biquad_type type, double freq, double Q,
- double gain)
-{
- /* Default is an identity filter. Also clear history values. */
- set_coefficient(bq, 1, 0, 0, 1, 0, 0);
- bq->x1 = 0;
- bq->x2 = 0;
- bq->y1 = 0;
- bq->y2 = 0;
switch (type) {
case BQ_LOWPASS:
- biquad_lowpass(bq, freq, Q);
+ biquad_lowpass(bq, freq);
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, gain);
- break;
- case BQ_HIGHSHELF:
- biquad_highshelf(bq, freq, 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:
+ biquad_highpass(bq, freq);
break;
}
}
diff --git a/src/pulsecore/filter/biquad.h b/src/pulsecore/filter/biquad.h
index c584aa9..bb8f2fb 100644
--- a/src/pulsecore/filter/biquad.h
+++ b/src/pulsecore/filter/biquad.h
@@ -21,21 +21,12 @@ extern "C" {
struct biquad {
float b0, b1, b2;
float a1, a2;
- float x1, x2;
- float y1, y2;
};
/* 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
};
/* Initialize a biquad filter parameters from its type and parameters.
@@ -44,11 +35,8 @@ enum biquad_type {
* 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);
+void biquad_set(struct biquad *bq, enum biquad_type type, double freq);
#ifdef __cplusplus
} /* extern "C" */
diff --git a/src/pulsecore/filter/crossover.c b/src/pulsecore/filter/crossover.c
index 0a571c3..dab34af 100644
--- a/src/pulsecore/filter/crossover.c
+++ b/src/pulsecore/filter/crossover.c
@@ -9,18 +9,11 @@
#include <pulsecore/macro.h>
-#include "biquad.h"
#include "crossover.h"
void lr4_set(struct lr4 *lr4, enum biquad_type type, float freq)
{
- struct biquad q;
- biquad_set(&q, type, freq, 0, 0);
- lr4->b0 = q.b0;
- lr4->b1 = q.b1;
- lr4->b2 = q.b2;
- lr4->a1 = q.a1;
- lr4->a2 = q.a2;
+ biquad_set(&lr4->bq, type, freq);
lr4->x1 = 0;
lr4->x2 = 0;
lr4->y1 = 0;
@@ -37,11 +30,11 @@ void lr4_process_float32(struct lr4 *lr4, int samples, int channels, float *src,
float ly2 = lr4->y2;
float lz1 = lr4->z1;
float lz2 = lr4->z2;
- float lb0 = lr4->b0;
- float lb1 = lr4->b1;
- float lb2 = lr4->b2;
- float la1 = lr4->a1;
- float la2 = lr4->a2;
+ float lb0 = lr4->bq.b0;
+ float lb1 = lr4->bq.b1;
+ float lb2 = lr4->bq.b2;
+ float la1 = lr4->bq.a1;
+ float la2 = lr4->bq.a2;
int i;
for (i = 0; i < samples * channels; i += channels) {
@@ -74,11 +67,11 @@ void lr4_process_s16(struct lr4 *lr4, int samples, int channels, short *src, sho
float ly2 = lr4->y2;
float lz1 = lr4->z1;
float lz2 = lr4->z2;
- float lb0 = lr4->b0;
- float lb1 = lr4->b1;
- float lb2 = lr4->b2;
- float la1 = lr4->a1;
- float la2 = lr4->a2;
+ float lb0 = lr4->bq.b0;
+ float lb1 = lr4->bq.b1;
+ float lb2 = lr4->bq.b2;
+ float la1 = lr4->bq.a1;
+ float la2 = lr4->bq.a2;
int i;
for (i = 0; i < samples * channels; i += channels) {
@@ -102,168 +95,3 @@ void lr4_process_s16(struct lr4 *lr4, int samples, int channels, short *src, sho
lr4->z1 = lz1;
lr4->z2 = lz2;
}
-
-
-/* Split input data using two LR4 filters, put the result into the input array
- * and another array.
- *
- * data0 --+-- lp --> data0
- * |
- * \-- hp --> data1
- */
-static void lr4_split(struct lr4 *lp, struct lr4 *hp, int count, float *data0,
- float *data1)
-{
- float lx1 = lp->x1;
- float lx2 = lp->x2;
- float ly1 = lp->y1;
- float ly2 = lp->y2;
- float lz1 = lp->z1;
- float lz2 = lp->z2;
- float lb0 = lp->b0;
- float lb1 = lp->b1;
- float lb2 = lp->b2;
- float la1 = lp->a1;
- float la2 = lp->a2;
-
- float hx1 = hp->x1;
- float hx2 = hp->x2;
- float hy1 = hp->y1;
- float hy2 = hp->y2;
- float hz1 = hp->z1;
- float hz2 = hp->z2;
- float hb0 = hp->b0;
- float hb1 = hp->b1;
- float hb2 = hp->b2;
- float ha1 = hp->a1;
- float ha2 = hp->a2;
-
- int i;
- for (i = 0; i < count; i++) {
- float x, y, z;
- x = data0[i];
- y = lb0*x + lb1*lx1 + lb2*lx2 - la1*ly1 - la2*ly2;
- z = lb0*y + lb1*ly1 + lb2*ly2 - la1*lz1 - la2*lz2;
- lx2 = lx1;
- lx1 = x;
- ly2 = ly1;
- ly1 = y;
- lz2 = lz1;
- lz1 = z;
- data0[i] = z;
-
- y = hb0*x + hb1*hx1 + hb2*hx2 - ha1*hy1 - ha2*hy2;
- z = hb0*y + hb1*hy1 + hb2*hy2 - ha1*hz1 - ha2*hz2;
- hx2 = hx1;
- hx1 = x;
- hy2 = hy1;
- hy1 = y;
- hz2 = hz1;
- hz1 = z;
- data1[i] = z;
- }
-
- lp->x1 = lx1;
- lp->x2 = lx2;
- lp->y1 = ly1;
- lp->y2 = ly2;
- lp->z1 = lz1;
- lp->z2 = lz2;
-
- hp->x1 = hx1;
- hp->x2 = hx2;
- hp->y1 = hy1;
- hp->y2 = hy2;
- hp->z1 = hz1;
- hp->z2 = hz2;
-}
-
-/* Split input data using two LR4 filters and sum them back to the original
- * data array.
- *
- * data --+-- lp --+--> data
- * | |
- * \-- hp --/
- */
-static void lr4_merge(struct lr4 *lp, struct lr4 *hp, int count, float *data)
-{
- float lx1 = lp->x1;
- float lx2 = lp->x2;
- float ly1 = lp->y1;
- float ly2 = lp->y2;
- float lz1 = lp->z1;
- float lz2 = lp->z2;
- float lb0 = lp->b0;
- float lb1 = lp->b1;
- float lb2 = lp->b2;
- float la1 = lp->a1;
- float la2 = lp->a2;
-
- float hx1 = hp->x1;
- float hx2 = hp->x2;
- float hy1 = hp->y1;
- float hy2 = hp->y2;
- float hz1 = hp->z1;
- float hz2 = hp->z2;
- float hb0 = hp->b0;
- float hb1 = hp->b1;
- float hb2 = hp->b2;
- float ha1 = hp->a1;
- float ha2 = hp->a2;
-
- int i;
- for (i = 0; i < count; i++) {
- float x, y, z;
- x = data[i];
- y = lb0*x + lb1*lx1 + lb2*lx2 - la1*ly1 - la2*ly2;
- z = lb0*y + lb1*ly1 + lb2*ly2 - la1*lz1 - la2*lz2;
- lx2 = lx1;
- lx1 = x;
- ly2 = ly1;
- ly1 = y;
- lz2 = lz1;
- lz1 = z;
-
- y = hb0*x + hb1*hx1 + hb2*hx2 - ha1*hy1 - ha2*hy2;
- z = hb0*y + hb1*hy1 + hb2*hy2 - ha1*hz1 - ha2*hz2;
- hx2 = hx1;
- hx1 = x;
- hy2 = hy1;
- hy1 = y;
- hz2 = hz1;
- hz1 = z;
- data[i] = z + lz1;
- }
-
- lp->x1 = lx1;
- lp->x2 = lx2;
- lp->y1 = ly1;
- lp->y2 = ly2;
- lp->z1 = lz1;
- lp->z2 = lz2;
-
- hp->x1 = hx1;
- hp->x2 = hx2;
- hp->y1 = hy1;
- hp->y2 = hy2;
- hp->z1 = hz1;
- hp->z2 = hz2;
-}
-
-void crossover_init(struct crossover *xo, float freq1, float freq2)
-{
- int i;
- for (i = 0; i < 3; i++) {
- float f = (i == 0) ? freq1 : freq2;
- lr4_set(&xo->lp[i], BQ_LOWPASS, f);
- lr4_set(&xo->hp[i], BQ_HIGHPASS, f);
- }
-}
-
-void crossover_process(struct crossover *xo, int count, float *data0,
- float *data1, float *data2)
-{
- lr4_split(&xo->lp[0], &xo->hp[0], count, data0, data1);
- lr4_merge(&xo->lp[1], &xo->hp[1], count, data0);
- lr4_split(&xo->lp[2], &xo->hp[2], count, data1, data2);
-}
diff --git a/src/pulsecore/filter/crossover.h b/src/pulsecore/filter/crossover.h
index a88f5b6..c5c9765 100644
--- a/src/pulsecore/filter/crossover.h
+++ b/src/pulsecore/filter/crossover.h
@@ -6,10 +6,7 @@
#ifndef CROSSOVER_H_
#define CROSSOVER_H_
-#ifdef __cplusplus
-extern "C" {
-#endif
-
+#include "biquad.h"
/* An LR4 filter is two biquads with the same parameters connected in series:
*
* x -- [BIQUAD] -- y -- [BIQUAD] -- z
@@ -18,8 +15,7 @@ extern "C" {
* The variable [xyz][12] keep the history values.
*/
struct lr4 {
- float b0, b1, b2;
- float a1, a2;
+ struct biquad bq;
float x1, x2;
float y1, y2;
float z1, z2;
@@ -30,47 +26,4 @@ void lr4_set(struct lr4 *lr4, enum biquad_type type, float freq);
void lr4_process_float32(struct lr4 *lr4, int samples, int channels, float *src, float *dest);
void lr4_process_s16(struct lr4 *lr4, int samples, int channels, short *src, short *dest);
-
-/* Three bands crossover filter:
- *
- * INPUT --+-- lp0 --+-- lp1 --+---> LOW (0)
- * | | |
- * | \-- hp1 --/
- * |
- * \-- hp0 --+-- lp2 ------> MID (1)
- * |
- * \-- hp2 ------> HIGH (2)
- *
- * [f0] [f1]
- *
- * Each lp or hp is an LR4 filter, which consists of two second-order
- * lowpass or highpass butterworth filters.
- */
-struct crossover {
- struct lr4 lp[3], hp[3];
-};
-
-/* Initializes a crossover filter
- * Args:
- * xo - The crossover filter we want to initialize.
- * freq1 - The normalized frequency splits low and mid band.
- * freq2 - The normalized frequency splits mid and high band.
- */
-void crossover_init(struct crossover *xo, float freq1, float freq2);
-
-/* Splits input samples to three bands.
- * Args:
- * xo - The crossover filter to use.
- * count - The number of input samples.
- * data0 - The input samples, also the place to store low band output.
- * data1 - The place to store mid band output.
- * data2 - The place to store high band output.
- */
-void crossover_process(struct crossover *xo, int count, float *data0,
- float *data1, float *data2);
-
-#ifdef __cplusplus
-} /* extern "C" */
-#endif
-
#endif /* CROSSOVER_H_ */
commit 979f19a434733afba0480e2ba456cccc98362e05
Author: David Henningsson <david.henningsson at canonical.com>
Date: Tue Mar 24 10:29:13 2015 +0100
lfe-filter: Enable LFE filter in the resampler
When enable-lfe-remixing is set, an LFE channel is present in the
resampler's destination channel map but not in the source channel map,
we insert a low-pass filter instead of just averaging the channels.
Other channels will get a high-pass filter.
In this patch, the crossover frequency is hardcoded to 120Hz (to be fixed
in later patches).
Note that in current state the LFE filter is
- not very optimised
- not rewind friendly (rewinding can cause audible artifacts)
Signed-off-by: David Henningsson <david.henningsson at canonical.com>
diff --git a/src/Makefile.am b/src/Makefile.am
index 67f8627..302c532 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -911,6 +911,9 @@ lib_LTLIBRARIES += libpulsecore- at PA_MAJORMINOR@.la
# Pure core stuff
libpulsecore_ at PA_MAJORMINOR@_la_SOURCES = \
+ pulsecore/filter/lfe-filter.c pulsecore/filter/lfe-filter.h \
+ pulsecore/filter/biquad.c pulsecore/filter/biquad.h \
+ pulsecore/filter/crossover.c pulsecore/filter/crossover.h \
pulsecore/asyncmsgq.c pulsecore/asyncmsgq.h \
pulsecore/asyncq.c pulsecore/asyncq.h \
pulsecore/auth-cookie.c pulsecore/auth-cookie.h \
diff --git a/src/pulsecore/filter/crossover.c b/src/pulsecore/filter/crossover.c
index 11a8c6e..0a571c3 100644
--- a/src/pulsecore/filter/crossover.c
+++ b/src/pulsecore/filter/crossover.c
@@ -3,10 +3,16 @@
* found in the LICENSE file.
*/
-#include "crossover.h"
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <pulsecore/macro.h>
+
#include "biquad.h"
+#include "crossover.h"
-static void lr4_set(struct lr4 *lr4, enum biquad_type type, float freq)
+void lr4_set(struct lr4 *lr4, enum biquad_type type, float freq)
{
struct biquad q;
biquad_set(&q, type, freq, 0, 0);
@@ -23,6 +29,81 @@ static void lr4_set(struct lr4 *lr4, enum biquad_type type, float freq)
lr4->z2 = 0;
}
+void lr4_process_float32(struct lr4 *lr4, int samples, int channels, float *src, float *dest)
+{
+ float lx1 = lr4->x1;
+ float lx2 = lr4->x2;
+ float ly1 = lr4->y1;
+ float ly2 = lr4->y2;
+ float lz1 = lr4->z1;
+ float lz2 = lr4->z2;
+ float lb0 = lr4->b0;
+ float lb1 = lr4->b1;
+ float lb2 = lr4->b2;
+ float la1 = lr4->a1;
+ float la2 = lr4->a2;
+
+ int i;
+ for (i = 0; i < samples * channels; i += channels) {
+ float x, y, z;
+ x = src[i];
+ y = lb0*x + lb1*lx1 + lb2*lx2 - la1*ly1 - la2*ly2;
+ z = lb0*y + lb1*ly1 + lb2*ly2 - la1*lz1 - la2*lz2;
+ lx2 = lx1;
+ lx1 = x;
+ ly2 = ly1;
+ ly1 = y;
+ lz2 = lz1;
+ lz1 = z;
+ dest[i] = z;
+ }
+
+ lr4->x1 = lx1;
+ lr4->x2 = lx2;
+ lr4->y1 = ly1;
+ lr4->y2 = ly2;
+ lr4->z1 = lz1;
+ lr4->z2 = lz2;
+}
+
+void lr4_process_s16(struct lr4 *lr4, int samples, int channels, short *src, short *dest)
+{
+ float lx1 = lr4->x1;
+ float lx2 = lr4->x2;
+ float ly1 = lr4->y1;
+ float ly2 = lr4->y2;
+ float lz1 = lr4->z1;
+ float lz2 = lr4->z2;
+ float lb0 = lr4->b0;
+ float lb1 = lr4->b1;
+ float lb2 = lr4->b2;
+ float la1 = lr4->a1;
+ float la2 = lr4->a2;
+
+ int i;
+ for (i = 0; i < samples * channels; i += channels) {
+ float x, y, z;
+ x = src[i];
+ y = lb0*x + lb1*lx1 + lb2*lx2 - la1*ly1 - la2*ly2;
+ z = lb0*y + lb1*ly1 + lb2*ly2 - la1*lz1 - la2*lz2;
+ lx2 = lx1;
+ lx1 = x;
+ ly2 = ly1;
+ ly1 = y;
+ lz2 = lz1;
+ lz1 = z;
+ dest[i] = PA_CLAMP_UNLIKELY((int) z, -0x8000, 0x7fff);
+ }
+
+ lr4->x1 = lx1;
+ lr4->x2 = lx2;
+ lr4->y1 = ly1;
+ lr4->y2 = ly2;
+ lr4->z1 = lz1;
+ lr4->z2 = lz2;
+}
+
+
/* Split input data using two LR4 filters, put the result into the input array
* and another array.
*
diff --git a/src/pulsecore/filter/crossover.h b/src/pulsecore/filter/crossover.h
index 99a601c..a88f5b6 100644
--- a/src/pulsecore/filter/crossover.h
+++ b/src/pulsecore/filter/crossover.h
@@ -25,6 +25,12 @@ struct lr4 {
float z1, z2;
};
+void lr4_set(struct lr4 *lr4, enum biquad_type type, float freq);
+
+void lr4_process_float32(struct lr4 *lr4, int samples, int channels, float *src, float *dest);
+void lr4_process_s16(struct lr4 *lr4, int samples, int channels, short *src, short *dest);
+
+
/* Three bands crossover filter:
*
* INPUT --+-- lp0 --+-- lp1 --+---> LOW (0)
diff --git a/src/pulsecore/filter/lfe-filter.c b/src/pulsecore/filter/lfe-filter.c
new file mode 100644
index 0000000..e2bc329
--- /dev/null
+++ b/src/pulsecore/filter/lfe-filter.c
@@ -0,0 +1,101 @@
+/***
+ This file is part of PulseAudio.
+
+ Copyright 2014 David Henningsson, Canonical Ltd.
+
+ 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 "lfe-filter.h"
+#include <pulse/xmalloc.h>
+#include <pulsecore/filter/biquad.h>
+#include <pulsecore/filter/crossover.h>
+
+/* An LR4 filter, implemented as a chain of two Butterworth filters.
+
+ Currently the channel map is fixed so that a highpass filter is applied to all
+ channels except for the LFE channel, where a lowpass filter is applied.
+ This works well for e g stereo to 2.1/5.1/7.1 scenarios, where the remap engine
+ has calculated the LFE channel to be the average of all source channels.
+*/
+
+struct pa_lfe_filter {
+ float crossover;
+ pa_channel_map cm;
+ pa_sample_spec ss;
+ bool active;
+ struct lr4 lr4[PA_CHANNELS_MAX];
+};
+
+pa_lfe_filter_t * pa_lfe_filter_new(const pa_sample_spec* ss, const pa_channel_map* cm, float crossover_freq) {
+
+ pa_lfe_filter_t *f = pa_xnew0(struct pa_lfe_filter, 1);
+ f->crossover = crossover_freq;
+ f->cm = *cm;
+ f->ss = *ss;
+ pa_lfe_filter_update_rate(f, ss->rate);
+ return f;
+}
+
+void pa_lfe_filter_free(pa_lfe_filter_t *f) {
+ pa_xfree(f);
+}
+
+void pa_lfe_filter_reset(pa_lfe_filter_t *f) {
+ pa_lfe_filter_update_rate(f, f->ss.rate);
+}
+
+pa_memchunk * pa_lfe_filter_process(pa_lfe_filter_t *f, pa_memchunk *buf) {
+ int samples = buf->length / pa_frame_size(&f->ss);
+
+ if (!f->active)
+ return buf;
+ if (f->ss.format == PA_SAMPLE_FLOAT32NE) {
+ int i;
+ float *data = pa_memblock_acquire_chunk(buf);
+ for (i = 0; i < f->cm.channels; i++)
+ lr4_process_float32(&f->lr4[i], samples, f->cm.channels, &data[i], &data[i]);
+ pa_memblock_release(buf->memblock);
+ }
+ else if (f->ss.format == PA_SAMPLE_S16NE) {
+ int i;
+ short *data = pa_memblock_acquire_chunk(buf);
+ for (i = 0; i < f->cm.channels; i++)
+ lr4_process_s16(&f->lr4[i], samples, f->cm.channels, &data[i], &data[i]);
+ pa_memblock_release(buf->memblock);
+ }
+ else pa_assert_not_reached();
+ return buf;
+}
+
+void pa_lfe_filter_update_rate(pa_lfe_filter_t *f, uint32_t new_rate) {
+ int i;
+ float biquad_freq = f->crossover / (new_rate / 2);
+
+ f->ss.rate = new_rate;
+ if (biquad_freq <= 0 || biquad_freq >= 1) {
+ pa_log_warn("Crossover frequency (%f) outside range for sample rate %d", f->crossover, new_rate);
+ f->active = false;
+ return;
+ }
+
+ for (i = 0; i < f->cm.channels; i++)
+ lr4_set(&f->lr4[i], f->cm.map[i] == PA_CHANNEL_POSITION_LFE ? BQ_LOWPASS : BQ_HIGHPASS, biquad_freq);
+
+ f->active = true;
+}
diff --git a/src/pulsecore/filter/lfe-filter.h b/src/pulsecore/filter/lfe-filter.h
new file mode 100644
index 0000000..25db8a0
--- /dev/null
+++ b/src/pulsecore/filter/lfe-filter.h
@@ -0,0 +1,38 @@
+#ifndef foolfefilterhfoo
+#define foolfefilterhfoo
+
+/***
+ This file is part of PulseAudio.
+
+ Copyright 2014 David Henningsson, Canonical Ltd.
+
+ 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, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ USA.
+***/
+
+#include <pulse/sample.h>
+#include <pulse/channelmap.h>
+#include <pulsecore/memchunk.h>
+
+
+typedef struct pa_lfe_filter pa_lfe_filter_t;
+
+pa_lfe_filter_t * pa_lfe_filter_new(const pa_sample_spec* ss, const pa_channel_map* cm, float crossover_freq);
+void pa_lfe_filter_free(pa_lfe_filter_t *);
+void pa_lfe_filter_reset(pa_lfe_filter_t *);
+pa_memchunk * pa_lfe_filter_process(pa_lfe_filter_t *filter, pa_memchunk *buf);
+void pa_lfe_filter_update_rate(pa_lfe_filter_t *, uint32_t new_rate);
+
+#endif
diff --git a/src/pulsecore/resampler.c b/src/pulsecore/resampler.c
index 0d7cc20..157671d 100644
--- a/src/pulsecore/resampler.c
+++ b/src/pulsecore/resampler.c
@@ -40,7 +40,7 @@ struct ffmpeg_data { /* data specific to ffmpeg */
static int copy_init(pa_resampler *r);
-static void setup_remap(const pa_resampler *r, pa_remap_t *m);
+static void setup_remap(const pa_resampler *r, pa_remap_t *m, bool *lfe_filter_required);
static void free_remap(pa_remap_t *m);
static int (* const init_table[])(pa_resampler *r) = {
@@ -324,6 +324,7 @@ pa_resampler* pa_resampler_new(
pa_resample_flags_t flags) {
pa_resampler *r = NULL;
+ bool lfe_filter_required = false;
pa_assert(pool);
pa_assert(a);
@@ -412,7 +413,15 @@ pa_resampler* pa_resampler_new(
/* set up the remap structure */
if (r->map_required)
- setup_remap(r, &r->remap);
+ setup_remap(r, &r->remap, &lfe_filter_required);
+
+ if (lfe_filter_required) {
+ pa_sample_spec wss = r->o_ss;
+ wss.format = r->work_format;
+ /* TODO: Temporary code that sets crossover freq to 120 Hz. This should be a parameter */
+ r->lfe_filter = pa_lfe_filter_new(&wss, &r->o_cm, 120.0f);
+ pa_log_debug(" lfe filter activated (LR4 type)");
+ }
/* initialize implementation */
if (init_table[method](r) < 0)
@@ -434,6 +443,9 @@ void pa_resampler_free(pa_resampler *r) {
else
pa_xfree(r->impl.data);
+ if (r->lfe_filter)
+ pa_lfe_filter_free(r->lfe_filter);
+
if (r->to_work_format_buf.memblock)
pa_memblock_unref(r->to_work_format_buf.memblock);
if (r->remap_buf.memblock)
@@ -472,6 +484,9 @@ void pa_resampler_set_output_rate(pa_resampler *r, uint32_t rate) {
r->o_ss.rate = rate;
r->impl.update_rates(r);
+
+ if (r->lfe_filter)
+ pa_lfe_filter_update_rate(r->lfe_filter, rate);
}
size_t pa_resampler_request(pa_resampler *r, size_t out_length) {
@@ -556,6 +571,9 @@ void pa_resampler_reset(pa_resampler *r) {
if (r->impl.reset)
r->impl.reset(r);
+ if (r->lfe_filter)
+ pa_lfe_filter_reset(r->lfe_filter);
+
*r->have_leftover = false;
}
@@ -756,7 +774,7 @@ static int front_rear_side(pa_channel_position_t p) {
return ON_OTHER;
}
-static void setup_remap(const pa_resampler *r, pa_remap_t *m) {
+static void setup_remap(const pa_resampler *r, pa_remap_t *m, bool *lfe_filter_required) {
unsigned oc, ic;
unsigned n_oc, n_ic;
bool ic_connected[PA_CHANNELS_MAX];
@@ -765,6 +783,7 @@ static void setup_remap(const pa_resampler *r, pa_remap_t *m) {
pa_assert(r);
pa_assert(m);
+ pa_assert(lfe_filter_required);
n_oc = r->o_ss.channels;
n_ic = r->i_ss.channels;
@@ -777,6 +796,7 @@ static void setup_remap(const pa_resampler *r, pa_remap_t *m) {
memset(m->map_table_i, 0, sizeof(m->map_table_i));
memset(ic_connected, 0, sizeof(ic_connected));
+ *lfe_filter_required = false;
if (r->flags & PA_RESAMPLER_NO_REMAP) {
for (oc = 0; oc < PA_MIN(n_ic, n_oc); oc++)
@@ -888,6 +908,9 @@ static void setup_remap(const pa_resampler *r, pa_remap_t *m) {
oc_connected = true;
ic_connected[ic] = true;
+
+ if (a == PA_CHANNEL_POSITION_MONO && on_lfe(b) && !(r->flags & PA_RESAMPLER_NO_LFE))
+ *lfe_filter_required = true;
}
else if (b == PA_CHANNEL_POSITION_MONO) {
m->map_table_f[oc][ic] = 1.0f / (float) n_ic;
@@ -970,6 +993,8 @@ static void setup_remap(const pa_resampler *r, pa_remap_t *m) {
/* Please note that a channel connected to LFE doesn't
* really count as connected. */
+
+ *lfe_filter_required = true;
}
}
}
@@ -1340,6 +1365,9 @@ void pa_resampler_run(pa_resampler *r, const pa_memchunk *in, pa_memchunk *out)
buf = remap_channels(r, buf);
}
+ if (r->lfe_filter)
+ buf = pa_lfe_filter_process(r->lfe_filter, buf);
+
if (buf->length) {
buf = convert_from_work_format(r, buf);
*out = *buf;
diff --git a/src/pulsecore/resampler.h b/src/pulsecore/resampler.h
index 4840a5e..45ad75a 100644
--- a/src/pulsecore/resampler.h
+++ b/src/pulsecore/resampler.h
@@ -26,6 +26,7 @@
#include <pulsecore/memchunk.h>
#include <pulsecore/sconv.h>
#include <pulsecore/remap.h>
+#include <pulsecore/filter/lfe-filter.h>
typedef struct pa_resampler pa_resampler;
typedef struct pa_resampler_impl pa_resampler_impl;
@@ -106,6 +107,8 @@ struct pa_resampler {
pa_remap_t remap;
bool map_required;
+ pa_lfe_filter_t *lfe_filter;
+
pa_resampler_impl impl;
};
commit f3ebf6b667b155f5fe6526bd70881c79e07d7874
Author: David Henningsson <david.henningsson at canonical.com>
Date: Tue Mar 24 10:29:12 2015 +0100
lfe-filter: Import code from the Chrome OS audio server
The chrome OS audio server has some already existing code, which
has been made available under a BSD-style license, which should be
safe to import by us.
Signed-off-by: David Henningsson <david.henningsson at canonical.com>
diff --git a/LICENSE b/LICENSE
index 226c4ce..6932317 100644
--- a/LICENSE
+++ b/LICENSE
@@ -29,6 +29,9 @@ considered too small and stable to be considered as an external library) use the
more permissive MIT license. This include the device reservation DBus protocol
and realtime kit implementations.
+A more permissive BSD-style license is used for LFE filters, see
+src/pulsecore/filter/LICENSE.WEBKIT for details.
+
Additionally, a more permissive Sun license is used for code that performs
u-law, A-law and linear PCM conversions.
diff --git a/src/pulsecore/filter/LICENSE.WEBKIT b/src/pulsecore/filter/LICENSE.WEBKIT
new file mode 100644
index 0000000..2f69d9f
--- /dev/null
+++ b/src/pulsecore/filter/LICENSE.WEBKIT
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2010 Google Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
diff --git a/src/pulsecore/filter/biquad.c b/src/pulsecore/filter/biquad.c
new file mode 100644
index 0000000..b28256d
--- /dev/null
+++ b/src/pulsecore/filter/biquad.c
@@ -0,0 +1,368 @@
+/* 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 max
+#define max(a, b) ({ __typeof__(a) _a = (a); \
+ __typeof__(b) _b = (b); \
+ _a > _b ? _a : _b; })
+#endif
+
+#ifndef min
+#define min(a, b) ({ __typeof__(a) _a = (a); \
+ __typeof__(b) _b = (b); \
+ _a < _b ? _a : _b; })
+#endif
+
+#ifndef M_PI
+#define M_PI 3.14159265358979323846
+#endif
+
+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 = b0 * a0_inv;
+ bq->b1 = b1 * a0_inv;
+ bq->b2 = b2 * a0_inv;
+ bq->a1 = a1 * a0_inv;
+ bq->a2 = a2 * a0_inv;
+}
+
+static void biquad_lowpass(struct biquad *bq, double cutoff, double resonance)
+{
+ /* Limit cutoff to 0 to 1. */
+ cutoff = max(0.0, min(cutoff, 1.0));
+
+ if (cutoff == 1) {
+ /* When cutoff is 1, the z-transform is 1. */
+ set_coefficient(bq, 1, 0, 0, 1, 0, 0);
+ } else if (cutoff > 0) {
+ /* Compute biquad coefficients for lowpass filter */
+ resonance = max(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);
+ } else {
+ /* When cutoff is zero, nothing gets through the filter, so set
+ * coefficients up correctly.
+ */
+ set_coefficient(bq, 0, 0, 0, 1, 0, 0);
+ }
+}
+
+static void biquad_highpass(struct biquad *bq, double cutoff, double resonance)
+{
+ /* Limit cutoff to 0 to 1. */
+ cutoff = max(0.0, min(cutoff, 1.0));
+
+ if (cutoff == 1) {
+ /* The z-transform is 0. */
+ set_coefficient(bq, 0, 0, 0, 1, 0, 0);
+ } else if (cutoff > 0) {
+ /* Compute biquad coefficients for highpass filter */
+ resonance = max(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);
+ } else {
+ /* 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, 0, 0, 1, 0, 0);
+ }
+}
+
+static void biquad_bandpass(struct biquad *bq, double frequency, double Q)
+{
+ /* No negative frequencies allowed. */
+ frequency = max(0.0, frequency);
+
+ /* Don't let Q go negative, which causes an unstable filter. */
+ Q = max(0.0, Q);
+
+ if (frequency > 0 && frequency < 1) {
+ double w0 = M_PI * frequency;
+ if (Q > 0) {
+ 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);
+ } else {
+ /* 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);
+ }
+ } else {
+ /* 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);
+ }
+}
+
+static void biquad_lowshelf(struct biquad *bq, double frequency, double db_gain)
+{
+ /* Clip frequencies to between 0 and 1, inclusive. */
+ frequency = max(0.0, min(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);
+ } else if (frequency > 0) {
+ double w0 = M_PI * frequency;
+ double S = 1; /* filter slope (1 is max value) */
+ double alpha = 0.5 * sin(w0) *
+ sqrt((A + 1 / A) * (1 / S - 1) + 2);
+ 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);
+ } else {
+ /* When frequency is 0, the z-transform is 1. */
+ set_coefficient(bq, 1, 0, 0, 1, 0, 0);
+ }
+}
+
+static void biquad_highshelf(struct biquad *bq, double frequency,
+ double db_gain)
+{
+ /* Clip frequencies to between 0 and 1, inclusive. */
+ frequency = max(0.0, min(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);
+ } else if (frequency > 0) {
+ double w0 = M_PI * frequency;
+ double S = 1; /* filter slope (1 is max value) */
+ double alpha = 0.5 * sin(w0) *
+ sqrt((A + 1 / A) * (1 / S - 1) + 2);
+ 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);
+ } else {
+ /* When frequency = 0, the filter is just a gain, A^2. */
+ set_coefficient(bq, A * A, 0, 0, 1, 0, 0);
+ }
+}
+
+static void biquad_peaking(struct biquad *bq, double frequency, double Q,
+ double db_gain)
+{
+ /* Clip frequencies to between 0 and 1, inclusive. */
+ frequency = max(0.0, min(frequency, 1.0));
+
+ /* Don't let Q go negative, which causes an unstable filter. */
+ Q = max(0.0, Q);
+
+ double A = pow(10.0, db_gain / 40);
+
+ if (frequency > 0 && frequency < 1) {
+ if (Q > 0) {
+ 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);
+ } else {
+ /* 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);
+ }
+ } else {
+ /* When frequency is 0 or 1, the z-transform is 1. */
+ set_coefficient(bq, 1, 0, 0, 1, 0, 0);
+ }
+}
+
+static void biquad_notch(struct biquad *bq, double frequency, double Q)
+{
+ /* Clip frequencies to between 0 and 1, inclusive. */
+ frequency = max(0.0, min(frequency, 1.0));
+
+ /* Don't let Q go negative, which causes an unstable filter. */
+ Q = max(0.0, Q);
+
+ if (frequency > 0 && frequency < 1) {
+ if (Q > 0) {
+ 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);
+ } else {
+ /* 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);
+ }
+ } else {
+ /* When frequency is 0 or 1, the z-transform is 1. */
+ set_coefficient(bq, 1, 0, 0, 1, 0, 0);
+ }
+}
+
+static void biquad_allpass(struct biquad *bq, double frequency, double Q)
+{
+ /* Clip frequencies to between 0 and 1, inclusive. */
+ frequency = max(0.0, min(frequency, 1.0));
+
+ /* Don't let Q go negative, which causes an unstable filter. */
+ Q = max(0.0, Q);
+
+ if (frequency > 0 && frequency < 1) {
+ if (Q > 0) {
+ 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);
+ } else {
+ /* 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);
+ }
+ } else {
+ /* When frequency is 0 or 1, the z-transform is 1. */
+ set_coefficient(bq, 1, 0, 0, 1, 0, 0);
+ }
+}
+
+void biquad_set(struct biquad *bq, enum biquad_type type, double freq, double Q,
+ double gain)
+{
+ /* Default is an identity filter. Also clear history values. */
+ set_coefficient(bq, 1, 0, 0, 1, 0, 0);
+ bq->x1 = 0;
+ bq->x2 = 0;
+ bq->y1 = 0;
+ bq->y2 = 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, gain);
+ break;
+ case BQ_HIGHSHELF:
+ biquad_highshelf(bq, freq, 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:
+ break;
+ }
+}
diff --git a/src/pulsecore/filter/biquad.h b/src/pulsecore/filter/biquad.h
new file mode 100644
index 0000000..c584aa9
--- /dev/null
+++ b/src/pulsecore/filter/biquad.h
@@ -0,0 +1,57 @@
+/* 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 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 accurary, but
+ * float is used during the actual filtering for faster computation.
+ */
+struct biquad {
+ float b0, b1, b2;
+ float a1, a2;
+ float x1, x2;
+ float y1, y2;
+};
+
+/* 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
+};
+
+/* 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_ */
diff --git a/src/pulsecore/filter/crossover.c b/src/pulsecore/filter/crossover.c
new file mode 100644
index 0000000..11a8c6e
--- /dev/null
+++ b/src/pulsecore/filter/crossover.c
@@ -0,0 +1,188 @@
+/* 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.
+ */
+
+#include "crossover.h"
+#include "biquad.h"
+
+static void lr4_set(struct lr4 *lr4, enum biquad_type type, float freq)
+{
+ struct biquad q;
+ biquad_set(&q, type, freq, 0, 0);
+ lr4->b0 = q.b0;
+ lr4->b1 = q.b1;
+ lr4->b2 = q.b2;
+ lr4->a1 = q.a1;
+ lr4->a2 = q.a2;
+ lr4->x1 = 0;
+ lr4->x2 = 0;
+ lr4->y1 = 0;
+ lr4->y2 = 0;
+ lr4->z1 = 0;
+ lr4->z2 = 0;
+}
+
+/* Split input data using two LR4 filters, put the result into the input array
+ * and another array.
+ *
+ * data0 --+-- lp --> data0
+ * |
+ * \-- hp --> data1
+ */
+static void lr4_split(struct lr4 *lp, struct lr4 *hp, int count, float *data0,
+ float *data1)
+{
+ float lx1 = lp->x1;
+ float lx2 = lp->x2;
+ float ly1 = lp->y1;
+ float ly2 = lp->y2;
+ float lz1 = lp->z1;
+ float lz2 = lp->z2;
+ float lb0 = lp->b0;
+ float lb1 = lp->b1;
+ float lb2 = lp->b2;
+ float la1 = lp->a1;
+ float la2 = lp->a2;
+
+ float hx1 = hp->x1;
+ float hx2 = hp->x2;
+ float hy1 = hp->y1;
+ float hy2 = hp->y2;
+ float hz1 = hp->z1;
+ float hz2 = hp->z2;
+ float hb0 = hp->b0;
+ float hb1 = hp->b1;
+ float hb2 = hp->b2;
+ float ha1 = hp->a1;
+ float ha2 = hp->a2;
+
+ int i;
+ for (i = 0; i < count; i++) {
+ float x, y, z;
+ x = data0[i];
+ y = lb0*x + lb1*lx1 + lb2*lx2 - la1*ly1 - la2*ly2;
+ z = lb0*y + lb1*ly1 + lb2*ly2 - la1*lz1 - la2*lz2;
+ lx2 = lx1;
+ lx1 = x;
+ ly2 = ly1;
+ ly1 = y;
+ lz2 = lz1;
+ lz1 = z;
+ data0[i] = z;
+
+ y = hb0*x + hb1*hx1 + hb2*hx2 - ha1*hy1 - ha2*hy2;
+ z = hb0*y + hb1*hy1 + hb2*hy2 - ha1*hz1 - ha2*hz2;
+ hx2 = hx1;
+ hx1 = x;
+ hy2 = hy1;
+ hy1 = y;
+ hz2 = hz1;
+ hz1 = z;
+ data1[i] = z;
+ }
+
+ lp->x1 = lx1;
+ lp->x2 = lx2;
+ lp->y1 = ly1;
+ lp->y2 = ly2;
+ lp->z1 = lz1;
+ lp->z2 = lz2;
+
+ hp->x1 = hx1;
+ hp->x2 = hx2;
+ hp->y1 = hy1;
+ hp->y2 = hy2;
+ hp->z1 = hz1;
+ hp->z2 = hz2;
+}
+
+/* Split input data using two LR4 filters and sum them back to the original
+ * data array.
+ *
+ * data --+-- lp --+--> data
+ * | |
+ * \-- hp --/
+ */
+static void lr4_merge(struct lr4 *lp, struct lr4 *hp, int count, float *data)
+{
+ float lx1 = lp->x1;
+ float lx2 = lp->x2;
+ float ly1 = lp->y1;
+ float ly2 = lp->y2;
+ float lz1 = lp->z1;
+ float lz2 = lp->z2;
+ float lb0 = lp->b0;
+ float lb1 = lp->b1;
+ float lb2 = lp->b2;
+ float la1 = lp->a1;
+ float la2 = lp->a2;
+
+ float hx1 = hp->x1;
+ float hx2 = hp->x2;
+ float hy1 = hp->y1;
+ float hy2 = hp->y2;
+ float hz1 = hp->z1;
+ float hz2 = hp->z2;
+ float hb0 = hp->b0;
+ float hb1 = hp->b1;
+ float hb2 = hp->b2;
+ float ha1 = hp->a1;
+ float ha2 = hp->a2;
+
+ int i;
+ for (i = 0; i < count; i++) {
+ float x, y, z;
+ x = data[i];
+ y = lb0*x + lb1*lx1 + lb2*lx2 - la1*ly1 - la2*ly2;
+ z = lb0*y + lb1*ly1 + lb2*ly2 - la1*lz1 - la2*lz2;
+ lx2 = lx1;
+ lx1 = x;
+ ly2 = ly1;
+ ly1 = y;
+ lz2 = lz1;
+ lz1 = z;
+
+ y = hb0*x + hb1*hx1 + hb2*hx2 - ha1*hy1 - ha2*hy2;
+ z = hb0*y + hb1*hy1 + hb2*hy2 - ha1*hz1 - ha2*hz2;
+ hx2 = hx1;
+ hx1 = x;
+ hy2 = hy1;
+ hy1 = y;
+ hz2 = hz1;
+ hz1 = z;
+ data[i] = z + lz1;
+ }
+
+ lp->x1 = lx1;
+ lp->x2 = lx2;
+ lp->y1 = ly1;
+ lp->y2 = ly2;
+ lp->z1 = lz1;
+ lp->z2 = lz2;
+
+ hp->x1 = hx1;
+ hp->x2 = hx2;
+ hp->y1 = hy1;
+ hp->y2 = hy2;
+ hp->z1 = hz1;
+ hp->z2 = hz2;
+}
+
+void crossover_init(struct crossover *xo, float freq1, float freq2)
+{
+ int i;
+ for (i = 0; i < 3; i++) {
+ float f = (i == 0) ? freq1 : freq2;
+ lr4_set(&xo->lp[i], BQ_LOWPASS, f);
+ lr4_set(&xo->hp[i], BQ_HIGHPASS, f);
+ }
+}
+
+void crossover_process(struct crossover *xo, int count, float *data0,
+ float *data1, float *data2)
+{
+ lr4_split(&xo->lp[0], &xo->hp[0], count, data0, data1);
+ lr4_merge(&xo->lp[1], &xo->hp[1], count, data0);
+ lr4_split(&xo->lp[2], &xo->hp[2], count, data1, data2);
+}
diff --git a/src/pulsecore/filter/crossover.h b/src/pulsecore/filter/crossover.h
new file mode 100644
index 0000000..99a601c
--- /dev/null
+++ b/src/pulsecore/filter/crossover.h
@@ -0,0 +1,70 @@
+/* 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 CROSSOVER_H_
+#define CROSSOVER_H_
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/* An LR4 filter is two biquads with the same parameters connected in series:
+ *
+ * x -- [BIQUAD] -- y -- [BIQUAD] -- z
+ *
+ * Both biquad filter has the same parameter b[012] and a[12],
+ * The variable [xyz][12] keep the history values.
+ */
+struct lr4 {
+ float b0, b1, b2;
+ float a1, a2;
+ float x1, x2;
+ float y1, y2;
+ float z1, z2;
+};
+
+/* Three bands crossover filter:
+ *
+ * INPUT --+-- lp0 --+-- lp1 --+---> LOW (0)
+ * | | |
+ * | \-- hp1 --/
+ * |
+ * \-- hp0 --+-- lp2 ------> MID (1)
+ * |
+ * \-- hp2 ------> HIGH (2)
+ *
+ * [f0] [f1]
+ *
+ * Each lp or hp is an LR4 filter, which consists of two second-order
+ * lowpass or highpass butterworth filters.
+ */
+struct crossover {
+ struct lr4 lp[3], hp[3];
+};
+
+/* Initializes a crossover filter
+ * Args:
+ * xo - The crossover filter we want to initialize.
+ * freq1 - The normalized frequency splits low and mid band.
+ * freq2 - The normalized frequency splits mid and high band.
+ */
+void crossover_init(struct crossover *xo, float freq1, float freq2);
+
+/* Splits input samples to three bands.
+ * Args:
+ * xo - The crossover filter to use.
+ * count - The number of input samples.
+ * data0 - The input samples, also the place to store low band output.
+ * data1 - The place to store mid band output.
+ * data2 - The place to store high band output.
+ */
+void crossover_process(struct crossover *xo, int count, float *data0,
+ float *data1, float *data2);
+
+#ifdef __cplusplus
+} /* extern "C" */
+#endif
+
+#endif /* CROSSOVER_H_ */
More information about the pulseaudio-commits
mailing list