Bug 1113634 - Update mLastComputedValue in AudioEventTimeline when skipping events of same time; r?karlt draft
authorDan Minor <dminor@mozilla.com>
Mon, 08 Aug 2016 16:00:35 -0400
changeset 398552 80c63f569454d4bd8e9c6ce66c551e757357895f
parent 395624 6608e5864780589b25d5421c3d3673ab30c4c318
child 527697 52730522df4c210772d438c3934b5229d258fc09
push id25570
push userdminor@mozilla.com
push dateTue, 09 Aug 2016 11:45:47 +0000
reviewerskarlt
bugs1113634
milestone51.0a1
Bug 1113634 - Update mLastComputedValue in AudioEventTimeline when skipping events of same time; r?karlt MozReview-Commit-ID: LuxSK6PHFHv
dom/media/webaudio/AudioEventTimeline.cpp
dom/media/webaudio/AudioEventTimeline.h
dom/media/webaudio/test/mochitest.ini
dom/media/webaudio/test/test_bug1113634.html
--- a/dom/media/webaudio/AudioEventTimeline.cpp
+++ b/dom/media/webaudio/AudioEventTimeline.cpp
@@ -179,69 +179,79 @@ AudioEventTimeline::GetValuesAtTimeHelpe
                  next->mType == AudioTimelineEvent::SetValueCurve);
 #endif
 
       if (TimesEqual(aTime, TimeOf(*next))) {
         mLastComputedValue = mComputedValue;
         // Find the last event with the same time
         while (eventIndex < mEvents.Length() - 1 &&
                TimesEqual(aTime, TimeOf(mEvents[eventIndex + 1]))) {
+          mLastComputedValue = GetValuesAtTimeHelperInternal(aTime,
+                                                             &mEvents[eventIndex]);
           ++eventIndex;
         }
+
         timeMatchesEventIndex = true;
         break;
       }
 
       previous = next;
     }
 
     if (timeMatchesEventIndex) {
       // The time matches one of the events exactly.
       MOZ_ASSERT(TimesEqual(aTime, TimeOf(mEvents[eventIndex])));
-
-      switch (mEvents[eventIndex].mType) {
-        case AudioTimelineEvent::SetTarget:
-          // SetTarget nodes can be handled no matter what their next node is
-          // (if they have one).
-          // Follow the curve, without regard to the next event, starting at
-          // the last value of the last event.
-          mComputedValue =
-            ExponentialApproach(TimeOf(mEvents[eventIndex]),
-                                mLastComputedValue, mEvents[eventIndex].mValue,
-                                mEvents[eventIndex].mTimeConstant, aTime);
-          break;
-        case AudioTimelineEvent::SetValueCurve:
-          // SetValueCurve events can be handled no matter what their event
-          // node is (if they have one)
-          mComputedValue =
-            ExtractValueFromCurve(TimeOf(mEvents[eventIndex]),
-                                  mEvents[eventIndex].mCurve,
-                                  mEvents[eventIndex].mCurveLength,
-                                  mEvents[eventIndex].mDuration, aTime);
-          break;
-        default:
-          // For other event types
-          mComputedValue = mEvents[eventIndex].mValue;
-      }
+      mComputedValue = GetValuesAtTimeHelperInternal(aTime, next);
     } else {
       mComputedValue = GetValuesAtTimeHelperInternal(aTime, previous, next);
     }
 
     aBuffer[bufferIndex] = mComputedValue;
   }
 }
 template void
 AudioEventTimeline::GetValuesAtTimeHelper(double aTime, float* aBuffer,
                                           const size_t aSize);
 template void
 AudioEventTimeline::GetValuesAtTimeHelper(int64_t aTime, float* aBuffer,
                                           const size_t aSize);
 
 template<class TimeType> float
 AudioEventTimeline::GetValuesAtTimeHelperInternal(TimeType aTime,
+                                    const AudioTimelineEvent* aNext)
+{
+  switch (aNext->mType) {
+    case AudioTimelineEvent::SetTarget:
+      // SetTarget nodes can be handled no matter what their next node is
+      // (if they have one).
+      // Follow the curve, without regard to the next event, starting at
+      // the last value of the last event.
+      return ExponentialApproach(aTime,
+                                 mLastComputedValue, aNext->mValue,
+                                 aNext->mTimeConstant, aTime);
+      break;
+    case AudioTimelineEvent::SetValueCurve:
+      // SetValueCurve events can be handled no matter what their event
+      // node is (if they have one)
+      return ExtractValueFromCurve(aTime,
+                                   aNext->mCurve,
+                                   aNext->mCurveLength,
+                                   aNext->mDuration, aTime);
+      break;
+    default:
+      // For other event types
+      return aNext->mValue;
+  }
+
+  MOZ_ASSERT(false, "unreached");
+  return 0.0f;
+}
+
+template<class TimeType> float
+AudioEventTimeline::GetValuesAtTimeHelperInternal(TimeType aTime,
                                     const AudioTimelineEvent* aPrevious,
                                     const AudioTimelineEvent* aNext)
 {
   // If the requested time is before all of the existing events
   if (!aPrevious) {
      return mValue;
   }
 
--- a/dom/media/webaudio/AudioEventTimeline.h
+++ b/dom/media/webaudio/AudioEventTimeline.h
@@ -346,16 +346,20 @@ public:
   }
 
 private:
   template<class TimeType>
   void GetValuesAtTimeHelper(TimeType aTime, float* aBuffer, const size_t aSize);
 
   template<class TimeType>
   float GetValuesAtTimeHelperInternal(TimeType aTime,
+                                      const AudioTimelineEvent* aNext);
+
+  template<class TimeType>
+  float GetValuesAtTimeHelperInternal(TimeType aTime,
                                       const AudioTimelineEvent* aPrevious,
                                       const AudioTimelineEvent* aNext);
 
   const AudioTimelineEvent* GetPreviousEvent(double aTime) const;
 
   static bool IsValid(double value)
   {
     return mozilla::IsFinite(value);
--- a/dom/media/webaudio/test/mochitest.ini
+++ b/dom/media/webaudio/test/mochitest.ini
@@ -84,16 +84,17 @@ tags=capturestream
 [test_bug875221.html]
 [test_bug875402.html]
 [test_bug894150.html]
 [test_bug956489.html]
 [test_bug964376.html]
 [test_bug966247.html]
 tags=capturestream
 [test_bug972678.html]
+[test_bug1113634.html]
 [test_bug1118372.html]
 [test_bug1027864.html]
 [test_bug1056032.html]
 skip-if = toolkit == 'android' # bug 1056706
 [test_bug1255618.html]
 [test_bug1267579.html]
 [test_channelMergerNode.html]
 [test_channelMergerNodeWithVolume.html]
new file mode 100644
--- /dev/null
+++ b/dom/media/webaudio/test/test_bug1113634.html
@@ -0,0 +1,55 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+  <title>Test AudioParam.setTargetAtTime</title>
+  <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <script type="text/javascript" src="webaudio.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+var V0 = 0.9;
+var V1 = 0.1;
+var T0 = 0;
+var TimeConstant = 10;
+
+var gTest = {
+  length: 2048,
+  numberOfChannels: 1,
+  createGraph: function(context) {
+    var sourceBuffer = context.createBuffer(1, 2048, context.sampleRate);
+    for (var i = 0; i < 2048; ++i) {
+      sourceBuffer.getChannelData(0)[i] = 1;
+    }
+
+    var source = context.createBufferSource();
+    source.buffer = sourceBuffer;
+
+    var gain = context.createGain();
+    gain.gain.setValueAtTime(V0, T0);
+    gain.gain.setTargetAtTime(V1, T0, TimeConstant);
+
+    source.connect(gain);
+
+    source.start(0);
+    return gain;
+  },
+  createExpectedBuffers: function(context) {
+    var T1 = 2048 / context.sampleRate;
+    var expectedBuffer = context.createBuffer(1, 2048, context.sampleRate);
+    for (var i = 0; i < 2048; ++i) {
+      var t = i / context.sampleRate;
+      expectedBuffer.getChannelData(0)[i] = V1 + (V0 - V1) * Math.exp(-(t - T0) / TimeConstant);
+    }
+    return expectedBuffer;
+  },
+};
+
+runTest();
+
+</script>
+</pre>
+</body>
+</html>