Skip to content

Commit 6b15274

Browse files
Make IntoInterleavedSamples(Iterator) stop yielding samples on exhaustion (#178)
* Make IntoInterleavedSamples(Iterator) stop yielding samples on exhaustion `IntoInterleavedSamples`, especially its companion `IntoInterleavedSamplesIterator` type, is, in my opinion, very useful to easily process samples from an audio signal with elegance. However, these helper types do not handle the source signal being exhausted at all. When the signal is exhausted, they always return equilibrium-valued samples, turning finite signals into infinite ones in an arguably counter-intuitive manner. I also think that quirk, even though it is documented, is somewhat hard to discover, because it is indirectly mentioned in the description of the `IntoInterleavedSamplesIterator` type, which is not shown when using the features of most IDEs. In particular, I think the following reasons justify this change to achieve a better API: - Exhaustion is contagious for the rest of adapters returned by `Signal` methods. It's inconsistent that `into_interleaved_samples` behaves differently. - In some scenarios the total number of samples of a finite signal is not known, and is not feasible to read it all to a buffer. Also, valid samples may have a value that is numerically equal to the equilibrium value, so checking for equilibrium values is not a good solution. On the contrary, it always is possible to make the finite sequence infinite easily. When dealing with iterators, this is as easy as calling `.chain(std::iter::repeat(f32::EQUILIBRIUM)`, for example. - It provides for more elegant and failure-proof client code in some cases. For example, the following code now terminates when the input signal is finite, as I think most people would expect: ```rust dasp_signal::from_interleaved_samples_iter::<_, Stereo<f32>>(signal.into_interleaved_samples().into_iter().map(|s| s * 2))) ``` To address this situation, these changes modify how the related methods are implemented and their signatures. `cargo test` runs successfully, and I have updated the related examples and documentation accordingly.
1 parent 66e8b83 commit 6b15274

File tree

2 files changed

+26
-12
lines changed

2 files changed

+26
-12
lines changed

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
11
# Unreleased
22

33
- Renamed `window-hanning` to `window-hann`
4+
- Made `IntoInterleavedSamples` and `IntoInterleavedSamplesIterator` stop
5+
yielding samples when the underlying signal gets exhausted. This is a breaking
6+
change. The return type of the `IntoInterleavedSamples#next_sample` method was
7+
modified.
48

59
---
610

dasp_signal/src/lib.rs

Lines changed: 22 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -547,18 +547,17 @@ pub trait Signal {
547547
/// let frames = [[0.1, 0.2], [0.3, 0.4]];
548548
/// let signal = signal::from_iter(frames.iter().cloned());
549549
/// let samples = signal.into_interleaved_samples();
550-
/// let samples: Vec<_> = samples.into_iter().take(4).collect();
550+
/// let samples: Vec<_> = samples.into_iter().collect();
551551
/// assert_eq!(samples, vec![0.1, 0.2, 0.3, 0.4]);
552552
/// }
553553
/// ```
554-
fn into_interleaved_samples(mut self) -> IntoInterleavedSamples<Self>
554+
fn into_interleaved_samples(self) -> IntoInterleavedSamples<Self>
555555
where
556556
Self: Sized,
557557
{
558-
let first = self.next().channels();
559558
IntoInterleavedSamples {
560559
signal: self,
561-
current_frame: first,
560+
current_frame: None,
562561
}
563562
}
564563

@@ -1052,10 +1051,11 @@ where
10521051
S: Signal,
10531052
{
10541053
signal: S,
1055-
current_frame: <S::Frame as Frame>::Channels,
1054+
current_frame: Option<<S::Frame as Frame>::Channels>,
10561055
}
10571056

1058-
/// Converts the `IntoInterleavedSamples` into an `Iterator` that always returns `Some`.
1057+
/// Converts the `IntoInterleavedSamples` into an `Iterator` that returns `Some`
1058+
/// until the signal is exhausted.
10591059
pub struct IntoInterleavedSamplesIterator<S>
10601060
where
10611061
S: Signal,
@@ -2286,13 +2286,23 @@ where
22862286
S: Signal,
22872287
{
22882288
/// Yield the next interleaved sample from the inner `Signal`.
2289+
///
2290+
/// The returned value is `None` if the signal is exhausted.
22892291
#[inline]
2290-
pub fn next_sample(&mut self) -> <S::Frame as Frame>::Sample {
2291-
loop {
2292-
match self.current_frame.next() {
2293-
Some(channel) => return channel,
2294-
None => self.current_frame = self.signal.next().channels(),
2292+
pub fn next_sample(&mut self) -> Option<<S::Frame as Frame>::Sample> {
2293+
if self.current_frame.is_none() && !self.signal.is_exhausted() {
2294+
self.current_frame = Some(self.signal.next().channels());
2295+
}
2296+
2297+
if let Some(current_frame) = &mut self.current_frame {
2298+
if let Some(sample) = current_frame.next() {
2299+
Some(sample)
2300+
} else {
2301+
self.current_frame = None;
2302+
self.next_sample()
22952303
}
2304+
} else {
2305+
None
22962306
}
22972307
}
22982308

@@ -2311,7 +2321,7 @@ where
23112321

23122322
#[inline]
23132323
fn next(&mut self) -> Option<Self::Item> {
2314-
Some(self.samples.next_sample())
2324+
self.samples.next_sample()
23152325
}
23162326
}
23172327

0 commit comments

Comments
 (0)