2019-07-30 19:35:27 +00:00
|
|
|
import { Unit } from "../type/Units";
|
2019-07-11 13:57:06 +00:00
|
|
|
import { isArray, isDefined, isNumber } from "../util/TypeCheck";
|
2019-04-12 14:37:47 +00:00
|
|
|
import { Param } from "./Param";
|
2019-05-23 18:00:49 +00:00
|
|
|
import { ToneWithContext, ToneWithContextOptions } from "./ToneWithContext";
|
2019-04-12 14:37:47 +00:00
|
|
|
|
|
|
|
export type InputNode = ToneAudioNode | AudioNode | AudioParam | Param<Unit>;
|
|
|
|
export type OutputNode = ToneAudioNode | AudioNode;
|
|
|
|
|
|
|
|
export interface ChannelProperties {
|
|
|
|
channelCount: number;
|
|
|
|
channelCountMode: ChannelCountMode;
|
|
|
|
channelInterpretation: ChannelInterpretation;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* The possible options for this node
|
|
|
|
*/
|
2019-05-23 18:00:49 +00:00
|
|
|
export interface ToneAudioNodeOptions extends ToneWithContextOptions {
|
2019-04-12 14:37:47 +00:00
|
|
|
channelCount: number;
|
|
|
|
channelCountMode: ChannelCountMode;
|
|
|
|
channelInterpretation: ChannelInterpretation;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* ToneAudioNode is the base class for classes which process audio.
|
|
|
|
*/
|
|
|
|
export abstract class ToneAudioNode<Options extends ToneAudioNodeOptions = ToneAudioNodeOptions>
|
2019-05-23 18:00:49 +00:00
|
|
|
extends ToneWithContext<Options> {
|
2019-04-12 14:37:47 +00:00
|
|
|
|
2019-05-23 18:00:49 +00:00
|
|
|
abstract name = "AudioNode";
|
2019-04-12 14:37:47 +00:00
|
|
|
|
|
|
|
/**
|
|
|
|
* The input node or nodes. If the object is a source,
|
|
|
|
* it does not have any input and this.input is undefined.
|
|
|
|
*/
|
2019-07-25 14:43:47 +00:00
|
|
|
abstract input: InputNode | InputNode | undefined;
|
2019-04-12 14:37:47 +00:00
|
|
|
|
|
|
|
/**
|
|
|
|
* The output nodes. If the object is a sink,
|
|
|
|
* it does not have any output and this.output is undefined.
|
|
|
|
*/
|
2019-07-25 14:43:47 +00:00
|
|
|
abstract output: OutputNode | OutputNode | undefined;
|
2019-04-12 14:37:47 +00:00
|
|
|
|
|
|
|
/**
|
|
|
|
* The number of inputs feeding into the AudioNode.
|
|
|
|
* For source nodes, this will be 0.
|
|
|
|
*/
|
2019-08-03 01:48:28 +00:00
|
|
|
get numberOfInputs(): number {
|
|
|
|
if (isDefined(this.input)) {
|
|
|
|
if (this.input instanceof AudioParam || this.input instanceof Param) {
|
|
|
|
return 1;
|
|
|
|
} else {
|
|
|
|
return this.input.numberOfInputs;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
}
|
2019-04-12 14:37:47 +00:00
|
|
|
|
|
|
|
/**
|
|
|
|
* The number of outputs of the AudioNode.
|
|
|
|
*/
|
2019-08-03 01:48:28 +00:00
|
|
|
get numberOfOutputs(): number {
|
|
|
|
if (isDefined(this.output)) {
|
|
|
|
if (this.output instanceof AudioParam || this.output instanceof Param) {
|
|
|
|
return 1;
|
|
|
|
} else {
|
|
|
|
return this.output.numberOfOutputs;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
}
|
2019-04-12 14:37:47 +00:00
|
|
|
|
|
|
|
/**
|
|
|
|
* List all of the node that must be set to match the ChannelProperties
|
|
|
|
*/
|
2019-08-03 01:48:28 +00:00
|
|
|
protected _internalChannels: OutputNode[] = [];
|
2019-04-12 14:37:47 +00:00
|
|
|
|
|
|
|
static getDefaults(): ToneAudioNodeOptions {
|
2019-05-23 18:00:49 +00:00
|
|
|
return Object.assign(ToneWithContext.getDefaults(), {
|
2019-04-12 14:37:47 +00:00
|
|
|
channelCount: 2,
|
|
|
|
channelCountMode: "max" as ChannelCountMode,
|
|
|
|
channelInterpretation: "speakers" as ChannelInterpretation,
|
|
|
|
numberOfInputs: 0,
|
|
|
|
numberOfOutputs: 0,
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2019-07-25 03:33:31 +00:00
|
|
|
constructor(options: ToneAudioNodeOptions) {
|
|
|
|
super(options);
|
2019-04-12 14:37:47 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
protected createInsOuts(numberOfInputs: number = 0, numberOfOutputs: number = 0): void {
|
2019-07-25 14:43:47 +00:00
|
|
|
// if (numberOfInputs === 1) {
|
|
|
|
// this.input = this.context.createGain();
|
|
|
|
// } else if (numberOfInputs > 1) {
|
|
|
|
// this.input = [];
|
|
|
|
// for (let i = 0; i < numberOfInputs; i++) {
|
|
|
|
// this.input[i] = this.context.createGain();
|
|
|
|
// }
|
|
|
|
// }
|
|
|
|
|
|
|
|
// if (numberOfOutputs === 1) {
|
|
|
|
// this.output = this.context.createGain();
|
|
|
|
// } else if (numberOfOutputs > 1) {
|
|
|
|
// this.output = [];
|
|
|
|
// for (let o = 0; o < numberOfOutputs; o++) {
|
|
|
|
// this.output[o] = this.context.createGain();
|
|
|
|
// }
|
|
|
|
// }
|
2019-04-12 14:37:47 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////////
|
|
|
|
// AUDIO PROPERTIES
|
|
|
|
///////////////////////////////////////////////////////////////////////////
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Set the audio options for this node such as channelInterpretation
|
|
|
|
* channelCount, etc.
|
|
|
|
* @param options
|
|
|
|
*/
|
|
|
|
private _setChannelProperties(options: ChannelProperties): void {
|
|
|
|
if (this._internalChannels.length) {
|
|
|
|
this._internalChannels.forEach(node => {
|
|
|
|
node.channelCount = options.channelCount;
|
|
|
|
node.channelCountMode = options.channelCountMode;
|
|
|
|
node.channelInterpretation = options.channelInterpretation;
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Get the current audio options for this node such as channelInterpretation
|
|
|
|
* channelCount, etc.
|
|
|
|
*/
|
|
|
|
private _getChannelProperties(): ChannelProperties {
|
|
|
|
if (this._internalChannels.length) {
|
|
|
|
const node = this._internalChannels[0];
|
|
|
|
return {
|
|
|
|
channelCount: node.channelCount,
|
|
|
|
channelCountMode: node.channelCountMode,
|
|
|
|
channelInterpretation: node.channelInterpretation,
|
|
|
|
};
|
|
|
|
} else {
|
|
|
|
// return the defaults
|
|
|
|
return {
|
|
|
|
channelCount: 2,
|
|
|
|
channelCountMode: "max",
|
|
|
|
channelInterpretation: "speakers",
|
|
|
|
};
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* channelCount is the number of channels used when up-mixing and down-mixing
|
|
|
|
* connections to any inputs to the node. The default value is 2 except for
|
|
|
|
* specific nodes where its value is specially determined.
|
|
|
|
*/
|
|
|
|
get channelCount(): number {
|
|
|
|
return this._getChannelProperties().channelCount;
|
|
|
|
}
|
|
|
|
set channelCount(channelCount: number) {
|
|
|
|
const props = this._getChannelProperties();
|
|
|
|
// merge it with the other properties
|
|
|
|
this._setChannelProperties(Object.assign(props, { channelCount }));
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* channelCountMode determines how channels will be counted when up-mixing and
|
|
|
|
* down-mixing connections to any inputs to the node.
|
|
|
|
* The default value is "max". This attribute has no effect for nodes with no inputs.
|
|
|
|
*/
|
|
|
|
get channelCountMode(): ChannelCountMode {
|
|
|
|
return this._getChannelProperties().channelCountMode;
|
|
|
|
}
|
|
|
|
set channelCountMode(channelCountMode: ChannelCountMode) {
|
|
|
|
const props = this._getChannelProperties();
|
|
|
|
// merge it with the other properties
|
|
|
|
this._setChannelProperties(Object.assign(props, { channelCountMode }));
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* channelInterpretation determines how individual channels will be treated
|
|
|
|
* when up-mixing and down-mixing connections to any inputs to the node.
|
|
|
|
* The default value is "speakers".
|
|
|
|
*/
|
|
|
|
get channelInterpretation(): ChannelInterpretation {
|
|
|
|
return this._getChannelProperties().channelInterpretation;
|
|
|
|
}
|
|
|
|
set channelInterpretation(channelInterpretation: ChannelInterpretation) {
|
|
|
|
const props = this._getChannelProperties();
|
|
|
|
// merge it with the other properties
|
|
|
|
this._setChannelProperties(Object.assign(props, { channelInterpretation }));
|
|
|
|
}
|
|
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////////
|
|
|
|
// CONNECTIONS
|
|
|
|
///////////////////////////////////////////////////////////////////////////
|
|
|
|
|
|
|
|
/**
|
|
|
|
* connect the output of a ToneAudioNode to an AudioParam, AudioNode, or ToneAudioNode
|
|
|
|
* @param unit The output to connect to
|
|
|
|
* @param outputNum The output to connect from
|
|
|
|
* @param inputNum The input to connect to
|
|
|
|
*/
|
|
|
|
connect(destination: InputNode, outputNum = 0, inputNum = 0): this {
|
|
|
|
connect(this, destination, outputNum, inputNum);
|
|
|
|
return this;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Connect the output to the context's destination node.
|
|
|
|
*/
|
2019-06-23 19:03:31 +00:00
|
|
|
toDestination(): this {
|
2019-04-12 14:37:47 +00:00
|
|
|
this.connect(this.context.destination);
|
|
|
|
return this;
|
|
|
|
}
|
|
|
|
|
2019-06-23 19:03:31 +00:00
|
|
|
/**
|
|
|
|
* Connect the output to the context's destination node.
|
|
|
|
* alias for {@link toDestination}
|
|
|
|
*/
|
|
|
|
toMaster(): this {
|
2019-07-25 17:10:52 +00:00
|
|
|
console.warn("toMaster() has been renamed toDestination()");
|
2019-06-23 19:03:31 +00:00
|
|
|
return this.toDestination();
|
|
|
|
}
|
|
|
|
|
2019-04-12 14:37:47 +00:00
|
|
|
/**
|
|
|
|
* disconnect the output
|
|
|
|
* @param output Either the output index to disconnect if the output is an array, or the node to disconnect from.
|
|
|
|
*/
|
|
|
|
disconnect(destination?: InputNode, outputNum = 0, inputNum = 0): this {
|
|
|
|
disconnect(this, destination, outputNum, inputNum);
|
|
|
|
return this;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Connect the output of this node to the rest of the nodes in series.
|
|
|
|
* @param nodes
|
|
|
|
* @example
|
|
|
|
* //connect a node to an effect, panVol and then to the master output
|
|
|
|
* node.chain(effect, panVol, Tone.Destination);
|
|
|
|
*/
|
|
|
|
chain(...nodes: InputNode[]): this {
|
2019-06-24 18:35:32 +00:00
|
|
|
connectSeries(this, ...nodes);
|
2019-04-12 14:37:47 +00:00
|
|
|
return this;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* connect the output of this node to the rest of the nodes in parallel.
|
|
|
|
* @param nodes
|
|
|
|
* @returns this
|
|
|
|
*/
|
|
|
|
fan(...nodes: InputNode[]): this {
|
|
|
|
nodes.forEach(node => this.connect(node));
|
|
|
|
return this;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Dispose and disconnect
|
|
|
|
*/
|
|
|
|
dispose(): this {
|
2019-07-23 16:11:57 +00:00
|
|
|
super.dispose();
|
2019-04-12 14:37:47 +00:00
|
|
|
if (isDefined(this.input)) {
|
|
|
|
if (isArray(this.input)) {
|
|
|
|
this.input.forEach(input => {
|
|
|
|
if (input instanceof ToneAudioNode) {
|
|
|
|
input.dispose();
|
|
|
|
} else if (input instanceof AudioNode) {
|
|
|
|
input.disconnect();
|
|
|
|
}
|
|
|
|
});
|
2019-07-23 16:11:57 +00:00
|
|
|
} else if (this.input instanceof ToneAudioNode) {
|
|
|
|
this.input.dispose();
|
|
|
|
} else if (this.input instanceof AudioNode) {
|
|
|
|
this.input.disconnect();
|
2019-04-12 14:37:47 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
if (isDefined(this.output)) {
|
|
|
|
if (isArray(this.output)) {
|
|
|
|
this.output.forEach(output => {
|
|
|
|
if (output instanceof ToneAudioNode) {
|
|
|
|
output.dispose();
|
|
|
|
} else {
|
|
|
|
output.disconnect();
|
|
|
|
}
|
|
|
|
});
|
2019-07-23 16:11:57 +00:00
|
|
|
} else if (this.output instanceof ToneAudioNode) {
|
|
|
|
this.output.dispose();
|
|
|
|
} else if (this.output instanceof AudioNode) {
|
2019-04-12 14:37:47 +00:00
|
|
|
this.output.disconnect();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
this._internalChannels = [];
|
|
|
|
return this;
|
|
|
|
}
|
|
|
|
}
|
2019-07-11 13:57:06 +00:00
|
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
|
|
// CONNECTIONS
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
|
|
|
|
|
|
/**
|
|
|
|
* connect together all of the arguments in series
|
|
|
|
* @param nodes
|
|
|
|
*/
|
|
|
|
export function connectSeries(...nodes: InputNode[]): void {
|
|
|
|
const first = nodes.shift();
|
|
|
|
nodes.reduce((prev, current) => {
|
2019-07-15 13:45:05 +00:00
|
|
|
if (prev instanceof ToneAudioNode) {
|
|
|
|
prev.connect(current);
|
|
|
|
} else if (prev instanceof AudioNode) {
|
2019-07-11 13:57:06 +00:00
|
|
|
connect(prev, current);
|
|
|
|
}
|
|
|
|
return current;
|
|
|
|
}, first);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Connect two nodes together so that signal flows from the
|
|
|
|
* first node to the second. Optionally specify the input and output channels.
|
|
|
|
* @param srcNode The source node
|
|
|
|
* @param dstNode The destination node
|
|
|
|
* @param outputNumber The output channel of the srcNode
|
|
|
|
* @param inputNumber The input channel of the dstNode
|
|
|
|
*/
|
|
|
|
export function connect(srcNode: OutputNode, dstNode: InputNode, outputNumber = 0, inputNumber = 0): void {
|
|
|
|
|
|
|
|
// resolve the input of the dstNode
|
|
|
|
while (!(dstNode instanceof AudioNode || dstNode instanceof AudioParam)) {
|
2019-07-25 14:43:47 +00:00
|
|
|
if (isDefined(dstNode.input)) {
|
2019-07-11 13:57:06 +00:00
|
|
|
dstNode = dstNode.input;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-07-25 14:43:47 +00:00
|
|
|
while (srcNode instanceof ToneAudioNode) {
|
|
|
|
if (isDefined(srcNode.output)) {
|
2019-07-11 13:57:06 +00:00
|
|
|
srcNode = srcNode.output;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// make the connection
|
|
|
|
if (dstNode instanceof AudioParam) {
|
|
|
|
srcNode.connect(dstNode, outputNumber);
|
|
|
|
} else {
|
|
|
|
srcNode.connect(dstNode, outputNumber, inputNumber);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Disconnect a node from all nodes or optionally include a destination node and input/output channels.
|
|
|
|
* @param srcNode The source node
|
|
|
|
* @param dstNode The destination node
|
|
|
|
* @param outputNumber The output channel of the srcNode
|
|
|
|
* @param inputNumber The input channel of the dstNode
|
|
|
|
*/
|
|
|
|
export function disconnect(
|
|
|
|
srcNode: OutputNode,
|
|
|
|
dstNode?: InputNode,
|
|
|
|
outputNumber = 0,
|
|
|
|
inputNumber = 0,
|
|
|
|
): void {
|
|
|
|
|
|
|
|
// resolve the destination node
|
|
|
|
if (isDefined(dstNode)) {
|
|
|
|
while (dstNode instanceof ToneAudioNode) {
|
2019-07-25 14:43:47 +00:00
|
|
|
if (dstNode.input) {
|
2019-07-11 13:57:06 +00:00
|
|
|
dstNode = dstNode.input;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// resolve the src node
|
|
|
|
while (!(srcNode instanceof AudioNode)) {
|
2019-07-25 14:43:47 +00:00
|
|
|
if (isDefined(srcNode.output)) {
|
2019-07-11 13:57:06 +00:00
|
|
|
srcNode = srcNode.output;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (dstNode instanceof AudioParam) {
|
|
|
|
srcNode.disconnect(dstNode, outputNumber);
|
|
|
|
} else if (dstNode instanceof AudioNode) {
|
|
|
|
srcNode.disconnect(dstNode, outputNumber, inputNumber);
|
|
|
|
} else {
|
|
|
|
srcNode.disconnect();
|
|
|
|
}
|
|
|
|
}
|