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