decibri 3.4.2 → 4.1.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,393 @@
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.1.0] - 2026-05-31
15
+
16
+ ### Added
17
+
18
+ - Async factories `Microphone.open(options)` and `Speaker.open(options)`, each returning a `Promise` that resolves to a constructed instance. They perform the blocking open work (the Silero VAD model load for the microphone; device resolution for both) on the native thread pool instead of the event loop, so latency-sensitive callers do not stall during construction. A failed open rejects with the matching error (`RangeError` / `TypeError` for invalid options, `DeviceError` / `OrtError` / `OrtPathError` for native failures). The synchronous `new Microphone(...)` and `new Speaker(...)` constructors are unchanged; the factories are an additive, non-blocking alternative that mirrors the Python `AsyncMicrophone.open()` / `AsyncSpeaker.open()` surface.
19
+ - Non-blocking `Speaker.writeAsync(chunk)` and `Speaker.drainAsync()` methods, each returning a `Promise`. They run the blocking parts of playback off the event loop: `writeAsync` performs the backpressure wait when the native playback queue is full, and `drainAsync` performs the wait for queued audio to finish playing. The audio stream stays on its own thread; only the thread-safe sample channel and drain state are used off the event loop. A failed write or drain rejects with the matching error class. The synchronous `write()` / `pipe()` / `end()` Writable interface is unchanged; the async methods are an additive, direct alternative (do not interleave the two paths on one instance).
20
+
21
+ ## [4.0.0] - 2026-05-30
22
+
23
+ ### Changed
24
+
25
+ - BREAKING: the package now uses named exports. Import with `const { Microphone, Speaker, inputDevices, outputDevices, version } = require('decibri')` instead of the previous single default export.
26
+ - BREAKING: the capture class `Decibri` is now `Microphone`, and the playback class `DecibriOutput` is now `Speaker`. The browser capture class is `Microphone` too.
27
+ - BREAKING: the sample-encoding option `format` is now `dtype` (the `'int16'` and `'float32'` values are unchanged).
28
+ - BREAKING: the device-info types are now `MicrophoneInfo` and `SpeakerInfo` (were `DeviceInfo` and `OutputDeviceInfo`).
29
+ - 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.
30
+ - BREAKING: `version()` now returns `{ decibri, audioBackend, binding }` (was `{ decibri, portaudio }`). `audioBackend` replaces the inaccurate `portaudio` name, and `binding` reports the npm package version.
31
+
32
+ ### Added
33
+
34
+ - Error classes `DecibriError`, `DeviceError`, `OrtError`, and `OrtPathError`, each with a `code` property, for `instanceof` and `err.code` handling.
35
+ - `vadScore` getter on the capture class: the Silero probability in `'silero'` mode, the normalized RMS in `'energy'` mode, 0 when disabled.
36
+ - Module-level `inputDevices()`, `outputDevices()`, and `version()` free functions, alongside the static `Microphone.devices()` and `Speaker.devices()` methods.
37
+
38
+ ## [3.4.2] - 2026-05-24
39
+
40
+ ### Fixed
41
+
42
+ - 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.
43
+
44
+ ## [3.4.1] - 2026-05-23
45
+
46
+ ### Fixed
47
+
48
+ - 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.
49
+ - `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).
50
+ - `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.
51
+
52
+ ## [3.4.0] - 2026-05-02
53
+
54
+ ### Added
55
+
56
+ - **`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.
57
+ - `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.
58
+ - **`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.
59
+
60
+ ### Internal
61
+
62
+ - `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.
63
+ - `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.
64
+ - `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.
65
+
66
+ ## [3.3.2] - 2026-04-26
67
+
68
+ ### Changed
69
+
70
+ - **`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.
71
+ - **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.
72
+ - **`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]`).
73
+ - **`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.
74
+
75
+ ### Migration notes
76
+
77
+ - **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.
78
+ - 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.
79
+ - **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.
80
+
81
+ ## [3.3.1] - 2026-04-25
82
+
83
+ ### Changed
84
+
85
+ - **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.
86
+ - `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).
87
+ - `FramesPerBufferOutOfRange`: `"framesPerBuffer must be between 64 and 65536"` -> `"frames per buffer must be between 64 and 65536"`. Same rationale.
88
+ - `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").
89
+ - `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.
90
+ - `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.
91
+ - `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).
92
+ - 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).
93
+
94
+ ### Migration notes
95
+
96
+ 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.
97
+
98
+ - **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.
99
+ - **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.
100
+ - **Python consumers**: the in-development Python wheel consumes `decibri@3.3.1`, so the messages it sees are the corrected forms.
101
+
102
+ ## [3.3.0] - 2026-04-23
103
+
104
+ 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).
105
+
106
+ ### Changed
107
+
108
+ - `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.
109
+
110
+ ### Added
111
+
112
+ - `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.
113
+ - `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`.
114
+ - `DecibriError::OutputDeviceNotFound(String)` variant, the output-device equivalent of `DeviceNotFound`. See Fixed below for the motivating bug.
115
+ - 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`.
116
+ - `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`.
117
+ - `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.
118
+ - `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.
119
+
120
+ ### Fixed
121
+
122
+ - `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.
123
+
124
+ ### Internal
125
+
126
+ - `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.
127
+ - 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.
128
+ - 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).
129
+ - `lib.rs` rustdoc "Feature flags" section cross-references `docs/features.md` for consumers wanting the deep-dive reference.
130
+ - 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.
131
+ - 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.
132
+ - `ort` crate version unchanged at `2.0.0-rc.12`.
133
+ - Bundled ONNX Runtime version unchanged at `1.24.4`.
134
+ - No Node.js API signatures, event names, or error messages changed.
135
+ - 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.
136
+
137
+ ### Migration notes for direct Rust crate consumers
138
+
139
+ - 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.
140
+ - 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.
141
+ - 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.
142
+ - MSRV unchanged at rustc 1.88.
143
+
144
+ ## [3.2.0] - 2026-04-22
145
+
146
+ Refactor release. Public Node.js and browser APIs are unchanged. Direct
147
+ Rust crate consumers get a structured `DecibriError` taxonomy with full
148
+ error-chain preservation, a new stable FFI-ready stream-reading API on
149
+ `CaptureStream`, a declared minimum-supported-Rust-version, and a Windows
150
+ hang fix in VAD initialization. See migration notes below.
151
+
152
+ ### Changed
153
+
154
+ - `DecibriError` is now `#[non_exhaustive]` and the `Other(String)`
155
+ catch-all variant has been removed. All previous `Other(...)` failures
156
+ now have dedicated typed variants: `DeviceEnumerationFailed`,
157
+ `CaptureStreamClosed`, `OutputStreamClosed`, `VadSampleRateUnsupported`,
158
+ `VadThresholdOutOfRange`, `OrtInitFailed`, `OrtLoadFailed`,
159
+ `OrtPathInvalid`, `OrtSessionBuildFailed`, `OrtThreadsConfigFailed`,
160
+ `VadModelLoadFailed`, `OrtInferenceFailed`, `OrtTensorCreateFailed`,
161
+ `OrtTensorExtractFailed`. Path-carrying variants use `PathBuf`; ORT
162
+ variants carry `#[source] ort::Error` so `error.source()` walks the
163
+ error chain. Display strings (and therefore `error.message` in Node
164
+ and `str(exception)` in future Python bindings) are byte-identical
165
+ to 3.1.0.
166
+ - `VadConfig` now has a public `validate()` method returning
167
+ `Result<usize, DecibriError>` where the `usize` is the Silero VAD
168
+ `window_size` for the validated sample rate. Called automatically by
169
+ `SileroVad::new`; can be called explicitly to fail-fast before paying
170
+ ORT-initialization cost.
171
+ - Device enumeration and resolution logic in `crates/decibri/src/device.rs`
172
+ consolidated into a shared direction-generic implementation (input and
173
+ output share code paths via an internal `DeviceDirection` trait). No
174
+ public API change.
175
+ - Workspace minimum-supported Rust version (MSRV) declared at rustc 1.88,
176
+ forced by `ort 2.0.0-rc.12` which requires `edition = "2024"`.
177
+
178
+ ### Added
179
+
180
+ - `CaptureStream::try_next_chunk()`: non-blocking read, returns
181
+ `Result<Option<AudioChunk>, DecibriError>` with a three-state return
182
+ (`Some` chunk / `None` if no data yet / `Err(CaptureStreamClosed)` if
183
+ terminal). Declared stable across 3.x as part of decibri's canonical
184
+ FFI-consumer surface.
185
+ - `CaptureStream::next_chunk(timeout: Option<Duration>)`: blocking read
186
+ with optional timeout, same three-state return shape. Declared stable
187
+ across 3.x. Concurrent `stop()` unblocks a waiter within approximately
188
+ 20 ms via internal polling.
189
+ - `DecibriError::is_ort_path_error()`: helper that returns true for both
190
+ `OrtLoadFailed` and `OrtPathInvalid`. Consumers handling path-level ORT
191
+ failures should match this rather than enumerating both variants
192
+ manually. The split between the two variants is a mechanical necessity
193
+ (constructing `ort::Error` under `ort-load-dynamic` triggers an ORT C
194
+ API call and would reintroduce the Windows hang).
195
+ - Crate-level rustdoc on `decibri`'s `lib.rs` covering capabilities,
196
+ feature flags, ORT distribution modes, an end-to-end capture-plus-VAD
197
+ example, the process-global ORT initialization constraint, thread-safety
198
+ summary, and the 3.x FFI-surface stability contract.
199
+ - Rust integration tests for the VAD / ORT pipeline in
200
+ `crates/decibri/tests/vad_integration.rs` (happy path, model-not-found,
201
+ config validation, end-to-end silence inference) and
202
+ `crates/decibri/tests/vad_ort_load_failure.rs` (load-failure path
203
+ isolation, feature-gated to `ort-load-dynamic`). All CI-safe; no audio
204
+ hardware required.
205
+ - Unit tests for `try_next_chunk` / `next_chunk` semantics (7 tests
206
+ covering empty-queue, chunk-available, buffered-flush-before-closed,
207
+ timeout, blocking-until-arrival, and polling-interval-correctness after
208
+ concurrent `stop()`).
209
+ - Pre-publish packaging gate in `.github/workflows/release.yml`:
210
+ ports the `verify_pack` function from `release-dryrun.yml` to run
211
+ `npm pack --dry-run` against all 5 packages before any `npm publish`
212
+ step. Closes the dryrun-skip honor-system gap: release-dryrun.yml
213
+ previously caught packaging bugs but only if it was actually run before
214
+ tagging.
215
+
216
+ ### Fixed
217
+
218
+ - Windows hang in VAD initialization: passing a nonexistent or
219
+ directory path as `VadConfig::ort_library_path` (or via the Node
220
+ binding's `ortLibraryPath`) caused `ort::init_from` to hang
221
+ indefinitely on Windows against pyke/ort 2.0.0-rc.12 with
222
+ onnxruntime 1.24.4. `init_ort_once` now performs a filesystem-level
223
+ `Path::is_file()` pre-check before handing the path to ORT, returning
224
+ `DecibriError::OrtPathInvalid` immediately for any path that fails the
225
+ check. The pre-check never touches ORT symbols, so it cannot itself
226
+ trigger the dylib load it is designed to prevent.
227
+
228
+ ### Migration notes for direct Rust crate consumers
229
+
230
+ - Matches against `DecibriError::Other(msg)` will stop compiling. Replace
231
+ with matches against the specific new variants. The `error.message`
232
+ text is unchanged; code using `.to_string()` rather than pattern
233
+ matching continues to work unaffected.
234
+ - `DecibriError` is now `#[non_exhaustive]`: match expressions against
235
+ it must include a `_ =>` catch-all arm. New variants added in future
236
+ releases are non-breaking under this constraint.
237
+ - Consumers handling "ORT path failed" should prefer
238
+ `err.is_ort_path_error()` or match both `OrtLoadFailed { .. }` and
239
+ `OrtPathInvalid { .. }`. The two variants represent the same
240
+ conceptual failure mode split for FFI-side-effect reasons.
241
+ - MSRV raised to rustc 1.88 (from effectively-unpinned in 3.1.x). This
242
+ is forced by `ort 2.0.0-rc.12` declaring `edition = "2024"`. Projects
243
+ on older rustc cannot build decibri 3.2.0 directly; stay on 3.1.x or
244
+ upgrade the toolchain.
245
+ - `VadConfig::validate()` was new in 3.2.0 (no 3.1.x public signature
246
+ to break) and has the final form `Result<usize, DecibriError>`. If you
247
+ only need pass/fail, call `.is_ok()` or `.map(|_| ())`.
248
+
249
+ Node.js and browser consumers: no API or behaviour change. `error.message`
250
+ text from the native addon is byte-identical to 3.1.0 (verified against
251
+ the 38-assertion CI suite).
252
+
253
+ ### Internal
254
+
255
+ - `ort` crate version unchanged at `2.0.0-rc.12`.
256
+ - Bundled ONNX Runtime version unchanged at `1.24.4`.
257
+ - Node binding error mapping at `bindings/node/src/lib.rs::to_napi_error`
258
+ explicitly enumerates every variant (compiler-enforced exhaustive
259
+ during the refactor by temporarily removing `#[non_exhaustive]` and
260
+ the `_ =>` arm; both restored). New variants added upstream fall
261
+ through to `GenericFailure` at runtime rather than failing to compile.
262
+ - `CaptureStream._stream` field type changed from `cpal::Stream` to
263
+ `Option<cpal::Stream>` purely to enable unit-test construction without
264
+ a real audio device. Production always stores `Some(stream)`; drop
265
+ semantics are identical.
266
+ - docs.rs metadata added to target all 4 production platforms (Linux x64/ARM64,
267
+ macOS ARM64, Windows x64) for full platform-specific rustdoc rendering.
268
+ Aligns with the docs.rs change effective 2026-05-01 (which builds fewer
269
+ targets by default).
270
+
271
+ ## [3.1.0] - 2026-04-22
272
+
273
+ Internal rearchitecture: ONNX Runtime is now loaded dynamically at runtime
274
+ instead of embedded statically at build time. Public Node.js and browser
275
+ APIs are unchanged. Direct Rust crate consumers see a behaviour change;
276
+ see migration notes below.
277
+
278
+ ### Changed
279
+
280
+ - ORT integration switched from `ort/download-binaries` to `ort/load-dynamic`.
281
+ npm platform packages now bundle the ONNX Runtime shared library alongside
282
+ the native addon. No change to `npm install decibri` workflow or to
283
+ Decibri construction.
284
+ - Silero VAD now loads ONNX Runtime dynamically from the bundled shared
285
+ library inside the installed platform package. Path resolution is
286
+ automatic; the `ORT_DYLIB_PATH` environment variable is honoured as a
287
+ developer escape hatch when set before Node.js starts.
288
+ - Bundled ONNX Runtime pinned to 1.24.4 (matches `ort 2.0.0-rc.12`'s
289
+ `api-24` ABI target).
290
+ - Native addon size reduced from ~20 MB (3.0.x) to ~817 KB on Windows x64.
291
+ ORT runtime now shipped separately as a ~13.5 MB bundled dylib inside
292
+ the platform package. Net platform package size roughly unchanged, with
293
+ better separation of concerns.
294
+ - Error message text in `decibri-*` error variants has been normalized
295
+ for style consistency (em-dashes replaced with sentence splits). The
296
+ error message prefixes (e.g., `"device index out of range"`) are
297
+ unchanged, so consumers matching those prefixes are unaffected.
298
+ Consumers matching full error message strings may need to update.
299
+
300
+ ### Added
301
+
302
+ - Cargo features on the `decibri` crate for direct Rust consumers:
303
+ `ort-load-dynamic` (default), `ort-download-binaries` (opt-in, restores
304
+ 3.0.x zero-config build behaviour), and execution-provider passthroughs
305
+ `coreml`, `cuda`, `directml`, `rocm` (off by default).
306
+ - Rust unit tests for device enumeration (`is_default` correctness under
307
+ duplicate display names).
308
+ - Release pipeline hardening: version-match preflight, `curl --retry` on
309
+ ORT downloads, macOS code-signature verification, Windows DLL imports
310
+ inspection, stage-and-verify packaging validation in release-dryrun.
311
+ - Upstream dependency monitoring for Microsoft's ONNX Runtime releases
312
+ (notification-only; guards against ABI mismatch upgrades).
313
+
314
+ ### Fixed
315
+
316
+ - Issue #14: when two audio devices share a display name (e.g. two USB
317
+ microphones both reporting "Microphone"), both were previously marked
318
+ `is_default: true`. Now uses cpal 0.17's `Device::id()` for stable
319
+ per-host device identity (WASAPI endpoint ID, CoreAudio UID, ALSA
320
+ PCM ID). Fix applies to both input and output device enumeration.
321
+
322
+ ### Migration notes for direct Rust crate consumers
323
+
324
+ If you depend on `decibri` directly via `cargo add decibri` and use the
325
+ `vad` feature:
326
+
327
+ - **Option 1 (recommended for zero-config builds):** pin with
328
+ `--features ort-download-binaries` on the dependency, which restores
329
+ the 3.0.x behaviour (ORT downloaded at build time, embedded statically).
330
+ - **Option 2 (recommended for production deployments):** keep default
331
+ features and either set `ORT_DYLIB_PATH=/path/to/libonnxruntime.so`
332
+ before first use, or call `ort::init_from(path).commit()` at startup,
333
+ or pass `ort_library_path` on `VadConfig` when constructing `SileroVad`.
334
+
335
+ Known limitation: ONNX Runtime is initialized once per process. Multiple
336
+ `Decibri`/`SileroVad` instances constructed with different `ort_library_path`
337
+ values will silently use the first-constructed instance's path. Pick one
338
+ path and use it consistently.
339
+
340
+ Direct consumers using only `capture`, `output`, `denoise`, or `gain`
341
+ features (no `vad`) are unaffected.
342
+
343
+ ### Internal
344
+
345
+ - `ort` crate version unchanged at `2.0.0-rc.12`.
346
+ - `tls-native` ORT feature removed (was only required for `download-binaries`'
347
+ HTTPS fetch).
348
+
349
+ ## [3.0.0] - 2026-04-11
350
+
351
+ 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.
352
+
353
+ ### Changed
354
+
355
+ - Complete rewrite from C++ (PortAudio) to Rust (cpal)
356
+ - Native addon built with napi-rs (replaces node-gyp / prebuildify)
357
+ - JS API unchanged: drop-in replacement for v1.x consumers (verified against the in-house consumer surface)
358
+
359
+ ### Added
360
+
361
+ - Audio output: `DecibriOutput` class (`Writable` stream, speaker playback)
362
+ - Browser support: unified package with conditional exports, AudioWorklet capture
363
+ - Silero VAD: ML-based voice activity detection via `vadMode: 'silero'`
364
+ - Full duplex: `mic.pipe(speaker)` for simultaneous capture and playback
365
+ - `format: 'float32'` output support alongside `'int16'`
366
+ - Output device enumeration: `DecibriOutput.devices()`
367
+ - TypeScript declarations for all APIs (Node.js capture, Node.js output, browser)
368
+ - `crates.io` publication as a Rust crate
369
+
370
+ ### Removed
371
+
372
+ - PortAudio dependency (replaced by cpal)
373
+ - node-gyp / prebuildify build system (replaced by napi-rs)
374
+ - Source build fallback (Rust binaries are self-contained)
375
+
376
+ ### Deprecated
377
+
378
+ - `decibri-web` npm package (use `decibri` with the browser conditional export instead)
379
+
380
+ ## [1.0.0] - 2025-06-15
381
+
382
+ Initial release. C++ native addon wrapping PortAudio with pre-built binaries.
383
+
384
+ - Microphone capture as a Node.js `Readable` stream
385
+ - Pre-built binaries for Windows x64, macOS ARM64, Linux x64, Linux ARM64
386
+ - Energy-based voice activity detection (`vad`, `vadThreshold`, `vadHoldoff`)
387
+ - Device enumeration and selection by index or name
388
+ - Int16 PCM output (little-endian)
389
+ - Source build fallback via node-gyp
390
+
391
+ [3.2.0]: https://github.com/decibri/decibri/compare/v3.1.0...v3.2.0
392
+ [3.1.0]: https://github.com/decibri/decibri/compare/v3.0.0...v3.1.0
393
+ [3.0.0]: https://github.com/decibri/decibri/compare/v1.0.0...v3.0.0
package/MIGRATION.md ADDED
@@ -0,0 +1,168 @@
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
+ ## New in 4.1.0 (additive, nothing to migrate)
9
+
10
+ decibri 4.1.0 is a non-breaking, additive release. Code written for 4.0.0 keeps
11
+ working unchanged; there is nothing to migrate. The release adds an opt-in
12
+ non-blocking API for event-loop-sensitive code:
13
+
14
+ - `Microphone.open(options)` and `Speaker.open(options)`: async factories that
15
+ construct an instance without blocking the event loop and resolve to a ready
16
+ instance. The synchronous `new Microphone(...)` and `new Speaker(...)`
17
+ constructors are unchanged.
18
+ - `speaker.writeAsync(chunk)` and `speaker.drainAsync()`: write and drain
19
+ without blocking the event loop. The synchronous `write()` / `pipe()` /
20
+ `end()` interface is unchanged.
21
+
22
+ See the Non-blocking API section of the README for examples. The rest of this
23
+ guide covers the 4.0.0 changes from 3.x.
24
+
25
+ ## Named exports
26
+
27
+ The package no longer has a single default export. Destructure what you need.
28
+
29
+ Before:
30
+
31
+ ```js
32
+ const Decibri = require('decibri');
33
+ const { DecibriOutput } = Decibri;
34
+ ```
35
+
36
+ After:
37
+
38
+ ```js
39
+ const { Microphone, Speaker, inputDevices, outputDevices, version } = require('decibri');
40
+ ```
41
+
42
+ ## Class renames
43
+
44
+ `Decibri` is now `Microphone`; `DecibriOutput` is now `Speaker`.
45
+
46
+ Before:
47
+
48
+ ```js
49
+ const mic = new Decibri({ sampleRate: 16000 });
50
+ const speaker = new DecibriOutput({ sampleRate: 16000 });
51
+ ```
52
+
53
+ After:
54
+
55
+ ```js
56
+ const mic = new Microphone({ sampleRate: 16000 });
57
+ const speaker = new Speaker({ sampleRate: 16000 });
58
+ ```
59
+
60
+ ## The format option is now dtype
61
+
62
+ Before:
63
+
64
+ ```js
65
+ new Microphone({ format: 'float32' });
66
+ ```
67
+
68
+ After:
69
+
70
+ ```js
71
+ new Microphone({ dtype: 'float32' });
72
+ ```
73
+
74
+ The accepted values (`'int16'`, `'float32'`) are unchanged.
75
+
76
+ ## Device-info type renames
77
+
78
+ `DeviceInfo` is now `MicrophoneInfo`; `OutputDeviceInfo` is now `SpeakerInfo`.
79
+
80
+ Before:
81
+
82
+ ```ts
83
+ import { DeviceInfo, OutputDeviceInfo } from 'decibri';
84
+ ```
85
+
86
+ After:
87
+
88
+ ```ts
89
+ import { MicrophoneInfo, SpeakerInfo } from 'decibri';
90
+ ```
91
+
92
+ ## The vad option is now a single union
93
+
94
+ The `vad: true` plus `vadMode` pair is gone. Pass the mode directly.
95
+
96
+ Before:
97
+
98
+ ```js
99
+ new Microphone({ vad: true, vadMode: 'silero' });
100
+ new Microphone({ vad: true, vadMode: 'energy' });
101
+ new Microphone({ vad: false });
102
+ ```
103
+
104
+ After:
105
+
106
+ ```js
107
+ new Microphone({ vad: 'silero' });
108
+ new Microphone({ vad: 'energy' });
109
+ new Microphone({ vad: false }); // unchanged; the default
110
+ ```
111
+
112
+ `vad: true` now throws a `TypeError` telling you to specify the mode.
113
+ `vadThreshold` (default 0.5 for silero, 0.01 for energy), `vadHoldoff`, and
114
+ `modelPath` are unchanged. A new `vadScore` getter returns the latest score for
115
+ the active mode (the Silero probability in silero mode, the normalized RMS in
116
+ energy mode, 0 when disabled).
117
+
118
+ ## version() shape
119
+
120
+ Before:
121
+
122
+ ```js
123
+ version(); // { decibri, portaudio }
124
+ ```
125
+
126
+ After:
127
+
128
+ ```js
129
+ version(); // { decibri, audioBackend, binding }
130
+ ```
131
+
132
+ `audioBackend` replaces the inaccurately named `portaudio` field; its value is
133
+ unchanged. `binding` is the new field reporting the npm package version.
134
+
135
+ ## Error handling
136
+
137
+ 4.0.0 adds error classes you can catch:
138
+
139
+ ```js
140
+ const { Microphone, DecibriError, DeviceError } = require('decibri');
141
+
142
+ try {
143
+ new Microphone({ device: 'no such device' });
144
+ } catch (err) {
145
+ if (err instanceof DeviceError) {
146
+ console.log(err.code); // e.g. 'MICROPHONE_NOT_FOUND'
147
+ }
148
+ }
149
+ ```
150
+
151
+ `DeviceError`, `OrtError`, and `OrtPathError` all extend `DecibriError`, which
152
+ extends `Error`. Each carries a stable `code` string.
153
+
154
+ ## Two deliberate differences from the Python package
155
+
156
+ If you use both the Node.js and Python packages, note these intended
157
+ differences:
158
+
159
+ 1. An out-of-range device index throws a `RangeError` in Node.js, the idiomatic
160
+ type for a bad numeric index. The Python package groups the same condition
161
+ under its `DeviceError`. Argument-validation errors in Node.js (bad sample
162
+ rate, channels, dtype, or vad value) also stay built-in `RangeError` or
163
+ `TypeError`, not `DecibriError` subclasses.
164
+ 2. The browser surface runs energy-mode VAD only, because Silero needs the
165
+ native runtime that ships with the Node.js build. The browser `vad` option
166
+ accepts `false` or `'energy'`, and the browser `version()` returns
167
+ `{ decibri }` only, because the browser has no native audio backend or a
168
+ separate binding version.