diff --git a/index.bs b/index.bs index 0cd9c1db6..ca42e35d2 100644 --- a/index.bs +++ b/index.bs @@ -4650,7 +4650,7 @@ Methods
 			when: The when parameter describes at what time (in seconds) the sound should start playing. It is in the same time coordinate system as the {{AudioContext}}'s {{BaseAudioContext/currentTime}} attribute. If 0 is passed in for this value or if the value is less than currentTime, then the sound will start playing immediately. A {{RangeError}} exception MUST be thrown if when is negative.
 			offset: The offset parameter supplies a playhead position where playback will begin. If 0 is passed in for this value, then playback will start from the beginning of the buffer. A {{RangeError}} exception MUST be thrown if offset is negative. If offset is greater than {{AudioBufferSourceNode/loopEnd}}, playback will begin at {{AudioBufferSourceNode/loopEnd}} (and immediately loop to {{AudioBufferSourceNode/loopStart}}). offset is silently clamped to [0, duration], when startTime is reached, where duration is the value of the duration attribute of the {{AudioBuffer}} set to the {{AudioBufferSourceNode/buffer}} attribute of this AudioBufferSourceNode.
-			duration: The {{AudioBufferSourceNode/start(when, offset, duration)/duration}} parameter describes the duration of the sound (in seconds) to be played. If this parameter is passed, this method has exactly the same effect as the invocation of start(when, offset) followed by stop(when + duration). A {{RangeError}} exception MUST be thrown if duration is negative.
+			duration: The {{AudioBufferSourceNode/start(when, offset, duration)/duration}} parameter describes the duration of sound to be played, expressed as seconds of total buffer content to be output, including any whole or partial loop iterations. The units of {{AudioBufferSourceNode/start(when, offset, duration)/duration}} are independent of the effects of {{AudioBufferSourceNode/playbackRate}}. For example, a {{AudioBufferSourceNode/start(when, offset, duration)/duration}} of 5 seconds with a playback rate of 0.5 will output 5 seconds of buffer content at half speed, producing 10 seconds of audible output. A {{RangeError}} exception MUST be thrown if duration is negative.
 		
@@ -4816,21 +4816,23 @@ let loopEnd; let playbackRate; // 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 +let start = 0, offset = 0, duration = Infinity; // Set by start() +let stop = Infinity; // Set by stop() + // Variables for tracking node's playback state let bufferTime = 0, started = false, enteredLoop = false; +let bufferTimeElapsed = 0; let dt = 1 / context.sampleRate; // Handle invocation of start method call -function handleStart(when, pos, duration) { +function handleStart(when, pos, dur) { if (arguments.length >= 1) { start = when; } offset = pos; if (arguments.length >= 3) { - stop = when + duration; + duration = dur; } } @@ -4894,8 +4896,8 @@ function process(numberOfFrames) { // Render each sample frame in the quantum for (let index = 0; index < numberOfFrames; index++) { - // Check that currentTime is within allowable range for playback - if (currentTime < start || currentTime >= stop) { + // Check that currentTime and bufferTimeElapsed are within allowable range for playback + if (currentTime < start || currentTime >= stop || bufferTimeElapsed >= duration) { output.push(0); // this sample frame is silent currentTime += dt; continue; @@ -4903,7 +4905,7 @@ function process(numberOfFrames) { if (!started) { // Take note that buffer has started playing and get initial playhead position. - bufferTime = offset + ((currentTime - start) * computedPlaybackRate); + bufferTime = offset; started = true; } @@ -4940,6 +4942,7 @@ function process(numberOfFrames) { } bufferTime += dt * computedPlaybackRate; + bufferTimeElapsed += dt * computedPlaybackRate; currentTime += dt; } // End of render quantum loop @@ -4970,6 +4973,8 @@ apply: * linear interpolation is depicted throughout, although a UA could employ other interpolation techniques. +* the duration values noted in the figures refer to the buffer, not arguments to {{AudioBufferSourceNode/start()}} + This figure illustrates basic playback of a buffer, with a simple loop that ends after the last sample frame in the buffer: diff --git a/index.html b/index.html index 2e90a1d21..78fd2e3b8 100644 --- a/index.html +++ b/index.html @@ -1218,9 +1218,9 @@ background-attachment: fixed; } - + - + @@ -1622,7 +1622,7 @@

Web Audio API

-

Editor’s Draft,

+

Editor’s Draft,

This version: @@ -6106,7 +6106,7 @@

- The duration parameter describes the duration of the sound (in seconds) to be played. If this parameter is passed, this method has exactly the same effect as the invocation of start(when, offset) followed by stop(when + duration). A RangeError exception MUST be thrown if duration is negative. + The duration parameter describes the duration of sound to be played, expressed as seconds of total buffer content to be output, including any whole or partial loop iterations. The units of duration are independent of the effects of playbackRate. For example, a duration of 5 seconds with a playback rate of 0.5 will output 5 seconds of buffer content at half speed, producing 10 seconds of audible output. A RangeError exception MUST be thrown if duration is negative.
Return type: void

@@ -6143,7 +6143,7 @@
loopStart attribute.

playbackRate, of type float, defaulting to 1
-

The initial value for the playbackRate AudioParam.

+

The initial value for the playbackRate AudioParam.

1.9.5. Looping

This section is non-normative. Please see the playback algorithm for @@ -6188,7 +6188,7 @@

playbackRate parameter which can vary +these values are independent of the node’s playbackRate parameter which can vary dynamically during the course of playback.

1.9.6. Playback of AudioBuffer Contents

This normative section specifies the playback of the contents of @@ -6220,7 +6220,7 @@

let buffer; // AudioBuffer employed by this nodelet context; // AudioContext employed by this node// The following variables capture attribute and AudioParam values for the node.// They are updated on a k-rate basis, prior to each invocation of process().let loop;let detune;let loopStart;let loopEnd;let playbackRate;// Variables for the node’s playback parameterslet start = 0, offset = 0; // Set by start()let stop = Infinity; // Set by stop(), or by start() with a supplied duration// Variables for tracking node’s playback statelet bufferTime = 0, started = false, enteredLoop = false;let dt = 1 / context.sampleRate;// Handle invocation of start method callfunction handleStart(when, pos, duration) { if (arguments.length >= 1) { start = when; } offset = pos; if (arguments.length >= 3) { stop = when + duration; }}// Handle invocation of stop method callfunction handleStop(when) { if (arguments.length >= 1) { stop = when; } else { stop = context.currentTime; }}// 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 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 position. If position is greater than or equal to loopEnd and there is no subsequent sample frame in buffer, then interpolation should be based on the sequence of subsequent frames beginning at loopStart. */ ...}// Generate a single render quantum of audio to be placed// 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 // Combine the two k-rate parameters affecting playback rate let computedPlaybackRate = playbackRate * Math.pow(2, detune / 1200); // Determine loop endpoints as applicable let actualLoopStart, actualLoopEnd; if (loop && buffer != null) { if (loopStart >= 0 && loopEnd > 0 && loopStart < loopEnd) { actualLoopStart = loopStart; actualLoopEnd = Math.min(loopEnd, buffer.duration); } else { actualLoopStart = 0; actualLoopEnd = buffer.duration; } } else { // If the loop flag is false, remove any record of the loop having been entered enteredLoop = false; } // Handle null buffer case if (buffer == null) { stop = currentTime; // force zero output for all time } // Render each sample frame in the quantum for (let index = 0; index < numberOfFrames; index++) { // Check that currentTime is within allowable range for playback if (currentTime < start || currentTime >= stop) { output.push(0); // this sample frame is silent currentTime += dt; continue; } if (!started) { // Take note that buffer has started playing and get initial playhead position. bufferTime = offset + ((currentTime - start) * computedPlaybackRate); started = true; } // Handle loop-related calculations if (loop) { // Determine if looped portion has been entered for the first time if (!enteredLoop) { if (offset < actualLoopEnd && bufferTime >= actualLoopStart) { // playback began before or within loop, and playhead is now past loop start enteredLoop = true; } if (offset >= actualLoopEnd && bufferTime < actualLoopEnd) { // playback began after loop, and playhead is now prior to the loop end enteredLoop = true; } } // Wrap loop iterations as needed. Note that enteredLoop // may become true inside the preceding conditional. if (enteredLoop) { while (bufferTime >= actualLoopEnd) { bufferTime -= actualLoopEnd - actualLoopStart; } while (bufferTime < actualLoopStart) { bufferTime += actualLoopEnd - actualLoopStart; } } } if (bufferTime >= 0 && bufferTime < buffer.duration) { output.push(playbackSignal(bufferTime)); } else { output.push(0); // past end of buffer, so output silent frame } bufferTime += dt * computedPlaybackRate; currentTime += dt; } // End of render quantum loop if (currentTime >= stop) { // end playback state of this node. // no further invocations of process() will occur. } return output;} +
let buffer; // AudioBuffer employed by this nodelet context; // AudioContext employed by this node// The following variables capture attribute and AudioParam values for the node.// They are updated on a k-rate basis, prior to each invocation of process().let loop;let detune;let loopStart;let loopEnd;let playbackRate;// Variables for the node’s playback parameterslet start = 0, offset = 0, duration = Infinity; // Set by start()let stop = Infinity; // Set by stop()// Variables for tracking node’s playback statelet bufferTime = 0, started = false, enteredLoop = false;let bufferDuration = 0;let dt = 1 / context.sampleRate;// Handle invocation of start method callfunction handleStart(when, pos, dur) {  if (arguments.length >= 1) {    start = when;  }  offset = pos;  if (arguments.length >= 3) {    duration = dur;  }}// Handle invocation of stop method callfunction handleStop(when) {  if (arguments.length >= 1) {    stop = when;  } else {    stop = context.currentTime;  }}// 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    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    position.    If position is greater than or equal to loopEnd and there is no subsequent    sample frame in buffer, then interpolation should be based on the sequence    of subsequent frames beginning at loopStart.   */   ...}// Generate a single render quantum of audio to be placed// 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  // Combine the two k-rate parameters affecting playback rate  let computedPlaybackRate = playbackRate * Math.pow(2, detune / 1200);  // Determine loop endpoints as applicable  let actualLoopStart, actualLoopEnd;  if (loop && buffer != null) {    if (loopStart >= 0 && loopEnd > 0 && loopStart < loopEnd) {      actualLoopStart = loopStart;      actualLoopEnd = Math.min(loopEnd, buffer.duration);    } else {      actualLoopStart = 0;      actualLoopEnd = buffer.duration;    }  } else {    // If the loop flag is false, remove any record of the loop having been entered    enteredLoop = false;  }  // Handle null buffer case  if (buffer == null) {    stop = currentTime; // force zero output for all time  }  // Render each sample frame in the quantum  for (let index = 0; index < numberOfFrames; index++) {    // Check that currentTime and bufferDuration are within allowable range for playback    if (currentTime < start || currentTime >= stop || bufferDuration >= duration) {      output.push(0); // this sample frame is silent      currentTime += dt;      continue;    }    if (!started) {      // Take note that buffer has started playing and get initial playhead position.      bufferTime = offset + bufferDuration;      started = true;    }    // Handle loop-related calculations    if (loop) {      // Determine if looped portion has been entered for the first time      if (!enteredLoop) {        if (offset < actualLoopEnd && bufferTime >= actualLoopStart) {          // playback began before or within loop, and playhead is now past loop start          enteredLoop = true;        }        if (offset >= actualLoopEnd && bufferTime < actualLoopEnd) {          // playback began after loop, and playhead is now prior to the loop end          enteredLoop = true;        }      }      // Wrap loop iterations as needed. Note that enteredLoop      // may become true inside the preceding conditional.      if (enteredLoop) {        while (bufferTime >= actualLoopEnd) {          bufferTime -= actualLoopEnd - actualLoopStart;        }        while (bufferTime < actualLoopStart) {          bufferTime += actualLoopEnd - actualLoopStart;        }      }    }    if (bufferTime >= 0 && bufferTime < buffer.duration) {      output.push(playbackSignal(bufferTime));    } else {      output.push(0); // past end of buffer, so output silent frame    }    bufferTime += dt * computedPlaybackRate;    bufferDuration += dt * computedPlaybackRate;    currentTime += dt;  } // End of render quantum loop  if (currentTime >= stop) {    // end playback state of this node.    // no further invocations of process() will occur.  }  return output;}

The following non-normative figures illustrate the behavior of the algorithm in assorted key scenarios. Dynamic resampling of the buffer is not considered, but as long as the times of loop @@ -6238,6 +6238,8 @@

linear interpolation is depicted throughout, although a UA could employ other interpolation techniques.

+
  • +

    the duration values noted in the figures refer to the buffer, not arguments to start()

    This figure illustrates basic playback of a buffer, with a simple loop that ends after the last sample frame in the buffer:

    @@ -14381,7 +14383,7 @@

    N
    [WebIDL]
    Cameron McCormack; Boris Zbarsky; Tobie Langel. Web IDL. 15 December 2016. ED. URL: https://heycam.github.io/webidl/
    [WEBRTC] -
    Adam Bergkvist; et al. WebRTC 1.0: Real-time Communication Between Browsers. 2 November 2017. CR. URL: https://www.w3.org/TR/webrtc/ +
    Adam Bergkvist; et al. WebRTC 1.0: Real-time Communication Between Browsers. 21 June 2018. CR. URL: https://www.w3.org/TR/webrtc/
    [WORKLETS-1]
    Ian Kilpatrick. Worklets Level 1. 7 June 2016. WD. URL: https://www.w3.org/TR/worklets-1/ @@ -14619,7 +14621,7 @@

    I attribute boolean loop; attribute double loopStart; attribute double loopEnd; - void start (optional double when = 0, + void start (optional double when = 0, optional double offset, optional double duration); }; @@ -18418,9 +18420,11 @@

    I The AudioBufferSourceNode Interface (2)
  • 1.9.2. Attributes -
  • 1.9.4.1. +
  • 1.9.3. +Methods +
  • 1.9.4.1. Dictionary AudioBufferSourceOptions Members -
  • 1.9.5. +
  • 1.9.5. Looping @@ -18433,6 +18437,8 @@

    I Methods
  • 1.9.5. Looping (2) +
  • 1.9.6. +Playback of AudioBuffer Contents