content/media/AudioEventTimeline.h

Tue, 06 Jan 2015 21:39:09 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Tue, 06 Jan 2015 21:39:09 +0100
branch
TOR_BUG_9701
changeset 8
97036ab72558
permissions
-rw-r--r--

Conditionally force memory storage according to privacy.thirdparty.isolate;
This solves Tor bug #9701, complying with disk avoidance documented in
https://www.torproject.org/projects/torbrowser/design/#disk-avoidance.

     1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
     2 /* vim:set ts=2 sw=2 sts=2 et cindent: */
     3 /* This Source Code Form is subject to the terms of the Mozilla Public
     4  * License, v. 2.0. If a copy of the MPL was not distributed with this
     5  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
     7 #ifndef AudioEventTimeline_h_
     8 #define AudioEventTimeline_h_
    10 #include <algorithm>
    11 #include "mozilla/Assertions.h"
    12 #include "mozilla/FloatingPoint.h"
    13 #include "mozilla/TypedEnum.h"
    14 #include "mozilla/PodOperations.h"
    16 #include "nsTArray.h"
    17 #include "math.h"
    19 namespace mozilla {
    21 namespace dom {
    23 // This is an internal helper class and should not be used outside of this header.
    24 struct AudioTimelineEvent {
    25   enum Type MOZ_ENUM_TYPE(uint32_t) {
    26     SetValue,
    27     LinearRamp,
    28     ExponentialRamp,
    29     SetTarget,
    30     SetValueCurve
    31   };
    33   AudioTimelineEvent(Type aType, double aTime, float aValue, double aTimeConstant = 0.0,
    34                      float aDuration = 0.0, const float* aCurve = nullptr, uint32_t aCurveLength = 0)
    35     : mType(aType)
    36     , mTimeConstant(aTimeConstant)
    37     , mDuration(aDuration)
    38 #ifdef DEBUG
    39     , mTimeIsInTicks(false)
    40 #endif
    41   {
    42     mTime = aTime;
    43     if (aType == AudioTimelineEvent::SetValueCurve) {
    44       SetCurveParams(aCurve, aCurveLength);
    45     } else {
    46       mValue = aValue;
    47     }
    48   }
    50   AudioTimelineEvent(const AudioTimelineEvent& rhs)
    51   {
    52     PodCopy(this, &rhs, 1);
    53     if (rhs.mType == AudioTimelineEvent::SetValueCurve) {
    54       SetCurveParams(rhs.mCurve, rhs.mCurveLength);
    55     }
    56   }
    58   ~AudioTimelineEvent()
    59   {
    60     if (mType == AudioTimelineEvent::SetValueCurve) {
    61       delete[] mCurve;
    62     }
    63   }
    65   bool IsValid() const
    66   {
    67     if (mType == AudioTimelineEvent::SetValueCurve) {
    68       if (!mCurve || !mCurveLength) {
    69         return false;
    70       }
    71       for (uint32_t i = 0; i < mCurveLength; ++i) {
    72         if (!IsValid(mCurve[i])) {
    73           return false;
    74         }
    75       }
    76     }
    78     return IsValid(mTime) &&
    79            IsValid(mValue) &&
    80            IsValid(mTimeConstant) &&
    81            IsValid(mDuration);
    82   }
    84   template <class TimeType>
    85   TimeType Time() const;
    87   void SetTimeInTicks(int64_t aTimeInTicks)
    88   {
    89     mTimeInTicks = aTimeInTicks;
    90 #ifdef DEBUG
    91     mTimeIsInTicks = true;
    92 #endif
    93   }
    95   void SetCurveParams(const float* aCurve, uint32_t aCurveLength) {
    96     mCurveLength = aCurveLength;
    97     if (aCurveLength) {
    98       mCurve = new float[aCurveLength];
    99       PodCopy(mCurve, aCurve, aCurveLength);
   100     } else {
   101       mCurve = nullptr;
   102     }
   103   }
   105   Type mType;
   106   union {
   107     float mValue;
   108     uint32_t mCurveLength;
   109   };
   110   // The time for an event can either be in absolute value or in ticks.
   111   // Initially the time of the event is always in absolute value.
   112   // In order to convert it to ticks, call SetTimeInTicks.  Once this
   113   // method has been called for an event, the time cannot be converted
   114   // back to absolute value.
   115   union {
   116     double mTime;
   117     int64_t mTimeInTicks;
   118   };
   119   // mCurve contains a buffer of SetValueCurve samples.  We sample the
   120   // values in the buffer depending on how far along we are in time.
   121   // If we're at time T and the event has started as time T0 and has a
   122   // duration of D, we sample the buffer at floor(mCurveLength*(T-T0)/D)
   123   // if T<T0+D, and just take the last sample in the buffer otherwise.
   124   float* mCurve;
   125   double mTimeConstant;
   126   double mDuration;
   127 #ifdef DEBUG
   128   bool mTimeIsInTicks;
   129 #endif
   131 private:
   132   static bool IsValid(double value)
   133   {
   134     return mozilla::IsFinite(value);
   135   }
   136 };
   138 template <>
   139 inline double AudioTimelineEvent::Time<double>() const
   140 {
   141   MOZ_ASSERT(!mTimeIsInTicks);
   142   return mTime;
   143 }
   145 template <>
   146 inline int64_t AudioTimelineEvent::Time<int64_t>() const
   147 {
   148   MOZ_ASSERT(mTimeIsInTicks);
   149   return mTimeInTicks;
   150 }
   152 /**
   153  * This class will be instantiated with different template arguments for testing and
   154  * production code.
   155  *
   156  * ErrorResult is a type which satisfies the following:
   157  *  - Implements a Throw() method taking an nsresult argument, representing an error code.
   158  */
   159 template <class ErrorResult>
   160 class AudioEventTimeline
   161 {
   162 public:
   163   explicit AudioEventTimeline(float aDefaultValue)
   164     : mValue(aDefaultValue),
   165       mComputedValue(aDefaultValue),
   166       mLastComputedValue(aDefaultValue)
   167   {
   168   }
   170   bool HasSimpleValue() const
   171   {
   172     return mEvents.IsEmpty();
   173   }
   175   float GetValue() const
   176   {
   177     // This method should only be called if HasSimpleValue() returns true
   178     MOZ_ASSERT(HasSimpleValue());
   179     return mValue;
   180   }
   182   float Value() const
   183   {
   184     // TODO: Return the current value based on the timeline of the AudioContext
   185     return mValue;
   186   }
   188   void SetValue(float aValue)
   189   {
   190     // Silently don't change anything if there are any events
   191     if (mEvents.IsEmpty()) {
   192       mLastComputedValue = mComputedValue = mValue = aValue;
   193     }
   194   }
   196   void SetValueAtTime(float aValue, double aStartTime, ErrorResult& aRv)
   197   {
   198     InsertEvent(AudioTimelineEvent(AudioTimelineEvent::SetValue, aStartTime, aValue), aRv);
   199   }
   201   void LinearRampToValueAtTime(float aValue, double aEndTime, ErrorResult& aRv)
   202   {
   203     InsertEvent(AudioTimelineEvent(AudioTimelineEvent::LinearRamp, aEndTime, aValue), aRv);
   204   }
   206   void ExponentialRampToValueAtTime(float aValue, double aEndTime, ErrorResult& aRv)
   207   {
   208     InsertEvent(AudioTimelineEvent(AudioTimelineEvent::ExponentialRamp, aEndTime, aValue), aRv);
   209   }
   211   void SetTargetAtTime(float aTarget, double aStartTime, double aTimeConstant, ErrorResult& aRv)
   212   {
   213     InsertEvent(AudioTimelineEvent(AudioTimelineEvent::SetTarget, aStartTime, aTarget, aTimeConstant), aRv);
   214   }
   216   void SetValueCurveAtTime(const float* aValues, uint32_t aValuesLength, double aStartTime, double aDuration, ErrorResult& aRv)
   217   {
   218     InsertEvent(AudioTimelineEvent(AudioTimelineEvent::SetValueCurve, aStartTime, 0.0f, 0.0f, aDuration, aValues, aValuesLength), aRv);
   219   }
   221   void CancelScheduledValues(double aStartTime)
   222   {
   223     for (unsigned i = 0; i < mEvents.Length(); ++i) {
   224       if (mEvents[i].mTime >= aStartTime) {
   225 #ifdef DEBUG
   226         // Sanity check: the array should be sorted, so all of the following
   227         // events should have a time greater than aStartTime too.
   228         for (unsigned j = i + 1; j < mEvents.Length(); ++j) {
   229           MOZ_ASSERT(mEvents[j].mTime >= aStartTime);
   230         }
   231 #endif
   232         mEvents.TruncateLength(i);
   233         break;
   234       }
   235     }
   236   }
   238   void CancelAllEvents()
   239   {
   240     mEvents.Clear();
   241   }
   243   static bool TimesEqual(int64_t aLhs, int64_t aRhs)
   244   {
   245     return aLhs == aRhs;
   246   }
   248   // Since we are going to accumulate error by adding 0.01 multiple time in a
   249   // loop, we want to fuzz the equality check in GetValueAtTime.
   250   static bool TimesEqual(double aLhs, double aRhs)
   251   {
   252     const float kEpsilon = 0.0000000001f;
   253     return fabs(aLhs - aRhs) < kEpsilon;
   254   }
   256   template<class TimeType>
   257   float GetValueAtTime(TimeType aTime)
   258   {
   259     mComputedValue = GetValueAtTimeHelper(aTime);
   260     return mComputedValue;
   261   }
   263   // This method computes the AudioParam value at a given time based on the event timeline
   264   template<class TimeType>
   265   float GetValueAtTimeHelper(TimeType aTime)
   266   {
   267     const AudioTimelineEvent* previous = nullptr;
   268     const AudioTimelineEvent* next = nullptr;
   270     bool bailOut = false;
   271     for (unsigned i = 0; !bailOut && i < mEvents.Length(); ++i) {
   272       switch (mEvents[i].mType) {
   273       case AudioTimelineEvent::SetValue:
   274       case AudioTimelineEvent::SetTarget:
   275       case AudioTimelineEvent::LinearRamp:
   276       case AudioTimelineEvent::ExponentialRamp:
   277       case AudioTimelineEvent::SetValueCurve:
   278         if (TimesEqual(aTime, mEvents[i].template Time<TimeType>())) {
   279           mLastComputedValue = mComputedValue;
   280           // Find the last event with the same time
   281           do {
   282             ++i;
   283           } while (i < mEvents.Length() &&
   284                    aTime == mEvents[i].template Time<TimeType>());
   286           // SetTarget nodes can be handled no matter what their next node is (if they have one)
   287           if (mEvents[i - 1].mType == AudioTimelineEvent::SetTarget) {
   288             // Follow the curve, without regard to the next event, starting at
   289             // the last value of the last event.
   290             return ExponentialApproach(mEvents[i - 1].template Time<TimeType>(),
   291                                        mLastComputedValue, mEvents[i - 1].mValue,
   292                                        mEvents[i - 1].mTimeConstant, aTime);
   293           }
   295           // SetValueCurve events can be handled no matter what their event node is (if they have one)
   296           if (mEvents[i - 1].mType == AudioTimelineEvent::SetValueCurve) {
   297             return ExtractValueFromCurve(mEvents[i - 1].template Time<TimeType>(),
   298                                          mEvents[i - 1].mCurve,
   299                                          mEvents[i - 1].mCurveLength,
   300                                          mEvents[i - 1].mDuration, aTime);
   301           }
   303           // For other event types
   304           return mEvents[i - 1].mValue;
   305         }
   306         previous = next;
   307         next = &mEvents[i];
   308         if (aTime < mEvents[i].template Time<TimeType>()) {
   309           bailOut = true;
   310         }
   311         break;
   312       default:
   313         MOZ_ASSERT(false, "unreached");
   314       }
   315     }
   316     // Handle the case where the time is past all of the events
   317     if (!bailOut) {
   318       previous = next;
   319       next = nullptr;
   320     }
   322     // Just return the default value if we did not find anything
   323     if (!previous && !next) {
   324       return mValue;
   325     }
   327     // If the requested time is before all of the existing events
   328     if (!previous) {
   329       return mValue;
   330     }
   332     // SetTarget nodes can be handled no matter what their next node is (if they have one)
   333     if (previous->mType == AudioTimelineEvent::SetTarget) {
   334       return ExponentialApproach(previous->template Time<TimeType>(),
   335                                  mLastComputedValue, previous->mValue,
   336                                  previous->mTimeConstant, aTime);
   337     }
   339     // SetValueCurve events can be handled no mattar what their next node is (if they have one)
   340     if (previous->mType == AudioTimelineEvent::SetValueCurve) {
   341       return ExtractValueFromCurve(previous->template Time<TimeType>(),
   342                                    previous->mCurve, previous->mCurveLength,
   343                                    previous->mDuration, aTime);
   344     }
   346     // If the requested time is after all of the existing events
   347     if (!next) {
   348       switch (previous->mType) {
   349       case AudioTimelineEvent::SetValue:
   350       case AudioTimelineEvent::LinearRamp:
   351       case AudioTimelineEvent::ExponentialRamp:
   352         // The value will be constant after the last event
   353         return previous->mValue;
   354       case AudioTimelineEvent::SetValueCurve:
   355         return ExtractValueFromCurve(previous->template Time<TimeType>(),
   356                                      previous->mCurve, previous->mCurveLength,
   357                                      previous->mDuration, aTime);
   358       case AudioTimelineEvent::SetTarget:
   359         MOZ_ASSERT(false, "unreached");
   360       }
   361       MOZ_ASSERT(false, "unreached");
   362     }
   364     // Finally, handle the case where we have both a previous and a next event
   366     // First, handle the case where our range ends up in a ramp event
   367     switch (next->mType) {
   368     case AudioTimelineEvent::LinearRamp:
   369       return LinearInterpolate(previous->template Time<TimeType>(), previous->mValue, next->template Time<TimeType>(), next->mValue, aTime);
   370     case AudioTimelineEvent::ExponentialRamp:
   371       return ExponentialInterpolate(previous->template Time<TimeType>(), previous->mValue, next->template Time<TimeType>(), next->mValue, aTime);
   372     case AudioTimelineEvent::SetValue:
   373     case AudioTimelineEvent::SetTarget:
   374     case AudioTimelineEvent::SetValueCurve:
   375       break;
   376     }
   378     // Now handle all other cases
   379     switch (previous->mType) {
   380     case AudioTimelineEvent::SetValue:
   381     case AudioTimelineEvent::LinearRamp:
   382     case AudioTimelineEvent::ExponentialRamp:
   383       // If the next event type is neither linear or exponential ramp, the
   384       // value is constant.
   385       return previous->mValue;
   386     case AudioTimelineEvent::SetValueCurve:
   387       return ExtractValueFromCurve(previous->template Time<TimeType>(),
   388                                    previous->mCurve, previous->mCurveLength,
   389                                    previous->mDuration, aTime);
   390     case AudioTimelineEvent::SetTarget:
   391       MOZ_ASSERT(false, "unreached");
   392     }
   394     MOZ_ASSERT(false, "unreached");
   395     return 0.0f;
   396   }
   398   // Return the number of events scheduled
   399   uint32_t GetEventCount() const
   400   {
   401     return mEvents.Length();
   402   }
   404   static float LinearInterpolate(double t0, float v0, double t1, float v1, double t)
   405   {
   406     return v0 + (v1 - v0) * ((t - t0) / (t1 - t0));
   407   }
   409   static float ExponentialInterpolate(double t0, float v0, double t1, float v1, double t)
   410   {
   411     return v0 * powf(v1 / v0, (t - t0) / (t1 - t0));
   412   }
   414   static float ExponentialApproach(double t0, double v0, float v1, double timeConstant, double t)
   415   {
   416     return v1 + (v0 - v1) * expf(-(t - t0) / timeConstant);
   417   }
   419   static float ExtractValueFromCurve(double startTime, float* aCurve, uint32_t aCurveLength, double duration, double t)
   420   {
   421     if (t >= startTime + duration) {
   422       // After the duration, return the last curve value
   423       return aCurve[aCurveLength - 1];
   424     }
   425     double ratio = std::max((t - startTime) / duration, 0.0);
   426     if (ratio >= 1.0) {
   427       return aCurve[aCurveLength - 1];
   428     }
   429     return aCurve[uint32_t(aCurveLength * ratio)];
   430   }
   432   void ConvertEventTimesToTicks(int64_t (*aConvertor)(double aTime, void* aClosure), void* aClosure,
   433                                 int32_t aSampleRate)
   434   {
   435     for (unsigned i = 0; i < mEvents.Length(); ++i) {
   436       mEvents[i].SetTimeInTicks(aConvertor(mEvents[i].template Time<double>(), aClosure));
   437       mEvents[i].mTimeConstant *= aSampleRate;
   438       mEvents[i].mDuration *= aSampleRate;
   439     }
   440   }
   442 private:
   443   const AudioTimelineEvent* GetPreviousEvent(double aTime) const
   444   {
   445     const AudioTimelineEvent* previous = nullptr;
   446     const AudioTimelineEvent* next = nullptr;
   448     bool bailOut = false;
   449     for (unsigned i = 0; !bailOut && i < mEvents.Length(); ++i) {
   450       switch (mEvents[i].mType) {
   451       case AudioTimelineEvent::SetValue:
   452       case AudioTimelineEvent::SetTarget:
   453       case AudioTimelineEvent::LinearRamp:
   454       case AudioTimelineEvent::ExponentialRamp:
   455       case AudioTimelineEvent::SetValueCurve:
   456         if (aTime == mEvents[i].mTime) {
   457           // Find the last event with the same time
   458           do {
   459             ++i;
   460           } while (i < mEvents.Length() &&
   461                    aTime == mEvents[i].mTime);
   462           return &mEvents[i - 1];
   463         }
   464         previous = next;
   465         next = &mEvents[i];
   466         if (aTime < mEvents[i].mTime) {
   467           bailOut = true;
   468         }
   469         break;
   470       default:
   471         MOZ_ASSERT(false, "unreached");
   472       }
   473     }
   474     // Handle the case where the time is past all of the events
   475     if (!bailOut) {
   476       previous = next;
   477     }
   479     return previous;
   480   }
   482   void InsertEvent(const AudioTimelineEvent& aEvent, ErrorResult& aRv)
   483   {
   484     if (!aEvent.IsValid()) {
   485       aRv.Throw(NS_ERROR_DOM_SYNTAX_ERR);
   486       return;
   487     }
   489     // Make sure that non-curve events don't fall within the duration of a
   490     // curve event.
   491     for (unsigned i = 0; i < mEvents.Length(); ++i) {
   492       if (mEvents[i].mType == AudioTimelineEvent::SetValueCurve &&
   493           mEvents[i].mTime <= aEvent.mTime &&
   494           (mEvents[i].mTime + mEvents[i].mDuration) >= aEvent.mTime) {
   495         aRv.Throw(NS_ERROR_DOM_SYNTAX_ERR);
   496         return;
   497       }
   498     }
   500     // Make sure that curve events don't fall in a range which includes other
   501     // events.
   502     if (aEvent.mType == AudioTimelineEvent::SetValueCurve) {
   503       for (unsigned i = 0; i < mEvents.Length(); ++i) {
   504         if (mEvents[i].mTime > aEvent.mTime &&
   505             mEvents[i].mTime < (aEvent.mTime + aEvent.mDuration)) {
   506           aRv.Throw(NS_ERROR_DOM_SYNTAX_ERR);
   507           return;
   508         }
   509       }
   510     }
   512     // Make sure that invalid values are not used for exponential curves
   513     if (aEvent.mType == AudioTimelineEvent::ExponentialRamp) {
   514       if (aEvent.mValue <= 0.f) {
   515         aRv.Throw(NS_ERROR_DOM_SYNTAX_ERR);
   516         return;
   517       }
   518       const AudioTimelineEvent* previousEvent = GetPreviousEvent(aEvent.mTime);
   519       if (previousEvent) {
   520         if (previousEvent->mValue <= 0.f) {
   521           aRv.Throw(NS_ERROR_DOM_SYNTAX_ERR);
   522           return;
   523         }
   524       } else {
   525         if (mValue <= 0.f) {
   526           aRv.Throw(NS_ERROR_DOM_SYNTAX_ERR);
   527           return;
   528         }
   529       }
   530     }
   532     for (unsigned i = 0; i < mEvents.Length(); ++i) {
   533       if (aEvent.mTime == mEvents[i].mTime) {
   534         if (aEvent.mType == mEvents[i].mType) {
   535           // If times and types are equal, replace the event
   536           mEvents.ReplaceElementAt(i, aEvent);
   537         } else {
   538           // Otherwise, place the element after the last event of another type
   539           do {
   540             ++i;
   541           } while (i < mEvents.Length() &&
   542                    aEvent.mType != mEvents[i].mType &&
   543                    aEvent.mTime == mEvents[i].mTime);
   544           mEvents.InsertElementAt(i, aEvent);
   545         }
   546         return;
   547       }
   548       // Otherwise, place the event right after the latest existing event
   549       if (aEvent.mTime < mEvents[i].mTime) {
   550         mEvents.InsertElementAt(i, aEvent);
   551         return;
   552       }
   553     }
   555     // If we couldn't find a place for the event, just append it to the list
   556     mEvents.AppendElement(aEvent);
   557   }
   559 private:
   560   // This is a sorted array of the events in the timeline.  Queries of this
   561   // data structure should probably be more frequent than modifications to it,
   562   // and that is the reason why we're using a simple array as the data structure.
   563   // We can optimize this in the future if the performance of the array ends up
   564   // being a bottleneck.
   565   nsTArray<AudioTimelineEvent> mEvents;
   566   float mValue;
   567   // This is the value of this AudioParam we computed at the last call.
   568   float mComputedValue;
   569   // This is the value of this AudioParam at the last tick of the previous event.
   570   float mLastComputedValue;
   571 };
   573 }
   574 }
   576 #endif

mercurial