1#![allow(clippy::cast_precision_loss)] #![allow(clippy::cast_possible_truncation)] #![allow(clippy::cast_sign_loss)] #![allow(clippy::missing_errors_doc)] #![allow(clippy::missing_panics_doc)] #![allow(clippy::unused_self)] #![allow(clippy::must_use_candidate)] #![allow(clippy::doc_markdown)] #![allow(clippy::unnecessary_wraps)] #![allow(clippy::float_cmp)] #![allow(clippy::match_same_arms)] #![allow(clippy::module_name_repetitions)] #![allow(clippy::struct_excessive_bools)] #![allow(clippy::too_many_lines)] #![allow(clippy::needless_pass_by_value)] #![allow(clippy::similar_names)] #![allow(clippy::unused_async)] #![allow(clippy::needless_range_loop)] #![allow(clippy::uninlined_format_args)] #![allow(clippy::manual_clamp)] #![allow(clippy::return_self_not_must_use)] #![allow(clippy::cast_possible_wrap)] #![allow(clippy::cast_lossless)] #![allow(clippy::wildcard_imports)] #![allow(clippy::format_push_string)] #![allow(clippy::redundant_closure_for_method_calls)] #![allow(clippy::too_many_arguments)] #![allow(clippy::field_reassign_with_default)] #![allow(clippy::trivially_copy_pass_by_ref)] #![allow(clippy::await_holding_lock)] use parking_lot::Mutex;
115use std::{
116 collections::HashMap,
117 ffi::{CStr, CString},
118 os::raw::{c_char, c_float, c_int, c_uint},
119 ptr,
120 sync::Arc,
121};
122use voirs_sdk::{
123 audio::AudioBuffer,
124 error::{Result, VoirsError},
125 types::{AudioFormat, LanguageCode, QualityLevel, SynthesisConfig},
126 VoirsPipeline as SdkPipeline,
127};
128
129pub mod c_api;
130pub mod config;
131pub mod error;
132pub mod memory;
133#[cfg(feature = "nodejs")]
134pub mod nodejs;
135pub mod performance;
136pub mod platform;
137#[cfg(feature = "python")]
138pub mod python;
139pub mod threading;
140pub mod types;
141pub mod utils;
142#[cfg(feature = "wasm")]
143pub mod wasm;
144
145pub use c_api::*;
150pub use error::*;
151pub use performance::*;
152pub use types::*;
153pub use utils::audio::VoirsAudioAnalysis;
154
155#[cfg(feature = "python")]
158pub use python::{PyAudioBuffer, PySynthesisConfig, PyVoiceInfo, VoirsPipeline as PyVoirsPipeline};
159
160#[cfg(feature = "nodejs")]
162pub use nodejs::napi_bindings::*;
163
164#[cfg(feature = "wasm")]
166pub use wasm::wasm_bindings::*;
167
168#[repr(C)]
170#[derive(Debug, Clone, Copy, PartialEq)]
171pub enum VoirsErrorCode {
172 Success = 0,
173 InvalidParameter = 1,
174 InitializationFailed = 2,
175 SynthesisFailed = 3,
176 VoiceNotFound = 4,
177 IoError = 5,
178 OutOfMemory = 6,
179 OperationCancelled = 7,
180 InternalError = 99,
181}
182
183#[repr(C)]
185#[derive(Debug, Clone, Copy, PartialEq)]
186pub enum VoirsAudioFormat {
187 Wav = 0,
188 Flac = 1,
189 Mp3 = 2,
190 Opus = 3,
191 Ogg = 4,
192}
193
194impl From<AudioFormat> for VoirsAudioFormat {
195 fn from(format: AudioFormat) -> Self {
196 match format {
197 AudioFormat::Wav => Self::Wav,
198 AudioFormat::Flac => Self::Flac,
199 AudioFormat::Mp3 => Self::Mp3,
200 AudioFormat::Opus => Self::Opus,
201 AudioFormat::Ogg => Self::Ogg,
202 }
203 }
204}
205
206impl From<VoirsAudioFormat> for AudioFormat {
207 fn from(format: VoirsAudioFormat) -> Self {
208 match format {
209 VoirsAudioFormat::Wav => Self::Wav,
210 VoirsAudioFormat::Flac => Self::Flac,
211 VoirsAudioFormat::Mp3 => Self::Mp3,
212 VoirsAudioFormat::Opus => Self::Opus,
213 VoirsAudioFormat::Ogg => Self::Ogg,
214 }
215 }
216}
217
218#[repr(C)]
220#[derive(Debug, Clone, Copy, PartialEq)]
221pub enum VoirsQualityLevel {
222 Low = 0,
223 Medium = 1,
224 High = 2,
225 Ultra = 3,
226}
227
228impl From<QualityLevel> for VoirsQualityLevel {
229 fn from(quality: QualityLevel) -> Self {
230 match quality {
231 QualityLevel::Low => Self::Low,
232 QualityLevel::Medium => Self::Medium,
233 QualityLevel::High => Self::High,
234 QualityLevel::Ultra => Self::Ultra,
235 }
236 }
237}
238
239impl From<VoirsQualityLevel> for QualityLevel {
240 fn from(quality: VoirsQualityLevel) -> Self {
241 match quality {
242 VoirsQualityLevel::Low => Self::Low,
243 VoirsQualityLevel::Medium => Self::Medium,
244 VoirsQualityLevel::High => Self::High,
245 VoirsQualityLevel::Ultra => Self::Ultra,
246 }
247 }
248}
249
250#[repr(C)]
252#[derive(Debug, Clone)]
253pub struct VoirsSynthesisConfig {
254 pub speaking_rate: c_float,
255 pub pitch_shift: c_float,
256 pub volume_gain: c_float,
257 pub enable_enhancement: c_int, pub output_format: VoirsAudioFormat,
259 pub sample_rate: c_uint,
260 pub quality: VoirsQualityLevel,
261}
262
263impl Default for VoirsSynthesisConfig {
264 fn default() -> Self {
265 let config = SynthesisConfig::default();
266 Self {
267 speaking_rate: config.speaking_rate,
268 pitch_shift: config.pitch_shift,
269 volume_gain: config.volume_gain,
270 enable_enhancement: if config.enable_enhancement { 1 } else { 0 },
271 output_format: config.output_format.into(),
272 sample_rate: config.sample_rate,
273 quality: config.quality.into(),
274 }
275 }
276}
277
278impl From<VoirsSynthesisConfig> for SynthesisConfig {
279 fn from(config: VoirsSynthesisConfig) -> Self {
280 Self {
281 speaking_rate: config.speaking_rate,
282 pitch_shift: config.pitch_shift,
283 volume_gain: config.volume_gain,
284 enable_enhancement: config.enable_enhancement != 0,
285 output_format: config.output_format.into(),
286 sample_rate: config.sample_rate,
287 quality: config.quality.into(),
288 language: LanguageCode::EnUs, effects: Vec::new(), streaming_chunk_size: None, seed: None, enable_emotion: false, emotion_type: None, emotion_intensity: 0.7, emotion_preset: None, auto_emotion_detection: false, enable_cloning: false,
298 cloning_method: None,
299 cloning_quality: 0.85,
300 enable_conversion: false,
301 conversion_target: None,
302 realtime_conversion: false,
303 enable_singing: false,
304 singing_voice_type: None,
305 singing_technique: None,
306 musical_key: None,
307 tempo: None,
308 enable_spatial: false,
309 listener_position: None,
310 hrtf_enabled: false,
311 room_size: None,
312 reverb_level: 0.3,
313 }
314 }
315}
316
317#[repr(C)]
319#[derive(Debug)]
320pub struct VoirsAudioBuffer {
321 pub samples: *mut c_float,
322 pub length: c_uint,
323 pub sample_rate: c_uint,
324 pub channels: c_uint,
325 pub duration: c_float,
326}
327
328impl VoirsAudioBuffer {
329 pub fn from_audio_buffer(audio: AudioBuffer) -> Self {
331 let samples = audio.samples().to_vec();
332 let length = samples.len() as c_uint;
333 let sample_rate = audio.sample_rate();
334 let channels = audio.channels();
335 let duration = audio.duration();
336
337 let mut c_samples = samples.into_boxed_slice();
339 let samples_ptr = c_samples.as_mut_ptr();
340 std::mem::forget(c_samples); Self {
343 samples: samples_ptr,
344 length,
345 sample_rate,
346 channels,
347 duration,
348 }
349 }
350
351 pub unsafe fn to_audio_buffer(&self) -> AudioBuffer {
360 let samples = std::slice::from_raw_parts(self.samples, self.length as usize).to_vec();
361 AudioBuffer::new(samples, self.sample_rate, self.channels)
362 }
363
364 pub unsafe fn free(&mut self) {
374 if !self.samples.is_null() {
375 let boxed_slice = Box::from_raw(std::ptr::slice_from_raw_parts_mut(
378 self.samples,
379 self.length as usize,
380 ));
381 drop(boxed_slice);
383 self.samples = ptr::null_mut();
384 }
385 }
386}
387
388use once_cell::sync::Lazy;
389
390struct PipelineManager {
392 pipelines: HashMap<u32, Arc<SdkPipeline>>,
393 placeholder_pipelines: std::collections::HashSet<u32>, next_id: u32,
395}
396
397impl PipelineManager {
398 fn new() -> Self {
399 Self {
400 pipelines: HashMap::new(),
401 placeholder_pipelines: std::collections::HashSet::new(),
402 next_id: 1,
403 }
404 }
405
406 fn add_pipeline(&mut self, pipeline: SdkPipeline) -> u32 {
407 let id = self.next_id;
408 self.pipelines.insert(id, Arc::new(pipeline));
409 self.next_id = self.next_id.wrapping_add(1);
410 if self.next_id == 0 {
411 self.next_id = 1; }
413 id
414 }
415
416 fn add_placeholder_pipeline(&mut self) -> u32 {
418 let id = self.next_id;
419 self.placeholder_pipelines.insert(id);
420 self.next_id = self.next_id.wrapping_add(1);
421 if self.next_id == 0 {
422 self.next_id = 1; }
424 id
425 }
426
427 fn get_pipeline(&self, id: u32) -> Option<Arc<SdkPipeline>> {
428 self.pipelines.get(&id).cloned()
429 }
430
431 fn remove_pipeline(&mut self, id: u32) -> bool {
432 let removed_real = self.pipelines.remove(&id).is_some();
434 let removed_placeholder = self.placeholder_pipelines.remove(&id);
435 removed_real || removed_placeholder
436 }
437
438 fn is_valid_pipeline(&self, id: u32) -> bool {
439 self.pipelines.contains_key(&id) || self.placeholder_pipelines.contains(&id)
440 }
441
442 fn count(&self) -> usize {
443 self.pipelines.len() + self.placeholder_pipelines.len()
444 }
445}
446
447static PIPELINE_MANAGER: Lazy<Mutex<PipelineManager>> =
449 Lazy::new(|| Mutex::new(PipelineManager::new()));
450
451thread_local! {
452 static LAST_ERROR: std::cell::RefCell<Option<String>> = const { std::cell::RefCell::new(None) };
453}
454
455pub fn set_last_error(error: String) {
457 LAST_ERROR.with(|e| {
458 *e.borrow_mut() = Some(error);
459 });
460}
461
462fn clear_last_error() {
464 LAST_ERROR.with(|e| {
465 *e.borrow_mut() = None;
466 });
467}
468
469fn get_last_error() -> Option<String> {
471 LAST_ERROR.with(|e| e.borrow().clone())
472}
473
474fn get_pipeline_manager() -> &'static Mutex<PipelineManager> {
476 &PIPELINE_MANAGER
477}
478
479static TOKIO_RUNTIME: Lazy<Mutex<Option<tokio::runtime::Runtime>>> = Lazy::new(|| Mutex::new(None));
481
482fn get_runtime() -> std::result::Result<tokio::runtime::Handle, VoirsErrorCode> {
484 if let Ok(handle) = tokio::runtime::Handle::try_current() {
486 return Ok(handle);
487 }
488
489 let mut runtime_guard = TOKIO_RUNTIME.lock();
491 if runtime_guard.is_none() {
492 let rt =
493 tokio::runtime::Runtime::new().map_err(|_| VoirsErrorCode::InitializationFailed)?;
494 *runtime_guard = Some(rt);
495 }
496
497 match runtime_guard.as_ref() {
498 Some(rt) => Ok(rt.handle().clone()),
499 None => Err(VoirsErrorCode::InternalError),
500 }
501}
502
503unsafe fn c_str_to_string(c_str: *const c_char) -> Result<String> {
505 if c_str.is_null() {
506 let err = VoirsError::config_error("Null string pointer");
507 set_last_error(format!("{err}"));
508 return Err(err);
509 }
510
511 CStr::from_ptr(c_str)
512 .to_str()
513 .map(|s| s.to_string())
514 .map_err(|e| {
515 let err = VoirsError::config_error(format!("Invalid UTF-8: {e}"));
516 set_last_error(format!("{err}"));
517 err
518 })
519}
520
521fn string_to_c_str(s: &str) -> *mut c_char {
523 match CString::new(s) {
524 Ok(c_string) => c_string.into_raw(),
525 Err(_) => ptr::null_mut(),
526 }
527}
528
529#[no_mangle]
539pub unsafe extern "C" fn voirs_free_string(s: *mut c_char) {
540 if !s.is_null() {
541 let _ = CString::from_raw(s);
542 }
543}
544
545#[no_mangle]
555pub unsafe extern "C" fn voirs_free_audio_buffer(buffer: *mut VoirsAudioBuffer) {
556 if !buffer.is_null() {
557 (*buffer).free();
558 let _ = Box::from_raw(buffer);
559 }
560}
561
562#[no_mangle]
564pub extern "C" fn voirs_error_message(code: VoirsErrorCode) -> *const c_char {
565 let message = match code {
566 VoirsErrorCode::Success => "Success",
567 VoirsErrorCode::InvalidParameter => "Invalid parameter",
568 VoirsErrorCode::InitializationFailed => "Initialization failed",
569 VoirsErrorCode::SynthesisFailed => "Synthesis failed",
570 VoirsErrorCode::VoiceNotFound => "Voice not found",
571 VoirsErrorCode::IoError => "I/O error",
572 VoirsErrorCode::OutOfMemory => "Out of memory",
573 VoirsErrorCode::OperationCancelled => "Operation cancelled",
574 VoirsErrorCode::InternalError => "Internal error",
575 };
576
577 message.as_ptr() as *const c_char
578}
579
580#[no_mangle]
582pub extern "C" fn voirs_get_last_error() -> *mut c_char {
583 match get_last_error() {
584 Some(error) => string_to_c_str(&error),
585 None => ptr::null_mut(),
586 }
587}
588
589#[no_mangle]
591pub extern "C" fn voirs_clear_error() {
592 clear_last_error();
593}
594
595#[no_mangle]
597pub extern "C" fn voirs_has_error() -> c_int {
598 if get_last_error().is_some() {
599 1
600 } else {
601 0
602 }
603}
604
605#[cfg(test)]
606mod tests {
607 use super::*;
608
609 #[test]
610 fn test_error_code_conversion() {
611 assert_eq!(VoirsErrorCode::Success as i32, 0);
612 assert_eq!(VoirsErrorCode::InvalidParameter as i32, 1);
613 assert_eq!(VoirsErrorCode::InitializationFailed as i32, 2);
614 assert_eq!(VoirsErrorCode::SynthesisFailed as i32, 3);
615 assert_eq!(VoirsErrorCode::VoiceNotFound as i32, 4);
616 assert_eq!(VoirsErrorCode::IoError as i32, 5);
617 assert_eq!(VoirsErrorCode::OutOfMemory as i32, 6);
618 assert_eq!(VoirsErrorCode::InternalError as i32, 99);
619 }
620
621 #[test]
622 fn test_audio_format_conversion() {
623 let format = AudioFormat::Wav;
624 let ffi_format: VoirsAudioFormat = format.into();
625 let back_format: AudioFormat = ffi_format.into();
626 assert_eq!(format, back_format);
627
628 let formats = [
630 (AudioFormat::Wav, VoirsAudioFormat::Wav),
631 (AudioFormat::Flac, VoirsAudioFormat::Flac),
632 (AudioFormat::Mp3, VoirsAudioFormat::Mp3),
633 (AudioFormat::Opus, VoirsAudioFormat::Opus),
634 (AudioFormat::Ogg, VoirsAudioFormat::Ogg),
635 ];
636
637 for (original, ffi) in formats {
638 let converted: VoirsAudioFormat = original.into();
639 assert_eq!(converted, ffi);
640 let back: AudioFormat = converted.into();
641 assert_eq!(back, original);
642 }
643 }
644
645 #[test]
646 fn test_quality_level_conversion() {
647 let qualities = [
648 (QualityLevel::Low, VoirsQualityLevel::Low),
649 (QualityLevel::Medium, VoirsQualityLevel::Medium),
650 (QualityLevel::High, VoirsQualityLevel::High),
651 (QualityLevel::Ultra, VoirsQualityLevel::Ultra),
652 ];
653
654 for (original, ffi) in qualities {
655 let converted: VoirsQualityLevel = original.into();
656 assert_eq!(converted, ffi);
657 let back: QualityLevel = converted.into();
658 assert_eq!(back, original);
659 }
660 }
661
662 #[test]
663 fn test_synthesis_config_conversion() {
664 let config = SynthesisConfig::default();
665 let ffi_config: VoirsSynthesisConfig = VoirsSynthesisConfig::default();
666 let back_config: SynthesisConfig = ffi_config.into();
667 assert_eq!(config.speaking_rate, back_config.speaking_rate);
668 assert_eq!(config.pitch_shift, back_config.pitch_shift);
669 assert_eq!(config.volume_gain, back_config.volume_gain);
670 assert_eq!(config.enable_enhancement, back_config.enable_enhancement);
671 assert_eq!(config.output_format, back_config.output_format);
672 assert_eq!(config.sample_rate, back_config.sample_rate);
673 assert_eq!(config.quality, back_config.quality);
674 }
675
676 #[test]
677 fn test_synthesis_config_default() {
678 let config = VoirsSynthesisConfig::default();
679 assert_eq!(config.speaking_rate, 1.0);
680 assert_eq!(config.pitch_shift, 0.0);
681 assert_eq!(config.volume_gain, 0.0);
682 assert_eq!(config.enable_enhancement, 1); assert_eq!(config.output_format, VoirsAudioFormat::Wav);
684 assert_eq!(config.sample_rate, 22050);
685 assert_eq!(config.quality, VoirsQualityLevel::High);
686 }
687
688 #[test]
689 fn test_error_message_function() {
690 let message = voirs_error_message(VoirsErrorCode::Success);
691 let c_str = unsafe { CStr::from_ptr(message) };
692 assert_eq!(c_str.to_str().unwrap(), "Success");
693
694 let message = voirs_error_message(VoirsErrorCode::InvalidParameter);
695 let c_str = unsafe { CStr::from_ptr(message) };
696 assert_eq!(c_str.to_str().unwrap(), "Invalid parameter");
697
698 let message = voirs_error_message(VoirsErrorCode::InternalError);
699 let c_str = unsafe { CStr::from_ptr(message) };
700 assert_eq!(c_str.to_str().unwrap(), "Internal error");
701 }
702
703 #[test]
704 fn test_audio_buffer_memory_safety() {
705 let samples = vec![1.0, 2.0, 3.0, 4.0];
707 let audio = AudioBuffer::new(samples.clone(), 44100, 1);
708 let mut ffi_buffer = VoirsAudioBuffer::from_audio_buffer(audio);
709
710 assert_eq!(ffi_buffer.length, 4);
711 assert_eq!(ffi_buffer.sample_rate, 44100);
712 assert_eq!(ffi_buffer.channels, 1);
713 assert!(!ffi_buffer.samples.is_null());
714
715 unsafe {
717 let samples_slice =
718 std::slice::from_raw_parts(ffi_buffer.samples, ffi_buffer.length as usize);
719 assert_eq!(samples_slice, &[1.0, 2.0, 3.0, 4.0]);
720 }
721
722 unsafe {
724 ffi_buffer.free();
725 }
726 assert!(ffi_buffer.samples.is_null());
727 }
728
729 #[test]
730 fn test_string_conversion_utilities() {
731 let test_string = "Hello, VoiRS!";
732 let c_string = string_to_c_str(test_string);
733
734 assert!(!c_string.is_null());
735
736 unsafe {
737 let converted_back = c_str_to_string(c_string);
738 assert!(converted_back.is_ok());
739 assert_eq!(converted_back.unwrap(), test_string);
740
741 voirs_free_string(c_string);
743 }
744 }
745
746 #[test]
747 fn test_null_string_handling() {
748 unsafe {
749 let result = c_str_to_string(std::ptr::null());
750 assert!(result.is_err());
751
752 voirs_free_string(std::ptr::null_mut());
754 }
755 }
756
757 #[test]
758 fn test_voice_info_default() {
759 let voice_info = types::VoirsVoiceInfo::default();
760 assert!(voice_info.id.is_null());
761 assert!(voice_info.name.is_null());
762 assert!(voice_info.language.is_null());
763 assert_eq!(voice_info.quality, VoirsQualityLevel::Medium);
764 assert_eq!(voice_info.is_available, 0);
765 }
766
767 #[test]
768 fn test_voice_list_default() {
769 let voice_list = types::VoirsVoiceList::default();
770 assert!(voice_list.voices.is_null());
771 assert_eq!(voice_list.count, 0);
772 }
773
774 #[test]
775 fn test_pipeline_config_default() {
776 let config = types::VoirsPipelineConfig::default();
777 assert_eq!(config.use_gpu, 0);
778 assert_eq!(config.num_threads, 0);
779 assert!(config.cache_dir.is_null());
780 assert!(config.device.is_null());
781 }
782
783 #[test]
784 fn test_ffi_struct_sizes() {
785 assert!(std::mem::size_of::<VoirsErrorCode>() <= 8);
787 assert!(std::mem::size_of::<VoirsAudioFormat>() <= 8);
788 assert!(std::mem::size_of::<VoirsQualityLevel>() <= 8);
789 assert!(std::mem::size_of::<VoirsSynthesisConfig>() <= 64);
790 assert!(std::mem::size_of::<VoirsAudioBuffer>() <= 64);
791 assert!(std::mem::size_of::<types::VoirsVoiceInfo>() <= 64);
792 assert!(std::mem::size_of::<types::VoirsVoiceList>() <= 16);
793 assert!(std::mem::size_of::<types::VoirsPipelineConfig>() <= 32);
794 }
795
796 #[test]
797 fn test_ffi_struct_alignment() {
798 assert_eq!(
800 std::mem::align_of::<VoirsErrorCode>(),
801 std::mem::align_of::<i32>()
802 );
803 assert_eq!(
804 std::mem::align_of::<VoirsAudioFormat>(),
805 std::mem::align_of::<i32>()
806 );
807 assert_eq!(
808 std::mem::align_of::<VoirsQualityLevel>(),
809 std::mem::align_of::<i32>()
810 );
811 }
812
813 #[test]
814 fn test_repr_c_layout() {
815 use std::mem::offset_of;
817
818 assert_eq!(offset_of!(VoirsSynthesisConfig, speaking_rate), 0);
820 assert_eq!(offset_of!(VoirsSynthesisConfig, pitch_shift), 4);
821 assert_eq!(offset_of!(VoirsSynthesisConfig, volume_gain), 8);
822 assert_eq!(offset_of!(VoirsSynthesisConfig, enable_enhancement), 12);
823
824 assert_eq!(offset_of!(VoirsAudioBuffer, samples), 0);
826 assert_eq!(offset_of!(VoirsAudioBuffer, length), 8);
827 assert_eq!(offset_of!(VoirsAudioBuffer, sample_rate), 12);
828 assert_eq!(offset_of!(VoirsAudioBuffer, channels), 16);
829 assert_eq!(offset_of!(VoirsAudioBuffer, duration), 20);
830 }
831
832 #[test]
833 fn test_enhanced_error_handling() {
834 clear_last_error();
836 assert!(!voirs_has_error() != 0);
837
838 set_last_error("Test error message".to_string());
839 assert!(voirs_has_error() != 0);
840
841 let error_msg = voirs_get_last_error();
842 assert!(!error_msg.is_null());
843
844 unsafe {
845 let c_str = std::ffi::CStr::from_ptr(error_msg);
846 assert_eq!(c_str.to_str().unwrap(), "Test error message");
847 voirs_free_string(error_msg);
848 }
849
850 voirs_clear_error();
851 assert!(voirs_has_error() == 0);
852 }
853
854 #[test]
855 fn test_memory_management_integration() {
856 use crate::memory::{check_memory_leaks, get_memory_stats, reset_memory_stats};
857
858 reset_memory_stats();
859 assert!(check_memory_leaks());
860
861 let initial_stats = get_memory_stats();
862 assert_eq!(initial_stats.current_allocations, 0);
863
864 let stats_json = memory::voirs_memory_get_stats();
866 assert!(!stats_json.is_null());
867
868 unsafe {
869 let stats_str = std::ffi::CStr::from_ptr(stats_json).to_str().unwrap();
870 assert!(stats_str.contains("total_allocations"));
871 voirs_free_string(stats_json);
872 }
873
874 let leak_check = memory::voirs_memory_check_leaks();
875 assert_eq!(leak_check, 1); }
877
878 #[test]
879 fn test_memory_pool_integration() {
880 use crate::memory::{pool_allocate, pool_deallocate};
881
882 let buffer1 = pool_allocate(100);
884 assert_eq!(buffer1.len(), 100);
885
886 let buffer2 = pool_allocate(100);
887 assert_eq!(buffer2.len(), 100);
888
889 pool_deallocate(buffer1);
891 pool_deallocate(buffer2);
892
893 let buffer3 = pool_allocate(100);
895 assert_eq!(buffer3.len(), 100);
896 }
897
898 #[test]
899 fn test_ref_counted_audio_buffer() {
900 use crate::memory::RefCountedBuffer;
901
902 let buffer = RefCountedBuffer::new(vec![1.0, 2.0, 3.0, 4.0], 44100, 2);
903 assert_eq!(buffer.data(), &[1.0, 2.0, 3.0, 4.0]);
904 assert_eq!(buffer.sample_rate(), 44100);
905 assert_eq!(buffer.channels(), 2);
906 assert_eq!(buffer.ref_count(), 1);
907
908 let buffer2 = buffer.clone();
909 assert_eq!(buffer.ref_count(), 2);
910 assert_eq!(buffer2.ref_count(), 2);
911
912 drop(buffer2);
913 assert_eq!(buffer.ref_count(), 1);
914 }
915}