Skip to content

Add range methods to Channel and Buf #28

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
52 changes: 52 additions & 0 deletions audio-core/src/buf.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@ pub use self::skip::Skip;
mod limit;
pub use self::limit::Limit;

mod range;
pub use self::range::Range;

mod tail;
pub use self::tail::Tail;

Expand Down Expand Up @@ -282,6 +285,55 @@ pub trait Buf {
{
Limit::new(self, limit)
}

/// Construct a new buffer limited to the specified range of frames of the
/// original buffer.
///
/// # Examples
///
/// ```
/// use audio::Buf;
///
/// let seq = audio::sequential![[1, 2, 3, 4]; 1];
/// let seq_unbounded = seq.range(..);
/// assert_eq!(seq_unbounded.get_channel(0).unwrap().as_ref(), [1, 2, 3, 4]);
/// let seq_limited = seq_unbounded.range(1..3);
/// assert_eq!(seq_limited.get_channel(0).unwrap().as_ref(), [2, 3]);
///
/// let interleaved = audio::interleaved![[1, 2, 3, 4]; 1];
/// let interleaved_unbounded = interleaved.range(..);
/// assert_eq!(interleaved_unbounded.get_channel(0).unwrap().get(0).unwrap(), 1);
/// assert_eq!(interleaved_unbounded.get_channel(0).unwrap().get(1).unwrap(), 2);
/// assert_eq!(interleaved_unbounded.get_channel(0).unwrap().get(2).unwrap(), 3);
/// assert_eq!(interleaved_unbounded.get_channel(0).unwrap().get(3).unwrap(), 4);
/// let interleaved_limited = interleaved_unbounded.range(1..3);
/// assert_eq!(interleaved_limited.get_channel(0).unwrap().get(0).unwrap(), 2);
/// assert_eq!(interleaved_limited.get_channel(0).unwrap().get(1).unwrap(), 3);
/// ```
///
/// # Panics
///
/// Panics if the end is out of bounds or [frames_hint][Buf::frames_hint] returns [None].
///
/// ```should_panic
/// use audio::Buf;
///
/// let seq = audio::sequential![[1, 2, 3, 4]; 1];
/// let seq_range = seq.range(..5);
/// ```
///
/// ```should_panic
/// use audio::Buf;
///
/// let interleaved = audio::interleaved![[1, 2, 3, 4]; 1];
/// let interleaved_range = interleaved.range(..5);
/// ```
fn range(self, range: impl core::ops::RangeBounds<usize>) -> Range<Self>
where
Self: Sized,
{
Range::new(self, range)
}
}

impl<B> Buf for &B
Expand Down
213 changes: 213 additions & 0 deletions audio-core/src/buf/range.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,213 @@
use crate::{Buf, BufMut, Channel, ChannelMut, ExactSizeBuf, ReadBuf};

use core::ops;
use core::ops::Bound;

/// A range of a buffer
///
/// See [Buf::range].
pub struct Range<B> {
buf: B,
range: ops::Range<usize>,
}

impl<B> Range<B> {
/// Construct a new limited buffer.
pub(crate) fn new(buf: B, range: impl ops::RangeBounds<usize>) -> Self
where B: Buf {
let start = match range.start_bound() {
Bound::Unbounded => 0,
Bound::Included(&i) => i,
Bound::Excluded(&i) => i + 1,
};
let max = buf.frames_hint().expect("Unable to check bounds of range for Buf because frames_hint returned None");
let end = match range.end_bound() {
Bound::Unbounded => max,
Bound::Included(&i) => i + 1,
Bound::Excluded(&i) => i,
};
assert!(end <= max, "End index {} out of bounds, maximum {}", end, max);
assert!(start <= end);
let range = core::ops::Range{ start, end };
Self { buf, range }
}
}

impl<B> Buf for Range<B>
where
B: Buf,
{
type Sample = B::Sample;

type Channel<'this> = B::Channel<'this>
where
Self: 'this;

type IterChannels<'this> = IterChannels<B::IterChannels<'this>>
where
Self: 'this;

fn frames_hint(&self) -> Option<usize> {
Some(self.range.len())
}

fn channels(&self) -> usize {
self.buf.channels()
}

fn get_channel(&self, channel: usize) -> Option<Self::Channel<'_>> {
Some(self.buf.get_channel(channel)?.range(self.range.clone()))
}

fn iter_channels(&self) -> Self::IterChannels<'_> {
IterChannels {
iter: self.buf.iter_channels(),
range: self.range.clone(),
}
}
}

impl<B> BufMut for Range<B>
where
B: BufMut,
{
type ChannelMut<'this> = B::ChannelMut<'this>
where
Self: 'this;

type IterChannelsMut<'this> = IterChannelsMut<B::IterChannelsMut<'this>>
where
Self: 'this;

fn get_channel_mut(&mut self, channel: usize) -> Option<Self::ChannelMut<'_>> {
Some(self.buf.get_channel_mut(channel)?.range(self.range.clone()))
}

fn copy_channel(&mut self, from: usize, to: usize)
where
Self::Sample: Copy,
{
self.buf.copy_channel(from, to);
}

fn iter_channels_mut(&mut self) -> Self::IterChannelsMut<'_> {
IterChannelsMut {
iter: self.buf.iter_channels_mut(),
range: self.range.clone(),
}
}
}

/// [Range] adjusts the implementation of [ExactSizeBuf] to take the frame
/// limiting into account.
///
/// ```
/// use audio::{Buf, ExactSizeBuf};
///
/// let buf = audio::interleaved![[0; 4]; 2];
///
/// assert_eq!((&buf).limit(0).frames(), 0);
/// assert_eq!((&buf).limit(1).frames(), 1);
/// assert_eq!((&buf).limit(5).frames(), 4);
/// ```
impl<B> ExactSizeBuf for Range<B>
where
B: ExactSizeBuf,
{
fn frames(&self) -> usize {
self.range.len()
}
}

impl<B> ReadBuf for Range<B>
where
B: ReadBuf,
{
fn remaining(&self) -> usize {
usize::min(self.buf.remaining(), self.range.len())
}

fn advance(&mut self, n: usize) {
self.buf.advance(usize::min(n, self.range.len()));
}
}

// TODO: fix macro
// iterators!(range: core::ops::Range<usize> => self.range(range.clone(());
Copy link
Contributor Author

@Be-ing Be-ing Dec 11, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you can suggest how to get the macro to accept the range.clone(), that would allow getting rid of the boilerplate below.


pub struct IterChannels<I> {
iter: I,
range: core::ops::Range<usize>,
}
impl<I> Iterator for IterChannels<I>
where
I: Iterator,
I::Item: Channel,
{
type Item = I::Item;
fn next(&mut self) -> Option<Self::Item> {
Some(self.iter.next()?.range(self.range.clone()))
}
fn nth(&mut self, n: usize) -> Option<Self::Item> {
Some(self.iter.nth(n)?.range(self.range.clone()))
}
}
impl<I> DoubleEndedIterator for IterChannels<I>
where
I: DoubleEndedIterator,
I::Item: Channel,
{
fn next_back(&mut self) -> Option<Self::Item> {
Some(self.iter.next_back()?.range(self.range.clone()))
}
fn nth_back(&mut self, n: usize) -> Option<Self::Item> {
Some(self.iter.nth_back(n)?.range(self.range.clone()))
}
}
impl<I> ExactSizeIterator for IterChannels<I>
where
I: ExactSizeIterator,
I::Item: ChannelMut,
{
fn len(&self) -> usize {
self.iter.len()
}
}
pub struct IterChannelsMut<I> {
iter: I,
range: core::ops::Range<usize>,
}
impl<I> Iterator for IterChannelsMut<I>
where
I: Iterator,
I::Item: ChannelMut,
{
type Item = I::Item;
fn next(&mut self) -> Option<Self::Item> {
Some(self.iter.next()?.range(self.range.clone()))
}
fn nth(&mut self, n: usize) -> Option<Self::Item> {
Some(self.iter.nth(n)?.range(self.range.clone()))
}
}
impl<I> DoubleEndedIterator for IterChannelsMut<I>
where
I: DoubleEndedIterator,
I::Item: ChannelMut,
{
fn next_back(&mut self) -> Option<Self::Item> {
Some(self.iter.next_back()?.range(self.range.clone()))
}
fn nth_back(&mut self, n: usize) -> Option<Self::Item> {
Some(self.iter.nth_back(n)?.range(self.range.clone()))
}
}
impl<I> ExactSizeIterator for IterChannelsMut<I>
where
I: ExactSizeIterator,
I::Item: ChannelMut,
{
fn len(&self) -> usize {
self.iter.len()
}
}
47 changes: 47 additions & 0 deletions audio-core/src/channel.rs
Original file line number Diff line number Diff line change
Expand Up @@ -197,4 +197,51 @@ pub trait Channel {
/// assert_eq!(to.as_slice(), &[1.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 0.0]);
/// ```
fn limit(self, limit: usize) -> Self;

/// Construct a channel using the specified range of frames
///
/// # Examples
///
/// ```
/// use audio::Channel;
///
/// let seq = audio::sequential![[1, 2, 3, 4]; 1];
/// let seq_channel = seq.get_channel(0).unwrap();
/// let seq_unbounded = seq_channel.range(..);
/// assert_eq!(seq_unbounded.as_ref(), [1, 2, 3, 4]);
/// let seq_limited = seq_unbounded.range(1..3);
/// assert_eq!(seq_limited.as_ref(), [2, 3]);
///
/// let interleaved = audio::interleaved![[1, 2, 3, 4]; 1];
/// let interleaved_channel = interleaved.get_channel(0).unwrap();
/// let interleaved_unbounded = interleaved_channel.range(..);
/// assert_eq!(interleaved_unbounded.get(0).unwrap(), 1);
/// assert_eq!(interleaved_unbounded.get(1).unwrap(), 2);
/// assert_eq!(interleaved_unbounded.get(2).unwrap(), 3);
/// assert_eq!(interleaved_unbounded.get(3).unwrap(), 4);
/// let interleaved_limited = interleaved_unbounded.range(1..3);
/// assert_eq!(interleaved_limited.get(0).unwrap(), 2);
/// assert_eq!(interleaved_limited.get(1).unwrap(), 3);
/// ```
///
/// # Panics
///
/// Panics if the end index is out of bounds.
///
/// ```should_panic
/// use audio::Channel;
///
/// let seq = audio::sequential![[1, 2, 3, 4]; 1];
/// let seq_channel = seq.get_channel(0).unwrap();
/// let seq_range = seq_channel.range(..5);
/// ```
///
/// ```should_panic
/// use audio::Channel;
///
/// let interleaved = audio::interleaved![[1, 2, 3, 4]; 1];
/// let interleaved_channel = interleaved.get_channel(0).unwrap();
/// let interleaved_range = interleaved_channel.range(..5);
/// ```
fn range(self, range: impl core::ops::RangeBounds<usize>) -> Self;
}
43 changes: 43 additions & 0 deletions audio/src/channel/interleaved/macros.rs
Original file line number Diff line number Diff line change
Expand Up @@ -367,6 +367,49 @@ macro_rules! interleaved_channel {
self
}

fn range(self, range: impl core::ops::RangeBounds<usize>) -> Self {
// Hack around trait method taking `self` rather than `mut self`.
// This method returns a Self by value, so it is okay to move self
// into a mut variable.
let mut new = self;
Comment on lines +370 to +374
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Alternatively the trait method could take mut self, but I wanted to keep the signature consistent with the skip/tail/limit methods.


let start_index = match range.start_bound() {
core::ops::Bound::Unbounded => 0,
core::ops::Bound::Included(&i) => i,
core::ops::Bound::Excluded(&i) => i + 1,
};
let original_length = len!(new);
let end_index = match range.end_bound() {
core::ops::Bound::Unbounded => original_length,
core::ops::Bound::Included(&i) => {
let end_index = i + 1;
assert!(end_index <= original_length);
end_index
},
core::ops::Bound::Excluded(&i) => {
assert!(i <= original_length);
i
},
};

if mem::size_of::<T>() == 0 {
let len = usize::min(original_length, end_index - start_index);
zst_set_len!(new, len);
} else {
let start_ptr_offset = usize::min(original_length, start_index).saturating_mul(new.step);
// Safety: internal invariants in this structure ensures it
// doesn't go out of bounds.
new.ptr = unsafe { ptr::NonNull::new_unchecked(new.ptr.as_ptr().wrapping_add(start_ptr_offset)) };

let end_ptr_offset = original_length.saturating_sub(end_index).saturating_mul(new.step);
// Safety: internal invariants in this structure ensures it
// doesn't go out of bounds.
new.end = new.end.wrapping_sub(end_ptr_offset);
}

new
}

fn try_as_linear(&self) -> Option<&[T]> {
None
}
Expand Down
Loading