blob: a16b9ac204c7f4750ca797b3017e2e2a2f4ab590 [file] [log] [blame]
/*
* Copyright (C) 2011 Google Inc. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
* THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include "third_party/blink/renderer/modules/webaudio/audio_param_timeline.h"
#include <algorithm>
#include <memory>
#include "base/memory/ptr_util.h"
#include "build/build_config.h"
#include "third_party/blink/renderer/core/frame/deprecation.h"
#include "third_party/blink/renderer/core/inspector/console_message.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/wtf/cpu.h"
#include "third_party/blink/renderer/platform/wtf/math_extras.h"
#if defined(ARCH_CPU_X86_FAMILY)
#include <emmintrin.h>
#endif
namespace blink {
// 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).
const float kTimeConstantsToConverge = 10;
const float kSetTargetThreshold = 4.539992976248485e-05;
static bool IsNonNegativeAudioParamTime(double time,
ExceptionState& exception_state,
String message = "Time") {
if (time >= 0)
return true;
exception_state.ThrowRangeError(
message +
" must be a finite non-negative number: " + String::Number(time));
return false;
}
static bool IsPositiveAudioParamTime(double time,
ExceptionState& exception_state,
String message) {
if (time > 0)
return true;
exception_state.ThrowRangeError(
message + " must be a finite positive number: " + String::Number(time));
return false;
}
String AudioParamTimeline::EventToString(const ParamEvent& event) const {
// The default arguments for most automation methods is the value and the
// time.
String args =
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::kSetValue:
s = "setValueAtTime";
break;
case ParamEvent::kLinearRampToValue:
s = "linearRampToValueAtTime";
break;
case ParamEvent::kExponentialRampToValue:
s = "exponentialRampToValue";
break;
case ParamEvent::kSetTarget:
s = "setTargetAtTime";
// This has an extra time constant arg
args = args + ", " + String::Number(event.TimeConstant(), 16);
break;
case ParamEvent::kSetValueCurve:
s = "setValueCurveAtTime";
// Replace the default arg, using "..." to denote the curve argument.
args = "..., " + String::Number(event.Time(), 16) + ", " +
String::Number(event.Duration(), 16);
break;
case ParamEvent::kCancelValues:
case ParamEvent::kSetValueCurveEnd:
// Fall through; we should never have to print out the internal
// |kCancelValues| or |kSetValueCurveEnd| event.
case ParamEvent::kLastType:
NOTREACHED();
break;
};
return s + "(" + args + ")";
}
// Computes the value of a linear ramp event at time t with the given event
// parameters.
float AudioParamTimeline::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 AudioParamTimeline::ExponentialRampAtTime(double t,
float value1,
double time1,
float value2,
double time2) {
return value1 * pow(value2 / value1, (t - time1) / (time2 - time1));
}
// Compute the value of a set target event at time t with the given event
// parameters.
float AudioParamTimeline::TargetValueAtTime(double t,
float value1,
double time1,
float value2,
float time_constant) {
return value2 + (value1 - value2) * exp(-(t - time1) / time_constant);
}
// Compute the value of a set curve event at time t with the given event
// parameters.
float AudioParamTimeline::ValueCurveAtTime(double t,
double time1,
double duration,
const float* curve_data,
unsigned curve_length) {
double curve_index = (curve_length - 1) / duration * (t - time1);
unsigned k = std::min(static_cast<unsigned>(curve_index), curve_length - 1);
unsigned k1 = std::min(k + 1, curve_length - 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;
}
std::unique_ptr<AudioParamTimeline::ParamEvent>
AudioParamTimeline::ParamEvent::CreateSetValueEvent(float value, double time) {
return base::WrapUnique(new ParamEvent(ParamEvent::kSetValue, value, time));
}
std::unique_ptr<AudioParamTimeline::ParamEvent>
AudioParamTimeline::ParamEvent::CreateLinearRampEvent(float value,
double time,
float initial_value,
double call_time) {
return base::WrapUnique(new ParamEvent(ParamEvent::kLinearRampToValue, value,
time, initial_value, call_time));
}
std::unique_ptr<AudioParamTimeline::ParamEvent>
AudioParamTimeline::ParamEvent::CreateExponentialRampEvent(float value,
double time,
float initial_value,
double call_time) {
return base::WrapUnique(new ParamEvent(ParamEvent::kExponentialRampToValue,
value, time, initial_value,
call_time));
}
std::unique_ptr<AudioParamTimeline::ParamEvent>
AudioParamTimeline::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::kSetTarget, value, time, time_constant));
}
std::unique_ptr<AudioParamTimeline::ParamEvent>
AudioParamTimeline::ParamEvent::CreateSetValueCurveEvent(
const Vector<float>& curve,
double time,
double duration) {
double curve_points = (curve.size() - 1) / duration;
float end_value = curve.data()[curve.size() - 1];
return base::WrapUnique(new ParamEvent(ParamEvent::kSetValueCurve, time,
duration, curve, curve_points,
end_value));
}
std::unique_ptr<AudioParamTimeline::ParamEvent>
AudioParamTimeline::ParamEvent::CreateSetValueCurveEndEvent(float value,
double time) {
return base::WrapUnique(
new ParamEvent(ParamEvent::kSetValueCurveEnd, value, time));
}
std::unique_ptr<AudioParamTimeline::ParamEvent>
AudioParamTimeline::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::kLastType);
DCHECK(saved_type == ParamEvent::kLinearRampToValue ||
saved_type == ParamEvent::kExponentialRampToValue ||
saved_type == ParamEvent::kSetValueCurve);
}
return base::WrapUnique(
new ParamEvent(ParamEvent::kCancelValues, time, std::move(saved_event)));
}
std::unique_ptr<AudioParamTimeline::ParamEvent>
AudioParamTimeline::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)));
}
AudioParamTimeline::ParamEvent* AudioParamTimeline::ParamEvent::SavedEvent()
const {
DCHECK_EQ(GetType(), ParamEvent::kCancelValues);
return saved_event_.get();
}
bool AudioParamTimeline::ParamEvent::HasDefaultCancelledValue() const {
DCHECK_EQ(GetType(), ParamEvent::kCancelValues);
return has_default_cancelled_value_;
}
void AudioParamTimeline::ParamEvent::SetCancelledValue(float value) {
DCHECK_EQ(GetType(), ParamEvent::kCancelValues);
value_ = value;
has_default_cancelled_value_ = true;
}
// General event
AudioParamTimeline::ParamEvent::ParamEvent(
ParamEvent::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)
: 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
AudioParamTimeline::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::kSetValue ||
type == ParamEvent::kSetValueCurveEnd);
}
// Create a linear or exponential ramp that requires an initial value and
// time in case
// there is no actual event that preceeds this event.
AudioParamTimeline::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::kLinearRampToValue ||
type == ParamEvent::kExponentialRampToValue);
}
// Create an event needing a time constant (setTargetAtTime)
AudioParamTimeline::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::kSetTarget);
}
// Create a setValueCurve event
AudioParamTimeline::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::kSetValueCurve);
unsigned curve_length = curve.size();
curve_.resize(curve_length);
memcpy(curve_.data(), curve.data(), curve_length * sizeof(float));
}
// Create CancelValues event
AudioParamTimeline::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::kCancelValues);
}
void AudioParamTimeline::SetValueAtTime(float value,
double time,
ExceptionState& exception_state) {
DCHECK(IsMainThread());
if (!IsNonNegativeAudioParamTime(time, exception_state))
return;
MutexLocker locker(events_lock_);
InsertEvent(ParamEvent::CreateSetValueEvent(value, time), exception_state);
}
void AudioParamTimeline::LinearRampToValueAtTime(
float value,
double time,
float initial_value,
double call_time,
ExceptionState& exception_state) {
DCHECK(IsMainThread());
if (!IsNonNegativeAudioParamTime(time, exception_state))
return;
MutexLocker locker(events_lock_);
InsertEvent(
ParamEvent::CreateLinearRampEvent(value, time, initial_value, call_time),
exception_state);
}
void AudioParamTimeline::ExponentialRampToValueAtTime(
float value,
double time,
float initial_value,
double call_time,
ExceptionState& exception_state) {
DCHECK(IsMainThread());
if (!IsNonNegativeAudioParamTime(time, exception_state))
return;
if (!value) {
exception_state.ThrowRangeError(
"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;
}
MutexLocker locker(events_lock_);
InsertEvent(ParamEvent::CreateExponentialRampEvent(value, time, initial_value,
call_time),
exception_state);
}
void AudioParamTimeline::SetTargetAtTime(float target,
double time,
double time_constant,
ExceptionState& exception_state) {
DCHECK(IsMainThread());
if (!IsNonNegativeAudioParamTime(time, exception_state) ||
!IsNonNegativeAudioParamTime(time_constant, exception_state,
"Time constant"))
return;
MutexLocker 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, time), exception_state);
} else {
InsertEvent(ParamEvent::CreateSetTargetEvent(target, time, time_constant),
exception_state);
}
}
void AudioParamTimeline::SetValueCurveAtTime(const Vector<float>& curve,
double time,
double duration,
ExceptionState& exception_state) {
DCHECK(IsMainThread());
if (!IsNonNegativeAudioParamTime(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;
}
MutexLocker locker(events_lock_);
InsertEvent(ParamEvent::CreateSetValueCurveEvent(curve, time, duration),
exception_state);
// 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.data()[curve.size() - 1], time + duration),
exception_state);
}
void AudioParamTimeline::InsertEvent(std::unique_ptr<ParamEvent> event,
ExceptionState& exception_state) {
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.
bool is_valid = event->GetType() < ParamEvent::kLastType &&
std::isfinite(event->Value()) &&
std::isfinite(event->Time()) &&
std::isfinite(event->TimeConstant()) &&
std::isfinite(event->Duration()) && event->Duration() >= 0;
DCHECK(is_valid);
if (!is_valid)
return;
unsigned i = 0;
double insert_time = event->Time();
if (!events_.size() &&
(event->GetType() == ParamEvent::kLinearRampToValue ||
event->GetType() == ParamEvent::kExponentialRampToValue)) {
// There are no events preceding these ramps. Insert a new
// setValueAtTime event to set the starting point for these
// events. Use a time of 0 to make sure it preceeds all other
// events. This will get fixed when when handle new events.
events_.insert(0, AudioParamTimeline::ParamEvent::CreateSetValueEvent(
event->InitialValue(), 0));
new_events_.insert(events_[0].get());
}
for (i = 0; i < events_.size(); ++i) {
if (event->GetType() == ParamEvent::kSetValueCurve) {
// If this event is a SetValueCurve, make sure it doesn't overlap any
// existing event. It's ok if the SetValueCurve starts at the same time as
// the end of some other duration.
double end_time = event->Time() + event->Duration();
ParamEvent::Type test_type = events_[i]->GetType();
// Events of type |kSetValueCurveEnd| or |kCancelValues| never
// conflict.
if (!(test_type == ParamEvent::kSetValueCurveEnd ||
test_type == ParamEvent::kCancelValues)) {
if (test_type == ParamEvent::kSetValueCurve) {
// A SetValueCurve overlapping an existing SetValueCurve requires
// special care.
double test_end_time = events_[i]->Time() + events_[i]->Duration();
// Test if old event starts somewhere in the middle of the new event.
bool overlap = (events_[i]->Time() >= event->Time() &&
events_[i]->Time() < end_time);
// Test if old event ends somewhere in the middle of the new event.
overlap = overlap ||
(test_end_time > event->Time() && test_end_time < end_time);
// Test if new event starts somewhere in the middle of the old event.
overlap = overlap || (event->Time() >= events_[i]->Time() &&
event->Time() < test_end_time);
// Test if new event ends somewhere in the middle of the old event.
overlap = overlap || (end_time >= events_[i]->Time() &&
end_time < test_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,
EventToString(*event) + " overlaps " +
EventToString(*events_[i]));
return;
}
} else {
if (events_[i]->Time() > event->Time() &&
events_[i]->Time() < end_time) {
exception_state.ThrowDOMException(
DOMExceptionCode::kNotSupportedError,
EventToString(*event) + " overlaps " +
EventToString(*events_[i]));
return;
}
}
}
} else {
// Otherwise, make sure this event doesn't overlap any existing
// SetValueCurve event.
if (events_[i]->GetType() == ParamEvent::kSetValueCurve) {
double end_time = events_[i]->Time() + events_[i]->Duration();
if (event->GetType() != ParamEvent::kSetValueCurveEnd &&
event->Time() >= events_[i]->Time() && event->Time() < end_time) {
exception_state.ThrowDOMException(
DOMExceptionCode::kNotSupportedError,
EventToString(*event) + " overlaps " +
EventToString(*events_[i]));
return;
}
}
}
// Overwrite same event type and time.
if (events_[i]->Time() == insert_time &&
events_[i]->GetType() == event->GetType()) {
// Be sure to remove the old event from |new_events_| too, in
// case it was just added.
if (new_events_.Contains(events_[i].get())) {
new_events_.erase(events_[i].get());
}
events_[i] = std::move(event);
new_events_.insert(events_[i].get());
return;
}
if (events_[i]->Time() > insert_time)
break;
}
events_.insert(i, std::move(event));
new_events_.insert(events_[i].get());
}
bool AudioParamTimeline::HasValues(size_t current_frame,
double sample_rate) const {
MutexTryLocker try_locker(events_lock_);
if (try_locker.Locked()) {
if (events_.size() == 0)
return false;
switch (events_[0]->GetType()) {
case ParamEvent::kSetValue:
case ParamEvent::kSetValueCurve:
case ParamEvent::kSetTarget:
// Need automation if the event starts somewhere before the
// end of the current render quantum.
return events_[0]->Time() <=
(current_frame + audio_utilities::kRenderQuantumFrames) /
sample_rate;
default:
// Otherwise, there's some kind of other event running, so we
// need to do automation.
return true;
}
}
// 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 AudioParamTimeline::CancelScheduledValues(
double start_time,
ExceptionState& exception_state) {
DCHECK(IsMainThread());
MutexLocker locker(events_lock_);
// Remove all events starting at startTime.
for (wtf_size_t i = 0; i < events_.size(); ++i) {
if (events_[i]->Time() >= start_time) {
RemoveCancelledEvents(i);
break;
}
}
}
void AudioParamTimeline::CancelAndHoldAtTime(double cancel_time,
ExceptionState& exception_state) {
DCHECK(IsMainThread());
if (!IsNonNegativeAudioParamTime(cancel_time, exception_state))
return;
MutexLocker locker(events_lock_);
wtf_size_t i;
// Find the first event at or just past cancelTime.
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
// cancelTime, if any.
wtf_size_t cancelled_event_index = i;
// If the event just before cancelTime 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::kSetTarget) ||
(events_[i - 1]->GetType() == ParamEvent::kSetValueCurve))) {
cancelled_event_index = i - 1;
} else if (i >= events_.size()) {
// If there were no events occurring after |cancelTime| (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 = nullptr;
std::unique_ptr<ParamEvent> new_set_value_event = nullptr;
switch (event_type) {
case ParamEvent::kLinearRampToValue:
case ParamEvent::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::kSetTarget: {
// Don't want to remove the SetTarget event, 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::kSetValueCurve: {
double new_duration = cancel_time - cancelled_event->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.
float end_value = ValueCurveAtTime(
cancel_time, cancelled_event->Time(), cancelled_event->Duration(),
cancelled_event->Curve().data(), cancelled_event->Curve().size());
// 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::kSetValue:
case ParamEvent::kSetValueCurveEnd:
case ParamEvent::kCancelValues:
// Nothing needs to be done for a SetValue or CancelValues event.
break;
case ParamEvent::kLastType:
NOTREACHED();
break;
}
// 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);
}
}
float AudioParamTimeline::ValueForContextTime(
AudioDestinationHandler& audio_destination,
float default_value,
bool& has_value,
float min_value,
float max_value) {
{
MutexTryLocker try_locker(events_lock_);
if (!try_locker.Locked() || !events_.size() ||
audio_destination.CurrentTime() < events_[0]->Time()) {
has_value = false;
return 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 / audio_utilities::kRenderQuantumFrames;
value =
ValuesForFrameRange(start_frame, start_frame + 1, default_value, &value,
1, sample_rate, control_rate, min_value, max_value);
has_value = true;
return value;
}
float AudioParamTimeline::ValuesForFrameRange(size_t start_frame,
size_t end_frame,
float default_value,
float* values,
unsigned number_of_values,
double sample_rate,
double control_rate,
float min_value,
float max_value) {
// We can't contend the lock in the realtime audio thread.
MutexTryLocker try_locker(events_lock_);
if (!try_locker.Locked()) {
if (values) {
for (unsigned i = 0; i < number_of_values; ++i)
values[i] = default_value;
}
return default_value;
}
float last_value =
ValuesForFrameRangeImpl(start_frame, end_frame, default_value, values,
number_of_values, sample_rate, control_rate);
// Clamp the values now to the nominal range
vector_math::Vclip(values, 1, &min_value, &max_value, values, 1,
number_of_values);
return last_value;
}
float AudioParamTimeline::ValuesForFrameRangeImpl(size_t start_frame,
size_t end_frame,
float default_value,
float* values,
unsigned number_of_values,
double sample_rate,
double control_rate) {
DCHECK(values);
DCHECK_GE(number_of_values, 1u);
if (!values || !(number_of_values >= 1))
return default_value;
// Return default value if there are no events matching the desired time
// range.
if (!events_.size() || (end_frame / sample_rate <= events_[0]->Time())) {
FillWithDefault(values, default_value, number_of_values, 0);
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,
number_of_values, values))
return default_value;
}
// Maintain a running time (frame) and index for writing the values buffer.
size_t current_frame = start_frame;
unsigned write_index = 0;
// If first event is after startFrame then fill initial part of values buffer
// with defaultValue until we reach the first event time.
std::tie(current_frame, write_index) =
HandleFirstEvent(values, default_value, number_of_values, start_frame,
end_frame, sample_rate, current_frame, write_index);
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 < number_of_values; ++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.
ParamEvent::Type next_event_type =
next_event ? static_cast<ParamEvent::Type>(next_event->GetType())
: ParamEvent::kLastType;
ProcessSetTargetFollowedByRamp(i, event, next_event_type, current_frame,
sample_rate, control_rate, value);
float value1 = event->Value();
double time1 = event->Time();
float value2 = next_event ? next_event->Value() : value1;
double time2 =
next_event ? next_event->Time() : end_frame / sample_rate + 1;
// Check to see if an event was cancelled.
std::tie(value2, time2, next_event_type) =
HandleCancelValues(event, next_event, value2, time2);
DCHECK_GE(time2, time1);
// |fillToEndFrame| is the exclusive upper bound of the last frame to be
// computed for this event. It's either the last desired frame (|endFrame|)
// or derived from the end time of the next event (time2). We compute
// ceil(time2*sampleRate) because fillToEndFrame is the exclusive upper
// bound. Consider the case where |startFrame| = 128 and time2 = 128.1
// (assuming sampleRate = 1). Since time2 is greater than 128, we want to
// output a value for frame 128. This requires that fillToEndFrame 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.
size_t fill_to_end_frame = end_frame;
if (end_frame > time2 * sample_rate)
fill_to_end_frame = static_cast<size_t>(ceil(time2 * sample_rate));
DCHECK_GE(fill_to_end_frame, start_frame);
unsigned fill_to_frame =
static_cast<unsigned>(fill_to_end_frame - start_frame);
fill_to_frame = std::min(fill_to_frame, number_of_values);
const AutomationState current_state = {
number_of_values,
start_frame,
end_frame,
sample_rate,
control_rate,
fill_to_frame,
fill_to_end_frame,
value1,
time1,
value2,
time2,
event,
i,
};
// First handle linear and exponential ramps which require looking ahead to
// the next event.
if (next_event_type == ParamEvent::kLinearRampToValue) {
std::tie(current_frame, value, write_index) = ProcessLinearRamp(
current_state, values, current_frame, value, write_index);
} else if (next_event_type == ParamEvent::kExponentialRampToValue) {
std::tie(current_frame, value, write_index) = ProcessExponentialRamp(
current_state, values, current_frame, value, write_index);
} else {
// Handle event types not requiring looking ahead to the next event.
switch (event->GetType()) {
case ParamEvent::kSetValue:
case ParamEvent::kSetValueCurveEnd:
case ParamEvent::kLinearRampToValue: {
current_frame = fill_to_end_frame;
// Simply stay at a constant value.
value = event->Value();
write_index =
FillWithDefault(values, value, fill_to_frame, write_index);
break;
}
case ParamEvent::kCancelValues: {
std::tie(current_frame, value, write_index) = ProcessCancelValues(
current_state, values, current_frame, value, write_index);
break;
}
case ParamEvent::kExponentialRampToValue: {
current_frame = fill_to_end_frame;
// If we're here, we've reached the end of the ramp. If we can
// (because the start and end values have the same sign, and neither
// is 0), use the actual end value. If not, we have to propagate
// whatever we have.
if (i >= 1 && ((events_[i - 1]->Value() * event->Value()) > 0))
value = event->Value();
// Simply stay at a constant value from the last time. We don't want
// to use the value of the event in case value1 * value2 < 0. In this
// case we should propagate the previous value, which is in |value|.
write_index =
FillWithDefault(values, value, fill_to_frame, write_index);
break;
}
case ParamEvent::kSetTarget: {
std::tie(current_frame, value, write_index) = ProcessSetTarget(
current_state, values, current_frame, value, write_index);
break;
}
case ParamEvent::kSetValueCurve: {
std::tie(current_frame, value, write_index) = ProcessSetValueCurve(
current_state, values, current_frame, value, write_index);
break;
}
case ParamEvent::kLastType:
NOTREACHED();
break;
}
}
}
// 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);
events_.EraseAt(0, 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.
write_index = FillWithDefault(values, value, number_of_values, write_index);
// This value is used to set the .value attribute of the AudioParam. it
// should be the last computed value.
return values[number_of_values - 1];
}
std::tuple<size_t, unsigned> AudioParamTimeline::HandleFirstEvent(
float* values,
float default_value,
unsigned number_of_values,
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) {
// |fillToFrame| is an exclusive upper bound, so use ceil() to compute the
// bound from the firstEventTime.
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);
unsigned fill_to_frame =
static_cast<unsigned>(fill_to_end_frame - start_frame);
fill_to_frame = std::min(fill_to_frame, number_of_values);
write_index =
FillWithDefault(values, default_value, fill_to_frame, write_index);
current_frame += fill_to_frame;
}
return std::make_tuple(current_frame, write_index);
}
bool AudioParamTimeline::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 nextEvent->time() is
// just larger than currentFrame/sampleRate. 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 currentTime/sampleRate. 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 curentFrame (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::kSetValue ||
event->GetType() == ParamEvent::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 AudioParamTimeline::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::EventPreceeds);
}
new_events_.clear();
}
// 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.
static 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 betwenn |value|
// and |target| is small. That is |target-value|/|target| <
// |kSetTargetThreshold|.
if (target != 0 && fabs(target - value) < kSetTargetThreshold * fabs(value)) {
return true;
}
return false;
}
bool AudioParamTimeline::HandleAllEventsInThePast(double current_time,
double sample_rate,
float& default_value,
unsigned number_of_values,
float* values) {
// 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 * audio_utilities::kRenderQuantumFrames / 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::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 |defaultValue| has the expected
// value.
FillWithDefault(values, default_value, number_of_values, 0);
smoothed_value_ = default_value;
events_.clear();
return true;
}
return false;
}
void AudioParamTimeline::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::kSetTarget &&
(next_event_type == ParamEvent::kLinearRampToValue ||
next_event_type == ParamEvent::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()) *
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, AudioParamTimeline::ParamEvent::Type>
AudioParamTimeline::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::kLastType;
if (next_event && next_event->GetType() == ParamEvent::kCancelValues &&
next_event->SavedEvent()) {
float value1 = current_event->Value();
double time1 = current_event->Time();
switch (current_event->GetType()) {
case ParamEvent::kLinearRampToValue:
case ParamEvent::kExponentialRampToValue:
case ParamEvent::kSetValueCurveEnd:
case ParamEvent::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::kLinearRampToValue:
value2 =
LinearRampAtTime(next_event->Time(), value1, time1,
saved_event->Value(), saved_event->Time());
break;
case ParamEvent::kExponentialRampToValue:
value2 = ExponentialRampAtTime(next_event->Time(), value1, time1,
saved_event->Value(),
saved_event->Time());
break;
case ParamEvent::kSetValueCurve:
case ParamEvent::kSetValueCurveEnd:
case ParamEvent::kSetValue:
case ParamEvent::kSetTarget:
case ParamEvent::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();
break;
case ParamEvent::kLastType:
// Illegal event type.
NOTREACHED();
break;
}
// Cache the new value so we don't keep computing it over and over.
next_event->SetCancelledValue(value2);
}
} break;
case ParamEvent::kSetValueCurve:
// Everything needed for this was handled when cancelling was
// done.
break;
case ParamEvent::kSetTarget:
case ParamEvent::kCancelValues:
// Nothing special needs to be done for SetTarget or
// CancelValues followed by CancelValues.
break;
case ParamEvent::kLastType:
NOTREACHED();
break;
}
}
return std::make_tuple(value2, time2, next_event_type);
}
std::tuple<size_t, float, unsigned> AudioParamTimeline::ProcessLinearRamp(
const AutomationState& current_state,
float* values,
size_t current_frame,
float value,
unsigned write_index) {
#if defined(ARCH_CPU_X86_FAMILY)
auto number_of_values = current_state.number_of_values;
#endif
auto fill_to_frame = current_state.fill_to_frame;
auto time1 = current_state.time1;
auto time2 = current_state.time2;
auto value1 = current_state.value1;
auto value2 = current_state.value2;
auto sample_rate = current_state.sample_rate;
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;
// Compute final time.
DCHECK_LE(fill_to_frame_trunc, number_of_values);
current_frame += fill_to_frame_trunc - write_index;
// Process 4 loop steps.
for (; write_index < fill_to_frame_trunc; write_index += 4) {
_mm_storeu_ps(values + 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
// Serially process remaining values.
for (; write_index < fill_to_frame; ++write_index) {
float x = (current_frame / sample_rate - time1) * k;
// value = (1 - x) * value1 + x * value2;
value = value1 + x * value_delta;
values[write_index] = value;
++current_frame;
}
return std::make_tuple(current_frame, value, write_index);
}
std::tuple<size_t, float, unsigned> AudioParamTimeline::ProcessExponentialRamp(
const AutomationState& current_state,
float* values,
size_t current_frame,
float value,
unsigned write_index) {
auto fill_to_frame = current_state.fill_to_frame;
auto time1 = current_state.time1;
auto time2 = current_state.time2;
auto value1 = current_state.value1;
auto value2 = current_state.value2;
auto sample_rate = current_state.sample_rate;
if (value1 * value2 <= 0) {
// It's an error if value1 and value2 have opposite signs or if one of
// them is zero. Handle this by propagating the previous value, and
// making it the default.
value = value1;
for (; write_index < fill_to_frame; ++write_index)
values[write_index] = value;
} 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.
float multiplier = powf(value2 / value1, 1 / 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 * pow(value2 / static_cast<double>(value1),
(current_frame / sample_rate - time1) / delta_time);
for (; write_index < fill_to_frame; ++write_index) {
values[write_index] = value;
value *= multiplier;
++current_frame;
}
// |value| got updated one extra time in the above loop. Restore it to
// the last computed value.
if (write_index >= 1)
value /= multiplier;
// 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> AudioParamTimeline::ProcessSetTarget(
const AutomationState& current_state,
float* values,
size_t current_frame,
float value,
unsigned write_index) {
#if defined(ARCH_CPU_X86_FAMILY)
auto number_of_values = current_state.number_of_values;
#endif
auto fill_to_frame = current_state.fill_to_frame;
auto time1 = current_state.time1;
auto value1 = current_state.value1;
auto sample_rate = current_state.sample_rate;
auto control_rate = current_state.control_rate;
auto fill_to_end_frame = current_state.fill_to_end_frame;
auto* event = current_state.event;
// 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) *
exp(-(current_frame / sample_rate - time1) / time_constant);
} else {
// Otherwise, need to compute a new value bacause |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)) {
for (; write_index < fill_to_frame; ++write_index)
values[write_index] = target;
} 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;
DCHECK_LE(fill_to_frame_trunc, number_of_values);
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));
_mm_storeu_ps(values + write_index, v_result);
// Update value for next iteration.
value += delta * c3;
}
}
#endif
// Serially process remaining values
for (; write_index < fill_to_frame; ++write_index) {
values[write_index] = value;
value += (target - value) * discrete_time_constant;
}
// 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> AudioParamTimeline::ProcessSetValueCurve(
const AutomationState& current_state,
float* values,
size_t current_frame,
float value,
unsigned write_index) {
auto number_of_values = current_state.number_of_values;
auto fill_to_frame = current_state.fill_to_frame;
auto time1 = current_state.time1;
auto sample_rate = current_state.sample_rate;
auto start_frame = current_state.start_frame;
auto end_frame = current_state.end_frame;
auto fill_to_end_frame = current_state.fill_to_end_frame;
auto* event = current_state.event;
const Vector<float> curve = event->Curve();
const float* curve_data = curve.data();
unsigned number_of_curve_points = curve.size();
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 (!number_of_curve_points || duration <= 0 || sample_rate <= 0) {
// Error condition - simply propagate previous value.
current_frame = fill_to_end_frame;
for (; write_index < fill_to_frame; ++write_index)
values[write_index] = value;
return std::make_tuple(current_frame, value, write_index);
}
// 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;
}
// |fillToFrame| can be less than |startFrame| when the end of the
// setValueCurve automation has been reached, but the next automation
// has not yet started. In this case, |fillToFrame| is clipped to
// |time1|+|duration| above, but |startFrame| 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, number_of_values);
// 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(number_of_curve_points - 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;
DCHECK_LE(fill_to_frame_trunc, number_of_values);
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 currentVirtualIndex can exceed
// curveIndex0 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((__m128i*)a_curve_index0, v_curve_index0);
_mm_storeu_si128((__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));
_mm_storeu_ps(values + write_index, v_value);
}
// Pass along k to the serial loop.
k = truncated_steps;
}
if (write_index >= 1)
value = values[write_index - 1];
#endif
for (; write_index < fill_to_frame; ++write_index, ++k) {
// 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;
unsigned curve_index0;
// Clamp index to the last element of the array.
if (current_virtual_index < number_of_curve_points) {
curve_index0 = static_cast<unsigned>(current_virtual_index);
} else {
curve_index0 = number_of_curve_points - 1;
}
unsigned curve_index1 =
std::min(curve_index0 + 1, number_of_curve_points - 1);
// Linearly interpolate between the two nearest curve points.
// |delta| is clamped to 1 because currentVirtualIndex can exceed
// curveIndex0 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, number_of_curve_points);
DCHECK_LT(curve_index1, number_of_curve_points);
float c0 = curve_data[curve_index0];
float c1 = curve_data[curve_index1];
double delta = std::min(current_virtual_index - curve_index0, 1.0);
value = c0 + (c1 - c0) * delta;
values[write_index] = value;
}
// 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
// curveData. Don't modify |value| unless there is time left.
if (write_index < next_event_fill_to_frame) {
value = curve_end_value;
for (; write_index < next_event_fill_to_frame; ++write_index)
values[write_index] = value;
}
// 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> AudioParamTimeline::ProcessCancelValues(
const AutomationState& current_state,
float* values,
size_t current_frame,
float value,
unsigned write_index) {
auto fill_to_frame = current_state.fill_to_frame;
auto time1 = current_state.time1;
auto sample_rate = current_state.sample_rate;
auto control_rate = current_state.control_rate;
auto fill_to_end_frame = current_state.fill_to_end_frame;
auto* event = current_state.event;
auto event_index = current_state.event_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::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.
for (; write_index < fill_to_frame; ++write_index)
values[write_index] = value;
current_frame = fill_to_end_frame;
return std::make_tuple(current_frame, value, write_index);
}
uint32_t AudioParamTimeline::FillWithDefault(float* values,
float default_value,
uint32_t end_frame,
uint32_t write_index) {
uint32_t index = write_index;
for (; index < end_frame; ++index)
values[index] = default_value;
return index;
}
void AudioParamTimeline::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());
}
}
// Now we can remove the cancelled events from the list.
events_.EraseAt(first_event_to_remove,
events_.size() - first_event_to_remove);
}
} // namespace blink