node-web-audio-api 0.14.0 → 0.15.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 (39) hide show
  1. package/CHANGELOG.md +7 -0
  2. package/LICENSE +1 -1
  3. package/README.md +35 -17
  4. package/index.cjs +1 -1
  5. package/index.mjs +2 -1
  6. package/js/AnalyserNode.js +135 -0
  7. package/js/AudioBufferSourceNode.js +105 -0
  8. package/js/AudioContext.js +60 -0
  9. package/js/AudioNode.mixin.js +121 -0
  10. package/js/AudioParam.js +132 -0
  11. package/js/AudioScheduledSourceNode.mixin.js +67 -0
  12. package/js/BaseAudioContext.mixin.js +140 -0
  13. package/js/BiquadFilterNode.js +75 -0
  14. package/js/ChannelMergerNode.js +51 -0
  15. package/js/ChannelSplitterNode.js +51 -0
  16. package/js/ConstantSourceNode.js +56 -0
  17. package/js/ConvolverNode.js +75 -0
  18. package/js/DelayNode.js +52 -0
  19. package/js/DynamicsCompressorNode.js +60 -0
  20. package/js/EventTarget.mixin.js +59 -0
  21. package/js/GainNode.js +52 -0
  22. package/js/IIRFilterNode.js +59 -0
  23. package/js/OfflineAudioContext.js +45 -0
  24. package/js/OscillatorNode.js +77 -0
  25. package/js/PannerNode.js +169 -0
  26. package/js/StereoPannerNode.js +52 -0
  27. package/js/WaveShaperNode.js +75 -0
  28. package/js/lib/errors.js +108 -0
  29. package/js/lib/utils.js +16 -0
  30. package/js/monkey-patch.js +67 -0
  31. package/node-web-audio-api.darwin-arm64.node +0 -0
  32. package/node-web-audio-api.darwin-x64.node +0 -0
  33. package/node-web-audio-api.linux-arm-gnueabihf.node +0 -0
  34. package/node-web-audio-api.linux-arm64-gnu.node +0 -0
  35. package/node-web-audio-api.linux-x64-gnu.node +0 -0
  36. package/node-web-audio-api.win32-arm64-msvc.node +0 -0
  37. package/node-web-audio-api.win32-x64-msvc.node +0 -0
  38. package/package.json +10 -6
  39. package/monkey-patch.js +0 -184
@@ -0,0 +1,75 @@
1
+ // -------------------------------------------------------------------------- //
2
+ // -------------------------------------------------------------------------- //
3
+ // //
4
+ // //
5
+ // //
6
+ // ██╗ ██╗ █████╗ ██████╗ ███╗ ██╗██╗███╗ ██╗ ██████╗ //
7
+ // ██║ ██║██╔══██╗██╔══██╗████╗ ██║██║████╗ ██║██╔════╝ //
8
+ // ██║ █╗ ██║███████║██████╔╝██╔██╗ ██║██║██╔██╗ ██║██║ ███╗ //
9
+ // ██║███╗██║██╔══██║██╔══██╗██║╚██╗██║██║██║╚██╗██║██║ ██║ //
10
+ // ╚███╔███╔╝██║ ██║██║ ██║██║ ╚████║██║██║ ╚████║╚██████╔╝ //
11
+ // ╚══╝╚══╝ ╚═╝ ╚═╝╚═╝ ╚═╝╚═╝ ╚═══╝╚═╝╚═╝ ╚═══╝ ╚═════╝ //
12
+ // //
13
+ // //
14
+ // - This file has been generated --------------------------- //
15
+ // //
16
+ // //
17
+ // -------------------------------------------------------------------------- //
18
+ // -------------------------------------------------------------------------- //
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
+
27
+
28
+ module.exports = (NativeWaveShaperNode) => {
29
+
30
+ const EventTarget = EventTargetMixin(NativeWaveShaperNode);
31
+ const AudioNode = AudioNodeMixin(EventTarget);
32
+
33
+ class WaveShaperNode extends AudioNode {
34
+ constructor(context, options) {
35
+ super(context, options);
36
+
37
+ }
38
+
39
+ // getters
40
+
41
+ get curve() {
42
+ return super.curve;
43
+ }
44
+
45
+ get oversample() {
46
+ return super.oversample;
47
+ }
48
+
49
+ // setters
50
+
51
+ set curve(value) {
52
+ try {
53
+ super.curve = value;
54
+ } catch (err) {
55
+ throwSanitizedError(err);
56
+ }
57
+ }
58
+
59
+ set oversample(value) {
60
+ try {
61
+ super.oversample = value;
62
+ } catch (err) {
63
+ throwSanitizedError(err);
64
+ }
65
+ }
66
+
67
+ // methods
68
+
69
+ }
70
+
71
+ return WaveShaperNode;
72
+ };
73
+
74
+
75
+
@@ -0,0 +1,108 @@
1
+ const { EOL } = require('os');
2
+ const path = require('path');
3
+
4
+ const internalPath = path.join('node-web-audio-api', 'js');
5
+ const internalRe = new RegExp(internalPath);
6
+
7
+ class NotSupportedError extends Error {
8
+ constructor(message) {
9
+ super(message);
10
+ this.name = 'NotSupportedError';
11
+ }
12
+ }
13
+
14
+ class InvalidStateError extends Error {
15
+ constructor(message) {
16
+ super(message);
17
+ this.name = 'InvalidStateError';
18
+ }
19
+ }
20
+
21
+ class IndexSizeError extends Error {
22
+ constructor(message) {
23
+ super(message);
24
+ this.name = 'IndexSizeError';
25
+ }
26
+ }
27
+
28
+ class InvalidAccessError extends Error {
29
+ constructor(message) {
30
+ super(message);
31
+ this.name = 'InvalidAccessError';
32
+ }
33
+ }
34
+
35
+ exports.NotSupportedError = NotSupportedError;
36
+ exports.InvalidStateError = InvalidStateError;
37
+ exports.IndexSizeError = IndexSizeError;
38
+
39
+ function overrideStack(originalError, newError) {
40
+ // override previous error message
41
+ const stack = originalError.stack.replace(originalError.message, newError.message);
42
+ const lines = stack.split(EOL);
43
+
44
+ // remove all lines that refer to internal classes, i.e. contains `node-web-audio-api/js`
45
+ for (let i = lines.length - 1; i > 0; i--) {
46
+ const line = lines[i];
47
+ if (internalRe.test(line)) {
48
+ lines.splice(i, 1);
49
+ }
50
+ }
51
+
52
+ // override new stack with modified one
53
+ newError.stack = lines.join(EOL);
54
+ }
55
+
56
+ exports.throwSanitizedError = function throwSanitizedError(err) {
57
+ // We also need to handle output of `assert_ne!` as well, e.g.
58
+ // assertion `left != right` failed: NotSupportedError - StereoPannerNode channel count mode cannot be set to max
59
+ // left: Max
60
+ // right: Max
61
+ let originalMessage = err.message;
62
+ originalMessage = originalMessage.replace('assertion `left != right` failed: ', '');
63
+ originalMessage = originalMessage.split(EOL)[0]; // keep only first line
64
+
65
+ // "Native Errors"
66
+ if (originalMessage.startsWith('TypeError')) {
67
+ const msg = originalMessage.replace(/^TypeError - /, '');
68
+ const error = new TypeError(msg);
69
+
70
+ throw error;
71
+ } else if (originalMessage.startsWith('RangeError')) {
72
+ const msg = originalMessage.replace(/^RangeError - /, '');
73
+ const error = new RangeError(msg);
74
+ overrideStack(err, error);
75
+
76
+ throw error;
77
+ }
78
+
79
+ // "other errors"
80
+ if (originalMessage.startsWith('NotSupportedError')) {
81
+ const msg = originalMessage.replace(/^NotSupportedError - /, '');
82
+ const error = new NotSupportedError(msg);
83
+ overrideStack(err, error);
84
+
85
+ throw error;
86
+ } else if (originalMessage.startsWith('InvalidStateError')) {
87
+ const msg = originalMessage.replace(/^InvalidStateError - /, '');
88
+ const error = new InvalidStateError(msg);
89
+ overrideStack(err, error);
90
+
91
+ throw error;
92
+ } if (originalMessage.startsWith('IndexSizeError')) {
93
+ const msg = originalMessage.replace(/^IndexSizeError - /, '');
94
+ const error = new IndexSizeError(msg);
95
+ overrideStack(err, error);
96
+
97
+ throw error;
98
+ } if (originalMessage.startsWith('InvalidAccessError')) {
99
+ const msg = originalMessage.replace(/^InvalidAccessError - /, '');
100
+ const error = new InvalidAccessError(msg);
101
+ overrideStack(err, error);
102
+
103
+ throw error;
104
+ }
105
+
106
+ console.warn('[lib/errors.js] Unhandled error type', err.name, err.message);
107
+ throw err;
108
+ }
@@ -0,0 +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
+ exports.isFunction = function isFunction(val) {
14
+ return Object.prototype.toString.call(val) == '[object Function]' ||
15
+ Object.prototype.toString.call(val) == '[object AsyncFunction]';
16
+ };
@@ -0,0 +1,67 @@
1
+ // -------------------------------------------------------------------------- //
2
+ // -------------------------------------------------------------------------- //
3
+ // //
4
+ // //
5
+ // //
6
+ // ██╗ ██╗ █████╗ ██████╗ ███╗ ██╗██╗███╗ ██╗ ██████╗ //
7
+ // ██║ ██║██╔══██╗██╔══██╗████╗ ██║██║████╗ ██║██╔════╝ //
8
+ // ██║ █╗ ██║███████║██████╔╝██╔██╗ ██║██║██╔██╗ ██║██║ ███╗ //
9
+ // ██║███╗██║██╔══██║██╔══██╗██║╚██╗██║██║██║╚██╗██║██║ ██║ //
10
+ // ╚███╔███╔╝██║ ██║██║ ██║██║ ╚████║██║██║ ╚████║╚██████╔╝ //
11
+ // ╚══╝╚══╝ ╚═╝ ╚═╝╚═╝ ╚═╝╚═╝ ╚═══╝╚═╝╚═╝ ╚═══╝ ╚═════╝ //
12
+ // //
13
+ // //
14
+ // - This file has been generated --------------------------- //
15
+ // //
16
+ // //
17
+ // -------------------------------------------------------------------------- //
18
+ // -------------------------------------------------------------------------- //
19
+
20
+ module.exports = function monkeyPatch(nativeBinding) {
21
+ // --------------------------------------------------------------------------
22
+ // Monkey Patch Web Audio API
23
+ // --------------------------------------------------------------------------
24
+
25
+ nativeBinding.AnalyserNode = require('./AnalyserNode.js')(nativeBinding.AnalyserNode);
26
+ nativeBinding.AudioBufferSourceNode = require('./AudioBufferSourceNode.js')(nativeBinding.AudioBufferSourceNode);
27
+ nativeBinding.BiquadFilterNode = require('./BiquadFilterNode.js')(nativeBinding.BiquadFilterNode);
28
+ nativeBinding.ChannelMergerNode = require('./ChannelMergerNode.js')(nativeBinding.ChannelMergerNode);
29
+ nativeBinding.ChannelSplitterNode = require('./ChannelSplitterNode.js')(nativeBinding.ChannelSplitterNode);
30
+ nativeBinding.ConstantSourceNode = require('./ConstantSourceNode.js')(nativeBinding.ConstantSourceNode);
31
+ nativeBinding.ConvolverNode = require('./ConvolverNode.js')(nativeBinding.ConvolverNode);
32
+ nativeBinding.DelayNode = require('./DelayNode.js')(nativeBinding.DelayNode);
33
+ nativeBinding.DynamicsCompressorNode = require('./DynamicsCompressorNode.js')(nativeBinding.DynamicsCompressorNode);
34
+ nativeBinding.GainNode = require('./GainNode.js')(nativeBinding.GainNode);
35
+ nativeBinding.IIRFilterNode = require('./IIRFilterNode.js')(nativeBinding.IIRFilterNode);
36
+ nativeBinding.OscillatorNode = require('./OscillatorNode.js')(nativeBinding.OscillatorNode);
37
+ nativeBinding.PannerNode = require('./PannerNode.js')(nativeBinding.PannerNode);
38
+ nativeBinding.StereoPannerNode = require('./StereoPannerNode.js')(nativeBinding.StereoPannerNode);
39
+ nativeBinding.WaveShaperNode = require('./WaveShaperNode.js')(nativeBinding.WaveShaperNode);
40
+
41
+ nativeBinding.AudioContext = require('./AudioContext.js')(nativeBinding);
42
+ nativeBinding.OfflineAudioContext = require('./OfflineAudioContext.js')(nativeBinding);
43
+
44
+ // --------------------------------------------------------------------------
45
+ // Promisify MediaDevices API
46
+ // --------------------------------------------------------------------------
47
+ const enumerateDevicesSync = nativeBinding.mediaDevices.enumerateDevices;
48
+ nativeBinding.mediaDevices.enumerateDevices = async function enumerateDevices() {
49
+ const list = enumerateDevicesSync();
50
+ return Promise.resolve(list);
51
+ };
52
+
53
+ const getUserMediaSync = nativeBinding.mediaDevices.getUserMedia;
54
+ nativeBinding.mediaDevices.getUserMedia = async function getUserMedia(options) {
55
+ if (options === undefined) {
56
+ throw new TypeError('Failed to execute "getUserMedia" on "MediaDevices": audio must be requested');
57
+ }
58
+
59
+ const stream = getUserMediaSync(options);
60
+ return Promise.resolve(stream);
61
+ };
62
+
63
+ return nativeBinding;
64
+ };
65
+
66
+
67
+
Binary file
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "node-web-audio-api",
3
- "version": "0.14.0",
3
+ "version": "0.15.0",
4
4
  "author": "Benjamin Matuszewski",
5
5
  "description": "Node.js bindings for web-audio-api-rs using napi-rs",
6
6
  "exports": {
@@ -38,10 +38,12 @@
38
38
  "build:only": "napi build --platform --release",
39
39
  "check": "cargo fmt && cargo clippy",
40
40
  "generate": "node generator/index.mjs && cargo fmt",
41
- "lint": "eslint monkey-patch.js index.cjs index.mjs && eslint examples/*.mjs",
41
+ "lint": "npx eslint index.cjs index.mjs && npx eslint js/*.js && npx eslint examples/*.mjs",
42
42
  "preversion": "yarn install && npm run generate",
43
- "postversion": "cargo bump $npm_package_version && git commit -am \"v$npm_package_version\" && node bin/check-changelog.mjs",
44
- "test": "mocha"
43
+ "postversion": "cargo bump $npm_package_version && git commit -am \"v$npm_package_version\" && node .scripts/check-changelog.mjs",
44
+ "test": "mocha",
45
+ "wpt": "npm run build && node ./.scripts/wpt-harness.mjs",
46
+ "wpt:only": "node ./.scripts/wpt-harness.mjs"
45
47
  },
46
48
  "devDependencies": {
47
49
  "@ircam/eslint-config": "^1.3.0",
@@ -49,8 +51,9 @@
49
51
  "@sindresorhus/slugify": "^2.1.1",
50
52
  "camelcase": "^7.0.1",
51
53
  "chai": "^4.3.7",
52
- "chalk": "^5.2.0",
54
+ "chalk": "^5.3.0",
53
55
  "cli-table": "^0.3.11",
56
+ "commander": "^11.1.0",
54
57
  "dotenv": "^16.0.3",
55
58
  "eslint": "^8.32.0",
56
59
  "mocha": "^10.2.0",
@@ -59,7 +62,8 @@
59
62
  "ping": "^0.4.2",
60
63
  "template-literal": "^1.0.4",
61
64
  "waves-masters": "^2.3.1",
62
- "webidl2": "^24.2.0"
65
+ "webidl2": "^24.2.0",
66
+ "wpt-runner": "^5.0.0"
63
67
  },
64
68
  "dependencies": {
65
69
  "@napi-rs/cli": "^2.14.3",
package/monkey-patch.js DELETED
@@ -1,184 +0,0 @@
1
- const fs = require('fs');
2
-
3
- const isPlainObject = function(obj) {
4
- return Object.prototype.toString.call(obj) === '[object Object]';
5
- };
6
-
7
- const isPositiveInt = function(n) {
8
- return Number.isSafeInteger(n) && 0 < n;
9
- };
10
-
11
- const isPositiveNumber = function(n) {
12
- return Number(n) === n && 0 < n;
13
- };
14
-
15
- class NotSupportedError extends Error {
16
- constructor(message) {
17
- super(message);
18
- this.name = 'NotSupportedError';
19
- }
20
- }
21
-
22
- const { platform, arch } = process;
23
-
24
- let contextIds = {
25
- audioinput: 0,
26
- audiooutput: 0,
27
- };
28
-
29
- let enumerateDevicesSync = null;
30
-
31
- function handleDefaultOptions(options, kind) {
32
- // increment contextIds as they are used to keep the process awake
33
- contextIds[kind] += 1;
34
-
35
- return options;
36
- }
37
-
38
- function patchAudioContext(nativeBinding) {
39
- class AudioContext extends nativeBinding.AudioContext {
40
- constructor(options = {}) {
41
- // special handling of options on linux, these are not spec compliant but are
42
- // ment to be more user-friendly than what we have now (is subject to change)
43
- options = handleDefaultOptions(options, 'audiooutput');
44
- super(options);
45
- // prevent garbage collection
46
- const processId = `__AudioContext_${contextIds['audiooutput']}`;
47
- process[processId] = this;
48
-
49
- Object.defineProperty(this, '__processId', {
50
- value: processId,
51
- enumerable: false,
52
- writable: false,
53
- configurable: false,
54
- });
55
-
56
- // keep process awake
57
- const keepAwakeId = setInterval(() => {}, 10000);
58
- Object.defineProperty(this, '__keepAwakeId', {
59
- value: keepAwakeId,
60
- enumerable: false,
61
- writable: true,
62
- configurable: false,
63
- });
64
- }
65
-
66
- // promisify sync APIs
67
- resume() {
68
- clearTimeout(this.__keepAwakeId);
69
- this.__keepAwakeId = setInterval(() => {}, 2000);
70
- return Promise.resolve(super.resume());
71
- }
72
-
73
- suspend() {
74
- return Promise.resolve(super.suspend());
75
- }
76
-
77
- close() {
78
- delete process[this.__processId];
79
- clearTimeout(this.__keepAwakeId);
80
- return Promise.resolve(super.close());
81
- }
82
-
83
- setSinkId(sinkId) {
84
- try {
85
- super.setSinkId(sinkId);
86
- Promise.resolve(undefined);
87
- } catch (err) {
88
- Promise.reject(err);
89
- }
90
- }
91
-
92
- decodeAudioData(audioData) {
93
- if (!audioData instanceof ArrayBuffer) {
94
- throw new Error('Invalid argument, please provide an ArrayBuffer');
95
- }
96
- try {
97
- const audioBuffer = super.decodeAudioData(audioData);
98
- return Promise.resolve(audioBuffer);
99
- } catch (err) {
100
- return Promise.reject(err);
101
- }
102
- }
103
- }
104
-
105
- return AudioContext;
106
- }
107
-
108
- function patchOfflineAudioContext(nativeBinding) {
109
- class OfflineAudioContext extends nativeBinding.OfflineAudioContext {
110
- constructor(...args) {
111
- // handle initialisation with either an options object or a sequence of parameters
112
- // https://webaudio.github.io/web-audio-api/#dom-offlineaudiocontext-constructor-contextoptions-contextoptions
113
- if (isPlainObject(args[0])
114
- && 'numberOfChannels' in args[0] && 'length' in args[0] && 'sampleRate' in args[0]
115
- ) {
116
- const { numberOfChannels, length, sampleRate } = args[0];
117
- args = [numberOfChannels, length, sampleRate];
118
- }
119
-
120
- const [numberOfChannels, length, sampleRate] = args;
121
-
122
- if (!isPositiveInt(numberOfChannels)) {
123
- throw new NotSupportedError(`Unsupported value for numberOfChannels: ${numberOfChannels}`);
124
- } else if (!isPositiveInt(length)) {
125
- throw new NotSupportedError(`Unsupported value for length: ${length}`);
126
- } else if (!isPositiveNumber(sampleRate)) {
127
- throw new NotSupportedError(`Unsupported value for sampleRate: ${sampleRate}`);
128
- }
129
-
130
- super(numberOfChannels, length, sampleRate);
131
- }
132
-
133
- // promisify sync APIs
134
- startRendering() {
135
- try {
136
- const audioBuffer = super.startRendering();
137
-
138
- clearTimeout(this.__keepAwakeId);
139
- return Promise.resolve(audioBuffer);
140
- } catch (err) {
141
- return Promise.reject(err);
142
- }
143
- }
144
-
145
- decodeAudioData(audioData) {
146
- if (!audioData instanceof ArrayBuffer) {
147
- throw new Error('Invalid argument, please provide an ArrayBuffer');
148
- }
149
- try {
150
- const audioBuffer = super.decodeAudioData(audioData);
151
- return Promise.resolve(audioBuffer);
152
- } catch (err) {
153
- return Promise.reject(err);
154
- }
155
- }
156
- }
157
-
158
- return OfflineAudioContext;
159
- }
160
-
161
- module.exports = function monkeyPatch(nativeBinding) {
162
- nativeBinding.AudioContext = patchAudioContext(nativeBinding);
163
- nativeBinding.OfflineAudioContext = patchOfflineAudioContext(nativeBinding);
164
-
165
- // Promisify MediaDevices API
166
- enumerateDevicesSync = nativeBinding.mediaDevices.enumerateDevices;
167
- nativeBinding.mediaDevices.enumerateDevices = async function enumerateDevices(options) {
168
- const list = enumerateDevicesSync();
169
- return Promise.resolve(list);
170
- }
171
-
172
- const getUserMediaSync = nativeBinding.mediaDevices.getUserMedia;
173
- nativeBinding.mediaDevices.getUserMedia = async function getUserMedia(options) {
174
- if (options === undefined) {
175
- throw new TypeError("Failed to execute 'getUserMedia' on 'MediaDevices': audio must be requested")
176
- }
177
-
178
- options = handleDefaultOptions(options, 'audioinput');
179
- const stream = getUserMediaSync(options);
180
- return Promise.resolve(stream);
181
- }
182
-
183
- return nativeBinding;
184
- }