UNPKG

6.04 kBJavaScriptView Raw
1import { Gain } from "../core/context/Gain.js";
2import { ToneAudioNode, } from "../core/context/ToneAudioNode.js";
3import { noOp } from "../core/util/Interface.js";
4import { assert } from "../core/util/Debug.js";
5/**
6 * Base class for fire-and-forget nodes
7 */
8export class OneShotSource extends ToneAudioNode {
9 constructor(options) {
10 super(options);
11 /**
12 * The callback to invoke after the
13 * source is done playing.
14 */
15 this.onended = noOp;
16 /**
17 * The start time
18 */
19 this._startTime = -1;
20 /**
21 * The stop time
22 */
23 this._stopTime = -1;
24 /**
25 * The id of the timeout
26 */
27 this._timeout = -1;
28 /**
29 * The public output node
30 */
31 this.output = new Gain({
32 context: this.context,
33 gain: 0,
34 });
35 /**
36 * The output gain node.
37 */
38 this._gainNode = this.output;
39 /**
40 * Get the playback state at the given time
41 */
42 this.getStateAtTime = function (time) {
43 const computedTime = this.toSeconds(time);
44 if (this._startTime !== -1 &&
45 computedTime >= this._startTime &&
46 (this._stopTime === -1 || computedTime <= this._stopTime)) {
47 return "started";
48 }
49 else {
50 return "stopped";
51 }
52 };
53 this._fadeIn = options.fadeIn;
54 this._fadeOut = options.fadeOut;
55 this._curve = options.curve;
56 this.onended = options.onended;
57 }
58 static getDefaults() {
59 return Object.assign(ToneAudioNode.getDefaults(), {
60 curve: "linear",
61 fadeIn: 0,
62 fadeOut: 0,
63 onended: noOp,
64 });
65 }
66 /**
67 * Start the source at the given time
68 * @param time When to start the source
69 */
70 _startGain(time, gain = 1) {
71 assert(this._startTime === -1, "Source cannot be started more than once");
72 // apply a fade in envelope
73 const fadeInTime = this.toSeconds(this._fadeIn);
74 // record the start time
75 this._startTime = time + fadeInTime;
76 this._startTime = Math.max(this._startTime, this.context.currentTime);
77 // schedule the envelope
78 if (fadeInTime > 0) {
79 this._gainNode.gain.setValueAtTime(0, time);
80 if (this._curve === "linear") {
81 this._gainNode.gain.linearRampToValueAtTime(gain, time + fadeInTime);
82 }
83 else {
84 this._gainNode.gain.exponentialApproachValueAtTime(gain, time, fadeInTime);
85 }
86 }
87 else {
88 this._gainNode.gain.setValueAtTime(gain, time);
89 }
90 return this;
91 }
92 /**
93 * Stop the source node at the given time.
94 * @param time When to stop the source
95 */
96 stop(time) {
97 this.log("stop", time);
98 this._stopGain(this.toSeconds(time));
99 return this;
100 }
101 /**
102 * Stop the source at the given time
103 * @param time When to stop the source
104 */
105 _stopGain(time) {
106 assert(this._startTime !== -1, "'start' must be called before 'stop'");
107 // cancel the previous stop
108 this.cancelStop();
109 // the fadeOut time
110 const fadeOutTime = this.toSeconds(this._fadeOut);
111 // schedule the stop callback
112 this._stopTime = this.toSeconds(time) + fadeOutTime;
113 this._stopTime = Math.max(this._stopTime, this.now());
114 if (fadeOutTime > 0) {
115 // start the fade out curve at the given time
116 if (this._curve === "linear") {
117 this._gainNode.gain.linearRampTo(0, fadeOutTime, time);
118 }
119 else {
120 this._gainNode.gain.targetRampTo(0, fadeOutTime, time);
121 }
122 }
123 else {
124 // stop any ongoing ramps, and set the value to 0
125 this._gainNode.gain.cancelAndHoldAtTime(time);
126 this._gainNode.gain.setValueAtTime(0, time);
127 }
128 this.context.clearTimeout(this._timeout);
129 this._timeout = this.context.setTimeout(() => {
130 // allow additional time for the exponential curve to fully decay
131 const additionalTail = this._curve === "exponential" ? fadeOutTime * 2 : 0;
132 this._stopSource(this.now() + additionalTail);
133 this._onended();
134 }, this._stopTime - this.context.currentTime);
135 return this;
136 }
137 /**
138 * Invoke the onended callback
139 */
140 _onended() {
141 if (this.onended !== noOp) {
142 this.onended(this);
143 // overwrite onended to make sure it only is called once
144 this.onended = noOp;
145 // dispose when it's ended to free up for garbage collection only in the online context
146 if (!this.context.isOffline) {
147 const disposeCallback = () => this.dispose();
148 // @ts-ignore
149 if (typeof window.requestIdleCallback !== "undefined") {
150 // @ts-ignore
151 window.requestIdleCallback(disposeCallback);
152 }
153 else {
154 setTimeout(disposeCallback, 1000);
155 }
156 }
157 }
158 }
159 /**
160 * Get the playback state at the current time
161 */
162 get state() {
163 return this.getStateAtTime(this.now());
164 }
165 /**
166 * Cancel a scheduled stop event
167 */
168 cancelStop() {
169 this.log("cancelStop");
170 assert(this._startTime !== -1, "Source is not started");
171 // cancel the stop envelope
172 this._gainNode.gain.cancelScheduledValues(this._startTime + this.sampleTime);
173 this.context.clearTimeout(this._timeout);
174 this._stopTime = -1;
175 return this;
176 }
177 dispose() {
178 super.dispose();
179 this._gainNode.dispose();
180 this.onended = noOp;
181 return this;
182 }
183}
184//# sourceMappingURL=OneShotSource.js.map
\No newline at end of file