use std::sync::Arc; use crate::*; /// A chunk of audio data that can be linked to form longer recordings. /// Designed for lock-free sharing between real-time and I/O threads. #[derive(Debug)] pub struct AudioChunk { /// Fixed-size audio buffer pub samples: Box<[f32]>, /// Number of valid samples (≤ samples.len()) pub sample_count: usize, /// Next chunk in linked list pub next: Option>, } impl AudioChunk { /// Create a new empty chunk with specified capacity (for buffer pool) pub fn allocate(size: usize) -> Arc { Arc::new(Self { samples: vec![0.0; size].into_boxed_slice(), sample_count: 0, next: None, }) } /// Consolidate a linked list of chunks into a single optimized chunk pub fn consolidate(this: &Arc) -> Arc { // Calculate total sample count let mut total_samples = 0; let mut current = Some(this.clone()); while let Some(chunk) = current { total_samples += chunk.sample_count; current = chunk.next.clone(); } // Create consolidated buffer let mut consolidated_samples = Vec::with_capacity(total_samples); let mut current = Some(this.clone()); while let Some(chunk) = current { consolidated_samples.extend_from_slice(&chunk.samples[..chunk.sample_count]); current = chunk.next.clone(); } Arc::new(Self { samples: consolidated_samples.into_boxed_slice(), sample_count: total_samples, next: None, }) } /// Add samples to this chunk and allocate additional chunks as needed. /// Uses the factory to get pre-allocated chunks when current chunk fills up. pub fn append_samples( self: &mut Arc, samples: &[f32], chunk_factory: &mut F, ) -> Result<()> { let mut_self = Arc::get_mut(self) .ok_or(LooperError::ChunkOwnership(std::panic::Location::caller()))?; if let Some(next) = &mut mut_self.next { // Not the last chunk, recurse next.append_samples(samples, chunk_factory) } else { // Add to this chunk first let available_space = mut_self.samples.len() - mut_self.sample_count; let samples_to_append_to_this_chunk = samples.len().min(available_space); let dest = &mut mut_self.samples [mut_self.sample_count..mut_self.sample_count + samples_to_append_to_this_chunk]; dest.copy_from_slice(&samples[..samples_to_append_to_this_chunk]); mut_self.sample_count += samples_to_append_to_this_chunk; // Handle remaining samples by recursion if samples_to_append_to_this_chunk < samples.len() { let mut new_chunk = chunk_factory.create_chunk()?; let remaining_samples = &samples[samples_to_append_to_this_chunk..]; new_chunk.append_samples(remaining_samples, chunk_factory)?; // Link the new chunk mut_self.next = Some(new_chunk); Ok(()) } else { Ok(()) } } } /// Get total sample count across entire chunk chain pub fn len(&self) -> usize { let mut total = self.sample_count; let mut current = &self.next; while let Some(chunk) = current { total += chunk.sample_count; current = &chunk.next; } total } pub fn copy_samples(self: &Arc, dest: &mut [f32], start: usize) -> Result<()> { if start < self.sample_count { // Copy from this chunk let end = start + dest.len(); if end <= self.sample_count { dest.copy_from_slice(&self.samples[start..end]); } else if let Some(next) = self.next.as_ref() { let sample_count_from_this_chunk = self.sample_count - start; dest[..sample_count_from_this_chunk] .copy_from_slice(&self.samples[start..self.sample_count]); next.copy_samples(&mut dest[sample_count_from_this_chunk..], 0)?; } else { return Err(LooperError::OutOfBounds(std::panic::Location::caller())); } Ok(()) } else if let Some(next) = &self.next { // Copy from next chunk next.copy_samples(dest, start - self.sample_count) } else { Err(LooperError::OutOfBounds(std::panic::Location::caller())) } } } #[cfg(test)] mod tests { use super::*; #[test] fn test_len_single_chunk_with_samples() { let chunk = Arc::new(AudioChunk { samples: vec![1.0, 2.0, 3.0, 4.0, 5.0].into_boxed_slice(), sample_count: 5, next: None, }); assert_eq!(chunk.len(), 5); } #[test] fn test_len_single_chunk_empty() { let chunk = Arc::new(AudioChunk { samples: vec![0.0, 0.0, 0.0].into_boxed_slice(), sample_count: 0, next: None, }); assert_eq!(chunk.len(), 0); } #[test] fn test_len_single_chunk_partial() { let chunk = Arc::new(AudioChunk { samples: vec![1.0, 2.0, 3.0, 0.0, 0.0].into_boxed_slice(), sample_count: 3, // Only first 3 samples are valid next: None, }); assert_eq!(chunk.len(), 3); } #[test] fn test_len_linked_chunks() { let chunk3 = Arc::new(AudioChunk { samples: vec![7.0, 8.0, 9.0].into_boxed_slice(), sample_count: 3, next: None, }); let chunk2 = Arc::new(AudioChunk { samples: vec![4.0, 5.0].into_boxed_slice(), sample_count: 2, next: Some(chunk3), }); let chunk1 = Arc::new(AudioChunk { samples: vec![1.0, 2.0, 3.0].into_boxed_slice(), sample_count: 3, next: Some(chunk2), }); assert_eq!(chunk1.len(), 8); // 3 + 2 + 3 } #[test] fn test_len_chain_with_empty_chunks() { let chunk3 = Arc::new(AudioChunk { samples: vec![4.0, 5.0].into_boxed_slice(), sample_count: 2, next: None, }); let chunk2 = Arc::new(AudioChunk { samples: vec![0.0, 0.0].into_boxed_slice(), sample_count: 0, // Empty chunk in middle next: Some(chunk3), }); let chunk1 = Arc::new(AudioChunk { samples: vec![1.0, 2.0, 3.0].into_boxed_slice(), sample_count: 3, next: Some(chunk2), }); assert_eq!(chunk1.len(), 5); // 3 + 0 + 2 } #[test] fn test_len_all_empty_chunks() { let chunk2 = Arc::new(AudioChunk { samples: vec![0.0, 0.0].into_boxed_slice(), sample_count: 0, next: None, }); let chunk1 = Arc::new(AudioChunk { samples: vec![0.0, 0.0, 0.0].into_boxed_slice(), sample_count: 0, next: Some(chunk2), }); assert_eq!(chunk1.len(), 0); } #[test] fn test_len_long_chain() { // Create a longer chain to test iteration let mut current_chunk = Arc::new(AudioChunk { samples: vec![10.0].into_boxed_slice(), sample_count: 1, next: None, }); // Build chain backwards: 9 -> 8 -> ... -> 1 -> 0 for i in (0..10).rev() { current_chunk = Arc::new(AudioChunk { samples: vec![i as f32].into_boxed_slice(), sample_count: 1, next: Some(current_chunk), }); } assert_eq!(current_chunk.len(), 11); // 11 chunks, each with 1 sample } #[test] fn test_allocate_creates_empty_chunk() { let chunk = AudioChunk::allocate(1024); assert_eq!(chunk.samples.len(), 1024); assert_eq!(chunk.sample_count, 0); assert!(chunk.next.is_none()); // All samples should be initialized to 0.0 assert!(chunk.samples.iter().all(|&x| x == 0.0)); } #[test] fn test_consolidate_single_chunk() { let mut chunk = AudioChunk::allocate(10); // Write some sample data let samples = vec![1.0, 2.0, 3.0, 4.0, 5.0]; let mut factory = || panic!("Factory should not be called"); let result = AudioChunk::append_samples(&mut chunk, &samples, &mut factory); assert!(result.is_ok()); let consolidated = AudioChunk::consolidate(&chunk); assert_eq!(consolidated.samples.len(), 5); assert_eq!(consolidated.sample_count, 5); assert!(consolidated.next.is_none()); assert_eq!(consolidated.samples[0], 1.0); assert_eq!(consolidated.samples[4], 5.0); } #[test] fn test_consolidate_linked_chunks() { // Create first chunk let chunk1 = Arc::new(AudioChunk { samples: vec![1.0, 2.0, 3.0].into_boxed_slice(), sample_count: 3, next: None, }); // Create second chunk let chunk2 = Arc::new(AudioChunk { samples: vec![4.0, 5.0].into_boxed_slice(), sample_count: 2, next: None, }); // Create third chunk let chunk3 = Arc::new(AudioChunk { samples: vec![6.0, 7.0, 8.0, 9.0].into_boxed_slice(), sample_count: 4, next: None, }); // Link them together let chunk1 = Arc::new(AudioChunk { samples: chunk1.samples.clone(), sample_count: chunk1.sample_count, next: Some(Arc::new(AudioChunk { samples: chunk2.samples.clone(), sample_count: chunk2.sample_count, next: Some(chunk3), })), }); let consolidated = AudioChunk::consolidate(&chunk1); assert_eq!(consolidated.samples.len(), 9); assert_eq!(consolidated.sample_count, 9); assert!(consolidated.next.is_none()); let expected = vec![1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0]; assert_eq!(consolidated.samples.as_ref(), expected.as_slice()); } #[test] fn test_consolidate_partial_chunks() { // Test chunks where sample_count < samples.len() let chunk1 = Arc::new(AudioChunk { samples: vec![1.0, 2.0, 3.0, 0.0, 0.0].into_boxed_slice(), sample_count: 3, // Only first 3 samples are valid next: None, }); let chunk2 = Arc::new(AudioChunk { samples: vec![4.0, 5.0, 0.0].into_boxed_slice(), sample_count: 2, // Only first 2 samples are valid next: None, }); let chunk1 = Arc::new(AudioChunk { samples: chunk1.samples.clone(), sample_count: chunk1.sample_count, next: Some(chunk2), }); let consolidated = AudioChunk::consolidate(&chunk1); assert_eq!(consolidated.samples.len(), 5); assert_eq!(consolidated.sample_count, 5); let expected = vec![1.0, 2.0, 3.0, 4.0, 5.0]; assert_eq!(consolidated.samples.as_ref(), expected.as_slice()); } #[test] fn test_append_samples_empty() { let mut chunk = AudioChunk::allocate(5); let mut factory = || panic!("Factory should not be called for empty samples"); let result = AudioChunk::append_samples(&mut chunk, &[], &mut factory); assert!(result.is_ok()); assert_eq!(chunk.sample_count, 0); assert!(chunk.next.is_none()); } #[test] fn test_append_samples_fits_in_one_chunk() { let mut chunk = AudioChunk::allocate(5); let mut factory = || panic!("Factory should not be called when samples fit"); let samples = vec![1.0, 2.0, 3.0]; let result = AudioChunk::append_samples(&mut chunk, &samples, &mut factory); assert!(result.is_ok()); assert_eq!(chunk.sample_count, 3); assert_eq!(chunk.samples[0], 1.0); assert_eq!(chunk.samples[1], 2.0); assert_eq!(chunk.samples[2], 3.0); assert!(chunk.next.is_none()); } #[test] fn test_append_samples_exactly_fills_chunk() { let mut chunk = AudioChunk::allocate(3); let mut factory = || panic!("Factory should not be called when samples exactly fit"); let samples = vec![1.0, 2.0, 3.0]; let result = AudioChunk::append_samples(&mut chunk, &samples, &mut factory); assert!(result.is_ok()); assert_eq!(chunk.sample_count, 3); assert_eq!(chunk.samples[0], 1.0); assert_eq!(chunk.samples[1], 2.0); assert_eq!(chunk.samples[2], 3.0); assert!(chunk.next.is_none()); } #[test] fn test_append_samples_requires_new_chunk() { let mut chunk = AudioChunk::allocate(2); let mut factory = chunk_factory::mock::MockFactory::new(vec![AudioChunk::allocate(3)]); let samples = vec![1.0, 2.0, 3.0, 4.0]; // 4 samples, first chunk holds 2 let result = AudioChunk::append_samples(&mut chunk, &samples, &mut factory); assert!(result.is_ok()); // First chunk: 2 samples assert_eq!(chunk.sample_count, 2); assert_eq!(chunk.samples[0], 1.0); assert_eq!(chunk.samples[1], 2.0); // Second chunk: remaining 2 samples let chunk2 = chunk.next.as_ref().unwrap(); assert_eq!(chunk2.sample_count, 2); assert_eq!(chunk2.samples[0], 3.0); assert_eq!(chunk2.samples[1], 4.0); assert!(chunk2.next.is_none()); } #[test] fn test_append_samples_multiple_chunks_cascade() { let mut chunk = AudioChunk::allocate(2); let mut factory = chunk_factory::mock::MockFactory::new(vec![ AudioChunk::allocate(2), AudioChunk::allocate(2), ]); let samples = vec![1.0, 2.0, 3.0, 4.0, 5.0, 6.0]; // 6 samples, each chunk holds 2 let result = AudioChunk::append_samples(&mut chunk, &samples, &mut factory); assert!(result.is_ok()); // First chunk assert_eq!(chunk.sample_count, 2); assert_eq!(chunk.samples[0], 1.0); assert_eq!(chunk.samples[1], 2.0); // Second chunk let chunk2 = chunk.next.as_ref().unwrap(); assert_eq!(chunk2.sample_count, 2); assert_eq!(chunk2.samples[0], 3.0); assert_eq!(chunk2.samples[1], 4.0); // Third chunk let chunk3 = chunk2.next.as_ref().unwrap(); assert_eq!(chunk3.sample_count, 2); assert_eq!(chunk3.samples[0], 5.0); assert_eq!(chunk3.samples[1], 6.0); assert!(chunk3.next.is_none()); } #[test] fn test_append_samples_factory_failure() { let mut chunk = AudioChunk::allocate(2); let mut factory = || Err(LooperError::ChunkAllocation(std::panic::Location::caller())); let samples = vec![1.0, 2.0, 3.0]; // 3 samples, chunk only holds 2 let result = AudioChunk::append_samples(&mut chunk, &samples, &mut factory); assert!(result.is_err()); assert!(matches!( result.unwrap_err(), LooperError::ChunkAllocation(_) )); // First chunk should have been written before factory failure assert_eq!(chunk.sample_count, 2); assert_eq!(chunk.samples[0], 1.0); assert_eq!(chunk.samples[1], 2.0); assert!(chunk.next.is_none()); } #[test] fn test_append_samples_ownership_failure() { let chunk = AudioChunk::allocate(3); let chunk_clone = chunk.clone(); // Create another reference let mut chunk_original = chunk; let mut factory = || panic!("Factory should not be called"); let samples = vec![1.0, 2.0]; let result = AudioChunk::append_samples(&mut chunk_original, &samples, &mut factory); assert!(result.is_err()); assert!(matches!( result.unwrap_err(), LooperError::ChunkOwnership(_) )); // Chunk should be unchanged assert_eq!(chunk_original.sample_count, 0); // Clean up the clone reference to avoid unused variable warning drop(chunk_clone); } #[test] fn test_copy_samples_single_chunk_full_copy() { let chunk = Arc::new(AudioChunk { samples: vec![1.0, 2.0, 3.0, 4.0, 5.0].into_boxed_slice(), sample_count: 5, next: None, }); let mut dest = vec![0.0; 5]; let result = chunk.copy_samples(&mut dest, 0); assert!(result.is_ok()); assert_eq!(dest, vec![1.0, 2.0, 3.0, 4.0, 5.0]); } #[test] fn test_copy_samples_single_chunk_partial_copy() { let chunk = Arc::new(AudioChunk { samples: vec![1.0, 2.0, 3.0, 4.0, 5.0].into_boxed_slice(), sample_count: 5, next: None, }); let mut dest = vec![0.0; 3]; let result = chunk.copy_samples(&mut dest, 1); // Start at index 1 assert!(result.is_ok()); assert_eq!(dest, vec![2.0, 3.0, 4.0]); } #[test] fn test_copy_samples_single_chunk_zero_samples() { let chunk = Arc::new(AudioChunk { samples: vec![1.0, 2.0, 3.0].into_boxed_slice(), sample_count: 3, next: None, }); let mut dest: Vec = vec![]; let result = chunk.copy_samples(&mut dest, 0); assert!(result.is_ok()); assert_eq!(dest, Vec::::new()); } #[test] fn test_copy_samples_single_chunk_out_of_bounds() { let chunk = Arc::new(AudioChunk { samples: vec![1.0, 2.0, 3.0].into_boxed_slice(), sample_count: 3, next: None, }); let mut dest = vec![0.0; 2]; let result = chunk.copy_samples(&mut dest, 5); // Start beyond sample_count assert!(result.is_err()); assert!(matches!(result.unwrap_err(), LooperError::OutOfBounds(_))); } #[test] fn test_copy_samples_single_chunk_partial_out_of_bounds() { let chunk = Arc::new(AudioChunk { samples: vec![1.0, 2.0, 3.0].into_boxed_slice(), sample_count: 3, next: None, }); let mut dest = vec![0.0; 5]; // Want 5 samples starting at index 1, but only 2 available let result = chunk.copy_samples(&mut dest, 1); assert!(result.is_err()); assert!(matches!(result.unwrap_err(), LooperError::OutOfBounds(_))); } #[test] fn test_copy_samples_linked_chunks_single_chunk_boundary() { let chunk1 = Arc::new(AudioChunk { samples: vec![1.0, 2.0, 3.0].into_boxed_slice(), sample_count: 3, next: Some(Arc::new(AudioChunk { samples: vec![4.0, 5.0, 6.0].into_boxed_slice(), sample_count: 3, next: None, })), }); let mut dest = vec![0.0; 3]; let result = chunk1.copy_samples(&mut dest, 3); // Start exactly at chunk boundary assert!(result.is_ok()); assert_eq!(dest, vec![4.0, 5.0, 6.0]); } #[test] fn test_copy_samples_linked_chunks_across_boundary() { let chunk1 = Arc::new(AudioChunk { samples: vec![1.0, 2.0, 3.0].into_boxed_slice(), sample_count: 3, next: Some(Arc::new(AudioChunk { samples: vec![4.0, 5.0, 6.0].into_boxed_slice(), sample_count: 3, next: None, })), }); let mut dest = vec![0.0; 4]; let result = chunk1.copy_samples(&mut dest, 2); // Start in first chunk, cross to second assert!(result.is_ok()); assert_eq!(dest, vec![3.0, 4.0, 5.0, 6.0]); } #[test] fn test_copy_samples_linked_chunks_multiple_spans() { let chunk1 = Arc::new(AudioChunk { samples: vec![1.0, 2.0].into_boxed_slice(), sample_count: 2, next: Some(Arc::new(AudioChunk { samples: vec![3.0, 4.0].into_boxed_slice(), sample_count: 2, next: Some(Arc::new(AudioChunk { samples: vec![5.0, 6.0, 7.0].into_boxed_slice(), sample_count: 3, next: None, })), })), }); let mut dest = vec![0.0; 5]; let result = chunk1.copy_samples(&mut dest, 1); // Start in first, span all three chunks assert!(result.is_ok()); assert_eq!(dest, vec![2.0, 3.0, 4.0, 5.0, 6.0]); } #[test] fn test_copy_samples_linked_chunks_out_of_bounds() { let chunk1 = Arc::new(AudioChunk { samples: vec![1.0, 2.0, 3.0].into_boxed_slice(), sample_count: 3, next: Some(Arc::new(AudioChunk { samples: vec![4.0, 5.0].into_boxed_slice(), sample_count: 2, next: None, })), }); let mut dest = vec![0.0; 3]; let result = chunk1.copy_samples(&mut dest, 10); // Start beyond all chunks assert!(result.is_err()); assert!(matches!(result.unwrap_err(), LooperError::OutOfBounds(_))); } #[test] fn test_append_samples_to_partially_filled_chunk() { let mut chunk = Arc::new(AudioChunk { samples: vec![1.0, 2.0, 0.0, 0.0, 0.0].into_boxed_slice(), sample_count: 2, // Already has 2 samples next: None, }); let mut factory = || panic!("Factory should not be called"); let samples = vec![3.0, 4.0]; let result = AudioChunk::append_samples(&mut chunk, &samples, &mut factory); assert!(result.is_ok()); assert_eq!(chunk.sample_count, 4); assert_eq!(chunk.samples[0], 1.0); assert_eq!(chunk.samples[1], 2.0); assert_eq!(chunk.samples[2], 3.0); assert_eq!(chunk.samples[3], 4.0); assert!(chunk.next.is_none()); } #[test] fn test_append_samples_to_full_chunk_creates_new() { let mut chunk = Arc::new(AudioChunk { samples: vec![1.0, 2.0, 3.0].into_boxed_slice(), sample_count: 3, // Chunk is full next: None, }); let mut factory = chunk_factory::mock::MockFactory::new(vec![AudioChunk::allocate(3)]); let samples = vec![4.0, 5.0]; let result = AudioChunk::append_samples(&mut chunk, &samples, &mut factory); assert!(result.is_ok()); assert_eq!(chunk.sample_count, 3); // First chunk unchanged let chunk2 = chunk.next.as_ref().unwrap(); assert_eq!(chunk2.sample_count, 2); assert_eq!(chunk2.samples[0], 4.0); assert_eq!(chunk2.samples[1], 5.0); } #[test] fn test_append_samples_to_middle_of_chain() { // Create a chain: chunk1 -> chunk2 -> chunk3 let chunk3 = Arc::new(AudioChunk { samples: vec![7.0, 8.0, 0.0].into_boxed_slice(), sample_count: 2, next: None, }); let chunk2 = Arc::new(AudioChunk { samples: vec![4.0, 5.0, 6.0].into_boxed_slice(), sample_count: 3, next: Some(chunk3), }); let mut chunk1 = Arc::new(AudioChunk { samples: vec![1.0, 2.0, 3.0].into_boxed_slice(), sample_count: 3, next: Some(chunk2), }); // Append should go to the last chunk (chunk3) let mut factory = || panic!("Factory should not be called when space available"); let samples = vec![9.0]; let result = AudioChunk::append_samples(&mut chunk1, &samples, &mut factory); assert!(result.is_ok()); // Navigate to chunk3 and verify the sample was added let chunk2 = chunk1.next.as_ref().unwrap(); let chunk3 = chunk2.next.as_ref().unwrap(); assert_eq!(chunk3.sample_count, 3); assert_eq!(chunk3.samples[2], 9.0); } #[test] fn test_append_samples_multiple_calls() { let mut chunk = AudioChunk::allocate(5); let mut factory = || panic!("Factory should not be called"); // First append let result1 = AudioChunk::append_samples(&mut chunk, &[1.0, 2.0], &mut factory); assert!(result1.is_ok()); assert_eq!(chunk.sample_count, 2); // Second append let result2 = AudioChunk::append_samples(&mut chunk, &[3.0], &mut factory); assert!(result2.is_ok()); assert_eq!(chunk.sample_count, 3); // Third append let result3 = AudioChunk::append_samples(&mut chunk, &[4.0, 5.0], &mut factory); assert!(result3.is_ok()); assert_eq!(chunk.sample_count, 5); // Verify all samples assert_eq!(chunk.samples[0], 1.0); assert_eq!(chunk.samples[1], 2.0); assert_eq!(chunk.samples[2], 3.0); assert_eq!(chunk.samples[3], 4.0); assert_eq!(chunk.samples[4], 5.0); } #[test] fn test_append_samples_chain_with_overflow() { // Create initial chain with some space in last chunk let chunk2 = Arc::new(AudioChunk { samples: vec![4.0, 5.0, 0.0].into_boxed_slice(), sample_count: 2, // Has space for 1 more next: None, }); let mut chunk1 = Arc::new(AudioChunk { samples: vec![1.0, 2.0, 3.0].into_boxed_slice(), sample_count: 3, next: Some(chunk2), }); let mut factory = chunk_factory::mock::MockFactory::new(vec![AudioChunk::allocate(3)]); let samples = vec![6.0, 7.0, 8.0]; // 3 samples, but only 1 space available let result = AudioChunk::append_samples(&mut chunk1, &samples, &mut factory); assert!(result.is_ok()); // Check that chunk2 got filled let chunk2 = chunk1.next.as_ref().unwrap(); assert_eq!(chunk2.sample_count, 3); assert_eq!(chunk2.samples[2], 6.0); // Check that chunk3 was created with remaining samples let chunk3 = chunk2.next.as_ref().unwrap(); assert_eq!(chunk3.sample_count, 2); assert_eq!(chunk3.samples[0], 7.0); assert_eq!(chunk3.samples[1], 8.0); } #[test] fn test_consolidate_empty_chunk() { let chunk = Arc::new(AudioChunk { samples: vec![0.0, 0.0, 0.0].into_boxed_slice(), sample_count: 0, // No valid samples next: None, }); let consolidated = AudioChunk::consolidate(&chunk); assert_eq!(consolidated.samples.len(), 0); assert_eq!(consolidated.sample_count, 0); assert!(consolidated.next.is_none()); } #[test] fn test_consolidate_chain_with_empty_chunks() { let chunk3 = Arc::new(AudioChunk { samples: vec![4.0, 5.0, 0.0].into_boxed_slice(), sample_count: 2, next: None, }); let chunk2 = Arc::new(AudioChunk { samples: vec![0.0, 0.0].into_boxed_slice(), sample_count: 0, // Empty middle chunk next: Some(chunk3), }); let chunk1 = Arc::new(AudioChunk { samples: vec![1.0, 2.0, 3.0].into_boxed_slice(), sample_count: 3, next: Some(chunk2), }); let consolidated = AudioChunk::consolidate(&chunk1); assert_eq!(consolidated.samples.len(), 5); assert_eq!(consolidated.sample_count, 5); assert_eq!(consolidated.samples.as_ref(), &[1.0, 2.0, 3.0, 4.0, 5.0]); } #[test] fn test_consolidate_all_empty_chunks() { let chunk2 = Arc::new(AudioChunk { samples: vec![0.0, 0.0].into_boxed_slice(), sample_count: 0, next: None, }); let chunk1 = Arc::new(AudioChunk { samples: vec![0.0, 0.0, 0.0].into_boxed_slice(), sample_count: 0, next: Some(chunk2), }); let consolidated = AudioChunk::consolidate(&chunk1); assert_eq!(consolidated.samples.len(), 0); assert_eq!(consolidated.sample_count, 0); assert!(consolidated.next.is_none()); } #[test] fn test_copy_samples_from_consolidated_chunk() { // Test that copy_samples works correctly after consolidation let chunk2 = Arc::new(AudioChunk { samples: vec![4.0, 5.0].into_boxed_slice(), sample_count: 2, next: None, }); let chunk1 = Arc::new(AudioChunk { samples: vec![1.0, 2.0, 3.0].into_boxed_slice(), sample_count: 3, next: Some(chunk2), }); let consolidated = AudioChunk::consolidate(&chunk1); let mut dest = vec![0.0; 3]; let result = consolidated.copy_samples(&mut dest, 2); assert!(result.is_ok()); assert_eq!(dest, vec![3.0, 4.0, 5.0]); } }