2019-05-23 18:00:49 +00:00
|
|
|
import { AbstractParam } from "../core/context/AbstractParam";
|
|
|
|
import { Param } from "../core/context/Param";
|
2019-07-11 21:14:23 +00:00
|
|
|
import { InputNode, OutputNode, ToneAudioNode, ToneAudioNodeOptions } from "../core/context/ToneAudioNode";
|
2019-07-17 17:46:48 +00:00
|
|
|
import { connect } from "../core/context/ToneAudioNode";
|
2019-10-31 19:43:16 +00:00
|
|
|
import { Time, UnitMap, UnitName } from "../core/type/Units";
|
2019-08-16 16:49:04 +00:00
|
|
|
import { isAudioParam } from "../core/util/AdvancedTypeCheck";
|
2019-05-23 18:00:49 +00:00
|
|
|
import { optionsFromArguments } from "../core/util/Defaults";
|
2019-09-08 18:12:01 +00:00
|
|
|
import { ToneConstantSource } from "./ToneConstantSource";
|
2019-04-12 14:37:47 +00:00
|
|
|
|
2019-10-28 15:37:53 +00:00
|
|
|
export interface SignalOptions<TypeName extends UnitName> extends ToneAudioNodeOptions {
|
|
|
|
value: UnitMap[TypeName];
|
|
|
|
units: TypeName;
|
2019-04-12 14:37:47 +00:00
|
|
|
convert: boolean;
|
2019-12-11 15:11:40 +00:00
|
|
|
minValue?: number;
|
|
|
|
maxValue?: number;
|
2019-04-12 14:37:47 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* A signal is an audio-rate value. Tone.Signal is a core component of the library.
|
|
|
|
* Unlike a number, Signals can be scheduled with sample-level accuracy. Tone.Signal
|
|
|
|
* has all of the methods available to native Web Audio
|
|
|
|
* [AudioParam](http://webaudio.github.io/web-audio-api/#the-audioparam-interface)
|
|
|
|
* as well as additional conveniences. Read more about working with signals
|
|
|
|
* [here](https://github.com/Tonejs/Tone.js/wiki/Signals).
|
2019-08-27 15:53:14 +00:00
|
|
|
*
|
2019-04-12 14:37:47 +00:00
|
|
|
* @example
|
2020-04-17 02:24:18 +00:00
|
|
|
* const osc = new Tone.Oscillator().toDestination().start();
|
2019-10-25 20:54:33 +00:00
|
|
|
* // a scheduleable signal which can be connected to control an AudioParam or another Signal
|
2020-04-17 02:24:18 +00:00
|
|
|
* const signal = new Tone.Signal({
|
2019-10-25 20:54:33 +00:00
|
|
|
* value: "C4",
|
|
|
|
* units: "frequency"
|
|
|
|
* }).connect(osc.frequency);
|
|
|
|
* // the scheduled ramp controls the connected signal
|
|
|
|
* signal.rampTo("C2", 4, "+0.5");
|
2019-09-16 14:15:23 +00:00
|
|
|
* @category Signal
|
2019-04-12 14:37:47 +00:00
|
|
|
*/
|
2019-10-28 15:37:53 +00:00
|
|
|
export class Signal<TypeName extends UnitName = "number"> extends ToneAudioNode<SignalOptions<any>>
|
|
|
|
implements AbstractParam<TypeName> {
|
2019-04-12 14:37:47 +00:00
|
|
|
|
2019-08-03 01:09:35 +00:00
|
|
|
readonly name: string = "Signal";
|
2019-04-12 14:37:47 +00:00
|
|
|
|
2019-07-11 21:14:23 +00:00
|
|
|
/**
|
|
|
|
* Indicates if the value should be overridden on connection.
|
|
|
|
*/
|
|
|
|
readonly override: boolean = true;
|
|
|
|
|
2019-04-12 14:37:47 +00:00
|
|
|
/**
|
|
|
|
* The constant source node which generates the signal
|
|
|
|
*/
|
2019-10-28 15:37:53 +00:00
|
|
|
protected _constantSource: ToneConstantSource<TypeName>;
|
2019-09-08 18:12:01 +00:00
|
|
|
readonly output: OutputNode;
|
2019-10-28 15:37:53 +00:00
|
|
|
protected _param: Param<TypeName>;
|
2019-07-11 21:14:23 +00:00
|
|
|
readonly input: InputNode;
|
2019-04-12 14:37:47 +00:00
|
|
|
|
2019-08-27 15:53:14 +00:00
|
|
|
/**
|
|
|
|
* @param value Initial value of the signal
|
|
|
|
* @param units The unit name, e.g. "frequency"
|
|
|
|
*/
|
2019-10-28 15:37:53 +00:00
|
|
|
constructor(value?: UnitMap[TypeName], units?: TypeName);
|
|
|
|
constructor(options?: Partial<SignalOptions<TypeName>>);
|
2019-04-12 14:37:47 +00:00
|
|
|
constructor() {
|
|
|
|
|
|
|
|
super(optionsFromArguments(Signal.getDefaults(), arguments, ["value", "units"]));
|
|
|
|
|
2019-10-28 15:37:53 +00:00
|
|
|
const options = optionsFromArguments(Signal.getDefaults(), arguments, ["value", "units"]) as SignalOptions<TypeName>;
|
2019-04-12 14:37:47 +00:00
|
|
|
|
2019-09-08 18:12:01 +00:00
|
|
|
this.output = this._constantSource = new ToneConstantSource({
|
2019-04-12 14:37:47 +00:00
|
|
|
context: this.context,
|
|
|
|
convert: options.convert,
|
2019-09-08 18:12:01 +00:00
|
|
|
offset: options.value,
|
2019-04-12 14:37:47 +00:00
|
|
|
units: options.units,
|
2019-12-11 15:11:40 +00:00
|
|
|
minValue: options.minValue,
|
|
|
|
maxValue: options.maxValue,
|
2019-04-12 14:37:47 +00:00
|
|
|
});
|
2019-09-08 18:12:01 +00:00
|
|
|
this._constantSource.start(0);
|
|
|
|
this.input = this._param = this._constantSource.offset;
|
2019-04-12 14:37:47 +00:00
|
|
|
}
|
|
|
|
|
2019-07-15 19:37:25 +00:00
|
|
|
static getDefaults(): SignalOptions<any> {
|
|
|
|
return Object.assign(ToneAudioNode.getDefaults(), {
|
|
|
|
convert: true,
|
|
|
|
units: "number" as UnitName,
|
|
|
|
value: 0,
|
2019-09-06 20:18:32 +00:00
|
|
|
});
|
2019-07-15 19:37:25 +00:00
|
|
|
}
|
|
|
|
|
2019-11-17 18:09:19 +00:00
|
|
|
connect(destination: InputNode, outputNum = 0, inputNum = 0): this {
|
2019-09-06 20:18:32 +00:00
|
|
|
// start it only when connected to something
|
2019-07-18 15:23:45 +00:00
|
|
|
connectSignal(this, destination, outputNum, inputNum);
|
2019-04-12 14:37:47 +00:00
|
|
|
return this;
|
|
|
|
}
|
|
|
|
|
2019-07-23 16:11:57 +00:00
|
|
|
dispose(): this {
|
|
|
|
super.dispose();
|
|
|
|
this._param.dispose();
|
2019-09-08 18:12:01 +00:00
|
|
|
this._constantSource.dispose();
|
2019-07-23 16:11:57 +00:00
|
|
|
return this;
|
|
|
|
}
|
|
|
|
|
2019-09-14 22:06:46 +00:00
|
|
|
//-------------------------------------
|
2019-04-12 14:37:47 +00:00
|
|
|
// ABSTRACT PARAM INTERFACE
|
|
|
|
// just a proxy for the ConstantSourceNode's offset AudioParam
|
2019-05-23 18:00:49 +00:00
|
|
|
// all docs are generated from AbstractParam.ts
|
2019-09-14 22:06:46 +00:00
|
|
|
//-------------------------------------
|
2019-04-12 14:37:47 +00:00
|
|
|
|
2019-10-28 15:37:53 +00:00
|
|
|
setValueAtTime(value: UnitMap[TypeName], time: Time): this {
|
2019-04-12 14:37:47 +00:00
|
|
|
this._param.setValueAtTime(value, time);
|
|
|
|
return this;
|
|
|
|
}
|
2019-10-28 15:37:53 +00:00
|
|
|
getValueAtTime(time: Time): UnitMap[TypeName] {
|
2019-04-12 14:37:47 +00:00
|
|
|
return this._param.getValueAtTime(time);
|
|
|
|
}
|
|
|
|
setRampPoint(time: Time): this {
|
|
|
|
this._param.setRampPoint(time);
|
|
|
|
return this;
|
|
|
|
}
|
2019-10-28 15:37:53 +00:00
|
|
|
linearRampToValueAtTime(value: UnitMap[TypeName], time: Time): this {
|
2019-04-12 14:37:47 +00:00
|
|
|
this._param.linearRampToValueAtTime(value, time);
|
|
|
|
return this;
|
|
|
|
}
|
2019-10-28 15:37:53 +00:00
|
|
|
exponentialRampToValueAtTime(value: UnitMap[TypeName], time: Time): this {
|
2019-04-12 14:37:47 +00:00
|
|
|
this._param.exponentialRampToValueAtTime(value, time);
|
|
|
|
return this;
|
|
|
|
}
|
2019-10-28 15:37:53 +00:00
|
|
|
exponentialRampTo(value: UnitMap[TypeName], rampTime: Time, startTime?: Time): this {
|
2019-04-12 14:37:47 +00:00
|
|
|
this._param.exponentialRampTo(value, rampTime, startTime);
|
|
|
|
return this;
|
|
|
|
}
|
2019-10-28 15:37:53 +00:00
|
|
|
linearRampTo(value: UnitMap[TypeName], rampTime: Time, startTime?: Time): this {
|
2019-04-12 14:37:47 +00:00
|
|
|
this._param.linearRampTo(value, rampTime, startTime);
|
|
|
|
return this;
|
|
|
|
}
|
2019-10-28 15:37:53 +00:00
|
|
|
targetRampTo(value: UnitMap[TypeName], rampTime: Time, startTime?: Time): this {
|
2019-04-12 14:37:47 +00:00
|
|
|
this._param.targetRampTo(value, rampTime, startTime);
|
|
|
|
return this;
|
|
|
|
}
|
2019-10-28 15:37:53 +00:00
|
|
|
exponentialApproachValueAtTime(value: UnitMap[TypeName], time: Time, rampTime: Time): this {
|
2019-04-12 14:37:47 +00:00
|
|
|
this._param.exponentialApproachValueAtTime(value, time, rampTime);
|
|
|
|
return this;
|
|
|
|
}
|
2019-10-28 15:37:53 +00:00
|
|
|
setTargetAtTime(value: UnitMap[TypeName], startTime: Time, timeConstant: number): this {
|
2019-04-12 14:37:47 +00:00
|
|
|
this._param.setTargetAtTime(value, startTime, timeConstant);
|
|
|
|
return this;
|
|
|
|
}
|
2019-10-28 15:37:53 +00:00
|
|
|
setValueCurveAtTime(values: UnitMap[TypeName][], startTime: Time, duration: Time, scaling?: number): this {
|
2019-04-12 14:37:47 +00:00
|
|
|
this._param.setValueCurveAtTime(values, startTime, duration, scaling);
|
|
|
|
return this;
|
|
|
|
}
|
|
|
|
cancelScheduledValues(time: Time): this {
|
|
|
|
this._param.cancelScheduledValues(time);
|
|
|
|
return this;
|
|
|
|
}
|
|
|
|
cancelAndHoldAtTime(time: Time): this {
|
|
|
|
this._param.cancelAndHoldAtTime(time);
|
|
|
|
return this;
|
|
|
|
}
|
2019-10-28 15:37:53 +00:00
|
|
|
rampTo(value: UnitMap[TypeName], rampTime: Time, startTime?: Time): this {
|
2019-04-12 14:37:47 +00:00
|
|
|
this._param.rampTo(value, rampTime, startTime);
|
|
|
|
return this;
|
|
|
|
}
|
|
|
|
|
2019-10-28 15:37:53 +00:00
|
|
|
get value(): UnitMap[TypeName] {
|
2019-04-12 14:37:47 +00:00
|
|
|
return this._param.value;
|
|
|
|
}
|
2019-10-28 15:37:53 +00:00
|
|
|
set value(value: UnitMap[TypeName]) {
|
2019-04-12 14:37:47 +00:00
|
|
|
this._param.value = value;
|
|
|
|
}
|
|
|
|
|
|
|
|
get convert(): boolean {
|
|
|
|
return this._param.convert;
|
|
|
|
}
|
|
|
|
set convert(convert: boolean) {
|
|
|
|
this._param.convert = convert;
|
|
|
|
}
|
|
|
|
|
2019-07-15 19:37:25 +00:00
|
|
|
get units(): UnitName {
|
2019-04-12 14:37:47 +00:00
|
|
|
return this._param.units;
|
|
|
|
}
|
|
|
|
|
|
|
|
get overridden(): boolean {
|
|
|
|
return this._param.overridden;
|
|
|
|
}
|
|
|
|
set overridden(overridden: boolean) {
|
|
|
|
this._param.overridden = overridden;
|
|
|
|
}
|
|
|
|
|
2019-07-15 19:37:25 +00:00
|
|
|
get maxValue(): number {
|
2019-04-12 14:37:47 +00:00
|
|
|
return this._param.maxValue;
|
|
|
|
}
|
2019-07-15 19:37:25 +00:00
|
|
|
get minValue(): number {
|
2019-04-12 14:37:47 +00:00
|
|
|
return this._param.minValue;
|
|
|
|
}
|
2019-09-06 20:18:32 +00:00
|
|
|
|
|
|
|
/**
|
|
|
|
* See [[Param.apply]].
|
|
|
|
*/
|
|
|
|
apply(param: Param | AudioParam): this {
|
|
|
|
this._param.apply(param);
|
|
|
|
return this;
|
|
|
|
}
|
2019-04-12 14:37:47 +00:00
|
|
|
}
|
2019-07-17 17:46:48 +00:00
|
|
|
|
|
|
|
/**
|
|
|
|
* When connecting from a signal, it's necessary to zero out the node destination
|
|
|
|
* node if that node is also a signal. If the destination is not 0, then the values
|
|
|
|
* will be summed. This method insures that the output of the destination signal will
|
|
|
|
* be the same as the source signal, making the destination signal a pass through node.
|
|
|
|
* @param signal The output signal to connect from
|
|
|
|
* @param destination the destination to connect to
|
|
|
|
* @param outputNum the optional output number
|
|
|
|
* @param inputNum the input number
|
|
|
|
*/
|
|
|
|
export function connectSignal(signal: OutputNode, destination: InputNode, outputNum?: number, inputNum?: number): void {
|
2019-08-16 16:49:04 +00:00
|
|
|
if (destination instanceof Param || isAudioParam(destination) ||
|
2019-07-17 17:46:48 +00:00
|
|
|
(destination instanceof Signal && destination.override)) {
|
|
|
|
// cancel changes
|
|
|
|
destination.cancelScheduledValues(0);
|
|
|
|
// reset the value
|
|
|
|
destination.setValueAtTime(0, 0);
|
|
|
|
// mark the value as overridden
|
|
|
|
if (destination instanceof Signal) {
|
|
|
|
destination.overridden = true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
connect(signal, destination, outputNum, inputNum);
|
|
|
|
}
|