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