- From: Joseph Berkovitz <joe@noteflight.com>
- Date: Mon, 18 Oct 2010 21:56:11 -0400
- To: public-xg-audio@w3.org
On the subgraph-scheduling topic that we discussed on the call today, we resolved that we'd work through some code examples to understand the issue of subgraph scheduling better. I would like to try to take a first step on this. If it doesn't feel valuable to go further with it, then I'll be fine with moving on! The issue for me is that I would like to be able to define a "local time origin" that is used to transform all time values used by noteOn(), startAt(), automateAt()... basically, most functions that care about time. Right now these are all scheduled relative to an "absolute time origin" that is associated with the owning AudioContext, which I feel is a bit inconvenient and requires extra parameters in every function in the call tree that makes a node graph. This feels to me like it's a low-impact thing to implement -- but only if people feel it's worth it. Let me make a concrete proposal that seems cheap and easy, and try to show how it affects couple of simple use cases. My proposal is adapted directly from the notion of transforms in the HTML5 Canvas specification, and consists of three functions on AudioContext: offset(), save(), restore(). AudioContext also acquires a new attribute: "currentOffset". Here are their definitions: Object transform: an Object with a numeric "offset" property which affects any time-based property of an object created from this AudioContext. Other properties could be added, e.g "gain". The idea is that these are properties that make sense to affect a wide array of objects. void offset(float delta): adds delta to the value of transform.offset save(): pushes a copy of "transform" onto an internal stack in the AudioContext restore(): pops an object from the same internal stack into "transform" Implementation concept: The parent AudioContext's currentOffset value is automatically added to any time-valued parameters that are used in scheduling functions on a node, such as noteOn(), etc. USE CASES The main difference is simple: with local time offsets saved in the context, one can eliminate a whole bunch of "startTime" parameters that need to be passed through everywhere. This may not seem like much of a saving, but it feels cleaner to me, and if the spec ever starts to generalize the notion of a saved/restored transform to include other variables besides time (e.g. a "local gain" or "local pan"), it starts really paying off. You don't want to go back and ask developers to add a whole bunch of new parameters to existing functions and pass all these values through everywhere. I'm going to give two use cases. The second one builds on the first. CASE 1. One wishes to play a sequence of audio buffers at some sequence of evenly spaced times starting right now. Code needed today: function main() { var context = new AudioContext(); playSequence(context, [/* buffers */], 0.25, context.currentTime); } function playSequence(context, bufferList, interval, startTime) { for (var buffer in bufferList) { playBuffer(context, buffer, startTime); startTime += interval; } } function playBuffer(context, buffer, startTime) { node = context.createBufferSource(); node.buffer = buffer; node.noteOn(startTime); node.connect(context.destination); } Code needed with time-offset transforms: function main() { var context = new AudioContext(); context.offset(context.currentTime); // from here on out, all time offsets are relative to "now" playSequence(context, [/* buffers */], 0.25); } function playSequence(context, bufferList, interval) { for (var buffer in bufferList) { playBuffer(context, buffer); context.offset(interval); } } function playBuffer(context, buffer) { node = context.createBufferSource(); node.buffer = buffer; node.noteOn(0); // starts relative to local time offset determined by caller node.connect(context.destination); } CASE 2: builds on CASE 1 by playing a supersequence of sequences, with its own time delay between the onset of lower-level sequences. Code needed today: function main() { var context = new AudioContext(); playSupersequence(context, [/* buffers */], 10, 5.0, context.currentTime); } function playSupersequence(context, bufferList, repeatCount, interval, startTime) { for (var i = 0; i < repeatCount; i++) { playSequence(context, [/* buffers */], 0.25, startTime + (i * interval)); } } function playSequence(context, bufferList, interval, startTime) { for (var buffer in bufferList) { playBuffer(context, buffer, startTime); startTime += interval; } } function playBuffer(context, buffer, startTime) { node = context.createBufferSource(); node.buffer = buffer; node.noteOn(startTime); node.connect(context.destination); } Code needed with time-offset transforms: function main() { var context = new AudioContext(); context.offset(context.currentTime); playSupersequence(context, [/* buffers */], 10, 5.0); } function playSupersequence(context, bufferList, repeatCount, interval) { for (var i = 0; i < repeatCount; i++) { playSequence(context, [/* buffers */], 0.25); context.offset(interval); } } // Note use of save() and restore() to allow this function to preserve caller's time shift function playSequence(context, bufferList, interval) { save(); for (var buffer in bufferList) { playBuffer(context, buffer); context.offset(interval); } restore(); } function playBuffer(context, buffer) { node = context.createBufferSource(); node.buffer = buffer; node.noteOn(0); // starts relative to local time offset determined by caller node.connect(context.destination); } ... . . . Joe Joe Berkovitz President Noteflight LLC 160 Sidney St, Cambridge, MA 02139 phone: +1 978 314 6271 www.noteflight.com
Received on Tuesday, 19 October 2010 01:56:52 UTC