Skip to content

Phase1a of audio engine rewrite - #903

Open
yara-blue wants to merge 6 commits into
masterfrom
phase1a
Open

Phase1a of audio engine rewrite#903
yara-blue wants to merge 6 commits into
masterfrom
phase1a

Conversation

@yara-blue

Copy link
Copy Markdown
Member

supersedes #902

Tracking issue: #901

Very minimal still, to get an idea of what this will all look like see: rodio-experiments.

Implementation notes:

  • I've moved some documentation into md files and include_str! those to prevent duplication. We'll be using that even more for the effects.
  • For now does not rename Source but do make it available under it's new name via a pub re-export in lib.rs
  • There is a placeholder so the example in the docs works

@yara-blue
yara-blue requested a review from roderickvd July 23, 2026 21:28

/// A buffer of samples treated as a source.
#[derive(Debug, Clone)]
pub struct SamplesBuffer<const SR: u32, const CH: u16> {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Can we use the SampleRate and ChannelCount type aliases?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Sadly not, const params are limited to primitive types atm :(

Comment thread src/common/source.rs
@@ -0,0 +1,20 @@
//! Code shared between source types.
//!
//! Since we have three source types there is a lot of code duplication. We

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I thought this shouldn't be user-facing Rustdoc when I saw it probably isn't, because the module isn't imported as public. Still, may want to consider the Rustdoc level.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

It's still useful to use doc comments instead of simple comments as rust-analyzer makes hover doc available for this when working on Rodio.

Comment thread src/const_source/buffer.rs Outdated
impl<const SR: u32, const CH: u16> SamplesBuffer<SR, CH> {
/// Builds a new `SamplesBuffer`.
///
/// Note any call to total_duration will panic if the buffer is larger then

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I see you moved that code from new to total_duration which probably makes sense. But I'd say it doesn't need docs here then.

where
D: Into<Vec<Sample>>,
{
const { assert!(SR > 0) };

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Not too familiar with const generics; I guess we can't use NonZero as we wanted?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

yes, pretty sad. One day we'll get rustc there. But thanks to the const block that assert will fire compile time.

Comment thread src/common/source/chain.rs Outdated
.map_err(|e| SeekError::Other(e))
} else {
self.second
.try_seek(pos)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I think this needs to be pos - first. As-is, chaining two 1-second sources and seeking to 1.5s puts the second source at 1.5s, which is past its own end.

Comment thread src/const_source/chain.rs

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This still needs try_seek, size_hint and ExactSizeIterator implemented.

Comment thread src/common/source/chain.rs Outdated

#[derive(Debug, thiserror::Error)]
pub enum ChainSeekError {
#[error("Could not get duration of first source ({ty}")]

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Add closing ).

use crate::source::SeekError;

#[derive(Debug, thiserror::Error)]
pub enum ChainSeekError {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Should this be exported publicly so users can downcast to them from a SeekError::Other(Arc<dyn Error>)?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

It should be re-exported in the source implementation as wel. See the: pub use crate::common::source::chain::ChainSeekError; in const_source/chain.rs

Comment thread src/common/source/chain.rs Outdated
pub enum ChainSeekError {
#[error("Could not get duration of first source ({ty}")]
NoTotalDurationForFirst { ty: &'static str },
#[error("Could not seek in first source")]

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Add ({ty}) here too? And below.

Comment thread src/generators/silence.rs Outdated
type Item = crate::Sample;

fn next(&mut self) -> Option<Self::Item> {
Some(0.0)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Sample::EQUILIBRIUM

@yara-blue

Copy link
Copy Markdown
Member Author

Thanks for the review! Should have addressed all of the feedback. I'll see if I can fix the Phase1b tonight.

@PetrGlad

PetrGlad commented Aug 24, 2026

Copy link
Copy Markdown
Collaborator

OK, I see channel and seeking precision bugs I saw yesterday, are already fixed :)
I do not really like use of macroses(like macro_rules! source_impl) since it is harder to see usages, and it postpones some checks to final compilation. Was there a reason to use macroses instead of traits, or is it just to save typing? This implementation would do, bit I'd considered something more explicit.

@yara-blue

Copy link
Copy Markdown
Member Author

First of all thanks for finding the time for the review! Secondly, I did not have the time to write a short answer so you get a very long one, apologies.

Was there a reason to use macroses instead of traits, or is it just to save typing? This implementation would do, bit I'd considered something more explicit.

There are going to be even more macro calls when we get to implementing all the effects... Lets see what we can do:

Traits

I've tried doing something with traits, separating out the repeated parts:

trait FixedSource {
  fn sample_rate(&self) -> SampleRate;
  fn channel_count(&self) -> ChannelCount;
  fn total_duration(&self) -> Duration;
  fn try_seek(&mut self, pos: Duration) -> Result<(), SeekError>;
}
trait ConstSource {
  fn sample_rate(&self) -> SampleRate;
  fn channel_count(&self) -> ChannelCount;
  fn total_duration(&self) -> Duration;
  fn try_seek(&mut self, pos: Duration) -> Result<(), SeekError>;
}

We would have:

trait FixedSource: TotalDuration + TrySeek {
  fn sample_rate(&self) -> SampleRate;
  fn channel_count(&self) -> ChannelCount;
}
trait ConstSource: TotalDuration + TrySeek {
  fn sample_rate(&self) -> SampleRate;
  fn channel_count(&self) -> ChannelCount;
}
trait TotalDuration {
  fn total_duration(&self) -> Duration;
}
trait TrySeek {
  fn try_seek(&mut self, pos: Duration) -> Result<(), SeekError>;
}

Now we can implement TotalDuration and TrySeek separately. This does not win us anything unfortunately, we still need a separate object for ConstSource since it will need to have const generic args.

Marker trait

Why is ConstSource not just a marker trait for FixedSource?

You'd do:

impl FixedSource for Amplify {
  fn sample_rate() -> SampleRate {
     ... 
  }
  ...
}

impl<SR, CH> ConstSource for Amplify<SR, CH>;

Well the reason for that is we do not want any type to implement both FixedSource and ConstSource. They mean different things so they must be separate types. Still we seem to be onto something here. Lets turn it around and wrap instead of marking:

Wrapping

struct Amplify(fixed_source::Amplify);

impl<SR, CH> ConstSource<SR, CH> for Amplify {
   fn total_duration(&self) -> Duration {
     self.0.total_duration()
   }
   ...
}

That seems nice, no more code duplication and no more macro's. But then we realize that S will probably wrap some existing source (like chain does or a future amplify (in phase 3a). But fixed_source::S can only be generic over a FixedSource and S here is a ConstSource so it is generic over one of those.

// Error: argument T to fixed_source::Amplify must implement FixedSource
struct Amplify<T: ConstSource>(fixed_source::Amplify<T>); 

Well for that we have the IntoFixedSource wrapper! So we get:

struct Amplify<T>(fixed_source::Amplify<IntoFixedSource<T>);

impl<SR, CH> ConstSource<SR, CH> for Amplify {
   fn total_duration(&self) -> Duration {
     self.0.total_duration()
   }
   ...
}

I don't like that this leads to a lot of IntoFixedSource's in there but those should compile out... I have to think about it. What do you all think?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants