Line data Source code
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 :
15 : #include "MainThreadUtils.h"
16 : #include "nsTArray.h"
17 : #include "math.h"
18 : #include "WebAudioUtils.h"
19 :
20 : namespace mozilla {
21 :
22 : class MediaStream;
23 :
24 : namespace dom {
25 :
26 : struct AudioTimelineEvent final
27 : {
28 : enum Type : uint32_t
29 : {
30 : SetValue,
31 : SetValueAtTime,
32 : LinearRamp,
33 : ExponentialRamp,
34 : SetTarget,
35 : SetValueCurve,
36 : Stream,
37 : Cancel
38 : };
39 :
40 0 : AudioTimelineEvent(Type aType, double aTime, float aValue, double aTimeConstant = 0.0,
41 : double aDuration = 0.0, const float* aCurve = nullptr,
42 : uint32_t aCurveLength = 0)
43 0 : : mType(aType)
44 : , mCurve(nullptr)
45 : , mTimeConstant(aTimeConstant)
46 : , mDuration(aDuration)
47 : #ifdef DEBUG
48 0 : , mTimeIsInTicks(false)
49 : #endif
50 : {
51 0 : mTime = aTime;
52 0 : if (aType == AudioTimelineEvent::SetValueCurve) {
53 0 : SetCurveParams(aCurve, aCurveLength);
54 : } else {
55 0 : mValue = aValue;
56 : }
57 0 : }
58 :
59 0 : explicit AudioTimelineEvent(MediaStream* aStream)
60 0 : : mType(Stream)
61 : , mCurve(nullptr)
62 : , mStream(aStream)
63 : , mTimeConstant(0.0)
64 : , mDuration(0.0)
65 : #ifdef DEBUG
66 0 : , mTimeIsInTicks(false)
67 : #endif
68 : {
69 0 : }
70 :
71 0 : AudioTimelineEvent(const AudioTimelineEvent& rhs)
72 0 : {
73 0 : PodCopy(this, &rhs, 1);
74 :
75 0 : if (rhs.mType == AudioTimelineEvent::SetValueCurve) {
76 0 : SetCurveParams(rhs.mCurve, rhs.mCurveLength);
77 0 : } else if (rhs.mType == AudioTimelineEvent::Stream) {
78 0 : new (&mStream) decltype(mStream)(rhs.mStream);
79 : }
80 0 : }
81 :
82 0 : ~AudioTimelineEvent()
83 0 : {
84 0 : if (mType == AudioTimelineEvent::SetValueCurve) {
85 0 : delete[] mCurve;
86 : }
87 0 : }
88 :
89 : template <class TimeType>
90 : TimeType Time() const;
91 :
92 0 : void SetTimeInTicks(int64_t aTimeInTicks)
93 : {
94 0 : mTimeInTicks = aTimeInTicks;
95 : #ifdef DEBUG
96 0 : mTimeIsInTicks = true;
97 : #endif
98 0 : }
99 :
100 0 : void SetCurveParams(const float* aCurve, uint32_t aCurveLength) {
101 0 : mCurveLength = aCurveLength;
102 0 : if (aCurveLength) {
103 0 : mCurve = new float[aCurveLength];
104 0 : PodCopy(mCurve, aCurve, aCurveLength);
105 : } else {
106 0 : mCurve = nullptr;
107 : }
108 0 : }
109 :
110 : Type mType;
111 : union {
112 : float mValue;
113 : uint32_t mCurveLength;
114 : };
115 : // mCurve contains a buffer of SetValueCurve samples. We sample the
116 : // values in the buffer depending on how far along we are in time.
117 : // If we're at time T and the event has started as time T0 and has a
118 : // duration of D, we sample the buffer at floor(mCurveLength*(T-T0)/D)
119 : // if T<T0+D, and just take the last sample in the buffer otherwise.
120 : float* mCurve;
121 : RefPtr<MediaStream> mStream;
122 : double mTimeConstant;
123 : double mDuration;
124 : #ifdef DEBUG
125 : bool mTimeIsInTicks;
126 : #endif
127 :
128 : private:
129 : // This member is accessed using the `Time` method, for safety.
130 : //
131 : // The time for an event can either be in absolute value or in ticks.
132 : // Initially the time of the event is always in absolute value.
133 : // In order to convert it to ticks, call SetTimeInTicks. Once this
134 : // method has been called for an event, the time cannot be converted
135 : // back to absolute value.
136 : union {
137 : double mTime;
138 : int64_t mTimeInTicks;
139 : };
140 : };
141 :
142 : template <>
143 0 : inline double AudioTimelineEvent::Time<double>() const
144 : {
145 0 : MOZ_ASSERT(!mTimeIsInTicks);
146 0 : return mTime;
147 : }
148 :
149 : template <>
150 0 : inline int64_t AudioTimelineEvent::Time<int64_t>() const
151 : {
152 0 : MOZ_ASSERT(!NS_IsMainThread());
153 0 : MOZ_ASSERT(mTimeIsInTicks);
154 0 : return mTimeInTicks;
155 : }
156 :
157 : /**
158 : * Some methods in this class will be instantiated with different ErrorResult
159 : * template arguments for testing and production code.
160 : *
161 : * ErrorResult is a type which satisfies the following:
162 : * - Implements a Throw() method taking an nsresult argument, representing an error code.
163 : */
164 0 : class AudioEventTimeline
165 : {
166 : public:
167 0 : explicit AudioEventTimeline(float aDefaultValue)
168 0 : : mValue(aDefaultValue),
169 : mComputedValue(aDefaultValue),
170 0 : mLastComputedValue(aDefaultValue)
171 0 : { }
172 :
173 : template <class ErrorResult>
174 0 : bool ValidateEvent(AudioTimelineEvent& aEvent, ErrorResult& aRv)
175 : {
176 0 : MOZ_ASSERT(NS_IsMainThread());
177 :
178 0 : auto TimeOf = [](const AudioTimelineEvent& aEvent) -> double {
179 0 : return aEvent.template Time<double>();
180 : };
181 :
182 : // Validate the event itself
183 0 : if (!WebAudioUtils::IsTimeValid(TimeOf(aEvent)) ||
184 0 : !WebAudioUtils::IsTimeValid(aEvent.mTimeConstant)) {
185 0 : aRv.Throw(NS_ERROR_DOM_SYNTAX_ERR);
186 0 : return false;
187 : }
188 :
189 0 : if (aEvent.mType == AudioTimelineEvent::SetValueCurve) {
190 0 : if (!aEvent.mCurve || !aEvent.mCurveLength) {
191 0 : aRv.Throw(NS_ERROR_DOM_SYNTAX_ERR);
192 0 : return false;
193 : }
194 0 : for (uint32_t i = 0; i < aEvent.mCurveLength; ++i) {
195 0 : if (!IsValid(aEvent.mCurve[i])) {
196 0 : aRv.Throw(NS_ERROR_TYPE_ERR);
197 0 : return false;
198 : }
199 : }
200 : }
201 :
202 0 : bool timeAndValueValid = IsValid(aEvent.mValue) &&
203 0 : IsValid(aEvent.mDuration);
204 0 : if (!timeAndValueValid) {
205 0 : aRv.Throw(NS_ERROR_DOM_SYNTAX_ERR);
206 0 : return false;
207 : }
208 :
209 : // Make sure that non-curve events don't fall within the duration of a
210 : // curve event.
211 0 : for (unsigned i = 0; i < mEvents.Length(); ++i) {
212 0 : if (mEvents[i].mType == AudioTimelineEvent::SetValueCurve &&
213 0 : !(aEvent.mType == AudioTimelineEvent::SetValueCurve &&
214 0 : TimeOf(aEvent) == TimeOf(mEvents[i])) &&
215 0 : TimeOf(mEvents[i]) <= TimeOf(aEvent) &&
216 0 : TimeOf(mEvents[i]) + mEvents[i].mDuration >= TimeOf(aEvent)) {
217 0 : aRv.Throw(NS_ERROR_DOM_SYNTAX_ERR);
218 0 : return false;
219 : }
220 : }
221 :
222 : // Make sure that curve events don't fall in a range which includes other
223 : // events.
224 0 : if (aEvent.mType == AudioTimelineEvent::SetValueCurve) {
225 0 : for (unsigned i = 0; i < mEvents.Length(); ++i) {
226 : // In case we have two curve at the same time
227 0 : if (mEvents[i].mType == AudioTimelineEvent::SetValueCurve &&
228 0 : TimeOf(mEvents[i]) == TimeOf(aEvent)) {
229 0 : continue;
230 : }
231 0 : if (TimeOf(mEvents[i]) > TimeOf(aEvent) &&
232 0 : TimeOf(mEvents[i]) < TimeOf(aEvent) + aEvent.mDuration) {
233 0 : aRv.Throw(NS_ERROR_DOM_SYNTAX_ERR);
234 0 : return false;
235 : }
236 : }
237 : }
238 :
239 : // Make sure that invalid values are not used for exponential curves
240 0 : if (aEvent.mType == AudioTimelineEvent::ExponentialRamp) {
241 0 : if (aEvent.mValue <= 0.f) {
242 0 : aRv.Throw(NS_ERROR_DOM_SYNTAX_ERR);
243 0 : return false;
244 : }
245 0 : const AudioTimelineEvent* previousEvent = GetPreviousEvent(TimeOf(aEvent));
246 0 : if (previousEvent) {
247 0 : if (previousEvent->mValue <= 0.f) {
248 0 : aRv.Throw(NS_ERROR_DOM_SYNTAX_ERR);
249 0 : return false;
250 : }
251 : } else {
252 0 : if (mValue <= 0.f) {
253 0 : aRv.Throw(NS_ERROR_DOM_SYNTAX_ERR);
254 0 : return false;
255 : }
256 : }
257 : }
258 0 : return true;
259 : }
260 :
261 : template<typename TimeType>
262 0 : void InsertEvent(const AudioTimelineEvent& aEvent)
263 : {
264 0 : for (unsigned i = 0; i < mEvents.Length(); ++i) {
265 0 : if (aEvent.template Time<TimeType>() == mEvents[i].template Time<TimeType>()) {
266 0 : if (aEvent.mType == mEvents[i].mType) {
267 : // If times and types are equal, replace the event
268 0 : mEvents.ReplaceElementAt(i, aEvent);
269 : } else {
270 : // Otherwise, place the element after the last event of another type
271 0 : do {
272 0 : ++i;
273 0 : } while (i < mEvents.Length() &&
274 0 : aEvent.mType != mEvents[i].mType &&
275 0 : aEvent.template Time<TimeType>() == mEvents[i].template Time<TimeType>());
276 0 : mEvents.InsertElementAt(i, aEvent);
277 : }
278 0 : return;
279 : }
280 : // Otherwise, place the event right after the latest existing event
281 0 : if (aEvent.template Time<TimeType>() < mEvents[i].template Time<TimeType>()) {
282 0 : mEvents.InsertElementAt(i, aEvent);
283 0 : return;
284 : }
285 : }
286 :
287 : // If we couldn't find a place for the event, just append it to the list
288 0 : mEvents.AppendElement(aEvent);
289 : }
290 :
291 0 : bool HasSimpleValue() const
292 : {
293 0 : return mEvents.IsEmpty();
294 : }
295 :
296 0 : float GetValue() const
297 : {
298 : // This method should only be called if HasSimpleValue() returns true
299 0 : MOZ_ASSERT(HasSimpleValue());
300 0 : return mValue;
301 : }
302 :
303 0 : float Value() const
304 : {
305 : // TODO: Return the current value based on the timeline of the AudioContext
306 0 : return mValue;
307 : }
308 :
309 0 : void SetValue(float aValue)
310 : {
311 : // Silently don't change anything if there are any events
312 0 : if (mEvents.IsEmpty()) {
313 0 : mLastComputedValue = mComputedValue = mValue = aValue;
314 : }
315 0 : }
316 :
317 : template <class ErrorResult>
318 : void SetValueAtTime(float aValue, double aStartTime, ErrorResult& aRv)
319 : {
320 : AudioTimelineEvent event(AudioTimelineEvent::SetValueAtTime, aStartTime, aValue);
321 :
322 : if (ValidateEvent(event, aRv)) {
323 : InsertEvent<double>(event);
324 : }
325 : }
326 :
327 : template <class ErrorResult>
328 : void LinearRampToValueAtTime(float aValue, double aEndTime, ErrorResult& aRv)
329 : {
330 : AudioTimelineEvent event(AudioTimelineEvent::LinearRamp, aEndTime, aValue);
331 :
332 : if (ValidateEvent(event, aRv)) {
333 : InsertEvent<double>(event);
334 : }
335 : }
336 :
337 : template <class ErrorResult>
338 : void ExponentialRampToValueAtTime(float aValue, double aEndTime, ErrorResult& aRv)
339 : {
340 : AudioTimelineEvent event(AudioTimelineEvent::ExponentialRamp, aEndTime, aValue);
341 :
342 : if (ValidateEvent(event, aRv)) {
343 : InsertEvent<double>(event);
344 : }
345 : }
346 :
347 : template <class ErrorResult>
348 : void SetTargetAtTime(float aTarget, double aStartTime, double aTimeConstant, ErrorResult& aRv)
349 : {
350 : AudioTimelineEvent event(AudioTimelineEvent::SetTarget, aStartTime, aTarget, aTimeConstant);
351 :
352 : if (ValidateEvent(event, aRv)) {
353 : InsertEvent<double>(event);
354 : }
355 : }
356 :
357 : template <class ErrorResult>
358 : void SetValueCurveAtTime(const float* aValues, uint32_t aValuesLength, double aStartTime, double aDuration, ErrorResult& aRv)
359 : {
360 : AudioTimelineEvent event(AudioTimelineEvent::SetValueCurve, aStartTime, 0.0f, 0.0f, aDuration, aValues, aValuesLength);
361 : if (ValidateEvent(event, aRv)) {
362 : InsertEvent<double>(event);
363 : }
364 : }
365 :
366 : template<typename TimeType>
367 0 : void CancelScheduledValues(TimeType aStartTime)
368 : {
369 0 : for (unsigned i = 0; i < mEvents.Length(); ++i) {
370 0 : if (mEvents[i].template Time<TimeType>() >= aStartTime) {
371 : #ifdef DEBUG
372 : // Sanity check: the array should be sorted, so all of the following
373 : // events should have a time greater than aStartTime too.
374 0 : for (unsigned j = i + 1; j < mEvents.Length(); ++j) {
375 0 : MOZ_ASSERT(mEvents[j].template Time<TimeType>() >= aStartTime);
376 : }
377 : #endif
378 0 : mEvents.TruncateLength(i);
379 0 : break;
380 : }
381 : }
382 0 : }
383 :
384 : void CancelAllEvents()
385 : {
386 : mEvents.Clear();
387 : }
388 :
389 0 : static bool TimesEqual(int64_t aLhs, int64_t aRhs)
390 : {
391 0 : return aLhs == aRhs;
392 : }
393 :
394 : // Since we are going to accumulate error by adding 0.01 multiple time in a
395 : // loop, we want to fuzz the equality check in GetValueAtTime.
396 0 : static bool TimesEqual(double aLhs, double aRhs)
397 : {
398 0 : const float kEpsilon = 0.0000000001f;
399 0 : return fabs(aLhs - aRhs) < kEpsilon;
400 : }
401 :
402 : template<class TimeType>
403 0 : float GetValueAtTime(TimeType aTime)
404 : {
405 : float result;
406 0 : GetValuesAtTimeHelper(aTime, &result, 1);
407 0 : return result;
408 : }
409 :
410 : template<class TimeType>
411 0 : void GetValuesAtTime(TimeType aTime, float* aBuffer, const size_t aSize)
412 : {
413 0 : MOZ_ASSERT(aBuffer);
414 0 : GetValuesAtTimeHelper(aTime, aBuffer, aSize);
415 0 : }
416 :
417 : // Return the number of events scheduled
418 : uint32_t GetEventCount() const
419 : {
420 : return mEvents.Length();
421 : }
422 :
423 : template<class TimeType>
424 0 : void CleanupEventsOlderThan(TimeType aTime)
425 : {
426 0 : while (mEvents.Length() > 1 &&
427 0 : aTime > mEvents[1].template Time<TimeType>()) {
428 :
429 0 : if (mEvents[1].mType == AudioTimelineEvent::SetTarget) {
430 0 : mLastComputedValue = GetValuesAtTimeHelperInternal(
431 0 : mEvents[1].template Time<TimeType>(),
432 0 : &mEvents[0], nullptr);
433 : }
434 :
435 0 : mEvents.RemoveElementAt(0);
436 : }
437 0 : }
438 :
439 : private:
440 : template<class TimeType>
441 : void GetValuesAtTimeHelper(TimeType aTime, float* aBuffer, const size_t aSize);
442 :
443 : template<class TimeType>
444 : float GetValueAtTimeOfEvent(const AudioTimelineEvent* aNext);
445 :
446 : template<class TimeType>
447 : float GetValuesAtTimeHelperInternal(TimeType aTime,
448 : const AudioTimelineEvent* aPrevious,
449 : const AudioTimelineEvent* aNext);
450 :
451 : const AudioTimelineEvent* GetPreviousEvent(double aTime) const;
452 :
453 0 : static bool IsValid(double value)
454 : {
455 0 : return mozilla::IsFinite(value);
456 : }
457 :
458 : // This is a sorted array of the events in the timeline. Queries of this
459 : // data structure should probably be more frequent than modifications to it,
460 : // and that is the reason why we're using a simple array as the data structure.
461 : // We can optimize this in the future if the performance of the array ends up
462 : // being a bottleneck.
463 : nsTArray<AudioTimelineEvent> mEvents;
464 : float mValue;
465 : // This is the value of this AudioParam we computed at the last tick.
466 : float mComputedValue;
467 : // This is the value of this AudioParam at the last tick of the previous event.
468 : float mLastComputedValue;
469 : };
470 :
471 : } // namespace dom
472 : } // namespace mozilla
473 :
474 : #endif
|