summaryrefslogtreecommitdiffstats
path: root/src/3rdparty/resonance-audio/resonance_audio/dsp/biquad_filter_test.cc
diff options
context:
space:
mode:
Diffstat (limited to 'src/3rdparty/resonance-audio/resonance_audio/dsp/biquad_filter_test.cc')
-rw-r--r--src/3rdparty/resonance-audio/resonance_audio/dsp/biquad_filter_test.cc303
1 files changed, 303 insertions, 0 deletions
diff --git a/src/3rdparty/resonance-audio/resonance_audio/dsp/biquad_filter_test.cc b/src/3rdparty/resonance-audio/resonance_audio/dsp/biquad_filter_test.cc
new file mode 100644
index 000000000..c8ac6de78
--- /dev/null
+++ b/src/3rdparty/resonance-audio/resonance_audio/dsp/biquad_filter_test.cc
@@ -0,0 +1,303 @@
+/*
+Copyright 2018 Google Inc. All Rights Reserved.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS-IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+#include "dsp/biquad_filter.h"
+
+#include <algorithm>
+#include <cmath>
+#include <numeric>
+
+#include "third_party/googletest/googletest/include/gtest/gtest.h"
+#include "base/constants_and_types.h"
+
+namespace vraudio {
+
+namespace {
+
+const size_t kTestInputDataSize = 16;
+
+const float kTestFilterCoefficients[] = {1.0f, 0.0f, 0.0, 1.0f, 0.1f, 0.1f};
+
+const size_t kIdealSamplesToIterate = 256;
+
+// For use with std::transform in the StabilityTest.
+float operator_abs(float f) { return std::abs(f); }
+
+} // namespace
+
+// Tests that the filtering of a mono signal produces the correct output.
+TEST(BiquadFilterTest, FilterTest) {
+ const size_t kNumIterations = 1;
+
+ const BiquadCoefficients kCoefficients(
+ kTestFilterCoefficients[0], kTestFilterCoefficients[1],
+ kTestFilterCoefficients[2], kTestFilterCoefficients[3],
+ kTestFilterCoefficients[4], kTestFilterCoefficients[5]);
+
+ AudioBuffer input(kNumMonoChannels, kTestInputDataSize);
+ AudioBuffer output(kNumMonoChannels, kTestInputDataSize);
+ // Set the input AudioBuffer to a Kronecker delta.
+ input.Clear();
+ input[0][0] = 1.0f;
+
+ // This vector will accumulate the output from the filter over time (Only
+ // works with mono channel).
+ std::vector<float> output_accumulator;
+ output_accumulator.reserve(kNumIterations * kTestInputDataSize);
+
+ // Create a biquad filter initialized with transfer function coefficients.
+ BiquadFilter biquad(kCoefficients, kTestInputDataSize);
+
+ // Perform filtering.
+ for (size_t i = 0; i < kNumIterations; ++i) {
+ biquad.Filter(input[0], &output[0]);
+ output_accumulator.insert(output_accumulator.end(), output[0].begin(),
+ output[0].end());
+ }
+
+ // Since the denominator of the biquad coefficients is [1 0 0] we can expect
+ // the impulse response to be equal to the numerator.
+ for (size_t i = 0; i < output_accumulator.size(); ++i) {
+ if (i < 3U) {
+ EXPECT_NEAR(kTestFilterCoefficients[3 + i], output_accumulator[i],
+ kEpsilonFloat);
+ } else {
+ EXPECT_EQ(0.0f, output_accumulator[i]);
+ }
+ }
+}
+
+// Tests that the filtering of a mono signal produces the correct output.
+TEST(BiquadFilterTest, InplaceFilterTest) {
+ const size_t kNumIterations = 1;
+
+ const BiquadCoefficients kCoefficients(
+ kTestFilterCoefficients[0], kTestFilterCoefficients[1],
+ kTestFilterCoefficients[2], kTestFilterCoefficients[3],
+ kTestFilterCoefficients[4], kTestFilterCoefficients[5]);
+
+ AudioBuffer input(kNumMonoChannels, kTestInputDataSize);
+ // Set the input AudioBuffer to a Kronecker delta.
+ input.Clear();
+ input[0][0] = 1.0f;
+
+ // This vector will accumulate the output from the filter over time (Only
+ // works with mono channel).
+ std::vector<float> output_accumulator;
+ output_accumulator.reserve(kTestInputDataSize);
+
+ // Create a biquad filter initialized with transfer function coefficients.
+ BiquadFilter biquad(kCoefficients, kTestInputDataSize);
+
+ // Perform inplace filtering of |input| vector.
+ for (size_t i = 0; i < kNumIterations; ++i) {
+ biquad.Filter(input[0], &input[0]);
+ output_accumulator.insert(output_accumulator.end(), input[0].begin(),
+ input[0].end());
+ }
+
+ // Since the denominator of the biquad coefficients is [1 0 0] we can expect
+ // the impulse response to be equal to the numerator.
+ for (size_t i = 0; i < output_accumulator.size(); ++i) {
+ if (i < 3) {
+ EXPECT_NEAR(kTestFilterCoefficients[3 + i], output_accumulator[i],
+ kEpsilonFloat);
+ } else {
+ EXPECT_EQ(0.0f, output_accumulator[i]);
+ }
+ }
+}
+
+// Tests whether the interpolation stops after kIdealSamplesToIterate samples,
+// given a long enough buffer.
+TEST(BiquadFilterTest, InterpolationStopsTest) {
+ const size_t kFramesPerBuffer = 512;
+
+ std::vector<std::vector<float>> planar_input_data(
+ kNumMonoChannels, std::vector<float>(kFramesPerBuffer));
+ planar_input_data[0][0] = 1.0f;
+ planar_input_data[0][256] = 1.0f;
+
+ AudioBuffer input_planar(kNumMonoChannels, kFramesPerBuffer);
+ AudioBuffer output_planar(kNumMonoChannels, kFramesPerBuffer);
+ input_planar = planar_input_data;
+
+ // Instantiate Biquad for planar data. The impulse response of the default is:
+ // 1, 0, 0, ......
+ BiquadFilter biquad(BiquadCoefficients(), kFramesPerBuffer);
+
+ // Coefficients we wish to interpolate to. The impulse response of this filter
+ // is: 1, 0, 1, ......
+ const BiquadCoefficients kNextCoefficients = {1.0f, 0.0f, 0.0f,
+ 1.0f, 0.0f, 1.0f};
+
+ biquad.InterpolateToCoefficients(kNextCoefficients);
+ biquad.Filter(input_planar[0], &output_planar[0]);
+
+ // Based on the known impulse responses we can see that if elements 256, 257
+ // and 258 have the values 1, 0, 1, then only the new filter coefficients are
+ // contributing and we have stopped crossfading. Note: The value of 256 here
+ // comes from kIdealSamplesToIterate defined in biquad_filter.cc
+ EXPECT_EQ(1.0f, output_planar[0][kIdealSamplesToIterate]);
+ EXPECT_EQ(0.0f, output_planar[0][kIdealSamplesToIterate + 1]);
+ EXPECT_EQ(1.0f, output_planar[0][kIdealSamplesToIterate + 2]);
+}
+
+// Tests whether the |BiquadFilter| remains stable when its z-domain poles lie
+// very close to the unit circle.
+TEST(BiquadFilterTest, StabilityTest) {
+ static const size_t kBufferSize = 1024;
+ const size_t kIterations = 200;
+ // The following filter was designed in MATLAB with a very slow decay rate.
+ // (There are both poles and zeros with magnitude 0.999). Even 200'000 samples
+ // after the initial impulse the response will have only died away, in an
+ // oscillating manner, to ~1/700 of its peak value.
+ static const BiquadCoefficients kHighQCoefficients(
+ 1.0f, -0.907804951302441f, 0.999869108872718f, 15279.8745150008f, 0.0f,
+ -15279.8745150008f);
+ BiquadFilter biquad(kHighQCoefficients, kBufferSize);
+
+ AudioBuffer input(kNumMonoChannels, kBufferSize);
+ AudioBuffer output(kNumMonoChannels, kBufferSize);
+ // Set the input AudioBuffer to a Kronecker delta.
+ input.Clear();
+ input[0][0] = 1.0f;
+
+ // This vector will accumulate the output from the filter over time (Only
+ // works with mono channel).
+ std::vector<float> output_accumulator;
+ output_accumulator.reserve(kBufferSize * kIterations);
+
+ // Filter once with the Kronecker delta.
+ biquad.Filter(input[0], &output[0]);
+ output_accumulator.insert(output_accumulator.end(), output[0].begin(),
+ output[0].end());
+
+ // Perform filtering with all zero input.
+ input[0][0] = 0.0f;
+ for (size_t i = 1; i < kIterations; ++i) {
+ biquad.Filter(input[0], &output[0]);
+ output_accumulator.insert(output_accumulator.end(), output[0].begin(),
+ output[0].end());
+ }
+
+ // Test that the signal is decaying over time (i.e. filter is stable).
+ for (size_t i = 0; i < output_accumulator.size() - kBufferSize;
+ i += kBufferSize) {
+ std::vector<float> absolute_first_half(kBufferSize / 2);
+ std::transform(output_accumulator.begin() + i,
+ output_accumulator.begin() + i + kBufferSize / 2,
+ absolute_first_half.begin(), operator_abs);
+ std::vector<float> absolute_second_half(kBufferSize / 2);
+ std::transform(output_accumulator.begin() + 1 + i + kBufferSize / 2,
+ output_accumulator.begin() + i + kBufferSize,
+ absolute_second_half.begin(), operator_abs);
+ const float sum_first_half = std::accumulate(
+ absolute_first_half.begin(), absolute_first_half.end(), 0.0f);
+ const float sum_second_half = std::accumulate(
+ absolute_second_half.begin(), absolute_second_half.end(), 0.0f);
+ EXPECT_LT(sum_second_half, sum_first_half);
+ }
+}
+
+class BiquadFilterInterpolateTest : public ::testing::Test {
+ protected:
+ BiquadFilterInterpolateTest() {}
+ // Virtual methods from ::testing::Test
+ ~BiquadFilterInterpolateTest() override {}
+ void SetUp() override {}
+ void TearDown() override {}
+
+ // Returns true if |filter|'s internal coefficients are equal to those of
+ // |expected_coefficients| after scaling by a0.
+ bool TestInternalCoefficients(
+ BiquadFilter* filter, const BiquadCoefficients& expected_coefficients) {
+ bool return_value = true;
+ return_value &=
+ expected_coefficients.a[0] - filter->coefficients_.a[0] < kEpsilonFloat;
+
+ // From this point we must take account of the scaling by 1/a0.
+ const float a0 = filter->coefficients_.a[0];
+ return_value &=
+ expected_coefficients.b[0] - filter->coefficients_.b[0] * a0 <
+ kEpsilonFloat;
+ for (int i = 1; i < 3; ++i) {
+ return_value &=
+ expected_coefficients.b[i] - filter->coefficients_.b[i] * a0 <
+ kEpsilonFloat;
+ return_value &=
+ expected_coefficients.a[i] - filter->coefficients_.a[i] * a0 <
+ kEpsilonFloat;
+ }
+ return return_value;
+ }
+};
+
+// Tests whether updating the filter's coefficients with the
+// InterpolateToCoefficients reaches the correct coefficients after filtering a
+// block of data and whether the samples_to_iterate_over_ value is set
+// correctly.
+TEST_F(BiquadFilterInterpolateTest, InterpolatedUpdateTest) {
+ const size_t kFramesPerBuffer = 8;
+
+ const std::vector<std::vector<float>> kPlanarInputData = {
+ {1.0f, 2.0f, 3.0f, 4.0f, 5.0f, 6.0f, 7.0f, 8.0f}};
+
+ const std::vector<float> kExpectedOutputAfterChange = {
+ 8.0f, 10.0f, 4.0f, 6.0f, 8.0f, 10.0f, 12.0f, 14.0f};
+
+ // Default constructor sets a = {1.0, 0.0, 0.0} and b = {1.0, 0.0, 0.0}.
+ const BiquadCoefficients kCoefficients;
+
+ // Create input and output AudioBuffers.
+ AudioBuffer input_planar(kNumMonoChannels, kFramesPerBuffer);
+ AudioBuffer output_planar(kNumMonoChannels, kFramesPerBuffer);
+ input_planar = kPlanarInputData;
+
+ // Instantiate Biquad for planar data.
+ BiquadFilter biquad(kCoefficients, kFramesPerBuffer);
+
+ // Perform filtering on kNumMonoChannels interleaved.
+ biquad.Filter(input_planar[0], &output_planar[0]);
+
+ for (size_t i = 0; i < kFramesPerBuffer; ++i) {
+ EXPECT_NEAR(output_planar[0][i], kPlanarInputData[0][i], kEpsilonFloat);
+ }
+
+ // Coefficients we wish to interpolate to.
+ static const BiquadCoefficients kNextCoefficients = {1.0f, 0.0f, 0.0f,
+ 1.0f, 0.0f, 1.0f};
+
+ biquad.InterpolateToCoefficients(kNextCoefficients);
+
+ // Filter once to transition and once to flush out the state.
+ biquad.Filter(input_planar[0], &output_planar[0]);
+ biquad.Filter(input_planar[0], &output_planar[0]);
+
+ // Now the output should be just from the new state.
+ biquad.Filter(input_planar[0], &output_planar[0]);
+
+ // Now check that we transitioned properly, i.e. the output is as expected.
+ for (size_t i = 0; i < kFramesPerBuffer; ++i) {
+ EXPECT_NEAR(output_planar[0][i], kExpectedOutputAfterChange[i],
+ kEpsilonFloat);
+ }
+
+ // Now check that we have the new coefficients.
+ EXPECT_TRUE(TestInternalCoefficients(&biquad, kNextCoefficients));
+}
+
+} // namespace vraudio