bug 1257718 don't export implementation of complex timeline methods r?padenot draft
authorKarl Tomlinson <karlt+@karlt.net>
Fri, 17 Jun 2016 05:26:13 +1200
changeset 380502 920df23dda6244eb2579ea67dc4d4315991ce4ba
parent 380501 424d4733c67892a738ac696362faab75485066cd
child 380503 c04058c30fef01a90c35e76d98f397bbcad8768b
push id21234
push userktomlinson@mozilla.com
push dateWed, 22 Jun 2016 05:11:59 +0000
reviewerspadenot
bugs1257718
milestone50.0a1
bug 1257718 don't export implementation of complex timeline methods r?padenot This limits recompilation required when modifying the methods, and makes the public interface easier to read. MozReview-Commit-ID: Lo2f7xmIdGu
dom/media/webaudio/AudioEventTimeline.cpp
dom/media/webaudio/AudioEventTimeline.h
dom/media/webaudio/AudioParamTimeline.h
dom/media/webaudio/compiledtest/TestAudioEventTimeline.cpp
dom/media/webaudio/moz.build
copy from dom/media/webaudio/AudioEventTimeline.h
copy to dom/media/webaudio/AudioEventTimeline.cpp
--- a/dom/media/webaudio/AudioEventTimeline.h
+++ b/dom/media/webaudio/AudioEventTimeline.cpp
@@ -1,691 +1,375 @@
 /* -*- 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/. */
 
-#ifndef AudioEventTimeline_h_
-#define AudioEventTimeline_h_
+#include "AudioEventTimeline.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));
+}
 
-#include <algorithm>
-#include "mozilla/Assertions.h"
-#include "mozilla/FloatingPoint.h"
-#include "mozilla/PodOperations.h"
+static float ExponentialApproach(double t0, double v0, float v1, double timeConstant, double t)
+{
+  return v1 + (v0 - v1) * expf(-(t - t0) / timeConstant);
+}
 
-#include "MainThreadUtils.h"
-#include "nsTArray.h"
-#include "math.h"
-#include "WebAudioUtils.h"
+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];
+  }
+  return aCurve[uint32_t(aCurveLength * ratio)];
+}
 
 namespace mozilla {
-
-class MediaStream;
-
 namespace dom {
 
-struct AudioTimelineEvent final
+template <class ErrorResult> bool
+AudioEventTimeline::ValidateEvent(AudioTimelineEvent& aEvent,
+                                  ErrorResult& aRv)
 {
-  enum Type : uint32_t
-  {
-    SetValue,
-    SetValueAtTime,
-    LinearRamp,
-    ExponentialRamp,
-    SetTarget,
-    SetValueCurve,
-    Stream,
-    Cancel
-  };
+  MOZ_ASSERT(NS_IsMainThread());
 
-  AudioTimelineEvent(Type aType, double aTime, float aValue, double aTimeConstant = 0.0,
-                     double aDuration = 0.0, const float* aCurve = nullptr,
-                     uint32_t aCurveLength = 0)
-    : 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;
-    }
+  // Validate the event itself
+  if (!WebAudioUtils::IsTimeValid(aEvent.template Time<double>()) ||
+      !WebAudioUtils::IsTimeValid(aEvent.mTimeConstant)) {
+    aRv.Throw(NS_ERROR_DOM_SYNTAX_ERR);
+    return false;
   }
 
-  explicit AudioTimelineEvent(MediaStream* aStream)
-    : mType(Stream)
-    , mCurve(nullptr)
-    , mStream(aStream)
-    , mTimeConstant(0.0)
-    , mDuration(0.0)
-#ifdef DEBUG
-    , mTimeIsInTicks(false)
-#endif
-  {
-  }
-
-  AudioTimelineEvent(const AudioTimelineEvent& rhs)
-  {
-    PodCopy(this, &rhs, 1);
-
-    if (rhs.mType == AudioTimelineEvent::SetValueCurve) {
-      SetCurveParams(rhs.mCurve, rhs.mCurveLength);
-    } else if (rhs.mType == AudioTimelineEvent::Stream) {
-      new (&mStream) decltype(mStream)(rhs.mStream);
+  if (aEvent.mType == AudioTimelineEvent::SetValueCurve) {
+    if (!aEvent.mCurve || !aEvent.mCurveLength) {
+      aRv.Throw(NS_ERROR_DOM_SYNTAX_ERR);
+      return false;
     }
-  }
-
-  ~AudioTimelineEvent()
-  {
-    if (mType == AudioTimelineEvent::SetValueCurve) {
-      delete[] mCurve;
-    }
-  }
-
-  template <class TimeType>
-  TimeType Time() const;
-
-  void SetTimeInTicks(int64_t aTimeInTicks)
-  {
-    mTimeInTicks = aTimeInTicks;
-#ifdef DEBUG
-    mTimeIsInTicks = true;
-#endif
-  }
-
-  void SetCurveParams(const float* aCurve, uint32_t aCurveLength) {
-    mCurveLength = aCurveLength;
-    if (aCurveLength) {
-      mCurve = new float[aCurveLength];
-      PodCopy(mCurve, aCurve, aCurveLength);
-    } else {
-      mCurve = nullptr;
+    for (uint32_t i = 0; i < aEvent.mCurveLength; ++i) {
+      if (!IsValid(aEvent.mCurve[i])) {
+        aRv.Throw(NS_ERROR_DOM_SYNTAX_ERR);
+        return false;
+      }
     }
   }
 
-  Type mType;
-  union {
-    float mValue;
-    uint32_t mCurveLength;
-  };
-  // mCurve contains a buffer of SetValueCurve samples.  We sample the
-  // values in the buffer depending on how far along we are in time.
-  // If we're at time T and the event has started as time T0 and has a
-  // duration of D, we sample the buffer at floor(mCurveLength*(T-T0)/D)
-  // if T<T0+D, and just take the last sample in the buffer otherwise.
-  float* mCurve;
-  RefPtr<MediaStream> mStream;
-  double mTimeConstant;
-  double mDuration;
-#ifdef DEBUG
-  bool mTimeIsInTicks;
-#endif
-
-private:
-  // This member is accessed using the `Time` method, for safety.
-  //
-  // The time for an event can either be in absolute value or in ticks.
-  // Initially the time of the event is always in absolute value.
-  // In order to convert it to ticks, call SetTimeInTicks.  Once this
-  // method has been called for an event, the time cannot be converted
-  // back to absolute value.
-  union {
-    double mTime;
-    int64_t mTimeInTicks;
-  };
-};
+  if (aEvent.mType == AudioTimelineEvent::SetTarget &&
+      WebAudioUtils::FuzzyEqual(aEvent.mTimeConstant, 0.0)) {
+    aRv.Throw(NS_ERROR_DOM_SYNTAX_ERR);
+    return false;
+  }
 
-template <>
-inline double AudioTimelineEvent::Time<double>() const
-{
-  MOZ_ASSERT(!mTimeIsInTicks);
-  return mTime;
-}
-
-template <>
-inline int64_t AudioTimelineEvent::Time<int64_t>() const
-{
-  MOZ_ASSERT(!NS_IsMainThread());
-  MOZ_ASSERT(mTimeIsInTicks);
-  return mTimeInTicks;
-}
+  bool timeAndValueValid = IsValid(aEvent.mValue) &&
+                           IsValid(aEvent.mDuration);
+  if (!timeAndValueValid) {
+    aRv.Throw(NS_ERROR_DOM_SYNTAX_ERR);
+    return false;
+  }
 
-/**
- * This class will be instantiated with different template arguments for testing and
- * production code.
- *
- * ErrorResult is a type which satisfies the following:
- *  - Implements a Throw() method taking an nsresult argument, representing an error code.
- */
-template <class ErrorResult>
-class AudioEventTimeline
-{
-public:
-  explicit AudioEventTimeline(float aDefaultValue)
-    : mValue(aDefaultValue),
-      mComputedValue(aDefaultValue),
-      mLastComputedValue(aDefaultValue)
-  { }
-
-  bool ValidateEvent(AudioTimelineEvent& aEvent,
-                     ErrorResult& aRv)
-  {
-    MOZ_ASSERT(NS_IsMainThread());
-
-    // Validate the event itself
-    if (!WebAudioUtils::IsTimeValid(aEvent.template Time<double>()) ||
-        !WebAudioUtils::IsTimeValid(aEvent.mTimeConstant)) {
+  // Make sure that non-curve events don't fall within the duration of a
+  // curve event.
+  for (unsigned i = 0; i < mEvents.Length(); ++i) {
+    if (mEvents[i].mType == AudioTimelineEvent::SetValueCurve &&
+        !(aEvent.mType == AudioTimelineEvent::SetValueCurve &&
+          aEvent.template Time<double>() == mEvents[i].template Time<double>()) &&
+        mEvents[i].template Time<double>() <= aEvent.template Time<double>() &&
+        (mEvents[i].template Time<double>() + mEvents[i].mDuration) >= aEvent.template Time<double>()) {
       aRv.Throw(NS_ERROR_DOM_SYNTAX_ERR);
       return false;
     }
+  }
 
-    if (aEvent.mType == AudioTimelineEvent::SetValueCurve) {
-      if (!aEvent.mCurve || !aEvent.mCurveLength) {
+  // Make sure that curve events don't fall in a range which includes other
+  // events.
+  if (aEvent.mType == AudioTimelineEvent::SetValueCurve) {
+    for (unsigned i = 0; i < mEvents.Length(); ++i) {
+      // In case we have two curve at the same time
+      if (mEvents[i].mType == AudioTimelineEvent::SetValueCurve &&
+          mEvents[i].template Time<double>() == aEvent.template Time<double>()) {
+        continue;
+      }
+      if (mEvents[i].template Time<double>() > aEvent.template Time<double>() &&
+          mEvents[i].template Time<double>() < (aEvent.template Time<double>() + aEvent.mDuration)) {
         aRv.Throw(NS_ERROR_DOM_SYNTAX_ERR);
         return false;
       }
-      for (uint32_t i = 0; i < aEvent.mCurveLength; ++i) {
-        if (!IsValid(aEvent.mCurve[i])) {
-          aRv.Throw(NS_ERROR_DOM_SYNTAX_ERR);
-          return false;
-        }
-      }
     }
+  }
 
-    if (aEvent.mType == AudioTimelineEvent::SetTarget &&
-        WebAudioUtils::FuzzyEqual(aEvent.mTimeConstant, 0.0)) {
+  // Make sure that invalid values are not used for exponential curves
+  if (aEvent.mType == AudioTimelineEvent::ExponentialRamp) {
+    if (aEvent.mValue <= 0.f) {
       aRv.Throw(NS_ERROR_DOM_SYNTAX_ERR);
       return false;
     }
-
-    bool timeAndValueValid = IsValid(aEvent.mValue) &&
-                             IsValid(aEvent.mDuration);
-    if (!timeAndValueValid) {
-      aRv.Throw(NS_ERROR_DOM_SYNTAX_ERR);
-      return false;
-    }
-
-    // Make sure that non-curve events don't fall within the duration of a
-    // curve event.
-    for (unsigned i = 0; i < mEvents.Length(); ++i) {
-      if (mEvents[i].mType == AudioTimelineEvent::SetValueCurve &&
-          !(aEvent.mType == AudioTimelineEvent::SetValueCurve &&
-            aEvent.template Time<double>() == mEvents[i].template Time<double>()) &&
-          mEvents[i].template Time<double>() <= aEvent.template Time<double>() &&
-          (mEvents[i].template Time<double>() + mEvents[i].mDuration) >= aEvent.template Time<double>()) {
+    const AudioTimelineEvent* previousEvent = GetPreviousEvent(aEvent.template Time<double>());
+    if (previousEvent) {
+      if (previousEvent->mValue <= 0.f) {
+        aRv.Throw(NS_ERROR_DOM_SYNTAX_ERR);
+        return false;
+      }
+    } else {
+      if (mValue <= 0.f) {
         aRv.Throw(NS_ERROR_DOM_SYNTAX_ERR);
         return false;
       }
     }
-
-    // Make sure that curve events don't fall in a range which includes other
-    // events.
-    if (aEvent.mType == AudioTimelineEvent::SetValueCurve) {
-      for (unsigned i = 0; i < mEvents.Length(); ++i) {
-        // In case we have two curve at the same time
-        if (mEvents[i].mType == AudioTimelineEvent::SetValueCurve &&
-            mEvents[i].template Time<double>() == aEvent.template Time<double>()) {
-          continue;
-        }
-        if (mEvents[i].template Time<double>() > aEvent.template Time<double>() &&
-            mEvents[i].template Time<double>() < (aEvent.template Time<double>() + aEvent.mDuration)) {
-          aRv.Throw(NS_ERROR_DOM_SYNTAX_ERR);
-          return false;
-        }
-      }
-    }
+  }
+  return true;
+}
+template bool
+AudioEventTimeline::ValidateEvent(AudioTimelineEvent& aEvent,
+                                  ErrorResult& aRv);
 
-    // Make sure that invalid values are not used for exponential curves
-    if (aEvent.mType == AudioTimelineEvent::ExponentialRamp) {
-      if (aEvent.mValue <= 0.f) {
-        aRv.Throw(NS_ERROR_DOM_SYNTAX_ERR);
-        return false;
-      }
-      const AudioTimelineEvent* previousEvent = GetPreviousEvent(aEvent.template Time<double>());
-      if (previousEvent) {
-        if (previousEvent->mValue <= 0.f) {
-          aRv.Throw(NS_ERROR_DOM_SYNTAX_ERR);
-          return false;
-        }
-      } else {
-        if (mValue <= 0.f) {
-          aRv.Throw(NS_ERROR_DOM_SYNTAX_ERR);
-          return false;
-        }
-      }
-    }
-    return true;
+// 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);
+
+  size_t lastEventId = 0;
+  const AudioTimelineEvent* previous = nullptr;
+  const AudioTimelineEvent* next = nullptr;
+  bool bailOut = false;
+
+  // Let's remove old events except the last one: we need it to calculate some curves.
+  while (mEvents.Length() > 1 &&
+         aTime > mEvents[1].template Time<TimeType>()) {
+    mEvents.RemoveElementAt(0);
   }
 
-  template<typename TimeType>
-  void InsertEvent(const AudioTimelineEvent& aEvent)
-  {
-    for (unsigned i = 0; i < mEvents.Length(); ++i) {
-      if (aEvent.template Time<TimeType>() == mEvents[i].template Time<TimeType>()) {
-        if (aEvent.mType == mEvents[i].mType) {
-          // If times and types are equal, replace the event
-          mEvents.ReplaceElementAt(i, aEvent);
-        } else {
-          // Otherwise, place the element after the last event of another type
-          do {
-            ++i;
-          } while (i < mEvents.Length() &&
-                   aEvent.mType != mEvents[i].mType &&
-                   aEvent.template Time<TimeType>() == mEvents[i].template Time<TimeType>());
-          mEvents.InsertElementAt(i, aEvent);
+  for (size_t bufferIndex = 0; bufferIndex < aSize; ++bufferIndex, ++aTime) {
+    for (; !bailOut && lastEventId < mEvents.Length(); ++lastEventId) {
+
+#ifdef DEBUG
+      const AudioTimelineEvent* current = &mEvents[lastEventId];
+      MOZ_ASSERT(current->mType == AudioTimelineEvent::SetValueAtTime ||
+                 current->mType == AudioTimelineEvent::SetTarget ||
+                 current->mType == AudioTimelineEvent::LinearRamp ||
+                 current->mType == AudioTimelineEvent::ExponentialRamp ||
+                 current->mType == AudioTimelineEvent::SetValueCurve);
+#endif
+
+      if (TimesEqual(aTime, mEvents[lastEventId].template Time<TimeType>())) {
+        mLastComputedValue = mComputedValue;
+        // Find the last event with the same time
+        while (lastEventId < mEvents.Length() - 1 &&
+               TimesEqual(aTime, mEvents[lastEventId + 1].template Time<TimeType>())) {
+          ++lastEventId;
         }
-        return;
+        break;
       }
-      // Otherwise, place the event right after the latest existing event
-      if (aEvent.template Time<TimeType>() < mEvents[i].template Time<TimeType>()) {
-        mEvents.InsertElementAt(i, aEvent);
-        return;
+
+      previous = next;
+      next = &mEvents[lastEventId];
+      if (aTime < mEvents[lastEventId].template Time<TimeType>()) {
+        bailOut = true;
       }
     }
 
-    // If we couldn't find a place for the event, just append it to the list
-    mEvents.AppendElement(aEvent);
-  }
-
-  bool HasSimpleValue() const
-  {
-    return mEvents.IsEmpty();
-  }
-
-  float GetValue() const
-  {
-    // This method should only be called if HasSimpleValue() returns true
-    MOZ_ASSERT(HasSimpleValue());
-    return mValue;
-  }
-
-  float Value() const
-  {
-    // TODO: Return the current value based on the timeline of the AudioContext
-    return mValue;
-  }
-
-  void SetValue(float aValue)
-  {
-    // Silently don't change anything if there are any events
-    if (mEvents.IsEmpty()) {
-      mLastComputedValue = mComputedValue = mValue = aValue;
-    }
-  }
-
-  void SetValueAtTime(float aValue, double aStartTime, ErrorResult& aRv)
-  {
-    AudioTimelineEvent event(AudioTimelineEvent::SetValueAtTime, aStartTime, aValue);
-
-    if (ValidateEvent(event, aRv)) {
-      InsertEvent<double>(event);
-    }
-  }
-
-  void LinearRampToValueAtTime(float aValue, double aEndTime, ErrorResult& aRv)
-  {
-    AudioTimelineEvent event(AudioTimelineEvent::LinearRamp, aEndTime, aValue);
-
-    if (ValidateEvent(event, aRv)) {
-      InsertEvent<double>(event);
-    }
-  }
-
-  void ExponentialRampToValueAtTime(float aValue, double aEndTime, ErrorResult& aRv)
-  {
-    AudioTimelineEvent event(AudioTimelineEvent::ExponentialRamp, aEndTime, aValue);
-
-    if (ValidateEvent(event, aRv)) {
-      InsertEvent<double>(event);
-    }
-  }
-
-  void SetTargetAtTime(float aTarget, double aStartTime, double aTimeConstant, ErrorResult& aRv)
-  {
-    AudioTimelineEvent event(AudioTimelineEvent::SetTarget, aStartTime, aTarget, aTimeConstant);
-
-    if (ValidateEvent(event, aRv)) {
-      InsertEvent<double>(event);
-    }
-  }
-
-  void SetValueCurveAtTime(const float* aValues, uint32_t aValuesLength, double aStartTime, double aDuration, ErrorResult& aRv)
-  {
-    AudioTimelineEvent event(AudioTimelineEvent::SetValueCurve, aStartTime, 0.0f, 0.0f, aDuration, aValues, aValuesLength);
-    if (ValidateEvent(event, aRv)) {
-      InsertEvent<double>(event);
-    }
-  }
+    if (!bailOut && lastEventId < mEvents.Length()) {
+      // The time matches one of the events exactly.
+      MOZ_ASSERT(TimesEqual(aTime, mEvents[lastEventId].template Time<TimeType>()));
 
-  template<typename TimeType>
-  void CancelScheduledValues(TimeType aStartTime)
-  {
-    for (unsigned i = 0; i < mEvents.Length(); ++i) {
-      if (mEvents[i].template Time<TimeType>() >= aStartTime) {
-#ifdef DEBUG
-        // Sanity check: the array should be sorted, so all of the following
-        // events should have a time greater than aStartTime too.
-        for (unsigned j = i + 1; j < mEvents.Length(); ++j) {
-          MOZ_ASSERT(mEvents[j].template Time<TimeType>() >= aStartTime);
-        }
-#endif
-        mEvents.TruncateLength(i);
-        break;
-      }
-    }
-  }
-
-  void CancelAllEvents()
-  {
-    mEvents.Clear();
-  }
-
-  static bool TimesEqual(int64_t aLhs, int64_t aRhs)
-  {
-    return aLhs == aRhs;
-  }
-
-  // Since we are going to accumulate error by adding 0.01 multiple time in a
-  // loop, we want to fuzz the equality check in GetValueAtTime.
-  static bool TimesEqual(double aLhs, double aRhs)
-  {
-    const float kEpsilon = 0.0000000001f;
-    return fabs(aLhs - aRhs) < kEpsilon;
-  }
-
-  template<class TimeType>
-  float GetValueAtTime(TimeType aTime)
-  {
-    GetValuesAtTimeHelper(aTime, &mComputedValue, 1);
-    return mComputedValue;
-  }
-
-  template<class TimeType>
-  void GetValuesAtTime(TimeType aTime, float* aBuffer, const size_t aSize)
-  {
-    MOZ_ASSERT(aBuffer);
-    GetValuesAtTimeHelper(aTime, aBuffer, aSize);
-    mComputedValue = aBuffer[aSize - 1];
-  }
-
-  // This method computes the AudioParam value at a given time based on the event timeline
-  template<class TimeType>
-  void GetValuesAtTimeHelper(TimeType aTime, float* aBuffer, const size_t aSize)
-  {
-    MOZ_ASSERT(aBuffer);
-    MOZ_ASSERT(aSize);
-
-    size_t lastEventId = 0;
-    const AudioTimelineEvent* previous = nullptr;
-    const AudioTimelineEvent* next = nullptr;
-    bool bailOut = false;
-
-    // Let's remove old events except the last one: we need it to calculate some curves.
-    while (mEvents.Length() > 1 &&
-           aTime > mEvents[1].template Time<TimeType>()) {
-      mEvents.RemoveElementAt(0);
-    }
-
-    for (size_t bufferIndex = 0; bufferIndex < aSize; ++bufferIndex, ++aTime) {
-      for (; !bailOut && lastEventId < mEvents.Length(); ++lastEventId) {
-
-#ifdef DEBUG
-        const AudioTimelineEvent* current = &mEvents[lastEventId];
-        MOZ_ASSERT(current->mType == AudioTimelineEvent::SetValueAtTime ||
-                   current->mType == AudioTimelineEvent::SetTarget ||
-                   current->mType == AudioTimelineEvent::LinearRamp ||
-                   current->mType == AudioTimelineEvent::ExponentialRamp ||
-                   current->mType == AudioTimelineEvent::SetValueCurve);
-#endif
-
-        if (TimesEqual(aTime, mEvents[lastEventId].template Time<TimeType>())) {
-          mLastComputedValue = mComputedValue;
-          // Find the last event with the same time
-          while (lastEventId < mEvents.Length() - 1 &&
-                 TimesEqual(aTime, mEvents[lastEventId + 1].template Time<TimeType>())) {
-            ++lastEventId;
-          }
-          break;
-        }
-
-        previous = next;
-        next = &mEvents[lastEventId];
-        if (aTime < mEvents[lastEventId].template Time<TimeType>()) {
-          bailOut = true;
-        }
+      // SetTarget nodes can be handled no matter what their next node is (if they have one)
+      if (mEvents[lastEventId].mType == AudioTimelineEvent::SetTarget) {
+        // Follow the curve, without regard to the next event, starting at
+        // the last value of the last event.
+        aBuffer[bufferIndex] = ExponentialApproach(mEvents[lastEventId].template Time<TimeType>(),
+                                                mLastComputedValue, mEvents[lastEventId].mValue,
+                                                mEvents[lastEventId].mTimeConstant, aTime);
+        continue;
       }
 
-      if (!bailOut && lastEventId < mEvents.Length()) {
-        // The time matches one of the events exactly.
-        MOZ_ASSERT(TimesEqual(aTime, mEvents[lastEventId].template Time<TimeType>()));
-
-        // SetTarget nodes can be handled no matter what their next node is (if they have one)
-        if (mEvents[lastEventId].mType == AudioTimelineEvent::SetTarget) {
-          // Follow the curve, without regard to the next event, starting at
-          // the last value of the last event.
-          aBuffer[bufferIndex] = ExponentialApproach(mEvents[lastEventId].template Time<TimeType>(),
-                                                  mLastComputedValue, mEvents[lastEventId].mValue,
-                                                  mEvents[lastEventId].mTimeConstant, aTime);
-          continue;
-        }
-
-        // SetValueCurve events can be handled no matter what their event node is (if they have one)
-        if (mEvents[lastEventId].mType == AudioTimelineEvent::SetValueCurve) {
-          aBuffer[bufferIndex] = ExtractValueFromCurve(mEvents[lastEventId].template Time<TimeType>(),
-                                                    mEvents[lastEventId].mCurve,
-                                                    mEvents[lastEventId].mCurveLength,
-                                                    mEvents[lastEventId].mDuration, aTime);
-          continue;
-        }
-
-        // For other event types
-        aBuffer[bufferIndex] = mEvents[lastEventId].mValue;
+      // SetValueCurve events can be handled no matter what their event node is (if they have one)
+      if (mEvents[lastEventId].mType == AudioTimelineEvent::SetValueCurve) {
+        aBuffer[bufferIndex] = ExtractValueFromCurve(mEvents[lastEventId].template Time<TimeType>(),
+                                                  mEvents[lastEventId].mCurve,
+                                                  mEvents[lastEventId].mCurveLength,
+                                                  mEvents[lastEventId].mDuration, aTime);
         continue;
       }
 
-      // Handle the case where the time is past all of the events
-      if (!bailOut) {
-        aBuffer[bufferIndex] = GetValuesAtTimeHelperInternal(aTime, next, nullptr);
-      } else {
-        aBuffer[bufferIndex] = GetValuesAtTimeHelperInternal(aTime, previous, next);
-      }
-    }
-  }
-
-  // Return the number of events scheduled
-  uint32_t GetEventCount() const
-  {
-    return mEvents.Length();
-  }
-
-  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)
-  {
-    return v1 + (v0 - v1) * expf(-(t - t0) / timeConstant);
-  }
-
-  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];
-    }
-    return aCurve[uint32_t(aCurveLength * ratio)];
-  }
-
-  template<class TimeType>
-  float GetValuesAtTimeHelperInternal(TimeType aTime,
-                                      const AudioTimelineEvent* aPrevious,
-                                      const AudioTimelineEvent* aNext)
-  {
-    // If the requested time is before all of the existing events
-    if (!aPrevious) {
-       return mValue;
-    }
-
-    // SetTarget nodes can be handled no matter what their next node is (if
-    // they have one)
-    if (aPrevious->mType == AudioTimelineEvent::SetTarget) {
-      return ExponentialApproach(aPrevious->template Time<TimeType>(),
-                                 mLastComputedValue, aPrevious->mValue,
-                                 aPrevious->mTimeConstant, aTime);
-    }
-
-    // SetValueCurve events can be handled no mattar what their next node is
-    // (if they have one)
-    if (aPrevious->mType == AudioTimelineEvent::SetValueCurve) {
-      return ExtractValueFromCurve(aPrevious->template 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->template 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::Stream:
-          MOZ_ASSERT(false, "Should have been handled earlier.");
-      }
-      MOZ_ASSERT(false, "unreached");
+      // For other event types
+      aBuffer[bufferIndex] = mEvents[lastEventId].mValue;
+      continue;
     }
 
-    // Finally, handle the case where we have both a previous and a next event
+    // Handle the case where the time is past all of the events
+    if (!bailOut) {
+      aBuffer[bufferIndex] = GetValuesAtTimeHelperInternal(aTime, next, nullptr);
+    } else {
+      aBuffer[bufferIndex] = GetValuesAtTimeHelperInternal(aTime, previous, next);
+    }
+  }
+}
+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);
 
-    // First, handle the case where our range ends up in a ramp event
-    switch (aNext->mType) {
-    case AudioTimelineEvent::LinearRamp:
-      return LinearInterpolate(aPrevious->template Time<TimeType>(),
-                               aPrevious->mValue,
-                               aNext->template Time<TimeType>(),
-                               aNext->mValue, aTime);
-
-    case AudioTimelineEvent::ExponentialRamp:
-      return ExponentialInterpolate(aPrevious->template Time<TimeType>(),
-                                    aPrevious->mValue,
-                                    aNext->template Time<TimeType>(),
-                                    aNext->mValue, aTime);
+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;
+  }
 
-    case AudioTimelineEvent::SetValueAtTime:
-    case AudioTimelineEvent::SetTarget:
-    case AudioTimelineEvent::SetValueCurve:
-      break;
-    case AudioTimelineEvent::SetValue:
-    case AudioTimelineEvent::Cancel:
-    case AudioTimelineEvent::Stream:
-      MOZ_ASSERT(false, "Should have been handled earlier.");
-    }
+  // SetTarget nodes can be handled no matter what their next node is (if
+  // they have one)
+  if (aPrevious->mType == AudioTimelineEvent::SetTarget) {
+    return ExponentialApproach(aPrevious->template Time<TimeType>(),
+                               mLastComputedValue, aPrevious->mValue,
+                               aPrevious->mTimeConstant, aTime);
+  }
 
-    // Now handle all other cases
+  // SetValueCurve events can be handled no mattar what their next node is
+  // (if they have one)
+  if (aPrevious->mType == AudioTimelineEvent::SetValueCurve) {
+    return ExtractValueFromCurve(aPrevious->template 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:
-      // If the next event type is neither linear or exponential ramp, the
-      // value is constant.
-      return aPrevious->mValue;
-    case AudioTimelineEvent::SetValueCurve:
-      return ExtractValueFromCurve(aPrevious->template 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::Stream:
-      MOZ_ASSERT(false, "Should have been handled earlier.");
+      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->template 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::Stream:
+        MOZ_ASSERT(false, "Should have been handled earlier.");
     }
-
     MOZ_ASSERT(false, "unreached");
-    return 0.0f;
   }
 
-  const AudioTimelineEvent* GetPreviousEvent(double aTime) const
-  {
-    const AudioTimelineEvent* previous = nullptr;
-    const AudioTimelineEvent* next = nullptr;
+  // 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(aPrevious->template Time<TimeType>(),
+                             aPrevious->mValue,
+                             aNext->template Time<TimeType>(),
+                             aNext->mValue, aTime);
+
+  case AudioTimelineEvent::ExponentialRamp:
+    return ExponentialInterpolate(aPrevious->template Time<TimeType>(),
+                                  aPrevious->mValue,
+                                  aNext->template Time<TimeType>(),
+                                  aNext->mValue, aTime);
 
-    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 == mEvents[i].template Time<double>()) {
-          // Find the last event with the same time
-          do {
-            ++i;
-          } while (i < mEvents.Length() &&
-                   aTime == mEvents[i].template Time<double>());
-          return &mEvents[i - 1];
-        }
-        previous = next;
-        next = &mEvents[i];
-        if (aTime < mEvents[i].template Time<double>()) {
-          bailOut = true;
-        }
-        break;
-      default:
-        MOZ_ASSERT(false, "unreached");
-      }
-    }
-    // Handle the case where the time is past all of the events
-    if (!bailOut) {
-      previous = next;
-    }
+  case AudioTimelineEvent::SetValueAtTime:
+  case AudioTimelineEvent::SetTarget:
+  case AudioTimelineEvent::SetValueCurve:
+    break;
+  case AudioTimelineEvent::SetValue:
+  case AudioTimelineEvent::Cancel:
+  case AudioTimelineEvent::Stream:
+    MOZ_ASSERT(false, "Should have been handled earlier.");
+  }
 
-    return previous;
-  }
-private:
-  static bool IsValid(double value)
-  {
-    return mozilla::IsFinite(value);
+  // 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->template 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::Stream:
+    MOZ_ASSERT(false, "Should have been handled earlier.");
   }
 
-  // This is a sorted array of the events in the timeline.  Queries of this
-  // data structure should probably be more frequent than modifications to it,
-  // and that is the reason why we're using a simple array as the data structure.
-  // We can optimize this in the future if the performance of the array ends up
-  // being a bottleneck.
-  nsTArray<AudioTimelineEvent> mEvents;
-  float mValue;
-  // This is the value of this AudioParam we computed at the last call.
-  float mComputedValue;
-  // This is the value of this AudioParam at the last tick of the previous event.
-  float mLastComputedValue;
-};
+  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;
+
+  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 == mEvents[i].template Time<double>()) {
+        // Find the last event with the same time
+        do {
+          ++i;
+        } while (i < mEvents.Length() &&
+                 aTime == mEvents[i].template Time<double>());
+        return &mEvents[i - 1];
+      }
+      previous = next;
+      next = &mEvents[i];
+      if (aTime < mEvents[i].template Time<double>()) {
+        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 dom
 } // namespace mozilla
 
-#endif
--- a/dom/media/webaudio/AudioEventTimeline.h
+++ b/dom/media/webaudio/AudioEventTimeline.h
@@ -150,121 +150,34 @@ template <>
 inline int64_t AudioTimelineEvent::Time<int64_t>() const
 {
   MOZ_ASSERT(!NS_IsMainThread());
   MOZ_ASSERT(mTimeIsInTicks);
   return mTimeInTicks;
 }
 
 /**
- * This class will be instantiated with different template arguments for testing and
- * production code.
+ * Some methods in this class will be instantiated with different ErrorResult
+ * template arguments for testing and production code.
  *
  * ErrorResult is a type which satisfies the following:
  *  - Implements a Throw() method taking an nsresult argument, representing an error code.
  */
-template <class ErrorResult>
 class AudioEventTimeline
 {
 public:
   explicit AudioEventTimeline(float aDefaultValue)
     : mValue(aDefaultValue),
       mComputedValue(aDefaultValue),
       mLastComputedValue(aDefaultValue)
   { }
 
+  template <class ErrorResult>
   bool ValidateEvent(AudioTimelineEvent& aEvent,
-                     ErrorResult& aRv)
-  {
-    MOZ_ASSERT(NS_IsMainThread());
-
-    // Validate the event itself
-    if (!WebAudioUtils::IsTimeValid(aEvent.template Time<double>()) ||
-        !WebAudioUtils::IsTimeValid(aEvent.mTimeConstant)) {
-      aRv.Throw(NS_ERROR_DOM_SYNTAX_ERR);
-      return false;
-    }
-
-    if (aEvent.mType == AudioTimelineEvent::SetValueCurve) {
-      if (!aEvent.mCurve || !aEvent.mCurveLength) {
-        aRv.Throw(NS_ERROR_DOM_SYNTAX_ERR);
-        return false;
-      }
-      for (uint32_t i = 0; i < aEvent.mCurveLength; ++i) {
-        if (!IsValid(aEvent.mCurve[i])) {
-          aRv.Throw(NS_ERROR_DOM_SYNTAX_ERR);
-          return false;
-        }
-      }
-    }
-
-    if (aEvent.mType == AudioTimelineEvent::SetTarget &&
-        WebAudioUtils::FuzzyEqual(aEvent.mTimeConstant, 0.0)) {
-      aRv.Throw(NS_ERROR_DOM_SYNTAX_ERR);
-      return false;
-    }
-
-    bool timeAndValueValid = IsValid(aEvent.mValue) &&
-                             IsValid(aEvent.mDuration);
-    if (!timeAndValueValid) {
-      aRv.Throw(NS_ERROR_DOM_SYNTAX_ERR);
-      return false;
-    }
-
-    // Make sure that non-curve events don't fall within the duration of a
-    // curve event.
-    for (unsigned i = 0; i < mEvents.Length(); ++i) {
-      if (mEvents[i].mType == AudioTimelineEvent::SetValueCurve &&
-          !(aEvent.mType == AudioTimelineEvent::SetValueCurve &&
-            aEvent.template Time<double>() == mEvents[i].template Time<double>()) &&
-          mEvents[i].template Time<double>() <= aEvent.template Time<double>() &&
-          (mEvents[i].template Time<double>() + mEvents[i].mDuration) >= aEvent.template Time<double>()) {
-        aRv.Throw(NS_ERROR_DOM_SYNTAX_ERR);
-        return false;
-      }
-    }
-
-    // Make sure that curve events don't fall in a range which includes other
-    // events.
-    if (aEvent.mType == AudioTimelineEvent::SetValueCurve) {
-      for (unsigned i = 0; i < mEvents.Length(); ++i) {
-        // In case we have two curve at the same time
-        if (mEvents[i].mType == AudioTimelineEvent::SetValueCurve &&
-            mEvents[i].template Time<double>() == aEvent.template Time<double>()) {
-          continue;
-        }
-        if (mEvents[i].template Time<double>() > aEvent.template Time<double>() &&
-            mEvents[i].template Time<double>() < (aEvent.template Time<double>() + aEvent.mDuration)) {
-          aRv.Throw(NS_ERROR_DOM_SYNTAX_ERR);
-          return false;
-        }
-      }
-    }
-
-    // Make sure that invalid values are not used for exponential curves
-    if (aEvent.mType == AudioTimelineEvent::ExponentialRamp) {
-      if (aEvent.mValue <= 0.f) {
-        aRv.Throw(NS_ERROR_DOM_SYNTAX_ERR);
-        return false;
-      }
-      const AudioTimelineEvent* previousEvent = GetPreviousEvent(aEvent.template Time<double>());
-      if (previousEvent) {
-        if (previousEvent->mValue <= 0.f) {
-          aRv.Throw(NS_ERROR_DOM_SYNTAX_ERR);
-          return false;
-        }
-      } else {
-        if (mValue <= 0.f) {
-          aRv.Throw(NS_ERROR_DOM_SYNTAX_ERR);
-          return false;
-        }
-      }
-    }
-    return true;
-  }
+                     ErrorResult& aRv);
 
   template<typename TimeType>
   void InsertEvent(const AudioTimelineEvent& aEvent)
   {
     for (unsigned i = 0; i < mEvents.Length(); ++i) {
       if (aEvent.template Time<TimeType>() == mEvents[i].template Time<TimeType>()) {
         if (aEvent.mType == mEvents[i].mType) {
           // If times and types are equal, replace the event
@@ -312,52 +225,57 @@ public:
   void SetValue(float aValue)
   {
     // Silently don't change anything if there are any events
     if (mEvents.IsEmpty()) {
       mLastComputedValue = mComputedValue = mValue = aValue;
     }
   }
 
+  template <class ErrorResult>
   void SetValueAtTime(float aValue, double aStartTime, ErrorResult& aRv)
   {
     AudioTimelineEvent event(AudioTimelineEvent::SetValueAtTime, aStartTime, aValue);
 
     if (ValidateEvent(event, aRv)) {
       InsertEvent<double>(event);
     }
   }
 
+  template <class ErrorResult>
   void LinearRampToValueAtTime(float aValue, double aEndTime, ErrorResult& aRv)
   {
     AudioTimelineEvent event(AudioTimelineEvent::LinearRamp, aEndTime, aValue);
 
     if (ValidateEvent(event, aRv)) {
       InsertEvent<double>(event);
     }
   }
 
+  template <class ErrorResult>
   void ExponentialRampToValueAtTime(float aValue, double aEndTime, ErrorResult& aRv)
   {
     AudioTimelineEvent event(AudioTimelineEvent::ExponentialRamp, aEndTime, aValue);
 
     if (ValidateEvent(event, aRv)) {
       InsertEvent<double>(event);
     }
   }
 
+  template <class ErrorResult>
   void SetTargetAtTime(float aTarget, double aStartTime, double aTimeConstant, ErrorResult& aRv)
   {
     AudioTimelineEvent event(AudioTimelineEvent::SetTarget, aStartTime, aTarget, aTimeConstant);
 
     if (ValidateEvent(event, aRv)) {
       InsertEvent<double>(event);
     }
   }
 
+  template <class ErrorResult>
   void SetValueCurveAtTime(const float* aValues, uint32_t aValuesLength, double aStartTime, double aDuration, ErrorResult& aRv)
   {
     AudioTimelineEvent event(AudioTimelineEvent::SetValueCurve, aStartTime, 0.0f, 0.0f, aDuration, aValues, aValuesLength);
     if (ValidateEvent(event, aRv)) {
       InsertEvent<double>(event);
     }
   }
 
@@ -407,271 +325,33 @@ public:
   template<class TimeType>
   void GetValuesAtTime(TimeType aTime, float* aBuffer, const size_t aSize)
   {
     MOZ_ASSERT(aBuffer);
     GetValuesAtTimeHelper(aTime, aBuffer, aSize);
     mComputedValue = aBuffer[aSize - 1];
   }
 
-  // This method computes the AudioParam value at a given time based on the event timeline
-  template<class TimeType>
-  void GetValuesAtTimeHelper(TimeType aTime, float* aBuffer, const size_t aSize)
-  {
-    MOZ_ASSERT(aBuffer);
-    MOZ_ASSERT(aSize);
-
-    size_t lastEventId = 0;
-    const AudioTimelineEvent* previous = nullptr;
-    const AudioTimelineEvent* next = nullptr;
-    bool bailOut = false;
-
-    // Let's remove old events except the last one: we need it to calculate some curves.
-    while (mEvents.Length() > 1 &&
-           aTime > mEvents[1].template Time<TimeType>()) {
-      mEvents.RemoveElementAt(0);
-    }
-
-    for (size_t bufferIndex = 0; bufferIndex < aSize; ++bufferIndex, ++aTime) {
-      for (; !bailOut && lastEventId < mEvents.Length(); ++lastEventId) {
-
-#ifdef DEBUG
-        const AudioTimelineEvent* current = &mEvents[lastEventId];
-        MOZ_ASSERT(current->mType == AudioTimelineEvent::SetValueAtTime ||
-                   current->mType == AudioTimelineEvent::SetTarget ||
-                   current->mType == AudioTimelineEvent::LinearRamp ||
-                   current->mType == AudioTimelineEvent::ExponentialRamp ||
-                   current->mType == AudioTimelineEvent::SetValueCurve);
-#endif
-
-        if (TimesEqual(aTime, mEvents[lastEventId].template Time<TimeType>())) {
-          mLastComputedValue = mComputedValue;
-          // Find the last event with the same time
-          while (lastEventId < mEvents.Length() - 1 &&
-                 TimesEqual(aTime, mEvents[lastEventId + 1].template Time<TimeType>())) {
-            ++lastEventId;
-          }
-          break;
-        }
-
-        previous = next;
-        next = &mEvents[lastEventId];
-        if (aTime < mEvents[lastEventId].template Time<TimeType>()) {
-          bailOut = true;
-        }
-      }
-
-      if (!bailOut && lastEventId < mEvents.Length()) {
-        // The time matches one of the events exactly.
-        MOZ_ASSERT(TimesEqual(aTime, mEvents[lastEventId].template Time<TimeType>()));
-
-        // SetTarget nodes can be handled no matter what their next node is (if they have one)
-        if (mEvents[lastEventId].mType == AudioTimelineEvent::SetTarget) {
-          // Follow the curve, without regard to the next event, starting at
-          // the last value of the last event.
-          aBuffer[bufferIndex] = ExponentialApproach(mEvents[lastEventId].template Time<TimeType>(),
-                                                  mLastComputedValue, mEvents[lastEventId].mValue,
-                                                  mEvents[lastEventId].mTimeConstant, aTime);
-          continue;
-        }
-
-        // SetValueCurve events can be handled no matter what their event node is (if they have one)
-        if (mEvents[lastEventId].mType == AudioTimelineEvent::SetValueCurve) {
-          aBuffer[bufferIndex] = ExtractValueFromCurve(mEvents[lastEventId].template Time<TimeType>(),
-                                                    mEvents[lastEventId].mCurve,
-                                                    mEvents[lastEventId].mCurveLength,
-                                                    mEvents[lastEventId].mDuration, aTime);
-          continue;
-        }
-
-        // For other event types
-        aBuffer[bufferIndex] = mEvents[lastEventId].mValue;
-        continue;
-      }
-
-      // Handle the case where the time is past all of the events
-      if (!bailOut) {
-        aBuffer[bufferIndex] = GetValuesAtTimeHelperInternal(aTime, next, nullptr);
-      } else {
-        aBuffer[bufferIndex] = GetValuesAtTimeHelperInternal(aTime, previous, next);
-      }
-    }
-  }
-
   // Return the number of events scheduled
   uint32_t GetEventCount() const
   {
     return mEvents.Length();
   }
 
-  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)
-  {
-    return v1 + (v0 - v1) * expf(-(t - t0) / timeConstant);
-  }
-
-  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];
-    }
-    return aCurve[uint32_t(aCurveLength * ratio)];
-  }
+private:
+  template<class TimeType>
+  void GetValuesAtTimeHelper(TimeType aTime, float* aBuffer, const size_t aSize);
 
   template<class TimeType>
   float GetValuesAtTimeHelperInternal(TimeType aTime,
                                       const AudioTimelineEvent* aPrevious,
-                                      const AudioTimelineEvent* aNext)
-  {
-    // If the requested time is before all of the existing events
-    if (!aPrevious) {
-       return mValue;
-    }
-
-    // SetTarget nodes can be handled no matter what their next node is (if
-    // they have one)
-    if (aPrevious->mType == AudioTimelineEvent::SetTarget) {
-      return ExponentialApproach(aPrevious->template Time<TimeType>(),
-                                 mLastComputedValue, aPrevious->mValue,
-                                 aPrevious->mTimeConstant, aTime);
-    }
-
-    // SetValueCurve events can be handled no mattar what their next node is
-    // (if they have one)
-    if (aPrevious->mType == AudioTimelineEvent::SetValueCurve) {
-      return ExtractValueFromCurve(aPrevious->template 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->template 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::Stream:
-          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(aPrevious->template Time<TimeType>(),
-                               aPrevious->mValue,
-                               aNext->template Time<TimeType>(),
-                               aNext->mValue, aTime);
-
-    case AudioTimelineEvent::ExponentialRamp:
-      return ExponentialInterpolate(aPrevious->template Time<TimeType>(),
-                                    aPrevious->mValue,
-                                    aNext->template Time<TimeType>(),
-                                    aNext->mValue, aTime);
+                                      const AudioTimelineEvent* aNext);
 
-    case AudioTimelineEvent::SetValueAtTime:
-    case AudioTimelineEvent::SetTarget:
-    case AudioTimelineEvent::SetValueCurve:
-      break;
-    case AudioTimelineEvent::SetValue:
-    case AudioTimelineEvent::Cancel:
-    case AudioTimelineEvent::Stream:
-      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->template 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::Stream:
-      MOZ_ASSERT(false, "Should have been handled earlier.");
-    }
-
-    MOZ_ASSERT(false, "unreached");
-    return 0.0f;
-  }
+  const AudioTimelineEvent* GetPreviousEvent(double aTime) const;
 
-  const AudioTimelineEvent* GetPreviousEvent(double aTime) const
-  {
-    const AudioTimelineEvent* previous = nullptr;
-    const AudioTimelineEvent* next = nullptr;
-
-    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 == mEvents[i].template Time<double>()) {
-          // Find the last event with the same time
-          do {
-            ++i;
-          } while (i < mEvents.Length() &&
-                   aTime == mEvents[i].template Time<double>());
-          return &mEvents[i - 1];
-        }
-        previous = next;
-        next = &mEvents[i];
-        if (aTime < mEvents[i].template Time<double>()) {
-          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;
-  }
-private:
   static bool IsValid(double value)
   {
     return mozilla::IsFinite(value);
   }
 
   // This is a sorted array of the events in the timeline.  Queries of this
   // data structure should probably be more frequent than modifications to it,
   // and that is the reason why we're using a simple array as the data structure.
--- a/dom/media/webaudio/AudioParamTimeline.h
+++ b/dom/media/webaudio/AudioParamTimeline.h
@@ -18,19 +18,19 @@ namespace dom {
 
 // This helper class is used to represent the part of the AudioParam
 // class that gets sent to AudioNodeEngine instances.  In addition to
 // AudioEventTimeline methods, it holds a pointer to an optional
 // MediaStream which represents the AudioNode inputs to the AudioParam.
 // This MediaStream is managed by the AudioParam subclass on the main
 // thread, and can only be obtained from the AudioNodeEngine instances
 // consuming this class.
-class AudioParamTimeline : public AudioEventTimeline<ErrorResult>
+class AudioParamTimeline : public AudioEventTimeline
 {
-  typedef AudioEventTimeline<ErrorResult> BaseClass;
+  typedef AudioEventTimeline BaseClass;
 
 public:
   explicit AudioParamTimeline(float aDefaultValue)
     : BaseClass(aDefaultValue)
   {
   }
 
   MediaStream* Stream() const
--- a/dom/media/webaudio/compiledtest/TestAudioEventTimeline.cpp
+++ b/dom/media/webaudio/compiledtest/TestAudioEventTimeline.cpp
@@ -1,15 +1,15 @@
 /* -*- 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 "AudioEventTimeline.cpp"
 #include "TestHarness.h"
 #include <sstream>
 #include <limits>
 
 // Mock the MediaStream class
 namespace mozilla {
 class MediaStream
 {
@@ -87,17 +87,17 @@ public:
     mRv = aRv;
     return *this;
   }
 
 private:
   nsresult mRv;
 };
 
-typedef AudioEventTimeline<ErrorResultMock> Timeline;
+typedef AudioEventTimeline Timeline;
 
 void TestSpecExample()
 {
   // First, run the basic tests
   Timeline timeline(10.0f);
   is(timeline.Value(), 10.0f, "Correct default value returned");
 
   ErrorResultMock rv;
--- a/dom/media/webaudio/moz.build
+++ b/dom/media/webaudio/moz.build
@@ -76,16 +76,17 @@ EXPORTS.mozilla.dom += [
 
 UNIFIED_SOURCES += [
     'AnalyserNode.cpp',
     'AudioBlock.cpp',
     'AudioBuffer.cpp',
     'AudioBufferSourceNode.cpp',
     'AudioContext.cpp',
     'AudioDestinationNode.cpp',
+    'AudioEventTimeline.cpp',
     'AudioListener.cpp',
     'AudioNode.cpp',
     'AudioNodeEngine.cpp',
     'AudioNodeExternalInputStream.cpp',
     'AudioNodeStream.cpp',
     'AudioParam.cpp',
     'AudioProcessingEvent.cpp',
     'BiquadFilterNode.cpp',