1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
//! Cross-platform audio capture, playback, and voice-activity detection.
//!
//! `decibri` is the Rust core behind the `decibri` npm package. It provides:
//!
//! - **Audio capture** ([`capture`]): microphone input with frame-exact
//! buffering, built on cpal.
//! - **Audio playback** ([`output`]): speaker output with graceful drain
//! and immediate-stop semantics.
//! - **Voice activity detection** ([`vad`]): Silero VAD v5 inference via
//! ONNX Runtime, per-call stateful.
//! - **Device enumeration** ([`device`]): list and select audio devices
//! by index or case-insensitive name substring.
//!
//! # Feature flags
//!
//! | Flag | Default | Purpose |
//! |-------------------------|---------|----------------------------------------------|
//! | `capture` | on | Microphone input stream support |
//! | `output` | on | Speaker output stream support |
//! | `vad` | on | Silero VAD ONNX inference |
//! | `denoise` | on | Reserved (stub) |
//! | `gain` | on | Reserved (stub) |
//! | `ort-load-dynamic` | on | ORT loaded at runtime from a user path |
//! | `ort-download-binaries` | off | ORT downloaded at build time, embedded |
//!
//! `ort-load-dynamic` and `ort-download-binaries` are mutually exclusive;
//! selecting both is a compile error.
//!
//! # Example: capture and run VAD
//!
//! [`CaptureStream::next_chunk`](capture::CaptureStream::next_chunk) is the
//! recommended FFI-ready interface for reading audio chunks, returning a
//! three-state `Result`: `Ok(Some(chunk))` / `Ok(None)` (timeout, stream
//! still open) / `Err(DecibriError::CaptureStreamClosed)`.
//!
//! ```ignore
//! use std::time::Duration;
//! use decibri::capture::{AudioCapture, CaptureConfig};
//! use decibri::error::DecibriError;
//! use decibri::vad::{SileroVad, VadConfig};
//!
//! let capture = AudioCapture::new(CaptureConfig::default())?;
//! let stream = capture.start()?;
//! let mut vad = SileroVad::new(VadConfig::default())?;
//!
//! loop {
//! match stream.next_chunk(Some(Duration::from_millis(100))) {
//! Ok(Some(chunk)) => {
//! let result = vad.process(&chunk.data)?;
//! if result.is_speech {
//! println!("speech @ p={:.2}", result.probability);
//! }
//! }
//! Ok(None) => continue,
//! Err(DecibriError::CaptureStreamClosed) => break,
//! Err(e) => return Err(e.into()),
//! }
//! }
//! # Ok::<(), Box<dyn std::error::Error>>(())
//! ```
//!
//! # Process-global ORT initialization
//!
//! ONNX Runtime initializes exactly once per process. The first successful
//! [`SileroVad::new`](vad::SileroVad::new) call wins; later constructions
//! with a different [`vad::VadConfig::ort_library_path`] silently reuse the
//! first path. See [`vad::VadConfig`] for details.
//!
//! # Thread safety
//!
//! All public types are `Send` and suitable for cross-thread handoff.
//! [`capture::CaptureStream`] and [`output::OutputStream`] are `!Sync`
//! because they hold a `cpal::Stream` internally; wrap them in a mutex or
//! move them into a dedicated thread for shared access.
//!
//! # Stability
//!
//! Within 3.x, the following FFI-consumer surface is declared stable and
//! will not change signature without a breaking version bump:
//!
//! - [`capture::CaptureStream`][] —
//! [`try_next_chunk`](capture::CaptureStream::try_next_chunk),
//! [`next_chunk`](capture::CaptureStream::next_chunk),
//! [`is_open`](capture::CaptureStream::is_open),
//! [`stop`](capture::CaptureStream::stop).
//! - [`output::OutputStream`][] —
//! [`send`](output::OutputStream::send),
//! [`drain`](output::OutputStream::drain),
//! [`is_playing`](output::OutputStream::is_playing),
//! [`stop`](output::OutputStream::stop).
compile_error!;