Bug 1313058 - Fix SetValueCurveAtTime interpolation; r=padenot draft
authorDan Minor <dminor@mozilla.com>
Wed, 26 Oct 2016 10:33:20 -0400
changeset 433308 1960128558ae9174933cd5be3c1fbfcb79f5ba1d
parent 433112 ade8d4a63e57560410de106450f37b50ed71cca5
child 535855 80f8e63c773b1dc4b8103b07b2b9fa9f34ba8cdb
push id34539
push userdminor@mozilla.com
push dateThu, 03 Nov 2016 13:29:21 +0000
reviewerspadenot
bugs1313058
milestone52.0a1
Bug 1313058 - Fix SetValueCurveAtTime interpolation; r=padenot This interpolates over aCurveLength - 1 steps rather than over aCurveLength steps as was done before. Previously we would reach the final value on the curve before the end of the specified duration. For example, on the curve [1.0, 0.0] with duration 1000, we would interpolate from 1.0 to 0.0 by time 500 rather than time 1000. With these changes, we don't reach 0.0 until time 1000, as expected. This also updates TestSpecExample in TestAudioEventTimeline.cpp to match the curve in the latest spec. MozReview-Commit-ID: Cgs8csbRUMh
dom/media/webaudio/AudioEventTimeline.cpp
dom/media/webaudio/compiledtest/TestAudioEventTimeline.cpp
dom/media/webaudio/test/test_audioParamSetCurveAtTime.html
dom/media/webaudio/test/test_audioParamSetCurveAtTimeTwice.html
--- a/dom/media/webaudio/AudioEventTimeline.cpp
+++ b/dom/media/webaudio/AudioEventTimeline.cpp
@@ -32,21 +32,22 @@ static float ExtractValueFromCurve(doubl
   if (t >= startTime + duration) {
     // After the duration, return the last curve value
     return aCurve[aCurveLength - 1];
   }
   double ratio = std::max((t - startTime) / duration, 0.0);
   if (ratio >= 1.0) {
     return aCurve[aCurveLength - 1];
   }
-  uint32_t current = uint32_t(aCurveLength * ratio);
+  uint32_t current = uint32_t(floor((aCurveLength - 1) * ratio));
   uint32_t next = current + 1;
+  double step = duration / double(aCurveLength - 1);
   if (next < aCurveLength) {
-    double t0 = double(current) / double(aCurveLength) * duration ;
-    double t1 = double(next) / double(aCurveLength) * duration ;
+    double t0 = current * step;
+    double t1 = next * step;
     return LinearInterpolate(t0, aCurve[current], t1, aCurve[next], t - startTime);
   } else {
     return aCurve[current];
   }
 }
 
 namespace mozilla {
 namespace dom {
--- a/dom/media/webaudio/compiledtest/TestAudioEventTimeline.cpp
+++ b/dom/media/webaudio/compiledtest/TestAudioEventTimeline.cpp
@@ -97,17 +97,21 @@ typedef AudioEventTimeline Timeline;
 void TestSpecExample()
 {
   // First, run the basic tests
   Timeline timeline(10.0f);
   is(timeline.Value(), 10.0f, "Correct default value returned");
 
   ErrorResultMock rv;
 
-  float curve[] = { -1.0f, 0.0f, 1.0f };
+  uint32_t curveLength = 44100;
+  float* curve = new float[curveLength];
+  for (uint32_t i = 0; i < curveLength; ++i) {
+    curve[i] = sin(M_PI * i / float(curveLength));
+  }
 
   // This test is copied from the example in the Web Audio spec
   const double t0 = 0.0,
                t1 = 0.1,
                t2 = 0.2,
                t3 = 0.3,
                t4 = 0.4,
                t5 = 0.6,
@@ -122,37 +126,38 @@ void TestSpecExample()
   timeline.LinearRampToValueAtTime(1.0f, t3, rv);
   is(rv, NS_OK, "LinearRampToValueAtTime succeeded");
   timeline.LinearRampToValueAtTime(0.15f, t4, rv);
   is(rv, NS_OK, "LinearRampToValueAtTime succeeded");
   timeline.ExponentialRampToValueAtTime(0.75f, t5, rv);
   is(rv, NS_OK, "ExponentialRampToValueAtTime succeeded");
   timeline.ExponentialRampToValueAtTime(0.05f, t6, rv);
   is(rv, NS_OK, "ExponentialRampToValueAtTime succeeded");
-  timeline.SetValueCurveAtTime(curve, ArrayLength(curve), t6, t7 - t6, rv);
+  timeline.SetValueCurveAtTime(curve, curveLength, t6, t7 - t6, rv);
   is(rv, NS_OK, "SetValueCurveAtTime succeeded");
 
   is(timeline.GetValueAtTime(0.0), 0.2f, "Correct value");
   is(timeline.GetValueAtTime(0.05), 0.2f, "Correct value");
   is(timeline.GetValueAtTime(0.1), 0.3f, "Correct value");
   is(timeline.GetValueAtTime(0.15), 0.3f, "Correct value");
   is(timeline.GetValueAtTime(0.2), 0.4f, "Correct value");
   is(timeline.GetValueAtTime(0.25), (0.4f + 1.0f) / 2, "Correct value");
   is(timeline.GetValueAtTime(0.3), 1.0f, "Correct value");
   is(timeline.GetValueAtTime(0.35), (1.0f + 0.15f) / 2, "Correct value");
   is(timeline.GetValueAtTime(0.4), 0.15f, "Correct value");
   is(timeline.GetValueAtTime(0.45), (0.15f * powf(0.75f / 0.15f, 0.05f / 0.2f)), "Correct value");
   is(timeline.GetValueAtTime(0.5), (0.15f * powf(0.75f / 0.15f, 0.5f)), "Correct value");
   is(timeline.GetValueAtTime(0.55), (0.15f * powf(0.75f / 0.15f, 0.15f / 0.2f)), "Correct value");
   is(timeline.GetValueAtTime(0.6), 0.75f, "Correct value");
   is(timeline.GetValueAtTime(0.65), (0.75f * powf(0.05f / 0.75f, 0.5f)), "Correct value");
-  is(timeline.GetValueAtTime(0.7), -1.0f, "Correct value");
-  is(timeline.GetValueAtTime(0.8), 0.0f, "Correct value");
-  is(timeline.GetValueAtTime(0.9), 1.0f, "Correct value");
-  is(timeline.GetValueAtTime(1.0), 1.0f, "Correct value");
+  is(timeline.GetValueAtTime(0.7), 0.0f, "Correct value");
+  is(timeline.GetValueAtTime(0.85), 1.0f, "Correct value");
+  is(timeline.GetValueAtTime(1.0), curve[curveLength - 1], "Correct value");
+
+  delete[] curve;
 }
 
 void TestInvalidEvents()
 {
   static_assert(numeric_limits<float>::has_quiet_NaN, "Platform must have a quiet NaN");
   const float NaN = numeric_limits<float>::quiet_NaN();
   const float Infinity = numeric_limits<float>::infinity();
   Timeline timeline(10.0f);
--- a/dom/media/webaudio/test/test_audioParamSetCurveAtTime.html
+++ b/dom/media/webaudio/test/test_audioParamSetCurveAtTime.html
@@ -25,23 +25,24 @@ var gTest = {
     source.start(0);
     return gain;
   },
   createExpectedBuffers: function(context) {
     this.duration = 1024 / context.sampleRate;
     this.curve = new Float32Array([1.0, 0.5, 0.75, 0.25]);
     var expectedBuffer = context.createBuffer(1, 2048, context.sampleRate);
     var data = expectedBuffer.getChannelData(0);
+    var step = 1024 / 3;
     for (var i = 0; i < 2048; ++i) {
-      if (i < 256) {
-        data[i] = 1.0 - 0.5*i/256;
-      } else if (i < 512) {
-        data[i] = 0.5 + 0.25*(i - 256)/256;
-      } else if (i < 768) {
-        data[i] = 0.75 - 0.5*(i - 512)/256;
+      if (i < step) {
+        data[i] = 1.0 - 0.5*i/step;
+      } else if (i < 2*step) {
+        data[i] = 0.5 + 0.25*(i - step)/step;
+      } else if (i < 3*step) {
+        data[i] = 0.75 - 0.5*(i - 2*step)/step;
       } else {
         data[i] = 0.25;
       }
     }
     return expectedBuffer;
   },
 };
 
--- a/dom/media/webaudio/test/test_audioParamSetCurveAtTimeTwice.html
+++ b/dom/media/webaudio/test/test_audioParamSetCurveAtTimeTwice.html
@@ -42,25 +42,23 @@ var gTest = {
   createExpectedBuffers: function(context) {
     this.duration = 1024 / context.sampleRate;
     this.curve = new Float32Array(100);
     for (var i = 0; i < 100; ++i) {
       this.curve[i] = Math.sin(440 * 2 * Math.PI * i / context.sampleRate);
     }
     var expectedBuffer = context.createBuffer(1, 2048, context.sampleRate);
     for (var i = 0; i < 2048; ++i) {
-      var t = i / context.sampleRate;
-      var current = Math.min(99, Math.floor(100 * Math.min(1.0, (t - T0) / this.duration)));
+      step = 1024.0/99.0;
+      var current = Math.floor(i / step);
       var next = current + 1;
       if (next < this.curve.length) {
-        var t0 = current / this.curve.length * this.duration;
-        var t1 = next / this.curve.length * this.duration;
-        expectedBuffer.getChannelData(0)[i] = linearInterpolate(t0, this.curve[current], t1, this.curve[next], t);
+        expectedBuffer.getChannelData(0)[i] = linearInterpolate(current*step, this.curve[current], next*step, this.curve[next], i);
       } else {
-        expectedBuffer.getChannelData(0)[i] = this.curve[current];
+        expectedBuffer.getChannelData(0)[i] = this.curve[99];
       }
     }
     return expectedBuffer;
   },
 };
 
 runTest();