aic_sdk/lib.rs
1#![doc = include_str!("../README.md")]
2
3use aic_sdk_sys::{AicErrorCode::*, AicModelType::*, AicParameter::*, *};
4use std::{
5 ffi::{CStr, CString},
6 ptr,
7 sync::Once,
8};
9use thiserror::Error;
10
11static SET_WRAPPER_ID: Once = Once::new();
12
13/// Rust-friendly error type for AIC SDK operations.
14#[derive(Debug, Clone, PartialEq, Eq, Error)]
15pub enum AicError {
16 #[error("Parameter is out of range")]
17 ParameterOutOfRange,
18 #[error("Processor was not initialized")]
19 ModelNotInitialized,
20 #[error("Audio config is not supported")]
21 AudioConfigUnsupported,
22 #[error("Audio config does not match the initialized config")]
23 AudioConfigMismatch,
24 #[error("Audio enhancement was disallowed")]
25 EnhancementNotAllowed,
26 #[error("Internal error")]
27 Internal,
28 #[error("License key is invalid")]
29 LicenseFormatInvalid,
30 #[error("License version unsupported")]
31 LicenseVersionUnsupported,
32 #[error("License key expired")]
33 LicenseExpired,
34 #[error("Unknown error code: {0}")]
35 Unknown(AicErrorCode::Type),
36}
37
38impl From<AicErrorCode::Type> for AicError {
39 fn from(error_code: AicErrorCode::Type) -> Self {
40 match error_code {
41 AIC_ERROR_CODE_NULL_POINTER => {
42 // This should never happen in our Rust wrapper, but if it does,
43 // it indicates a serious bug in our wrapper logic
44 panic!(
45 "Unexpected null pointer error from C library - this is a bug in the Rust wrapper"
46 );
47 }
48 AIC_ERROR_CODE_PARAMETER_OUT_OF_RANGE => AicError::ParameterOutOfRange,
49 AIC_ERROR_CODE_MODEL_NOT_INITIALIZED => AicError::ModelNotInitialized,
50 AIC_ERROR_CODE_AUDIO_CONFIG_UNSUPPORTED => AicError::AudioConfigUnsupported,
51 AIC_ERROR_CODE_AUDIO_CONFIG_MISMATCH => AicError::AudioConfigMismatch,
52 AIC_ERROR_CODE_ENHANCEMENT_NOT_ALLOWED => AicError::EnhancementNotAllowed,
53 AIC_ERROR_CODE_INTERNAL_ERROR => AicError::Internal,
54 AIC_ERROR_CODE_LICENSE_FORMAT_INVALID => AicError::LicenseFormatInvalid,
55 AIC_ERROR_CODE_LICENSE_VERSION_UNSUPPORTED => AicError::LicenseVersionUnsupported,
56 AIC_ERROR_CODE_LICENSE_EXPIRED => AicError::LicenseExpired,
57 code => AicError::Unknown(code),
58 }
59 }
60}
61
62/// Helper function to convert C error codes to Rust Results.
63pub fn handle_error(error_code: AicErrorCode::Type) -> Result<(), AicError> {
64 match error_code {
65 AIC_ERROR_CODE_SUCCESS => Ok(()),
66 code => Err(AicError::from(code)),
67 }
68}
69
70/// Available model types for audio enhancement.
71#[derive(Debug, Clone, Copy, PartialEq, Eq)]
72pub enum ModelType {
73 /// **Specifications:**
74 /// - Native sample rate: 48 kHz
75 /// - Native num frames: 480
76 /// - Processing latency: 30ms
77 QuailL48,
78 /// **Specifications:**
79 /// - Native sample rate: 16 kHz
80 /// - Native num frames: 160
81 /// - Processing latency: 30ms
82 QuailL16,
83 /// **Specifications:**
84 /// - Native sample rate: 8 kHz
85 /// - Native num frames: 80
86 /// - Processing latency: 30ms
87 QuailL8,
88 /// **Specifications:**
89 /// - Native sample rate: 48 kHz
90 /// - Native num frames: 480
91 /// - Processing latency: 30ms
92 QuailS48,
93 /// **Specifications:**
94 /// - Native sample rate: 16 kHz
95 /// - Native num frames: 160
96 /// - Processing latency: 30ms
97 QuailS16,
98 /// **Specifications:**
99 /// - Native sample rate: 8 kHz
100 /// - Native num frames: 80
101 /// - Processing latency: 30ms
102 QuailS8,
103 /// **Specifications:**
104 /// - Native sample rate: 48 kHz
105 /// - Native num frames: 480
106 /// - Processing latency: 10ms
107 QuailXS,
108 /// **Specifications:**
109 /// - Native sample rate: 48 kHz
110 /// - Native num frames: 480
111 /// - Processing latency: 10ms
112 QuailXXS,
113}
114
115impl From<ModelType> for AicModelType::Type {
116 fn from(model_type: ModelType) -> Self {
117 match model_type {
118 ModelType::QuailL48 => AIC_MODEL_TYPE_QUAIL_L48,
119 ModelType::QuailL16 => AIC_MODEL_TYPE_QUAIL_L16,
120 ModelType::QuailL8 => AIC_MODEL_TYPE_QUAIL_L8,
121 ModelType::QuailS48 => AIC_MODEL_TYPE_QUAIL_S48,
122 ModelType::QuailS16 => AIC_MODEL_TYPE_QUAIL_S16,
123 ModelType::QuailS8 => AIC_MODEL_TYPE_QUAIL_S8,
124 ModelType::QuailXS => AIC_MODEL_TYPE_QUAIL_XS,
125 ModelType::QuailXXS => AIC_MODEL_TYPE_QUAIL_XXS,
126 }
127 }
128}
129
130/// Configurable parameters for audio enhancement
131#[derive(Debug, Clone, Copy, PartialEq, Eq)]
132pub enum Parameter {
133 /// Controls whether audio processing is bypassed while preserving algorithmic delay.
134 ///
135 /// When enabled, the input audio passes through unmodified, but the output is still
136 /// delayed by the same amount as during normal processing. This ensures seamless
137 /// transitions when toggling enhancement on/off without audible clicks or timing shifts.
138 ///
139 /// **Range:** 0.0 to 1.0
140 /// - **0.0:** Enhancement active (normal processing)
141 /// - **1.0:** Bypass enabled (latency-compensated passthrough)
142 ///
143 /// **Default:** 0.0
144 Bypass,
145 /// Controls the intensity of speech enhancement processing.
146 ///
147 /// **Range:** 0.0 to 1.0
148 /// - **0.0:** Bypass mode - original signal passes through unchanged
149 /// - **1.0:** Full enhancement - maximum noise reduction but also more audible artifacts
150 ///
151 /// **Default:** 1.0
152 EnhancementLevel,
153 /// Compensates for perceived volume reduction after noise removal.
154 ///
155 /// **Range:** 0.1 to 4.0 (linear amplitude multiplier)
156 /// - **0.1:** Significant volume reduction (-20 dB)
157 /// - **1.0:** No gain change (0 dB, default)
158 /// - **2.0:** Double amplitude (+6 dB)
159 /// - **4.0:** Maximum boost (+12 dB)
160 ///
161 /// **Formula:** Gain (dB) = 20 × log₁₀(value)
162 /// **Default:** 1.0
163 VoiceGain,
164 /// Enables/disables a noise gate as a post-processing step,
165 /// before passing the audio buffer to the model.
166 ///
167 /// **Valid values:** 0.0 or 1.0
168 /// - **0.0:** Noise gate disabled
169 /// - **1.0:** Noise gate enabled
170 ///
171 /// **Default:** 0.0
172 NoiseGateEnable,
173}
174
175impl From<Parameter> for AicParameter::Type {
176 fn from(parameter: Parameter) -> Self {
177 match parameter {
178 Parameter::Bypass => AIC_PARAMETER_BYPASS,
179 Parameter::EnhancementLevel => AIC_PARAMETER_ENHANCEMENT_LEVEL,
180 Parameter::VoiceGain => AIC_PARAMETER_VOICE_GAIN,
181 Parameter::NoiseGateEnable => AIC_PARAMETER_NOISE_GATE_ENABLE,
182 }
183 }
184}
185
186/// High-level wrapper for the AIC audio enhancement model.
187///
188/// This struct provides a safe, Rust-friendly interface to the underlying C library.
189/// It handles memory management automatically and converts C-style error codes
190/// to Rust `Result` types.
191///
192/// # Example
193///
194/// ```rust
195/// use aic_sdk::{Model, ModelType};
196///
197/// let license_key = std::env::var("AIC_SDK_LICENSE").unwrap();
198/// let mut model = Model::new(ModelType::QuailS48, &license_key).unwrap();
199///
200/// model.initialize(48000, 1, 1024, false).unwrap();
201///
202/// // Process audio data
203/// let mut audio_buffer = vec![0.0f32; 1024];
204/// model.process_interleaved(&mut audio_buffer, 1, 1024).unwrap();
205/// ```
206pub struct Model {
207 /// Raw pointer to the C model structure
208 inner: *mut AicModel,
209}
210
211impl Model {
212 /// Creates a new audio enhancement model instance.
213 ///
214 /// Multiple models can be created to process different audio streams simultaneously
215 /// or to switch between different enhancement algorithms during runtime.
216 ///
217 /// # Arguments
218 ///
219 /// * `model_type` - Selects the enhancement algorithm variant
220 /// * `license_key` - Valid license key for the AIC SDK
221 ///
222 /// # Returns
223 ///
224 /// Returns a `Result` containing the new `Model` instance or an `AicError` if creation fails.
225 ///
226 /// # Example
227 ///
228 /// ```rust
229 /// # use aic_sdk::{Model, ModelType};
230 /// let license_key = std::env::var("AIC_SDK_LICENSE").unwrap();
231 /// let model = Model::new(ModelType::QuailS48, &license_key).unwrap();
232 /// ```
233 pub fn new(model_type: ModelType, license_key: &str) -> Result<Self, AicError> {
234 SET_WRAPPER_ID.call_once(|| unsafe {
235 aic_set_sdk_wrapper_id(2);
236 });
237
238 let mut model_ptr: *mut AicModel = ptr::null_mut();
239 let c_license_key =
240 CString::new(license_key).map_err(|_| AicError::LicenseFormatInvalid)?;
241
242 let error_code =
243 unsafe { aic_model_create(&mut model_ptr, model_type.into(), c_license_key.as_ptr()) };
244
245 handle_error(error_code)?;
246
247 // This should never happen if the C library is well-behaved, but let's be defensive
248 assert!(
249 !model_ptr.is_null(),
250 "C library returned success but null pointer"
251 );
252
253 Ok(Self { inner: model_ptr })
254 }
255
256 /// Configures the model for a specific audio format.
257 ///
258 /// This function must be called before processing any audio.
259 /// For the lowest delay use the sample rate and frame size returned by
260 /// `optimal_sample_rate` and `optimal_num_frames`.
261 ///
262 /// # Arguments
263 ///
264 /// * `sample_rate` - Audio sample rate in Hz (8000 - 192000)
265 /// * `num_channels` - Number of audio channels (1 for mono, 2 for stereo, etc.)
266 /// * `num_frames` - Number of samples per channel in each process call
267 /// * `allow_variable_frames` - Allows varying frame counts per process call (up to `num_frames`), but increases delay.
268 ///
269 /// # Returns
270 ///
271 /// Returns `Ok(())` on success or an `AicError` if initialization fails.
272 ///
273 /// # Warning
274 /// Do not call from audio processing threads as this allocates memory.
275 ///
276 /// # Note
277 /// All channels are mixed to mono for processing. To process channels
278 /// independently, create separate model instances.
279 ///
280 /// # Example
281 ///
282 /// ```rust
283 /// # use aic_sdk::{Model, ModelType};
284 /// # let license_key = std::env::var("AIC_SDK_LICENSE").unwrap();
285 /// # let mut model = Model::new(ModelType::QuailS48, &license_key).unwrap();
286 /// model.initialize(48000, 1, 1024, true).unwrap();
287 /// ```
288 pub fn initialize(
289 &mut self,
290 sample_rate: u32,
291 num_channels: u16,
292 num_frames: usize,
293 allow_variable_frames: bool,
294 ) -> Result<(), AicError> {
295 let error_code = unsafe {
296 aic_model_initialize(
297 self.inner,
298 sample_rate,
299 num_channels,
300 num_frames,
301 allow_variable_frames,
302 )
303 };
304
305 handle_error(error_code)?;
306 Ok(())
307 }
308
309 /// Clears all internal state and buffers.
310 ///
311 /// Call this when the audio stream is interrupted or when seeking
312 /// to prevent artifacts from previous audio content.
313 ///
314 /// The model stays initialized to the configured settings.
315 ///
316 /// # Returns
317 ///
318 /// Returns `Ok(())` on success or an `AicError` if the reset fails.
319 ///
320 /// # Thread Safety
321 /// Real-time safe. Can be called from audio processing threads.
322 ///
323 /// # Example
324 ///
325 /// ```rust
326 /// # use aic_sdk::{Model, ModelType};
327 /// # let license_key = std::env::var("AIC_SDK_LICENSE").unwrap();
328 /// # let mut model = Model::new(ModelType::QuailS48, &license_key).unwrap();
329 /// model.reset().unwrap();
330 /// ```
331 pub fn reset(&mut self) -> Result<(), AicError> {
332 let error_code = unsafe { aic_model_reset(self.inner) };
333 handle_error(error_code)
334 }
335
336 /// Processes audio with separate buffers for each channel (planar layout).
337 ///
338 /// Enhances speech in the provided audio buffers in-place.
339 ///
340 /// The planar function allows a maximum of 16 channels.
341 ///
342 /// # Arguments
343 ///
344 /// * `audio` - Array of channel buffer pointers to be enhanced in-place
345 ///
346 /// # Returns
347 ///
348 /// Returns `Ok(())` on success or an `AicError` if processing fails.
349 ///
350 /// # Example
351 ///
352 /// ```rust
353 /// # use aic_sdk::{Model, ModelType};
354 /// # let license_key = std::env::var("AIC_SDK_LICENSE").unwrap();
355 /// # let mut model = Model::new(ModelType::QuailS48, &license_key).unwrap();
356 /// let mut audio = vec![vec![0.0f32; 480]; 2]; // 2 channels, 480 frames each
357 /// let mut audio_refs: Vec<&mut [f32]> = audio.iter_mut().map(|ch| ch.as_mut_slice()).collect();
358 /// model.initialize(48000, 2, 480, false).unwrap();
359 /// model.process_planar(&mut audio_refs).unwrap();
360 /// ```
361 pub fn process_planar(&mut self, audio: &mut [&mut [f32]]) -> Result<(), AicError> {
362 const MAX_CHANNELS: usize = 16;
363
364 let num_channels = audio.len() as u16;
365 let num_frames = if audio.is_empty() { 0 } else { audio[0].len() };
366
367 let mut audio_ptrs = [std::ptr::null_mut::<f32>(); MAX_CHANNELS];
368 for (i, channel) in audio.iter_mut().enumerate().take(MAX_CHANNELS) {
369 audio_ptrs[i] = channel.as_mut_ptr();
370 }
371
372 let error_code = unsafe {
373 aic_model_process_planar(self.inner, audio_ptrs.as_ptr(), num_channels, num_frames)
374 };
375
376 handle_error(error_code)
377 }
378
379 /// Processes audio with interleaved channel data.
380 ///
381 /// Enhances speech in the provided audio buffer in-place.
382 ///
383 /// # Arguments
384 ///
385 /// * `audio` - Interleaved audio buffer to be enhanced in-place. Must be exactly of size `num_channels` * `num_frames`
386 /// * `num_channels` - Number of channels (must match initialization)
387 /// * `num_frames` - Number of frames (must match initialization)
388 ///
389 /// # Returns
390 ///
391 /// Returns `Ok(())` on success or an `AicError` if processing fails.
392 ///
393 /// # Example
394 ///
395 /// ```rust
396 /// # use aic_sdk::{Model, ModelType};
397 /// # let license_key = std::env::var("AIC_SDK_LICENSE").unwrap();
398 /// # let mut model = Model::new(ModelType::QuailS48, &license_key).unwrap();
399 /// let mut audio = vec![0.0f32; 2 * 480]; // 2 channels, 480 frames
400 /// model.initialize(48000, 2, 480, false).unwrap();
401 /// model.process_interleaved(&mut audio, 2, 480).unwrap();
402 /// ```
403 pub fn process_interleaved(
404 &mut self,
405 audio: &mut [f32],
406 num_channels: u16,
407 num_frames: usize,
408 ) -> Result<(), AicError> {
409 let error_code = unsafe {
410 aic_model_process_interleaved(self.inner, audio.as_mut_ptr(), num_channels, num_frames)
411 };
412
413 handle_error(error_code)
414 }
415
416 /// Modifies a model parameter.
417 ///
418 /// All parameters can be changed during audio processing.
419 /// This function can be called from any thread.
420 ///
421 /// # Arguments
422 ///
423 /// * `parameter` - Parameter to modify
424 /// * `value` - New parameter value. See parameter documentation for ranges
425 ///
426 /// # Returns
427 ///
428 /// Returns `Ok(())` on success or an `AicError` if the parameter cannot be set.
429 ///
430 /// # Example
431 ///
432 /// ```rust
433 /// # use aic_sdk::{Model, ModelType, Parameter};
434 /// # let license_key = std::env::var("AIC_SDK_LICENSE").unwrap();
435 /// # let mut model = Model::new(ModelType::QuailS48, &license_key).unwrap();
436 /// model.set_parameter(Parameter::EnhancementLevel, 0.8).unwrap();
437 /// model.set_parameter(Parameter::NoiseGateEnable, 1.0).unwrap(); // 1.0 = enabled
438 /// ```
439 pub fn set_parameter(&mut self, parameter: Parameter, value: f32) -> Result<(), AicError> {
440 let error_code = unsafe { aic_model_set_parameter(self.inner, parameter.into(), value) };
441 handle_error(error_code)
442 }
443
444 /// Retrieves the current value of a parameter.
445 ///
446 /// This function can be called from any thread.
447 ///
448 /// # Arguments
449 ///
450 /// * `parameter` - Parameter to query
451 ///
452 /// # Returns
453 ///
454 /// Returns `Ok(value)` containing the current parameter value, or an `AicError` if the query fails.
455 ///
456 /// # Example
457 ///
458 /// ```rust
459 /// # use aic_sdk::{Model, ModelType, Parameter};
460 /// # let license_key = std::env::var("AIC_SDK_LICENSE").unwrap();
461 /// # let mut model = Model::new(ModelType::QuailS48, &license_key).unwrap();
462 /// let enhancement_level = model.get_parameter(Parameter::EnhancementLevel).unwrap();
463 /// println!("Current enhancement level: {}", enhancement_level);
464 /// ```
465 pub fn get_parameter(&self, parameter: Parameter) -> Result<f32, AicError> {
466 let mut value: f32 = 0.0;
467 let error_code =
468 unsafe { aic_model_get_parameter(self.inner, parameter.into(), &mut value) };
469 handle_error(error_code)?;
470 Ok(value)
471 }
472
473 /// Returns the total output delay in samples for the current audio configuration.
474 ///
475 /// This function provides the complete end-to-end latency introduced by the model,
476 /// which includes both algorithmic processing delay and any buffering overhead.
477 /// Use this value to synchronize enhanced audio with other streams or to implement
478 /// delay compensation in your application.
479 ///
480 /// **Delay behavior:**
481 /// - **Before initialization:** Returns the base processing delay using the model's
482 /// optimal frame size at its native sample rate
483 /// - **After initialization:** Returns the actual delay for your specific configuration,
484 /// including any additional buffering introduced by non-optimal frame sizes
485 ///
486 /// **Important:** The delay value is always expressed in samples at the sample rate
487 /// you configured during `initialize`. To convert to time units:
488 /// `delay_ms = (delay_samples * 1000) / sample_rate`
489 ///
490 /// **Note:** Using frame sizes different from the optimal value returned by
491 /// `optimal_num_frames` will increase the delay beyond the model's base latency.
492 ///
493 /// # Returns
494 ///
495 /// Returns `Ok(delay_samples)` or an `AicError` if the query fails.
496 ///
497 /// # Example
498 ///
499 /// ```rust
500 /// # use aic_sdk::{Model, ModelType};
501 /// # let license_key = std::env::var("AIC_SDK_LICENSE").unwrap();
502 /// # let mut model = Model::new(ModelType::QuailS48, &license_key).unwrap();
503 /// let delay = model.output_delay().unwrap();
504 /// println!("Output delay: {} samples", delay);
505 /// ```
506 pub fn output_delay(&self) -> Result<usize, AicError> {
507 let mut delay: usize = 0;
508 let error_code = unsafe { aic_get_output_delay(self.inner, &mut delay) };
509 handle_error(error_code)?;
510 Ok(delay)
511 }
512
513 /// Retrieves the native sample rate of the selected model.
514 ///
515 /// Each model is optimized for a specific sample rate, which determines the frequency
516 /// range of the enhanced audio output. While you can process audio at any sample rate,
517 /// understanding the model's native rate helps predict the enhancement quality.
518 ///
519 /// **How sample rate affects enhancement:**
520 /// - Models trained at lower sample rates (e.g., 8 kHz) can only enhance frequencies
521 /// up to their Nyquist limit (4 kHz for 8 kHz models)
522 /// - When processing higher sample rate input (e.g., 48 kHz) with a lower-rate model,
523 /// only the lower frequency components will be enhanced
524 ///
525 /// **Enhancement blending:**
526 /// When enhancement strength is set below 1.0, the enhanced signal is blended with
527 /// the original, maintaining the full frequency spectrum of your input while adding
528 /// the model's noise reduction capabilities to the lower frequencies.
529 ///
530 /// **Sample rate and optimal frames relationship:**
531 /// When using different sample rates than the model's native rate, the optimal number
532 /// of frames (returned by `optimal_num_frames`) will change. The model's output
533 /// delay remains constant regardless of sample rate as long as you use the optimal frame
534 /// count for that rate.
535 ///
536 /// **Recommendation:**
537 /// For maximum enhancement quality across the full frequency spectrum, match your
538 /// input sample rate to the model's native rate when possible.
539 ///
540 /// # Returns
541 ///
542 /// Returns `Ok(sample_rate)` or an `AicError` if the query fails.
543 ///
544 /// # Example
545 ///
546 /// ```rust
547 /// # use aic_sdk::{Model, ModelType};
548 /// # let license_key = std::env::var("AIC_SDK_LICENSE").unwrap();
549 /// # let mut model = Model::new(ModelType::QuailS48, &license_key).unwrap();
550 /// let optimal_rate = model.optimal_sample_rate().unwrap();
551 /// println!("Optimal sample rate: {} Hz", optimal_rate);
552 /// ```
553 pub fn optimal_sample_rate(&self) -> Result<u32, AicError> {
554 let mut sample_rate: u32 = 0;
555 let error_code = unsafe { aic_get_optimal_sample_rate(self.inner, &mut sample_rate) };
556 handle_error(error_code)?;
557 Ok(sample_rate)
558 }
559
560 /// Retrieves the optimal number of frames for the selected model at a given sample rate.
561 ///
562 ///
563 /// Using the optimal number of frames minimizes latency by avoiding internal buffering.
564 ///
565 /// **When you use a different frame count than the optimal value, the model will
566 /// introduce additional buffering latency on top of its base processing delay.**
567 ///
568 /// The optimal frame count varies based on the sample rate. Each model operates on a
569 /// fixed time window duration, so the required number of frames changes with sample rate.
570 /// For example, a model designed for 10 ms processing windows requires 480 frames at
571 /// 48 kHz, but only 160 frames at 16 kHz to capture the same duration of audio.
572 ///
573 /// Call this function with your intended sample rate before calling `aic_model_initialize`
574 /// to determine the best frame count for minimal latency.
575 ///
576 /// # Arguments
577 ///
578 /// * `sample_rate` - The sample rate in Hz for which to calculate the optimal frame count.
579 ///
580 /// # Returns
581 ///
582 /// Returns `Ok(num_frames)` or an `AicError` if the query fails.
583 ///
584 /// # Example
585 ///
586 /// ```rust
587 /// # use aic_sdk::{Model, ModelType};
588 /// # let license_key = std::env::var("AIC_SDK_LICENSE").unwrap();
589 /// # let mut model = Model::new(ModelType::QuailS48, &license_key).unwrap();
590 /// # let sample_rate = model.optimal_sample_rate().unwrap();
591 /// let optimal_frames = model.optimal_num_frames(sample_rate).unwrap();
592 /// println!("Optimal frame count: {}", optimal_frames);
593 /// ```
594 pub fn optimal_num_frames(&self, sample_rate: u32) -> Result<usize, AicError> {
595 let mut num_frames: usize = 0;
596 let error_code =
597 unsafe { aic_get_optimal_num_frames(self.inner, sample_rate, &mut num_frames) };
598 handle_error(error_code)?;
599 Ok(num_frames)
600 }
601}
602
603impl Drop for Model {
604 /// Releases all resources associated with a model instance.
605 ///
606 /// After calling this function, the model handle becomes invalid.
607 /// This function is safe to call with NULL.
608 fn drop(&mut self) {
609 if !self.inner.is_null() {
610 unsafe {
611 aic_model_destroy(self.inner);
612 }
613 }
614 }
615}
616
617// Safety: The underlying C library should be thread-safe for individual model instances
618unsafe impl Send for Model {}
619unsafe impl Sync for Model {}
620
621/// Returns the version of the SDK.
622///
623/// # Safety
624/// The returned pointer points to a static string and remains valid
625/// for the lifetime of the program. The caller should NOT free this pointer.
626///
627/// # Returns
628///
629/// Returns the library version as a string, or `None` if the version cannot be retrieved.
630///
631/// # Example
632///
633/// ```rust
634/// let version = aic_sdk::get_version();
635/// println!("ai-coustics SDK version: {}", version);
636/// ```
637pub fn get_version() -> &'static str {
638 let version_ptr = unsafe { aic_get_sdk_version() };
639 if version_ptr.is_null() {
640 return "unknown";
641 }
642
643 unsafe { CStr::from_ptr(version_ptr).to_str().unwrap_or("unknown") }
644}
645
646#[cfg(test)]
647mod tests {
648 use super::*;
649
650 #[test]
651 fn test_model_creation_and_basic_operations() -> Result<(), AicError> {
652 dbg!(aic_sdk_version());
653
654 // Read license key from environment variable
655 let license_key = std::env::var("AIC_SDK_LICENSE")
656 .expect("AIC_SDK_LICENSE environment variable must be set for tests");
657
658 // Test model creation with QuailL48 at optimal settings
659 let mut model = Model::new(ModelType::QuailL48, &license_key)?;
660
661 // Test initialization with QuailL48 optimal settings (48000 Hz, 480 frames)
662 model.initialize(48000, 2, 480, false)?;
663
664 let mut audio = vec![vec![0.0f32; 480]; 2]; // 2 channels, 480 frames each
665 let mut audio_refs: Vec<&mut [f32]> =
666 audio.iter_mut().map(|ch| ch.as_mut_slice()).collect();
667
668 model.process_planar(&mut audio_refs).unwrap();
669
670 Ok(())
671 }
672
673 #[test]
674 fn processing() {
675 let license_key = std::env::var("AIC_SDK_LICENSE").unwrap();
676 let mut model = Model::new(ModelType::QuailS48, &license_key).unwrap();
677 let mut audio = vec![0.0f32; 2 * 480]; // 2 channels, 480 frames
678 model.initialize(48000, 2, 480, false).unwrap();
679 model.process_interleaved(&mut audio, 2, 480).unwrap();
680 }
681}