@tensamin/audio 0.1.14 → 0.2.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/README.md +48 -231
- package/dist/chunk-6BJ4XGSA.mjs +80 -0
- package/dist/chunk-AQ5RVY33.mjs +74 -0
- package/dist/chunk-IS37FHDN.mjs +33 -0
- package/dist/chunk-K4J3UUOR.mjs +178 -0
- package/dist/chunk-QNQK6QFB.mjs +71 -0
- package/dist/context/audio-context.d.mts +0 -24
- package/dist/context/audio-context.d.ts +0 -24
- package/dist/index.d.mts +2 -8
- package/dist/index.d.ts +2 -8
- package/dist/index.js +285 -680
- package/dist/index.mjs +8 -43
- package/dist/livekit/integration.d.mts +3 -7
- package/dist/livekit/integration.d.ts +3 -7
- package/dist/livekit/integration.js +280 -626
- package/dist/livekit/integration.mjs +7 -8
- package/dist/noise-suppression/deepfilternet-node.d.mts +12 -0
- package/dist/noise-suppression/deepfilternet-node.d.ts +12 -0
- package/dist/noise-suppression/deepfilternet-node.js +57 -0
- package/dist/noise-suppression/deepfilternet-node.mjs +6 -0
- package/dist/pipeline/audio-pipeline.d.mts +2 -2
- package/dist/pipeline/audio-pipeline.d.ts +2 -2
- package/dist/pipeline/audio-pipeline.js +219 -554
- package/dist/pipeline/audio-pipeline.mjs +4 -5
- package/dist/types.d.mts +42 -257
- package/dist/types.d.ts +42 -257
- package/dist/vad/vad-node.d.mts +7 -9
- package/dist/vad/vad-node.d.ts +7 -9
- package/dist/vad/vad-node.js +47 -156
- package/dist/vad/vad-node.mjs +3 -3
- package/dist/vad/vad-state.d.mts +9 -11
- package/dist/vad/vad-state.d.ts +9 -11
- package/dist/vad/vad-state.js +50 -79
- package/dist/vad/vad-state.mjs +3 -3
- package/package.json +21 -21
- package/dist/chunk-2G2JFHJY.mjs +0 -180
- package/dist/chunk-6F2HZUYO.mjs +0 -91
- package/dist/chunk-K4YLH73B.mjs +0 -103
- package/dist/chunk-R5M2DGAQ.mjs +0 -311
- package/dist/chunk-UFKIAMG3.mjs +0 -47
- package/dist/chunk-XO6B3D4A.mjs +0 -67
- package/dist/extensibility/plugins.d.mts +0 -9
- package/dist/extensibility/plugins.d.ts +0 -9
- package/dist/extensibility/plugins.js +0 -320
- package/dist/extensibility/plugins.mjs +0 -14
- package/dist/noise-suppression/rnnoise-node.d.mts +0 -10
- package/dist/noise-suppression/rnnoise-node.d.ts +0 -10
- package/dist/noise-suppression/rnnoise-node.js +0 -101
- package/dist/noise-suppression/rnnoise-node.mjs +0 -6
package/dist/chunk-UFKIAMG3.mjs
DELETED
|
@@ -1,47 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
RNNoisePlugin
|
|
3
|
-
} from "./chunk-XO6B3D4A.mjs";
|
|
4
|
-
import {
|
|
5
|
-
EnergyVADPlugin
|
|
6
|
-
} from "./chunk-2G2JFHJY.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
|
-
};
|
package/dist/chunk-XO6B3D4A.mjs
DELETED
|
@@ -1,67 +0,0 @@
|
|
|
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
|
-
console.log("Noise suppression disabled, using passthrough node");
|
|
9
|
-
const pass = context.createGain();
|
|
10
|
-
return pass;
|
|
11
|
-
}
|
|
12
|
-
if (!config?.wasmUrl || !config?.simdUrl || !config?.workletUrl) {
|
|
13
|
-
const error = new Error(
|
|
14
|
-
`RNNoisePlugin requires 'wasmUrl', 'simdUrl', and 'workletUrl' to be configured. Please download the assets from @sapphi-red/web-noise-suppressor and provide the URLs in the config. Current config: wasmUrl=${config?.wasmUrl}, simdUrl=${config?.simdUrl}, workletUrl=${config?.workletUrl}
|
|
15
|
-
To disable noise suppression, set noiseSuppression.enabled to false.`
|
|
16
|
-
);
|
|
17
|
-
console.error(error.message);
|
|
18
|
-
throw error;
|
|
19
|
-
}
|
|
20
|
-
try {
|
|
21
|
-
if (!this.wasmBuffer) {
|
|
22
|
-
console.log("Loading RNNoise WASM binary...");
|
|
23
|
-
this.wasmBuffer = await loadRnnoise({
|
|
24
|
-
url: config.wasmUrl,
|
|
25
|
-
simdUrl: config.simdUrl
|
|
26
|
-
});
|
|
27
|
-
console.log("RNNoise WASM loaded successfully");
|
|
28
|
-
}
|
|
29
|
-
} catch (error) {
|
|
30
|
-
const err = new Error(
|
|
31
|
-
`Failed to load RNNoise WASM binary: ${error instanceof Error ? error.message : String(error)}`
|
|
32
|
-
);
|
|
33
|
-
console.error(err);
|
|
34
|
-
throw err;
|
|
35
|
-
}
|
|
36
|
-
const workletUrl = config.workletUrl;
|
|
37
|
-
try {
|
|
38
|
-
await context.audioWorklet.addModule(workletUrl);
|
|
39
|
-
console.log("RNNoise worklet loaded successfully");
|
|
40
|
-
} catch (e) {
|
|
41
|
-
const error = new Error(
|
|
42
|
-
`Failed to load RNNoise worklet from ${workletUrl}: ${e instanceof Error ? e.message : String(e)}. Ensure the workletUrl points to a valid RNNoise worklet script.`
|
|
43
|
-
);
|
|
44
|
-
console.error(error.message);
|
|
45
|
-
throw error;
|
|
46
|
-
}
|
|
47
|
-
try {
|
|
48
|
-
const node = new RnnoiseWorkletNode(context, {
|
|
49
|
-
wasmBinary: this.wasmBuffer,
|
|
50
|
-
maxChannels: 1
|
|
51
|
-
// Mono for now
|
|
52
|
-
});
|
|
53
|
-
console.log("RNNoise worklet node created successfully");
|
|
54
|
-
return node;
|
|
55
|
-
} catch (error) {
|
|
56
|
-
const err = new Error(
|
|
57
|
-
`Failed to create RNNoise worklet node: ${error instanceof Error ? error.message : String(error)}`
|
|
58
|
-
);
|
|
59
|
-
console.error(err);
|
|
60
|
-
throw err;
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
};
|
|
64
|
-
|
|
65
|
-
export {
|
|
66
|
-
RNNoisePlugin
|
|
67
|
-
};
|
|
@@ -1,9 +0,0 @@
|
|
|
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 };
|
|
@@ -1,9 +0,0 @@
|
|
|
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 };
|
|
@@ -1,320 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
var __create = Object.create;
|
|
3
|
-
var __defProp = Object.defineProperty;
|
|
4
|
-
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
-
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
-
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
-
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
-
var __export = (target, all) => {
|
|
9
|
-
for (var name in all)
|
|
10
|
-
__defProp(target, name, { get: all[name], enumerable: true });
|
|
11
|
-
};
|
|
12
|
-
var __copyProps = (to, from, except, desc) => {
|
|
13
|
-
if (from && typeof from === "object" || typeof from === "function") {
|
|
14
|
-
for (let key of __getOwnPropNames(from))
|
|
15
|
-
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
16
|
-
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
17
|
-
}
|
|
18
|
-
return to;
|
|
19
|
-
};
|
|
20
|
-
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
21
|
-
// If the importer is in node compatibility mode or this is not an ESM
|
|
22
|
-
// file that has been converted to a CommonJS file using a Babel-
|
|
23
|
-
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
24
|
-
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
25
|
-
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
26
|
-
mod
|
|
27
|
-
));
|
|
28
|
-
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
29
|
-
|
|
30
|
-
// src/extensibility/plugins.ts
|
|
31
|
-
var plugins_exports = {};
|
|
32
|
-
__export(plugins_exports, {
|
|
33
|
-
getNoiseSuppressionPlugin: () => getNoiseSuppressionPlugin,
|
|
34
|
-
getVADPlugin: () => getVADPlugin,
|
|
35
|
-
registerNoiseSuppressionPlugin: () => registerNoiseSuppressionPlugin,
|
|
36
|
-
registerVADPlugin: () => registerVADPlugin
|
|
37
|
-
});
|
|
38
|
-
module.exports = __toCommonJS(plugins_exports);
|
|
39
|
-
|
|
40
|
-
// src/noise-suppression/rnnoise-node.ts
|
|
41
|
-
var RNNoisePlugin = class {
|
|
42
|
-
name = "rnnoise-ns";
|
|
43
|
-
wasmBuffer = null;
|
|
44
|
-
async createNode(context, config) {
|
|
45
|
-
const { loadRnnoise, RnnoiseWorkletNode } = await import("@sapphi-red/web-noise-suppressor");
|
|
46
|
-
if (!config?.enabled) {
|
|
47
|
-
console.log("Noise suppression disabled, using passthrough node");
|
|
48
|
-
const pass = context.createGain();
|
|
49
|
-
return pass;
|
|
50
|
-
}
|
|
51
|
-
if (!config?.wasmUrl || !config?.simdUrl || !config?.workletUrl) {
|
|
52
|
-
const error = new Error(
|
|
53
|
-
`RNNoisePlugin requires 'wasmUrl', 'simdUrl', and 'workletUrl' to be configured. Please download the assets from @sapphi-red/web-noise-suppressor and provide the URLs in the config. Current config: wasmUrl=${config?.wasmUrl}, simdUrl=${config?.simdUrl}, workletUrl=${config?.workletUrl}
|
|
54
|
-
To disable noise suppression, set noiseSuppression.enabled to false.`
|
|
55
|
-
);
|
|
56
|
-
console.error(error.message);
|
|
57
|
-
throw error;
|
|
58
|
-
}
|
|
59
|
-
try {
|
|
60
|
-
if (!this.wasmBuffer) {
|
|
61
|
-
console.log("Loading RNNoise WASM binary...");
|
|
62
|
-
this.wasmBuffer = await loadRnnoise({
|
|
63
|
-
url: config.wasmUrl,
|
|
64
|
-
simdUrl: config.simdUrl
|
|
65
|
-
});
|
|
66
|
-
console.log("RNNoise WASM loaded successfully");
|
|
67
|
-
}
|
|
68
|
-
} catch (error) {
|
|
69
|
-
const err = new Error(
|
|
70
|
-
`Failed to load RNNoise WASM binary: ${error instanceof Error ? error.message : String(error)}`
|
|
71
|
-
);
|
|
72
|
-
console.error(err);
|
|
73
|
-
throw err;
|
|
74
|
-
}
|
|
75
|
-
const workletUrl = config.workletUrl;
|
|
76
|
-
try {
|
|
77
|
-
await context.audioWorklet.addModule(workletUrl);
|
|
78
|
-
console.log("RNNoise worklet loaded successfully");
|
|
79
|
-
} catch (e) {
|
|
80
|
-
const error = new Error(
|
|
81
|
-
`Failed to load RNNoise worklet from ${workletUrl}: ${e instanceof Error ? e.message : String(e)}. Ensure the workletUrl points to a valid RNNoise worklet script.`
|
|
82
|
-
);
|
|
83
|
-
console.error(error.message);
|
|
84
|
-
throw error;
|
|
85
|
-
}
|
|
86
|
-
try {
|
|
87
|
-
const node = new RnnoiseWorkletNode(context, {
|
|
88
|
-
wasmBinary: this.wasmBuffer,
|
|
89
|
-
maxChannels: 1
|
|
90
|
-
// Mono for now
|
|
91
|
-
});
|
|
92
|
-
console.log("RNNoise worklet node created successfully");
|
|
93
|
-
return node;
|
|
94
|
-
} catch (error) {
|
|
95
|
-
const err = new Error(
|
|
96
|
-
`Failed to create RNNoise worklet node: ${error instanceof Error ? error.message : String(error)}`
|
|
97
|
-
);
|
|
98
|
-
console.error(err);
|
|
99
|
-
throw err;
|
|
100
|
-
}
|
|
101
|
-
}
|
|
102
|
-
};
|
|
103
|
-
|
|
104
|
-
// src/vad/vad-node.ts
|
|
105
|
-
var createEnergyVadWorkletCode = (vadConfig) => {
|
|
106
|
-
const energyParams = vadConfig?.energyVad || {};
|
|
107
|
-
const smoothing = energyParams.smoothing ?? 0.95;
|
|
108
|
-
const initialNoiseFloor = energyParams.initialNoiseFloor ?? 1e-3;
|
|
109
|
-
const noiseFloorAdaptRateQuiet = energyParams.noiseFloorAdaptRateQuiet ?? 2e-3;
|
|
110
|
-
const noiseFloorAdaptRateLoud = energyParams.noiseFloorAdaptRateLoud ?? 0.02;
|
|
111
|
-
const minSNR = energyParams.minSNR ?? 12;
|
|
112
|
-
const snrRange = energyParams.snrRange ?? 10;
|
|
113
|
-
const minEnergy = energyParams.minEnergy ?? 3e-3;
|
|
114
|
-
return `
|
|
115
|
-
class EnergyVadProcessor extends AudioWorkletProcessor {
|
|
116
|
-
constructor() {
|
|
117
|
-
super();
|
|
118
|
-
this.smoothing = ${smoothing};
|
|
119
|
-
this.energy = 0;
|
|
120
|
-
this.noiseFloor = ${initialNoiseFloor};
|
|
121
|
-
this.noiseFloorAdaptRateQuiet = ${noiseFloorAdaptRateQuiet};
|
|
122
|
-
this.noiseFloorAdaptRateLoud = ${noiseFloorAdaptRateLoud};
|
|
123
|
-
this.minSNR = ${minSNR};
|
|
124
|
-
this.snrRange = ${snrRange};
|
|
125
|
-
this.minEnergy = ${minEnergy};
|
|
126
|
-
this.isSpeaking = false;
|
|
127
|
-
|
|
128
|
-
this.port.onmessage = (event) => {
|
|
129
|
-
if (event.data && event.data.isSpeaking !== undefined) {
|
|
130
|
-
this.isSpeaking = event.data.isSpeaking;
|
|
131
|
-
}
|
|
132
|
-
};
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
process(inputs, outputs, parameters) {
|
|
136
|
-
const input = inputs[0];
|
|
137
|
-
if (!input || !input.length) return true;
|
|
138
|
-
const channel = input[0];
|
|
139
|
-
|
|
140
|
-
// Calculate instantaneous RMS (Root Mean Square) energy
|
|
141
|
-
let sum = 0;
|
|
142
|
-
let peak = 0;
|
|
143
|
-
for (let i = 0; i < channel.length; i++) {
|
|
144
|
-
const sample = Math.abs(channel[i]);
|
|
145
|
-
sum += channel[i] * channel[i];
|
|
146
|
-
peak = Math.max(peak, sample);
|
|
147
|
-
}
|
|
148
|
-
const instantRms = Math.sqrt(sum / channel.length);
|
|
149
|
-
|
|
150
|
-
// Smooth the RMS energy to reduce jitter
|
|
151
|
-
// this.energy acts as the smoothed RMS value
|
|
152
|
-
this.energy = this.energy * this.smoothing + instantRms * (1 - this.smoothing);
|
|
153
|
-
|
|
154
|
-
// Calculate Crest Factor (peak-to-RMS ratio)
|
|
155
|
-
// Voice typically has crest factor of 2-4 (6-12dB)
|
|
156
|
-
// Keyboard clicks have crest factor of 10-30+ (20-30dB)
|
|
157
|
-
const crestFactor = peak / (instantRms + 1e-10);
|
|
158
|
-
const crestFactorDb = 20 * Math.log10(Math.max(1e-6, crestFactor));
|
|
159
|
-
|
|
160
|
-
// Adaptive noise floor estimation using SMOOTHED energy (not instantaneous)
|
|
161
|
-
// This prevents sharp transients from affecting the noise floor
|
|
162
|
-
if (this.energy < this.noiseFloor) {
|
|
163
|
-
// Signal is quieter than noise floor, adapt downwards slowly
|
|
164
|
-
this.noiseFloor = this.noiseFloor * (1 - this.noiseFloorAdaptRateQuiet) + this.energy * this.noiseFloorAdaptRateQuiet;
|
|
165
|
-
} else {
|
|
166
|
-
// Calculate SNR based on smoothed energy
|
|
167
|
-
const smoothedSnr = this.energy / (this.noiseFloor + 1e-6);
|
|
168
|
-
const smoothedSnrDb = 20 * Math.log10(Math.max(1e-6, smoothedSnr));
|
|
169
|
-
|
|
170
|
-
// Only adapt upwards if:
|
|
171
|
-
// 1. SNR is low (< 10dB) - likely just background noise
|
|
172
|
-
// 2. AND crest factor is low (< 15dB) - not a sharp transient
|
|
173
|
-
if (smoothedSnrDb < 10 && crestFactorDb < 15) {
|
|
174
|
-
// This is persistent background noise, adapt upwards
|
|
175
|
-
this.noiseFloor = this.noiseFloor * (1 - this.noiseFloorAdaptRateLoud) + this.energy * this.noiseFloorAdaptRateLoud;
|
|
176
|
-
} else {
|
|
177
|
-
// Either high SNR (speech) or high crest factor (click) - adapt very slowly
|
|
178
|
-
const slowRate = this.noiseFloorAdaptRateLoud * 0.01;
|
|
179
|
-
this.noiseFloor = this.noiseFloor * (1 - slowRate) + this.energy * slowRate;
|
|
180
|
-
}
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
// Ensure noise floor doesn't drop to absolute zero
|
|
184
|
-
this.noiseFloor = Math.max(this.noiseFloor, 0.0001);
|
|
185
|
-
|
|
186
|
-
// SECOND PASS: Calculate Signal-to-Noise Ratio (SNR) in dB using smoothed energy
|
|
187
|
-
const snr = this.energy / (this.noiseFloor + 1e-6);
|
|
188
|
-
const snrDb = 20 * Math.log10(Math.max(1e-6, snr));
|
|
189
|
-
|
|
190
|
-
// Map SNR dB to probability (0-1)
|
|
191
|
-
// Probability is 0 when snrDb <= minSNR
|
|
192
|
-
// Probability scales linearly from 0 to 1 between minSNR and (minSNR + snrRange)
|
|
193
|
-
let probability = Math.min(1, Math.max(0, (snrDb - this.minSNR) / this.snrRange));
|
|
194
|
-
|
|
195
|
-
// Apply absolute energy threshold with soft knee
|
|
196
|
-
if (this.energy < this.minEnergy) {
|
|
197
|
-
const energyRatio = this.energy / (this.minEnergy + 1e-6);
|
|
198
|
-
probability *= Math.pow(energyRatio, 2);
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
// Apply crest factor penalty
|
|
202
|
-
// Reject signals with high crest factor (sharp transients like keyboard clicks)
|
|
203
|
-
// Voice: 6-12dB, Keyboard: 20-30dB
|
|
204
|
-
// We penalize anything above 14dB
|
|
205
|
-
if (crestFactorDb > 14) {
|
|
206
|
-
const excess = crestFactorDb - 14;
|
|
207
|
-
const penalty = Math.max(0, 1 - (excess / 10)); // Linear falloff over 10dB
|
|
208
|
-
probability *= penalty;
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
this.port.postMessage({ probability, snr: snrDb, noiseFloor: this.noiseFloor, rms: this.energy });
|
|
212
|
-
|
|
213
|
-
return true;
|
|
214
|
-
}
|
|
215
|
-
}
|
|
216
|
-
registerProcessor('energy-vad-processor', EnergyVadProcessor);
|
|
217
|
-
`;
|
|
218
|
-
};
|
|
219
|
-
var EnergyVADPlugin = class {
|
|
220
|
-
name = "energy-vad";
|
|
221
|
-
workletNode = null;
|
|
222
|
-
async createNode(context, config, onDecision) {
|
|
223
|
-
if (!config?.enabled) {
|
|
224
|
-
console.log("VAD disabled, using passthrough node");
|
|
225
|
-
const pass = context.createGain();
|
|
226
|
-
return pass;
|
|
227
|
-
}
|
|
228
|
-
const workletCode = createEnergyVadWorkletCode(config);
|
|
229
|
-
const blob = new Blob([workletCode], {
|
|
230
|
-
type: "application/javascript"
|
|
231
|
-
});
|
|
232
|
-
const url = URL.createObjectURL(blob);
|
|
233
|
-
try {
|
|
234
|
-
await context.audioWorklet.addModule(url);
|
|
235
|
-
console.log("Energy VAD worklet loaded successfully");
|
|
236
|
-
} catch (e) {
|
|
237
|
-
const error = new Error(
|
|
238
|
-
`Failed to load Energy VAD worklet: ${e instanceof Error ? e.message : String(e)}`
|
|
239
|
-
);
|
|
240
|
-
console.error(error.message);
|
|
241
|
-
URL.revokeObjectURL(url);
|
|
242
|
-
throw error;
|
|
243
|
-
}
|
|
244
|
-
URL.revokeObjectURL(url);
|
|
245
|
-
let node;
|
|
246
|
-
try {
|
|
247
|
-
node = new AudioWorkletNode(context, "energy-vad-processor");
|
|
248
|
-
this.workletNode = node;
|
|
249
|
-
console.log("Energy VAD node created successfully");
|
|
250
|
-
} catch (e) {
|
|
251
|
-
const error = new Error(
|
|
252
|
-
`Failed to create Energy VAD node: ${e instanceof Error ? e.message : String(e)}`
|
|
253
|
-
);
|
|
254
|
-
console.error(error.message);
|
|
255
|
-
throw error;
|
|
256
|
-
}
|
|
257
|
-
node.port.onmessage = (event) => {
|
|
258
|
-
try {
|
|
259
|
-
const { probability } = event.data;
|
|
260
|
-
if (typeof probability === "number" && !isNaN(probability)) {
|
|
261
|
-
onDecision(probability);
|
|
262
|
-
} else {
|
|
263
|
-
console.warn("Invalid VAD probability received:", event.data);
|
|
264
|
-
}
|
|
265
|
-
} catch (error) {
|
|
266
|
-
console.error("Error in VAD message handler:", error);
|
|
267
|
-
}
|
|
268
|
-
};
|
|
269
|
-
node.port.onmessageerror = (event) => {
|
|
270
|
-
console.error("VAD port message error:", event);
|
|
271
|
-
};
|
|
272
|
-
return node;
|
|
273
|
-
}
|
|
274
|
-
updateSpeakingState(isSpeaking) {
|
|
275
|
-
if (this.workletNode) {
|
|
276
|
-
this.workletNode.port.postMessage({ isSpeaking });
|
|
277
|
-
}
|
|
278
|
-
}
|
|
279
|
-
};
|
|
280
|
-
|
|
281
|
-
// src/extensibility/plugins.ts
|
|
282
|
-
var nsPlugins = /* @__PURE__ */ new Map();
|
|
283
|
-
var vadPlugins = /* @__PURE__ */ new Map();
|
|
284
|
-
var defaultNs = new RNNoisePlugin();
|
|
285
|
-
nsPlugins.set(defaultNs.name, defaultNs);
|
|
286
|
-
var defaultVad = new EnergyVADPlugin();
|
|
287
|
-
vadPlugins.set(defaultVad.name, defaultVad);
|
|
288
|
-
function registerNoiseSuppressionPlugin(plugin) {
|
|
289
|
-
nsPlugins.set(plugin.name, plugin);
|
|
290
|
-
}
|
|
291
|
-
function registerVADPlugin(plugin) {
|
|
292
|
-
vadPlugins.set(plugin.name, plugin);
|
|
293
|
-
}
|
|
294
|
-
function getNoiseSuppressionPlugin(name) {
|
|
295
|
-
if (!name) return defaultNs;
|
|
296
|
-
const plugin = nsPlugins.get(name);
|
|
297
|
-
if (!plugin) {
|
|
298
|
-
console.warn(
|
|
299
|
-
`Noise suppression plugin '${name}' not found, falling back to default.`
|
|
300
|
-
);
|
|
301
|
-
return defaultNs;
|
|
302
|
-
}
|
|
303
|
-
return plugin;
|
|
304
|
-
}
|
|
305
|
-
function getVADPlugin(name) {
|
|
306
|
-
if (!name) return defaultVad;
|
|
307
|
-
const plugin = vadPlugins.get(name);
|
|
308
|
-
if (!plugin) {
|
|
309
|
-
console.warn(`VAD plugin '${name}' not found, falling back to default.`);
|
|
310
|
-
return defaultVad;
|
|
311
|
-
}
|
|
312
|
-
return plugin;
|
|
313
|
-
}
|
|
314
|
-
// Annotate the CommonJS export names for ESM import in node:
|
|
315
|
-
0 && (module.exports = {
|
|
316
|
-
getNoiseSuppressionPlugin,
|
|
317
|
-
getVADPlugin,
|
|
318
|
-
registerNoiseSuppressionPlugin,
|
|
319
|
-
registerVADPlugin
|
|
320
|
-
});
|
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
getNoiseSuppressionPlugin,
|
|
3
|
-
getVADPlugin,
|
|
4
|
-
registerNoiseSuppressionPlugin,
|
|
5
|
-
registerVADPlugin
|
|
6
|
-
} from "../chunk-UFKIAMG3.mjs";
|
|
7
|
-
import "../chunk-XO6B3D4A.mjs";
|
|
8
|
-
import "../chunk-2G2JFHJY.mjs";
|
|
9
|
-
export {
|
|
10
|
-
getNoiseSuppressionPlugin,
|
|
11
|
-
getVADPlugin,
|
|
12
|
-
registerNoiseSuppressionPlugin,
|
|
13
|
-
registerVADPlugin
|
|
14
|
-
};
|
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
import { NoiseSuppressionPlugin, AudioProcessingConfig } from '../types.mjs';
|
|
2
|
-
import 'mitt';
|
|
3
|
-
|
|
4
|
-
declare class RNNoisePlugin implements NoiseSuppressionPlugin {
|
|
5
|
-
name: string;
|
|
6
|
-
private wasmBuffer;
|
|
7
|
-
createNode(context: AudioContext, config: AudioProcessingConfig["noiseSuppression"]): Promise<AudioNode>;
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
export { RNNoisePlugin };
|
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
import { NoiseSuppressionPlugin, AudioProcessingConfig } from '../types.js';
|
|
2
|
-
import 'mitt';
|
|
3
|
-
|
|
4
|
-
declare class RNNoisePlugin implements NoiseSuppressionPlugin {
|
|
5
|
-
name: string;
|
|
6
|
-
private wasmBuffer;
|
|
7
|
-
createNode(context: AudioContext, config: AudioProcessingConfig["noiseSuppression"]): Promise<AudioNode>;
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
export { RNNoisePlugin };
|
|
@@ -1,101 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
var __create = Object.create;
|
|
3
|
-
var __defProp = Object.defineProperty;
|
|
4
|
-
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
-
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
-
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
-
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
-
var __export = (target, all) => {
|
|
9
|
-
for (var name in all)
|
|
10
|
-
__defProp(target, name, { get: all[name], enumerable: true });
|
|
11
|
-
};
|
|
12
|
-
var __copyProps = (to, from, except, desc) => {
|
|
13
|
-
if (from && typeof from === "object" || typeof from === "function") {
|
|
14
|
-
for (let key of __getOwnPropNames(from))
|
|
15
|
-
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
16
|
-
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
17
|
-
}
|
|
18
|
-
return to;
|
|
19
|
-
};
|
|
20
|
-
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
21
|
-
// If the importer is in node compatibility mode or this is not an ESM
|
|
22
|
-
// file that has been converted to a CommonJS file using a Babel-
|
|
23
|
-
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
24
|
-
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
25
|
-
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
26
|
-
mod
|
|
27
|
-
));
|
|
28
|
-
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
29
|
-
|
|
30
|
-
// src/noise-suppression/rnnoise-node.ts
|
|
31
|
-
var rnnoise_node_exports = {};
|
|
32
|
-
__export(rnnoise_node_exports, {
|
|
33
|
-
RNNoisePlugin: () => RNNoisePlugin
|
|
34
|
-
});
|
|
35
|
-
module.exports = __toCommonJS(rnnoise_node_exports);
|
|
36
|
-
var RNNoisePlugin = class {
|
|
37
|
-
name = "rnnoise-ns";
|
|
38
|
-
wasmBuffer = null;
|
|
39
|
-
async createNode(context, config) {
|
|
40
|
-
const { loadRnnoise, RnnoiseWorkletNode } = await import("@sapphi-red/web-noise-suppressor");
|
|
41
|
-
if (!config?.enabled) {
|
|
42
|
-
console.log("Noise suppression disabled, using passthrough node");
|
|
43
|
-
const pass = context.createGain();
|
|
44
|
-
return pass;
|
|
45
|
-
}
|
|
46
|
-
if (!config?.wasmUrl || !config?.simdUrl || !config?.workletUrl) {
|
|
47
|
-
const error = new Error(
|
|
48
|
-
`RNNoisePlugin requires 'wasmUrl', 'simdUrl', and 'workletUrl' to be configured. Please download the assets from @sapphi-red/web-noise-suppressor and provide the URLs in the config. Current config: wasmUrl=${config?.wasmUrl}, simdUrl=${config?.simdUrl}, workletUrl=${config?.workletUrl}
|
|
49
|
-
To disable noise suppression, set noiseSuppression.enabled to false.`
|
|
50
|
-
);
|
|
51
|
-
console.error(error.message);
|
|
52
|
-
throw error;
|
|
53
|
-
}
|
|
54
|
-
try {
|
|
55
|
-
if (!this.wasmBuffer) {
|
|
56
|
-
console.log("Loading RNNoise WASM binary...");
|
|
57
|
-
this.wasmBuffer = await loadRnnoise({
|
|
58
|
-
url: config.wasmUrl,
|
|
59
|
-
simdUrl: config.simdUrl
|
|
60
|
-
});
|
|
61
|
-
console.log("RNNoise WASM loaded successfully");
|
|
62
|
-
}
|
|
63
|
-
} catch (error) {
|
|
64
|
-
const err = new Error(
|
|
65
|
-
`Failed to load RNNoise WASM binary: ${error instanceof Error ? error.message : String(error)}`
|
|
66
|
-
);
|
|
67
|
-
console.error(err);
|
|
68
|
-
throw err;
|
|
69
|
-
}
|
|
70
|
-
const workletUrl = config.workletUrl;
|
|
71
|
-
try {
|
|
72
|
-
await context.audioWorklet.addModule(workletUrl);
|
|
73
|
-
console.log("RNNoise worklet loaded successfully");
|
|
74
|
-
} catch (e) {
|
|
75
|
-
const error = new Error(
|
|
76
|
-
`Failed to load RNNoise worklet from ${workletUrl}: ${e instanceof Error ? e.message : String(e)}. Ensure the workletUrl points to a valid RNNoise worklet script.`
|
|
77
|
-
);
|
|
78
|
-
console.error(error.message);
|
|
79
|
-
throw error;
|
|
80
|
-
}
|
|
81
|
-
try {
|
|
82
|
-
const node = new RnnoiseWorkletNode(context, {
|
|
83
|
-
wasmBinary: this.wasmBuffer,
|
|
84
|
-
maxChannels: 1
|
|
85
|
-
// Mono for now
|
|
86
|
-
});
|
|
87
|
-
console.log("RNNoise worklet node created successfully");
|
|
88
|
-
return node;
|
|
89
|
-
} catch (error) {
|
|
90
|
-
const err = new Error(
|
|
91
|
-
`Failed to create RNNoise worklet node: ${error instanceof Error ? error.message : String(error)}`
|
|
92
|
-
);
|
|
93
|
-
console.error(err);
|
|
94
|
-
throw err;
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
};
|
|
98
|
-
// Annotate the CommonJS export names for ESM import in node:
|
|
99
|
-
0 && (module.exports = {
|
|
100
|
-
RNNoisePlugin
|
|
101
|
-
});
|