decibri 5.2.4 → 5.3.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 CHANGED
@@ -9,6 +9,22 @@ For other decibri packages, see:
9
9
  - Rust core: [crates/decibri/CHANGELOG.md](../../crates/decibri/CHANGELOG.md)
10
10
  - Python package: [bindings/python/CHANGELOG.md](../../bindings/python/CHANGELOG.md)
11
11
 
12
+ ## [5.3.0] - 2026-07-31
13
+
14
+ ### Added
15
+
16
+ - `aec` option on `Microphone`: acoustic echo cancellation on the capture path. The short form names the model (`aec: 'tau'`); the object form takes `{ model, tailMs, suppression, referenceSampleRate }`. It runs before the detector tap, so `vadScore` and the `speech` / `silence` events read the echo-removed signal, and it requires `sampleRate` in 8000 to 48000. Native capture only: the browser entry keeps the platform's own `echoCancellation` constraint.
17
+ - `Microphone.pushAecReference(data)`, which queues the far-end audio the canceller cancels against: the same input shapes `Speaker.write` accepts, mono, in played order, at the declared `referenceSampleRate`. It never blocks and never throws on a full queue. When it is pushed does not have to match when it plays: the queue is read at the rate the capture consumes it, so a greeting pushed before the first `data` event, or a whole utterance handed over in one call, is read out over the capture it echoes into and every sample of it is cancelled against. The queue holds two seconds, which bounds how far ahead of its own capture a caller may run.
18
+ - `Microphone.aecMetrics()`, the canceller's transport and cancellation metrics merged with the reference queue's counters, or `null` while echo cancellation is off or capture is not running.
19
+
20
+ Echo cancellation joins automatic gain control as a stage that can drive captured samples above full scale when the limiter is off, because it subtracts its estimate of the echo from the capture and exceeds the capture wherever that estimate is wrong in phase. The limiter runs after it and bounds the output to its ceiling; the `int16` sample format clamps, so an over-scale sample arrives as full scale rather than wrapping, and a `float32` consumer without the limiter should clamp its own output.
21
+
22
+ ## [5.2.5] - 2026-07-28
23
+
24
+ ### Changed
25
+
26
+ - The packaged addon is rebuilt from the current core, and the bundle regenerate command in `examples/README.md` reproduces the committed file.
27
+
12
28
  ## [5.2.4] - 2026-07-28
13
29
 
14
30
  ### Added
@@ -71,10 +71,15 @@ npx localtunnel --port 8080
71
71
  ### Regenerating the browser bundle
72
72
 
73
73
  `decibri.browser.js` is generated from the package's browser entry
74
- (`../src/browser/index.js`) with rolldown, the bundler used to produce the
75
- checked-in build. Regenerate it after changing the browser source so the
76
- bundle stays in sync:
74
+ (`npm/decibri/src/browser/index.js`) with rolldown, the bundler used to produce
75
+ the checked-in build. Regenerate it after changing the browser source so the
76
+ bundle stays in sync.
77
+
78
+ Run the command from the repository root, with both paths repo-root relative.
79
+ Run from anywhere else and the bundle still builds, but it records the module
80
+ paths relative to that directory instead. The `--banner` flag carries the
81
+ generated file's first line:
77
82
 
78
83
  ```bash
79
- npx rolldown ../src/browser/index.js --format iife --name decibri --file decibri.browser.js
84
+ npx rolldown npm/decibri/src/browser/index.js --format iife --name decibri --file npm/decibri/examples/decibri.browser.js --banner "// decibri browser bundle. GENERATED from src/browser/index.js. Do not edit by hand; see examples/README.md to regenerate."
80
85
  ```
@@ -70,7 +70,7 @@ var decibri = (function() {
70
70
  var require_decibri_browser = /* @__PURE__ */ __commonJSMin(((exports, module) => {
71
71
  const { Emitter } = require_emitter();
72
72
  const { WORKLET_SOURCE } = require_worklet_inline();
73
- const VERSION = "5.2.4";
73
+ const VERSION = "5.3.0";
74
74
  /**
75
75
  * Browser microphone capture.
76
76
  *
package/index.d.ts CHANGED
@@ -29,6 +29,21 @@ export declare class DecibriBridge {
29
29
  * number for any realistic count) to match the `vadProbability` getter.
30
30
  */
31
31
  get overrunCount(): number
32
+ /**
33
+ * Queue far-end reference audio for the echo canceller. `buffer` is mono
34
+ * PCM bytes in the bridge's configured format, at the declared reference
35
+ * rate, in played order. Never blocks and never fails: a full queue
36
+ * discards and counts rather than erroring, and a push with no active
37
+ * stream (not started, stopped, or echo cancellation off) is a no-op.
38
+ */
39
+ pushAecReference(buffer: Buffer): void
40
+ /**
41
+ * The echo canceller's transport and cancellation metrics, merged with the
42
+ * reference queue's own counters, or `null` when no stream is active or
43
+ * echo cancellation is off. Counters are returned as f64 (exact JS numbers
44
+ * for any realistic count), matching the `overrunCount` getter.
45
+ */
46
+ aecMetrics(): AecMetricsJs | null
32
47
  /** List all available audio input devices. */
33
48
  static devices(): Array<DeviceInfoJs>
34
49
  /** Version information. */
@@ -148,6 +163,62 @@ export declare class FileHandle {
148
163
  get inputRate(): number
149
164
  }
150
165
 
166
+ /**
167
+ * Echo-cancellation metrics returned to JS by `aecMetrics()`. One object
168
+ * carries the canceller's own report and the reference queue's counters, so a
169
+ * caller reads one surface for the whole diagnosis.
170
+ */
171
+ export interface AecMetricsJs {
172
+ /**
173
+ * The active delay alignment in samples, or `null` while the estimator is
174
+ * still searching. Staying `null` while `acquisitionParked` climbs is the
175
+ * signature of a canceller with no usable reference: none pushed, not at
176
+ * the declared rate, or not the signal that produced the echo.
177
+ */
178
+ delaySamples?: number
179
+ /**
180
+ * Smoothed echo-return-loss-enhancement estimate in dB: how much echo the
181
+ * canceller is currently removing. 0 before the filter has converged.
182
+ */
183
+ erleDb: number
184
+ /**
185
+ * Whether the double-talk detector currently believes the near-end talker
186
+ * is active; adaptation is held while true.
187
+ */
188
+ doubleTalk: boolean
189
+ /**
190
+ * Near-end samples the canceller could find no far-end sample for while an
191
+ * alignment was active. The core keeps the far-end stream level with the
192
+ * capture, so this stays 0 for a caller who simply stops pushing; a
193
+ * non-zero count means the caller ran further ahead of the capture than
194
+ * the canceller's far-end history reaches.
195
+ */
196
+ referenceStarved: number
197
+ /**
198
+ * Near-end samples processed while no delay alignment was active: the
199
+ * searching span, not a transport failure.
200
+ */
201
+ acquisitionParked: number
202
+ /**
203
+ * Times the canceller inferred a capture discontinuity and rebuilt its
204
+ * alignment from the reference frontier.
205
+ */
206
+ referenceReanchors: number
207
+ /**
208
+ * Far-end samples discarded because a single push exceeded the reference
209
+ * queue's bound, at the declared reference rate. The span they occupied is
210
+ * still represented as silence, so a discard costs the cancellation of
211
+ * that span alone.
212
+ */
213
+ referenceDropped: number
214
+ /**
215
+ * Far-end samples the core supplied as silence because the caller had
216
+ * pushed none for them, at the capture rate. An accounting figure, not a
217
+ * fault: while nothing is playing, the far end is silence.
218
+ */
219
+ referenceSilence: number
220
+ }
221
+
151
222
  /**
152
223
  * Options passed from JS constructor.
153
224
  *
@@ -215,6 +286,34 @@ export interface DecibriOptions {
215
286
  * DSP: no bundled file and no model path, like `agc`.
216
287
  */
217
288
  limiter?: number
289
+ /**
290
+ * Echo canceller model name. The accepted set is owned by the canceller
291
+ * (`AecModel::from_str`); today it is `'tau'`. Absent leaves echo
292
+ * cancellation off. The JS wrapper resolves both public forms (the string
293
+ * shorthand and the `AecOptions` object) into this field and the three
294
+ * below. Pure DSP: no bundled file and no model path, like `highpass`.
295
+ */
296
+ aec?: string
297
+ /**
298
+ * Echo canceller filter tail in milliseconds: an integer in `16..=500`.
299
+ * Absent takes the canceller's own default. Consulted only when `aec`
300
+ * names a model.
301
+ */
302
+ aecTailMs?: number
303
+ /**
304
+ * Residual-suppression policy for the echo canceller: `'conservative'` or
305
+ * `'off'`. Absent takes the canceller's own default. Consulted only when
306
+ * `aec` names a model.
307
+ */
308
+ aecSuppression?: string
309
+ /**
310
+ * Sample rate in Hz of the far-end reference pushed through
311
+ * `pushAecReference`, in `1000..=384000`. Absent means the reference is
312
+ * already at the capture rate; when it names a different rate, the core
313
+ * converts the reference before the canceller sees it. Consulted only
314
+ * when `aec` names a model.
315
+ */
316
+ aecReferenceSampleRate?: number
218
317
  }
219
318
 
220
319
  /** Options passed from JS constructor for output. */
package/index.js CHANGED
@@ -77,8 +77,8 @@ function requireNative() {
77
77
  try {
78
78
  const binding = require('@decibri/decibri-android-arm64')
79
79
  const bindingPackageVersion = require('@decibri/decibri-android-arm64/package.json').version
80
- if (bindingPackageVersion !== '5.2.4' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
81
- throw new Error(`Native binding package version mismatch, expected 5.2.4 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
80
+ if (bindingPackageVersion !== '5.3.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
81
+ throw new Error(`Native binding package version mismatch, expected 5.3.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
82
82
  }
83
83
  return binding
84
84
  } catch (e) {
@@ -93,8 +93,8 @@ function requireNative() {
93
93
  try {
94
94
  const binding = require('@decibri/decibri-android-arm-eabi')
95
95
  const bindingPackageVersion = require('@decibri/decibri-android-arm-eabi/package.json').version
96
- if (bindingPackageVersion !== '5.2.4' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
97
- throw new Error(`Native binding package version mismatch, expected 5.2.4 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
96
+ if (bindingPackageVersion !== '5.3.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
97
+ throw new Error(`Native binding package version mismatch, expected 5.3.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
98
98
  }
99
99
  return binding
100
100
  } catch (e) {
@@ -114,8 +114,8 @@ function requireNative() {
114
114
  try {
115
115
  const binding = require('@decibri/decibri-win32-x64-gnu')
116
116
  const bindingPackageVersion = require('@decibri/decibri-win32-x64-gnu/package.json').version
117
- if (bindingPackageVersion !== '5.2.4' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
118
- throw new Error(`Native binding package version mismatch, expected 5.2.4 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
117
+ if (bindingPackageVersion !== '5.3.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
118
+ throw new Error(`Native binding package version mismatch, expected 5.3.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
119
119
  }
120
120
  return binding
121
121
  } catch (e) {
@@ -130,8 +130,8 @@ function requireNative() {
130
130
  try {
131
131
  const binding = require('@decibri/decibri-win32-x64-msvc')
132
132
  const bindingPackageVersion = require('@decibri/decibri-win32-x64-msvc/package.json').version
133
- if (bindingPackageVersion !== '5.2.4' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
134
- throw new Error(`Native binding package version mismatch, expected 5.2.4 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
133
+ if (bindingPackageVersion !== '5.3.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
134
+ throw new Error(`Native binding package version mismatch, expected 5.3.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
135
135
  }
136
136
  return binding
137
137
  } catch (e) {
@@ -147,8 +147,8 @@ function requireNative() {
147
147
  try {
148
148
  const binding = require('@decibri/decibri-win32-ia32-msvc')
149
149
  const bindingPackageVersion = require('@decibri/decibri-win32-ia32-msvc/package.json').version
150
- if (bindingPackageVersion !== '5.2.4' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
151
- throw new Error(`Native binding package version mismatch, expected 5.2.4 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
150
+ if (bindingPackageVersion !== '5.3.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
151
+ throw new Error(`Native binding package version mismatch, expected 5.3.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
152
152
  }
153
153
  return binding
154
154
  } catch (e) {
@@ -163,8 +163,8 @@ function requireNative() {
163
163
  try {
164
164
  const binding = require('@decibri/decibri-win32-arm64-msvc')
165
165
  const bindingPackageVersion = require('@decibri/decibri-win32-arm64-msvc/package.json').version
166
- if (bindingPackageVersion !== '5.2.4' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
167
- throw new Error(`Native binding package version mismatch, expected 5.2.4 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
166
+ if (bindingPackageVersion !== '5.3.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
167
+ throw new Error(`Native binding package version mismatch, expected 5.3.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
168
168
  }
169
169
  return binding
170
170
  } catch (e) {
@@ -182,8 +182,8 @@ function requireNative() {
182
182
  try {
183
183
  const binding = require('@decibri/decibri-darwin-universal')
184
184
  const bindingPackageVersion = require('@decibri/decibri-darwin-universal/package.json').version
185
- if (bindingPackageVersion !== '5.2.4' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
186
- throw new Error(`Native binding package version mismatch, expected 5.2.4 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
185
+ if (bindingPackageVersion !== '5.3.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
186
+ throw new Error(`Native binding package version mismatch, expected 5.3.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
187
187
  }
188
188
  return binding
189
189
  } catch (e) {
@@ -198,8 +198,8 @@ function requireNative() {
198
198
  try {
199
199
  const binding = require('@decibri/decibri-darwin-x64')
200
200
  const bindingPackageVersion = require('@decibri/decibri-darwin-x64/package.json').version
201
- if (bindingPackageVersion !== '5.2.4' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
202
- throw new Error(`Native binding package version mismatch, expected 5.2.4 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
201
+ if (bindingPackageVersion !== '5.3.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
202
+ throw new Error(`Native binding package version mismatch, expected 5.3.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
203
203
  }
204
204
  return binding
205
205
  } catch (e) {
@@ -214,8 +214,8 @@ function requireNative() {
214
214
  try {
215
215
  const binding = require('@decibri/decibri-darwin-arm64')
216
216
  const bindingPackageVersion = require('@decibri/decibri-darwin-arm64/package.json').version
217
- if (bindingPackageVersion !== '5.2.4' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
218
- throw new Error(`Native binding package version mismatch, expected 5.2.4 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
217
+ if (bindingPackageVersion !== '5.3.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
218
+ throw new Error(`Native binding package version mismatch, expected 5.3.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
219
219
  }
220
220
  return binding
221
221
  } catch (e) {
@@ -234,8 +234,8 @@ function requireNative() {
234
234
  try {
235
235
  const binding = require('@decibri/decibri-freebsd-x64')
236
236
  const bindingPackageVersion = require('@decibri/decibri-freebsd-x64/package.json').version
237
- if (bindingPackageVersion !== '5.2.4' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
238
- throw new Error(`Native binding package version mismatch, expected 5.2.4 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
237
+ if (bindingPackageVersion !== '5.3.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
238
+ throw new Error(`Native binding package version mismatch, expected 5.3.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
239
239
  }
240
240
  return binding
241
241
  } catch (e) {
@@ -250,8 +250,8 @@ function requireNative() {
250
250
  try {
251
251
  const binding = require('@decibri/decibri-freebsd-arm64')
252
252
  const bindingPackageVersion = require('@decibri/decibri-freebsd-arm64/package.json').version
253
- if (bindingPackageVersion !== '5.2.4' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
254
- throw new Error(`Native binding package version mismatch, expected 5.2.4 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
253
+ if (bindingPackageVersion !== '5.3.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
254
+ throw new Error(`Native binding package version mismatch, expected 5.3.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
255
255
  }
256
256
  return binding
257
257
  } catch (e) {
@@ -271,8 +271,8 @@ function requireNative() {
271
271
  try {
272
272
  const binding = require('@decibri/decibri-linux-x64-musl')
273
273
  const bindingPackageVersion = require('@decibri/decibri-linux-x64-musl/package.json').version
274
- if (bindingPackageVersion !== '5.2.4' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
275
- throw new Error(`Native binding package version mismatch, expected 5.2.4 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
274
+ if (bindingPackageVersion !== '5.3.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
275
+ throw new Error(`Native binding package version mismatch, expected 5.3.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
276
276
  }
277
277
  return binding
278
278
  } catch (e) {
@@ -287,8 +287,8 @@ function requireNative() {
287
287
  try {
288
288
  const binding = require('@decibri/decibri-linux-x64-gnu')
289
289
  const bindingPackageVersion = require('@decibri/decibri-linux-x64-gnu/package.json').version
290
- if (bindingPackageVersion !== '5.2.4' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
291
- throw new Error(`Native binding package version mismatch, expected 5.2.4 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
290
+ if (bindingPackageVersion !== '5.3.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
291
+ throw new Error(`Native binding package version mismatch, expected 5.3.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
292
292
  }
293
293
  return binding
294
294
  } catch (e) {
@@ -305,8 +305,8 @@ function requireNative() {
305
305
  try {
306
306
  const binding = require('@decibri/decibri-linux-arm64-musl')
307
307
  const bindingPackageVersion = require('@decibri/decibri-linux-arm64-musl/package.json').version
308
- if (bindingPackageVersion !== '5.2.4' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
309
- throw new Error(`Native binding package version mismatch, expected 5.2.4 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
308
+ if (bindingPackageVersion !== '5.3.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
309
+ throw new Error(`Native binding package version mismatch, expected 5.3.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
310
310
  }
311
311
  return binding
312
312
  } catch (e) {
@@ -321,8 +321,8 @@ function requireNative() {
321
321
  try {
322
322
  const binding = require('@decibri/decibri-linux-arm64-gnu')
323
323
  const bindingPackageVersion = require('@decibri/decibri-linux-arm64-gnu/package.json').version
324
- if (bindingPackageVersion !== '5.2.4' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
325
- throw new Error(`Native binding package version mismatch, expected 5.2.4 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
324
+ if (bindingPackageVersion !== '5.3.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
325
+ throw new Error(`Native binding package version mismatch, expected 5.3.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
326
326
  }
327
327
  return binding
328
328
  } catch (e) {
@@ -339,8 +339,8 @@ function requireNative() {
339
339
  try {
340
340
  const binding = require('@decibri/decibri-linux-arm-musleabihf')
341
341
  const bindingPackageVersion = require('@decibri/decibri-linux-arm-musleabihf/package.json').version
342
- if (bindingPackageVersion !== '5.2.4' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
343
- throw new Error(`Native binding package version mismatch, expected 5.2.4 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
342
+ if (bindingPackageVersion !== '5.3.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
343
+ throw new Error(`Native binding package version mismatch, expected 5.3.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
344
344
  }
345
345
  return binding
346
346
  } catch (e) {
@@ -355,8 +355,8 @@ function requireNative() {
355
355
  try {
356
356
  const binding = require('@decibri/decibri-linux-arm-gnueabihf')
357
357
  const bindingPackageVersion = require('@decibri/decibri-linux-arm-gnueabihf/package.json').version
358
- if (bindingPackageVersion !== '5.2.4' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
359
- throw new Error(`Native binding package version mismatch, expected 5.2.4 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
358
+ if (bindingPackageVersion !== '5.3.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
359
+ throw new Error(`Native binding package version mismatch, expected 5.3.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
360
360
  }
361
361
  return binding
362
362
  } catch (e) {
@@ -373,8 +373,8 @@ function requireNative() {
373
373
  try {
374
374
  const binding = require('@decibri/decibri-linux-loong64-musl')
375
375
  const bindingPackageVersion = require('@decibri/decibri-linux-loong64-musl/package.json').version
376
- if (bindingPackageVersion !== '5.2.4' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
377
- throw new Error(`Native binding package version mismatch, expected 5.2.4 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
376
+ if (bindingPackageVersion !== '5.3.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
377
+ throw new Error(`Native binding package version mismatch, expected 5.3.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
378
378
  }
379
379
  return binding
380
380
  } catch (e) {
@@ -389,8 +389,8 @@ function requireNative() {
389
389
  try {
390
390
  const binding = require('@decibri/decibri-linux-loong64-gnu')
391
391
  const bindingPackageVersion = require('@decibri/decibri-linux-loong64-gnu/package.json').version
392
- if (bindingPackageVersion !== '5.2.4' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
393
- throw new Error(`Native binding package version mismatch, expected 5.2.4 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
392
+ if (bindingPackageVersion !== '5.3.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
393
+ throw new Error(`Native binding package version mismatch, expected 5.3.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
394
394
  }
395
395
  return binding
396
396
  } catch (e) {
@@ -407,8 +407,8 @@ function requireNative() {
407
407
  try {
408
408
  const binding = require('@decibri/decibri-linux-riscv64-musl')
409
409
  const bindingPackageVersion = require('@decibri/decibri-linux-riscv64-musl/package.json').version
410
- if (bindingPackageVersion !== '5.2.4' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
411
- throw new Error(`Native binding package version mismatch, expected 5.2.4 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
410
+ if (bindingPackageVersion !== '5.3.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
411
+ throw new Error(`Native binding package version mismatch, expected 5.3.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
412
412
  }
413
413
  return binding
414
414
  } catch (e) {
@@ -423,8 +423,8 @@ function requireNative() {
423
423
  try {
424
424
  const binding = require('@decibri/decibri-linux-riscv64-gnu')
425
425
  const bindingPackageVersion = require('@decibri/decibri-linux-riscv64-gnu/package.json').version
426
- if (bindingPackageVersion !== '5.2.4' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
427
- throw new Error(`Native binding package version mismatch, expected 5.2.4 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
426
+ if (bindingPackageVersion !== '5.3.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
427
+ throw new Error(`Native binding package version mismatch, expected 5.3.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
428
428
  }
429
429
  return binding
430
430
  } catch (e) {
@@ -440,8 +440,8 @@ function requireNative() {
440
440
  try {
441
441
  const binding = require('@decibri/decibri-linux-ppc64-gnu')
442
442
  const bindingPackageVersion = require('@decibri/decibri-linux-ppc64-gnu/package.json').version
443
- if (bindingPackageVersion !== '5.2.4' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
444
- throw new Error(`Native binding package version mismatch, expected 5.2.4 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
443
+ if (bindingPackageVersion !== '5.3.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
444
+ throw new Error(`Native binding package version mismatch, expected 5.3.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
445
445
  }
446
446
  return binding
447
447
  } catch (e) {
@@ -456,8 +456,8 @@ function requireNative() {
456
456
  try {
457
457
  const binding = require('@decibri/decibri-linux-s390x-gnu')
458
458
  const bindingPackageVersion = require('@decibri/decibri-linux-s390x-gnu/package.json').version
459
- if (bindingPackageVersion !== '5.2.4' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
460
- throw new Error(`Native binding package version mismatch, expected 5.2.4 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
459
+ if (bindingPackageVersion !== '5.3.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
460
+ throw new Error(`Native binding package version mismatch, expected 5.3.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
461
461
  }
462
462
  return binding
463
463
  } catch (e) {
@@ -476,8 +476,8 @@ function requireNative() {
476
476
  try {
477
477
  const binding = require('@decibri/decibri-openharmony-arm64')
478
478
  const bindingPackageVersion = require('@decibri/decibri-openharmony-arm64/package.json').version
479
- if (bindingPackageVersion !== '5.2.4' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
480
- throw new Error(`Native binding package version mismatch, expected 5.2.4 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
479
+ if (bindingPackageVersion !== '5.3.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
480
+ throw new Error(`Native binding package version mismatch, expected 5.3.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
481
481
  }
482
482
  return binding
483
483
  } catch (e) {
@@ -492,8 +492,8 @@ function requireNative() {
492
492
  try {
493
493
  const binding = require('@decibri/decibri-openharmony-x64')
494
494
  const bindingPackageVersion = require('@decibri/decibri-openharmony-x64/package.json').version
495
- if (bindingPackageVersion !== '5.2.4' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
496
- throw new Error(`Native binding package version mismatch, expected 5.2.4 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
495
+ if (bindingPackageVersion !== '5.3.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
496
+ throw new Error(`Native binding package version mismatch, expected 5.3.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
497
497
  }
498
498
  return binding
499
499
  } catch (e) {
@@ -508,8 +508,8 @@ function requireNative() {
508
508
  try {
509
509
  const binding = require('@decibri/decibri-openharmony-arm')
510
510
  const bindingPackageVersion = require('@decibri/decibri-openharmony-arm/package.json').version
511
- if (bindingPackageVersion !== '5.2.4' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
512
- throw new Error(`Native binding package version mismatch, expected 5.2.4 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
511
+ if (bindingPackageVersion !== '5.3.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
512
+ throw new Error(`Native binding package version mismatch, expected 5.3.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
513
513
  }
514
514
  return binding
515
515
  } catch (e) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "decibri",
3
- "version": "5.2.4",
3
+ "version": "5.3.0",
4
4
  "description": "Cross-platform audio capture, playback, and processing for Node.js and browsers",
5
5
  "main": "src/decibri.js",
6
6
  "types": "src/decibri.d.ts",
@@ -72,10 +72,10 @@
72
72
  "MIGRATION.md"
73
73
  ],
74
74
  "optionalDependencies": {
75
- "@decibri/decibri-win32-x64-msvc": "5.2.4",
76
- "@decibri/decibri-darwin-arm64": "5.2.4",
77
- "@decibri/decibri-linux-x64-gnu": "5.2.4",
78
- "@decibri/decibri-linux-arm64-gnu": "5.2.4"
75
+ "@decibri/decibri-win32-x64-msvc": "5.3.0",
76
+ "@decibri/decibri-darwin-arm64": "5.3.0",
77
+ "@decibri/decibri-linux-x64-gnu": "5.3.0",
78
+ "@decibri/decibri-linux-arm64-gnu": "5.3.0"
79
79
  },
80
80
  "devDependencies": {
81
81
  "@napi-rs/cli": "^3.7.0"
@@ -6,7 +6,7 @@ const { WORKLET_SOURCE } = require('./worklet-inline.js');
6
6
  // Browser build version. Keep in sync with package.json on each release; the
7
7
  // browser bundle cannot read package.json at runtime the way the Node wrapper
8
8
  // does, so this is a maintained constant.
9
- const VERSION = '5.2.4';
9
+ const VERSION = '5.3.0';
10
10
 
11
11
  /**
12
12
  * Browser microphone capture.
package/src/decibri.d.ts CHANGED
@@ -61,6 +61,108 @@ export interface VadOptions {
61
61
  holdoffMs?: number;
62
62
  }
63
63
 
64
+ /**
65
+ * Acoustic echo cancellation config object, passed on the `aec` option to tune
66
+ * the canceller. The bare `aec: 'tau'` shorthand selects the model with its
67
+ * defaults; pass this object to override them.
68
+ *
69
+ * The canceller's behaviour while a lost delay alignment is being reacquired
70
+ * is fixed: decibri applies the canceller's graded output transition and does
71
+ * not expose a setting for it.
72
+ */
73
+ export interface AecOptions {
74
+ /**
75
+ * Which echo canceller model to run. The accepted set is owned by the
76
+ * canceller and grows the way `denoise` grows; today it is `'tau'`, a
77
+ * classical adaptive canceller with no model file. An unknown name is
78
+ * rejected with the canceller's own message naming the accepted set.
79
+ */
80
+ model: 'tau';
81
+
82
+ /**
83
+ * Adaptive filter tail length in milliseconds: how much echo delay spread
84
+ * the canceller can model.
85
+ * @default the canceller's own default (200)
86
+ * @range 16 to 500
87
+ */
88
+ tailMs?: number;
89
+
90
+ /**
91
+ * Residual echo suppression policy. `'conservative'` attenuates the residual
92
+ * echo the linear canceller leaves behind while keeping the near-end voice
93
+ * intact; `'off'` delivers the linear canceller output as-is.
94
+ * @default 'conservative'
95
+ */
96
+ suppression?: 'conservative' | 'off';
97
+
98
+ /**
99
+ * Sample rate in Hz of the far-end reference pushed through
100
+ * `pushAecReference`. When it names a rate other than `sampleRate`, decibri
101
+ * converts the reference before the canceller sees it: a reference at an
102
+ * undeclared different rate cancels nothing and reports no error, so the
103
+ * conversion is decibri's rather than the caller's.
104
+ * @default the capture `sampleRate`
105
+ * @range 1000 to 384000
106
+ */
107
+ referenceSampleRate?: number;
108
+ }
109
+
110
+ /**
111
+ * The echo canceller's transport and cancellation metrics, returned by
112
+ * `Microphone.aecMetrics()`. One object carries the canceller's own report and
113
+ * the reference queue's counters.
114
+ */
115
+ export interface AecMetrics {
116
+ /**
117
+ * The active delay alignment in samples, or `null` while the estimator is
118
+ * still searching. Staying `null` while `acquisitionParked` climbs is the
119
+ * signature of a canceller with no usable reference: none pushed, not at the
120
+ * declared rate, or not the signal that produced the echo.
121
+ */
122
+ delaySamples: number | null;
123
+ /**
124
+ * Smoothed echo-return-loss-enhancement estimate in dB: how much echo the
125
+ * canceller is currently removing. 0 before the filter has converged.
126
+ */
127
+ erleDb: number;
128
+ /**
129
+ * Whether the double-talk detector currently believes the near-end talker is
130
+ * active; adaptation is held while true.
131
+ */
132
+ doubleTalk: boolean;
133
+ /**
134
+ * Near-end samples the canceller could find no far-end sample for while an
135
+ * alignment was active. decibri keeps the far-end stream level with the
136
+ * capture, so this stays 0 for a caller who simply stops pushing; a non-zero
137
+ * count means the caller ran further ahead of the capture than the
138
+ * canceller's far-end history reaches.
139
+ */
140
+ referenceStarved: number;
141
+ /**
142
+ * Near-end samples processed while no delay alignment was active: the
143
+ * searching span, not a transport failure.
144
+ */
145
+ acquisitionParked: number;
146
+ /**
147
+ * Times the canceller inferred a capture discontinuity and rebuilt its
148
+ * alignment from the reference frontier.
149
+ */
150
+ referenceReanchors: number;
151
+ /**
152
+ * Far-end samples discarded because a single push exceeded the reference
153
+ * queue's bound, at the declared reference rate. The span they occupied is
154
+ * still represented as silence, so a discard costs the cancellation of that
155
+ * span alone.
156
+ */
157
+ referenceDropped: number;
158
+ /**
159
+ * Far-end samples decibri supplied as silence because the caller had pushed
160
+ * none for them, at the capture rate. An accounting figure, not a fault:
161
+ * while nothing is playing, the far end is silence.
162
+ */
163
+ referenceSilence: number;
164
+ }
165
+
64
166
  /** Constructor options for `Microphone`. */
65
167
  export interface MicrophoneOptions extends ReadableOptions {
66
168
  /**
@@ -186,6 +288,26 @@ export interface MicrophoneOptions extends ReadableOptions {
186
288
  * @default undefined
187
289
  */
188
290
  limiter?: number;
291
+
292
+ /**
293
+ * Acoustic echo cancellation applied to the captured audio, removing the
294
+ * echo of far-end audio the caller pushes through `pushAecReference`. The
295
+ * `'tau'` shorthand names the model; an `AecOptions` object tunes it. Omit
296
+ * to leave echo cancellation off (the default), which keeps the capture
297
+ * path unchanged.
298
+ *
299
+ * Runs before the detector tap: with it on, `vadScore` and the `speech` /
300
+ * `silence` events read the echo-removed signal, so playback stops
301
+ * triggering detection. Requires `sampleRate` in 8000 to 48000, narrower
302
+ * than the range the option otherwise accepts. With no reference pushed,
303
+ * the captured audio passes through unchanged.
304
+ *
305
+ * Native capture only: the browser entry does not take this option, because
306
+ * browser capture already carries the platform's own echo cancellation
307
+ * through its `echoCancellation` constraint, on by default.
308
+ * @default undefined
309
+ */
310
+ aec?: 'tau' | AecOptions;
189
311
  }
190
312
 
191
313
  /**
@@ -245,6 +367,27 @@ export declare class Microphone extends Readable {
245
367
  */
246
368
  readonly overrunCount: number;
247
369
 
370
+ /**
371
+ * Queue far-end reference audio for the echo canceller: the audio being
372
+ * played out, pushed as it is played, in played order. Accepts the same
373
+ * input shapes `Speaker.write` accepts (a `Buffer`, any TypedArray, or a
374
+ * `DataView` of PCM bytes in this microphone's `dtype`), mono, at the
375
+ * declared `referenceSampleRate` (the capture rate when unset).
376
+ *
377
+ * Never blocks and never throws on a full queue: samples that do not fit
378
+ * are discarded and counted by `aecMetrics().referenceDropped`. Silence
379
+ * between played audio need not be pushed. A push while capture is not
380
+ * running, or with the `aec` option unset, is a no-op.
381
+ */
382
+ pushAecReference(data: Buffer | NodeJS.ArrayBufferView): void;
383
+
384
+ /**
385
+ * The echo canceller's transport and cancellation metrics, merged with the
386
+ * reference queue's counters, or `null` when the `aec` option is unset or
387
+ * capture is not running.
388
+ */
389
+ aecMetrics(): AecMetrics | null;
390
+
248
391
  /** List all available audio input devices. */
249
392
  static devices(): MicrophoneInfo[];
250
393
 
package/src/decibri.js CHANGED
@@ -342,6 +342,68 @@ class Microphone extends Readable {
342
342
  throw new RangeError('limiter ceiling must be between -3.0 and 0.0');
343
343
  }
344
344
 
345
+ // ── Validate AEC ─────────────────────────────────────────────────────────
346
+
347
+ // Echo cancellation: the 'tau' shorthand names the model, or an
348
+ // { model, tailMs, suppression, referenceSampleRate } object tunes it;
349
+ // absence leaves it off. The model name is deliberately NOT checked
350
+ // against a list here: the canceller owns the accepted set, so the native
351
+ // layer parses it (AecModel::from_str) and an unknown name is rejected by
352
+ // the native constructor with the canceller's own message (a DecibriError
353
+ // with code 'AEC_CONFIG_INVALID'). The three tuning fields are checked
354
+ // here with the same RangeError / TypeError classes the other
355
+ // conditioning options use; the native layer backstops the same checks.
356
+ // The capture-rate window (8000..=48000 with AEC on) is guarded by the
357
+ // core, surfacing as a RangeError from the native constructor.
358
+ const aec = options.aec;
359
+ let aecModel;
360
+ let aecTailMs;
361
+ let aecSuppression;
362
+ let aecReferenceSampleRate;
363
+ if (aec !== undefined) {
364
+ if (typeof aec === 'string') {
365
+ aecModel = aec;
366
+ } else if (aec !== null && typeof aec === 'object' && !Array.isArray(aec)) {
367
+ const { model, tailMs, suppression, referenceSampleRate } = aec;
368
+ if (typeof model !== 'string') {
369
+ throw new TypeError(
370
+ `Invalid aec model: ${JSON.stringify(model)}. Expected a model name string such as 'tau'.`
371
+ );
372
+ }
373
+ aecModel = model;
374
+ if (tailMs !== undefined) {
375
+ if (typeof tailMs !== 'number' || Number.isNaN(tailMs)) {
376
+ throw new TypeError('aec tailMs must be a number');
377
+ }
378
+ if (tailMs < 16 || tailMs > 500) {
379
+ throw new RangeError('aec tailMs must be between 16 and 500');
380
+ }
381
+ aecTailMs = tailMs;
382
+ }
383
+ if (suppression !== undefined) {
384
+ if (suppression !== 'conservative' && suppression !== 'off') {
385
+ throw new TypeError(
386
+ `aec suppression must be 'conservative' or 'off'; got ${JSON.stringify(suppression)}`
387
+ );
388
+ }
389
+ aecSuppression = suppression;
390
+ }
391
+ if (referenceSampleRate !== undefined) {
392
+ if (typeof referenceSampleRate !== 'number' || Number.isNaN(referenceSampleRate)) {
393
+ throw new TypeError('aec referenceSampleRate must be a number');
394
+ }
395
+ if (referenceSampleRate < 1000 || referenceSampleRate > 384000) {
396
+ throw new RangeError('aec referenceSampleRate must be between 1000 and 384000');
397
+ }
398
+ aecReferenceSampleRate = referenceSampleRate;
399
+ }
400
+ } else {
401
+ throw new TypeError(
402
+ `Invalid aec value: ${JSON.stringify(aec)}. Expected a model name such as 'tau', or a config object { model, tailMs, suppression, referenceSampleRate }.`
403
+ );
404
+ }
405
+ }
406
+
345
407
  // Internal plumbing: inject the bundled ORT dylib path into the napi
346
408
  // constructor whenever an ONNX stage loads (Silero VAD or denoise). If
347
409
  // resolution fails (unknown platform, platform package not installed), this
@@ -377,6 +439,10 @@ class Microphone extends Readable {
377
439
  highpass,
378
440
  agc,
379
441
  limiter,
442
+ aec: aecModel,
443
+ aecTailMs,
444
+ aecSuppression,
445
+ aecReferenceSampleRate,
380
446
  },
381
447
  };
382
448
  }
@@ -535,6 +601,67 @@ class Microphone extends Readable {
535
601
  return this._native.overrunCount;
536
602
  }
537
603
 
604
+ /**
605
+ * Queue far-end reference audio for the echo canceller: the audio being
606
+ * played out, pushed as it is played, in played order. Accepts the same
607
+ * input shapes `Speaker.write` accepts (a `Buffer`, any TypedArray, or a
608
+ * `DataView` of PCM bytes in this microphone's `dtype`), mono, at the
609
+ * declared `referenceSampleRate` (the capture rate when unset).
610
+ *
611
+ * Never blocks and never throws on a full queue: samples that do not fit
612
+ * are discarded and counted by `aecMetrics().referenceDropped`, and the
613
+ * span they occupied is represented as silence. Silence between played
614
+ * audio need not be pushed; a caller that stops pushing has said nothing is
615
+ * playing. A push while capture is not running, or with the `aec` option
616
+ * unset, is a no-op.
617
+ *
618
+ * @param {Buffer | NodeJS.ArrayBufferView} data PCM samples in the
619
+ * configured `dtype`.
620
+ */
621
+ pushAecReference(data) {
622
+ let buf;
623
+ if (Buffer.isBuffer(data)) {
624
+ buf = data;
625
+ } else if (ArrayBuffer.isView(data)) {
626
+ // Any TypedArray or DataView: view the same bytes, no copy, exactly as
627
+ // the stream machinery normalizes a typed-array write to a Speaker.
628
+ buf = Buffer.from(data.buffer, data.byteOffset, data.byteLength);
629
+ } else {
630
+ throw new TypeError(
631
+ 'pushAecReference requires a Buffer, TypedArray, or DataView of PCM samples in the configured dtype'
632
+ );
633
+ }
634
+ this._native.pushAecReference(buf);
635
+ }
636
+
637
+ /**
638
+ * The echo canceller's transport and cancellation metrics, merged with the
639
+ * reference queue's counters, or `null` when the `aec` option is unset or
640
+ * capture is not running.
641
+ *
642
+ * `delaySamples` staying `null` while `acquisitionParked` climbs is the
643
+ * signature of a canceller with no usable reference: none pushed, not at
644
+ * the declared rate, or not the signal that produced the echo. A climbing
645
+ * `referenceDropped` means single pushes are exceeding the reference
646
+ * queue's bound.
647
+ *
648
+ * @returns {import('./decibri').AecMetrics | null}
649
+ */
650
+ aecMetrics() {
651
+ const m = this._native.aecMetrics();
652
+ if (m === null || m === undefined) return null;
653
+ return {
654
+ delaySamples: m.delaySamples ?? null,
655
+ erleDb: m.erleDb,
656
+ doubleTalk: m.doubleTalk,
657
+ referenceStarved: m.referenceStarved,
658
+ acquisitionParked: m.acquisitionParked,
659
+ referenceReanchors: m.referenceReanchors,
660
+ referenceDropped: m.referenceDropped,
661
+ referenceSilence: m.referenceSilence,
662
+ };
663
+ }
664
+
538
665
  /**
539
666
  * List all available input devices on the system.
540
667
  * @returns {Array<{index: number, name: string, id: string, maxInputChannels: number, defaultSampleRate: number, isDefault: boolean}>}
package/src/errors.js CHANGED
@@ -80,8 +80,11 @@ const RANGE_PREFIXES = [
80
80
  'frames per buffer must be between',
81
81
  'agc target level must be between',
82
82
  'limiter ceiling must be between',
83
+ 'aec tailMs must be between',
84
+ 'aec referenceSampleRate must be between',
83
85
  'Silero VAD only supports',
84
86
  'VAD threshold must be between',
87
+ 'echo cancellation only supports',
85
88
  'device index out of range',
86
89
  'analysis requires VAD',
87
90
  ];
@@ -89,6 +92,7 @@ const RANGE_PREFIXES = [
89
92
  const TYPE_PREFIXES = [
90
93
  "dtype must be 'int16' or 'float32'",
91
94
  "format must be 'int16' or 'float32'",
95
+ 'aec suppression must be',
92
96
  ];
93
97
 
94
98
  const DEVICE_CODES = [
@@ -126,6 +130,7 @@ const BASE_CODES = [
126
130
  ['the requested sample rate conversion is not supported', 'RESAMPLE_CONFIG_INVALID'],
127
131
  ['the resample chain was fed after it was flushed', 'RESAMPLE_AFTER_FLUSH'],
128
132
  ['resampler error:', 'RESAMPLE_FAILED'],
133
+ ['echo canceller configuration error:', 'AEC_CONFIG_INVALID'],
129
134
  ['Failed to read audio file', 'FILE_READ_FAILED'],
130
135
  ['invalid WAV file:', 'WAV_INVALID'],
131
136
  ['ONNX Runtime was initialized in pid', 'FORK_AFTER_ORT_INIT'],