node-web-audio-api 0.18.0 → 0.19.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (49) hide show
  1. package/CHANGELOG.md +7 -0
  2. package/TODOS.md +140 -12
  3. package/index.mjs +10 -5
  4. package/js/AnalyserNode.js +262 -48
  5. package/js/AudioBuffer.js +244 -0
  6. package/js/AudioBufferSourceNode.js +265 -41
  7. package/js/AudioContext.js +271 -28
  8. package/js/AudioDestinationNode.js +42 -100
  9. package/js/AudioListener.js +219 -0
  10. package/js/AudioNode.js +323 -0
  11. package/js/AudioParam.js +252 -39
  12. package/js/AudioScheduledSourceNode.js +105 -0
  13. package/js/BaseAudioContext.js +419 -0
  14. package/js/BiquadFilterNode.js +221 -29
  15. package/js/ChannelMergerNode.js +96 -22
  16. package/js/ChannelSplitterNode.js +96 -22
  17. package/js/ConstantSourceNode.js +92 -26
  18. package/js/ConvolverNode.js +161 -29
  19. package/js/DelayNode.js +115 -21
  20. package/js/DynamicsCompressorNode.js +198 -27
  21. package/js/GainNode.js +107 -21
  22. package/js/IIRFilterNode.js +139 -23
  23. package/js/MediaStreamAudioSourceNode.js +83 -24
  24. package/js/OfflineAudioContext.js +182 -34
  25. package/js/OscillatorNode.js +195 -32
  26. package/js/PannerNode.js +461 -56
  27. package/js/PeriodicWave.js +67 -3
  28. package/js/StereoPannerNode.js +107 -21
  29. package/js/WaveShaperNode.js +147 -29
  30. package/js/lib/cast.js +19 -0
  31. package/js/lib/errors.js +10 -55
  32. package/js/lib/events.js +20 -0
  33. package/js/lib/symbols.js +5 -0
  34. package/js/lib/utils.js +12 -12
  35. package/js/monkey-patch.js +32 -30
  36. package/node-web-audio-api.darwin-arm64.node +0 -0
  37. package/node-web-audio-api.darwin-x64.node +0 -0
  38. package/node-web-audio-api.linux-arm-gnueabihf.node +0 -0
  39. package/node-web-audio-api.linux-arm64-gnu.node +0 -0
  40. package/node-web-audio-api.linux-x64-gnu.node +0 -0
  41. package/node-web-audio-api.win32-arm64-msvc.node +0 -0
  42. package/node-web-audio-api.win32-x64-msvc.node +0 -0
  43. package/package.json +7 -4
  44. package/run-wpt.md +27 -0
  45. package/run-wpt.sh +5 -0
  46. package/js/AudioNode.mixin.js +0 -132
  47. package/js/AudioScheduledSourceNode.mixin.js +0 -67
  48. package/js/BaseAudioContext.mixin.js +0 -154
  49. package/js/EventTarget.mixin.js +0 -60
@@ -0,0 +1,244 @@
1
+ const conversions = require("webidl-conversions");
2
+
3
+ const {
4
+ throwSanitizedError,
5
+ } = require('./lib/errors.js');
6
+ const {
7
+ kEnumerableProperty,
8
+ kHiddenProperty,
9
+ } = require('./lib/utils.js');
10
+ const {
11
+ kNapiObj,
12
+ kAudioBuffer,
13
+ } = require('./lib/symbols.js');
14
+
15
+
16
+ module.exports = (_jsExport, nativeBinding) => {
17
+ class AudioBuffer {
18
+ constructor(options) {
19
+ if (arguments.length < 1) {
20
+ throw new TypeError(`Failed to construct 'AudioBuffer': 1 argument required, but only ${arguments.length} present`);
21
+ }
22
+
23
+ if (typeof options !== 'object') {
24
+ throw new TypeError("Failed to construct 'AudioBuffer': argument 1 is not of type 'AudioBufferOptions'");
25
+ }
26
+
27
+ if (kNapiObj in options) {
28
+ // internal constructor for `startRendering` and `decodeAudioData` cases
29
+ Object.defineProperty(this, kNapiObj, {
30
+ value: options[kNapiObj],
31
+ ...kHiddenProperty,
32
+ });
33
+ } else {
34
+ // Regular public constructor
35
+ // dictionary AudioBufferOptions {
36
+ // unsigned long numberOfChannels = 1;
37
+ // required unsigned long length;
38
+ // required float sampleRate;
39
+ // };
40
+ const parsedOptions = {};
41
+
42
+ if (options.numberOfChannels !== undefined) {
43
+ parsedOptions.numberOfChannels = conversions['unsigned long'](options.numberOfChannels, {
44
+ enforceRange: true,
45
+ context: `Failed to construct 'AudioBuffer': Failed to read the 'numberOfChannels' property from AudioBufferOptions: numberOfCHannels`,
46
+ });
47
+ } else {
48
+ parsedOptions.numberOfChannels = 1;
49
+ }
50
+
51
+ if (options.length === undefined) {
52
+ throw new TypeError(`Failed to construct 'AudioBuffer': Failed to read the 'numberOfChannels' property from AudioBufferOptions: required member is undefined`);
53
+ }
54
+
55
+ parsedOptions.length = conversions['unsigned long'](options.length, {
56
+ enforceRange: true,
57
+ context: `Failed to construct 'AudioBuffer': Failed to read the 'length' property from AudioBufferOptions: length`,
58
+ });
59
+
60
+ if (options.sampleRate === undefined) {
61
+ throw new TypeError(`Failed to construct 'AudioBuffer': Failed to read the 'numberOfChannels' property from AudioBufferOptions: required member is undefined`);
62
+ }
63
+
64
+ parsedOptions.sampleRate = conversions['float'](options.sampleRate, {
65
+ context: `Failed to construct 'AudioBuffer': Failed to read the 'sampleRate' property from AudioBufferOptions: sampleRate`,
66
+ });
67
+
68
+ let napiObj;
69
+
70
+ try {
71
+ napiObj = new nativeBinding.AudioBuffer(parsedOptions);
72
+ } catch (err) {
73
+ throwSanitizedError(err);
74
+ }
75
+
76
+ Object.defineProperty(this, kNapiObj, {
77
+ value: napiObj,
78
+ ...kHiddenProperty,
79
+ });
80
+ }
81
+ }
82
+
83
+ get sampleRate() {
84
+ if (!(this instanceof AudioBuffer)) {
85
+ throw new TypeError("Invalid Invocation: Value of 'this' must be of type 'AudioBuffer'");
86
+ }
87
+
88
+ return this[kNapiObj].sampleRate;
89
+ }
90
+
91
+ get duration() {
92
+ if (!(this instanceof AudioBuffer)) {
93
+ throw new TypeError("Invalid Invocation: Value of 'this' must be of type 'AudioBuffer'");
94
+ }
95
+
96
+ return this[kNapiObj].duration;
97
+ }
98
+
99
+ get length() {
100
+ if (!(this instanceof AudioBuffer)) {
101
+ throw new TypeError("Invalid Invocation: Value of 'this' must be of type 'AudioBuffer'");
102
+ }
103
+
104
+ return this[kNapiObj].length;
105
+ }
106
+
107
+ get numberOfChannels() {
108
+ if (!(this instanceof AudioBuffer)) {
109
+ throw new TypeError("Invalid Invocation: Value of 'this' must be of type 'AudioBuffer'");
110
+ }
111
+
112
+ return this[kNapiObj].numberOfChannels;
113
+ }
114
+
115
+ copyFromChannel(destination, channelNumber, bufferOffset = 0) {
116
+ if (!(this instanceof AudioBuffer)) {
117
+ throw new TypeError("Invalid Invocation: Value of 'this' must be of type 'AudioBuffer'");
118
+ }
119
+
120
+ if (arguments.length < 2) {
121
+ throw new TypeError(`Failed to execute 'copyFromChannel' on 'AudioBuffer': 2 argument required, but only ${arguments.length} present`);
122
+ }
123
+
124
+ if (!(destination instanceof Float32Array)) {
125
+ throw new TypeError(`Failed to execute 'copyFromChannel' on 'AudioBuffer': parameter 1 is not of type 'Float32Array'`);
126
+ }
127
+
128
+ // Rust implementation uses a usize which will clamp -1 to 0, and spec requires
129
+ // an IndexSizeError rather than a TypeError, so this check must be done here.
130
+ // cf. note on AnalyzerNode::fftSize
131
+ if (channelNumber < 0) {
132
+ throw new DOMException(`Failed to execute 'copyFromChannel' on 'AudioBuffer': channelNumber must equal or greater than 0`, 'IndexSizeError');
133
+ }
134
+
135
+ channelNumber = conversions['unsigned long'](channelNumber, {
136
+ context: `Failed to execute 'copyFromChannel' on 'AudioBuffer': channelNumber`,
137
+ });
138
+
139
+ bufferOffset = conversions['unsigned long'](bufferOffset, {
140
+ context: `Failed to execute 'copyFromChannel' on 'AudioBuffer': bufferOffset`,
141
+ });
142
+
143
+ try {
144
+ this[kNapiObj].copyFromChannel(destination, channelNumber, bufferOffset);
145
+ } catch (err) {
146
+ throwSanitizedError(err);
147
+ }
148
+ }
149
+
150
+ copyToChannel(source, channelNumber, bufferOffset = 0) {
151
+ if (!(this instanceof AudioBuffer)) {
152
+ throw new TypeError("Invalid Invocation: Value of 'this' must be of type 'AudioBuffer'");
153
+ }
154
+
155
+ if (arguments.length < 2) {
156
+ throw new TypeError(`Failed to execute 'copyToChannel' on 'AudioBuffer': 2 argument required, but only ${arguments.length} present`);
157
+ }
158
+
159
+ if (!(source instanceof Float32Array)) {
160
+ throw new TypeError(`Failed to execute 'copyToChannel' on 'AudioBuffer': source is not of type 'Float32Array'`);
161
+ }
162
+
163
+ // Rust implementation uses a usize which will clamp -1 to 0, and spec requires
164
+ // an IndexSizeError rather than a TypeError, so this check must be done here.
165
+ // cf. note on AnalyzerNode::fftSize
166
+ if (channelNumber < 0) {
167
+ throw new DOMException(`Failed to execute 'copyToChannel' on 'AudioBuffer': channelNumber must equal or greater than 0`, 'IndexSizeError');
168
+ }
169
+
170
+ channelNumber = conversions['unsigned long'](channelNumber, {
171
+ context: `Failed to execute 'copyToChannel' on 'AudioBuffer': channelNumber`,
172
+ });
173
+
174
+ bufferOffset = conversions['unsigned long'](bufferOffset, {
175
+ context: `Failed to execute 'copyToChannel' on 'AudioBuffer': bufferOffset`,
176
+ });
177
+
178
+ try {
179
+ this[kNapiObj].copyToChannel(source, channelNumber, bufferOffset);
180
+ } catch (err) {
181
+ throwSanitizedError(err);
182
+ }
183
+ }
184
+
185
+ getChannelData(channel) {
186
+ if (!(this instanceof AudioBuffer)) {
187
+ throw new TypeError("Invalid Invocation: Value of 'this' must be of type 'AudioBuffer'");
188
+ }
189
+
190
+ if (arguments.length < 1) {
191
+ throw new TypeError(`Failed to execute 'getChannelData' on 'AudioBuffer': 1 argument required, but only ${arguments.length} present`);
192
+ }
193
+
194
+ // Rust implementation uses a usize which will clamp -1 to 0, and spec requires
195
+ // an IndexSizeError rather than a TypeError, so this check must be done here.
196
+ // cf. note on AnalyzerNode::fftSize
197
+ if (channel < 0) {
198
+ throw new DOMException(`Failed to execute 'getChannelData' on 'AudioBuffer': channel must equal or greater than 0`, 'IndexSizeError');
199
+ }
200
+
201
+ channel = conversions['unsigned long'](channel, {
202
+ context: `Failed to execute 'getChannelData' on 'AudioBuffer': channel`,
203
+ });
204
+
205
+ try {
206
+ return this[kNapiObj].getChannelData(channel);
207
+ } catch (err) {
208
+ throwSanitizedError(err);
209
+ }
210
+ }
211
+ }
212
+
213
+ Object.defineProperties(AudioBuffer, {
214
+ length: {
215
+ __proto__: null,
216
+ writable: false,
217
+ enumerable: false,
218
+ configurable: true,
219
+ value: 1,
220
+ },
221
+ });
222
+
223
+ Object.defineProperties(AudioBuffer.prototype, {
224
+ [Symbol.toStringTag]: {
225
+ __proto__: null,
226
+ writable: false,
227
+ enumerable: false,
228
+ configurable: true,
229
+ value: 'AudioBuffer',
230
+ },
231
+
232
+ sampleRate: kEnumerableProperty,
233
+ duration: kEnumerableProperty,
234
+ length: kEnumerableProperty,
235
+ numberOfChannels: kEnumerableProperty,
236
+ copyFromChannel: kEnumerableProperty,
237
+ copyToChannel: kEnumerableProperty,
238
+ getChannelData: kEnumerableProperty,
239
+ });
240
+
241
+ return AudioBuffer;
242
+ };
243
+
244
+
@@ -17,93 +17,317 @@
17
17
  // -------------------------------------------------------------------------- //
18
18
  // -------------------------------------------------------------------------- //
19
19
 
20
- // eslint-disable-next-line no-unused-vars
21
- const { throwSanitizedError } = require('./lib/errors.js');
22
- // eslint-disable-next-line no-unused-vars
23
- const { AudioParam } = require('./AudioParam.js');
24
- const EventTargetMixin = require('./EventTarget.mixin.js');
25
- const AudioNodeMixin = require('./AudioNode.mixin.js');
26
- const AudioScheduledSourceNodeMixin = require('./AudioScheduledSourceNode.mixin.js');
20
+ /* eslint-disable no-unused-vars */
21
+ const conversions = require('webidl-conversions');
22
+ const {
23
+ toSanitizedSequence,
24
+ } = require('./lib/cast.js');
25
+ const {
26
+ isFunction,
27
+ kEnumerableProperty,
28
+ } = require('./lib/utils.js');
29
+ const {
30
+ throwSanitizedError,
31
+ } = require('./lib/errors.js');
32
+ const {
33
+ kNapiObj,
34
+ kAudioBuffer,
35
+ } = require('./lib/symbols.js');
36
+ const {
37
+ bridgeEventTarget,
38
+ } = require('./lib/events.js');
39
+ /* eslint-enable no-unused-vars */
27
40
 
28
- module.exports = (NativeAudioBufferSourceNode) => {
29
-
30
- const EventTarget = EventTargetMixin(NativeAudioBufferSourceNode, ['ended']);
31
- const AudioNode = AudioNodeMixin(EventTarget);
32
- const AudioScheduledSourceNode = AudioScheduledSourceNodeMixin(AudioNode);
41
+ const AudioScheduledSourceNode = require('./AudioScheduledSourceNode.js');
33
42
 
43
+ module.exports = (jsExport, nativeBinding) => {
34
44
  class AudioBufferSourceNode extends AudioScheduledSourceNode {
45
+
46
+ #playbackRate = null;
47
+ #detune = null;
48
+
35
49
  constructor(context, options) {
36
- if (options !== undefined && typeof options !== 'object') {
37
- throw new TypeError("Failed to construct 'AudioBufferSourceNode': argument 2 is not of type 'AudioBufferSourceOptions'")
50
+
51
+ if (arguments.length < 1) {
52
+ throw new TypeError(`Failed to construct 'AudioBufferSourceNode': 1 argument required, but only ${arguments.length} present`);
38
53
  }
39
54
 
40
- super(context, options);
41
- // EventTargetMixin has been called so EventTargetMixin[kDispatchEvent] is
42
- // bound to this, then we can safely finalize event target initialization
43
- super.__initEventTarget__();
55
+ if (!(context instanceof jsExport.BaseAudioContext)) {
56
+ throw new TypeError(`Failed to construct 'AudioBufferSourceNode': argument 1 is not of type BaseAudioContext`);
57
+ }
44
58
 
45
- this.playbackRate = new AudioParam(this.playbackRate);
46
- this.detune = new AudioParam(this.detune);
47
- }
59
+ // parsed version of the option to be passed to NAPI
60
+ const parsedOptions = {};
48
61
 
49
- // getters
62
+ if (options && typeof options !== 'object') {
63
+ throw new TypeError('Failed to construct \'AudioBufferSourceNode\': argument 2 is not of type \'AudioBufferSourceOptions\'');
64
+ }
50
65
 
51
- get buffer() {
52
- return super.buffer;
53
- }
66
+ if (options && options.buffer !== undefined) {
67
+ if (options.buffer !== null) {
68
+ if (!(options.buffer instanceof jsExport.AudioBuffer)) {
69
+ throw new TypeError('Failed to construct \'AudioBufferSourceNode\': Failed to read the \'buffer\' property from AudioBufferSourceOptions: The provided value cannot be converted to \'AudioBuffer\'');
70
+ }
54
71
 
55
- get loop() {
56
- return super.loop;
72
+ // unwrap napi audio buffer
73
+ parsedOptions.buffer = options.buffer[kNapiObj];
74
+ } else {
75
+ parsedOptions.buffer = null;
76
+ }
77
+ } else {
78
+ parsedOptions.buffer = null;
79
+ }
80
+
81
+ if (options && options.detune !== undefined) {
82
+ parsedOptions.detune = conversions['float'](options.detune, {
83
+ context: `Failed to construct 'AudioBufferSourceNode': Failed to read the 'detune' property from AudioBufferSourceOptions: The provided value (${options.detune}})`,
84
+ });
85
+ } else {
86
+ parsedOptions.detune = 0;
87
+ }
88
+
89
+ if (options && options.loop !== undefined) {
90
+ parsedOptions.loop = conversions['boolean'](options.loop, {
91
+ context: `Failed to construct 'AudioBufferSourceNode': Failed to read the 'loop' property from AudioBufferSourceOptions: The provided value (${options.loop}})`,
92
+ });
93
+ } else {
94
+ parsedOptions.loop = false;
95
+ }
96
+
97
+ if (options && options.loopEnd !== undefined) {
98
+ parsedOptions.loopEnd = conversions['double'](options.loopEnd, {
99
+ context: `Failed to construct 'AudioBufferSourceNode': Failed to read the 'loopEnd' property from AudioBufferSourceOptions: The provided value (${options.loopEnd}})`,
100
+ });
101
+ } else {
102
+ parsedOptions.loopEnd = 0;
103
+ }
104
+
105
+ if (options && options.loopStart !== undefined) {
106
+ parsedOptions.loopStart = conversions['double'](options.loopStart, {
107
+ context: `Failed to construct 'AudioBufferSourceNode': Failed to read the 'loopStart' property from AudioBufferSourceOptions: The provided value (${options.loopStart}})`,
108
+ });
109
+ } else {
110
+ parsedOptions.loopStart = 0;
111
+ }
112
+
113
+ if (options && options.playbackRate !== undefined) {
114
+ parsedOptions.playbackRate = conversions['float'](options.playbackRate, {
115
+ context: `Failed to construct 'AudioBufferSourceNode': Failed to read the 'playbackRate' property from AudioBufferSourceOptions: The provided value (${options.playbackRate}})`,
116
+ });
117
+ } else {
118
+ parsedOptions.playbackRate = 1;
119
+ }
120
+
121
+ let napiObj;
122
+
123
+ try {
124
+ napiObj = new nativeBinding.AudioBufferSourceNode(context[kNapiObj], parsedOptions);
125
+ } catch (err) {
126
+ throwSanitizedError(err);
127
+ }
128
+
129
+ super(context, {
130
+ [kNapiObj]: napiObj,
131
+ });
132
+
133
+ // keep the wrapped AudioBuffer around
134
+ Object.defineProperty(this, kAudioBuffer, {
135
+ __proto__: null,
136
+ enumerable: false,
137
+ writable: true,
138
+ value: null,
139
+ });
140
+
141
+ if (options && options.buffer !== undefined) {
142
+ this[kAudioBuffer] = options.buffer;
143
+ }
144
+
145
+ // Bridge Rust native event to Node EventTarget
146
+ bridgeEventTarget(this);
147
+
148
+ this.#playbackRate = new jsExport.AudioParam({
149
+ [kNapiObj]: this[kNapiObj].playbackRate,
150
+ });
151
+ this.#detune = new jsExport.AudioParam({
152
+ [kNapiObj]: this[kNapiObj].detune,
153
+ });
57
154
  }
58
155
 
59
- get loopStart() {
60
- return super.loopStart;
156
+ get playbackRate() {
157
+ if (!(this instanceof AudioBufferSourceNode)) {
158
+ throw new TypeError('Invalid Invocation: Value of \'this\' must be of type \'AudioBufferSourceNode\'');
159
+ }
160
+
161
+ return this.#playbackRate;
61
162
  }
62
163
 
63
- get loopEnd() {
64
- return super.loopEnd;
164
+ get detune() {
165
+ if (!(this instanceof AudioBufferSourceNode)) {
166
+ throw new TypeError('Invalid Invocation: Value of \'this\' must be of type \'AudioBufferSourceNode\'');
167
+ }
168
+
169
+ return this.#detune;
65
170
  }
66
171
 
67
- // setters
172
+ get buffer() {
173
+ if (!(this instanceof AudioBufferSourceNode)) {
174
+ throw new TypeError('Invalid Invocation: Value of \'this\' must be of type \'AudioBufferSourceNode\'');
175
+ }
176
+
177
+ return this[kAudioBuffer];
178
+ }
68
179
 
69
180
  set buffer(value) {
181
+ if (!(this instanceof AudioBufferSourceNode)) {
182
+ throw new TypeError('Invalid Invocation: Value of \'this\' must be of type \'AudioBufferSourceNode\'');
183
+ }
184
+
185
+ if (value === null) {
186
+ console.warn('Setting the \'buffer\' property on \'AudioBufferSourceNode\' to \'null\' is not supported yet');
187
+ return;
188
+ } else if (!(kNapiObj in value)) {
189
+ throw new TypeError('Failed to set the \'buffer\' property on \'AudioBufferSourceNode\': Failed to convert value to \'AudioBuffer\'');
190
+ }
191
+
70
192
  try {
71
- super.buffer = value;
193
+ this[kNapiObj].buffer = value[kNapiObj];
72
194
  } catch (err) {
73
195
  throwSanitizedError(err);
74
196
  }
197
+
198
+ this[kAudioBuffer] = value;
199
+ }
200
+
201
+ get loop() {
202
+ if (!(this instanceof AudioBufferSourceNode)) {
203
+ throw new TypeError('Invalid Invocation: Value of \'this\' must be of type \'AudioBufferSourceNode\'');
204
+ }
205
+
206
+ return this[kNapiObj].loop;
75
207
  }
76
208
 
77
209
  set loop(value) {
210
+ if (!(this instanceof AudioBufferSourceNode)) {
211
+ throw new TypeError('Invalid Invocation: Value of \'this\' must be of type \'AudioBufferSourceNode\'');
212
+ }
213
+
214
+ value = conversions['boolean'](value, {
215
+ context: `Failed to set the 'loop' property on 'AudioBufferSourceNode': Value`,
216
+ });
217
+
78
218
  try {
79
- super.loop = value;
219
+ this[kNapiObj].loop = value;
80
220
  } catch (err) {
81
221
  throwSanitizedError(err);
82
222
  }
83
223
  }
84
224
 
225
+ get loopStart() {
226
+ if (!(this instanceof AudioBufferSourceNode)) {
227
+ throw new TypeError('Invalid Invocation: Value of \'this\' must be of type \'AudioBufferSourceNode\'');
228
+ }
229
+
230
+ return this[kNapiObj].loopStart;
231
+ }
232
+
85
233
  set loopStart(value) {
234
+ if (!(this instanceof AudioBufferSourceNode)) {
235
+ throw new TypeError('Invalid Invocation: Value of \'this\' must be of type \'AudioBufferSourceNode\'');
236
+ }
237
+
238
+ value = conversions['double'](value, {
239
+ context: `Failed to set the 'loopStart' property on 'AudioBufferSourceNode': Value`,
240
+ });
241
+
86
242
  try {
87
- super.loopStart = value;
243
+ this[kNapiObj].loopStart = value;
88
244
  } catch (err) {
89
245
  throwSanitizedError(err);
90
246
  }
91
247
  }
92
248
 
249
+ get loopEnd() {
250
+ if (!(this instanceof AudioBufferSourceNode)) {
251
+ throw new TypeError('Invalid Invocation: Value of \'this\' must be of type \'AudioBufferSourceNode\'');
252
+ }
253
+
254
+ return this[kNapiObj].loopEnd;
255
+ }
256
+
93
257
  set loopEnd(value) {
258
+ if (!(this instanceof AudioBufferSourceNode)) {
259
+ throw new TypeError('Invalid Invocation: Value of \'this\' must be of type \'AudioBufferSourceNode\'');
260
+ }
261
+
262
+ value = conversions['double'](value, {
263
+ context: `Failed to set the 'loopEnd' property on 'AudioBufferSourceNode': Value`,
264
+ });
265
+
266
+ try {
267
+ this[kNapiObj].loopEnd = value;
268
+ } catch (err) {
269
+ throwSanitizedError(err);
270
+ }
271
+ }
272
+
273
+ start(when = 0, offset = null, duration = null) {
274
+ if (!(this instanceof AudioBufferSourceNode)) {
275
+ throw new TypeError('Invalid Invocation: Value of \'this\' must be of type \'AudioBufferSourceNode\'');
276
+ }
277
+
278
+ if (when !== 0) {
279
+ when = conversions['double'](when, {
280
+ context: `Failed to execute 'start' on 'AudioBufferSourceNode': Parameter 1`,
281
+ });
282
+ }
283
+
284
+ if (offset !== null) {
285
+ offset = conversions['double'](offset, {
286
+ context: `Failed to execute 'start' on 'AudioBufferSourceNode': Parameter 2`,
287
+ });
288
+ }
289
+
290
+ if (duration !== null) {
291
+ duration = conversions['double'](duration, {
292
+ context: `Failed to execute 'start' on 'AudioBufferSourceNode': Parameter 3`,
293
+ });
294
+ }
295
+
94
296
  try {
95
- super.loopEnd = value;
297
+ return this[kNapiObj].start(when, offset, duration);
96
298
  } catch (err) {
97
299
  throwSanitizedError(err);
98
300
  }
99
301
  }
100
302
 
101
- // methods
102
-
103
303
  }
104
304
 
105
- return AudioBufferSourceNode;
106
- };
305
+ Object.defineProperties(AudioBufferSourceNode, {
306
+ length: {
307
+ __proto__: null,
308
+ writable: false,
309
+ enumerable: false,
310
+ configurable: true,
311
+ value: 1,
312
+ },
313
+ });
107
314
 
315
+ Object.defineProperties(AudioBufferSourceNode.prototype, {
316
+ [Symbol.toStringTag]: {
317
+ __proto__: null,
318
+ writable: false,
319
+ enumerable: false,
320
+ configurable: true,
321
+ value: 'AudioBufferSourceNode',
322
+ },
323
+ playbackRate: kEnumerableProperty,
324
+ detune: kEnumerableProperty,
325
+ buffer: kEnumerableProperty,
326
+ loop: kEnumerableProperty,
327
+ loopStart: kEnumerableProperty,
328
+ loopEnd: kEnumerableProperty,
329
+ start: kEnumerableProperty,
330
+ });
108
331
 
109
-
332
+ return AudioBufferSourceNode;
333
+ };