UNPKG

12 kBJavaScriptView Raw
1import { isAudioNode, isAudioParam } from "../util/AdvancedTypeCheck.js";
2import { isDefined } from "../util/TypeCheck.js";
3import { Param } from "./Param.js";
4import { ToneWithContext } from "./ToneWithContext.js";
5import { assert, warn } from "../util/Debug.js";
6/**
7 * ToneAudioNode is the base class for classes which process audio.
8 * @category Core
9 */
10export class ToneAudioNode extends ToneWithContext {
11 constructor() {
12 super(...arguments);
13 /**
14 * List all of the node that must be set to match the ChannelProperties
15 */
16 this._internalChannels = [];
17 }
18 /**
19 * The number of inputs feeding into the AudioNode.
20 * For source nodes, this will be 0.
21 * @example
22 * const node = new Tone.Gain();
23 * console.log(node.numberOfInputs);
24 */
25 get numberOfInputs() {
26 if (isDefined(this.input)) {
27 if (isAudioParam(this.input) || this.input instanceof Param) {
28 return 1;
29 }
30 else {
31 return this.input.numberOfInputs;
32 }
33 }
34 else {
35 return 0;
36 }
37 }
38 /**
39 * The number of outputs of the AudioNode.
40 * @example
41 * const node = new Tone.Gain();
42 * console.log(node.numberOfOutputs);
43 */
44 get numberOfOutputs() {
45 if (isDefined(this.output)) {
46 return this.output.numberOfOutputs;
47 }
48 else {
49 return 0;
50 }
51 }
52 //-------------------------------------
53 // AUDIO PROPERTIES
54 //-------------------------------------
55 /**
56 * Used to decide which nodes to get/set properties on
57 */
58 _isAudioNode(node) {
59 return (isDefined(node) &&
60 (node instanceof ToneAudioNode || isAudioNode(node)));
61 }
62 /**
63 * Get all of the audio nodes (either internal or input/output) which together
64 * make up how the class node responds to channel input/output
65 */
66 _getInternalNodes() {
67 const nodeList = this._internalChannels.slice(0);
68 if (this._isAudioNode(this.input)) {
69 nodeList.push(this.input);
70 }
71 if (this._isAudioNode(this.output)) {
72 if (this.input !== this.output) {
73 nodeList.push(this.output);
74 }
75 }
76 return nodeList;
77 }
78 /**
79 * Set the audio options for this node such as channelInterpretation
80 * channelCount, etc.
81 * @param options
82 */
83 _setChannelProperties(options) {
84 const nodeList = this._getInternalNodes();
85 nodeList.forEach((node) => {
86 node.channelCount = options.channelCount;
87 node.channelCountMode = options.channelCountMode;
88 node.channelInterpretation = options.channelInterpretation;
89 });
90 }
91 /**
92 * Get the current audio options for this node such as channelInterpretation
93 * channelCount, etc.
94 */
95 _getChannelProperties() {
96 const nodeList = this._getInternalNodes();
97 assert(nodeList.length > 0, "ToneAudioNode does not have any internal nodes");
98 // use the first node to get properties
99 // they should all be the same
100 const node = nodeList[0];
101 return {
102 channelCount: node.channelCount,
103 channelCountMode: node.channelCountMode,
104 channelInterpretation: node.channelInterpretation,
105 };
106 }
107 /**
108 * channelCount is the number of channels used when up-mixing and down-mixing
109 * connections to any inputs to the node. The default value is 2 except for
110 * specific nodes where its value is specially determined.
111 */
112 get channelCount() {
113 return this._getChannelProperties().channelCount;
114 }
115 set channelCount(channelCount) {
116 const props = this._getChannelProperties();
117 // merge it with the other properties
118 this._setChannelProperties(Object.assign(props, { channelCount }));
119 }
120 /**
121 * channelCountMode determines how channels will be counted when up-mixing and
122 * down-mixing connections to any inputs to the node.
123 * The default value is "max". This attribute has no effect for nodes with no inputs.
124 * * "max" - computedNumberOfChannels is the maximum of the number of channels of all connections to an input. In this mode channelCount is ignored.
125 * * "clamped-max" - computedNumberOfChannels is determined as for "max" and then clamped to a maximum value of the given channelCount.
126 * * "explicit" - computedNumberOfChannels is the exact value as specified by the channelCount.
127 */
128 get channelCountMode() {
129 return this._getChannelProperties().channelCountMode;
130 }
131 set channelCountMode(channelCountMode) {
132 const props = this._getChannelProperties();
133 // merge it with the other properties
134 this._setChannelProperties(Object.assign(props, { channelCountMode }));
135 }
136 /**
137 * channelInterpretation determines how individual channels will be treated
138 * when up-mixing and down-mixing connections to any inputs to the node.
139 * The default value is "speakers".
140 */
141 get channelInterpretation() {
142 return this._getChannelProperties().channelInterpretation;
143 }
144 set channelInterpretation(channelInterpretation) {
145 const props = this._getChannelProperties();
146 // merge it with the other properties
147 this._setChannelProperties(Object.assign(props, { channelInterpretation }));
148 }
149 //-------------------------------------
150 // CONNECTIONS
151 //-------------------------------------
152 /**
153 * connect the output of a ToneAudioNode to an AudioParam, AudioNode, or ToneAudioNode
154 * @param destination The output to connect to
155 * @param outputNum The output to connect from
156 * @param inputNum The input to connect to
157 */
158 connect(destination, outputNum = 0, inputNum = 0) {
159 connect(this, destination, outputNum, inputNum);
160 return this;
161 }
162 /**
163 * Connect the output to the context's destination node.
164 * @example
165 * const osc = new Tone.Oscillator("C2").start();
166 * osc.toDestination();
167 */
168 toDestination() {
169 this.connect(this.context.destination);
170 return this;
171 }
172 /**
173 * Connect the output to the context's destination node.
174 * @see {@link toDestination}
175 * @deprecated
176 */
177 toMaster() {
178 warn("toMaster() has been renamed toDestination()");
179 return this.toDestination();
180 }
181 /**
182 * disconnect the output
183 */
184 disconnect(destination, outputNum = 0, inputNum = 0) {
185 disconnect(this, destination, outputNum, inputNum);
186 return this;
187 }
188 /**
189 * Connect the output of this node to the rest of the nodes in series.
190 * @example
191 * const player = new Tone.Player("https://tonejs.github.io/audio/drum-samples/handdrum-loop.mp3");
192 * player.autostart = true;
193 * const filter = new Tone.AutoFilter(4).start();
194 * const distortion = new Tone.Distortion(0.5);
195 * // connect the player to the filter, distortion and then to the master output
196 * player.chain(filter, distortion, Tone.Destination);
197 */
198 chain(...nodes) {
199 connectSeries(this, ...nodes);
200 return this;
201 }
202 /**
203 * connect the output of this node to the rest of the nodes in parallel.
204 * @example
205 * const player = new Tone.Player("https://tonejs.github.io/audio/drum-samples/conga-rhythm.mp3");
206 * player.autostart = true;
207 * const pitchShift = new Tone.PitchShift(4).toDestination();
208 * const filter = new Tone.Filter("G5").toDestination();
209 * // connect a node to the pitch shift and filter in parallel
210 * player.fan(pitchShift, filter);
211 */
212 fan(...nodes) {
213 nodes.forEach((node) => this.connect(node));
214 return this;
215 }
216 /**
217 * Dispose and disconnect
218 */
219 dispose() {
220 super.dispose();
221 if (isDefined(this.input)) {
222 if (this.input instanceof ToneAudioNode) {
223 this.input.dispose();
224 }
225 else if (isAudioNode(this.input)) {
226 this.input.disconnect();
227 }
228 }
229 if (isDefined(this.output)) {
230 if (this.output instanceof ToneAudioNode) {
231 this.output.dispose();
232 }
233 else if (isAudioNode(this.output)) {
234 this.output.disconnect();
235 }
236 }
237 this._internalChannels = [];
238 return this;
239 }
240}
241//-------------------------------------
242// CONNECTIONS
243//-------------------------------------
244/**
245 * connect together all of the arguments in series
246 * @param nodes
247 */
248export function connectSeries(...nodes) {
249 const first = nodes.shift();
250 nodes.reduce((prev, current) => {
251 if (prev instanceof ToneAudioNode) {
252 prev.connect(current);
253 }
254 else if (isAudioNode(prev)) {
255 connect(prev, current);
256 }
257 return current;
258 }, first);
259}
260/**
261 * Connect two nodes together so that signal flows from the
262 * first node to the second. Optionally specify the input and output channels.
263 * @param srcNode The source node
264 * @param dstNode The destination node
265 * @param outputNumber The output channel of the srcNode
266 * @param inputNumber The input channel of the dstNode
267 */
268export function connect(srcNode, dstNode, outputNumber = 0, inputNumber = 0) {
269 assert(isDefined(srcNode), "Cannot connect from undefined node");
270 assert(isDefined(dstNode), "Cannot connect to undefined node");
271 if (dstNode instanceof ToneAudioNode || isAudioNode(dstNode)) {
272 assert(dstNode.numberOfInputs > 0, "Cannot connect to node with no inputs");
273 }
274 assert(srcNode.numberOfOutputs > 0, "Cannot connect from node with no outputs");
275 // resolve the input of the dstNode
276 while (dstNode instanceof ToneAudioNode || dstNode instanceof Param) {
277 if (isDefined(dstNode.input)) {
278 dstNode = dstNode.input;
279 }
280 }
281 while (srcNode instanceof ToneAudioNode) {
282 if (isDefined(srcNode.output)) {
283 srcNode = srcNode.output;
284 }
285 }
286 // make the connection
287 if (isAudioParam(dstNode)) {
288 srcNode.connect(dstNode, outputNumber);
289 }
290 else {
291 srcNode.connect(dstNode, outputNumber, inputNumber);
292 }
293}
294/**
295 * Disconnect a node from all nodes or optionally include a destination node and input/output channels.
296 * @param srcNode The source node
297 * @param dstNode The destination node
298 * @param outputNumber The output channel of the srcNode
299 * @param inputNumber The input channel of the dstNode
300 */
301export function disconnect(srcNode, dstNode, outputNumber = 0, inputNumber = 0) {
302 // resolve the destination node
303 if (isDefined(dstNode)) {
304 while (dstNode instanceof ToneAudioNode) {
305 dstNode = dstNode.input;
306 }
307 }
308 // resolve the src node
309 while (!isAudioNode(srcNode)) {
310 if (isDefined(srcNode.output)) {
311 srcNode = srcNode.output;
312 }
313 }
314 if (isAudioParam(dstNode)) {
315 srcNode.disconnect(dstNode, outputNumber);
316 }
317 else if (isAudioNode(dstNode)) {
318 srcNode.disconnect(dstNode, outputNumber, inputNumber);
319 }
320 else {
321 srcNode.disconnect();
322 }
323}
324/**
325 * Connect the output of one or more source nodes to a single destination node
326 * @param nodes One or more source nodes followed by one destination node
327 * @example
328 * const player = new Tone.Player("https://tonejs.github.io/audio/drum-samples/conga-rhythm.mp3");
329 * const player1 = new Tone.Player("https://tonejs.github.io/audio/drum-samples/conga-rhythm.mp3");
330 * const filter = new Tone.Filter("G5").toDestination();
331 * // connect nodes to a common destination
332 * Tone.fanIn(player, player1, filter);
333 */
334export function fanIn(...nodes) {
335 const dstNode = nodes.pop();
336 if (isDefined(dstNode)) {
337 nodes.forEach((node) => connect(node, dstNode));
338 }
339}
340//# sourceMappingURL=ToneAudioNode.js.map
\No newline at end of file