react-native-audio-api 0.7.0-nightly-4fc09d1-20250727 → 0.7.0-nightly-a914864-20250729

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.
Files changed (61) hide show
  1. package/README.md +3 -4
  2. package/android/src/main/cpp/audioapi/android/core/AndroidAudioRecorder.cpp +3 -1
  3. package/common/cpp/audioapi/HostObjects/AudioNodeHostObject.cpp +9 -0
  4. package/common/cpp/audioapi/HostObjects/AudioNodeHostObject.h +2 -0
  5. package/common/cpp/audioapi/HostObjects/AudioRecorderHostObject.h +17 -1
  6. package/common/cpp/audioapi/HostObjects/BaseAudioContextHostObject.cpp +9 -0
  7. package/common/cpp/audioapi/HostObjects/BaseAudioContextHostObject.h +18 -16
  8. package/common/cpp/audioapi/HostObjects/RecorderAdapterNodeHostObject.h +26 -0
  9. package/common/cpp/audioapi/core/BaseAudioContext.cpp +9 -10
  10. package/common/cpp/audioapi/core/BaseAudioContext.h +3 -2
  11. package/common/cpp/audioapi/core/inputs/AudioRecorder.cpp +28 -2
  12. package/common/cpp/audioapi/core/inputs/AudioRecorder.h +24 -0
  13. package/common/cpp/audioapi/core/sources/RecorderAdapterNode.cpp +48 -0
  14. package/common/cpp/audioapi/core/sources/RecorderAdapterNode.h +43 -0
  15. package/common/cpp/audioapi/jsi/AudioArrayBuffer.h +1 -1
  16. package/common/cpp/audioapi/utils/CircularOverflowableAudioArray.cpp +61 -0
  17. package/common/cpp/audioapi/utils/CircularOverflowableAudioArray.h +42 -0
  18. package/ios/audioapi/ios/core/IOSAudioRecorder.mm +2 -1
  19. package/ios/audioapi/ios/core/NativeAudioRecorder.m +7 -0
  20. package/lib/commonjs/api.js +7 -7
  21. package/lib/commonjs/api.js.map +1 -1
  22. package/lib/commonjs/core/AudioRecorder.js +10 -0
  23. package/lib/commonjs/core/AudioRecorder.js.map +1 -1
  24. package/lib/commonjs/core/BaseAudioContext.js +4 -4
  25. package/lib/commonjs/core/BaseAudioContext.js.map +1 -1
  26. package/lib/commonjs/core/RecorderAdapterNode.js +19 -0
  27. package/lib/commonjs/core/RecorderAdapterNode.js.map +1 -0
  28. package/lib/module/api.js +1 -1
  29. package/lib/module/api.js.map +1 -1
  30. package/lib/module/core/AudioRecorder.js +10 -0
  31. package/lib/module/core/AudioRecorder.js.map +1 -1
  32. package/lib/module/core/BaseAudioContext.js +4 -4
  33. package/lib/module/core/BaseAudioContext.js.map +1 -1
  34. package/lib/module/core/RecorderAdapterNode.js +13 -0
  35. package/lib/module/core/RecorderAdapterNode.js.map +1 -0
  36. package/lib/typescript/api.d.ts +1 -1
  37. package/lib/typescript/api.d.ts.map +1 -1
  38. package/lib/typescript/core/AudioRecorder.d.ts +3 -0
  39. package/lib/typescript/core/AudioRecorder.d.ts.map +1 -1
  40. package/lib/typescript/core/BaseAudioContext.d.ts +3 -3
  41. package/lib/typescript/core/BaseAudioContext.d.ts.map +1 -1
  42. package/lib/typescript/core/RecorderAdapterNode.d.ts +9 -0
  43. package/lib/typescript/core/RecorderAdapterNode.d.ts.map +1 -0
  44. package/lib/typescript/interfaces.d.ts +6 -6
  45. package/lib/typescript/interfaces.d.ts.map +1 -1
  46. package/package.json +1 -1
  47. package/src/api.ts +1 -1
  48. package/src/core/AudioRecorder.ts +16 -0
  49. package/src/core/BaseAudioContext.ts +6 -9
  50. package/src/core/RecorderAdapterNode.ts +12 -0
  51. package/src/interfaces.ts +5 -7
  52. package/common/cpp/audioapi/HostObjects/CustomProcessorNodeHostObject.h +0 -54
  53. package/common/cpp/audioapi/core/effects/CustomProcessorNode.cpp +0 -173
  54. package/common/cpp/audioapi/core/effects/CustomProcessorNode.h +0 -186
  55. package/lib/commonjs/core/CustomProcessorNode.js +0 -23
  56. package/lib/commonjs/core/CustomProcessorNode.js.map +0 -1
  57. package/lib/module/core/CustomProcessorNode.js +0 -17
  58. package/lib/module/core/CustomProcessorNode.js.map +0 -1
  59. package/lib/typescript/core/CustomProcessorNode.d.ts +0 -12
  60. package/lib/typescript/core/CustomProcessorNode.d.ts.map +0 -1
  61. package/src/core/CustomProcessorNode.ts +0 -28
package/README.md CHANGED
@@ -3,7 +3,6 @@
3
3
  ### High-performance audio engine for React Native based on web audio api specification
4
4
 
5
5
  [![NPM latest](https://img.shields.io/npm/v/react-native-audio-api/latest)](https://www.npmjs.com/package/react-native-audio-api)
6
- [![NPM next](https://img.shields.io/npm/v/react-native-audio-api/next)](https://www.npmjs.com/package/react-native-audio-api?activeTab=versions)
7
6
  [![NPM nightly](https://img.shields.io/npm/v/react-native-audio-api/audio-api-nightly)](https://www.npmjs.com/package/react-native-audio-api?activeTab=versions)
8
7
  [![github ci](https://github.com/software-mansion/react-native-audio-api/actions/workflows/ci.yml/badge.svg)](https://github.com/software-mansion/react-native-audio-api/actions/workflows/ci.yml)
9
8
  [![NPM Audio Api publish nightly](https://github.com/software-mansion/react-native-audio-api/actions/workflows/npm-publish-nightly.yml/badge.svg)](https://github.com/software-mansion/react-native-audio-api/actions/workflows/npm-publish-nightly.yml)
@@ -40,12 +39,12 @@ check out the [Getting Started](https://docs.swmansion.com/react-native-audio-ap
40
39
  - <sub>[![Released in 0.6.0](https://img.shields.io/badge/Released_in-0.6.0-green)](https://github.com/software-mansion/react-native-audio-api/releases/tag/0.6.0)</sub> **System configuration** 🛠️ <br />
41
40
  Full control of system audio settings, remote controls, lock screen integration and most importantly configurable background modes <br />
42
41
 
42
+ - <sub>[![Released in 0.6.0](https://img.shields.io/badge/Released_in-0.6.0-green)](https://github.com/software-mansion/react-native-audio-api/releases/tag/0.6.0)</sub> **Connect audio param** 🤞 <br />
43
+ Ability to connect Audio nodes to audio params, which will allow for powerful and efficient modulation of audio parameters, creating effects like tremolo, vibrato or complex envelope followers. <br />
44
+
43
45
  - **Microphone support** 🎙️ <br />
44
46
  Grab audio data from device microphone or connected device, connect it to the audio graph or stream through the internet <br />
45
47
 
46
- - **Connect audio param** 🤞 <br />
47
- Ability to connect Audio nodes to audio params, which will allow for powerful and efficient modulation of audio parameters, creating effects like tremolo, vibrato or complex envelope followers. <br />
48
-
49
48
  - **JS Audio Worklets** 🐎 <br />
50
49
  Ability to run JS functions connected to the audio graph running on audio thread allowing for full customization of what happens to the audio signal.
51
50
  <br />
@@ -1,9 +1,11 @@
1
1
  #include <audioapi/android/core/AndroidAudioRecorder.h>
2
2
  #include <audioapi/core/Constants.h>
3
+ #include <audioapi/core/sources/RecorderAdapterNode.h>
3
4
  #include <audioapi/events/AudioEventHandlerRegistry.h>
4
5
  #include <audioapi/utils/AudioArray.h>
5
6
  #include <audioapi/utils/AudioBus.h>
6
7
  #include <audioapi/utils/CircularAudioArray.h>
8
+ #include <audioapi/utils/CircularOverflowableAudioArray.h>
7
9
 
8
10
  namespace audioapi {
9
11
 
@@ -65,7 +67,7 @@ DataCallbackResult AndroidAudioRecorder::onAudioReady(
65
67
  int32_t numFrames) {
66
68
  if (isRunning_.load()) {
67
69
  auto *inputChannel = static_cast<float *>(audioData);
68
- circularBuffer_->push_back(inputChannel, numFrames);
70
+ writeToBuffers(inputChannel, numFrames);
69
71
  }
70
72
 
71
73
  while (circularBuffer_->getNumberOfAvailableFrames() >= bufferLength_) {
@@ -0,0 +1,9 @@
1
+ #include <audioapi/HostObjects/AudioNodeHostObject.h>
2
+
3
+ // Explicitly define destructors here, as they to exist in order to act as a
4
+ // "key function" for the audio classes - this allow for RTTI to work
5
+ // properly across dynamic library boundaries (i.e. dynamic_cast that is used by
6
+ // isHostObject method), android specific issue
7
+ namespace audioapi {
8
+ AudioNodeHostObject::~AudioNodeHostObject() = default;
9
+ }
@@ -26,6 +26,8 @@ class AudioNodeHostObject : public JsiHostObject {
26
26
  JSI_EXPORT_FUNCTION(AudioNodeHostObject, disconnect));
27
27
  }
28
28
 
29
+ ~AudioNodeHostObject() override;
30
+
29
31
  JSI_PROPERTY_GETTER(numberOfInputs) {
30
32
  return {node_->getNumberOfInputs()};
31
33
  }
@@ -5,6 +5,7 @@
5
5
  #include <audioapi/core/sources/AudioBuffer.h>
6
6
  #include <audioapi/HostObjects/AudioBufferHostObject.h>
7
7
  #include <audioapi/core/inputs/AudioRecorder.h>
8
+ #include <audioapi/HostObjects/RecorderAdapterNodeHostObject.h>
8
9
 
9
10
  #ifdef ANDROID
10
11
  #include <audioapi/android/core/AndroidAudioRecorder.h>
@@ -45,7 +46,22 @@ class AudioRecorderHostObject : public JsiHostObject {
45
46
 
46
47
  addFunctions(
47
48
  JSI_EXPORT_FUNCTION(AudioRecorderHostObject, start),
48
- JSI_EXPORT_FUNCTION(AudioRecorderHostObject, stop));
49
+ JSI_EXPORT_FUNCTION(AudioRecorderHostObject, stop),
50
+ JSI_EXPORT_FUNCTION(AudioRecorderHostObject, connect),
51
+ JSI_EXPORT_FUNCTION(AudioRecorderHostObject, disconnect)
52
+ );
53
+ }
54
+
55
+ JSI_HOST_FUNCTION(connect) {
56
+ auto adapterNodeHostObject = args[0].getObject(runtime).getHostObject<RecorderAdapterNodeHostObject>(runtime);
57
+ audioRecorder_->connect(
58
+ std::static_pointer_cast<RecorderAdapterNode>(adapterNodeHostObject->node_));
59
+ return jsi::Value::undefined();
60
+ }
61
+
62
+ JSI_HOST_FUNCTION(disconnect) {
63
+ audioRecorder_->disconnect();
64
+ return jsi::Value::undefined();
49
65
  }
50
66
 
51
67
  JSI_HOST_FUNCTION(start) {
@@ -0,0 +1,9 @@
1
+ #include <audioapi/HostObjects/BaseAudioContextHostObject.h>
2
+
3
+ // Explicitly define destructors here, as they to exist in order to act as a
4
+ // "key function" for the audio classes - this allow for RTTI to work
5
+ // properly across dynamic library boundaries (i.e. dynamic_cast that is used by
6
+ // isHostObject method), android specific issue
7
+ namespace audioapi {
8
+ BaseAudioContextHostObject::~BaseAudioContextHostObject() = default;
9
+ }
@@ -8,12 +8,12 @@
8
8
  #include <audioapi/HostObjects/AudioDestinationNodeHostObject.h>
9
9
  #include <audioapi/core/BaseAudioContext.h>
10
10
  #include <audioapi/HostObjects/BiquadFilterNodeHostObject.h>
11
- #include <audioapi/HostObjects/CustomProcessorNodeHostObject.h>
12
11
  #include <audioapi/HostObjects/GainNodeHostObject.h>
13
12
  #include <audioapi/HostObjects/OscillatorNodeHostObject.h>
14
13
  #include <audioapi/HostObjects/PeriodicWaveHostObject.h>
15
14
  #include <audioapi/HostObjects/StereoPannerNodeHostObject.h>
16
15
  #include <audioapi/HostObjects/AnalyserNodeHostObject.h>
16
+ #include <audioapi/HostObjects/RecorderAdapterNodeHostObject.h>
17
17
 
18
18
  #include <jsi/jsi.h>
19
19
  #include <memory>
@@ -40,8 +40,8 @@ class BaseAudioContextHostObject : public JsiHostObject {
40
40
  JSI_EXPORT_PROPERTY_GETTER(BaseAudioContextHostObject, currentTime));
41
41
 
42
42
  addFunctions(
43
+ JSI_EXPORT_FUNCTION(BaseAudioContextHostObject, createRecorderAdapter),
43
44
  JSI_EXPORT_FUNCTION(BaseAudioContextHostObject, createOscillator),
44
- JSI_EXPORT_FUNCTION(BaseAudioContextHostObject, createCustomProcessor),
45
45
  JSI_EXPORT_FUNCTION(BaseAudioContextHostObject, createGain),
46
46
  JSI_EXPORT_FUNCTION(BaseAudioContextHostObject, createStereoPanner),
47
47
  JSI_EXPORT_FUNCTION(BaseAudioContextHostObject, createBiquadFilter),
@@ -55,6 +55,8 @@ class BaseAudioContextHostObject : public JsiHostObject {
55
55
  JSI_EXPORT_FUNCTION(BaseAudioContextHostObject, decodePCMAudioDataInBase64));
56
56
  }
57
57
 
58
+ ~BaseAudioContextHostObject() override;
59
+
58
60
  JSI_PROPERTY_GETTER(destination) {
59
61
  auto destination = std::make_shared<AudioDestinationNodeHostObject>(
60
62
  context_->getDestination());
@@ -73,6 +75,12 @@ class BaseAudioContextHostObject : public JsiHostObject {
73
75
  return {context_->getCurrentTime()};
74
76
  }
75
77
 
78
+ JSI_HOST_FUNCTION(createRecorderAdapter) {
79
+ auto recorderAdapter = context_->createRecorderAdapter();
80
+ auto recorderAdapterHostObject = std::make_shared<RecorderAdapterNodeHostObject>(recorderAdapter);
81
+ return jsi::Object::createFromHostObject(runtime, recorderAdapterHostObject);
82
+ }
83
+
76
84
  JSI_HOST_FUNCTION(createOscillator) {
77
85
  auto oscillator = context_->createOscillator();
78
86
  auto oscillatorHostObject =
@@ -80,13 +88,6 @@ class BaseAudioContextHostObject : public JsiHostObject {
80
88
  return jsi::Object::createFromHostObject(runtime, oscillatorHostObject);
81
89
  }
82
90
 
83
- JSI_HOST_FUNCTION(createCustomProcessor) {
84
- auto identifier = args[0].getString(runtime).utf8(runtime);
85
- auto customProcessor = context_->createCustomProcessor(identifier);
86
- auto customProcessorHostObject = std::make_shared<CustomProcessorNodeHostObject>(customProcessor);
87
- return jsi::Object::createFromHostObject(runtime, customProcessorHostObject);
88
- }
89
-
90
91
  JSI_HOST_FUNCTION(createGain) {
91
92
  auto gain = context_->createGain();
92
93
  auto gainHostObject = std::make_shared<GainNodeHostObject>(gain);
@@ -115,12 +116,12 @@ class BaseAudioContextHostObject : public JsiHostObject {
115
116
  return jsi::Object::createFromHostObject(runtime, bufferSourceHostObject);
116
117
  }
117
118
 
118
- JSI_HOST_FUNCTION(createBufferQueueSource) {
119
- auto bufferSource = context_->createBufferQueueSource();
120
- auto bufferStreamSourceHostObject =
121
- std::make_shared<AudioBufferQueueSourceNodeHostObject>(bufferSource);
122
- return jsi::Object::createFromHostObject(runtime, bufferStreamSourceHostObject);
123
- }
119
+ JSI_HOST_FUNCTION(createBufferQueueSource) {
120
+ auto bufferSource = context_->createBufferQueueSource();
121
+ auto bufferStreamSourceHostObject =
122
+ std::make_shared<AudioBufferQueueSourceNodeHostObject>(bufferSource);
123
+ return jsi::Object::createFromHostObject(runtime, bufferStreamSourceHostObject);
124
+ }
124
125
 
125
126
  JSI_HOST_FUNCTION(createBuffer) {
126
127
  auto numberOfChannels = static_cast<int>(args[0].getNumber());
@@ -245,8 +246,9 @@ JSI_HOST_FUNCTION(createBufferQueueSource) {
245
246
  return promise;
246
247
  }
247
248
 
249
+ std::shared_ptr<BaseAudioContext> context_;
250
+
248
251
  protected:
249
- std::shared_ptr<BaseAudioContext> context_;
250
252
  std::shared_ptr<PromiseVendor> promiseVendor_;
251
253
  std::shared_ptr<react::CallInvoker> callInvoker_;
252
254
  };
@@ -0,0 +1,26 @@
1
+ #pragma once
2
+
3
+ #include <audioapi/core/sources/RecorderAdapterNode.h>
4
+ #include <audioapi/HostObjects/AudioNodeHostObject.h>
5
+
6
+ #include <memory>
7
+ #include <string>
8
+ #include <vector>
9
+
10
+ namespace audioapi {
11
+ using namespace facebook;
12
+
13
+ class AudioRecorderHostObject;
14
+
15
+ class RecorderAdapterNodeHostObject : public AudioNodeHostObject {
16
+ public:
17
+ explicit RecorderAdapterNodeHostObject(
18
+ const std::shared_ptr<RecorderAdapterNode> &node)
19
+ : AudioNodeHostObject(node) {
20
+ }
21
+
22
+ private:
23
+ friend class AudioRecorderHostObject;
24
+ };
25
+
26
+ } // namespace audioapi
@@ -2,13 +2,13 @@
2
2
  #include <audioapi/core/analysis/AnalyserNode.h>
3
3
  #include <audioapi/core/destinations/AudioDestinationNode.h>
4
4
  #include <audioapi/core/effects/BiquadFilterNode.h>
5
- #include <audioapi/core/effects/CustomProcessorNode.h>
6
5
  #include <audioapi/core/effects/GainNode.h>
7
6
  #include <audioapi/core/effects/StereoPannerNode.h>
8
7
  #include <audioapi/core/sources/AudioBuffer.h>
9
8
  #include <audioapi/core/sources/AudioBufferQueueSourceNode.h>
10
9
  #include <audioapi/core/sources/AudioBufferSourceNode.h>
11
10
  #include <audioapi/core/sources/OscillatorNode.h>
11
+ #include <audioapi/core/sources/RecorderAdapterNode.h>
12
12
  #include <audioapi/core/utils/AudioDecoder.h>
13
13
  #include <audioapi/core/utils/AudioNodeManager.h>
14
14
  #include <audioapi/events/AudioEventHandlerRegistry.h>
@@ -49,20 +49,18 @@ std::shared_ptr<AudioDestinationNode> BaseAudioContext::getDestination() {
49
49
  return destination_;
50
50
  }
51
51
 
52
+ std::shared_ptr<RecorderAdapterNode> BaseAudioContext::createRecorderAdapter() {
53
+ auto recorderAdapter = std::make_shared<RecorderAdapterNode>(this);
54
+ nodeManager_->addProcessingNode(recorderAdapter);
55
+ return recorderAdapter;
56
+ }
57
+
52
58
  std::shared_ptr<OscillatorNode> BaseAudioContext::createOscillator() {
53
59
  auto oscillator = std::make_shared<OscillatorNode>(this);
54
60
  nodeManager_->addSourceNode(oscillator);
55
61
  return oscillator;
56
62
  }
57
63
 
58
- std::shared_ptr<CustomProcessorNode> BaseAudioContext::createCustomProcessor(
59
- const std::string &identifier) {
60
- auto customProcessor =
61
- std::make_shared<CustomProcessorNode>(this, identifier);
62
- nodeManager_->addProcessingNode(customProcessor);
63
- return customProcessor;
64
- }
65
-
66
64
  std::shared_ptr<GainNode> BaseAudioContext::createGain() {
67
65
  auto gain = std::make_shared<GainNode>(this);
68
66
  nodeManager_->addProcessingNode(gain);
@@ -141,7 +139,8 @@ std::shared_ptr<AudioBuffer> BaseAudioContext::decodeAudioData(
141
139
  }
142
140
 
143
141
  std::shared_ptr<AudioBuffer> BaseAudioContext::decodeWithPCMInBase64(
144
- const std::string &data, float playbackSpeed) {
142
+ const std::string &data,
143
+ float playbackSpeed) {
145
144
  auto audioBus = audioDecoder_->decodeWithPCMInBase64(data, playbackSpeed);
146
145
 
147
146
  if (!audioBus) {
@@ -3,6 +3,7 @@
3
3
  #include <audioapi/core/types/ContextState.h>
4
4
  #include <audioapi/core/types/OscillatorType.h>
5
5
 
6
+
6
7
  #include <functional>
7
8
  #include <memory>
8
9
  #include <string>
@@ -15,7 +16,6 @@
15
16
  namespace audioapi {
16
17
 
17
18
  class AudioBus;
18
- class CustomProcessorNode;
19
19
  class GainNode;
20
20
  class AudioBuffer;
21
21
  class PeriodicWave;
@@ -30,6 +30,7 @@ class AudioDecoder;
30
30
  class AnalyserNode;
31
31
  class AudioEventHandlerRegistry;
32
32
  class IAudioEventHandlerRegistry;
33
+ class RecorderAdapterNode;
33
34
 
34
35
  class BaseAudioContext {
35
36
  public:
@@ -42,8 +43,8 @@ class BaseAudioContext {
42
43
  [[nodiscard]] std::size_t getCurrentSampleFrame() const;
43
44
  std::shared_ptr<AudioDestinationNode> getDestination();
44
45
 
46
+ std::shared_ptr<RecorderAdapterNode> createRecorderAdapter();
45
47
  std::shared_ptr<OscillatorNode> createOscillator();
46
- std::shared_ptr<CustomProcessorNode> createCustomProcessor(const std::string& identifier);
47
48
  std::shared_ptr<GainNode> createGain();
48
49
  std::shared_ptr<StereoPannerNode> createStereoPanner();
49
50
  std::shared_ptr<BiquadFilterNode> createBiquadFilter();
@@ -1,9 +1,11 @@
1
1
  #include <audioapi/HostObjects/AudioBufferHostObject.h>
2
2
  #include <audioapi/core/inputs/AudioRecorder.h>
3
3
  #include <audioapi/core/sources/AudioBuffer.h>
4
+ #include <audioapi/core/sources/RecorderAdapterNode.h>
4
5
  #include <audioapi/events/AudioEventHandlerRegistry.h>
5
6
  #include <audioapi/utils/AudioBus.h>
6
7
  #include <audioapi/utils/CircularAudioArray.h>
8
+ #include <audioapi/utils/CircularOverflowableAudioArray.h>
7
9
 
8
10
  namespace audioapi {
9
11
 
@@ -14,8 +16,9 @@ AudioRecorder::AudioRecorder(
14
16
  : sampleRate_(sampleRate),
15
17
  bufferLength_(bufferLength),
16
18
  audioEventHandlerRegistry_(audioEventHandlerRegistry) {
17
- circularBuffer_ =
18
- std::make_shared<CircularAudioArray>(std::max(2 * bufferLength, 2048));
19
+ constexpr int minRingBufferSize = 8192;
20
+ ringBufferSize_ = std::max(2 * bufferLength, minRingBufferSize);
21
+ circularBuffer_ = std::make_shared<CircularAudioArray>(ringBufferSize_);
19
22
  isRunning_.store(false);
20
23
  }
21
24
 
@@ -55,4 +58,27 @@ void AudioRecorder::sendRemainingData() {
55
58
  invokeOnAudioReadyCallback(bus, availableFrames, 0);
56
59
  }
57
60
 
61
+ void AudioRecorder::connect(const std::shared_ptr<RecorderAdapterNode> &node) {
62
+ node->init(ringBufferSize_);
63
+ adapterNodeLock_.lock();
64
+ adapterNode_ = node;
65
+ adapterNodeLock_.unlock();
66
+ }
67
+
68
+ void AudioRecorder::disconnect() {
69
+ adapterNodeLock_.lock();
70
+ adapterNode_ = nullptr;
71
+ adapterNodeLock_.unlock();
72
+ }
73
+
74
+ void AudioRecorder::writeToBuffers(const float *data, int numFrames) {
75
+ if (adapterNodeLock_.try_lock()) {
76
+ if (adapterNode_ != nullptr) {
77
+ adapterNode_->buff_->write(data, numFrames);
78
+ }
79
+ adapterNodeLock_.unlock();
80
+ }
81
+ circularBuffer_->push_back(data, numFrames);
82
+ }
83
+
58
84
  } // namespace audioapi
@@ -1,11 +1,15 @@
1
1
  #pragma once
2
2
 
3
+
3
4
  #include <memory>
5
+ #include <mutex>
4
6
  #include <atomic>
5
7
 
6
8
  namespace audioapi {
9
+ class RecorderAdapterNode;
7
10
  class AudioBus;
8
11
  class CircularAudioArray;
12
+ class CircularOverflowableAudioArray;
9
13
  class AudioEventHandlerRegistry;
10
14
 
11
15
  class AudioRecorder {
@@ -22,18 +26,38 @@ class AudioRecorder {
22
26
  void invokeOnAudioReadyCallback(const std::shared_ptr<AudioBus> &bus, int numFrames, double when);
23
27
  void sendRemainingData();
24
28
 
29
+ /// @brief
30
+ /// # Connects the recorder to the adapter node.
31
+ ///
32
+ /// The adapter node will be used to read audio data from the recorder.
33
+ /// @note Few frames of audio might not yet be written to the buffer when connecting.
34
+ void connect(const std::shared_ptr<RecorderAdapterNode> &node);
35
+
36
+ /// @brief
37
+ /// # Disconnects the recorder from the adapter node.
38
+ ///
39
+ /// The adapter node will no longer be used to read audio data from the recorder.
40
+ /// @note Last few frames of audio might be written to the buffer after disconnecting.
41
+ void disconnect();
42
+
25
43
  virtual void start() = 0;
26
44
  virtual void stop() = 0;
27
45
 
28
46
  protected:
29
47
  float sampleRate_;
30
48
  int bufferLength_;
49
+ size_t ringBufferSize_;
31
50
 
32
51
  std::atomic<bool> isRunning_;
33
52
  std::shared_ptr<CircularAudioArray> circularBuffer_;
34
53
 
54
+ mutable std::mutex adapterNodeLock_;
55
+ std::shared_ptr<RecorderAdapterNode> adapterNode_ = nullptr;
56
+
35
57
  std::shared_ptr<AudioEventHandlerRegistry> audioEventHandlerRegistry_;
36
58
  uint64_t onAudioReadyCallbackId_ = 0;
59
+
60
+ void writeToBuffers(const float *data, int numFrames);
37
61
  };
38
62
 
39
63
  } // namespace audioapi
@@ -0,0 +1,48 @@
1
+
2
+ #include <audioapi/core/sources/RecorderAdapterNode.h>
3
+ #include <audioapi/utils/AudioArray.h>
4
+ #include <audioapi/utils/AudioBus.h>
5
+ #include <type_traits>
6
+
7
+ namespace audioapi {
8
+
9
+ RecorderAdapterNode::RecorderAdapterNode(BaseAudioContext *context) noexcept(
10
+ std::is_nothrow_constructible<AudioNode, BaseAudioContext *>::value)
11
+ : AudioNode(context) {
12
+ // It should be marked as initialized only after it is connected to the
13
+ // recorder. Internall buffer size is based on the recorder's buffer length.
14
+ isInitialized_ = false;
15
+ }
16
+
17
+ void RecorderAdapterNode::init(size_t bufferSize) {
18
+ if (isInitialized_) {
19
+ throw std::runtime_error(
20
+ "RecorderAdapterNode should not be initialized more than once. Just create a new instance.");
21
+ }
22
+ isInitialized_ = true;
23
+ buff_ = std::make_shared<CircularOverflowableAudioArray>(bufferSize);
24
+ }
25
+
26
+ void RecorderAdapterNode::processNode(
27
+ const std::shared_ptr<AudioBus> &processingBus,
28
+ int framesToProcess) {
29
+ float *outputChannel = processingBus->getChannel(0)->getData();
30
+ readFrames(outputChannel, framesToProcess);
31
+
32
+ for (int i = 1; i < processingBus->getNumberOfChannels(); i++) {
33
+ processingBus->getChannel(i)->copy(
34
+ processingBus->getChannel(0), 0, framesToProcess);
35
+ }
36
+ }
37
+
38
+ void RecorderAdapterNode::readFrames(float *output, const size_t framesToRead) {
39
+ size_t readFrames = buff_->read(output, framesToRead);
40
+
41
+ if (readFrames < framesToRead) {
42
+ // Fill the rest with silence
43
+ std::memset(
44
+ output + readFrames, 0, (framesToRead - readFrames) * sizeof(float));
45
+ }
46
+ }
47
+
48
+ } // namespace audioapi
@@ -0,0 +1,43 @@
1
+ #pragma once
2
+
3
+ #include <audioapi/core/AudioParam.h>
4
+ #include <audioapi/core/AudioNode.h>
5
+ #include <audioapi/core/inputs/AudioRecorder.h>
6
+ #include <audioapi/core/BaseAudioContext.h>
7
+ #include <audioapi/utils/CircularOverflowableAudioArray.h>
8
+ #include <memory>
9
+
10
+ namespace audioapi {
11
+
12
+ class AudioBus;
13
+
14
+ /// @brief RecorderAdapterNode is an AudioNode which adapts push Recorder into pull graph.
15
+ /// It uses RingBuffer to store audio data and AudioParam to provide audio data in pull mode.
16
+ /// It is used to connect native audio recording APIs with Audio API.
17
+ ///
18
+ /// @note it will push silence if it is not connected to any Recorder
19
+ class RecorderAdapterNode : public AudioNode {
20
+ public:
21
+ explicit RecorderAdapterNode(BaseAudioContext *context);
22
+
23
+ /// @brief Initialize the RecorderAdapterNode with a buffer size.
24
+ /// @note This method should be called ONLY ONCE when the buffer size is known.
25
+ /// @throws std::runtime_error if the node is already initialized.
26
+ /// @param bufferSize The size of the buffer to be used.
27
+ void init(size_t bufferSize);
28
+
29
+ protected:
30
+ void processNode(const std::shared_ptr<AudioBus>& processingBus, int framesToProcess) override;
31
+ std::shared_ptr<CircularOverflowableAudioArray> buff_;
32
+
33
+ private:
34
+ /// @brief Read audio frames from the recorder's internal adapterBuffer.
35
+ /// @note If `framesToRead` is greater than the number of available frames, it will fill empty space with silence.
36
+ /// @param output Pointer to the output buffer.
37
+ /// @param framesToRead Number of frames to read.
38
+ void readFrames(float *output, size_t framesToRead);
39
+
40
+ friend class AudioRecorder;
41
+ };
42
+
43
+ } // namespace audioapi
@@ -8,7 +8,7 @@ using namespace facebook;
8
8
 
9
9
  class AudioArrayBuffer : public jsi::MutableBuffer {
10
10
  public:
11
- AudioArrayBuffer(uint8_t *data, const size_t size): data_(data), size_(size) {}
11
+ AudioArrayBuffer(uint8_t *data, size_t size): data_(data), size_(size) {}
12
12
  ~AudioArrayBuffer() override = default;
13
13
 
14
14
  [[nodiscard]] size_t size() const override;
@@ -0,0 +1,61 @@
1
+ #include <audioapi/utils/CircularOverflowableAudioArray.h>
2
+ #include <type_traits>
3
+
4
+ namespace audioapi {
5
+
6
+ CircularOverflowableAudioArray::CircularOverflowableAudioArray(
7
+ size_t
8
+ size) noexcept(std::is_nothrow_constructible<AudioArray, size_t>::value)
9
+ : AudioArray(size) {}
10
+
11
+ void CircularOverflowableAudioArray::write(
12
+ const float *data,
13
+ const size_t size) {
14
+ size_t writeIndex = vWriteIndex_.load(std::memory_order_relaxed);
15
+
16
+ if (size > size_) {
17
+ return; // Ignore write if size exceeds buffer size
18
+ }
19
+
20
+ /// Advances the read index if there is not enough space
21
+ readLock_.lock();
22
+ size_t availableSpace = (size_ + vReadIndex_ - writeIndex - 1) % size_;
23
+ if (size > availableSpace) {
24
+ vReadIndex_ = (writeIndex + size + 1) % size_;
25
+ }
26
+ readLock_.unlock();
27
+
28
+ size_t partSize = size_ - writeIndex;
29
+ if (size > partSize) {
30
+ std::memcpy(data_ + writeIndex, data, partSize * sizeof(float));
31
+ std::memcpy(data_, data + partSize, (size - partSize) * sizeof(float));
32
+ } else {
33
+ std::memcpy(data_ + writeIndex, data, size * sizeof(float));
34
+ }
35
+ vWriteIndex_.store((writeIndex + size) % size_, std::memory_order_relaxed);
36
+ }
37
+
38
+ size_t CircularOverflowableAudioArray::read(float *output, size_t size) const {
39
+ readLock_.lock();
40
+ size_t availableSpace = getAvailableSpace();
41
+ size_t readSize = std::min(size, availableSpace);
42
+
43
+ size_t partSize = size_ - vReadIndex_;
44
+ if (readSize > partSize) {
45
+ std::memcpy(output, data_ + vReadIndex_, partSize * sizeof(float));
46
+ std::memcpy(
47
+ output + partSize, data_, (readSize - partSize) * sizeof(float));
48
+ } else {
49
+ std::memcpy(output, data_ + vReadIndex_, readSize * sizeof(float));
50
+ }
51
+ vReadIndex_ = (vReadIndex_ + readSize) % size_;
52
+ readLock_.unlock();
53
+ return readSize;
54
+ }
55
+
56
+ size_t CircularOverflowableAudioArray::getAvailableSpace() const {
57
+ return (size_ + vWriteIndex_.load(std::memory_order_relaxed) - vReadIndex_) %
58
+ size_;
59
+ }
60
+
61
+ } // namespace audioapi
@@ -0,0 +1,42 @@
1
+
2
+ #pragma once
3
+
4
+ #include <audioapi/utils/AudioArray.h>
5
+ #include <stdexcept>
6
+ #include <atomic>
7
+ #include <mutex>
8
+
9
+ namespace audioapi {
10
+
11
+ /// @brief CircularOverflowableAudioArray is a circular audio array that allows for overflow.
12
+ /// It provides a way to push data safely from one thread while reading from another.
13
+ /// It is designed to handle cases where the write index may be updated while the read index is being read.
14
+ /// It has read precedence, meaning that read locks are acquired before write locks.
15
+ /// @note read can fail when there are a lot of writes and buffer is small.
16
+ class CircularOverflowableAudioArray : public AudioArray {
17
+ public:
18
+ explicit CircularOverflowableAudioArray(size_t size);
19
+ CircularOverflowableAudioArray(const CircularOverflowableAudioArray &other) = default;
20
+ ~CircularOverflowableAudioArray() = default;
21
+
22
+ /// @brief Writes data to the circular buffer.
23
+ /// @note Might wait for read operation to finish if it is in progress. It ignores writes that exceed the buffer size.
24
+ /// @param data Pointer to the input buffer.
25
+ /// @param size Number of frames to write.
26
+ void write(const float *data, size_t size);
27
+
28
+ /// @brief Reads data from the circular buffer.
29
+ /// @param output Pointer to the output buffer.
30
+ /// @param size Number of frames to read.
31
+ /// @return The number of frames actually read.
32
+ size_t read(float *output, size_t size) const;
33
+
34
+ private:
35
+ std::atomic<size_t> vWriteIndex_ = { 0 };
36
+ mutable size_t vReadIndex_ = 0; // it is only used after acquiring readLock_
37
+ mutable std::mutex readLock_;
38
+
39
+ [[nodiscard]] size_t getAvailableSpace() const;
40
+ };
41
+
42
+ } // namespace audioapi
@@ -7,6 +7,7 @@
7
7
  #include <audioapi/utils/AudioArray.h>
8
8
  #include <audioapi/utils/AudioBus.h>
9
9
  #include <audioapi/utils/CircularAudioArray.h>
10
+ #include <audioapi/utils/CircularOverflowableAudioArray.h>
10
11
  #include <unordered_map>
11
12
 
12
13
  namespace audioapi {
@@ -20,7 +21,7 @@ IOSAudioRecorder::IOSAudioRecorder(
20
21
  AudioReceiverBlock audioReceiverBlock = ^(const AudioBufferList *inputBuffer, int numFrames, AVAudioTime *when) {
21
22
  if (isRunning_.load()) {
22
23
  auto *inputChannel = static_cast<float *>(inputBuffer->mBuffers[0].mData);
23
- circularBuffer_->push_back(inputChannel, numFrames);
24
+ writeToBuffers(inputChannel, numFrames);
24
25
  }
25
26
 
26
27
  while (circularBuffer_->getNumberOfAvailableFrames() >= bufferLength_) {
@@ -16,6 +16,13 @@
16
16
 
17
17
  float devicePrefferedSampleRate = [[AVAudioSession sharedInstance] sampleRate];
18
18
 
19
+ if (!devicePrefferedSampleRate) {
20
+ NSError *error;
21
+ devicePrefferedSampleRate = sampleRate;
22
+
23
+ [[AVAudioSession sharedInstance] setPreferredSampleRate:sampleRate error:&error];
24
+ }
25
+
19
26
  self.inputFormat = [[AVAudioFormat alloc] initWithCommonFormat:AVAudioPCMFormatFloat32
20
27
  sampleRate:devicePrefferedSampleRate
21
28
  channels:1