import Tone from "../core/Tone"; import "../core/Timeline"; /** * @class Tone.Draw is useful for synchronizing visuals and audio events. * Callbacks from Tone.Transport or any of the Tone.Event classes * always happen _before_ the scheduled time and are not synchronized * to the animation frame so they are not good for triggering tightly * synchronized visuals and sound. Tone.Draw makes it easy to schedule * callbacks using the AudioContext time and uses requestAnimationFrame. * * @singleton * @extends {Tone} * @example * Tone.Transport.schedule(function(time){ * //use the time argument to schedule a callback with Tone.Draw * Tone.Draw.schedule(function(){ * //do drawing or DOM manipulation here * }, time) * }, "+0.5") */ Tone.Draw = function(){ Tone.call(this); /** * All of the events. * @type {Tone.Timeline} * @private */ this._events = new Tone.Timeline(); /** * The duration after which events are not invoked. * @type {Number} * @default 0.25 */ this.expiration = 0.25; /** * The amount of time before the scheduled time * that the callback can be invoked. Default is * half the time of an animation frame (0.008 seconds). * @type {Number} * @default 0.008 */ this.anticipation = 0.008; /** * The draw loop * @type {Function} * @private */ this._boundDrawLoop = this._drawLoop.bind(this); }; Tone.extend(Tone.Draw); /** * Schedule a function at the given time to be invoked * on the nearest animation frame. * @param {Function} callback Callback is invoked at the given time. * @param {Time} time The time relative to the AudioContext time * to invoke the callback. * @return {Tone.Draw} this */ Tone.Draw.prototype.schedule = function(callback, time){ this._events.add({ callback : callback, time : this.toSeconds(time) }); //start the draw loop on the first event if (this._events.length === 1){ requestAnimationFrame(this._boundDrawLoop); } return this; }; /** * Cancel events scheduled after the given time * @param {Time=} after Time after which scheduled events will * be removed from the scheduling timeline. * @return {Tone.Draw} this */ Tone.Draw.prototype.cancel = function(after){ this._events.cancel(this.toSeconds(after)); return this; }; /** * The draw loop * @private */ Tone.Draw.prototype._drawLoop = function(){ var now = Tone.context.currentTime; while (this._events.length && this._events.peek().time - this.anticipation <= now){ var event = this._events.shift(); if (now - event.time <= this.expiration){ event.callback(); } } if (this._events.length > 0){ requestAnimationFrame(this._boundDrawLoop); } }; //make a singleton Tone.Draw = new Tone.Draw(); export default Tone.Draw;