Conversation
|
|
||
| /// A buffer of samples treated as a source. | ||
| #[derive(Debug, Clone)] | ||
| pub struct SamplesBuffer<const SR: u32, const CH: u16> { |
There was a problem hiding this comment.
Can we use the SampleRate and ChannelCount type aliases?
There was a problem hiding this comment.
Sadly not, const params are limited to primitive types atm :(
| @@ -0,0 +1,20 @@ | |||
| //! Code shared between source types. | |||
| //! | |||
| //! Since we have three source types there is a lot of code duplication. We | |||
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
| 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 |
There was a problem hiding this comment.
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) }; |
There was a problem hiding this comment.
Not too familiar with const generics; I guess we can't use NonZero as we wanted?
There was a problem hiding this comment.
yes, pretty sad. One day we'll get rustc there. But thanks to the const block that assert will fire compile time.
| .map_err(|e| SeekError::Other(e)) | ||
| } else { | ||
| self.second | ||
| .try_seek(pos) |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
This still needs try_seek, size_hint and ExactSizeIterator implemented.
|
|
||
| #[derive(Debug, thiserror::Error)] | ||
| pub enum ChainSeekError { | ||
| #[error("Could not get duration of first source ({ty}")] |
| use crate::source::SeekError; | ||
|
|
||
| #[derive(Debug, thiserror::Error)] | ||
| pub enum ChainSeekError { |
There was a problem hiding this comment.
Should this be exported publicly so users can downcast to them from a SeekError::Other(Arc<dyn Error>)?
There was a problem hiding this comment.
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
| pub enum ChainSeekError { | ||
| #[error("Could not get duration of first source ({ty}")] | ||
| NoTotalDurationForFirst { ty: &'static str }, | ||
| #[error("Could not seek in first source")] |
There was a problem hiding this comment.
Add ({ty}) here too? And below.
| type Item = crate::Sample; | ||
|
|
||
| fn next(&mut self) -> Option<Self::Item> { | ||
| Some(0.0) |
|
Thanks for the review! Should have addressed all of the feedback. I'll see if I can fix the Phase1b tonight. |
|
OK, I see channel and seeking precision bugs I saw yesterday, are already fixed :) |
|
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.
There are going to be even more macro calls when we get to implementing all the effects... Lets see what we can do: TraitsI'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 Marker traitWhy is 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 Wrappingstruct 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 // 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? |
|
Yes, this is the explanation I was looking for. Of the versions you mentioned this one I had in mind, actually: 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>;
}Regarding trait ConstSource<const SR: u32, const CH: u16>: TotalDuration + TrySeek {
fn sample_rate(&self) -> SampleRate;
fn channel_count(&self) -> ChannelCount;
} |
|
// I understand that |
|
Regarding simultaneous implementation of |
Not really, for example the implementation of queue gets far simpler (and more performant) when it no longer needs to watch for parameter changes. That was the primary motivation for this rework.
I would not mind eventually removing
We could, but it would not really help us. You'd still need to implement four methods except now they are divided by between three traits. It would help us if we could simultaneously implement the source trait for one object (see next answer for why that is impossible).
do you mean a struct implementing both traits? While you can do that it becomes really annoying to then use the struct. You'd have to disambiguate (specify which trait you want a method from) every time you call |
supersedes #902
Tracking issue: #901
Very minimal still, to get an idea of what this will all look like see: rodio-experiments.
Implementation notes:
include_str!those to prevent duplication. We'll be using that even more for the effects.