summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--LICENSE.WEBKIT27
-rw-r--r--cras/src/Makefile.am42
-rw-r--r--cras/src/dsp/biquad.c368
-rw-r--r--cras/src/dsp/biquad.h57
-rw-r--r--cras/src/dsp/dsp_util.c22
-rw-r--r--cras/src/dsp/dsp_util.h22
-rw-r--r--cras/src/dsp/eq.c152
-rw-r--r--cras/src/dsp/eq.h67
-rw-r--r--cras/src/dsp/tests/cmpraw.c54
-rw-r--r--cras/src/dsp/tests/dsp_test_util.c37
-rw-r--r--cras/src/dsp/tests/dsp_test_util.h27
-rw-r--r--cras/src/dsp/tests/eq_test.c137
-rw-r--r--cras/src/dsp/tests/plot_fftl.m40
-rw-r--r--cras/src/dsp/tests/raw.c93
-rw-r--r--cras/src/dsp/tests/raw.h46
-rw-r--r--cras/src/server/cras_dsp.c2
-rw-r--r--cras/src/server/cras_dsp_mod_builtin.c164
-rw-r--r--cras/src/tests/dsp_core_unittest.cc98
-rw-r--r--cras/src/tests/dsp_unittest.cc48
19 files changed, 1463 insertions, 40 deletions
diff --git a/LICENSE.WEBKIT b/LICENSE.WEBKIT
new file mode 100644
index 00000000..2f69d9f0
--- /dev/null
+++ b/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/cras/src/Makefile.am b/cras/src/Makefile.am
index 28db641c..d1918aaa 100644
--- a/cras/src/Makefile.am
+++ b/cras/src/Makefile.am
@@ -1,3 +1,7 @@
+# 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.
+
AUTOMAKE_OPTIONS = subdir-objects
ACLOCAL_AMFLAGS = ${ACLOCAL_FLAGS}
@@ -12,6 +16,9 @@ cras_SOURCES = \
common/cras_util.c \
common/dumper.c \
common/edid_utils.c \
+ dsp/biquad.c \
+ dsp/dsp_util.c \
+ dsp/eq.c \
server/audio_thread.c \
server/config/cras_card_config.c \
server/config/cras_device_blacklist.c \
@@ -56,9 +63,10 @@ cras_SOURCES = \
server/softvol_curve.c
cras_CPPFLAGS = $(COMMON_CPPFLAGS) -I$(top_srcdir)/src/common \
- -I$(top_srcdir)/src/server -I$(top_srcdir)/src/server/config \
+ -I$(top_srcdir)/src/dsp -I$(top_srcdir)/src/server \
+ -I$(top_srcdir)/src/server/config \
$(DBUS_CFLAGS) $(SBC_CFLAGS)
-cras_LDADD = -lpthread -lasound -lrt -liniparser -ludev -ldl \
+cras_LDADD = -lpthread -lasound -lrt -liniparser -ludev -ldl -lm \
$(SBC_LIBS) \
$(DBUS_LIBS)
@@ -108,7 +116,7 @@ libasound_module_pcm_cras_la_LIBADD = -lasound libcras.la
libasound_module_ctl_cras_la_SOURCES = alsa_plugin/ctl_cras.c
libasound_module_ctl_cras_la_LIBADD = -lasound libcras.la
-check_PROGRAMS = \
+TESTS = \
a2dp_info_unittest \
a2dp_iodev_unittest \
alert_unittest \
@@ -136,18 +144,33 @@ check_PROGRAMS = \
alsa_ucm_unittest \
edid_utils_unittest \
expr_unittest \
+ dsp_core_unittest \
dsp_ini_unittest \
dsp_pipeline_unittest \
dsp_unittest \
util_unittest
-TESTS = $(check_PROGRAMS)
+check_PROGRAMS = $(TESTS)
cras_test_client_SOURCES = tests/cras_test_client.c
cras_test_client_LDADD = libcras.la
cras_test_client_CPPFLAGS = $(COMMON_CPPFLAGS) -I$(top_srcdir)/src/libcras \
-I$(top_srcdir)/src/common
+# dsp test programs (not run automatically)
+check_PROGRAMS += \
+ eq_test \
+ cmpraw
+
+eq_test_SOURCES = dsp/biquad.c dsp/eq.c dsp/dsp_util.c dsp/tests/eq_test.c \
+ dsp/tests/dsp_test_util.c dsp/tests/raw.c
+eq_test_LDADD = -lrt -lm
+eq_test_CPPFLAGS = $(COMMON_CPPFLAGS) -I$(top_srcdir)/src/dsp
+
+cmpraw_SOURCES = dsp/tests/cmpraw.c dsp/tests/raw.c
+cmpraw_LDADD = -lm
+cmpraw_CPPFLAGS = $(COMMON_CPPFLAGS) -I$(top_srcdir)/src/dsp
+
# unit tests
alert_unittest_SOURCES = tests/alert_unittest.cc \
server/cras_alert.c
@@ -242,6 +265,11 @@ device_blacklist_unittest_CPPFLAGS = $(COMMON_CPPFLAGS) \
-I$(top_srcdir)/src/server/config
device_blacklist_unittest_LDADD = -lgtest -liniparser -lpthread
+dsp_core_unittest_SOURCES = tests/dsp_core_unittest.cc dsp/eq.c \
+ dsp/biquad.c dsp/dsp_util.c
+dsp_core_unittest_CPPFLAGS = $(COMMON_CPPFLAGS) -I$(top_srcdir)/src/dsp
+dsp_core_unittest_LDADD = -lgtest -lpthread
+
dsp_ini_unittest_SOURCES = tests/dsp_ini_unittest.cc \
server/cras_dsp_ini.c server/cras_expr.c common/dumper.c
dsp_ini_unittest_CPPFLAGS = $(COMMON_CPPFLAGS) -I$(top_srcdir)/src/common \
@@ -257,10 +285,10 @@ dsp_pipeline_unittest_LDADD = -lgtest -lrt -liniparser -lpthread
dsp_unittest_SOURCES = tests/dsp_unittest.cc \
server/cras_dsp.c server/cras_dsp_ini.c server/cras_dsp_pipeline.c \
- server/cras_expr.c ../src/server/cras_dsp_mod_builtin.c \
- common/dumper.c
+ server/cras_expr.c common/dumper.c dsp/dsp_util.c \
+ dsp/tests/dsp_test_util.c
dsp_unittest_CPPFLAGS = $(COMMON_CPPFLAGS) -I$(top_srcdir)/src/common \
- -I$(top_srcdir)/src/server
+ -I$(top_srcdir)/src/server -I$(top_srcdir)/src/dsp
dsp_unittest_LDADD = -lgtest -lrt -liniparser -lpthread
dumper_unittest_SOURCES = tests/dumper_unittest.cc common/dumper.c
diff --git a/cras/src/dsp/biquad.c b/cras/src/dsp/biquad.c
new file mode 100644
index 00000000..b28256d4
--- /dev/null
+++ b/cras/src/dsp/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/cras/src/dsp/biquad.h b/cras/src/dsp/biquad.h
new file mode 100644
index 00000000..c584aa96
--- /dev/null
+++ b/cras/src/dsp/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/cras/src/dsp/dsp_util.c b/cras/src/dsp/dsp_util.c
new file mode 100644
index 00000000..7e129496
--- /dev/null
+++ b/cras/src/dsp/dsp_util.c
@@ -0,0 +1,22 @@
+/* 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 <fpu_control.h>
+#include "dsp_util.h"
+
+void dsp_enable_flush_denormal_to_zero()
+{
+#if defined(__i386__) || defined(__x86_64__)
+ unsigned int mxcsr;
+ mxcsr = __builtin_ia32_stmxcsr();
+ __builtin_ia32_ldmxcsr(mxcsr | 0x8040);
+#elif defined(__arm__)
+ int cw;
+ _FPU_GETCW(cw);
+ _FPU_SETCW(cw | (1 << 24));
+#else
+#warning "Don't know how to disable denorms. Performace may suffer."
+#endif
+}
diff --git a/cras/src/dsp/dsp_util.h b/cras/src/dsp/dsp_util.h
new file mode 100644
index 00000000..21fdff3b
--- /dev/null
+++ b/cras/src/dsp/dsp_util.h
@@ -0,0 +1,22 @@
+/* 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 DSPUTIL_H_
+#define DSPUTIL_H_
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/* Disables denormal numbers in floating point calculation. Denormal numbers
+ * happens often in IIR filters, and it can be very slow.
+ */
+void dsp_enable_flush_denormal_to_zero();
+
+#ifdef __cplusplus
+} /* extern "C" */
+#endif
+
+#endif /* DSPUTIL_H_ */
diff --git a/cras/src/dsp/eq.c b/cras/src/dsp/eq.c
new file mode 100644
index 00000000..deec8718
--- /dev/null
+++ b/cras/src/dsp/eq.c
@@ -0,0 +1,152 @@
+/* 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 <stdlib.h>
+#include "eq.h"
+
+struct eq {
+ int n;
+ struct biquad biquad[MAX_BIQUADS_PER_EQ];
+};
+
+struct eq *eq_new()
+{
+ struct eq *eq = (struct eq *)calloc(1, sizeof(*eq));
+ return eq;
+}
+
+void eq_free(struct eq *eq)
+{
+ free(eq);
+}
+
+int eq_append_biquad(struct eq *eq, enum biquad_type type, float freq, float Q,
+ float gain)
+{
+ if (eq->n >= MAX_BIQUADS_PER_EQ)
+ return -1;
+ biquad_set(&eq->biquad[eq->n++], type, freq, Q, gain);
+ return 0;
+}
+
+int eq_append_biquad_direct(struct eq *eq, const struct biquad *biquad)
+{
+ if (eq->n >= MAX_BIQUADS_PER_EQ)
+ return -1;
+ eq->biquad[eq->n++] = *biquad;
+ return 0;
+}
+
+/* This is the prototype of the processing loop. */
+void eq_process1(struct eq *eq, float *data, int count)
+{
+ int i, j;
+ for (i = 0; i < eq->n; i++) {
+ struct biquad *q = &eq->biquad[i];
+ float x1 = q->x1;
+ float x2 = q->x2;
+ float y1 = q->y1;
+ float y2 = q->y2;
+ float b0 = q->b0;
+ float b1 = q->b1;
+ float b2 = q->b2;
+ float a1 = q->a1;
+ float a2 = q->a2;
+ for (j = 0; j < count; j++) {
+ float x = data[j];
+ float y = b0*x
+ + (b1*x1 + b2*x2)
+ - (a1*y1 + a2*y2);
+ data[j] = y;
+ x2 = x1;
+ x1 = x;
+ y2 = y1;
+ y1 = y;
+ }
+ q->x1 = x1;
+ q->x2 = x2;
+ q->y1 = y1;
+ q->y2 = y2;
+ }
+}
+
+/* This is the actual processing loop used. It is the unrolled version of the
+ * above prototype. */
+void eq_process(struct eq *eq, float *data, int count)
+{
+ int i, j;
+ for (i = 0; i < eq->n; i += 2) {
+ if (i + 1 == eq->n) {
+ struct biquad *q = &eq->biquad[i];
+ float x1 = q->x1;
+ float x2 = q->x2;
+ float y1 = q->y1;
+ float y2 = q->y2;
+ float b0 = q->b0;
+ float b1 = q->b1;
+ float b2 = q->b2;
+ float a1 = q->a1;
+ float a2 = q->a2;
+ for (j = 0; j < count; j++) {
+ float x = data[j];
+ float y = b0*x
+ + (b1*x1 + b2*x2)
+ - (a1*y1 + a2*y2);
+ data[j] = y;
+ x2 = x1;
+ x1 = x;
+ y2 = y1;
+ y1 = y;
+ }
+ q->x1 = x1;
+ q->x2 = x2;
+ q->y1 = y1;
+ q->y2 = y2;
+ } else {
+ struct biquad *q = &eq->biquad[i];
+ struct biquad *r = &eq->biquad[i+1];
+ float x1 = q->x1;
+ float x2 = q->x2;
+ float y1 = q->y1;
+ float y2 = q->y2;
+ float qb0 = q->b0;
+ float qb1 = q->b1;
+ float qb2 = q->b2;
+ float qa1 = q->a1;
+ float qa2 = q->a2;
+
+ float z1 = r->y1;
+ float z2 = r->y2;
+ float rb0 = r->b0;
+ float rb1 = r->b1;
+ float rb2 = r->b2;
+ float ra1 = r->a1;
+ float ra2 = r->a2;
+
+ for (j = 0; j < count; j++) {
+ float x = data[j];
+ float y = qb0*x
+ + (qb1*x1 + qb2*x2)
+ - (qa1*y1 + qa2*y2);
+ float z = rb0*y
+ + (rb1*y1 + rb2*y2)
+ - (ra1*z1 + ra2*z2);
+ data[j] = z;
+ x2 = x1;
+ x1 = x;
+ y2 = y1;
+ y1 = y;
+ z2 = z1;
+ z1 = z;
+ }
+ q->x1 = x1;
+ q->x2 = x2;
+ q->y1 = y1;
+ q->y2 = y2;
+ r->y1 = z1;
+ r->y2 = z2;
+ }
+ }
+}
diff --git a/cras/src/dsp/eq.h b/cras/src/dsp/eq.h
new file mode 100644
index 00000000..c5836329
--- /dev/null
+++ b/cras/src/dsp/eq.h
@@ -0,0 +1,67 @@
+/* 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 EQ_H_
+#define EQ_H_
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/* An EQ is a chain of biquad filters. See Web Audio API spec for details of the
+ * biquad filters and their parameters. */
+
+#include "biquad.h"
+
+/* Maximum number of biquad filters an EQ can have */
+#define MAX_BIQUADS_PER_EQ 10
+
+struct eq;
+
+/* Create an EQ. */
+struct eq *eq_new();
+
+/* Free an EQ. */
+void eq_free(struct eq *eq);
+
+/* Append a biquad filter to an EQ. An EQ can have at most MAX_BIQUADS_PER_EQ
+ * biquad filters.
+ * Args:
+ * eq - The EQ we want to use.
+ * type - The type of the biquad filter we want to append.
+ * frequency - The value should be in the range [0, 1]. It is relative to
+ * half of the sampling rate.
+ * Q, gain - The meaning depends on the type of the filter. See Web Audio
+ * API for details.
+ * Returns:
+ * 0 if success. -1 if the eq has no room for more biquads.
+ */
+int eq_append_biquad(struct eq *eq, enum biquad_type type, float freq, float Q,
+ float gain);
+
+/* Append a biquad filter to an EQ. An EQ can have at most MAX_BIQUADS_PER_EQ
+ * biquad filters. This is similar to eq_append_biquad(), but it specifies the
+ * biquad coefficients directly.
+ * Args:
+ * eq - The EQ we want to use.
+ * biquad - The parameters for the biquad filter.
+ * Returns:
+ * 0 if success. -1 if the eq has no room for more biquads.
+ */
+int eq_append_biquad_direct(struct eq *eq, const struct biquad *biquad);
+
+/* Process a buffer of audio data through the EQ.
+ * Args:
+ * eq - The EQ we want to use.
+ * data - The array of audio samples.
+ * count - The number of elements in the data array to process.
+ */
+void eq_process(struct eq *eq, float *data, int count);
+
+#ifdef __cplusplus
+} /* extern "C" */
+#endif
+
+#endif /* EQ_H_ */
diff --git a/cras/src/dsp/tests/cmpraw.c b/cras/src/dsp/tests/cmpraw.c
new file mode 100644
index 00000000..3fed9440
--- /dev/null
+++ b/cras/src/dsp/tests/cmpraw.c
@@ -0,0 +1,54 @@
+/* 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 <math.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include "raw.h"
+
+/* Compare the difference between two raw files */
+
+static inline double max(double a, double b)
+{
+ return (a > b) ? a : b;
+}
+
+int main(int argc, char **argv)
+{
+ size_t frame1, frame2;
+ float *data1, *data2;
+ size_t i, n;
+ double diff = 0;
+ double maxdiff = 0;
+
+ if (argc != 3) {
+ fprintf(stderr, "usage: cmpraw 1.raw 2.raw\n");
+ exit(1);
+ }
+
+ data1 = read_raw(argv[1], &frame1);
+ data2 = read_raw(argv[2], &frame2);
+
+ if (frame1 != frame2) {
+ fprintf(stderr, "mismatch size (%zu vs %zu)\n", frame1, frame2);
+ exit(1);
+ }
+
+ n = frame1;
+ for (i = 0; i < n; i++) {
+ diff += fabs(data1[i] - data2[i]);
+ maxdiff = max(fabs(data1[i] - data2[i]), maxdiff);
+ }
+ printf("avg diff = %g, max diff = %g/65536.0\n",
+ diff / n / 65536.0, maxdiff);
+
+ free(data1);
+ free(data2);
+ return 0;
+}
diff --git a/cras/src/dsp/tests/dsp_test_util.c b/cras/src/dsp/tests/dsp_test_util.c
new file mode 100644
index 00000000..e52a481a
--- /dev/null
+++ b/cras/src/dsp/tests/dsp_test_util.c
@@ -0,0 +1,37 @@
+/* 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 <fenv.h>
+#include <float.h>
+#include <stdio.h>
+#include "dsp_test_util.h"
+
+int dsp_util_has_denormal()
+{
+ float x = 1;
+ while (x >= FLT_MIN)
+ x /= 2;
+ return x > 0;
+}
+
+void dsp_util_clear_fp_exceptions()
+{
+ feclearexcept(FE_ALL_EXCEPT);
+}
+
+void dsp_util_print_fp_exceptions()
+{
+ int excepts = fetestexcept(FE_ALL_EXCEPT);
+ printf("floating-point exceptions: ");
+ if (excepts & FE_DIVBYZERO)
+ printf("FE_DIVBYZERO ");
+ if (excepts & FE_INVALID)
+ printf("FE_INVALID ");
+ if (excepts & FE_OVERFLOW)
+ printf("FE_OVERFLOW ");
+ if (excepts & FE_UNDERFLOW)
+ printf("FE_UNDERFLOW ");
+ printf("\n");
+}
diff --git a/cras/src/dsp/tests/dsp_test_util.h b/cras/src/dsp/tests/dsp_test_util.h
new file mode 100644
index 00000000..eb97815c
--- /dev/null
+++ b/cras/src/dsp/tests/dsp_test_util.h
@@ -0,0 +1,27 @@
+/* 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 DSP_TEST_UTIL_H_
+#define DSP_TEST_UTIL_H_
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/* Tests if the system supports denormal numbers. Returns 1 if so, 0
+ * otherwise.*/
+int dsp_util_has_denormal();
+
+/* Clears floating point exceptions. For debugging only. */
+void dsp_util_clear_fp_exceptions();
+
+/* Prints floating point exceptions to stdout. For debugging only. */
+void dsp_util_print_fp_exceptions();
+
+#ifdef __cplusplus
+} /* extern "C" */
+#endif
+
+#endif /* DSP_TEST_UTIL_H_ */
diff --git a/cras/src/dsp/tests/eq_test.c b/cras/src/dsp/tests/eq_test.c
new file mode 100644
index 00000000..d7991014
--- /dev/null
+++ b/cras/src/dsp/tests/eq_test.c
@@ -0,0 +1,137 @@
+/* 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 <stdio.h>
+#include <stdlib.h>
+#include <time.h>
+
+#include "dsp_test_util.h"
+#include "dsp_util.h"
+#include "eq.h"
+#include "raw.h"
+
+#ifndef min
+#define min(a, b) ({ __typeof__(a) _a = (a); \
+ __typeof__(b) _b = (b); \
+ _a < _b ? _a : _b; })
+#endif
+
+static double tp_diff(struct timespec *tp2, struct timespec *tp1)
+{
+ return (tp2->tv_sec - tp1->tv_sec)
+ + (tp2->tv_nsec - tp1->tv_nsec) * 1e-9;
+}
+
+/* Generates impulse response */
+static void test_ir()
+{
+ int N = 32768;
+ float *data;
+ struct eq *eq;
+ double NQ = 44100 / 2; /* nyquist frequency */
+ struct timespec tp1, tp2;
+ int i;
+ FILE *ir;
+
+ data = calloc(1, sizeof(float) * N);
+ data[0] = 1;
+
+ eq = eq_new();
+ eq_append_biquad(eq, BQ_PEAKING, 380/NQ, 3, -10);
+ eq_append_biquad(eq, BQ_PEAKING, 720/NQ, 3, -12);
+ eq_append_biquad(eq, BQ_PEAKING, 1705/NQ, 3, -8);
+ eq_append_biquad(eq, BQ_HIGHPASS, 218/NQ, 0.7, -10.2);
+ eq_append_biquad(eq, BQ_PEAKING, 580/NQ, 6, -8);
+ eq_append_biquad(eq, BQ_HIGHSHELF, 8000/NQ, 3, 2);
+
+ clock_gettime(CLOCK_THREAD_CPUTIME_ID, &tp1);
+ eq_process(eq, data, N);
+ clock_gettime(CLOCK_THREAD_CPUTIME_ID, &tp2);
+ printf("processing takes %g seconds\n", tp_diff(&tp2, &tp1));
+ eq_free(eq);
+
+ ir = fopen("ir.dat", "w");
+ for (i = 0; i < N; i++)
+ fprintf(ir, "%g\n", data[i]);
+ fclose(ir);
+ free(data);
+}
+
+/* Processes a buffer of data chunk by chunk using eq */
+static void process(struct eq *eq, float *data, int count)
+{
+ int start;
+ for (start = 0; start < count; start += 2048)
+ eq_process(eq, data + start, min(2048, count - start));
+}
+
+/* Runs the filters on an input file */
+static void test_file(const char *input_filename, const char *output_filename)
+{
+ size_t frames;
+ int i;
+ double NQ = 44100 / 2; /* nyquist frequency */
+ struct timespec tp1, tp2;
+ struct eq *eq;
+
+ float *data = read_raw(input_filename, &frames);
+
+ /* Set some data to 0 to test for denormals. */
+ for (i = frames / 10; i < frames; i++)
+ data[i] = 0.0;
+
+ /* Left eq chain */
+ eq = eq_new();
+ eq_append_biquad(eq, BQ_PEAKING, 380/NQ, 3, -10);
+ eq_append_biquad(eq, BQ_PEAKING, 720/NQ, 3, -12);
+ eq_append_biquad(eq, BQ_PEAKING, 1705/NQ, 3, -8);
+ eq_append_biquad(eq, BQ_HIGHPASS, 218/NQ, 0.7, -10.2);
+ eq_append_biquad(eq, BQ_PEAKING, 580/NQ, 6, -8);
+ eq_append_biquad(eq, BQ_HIGHSHELF, 8000/NQ, 3, 2);
+ clock_gettime(CLOCK_THREAD_CPUTIME_ID, &tp1);
+ process(eq, data, frames);
+ clock_gettime(CLOCK_THREAD_CPUTIME_ID, &tp2);
+ printf("processing takes %g seconds for %zu samples\n",
+ tp_diff(&tp2, &tp1), frames);
+ eq_free(eq);
+
+ /* Right eq chain */
+ eq = eq_new();
+ eq_append_biquad(eq, BQ_PEAKING, 450/NQ, 3, -12);
+ eq_append_biquad(eq, BQ_PEAKING, 721/NQ, 3, -12);
+ eq_append_biquad(eq, BQ_PEAKING, 1800/NQ, 8, -10.2);
+ eq_append_biquad(eq, BQ_PEAKING, 580/NQ, 6, -8);
+ eq_append_biquad(eq, BQ_HIGHPASS, 250/NQ, 0.6578, 0);
+ eq_append_biquad(eq, BQ_HIGHSHELF, 8000/NQ, 0, 2);
+ clock_gettime(CLOCK_THREAD_CPUTIME_ID, &tp1);
+ process(eq, data + frames, frames);
+ clock_gettime(CLOCK_THREAD_CPUTIME_ID, &tp2);
+ printf("processing takes %g seconds for %zu samples\n",
+ tp_diff(&tp2, &tp1), frames);
+ eq_free(eq);
+
+ write_raw(output_filename, data, frames);
+ free(data);
+}
+
+int main(int argc, char **argv)
+{
+ dsp_enable_flush_denormal_to_zero();
+ if (dsp_util_has_denormal())
+ printf("denormal still supported?\n");
+ else
+ printf("denormal disabled\n");
+ dsp_util_clear_fp_exceptions();
+
+ if (argc == 1)
+ test_ir();
+ else if (argc == 3)
+ test_file(argv[1], argv[2]);
+ else
+ printf("Usage: eq_test [input.raw output.raw]\n");
+
+ dsp_util_print_fp_exceptions();
+ return 0;
+}
diff --git a/cras/src/dsp/tests/plot_fftl.m b/cras/src/dsp/tests/plot_fftl.m
new file mode 100644
index 00000000..bffc0b8e
--- /dev/null
+++ b/cras/src/dsp/tests/plot_fftl.m
@@ -0,0 +1,40 @@
+% Copyright (c) 2012 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.
+%
+% This is an octave script.
+% It reads impulse response from "ir.dat" and plots frequency response.
+% Both x-axis and y-axis is in log scale.
+h=load("ir.dat");
+N=columns(h);
+K=rows(h)/2;
+NQ=44100/2;
+% This tries to match the labels in the audio tuning UI.
+xticks=[22050, 11025, 5513, 2756, 1378, 689, 345, 172, 86, 43, 21];
+xticklabels={"22050Hz", "11025Hz", "5513Hz", "2756Hz", "1378Hz", \
+"689Hz", "345Hz", "172Hz", "86Hz", "43Hz", "21Hz"};
+yticks=[18,12,6,0,-6,-12,-18,-24];
+yticklabels={"18dB","12dB","6dB","0dB","-6dB","-12dB","-18dB","-24dB"};
+xyrange=[21,22050,-24,18];
+xrange=[21,22050];
+
+for i=1:N
+ figure(i);
+ title('fftl');
+ fr = fft(h(:,i))(1:K);
+ subplot(2, 1, 1);
+ semilogx(NQ*(1:K)/K, 20*log10(abs(fr)));
+ xlabel('Frequency'), ylabel('Magnitude'), grid;
+ set (gca, "xtick", xticks);
+ set (gca, "xticklabel", xticklabels);
+ set (gca, "ytick", yticks);
+ set (gca, "yticklabel", yticklabels);
+ axis(xyrange);
+ subplot(2, 1, 2);
+ semilogx(NQ*(1:K)/K,180/pi*unwrap(angle(fr)));
+ xlabel('Frequency'), ylabel('Phase (degrees)'), grid;
+ set (gca, "xtick", xticks);
+ set (gca, "xticklabel", xticklabels);
+ axis(xrange);
+end
+pause
diff --git a/cras/src/dsp/tests/raw.c b/cras/src/dsp/tests/raw.c
new file mode 100644
index 00000000..65c1d92c
--- /dev/null
+++ b/cras/src/dsp/tests/raw.c
@@ -0,0 +1,93 @@
+/* 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 <stdio.h>
+#include <stdlib.h>
+#include <fcntl.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+float *read_raw(const char *filename, size_t *frames)
+{
+ struct stat st;
+ int16_t *buf;
+ size_t f, n;
+ int fd;
+ float *data;
+ int i;
+
+ if (stat(filename, &st) < 0) {
+ fprintf(stderr, "cannot stat file %s\n", filename);
+ return NULL;
+ }
+
+ fd = open(filename, O_RDONLY);
+ if (fd < 0) {
+ fprintf(stderr, "cannot open file %s\n", filename);
+ return NULL;
+ }
+
+ f = st.st_size / 4;
+ n = f * 4;
+ buf = (int16_t *)malloc(n);
+ if (read(fd, buf, n) != n) {
+ fprintf(stderr, "short read %zu\n", n);
+ free(buf);
+ return NULL;
+ }
+ close(fd);
+
+ /* deinterleave and convert to float */
+ data = (float *)malloc(sizeof(float) * f * 2);
+ for (i = 0; i < f; i++) {
+ data[i] = buf[2*i] / 32768.0f;
+ data[i + f] = buf[2*i+1] / 32768.0f;
+ }
+ free(buf);
+ *frames = f;
+ return data;
+}
+
+static int16_t f2s16(float f)
+{
+ int i;
+ f *= 32768;
+ i = (int)((f > 0) ? (f + 0.5f) : (f - 0.5f));
+ if (i < -32768)
+ i = -32768;
+ else if (i > 32767)
+ i = 32767;
+ return (int16_t)i;
+}
+
+int write_raw(const char *filename, float *input, size_t frames)
+{
+ int16_t *buf;
+ int rc = -1;
+ int n = frames * 4;
+ int i;
+
+ buf = (int16_t *)malloc(n);
+ for (i = 0; i < frames; i++) {
+ buf[2*i] = f2s16(input[i]);
+ buf[2*i+1] = f2s16(input[i + frames]);
+ }
+
+ int fd = open(filename, O_WRONLY | O_CREAT, 0644);
+ if (fd < 0) {
+ fprintf(stderr, "cannot open file %s\n", filename);
+ goto quit;
+ }
+ if (write(fd, buf, n) != n) {
+ fprintf(stderr, "short write file %s\n", filename);
+ goto quit;
+ }
+ rc = 0;
+quit:
+ close(fd);
+ free(buf);
+ return rc;
+}
diff --git a/cras/src/dsp/tests/raw.h b/cras/src/dsp/tests/raw.h
new file mode 100644
index 00000000..a0a30ab3
--- /dev/null
+++ b/cras/src/dsp/tests/raw.h
@@ -0,0 +1,46 @@
+/* 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 RAW_H_
+#define RAW_H_
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include <stddef.h>
+
+/* Reads a raw file to a float buffer.
+ * Args:
+ * filename - The name of the raw file.
+ * frames - Returns the number of frames read.
+ * Returns:
+ * The float buffer allocated by malloc(), or NULL if reading fails. The
+ * first half of the buffer contains left channel data, and the second half
+ * contains the right channel data.
+ * The raw file is assumed to have two channel 16 bit signed integer samples in
+ * native endian. The raw file can be created by:
+ * sox input.wav output.raw
+ * The raw file can be played by:
+ * play -r 44100 -s -b 16 -c 2 test.raw
+ */
+float *read_raw(const char *filename, size_t *frames);
+
+/* Writes a float buffer to a raw file.
+ * Args:
+ * filename - The name of the raw file.
+ * buf - The float buffer containing the samples.
+ * frames - The number of frames in the float buffer.
+ * Returns:
+ * 0 if success. -1 if writing fails.
+ * The format of the float buffer is the same as described in read_raw().
+ */
+void write_raw(const char *filename, float *buf, size_t frames);
+
+#ifdef __cplusplus
+} /* extern "C" */
+#endif
+
+#endif /* RAW_H_ */
diff --git a/cras/src/server/cras_dsp.c b/cras/src/server/cras_dsp.c
index c54bc8b4..5088e9d2 100644
--- a/cras/src/server/cras_dsp.c
+++ b/cras/src/server/cras_dsp.c
@@ -10,6 +10,7 @@
#include "cras_expr.h"
#include "cras_dsp_ini.h"
#include "cras_dsp_pipeline.h"
+#include "dsp_util.h"
#include "utlist.h"
/* We have a dsp_context for each pipeline. The context records the
@@ -277,6 +278,7 @@ static void *dsp_thread_function(void *arg)
void cras_dsp_init(const char *filename)
{
+ dsp_enable_flush_denormal_to_zero();
ini_filename = strdup(filename);
syslog_dumper = syslog_dumper_create(LOG_ERR);
pthread_create(&dsp_thread, NULL, dsp_thread_function, NULL);
diff --git a/cras/src/server/cras_dsp_mod_builtin.c b/cras/src/server/cras_dsp_mod_builtin.c
index 0736579c..1b3e6e25 100644
--- a/cras/src/server/cras_dsp_mod_builtin.c
+++ b/cras/src/server/cras_dsp_mod_builtin.c
@@ -4,38 +4,69 @@
*/
#include <stdlib.h>
-
#include "cras_dsp_module.h"
+#include "dsp_util.h"
+#include "eq.h"
-
-static int instantiate(struct dsp_module *module, unsigned long sample_rate)
+/*
+ * empty module functions (for source and sink)
+ */
+static int empty_instantiate(struct dsp_module *module,
+ unsigned long sample_rate)
{
return 0;
}
-static int instantiate_mix_stereo(struct dsp_module *module,
+static void empty_connect_port(struct dsp_module *module, unsigned long port,
+ float *data_location) {}
+
+static void empty_run(struct dsp_module *module, unsigned long sample_count) {}
+
+static void empty_deinstantiate(struct dsp_module *module) {}
+
+static void empty_free_module(struct dsp_module *module)
+{
+ free(module);
+}
+
+static int empty_get_properties(struct dsp_module *module) { return 0; }
+
+static void empty_dump(struct dsp_module *module, struct dumper *d)
+{
+ dumpf(d, "built-in module\n");
+}
+
+static void empty_init_module(struct dsp_module *module)
+{
+ module->instantiate = &empty_instantiate;
+ module->connect_port = &empty_connect_port;
+ module->run = &empty_run;
+ module->deinstantiate = &empty_deinstantiate;
+ module->free_module = &empty_free_module;
+ module->get_properties = &empty_get_properties;
+ module->dump = &empty_dump;
+}
+
+/*
+ * mix_stereo module functions
+ */
+static int mix_stereo_instantiate(struct dsp_module *module,
unsigned long sample_rate)
{
module->data = calloc(4, sizeof(float*));
-
return 0;
}
-static void connect_port(struct dsp_module *module, unsigned long port,
- float *data_location) {}
-
-static void connect_port_mix_stereo(struct dsp_module *module,
- unsigned long port, float *data_location)
+static void mix_stereo_connect_port(struct dsp_module *module,
+ unsigned long port, float *data_location)
{
float **ports;
ports = (float **)module->data;
ports[port] = data_location;
}
-static void run(struct dsp_module *module, unsigned long sample_count) {}
-
-static void run_mix_stereo(struct dsp_module *module,
- unsigned long sample_count)
+static void mix_stereo_run(struct dsp_module *module,
+ unsigned long sample_count)
{
size_t i;
float tmp;
@@ -48,24 +79,97 @@ static void run_mix_stereo(struct dsp_module *module,
}
}
-static void deinstantiate(struct dsp_module *module) {}
-
-static void deinstantiate_mix_stereo(struct dsp_module *module)
+static void mix_stereo_deinstantiate(struct dsp_module *module)
{
free(module->data);
}
-static int get_properties(struct dsp_module *module) { return 0; }
-static void dump(struct dsp_module *module, struct dumper *d)
+static void mix_stereo_init_module(struct dsp_module *module)
{
- dumpf(d, "built-in module\n");
+ module->instantiate = &mix_stereo_instantiate;
+ module->connect_port = &mix_stereo_connect_port;
+ module->run = &mix_stereo_run;
+ module->deinstantiate = &mix_stereo_deinstantiate;
+ module->free_module = &empty_free_module;
+ module->get_properties = &empty_get_properties;
+ module->dump = &empty_dump;
}
-static void free_module(struct dsp_module *module)
+/*
+ * eq module functions
+ */
+struct eq_data {
+ int sample_rate;
+ struct eq *eq; /* Initialized in the first call of eq_run() */
+
+ /* One port for input, one for output, and 4 parameters per eq */
+ float *ports[2 + MAX_BIQUADS_PER_EQ * 4];
+};
+
+static int eq_instantiate(struct dsp_module *module, unsigned long sample_rate)
{
- free(module);
+ struct eq_data *data;
+
+ module->data = calloc(1, sizeof(struct eq_data));
+ data = (struct eq_data *) module->data;
+ data->sample_rate = (int) sample_rate;
+ return 0;
+}
+
+static void eq_connect_port(struct dsp_module *module,
+ unsigned long port, float *data_location)
+{
+ struct eq_data *data = (struct eq_data *) module->data;
+ data->ports[port] = data_location;
}
+static void eq_run(struct dsp_module *module, unsigned long sample_count)
+{
+ struct eq_data *data = (struct eq_data *) module->data;
+ if (!data->eq) {
+ float nyquist = data->sample_rate / 2;
+ int i;
+
+ data->eq = eq_new();
+ for (i = 2; i < 2 + MAX_BIQUADS_PER_EQ * 4; i += 4) {
+ if (!data->ports[i])
+ break;
+ int type = (int) *data->ports[i];
+ float freq = *data->ports[i+1];
+ float Q = *data->ports[i+2];
+ float gain = *data->ports[i+3];
+ eq_append_biquad(data->eq, type, freq / nyquist, Q,
+ gain);
+ }
+ }
+ if (data->ports[0] != data->ports[1])
+ memcpy(data->ports[1], data->ports[0],
+ sizeof(float) * sample_count);
+ eq_process(data->eq, data->ports[1], (int) sample_count);
+}
+
+static void eq_deinstantiate(struct dsp_module *module)
+{
+ struct eq_data *data = (struct eq_data *) module->data;
+ if (data->eq)
+ eq_free(data->eq);
+ free(data);
+}
+
+static void eq_init_module(struct dsp_module *module)
+{
+ module->instantiate = &eq_instantiate;
+ module->connect_port = &eq_connect_port;
+ module->run = &eq_run;
+ module->deinstantiate = &eq_deinstantiate;
+ module->free_module = &empty_free_module;
+ module->get_properties = &empty_get_properties;
+ module->dump = &empty_dump;
+}
+
+/*
+ * builtin module dispatcher
+ */
struct dsp_module *cras_dsp_module_load_builtin(struct plugin *plugin)
{
struct dsp_module *module;
@@ -73,19 +177,13 @@ struct dsp_module *cras_dsp_module_load_builtin(struct plugin *plugin)
return NULL;
module = calloc(1, sizeof(struct dsp_module));
- module->instantiate = &instantiate;
- module->connect_port = &connect_port;
- module->run = &run;
- module->deinstantiate = &deinstantiate;
- module->free_module = &free_module;
- module->get_properties = &get_properties;
- module->dump = &dump;
if (strcmp(plugin->label, "mix_stereo") == 0) {
- module->instantiate = &instantiate_mix_stereo;
- module->run = &run_mix_stereo;
- module->connect_port = &connect_port_mix_stereo;
- module->deinstantiate = &deinstantiate_mix_stereo;
+ mix_stereo_init_module(module);
+ } else if (strcmp(plugin->label, "eq") == 0) {
+ eq_init_module(module);
+ } else {
+ empty_init_module(module);
}
return module;
diff --git a/cras/src/tests/dsp_core_unittest.cc b/cras/src/tests/dsp_core_unittest.cc
new file mode 100644
index 00000000..feac4246
--- /dev/null
+++ b/cras/src/tests/dsp_core_unittest.cc
@@ -0,0 +1,98 @@
+// 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 <gtest/gtest.h>
+#include <math.h>
+#include "dsp_util.h"
+#include "eq.h"
+
+namespace {
+
+/* Adds amplitude * sin(pi*freq*i + offset) to the data array. */
+static void add_sine(float *data, size_t len, float freq, float offset,
+ float amplitude)
+{
+ for (size_t i = 0; i < len; i++)
+ data[i] += amplitude * sinf((float)M_PI*freq*i + offset);
+}
+
+/* Calculates the magnitude at normalized frequency f. The output is
+ * the result of DFT, multiplied by 2/len. */
+static float magnitude_at(float *data, size_t len, float f)
+{
+ double re = 0, im = 0;
+ f *= (float)M_PI;
+ for (size_t i = 0; i < len; i++) {
+ re += data[i] * cos(i * f);
+ im += data[i] * sin(i * f);
+ }
+ return sqrt(re * re + im * im) * (2.0 / len);
+}
+
+TEST(EqTest, All) {
+ struct eq *eq;
+ size_t len = 44100;
+ float NQ = len / 2;
+ float f_low = 10 / NQ;
+ float f_mid = 100 / NQ;
+ float f_high = 1000 / NQ;
+ float *data = (float *)malloc(sizeof(float) * len);
+
+ dsp_enable_flush_denormal_to_zero();
+ /* low pass */
+ memset(data, 0, sizeof(float) * len);
+ add_sine(data, len, f_low, 0, 1); // 10Hz sine, magnitude = 1
+ EXPECT_FLOAT_EQ(1, magnitude_at(data, len, f_low));
+ add_sine(data, len, f_high, 0, 1); // 1000Hz sine, magnitude = 1
+ EXPECT_FLOAT_EQ(1, magnitude_at(data, len, f_low));
+ EXPECT_FLOAT_EQ(1, magnitude_at(data, len, f_high));
+
+ eq = eq_new();
+ EXPECT_EQ(0, eq_append_biquad(eq, BQ_LOWPASS, f_mid, 0, 0));
+ eq_process(eq, data, len);
+ EXPECT_NEAR(1, magnitude_at(data, len, f_low), 0.01);
+ EXPECT_NEAR(0, magnitude_at(data, len, f_high), 0.01);
+ eq_free(eq);
+
+ /* high pass */
+ memset(data, 0, sizeof(float) * len);
+ add_sine(data, len, f_low, 0, 1);
+ add_sine(data, len, f_high, 0, 1);
+
+ eq = eq_new();
+ EXPECT_EQ(0, eq_append_biquad(eq, BQ_HIGHPASS, f_mid, 0, 0));
+ eq_process(eq, data, len);
+ EXPECT_NEAR(0, magnitude_at(data, len, f_low), 0.01);
+ EXPECT_NEAR(1, magnitude_at(data, len, f_high), 0.01);
+ eq_free(eq);
+
+ /* peaking */
+ memset(data, 0, sizeof(float) * len);
+ add_sine(data, len, f_low, 0, 1);
+ add_sine(data, len, f_high, 0, 1);
+
+ eq = eq_new();
+ EXPECT_EQ(0, eq_append_biquad(eq, BQ_PEAKING, f_high, 5, 6)); // Q=5, 6dB gain
+ eq_process(eq, data, len);
+ EXPECT_NEAR(1, magnitude_at(data, len, f_low), 0.01);
+ EXPECT_NEAR(2, magnitude_at(data, len, f_high), 0.01);
+ eq_free(eq);
+
+ free(data);
+
+ /* Too many biquads */
+ eq = eq_new();
+ for (int i = 0; i < MAX_BIQUADS_PER_EQ; i++) {
+ EXPECT_EQ(0, eq_append_biquad(eq, BQ_PEAKING, f_high, 5, 6));
+ }
+ EXPECT_EQ(-1, eq_append_biquad(eq, BQ_PEAKING, f_high, 5, 6));
+ eq_free(eq);
+}
+
+} // namespace
+
+int main(int argc, char **argv) {
+ ::testing::InitGoogleTest(&argc, argv);
+ return RUN_ALL_TESTS();
+}
diff --git a/cras/src/tests/dsp_unittest.cc b/cras/src/tests/dsp_unittest.cc
index 3abec8ff..32e0a1ed 100644
--- a/cras/src/tests/dsp_unittest.cc
+++ b/cras/src/tests/dsp_unittest.cc
@@ -5,6 +5,7 @@
#include <gtest/gtest.h>
#include "cras_dsp.h"
+#include "cras_dsp_module.h"
#define FILENAME_TEMPLATE "DspTest.XXXXXX"
@@ -104,8 +105,55 @@ TEST_F(DspTestSuite, Simple) {
cras_dsp_stop();
}
+static int empty_instantiate(struct dsp_module *module,
+ unsigned long sample_rate)
+{
+ return 0;
+}
+
+static void empty_connect_port(struct dsp_module *module, unsigned long port,
+ float *data_location) {}
+
+static void empty_run(struct dsp_module *module, unsigned long sample_count) {}
+
+static void empty_deinstantiate(struct dsp_module *module) {}
+
+static void empty_free_module(struct dsp_module *module)
+{
+ free(module);
+}
+
+static int empty_get_properties(struct dsp_module *module) { return 0; }
+
+static void empty_dump(struct dsp_module *module, struct dumper *d)
+{
+ dumpf(d, "built-in module\n");
+}
+
+static void empty_init_module(struct dsp_module *module)
+{
+ module->instantiate = &empty_instantiate;
+ module->connect_port = &empty_connect_port;
+ module->run = &empty_run;
+ module->deinstantiate = &empty_deinstantiate;
+ module->free_module = &empty_free_module;
+ module->get_properties = &empty_get_properties;
+ module->dump = &empty_dump;
+}
+
} // namespace
+extern "C"
+{
+struct dsp_module *cras_dsp_module_load_builtin(struct plugin *plugin)
+{
+ struct dsp_module *module;
+ module = (struct dsp_module *)calloc(1, sizeof(struct dsp_module));
+ empty_init_module(module);
+ return module;
+}
+}
+
int main(int argc, char **argv) {
::testing::InitGoogleTest(&argc, argv);
return RUN_ALL_TESTS();