-
-
Notifications
You must be signed in to change notification settings - Fork 33
Feat/delay node #803
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Feat/delay node #803
Changes from all commits
57ce36d
0dbb0d0
f6bc80c
5d62392
96c36c0
02c4443
ed9ac5d
67f1a5d
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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 | ||
|
|
||
| <AudioNodePropsTable numberOfInputs={1} numberOfOutputs={1} channelCount={2} channelCountMode={"max"} channelInterpretation={"speakers"} /> | ||
|
|
||
| :::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`| <ReadOnly /> [`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`. | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,27 @@ | ||
| #include <audioapi/HostObjects/effects/DelayNodeHostObject.h> | ||
|
|
||
| #include <audioapi/HostObjects/AudioParamHostObject.h> | ||
| #include <audioapi/core/BaseAudioContext.h> | ||
| #include <audioapi/core/effects/DelayNode.h> | ||
| #include <memory> | ||
|
|
||
| namespace audioapi { | ||
|
|
||
| DelayNodeHostObject::DelayNodeHostObject(const std::shared_ptr<DelayNode> &node) | ||
| : AudioNodeHostObject(node) { | ||
| addGetters(JSI_EXPORT_PROPERTY_GETTER(DelayNodeHostObject, delayTime)); | ||
| } | ||
|
|
||
| size_t DelayNodeHostObject::getSizeInBytes() const { | ||
| auto delayNode = std::static_pointer_cast<DelayNode>(node_); | ||
| return sizeof(float) * delayNode->context_->getSampleRate() * | ||
| delayNode->getDelayTimeParam()->getMaxValue(); | ||
| } | ||
|
|
||
| JSI_PROPERTY_GETTER_IMPL(DelayNodeHostObject, delayTime) { | ||
| auto delayNode = std::static_pointer_cast<DelayNode>(node_); | ||
| auto delayTimeParam = std::make_shared<AudioParamHostObject>(delayNode->getDelayTimeParam()); | ||
| return jsi::Object::createFromHostObject(runtime, delayTimeParam); | ||
| } | ||
|
|
||
| } // namespace audioapi |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,21 @@ | ||
| #pragma once | ||
|
|
||
| #include <audioapi/HostObjects/AudioNodeHostObject.h> | ||
|
|
||
| #include <memory> | ||
| #include <vector> | ||
|
|
||
| namespace audioapi { | ||
| using namespace facebook; | ||
|
|
||
| class DelayNode; | ||
|
|
||
| class DelayNodeHostObject : public AudioNodeHostObject { | ||
| public: | ||
| explicit DelayNodeHostObject(const std::shared_ptr<DelayNode> &node); | ||
|
|
||
| [[nodiscard]] size_t getSizeInBytes() const; | ||
|
|
||
| JSI_PROPERTY_GETTER_DECL(delayTime); | ||
| }; | ||
| } // namespace audioapi |
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
| @@ -0,0 +1,93 @@ | ||||||
| #include <audioapi/core/BaseAudioContext.h> | ||||||
| #include <audioapi/core/effects/DelayNode.h> | ||||||
| #include <audioapi/dsp/VectorMath.h> | ||||||
| #include <audioapi/utils/AudioArray.h> | ||||||
| #include <audioapi/utils/AudioBus.h> | ||||||
| #include <memory> | ||||||
|
|
||||||
| namespace audioapi { | ||||||
|
|
||||||
| DelayNode::DelayNode(BaseAudioContext *context, float maxDelayTime) : AudioNode(context) { | ||||||
| delayTimeParam_ = std::make_shared<AudioParam>(0, 0, maxDelayTime, context); | ||||||
| delayBuffer_ = std::make_shared<AudioBus>( | ||||||
| static_cast<size_t>( | ||||||
| maxDelayTime * context->getSampleRate() + | ||||||
| 1), // +1 to enable delayTime equal to maxDelayTime | ||||||
| channelCount_, | ||||||
| context->getSampleRate()); | ||||||
| requiresTailProcessing_ = true; | ||||||
| isInitialized_ = true; | ||||||
| } | ||||||
|
|
||||||
| std::shared_ptr<AudioParam> 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<AudioBus> &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<AudioBus> DelayNode::processNode( | ||||||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. add comments about write/read process, separate each step with new line |
||||||
| const std::shared_ptr<AudioBus> &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<size_t>(readIndex_ + delayTime * context_->getSampleRate()) % | ||||||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
| delayBuffer_->getSize(); | ||||||
| delayBufferOperation(processingBus, framesToProcess, writeIndex, DelayNode::BufferAction::WRITE); | ||||||
| delayBufferOperation(processingBus, framesToProcess, readIndex_, DelayNode::BufferAction::READ); | ||||||
| return processingBus; | ||||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. could you format the process method a bit? It is really hard to read when it is wall of text |
||||||
| } | ||||||
|
|
||||||
| } // namespace audioapi | ||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,39 @@ | ||
| #pragma once | ||
|
|
||
| #include <audioapi/core/AudioNode.h> | ||
| #include <audioapi/core/AudioParam.h> | ||
|
|
||
| #include <functional> | ||
| #include <memory> | ||
|
|
||
| namespace audioapi { | ||
|
|
||
| class AudioBus; | ||
|
|
||
| class DelayNode : public AudioNode { | ||
| public: | ||
| explicit DelayNode(BaseAudioContext *context, float maxDelayTime); | ||
|
|
||
| [[nodiscard]] std::shared_ptr<AudioParam> getDelayTimeParam() const; | ||
|
|
||
| protected: | ||
| std::shared_ptr<AudioBus> processNode( | ||
| const std::shared_ptr<AudioBus> &processingBus, | ||
| int framesToProcess) override; | ||
|
|
||
| private: | ||
| void onInputDisabled() override; | ||
| enum class BufferAction { READ, WRITE }; | ||
| void delayBufferOperation( | ||
| const std::shared_ptr<AudioBus> &processingBus, | ||
| int framesToProcess, | ||
| size_t &operationStartingIndex, | ||
| BufferAction action); | ||
| std::shared_ptr<AudioParam> delayTimeParam_; | ||
| std::shared_ptr<AudioBus> delayBuffer_; | ||
| size_t readIndex_ = 0; | ||
| bool signalledToStop_ = false; | ||
| int remainingFrames_ = 0; | ||
| }; | ||
|
|
||
| } // namespace audioapi |
Uh oh!
There was an error while loading. Please reload this page.