HEnquist / rubato

An asyncronous resampling library written in Rust
MIT License
189 stars 21 forks source link

Iterator-based version of process_into_buffer() #51

Open ilmai opened 2 years ago

ilmai commented 2 years ago

49 proposes making process_into_buffer() more generic by accepting a slice of mutable references instead of a slice of vectors. However, even this doesn't cater to all use cases. In my codebase, I have a multi-channel signal type which can have an arbitrary number of channels whose number isn't known at compile time. I can't make its interface return &mut [&mut [f32]] because that would be unsound: the caller could assign an arbitrary reference to the inner slice instead of using it in the intended way of simply mutating the inner slices. I'm solving the issue by having an interface that returns iterators of slices, so you can still do stuff like for channel in signal.channels_mut() { for sample in channel { *sample *= 0.1 } }.

At the moment, interfacing my signal type with Rubato requires me to have a temporary buffer which I use to copy to/from my signal type and that's then processed by Rubato. If Rubato had a version of process_into_buffer() that accepted iterators of slices, I could get rid of the extra copies and temporary buffers.

I would be happy to submit a pull request for this if this sounds like something that you would consider accepting.

HEnquist commented 2 years ago

I have been considering something like this and I think it's a good idea. I just didn't find the time to do anything yet. It would be really great if it could be done completely without the slices, like an iterator for the channels, that in turn provides an iterators for the samples of each channel. I'm not sure if that's practical though, haven't really thought about it. Perhaps it would be a good start to make a mockup of the new api, something like I made here: https://github.com/HEnquist/rubato/pull/50#discussion_r980385659

Be-ing commented 2 years ago

It would be really great if it could be done completely without the slices, like an iterator for the channels, that in turn provides an iterators for the samples of each channel.

Instead of reinventing the wheel, how about using the audio crate? @udoprog is planning on releasing a major rewrite soon with the immanent stabilization of generic associated types.

udoprog commented 2 years ago

I'd be happy to join efforts if you're up for it. I've got a rubato branch where I'm vetting the audio crate prior to its initial release. It's currently 90 commits behind. But should give an idea of how things work: https://github.com/udoprog/rubato/tree/next.

The idea for me is to release the 0.2 versions of all the audio crate abstractions on November 6 (Rust 1.65!) when GATs are available. This version can then be used to vet the implementation against the ecosystem and garner feedback.

HEnquist commented 2 years ago

audio is definitely interesting. That branch also has some very useful refactoring to reduce the duplication in the asynchronous resamplers. The new polynomial types would benefit from the same treatment. Maybe the sinc and polynomial types can even be merged, they are quite similar. One question is if there are use cases where using the audio buffers for input and output won't work. @ilmai would it work for you?

ilmai commented 2 years ago

Oh yeah that would work! I have my own set of audio signal traits and types in my codebase, but the Buf trait looks simple enough to implement for my types so it would definitely work.

Be-ing commented 1 year ago

@udoprog Are you planning to rebase your rubato branch and open a PR?

Be-ing commented 1 year ago

I've got a rubato branch where I'm vetting the audio crate prior to its initial release. It's currently 90 commits behind. But should give an idea of how things work: https://github.com/udoprog/rubato/tree/next.

That branch started from Rubato in August 2021. Instead of trying to bring it up to date, I've started a new branch in #52 starting from #45. It's not done yet, but I opened the PR early to get preliminary feedback on the API. So far the refactoring has been straightforward albeit tedious. I changed the signature of process_into_buffer to:

fn process_into_buffer<In, Out>(
    &mut self,
    wave_in: &In,
    wave_out: &mut Out,
    active_channels_mask: Option<&[bool]>,
) -> ResampleResult<(usize, usize)>
where
    In: ExactSizeBuf<Sample = T>,
    Out: ExactSizeBuf<Sample = T> + BufMut<Sample = T>;

The ExactSizeBuf bound is required rather than the base Buf trait for the validate_buffers function to check that the buffers' numbers of frames are sufficient.

The audio crate has a nice wrap module which provides wrappers for standard library types (Vecs and slices) that implement its traits without requiring copying. By re-exporting that module, Rubato can accept buffers in a variety of forms (including interleaved buffers) without requiring that Rubato users have to use the audio crate's buffer structs (though I recommend using them; their APIs are quite nice).