Tone.js/Tone/core/clock/TransportRepeatEvent.ts
2019-05-23 14:00:49 -04:00

130 lines
3.6 KiB
TypeScript

import { Context } from "../context/Context";
import { Ticks, TicksClass } from "../type/Ticks";
import { Transport } from "./Transport";
import { TransportEvent, TransportEventOptions } from "./TransportEvent";
interface TransportRepeatEventOptions extends TransportEventOptions {
interval: Ticks;
duration: Ticks;
}
/**
* TransportRepeatEvent is an internal class used by Tone.Transport
* to schedule repeat events. This class should not be instantiated directly.
*/
export class TransportRepeatEvent extends TransportEvent {
/**
* When the event should stop repeating
*/
private duration: Ticks;
/**
* The interval of the repeated event
*/
private _interval: Ticks;
/**
* The ID of the current timeline event
*/
private _currentId: number = -1;
/**
* The ID of the next timeline event
*/
private _nextId: number = -1;
/**
* The time of the next event
*/
private _nextTick = this.time;
/**
* a reference to the bound start method
*/
private _boundRestart = this._restart.bind(this);
/**
* The audio context belonging to this event
*/
protected context: Context;
constructor(transport: Transport, opts: Partial<TransportRepeatEventOptions>) {
super(transport, opts);
const options = Object.assign(TransportRepeatEvent.getDefaults(), opts);
this.duration = Ticks(options.duration).valueOf();
this._interval = Ticks(options.interval).valueOf();
this._nextTick = options.time;
this.transport.on("start", this._boundRestart);
this.transport.on("loopStart", this._boundRestart);
this.context = this.transport.context;
this._restart();
}
static getDefaults(): TransportRepeatEventOptions {
return Object.assign({}, TransportEvent.getDefaults(), {
duration: Infinity,
interval: 1,
once: false,
});
}
/**
* Invoke the callback. Returns the tick time which
* the next event should be scheduled at.
* @param time The AudioContext time in seconds of the event
*/
invoke(time: Seconds): void {
// create more events if necessary
this._createEvents(time);
// call the super class
super.invoke(time);
}
/**
* Push more events onto the timeline to keep up with the position of the timeline
*/
private _createEvents(time: Seconds): void {
// schedule the next event
const ticks = this.transport.getTicksAtTime(time);
if (ticks >= this.time && ticks >= this._nextTick && this._nextTick + this._interval < this.time + this.duration) {
this._nextTick += this._interval;
this._currentId = this._nextId;
this._nextId = this.transport.scheduleOnce(this.invoke.bind(this),
new TicksClass(this.context, this._nextTick).toSeconds());
}
}
/**
* Push more events onto the timeline to keep up with the position of the timeline
*/
private _restart(time?: Time): void {
this.transport.clear(this._currentId);
this.transport.clear(this._nextId);
this._nextTick = this.time;
const ticks = this.transport.getTicksAtTime(time);
if (ticks > this.time) {
this._nextTick = this.time + Math.ceil((ticks - this.time) / this._interval) * this._interval;
}
this._currentId = this.transport.scheduleOnce(this.invoke.bind(this),
new TicksClass(this.context, this._nextTick).toSeconds());
this._nextTick += this._interval;
this._nextId = this.transport.scheduleOnce(this.invoke.bind(this),
new TicksClass(this.context, this._nextTick).toSeconds());
}
/**
* Clean up
*/
dispose(): this {
super.dispose();
this.transport.clear(this._currentId);
this.transport.clear(this._nextId);
this.transport.off("start", this._boundRestart);
this.transport.off("loopStart", this._boundRestart);
return this;
}
}