AudioParam needs tests for the parameter automation routines.
authorcommit-queue@webkit.org <commit-queue@webkit.org@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Fri, 2 Mar 2012 08:01:01 +0000 (08:01 +0000)
committercommit-queue@webkit.org <commit-queue@webkit.org@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Fri, 2 Mar 2012 08:01:01 +0000 (08:01 +0000)
https://bugs.webkit.org/show_bug.cgi?id=77666

Patch by Raymond Toy <rtoy@google.com> on 2012-03-02
Reviewed by Chris Rogers.

Source/WebCore:

Tests: webaudio/audioparam-linearRampToValueAtTime.html
       webaudio/audioparam-setTargetValueAtTime.html
       webaudio/audioparam-setValueAtTime.html
       webaudio/audioparam-setValueCurveAtTime.html

* webaudio/AudioParamTimeline.cpp:
(WebCore::AudioParamTimeline::valuesForTimeRangeImpl): Round the
curveIndex to fix timing issue in setValueCurveAtTime.

LayoutTests:

* webaudio/audioparam-exponentialRampToValueAtTime.html:
* webaudio/audioparam-linearRampToValueAtTime-expected.txt: Added.
* webaudio/audioparam-linearRampToValueAtTime.html: Added.
* webaudio/audioparam-setTargetValueAtTime-expected.txt: Added.
* webaudio/audioparam-setTargetValueAtTime.html: Added.
* webaudio/audioparam-setValueAtTime-expected.txt: Added.
* webaudio/audioparam-setValueAtTime.html: Added.
* webaudio/audioparam-setValueCurveAtTime-expected.txt: Added.
* webaudio/audioparam-setValueCurveAtTime.html: Added.
* webaudio/resources/audioparam-testing.js:
(renderLength):
(createConstantArray):
(createLinearRampArray):
(discreteTimeConstantForSampleRate):
(createExponentialApproachArray):
(createSineWaveArray):
(endValueDelta):
(valueUpdate):
(comparePartialSignals):
(verifyDiscontinuities):
(compareSignals):
(checkResultFunction.return.var):
(checkResultFunction):
(doAutomation):
(createAudioGraphAndTest):
* webaudio/resources/audio-testing.js: Moved isValidNumber from
biquad-testing.js to here.
* webaudio/resources/biquad-testing.js: Moved isValidNumber from
here to audio-testing.js.

git-svn-id: https://svn.webkit.org/repository/webkit/trunk@109518 268f45cc-cd09-0410-ab3c-d52691b4dbfc

15 files changed:
LayoutTests/ChangeLog
LayoutTests/webaudio/audioparam-exponentialRampToValueAtTime.html
LayoutTests/webaudio/audioparam-linearRampToValueAtTime-expected.txt [new file with mode: 0644]
LayoutTests/webaudio/audioparam-linearRampToValueAtTime.html [new file with mode: 0644]
LayoutTests/webaudio/audioparam-setTargetValueAtTime-expected.txt [new file with mode: 0644]
LayoutTests/webaudio/audioparam-setTargetValueAtTime.html [new file with mode: 0644]
LayoutTests/webaudio/audioparam-setValueAtTime-expected.txt [new file with mode: 0644]
LayoutTests/webaudio/audioparam-setValueAtTime.html [new file with mode: 0644]
LayoutTests/webaudio/audioparam-setValueCurveAtTime-expected.txt [new file with mode: 0644]
LayoutTests/webaudio/audioparam-setValueCurveAtTime.html [new file with mode: 0644]
LayoutTests/webaudio/resources/audio-testing.js
LayoutTests/webaudio/resources/audioparam-testing.js
LayoutTests/webaudio/resources/biquad-testing.js
Source/WebCore/ChangeLog
Source/WebCore/webaudio/AudioParamTimeline.cpp

index cc46b39..ef6f365 100644 (file)
@@ -1,3 +1,40 @@
+2012-03-02  Raymond Toy  <rtoy@google.com>
+
+        AudioParam needs tests for the parameter automation routines.
+        https://bugs.webkit.org/show_bug.cgi?id=77666
+
+        Reviewed by Chris Rogers.
+
+        * webaudio/audioparam-exponentialRampToValueAtTime.html:
+        * webaudio/audioparam-linearRampToValueAtTime-expected.txt: Added.
+        * webaudio/audioparam-linearRampToValueAtTime.html: Added.
+        * webaudio/audioparam-setTargetValueAtTime-expected.txt: Added.
+        * webaudio/audioparam-setTargetValueAtTime.html: Added.
+        * webaudio/audioparam-setValueAtTime-expected.txt: Added.
+        * webaudio/audioparam-setValueAtTime.html: Added.
+        * webaudio/audioparam-setValueCurveAtTime-expected.txt: Added.
+        * webaudio/audioparam-setValueCurveAtTime.html: Added.
+        * webaudio/resources/audioparam-testing.js:
+        (renderLength):
+        (createConstantArray):
+        (createLinearRampArray):
+        (discreteTimeConstantForSampleRate):
+        (createExponentialApproachArray):
+        (createSineWaveArray):
+        (endValueDelta):
+        (valueUpdate):
+        (comparePartialSignals):
+        (verifyDiscontinuities):
+        (compareSignals):
+        (checkResultFunction.return.var):
+        (checkResultFunction):
+        (doAutomation):
+        (createAudioGraphAndTest):
+        * webaudio/resources/audio-testing.js: Moved isValidNumber from
+        biquad-testing.js to here.
+        * webaudio/resources/biquad-testing.js: Moved isValidNumber from
+        here to audio-testing.js.
+
 2012-03-01  Dirk Pranke  <dpranke@chromium.org>
 
         Moar tightening of expectations (mac this time).
index 3e62f84..02e1702 100644 (file)
       // discontinuity is for testing timing.  Also, we alternate between an increasing and
       // decreasing ramp for each interval.
 
+      // Number of tests to run.
+      var numberOfTests = 100;
+
       // Max allowed difference between the rendered data and the expected result.
       var maxAllowedError = 6.75e-4;
 
-      // Information about the starting and ending times and starting and ending values for each
-      // time interval.
-      var timeValueInfo;
-
       // The AudioGainNode starts with this value instead of the default value.
       var initialValue = 100;
 
-      // The difference between starting values between each time interval.
-      var startingValueDelta = initialValue / numberOfTests;
-      
-      // Each exponential ramp will increase or decrease by this much from the start to the end of
-      // the time interval.  We choose half of startingValueDelta so that the ending value of the
-      // ramp will be distinct from the starting value of the ramp for next time interval.  This
-      // allows us to detect where the ramp begins and ends.
-      var startEndValueChange = startingValueDelta / 2;
-
-      // Threshold to use for detecting discontinuities that should appear at each time interval.
-      var discontinuityThreshold = startEndValueChange / 2;
-
-      var gainNode;
-      
-      var constantBuffer;
-      var bufferSource;
-
-      // Return the difference between the starting value and the ending value for time interval
-      // |timeIntervalIndex|.  We alternate between an end value that is above or below the starting
-      // value.
-      function endValueDelta(timeIntervalIndex)
-      {
-          if (timeIntervalIndex & 1) {
-              return -startEndValueChange;
-          } else {
-              return startEndValueChange;
-          }
-      }
-
       // Set the gain node value to the specified value at the specified time.
       function setValue(value, time)
       {
           gainNode.gain.exponentialRampToValueAtTime(value, endTime)
       }
       
-      // Return the difference between the starting value at |timeIntervalIndex| and the starting
-      // value at the next time interval.  Since we started at a large initial value, we decrease
-      // the value at each time interval.
-      function valueUpdate(timeIntervalIndex)
-      {
-          return -startingValueDelta;
-      }
-
-      function checkResult(event)
-      {
-          var buffer = event.renderedBuffer;
-          renderedData = buffer.getChannelData(0);
-
-          compareSignals("exponentialRampToValueAtTime()", maxAllowedError, renderedData, createExponentialRampArray, timeValueInfo, discontinuityThreshold);
-
-          finishJSTest();
-      }
-      
       function runTest()
       {
-          if (window.layoutTestController) {
-              layoutTestController.dumpAsText();
-              layoutTestController.waitUntilDone();
-          }
-
-          window.jsTestIsAsync = true;
+          createAudioGraphAndTest(numberOfTests,
+                                  initialValue,
+                                  setValue,
+                                  generateRamp,
+                                  "exponentialRampToValueAtTime()",
+                                  maxAllowedError,
+                                  createExponentialRampArray);
 
-          // Create offline audio context.
-          context = new webkitAudioContext(2, renderLength, sampleRate);
-          constantBuffer = createConstantBuffer(context, 1, renderLength);
-
-          // We use an AudioGainNode here simply as a convenient way to test the AudioParam
-          // automation, since it's easy to pass a constant value through the node, automate the
-          // .gain attribute and observe the resulting values.
-
-          gainNode = context.createGainNode();
-
-          bufferSource = context.createBufferSource();
-          bufferSource.buffer = constantBuffer;
-          bufferSource.connect(gainNode);
-          gainNode.connect(context.destination);
-
-          // At each time interval, we adjust the value by startingValueDelta.  However, the
-          // exponential ramp end value is the value +/- startEndValueChange.  (We alternately
-          // specify the end value to be greater or smaller than the starting value, to test the
-          // ramp going up and down.)
-          //
-          // By doing it this way, we get a discontinuity at each time interval which we can use to
-          // test if the ramp started and ended at the correct time.
-          timeValueInfo = doAutomation(initialValue,
-                                       endValueDelta,
-                                       setValue,
-                                       generateRamp,
-                                       valueUpdate);
-
-          bufferSource.noteOn(0);
-      
-          context.oncomplete = checkResult;
-          context.startRendering();
       }
 
       runTest();
diff --git a/LayoutTests/webaudio/audioparam-linearRampToValueAtTime-expected.txt b/LayoutTests/webaudio/audioparam-linearRampToValueAtTime-expected.txt
new file mode 100644 (file)
index 0000000..45ab57a
--- /dev/null
@@ -0,0 +1,11 @@
+Test AudioParam linearRampToValueAtTime() functionality.
+
+On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".
+
+PASS All 100 tests started and ended at the correct time.
+PASS All 100 tests passed within an acceptable tolerance.
+PASS AudioParam linearRampToValueAtTime() test passed.
+PASS successfullyParsed is true
+
+TEST COMPLETE
+
diff --git a/LayoutTests/webaudio/audioparam-linearRampToValueAtTime.html b/LayoutTests/webaudio/audioparam-linearRampToValueAtTime.html
new file mode 100644 (file)
index 0000000..185a2cd
--- /dev/null
@@ -0,0 +1,60 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
+<html>
+<head>
+<script src="resources/audio-testing.js"></script>
+<script src="resources/audioparam-testing.js"></script>
+<script src="../fast/js/resources/js-test-pre.js"></script>
+</head>
+
+<body>
+<div id="description"></div>
+<div id="console"></div>
+
+<script>
+description("Test AudioParam linearRampToValueAtTime() functionality.");
+
+// Play a long DC signal out through an AudioGainNode, and call setValueAtTime() and
+// linearRampToValueAtTime() at regular intervals to set the starting and ending values for a
+// linear ramp. Each time interval has a ramp with a different starting and ending value so
+// that there is a discontinuity at each time interval boundary.  The discontinuity is for
+// testing timing.  Also, we alternate between an increasing and decreasing ramp for each
+// interval.
+
+// Number of tests to run.
+var numberOfTests = 100;
+
+// Max allowed difference between the rendered data and the expected result.
+var maxAllowedError = 6.5e-7;
+
+// Set the gain node value to the specified value at the specified time.
+function setValue(value, time)
+{
+    gainNode.gain.setValueAtTime(value, time);
+}
+
+// Generate a linear ramp ending at time |endTime| with an ending value of |value|.
+function generateRamp(value, startTime, endTime)
+{
+    // |startTime| is ignored because the linear ramp uses the value from the setValueAtTime() call above.
+    gainNode.gain.linearRampToValueAtTime(value, endTime)
+}
+
+function runTest()
+{
+    createAudioGraphAndTest(numberOfTests,
+                            1,
+                            setValue,
+                            generateRamp,
+                            "linearRampToValueAtTime()",
+                            maxAllowedError,
+                            createLinearRampArray);
+}
+
+runTest();
+successfullyParsed = true;
+  
+</script>
+
+<script src="../fast/js/resources/js-test-post.js"></script>
+</body>
+</html>
diff --git a/LayoutTests/webaudio/audioparam-setTargetValueAtTime-expected.txt b/LayoutTests/webaudio/audioparam-setTargetValueAtTime-expected.txt
new file mode 100644 (file)
index 0000000..cc6d2a0
--- /dev/null
@@ -0,0 +1,11 @@
+Test AudioParam setTargetValueAtTime() functionality.
+
+On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".
+
+PASS All 100 tests started and ended at the correct time.
+PASS All 100 tests passed within an acceptable tolerance.
+PASS AudioParam setTargetValueAtTime() test passed.
+PASS successfullyParsed is true
+
+TEST COMPLETE
+
diff --git a/LayoutTests/webaudio/audioparam-setTargetValueAtTime.html b/LayoutTests/webaudio/audioparam-setTargetValueAtTime.html
new file mode 100644 (file)
index 0000000..aedcf80
--- /dev/null
@@ -0,0 +1,62 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
+<html>
+<head>
+<script src="resources/audio-testing.js"></script>
+<script src="resources/audioparam-testing.js"></script>
+<script src="../fast/js/resources/js-test-pre.js"></script>
+</head>
+
+<body>
+<div id="description"></div>
+<div id="console"></div>
+
+<script>
+description("Test AudioParam setTargetValueAtTime() functionality.");
+
+// Play a long DC signal out through an AudioGainNode, and call setValueAtTime() and
+// setTargetValueAtTime at regular intervals to set the starting value and the target
+// value. Each time interval has a ramp with a different starting and target value so that
+// there is a discontinuity at each time interval boundary.  The discontinuity is for testing
+// timing.  Also, we alternate between an increasing and decreasing ramp for each interval.
+
+// Number of tests to run.
+var numberOfTests = 100;
+
+// Max allowed difference between the rendered data and the expected result.
+var maxAllowedError = 2.79e-5;
+
+// The AudioGainNode starts with this value instead of the default value.
+var initialValue = 100;
+
+// Set the gain node value to the specified value at the specified time.
+function setValue(value, time)
+{
+    gainNode.gain.setValueAtTime(value, time);
+}
+
+// Generate an exponential approach starting at |startTime| with a target value of |value|.
+function automation(value, startTime, endTime)
+{
+    // endTime is not used for setTargetValueAtTime.
+    gainNode.gain.setTargetValueAtTime(value, startTime, timeConstant)
+}
+
+function runTest()
+{
+    createAudioGraphAndTest(numberOfTests,
+                            initialValue,
+                            setValue,
+                            automation,
+                            "setTargetValueAtTime()",
+                            maxAllowedError,
+                            createExponentialApproachArray);
+}
+
+runTest();
+successfullyParsed = true;
+
+</script>
+
+<script src="../fast/js/resources/js-test-post.js"></script>
+</body>
+</html>
diff --git a/LayoutTests/webaudio/audioparam-setValueAtTime-expected.txt b/LayoutTests/webaudio/audioparam-setValueAtTime-expected.txt
new file mode 100644 (file)
index 0000000..adbe35c
--- /dev/null
@@ -0,0 +1,11 @@
+Test AudioParam setValueAtTime() functionality.
+
+On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".
+
+PASS All 100 tests started and ended at the correct time.
+PASS All 100 tests passed within an acceptable tolerance.
+PASS AudioParam setValueAtTime() test passed.
+PASS successfullyParsed is true
+
+TEST COMPLETE
+
diff --git a/LayoutTests/webaudio/audioparam-setValueAtTime.html b/LayoutTests/webaudio/audioparam-setValueAtTime.html
new file mode 100644 (file)
index 0000000..98894d8
--- /dev/null
@@ -0,0 +1,59 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
+<html>
+<head>
+<script src="resources/audio-testing.js"></script>
+<script src="resources/audioparam-testing.js"></script>
+<script src="../fast/js/resources/js-test-pre.js"></script>
+</head>
+
+<body>
+<div id="description"></div>
+<div id="console"></div>
+
+<script>
+description("Test AudioParam setValueAtTime() functionality.");
+
+// Play a long DC signal out through an AudioGainNode, and call setValueAtTime() at regular
+// intervals to set the value for the duration of the interval.  Each time interval has
+// different value so that there is a discontinuity at each time interval boundary.  The
+// discontinuity is for testing timing.
+
+// Number of tests to run.
+var numberOfTests = 100;
+
+// Max allowed difference between the rendered data and the expected result.
+var maxAllowedError = 6e-8;
+
+// Set the gain node value to the specified value at the specified time.
+function setValue(value, time)
+{
+    gainNode.gain.setValueAtTime(value, time);
+}
+
+// For testing setValueAtTime(), we don't need to do anything for automation. because the value at
+// the beginning of the interval is set by setValue and it remains constant for the duration, which
+// is what we want.
+function automation(value, startTime, endTime)
+{
+    // Do nothing.
+}
+
+function runTest()
+{
+    createAudioGraphAndTest(numberOfTests,
+                            1,
+                            setValue,
+                            automation,
+                            "setValueAtTime()",
+                            maxAllowedError,
+                            createConstantArray);
+}
+
+runTest();
+successfullyParsed = true;
+
+</script>
+
+<script src="../fast/js/resources/js-test-post.js"></script>
+</body>
+</html>
diff --git a/LayoutTests/webaudio/audioparam-setValueCurveAtTime-expected.txt b/LayoutTests/webaudio/audioparam-setValueCurveAtTime-expected.txt
new file mode 100644 (file)
index 0000000..7377bb0
--- /dev/null
@@ -0,0 +1,11 @@
+Test AudioParam setValueCurveAtTime() functionality.
+
+On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".
+
+PASS All 20 tests started and ended at the correct time.
+PASS All 20 tests passed within an acceptable tolerance.
+PASS AudioParam setValueCurveAtTime() test passed.
+PASS successfullyParsed is true
+
+TEST COMPLETE
+
diff --git a/LayoutTests/webaudio/audioparam-setValueCurveAtTime.html b/LayoutTests/webaudio/audioparam-setValueCurveAtTime.html
new file mode 100644 (file)
index 0000000..2ef8c78
--- /dev/null
@@ -0,0 +1,74 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
+<html>
+<head>
+<script src="resources/audio-testing.js"></script>
+<script src="resources/audioparam-testing.js"></script>
+<script src="../fast/js/resources/js-test-pre.js"></script>
+</head>
+
+<body>
+<div id="description"></div>
+<div id="console"></div>
+
+<script>
+description("Test AudioParam setValueCurveAtTime() functionality.");
+
+// Play a long DC signal out through an AudioGainNode and for each time interval call
+// setValueCurveAtTime() to set the values for the duration of the interval.  Each curve is a sine
+// wave, and we assume that the time interval is not an exact multiple of the period. This causes a
+// discontinuity between time intervals which is used to test timing.
+
+// Number of tests to run.
+var numberOfTests = 20;
+
+// Max allowed difference between the rendered data and the expected result.  (The error is zero
+// because the rendered curve should really be exactly the same as the reference.)
+var maxAllowedError = 0;
+
+// The amplitude of the sine wave.
+var sineAmplitude = 1;
+
+// Frequency of the sine wave.
+var freqHz = 440;
+
+// Curve to use for setValueCurveAtTime().
+var curve;
+
+// Sets the curve data for the entire time interval.
+function automation(value, startTime, endTime)
+{
+    gainNode.gain.setValueCurveAtTime(curve, startTime, endTime - startTime);
+}
+
+// Create a sine wave of the specified duration.
+function createReferenceSineArray(startTime, endTime, startValue, endValue, sampleRate)
+{
+    // Ignore |startValue| and |endValue| for the sine wave.
+    return createSineWaveArray(endTime - startTime, freqHz, sineAmplitude, sampleRate);
+}
+
+function runTest() 
+{
+    // The curve of values to use.
+    curve = createSineWaveArray(timeInterval, freqHz, sineAmplitude, sampleRate);
+
+    createAudioGraphAndTest(numberOfTests,
+                            sineAmplitude,
+                            function(k) {
+                                // Don't need to set the value.
+                            },
+                            automation,
+                            "setValueCurveAtTime()",
+                            maxAllowedError,
+                            createReferenceSineArray,
+                            2 * Math.PI * sineAmplitude * freqHz / sampleRate);
+}
+
+runTest();
+successfullyParsed = true;
+
+</script>
+
+<script src="../fast/js/resources/js-test-post.js"></script>
+</body>
+</html>
index 8f67f2d..af0ad27 100644 (file)
@@ -128,3 +128,8 @@ function grainLengthInSampleFrames(grainOffset, duration, sampleRate) {
 
     return endFrame - startFrame;
 }
+
+// True if the number is not an infinity or NaN
+function isValidNumber(x) {
+    return !isNaN(x) && (x != Infinity) && (x != -Infinity);
+}
index cca34c9..8d3ac02 100644 (file)
@@ -1,18 +1,38 @@
 var sampleRate = 44100;
 
-// Number of tests to run.
-var numberOfTests = 100;
+// Information about the starting/ending times and starting/ending values for each time interval.
+var timeValueInfo;
 
-// Time interval between value changes.  It is best if 1 / numberOfTests is not close to
-// timeInterval.
+// The difference between starting values between each time interval.
+var startingValueDelta;
+      
+// For any automation function that has an end or target value, the end value is based the starting
+// value of the time interval.  The starting value will be increased or decreased by
+// |startEndValueChange|. We choose half of |startingValueDelta| so that the ending value will be
+// distinct from the starting value for next time interval.  This allows us to detect where the ramp
+// begins and ends.
+var startEndValueChange;
+
+// Default threshold to use for detecting discontinuities that should appear at each time interval.
+var discontinuityThreshold;
+
+// Time interval between value changes.  It is best if 1 / numberOfTests is not close to timeInterval.
 var timeInterval = .03;
 
-// Make sure we render long enough to capture all of our test data.
-var renderTime = (numberOfTests + 1) * timeInterval;
-var renderLength = timeToSampleFrame(renderTime, sampleRate);
+// Some suitable time constant so that we can see a significant change over a timeInterval.  This is
+// only needed by setTargetValueAtTime() which needs a time constant.
+var timeConstant = timeInterval / 3;
+
+var gainNode;
 
 var context;
 
+// Make sure we render long enough to capture all of our test data.
+function renderLength(numberOfTests)
+{
+    return timeToSampleFrame((numberOfTests + 1) * timeInterval, sampleRate);
+}
+
 // Create a buffer containing the same constant value.
 function createConstantBuffer(context, constant, length) {
     var buffer = context.createBuffer(1, length, context.sampleRate);
@@ -26,6 +46,39 @@ function createConstantBuffer(context, constant, length) {
     return buffer;
 }
 
+// Create a constant reference signal with the given |value|.  Basically the same as
+// |createConstantBuffer|, but with the parameters to match the other create functions.  The
+// |endValue| is ignored.
+function createConstantArray(startTime, endTime, value, endValue, sampleRate)
+{
+    var startFrame = timeToSampleFrame(startTime, sampleRate);
+    var endFrame = timeToSampleFrame(endTime, sampleRate);
+    var length = endFrame - startFrame;
+
+    var buffer = createConstantBuffer(context, value, length);
+
+    return buffer.getChannelData(0);
+}
+
+// Create a linear ramp starting at |startValue| and ending at |endValue|.  The ramp starts at time
+// |startTime| and ends at |endTime|.  (The start and end times are only used to compute how many
+// samples to return.)
+function createLinearRampArray(startTime, endTime, startValue, endValue, sampleRate)
+{
+    var startFrame = timeToSampleFrame(startTime, sampleRate);
+    var endFrame = timeToSampleFrame(endTime, sampleRate);
+    var length = endFrame - startFrame;
+    var array = new Array(length);
+
+    var step = (endValue - startValue) / length;
+
+    for (k = 0; k < length; ++k) {
+        array[k] = startValue + k * step;
+    }
+
+    return array;
+}
+
 // Create an exponential ramp starting at |startValue| and ending at |endValue|.  The ramp starts at
 // time |startTime| and ends at |endTime|.  (The start and end times are only used to compute how
 // many samples to return.)
@@ -34,28 +87,105 @@ function createExponentialRampArray(startTime, endTime, startValue, endValue, sa
     var startFrame = timeToSampleFrame(startTime, sampleRate);
     var endFrame = timeToSampleFrame(endTime, sampleRate);
     var length = endFrame - startFrame;
-    var buffer = new Array(length);
+    var array = new Array(length);
 
     var multiplier = Math.pow(endValue / startValue, 1 / length);
     
     for (var k = 0; k < length; ++k) {
-        buffer[k] = startValue * Math.pow(multiplier, k);
+        array[k] = startValue * Math.pow(multiplier, k);
     }
 
-    return buffer;
+    return array;
+}
+
+function discreteTimeConstantForSampleRate(timeConstant, sampleRate)
+{
+    return 1 - Math.exp(-1 / (sampleRate * timeConstant));
+}
+
+// Create a signal that starts at |startValue| and exponentially approaches the target value of
+// |targetValue|, using a time constant of |timeConstant|.  The ramp starts at time |startTime| and
+// ends at |endTime|.  (The start and end times are only used to compute how many samples to
+// return.)
+function createExponentialApproachArray(startTime, endTime, startValue, targetValue, sampleRate, timeConstant)
+{
+    var startFrame = timeToSampleFrame(startTime, sampleRate);
+    var endFrame = timeToSampleFrame(endTime, sampleRate);
+    var length = endFrame - startFrame;
+    var array = new Array(length);
+    var c = discreteTimeConstantForSampleRate(timeConstant, sampleRate);
+
+    var value = startValue;
+    
+    for (var k = 0; k < length; ++k) {
+        array[k] = value;
+        value += (targetValue - value) * c;
+    }
+
+    return array;
+}
+
+// Create a sine wave of the given frequency and amplitude.  The sine wave is offset by half the
+// amplitude so that result is always positive.
+function createSineWaveArray(durationSeconds, freqHz, amplitude, sampleRate)
+{
+    var length = timeToSampleFrame(durationSeconds, sampleRate);
+    var signal = new Float32Array(length);
+    var omega = 2 * Math.PI * freqHz / sampleRate;
+    var halfAmplitude = amplitude / 2;
+    
+    for (var k = 0; k < length; ++k) {
+        signal[k] = halfAmplitude + halfAmplitude * Math.sin(omega * k);
+    }
+
+    return signal;
+}
+
+// Return the difference between the starting value and the ending value for time interval
+// |timeIntervalIndex|.  We alternate between an end value that is above or below the starting
+// value.
+function endValueDelta(timeIntervalIndex)
+{
+    if (timeIntervalIndex & 1) {
+        return -startEndValueChange;
+    } else {
+        return startEndValueChange;
+    }
+}
+
+// Return the difference between the starting value at |timeIntervalIndex| and the starting value at
+// the next time interval.  Since we started at a large initial value, we decrease the value at each
+// time interval.
+function valueUpdate(timeIntervalIndex)
+{
+    return -startingValueDelta;
 }
 
 // Compare a section of the rendered data against our expected signal.
 function comparePartialSignals(rendered, expectedFunction, startTime, endTime, valueInfo, sampleRate)
 {
     var startSample = timeToSampleFrame(startTime, sampleRate);
-    var expected = expectedFunction(startTime, endTime, valueInfo.startValue, valueInfo.endValue, sampleRate);
+    var expected = expectedFunction(startTime, endTime, valueInfo.startValue, valueInfo.endValue, sampleRate, timeConstant);
 
     var n = expected.length;
     var maxError = -1;
     var maxErrorIndex = -1;
     
     for (var k = 0; k < n; ++k) {
+        // Make sure we don't pass these tests because a NaN has been generated in either the
+        // rendered data or the reference data.
+        if (!isValidNumber(rendered[startSample + k])) {
+            maxError = Infinity;
+            maxErrorIndex = startSample + k;
+            testFailed("NaN or infinity for rendered data at " + maxErrorIndex);
+            break;
+        }
+        if (!isValidNumber(expected[k])) {
+            maxError = Infinity;
+            maxErrorIndex = startSample + k;
+            testFailed("Nan or infinity for reference data at " + maxErrorIndex);
+            break;
+        }
         var error = Math.abs(rendered[startSample + k] - expected[k]);
         if (error > maxError) {
             maxError = error;
@@ -83,9 +213,26 @@ function verifyDiscontinuities(values, times, threshold)
         }
     }
 
+    var testCount;
+
+    // If there are numberOfTests intervals, there are only numberOfTests - 1 internal interval
+    // boundaries. Hence the maximum number of discontinuties we expect to find is numberOfTests -
+    // 1. If we find more than that, we have no reference to compare against. We also assume that
+    // the actual discontinuities are close to the expected ones.
+    //
+    // This is just a sanity check when something goes really wrong.  For example, if the threshold
+    // is too low, every sample frame looks like a discontinuity.
+    if (breaks.length >= numberOfTests) {
+        testCount = numberOfTests - 1;
+        testFailed("Found more discontinuities (" + breaks.length + ") than expected.  Only comparing first " + testCount + "discontinuities.");
+        success = false;
+    } else {
+        testCount = breaks.length;
+    }
+    
     // Compare the location of each discontinuity with the end time of each interval. (There is no
     // discontinuity at the start of the signal.)
-    for (var k = 0; k < breaks.length; ++k) {
+    for (var k = 0; k < testCount; ++k) {
         var expectedSampleFrame = timeToSampleFrame(times[k + 1], sampleRate);
         if (breaks[k] != expectedSampleFrame) {
             success = false;
@@ -96,8 +243,14 @@ function verifyDiscontinuities(values, times, threshold)
 
     if (badLocations) {
         testFailed(badLocations + " discontinuities at incorrect locations");
+        success = false;
     } else {
-        testPassed("All " + (breaks.length + 1) + " tests started and ended at the correct time.");
+        if (breaks.length == numberOfTests - 1) {
+            testPassed("All " + numberOfTests + " tests started and ended at the correct time.");
+        } else {
+            testFailed("Found " + breaks.length + " discontinuities but expected " + (numberOfTests - 1));
+            success = false;
+        }
     }
     
     return success;
@@ -131,7 +284,7 @@ function compareSignals(testName, maxError, renderedData, expectedFunction, time
         var result = comparePartialSignals(renderedData, expectedFunction, times[k], times[k + 1], values[k], sampleRate);
 
         if (result.maxError > maxError) {
-            testFailed("Incorrect gain for test " + k + ". Max error = " + result.maxError + " at offset " + (result.index + timeToSampleFrame(times[k], sampleRate)));
+            testFailed("Incorrect value for test " + k + ". Max error = " + result.maxError + " at offset " + (result.index + timeToSampleFrame(times[k], sampleRate)));
             ++failedTestCount;
         }
     }
@@ -150,23 +303,52 @@ function compareSignals(testName, maxError, renderedData, expectedFunction, time
     }
 }
 
+// Create a function to test the rendered data with the reference data.
+//
+// testName - string describing the test
+//
+// error - max allowed error between rendered data and the reference data.
+//
+// referenceFunction - function that generates the reference data to be compared with the rendered
+// data.
+//
+// jumpThreshold - optional parameter that specifies the threshold to use for detecting
+// discontinuities.  If not specified, defaults to discontinuityThreshold.
+//
+function checkResultFunction(testName, error, referenceFunction, jumpThreshold)
+{
+    return function(event) {
+        var buffer = event.renderedBuffer;
+        renderedData = buffer.getChannelData(0);
+
+        var threshold;
+
+        if (!jumpThreshold) {
+            threshold = discontinuityThreshold;
+        } else {
+            threshold = jumpThreshold;
+        }
+        
+        compareSignals(testName, error, renderedData, referenceFunction, timeValueInfo, threshold);
+
+        finishJSTest();
+    }
+}
+
 // Run all the automation tests.
 //
-// initialValue - The initial value of the first time interval.
+// numberOfTests - number of tests (time intervals) to run.
 //
-// endValueDeltaFunction - How much the end value differs from the value at the start of the
-// interval.
+// initialValue - The initial value of the first time interval.
 //
 // setValueFunction - function that sets the specified value at the start of a time interval.
 //
 // automationFunction - function that sets the end value for the time interval.  It specifies how
 // the value approaches the end value.
 //
-// valueUpdateFunction - The starting value at time interval k is adjusted by valueUpdateFunction(k) to give the new starting value at time interval k + 1.
-//
 // An object is returned containing an array of start times for each time interval, and an array
 // giving the start and end values for the interval.
-function doAutomation(initialValue, endValueDeltaFunction, setValueFunction, automationFunction, valueUpdateFunction)
+function doAutomation(numberOfTests, initialValue, setValueFunction, automationFunction)
 {
     var timeInfo = [0];
     var valueInfo = [];
@@ -175,20 +357,84 @@ function doAutomation(initialValue, endValueDeltaFunction, setValueFunction, aut
     for (var k = 0; k < numberOfTests; ++k) {
         var startTime = k * timeInterval;
         var endTime = (k + 1) * timeInterval;
-        var endValue = value + endValueDeltaFunction(k);
+        var endValue = value + endValueDelta(k);
 
         // Set the value at the start of the time interval.
         setValueFunction(value, startTime);
 
-        // Specify the target value, and how we should approach the target value.
+        // Specify the end or target value, and how we should approach it.
         automationFunction(endValue, startTime, endTime);
 
         // Keep track of the start times, and the start and end values for each time interval.
         timeInfo.push(endTime);
         valueInfo.push({startValue: value, endValue : endValue});
 
-        value += valueUpdateFunction(k);
+        value += valueUpdate(k);
     }
 
     return {times : timeInfo, values : valueInfo};
 }
+
+// Create the audio graph for the test and then run the test.
+//
+// numberOfTests - number of time intervals (tests) to run.
+//
+// initialValue - the initial value of the gain at time 0.
+//
+// setValueFunction - function to set the value at the beginning of each time interval.
+//
+// automationFunction - the AudioParamTimeline automation function
+//
+// testName - string indicating the test that is being run.
+//
+// maxError - maximum allowed error between the rendered data and the reference data
+//
+// referenceFunction - function that generates the reference data to be compared against the
+// rendered data.
+//
+// jumpThreshold - optional parameter that specifies the threshold to use for detecting
+// discontinuities.  If not specified, defaults to discontinuityThreshold.
+//
+function createAudioGraphAndTest(numberOfTests, initialValue, setValueFunction, automationFunction, testName, maxError, referenceFunction, jumpThreshold)
+{
+    if (window.layoutTestController) {
+        layoutTestController.dumpAsText();
+        layoutTestController.waitUntilDone();
+    }
+
+    window.jsTestIsAsync = true;
+
+    // Create offline audio context.
+    context = new webkitAudioContext(2, renderLength(numberOfTests), sampleRate);
+    var constantBuffer = createConstantBuffer(context, 1, renderLength(numberOfTests));
+
+    // We use an AudioGainNode here simply as a convenient way to test the AudioParam
+    // automation, since it's easy to pass a constant value through the node, automate the
+    // .gain attribute and observe the resulting values.
+
+    gainNode = context.createGainNode();
+
+    var bufferSource = context.createBufferSource();
+    bufferSource.buffer = constantBuffer;
+    bufferSource.connect(gainNode);
+    gainNode.connect(context.destination);
+
+    // Set up default values for the parameters that control how the automation test values progress
+    // for each time interval.
+    startingValueDelta = initialValue / numberOfTests;
+    startEndValueChange = startingValueDelta / 2;
+    discontinuityThreshold = startEndValueChange / 2;
+
+    // Run the automation tests.
+    timeValueInfo = doAutomation(numberOfTests,
+                                 initialValue,
+                                 setValueFunction,
+                                 automationFunction);
+    bufferSource.noteOn(0);
+      
+    context.oncomplete = checkResultFunction(testName,
+                                             maxError,
+                                             referenceFunction,
+                                             jumpThreshold);
+    context.startRendering();
+}
index e5815e7..99a2e8d 100644 (file)
@@ -480,11 +480,6 @@ function generateReference(filterType, filterParameters) {
     return result;
 }
 
-// True if the number is not an infinity or NaN
-function isValidNumber(x) {
-    return !isNaN(x) && (x != Infinity) && (x != -Infinity);
-}
-
 function checkFilterResponse(filterType, filterParameters) {
     return function(event) {
         renderedBuffer = event.renderedBuffer;
index 9eee095..c869727 100644 (file)
@@ -1,3 +1,19 @@
+2012-03-02  Raymond Toy  <rtoy@google.com>
+
+        AudioParam needs tests for the parameter automation routines.
+        https://bugs.webkit.org/show_bug.cgi?id=77666
+
+        Reviewed by Chris Rogers.
+
+        Tests: webaudio/audioparam-linearRampToValueAtTime.html
+               webaudio/audioparam-setTargetValueAtTime.html
+               webaudio/audioparam-setValueAtTime.html
+               webaudio/audioparam-setValueCurveAtTime.html
+
+        * webaudio/AudioParamTimeline.cpp:
+        (WebCore::AudioParamTimeline::valuesForTimeRangeImpl): Round the
+        curveIndex to fix timing issue in setValueCurveAtTime.
+
 2012-03-01  Pablo Flouret  <pablof@motorola.com>
 
         Fix code generators to correctly guard header declarations that have a [Conditional] attribute.
index cd8f536..8be48c5 100644 (file)
@@ -329,7 +329,10 @@ float AudioParamTimeline::valuesForTimeRangeImpl(float startTime,
                     // Render the stretched curve data using nearest neighbor sampling.
                     // Oversampled curve data can be provided if smoothness is desired.
                     for (; writeIndex < fillToFrame; ++writeIndex) {
-                        unsigned curveIndex = static_cast<unsigned>(curveVirtualIndex);
+                        // Ideally we'd use round() from MathExtras, but we're in a tight loop here
+                        // and we're trading off precision for extra speed.
+                        unsigned curveIndex = static_cast<unsigned>(0.5 + curveVirtualIndex);
+
                         curveVirtualIndex += curvePointsPerFrame;
 
                         // Bounds check.