diff --git a/packages/audiodocs/docs/core/base-audio-context.mdx b/packages/audiodocs/docs/core/base-audio-context.mdx
index dd0dc9fa2..2e6a098bf 100644
--- a/packages/audiodocs/docs/core/base-audio-context.mdx
+++ b/packages/audiodocs/docs/core/base-audio-context.mdx
@@ -120,6 +120,16 @@ Creates [`ConvolverNode`](/docs/effects/convolver-node).
#### Returns `ConvolverNode`.
+### `createDelay`
+
+Creates [`DelayNode`](/docs/effects/delay-node)
+
+| Parameter | Type | Description |
+| :---: | :---: | :---- |
+| `maxDelayTime` | `number` | Maximum amount of time to buffer delayed values|
+
+#### Returns `DelayNode`
+
### `createGain`
Creates [`GainNode`](/docs/effects/gain-node).
diff --git a/packages/audiodocs/docs/effects/delay-node.mdx b/packages/audiodocs/docs/effects/delay-node.mdx
new file mode 100644
index 000000000..c77309c7d
--- /dev/null
+++ b/packages/audiodocs/docs/effects/delay-node.mdx
@@ -0,0 +1,50 @@
+---
+sidebar_position: 5
+---
+
+import AudioNodePropsTable from "@site/src/components/AudioNodePropsTable"
+import { ReadOnly } from '@site/src/components/Badges';
+
+# DelayNode
+
+The `DelayNode` interface represents the latency of the audio signal by given time. It is an [`AudioNode`](/docs/core/audio-node) that applies time shift to incoming signal f.e.
+if `delayTime` value is 0.5, it means that audio will be played after 0.5 seconds.
+
+#### [`AudioNode`](/docs/core/audio-node#properties) properties
+
+
+
+:::info
+Delay is a node with tail-time, which means, that it continues to output non-silent audio with zero input for the duration of `delayTime`.
+:::
+
+## Constructor
+
+[`BaseAudioContext.createDelay(maxDelayTime?: number)`](/docs/core/base-audio-context#createdelay)
+
+## Properties
+
+It inherits all properties from [`AudioNode`](/docs/core/audio-node#properties).
+
+| Name | Type | Description |
+| :----: | :----: | :-------- |
+| `delayTime`| [`AudioParam`](/docs/core/audio-param) | [`k-rate`](/docs/core/audio-param#a-rate-vs-k-rate) `AudioParam` representing value of time shift to apply. |
+
+:::warning
+In web audio api specs `delayTime` is an `a-rate` param.
+:::
+
+## Methods
+
+`DelayNode` does not define any additional methods.
+It inherits all methods from [`AudioNode`](/docs/core/audio-node#methods).
+
+## Remarks
+
+#### `maxDelayTime`
+- Default value is 1.0.
+- Nominal range is 0 - 180.
+
+#### `delayTime`
+- Default value is 0.
+- Nominal range is 0 - `maxDelayTime`.
diff --git a/packages/audiodocs/docs/other/web-audio-api-coverage.mdx b/packages/audiodocs/docs/other/web-audio-api-coverage.mdx
index f273bddb5..2eac36708 100644
--- a/packages/audiodocs/docs/other/web-audio-api-coverage.mdx
+++ b/packages/audiodocs/docs/other/web-audio-api-coverage.mdx
@@ -1,3 +1,4 @@
+
---
id: web-audio-api-coverage
sidebar_label: Web Audio API coverage
@@ -20,6 +21,7 @@ sidebar_position: 2
| BiquadFilterNode | ✅ |
| ConstantSourceNode | ✅ |
| ConvolverNode | ✅ |
+| DelayNode | ✅ |
| GainNode | ✅ |
| IIRFilterNode | ✅ |
| OfflineAudioContext | ✅ |
@@ -36,7 +38,6 @@ sidebar_position: 2
| AudioWorkletProcessor | ❌ |
| ChannelMergerNode | ❌ |
| ChannelSplitterNode | ❌ |
-| DelayNode | ❌ |
| DynamicsCompressorNode | ❌ |
| MediaElementAudioSourceNode | ❌ |
| MediaStreamAudioDestinationNode | ❌ |
diff --git a/packages/react-native-audio-api/common/cpp/audioapi/HostObjects/BaseAudioContextHostObject.cpp b/packages/react-native-audio-api/common/cpp/audioapi/HostObjects/BaseAudioContextHostObject.cpp
index 529782ee0..8487e859c 100644
--- a/packages/react-native-audio-api/common/cpp/audioapi/HostObjects/BaseAudioContextHostObject.cpp
+++ b/packages/react-native-audio-api/common/cpp/audioapi/HostObjects/BaseAudioContextHostObject.cpp
@@ -6,6 +6,7 @@
#include
#include
#include
+#include
#include
#include
#include
@@ -47,6 +48,7 @@ BaseAudioContextHostObject::BaseAudioContextHostObject(
JSI_EXPORT_FUNCTION(BaseAudioContextHostObject, createStreamer),
JSI_EXPORT_FUNCTION(BaseAudioContextHostObject, createConstantSource),
JSI_EXPORT_FUNCTION(BaseAudioContextHostObject, createGain),
+ JSI_EXPORT_FUNCTION(BaseAudioContextHostObject, createDelay),
JSI_EXPORT_FUNCTION(BaseAudioContextHostObject, createStereoPanner),
JSI_EXPORT_FUNCTION(BaseAudioContextHostObject, createBiquadFilter),
JSI_EXPORT_FUNCTION(BaseAudioContextHostObject, createIIRFilter),
@@ -184,6 +186,15 @@ JSI_HOST_FUNCTION_IMPL(BaseAudioContextHostObject, createGain) {
return jsi::Object::createFromHostObject(runtime, gainHostObject);
}
+JSI_HOST_FUNCTION_IMPL(BaseAudioContextHostObject, createDelay) {
+ auto maxDelayTime = static_cast(args[0].getNumber());
+ auto delayNode = context_->createDelay(maxDelayTime);
+ auto delayNodeHostObject = std::make_shared(delayNode);
+ auto jsiObject = jsi::Object::createFromHostObject(runtime, delayNodeHostObject);
+ jsiObject.setExternalMemoryPressure(runtime, delayNodeHostObject->getSizeInBytes());
+ return jsiObject;
+}
+
JSI_HOST_FUNCTION_IMPL(BaseAudioContextHostObject, createStereoPanner) {
auto stereoPanner = context_->createStereoPanner();
auto stereoPannerHostObject = std::make_shared(stereoPanner);
diff --git a/packages/react-native-audio-api/common/cpp/audioapi/HostObjects/BaseAudioContextHostObject.h b/packages/react-native-audio-api/common/cpp/audioapi/HostObjects/BaseAudioContextHostObject.h
index c2680bb7a..37daf4e04 100644
--- a/packages/react-native-audio-api/common/cpp/audioapi/HostObjects/BaseAudioContextHostObject.h
+++ b/packages/react-native-audio-api/common/cpp/audioapi/HostObjects/BaseAudioContextHostObject.h
@@ -43,6 +43,7 @@ class BaseAudioContextHostObject : public JsiHostObject {
JSI_HOST_FUNCTION_DECL(createPeriodicWave);
JSI_HOST_FUNCTION_DECL(createAnalyser);
JSI_HOST_FUNCTION_DECL(createConvolver);
+ JSI_HOST_FUNCTION_DECL(createDelay);
std::shared_ptr context_;
diff --git a/packages/react-native-audio-api/common/cpp/audioapi/HostObjects/effects/DelayNodeHostObject.cpp b/packages/react-native-audio-api/common/cpp/audioapi/HostObjects/effects/DelayNodeHostObject.cpp
new file mode 100644
index 000000000..92e66d8b2
--- /dev/null
+++ b/packages/react-native-audio-api/common/cpp/audioapi/HostObjects/effects/DelayNodeHostObject.cpp
@@ -0,0 +1,27 @@
+#include
+
+#include
+#include
+#include
+#include
+
+namespace audioapi {
+
+DelayNodeHostObject::DelayNodeHostObject(const std::shared_ptr &node)
+ : AudioNodeHostObject(node) {
+ addGetters(JSI_EXPORT_PROPERTY_GETTER(DelayNodeHostObject, delayTime));
+}
+
+size_t DelayNodeHostObject::getSizeInBytes() const {
+ auto delayNode = std::static_pointer_cast(node_);
+ return sizeof(float) * delayNode->context_->getSampleRate() *
+ delayNode->getDelayTimeParam()->getMaxValue();
+}
+
+JSI_PROPERTY_GETTER_IMPL(DelayNodeHostObject, delayTime) {
+ auto delayNode = std::static_pointer_cast(node_);
+ auto delayTimeParam = std::make_shared(delayNode->getDelayTimeParam());
+ return jsi::Object::createFromHostObject(runtime, delayTimeParam);
+}
+
+} // namespace audioapi
diff --git a/packages/react-native-audio-api/common/cpp/audioapi/HostObjects/effects/DelayNodeHostObject.h b/packages/react-native-audio-api/common/cpp/audioapi/HostObjects/effects/DelayNodeHostObject.h
new file mode 100644
index 000000000..379c8e5f6
--- /dev/null
+++ b/packages/react-native-audio-api/common/cpp/audioapi/HostObjects/effects/DelayNodeHostObject.h
@@ -0,0 +1,21 @@
+#pragma once
+
+#include
+
+#include
+#include
+
+namespace audioapi {
+using namespace facebook;
+
+class DelayNode;
+
+class DelayNodeHostObject : public AudioNodeHostObject {
+ public:
+ explicit DelayNodeHostObject(const std::shared_ptr &node);
+
+ [[nodiscard]] size_t getSizeInBytes() const;
+
+ JSI_PROPERTY_GETTER_DECL(delayTime);
+};
+} // namespace audioapi
diff --git a/packages/react-native-audio-api/common/cpp/audioapi/core/AudioNode.cpp b/packages/react-native-audio-api/common/cpp/audioapi/core/AudioNode.cpp
index 9035ab02a..47ff549b3 100644
--- a/packages/react-native-audio-api/common/cpp/audioapi/core/AudioNode.cpp
+++ b/packages/react-native-audio-api/common/cpp/audioapi/core/AudioNode.cpp
@@ -70,6 +70,10 @@ bool AudioNode::isEnabled() const {
return isEnabled_;
}
+bool AudioNode::requiresTailProcessing() const {
+ return requiresTailProcessing_;
+}
+
void AudioNode::enable() {
if (isEnabled()) {
return;
diff --git a/packages/react-native-audio-api/common/cpp/audioapi/core/AudioNode.h b/packages/react-native-audio-api/common/cpp/audioapi/core/AudioNode.h
index 3673fd81e..a56e79454 100644
--- a/packages/react-native-audio-api/common/cpp/audioapi/core/AudioNode.h
+++ b/packages/react-native-audio-api/common/cpp/audioapi/core/AudioNode.h
@@ -38,6 +38,7 @@ class AudioNode : public std::enable_shared_from_this {
bool checkIsAlreadyProcessed);
bool isEnabled() const;
+ bool requiresTailProcessing() const;
void enable();
virtual void disable();
@@ -45,6 +46,7 @@ class AudioNode : public std::enable_shared_from_this {
friend class AudioNodeManager;
friend class AudioDestinationNode;
friend class ConvolverNode;
+ friend class DelayNodeHostObject;
BaseAudioContext *context_;
std::shared_ptr audioBus_;
@@ -64,6 +66,7 @@ class AudioNode : public std::enable_shared_from_this {
int numberOfEnabledInputNodes_ = 0;
bool isInitialized_ = false;
bool isEnabled_ = true;
+ bool requiresTailProcessing_ = false;
std::size_t lastRenderedFrame_{SIZE_MAX};
diff --git a/packages/react-native-audio-api/common/cpp/audioapi/core/BaseAudioContext.cpp b/packages/react-native-audio-api/common/cpp/audioapi/core/BaseAudioContext.cpp
index 2e4723c2a..afebaf2b5 100644
--- a/packages/react-native-audio-api/common/cpp/audioapi/core/BaseAudioContext.cpp
+++ b/packages/react-native-audio-api/common/cpp/audioapi/core/BaseAudioContext.cpp
@@ -3,6 +3,7 @@
#include
#include
#include
+#include
#include
#include
#include
@@ -140,6 +141,12 @@ std::shared_ptr BaseAudioContext::createGain() {
return gain;
}
+std::shared_ptr BaseAudioContext::createDelay(float maxDelayTime) {
+ auto delay = std::make_shared(this, maxDelayTime);
+ nodeManager_->addProcessingNode(delay);
+ return delay;
+}
+
std::shared_ptr BaseAudioContext::createStereoPanner() {
auto stereoPanner = std::make_shared(this);
nodeManager_->addProcessingNode(stereoPanner);
diff --git a/packages/react-native-audio-api/common/cpp/audioapi/core/BaseAudioContext.h b/packages/react-native-audio-api/common/cpp/audioapi/core/BaseAudioContext.h
index 6f40ad7ee..b17304977 100644
--- a/packages/react-native-audio-api/common/cpp/audioapi/core/BaseAudioContext.h
+++ b/packages/react-native-audio-api/common/cpp/audioapi/core/BaseAudioContext.h
@@ -16,6 +16,7 @@ namespace audioapi {
class AudioBus;
class GainNode;
+class DelayNode;
class AudioBuffer;
class PeriodicWave;
class OscillatorNode;
@@ -69,6 +70,7 @@ class BaseAudioContext {
std::shared_ptr createConstantSource();
std::shared_ptr createStreamer();
std::shared_ptr createGain();
+ std::shared_ptr createDelay(float maxDelayTime);
std::shared_ptr createStereoPanner();
std::shared_ptr createBiquadFilter();
std::shared_ptr createIIRFilter(
diff --git a/packages/react-native-audio-api/common/cpp/audioapi/core/effects/ConvolverNode.cpp b/packages/react-native-audio-api/common/cpp/audioapi/core/effects/ConvolverNode.cpp
index a09bc0ced..2692beeab 100644
--- a/packages/react-native-audio-api/common/cpp/audioapi/core/effects/ConvolverNode.cpp
+++ b/packages/react-native-audio-api/common/cpp/audioapi/core/effects/ConvolverNode.cpp
@@ -30,6 +30,7 @@ ConvolverNode::ConvolverNode(
setBuffer(buffer);
audioBus_ =
std::make_shared(RENDER_QUANTUM_SIZE, channelCount_, context->getSampleRate());
+ requiresTailProcessing_ = true;
isInitialized_ = true;
}
diff --git a/packages/react-native-audio-api/common/cpp/audioapi/core/effects/DelayNode.cpp b/packages/react-native-audio-api/common/cpp/audioapi/core/effects/DelayNode.cpp
new file mode 100644
index 000000000..cf2e44c5b
--- /dev/null
+++ b/packages/react-native-audio-api/common/cpp/audioapi/core/effects/DelayNode.cpp
@@ -0,0 +1,101 @@
+#include
+#include
+#include
+#include
+#include
+#include
+
+namespace audioapi {
+
+DelayNode::DelayNode(BaseAudioContext *context, float maxDelayTime) : AudioNode(context) {
+ delayTimeParam_ = std::make_shared(0, 0, maxDelayTime, context);
+ delayBuffer_ = std::make_shared(
+ static_cast(
+ maxDelayTime * context->getSampleRate() +
+ 1), // +1 to enable delayTime equal to maxDelayTime
+ channelCount_,
+ context->getSampleRate());
+ requiresTailProcessing_ = true;
+ isInitialized_ = true;
+}
+
+std::shared_ptr DelayNode::getDelayTimeParam() const {
+ return delayTimeParam_;
+}
+
+void DelayNode::onInputDisabled() {
+ numberOfEnabledInputNodes_ -= 1;
+ if (isEnabled() && numberOfEnabledInputNodes_ == 0) {
+ signalledToStop_ = true;
+ remainingFrames_ = delayTimeParam_->getValue() * context_->getSampleRate();
+ }
+}
+
+void DelayNode::delayBufferOperation(
+ const std::shared_ptr &processingBus,
+ int framesToProcess,
+ size_t &operationStartingIndex,
+ DelayNode::BufferAction action) {
+ size_t processingBusStartIndex = 0;
+
+ // handle buffer wrap around
+ if (operationStartingIndex + framesToProcess > delayBuffer_->getSize()) {
+ int framesToEnd = operationStartingIndex + framesToProcess - delayBuffer_->getSize();
+
+ if (action == BufferAction::WRITE) {
+ delayBuffer_->sum(
+ processingBus.get(), processingBusStartIndex, operationStartingIndex, framesToEnd);
+ } else { // READ
+ processingBus->sum(
+ delayBuffer_.get(), operationStartingIndex, processingBusStartIndex, framesToEnd);
+ }
+
+ operationStartingIndex = 0;
+ processingBusStartIndex += framesToEnd;
+ framesToProcess -= framesToEnd;
+ }
+
+ if (action == BufferAction::WRITE) {
+ delayBuffer_->sum(
+ processingBus.get(), processingBusStartIndex, operationStartingIndex, framesToProcess);
+ processingBus->zero();
+ } else { // READ
+ processingBus->sum(
+ delayBuffer_.get(), operationStartingIndex, processingBusStartIndex, framesToProcess);
+ delayBuffer_->zero(operationStartingIndex, framesToProcess);
+ }
+
+ operationStartingIndex += framesToProcess;
+}
+
+// delay buffer always has channelCount_ channels
+// processing is split into two parts
+// 1. writing to delay buffer (mixing if needed) from processing bus
+// 2. reading from delay buffer to processing bus (mixing if needed) with delay
+std::shared_ptr DelayNode::processNode(
+ const std::shared_ptr &processingBus,
+ int framesToProcess) {
+ // handling tail processing
+ if (signalledToStop_) {
+ if (remainingFrames_ <= 0) {
+ disable();
+ signalledToStop_ = false;
+ return processingBus;
+ }
+
+ delayBufferOperation(processingBus, framesToProcess, readIndex_, DelayNode::BufferAction::READ);
+ remainingFrames_ -= framesToProcess;
+ return processingBus;
+ }
+
+ // normal processing
+ auto delayTime = delayTimeParam_->processKRateParam(framesToProcess, context_->getCurrentTime());
+ size_t writeIndex = static_cast(readIndex_ + delayTime * context_->getSampleRate()) %
+ delayBuffer_->getSize();
+ delayBufferOperation(processingBus, framesToProcess, writeIndex, DelayNode::BufferAction::WRITE);
+ delayBufferOperation(processingBus, framesToProcess, readIndex_, DelayNode::BufferAction::READ);
+
+ return processingBus;
+}
+
+} // namespace audioapi
diff --git a/packages/react-native-audio-api/common/cpp/audioapi/core/effects/DelayNode.h b/packages/react-native-audio-api/common/cpp/audioapi/core/effects/DelayNode.h
new file mode 100644
index 000000000..15ab28f10
--- /dev/null
+++ b/packages/react-native-audio-api/common/cpp/audioapi/core/effects/DelayNode.h
@@ -0,0 +1,39 @@
+#pragma once
+
+#include
+#include
+
+#include
+#include
+
+namespace audioapi {
+
+class AudioBus;
+
+class DelayNode : public AudioNode {
+ public:
+ explicit DelayNode(BaseAudioContext *context, float maxDelayTime);
+
+ [[nodiscard]] std::shared_ptr getDelayTimeParam() const;
+
+ protected:
+ std::shared_ptr processNode(
+ const std::shared_ptr &processingBus,
+ int framesToProcess) override;
+
+ private:
+ void onInputDisabled() override;
+ enum class BufferAction { READ, WRITE };
+ void delayBufferOperation(
+ const std::shared_ptr &processingBus,
+ int framesToProcess,
+ size_t &operationStartingIndex,
+ BufferAction action);
+ std::shared_ptr delayTimeParam_;
+ std::shared_ptr delayBuffer_;
+ size_t readIndex_ = 0;
+ bool signalledToStop_ = false;
+ int remainingFrames_ = 0;
+};
+
+} // namespace audioapi
diff --git a/packages/react-native-audio-api/common/cpp/audioapi/core/utils/AudioNodeManager.cpp b/packages/react-native-audio-api/common/cpp/audioapi/core/utils/AudioNodeManager.cpp
index eae7498e4..ca4a182de 100644
--- a/packages/react-native-audio-api/common/cpp/audioapi/core/utils/AudioNodeManager.cpp
+++ b/packages/react-native-audio-api/common/cpp/audioapi/core/utils/AudioNodeManager.cpp
@@ -1,6 +1,7 @@
#include
#include
#include
+#include
#include
#include
#include
@@ -219,7 +220,8 @@ inline bool AudioNodeManager::nodeCanBeDestructed(std::shared_ptr const &node
// playing
if constexpr (std::is_base_of_v) {
return node.use_count() == 1 && (node->isUnscheduled() || node->isFinished());
- } else if constexpr (std::is_base_of_v) {
+ } else if (node->requiresTailProcessing()) {
+ // if the node requires tail processing, its own implementation handles disabling it at the right time
return node.use_count() == 1 && !node->isEnabled();
}
return node.use_count() == 1;
diff --git a/packages/react-native-audio-api/common/cpp/test/src/DelayTest.cpp b/packages/react-native-audio-api/common/cpp/test/src/DelayTest.cpp
new file mode 100644
index 000000000..8f247607f
--- /dev/null
+++ b/packages/react-native-audio-api/common/cpp/test/src/DelayTest.cpp
@@ -0,0 +1,108 @@
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+using namespace audioapi;
+
+class DelayTest : public ::testing::Test {
+ protected:
+ std::shared_ptr eventRegistry;
+ std::unique_ptr context;
+ static constexpr int sampleRate = 44100;
+
+ void SetUp() override {
+ eventRegistry = std::make_shared();
+ context = std::make_unique(
+ 2, 5 * sampleRate, sampleRate, eventRegistry, RuntimeRegistry{});
+ }
+};
+
+class TestableDelayNode : public DelayNode {
+ public:
+ explicit TestableDelayNode(BaseAudioContext *context) : DelayNode(context, 1) {}
+
+ void setDelayTimeParam(float value) {
+ getDelayTimeParam()->setValue(value);
+ }
+
+ std::shared_ptr processNode(
+ const std::shared_ptr &processingBus,
+ int framesToProcess) override {
+ return DelayNode::processNode(processingBus, framesToProcess);
+ }
+};
+
+TEST_F(DelayTest, DelayCanBeCreated) {
+ auto delay = context->createDelay(1.0f);
+ ASSERT_NE(delay, nullptr);
+}
+
+TEST_F(DelayTest, DelayWithZeroDelayOutputsInputSignal) {
+ static constexpr float DELAY_TIME = 0.0f;
+ static constexpr int FRAMES_TO_PROCESS = 4;
+ auto delayNode = std::make_shared(context.get());
+ delayNode->setDelayTimeParam(DELAY_TIME);
+
+ auto bus = std::make_shared(FRAMES_TO_PROCESS, 1, sampleRate);
+ for (size_t i = 0; i < bus->getSize(); ++i) {
+ bus->getChannel(0)->getData()[i] = i + 1;
+ }
+
+ auto resultBus = delayNode->processNode(bus, FRAMES_TO_PROCESS);
+ for (size_t i = 0; i < FRAMES_TO_PROCESS; ++i) {
+ EXPECT_FLOAT_EQ((*resultBus->getChannel(0))[i], static_cast(i + 1));
+ }
+}
+
+TEST_F(DelayTest, DelayAppliesTimeShiftCorrectly) {
+ float DELAY_TIME = (128.0 / context->getSampleRate()) * 0.5;
+ static constexpr int FRAMES_TO_PROCESS = 128;
+ auto delayNode = std::make_shared(context.get());
+ delayNode->setDelayTimeParam(DELAY_TIME);
+
+ auto bus = std::make_shared(FRAMES_TO_PROCESS, 1, sampleRate);
+ for (size_t i = 0; i < bus->getSize(); ++i) {
+ bus->getChannel(0)->getData()[i] = i + 1;
+ }
+
+ auto resultBus = delayNode->processNode(bus, FRAMES_TO_PROCESS);
+ for (size_t i = 0; i < FRAMES_TO_PROCESS; ++i) {
+ if (i < FRAMES_TO_PROCESS / 2) { // First 64 samples should be zero due to delay
+ EXPECT_FLOAT_EQ((*resultBus->getChannel(0))[i], 0.0f);
+ } else {
+ EXPECT_FLOAT_EQ(
+ (*resultBus->getChannel(0))[i],
+ static_cast(
+ i + 1 - FRAMES_TO_PROCESS / 2)); // Last 64 samples should be 1st part of bus
+ }
+ }
+}
+
+TEST_F(DelayTest, DelayHandlesTailCorrectly) {
+ float DELAY_TIME = (128.0 / context->getSampleRate()) * 0.5;
+ static constexpr int FRAMES_TO_PROCESS = 128;
+ auto delayNode = std::make_shared(context.get());
+ delayNode->setDelayTimeParam(DELAY_TIME);
+
+ auto bus = std::make_shared(FRAMES_TO_PROCESS, 1, sampleRate);
+ for (size_t i = 0; i < bus->getSize(); ++i) {
+ bus->getChannel(0)->getData()[i] = i + 1;
+ }
+
+ delayNode->processNode(bus, FRAMES_TO_PROCESS);
+ auto resultBus = delayNode->processNode(bus, FRAMES_TO_PROCESS);
+ for (size_t i = 0; i < FRAMES_TO_PROCESS; ++i) {
+ if (i < FRAMES_TO_PROCESS / 2) { // First 64 samples should be 2nd part of bus
+ EXPECT_FLOAT_EQ(
+ (*resultBus->getChannel(0))[i], static_cast(i + 1 + FRAMES_TO_PROCESS / 2));
+ } else {
+ EXPECT_FLOAT_EQ((*resultBus->getChannel(0))[i],
+ 0.0f); // Last 64 samples should be zero
+ }
+ }
+}
diff --git a/packages/react-native-audio-api/src/core/AudioNode.ts b/packages/react-native-audio-api/src/core/AudioNode.ts
index 2e19d51d1..7990f4ce3 100644
--- a/packages/react-native-audio-api/src/core/AudioNode.ts
+++ b/packages/react-native-audio-api/src/core/AudioNode.ts
@@ -23,7 +23,9 @@ export default class AudioNode {
this.channelInterpretation = this.node.channelInterpretation;
}
- public connect(destination: AudioNode | AudioParam): AudioNode | AudioParam {
+ public connect(destination: AudioNode): AudioNode;
+ public connect(destination: AudioParam): void;
+ public connect(destination: AudioNode | AudioParam): AudioNode | void {
if (this.context !== destination.context) {
throw new InvalidAccessError(
'Source and destination are from different BaseAudioContexts'
@@ -34,9 +36,8 @@ export default class AudioNode {
this.node.connect(destination.audioParam);
} else {
this.node.connect(destination.node);
+ return destination;
}
-
- return destination;
}
public disconnect(destination?: AudioNode | AudioParam): void {
diff --git a/packages/react-native-audio-api/src/core/BaseAudioContext.ts b/packages/react-native-audio-api/src/core/BaseAudioContext.ts
index 8b51daf0e..248cba436 100644
--- a/packages/react-native-audio-api/src/core/BaseAudioContext.ts
+++ b/packages/react-native-audio-api/src/core/BaseAudioContext.ts
@@ -30,6 +30,7 @@ import RecorderAdapterNode from './RecorderAdapterNode';
import StereoPannerNode from './StereoPannerNode';
import StreamerNode from './StreamerNode';
import WorkletNode from './WorkletNode';
+import DelayNode from './DelayNode';
import IIRFilterNode from './IIRFilterNode';
import { decodeAudioData, decodePCMInBase64 } from './AudioDecoder';
@@ -206,6 +207,11 @@ export default class BaseAudioContext {
return new GainNode(this, this.context.createGain());
}
+ createDelay(maxDelayTime?: number): DelayNode {
+ const maxTime = maxDelayTime ?? 1.0;
+ return new DelayNode(this, this.context.createDelay(maxTime));
+ }
+
createStereoPanner(): StereoPannerNode {
return new StereoPannerNode(this, this.context.createStereoPanner());
}
diff --git a/packages/react-native-audio-api/src/core/DelayNode.ts b/packages/react-native-audio-api/src/core/DelayNode.ts
new file mode 100644
index 000000000..ac4ab9d86
--- /dev/null
+++ b/packages/react-native-audio-api/src/core/DelayNode.ts
@@ -0,0 +1,13 @@
+import { IDelayNode } from '../interfaces';
+import AudioNode from './AudioNode';
+import AudioParam from './AudioParam';
+import BaseAudioContext from './BaseAudioContext';
+
+export default class DelayNode extends AudioNode {
+ readonly delayTime: AudioParam;
+
+ constructor(context: BaseAudioContext, delay: IDelayNode) {
+ super(context, delay);
+ this.delayTime = new AudioParam(delay.delayTime, context);
+ }
+}
diff --git a/packages/react-native-audio-api/src/interfaces.ts b/packages/react-native-audio-api/src/interfaces.ts
index 342588fe9..d06e5173e 100644
--- a/packages/react-native-audio-api/src/interfaces.ts
+++ b/packages/react-native-audio-api/src/interfaces.ts
@@ -60,6 +60,7 @@ export interface IBaseAudioContext {
createOscillator(): IOscillatorNode;
createConstantSource(): IConstantSourceNode;
createGain(): IGainNode;
+ createDelay(maxDelayTime: number): IDelayNode;
createStereoPanner(): IStereoPannerNode;
createBiquadFilter: () => IBiquadFilterNode;
createIIRFilter: (
@@ -112,6 +113,11 @@ export interface IAudioNode {
disconnect: (destination?: IAudioNode | IAudioParam) => void;
}
+export interface IDelayNode extends IAudioNode {
+ readonly delayTime: IAudioParam;
+ maxDelayTime: number;
+}
+
export interface IGainNode extends IAudioNode {
readonly gain: IAudioParam;
}
diff --git a/packages/react-native-audio-api/src/web-core/AudioContext.tsx b/packages/react-native-audio-api/src/web-core/AudioContext.tsx
index 6e704882e..f2770ef11 100644
--- a/packages/react-native-audio-api/src/web-core/AudioContext.tsx
+++ b/packages/react-native-audio-api/src/web-core/AudioContext.tsx
@@ -18,6 +18,7 @@ import OscillatorNode from './OscillatorNode';
import PeriodicWave from './PeriodicWave';
import StereoPannerNode from './StereoPannerNode';
import ConvolverNode from './ConvolverNode';
+import DelayNode from './DelayNode';
import { ConvolverNodeOptions } from './ConvolverNodeOptions';
import { globalWasmPromise, globalTag } from './custom/LoadCustomWasm';
@@ -66,6 +67,10 @@ export default class AudioContext implements BaseAudioContext {
return new GainNode(this, this.context.createGain());
}
+ createDelay(maxDelayTime?: number): DelayNode {
+ return new DelayNode(this, this.context.createDelay(maxDelayTime));
+ }
+
createStereoPanner(): StereoPannerNode {
return new StereoPannerNode(this, this.context.createStereoPanner());
}
diff --git a/packages/react-native-audio-api/src/web-core/BaseAudioContext.tsx b/packages/react-native-audio-api/src/web-core/BaseAudioContext.tsx
index bc04f0e91..c9341289f 100644
--- a/packages/react-native-audio-api/src/web-core/BaseAudioContext.tsx
+++ b/packages/react-native-audio-api/src/web-core/BaseAudioContext.tsx
@@ -15,6 +15,7 @@ import PeriodicWave from './PeriodicWave';
import StereoPannerNode from './StereoPannerNode';
import ConstantSourceNode from './ConstantSourceNode';
import ConvolverNode from './ConvolverNode';
+import DelayNode from './DelayNode';
export default interface BaseAudioContext {
readonly context: globalThis.BaseAudioContext;
@@ -27,6 +28,7 @@ export default interface BaseAudioContext {
createOscillator(): OscillatorNode;
createConstantSource(): ConstantSourceNode;
createGain(): GainNode;
+ createDelay(maxDelayTime?: number): DelayNode;
createStereoPanner(): StereoPannerNode;
createBiquadFilter(): BiquadFilterNode;
createIIRFilter(options: IIRFilterNodeOptions): IIRFilterNode;
diff --git a/packages/react-native-audio-api/src/web-core/DelayNode.tsx b/packages/react-native-audio-api/src/web-core/DelayNode.tsx
new file mode 100644
index 000000000..68e8e54a6
--- /dev/null
+++ b/packages/react-native-audio-api/src/web-core/DelayNode.tsx
@@ -0,0 +1,12 @@
+import BaseAudioContext from './BaseAudioContext';
+import AudioNode from './AudioNode';
+import AudioParam from './AudioParam';
+
+export default class DelayNode extends AudioNode {
+ readonly delayTime: AudioParam;
+
+ constructor(context: BaseAudioContext, delay: globalThis.DelayNode) {
+ super(context, delay);
+ this.delayTime = new AudioParam(delay.delayTime, context);
+ }
+}
diff --git a/packages/react-native-audio-api/src/web-core/OfflineAudioContext.tsx b/packages/react-native-audio-api/src/web-core/OfflineAudioContext.tsx
index 86fbae9f4..54c7f9288 100644
--- a/packages/react-native-audio-api/src/web-core/OfflineAudioContext.tsx
+++ b/packages/react-native-audio-api/src/web-core/OfflineAudioContext.tsx
@@ -22,6 +22,7 @@ import ConstantSourceNode from './ConstantSourceNode';
import { globalWasmPromise, globalTag } from './custom/LoadCustomWasm';
import ConvolverNode from './ConvolverNode';
import { ConvolverNodeOptions } from './ConvolverNodeOptions';
+import DelayNode from './DelayNode';
export default class OfflineAudioContext implements BaseAudioContext {
readonly context: globalThis.OfflineAudioContext;
@@ -72,6 +73,10 @@ export default class OfflineAudioContext implements BaseAudioContext {
return new GainNode(this, this.context.createGain());
}
+ createDelay(maxDelayTime?: number): DelayNode {
+ return new DelayNode(this, this.context.createDelay(maxDelayTime));
+ }
+
createStereoPanner(): StereoPannerNode {
return new StereoPannerNode(this, this.context.createStereoPanner());
}