node-web-audio-api 0.2.1 → 0.4.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.
package/CHANGELOG.md ADDED
@@ -0,0 +1,8 @@
1
+ ## v0.4.0
2
+
3
+ - Implement offline audio context
4
+ - Update web-audio-api-rs to v0.24.0
5
+ - Implement `audio_node.disconnect()`
6
+ - Properly support ESM
7
+ - Limit number of online contexts to 1 on Linux
8
+ - Force latencyHint to 'playback' if not manually set on RPi
package/README.md CHANGED
@@ -30,37 +30,87 @@ setInterval(() => {
30
30
  osc.connect(env);
31
31
  osc.start(now);
32
32
  osc.stop(now + 1);
33
- }, 50);
33
+ }, 80);
34
34
  ```
35
35
 
36
+ or using with old fashionned commonjs syntax
36
37
 
38
+ ```js
39
+ const { AudioContext, OscillatorNode, GainNode } = require('node-web-audio-api');
40
+
41
+ const audioContext = new AudioContext();
42
+ //...
43
+ ```
37
44
 
38
- ## Build manually
45
+ ## Caveats
39
46
 
40
- If prebuilt binaries are not shippped for your platform, you will need to install
41
- the rust toolchain and install and build the package from github.
47
+ - Currently the library does not provide any way of chosing the output interface, system default interface will be used. As the spec and web-audio-api evolve evolve, thus should change in the future see [https://github.com/orottier/web-audio-api-rs/issues/216](https://github.com/orottier/web-audio-api-rs/issues/216)
48
+ - On Linux systems, the audio backend is Alsa, which limits the number of online
49
+ AudioContext to 1. This is subject to change in the future.
42
50
 
43
- 1. Install rust toolchain
51
+ ### Raspberry Pi
52
+
53
+ On Raspberry Pi, the default render quantum size (128) is too small and underruns
54
+ occurs frequently. To prevent that, if you do not explicitely provide a latency hint
55
+ in the AudioContext options, the value is automatically set to 'playback' which uses
56
+ a buffer of 1024 samples. While this is not per se spec compliant, it allow usage
57
+ of the library in a more user friendly manner. In the future, this might change according
58
+ to the support of other audio backend, which is now alsa.
44
59
 
60
+ ```js
61
+ const audioContext = new AudioContext({ latencyHint: 'playback' });
45
62
  ```
63
+
64
+ The 'playback' latency hint, 1024 samples / ~21ms at 48000Hz, has been found
65
+ a good value.
66
+
67
+ ## Supported Platforms
68
+
69
+ | | binaries | tested |
70
+ | ---------------------------| ------ | ------ |
71
+ | Windows x64 | ✓ | |
72
+ | Windows arm64 | ✓ | |
73
+ | macOS x64 | ✓ | ✓ |
74
+ | macOS aarch64 | ✓ | |
75
+ | Linux x64 gnu | ✓ | |
76
+ | Linux arm gnueabihf (RPi) | ✓ | ✓ |
77
+
78
+
79
+ ### Manual Build
80
+
81
+ If prebuilt binaries are not shippped for your platform, you will need to:
82
+
83
+ 1. Install rust toolchain
84
+
85
+ ```sh
46
86
  curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
47
87
  ```
48
88
 
49
- 2. Install from github
89
+ 2. Install and build from github
50
90
 
51
- ```
91
+ ```sh
52
92
  npm install --save git+https://github.com/b-ma/node-web-audio-api.git
93
+ cd node_modules/node-web-audio-api
94
+ npm run build
53
95
  ```
54
96
 
55
- Note that the package will be built on your machine, so the install process might be a bit long
97
+ The package will then be built on your machine, which might take some time
56
98
 
57
- ## Roadmap
99
+ ## Known limitation / caveats
58
100
 
59
- - Make a few nodes work properly with clean and predictable code
60
- - Generate bindings from IDL [https://webaudio.github.io/web-audio-api/#idl-index](https://webaudio.github.io/web-audio-api/#idl-index)
61
- - Publish on `npm` with binaries
62
- - Implement prototype chain (?)
63
- - Follow developments of `web-audio-api-rs`
101
+ - async function are not trully async but only monkey patched on the JS side, this will
102
+ be updated once `web-audio-api-rs` provide async version of the methods.
103
+ - see `web-audio-api-rs`
104
+
105
+ ## Development notes
106
+
107
+ The npm script rely on [`cargo-bump`](https://crates.io/crates/cargo-bump) to maintain version synced between
108
+ the `package.json` and the `Cargo.toml` files. Therefore, you will need to install
109
+ `cargo-bump` on your machine
110
+
111
+ ```
112
+ cargo install cargo-bump
113
+ ```
64
114
 
65
115
  ## License
66
116
 
@@ -1,6 +1,5 @@
1
1
  const { existsSync, readFileSync } = require('fs');
2
2
  const { join } = require('path');
3
- const { patchAudioContext } = require('./monkey-patch.js');
4
3
 
5
4
  const { platform, arch } = process;
6
5
 
@@ -87,7 +86,15 @@ if (!nativeBinding) {
87
86
  throw new Error(`Failed to load native binding for OS: ${platform}, architecture: ${arch}`);
88
87
  }
89
88
 
89
+ const {
90
+ patchAudioContext,
91
+ patchOfflineAudioContext,
92
+ load,
93
+ } = require('./monkey-patch.js');
94
+
90
95
  nativeBinding.AudioContext = patchAudioContext(nativeBinding.AudioContext);
96
+ nativeBinding.OfflineAudioContext = patchOfflineAudioContext(nativeBinding.OfflineAudioContext);
97
+ nativeBinding.load = load;
91
98
 
92
99
  module.exports = nativeBinding;
93
100
 
package/index.mjs ADDED
@@ -0,0 +1,39 @@
1
+ // ---------------------------------------------------------- //
2
+ // ---------------------------------------------------------- //
3
+ // - WARNING - DO NOT EDIT - //
4
+ // - This file has been generated - //
5
+ // ---------------------------------------------------------- //
6
+ // ---------------------------------------------------------- //
7
+
8
+ // re-export index.js to support clean esm syntax
9
+ // see https://github.com/nodejs/node/issues/40541#issuecomment-951609570
10
+
11
+ import { createRequire } from 'module';
12
+ const require = createRequire(import.meta.url);
13
+
14
+ const nativeModule = require('./index.cjs');
15
+ export const {
16
+ AudioContext,
17
+ OfflineAudioContext,
18
+ AudioBuffer,
19
+ PeriodicWave,
20
+ // generated supported nodes
21
+ AudioBufferSourceNode,
22
+ BiquadFilterNode,
23
+ ChannelMergerNode,
24
+ ChannelSplitterNode,
25
+ ConstantSourceNode,
26
+ DelayNode,
27
+ DynamicsCompressorNode,
28
+ GainNode,
29
+ IIRFilterNode,
30
+ OscillatorNode,
31
+ StereoPannerNode,
32
+ WaveShaperNode,
33
+ // helper methods
34
+ load
35
+ } = nativeModule;
36
+
37
+ export default nativeModule;
38
+
39
+
package/monkey-patch.js CHANGED
@@ -1,38 +1,144 @@
1
+ const fs = require('fs');
1
2
 
3
+ const isPlainObject = function(obj) {
4
+ return Object.prototype.toString.call(obj) === '[object Object]';
5
+ };
6
+
7
+ const { platform, arch } = process;
2
8
  let contextId = 0;
3
9
 
4
- module.exports.patchAudioContext = function(NativeAudioContext) {
10
+ function patchAudioContext(NativeAudioContext) {
5
11
  class AudioContext extends NativeAudioContext {
6
- constructor(...args) {
7
- super(...args);
12
+ constructor(options = {}) {
13
+
14
+ // special handling of options on linux, these are not spec compliant but are
15
+ // ment to be more user-friendly than what we have now (is subject to change)
16
+ if (platform === 'linux') {
17
+ // throw meaningfull error if several contexts are created on linux,
18
+ // because of alsa backend we currently use
19
+ if (contextId === 1) {
20
+ throw new Error(`[node-web-audio-api] node-web-audio-api currently uses alsa as backend, therefore only one context can be safely created`);
21
+ }
22
+
23
+ // fallback latencyHint to "playback" on RPi if not explicitely defined
24
+ if (arch === 'arm') {
25
+ if (!('latencyHint' in options)) {
26
+ options.latencyHint = 'playback';
27
+ }
28
+ }
29
+ }
30
+
31
+ super(options);
8
32
  // prevent garbage collection
9
33
  const processId = `__AudioContext_${contextId}`;
10
34
  process[processId] = this;
11
- this.__processId = processId;
35
+
36
+ Object.defineProperty(this, '__processId', {
37
+ value: processId,
38
+ enumerable: false,
39
+ writable: false,
40
+ configurable: false,
41
+ });
12
42
 
13
43
  contextId += 1;
14
44
  // keep process awake
15
- this.__keepAwakeId = setInterval(() => {}, 10000);
16
- }
17
-
18
- // @todo
19
- // resume() {
20
- // this.__keepAwakeId = setInterval(() => {}, 2000);
21
- // return super.resume();
22
- // }
23
-
24
- // suspend() {
25
- // // not sure to be confirmed
26
- // clearTimeout(this.__keepAwakeId);
27
- // return super.suspend();
28
- // }
29
-
30
- // close() {
31
- // delete process[this.__processId];
32
- // clearTimeout(this.__keepAwakeId);
33
- // return super.close();
34
- // }
45
+ const keepAwakeId = setInterval(() => {}, 10000);
46
+ Object.defineProperty(this, '__keepAwakeId', {
47
+ value: keepAwakeId,
48
+ enumerable: false,
49
+ writable: true,
50
+ configurable: false,
51
+ });
52
+ }
53
+
54
+ // promisify sync APIs
55
+ resume() {
56
+ clearTimeout(this.__keepAwakeId);
57
+ this.__keepAwakeId = setInterval(() => {}, 2000);
58
+ return Promise.resolve(super.resume());
59
+ }
60
+
61
+ suspend() {
62
+ return Promise.resolve(super.suspend());
63
+ }
64
+
65
+ close() {
66
+ delete process[this.__processId];
67
+ clearTimeout(this.__keepAwakeId);
68
+ return Promise.resolve(super.close());
69
+ }
70
+
71
+ decodeAudioData(audioData) {
72
+ if (!isPlainObject(audioData) || !('path' in audioData)) {
73
+ throw new Error(`Invalid argument, please consider using the load helper`);
74
+ }
75
+
76
+ try {
77
+ const audioBuffer = super.decodeAudioData(audioData);
78
+ return Promise.resolve(audioBuffer);
79
+ } catch (err) {
80
+ return Promise.reject(err);
81
+ }
82
+ }
35
83
  }
36
84
 
37
85
  return AudioContext;
38
86
  }
87
+
88
+ function patchOfflineAudioContext(NativeOfflineAudioContext) {
89
+ class OfflineAudioContext extends NativeOfflineAudioContext {
90
+ constructor(...args) {
91
+ super(...args);
92
+ console.log(args);
93
+
94
+ // not sure this is usefull, to be tested
95
+ const keepAwakeId = setInterval(() => {}, 10000);
96
+ Object.defineProperty(this, '__keepAwakeId', {
97
+ value: keepAwakeId,
98
+ enumerable: false,
99
+ writable: true,
100
+ configurable: false,
101
+ });
102
+ }
103
+
104
+ // promisify sync APIs
105
+ startRendering() {
106
+ try {
107
+ const audioBuffer = super.startRendering();
108
+
109
+ clearTimeout(this.__keepAwakeId);
110
+ return Promise.resolve(audioBuffer);
111
+ } catch (err) {
112
+ return Promise.reject(err);
113
+ }
114
+ }
115
+
116
+ decodeAudioData(audioData) {
117
+ if (!isPlainObject(audioData) || !('path' in audioData)) {
118
+ throw new Error(`Invalid argument, please consider using the load helper`);
119
+ }
120
+
121
+ try {
122
+ const audioBuffer = super.decodeAudioData(audioData);
123
+ return Promise.resolve(audioBuffer);
124
+ } catch (err) {
125
+ return Promise.reject(err);
126
+ }
127
+ }
128
+ }
129
+
130
+ return OfflineAudioContext;
131
+ }
132
+
133
+ module.exports.patchAudioContext = patchAudioContext;
134
+ module.exports.patchOfflineAudioContext = patchOfflineAudioContext;
135
+
136
+ // dumb method provided to mock an xhr call and mimick browser's API
137
+ // see also `AudioContext.decodeAudioData`
138
+ module.exports.load = function(path) {
139
+ if (!fs.existsSync(path)) {
140
+ throw new Error(`File not found: "${path}"`);
141
+ }
142
+
143
+ return { path };
144
+ }
Binary file
package/package.json CHANGED
@@ -1,10 +1,13 @@
1
1
  {
2
2
  "name": "node-web-audio-api",
3
- "version": "0.2.1",
3
+ "version": "0.4.0",
4
4
  "author": "Benjamin Matuszewski",
5
5
  "description": "Node.js bindings for web-audio-api-rs using napi-rs",
6
- "main": "index.js",
7
- "repository": "git@github.com:b-ma/node-web-audio-api.git",
6
+ "exports": {
7
+ "import": "./index.mjs",
8
+ "require": "./index.cjs"
9
+ },
10
+ "repository": "https://github.com/ircam-ismm/node-web-audio-api",
8
11
  "license": "BSD-3-Clause",
9
12
  "keywords": [
10
13
  "audio",
@@ -24,18 +27,24 @@
24
27
  },
25
28
  "scripts": {
26
29
  "artifacts": "napi artifacts",
27
- "build": "napi build --platform --release",
28
- "build:debug": "napi build --platform",
30
+ "build": "npm run generate && napi build --platform --release",
31
+ "build:debug": "npm run generate && napi build --platform",
29
32
  "check": "cargo fmt && cargo clippy",
30
- "generate": "node generator/index.js",
31
- "preversion": "npm run generate"
33
+ "generate": "node generator/index.mjs && cargo fmt",
34
+ "preversion": "yarn install && npm run generate",
35
+ "postversion": "cargo bump $npm_package_version && git commit -am \"v$npm_package_version\""
32
36
  },
33
37
  "devDependencies": {
38
+ "@sindresorhus/slugify": "^2.1.0",
34
39
  "chalk": "^5.0.1",
40
+ "camelcase": "^6.3.0",
35
41
  "dotenv": "^16.0.2",
36
42
  "node-ssh": "^13.0.0",
37
43
  "octokit": "^2.0.7",
38
- "ping": "^0.4.2"
44
+ "ping": "^0.4.2",
45
+ "template-literal": "^1.0.4",
46
+ "waves-masters": "^2.3.1",
47
+ "webidl2": "^24.2.0"
39
48
  },
40
49
  "dependencies": {
41
50
  "@napi-rs/cli": "^2.10.0",
package/simple-test.mjs CHANGED
@@ -1,7 +1,6 @@
1
- import webaudio from './index.js';
1
+ import { AudioContext } from './index.mjs';
2
2
 
3
- const audioContext = new webaudio.AudioContext();
4
- // process.audioContext = audioContext;
3
+ const audioContext = new AudioContext();
5
4
 
6
5
  setInterval(() => {
7
6
  const now = audioContext.currentTime;