diff --git a/CHANGELOG.md b/CHANGELOG.md index 81f49f11..a5701a84 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,6 +19,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Minimal builds without `cpal` audio output are now supported. See `README.md` for instructions. (#349) - Added `Sample::is_zero()` method for checking zero samples. +- Added `TakeDuration::with_fadeout` similar to `TakeDuration::fadeout` but returns `Self` ### Changed - Breaking: `OutputStreamBuilder` should now be used to initialize an audio output stream. @@ -31,6 +32,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Breaking: The term 'frame' was renamed to 'span' in the crate and documentation. - Breaking: Sources now use `f32` samples. To convert to and from other types of samples use functions from `dasp_sample` crate. For example `DaspSample::from_sample(sample)`. Remove `integer-decoder` feature. +- Breaking: Sources wrapping an existing source had a public factory method + which is now removed. Something like: `source::amplify(unamplified, 1.2)` must now be + written as `unamplified.amplify(1.2)`. +- Breaking: `TakeDuration::set_filter_fadeout()` has been removed. Use `TakeDuration.fadeout(true)`. +- `SignalGenerator`'s `Function` is now Copy. ### Fixed diff --git a/Cargo.lock b/Cargo.lock index 9f881afd..20084ff6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -39,6 +39,12 @@ version = "1.0.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "55cc3b69f167a1ef2e161439aa98aed94e6028e5f9a59be9a6ffb47aef1651f9" +[[package]] +name = "append-only-vec" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7992085ec035cfe96992dd31bfd495a2ebd31969bb95f624471cb6c0b349e571" + [[package]] name = "approx" version = "0.5.1" @@ -75,7 +81,7 @@ dependencies = [ "bitflags 2.8.0", "cexpr", "clang-sys", - "itertools", + "itertools 0.13.0", "proc-macro2", "quote", "regex", @@ -353,6 +359,15 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "af9673d8203fcb076b19dfd17e38b3d4ae9f44959416ea532ce72415a6020365" +[[package]] +name = "float-cmp" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b09cf3155332e944990140d967ff5eceb70df778b34f77d8075db46e4704e6d8" +dependencies = [ + "num-traits", +] + [[package]] name = "futures" version = "0.3.31" @@ -508,6 +523,15 @@ dependencies = [ "either", ] +[[package]] +name = "itertools" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b192c782037fadd9cfa75548310488aabdbf3d2da73885b31bd0abd03351285" +dependencies = [ + "either", +] + [[package]] name = "jni" version = "0.21.1" @@ -582,6 +606,12 @@ dependencies = [ "windows-targets 0.52.6", ] +[[package]] +name = "libm" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8355be11b20d696c8f18f6cc018c4e372165b1fa8126cef092399c9951984ffa" + [[package]] name = "linux-raw-sys" version = "0.4.15" @@ -609,6 +639,17 @@ version = "2.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" +[[package]] +name = "microfft" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2b6673eb0cc536241d6734c2ca45abfdbf90e9e7791c66a36a7ba3c315b76cf" +dependencies = [ + "cfg-if", + "num-complex", + "static_assertions", +] + [[package]] name = "minimal-lexical" version = "0.2.1" @@ -684,6 +725,15 @@ dependencies = [ "num-traits", ] +[[package]] +name = "num-complex" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73f88a1307638156682bada9d7604135552957b7818057dcef22705b4d509495" +dependencies = [ + "num-traits", +] + [[package]] name = "num-derive" version = "0.4.2" @@ -783,6 +833,12 @@ version = "1.20.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "945462a4b81e43c4e3ba96bd7b49d834c6f61198356aa858733bc4acf3cbe62e" +[[package]] +name = "paste" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" + [[package]] name = "pin-project-lite" version = "0.2.16" @@ -954,6 +1010,7 @@ checksum = "ba39f3699c378cd8970968dcbff9c43159ea4cfbd88d43c00b22f2ef10a435d2" name = "rodio" version = "0.20.1" dependencies = [ + "append-only-vec", "approx", "atomic_float", "claxon", @@ -962,6 +1019,7 @@ dependencies = [ "dasp_sample", "divan", "hound", + "itertools 0.14.0", "lewton", "minimp3_fixed", "num-rational", @@ -969,6 +1027,7 @@ dependencies = [ "rand 0.9.0", "rstest", "rstest_reuse", + "spectrum-analyzer", "symphonia", "tracing", ] @@ -1089,6 +1148,24 @@ dependencies = [ "winapi", ] +[[package]] +name = "spectrum-analyzer" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d06eea3d02718333cc1e726db92b5af1bf92e66f857f81268ff1633acd8a2d3" +dependencies = [ + "float-cmp", + "libm", + "microfft", + "paste", +] + +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + [[package]] name = "symphonia" version = "0.5.4" diff --git a/Cargo.toml b/Cargo.toml index eed950d3..0ac87b10 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -24,6 +24,7 @@ tracing = { version = "0.1.40", optional = true } atomic_float = { version = "1.1.0", optional = true } num-rational = "0.4.2" +append-only-vec = "0.1.7" [features] default = ["playback", "flac", "vorbis", "wav", "mp3"] @@ -66,6 +67,8 @@ rstest_reuse = "0.6.0" approx = "0.5.1" dasp_sample = "0.11.0" divan = "0.1.14" +spectrum-analyzer = "1.6.0" +itertools = "0.14.0" [[bench]] name = "effects" diff --git a/benches/pipeline.rs b/benches/pipeline.rs index e4a0d8a9..ac0c3be0 100644 --- a/benches/pipeline.rs +++ b/benches/pipeline.rs @@ -1,6 +1,7 @@ use std::time::Duration; use divan::Bencher; +use rodio::ChannelCount; use rodio::{source::UniformSourceIterator, Source}; mod shared; @@ -13,7 +14,7 @@ fn main() { #[divan::bench] fn long(bencher: Bencher) { bencher.with_inputs(|| music_wav()).bench_values(|source| { - let mut take_dur = source + let effects_applied = source .high_pass(300) .amplify(1.2) .speed(0.9) @@ -25,13 +26,13 @@ fn long(bencher: Bencher) { ) .delay(Duration::from_secs_f32(0.5)) .fade_in(Duration::from_secs_f32(2.0)) - .take_duration(Duration::from_secs(10)); - take_dur.set_filter_fadeout(); - let effects_applied = take_dur + .take_duration(Duration::from_secs(10)) + .with_fadeout(true) .buffered() .reverb(Duration::from_secs_f32(0.05), 0.3) .skippable(); - let resampled = UniformSourceIterator::new(effects_applied, 2, 40_000); + let resampled = + UniformSourceIterator::new(effects_applied, ChannelCount::new(2).unwrap(), 40_000); resampled.for_each(divan::black_box_drop) }) } diff --git a/benches/shared.rs b/benches/shared.rs index 442621f4..43708542 100644 --- a/benches/shared.rs +++ b/benches/shared.rs @@ -6,7 +6,7 @@ use rodio::{ChannelCount, Sample, SampleRate, Source}; pub struct TestSource { samples: vec::IntoIter, - channels: u16, + channels: ChannelCount, sample_rate: u32, total_duration: Duration, } @@ -29,8 +29,8 @@ impl ExactSizeIterator for TestSource { impl Source for TestSource { #[inline] - fn current_span_len(&self) -> Option { - None // forever + fn parameters_changed(&self) -> bool { + false } #[inline] diff --git a/examples/mix_multiple_sources.rs b/examples/mix_multiple_sources.rs index 065b6585..a6d0a962 100644 --- a/examples/mix_multiple_sources.rs +++ b/examples/mix_multiple_sources.rs @@ -1,11 +1,12 @@ use rodio::mixer; use rodio::source::{SineWave, Source}; use std::error::Error; +use std::num::NonZero; use std::time::Duration; fn main() -> Result<(), Box> { // Construct a dynamic controller and mixer, stream_handle, and sink. - let (controller, mixer) = mixer::mixer(2, 44_100); + let (controller, mixer) = mixer::mixer(NonZero::new(2).unwrap(), 44_100); let stream_handle = rodio::OutputStreamBuilder::open_default_stream()?; let sink = rodio::Sink::connect_new(&stream_handle.mixer()); diff --git a/src/buffer.rs b/src/buffer.rs index 192245e6..fa2cddf0 100644 --- a/src/buffer.rs +++ b/src/buffer.rs @@ -30,7 +30,6 @@ impl 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. @@ -39,13 +38,12 @@ impl SamplesBuffer { where D: Into>, { - assert!(channels >= 1); assert!(sample_rate >= 1); 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; + / channels.get() as u64; let duration = Duration::new( duration_ns / 1_000_000_000, (duration_ns % 1_000_000_000) as u32, @@ -63,8 +61,8 @@ impl SamplesBuffer { impl Source for SamplesBuffer { #[inline] - fn current_span_len(&self) -> Option { - None + fn parameters_changed(&self) -> bool { + false } #[inline] @@ -89,14 +87,14 @@ impl Source for SamplesBuffer { // and due to the constant sample_rate we can jump to the right // sample directly. - 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; + let curr_channel = self.pos % self.channels().get() as usize; + let new_pos = pos.as_secs_f32() * self.sample_rate() as f32 * self.channels().get() 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.next_multiple_of(self.channels().get() as usize); let new_pos = new_pos - curr_channel; self.pos = new_pos; @@ -123,28 +121,23 @@ impl Iterator for SamplesBuffer { #[cfg(test)] mod tests { use crate::buffer::SamplesBuffer; + use crate::math::ch; use crate::source::Source; #[test] fn basic() { - let _ = SamplesBuffer::new(1, 44100, vec![0.0, 0.0, 0.0, 0.0, 0.0, 0.0]); - } - - #[test] - #[should_panic] - fn panic_if_zero_channels() { - SamplesBuffer::new(0, 44100, vec![0.0, 0.0, 0.0, 0.0, 0.0, 0.0]); + let _ = SamplesBuffer::new(ch!(1), 44100, vec![0.0, 0.0, 0.0, 0.0, 0.0, 0.0]); } #[test] #[should_panic] fn panic_if_zero_sample_rate() { - SamplesBuffer::new(1, 0, vec![0.0, 0.0, 0.0, 0.0, 0.0, 0.0]); + SamplesBuffer::new(ch!(1), 0, vec![0.0, 0.0, 0.0, 0.0, 0.0, 0.0]); } #[test] fn duration_basic() { - let buf = SamplesBuffer::new(2, 2, vec![0.0, 0.0, 0.0, 0.0, 0.0, 0.0]); + let buf = SamplesBuffer::new(ch!(2), 2, vec![0.0, 0.0, 0.0, 0.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); @@ -152,7 +145,7 @@ mod tests { #[test] fn iteration() { - let mut buf = SamplesBuffer::new(1, 44100, vec![1.0, 2.0, 3.0, 4.0, 5.0, 6.0]); + let mut buf = SamplesBuffer::new(ch!(1), 44100, vec![1.0, 2.0, 3.0, 4.0, 5.0, 6.0]); assert_eq!(buf.next(), Some(1.0)); assert_eq!(buf.next(), Some(2.0)); assert_eq!(buf.next(), Some(3.0)); @@ -172,7 +165,7 @@ mod tests { #[test] fn channel_order_stays_correct() { const SAMPLE_RATE: SampleRate = 100; - const CHANNELS: ChannelCount = 2; + const CHANNELS: ChannelCount = ch!(2); let mut buf = SamplesBuffer::new( CHANNELS, SAMPLE_RATE, @@ -182,7 +175,10 @@ mod tests { .collect::>(), ); buf.try_seek(Duration::from_secs(5)).unwrap(); - assert_eq!(buf.next(), Some(5.0 * SAMPLE_RATE as f32 * CHANNELS as f32)); + assert_eq!( + buf.next(), + Some(5.0 * SAMPLE_RATE as f32 * CHANNELS.get() as f32) + ); assert!(buf.next().is_some_and(|s| s.trunc() as i32 % 2 == 1)); assert!(buf.next().is_some_and(|s| s.trunc() as i32 % 2 == 0)); diff --git a/src/common.rs b/src/common.rs index 12b3a94d..bf3f7acb 100644 --- a/src/common.rs +++ b/src/common.rs @@ -1,8 +1,10 @@ +use std::num::NonZero; + /// Stream sample rate (a frame rate or samples per second per channel). pub type SampleRate = u32; -/// Number of channels in a stream. -pub type ChannelCount = u16; +/// Number of channels in a stream. Can never be Zero +pub type ChannelCount = NonZero; /// Represents value of a single sample. /// Silence corresponds to the value `0.0`. The expected amplitude range is -1.0...1.0. diff --git a/src/conversions/channels.rs b/src/conversions/channels.rs index 17887673..980ebf98 100644 --- a/src/conversions/channels.rs +++ b/src/conversions/channels.rs @@ -11,7 +11,7 @@ where from: ChannelCount, to: ChannelCount, sample_repeat: Option, - next_output_sample_pos: ChannelCount, + next_output_sample_pos: u16, } impl ChannelCountConverter @@ -26,9 +26,6 @@ where /// #[inline] pub fn new(input: I, from: ChannelCount, to: ChannelCount) -> ChannelCountConverter { - assert!(from >= 1); - assert!(to >= 1); - ChannelCountConverter { input, from, @@ -65,7 +62,7 @@ where self.sample_repeat = value; value } - x if x < self.from => self.input.next(), + x if x < self.from.get() => self.input.next(), 1 => self.sample_repeat, _ => Some(0.0), }; @@ -74,11 +71,11 @@ where self.next_output_sample_pos += 1; } - if self.next_output_sample_pos == self.to { + if self.next_output_sample_pos == self.to.get() { self.next_output_sample_pos = 0; if self.from > self.to { - for _ in self.to..self.from { + for _ in self.to.get()..self.from.get() { self.input.next(); // discarding extra input } } @@ -91,13 +88,13 @@ where fn size_hint(&self) -> (usize, Option) { let (min, max) = self.input.size_hint(); - let consumed = std::cmp::min(self.from, self.next_output_sample_pos) as usize; + let consumed = std::cmp::min(self.from.get(), self.next_output_sample_pos) as usize; - let min = ((min + consumed) / self.from as usize * self.to as usize) + let min = ((min + consumed) / self.from.get() as usize * self.to.get() as usize) .saturating_sub(self.next_output_sample_pos as usize); let max = max.map(|max| { - ((max + consumed) / self.from as usize * self.to as usize) + ((max + consumed) / self.from.get() as usize * self.to.get() as usize) .saturating_sub(self.next_output_sample_pos as usize) }); @@ -111,31 +108,37 @@ impl ExactSizeIterator for ChannelCountConverter where I: ExactSizeIterato mod test { use super::ChannelCountConverter; use crate::common::ChannelCount; + use crate::math::ch; use crate::Sample; #[test] fn remove_channels() { let input = vec![1.0, 2.0, 3.0, 4.0, 5.0, 6.0]; - let output = ChannelCountConverter::new(input.into_iter(), 3, 2).collect::>(); + let output = + ChannelCountConverter::new(input.into_iter(), ch!(3), ch!(2)).collect::>(); assert_eq!(output, [1.0, 2.0, 4.0, 5.0]); let input = vec![1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0]; - let output = ChannelCountConverter::new(input.into_iter(), 4, 1).collect::>(); + let output = + ChannelCountConverter::new(input.into_iter(), ch!(4), ch!(1)).collect::>(); assert_eq!(output, [1.0, 5.0]); } #[test] fn add_channels() { let input = vec![1.0, 2.0, 3.0, 4.0]; - let output = ChannelCountConverter::new(input.into_iter(), 1, 2).collect::>(); + let output = + ChannelCountConverter::new(input.into_iter(), ch!(1), ch!(2)).collect::>(); assert_eq!(output, [1.0, 1.0, 2.0, 2.0, 3.0, 3.0, 4.0, 4.0]); let input = vec![1.0, 2.0]; - let output = ChannelCountConverter::new(input.into_iter(), 1, 4).collect::>(); + let output = + ChannelCountConverter::new(input.into_iter(), ch!(1), ch!(4)).collect::>(); assert_eq!(output, [1.0, 1.0, 0.0, 0.0, 2.0, 2.0, 0.0, 0.0]); let input = vec![1.0, 2.0, 3.0, 4.0]; - let output = ChannelCountConverter::new(input.into_iter(), 2, 4).collect::>(); + let output = + ChannelCountConverter::new(input.into_iter(), ch!(2), ch!(4)).collect::>(); assert_eq!(output, [1.0, 2.0, 0.0, 0.0, 3.0, 4.0, 0.0, 0.0]); } @@ -152,24 +155,24 @@ mod test { assert_eq!(converter.size_hint(), (0, Some(0))); } - test(&[1.0, 2.0, 3.0], 1, 2); - test(&[1.0, 2.0, 3.0, 4.0], 2, 4); - test(&[1.0, 2.0, 3.0, 4.0], 4, 2); - test(&[1.0, 2.0, 3.0, 4.0, 5.0, 6.0], 3, 8); - test(&[1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0], 4, 1); + test(&[1.0, 2.0, 3.0], ch!(1), ch!(2)); + test(&[1.0, 2.0, 3.0, 4.0], ch!(2), ch!(4)); + test(&[1.0, 2.0, 3.0, 4.0], ch!(4), ch!(2)); + test(&[1.0, 2.0, 3.0, 4.0, 5.0, 6.0], ch!(3), ch!(8)); + test(&[1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0], ch!(4), ch!(1)); } #[test] fn len_more() { let input = vec![1.0, 2.0, 3.0, 4.0]; - let output = ChannelCountConverter::new(input.into_iter(), 2, 3); + let output = ChannelCountConverter::new(input.into_iter(), ch!(2), ch!(3)); assert_eq!(output.len(), 6); } #[test] fn len_less() { let input = vec![1.0, 2.0, 3.0, 4.0]; - let output = ChannelCountConverter::new(input.into_iter(), 2, 1); + let output = ChannelCountConverter::new(input.into_iter(), ch!(2), ch!(1)); assert_eq!(output.len(), 2); } } diff --git a/src/conversions/sample_rate.rs b/src/conversions/sample_rate.rs index da85f52e..363f8ca9 100644 --- a/src/conversions/sample_rate.rs +++ b/src/conversions/sample_rate.rs @@ -54,7 +54,6 @@ where to: SampleRate, num_channels: ChannelCount, ) -> SampleRateConverter { - assert!(num_channels >= 1); assert!(from >= 1); assert!(to >= 1); @@ -64,11 +63,11 @@ where } else { let first = input .by_ref() - .take(num_channels as usize) + .take(num_channels.get() as usize) .collect::>(); let next = input .by_ref() - .take(num_channels as usize) + .take(num_channels.get() as usize) .collect::>(); (first, next) }; @@ -85,7 +84,7 @@ where next_output_span_pos_in_chunk: 0, current_span: first_samples, next_frame: next_samples, - output_buffer: Vec::with_capacity(num_channels as usize - 1), + output_buffer: Vec::with_capacity(num_channels.get() as usize - 1), } } @@ -106,7 +105,7 @@ where mem::swap(&mut self.current_span, &mut self.next_frame); self.next_frame.clear(); - for _ in 0..self.channels { + for _ in 0..self.channels.get() { if let Some(i) = self.input.next() { self.next_frame.push(i); } else { @@ -213,7 +212,7 @@ where // removing the samples of the current chunk that have not yet been read let samples_after_chunk = samples_after_chunk.saturating_sub( self.from.saturating_sub(self.current_span_pos_in_chunk + 2) as usize - * usize::from(self.channels), + * usize::from(self.channels.get()), ); // calculating the number of samples after the transformation // TODO: this is wrong here \|/ @@ -222,7 +221,7 @@ where // `samples_current_chunk` will contain the number of samples remaining to be output // for the chunk currently being processed let samples_current_chunk = (self.to - self.next_output_span_pos_in_chunk) as usize - * usize::from(self.channels); + * usize::from(self.channels.get()); samples_current_chunk + samples_after_chunk + self.output_buffer.len() }; @@ -242,14 +241,15 @@ impl ExactSizeIterator for SampleRateConverter where I: ExactSizeIterator< mod test { use super::SampleRateConverter; use crate::common::{ChannelCount, SampleRate}; + use crate::math::ch; use crate::Sample; use core::time::Duration; use quickcheck::{quickcheck, TestResult}; quickcheck! { /// Check that resampling an empty input produces no output. - fn empty(from: u16, to: u16, channels: u8) -> TestResult { - if channels == 0 || channels > 128 + fn empty(from: u16, to: u16, channels: ChannelCount) -> TestResult { + if channels.get() > 128 || from == 0 || to == 0 { @@ -260,7 +260,7 @@ mod test { let input: Vec = Vec::new(); let output = - SampleRateConverter::new(input.into_iter(), from, to, channels as ChannelCount) + SampleRateConverter::new(input.into_iter(), from, to, channels) .collect::>(); assert_eq!(output, []); @@ -268,13 +268,13 @@ mod test { } /// Check that resampling to the same rate does not change the signal. - fn identity(from: u16, channels: u8, input: Vec) -> TestResult { - if channels == 0 || channels > 128 || from == 0 { return TestResult::discard(); } + fn identity(from: u16, channels: ChannelCount, input: Vec) -> TestResult { + if channels.get() > 128 || from == 0 { return TestResult::discard(); } let from = from as SampleRate; let input = Vec::from_iter(input.iter().map(|x| *x as Sample)); let output = - SampleRateConverter::new(input.clone().into_iter(), from, from, channels as ChannelCount) + SampleRateConverter::new(input.clone().into_iter(), from, from, channels) .collect::>(); TestResult::from_bool(input == output) @@ -282,8 +282,8 @@ mod test { /// Check that dividing the sample rate by k (integer) is the same as /// dropping a sample from each channel. - fn divide_sample_rate(to: u16, k: u16, input: Vec, channels: u8) -> TestResult { - if k == 0 || channels == 0 || channels > 128 || to == 0 || to > 48000 { + fn divide_sample_rate(to: u16, k: u16, input: Vec, channels: ChannelCount) -> TestResult { + if k == 0 || channels.get() > 128 || to == 0 || to > 48000 { return TestResult::discard(); } let input = Vec::from_iter(input.iter().map(|x| *x as Sample)); @@ -293,24 +293,24 @@ mod test { // Truncate the input, so it contains an integer number of spans. let input = { - let ns = channels as usize; + let ns = channels.get() as usize; let mut i = input; i.truncate(ns * (i.len() / ns)); i }; let output = - SampleRateConverter::new(input.clone().into_iter(), from, to, channels as ChannelCount) + SampleRateConverter::new(input.clone().into_iter(), from, to, channels) .collect::>(); - TestResult::from_bool(input.chunks_exact(channels.into()) + TestResult::from_bool(input.chunks_exact(channels.get().into()) .step_by(k as usize).collect::>().concat() == output) } /// Check that, after multiplying the sample rate by k, every k-th /// sample in the output matches exactly with the input. - fn multiply_sample_rate(from: u16, k: u8, input: Vec, channels: u8) -> TestResult { - if k == 0 || channels == 0 || channels > 128 || from == 0 { + fn multiply_sample_rate(from: u16, k: u8, input: Vec, channels: ChannelCount) -> TestResult { + if k == 0 || channels.get() > 128 || from == 0 { return TestResult::discard(); } let input = Vec::from_iter(input.iter().map(|x| *x as Sample)); @@ -320,24 +320,24 @@ mod test { // Truncate the input, so it contains an integer number of spans. let input = { - let ns = channels as usize; + let ns = channels.get() as usize; let mut i = input; i.truncate(ns * (i.len() / ns)); i }; let output = - SampleRateConverter::new(input.clone().into_iter(), from, to, channels as ChannelCount) + SampleRateConverter::new(input.clone().into_iter(), from, to, channels) .collect::>(); TestResult::from_bool(input == - output.chunks_exact(channels.into()) + output.chunks_exact(channels.get().into()) .step_by(k as usize).collect::>().concat()) } #[ignore] /// Check that resampling does not change the audio duration, - /// except by a negligible amount (± 1ms). Reproduces #316. + /// except by a negligible amount (± 1ms). Reproduces #316. /// Ignored, pending a bug fix. fn preserve_durations(d: Duration, freq: f32, to: SampleRate) -> TestResult { if to == 0 { return TestResult::discard(); } @@ -348,7 +348,7 @@ mod test { let from = source.sample_rate(); let resampled = - SampleRateConverter::new(source, from, to, 1); + SampleRateConverter::new(source, from, to, ch!(1)); let duration = Duration::from_secs_f32(resampled.count() as f32 / to as f32); @@ -360,7 +360,7 @@ mod test { #[test] fn upsample() { let input = vec![2.0, 16.0, 4.0, 18.0, 6.0, 20.0, 8.0, 22.0]; - let output = SampleRateConverter::new(input.into_iter(), 2000, 3000, 2); + let output = SampleRateConverter::new(input.into_iter(), 2000, 3000, ch!(2)); assert_eq!(output.len(), 12); // Test the source's Iterator::size_hint() let output = output.map(|x| x.trunc()).collect::>(); @@ -373,7 +373,7 @@ mod test { #[test] fn upsample2() { let input = vec![1.0, 14.0]; - let output = SampleRateConverter::new(input.into_iter(), 1000, 7000, 1); + let output = SampleRateConverter::new(input.into_iter(), 1000, 7000, ch!(1)); let size_estimation = output.len(); let output = output.map(|x| x.trunc()).collect::>(); assert_eq!(output, [1.0, 2.0, 4.0, 6.0, 8.0, 10.0, 12.0, 14.0]); @@ -383,7 +383,7 @@ mod test { #[test] fn downsample() { let input = Vec::from_iter((0..17).map(|x| x as Sample)); - let output = SampleRateConverter::new(input.into_iter(), 12000, 2400, 1); + let output = SampleRateConverter::new(input.into_iter(), 12000, 2400, ch!(1)); let size_estimation = output.len(); let output = output.collect::>(); assert_eq!(output, [0.0, 5.0, 10.0, 15.0]); diff --git a/src/decoder/flac.rs b/src/decoder/flac.rs index dd21d871..15a9072b 100644 --- a/src/decoder/flac.rs +++ b/src/decoder/flac.rs @@ -61,7 +61,12 @@ where current_block_off: 0, bits_per_sample: spec.bits_per_sample, sample_rate, - channels: spec.channels as ChannelCount, + channels: ChannelCount::new( + spec.channels + .try_into() + .expect("rodio supports only up to u16::MAX (65_535) channels"), + ) + .expect("flac should never have zero channels"), total_duration, }) } @@ -77,8 +82,8 @@ where R: Read + Seek, { #[inline] - fn current_span_len(&self) -> Option { - None + fn parameters_changed(&self) -> bool { + todo!() } #[inline] @@ -115,9 +120,9 @@ where loop { if self.current_block_off < self.current_block.len() { // Read from current block. - let real_offset = (self.current_block_off % self.channels as usize) + let real_offset = (self.current_block_off % self.channels.get() as usize) * self.current_block_channel_len - + self.current_block_off / self.channels as usize; + + self.current_block_off / self.channels.get() as usize; let raw_val = self.current_block[real_offset]; self.current_block_off += 1; let bits = self.bits_per_sample; diff --git a/src/decoder/mod.rs b/src/decoder/mod.rs index 84aa469e..0ad970ea 100644 --- a/src/decoder/mod.rs +++ b/src/decoder/mod.rs @@ -8,6 +8,7 @@ use std::mem; use std::str::FromStr; use std::time::Duration; +use crate::math::ch; use crate::source::SeekError; use crate::{Sample, Source}; @@ -102,19 +103,19 @@ impl DecoderImpl { } #[inline] - fn current_span_len(&self) -> Option { + fn parameters_changed(&self) -> bool { match self { #[cfg(all(feature = "wav", not(feature = "symphonia-wav")))] - DecoderImpl::Wav(source) => source.current_span_len(), + DecoderImpl::Wav(source) => source.parameters_changed(), #[cfg(all(feature = "vorbis", not(feature = "symphonia-vorbis")))] - DecoderImpl::Vorbis(source) => source.current_span_len(), + DecoderImpl::Vorbis(source) => source.parameters_changed(), #[cfg(all(feature = "flac", not(feature = "symphonia-flac")))] - DecoderImpl::Flac(source) => source.current_span_len(), + DecoderImpl::Flac(source) => source.parameters_changed(), #[cfg(all(feature = "minimp3", not(feature = "symphonia-mp3")))] - DecoderImpl::Mp3(source) => source.current_span_len(), + DecoderImpl::Mp3(source) => source.parameters_changed(), #[cfg(feature = "symphonia")] - DecoderImpl::Symphonia(source) => source.current_span_len(), - DecoderImpl::None(_) => Some(0), + DecoderImpl::Symphonia(source) => source.parameters_changed(), + DecoderImpl::None(_) => false, } } @@ -131,7 +132,7 @@ impl DecoderImpl { DecoderImpl::Mp3(source) => source.channels(), #[cfg(feature = "symphonia")] DecoderImpl::Symphonia(source) => source.channels(), - DecoderImpl::None(_) => 0, + DecoderImpl::None(_) => ch!(1), } } @@ -414,8 +415,8 @@ where R: Read + Seek, { #[inline] - fn current_span_len(&self) -> Option { - self.0.current_span_len() + fn parameters_changed(&self) -> bool { + self.0.parameters_changed() } #[inline] @@ -512,8 +513,8 @@ where R: Read + Seek, { #[inline] - fn current_span_len(&self) -> Option { - self.0.current_span_len() + fn parameters_changed(&self) -> bool { + self.0.parameters_changed() } #[inline] diff --git a/src/decoder/mp3.rs b/src/decoder/mp3.rs index e0680cbf..4e8cf4a6 100644 --- a/src/decoder/mp3.rs +++ b/src/decoder/mp3.rs @@ -55,7 +55,7 @@ where R: Read + Seek, { #[inline] - fn current_span_len(&self) -> Option { + fn parameters_changed(&self) -> bool { Some(self.current_span.data.len()) } @@ -96,7 +96,7 @@ where type Item = DecoderSample; fn next(&mut self) -> Option { - let current_span_len = self.current_span_len()?; + let current_span_len = self.parameters_changed()?; if self.current_span_offset == current_span_len { if let Ok(span) = self.decoder.next_frame() { // if let Ok(span) = self.decoder.decode_frame() { diff --git a/src/decoder/symphonia.rs b/src/decoder/symphonia.rs index 42d0d834..275b64eb 100644 --- a/src/decoder/symphonia.rs +++ b/src/decoder/symphonia.rs @@ -158,13 +158,21 @@ impl SymphoniaDecoder { impl Source for SymphoniaDecoder { #[inline] - fn current_span_len(&self) -> Option { - Some(self.buffer.samples().len()) + fn parameters_changed(&self) -> bool { + todo!() + // Some(self.buffer.samples().len()) } #[inline] fn channels(&self) -> ChannelCount { - self.spec.channels.count() as ChannelCount + ChannelCount::new( + self.spec + .channels + .count() + .try_into() + .expect("rodio only support up to u16::MAX channels (65_535)"), + ) + .expect("audio should always have at least one channel") } #[inline] @@ -192,7 +200,7 @@ impl Source for SymphoniaDecoder { }; // make sure the next sample is for the right channel - let to_skip = self.current_span_offset % self.channels() as usize; + let to_skip = self.current_span_offset % self.channels().get() as usize; let seek_res = self .format @@ -289,7 +297,7 @@ impl SymphoniaDecoder { let decoded = decoded.map_err(SeekError::Decoding)?; decoded.spec().clone_into(&mut self.spec); self.buffer = SymphoniaDecoder::get_buffer(decoded, &self.spec); - self.current_span_offset = samples_to_pass as usize * self.channels() as usize; + self.current_span_offset = samples_to_pass as usize * self.channels().get() as usize; Ok(()) } } diff --git a/src/decoder/vorbis.rs b/src/decoder/vorbis.rs index 656d96ca..cfd80d24 100644 --- a/src/decoder/vorbis.rs +++ b/src/decoder/vorbis.rs @@ -63,13 +63,15 @@ where R: Read + Seek, { #[inline] - fn current_span_len(&self) -> Option { - Some(self.current_data.len()) + fn parameters_changed(&self) -> bool { + todo!() + //Some(self.current_data.len()) } #[inline] fn channels(&self) -> ChannelCount { - self.stream_reader.ident_hdr.audio_channels as ChannelCount + ChannelCount::new(self.stream_reader.ident_hdr.audio_channels.into()) + .expect("audio should have at least one channel") } #[inline] diff --git a/src/decoder/wav.rs b/src/decoder/wav.rs index 421a75e0..4c35c8f4 100644 --- a/src/decoder/wav.rs +++ b/src/decoder/wav.rs @@ -41,6 +41,7 @@ where let sample_rate = spec.sample_rate; let channels = spec.channels; + assert!(channels > 0); let total_duration = { let data_rate = sample_rate as u64 * channels as u64; @@ -53,7 +54,7 @@ where reader, total_duration, sample_rate: sample_rate as SampleRate, - channels: channels as ChannelCount, + channels: ChannelCount::new(channels).expect("wav should have a least one channel"), }) } @@ -147,8 +148,8 @@ where R: Read + Seek, { #[inline] - fn current_span_len(&self) -> Option { - None + fn parameters_changed(&self) -> bool { + false // wav never changes sample rate or channel count } #[inline] @@ -175,13 +176,13 @@ where let new_pos = new_pos.min(file_len); // saturate pos at the end of the source // make sure the next sample is for the right channel - let to_skip = self.reader.samples_read % self.channels() as u32; + let to_skip = self.reader.samples_read % self.channels().get() as u32; self.reader .reader .seek(new_pos) .map_err(SeekError::HoundDecoder)?; - self.reader.samples_read = new_pos * self.channels() as u32; + self.reader.samples_read = new_pos * self.channels().get() as u32; for _ in 0..to_skip { self.next(); diff --git a/src/math.rs b/src/math.rs index 2f020748..efa902f1 100644 --- a/src/math.rs +++ b/src/math.rs @@ -10,6 +10,57 @@ pub fn lerp(first: &f32, second: &f32, numerator: u32, denominator: u32) -> f32 first + (second - first) * numerator as f32 / denominator as f32 } +pub(crate) const NS_PER_SECOND: u64 = 1_000_000_000; + +/// short macro to generate a `ChannelCount` for tests +/// this panics during compile if the passed in literal is zero +macro_rules! ch { + ($n:literal) => { + const { core::num::NonZeroU16::new($n).unwrap() } + }; +} + +pub(crate) use ch; + +/// will hopefully get stabilized, this is slightly different to the future +/// std's version since it does some casting already. When the std's version gets +/// stable remove this trait. +pub(crate) trait PrevMultipleOf { + fn prev_multiple_of(self, n: u16) -> Self; +} +pub(crate) trait PrevMultipleOfNonZero { + fn prev_multiple_of(self, n: core::num::NonZero) -> Self; +} + +macro_rules! impl_prev_multiple_of { + ($type:ty) => { + impl PrevMultipleOf for $type { + /// # Panics + /// Like std's `next_multiple_of` this panics if n is zero + fn prev_multiple_of(self, n: u16) -> $type { + if self.next_multiple_of(n as $type) > self { + self.next_multiple_of(n as $type) - n as $type + } else { + self.next_multiple_of(n as $type) + } + } + } + impl PrevMultipleOfNonZero for $type { + /// Never panics thanks to n being NonZero + fn prev_multiple_of(self, n: core::num::NonZero) -> $type { + if self.next_multiple_of(n.get() as $type) > self { + self.next_multiple_of(n.get() as $type) - n.get() as $type + } else { + self.next_multiple_of(n.get() as $type) + } + } + } + }; +} + +impl_prev_multiple_of! {usize} +impl_prev_multiple_of! {u64} + #[cfg(test)] mod test { use super::*; diff --git a/src/mixer.rs b/src/mixer.rs index 9c4b402e..331bf1a5 100644 --- a/src/mixer.rs +++ b/src/mixer.rs @@ -81,8 +81,9 @@ pub struct MixerSource { impl Source for MixerSource { #[inline] - fn current_span_len(&self) -> Option { - None + fn parameters_changed(&self) -> bool { + todo!() + // None } #[inline] @@ -172,7 +173,7 @@ impl MixerSource { let mut pending = self.input.0.pending_sources.lock().unwrap(); // TODO: relax ordering? for source in pending.drain(..) { - let in_step = self.sample_count % source.channels() as usize == 0; + let in_step = self.sample_count % source.channels().get() as usize == 0; if in_step { self.current_sources.push(source); @@ -206,17 +207,18 @@ impl MixerSource { #[cfg(test)] mod tests { use crate::buffer::SamplesBuffer; + use crate::math::ch; use crate::mixer; use crate::source::Source; #[test] fn basic() { - let (tx, mut rx) = mixer::mixer(1, 48000); + let (tx, mut rx) = mixer::mixer(ch!(1), 48000); - tx.add(SamplesBuffer::new(1, 48000, vec![10.0, -10.0, 10.0, -10.0])); - tx.add(SamplesBuffer::new(1, 48000, vec![5.0, 5.0, 5.0, 5.0])); + tx.add(SamplesBuffer::new(ch!(1), 48000, vec![10.0, -10.0, 10.0, -10.0])); + tx.add(SamplesBuffer::new(ch!(1), 48000, vec![5.0, 5.0, 5.0, 5.0])); - assert_eq!(rx.channels(), 1); + assert_eq!(rx.channels(), ch!(1)); assert_eq!(rx.sample_rate(), 48000); assert_eq!(rx.next(), Some(15.0)); assert_eq!(rx.next(), Some(-5.0)); @@ -227,12 +229,12 @@ mod tests { #[test] fn channels_conv() { - let (tx, mut rx) = mixer::mixer(2, 48000); + let (tx, mut rx) = mixer::mixer(ch!(2), 48000); - tx.add(SamplesBuffer::new(1, 48000, vec![10.0, -10.0, 10.0, -10.0])); - tx.add(SamplesBuffer::new(1, 48000, vec![5.0, 5.0, 5.0, 5.0])); + tx.add(SamplesBuffer::new(ch!(1), 48000, vec![10.0, -10.0, 10.0, -10.0])); + tx.add(SamplesBuffer::new(ch!(1), 48000, vec![5.0, 5.0, 5.0, 5.0])); - assert_eq!(rx.channels(), 2); + assert_eq!(rx.channels(), ch!(2)); assert_eq!(rx.sample_rate(), 48000); assert_eq!(rx.next(), Some(15.0)); assert_eq!(rx.next(), Some(15.0)); @@ -247,12 +249,12 @@ mod tests { #[test] fn rate_conv() { - let (tx, mut rx) = mixer::mixer(1, 96000); + let (tx, mut rx) = mixer::mixer(ch!(1), 96000); - tx.add(SamplesBuffer::new(1, 48000, vec![10.0, -10.0, 10.0, -10.0])); - tx.add(SamplesBuffer::new(1, 48000, vec![5.0, 5.0, 5.0, 5.0])); + tx.add(SamplesBuffer::new(ch!(1), 48000, vec![10.0, -10.0, 10.0, -10.0])); + tx.add(SamplesBuffer::new(ch!(1), 48000, vec![5.0, 5.0, 5.0, 5.0])); - assert_eq!(rx.channels(), 1); + assert_eq!(rx.channels(), ch!(1)); assert_eq!(rx.sample_rate(), 96000); assert_eq!(rx.next(), Some(15.0)); assert_eq!(rx.next(), Some(5.0)); @@ -266,15 +268,15 @@ mod tests { #[test] fn start_afterwards() { - let (tx, mut rx) = mixer::mixer(1, 48000); + let (tx, mut rx) = mixer::mixer(ch!(1), 48000); - tx.add(SamplesBuffer::new(1, 48000, vec![10.0, -10.0, 10.0, -10.0])); + tx.add(SamplesBuffer::new(ch!(1), 48000, vec![10.0, -10.0, 10.0, -10.0])); assert_eq!(rx.next(), Some(10.0)); assert_eq!(rx.next(), Some(-10.0)); tx.add(SamplesBuffer::new( - 1, + ch!(1), 48000, vec![5.0, 5.0, 6.0, 6.0, 7.0, 7.0, 7.0], )); @@ -285,7 +287,7 @@ mod tests { assert_eq!(rx.next(), Some(6.0)); assert_eq!(rx.next(), Some(6.0)); - tx.add(SamplesBuffer::new(1, 48000, vec![2.0])); + tx.add(SamplesBuffer::new(ch!(1), 48000, vec![2.0])); assert_eq!(rx.next(), Some(9.0)); assert_eq!(rx.next(), Some(7.0)); diff --git a/src/queue.rs b/src/queue.rs index ccae005f..b06101bd 100644 --- a/src/queue.rs +++ b/src/queue.rs @@ -4,151 +4,92 @@ use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::{Arc, Mutex}; use std::time::Duration; +use crate::math::ch; use crate::source::{Empty, SeekError, Source, Zero}; +use crate::source::PeekableSource; use crate::Sample; use crate::common::{ChannelCount, SampleRate}; -#[cfg(feature = "crossbeam-channel")] -use crossbeam_channel::{unbounded as channel, Receiver, Sender}; -#[cfg(not(feature = "crossbeam-channel"))] -use std::sync::mpsc::{channel, Receiver, Sender}; - -/// Builds a new queue. It consists of an input and an output. -/// -/// The input can be used to add sounds to the end of the queue, while the output implements -/// `Source` and plays the sounds. -/// -/// The parameter indicates how the queue should behave if the queue becomes empty: -/// -/// - If you pass `true`, then the queue is infinite and will play a silence instead until you add -/// a new sound. -/// - If you pass `false`, then the queue will report that it has finished playing. -/// -pub fn queue(keep_alive_if_empty: bool) -> (Arc, SourcesQueueOutput) { - let input = Arc::new(SourcesQueueInput { - next_sounds: Mutex::new(Vec::new()), - keep_alive_if_empty: AtomicBool::new(keep_alive_if_empty), - }); - - let output = SourcesQueueOutput { - current: Box::new(Empty::new()) as Box<_>, - signal_after_end: None, - input: input.clone(), - }; - - (input, output) -} - -// TODO: consider reimplementing this with `from_factory` type Sound = Box; -type SignalDone = Option>; +struct Inner { + next_sounds: Mutex>>, + keep_alive_if_empty: AtomicBool, +} /// The input of the queue. -pub struct SourcesQueueInput { - next_sounds: Mutex>, +pub struct Queue(Arc); - // See constructor. - keep_alive_if_empty: AtomicBool, -} +impl Queue { + /// Builds a new queue. It consists of an input and an output. + /// + /// The input can be used to add sounds to the end of the queue, while the output implements + /// `Source` and plays the sounds. + /// + /// The parameter indicates how the queue should behave if the queue becomes empty: + /// + /// - If you pass `true`, then the queue is infinite and will play a silence instead until you add + /// a new sound. + /// - If you pass `false`, then the queue will report that it has finished playing. + pub fn new(keep_alive_if_empty: bool) -> (Self, QueueSource) { + let input = Arc::new(Inner { + next_sounds: Mutex::new(Vec::new()), + keep_alive_if_empty: AtomicBool::new(keep_alive_if_empty), + }); + + let output = QueueSource { + current: Empty::new().type_erased().peekable_source(), + input: input.clone(), + starting_new_source: false, + }; -impl SourcesQueueInput { - /// Adds a new source to the end of the queue. - #[inline] - pub fn append(&self, source: T) - where - T: Source + Send + 'static, - { - self.next_sounds - .lock() - .unwrap() - .push((Box::new(source) as Box<_>, None)); + (Self(input), output) } /// Adds a new source to the end of the queue. - /// - /// The `Receiver` will be signalled when the sound has finished playing. - /// - /// Enable the feature flag `crossbeam-channel` in rodio to use a `crossbeam_channel::Receiver` instead. #[inline] - pub fn append_with_signal(&self, source: T) -> Receiver<()> + pub fn append(&self, source: T) where T: Source + Send + 'static, { - let (tx, rx) = channel(); - self.next_sounds + self.0 + .next_sounds .lock() .unwrap() - .push((Box::new(source) as Box<_>, Some(tx))); - rx + .push(source.type_erased().peekable_source()); } /// Sets whether the queue stays alive if there's no more sound to play. /// /// See also the constructor. - pub fn set_keep_alive_if_empty(&self, keep_alive_if_empty: bool) { - self.keep_alive_if_empty - .store(keep_alive_if_empty, Ordering::Release); + pub fn keep_alive_if_empty(&self, keep_alive_if_empty: bool) { + self.0 // relaxed: no one depends on the exact order + .keep_alive_if_empty + .store(keep_alive_if_empty, Ordering::Relaxed); } /// Removes all the sounds from the queue. Returns the number of sounds cleared. pub fn clear(&self) -> usize { - let mut sounds = self.next_sounds.lock().unwrap(); + let mut sounds = self.0.next_sounds.lock().unwrap(); let len = sounds.len(); sounds.clear(); len } } -/// The output of the queue. Implements `Source`. -pub struct SourcesQueueOutput { - // The current iterator that produces samples. - current: Box, - - // Signal this sender before picking from `next`. - signal_after_end: Option>, - // The next sounds. - input: Arc, +/// Play this source to hear sounds appended to the Queue +pub struct QueueSource { + current: PeekableSource>, + starting_new_source: bool, + input: Arc, } const THRESHOLD: usize = 512; -impl Source for SourcesQueueOutput { +impl Source for QueueSource { #[inline] - fn current_span_len(&self) -> Option { - // This function is non-trivial because the boundary between two sounds in the queue should - // be a span boundary as well. - // - // The current sound is free to return `None` for `current_span_len()`, in which case - // we *should* return the number of samples remaining the current sound. - // This can be estimated with `size_hint()`. - // - // If the `size_hint` is `None` as well, we are in the worst case scenario. To handle this - // situation we force a span to have a maximum number of samples indicate by this - // constant. - - // Try the current `current_span_len`. - if let Some(val) = self.current.current_span_len() { - if val != 0 { - return Some(val); - } else if self.input.keep_alive_if_empty.load(Ordering::Acquire) - && self.input.next_sounds.lock().unwrap().is_empty() - { - // The next source will be a filler silence which will have the length of `THRESHOLD` - return Some(THRESHOLD); - } - } - - // Try the size hint. - let (lower_bound, _) = self.current.size_hint(); - // The iterator default implementation just returns 0. - // That's a problematic value, so skip it. - if lower_bound > 0 { - return Some(lower_bound); - } - - // Otherwise we use the constant value. - Some(THRESHOLD) + fn parameters_changed(&self) -> bool { + self.current.peek_next().is_none() || self.current.parameters_changed() } #[inline] @@ -171,7 +112,7 @@ impl Source for SourcesQueueOutput { // that it advances the queue if the position is beyond the current song. // // We would then however need to enable seeking backwards across sources too. - // That no longer seems in line with the queue behaviour. + // That no longer seems in line with the queue behavior. // // A final pain point is that we would need the total duration for the // next few songs. @@ -181,19 +122,21 @@ impl Source for SourcesQueueOutput { } } -impl Iterator for SourcesQueueOutput { +impl Iterator for QueueSource { type Item = Sample; #[inline] fn next(&mut self) -> Option { loop { // Basic situation that will happen most of the time. + self.starting_new_source = false; if let Some(sample) = self.current.next() { return Some(sample); } // Since `self.current` has finished, we need to pick the next sound. - // In order to avoid inlining this expensive operation, the code is in another function. + // In order to avoid inlining this expensive operation, + // the code is in another function. if self.go_next().is_err() { return None; } @@ -206,24 +149,20 @@ impl Iterator for SourcesQueueOutput { } } -impl SourcesQueueOutput { +impl QueueSource { // Called when `current` is empty, and we must jump to the next element. // Returns `Ok` if the sound should continue playing, or an error if it should stop. // // This method is separate so that it is not inlined. fn go_next(&mut self) -> Result<(), ()> { - if let Some(signal_after_end) = self.signal_after_end.take() { - let _ = signal_after_end.send(()); - } - - let (next, signal_after_end) = { + let next = { let mut next = self.input.next_sounds.lock().unwrap(); - if next.len() == 0 { - let silence = Box::new(Zero::new_samples(1, 44100, THRESHOLD)) as Box<_>; - if self.input.keep_alive_if_empty.load(Ordering::Acquire) { + if next.is_empty() { + let silence = Zero::new_samples(ch!(1), 44100, THRESHOLD).type_erased().peekable_source(); + if self.input.keep_alive_if_empty.load(Ordering::Relaxed) { // Play a short silence in order to avoid spinlocking. - (silence, None) + silence } else { return Err(()); } @@ -232,75 +171,8 @@ impl SourcesQueueOutput { } }; + self.starting_new_source = true; self.current = next; - self.signal_after_end = signal_after_end; Ok(()) } } - -#[cfg(test)] -mod tests { - use crate::buffer::SamplesBuffer; - use crate::queue; - use crate::source::Source; - - #[test] - #[ignore] // FIXME: samples rate and channel not updated immediately after transition - fn basic() { - let (tx, mut rx) = queue::queue(false); - - tx.append(SamplesBuffer::new(1, 48000, vec![10.0, -10.0, 10.0, -10.0])); - tx.append(SamplesBuffer::new(2, 96000, vec![5.0, 5.0, 5.0, 5.0])); - - assert_eq!(rx.channels(), 1); - assert_eq!(rx.sample_rate(), 48000); - assert_eq!(rx.next(), Some(10.0)); - assert_eq!(rx.next(), Some(-10.0)); - assert_eq!(rx.next(), Some(10.0)); - assert_eq!(rx.next(), Some(-10.0)); - assert_eq!(rx.channels(), 2); - assert_eq!(rx.sample_rate(), 96000); - assert_eq!(rx.next(), Some(5.0)); - assert_eq!(rx.next(), Some(5.0)); - assert_eq!(rx.next(), Some(5.0)); - assert_eq!(rx.next(), Some(5.0)); - assert_eq!(rx.next(), None); - } - - #[test] - fn immediate_end() { - let (_, mut rx) = queue::queue(false); - assert_eq!(rx.next(), None); - } - - #[test] - fn keep_alive() { - let (tx, mut rx) = queue::queue(true); - tx.append(SamplesBuffer::new(1, 48000, vec![10.0, -10.0, 10.0, -10.0])); - - assert_eq!(rx.next(), Some(10.0)); - assert_eq!(rx.next(), Some(-10.0)); - assert_eq!(rx.next(), Some(10.0)); - assert_eq!(rx.next(), Some(-10.0)); - - for _ in 0..100000 { - assert_eq!(rx.next(), Some(0.0)); - } - } - - #[test] - #[ignore] // TODO: not yet implemented - fn no_delay_when_added() { - let (tx, mut rx) = queue::queue(true); - - for _ in 0..500 { - assert_eq!(rx.next(), Some(0.0)); - } - - tx.append(SamplesBuffer::new(1, 48000, vec![10.0, -10.0, 10.0, -10.0])); - assert_eq!(rx.next(), Some(10.0)); - assert_eq!(rx.next(), Some(-10.0)); - assert_eq!(rx.next(), Some(10.0)); - assert_eq!(rx.next(), Some(-10.0)); - } -} diff --git a/src/sink.rs b/src/sink.rs index 0ba019af..8cbd1344 100644 --- a/src/sink.rs +++ b/src/sink.rs @@ -1,5 +1,5 @@ use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering}; -use std::sync::{Arc, Mutex}; +use std::sync::{mpsc, Arc, Mutex}; use std::time::Duration; #[cfg(feature = "crossbeam-channel")] @@ -9,15 +9,16 @@ use dasp_sample::FromSample; use std::sync::mpsc::{Receiver, Sender}; use crate::mixer::Mixer; +use crate::queue::{Queue, QueueSource}; use crate::source::SeekError; -use crate::{queue, source::Done, Source}; +use crate::{queue, source::Done, source::EmptyCallback, Source}; /// Handle to a device that outputs sounds. /// /// Dropping the `Sink` stops all its sounds. You can use `detach` if you want the sounds to continue /// playing. pub struct Sink { - queue_tx: Arc, + queue: queue::Queue, sleep_until_end: Mutex>>, controls: Arc, @@ -77,11 +78,11 @@ impl Sink { /// Builds a new `Sink`. #[inline] - pub fn new() -> (Sink, queue::SourcesQueueOutput) { - let (queue_tx, queue_rx) = queue::queue(true); + pub fn new() -> (Sink, QueueSource) { + let (queue, source) = Queue::new(true); let sink = Sink { - queue_tx, + queue, sleep_until_end: Mutex::new(None), controls: Arc::new(Controls { pause: AtomicBool::new(false), @@ -95,7 +96,7 @@ impl Sink { sound_count: Arc::new(AtomicUsize::new(0)), detached: false, }; - (sink, queue_rx) + (sink, source) } /// Appends a sound to the queue of sounds to play. @@ -156,7 +157,15 @@ impl Sink { }); self.sound_count.fetch_add(1, Ordering::Relaxed); let source = Done::new(source, self.sound_count.clone()); - *self.sleep_until_end.lock().unwrap() = Some(self.queue_tx.append_with_signal(source)); + self.queue.append(source); + + let (tx, rx) = mpsc::channel(); + let callback_source = EmptyCallback::new(Box::new(move || { + let _ = tx.send(()); + })); + let callback_source = Box::new(callback_source) as Box; + self.queue.append(callback_source); + *self.sleep_until_end.lock().unwrap() = Some(rx); } /// Gets the volume of the sound. @@ -353,7 +362,7 @@ impl Sink { impl Drop for Sink { #[inline] fn drop(&mut self) { - self.queue_tx.set_keep_alive_if_empty(false); + self.queue.keep_alive_if_empty(false); if !self.detached { self.controls.stopped.store(true, Ordering::Relaxed); @@ -366,6 +375,7 @@ mod tests { use std::sync::atomic::Ordering; use crate::buffer::SamplesBuffer; + use crate::math::ch; use crate::{Sink, Source}; #[test] @@ -382,8 +392,8 @@ mod tests { let v = vec![10.0, -10.0, 20.0, -20.0, 30.0, -30.0]; // Low rate to ensure immediate control. - sink.append(SamplesBuffer::new(1, 1, v.clone())); - let mut reference_src = SamplesBuffer::new(1, 1, v); + sink.append(SamplesBuffer::new(ch!(1), 1, v.clone())); + let mut reference_src = SamplesBuffer::new(ch!(1), 1, v); assert_eq!(source.next(), reference_src.next()); assert_eq!(source.next(), reference_src.next()); @@ -410,8 +420,8 @@ mod tests { let v = vec![10.0, -10.0, 20.0, -20.0, 30.0, -30.0]; - sink.append(SamplesBuffer::new(1, 1, v.clone())); - let mut src = SamplesBuffer::new(1, 1, v.clone()); + sink.append(SamplesBuffer::new(ch!(1), 1, v.clone())); + let mut src = SamplesBuffer::new(ch!(1), 1, v.clone()); assert_eq!(queue_rx.next(), src.next()); assert_eq!(queue_rx.next(), src.next()); @@ -421,8 +431,8 @@ mod tests { assert!(sink.controls.stopped.load(Ordering::SeqCst)); assert_eq!(queue_rx.next(), Some(0.0)); - src = SamplesBuffer::new(1, 1, v.clone()); - sink.append(SamplesBuffer::new(1, 1, v)); + src = SamplesBuffer::new(ch!(1), 1, v.clone()); + sink.append(SamplesBuffer::new(ch!(1), 1, v)); assert!(!sink.controls.stopped.load(Ordering::SeqCst)); // Flush silence @@ -439,8 +449,8 @@ mod tests { let v = vec![10.0, -10.0, 20.0, -20.0, 30.0, -30.0]; // High rate to avoid immediate control. - sink.append(SamplesBuffer::new(2, 44100, v.clone())); - let src = SamplesBuffer::new(2, 44100, v.clone()); + sink.append(SamplesBuffer::new(ch!(2), 44100, v.clone())); + let src = SamplesBuffer::new(ch!(2), 44100, v.clone()); let mut src = src.amplify(0.5); sink.set_volume(0.5); diff --git a/src/source/agc.rs b/src/source/agc.rs index 39a7019b..224f569d 100644 --- a/src/source/agc.rs +++ b/src/source/agc.rs @@ -459,8 +459,8 @@ where I: Source, { #[inline] - fn current_span_len(&self) -> Option { - self.input.current_span_len() + fn parameters_changed(&self) -> bool { + self.input.parameters_changed() } #[inline] diff --git a/src/source/amplify.rs b/src/source/amplify.rs index 87f41fa3..fdc155c3 100644 --- a/src/source/amplify.rs +++ b/src/source/amplify.rs @@ -69,8 +69,8 @@ where I: Source, { #[inline] - fn current_span_len(&self) -> Option { - self.input.current_span_len() + fn parameters_changed(&self) -> bool { + self.input.parameters_changed() } #[inline] diff --git a/src/source/blt.rs b/src/source/blt.rs index 2aad5b76..0860e2a4 100644 --- a/src/source/blt.rs +++ b/src/source/blt.rs @@ -6,67 +6,55 @@ use std::time::Duration; use super::SeekError; // Implemented following http://www.musicdsp.org/files/Audio-EQ-Cookbook.txt - -/// Internal function that builds a `BltFilter` object. -pub fn low_pass(input: I, freq: u32) -> BltFilter -where - I: Source, -{ - low_pass_with_q(input, freq, 0.5) -} - -pub fn high_pass(input: I, freq: u32) -> BltFilter -where - I: Source, -{ - high_pass_with_q(input, freq, 0.5) -} - -/// Same as low_pass but allows the q value (bandwidth) to be changed -pub fn low_pass_with_q(input: I, freq: u32, q: f32) -> BltFilter -where - I: Source, -{ - BltFilter { - input, - formula: BltFormula::LowPass { freq, q }, - applier: None, - x_n1: 0.0, - x_n2: 0.0, - y_n1: 0.0, - y_n2: 0.0, - } -} - -/// Same as high_pass but allows the q value (bandwidth) to be changed -pub fn high_pass_with_q(input: I, freq: u32, q: f32) -> BltFilter -where - I: Source, -{ - BltFilter { - input, - formula: BltFormula::HighPass { freq, q }, - applier: None, - x_n1: 0.0, - x_n2: 0.0, - y_n1: 0.0, - y_n2: 0.0, - } -} - /// This applies an audio filter, it can be a high or low pass filter. #[derive(Clone, Debug)] pub struct BltFilter { input: I, formula: BltFormula, - applier: Option, + applier: BltApplier, x_n1: f32, x_n2: f32, y_n1: f32, y_n2: f32, } -impl BltFilter { +impl> BltFilter { + pub(crate) fn low_pass(input: I, freq: u32) -> BltFilter { + Self::low_pass_with_q(input, freq, 0.5) + } + + pub(crate) fn high_pass(input: I, freq: u32) -> BltFilter { + Self::high_pass_with_q(input, freq, 0.5) + } + + pub(crate) fn low_pass_with_q(input: I, freq: u32, q: f32) -> BltFilter { + let formula = BltFormula::LowPass { freq, q }; + let applier = formula.to_applier(input.sample_rate()); + BltFilter { + input, + formula, + applier, + x_n1: 0.0, + x_n2: 0.0, + y_n1: 0.0, + y_n2: 0.0, + } + } + + pub(crate) fn high_pass_with_q(input: I, freq: u32, q: f32) -> BltFilter { + let formula = BltFormula::HighPass { freq, q }; + let applier = formula.to_applier(input.sample_rate()); + BltFilter { + input, + formula, + applier, + x_n1: 0.0, + x_n2: 0.0, + y_n1: 0.0, + y_n2: 0.0, + } + } + /// Modifies this filter so that it becomes a low-pass filter. pub fn to_low_pass(&mut self, freq: u32) { self.to_low_pass_with_q(freq, 0.5); @@ -80,13 +68,13 @@ impl BltFilter { /// Same as to_low_pass but allows the q value (bandwidth) to be changed pub fn to_low_pass_with_q(&mut self, freq: u32, q: f32) { self.formula = BltFormula::LowPass { freq, q }; - self.applier = None; + self.applier = self.formula.to_applier(self.input.sample_rate()); } /// Same as to_high_pass but allows the q value (bandwidth) to be changed pub fn to_high_pass_with_q(&mut self, freq: u32, q: f32) { self.formula = BltFormula::HighPass { freq, q }; - self.applier = None; + self.applier = self.formula.to_applier(self.input.sample_rate()); } /// Returns a reference to the inner source. @@ -116,17 +104,13 @@ where #[inline] fn next(&mut self) -> Option { - let last_in_span = self.input.current_span_len() == Some(1); - - if self.applier.is_none() { - self.applier = Some(self.formula.to_applier(self.input.sample_rate())); + if self.input.parameters_changed() { + self.applier = self.formula.to_applier(self.input.sample_rate()); } let sample = self.input.next()?; let result = self .applier - .as_ref() - .unwrap() .apply(sample, self.x_n1, self.x_n2, self.y_n1, self.y_n2); self.y_n2 = self.y_n1; @@ -134,10 +118,6 @@ where self.y_n1 = result; self.x_n1 = sample; - if last_in_span { - self.applier = None; - } - Some(result) } @@ -154,8 +134,8 @@ where I: Source, { #[inline] - fn current_span_len(&self) -> Option { - self.input.current_span_len() + fn parameters_changed(&self) -> bool { + self.input.parameters_changed() } #[inline] diff --git a/src/source/buffered.rs b/src/source/buffered.rs index 402e63bb..08d1a0a6 100644 --- a/src/source/buffered.rs +++ b/src/source/buffered.rs @@ -1,35 +1,22 @@ -use std::cmp; +/// A iterator that stores extracted data in memory while allowing +/// concurrent reading in real time. use std::mem; use std::sync::{Arc, Mutex}; use std::time::Duration; -use super::SeekError; +use super::{PeekableSource, SeekError}; use crate::common::{ChannelCount, SampleRate}; +use crate::math::{ch, PrevMultipleOf}; use crate::Source; -/// Internal function that builds a `Buffered` object. -#[inline] -pub fn buffered(input: I) -> Buffered -where - I: Source, -{ - let total_duration = input.total_duration(); - let first_span = extract(input); - - Buffered { - current_span: first_span, - position_in_span: 0, - total_duration, - } -} - -/// Iterator that at the same time extracts data from the iterator and stores it in a buffer. +/// Iterator that at the same time extracts data from the iterator and +/// stores it in a buffer. pub struct Buffered where I: Source, { /// Immutable reference to the next span of data. Cannot be `Span::Input`. - current_span: Arc>, + current_span: Arc>>, /// The position in number of samples of this iterator inside `current_span`. position_in_span: usize, @@ -38,95 +25,18 @@ where total_duration: Option, } -enum Span -where - I: Source, -{ - /// Data that has already been extracted from the iterator. Also contains a pointer to the - /// next span. - Data(SpanData), - - /// No more data. - End, - - /// Unextracted data. The `Option` should never be `None` and is only here for easier data - /// processing. - Input(Mutex>), -} - -struct SpanData -where - I: Source, -{ - data: Vec, - channels: ChannelCount, - rate: SampleRate, - next: Mutex>>, -} +impl Buffered { + pub(crate) fn new(input: I) -> Buffered { + let total_duration = input.total_duration(); + let first_span = extract(input.peekable_source()); -impl Drop for SpanData -where - I: Source, -{ - fn drop(&mut self) { - // This is necessary to prevent stack overflows deallocating long chains of the mutually - // recursive `Span` and `SpanData` types. This iteratively traverses as much of the - // chain as needs to be deallocated, and repeatedly "pops" the head off the list. This - // solves the problem, as when the time comes to actually deallocate the `SpanData`, - // the `next` field will contain a `Span::End`, or an `Arc` with additional references, - // so the depth of recursive drops will be bounded. - while let Ok(arc_next) = self.next.get_mut() { - if let Some(next_ref) = Arc::get_mut(arc_next) { - // This allows us to own the next Span. - let next = mem::replace(next_ref, Span::End); - if let Span::Data(next_data) = next { - // Swap the current SpanData with the next one, allowing the current one - // to go out of scope. - *self = next_data; - } else { - break; - } - } else { - break; - } + Buffered { + current_span: first_span, + position_in_span: 0, + total_duration, } } -} -/// Builds a span from the input iterator. -fn extract(mut input: I) -> Arc> -where - I: Source, -{ - let span_len = input.current_span_len(); - - if span_len == Some(0) { - return Arc::new(Span::End); - } - - let channels = input.channels(); - let rate = input.sample_rate(); - let data: Vec = input - .by_ref() - .take(cmp::min(span_len.unwrap_or(32768), 32768)) - .collect(); - - if data.is_empty() { - return Arc::new(Span::End); - } - - Arc::new(Span::Data(SpanData { - data, - channels, - rate, - next: Mutex::new(Arc::new(Span::Input(Mutex::new(Some(input))))), - })) -} - -impl Buffered -where - I: Source, -{ /// Advances to the next span. fn next_span(&mut self) { let next_span = { @@ -161,6 +71,7 @@ where #[inline] fn next(&mut self) -> Option { + dbg!(); let current_sample; let advance_span; @@ -168,6 +79,7 @@ where Span::Data(SpanData { data, .. }) => { current_sample = Some(data[self.position_in_span]); self.position_in_span += 1; + dbg!(self.position_in_span); advance_span = self.position_in_span >= data.len(); } @@ -180,41 +92,28 @@ where }; if advance_span { + dbg!(); self.next_span(); } current_sample } - - #[inline] - fn size_hint(&self) -> (usize, Option) { - // TODO: - (0, None) - } } -// TODO: uncomment when `size_hint` is fixed -/*impl ExactSizeIterator for Amplify where I: Source + ExactSizeIterator, I::Item: Sample { -}*/ - impl Source for Buffered where I: Source, { #[inline] - fn current_span_len(&self) -> Option { - match &*self.current_span { - Span::Data(SpanData { data, .. }) => Some(data.len() - self.position_in_span), - Span::End => Some(0), - Span::Input(_) => unreachable!(), - } + fn parameters_changed(&self) -> bool { + dbg!(self.position_in_span) == 1 } #[inline] fn channels(&self) -> ChannelCount { match *self.current_span { Span::Data(SpanData { channels, .. }) => channels, - Span::End => 1, + Span::End => ch!(1), Span::Input(_) => unreachable!(), } } @@ -223,7 +122,7 @@ where fn sample_rate(&self) -> SampleRate { match *self.current_span { Span::Data(SpanData { rate, .. }) => rate, - Span::End => 44100, + Span::End => 1, Span::Input(_) => unreachable!(), } } @@ -256,3 +155,102 @@ where } } } + +enum Span +where + I: Source, +{ + /// Data that has already been extracted from the iterator. + /// Also contains a pointer to the next span. + Data(SpanData), + + /// No more data. + End, + + /// Unextracted data. The `Option` should never be `None` and is only here for easier data + /// processing. + Input(Mutex>), +} + +impl std::fmt::Debug for Span { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Span::Data(_) => f.write_str("Span::Data"), + Span::End => f.write_str("Span::End"), + Span::Input(_) => f.write_str("Span::Input"), + } + } +} + +struct SpanData +where + I: Source, +{ + data: Vec, + channels: ChannelCount, + rate: SampleRate, + next: Mutex>>, +} + +impl Drop for SpanData +where + I: Source, +{ + fn drop(&mut self) { + // This is necessary to prevent stack overflows deallocating long chains of the mutually + // recursive `Span` and `SpanData` types. This iteratively traverses as much of the + // chain as needs to be deallocated, and repeatedly "pops" the head off the list. This + // solves the problem, as when the time comes to actually deallocate the `SpanData`, + // the `next` field will contain a `Span::End`, or an `Arc` with additional references, + // so the depth of recursive drops will be bounded. + while let Ok(arc_next) = self.next.get_mut() { + if let Some(next_ref) = Arc::get_mut(arc_next) { + // This allows us to own the next Span. + let next = mem::replace(next_ref, Span::End); + if let Span::Data(next_data) = next { + // Swap the current SpanData with the next one, allowing the current one + // to go out of scope. + *self = next_data; + } else { + break; + } + } else { + break; + } + } + } +} + +/// Builds a span from the input iterator. +fn extract(mut input: PeekableSource) -> Arc>> +where + I: Source, +{ + let channels = input.channels(); + let rate = input.sample_rate(); + + let mut data = Vec::new(); + loop { + let Some(sample) = input.next() else { + break; + }; + data.push(sample); + if input.peek_parameters_changed() { + break; + } + if data.len() > 32768.prev_multiple_of(channels.into()) { + break; + } + } + + if data.is_empty() { + return Arc::new(Span::End); + } + + Arc::new(Span::Data(SpanData { + data, + channels, + rate, + next: Mutex::new(Arc::new(Span::Input(Mutex::new(Some(input))))), + })) +} diff --git a/src/source/buffered2.rs b/src/source/buffered2.rs new file mode 100644 index 00000000..a40d8c37 --- /dev/null +++ b/src/source/buffered2.rs @@ -0,0 +1,125 @@ +use std::sync::{Arc, Mutex, MutexGuard, TryLockError}; + +use append_only_vec::AppendOnlyVec; + +use crate::{ChannelCount, Sample, SampleRate}; + +use super::Source; + +struct ParameterChange { + index: usize, + channel_count: ChannelCount, + sample_rate: SampleRate, +} + +struct Shared { + input: Mutex, + parameters: AppendOnlyVec, + samples_in_memory: AppendOnlyVec, +} + +#[derive(Clone)] +struct Buffered { + shared: Arc>, + next_parameter_change_at: usize, + channel_count: ChannelCount, + sample_rate: SampleRate, + samples_index: usize, + parameter_changes_index: usize, +} + +impl Buffered { + fn next_parameter_change_at(&self) -> usize { + if self.parameter_changes_index > self.shared.parameters.len() { + usize::MAX + } else { + self.shared.parameters[self.parameter_changes_index].index + } + } +} + +impl Iterator for Buffered { + type Item = Sample; + + fn next(&mut self) -> Option { + loop { + if self.shared.samples_in_memory.len() < self.samples_index { + let sample = self.shared.samples_in_memory[self.samples_index]; + + if self.samples_index == self.next_parameter_change_at { + let new_params = &self.shared.parameters[self.parameter_changes_index]; + self.sample_rate = new_params.sample_rate; + self.channel_count = new_params.channel_count; + } + + // sample after sample where flag a parameter_change + if self.samples_index > self.next_parameter_change_at { + self.next_parameter_change_at = + if self.parameter_changes_index > self.shared.parameters.len() { + usize::MAX + } else { + self.shared.parameters[self.parameter_changes_index].index + }; + } + + self.samples_index += 1; + return Some(sample); + } + + match self.shared.input.try_lock() { + Ok(input) => read_chunk( + self.samples_index, + input, + &self.shared.samples_in_memory, + &self.shared.parameters, + ), + Err(TryLockError::WouldBlock) => { + let _wait_for_other_to_finish_read_chunk = self.shared.input.lock(); + } + Err(TryLockError::Poisoned(_)) => panic!("reader panicked in Buffered"), + } + } + } +} + +fn read_chunk( + start_index: usize, + mut input: MutexGuard<'_, I>, + in_memory: &AppendOnlyVec, + parameter_changes: &AppendOnlyVec, +) { + let taken = 0; + loop { + if let Some(sample) = input.next() { + in_memory.push(sample); + } + if input.parameters_changed() { + parameter_changes.push(ParameterChange { + index: start_index + taken, + channel_count: input.channels(), + sample_rate: input.sample_rate(), + }); + } + if taken < 2usize.pow(15) { + break; + } + } +} + +impl Source for Buffered { + fn parameters_changed(&self) -> bool { + self.next_parameter_change_at != self.samples_index + } + + fn channels(&self) -> crate::ChannelCount { + self.channel_count + } + + fn sample_rate(&self) -> crate::SampleRate { + self.sample_rate + } + + fn total_duration(&self) -> Option { + todo!() + } +} diff --git a/src/source/channel_volume.rs b/src/source/channel_volume.rs index 89b2280d..3ee1508f 100644 --- a/src/source/channel_volume.rs +++ b/src/source/channel_volume.rs @@ -24,10 +24,10 @@ where /// Wrap the input source and make it mono. Play that mono sound to each /// channel at the volume set by the user. The volume can be changed using /// [`ChannelVolume::set_volume`]. - pub fn new(input: I, channel_volumes: Vec) -> ChannelVolume - where - I: Source, - { + /// + /// # Panics if channel_volumes is empty + pub fn new(input: I, channel_volumes: Vec) -> ChannelVolume { + assert!(!channel_volumes.is_empty()); let channel_count = channel_volumes.len(); // See next() implementation. ChannelVolume { input, @@ -75,12 +75,12 @@ where self.current_channel = 0; self.current_sample = None; let num_channels = self.input.channels(); - for _ in 0..num_channels { + for _ in 0..num_channels.get() { if let Some(s) = self.input.next() { self.current_sample = Some(self.current_sample.unwrap_or(0.0) + s); } } - self.current_sample.map(|s| s / num_channels as f32); + self.current_sample.map(|s| s / num_channels.get() as f32); } let result = self .current_sample @@ -102,13 +102,14 @@ where I: Source, { #[inline] - fn current_span_len(&self) -> Option { - self.input.current_span_len() + fn parameters_changed(&self) -> bool { + self.input.parameters_changed() } #[inline] fn channels(&self) -> ChannelCount { - self.channel_volumes.len() as ChannelCount + ChannelCount::new(self.channel_volumes.len() as u16) + .expect("checked to be non-empty in new implementation") } #[inline] diff --git a/src/source/chirp.rs b/src/source/chirp.rs index 4b99eb85..9768c940 100644 --- a/src/source/chirp.rs +++ b/src/source/chirp.rs @@ -1,6 +1,7 @@ //! Chirp/sweep source. use crate::common::{ChannelCount, SampleRate}; +use crate::math::ch; use crate::Source; use std::{f32::consts::TAU, time::Duration}; @@ -57,12 +58,12 @@ impl Iterator for Chirp { } impl Source for Chirp { - fn current_span_len(&self) -> Option { - None + fn parameters_changed(&self) -> bool { + false } fn channels(&self) -> ChannelCount { - 1 + ch!(1) } fn sample_rate(&self) -> SampleRate { diff --git a/src/source/crossfade.rs b/src/source/crossfade.rs index 0109c5e4..91315648 100644 --- a/src/source/crossfade.rs +++ b/src/source/crossfade.rs @@ -7,7 +7,7 @@ use std::time::Duration; /// /// Only the crossfaded portion (beginning of fadeout, beginning of fadein) is /// returned. -pub fn crossfade( +pub(crate) fn crossfade( input_fadeout: I1, input_fadein: I2, duration: Duration, @@ -16,8 +16,7 @@ where I1: Source, I2: Source, { - let mut input_fadeout = input_fadeout.take_duration(duration); - input_fadeout.set_filter_fadeout(); + let input_fadeout = input_fadeout.take_duration(duration).with_fadeout(true); let input_fadein = input_fadein.take_duration(duration).fade_in(duration); input_fadeout.mix(input_fadein) } @@ -28,49 +27,3 @@ where /// Only the crossfaded portion (beginning of fadeout, beginning of fadein) is /// covered. pub type Crossfade = Mix, FadeIn>>; - -#[cfg(test)] -mod tests { - use super::*; - use crate::buffer::SamplesBuffer; - use crate::source::Zero; - - fn dummy_source(length: u8) -> SamplesBuffer { - let data: Vec = (1..=length).map(f32::from).collect(); - SamplesBuffer::new(1, 1, data) - } - - #[test] - fn test_crossfade_with_self() { - let source1 = dummy_source(10); - let source2 = dummy_source(10); - let mut mixed = crossfade( - source1, - source2, - Duration::from_secs(5) + Duration::from_nanos(1), - ); - assert_eq!(mixed.next(), Some(1.0)); - assert_eq!(mixed.next(), Some(2.0)); - assert_eq!(mixed.next(), Some(3.0)); - assert_eq!(mixed.next(), Some(4.0)); - assert_eq!(mixed.next(), Some(5.0)); - assert_eq!(mixed.next(), None); - } - - #[test] - fn test_crossfade() { - let source1 = dummy_source(10); - let source2 = Zero::new(1, 1); - let mixed = crossfade( - source1, - source2, - Duration::from_secs(5) + Duration::from_nanos(1), - ); - let result = mixed.collect::>(); - assert_eq!(result.len(), 5); - assert!(result - .iter() - .zip(vec![1.0, 2.0 * 0.8, 3.0 * 0.6, 4.0 * 0.4, 5.0 * 0.2]) - .all(|(a, b)| (a - b).abs() < 1e-6)); - } -} diff --git a/src/source/delay.rs b/src/source/delay.rs index f2476ddd..6edab2eb 100644 --- a/src/source/delay.rs +++ b/src/source/delay.rs @@ -10,7 +10,7 @@ fn remaining_samples( channels: ChannelCount, ) -> usize { let ns = until_playback.as_secs() * 1_000_000_000 + until_playback.subsec_nanos() as u64; - let samples = ns * channels as u64 * sample_rate as u64 / 1_000_000_000; + let samples = ns * channels.get() as u64 * sample_rate as u64 / 1_000_000_000; samples as usize } @@ -88,10 +88,8 @@ where I: Iterator + Source, { #[inline] - fn current_span_len(&self) -> Option { - self.input - .current_span_len() - .map(|val| val + self.remaining_samples) + fn parameters_changed(&self) -> bool { + self.input.parameters_changed() } #[inline] diff --git a/src/source/done.rs b/src/source/done.rs index 5bc922b7..ca56d0ca 100644 --- a/src/source/done.rs +++ b/src/source/done.rs @@ -71,8 +71,8 @@ where I: Source, { #[inline] - fn current_span_len(&self) -> Option { - self.input.current_span_len() + fn parameters_changed(&self) -> bool { + self.input.parameters_changed() } #[inline] diff --git a/src/source/empty.rs b/src/source/empty.rs index 8c8b4853..6608ce59 100644 --- a/src/source/empty.rs +++ b/src/source/empty.rs @@ -2,6 +2,7 @@ use std::time::Duration; use super::SeekError; use crate::common::{ChannelCount, SampleRate}; +use crate::math::ch; use crate::{Sample, Source}; /// An empty source. @@ -35,13 +36,13 @@ impl Iterator for Empty { impl Source for Empty { #[inline] - fn current_span_len(&self) -> Option { - None + fn parameters_changed(&self) -> bool { + false } #[inline] fn channels(&self) -> ChannelCount { - 1 + ch!(1) } #[inline] diff --git a/src/source/empty_callback.rs b/src/source/empty_callback.rs index 08d8b0fb..e239a471 100644 --- a/src/source/empty_callback.rs +++ b/src/source/empty_callback.rs @@ -2,6 +2,7 @@ use std::time::Duration; use super::SeekError; use crate::common::{ChannelCount, SampleRate}; +use crate::math::ch; use crate::{Sample, Source}; /// An empty source that executes a callback function @@ -32,13 +33,13 @@ impl Iterator for EmptyCallback { impl Source for EmptyCallback { #[inline] - fn current_span_len(&self) -> Option { - None + fn parameters_changed(&self) -> bool { + false } #[inline] fn channels(&self) -> ChannelCount { - 1 + ch!(1) } #[inline] diff --git a/src/source/fadein.rs b/src/source/fadein.rs index 85e19293..88c5c4a2 100644 --- a/src/source/fadein.rs +++ b/src/source/fadein.rs @@ -67,8 +67,8 @@ where I: Source, { #[inline] - fn current_span_len(&self) -> Option { - self.inner().current_span_len() + fn parameters_changed(&self) -> bool { + self.inner().parameters_changed() } #[inline] diff --git a/src/source/fadeout.rs b/src/source/fadeout.rs index 14a41569..b987e074 100644 --- a/src/source/fadeout.rs +++ b/src/source/fadeout.rs @@ -67,8 +67,8 @@ where I: Source, { #[inline] - fn current_span_len(&self) -> Option { - self.inner().current_span_len() + fn parameters_changed(&self) -> bool { + self.inner().parameters_changed() } #[inline] diff --git a/src/source/from_iter.rs b/src/source/from_iter.rs index 48251763..acd712b1 100644 --- a/src/source/from_iter.rs +++ b/src/source/from_iter.rs @@ -2,6 +2,7 @@ use std::time::Duration; use super::SeekError; use crate::common::{ChannelCount, SampleRate}; +use crate::math::ch; use crate::Source; /// Builds a source that chains sources provided by an iterator. @@ -76,39 +77,41 @@ where I::Item: Iterator + Source, { #[inline] - fn current_span_len(&self) -> Option { + fn parameters_changed(&self) -> bool { // This function is non-trivial because the boundary between the current source and the // next must be a span boundary as well. // - // The current sound is free to return `None` for `current_span_len()`, in which case + // The current sound is free to return `None` for `parameters_changed()`, in which case // we *should* return the number of samples remaining the current sound. // This can be estimated with `size_hint()`. // // If the `size_hint` is `None` as well, we are in the worst case scenario. To handle this // situation we force a span to have a maximum number of samples indicate by this // constant. - const THRESHOLD: usize = 10240; - // Try the current `current_span_len`. - if let Some(src) = &self.current_source { - if let Some(val) = src.current_span_len() { - if val != 0 { - return Some(val); - } - } - } - - // Try the size hint. - if let Some(src) = &self.current_source { - if let Some(val) = src.size_hint().1 { - if val < THRESHOLD && val != 0 { - return Some(val); - } - } - } - - // Otherwise we use the constant value. - Some(THRESHOLD) + todo!() + // const THRESHOLD: usize = 10240; + // + // // Try the current `current_span_len`. + // if let Some(src) = &self.current_source { + // if let Some(val) = src.parameters_changed() { + // if val != 0 { + // return Some(val); + // } + // } + // } + // + // // Try the size hint. + // if let Some(src) = &self.current_source { + // if let Some(val) = src.size_hint().1 { + // if val < THRESHOLD && val != 0 { + // return Some(val); + // } + // } + // } + // + // // Otherwise we use the constant value. + // Some(THRESHOLD) } #[inline] @@ -117,7 +120,7 @@ where src.channels() } else { // Dummy value that only happens if the iterator was empty. - 2 + ch!(2) } } @@ -149,21 +152,22 @@ where #[cfg(test)] mod tests { use crate::buffer::SamplesBuffer; + use crate::math::ch; use crate::source::{from_iter, Source}; #[test] fn basic() { let mut rx = from_iter((0..2).map(|n| { if n == 0 { - SamplesBuffer::new(1, 48000, vec![10.0, -10.0, 10.0, -10.0]) + SamplesBuffer::new(ch!(1), 48000, vec![10.0, -10.0, 10.0, -10.0]) } else if n == 1 { - SamplesBuffer::new(2, 96000, vec![5.0, 5.0, 5.0, 5.0]) + SamplesBuffer::new(ch!(2), 96000, vec![5.0, 5.0, 5.0, 5.0]) } else { unreachable!() } })); - assert_eq!(rx.channels(), 1); + assert_eq!(rx.channels(), ch!(1)); assert_eq!(rx.sample_rate(), 48000); assert_eq!(rx.next(), Some(10.0)); assert_eq!(rx.next(), Some(-10.0)); diff --git a/src/source/linear_ramp.rs b/src/source/linear_ramp.rs index af830a5e..9fef3d9d 100644 --- a/src/source/linear_ramp.rs +++ b/src/source/linear_ramp.rs @@ -45,7 +45,7 @@ impl LinearGainRamp where I: Source, { - /// Returns a reference to the innner source. + /// Returns a reference to the inner source. #[inline] pub fn inner(&self) -> &I { &self.input @@ -88,7 +88,7 @@ where factor = self.start_gain * (1.0f32 - p) + self.end_gain * p; } - if self.sample_idx % (self.channels() as u64) == 0 { + if self.sample_idx % (self.channels().get() as u64) == 0 { self.elapsed_ns += 1000000000.0 / (self.input.sample_rate() as f32); } @@ -108,8 +108,8 @@ where I: Source, { #[inline] - fn current_span_len(&self) -> Option { - self.input.current_span_len() + fn parameters_changed(&self) -> bool { + self.input.parameters_changed() } #[inline] @@ -140,13 +140,14 @@ mod tests { use super::*; use crate::buffer::SamplesBuffer; + use crate::math::ch; use crate::Sample; /// Create a SamplesBuffer of identical samples with value `value`. - /// Returned buffer is one channel and has a sample rate of 1 hz. + /// Returned buffer is one channel and has a sample rate of 1 Hz. fn const_source(length: u8, value: Sample) -> SamplesBuffer { let data: Vec = (1..=length).map(|_| value).collect(); - SamplesBuffer::new(1, 1, data) + SamplesBuffer::new(ch!(1), 1, data) } /// Create a SamplesBuffer of repeating sample values from `values`. @@ -156,7 +157,7 @@ mod tests { .map(|(i, _)| values[i % values.len()]) .collect(); - SamplesBuffer::new(1, 1, data) + SamplesBuffer::new(ch!(1), 1, data) } #[test] diff --git a/src/source/mix.rs b/src/source/mix.rs index c5fedfb6..21837ddb 100644 --- a/src/source/mix.rs +++ b/src/source/mix.rs @@ -80,14 +80,8 @@ where I2: Source, { #[inline] - fn current_span_len(&self) -> Option { - let f1 = self.input1.current_span_len(); - let f2 = self.input2.current_span_len(); - - match (f1, f2) { - (Some(f1), Some(f2)) => Some(cmp::min(f1, f2)), - _ => None, - } + fn parameters_changed(&self) -> bool { + false } #[inline] diff --git a/src/source/mod.rs b/src/source/mod.rs index aefbd1b2..35dbdc6f 100644 --- a/src/source/mod.rs +++ b/src/source/mod.rs @@ -7,45 +7,48 @@ use crate::common::{ChannelCount, SampleRate}; use crate::Sample; use dasp_sample::FromSample; -pub use self::agc::AutomaticGainControl; -pub use self::amplify::Amplify; -pub use self::blt::BltFilter; -pub use self::buffered::Buffered; -pub use self::channel_volume::ChannelVolume; -pub use self::chirp::{chirp, Chirp}; -pub use self::crossfade::Crossfade; -pub use self::delay::Delay; -pub use self::done::Done; -pub use self::empty::Empty; -pub use self::empty_callback::EmptyCallback; -pub use self::fadein::FadeIn; -pub use self::fadeout::FadeOut; -pub use self::from_factory::{from_factory, FromFactoryIter}; -pub use self::from_iter::{from_iter, FromIter}; -pub use self::linear_ramp::LinearGainRamp; -pub use self::mix::Mix; -pub use self::pausable::Pausable; -pub use self::periodic::PeriodicAccess; -pub use self::position::TrackPosition; -pub use self::repeat::Repeat; -pub use self::sawtooth::SawtoothWave; -pub use self::signal_generator::{Function, SignalGenerator}; -pub use self::sine::SineWave; -pub use self::skip::SkipDuration; -pub use self::skippable::Skippable; -pub use self::spatial::Spatial; -pub use self::speed::Speed; -pub use self::square::SquareWave; -pub use self::stoppable::Stoppable; -pub use self::take::TakeDuration; -pub use self::triangle::TriangleWave; -pub use self::uniform::UniformSourceIterator; -pub use self::zero::Zero; +pub use agc::AutomaticGainControl; +pub use amplify::Amplify; +pub use blt::BltFilter; +pub use buffered::Buffered; +pub use channel_volume::ChannelVolume; +pub use chirp::{chirp, Chirp}; +pub use crossfade::Crossfade; +pub use delay::Delay; +pub use done::Done; +pub use empty::Empty; +pub use empty_callback::EmptyCallback; +pub use fadein::FadeIn; +pub use fadeout::FadeOut; +pub use from_factory::{from_factory, FromFactoryIter}; +pub use from_iter::{from_iter, FromIter}; +pub use linear_ramp::LinearGainRamp; +pub use mix::Mix; +pub use pausable::Pausable; +pub use peekable::PeekableSource; +pub use periodic::PeriodicAccess; +pub use position::TrackPosition; +pub use repeat::Repeat; +pub use sawtooth::SawtoothWave; +pub use signal_generator::{Function, SignalGenerator}; +pub use sine::SineWave; +pub use skip_duration::SkipDuration; +pub use skippable::Skippable; +pub use spatial::Spatial; +pub use speed::Speed; +pub use square::SquareWave; +pub use stoppable::Stoppable; +pub use take_duration::TakeDuration; +pub use take_samples::TakeSamples; +pub use triangle::TriangleWave; +pub use uniform::UniformSourceIterator; +pub use zero::Zero; mod agc; mod amplify; mod blt; mod buffered; +mod buffered2; mod channel_volume; mod chirp; mod crossfade; @@ -60,19 +63,21 @@ mod from_iter; mod linear_ramp; mod mix; mod pausable; +mod peekable; mod periodic; mod position; mod repeat; mod sawtooth; mod signal_generator; mod sine; -mod skip; +mod skip_duration; mod skippable; mod spatial; mod speed; mod square; mod stoppable; -mod take; +mod take_duration; +mod take_samples; mod triangle; mod uniform; mod zero; @@ -146,20 +151,20 @@ pub use self::noise::{pink, white, PinkNoise, WhiteNoise}; /// stay the same for long periods of time and avoids calling `channels()` and /// `sample_rate` too frequently. /// -/// In order to properly handle this situation, the `current_span_len()` method should return +/// In order to properly handle this situation, the `parameters_changed()` method should return /// the number of samples that remain in the iterator before the samples rate and number of /// channels can potentially change. /// pub trait Source: Iterator { - /// Returns the number of samples before the current span ends. `None` means "infinite" or - /// "until the sound ends". - /// Should never return 0 unless there's no more data. + /// Whether the value of `channels()` and/or `sample_rate()` have changed. + /// This is true before the next call to `Source::next`. After that + /// `Source::next` call this will be false again. /// - /// After the engine has finished reading the specified number of samples, it will check - /// whether the value of `channels()` and/or `sample_rate()` have changed. - fn current_span_len(&self) -> Option; + /// The value before the first call to next is not defined. + fn parameters_changed(&self) -> bool; /// Returns the number of channels. Channels are always interleaved. + /// Should never be Zero fn channels(&self) -> ChannelCount; /// Returns the rate at which the source should be played. In number of samples per second. @@ -170,13 +175,14 @@ pub trait Source: Iterator { /// `None` indicates at the same time "infinite" or "unknown". fn total_duration(&self) -> Option; - /// Stores the source in a buffer in addition to returning it. This iterator can be cloned. + /// Stores the source in a buffer in addition to returning it. This iterator can + /// be cloned. Note that this incurs significant additional latency. #[inline] fn buffered(self) -> Buffered where Self: Sized, { - buffered::buffered(self) + Buffered::new(self) } /// Mixes this source with another one. @@ -198,7 +204,7 @@ pub trait Source: Iterator { where Self: Sized, { - repeat::repeat(self) + Repeat::new(self) } /// Takes a certain duration of this source and then stops. @@ -207,7 +213,17 @@ pub trait Source: Iterator { where Self: Sized, { - take::take_duration(self, duration) + TakeDuration::new(self, duration) + } + + /// Takes n samples. N might be slightly adjusted to make sure + /// this source ends on a frame boundary + #[inline] + fn take_samples(self, n: usize) -> TakeSamples + where + Self: Sized, + { + TakeSamples::new(self, n) } /// Delays the sound by a certain duration. @@ -230,7 +246,7 @@ pub trait Source: Iterator { where Self: Sized, { - skip::skip_duration(self, duration) + SkipDuration::new(self, duration) } /// Amplifies the sound by the given value. @@ -351,6 +367,14 @@ pub trait Source: Iterator { crossfade::crossfade(self, other, duration) } + /// Erases the type of self by placing it on the heap + fn type_erased(self) -> Box + where + Self: Sized + Send + 'static, + { + Box::new(self) + } + /// Fades in the sound. #[inline] fn fade_in(self, duration: Duration) -> FadeIn @@ -494,7 +518,23 @@ pub trait Source: Iterator { where Self: Sized, { - position::track_position(self) + TrackPosition::new(self) + } + + /// This is the [`Source`] equivalent of + /// [`Iterator::peek`](std::iter::Iterator::peekable). Creates an iterator + /// which can use the [`peek`](PeekableSource::peek) method to look at the next + /// element of the iterator without consuming it. See their documentation + /// for more information. + + /// # Note + /// The underlying source is advanced by one sample when you call this + /// function. Any side effects of the next method will occur. + fn peekable_source(self) -> PeekableSource + where + Self: Sized, + { + PeekableSource::new(self) } /// Applies a low-pass filter to the source. @@ -505,7 +545,7 @@ pub trait Source: Iterator { Self: Sized, Self: Source, { - blt::low_pass(self, freq) + BltFilter::low_pass(self, freq) } /// Applies a high-pass filter to the source. @@ -515,7 +555,7 @@ pub trait Source: Iterator { Self: Sized, Self: Source, { - blt::high_pass(self, freq) + BltFilter::high_pass(self, freq) } /// Applies a low-pass filter to the source while allowing the q (bandwidth) to be changed. @@ -525,7 +565,7 @@ pub trait Source: Iterator { Self: Sized, Self: Source, { - blt::low_pass_with_q(self, freq, q) + BltFilter::low_pass_with_q(self, freq, q) } /// Applies a high-pass filter to the source while allowing the q (bandwidth) to be changed. @@ -535,7 +575,7 @@ pub trait Source: Iterator { Self: Sized, Self: Source, { - blt::high_pass_with_q(self, freq, q) + BltFilter::high_pass_with_q(self, freq, q) } // There is no `can_seek()` method as it is impossible to use correctly. Between @@ -646,8 +686,8 @@ macro_rules! source_pointer_impl { ($($sig:tt)+) => { impl $($sig)+ { #[inline] - fn current_span_len(&self) -> Option { - (**self).current_span_len() + fn parameters_changed(&self) -> bool { + (**self).parameters_changed() } #[inline] diff --git a/src/source/noise.rs b/src/source/noise.rs index 03a2e2dd..be4a1587 100644 --- a/src/source/noise.rs +++ b/src/source/noise.rs @@ -60,8 +60,8 @@ impl Iterator for WhiteNoise { impl Source for WhiteNoise { #[inline] - fn current_span_len(&self) -> Option { - None + fn parameters_changed(&self) -> bool { + false } #[inline] @@ -136,7 +136,7 @@ impl Iterator for PinkNoise { } impl Source for PinkNoise { - fn current_span_len(&self) -> Option { + fn parameters_changed(&self) -> bool { None } diff --git a/src/source/pausable.rs b/src/source/pausable.rs index f9fddadc..8c8800f0 100644 --- a/src/source/pausable.rs +++ b/src/source/pausable.rs @@ -31,7 +31,7 @@ where pub struct Pausable { input: I, paused_channels: Option, - remaining_paused_samples: ChannelCount, + remaining_paused_samples: u16, } impl Pausable @@ -83,7 +83,7 @@ where } if let Some(paused_channels) = self.paused_channels { - self.remaining_paused_samples = paused_channels - 1; + self.remaining_paused_samples = paused_channels.get() - 1; return Some(0.0); } @@ -101,8 +101,8 @@ where I: Source, { #[inline] - fn current_span_len(&self) -> Option { - self.input.current_span_len() + fn parameters_changed(&self) -> bool { + self.input.parameters_changed() } #[inline] diff --git a/src/source/peekable.rs b/src/source/peekable.rs new file mode 100644 index 00000000..0eb08cd4 --- /dev/null +++ b/src/source/peekable.rs @@ -0,0 +1,106 @@ +use std::time::Duration; + +use crate::{ChannelCount, Sample, SampleRate}; + +use super::Source; + +/// A source with a `peek()` method that returns the next sample without +/// advancing the source. This `struct` is created by the +/// [`peekable_source`](Source::peekable_source) method on [Source]. See its +/// documentation for more. +pub struct PeekableSource { + next: Option, + channels: ChannelCount, + sample_rate: SampleRate, + channels_after_next: ChannelCount, + sample_rate_after_next: SampleRate, + parameters_changed_after_next: bool, + parameters_changed: bool, + inner: I, +} + +impl Clone for PeekableSource { + fn clone(&self) -> Self { + Self { + next: self.next, + channels: self.channels, + sample_rate: self.sample_rate, + parameters_changed_after_next: self.parameters_changed_after_next, + parameters_changed: self.parameters_changed, + inner: self.inner.clone(), + channels_after_next: self.channels_after_next, + sample_rate_after_next: self.sample_rate_after_next, + } + } +} + +impl PeekableSource { + pub(crate) fn new(mut inner: I) -> PeekableSource { + Self { + // field order is critical! do not change + channels: inner.channels(), + sample_rate: inner.sample_rate(), + next: inner.next(), + channels_after_next: inner.channels(), + sample_rate_after_next: inner.sample_rate(), + parameters_changed_after_next: inner.parameters_changed(), + parameters_changed: false, + inner, + } + } + + /// Look at the next sample. This does not advance the source. + /// Can be used to determine if the current sample was the last. + pub fn peek_next(&self) -> Option { + self.next + } + + /// Do the parameters change after the next sample? This does not advance + /// the source. + pub fn peek_parameters_changed(&self) -> bool { + self.parameters_changed_after_next + } +} + +impl Iterator for PeekableSource { + type Item = Sample; + + fn next(&mut self) -> Option { + let item = self.next.take()?; + self.next = self.inner.next(); + + self.parameters_changed = self.parameters_changed_after_next; + self.channels = self.channels_after_next; + self.sample_rate = self.sample_rate_after_next; + + self.parameters_changed_after_next = self.inner.parameters_changed(); + self.channels_after_next = self.inner.channels(); + self.sample_rate_after_next = self.inner.sample_rate(); + + Some(item) + } + + fn size_hint(&self) -> (usize, Option) { + self.inner.size_hint() + } +} + +impl ExactSizeIterator for PeekableSource {} + +impl Source for PeekableSource { + fn parameters_changed(&self) -> bool { + self.parameters_changed + } + + fn channels(&self) -> ChannelCount { + self.channels + } + + fn sample_rate(&self) -> SampleRate { + self.sample_rate + } + + fn total_duration(&self) -> Option { + self.inner.total_duration() + } +} diff --git a/src/source/periodic.rs b/src/source/periodic.rs index 31260fd1..da960a0c 100644 --- a/src/source/periodic.rs +++ b/src/source/periodic.rs @@ -12,7 +12,8 @@ where // TODO: handle the fact that the samples rate can change // TODO: generally, just wrong let update_ms = period.as_secs() as u32 * 1_000 + period.subsec_millis(); - let update_frequency = (update_ms * source.sample_rate()) / 1000 * source.channels() as u32; + let update_frequency = + (update_ms * source.sample_rate()) / 1000 * source.channels().get() as u32; PeriodicAccess { input: source, @@ -100,8 +101,8 @@ where F: FnMut(&mut I), { #[inline] - fn current_span_len(&self) -> Option { - self.input.current_span_len() + fn parameters_changed(&self) -> bool { + self.input.parameters_changed() } #[inline] @@ -131,12 +132,13 @@ mod tests { use std::time::Duration; use crate::buffer::SamplesBuffer; + use crate::math::ch; use crate::source::Source; #[test] fn stereo_access() { // Stereo, 1Hz audio buffer - let inner = SamplesBuffer::new(2, 1, vec![10.0, -10.0, 10.0, -10.0, 20.0, -20.0]); + let inner = SamplesBuffer::new(ch!(2), 1, vec![10.0, -10.0, 10.0, -10.0, 20.0, -20.0]); let cnt = RefCell::new(0); @@ -164,7 +166,7 @@ mod tests { #[test] fn fast_access_overflow() { // 1hz is lower than 0.5 samples per 5ms - let inner = SamplesBuffer::new(1, 1, vec![10.0, -10.0, 10.0, -10.0, 20.0, -20.0]); + let inner = SamplesBuffer::new(ch!(1), 1, vec![10.0, -10.0, 10.0, -10.0, 20.0, -20.0]); let mut source = inner.periodic_access(Duration::from_millis(5), |_src| {}); source.next(); diff --git a/src/source/position.rs b/src/source/position.rs index aa6b63f8..f5a1dba1 100644 --- a/src/source/position.rs +++ b/src/source/position.rs @@ -4,31 +4,38 @@ use super::SeekError; use crate::common::{ChannelCount, SampleRate}; use crate::Source; -/// Internal function that builds a `TrackPosition` object. See trait docs for -/// details -pub fn track_position(source: I) -> TrackPosition { - TrackPosition { - input: source, - samples_counted: 0, - offset_duration: 0.0, - current_span_sample_rate: 0, - current_span_channels: 0, - current_span_len: None, - } -} - /// Tracks the elapsed duration since the start of the underlying source. -#[derive(Debug)] pub struct TrackPosition { input: I, samples_counted: usize, offset_duration: f64, current_span_sample_rate: SampleRate, current_span_channels: ChannelCount, - current_span_len: Option, } -impl TrackPosition { +impl std::fmt::Debug for TrackPosition { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("TrackPosition") + .field("samples_counted", &self.samples_counted) + .field("offset_duration", &self.offset_duration) + .field("current_span_sample_rate", &self.current_span_sample_rate) + .field("current_span_channels", &self.current_span_channels) + .finish() + } +} + +impl TrackPosition { + pub(crate) fn new(source: I) -> TrackPosition { + assert!(source.sample_rate() > 0); + TrackPosition { + samples_counted: 0, + offset_duration: 0.0, + current_span_sample_rate: source.sample_rate(), + current_span_channels: source.channels(), + input: source, + } + } + /// Returns a reference to the inner source. #[inline] pub fn inner(&self) -> &I { @@ -59,24 +66,17 @@ where /// returned by [`get_pos`](TrackPosition::get_pos). /// /// This can get confusing when using [`get_pos()`](TrackPosition::get_pos) - /// together with [`Source::try_seek()`] as the the latter does take all + /// together with [`Source::try_seek()`] as the latter does take all /// speedup's and delay's into account. Its recommended therefore to apply /// track_position after speedup's and delay's. #[inline] pub fn get_pos(&self) -> Duration { let seconds = self.samples_counted as f64 / self.input.sample_rate() as f64 - / self.input.channels() as f64 + / self.input.channels().get() as f64 + self.offset_duration; Duration::from_secs_f64(seconds) } - - #[inline] - fn set_current_span(&mut self) { - self.current_span_len = self.current_span_len(); - self.current_span_sample_rate = self.sample_rate(); - self.current_span_channels = self.channels(); - } } impl Iterator for TrackPosition @@ -87,25 +87,21 @@ where #[inline] fn next(&mut self) -> Option { - // This should only be executed once at the first call to next. - if self.current_span_len.is_none() { - self.set_current_span(); - } - let item = self.input.next(); if item.is_some() { self.samples_counted += 1; // At the end of a span add the duration of this span to // offset_duration and start collecting samples again. - if Some(self.samples_counted) == self.current_span_len() { + if self.parameters_changed() { self.offset_duration += self.samples_counted as f64 / self.current_span_sample_rate as f64 - / self.current_span_channels as f64; + / self.current_span_channels.get() as f64; // Reset. self.samples_counted = 0; - self.set_current_span(); + self.current_span_sample_rate = self.sample_rate(); + self.current_span_channels = self.channels(); }; }; item @@ -122,8 +118,8 @@ where I: Source, { #[inline] - fn current_span_len(&self) -> Option { - self.input.current_span_len() + fn parameters_changed(&self) -> bool { + self.input.parameters_changed() } #[inline] @@ -154,43 +150,3 @@ where result } } - -#[cfg(test)] -mod tests { - use std::time::Duration; - - use crate::buffer::SamplesBuffer; - use crate::source::Source; - - #[test] - fn test_position() { - let inner = SamplesBuffer::new(1, 1, vec![10.0, -10.0, 10.0, -10.0, 20.0, -20.0]); - let mut source = inner.track_position(); - - assert_eq!(source.get_pos().as_secs_f32(), 0.0); - source.next(); - assert_eq!(source.get_pos().as_secs_f32(), 1.0); - - source.next(); - assert_eq!(source.get_pos().as_secs_f32(), 2.0); - - assert_eq!(source.try_seek(Duration::new(1, 0)).is_ok(), true); - assert_eq!(source.get_pos().as_secs_f32(), 1.0); - } - - #[test] - fn test_position_in_presence_of_speedup() { - let inner = SamplesBuffer::new(1, 1, vec![10.0, -10.0, 10.0, -10.0, 20.0, -20.0]); - let mut source = inner.speed(2.0).track_position(); - - assert_eq!(source.get_pos().as_secs_f32(), 0.0); - source.next(); - assert_eq!(source.get_pos().as_secs_f32(), 0.5); - - source.next(); - assert_eq!(source.get_pos().as_secs_f32(), 1.0); - - assert_eq!(source.try_seek(Duration::new(1, 0)).is_ok(), true); - assert_eq!(source.get_pos().as_secs_f32(), 1.0); - } -} diff --git a/src/source/repeat.rs b/src/source/repeat.rs index 12a6b947..1deb030f 100644 --- a/src/source/repeat.rs +++ b/src/source/repeat.rs @@ -2,45 +2,41 @@ use std::time::Duration; use crate::source::buffered::Buffered; +use super::peekable::PeekableSource; use super::SeekError; use crate::common::{ChannelCount, SampleRate}; use crate::Source; -/// Internal function that builds a `Repeat` object. -pub fn repeat(input: I) -> Repeat -where - I: Source, -{ - let input = input.buffered(); - Repeat { - inner: input.clone(), - next: input, - } -} - /// A source that repeats the given source. pub struct Repeat where I: Source, { - inner: Buffered, + inner: PeekableSource>, next: Buffered, } -impl Iterator for Repeat -where - I: Source, -{ +impl Repeat { + pub(crate) fn new(input: I) -> Repeat { + let input = input.buffered(); + Repeat { + inner: PeekableSource::new(input.clone()), + next: input, + } + } +} + +impl Iterator for Repeat { type Item = ::Item; #[inline] fn next(&mut self) -> Option<::Item> { if let Some(value) = self.inner.next() { - return Some(value); + Some(value) + } else { + self.inner = PeekableSource::new(self.next.clone()); + self.inner.next() } - - self.inner = self.next.clone(); - self.inner.next() } #[inline] @@ -55,26 +51,29 @@ where I: Iterator + Source, { #[inline] - fn current_span_len(&self) -> Option { - match self.inner.current_span_len() { - Some(0) => self.next.current_span_len(), - a => a, + fn parameters_changed(&self) -> bool { + if self.inner.peek_next().is_none() { + true // back to beginning of source source + } else { + self.inner.parameters_changed() } } #[inline] fn channels(&self) -> ChannelCount { - match self.inner.current_span_len() { - Some(0) => self.next.channels(), - _ => self.inner.channels(), + if self.inner.peek_next().is_none() { + self.next.channels() + } else { + self.inner.channels() } } #[inline] fn sample_rate(&self) -> SampleRate { - match self.inner.current_span_len() { - Some(0) => self.next.sample_rate(), - _ => self.inner.sample_rate(), + if self.inner.peek_next().is_none() { + self.next.sample_rate() + } else { + self.inner.sample_rate() } } diff --git a/src/source/sawtooth.rs b/src/source/sawtooth.rs index c6ae01f3..1ce19dc6 100644 --- a/src/source/sawtooth.rs +++ b/src/source/sawtooth.rs @@ -1,4 +1,5 @@ use crate::common::{ChannelCount, SampleRate}; +use crate::math::ch; use crate::source::{Function, SignalGenerator}; use crate::Source; use std::time::Duration; @@ -39,13 +40,13 @@ impl Iterator for SawtoothWave { impl Source for SawtoothWave { #[inline] - fn current_span_len(&self) -> Option { - None + fn parameters_changed(&self) -> bool { + false } #[inline] fn channels(&self) -> ChannelCount { - 1 + ch!(1) } #[inline] diff --git a/src/source/signal_generator.rs b/src/source/signal_generator.rs index e0c33bb8..1f41a0ba 100644 --- a/src/source/signal_generator.rs +++ b/src/source/signal_generator.rs @@ -13,6 +13,7 @@ //! ``` use super::SeekError; use crate::common::{ChannelCount, SampleRate}; +use crate::math::ch; use crate::Source; use std::f32::consts::TAU; use std::time::Duration; @@ -36,7 +37,7 @@ use std::time::Duration; pub type GeneratorFunction = fn(f32) -> f32; /// Waveform functions. -#[derive(Clone, Debug)] +#[derive(Clone, Copy, Debug)] pub enum Function { /// A sinusoidal waveform. Sine, @@ -137,13 +138,13 @@ impl Iterator for SignalGenerator { impl Source for SignalGenerator { #[inline] - fn current_span_len(&self) -> Option { - None + fn parameters_changed(&self) -> bool { + false } #[inline] fn channels(&self) -> ChannelCount { - 1 + ch!(1) } #[inline] diff --git a/src/source/sine.rs b/src/source/sine.rs index e3814435..073f59db 100644 --- a/src/source/sine.rs +++ b/src/source/sine.rs @@ -1,4 +1,5 @@ use crate::common::{ChannelCount, SampleRate}; +use crate::math::ch; use crate::source::{Function, SignalGenerator}; use crate::Source; use std::time::Duration; @@ -39,13 +40,13 @@ impl Iterator for SineWave { impl Source for SineWave { #[inline] - fn current_span_len(&self) -> Option { - None + fn parameters_changed(&self) -> bool { + false } #[inline] fn channels(&self) -> ChannelCount { - 1 + ch!(1) } #[inline] diff --git a/src/source/skip.rs b/src/source/skip.rs deleted file mode 100644 index ee3c7365..00000000 --- a/src/source/skip.rs +++ /dev/null @@ -1,253 +0,0 @@ -use std::time::Duration; - -use super::SeekError; -use crate::common::{ChannelCount, SampleRate}; -use crate::Source; - -const NS_PER_SECOND: u128 = 1_000_000_000; - -/// Internal function that builds a `SkipDuration` object. -pub fn skip_duration(mut input: I, duration: Duration) -> SkipDuration -where - I: Source, -{ - do_skip_duration(&mut input, duration); - SkipDuration { - input, - skipped_duration: duration, - } -} - -/// Skips specified `duration` of the given `input` source from it's current position. -fn do_skip_duration(input: &mut I, mut duration: Duration) -where - I: Source, -{ - while duration > Duration::new(0, 0) { - if input.current_span_len().is_none() { - // Sample rate and the amount of channels will be the same till the end. - do_skip_duration_unchecked(input, duration); - return; - } - - // .unwrap() safety: if `current_span_len()` is None, the body of the `if` statement - // above returns before we get here. - let span_len: usize = input.current_span_len().unwrap(); - // If span_len is zero, then there is no more data to skip. Instead - // just bail out. - if span_len == 0 { - return; - } - - let ns_per_sample: u128 = - NS_PER_SECOND / input.sample_rate() as u128 / input.channels() as u128; - - // Check if we need to skip only part of the current span. - if span_len as u128 * ns_per_sample > duration.as_nanos() { - skip_samples(input, (duration.as_nanos() / ns_per_sample) as usize); - return; - } - - skip_samples(input, span_len); - - duration -= Duration::from_nanos((span_len * ns_per_sample as usize) as u64); - } -} - -/// Skips specified `duration` from the `input` source assuming that sample rate -/// and amount of channels are not changing. -fn do_skip_duration_unchecked(input: &mut I, duration: Duration) -where - I: Source, -{ - let samples_per_channel: u128 = - duration.as_nanos() * input.sample_rate() as u128 / NS_PER_SECOND; - let samples_to_skip: u128 = samples_per_channel * input.channels() as u128; - - skip_samples(input, samples_to_skip as usize); -} - -/// Skips `n` samples from the given `input` source. -fn skip_samples(input: &mut I, n: usize) -where - I: Source, -{ - for _ in 0..n { - if input.next().is_none() { - break; - } - } -} - -/// A source that skips specified duration of the given source from it's current position. -#[derive(Clone, Debug)] -pub struct SkipDuration { - input: I, - skipped_duration: Duration, -} - -impl SkipDuration -where - I: Source, -{ - /// Returns a reference to the inner source. - #[inline] - pub fn inner(&self) -> &I { - &self.input - } - - /// Returns a mutable reference to the inner source. - #[inline] - pub fn inner_mut(&mut self) -> &mut I { - &mut self.input - } - - /// Returns the inner source. - #[inline] - pub fn into_inner(self) -> I { - self.input - } -} - -impl Iterator for SkipDuration -where - I: Source, -{ - type Item = ::Item; - - #[inline] - fn next(&mut self) -> Option { - self.input.next() - } - - #[inline] - fn size_hint(&self) -> (usize, Option) { - self.input.size_hint() - } -} - -impl Source for SkipDuration -where - I: Source, -{ - #[inline] - fn current_span_len(&self) -> Option { - self.input.current_span_len() - } - - #[inline] - fn channels(&self) -> ChannelCount { - self.input.channels() - } - - #[inline] - fn sample_rate(&self) -> SampleRate { - self.input.sample_rate() - } - - #[inline] - fn total_duration(&self) -> Option { - self.input.total_duration().map(|val| { - val.checked_sub(self.skipped_duration) - .unwrap_or_else(|| Duration::from_secs(0)) - }) - } - - #[inline] - fn try_seek(&mut self, pos: Duration) -> Result<(), SeekError> { - self.input.try_seek(pos) - } -} - -#[cfg(test)] -mod tests { - use std::time::Duration; - - use crate::buffer::SamplesBuffer; - use crate::common::{ChannelCount, SampleRate}; - use crate::source::Source; - - fn test_skip_duration_samples_left( - channels: ChannelCount, - sample_rate: SampleRate, - seconds: u32, - seconds_to_skip: u32, - ) { - let buf_len = (sample_rate * channels as u32 * seconds) as usize; - assert!(buf_len < 10 * 1024 * 1024); - let data: Vec = vec![0f32; buf_len]; - let test_buffer = SamplesBuffer::new(channels, sample_rate, data); - let seconds_left = seconds.saturating_sub(seconds_to_skip); - - let samples_left_expected = (sample_rate * channels as u32 * seconds_left) as usize; - let samples_left = test_buffer - .skip_duration(Duration::from_secs(seconds_to_skip as u64)) - .count(); - - assert_eq!(samples_left, samples_left_expected); - } - - macro_rules! skip_duration_test_block { - ($(channels: $ch:expr, sample rate: $sr:expr, seconds: $sec:expr, seconds to skip: $sec_to_skip:expr;)+) => { - $( - test_skip_duration_samples_left($ch, $sr, $sec, $sec_to_skip); - )+ - } - } - - #[test] - fn skip_duration_shorter_than_source() { - skip_duration_test_block! { - channels: 1, sample rate: 44100, seconds: 5, seconds to skip: 3; - channels: 1, sample rate: 96000, seconds: 5, seconds to skip: 3; - - channels: 2, sample rate: 44100, seconds: 5, seconds to skip: 3; - channels: 2, sample rate: 96000, seconds: 5, seconds to skip: 3; - - channels: 4, sample rate: 44100, seconds: 5, seconds to skip: 3; - channels: 4, sample rate: 96000, seconds: 5, seconds to skip: 3; - } - } - - #[test] - fn skip_duration_zero_duration() { - skip_duration_test_block! { - channels: 1, sample rate: 44100, seconds: 5, seconds to skip: 0; - channels: 1, sample rate: 96000, seconds: 5, seconds to skip: 0; - - channels: 2, sample rate: 44100, seconds: 5, seconds to skip: 0; - channels: 2, sample rate: 96000, seconds: 5, seconds to skip: 0; - - channels: 4, sample rate: 44100, seconds: 5, seconds to skip: 0; - channels: 4, sample rate: 96000, seconds: 5, seconds to skip: 0; - } - } - - #[test] - fn skip_duration_longer_than_source() { - skip_duration_test_block! { - channels: 1, sample rate: 44100, seconds: 1, seconds to skip: 5; - channels: 1, sample rate: 96000, seconds: 10, seconds to skip: 11; - - channels: 2, sample rate: 44100, seconds: 1, seconds to skip: 5; - channels: 2, sample rate: 96000, seconds: 10, seconds to skip: 11; - - channels: 4, sample rate: 44100, seconds: 1, seconds to skip: 5; - channels: 4, sample rate: 96000, seconds: 10, seconds to skip: 11; - } - } - - #[test] - fn skip_duration_equal_to_source_length() { - skip_duration_test_block! { - channels: 1, sample rate: 44100, seconds: 1, seconds to skip: 1; - channels: 1, sample rate: 96000, seconds: 10, seconds to skip: 10; - - channels: 2, sample rate: 44100, seconds: 1, seconds to skip: 1; - channels: 2, sample rate: 96000, seconds: 10, seconds to skip: 10; - - channels: 4, sample rate: 44100, seconds: 1, seconds to skip: 1; - channels: 4, sample rate: 96000, seconds: 10, seconds to skip: 10; - } - } -} diff --git a/src/source/skip_duration.rs b/src/source/skip_duration.rs new file mode 100644 index 00000000..b6595e7e --- /dev/null +++ b/src/source/skip_duration.rs @@ -0,0 +1,144 @@ +use std::time::Duration; + +use super::SeekError; +use crate::common::{ChannelCount, SampleRate}; +use crate::math::{PrevMultipleOfNonZero, NS_PER_SECOND}; +use crate::Source; + +/// A source that skips specified duration of the given source from it's current position. +#[derive(Clone, Debug)] +pub struct SkipDuration { + input: I, + skipped_duration: Duration, +} + +impl SkipDuration { + pub(crate) fn new(mut input: I, duration: Duration) -> SkipDuration + where + I: Source, + { + do_skip_duration(&mut input, duration); + Self { + input, + skipped_duration: duration, + } + } +} + +impl SkipDuration +where + I: Source, +{ + /// Returns a reference to the inner source. + #[inline] + pub fn inner(&self) -> &I { + &self.input + } + + /// Returns a mutable reference to the inner source. + #[inline] + pub fn inner_mut(&mut self) -> &mut I { + &mut self.input + } + + /// Returns the inner source. + #[inline] + pub fn into_inner(self) -> I { + self.input + } +} + +impl Iterator for SkipDuration +where + I: Source, +{ + type Item = ::Item; + + #[inline] + fn next(&mut self) -> Option { + self.input.next() + } + + #[inline] + fn size_hint(&self) -> (usize, Option) { + self.input.size_hint() + } +} + +impl Source for SkipDuration +where + I: Source, +{ + #[inline] + fn parameters_changed(&self) -> bool { + self.input.parameters_changed() + } + + #[inline] + fn channels(&self) -> ChannelCount { + self.input.channels() + } + + #[inline] + fn sample_rate(&self) -> SampleRate { + self.input.sample_rate() + } + + #[inline] + fn total_duration(&self) -> Option { + self.input.total_duration().map(|val| { + val.checked_sub(self.skipped_duration) + .unwrap_or_else(|| Duration::from_secs(0)) + }) + } + + #[inline] + fn try_seek(&mut self, pos: Duration) -> Result<(), SeekError> { + self.input.try_seek(pos) + } +} + +/// Skips specified `duration` of the given `input` source from it's current position. +/// +/// # Panics +/// If trying to skip more than 584 days ahead. If you need that functionality +/// please open an issue I would love to know why (and help out). +fn do_skip_duration(input: &mut I, duration: Duration) +where + I: Source, +{ + // `u64::MAX` can store 584 days of nanosecond precision time. To not be off by + // a single sample (that would be regression) we first multiply the time by + // `samples_per second`. Which for a 96kHz 10 channel audio stream is + // 960_000 samples. That would only leave 0.87 hour of potential skip time. Hence + // we use an `u128` to calculate samples to skip. + let mut duration: u64 = duration + .as_nanos() + .try_into() + .expect("can not skip more then 584 days of audio"); + let mut ns_per_frame: u64 = 0; + + while duration > ns_per_frame { + assert!(input.sample_rate() > 0); + + ns_per_frame = NS_PER_SECOND / input.sample_rate() as u64; + + let samples_per_second = input.sample_rate() as u64 * input.channels().get() as u64; + let samples_to_skip = + (duration as u128 * samples_per_second as u128 / NS_PER_SECOND as u128) as u64; + let samples_to_skip = samples_to_skip.prev_multiple_of(input.channels()); + + let mut skipped = 0; + while skipped < samples_to_skip { + if input.next().is_none() { + return; + } + skipped += 1; + if input.parameters_changed() { + break; + } + } + + duration -= skipped * NS_PER_SECOND / samples_per_second; + } +} diff --git a/src/source/skippable.rs b/src/source/skippable.rs index ba4d43d9..1ec2ee6f 100644 --- a/src/source/skippable.rs +++ b/src/source/skippable.rs @@ -75,8 +75,8 @@ where I: Source, { #[inline] - fn current_span_len(&self) -> Option { - self.input.current_span_len() + fn parameters_changed(&self) -> bool { + self.input.parameters_changed() } #[inline] diff --git a/src/source/spatial.rs b/src/source/spatial.rs index 6ea3555d..259522d3 100644 --- a/src/source/spatial.rs +++ b/src/source/spatial.rs @@ -93,8 +93,8 @@ where I: Source, { #[inline] - fn current_span_len(&self) -> Option { - self.input.current_span_len() + fn parameters_changed(&self) -> bool { + self.input.parameters_changed() } #[inline] diff --git a/src/source/speed.rs b/src/source/speed.rs index 25e25efc..ec3aeebd 100644 --- a/src/source/speed.rs +++ b/src/source/speed.rs @@ -116,8 +116,8 @@ where I: Source, { #[inline] - fn current_span_len(&self) -> Option { - self.input.current_span_len() + fn parameters_changed(&self) -> bool { + self.input.parameters_changed() } #[inline] diff --git a/src/source/square.rs b/src/source/square.rs index ac6bd678..7654cf21 100644 --- a/src/source/square.rs +++ b/src/source/square.rs @@ -1,4 +1,5 @@ use crate::common::{ChannelCount, SampleRate}; +use crate::math::ch; use crate::source::{Function, SignalGenerator}; use crate::Source; use std::time::Duration; @@ -39,13 +40,13 @@ impl Iterator for SquareWave { impl Source for SquareWave { #[inline] - fn current_span_len(&self) -> Option { - None + fn parameters_changed(&self) -> bool { + false } #[inline] fn channels(&self) -> ChannelCount { - 1 + ch!(1) } #[inline] diff --git a/src/source/stoppable.rs b/src/source/stoppable.rs index 57cc7dae..dae04ff5 100644 --- a/src/source/stoppable.rs +++ b/src/source/stoppable.rs @@ -71,8 +71,8 @@ where I: Source, { #[inline] - fn current_span_len(&self) -> Option { - self.input.current_span_len() + fn parameters_changed(&self) -> bool { + self.input.parameters_changed() } #[inline] diff --git a/src/source/take.rs b/src/source/take.rs deleted file mode 100644 index 9abcf7f6..00000000 --- a/src/source/take.rs +++ /dev/null @@ -1,177 +0,0 @@ -use std::time::Duration; - -use super::SeekError; -use crate::common::{ChannelCount, SampleRate}; -use crate::{Sample, Source}; - -/// Internal function that builds a `TakeDuration` object. -pub fn take_duration(input: I, duration: Duration) -> TakeDuration -where - I: Source, -{ - TakeDuration { - current_span_len: input.current_span_len(), - duration_per_sample: TakeDuration::get_duration_per_sample(&input), - input, - remaining_duration: duration, - requested_duration: duration, - filter: None, - } -} - -/// A filter that can be applied to a `TakeDuration`. -#[derive(Clone, Debug)] -enum DurationFilter { - FadeOut, -} -impl DurationFilter { - fn apply(&self, sample: Sample, parent: &TakeDuration) -> Sample { - match self { - DurationFilter::FadeOut => { - let remaining = parent.remaining_duration.as_millis() as f32; - let total = parent.requested_duration.as_millis() as f32; - sample * remaining / total - } - } - } -} - -const NANOS_PER_SEC: u64 = 1_000_000_000; - -/// A source that truncates the given source to a certain duration. -#[derive(Clone, Debug)] -pub struct TakeDuration { - input: I, - remaining_duration: Duration, - requested_duration: Duration, - filter: Option, - // Remaining samples in current span. - current_span_len: Option, - // Only updated when the current span len is exhausted. - duration_per_sample: Duration, -} - -impl TakeDuration -where - I: Source, -{ - /// Returns the duration elapsed for each sample extracted. - #[inline] - fn get_duration_per_sample(input: &I) -> Duration { - let ns = NANOS_PER_SEC / (input.sample_rate() as u64 * input.channels() as u64); - // \|/ the maximum value of `ns` is one billion, so this can't fail - Duration::new(0, ns as u32) - } - - /// Returns a reference to the inner source. - #[inline] - pub fn inner(&self) -> &I { - &self.input - } - - /// Returns a mutable reference to the inner source. - #[inline] - pub fn inner_mut(&mut self) -> &mut I { - &mut self.input - } - - /// Returns the inner source. - #[inline] - pub fn into_inner(self) -> I { - self.input - } - - /// Make the truncated source end with a FadeOut. The fadeout covers the - /// entire length of the take source. - pub fn set_filter_fadeout(&mut self) { - self.filter = Some(DurationFilter::FadeOut); - } - - /// Remove any filter set. - pub fn clear_filter(&mut self) { - self.filter = None; - } -} - -impl Iterator for TakeDuration -where - I: Source, -{ - type Item = ::Item; - - fn next(&mut self) -> Option<::Item> { - if let Some(span_len) = self.current_span_len.take() { - if span_len > 0 { - self.current_span_len = Some(span_len - 1); - } else { - self.current_span_len = self.input.current_span_len(); - // Sample rate might have changed - self.duration_per_sample = Self::get_duration_per_sample(&self.input); - } - } - - if self.remaining_duration <= self.duration_per_sample { - None - } else if let Some(sample) = self.input.next() { - let sample = match &self.filter { - Some(filter) => filter.apply(sample, self), - None => sample, - }; - - self.remaining_duration -= self.duration_per_sample; - - Some(sample) - } else { - None - } - } - - // TODO: size_hint -} - -impl Source for TakeDuration -where - I: Iterator + Source, -{ - #[inline] - fn current_span_len(&self) -> Option { - let remaining_nanos = self.remaining_duration.as_secs() * NANOS_PER_SEC - + self.remaining_duration.subsec_nanos() as u64; - let nanos_per_sample = self.duration_per_sample.as_secs() * NANOS_PER_SEC - + self.duration_per_sample.subsec_nanos() as u64; - let remaining_samples = (remaining_nanos / nanos_per_sample) as usize; - - self.input - .current_span_len() - .filter(|value| *value < remaining_samples) - .or(Some(remaining_samples)) - } - - #[inline] - fn channels(&self) -> ChannelCount { - self.input.channels() - } - - #[inline] - fn sample_rate(&self) -> SampleRate { - self.input.sample_rate() - } - - #[inline] - fn total_duration(&self) -> Option { - if let Some(duration) = self.input.total_duration() { - if duration < self.requested_duration { - Some(duration) - } else { - Some(self.requested_duration) - } - } else { - None - } - } - - #[inline] - fn try_seek(&mut self, pos: Duration) -> Result<(), SeekError> { - self.input.try_seek(pos) - } -} diff --git a/src/source/take_duration.rs b/src/source/take_duration.rs new file mode 100644 index 00000000..b59a9d5b --- /dev/null +++ b/src/source/take_duration.rs @@ -0,0 +1,182 @@ +use std::time::Duration; + +use super::SeekError; +use crate::common::{ChannelCount, SampleRate}; +use crate::math::{PrevMultipleOfNonZero, NS_PER_SECOND}; +use crate::Source; + +/// A source that truncates the given source to a certain duration. +#[derive(Clone, Debug)] +pub struct TakeDuration { + input: I, + requested_duration: Duration, + remaining_ns: u64, + samples_per_second: u64, + samples_to_take: u64, + samples_taken: u64, + fadeout: bool, +} + +impl TakeDuration +where + I: Source, +{ + pub(crate) fn new(input: I, duration: Duration) -> TakeDuration + where + I: Source, + { + let remaining_ns: u64 = duration + .as_nanos() + .try_into() + .expect("can not take more then 584 days of audio"); + + let samples_per_second = input.sample_rate() as u64 * input.channels().get() as u64; + let samples_to_take = + (remaining_ns as u128 * samples_per_second as u128 / NS_PER_SECOND as u128) as u64; + let samples_to_take = samples_to_take.prev_multiple_of(input.channels()); + + Self { + input, + remaining_ns, + fadeout: false, + samples_per_second, + samples_to_take, + samples_taken: 0, + requested_duration: duration, + } + } + + /// Returns a reference to the inner source. + #[inline] + pub fn inner(&self) -> &I { + &self.input + } + + /// Returns a mutable reference to the inner source. + #[inline] + pub fn inner_mut(&mut self) -> &mut I { + &mut self.input + } + + /// Returns the inner source. + #[inline] + pub fn into_inner(self) -> I { + self.input + } + + /// Optionally the truncated source end with a FadeOut. The fade-out + /// covers the entire length of the take source. + pub fn fadeout(&mut self, enabled: bool) { + self.fadeout = enabled; + } + + /// Make the truncated source end with a FadeOut. The fade-out covers the + /// entire length of the take source. + pub fn with_fadeout(mut self, enabled: bool) -> Self { + self.fadeout = enabled; + self + } + + /// Remove any filter set. + pub fn clear_filter(&mut self) { + self.fadeout = false; + } + + fn next_samples_per_second(&mut self) -> u64 { + self.input.sample_rate() as u64 * self.input.channels().get() as u64 + } + + fn next_samples_to_take(&mut self) -> u64 { + let samples_to_take = (self.remaining_ns as u128 * self.samples_per_second as u128 + / NS_PER_SECOND as u128) as u64; + let samples_to_take = samples_to_take.prev_multiple_of(self.input.channels()); + samples_to_take + } + + fn duration_taken(&mut self) -> u64 { + self.samples_taken * NS_PER_SECOND / self.samples_per_second + } +} + +impl Iterator for TakeDuration +where + I: Source, +{ + type Item = ::Item; + + // implementation is adapted of skip_duration + // + // if tuples are frames you could define fadeout as this: + // [(1.0, 1.0), (1.0, 1.0), (1.0, 1.0)] + // -> [(1.0, 1.0), (0.5, 0.5), (0.0, 0.0)] + // instead because its simpler, faster and what previous rodio versions did we do: + // [(1.0, 1.0), (1.0, 1.0), (1.0, 1.0)] + // -> [(1.0, .83), (.66, 0.5), (.33, .16)] + // at normal sample_rates you do not hear a difference + fn next(&mut self) -> Option<::Item> { + if self.input.parameters_changed() { + self.remaining_ns -= self.duration_taken(); + self.samples_per_second = self.next_samples_per_second(); + self.samples_to_take = self.next_samples_to_take(); + self.samples_taken = 0; + } + + if self.samples_taken >= self.samples_to_take { + return None; + } + + let Some(sample) = self.input.next() else { + return None; + }; + + let ret = if self.fadeout { + let total = self.requested_duration.as_nanos() as u64; + let remaining = self.remaining_ns - self.duration_taken(); + Some(sample * remaining as f32 / total as f32) + } else { + Some(sample) + }; + self.samples_taken += 1; + ret + } +} + +// TODO: size_hint + +impl Source for TakeDuration +where + I: Iterator + Source, +{ + #[inline] + fn parameters_changed(&self) -> bool { + self.input.parameters_changed() + } + + #[inline] + fn channels(&self) -> ChannelCount { + self.input.channels() + } + + #[inline] + fn sample_rate(&self) -> SampleRate { + self.input.sample_rate() + } + + #[inline] + fn total_duration(&self) -> Option { + if let Some(duration) = self.input.total_duration() { + if duration < self.requested_duration { + Some(duration) + } else { + Some(self.requested_duration) + } + } else { + None + } + } + + #[inline] + fn try_seek(&mut self, pos: Duration) -> Result<(), SeekError> { + self.input.try_seek(pos) + } +} diff --git a/src/source/take_samples.rs b/src/source/take_samples.rs new file mode 100644 index 00000000..1a3d9b54 --- /dev/null +++ b/src/source/take_samples.rs @@ -0,0 +1,89 @@ +use std::time::Duration; + +use super::SeekError; +use crate::common::{ChannelCount, SampleRate}; +use crate::math::PrevMultipleOf; +use crate::Source; + +/// A source that truncates the given source to a certain duration. This +/// `struct` is created by the [`take_samples`](Source::take_samples) method +/// on [Source]. See its documentation for more. +#[derive(Clone, Debug)] +pub struct TakeSamples { + input: I, + taken: usize, + target: usize, +} + +impl TakeSamples +where + I: Source, +{ + pub(crate) fn new(input: I, n: usize) -> Self { + Self { + input, + taken: 0, + target: n, + } + } + + /// Returns a mutable reference to the inner source. + #[inline] + pub fn inner_mut(&mut self) -> &mut I { + &mut self.input + } + + /// Returns the inner source. + #[inline] + pub fn into_inner(self) -> I { + self.input + } +} + +impl Iterator for TakeSamples +where + I: Source, +{ + type Item = ::Item; + + fn next(&mut self) -> Option<::Item> { + if self.taken >= self.target.prev_multiple_of(self.input.channels().get()) { + None + } else { + self.taken += 1; + self.input.next() + } + } +} + +// TODO: size_hint + +impl Source for TakeSamples +where + I: Iterator + Source, +{ + #[inline] + fn parameters_changed(&self) -> bool { + self.input.parameters_changed() + } + + #[inline] + fn channels(&self) -> ChannelCount { + self.input.channels() + } + + #[inline] + fn sample_rate(&self) -> SampleRate { + self.input.sample_rate() + } + + #[inline] + fn total_duration(&self) -> Option { + self.input.total_duration() + } + + #[inline] + fn try_seek(&mut self, pos: Duration) -> Result<(), SeekError> { + self.input.try_seek(pos) + } +} diff --git a/src/source/triangle.rs b/src/source/triangle.rs index eb73801d..c08174d2 100644 --- a/src/source/triangle.rs +++ b/src/source/triangle.rs @@ -1,4 +1,5 @@ use crate::common::{ChannelCount, SampleRate}; +use crate::math::ch; use crate::source::{Function, SignalGenerator}; use crate::Source; use std::time::Duration; @@ -39,13 +40,13 @@ impl Iterator for TriangleWave { impl Source for TriangleWave { #[inline] - fn current_span_len(&self) -> Option { - None + fn parameters_changed(&self) -> bool { + false } #[inline] fn channels(&self) -> ChannelCount { - 1 + ch!(1) } #[inline] diff --git a/src/source/uniform.rs b/src/source/uniform.rs index b92642fa..7c9849f5 100644 --- a/src/source/uniform.rs +++ b/src/source/uniform.rs @@ -1,11 +1,13 @@ -use std::cmp; use std::time::Duration; +use super::take_samples::TakeSamples; use super::SeekError; use crate::common::{ChannelCount, SampleRate}; use crate::conversions::{ChannelCountConverter, SampleRateConverter}; use crate::Source; +type Converted = ChannelCountConverter>>; + /// An iterator that reads from a `Source` and converts the samples to a /// specific type, sample-rate and channels count. /// @@ -16,7 +18,7 @@ pub struct UniformSourceIterator where I: Source, { - inner: Option>>>, + inner: Option>, target_channels: ChannelCount, target_sample_rate: SampleRate, total_duration: Option, @@ -50,17 +52,11 @@ where input: I, target_channels: ChannelCount, target_sample_rate: SampleRate, - ) -> ChannelCountConverter>> { - // Limit the span length to something reasonable - let span_len = input.current_span_len().map(|x| x.min(32768)); - + ) -> Converted { let from_channels = input.channels(); let from_sample_rate = input.sample_rate(); - let input = Take { - iter: input, - n: span_len, - }; + let input = input.take_samples(32768); let input = SampleRateConverter::new(input, from_sample_rate, target_sample_rate, from_channels); ChannelCountConverter::new(input, from_channels, target_channels) @@ -79,7 +75,13 @@ where return Some(value); } - let input = self.inner.take().unwrap().into_inner().into_inner().iter; + let input = self + .inner + .take() + .unwrap() + .into_inner() + .into_inner() + .into_inner(); let mut input = UniformSourceIterator::bootstrap(input, self.target_channels, self.target_sample_rate); @@ -100,8 +102,8 @@ where I: Iterator + Source, { #[inline] - fn current_span_len(&self) -> Option { - None + fn parameters_changed(&self) -> bool { + false } #[inline] @@ -128,57 +130,3 @@ where } } } - -#[derive(Clone, Debug)] -struct Take { - iter: I, - n: Option, -} - -impl Take { - #[inline] - pub fn inner_mut(&mut self) -> &mut I { - &mut self.iter - } -} - -impl Iterator for Take -where - I: Iterator, -{ - type Item = ::Item; - - #[inline] - fn next(&mut self) -> Option<::Item> { - if let Some(n) = &mut self.n { - if *n != 0 { - *n -= 1; - self.iter.next() - } else { - None - } - } else { - self.iter.next() - } - } - - #[inline] - fn size_hint(&self) -> (usize, Option) { - if let Some(n) = self.n { - let (lower, upper) = self.iter.size_hint(); - - let lower = cmp::min(lower, n); - - let upper = match upper { - Some(x) if x < n => Some(x), - _ => Some(n), - }; - - (lower, upper) - } else { - self.iter.size_hint() - } - } -} - -impl ExactSizeIterator for Take where I: ExactSizeIterator {} diff --git a/src/source/zero.rs b/src/source/zero.rs index aba88aba..71e3a405 100644 --- a/src/source/zero.rs +++ b/src/source/zero.rs @@ -59,8 +59,8 @@ impl Iterator for Zero { impl Source for Zero { #[inline] - fn current_span_len(&self) -> Option { - self.num_samples + fn parameters_changed(&self) -> bool { + false } #[inline] diff --git a/src/static_buffer.rs b/src/static_buffer.rs index 014d824c..4d7990b8 100644 --- a/src/static_buffer.rs +++ b/src/static_buffer.rs @@ -41,12 +41,11 @@ impl StaticSamplesBuffer { sample_rate: SampleRate, data: &'static [Sample], ) -> StaticSamplesBuffer { - assert!(channels != 0); assert!(sample_rate != 0); let duration_ns = 1_000_000_000u64.checked_mul(data.len() as u64).unwrap() / sample_rate as u64 - / channels as u64; + / channels.get() as u64; let duration = Duration::new( duration_ns / 1_000_000_000, (duration_ns % 1_000_000_000) as u32, @@ -63,8 +62,8 @@ impl StaticSamplesBuffer { impl Source for StaticSamplesBuffer { #[inline] - fn current_span_len(&self) -> Option { - None + fn parameters_changed(&self) -> bool { + false } #[inline] @@ -106,29 +105,24 @@ impl Iterator for StaticSamplesBuffer { #[cfg(test)] mod tests { + use crate::math::ch; use crate::source::Source; use crate::static_buffer::StaticSamplesBuffer; #[test] fn basic() { - let _ = StaticSamplesBuffer::new(1, 44100, &[0.0, 0.0, 0.0, 0.0, 0.0, 0.0]); - } - - #[test] - #[should_panic] - fn panic_if_zero_channels() { - StaticSamplesBuffer::new(0, 44100, &[0.0, 0.0, 0.0, 0.0, 0.0, 0.0]); + let _ = StaticSamplesBuffer::new(ch!(1), 44100, &[0.0, 0.0, 0.0, 0.0, 0.0, 0.0]); } #[test] #[should_panic] fn panic_if_zero_sample_rate() { - StaticSamplesBuffer::new(1, 0, &[0.0, 0.0, 0.0, 0.0, 0.0, 0.0]); + StaticSamplesBuffer::new(ch!(1), 0, &[0.0, 0.0, 0.0, 0.0, 0.0, 0.0]); } #[test] fn duration_basic() { - let buf = StaticSamplesBuffer::new(2, 2, &[0.0, 0.0, 0.0, 0.0, 0.0, 0.0]); + let buf = StaticSamplesBuffer::new(ch!(2), 2, &[0.0, 0.0, 0.0, 0.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); @@ -136,7 +130,7 @@ mod tests { #[test] fn iteration() { - let mut buf = StaticSamplesBuffer::new(1, 44100, &[1.0, 2.0, 3.0, 4.0, 5.0, 6.0]); + let mut buf = StaticSamplesBuffer::new(ch!(1), 44100, &[1.0, 2.0, 3.0, 4.0, 5.0, 6.0]); assert_eq!(buf.next(), Some(1.0)); assert_eq!(buf.next(), Some(2.0)); assert_eq!(buf.next(), Some(3.0)); diff --git a/src/stream.rs b/src/stream.rs index 6e5fffa8..51871bd6 100644 --- a/src/stream.rs +++ b/src/stream.rs @@ -1,5 +1,6 @@ use crate::common::{ChannelCount, SampleRate}; use crate::decoder; +use crate::math::ch; use crate::mixer::{mixer, Mixer, MixerSource}; use crate::sink::Sink; use cpal::traits::{DeviceTrait, HostTrait, StreamTrait}; @@ -36,7 +37,7 @@ struct OutputStreamConfig { impl Default for OutputStreamConfig { fn default() -> Self { Self { - channel_count: 2, + channel_count: ch!(2), sample_rate: HZ_44100, buffer_size: BufferSize::Default, sample_format: SampleFormat::F32, @@ -97,7 +98,6 @@ impl OutputStreamBuilder { /// Sets number of output stream's channels. pub fn with_channels(mut self, channel_count: ChannelCount) -> OutputStreamBuilder { - assert!(channel_count > 0); self.config.channel_count = channel_count; self } @@ -122,14 +122,15 @@ impl OutputStreamBuilder { self } - /// Set available parameters from a CPAL supported config. You can ge list of + /// Set available parameters from a CPAL supported config. You can get list of /// such configurations for an output device using [crate::stream::supported_output_configs()] pub fn with_supported_config( mut self, config: &cpal::SupportedStreamConfig, ) -> OutputStreamBuilder { self.config = OutputStreamConfig { - channel_count: config.channels() as ChannelCount, + channel_count: ChannelCount::new(config.channels()) + .expect("cpal should never return a zero channel output"), sample_rate: config.sample_rate().0 as SampleRate, // In case of supported range limit buffer size to avoid unexpectedly long playback delays. buffer_size: clamp_supported_buffer_size(config.buffer_size(), 1024), @@ -141,7 +142,8 @@ impl OutputStreamBuilder { /// Set all output stream parameters at once from CPAL stream config. pub fn with_config(mut self, config: &cpal::StreamConfig) -> OutputStreamBuilder { self.config = OutputStreamConfig { - channel_count: config.channels as ChannelCount, + channel_count: ChannelCount::new(config.channels) + .expect("cpal should never return a zero channel output"), sample_rate: config.sample_rate.0 as SampleRate, buffer_size: config.buffer_size, ..self.config @@ -233,7 +235,7 @@ where impl From<&OutputStreamConfig> for StreamConfig { fn from(config: &OutputStreamConfig) -> Self { cpal::StreamConfig { - channels: config.channel_count as cpal::ChannelCount, + channels: config.channel_count.get() as cpal::ChannelCount, sample_rate: cpal::SampleRate(config.sample_rate), buffer_size: config.buffer_size, } @@ -326,10 +328,6 @@ impl OutputStream { assert!(sz > 0, "fixed buffer size is greater than zero"); } assert!(config.sample_rate > 0, "sample rate is greater than zero"); - assert!( - config.channel_count > 0, - "channel number is greater than zero" - ); } fn open( diff --git a/src/wav_output.rs b/src/wav_output.rs index 3b91d8a4..fabcc1b9 100644 --- a/src/wav_output.rs +++ b/src/wav_output.rs @@ -1,4 +1,4 @@ -use crate::{ChannelCount, Source}; +use crate::Source; use hound::{SampleFormat, WavSpec}; use std::path; @@ -10,7 +10,7 @@ pub fn output_to_wav( wav_file: impl AsRef, ) -> Result<(), Box> { let format = WavSpec { - channels: source.channels() as ChannelCount, + channels: source.channels().get(), sample_rate: source.sample_rate(), bits_per_sample: 32, sample_format: SampleFormat::Float, @@ -26,7 +26,6 @@ pub fn output_to_wav( #[cfg(test)] mod test { use super::output_to_wav; - use crate::common::ChannelCount; use crate::Source; use std::io::BufReader; use std::time::Duration; @@ -47,7 +46,7 @@ mod test { hound::WavReader::new(BufReader::new(file)).expect("wav file can be read back"); let reference = make_source(); assert_eq!(reference.sample_rate(), reader.spec().sample_rate); - assert_eq!(reference.channels(), reader.spec().channels as ChannelCount); + assert_eq!(reference.channels().get(), reader.spec().channels); let actual_samples: Vec = reader.samples::().map(|x| x.unwrap()).collect(); let expected_samples: Vec = reference.collect(); diff --git a/tests/blt_filter.rs b/tests/blt_filter.rs new file mode 100644 index 00000000..e4189aae --- /dev/null +++ b/tests/blt_filter.rs @@ -0,0 +1,112 @@ +mod test_support; +use rodio::{SampleRate, Source}; +use test_support::{TestSource, TestSpan}; + +use spectrum_analyzer::scaling::scale_to_zero_to_one; +use spectrum_analyzer::windows::hann_window; +use spectrum_analyzer::{samples_fft_to_spectrum, FrequencyLimit}; + +const SAMPLE_RATE: SampleRate = 44_100; + +#[test] +fn low_pass_attenuates() { + attenuate_test( + |s: TestSource| s.low_pass(200), + |ratio_before, ratio_after| { + assert!( + ratio_after < ratio_before / 10.0, + "expected ratio between frequencies above freq (400) and below to have \ + decreased by at least a factor 10 after the low pass filter.\ + \n\tratio before: {ratio_before},\n\tratio_after: {ratio_after}" + ) + }, + ); +} + +#[test] +fn high_pass_attenuates() { + attenuate_test( + |s: TestSource| s.high_pass(200), + |ratio_before, ratio_after| { + assert!( + ratio_after > ratio_before * 4.0, + "expected ratio between frequencies above freq (400) and below to have \ + increased by at least a factor 4 after the low pass filter.\ + \n\tratio before: {ratio_before},\n\tratio_after: {ratio_after}" + ) + }, + ); +} + +fn attenuate_test(filter: impl Fn(TestSource) -> S, assert: impl Fn(f32, f32)) { + let source = TestSource::new() + .with_span( + TestSpan::square(40.0) + .with_sample_rate(SAMPLE_RATE) + .with_channel_count(1) + .with_sample_count(2048), + ) + .with_span( + TestSpan::square(40.0) + .with_sample_rate(SAMPLE_RATE / 2) + .with_channel_count(1) + .with_sample_count(1024), + ); + + let span0_ratio_before = power_above_freq_vs_below( + source.clone().take(source.spans[0].len()), + 400.0, + source.spans[0].sample_rate, + ); + let span1_ratio_before = power_above_freq_vs_below( + source.clone().skip(source.spans[0].len()), + 400.0, + source.spans[1].sample_rate, + ); + + let filterd = filter(source.clone()); + + let span0_ratio_after = power_above_freq_vs_below( + filterd.clone().take(source.spans[0].len()), + 400.0, + source.spans[0].sample_rate, + ); + let span1_ratio_after = power_above_freq_vs_below( + filterd.skip(source.spans[0].len()), + 400.0, + source.spans[1].sample_rate, + ); + + assert(span0_ratio_before, span0_ratio_after); + assert(span1_ratio_before, span1_ratio_after); +} + +fn power_above_freq_vs_below( + source: impl Iterator, + split: f32, + sample_rate: SampleRate, +) -> f32 { + let samples: Vec = source.collect(); + let hann_window = hann_window(&samples); + let spectrum = samples_fft_to_spectrum( + &hann_window, + sample_rate, + FrequencyLimit::All, + Some(&scale_to_zero_to_one), + ) + .unwrap(); + + let data = spectrum.data(); + let below: f32 = data + .iter() + .take_while(|(freq, _)| freq.val() < split) + .map(|(_, val)| val.val()) + .sum(); + let above: f32 = data + .iter() + .skip_while(|(freq, _)| freq.val() < split) + .map(|(_, val)| val.val()) + .sum(); + + above / below +} diff --git a/tests/buffered.rs b/tests/buffered.rs new file mode 100644 index 00000000..3e86c69b --- /dev/null +++ b/tests/buffered.rs @@ -0,0 +1,94 @@ +mod test_support; +use rodio::Source; +use test_support::{TestSource, TestSpan}; + +#[test] +fn parameters_change_correct() { + let mut source = TestSource::new() + .with_span(TestSpan::silence().with_sample_count(10)) + .with_span(TestSpan::silence().with_sample_count(10)) + .buffered(); + + assert!(source.next().is_some()); + assert!(source.parameters_changed()); + assert_eq!(source.by_ref().take(9).count(), 9); + // span ends + + assert!(source.next().is_some()); + assert!(source.parameters_changed()); + assert!(source.next().is_some()); + assert!(!source.parameters_changed()); + + assert_eq!(source.count(), 8); +} + +#[test] +fn channel_count_changes() { + let mut source = TestSource::new() + .with_span( + TestSpan::silence() + .with_channel_count(1) + .with_sample_count(10), + ) + .with_span( + TestSpan::silence() + .with_channel_count(2) + .with_sample_count(10), + ) + .buffered(); + + assert!(source.next().is_some()); + assert_eq!(source.channels().get(), 1); + assert_eq!(source.by_ref().take(9).count(), 9); + + // next span starts + assert!(source.next().is_some()); + assert_eq!(source.channels().get(), 2); +} + +#[test] +fn buffered_sample_rate_changes() { + let mut source = TestSource::new() + .with_span( + TestSpan::silence() + .with_sample_rate(10) + .with_sample_count(10), + ) + .with_span( + TestSpan::silence() + .with_sample_rate(20) + .with_sample_count(10), + ) + .buffered(); + + assert!(source.next().is_some()); + assert_eq!(source.sample_rate(), 10); + assert_eq!(source.by_ref().take(9).count(), 9); + + // next span starts + assert!(source.next().is_some()); + assert_eq!(source.sample_rate(), 20); +} + +#[test] +fn equals_unbuffered() { + let mut source = TestSource::new() + .with_span( + TestSpan::from_samples((0..10).into_iter().map(|n| n as f32)) + .with_sample_rate(10) + .with_sample_count(10), + ) + .with_span( + TestSpan::silence() + .with_sample_rate(20) + .with_sample_count(10), + ); + + let mut buffered = source.clone().buffered(); + for (sample, expected) in buffered.by_ref().zip(source.by_ref()) { + assert_eq!(sample, expected); + } + + assert!(buffered.next().is_none()); + assert!(source.next().is_none()); +} diff --git a/tests/channel_volume.rs b/tests/channel_volume.rs new file mode 100644 index 00000000..5a077d38 --- /dev/null +++ b/tests/channel_volume.rs @@ -0,0 +1,46 @@ +use std::fs; +use std::io::BufReader; + +use itertools::Itertools; + +use rodio::queue::Queue; +use rodio::source::ChannelVolume; +use rodio::{Decoder, Source}; + +#[test] +fn no_queue() { + let file = fs::File::open("assets/music.mp3").unwrap(); + let decoder = Decoder::new(BufReader::new(file)).unwrap(); + assert_eq!(decoder.channels().get(), 2); + let channel_volume = ChannelVolume::new(decoder, vec![1.0, 1.0, 0.0, 0.0, 0.0, 0.0]); + assert_eq!(channel_volume.channels().get(), 6); + + assert_output_only_on_channel_1_and_2(channel_volume); +} + +#[test] +fn with_queue_in_between() { + let file = fs::File::open("assets/music.mp3").unwrap(); + let decoder = Decoder::new(BufReader::new(file)).unwrap(); + assert_eq!(decoder.channels().get(), 2); + let channel_volume = ChannelVolume::new(decoder, vec![1.0, 1.0, 0.0, 0.0, 0.0, 0.0]); + assert_eq!(channel_volume.channels().get(), 6); + + let (controls, queue) = Queue::new(false); + controls.append(channel_volume); + + assert_output_only_on_channel_1_and_2(queue); +} + +fn assert_output_only_on_channel_1_and_2(source: impl Source) { + for (frame_number, mut frame) in source.chunks(6).into_iter().enumerate() { + let frame: [_; 6] = frame.next_array().expect(&format!( + "Source should contain whole frames, frame {frame_number} was partial" + )); + assert_eq!( + &frame[2..], + &[0., 0., 0., 0.], + "frame {frame_number} had nonzero volume on a channel that should be zero" + ) + } +} diff --git a/tests/crossfade.rs b/tests/crossfade.rs new file mode 100644 index 00000000..8c7c6546 --- /dev/null +++ b/tests/crossfade.rs @@ -0,0 +1,39 @@ +use std::num::NonZero; +use std::time::Duration; + +use rodio::buffer::SamplesBuffer; +use rodio::source::Zero; +use rodio::Source; + +fn dummy_source(length: u8) -> SamplesBuffer { + let data: Vec = (1..=length).map(f32::from).collect(); + SamplesBuffer::new(NonZero::new(1).unwrap(), 1, data) +} + +#[test] +fn test_crossfade_with_self() { + let source1 = dummy_source(10); + let source2 = dummy_source(10); + let mut mixed = + source1.take_crossfade_with(source2, Duration::from_secs(5) + Duration::from_nanos(1)); + assert_eq!(mixed.next(), Some(1.0)); + assert_eq!(mixed.next(), Some(2.0)); + assert_eq!(mixed.next(), Some(3.0)); + assert_eq!(mixed.next(), Some(4.0)); + assert_eq!(mixed.next(), Some(5.0)); + assert_eq!(mixed.next(), None); +} + +#[test] +fn test_crossfade() { + let source1 = dummy_source(10); + let source2 = Zero::new(NonZero::new(1).unwrap(), 1); + let mixed = + source1.take_crossfade_with(source2, Duration::from_secs(5) + Duration::from_nanos(1)); + let result = mixed.collect::>(); + assert_eq!(result.len(), 5); + assert!(result + .iter() + .zip(vec![1.0, 2.0 * 0.8, 3.0 * 0.6, 4.0 * 0.4, 5.0 * 0.2]) + .all(|(a, b)| (a - b).abs() < 1e-6)); +} diff --git a/tests/peekable.rs b/tests/peekable.rs new file mode 100644 index 00000000..f9487740 --- /dev/null +++ b/tests/peekable.rs @@ -0,0 +1,87 @@ +mod test_support; +use rodio::Source; +use test_support::{TestSource, TestSpan}; + +#[test] +fn peeked_matches_next() { + let source = TestSource::new() + .with_span( + TestSpan::from_samples((0..10).map(|n| n as f32)) + .with_sample_count(10), + ) + .with_span( + TestSpan::from_samples((10..20).map(|n| n as f32)) + .with_sample_count(10), + ); + + let mut peekable = source.peekable_source(); + + for _ in 0..peekable.len() { + let peeked = peekable.peek_next(); + let next = peekable.next(); + assert!( + peeked == next, + "peeked: {peeked:?} does not match following next: {next:?}" + ); + } +} + +#[test] +fn parameters_change_correct() { + let source = TestSource::new() + .with_span(TestSpan::silence().with_sample_count(10)) + .with_span(TestSpan::silence().with_sample_count(10)); + let mut peekable = source.peekable_source(); + peekable.peek_next(); + + assert_eq!(peekable.by_ref().take(10).count(), 10); + assert!(!peekable.parameters_changed()); + // end of first span + + assert!(peekable.next().is_some()); + assert!(peekable.parameters_changed()); + + assert_eq!(peekable.count(), 9); +} + +#[test] +fn channel_count_changes() { + let source = TestSource::new() + .with_span( + TestSpan::silence() + .with_channel_count(1) + .with_sample_count(10), + ) + .with_span( + TestSpan::silence() + .with_channel_count(2) + .with_sample_count(10), + ); + let mut peekable = source.peekable_source(); + peekable.peek_next(); + + assert_eq!(peekable.channels().get(), 1); + assert_eq!(peekable.by_ref().take(10).count(), 10); + assert_eq!(peekable.channels().get(), 2); +} + +#[test] +fn sample_rate_changes() { + let source = TestSource::new() + .with_span( + TestSpan::silence() + .with_sample_rate(10) + .with_sample_count(10), + ) + .with_span( + TestSpan::silence() + .with_sample_rate(20) + .with_sample_count(10), + ); + let mut peekable = source.peekable_source(); + peekable.peek_next(); + + assert_eq!(peekable.sample_rate(), 10); + assert_eq!(peekable.by_ref().take(10).count(), 10); + assert_eq!(peekable.sample_rate(), 20); +} diff --git a/tests/position.rs b/tests/position.rs new file mode 100644 index 00000000..b0f09d7c --- /dev/null +++ b/tests/position.rs @@ -0,0 +1,93 @@ +use std::num::NonZero; +use std::time::Duration; + +use rodio::buffer::SamplesBuffer; +use rodio::Source; + +mod test_support; +use rstest::rstest; +use test_support::{TestSource, TestSpan}; + +#[rstest] +fn frame_changes( + #[values( + Duration::from_secs(0), + Duration::from_secs_f32(4.8), + Duration::from_secs(5), + Duration::from_secs_f32(9.5), + Duration::from_secs(12) + )] + to_skip: Duration, +) { + // 5 seconds per span + let source = TestSource::new() + .with_span( + TestSpan::silence() + .with_sample_rate(100) + .with_channel_count(2) + .with_exact_duration(Duration::from_secs(5)), + ) + .with_span( + TestSpan::silence() + .with_sample_rate(200) + .with_channel_count(10) + .with_exact_duration(Duration::from_secs(5)), + ) + .with_span( + TestSpan::silence() + .with_sample_rate(50) + .with_channel_count(1) + .with_exact_duration(Duration::from_secs(5)), + ); + + let tracked = source.clone().track_position(); + let skipped = tracked.skip_duration(to_skip); + let diff = to_skip + .checked_sub(skipped.inner().get_pos()) + .expect("Should never report position beyond where we are") + .as_secs_f32(); + + let curr_span = (to_skip.as_secs_f32() / 5.0) as usize; + let sample_rate = source.spans[curr_span].sample_rate as f32; + assert!(diff < 1. / sample_rate) // +} + +#[test] +fn basic_and_seek() { + let inner = SamplesBuffer::new( + NonZero::new(1).unwrap(), + 1, + vec![10.0, -10.0, 10.0, -10.0, 20.0, -20.0], + ); + let mut source = inner.track_position(); + + assert_eq!(source.get_pos().as_secs_f32(), 0.0); + source.next(); + assert_eq!(source.get_pos().as_secs_f32(), 1.0); + + source.next(); + assert_eq!(source.get_pos().as_secs_f32(), 2.0); + + assert_eq!(source.try_seek(Duration::new(1, 0)).is_ok(), true); + assert_eq!(source.get_pos().as_secs_f32(), 1.0); +} + +#[test] +fn basic_and_seek_in_presence_of_speedup() { + let inner = SamplesBuffer::new( + NonZero::new(1).unwrap(), + 1, + vec![10.0, -10.0, 10.0, -10.0, 20.0, -20.0], + ); + let mut source = inner.speed(2.0).track_position(); + + assert_eq!(source.get_pos().as_secs_f32(), 0.0); + source.next(); + assert_eq!(source.get_pos().as_secs_f32(), 0.5); + + source.next(); + assert_eq!(source.get_pos().as_secs_f32(), 1.0); + + assert_eq!(source.try_seek(Duration::new(1, 0)).is_ok(), true); + assert_eq!(source.get_pos().as_secs_f32(), 1.0); +} diff --git a/tests/queue.rs b/tests/queue.rs new file mode 100644 index 00000000..237bb2e1 --- /dev/null +++ b/tests/queue.rs @@ -0,0 +1,105 @@ +use rodio::queue::Queue; +use rodio::source::Source; + +mod test_support; +use test_support::{TestSource, TestSpan}; + +#[test] +fn basic() { + let (controls, mut source) = Queue::new(false); + + let mut source1 = TestSource::new().with_span( + TestSpan::silence() + .with_sample_rate(48000) + .with_sample_count(4), + ); + let mut source2 = TestSource::new().with_span( + TestSpan::silence() + .with_sample_rate(96000) + .with_channel_count(2) + .with_sample_count(4), + ); + controls.append(source1.clone()); + controls.append(source2.clone()); + + assert_eq!(source.parameters_changed(), true); + assert_eq!(source.channels(), source1.channels()); + assert_eq!(source.sample_rate(), source1.sample_rate()); + assert_eq!(source.next(), source1.next()); + assert_eq!(source.next(), source1.next()); + assert_eq!(source.next(), source1.next()); + assert_eq!(source.next(), source1.next()); + assert_eq!(None, source1.next()); + assert_eq!(source.parameters_changed(), true); + + assert_eq!(source.parameters_changed(), true); + assert_eq!(source.channels(), source2.channels()); + assert_eq!(source.sample_rate(), source2.sample_rate()); + assert_eq!(source.next(), source2.next()); + assert_eq!(source.next(), source2.next()); + assert_eq!(source.parameters_changed(), false); + assert_eq!(source.next(), source2.next()); + assert_eq!(source.next(), source2.next()); + assert_eq!(None, source2.next()); + + assert_eq!(source.next(), None); +} + +#[test] +fn immediate_end() { + let (_, mut source) = Queue::new(false); + assert_eq!(source.next(), None); +} + +#[test] +fn keep_alive() { + let (controls, mut source) = Queue::new(true); + controls.append( + TestSource::new() + .with_span(TestSpan::from_samples([0.1, -0.1, 0.1, -0.1]).with_sample_count(4)), + ); + + assert_eq!(source.next(), Some(0.1)); + assert_eq!(source.next(), Some(-0.1)); + assert_eq!(source.next(), Some(0.1)); + assert_eq!(source.next(), Some(-0.1)); + + for _ in 0..1000 { + assert_eq!(source.next(), Some(0.0)); + } +} + +#[test] +fn no_delay_when_added_with_keep_alive() { + let (controls, mut source) = Queue::new(true); + + for _ in 0..500 { + assert_eq!(source.next(), Some(0.0)); + } + + controls.append( + TestSource::new().with_span( + TestSpan::from_samples([0.1, -0.1, 0.1, -0.1]) + .with_channel_count(4) + .with_sample_count(4), + ), + ); + + assert_eq!(source.next(), Some(0.1)); + assert_eq!(source.next(), Some(-0.1)); + assert_eq!(source.next(), Some(0.1)); + assert_eq!(source.next(), Some(-0.1)); +} + +#[test] +fn parameters_queried_before_next() { + let test_source = + TestSource::new().with_span(TestSpan::ones().with_channel_count(5).with_sample_count(20)); + + let (controls, mut source) = Queue::new(true); + + assert_eq!(source.channels().get(), 1); + controls.append(test_source); + assert_eq!(source.channels().get(), 5); + assert_eq!(source.next(), Some(1.0)); +} diff --git a/tests/repeat.rs b/tests/repeat.rs new file mode 100644 index 00000000..fcef6a54 --- /dev/null +++ b/tests/repeat.rs @@ -0,0 +1,56 @@ +use rodio::Source; +use test_support::{TestSource, TestSpan}; + +mod test_support; + +#[test] +fn frame_boundray_at_start_of_repeat() { + let source = TestSource::new() + .with_span(TestSpan::from_samples((0..10).map(|n| n as f32)).with_sample_count(10)) + .with_span(TestSpan::from_samples((10..20).map(|n| n as f32)).with_sample_count(10)); + + let mut repeating = source.clone().repeat_infinite(); + repeating.by_ref().take(source.len()).count(); + assert!(repeating.parameters_changed()); + + assert!(repeating.next().is_some()); + assert!(!repeating.parameters_changed()); +} + +#[test] +fn parameters_identical_on_second_run() { + let source = TestSource::new() + .with_span(TestSpan::from_samples((0..10).map(|n| n as f32)).with_sample_count(10)) + .with_span(TestSpan::from_samples((10..20).map(|n| n as f32)).with_sample_count(10)); + + let mut repeating = source.clone().repeat_infinite(); + + let mut first_run_params = Vec::new(); + let mut second_run_params = Vec::new(); + + for params in [&mut first_run_params, &mut second_run_params] { + for _ in 0..source.len() { + assert!(repeating.by_ref().next().is_some()); + params.push(( + repeating.parameters_changed(), + repeating.channels(), + repeating.sample_rate(), + )); + } + } + + assert_eq!(first_run_params, second_run_params); +} + +#[test] +fn same_samples_on_second_run() { + let source = TestSource::new() + .with_span(TestSpan::from_samples((0..10).map(|n| n as f32)).with_sample_count(10)) + .with_span(TestSpan::from_samples((10..20).map(|n| n as f32)).with_sample_count(10)); + + let mut repeating = source.clone().repeat_infinite(); + let first_run: Vec<_> = repeating.by_ref().take(source.len()).collect(); + let second_run: Vec<_> = repeating.take(source.len()).collect(); + + assert_eq!(first_run, second_run); +} diff --git a/tests/reverb.rs b/tests/reverb.rs new file mode 100644 index 00000000..0191a09d --- /dev/null +++ b/tests/reverb.rs @@ -0,0 +1,27 @@ +mod test_support; +use std::time::Duration; + +use rodio::Source; +use test_support::{TestSource, TestSpan}; + +#[test] +fn without_buffered() { + let source = TestSource::new() + .with_span(TestSpan::silence().with_exact_duration(Duration::from_secs(5))); + + let reverb = source.clone().reverb(Duration::from_secs_f32(0.05), 0.3); + let n_samples = reverb.count(); + + assert_eq!(n_samples, source.len(),); +} + +#[test] +fn with_buffered() { + let source = TestSource::new() + .with_span(TestSpan::silence().with_exact_duration(Duration::from_secs(5))); + + let reverb = source.clone().buffered().reverb(Duration::from_secs_f32(0.05), 0.3); + let n_samples = reverb.count(); + + assert_eq!(n_samples, source.len(),); +} diff --git a/tests/seek.rs b/tests/seek.rs index 38691144..987f8d12 100644 --- a/tests/seek.rs +++ b/tests/seek.rs @@ -1,4 +1,4 @@ -use rodio::{ChannelCount, Decoder, Source}; +use rodio::{Decoder, Source}; use rstest::rstest; use rstest_reuse::{self, *}; use std::io::{BufReader, Read, Seek}; @@ -81,7 +81,6 @@ fn seek_results_in_correct_remaining_playtime( let decoder = get_music(format); let total_duration = time_remaining(decoder); - dbg!(total_duration); const SEEK_BEFORE_END: Duration = Duration::from_secs(5); let mut decoder = get_music(format); @@ -121,11 +120,11 @@ fn seek_does_not_break_channel_order( ) { let mut source = get_rl(format); let channels = source.channels(); - assert_eq!(channels, 2, "test needs a stereo beep file"); + assert_eq!(channels.get(), 2, "test needs a stereo beep file"); let beep_range = second_channel_beep_range(&mut source); let beep_start = Duration::from_secs_f32( - beep_range.start as f32 / source.channels() as f32 / source.sample_rate() as f32, + beep_range.start as f32 / source.channels().get() as f32 / source.sample_rate() as f32, ); let mut source = get_rl(format); @@ -144,7 +143,7 @@ fn seek_does_not_break_channel_order( let samples: Vec<_> = source.by_ref().take(100).collect(); let channel0 = 0 + channel_offset; assert!( - is_silent(&samples, source.channels(), channel0), + is_silent(&samples, source.channels().get() as usize, channel0), "channel0 should be silent, channel0 starts at idx: {channel0} seek: {beep_start:?} + {offset:?} @@ -152,7 +151,7 @@ fn seek_does_not_break_channel_order( ); let channel1 = (1 + channel_offset) % 2; assert!( - !is_silent(&samples, source.channels(), channel1), + !is_silent(&samples, source.channels().get() as usize, channel1), "channel1 should not be silent, channel1; starts at idx: {channel1} seek: {beep_start:?} + {offset:?} @@ -165,7 +164,7 @@ fn second_channel_beep_range(source: &mut R) -> std::ops::Rang where R: Iterator, { - let channels = source.channels() as usize; + let channels = source.channels().get() as usize; let samples: Vec = source.by_ref().collect(); const WINDOW: usize = 50; @@ -202,21 +201,15 @@ where .next_multiple_of(channels); let samples = &samples[beep_starts..beep_starts + 100]; - assert!( - is_silent(samples, channels as ChannelCount, 0), - "{samples:?}" - ); - assert!( - !is_silent(samples, channels as ChannelCount, 1), - "{samples:?}" - ); + assert!(is_silent(samples, channels, 0), "{samples:?}"); + assert!(!is_silent(samples, channels, 1), "{samples:?}"); beep_starts..beep_ends } -fn is_silent(samples: &[f32], channels: ChannelCount, channel: usize) -> bool { +fn is_silent(samples: &[f32], channels: usize, channel: usize) -> bool { assert_eq!(samples.len(), 100); - let channel = samples.iter().skip(channel).step_by(channels as usize); + let channel = samples.iter().skip(channel).step_by(channels); let volume = channel.map(|s| s.abs()).sum::() / samples.len() as f32 * channels as f32; const BASICALLY_ZERO: f32 = 0.0001; @@ -225,7 +218,7 @@ fn is_silent(samples: &[f32], channels: ChannelCount, channel: usize) -> bool { fn time_remaining(decoder: Decoder) -> Duration { let rate = decoder.sample_rate() as f64; - let n_channels = decoder.channels() as f64; + let n_channels = decoder.channels().get() as f64; let n_samples = decoder.into_iter().count() as f64; Duration::from_secs_f64(n_samples / rate / n_channels) } diff --git a/tests/skip_duration.rs b/tests/skip_duration.rs new file mode 100644 index 00000000..e024f799 --- /dev/null +++ b/tests/skip_duration.rs @@ -0,0 +1,92 @@ +use std::time::Duration; + +use rodio::buffer::SamplesBuffer; +use rodio::source::Source; +use rodio::{ChannelCount, SampleRate}; +use rstest::rstest; + +mod test_support; +use test_support::{TestSource, TestSpan}; + +#[rstest] +fn ends_on_frame_boundary(#[values(1.483, 2.999)] seconds_to_skip: f32) { + let source = TestSource::new().with_span( + TestSpan::silence() + .with_channel_count(10) + .with_sample_rate(1) + .with_exact_duration(Duration::from_secs(3)), + ); + let leftover = source + .clone() + .skip_duration(Duration::from_secs_f32(seconds_to_skip)) + .count(); + assert!(leftover % source.channels().get() as usize == 0) +} + +#[rstest] +fn param_changes_during_skip(#[values(6, 11)] seconds_to_skip: u64) { + let span_duration = Duration::from_secs(5); + let source = TestSource::new() + .with_span( + TestSpan::silence() + .with_sample_rate(10) + .with_channel_count(1) + .with_exact_duration(span_duration), + ) + .with_span( + TestSpan::silence() + .with_sample_rate(20) + .with_channel_count(2) + .with_exact_duration(span_duration), + ) + .with_span( + TestSpan::silence() + .with_sample_rate(5) + .with_channel_count(3) + .with_exact_duration(span_duration), + ); + + let leftover = source + .clone() + .skip_duration(Duration::from_secs(seconds_to_skip)) + .count(); + + let spans = source.spans; + let expected_leftover = match seconds_to_skip { + 6 => 4 * spans[1].sample_rate as usize * spans[1].channels.get() as usize + spans[2].len(), + 11 => 4 * spans[2].sample_rate as usize * spans[2].channels.get() as usize, + _ => unreachable!(), + }; + + assert_eq!(leftover, expected_leftover); +} + +#[rstest] +fn samples_left( + #[values(1, 2, 4)] channels: u16, + #[values(100_000)] sample_rate: SampleRate, + #[values(0, 5)] seconds: u32, + #[values(0, 3, 5)] seconds_to_skip: u32, +) { + println!( + "channels: {channels}, sample_rate: {sample_rate}, \ + seconds: {seconds}, seconds_to_skip: {seconds_to_skip}" + ); + let channels = ChannelCount::new(channels).unwrap(); + + let buf_len = (sample_rate * channels.get() as u32 * seconds) as usize; + assert!(buf_len < 10 * 1024 * 1024); + let data: Vec = vec![0f32; buf_len]; + let test_buffer = SamplesBuffer::new(channels, sample_rate, data); + let seconds_left = seconds.saturating_sub(seconds_to_skip); + + let samples_left_expected = (sample_rate * channels.get() as u32 * seconds_left) as usize; + let samples_left = test_buffer + .skip_duration(Duration::from_secs(seconds_to_skip as u64)) + .count(); + + assert!( + samples_left == samples_left_expected, + "expected {samples_left_expected} samples left, counted: {samples_left}" + ); +} diff --git a/tests/take_duration.rs b/tests/take_duration.rs new file mode 100644 index 00000000..b322c4e1 --- /dev/null +++ b/tests/take_duration.rs @@ -0,0 +1,130 @@ +use std::time::Duration; + +use rodio::buffer::SamplesBuffer; +use rodio::source::Source; +use rodio::{ChannelCount, SampleRate}; +use rstest::rstest; + +mod test_support; +use test_support::{TestSource, TestSpan}; + +#[rstest] +fn ends_on_frame_boundary(#[values(1.483, 2.999)] seconds_to_skip: f32) { + let source = TestSource::new().with_span( + TestSpan::silence() + .with_channel_count(10) + .with_sample_rate(1) + .with_exact_duration(Duration::from_secs(3)), + ); + let got = source + .clone() + .take_duration(Duration::from_secs_f32(seconds_to_skip)) + // fadeout enables extra logic, run it too to check for bounds/overflow issues + .with_fadeout(true) + + .count(); + assert!(got % source.channels().get() as usize == 0) +} + +#[rstest] +fn param_changes_during_skip(#[values(6, 11)] seconds_to_skip: u64) { + let span_duration = Duration::from_secs(5); + let source = TestSource::new() + .with_span( + TestSpan::silence() + .with_sample_rate(10) + .with_channel_count(1) + .with_exact_duration(span_duration), + ) + .with_span( + TestSpan::silence() + .with_sample_rate(20) + .with_channel_count(2) + .with_exact_duration(span_duration), + ) + .with_span( + TestSpan::silence() + .with_sample_rate(5) + .with_channel_count(3) + .with_exact_duration(span_duration), + ); + + let took = source + .clone() + .take_duration(Duration::from_secs(seconds_to_skip)) + .with_fadeout(true) + .count(); + + let spans = source.spans; + let expected = match seconds_to_skip { + 6 => spans[0].len() + 1 * spans[1].sample_rate as usize * spans[1].channels.get() as usize, + 11 => { + spans[0].len() + + spans[1].len() + + 1 * spans[2].sample_rate as usize * spans[2].channels.get() as usize + } + _ => unreachable!(), + }; + + assert!( + took == expected, + "expected {expected} samples, took only: {took}" + ); +} + +#[test] +fn fadeout() { + let span_duration = Duration::from_secs(5); + let source = TestSource::new() + .with_span( + TestSpan::ones() + .with_sample_rate(5) + .with_channel_count(1) + .with_exact_duration(span_duration), + ) + .with_span( + TestSpan::ones() + .with_sample_rate(5) + .with_channel_count(2) + .with_exact_duration(span_duration), + ); + + let fade_out = source + .take_duration(span_duration.mul_f32(1.5)) + .with_fadeout(true) + .collect::>(); + assert_eq!(fade_out.first(), Some(&1.0)); + // fade_out ends the step before zero + assert!(fade_out.last().unwrap() > &0.0); +} + +#[rstest] +fn samples_taken( + #[values(1, 2, 4)] channels: u16, + #[values(100_000)] sample_rate: SampleRate, + #[values(0, 5)] seconds: u32, + #[values(0, 3, 5)] seconds_to_take: u32, +) { + println!( + "channels: {channels}, sample_rate: {sample_rate}, \ + seconds: {seconds}, seconds_to_take: {seconds_to_take}" + ); + let channels = ChannelCount::new(channels).unwrap(); + + let buf_len = (sample_rate * channels.get() as u32 * seconds) as usize; + assert!(buf_len < 10 * 1024 * 1024); + let data: Vec = vec![0f32; buf_len]; + let test_buffer = SamplesBuffer::new(channels, sample_rate, data); + + let samples = test_buffer + .take_duration(Duration::from_secs(seconds_to_take as u64)) + .with_fadeout(true) + .count(); + + let seconds_we_can_take = seconds_to_take.min(seconds); + let samples_expected = (sample_rate * channels.get() as u32 * seconds_we_can_take) as usize; + assert!( + samples == samples_expected, + "expected {samples_expected} samples, took only: {samples}" + ); +} diff --git a/tests/test_support/mod.rs b/tests/test_support/mod.rs new file mode 100644 index 00000000..cce584d1 --- /dev/null +++ b/tests/test_support/mod.rs @@ -0,0 +1,437 @@ +#![allow(dead_code)] +use std::iter; +/// in separate folder so its not ran as integration test +/// should probably be moved to its own crate (rodio-test-support) +/// that would fix the unused code warnings. +use std::time::Duration; + +use rodio::source::{self, Function, SignalGenerator}; +use rodio::{ChannelCount, Sample, SampleRate, Source}; + +#[derive(Debug, Clone)] +pub enum SampleSource { + SignalGen { + function: Function, + samples: Vec, + frequency: f32, + }, + Silence, + List(Vec), + SampleIndex, + Ones, +} + +impl SampleSource { + fn get( + &mut self, + pos: usize, + pos_in_source: usize, + sample_rate: SampleRate, + channels: ChannelCount, + numb_samples: usize, + ) -> Option { + if pos >= numb_samples { + return None; + } + + match self { + SampleSource::SignalGen { + function, + samples, + frequency, + } if samples.len() != numb_samples => { + *samples = SignalGenerator::new(sample_rate, *frequency, function.clone()) + .take(numb_samples) + .flat_map(|sample| iter::repeat_n(sample, channels.get().into())) + .collect(); + samples.get(pos).copied() + } + SampleSource::SignalGen { samples, .. } => samples.get(pos).copied(), + SampleSource::List(list) => list.get(pos).copied(), + SampleSource::SampleIndex => Some(pos_in_source as f32), + SampleSource::Silence { .. } => Some(0.0), + SampleSource::Ones => Some(1.0), + } + } +} + +#[derive(Debug, Clone)] +pub struct TestSpan { + pub sample_source: SampleSource, + pub sample_rate: SampleRate, + pub channels: ChannelCount, + numb_samples: usize, +} + +#[derive(Debug, Clone)] +pub struct TestSpanBuilder { + pub sample_source: SampleSource, + pub sample_rate: SampleRate, + pub channels: u16, +} + +impl TestSpan { + pub fn silence() -> TestSpanBuilder { + TestSpanBuilder { + sample_source: SampleSource::Silence, + sample_rate: 1, + channels: 1, + } + } + pub fn ones() -> TestSpanBuilder { + TestSpanBuilder { + sample_source: SampleSource::Ones, + sample_rate: 1, + channels: 1, + } + } + pub fn sample_counter() -> TestSpanBuilder { + TestSpanBuilder { + sample_source: SampleSource::SampleIndex, + sample_rate: 1, + channels: 1, + } + } + pub fn sine(frequency: f32) -> TestSpanBuilder { + TestSpanBuilder { + sample_source: SampleSource::SignalGen { + frequency, + samples: Vec::new(), + function: Function::Sine, + }, + sample_rate: 1, + channels: 1, + } + } + pub fn square(frequency: f32) -> TestSpanBuilder { + TestSpanBuilder { + sample_source: SampleSource::SignalGen { + frequency, + samples: Vec::new(), + function: Function::Square, + }, + sample_rate: 1, + channels: 1, + } + } + pub fn from_samples<'a>(samples: impl IntoIterator) -> TestSpanBuilder { + let samples = samples.into_iter().collect::>(); + TestSpanBuilder { + sample_source: SampleSource::List(samples), + sample_rate: 1, + channels: 1, + } + } + + fn get(&mut self, pos: usize, pos_in_source: usize) -> Option { + self.sample_source.get( + pos, + pos_in_source, + self.sample_rate, + self.channels, + self.numb_samples, + ) + } + + pub fn len(&self) -> usize { + self.numb_samples + } +} + +impl TestSpanBuilder { + pub fn with_sample_rate(mut self, sample_rate: SampleRate) -> Self { + self.sample_rate = sample_rate; + self + } + /// # Panics + /// if channel_count is zero + pub fn with_channel_count(mut self, channel_count: u16) -> Self { + self.channels = channel_count; + self + } + pub fn with_sample_count(self, n: usize) -> TestSpan { + if let SampleSource::List(list) = &self.sample_source { + assert!( + list.len() == n, + "The list providing samples is a different length \ + ({}) as the required sample count {}", + list.len(), + n + ); + } + + TestSpan { + sample_source: self.sample_source, + sample_rate: self.sample_rate, + channels: ChannelCount::new(self.channels).expect("Channel count must be > 0"), + numb_samples: n, + } + } + /// is allowed to be 1% off + pub fn with_rough_duration(self, duration: Duration) -> TestSpan { + let (needed_samples, _) = self.needed_samples(duration); + + if let SampleSource::List(list) = &self.sample_source { + let allowed_deviation = needed_samples as usize / 10; + assert!( + list.len().abs_diff(needed_samples as usize) > allowed_deviation, + "provided sample list does not provide the correct amount + of samples for a test span with the given duration" + ) + } + + TestSpan { + numb_samples: needed_samples + .try_into() + .expect("too many samples for test source"), + sample_source: self.sample_source, + sample_rate: self.sample_rate, + channels: ChannelCount::new(self.channels).expect("Channel count must be > 0"), + } + } + pub fn with_exact_duration(self, duration: Duration) -> TestSpan { + let (needed_samples, deviation) = self.needed_samples(duration); + + assert_eq!( + deviation, 0, + "requested duration {:?} is, at the highest precision not a \ + multiple of sample_rate {} and channels {}. Consider using \ + `with_rough_duration`", + duration, self.sample_rate, self.channels + ); + + if let SampleSource::List(list) = &self.sample_source { + assert_eq!( + list.len(), + needed_samples as usize, + "provided sample list does not provide the correct amount + of samples for a test span with the given duration" + ) + } + + TestSpan { + numb_samples: needed_samples + .try_into() + .expect("too many samples for test source"), + sample_source: self.sample_source, + sample_rate: self.sample_rate, + channels: ChannelCount::new(self.channels).expect("Channel count must be > 0"), + } + } + + fn needed_samples(&self, duration: Duration) -> (u64, u64) { + const NS_PER_SECOND: u64 = 1_000_000_000; + let duration_ns: u64 = duration + .as_nanos() + .try_into() + .expect("Test duration should not be more then ~500 days"); + + let needed_samples = + duration_ns * self.sample_rate as u64 * self.channels as u64 / NS_PER_SECOND; + let duration_of_those_samples = + needed_samples * NS_PER_SECOND / self.sample_rate as u64 / self.channels as u64; + let deviation = duration_ns.abs_diff(duration_of_those_samples); + (needed_samples, deviation) + } +} + +#[derive(Debug, Clone)] +pub struct TestSource { + pub spans: Vec, + current_span: usize, + pos_in_span: usize, + pos_in_source: usize, + parameters_changed: bool, +} + +impl TestSource { + pub fn new() -> Self { + Self { + spans: Vec::new(), + current_span: 0, + pos_in_span: 0, + parameters_changed: false, + pos_in_source: 0, + } + } + pub fn with_span(mut self, span: TestSpan) -> Self { + self.spans.push(span); + self + } +} + +impl Iterator for TestSource { + type Item = f32; + + fn next(&mut self) -> Option { + let current_span = self.spans.get_mut(self.current_span)?; + let sample = current_span.get(self.pos_in_span, self.pos_in_source)?; + self.pos_in_source += 1; + self.pos_in_span += 1; + + // if span is out of samples + // - next set parameters_changed now + // - switch to the next span + if self.pos_in_span == current_span.len() { + self.pos_in_span = 0; + self.current_span += 1; + } + + Some(sample) + } + + fn size_hint(&self) -> (usize, Option) { + let len = self + .spans + .iter() + .skip(self.current_span) + .map(TestSpan::len) + .sum::() + - self.pos_in_span; + (len, Some(len)) + } +} + +impl ExactSizeIterator for TestSource {} + +impl rodio::Source for TestSource { + fn parameters_changed(&self) -> bool { + self.pos_in_span == 1 + } + fn channels(&self) -> rodio::ChannelCount { + self.spans + .get(self.current_span) + .map(|span| span.channels) + .unwrap_or_else(|| { + self.spans + .last() + .expect("TestSource must have at least one span") + .channels + }) + } + fn sample_rate(&self) -> rodio::SampleRate { + self.spans + .get(self.current_span) + .map(|span| span.sample_rate) + .unwrap_or_else(|| { + self.spans + .last() + .expect("TestSource must have at least one span") + .sample_rate + }) + } + fn total_duration(&self) -> Option { + None + } + fn try_seek(&mut self, _pos: Duration) -> Result<(), source::SeekError> { + todo!(); + // let duration_per_sample = Duration::from_secs(1) / self.sample_rate; + // let offset = pos.div_duration_f64(duration_per_sample).floor() as usize; + // self.pos = offset; + + // Ok(()) + } +} + +// test for your tests of course. Leave these in, they guard regression when we +// expand the functionally which we probably will. +#[test] +fn parameters_change_correct() { + let mut source = TestSource::new() + .with_span(TestSpan::silence().with_sample_count(10)) + .with_span(TestSpan::silence().with_sample_count(10)); + + assert!(source.next().is_some()); + assert!(source.parameters_changed()); + assert_eq!(source.by_ref().take(9).count(), 9); + assert!(!source.parameters_changed()); + // end first span + + assert!(source.next().is_some()); + assert!(source.parameters_changed()); + assert_eq!(source.count(), 9); +} + +#[test] +fn channel_count_changes() { + let mut source = TestSource::new() + .with_span( + TestSpan::silence() + .with_channel_count(1) + .with_sample_count(10), + ) + .with_span( + TestSpan::silence() + .with_channel_count(2) + .with_sample_count(10), + ); + + assert_eq!(source.channels().get(), 1); + assert_eq!(source.by_ref().take(10).count(), 10); + assert_eq!(source.channels().get(), 2); +} + +#[test] +fn sample_rate_changes() { + let mut source = TestSource::new() + .with_span( + TestSpan::silence() + .with_sample_rate(10) + .with_sample_count(10), + ) + .with_span( + TestSpan::silence() + .with_sample_rate(20) + .with_sample_count(10), + ); + + assert_eq!(source.sample_rate(), 10); + assert_eq!(source.by_ref().take(10).count(), 10); + assert_eq!(source.sample_rate(), 20); +} + +#[test] +fn sine_is_avg_zero() { + let sine = TestSource::new().with_span( + TestSpan::sine(400.0) + .with_sample_rate(10_000) + .with_sample_count(500), + ); + + let avg = sine.clone().sum::() / sine.spans[0].len() as f32; + assert!(avg < 0.00001f32); +} + +#[test] +fn sine_abs_avg_not_zero() { + let sine = TestSource::new().with_span( + TestSpan::sine(400.0) + .with_sample_rate(10_000) + .with_sample_count(500), + ); + + let avg = sine.clone().map(f32::abs).sum::() / sine.spans[0].len() as f32; + assert!(avg > 0.5); +} + +#[test] +fn size_hint() { + let mut source = TestSource::new() + .with_span( + TestSpan::silence() + .with_sample_rate(10) + .with_sample_count(10), + ) + .with_span( + TestSpan::silence() + .with_sample_rate(20) + .with_sample_count(10), + ); + + assert_eq!(source.len(), 20); + assert_eq!(source.by_ref().take(10).count(), 10); + assert_eq!(source.len(), 10); + assert_eq!(source.by_ref().take(10).count(), 10); + assert_eq!(source.len(), 0); + assert!(source.next().is_none()) +}