Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions packages/audiodocs/docs/core/base-audio-context.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,16 @@ Creates [`ConvolverNode`](/docs/effects/convolver-node).

#### Returns `ConvolverNode`.

### `createDelay`

Creates [`DelayNode`](/docs/effects/delay-node)

| Parameter | Type | Description |
| :---: | :---: | :---- |
| `maxDelayTime` <Optional /> | `number` | Maximum amount of time to buffer delayed values|

#### Returns `DelayNode`

### `createGain`

Creates [`GainNode`](/docs/effects/gain-node).
Expand Down
50 changes: 50 additions & 0 deletions packages/audiodocs/docs/effects/delay-node.mdx
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`.
3 changes: 2 additions & 1 deletion packages/audiodocs/docs/other/web-audio-api-coverage.mdx
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@

---
id: web-audio-api-coverage
sidebar_label: Web Audio API coverage
Expand All @@ -20,6 +21,7 @@ sidebar_position: 2
| BiquadFilterNode | ✅ |
| ConstantSourceNode | ✅ |
| ConvolverNode | ✅ |
| DelayNode | ✅ |
| GainNode | ✅ |
| IIRFilterNode | ✅ |
| OfflineAudioContext | ✅ |
Expand All @@ -36,7 +38,6 @@ sidebar_position: 2
| AudioWorkletProcessor | ❌ |
| ChannelMergerNode | ❌ |
| ChannelSplitterNode | ❌ |
| DelayNode | ❌ |
| DynamicsCompressorNode | ❌ |
| MediaElementAudioSourceNode | ❌ |
| MediaStreamAudioDestinationNode | ❌ |
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
#include <audioapi/HostObjects/destinations/AudioDestinationNodeHostObject.h>
#include <audioapi/HostObjects/effects/BiquadFilterNodeHostObject.h>
#include <audioapi/HostObjects/effects/ConvolverNodeHostObject.h>
#include <audioapi/HostObjects/effects/DelayNodeHostObject.h>
#include <audioapi/HostObjects/effects/GainNodeHostObject.h>
#include <audioapi/HostObjects/effects/IIRFilterNodeHostObject.h>
#include <audioapi/HostObjects/effects/PeriodicWaveHostObject.h>
Expand Down Expand Up @@ -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),
Expand Down Expand Up @@ -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<float>(args[0].getNumber());
auto delayNode = context_->createDelay(maxDelayTime);
auto delayNodeHostObject = std::make_shared<DelayNodeHostObject>(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<StereoPannerNodeHostObject>(stereoPanner);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<BaseAudioContext> context_;

Expand Down
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
Expand Up @@ -70,6 +70,10 @@ bool AudioNode::isEnabled() const {
return isEnabled_;
}

bool AudioNode::requiresTailProcessing() const {
return requiresTailProcessing_;
}

void AudioNode::enable() {
if (isEnabled()) {
return;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,13 +38,15 @@ class AudioNode : public std::enable_shared_from_this<AudioNode> {
bool checkIsAlreadyProcessed);

bool isEnabled() const;
bool requiresTailProcessing() const;
void enable();
virtual void disable();

protected:
friend class AudioNodeManager;
friend class AudioDestinationNode;
friend class ConvolverNode;
friend class DelayNodeHostObject;

BaseAudioContext *context_;
std::shared_ptr<AudioBus> audioBus_;
Expand All @@ -64,6 +66,7 @@ class AudioNode : public std::enable_shared_from_this<AudioNode> {
int numberOfEnabledInputNodes_ = 0;
bool isInitialized_ = false;
bool isEnabled_ = true;
bool requiresTailProcessing_ = false;

std::size_t lastRenderedFrame_{SIZE_MAX};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
#include <audioapi/core/destinations/AudioDestinationNode.h>
#include <audioapi/core/effects/BiquadFilterNode.h>
#include <audioapi/core/effects/ConvolverNode.h>
#include <audioapi/core/effects/DelayNode.h>
#include <audioapi/core/effects/GainNode.h>
#include <audioapi/core/effects/IIRFilterNode.h>
#include <audioapi/core/effects/StereoPannerNode.h>
Expand Down Expand Up @@ -140,6 +141,12 @@ std::shared_ptr<GainNode> BaseAudioContext::createGain() {
return gain;
}

std::shared_ptr<DelayNode> BaseAudioContext::createDelay(float maxDelayTime) {
auto delay = std::make_shared<DelayNode>(this, maxDelayTime);
nodeManager_->addProcessingNode(delay);
return delay;
}

std::shared_ptr<StereoPannerNode> BaseAudioContext::createStereoPanner() {
auto stereoPanner = std::make_shared<StereoPannerNode>(this);
nodeManager_->addProcessingNode(stereoPanner);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ namespace audioapi {

class AudioBus;
class GainNode;
class DelayNode;
class AudioBuffer;
class PeriodicWave;
class OscillatorNode;
Expand Down Expand Up @@ -69,6 +70,7 @@ class BaseAudioContext {
std::shared_ptr<ConstantSourceNode> createConstantSource();
std::shared_ptr<StreamerNode> createStreamer();
std::shared_ptr<GainNode> createGain();
std::shared_ptr<DelayNode> createDelay(float maxDelayTime);
std::shared_ptr<StereoPannerNode> createStereoPanner();
std::shared_ptr<BiquadFilterNode> createBiquadFilter();
std::shared_ptr<IIRFilterNode> createIIRFilter(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ ConvolverNode::ConvolverNode(
setBuffer(buffer);
audioBus_ =
std::make_shared<AudioBus>(RENDER_QUANTUM_SIZE, channelCount_, context->getSampleRate());
requiresTailProcessing_ = true;
isInitialized_ = true;
}

Expand Down
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(
Copy link
Collaborator

Choose a reason for hiding this comment

The 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()) %
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
size_t writeIndex = static_cast<size_t>(readIndex_ + delayTime * context_->getSampleRate()) %
auto writeIndex = static_cast<size_t>(readIndex_ + delayTime * context_->getSampleRate()) %

delayBuffer_->getSize();
delayBufferOperation(processingBus, framesToProcess, writeIndex, DelayNode::BufferAction::WRITE);
delayBufferOperation(processingBus, framesToProcess, readIndex_, DelayNode::BufferAction::READ);
return processingBus;
Copy link
Member

Choose a reason for hiding this comment

The 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
Loading