Git Product home page Git Product logo

waaclock's Introduction

WAAClock.js

WAAClock is a small library to help you schedule things in time with Web Audio API.

var clock = new WAAClock(audioContext)
clock.start()

Schedule custom events

// prints 'wow!' at context.currentTime = 13
var event = clock.callbackAtTime(function() { console.log('wow!') }, 13)
// prints 'wow!' in 13 seconds
var event = clock.setTimeout(function() { console.log('wow!') }, 13)

Set events to repeat periodically

var event = clock.callbackAtTime(function() { console.log('wow!') }, 3).repeat(2)

Cancel an event

// Start an oscillator node at context.currentTime = 13
var event = clock.callbackAtTime(function() { oscNode.start(13) }, 13)
// ... but change your mind and cancel that
event.clear()

Change the tempo of a group of events

var event1 = clock.callbackAtTime(function() { console.log('wow!') }, 1).repeat(2)
  , event2 = clock.callbackAtTime(function() { console.log('what?') }, 2).repeat(2)

// in 10 seconds, the tempo will be multiplied by 2
clock.setTimeout(function() {
  clock.timeStretch(context.currentTime, [event1, event2], 0.5)
}, 10)

note : this library uses current Web Audio API specification. Some older browsers still use prefixed / deprecated function names. You can use Chris Wilson's AudioContext-MonkeyPatch if you want to support those older browsers as well.

Downloads

You can download the latest stable release of WAAClock from dist/.

Examples

More infos about scheduling

WAAClock implements the technique explained in Chris Wilson's article A Tale of Two Clocks, providing it as a reusable library and adding extra control and features. By default, events are triggered by a combination of ScriptProcessorNode and setTimeout, but you can also implement a custom tickMethod to better control event triggering precision and performance.

In short, WAAClock merely executes your callback slightly before the given deadline, so you would have time to schedule things exactly using Web Audio API primitives. For example :

var osc = audioContext.createOscillator()
osc.connect(audioContext.destination)

var startEvent = clock.callbackAtTime(function(event) {
  osc.start(event.deadline)
}, 100)

Each event created with WAAClock has a tolerance zone [deadline - early, deadline + late] in which it must be executed. The event is executed as soon as the clock enters this tolerance zone. On the other hand, if the event hasn't been executed when the clock gets out of the tolerance zone, the event will be dropped (but in practice this shouldn't happen).

You can change the tolerance of an event by calling Event.tolerance, but be wise about it : a too tight upper bound late, and the event could be dropped abusively, a too loose lower bound early, and the event will be executed too early.

API

WAAClock(context, opts)

WAAClock handles all the scheduling work. It is the only object you need to create directly.

You can set the default tolerance of events with the options toleranceLate and toleranceEarly.

You can also pass a tickMethod option of "manual" to disable the built-in ScriptProcessorNode method of triggering scheduled events, and instead call clock.tick() yourself on a regular interval.

start()

Starts the clock. This will also erase all the events that were previously scheduled.

stop()

Stops the clock.

callbackAtTime(func, deadline)

Schedules func to run before deadline in seconds, and returns an Event object.

setTimeout(func, delay)

Schedules func to run after delay seconds, and returns an Event object.

timeStretch(tRef, events, ratio)

Stretch time and repeat time of events by ratio, keeping their relative distance, and taking tRef as a reference . In fact this is equivalent to changing the tempo.

tick()

Executes any events that are due since the last tick() call. This method does not need to be called unless you are using tickMethod: 'manual'. If you are, this method must be called regularly during playback to trigger events. For example, to use worker-timers for consistent scheduling when the page is in the background:

import WAAClock from "waaclock";
import * as wt from "worker-timers";

const clock = new WAAClock(audioContext, { tickMethod: 'manual' });
clock.start();

// Event precision is limited by how often clock.tick() is called.
const interval = wt.setInterval(() => clock.tick(), 10);

// ...later...
clock.stop()
wt.clearInterval(interval);

Event

Every scheduling method returns an event object. All methods from Event return the calling event, so that you can chain them.

deadline

The deadline of the event.

schedule(deadline)

Reschedule the deadline of an event, deadline is the absolute time as given by context.currentTime.

tolerance(values)

Sets the event's tolerance, values is on object that can have keys late and early. See WAAClock for a detailed explanation. Example :

// The following executes `cb` before time 11. However, `cb` can be executed as early as
// time 10.9, and if something happends that prevent the event to be executed early enough,
// after time 12 the event will be dropped.
var clock.callbackAtTime(cb, 11)
  .tolerance({ early: 0.1, late: 1 })

repeat(time)

Sets the event to repeat every time seconds. If you want to remove the repeat you can pass null. Note that even if an event is dropped because it expired, subsequent "repeats" of the event will still be executed.

clear()

Cancels the event execution. This will work only if the event hasn't been scheduled yet (see WAAClock for more infos).

Event: 'expired'

This message is emitted when the clock fell out of the event tolerance zone. You can listen to it by calling on :

event.onexpired = function(event) { console.log('oooh :(!') }

Running the tests

Tests are written with mocha. Just install mocha globally and run mocha from the root directory. Integration with node-web-audio-api is tested manually running node test/node-web-audio-api-test.js.

Building

Build with browserify to dist/WAAClock-latest.js by running npm run build.

License

Released under MIT license

Change log

0.5.5

  • added { tickMethod: 'manual' } to allow opting out of ScriptProcessorNode

0.5.3

  • bug fixes.

0.5.2

  • bug fixes.

0.5.1

  • bug fixes.

0.5.0

  • removed support for prefixed AudioContext
  • removed underscore dependency
  • changed Event.tolerance API
  • renamed Event.time to Event.deadline
  • added tRef argument to timeStretch
  • removed executed event and EventEmitter
  • expired event to callback

0.4.0

  • made WAAClock.start method public, and start needs to be called explicitely
  • WAAClock.stop method
  • removed web audio API monkey-patching
  • removed support for old web audio API functions

0.3.2

  • bug fix

0.3.1

  • made schedule method of Event public.

0.3.0

0.2.0

  • changed the tick method from setInterval to ScriptProcessorNode
  • added event's toleranceEarly and toleranceLate
  • removed clock tickTime and lookAheadTime options
  • added support for old Web Audio API names

0.1.2

  • added callbackAtTime
  • bug fixes

waaclock's People

Contributors

domchristie avatar fgr-araujo avatar nedudi avatar sebpiq avatar smona avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

waaclock's Issues

What about a metronome?

for example :

var clock = new WAAClock(context)
  , metro = clock.metro(0.1, 0) // <interval>, <startTime>

metro.on('tick', function() {
   // do something
})

How to keep the clock ticking in a background tab?

So I'm making a generative instrument web app, and I'd like for it to work in a background tab, but modern browsers clamp setInterval to 1000ms minimum. I thought of using web workers, but they don't have access to AudioContext, and therefore no WAAClock ability. Any way you can see around this? Any way to keep the clock ticking on-time when it's in a background tab?

Fix examples on iOS

Hi, I'm also looking to use your great library in an iOS Application but having some difficulty.
This works well in OS X Chrome v40.0.22, Firefox v35.0, Safari v8.0.2

var context = window.AudioContext ? new AudioContext() : new webkitAudioContext(), clock = new WAAClock(context);
var event = clock.callbackAtTime(function() { console.log('wow!') }, 13) 

but does not on iPad iOS 8.0, iPhone iOS 8.0.2... nor do your examples (basic sequencer)
Having looked in console log I can see no errors, no logs and am having some difficulty finding where the fault is, wonder if you could give any insight?

Thanks. Chris

Script Processors evaluation order

Greetings!

I noticed that WAAClock, instead of using window.setTimeout, window.setInterval or a web worker, uses a ScriptProcessorNode. But there are "problems" with the evaluation order of the clock processor node if there are other ScriptProcessorNodes at the same "depth" in the audio graph. Consider the following example:

var c = new AudioContext();

var sp1 = c.createScriptProcessor(256, 1, 1);
var sp2 = c.createScriptProcessor(256, 1, 1);
var sp3 = c.createScriptProcessor(256, 1, 1);

sp1.onaudioprocess = function () {
    console.log("sp1");
};
sp2.onaudioprocess = function () {
    console.log("sp2");
};
sp3.onaudioprocess = function () {
    console.log("sp3");
};

sp1.connect(c.destination);
sp2.connect(c.destination);
sp3.connect(c.destination);

Sometimes, the execution order makes the browser print:
sp3
sp1
sp2

Other times:
sp2
sp1
sp3

I guess you can see the problem in this.

asm.js

For super performance.

the clock doesn't work as expect

I'm new to web audio and I'm trying to use react and WAAClock to build a step sequencer. My problem happened when I started the clock and tried to add new events to it with calculated nextBeatTime(always ahead of current time). I upload the console so you can see only the first event scheduled at the same time as the clock start is working. All later attempts won't work. I'm not sure where's my mistake but I've already spent the whole day working on it and getting no result. If you need to have a look at my code tell me and I will show you. Thanks a lot!
image

ScriptProcessorNode is deprecated

As of now, ScriptProcessorNode is deprecated and it is recommended to look into other ways of handling scheduling such as using AudioWorklets. As I see in the source code, ScriptProcessorNode is the default way to schedule events in this library. Will there be any effort to replace it with some other way? Should the community look into implementing this?

Update npm package?

Hi, thanks for this great library, it seems it will be useful in general for implementing constant FPS loops, e.g. for a midi player (because both setInterval and requestAnimationFrame have the issue that they don't call their callback when the browser window is not focused).
I noticed that the npm package was last updated 7 years ago, but improvements have been committed to this repo since then.
Could you please update the npm package? :)

how about ability to "delay" an event by t seconds?

Hi,

First off, thanks so much for providing this library! I'm using it as a scheduler in a music playlisting app...

Something I find myself wanting is the ability to change an event's execution time after the event has already been created (rather than canceling the event, recalculating when that execution time would be, and recreating the event). I have in mind something like this:

// prints 'wow!' in 13 seconds
var event = clock.setTimeout(function() { console.log('wow!') }, 13)
// delays print by 5 seconds, such that print happens in 18 seconds
event.delay(5);
// 'un'delays print by 18 seconds, such that print happens now
event.delay(-18);

What do you think?

repeat / timeStretch bug

So I'm not actually sure if this is a bug, but here's some sample code to try.

Run "WAABugTestInitWithBug()", then refresh page and run "WAABugTestInitWithWorkaround()". The test isn't quite perfect (meant to play 4 beats at 60 BPM, then 8 beats at 120 BPM), but I think it gets the idea across. There's this weird stuttering of events that happens when BPM changes / timeStretches are called in the callback function of callBackWithTime. I have to add a tiny setTimeout to fix this issue.

I'm relatively new to JS so haven't dived into the code - thoughts though? Is this a bug or is it the way it's supposed to work and I'm just not quite understanding it?

var audioContext = window.AudioContext ? new AudioContext() : new webkitAudioContext();
var clock = new WAAClock(audioContext);
var bugTestMetronome;
var WAACounter = 0;

clock.start();

// play 4 beats at 60 BPM, then 8 beats at 120 BPM (then cycle)
function WAABugTestInitWithBug() {
    bugTestMetronome = clock.callbackAtTime(WAABugTestBangWithBug, audioContext.currentTime).repeat(1);
}

function WAABugTestInitWithWorkaround() {
    bugTestMetronome = clock.callbackAtTime(WAABugTestBangWithWorkaround, audioContext.currentTime).repeat(1);
}

function WAABugTestBangWithBug() {
    WAACounter++;
    console.log(WAACounter);
    if (WAACounter == 5) {
        WAABugTestDoubleTime();
    } else if (WAACounter == 11) {
        WAABugTestHalveTime();
        WAACounter = 0;
    }
}

function WAABugTestBangWithWorkaround() {
    // console.log(audioContext.currentTime);
    WAACounter++;
    console.log(WAACounter);
    clock.setTimeout(function() {
        if (WAACounter == 5) {
            WAABugTestDoubleTime();
        } else if (WAACounter == 11) {
            WAABugTestHalveTime();
            WAACounter = 0;
        }
    }, 0.0000001);
}

function WAABugTestHalveTime() {
    clock.timeStretch([bugTestMetronome], 2);
}

function WAABugTestDoubleTime() {
    clock.timeStretch([bugTestMetronome], 0.5);
}

Puase callbackAtTime

Is there away to just pause the method callbackAtTime ? Thanks for any suggetion.

Callbacks still aren't reliably fired when tab is in the background

Hi @sebpiq! WAAClock has been working great for my project, but I noticed that callbacks are fired inconsistently when the tab is in the background. I know you aren't actively maintaining this, but I would be happy to take a shot at a PR. I just want to make sure I have all necessary context first.

I noticed a couple of attempts were made at fixing this issue, using both setImmediate and process.nextTick, but neither are used in the latest version, which reverts back to setTimeout. Was this just an accidental regression, or do we need to find a different approach?

Thanks!

Event Expired -- What am I supposed to do with events?

I'm absolutely loving a couple of the features in WAAClock, most notably the time stretching. (Am I wrong in thinking that the time stretch feature actually accurately stretches time even for events that have been already scheduled by callbackAtTime? If not, then I might have bigger problems.)

However, I'm getting dozens, hundreds of event expired warnings and I'm not sure what I'm supposed to do with them. I converted a metronome from the awesome (Tale of Two Clocks)[https://www.html5rocks.com/en/tutorials/audio/scheduling/] article into React and now I'm adding WAAClock. In the constructor for the main component, I set up my audioContext and clock:

this.audioContext = new (window.AudioContext || window.webkitAudioContext)()
this.clock = new WAAClock(this.audioContext)

On clicking a play button, I (surprise surprise), call the play() method, which looks like:

play () {
    const isPlaying = !this.state.isPlaying
    this.setState({ isPlaying })
    if (isPlaying) {
      this.clock.start()
      var secondsPerBeat = 60.0 / this.state.tempo
      this.event1 = this.clock.callbackAtTime(() => {
        this.scheduleNote(this.state.current16thNote)
        this.nextNote()
      }, 0).repeat(secondsPerBeat * this.state.noteLength)
    } else {
      this.event1.clear()
      this.clock.stop()
    }
  }

scheduleNote() simply turns on an oscillator at a specific time (this.state.current16thNote) and then turns it off 30 ms later. The second I press play, I get a stream of event expired warnings in my console and I'm really not sure what I'm supposed to do with them. Any help here would be very appreciated. You can find the full source for this here

ios support

I've been looking at using this library for a project which I want to use on mobile devices, unfortunately it doesn't seem to work on ios I've been testing the web audio api and that works on an iphone that I have access to but this library does not. Alas I cannot give much more information as I just have an iphone I have borrowed for testing and as I don't have a Mac I can't enable remote debugging via safari.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.