Repository: software-mansion/react-native-audio-api
Pull request #432: feat: added logic for connecting node to param
Author: login: mdydek
Labels: name: feature
description: New feature
Created: 2025-05-05T14:37:57Z | Merged: 2025-05-12T11:13:58Z

<!-- Reference any GitHub issues resolved by this PR -->

Closes RNAA-80, RNAA-82, RNAA-94, RNAA-88, RNAA-89, RNAA-83, RNAA-87, RNAA-97, RNAA-79

## Introduced changes

<!-- A brief description of the changes -->

- added methods in audioNode and audioNodeManager to handle connection to params
- migrated oscillator node to be aligned with new implementation of params
- migrated gain node to be aligned with ne implementation of params
- added typescript interfaces for new audio param

## Checklist

<!-- Make sure all of these are complete -->

- [x] Linked relevant issue
- [ ] Updated relevant documentation
- [x] Added/Conducted relevant tests
- [x] Performed self-review of the code
- [ ] Updated Web Audio API coverage
- [ ] Added support for web



Diff:
diff --git a/packages/react-native-audio-api/common/cpp/audioapi/HostObjects/AudioNodeHostObject.h b/packages/react-native-audio-api/common/cpp/audioapi/HostObjects/AudioNodeHostObject.h
index 40784f872..c6119b159 100644
--- a/packages/react-native-audio-api/common/cpp/audioapi/HostObjects/AudioNodeHostObject.h
+++ b/packages/react-native-audio-api/common/cpp/audioapi/HostObjects/AudioNodeHostObject.h
@@ -1,7 +1,8 @@
 #pragma once
 
-#include <audioapi/jsi/JsiHostObject.h>
+#include <audioapi/HostObjects/AudioParamHostObject.h>
 #include <audioapi/core/AudioNode.h>
+#include <audioapi/jsi/JsiHostObject.h>
 
 #include <jsi/jsi.h>
 #include <memory>
@@ -47,21 +48,33 @@ class AudioNodeHostObject : public JsiHostObject {
   }
 
   JSI_HOST_FUNCTION(connect) {
-    auto node =
-        args[0].getObject(runtime).getHostObject<AudioNodeHostObject>(runtime);
-    node_->connect(std::shared_ptr<AudioNodeHostObject>(node)->node_);
+    auto obj = args[0].getObject(runtime);
+    if (obj.isHostObject<AudioNodeHostObject>(runtime)) {
+      auto node = obj.getHostObject<AudioNodeHostObject>(runtime);
+      node_->connect(std::shared_ptr<AudioNodeHostObject>(node)->node_);
+    }
+    if (obj.isHostObject<AudioParamHostObject>(runtime)) {
+      auto param = obj.getHostObject<AudioParamHostObject>(runtime);
+      node_->connect(std::shared_ptr<AudioParamHostObject>(param)->param_);
+    }
     return jsi::Value::undefined();
   }
 
   JSI_HOST_FUNCTION(disconnect) {
-      if(args[0].isUndefined()) {
-          node_->disconnect();
-          return jsi::Value::undefined();
-      }
+    if (args[0].isUndefined()) {
+      node_->disconnect();
+      return jsi::Value::undefined();
+    }
+    auto obj = args[0].getObject(runtime);
+    if (obj.isHostObject<AudioNodeHostObject>(runtime)) {
+      auto node = obj.getHostObject<AudioNodeHostObject>(runtime);
+      node_->disconnect(std::shared_ptr<AudioNodeHostObject>(node)->node_);
+    }
 
-    auto node =
-        args[0].getObject(runtime).getHostObject<AudioNodeHostObject>(runtime);
-    node_->disconnect(std::shared_ptr<AudioNodeHostObject>(node)->node_);
+    if (obj.isHostObject<AudioParamHostObject>(runtime)) {
+      auto param = obj.getHostObject<AudioParamHostObject>(runtime);
+      node_->disconnect(std::shared_ptr<AudioParamHostObject>(param)->param_);
+    }
     return jsi::Value::undefined();
   }
 
diff --git a/packages/react-native-audio-api/common/cpp/audioapi/HostObjects/AudioParamHostObject.h b/packages/react-native-audio-api/common/cpp/audioapi/HostObjects/AudioParamHostObject.h
index 566f23caf..b737b6b58 100644
--- a/packages/react-native-audio-api/common/cpp/audioapi/HostObjects/AudioParamHostObject.h
+++ b/packages/react-native-audio-api/common/cpp/audioapi/HostObjects/AudioParamHostObject.h
@@ -32,6 +32,7 @@ class AudioParamHostObject : public JsiHostObject {
 
     addSetters(JSI_EXPORT_PROPERTY_SETTER(AudioParamHostObject, value));
   }
+  friend class AudioNodeHostObject;
 
   JSI_PROPERTY_GETTER(value) {
     return {param_->getValue()};
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 a35871daa..55b550095 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
@@ -1,4 +1,5 @@
 #include <audioapi/core/AudioNode.h>
+#include <audioapi/core/AudioParam.h>
 #include <audioapi/core/BaseAudioContext.h>
 #include <audioapi/core/utils/AudioNodeManager.h>
 #include <audioapi/utils/AudioArray.h>
@@ -38,10 +39,15 @@ std::string AudioNode::getChannelInterpretation() const {
 }
 
 void AudioNode::connect(const std::shared_ptr<AudioNode> &node) {
-  context_->getNodeManager()->addPendingConnection(
+  context_->getNodeManager()->addPendingNodeConnection(
       shared_from_this(), node, AudioNodeManager::ConnectionType::CONNECT);
 }
 
+void AudioNode::connect(const std::shared_ptr<AudioParam> &param) {
+  context_->getNodeManager()->addPendingParamConnection(
+      shared_from_this(), param, AudioNodeManager::ConnectionType::CONNECT);
+}
+
 void AudioNode::disconnect() {
   for (auto it = outputNodes_.begin(), end = outputNodes_.end(); it != end;
        ++it) {
@@ -50,10 +56,15 @@ void AudioNode::disconnect() {
 }
 
 void AudioNode::disconnect(const std::shared_ptr<AudioNode> &node) {
-  context_->getNodeManager()->addPendingConnection(
+  context_->getNodeManager()->addPendingNodeConnection(
       shared_from_this(), node, AudioNodeManager::ConnectionType::DISCONNECT);
 }
 
+void AudioNode::disconnect(const std::shared_ptr<AudioParam> &param) {
+  context_->getNodeManager()->addPendingParamConnection(
+      shared_from_this(), param, AudioNodeManager::ConnectionType::DISCONNECT);
+}
+
 bool AudioNode::isEnabled() const {
   return isEnabled_;
 }
@@ -221,6 +232,15 @@ void AudioNode::connectNode(const std::shared_ptr<AudioNode> &node) {
   }
 }
 
+void AudioNode::connectParam(const std::shared_ptr<AudioParam> &param) {
+  auto position = outputParams_.find(param);
+
+  if (position == outputParams_.end()) {
+    outputParams_.insert(param);
+    param->addInputNode(this);
+  }
+}
+
 void AudioNode::disconnectNode(const std::shared_ptr<AudioNode> &node) {
   auto position = outputNodes_.find(node);
 
@@ -230,6 +250,15 @@ void AudioNode::disconnectNode(const std::shared_ptr<AudioNode> &node) {
   }
 }
 
+void AudioNode::disconnectParam(const std::shared_ptr<AudioParam> &param) {
+  auto position = outputParams_.find(param);
+
+  if (position != outputParams_.end()) {
+    param->removeInputNode(this);
+    outputParams_.erase(param);
+  }
+}
+
 void AudioNode::onInputEnabled() {
   numberOfEnabledInputNodes_ += 1;
 
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 f5138f8b1..79cbb479f 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
@@ -15,6 +15,7 @@ namespace audioapi {
 
 class AudioBus;
 class BaseAudioContext;
+class AudioParam;
 
 class AudioNode : public std::enable_shared_from_this<AudioNode> {
  public:
@@ -27,8 +28,11 @@ class AudioNode : public std::enable_shared_from_this<AudioNode> {
   std::string getChannelCountMode() const;
   std::string getChannelInterpretation() const;
   void connect(const std::shared_ptr<AudioNode> &node);
+  void connect(const std::shared_ptr<AudioParam> &param);
   void disconnect();
   void disconnect(const std::shared_ptr<AudioNode> &node);
+  void disconnect(const std::shared_ptr<AudioParam> &param);
+  virtual std::shared_ptr<AudioBus> processAudio(const std::shared_ptr<AudioBus> &outputBus, int framesToProcess, bool checkIsAlreadyProcessed);
 
   bool isEnabled() const;
   void enable();
@@ -50,6 +54,7 @@ class AudioNode : public std::enable_shared_from_this<AudioNode> {
 
   std::unordered_set<AudioNode *> inputNodes_ = {};
   std::unordered_set<std::shared_ptr<AudioNode>> outputNodes_ = {};
+  std::unordered_set<std::shared_ptr<AudioParam>> outputParams_ = {};
 
   int numberOfEnabledInputNodes_ = 0;
   bool isInitialized_ = false;
@@ -63,7 +68,6 @@ class AudioNode : public std::enable_shared_from_this<AudioNode> {
   static std::string toString(ChannelCountMode mode);
   static std::string toString(ChannelInterpretation interpretation);
 
-  virtual std::shared_ptr<AudioBus> processAudio(const std::shared_ptr<AudioBus> &outputBus, int framesToProcess, bool checkIsAlreadyProcessed);
   virtual void processNode(const std::shared_ptr<AudioBus>&, int) = 0;
 
   bool isAlreadyProcessed();
@@ -73,6 +77,8 @@ class AudioNode : public std::enable_shared_from_this<AudioNode> {
 
   void connectNode(const std::shared_ptr<AudioNode> &node);
   void disconnectNode(const std::shared_ptr<AudioNode> &node);
+  void connectParam(const std::shared_ptr<AudioParam> &param);
+  void disconnectParam(const std::shared_ptr<AudioParam> &param);
 
   void onInputEnabled();
   void onInputDisabled();
diff --git a/packages/react-native-audio-api/common/cpp/audioapi/core/AudioParam.cpp b/packages/react-native-audio-api/common/cpp/audioapi/core/AudioParam.cpp
index 43403ddba..dada1b666 100644
--- a/packages/react-native-audio-api/common/cpp/audioapi/core/AudioParam.cpp
+++ b/packages/react-native-audio-api/common/cpp/audioapi/core/AudioParam.cpp
@@ -1,14 +1,27 @@
 #include <audioapi/core/AudioParam.h>
 #include <audioapi/core/BaseAudioContext.h>
 #include <audioapi/dsp/AudioUtils.h>
+#include <audioapi/dsp/VectorMath.h>
+#include <audioapi/utils/AudioArray.h>
+#include <iostream>
 
 namespace audioapi {
 
-AudioParam::AudioParam(float defaultValue, float minValue, float maxValue)
+AudioParam::AudioParam(
+    float defaultValue,
+    float minValue,
+    float maxValue,
+    BaseAudioContext *context)
     : value_(defaultValue),
       defaultValue_(defaultValue),
       minValue_(minValue),
-      maxValue_(maxValue) {
+      maxValue_(maxValue),
+      context_(context),
+      audioBus_(
+          std::make_shared<AudioBus>(
+              RENDER_QUANTUM_SIZE,
+              1,
+              context->getSampleRate())) {
   startTime_ = 0;
   endTime_ = 0;
   startValue_ = value_;
@@ -256,6 +269,44 @@ void AudioParam::cancelAndHoldAtTime(double cancelTime) {
   }
 }
 
+void AudioParam::addInputNode(AudioNode *node) {
+  auto position = inputNodes_.find(node);
+  if (position == inputNodes_.end()) {
+    inputNodes_.insert(node);
+  }
+}
+
+void AudioParam::removeInputNode(AudioNode *node) {
+  auto position = inputNodes_.find(node);
+
+  if (position != inputNodes_.end()) {
+    inputNodes_.erase(position);
+  }
+}
+
+std::shared_ptr<AudioBus> AudioParam::processARateParam(
+    int framesToProcess,
+    double time) {
+  auto processingBus = audioBus_;
+  processingBus->zero();
+  if (!inputNodes_.empty()) {
+    processInputs(processingBus, framesToProcess, true);
+    mixInputsBuses(processingBus);
+  }
+  for (size_t i = 0; i < framesToProcess; i++) {
+    auto sample = getValueAtTime(time + i / context_->getSampleRate());
+    processingBus->getChannel(0)->getData()[i] += sample;
+  }
+  // processingBus is a mono bus
+  return processingBus;
+}
+
+float AudioParam::processKRateParam(int framesToProcess, double time) {
+  auto processingBus = processARateParam(framesToProcess, time);
+  // processingBus is a mono bus
+  return processingBus->getChannel(0)->getData()[0];
+}
+
 double AudioParam::getQueueEndTime() {
   if (eventsQueue_.empty()) {
     return endTime_;
@@ -292,4 +343,35 @@ void AudioParam::updateQueue(ParamChangeEvent &event) {
   eventsQueue_.push_back(event);
 }
 
+void AudioParam::processInputs(
+    const std::shared_ptr<AudioBus> &outputBus,
+    int framesToProcess,
+    bool checkIsAlreadyProcessed) {
+  for (auto it = inputNodes_.begin(), end = inputNodes_.end(); it != end;
+       ++it) {
+    auto inputNode = *it;
+    assert(inputNode != nullptr);
+
+    if (!inputNode->isEnabled()) {
+      continue;
+    }
+
+    auto inputBus = inputNode->processAudio(
+        outputBus, framesToProcess, checkIsAlreadyProcessed);
+    inputBuses_.push_back(inputBus);
+  }
+}
+
+void AudioParam::mixInputsBuses(
+    const std::shared_ptr<AudioBus> &processingBus) {
+  assert(processingBus != nullptr);
+
+  for (auto it = inputBuses_.begin(), end = inputBuses_.end(); it != end;
+       ++it) {
+    processingBus->sum(it->get(), ChannelInterpretation::SPEAKERS);
+  }
+
+  inputBuses_.clear();
+}
+
 } // namespace audioapi
diff --git a/packages/react-native-audio-api/common/cpp/audioapi/core/AudioParam.h b/packages/react-native-audio-api/common/cpp/audioapi/core/AudioParam.h
index adb202c5f..ad931f1c9 100644
--- a/packages/react-native-audio-api/common/cpp/audioapi/core/AudioParam.h
+++ b/packages/react-native-audio-api/common/cpp/audioapi/core/AudioParam.h
@@ -2,17 +2,20 @@
 
 #include <audioapi/core/utils/ParamChangeEvent.h>
 #include <audioapi/core/types/ParamChangeEventType.h>
+#include <audioapi/utils/AudioBus.h>
+#include <audioapi/core/AudioNode.h>
 
 #include <deque>
 #include <memory>
 #include <vector>
+#include <unordered_set>
 #include <cstddef>
 
 namespace audioapi {
 
 class AudioParam {
  public:
-  explicit AudioParam(float defaultValue, float minValue, float maxValue);
+  explicit AudioParam(float defaultValue, float minValue, float maxValue, BaseAudioContext *context);
 
   [[nodiscard]] float getValue() const;
   float getValueAtTime(double time);
@@ -33,23 +36,33 @@ class AudioParam {
       double duration);
   void cancelScheduledValues(double cancelTime);
   void cancelAndHoldAtTime(double cancelTime);
+  void addInputNode(AudioNode* node);
+  void removeInputNode(AudioNode* node);
+  std::shared_ptr<AudioBus> processARateParam(int framesToProcess, double time);
+  float processKRateParam(int framesToProcess, double time);
 
  private:
   float value_;
   float defaultValue_;
   float minValue_;
   float maxValue_;
+  BaseAudioContext *context_;
   std::deque<ParamChangeEvent> eventsQueue_;
+  std::unordered_set<AudioNode *> inputNodes_;
+  std::shared_ptr<AudioBus> audioBus_;
 
   double startTime_;
   double endTime_;
   float startValue_;
   float endValue_;
   std::function<float(double, double, float, float, double)> calculateValue_;
+  std::vector<std::shared_ptr<AudioBus>> inputBuses_ = {};
 
   double getQueueEndTime();
   float getQueueEndValue();
   void updateQueue(ParamChangeEvent &event);
+  void processInputs(const std::shared_ptr<AudioBus>& outputBus, int framesToProcess, bool checkIsAlreadyProcessed);
+  void mixInputsBuses(const std::shared_ptr<AudioBus>& processingBus);
 };
 
 } // namespace audioapi
diff --git a/packages/react-native-audio-api/common/cpp/audioapi/core/effects/BiquadFilterNode.cpp b/packages/react-native-audio-api/common/cpp/audioapi/core/effects/BiquadFilterNode.cpp
index b6d54afcc..ca7c36907 100644
--- a/packages/react-native-audio-api/common/cpp/audioapi/core/effects/BiquadFilterNode.cpp
+++ b/packages/react-native-audio-api/common/cpp/audioapi/core/effects/BiquadFilterNode.cpp
@@ -10,16 +10,20 @@ namespace audioapi {
 
 BiquadFilterNode::BiquadFilterNode(BaseAudioContext *context)
     : AudioNode(context) {
-  frequencyParam_ =
-      std::make_shared<AudioParam>(350.0, 0.0f, context->getNyquistFrequency());
+  frequencyParam_ = std::make_shared<AudioParam>(
+      350.0, 0.0f, context->getNyquistFrequency(), context);
   detuneParam_ = std::make_shared<AudioParam>(
       0.0,
       -1200 * LOG2_MOST_POSITIVE_SINGLE_FLOAT,
-      1200 * LOG2_MOST_POSITIVE_SINGLE_FLOAT);
+      1200 * LOG2_MOST_POSITIVE_SINGLE_FLOAT,
+      context);
   QParam_ = std::make_shared<AudioParam>(
-      1.0, MOST_NEGATIVE_SINGLE_FLOAT, MOST_POSITIVE_SINGLE_FLOAT);
+      1.0, MOST_NEGATIVE_SINGLE_FLOAT, MOST_POSITIVE_SINGLE_FLOAT, context);
   gainParam_ = std::make_shared<AudioParam>(
-      0.0, MOST_NEGATIVE_SINGLE_FLOAT, 40 * LOG10_MOST_POSITIVE_SINGLE_FLOAT);
+      0.0,
+      MOST_NEGATIVE_SINGLE_FLOAT,
+      40 * LOG10_MOST_POSITIVE_SINGLE_FLOAT,
+      context);
   type_ = BiquadFilterType::LOWPASS;
   isInitialized_ = true;
 }
@@ -304,48 +308,46 @@ void BiquadFilterNode::setAllpassCoefficients(float frequency, float Q) {
 void BiquadFilterNode::applyFilter() {
   double currentTime = context_->getCurrentTime();
 
-  float normalizedFrequency = frequencyParam_->getValueAtTime(currentTime) /
-      context_->getNyquistFrequency();
-  float detuneValue = detuneParam_->getValueAtTime(currentTime);
+  float frequencyParamValue =
+      frequencyParam_->processKRateParam(RENDER_QUANTUM_SIZE, currentTime);
+  float normalizedFrequency =
+      frequencyParamValue / context_->getNyquistFrequency();
+
+  float detuneValue =
+      detuneParam_->processKRateParam(RENDER_QUANTUM_SIZE, currentTime);
 
   if (detuneValue != 0.0) {
     normalizedFrequency *= std::pow(2.0f, detuneValue / 1200.0f);
   }
 
+  auto qparamValue =
+      QParam_->processKRateParam(RENDER_QUANTUM_SIZE, currentTime);
+  auto gainParamValue =
+      gainParam_->processKRateParam(RENDER_QUANTUM_SIZE, currentTime);
   switch (type_) {
     case BiquadFilterType::LOWPASS:
-      setLowpassCoefficients(
-          normalizedFrequency, QParam_->getValueAtTime(currentTime));
+      setLowpassCoefficients(normalizedFrequency, qparamValue);
       break;
     case BiquadFilterType::HIGHPASS:
-      setHighpassCoefficients(
-          normalizedFrequency, QParam_->getValueAtTime(currentTime));
+      setHighpassCoefficients(normalizedFrequency, qparamValue);
       break;
     case BiquadFilterType::BANDPASS:
-      setBandpassCoefficients(
-          normalizedFrequency, QParam_->getValueAtTime(currentTime));
+      setBandpassCoefficients(normalizedFrequency, qparamValue);
       break;
     case BiquadFilterType::LOWSHELF:
-      setLowshelfCoefficients(
-          normalizedFrequency, gainParam_->getValueAtTime(currentTime));
+      setLowshelfCoefficients(normalizedFrequency, gainParamValue);
       break;
     case BiquadFilterType::HIGHSHELF:
-      setHighshelfCoefficients(
-          normalizedFrequency, gainParam_->getValueAtTime(currentTime));
+      setHighshelfCoefficients(normalizedFrequency, gainParamValue);
       break;
     case BiquadFilterType::PEAKING:
-      setPeakingCoefficients(
-          normalizedFrequency,
-          QParam_->getValueAtTime(currentTime),
-          gainParam_->getValueAtTime(currentTime));
+      setPeakingCoefficients(normalizedFrequency, qparamValue, gainParamValue);
       break;
     case BiquadFilterType::NOTCH:
-      setNotchCoefficients(
-          normalizedFrequency, QParam_->getValueAtTime(currentTime));
+      setNotchCoefficients(normalizedFrequency, qparamValue);
       break;
     case BiquadFilterType::ALLPASS:
-      setAllpassCoefficients(
-          normalizedFrequency, QParam_->getValueAtTime(currentTime));
+      setAllpassCoefficients(normalizedFrequency, qparamValue);
       break;
     default:
       break;
diff --git a/packages/react-native-audio-api/common/cpp/audioapi/core/effects/GainNode.cpp b/packages/react-native-audio-api/common/cpp/audioapi/core/effects/GainNode.cpp
index f7af24bcd..f6500e733 100644
--- a/packages/react-native-audio-api/common/cpp/audioapi/core/effects/GainNode.cpp
+++ b/packages/react-native-audio-api/common/cpp/audioapi/core/effects/GainNode.cpp
@@ -1,5 +1,6 @@
 #include <audioapi/core/BaseAudioContext.h>
 #include <audioapi/core/effects/GainNode.h>
+#include <audioapi/dsp/VectorMath.h>
 #include <audioapi/utils/AudioArray.h>
 #include <audioapi/utils/AudioBus.h>
 
@@ -7,7 +8,7 @@ namespace audioapi {
 
 GainNode::GainNode(BaseAudioContext *context) : AudioNode(context) {
   gainParam_ = std::make_shared<AudioParam>(
-      1.0, MOST_NEGATIVE_SINGLE_FLOAT, MOST_POSITIVE_SINGLE_FLOAT);
+      1.0, MOST_NEGATIVE_SINGLE_FLOAT, MOST_POSITIVE_SINGLE_FLOAT, context);
   isInitialized_ = true;
 }
 
@@ -19,14 +20,13 @@ void GainNode::processNode(
     const std::shared_ptr<AudioBus> &processingBus,
     int framesToProcess) {
   double time = context_->getCurrentTime();
-  double deltaTime = 1.0 / context_->getSampleRate();
-
-  for (int i = 0; i < framesToProcess; i += 1) {
-    for (int j = 0; j < processingBus->getNumberOfChannels(); j += 1) {
-      (*processingBus->getChannel(j))[i] *= gainParam_->getValueAtTime(time);
-    }
-
-    time += deltaTime;
+  auto gainParamValues = gainParam_->processARateParam(framesToProcess, time);
+  for (int i = 0; i < processingBus->getNumberOfChannels(); i += 1) {
+    dsp::multiply(
+        processingBus->getChannel(i)->getData(),
+        gainParamValues->getChannel(0)->getData(),
+        processingBus->getChannel(i)->getData(),
+        framesToProcess);
   }
 }
 
diff --git a/packages/react-native-audio-api/common/cpp/audioapi/core/effects/StereoPannerNode.cpp b/packages/react-native-audio-api/common/cpp/audioapi/core/effects/StereoPannerNode.cpp
index 343004717..8921330e5 100644
--- a/packages/react-native-audio-api/common/cpp/audioapi/core/effects/StereoPannerNode.cpp
+++ b/packages/react-native-audio-api/common/cpp/audioapi/core/effects/StereoPannerNode.cpp
@@ -11,7 +11,7 @@ namespace audioapi {
 StereoPannerNode::StereoPannerNode(BaseAudioContext *context)
     : AudioNode(context) {
   channelCountMode_ = ChannelCountMode::EXPLICIT;
-  panParam_ = std::make_shared<AudioParam>(0.0, -1.0f, 1.0f);
+  panParam_ = std::make_shared<AudioParam>(0.0, -1.0f, 1.0f, context);
   isInitialized_ = true;
 }
 
@@ -27,9 +27,12 @@ void StereoPannerNode::processNode(
 
   AudioArray *left = processingBus->getChannelByType(AudioBus::ChannelLeft);
   AudioArray *right = processingBus->getChannelByType(AudioBus::ChannelRight);
+  auto panParamValues = panParam_->processARateParam(framesToProcess, time)
+                            ->getChannel(0)
+                            ->getData();
 
   for (int i = 0; i < framesToProcess; i += 1) {
-    auto pan = panParam_->getValueAtTime(time);
+    auto pan = panParamValues[i];
 
     auto x = (pan <= 0 ? pan + 1 : pan);
 
diff --git a/packages/react-native-audio-api/common/cpp/audioapi/core/sources/AudioBufferSourceNode.cpp b/packages/react-native-audio-api/common/cpp/audioapi/core/sources/AudioBufferSourceNode.cpp
index 44a659914..79be9a9de 100644
--- a/packages/react-native-audio-api/common/cpp/audioapi/core/sources/AudioBufferSourceNode.cpp
+++ b/packages/react-native-audio-api/common/cpp/audioapi/core/sources/AudioBufferSourceNode.cpp
@@ -22,9 +22,9 @@ AudioBufferSourceNode::AudioBufferSourceNode(
   alignedBus_ = std::shared_ptr<AudioBus>(nullptr);
 
   detuneParam_ = std::make_shared<AudioParam>(
-      0.0, MOST_NEGATIVE_SINGLE_FLOAT, MOST_POSITIVE_SINGLE_FLOAT);
+      0.0, MOST_NEGATIVE_SINGLE_FLOAT, MOST_POSITIVE_SINGLE_FLOAT, context);
   playbackRateParam_ = std::make_shared<AudioParam>(
-      1.0, MOST_NEGATIVE_SINGLE_FLOAT, MOST_POSITIVE_SINGLE_FLOAT);
+      1.0, MOST_NEGATIVE_SINGLE_FLOAT, MOST_POSITIVE_SINGLE_FLOAT, context);
 
   playbackRateBus_ = std::make_shared<AudioBus>(
       RENDER_QUANTUM_SIZE * 3, channelCount_, context_->getSampleRate());
@@ -173,7 +173,7 @@ void AudioBufferSourceNode::processWithoutPitchCorrection(
   size_t startOffset = 0;
   size_t offsetLength = 0;
 
-  auto computedPlaybackRate = getComputedPlaybackRateValue();
+  auto computedPlaybackRate = getComputedPlaybackRateValue(framesToProcess);
   updatePlaybackInfo(processingBus, framesToProcess, startOffset, offsetLength);
 
   if (computedPlaybackRate == 0.0f || (!isPlaying() && !isStopScheduled())) {
@@ -197,10 +197,12 @@ void AudioBufferSourceNode::processWithPitchCorrection(
   size_t offsetLength = 0;
 
   auto time = context_->getCurrentTime();
-  auto playbackRate =
-      std::clamp(playbackRateParam_->getValueAtTime(time), 0.0f, 3.0f);
-  auto detune =
-      std::clamp(detuneParam_->getValueAtTime(time) / 100.0f, -12.0f, 12.0f);
+  auto playbackRate = std::clamp(
+      playbackRateParam_->processKRateParam(framesToProcess, time), 0.0f, 3.0f);
+  auto detune = std::clamp(
+      detuneParam_->processKRateParam(framesToProcess, time) / 100.0f,
+      -12.0f,
+      12.0f);
 
   playbackRateBus_->zero();
 
@@ -349,13 +351,15 @@ void AudioBufferSourceNode::processWithInterpolation(
   }
 }
 
-float AudioBufferSourceNode::getComputedPlaybackRateValue() {
+float AudioBufferSourceNode::getComputedPlaybackRateValue(int framesToProcess) {
   auto time = context_->getCurrentTime();
 
   auto sampleRateFactor =
       alignedBus_->getSampleRate() / context_->getSampleRate();
-  auto playbackRate = playbackRateParam_->getValueAtTime(time);
-  auto detune = std::pow(2.0f, detuneParam_->getValueAtTime(time) / 1200.0f);
+  auto playbackRate =
+      playbackRateParam_->processKRateParam(framesToProcess, time);
+  auto detune = std::pow(
+      2.0f, detuneParam_->processKRateParam(framesToProcess, time) / 1200.0f);
 
   return playbackRate * sampleRateFactor * detune;
 }
diff --git a/packages/react-native-audio-api/common/cpp/audioapi/core/sources/AudioBufferSourceNode.h b/packages/react-native-audio-api/common/cpp/audioapi/core/sources/AudioBufferSourceNode.h
index c414b8105..205330238 100644
--- a/packages/react-native-audio-api/common/cpp/audioapi/core/sources/AudioBufferSourceNode.h
+++ b/packages/react-native-audio-api/common/cpp/audioapi/core/sources/AudioBufferSourceNode.h
@@ -81,7 +81,7 @@ class AudioBufferSourceNode : public AudioScheduledSourceNode {
       size_t offsetLength,
       float playbackRate);
 
-  float getComputedPlaybackRateValue();
+  float getComputedPlaybackRateValue(int framesToProcess);
 
   double getVirtualStartFrame();
   double getVirtualEndFrame();
diff --git a/packages/react-native-audio-api/common/cpp/audioapi/core/sources/OscillatorNode.cpp b/packages/react-native-audio-api/common/cpp/audioapi/core/sources/OscillatorNode.cpp
index 43ee1db2c..7d7b036c9 100644
--- a/packages/react-native-audio-api/common/cpp/audioapi/core/sources/OscillatorNode.cpp
+++ b/packages/react-native-audio-api/common/cpp/audioapi/core/sources/OscillatorNode.cpp
@@ -9,11 +9,15 @@ namespace audioapi {
 OscillatorNode::OscillatorNode(BaseAudioContext *context)
     : AudioScheduledSourceNode(context) {
   frequencyParam_ = std::make_shared<AudioParam>(
-      444.0, -context_->getNyquistFrequency(), context_->getNyquistFrequency());
+      444.0,
+      -context_->getNyquistFrequency(),
+      context_->getNyquistFrequency(),
+      context);
   detuneParam_ = std::make_shared<AudioParam>(
       0.0,
       -1200 * LOG2_MOST_POSITIVE_SINGLE_FLOAT,
-      1200 * LOG2_MOST_POSITIVE_SINGLE_FLOAT);
+      1200 * LOG2_MOST_POSITIVE_SINGLE_FLOAT,
+      context);
   type_ = OscillatorType::SINE;
   periodicWave_ = context_->getBasicWaveForm(type_);
 
@@ -56,14 +60,17 @@ void OscillatorNode::processNode(
     return;
   }
 
-  auto deltaTime = 1.0 / context_->getSampleRate();
-  auto time =
-      context_->getCurrentTime() + static_cast<double>(startOffset) * deltaTime;
+  auto time = context_->getCurrentTime() +
+      static_cast<double>(startOffset) * 1.0 / context_->getSampleRate();
+  auto detuneParamValues =
+      detuneParam_->processARateParam(framesToProcess, time);
+  auto frequencyParamValues =
+      frequencyParam_->processARateParam(framesToProcess, time);
 
   for (size_t i = startOffset; i < offsetLength; i += 1) {
-    auto detuneRatio =
-        std::pow(2.0f, detuneParam_->getValueAtTime(time) / 1200.0f);
-    auto detunedFrequency = frequencyParam_->getValueAtTime(time) * detuneRatio;
+    auto detuneRatio = std::pow(
+        2.0f, detuneParamValues->getChannel(0)->getData()[i] / 1200.0f);
+    auto detunedFrequency = frequencyParamValues->getChannel(0)->getData()[i] * detuneRatio;
     auto phaseIncrement = detunedFrequency * periodicWave_->getScale();
 
     float sample =
@@ -78,8 +85,6 @@ void OscillatorNode::processNode(
         floor(
             phase_ / static_cast<float>(periodicWave_->getPeriodicWaveSize())) *
         static_cast<float>(periodicWave_->getPeriodicWaveSize());
-
-    time += deltaTime;
   }
 
   handleStopScheduled();
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 5cbf98b51..5464843ea 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,4 +1,5 @@
 #include <audioapi/core/AudioNode.h>
+#include <audioapi/core/AudioParam.h>
 #include <audioapi/core/sources/AudioScheduledSourceNode.h>
 #include <audioapi/core/utils/AudioNodeManager.h>
 #include <audioapi/core/utils/Locker.h>
@@ -9,7 +10,7 @@ AudioNodeManager::~AudioNodeManager() {
   cleanup();
 }
 
-void AudioNodeManager::addPendingConnection(
+void AudioNodeManager::addPendingNodeConnection(
     const std::shared_ptr<AudioNode> &from,
     const std::shared_ptr<AudioNode> &to,
     ConnectionType type) {
@@ -18,6 +19,15 @@ void AudioNodeManager::addPendingConnection(
   audioNodesToConnect_.emplace_back(from, to, type);
 }
 
+void AudioNodeManager::addPendingParamConnection(
+    const std::shared_ptr<AudioNode> &from,
+    const std::shared_ptr<AudioParam> &to,
+    ConnectionType type) {
+  Locker lock(getGraphLock());
+
+  audioParamToConnect_.emplace_back(from, to, type);
+}
+
 void AudioNodeManager::preProcessGraph() {
   if (auto locker = Locker::tryLock(getGraphLock())) {
     settlePendingConnections();
@@ -41,6 +51,11 @@ void AudioNodeManager::addSourceNode(
   sourceNodes_.insert(node);
 }
 
+void AudioNodeManager::addAudioParam(const std::shared_ptr<AudioParam> &param) {
+  Locker lock(getGraphLock());
+  audioParams_.insert(param);
+}
+
 void AudioNodeManager::settlePendingConnections() {
   for (auto it = audioNodesToConnect_.begin(), end = audioNodesToConnect_.end();
        it != end;
@@ -60,6 +75,25 @@ void AudioNodeManager::settlePendingConnections() {
   }
 
   audioNodesToConnect_.clear();
+
+  for (auto it = audioParamToConnect_.begin(), end = audioParamToConnect_.end();
+       it != end;
+       ++it) {
+    std::shared_ptr<AudioNode> from = std::get<0>(*it);
+    std::shared_ptr<AudioParam> to = std::get<1>(*it);
+    ConnectionType type = std::get<2>(*it);
+
+    assert(from != nullptr);
+    assert(to != nullptr);
+
+    if (type == ConnectionType::CONNECT) {
+      from->connectParam(to);
+    } else {
+      from->disconnectParam(to);
+    }
+  }
+
+  audioParamToConnect_.clear();
 }
 
 void AudioNodeManager::cleanupNode(const std::shared_ptr<AudioNode> &node) {
diff --git a/packages/react-native-audio-api/common/cpp/audioapi/core/utils/AudioNodeManager.h b/packages/react-native-audio-api/common/cpp/audioapi/core/utils/AudioNodeManager.h
index 4a2350239..81c0384f4 100644
--- a/packages/react-native-audio-api/common/cpp/audioapi/core/utils/AudioNodeManager.h
+++ b/packages/react-native-audio-api/common/cpp/audioapi/core/utils/AudioNodeManager.h
@@ -12,6 +12,7 @@ namespace audioapi {
 
 class AudioNode;
 class AudioScheduledSourceNode;
+class AudioParam;
 
 class AudioNodeManager {
  public:
@@ -23,13 +24,19 @@ class AudioNodeManager {
 
   void preProcessGraph();
 
-  void addPendingConnection(
+  void addPendingNodeConnection(
       const std::shared_ptr<AudioNode> &from,
       const std::shared_ptr<AudioNode> &to,
       ConnectionType type);
 
+  void addPendingParamConnection(
+      const std::shared_ptr<AudioNode> &from,
+      const std::shared_ptr<AudioParam> &to,
+    ConnectionType type);
+
   void addProcessingNode(const std::shared_ptr<AudioNode> &node);
   void addSourceNode(const std::shared_ptr<AudioScheduledSourceNode> &node);
+  void addAudioParam(const std::shared_ptr<AudioParam> &param);
 
   void cleanup();
 
@@ -40,6 +47,7 @@ class AudioNodeManager {
   // all nodes created in the context
   std::unordered_set<std::shared_ptr<AudioScheduledSourceNode>> sourceNodes_;
   std::unordered_set<std::shared_ptr<AudioNode>> processingNodes_;
+  std::unordered_set<std::shared_ptr<AudioParam>> audioParams_;
 
   // connections to be settled
   std::vector<std::tuple<
@@ -48,6 +56,12 @@ class AudioNodeManager {
       ConnectionType>>
       audioNodesToConnect_;
 
+  std::vector<std::tuple<
+      std::shared_ptr<AudioNode>,
+      std::shared_ptr<AudioParam>,
+      ConnectionType>>
+      audioParamToConnect_;
+
   void settlePendingConnections();
   void cleanupNode(const std::shared_ptr<AudioNode> &node);
   void prepareNodesForDestruction();
diff --git a/packages/react-native-audio-api/src/core/AudioBufferSourceNode.ts b/packages/react-native-audio-api/src/core/AudioBufferSourceNode.ts
index d93b71e98..581d9d7d8 100644
--- a/packages/react-native-audio-api/src/core/AudioBufferSourceNode.ts
+++ b/packages/react-native-audio-api/src/core/AudioBufferSourceNode.ts
@@ -12,8 +12,8 @@ export default class AudioBufferSourceNode extends AudioScheduledSourceNode {
   constructor(context: BaseAudioContext, node: IAudioBufferSourceNode) {
     super(context, node);
 
-    this.detune = new AudioParam(node.detune);
-    this.playbackRate = new AudioParam(node.playbackRate);
+    this.detune = new AudioParam(node.detune, context);
+    this.playbackRate = new AudioParam(node.playbackRate, context);
   }
 
   public get buffer(): AudioBuffer | null {
diff --git a/packages/react-native-audio-api/src/core/AudioNode.ts b/packages/react-native-audio-api/src/core/AudioNode.ts
index ffbdad08c..150f412fb 100644
--- a/packages/react-native-audio-api/src/core/AudioNode.ts
+++ b/packages/react-native-audio-api/src/core/AudioNode.ts
@@ -1,4 +1,5 @@
 import { IAudioNode } from '../interfaces';
+import AudioParam from './AudioParam';
 import { ChannelCountMode, ChannelInterpretation } from '../types';
 import BaseAudioContext from './BaseAudioContext';
 import { InvalidAccessError } from '../errors';
@@ -22,14 +23,18 @@ export default class AudioNode {
     this.channelInterpretation = this.node.channelInterpretation;
   }
 
-  public connect(destination: AudioNode): AudioNode {
+  public connect(destination: AudioNode | AudioParam): AudioNode | AudioParam {
     if (this.context !== destination.context) {
       throw new InvalidAccessError(
-        'The AudioNodes are from different BaseAudioContexts'
+        'Source and destination are from different BaseAudioContexts'
       );
     }
 
-    this.node.connect(destination.node);
+    if (destination instanceof AudioParam) {
+      this.node.connect(destination.audioParam);
+    } else {
+      this.node.connect(destination.node);
+    }
 
     return destination;
   }
diff --git a/packages/react-native-audio-api/src/core/AudioParam.ts b/packages/react-native-audio-api/src/core/AudioParam.ts
index 72bb6674f..d8cfb5fed 100644
--- a/packages/react-native-audio-api/src/core/AudioParam.ts
+++ b/packages/react-native-audio-api/src/core/AudioParam.ts
@@ -1,18 +1,21 @@
 import { IAudioParam } from '../interfaces';
 import { RangeError, InvalidStateError } from '../errors';
+import BaseAudioContext from './BaseAudioContext';
 
 export default class AudioParam {
   readonly defaultValue: number;
   readonly minValue: number;
   readonly maxValue: number;
-  private readonly audioParam: IAudioParam;
+  readonly audioParam: IAudioParam;
+  readonly context: BaseAudioContext;
 
-  constructor(audioParam: IAudioParam) {
+  constructor(audioParam: IAudioParam, context: BaseAudioContext) {
     this.audioParam = audioParam;
     this.value = audioParam.value;
     this.defaultValue = audioParam.defaultValue;
     this.minValue = audioParam.minValue;
     this.maxValue = audioParam.maxValue;
+    this.context = context;
   }
 
   public get value(): number {
diff --git a/packages/react-native-audio-api/src/core/BiquadFilterNode.ts b/packages/react-native-audio-api/src/core/BiquadFilterNode.ts
index 80ec88de2..68db7c02a 100644
--- a/packages/react-native-audio-api/src/core/BiquadFilterNode.ts
+++ b/packages/react-native-audio-api/src/core/BiquadFilterNode.ts
@@ -13,10 +13,10 @@ export default class BiquadFilterNode extends AudioNode {
 
   constructor(context: BaseAudioContext, biquadFilter: IBiquadFilterNode) {
     super(context, biquadFilter);
-    this.frequency = new AudioParam(biquadFilter.frequency);
-    this.detune = new AudioParam(biquadFilter.detune);
-    this.Q = new AudioParam(biquadFilter.Q);
-    this.gain = new AudioParam(biquadFilter.gain);
+    this.frequency = new AudioParam(biquadFilter.frequency, context);
+    this.detune = new AudioParam(biquadFilter.detune, context);
+    this.Q = new AudioParam(biquadFilter.Q, context);
+    this.gain = new AudioParam(biquadFilter.gain, context);
   }
 
   public get type(): BiquadFilterType {
diff --git a/packages/react-native-audio-api/src/core/GainNode.ts b/packages/react-native-audio-api/src/core/GainNode.ts
index 03358e9d4..a216f1365 100644
--- a/packages/react-native-audio-api/src/core/GainNode.ts
+++ b/packages/react-native-audio-api/src/core/GainNode.ts
@@ -8,6 +8,6 @@ export default class GainNode extends AudioNode {
 
   constructor(context: BaseAudioContext, gain: IGainNode) {
     super(context, gain);
-    this.gain = new AudioParam(gain.gain);
+    this.gain = new AudioParam(gain.gain, context);
   }
 }
diff --git a/packages/react-native-audio-api/src/core/OscillatorNode.ts b/packages/react-native-audio-api/src/core/OscillatorNode.ts
index 00a6028f3..40248071e 100644
--- a/packages/react-native-audio-api/src/core/OscillatorNode.ts
+++ b/packages/react-native-audio-api/src/core/OscillatorNode.ts
@@ -12,8 +12,8 @@ export default class OscillatorNode extends AudioScheduledSourceNode {
 
   constructor(context: BaseAudioContext, node: IOscillatorNode) {
     super(context, node);
-    this.frequency = new AudioParam(node.frequency);
-    this.detune = new AudioParam(node.detune);
+    this.frequency = new AudioParam(node.frequency, context);
+    this.detune = new AudioParam(node.detune, context);
     this.type = node.type;
   }
 
diff --git a/packages/react-native-audio-api/src/core/StereoPannerNode.ts b/packages/react-native-audio-api/src/core/StereoPannerNode.ts
index a4195da63..a8fa77006 100644
--- a/packages/react-native-audio-api/src/core/StereoPannerNode.ts
+++ b/packages/react-native-audio-api/src/core/StereoPannerNode.ts
@@ -8,6 +8,6 @@ export default class StereoPannerNode extends AudioNode {
 
   constructor(context: BaseAudioContext, pan: IStereoPannerNode) {
     super(context, pan);
-    this.pan = new AudioParam(pan.pan);
+    this.pan = new AudioParam(pan.pan, context);
   }
 }
diff --git a/packages/react-native-audio-api/src/interfaces.ts b/packages/react-native-audio-api/src/interfaces.ts
index dcfc6e3ee..62b9f4104 100644
--- a/packages/react-native-audio-api/src/interfaces.ts
+++ b/packages/react-native-audio-api/src/interfaces.ts
@@ -54,8 +54,8 @@ export interface IAudioNode {
   readonly channelCountMode: ChannelCountMode;
   readonly channelInterpretation: ChannelInterpretation;
 
-  connect: (node: IAudioNode) => void;
-  disconnect: (node?: IAudioNode) => void;
+  connect: (destination: IAudioNode | IAudioParam) => void;
+  disconnect: (destination?: IAudioNode | IAudioParam) => void;
 }
 
 export interface IGainNode extends IAudioNode {


Reviews:
user: login: michalsek
state: APPROVED
author_association: MEMBER
submitted_at: 2025-05-05T15:46:03Z
user: login: maciejmakowski2003
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-05-07T09:20:37Z
user: login: mdydek
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-05-07T09:47:50Z
user: login: maciejmakowski2003
body: Looks great! If you test it, we can merge ;))
state: APPROVED
author_association: COLLABORATOR
submitted_at: 2025-05-12T10:58:49Z

Review comments:
diff_hunk: @@ -47,21 +48,36 @@ class AudioNodeHostObject : public JsiHostObject {
   }
 
   JSI_HOST_FUNCTION(connect) {
-    auto node =
-        args[0].getObject(runtime).getHostObject<AudioNodeHostObject>(runtime);
-    node_->connect(std::shared_ptr<AudioNodeHostObject>(node)->node_);
+    if (auto obj = args[0].getObject(runtime);
path: packages/react-native-audio-api/common/cpp/audioapi/HostObjects/AudioNodeHostObject.h
user: login: maciejmakowski2003
body: Could we move getting jsi::Object before if statements, it prevents code duplication.


created_at: 2025-05-07T07:39:06Z
author_association: COLLABORATOR
diff_hunk: @@ -47,21 +48,36 @@ class AudioNodeHostObject : public JsiHostObject {
   }
 
   JSI_HOST_FUNCTION(connect) {
-    auto node =
-        args[0].getObject(runtime).getHostObject<AudioNodeHostObject>(runtime);
-    node_->connect(std::shared_ptr<AudioNodeHostObject>(node)->node_);
+    if (auto obj = args[0].getObject(runtime);
+        obj.isHostObject<AudioNodeHostObject>(runtime)) {
+      auto node = obj.getHostObject<AudioNodeHostObject>(runtime);
+      node_->connect(std::shared_ptr<AudioNodeHostObject>(node)->node_);
+    }
+    if (auto obj = args[0].getObject(runtime);
+        obj.isHostObject<AudioParamHostObject>(runtime)) {
+      auto param = obj.getHostObject<AudioParamHostObject>(runtime);
+      node_->connect(std::shared_ptr<AudioParamHostObject>(param)->param_);
+    }
     return jsi::Value::undefined();
   }
 
   JSI_HOST_FUNCTION(disconnect) {
-      if(args[0].isUndefined()) {
-          node_->disconnect();
-          return jsi::Value::undefined();
-      }
+    if (args[0].isUndefined()) {
+      node_->disconnect();
+      return jsi::Value::undefined();
+    }
+
+    if (auto obj = args[0].getObject(runtime);
path: packages/react-native-audio-api/common/cpp/audioapi/HostObjects/AudioNodeHostObject.h
user: login: maciejmakowski2003
body: same here ;))
created_at: 2025-05-07T07:40:42Z
author_association: COLLABORATOR
diff_hunk: @@ -105,7 +105,7 @@ class AudioParamHostObject : public JsiHostObject {
     param_->setValue(static_cast<float>(value.getNumber()));
   }
 
- private:
+ public:
path: packages/react-native-audio-api/common/cpp/audioapi/HostObjects/AudioParamHostObject.h
user: login: maciejmakowski2003
body: instead of public, we can leave private and add AudioNodeHostObject as a friend class
created_at: 2025-05-07T07:42:06Z
author_association: COLLABORATOR
diff_hunk: @@ -304,9 +308,13 @@ void BiquadFilterNode::setAllpassCoefficients(float frequency, float Q) {
 void BiquadFilterNode::applyFilter() {
   double currentTime = context_->getCurrentTime();
 
-  float normalizedFrequency = frequencyParam_->getValueAtTime(currentTime) /
-      context_->getNyquistFrequency();
-  float detuneValue = detuneParam_->getValueAtTime(currentTime);
+  float frequencyParamValue = frequencyParam_->processKRateParam(
path: packages/react-native-audio-api/common/cpp/audioapi/core/effects/BiquadFilterNode.cpp
user: login: maciejmakowski2003
body: I guess we have to fix how we process data in `BiquadFilterNode`, because all params are a-rate ([source](https://developer.mozilla.org/en-US/docs/Web/API/BiquadFilterNode)), so we have to apply filter in each iteration.
created_at: 2025-05-07T07:51:28Z
author_association: COLLABORATOR
diff_hunk: @@ -19,14 +19,12 @@ void GainNode::processNode(
     const std::shared_ptr<AudioBus> &processingBus,
     int framesToProcess) {
   double time = context_->getCurrentTime();
-  double deltaTime = 1.0 / context_->getSampleRate();
-
+  auto gainParamValues = gainParam_->processARateParam(
+      framesToProcess, time, context_->getSampleRate());
   for (int i = 0; i < framesToProcess; i += 1) {
     for (int j = 0; j < processingBus->getNumberOfChannels(); j += 1) {
-      (*processingBus->getChannel(j))[i] *= gainParam_->getValueAtTime(time);
+      (*processingBus->getChannel(j))[i] *= gainParamValues->getData()[i];
path: packages/react-native-audio-api/common/cpp/audioapi/core/effects/GainNode.cpp
user: login: maciejmakowski2003
body: Please use `multiply` function from `dsp/VectorMath.h` here
created_at: 2025-05-07T07:54:23Z
author_association: COLLABORATOR
diff_hunk: @@ -1,18 +1,21 @@
 import { IAudioParam } from '../interfaces';
 import { RangeError, InvalidStateError } from '../errors';
+import BaseAudioContext from './BaseAudioContext';
 
 export default class AudioParam {
   readonly defaultValue: number;
   readonly minValue: number;
   readonly maxValue: number;
-  private readonly audioParam: IAudioParam;
+  readonly audioParam: IAudioParam;
+  readonly context: BaseAudioContext;
 
-  constructor(audioParam: IAudioParam) {
+  constructor(audioParam: IAudioParam, context: BaseAudioContext) {
path: packages/react-native-audio-api/src/core/AudioParam.ts
user: login: maciejmakowski2003
body: Why do we need context as a property in TS layer?
created_at: 2025-05-07T07:56:24Z
author_association: COLLABORATOR
diff_hunk: @@ -1,18 +1,21 @@
 import { IAudioParam } from '../interfaces';
 import { RangeError, InvalidStateError } from '../errors';
+import BaseAudioContext from './BaseAudioContext';
 
 export default class AudioParam {
   readonly defaultValue: number;
   readonly minValue: number;
   readonly maxValue: number;
-  private readonly audioParam: IAudioParam;
+  readonly audioParam: IAudioParam;
+  readonly context: BaseAudioContext;
 
-  constructor(audioParam: IAudioParam) {
+  constructor(audioParam: IAudioParam, context: BaseAudioContext) {
path: packages/react-native-audio-api/src/core/AudioParam.ts
user: login: mdydek
body: We need it, because we are comparing audio contexts when we connect node and param, to check whether they are created from the same context
created_at: 2025-05-07T09:47:50Z
author_association: COLLABORATOR
diff_hunk: @@ -315,37 +323,53 @@ void BiquadFilterNode::applyFilter() {
   switch (type_) {
     case BiquadFilterType::LOWPASS:
       setLowpassCoefficients(
-          normalizedFrequency, QParam_->getValueAtTime(currentTime));
+          normalizedFrequency,
+          QParam_->processKRateParam(
path: packages/react-native-audio-api/common/cpp/audioapi/core/effects/BiquadFilterNode.cpp
user: login: maciejmakowski2003
body: Please move getting param values before switch statement. it will allow us to avoid code duplication
created_at: 2025-05-12T10:56:38Z
author_association: COLLABORATOR

PR comments:
user: login: github-actions[bot]
created_at: 2025-05-05T15:26:06Z
body: 
# @check-spelling-bot Report
## :red_circle: Please review
### See the [:open_file_folder: files](https://github.com/software-mansion/react-native-audio-api/pull/432/files/) view, the [:scroll:action log](https://github.com/software-mansion/react-native-audio-api/actions/runs/14840048679/job/41660572105#step:4:1),  or [:memo: job summary](https://github.com/software-mansion/react-native-audio-api/actions/runs/14840048679/attempts/1#summary-41660572105) for details.

### Unrecognized words (4)

FLAC
Roadmap
WAV
Worklets

<details><summary>To accept these unrecognized words as correct,
you could run the following commands</summary>

... in a clone of the [git@github.com:software-mansion/react-native-audio-api.git](https://github.com/software-mansion/react-native-audio-api.git) repository
on the `feat/audio-param-connection-from-node` branch ([:information_source: how do I use this?](
https://docs.check-spelling.dev/Accepting-Suggestions)):

``` sh
curl -s -S -L 'https://raw.githubusercontent.com/check-spelling/check-spelling/main/apply.pl' |
perl - 'https://github.com/software-mansion/react-native-audio-api/actions/runs/14840048679/attempts/1'

```
</details>


**OR**

To have the bot accept them for you, comment in the PR quoting the following line:
@check-spelling-bot apply [updates](https://github.com/software-mansion/react-native-audio-api/actions/runs/14840048679/attempts/1).


<!-- See https://github.com/check-spelling/check-spelling/wiki/Configuration-Examples%3A-advice --> <!-- markdownlint-disable MD033 MD041 -->
<details><summary>If the flagged items are :exploding_head: false positives</summary>

If items relate to a ...
* binary file (or some other file you wouldn't want to check at all).

  Please add a file path to the `excludes.txt` file matching the containing file.

  File paths are Perl 5 Regular Expressions - you can [test](
https://www.regexplanet.com/advanced/perl/) yours before committing to verify it will match your files.

  `^` refers to the file's path from the root of the repository, so `^README\.md$` would exclude [README.md](
../tree/HEAD/README.md) (on whichever branch you're using).

* well-formed pattern.

  If you can write a [pattern](https://github.com/check-spelling/check-spelling/wiki/Configuration-Examples:-patterns) that would match it,
  try adding it to the `patterns.txt` file.

  Patterns are Perl 5 Regular Expressions - you can [test](
https://www.regexplanet.com/advanced/perl/) yours before committing to verify it will match your lines.

  Note that patterns can't match multiline strings.

</details>

<!-- adoption information-->
:steam_locomotive: If you're seeing this message and your PR is from a branch that doesn't have check-spelling,
please merge to your PR's base branch to get the version configured for your repository.

author_association: NONE
user: login: github-actions[bot]
created_at: 2025-05-05T15:44:22Z
body: 
# @check-spelling-bot Report
## :red_circle: Please review
### See the [:open_file_folder: files](https://github.com/software-mansion/react-native-audio-api/pull/432/files/) view, the [:scroll:action log](https://github.com/software-mansion/react-native-audio-api/actions/runs/14840305396/job/41661402032#step:4:1),  or [:memo: job summary](https://github.com/software-mansion/react-native-audio-api/actions/runs/14840305396/attempts/1#summary-41661402032) for details.

### Unrecognized words (4)

FLAC
Roadmap
WAV
Worklets

<details><summary>To accept these unrecognized words as correct,
you could run the following commands</summary>

... in a clone of the [git@github.com:software-mansion/react-native-audio-api.git](https://github.com/software-mansion/react-native-audio-api.git) repository
on the `feat/audio-param-connection-from-node` branch ([:information_source: how do I use this?](
https://docs.check-spelling.dev/Accepting-Suggestions)):

``` sh
curl -s -S -L 'https://raw.githubusercontent.com/check-spelling/check-spelling/main/apply.pl' |
perl - 'https://github.com/software-mansion/react-native-audio-api/actions/runs/14840305396/attempts/1'

```
</details>


**OR**

To have the bot accept them for you, comment in the PR quoting the following line:
@check-spelling-bot apply [updates](https://github.com/software-mansion/react-native-audio-api/actions/runs/14840305396/attempts/1).


<!-- See https://github.com/check-spelling/check-spelling/wiki/Configuration-Examples%3A-advice --> <!-- markdownlint-disable MD033 MD041 -->
<details><summary>If the flagged items are :exploding_head: false positives</summary>

If items relate to a ...
* binary file (or some other file you wouldn't want to check at all).

  Please add a file path to the `excludes.txt` file matching the containing file.

  File paths are Perl 5 Regular Expressions - you can [test](
https://www.regexplanet.com/advanced/perl/) yours before committing to verify it will match your files.

  `^` refers to the file's path from the root of the repository, so `^README\.md$` would exclude [README.md](
../tree/HEAD/README.md) (on whichever branch you're using).

* well-formed pattern.

  If you can write a [pattern](https://github.com/check-spelling/check-spelling/wiki/Configuration-Examples:-patterns) that would match it,
  try adding it to the `patterns.txt` file.

  Patterns are Perl 5 Regular Expressions - you can [test](
https://www.regexplanet.com/advanced/perl/) yours before committing to verify it will match your lines.

  Note that patterns can't match multiline strings.

</details>

<!-- adoption information-->
:steam_locomotive: If you're seeing this message and your PR is from a branch that doesn't have check-spelling,
please merge to your PR's base branch to get the version configured for your repository.

author_association: NONE
user: login: github-actions[bot]
created_at: 2025-05-06T09:41:19Z
body: 
# @check-spelling-bot Report
## :red_circle: Please review
### See the [:open_file_folder: files](https://github.com/software-mansion/react-native-audio-api/pull/432/files/) view, the [:scroll:action log](https://github.com/software-mansion/react-native-audio-api/actions/runs/14856485971/job/41710942918#step:4:1),  or [:memo: job summary](https://github.com/software-mansion/react-native-audio-api/actions/runs/14856485971/attempts/1#summary-41710942918) for details.

### Unrecognized words (4)

FLAC
Roadmap
WAV
Worklets

<details><summary>To accept these unrecognized words as correct,
you could run the following commands</summary>

... in a clone of the [git@github.com:software-mansion/react-native-audio-api.git](https://github.com/software-mansion/react-native-audio-api.git) repository
on the `feat/audio-param-connection-from-node` branch ([:information_source: how do I use this?](
https://docs.check-spelling.dev/Accepting-Suggestions)):

``` sh
curl -s -S -L 'https://raw.githubusercontent.com/check-spelling/check-spelling/main/apply.pl' |
perl - 'https://github.com/software-mansion/react-native-audio-api/actions/runs/14856485971/attempts/1'

```
</details>


**OR**

To have the bot accept them for you, comment in the PR quoting the following line:
@check-spelling-bot apply [updates](https://github.com/software-mansion/react-native-audio-api/actions/runs/14856485971/attempts/1).


<!-- See https://github.com/check-spelling/check-spelling/wiki/Configuration-Examples%3A-advice --> <!-- markdownlint-disable MD033 MD041 -->
<details><summary>If the flagged items are :exploding_head: false positives</summary>

If items relate to a ...
* binary file (or some other file you wouldn't want to check at all).

  Please add a file path to the `excludes.txt` file matching the containing file.

  File paths are Perl 5 Regular Expressions - you can [test](
https://www.regexplanet.com/advanced/perl/) yours before committing to verify it will match your files.

  `^` refers to the file's path from the root of the repository, so `^README\.md$` would exclude [README.md](
../tree/HEAD/README.md) (on whichever branch you're using).

* well-formed pattern.

  If you can write a [pattern](https://github.com/check-spelling/check-spelling/wiki/Configuration-Examples:-patterns) that would match it,
  try adding it to the `patterns.txt` file.

  Patterns are Perl 5 Regular Expressions - you can [test](
https://www.regexplanet.com/advanced/perl/) yours before committing to verify it will match your lines.

  Note that patterns can't match multiline strings.

</details>

<!-- adoption information-->
:steam_locomotive: If you're seeing this message and your PR is from a branch that doesn't have check-spelling,
please merge to your PR's base branch to get the version configured for your repository.

author_association: NONE
user: login: github-actions[bot]
created_at: 2025-05-06T10:56:37Z
body: 
# @check-spelling-bot Report
## :red_circle: Please review
### See the [:open_file_folder: files](https://github.com/software-mansion/react-native-audio-api/pull/432/files/) view, the [:scroll:action log](https://github.com/software-mansion/react-native-audio-api/actions/runs/14857883316/job/41715403786#step:4:1),  or [:memo: job summary](https://github.com/software-mansion/react-native-audio-api/actions/runs/14857883316/attempts/1#summary-41715403786) for details.

### Unrecognized words (4)

FLAC
Roadmap
WAV
Worklets

<details><summary>To accept these unrecognized words as correct,
you could run the following commands</summary>

... in a clone of the [git@github.com:software-mansion/react-native-audio-api.git](https://github.com/software-mansion/react-native-audio-api.git) repository
on the `feat/audio-param-connection-from-node` branch ([:information_source: how do I use this?](
https://docs.check-spelling.dev/Accepting-Suggestions)):

``` sh
curl -s -S -L 'https://raw.githubusercontent.com/check-spelling/check-spelling/main/apply.pl' |
perl - 'https://github.com/software-mansion/react-native-audio-api/actions/runs/14857883316/attempts/1'

```
</details>


**OR**

To have the bot accept them for you, comment in the PR quoting the following line:
@check-spelling-bot apply [updates](https://github.com/software-mansion/react-native-audio-api/actions/runs/14857883316/attempts/1).


<!-- See https://github.com/check-spelling/check-spelling/wiki/Configuration-Examples%3A-advice --> <!-- markdownlint-disable MD033 MD041 -->
<details><summary>If the flagged items are :exploding_head: false positives</summary>

If items relate to a ...
* binary file (or some other file you wouldn't want to check at all).

  Please add a file path to the `excludes.txt` file matching the containing file.

  File paths are Perl 5 Regular Expressions - you can [test](
https://www.regexplanet.com/advanced/perl/) yours before committing to verify it will match your files.

  `^` refers to the file's path from the root of the repository, so `^README\.md$` would exclude [README.md](
../tree/HEAD/README.md) (on whichever branch you're using).

* well-formed pattern.

  If you can write a [pattern](https://github.com/check-spelling/check-spelling/wiki/Configuration-Examples:-patterns) that would match it,
  try adding it to the `patterns.txt` file.

  Patterns are Perl 5 Regular Expressions - you can [test](
https://www.regexplanet.com/advanced/perl/) yours before committing to verify it will match your lines.

  Note that patterns can't match multiline strings.

</details>

<!-- adoption information-->
:steam_locomotive: If you're seeing this message and your PR is from a branch that doesn't have check-spelling,
please merge to your PR's base branch to get the version configured for your repository.

author_association: NONE
user: login: github-actions[bot]
created_at: 2025-05-06T13:42:57Z
body: 
# @check-spelling-bot Report
## :red_circle: Please review
### See the [:open_file_folder: files](https://github.com/software-mansion/react-native-audio-api/pull/432/files/) view, the [:scroll:action log](https://github.com/software-mansion/react-native-audio-api/actions/runs/14861217346/job/41726311771#step:4:1),  or [:memo: job summary](https://github.com/software-mansion/react-native-audio-api/actions/runs/14861217346/attempts/1#summary-41726311771) for details.

### Unrecognized words (4)

FLAC
Roadmap
WAV
Worklets

<details><summary>To accept these unrecognized words as correct,
you could run the following commands</summary>

... in a clone of the [git@github.com:software-mansion/react-native-audio-api.git](https://github.com/software-mansion/react-native-audio-api.git) repository
on the `feat/audio-param-connection-from-node` branch ([:information_source: how do I use this?](
https://docs.check-spelling.dev/Accepting-Suggestions)):

``` sh
curl -s -S -L 'https://raw.githubusercontent.com/check-spelling/check-spelling/main/apply.pl' |
perl - 'https://github.com/software-mansion/react-native-audio-api/actions/runs/14861217346/attempts/1'

```
</details>


**OR**

To have the bot accept them for you, comment in the PR quoting the following line:
@check-spelling-bot apply [updates](https://github.com/software-mansion/react-native-audio-api/actions/runs/14861217346/attempts/1).


<!-- See https://github.com/check-spelling/check-spelling/wiki/Configuration-Examples%3A-advice --> <!-- markdownlint-disable MD033 MD041 -->
<details><summary>If the flagged items are :exploding_head: false positives</summary>

If items relate to a ...
* binary file (or some other file you wouldn't want to check at all).

  Please add a file path to the `excludes.txt` file matching the containing file.

  File paths are Perl 5 Regular Expressions - you can [test](
https://www.regexplanet.com/advanced/perl/) yours before committing to verify it will match your files.

  `^` refers to the file's path from the root of the repository, so `^README\.md$` would exclude [README.md](
../tree/HEAD/README.md) (on whichever branch you're using).

* well-formed pattern.

  If you can write a [pattern](https://github.com/check-spelling/check-spelling/wiki/Configuration-Examples:-patterns) that would match it,
  try adding it to the `patterns.txt` file.

  Patterns are Perl 5 Regular Expressions - you can [test](
https://www.regexplanet.com/advanced/perl/) yours before committing to verify it will match your lines.

  Note that patterns can't match multiline strings.

</details>

<!-- adoption information-->
:steam_locomotive: If you're seeing this message and your PR is from a branch that doesn't have check-spelling,
please merge to your PR's base branch to get the version configured for your repository.

author_association: NONE
user: login: github-actions[bot]
created_at: 2025-05-06T15:46:28Z
body: 
# @check-spelling-bot Report
## :red_circle: Please review
### See the [:open_file_folder: files](https://github.com/software-mansion/react-native-audio-api/pull/432/files/) view, the [:scroll:action log](https://github.com/software-mansion/react-native-audio-api/actions/runs/14863963430/job/41735904184#step:4:1),  or [:memo: job summary](https://github.com/software-mansion/react-native-audio-api/actions/runs/14863963430/attempts/1#summary-41735904184) for details.

### Unrecognized words (4)

FLAC
Roadmap
WAV
Worklets

<details><summary>To accept these unrecognized words as correct,
you could run the following commands</summary>

... in a clone of the [git@github.com:software-mansion/react-native-audio-api.git](https://github.com/software-mansion/react-native-audio-api.git) repository
on the `feat/audio-param-connection-from-node` branch ([:information_source: how do I use this?](
https://docs.check-spelling.dev/Accepting-Suggestions)):

``` sh
curl -s -S -L 'https://raw.githubusercontent.com/check-spelling/check-spelling/main/apply.pl' |
perl - 'https://github.com/software-mansion/react-native-audio-api/actions/runs/14863963430/attempts/1'

```
</details>


**OR**

To have the bot accept them for you, comment in the PR quoting the following line:
@check-spelling-bot apply [updates](https://github.com/software-mansion/react-native-audio-api/actions/runs/14863963430/attempts/1).


<!-- See https://github.com/check-spelling/check-spelling/wiki/Configuration-Examples%3A-advice --> <!-- markdownlint-disable MD033 MD041 -->
<details><summary>If the flagged items are :exploding_head: false positives</summary>

If items relate to a ...
* binary file (or some other file you wouldn't want to check at all).

  Please add a file path to the `excludes.txt` file matching the containing file.

  File paths are Perl 5 Regular Expressions - you can [test](
https://www.regexplanet.com/advanced/perl/) yours before committing to verify it will match your files.

  `^` refers to the file's path from the root of the repository, so `^README\.md$` would exclude [README.md](
../tree/HEAD/README.md) (on whichever branch you're using).

* well-formed pattern.

  If you can write a [pattern](https://github.com/check-spelling/check-spelling/wiki/Configuration-Examples:-patterns) that would match it,
  try adding it to the `patterns.txt` file.

  Patterns are Perl 5 Regular Expressions - you can [test](
https://www.regexplanet.com/advanced/perl/) yours before committing to verify it will match your lines.

  Note that patterns can't match multiline strings.

</details>

<!-- adoption information-->
:steam_locomotive: If you're seeing this message and your PR is from a branch that doesn't have check-spelling,
please merge to your PR's base branch to get the version configured for your repository.

author_association: NONE