tor-browser

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

AudioEventTimeline.cpp (19408B)


      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 #include "AudioEventTimeline.h"
      8 
      9 #include "AudioNodeTrack.h"
     10 #include "mozilla/ErrorResult.h"
     11 
     12 using mozilla::Span;
     13 
     14 // v1 and v0 are passed from float variables but converted to double for
     15 // double precision interpolation.
     16 static void FillLinearRamp(double aBufferStartTime, Span<float> aBuffer,
     17                           double t0, double v0, double t1, double v1) {
     18  double bufferStartDelta = aBufferStartTime - t0;
     19  double gradient = (v1 - v0) / (t1 - t0);
     20  for (size_t i = 0; i < aBuffer.Length(); ++i) {
     21    double v = v0 + (bufferStartDelta + static_cast<double>(i)) * gradient;
     22    aBuffer[i] = static_cast<float>(v);
     23  }
     24 }
     25 
     26 static void FillExponentialRamp(double aBufferStartTime, Span<float> aBuffer,
     27                                double t0, float v0, double t1, float v1) {
     28  MOZ_ASSERT(aBuffer.Length() >= 1);
     29  double fullRatio = static_cast<double>(v1) / v0;
     30  if (v0 == 0.f || fullRatio < 0.0) {
     31    std::fill_n(aBuffer.Elements(), aBuffer.Length(), v0);
     32    return;
     33  }
     34 
     35  double tDelta = t1 - t0;
     36  // Calculate the value for the first tick from the curve initial value.
     37  // v(t) = v0 * (v1/v0)^((t-t0)/(t1-t0))
     38  double exponent = (aBufferStartTime - t0) / tDelta;
     39  // The power function can amplify rounding error in the exponent by
     40  // ((t−t0)/(t1−t0)) ln (v1/v0).  The single precision exponent argument for
     41  // powf() would be sufficient when max(v1/v0,v0/v1) <= e, where e is Euler's
     42  // number, but fdlibm's single precision powf() is not expected to provide
     43  // speed advantages over double precision pow().
     44  double v = v0 * fdlibm_pow(fullRatio, exponent);
     45  aBuffer[0] = static_cast<float>(v);
     46  if (aBuffer.Length() == 1) {
     47    return;
     48  }
     49 
     50  // Use the inter-tick ratio to calculate values at other ticks.
     51  // v(t+1) = (v1/v0)^(1/(t1-t0)) * v(t)
     52  // Double precision is used so that accumulation of rounding error is not
     53  // significant.
     54  double tickRatio = fdlibm_pow(fullRatio, 1.0 / tDelta);
     55  for (size_t i = 1; i < aBuffer.Length(); ++i) {
     56    v *= tickRatio;
     57    aBuffer[i] = static_cast<float>(v);
     58  }
     59 }
     60 
     61 template <typename TimeType, typename DurationType>
     62 static size_t LimitedCountForDuration(size_t aMax, DurationType aDuration);
     63 
     64 template <>
     65 size_t LimitedCountForDuration<double>(size_t aMax, double aDuration) {
     66  // aDuration is in seconds, so tick arithmetic is inappropriate,
     67  // and unnecessary.
     68  // GetValuesAtTime() is not available, so at most one value is fetched.
     69  MOZ_ASSERT(aMax <= 1);
     70  return aMax;
     71 }
     72 template <>
     73 size_t LimitedCountForDuration<int64_t>(size_t aMax, int64_t aDuration) {
     74  MOZ_ASSERT(aDuration >= 0);
     75  // int64_t aDuration is in ticks.
     76  // On 32-bit systems, aDuration may be larger than SIZE_MAX.
     77  // Determine the larger with int64_t to avoid truncating before the
     78  // comparison.
     79  return static_cast<int64_t>(aMax) <= aDuration
     80             ? aMax
     81             : static_cast<size_t>(aDuration);
     82 }
     83 template <>
     84 size_t LimitedCountForDuration<int64_t>(size_t aMax, double aDuration) {
     85  MOZ_ASSERT(aDuration >= 0);
     86  // double aDuration is in ticks.
     87  // AudioTimelineEvent::mDuration may be larger than INT64_MAX.
     88  // On 32-bit systems, mDuration may be larger than SIZE_MAX.
     89  // Determine the larger with double to avoid truncating before the
     90  // comparison.
     91  return static_cast<double>(aMax) <= aDuration
     92             ? aMax
     93             : static_cast<size_t>(aDuration);
     94 }
     95 
     96 static float* NewCurveCopy(Span<const float> aCurve) {
     97  if (aCurve.Length() == 0) {
     98    return nullptr;
     99  }
    100 
    101  float* curve = new float[aCurve.Length()];
    102  mozilla::PodCopy(curve, aCurve.Elements(), aCurve.Length());
    103  return curve;
    104 }
    105 
    106 namespace mozilla::dom {
    107 
    108 AudioTimelineEvent::AudioTimelineEvent(Type aType, double aTime, float aValue,
    109                                       double aTimeConstant)
    110    : mType(aType),
    111      mValue(aValue),
    112      mTimeConstant(aTimeConstant),
    113      mPerTickRatio(std::numeric_limits<double>::quiet_NaN()),
    114      mTime(aTime) {}
    115 
    116 AudioTimelineEvent::AudioTimelineEvent(Type aType,
    117                                       const nsTArray<float>& aValues,
    118                                       double aStartTime, double aDuration)
    119    : mType(aType),
    120      mCurveLength(aValues.Length()),
    121      mCurve(NewCurveCopy(aValues)),
    122      mDuration(aDuration),
    123      mTime(aStartTime) {
    124  MOZ_ASSERT(aType == AudioTimelineEvent::SetValueCurve);
    125 }
    126 
    127 AudioTimelineEvent::AudioTimelineEvent(const AudioTimelineEvent& rhs)
    128    : mType(rhs.mType), mTime(rhs.mTime) {
    129  if (mType == AudioTimelineEvent::SetValueCurve) {
    130    mCurveLength = rhs.mCurveLength;
    131    mCurve = NewCurveCopy(Span(rhs.mCurve, rhs.mCurveLength));
    132    mDuration = rhs.mDuration;
    133  } else {
    134    mValue = rhs.mValue;
    135    mTimeConstant = rhs.mTimeConstant;
    136    mPerTickRatio = rhs.mPerTickRatio;
    137  }
    138 }
    139 
    140 AudioTimelineEvent::~AudioTimelineEvent() {
    141  if (mType == AudioTimelineEvent::SetValueCurve) {
    142    delete[] mCurve;
    143  }
    144 }
    145 
    146 template <class TimeType>
    147 double AudioTimelineEvent::EndTime() const {
    148  MOZ_ASSERT(mType != AudioTimelineEvent::SetTarget);
    149  if (mType == AudioTimelineEvent::SetValueCurve) {
    150    return Time<TimeType>() + mDuration;
    151  }
    152  return Time<TimeType>();
    153 };
    154 
    155 float AudioTimelineEvent::EndValue() const {
    156  if (mType == AudioTimelineEvent::SetValueCurve) {
    157    return mCurve[mCurveLength - 1];
    158  }
    159  return mValue;
    160 };
    161 
    162 void AudioTimelineEvent::ConvertToTicks(AudioNodeTrack* aDestination) {
    163  mTime = aDestination->SecondsToNearestTrackTime(mTime.Get<double>());
    164  switch (mType) {
    165    case SetTarget:
    166      mTimeConstant *= aDestination->mSampleRate;
    167      // exp(-1/timeConstant) is usually very close to 1, but its effect
    168      // depends on the difference from 1 and rounding errors would
    169      // accumulate, so use double precision to retain precision in the
    170      // difference.  Single precision expm1f() would be sufficient, but the
    171      // arithmetic in AudioTimelineEvent::FillTargetApproach() is simpler
    172      // with exp().
    173      mPerTickRatio =
    174          mTimeConstant == 0.0 ? 0.0 : fdlibm_exp(-1.0 / mTimeConstant);
    175 
    176      break;
    177    case SetValueCurve:
    178      mDuration *= aDestination->mSampleRate;
    179      break;
    180    default:
    181      break;
    182  }
    183 }
    184 
    185 template <class TimeType>
    186 void AudioTimelineEvent::FillTargetApproach(TimeType aBufferStartTime,
    187                                            Span<float> aBuffer,
    188                                            double v0) const {
    189  MOZ_ASSERT(mType == SetTarget);
    190  MOZ_ASSERT(aBuffer.Length() >= 1);
    191  double v1 = mValue;
    192  double vDelta = v0 - v1;
    193  if (vDelta == 0.0 || mTimeConstant == 0.0) {
    194    std::fill_n(aBuffer.Elements(), aBuffer.Length(), mValue);
    195    return;
    196  }
    197 
    198  // v(t) = v1 + vDelta(t) where vDelta(t) = (v0-v1) * e^(-(t-t0)/timeConstant).
    199  // Calculate the value for the first element in the buffer using this
    200  // formulation.
    201  vDelta *= fdlibm_expf(-(aBufferStartTime - Time<TimeType>()) / mTimeConstant);
    202  for (size_t i = 0; true;) {
    203    aBuffer[i] = static_cast<float>(v1 + vDelta);
    204    ++i;
    205    if (i == aBuffer.Length()) {
    206      return;
    207    }
    208    // For other buffer elements, use the pre-computed exp(-1/timeConstant)
    209    // for the inter-tick ratio of the difference from the target.
    210    // vDelta(t+1) = vDelta(t) * e^(-1/timeConstant)
    211    vDelta *= mPerTickRatio;
    212  }
    213 }
    214 
    215 static_assert(TRACK_TIME_MAX >> FloatingPoint<double>::kSignificandWidth == 0,
    216              "double precision must be exact for integer tick counts");
    217 template <class TimeType>
    218 void AudioTimelineEvent::FillFromValueCurve(TimeType aBufferStartTime,
    219                                            Span<float> aBuffer) const {
    220  MOZ_ASSERT(mType == SetValueCurve);
    221  double curveStartTime = Time<TimeType>();
    222  MOZ_ASSERT(aBufferStartTime >= curveStartTime);
    223  MOZ_ASSERT(aBufferStartTime - curveStartTime <= mDuration);
    224  MOZ_ASSERT((std::is_same<TimeType, int64_t>::value) || aBuffer.Length() == 1);
    225  MOZ_ASSERT((!std::is_same<TimeType, int64_t>::value) ||
    226             aBufferStartTime - curveStartTime + aBuffer.Length() - 1 <=
    227                 mDuration);
    228  uint32_t stepCount = mCurveLength - 1;
    229  double timeStep = mDuration / stepCount;
    230 
    231  for (size_t fillStart = 0; fillStart < aBuffer.Length();) {
    232    // Find the curve sample index, spec'd as `k`, corresponding to a time less
    233    // than or equal to the first buffer element to be filled.
    234    double stepPos =
    235        (aBufferStartTime + fillStart - curveStartTime) / mDuration * stepCount;
    236    // GetValuesAtTimeHelperInternal() calls this only when
    237    // aBufferStartTime + fillStart - curveStartTime <= mDuration.
    238    MOZ_ASSERT(stepPos >= 0 && stepPos <= UINT32_MAX - 1);
    239    uint32_t currentNode = floor(stepPos);
    240    if (currentNode >= stepCount) {
    241      auto remaining = aBuffer.From(fillStart);
    242      std::fill_n(remaining.Elements(), remaining.Length(), mCurve[stepCount]);
    243      return;
    244    }
    245 
    246    // Linearly interpolate to fill the buffer elements for any ticks between
    247    // curve samples k and k + 1 inclusive.
    248    double tCurrent = curveStartTime + currentNode * timeStep;
    249    uint32_t nextNode = currentNode + 1;
    250    double tNext = curveStartTime + nextNode * timeStep;
    251    // The first buffer index that cannot be filled with these curve samples
    252    size_t fillEnd = LimitedCountForDuration<TimeType>(
    253        aBuffer.Length(),
    254        // This parameter is used only when time is in ticks:
    255        // If tNext aligns exactly with a tick then fill to tNext, thus
    256        // ensuring that fillStart is advanced even when timeStep is so small
    257        // that tNext == tCurrent.
    258        floor(tNext - aBufferStartTime) + 1.0);
    259    TimeType fillStartTime =
    260        aBufferStartTime + static_cast<TimeType>(fillStart);
    261    FillLinearRamp(fillStartTime, aBuffer.FromTo(fillStart, fillEnd), tCurrent,
    262                   mCurve[currentNode], tNext, mCurve[nextNode]);
    263    fillStart = fillEnd;
    264  }
    265 }
    266 
    267 template <class TimeType>
    268 float AudioEventTimeline::ComputeSetTargetStartValue(
    269    const AudioTimelineEvent* aPreviousEvent, TimeType aTime) {
    270  mSetTargetStartTime = aTime;
    271  GetValuesAtTimeHelperInternal(aTime, Span(&mSetTargetStartValue, 1),
    272                                aPreviousEvent, nullptr);
    273  return mSetTargetStartValue;
    274 }
    275 
    276 template void AudioEventTimeline::CleanupEventsOlderThan(double);
    277 template void AudioEventTimeline::CleanupEventsOlderThan(int64_t);
    278 template <class TimeType>
    279 void AudioEventTimeline::CleanupEventsOlderThan(TimeType aTime) {
    280  auto TimeOf =
    281      [](const decltype(mEvents)::const_iterator& aEvent) -> TimeType {
    282    return aEvent->Time<TimeType>();
    283  };
    284 
    285  if (mSimpleValue.isSome()) {
    286    return;  // already only a single event
    287  }
    288 
    289  // Find first event to keep.  Keep one event prior to aTime.
    290  auto begin = mEvents.cbegin();
    291  auto end = mEvents.cend();
    292  auto event = begin + 1;
    293  for (; event < end && aTime > TimeOf(event); ++event) {
    294  }
    295  auto firstToKeep = event - 1;
    296 
    297  if (firstToKeep->mType != AudioTimelineEvent::SetTarget) {
    298    // The value is constant if there is a single remaining non-SetTarget event
    299    // that has already passed.
    300    if (end - firstToKeep == 1 && aTime >= firstToKeep->EndTime<TimeType>()) {
    301      mSimpleValue.emplace(firstToKeep->EndValue());
    302    }
    303  } else {
    304    // The firstToKeep event is a SetTarget.  Set its initial value if
    305    // not already set.  First find the most recent event where the value at
    306    // the end time of the event is known, either from the event or for
    307    // SetTarget events because it has already been calculated.  This may not
    308    // have been calculated if GetValuesAtTime() was not called for the start
    309    // time of the SetTarget event.
    310    for (event = firstToKeep;
    311         event > begin && event->mType == AudioTimelineEvent::SetTarget &&
    312         TimeOf(event) > mSetTargetStartTime.Get<TimeType>();
    313         --event) {
    314    }
    315    // Compute SetTarget start times.
    316    for (; event < firstToKeep; ++event) {
    317      MOZ_ASSERT((event + 1)->mType == AudioTimelineEvent::SetTarget);
    318      ComputeSetTargetStartValue(&*event, TimeOf(event + 1));
    319    }
    320  }
    321  if (firstToKeep == begin) {
    322    return;
    323  }
    324 
    325  mEvents.RemoveElementsRange(begin, firstToKeep);
    326 }
    327 
    328 // This method computes the AudioParam value at a given time based on the event
    329 // timeline
    330 template <class TimeType>
    331 void AudioEventTimeline::GetValuesAtTimeHelper(TimeType aTime, float* aBuffer,
    332                                               const size_t aSize) {
    333  MOZ_ASSERT(aBuffer);
    334  MOZ_ASSERT(aSize);
    335 
    336  auto TimeOf = [](const AudioTimelineEvent& aEvent) -> TimeType {
    337    return aEvent.Time<TimeType>();
    338  };
    339 
    340  size_t eventIndex = 0;
    341  const AudioTimelineEvent* previous = nullptr;
    342 
    343  // Let's remove old events except the last one: we need it to calculate some
    344  // curves.
    345  CleanupEventsOlderThan(aTime);
    346 
    347  for (size_t bufferIndex = 0; bufferIndex < aSize;) {
    348    bool timeMatchesEventIndex = false;
    349    const AudioTimelineEvent* next;
    350    for (;; ++eventIndex) {
    351      if (eventIndex >= mEvents.Length()) {
    352        next = nullptr;
    353        break;
    354      }
    355 
    356      next = &mEvents[eventIndex];
    357      if (aTime < TimeOf(*next)) {
    358        break;
    359      }
    360 
    361 #ifdef DEBUG
    362      MOZ_ASSERT(next->mType == AudioTimelineEvent::SetValueAtTime ||
    363                 next->mType == AudioTimelineEvent::SetTarget ||
    364                 next->mType == AudioTimelineEvent::LinearRamp ||
    365                 next->mType == AudioTimelineEvent::ExponentialRamp ||
    366                 next->mType == AudioTimelineEvent::SetValueCurve);
    367 #endif
    368 
    369      if (TimesEqual(aTime, TimeOf(*next))) {
    370        timeMatchesEventIndex = true;
    371        aBuffer[bufferIndex] = GetValueAtTimeOfEvent<TimeType>(next, previous);
    372        // Advance to next event, which may or may not have the same time.
    373      }
    374      previous = next;
    375    }
    376 
    377    if (timeMatchesEventIndex) {
    378      // The time matches one of the events exactly.
    379      MOZ_ASSERT(TimesEqual(aTime, TimeOf(mEvents[eventIndex - 1])));
    380      ++bufferIndex;
    381      ++aTime;
    382    } else {
    383      size_t count = aSize - bufferIndex;
    384      if (next) {
    385        count = LimitedCountForDuration<TimeType>(count, TimeOf(*next) - aTime);
    386      }
    387      GetValuesAtTimeHelperInternal(aTime, Span(aBuffer + bufferIndex, count),
    388                                    previous, next);
    389      bufferIndex += count;
    390      aTime += static_cast<TimeType>(count);
    391    }
    392  }
    393 }
    394 template void AudioEventTimeline::GetValuesAtTimeHelper(double aTime,
    395                                                        float* aBuffer,
    396                                                        const size_t aSize);
    397 template void AudioEventTimeline::GetValuesAtTimeHelper(int64_t aTime,
    398                                                        float* aBuffer,
    399                                                        const size_t aSize);
    400 
    401 template <class TimeType>
    402 float AudioEventTimeline::GetValueAtTimeOfEvent(
    403    const AudioTimelineEvent* aEvent, const AudioTimelineEvent* aPrevious) {
    404  TimeType time = aEvent->Time<TimeType>();
    405  switch (aEvent->mType) {
    406    case AudioTimelineEvent::SetTarget:
    407      // Start the curve, from the last value of the previous event.
    408      return ComputeSetTargetStartValue(aPrevious, time);
    409    case AudioTimelineEvent::SetValueCurve:
    410      return aEvent->StartValue();
    411    default:
    412      // For other event types
    413      return aEvent->NominalValue();
    414  }
    415 }
    416 
    417 template <class TimeType>
    418 void AudioEventTimeline::GetValuesAtTimeHelperInternal(
    419    TimeType aStartTime, Span<float> aBuffer,
    420    const AudioTimelineEvent* aPrevious, const AudioTimelineEvent* aNext) {
    421  MOZ_ASSERT(aBuffer.Length() >= 1);
    422  MOZ_ASSERT((std::is_same<TimeType, int64_t>::value) || aBuffer.Length() == 1);
    423 
    424  // If the requested time is before all of the existing events
    425  if (!aPrevious) {
    426    std::fill_n(aBuffer.Elements(), aBuffer.Length(), mDefaultValue);
    427    return;
    428  }
    429 
    430  auto TimeOf = [](const AudioTimelineEvent* aEvent) -> TimeType {
    431    return aEvent->Time<TimeType>();
    432  };
    433  auto EndTimeOf = [](const AudioTimelineEvent* aEvent) -> double {
    434    return aEvent->EndTime<TimeType>();
    435  };
    436 
    437  // SetTarget nodes can be handled no matter what their next node is (if
    438  // they have one)
    439  if (aPrevious->mType == AudioTimelineEvent::SetTarget) {
    440    aPrevious->FillTargetApproach(aStartTime, aBuffer, mSetTargetStartValue);
    441    return;
    442  }
    443 
    444  // SetValueCurve events can be handled no matter what their next node is
    445  // (if they have one), when aStartTime is in the curve region.
    446  if (aPrevious->mType == AudioTimelineEvent::SetValueCurve) {
    447    double remainingDuration =
    448        TimeOf(aPrevious) - aStartTime + aPrevious->Duration();
    449    if (remainingDuration >= 0.0) {
    450      // aBuffer.Length() is 1 if remainingDuration is not in ticks.
    451      size_t count = LimitedCountForDuration<TimeType>(
    452          aBuffer.Length(),
    453          // This parameter is used only when time is in ticks:
    454          // Fill the last tick in the curve before possible ramps below.
    455          floor(remainingDuration) + 1.0);
    456      // GetValueAtTimeOfEvent() will set the value at the end of the curve if
    457      // another event immediately follows.
    458      MOZ_ASSERT(!aNext ||
    459                 aStartTime + static_cast<TimeType>(count - 1) < TimeOf(aNext));
    460      aPrevious->FillFromValueCurve(aStartTime,
    461                                    Span(aBuffer.Elements(), count));
    462      aBuffer = aBuffer.From(count);
    463      if (aBuffer.Length() == 0) {
    464        return;
    465      }
    466      aStartTime += static_cast<TimeType>(count);
    467    }
    468  }
    469 
    470  // Handle the cases where our range ends up in a ramp event
    471  if (aNext) {
    472    switch (aNext->mType) {
    473      case AudioTimelineEvent::LinearRamp:
    474        FillLinearRamp(aStartTime, aBuffer, EndTimeOf(aPrevious),
    475                       aPrevious->EndValue(), TimeOf(aNext),
    476                       aNext->NominalValue());
    477        return;
    478      case AudioTimelineEvent::ExponentialRamp:
    479        FillExponentialRamp(aStartTime, aBuffer, EndTimeOf(aPrevious),
    480                            aPrevious->EndValue(), TimeOf(aNext),
    481                            aNext->NominalValue());
    482        return;
    483      case AudioTimelineEvent::SetValueAtTime:
    484      case AudioTimelineEvent::SetTarget:
    485      case AudioTimelineEvent::SetValueCurve:
    486        break;
    487      case AudioTimelineEvent::Cancel:
    488      case AudioTimelineEvent::Track:
    489        MOZ_ASSERT(false, "Should have been handled earlier.");
    490    }
    491  }
    492 
    493  // Now handle all other cases
    494  switch (aPrevious->mType) {
    495    case AudioTimelineEvent::SetValueAtTime:
    496    case AudioTimelineEvent::LinearRamp:
    497    case AudioTimelineEvent::ExponentialRamp:
    498      break;
    499    case AudioTimelineEvent::SetValueCurve:
    500      MOZ_ASSERT(aStartTime - TimeOf(aPrevious) >= aPrevious->Duration());
    501      break;
    502    case AudioTimelineEvent::SetTarget:
    503      MOZ_FALLTHROUGH_ASSERT("AudioTimelineEvent::SetTarget");
    504    case AudioTimelineEvent::Cancel:
    505    case AudioTimelineEvent::Track:
    506      MOZ_ASSERT(false, "Should have been handled earlier.");
    507  }
    508  // If the next event type is neither linear or exponential ramp, the
    509  // value is constant.
    510  std::fill_n(aBuffer.Elements(), aBuffer.Length(), aPrevious->EndValue());
    511 }
    512 
    513 }  // namespace mozilla::dom