This commit is contained in:
Geens 2025-06-04 21:06:27 +02:00
parent a450e81338
commit ee5807e9a9
7 changed files with 385 additions and 164 deletions

View File

@ -1,26 +1,43 @@
use std::sync::Arc; use crate::*;
use crate::AudioChunk;
pub struct Allocator { pub struct Allocator {
buffer_size: usize, channel: tokio::sync::mpsc::Receiver<Arc<AudioChunk>>,
allocated_buffer_sender: tokio::sync::mpsc::Sender<Arc<AudioChunk>>,
} }
impl Allocator { impl Allocator {
pub fn new(buffer_size: usize, allocated_buffer_sender: tokio::sync::mpsc::Sender<Arc<AudioChunk>>) -> Self { pub fn spawn(buffer_size: usize, pool_size: usize) -> (Self, tokio::task::JoinHandle<()>) {
while allocated_buffer_sender.try_send(AudioChunk::allocate(buffer_size)).is_ok() {} let (allocated_buffer_sender, allocated_buffer_receiver) =
Self { tokio::sync::mpsc::channel(pool_size);
buffer_size,
allocated_buffer_sender, // Pre-fill the channel with initial buffers
} while allocated_buffer_sender
} .try_send(AudioChunk::allocate(buffer_size))
.is_ok()
pub async fn run(self) { {}
loop {
let chunk = AudioChunk::allocate(self.buffer_size); let allocator = Allocator {
if self.allocated_buffer_sender.send(chunk).await.is_err() { channel: allocated_buffer_receiver,
break; };
// Spawn the background task that continuously allocates buffers
let join_handle = tokio::runtime::Handle::current().spawn(async move {
loop {
let chunk = AudioChunk::allocate(buffer_size);
if allocated_buffer_sender.send(chunk).await.is_err() {
break;
}
} }
});
(allocator, join_handle)
}
}
impl ChunkFactory for Allocator {
fn create_chunk(&mut self) -> Result<std::sync::Arc<AudioChunk>> {
match self.channel.try_recv() {
Ok(chunk) => Ok(chunk),
Err(_) => Err(LooperError::ChunkAllocation(std::panic::Location::caller())),
} }
} }
} }

View File

@ -8,10 +8,10 @@ use crate::*;
pub struct AudioChunk { pub struct AudioChunk {
/// Fixed-size audio buffer /// Fixed-size audio buffer
pub samples: Box<[f32]>, pub samples: Box<[f32]>,
/// Number of valid samples (≤ samples.len()) /// Number of valid samples (≤ samples.len())
pub sample_count: usize, pub sample_count: usize,
/// Next chunk in linked list /// Next chunk in linked list
pub next: Option<Arc<AudioChunk>>, pub next: Option<Arc<AudioChunk>>,
} }
@ -25,7 +25,7 @@ impl AudioChunk {
next: None, next: None,
}) })
} }
/// Consolidate a linked list of chunks into a single optimized chunk /// Consolidate a linked list of chunks into a single optimized chunk
pub fn consolidate(this: &Arc<AudioChunk>) -> Arc<Self> { pub fn consolidate(this: &Arc<AudioChunk>) -> Arc<Self> {
// Calculate total sample count // Calculate total sample count
@ -35,7 +35,7 @@ impl AudioChunk {
total_samples += chunk.sample_count; total_samples += chunk.sample_count;
current = chunk.next.clone(); current = chunk.next.clone();
} }
// Create consolidated buffer // Create consolidated buffer
let mut consolidated_samples = Vec::with_capacity(total_samples); let mut consolidated_samples = Vec::with_capacity(total_samples);
let mut current = Some(this.clone()); let mut current = Some(this.clone());
@ -43,50 +43,69 @@ impl AudioChunk {
consolidated_samples.extend_from_slice(&chunk.samples[..chunk.sample_count]); consolidated_samples.extend_from_slice(&chunk.samples[..chunk.sample_count]);
current = chunk.next.clone(); current = chunk.next.clone();
} }
Arc::new(Self { Arc::new(Self {
samples: consolidated_samples.into_boxed_slice(), samples: consolidated_samples.into_boxed_slice(),
sample_count: total_samples, sample_count: total_samples,
next: None, next: None,
}) })
} }
/// Write samples into this chunk and additional chunks as needed. /// Add samples to this chunk and allocate additional chunks as needed.
/// Uses the factory to get pre-allocated chunks when current chunk fills up. /// Uses the factory to get pre-allocated chunks when current chunk fills up.
pub fn write_samples<F>(this: &mut Arc<Self>, samples: &[f32], mut chunk_factory: F) -> Result<(), LooperError> pub fn append_samples<F: ChunkFactory>(
where self: &mut Arc<Self>,
F: FnMut() -> Result<Arc<AudioChunk>, LooperError> samples: &[f32],
{ chunk_factory: &mut F,
let sample_count = samples.len(); ) -> Result<()> {
let mut_self = Arc::get_mut(self)
if sample_count > 0 { .ok_or(LooperError::ChunkOwnership(std::panic::Location::caller()))?;
let available_space = this.samples.len(); if let Some(next) = &mut mut_self.next {
let samples_to_write = sample_count.min(available_space); // Not the last chunk, recurse
next.append_samples(samples, chunk_factory)
// Get mutable access to write samples } else {
let chunk_mut = Arc::get_mut(this).ok_or(LooperError::ChunkOwnership)?; // Add to this chunk first
let available_space = mut_self.samples.len() - mut_self.sample_count;
// Write what fits let samples_to_append_to_this_chunk = samples.len().min(available_space);
if let Some(dest) = chunk_mut.samples.get_mut(..samples_to_write) { let dest = &mut mut_self.samples
dest.copy_from_slice(&samples[..samples_to_write]); [mut_self.sample_count..mut_self.sample_count + samples_to_append_to_this_chunk];
chunk_mut.sample_count = samples_to_write; 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
// Handle remaining samples if any if samples_to_append_to_this_chunk < samples.len() {
if samples_to_write < sample_count { let mut new_chunk = chunk_factory.create_chunk()?;
// Need a new chunk for remaining samples let remaining_samples = &samples[samples_to_append_to_this_chunk..];
let mut new_chunk = chunk_factory()?; new_chunk.append_samples(remaining_samples, chunk_factory)?;
let remaining_samples = &samples[samples_to_write..];
// Recurse on the new chunk
Self::write_samples(&mut new_chunk, remaining_samples, chunk_factory)?;
// Link the new chunk // Link the new chunk
chunk_mut.next = Some(new_chunk); mut_self.next = Some(new_chunk);
Ok(())
} else {
Ok(())
} }
} }
}
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()))
}
} }
} }
@ -97,34 +116,34 @@ mod tests {
#[test] #[test]
fn test_allocate_creates_empty_chunk() { fn test_allocate_creates_empty_chunk() {
let chunk = AudioChunk::allocate(1024); let chunk = AudioChunk::allocate(1024);
assert_eq!(chunk.samples.len(), 1024); assert_eq!(chunk.samples.len(), 1024);
assert_eq!(chunk.sample_count, 0); assert_eq!(chunk.sample_count, 0);
assert!(chunk.next.is_none()); assert!(chunk.next.is_none());
// All samples should be initialized to 0.0 // All samples should be initialized to 0.0
assert!(chunk.samples.iter().all(|&x| x == 0.0)); assert!(chunk.samples.iter().all(|&x| x == 0.0));
} }
#[test] #[test]
fn test_consolidate_single_chunk() { fn test_consolidate_single_chunk() {
let mut chunk = AudioChunk::allocate(10); let mut chunk = AudioChunk::allocate(10);
// Write some sample data // Write some sample data
let samples = vec![1.0, 2.0, 3.0, 4.0, 5.0]; let samples = vec![1.0, 2.0, 3.0, 4.0, 5.0];
let factory = || panic!("Factory should not be called"); let mut factory = || panic!("Factory should not be called");
let result = AudioChunk::write_samples(&mut chunk, &samples, factory); let result = AudioChunk::append_samples(&mut chunk, &samples, &mut factory);
assert!(result.is_ok()); assert!(result.is_ok());
let consolidated = AudioChunk::consolidate(&chunk); let consolidated = AudioChunk::consolidate(&chunk);
assert_eq!(consolidated.samples.len(), 5); assert_eq!(consolidated.samples.len(), 5);
assert_eq!(consolidated.sample_count, 5); assert_eq!(consolidated.sample_count, 5);
assert!(consolidated.next.is_none()); assert!(consolidated.next.is_none());
assert_eq!(consolidated.samples[0], 1.0); assert_eq!(consolidated.samples[0], 1.0);
assert_eq!(consolidated.samples[4], 5.0); assert_eq!(consolidated.samples[4], 5.0);
} }
#[test] #[test]
fn test_consolidate_linked_chunks() { fn test_consolidate_linked_chunks() {
// Create first chunk // Create first chunk
@ -133,21 +152,21 @@ mod tests {
sample_count: 3, sample_count: 3,
next: None, next: None,
}); });
// Create second chunk // Create second chunk
let chunk2 = Arc::new(AudioChunk { let chunk2 = Arc::new(AudioChunk {
samples: vec![4.0, 5.0].into_boxed_slice(), samples: vec![4.0, 5.0].into_boxed_slice(),
sample_count: 2, sample_count: 2,
next: None, next: None,
}); });
// Create third chunk // Create third chunk
let chunk3 = Arc::new(AudioChunk { let chunk3 = Arc::new(AudioChunk {
samples: vec![6.0, 7.0, 8.0, 9.0].into_boxed_slice(), samples: vec![6.0, 7.0, 8.0, 9.0].into_boxed_slice(),
sample_count: 4, sample_count: 4,
next: None, next: None,
}); });
// Link them together // Link them together
let chunk1 = Arc::new(AudioChunk { let chunk1 = Arc::new(AudioChunk {
samples: chunk1.samples.clone(), samples: chunk1.samples.clone(),
@ -158,17 +177,17 @@ mod tests {
next: Some(chunk3), next: Some(chunk3),
})), })),
}); });
let consolidated = AudioChunk::consolidate(&chunk1); let consolidated = AudioChunk::consolidate(&chunk1);
assert_eq!(consolidated.samples.len(), 9); assert_eq!(consolidated.samples.len(), 9);
assert_eq!(consolidated.sample_count, 9); assert_eq!(consolidated.sample_count, 9);
assert!(consolidated.next.is_none()); 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]; 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()); assert_eq!(consolidated.samples.as_ref(), expected.as_slice());
} }
#[test] #[test]
fn test_consolidate_partial_chunks() { fn test_consolidate_partial_chunks() {
// Test chunks where sample_count < samples.len() // Test chunks where sample_count < samples.len()
@ -177,48 +196,48 @@ mod tests {
sample_count: 3, // Only first 3 samples are valid sample_count: 3, // Only first 3 samples are valid
next: None, next: None,
}); });
let chunk2 = Arc::new(AudioChunk { let chunk2 = Arc::new(AudioChunk {
samples: vec![4.0, 5.0, 0.0].into_boxed_slice(), samples: vec![4.0, 5.0, 0.0].into_boxed_slice(),
sample_count: 2, // Only first 2 samples are valid sample_count: 2, // Only first 2 samples are valid
next: None, next: None,
}); });
let chunk1 = Arc::new(AudioChunk { let chunk1 = Arc::new(AudioChunk {
samples: chunk1.samples.clone(), samples: chunk1.samples.clone(),
sample_count: chunk1.sample_count, sample_count: chunk1.sample_count,
next: Some(chunk2), next: Some(chunk2),
}); });
let consolidated = AudioChunk::consolidate(&chunk1); let consolidated = AudioChunk::consolidate(&chunk1);
assert_eq!(consolidated.samples.len(), 5); assert_eq!(consolidated.samples.len(), 5);
assert_eq!(consolidated.sample_count, 5); assert_eq!(consolidated.sample_count, 5);
let expected = vec![1.0, 2.0, 3.0, 4.0, 5.0]; let expected = vec![1.0, 2.0, 3.0, 4.0, 5.0];
assert_eq!(consolidated.samples.as_ref(), expected.as_slice()); assert_eq!(consolidated.samples.as_ref(), expected.as_slice());
} }
#[test] #[test]
fn test_write_samples_empty() { fn test_append_samples_empty() {
let mut chunk = AudioChunk::allocate(5); let mut chunk = AudioChunk::allocate(5);
let factory = || panic!("Factory should not be called for empty samples"); let mut factory = || panic!("Factory should not be called for empty samples");
let result = AudioChunk::write_samples(&mut chunk, &[], factory); let result = AudioChunk::append_samples(&mut chunk, &[], &mut factory);
assert!(result.is_ok()); assert!(result.is_ok());
assert_eq!(chunk.sample_count, 0); assert_eq!(chunk.sample_count, 0);
assert!(chunk.next.is_none()); assert!(chunk.next.is_none());
} }
#[test] #[test]
fn test_write_samples_fits_in_one_chunk() { fn test_append_samples_fits_in_one_chunk() {
let mut chunk = AudioChunk::allocate(5); let mut chunk = AudioChunk::allocate(5);
let factory = || panic!("Factory should not be called when samples fit"); let mut factory = || panic!("Factory should not be called when samples fit");
let samples = vec![1.0, 2.0, 3.0]; let samples = vec![1.0, 2.0, 3.0];
let result = AudioChunk::write_samples(&mut chunk, &samples, factory); let result = AudioChunk::append_samples(&mut chunk, &samples, &mut factory);
assert!(result.is_ok()); assert!(result.is_ok());
assert_eq!(chunk.sample_count, 3); assert_eq!(chunk.sample_count, 3);
assert_eq!(chunk.samples[0], 1.0); assert_eq!(chunk.samples[0], 1.0);
@ -226,15 +245,15 @@ mod tests {
assert_eq!(chunk.samples[2], 3.0); assert_eq!(chunk.samples[2], 3.0);
assert!(chunk.next.is_none()); assert!(chunk.next.is_none());
} }
#[test] #[test]
fn test_write_samples_exactly_fills_chunk() { fn test_append_samples_exactly_fills_chunk() {
let mut chunk = AudioChunk::allocate(3); let mut chunk = AudioChunk::allocate(3);
let factory = || panic!("Factory should not be called when samples exactly fit"); let mut factory = || panic!("Factory should not be called when samples exactly fit");
let samples = vec![1.0, 2.0, 3.0]; let samples = vec![1.0, 2.0, 3.0];
let result = AudioChunk::write_samples(&mut chunk, &samples, factory); let result = AudioChunk::append_samples(&mut chunk, &samples, &mut factory);
assert!(result.is_ok()); assert!(result.is_ok());
assert_eq!(chunk.sample_count, 3); assert_eq!(chunk.sample_count, 3);
assert_eq!(chunk.samples[0], 1.0); assert_eq!(chunk.samples[0], 1.0);
@ -242,25 +261,23 @@ mod tests {
assert_eq!(chunk.samples[2], 3.0); assert_eq!(chunk.samples[2], 3.0);
assert!(chunk.next.is_none()); assert!(chunk.next.is_none());
} }
#[test] #[test]
fn test_write_samples_requires_new_chunk() { fn test_append_samples_requires_new_chunk() {
let mut chunk = AudioChunk::allocate(2); let mut chunk = AudioChunk::allocate(2);
use std::cell::RefCell; let mut factory = chunk_factory::mock::MockFactory::new(vec![AudioChunk::allocate(3)]);
let available_chunks = RefCell::new(vec![AudioChunk::allocate(3)]);
let factory = || available_chunks.borrow_mut().pop().ok_or(LooperError::ChunkAllocation);
let samples = vec![1.0, 2.0, 3.0, 4.0]; // 4 samples, first chunk holds 2 let samples = vec![1.0, 2.0, 3.0, 4.0]; // 4 samples, first chunk holds 2
let result = AudioChunk::write_samples(&mut chunk, &samples, factory); let result = AudioChunk::append_samples(&mut chunk, &samples, &mut factory);
assert!(result.is_ok()); assert!(result.is_ok());
// First chunk: 2 samples // First chunk: 2 samples
assert_eq!(chunk.sample_count, 2); assert_eq!(chunk.sample_count, 2);
assert_eq!(chunk.samples[0], 1.0); assert_eq!(chunk.samples[0], 1.0);
assert_eq!(chunk.samples[1], 2.0); assert_eq!(chunk.samples[1], 2.0);
// Second chunk: remaining 2 samples // Second chunk: remaining 2 samples
let chunk2 = chunk.next.as_ref().unwrap(); let chunk2 = chunk.next.as_ref().unwrap();
assert_eq!(chunk2.sample_count, 2); assert_eq!(chunk2.sample_count, 2);
@ -268,34 +285,32 @@ mod tests {
assert_eq!(chunk2.samples[1], 4.0); assert_eq!(chunk2.samples[1], 4.0);
assert!(chunk2.next.is_none()); assert!(chunk2.next.is_none());
} }
#[test] #[test]
fn test_write_samples_multiple_chunks_cascade() { fn test_append_samples_multiple_chunks_cascade() {
let mut chunk = AudioChunk::allocate(2); let mut chunk = AudioChunk::allocate(2);
use std::cell::RefCell; let mut factory = chunk_factory::mock::MockFactory::new(vec![
let available_chunks = RefCell::new(vec![
AudioChunk::allocate(2), AudioChunk::allocate(2),
AudioChunk::allocate(2), AudioChunk::allocate(2),
]); ]);
let factory = || available_chunks.borrow_mut().pop().ok_or(LooperError::ChunkAllocation);
let samples = vec![1.0, 2.0, 3.0, 4.0, 5.0, 6.0]; // 6 samples, each chunk holds 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::write_samples(&mut chunk, &samples, factory); let result = AudioChunk::append_samples(&mut chunk, &samples, &mut factory);
assert!(result.is_ok()); assert!(result.is_ok());
// First chunk // First chunk
assert_eq!(chunk.sample_count, 2); assert_eq!(chunk.sample_count, 2);
assert_eq!(chunk.samples[0], 1.0); assert_eq!(chunk.samples[0], 1.0);
assert_eq!(chunk.samples[1], 2.0); assert_eq!(chunk.samples[1], 2.0);
// Second chunk // Second chunk
let chunk2 = chunk.next.as_ref().unwrap(); let chunk2 = chunk.next.as_ref().unwrap();
assert_eq!(chunk2.sample_count, 2); assert_eq!(chunk2.sample_count, 2);
assert_eq!(chunk2.samples[0], 3.0); assert_eq!(chunk2.samples[0], 3.0);
assert_eq!(chunk2.samples[1], 4.0); assert_eq!(chunk2.samples[1], 4.0);
// Third chunk // Third chunk
let chunk3 = chunk2.next.as_ref().unwrap(); let chunk3 = chunk2.next.as_ref().unwrap();
assert_eq!(chunk3.sample_count, 2); assert_eq!(chunk3.sample_count, 2);
@ -303,42 +318,48 @@ mod tests {
assert_eq!(chunk3.samples[1], 6.0); assert_eq!(chunk3.samples[1], 6.0);
assert!(chunk3.next.is_none()); assert!(chunk3.next.is_none());
} }
#[test] #[test]
fn test_write_samples_factory_failure() { fn test_append_samples_factory_failure() {
let mut chunk = AudioChunk::allocate(2); let mut chunk = AudioChunk::allocate(2);
let factory = || Err(LooperError::ChunkAllocation); 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 samples = vec![1.0, 2.0, 3.0]; // 3 samples, chunk only holds 2
let result = AudioChunk::write_samples(&mut chunk, &samples, factory); let result = AudioChunk::append_samples(&mut chunk, &samples, &mut factory);
assert!(result.is_err()); assert!(result.is_err());
assert!(matches!(result.unwrap_err(), LooperError::ChunkAllocation)); assert!(matches!(
result.unwrap_err(),
LooperError::ChunkAllocation(_)
));
// First chunk should have been written before factory failure // First chunk should have been written before factory failure
assert_eq!(chunk.sample_count, 2); assert_eq!(chunk.sample_count, 2);
assert_eq!(chunk.samples[0], 1.0); assert_eq!(chunk.samples[0], 1.0);
assert_eq!(chunk.samples[1], 2.0); assert_eq!(chunk.samples[1], 2.0);
assert!(chunk.next.is_none()); assert!(chunk.next.is_none());
} }
#[test] #[test]
fn test_write_samples_ownership_failure() { fn test_append_samples_ownership_failure() {
let chunk = AudioChunk::allocate(3); let chunk = AudioChunk::allocate(3);
let chunk_clone = chunk.clone(); // Create another reference let chunk_clone = chunk.clone(); // Create another reference
let mut chunk_original = chunk; let mut chunk_original = chunk;
let factory = || panic!("Factory should not be called"); let mut factory = || panic!("Factory should not be called");
let samples = vec![1.0, 2.0]; let samples = vec![1.0, 2.0];
let result = AudioChunk::write_samples(&mut chunk_original, &samples, factory); let result = AudioChunk::append_samples(&mut chunk_original, &samples, &mut factory);
assert!(result.is_err()); assert!(result.is_err());
assert!(matches!(result.unwrap_err(), LooperError::ChunkOwnership)); assert!(matches!(
result.unwrap_err(),
LooperError::ChunkOwnership(_)
));
// Chunk should be unchanged // Chunk should be unchanged
assert_eq!(chunk_original.sample_count, 0); assert_eq!(chunk_original.sample_count, 0);
// Clean up the clone reference to avoid unused variable warning // Clean up the clone reference to avoid unused variable warning
drop(chunk_clone); drop(chunk_clone);
} }
} }

40
src/chunk_factory.rs Normal file
View File

@ -0,0 +1,40 @@
use crate::*;
/// Creates a new chunk of audio data.
pub trait ChunkFactory: Send {
fn create_chunk(&mut self) -> Result<std::sync::Arc<AudioChunk>>;
}
impl<F> ChunkFactory for F
where
F: FnMut() -> Result<std::sync::Arc<AudioChunk>> + Send,
{
fn create_chunk(&mut self) -> Result<std::sync::Arc<AudioChunk>> {
self()
}
}
#[cfg(test)]
pub mod mock {
use super::*;
pub struct MockFactory {
chunks: Vec<std::sync::Arc<AudioChunk>>,
}
impl MockFactory {
pub fn new(chunks: Vec<std::sync::Arc<AudioChunk>>) -> Self {
Self { chunks }
}
}
impl ChunkFactory for MockFactory {
fn create_chunk(&mut self) -> Result<std::sync::Arc<AudioChunk>> {
if let Some(chunk) = self.chunks.pop() {
Ok(chunk)
} else {
Err(LooperError::ChunkAllocation(std::panic::Location::caller()))
}
}
}
}

View File

@ -1,8 +1,13 @@
#[derive(Debug, thiserror::Error)] #[derive(Debug, thiserror::Error)]
pub enum LooperError { pub enum LooperError {
#[error("Failed to allocate new audio chunk")] #[error("Failed to allocate new audio chunk")]
ChunkAllocation, ChunkAllocation(&'static std::panic::Location<'static>),
#[error("Cannot modify chunk with multiple references")] #[error("Cannot modify chunk with multiple references")]
ChunkOwnership, ChunkOwnership(&'static std::panic::Location<'static>),
}
#[error("Index out of bounds")]
OutOfBounds(&'static std::panic::Location<'static>),
}
pub type Result<T> = std::result::Result<T, LooperError>;

View File

@ -1,30 +1,49 @@
mod audio_chunk;
mod looper_error;
mod allocator; mod allocator;
mod audio_chunk;
mod chunk_factory;
mod looper_error;
mod process_handler;
mod track;
use std::sync::Arc;
use audio_chunk::AudioChunk;
use looper_error::LooperError;
use allocator::Allocator; use allocator::Allocator;
use audio_chunk::AudioChunk;
use chunk_factory::ChunkFactory;
use looper_error::LooperError;
use looper_error::Result;
use process_handler::ProcessHandler;
use track::Track;
fn main() { #[tokio::main]
let (allocated_buffer_sender, mut allocated_buffer_receiver) = tokio::sync::mpsc::channel(1); async fn main() {
let allocator = Allocator::new(3, allocated_buffer_sender); let (allocator, factory_handle) = Allocator::spawn(5000, 100);
let mut factory = move || {
match allocated_buffer_receiver.try_recv() {
Ok(chunk) => Ok(chunk),
Err(_) => Err(LooperError::ChunkAllocation),
}
};
let handle = std::thread::spawn(move || { let (jack_client, jack_status) =
let mut chunk = factory().unwrap(); jack::Client::new("looper", jack::ClientOptions::NO_START_SERVER)
std::thread::sleep(std::time::Duration::from_millis(100)); .expect("Could not create Jack client");
let samples = vec![1.0, 2.0, 3.0, 4.0, 5.0, 6.0]; // 6 samples, each chunk holds 2 if !jack_status.is_empty() {
AudioChunk::write_samples(&mut chunk, &samples, factory).unwrap(); println!("Could not start jack client: {jack_status:?}");
AudioChunk::consolidate(&chunk); return;
}); }
let tokio_runtime = tokio::runtime::Builder::new_current_thread().build().unwrap(); let audio_in = jack_client
tokio_runtime.block_on(allocator.run()); .register_port("audio_in", jack::AudioIn::default())
handle.join().unwrap(); .expect("Could not create audio_in port");
} let audio_out = jack_client
.register_port("audio_out", jack::AudioOut::default())
.expect("Could not create audio_out port");
let process_handler = ProcessHandler::new(audio_in, audio_out, allocator)
.expect("Could not create process handler");
let async_client = jack_client
.activate_async((), process_handler)
.expect("Could not activate Jack");
factory_handle.await.unwrap();
async_client
.deactivate()
.expect("Could not deactivate Jack");
}

80
src/process_handler.rs Normal file
View File

@ -0,0 +1,80 @@
use crate::*;
pub struct ProcessHandler<F: ChunkFactory> {
track: Track,
playback_position: usize,
input_port: jack::Port<jack::AudioIn>,
output_port: jack::Port<jack::AudioOut>,
chunk_factory: F,
}
impl<F: ChunkFactory> ProcessHandler<F> {
pub fn new(
input_port: jack::Port<jack::AudioIn>,
output_port: jack::Port<jack::AudioOut>,
mut chunk_factory: F,
) -> Result<Self> {
Ok(Self {
track: Track::new(&mut chunk_factory)?,
playback_position: 0,
input_port,
output_port,
chunk_factory,
})
}
}
impl<F: ChunkFactory> jack::ProcessHandler for ProcessHandler<F> {
fn process(&mut self, client: &jack::Client, ps: &jack::ProcessScope) -> jack::Control {
let input_buffer = self.input_port.as_slice(ps);
let output_buffer = self.output_port.as_mut_slice(ps);
let jack_buffer_size = client.buffer_size() as usize;
let recording_samples = client.sample_rate() * 5; // 5 seconds
let mut index = 0;
while index < jack_buffer_size {
if self.track.len() < recording_samples {
// recording
let sample_count_to_append = jack_buffer_size - index;
let sample_count_to_append =
sample_count_to_append.min(recording_samples - self.track.len());
let samples_to_append = &input_buffer[index..index + sample_count_to_append];
if self
.track
.append_samples(samples_to_append, &mut self.chunk_factory)
.is_err()
{
return jack::Control::Quit;
};
output_buffer[index..(index + sample_count_to_append)].fill(0.0);
index += sample_count_to_append;
} else {
// playback
let sample_count_to_play = jack_buffer_size - index;
let sample_count_to_play =
sample_count_to_play.min(self.track.len() - self.playback_position);
if self
.track
.copy_samples(
&mut output_buffer[index..(index + sample_count_to_play)],
self.playback_position,
)
.is_err()
{
return jack::Control::Quit;
}
index += sample_count_to_play;
self.playback_position += sample_count_to_play;
if self.playback_position >= self.track.len() {
self.playback_position = 0;
if self.track.clear(&mut self.chunk_factory).is_err() {
return jack::Control::Quit;
}
}
}
}
jack::Control::Continue
}
}

39
src/track.rs Normal file
View File

@ -0,0 +1,39 @@
use crate::*;
pub struct Track {
audio_chunks: Arc<AudioChunk>,
length: usize,
}
impl Track {
pub fn new<F: ChunkFactory>(chunk_factory: &mut F) -> Result<Self> {
Ok(Self {
audio_chunks: chunk_factory.create_chunk()?,
length: 0,
})
}
pub fn append_samples<F: ChunkFactory>(
&mut self,
samples: &[f32],
chunk_factory: &mut F,
) -> Result<()> {
self.audio_chunks.append_samples(samples, chunk_factory)?;
self.length += samples.len();
Ok(())
}
pub fn len(&self) -> usize {
self.length
}
pub fn clear<F: ChunkFactory>(&mut self, chunk_factory: &mut F) -> Result<()> {
self.audio_chunks = chunk_factory.create_chunk()?;
self.length = 0;
Ok(())
}
pub fn copy_samples(&self, dest: &mut [f32], start: usize) -> Result<()> {
self.audio_chunks.copy_samples(dest, start)
}
}