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