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
@@ -1,16 +1,80 @@
1
+ const conversions = require('webidl-conversions');
2
+
1
3
  const { throwSanitizedError } = require('./lib/errors.js');
4
+ const { toSanitizedSequence } = require('./lib/cast.js');
5
+ const { kNapiObj } = require('./lib/symbols.js');
6
+ const { kHiddenProperty } = require('./lib/utils.js');
2
7
 
3
- module.exports = (NativePeriodicWave) => {
4
- class PeriodicWave extends NativePeriodicWave {
8
+ module.exports = (jsExport, nativeBinding) => {
9
+ class PeriodicWave {
5
10
  constructor(context, options) {
11
+ if (arguments.length < 1) {
12
+ throw new TypeError(`Failed to construct 'PeriodicWave': 1 argument required, but only ${arguments.length} present`);
13
+ }
14
+
15
+ if (!(context instanceof jsExport.BaseAudioContext)) {
16
+ throw new TypeError(`Failed to construct 'PeriodicWave': argument 1 is not of type BaseAudioContext`);
17
+ }
18
+
19
+ const parsedOptions = {};
20
+
21
+ if (options && 'real' in options) {
22
+ try {
23
+ parsedOptions.real = toSanitizedSequence(options.real, Float32Array);
24
+ } catch (err) {
25
+ throw new TypeError(`Failed to construct 'PeriodicWave': Failed to read the 'real' property from PeriodicWaveOptions: The provided value ${err.message}`);
26
+ }
27
+ }
28
+
29
+ if (options && 'imag' in options) {
30
+ try {
31
+ parsedOptions.imag = toSanitizedSequence(options.imag, Float32Array);
32
+ } catch (err) {
33
+ throw new TypeError(`Failed to construct 'PeriodicWave': Failed to read the 'imag' property from PeriodicWaveOptions: The provided value ${err.message}`);
34
+ }
35
+ }
36
+
37
+ // disableNormalization = false
38
+ if (options && 'disableNormalization' in options) {
39
+ parsedOptions.disableNormalization = conversions['boolean'](options.disableNormalization, {
40
+ context: `Failed to construct 'PeriodicWave': Failed to read the 'imag' property from PeriodicWaveOptions: The provided value`,
41
+ });
42
+ } else {
43
+ parsedOptions.disableNormalization;
44
+ }
45
+
6
46
  try {
7
- super(context, options);
47
+ const napiObj = new nativeBinding.PeriodicWave(context[kNapiObj], parsedOptions);
48
+ Object.defineProperty(this, kNapiObj, {
49
+ value: napiObj,
50
+ ...kHiddenProperty,
51
+ });
8
52
  } catch (err) {
9
53
  throwSanitizedError(err);
10
54
  }
11
55
  }
12
56
  }
13
57
 
58
+ Object.defineProperties(PeriodicWave, {
59
+ length: {
60
+ __proto__: null,
61
+ writable: false,
62
+ enumerable: false,
63
+ configurable: true,
64
+ value: 1,
65
+ },
66
+ });
67
+
68
+ Object.defineProperties(PeriodicWave.prototype, {
69
+ [Symbol.toStringTag]: {
70
+ __proto__: null,
71
+ writable: false,
72
+ enumerable: false,
73
+ configurable: true,
74
+ value: 'PeriodicWave',
75
+ },
76
+ });
77
+
14
78
  return PeriodicWave;
15
79
  };
16
80
 
@@ -17,40 +17,126 @@
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');
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 */
26
40
 
41
+ const AudioNode = require('./AudioNode.js');
27
42
 
28
- module.exports = (NativeStereoPannerNode) => {
43
+ module.exports = (jsExport, nativeBinding) => {
44
+ class StereoPannerNode extends AudioNode {
29
45
 
30
- const EventTarget = EventTargetMixin(NativeStereoPannerNode);
31
- const AudioNode = AudioNodeMixin(EventTarget);
46
+ #pan = null;
32
47
 
33
- class StereoPannerNode extends AudioNode {
34
48
  constructor(context, options) {
35
- if (options !== undefined && typeof options !== 'object') {
36
- throw new TypeError("Failed to construct 'StereoPannerNode': argument 2 is not of type 'StereoPannerOptions'")
49
+
50
+ if (arguments.length < 1) {
51
+ throw new TypeError(`Failed to construct 'StereoPannerNode': 1 argument required, but only ${arguments.length} present`);
52
+ }
53
+
54
+ if (!(context instanceof jsExport.BaseAudioContext)) {
55
+ throw new TypeError(`Failed to construct 'StereoPannerNode': argument 1 is not of type BaseAudioContext`);
56
+ }
57
+
58
+ // parsed version of the option to be passed to NAPI
59
+ const parsedOptions = {};
60
+
61
+ if (options && typeof options !== 'object') {
62
+ throw new TypeError('Failed to construct \'StereoPannerNode\': argument 2 is not of type \'StereoPannerOptions\'');
63
+ }
64
+
65
+ if (options && options.pan !== undefined) {
66
+ parsedOptions.pan = conversions['float'](options.pan, {
67
+ context: `Failed to construct 'StereoPannerNode': Failed to read the 'pan' property from StereoPannerOptions: The provided value (${options.pan}})`,
68
+ });
69
+ } else {
70
+ parsedOptions.pan = 0;
71
+ }
72
+
73
+ if (options && options.channelCount !== undefined) {
74
+ parsedOptions.channelCount = conversions['unsigned long'](options.channelCount, {
75
+ enforceRange: true,
76
+ context: `Failed to construct 'StereoPannerNode': Failed to read the 'channelCount' property from StereoPannerOptions: The provided value '${options.channelCount}'`,
77
+ });
78
+ }
79
+
80
+ if (options && options.channelCountMode !== undefined) {
81
+ parsedOptions.channelCountMode = conversions['DOMString'](options.channelCountMode, {
82
+ context: `Failed to construct 'StereoPannerNode': Failed to read the 'channelCount' property from StereoPannerOptions: The provided value '${options.channelCountMode}'`,
83
+ });
84
+ }
85
+
86
+ if (options && options.channelInterpretation !== undefined) {
87
+ parsedOptions.channelInterpretation = conversions['DOMString'](options.channelInterpretation, {
88
+ context: `Failed to construct 'StereoPannerNode': Failed to read the 'channelInterpretation' property from StereoPannerOptions: The provided value '${options.channelInterpretation}'`,
89
+ });
37
90
  }
38
91
 
39
- super(context, options);
92
+ let napiObj;
40
93
 
41
- this.pan = new AudioParam(this.pan);
94
+ try {
95
+ napiObj = new nativeBinding.StereoPannerNode(context[kNapiObj], parsedOptions);
96
+ } catch (err) {
97
+ throwSanitizedError(err);
98
+ }
99
+
100
+ super(context, {
101
+ [kNapiObj]: napiObj,
102
+ });
103
+
104
+ this.#pan = new jsExport.AudioParam({
105
+ [kNapiObj]: this[kNapiObj].pan,
106
+ });
42
107
  }
43
108
 
44
- // getters
109
+ get pan() {
110
+ if (!(this instanceof StereoPannerNode)) {
111
+ throw new TypeError('Invalid Invocation: Value of \'this\' must be of type \'StereoPannerNode\'');
112
+ }
45
113
 
46
- // setters
114
+ return this.#pan;
115
+ }
47
116
 
48
- // methods
49
-
50
117
  }
51
118
 
52
- return StereoPannerNode;
53
- };
119
+ Object.defineProperties(StereoPannerNode, {
120
+ length: {
121
+ __proto__: null,
122
+ writable: false,
123
+ enumerable: false,
124
+ configurable: true,
125
+ value: 1,
126
+ },
127
+ });
54
128
 
129
+ Object.defineProperties(StereoPannerNode.prototype, {
130
+ [Symbol.toStringTag]: {
131
+ __proto__: null,
132
+ writable: false,
133
+ enumerable: false,
134
+ configurable: true,
135
+ value: 'StereoPannerNode',
136
+ },
137
+ pan: kEnumerableProperty,
55
138
 
56
-
139
+ });
140
+
141
+ return StereoPannerNode;
142
+ };
@@ -17,63 +17,181 @@
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');
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 */
40
+
41
+ const AudioNode = require('./AudioNode.js');
42
+
43
+ module.exports = (jsExport, nativeBinding) => {
44
+ class WaveShaperNode extends AudioNode {
26
45
 
46
+ constructor(context, options) {
27
47
 
28
- module.exports = (NativeWaveShaperNode) => {
48
+ if (arguments.length < 1) {
49
+ throw new TypeError(`Failed to construct 'WaveShaperNode': 1 argument required, but only ${arguments.length} present`);
50
+ }
29
51
 
30
- const EventTarget = EventTargetMixin(NativeWaveShaperNode);
31
- const AudioNode = AudioNodeMixin(EventTarget);
52
+ if (!(context instanceof jsExport.BaseAudioContext)) {
53
+ throw new TypeError(`Failed to construct 'WaveShaperNode': argument 1 is not of type BaseAudioContext`);
54
+ }
32
55
 
33
- class WaveShaperNode extends AudioNode {
34
- constructor(context, options) {
35
- if (options !== undefined && typeof options !== 'object') {
36
- throw new TypeError("Failed to construct 'WaveShaperNode': argument 2 is not of type 'WaveShaperOptions'")
56
+ // parsed version of the option to be passed to NAPI
57
+ const parsedOptions = {};
58
+
59
+ if (options && typeof options !== 'object') {
60
+ throw new TypeError('Failed to construct \'WaveShaperNode\': argument 2 is not of type \'WaveShaperOptions\'');
37
61
  }
38
62
 
39
- super(context, options);
63
+ if (options && options.curve !== undefined) {
64
+ try {
65
+ parsedOptions.curve = toSanitizedSequence(options.curve, Float32Array);
66
+ } catch (err) {
67
+ throw new TypeError(`Failed to construct 'WaveShaperNode': Failed to read the 'curve' property from WaveShaperOptions: The provided value ${err.message}`);
68
+ }
69
+ } else {
70
+ parsedOptions.curve = null;
71
+ }
40
72
 
41
- }
73
+ if (options && options.oversample !== undefined) {
74
+ if (!['none', '2x', '4x'].includes(options.oversample)) {
75
+ throw new TypeError(`Failed to construct 'WaveShaperNode': Failed to read the 'oversample' property from WaveShaperOptions: The provided value '${options.oversample}' is not a valid enum value of type OverSampleType`);
76
+ }
42
77
 
43
- // getters
78
+ parsedOptions.oversample = conversions['DOMString'](options.oversample, {
79
+ context: `Failed to construct 'WaveShaperNode': Failed to read the 'oversample' property from WaveShaperOptions: The provided value '${options.oversample}'`,
80
+ });
81
+ } else {
82
+ parsedOptions.oversample = 'none';
83
+ }
44
84
 
45
- get curve() {
46
- return super.curve;
47
- }
85
+ if (options && options.channelCount !== undefined) {
86
+ parsedOptions.channelCount = conversions['unsigned long'](options.channelCount, {
87
+ enforceRange: true,
88
+ context: `Failed to construct 'WaveShaperNode': Failed to read the 'channelCount' property from WaveShaperOptions: The provided value '${options.channelCount}'`,
89
+ });
90
+ }
91
+
92
+ if (options && options.channelCountMode !== undefined) {
93
+ parsedOptions.channelCountMode = conversions['DOMString'](options.channelCountMode, {
94
+ context: `Failed to construct 'WaveShaperNode': Failed to read the 'channelCount' property from WaveShaperOptions: The provided value '${options.channelCountMode}'`,
95
+ });
96
+ }
97
+
98
+ if (options && options.channelInterpretation !== undefined) {
99
+ parsedOptions.channelInterpretation = conversions['DOMString'](options.channelInterpretation, {
100
+ context: `Failed to construct 'WaveShaperNode': Failed to read the 'channelInterpretation' property from WaveShaperOptions: The provided value '${options.channelInterpretation}'`,
101
+ });
102
+ }
103
+
104
+ let napiObj;
105
+
106
+ try {
107
+ napiObj = new nativeBinding.WaveShaperNode(context[kNapiObj], parsedOptions);
108
+ } catch (err) {
109
+ throwSanitizedError(err);
110
+ }
111
+
112
+ super(context, {
113
+ [kNapiObj]: napiObj,
114
+ });
48
115
 
49
- get oversample() {
50
- return super.oversample;
51
116
  }
52
117
 
53
- // setters
118
+ get curve() {
119
+ if (!(this instanceof WaveShaperNode)) {
120
+ throw new TypeError('Invalid Invocation: Value of \'this\' must be of type \'WaveShaperNode\'');
121
+ }
122
+
123
+ return this[kNapiObj].curve;
124
+ }
54
125
 
55
126
  set curve(value) {
127
+ if (!(this instanceof WaveShaperNode)) {
128
+ throw new TypeError('Invalid Invocation: Value of \'this\' must be of type \'WaveShaperNode\'');
129
+ }
130
+
131
+ if (value === null) {
132
+ console.warn('Setting the \'curve\' property on \'WaveShaperNode\' to \'null\' is not supported yet');
133
+ return;
134
+ } else if (!(value instanceof Float32Array)) {
135
+ throw new TypeError('Failed to set the \'curve\' property on \'WaveShaperNode\': Value is not a valid \'Float32Array\' value');
136
+ }
137
+
56
138
  try {
57
- super.curve = value;
139
+ this[kNapiObj].curve = value;
58
140
  } catch (err) {
59
141
  throwSanitizedError(err);
60
142
  }
61
143
  }
62
144
 
145
+ get oversample() {
146
+ if (!(this instanceof WaveShaperNode)) {
147
+ throw new TypeError('Invalid Invocation: Value of \'this\' must be of type \'WaveShaperNode\'');
148
+ }
149
+
150
+ return this[kNapiObj].oversample;
151
+ }
152
+
63
153
  set oversample(value) {
154
+ if (!(this instanceof WaveShaperNode)) {
155
+ throw new TypeError('Invalid Invocation: Value of \'this\' must be of type \'WaveShaperNode\'');
156
+ }
157
+
158
+ if (!['none', '2x', '4x'].includes(value)) {
159
+ console.warn(`Failed to set the 'oversample' property on 'WaveShaperNode': Value '${value}' is not a valid 'OverSampleType' enum value`);
160
+ return;
161
+ }
162
+
64
163
  try {
65
- super.oversample = value;
164
+ this[kNapiObj].oversample = value;
66
165
  } catch (err) {
67
166
  throwSanitizedError(err);
68
167
  }
69
168
  }
70
169
 
71
- // methods
72
-
73
170
  }
74
171
 
172
+ Object.defineProperties(WaveShaperNode, {
173
+ length: {
174
+ __proto__: null,
175
+ writable: false,
176
+ enumerable: false,
177
+ configurable: true,
178
+ value: 1,
179
+ },
180
+ });
181
+
182
+ Object.defineProperties(WaveShaperNode.prototype, {
183
+ [Symbol.toStringTag]: {
184
+ __proto__: null,
185
+ writable: false,
186
+ enumerable: false,
187
+ configurable: true,
188
+ value: 'WaveShaperNode',
189
+ },
190
+
191
+ curve: kEnumerableProperty,
192
+ oversample: kEnumerableProperty,
193
+
194
+ });
195
+
75
196
  return WaveShaperNode;
76
197
  };
77
-
78
-
79
-
package/js/lib/cast.js ADDED
@@ -0,0 +1,19 @@
1
+ exports.toSanitizedSequence = function toSanitizedSequence(data, targetCtor) {
2
+ if (
3
+ (data.buffer && data.buffer instanceof ArrayBuffer)
4
+ || Array.isArray(data)
5
+ ) {
6
+ data = new targetCtor(data);
7
+ } else {
8
+ throw new TypeError(`cannot be converted to sequence of ${targetCtor}`);
9
+ }
10
+
11
+ // check it only contains finite values
12
+ for (let i = 0; i < data.length; i++) {
13
+ if (!Number.isFinite(data[i])) {
14
+ throw new TypeError(`should contain only finite values`);
15
+ }
16
+ }
17
+
18
+ return data;
19
+ }
package/js/lib/errors.js CHANGED
@@ -4,58 +4,6 @@ const path = require('path');
4
4
  const internalPath = path.join('node-web-audio-api', 'js');
5
5
  const internalRe = new RegExp(internalPath);
6
6
 
7
- // from wpt/resources/tesharness.js (line 2226)
8
- const nameCodeMap = {
9
- IndexSizeError: 1,
10
- HierarchyRequestError: 3,
11
- WrongDocumentError: 4,
12
- InvalidCharacterError: 5,
13
- NoModificationAllowedError: 7,
14
- NotFoundError: 8,
15
- NotSupportedError: 9,
16
- InUseAttributeError: 10,
17
- InvalidStateError: 11,
18
- SyntaxError: 12,
19
- InvalidModificationError: 13,
20
- NamespaceError: 14,
21
- InvalidAccessError: 15,
22
- TypeMismatchError: 17,
23
- SecurityError: 18,
24
- NetworkError: 19,
25
- AbortError: 20,
26
- URLMismatchError: 21,
27
- QuotaExceededError: 22,
28
- TimeoutError: 23,
29
- InvalidNodeTypeError: 24,
30
- DataCloneError: 25,
31
-
32
- EncodingError: 0,
33
- NotReadableError: 0,
34
- UnknownError: 0,
35
- ConstraintError: 0,
36
- DataError: 0,
37
- TransactionInactiveError: 0,
38
- ReadOnlyError: 0,
39
- VersionError: 0,
40
- OperationError: 0,
41
- NotAllowedError: 0,
42
- OptOutError: 0
43
- };
44
-
45
- exports.nameCodeMap = nameCodeMap;
46
-
47
-
48
- class DOMException extends Error {
49
- constructor(message, name) {
50
- super(message);
51
-
52
- this.name = name;
53
- this.code = nameCodeMap[this.name];
54
- }
55
- }
56
-
57
- exports.DOMException = DOMException;
58
-
59
7
  function overrideStack(originalError, newError) {
60
8
  // override previous error message
61
9
  const stack = originalError.stack.replace(originalError.message, newError.message);
@@ -87,6 +35,7 @@ exports.throwSanitizedError = function throwSanitizedError(err) {
87
35
  if (originalMessage.startsWith('TypeError')) {
88
36
  const msg = originalMessage.replace(/^TypeError - /, '');
89
37
  const error = new TypeError(msg);
38
+ overrideStack(err, error);
90
39
 
91
40
  throw error;
92
41
  } else if (originalMessage.startsWith('RangeError')) {
@@ -110,20 +59,26 @@ exports.throwSanitizedError = function throwSanitizedError(err) {
110
59
  overrideStack(err, error);
111
60
 
112
61
  throw error;
113
- } if (originalMessage.startsWith('IndexSizeError')) {
62
+ } else if (originalMessage.startsWith('IndexSizeError')) {
114
63
  const msg = originalMessage.replace(/^IndexSizeError - /, '');
115
64
  const error = new DOMException(msg, 'IndexSizeError');
116
65
  overrideStack(err, error);
117
66
 
118
67
  throw error;
119
- } if (originalMessage.startsWith('InvalidAccessError')) {
68
+ } else if (originalMessage.startsWith('InvalidAccessError')) {
120
69
  const msg = originalMessage.replace(/^InvalidAccessError - /, '');
121
70
  const error = new DOMException(msg, 'InvalidAccessError');
122
71
  overrideStack(err, error);
123
72
 
73
+ throw error;
74
+ } else if (originalMessage.startsWith('NotFoundError')) {
75
+ const msg = originalMessage.replace(/^NotFoundError - /, '');
76
+ const error = new DOMException(msg, 'NotFoundError');
77
+ overrideStack(err, error);
78
+
124
79
  throw error;
125
80
  }
126
81
 
127
- console.warn('[lib/errors.js] Possibly unhandled error type', err.message);
82
+ console.warn('[lib/errors.js] Unexpected Rust error', err);
128
83
  throw err;
129
84
  }
@@ -0,0 +1,20 @@
1
+ const { kNapiObj, kDispatchEvent } = require('./symbols.js');
2
+ const { isFunction } = require('./utils.js');
3
+
4
+ /**
5
+ * Listen for events from Rust, and bridge them to Node EventTarget paradigm
6
+ */
7
+ module.exports.bridgeEventTarget = function bridgeEventTarget(jsObj) {
8
+ // Finalize event registration on Rust side
9
+ jsObj[kNapiObj][kDispatchEvent] = (err, eventType) => {
10
+ const event = new Event(eventType);
11
+ // call attribute first if exists
12
+ if (isFunction(jsObj[`on${event.type}`])) {
13
+ jsObj[`on${event.type}`](event);
14
+ }
15
+ // then distach to add event listeners
16
+ jsObj.dispatchEvent(event);
17
+ }
18
+ // ask Rust to register `kDispatchEvent` as listener
19
+ jsObj[kNapiObj].__initEventTarget__();
20
+ }
@@ -0,0 +1,5 @@
1
+ module.exports.kNapiObj = Symbol('node-web-audio-api:napi-obj');
2
+ module.exports.kAudioBuffer = Symbol('node-web-audio-api:audio-buffer');
3
+ // this needs to be shared with Rust ide
4
+ module.exports.kDispatchEvent = Symbol.for('node-web-audio-api:napi-dispatch-event');
5
+
package/js/lib/utils.js CHANGED
@@ -1,16 +1,16 @@
1
- exports.isPlainObject = function isPlainObject(obj) {
2
- return Object.prototype.toString.call(obj) === '[object Object]';
3
- };
4
-
5
- exports.isPositiveInt = function isPositiveInt(n) {
6
- return Number.isSafeInteger(n) && 0 < n;
7
- };
8
-
9
- exports.isPositiveNumber = function isPositiveNumber(n) {
10
- return Number(n) === n && 0 < n;
11
- };
12
-
13
1
  exports.isFunction = function isFunction(val) {
14
2
  return Object.prototype.toString.call(val) == '[object Function]' ||
15
3
  Object.prototype.toString.call(val) == '[object AsyncFunction]';
16
4
  };
5
+
6
+ const kEnumerableProperty = { __proto__: null };
7
+ kEnumerableProperty.enumerable = true;
8
+ Object.freeze(kEnumerableProperty);
9
+
10
+ exports.kEnumerableProperty = kEnumerableProperty;
11
+
12
+ const kHiddenProperty = { __proto__: null };
13
+ kHiddenProperty.enumerable = false;
14
+ Object.freeze(kHiddenProperty);
15
+
16
+ exports.kHiddenProperty = kHiddenProperty;