diff --git a/CHANGELOG.md b/CHANGELOG.md index 87e30a3..a044a15 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,10 @@ # Version History +## Version 1.2.2 (2024-1-5) + +- Fixed clippy warnings +- Added panic documentation + ## Version 1.2.1 (2024-1-3) - Improved performance when decoding near the end of a file diff --git a/Cargo.toml b/Cargo.toml index 041c99d..926f1cf 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "creek" -version = "1.2.1" +version = "1.2.2" authors = ["Billy Messenger "] edition = "2021" license = "MIT OR Apache-2.0" @@ -57,7 +57,7 @@ decode-all = [ encode-wav = ["creek-encode-wav"] [dependencies] -creek-core = { version = "0.2.1", path = "core" } +creek-core = { version = "0.2.2", path = "core" } creek-decode-symphonia = { version = "0.3.1", path = "decode_symphonia", optional = true } creek-encode-wav = { version = "0.2.0", path = "encode_wav", optional = true } diff --git a/core/Cargo.toml b/core/Cargo.toml index 5c6ef82..e4f25a0 100644 --- a/core/Cargo.toml +++ b/core/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "creek-core" -version = "0.2.1" +version = "0.2.2" authors = ["Billy Messenger "] edition = "2021" license = "MIT OR Apache-2.0" diff --git a/core/src/lib.rs b/core/src/lib.rs index 1f5676c..da8e14d 100644 --- a/core/src/lib.rs +++ b/core/src/lib.rs @@ -1,6 +1,6 @@ #![warn(rust_2018_idioms)] #![warn(rust_2021_compatibility)] -// TODO: #![warn(clippy::missing_panics_doc)] +#![warn(clippy::missing_panics_doc)] #![warn(clippy::clone_on_ref_ptr)] #![deny(trivial_numeric_casts)] #![forbid(unsafe_code)] diff --git a/core/src/read/read_stream.rs b/core/src/read/read_stream.rs index 7cdca03..c4e480a 100644 --- a/core/src/read/read_stream.rs +++ b/core/src/read/read_stream.rs @@ -7,6 +7,7 @@ use super::{ ClientToServerMsg, DataBlock, Decoder, HeapData, ReadData, ReadServer, ReadStreamOptions, ServerToClientMsg, }; +use crate::read::server::ReadServerOptions; use crate::{FileInfo, SERVER_WAIT_TIME}; /// Describes how to search for suitable caches when seeking in a [`ReadDiskStream`]. @@ -34,6 +35,15 @@ pub enum SeekMode { NoCache, } +struct ReadDiskStreamOptions { + start_frame: usize, + num_cache_blocks: usize, + num_look_ahead_blocks: usize, + max_num_caches: usize, + block_size: usize, + file_info: FileInfo, +} + /// A realtime-safe disk-streaming reader of audio files. pub struct ReadDiskStream { to_server_tx: Producer>, @@ -65,21 +75,32 @@ impl ReadDiskStream { /// * `file` - The path to the file to open. /// * `start_frame` - The frame in the file to start reading from. /// * `stream_opts` - Additional stream options. + /// + /// # Panics + /// + /// This will panic if `stream_block_size`, `stream_num_look_ahead_blocks`, + /// or `stream_server_msg_channel_size` is `0`. pub fn new>( file: P, start_frame: usize, stream_opts: ReadStreamOptions, ) -> Result, D::OpenError> { - assert_ne!(stream_opts.block_size, 0); - assert_ne!(stream_opts.num_look_ahead_blocks, 0); - assert_ne!(stream_opts.server_msg_channel_size, Some(0)); + let ReadStreamOptions { + num_cache_blocks, + num_caches, + additional_opts, + num_look_ahead_blocks, + block_size, + server_msg_channel_size, + } = stream_opts; + + assert_ne!(block_size, 0); + assert_ne!(num_look_ahead_blocks, 0); + assert_ne!(server_msg_channel_size, Some(0)); // Reserve ample space for the message channels. - let msg_channel_size = stream_opts.server_msg_channel_size.unwrap_or( - ((stream_opts.num_cache_blocks + stream_opts.num_look_ahead_blocks) * 4) - + (stream_opts.num_caches * 4) - + 8, - ); + let msg_channel_size = server_msg_channel_size + .unwrap_or(((num_cache_blocks + num_look_ahead_blocks) * 4) + (num_caches * 4) + 8); let (to_server_tx, from_client_rx) = RingBuffer::>::new(msg_channel_size); @@ -91,27 +112,31 @@ impl ReadDiskStream { let file: PathBuf = file.into(); - match ReadServer::new( - file, - start_frame, - stream_opts.num_cache_blocks + stream_opts.num_look_ahead_blocks, - stream_opts.block_size, + match ReadServer::spawn( + ReadServerOptions { + file, + start_frame, + num_prefetch_blocks: num_cache_blocks + num_look_ahead_blocks, + block_size, + additional_opts, + }, to_client_tx, from_client_rx, close_signal_rx, - stream_opts.additional_opts, ) { Ok(file_info) => { let client = ReadDiskStream::create( + ReadDiskStreamOptions { + start_frame, + num_cache_blocks, + num_look_ahead_blocks, + max_num_caches: num_caches, + block_size, + file_info, + }, to_server_tx, from_server_rx, close_signal_tx, - start_frame, - stream_opts.num_cache_blocks, - stream_opts.num_look_ahead_blocks, - stream_opts.num_caches, - stream_opts.block_size, - file_info, ); Ok(client) @@ -120,18 +145,21 @@ impl ReadDiskStream { } } - #[allow(clippy::too_many_arguments)] // TODO: Reduce number of arguments - pub(crate) fn create( + fn create( + opts: ReadDiskStreamOptions, to_server_tx: Producer>, from_server_rx: Consumer>, close_signal_tx: Producer>>, - start_frame: usize, - num_cache_blocks: usize, - num_look_ahead_blocks: usize, - max_num_caches: usize, - block_size: usize, - file_info: FileInfo, ) -> Self { + let ReadDiskStreamOptions { + start_frame, + num_cache_blocks, + num_look_ahead_blocks, + max_num_caches, + block_size, + file_info, + } = opts; + let num_prefetch_blocks = num_cache_blocks + num_look_ahead_blocks; let read_buffer = DataBlock::new(usize::from(file_info.num_channels), block_size); @@ -219,8 +247,10 @@ impl ReadDiskStream { /// relied on, then any blocks relying on the oldest cache will be silenced. In this case, (false) /// will be returned. pub fn can_move_cache(&mut self, cache_index: usize) -> bool { - // This check should never fail because it can only be `None` in the destructor. - let heap = self.heap_data.as_ref().unwrap(); + let Some(heap) = self.heap_data.as_ref() else { + // This will never return here because `heap_data` can only be `None` in the destructor. + return false; + }; let mut using_cache = false; let mut using_temp_cache = false; @@ -266,8 +296,10 @@ impl ReadDiskStream { return Err(ReadError::FatalError(FatalReadError::StreamClosed)); } - // This check should never fail because it can only be `None` in the destructor. - let heap = self.heap_data.as_mut().unwrap(); + let Some(heap) = self.heap_data.as_mut() else { + // This will never return here because `heap_data` can only be `None` in the destructor. + return Ok(false); + }; if cache_index >= heap.caches.len() - 2 { return Err(ReadError::CacheIndexOutOfRange { @@ -373,8 +405,10 @@ impl ReadDiskStream { return Err(ReadError::IOServerChannelFull); } - // This check should never fail because it can only be `None` in the destructor. - let heap = self.heap_data.as_mut().unwrap(); + let Some(heap) = self.heap_data.as_mut() else { + // This will never return here because `heap_data` can only be `None` in the destructor. + return Ok(false); + }; let mut found_cache = None; @@ -509,8 +543,10 @@ impl ReadDiskStream { return Ok(false); } - // This check should never fail because it can only be `None` in the destructor. - let heap = self.heap_data.as_ref().unwrap(); + let Some(heap) = self.heap_data.as_mut() else { + // This will never return here because `heap_data` can only be `None` in the destructor. + return Ok(false); + }; // Check if the next two blocks are ready. @@ -631,8 +667,10 @@ impl ReadDiskStream { // Retrieve any data sent from the server. - // This check should never fail because it can only be `None` in the destructor. - let heap = self.heap_data.as_mut().unwrap(); + let Some(heap) = self.heap_data.as_mut() else { + // This will never return here because `heap_data` can only be `None` in the destructor. + return Ok(()); + }; loop { // Check that there is at-least one slot open before popping the next message. @@ -765,8 +803,10 @@ impl ReadDiskStream { // Copy from first block. { - // This check should never fail because it can only be `None` in the destructor. - let heap = self.heap_data.as_mut().unwrap(); + let Some(heap) = self.heap_data.as_mut() else { + // This will never return here because `heap_data` can only be `None` in the destructor. + return Err(ReadError::IOServerChannelFull); + }; heap.read_buffer.clear(); @@ -782,8 +822,10 @@ impl ReadDiskStream { // Copy from second block { - // This check should never fail because it can only be `None` in the destructor. - let heap = self.heap_data.as_mut().unwrap(); + let Some(heap) = self.heap_data.as_mut() else { + // This will never return here because `heap_data` can only be `None` in the destructor. + return Err(ReadError::IOServerChannelFull); + }; copy_block_into_read_buffer(heap, self.current_block_index, 0, second_len); } @@ -792,8 +834,10 @@ impl ReadDiskStream { } else { // Only need to copy from current block. { - // This check should never fail because it can only be `None` in the destructor. - let heap = self.heap_data.as_mut().unwrap(); + let Some(heap) = self.heap_data.as_mut() else { + // This will never return here because `heap_data` can only be `None` in the destructor. + return Err(ReadError::IOServerChannelFull); + }; heap.read_buffer.clear(); @@ -812,8 +856,10 @@ impl ReadDiskStream { } } - // This check should never fail because it can only be `None` in the destructor. - let heap = self.heap_data.as_mut().unwrap(); + let Some(heap) = self.heap_data.as_mut() else { + // This will never return here because `heap_data` can only be `None` in the destructor. + return Err(ReadError::IOServerChannelFull); + }; // This check should never fail because it can only be `None` in the destructor. Ok(ReadData::new( @@ -824,8 +870,10 @@ impl ReadDiskStream { } fn advance_to_next_block(&mut self) -> Result<(), ReadError> { - // This check should never fail because it can only be `None` in the destructor. - let heap = self.heap_data.as_mut().unwrap(); + let Some(heap) = self.heap_data.as_mut() else { + // This will never return here because `heap_data` can only be `None` in the destructor. + return Ok(()); + }; let entry = &mut heap.prefetch_buffer[self.current_block_index]; diff --git a/core/src/read/server.rs b/core/src/read/server.rs index f6fd987..b3b354d 100644 --- a/core/src/read/server.rs +++ b/core/src/read/server.rs @@ -6,6 +6,14 @@ use crate::{FileInfo, SERVER_WAIT_TIME}; use super::{ClientToServerMsg, DataBlock, DataBlockCache, Decoder, HeapData, ServerToClientMsg}; +pub(crate) struct ReadServerOptions { + pub file: PathBuf, + pub start_frame: usize, + pub num_prefetch_blocks: usize, + pub block_size: usize, + pub additional_opts: D::AdditionalOpts, +} + pub(crate) struct ReadServer { to_client_tx: Producer>, from_client_rx: Consumer>, @@ -25,18 +33,20 @@ pub(crate) struct ReadServer { } impl ReadServer { - #[allow(clippy::new_ret_no_self)] // TODO: Rename to `spawn` (breaking API change) - #[allow(clippy::too_many_arguments)] // TODO: Reduce number of arguments - pub(crate) fn new( - file: PathBuf, - start_frame: usize, - num_prefetch_blocks: usize, - block_size: usize, + pub(crate) fn spawn( + opts: ReadServerOptions, to_client_tx: Producer>, from_client_rx: Consumer>, close_signal_rx: Consumer>>, - additional_opts: D::AdditionalOpts, ) -> Result, D::OpenError> { + let ReadServerOptions { + file, + start_frame, + num_prefetch_blocks, + block_size, + additional_opts, + } = opts; + let (mut open_tx, mut open_rx) = RingBuffer::, D::OpenError>>::new(1); @@ -79,8 +89,13 @@ impl ReadServer { } fn run(mut self) { - #[allow(clippy::type_complexity)] // TODO: Use a dedicated type for the elements - let mut cache_requests: Vec<(usize, Option>, usize)> = Vec::new(); + struct CacheRequest { + cache_index: usize, + cache: Option>, + start_frame: usize, + } + + let mut cache_requests: Vec> = Vec::new(); while self.run { let mut do_sleep = true; @@ -147,7 +162,11 @@ impl ReadServer { start_frame, } => { // Prioritize read blocks over caching. - cache_requests.push((cache_index, cache, start_frame)); + cache_requests.push(CacheRequest { + cache_index, + cache, + start_frame, + }); } ClientToServerMsg::DisposeCache { cache } => { // Store the cache to be reused. @@ -156,8 +175,8 @@ impl ReadServer { } } - while let Some((cache_index, cache, start_frame)) = cache_requests.pop() { - let mut cache = cache.unwrap_or( + while let Some(request) = cache_requests.pop() { + let mut cache = request.cache.unwrap_or( // Try using one in the pool if it exists. self.cache_pool.pop().unwrap_or( // No caches in pool. Create a new one. @@ -172,7 +191,7 @@ impl ReadServer { let current_frame = self.decoder.current_frame(); // Seek to the position the client wants to cache. - if let Err(e) = self.decoder.seek(start_frame) { + if let Err(e) = self.decoder.seek(request.start_frame) { self.send_msg(ServerToClientMsg::FatalError(e)); self.run = false; do_sleep = false; @@ -202,9 +221,9 @@ impl ReadServer { } self.send_msg(ServerToClientMsg::CacheRes { - cache_index, + cache_index: request.cache_index, cache, - wanted_start_frame: start_frame, + wanted_start_frame: request.start_frame, }); // If any new messages have been received while caching, prioritize those diff --git a/core/src/write/server.rs b/core/src/write/server.rs index 5698376..744e5c6 100644 --- a/core/src/write/server.rs +++ b/core/src/write/server.rs @@ -6,6 +6,15 @@ use crate::{FileInfo, SERVER_WAIT_TIME}; use super::{ClientToServerMsg, Encoder, HeapData, ServerToClientMsg, WriteStatus}; +pub(crate) struct WriteServerOptions { + pub file: PathBuf, + pub num_write_blocks: usize, + pub block_size: usize, + pub num_channels: u16, + pub sample_rate: u32, + pub additional_opts: E::AdditionalOpts, +} + pub(crate) struct WriteServer { to_client_tx: Producer>, from_client_rx: Consumer>, @@ -22,19 +31,21 @@ pub(crate) struct WriteServer { } impl WriteServer { - #[allow(clippy::new_ret_no_self)] // TODO: Rename to `spawn` (breaking API change) - #[allow(clippy::too_many_arguments)] // TODO: Reduce number of arguments - pub fn new( - file: PathBuf, - num_write_blocks: usize, - block_size: usize, - num_channels: u16, - sample_rate: u32, + pub(crate) fn spawn( + opts: WriteServerOptions, to_client_tx: Producer>, from_client_rx: Consumer>, close_signal_rx: Consumer>>, - additional_opts: E::AdditionalOpts, ) -> Result, E::OpenError> { + let WriteServerOptions { + file, + num_write_blocks, + block_size, + num_channels, + sample_rate, + additional_opts, + } = opts; + let (mut open_tx, mut open_rx) = RingBuffer::, E::OpenError>>::new(1); diff --git a/core/src/write/write_stream.rs b/core/src/write/write_stream.rs index 1e989e2..567d370 100644 --- a/core/src/write/write_stream.rs +++ b/core/src/write/write_stream.rs @@ -6,6 +6,7 @@ use super::{ ClientToServerMsg, Encoder, HeapData, ServerToClientMsg, WriteBlock, WriteServer, WriteStreamOptions, }; +use crate::write::server::WriteServerOptions; use crate::{FileInfo, SERVER_WAIT_TIME}; /// A realtime-safe disk-streaming writer of audio files. @@ -34,22 +35,34 @@ impl WriteDiskStream { /// * `num_channels` - The number of channels in the file. /// * `sample_rate` - The sample rate of the file. /// * `stream_opts` - Additional stream options. + /// + /// # Panics + /// + /// This will panic if `num_channels`, `sample_rate`, `stream_opts.block_size`, + /// `stream_opts.num_write_blocks`, or `stream_opts.server_msg_channel_size` is `0`. pub fn new>( file: P, num_channels: u16, sample_rate: u32, stream_opts: WriteStreamOptions, ) -> Result, E::OpenError> { + let WriteStreamOptions { + additional_opts, + num_write_blocks, + block_size, + server_msg_channel_size, + } = stream_opts; + assert_ne!(num_channels, 0); assert_ne!(sample_rate, 0); - assert_ne!(stream_opts.block_size, 0); - assert_ne!(stream_opts.num_write_blocks, 0); - assert_ne!(stream_opts.server_msg_channel_size, Some(0)); + assert_ne!(block_size, 0); + assert_ne!(num_write_blocks, 0); + assert_ne!(server_msg_channel_size, Some(0)); // Reserve ample space for the message channels. let msg_channel_size = stream_opts .server_msg_channel_size - .unwrap_or((stream_opts.num_write_blocks * 4) + 8); + .unwrap_or((num_write_blocks * 4) + 8); let (to_server_tx, from_client_rx) = RingBuffer::>::new(msg_channel_size); @@ -61,24 +74,26 @@ impl WriteDiskStream { let file: PathBuf = file.into(); - match WriteServer::new( - file, - stream_opts.num_write_blocks, - stream_opts.block_size, - num_channels, - sample_rate, + match WriteServer::spawn( + WriteServerOptions { + file, + num_write_blocks, + block_size, + num_channels, + sample_rate, + additional_opts, + }, to_client_tx, from_client_rx, close_signal_rx, - stream_opts.additional_opts, ) { Ok(file_info) => { let client = WriteDiskStream::create( to_server_tx, from_server_rx, close_signal_tx, - stream_opts.num_write_blocks, - stream_opts.block_size, + num_write_blocks, + block_size, file_info, ); @@ -146,8 +161,10 @@ impl WriteDiskStream { self.poll()?; - // This check should never fail because it can only be `None` in the destructor. - let heap = self.heap_data.as_ref().unwrap(); + let Some(heap) = self.heap_data.as_mut() else { + // This will never return here because `heap_data` can only be `None` in the destructor. + return Ok(false); + }; Ok(heap.current_block.is_some() && heap.next_block.is_some() @@ -212,8 +229,11 @@ impl WriteDiskStream { return Err(WriteError::IOServerChannelFull); } - // This check should never fail because it can only be `None` in the destructor. - let heap = self.heap_data.as_mut().unwrap(); + let Some(heap) = self.heap_data.as_mut() else { + // This will never return here because `heap_data` can only be `None` + // in the destructor. + return Ok(()); + }; // Check that there are available blocks to write to. if let Some(mut current_block) = heap.current_block.take() { @@ -308,8 +328,11 @@ impl WriteDiskStream { self.finished = true; { - // This check should never fail because it can only be `None` in the destructor. - let heap = self.heap_data.as_mut().unwrap(); + let Some(heap) = self.heap_data.as_mut() else { + // This will never return here because `heap_data` can only be `None` + // in the destructor. + return Ok(()); + }; if let Some(mut current_block) = heap.current_block.take() { if !current_block.block[0].is_empty() { @@ -393,8 +416,11 @@ impl WriteDiskStream { // a previous step. let _ = self.to_server_tx.push(ClientToServerMsg::DiscardAndRestart); - // This check should never fail because it can only be `None` in the destructor. - let heap = self.heap_data.as_mut().unwrap(); + let Some(heap) = self.heap_data.as_mut() else { + // This will never return here because `heap_data` can only be `None` + // in the destructor. + return Ok(()); + }; if let Some(block) = &mut heap.current_block { block.clear(); @@ -417,8 +443,11 @@ impl WriteDiskStream { // Retrieve any data sent from the server. - // This check should never fail because it can only be `None` in the destructor. - let heap = self.heap_data.as_mut().unwrap(); + let Some(heap) = self.heap_data.as_mut() else { + // This will never return here because `heap_data` can only be `None` + // in the destructor. + return Ok(()); + }; while let Ok(msg) = self.from_server_rx.pop() { match msg { diff --git a/decode_symphonia/Cargo.toml b/decode_symphonia/Cargo.toml index cc9b816..e420fe0 100644 --- a/decode_symphonia/Cargo.toml +++ b/decode_symphonia/Cargo.toml @@ -14,7 +14,7 @@ repository = "https://github.com/RustyDAW/creek" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -creek-core = { version = "0.2.1", path = "../core" } +creek-core = { version = "0.2.2", path = "../core" } log = "0.4" symphonia = "0.5" diff --git a/demos/player/src/main.rs b/demos/player/src/main.rs index 6605b0f..9e69f64 100644 --- a/demos/player/src/main.rs +++ b/demos/player/src/main.rs @@ -5,9 +5,12 @@ mod output; mod process; mod ui; -#[allow(clippy::large_enum_variant)] pub enum GuiToProcessMsg { - UseStream(ReadDiskStream), + // Note, you could opt to not wrap the stream struct inside of a `Box` and + // not have to worry about sending it back to be deallocated. Doing so may + // also sometimes perform a bit better by avoiding a pointer dereference. + // But doing so will increase the size of the enum by quite a bit. + UseStream(Box>), SetLoop { start: usize, end: usize }, PlayResume, Pause, @@ -19,6 +22,7 @@ pub enum GuiToProcessMsg { pub enum ProcessToGuiMsg { PlaybackPos(usize), Buffering, + DropOldStream(Box>), } fn main() { @@ -30,7 +34,7 @@ fn main() { let (to_gui_tx, from_process_rx) = RingBuffer::::new(256); let (to_process_tx, from_gui_rx) = RingBuffer::::new(64); - let _cpal_stream = output::Output::new(to_gui_tx, from_gui_rx); + let _cpal_stream = output::spawn_cpal_stream(to_gui_tx, from_gui_rx); eframe::run_native( "creek demo player", diff --git a/demos/player/src/output.rs b/demos/player/src/output.rs index 7c58a5c..4464e2f 100644 --- a/demos/player/src/output.rs +++ b/demos/player/src/output.rs @@ -4,45 +4,40 @@ use rtrb::{Consumer, Producer}; use crate::process::Process; use crate::{GuiToProcessMsg, ProcessToGuiMsg}; -pub struct Output; - -impl Output { - #[allow(clippy::new_ret_no_self)] // TODO: Rename? - pub fn new( - to_gui_tx: Producer, - from_gui_rx: Consumer, - ) -> cpal::Stream { - // Setup cpal audio output - - let host = cpal::default_host(); - - let device = host - .default_output_device() - .expect("no output device available"); - - let sample_rate = device.default_output_config().unwrap().sample_rate(); - - let config = cpal::StreamConfig { - channels: 2, - sample_rate, - buffer_size: cpal::BufferSize::Default, - }; - - let mut process = Process::new(to_gui_tx, from_gui_rx); - - let stream = device - .build_output_stream( - &config, - move |data: &mut [f32], _: &cpal::OutputCallbackInfo| process.process(data), - move |err| { - eprintln!("{}", err); - }, - None, - ) - .unwrap(); - - stream.play().unwrap(); - - stream - } +pub fn spawn_cpal_stream( + to_gui_tx: Producer, + from_gui_rx: Consumer, +) -> cpal::Stream { + // Setup cpal audio output + + let host = cpal::default_host(); + + let device = host + .default_output_device() + .expect("no output device available"); + + let sample_rate = device.default_output_config().unwrap().sample_rate(); + + let config = cpal::StreamConfig { + channels: 2, + sample_rate, + buffer_size: cpal::BufferSize::Default, + }; + + let mut process = Process::new(to_gui_tx, from_gui_rx); + + let stream = device + .build_output_stream( + &config, + move |data: &mut [f32], _: &cpal::OutputCallbackInfo| process.process(data), + move |err| { + eprintln!("{}", err); + }, + None, + ) + .unwrap(); + + stream.play().unwrap(); + + stream } diff --git a/demos/player/src/process.rs b/demos/player/src/process.rs index f4e5780..8eda6d7 100644 --- a/demos/player/src/process.rs +++ b/demos/player/src/process.rs @@ -11,7 +11,7 @@ pub enum PlaybackState { } pub struct Process { - read_disk_stream: Option>, + read_disk_stream: Option>>, to_gui_tx: Producer, from_gui_rx: Consumer, @@ -72,6 +72,14 @@ impl Process { self.playback_state = PlaybackState::Paused; self.loop_start = 0; self.loop_end = 0; + + if let Some(old_stream) = self.read_disk_stream.take() { + // Send the old stream to be deallocated on a different thread. + let _ = self + .to_gui_tx + .push(ProcessToGuiMsg::DropOldStream(old_stream)); + } + self.read_disk_stream = Some(read_disk_stream); } GuiToProcessMsg::SetLoop { start, end } => { @@ -225,6 +233,14 @@ impl Process { } } +impl Drop for Process { + fn drop(&mut self) { + if let Some(stream) = self.read_disk_stream.take() { + let _ = self.to_gui_tx.push(ProcessToGuiMsg::DropOldStream(stream)); + } + } +} + fn silence(data: &mut [f32]) { for sample in data.iter_mut() { *sample = 0.0; diff --git a/demos/player/src/ui.rs b/demos/player/src/ui.rs index 03ca9ca..9ce37da 100644 --- a/demos/player/src/ui.rs +++ b/demos/player/src/ui.rs @@ -72,7 +72,7 @@ impl DemoPlayerApp { let num_frames = read_stream.info().num_frames; to_player_tx - .push(GuiToProcessMsg::UseStream(read_stream)) + .push(GuiToProcessMsg::UseStream(Box::new(read_stream))) .unwrap(); let loop_start = 0; @@ -114,6 +114,7 @@ impl eframe::App for DemoPlayerApp { ProcessToGuiMsg::Buffering => { self.buffering_anim = BUFFERING_FADEOUT_FRAMES; } + ProcessToGuiMsg::DropOldStream(_) => {} } } diff --git a/demos/writer/src/main.rs b/demos/writer/src/main.rs index d08127a..e1ec8b3 100644 --- a/demos/writer/src/main.rs +++ b/demos/writer/src/main.rs @@ -27,7 +27,7 @@ fn main() { let (to_gui_tx, from_process_rx) = RingBuffer::::new(256); let (to_process_tx, from_gui_rx) = RingBuffer::::new(64); - let (_cpal_stream, sample_rate) = output::Output::new(to_gui_tx, from_gui_rx); + let (_cpal_stream, sample_rate) = output::spawn_cpal_stream(to_gui_tx, from_gui_rx); eframe::run_native( "creek demo writer", diff --git a/demos/writer/src/output.rs b/demos/writer/src/output.rs index 9e66df6..fe05579 100644 --- a/demos/writer/src/output.rs +++ b/demos/writer/src/output.rs @@ -4,45 +4,40 @@ use rtrb::{Consumer, Producer}; use crate::process::Process; use crate::{GuiToProcessMsg, ProcessToGuiMsg}; -pub struct Output; - -impl Output { - #[allow(clippy::new_ret_no_self)] // TODO: Rename? - pub fn new( - to_gui_tx: Producer, - from_gui_rx: Consumer, - ) -> (cpal::Stream, u32) { - // Setup cpal audio output - - let host = cpal::default_host(); - - let device = host - .default_output_device() - .expect("no output device available"); - - let sample_rate = device.default_output_config().unwrap().sample_rate(); - - let config = cpal::StreamConfig { - channels: 2, - sample_rate, - buffer_size: cpal::BufferSize::Default, - }; - - let mut process = Process::new(to_gui_tx, from_gui_rx, sample_rate.0); - - let stream = device - .build_output_stream( - &config, - move |data: &mut [f32], _: &cpal::OutputCallbackInfo| process.process(data), - move |err| { - eprintln!("{}", err); - }, - None, - ) - .unwrap(); - - stream.play().unwrap(); - - (stream, sample_rate.0) - } +pub fn spawn_cpal_stream( + to_gui_tx: Producer, + from_gui_rx: Consumer, +) -> (cpal::Stream, u32) { + // Setup cpal audio output + + let host = cpal::default_host(); + + let device = host + .default_output_device() + .expect("no output device available"); + + let sample_rate = device.default_output_config().unwrap().sample_rate(); + + let config = cpal::StreamConfig { + channels: 2, + sample_rate, + buffer_size: cpal::BufferSize::Default, + }; + + let mut process = Process::new(to_gui_tx, from_gui_rx, sample_rate.0); + + let stream = device + .build_output_stream( + &config, + move |data: &mut [f32], _: &cpal::OutputCallbackInfo| process.process(data), + move |err| { + eprintln!("{}", err); + }, + None, + ) + .unwrap(); + + stream.play().unwrap(); + + (stream, sample_rate.0) } diff --git a/encode_wav/Cargo.toml b/encode_wav/Cargo.toml index 29a4f5a..3642325 100644 --- a/encode_wav/Cargo.toml +++ b/encode_wav/Cargo.toml @@ -13,5 +13,5 @@ repository = "https://github.com/RustyDAW/creek" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -creek-core = { version = "0.2.1", path = "../core" } +creek-core = { version = "0.2.2", path = "../core" } byte-slice-cast = "1.0.0"