tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

AudioEventTimeline.h (12309B)


      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/. */
      6 
      7 #ifndef AudioEventTimeline_h_
      8 #define AudioEventTimeline_h_
      9 
     10 #include "MainThreadUtils.h"
     11 #include "WebAudioUtils.h"
     12 #include "math.h"
     13 #include "mozilla/Assertions.h"
     14 #include "mozilla/DefineEnum.h"
     15 #include "mozilla/ErrorResult.h"
     16 #include "nsTArray.h"
     17 
     18 // XXX Avoid including this here by moving function bodies to the cpp file
     19 #include "js/GCAPI.h"
     20 
     21 namespace mozilla {
     22 
     23 class AudioNodeTrack;
     24 
     25 namespace dom {
     26 
     27 struct AudioTimelineEvent {
     28  MOZ_DEFINE_ENUM_WITH_BASE_AND_TOSTRING_AT_CLASS_SCOPE(
     29      Type, uint32_t,
     30      (SetValueAtTime, LinearRamp, ExponentialRamp, SetTarget, SetValueCurve,
     31       Track, Cancel));
     32 
     33  class TimeUnion {
     34   public:
     35    // double 0.0 is bit-identical to int64_t 0.
     36    TimeUnion()
     37        : mSeconds()
     38 #if DEBUG
     39          ,
     40          mIsInSeconds(true),
     41          mIsInTicks(true)
     42 #endif
     43    {
     44    }
     45    explicit TimeUnion(double aTime)
     46        : mSeconds(aTime)
     47 #if DEBUG
     48          ,
     49          mIsInSeconds(true),
     50          mIsInTicks(false)
     51 #endif
     52    {
     53    }
     54    explicit TimeUnion(int64_t aTime)
     55        : mTicks(aTime)
     56 #if DEBUG
     57          ,
     58          mIsInSeconds(false),
     59          mIsInTicks(true)
     60 #endif
     61    {
     62    }
     63 
     64    double operator=(double aTime) {
     65 #if DEBUG
     66      mIsInSeconds = true;
     67      mIsInTicks = true;
     68 #endif
     69      return mSeconds = aTime;
     70    }
     71    int64_t operator=(int64_t aTime) {
     72 #if DEBUG
     73      mIsInSeconds = true;
     74      mIsInTicks = true;
     75 #endif
     76      return mTicks = aTime;
     77    }
     78 
     79    template <class TimeType>
     80    TimeType Get() const;
     81 
     82   private:
     83    union {
     84      double mSeconds;
     85      int64_t mTicks;
     86    };
     87 #ifdef DEBUG
     88    bool mIsInSeconds;
     89    bool mIsInTicks;
     90 
     91   public:
     92    bool IsInTicks() const { return mIsInTicks; };
     93 #endif
     94  };
     95 
     96  AudioTimelineEvent(Type aType, double aTime, float aValue,
     97                     double aTimeConstant = 0.0);
     98  // For SetValueCurve
     99  AudioTimelineEvent(Type aType, const nsTArray<float>& aValues,
    100                     double aStartTime, double aDuration);
    101  AudioTimelineEvent(const AudioTimelineEvent& rhs);
    102  ~AudioTimelineEvent();
    103 
    104  template <class TimeType>
    105  TimeType Time() const {
    106    return mTime.Get<TimeType>();
    107  }
    108  // If this event is a curve event, this returns the end time of the curve.
    109  // Otherwise, this returns the time of the event.
    110  template <class TimeType>
    111  double EndTime() const;
    112 
    113  float NominalValue() const {
    114    MOZ_ASSERT(mType != SetValueCurve);
    115    return mValue;
    116  }
    117  float StartValue() const {
    118    MOZ_ASSERT(mType == SetValueCurve);
    119    return mCurve[0];
    120  }
    121  // Value for an event, or for a ValueCurve event, this is the value of the
    122  // last element of the curve.
    123  float EndValue() const;
    124 
    125  double TimeConstant() const {
    126    MOZ_ASSERT(mType == SetTarget);
    127    return mTimeConstant;
    128  }
    129  uint32_t CurveLength() const {
    130    MOZ_ASSERT(mType == SetValueCurve);
    131    return mCurveLength;
    132  }
    133  double Duration() const {
    134    MOZ_ASSERT(mType == SetValueCurve);
    135    return mDuration;
    136  }
    137  /**
    138   * Converts an AudioTimelineEvent's floating point time members to tick
    139   * values with respect to a destination AudioNodeTrack.
    140   *
    141   * This needs to be called for each AudioTimelineEvent that gets sent to an
    142   * AudioNodeEngine, on the engine side where the AudioTimlineEvent is
    143   * received.  This means that such engines need to be aware of their
    144   * destination tracks as well.
    145   */
    146  void ConvertToTicks(AudioNodeTrack* aDestination);
    147 
    148  template <class TimeType>
    149  void FillTargetApproach(TimeType aBufferStartTime, Span<float> aBuffer,
    150                          double v0) const;
    151  template <class TimeType>
    152  void FillFromValueCurve(TimeType aBufferStartTime, Span<float> aBuffer) const;
    153 
    154  const Type mType;
    155 
    156 private:
    157  union {
    158    float mValue;
    159    uint32_t mCurveLength;  // for SetValueCurve
    160  };
    161  union {
    162    double mTimeConstant;
    163    // mCurve contains a buffer of SetValueCurve samples.  We sample the
    164    // values in the buffer depending on how far along we are in time.
    165    // If we're at time T and the event has started as time T0 and has a
    166    // duration of D, we sample the buffer at floor(mCurveLength*(T-T0)/D)
    167    // if T<T0+D, and just take the last sample in the buffer otherwise.
    168    float* mCurve;
    169  };
    170  union {
    171    // mPerTickRatio is used only with SetTarget and int64_t TimeType.
    172    double mPerTickRatio;
    173    double mDuration;  // for SetValueCurve
    174  };
    175 
    176  // This member is accessed using the `Time` method.
    177  //
    178  // The time for an event can either be in seconds or in ticks.
    179  // Initially the time of the event is always in seconds.
    180  // In order to convert it to ticks, call SetTimeInTicks.  Once this
    181  // method has been called for an event, the time cannot be converted
    182  // back to seconds.
    183  TimeUnion mTime;
    184 };
    185 
    186 template <>
    187 inline double AudioTimelineEvent::TimeUnion::Get<double>() const {
    188  MOZ_ASSERT(mIsInSeconds);
    189  return mSeconds;
    190 }
    191 template <>
    192 inline int64_t AudioTimelineEvent::TimeUnion::Get<int64_t>() const {
    193  MOZ_ASSERT(mIsInTicks);
    194  return mTicks;
    195 }
    196 
    197 class AudioEventTimeline {
    198 public:
    199  explicit AudioEventTimeline(float aDefaultValue)
    200      : mDefaultValue(aDefaultValue),
    201        mSetTargetStartValue(aDefaultValue),
    202        mSimpleValue(Some(aDefaultValue)) {}
    203 
    204  bool ValidateEvent(const AudioTimelineEvent& aEvent, ErrorResult& aRv) const {
    205    MOZ_ASSERT(NS_IsMainThread());
    206 
    207    auto TimeOf = [](const AudioTimelineEvent& aEvent) -> double {
    208      return aEvent.Time<double>();
    209    };
    210 
    211    // Validate the event itself
    212    if (!WebAudioUtils::IsTimeValid(TimeOf(aEvent))) {
    213      aRv.ThrowRangeError<MSG_INVALID_AUDIOPARAM_METHOD_START_TIME_ERROR>();
    214      return false;
    215    }
    216 
    217    switch (aEvent.mType) {
    218      case AudioTimelineEvent::SetValueCurve:
    219        if (aEvent.CurveLength() < 2) {
    220          aRv.ThrowInvalidStateError("Curve length must be at least 2");
    221          return false;
    222        }
    223        if (aEvent.Duration() <= 0) {
    224          aRv.ThrowRangeError(
    225              "The curve duration for setValueCurveAtTime must be strictly "
    226              "positive.");
    227          return false;
    228        }
    229        MOZ_ASSERT(IsValid(aEvent.Duration()));
    230        break;
    231      case AudioTimelineEvent::SetTarget:
    232        if (!WebAudioUtils::IsTimeValid(aEvent.TimeConstant())) {
    233          aRv.ThrowRangeError(
    234              "The exponential constant passed to setTargetAtTime must be "
    235              "non-negative.");
    236          return false;
    237        }
    238        [[fallthrough]];
    239      default:
    240        MOZ_ASSERT(IsValid(aEvent.NominalValue()));
    241    }
    242 
    243    // Make sure that new events don't fall within the duration of a
    244    // curve event.
    245    for (unsigned i = 0; i < mEvents.Length(); ++i) {
    246      if (mEvents[i].mType == AudioTimelineEvent::SetValueCurve &&
    247          TimeOf(mEvents[i]) <= TimeOf(aEvent) &&
    248          TimeOf(mEvents[i]) + mEvents[i].Duration() > TimeOf(aEvent)) {
    249        aRv.ThrowNotSupportedError("Can't add events during a curve event");
    250        return false;
    251      }
    252    }
    253 
    254    // Make sure that new curve events don't fall in a range which includes
    255    // other events.
    256    if (aEvent.mType == AudioTimelineEvent::SetValueCurve) {
    257      for (unsigned i = 0; i < mEvents.Length(); ++i) {
    258        if (TimeOf(aEvent) < TimeOf(mEvents[i]) &&
    259            TimeOf(aEvent) + aEvent.Duration() > TimeOf(mEvents[i])) {
    260          aRv.ThrowNotSupportedError(
    261              "Can't add curve events that overlap other events");
    262          return false;
    263        }
    264      }
    265    }
    266 
    267    // Make sure that invalid values are not used for exponential curves
    268    if (aEvent.mType == AudioTimelineEvent::ExponentialRamp) {
    269      if (aEvent.NominalValue() == 0.f) {
    270        aRv.ThrowRangeError(
    271            "The value passed to exponentialRampToValueAtTime must be "
    272            "non-zero.");
    273        return false;
    274      }
    275    }
    276    return true;
    277  }
    278 
    279  template <typename TimeType>
    280  void InsertEvent(const AudioTimelineEvent& aEvent) {
    281    mSimpleValue.reset();
    282    for (unsigned i = 0; i < mEvents.Length(); ++i) {
    283      if (aEvent.Time<TimeType>() == mEvents[i].Time<TimeType>()) {
    284        // If two events happen at the same time, have them in chronological
    285        // order of insertion.
    286        do {
    287          ++i;
    288        } while (i < mEvents.Length() &&
    289                 aEvent.Time<TimeType>() == mEvents[i].Time<TimeType>());
    290        mEvents.InsertElementAt(i, aEvent);
    291        return;
    292      }
    293      // Otherwise, place the event right after the latest existing event
    294      if (aEvent.Time<TimeType>() < mEvents[i].Time<TimeType>()) {
    295        mEvents.InsertElementAt(i, aEvent);
    296        return;
    297      }
    298    }
    299 
    300    // If we couldn't find a place for the event, just append it to the list
    301    mEvents.AppendElement(aEvent);
    302  }
    303 
    304  bool HasSimpleValue() const { return mSimpleValue.isSome(); }
    305 
    306  float GetValue() const {
    307    // This method should only be called if HasSimpleValue() returns true
    308    MOZ_ASSERT(HasSimpleValue());
    309    return mSimpleValue.value();
    310  }
    311 
    312  template <typename TimeType>
    313  void CancelScheduledValues(TimeType aStartTime) {
    314    for (unsigned i = 0; i < mEvents.Length(); ++i) {
    315      if (mEvents[i].Time<TimeType>() >= aStartTime) {
    316 #ifdef DEBUG
    317        // Sanity check: the array should be sorted, so all of the following
    318        // events should have a time greater than aStartTime too.
    319        for (unsigned j = i + 1; j < mEvents.Length(); ++j) {
    320          MOZ_ASSERT(mEvents[j].Time<TimeType>() >= aStartTime);
    321        }
    322 #endif
    323        mEvents.TruncateLength(i);
    324        break;
    325      }
    326    }
    327    if (mEvents.IsEmpty()) {
    328      mSimpleValue = Some(mDefaultValue);
    329    }
    330  }
    331 
    332  static bool TimesEqual(int64_t aLhs, int64_t aRhs) { return aLhs == aRhs; }
    333 
    334  // Since we are going to accumulate error by adding 0.01 multiple time in a
    335  // loop, we want to fuzz the equality check in GetValueAtTime.
    336  static bool TimesEqual(double aLhs, double aRhs) {
    337    const float kEpsilon = 0.0000000001f;
    338    return fabs(aLhs - aRhs) < kEpsilon;
    339  }
    340 
    341  template <class TimeType>
    342  float GetValueAtTime(TimeType aTime) {
    343    float result;
    344    GetValuesAtTimeHelper(aTime, &result, 1);
    345    return result;
    346  }
    347 
    348  void GetValuesAtTime(int64_t aTime, float* aBuffer, const size_t aSize) {
    349    MOZ_ASSERT(aBuffer);
    350    GetValuesAtTimeHelper(aTime, aBuffer, aSize);
    351  }
    352  void GetValuesAtTime(double aTime, float* aBuffer,
    353                       const size_t aSize) = delete;
    354 
    355  // Return the number of events scheduled
    356  uint32_t GetEventCount() const { return mEvents.Length(); }
    357 
    358  template <class TimeType>
    359  void CleanupEventsOlderThan(TimeType aTime);
    360 
    361 private:
    362  template <class TimeType>
    363  void GetValuesAtTimeHelper(TimeType aTime, float* aBuffer,
    364                             const size_t aSize);
    365 
    366  template <class TimeType>
    367  float GetValueAtTimeOfEvent(const AudioTimelineEvent* aEvent,
    368                              const AudioTimelineEvent* aPrevious);
    369 
    370  template <class TimeType>
    371  void GetValuesAtTimeHelperInternal(TimeType aStartTime, Span<float> aBuffer,
    372                                     const AudioTimelineEvent* aPrevious,
    373                                     const AudioTimelineEvent* aNext);
    374 
    375  static bool IsValid(double value) { return std::isfinite(value); }
    376 
    377  template <class TimeType>
    378  float ComputeSetTargetStartValue(const AudioTimelineEvent* aPreviousEvent,
    379                                   TimeType aTime);
    380 
    381  // This is a sorted array of the events in the timeline.  Queries of this
    382  // data structure should probably be more frequent than modifications to it,
    383  // and that is the reason why we're using a simple array as the data
    384  // structure. We can optimize this in the future if the performance of the
    385  // array ends up being a bottleneck.
    386  nsTArray<AudioTimelineEvent> mEvents;
    387  float mDefaultValue;
    388  // This is the value of this AudioParam at the end of the previous
    389  // event for SetTarget curves.
    390  float mSetTargetStartValue;
    391  AudioTimelineEvent::TimeUnion mSetTargetStartTime;
    392  Maybe<float> mSimpleValue;
    393 };
    394 
    395 }  // namespace dom
    396 }  // namespace mozilla
    397 
    398 #endif