1 | import { isAudioNode, isAudioParam } from "../util/AdvancedTypeCheck.js";
|
2 | import { isDefined } from "../util/TypeCheck.js";
|
3 | import { Param } from "./Param.js";
|
4 | import { ToneWithContext } from "./ToneWithContext.js";
|
5 | import { assert, warn } from "../util/Debug.js";
|
6 | /**
|
7 | * ToneAudioNode is the base class for classes which process audio.
|
8 | * @category Core
|
9 | */
|
10 | export 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 | */
|
248 | export 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 | */
|
268 | export 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 | */
|
301 | export 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 | */
|
334 | export 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 |