747 lines
25 KiB
Rust
747 lines
25 KiB
Rust
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<Arc<AudioChunk>>,
|
|
}
|
|
|
|
impl AudioChunk {
|
|
/// Create a new empty chunk with specified capacity (for buffer pool)
|
|
pub fn allocate(size: usize) -> Arc<Self> {
|
|
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<AudioChunk>) -> Arc<Self> {
|
|
// 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<F: ChunkFactory>(
|
|
self: &mut Arc<Self>,
|
|
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(())
|
|
}
|
|
}
|
|
}
|
|
|
|
pub fn copy_samples(self: &Arc<Self>, 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_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<f32> = vec![];
|
|
let result = chunk.copy_samples(&mut dest, 0);
|
|
|
|
assert!(result.is_ok());
|
|
assert_eq!(dest, Vec::<f32>::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]);
|
|
}
|
|
}
|