Re: Reflections on writing a sequencer

The way I've done this, BTW, is to set up a setTimeout loop that schedules
the noteOns for some set amount of time ahead.

On Wed, Jul 25, 2012 at 5:40 AM, Joe Berkovitz <joe@noteflight.com> wrote:

> Rate or tempo changes can be computed an arbitrary amount of time in
> advance, just like notes.  The sequencer's job is to convert logical or
> musical time (which can flow at a variable rate) into absolute time, which
> is just a matter of more linear math.
>
> I think that the real issue is the amount of time it takes for the
> sequencer to respond to real time user interaction that affects playback,
> e.g. a tempo slider gesture to name just one of many examples.
>
> The good news is that the setTimeout throttling limitations only affect
> windows that the user is not interacting with, in which case this issue
> doesn't apply.
>
> Having said all that I am also curious whether notes are schedulable fron
> onaudioprocess handlers but I see no reason why not... nor any overriding
> reason to do so yet.
>
> ...Joe
> On Jul 25, 2012 8:28 AM, "lonce wyse" <lonce.wyse@zwhome.org> wrote:
>
>>
>> Hi  -
>>     Of course, you would want to generate events as short a time in to
>> the future as possible in order to stay responsive to rate (or tempo)
>> changes.
>>     Ideally a JavaScriptAudioNode could be used as the event generator.
>> It's onaudioprocess() method could check the length of the output buffer it
>> is passed, and do nothing else but call "note on" events for other nodes it
>> wants to play within that short period of time.
>>     I haven't tried that yet, but would noteon events be handled properly
>> when generated in this "just in time" manner? Would this be a violation of
>> protocol to use a onaudioprocess() as what would amount to a rock-solid
>> sample-accurate periodic callback function?
>>
>> Best,
>>              - lonce
>>
>>
>>
>> On 25/7/2012 12:40 AM, Joseph Berkovitz wrote:
>>
>> HI Adam,
>>
>>  I think one general way to structure sequencer playback is as follows
>> -- I've used this approach with WebAudio successfully in the past:
>>
>>  1. Just before starting playback, take note of the AudioContext's
>> currentTime property.  Add a small time offset to it, say 100 ms.  The
>> result will be your performance start time, corresponding to time offset
>> zero in your sequencer data.  (The time offset provides a short window in
>> which to schedule the first events in the sequence).
>>
>>  2. Create a scheduler function that will run periodically, which
>> examines the AudioContext's currentTime and subtracts the previously
>> captured startTime. That gives you a "current performance time" at the
>> moment the callback occurs, expressed in terms of your sequencer
>> data.  Then create AudioNodes representing all sequencer events that occur
>> within an arbitrary time window after this current performance time (say,
>> several seconds) and schedule them with noteOn/noteOff.
>>
>>  3. Call the function immediately, and also use setInterval() or
>> setTimeout() to schedule callbacks to the above function on some reasonable
>> basis, say every 100-200 ms. The exact interval is not important and can be
>> tuned for best performance.
>>
>>  This approach is relatively insensitive to callback timing and in
>> general allows audio to be scheduled an arbitrary interval in advance of
>> its being played.
>>
>>  ...Joe
>>
>>
>>  On Jul 24, 2012, at 11:40 AM, Adam Goode wrote:
>>
>> Hi,
>>
>>  Yesterday I tried to write an extremely simple sequencer using
>> webaudio. My goal was to have a tone play periodically, at a
>> user-selectable low frequency interval.
>>
>>  The main problem I ran into was the difficulties in scheduling events
>> synchronized with the a-rate clock.
>>
>>  If I want to play a tone twice per second, I want to call this code in
>> a loop, indefinitely:
>>
>>  var startTime = ....
>> var o = c.createOscillator();
>> o.connect(c.destination);
>> o.noteOn(startTime);
>> o.noteOff(startTime + 0.1);
>>
>>  I can't just put it in a loop, I need to schedule this in a callback,
>> when necessary to fill the event queue. But what callback to use?
>> setInterval is not appropriate, since the setInterval clock will skew
>> quickly from c.currentTime. And busy looping with setInterval(0) will
>> consume a lot of CPU and gets throttled when switching tabs (try putting
>> the drum machine demo in a background tab and see).
>>
>>  My solution was this:
>>
>>  var controlOscillator = c.createOscillator();
>> controlOscillator.frequency.value = 2;
>> var js = c.createJavaScriptNode(256, 1, 0);
>> controlOscillator.connect(js);
>>
>>  js.onaudioprocess = function(e) {
>>    ... detect positive zero crossing from control oscillator ...
>>   if (zeroCross) {
>>     var o = c.createOscillator();
>>     o.connect(c.destination);
>>     var startTime = ... zero crossing offset + playbackTime ...
>>     o.noteOn(startTime);
>>     o.noteOff(startTime + 0.1);
>>   }
>> };
>>
>>
>>  This does work (except for missing playbackTime
>> https://bugs.webkit.org/show_bug.cgi?id=61524, needing to connect the
>> javascript node to destination, and another bug on chrome
>> http://crbug.com/138646), but is awkward. There is also the question of
>> having a disconnected graph: I am sending control data, not audio data, so
>> I don't want to connect it to destination.
>>
>>  I essentially want to have a callback for getting new control data, to
>> keep the event pipeline filled without overflowing any noteOn buffer or
>> falling behind. Is the javascript node appropriate for this? I feel like
>> there could be something more explicit, like a setInterval off of the audio
>> context.
>>
>>
>>
>>  Adam
>>
>>
>>
>>         ... .  .    .       Joe
>>
>>   *Joe Berkovitz*
>> President
>>
>>  * Noteflight LLC *
>> 84 Hamilton St, Cambridge, MA 02139
>> phone: +1 978 314 6271
>> www.noteflight.com
>>
>>
>>

Received on Sunday, 29 July 2012 19:09:08 UTC