Index: third_party/WebKit/Source/modules/webaudio/AudioParamTimeline.cpp |
diff --git a/third_party/WebKit/Source/modules/webaudio/AudioParamTimeline.cpp b/third_party/WebKit/Source/modules/webaudio/AudioParamTimeline.cpp |
index 189aaa81c620a1b06e4c053aea7ee89238244611..56101277bba75f5a853e0490c01e64b37e29a44b 100644 |
--- a/third_party/WebKit/Source/modules/webaudio/AudioParamTimeline.cpp |
+++ b/third_party/WebKit/Source/modules/webaudio/AudioParamTimeline.cpp |
@@ -29,6 +29,7 @@ |
#include "platform/audio/AudioUtilities.h" |
#include "wtf/CPU.h" |
#include "wtf/MathExtras.h" |
+#include "wtf/PtrUtil.h" |
#include <algorithm> |
#if CPU(X86) || CPU(X86_64) |
@@ -106,6 +107,9 @@ String AudioParamTimeline::eventToString(const ParamEvent& event) { |
args = "..., " + String::number(event.time(), 16) + ", " + |
String::number(event.duration(), 16); |
break; |
+ case ParamEvent::CancelValues: |
+ // Fall through; we should never have to print out the internal |
+ // CancelValues event. |
case ParamEvent::LastType: |
ASSERT_NOT_REACHED(); |
break; |
@@ -114,54 +118,77 @@ String AudioParamTimeline::eventToString(const ParamEvent& event) { |
return s + "(" + args + ")"; |
} |
-AudioParamTimeline::ParamEvent::ParamEvent(Type type, |
- float value, |
- double time, |
- double timeConstant, |
+// 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 timeConstant) { |
+ return value2 + (value1 - value2) * exp(-(t - time1) / timeConstant); |
+} |
+ |
+// 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 DOMFloat32Array* curve, |
- float initialValue, |
- double callTime) |
- : m_type(type), |
- m_value(value), |
- m_time(time), |
- m_timeConstant(timeConstant), |
- m_duration(duration), |
- m_initialValue(initialValue), |
- m_callTime(callTime), |
- m_needsTimeClampCheck(true) { |
- if (curve) { |
- // Copy the curve data |
- unsigned curveLength = curve->length(); |
- m_curve.resize(curveLength); |
- memcpy(m_curve.data(), curve->data(), curveLength * sizeof(float)); |
- } |
+ const float* curveData, |
+ unsigned curveLength) { |
+ double curveIndex = (curveLength - 1) / duration * (t - time1); |
+ unsigned k = std::min(static_cast<unsigned>(curveIndex), curveLength - 1); |
+ unsigned k1 = std::min(k + 1, curveLength - 1); |
+ float c0 = curveData[k]; |
+ float c1 = curveData[k1]; |
+ float delta = std::min(curveIndex - k, 1.0); |
+ |
+ return c0 + (c1 - c0) * delta; |
} |
-AudioParamTimeline::ParamEvent |
+std::unique_ptr<AudioParamTimeline::ParamEvent> |
AudioParamTimeline::ParamEvent::createSetValueEvent(float value, double time) { |
- return ParamEvent(ParamEvent::SetValue, value, time, 0, 0, nullptr); |
+ return WTF::wrapUnique(new ParamEvent(ParamEvent::SetValue, value, time)); |
} |
-AudioParamTimeline::ParamEvent |
+std::unique_ptr<AudioParamTimeline::ParamEvent> |
AudioParamTimeline::ParamEvent::createLinearRampEvent(float value, |
double time, |
float initialValue, |
double callTime) { |
- return ParamEvent(ParamEvent::LinearRampToValue, value, time, 0, 0, nullptr, |
- initialValue, callTime); |
+ return WTF::wrapUnique(new ParamEvent(ParamEvent::LinearRampToValue, value, |
+ time, initialValue, callTime)); |
} |
-AudioParamTimeline::ParamEvent |
+std::unique_ptr<AudioParamTimeline::ParamEvent> |
AudioParamTimeline::ParamEvent::createExponentialRampEvent(float value, |
double time, |
float initialValue, |
double callTime) { |
- return ParamEvent(ParamEvent::ExponentialRampToValue, value, time, 0, 0, |
- nullptr, initialValue, callTime); |
+ return WTF::wrapUnique(new ParamEvent(ParamEvent::ExponentialRampToValue, |
+ value, time, initialValue, callTime)); |
} |
-AudioParamTimeline::ParamEvent |
+std::unique_ptr<AudioParamTimeline::ParamEvent> |
AudioParamTimeline::ParamEvent::createSetTargetEvent(float value, |
double time, |
double timeConstant) { |
@@ -169,16 +196,211 @@ AudioParamTimeline::ParamEvent::createSetTargetEvent(float value, |
// returns NaN or Infinity due to division by zero. The caller |
// should have converted this to a SetValueEvent. |
DCHECK_NE(timeConstant, 0); |
- return ParamEvent(ParamEvent::SetTarget, value, time, timeConstant, 0, |
- nullptr); |
+ return WTF::wrapUnique( |
+ new ParamEvent(ParamEvent::SetTarget, value, time, timeConstant)); |
} |
-AudioParamTimeline::ParamEvent |
+std::unique_ptr<AudioParamTimeline::ParamEvent> |
AudioParamTimeline::ParamEvent::createSetValueCurveEvent( |
const DOMFloat32Array* curve, |
double time, |
double duration) { |
- return ParamEvent(ParamEvent::SetValueCurve, 0, time, 0, duration, curve); |
+ double curvePoints = (curve->length() - 1) / duration; |
+ float endValue = curve->data()[curve->length() - 1]; |
+ |
+ return WTF::wrapUnique(new ParamEvent( |
+ ParamEvent::SetValueCurve, time, duration, curve, curvePoints, endValue)); |
+} |
+ |
+std::unique_ptr<AudioParamTimeline::ParamEvent> |
+AudioParamTimeline::ParamEvent::createCancelValuesEvent( |
+ double time, |
+ std::unique_ptr<ParamEvent> savedEvent) { |
+ if (savedEvent) { |
+ // The savedEvent can only have certain event types. Verify that. |
+ ParamEvent::Type savedType = savedEvent->getType(); |
+ |
+ DCHECK_NE(savedType, ParamEvent::LastType); |
+ DCHECK(savedType == ParamEvent::LinearRampToValue || |
+ savedType == ParamEvent::ExponentialRampToValue || |
+ savedType == ParamEvent::SetValueCurve); |
+ } |
+ |
+ return WTF::wrapUnique( |
+ new ParamEvent(ParamEvent::CancelValues, time, std::move(savedEvent))); |
+} |
+ |
+std::unique_ptr<AudioParamTimeline::ParamEvent> |
+AudioParamTimeline::ParamEvent::createGeneralEvent( |
+ Type type, |
+ float value, |
+ double time, |
+ float initialValue, |
+ double callTime, |
+ double timeConstant, |
+ double duration, |
+ Vector<float>& curve, |
+ double curvePointsPerSecond, |
+ float curveEndValue, |
+ std::unique_ptr<ParamEvent> savedEvent) { |
+ return WTF::wrapUnique(new ParamEvent( |
+ type, value, time, initialValue, callTime, timeConstant, duration, curve, |
+ curvePointsPerSecond, curveEndValue, std::move(savedEvent))); |
+} |
+ |
+AudioParamTimeline::ParamEvent* AudioParamTimeline::ParamEvent::savedEvent() |
+ const { |
+ DCHECK(getType() == ParamEvent::CancelValues); |
+ return m_savedEvent.get(); |
+} |
+ |
+bool AudioParamTimeline::ParamEvent::hasDefaultCancelledValue() const { |
+ DCHECK(getType() == ParamEvent::CancelValues); |
+ return m_hasDefaultCancelledValue; |
+} |
+ |
+void AudioParamTimeline::ParamEvent::setCancelledValue(float value) { |
+ DCHECK(getType() == ParamEvent::CancelValues); |
+ m_value = value; |
+ m_hasDefaultCancelledValue = true; |
+} |
+ |
+// General event |
+AudioParamTimeline::ParamEvent::ParamEvent( |
+ ParamEvent::Type type, |
+ float value, |
+ double time, |
+ float initialValue, |
+ double callTime, |
+ double timeConstant, |
+ double duration, |
+ Vector<float>& curve, |
+ double curvePointsPerSecond, |
+ float curveEndValue, |
+ std::unique_ptr<ParamEvent> savedEvent) |
+ : m_type(type), |
+ m_value(value), |
+ m_time(time), |
+ m_initialValue(initialValue), |
+ m_callTime(callTime), |
+ m_timeConstant(timeConstant), |
+ m_duration(duration), |
+ m_curvePointsPerSecond(curvePointsPerSecond), |
+ m_curveEndValue(curveEndValue), |
+ m_savedEvent(std::move(savedEvent)), |
+ m_needsTimeClampCheck(true), |
+ m_hasDefaultCancelledValue(false) { |
+ m_curve = curve; |
+} |
+ |
+// Create simplest event needing just a value and time, like setValueAtTime |
+AudioParamTimeline::ParamEvent::ParamEvent(ParamEvent::Type type, |
+ float value, |
+ double time) |
+ : m_type(type), |
+ m_value(value), |
+ m_time(time), |
+ m_initialValue(0), |
+ m_callTime(0), |
+ m_timeConstant(0), |
+ m_duration(0), |
+ m_curvePointsPerSecond(0), |
+ m_curveEndValue(0), |
+ m_savedEvent(nullptr), |
+ m_needsTimeClampCheck(true), |
+ m_hasDefaultCancelledValue(false) { |
+ DCHECK_EQ(type, ParamEvent::SetValue); |
+} |
+ |
+// 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 initialValue, |
+ double callTime) |
+ : m_type(type), |
+ m_value(value), |
+ m_time(time), |
+ m_initialValue(initialValue), |
+ m_callTime(callTime), |
+ m_timeConstant(0), |
+ m_duration(0), |
+ m_curvePointsPerSecond(0), |
+ m_curveEndValue(0), |
+ m_savedEvent(nullptr), |
+ m_needsTimeClampCheck(true), |
+ m_hasDefaultCancelledValue(false) { |
+ DCHECK(type == ParamEvent::LinearRampToValue || |
+ type == ParamEvent::ExponentialRampToValue); |
+} |
+ |
+// Create an event needing a time constant (setTargetAtTime) |
+AudioParamTimeline::ParamEvent::ParamEvent(ParamEvent::Type type, |
+ float value, |
+ double time, |
+ double timeConstant) |
+ : m_type(type), |
+ m_value(value), |
+ m_time(time), |
+ m_initialValue(0), |
+ m_callTime(0), |
+ m_timeConstant(timeConstant), |
+ m_duration(0), |
+ m_curvePointsPerSecond(0), |
+ m_curveEndValue(0), |
+ m_savedEvent(nullptr), |
+ m_needsTimeClampCheck(true), |
+ m_hasDefaultCancelledValue(false) { |
+ DCHECK_EQ(type, ParamEvent::SetTarget); |
+} |
+ |
+// Create a setValueCurve event |
+AudioParamTimeline::ParamEvent::ParamEvent(ParamEvent::Type type, |
+ double time, |
+ double duration, |
+ const DOMFloat32Array* curve, |
+ double curvePointsPerSecond, |
+ float curveEndValue) |
+ : m_type(type), |
+ m_value(0), |
+ m_time(time), |
+ m_initialValue(0), |
+ m_callTime(0), |
+ m_timeConstant(0), |
+ m_duration(duration), |
+ m_curvePointsPerSecond(curvePointsPerSecond), |
+ m_curveEndValue(curveEndValue), |
+ m_savedEvent(nullptr), |
+ m_needsTimeClampCheck(true), |
+ m_hasDefaultCancelledValue(false) { |
+ DCHECK_EQ(type, ParamEvent::SetValueCurve); |
+ if (curve) { |
+ unsigned curveLength = curve->length(); |
+ m_curve.resize(curve->length()); |
+ memcpy(m_curve.data(), curve->data(), curveLength * sizeof(float)); |
+ } |
+} |
+ |
+// Create CancelValues event |
+AudioParamTimeline::ParamEvent::ParamEvent( |
+ ParamEvent::Type type, |
+ double time, |
+ std::unique_ptr<ParamEvent> savedEvent) |
+ : m_type(type), |
+ m_value(0), |
+ m_time(time), |
+ m_initialValue(0), |
+ m_callTime(0), |
+ m_timeConstant(0), |
+ m_duration(0), |
+ m_curvePointsPerSecond(0), |
+ m_curveEndValue(0), |
+ m_savedEvent(std::move(savedEvent)), |
+ m_needsTimeClampCheck(true), |
+ m_hasDefaultCancelledValue(false) { |
+ DCHECK_EQ(type, ParamEvent::CancelValues); |
} |
void AudioParamTimeline::setValueAtTime(float value, |
@@ -189,6 +411,7 @@ void AudioParamTimeline::setValueAtTime(float value, |
if (!isNonNegativeAudioParamTime(time, exceptionState)) |
return; |
+ MutexLocker locker(m_eventsLock); |
insertEvent(ParamEvent::createSetValueEvent(value, time), exceptionState); |
} |
@@ -203,6 +426,7 @@ void AudioParamTimeline::linearRampToValueAtTime( |
if (!isNonNegativeAudioParamTime(time, exceptionState)) |
return; |
+ MutexLocker locker(m_eventsLock); |
insertEvent( |
ParamEvent::createLinearRampEvent(value, time, initialValue, callTime), |
exceptionState); |
@@ -229,6 +453,7 @@ void AudioParamTimeline::exponentialRampToValueAtTime( |
return; |
} |
+ MutexLocker locker(m_eventsLock); |
insertEvent(ParamEvent::createExponentialRampEvent(value, time, initialValue, |
callTime), |
exceptionState); |
@@ -245,6 +470,8 @@ void AudioParamTimeline::setTargetAtTime(float target, |
"Time constant")) |
return; |
+ MutexLocker locker(m_eventsLock); |
+ |
// If timeConstant = 0, we instantly jump to the target value, so |
// insert a SetValueEvent instead of SetTargetEvent. |
if (timeConstant == 0) { |
@@ -273,6 +500,7 @@ void AudioParamTimeline::setValueCurveAtTime(DOMFloat32Array* curve, |
return; |
} |
+ MutexLocker locker(m_eventsLock); |
insertEvent(ParamEvent::createSetValueCurveEvent(curve, time, duration), |
exceptionState); |
@@ -284,73 +512,73 @@ void AudioParamTimeline::setValueCurveAtTime(DOMFloat32Array* curve, |
exceptionState); |
} |
-void AudioParamTimeline::insertEvent(const ParamEvent& event, |
+void AudioParamTimeline::insertEvent(std::unique_ptr<ParamEvent> event, |
ExceptionState& exceptionState) { |
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 isValid = event.getType() < ParamEvent::LastType && |
- std::isfinite(event.value()) && std::isfinite(event.time()) && |
- std::isfinite(event.timeConstant()) && |
- std::isfinite(event.duration()) && event.duration() >= 0; |
+ bool isValid = event->getType() < ParamEvent::LastType && |
+ std::isfinite(event->value()) && |
+ std::isfinite(event->time()) && |
+ std::isfinite(event->timeConstant()) && |
+ std::isfinite(event->duration()) && event->duration() >= 0; |
DCHECK(isValid); |
if (!isValid) |
return; |
- MutexLocker locker(m_eventsLock); |
- |
unsigned i = 0; |
- double insertTime = event.time(); |
+ double insertTime = event->time(); |
if (!m_events.size() && |
- (event.getType() == ParamEvent::LinearRampToValue || |
- event.getType() == ParamEvent::ExponentialRampToValue)) { |
+ (event->getType() == ParamEvent::LinearRampToValue || |
+ event->getType() == ParamEvent::ExponentialRampToValue)) { |
// There are no events preceding these ramps. Insert a new setValueAtTime |
// event to set the starting point for these events. |
m_events.insert(0, AudioParamTimeline::ParamEvent::createSetValueEvent( |
- event.initialValue(), event.callTime())); |
+ event->initialValue(), event->callTime())); |
} |
for (i = 0; i < m_events.size(); ++i) { |
- if (event.getType() == ParamEvent::SetValueCurve) { |
+ if (event->getType() == ParamEvent::SetValueCurve) { |
// 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 endTime = event.time() + event.duration(); |
- if (m_events[i].time() > event.time() && m_events[i].time() < endTime) { |
+ double endTime = event->time() + event->duration(); |
+ if (m_events[i]->time() > event->time() && |
+ m_events[i]->time() < endTime) { |
exceptionState.throwDOMException( |
NotSupportedError, |
- eventToString(event) + " overlaps " + eventToString(m_events[i])); |
+ eventToString(*event) + " overlaps " + eventToString(*m_events[i])); |
return; |
} |
} else { |
// Otherwise, make sure this event doesn't overlap any existing |
// SetValueCurve event. |
- if (m_events[i].getType() == ParamEvent::SetValueCurve) { |
- double endTime = m_events[i].time() + m_events[i].duration(); |
- if (event.time() >= m_events[i].time() && event.time() < endTime) { |
+ if (m_events[i]->getType() == ParamEvent::SetValueCurve) { |
+ double endTime = m_events[i]->time() + m_events[i]->duration(); |
+ if (event->time() >= m_events[i]->time() && event->time() < endTime) { |
exceptionState.throwDOMException( |
- NotSupportedError, |
- eventToString(event) + " overlaps " + eventToString(m_events[i])); |
+ NotSupportedError, eventToString(*event) + " overlaps " + |
+ eventToString(*m_events[i])); |
return; |
} |
} |
} |
// Overwrite same event type and time. |
- if (m_events[i].time() == insertTime && |
- m_events[i].getType() == event.getType()) { |
- m_events[i] = event; |
+ if (m_events[i]->time() == insertTime && |
+ m_events[i]->getType() == event->getType()) { |
+ m_events[i] = std::move(event); |
return; |
} |
- if (m_events[i].time() > insertTime) |
+ if (m_events[i]->time() > insertTime) |
break; |
} |
- m_events.insert(i, event); |
+ m_events.insert(i, std::move(event)); |
} |
bool AudioParamTimeline::hasValues() const { |
@@ -383,13 +611,131 @@ void AudioParamTimeline::cancelScheduledValues(double startTime, |
// Remove all events starting at startTime. |
for (unsigned i = 0; i < m_events.size(); ++i) { |
- if (m_events[i].time() >= startTime) { |
+ if (m_events[i]->time() >= startTime) { |
m_events.remove(i, m_events.size() - i); |
break; |
} |
} |
} |
+void AudioParamTimeline::cancelValuesAndHoldAtTime( |
+ double cancelTime, |
+ ExceptionState& exceptionState) { |
+ DCHECK(isMainThread()); |
+ |
+ if (!isNonNegativeAudioParamTime(cancelTime, exceptionState)) |
+ return; |
+ |
+ MutexLocker locker(m_eventsLock); |
+ |
+ unsigned i; |
+ // Find the first event at or just past cancelTime. |
+ for (i = 0; i < m_events.size(); ++i) { |
+ if (m_events[i]->time() > cancelTime) { |
+ break; |
+ } |
+ } |
+ |
+ // The event that is being cancelled. This is the event just past |
+ // cancelTime, if any. |
+ unsigned cancelledEventIndex = 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 && ((m_events[i - 1]->getType() == ParamEvent::SetTarget) || |
+ (m_events[i - 1]->getType() == ParamEvent::SetValueCurve))) { |
+ cancelledEventIndex = i - 1; |
+ } else if (i >= m_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* cancelledEvent = m_events[cancelledEventIndex].get(); |
+ ParamEvent::Type eventType = cancelledEvent->getType(); |
+ |
+ // New event to be inserted, if any, and a SetValueEvent if needed. |
+ std::unique_ptr<ParamEvent> newEvent = nullptr; |
+ std::unique_ptr<ParamEvent> newSetValueEvent = nullptr; |
+ |
+ switch (eventType) { |
+ case ParamEvent::LinearRampToValue: |
+ case ParamEvent::ExponentialRampToValue: { |
+ // 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> savedEvent = ParamEvent::createGeneralEvent( |
+ eventType, cancelledEvent->value(), cancelledEvent->time(), |
+ cancelledEvent->initialValue(), cancelledEvent->callTime(), |
+ cancelledEvent->timeConstant(), cancelledEvent->duration(), |
+ cancelledEvent->curve(), cancelledEvent->curvePointsPerSecond(), |
+ cancelledEvent->curveEndValue(), nullptr); |
+ |
+ newEvent = ParamEvent::createCancelValuesEvent(cancelTime, |
+ std::move(savedEvent)); |
+ } break; |
+ case ParamEvent::SetTarget: { |
+ // 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. |
+ ++cancelledEventIndex; |
+ |
+ newEvent = ParamEvent::createCancelValuesEvent(cancelTime, nullptr); |
+ } break; |
+ case ParamEvent::SetValueCurve: { |
+ double newDuration = cancelTime - cancelledEvent->time(); |
+ |
+ if (cancelTime > cancelledEvent->time() + cancelledEvent->duration()) { |
+ // If the cancellation time is past the end of the curve, |
+ // there's nothing to do except remove the following events. |
+ ++cancelledEventIndex; |
+ } 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 endValue = valueCurveAtTime( |
+ cancelTime, cancelledEvent->time(), cancelledEvent->duration(), |
+ cancelledEvent->curve().data(), cancelledEvent->curve().size()); |
+ |
+ // Replace the existing SetValueCurve with this new one that is |
+ // identical except for the duration. |
+ newEvent = ParamEvent::createGeneralEvent( |
+ eventType, cancelledEvent->value(), cancelledEvent->time(), |
+ cancelledEvent->initialValue(), cancelledEvent->callTime(), |
+ cancelledEvent->timeConstant(), newDuration, |
+ cancelledEvent->curve(), cancelledEvent->curvePointsPerSecond(), |
+ endValue, nullptr); |
+ |
+ newSetValueEvent = ParamEvent::createSetValueEvent( |
+ endValue, cancelledEvent->time() + newDuration); |
+ } |
+ } break; |
+ case ParamEvent::SetValue: |
+ case ParamEvent::CancelValues: |
+ // Nothing needs to be done for a SetValue or CancelValues event. |
+ break; |
+ case ParamEvent::LastType: |
+ NOTREACHED(); |
+ break; |
+ } |
+ |
+ // Now remove all the following events from the timeline. |
+ if (cancelledEventIndex < m_events.size()) |
+ m_events.remove(cancelledEventIndex, m_events.size() - cancelledEventIndex); |
+ |
+ // Insert the new event, if any. |
+ if (newEvent) { |
+ insertEvent(std::move(newEvent), exceptionState); |
+ if (newSetValueEvent) |
+ insertEvent(std::move(newSetValueEvent), exceptionState); |
+ } |
+} |
+ |
float AudioParamTimeline::valueForContextTime( |
AudioDestinationHandler& audioDestination, |
float defaultValue, |
@@ -399,7 +745,7 @@ float AudioParamTimeline::valueForContextTime( |
{ |
MutexTryLocker tryLocker(m_eventsLock); |
if (!tryLocker.locked() || !m_events.size() || |
- audioDestination.currentTime() < m_events[0].time()) { |
+ audioDestination.currentTime() < m_events[0]->time()) { |
hasValue = false; |
return defaultValue; |
} |
@@ -462,7 +808,7 @@ float AudioParamTimeline::valuesForFrameRangeImpl(size_t startFrame, |
// Return default value if there are no events matching the desired time |
// range. |
- if (!m_events.size() || (endFrame / sampleRate <= m_events[0].time())) { |
+ if (!m_events.size() || (endFrame / sampleRate <= m_events[0]->time())) { |
for (unsigned i = 0; i < numberOfValues; ++i) |
values[i] = defaultValue; |
return defaultValue; |
@@ -476,25 +822,25 @@ float AudioParamTimeline::valuesForFrameRangeImpl(size_t startFrame, |
// Look at all the events in the timeline and check to see if any needs |
// to clamp the start time to the current time. |
for (int k = 0; k < numberOfEvents; ++k) { |
- ParamEvent& event = m_events[k]; |
+ ParamEvent* event = m_events[k].get(); |
// We're examining the event for the first time and the event time is |
// in the past so clamp the event time to the current time (start of |
// the rendering quantum). |
- if (event.needsTimeClampCheck()) { |
- if (event.time() < currentTime) |
- event.setTime(currentTime); |
+ if (event->needsTimeClampCheck()) { |
+ if (event->time() < currentTime) |
+ event->setTime(currentTime); |
// In all cases, we can clear the flag because the event is either |
// in the future, or we've already checked it (just now). |
- event.clearTimeClampCheck(); |
+ event->clearTimeClampCheck(); |
} |
} |
// Optimize the case where the last event is in the past. |
- ParamEvent& lastEvent = m_events[m_events.size() - 1]; |
- ParamEvent::Type lastEventType = lastEvent.getType(); |
- double lastEventTime = lastEvent.time(); |
+ ParamEvent* lastEvent = m_events[m_events.size() - 1].get(); |
+ ParamEvent::Type lastEventType = lastEvent->getType(); |
+ double lastEventTime = lastEvent->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 |
@@ -520,7 +866,7 @@ float AudioParamTimeline::valuesForFrameRangeImpl(size_t startFrame, |
// If first event is after startFrame then fill initial part of values buffer |
// with defaultValue until we reach the first event time. |
- double firstEventTime = m_events[0].time(); |
+ double firstEventTime = m_events[0]->time(); |
if (firstEventTime > startFrame / sampleRate) { |
// |fillToFrame| is an exclusive upper bound, so use ceil() to compute the |
// bound from the firstEventTime. |
@@ -544,8 +890,8 @@ float AudioParamTimeline::valuesForFrameRangeImpl(size_t startFrame, |
// stopping when we've rendered all the requested values. |
int lastSkippedEventIndex = 0; |
for (int i = 0; i < numberOfEvents && writeIndex < numberOfValues; ++i) { |
- ParamEvent& event = m_events[i]; |
- ParamEvent* nextEvent = i < numberOfEvents - 1 ? &(m_events[i + 1]) : 0; |
+ ParamEvent* event = m_events[i].get(); |
+ ParamEvent* nextEvent = i < numberOfEvents - 1 ? m_events[i + 1].get() : 0; |
// Wait until we get a more recent event. |
// |
@@ -568,12 +914,12 @@ float AudioParamTimeline::valuesForFrameRangeImpl(size_t startFrame, |
// 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 eventFrame = event.time() * sampleRate; |
+ double eventFrame = event->time() * sampleRate; |
// Condition is currentFrame - 1 < eventFrame <= currentFrame, but |
// currentFrame is unsigned and could be 0, so use |
// currentFrame < eventFrame + 1 instead. |
- if (!((event.getType() == ParamEvent::SetValue && |
+ if (!((event->getType() == ParamEvent::SetValue && |
(eventFrame <= currentFrame) && |
(currentFrame < eventFrame + 1)))) { |
// This is not the special SetValue event case, and nextEvent is |
@@ -594,7 +940,7 @@ float AudioParamTimeline::valuesForFrameRangeImpl(size_t startFrame, |
// 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::SetTarget && |
+ if (event->getType() == ParamEvent::SetTarget && |
(nextEventType == ParamEvent::LinearRampToValue || |
nextEventType == ParamEvent::ExponentialRampToValue)) { |
// Replace the SetTarget with a SetValue to set the starting time and |
@@ -616,36 +962,111 @@ float AudioParamTimeline::valuesForFrameRangeImpl(size_t startFrame, |
// -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 * sampleRate * event.time() - 2 * currentFrame + 1) <= 1) { |
+ if (fabs(2 * sampleRate * event->time() - 2 * currentFrame + 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(-(currentFrame / sampleRate - event.time()) / |
- event.timeConstant()); |
+ value = event->value() + |
+ (value - event->value()) * |
+ exp(-(currentFrame / sampleRate - event->time()) / |
+ event->timeConstant()); |
} else { |
// SetTarget has already started. Update |value| one frame because it's |
// the value from the previous frame. |
float discreteTimeConstant = static_cast<float>( |
AudioUtilities::discreteTimeConstantForSampleRate( |
- event.timeConstant(), controlRate)); |
- value += (event.value() - value) * discreteTimeConstant; |
+ event->timeConstant(), controlRate)); |
+ value += (event->value() - value) * discreteTimeConstant; |
} |
// Insert a SetValueEvent to mark the starting value and time. |
// Clear the clamp check because this doesn't need it. |
m_events[i] = |
ParamEvent::createSetValueEvent(value, currentFrame / sampleRate); |
- m_events[i].clearTimeClampCheck(); |
+ m_events[i]->clearTimeClampCheck(); |
+ |
+ // Update our pointer to the current event because we just changed it. |
+ event = m_events[i].get(); |
} |
- float value1 = event.value(); |
- double time1 = event.time(); |
+ float value1 = event->value(); |
+ double time1 = event->time(); |
float value2 = nextEvent ? nextEvent->value() : value1; |
double time2 = nextEvent ? nextEvent->time() : endFrame / sampleRate + 1; |
+ // Check to see if an event was cancelled. |
+ if (nextEventType == ParamEvent::CancelValues) { |
+ switch (event->getType()) { |
+ case ParamEvent::LinearRampToValue: |
+ case ParamEvent::ExponentialRampToValue: |
+ case ParamEvent::SetValue: { |
+ // 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* savedEvent = nextEvent->savedEvent(); |
+ |
+ // Update the end time and type to pretend that we're running |
+ // this saved event type. |
+ time2 = nextEvent->time(); |
+ nextEventType = savedEvent->getType(); |
+ |
+ if (nextEvent->hasDefaultCancelledValue()) { |
+ // We've already established a value for the cancelled |
+ // event, so just return it. |
+ value2 = nextEvent->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 (savedEvent->getType()) { |
+ case ParamEvent::LinearRampToValue: |
+ value2 = |
+ linearRampAtTime(nextEvent->time(), value1, time1, |
+ savedEvent->value(), savedEvent->time()); |
+ break; |
+ case ParamEvent::ExponentialRampToValue: |
+ value2 = exponentialRampAtTime(nextEvent->time(), value1, time1, |
+ savedEvent->value(), |
+ savedEvent->time()); |
+ break; |
+ case ParamEvent::SetValueCurve: |
+ case ParamEvent::SetValue: |
+ case ParamEvent::SetTarget: |
+ case ParamEvent::CancelValues: |
+ // 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::LastType: |
+ // Illegal event type. |
+ NOTREACHED(); |
+ break; |
+ } |
+ |
+ // Cache the new value so we don't keep computing it over and over. |
+ nextEvent->setCancelledValue(value2); |
+ } |
+ } break; |
+ case ParamEvent::SetValueCurve: |
+ // Everything needed for this was handled when cancelling was |
+ // done. |
+ break; |
+ case ParamEvent::SetTarget: |
+ case ParamEvent::CancelValues: |
+ // Nothing special needs to be done for SetTarget or |
+ // CancelValues followed by CancelValues. |
+ break; |
+ case ParamEvent::LastType: |
+ NOTREACHED(); |
+ break; |
+ } |
+ } |
+ |
double deltaTime = time2 - time1; |
float k = deltaTime > 0 ? 1 / deltaTime : 0; |
@@ -776,17 +1197,48 @@ float AudioParamTimeline::valuesForFrameRangeImpl(size_t startFrame, |
} |
} else { |
// Handle event types not requiring looking ahead to the next event. |
- switch (event.getType()) { |
+ switch (event->getType()) { |
case ParamEvent::SetValue: |
case ParamEvent::LinearRampToValue: { |
currentFrame = fillToEndFrame; |
// Simply stay at a constant value. |
- value = event.value(); |
+ value = event->value(); |
+ |
+ for (; writeIndex < fillToFrame; ++writeIndex) |
+ values[writeIndex] = value; |
+ |
+ break; |
+ } |
+ case ParamEvent::CancelValues: { |
+ // 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 cancelFrame = time1 * sampleRate; |
+ if (i >= 1 && cancelFrame <= currentFrame && |
+ currentFrame < cancelFrame + 1) { |
+ ParamEvent::Type lastEventType = m_events[i - 1]->getType(); |
+ if (lastEventType == ParamEvent::SetTarget) { |
+ float target = m_events[i - 1]->value(); |
+ float timeConstant = m_events[i - 1]->timeConstant(); |
+ float discreteTimeConstant = static_cast<float>( |
+ AudioUtilities::discreteTimeConstantForSampleRate( |
+ timeConstant, controlRate)); |
+ value += (target - value) * discreteTimeConstant; |
+ } |
+ } |
+ } |
+ |
+ // Simply stay at the current value. |
for (; writeIndex < fillToFrame; ++writeIndex) |
values[writeIndex] = value; |
+ currentFrame = fillToEndFrame; |
break; |
} |
@@ -797,8 +1249,8 @@ float AudioParamTimeline::valuesForFrameRangeImpl(size_t startFrame, |
// (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 && ((m_events[i - 1].value() * event.value()) > 0)) |
- value = event.value(); |
+ if (i >= 1 && ((m_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 |
@@ -815,8 +1267,8 @@ float AudioParamTimeline::valuesForFrameRangeImpl(size_t startFrame, |
// v(t) = v2 + (v1 - v2)*exp(-(t-t1/tau)) |
// |
- float target = event.value(); |
- float timeConstant = event.timeConstant(); |
+ float target = event->value(); |
+ float timeConstant = event->timeConstant(); |
float discreteTimeConstant = static_cast<float>( |
AudioUtilities::discreteTimeConstantForSampleRate(timeConstant, |
controlRate)); |
@@ -905,16 +1357,18 @@ float AudioParamTimeline::valuesForFrameRangeImpl(size_t startFrame, |
} |
case ParamEvent::SetValueCurve: { |
- Vector<float> curve = event.curve(); |
+ Vector<float> curve = event->curve(); |
float* curveData = curve.data(); |
unsigned numberOfCurvePoints = curve.size(); |
+ float curveEndValue = event->curveEndValue(); |
+ |
// Curve events have duration, so don't just use next event time. |
- double duration = event.duration(); |
+ 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 curvePointsPerFrame = |
- (numberOfCurvePoints - 1) / duration / sampleRate; |
+ event->curvePointsPerSecond() / sampleRate; |
if (!numberOfCurvePoints || duration <= 0 || sampleRate <= 0) { |
// Error condition - simply propagate previous value. |
@@ -964,7 +1418,7 @@ float AudioParamTimeline::valuesForFrameRangeImpl(size_t startFrame, |
} |
// Set the default value in case fillToFrame is 0. |
- value = curveData[numberOfCurvePoints - 1]; |
+ value = curveEndValue; |
// Render the stretched curve data using linear interpolation. |
// Oversampled curve data can be provided if sharp discontinuities are |
@@ -1067,11 +1521,12 @@ float AudioParamTimeline::valuesForFrameRangeImpl(size_t startFrame, |
// 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. |
- if (writeIndex < nextEventFillToFrame) |
- value = curveData[numberOfCurvePoints - 1]; |
- for (; writeIndex < nextEventFillToFrame; ++writeIndex) |
- values[writeIndex] = value; |
+ // curveData. Don't modify |value| unless there is time left. |
+ if (writeIndex < nextEventFillToFrame) { |
+ value = curveEndValue; |
+ for (; writeIndex < nextEventFillToFrame; ++writeIndex) |
+ values[writeIndex] = value; |
+ } |
// Re-adjust current time |
currentFrame += nextEventFillToFrame; |