@tensamin/audio 0.1.0 → 0.1.2
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/README.md +24 -2
- package/dist/chunk-FS635GMR.mjs +47 -0
- package/dist/chunk-HFSKQ33X.mjs +38 -0
- package/{src/vad/vad-state.ts → dist/chunk-JJASCVEW.mjs} +21 -33
- package/dist/chunk-OZ7KMC4S.mjs +46 -0
- package/dist/chunk-QU7E5HBA.mjs +106 -0
- package/dist/chunk-SDTOKWM2.mjs +39 -0
- package/{src/vad/vad-node.ts → dist/chunk-UMU2KIB6.mjs} +10 -20
- package/dist/chunk-WBQAMGXK.mjs +0 -0
- package/dist/context/audio-context.d.mts +32 -0
- package/dist/context/audio-context.d.ts +32 -0
- package/dist/context/audio-context.js +75 -0
- package/dist/context/audio-context.mjs +16 -0
- package/dist/extensibility/plugins.d.mts +9 -0
- package/dist/extensibility/plugins.d.ts +9 -0
- package/dist/extensibility/plugins.js +180 -0
- package/dist/extensibility/plugins.mjs +14 -0
- package/dist/index.d.mts +10 -0
- package/dist/index.d.ts +10 -0
- package/dist/index.js +419 -0
- package/dist/index.mjs +47 -0
- package/dist/livekit/integration.d.mts +11 -0
- package/dist/livekit/integration.d.ts +11 -0
- package/dist/livekit/integration.js +368 -0
- package/dist/livekit/integration.mjs +12 -0
- package/dist/noise-suppression/rnnoise-node.d.mts +10 -0
- package/dist/noise-suppression/rnnoise-node.d.ts +10 -0
- package/dist/noise-suppression/rnnoise-node.js +73 -0
- package/dist/noise-suppression/rnnoise-node.mjs +6 -0
- package/dist/pipeline/audio-pipeline.d.mts +6 -0
- package/dist/pipeline/audio-pipeline.d.ts +6 -0
- package/dist/pipeline/audio-pipeline.js +335 -0
- package/dist/pipeline/audio-pipeline.mjs +11 -0
- package/dist/types.d.mts +155 -0
- package/dist/types.d.ts +155 -0
- package/dist/types.js +18 -0
- package/dist/types.mjs +1 -0
- package/dist/vad/vad-node.d.mts +9 -0
- package/dist/vad/vad-node.d.ts +9 -0
- package/dist/vad/vad-node.js +92 -0
- package/dist/vad/vad-node.mjs +6 -0
- package/dist/vad/vad-state.d.mts +15 -0
- package/dist/vad/vad-state.d.ts +15 -0
- package/dist/vad/vad-state.js +83 -0
- package/dist/vad/vad-state.mjs +6 -0
- package/package.json +11 -14
- package/.github/workflows/publish.yml +0 -23
- package/src/context/audio-context.ts +0 -69
- package/src/extensibility/plugins.ts +0 -45
- package/src/index.ts +0 -8
- package/src/livekit/integration.ts +0 -61
- package/src/noise-suppression/rnnoise-node.ts +0 -62
- package/src/pipeline/audio-pipeline.ts +0 -154
- package/src/types.ts +0 -167
- package/tsconfig.json +0 -29
package/README.md
CHANGED
|
@@ -18,6 +18,18 @@ bun add @tensamin/audio livekit-client
|
|
|
18
18
|
pnpm install @tensamin/audio livekit-client
|
|
19
19
|
```
|
|
20
20
|
|
|
21
|
+
## Setup Assets
|
|
22
|
+
|
|
23
|
+
This library uses WASM and AudioWorklets for processing. You must manually download these assets and serve them from your application (e.g., in your `public/` folder).
|
|
24
|
+
|
|
25
|
+
1. Download the following files from `https://unpkg.com/@sapphi-red/web-noise-suppressor@0.3.5/dist/`:
|
|
26
|
+
|
|
27
|
+
- `rnnoise.wasm`
|
|
28
|
+
- `rnnoise_simd.wasm`
|
|
29
|
+
- `noise-suppressor-worklet.min.js`
|
|
30
|
+
|
|
31
|
+
2. Place them in your project's public directory (e.g., `public/audio-processor/`).
|
|
32
|
+
|
|
21
33
|
## Usage
|
|
22
34
|
|
|
23
35
|
### Basic Usage (Raw MediaStream)
|
|
@@ -31,7 +43,12 @@ const track = stream.getAudioTracks()[0];
|
|
|
31
43
|
|
|
32
44
|
// Create pipeline
|
|
33
45
|
const pipeline = await createAudioPipeline(track, {
|
|
34
|
-
noiseSuppression: {
|
|
46
|
+
noiseSuppression: {
|
|
47
|
+
enabled: true,
|
|
48
|
+
wasmUrl: "/audio-processor/rnnoise.wasm",
|
|
49
|
+
simdUrl: "/audio-processor/rnnoise_simd.wasm",
|
|
50
|
+
workletUrl: "/audio-processor/noise-suppressor-worklet.min.js",
|
|
51
|
+
},
|
|
35
52
|
vad: { enabled: true },
|
|
36
53
|
});
|
|
37
54
|
|
|
@@ -56,7 +73,12 @@ const localTrack = await LocalAudioTrack.create();
|
|
|
56
73
|
|
|
57
74
|
// Attach processing (replaces the underlying track)
|
|
58
75
|
const pipeline = await attachProcessingToTrack(localTrack, {
|
|
59
|
-
noiseSuppression: {
|
|
76
|
+
noiseSuppression: {
|
|
77
|
+
enabled: true,
|
|
78
|
+
wasmUrl: "/audio-processor/rnnoise.wasm",
|
|
79
|
+
simdUrl: "/audio-processor/rnnoise_simd.wasm",
|
|
80
|
+
workletUrl: "/audio-processor/noise-suppressor-worklet.min.js",
|
|
81
|
+
},
|
|
60
82
|
vad: { enabled: true },
|
|
61
83
|
livekit: { manageTrackMute: true }, // Optional: mute the track object itself
|
|
62
84
|
});
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import {
|
|
2
|
+
RNNoisePlugin
|
|
3
|
+
} from "./chunk-SDTOKWM2.mjs";
|
|
4
|
+
import {
|
|
5
|
+
EnergyVADPlugin
|
|
6
|
+
} from "./chunk-UMU2KIB6.mjs";
|
|
7
|
+
|
|
8
|
+
// src/extensibility/plugins.ts
|
|
9
|
+
var nsPlugins = /* @__PURE__ */ new Map();
|
|
10
|
+
var vadPlugins = /* @__PURE__ */ new Map();
|
|
11
|
+
var defaultNs = new RNNoisePlugin();
|
|
12
|
+
nsPlugins.set(defaultNs.name, defaultNs);
|
|
13
|
+
var defaultVad = new EnergyVADPlugin();
|
|
14
|
+
vadPlugins.set(defaultVad.name, defaultVad);
|
|
15
|
+
function registerNoiseSuppressionPlugin(plugin) {
|
|
16
|
+
nsPlugins.set(plugin.name, plugin);
|
|
17
|
+
}
|
|
18
|
+
function registerVADPlugin(plugin) {
|
|
19
|
+
vadPlugins.set(plugin.name, plugin);
|
|
20
|
+
}
|
|
21
|
+
function getNoiseSuppressionPlugin(name) {
|
|
22
|
+
if (!name) return defaultNs;
|
|
23
|
+
const plugin = nsPlugins.get(name);
|
|
24
|
+
if (!plugin) {
|
|
25
|
+
console.warn(
|
|
26
|
+
`Noise suppression plugin '${name}' not found, falling back to default.`
|
|
27
|
+
);
|
|
28
|
+
return defaultNs;
|
|
29
|
+
}
|
|
30
|
+
return plugin;
|
|
31
|
+
}
|
|
32
|
+
function getVADPlugin(name) {
|
|
33
|
+
if (!name) return defaultVad;
|
|
34
|
+
const plugin = vadPlugins.get(name);
|
|
35
|
+
if (!plugin) {
|
|
36
|
+
console.warn(`VAD plugin '${name}' not found, falling back to default.`);
|
|
37
|
+
return defaultVad;
|
|
38
|
+
}
|
|
39
|
+
return plugin;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export {
|
|
43
|
+
registerNoiseSuppressionPlugin,
|
|
44
|
+
registerVADPlugin,
|
|
45
|
+
getNoiseSuppressionPlugin,
|
|
46
|
+
getVADPlugin
|
|
47
|
+
};
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import {
|
|
2
|
+
createAudioPipeline
|
|
3
|
+
} from "./chunk-QU7E5HBA.mjs";
|
|
4
|
+
|
|
5
|
+
// src/livekit/integration.ts
|
|
6
|
+
async function attachProcessingToTrack(track, config = {}) {
|
|
7
|
+
const originalTrack = track.mediaStreamTrack;
|
|
8
|
+
const pipeline = await createAudioPipeline(originalTrack, config);
|
|
9
|
+
await track.replaceTrack(pipeline.processedTrack);
|
|
10
|
+
if (config.livekit?.manageTrackMute) {
|
|
11
|
+
let isVadMuted = false;
|
|
12
|
+
pipeline.events.on("vadChange", async (state) => {
|
|
13
|
+
if (state.isSpeaking) {
|
|
14
|
+
if (isVadMuted) {
|
|
15
|
+
await track.unmute();
|
|
16
|
+
isVadMuted = false;
|
|
17
|
+
}
|
|
18
|
+
} else {
|
|
19
|
+
if (!track.isMuted) {
|
|
20
|
+
await track.mute();
|
|
21
|
+
isVadMuted = true;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
const originalDispose = pipeline.dispose;
|
|
27
|
+
pipeline.dispose = () => {
|
|
28
|
+
if (originalTrack.readyState === "live") {
|
|
29
|
+
track.replaceTrack(originalTrack).catch(console.error);
|
|
30
|
+
}
|
|
31
|
+
originalDispose();
|
|
32
|
+
};
|
|
33
|
+
return pipeline;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export {
|
|
37
|
+
attachProcessingToTrack
|
|
38
|
+
};
|
|
@@ -1,36 +1,28 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
constructor(config: AudioProcessingConfig["vad"]) {
|
|
1
|
+
// src/vad/vad-state.ts
|
|
2
|
+
var VADStateMachine = class {
|
|
3
|
+
config;
|
|
4
|
+
currentState = "silent";
|
|
5
|
+
lastSpeechTime = 0;
|
|
6
|
+
speechStartTime = 0;
|
|
7
|
+
frameDurationMs = 20;
|
|
8
|
+
// Assumed frame duration, updated by calls
|
|
9
|
+
constructor(config) {
|
|
11
10
|
this.config = {
|
|
12
11
|
enabled: config?.enabled ?? true,
|
|
13
12
|
pluginName: config?.pluginName ?? "energy-vad",
|
|
14
13
|
startThreshold: config?.startThreshold ?? 0.5,
|
|
15
14
|
stopThreshold: config?.stopThreshold ?? 0.4,
|
|
16
15
|
hangoverMs: config?.hangoverMs ?? 300,
|
|
17
|
-
preRollMs: config?.preRollMs ?? 200
|
|
16
|
+
preRollMs: config?.preRollMs ?? 200
|
|
18
17
|
};
|
|
19
18
|
}
|
|
20
|
-
|
|
21
|
-
updateConfig(config: Partial<AudioProcessingConfig["vad"]>) {
|
|
19
|
+
updateConfig(config) {
|
|
22
20
|
this.config = { ...this.config, ...config };
|
|
23
21
|
}
|
|
24
|
-
|
|
25
|
-
processFrame(probability: number, timestamp: number): VADState {
|
|
22
|
+
processFrame(probability, timestamp) {
|
|
26
23
|
const { startThreshold, stopThreshold, hangoverMs } = this.config;
|
|
27
|
-
|
|
28
24
|
let newState = this.currentState;
|
|
29
|
-
|
|
30
|
-
if (
|
|
31
|
-
this.currentState === "silent" ||
|
|
32
|
-
this.currentState === "speech_ending"
|
|
33
|
-
) {
|
|
25
|
+
if (this.currentState === "silent" || this.currentState === "speech_ending") {
|
|
34
26
|
if (probability >= startThreshold) {
|
|
35
27
|
newState = "speech_starting";
|
|
36
28
|
this.speechStartTime = timestamp;
|
|
@@ -38,34 +30,30 @@ export class VADStateMachine {
|
|
|
38
30
|
} else {
|
|
39
31
|
newState = "silent";
|
|
40
32
|
}
|
|
41
|
-
} else if (
|
|
42
|
-
this.currentState === "speech_starting" ||
|
|
43
|
-
this.currentState === "speaking"
|
|
44
|
-
) {
|
|
33
|
+
} else if (this.currentState === "speech_starting" || this.currentState === "speaking") {
|
|
45
34
|
if (probability >= stopThreshold) {
|
|
46
35
|
newState = "speaking";
|
|
47
36
|
this.lastSpeechTime = timestamp;
|
|
48
37
|
} else {
|
|
49
|
-
// Check hangover
|
|
50
38
|
const timeSinceSpeech = timestamp - this.lastSpeechTime;
|
|
51
39
|
if (timeSinceSpeech < hangoverMs) {
|
|
52
|
-
newState = "speaking";
|
|
40
|
+
newState = "speaking";
|
|
53
41
|
} else {
|
|
54
42
|
newState = "speech_ending";
|
|
55
43
|
}
|
|
56
44
|
}
|
|
57
45
|
}
|
|
58
|
-
|
|
59
|
-
// Transition from starting/ending to stable states
|
|
60
46
|
if (newState === "speech_starting") newState = "speaking";
|
|
61
47
|
if (newState === "speech_ending") newState = "silent";
|
|
62
|
-
|
|
63
48
|
this.currentState = newState;
|
|
64
|
-
|
|
65
49
|
return {
|
|
66
50
|
isSpeaking: newState === "speaking",
|
|
67
51
|
probability,
|
|
68
|
-
state: newState
|
|
52
|
+
state: newState
|
|
69
53
|
};
|
|
70
54
|
}
|
|
71
|
-
}
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
export {
|
|
58
|
+
VADStateMachine
|
|
59
|
+
};
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
// src/context/audio-context.ts
|
|
2
|
+
var sharedContext = null;
|
|
3
|
+
var activePipelines = 0;
|
|
4
|
+
function getAudioContext(options) {
|
|
5
|
+
if (typeof window === "undefined" || typeof AudioContext === "undefined") {
|
|
6
|
+
throw new Error(
|
|
7
|
+
"AudioContext is not supported in this environment (browser only)."
|
|
8
|
+
);
|
|
9
|
+
}
|
|
10
|
+
if (!sharedContext || sharedContext.state === "closed") {
|
|
11
|
+
sharedContext = new AudioContext(options);
|
|
12
|
+
}
|
|
13
|
+
return sharedContext;
|
|
14
|
+
}
|
|
15
|
+
function registerPipeline() {
|
|
16
|
+
activePipelines++;
|
|
17
|
+
}
|
|
18
|
+
function unregisterPipeline() {
|
|
19
|
+
activePipelines = Math.max(0, activePipelines - 1);
|
|
20
|
+
}
|
|
21
|
+
async function resumeAudioContext() {
|
|
22
|
+
if (sharedContext && sharedContext.state === "suspended") {
|
|
23
|
+
await sharedContext.resume();
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
async function suspendAudioContext() {
|
|
27
|
+
if (sharedContext && sharedContext.state === "running") {
|
|
28
|
+
await sharedContext.suspend();
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
async function closeAudioContext() {
|
|
32
|
+
if (sharedContext && sharedContext.state !== "closed") {
|
|
33
|
+
await sharedContext.close();
|
|
34
|
+
}
|
|
35
|
+
sharedContext = null;
|
|
36
|
+
activePipelines = 0;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export {
|
|
40
|
+
getAudioContext,
|
|
41
|
+
registerPipeline,
|
|
42
|
+
unregisterPipeline,
|
|
43
|
+
resumeAudioContext,
|
|
44
|
+
suspendAudioContext,
|
|
45
|
+
closeAudioContext
|
|
46
|
+
};
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import {
|
|
2
|
+
VADStateMachine
|
|
3
|
+
} from "./chunk-JJASCVEW.mjs";
|
|
4
|
+
import {
|
|
5
|
+
getAudioContext,
|
|
6
|
+
registerPipeline,
|
|
7
|
+
unregisterPipeline
|
|
8
|
+
} from "./chunk-OZ7KMC4S.mjs";
|
|
9
|
+
import {
|
|
10
|
+
getNoiseSuppressionPlugin,
|
|
11
|
+
getVADPlugin
|
|
12
|
+
} from "./chunk-FS635GMR.mjs";
|
|
13
|
+
|
|
14
|
+
// src/pipeline/audio-pipeline.ts
|
|
15
|
+
import mitt from "mitt";
|
|
16
|
+
async function createAudioPipeline(sourceTrack, config = {}) {
|
|
17
|
+
const context = getAudioContext();
|
|
18
|
+
registerPipeline();
|
|
19
|
+
const fullConfig = {
|
|
20
|
+
noiseSuppression: { enabled: true, ...config.noiseSuppression },
|
|
21
|
+
vad: { enabled: true, ...config.vad },
|
|
22
|
+
output: {
|
|
23
|
+
speechGain: 1,
|
|
24
|
+
silenceGain: 0,
|
|
25
|
+
gainRampTime: 0.02,
|
|
26
|
+
...config.output
|
|
27
|
+
},
|
|
28
|
+
livekit: { manageTrackMute: false, ...config.livekit }
|
|
29
|
+
};
|
|
30
|
+
const sourceStream = new MediaStream([sourceTrack]);
|
|
31
|
+
const sourceNode = context.createMediaStreamSource(sourceStream);
|
|
32
|
+
const nsPlugin = getNoiseSuppressionPlugin(
|
|
33
|
+
fullConfig.noiseSuppression?.pluginName
|
|
34
|
+
);
|
|
35
|
+
const nsNode = await nsPlugin.createNode(
|
|
36
|
+
context,
|
|
37
|
+
fullConfig.noiseSuppression
|
|
38
|
+
);
|
|
39
|
+
const vadPlugin = getVADPlugin(fullConfig.vad?.pluginName);
|
|
40
|
+
const vadStateMachine = new VADStateMachine(fullConfig.vad);
|
|
41
|
+
const emitter = mitt();
|
|
42
|
+
const vadNode = await vadPlugin.createNode(
|
|
43
|
+
context,
|
|
44
|
+
fullConfig.vad,
|
|
45
|
+
(prob) => {
|
|
46
|
+
const timestamp = context.currentTime * 1e3;
|
|
47
|
+
const newState = vadStateMachine.processFrame(prob, timestamp);
|
|
48
|
+
if (newState.state !== lastVadState.state || Math.abs(newState.probability - lastVadState.probability) > 0.1) {
|
|
49
|
+
emitter.emit("vadChange", newState);
|
|
50
|
+
lastVadState = newState;
|
|
51
|
+
updateGain(newState);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
);
|
|
55
|
+
let lastVadState = {
|
|
56
|
+
isSpeaking: false,
|
|
57
|
+
probability: 0,
|
|
58
|
+
state: "silent"
|
|
59
|
+
};
|
|
60
|
+
const splitter = context.createGain();
|
|
61
|
+
sourceNode.connect(nsNode);
|
|
62
|
+
nsNode.connect(splitter);
|
|
63
|
+
splitter.connect(vadNode);
|
|
64
|
+
const delayNode = context.createDelay(1);
|
|
65
|
+
const preRollSeconds = (fullConfig.vad?.preRollMs ?? 200) / 1e3;
|
|
66
|
+
delayNode.delayTime.value = preRollSeconds;
|
|
67
|
+
const gainNode = context.createGain();
|
|
68
|
+
gainNode.gain.value = fullConfig.output?.silenceGain ?? 0;
|
|
69
|
+
const destination = context.createMediaStreamDestination();
|
|
70
|
+
splitter.connect(delayNode);
|
|
71
|
+
delayNode.connect(gainNode);
|
|
72
|
+
gainNode.connect(destination);
|
|
73
|
+
function updateGain(state) {
|
|
74
|
+
const { speechGain, silenceGain, gainRampTime } = fullConfig.output;
|
|
75
|
+
const targetGain = state.isSpeaking ? speechGain ?? 1 : silenceGain ?? 0;
|
|
76
|
+
const now = context.currentTime;
|
|
77
|
+
gainNode.gain.setTargetAtTime(targetGain, now, gainRampTime ?? 0.02);
|
|
78
|
+
}
|
|
79
|
+
function dispose() {
|
|
80
|
+
sourceNode.disconnect();
|
|
81
|
+
nsNode.disconnect();
|
|
82
|
+
splitter.disconnect();
|
|
83
|
+
vadNode.disconnect();
|
|
84
|
+
delayNode.disconnect();
|
|
85
|
+
gainNode.disconnect();
|
|
86
|
+
destination.stream.getTracks().forEach((t) => t.stop());
|
|
87
|
+
unregisterPipeline();
|
|
88
|
+
}
|
|
89
|
+
return {
|
|
90
|
+
processedTrack: destination.stream.getAudioTracks()[0],
|
|
91
|
+
events: emitter,
|
|
92
|
+
get state() {
|
|
93
|
+
return lastVadState;
|
|
94
|
+
},
|
|
95
|
+
setConfig: (newConfig) => {
|
|
96
|
+
if (newConfig.vad) {
|
|
97
|
+
vadStateMachine.updateConfig(newConfig.vad);
|
|
98
|
+
}
|
|
99
|
+
},
|
|
100
|
+
dispose
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
export {
|
|
105
|
+
createAudioPipeline
|
|
106
|
+
};
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
// src/noise-suppression/rnnoise-node.ts
|
|
2
|
+
var RNNoisePlugin = class {
|
|
3
|
+
name = "rnnoise-ns";
|
|
4
|
+
wasmBuffer = null;
|
|
5
|
+
async createNode(context, config) {
|
|
6
|
+
const { loadRnnoise, RnnoiseWorkletNode } = await import("@sapphi-red/web-noise-suppressor");
|
|
7
|
+
if (!config?.enabled) {
|
|
8
|
+
const pass = context.createGain();
|
|
9
|
+
return pass;
|
|
10
|
+
}
|
|
11
|
+
if (!config?.wasmUrl || !config?.simdUrl || !config?.workletUrl) {
|
|
12
|
+
throw new Error(
|
|
13
|
+
"RNNoisePlugin requires 'wasmUrl', 'simdUrl', and 'workletUrl' to be configured. Please download the assets and provide the URLs."
|
|
14
|
+
);
|
|
15
|
+
}
|
|
16
|
+
if (!this.wasmBuffer) {
|
|
17
|
+
this.wasmBuffer = await loadRnnoise({
|
|
18
|
+
url: config.wasmUrl,
|
|
19
|
+
simdUrl: config.simdUrl
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
const workletUrl = config.workletUrl;
|
|
23
|
+
try {
|
|
24
|
+
await context.audioWorklet.addModule(workletUrl);
|
|
25
|
+
} catch (e) {
|
|
26
|
+
console.warn("Failed to add RNNoise worklet module:", e);
|
|
27
|
+
}
|
|
28
|
+
const node = new RnnoiseWorkletNode(context, {
|
|
29
|
+
wasmBinary: this.wasmBuffer,
|
|
30
|
+
maxChannels: 1
|
|
31
|
+
// Mono for now
|
|
32
|
+
});
|
|
33
|
+
return node;
|
|
34
|
+
}
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
export {
|
|
38
|
+
RNNoisePlugin
|
|
39
|
+
};
|
|
@@ -1,7 +1,5 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
// Inline AudioWorklet processor for Energy VAD
|
|
4
|
-
const energyVadWorkletCode = `
|
|
1
|
+
// src/vad/vad-node.ts
|
|
2
|
+
var energyVadWorkletCode = `
|
|
5
3
|
class EnergyVadProcessor extends AudioWorkletProcessor {
|
|
6
4
|
constructor() {
|
|
7
5
|
super();
|
|
@@ -41,21 +39,13 @@ class EnergyVadProcessor extends AudioWorkletProcessor {
|
|
|
41
39
|
}
|
|
42
40
|
registerProcessor('energy-vad-processor', EnergyVadProcessor);
|
|
43
41
|
`;
|
|
44
|
-
|
|
45
|
-
export class EnergyVADPlugin implements VADPlugin {
|
|
42
|
+
var EnergyVADPlugin = class {
|
|
46
43
|
name = "energy-vad";
|
|
47
|
-
|
|
48
|
-
async createNode(
|
|
49
|
-
context: AudioContext,
|
|
50
|
-
config: AudioProcessingConfig["vad"],
|
|
51
|
-
onDecision: (probability: number) => void,
|
|
52
|
-
): Promise<AudioNode> {
|
|
53
|
-
// 1. Create Worklet
|
|
44
|
+
async createNode(context, config, onDecision) {
|
|
54
45
|
const blob = new Blob([energyVadWorkletCode], {
|
|
55
|
-
type: "application/javascript"
|
|
46
|
+
type: "application/javascript"
|
|
56
47
|
});
|
|
57
48
|
const url = URL.createObjectURL(blob);
|
|
58
|
-
|
|
59
49
|
try {
|
|
60
50
|
await context.audioWorklet.addModule(url);
|
|
61
51
|
} catch (e) {
|
|
@@ -64,15 +54,15 @@ export class EnergyVADPlugin implements VADPlugin {
|
|
|
64
54
|
} finally {
|
|
65
55
|
URL.revokeObjectURL(url);
|
|
66
56
|
}
|
|
67
|
-
|
|
68
|
-
// 2. Create Node
|
|
69
57
|
const node = new AudioWorkletNode(context, "energy-vad-processor");
|
|
70
|
-
|
|
71
58
|
node.port.onmessage = (event) => {
|
|
72
59
|
const { probability } = event.data;
|
|
73
60
|
onDecision(probability);
|
|
74
61
|
};
|
|
75
|
-
|
|
76
62
|
return node;
|
|
77
63
|
}
|
|
78
|
-
}
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
export {
|
|
67
|
+
EnergyVADPlugin
|
|
68
|
+
};
|
|
File without changes
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Manages a shared AudioContext for the application.
|
|
3
|
+
*/
|
|
4
|
+
/**
|
|
5
|
+
* Gets the shared AudioContext, creating it if necessary.
|
|
6
|
+
* @param options Optional AudioContextOptions
|
|
7
|
+
*/
|
|
8
|
+
declare function getAudioContext(options?: AudioContextOptions): AudioContext;
|
|
9
|
+
/**
|
|
10
|
+
* Registers a pipeline usage. Keeps track of active users.
|
|
11
|
+
*/
|
|
12
|
+
declare function registerPipeline(): void;
|
|
13
|
+
/**
|
|
14
|
+
* Unregisters a pipeline usage.
|
|
15
|
+
* Optionally closes the context if no pipelines are active (not implemented by default to avoid churn).
|
|
16
|
+
*/
|
|
17
|
+
declare function unregisterPipeline(): void;
|
|
18
|
+
/**
|
|
19
|
+
* Resumes the shared AudioContext.
|
|
20
|
+
* Should be called in response to a user gesture.
|
|
21
|
+
*/
|
|
22
|
+
declare function resumeAudioContext(): Promise<void>;
|
|
23
|
+
/**
|
|
24
|
+
* Suspends the shared AudioContext.
|
|
25
|
+
*/
|
|
26
|
+
declare function suspendAudioContext(): Promise<void>;
|
|
27
|
+
/**
|
|
28
|
+
* Closes the shared AudioContext and releases resources.
|
|
29
|
+
*/
|
|
30
|
+
declare function closeAudioContext(): Promise<void>;
|
|
31
|
+
|
|
32
|
+
export { closeAudioContext, getAudioContext, registerPipeline, resumeAudioContext, suspendAudioContext, unregisterPipeline };
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Manages a shared AudioContext for the application.
|
|
3
|
+
*/
|
|
4
|
+
/**
|
|
5
|
+
* Gets the shared AudioContext, creating it if necessary.
|
|
6
|
+
* @param options Optional AudioContextOptions
|
|
7
|
+
*/
|
|
8
|
+
declare function getAudioContext(options?: AudioContextOptions): AudioContext;
|
|
9
|
+
/**
|
|
10
|
+
* Registers a pipeline usage. Keeps track of active users.
|
|
11
|
+
*/
|
|
12
|
+
declare function registerPipeline(): void;
|
|
13
|
+
/**
|
|
14
|
+
* Unregisters a pipeline usage.
|
|
15
|
+
* Optionally closes the context if no pipelines are active (not implemented by default to avoid churn).
|
|
16
|
+
*/
|
|
17
|
+
declare function unregisterPipeline(): void;
|
|
18
|
+
/**
|
|
19
|
+
* Resumes the shared AudioContext.
|
|
20
|
+
* Should be called in response to a user gesture.
|
|
21
|
+
*/
|
|
22
|
+
declare function resumeAudioContext(): Promise<void>;
|
|
23
|
+
/**
|
|
24
|
+
* Suspends the shared AudioContext.
|
|
25
|
+
*/
|
|
26
|
+
declare function suspendAudioContext(): Promise<void>;
|
|
27
|
+
/**
|
|
28
|
+
* Closes the shared AudioContext and releases resources.
|
|
29
|
+
*/
|
|
30
|
+
declare function closeAudioContext(): Promise<void>;
|
|
31
|
+
|
|
32
|
+
export { closeAudioContext, getAudioContext, registerPipeline, resumeAudioContext, suspendAudioContext, unregisterPipeline };
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/context/audio-context.ts
|
|
21
|
+
var audio_context_exports = {};
|
|
22
|
+
__export(audio_context_exports, {
|
|
23
|
+
closeAudioContext: () => closeAudioContext,
|
|
24
|
+
getAudioContext: () => getAudioContext,
|
|
25
|
+
registerPipeline: () => registerPipeline,
|
|
26
|
+
resumeAudioContext: () => resumeAudioContext,
|
|
27
|
+
suspendAudioContext: () => suspendAudioContext,
|
|
28
|
+
unregisterPipeline: () => unregisterPipeline
|
|
29
|
+
});
|
|
30
|
+
module.exports = __toCommonJS(audio_context_exports);
|
|
31
|
+
var sharedContext = null;
|
|
32
|
+
var activePipelines = 0;
|
|
33
|
+
function getAudioContext(options) {
|
|
34
|
+
if (typeof window === "undefined" || typeof AudioContext === "undefined") {
|
|
35
|
+
throw new Error(
|
|
36
|
+
"AudioContext is not supported in this environment (browser only)."
|
|
37
|
+
);
|
|
38
|
+
}
|
|
39
|
+
if (!sharedContext || sharedContext.state === "closed") {
|
|
40
|
+
sharedContext = new AudioContext(options);
|
|
41
|
+
}
|
|
42
|
+
return sharedContext;
|
|
43
|
+
}
|
|
44
|
+
function registerPipeline() {
|
|
45
|
+
activePipelines++;
|
|
46
|
+
}
|
|
47
|
+
function unregisterPipeline() {
|
|
48
|
+
activePipelines = Math.max(0, activePipelines - 1);
|
|
49
|
+
}
|
|
50
|
+
async function resumeAudioContext() {
|
|
51
|
+
if (sharedContext && sharedContext.state === "suspended") {
|
|
52
|
+
await sharedContext.resume();
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
async function suspendAudioContext() {
|
|
56
|
+
if (sharedContext && sharedContext.state === "running") {
|
|
57
|
+
await sharedContext.suspend();
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
async function closeAudioContext() {
|
|
61
|
+
if (sharedContext && sharedContext.state !== "closed") {
|
|
62
|
+
await sharedContext.close();
|
|
63
|
+
}
|
|
64
|
+
sharedContext = null;
|
|
65
|
+
activePipelines = 0;
|
|
66
|
+
}
|
|
67
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
68
|
+
0 && (module.exports = {
|
|
69
|
+
closeAudioContext,
|
|
70
|
+
getAudioContext,
|
|
71
|
+
registerPipeline,
|
|
72
|
+
resumeAudioContext,
|
|
73
|
+
suspendAudioContext,
|
|
74
|
+
unregisterPipeline
|
|
75
|
+
});
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import {
|
|
2
|
+
closeAudioContext,
|
|
3
|
+
getAudioContext,
|
|
4
|
+
registerPipeline,
|
|
5
|
+
resumeAudioContext,
|
|
6
|
+
suspendAudioContext,
|
|
7
|
+
unregisterPipeline
|
|
8
|
+
} from "../chunk-OZ7KMC4S.mjs";
|
|
9
|
+
export {
|
|
10
|
+
closeAudioContext,
|
|
11
|
+
getAudioContext,
|
|
12
|
+
registerPipeline,
|
|
13
|
+
resumeAudioContext,
|
|
14
|
+
suspendAudioContext,
|
|
15
|
+
unregisterPipeline
|
|
16
|
+
};
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { NoiseSuppressionPlugin, VADPlugin } from '../types.mjs';
|
|
2
|
+
import 'mitt';
|
|
3
|
+
|
|
4
|
+
declare function registerNoiseSuppressionPlugin(plugin: NoiseSuppressionPlugin): void;
|
|
5
|
+
declare function registerVADPlugin(plugin: VADPlugin): void;
|
|
6
|
+
declare function getNoiseSuppressionPlugin(name?: string): NoiseSuppressionPlugin;
|
|
7
|
+
declare function getVADPlugin(name?: string): VADPlugin;
|
|
8
|
+
|
|
9
|
+
export { getNoiseSuppressionPlugin, getVADPlugin, registerNoiseSuppressionPlugin, registerVADPlugin };
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { NoiseSuppressionPlugin, VADPlugin } from '../types.js';
|
|
2
|
+
import 'mitt';
|
|
3
|
+
|
|
4
|
+
declare function registerNoiseSuppressionPlugin(plugin: NoiseSuppressionPlugin): void;
|
|
5
|
+
declare function registerVADPlugin(plugin: VADPlugin): void;
|
|
6
|
+
declare function getNoiseSuppressionPlugin(name?: string): NoiseSuppressionPlugin;
|
|
7
|
+
declare function getVADPlugin(name?: string): VADPlugin;
|
|
8
|
+
|
|
9
|
+
export { getNoiseSuppressionPlugin, getVADPlugin, registerNoiseSuppressionPlugin, registerVADPlugin };
|