/src/mozilla-central/dom/media/webaudio/AudioEventTimeline.cpp
Line | Count | Source (jump to first uncovered line) |
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 | | #include "AudioEventTimeline.h" |
8 | | #include "MediaStreamGraph.h" |
9 | | |
10 | | #include "mozilla/ErrorResult.h" |
11 | | |
12 | | static float LinearInterpolate(double t0, float v0, double t1, float v1, double t) |
13 | 0 | { |
14 | 0 | return v0 + (v1 - v0) * ((t - t0) / (t1 - t0)); |
15 | 0 | } |
16 | | |
17 | | static float ExponentialInterpolate(double t0, float v0, double t1, float v1, double t) |
18 | 0 | { |
19 | 0 | return v0 * powf(v1 / v0, (t - t0) / (t1 - t0)); |
20 | 0 | } |
21 | | |
22 | | static float ExponentialApproach(double t0, double v0, float v1, double timeConstant, double t) |
23 | 0 | { |
24 | 0 | if (!mozilla::dom::WebAudioUtils::FuzzyEqual(timeConstant, 0.0)) { |
25 | 0 | return v1 + (v0 - v1) * expf(-(t - t0) / timeConstant); |
26 | 0 | } else { |
27 | 0 | return v1; |
28 | 0 | } |
29 | 0 | } |
30 | | |
31 | | static float ExtractValueFromCurve(double startTime, float* aCurve, uint32_t aCurveLength, double duration, double t) |
32 | 0 | { |
33 | 0 | if (t >= startTime + duration) { |
34 | 0 | // After the duration, return the last curve value |
35 | 0 | return aCurve[aCurveLength - 1]; |
36 | 0 | } |
37 | 0 | double ratio = std::max((t - startTime) / duration, 0.0); |
38 | 0 | if (ratio >= 1.0) { |
39 | 0 | return aCurve[aCurveLength - 1]; |
40 | 0 | } |
41 | 0 | uint32_t current = uint32_t(floor((aCurveLength - 1) * ratio)); |
42 | 0 | uint32_t next = current + 1; |
43 | 0 | double step = duration / double(aCurveLength - 1); |
44 | 0 | if (next < aCurveLength) { |
45 | 0 | double t0 = current * step; |
46 | 0 | double t1 = next * step; |
47 | 0 | return LinearInterpolate(t0, aCurve[current], t1, aCurve[next], t - startTime); |
48 | 0 | } else { |
49 | 0 | return aCurve[current]; |
50 | 0 | } |
51 | 0 | } |
52 | | |
53 | | namespace mozilla { |
54 | | namespace dom { |
55 | | |
56 | | AudioTimelineEvent::AudioTimelineEvent(Type aType, |
57 | | double aTime, |
58 | | float aValue, |
59 | | double aTimeConstant, |
60 | | double aDuration, |
61 | | const float* aCurve, |
62 | | uint32_t aCurveLength) |
63 | | : mType(aType) |
64 | | , mCurve(nullptr) |
65 | | , mTimeConstant(aTimeConstant) |
66 | | , mDuration(aDuration) |
67 | | #ifdef DEBUG |
68 | | , mTimeIsInTicks(false) |
69 | | #endif |
70 | 0 | { |
71 | 0 | mTime = aTime; |
72 | 0 | if (aType == AudioTimelineEvent::SetValueCurve) { |
73 | 0 | SetCurveParams(aCurve, aCurveLength); |
74 | 0 | } else { |
75 | 0 | mValue = aValue; |
76 | 0 | } |
77 | 0 | } |
78 | | |
79 | | AudioTimelineEvent::AudioTimelineEvent(MediaStream* aStream) |
80 | | : mType(Stream) |
81 | | , mCurve(nullptr) |
82 | | , mStream(aStream) |
83 | | , mTimeConstant(0.0) |
84 | | , mDuration(0.0) |
85 | | #ifdef DEBUG |
86 | | , mTimeIsInTicks(false) |
87 | | #endif |
88 | | , mTime(0.0) |
89 | 0 | { |
90 | 0 | } |
91 | | |
92 | | AudioTimelineEvent::AudioTimelineEvent(const AudioTimelineEvent& rhs) |
93 | 0 | { |
94 | 0 | PodCopy(this, &rhs, 1); |
95 | 0 |
|
96 | 0 | if (rhs.mType == AudioTimelineEvent::SetValueCurve) { |
97 | 0 | SetCurveParams(rhs.mCurve, rhs.mCurveLength); |
98 | 0 | } else if (rhs.mType == AudioTimelineEvent::Stream) { |
99 | 0 | new (&mStream) decltype(mStream)(rhs.mStream); |
100 | 0 | } |
101 | 0 | } |
102 | | |
103 | | AudioTimelineEvent::~AudioTimelineEvent() |
104 | 0 | { |
105 | 0 | if (mType == AudioTimelineEvent::SetValueCurve) { |
106 | 0 | delete[] mCurve; |
107 | 0 | } |
108 | 0 | } |
109 | | |
110 | | // This method computes the AudioParam value at a given time based on the event timeline |
111 | | template<class TimeType> void |
112 | | AudioEventTimeline::GetValuesAtTimeHelper(TimeType aTime, float* aBuffer, |
113 | | const size_t aSize) |
114 | 0 | { |
115 | 0 | MOZ_ASSERT(aBuffer); |
116 | 0 | MOZ_ASSERT(aSize); |
117 | 0 |
|
118 | 0 | auto TimeOf = [](const AudioTimelineEvent& aEvent) -> TimeType { |
119 | 0 | return aEvent.Time<TimeType>(); |
120 | 0 | }; Unexecuted instantiation: void mozilla::dom::AudioEventTimeline::GetValuesAtTimeHelper<double>(double, float*, unsigned long)::{lambda(mozilla::dom::AudioTimelineEvent const&)#1}::operator()(mozilla::dom::AudioTimelineEvent const&) const Unexecuted instantiation: void mozilla::dom::AudioEventTimeline::GetValuesAtTimeHelper<long>(long, float*, unsigned long)::{lambda(mozilla::dom::AudioTimelineEvent const&)#1}::operator()(mozilla::dom::AudioTimelineEvent const&) const |
121 | 0 |
|
122 | 0 | size_t eventIndex = 0; |
123 | 0 | const AudioTimelineEvent* previous = nullptr; |
124 | 0 |
|
125 | 0 | // Let's remove old events except the last one: we need it to calculate some curves. |
126 | 0 | CleanupEventsOlderThan(aTime); |
127 | 0 |
|
128 | 0 | for (size_t bufferIndex = 0; bufferIndex < aSize; ++bufferIndex, ++aTime) { |
129 | 0 |
|
130 | 0 | bool timeMatchesEventIndex = false; |
131 | 0 | const AudioTimelineEvent* next; |
132 | 0 | for (; ; ++eventIndex) { |
133 | 0 |
|
134 | 0 | if (eventIndex >= mEvents.Length()) { |
135 | 0 | next = nullptr; |
136 | 0 | break; |
137 | 0 | } |
138 | 0 | |
139 | 0 | next = &mEvents[eventIndex]; |
140 | 0 | if (aTime < TimeOf(*next)) { |
141 | 0 | break; |
142 | 0 | } |
143 | 0 | |
144 | | #ifdef DEBUG |
145 | | MOZ_ASSERT(next->mType == AudioTimelineEvent::SetValueAtTime || |
146 | | next->mType == AudioTimelineEvent::SetTarget || |
147 | | next->mType == AudioTimelineEvent::LinearRamp || |
148 | | next->mType == AudioTimelineEvent::ExponentialRamp || |
149 | | next->mType == AudioTimelineEvent::SetValueCurve); |
150 | | #endif |
151 | | |
152 | 0 | if (TimesEqual(aTime, TimeOf(*next))) { |
153 | 0 | mLastComputedValue = mComputedValue; |
154 | 0 | // Find the last event with the same time |
155 | 0 | while (eventIndex < mEvents.Length() - 1 && |
156 | 0 | TimesEqual(aTime, TimeOf(mEvents[eventIndex + 1]))) { |
157 | 0 | mLastComputedValue = GetValueAtTimeOfEvent<TimeType>(&mEvents[eventIndex]); |
158 | 0 | ++eventIndex; |
159 | 0 | } |
160 | 0 |
|
161 | 0 | timeMatchesEventIndex = true; |
162 | 0 | break; |
163 | 0 | } |
164 | 0 |
|
165 | 0 | previous = next; |
166 | 0 | } |
167 | 0 |
|
168 | 0 | if (timeMatchesEventIndex) { |
169 | 0 | // The time matches one of the events exactly. |
170 | 0 | MOZ_ASSERT(TimesEqual(aTime, TimeOf(mEvents[eventIndex]))); |
171 | 0 | mComputedValue = GetValueAtTimeOfEvent<TimeType>(&mEvents[eventIndex]); |
172 | 0 | } else { |
173 | 0 | mComputedValue = GetValuesAtTimeHelperInternal(aTime, previous, next); |
174 | 0 | } |
175 | 0 |
|
176 | 0 | aBuffer[bufferIndex] = mComputedValue; |
177 | 0 | } |
178 | 0 | } Unexecuted instantiation: void mozilla::dom::AudioEventTimeline::GetValuesAtTimeHelper<double>(double, float*, unsigned long) Unexecuted instantiation: void mozilla::dom::AudioEventTimeline::GetValuesAtTimeHelper<long>(long, float*, unsigned long) |
179 | | template void |
180 | | AudioEventTimeline::GetValuesAtTimeHelper(double aTime, float* aBuffer, |
181 | | const size_t aSize); |
182 | | template void |
183 | | AudioEventTimeline::GetValuesAtTimeHelper(int64_t aTime, float* aBuffer, |
184 | | const size_t aSize); |
185 | | |
186 | | template<class TimeType> float |
187 | | AudioEventTimeline::GetValueAtTimeOfEvent(const AudioTimelineEvent* aNext) |
188 | 0 | { |
189 | 0 | TimeType time = aNext->Time<TimeType>(); |
190 | 0 | switch (aNext->mType) { |
191 | 0 | case AudioTimelineEvent::SetTarget: |
192 | 0 | // SetTarget nodes can be handled no matter what their next node is |
193 | 0 | // (if they have one). |
194 | 0 | // Follow the curve, without regard to the next event, starting at |
195 | 0 | // the last value of the last event. |
196 | 0 | return ExponentialApproach(time, |
197 | 0 | mLastComputedValue, aNext->mValue, |
198 | 0 | aNext->mTimeConstant, time); |
199 | 0 | break; |
200 | 0 | case AudioTimelineEvent::SetValueCurve: |
201 | 0 | // SetValueCurve events can be handled no matter what their event |
202 | 0 | // node is (if they have one) |
203 | 0 | return ExtractValueFromCurve(time, |
204 | 0 | aNext->mCurve, |
205 | 0 | aNext->mCurveLength, |
206 | 0 | aNext->mDuration, time); |
207 | 0 | break; |
208 | 0 | default: |
209 | 0 | // For other event types |
210 | 0 | return aNext->mValue; |
211 | 0 | } |
212 | 0 | } Unexecuted instantiation: float mozilla::dom::AudioEventTimeline::GetValueAtTimeOfEvent<double>(mozilla::dom::AudioTimelineEvent const*) Unexecuted instantiation: float mozilla::dom::AudioEventTimeline::GetValueAtTimeOfEvent<long>(mozilla::dom::AudioTimelineEvent const*) |
213 | | |
214 | | template<class TimeType> float |
215 | | AudioEventTimeline::GetValuesAtTimeHelperInternal(TimeType aTime, |
216 | | const AudioTimelineEvent* aPrevious, |
217 | | const AudioTimelineEvent* aNext) |
218 | 0 | { |
219 | 0 | // If the requested time is before all of the existing events |
220 | 0 | if (!aPrevious) { |
221 | 0 | return mValue; |
222 | 0 | } |
223 | 0 | |
224 | 0 | // If this event is a curve event, this returns the end time of the curve. |
225 | 0 | // Otherwise, this returns the time of the event. |
226 | 0 | auto TimeOf = [](const AudioTimelineEvent* aEvent) -> TimeType { |
227 | 0 | if (aEvent->mType == AudioTimelineEvent::SetValueCurve) { |
228 | 0 | return aEvent->Time<TimeType>() + aEvent->mDuration; |
229 | 0 | } |
230 | 0 | return aEvent->Time<TimeType>(); |
231 | 0 | }; Unexecuted instantiation: float mozilla::dom::AudioEventTimeline::GetValuesAtTimeHelperInternal<double>(double, mozilla::dom::AudioTimelineEvent const*, mozilla::dom::AudioTimelineEvent const*)::{lambda(mozilla::dom::AudioTimelineEvent const*)#1}::operator()(mozilla::dom::AudioTimelineEvent const*) const Unexecuted instantiation: float mozilla::dom::AudioEventTimeline::GetValuesAtTimeHelperInternal<long>(long, mozilla::dom::AudioTimelineEvent const*, mozilla::dom::AudioTimelineEvent const*)::{lambda(mozilla::dom::AudioTimelineEvent const*)#1}::operator()(mozilla::dom::AudioTimelineEvent const*) const |
232 | 0 |
|
233 | 0 | // Value for an event. For a ValueCurve event, this is the value of the last |
234 | 0 | // element of the curve. |
235 | 0 | auto ValueOf = [](const AudioTimelineEvent* aEvent) -> float { |
236 | 0 | if (aEvent->mType == AudioTimelineEvent::SetValueCurve) { |
237 | 0 | return aEvent->mCurve[aEvent->mCurveLength - 1]; |
238 | 0 | } |
239 | 0 | return aEvent->mValue; |
240 | 0 | }; Unexecuted instantiation: float mozilla::dom::AudioEventTimeline::GetValuesAtTimeHelperInternal<double>(double, mozilla::dom::AudioTimelineEvent const*, mozilla::dom::AudioTimelineEvent const*)::{lambda(mozilla::dom::AudioTimelineEvent const*)#2}::operator()(mozilla::dom::AudioTimelineEvent const*) const Unexecuted instantiation: float mozilla::dom::AudioEventTimeline::GetValuesAtTimeHelperInternal<long>(long, mozilla::dom::AudioTimelineEvent const*, mozilla::dom::AudioTimelineEvent const*)::{lambda(mozilla::dom::AudioTimelineEvent const*)#2}::operator()(mozilla::dom::AudioTimelineEvent const*) const |
241 | 0 |
|
242 | 0 | // SetTarget nodes can be handled no matter what their next node is (if |
243 | 0 | // they have one) |
244 | 0 | if (aPrevious->mType == AudioTimelineEvent::SetTarget) { |
245 | 0 | return ExponentialApproach(TimeOf(aPrevious), |
246 | 0 | mLastComputedValue, |
247 | 0 | ValueOf(aPrevious), |
248 | 0 | aPrevious->mTimeConstant, |
249 | 0 | aTime); |
250 | 0 | } |
251 | 0 | |
252 | 0 | // SetValueCurve events can be handled no matter what their next node is |
253 | 0 | // (if they have one), when aTime is in the curve region. |
254 | 0 | if (aPrevious->mType == AudioTimelineEvent::SetValueCurve && |
255 | 0 | aTime <= aPrevious->Time<TimeType>() + aPrevious->mDuration) { |
256 | 0 | return ExtractValueFromCurve(aPrevious->Time<TimeType>(), |
257 | 0 | aPrevious->mCurve, |
258 | 0 | aPrevious->mCurveLength, |
259 | 0 | aPrevious->mDuration, |
260 | 0 | aTime); |
261 | 0 | } |
262 | 0 | |
263 | 0 | // If the requested time is after all of the existing events |
264 | 0 | if (!aNext) { |
265 | 0 | switch (aPrevious->mType) { |
266 | 0 | case AudioTimelineEvent::SetValueAtTime: |
267 | 0 | case AudioTimelineEvent::LinearRamp: |
268 | 0 | case AudioTimelineEvent::ExponentialRamp: |
269 | 0 | // The value will be constant after the last event |
270 | 0 | return aPrevious->mValue; |
271 | 0 | case AudioTimelineEvent::SetValueCurve: |
272 | 0 | return ExtractValueFromCurve(aPrevious->Time<TimeType>(), |
273 | 0 | aPrevious->mCurve, |
274 | 0 | aPrevious->mCurveLength, |
275 | 0 | aPrevious->mDuration, |
276 | 0 | aTime); |
277 | 0 | case AudioTimelineEvent::SetTarget: |
278 | 0 | MOZ_FALLTHROUGH_ASSERT("AudioTimelineEvent::SetTarget"); |
279 | 0 | case AudioTimelineEvent::SetValue: |
280 | 0 | case AudioTimelineEvent::Cancel: |
281 | 0 | case AudioTimelineEvent::Stream: |
282 | 0 | MOZ_ASSERT(false, "Should have been handled earlier."); |
283 | 0 | } |
284 | 0 | MOZ_ASSERT(false, "unreached"); |
285 | 0 | } |
286 | 0 |
|
287 | 0 | // Finally, handle the case where we have both a previous and a next event |
288 | 0 |
|
289 | 0 | // First, handle the case where our range ends up in a ramp event |
290 | 0 | switch (aNext->mType) { |
291 | 0 | case AudioTimelineEvent::LinearRamp: |
292 | 0 | return LinearInterpolate(TimeOf(aPrevious), |
293 | 0 | ValueOf(aPrevious), |
294 | 0 | TimeOf(aNext), |
295 | 0 | ValueOf(aNext), |
296 | 0 | aTime); |
297 | 0 |
|
298 | 0 | case AudioTimelineEvent::ExponentialRamp: |
299 | 0 | return ExponentialInterpolate(TimeOf(aPrevious), |
300 | 0 | ValueOf(aPrevious), |
301 | 0 | TimeOf(aNext), |
302 | 0 | ValueOf(aNext), |
303 | 0 | aTime); |
304 | 0 |
|
305 | 0 | case AudioTimelineEvent::SetValueAtTime: |
306 | 0 | case AudioTimelineEvent::SetTarget: |
307 | 0 | case AudioTimelineEvent::SetValueCurve: |
308 | 0 | break; |
309 | 0 | case AudioTimelineEvent::SetValue: |
310 | 0 | case AudioTimelineEvent::Cancel: |
311 | 0 | case AudioTimelineEvent::Stream: |
312 | 0 | MOZ_ASSERT(false, "Should have been handled earlier."); |
313 | 0 | } |
314 | 0 |
|
315 | 0 | // Now handle all other cases |
316 | 0 | switch (aPrevious->mType) { |
317 | 0 | case AudioTimelineEvent::SetValueAtTime: |
318 | 0 | case AudioTimelineEvent::LinearRamp: |
319 | 0 | case AudioTimelineEvent::ExponentialRamp: |
320 | 0 | // If the next event type is neither linear or exponential ramp, the |
321 | 0 | // value is constant. |
322 | 0 | return aPrevious->mValue; |
323 | 0 | case AudioTimelineEvent::SetValueCurve: |
324 | 0 | return ExtractValueFromCurve(aPrevious->Time<TimeType>(), |
325 | 0 | aPrevious->mCurve, |
326 | 0 | aPrevious->mCurveLength, |
327 | 0 | aPrevious->mDuration, |
328 | 0 | aTime); |
329 | 0 | case AudioTimelineEvent::SetTarget: |
330 | 0 | MOZ_FALLTHROUGH_ASSERT("AudioTimelineEvent::SetTarget"); |
331 | 0 | case AudioTimelineEvent::SetValue: |
332 | 0 | case AudioTimelineEvent::Cancel: |
333 | 0 | case AudioTimelineEvent::Stream: |
334 | 0 | MOZ_ASSERT(false, "Should have been handled earlier."); |
335 | 0 | } |
336 | 0 |
|
337 | 0 | MOZ_ASSERT(false, "unreached"); |
338 | 0 | return 0.0f; |
339 | 0 | } Unexecuted instantiation: float mozilla::dom::AudioEventTimeline::GetValuesAtTimeHelperInternal<double>(double, mozilla::dom::AudioTimelineEvent const*, mozilla::dom::AudioTimelineEvent const*) Unexecuted instantiation: float mozilla::dom::AudioEventTimeline::GetValuesAtTimeHelperInternal<long>(long, mozilla::dom::AudioTimelineEvent const*, mozilla::dom::AudioTimelineEvent const*) |
340 | | template float |
341 | | AudioEventTimeline::GetValuesAtTimeHelperInternal(double aTime, |
342 | | const AudioTimelineEvent* aPrevious, |
343 | | const AudioTimelineEvent* aNext); |
344 | | template float |
345 | | AudioEventTimeline::GetValuesAtTimeHelperInternal(int64_t aTime, |
346 | | const AudioTimelineEvent* aPrevious, |
347 | | const AudioTimelineEvent* aNext); |
348 | | |
349 | | const AudioTimelineEvent* |
350 | | AudioEventTimeline::GetPreviousEvent(double aTime) const |
351 | 0 | { |
352 | 0 | const AudioTimelineEvent* previous = nullptr; |
353 | 0 | const AudioTimelineEvent* next = nullptr; |
354 | 0 |
|
355 | 0 | auto TimeOf = [](const AudioTimelineEvent& aEvent) -> double { |
356 | 0 | return aEvent.Time<double>(); |
357 | 0 | }; |
358 | 0 |
|
359 | 0 | bool bailOut = false; |
360 | 0 | for (unsigned i = 0; !bailOut && i < mEvents.Length(); ++i) { |
361 | 0 | switch (mEvents[i].mType) { |
362 | 0 | case AudioTimelineEvent::SetValueAtTime: |
363 | 0 | case AudioTimelineEvent::SetTarget: |
364 | 0 | case AudioTimelineEvent::LinearRamp: |
365 | 0 | case AudioTimelineEvent::ExponentialRamp: |
366 | 0 | case AudioTimelineEvent::SetValueCurve: |
367 | 0 | if (aTime == TimeOf(mEvents[i])) { |
368 | 0 | // Find the last event with the same time |
369 | 0 | do { |
370 | 0 | ++i; |
371 | 0 | } while (i < mEvents.Length() && |
372 | 0 | aTime == TimeOf(mEvents[i])); |
373 | 0 | return &mEvents[i - 1]; |
374 | 0 | } |
375 | 0 | previous = next; |
376 | 0 | next = &mEvents[i]; |
377 | 0 | if (aTime < TimeOf(mEvents[i])) { |
378 | 0 | bailOut = true; |
379 | 0 | } |
380 | 0 | break; |
381 | 0 | default: |
382 | 0 | MOZ_ASSERT(false, "unreached"); |
383 | 0 | } |
384 | 0 | } |
385 | 0 | // Handle the case where the time is past all of the events |
386 | 0 | if (!bailOut) { |
387 | 0 | previous = next; |
388 | 0 | } |
389 | 0 |
|
390 | 0 | return previous; |
391 | 0 | } |
392 | | |
393 | | } // namespace dom |
394 | | } // namespace mozilla |
395 | | |