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}