react-native-audio-api 0.10.0-nightly-2d11b56-20251021 → 0.10.0-nightly-325e87a-20251023
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.
- package/RNAudioAPI.podspec +3 -0
- package/android/build.gradle +26 -2
- package/android/src/main/cpp/audioapi/android/AudioAPIModule.cpp +5 -0
- package/android/src/main/cpp/audioapi/android/AudioAPIModule.h +1 -0
- package/android/src/main/cpp/audioapi/android/core/AudioPlayer.cpp +4 -2
- package/android/src/main/java/com/swmansion/audioapi/AudioAPIModule.kt +29 -1
- package/common/cpp/audioapi/AudioAPIModuleInstaller.h +20 -0
- package/common/cpp/audioapi/HostObjects/effects/BiquadFilterNodeHostObject.cpp +1 -1
- package/common/cpp/audioapi/core/AudioParam.cpp +2 -2
- package/common/cpp/audioapi/core/effects/BiquadFilterNode.cpp +69 -32
- package/common/cpp/audioapi/core/effects/BiquadFilterNode.h +37 -1
- package/common/cpp/audioapi/core/utils/Constants.h +2 -1
- package/common/cpp/audioapi/core/utils/worklets/SafeIncludes.h +0 -10
- package/common/cpp/audioapi/libs/ffmpeg/FFmpegDecoding.cpp +2 -3
- package/common/cpp/test/CMakeLists.txt +3 -0
- package/common/cpp/test/src/biquad/BiquadFilterChromium.cpp +389 -0
- package/common/cpp/test/src/biquad/BiquadFilterChromium.h +64 -0
- package/common/cpp/test/src/biquad/BiquadFilterTest.cpp +284 -0
- package/common/cpp/test/src/biquad/BiquadFilterTest.h +40 -0
- package/ios/audioapi/ios/AudioAPIModule.h +2 -1
- package/ios/audioapi/ios/AudioAPIModule.mm +2 -0
- package/ios/audioapi/ios/core/IOSAudioRecorder.h +1 -2
- package/ios/audioapi/ios/system/AudioSessionManager.mm +9 -0
- package/lib/commonjs/core/AudioContext.js +1 -1
- package/lib/commonjs/core/AudioContext.js.map +1 -1
- package/lib/commonjs/core/BaseAudioContext.js +16 -25
- package/lib/commonjs/core/BaseAudioContext.js.map +1 -1
- package/lib/commonjs/core/OfflineAudioContext.js +1 -1
- package/lib/commonjs/core/OfflineAudioContext.js.map +1 -1
- package/lib/commonjs/utils/index.js +19 -4
- package/lib/commonjs/utils/index.js.map +1 -1
- package/lib/module/core/AudioContext.js +2 -2
- package/lib/module/core/AudioContext.js.map +1 -1
- package/lib/module/core/BaseAudioContext.js +17 -26
- package/lib/module/core/BaseAudioContext.js.map +1 -1
- package/lib/module/core/OfflineAudioContext.js +2 -2
- package/lib/module/core/OfflineAudioContext.js.map +1 -1
- package/lib/module/utils/index.js +15 -1
- package/lib/module/utils/index.js.map +1 -1
- package/lib/typescript/core/AudioContext.d.ts.map +1 -1
- package/lib/typescript/core/BaseAudioContext.d.ts.map +1 -1
- package/lib/typescript/utils/index.d.ts +4 -1
- package/lib/typescript/utils/index.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/core/AudioContext.ts +3 -2
- package/src/core/BaseAudioContext.ts +44 -60
- package/src/core/OfflineAudioContext.ts +2 -2
- package/src/utils/index.ts +22 -1
package/RNAudioAPI.podspec
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
require "json"
|
|
2
|
+
require_relative './scripts/rnaa_utils'
|
|
2
3
|
|
|
3
4
|
package_json = JSON.parse(File.read(File.join(__dir__, "package.json")))
|
|
4
5
|
|
|
@@ -8,6 +9,8 @@ folly_flags = "-DFOLLY_MOBILE=1 -DFOLLY_USE_LIBCPP=1 -Wno-comma -Wno-shorten-64-
|
|
|
8
9
|
fabric_flags = $new_arch_enabled ? '-DRCT_NEW_ARCH_ENABLED' : ''
|
|
9
10
|
version_flag = "-DAUDIOAPI_VERSION=#{package_json['version']}"
|
|
10
11
|
|
|
12
|
+
worklets_preprocessor_flag = check_if_worklets_enabled() ? '-DRN_AUDIO_API_ENABLE_WORKLETS=1' : ''
|
|
13
|
+
|
|
11
14
|
Pod::Spec.new do |s|
|
|
12
15
|
s.name = "RNAudioAPI"
|
|
13
16
|
s.version = package_json["version"]
|
package/android/build.gradle
CHANGED
|
@@ -74,6 +74,30 @@ def resolveReactNativeWorkletsDirectory() {
|
|
|
74
74
|
return null;
|
|
75
75
|
}
|
|
76
76
|
|
|
77
|
+
def validateWorkletsVersion() {
|
|
78
|
+
def validationScript = file("${projectDir}/../scripts/validate-worklets-version.js")
|
|
79
|
+
if (!validationScript.exists()) {
|
|
80
|
+
logger.error("[AudioAPI] Worklets validation script not found at ${validationScript.absolutePath}")
|
|
81
|
+
return false
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
try {
|
|
85
|
+
def process = ["node", validationScript.absolutePath].execute()
|
|
86
|
+
process.waitForProcessOutput(System.out, System.err)
|
|
87
|
+
def exitCode = process.exitValue()
|
|
88
|
+
|
|
89
|
+
if (exitCode == 0) {
|
|
90
|
+
return true
|
|
91
|
+
} else {
|
|
92
|
+
logger.warn("[AudioAPI] Worklets version validation failed")
|
|
93
|
+
return false
|
|
94
|
+
}
|
|
95
|
+
} catch (Exception e) {
|
|
96
|
+
logger.error("[AudioAPI] Failed to validate worklets version: ${e.message}")
|
|
97
|
+
return false
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
77
101
|
def toPlatformFileString(String path) {
|
|
78
102
|
if (Os.isFamily(Os.FAMILY_WINDOWS)) {
|
|
79
103
|
path = path.replace(File.separatorChar, '/' as char)
|
|
@@ -92,7 +116,7 @@ static def supportsNamespace() {
|
|
|
92
116
|
|
|
93
117
|
def reactNativeRootDir = resolveReactNativeDirectory()
|
|
94
118
|
def reactNativeWorkletsRootDir = resolveReactNativeWorkletsDirectory()
|
|
95
|
-
def isWorkletsAvailable = reactNativeWorkletsRootDir != null
|
|
119
|
+
def isWorkletsAvailable = reactNativeWorkletsRootDir != null && validateWorkletsVersion()
|
|
96
120
|
|
|
97
121
|
def reactProperties = new Properties()
|
|
98
122
|
file("$reactNativeRootDir/ReactAndroid/gradle.properties").withInputStream { reactProperties.load(it) }
|
|
@@ -123,7 +147,7 @@ android {
|
|
|
123
147
|
defaultConfig {
|
|
124
148
|
minSdkVersion getExtOrIntegerDefault("minSdkVersion")
|
|
125
149
|
targetSdkVersion getExtOrIntegerDefault("targetSdkVersion")
|
|
126
|
-
|
|
150
|
+
|
|
127
151
|
buildConfigField "boolean", "IS_NEW_ARCHITECTURE_ENABLED", isNewArchitectureEnabled().toString()
|
|
128
152
|
buildConfigField "boolean", "RN_AUDIO_API_ENABLE_WORKLETS", "${isWorkletsAvailable}"
|
|
129
153
|
|
|
@@ -51,6 +51,7 @@ void AudioAPIModule::registerNatives() {
|
|
|
51
51
|
makeNativeMethod(
|
|
52
52
|
"invokeHandlerWithEventNameAndEventBody",
|
|
53
53
|
AudioAPIModule::invokeHandlerWithEventNameAndEventBody),
|
|
54
|
+
makeNativeMethod("closeAllContexts", AudioAPIModule::closeAllContexts),
|
|
54
55
|
});
|
|
55
56
|
}
|
|
56
57
|
|
|
@@ -101,4 +102,8 @@ void AudioAPIModule::invokeHandlerWithEventNameAndEventBody(
|
|
|
101
102
|
eventName->toStdString(), body);
|
|
102
103
|
}
|
|
103
104
|
}
|
|
105
|
+
|
|
106
|
+
void AudioAPIModule::closeAllContexts() {
|
|
107
|
+
AudioAPIModuleInstaller::closeAllContexts();
|
|
108
|
+
}
|
|
104
109
|
} // namespace audioapi
|
|
@@ -34,6 +34,7 @@ class AudioAPIModule : public jni::HybridClass<AudioAPIModule> {
|
|
|
34
34
|
|
|
35
35
|
void injectJSIBindings();
|
|
36
36
|
void invokeHandlerWithEventNameAndEventBody(jni::alias_ref<jni::JString> eventName, jni::alias_ref<jni::JMap<jstring, jobject>> eventBody);
|
|
37
|
+
void closeAllContexts();
|
|
37
38
|
|
|
38
39
|
private:
|
|
39
40
|
friend HybridBase;
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
#include <audioapi/core/utils/Constants.h>
|
|
5
5
|
#include <audioapi/utils/AudioArray.h>
|
|
6
6
|
#include <audioapi/utils/AudioBus.h>
|
|
7
|
+
#include <jni.h>
|
|
7
8
|
|
|
8
9
|
namespace audioapi {
|
|
9
10
|
|
|
@@ -49,7 +50,8 @@ bool AudioPlayer::openAudioStream() {
|
|
|
49
50
|
|
|
50
51
|
bool AudioPlayer::start() {
|
|
51
52
|
if (mStream_) {
|
|
52
|
-
|
|
53
|
+
jni::ThreadScope::WithClassLoader(
|
|
54
|
+
[this]() { nativeAudioPlayer_->start(); });
|
|
53
55
|
auto result = mStream_->requestStart();
|
|
54
56
|
return result == oboe::Result::OK;
|
|
55
57
|
}
|
|
@@ -59,7 +61,7 @@ bool AudioPlayer::start() {
|
|
|
59
61
|
|
|
60
62
|
void AudioPlayer::stop() {
|
|
61
63
|
if (mStream_) {
|
|
62
|
-
nativeAudioPlayer_->stop();
|
|
64
|
+
jni::ThreadScope::WithClassLoader([this]() { nativeAudioPlayer_->stop(); });
|
|
63
65
|
mStream_->requestStop();
|
|
64
66
|
}
|
|
65
67
|
}
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
package com.swmansion.audioapi
|
|
2
2
|
|
|
3
3
|
import com.facebook.jni.HybridData
|
|
4
|
+
import com.facebook.react.bridge.LifecycleEventListener
|
|
5
|
+
import com.facebook.react.bridge.NativeModule
|
|
4
6
|
import com.facebook.react.bridge.Promise
|
|
5
7
|
import com.facebook.react.bridge.ReactApplicationContext
|
|
6
8
|
import com.facebook.react.bridge.ReadableArray
|
|
@@ -16,7 +18,8 @@ import java.lang.ref.WeakReference
|
|
|
16
18
|
@ReactModule(name = AudioAPIModule.NAME)
|
|
17
19
|
class AudioAPIModule(
|
|
18
20
|
reactContext: ReactApplicationContext,
|
|
19
|
-
) : NativeAudioAPIModuleSpec(reactContext)
|
|
21
|
+
) : NativeAudioAPIModuleSpec(reactContext),
|
|
22
|
+
LifecycleEventListener {
|
|
20
23
|
companion object {
|
|
21
24
|
const val NAME = NativeAudioAPIModuleSpec.NAME
|
|
22
25
|
}
|
|
@@ -24,6 +27,7 @@ class AudioAPIModule(
|
|
|
24
27
|
val reactContext: WeakReference<ReactApplicationContext> = WeakReference(reactContext)
|
|
25
28
|
|
|
26
29
|
private val mHybridData: HybridData
|
|
30
|
+
private var reanimatedModule: NativeModule? = null
|
|
27
31
|
|
|
28
32
|
external fun initHybrid(
|
|
29
33
|
workletsModule: Any?,
|
|
@@ -38,6 +42,8 @@ class AudioAPIModule(
|
|
|
38
42
|
eventBody: Map<String, Any>,
|
|
39
43
|
)
|
|
40
44
|
|
|
45
|
+
private external fun closeAllContexts()
|
|
46
|
+
|
|
41
47
|
init {
|
|
42
48
|
try {
|
|
43
49
|
System.loadLibrary("react-native-audio-api")
|
|
@@ -47,6 +53,8 @@ class AudioAPIModule(
|
|
|
47
53
|
if (BuildConfig.RN_AUDIO_API_ENABLE_WORKLETS) {
|
|
48
54
|
try {
|
|
49
55
|
workletsModule = reactContext.getNativeModule("WorkletsModule")
|
|
56
|
+
reanimatedModule = reactContext.getNativeModule("ReanimatedModule")
|
|
57
|
+
reanimatedModule
|
|
50
58
|
} catch (ex: Exception) {
|
|
51
59
|
throw RuntimeException("WorkletsModule not found - make sure react-native-worklets is properly installed")
|
|
52
60
|
}
|
|
@@ -64,6 +72,26 @@ class AudioAPIModule(
|
|
|
64
72
|
return true
|
|
65
73
|
}
|
|
66
74
|
|
|
75
|
+
override fun onHostResume() {
|
|
76
|
+
// do nothing
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
override fun onHostPause() {
|
|
80
|
+
closeAllContexts()
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
override fun onHostDestroy() {
|
|
84
|
+
// do nothing
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
override fun initialize() {
|
|
88
|
+
reactContext.get()?.addLifecycleEventListener(this)
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
override fun invalidate() {
|
|
92
|
+
// think about cleaning up resources, singletons etc.
|
|
93
|
+
}
|
|
94
|
+
|
|
67
95
|
override fun getDevicePreferredSampleRate(): Double = MediaSessionManager.getDevicePreferredSampleRate()
|
|
68
96
|
|
|
69
97
|
override fun setAudioSessionActivity(
|
|
@@ -16,12 +16,16 @@
|
|
|
16
16
|
#include <audioapi/core/utils/worklets/SafeIncludes.h>
|
|
17
17
|
|
|
18
18
|
#include <memory>
|
|
19
|
+
#include <vector>
|
|
19
20
|
|
|
20
21
|
namespace audioapi {
|
|
21
22
|
|
|
22
23
|
using namespace facebook;
|
|
23
24
|
|
|
24
25
|
class AudioAPIModuleInstaller {
|
|
26
|
+
private:
|
|
27
|
+
inline static std::vector<std::weak_ptr<AudioContext>> contexts_ = {};
|
|
28
|
+
|
|
25
29
|
public:
|
|
26
30
|
static void injectJSIBindings(
|
|
27
31
|
jsi::Runtime *jsiRuntime,
|
|
@@ -61,6 +65,19 @@ class AudioAPIModuleInstaller {
|
|
|
61
65
|
*jsiRuntime, audioEventHandlerRegistryHostObject));
|
|
62
66
|
}
|
|
63
67
|
|
|
68
|
+
static void closeAllContexts() {
|
|
69
|
+
for (auto it = contexts_.begin(); it != contexts_.end(); ++it) {
|
|
70
|
+
auto weakContext = *it;
|
|
71
|
+
|
|
72
|
+
if (auto context = weakContext.lock()) {
|
|
73
|
+
context->close();
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
it = contexts_.erase(it);
|
|
77
|
+
--it;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
64
81
|
private:
|
|
65
82
|
static jsi::Function getCreateAudioContextFunction(
|
|
66
83
|
jsi::Runtime *jsiRuntime,
|
|
@@ -95,6 +112,8 @@ class AudioAPIModuleInstaller {
|
|
|
95
112
|
initSuspended,
|
|
96
113
|
audioEventHandlerRegistry,
|
|
97
114
|
runtimeRegistry);
|
|
115
|
+
AudioAPIModuleInstaller::contexts_.push_back(audioContext);
|
|
116
|
+
|
|
98
117
|
auto audioContextHostObject =
|
|
99
118
|
std::make_shared<AudioContextHostObject>(
|
|
100
119
|
audioContext, &runtime, jsCallInvoker);
|
|
@@ -138,6 +157,7 @@ class AudioAPIModuleInstaller {
|
|
|
138
157
|
sampleRate,
|
|
139
158
|
audioEventHandlerRegistry,
|
|
140
159
|
runtimeRegistry);
|
|
160
|
+
|
|
141
161
|
auto audioContextHostObject =
|
|
142
162
|
std::make_shared<OfflineAudioContextHostObject>(
|
|
143
163
|
offlineAudioContext, &runtime, jsCallInvoker);
|
|
@@ -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<
|
|
70
|
+
auto length = static_cast<size_t>(arrayBufferFrequency.size(runtime));
|
|
71
71
|
|
|
72
72
|
auto arrayBufferMag = args[1]
|
|
73
73
|
.getObject(runtime)
|
|
@@ -56,7 +56,7 @@ float AudioParam::getValueAtTime(double time) {
|
|
|
56
56
|
void AudioParam::setValueAtTime(float value, double startTime) {
|
|
57
57
|
auto event = [value, startTime](AudioParam ¶m) {
|
|
58
58
|
// Ignore events scheduled before the end of existing automation
|
|
59
|
-
if (startTime
|
|
59
|
+
if (startTime < param.getQueueEndTime()) {
|
|
60
60
|
return;
|
|
61
61
|
}
|
|
62
62
|
|
|
@@ -87,7 +87,7 @@ void AudioParam::setValueAtTime(float value, double startTime) {
|
|
|
87
87
|
void AudioParam::linearRampToValueAtTime(float value, double endTime) {
|
|
88
88
|
auto event = [value, endTime](AudioParam ¶m) {
|
|
89
89
|
// Ignore events scheduled before the end of existing automation
|
|
90
|
-
if (endTime
|
|
90
|
+
if (endTime < param.getQueueEndTime()) {
|
|
91
91
|
return;
|
|
92
92
|
}
|
|
93
93
|
|
|
@@ -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.
|
|
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.
|
|
49
|
+
1.0f, MOST_NEGATIVE_SINGLE_FLOAT, MOST_POSITIVE_SINGLE_FLOAT, context);
|
|
22
50
|
gainParam_ = std::make_shared<AudioParam>(
|
|
23
|
-
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
|
|
107
|
+
const size_t length) {
|
|
108
|
+
#ifndef AUDIO_API_TEST_SUITE
|
|
80
109
|
applyFilter();
|
|
110
|
+
#endif
|
|
81
111
|
|
|
82
|
-
//
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
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
|
-
|
|
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
|
-
|
|
97
|
-
auto z = std::complex<
|
|
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<
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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
|
-
|
|
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 =
|
|
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;
|
|
@@ -5,16 +5,6 @@
|
|
|
5
5
|
#include <string>
|
|
6
6
|
#include <memory>
|
|
7
7
|
|
|
8
|
-
#ifdef __APPLE__
|
|
9
|
-
/// We cannot make any conditional logic inside podspec but it should automatically compile those files
|
|
10
|
-
/// they should be accessible if someone has react-native-worklets in node_modules
|
|
11
|
-
#if __has_include(<worklets/WorkletRuntime/WorkletRuntime.h>)
|
|
12
|
-
#define RN_AUDIO_API_ENABLE_WORKLETS 1
|
|
13
|
-
#else
|
|
14
|
-
#define RN_AUDIO_API_ENABLE_WORKLETS 0
|
|
15
|
-
#endif
|
|
16
|
-
#endif
|
|
17
|
-
|
|
18
8
|
#ifndef RN_AUDIO_API_TEST
|
|
19
9
|
#define RN_AUDIO_API_TEST 0
|
|
20
10
|
#endif
|
|
@@ -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
|
-
|
|
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
|
|
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
|