# SDK information
```
use aic_sdk;
// Get SDK version
println!("SDK version: {}", aic_sdk::get_sdk_version());
// Get compatible model version
println!("Compatible model version: {}", aic_sdk::get_compatible_model_version());
```
# Loading models
Download models and find available IDs at <https://artifacts.ai-coustics.io/>.
## Load from file
```rust,no_run
use aic_sdk::Model;
let model = Model::from_file("path/to/model.aicmodel")?;
# Ok::<(), aic_sdk::AicError>(())
```
## Embed at compile time
```rust,no_run
use aic_sdk::{Model, include_model};
static MODEL: &[u8] = include_model!("path/to/model.aicmodel");
let model = Model::from_buffer(MODEL)?;
# Ok::<(), aic_sdk::AicError>(())
```
## Download from the CDN
Enable the `download-model` feature:
```bash
cargo add aic-sdk --features download-lib,download-model
```
```rust,no_run
# #[cfg(feature = "download-model")]
# fn demo() -> Result<(), aic_sdk::AicError> {
use aic_sdk::Model;
let model_path = Model::download("quail-vf-2.2-s-16khz", "./models")?;
let model = Model::from_file(&model_path)?;
# Ok(())
# }
```
# Model information
```rust,no_run
# use aic_sdk::Model;
# let model = Model::from_file("path/to/model.aicmodel")?;
// Get model ID
let model_id = model.id();
// Get optimal sample rate for the model
let optimal_rate = model.optimal_sample_rate();
// Get optimal block size for a specific sample rate
let optimal_block_size = model.optimal_block_size(48000);
# Ok::<(), aic_sdk::AicError>(())
```
# Configuring the processor
```rust,no_run
# use aic_sdk::Model;
# let license_key = std::env::var("AIC_SDK_LICENSE").unwrap();
# let model = Model::from_file("path/to/model.aicmodel")?;
use aic_sdk::{Processor, ProcessorConfig};
// Get optimal configuration for the model
let config = ProcessorConfig::optimal(&model).with_variable_block_size(false);
println!("{:?}", config); // ProcessorConfig { sample_rate: 48000, block_size: 480, variable_block_size: false }
// Or create from scratch
let config = ProcessorConfig {
sample_rate: 48000,
block_size: 480,
variable_block_size: false,
};
// Processor needs to be initialized before processing
// Option 1: Create and initialize in one step
let processor = Processor::new(&model, &license_key)?.with_config(&config)?;
// Option 2: Create first, then initialize separately
let mut processor = Processor::new(&model, &license_key)?;
processor.initialize(&config)?;
# Ok::<(), aic_sdk::AicError>(())
```
# OpenTelemetry
By default, telemetry follows the SDK environment configuration, such as
`AIC_SDK_OTEL_ENABLE`. Use `OtelConfig` when a single processor or VAD needs an explicit
telemetry setting or session ID.
```rust,no_run
# use aic_sdk::{Model, ProcessorConfig};
# let license_key = std::env::var("AIC_SDK_LICENSE").unwrap();
# let model = Model::from_file("path/to/model.aicmodel")?;
# let config = ProcessorConfig::optimal(&model);
use aic_sdk::{OtelConfig, Processor};
let otel = OtelConfig::with_session_id("session-1");
let processor = Processor::with_otel_config(&model, &license_key, &otel)?
.with_config(&config)?;
# Ok::<(), aic_sdk::AicError>(())
```
# Processing audio
```rust,no_run
# use aic_sdk::{Model, Processor, ProcessorConfig};
# let license_key = std::env::var("AIC_SDK_LICENSE").unwrap();
# let model = Model::from_file("path/to/model.aicmodel")?;
# let config = ProcessorConfig::optimal(&model);
# let mut processor = Processor::new(&model, &license_key)?.with_config(&config)?;
let mut audio_block = vec![0.0f32; config.block_size];
processor.process(&mut audio_block)?;
# Ok::<(), aic_sdk::AicError>(())
```
# Ending a session
A telemetry session is closed automatically when the processor is dropped. Call
`terminate_session` when the session has to end at a specific point instead, for example in
a lifecycle event. The processor cannot process audio afterwards.
```rust,no_run
# use aic_sdk::{Model, Processor, ProcessorConfig};
# let license_key = std::env::var("AIC_SDK_LICENSE").unwrap();
# let model = Model::from_file("path/to/model.aicmodel")?;
# let config = ProcessorConfig::optimal(&model);
# let mut processor = Processor::new(&model, &license_key)?.with_config(&config)?;
processor.terminate_session();
# Ok::<(), aic_sdk::AicError>(())
```
The same applies to `Vad::terminate_session` and `Analyzer::terminate_session`.
# Processor context
The processor context provides thread-safe access to processor parameters and state. You can
create multiple contexts and move them to any thread for concurrent parameter updates.
```rust,no_run
# use aic_sdk::{Model, Processor, ProcessorConfig};
# let license_key = std::env::var("AIC_SDK_LICENSE").unwrap();
# let model = Model::from_file("path/to/model.aicmodel")?;
# let config = ProcessorConfig::optimal(&model);
# let processor = Processor::new(&model, &license_key)?.with_config(&config)?;
use aic_sdk::ProcessorParameter;
// Get processor context
let proc_ctx = processor.context();
// Get the delay applied to the audio in samples
let delay = proc_ctx.audio_delay();
// Reset processor state (clears internal buffers)
proc_ctx.reset();
// Set enhancement parameters
proc_ctx.set_parameter(ProcessorParameter::EnhancementLevel, 0.8)?;
proc_ctx.set_parameter(ProcessorParameter::Bypass, 0.0)?;
// Get parameter values
let level = proc_ctx.parameter(ProcessorParameter::EnhancementLevel);
println!("Enhancement level: {}", level);
# Ok::<(), aic_sdk::AicError>(())
```
# Voice activity detection
Voice activity detection runs on its own `Vad` instance, created from a dedicated VAD model
(e.g. `vad-2.1-xxs-16khz`). Enhancement models are rejected with
`AicError::ModelTypeUnsupported`.
```rust,no_run
# let license_key = std::env::var("AIC_SDK_LICENSE").unwrap();
use aic_sdk::{Model, ProcessorConfig, Vad};
let model = Model::from_file("path/to/vad_model.aicmodel")?;
let config = ProcessorConfig::optimal(&model);
let mut vad = Vad::new(&model, &license_key)?.with_config(&config)?;
// Feed mono audio to the detector. The audio block is not modified.
let audio_block = vec![0.0f32; config.block_size];
vad.process(&audio_block)?;
# Ok::<(), aic_sdk::AicError>(())
```
When enhancement and VAD run together, feed the VAD the original input audio, not the
processor's enhanced output. Run both on the same block instead of chaining them:
```rust,no_run
# use aic_sdk::{Model, Processor, ProcessorConfig, Vad};
# let license_key = std::env::var("AIC_SDK_LICENSE").unwrap();
# let model = Model::from_file("path/to/model.aicmodel")?;
# let config = ProcessorConfig::optimal(&model);
# let mut processor = Processor::new(&model, &license_key)?.with_config(&config)?;
# let vad_model = Model::from_file("path/to/vad_model.aicmodel")?;
# let mut vad = Vad::new(&vad_model, &license_key)?.with_config(&config)?;
let mut audio_block = vec![0.0f32; config.block_size];
vad.process(&audio_block)?; // reads the block, does not modify it
processor.process(&mut audio_block)?; // enhances the block in place
# Ok::<(), aic_sdk::AicError>(())
```
Enhancement is designed to change the signal, so running the VAD on its output means
detecting speech in audio that no longer matches what the VAD model expects, and it stacks
the processor's audio delay on top of the VAD's prediction delay.
The VAD context provides thread-safe access to the prediction, the VAD parameters and its
state. You can create multiple contexts and move them to any thread for concurrent parameter
updates.
```rust,no_run
# use aic_sdk::{Model, ProcessorConfig, Vad};
# let license_key = std::env::var("AIC_SDK_LICENSE").unwrap();
# let model = Model::from_file("path/to/vad_model.aicmodel")?;
# let config = ProcessorConfig::optimal(&model);
# let vad = Vad::new(&model, &license_key)?.with_config(&config)?;
use aic_sdk::VadParameter;
// Get VAD context from the VAD
let vad_ctx = vad.context();
// Configure VAD parameters. Sensitivity is the probability threshold of the model output.
vad_ctx.set_parameter(VadParameter::Sensitivity, 0.5)?;
vad_ctx.set_parameter(VadParameter::SpeechHoldDuration, 0.05)?;
vad_ctx.set_parameter(VadParameter::MinimumSpeechDuration, 0.0)?;
// Get parameter values
let sensitivity = vad_ctx.parameter(VadParameter::Sensitivity);
println!("VAD sensitivity: {}", sensitivity);
// How many samples the prediction lags behind the input. This delay is not applied to the
// audio, `Vad::process` leaves the buffer untouched.
let delay = vad_ctx.prediction_delay();
// Check for speech (after processing audio through the VAD)
if vad_ctx.is_speech_detected() {
println!("Speech detected!");
}
// Clear the prediction and all internal state, e.g. when the stream is interrupted
vad_ctx.reset();
# Ok::<(), aic_sdk::AicError>(())
```
With the `async` feature, `VadAsync` mirrors `ProcessorAsync` for use in async contexts.
# Energy-based voice activity detection
An enhancement processor can report speech activity without running a separate VAD model.
Create a context before processing and read its prediction after each audio block:
```rust,no_run
# use aic_sdk::{Model, Processor, ProcessorConfig};
# let license_key = std::env::var("AIC_SDK_LICENSE").unwrap();
# let model = Model::from_file("path/to/model.aicmodel")?;
# let config = ProcessorConfig::optimal(&model);
# let mut processor = Processor::new(&model, &license_key)?.with_config(&config)?;
# let mut audio = vec![0.0f32; config.block_size];
use aic_sdk::VadParameter;
let vad = processor.energy_vad_context();
vad.set_parameter(VadParameter::Sensitivity, 6.0)?;
processor.process(&mut audio)?;
let speech_detected = vad.is_speech_detected();
# Ok::<(), aic_sdk::AicError>(())
```
`EnergyVadContext` also provides `parameter`, `prediction_delay`, and `reset`.
It can be shared across threads and outlive the processor. Resetting it retains its
settings without resetting enhancement; resetting the processor also resets its energy VAD.
Sensitivity ranges from 1 to 15, with higher values detecting quieter signals.
Creating a context keeps inference active even while bypassed or fully dry.
With the `async` feature, use `processor.energy_vad_context().await` on `ProcessorAsync`.
# Working with the analyzer
Instantiate an analyzer pair:
```rust,no_run
# use aic_sdk::Model;
# let license_key = std::env::var("AIC_SDK_LICENSE").unwrap();
# let model = Model::from_file("path/to/model.aicmodel")?;
let (mut collector, mut analyzer) = aic_sdk::analyzer_pair(&model, &license_key)?;
# Ok::<(), aic_sdk::AicError>(())
```
Initialize the collector, similar to the processor initialization:
```rust,no_run
# use aic_sdk::{Model, ProcessorConfig};
# let license_key = std::env::var("AIC_SDK_LICENSE").unwrap();
# let model = Model::from_file("path/to/model.aicmodel")?;
# let (mut collector, mut analyzer) = aic_sdk::analyzer_pair(&model, &license_key)?;
let config = ProcessorConfig::optimal(&model);
collector.initialize(&config)?;
# Ok::<(), aic_sdk::AicError>(())
```
Buffer the audio using `Collector::buffer`. It mirrors `Processor::process`; see
[Processing audio](#processing-audio).
Analyze the buffered audio in a separate thread:
```rust,no_run
# use aic_sdk::Model;
# let license_key = std::env::var("AIC_SDK_LICENSE").unwrap();
# let model = Model::from_file("path/to/model.aicmodel")?;
# let (mut collector, mut analyzer) = aic_sdk::analyzer_pair(&model, &license_key)?;
let result = analyzer.analyze_buffered()?;
println!("Risk score: {}", result.risk_score);
# Ok::<(), aic_sdk::AicError>(())
```
# Async processing
Enable the `async` feature to use `ProcessorAsync`, which offloads processing to a
background thread pool and returns a future. The implementation is runtime-agnostic and
works on any executor (tokio, smol, async-std, ...). The pool defaults to one thread per
logical CPU; override with the `AIC_NUM_THREADS` environment variable.
```bash
cargo add aic-sdk --features async
```
```rust,no_run
# #[cfg(feature = "async")]
# fn demo() {
use aic_sdk::{Model, ProcessorAsync, ProcessorConfig};
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let license_key = std::env::var("AIC_SDK_LICENSE")?;
let model = Model::from_file("path/to/model.aicmodel")?;
let config = ProcessorConfig::optimal(&model);
let processor = ProcessorAsync::new(&model, &license_key)?
.with_config(&config)
.await?;
// The async API takes ownership of the audio block and returns it back.
let audio = vec![0.0f32; config.block_size];
let audio = processor.process(audio).await?;
Ok(())
}
# }
```