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