//! A simple source of samples coming from a buffer. //! //! The `SamplesBuffer` struct can be used to treat a list of values as a `Source`. //! //! # Example //! //! ``` //! use rodio::buffer::SamplesBuffer; //! let _ = SamplesBuffer::new(1, 44100, vec![1i16, 2, 3, 4, 5, 6]); //! ``` //! use std::time::Duration; use crate::source::SeekError; use crate::{Sample, Source}; /// A buffer of samples treated as a source. #[derive(Debug, Clone)] pub struct SamplesBuffer { data: Vec, pos: usize, channels: u16, sample_rate: u32, duration: Duration, } impl SamplesBuffer where S: Sample, { /// Builds a new `SamplesBuffer`. /// /// # Panic /// /// - Panics if the number of channels is zero. /// - Panics if the samples rate is zero. /// - Panics if the length of the buffer is larger than approximately 16 billion elements. /// This is because the calculation of the duration would overflow. /// pub fn new(channels: u16, sample_rate: u32, data: D) -> SamplesBuffer where D: Into>, { assert!(channels != 0); assert!(sample_rate != 0); let data = data.into(); let duration_ns = 1_000_000_000u64.checked_mul(data.len() as u64).unwrap() / sample_rate as u64 / channels as u64; let duration = Duration::new( duration_ns / 1_000_000_000, (duration_ns % 1_000_000_000) as u32, ); SamplesBuffer { data, pos: 0, channels, sample_rate, duration, } } } impl Source for SamplesBuffer where S: Sample, { #[inline] fn current_frame_len(&self) -> Option { None } #[inline] fn channels(&self) -> u16 { self.channels } #[inline] fn sample_rate(&self) -> u32 { self.sample_rate } #[inline] fn total_duration(&self) -> Option { Some(self.duration) } // this is fast because all the samples are in memory already // and due to the constant sample_rate we can jump to the right // sample directly // /// This jumps in memory till the sample for `pos`. #[inline] fn try_seek(&mut self, pos: Duration) -> Result<(), SeekError> { let curr_channel = self.pos % self.channels() as usize; let new_pos = pos.as_secs_f32() * self.sample_rate() as f32 * self.channels() as f32; // saturate pos at the end of the source let new_pos = new_pos as usize; let new_pos = new_pos.min(self.data.len()); // make sure the next sample is for the right channel let new_pos = new_pos.next_multiple_of(self.channels() as usize); let new_pos = new_pos - curr_channel; self.pos = new_pos; Ok(()) } } impl Iterator for SamplesBuffer where S: Sample, { type Item = S; #[inline] fn next(&mut self) -> Option { let sample = self.data.get(self.pos)?; self.pos += 1; Some(*sample) } #[inline] fn size_hint(&self) -> (usize, Option) { (self.data.len(), Some(self.data.len())) } } #[cfg(test)] mod tests { use crate::buffer::SamplesBuffer; use crate::source::Source; #[test] fn basic() { let _ = SamplesBuffer::new(1, 44100, vec![0i16, 0, 0, 0, 0, 0]); } #[test] #[should_panic] fn panic_if_zero_channels() { SamplesBuffer::new(0, 44100, vec![0i16, 0, 0, 0, 0, 0]); } #[test] #[should_panic] fn panic_if_zero_sample_rate() { SamplesBuffer::new(1, 0, vec![0i16, 0, 0, 0, 0, 0]); } #[test] fn duration_basic() { let buf = SamplesBuffer::new(2, 2, vec![0i16, 0, 0, 0, 0, 0]); let dur = buf.total_duration().unwrap(); assert_eq!(dur.as_secs(), 1); assert_eq!(dur.subsec_nanos(), 500_000_000); } #[test] fn iteration() { let mut buf = SamplesBuffer::new(1, 44100, vec![1i16, 2, 3, 4, 5, 6]); assert_eq!(buf.next(), Some(1)); assert_eq!(buf.next(), Some(2)); assert_eq!(buf.next(), Some(3)); assert_eq!(buf.next(), Some(4)); assert_eq!(buf.next(), Some(5)); assert_eq!(buf.next(), Some(6)); assert_eq!(buf.next(), None); } #[cfg(test)] mod try_seek { use super::*; use std::time::Duration; #[test] fn channel_order_stays_correct() { const SAMPLE_RATE: u32 = 100; const CHANNELS: u16 = 2; let mut buf = SamplesBuffer::new( CHANNELS, SAMPLE_RATE, (0..2000i16).into_iter().collect::>(), ); buf.try_seek(Duration::from_secs(5)).unwrap(); assert_eq!( buf.next(), Some(5i16 * SAMPLE_RATE as i16 * CHANNELS as i16) ); assert!(buf.next().is_some_and(|s| s % 2 == 1)); assert!(buf.next().is_some_and(|s| s % 2 == 0)); buf.try_seek(Duration::from_secs(6)).unwrap(); assert!(buf.next().is_some_and(|s| s % 2 == 1),); } } }