Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Specify ABSN playback algorithm exactly. #1143

Merged
merged 20 commits into from Apr 11, 2017
Merged
Changes from 1 commit
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
1755177
Specify ABSN playback algorithm exactly.
joeberkovitz Feb 2, 2017
5239a89
Add figures. Clarify wording and remove stray references to sample-fr…
joeberkovitz Feb 7, 2017
ac037da
Add language and figure to account for possible difference in sample …
joeberkovitz Feb 8, 2017
473db22
Clarify content of figures and tidy up
joeberkovitz Feb 8, 2017
73a9fd6
Refactor playhead position description so it can be used across loop …
joeberkovitz Feb 9, 2017
f148cfe
Remove incorrect sample rate correction from algorithm.
joeberkovitz Feb 9, 2017
aefdab3
Miscellaneous cleanup in response to @rtoy feedback
joeberkovitz Feb 13, 2017
42ede16
Further simplify language to eliminate buffer-resampling
joeberkovitz Feb 13, 2017
478015e
Respond to @rtoy feedback
joeberkovitz Feb 21, 2017
4307ea7
Add “started” flag in place of using negative value of bufferTime for…
joeberkovitz Feb 22, 2017
057d63c
Handle case in ABSN playback where playhead is outside bounds of buff…
joeberkovitz Feb 23, 2017
5d4a6e8
Add language to “fold” backwards iterations through the loop, but onl…
joeberkovitz Mar 2, 2017
56158e9
Correct ABSN algorithm to treat loop entry symmetrically from buffer …
joeberkovitz Mar 14, 2017
5c2d154
Correct details of loop entry detection to handle offset within loop.
joeberkovitz Mar 14, 2017
329a623
Redo ABSN algorithm as JS code
joeberkovitz Mar 15, 2017
299a4e7
Apply remaining feedback from @rtoy review
joeberkovitz Mar 23, 2017
dfbd3c3
Apply @rtoy feedback.
joeberkovitz Mar 24, 2017
03b2510
Apply feedback from @padenot.
joeberkovitz Apr 4, 2017
2dc8726
Merge branch 'gh-pages' into 95-absn-playback
joeberkovitz Apr 10, 2017
c19243a
Add qualifications on normative vs non-normative text as requested by…
joeberkovitz Apr 10, 2017
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
153 changes: 61 additions & 92 deletions index.html
Expand Up @@ -5021,55 +5021,28 @@ <h3 id="playback-AudioBufferSourceNode">
The description of the algorithm is as follows:
</p>
<pre>
// The AudioBuffer and context employed by this node
var buffer;
var context;
let buffer; /* AudioBuffer employed by this node */;
let context; /* AudioContext employed by this node */;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit. Remove ";" at end of line. Maybe just use // style comments here too?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure, this went through a couple of iterations and got messed up in the process.


// The following variables capture attribute and AudioParam values for the node.
// They are updated k-rate, except for buffer.
var loop;
var detune;
var loopEnd;
var loopStart;
var playbackRate;

// State variables for the node's playback
var start = 0;
var offset;
var stop = Infinity;

var bufferTime = 0;
var started = false;
var enteredLoop = false;
var dt;

// Handle invocation of start method call
function handleStart(when, pos, duration) {
if (arguments.length &gt;= 1) {
start = when;
}
offset = pos;
if (arguments.length &gt;= 3) {
stop = when + duration;
}
}
// They are updated on a k-rate basis, prior to each invocation of process().
let loop, detune, loopStart, loopEnd, playbackRate;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I know people disagree on this, but I hate the style that declares a bunch of vars all on one line.

I defer to others on this.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't really care -- I usually don't use this style, but thought we wanted more compactness where possible. So happy to make this change.


// Handle invocation of stop method call
function handleStop(when) {
if (arguments.length &gt;= 1) {
stop = when;
}
else {
stop = context.currentTime;
}
}
// Variables for the node's playback parameters
let start = 0, offset = 0; // Set by start()
let stop = Infinity; // Set by stop(), or by start() with a supplied duration
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe expand these comments to include your original description (or code) on what the values are. Now it's really unclear what offset and stop are actually set to.

Perhaps I was wrong about removing the functions. Maybe reinstate and do

let start = function_to_initialize_start();
let stop = function_to_initialize_stop();

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well, that was why I had the functions. I will put them back in at this point. However, the use of let here doesn't make sense as start() and stop() can both be called multiple times.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The problem with having these functions is that they're never called (or mentioned that they're magically called) anywhere. So they serve no purpose.

So my proposal is to call them somehow either explicitly so implicitly using comments.

Or add comments to where start and stop are declared to explain how these values are initialized.


// Variables for tracking node's playback state
let bufferTime = 0, started = false, enteredLoop = false;
let dt = 1 / context.sampleRate;

// Interpolate a multi-channel signal value for some sample frame
// Interpolate a multi-channel signal value for some sample frame.
// Returns an array of signal values.
function playbackSignal(position) {
/*
This function provides the playback signal function for buffer, which is a
function that maps from a playhead position to a set of output signal
values, one for each output channel. If position corresponds to the
values, one for each output channel. If |position| corresponds to the
location of an exact sample frame in the buffer, this function returns
that frame. Otherwise, its return value is determined by a UA-supplied
algorithm that interpolates between sample frames in the neighborhood of
Expand All @@ -5082,24 +5055,18 @@ <h3 id="playback-AudioBufferSourceNode">
...
}

// Render a single frame of audio into the channel data arrays given by output
// at the given element index. The frame value may be an array of channel signal
// values, or a single value to be rendered to all channels.
function renderFrame(output, index, frame) {
...
}

// Generate a single render quantum of audio to be placed
// in the channel arrays defined by output.
function process(output) {
var dt = 1 / context.sampleRate;
var index = 0;
// in the channel arrays defined by output. Returns an array
// of |numberOfFrames| sample frames to be output.
function process(numberOfFrames) {
let currentTime = context.currentTime; // context time of next rendered frame
let output = []; // accumulates rendered sample frames

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Need to initialize currentTime variable here from context.currentTime.

// Combine the two k-rate parameters affecting playback rate
var computedPlaybackRate = playbackRate * Math.pow(2, detune / 1200);
let computedPlaybackRate = playbackRate * Math.pow(2, detune / 1200);

// Determine loop endpoints as applicable
var actualLoopStart, actualLoopEnd;
let actualLoopStart, actualLoopEnd;
if (loop) {
if (loopStart &gt;= 0 &amp;& loopEnd &gt; 0 &amp;& loopStart &lt; loopEnd) {
actualLoopStart = loopStart;
Expand All @@ -5112,60 +5079,62 @@ <h3 id="playback-AudioBufferSourceNode">
}

// Render each sample frame in the quantum
while (index &lt; output[0].length) {
for (let index = 0; index &lt; numberOfFrames; index++) {
// Check that currentTime is within allowable range for playback
if (currentTime &lt; start || currentTime &gt;= stop) {
// emit silence for the output frame for the element specified by index
renderFrame(output, index, 0);
output.push(0); // this sample frame is silent
currentTime += dt;
continue;
}
else {
if (!started) {
// Take note that buffer has started playing and get initial playhead position.
bufferTime = offset + ((context.currentTime - start) * computedPlaybackRate);
started = true;
}

// Handle loop-related calculations
if (loop) {
// Determine if looped portion has been entered
if (!enteredLoop) {
if (offset &lt; actualLoopEnd &amp;& bufferTime &gt;= actualLoopStart) {
// playback began before or within loop, and playhead is now past loop start
enteredLoop = true;
}
if (offset &gt;= actualLoopEnd &amp;& bufferTime &lt; actualLoopEnd) {
// playback began after loop, and playhead is now prior to the loop end
enteredLoop = true;
}
}
if (!started) {
// Take note that buffer has started playing and get initial playhead position.
bufferTime = offset + ((currentTime - start) * computedPlaybackRate);
started = true;
}

// Wrap loop iterations as needed
if (enteredLoop) {
while (bufferTime &gt;= actualLoopEnd) {
bufferTime -= actualLoopEnd - actualLoopStart;
}
while (bufferTime &lt; actualLoopStart) {
bufferTime += actualLoopEnd - actualLoopStart;
}
// Handle loop-related calculations
if (loop) {
// Determine if looped portion has been entered for the first time
if (!enteredLoop) {
if (offset &lt; actualLoopEnd &amp;& bufferTime &gt;= actualLoopStart) {
// playback began before or within loop, and playhead is now past loop start
enteredLoop = true;
}
if (offset &gt;= actualLoopEnd &amp;& bufferTime &lt; actualLoopEnd) {
// playback began after loop, and playhead is now prior to the loop end
enteredLoop = true;
}
}

if (bufferTime &gt;= 0 &amp;& bufferTime &lt; buffer.duration) {
renderFrame(output, index, playbackSignal(bufferTime));
}
else {
renderFrame(output, index, 0);
// Wrap loop iterations as needed
if (enteredLoop) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe convert this to

} else {

to emphasize that this is the alternative case for the if on line 5099.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, but it is not the alternative case. The preceding if (!enteredLoop) may set enteredLoop to true.

while (bufferTime &gt;= actualLoopEnd) {
bufferTime -= actualLoopEnd - actualLoopStart;
}
while (bufferTime &lt; actualLoopStart) {
bufferTime += actualLoopEnd - actualLoopStart;
}
}
bufferTime += dt * computedPlaybackRate;
} // End active rendering "else" case
}

if (bufferTime &gt;= 0 &amp;& bufferTime &lt; buffer.duration) {
output.push(playbackSignal(bufferTime));
}
else {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Style nit: Use

} else {

Here and elsewhere.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

sure thing

output.push(0); // past end of buffer, so output silent frame
}

index += 1;
bufferTime += dt * computedPlaybackRate;
currentTime += dt;
} // End of render quantum loop

if (currentTime &gt;= stop) {
// end playback state of this node.
// no further invocations of process() will occur.
}

return output;
}
</pre>
<p>
Expand Down