<!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 "start" and "end" to "min" and "max"
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 <jer.noble@apple.com>
+
+ [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 <ddkilzer@apple.com>
</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>+<!DOCTYPE html>
+<html>
+<head>
+ <title>audiobuffersource-negative-playbackrate-interpolated-loop</title>
+ <script src="../resources/js-test-pre.js"></script>
+ <script src="resources/audio-testing.js"></script>
+ <script src="resources/audiobuffersource-testing.js"></script>
+
+ <script>
+
+ 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:"Test looping playback at -0.75 playbackRate",
+ 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();
+ }
+
+ </script>
+</head>
+<body onload="go()">
+</body>
+</html>
</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>+<!DOCTYPE html>
+<html>
+<head>
+ <title>audiobuffersource-negative-playbackrate-interpolated</title>
+ <script src="../resources/js-test-pre.js"></script>
+ <script src="resources/audio-testing.js"></script>
+ <script src="resources/audiobuffersource-testing.js"></script>
+
+ <script>
+
+ var sampleRate = 44100.0;
+ var sourceFrames = 128;
+ var renderFrames = 10;
+ var testSpacingFrames = 0;
+
+ var tests = [{
+ description:"Test playback at -0.75 playbackRate",
+ 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();
+ }
+
+ </script>
+</head>
+<body onload="go()">
+</body>
+</html>
</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>+<!DOCTYPE html>
+<html>
+<head>
+ <title>audiobuffersource-negative-playbackrate-loop</title>
+ <script src="../resources/js-test-pre.js"></script>
+ <script src="resources/audio-testing.js"></script>
+ <script src="resources/audiobuffersource-testing.js"></script>
+
+ <script>
+
+ 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:"Test looping playback at -1 playbackRate",
+ 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();
+ }
+
+ </script>
+</head>
+<body onload="go()">
+</body>
+</html>
</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>+<!DOCTYPE html>
+<html>
+<head>
+ <title>audiobuffersource-negative-playbackrate</title>
+ <script src="../resources/js-test-pre.js"></script>
+ <script src="resources/audio-testing.js"></script>
+ <script src="resources/audiobuffersource-testing.js"></script>
+
+ <script>
+
+ var sampleRate = 44100.0;
+ var sourceFrames = 128;
+ var renderFrames = 10;
+ var testSpacingFrames = 0;
+
+ var tests = [{
+ description:"Test playback at -1 playbackRate",
+ 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();
+ }
+
+ </script>
+</head>
+<body onload="go()">
+</body>
+</html>
</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 < 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("Pulse " + k + " started at " + startFrames[k] + " but expected at " + 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 <jer.noble@apple.com>
+
+ [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 "start" and "end" to "min" and "max"
+ 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 <darin@apple.com>
</span><span class="cx">
</span><span class="cx"> Try to fix build on platforms that use SVG "all in one" 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, "gain", 1.0, 0.0, 1.0);
</span><del>- m_playbackRate = AudioParam::create(context, "playbackRate", 1.0, 0.0, MaxRate);
</del><ins>+ m_playbackRate = AudioParam::create(context, "playbackRate", 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<AudioNodeOutput>(this, 1));
</span><span class="lines">@@ -200,47 +200,54 @@
</span><span class="cx">
</span><span class="cx"> size_t bufferLength = buffer()->length();
</span><span class="cx"> double bufferSampleRate = buffer()->sampleRate();
</span><ins>+ double pitchRate = totalPitchRate();
+ bool reverse = pitchRate < 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 > bufferLength)
- endFrame = bufferLength;
- if (m_virtualReadIndex >= endFrame)
</del><ins>+ if (maxFrame > bufferLength)
+ maxFrame = bufferLength;
+ if (reverse && m_virtualReadIndex <= 0)
+ m_virtualReadIndex = maxFrame - 1;
+ else if (!reverse && m_virtualReadIndex >= 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 && 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() && (m_loopStart || m_loopEnd) && m_loopStart >= 0 && m_loopEnd > 0 && m_loopStart < m_loopEnd) {
</span><span class="cx"> // Convert from seconds to sample-frames.
</span><del>- double loopStartFrame = m_loopStart * buffer()->sampleRate();
- double loopEndFrame = m_loopEnd * buffer()->sampleRate();
</del><ins>+ double loopMinFrame = m_loopStart * buffer()->sampleRate();
+ double loopMaxFrame = m_loopEnd * buffer()->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 >= virtualDeltaFrames)
</del><ins>+ if (fabs(pitchRate) >= 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 && virtualReadIndex == floor(virtualReadIndex)
- && virtualDeltaFrames == floor(virtualDeltaFrames)
- && virtualEndFrame == floor(virtualEndFrame)) {
</del><ins>+ if (pitchRate == 1 && !needsInterpolation) {
</ins><span class="cx"> unsigned readIndex = static_cast<unsigned>(virtualReadIndex);
</span><span class="cx"> unsigned deltaFrames = static_cast<unsigned>(virtualDeltaFrames);
</span><del>- endFrame = static_cast<unsigned>(virtualEndFrame);
</del><ins>+ maxFrame = static_cast<unsigned>(virtualMaxFrame);
</ins><span class="cx"> while (framesToProcess > 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 >= endFrame) {
</del><ins>+ if (readIndex >= 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 && !needsInterpolation) {
+ int readIndex = static_cast<int>(virtualReadIndex);
+ int deltaFrames = static_cast<int>(virtualDeltaFrames);
+ int minFrame = static_cast<int>(virtualMinFrame) - 1;
+ while (framesToProcess > 0) {
+ int framesToEnd = readIndex - minFrame;
+ int framesThisTime = std::min<int>(framesToProcess, framesToEnd);
+ framesThisTime = std::max<int>(0, framesThisTime);
+
+ while (framesThisTime--) {
+ for (unsigned i = 0; i < numberOfChannels; ++i) {
+ float* destination = destinationChannels[i];
+ const float* source = sourceChannels[i];
+
+ destination[writeIndex] = source[readIndex];
+ }
+
+ ++writeIndex;
+ --readIndex;
+ --framesToProcess;
+ }
+
+ // Wrap-around.
+ if (readIndex <= minFrame) {
+ readIndex += deltaFrames;
+ if (renderSilenceAndFinishIfNotLooping(bus, writeIndex, framesToProcess))
+ break;
+ }
+ }
+ virtualReadIndex = readIndex;
+ } else if (!pitchRate) {
+ unsigned readIndex = static_cast<unsigned>(virtualReadIndex);
+
+ for (unsigned i = 0; i < numberOfChannels; ++i)
+ std::fill_n(destinationChannels[i], framesToProcess, sourceChannels[i][readIndex]);
+ } else if (reverse) {
+ unsigned maxFrame = static_cast<unsigned>(virtualMaxFrame);
+ unsigned minFrame = static_cast<unsigned>(floorf(virtualMinFrame));
+
+ while (framesToProcess--) {
+ unsigned readIndex = static_cast<unsigned>(floorf(virtualReadIndex));
+ double interpolationFactor = virtualReadIndex - readIndex;
+
+ unsigned readIndex2 = readIndex + 1;
+ if (readIndex2 >= maxFrame)
+ readIndex2 = loop() ? minFrame : maxFrame - 1;
+
+ // Linear interpolation.
+ for (unsigned i = 0; i < 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 < 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<unsigned>(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 >= virtualEndFrame) {
</del><ins>+ if (virtualReadIndex >= 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& ec)
</span><span class="cx"> {
</span><del>- startPlaying(Partial, when, grainOffset, 0, ec);
</del><ins>+ startPlaying(Partial, when, grainOffset, buffer() ? buffer()->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& 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()->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()->sampleRate());
</del><ins>+ if (totalPitchRate() < 0)
+ m_virtualReadIndex = AudioUtilities::timeToSampleFrame(m_grainOffset + m_grainDuration, buffer()->sampleRate()) - 1;
+ else
+ m_virtualReadIndex = AudioUtilities::timeToSampleFrame(m_grainOffset, buffer()->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) && !std::isinf(totalRate);
</span><span class="cx"> ASSERT(isTotalRateValid);
</span></span></pre>
</div>
</div>
</body>
</html>