<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"
"http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head><meta http-equiv="content-type" content="text/html; charset=utf-8" />
<title>[179838] trunk</title>
</head>
<body>

<style type="text/css"><!--
#msg dl.meta { border: 1px #006 solid; background: #369; padding: 6px; color: #fff; }
#msg dl.meta dt { float: left; width: 6em; font-weight: bold; }
#msg dt:after { content:':';}
#msg dl, #msg dt, #msg ul, #msg li, #header, #footer, #logmsg { font-family: verdana,arial,helvetica,sans-serif; font-size: 10pt;  }
#msg dl a { font-weight: bold}
#msg dl a:link    { color:#fc3; }
#msg dl a:active  { color:#ff0; }
#msg dl a:visited { color:#cc6; }
h3 { font-family: verdana,arial,helvetica,sans-serif; font-size: 10pt; font-weight: bold; }
#msg pre { overflow: auto; background: #ffc; border: 1px #fa0 solid; padding: 6px; }
#logmsg { background: #ffc; border: 1px #fa0 solid; padding: 1em 1em 0 1em; }
#logmsg p, #logmsg pre, #logmsg blockquote { margin: 0 0 1em 0; }
#logmsg p, #logmsg li, #logmsg dt, #logmsg dd { line-height: 14pt; }
#logmsg h1, #logmsg h2, #logmsg h3, #logmsg h4, #logmsg h5, #logmsg h6 { margin: .5em 0; }
#logmsg h1:first-child, #logmsg h2:first-child, #logmsg h3:first-child, #logmsg h4:first-child, #logmsg h5:first-child, #logmsg h6:first-child { margin-top: 0; }
#logmsg ul, #logmsg ol { padding: 0; list-style-position: inside; margin: 0 0 0 1em; }
#logmsg ul { text-indent: -1em; padding-left: 1em; }#logmsg ol { text-indent: -1.5em; padding-left: 1.5em; }
#logmsg > ul, #logmsg > ol { margin: 0 0 1em 0; }
#logmsg pre { background: #eee; padding: 1em; }
#logmsg blockquote { border: 1px solid #fa0; border-left-width: 10px; padding: 1em 1em 0 1em; background: white;}
#logmsg dl { margin: 0; }
#logmsg dt { font-weight: bold; }
#logmsg dd { margin: 0; padding: 0 0 0.5em 0; }
#logmsg dd:before { content:'\00bb';}
#logmsg table { border-spacing: 0px; border-collapse: collapse; border-top: 4px solid #fa0; border-bottom: 1px solid #fa0; background: #fff; }
#logmsg table th { text-align: left; font-weight: normal; padding: 0.2em 0.5em; border-top: 1px dotted #fa0; }
#logmsg table td { text-align: right; border-top: 1px dotted #fa0; padding: 0.2em 0.5em; }
#logmsg table thead th { text-align: center; border-bottom: 1px solid #fa0; }
#logmsg table th.Corner { text-align: left; }
#logmsg hr { border: none 0; border-top: 2px dashed #fa0; height: 1px; }
#header, #footer { color: #fff; background: #636; border: 1px #300 solid; padding: 6px; }
#patch { width: 100%; }
#patch h4 {font-family: verdana,arial,helvetica,sans-serif;font-size:10pt;padding:8px;background:#369;color:#fff;margin:0;}
#patch .propset h4, #patch .binary h4 {margin:0;}
#patch pre {padding:0;line-height:1.2em;margin:0;}
#patch .diff {width:100%;background:#eee;padding: 0 0 10px 0;overflow:auto;}
#patch .propset .diff, #patch .binary .diff  {padding:10px 0;}
#patch span {display:block;padding:0 10px;}
#patch .modfile, #patch .addfile, #patch .delfile, #patch .propset, #patch .binary, #patch .copfile {border:1px solid #ccc;margin:10px 0;}
#patch ins {background:#dfd;text-decoration:none;display:block;padding:0 10px;}
#patch del {background:#fdd;text-decoration:none;display:block;padding:0 10px;}
#patch .lines, .info {color:#888;background:#fff;}
--></style>
<div id="msg">
<dl class="meta">
<dt>Revision</dt> <dd><a href="http://trac.webkit.org/projects/webkit/changeset/179838">179838</a></dd>
<dt>Author</dt> <dd>jer.noble@apple.com</dd>
<dt>Date</dt> <dd>2015-02-09 11:45:54 -0800 (Mon, 09 Feb 2015)</dd>
</dl>

<h3>Log Message</h3>
<pre>[WebAudio] AudioBufferSourceNodes should accurately play backwards if given a negative playbackRate.
https://bugs.webkit.org/show_bug.cgi?id=140955

Reviewed by Eric Carlson.

Source/WebCore:

Tests: webaudio/audiobuffersource-negative-playbackrate-interpolated.html
       webaudio/audiobuffersource-negative-playbackrate.html

Add support for playing an AudioBufferSourceNode at a negative playbackRate. Change the meaning of
start() to set the initial playback position at the end of the play range if the rate of playback
is negtive.

* Modules/webaudio/AudioBufferSourceNode.cpp:
(WebCore::AudioBufferSourceNode::AudioBufferSourceNode): Allow the playbackRate AudioParam to range from [-32, 32].
(WebCore::AudioBufferSourceNode::renderFromBuffer): Change variable names from &quot;start&quot; and &quot;end&quot; to &quot;min&quot; and &quot;max&quot;
    for clarity. Add a non-interpolated and interpolated render step for negative playback.
(WebCore::AudioBufferSourceNode::start): Drive-by fix: default value of grainDuration is not 0.02.
(WebCore::AudioBufferSourceNode::startPlaying): Start playing at the end of the buffer for negative playback.
(WebCore::AudioBufferSourceNode::totalPitchRate): Allow the pitch to be negative.

LayoutTests:

* webaudio/audiobuffersource-negative-playbackrate-expected.txt: Added.
* webaudio/audiobuffersource-negative-playbackrate-interpolated-expected.txt: Added.
* webaudio/audiobuffersource-negative-playbackrate-interpolated-loop-expected.txt: Added.
* webaudio/audiobuffersource-negative-playbackrate-interpolated-loop.html: Added.
* webaudio/audiobuffersource-negative-playbackrate-interpolated.html:
* webaudio/audiobuffersource-negative-playbackrate-loop-expected.txt: Added.
* webaudio/audiobuffersource-negative-playbackrate-loop.html: Added.
* webaudio/audiobuffersource-negative-playbackrate.html:
* webaudio/resources/audiobuffersource-testing.js:
(createRamp):

Get rid of extra HRTF padding as it's now unnecessary.

* webaudio/resources/note-grain-on-testing.js:
(createSignalBuffer):
(verifyStartAndEndFrames):</pre>

<h3>Modified Paths</h3>
<ul>
<li><a href="#trunkLayoutTestsChangeLog">trunk/LayoutTests/ChangeLog</a></li>
<li><a href="#trunkLayoutTestswebaudioresourcesaudiobuffersourcetestingjs">trunk/LayoutTests/webaudio/resources/audiobuffersource-testing.js</a></li>
<li><a href="#trunkLayoutTestswebaudioresourcesnotegrainontestingjs">trunk/LayoutTests/webaudio/resources/note-grain-on-testing.js</a></li>
<li><a href="#trunkSourceWebCoreChangeLog">trunk/Source/WebCore/ChangeLog</a></li>
<li><a href="#trunkSourceWebCoreModuleswebaudioAudioBufferSourceNodecpp">trunk/Source/WebCore/Modules/webaudio/AudioBufferSourceNode.cpp</a></li>
</ul>

<h3>Added Paths</h3>
<ul>
<li><a href="#trunkLayoutTestswebaudioaudiobuffersourcenegativeplaybackrateexpectedtxt">trunk/LayoutTests/webaudio/audiobuffersource-negative-playbackrate-expected.txt</a></li>
<li><a href="#trunkLayoutTestswebaudioaudiobuffersourcenegativeplaybackrateinterpolatedexpectedtxt">trunk/LayoutTests/webaudio/audiobuffersource-negative-playbackrate-interpolated-expected.txt</a></li>
<li><a href="#trunkLayoutTestswebaudioaudiobuffersourcenegativeplaybackrateinterpolatedloopexpectedtxt">trunk/LayoutTests/webaudio/audiobuffersource-negative-playbackrate-interpolated-loop-expected.txt</a></li>
<li><a href="#trunkLayoutTestswebaudioaudiobuffersourcenegativeplaybackrateinterpolatedloophtml">trunk/LayoutTests/webaudio/audiobuffersource-negative-playbackrate-interpolated-loop.html</a></li>
<li><a href="#trunkLayoutTestswebaudioaudiobuffersourcenegativeplaybackrateinterpolatedhtml">trunk/LayoutTests/webaudio/audiobuffersource-negative-playbackrate-interpolated.html</a></li>
<li><a href="#trunkLayoutTestswebaudioaudiobuffersourcenegativeplaybackrateloopexpectedtxt">trunk/LayoutTests/webaudio/audiobuffersource-negative-playbackrate-loop-expected.txt</a></li>
<li><a href="#trunkLayoutTestswebaudioaudiobuffersourcenegativeplaybackrateloophtml">trunk/LayoutTests/webaudio/audiobuffersource-negative-playbackrate-loop.html</a></li>
<li><a href="#trunkLayoutTestswebaudioaudiobuffersourcenegativeplaybackratehtml">trunk/LayoutTests/webaudio/audiobuffersource-negative-playbackrate.html</a></li>
</ul>

</div>
<div id="patch">
<h3>Diff</h3>
<a id="trunkLayoutTestsChangeLog"></a>
<div class="modfile"><h4>Modified: trunk/LayoutTests/ChangeLog (179837 => 179838)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/LayoutTests/ChangeLog        2015-02-09 18:59:44 UTC (rev 179837)
+++ trunk/LayoutTests/ChangeLog        2015-02-09 19:45:54 UTC (rev 179838)
</span><span class="lines">@@ -1,3 +1,27 @@
</span><ins>+2015-02-09  Jer Noble  &lt;jer.noble@apple.com&gt;
+
+        [WebAudio] AudioBufferSourceNodes should accurately play backwards if given a negative playbackRate.
+        https://bugs.webkit.org/show_bug.cgi?id=140955
+
+        Reviewed by Eric Carlson.
+
+        * webaudio/audiobuffersource-negative-playbackrate-expected.txt: Added.
+        * webaudio/audiobuffersource-negative-playbackrate-interpolated-expected.txt: Added.
+        * webaudio/audiobuffersource-negative-playbackrate-interpolated-loop-expected.txt: Added.
+        * webaudio/audiobuffersource-negative-playbackrate-interpolated-loop.html: Added.
+        * webaudio/audiobuffersource-negative-playbackrate-interpolated.html:
+        * webaudio/audiobuffersource-negative-playbackrate-loop-expected.txt: Added.
+        * webaudio/audiobuffersource-negative-playbackrate-loop.html: Added.
+        * webaudio/audiobuffersource-negative-playbackrate.html:
+        * webaudio/resources/audiobuffersource-testing.js:
+        (createRamp):
+
+        Get rid of extra HRTF padding as it's now unnecessary.
+
+        * webaudio/resources/note-grain-on-testing.js:
+        (createSignalBuffer):
+        (verifyStartAndEndFrames):
+
</ins><span class="cx"> 2015-02-09  David Kilzer  &lt;ddkilzer@apple.com&gt;
</span><span class="cx"> 
</span><span class="cx">         [iOS] Gardening: css3/masking/mask-repeat-space-padding.html
</span></span></pre></div>
<a id="trunkLayoutTestswebaudioaudiobuffersourcenegativeplaybackrateexpectedtxt"></a>
<div class="addfile"><h4>Added: trunk/LayoutTests/webaudio/audiobuffersource-negative-playbackrate-expected.txt (0 => 179838)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/LayoutTests/webaudio/audiobuffersource-negative-playbackrate-expected.txt                                (rev 0)
+++ trunk/LayoutTests/webaudio/audiobuffersource-negative-playbackrate-expected.txt        2015-02-09 19:45:54 UTC (rev 179838)
</span><span class="lines">@@ -0,0 +1,2 @@
</span><ins>+PASS Test playback at -1 playbackRate
+
</ins></span></pre></div>
<a id="trunkLayoutTestswebaudioaudiobuffersourcenegativeplaybackrateinterpolatedexpectedtxt"></a>
<div class="addfile"><h4>Added: trunk/LayoutTests/webaudio/audiobuffersource-negative-playbackrate-interpolated-expected.txt (0 => 179838)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/LayoutTests/webaudio/audiobuffersource-negative-playbackrate-interpolated-expected.txt                                (rev 0)
+++ trunk/LayoutTests/webaudio/audiobuffersource-negative-playbackrate-interpolated-expected.txt        2015-02-09 19:45:54 UTC (rev 179838)
</span><span class="lines">@@ -0,0 +1,2 @@
</span><ins>+PASS Test playback at -0.75 playbackRate
+
</ins></span></pre></div>
<a id="trunkLayoutTestswebaudioaudiobuffersourcenegativeplaybackrateinterpolatedloopexpectedtxt"></a>
<div class="addfile"><h4>Added: trunk/LayoutTests/webaudio/audiobuffersource-negative-playbackrate-interpolated-loop-expected.txt (0 => 179838)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/LayoutTests/webaudio/audiobuffersource-negative-playbackrate-interpolated-loop-expected.txt                                (rev 0)
+++ trunk/LayoutTests/webaudio/audiobuffersource-negative-playbackrate-interpolated-loop-expected.txt        2015-02-09 19:45:54 UTC (rev 179838)
</span><span class="lines">@@ -0,0 +1,2 @@
</span><ins>+PASS Test looping playback at -0.75 playbackRate
+
</ins></span></pre></div>
<a id="trunkLayoutTestswebaudioaudiobuffersourcenegativeplaybackrateinterpolatedloophtml"></a>
<div class="addfile"><h4>Added: trunk/LayoutTests/webaudio/audiobuffersource-negative-playbackrate-interpolated-loop.html (0 => 179838)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/LayoutTests/webaudio/audiobuffersource-negative-playbackrate-interpolated-loop.html                                (rev 0)
+++ trunk/LayoutTests/webaudio/audiobuffersource-negative-playbackrate-interpolated-loop.html        2015-02-09 19:45:54 UTC (rev 179838)
</span><span class="lines">@@ -0,0 +1,53 @@
</span><ins>+&lt;!DOCTYPE html&gt;
+&lt;html&gt;
+&lt;head&gt;
+    &lt;title&gt;audiobuffersource-negative-playbackrate-interpolated-loop&lt;/title&gt;
+    &lt;script src=&quot;../resources/js-test-pre.js&quot;&gt;&lt;/script&gt;
+    &lt;script src=&quot;resources/audio-testing.js&quot;&gt;&lt;/script&gt;
+    &lt;script src=&quot;resources/audiobuffersource-testing.js&quot;&gt;&lt;/script&gt;
+
+    &lt;script&gt;
+
+    var sampleRate = 44100.0;
+    var sourceFrames = 128;
+    var renderFrames = 10;
+    var testSpacingFrames = 0;
+    var startLoop = 100 / sampleRate;
+    var loopDuration = (renderFrames / 2) / sampleRate;
+    var endLoop = startLoop + loopDuration;
+
+    var tests = [{ 
+        description:&quot;Test looping playback at -0.75 playbackRate&quot;,
+        offsetFrame:0,
+        renderFrames:renderFrames,
+        expected:[104,103.25,102.5,101.75,101,100.25,102,103.75,103,102.25],
+    }];
+
+    function go() {
+        if (window.testRunner) {
+            testRunner.dumpAsText();
+            testRunner.waitUntilDone();
+        }
+
+        context = new webkitOfflineAudioContext(1, renderFrames, sampleRate);
+
+        var bufferSource = context.createBufferSource();
+        bufferSource.buffer = createRamp(context, 0, 127, sourceFrames);
+
+        bufferSource.connect(context.destination);
+        bufferSource.playbackRate.value = -0.75;
+        bufferSource.loop = true;
+        bufferSource.loopStart = startLoop;
+        bufferSource.loopEnd = endLoop;
+        bufferSource.start(0, startLoop, loopDuration);
+        bufferSource.stop(loopDuration * 2);
+
+        context.oncomplete = checkAllTests;
+        context.startRendering();
+    }
+
+    &lt;/script&gt;
+&lt;/head&gt;
+&lt;body onload=&quot;go()&quot;&gt;
+&lt;/body&gt;
+&lt;/html&gt;
</ins></span></pre></div>
<a id="trunkLayoutTestswebaudioaudiobuffersourcenegativeplaybackrateinterpolatedhtml"></a>
<div class="addfile"><h4>Added: trunk/LayoutTests/webaudio/audiobuffersource-negative-playbackrate-interpolated.html (0 => 179838)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/LayoutTests/webaudio/audiobuffersource-negative-playbackrate-interpolated.html                                (rev 0)
+++ trunk/LayoutTests/webaudio/audiobuffersource-negative-playbackrate-interpolated.html        2015-02-09 19:45:54 UTC (rev 179838)
</span><span class="lines">@@ -0,0 +1,46 @@
</span><ins>+&lt;!DOCTYPE html&gt;
+&lt;html&gt;
+&lt;head&gt;
+    &lt;title&gt;audiobuffersource-negative-playbackrate-interpolated&lt;/title&gt;
+    &lt;script src=&quot;../resources/js-test-pre.js&quot;&gt;&lt;/script&gt;
+    &lt;script src=&quot;resources/audio-testing.js&quot;&gt;&lt;/script&gt;
+    &lt;script src=&quot;resources/audiobuffersource-testing.js&quot;&gt;&lt;/script&gt;
+
+    &lt;script&gt;
+
+    var sampleRate = 44100.0;
+    var sourceFrames = 128;
+    var renderFrames = 10;
+    var testSpacingFrames = 0;
+
+    var tests = [{ 
+        description:&quot;Test playback at -0.75 playbackRate&quot;,
+        offsetFrame:0,
+        renderFrames:renderFrames,
+        expected:[127, 126.25, 125.5, 124.75, 124, 123.25, 122.5, 121.75, 121, 120.25]
+    }];
+
+    function go() {
+        if (window.testRunner) {
+            testRunner.dumpAsText();
+            testRunner.waitUntilDone();
+        }
+
+        context = new webkitOfflineAudioContext(1, renderFrames, sampleRate);
+
+        var bufferSource = context.createBufferSource();
+        bufferSource.buffer = createRamp(context, 0, 127, sourceFrames);
+
+        bufferSource.connect(context.destination);
+        bufferSource.playbackRate.value = -0.75;
+        bufferSource.start(0);
+
+        context.oncomplete = checkAllTests;
+        context.startRendering();
+    }
+
+    &lt;/script&gt;
+&lt;/head&gt;
+&lt;body onload=&quot;go()&quot;&gt;
+&lt;/body&gt;
+&lt;/html&gt;
</ins></span></pre></div>
<a id="trunkLayoutTestswebaudioaudiobuffersourcenegativeplaybackrateloopexpectedtxt"></a>
<div class="addfile"><h4>Added: trunk/LayoutTests/webaudio/audiobuffersource-negative-playbackrate-loop-expected.txt (0 => 179838)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/LayoutTests/webaudio/audiobuffersource-negative-playbackrate-loop-expected.txt                                (rev 0)
+++ trunk/LayoutTests/webaudio/audiobuffersource-negative-playbackrate-loop-expected.txt        2015-02-09 19:45:54 UTC (rev 179838)
</span><span class="lines">@@ -0,0 +1,2 @@
</span><ins>+PASS Test looping playback at -1 playbackRate
+
</ins></span></pre></div>
<a id="trunkLayoutTestswebaudioaudiobuffersourcenegativeplaybackrateloophtml"></a>
<div class="addfile"><h4>Added: trunk/LayoutTests/webaudio/audiobuffersource-negative-playbackrate-loop.html (0 => 179838)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/LayoutTests/webaudio/audiobuffersource-negative-playbackrate-loop.html                                (rev 0)
+++ trunk/LayoutTests/webaudio/audiobuffersource-negative-playbackrate-loop.html        2015-02-09 19:45:54 UTC (rev 179838)
</span><span class="lines">@@ -0,0 +1,53 @@
</span><ins>+&lt;!DOCTYPE html&gt;
+&lt;html&gt;
+&lt;head&gt;
+    &lt;title&gt;audiobuffersource-negative-playbackrate-loop&lt;/title&gt;
+    &lt;script src=&quot;../resources/js-test-pre.js&quot;&gt;&lt;/script&gt;
+    &lt;script src=&quot;resources/audio-testing.js&quot;&gt;&lt;/script&gt;
+    &lt;script src=&quot;resources/audiobuffersource-testing.js&quot;&gt;&lt;/script&gt;
+
+    &lt;script&gt;
+
+    var sampleRate = 44100.0;
+    var sourceFrames = 128;
+    var renderFrames = 10;
+    var testSpacingFrames = 0;
+    var startLoop = 100 / sampleRate;
+    var loopDuration = (renderFrames / 2) / sampleRate;
+    var endLoop = startLoop + loopDuration;
+
+    var tests = [{ 
+        description:&quot;Test looping playback at -1 playbackRate&quot;,
+        offsetFrame:0,
+        renderFrames:renderFrames,
+        expected:[104, 103, 102, 101, 100, 104, 103, 102, 101, 100],
+    }];
+
+    function go() {
+        if (window.testRunner) {
+            testRunner.dumpAsText();
+            testRunner.waitUntilDone();
+        }
+
+        context = new webkitOfflineAudioContext(1, renderFrames, sampleRate);
+
+        var bufferSource = context.createBufferSource();
+        bufferSource.buffer = createRamp(context, 0, 127, sourceFrames);
+
+        bufferSource.connect(context.destination);
+        bufferSource.playbackRate.value = -1;
+        bufferSource.loop = true;
+        bufferSource.loopStart = startLoop;
+        bufferSource.loopEnd = endLoop;
+        bufferSource.start(0, startLoop, loopDuration);
+        bufferSource.stop(loopDuration * 2);
+
+        context.oncomplete = checkAllTests;
+        context.startRendering();
+    }
+
+    &lt;/script&gt;
+&lt;/head&gt;
+&lt;body onload=&quot;go()&quot;&gt;
+&lt;/body&gt;
+&lt;/html&gt;
</ins></span></pre></div>
<a id="trunkLayoutTestswebaudioaudiobuffersourcenegativeplaybackratehtml"></a>
<div class="addfile"><h4>Added: trunk/LayoutTests/webaudio/audiobuffersource-negative-playbackrate.html (0 => 179838)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/LayoutTests/webaudio/audiobuffersource-negative-playbackrate.html                                (rev 0)
+++ trunk/LayoutTests/webaudio/audiobuffersource-negative-playbackrate.html        2015-02-09 19:45:54 UTC (rev 179838)
</span><span class="lines">@@ -0,0 +1,46 @@
</span><ins>+&lt;!DOCTYPE html&gt;
+&lt;html&gt;
+&lt;head&gt;
+    &lt;title&gt;audiobuffersource-negative-playbackrate&lt;/title&gt;
+    &lt;script src=&quot;../resources/js-test-pre.js&quot;&gt;&lt;/script&gt;
+    &lt;script src=&quot;resources/audio-testing.js&quot;&gt;&lt;/script&gt;
+    &lt;script src=&quot;resources/audiobuffersource-testing.js&quot;&gt;&lt;/script&gt;
+
+    &lt;script&gt;
+
+    var sampleRate = 44100.0;
+    var sourceFrames = 128;
+    var renderFrames = 10;
+    var testSpacingFrames = 0;
+
+    var tests = [{ 
+        description:&quot;Test playback at -1 playbackRate&quot;,
+        offsetFrame:0,
+        renderFrames:renderFrames,
+        expected:[127, 126, 125, 124, 123, 122, 121, 120, 119, 118],
+    }];
+
+    function go() {
+        if (window.testRunner) {
+            testRunner.dumpAsText();
+            testRunner.waitUntilDone();
+        }
+
+        context = new webkitOfflineAudioContext(1, renderFrames, sampleRate);
+
+        var bufferSource = context.createBufferSource();
+        bufferSource.buffer = createRamp(context, 0, 127, sourceFrames);
+
+        bufferSource.connect(context.destination);
+        bufferSource.playbackRate.value = -1;
+        bufferSource.start(0);
+
+        context.oncomplete = checkAllTests;
+        context.startRendering();
+    }
+
+    &lt;/script&gt;
+&lt;/head&gt;
+&lt;body onload=&quot;go()&quot;&gt;
+&lt;/body&gt;
+&lt;/html&gt;
</ins></span></pre></div>
<a id="trunkLayoutTestswebaudioresourcesaudiobuffersourcetestingjs"></a>
<div class="modfile"><h4>Modified: trunk/LayoutTests/webaudio/resources/audiobuffersource-testing.js (179837 => 179838)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/LayoutTests/webaudio/resources/audiobuffersource-testing.js        2015-02-09 18:59:44 UTC (rev 179837)
+++ trunk/LayoutTests/webaudio/resources/audiobuffersource-testing.js        2015-02-09 19:45:54 UTC (rev 179838)
</span><span class="lines">@@ -9,6 +9,21 @@
</span><span class="cx">     return audioBuffer;
</span><span class="cx"> }
</span><span class="cx"> 
</span><ins>+function createRamp(context, startValue, endValue, numberOfSamples) {
+    var audioBuffer = context.createBuffer(1, numberOfSamples, context.sampleRate);
+    var channelData = audioBuffer.getChannelData(0);
+
+    var delta = (endValue - startValue) / (numberOfSamples - 1);
+    var nextValue = startValue;
+
+    for (var i = 0; i &lt; numberOfSamples; ++i) {
+        channelData[i] = nextValue;
+        nextValue += delta;
+    }
+
+    return audioBuffer;
+}
+
</ins><span class="cx"> function checkSingleTest(renderedBuffer, i) {
</span><span class="cx">     var renderedData = renderedBuffer.getChannelData(0);
</span><span class="cx">     var offsetFrame = i * testSpacingFrames;
</span></span></pre></div>
<a id="trunkLayoutTestswebaudioresourcesnotegrainontestingjs"></a>
<div class="modfile"><h4>Modified: trunk/LayoutTests/webaudio/resources/note-grain-on-testing.js (179837 => 179838)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/LayoutTests/webaudio/resources/note-grain-on-testing.js        2015-02-09 18:59:44 UTC (rev 179837)
+++ trunk/LayoutTests/webaudio/resources/note-grain-on-testing.js        2015-02-09 19:45:54 UTC (rev 179838)
</span><span class="lines">@@ -1,21 +1,13 @@
</span><span class="cx"> var sampleRate = 44100.0;
</span><span class="cx"> 
</span><del>-// HRTF extra frames.  This is a magic constant currently in
-// AudioBufferSourceNode::process that always extends the
-// duration by this number of samples.  See bug 77224
-// (https://bugs.webkit.org/show_bug.cgi?id=77224).
-var extraFramesHRTF = 512;
-
</del><span class="cx"> // How many grains to play.
</span><span class="cx"> var numberOfTests = 100;
</span><span class="cx"> 
</span><span class="cx"> // Duration of each grain to be played
</span><span class="cx"> var duration = 0.01;
</span><span class="cx"> 
</span><del>-// Time step between the start of each grain.  We need to add a little
-// bit of silence so we can detect grain boundaries and also account
-// for the extra frames for HRTF.
-var timeStep = duration + .005 + extraFramesHRTF / sampleRate;
</del><ins>+// Time step between the start of each grain.
+var timeStep = duration + .005;
</ins><span class="cx"> 
</span><span class="cx"> // Time step between the start for each grain.
</span><span class="cx"> var grainOffsetStep = 0.001;
</span><span class="lines">@@ -33,7 +25,7 @@
</span><span class="cx">     // Make sure the buffer has enough data for all of the possible
</span><span class="cx">     // grain offsets and durations.  Need to include the extra frames
</span><span class="cx">     // for HRTF.  The additional 1 is for any round-off errors.
</span><del>-    var signalLength = Math.floor(1 + extraFramesHRTF + sampleRate * (numberOfTests * grainOffsetStep + duration));
</del><ins>+    var signalLength = Math.floor(1 + sampleRate * (numberOfTests * grainOffsetStep + duration));
</ins><span class="cx"> 
</span><span class="cx">     var buffer = context.createBuffer(2, signalLength, sampleRate);
</span><span class="cx">     var data = buffer.getChannelData(0);
</span><span class="lines">@@ -135,7 +127,7 @@
</span><span class="cx">         var expectedStart = timeToSampleFrame(k * timeStep, sampleRate);
</span><span class="cx">         // The end point is the duration, plus the extra frames
</span><span class="cx">         // for HRTF.
</span><del>-        var expectedEnd = extraFramesHRTF + expectedStart + grainLengthInSampleFrames(k * grainOffsetStep, duration, sampleRate);
</del><ins>+        var expectedEnd = expectedStart + grainLengthInSampleFrames(k * grainOffsetStep, duration, sampleRate);
</ins><span class="cx"> 
</span><span class="cx">         if (startFrames[k] != expectedStart) {
</span><span class="cx">             testFailed(&quot;Pulse &quot; + k + &quot; started at &quot; + startFrames[k] + &quot; but expected at &quot; + expectedStart);
</span></span></pre></div>
<a id="trunkSourceWebCoreChangeLog"></a>
<div class="modfile"><h4>Modified: trunk/Source/WebCore/ChangeLog (179837 => 179838)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Source/WebCore/ChangeLog        2015-02-09 18:59:44 UTC (rev 179837)
+++ trunk/Source/WebCore/ChangeLog        2015-02-09 19:45:54 UTC (rev 179838)
</span><span class="lines">@@ -1,3 +1,25 @@
</span><ins>+2015-02-09  Jer Noble  &lt;jer.noble@apple.com&gt;
+
+        [WebAudio] AudioBufferSourceNodes should accurately play backwards if given a negative playbackRate.
+        https://bugs.webkit.org/show_bug.cgi?id=140955
+
+        Reviewed by Eric Carlson.
+
+        Tests: webaudio/audiobuffersource-negative-playbackrate-interpolated.html
+               webaudio/audiobuffersource-negative-playbackrate.html
+
+        Add support for playing an AudioBufferSourceNode at a negative playbackRate. Change the meaning of
+        start() to set the initial playback position at the end of the play range if the rate of playback
+        is negtive.
+
+        * Modules/webaudio/AudioBufferSourceNode.cpp:
+        (WebCore::AudioBufferSourceNode::AudioBufferSourceNode): Allow the playbackRate AudioParam to range from [-32, 32].
+        (WebCore::AudioBufferSourceNode::renderFromBuffer): Change variable names from &quot;start&quot; and &quot;end&quot; to &quot;min&quot; and &quot;max&quot;
+            for clarity. Add a non-interpolated and interpolated render step for negative playback.
+        (WebCore::AudioBufferSourceNode::start): Drive-by fix: default value of grainDuration is not 0.02.
+        (WebCore::AudioBufferSourceNode::startPlaying): Start playing at the end of the buffer for negative playback.
+        (WebCore::AudioBufferSourceNode::totalPitchRate): Allow the pitch to be negative.
+
</ins><span class="cx"> 2015-02-09  Darin Adler  &lt;darin@apple.com&gt;
</span><span class="cx"> 
</span><span class="cx">         Try to fix build on platforms that use SVG &quot;all in one&quot; file (Windows).
</span></span></pre></div>
<a id="trunkSourceWebCoreModuleswebaudioAudioBufferSourceNodecpp"></a>
<div class="modfile"><h4>Modified: trunk/Source/WebCore/Modules/webaudio/AudioBufferSourceNode.cpp (179837 => 179838)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Source/WebCore/Modules/webaudio/AudioBufferSourceNode.cpp        2015-02-09 18:59:44 UTC (rev 179837)
+++ trunk/Source/WebCore/Modules/webaudio/AudioBufferSourceNode.cpp        2015-02-09 19:45:54 UTC (rev 179838)
</span><span class="lines">@@ -70,7 +70,7 @@
</span><span class="cx">     setNodeType(NodeTypeAudioBufferSource);
</span><span class="cx"> 
</span><span class="cx">     m_gain = AudioParam::create(context, &quot;gain&quot;, 1.0, 0.0, 1.0);
</span><del>-    m_playbackRate = AudioParam::create(context, &quot;playbackRate&quot;, 1.0, 0.0, MaxRate);
</del><ins>+    m_playbackRate = AudioParam::create(context, &quot;playbackRate&quot;, 1.0, -MaxRate, MaxRate);
</ins><span class="cx"> 
</span><span class="cx">     // Default to mono.  A call to setBuffer() will set the number of output channels to that of the buffer.
</span><span class="cx">     addOutput(std::make_unique&lt;AudioNodeOutput&gt;(this, 1));
</span><span class="lines">@@ -200,47 +200,54 @@
</span><span class="cx"> 
</span><span class="cx">     size_t bufferLength = buffer()-&gt;length();
</span><span class="cx">     double bufferSampleRate = buffer()-&gt;sampleRate();
</span><ins>+    double pitchRate = totalPitchRate();
+    bool reverse = pitchRate &lt; 0;
</ins><span class="cx"> 
</span><span class="cx">     // Avoid converting from time to sample-frames twice by computing
</span><span class="cx">     // the grain end time first before computing the sample frame.
</span><del>-    unsigned endFrame = m_isGrain ? AudioUtilities::timeToSampleFrame(m_grainOffset + m_grainDuration, bufferSampleRate) : bufferLength;
-    
-    // This is a HACK to allow for HRTF tail-time - avoids glitch at end.
-    // FIXME: implement tailTime for each AudioNode for a more general solution to this problem.
-    // https://bugs.webkit.org/show_bug.cgi?id=77224
</del><ins>+    unsigned maxFrame;
</ins><span class="cx">     if (m_isGrain)
</span><del>-        endFrame += 512;
</del><ins>+        maxFrame = AudioUtilities::timeToSampleFrame(m_grainOffset + m_grainDuration, bufferSampleRate);
+    else
+        maxFrame = bufferLength;
</ins><span class="cx"> 
</span><span class="cx">     // Do some sanity checking.
</span><del>-    if (endFrame &gt; bufferLength)
-        endFrame = bufferLength;
-    if (m_virtualReadIndex &gt;= endFrame)
</del><ins>+    if (maxFrame &gt; bufferLength)
+        maxFrame = bufferLength;
+    if (reverse &amp;&amp; m_virtualReadIndex &lt;= 0)
+        m_virtualReadIndex = maxFrame - 1;
+    else if (!reverse &amp;&amp; m_virtualReadIndex &gt;= maxFrame)
</ins><span class="cx">         m_virtualReadIndex = 0; // reset to start
</span><span class="cx"> 
</span><span class="cx">     // If the .loop attribute is true, then values of m_loopStart == 0 &amp;&amp; m_loopEnd == 0 implies
</span><span class="cx">     // that we should use the entire buffer as the loop, otherwise use the loop values in m_loopStart and m_loopEnd.
</span><del>-    double virtualEndFrame = endFrame;
-    double virtualDeltaFrames = endFrame;
</del><ins>+    double virtualMaxFrame = maxFrame;
+    double virtualMinFrame = 0;
+    double virtualDeltaFrames = maxFrame;
</ins><span class="cx"> 
</span><span class="cx">     if (loop() &amp;&amp; (m_loopStart || m_loopEnd) &amp;&amp; m_loopStart &gt;= 0 &amp;&amp; m_loopEnd &gt; 0 &amp;&amp; m_loopStart &lt; m_loopEnd) {
</span><span class="cx">         // Convert from seconds to sample-frames.
</span><del>-        double loopStartFrame = m_loopStart * buffer()-&gt;sampleRate();
-        double loopEndFrame = m_loopEnd * buffer()-&gt;sampleRate();
</del><ins>+        double loopMinFrame = m_loopStart * buffer()-&gt;sampleRate();
+        double loopMaxFrame = m_loopEnd * buffer()-&gt;sampleRate();
</ins><span class="cx"> 
</span><del>-        virtualEndFrame = std::min(loopEndFrame, virtualEndFrame);
-        virtualDeltaFrames = virtualEndFrame - loopStartFrame;
</del><ins>+        virtualMaxFrame = std::min(loopMaxFrame, virtualMaxFrame);
+        virtualMinFrame = std::max(loopMinFrame, virtualMinFrame);
+        virtualDeltaFrames = virtualMaxFrame - virtualMinFrame;
</ins><span class="cx">     }
</span><span class="cx"> 
</span><span class="cx"> 
</span><del>-    double pitchRate = totalPitchRate();
-
</del><span class="cx">     // Sanity check that our playback rate isn't larger than the loop size.
</span><del>-    if (pitchRate &gt;= virtualDeltaFrames)
</del><ins>+    if (fabs(pitchRate) &gt;= virtualDeltaFrames)
</ins><span class="cx">         return false;
</span><span class="cx"> 
</span><span class="cx">     // Get local copy.
</span><span class="cx">     double virtualReadIndex = m_virtualReadIndex;
</span><span class="cx"> 
</span><ins>+    bool needsInterpolation = virtualReadIndex != floor(virtualReadIndex)
+        || virtualDeltaFrames != floor(virtualDeltaFrames)
+        || virtualMaxFrame != floor(virtualMaxFrame)
+        || virtualMinFrame != floor(virtualMinFrame);
+
</ins><span class="cx">     // Render loop - reading from the source buffer to the destination using linear interpolation.
</span><span class="cx">     int framesToProcess = numberOfFrames;
</span><span class="cx"> 
</span><span class="lines">@@ -249,14 +256,12 @@
</span><span class="cx"> 
</span><span class="cx">     // Optimize for the very common case of playing back with pitchRate == 1.
</span><span class="cx">     // We can avoid the linear interpolation.
</span><del>-    if (pitchRate == 1 &amp;&amp; virtualReadIndex == floor(virtualReadIndex)
-        &amp;&amp; virtualDeltaFrames == floor(virtualDeltaFrames)
-        &amp;&amp; virtualEndFrame == floor(virtualEndFrame)) {
</del><ins>+    if (pitchRate == 1 &amp;&amp; !needsInterpolation) {
</ins><span class="cx">         unsigned readIndex = static_cast&lt;unsigned&gt;(virtualReadIndex);
</span><span class="cx">         unsigned deltaFrames = static_cast&lt;unsigned&gt;(virtualDeltaFrames);
</span><del>-        endFrame = static_cast&lt;unsigned&gt;(virtualEndFrame);
</del><ins>+        maxFrame = static_cast&lt;unsigned&gt;(virtualMaxFrame);
</ins><span class="cx">         while (framesToProcess &gt; 0) {
</span><del>-            int framesToEnd = endFrame - readIndex;
</del><ins>+            int framesToEnd = maxFrame - readIndex;
</ins><span class="cx">             int framesThisTime = std::min(framesToProcess, framesToEnd);
</span><span class="cx">             framesThisTime = std::max(0, framesThisTime);
</span><span class="cx"> 
</span><span class="lines">@@ -268,13 +273,83 @@
</span><span class="cx">             framesToProcess -= framesThisTime;
</span><span class="cx"> 
</span><span class="cx">             // Wrap-around.
</span><del>-            if (readIndex &gt;= endFrame) {
</del><ins>+            if (readIndex &gt;= maxFrame) {
</ins><span class="cx">                 readIndex -= deltaFrames;
</span><span class="cx">                 if (renderSilenceAndFinishIfNotLooping(bus, writeIndex, framesToProcess))
</span><span class="cx">                     break;
</span><span class="cx">             }
</span><span class="cx">         }
</span><span class="cx">         virtualReadIndex = readIndex;
</span><ins>+    } else if (pitchRate == -1 &amp;&amp; !needsInterpolation) {
+        int readIndex = static_cast&lt;int&gt;(virtualReadIndex);
+        int deltaFrames = static_cast&lt;int&gt;(virtualDeltaFrames);
+        int minFrame = static_cast&lt;int&gt;(virtualMinFrame) - 1;
+        while (framesToProcess &gt; 0) {
+            int framesToEnd = readIndex - minFrame;
+            int framesThisTime = std::min&lt;int&gt;(framesToProcess, framesToEnd);
+            framesThisTime = std::max&lt;int&gt;(0, framesThisTime);
+
+            while (framesThisTime--) {
+                for (unsigned i = 0; i &lt; numberOfChannels; ++i) {
+                    float* destination = destinationChannels[i];
+                    const float* source = sourceChannels[i];
+
+                    destination[writeIndex] = source[readIndex];
+                }
+
+                ++writeIndex;
+                --readIndex;
+                --framesToProcess;
+            }
+
+            // Wrap-around.
+            if (readIndex &lt;= minFrame) {
+                readIndex += deltaFrames;
+                if (renderSilenceAndFinishIfNotLooping(bus, writeIndex, framesToProcess))
+                    break;
+            }
+        }
+        virtualReadIndex = readIndex;
+    } else if (!pitchRate) {
+        unsigned readIndex = static_cast&lt;unsigned&gt;(virtualReadIndex);
+
+        for (unsigned i = 0; i &lt; numberOfChannels; ++i)
+            std::fill_n(destinationChannels[i], framesToProcess, sourceChannels[i][readIndex]);
+    } else if (reverse) {
+        unsigned maxFrame = static_cast&lt;unsigned&gt;(virtualMaxFrame);
+        unsigned minFrame = static_cast&lt;unsigned&gt;(floorf(virtualMinFrame));
+
+        while (framesToProcess--) {
+            unsigned readIndex = static_cast&lt;unsigned&gt;(floorf(virtualReadIndex));
+            double interpolationFactor = virtualReadIndex - readIndex;
+
+            unsigned readIndex2 = readIndex + 1;
+            if (readIndex2 &gt;= maxFrame)
+                readIndex2 = loop() ? minFrame : maxFrame - 1;
+
+            // Linear interpolation.
+            for (unsigned i = 0; i &lt; numberOfChannels; ++i) {
+                float* destination = destinationChannels[i];
+                const float* source = sourceChannels[i];
+
+                double sample1 = source[readIndex];
+                double sample2 = source[readIndex2];
+                double sample = (1.0 - interpolationFactor) * sample1 + interpolationFactor * sample2;
+
+                destination[writeIndex] = narrowPrecisionToFloat(sample);
+            }
+
+            writeIndex++;
+
+            virtualReadIndex += pitchRate;
+
+            // Wrap-around, retaining sub-sample position since virtualReadIndex is floating-point.
+            if (virtualReadIndex &lt; virtualMinFrame) {
+                virtualReadIndex += virtualDeltaFrames;
+                if (renderSilenceAndFinishIfNotLooping(bus, writeIndex, framesToProcess))
+                    break;
+            }
+        }
</ins><span class="cx">     } else {
</span><span class="cx">         while (framesToProcess--) {
</span><span class="cx">             unsigned readIndex = static_cast&lt;unsigned&gt;(virtualReadIndex);
</span><span class="lines">@@ -311,7 +386,7 @@
</span><span class="cx">             virtualReadIndex += pitchRate;
</span><span class="cx"> 
</span><span class="cx">             // Wrap-around, retaining sub-sample position since virtualReadIndex is floating-point.
</span><del>-            if (virtualReadIndex &gt;= virtualEndFrame) {
</del><ins>+            if (virtualReadIndex &gt;= virtualMaxFrame) {
</ins><span class="cx">                 virtualReadIndex -= virtualDeltaFrames;
</span><span class="cx">                 if (renderSilenceAndFinishIfNotLooping(bus, writeIndex, framesToProcess))
</span><span class="cx">                     break;
</span><span class="lines">@@ -382,7 +457,7 @@
</span><span class="cx"> 
</span><span class="cx"> void AudioBufferSourceNode::start(double when, double grainOffset, ExceptionCode&amp; ec)
</span><span class="cx"> {
</span><del>-    startPlaying(Partial, when, grainOffset, 0, ec);
</del><ins>+    startPlaying(Partial, when, grainOffset, buffer() ? buffer()-&gt;duration() - grainOffset : 0, ec);
</ins><span class="cx"> }
</span><span class="cx"> 
</span><span class="cx"> void AudioBufferSourceNode::start(double when, double grainOffset, double grainDuration, ExceptionCode&amp; ec)
</span><span class="lines">@@ -431,7 +506,7 @@
</span><span class="cx">         m_grainDuration = std::min(maxDuration, grainDuration);
</span><span class="cx">     } else {
</span><span class="cx">         m_grainOffset = 0.0;
</span><del>-        m_grainDuration = DefaultGrainDuration;
</del><ins>+        m_grainDuration = buffer()-&gt;duration();
</ins><span class="cx">     }
</span><span class="cx"> 
</span><span class="cx">     m_startTime = when;
</span><span class="lines">@@ -440,7 +515,10 @@
</span><span class="cx">     // at a sub-sample position since it will degrade the quality.
</span><span class="cx">     // When aligned to the sample-frame the playback will be identical to the PCM data stored in the buffer.
</span><span class="cx">     // Since playbackRate == 1 is very common, it's worth considering quality.
</span><del>-    m_virtualReadIndex = AudioUtilities::timeToSampleFrame(m_grainOffset, buffer()-&gt;sampleRate());
</del><ins>+    if (totalPitchRate() &lt; 0)
+        m_virtualReadIndex = AudioUtilities::timeToSampleFrame(m_grainOffset + m_grainDuration, buffer()-&gt;sampleRate()) - 1;
+    else
+        m_virtualReadIndex = AudioUtilities::timeToSampleFrame(m_grainOffset, buffer()-&gt;sampleRate());
</ins><span class="cx">     
</span><span class="cx">     m_playbackState = SCHEDULED_STATE;
</span><span class="cx"> }
</span><span class="lines">@@ -471,11 +549,7 @@
</span><span class="cx"> 
</span><span class="cx">     double totalRate = dopplerRate * sampleRateFactor * basePitchRate;
</span><span class="cx"> 
</span><del>-    // Sanity check the total rate.  It's very important that the resampler not get any bad rate values.
-    totalRate = std::max(0.0, totalRate);
-    if (!totalRate)
-        totalRate = 1; // zero rate is considered illegal
-    totalRate = std::min(MaxRate, totalRate);
</del><ins>+    totalRate = std::max(-MaxRate, std::min(MaxRate, totalRate));
</ins><span class="cx">     
</span><span class="cx">     bool isTotalRateValid = !std::isnan(totalRate) &amp;&amp; !std::isinf(totalRate);
</span><span class="cx">     ASSERT(isTotalRateValid);
</span></span></pre>
</div>
</div>

</body>
</html>