Tone.js/Tone/signal/SyncedSignal.ts
2019-11-16 16:36:51 -05:00

172 lines
5.8 KiB
TypeScript

import { Signal, SignalOptions } from "./Signal";
import { NormalRange, Seconds, Time, TransportTime, UnitMap, UnitName } from "../core/type/Units";
import { optionsFromArguments } from "../core/util/Defaults";
import { TransportTimeClass } from "../core/type/TransportTime";
import { ToneConstantSource } from "./ToneConstantSource";
import { OutputNode } from "../core/context/ToneAudioNode";
/**
* Adds the ability to synchronize the signal to the [[Transport]]
*/
export class SyncedSignal<TypeName extends UnitName = "number"> extends Signal<TypeName> {
readonly name: string = "SyncedSignal";
/**
* Don't override when something is connected to the input
*/
readonly override = false;
readonly output: OutputNode;
/**
* Keep track of the last value as an optimization.
*/
private _lastVal: UnitMap[TypeName];
/**
* The ID returned from scheduleRepeat
*/
private _synced: number;
/**
* Remember the callback value
*/
private _syncedCallback: () => void;
/**
* @param value Initial value of the signal
* @param units The unit name, e.g. "frequency"
*/
constructor(value?: UnitMap[TypeName], units?: TypeName);
constructor(options?: Partial<SignalOptions<TypeName>>);
constructor() {
super(optionsFromArguments(Signal.getDefaults(), arguments, ["value", "units"]));
const options = optionsFromArguments(Signal.getDefaults(), arguments, ["value", "units"]) as SignalOptions<TypeName>;
this._lastVal = options.value;
this._synced = this.context.transport.scheduleRepeat(this._onTick.bind(this), "1i");
this._syncedCallback = this._anchorValue.bind(this);
this.context.transport.on("start", this._syncedCallback);
this.context.transport.on("pause", this._syncedCallback);
this.context.transport.on("stop", this._syncedCallback);
// disconnect the constant source from the output and replace it with another one
this._constantSource.disconnect();
this._constantSource.stop(0);
// create a new one
this._constantSource = this.output = new ToneConstantSource<TypeName>({
context: this.context,
offset: options.value,
units: options.units,
}).start(0);
this.setValueAtTime(options.value, 0);
}
/**
* Callback which is invoked every tick.
*/
private _onTick(time: Seconds): void {
const val = super.getValueAtTime(this.context.transport.seconds);
// approximate ramp curves with linear ramps
if (this._lastVal !== val) {
this._lastVal = val;
this._constantSource.offset.setValueAtTime(val, time);
}
}
/**
* Anchor the value at the start and stop of the Transport
*/
private _anchorValue(time: Seconds): void {
const val = super.getValueAtTime(this.context.transport.seconds);
this._lastVal = val;
this._constantSource.offset.cancelAndHoldAtTime(time);
this._constantSource.offset.setValueAtTime(val, time);
}
getValueAtTime(time: TransportTime): UnitMap[TypeName] {
const computedTime = new TransportTimeClass(this.context, time).toSeconds();
return super.getValueAtTime(computedTime);
}
setValueAtTime(value: UnitMap[TypeName], time: TransportTime) {
const computedTime = new TransportTimeClass(this.context, time).toSeconds();
super.setValueAtTime(value, computedTime);
return this;
}
linearRampToValueAtTime(value: UnitMap[TypeName], time: TransportTime) {
const computedTime = new TransportTimeClass(this.context, time).toSeconds();
super.linearRampToValueAtTime(value, computedTime);
return this;
}
exponentialRampToValueAtTime(value: UnitMap[TypeName], time: TransportTime) {
const computedTime = new TransportTimeClass(this.context, time).toSeconds();
super.exponentialRampToValueAtTime(value, computedTime);
return this;
}
setTargetAtTime(value, startTime: TransportTime, timeConstant: number): this {
const computedTime = new TransportTimeClass(this.context, startTime).toSeconds();
super.setTargetAtTime(value, computedTime, timeConstant);
return this;
}
cancelScheduledValues(startTime: TransportTime): this {
const computedTime = new TransportTimeClass(this.context, startTime).toSeconds();
super.cancelScheduledValues(computedTime);
return this;
}
setValueCurveAtTime(values: UnitMap[TypeName][], startTime: TransportTime, duration: Time, scaling: NormalRange): this {
const computedTime = new TransportTimeClass(this.context, startTime).toSeconds();
duration = this.toSeconds(duration);
super.setValueCurveAtTime(values, computedTime, duration, scaling);
return this;
}
cancelAndHoldAtTime(time: TransportTime): this {
const computedTime = new TransportTimeClass(this.context, time).toSeconds();
super.cancelAndHoldAtTime(computedTime);
return this;
}
setRampPoint(time: TransportTime): this {
const computedTime = new TransportTimeClass(this.context, time).toSeconds();
super.setRampPoint(computedTime);
return this;
}
exponentialRampTo(value: UnitMap[TypeName], rampTime: Time, startTime?: TransportTime): this {
const computedTime = new TransportTimeClass(this.context, startTime).toSeconds();
super.exponentialRampTo(value, rampTime, computedTime);
return this;
}
linearRampTo(value: UnitMap[TypeName], rampTime: Time, startTime?: TransportTime): this {
const computedTime = new TransportTimeClass(this.context, startTime).toSeconds();
super.linearRampTo(value, rampTime, computedTime);
return this;
}
targetRampTo(value: UnitMap[TypeName], rampTime: Time, startTime?: TransportTime): this {
const computedTime = new TransportTimeClass(this.context, startTime).toSeconds();
super.targetRampTo(value, rampTime, computedTime);
return this;
}
dispose(): this {
super.dispose();
this.context.transport.clear(this._synced);
this.context.transport.off("start", this._syncedCallback);
this.context.transport.off("pause", this._syncedCallback);
this.context.transport.off("stop", this._syncedCallback);
this._constantSource.dispose();
return this;
}
}