content/media/AudioEventTimeline.h

branch
TOR_BUG_9701
changeset 8
97036ab72558
equal deleted inserted replaced
-1:000000000000 0:fc203dc92350
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/TypedEnum.h"
14 #include "mozilla/PodOperations.h"
15
16 #include "nsTArray.h"
17 #include "math.h"
18
19 namespace mozilla {
20
21 namespace dom {
22
23 // This is an internal helper class and should not be used outside of this header.
24 struct AudioTimelineEvent {
25 enum Type MOZ_ENUM_TYPE(uint32_t) {
26 SetValue,
27 LinearRamp,
28 ExponentialRamp,
29 SetTarget,
30 SetValueCurve
31 };
32
33 AudioTimelineEvent(Type aType, double aTime, float aValue, double aTimeConstant = 0.0,
34 float aDuration = 0.0, const float* aCurve = nullptr, uint32_t aCurveLength = 0)
35 : mType(aType)
36 , mTimeConstant(aTimeConstant)
37 , mDuration(aDuration)
38 #ifdef DEBUG
39 , mTimeIsInTicks(false)
40 #endif
41 {
42 mTime = aTime;
43 if (aType == AudioTimelineEvent::SetValueCurve) {
44 SetCurveParams(aCurve, aCurveLength);
45 } else {
46 mValue = aValue;
47 }
48 }
49
50 AudioTimelineEvent(const AudioTimelineEvent& rhs)
51 {
52 PodCopy(this, &rhs, 1);
53 if (rhs.mType == AudioTimelineEvent::SetValueCurve) {
54 SetCurveParams(rhs.mCurve, rhs.mCurveLength);
55 }
56 }
57
58 ~AudioTimelineEvent()
59 {
60 if (mType == AudioTimelineEvent::SetValueCurve) {
61 delete[] mCurve;
62 }
63 }
64
65 bool IsValid() const
66 {
67 if (mType == AudioTimelineEvent::SetValueCurve) {
68 if (!mCurve || !mCurveLength) {
69 return false;
70 }
71 for (uint32_t i = 0; i < mCurveLength; ++i) {
72 if (!IsValid(mCurve[i])) {
73 return false;
74 }
75 }
76 }
77
78 return IsValid(mTime) &&
79 IsValid(mValue) &&
80 IsValid(mTimeConstant) &&
81 IsValid(mDuration);
82 }
83
84 template <class TimeType>
85 TimeType Time() const;
86
87 void SetTimeInTicks(int64_t aTimeInTicks)
88 {
89 mTimeInTicks = aTimeInTicks;
90 #ifdef DEBUG
91 mTimeIsInTicks = true;
92 #endif
93 }
94
95 void SetCurveParams(const float* aCurve, uint32_t aCurveLength) {
96 mCurveLength = aCurveLength;
97 if (aCurveLength) {
98 mCurve = new float[aCurveLength];
99 PodCopy(mCurve, aCurve, aCurveLength);
100 } else {
101 mCurve = nullptr;
102 }
103 }
104
105 Type mType;
106 union {
107 float mValue;
108 uint32_t mCurveLength;
109 };
110 // The time for an event can either be in absolute value or in ticks.
111 // Initially the time of the event is always in absolute value.
112 // In order to convert it to ticks, call SetTimeInTicks. Once this
113 // method has been called for an event, the time cannot be converted
114 // back to absolute value.
115 union {
116 double mTime;
117 int64_t mTimeInTicks;
118 };
119 // mCurve contains a buffer of SetValueCurve samples. We sample the
120 // values in the buffer depending on how far along we are in time.
121 // If we're at time T and the event has started as time T0 and has a
122 // duration of D, we sample the buffer at floor(mCurveLength*(T-T0)/D)
123 // if T<T0+D, and just take the last sample in the buffer otherwise.
124 float* mCurve;
125 double mTimeConstant;
126 double mDuration;
127 #ifdef DEBUG
128 bool mTimeIsInTicks;
129 #endif
130
131 private:
132 static bool IsValid(double value)
133 {
134 return mozilla::IsFinite(value);
135 }
136 };
137
138 template <>
139 inline double AudioTimelineEvent::Time<double>() const
140 {
141 MOZ_ASSERT(!mTimeIsInTicks);
142 return mTime;
143 }
144
145 template <>
146 inline int64_t AudioTimelineEvent::Time<int64_t>() const
147 {
148 MOZ_ASSERT(mTimeIsInTicks);
149 return mTimeInTicks;
150 }
151
152 /**
153 * This class will be instantiated with different template arguments for testing and
154 * production code.
155 *
156 * ErrorResult is a type which satisfies the following:
157 * - Implements a Throw() method taking an nsresult argument, representing an error code.
158 */
159 template <class ErrorResult>
160 class AudioEventTimeline
161 {
162 public:
163 explicit AudioEventTimeline(float aDefaultValue)
164 : mValue(aDefaultValue),
165 mComputedValue(aDefaultValue),
166 mLastComputedValue(aDefaultValue)
167 {
168 }
169
170 bool HasSimpleValue() const
171 {
172 return mEvents.IsEmpty();
173 }
174
175 float GetValue() const
176 {
177 // This method should only be called if HasSimpleValue() returns true
178 MOZ_ASSERT(HasSimpleValue());
179 return mValue;
180 }
181
182 float Value() const
183 {
184 // TODO: Return the current value based on the timeline of the AudioContext
185 return mValue;
186 }
187
188 void SetValue(float aValue)
189 {
190 // Silently don't change anything if there are any events
191 if (mEvents.IsEmpty()) {
192 mLastComputedValue = mComputedValue = mValue = aValue;
193 }
194 }
195
196 void SetValueAtTime(float aValue, double aStartTime, ErrorResult& aRv)
197 {
198 InsertEvent(AudioTimelineEvent(AudioTimelineEvent::SetValue, aStartTime, aValue), aRv);
199 }
200
201 void LinearRampToValueAtTime(float aValue, double aEndTime, ErrorResult& aRv)
202 {
203 InsertEvent(AudioTimelineEvent(AudioTimelineEvent::LinearRamp, aEndTime, aValue), aRv);
204 }
205
206 void ExponentialRampToValueAtTime(float aValue, double aEndTime, ErrorResult& aRv)
207 {
208 InsertEvent(AudioTimelineEvent(AudioTimelineEvent::ExponentialRamp, aEndTime, aValue), aRv);
209 }
210
211 void SetTargetAtTime(float aTarget, double aStartTime, double aTimeConstant, ErrorResult& aRv)
212 {
213 InsertEvent(AudioTimelineEvent(AudioTimelineEvent::SetTarget, aStartTime, aTarget, aTimeConstant), aRv);
214 }
215
216 void SetValueCurveAtTime(const float* aValues, uint32_t aValuesLength, double aStartTime, double aDuration, ErrorResult& aRv)
217 {
218 InsertEvent(AudioTimelineEvent(AudioTimelineEvent::SetValueCurve, aStartTime, 0.0f, 0.0f, aDuration, aValues, aValuesLength), aRv);
219 }
220
221 void CancelScheduledValues(double aStartTime)
222 {
223 for (unsigned i = 0; i < mEvents.Length(); ++i) {
224 if (mEvents[i].mTime >= aStartTime) {
225 #ifdef DEBUG
226 // Sanity check: the array should be sorted, so all of the following
227 // events should have a time greater than aStartTime too.
228 for (unsigned j = i + 1; j < mEvents.Length(); ++j) {
229 MOZ_ASSERT(mEvents[j].mTime >= aStartTime);
230 }
231 #endif
232 mEvents.TruncateLength(i);
233 break;
234 }
235 }
236 }
237
238 void CancelAllEvents()
239 {
240 mEvents.Clear();
241 }
242
243 static bool TimesEqual(int64_t aLhs, int64_t aRhs)
244 {
245 return aLhs == aRhs;
246 }
247
248 // Since we are going to accumulate error by adding 0.01 multiple time in a
249 // loop, we want to fuzz the equality check in GetValueAtTime.
250 static bool TimesEqual(double aLhs, double aRhs)
251 {
252 const float kEpsilon = 0.0000000001f;
253 return fabs(aLhs - aRhs) < kEpsilon;
254 }
255
256 template<class TimeType>
257 float GetValueAtTime(TimeType aTime)
258 {
259 mComputedValue = GetValueAtTimeHelper(aTime);
260 return mComputedValue;
261 }
262
263 // This method computes the AudioParam value at a given time based on the event timeline
264 template<class TimeType>
265 float GetValueAtTimeHelper(TimeType aTime)
266 {
267 const AudioTimelineEvent* previous = nullptr;
268 const AudioTimelineEvent* next = nullptr;
269
270 bool bailOut = false;
271 for (unsigned i = 0; !bailOut && i < mEvents.Length(); ++i) {
272 switch (mEvents[i].mType) {
273 case AudioTimelineEvent::SetValue:
274 case AudioTimelineEvent::SetTarget:
275 case AudioTimelineEvent::LinearRamp:
276 case AudioTimelineEvent::ExponentialRamp:
277 case AudioTimelineEvent::SetValueCurve:
278 if (TimesEqual(aTime, mEvents[i].template Time<TimeType>())) {
279 mLastComputedValue = mComputedValue;
280 // Find the last event with the same time
281 do {
282 ++i;
283 } while (i < mEvents.Length() &&
284 aTime == mEvents[i].template Time<TimeType>());
285
286 // SetTarget nodes can be handled no matter what their next node is (if they have one)
287 if (mEvents[i - 1].mType == AudioTimelineEvent::SetTarget) {
288 // Follow the curve, without regard to the next event, starting at
289 // the last value of the last event.
290 return ExponentialApproach(mEvents[i - 1].template Time<TimeType>(),
291 mLastComputedValue, mEvents[i - 1].mValue,
292 mEvents[i - 1].mTimeConstant, aTime);
293 }
294
295 // SetValueCurve events can be handled no matter what their event node is (if they have one)
296 if (mEvents[i - 1].mType == AudioTimelineEvent::SetValueCurve) {
297 return ExtractValueFromCurve(mEvents[i - 1].template Time<TimeType>(),
298 mEvents[i - 1].mCurve,
299 mEvents[i - 1].mCurveLength,
300 mEvents[i - 1].mDuration, aTime);
301 }
302
303 // For other event types
304 return mEvents[i - 1].mValue;
305 }
306 previous = next;
307 next = &mEvents[i];
308 if (aTime < mEvents[i].template Time<TimeType>()) {
309 bailOut = true;
310 }
311 break;
312 default:
313 MOZ_ASSERT(false, "unreached");
314 }
315 }
316 // Handle the case where the time is past all of the events
317 if (!bailOut) {
318 previous = next;
319 next = nullptr;
320 }
321
322 // Just return the default value if we did not find anything
323 if (!previous && !next) {
324 return mValue;
325 }
326
327 // If the requested time is before all of the existing events
328 if (!previous) {
329 return mValue;
330 }
331
332 // SetTarget nodes can be handled no matter what their next node is (if they have one)
333 if (previous->mType == AudioTimelineEvent::SetTarget) {
334 return ExponentialApproach(previous->template Time<TimeType>(),
335 mLastComputedValue, previous->mValue,
336 previous->mTimeConstant, aTime);
337 }
338
339 // SetValueCurve events can be handled no mattar what their next node is (if they have one)
340 if (previous->mType == AudioTimelineEvent::SetValueCurve) {
341 return ExtractValueFromCurve(previous->template Time<TimeType>(),
342 previous->mCurve, previous->mCurveLength,
343 previous->mDuration, aTime);
344 }
345
346 // If the requested time is after all of the existing events
347 if (!next) {
348 switch (previous->mType) {
349 case AudioTimelineEvent::SetValue:
350 case AudioTimelineEvent::LinearRamp:
351 case AudioTimelineEvent::ExponentialRamp:
352 // The value will be constant after the last event
353 return previous->mValue;
354 case AudioTimelineEvent::SetValueCurve:
355 return ExtractValueFromCurve(previous->template Time<TimeType>(),
356 previous->mCurve, previous->mCurveLength,
357 previous->mDuration, aTime);
358 case AudioTimelineEvent::SetTarget:
359 MOZ_ASSERT(false, "unreached");
360 }
361 MOZ_ASSERT(false, "unreached");
362 }
363
364 // Finally, handle the case where we have both a previous and a next event
365
366 // First, handle the case where our range ends up in a ramp event
367 switch (next->mType) {
368 case AudioTimelineEvent::LinearRamp:
369 return LinearInterpolate(previous->template Time<TimeType>(), previous->mValue, next->template Time<TimeType>(), next->mValue, aTime);
370 case AudioTimelineEvent::ExponentialRamp:
371 return ExponentialInterpolate(previous->template Time<TimeType>(), previous->mValue, next->template Time<TimeType>(), next->mValue, aTime);
372 case AudioTimelineEvent::SetValue:
373 case AudioTimelineEvent::SetTarget:
374 case AudioTimelineEvent::SetValueCurve:
375 break;
376 }
377
378 // Now handle all other cases
379 switch (previous->mType) {
380 case AudioTimelineEvent::SetValue:
381 case AudioTimelineEvent::LinearRamp:
382 case AudioTimelineEvent::ExponentialRamp:
383 // If the next event type is neither linear or exponential ramp, the
384 // value is constant.
385 return previous->mValue;
386 case AudioTimelineEvent::SetValueCurve:
387 return ExtractValueFromCurve(previous->template Time<TimeType>(),
388 previous->mCurve, previous->mCurveLength,
389 previous->mDuration, aTime);
390 case AudioTimelineEvent::SetTarget:
391 MOZ_ASSERT(false, "unreached");
392 }
393
394 MOZ_ASSERT(false, "unreached");
395 return 0.0f;
396 }
397
398 // Return the number of events scheduled
399 uint32_t GetEventCount() const
400 {
401 return mEvents.Length();
402 }
403
404 static float LinearInterpolate(double t0, float v0, double t1, float v1, double t)
405 {
406 return v0 + (v1 - v0) * ((t - t0) / (t1 - t0));
407 }
408
409 static float ExponentialInterpolate(double t0, float v0, double t1, float v1, double t)
410 {
411 return v0 * powf(v1 / v0, (t - t0) / (t1 - t0));
412 }
413
414 static float ExponentialApproach(double t0, double v0, float v1, double timeConstant, double t)
415 {
416 return v1 + (v0 - v1) * expf(-(t - t0) / timeConstant);
417 }
418
419 static float ExtractValueFromCurve(double startTime, float* aCurve, uint32_t aCurveLength, double duration, double t)
420 {
421 if (t >= startTime + duration) {
422 // After the duration, return the last curve value
423 return aCurve[aCurveLength - 1];
424 }
425 double ratio = std::max((t - startTime) / duration, 0.0);
426 if (ratio >= 1.0) {
427 return aCurve[aCurveLength - 1];
428 }
429 return aCurve[uint32_t(aCurveLength * ratio)];
430 }
431
432 void ConvertEventTimesToTicks(int64_t (*aConvertor)(double aTime, void* aClosure), void* aClosure,
433 int32_t aSampleRate)
434 {
435 for (unsigned i = 0; i < mEvents.Length(); ++i) {
436 mEvents[i].SetTimeInTicks(aConvertor(mEvents[i].template Time<double>(), aClosure));
437 mEvents[i].mTimeConstant *= aSampleRate;
438 mEvents[i].mDuration *= aSampleRate;
439 }
440 }
441
442 private:
443 const AudioTimelineEvent* GetPreviousEvent(double aTime) const
444 {
445 const AudioTimelineEvent* previous = nullptr;
446 const AudioTimelineEvent* next = nullptr;
447
448 bool bailOut = false;
449 for (unsigned i = 0; !bailOut && i < mEvents.Length(); ++i) {
450 switch (mEvents[i].mType) {
451 case AudioTimelineEvent::SetValue:
452 case AudioTimelineEvent::SetTarget:
453 case AudioTimelineEvent::LinearRamp:
454 case AudioTimelineEvent::ExponentialRamp:
455 case AudioTimelineEvent::SetValueCurve:
456 if (aTime == mEvents[i].mTime) {
457 // Find the last event with the same time
458 do {
459 ++i;
460 } while (i < mEvents.Length() &&
461 aTime == mEvents[i].mTime);
462 return &mEvents[i - 1];
463 }
464 previous = next;
465 next = &mEvents[i];
466 if (aTime < mEvents[i].mTime) {
467 bailOut = true;
468 }
469 break;
470 default:
471 MOZ_ASSERT(false, "unreached");
472 }
473 }
474 // Handle the case where the time is past all of the events
475 if (!bailOut) {
476 previous = next;
477 }
478
479 return previous;
480 }
481
482 void InsertEvent(const AudioTimelineEvent& aEvent, ErrorResult& aRv)
483 {
484 if (!aEvent.IsValid()) {
485 aRv.Throw(NS_ERROR_DOM_SYNTAX_ERR);
486 return;
487 }
488
489 // Make sure that non-curve events don't fall within the duration of a
490 // curve event.
491 for (unsigned i = 0; i < mEvents.Length(); ++i) {
492 if (mEvents[i].mType == AudioTimelineEvent::SetValueCurve &&
493 mEvents[i].mTime <= aEvent.mTime &&
494 (mEvents[i].mTime + mEvents[i].mDuration) >= aEvent.mTime) {
495 aRv.Throw(NS_ERROR_DOM_SYNTAX_ERR);
496 return;
497 }
498 }
499
500 // Make sure that curve events don't fall in a range which includes other
501 // events.
502 if (aEvent.mType == AudioTimelineEvent::SetValueCurve) {
503 for (unsigned i = 0; i < mEvents.Length(); ++i) {
504 if (mEvents[i].mTime > aEvent.mTime &&
505 mEvents[i].mTime < (aEvent.mTime + aEvent.mDuration)) {
506 aRv.Throw(NS_ERROR_DOM_SYNTAX_ERR);
507 return;
508 }
509 }
510 }
511
512 // Make sure that invalid values are not used for exponential curves
513 if (aEvent.mType == AudioTimelineEvent::ExponentialRamp) {
514 if (aEvent.mValue <= 0.f) {
515 aRv.Throw(NS_ERROR_DOM_SYNTAX_ERR);
516 return;
517 }
518 const AudioTimelineEvent* previousEvent = GetPreviousEvent(aEvent.mTime);
519 if (previousEvent) {
520 if (previousEvent->mValue <= 0.f) {
521 aRv.Throw(NS_ERROR_DOM_SYNTAX_ERR);
522 return;
523 }
524 } else {
525 if (mValue <= 0.f) {
526 aRv.Throw(NS_ERROR_DOM_SYNTAX_ERR);
527 return;
528 }
529 }
530 }
531
532 for (unsigned i = 0; i < mEvents.Length(); ++i) {
533 if (aEvent.mTime == mEvents[i].mTime) {
534 if (aEvent.mType == mEvents[i].mType) {
535 // If times and types are equal, replace the event
536 mEvents.ReplaceElementAt(i, aEvent);
537 } else {
538 // Otherwise, place the element after the last event of another type
539 do {
540 ++i;
541 } while (i < mEvents.Length() &&
542 aEvent.mType != mEvents[i].mType &&
543 aEvent.mTime == mEvents[i].mTime);
544 mEvents.InsertElementAt(i, aEvent);
545 }
546 return;
547 }
548 // Otherwise, place the event right after the latest existing event
549 if (aEvent.mTime < mEvents[i].mTime) {
550 mEvents.InsertElementAt(i, aEvent);
551 return;
552 }
553 }
554
555 // If we couldn't find a place for the event, just append it to the list
556 mEvents.AppendElement(aEvent);
557 }
558
559 private:
560 // This is a sorted array of the events in the timeline. Queries of this
561 // data structure should probably be more frequent than modifications to it,
562 // and that is the reason why we're using a simple array as the data structure.
563 // We can optimize this in the future if the performance of the array ends up
564 // being a bottleneck.
565 nsTArray<AudioTimelineEvent> mEvents;
566 float mValue;
567 // This is the value of this AudioParam we computed at the last call.
568 float mComputedValue;
569 // This is the value of this AudioParam at the last tick of the previous event.
570 float mLastComputedValue;
571 };
572
573 }
574 }
575
576 #endif
577

mercurial