decibri 3.4.2 → 4.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md ADDED
@@ -0,0 +1,386 @@
1
+ <!-- markdownlint-disable MD024 -->
2
+
3
+ # Decibri npm Package Changelog
4
+
5
+ Changes to the decibri npm package, published to npmjs.com. Tags use the `npm-v*` pattern (e.g., `npm-v3.4.2`).
6
+
7
+ For other decibri packages, see:
8
+
9
+ - Rust crate: [crates/decibri/CHANGELOG.md](../../crates/decibri/CHANGELOG.md)
10
+ - Python wheel: [bindings/python/CHANGELOG.md](../../bindings/python/CHANGELOG.md)
11
+
12
+ ## [Unreleased]
13
+
14
+ ## [4.0.0] - 2026-05-30
15
+
16
+ ### Changed
17
+
18
+ - BREAKING: the package now uses named exports. Import with `const { Microphone, Speaker, inputDevices, outputDevices, version } = require('decibri')` instead of the previous single default export.
19
+ - BREAKING: the capture class `Decibri` is now `Microphone`, and the playback class `DecibriOutput` is now `Speaker`. The browser capture class is `Microphone` too.
20
+ - BREAKING: the sample-encoding option `format` is now `dtype` (the `'int16'` and `'float32'` values are unchanged).
21
+ - BREAKING: the device-info types are now `MicrophoneInfo` and `SpeakerInfo` (were `DeviceInfo` and `OutputDeviceInfo`).
22
+ - BREAKING: voice activity detection is now a single `vad` option accepting `false`, `'silero'`, or `'energy'`. The `vad: true` plus `vadMode` pair is no longer accepted; pass the mode directly as `vad: 'silero'` or `vad: 'energy'`. The browser runs energy mode only.
23
+ - BREAKING: `version()` now returns `{ decibri, audioBackend, binding }` (was `{ decibri, portaudio }`). `audioBackend` replaces the inaccurate `portaudio` name, and `binding` reports the npm package version.
24
+
25
+ ### Added
26
+
27
+ - Error classes `DecibriError`, `DeviceError`, `OrtError`, and `OrtPathError`, each with a `code` property, for `instanceof` and `err.code` handling.
28
+ - `vadScore` getter on the capture class: the Silero probability in `'silero'` mode, the normalized RMS in `'energy'` mode, 0 when disabled.
29
+ - Module-level `inputDevices()`, `outputDevices()`, and `version()` free functions, alongside the static `Microphone.devices()` and `Speaker.devices()` methods.
30
+
31
+ ## [3.4.2] - 2026-05-24
32
+
33
+ ### Fixed
34
+
35
+ - npm package now ships its Node.js-specific README to npmjs.com. The publish workflow previously copied the root README into the npm package directory at publish time, which meant the npmjs.com page for `decibri` showed a generic multi-language overview instead of the Node-focused documentation at `npm/decibri/README.md`. Removing the copy step lets the proper README ship.
36
+
37
+ ## [3.4.1] - 2026-05-23
38
+
39
+ ### Fixed
40
+
41
+ - Speaker example in `crates/decibri/README.md`: defined `pcm_int16_bytes` as `Vec<u8>` of 48000 zero bytes (1 second of int16 silence at 24kHz mono) so the example has a real value to send. Previously the example referenced `pcm_int16_bytes` without defining it, raising `error[E0425]: cannot find value 'pcm_int16_bytes' in this scope` on copy-paste.
42
+ - `npm/decibri/README.md`: replaced the accidental Rust crate README copy with a Node.js and browser focused README. Users on npmjs.com landing on the decibri package were previously shown Rust documentation (`cargo add decibri`, Rust feature flags, etc.) instead of Node.js installation and API documentation. The new README documents the actual Node.js API surface (`Decibri` capture stream, `DecibriOutput` playback stream, events, browser conditional export, VAD modes, device selection by index / name / stable per-host ID).
43
+ - `npm/decibri/examples/`: moved three runnable examples (`wav-capture.js`, `websocket-server.js`, `websocket-stream.js`) from the top-level `examples/` directory into the npm package directory, rewrote imports to use `require('decibri')`, and added `examples/` to `package.json` `files` so the examples ship in the published tarball. The Examples section in `npm/decibri/README.md` was previously removed in error during an audit pass that searched only `npm/decibri/examples/` rather than the top-level `examples/` where the files actually lived; this restoration corrects that and ensures `npm install decibri` users actually receive the example files.
44
+
45
+ ## [3.4.0] - 2026-05-02
46
+
47
+ ### Added
48
+
49
+ - **`OnnxSession` trait abstraction in Rust core.** New internal `pub(crate) trait OnnxSession` inside `crates/decibri/src/onnx.rs` abstracts ONNX Runtime usage behind a backend-agnostic interface. `SileroVad` consumes the trait through `Box<dyn OnnxSession>`. The ORT-backed implementation is the only impl in 3.x.
50
+ - `DecibriError::OnnxBackendFailed { backend: &'static str, source: Box<dyn std::error::Error + Send + Sync> }` variant. Reserved on the `#[non_exhaustive]` enum. Additive; existing 8 ORT variants unchanged. `is_ort_path_error` continues to return false on the new variant.
51
+ - **`DecibriError::ForkAfterOrtInit { init_pid: u32, current_pid: u32 }` variant + runtime fork detection.** Linux-only failure-mode hardening: a process that forks after a successful `SileroVad::new` previously inherited `static ORT_INIT` flagged as set while the underlying ORT runtime state (allocators, thread pools) was unsafe to reuse, producing silent wrong probabilities, segfaults, or hangs in the child. 3.4.0 stamps the initializing pid into a paired `static ORT_INIT_PID: OnceLock<u32>` inside the same `init_ort_once` success path; a `pub(crate) fn check_pid_for_ort()` runs at the entry of `SileroVad::process()` and returns `Err(ForkAfterOrtInit { init_pid, current_pid })` on pid mismatch. The `Display` message embeds both pids and the two remediation options ("Use `multiprocessing.set_start_method('spawn')` or construct `Microphone(vad='silero')` inside each child process"). Single-check coverage at the outer entry point applies to every inference call without per-window overhead. macOS and Windows are unaffected by fork semantics. Additive on the `#[non_exhaustive]` `DecibriError` enum; mapped through to npm via the napi `_ =>` catch-all (`Status::GenericFailure` carrying `e.to_string()`) and to Python via an explicit `ForkAfterOrtInit(DecibriError)` subclass in the wheel.
52
+
53
+ ### Internal
54
+
55
+ - `crates/decibri` 3.x public API stays byte-identical (`SileroVad`, `VadConfig`, `VadResult`, `DecibriError` keep their 3.3.x signatures). npm binding, Python binding, browser shim are unchanged.
56
+ - `vad::init_ort_once` visibility raised from private to `pub(crate)` so the `onnx` module's inline ORT-backed test can reuse the same process-global init path that `vad` tests use.
57
+ - `static ORT_INIT_PID: OnceLock<u32>` paired with the existing `ORT_INIT`, set inside the `OnceLock::get_or_init` callback so the pid stamp is paired with the successful ORT init rather than a speculative pre-init value. `pub(crate) fn check_pid_for_ort()` exposes the comparison to inference call sites within the crate. Linux-only `test_fork_safety.py` tests in the Python wheel pin the behavior end-to-end (gated `skipif sys.platform != "linux"`); they run on CI's `ubuntu-latest` job and skip on other hosts.
58
+
59
+ ## [3.3.2] - 2026-04-26
60
+
61
+ ### Changed
62
+
63
+ - **`crates/decibri/build.rs` rewrite to use Cargo.toml as primary source.** The previous build script (introduced in 3.3.1 to source the cpal version from a single point of truth) read the workspace `Cargo.lock` via a path traversal that worked in workspace builds but panicked in `cargo publish` verify because the published tarball is flat (`decibri-X.Y.Z/Cargo.toml` and `decibri-X.Y.Z/Cargo.lock` are siblings, not in workspace structure). The traversal landed at `target/Cargo.lock` (nonexistent) and aborted the build. **3.3.1's crates.io publish failed on this defect; 3.3.1 shipped to npm but not to crates.io.** 3.3.2 fixes the build.rs to read `CARGO_MANIFEST_DIR / Cargo.toml` directly, with belt-and-suspenders fallbacks: env-var override (`DECIBRI_CPAL_VERSION`), workspace `Cargo.toml` fallback for `{ workspace = true }` inherit form, hardcoded `"0.17"` constant fallback (with `cargo:warning=`) for unforeseen build contexts. Cargo.toml is unambiguously present in every build context because cargo guarantees `CARGO_MANIFEST_DIR` always points at the manifest's directory.
64
+ - **No functional change to user-visible API or error messages.** All 5 message refinements from 3.3.1 (`SampleRateOutOfRange`, `FramesPerBufferOutOfRange`, `AlreadyRunning`, `OrtInitFailed`, `OrtLoadFailed` / `OrtPathInvalid`, `PermissionDenied`) are preserved unchanged.
65
+ - **`decibri::CPAL_VERSION` byte-identity preserved across all four cargo-emitted dep forms.** Verified 2026-04-26 by walking through `find_cpal_in_dependencies` + `truncate_to_major_minor` against on-disk Cargo.toml content: Form 1 (`cpal = "0.17"` workspace.dependencies) -> `"0.17"`; Form 2 (`cpal = { version = "0.17", optional = true }` hypothetical inline-table) -> `"0.17"`; Form 3 (`cpal = { workspace = true, optional = true }` source crate) -> workspace fallback -> `"0.17"`; Form 4 (`[dependencies.cpal] version = "0.17"` published normalized) -> `"0.17"`. All four forms produce identical output to the v3.3.1 build.rs's Cargo.lock-resolved truncation because the workspace pin is at major.minor granularity already (`cpal = "0.17"` in `[workspace.dependencies]`).
66
+ - **`release-dryrun.yml` extended to exercise `cargo publish -p decibri --dry-run`.** The previous dryrun workflow ran the npm-side build matrix and `verify_pack` packaging gate but had no crates.io publish path coverage. The new step catches build.rs failures and any other publish-time issues that affect the Rust crate publish but not the npm packaging. Closes the procedural gap that allowed 3.3.1's defect to ship through CI green.
67
+
68
+ ### Migration notes
69
+
70
+ - **Direct Rust crate consumers** of decibri: 3.3.1 was never published to crates.io. crates.io's decibri version history is 3.3.0 -> 3.3.2; 3.3.1 is skipped entirely on this registry. **npm consumers** see the standard 3.3.0 -> 3.3.1 -> 3.3.2 progression (3.3.1 shipped successfully on npm; only the cargo publish step in `release.yml` failed). The asymmetry is documented here for archaeological clarity.
71
+ - All 3.3.1 message refinements (per `[3.3.1]` entry above) are preserved in 3.3.2. Consumer-side migration from 3.3.0 to 3.3.2 is the same as 3.3.0 to 3.3.1 from the user-visible API perspective.
72
+ - **No npm migration required** for 3.3.1 -> 3.3.2. 3.3.2 publishes a new wheel set with the same user-visible behavior; bump-and-rebuild is sufficient.
73
+
74
+ ## [3.3.1] - 2026-04-25
75
+
76
+ ### Changed
77
+
78
+ - **Audience-neutral error message pass.** Five `DecibriError` `Display` strings refined to remove cross-binding and platform-specific awkwardness. No variant identity, layout, or count change; no API-surface change. Strict patch release.
79
+ - `SampleRateOutOfRange`: `"sampleRate must be between 1000 and 384000"` -> `"sample rate must be between 1000 and 384000"`. The previous camelCase form was Node-API-targeting and matched no other field-name convention in the rest of the Rust crate (snake_case fields throughout, natural-language rustdoc voice).
80
+ - `FramesPerBufferOutOfRange`: `"framesPerBuffer must be between 64 and 65536"` -> `"frames per buffer must be between 64 and 65536"`. Same rationale.
81
+ - `AlreadyRunning`: `"Decibri is already running. Call stop() first."` -> `"audio stream is already running. Call stop() first."`. Hardcoded class name was misleading when raised from `DecibriOutput`; "audio stream" matches the existing `error.rs` vocabulary ("capture stream", "output stream", "audio stream").
82
+ - `OrtInitFailed`: `"Either pass ort_library_path in VadConfig, ..."` -> `"Either pass ort_library_path when constructing the VAD, ..."`. Drops Rust-internal `VadConfig` type reference; phrasing now correct for Node, Python, and direct-Rust crate consumers alike.
83
+ - `OrtLoadFailed` and `OrtPathInvalid`: `"the bundled ORT may be missing from your platform package"` -> `"the bundled ONNX Runtime may be missing from your installation"`. Drops npm-internal "platform package" phrasing; "installation" works for npm platform packages, Python wheels, and direct-Rust crate use.
84
+ - `PermissionDenied`: macOS-specific `"Enable in System Preferences > Security & Privacy."` replaced with attribute-gated per-platform guidance. macOS hint extended to specifically reference `> Microphone`. Windows hint references the modern `Settings > Privacy & Security > Microphone` UX. Linux hint references PulseAudio / PipeWire (the user-facing audio control layer over cpal's ALSA backend).
85
+ - Lockstep updates to `bindings/node/src/lib.rs` (one duplicate `AlreadyRunning` message in the napi `start()` pre-running check), `npm/decibri/src/errors.js` (two prefix-match strings in the typed-error shim), `npm/decibri/src/decibri.js` (two thrown messages in client-side validation; line 101's `channels` message is already natural-language and unchanged), `npm/decibri/src/decibri-output.js` (one thrown message), `npm/decibri/src/browser/decibri-browser.js` (two thrown messages in browser-side validation), `tests/test-ci.js`, `tests/test-api.js`, `tests/test-output.js` (15 hardcoded message-substring assertions).
86
+
87
+ ### Migration notes
88
+
89
+ Error message wording on shipped `DecibriError` variants has historically been stable across the 3.x line. 3.3.1 explicitly refines five messages to remove audience-leak issues: Node-API-targeted camelCase parameter names, class-name hardcoding in `AlreadyRunning`, a Rust-internal type reference (`VadConfig`) in `OrtInitFailed`, npm-internal phrasing ("platform package") in `OrtLoadFailed` / `OrtPathInvalid`, and macOS-only platform guidance in `PermissionDenied`. 3.3.1 is the consolidation point.
90
+
91
+ - **Direct Rust crate consumers** asserting on `DecibriError::Display` strings should update assertions for the five refined messages. Type-level matching on `DecibriError` variants is unaffected; only string-text assertions need updating.
92
+ - **Node consumers** using `e.message.includes(prefix)` or `e.message.startsWith(prefix)` patterns should update for `sampleRate` -> `sample rate`, `framesPerBuffer` -> `frames per buffer`, and the `AlreadyRunning` message text. Type-level matching on `RangeError` / `TypeError` is unaffected.
93
+ - **Python consumers**: the in-development Python wheel consumes `decibri@3.3.1`, so the messages it sees are the corrected forms.
94
+
95
+ ## [3.3.0] - 2026-04-23
96
+
97
+ Groundwork release for upcoming P3 Python bindings. Adds a stable-ID form for audio device selection (`DeviceSelector::Id`), fixes a long-standing direction bug in `DecibriError::DeviceNotFound` when resolving output devices, exposes both in the Node binding, and extends the reference documentation with a Cargo feature flag guide plus additional crate-level rustdoc. No Node.js or browser API break. Direct Rust crate consumers pattern-matching on `DeviceSelector` or struct-literal-constructing `DeviceInfo` / `OutputDeviceInfo` need to update for the new `#[non_exhaustive]` attributes (see Migration notes below).
98
+
99
+ ### Changed
100
+
101
+ - `DeviceSelector`, `DeviceInfo`, and `OutputDeviceInfo` are now `#[non_exhaustive]`. External Rust consumers pattern-matching on `DeviceSelector` must add a `_ =>` catch-all arm; consumers constructing `DeviceInfo` or `OutputDeviceInfo` via struct literal from outside the crate must switch to reading fields off instances returned by `enumerate_input_devices` / `enumerate_output_devices`. Field names and display strings are unchanged. This future-proofs the API: subsequent variant or field additions are source-compatible for consumers who include the catch-all.
102
+
103
+ ### Added
104
+
105
+ - `DeviceSelector::Id(String)` for selecting audio devices by stable per-host identifier (WASAPI endpoint ID on Windows, CoreAudio UID on macOS, ALSA pcm_id on Linux). Unlike `DeviceSelector::Name` (case-insensitive substring) and `DeviceSelector::Index` (positional), `Id` survives across enumerations: display names can shift when other devices are plugged in but per-host IDs do not.
106
+ - `id: String` field on `DeviceInfo` and `OutputDeviceInfo`, populated from `cpal::DeviceId`'s `Display` output. Empty string if cpal cannot produce a stable ID for a given device (rare; some host backends cannot assign IDs to every enumerated device). Obtain the ID from these fields and pass to `DeviceSelector::Id`.
107
+ - `DecibriError::OutputDeviceNotFound(String)` variant, the output-device equivalent of `DeviceNotFound`. See Fixed below for the motivating bug.
108
+ - Node binding accepts `device: { id: string }` as a third form alongside `device: <number>` (index) and `device: <string>` (name substring). The JS wrapper passes it through to Rust unchanged; Rust resolves via cpal's `DeviceId`.
109
+ - `DeviceInfoJs.id` and `OutputDeviceInfoJs.id` fields on the Node binding types, mirroring the Rust `DeviceInfo.id` / `OutputDeviceInfo.id` additions. Visible in the auto-regenerated `npm/decibri/index.d.ts` and in the hand-authored `npm/decibri/src/decibri.d.ts`.
110
+ - `npm/decibri/src/errors.js` helper that re-wraps plain `Error` instances thrown from the native boundary as `TypeError` or `RangeError`, matching the JS wrapper's existing validation error classes. Brought in by `decibri.js` and `decibri-output.js` constructors to align Rust-originated errors with the JS wrapper's error class contract. Triggered only by code paths that reach Rust's `to_napi_error` (currently only `device: { id: ... }` selection); all other validation paths continue to throw from the JS wrapper directly with no behavior change.
111
+ - `docs/features.md`: comprehensive Cargo feature reference covering ORT distribution mode tradeoffs, execution-provider features, binding-author guidance, and feature compatibility constraints. Targeted at Rust crate consumers and FFI binding authors; lib.rs rustdoc now links to it for deep-dive reference.
112
+
113
+ ### Fixed
114
+
115
+ - `DecibriError::DeviceNotFound`'s display string hardcoded "No audio input device found matching..." regardless of whether the lookup was against input or output devices. Direct Rust consumers and the new `device: { id: ... }` Node path now receive the correct direction via `DecibriError::OutputDeviceNotFound` for output-device misses. No change visible through the Node binding for existing name- and index-based lookups: those are intercepted by the JS wrapper and always threw direction-correct messages from JS before reaching Rust.
116
+
117
+ ### Internal
118
+
119
+ - `DeviceDirection` trait gains a `not_found_error(String) -> DecibriError` method so `resolve_device_generic`'s `Name` and `Id` arms produce direction-correct errors via the `Input` / `Output` impls.
120
+ - Unit tests for `Arc<Mutex<CaptureStream>>` confirming the wrapping is `Send + Sync` (compile-time assertion) and serializes concurrent access across two threads (runtime test with `Barrier`). Documents the wrapping strategy the P3 Python binding will apply to share `!Sync` capture streams across Python threads.
121
+ - Crate-level rustdoc additions in `lib.rs`: a section on ORT error construction FFI side effects (the `ortsys![CreateStatus]` dylib-load trigger that motivates the `OrtPathInvalid` split from `OrtLoadFailed`) and a section on fork safety (guidance for Python `multiprocessing` consumers to use `spawn` start method).
122
+ - `lib.rs` rustdoc "Feature flags" section cross-references `docs/features.md` for consumers wanting the deep-dive reference.
123
+ - Em-dash cleanup across 19 code locations in `lib.rs`, `capture.rs`, `output.rs`, `vad.rs`, `error.rs`, `vad_integration.rs`, and `vad_ort_load_failure.rs`. Per CLAUDE.md, the codebase forbids em dashes; these were pre-existing violations.
124
+ - CLAUDE.md corrections: validation-gate commands updated to the canonical set (`cargo clippy --workspace -- -D warnings`, `cargo fmt --all -- --check`, `cargo test-decibri`); stale `## [3.0.0] - Unreleased` reference replaced with a template placeholder.
125
+ - `ort` crate version unchanged at `2.0.0-rc.12`.
126
+ - Bundled ONNX Runtime version unchanged at `1.24.4`.
127
+ - No Node.js API signatures, event names, or error messages changed.
128
+ - TypeScript declaration files in both `npm/decibri/index.d.ts` (auto-regenerated) and `npm/decibri/src/decibri.d.ts` (hand-authored) updated for the new `id` field and extended `device` option type.
129
+
130
+ ### Migration notes for direct Rust crate consumers
131
+
132
+ - Exhaustive matches on `DeviceSelector` will stop compiling. Add a `_ =>` catch-all arm. Display strings and existing variant names are unchanged; code using `to_string()` or only constructing variants (not matching them) continues to work unaffected.
133
+ - Struct literal construction of `DeviceInfo` and `OutputDeviceInfo` from outside the `decibri` crate will stop compiling (added `#[non_exhaustive]`, added `id: String` field). External consumers should read these structs from `enumerate_input_devices()` / `enumerate_output_devices()` rather than constructing them directly.
134
+ - Consumers matching specifically on `DecibriError::DeviceNotFound` for output-device misses should now also match `DecibriError::OutputDeviceNotFound`. The convenience predicate `DecibriError::is_ort_path_error` remains unchanged and already groups only ORT-path variants.
135
+ - MSRV unchanged at rustc 1.88.
136
+
137
+ ## [3.2.0] - 2026-04-22
138
+
139
+ Refactor release. Public Node.js and browser APIs are unchanged. Direct
140
+ Rust crate consumers get a structured `DecibriError` taxonomy with full
141
+ error-chain preservation, a new stable FFI-ready stream-reading API on
142
+ `CaptureStream`, a declared minimum-supported-Rust-version, and a Windows
143
+ hang fix in VAD initialization. See migration notes below.
144
+
145
+ ### Changed
146
+
147
+ - `DecibriError` is now `#[non_exhaustive]` and the `Other(String)`
148
+ catch-all variant has been removed. All previous `Other(...)` failures
149
+ now have dedicated typed variants: `DeviceEnumerationFailed`,
150
+ `CaptureStreamClosed`, `OutputStreamClosed`, `VadSampleRateUnsupported`,
151
+ `VadThresholdOutOfRange`, `OrtInitFailed`, `OrtLoadFailed`,
152
+ `OrtPathInvalid`, `OrtSessionBuildFailed`, `OrtThreadsConfigFailed`,
153
+ `VadModelLoadFailed`, `OrtInferenceFailed`, `OrtTensorCreateFailed`,
154
+ `OrtTensorExtractFailed`. Path-carrying variants use `PathBuf`; ORT
155
+ variants carry `#[source] ort::Error` so `error.source()` walks the
156
+ error chain. Display strings (and therefore `error.message` in Node
157
+ and `str(exception)` in future Python bindings) are byte-identical
158
+ to 3.1.0.
159
+ - `VadConfig` now has a public `validate()` method returning
160
+ `Result<usize, DecibriError>` where the `usize` is the Silero VAD
161
+ `window_size` for the validated sample rate. Called automatically by
162
+ `SileroVad::new`; can be called explicitly to fail-fast before paying
163
+ ORT-initialization cost.
164
+ - Device enumeration and resolution logic in `crates/decibri/src/device.rs`
165
+ consolidated into a shared direction-generic implementation (input and
166
+ output share code paths via an internal `DeviceDirection` trait). No
167
+ public API change.
168
+ - Workspace minimum-supported Rust version (MSRV) declared at rustc 1.88,
169
+ forced by `ort 2.0.0-rc.12` which requires `edition = "2024"`.
170
+
171
+ ### Added
172
+
173
+ - `CaptureStream::try_next_chunk()`: non-blocking read, returns
174
+ `Result<Option<AudioChunk>, DecibriError>` with a three-state return
175
+ (`Some` chunk / `None` if no data yet / `Err(CaptureStreamClosed)` if
176
+ terminal). Declared stable across 3.x as part of decibri's canonical
177
+ FFI-consumer surface.
178
+ - `CaptureStream::next_chunk(timeout: Option<Duration>)`: blocking read
179
+ with optional timeout, same three-state return shape. Declared stable
180
+ across 3.x. Concurrent `stop()` unblocks a waiter within approximately
181
+ 20 ms via internal polling.
182
+ - `DecibriError::is_ort_path_error()`: helper that returns true for both
183
+ `OrtLoadFailed` and `OrtPathInvalid`. Consumers handling path-level ORT
184
+ failures should match this rather than enumerating both variants
185
+ manually. The split between the two variants is a mechanical necessity
186
+ (constructing `ort::Error` under `ort-load-dynamic` triggers an ORT C
187
+ API call and would reintroduce the Windows hang).
188
+ - Crate-level rustdoc on `decibri`'s `lib.rs` covering capabilities,
189
+ feature flags, ORT distribution modes, an end-to-end capture-plus-VAD
190
+ example, the process-global ORT initialization constraint, thread-safety
191
+ summary, and the 3.x FFI-surface stability contract.
192
+ - Rust integration tests for the VAD / ORT pipeline in
193
+ `crates/decibri/tests/vad_integration.rs` (happy path, model-not-found,
194
+ config validation, end-to-end silence inference) and
195
+ `crates/decibri/tests/vad_ort_load_failure.rs` (load-failure path
196
+ isolation, feature-gated to `ort-load-dynamic`). All CI-safe; no audio
197
+ hardware required.
198
+ - Unit tests for `try_next_chunk` / `next_chunk` semantics (7 tests
199
+ covering empty-queue, chunk-available, buffered-flush-before-closed,
200
+ timeout, blocking-until-arrival, and polling-interval-correctness after
201
+ concurrent `stop()`).
202
+ - Pre-publish packaging gate in `.github/workflows/release.yml`:
203
+ ports the `verify_pack` function from `release-dryrun.yml` to run
204
+ `npm pack --dry-run` against all 5 packages before any `npm publish`
205
+ step. Closes the dryrun-skip honor-system gap: release-dryrun.yml
206
+ previously caught packaging bugs but only if it was actually run before
207
+ tagging.
208
+
209
+ ### Fixed
210
+
211
+ - Windows hang in VAD initialization: passing a nonexistent or
212
+ directory path as `VadConfig::ort_library_path` (or via the Node
213
+ binding's `ortLibraryPath`) caused `ort::init_from` to hang
214
+ indefinitely on Windows against pyke/ort 2.0.0-rc.12 with
215
+ onnxruntime 1.24.4. `init_ort_once` now performs a filesystem-level
216
+ `Path::is_file()` pre-check before handing the path to ORT, returning
217
+ `DecibriError::OrtPathInvalid` immediately for any path that fails the
218
+ check. The pre-check never touches ORT symbols, so it cannot itself
219
+ trigger the dylib load it is designed to prevent.
220
+
221
+ ### Migration notes for direct Rust crate consumers
222
+
223
+ - Matches against `DecibriError::Other(msg)` will stop compiling. Replace
224
+ with matches against the specific new variants. The `error.message`
225
+ text is unchanged; code using `.to_string()` rather than pattern
226
+ matching continues to work unaffected.
227
+ - `DecibriError` is now `#[non_exhaustive]`: match expressions against
228
+ it must include a `_ =>` catch-all arm. New variants added in future
229
+ releases are non-breaking under this constraint.
230
+ - Consumers handling "ORT path failed" should prefer
231
+ `err.is_ort_path_error()` or match both `OrtLoadFailed { .. }` and
232
+ `OrtPathInvalid { .. }`. The two variants represent the same
233
+ conceptual failure mode split for FFI-side-effect reasons.
234
+ - MSRV raised to rustc 1.88 (from effectively-unpinned in 3.1.x). This
235
+ is forced by `ort 2.0.0-rc.12` declaring `edition = "2024"`. Projects
236
+ on older rustc cannot build decibri 3.2.0 directly; stay on 3.1.x or
237
+ upgrade the toolchain.
238
+ - `VadConfig::validate()` was new in 3.2.0 (no 3.1.x public signature
239
+ to break) and has the final form `Result<usize, DecibriError>`. If you
240
+ only need pass/fail, call `.is_ok()` or `.map(|_| ())`.
241
+
242
+ Node.js and browser consumers: no API or behaviour change. `error.message`
243
+ text from the native addon is byte-identical to 3.1.0 (verified against
244
+ the 38-assertion CI suite).
245
+
246
+ ### Internal
247
+
248
+ - `ort` crate version unchanged at `2.0.0-rc.12`.
249
+ - Bundled ONNX Runtime version unchanged at `1.24.4`.
250
+ - Node binding error mapping at `bindings/node/src/lib.rs::to_napi_error`
251
+ explicitly enumerates every variant (compiler-enforced exhaustive
252
+ during the refactor by temporarily removing `#[non_exhaustive]` and
253
+ the `_ =>` arm; both restored). New variants added upstream fall
254
+ through to `GenericFailure` at runtime rather than failing to compile.
255
+ - `CaptureStream._stream` field type changed from `cpal::Stream` to
256
+ `Option<cpal::Stream>` purely to enable unit-test construction without
257
+ a real audio device. Production always stores `Some(stream)`; drop
258
+ semantics are identical.
259
+ - docs.rs metadata added to target all 4 production platforms (Linux x64/ARM64,
260
+ macOS ARM64, Windows x64) for full platform-specific rustdoc rendering.
261
+ Aligns with the docs.rs change effective 2026-05-01 (which builds fewer
262
+ targets by default).
263
+
264
+ ## [3.1.0] - 2026-04-22
265
+
266
+ Internal rearchitecture: ONNX Runtime is now loaded dynamically at runtime
267
+ instead of embedded statically at build time. Public Node.js and browser
268
+ APIs are unchanged. Direct Rust crate consumers see a behaviour change;
269
+ see migration notes below.
270
+
271
+ ### Changed
272
+
273
+ - ORT integration switched from `ort/download-binaries` to `ort/load-dynamic`.
274
+ npm platform packages now bundle the ONNX Runtime shared library alongside
275
+ the native addon. No change to `npm install decibri` workflow or to
276
+ Decibri construction.
277
+ - Silero VAD now loads ONNX Runtime dynamically from the bundled shared
278
+ library inside the installed platform package. Path resolution is
279
+ automatic; the `ORT_DYLIB_PATH` environment variable is honoured as a
280
+ developer escape hatch when set before Node.js starts.
281
+ - Bundled ONNX Runtime pinned to 1.24.4 (matches `ort 2.0.0-rc.12`'s
282
+ `api-24` ABI target).
283
+ - Native addon size reduced from ~20 MB (3.0.x) to ~817 KB on Windows x64.
284
+ ORT runtime now shipped separately as a ~13.5 MB bundled dylib inside
285
+ the platform package. Net platform package size roughly unchanged, with
286
+ better separation of concerns.
287
+ - Error message text in `decibri-*` error variants has been normalized
288
+ for style consistency (em-dashes replaced with sentence splits). The
289
+ error message prefixes (e.g., `"device index out of range"`) are
290
+ unchanged, so consumers matching those prefixes are unaffected.
291
+ Consumers matching full error message strings may need to update.
292
+
293
+ ### Added
294
+
295
+ - Cargo features on the `decibri` crate for direct Rust consumers:
296
+ `ort-load-dynamic` (default), `ort-download-binaries` (opt-in, restores
297
+ 3.0.x zero-config build behaviour), and execution-provider passthroughs
298
+ `coreml`, `cuda`, `directml`, `rocm` (off by default).
299
+ - Rust unit tests for device enumeration (`is_default` correctness under
300
+ duplicate display names).
301
+ - Release pipeline hardening: version-match preflight, `curl --retry` on
302
+ ORT downloads, macOS code-signature verification, Windows DLL imports
303
+ inspection, stage-and-verify packaging validation in release-dryrun.
304
+ - Upstream dependency monitoring for Microsoft's ONNX Runtime releases
305
+ (notification-only; guards against ABI mismatch upgrades).
306
+
307
+ ### Fixed
308
+
309
+ - Issue #14: when two audio devices share a display name (e.g. two USB
310
+ microphones both reporting "Microphone"), both were previously marked
311
+ `is_default: true`. Now uses cpal 0.17's `Device::id()` for stable
312
+ per-host device identity (WASAPI endpoint ID, CoreAudio UID, ALSA
313
+ PCM ID). Fix applies to both input and output device enumeration.
314
+
315
+ ### Migration notes for direct Rust crate consumers
316
+
317
+ If you depend on `decibri` directly via `cargo add decibri` and use the
318
+ `vad` feature:
319
+
320
+ - **Option 1 (recommended for zero-config builds):** pin with
321
+ `--features ort-download-binaries` on the dependency, which restores
322
+ the 3.0.x behaviour (ORT downloaded at build time, embedded statically).
323
+ - **Option 2 (recommended for production deployments):** keep default
324
+ features and either set `ORT_DYLIB_PATH=/path/to/libonnxruntime.so`
325
+ before first use, or call `ort::init_from(path).commit()` at startup,
326
+ or pass `ort_library_path` on `VadConfig` when constructing `SileroVad`.
327
+
328
+ Known limitation: ONNX Runtime is initialized once per process. Multiple
329
+ `Decibri`/`SileroVad` instances constructed with different `ort_library_path`
330
+ values will silently use the first-constructed instance's path. Pick one
331
+ path and use it consistently.
332
+
333
+ Direct consumers using only `capture`, `output`, `denoise`, or `gain`
334
+ features (no `vad`) are unaffected.
335
+
336
+ ### Internal
337
+
338
+ - `ort` crate version unchanged at `2.0.0-rc.12`.
339
+ - `tls-native` ORT feature removed (was only required for `download-binaries`'
340
+ HTTPS fetch).
341
+
342
+ ## [3.0.0] - 2026-04-11
343
+
344
+ Complete rewrite from C++ (PortAudio) to Rust (cpal). One unified package for Node.js and browsers. Version jumps from 1.0.0 to 3.0.0 because this release replaces both `decibri` (v1) and `decibri-web` (v0.1.1). v2.x was never published.
345
+
346
+ ### Changed
347
+
348
+ - Complete rewrite from C++ (PortAudio) to Rust (cpal)
349
+ - Native addon built with napi-rs (replaces node-gyp / prebuildify)
350
+ - JS API unchanged: drop-in replacement for v1.x consumers (verified against the in-house consumer surface)
351
+
352
+ ### Added
353
+
354
+ - Audio output: `DecibriOutput` class (`Writable` stream, speaker playback)
355
+ - Browser support: unified package with conditional exports, AudioWorklet capture
356
+ - Silero VAD: ML-based voice activity detection via `vadMode: 'silero'`
357
+ - Full duplex: `mic.pipe(speaker)` for simultaneous capture and playback
358
+ - `format: 'float32'` output support alongside `'int16'`
359
+ - Output device enumeration: `DecibriOutput.devices()`
360
+ - TypeScript declarations for all APIs (Node.js capture, Node.js output, browser)
361
+ - `crates.io` publication as a Rust crate
362
+
363
+ ### Removed
364
+
365
+ - PortAudio dependency (replaced by cpal)
366
+ - node-gyp / prebuildify build system (replaced by napi-rs)
367
+ - Source build fallback (Rust binaries are self-contained)
368
+
369
+ ### Deprecated
370
+
371
+ - `decibri-web` npm package (use `decibri` with the browser conditional export instead)
372
+
373
+ ## [1.0.0] - 2025-06-15
374
+
375
+ Initial release. C++ native addon wrapping PortAudio with pre-built binaries.
376
+
377
+ - Microphone capture as a Node.js `Readable` stream
378
+ - Pre-built binaries for Windows x64, macOS ARM64, Linux x64, Linux ARM64
379
+ - Energy-based voice activity detection (`vad`, `vadThreshold`, `vadHoldoff`)
380
+ - Device enumeration and selection by index or name
381
+ - Int16 PCM output (little-endian)
382
+ - Source build fallback via node-gyp
383
+
384
+ [3.2.0]: https://github.com/decibri/decibri/compare/v3.1.0...v3.2.0
385
+ [3.1.0]: https://github.com/decibri/decibri/compare/v3.0.0...v3.1.0
386
+ [3.0.0]: https://github.com/decibri/decibri/compare/v1.0.0...v3.0.0
package/MIGRATION.md ADDED
@@ -0,0 +1,151 @@
1
+ # Migrating to decibri 4.0.0
2
+
3
+ decibri 4.0.0 renames the Node.js API to a shared microphone and speaker
4
+ vocabulary that matches the Rust and Python packages, and tidies several option
5
+ and return shapes. This guide lists every breaking change with before and after
6
+ code.
7
+
8
+ ## Named exports
9
+
10
+ The package no longer has a single default export. Destructure what you need.
11
+
12
+ Before:
13
+
14
+ ```js
15
+ const Decibri = require('decibri');
16
+ const { DecibriOutput } = Decibri;
17
+ ```
18
+
19
+ After:
20
+
21
+ ```js
22
+ const { Microphone, Speaker, inputDevices, outputDevices, version } = require('decibri');
23
+ ```
24
+
25
+ ## Class renames
26
+
27
+ `Decibri` is now `Microphone`; `DecibriOutput` is now `Speaker`.
28
+
29
+ Before:
30
+
31
+ ```js
32
+ const mic = new Decibri({ sampleRate: 16000 });
33
+ const speaker = new DecibriOutput({ sampleRate: 16000 });
34
+ ```
35
+
36
+ After:
37
+
38
+ ```js
39
+ const mic = new Microphone({ sampleRate: 16000 });
40
+ const speaker = new Speaker({ sampleRate: 16000 });
41
+ ```
42
+
43
+ ## The format option is now dtype
44
+
45
+ Before:
46
+
47
+ ```js
48
+ new Microphone({ format: 'float32' });
49
+ ```
50
+
51
+ After:
52
+
53
+ ```js
54
+ new Microphone({ dtype: 'float32' });
55
+ ```
56
+
57
+ The accepted values (`'int16'`, `'float32'`) are unchanged.
58
+
59
+ ## Device-info type renames
60
+
61
+ `DeviceInfo` is now `MicrophoneInfo`; `OutputDeviceInfo` is now `SpeakerInfo`.
62
+
63
+ Before:
64
+
65
+ ```ts
66
+ import { DeviceInfo, OutputDeviceInfo } from 'decibri';
67
+ ```
68
+
69
+ After:
70
+
71
+ ```ts
72
+ import { MicrophoneInfo, SpeakerInfo } from 'decibri';
73
+ ```
74
+
75
+ ## The vad option is now a single union
76
+
77
+ The `vad: true` plus `vadMode` pair is gone. Pass the mode directly.
78
+
79
+ Before:
80
+
81
+ ```js
82
+ new Microphone({ vad: true, vadMode: 'silero' });
83
+ new Microphone({ vad: true, vadMode: 'energy' });
84
+ new Microphone({ vad: false });
85
+ ```
86
+
87
+ After:
88
+
89
+ ```js
90
+ new Microphone({ vad: 'silero' });
91
+ new Microphone({ vad: 'energy' });
92
+ new Microphone({ vad: false }); // unchanged; the default
93
+ ```
94
+
95
+ `vad: true` now throws a `TypeError` telling you to specify the mode.
96
+ `vadThreshold` (default 0.5 for silero, 0.01 for energy), `vadHoldoff`, and
97
+ `modelPath` are unchanged. A new `vadScore` getter returns the latest score for
98
+ the active mode (the Silero probability in silero mode, the normalized RMS in
99
+ energy mode, 0 when disabled).
100
+
101
+ ## version() shape
102
+
103
+ Before:
104
+
105
+ ```js
106
+ version(); // { decibri, portaudio }
107
+ ```
108
+
109
+ After:
110
+
111
+ ```js
112
+ version(); // { decibri, audioBackend, binding }
113
+ ```
114
+
115
+ `audioBackend` replaces the inaccurately named `portaudio` field; its value is
116
+ unchanged. `binding` is the new field reporting the npm package version.
117
+
118
+ ## Error handling
119
+
120
+ 4.0.0 adds error classes you can catch:
121
+
122
+ ```js
123
+ const { Microphone, DecibriError, DeviceError } = require('decibri');
124
+
125
+ try {
126
+ new Microphone({ device: 'no such device' });
127
+ } catch (err) {
128
+ if (err instanceof DeviceError) {
129
+ console.log(err.code); // e.g. 'MICROPHONE_NOT_FOUND'
130
+ }
131
+ }
132
+ ```
133
+
134
+ `DeviceError`, `OrtError`, and `OrtPathError` all extend `DecibriError`, which
135
+ extends `Error`. Each carries a stable `code` string.
136
+
137
+ ## Two deliberate differences from the Python package
138
+
139
+ If you use both the Node.js and Python packages, note these intended
140
+ differences:
141
+
142
+ 1. An out-of-range device index throws a `RangeError` in Node.js, the idiomatic
143
+ type for a bad numeric index. The Python package groups the same condition
144
+ under its `DeviceError`. Argument-validation errors in Node.js (bad sample
145
+ rate, channels, dtype, or vad value) also stay built-in `RangeError` or
146
+ `TypeError`, not `DecibriError` subclasses.
147
+ 2. The browser surface runs energy-mode VAD only, because Silero needs the
148
+ native runtime that ships with the Node.js build. The browser `vad` option
149
+ accepts `false` or `'energy'`, and the browser `version()` returns
150
+ `{ decibri }` only, because the browser has no native audio backend or a
151
+ separate binding version.