- From: Joseph Berkovitz <joe@noteflight.com>
- Date: Tue, 19 Oct 2010 15:44:17 -0400
- To: Chris Rogers <crogers@google.com>
- Cc: public-xg-audio@w3.org
- Message-Id: <5A894A1C-491F-4E5C-B9E5-2B275E55D165@noteflight.com>
Yes, I can see that if such a JS wrapper were to encapsulate the
AudioContext along with some notion of transforms, that would probably
work. One could build a "smarter context" that incorporates a notion
of transformation. I will most likely be persuaded when I see a
sketch of the alternative that you're proposing.
The other issue of scheduling programmatic audio generation seems
separate from this, and I think it's a higher priority to resolve it.
...joe
On Oct 19, 2010, at 3:33 PM, Chris Rogers wrote:
> Hi Joe,
>
> As we discussed a little bit in the tele-conference on Monday, my
> strategy has been to create an API which is as lightweight and
> concise as possible while still allowing for efficient native
> processing, easy graph construction, and low-latency playback. I
> can definitely see where the concepts of time-offset can be useful
> when building complex sequencing applications, but I believe that it
> is very straightforward to create JS sequencing wrapper classes to
> encapsulate the time-offset. The user of this class would not need
> to worry about passing in the time-offset to every method call,
> because the implementation of the class would take care of the
> details. Exactly what the API of this JS sequencing class looks
> like would depend on the details of the sequencing application
> (traditional sequencer, loop-based, region-based digital-audio
> workstation timeline, etc.), so I'm hesitant to build in anything
> more complex into the base API other than what is necessary.
>
> As we have more time to play with the API and build more sequencing
> applications, then I may be proven wrong. But I'd like to have a
> chance to see how different kinds of sequencing wrapper classes can
> solve different use cases.
>
> Chris
>
>
>
> On Mon, Oct 18, 2010 at 6:56 PM, Joseph Berkovitz
> <joe@noteflight.com> wrote:
> 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
>
>
>
>
>
>
>
... . . . 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 19:44:58 UTC