diff options
Diffstat (limited to '')
-rw-r--r-- | dom/media/webaudio/AudioEventTimeline.cpp | 369 |
1 files changed, 369 insertions, 0 deletions
diff --git a/dom/media/webaudio/AudioEventTimeline.cpp b/dom/media/webaudio/AudioEventTimeline.cpp new file mode 100644 index 0000000000..4e6643b08e --- /dev/null +++ b/dom/media/webaudio/AudioEventTimeline.cpp @@ -0,0 +1,369 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:set ts=2 sw=2 sts=2 et cindent: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "AudioEventTimeline.h" +#include "AudioNodeTrack.h" + +#include "mozilla/ErrorResult.h" + +static float LinearInterpolate(double t0, float v0, double t1, float v1, + double t) { + return v0 + (v1 - v0) * ((t - t0) / (t1 - t0)); +} + +static float ExponentialInterpolate(double t0, float v0, double t1, float v1, + double t) { + return v0 * powf(v1 / v0, (t - t0) / (t1 - t0)); +} + +static float ExponentialApproach(double t0, double v0, float v1, + double timeConstant, double t) { + if (!mozilla::dom::WebAudioUtils::FuzzyEqual(timeConstant, 0.0)) { + return v1 + (v0 - v1) * expf(-(t - t0) / timeConstant); + } else { + return v1; + } +} + +static float ExtractValueFromCurve(double startTime, float* aCurve, + uint32_t aCurveLength, double duration, + double t) { + if (t >= startTime + duration) { + // After the duration, return the last curve value + return aCurve[aCurveLength - 1]; + } + double ratio = std::max((t - startTime) / duration, 0.0); + if (ratio >= 1.0) { + return aCurve[aCurveLength - 1]; + } + uint32_t current = uint32_t(floor((aCurveLength - 1) * ratio)); + uint32_t next = current + 1; + double step = duration / double(aCurveLength - 1); + if (next < aCurveLength) { + double t0 = current * step; + double t1 = next * step; + return LinearInterpolate(t0, aCurve[current], t1, aCurve[next], + t - startTime); + } else { + return aCurve[current]; + } +} + +namespace mozilla::dom { + +AudioTimelineEvent::AudioTimelineEvent(Type aType, double aTime, float aValue, + double aTimeConstant, double aDuration, + const float* aCurve, + uint32_t aCurveLength) + : mType(aType), + mCurve(nullptr), + mTimeConstant(aTimeConstant), + mDuration(aDuration) +#ifdef DEBUG + , + mTimeIsInTicks(false) +#endif +{ + mTime = aTime; + if (aType == AudioTimelineEvent::SetValueCurve) { + SetCurveParams(aCurve, aCurveLength); + } else { + mValue = aValue; + } +} + +AudioTimelineEvent::AudioTimelineEvent(AudioNodeTrack* aTrack) + : mType(Track), + mCurve(nullptr), + mTrack(aTrack), + mTimeConstant(0.0), + mDuration(0.0) +#ifdef DEBUG + , + mTimeIsInTicks(false) +#endif + , + mTime(0.0) { +} + +AudioTimelineEvent::AudioTimelineEvent(const AudioTimelineEvent& rhs) { + PodCopy(this, &rhs, 1); + + if (rhs.mType == AudioTimelineEvent::SetValueCurve) { + SetCurveParams(rhs.mCurve, rhs.mCurveLength); + } else if (rhs.mType == AudioTimelineEvent::Track) { + new (&mTrack) decltype(mTrack)(rhs.mTrack); + } +} + +AudioTimelineEvent::~AudioTimelineEvent() { + if (mType == AudioTimelineEvent::SetValueCurve) { + delete[] mCurve; + } +} + +// This method computes the AudioParam value at a given time based on the event +// timeline +template <class TimeType> +void AudioEventTimeline::GetValuesAtTimeHelper(TimeType aTime, float* aBuffer, + const size_t aSize) { + MOZ_ASSERT(aBuffer); + MOZ_ASSERT(aSize); + + auto TimeOf = [](const AudioTimelineEvent& aEvent) -> TimeType { + return aEvent.Time<TimeType>(); + }; + + size_t eventIndex = 0; + const AudioTimelineEvent* previous = nullptr; + + // Let's remove old events except the last one: we need it to calculate some + // curves. + CleanupEventsOlderThan(aTime); + + for (size_t bufferIndex = 0; bufferIndex < aSize; ++bufferIndex, ++aTime) { + bool timeMatchesEventIndex = false; + const AudioTimelineEvent* next; + for (;; ++eventIndex) { + if (eventIndex >= mEvents.Length()) { + next = nullptr; + break; + } + + next = &mEvents[eventIndex]; + if (aTime < TimeOf(*next)) { + break; + } + +#ifdef DEBUG + MOZ_ASSERT(next->mType == AudioTimelineEvent::SetValueAtTime || + next->mType == AudioTimelineEvent::SetTarget || + next->mType == AudioTimelineEvent::LinearRamp || + next->mType == AudioTimelineEvent::ExponentialRamp || + next->mType == AudioTimelineEvent::SetValueCurve); +#endif + + if (TimesEqual(aTime, TimeOf(*next))) { + mLastComputedValue = mComputedValue; + // Find the last event with the same time + while (eventIndex < mEvents.Length() - 1 && + TimesEqual(aTime, TimeOf(mEvents[eventIndex + 1]))) { + mLastComputedValue = + GetValueAtTimeOfEvent<TimeType>(&mEvents[eventIndex]); + ++eventIndex; + } + + timeMatchesEventIndex = true; + break; + } + + previous = next; + } + + if (timeMatchesEventIndex) { + // The time matches one of the events exactly. + MOZ_ASSERT(TimesEqual(aTime, TimeOf(mEvents[eventIndex]))); + mComputedValue = GetValueAtTimeOfEvent<TimeType>(&mEvents[eventIndex]); + } else { + mComputedValue = GetValuesAtTimeHelperInternal(aTime, previous, next); + } + + aBuffer[bufferIndex] = mComputedValue; + } +} +template void AudioEventTimeline::GetValuesAtTimeHelper(double aTime, + float* aBuffer, + const size_t aSize); +template void AudioEventTimeline::GetValuesAtTimeHelper(int64_t aTime, + float* aBuffer, + const size_t aSize); + +template <class TimeType> +float AudioEventTimeline::GetValueAtTimeOfEvent( + const AudioTimelineEvent* aNext) { + TimeType time = aNext->Time<TimeType>(); + switch (aNext->mType) { + case AudioTimelineEvent::SetTarget: + // SetTarget nodes can be handled no matter what their next node is + // (if they have one). + // Follow the curve, without regard to the next event, starting at + // the last value of the last event. + return ExponentialApproach(time, mLastComputedValue, aNext->mValue, + aNext->mTimeConstant, time); + break; + case AudioTimelineEvent::SetValueCurve: + // SetValueCurve events can be handled no matter what their event + // node is (if they have one) + return ExtractValueFromCurve(time, aNext->mCurve, aNext->mCurveLength, + aNext->mDuration, time); + break; + default: + // For other event types + return aNext->mValue; + } +} + +template <class TimeType> +float AudioEventTimeline::GetValuesAtTimeHelperInternal( + TimeType aTime, const AudioTimelineEvent* aPrevious, + const AudioTimelineEvent* aNext) { + // If the requested time is before all of the existing events + if (!aPrevious) { + return mValue; + } + + // If this event is a curve event, this returns the end time of the curve. + // Otherwise, this returns the time of the event. + auto TimeOf = [](const AudioTimelineEvent* aEvent) -> TimeType { + if (aEvent->mType == AudioTimelineEvent::SetValueCurve) { + return aEvent->Time<TimeType>() + aEvent->mDuration; + } + return aEvent->Time<TimeType>(); + }; + + // Value for an event. For a ValueCurve event, this is the value of the last + // element of the curve. + auto ValueOf = [](const AudioTimelineEvent* aEvent) -> float { + if (aEvent->mType == AudioTimelineEvent::SetValueCurve) { + return aEvent->mCurve[aEvent->mCurveLength - 1]; + } + return aEvent->mValue; + }; + + // SetTarget nodes can be handled no matter what their next node is (if + // they have one) + if (aPrevious->mType == AudioTimelineEvent::SetTarget) { + return ExponentialApproach(TimeOf(aPrevious), mLastComputedValue, + ValueOf(aPrevious), aPrevious->mTimeConstant, + aTime); + } + + // SetValueCurve events can be handled no matter what their next node is + // (if they have one), when aTime is in the curve region. + if (aPrevious->mType == AudioTimelineEvent::SetValueCurve && + aTime <= aPrevious->Time<TimeType>() + aPrevious->mDuration) { + return ExtractValueFromCurve(aPrevious->Time<TimeType>(), aPrevious->mCurve, + aPrevious->mCurveLength, aPrevious->mDuration, + aTime); + } + + // If the requested time is after all of the existing events + if (!aNext) { + switch (aPrevious->mType) { + case AudioTimelineEvent::SetValueAtTime: + case AudioTimelineEvent::LinearRamp: + case AudioTimelineEvent::ExponentialRamp: + // The value will be constant after the last event + return aPrevious->mValue; + case AudioTimelineEvent::SetValueCurve: + return ExtractValueFromCurve(aPrevious->Time<TimeType>(), + aPrevious->mCurve, aPrevious->mCurveLength, + aPrevious->mDuration, aTime); + case AudioTimelineEvent::SetTarget: + MOZ_FALLTHROUGH_ASSERT("AudioTimelineEvent::SetTarget"); + case AudioTimelineEvent::SetValue: + case AudioTimelineEvent::Cancel: + case AudioTimelineEvent::Track: + MOZ_ASSERT(false, "Should have been handled earlier."); + } + MOZ_ASSERT(false, "unreached"); + } + + // Finally, handle the case where we have both a previous and a next event + + // First, handle the case where our range ends up in a ramp event + switch (aNext->mType) { + case AudioTimelineEvent::LinearRamp: + return LinearInterpolate(TimeOf(aPrevious), ValueOf(aPrevious), + TimeOf(aNext), ValueOf(aNext), aTime); + + case AudioTimelineEvent::ExponentialRamp: + return ExponentialInterpolate(TimeOf(aPrevious), ValueOf(aPrevious), + TimeOf(aNext), ValueOf(aNext), aTime); + + case AudioTimelineEvent::SetValueAtTime: + case AudioTimelineEvent::SetTarget: + case AudioTimelineEvent::SetValueCurve: + break; + case AudioTimelineEvent::SetValue: + case AudioTimelineEvent::Cancel: + case AudioTimelineEvent::Track: + MOZ_ASSERT(false, "Should have been handled earlier."); + } + + // Now handle all other cases + switch (aPrevious->mType) { + case AudioTimelineEvent::SetValueAtTime: + case AudioTimelineEvent::LinearRamp: + case AudioTimelineEvent::ExponentialRamp: + // If the next event type is neither linear or exponential ramp, the + // value is constant. + return aPrevious->mValue; + case AudioTimelineEvent::SetValueCurve: + return ExtractValueFromCurve(aPrevious->Time<TimeType>(), + aPrevious->mCurve, aPrevious->mCurveLength, + aPrevious->mDuration, aTime); + case AudioTimelineEvent::SetTarget: + MOZ_FALLTHROUGH_ASSERT("AudioTimelineEvent::SetTarget"); + case AudioTimelineEvent::SetValue: + case AudioTimelineEvent::Cancel: + case AudioTimelineEvent::Track: + MOZ_ASSERT(false, "Should have been handled earlier."); + } + + MOZ_ASSERT(false, "unreached"); + return 0.0f; +} +template float AudioEventTimeline::GetValuesAtTimeHelperInternal( + double aTime, const AudioTimelineEvent* aPrevious, + const AudioTimelineEvent* aNext); +template float AudioEventTimeline::GetValuesAtTimeHelperInternal( + int64_t aTime, const AudioTimelineEvent* aPrevious, + const AudioTimelineEvent* aNext); + +const AudioTimelineEvent* AudioEventTimeline::GetPreviousEvent( + double aTime) const { + const AudioTimelineEvent* previous = nullptr; + const AudioTimelineEvent* next = nullptr; + + auto TimeOf = [](const AudioTimelineEvent& aEvent) -> double { + return aEvent.Time<double>(); + }; + + bool bailOut = false; + for (unsigned i = 0; !bailOut && i < mEvents.Length(); ++i) { + switch (mEvents[i].mType) { + case AudioTimelineEvent::SetValueAtTime: + case AudioTimelineEvent::SetTarget: + case AudioTimelineEvent::LinearRamp: + case AudioTimelineEvent::ExponentialRamp: + case AudioTimelineEvent::SetValueCurve: + if (aTime == TimeOf(mEvents[i])) { + // Find the last event with the same time + do { + ++i; + } while (i < mEvents.Length() && aTime == TimeOf(mEvents[i])); + return &mEvents[i - 1]; + } + previous = next; + next = &mEvents[i]; + if (aTime < TimeOf(mEvents[i])) { + bailOut = true; + } + break; + default: + MOZ_ASSERT(false, "unreached"); + } + } + // Handle the case where the time is past all of the events + if (!bailOut) { + previous = next; + } + + return previous; +} + +} // namespace mozilla::dom |