Coverage Report

Created: 2018-09-25 14:53

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