-
Notifications
You must be signed in to change notification settings - Fork 329
Phase1a of audio engine rewrite #903
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
base: master
Are you sure you want to change the base?
Changes from all commits
a365e10
3f086ff
8ed9f6e
f4e9464
a5e02a9
49a3fa0
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,20 @@ | ||
| //! Code shared between source types. | ||
| //! | ||
| //! Since we have three source types there is a lot of code duplication. We | ||
| //! combat that by placing whatever can be shared here. | ||
| //! | ||
| //! This can be: | ||
| //! - shared types like error enums | ||
| //! - shared free functions | ||
| //! - shared members / impl blocks through macro_rules | ||
| //! | ||
| //! # Note | ||
| //! Effects are defined through a macro and do not need this kind of | ||
| //! deduplication | ||
| //! | ||
| //! This modules structure mirrors that of what it deduplicates. For example | ||
| //! the code shared between [fixed_source::chain] and [const_source::chain] is in | ||
| //! common/source/chain.rs | ||
|
|
||
| pub(crate) mod buffer; | ||
| pub(crate) mod chain; | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,66 @@ | ||
| macro_rules! source_impl { | ||
| () => { | ||
| /// # Panics | ||
| /// If the length of the buffer is larger than approximately 16 billion elements. | ||
| /// This is because the calculation of the duration would overflow. | ||
| #[inline] | ||
| fn total_duration(&self) -> Option<Duration> { | ||
| use crate::math::NANOS_PER_SEC; | ||
|
|
||
| let duration_ns = NANOS_PER_SEC | ||
| .checked_mul(self.data.len() as u64) | ||
| .expect("slices longer then 16 billion elements are not supported") | ||
| / self.sample_rate().get() as u64 | ||
| / self.channels().get() as u64; | ||
| let duration = Duration::new( | ||
| duration_ns / NANOS_PER_SEC, | ||
| (duration_ns % NANOS_PER_SEC) as u32, | ||
| ); | ||
|
|
||
| Some(duration) | ||
| } | ||
|
|
||
| /// This jumps in memory to the sample corresponding to `pos`. | ||
| #[inline] | ||
| fn try_seek(&mut self, pos: Duration) -> Result<(), SeekError> { | ||
| // This is fast because all the samples are in memory already | ||
| // and due to the constant sample_rate we can jump to the right | ||
| // sample directly. | ||
|
|
||
| let curr_channel = self.pos % self.channels().get() as usize; | ||
| let new_pos = crate::math::duration_to_float(pos) | ||
| * self.sample_rate().get() as crate::Float | ||
| * self.channels().get() as crate::Float; | ||
| // saturate pos at the end of the source | ||
| let new_pos = new_pos as usize; | ||
| let new_pos = new_pos.min(self.data.len()); | ||
|
|
||
| // make sure the next sample is for the right channel | ||
| let new_pos = new_pos.next_multiple_of(self.channels().get() as usize); | ||
| let new_pos = new_pos + curr_channel; | ||
|
|
||
| self.pos = new_pos; | ||
| Ok(()) | ||
| } | ||
| }; | ||
| } | ||
|
|
||
| macro_rules! iter_impl { | ||
| () => { | ||
| type Item = Sample; | ||
| #[inline] | ||
| fn next(&mut self) -> Option<Self::Item> { | ||
| let sample = self.data.get(self.pos)?; | ||
| self.pos += 1; | ||
| Some(*sample) | ||
| } | ||
| #[inline] | ||
| fn size_hint(&self) -> (usize, Option<usize>) { | ||
| let remaining = self.data.len().saturating_sub(self.pos); | ||
| (remaining, Some(remaining)) | ||
| } | ||
| }; | ||
| } | ||
|
|
||
| pub(crate) use iter_impl; | ||
| pub(crate) use source_impl; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,131 @@ | ||
| use crate::source::SeekError; | ||
|
|
||
| #[derive(Debug, thiserror::Error)] | ||
| pub enum ChainSeekError { | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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: |
||
| #[error("Could not get duration of first source ({ty})")] | ||
| NoTotalDurationForFirst { ty: &'static str }, | ||
| #[error("Could not seek in first source ({ty})")] | ||
| FailedToSeekInFirst { | ||
| ty: &'static str, | ||
| #[source] | ||
| error: SeekError, | ||
| }, | ||
| #[error("Could not reset first source ({ty}) to start")] | ||
| FailedToResetFirst { | ||
| ty: &'static str, | ||
| #[source] | ||
| error: SeekError, | ||
| }, | ||
| #[error("Could not seek in second source ({ty})")] | ||
| FailedToSeekInSecond { | ||
| ty: &'static str, | ||
| #[source] | ||
| error: SeekError, | ||
| }, | ||
| } | ||
|
|
||
| macro_rules! source_impl { | ||
| () => { | ||
| fn channels(&self) -> crate::ChannelCount { | ||
| self.first.channels() | ||
| } | ||
|
|
||
| fn sample_rate(&self) -> crate::SampleRate { | ||
| self.first.sample_rate() | ||
| } | ||
|
|
||
| fn total_duration(&self) -> Option<std::time::Duration> { | ||
| self.first | ||
| .total_duration() | ||
| .and_then(|d| self.second.total_duration().map(|d2| d2 + d)) | ||
| } | ||
|
|
||
| fn try_seek(&mut self, pos: std::time::Duration) -> Result<(), crate::source::SeekError> { | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
| use crate::source::SeekError; | ||
| use std::any::type_name_of_val; | ||
| use std::sync::Arc; | ||
|
|
||
| let Some(first) = self.first.total_duration() else { | ||
| return Err(ChainSeekError::NoTotalDurationForFirst { | ||
| ty: type_name_of_val(&self.first), | ||
| }) | ||
| .map_err(Arc::new) | ||
| .map_err(|e| SeekError::Other(e)); | ||
| }; | ||
|
|
||
| if pos < first { | ||
| // Reset first source to prevent a jump to the current position | ||
| // after the first source completes again. | ||
| if !self.playing_first { | ||
| // FIXME(yara): implement Seekable trait for all sources and extract | ||
| // this to a function. (all sources are required to impl Seekable). | ||
| // Might wanna do a similar thing for other shared functionality | ||
| // like total duration | ||
| self.second | ||
| .try_seek(std::time::Duration::ZERO) | ||
| .map_err(|error| ChainSeekError::FailedToResetFirst { | ||
| ty: type_name_of_val(&self.first), | ||
| error, | ||
| }) | ||
| .map_err(Arc::new) | ||
| .map_err(|e| SeekError::Other(e))?; | ||
| } | ||
|
|
||
| self.first | ||
| .try_seek(pos) | ||
| .map_err(|error| ChainSeekError::FailedToSeekInFirst { | ||
| ty: type_name_of_val(&self.first), | ||
| error, | ||
| }) | ||
| .map_err(Arc::new) | ||
| .map_err(|e| SeekError::Other(e))?; | ||
| self.playing_first = true; | ||
| Ok(()) | ||
| } else { | ||
| self.second | ||
| .try_seek(pos - first) | ||
| .map_err(|error| ChainSeekError::FailedToSeekInSecond { | ||
| ty: type_name_of_val(&self.second), | ||
| error, | ||
| }) | ||
| .map_err(Arc::new) | ||
| .map_err(|e| SeekError::Other(e))?; | ||
| self.playing_first = false; | ||
| Ok(()) | ||
| } | ||
| } | ||
| }; | ||
| } | ||
|
|
||
| macro_rules! iter_impl { | ||
| () => { | ||
| type Item = Sample; | ||
|
|
||
| fn next(&mut self) -> Option<Self::Item> { | ||
| if self.playing_first { | ||
| match self.first.next() { | ||
| Some(sample) => Some(sample), | ||
| None => { | ||
| self.playing_first = false; | ||
| self.second.next() | ||
| } | ||
| } | ||
| } else { | ||
| self.second.next() | ||
| } | ||
| } | ||
|
|
||
| #[inline] | ||
| fn size_hint(&self) -> (usize, Option<usize>) { | ||
| let (lower_bound_a, upper_bound_a) = self.first.size_hint(); | ||
| let (lower_bound_b, upper_bound_b) = self.second.size_hint(); | ||
|
|
||
| let lower_bound = lower_bound_a + lower_bound_b; | ||
| let upper_bound = upper_bound_a.zip(upper_bound_b).map(|(a, b)| a + b); | ||
| (lower_bound, upper_bound) | ||
| } | ||
| }; | ||
| } | ||
|
|
||
| pub(crate) use iter_impl; | ||
| pub(crate) use source_impl; | ||
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.