react-native-audio-api 0.10.0-nightly-2d11b56-20251021 → 0.10.0-nightly-75aa9d0-20251022

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -67,7 +67,7 @@ JSI_HOST_FUNCTION_IMPL(BiquadFilterNodeHostObject, getFrequencyResponse) {
67
67
  .getArrayBuffer(runtime);
68
68
  auto frequencyArray =
69
69
  reinterpret_cast<float *>(arrayBufferFrequency.data(runtime));
70
- auto length = static_cast<int>(arrayBufferFrequency.size(runtime));
70
+ auto length = static_cast<size_t>(arrayBufferFrequency.size(runtime));
71
71
 
72
72
  auto arrayBufferMag = args[1]
73
73
  .getObject(runtime)
@@ -1,3 +1,31 @@
1
+ /*
2
+ * Copyright (C) 2010 Google Inc. All rights reserved.
3
+ *
4
+ * Redistribution and use in source and binary forms, with or without
5
+ * modification, are permitted provided that the following conditions
6
+ * are met:
7
+ *
8
+ * 1. Redistributions of source code must retain the above copyright
9
+ * notice, this list of conditions and the following disclaimer.
10
+ * 2. Redistributions in binary form must reproduce the above copyright
11
+ * notice, this list of conditions and the following disclaimer in the
12
+ * documentation and/or other materials provided with the distribution.
13
+ * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of
14
+ * its contributors may be used to endorse or promote products derived
15
+ * from this software without specific prior written permission.
16
+ *
17
+ * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
18
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
19
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
20
+ * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
21
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
22
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
23
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
24
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
26
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27
+ */
28
+
1
29
  #include <audioapi/core/BaseAudioContext.h>
2
30
  #include <audioapi/core/effects/BiquadFilterNode.h>
3
31
  #include <audioapi/utils/AudioArray.h>
@@ -13,14 +41,14 @@ BiquadFilterNode::BiquadFilterNode(BaseAudioContext *context)
13
41
  frequencyParam_ = std::make_shared<AudioParam>(
14
42
  350.0, 0.0f, context->getNyquistFrequency(), context);
15
43
  detuneParam_ = std::make_shared<AudioParam>(
16
- 0.0,
44
+ 0.0f,
17
45
  -1200 * LOG2_MOST_POSITIVE_SINGLE_FLOAT,
18
46
  1200 * LOG2_MOST_POSITIVE_SINGLE_FLOAT,
19
47
  context);
20
48
  QParam_ = std::make_shared<AudioParam>(
21
- 1.0, MOST_NEGATIVE_SINGLE_FLOAT, MOST_POSITIVE_SINGLE_FLOAT, context);
49
+ 1.0f, MOST_NEGATIVE_SINGLE_FLOAT, MOST_POSITIVE_SINGLE_FLOAT, context);
22
50
  gainParam_ = std::make_shared<AudioParam>(
23
- 0.0,
51
+ 0.0f,
24
52
  MOST_NEGATIVE_SINGLE_FLOAT,
25
53
  40 * LOG10_MOST_POSITIVE_SINGLE_FLOAT,
26
54
  context);
@@ -76,27 +104,35 @@ void BiquadFilterNode::getFrequencyResponse(
76
104
  const float *frequencyArray,
77
105
  float *magResponseOutput,
78
106
  float *phaseResponseOutput,
79
- const int length) {
107
+ const size_t length) {
108
+ #ifndef AUDIO_API_TEST_SUITE
80
109
  applyFilter();
110
+ #endif
81
111
 
82
- // Local copies for micro-optimization
83
- float b0 = b0_;
84
- float b1 = b1_;
85
- float b2 = b2_;
86
- float a1 = a1_;
87
- float a2 = a2_;
112
+ // Use double precision for later calculations
113
+ double b0 = static_cast<double>(b0_);
114
+ double b1 = static_cast<double>(b1_);
115
+ double b2 = static_cast<double>(b2_);
116
+ double a1 = static_cast<double>(a1_);
117
+ double a2 = static_cast<double>(a2_);
118
+
119
+ float nyquist = context_->getNyquistFrequency();
88
120
 
89
121
  for (size_t i = 0; i < length; i++) {
90
- if (frequencyArray[i] < 0.0 || frequencyArray[i] > 1.0) {
122
+ // Convert from frequency in Hz to normalized frequency [0, 1]
123
+ float normalizedFreq = frequencyArray[i] / nyquist;
124
+
125
+ if (normalizedFreq < 0.0f || normalizedFreq > 1.0f) {
126
+ // Out-of-bounds frequencies should return NaN.
91
127
  magResponseOutput[i] = std::nanf("");
92
128
  phaseResponseOutput[i] = std::nanf("");
93
129
  continue;
94
130
  }
95
131
 
96
- auto omega = -PI * frequencyArray[i] / context_->getNyquistFrequency();
97
- auto z = std::complex<float>(cos(omega), sin(omega));
132
+ double omega = -PI * normalizedFreq;
133
+ auto z = std::complex<double>(std::cos(omega), std::sin(omega));
98
134
  auto response = (b0 + (b1 + b2 * z) * z) /
99
- (std::complex<float>(1, 0) + (a1 + a2 * z) * z);
135
+ (std::complex<double>(1, 0) + (a1 + a2 * z) * z);
100
136
  magResponseOutput[i] = static_cast<float>(std::abs(response));
101
137
  phaseResponseOutput[i] =
102
138
  static_cast<float>(atan2(imag(response), real(response)));
@@ -120,17 +156,16 @@ void BiquadFilterNode::setNormalizedCoefficients(
120
156
 
121
157
  void BiquadFilterNode::setLowpassCoefficients(float frequency, float Q) {
122
158
  // Limit frequency to [0, 1] range
123
- if (frequency >= 1.0) {
159
+ if (frequency >= 1.0f) {
124
160
  setNormalizedCoefficients(1.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f);
125
161
  return;
126
162
  }
127
163
 
128
- if (frequency <= 0.0) {
164
+ if (frequency <= 0.0f) {
129
165
  setNormalizedCoefficients(0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f);
130
166
  return;
131
167
  }
132
168
 
133
- Q = std::max(0.0f, Q);
134
169
  float g = std::pow(10.0f, 0.05f * Q);
135
170
 
136
171
  float theta = PI * frequency;
@@ -143,16 +178,15 @@ void BiquadFilterNode::setLowpassCoefficients(float frequency, float Q) {
143
178
  }
144
179
 
145
180
  void BiquadFilterNode::setHighpassCoefficients(float frequency, float Q) {
146
- if (frequency >= 1.0) {
181
+ if (frequency >= 1.0f) {
147
182
  setNormalizedCoefficients(0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f);
148
183
  return;
149
184
  }
150
- if (frequency <= 0.0) {
185
+ if (frequency <= 0.0f) {
151
186
  setNormalizedCoefficients(1.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f);
152
187
  return;
153
188
  }
154
189
 
155
- Q = std::max(0.0f, Q);
156
190
  float g = std::pow(10.0f, 0.05f * Q);
157
191
 
158
192
  float theta = PI * frequency;
@@ -166,13 +200,13 @@ void BiquadFilterNode::setHighpassCoefficients(float frequency, float Q) {
166
200
 
167
201
  void BiquadFilterNode::setBandpassCoefficients(float frequency, float Q) {
168
202
  // Limit frequency to [0, 1] range
169
- if (frequency <= 0.0 || frequency >= 1.0) {
203
+ if (frequency <= 0.0f || frequency >= 1.0f) {
170
204
  setNormalizedCoefficients(0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f);
171
205
  return;
172
206
  }
173
207
 
174
208
  // Limit Q to positive values
175
- if (Q <= 0.0) {
209
+ if (Q <= 0.0f) {
176
210
  setNormalizedCoefficients(1.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f);
177
211
  return;
178
212
  }
@@ -188,12 +222,12 @@ void BiquadFilterNode::setBandpassCoefficients(float frequency, float Q) {
188
222
  void BiquadFilterNode::setLowshelfCoefficients(float frequency, float gain) {
189
223
  float A = std::pow(10.0f, gain / 40.0f);
190
224
 
191
- if (frequency >= 1.0) {
225
+ if (frequency >= 1.0f) {
192
226
  setNormalizedCoefficients(A * A, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f);
193
227
  return;
194
228
  }
195
229
 
196
- if (frequency <= 0.0) {
230
+ if (frequency <= 0.0f) {
197
231
  setNormalizedCoefficients(1.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f);
198
232
  return;
199
233
  }
@@ -215,12 +249,12 @@ void BiquadFilterNode::setLowshelfCoefficients(float frequency, float gain) {
215
249
  void BiquadFilterNode::setHighshelfCoefficients(float frequency, float gain) {
216
250
  float A = std::pow(10.0f, gain / 40.0f);
217
251
 
218
- if (frequency >= 1.0) {
252
+ if (frequency >= 1.0f) {
219
253
  setNormalizedCoefficients(1.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f);
220
254
  return;
221
255
  }
222
256
 
223
- if (frequency <= 0.0) {
257
+ if (frequency <= 0.0f) {
224
258
  setNormalizedCoefficients(A * A, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f);
225
259
  return;
226
260
  }
@@ -247,12 +281,12 @@ void BiquadFilterNode::setPeakingCoefficients(
247
281
  float gain) {
248
282
  float A = std::pow(10.0f, gain / 40.0f);
249
283
 
250
- if (frequency <= 0.0 || frequency >= 1.0) {
284
+ if (frequency <= 0.0f || frequency >= 1.0f) {
251
285
  setNormalizedCoefficients(1.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f);
252
286
  return;
253
287
  }
254
288
 
255
- if (Q <= 0.0) {
289
+ if (Q <= 0.0f) {
256
290
  setNormalizedCoefficients(A * A, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f);
257
291
  return;
258
292
  }
@@ -271,12 +305,12 @@ void BiquadFilterNode::setPeakingCoefficients(
271
305
  }
272
306
 
273
307
  void BiquadFilterNode::setNotchCoefficients(float frequency, float Q) {
274
- if (frequency <= 0.0 || frequency >= 1.0) {
308
+ if (frequency <= 0.0f || frequency >= 1.0f) {
275
309
  setNormalizedCoefficients(1.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f);
276
310
  return;
277
311
  }
278
312
 
279
- if (Q <= 0.0) {
313
+ if (Q <= 0.0f) {
280
314
  setNormalizedCoefficients(0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f);
281
315
  return;
282
316
  }
@@ -290,12 +324,12 @@ void BiquadFilterNode::setNotchCoefficients(float frequency, float Q) {
290
324
  }
291
325
 
292
326
  void BiquadFilterNode::setAllpassCoefficients(float frequency, float Q) {
293
- if (frequency <= 0.0 || frequency >= 1.0) {
327
+ if (frequency <= 0.0f || frequency >= 1.0f) {
294
328
  setNormalizedCoefficients(1.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f);
295
329
  return;
296
330
  }
297
331
 
298
- if (Q <= 0.0) {
332
+ if (Q <= 0.0f) {
299
333
  setNormalizedCoefficients(-1.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f);
300
334
  return;
301
335
  }
@@ -318,6 +352,9 @@ void BiquadFilterNode::applyFilter() {
318
352
  auto Q = QParam_->processKRateParam(RENDER_QUANTUM_SIZE, currentTime);
319
353
  auto gain = gainParam_->processKRateParam(RENDER_QUANTUM_SIZE, currentTime);
320
354
 
355
+ // NyquistFrequency is half of the sample rate.
356
+ // Normalized frequency is therefore:
357
+ // frequency / (sampleRate / 2) = (2 * frequency) / sampleRate
321
358
  float normalizedFrequency = frequency / context_->getNyquistFrequency();
322
359
  if (detune != 0.0f) {
323
360
  normalizedFrequency *= std::pow(2.0f, detune / 1200.0f);
@@ -1,8 +1,39 @@
1
+ /*
2
+ * Copyright (C) 2010 Google Inc. All rights reserved.
3
+ *
4
+ * Redistribution and use in source and binary forms, with or without
5
+ * modification, are permitted provided that the following conditions
6
+ * are met:
7
+ *
8
+ * 1. Redistributions of source code must retain the above copyright
9
+ * notice, this list of conditions and the following disclaimer.
10
+ * 2. Redistributions in binary form must reproduce the above copyright
11
+ * notice, this list of conditions and the following disclaimer in the
12
+ * documentation and/or other materials provided with the distribution.
13
+ * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of
14
+ * its contributors may be used to endorse or promote products derived
15
+ * from this software without specific prior written permission.
16
+ *
17
+ * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
18
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
19
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
20
+ * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
21
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
22
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
23
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
24
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
26
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27
+ */
28
+
1
29
  #pragma once
2
30
 
3
31
  #include <audioapi/core/AudioNode.h>
4
32
  #include <audioapi/core/AudioParam.h>
5
33
  #include <audioapi/core/types/BiquadFilterType.h>
34
+ #ifdef AUDIO_API_TEST_SUITE
35
+ #include <gtest/gtest_prod.h>
36
+ #endif
6
37
 
7
38
  #include <algorithm>
8
39
  #include <cmath>
@@ -17,6 +48,11 @@ namespace audioapi {
17
48
  class AudioBus;
18
49
 
19
50
  class BiquadFilterNode : public AudioNode {
51
+ #ifdef AUDIO_API_TEST_SUITE
52
+ friend class BiquadFilterTest;
53
+ FRIEND_TEST(BiquadFilterTest, GetFrequencyResponse);
54
+ #endif
55
+
20
56
  public:
21
57
  explicit BiquadFilterNode(BaseAudioContext *context);
22
58
 
@@ -30,7 +66,7 @@ class BiquadFilterNode : public AudioNode {
30
66
  const float *frequencyArray,
31
67
  float *magResponseOutput,
32
68
  float *phaseResponseOutput,
33
- int length);
69
+ size_t length);
34
70
 
35
71
  protected:
36
72
  std::shared_ptr<AudioBus> processNode(
@@ -1,5 +1,6 @@
1
1
  #pragma once
2
2
 
3
+ #include <numbers>
3
4
  #include <cmath>
4
5
  #include <limits>
5
6
 
@@ -19,7 +20,7 @@ static constexpr float MOST_POSITIVE_SINGLE_FLOAT = static_cast<float>(std::nume
19
20
  static constexpr float MOST_NEGATIVE_SINGLE_FLOAT = static_cast<float>(std::numeric_limits<float>::lowest());
20
21
  static float LOG2_MOST_POSITIVE_SINGLE_FLOAT = std::log2(MOST_POSITIVE_SINGLE_FLOAT);
21
22
  static float LOG10_MOST_POSITIVE_SINGLE_FLOAT = std::log10(MOST_POSITIVE_SINGLE_FLOAT);
22
- static constexpr float PI = static_cast<float>(M_PI);
23
+ static constexpr float PI = std::numbers::pi_v<float>;
23
24
 
24
25
  // buffer sizes
25
26
  static constexpr size_t PROMISE_VENDOR_THREAD_POOL_WORKER_COUNT = 4;
@@ -277,8 +277,7 @@ decodeWithMemoryBlock(const void *data, size_t size, int sample_rate) {
277
277
  MemoryIOContext io_ctx{static_cast<const uint8_t *>(data), size, 0};
278
278
 
279
279
  constexpr size_t buffer_size = 4096;
280
- auto io_buffer = std::unique_ptr<uint8_t, decltype(&av_free)>(
281
- static_cast<uint8_t *>(av_malloc(buffer_size)), &av_free);
280
+ uint8_t *io_buffer = static_cast<uint8_t *>(av_malloc(buffer_size));
282
281
  if (io_buffer == nullptr) {
283
282
  return nullptr;
284
283
  }
@@ -286,7 +285,7 @@ decodeWithMemoryBlock(const void *data, size_t size, int sample_rate) {
286
285
  auto avio_ctx =
287
286
  std::unique_ptr<AVIOContext, std::function<void(AVIOContext *)>>(
288
287
  avio_alloc_context(
289
- io_buffer.get(),
288
+ io_buffer,
290
289
  buffer_size,
291
290
  0,
292
291
  &io_ctx,
@@ -12,6 +12,7 @@ FetchContent_Declare(
12
12
  googletest
13
13
  URL https://github.com/google/googletest/archive/3983f67e32fb3e9294487b9d4f9586efa6e5d088.zip
14
14
  )
15
+
15
16
  # For Windows: Prevent overriding the parent project's compiler/linker settings
16
17
  set(gtest_force_shared_crt ON CACHE BOOL "" FORCE)
17
18
  FetchContent_MakeAvailable(googletest)
@@ -57,6 +58,8 @@ target_include_directories(rnaudioapi PUBLIC
57
58
  ${JSI_DIR}
58
59
  "${REACT_NATIVE_DIR}/ReactCommon"
59
60
  "${REACT_NATIVE_DIR}/ReactCommon/callinvoker"
61
+ ${gtest_SOURCE_DIR}/include
62
+ ${gmock_SOURCE_DIR}/include
60
63
  )
61
64
 
62
65
  target_include_directories(rnaudioapi_libs PUBLIC
@@ -0,0 +1,389 @@
1
+ /*
2
+ * Copyright (C) 2010 Google Inc. All rights reserved.
3
+ *
4
+ * Redistribution and use in source and binary forms, with or without
5
+ * modification, are permitted provided that the following conditions
6
+ * are met:
7
+ *
8
+ * 1. Redistributions of source code must retain the above copyright
9
+ * notice, this list of conditions and the following disclaimer.
10
+ * 2. Redistributions in binary form must reproduce the above copyright
11
+ * notice, this list of conditions and the following disclaimer in the
12
+ * documentation and/or other materials provided with the distribution.
13
+ * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of
14
+ * its contributors may be used to endorse or promote products derived
15
+ * from this software without specific prior written permission.
16
+ *
17
+ * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
18
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
19
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
20
+ * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
21
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
22
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
23
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
24
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
26
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27
+ */
28
+
29
+ #include <assert.h>
30
+ #include <audioapi/core/utils/Constants.h>
31
+ #include <test/src/biquad/BiquadFilterChromium.h>
32
+ #include <algorithm>
33
+ #include <cmath>
34
+ #include <complex>
35
+ #include <span>
36
+ #include <vector>
37
+
38
+ namespace audioapi {
39
+
40
+ // Compute 10^x = exp(x*log(10))
41
+ static double pow10(double x) {
42
+ return std::exp(x * 2.30258509299404568402);
43
+ }
44
+
45
+ void getFrequencyResponse(
46
+ const BiquadCoefficients &coeffs,
47
+ std::span<const float> frequency,
48
+ std::span<float> mag_response,
49
+ std::span<float> phase_response,
50
+ float nyquistFrequency) {
51
+ assert(!frequency.empty());
52
+ assert(!mag_response.empty());
53
+ assert(!phase_response.empty());
54
+
55
+ std::vector<float> normalizedFreq(frequency.size());
56
+ for (size_t i = 0; i < frequency.size(); ++i) {
57
+ normalizedFreq[i] = frequency[i] / nyquistFrequency;
58
+ }
59
+
60
+ // Evaluate the Z-transform of the filter at given normalized
61
+ // frequency from 0 to 1. (1 corresponds to the Nyquist
62
+ // frequency.)
63
+ //
64
+ // The z-transform of the filter is
65
+ //
66
+ // H(z) = (b0 + b1*z^(-1) + b2*z^(-2))/(1 + a1*z^(-1) + a2*z^(-2))
67
+ //
68
+ // Evaluate as
69
+ //
70
+ // b0 + (b1 + b2*z1)*z1
71
+ // --------------------
72
+ // 1 + (a1 + a2*z1)*z1
73
+ //
74
+ // with z1 = 1/z and z = exp(j*pi*frequency). Hence z1 = exp(-j*pi*frequency)
75
+
76
+ double b0 = coeffs.b0;
77
+ double b1 = coeffs.b1;
78
+ double b2 = coeffs.b2;
79
+ double a1 = coeffs.a1;
80
+ double a2 = coeffs.a2;
81
+
82
+ for (size_t k = 0; k < normalizedFreq.size(); ++k) {
83
+ if (normalizedFreq[k] < 0.0 || normalizedFreq[k] > 1.0) {
84
+ // Out-of-bounds frequencies should return NaN.
85
+ mag_response[k] = std::nanf("");
86
+ phase_response[k] = std::nanf("");
87
+ } else {
88
+ double omega = -PI * normalizedFreq[k];
89
+ std::complex<double> z =
90
+ std::complex<double>(std::cos(omega), std::sin(omega));
91
+ std::complex<double> numerator = b0 + (b1 + b2 * z) * z;
92
+ std::complex<double> denominator =
93
+ std::complex<double>(1, 0) + (a1 + a2 * z) * z;
94
+ std::complex<double> response = numerator / denominator;
95
+ mag_response[k] = static_cast<float>(abs(response));
96
+ phase_response[k] =
97
+ static_cast<float>(std::atan2(imag(response), real(response)));
98
+ }
99
+ }
100
+ }
101
+
102
+ BiquadCoefficients normalizeCoefficients(
103
+ double b0,
104
+ double b1,
105
+ double b2,
106
+ double a0,
107
+ double a1,
108
+ double a2) {
109
+ return BiquadCoefficients{b0 / a0, b1 / a0, b2 / a0, a1 / a0, a2 / a0};
110
+ }
111
+
112
+ BiquadCoefficients calculateLowpassCoefficients(
113
+ double cutoff,
114
+ double resonance) {
115
+ // Limit cutoff to 0 to 1.
116
+ cutoff = std::clamp(cutoff, 0.0, 1.0);
117
+
118
+ if (cutoff == 1) {
119
+ // When cutoff is 1, the z-transform is 1.
120
+ return normalizeCoefficients(1, 0, 0, 1, 0, 0);
121
+ } else if (cutoff > 0) {
122
+ // Compute biquad coefficients for lowpass filter
123
+
124
+ resonance = pow10(resonance / 20);
125
+
126
+ double theta = kPiDouble * cutoff;
127
+ double alpha = std::sin(theta) / (2 * resonance);
128
+ double cosw = std::cos(theta);
129
+ double beta = (1 - cosw) / 2;
130
+
131
+ double b0 = beta;
132
+ double b1 = 2 * beta;
133
+ double b2 = beta;
134
+
135
+ double a0 = 1 + alpha;
136
+ double a1 = -2 * cosw;
137
+ double a2 = 1 - alpha;
138
+
139
+ return normalizeCoefficients(b0, b1, b2, a0, a1, a2);
140
+ } else {
141
+ // When cutoff is zero, nothing gets through the filter, so set
142
+ // coefficients up correctly.
143
+ return normalizeCoefficients(0, 0, 0, 1, 0, 0);
144
+ }
145
+ }
146
+
147
+ BiquadCoefficients calculateHighpassCoefficients(
148
+ double cutoff,
149
+ double resonance) {
150
+ // Limit cutoff to 0 to 1.
151
+ cutoff = std::clamp(cutoff, 0.0, 1.0);
152
+
153
+ if (cutoff == 1) {
154
+ // The z-transform is 0.
155
+ return normalizeCoefficients(0, 0, 0, 1, 0, 0);
156
+ } else if (cutoff > 0) {
157
+ // Compute biquad coefficients for highpass filter
158
+
159
+ resonance = pow10(resonance / 20);
160
+ double theta = kPiDouble * cutoff;
161
+ double alpha = std::sin(theta) / (2 * resonance);
162
+ double cosw = std::cos(theta);
163
+ double beta = (1 + cosw) / 2;
164
+
165
+ double b0 = beta;
166
+ double b1 = -2 * beta;
167
+ double b2 = beta;
168
+
169
+ double a0 = 1 + alpha;
170
+ double a1 = -2 * cosw;
171
+ double a2 = 1 - alpha;
172
+
173
+ return normalizeCoefficients(b0, b1, b2, a0, a1, a2);
174
+ } else {
175
+ // When cutoff is zero, we need to be careful because the above
176
+ // gives a quadratic divided by the same quadratic, with poles
177
+ // and zeros on the unit circle in the same place. When cutoff
178
+ // is zero, the z-transform is 1.
179
+ return normalizeCoefficients(1, 0, 0, 1, 0, 0);
180
+ }
181
+ }
182
+
183
+ BiquadCoefficients calculateLowshelfCoefficients(
184
+ double frequency,
185
+ double db_gain) {
186
+ // Clip frequencies to between 0 and 1, inclusive.
187
+ frequency = std::clamp(frequency, 0.0, 1.0);
188
+
189
+ double a = pow10(db_gain / 40);
190
+
191
+ if (frequency == 1) {
192
+ // The z-transform is a constant gain.
193
+ return normalizeCoefficients(a * a, 0, 0, 1, 0, 0);
194
+ } else if (frequency > 0) {
195
+ double w0 = kPiDouble * frequency;
196
+ double s = 1; // filter slope (1 is max value)
197
+ double alpha = 0.5 * std::sin(w0) * sqrt((a + 1 / a) * (1 / s - 1) + 2);
198
+ double k = std::cos(w0);
199
+ double k2 = 2 * sqrt(a) * alpha;
200
+ double a_plus_one = a + 1;
201
+ double a_minus_one = a - 1;
202
+
203
+ double b0 = a * (a_plus_one - a_minus_one * k + k2);
204
+ double b1 = 2 * a * (a_minus_one - a_plus_one * k);
205
+ double b2 = a * (a_plus_one - a_minus_one * k - k2);
206
+ double a0 = a_plus_one + a_minus_one * k + k2;
207
+ double a1 = -2 * (a_minus_one + a_plus_one * k);
208
+ double a2 = a_plus_one + a_minus_one * k - k2;
209
+
210
+ return normalizeCoefficients(b0, b1, b2, a0, a1, a2);
211
+ } else {
212
+ // When frequency is 0, the z-transform is 1.
213
+ return normalizeCoefficients(1, 0, 0, 1, 0, 0);
214
+ }
215
+ }
216
+
217
+ BiquadCoefficients calculateHighshelfCoefficients(
218
+ double frequency,
219
+ double db_gain) {
220
+ // Clip frequencies to between 0 and 1, inclusive.
221
+ frequency = std::clamp(frequency, 0.0, 1.0);
222
+
223
+ double a = pow10(db_gain / 40);
224
+
225
+ if (frequency == 1) {
226
+ // The z-transform is 1.
227
+ return normalizeCoefficients(1, 0, 0, 1, 0, 0);
228
+ } else if (frequency > 0) {
229
+ double w0 = kPiDouble * frequency;
230
+ double s = 1; // filter slope (1 is max value)
231
+ double alpha = 0.5 * std::sin(w0) * sqrt((a + 1 / a) * (1 / s - 1) + 2);
232
+ double k = std::cos(w0);
233
+ double k2 = 2 * sqrt(a) * alpha;
234
+ double a_plus_one = a + 1;
235
+ double a_minus_one = a - 1;
236
+
237
+ double b0 = a * (a_plus_one + a_minus_one * k + k2);
238
+ double b1 = -2 * a * (a_minus_one + a_plus_one * k);
239
+ double b2 = a * (a_plus_one + a_minus_one * k - k2);
240
+ double a0 = a_plus_one - a_minus_one * k + k2;
241
+ double a1 = 2 * (a_minus_one - a_plus_one * k);
242
+ double a2 = a_plus_one - a_minus_one * k - k2;
243
+
244
+ return normalizeCoefficients(b0, b1, b2, a0, a1, a2);
245
+ } else {
246
+ // When frequency = 0, the filter is just a gain, A^2.
247
+ return normalizeCoefficients(a * a, 0, 0, 1, 0, 0);
248
+ }
249
+ }
250
+
251
+ BiquadCoefficients
252
+ calculatePeakingCoefficients(double frequency, double q, double db_gain) {
253
+ // Clip frequencies to between 0 and 1, inclusive.
254
+ frequency = std::clamp(frequency, 0.0, 1.0);
255
+
256
+ // Don't let Q go negative, which causes an unstable filter.
257
+ q = std::max(0.0, q);
258
+
259
+ double a = pow10(db_gain / 40);
260
+
261
+ if (frequency > 0 && frequency < 1) {
262
+ if (q > 0) {
263
+ double w0 = kPiDouble * frequency;
264
+ double alpha = std::sin(w0) / (2 * q);
265
+ double k = std::cos(w0);
266
+
267
+ double b0 = 1 + alpha * a;
268
+ double b1 = -2 * k;
269
+ double b2 = 1 - alpha * a;
270
+ double a0 = 1 + alpha / a;
271
+ double a1 = -2 * k;
272
+ double a2 = 1 - alpha / a;
273
+
274
+ return normalizeCoefficients(b0, b1, b2, a0, a1, a2);
275
+ } else {
276
+ // When Q = 0, the above formulas have problems. If we look at
277
+ // the z-transform, we can see that the limit as Q->0 is A^2, so
278
+ // set the filter that way.
279
+ return normalizeCoefficients(a * a, 0, 0, 1, 0, 0);
280
+ }
281
+ } else {
282
+ // When frequency is 0 or 1, the z-transform is 1.
283
+ return normalizeCoefficients(1, 0, 0, 1, 0, 0);
284
+ }
285
+ }
286
+
287
+ BiquadCoefficients calculateAllpassCoefficients(double frequency, double q) {
288
+ // Clip frequencies to between 0 and 1, inclusive.
289
+ frequency = std::clamp(frequency, 0.0, 1.0);
290
+
291
+ // Don't let Q go negative, which causes an unstable filter.
292
+ q = std::max(0.0, q);
293
+
294
+ if (frequency > 0 && frequency < 1) {
295
+ if (q > 0) {
296
+ double w0 = kPiDouble * frequency;
297
+ double alpha = std::sin(w0) / (2 * q);
298
+ double k = std::cos(w0);
299
+
300
+ double b0 = 1 - alpha;
301
+ double b1 = -2 * k;
302
+ double b2 = 1 + alpha;
303
+ double a0 = 1 + alpha;
304
+ double a1 = -2 * k;
305
+ double a2 = 1 - alpha;
306
+
307
+ return normalizeCoefficients(b0, b1, b2, a0, a1, a2);
308
+ } else {
309
+ // When Q = 0, the above formulas have problems. If we look at
310
+ // the z-transform, we can see that the limit as Q->0 is -1, so
311
+ // set the filter that way.
312
+ return normalizeCoefficients(-1, 0, 0, 1, 0, 0);
313
+ }
314
+ } else {
315
+ // When frequency is 0 or 1, the z-transform is 1.
316
+ return normalizeCoefficients(1, 0, 0, 1, 0, 0);
317
+ }
318
+ }
319
+
320
+ BiquadCoefficients calculateNotchCoefficients(double frequency, double q) {
321
+ // Clip frequencies to between 0 and 1, inclusive.
322
+ frequency = std::clamp(frequency, 0.0, 1.0);
323
+
324
+ // Don't let Q go negative, which causes an unstable filter.
325
+ q = std::max(0.0, q);
326
+
327
+ if (frequency > 0 && frequency < 1) {
328
+ if (q > 0) {
329
+ double w0 = kPiDouble * frequency;
330
+ double alpha = std::sin(w0) / (2 * q);
331
+ double k = std::cos(w0);
332
+
333
+ double b0 = 1;
334
+ double b1 = -2 * k;
335
+ double b2 = 1;
336
+ double a0 = 1 + alpha;
337
+ double a1 = -2 * k;
338
+ double a2 = 1 - alpha;
339
+
340
+ return normalizeCoefficients(b0, b1, b2, a0, a1, a2);
341
+ } else {
342
+ // When Q = 0, the above formulas have problems. If we look at
343
+ // the z-transform, we can see that the limit as Q->0 is 0, so
344
+ // set the filter that way.
345
+ return normalizeCoefficients(0, 0, 0, 1, 0, 0);
346
+ }
347
+ } else {
348
+ // When frequency is 0 or 1, the z-transform is 1.
349
+ return normalizeCoefficients(1, 0, 0, 1, 0, 0);
350
+ }
351
+ }
352
+
353
+ BiquadCoefficients calculateBandpassCoefficients(double frequency, double q) {
354
+ // No negative frequencies allowed.
355
+ frequency = std::max(0.0, frequency);
356
+
357
+ // Don't let Q go negative, which causes an unstable filter.
358
+ q = std::max(0.0, q);
359
+
360
+ if (frequency > 0 && frequency < 1) {
361
+ double w0 = kPiDouble * frequency;
362
+ if (q > 0) {
363
+ double alpha = std::sin(w0) / (2 * q);
364
+ double k = std::cos(w0);
365
+
366
+ double b0 = alpha;
367
+ double b1 = 0;
368
+ double b2 = -alpha;
369
+ double a0 = 1 + alpha;
370
+ double a1 = -2 * k;
371
+ double a2 = 1 - alpha;
372
+
373
+ return normalizeCoefficients(b0, b1, b2, a0, a1, a2);
374
+ } else {
375
+ // When Q = 0, the above formulas have problems. If we look at
376
+ // the z-transform, we can see that the limit as Q->0 is 1, so
377
+ // set the filter that way.
378
+ return normalizeCoefficients(1, 0, 0, 1, 0, 0);
379
+ }
380
+ } else {
381
+ // When the cutoff is zero, the z-transform approaches 0, if Q
382
+ // > 0. When both Q and cutoff are zero, the z-transform is
383
+ // pretty much undefined. What should we do in this case?
384
+ // For now, just make the filter 0. When the cutoff is 1, the
385
+ // z-transform also approaches 0.
386
+ return normalizeCoefficients(0, 0, 0, 1, 0, 0);
387
+ }
388
+ }
389
+ } // namespace audioapi
@@ -0,0 +1,64 @@
1
+ /*
2
+ * Copyright (C) 2010 Google Inc. All rights reserved.
3
+ *
4
+ * Redistribution and use in source and binary forms, with or without
5
+ * modification, are permitted provided that the following conditions
6
+ * are met:
7
+ *
8
+ * 1. Redistributions of source code must retain the above copyright
9
+ * notice, this list of conditions and the following disclaimer.
10
+ * 2. Redistributions in binary form must reproduce the above copyright
11
+ * notice, this list of conditions and the following disclaimer in the
12
+ * documentation and/or other materials provided with the distribution.
13
+ * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of
14
+ * its contributors may be used to endorse or promote products derived
15
+ * from this software without specific prior written permission.
16
+ *
17
+ * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
18
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
19
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
20
+ * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
21
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
22
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
23
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
24
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
26
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27
+ */
28
+
29
+ #pragma once
30
+
31
+ #include <numbers>
32
+ #include <span>
33
+
34
+ namespace audioapi {
35
+
36
+ constexpr double kPiDouble = std::numbers::pi_v<double>;
37
+
38
+ struct BiquadCoefficients {
39
+ double b0;
40
+ double b1;
41
+ double b2;
42
+ double a1;
43
+ double a2;
44
+ };
45
+
46
+ void getFrequencyResponse(
47
+ const BiquadCoefficients &coeffs,
48
+ std::span<const float> frequency,
49
+ std::span<float> mag_response,
50
+ std::span<float> phase_response,
51
+ float nyquistFrequency);
52
+
53
+ BiquadCoefficients normalizeCoefficients(double b0, double b1, double b2, double a0, double a1, double a2);
54
+
55
+ BiquadCoefficients calculateLowpassCoefficients(double cutoff, double Q);
56
+ BiquadCoefficients calculateHighpassCoefficients(double cutoff, double Q);
57
+ BiquadCoefficients calculateBandpassCoefficients(double frequency, double Q);
58
+ BiquadCoefficients calculateNotchCoefficients(double frequency, double Q);
59
+ BiquadCoefficients calculateAllpassCoefficients(double frequency, double Q);
60
+ BiquadCoefficients calculatePeakingCoefficients(double frequency, double Q, double db_gain);
61
+ BiquadCoefficients calculateLowshelfCoefficients(double frequency, double db_gain);
62
+ BiquadCoefficients calculateHighshelfCoefficients(double frequency, double db_gain);
63
+
64
+ } // namespace audioapi
@@ -0,0 +1,284 @@
1
+ #include <test/src/biquad/BiquadFilterChromium.h>
2
+ #include <test/src/biquad/BiquadFilterTest.h>
3
+
4
+ namespace audioapi {
5
+
6
+ void BiquadFilterTest::expectCoefficientsNear(
7
+ const std::shared_ptr<BiquadFilterNode> &biquadNode,
8
+ const BiquadCoefficients &expected) {
9
+ EXPECT_NEAR(biquadNode->b0_, expected.b0, tolerance);
10
+ EXPECT_NEAR(biquadNode->b1_, expected.b1, tolerance);
11
+ EXPECT_NEAR(biquadNode->b2_, expected.b2, tolerance);
12
+ EXPECT_NEAR(biquadNode->a1_, expected.a1, tolerance);
13
+ EXPECT_NEAR(biquadNode->a2_, expected.a2, tolerance);
14
+ }
15
+
16
+ void BiquadFilterTest::testLowpass(float frequency, float Q) {
17
+ auto node = std::make_shared<BiquadFilterNode>(context.get());
18
+ float normalizedFrequency = frequency / nyquistFrequency;
19
+
20
+ node->setLowpassCoefficients(normalizedFrequency, Q);
21
+ expectCoefficientsNear(
22
+ node, calculateLowpassCoefficients(normalizedFrequency, Q));
23
+ }
24
+
25
+ void BiquadFilterTest::testHighpass(float frequency, float Q) {
26
+ auto node = std::make_shared<BiquadFilterNode>(context.get());
27
+ float normalizedFrequency = frequency / nyquistFrequency;
28
+
29
+ node->setHighpassCoefficients(normalizedFrequency, Q);
30
+ expectCoefficientsNear(
31
+ node, calculateHighpassCoefficients(normalizedFrequency, Q));
32
+ }
33
+
34
+ void BiquadFilterTest::testBandpass(float frequency, float Q) {
35
+ auto node = std::make_shared<BiquadFilterNode>(context.get());
36
+ float normalizedFrequency = frequency / nyquistFrequency;
37
+
38
+ node->setBandpassCoefficients(normalizedFrequency, Q);
39
+ expectCoefficientsNear(
40
+ node, calculateBandpassCoefficients(normalizedFrequency, Q));
41
+ }
42
+
43
+ void BiquadFilterTest::testNotch(float frequency, float Q) {
44
+ auto node = std::make_shared<BiquadFilterNode>(context.get());
45
+ float normalizedFrequency = frequency / nyquistFrequency;
46
+
47
+ node->setNotchCoefficients(normalizedFrequency, Q);
48
+ expectCoefficientsNear(
49
+ node, calculateNotchCoefficients(normalizedFrequency, Q));
50
+ }
51
+
52
+ void BiquadFilterTest::testAllpass(float frequency, float Q) {
53
+ auto node = std::make_shared<BiquadFilterNode>(context.get());
54
+ float normalizedFrequency = frequency / nyquistFrequency;
55
+
56
+ node->setAllpassCoefficients(normalizedFrequency, Q);
57
+ expectCoefficientsNear(
58
+ node, calculateAllpassCoefficients(normalizedFrequency, Q));
59
+ }
60
+
61
+ void BiquadFilterTest::testPeaking(float frequency, float Q, float gain) {
62
+ auto node = std::make_shared<BiquadFilterNode>(context.get());
63
+ float normalizedFrequency = frequency / nyquistFrequency;
64
+
65
+ node->setPeakingCoefficients(normalizedFrequency, Q, gain);
66
+ expectCoefficientsNear(
67
+ node, calculatePeakingCoefficients(normalizedFrequency, Q, gain));
68
+ }
69
+
70
+ void BiquadFilterTest::testLowshelf(float frequency, float gain) {
71
+ auto node = std::make_shared<BiquadFilterNode>(context.get());
72
+ float normalizedFrequency = frequency / nyquistFrequency;
73
+
74
+ node->setLowshelfCoefficients(normalizedFrequency, gain);
75
+ expectCoefficientsNear(
76
+ node, calculateLowshelfCoefficients(normalizedFrequency, gain));
77
+ }
78
+
79
+ void BiquadFilterTest::testHighshelf(float frequency, float gain) {
80
+ auto node = std::make_shared<BiquadFilterNode>(context.get());
81
+ float normalizedFrequency = frequency / nyquistFrequency;
82
+
83
+ node->setHighshelfCoefficients(normalizedFrequency, gain);
84
+ expectCoefficientsNear(
85
+ node, calculateHighshelfCoefficients(normalizedFrequency, gain));
86
+ }
87
+
88
+ INSTANTIATE_TEST_SUITE_P(
89
+ Frequencies,
90
+ BiquadFilterFrequencyTest,
91
+ ::testing::Values(
92
+ 0.0f, // 0 Hz - the filter should block all input signal
93
+ 10.0f, // very low frequency
94
+ 350.0f, // default
95
+ nyquistFrequency - 0.0001f, // frequency near Nyquist
96
+ nyquistFrequency)); // maximal frequency
97
+
98
+ INSTANTIATE_TEST_SUITE_P(
99
+ QEdgeCases,
100
+ BiquadFilterQTestLowpassHighpass,
101
+ ::testing::Values(
102
+ -770.63678f, // min value for lowpass and highpass
103
+ 0.0f, // default
104
+ 770.63678f)); // max value for lowpass and highpass
105
+
106
+ INSTANTIATE_TEST_SUITE_P(
107
+ QEdgeCases,
108
+ BiquadFilterQTestRestTypes, // bandpass, notch, allpass, peaking
109
+ ::testing::Values(
110
+ 0.0f, // default and min value
111
+ MOST_POSITIVE_SINGLE_FLOAT));
112
+
113
+ INSTANTIATE_TEST_SUITE_P(
114
+ GainEdgeCases,
115
+ BiquadFilterGainTest,
116
+ ::testing::Values(
117
+ -40.0f,
118
+ 0.0f, // default
119
+ 40.0f));
120
+
121
+ TEST_P(BiquadFilterFrequencyTest, SetLowpassCoefficients) {
122
+ float frequency = GetParam();
123
+ float Q = 1.0f;
124
+ testLowpass(frequency, Q);
125
+ }
126
+
127
+ TEST_P(BiquadFilterFrequencyTest, SetHighpassCoefficients) {
128
+ float frequency = GetParam();
129
+ float Q = 1.0f;
130
+ testHighpass(frequency, Q);
131
+ }
132
+
133
+ TEST_P(BiquadFilterFrequencyTest, SetBandpassCoefficients) {
134
+ float frequency = GetParam();
135
+ float Q = 1.0f;
136
+ testBandpass(frequency, Q);
137
+ }
138
+
139
+ TEST_P(BiquadFilterFrequencyTest, SetNotchCoefficients) {
140
+ float frequency = GetParam();
141
+ float Q = 1.0f;
142
+ testNotch(frequency, Q);
143
+ }
144
+
145
+ TEST_P(BiquadFilterFrequencyTest, SetAllpassCoefficients) {
146
+ float frequency = GetParam();
147
+ float Q = 1.0f;
148
+ testAllpass(frequency, Q);
149
+ }
150
+
151
+ TEST_P(BiquadFilterFrequencyTest, SetPeakingCoefficients) {
152
+ float frequency = GetParam();
153
+ float Q = 1.0f;
154
+ float gain = 2.0f;
155
+ testPeaking(frequency, Q, gain);
156
+ }
157
+
158
+ TEST_P(BiquadFilterFrequencyTest, SetLowshelfCoefficients) {
159
+ float frequency = GetParam();
160
+ float gain = 2.0f;
161
+ testLowshelf(frequency, gain);
162
+ }
163
+
164
+ TEST_P(BiquadFilterFrequencyTest, SetHighshelfCoefficients) {
165
+ float frequency = GetParam();
166
+ float gain = 2.0f;
167
+ testHighshelf(frequency, gain);
168
+ }
169
+
170
+ TEST_P(BiquadFilterQTestLowpassHighpass, SetLowpassCoefficients) {
171
+ float frequency = 1000.0f;
172
+ float Q = GetParam();
173
+ testLowpass(frequency, Q);
174
+ }
175
+
176
+ TEST_P(BiquadFilterQTestLowpassHighpass, SetHighpassCoefficients) {
177
+ float frequency = 1000.0f;
178
+ float Q = GetParam();
179
+ testHighpass(frequency, Q);
180
+ }
181
+
182
+ TEST_P(BiquadFilterQTestRestTypes, SetBandpassCoefficients) {
183
+ float frequency = 1000.0f;
184
+ float Q = GetParam();
185
+ testBandpass(frequency, Q);
186
+ }
187
+
188
+ TEST_P(BiquadFilterQTestRestTypes, SetNotchCoefficients) {
189
+ float frequency = 1000.0f;
190
+ float Q = GetParam();
191
+ testNotch(frequency, Q);
192
+ }
193
+
194
+ TEST_P(BiquadFilterQTestRestTypes, SetAllpassCoefficients) {
195
+ float frequency = 1000.0f;
196
+ float Q = GetParam();
197
+ testAllpass(frequency, Q);
198
+ }
199
+
200
+ TEST_P(BiquadFilterQTestRestTypes, SetPeakingCoefficients) {
201
+ float frequency = 1000.0f;
202
+ float Q = GetParam();
203
+ float gain = 2.0f;
204
+ testPeaking(frequency, Q, gain);
205
+ }
206
+
207
+ TEST_P(BiquadFilterGainTest, SetPeakingCoefficients) {
208
+ float frequency = 1000.0f;
209
+ float Q = 1.0f;
210
+ float gain = GetParam();
211
+ testPeaking(frequency, Q, gain);
212
+ }
213
+
214
+ TEST_P(BiquadFilterGainTest, SetLowshelfCoefficients) {
215
+ float frequency = 1000.0f;
216
+ float gain = GetParam();
217
+ testLowshelf(frequency, gain);
218
+ }
219
+
220
+ TEST_P(BiquadFilterGainTest, SetHighshelfCoefficients) {
221
+ float frequency = 1000.0f;
222
+ float gain = GetParam();
223
+ testHighshelf(frequency, gain);
224
+ }
225
+
226
+ TEST_F(BiquadFilterTest, GetFrequencyResponse) {
227
+ auto node = std::make_shared<BiquadFilterNode>(context.get());
228
+
229
+ float frequency = 1000.0f;
230
+ float Q = 1.0f;
231
+ float normalizedFrequency = frequency / nyquistFrequency;
232
+
233
+ node->setLowpassCoefficients(normalizedFrequency, Q);
234
+ auto coeffs = calculateLowpassCoefficients(normalizedFrequency, Q);
235
+
236
+ std::vector<float> TestFrequencies = {
237
+ -0.0001f,
238
+ 0.0f,
239
+ 0.0001f,
240
+ 0.25f * nyquistFrequency,
241
+ 0.5f * nyquistFrequency,
242
+ 0.75f * nyquistFrequency,
243
+ nyquistFrequency - 0.0001f,
244
+ nyquistFrequency,
245
+ nyquistFrequency + 0.0001f};
246
+
247
+ std::vector<float> magResponseNode(TestFrequencies.size());
248
+ std::vector<float> phaseResponseNode(TestFrequencies.size());
249
+ std::vector<float> magResponseExpected(TestFrequencies.size());
250
+ std::vector<float> phaseResponseExpected(TestFrequencies.size());
251
+
252
+ node->getFrequencyResponse(
253
+ TestFrequencies.data(),
254
+ magResponseNode.data(),
255
+ phaseResponseNode.data(),
256
+ TestFrequencies.size());
257
+ getFrequencyResponse(
258
+ coeffs,
259
+ TestFrequencies,
260
+ magResponseExpected,
261
+ phaseResponseExpected,
262
+ nyquistFrequency);
263
+
264
+ for (size_t i = 0; i < TestFrequencies.size(); ++i) {
265
+ float f = TestFrequencies[i];
266
+ if (std::isnan(magResponseExpected[i])) {
267
+ EXPECT_TRUE(std::isnan(magResponseNode[i]))
268
+ << "Expected NaN at frequency " << f;
269
+ } else {
270
+ EXPECT_NEAR(magResponseNode[i], magResponseExpected[i], tolerance)
271
+ << "Magnitude mismatch at " << f << " Hz";
272
+ }
273
+
274
+ if (std::isnan(phaseResponseExpected[i])) {
275
+ EXPECT_TRUE(std::isnan(phaseResponseNode[i]))
276
+ << "Expected NaN at frequency " << f;
277
+ } else {
278
+ EXPECT_NEAR(phaseResponseNode[i], phaseResponseExpected[i], tolerance)
279
+ << "Phase mismatch at " << f << " Hz";
280
+ }
281
+ }
282
+ }
283
+
284
+ } // namespace audioapi
@@ -0,0 +1,40 @@
1
+ #pragma once
2
+
3
+ #include <audioapi/core/OfflineAudioContext.h>
4
+ #include <audioapi/core/effects/BiquadFilterNode.h>
5
+ #include <audioapi/core/utils/worklets/SafeIncludes.h>
6
+ #include <gtest/gtest.h>
7
+ #include <test/src/MockAudioEventHandlerRegistry.h>
8
+ #include <memory>
9
+
10
+ static constexpr int sampleRate = 44100;
11
+ static constexpr float nyquistFrequency = sampleRate / 2.0f;
12
+ static constexpr float tolerance = 0.0001f;
13
+
14
+ namespace audioapi {
15
+ class BiquadFilterTest : public ::testing::Test {
16
+ protected:
17
+ std::shared_ptr<IAudioEventHandlerRegistry> eventRegistry;
18
+ std::unique_ptr<OfflineAudioContext> context;
19
+
20
+ void SetUp() override {
21
+ eventRegistry = std::make_shared<MockAudioEventHandlerRegistry>();
22
+ context = std::make_unique<OfflineAudioContext>(2, 5 * sampleRate, sampleRate, eventRegistry, RuntimeRegistry{});
23
+ }
24
+
25
+ void expectCoefficientsNear(const std::shared_ptr<BiquadFilterNode> &node, const BiquadCoefficients &expected);
26
+ void testLowpass(float frequency, float Q);
27
+ void testHighpass(float frequency, float Q);
28
+ void testBandpass(float frequency, float Q);
29
+ void testNotch(float frequency, float Q);
30
+ void testAllpass(float frequency, float Q);
31
+ void testPeaking(float frequency, float Q, float gain);
32
+ void testLowshelf(float frequency, float gain);
33
+ void testHighshelf(float frequency, float gain);
34
+ };
35
+
36
+ class BiquadFilterQTestLowpassHighpass : public BiquadFilterTest, public ::testing::WithParamInterface<float> {};
37
+ class BiquadFilterQTestRestTypes : public BiquadFilterTest, public ::testing::WithParamInterface<float> {};
38
+ class BiquadFilterFrequencyTest : public BiquadFilterTest, public ::testing::WithParamInterface<float> {};
39
+ class BiquadFilterGainTest : public BiquadFilterTest, public ::testing::WithParamInterface<float> {};
40
+ } // namespace audioapi
@@ -18,8 +18,7 @@ class IOSAudioRecorder : public AudioRecorder {
18
18
  IOSAudioRecorder(
19
19
  float sampleRate,
20
20
  int bufferLength,
21
- const std::shared_ptr<AudioEventHandlerRegistry>
22
- &audioEventHandlerRegistry);
21
+ const std::shared_ptr<AudioEventHandlerRegistry> &audioEventHandlerRegistry);
23
22
 
24
23
  ~IOSAudioRecorder() override;
25
24
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-native-audio-api",
3
- "version": "0.10.0-nightly-2d11b56-20251021",
3
+ "version": "0.10.0-nightly-75aa9d0-20251022",
4
4
  "description": "react-native-audio-api provides system for controlling audio in React Native environment compatible with Web Audio API specification",
5
5
  "bin": {
6
6
  "setup-rn-audio-api-web": "./scripts/setup-rn-audio-api-web.js"