blob: 5c2bbdc0ee0f40f67d219769078242fe0cba2fbf [file] [log] [blame]
// Copyright 2022 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "third_party/blink/renderer/modules/webaudio/audio_param_handler.h"
#include <algorithm>
#include <limits>
#include <memory>
#include "base/check_op.h"
#include "base/containers/span.h"
#include "base/memory/ptr_util.h"
#include "build/build_config.h"
#include "third_party/blink/renderer/core/execution_context/execution_context.h"
#include "third_party/blink/renderer/core/inspector/console_message.h"
#include "third_party/blink/renderer/modules/webaudio/audio_graph_tracer.h"
#include "third_party/blink/renderer/modules/webaudio/audio_node.h"
#include "third_party/blink/renderer/modules/webaudio/audio_node_output.h"
#include "third_party/blink/renderer/platform/audio/audio_utilities.h"
#include "third_party/blink/renderer/platform/audio/vector_math.h"
#include "third_party/blink/renderer/platform/bindings/exception_messages.h"
#include "third_party/blink/renderer/platform/bindings/exception_state.h"
#include "third_party/blink/renderer/platform/heap/garbage_collected.h"
#include "third_party/blink/renderer/platform/instrumentation/tracing/trace_event.h"
#include "third_party/blink/renderer/platform/wtf/math_extras.h"
#include "third_party/blink/renderer/platform/wtf/std_lib_extras.h"
#include "third_party/blink/renderer/platform/wtf/text/strcat.h"
#include "third_party/fdlibm/ieee754.h"
#if defined(ARCH_CPU_X86_FAMILY)
#include <emmintrin.h>
#include <xmmintrin.h>
#elif defined(CPU_ARM_NEON)
#include <arm_neon.h>
#endif
namespace blink {
namespace {
// For a SetTarget event, we want the event to terminate eventually so that we
// can stop using the timeline to compute the values. See
// `HasSetTargetConverged()` for the algorithm. `kSetTargetThreshold` is
// exp(-`kTimeConstantsToConverge`).
constexpr float kTimeConstantsToConverge = 10.0f;
constexpr float kSetTargetThreshold = 4.539992976248485e-05f;
// Replace NaN values in `values` with `default_value`.
void HandleNaNValues(base::span<float> values, float default_value) {
unsigned k = 0;
#if defined(ARCH_CPU_X86_FAMILY)
if (values.size() >= 4) {
__m128 defaults = _mm_set1_ps(default_value);
for (k = 0; k < values.size(); k += 4) {
// SAFETY: The for loop condition has been checked k < values.size().
__m128 v = _mm_loadu_ps(UNSAFE_BUFFERS(values.data() + k));
// cmpuord returns all 1's if v is NaN for each elmeent of v.
__m128 isnan = _mm_cmpunord_ps(v, v);
// Replace NaN parts with default.
__m128 result = _mm_and_ps(isnan, defaults);
// Merge in the parts that aren't NaN
result = _mm_or_ps(_mm_andnot_ps(isnan, v), result);
// SAFETY: The for loop condition has been checked k < values.size().
_mm_storeu_ps(UNSAFE_BUFFERS(values.data() + k), result);
}
}
#elif defined(CPU_ARM_NEON)
if (values.size() >= 4) {
uint32x4_t defaults =
reinterpret_cast<uint32x4_t>(vdupq_n_f32(default_value));
for (k = 0; k < values.size(); k += 4) {
// SAFETY: The for loop condition has been checked k < values.size().
float32x4_t v = vld1q_f32(UNSAFE_BUFFERS(values.data() + k));
// Returns true (all ones) if v is not NaN
uint32x4_t is_not_nan = vceqq_f32(v, v);
// Get the parts that are not NaN
uint32x4_t result =
vandq_u32(is_not_nan, reinterpret_cast<uint32x4_t>(v));
// Replace the parts that are NaN with the default and merge with previous
// result. (Note: vbic_u32(x, y) = x and not y)
result = vorrq_u32(result, vbicq_u32(defaults, is_not_nan));
// SAFETY: The for loop condition has been checked k < values.size().
vst1q_f32(UNSAFE_BUFFERS(values.data() + k),
reinterpret_cast<float32x4_t>(result));
}
}
#endif
std::ranges::replace_if(
values.subspan(k), [](float value) { return std::isnan(value); },
default_value);
}
bool IsNonNegativeAudioParamTime(double time,
ExceptionState& exception_state,
String message = "Time") {
if (time >= 0) {
return true;
}
exception_state.ThrowRangeError(StrCat(
{message,
" must be a finite non-negative number: ", String::Number(time)}));
return false;
}
bool IsPositiveAudioParamTime(double time,
ExceptionState& exception_state,
String message) {
if (time > 0) {
return true;
}
exception_state.ThrowRangeError(StrCat(
{message, " must be a finite positive number: ", String::Number(time)}));
return false;
}
// Test that for a SetTarget event, the current value is close enough
// to the target value that we can consider the event to have
// converged to the target.
bool HasSetTargetConverged(float value,
float target,
double current_time,
double start_time,
double time_constant) {
// Converged if enough time constants (`kTimeConstantsToConverge`) have passed
// since the start of the event.
if (current_time > start_time + kTimeConstantsToConverge * time_constant) {
return true;
}
// If `target` is 0, converged if |`value`| is less than
// `kSetTargetThreshold`.
if (target == 0 && fabs(value) < kSetTargetThreshold) {
return true;
}
// If `target` is not zero, converged if relative difference between `value`
// and `target` is small. That is |`target`-`value`|/|`value`| <
// `kSetTargetThreshold`.
if (target != 0 && fabs(target - value) < kSetTargetThreshold * fabs(value)) {
return true;
}
return false;
}
// Computes the value of a linear ramp event at time t with the given event
// parameters.
float LinearRampAtTime(double t,
float value1,
double time1,
float value2,
double time2) {
return value1 + (value2 - value1) * (t - time1) / (time2 - time1);
}
// Computes the value of an exponential ramp event at time t with the given
// event parameters.
float ExponentialRampAtTime(double t,
float value1,
double time1,
float value2,
double time2) {
DCHECK(!std::isnan(value1) && std::isfinite(value1));
DCHECK(!std::isnan(value2) && std::isfinite(value2));
return (value1 == 0.0f || std::signbit(value1) != std::signbit(value2))
? value1
: value1 *
fdlibm::pow(value2 / value1, (t - time1) / (time2 - time1));
}
// Compute the value of a set curve event at time t with the given event
// parameters.
float ValueCurveAtTime(double t,
double time1,
double duration,
base::span<const float> curve_data) {
double curve_index = (curve_data.size() - 1) / duration * (t - time1);
size_t k = std::min(static_cast<size_t>(curve_index), curve_data.size() - 1);
size_t k1 = std::min(k + 1, curve_data.size() - 1);
float c0 = curve_data[k];
float c1 = curve_data[k1];
float delta = std::min(curve_index - k, 1.0);
return c0 + (c1 - c0) * delta;
}
} // namespace
AudioParamHandler::AudioParamHandler(BaseAudioContext& context,
AudioParamType param_type,
double default_value,
AutomationRate rate,
AutomationRateMode rate_mode,
float min_value,
float max_value)
: AudioSummingJunction(context.GetDeferredTaskHandler()),
param_type_(param_type),
intrinsic_value_(default_value),
default_value_(default_value),
automation_rate_(rate),
rate_mode_(rate_mode),
min_value_(min_value),
max_value_(max_value),
summing_bus_(
AudioBus::Create(1,
GetDeferredTaskHandler().RenderQuantumFrames(),
false)) {
// An AudioParam needs the destination handler to run the timeline. But the
// destination may have been destroyed (e.g. page gone), so the destination is
// null. However, if the destination is gone, the AudioParam will never get
// pulled, so this is ok. We have checks for the destination handler existing
// when the AudioParam want to use it.
if (context.destination()) {
destination_handler_ = &context.destination()->GetAudioDestinationHandler();
}
}
AudioDestinationHandler& AudioParamHandler::DestinationHandler() const {
CHECK(destination_handler_);
return *destination_handler_;
}
void AudioParamHandler::SetCustomParamName(const String name) {
DCHECK(param_type_ == AudioParamType::kParamTypeAudioWorklet);
custom_param_name_ = name;
}
String AudioParamHandler::GetParamName() const {
switch (param_type_) {
case AudioParamType::kParamTypeAudioBufferSourcePlaybackRate:
return "AudioBufferSource.playbackRate";
case AudioParamType::kParamTypeAudioBufferSourceDetune:
return "AudioBufferSource.detune";
case AudioParamType::kParamTypeBiquadFilterFrequency:
return "BiquadFilter.frequency";
case AudioParamType::kParamTypeBiquadFilterQ:
return "BiquadFilter.Q";
case AudioParamType::kParamTypeBiquadFilterGain:
return "BiquadFilter.gain";
case AudioParamType::kParamTypeBiquadFilterDetune:
return "BiquadFilter.detune";
case AudioParamType::kParamTypeDelayDelayTime:
return "Delay.delayTime";
case AudioParamType::kParamTypeDynamicsCompressorThreshold:
return "DynamicsCompressor.threshold";
case AudioParamType::kParamTypeDynamicsCompressorKnee:
return "DynamicsCompressor.knee";
case AudioParamType::kParamTypeDynamicsCompressorRatio:
return "DynamicsCompressor.ratio";
case AudioParamType::kParamTypeDynamicsCompressorAttack:
return "DynamicsCompressor.attack";
case AudioParamType::kParamTypeDynamicsCompressorRelease:
return "DynamicsCompressor.release";
case AudioParamType::kParamTypeGainGain:
return "Gain.gain";
case AudioParamType::kParamTypeOscillatorFrequency:
return "Oscillator.frequency";
case AudioParamType::kParamTypeOscillatorDetune:
return "Oscillator.detune";
case AudioParamType::kParamTypeStereoPannerPan:
return "StereoPanner.pan";
case AudioParamType::kParamTypePannerPositionX:
return "Panner.positionX";
case AudioParamType::kParamTypePannerPositionY:
return "Panner.positionY";
case AudioParamType::kParamTypePannerPositionZ:
return "Panner.positionZ";
case AudioParamType::kParamTypePannerOrientationX:
return "Panner.orientationX";
case AudioParamType::kParamTypePannerOrientationY:
return "Panner.orientationY";
case AudioParamType::kParamTypePannerOrientationZ:
return "Panner.orientationZ";
case AudioParamType::kParamTypeAudioListenerPositionX:
return "AudioListener.positionX";
case AudioParamType::kParamTypeAudioListenerPositionY:
return "AudioListener.positionY";
case AudioParamType::kParamTypeAudioListenerPositionZ:
return "AudioListener.positionZ";
case AudioParamType::kParamTypeAudioListenerForwardX:
return "AudioListener.forwardX";
case AudioParamType::kParamTypeAudioListenerForwardY:
return "AudioListener.forwardY";
case AudioParamType::kParamTypeAudioListenerForwardZ:
return "AudioListener.forwardZ";
case AudioParamType::kParamTypeAudioListenerUpX:
return "AudioListener.upX";
case AudioParamType::kParamTypeAudioListenerUpY:
return "AudioListener.upY";
case AudioParamType::kParamTypeAudioListenerUpZ:
return "AudioListener.upZ";
case AudioParamType::kParamTypeConstantSourceOffset:
return "ConstantSource.offset";
case AudioParamType::kParamTypeAudioWorklet:
return custom_param_name_;
default:
NOTREACHED();
}
}
float AudioParamHandler::Value() {
// Update value for timeline.
float v = IntrinsicValue();
if (GetDeferredTaskHandler().IsAudioThread()) {
auto [has_value, timeline_value] =
ValueForContextTime(DestinationHandler(), v, MinValue(), MaxValue(),
GetDeferredTaskHandler().RenderQuantumFrames());
if (has_value) {
v = timeline_value;
}
}
SetValue(v);
return v;
}
void AudioParamHandler::SetValue(float value) {
value = ClampTo(value, min_value_, max_value_);
intrinsic_value_.store(value, std::memory_order_relaxed);
}
float AudioParamHandler::FinalValue() {
float value = IntrinsicValue();
CalculateFinalValues(base::span_from_ref(value), false);
return value;
}
void AudioParamHandler::CalculateSampleAccurateValues(
base::span<float> values) {
DCHECK(GetDeferredTaskHandler().IsAudioThread());
DCHECK(!values.empty());
CalculateFinalValues(values, IsAudioRate());
}
void AudioParamHandler::CalculateFinalValues(base::span<float> values,
bool sample_accurate) {
DCHECK(GetDeferredTaskHandler().IsAudioThread());
DCHECK(!values.empty());
// The calculated result will be the "intrinsic" value summed with all
// audio-rate connections.
if (sample_accurate) {
// Calculate sample-accurate (a-rate) intrinsic values.
CalculateTimelineValues(values);
} else {
// Calculate control-rate (k-rate) intrinsic value.
float value = IntrinsicValue();
auto [has_value, timeline_value] =
ValueForContextTime(DestinationHandler(), value, MinValue(), MaxValue(),
GetDeferredTaskHandler().RenderQuantumFrames());
if (has_value) {
value = timeline_value;
}
std::ranges::fill(values, value);
SetValue(value);
}
// If there are any connections, sum all of the audio-rate connections
// together (unity-gain summing junction). Note that connections would
// normally be mono, but we mix down to mono if necessary.
if (NumberOfRenderingConnections() > 0) {
DCHECK_LE(values.size(), GetDeferredTaskHandler().RenderQuantumFrames());
// If we're not sample accurate, we only need one value, so make the summing
// bus have length 1. When the connections are added in, only the first
// value will be added. Which is exactly what we want.
summing_bus_->SetChannelMemory(0, values.data(),
sample_accurate ? values.size() : 1);
for (unsigned i = 0; i < NumberOfRenderingConnections(); ++i) {
AudioNodeOutput* output = RenderingOutput(i);
DCHECK(output);
// Render audio from this output.
AudioBus* connection_bus =
output->Pull(nullptr, GetDeferredTaskHandler().RenderQuantumFrames());
// Sum, with unity-gain.
summing_bus_->SumFrom(*connection_bus);
}
// If we're not sample accurate, duplicate the first element of `values` to
// all of the elements.
if (!sample_accurate) {
std::ranges::fill(values, values[0]);
}
float min_value = MinValue();
float max_value = MaxValue();
if (NumberOfRenderingConnections() > 0) {
// AudioParams by themselves don't produce NaN because of the finite min
// and max values. But an input to an AudioParam could have NaNs.
//
// NaN values in AudioParams must be replaced by the AudioParam's
// defaultValue. Then these values must be clamped to lie in the nominal
// range between the AudioParam's minValue and maxValue.
//
// See https://webaudio.github.io/web-audio-api/#computation-of-value.
HandleNaNValues(values, DefaultValue());
}
vector_math::Vclip(values, 1, &min_value, &max_value, values, 1);
}
}
void AudioParamHandler::CalculateTimelineValues(base::span<float> values) {
// Calculate values for this render quantum. Normally
// `number_of_values` will equal to
// GetDeferredTaskHandler().RenderQuantumFrames() (the render quantum size).
double sample_rate = DestinationHandler().SampleRate();
size_t start_frame = DestinationHandler().CurrentSampleFrame();
size_t end_frame = start_frame + values.size();
// Note we're running control rate at the sample-rate.
// Pass in the current value as default value.
SetValue(ValuesForFrameRange(start_frame, end_frame, IntrinsicValue(), values,
sample_rate, sample_rate, MinValue(), MaxValue(),
GetDeferredTaskHandler().RenderQuantumFrames()));
}
double AudioParamHandler::ClampedToCurrentTime(double time) {
return std::max(time, DestinationHandler().CurrentTime());
}
String AudioParamHandler::EventToString(const ParamEvent& event) const {
// The default arguments for most automation methods is the value and the
// time.
String args = StrCat(
{String::Number(event.Value()), ", ", String::Number(event.Time(), 16)});
// Get a nice printable name for the event and update the args if necessary.
String s;
switch (event.GetType()) {
case ParamEvent::Type::kSetValue:
s = "setValueAtTime";
break;
case ParamEvent::Type::kLinearRampToValue:
s = "linearRampToValueAtTime";
break;
case ParamEvent::Type::kExponentialRampToValue:
s = "exponentialRampToValue";
break;
case ParamEvent::Type::kSetTarget:
s = "setTargetAtTime";
// This has an extra time constant arg
args = StrCat({args, ", ", String::Number(event.TimeConstant(), 16)});
break;
case ParamEvent::Type::kSetValueCurve:
s = "setValueCurveAtTime";
// Replace the default arg, using "..." to denote the curve argument.
args = StrCat({"..., ", String::Number(event.Time(), 16), ", ",
String::Number(event.Duration(), 16)});
break;
case ParamEvent::Type::kCancelValues:
case ParamEvent::Type::kSetValueCurveEnd:
// Fall through; we should never have to print out the internal
// `kCancelValues` or `kSetValueCurveEnd` event.
case ParamEvent::Type::kLastType:
NOTREACHED();
};
return StrCat({s, "(", args, ")"});
}
std::unique_ptr<AudioParamHandler::ParamEvent>
AudioParamHandler::ParamEvent::CreateSetValueEvent(float value, double time) {
return base::WrapUnique(
new ParamEvent(ParamEvent::Type::kSetValue, value, time));
}
std::unique_ptr<AudioParamHandler::ParamEvent>
AudioParamHandler::ParamEvent::CreateLinearRampEvent(float value,
double time,
float initial_value,
double call_time) {
return base::WrapUnique(new ParamEvent(ParamEvent::Type::kLinearRampToValue,
value, time, initial_value,
call_time));
}
std::unique_ptr<AudioParamHandler::ParamEvent>
AudioParamHandler::ParamEvent::CreateExponentialRampEvent(float value,
double time,
float initial_value,
double call_time) {
return base::WrapUnique(
new ParamEvent(ParamEvent::Type::kExponentialRampToValue, value, time,
initial_value, call_time));
}
std::unique_ptr<AudioParamHandler::ParamEvent>
AudioParamHandler::ParamEvent::CreateSetTargetEvent(float value,
double time,
double time_constant) {
// The time line code does not expect a timeConstant of 0. (IT
// returns NaN or Infinity due to division by zero. The caller
// should have converted this to a SetValueEvent.
DCHECK_NE(time_constant, 0);
return base::WrapUnique(
new ParamEvent(ParamEvent::Type::kSetTarget, value, time, time_constant));
}
std::unique_ptr<AudioParamHandler::ParamEvent>
AudioParamHandler::ParamEvent::CreateSetValueCurveEvent(
const Vector<float>& curve,
double time,
double duration) {
double curve_points = (curve.size() - 1) / duration;
float end_value = curve.back();
return base::WrapUnique(new ParamEvent(ParamEvent::Type::kSetValueCurve, time,
duration, curve, curve_points,
end_value));
}
std::unique_ptr<AudioParamHandler::ParamEvent>
AudioParamHandler::ParamEvent::CreateSetValueCurveEndEvent(float value,
double time) {
return base::WrapUnique(
new ParamEvent(ParamEvent::Type::kSetValueCurveEnd, value, time));
}
std::unique_ptr<AudioParamHandler::ParamEvent>
AudioParamHandler::ParamEvent::CreateCancelValuesEvent(
double time,
std::unique_ptr<ParamEvent> saved_event) {
if (saved_event) {
// The savedEvent can only have certain event types. Verify that.
ParamEvent::Type saved_type = saved_event->GetType();
DCHECK_NE(saved_type, ParamEvent::Type::kLastType);
DCHECK(saved_type == ParamEvent::Type::kLinearRampToValue ||
saved_type == ParamEvent::Type::kExponentialRampToValue ||
saved_type == ParamEvent::Type::kSetValueCurve);
}
return base::WrapUnique(new ParamEvent(ParamEvent::Type::kCancelValues, time,
std::move(saved_event)));
}
std::unique_ptr<AudioParamHandler::ParamEvent>
AudioParamHandler::ParamEvent::CreateGeneralEvent(
Type type,
float value,
double time,
float initial_value,
double call_time,
double time_constant,
double duration,
Vector<float>& curve,
double curve_points_per_second,
float curve_end_value,
std::unique_ptr<ParamEvent> saved_event) {
return base::WrapUnique(new ParamEvent(
type, value, time, initial_value, call_time, time_constant, duration,
curve, curve_points_per_second, curve_end_value, std::move(saved_event)));
}
AudioParamHandler::ParamEvent* AudioParamHandler::ParamEvent::SavedEvent()
const {
DCHECK_EQ(GetType(), ParamEvent::Type::kCancelValues);
return saved_event_.get();
}
bool AudioParamHandler::ParamEvent::HasDefaultCancelledValue() const {
DCHECK_EQ(GetType(), ParamEvent::Type::kCancelValues);
return has_default_cancelled_value_;
}
void AudioParamHandler::ParamEvent::SetCancelledValue(float value) {
DCHECK_EQ(GetType(), ParamEvent::Type::kCancelValues);
value_ = value;
has_default_cancelled_value_ = true;
}
// General event
AudioParamHandler::ParamEvent::ParamEvent(
ParamEvent::Type type,
float value,
double time,
float initial_value,
double call_time,
double time_constant,
double duration,
const Vector<float>& curve,
double curve_points_per_second,
float curve_end_value,
std::unique_ptr<ParamEvent> saved_event)
: type_(type),
value_(value),
time_(time),
initial_value_(initial_value),
call_time_(call_time),
time_constant_(time_constant),
duration_(duration),
curve_points_per_second_(curve_points_per_second),
curve_end_value_(curve_end_value),
saved_event_(std::move(saved_event)),
has_default_cancelled_value_(false) {
curve_ = curve;
}
// Create simplest event needing just a value and time, like setValueAtTime
AudioParamHandler::ParamEvent::ParamEvent(ParamEvent::Type type,
float value,
double time)
: type_(type),
value_(value),
time_(time),
initial_value_(0),
call_time_(0),
time_constant_(0),
duration_(0),
curve_points_per_second_(0),
curve_end_value_(0),
saved_event_(nullptr),
has_default_cancelled_value_(false) {
DCHECK(type == ParamEvent::Type::kSetValue ||
type == ParamEvent::Type::kSetValueCurveEnd);
}
// Create a linear or exponential ramp that requires an initial value and
// time in case
// there is no actual event that precedes this event.
AudioParamHandler::ParamEvent::ParamEvent(ParamEvent::Type type,
float value,
double time,
float initial_value,
double call_time)
: type_(type),
value_(value),
time_(time),
initial_value_(initial_value),
call_time_(call_time),
time_constant_(0),
duration_(0),
curve_points_per_second_(0),
curve_end_value_(0),
saved_event_(nullptr),
has_default_cancelled_value_(false) {
DCHECK(type == ParamEvent::Type::kLinearRampToValue ||
type == ParamEvent::Type::kExponentialRampToValue);
}
// Create an event needing a time constant (setTargetAtTime)
AudioParamHandler::ParamEvent::ParamEvent(ParamEvent::Type type,
float value,
double time,
double time_constant)
: type_(type),
value_(value),
time_(time),
initial_value_(0),
call_time_(0),
time_constant_(time_constant),
duration_(0),
curve_points_per_second_(0),
curve_end_value_(0),
saved_event_(nullptr),
has_default_cancelled_value_(false) {
DCHECK_EQ(type, ParamEvent::Type::kSetTarget);
}
// Create a setValueCurve event
AudioParamHandler::ParamEvent::ParamEvent(ParamEvent::Type type,
double time,
double duration,
const Vector<float>& curve,
double curve_points_per_second,
float curve_end_value)
: type_(type),
value_(0),
time_(time),
initial_value_(0),
call_time_(0),
time_constant_(0),
duration_(duration),
curve_points_per_second_(curve_points_per_second),
curve_end_value_(curve_end_value),
saved_event_(nullptr),
has_default_cancelled_value_(false) {
DCHECK_EQ(type, ParamEvent::Type::kSetValueCurve);
unsigned curve_length = curve.size();
curve_.resize(curve_length);
base::span(curve_).copy_from(curve);
}
// Create CancelValues event
AudioParamHandler::ParamEvent::ParamEvent(
ParamEvent::Type type,
double time,
std::unique_ptr<ParamEvent> saved_event)
: type_(type),
value_(0),
time_(time),
initial_value_(0),
call_time_(0),
time_constant_(0),
duration_(0),
curve_points_per_second_(0),
curve_end_value_(0),
saved_event_(std::move(saved_event)),
has_default_cancelled_value_(false) {
DCHECK_EQ(type, ParamEvent::Type::kCancelValues);
}
void AudioParamHandler::SetValueAtTime(float value,
double start_time,
ExceptionState& exception_state) {
DCHECK(IsMainThread());
if (!IsNonNegativeAudioParamTime(start_time, exception_state)) {
return;
}
start_time = ClampedToCurrentTime(start_time);
base::AutoLock locker(events_lock_);
InsertEvent(ParamEvent::CreateSetValueEvent(value, start_time),
exception_state);
}
void AudioParamHandler::LinearRampToValueAtTime(
float value,
double end_time,
float initial_value,
double call_time,
ExceptionState& exception_state) {
DCHECK(IsMainThread());
if (!IsNonNegativeAudioParamTime(end_time, exception_state)) {
return;
}
end_time = ClampedToCurrentTime(end_time);
base::AutoLock locker(events_lock_);
InsertEvent(ParamEvent::CreateLinearRampEvent(value, end_time, initial_value,
call_time),
exception_state);
}
void AudioParamHandler::ExponentialRampToValueAtTime(
float value,
double end_time,
float initial_value,
double call_time,
ExceptionState& exception_state) {
DCHECK(IsMainThread());
if (!IsNonNegativeAudioParamTime(end_time, exception_state)) {
return;
}
if (!value) {
exception_state.ThrowRangeError(StrCat(
{"The float target value provided (", String::Number(value),
") should not be in the range (",
String::Number(-std::numeric_limits<float>::denorm_min()), ", ",
String::Number(std::numeric_limits<float>::denorm_min()), ")."}));
return;
}
end_time = ClampedToCurrentTime(end_time);
base::AutoLock locker(events_lock_);
InsertEvent(ParamEvent::CreateExponentialRampEvent(value, end_time,
initial_value, call_time),
exception_state);
}
void AudioParamHandler::SetTargetAtTime(float target,
double start_time,
double time_constant,
ExceptionState& exception_state) {
DCHECK(IsMainThread());
if (!IsNonNegativeAudioParamTime(start_time, exception_state) ||
!IsNonNegativeAudioParamTime(time_constant, exception_state,
"Time constant")) {
return;
}
start_time = ClampedToCurrentTime(start_time);
base::AutoLock locker(events_lock_);
// If timeConstant = 0, we instantly jump to the target value, so
// insert a SetValueEvent instead of SetTargetEvent.
if (time_constant == 0) {
InsertEvent(ParamEvent::CreateSetValueEvent(target, start_time),
exception_state);
} else {
InsertEvent(
ParamEvent::CreateSetTargetEvent(target, start_time, time_constant),
exception_state);
}
}
void AudioParamHandler::SetValueCurveAtTime(const Vector<float>& curve,
double start_time,
double duration,
ExceptionState& exception_state) {
DCHECK(IsMainThread());
if (!IsNonNegativeAudioParamTime(start_time, exception_state) ||
!IsPositiveAudioParamTime(duration, exception_state, "Duration")) {
return;
}
if (curve.size() < 2) {
exception_state.ThrowDOMException(
DOMExceptionCode::kInvalidStateError,
ExceptionMessages::IndexExceedsMinimumBound("curve length",
curve.size(), 2u));
return;
}
start_time = ClampedToCurrentTime(start_time);
base::AutoLock locker(events_lock_);
bool result = InsertEvent(
ParamEvent::CreateSetValueCurveEvent(curve, start_time, duration),
exception_state);
// `InsertEvent` will have already thrown an exception for us if `result` is
// false.
if (!result) {
return;
}
// Insert a setValueAtTime event too to establish an event so that all
// following events will process from the end of the curve instead of the
// beginning.
InsertEvent(ParamEvent::CreateSetValueCurveEndEvent(curve.back(),
start_time + duration),
exception_state);
}
bool AudioParamHandler::InsertEvent(std::unique_ptr<ParamEvent> event,
ExceptionState& exception_state) {
TRACE_EVENT0(TRACE_DISABLED_BY_DEFAULT("webaudio.audionode"),
"AudioParamHandler::InsertEvent");
DCHECK(IsMainThread());
// Sanity check the event. Be super careful we're not getting infected with
// NaN or Inf. These should have been handled by the caller.
DCHECK_LT(event->GetType(), ParamEvent::Type::kLastType);
DCHECK(std::isfinite(event->Value()));
DCHECK(std::isfinite(event->Time()));
DCHECK(std::isfinite(event->TimeConstant()));
DCHECK(std::isfinite(event->Duration()));
DCHECK_GE(event->Duration(), 0);
double insert_time = event->Time();
if (events_.empty() &&
(event->GetType() == ParamEvent::Type::kLinearRampToValue ||
event->GetType() == ParamEvent::Type::kExponentialRampToValue)) {
// There are no events preceding these ramps. Insert a new
// setValueAtTime event to set the starting point for these
// events.
events_.insert(
0, AudioParamHandler::ParamEvent::CreateSetValueEvent(
event->InitialValue(), DestinationHandler().CurrentTime()));
new_events_.insert(events_[0].get());
}
if (events_.empty()) {
events_.insert(0, std::move(event));
new_events_.insert(events_[0].get());
return true;
}
// Most of the time, we must insert after the last event. If the time of the
// last event is greater than the insert_time, use binary search to find the
// insertion point.
wtf_size_t insertion_idx = events_.size();
DCHECK_GT(insertion_idx, wtf_size_t{0});
wtf_size_t ub = insertion_idx - 1; // upper bound of events that can overlap.
if (events_.back()->Time() > insert_time) {
auto it = std::upper_bound(
events_.begin(), events_.end(), insert_time,
[](const double value, const std::unique_ptr<ParamEvent>& entry) {
return value < entry->Time();
});
insertion_idx = static_cast<wtf_size_t>(std::distance(events_.begin(), it));
DCHECK_LT(insertion_idx, events_.size());
ub = insertion_idx;
}
DCHECK_LT(ub, static_cast<wtf_size_t>(std::numeric_limits<int>::max()));
if (event->GetType() == ParamEvent::Type::kSetValueCurve) {
double end_time = event->Time() + event->Duration();
for (int i = ub; i >= 0; i--) {
ParamEvent::Type test_type = events_[i]->GetType();
// Events of type `kSetValueCurveEnd` or `kCancelValues` never conflict.
if (test_type == ParamEvent::Type::kSetValueCurveEnd ||
test_type == ParamEvent::Type::kCancelValues) {
continue;
}
if (test_type == ParamEvent::Type::kSetValueCurve) {
// A SetValueCurve overlapping an existing SetValueCurve requires
// special care.
double test_end_time = events_[i]->Time() + events_[i]->Duration();
// Events are overlapped if the new event starts before the old event
// ends and the old event starts before the new event ends.
bool overlap =
event->Time() < test_end_time && events_[i]->Time() < end_time;
if (overlap) {
// If the start time of the event overlaps the start/end of an
// existing event or if the existing event end overlaps the
// start/end of the event, it's an error.
exception_state.ThrowDOMException(
DOMExceptionCode::kNotSupportedError,
StrCat({EventToString(*event), " overlaps ",
EventToString(*events_[i])}));
return false;
}
} else {
// Here we handle existing events of types other than
// `kSetValueCurveEnd`, `kCancelValues` and `kSetValueCurve`.
// Throw an error if an existing event starts in the middle of this
// SetValueCurve event.
if (events_[i]->Time() > event->Time() &&
events_[i]->Time() < end_time) {
exception_state.ThrowDOMException(
DOMExceptionCode::kNotSupportedError,
StrCat({EventToString(*event), " overlaps ",
EventToString(*events_[i])}));
return false;
}
}
if (events_[i]->Time() < insert_time) {
// We found an existing event, E, of type other than `kSetValueCurveEnd`
// or `kCancelValues` that starts before the new event of type
// `kSetValueCurve` that we want to insert. No earlier existing event
// can overlap with the new event. An overlapping `kSetValueCurve` would
// have overlapped with E too, so one of them would not be inserted.
// Other event types overlap with the new `kSetValueCurve` event only if
// they start in the middle of the new event, which is not the case.
break;
}
}
} else {
// Not a `SetValueCurve` new event. Make sure this new event doesn't overlap
// any existing `SetValueCurve` event.
for (int i = ub; i >= 0; i--) {
ParamEvent::Type test_type = events_[i]->GetType();
// Events of type `kSetValueCurveEnd` or `kCancelValues` never conflict.
if (test_type == ParamEvent::Type::kSetValueCurveEnd ||
test_type == ParamEvent::Type::kCancelValues) {
continue;
}
if (test_type == ParamEvent::Type::kSetValueCurve) {
double end_time = events_[i]->Time() + events_[i]->Duration();
if (event->GetType() != ParamEvent::Type::kSetValueCurveEnd &&
event->Time() >= events_[i]->Time() && event->Time() < end_time) {
exception_state.ThrowDOMException(
DOMExceptionCode::kNotSupportedError,
StrCat({EventToString(*event), " overlaps ",
EventToString(*events_[i])}));
return false;
}
}
if (events_[i]->Time() < insert_time) {
// We found an existing event, E, of type other than `kSetValueCurveEnd`
// or `kCancelValues` that starts before the new event that we want to
// insert. No earlier event of type `kSetValueCurve` can overlap with
// the new event, because it would have overlapped with E too.
break;
}
}
}
events_.insert(insertion_idx, std::move(event));
new_events_.insert(events_[insertion_idx].get());
return true;
}
bool AudioParamHandler::HasSampleAccurateValues() const {
if (NumberOfRenderingConnections()) {
return true;
}
base::AutoTryLock try_locker(events_lock_);
if (try_locker.is_acquired()) {
// Return true if the AudioParam timeline needs to run in this rendering
// quantum. This means some automation is already running or is scheduled
// to run in the current rendering quantum.
const unsigned n_events = events_.size();
// Clearly, if there are no scheduled events, we have no timeline values.
if (n_events == 0) {
return false;
}
const size_t current_frame = destination_handler_->CurrentSampleFrame();
const double sample_rate = destination_handler_->SampleRate();
const unsigned render_quantum_frames =
GetDeferredTaskHandler().RenderQuantumFrames();
// Handle the case where the first event (of certain types) is in the
// future. Then, no sample-accurate processing is needed because the event
// hasn't started.
if (events_[0]->Time() >
(current_frame + render_quantum_frames) / sample_rate) {
switch (events_[0]->GetType()) {
case ParamEvent::Type::kSetTarget:
case ParamEvent::Type::kSetValue:
case ParamEvent::Type::kSetValueCurve:
// If the first event is one of these types, and the event starts
// after the end of the current render quantum, we don't need to do
// the slow sample-accurate path.
return false;
default:
// Handle other event types below.
break;
}
}
// If there are at least 2 events in the timeline, assume there are timeline
// values. This could be optimized to be more careful, but checking is
// complicated and keeping this consistent with `ValuesForFrameRangeImpl()`
// will be hard, so it's probably best to let the general timeline handle
// this until the events are in the past.
if (n_events >= 2) {
return true;
}
// We have exactly one event in the timeline.
switch (events_[0]->GetType()) {
case ParamEvent::Type::kSetTarget:
// Need automation if the event starts somewhere before the
// end of the current render quantum.
return events_[0]->Time() <=
(current_frame + render_quantum_frames) / sample_rate;
case ParamEvent::Type::kSetValue:
case ParamEvent::Type::kLinearRampToValue:
case ParamEvent::Type::kExponentialRampToValue:
case ParamEvent::Type::kCancelValues:
case ParamEvent::Type::kSetValueCurveEnd:
// If these events are in the past, we don't need any automation; the
// value is a constant.
return !(events_[0]->Time() < current_frame / sample_rate);
case ParamEvent::Type::kSetValueCurve: {
double curve_end_time = events_[0]->Time() + events_[0]->Duration();
double current_time = current_frame / sample_rate;
return (events_[0]->Time() <= current_time) &&
(current_time < curve_end_time);
}
case ParamEvent::Type::kLastType:
NOTREACHED();
}
}
// Can't get the lock so that means the main thread is trying to insert an
// event. Just return true then. If the main thread releases the lock before
// valueForContextTime or valuesForFrameRange runs, then the there will be an
// event on the timeline, so everything is fine. If the lock is held so that
// neither valueForContextTime nor valuesForFrameRange can run, this is ok
// too, because they have tryLocks to produce a default value. The event will
// then get processed in the next rendering quantum.
//
// Don't want to return false here because that would confuse the processing
// of the timeline if previously we returned true and now suddenly return
// false, only to return true on the next rendering quantum. Currently, once
// a timeline has been introduced it is always true forever because m_events
// never shrinks.
return true;
}
void AudioParamHandler::CancelScheduledValues(double cancel_time,
ExceptionState& exception_state) {
DCHECK(IsMainThread());
if (!IsNonNegativeAudioParamTime(cancel_time, exception_state)) {
return;
}
cancel_time = ClampedToCurrentTime(cancel_time);
base::AutoLock locker(events_lock_);
// Remove all events starting at startTime.
for (wtf_size_t i = 0; i < events_.size(); ++i) {
// Removal all events whose event time (start) is greater than or
// equal to the cancel time. And also handle the special case
// where the cancel time lies in the middle of a setValueCurve
// event.
//
// This critically depends on the fact that no event can be
// scheduled in the middle of the curve or at the same start time.
// Then removing the setValueCurve doesn't remove any events that
// shouldn't have been.
double start_time = events_[i]->Time();
if (start_time >= cancel_time ||
((events_[i]->GetType() == ParamEvent::Type::kSetValueCurve) &&
start_time <= cancel_time &&
(start_time + events_[i]->Duration() > cancel_time))) {
RemoveCancelledEvents(i);
break;
}
}
}
void AudioParamHandler::CancelAndHoldAtTime(double cancel_time,
ExceptionState& exception_state) {
DCHECK(IsMainThread());
if (!IsNonNegativeAudioParamTime(cancel_time, exception_state)) {
return;
}
cancel_time = ClampedToCurrentTime(cancel_time);
base::AutoLock locker(events_lock_);
wtf_size_t i;
// Find the first event at or just past `cancel_time`.
for (i = 0; i < events_.size(); ++i) {
if (events_[i]->Time() > cancel_time) {
break;
}
}
// The event that is being cancelled. This is the event just past
// `cancel_time`, if any.
wtf_size_t cancelled_event_index = i;
// If the event just before `cancel_time` is a SetTarget or SetValueCurve
// event, we need to handle that event specially instead of the event after.
if (i > 0 &&
((events_[i - 1]->GetType() == ParamEvent::Type::kSetTarget) ||
(events_[i - 1]->GetType() == ParamEvent::Type::kSetValueCurve))) {
cancelled_event_index = i - 1;
} else if (i >= events_.size()) {
// If there were no events occurring after `cancel_time` (and the
// previous event is not SetTarget or SetValueCurve, we're done.
return;
}
// cancelledEvent is the event that is being cancelled.
ParamEvent* cancelled_event = events_[cancelled_event_index].get();
ParamEvent::Type event_type = cancelled_event->GetType();
// New event to be inserted, if any, and a SetValueEvent if needed.
std::unique_ptr<ParamEvent> new_event;
std::unique_ptr<ParamEvent> new_set_value_event;
switch (event_type) {
case ParamEvent::Type::kLinearRampToValue:
case ParamEvent::Type::kExponentialRampToValue: {
// For these events we need to remember the parameters of the event
// for a CancelValues event so that we can properly cancel the event
// and hold the value.
std::unique_ptr<ParamEvent> saved_event = ParamEvent::CreateGeneralEvent(
event_type, cancelled_event->Value(), cancelled_event->Time(),
cancelled_event->InitialValue(), cancelled_event->CallTime(),
cancelled_event->TimeConstant(), cancelled_event->Duration(),
cancelled_event->Curve(), cancelled_event->CurvePointsPerSecond(),
cancelled_event->CurveEndValue(), nullptr);
new_event = ParamEvent::CreateCancelValuesEvent(cancel_time,
std::move(saved_event));
} break;
case ParamEvent::Type::kSetTarget: {
if (cancelled_event->Time() < cancel_time) {
// Don't want to remove the SetTarget event if it started before the
// cancel time, so bump the index. But we do want to insert a
// cancelEvent so that we stop this automation and hold the value when
// we get there.
++cancelled_event_index;
new_event = ParamEvent::CreateCancelValuesEvent(cancel_time, nullptr);
}
} break;
case ParamEvent::Type::kSetValueCurve: {
// If the setValueCurve event started strictly before the cancel time,
// there might be something to do....
if (cancelled_event->Time() < cancel_time) {
if (cancel_time >
cancelled_event->Time() + cancelled_event->Duration()) {
// If the cancellation time is past the end of the curve there's
// nothing to do except remove the following events.
++cancelled_event_index;
} else {
// Cancellation time is in the middle of the curve. Therefore,
// create a new SetValueCurve event with the appropriate new
// parameters to cancel this event properly. Since it's illegal
// to insert any event within a SetValueCurve event, we can
// compute the new end value now instead of doing when running
// the timeline.
double new_duration = cancel_time - cancelled_event->Time();
float end_value = ValueCurveAtTime(
cancel_time, cancelled_event->Time(), cancelled_event->Duration(),
cancelled_event->Curve());
// Replace the existing SetValueCurve with this new one that is
// identical except for the duration.
new_event = ParamEvent::CreateGeneralEvent(
event_type, cancelled_event->Value(), cancelled_event->Time(),
cancelled_event->InitialValue(), cancelled_event->CallTime(),
cancelled_event->TimeConstant(), new_duration,
cancelled_event->Curve(), cancelled_event->CurvePointsPerSecond(),
end_value, nullptr);
new_set_value_event = ParamEvent::CreateSetValueEvent(
end_value, cancelled_event->Time() + new_duration);
}
}
} break;
case ParamEvent::Type::kSetValue:
case ParamEvent::Type::kSetValueCurveEnd:
case ParamEvent::Type::kCancelValues:
// Nothing needs to be done for a SetValue or CancelValues event.
break;
case ParamEvent::Type::kLastType:
NOTREACHED();
}
// Now remove all the following events from the timeline.
if (cancelled_event_index < events_.size()) {
RemoveCancelledEvents(cancelled_event_index);
}
// Insert the new event, if any.
if (new_event) {
InsertEvent(std::move(new_event), exception_state);
if (new_set_value_event) {
InsertEvent(std::move(new_set_value_event), exception_state);
}
}
}
std::tuple<bool, float> AudioParamHandler::ValueForContextTime(
AudioDestinationHandler& audio_destination,
float default_value,
float min_value,
float max_value,
unsigned render_quantum_frames) {
{
base::AutoTryLock try_locker(events_lock_);
if (!try_locker.is_acquired() || !events_.size() ||
audio_destination.CurrentTime() < events_[0]->Time()) {
return std::make_tuple(false, default_value);
}
}
// Ask for just a single value.
float value;
double sample_rate = audio_destination.SampleRate();
size_t start_frame = audio_destination.CurrentSampleFrame();
// One parameter change per render quantum.
double control_rate = sample_rate / render_quantum_frames;
value = ValuesForFrameRange(
start_frame, start_frame + 1, default_value, base::span_from_ref(value),
sample_rate, control_rate, min_value, max_value, render_quantum_frames);
return std::make_tuple(true, value);
}
float AudioParamHandler::ValuesForFrameRange(size_t start_frame,
size_t end_frame,
float default_value,
base::span<float> values,
double sample_rate,
double control_rate,
float min_value,
float max_value,
unsigned render_quantum_frames) {
// We can't contend the lock in the realtime audio thread.
base::AutoTryLock try_locker(events_lock_);
if (!try_locker.is_acquired()) {
std::ranges::fill(values, default_value);
return default_value;
}
float last_value =
ValuesForFrameRangeImpl(start_frame, end_frame, default_value, values,
sample_rate, control_rate, render_quantum_frames);
// Clamp the values now to the nominal range
vector_math::Vclip(values, 1, &min_value, &max_value, values, 1);
return last_value;
}
float AudioParamHandler::ValuesForFrameRangeImpl(
const size_t start_frame,
const size_t end_frame,
float default_value,
base::span<float> values,
const double sample_rate,
const double control_rate,
unsigned render_quantum_frames) {
DCHECK_GE(values.size(), 1u);
// Return default value if there are no events matching the desired time
// range.
if (!events_.size() || (end_frame / sample_rate <= events_[0]->Time())) {
std::ranges::fill(values, default_value);
return default_value;
}
int number_of_events = events_.size();
// MUST clamp event before `events_` is possibly mutated because
// `new_events_` has raw pointers to objects in `events_`. Clamping
// will clear out all of these pointers before `events_` is
// potentially modified.
//
// TODO(rtoy): Consider making `events_` be scoped_refptr instead of
// unique_ptr.
if (new_events_.size() > 0) {
ClampNewEventsToCurrentTime(start_frame / sample_rate);
}
if (number_of_events > 0) {
double current_time = start_frame / sample_rate;
if (HandleAllEventsInThePast(current_time, sample_rate, default_value,
values, render_quantum_frames)) {
return default_value;
}
}
// Maintain a running time (frame) and index for writing the values buffer.
// If first event is after startFrame then fill initial part of values buffer
// with defaultValue until we reach the first event time.
auto [current_frame, write_index] =
HandleFirstEvent(values, default_value, start_frame, end_frame,
sample_rate, start_frame, 0);
float value = default_value;
// Go through each event and render the value buffer where the times overlap,
// stopping when we've rendered all the requested values.
int last_skipped_event_index = 0;
for (int i = 0; i < number_of_events && write_index < values.size(); ++i) {
ParamEvent* event = events_[i].get();
ParamEvent* next_event =
i < number_of_events - 1 ? events_[i + 1].get() : nullptr;
// Wait until we get a more recent event.
if (!IsEventCurrent(event, next_event, current_frame, sample_rate)) {
// This is not the special SetValue event case, and nextEvent is
// in the past. We can skip processing of this event since it's
// in past. We keep track of this event in lastSkippedEventIndex
// to note what events we've skipped.
last_skipped_event_index = i;
continue;
}
// If there's no next event, set nextEventType to LastType to indicate that.
ProcessSetTargetFollowedByRamp(
i, event,
next_event ? static_cast<ParamEvent::Type>(next_event->GetType())
: ParamEvent::Type::kLastType,
current_frame, sample_rate, control_rate, value);
const float value1 = event->Value();
const double time1 = event->Time();
// Check to see if an event was cancelled.
const auto [value2, time2, next_event_type] = HandleCancelValues(
event, next_event, next_event ? next_event->Value() : value1,
next_event ? next_event->Time() : end_frame / sample_rate + 1);
DCHECK(!std::isnan(value1));
DCHECK(!std::isnan(value2));
CHECK_GE(time2, time1);
// `fill_to_end_frame` is the exclusive upper bound of the last frame to be
// computed for this event. It's either the last desired frame
// (`end_frame`) or derived from the end time of the next event
// (`time2`). We compute ceil(`time2`*`sample_rate`) because
// `fill_to_end_frame` is the exclusive upper bound. Consider the case
// where `start_frame` = 128 and `time2` = 128.1 (assuming `sample_rate` =
// 1). Since `time2` is greater than 128, we want to output a value for
// frame 128. This requires that `fill_to_end_frame` be at least 129. This
// is achieved by ceil(`time2`).
//
// However, `time2` can be very large, so compute this carefully in the case
// where `time2` exceeds the size of a size_t.
const size_t fill_to_end_frame =
end_frame > time2 * sample_rate
? static_cast<size_t>(ceil(time2 * sample_rate))
: end_frame;
DCHECK_GE(fill_to_end_frame, start_frame);
const size_t fill_to_frame =
std::min(fill_to_end_frame - start_frame, values.size());
// Time should be monotonically forward. So `fill_to_frame` should be
// greater than or equal to `write_index`. We have ensured that the time
// does not overlap when inserting events.
CHECK_GE(fill_to_frame, write_index);
// First handle linear and exponential ramps which require looking ahead to
// the next event.
if (next_event_type == ParamEvent::Type::kLinearRampToValue) {
std::tie(current_frame, value, write_index) = ProcessLinearRamp(
fill_to_frame, time1, time2, value1, value2, sample_rate, values,
current_frame, value, write_index);
} else if (next_event_type == ParamEvent::Type::kExponentialRampToValue) {
std::tie(current_frame, value, write_index) = ProcessExponentialRamp(
fill_to_frame, time1, time2, value1, value2, sample_rate, values,
current_frame, value, write_index);
} else {
// Handle event types not requiring looking ahead to the next event.
switch (event->GetType()) {
case ParamEvent::Type::kSetValue:
case ParamEvent::Type::kSetValueCurveEnd:
case ParamEvent::Type::kLinearRampToValue: {
current_frame = fill_to_end_frame;
// Simply stay at a constant value.
value = event->Value();
std::ranges::fill(
values.subspan(write_index, fill_to_frame - write_index), value);
write_index = fill_to_frame;
break;
}
case ParamEvent::Type::kCancelValues: {
std::tie(current_frame, value, write_index) =
ProcessCancelValues(fill_to_frame, time1, sample_rate,
control_rate, fill_to_end_frame, event, i,
values, current_frame, value, write_index);
break;
}
case ParamEvent::Type::kExponentialRampToValue: {
current_frame = fill_to_end_frame;
// If we're here, we've reached the end of the ramp. For
// the values after the end of the ramp, we just want to
// continue with the ramp end value.
value = event->Value();
std::ranges::fill(
values.subspan(write_index, fill_to_frame - write_index), value);
write_index = fill_to_frame;
break;
}
case ParamEvent::Type::kSetTarget: {
std::tie(current_frame, value, write_index) =
ProcessSetTarget(fill_to_frame, time1, value1, sample_rate,
control_rate, fill_to_end_frame, event, values,
current_frame, value, write_index);
break;
}
case ParamEvent::Type::kSetValueCurve: {
std::tie(current_frame, value, write_index) = ProcessSetValueCurve(
fill_to_frame, time1, sample_rate, start_frame, end_frame,
fill_to_end_frame, event, values, current_frame, value,
write_index);
break;
}
case ParamEvent::Type::kLastType:
NOTREACHED();
}
}
}
// If we skipped over any events (because they are in the past), we can
// remove them so we don't have to check them ever again. (This MUST be
// running with the m_events lock so we can safely modify the m_events
// array.)
if (last_skipped_event_index > 0) {
// `new_events_` should be empty here so we don't have to
// do any updates due to this mutation of `events_`.
DCHECK_EQ(new_events_.size(), 0u);
RemoveOldEvents(last_skipped_event_index - 1);
}
// If there's any time left after processing the last event then just
// propagate the last value to the end of the values buffer.
std::ranges::fill(values.subspan(write_index), value);
// This value is used to set the `.value` attribute of the AudioParam. it
// should be the last computed value.
return values.back();
}
std::tuple<size_t, unsigned> AudioParamHandler::HandleFirstEvent(
base::span<float> values,
float default_value,
size_t start_frame,
size_t end_frame,
double sample_rate,
size_t current_frame,
unsigned write_index) {
double first_event_time = events_[0]->Time();
if (first_event_time > start_frame / sample_rate) {
// `fill_to_frame` is an exclusive upper bound, so use ceil() to compute the
// bound from the `first_event_time`.
size_t fill_to_end_frame = end_frame;
double first_event_frame = ceil(first_event_time * sample_rate);
if (end_frame > first_event_frame) {
fill_to_end_frame = first_event_frame;
}
DCHECK_GE(fill_to_end_frame, start_frame);
size_t fill_to_frame = fill_to_end_frame - start_frame;
fill_to_frame = std::min(fill_to_frame, values.size());
std::ranges::fill(values.subspan(write_index, fill_to_frame - write_index),
default_value);
write_index = fill_to_frame;
current_frame += fill_to_frame;
}
return std::make_tuple(current_frame, write_index);
}
bool AudioParamHandler::IsEventCurrent(const ParamEvent* event,
const ParamEvent* next_event,
size_t current_frame,
double sample_rate) const {
// WARNING: due to round-off it might happen that `next_event->Time()` is just
// larger than `current_frame`/`sample_rate`. This means that we will end up
// running the `event` again. The code below had better be prepared for this
// case! What should happen is the fillToFrame should be 0 so that while the
// event is actually run again, nothing actually gets computed, and we move on
// to the next event.
//
// An example of this case is `SetValueCurveAtTime()`. The time at which
// `SetValueCurveAtTime()` ends (and the `SetValueAtTime()` begins) might be
// just past `current_time`/`sample_rate`. Then `SetValueCurveAtTime()` will
// be processed again before advancing to `SetValueAtTime()`. The number of
// frames to be processed should be zero in this case.
if (next_event && next_event->Time() < current_frame / sample_rate) {
// But if the current event is a SetValue event and the event time is
// between currentFrame - 1 and currentFrame (in time). we don't want to
// skip it. If we do skip it, the SetValue event is completely skipped
// and not applied, which is wrong. Other events don't have this problem.
// (Because currentFrame is unsigned, we do the time check in this funny,
// but equivalent way.)
double event_frame = event->Time() * sample_rate;
// Condition is currentFrame - 1 < eventFrame <= currentFrame, but
// currentFrame is unsigned and could be 0, so use
// currentFrame < eventFrame + 1 instead.
if (!(((event->GetType() == ParamEvent::Type::kSetValue ||
event->GetType() == ParamEvent::Type::kSetValueCurveEnd) &&
(event_frame <= current_frame) &&
(current_frame < event_frame + 1)))) {
// This is not the special SetValue event case, and nextEvent is
// in the past. We can skip processing of this event since it's
// in past.
return false;
}
}
return true;
}
void AudioParamHandler::ClampNewEventsToCurrentTime(double current_time) {
bool clamped_some_event_time = false;
for (auto* event : new_events_) {
if (event->Time() < current_time) {
event->SetTime(current_time);
clamped_some_event_time = true;
}
}
if (clamped_some_event_time) {
// If we clamped some event time to current time, we need to sort
// the event list in time order again, but it must be stable!
std::stable_sort(events_.begin(), events_.end(), ParamEvent::EventPrecedes);
}
new_events_.clear();
}
bool AudioParamHandler::HandleAllEventsInThePast(
double current_time,
double sample_rate,
float& default_value,
base::span<float> values,
unsigned render_quantum_frames) {
// Optimize the case where the last event is in the past.
ParamEvent* last_event = events_[events_.size() - 1].get();
ParamEvent::Type last_event_type = last_event->GetType();
double last_event_time = last_event->Time();
// If the last event is in the past and the event has ended, then we can
// just propagate the same value. Except for SetTarget which lasts
// "forever". SetValueCurve also has an explicit SetValue at the end of
// the curve, so we don't need to worry that SetValueCurve time is a
// start time, not an end time.
if (last_event_time + 1.5 * render_quantum_frames / sample_rate <
current_time) {
// If the last event is SetTarget, make sure we've converged and, that
// we're at least 5 time constants past the start of the event. If not, we
// have to continue processing it.
if (last_event_type == ParamEvent::Type::kSetTarget) {
if (HasSetTargetConverged(default_value, last_event->Value(),
current_time, last_event_time,
last_event->TimeConstant())) {
// We've converged. Slam the default value with the target value.
default_value = last_event->Value();
} else {
// Not converged, so give up; we can't remove this event yet.
return false;
}
}
// `events_` is being mutated. `new_events_` better be empty because
// there are raw pointers there.
DCHECK_EQ(new_events_.size(), 0U);
// The event has finished, so just copy the default value out.
// Since all events are now also in the past, we can just remove all
// timeline events too because `default_value` has the expected
// value.
std::ranges::fill(values, default_value);
RemoveOldEvents(events_.size());
return true;
}
return false;
}
void AudioParamHandler::ProcessSetTargetFollowedByRamp(
int event_index,
ParamEvent*& event,
ParamEvent::Type next_event_type,
size_t current_frame,
double sample_rate,
double control_rate,
float& value) {
// If the current event is SetTarget and the next event is a
// LinearRampToValue or ExponentialRampToValue, special handling is needed.
// In this case, the linear and exponential ramp should start at wherever
// the SetTarget processing has reached.
if (event->GetType() == ParamEvent::Type::kSetTarget &&
(next_event_type == ParamEvent::Type::kLinearRampToValue ||
next_event_type == ParamEvent::Type::kExponentialRampToValue)) {
// Replace the SetTarget with a SetValue to set the starting time and
// value for the ramp using the current frame. We need to update `value`
// appropriately depending on whether the ramp has started or not.
//
// If SetTarget starts somewhere between currentFrame - 1 and
// currentFrame, we directly compute the value it would have at
// currentFrame. If not, we update the value from the value from
// currentFrame - 1.
//
// Can't use the condition currentFrame - 1 <= t0 * sampleRate <=
// currentFrame because currentFrame is unsigned and could be 0. Instead,
// compute the condition this way,
// where f = currentFrame and Fs = sampleRate:
//
// f - 1 <= t0 * Fs <= f
// 2 * f - 2 <= 2 * Fs * t0 <= 2 * f
// -2 <= 2 * Fs * t0 - 2 * f <= 0
// -1 <= 2 * Fs * t0 - 2 * f + 1 <= 1
// abs(2 * Fs * t0 - 2 * f + 1) <= 1
if (fabs(2 * sample_rate * event->Time() - 2 * current_frame + 1) <= 1) {
// SetTarget is starting somewhere between currentFrame - 1 and
// currentFrame. Compute the value the SetTarget would have at the
// currentFrame.
value = event->Value() +
(value - event->Value()) *
fdlibm::exp(-(current_frame / sample_rate - event->Time()) /
event->TimeConstant());
} else {
// SetTarget has already started. Update `value` one frame because it's
// the value from the previous frame.
float discrete_time_constant =
static_cast<float>(audio_utilities::DiscreteTimeConstantForSampleRate(
event->TimeConstant(), control_rate));
value += (event->Value() - value) * discrete_time_constant;
}
// Insert a SetValueEvent to mark the starting value and time.
// Clear the clamp check because this doesn't need it.
events_[event_index] =
ParamEvent::CreateSetValueEvent(value, current_frame / sample_rate);
// Update our pointer to the current event because we just changed it.
event = events_[event_index].get();
}
}
std::tuple<float, double, AudioParamHandler::ParamEvent::Type>
AudioParamHandler::HandleCancelValues(const ParamEvent* current_event,
ParamEvent* next_event,
float value2,
double time2) {
DCHECK(current_event);
ParamEvent::Type next_event_type =
next_event ? next_event->GetType() : ParamEvent::Type::kLastType;
if (next_event && next_event->GetType() == ParamEvent::Type::kCancelValues &&
next_event->SavedEvent()) {
float value1 = current_event->Value();
double time1 = current_event->Time();
switch (current_event->GetType()) {
case ParamEvent::Type::kCancelValues:
case ParamEvent::Type::kLinearRampToValue:
case ParamEvent::Type::kExponentialRampToValue:
case ParamEvent::Type::kSetValueCurveEnd:
case ParamEvent::Type::kSetValue: {
// These three events potentially establish a starting value for
// the following event, so we need to examine the cancelled
// event to see what to do.
const ParamEvent* saved_event = next_event->SavedEvent();
// Update the end time and type to pretend that we're running
// this saved event type.
time2 = next_event->Time();
next_event_type = saved_event->GetType();
if (next_event->HasDefaultCancelledValue()) {
// We've already established a value for the cancelled
// event, so just return it.
value2 = next_event->Value();
} else {
// If the next event would have been a LinearRamp or
// ExponentialRamp, we need to compute a new end value for
// the event so that the curve works continues as if it were
// not cancelled.
switch (saved_event->GetType()) {
case ParamEvent::Type::kLinearRampToValue:
value2 =
LinearRampAtTime(next_event->Time(), value1, time1,
saved_event->Value(), saved_event->Time());
break;
case ParamEvent::Type::kExponentialRampToValue:
value2 = ExponentialRampAtTime(next_event->Time(), value1, time1,
saved_event->Value(),
saved_event->Time());
DCHECK(!std::isnan(value1));
break;
case ParamEvent::Type::kSetValueCurve:
case ParamEvent::Type::kSetValueCurveEnd:
case ParamEvent::Type::kSetValue:
case ParamEvent::Type::kSetTarget:
case ParamEvent::Type::kCancelValues:
// These cannot be possible types for the saved event
// because they can't be created.
// createCancelValuesEvent doesn't allow them (SetValue,
// SetTarget, CancelValues) or cancelScheduledValues()
// doesn't create such an event (SetValueCurve).
NOTREACHED();
case ParamEvent::Type::kLastType:
// Illegal event type.
NOTREACHED();
}
// Cache the new value so we don't keep computing it over and over.
next_event->SetCancelledValue(value2);
}
} break;
case ParamEvent::Type::kSetValueCurve:
// Everything needed for this was handled when cancelling was
// done.
break;
case ParamEvent::Type::kSetTarget:
// Nothing special needs to be done for SetTarget
// followed by CancelValues.
break;
case ParamEvent::Type::kLastType:
NOTREACHED();
}
}
return std::make_tuple(value2, time2, next_event_type);
}
std::tuple<size_t, float, unsigned> AudioParamHandler::ProcessLinearRamp(
const size_t fill_to_frame,
const double time1,
const double time2,
const float value1,
const float value2,
const double sample_rate,
base::span<float> values,
size_t current_frame,
float value,
unsigned write_index) {
double delta_time = time2 - time1;
DCHECK_GE(delta_time, 0);
// Since delta_time is a double, 1/delta_time can easily overflow a float.
// Thus, if delta_time is close enough to zero (less than float min), treat it
// as zero.
float k =
delta_time <= std::numeric_limits<float>::min() ? 0 : 1 / delta_time;
const float value_delta = value2 - value1;
#if defined(ARCH_CPU_X86_FAMILY)
if (fill_to_frame > write_index) {
// Minimize in-loop operations. Calculate starting value and increment.
// Next step: value += inc.
// value = value1 +
// (currentFrame/sampleRate - time1) * k * (value2 - value1);
// inc = 4 / sampleRate * k * (value2 - value1);
// Resolve recursion by expanding constants to achieve a 4-step loop
// unrolling.
// value = value1 +
// ((currentFrame/sampleRate - time1) + i * sampleFrameTimeIncr) * k
// * (value2 -value1), i in 0..3
__m128 v_value =
_mm_mul_ps(_mm_set_ps1(1 / sample_rate), _mm_set_ps(3, 2, 1, 0));
v_value =
_mm_add_ps(v_value, _mm_set_ps1(current_frame / sample_rate - time1));
v_value = _mm_mul_ps(v_value, _mm_set_ps1(k * value_delta));
v_value = _mm_add_ps(v_value, _mm_set_ps1(value1));
__m128 v_inc = _mm_set_ps1(4 / sample_rate * k * value_delta);
// Truncate loop steps to multiple of 4.
unsigned fill_to_frame_trunc =
write_index + ((fill_to_frame - write_index) / 4) * 4;
// `fill_to_frame_trunc` should be less than or equal to `fill_to_frame`.
CHECK_LE(fill_to_frame_trunc, fill_to_frame);
current_frame += fill_to_frame_trunc - write_index;
// Process 4 loop steps.
for (; write_index < fill_to_frame_trunc; write_index += 4) {
// SAFETY: DCHECK previously checked that `fill_to_frame_trunc <
// values.size()`. In the for loop, `write_index < fill_to_frame_trunc` so
// this is safe.
_mm_storeu_ps(UNSAFE_BUFFERS(values.data() + write_index), v_value);
v_value = _mm_add_ps(v_value, v_inc);
}
}
// Update `value` with the last value computed so that the
// `.value` attribute of the AudioParam gets the correct linear
// ramp value, in case the following loop doesn't execute.
if (write_index >= 1) {
value = values[write_index - 1];
}
#endif
DCHECK_GE(fill_to_frame, write_index);
// Serially process remaining values.
std::ranges::generate(
values.subspan(write_index, fill_to_frame - write_index),
[=, &current_frame, &value]() {
float x = (current_frame / sample_rate - time1) * k;
// value = (1 - x) * value1 + x * value2;
value = value1 + x * value_delta;
++current_frame;
return value;
});
return std::make_tuple(current_frame, value, fill_to_frame);
}
std::tuple<size_t, float, unsigned> AudioParamHandler::ProcessExponentialRamp(
const size_t fill_to_frame,
const double time1,
const double time2,
const float value1,
const float value2,
const double sample_rate,
base::span<float> values,
size_t current_frame,
float value,
unsigned write_index) {
DCHECK_GE(fill_to_frame, write_index);
if (value1 * value2 <= 0 || time1 >= time2) {
// It's an error 1) if `value1` and `value2` have opposite signs or if one
// of them is zero, or 2) if `time1` is greater than or equal to `time2`.
// Handle this by propagating the previous value.
value = value1;
std::ranges::fill(values.subspan(write_index, fill_to_frame - write_index),
value);
write_index = fill_to_frame;
} else {
double delta_time = time2 - time1;
double num_sample_frames = delta_time * sample_rate;
// The value goes exponentially from value1 to value2 in a duration of
// deltaTime seconds according to
//
// v(t) = v1*(v2/v1)^((t-t1)/(t2-t1))
//
// Let c be currentFrame and F be the sampleRate. Then we want to
// sample v(t) at times t = (c + k)/F for k = 0, 1, ...:
//
// v((c+k)/F) = v1*(v2/v1)^(((c/F+k/F)-t1)/(t2-t1))
// = v1*(v2/v1)^((c/F-t1)/(t2-t1))
// *(v2/v1)^((k/F)/(t2-t1))
// = v1*(v2/v1)^((c/F-t1)/(t2-t1))
// *[(v2/v1)^(1/(F*(t2-t1)))]^k
//
// Thus, this can be written as
//
// v((c+k)/F) = V*m^k
//
// where
// V = v1*(v2/v1)^((c/F-t1)/(t2-t1))
// m = (v2/v1)^(1/(F*(t2-t1)))
// Compute the per-sample multiplier.
double multiplier = fdlibm::pow(value2 / value1, 1.0 / num_sample_frames);
// Set the starting value of the exponential ramp. Do not attempt
// to optimize pow to powf. See crbug.com/771306.
value = value1 *
fdlibm::pow(value2 / static_cast<double>(value1),
(current_frame / sample_rate - time1) / delta_time);
double accumulator = value;
std::ranges::generate(
values.subspan(write_index, fill_to_frame - write_index), [&]() {
value = accumulator;
accumulator *= multiplier;
++current_frame;
return value;
});
write_index = fill_to_frame;
// Due to roundoff it's possible that value exceeds value2. Clip value
// to value2 if we are within 1/2 frame of time2.
if (current_frame > time2 * sample_rate - 0.5) {
value = value2;
}
}
return std::make_tuple(current_frame, value, write_index);
}
std::tuple<size_t, float, unsigned> AudioParamHandler::ProcessSetTarget(
const size_t fill_to_frame,
const double time1,
const float value1,
const double sample_rate,
const double control_rate,
const size_t fill_to_end_frame,
const ParamEvent* const event,
base::span<float> values,
size_t current_frame,
float value,
unsigned write_index) {
DCHECK_GE(fill_to_frame, write_index);
// Exponential approach to target value with given time constant.
//
// v(t) = v2 + (v1 - v2)*exp(-(t-t1/tau))
//
float target = value1;
float time_constant = event->TimeConstant();
float discrete_time_constant =
static_cast<float>(audio_utilities::DiscreteTimeConstantForSampleRate(
time_constant, control_rate));
// Set the starting value correctly. This is only needed when the
// current time is "equal" to the start time of this event. This is
// to get the sampling correct if the start time of this automation
// isn't on a frame boundary. Otherwise, we can just continue from
// where we left off from the previous rendering quantum.
{
double ramp_start_frame = time1 * sample_rate;
// Condition is c - 1 < r <= c where c = currentFrame and r =
// rampStartFrame. Compute it this way because currentFrame is
// unsigned and could be 0.
if (ramp_start_frame <= current_frame &&
current_frame < ramp_start_frame + 1) {
value = target + (value - target) *
fdlibm::exp(-(current_frame / sample_rate - time1) /
time_constant);
} else {
// Otherwise, need to compute a new value because `value` is the
// last computed value of SetTarget. Time has progressed by one
// frame, so we need to update the value for the new frame.
value += (target - value) * discrete_time_constant;
}
}
// If the value is close enough to the target, just fill in the data
// with the target value.
if (HasSetTargetConverged(value, target, current_frame / sample_rate, time1,
time_constant)) {
current_frame += fill_to_frame - write_index;
std::ranges::fill(values.subspan(write_index, fill_to_frame - write_index),
target);
write_index = fill_to_frame;
} else {
#if defined(ARCH_CPU_X86_FAMILY)
if (fill_to_frame > write_index) {
// Resolve recursion by expanding constants to achieve a 4-step
// loop unrolling.
//
// v1 = v0 + (t - v0) * c
// v2 = v1 + (t - v1) * c
// v2 = v0 + (t - v0) * c + (t - (v0 + (t - v0) * c)) * c
// v2 = v0 + (t - v0) * c + (t - v0) * c - (t - v0) * c * c
// v2 = v0 + (t - v0) * c * (2 - c)
// Thus c0 = c, c1 = c*(2-c). The same logic applies to c2 and c3.
const float c0 = discrete_time_constant;
const float c1 = c0 * (2 - c0);
const float c2 = c0 * ((c0 - 3) * c0 + 3);
const float c3 = c0 * (c0 * ((4 - c0) * c0 - 6) + 4);
float delta;
__m128 v_c = _mm_set_ps(c2, c1, c0, 0);
__m128 v_delta, v_value, v_result;
// Process 4 loop steps.
unsigned fill_to_frame_trunc =
write_index + ((fill_to_frame - write_index) / 4) * 4;
// `fill_to_frame_trunc` should be less than or equal to `fill_to_frame`.
CHECK_LE(fill_to_frame_trunc, fill_to_frame);
for (; write_index < fill_to_frame_trunc; write_index += 4) {
delta = target - value;
v_delta = _mm_set_ps1(delta);
v_value = _mm_set_ps1(value);
v_result = _mm_add_ps(v_value, _mm_mul_ps(v_delta, v_c));
// SAFETY: DCHECK previously checked that `fill_to_frame_trunc <
// values.size()`. In the for loop, `write_index < fill_to_frame_trunc`
// so this is safe.
_mm_storeu_ps(UNSAFE_BUFFERS(values.data() + write_index), v_result);
// Update value for next iteration.
value += delta * c3;
}
}
#endif
// Serially process remaining values
std::ranges::generate(
values.subspan(write_index, fill_to_frame - write_index), [&]() {
float v = value;
value += (target - value) * discrete_time_constant;
return v;
});
write_index = fill_to_frame;
// The previous loops may have updated `value` one extra time.
// Reset it to the last computed value.
if (write_index >= 1) {
value = values[write_index - 1];
}
current_frame = fill_to_end_frame;
}
return std::make_tuple(current_frame, value, write_index);
}
std::tuple<size_t, float, unsigned> AudioParamHandler::ProcessSetValueCurve(
size_t fill_to_frame,
const double time1,
const double sample_rate,
const size_t start_frame,
const size_t end_frame,
size_t fill_to_end_frame,
const ParamEvent* const event,
base::span<float> values,
size_t current_frame,
float value,
unsigned write_index) {
DCHECK_GE(fill_to_frame, write_index);
base::span<const float> curve_data(event->Curve());
float curve_end_value = event->CurveEndValue();
// Curve events have duration, so don't just use next event time.
double duration = event->Duration();
// How much to step the curve index for each frame. This is basically
// the term (N - 1)/Td in the specification.
double curve_points_per_frame = event->CurvePointsPerSecond() / sample_rate;
if (curve_data.empty() || duration <= 0 || sample_rate <= 0) {
// Error condition - simply propagate previous value.
current_frame = fill_to_end_frame;
std::ranges::fill(values.subspan(write_index, fill_to_frame - write_index),
value);
return std::make_tuple(current_frame, value, fill_to_frame);
}
// Save old values and recalculate information based on the curve's
// duration instead of the next event time.
size_t next_event_fill_to_frame = fill_to_frame;
// fillToEndFrame = min(endFrame,
// ceil(sampleRate * (time1 + duration))),
// but compute this carefully in case sampleRate*(time1 + duration) is
// huge. fillToEndFrame is an exclusive upper bound of the last frame
// to be computed, so ceil is used.
{
double curve_end_frame = ceil(sample_rate * (time1 + duration));
if (end_frame > curve_end_frame) {
fill_to_end_frame = static_cast<size_t>(curve_end_frame);
} else {
fill_to_end_frame = end_frame;
}
}
// `fill_to_frame` can be less than `start_frame` when the end of the
// setValueCurve automation has been reached, but the next automation
// has not yet started. In this case, `fill_to_frame` is clipped to
// `time1`+`duration` above, but `start_frame` will keep increasing
// (because the current time is increasing).
fill_to_frame = (fill_to_end_frame < start_frame)
? 0
: static_cast<unsigned>(fill_to_end_frame - start_frame);
fill_to_frame = std::min(fill_to_frame, values.size());
// Index into the curve data using a floating-point value.
// We're scaling the number of curve points by the duration (see
// curvePointsPerFrame).
double curve_virtual_index = 0;
if (time1 < current_frame / sample_rate) {
// Index somewhere in the middle of the curve data.
// Don't use timeToSampleFrame() since we want the exact
// floating-point frame.
double frame_offset = current_frame - time1 * sample_rate;
curve_virtual_index = curve_points_per_frame * frame_offset;
}
// Set the default value in case fillToFrame is 0.
value = curve_end_value;
// Render the stretched curve data using linear interpolation.
// Oversampled curve data can be provided if sharp discontinuities are
// desired.
unsigned k = 0;
#if defined(ARCH_CPU_X86_FAMILY)
if (fill_to_frame > write_index) {
const __m128 v_curve_virtual_index = _mm_set_ps1(curve_virtual_index);
const __m128 v_curve_points_per_frame = _mm_set_ps1(curve_points_per_frame);
const __m128 v_number_of_curve_points_m1 =
_mm_set_ps1(curve_data.size() - 1);
const __m128 v_n1 = _mm_set_ps1(1.0f);
const __m128 v_n4 = _mm_set_ps1(4.0f);
__m128 v_k = _mm_set_ps(3, 2, 1, 0);
int a_curve_index0[4];
int a_curve_index1[4];
// Truncate loop steps to multiple of 4
unsigned truncated_steps = ((fill_to_frame - write_index) / 4) * 4;
unsigned fill_to_frame_trunc = write_index + truncated_steps;
// `fill_to_frame_trunc` should be less than or equal to `fill_to_frame`.
CHECK_LE(fill_to_frame_trunc, fill_to_frame);
for (; write_index < fill_to_frame_trunc; write_index += 4) {
// Compute current index this way to minimize round-off that would
// have occurred by incrementing the index by curvePointsPerFrame.
__m128 v_current_virtual_index = _mm_add_ps(
v_curve_virtual_index, _mm_mul_ps(v_k, v_curve_points_per_frame));
v_k = _mm_add_ps(v_k, v_n4);
// Clamp index to the last element of the array.
__m128i v_curve_index0 = _mm_cvttps_epi32(
_mm_min_ps(v_current_virtual_index, v_number_of_curve_points_m1));
__m128i v_curve_index1 =
_mm_cvttps_epi32(_mm_min_ps(_mm_add_ps(v_current_virtual_index, v_n1),
v_number_of_curve_points_m1));
// Linearly interpolate between the two nearest curve points.
// `delta` is clamped to 1 because `current_virtual_index` can exceed
// `curve_index0` by more than one. This can happen when we reached
// the end of the curve but still need values to fill out the
// current rendering quantum.
_mm_storeu_si128(reinterpret_cast<__m128i*>(a_curve_index0),
v_curve_index0);
_mm_storeu_si128(reinterpret_cast<__m128i*>(a_curve_index1),
v_curve_index1);
__m128 v_c0 = _mm_set_ps(
curve_data[a_curve_index0[3]], curve_data[a_curve_index0[2]],
curve_data[a_curve_index0[1]], curve_data[a_curve_index0[0]]);
__m128 v_c1 = _mm_set_ps(
curve_data[a_curve_index1[3]], curve_data[a_curve_index1[2]],
curve_data[a_curve_index1[1]], curve_data[a_curve_index1[0]]);
__m128 v_delta = _mm_min_ps(
_mm_sub_ps(v_current_virtual_index, _mm_cvtepi32_ps(v_curve_index0)),
v_n1);
__m128 v_value =
_mm_add_ps(v_c0, _mm_mul_ps(_mm_sub_ps(v_c1, v_c0), v_delta));
// SAFETY: DCHECK previously checked that `fill_to_frame_trunc <
// values.size()`. In the for loop, `write_index < fill_to_frame_trunc` so
// this is safe.
_mm_storeu_ps(UNSAFE_BUFFERS(values.data() + write_index), v_value);
}
// Pass along k to the serial loop.
k = truncated_steps;
}
if (write_index >= 1) {
value = values[write_index - 1];
}
#endif
DCHECK_GE(fill_to_frame, write_index);
std::ranges::generate(
values.subspan(write_index, fill_to_frame - write_index), [&]() {
// Compute current index this way to minimize round-off that would have
// occurred by incrementing the index by curvePointsPerFrame.
double current_virtual_index =
curve_virtual_index + k * curve_points_per_frame;
size_t curve_index0;
// Clamp index to the last element of the array.
if (current_virtual_index < curve_data.size()) {
curve_index0 = static_cast<unsigned>(current_virtual_index);
} else {
curve_index0 = curve_data.size() - 1;
}
size_t curve_index1 = std::min(curve_index0 + 1, curve_data.size() - 1);
// Linearly interpolate between the two nearest curve points. `delta`
// is clamped to 1 because `current_virtual_index` can exceed
// `curve_index0` by more than one. This can happen when we reached
// the end of the curve but still need values to fill out the current
// rendering quantum.
DCHECK_LT(curve_index0, curve_data.size());
DCHECK_LT(curve_index1, curve_data.size());
float c0 = curve_data[curve_index0];
float c1 = curve_data[curve_index1];
double delta = std::min(current_virtual_index - curve_index0, 1.0);
++k;
value = c0 + (c1 - c0) * delta;
return value;
});
write_index = fill_to_frame;
// If there's any time left after the duration of this event and the
// start of the next, then just propagate the last value of the
// `curve_data`. Don't modify `value` unless there is time left.
if (write_index < next_event_fill_to_frame) {
value = curve_end_value;
std::ranges::fill(
values.subspan(write_index, next_event_fill_to_frame - write_index),
value);
write_index = next_event_fill_to_frame;
}
// Re-adjust current time
current_frame += next_event_fill_to_frame;
return std::make_tuple(current_frame, value, write_index);
}
std::tuple<size_t, float, unsigned> AudioParamHandler::ProcessCancelValues(
const size_t fill_to_frame,
const double time1,
const double sample_rate,
const double control_rate,
const size_t fill_to_end_frame,
const ParamEvent* const event,
const int event_index,
base::span<float> values,
size_t current_frame,
float value,
unsigned write_index) {
DCHECK_GE(fill_to_frame, write_index);
// If the previous event was a SetTarget or ExponentialRamp
// event, the current value is one sample behind. Update
// the sample value by one sample, but only at the start of
// this CancelValues event.
if (event->HasDefaultCancelledValue()) {
value = event->Value();
} else {
double cancel_frame = time1 * sample_rate;
if (event_index >= 1 && cancel_frame <= current_frame &&
current_frame < cancel_frame + 1) {
ParamEvent::Type last_event_type = events_[event_index - 1]->GetType();
if (last_event_type == ParamEvent::Type::kSetTarget) {
float target = events_[event_index - 1]->Value();
float time_constant = events_[event_index - 1]->TimeConstant();
float discrete_time_constant = static_cast<float>(
audio_utilities::DiscreteTimeConstantForSampleRate(time_constant,
control_rate));
value += (target - value) * discrete_time_constant;
}
}
}
// Simply stay at the current value.
std::ranges::fill(values.subspan(write_index, fill_to_frame - write_index),
value);
write_index = fill_to_frame;
current_frame = fill_to_end_frame;
return std::make_tuple(current_frame, value, write_index);
}
void AudioParamHandler::RemoveCancelledEvents(
wtf_size_t first_event_to_remove) {
// For all the events that are being removed, also remove that event
// from `new_events_`.
if (new_events_.size() > 0) {
for (wtf_size_t k = first_event_to_remove; k < events_.size(); ++k) {
new_events_.erase(events_[k].get());
}
}
// Remove the cancelled events from the list.
events_.EraseAt(first_event_to_remove,
events_.size() - first_event_to_remove);
}
void AudioParamHandler::RemoveOldEvents(wtf_size_t event_count) {
wtf_size_t n_events = events_.size();
DCHECK(event_count <= n_events);
// Always leave at least one event in the event list!
if (n_events > 1) {
events_.EraseAt(0, std::min(event_count, n_events - 1));
}
}
} // namespace blink