Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
d810a25
fix: missing error in exponentialRampToValue
closetcaiman Mar 10, 2026
eb0804f
fix: timeConstant edgecase
closetcaiman Mar 10, 2026
bac2dc1
fix: startValue and endValue edgecases
closetcaiman Mar 10, 2026
01666b8
fix: error message
closetcaiman Mar 10, 2026
afcc3d0
chore: merge
closetcaiman Mar 10, 2026
76df938
feat: add BoundedPriorityQueue
closetcaiman Mar 11, 2026
1a2c9c2
feat: make BoundedPriorityQueue stable
closetcaiman Mar 12, 2026
107f338
feat: integrate new queue
closetcaiman Mar 12, 2026
8a59035
feat: update queue to support curve exclusion
closetcaiman Mar 12, 2026
ee635d3
fix: curve exclusion time windows
closetcaiman Mar 13, 2026
6c5cfa8
fix: queue element handling
closetcaiman Mar 16, 2026
4c1c8df
feat: add JS priority queue
closetcaiman Mar 23, 2026
993e3c9
Merge branch 'main' into refactor/audio-param-automation-events
closetcaiman Mar 25, 2026
873adb9
feat: split event queue, split automation event
closetcaiman Mar 26, 2026
c6a30d9
chore: remove unused js
closetcaiman Mar 26, 2026
ba6d33b
feat: add toString to AutomationEventType
closetcaiman Mar 31, 2026
3841deb
refactor: checkCurveExclusion
closetcaiman Mar 31, 2026
238a08a
feat: add JSI handling
closetcaiman Mar 31, 2026
195544f
feat: add TS typing
closetcaiman Mar 31, 2026
b5ff172
feat: add TS-side validation
closetcaiman Mar 31, 2026
fb71200
chore: remove unused code
closetcaiman Mar 31, 2026
f8e4386
refactor: automation directory structure
closetcaiman Mar 31, 2026
9dc8532
chore: remove RingBuffer
closetcaiman Mar 31, 2026
e9a0a60
refactor: rename AudioParamHostObject queue
closetcaiman Apr 1, 2026
677bea8
feat: change queue base, refactor abstraction
closetcaiman Apr 1, 2026
5310df6
chore: resolve merge conflicts
closetcaiman Apr 1, 2026
088179e
fix: checkCurveExclusion signature
closetcaiman Apr 1, 2026
ae79c90
fix: clang-tidy warwnings
closetcaiman Apr 1, 2026
e445b3b
fix: remove unnecessary iterator wrapper
closetcaiman Apr 1, 2026
5f0dbd8
fix: clang-tidy warnings, naming, AQBase TEvent
closetcaiman Apr 2, 2026
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
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
#include <audioapi/HostObjects/AudioParamHostObject.h>

#include <audioapi/core/AudioParam.h>
#include <audioapi/core/utils/automation/AutomationEvent.hpp>
#include <audioapi/utils/AudioArray.hpp>

#include <memory>
#include <string>
#include <utility>
#include "audioapi/jsi/JsiHostObject.h"

namespace audioapi {

Expand All @@ -26,7 +29,8 @@ AudioParamHostObject::AudioParamHostObject(const std::shared_ptr<AudioParam> &pa
JSI_EXPORT_FUNCTION(AudioParamHostObject, setTargetAtTime),
JSI_EXPORT_FUNCTION(AudioParamHostObject, setValueCurveAtTime),
JSI_EXPORT_FUNCTION(AudioParamHostObject, cancelScheduledValues),
JSI_EXPORT_FUNCTION(AudioParamHostObject, cancelAndHoldAtTime));
JSI_EXPORT_FUNCTION(AudioParamHostObject, cancelAndHoldAtTime),
JSI_EXPORT_FUNCTION(AudioParamHostObject, checkCurveExclusion));

addSetters(JSI_EXPORT_PROPERTY_SETTER(AudioParamHostObject, value));
}
Expand Down Expand Up @@ -56,9 +60,11 @@ JSI_PROPERTY_SETTER_IMPL(AudioParamHostObject, value) {
}

JSI_HOST_FUNCTION_IMPL(AudioParamHostObject, setValueAtTime) {
auto event = [param = param_,
value = static_cast<float>(args[0].getNumber()),
startTime = args[1].getNumber()](BaseAudioContext &) {
auto startTime = args[1].getNumber();
controlQueue_.push(AutomationEvent(AutomationEventType::SET_VALUE, startTime));

auto event = [param = param_, value = static_cast<float>(args[0].getNumber()), startTime](
BaseAudioContext &) {
param->setValueAtTime(value, startTime);
};

Expand All @@ -67,9 +73,11 @@ JSI_HOST_FUNCTION_IMPL(AudioParamHostObject, setValueAtTime) {
}

JSI_HOST_FUNCTION_IMPL(AudioParamHostObject, linearRampToValueAtTime) {
auto event = [param = param_,
value = static_cast<float>(args[0].getNumber()),
endTime = args[1].getNumber()](BaseAudioContext &) {
auto endTime = args[1].getNumber();
controlQueue_.push(AutomationEvent(AutomationEventType::LINEAR_RAMP, endTime));

auto event = [param = param_, value = static_cast<float>(args[0].getNumber()), endTime](
BaseAudioContext &) {
param->linearRampToValueAtTime(value, endTime);
};

Expand All @@ -78,9 +86,11 @@ JSI_HOST_FUNCTION_IMPL(AudioParamHostObject, linearRampToValueAtTime) {
}

JSI_HOST_FUNCTION_IMPL(AudioParamHostObject, exponentialRampToValueAtTime) {
auto event = [param = param_,
value = static_cast<float>(args[0].getNumber()),
endTime = args[1].getNumber()](BaseAudioContext &) {
auto endTime = args[1].getNumber();
controlQueue_.push(AutomationEvent(AutomationEventType::EXPONENTIAL_RAMP, endTime));

auto event = [param = param_, value = static_cast<float>(args[0].getNumber()), endTime](
BaseAudioContext &) {
param->exponentialRampToValueAtTime(value, endTime);
};

Expand All @@ -89,9 +99,12 @@ JSI_HOST_FUNCTION_IMPL(AudioParamHostObject, exponentialRampToValueAtTime) {
}

JSI_HOST_FUNCTION_IMPL(AudioParamHostObject, setTargetAtTime) {
auto startTime = args[1].getNumber();
controlQueue_.push(AutomationEvent(AutomationEventType::SET_TARGET, startTime));

auto event = [param = param_,
target = static_cast<float>(args[0].getNumber()),
startTime = args[1].getNumber(),
startTime,
timeConstant = args[2].getNumber()](BaseAudioContext &) {
param->setTargetAtTime(target, startTime, timeConstant);
};
Expand All @@ -101,17 +114,18 @@ JSI_HOST_FUNCTION_IMPL(AudioParamHostObject, setTargetAtTime) {
}

JSI_HOST_FUNCTION_IMPL(AudioParamHostObject, setValueCurveAtTime) {
auto startTime = args[1].getNumber();
auto duration = args[2].getNumber();
controlQueue_.push(
AutomationEvent(AutomationEventType::SET_VALUE_CURVE, startTime, startTime + duration));

auto arrayBuffer =
args[0].getObject(runtime).getPropertyAsObject(runtime, "buffer").getArrayBuffer(runtime);
auto *rawValues = reinterpret_cast<float *>(arrayBuffer.data(runtime));
auto length = static_cast<int>(arrayBuffer.size(runtime) / sizeof(float));
auto values = std::make_shared<AudioArray>(rawValues, length);

auto event = [param = param_,
values,
length,
startTime = args[1].getNumber(),
duration = args[2].getNumber()](BaseAudioContext &) {
auto event = [param = param_, values, length, startTime, duration](BaseAudioContext &) {
param->setValueCurveAtTime(values, length, startTime, duration);
};

Expand All @@ -120,7 +134,10 @@ JSI_HOST_FUNCTION_IMPL(AudioParamHostObject, setValueCurveAtTime) {
}

JSI_HOST_FUNCTION_IMPL(AudioParamHostObject, cancelScheduledValues) {
auto event = [param = param_, cancelTime = args[0].getNumber()](BaseAudioContext &) {
auto cancelTime = args[0].getNumber();
controlQueue_.cancelScheduledValues(cancelTime);

auto event = [param = param_, cancelTime](BaseAudioContext &) {
param->cancelScheduledValues(cancelTime);
};

Expand All @@ -129,12 +146,51 @@ JSI_HOST_FUNCTION_IMPL(AudioParamHostObject, cancelScheduledValues) {
}

JSI_HOST_FUNCTION_IMPL(AudioParamHostObject, cancelAndHoldAtTime) {
auto event = [param = param_, cancelTime = args[0].getNumber()](BaseAudioContext &) {
auto cancelTime = args[0].getNumber();
controlQueue_.cancelScheduledValues(cancelTime);

auto event = [param = param_, cancelTime](BaseAudioContext &) {
param->cancelAndHoldAtTime(cancelTime);
};

param_->scheduleAudioEvent(std::move(event));
return jsi::Value::undefined();
}

JSI_HOST_FUNCTION_IMPL(AudioParamHostObject, checkCurveExclusion) {

auto checkExclusionResult = checkCurveExclusionFromJSI(runtime, args);

auto jsResult = jsi::Object(runtime);
jsResult.setProperty(
runtime,
"status",
jsi::String::createFromUtf8(runtime, checkExclusionResult.is_ok() ? "success" : "error"));
if (checkExclusionResult.is_err()) {
jsResult.setProperty(
runtime,
"message",
jsi::String::createFromUtf8(runtime, checkExclusionResult.unwrap_err()));
}
return jsResult;
}

Result<NoneType, std::string> AudioParamHostObject::checkCurveExclusionFromJSI(
jsi::Runtime &runtime,
const jsi::Value *args) {
auto arg = args[0].getObject(runtime);
auto type = static_cast<AutomationEventType>(arg.getProperty(runtime, "type").getNumber());
auto automationTime = arg.getProperty(runtime, "automationTime").getNumber();

AutomationEvent event;
if (type == AutomationEventType::SET_VALUE_CURVE) {
auto duration = arg.getProperty(runtime, "duration").getNumber();
event = AutomationEvent(type, automationTime, automationTime + duration);
} else {
event = AutomationEvent(type, automationTime);
}

return controlQueue_.checkCurveExclusion(event);
}

} // namespace audioapi
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@
#include <jsi/jsi.h>
#include <cstddef>
#include <memory>
#include <string>
#include "audioapi/core/utils/automation/AutomationControlQueue.h"
#include "audioapi/utils/Result.hpp"

namespace audioapi {
using namespace facebook;
Expand All @@ -30,12 +33,19 @@ class AudioParamHostObject : public JsiHostObject {
JSI_HOST_FUNCTION_DECL(cancelScheduledValues);
JSI_HOST_FUNCTION_DECL(cancelAndHoldAtTime);

JSI_HOST_FUNCTION_DECL(checkCurveExclusion);

private:
friend class AudioNodeHostObject;

std::shared_ptr<AudioParam> param_;
AutomationControlQueue controlQueue_;
float defaultValue_;
float minValue_;
float maxValue_;

Result<NoneType, std::string> checkCurveExclusionFromJSI(
jsi::Runtime &runtime,
const jsi::Value *args);
};
} // namespace audioapi
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
#include <audioapi/utils/AudioArray.hpp>
#include <memory>
#include <utility>
#include "audioapi/core/types/AutomationEventType.h"
#include "audioapi/core/utils/automation/RenderAutomationEvent.hpp"

namespace audioapi {

Expand Down Expand Up @@ -36,8 +38,8 @@ float AudioParam::getValueAtTime(double time) {
// Check if current automation segment has ended and we need to advance to
// next event
if (endTime_ < time && !eventsQueue_.isEmpty()) {
ParamChangeEvent event;
eventsQueue_.popFront(event);
RenderAutomationEvent event;
eventsQueue_.pop(event);
startTime_ = event.getStartTime();
endTime_ = event.getEndTime();
startValue_ = event.getStartValue();
Expand Down Expand Up @@ -67,13 +69,13 @@ void AudioParam::setValueAtTime(float value, double startTime) {
return endValue;
};

this->updateQueue(ParamChangeEvent(
this->updateQueue(RenderAutomationEvent(
startTime,
startTime,
this->getQueueEndValue(),
value,
std::move(calculateValue),
ParamChangeEventType::SET_VALUE));
AutomationEventType::SET_VALUE));
}

void AudioParam::linearRampToValueAtTime(float value, double endTime) {
Expand All @@ -97,13 +99,13 @@ void AudioParam::linearRampToValueAtTime(float value, double endTime) {
return endValue;
};

this->updateQueue(ParamChangeEvent(
this->updateQueue(RenderAutomationEvent(
this->getQueueEndTime(),
endTime,
this->getQueueEndValue(),
value,
std::move(calculateValue),
ParamChangeEventType::LINEAR_RAMP));
AutomationEventType::LINEAR_RAMP));
}

void AudioParam::exponentialRampToValueAtTime(float value, double endTime) {
Expand All @@ -130,13 +132,13 @@ void AudioParam::exponentialRampToValueAtTime(float value, double endTime) {
return endValue;
};

this->updateQueue(ParamChangeEvent(
this->updateQueue(RenderAutomationEvent(
this->getQueueEndTime(),
endTime,
this->getQueueEndValue(),
value,
std::move(calculateValue),
ParamChangeEventType::EXPONENTIAL_RAMP));
AutomationEventType::EXPONENTIAL_RAMP));
}

void AudioParam::setTargetAtTime(float target, double startTime, double timeConstant) {
Expand All @@ -157,14 +159,14 @@ void AudioParam::setTargetAtTime(float target, double startTime, double timeCons
return static_cast<float>(
target + (startValue - target) * exp(-(time - startTime) / timeConstant));
};
this->updateQueue(ParamChangeEvent(
this->updateQueue(RenderAutomationEvent(
startTime,
startTime, // SetTarget events have infinite duration conceptually
this->getQueueEndValue(),
this->getQueueEndValue(), // End value is not meaningful for
// infinite events
std::move(calculateValue),
ParamChangeEventType::SET_TARGET));
AutomationEventType::SET_TARGET));
}

void AudioParam::setValueCurveAtTime(
Expand Down Expand Up @@ -196,13 +198,13 @@ void AudioParam::setValueCurveAtTime(
return endValue;
};

this->updateQueue(ParamChangeEvent(
this->updateQueue(RenderAutomationEvent(
startTime,
startTime + duration,
this->getQueueEndValue(),
values->span()[length - 1],
std::move(calculateValue),
ParamChangeEventType::SET_VALUE_CURVE));
AutomationEventType::SET_VALUE_CURVE));
}

void AudioParam::cancelScheduledValues(double cancelTime) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@

#include <audioapi/core/AudioNode.h>
#include <audioapi/core/BaseAudioContext.h>
#include <audioapi/core/types/ParamChangeEventType.h>
#include <audioapi/core/utils/AudioParamEventQueue.h>
#include <audioapi/core/utils/ParamChangeEvent.hpp>
#include <audioapi/core/types/AutomationEventType.h>
#include <audioapi/core/utils/automation/AutomationRenderQueue.h>
#include <audioapi/core/utils/automation/RenderAutomationEvent.hpp>
#include <audioapi/utils/AudioBuffer.hpp>

#include <audioapi/utils/CrossThreadEventScheduler.hpp>
Expand Down Expand Up @@ -68,10 +68,10 @@ class AudioParam {
/// @note Audio Thread only
void cancelAndHoldAtTime(double cancelTime);

template <
typename F,
typename = std::enable_if_t<std::is_invocable_r_v<void, std::decay_t<F>, BaseAudioContext &>>>
bool scheduleAudioEvent(F &&event) noexcept {
template <typename F>
bool scheduleAudioEvent(F &&event) noexcept
requires(std::is_invocable_r_v<void, std::decay_t<F>, BaseAudioContext &>)
{
if (std::shared_ptr<BaseAudioContext> context = context_.lock()) {
return context->scheduleAudioEvent(std::forward<F>(event));
}
Expand Down Expand Up @@ -102,7 +102,7 @@ class AudioParam {
float minValue_;
float maxValue_;

AudioParamEventQueue eventsQueue_;
AutomationRenderQueue eventsQueue_;

// Current automation state (cached for performance)
double startTime_;
Expand Down Expand Up @@ -137,9 +137,10 @@ class AudioParam {
/// @brief Update the parameter queue with a new event.
/// @param event The new event to add to the queue.
/// @note Handles connecting start value of the new event to the end value of the previous event.
void updateQueue(ParamChangeEvent &&event) {
eventsQueue_.pushBack(std::move(event));
void updateQueue(RenderAutomationEvent &&event) {
eventsQueue_.push(std::move(event));
}

float getValueAtTime(double time);
void processInputs(
const std::shared_ptr<DSPAudioBuffer> &outputBuffer,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
#pragma once

#include <string_view>
namespace audioapi {

enum class AutomationEventType {
LINEAR_RAMP,
EXPONENTIAL_RAMP,
SET_VALUE,
SET_TARGET,
SET_VALUE_CURVE,
};

inline std::string_view toString(AutomationEventType type) {
switch (type) {
case AutomationEventType::LINEAR_RAMP:
return "LinearRampToValueAtTime";
case AutomationEventType::EXPONENTIAL_RAMP:
return "ExponentialRampToValueAtTime";
case AutomationEventType::SET_VALUE:
return "SetValueAtTime";
case AutomationEventType::SET_TARGET:
return "SetTargetAtTime";
case AutomationEventType::SET_VALUE_CURVE:
return "SetValueCurveAtTime";
}
return "Unknown";
}

} // namespace audioapi

This file was deleted.

Loading
Loading