Repository navigation
Description
This tracks landing the changes from https://github.com/RustAudio/rodio-experiments into Rodio. This involves integrating the new rubato resampler and specializing it where possible.
Background:
For a long time we've had mysterious bugs round spans. Spans allow a source to change parameters (sample-rate and channel count) during playback. We've concluded that spans where a mistake as correctly handling audio that at any point is:
- really hard to do correctly.
- impossible to optimize well leading to bad performance.
Most audio should be converted to the playback parameters at the very start of the audio pipeline. To make this easier we are adding two new source types. Both do not allow parameters to change, one fixes this at runtime the other needs the parameters known at compile time.
Approach
This is a large overhaul at around 7700 lines of code (ignoring docs & comments). To keep things reviewable we'll do this in reasonably sized PRs of around 500 lines. Help is greatly appreciated!
Phase one
Implementation in: #903 and #904
- land const source
- remove experimental from fixed source
- land try-into adaptors/convertors between sources (others in phase two)
- note overhaul in Readme
- land most basic generators
Phase two
Implementation in: #905 and #910
- channel conversions
- resampler and integrate with rubato
Phase three:
Implementation in: #911
- land one effect and pure_effect! macro
- land all other effect
- deprecation warning on effects in DynamicSource that are not sound
Phase four
- add new combinators [a, b, c].mixed() etc.
- add all generators (noise etc, function gen. etc)
Phase five
- rename current source to dynamic source
- add Source = DynamicSource type alias with deprecation warning
- Remove placeholders
Phase six
- port all examples
- re-add controls to automatic gain control
- rewrite Rodio main docs
- update porting guide (needs to be really nice this time :3 )
- prepare and make announcements
Small chores
- check the new queues for: fix: don't re-invoke exhausted source during keep-alive silence #913
Open Questions
- Do we deprecate Source in favor of DynamicSource?
- Do cons sources and the ConstSource trait get default generics?
Activity
[-]Tracking issue for audio pipeline upgrades[/-][+]Tracking issue for audio engine rewrite[/+]yara-blue commentedon Jul 22, 2026
One point for discussion:
Should the ConstSource have a default generic? as in should it be:
Such that it can be used as:
Or
If we do we should do the same for the generators (for now only silence but we will land others soon see #901)
For now I've gone with no default generics. While removing them would be a breaking change adding is not*, so we can safely postpone the decision.
*: this gets complicated but let's just say it is not breaking...
yara-blue commentedon Jul 29, 2026
Since this is a rather big effort I'd love to spread out the review load a little. @PetrGlad do you have time by change to review some of this? Each PR is around 500 lines when compared to the previous (so 1a compared to 1b etc). I'd also love your take on this @iluvcapra.
The larger effort is to add two new source traits that both forbid sample rate and channel count from changing. This enables a bunch of optimizations (or rather saves us from doing a bunch of checks) and makes Rodio simpler and more correct. In time we will be moving over functionality from the current Source trait to these two new traits: (FixedSource and ConstSource).
PetrGlad commentedon Aug 22, 2026
I've not been following the development for a while, frankly do not have much energy for everything :). I do not really mind larger PRs as long as they single-purposed. The change would be big, so anyway everything has to be checked anyway. I will look at those this weekend.
PetrGlad commentedon Aug 24, 2026
Since this changes the API anyway. Was it considered to use frame count (multiply of number of channels in a source) as seek unit? Using sample count is a constant source of errors (mistakes can unintentionally swap channels).
The buffers do not necessarily should be structured as list of frames, they can stay as plain sequences of interleaved samples, but the APIs would always assume the minimal seek unit is a frame (set of samples at certain moment in time).
Or was this idea abandoned for some reason?
roderickvd commentedon Aug 24, 2026
Hey @PetrGlad, glad to see you back in action and helping review!
I think working in frames would be great too. I remember @yara-blue and I kicked it around but couldn't make it work. Can't find it on the repo anymore, maybe it was in Discord? Anyway I vaguely remember it was an issue with the const generics or something?
We've got this in the ecosystem: https://docs.rs/dasp_frame/latest/dasp_frame/
PetrGlad commentedon Sep 7, 2026
I had to fix my sleep again :)
I have a comment regarding those
constsources. I would expect them to be interchangeable with sources whose parameter are set at run-tine (during initialization). Is this going to be supported?Span-less API is a long lasting discussion, and I am wondering if supporting
constcan be left to a later extension/improvement. One can for example to have a couple implementation to demo that those versions are now possible, but start with completing sources whose parameters are initialized at run-time.PetrGlad commentedon Sep 7, 2026
Regarding using frames as a seek unit, I see that the new API uses Duration, in that case it is less relevant. Maybe Duration is sufficient for most cases, but seeks would not be "sample perfect".
A bit less convenient but simpler API would be to have a common Duration->frame-number convertor method/function and allow picking frame number directly. Alternatively frame-seeking could be a trait that is implemented only whenever it makes sense.
I would not necessarily suggest the change, just to make sure that the choice is conscious.
yara-blue commentedon Sep 7, 2026
Well I thought that was impossible, turns out I was wrong. We might still want three distinct implementations for some sources (one const, one fixed and one dynamic), simple because
constmight allow more optimizations. That will have to wait tillconst_trait_methodsget stabilized. But that is fine, we can fine tune things later.See the playground below on how we can do this:
https://play.rust-lang.org/?version=stable&mode=debug&edition=2024&gist=08efa451291a2f6171ce9e65a4754a47
I'm strongly considering taking the above and reworking everything into that. Will take a bit though it's a lot of work. @PetrGlad what do you think of it? Also @roderickvd If you got the time could you give that playground a quick look I'd love you're take. Both of you feel free to say if it needs some documentation to make clear what is going on. I considered adding some but I did not want to make it even longer.
We could, it might still be worth to bring them along now so we are sure the current module structure and APIs make sense when offering users a switch.
yara-blue commentedon Sep 13, 2026
@roderickvd and @PetrGlad if you have half an hour this weekend please give the above a look. I'm planning to start implementation somewhere next week.
PetrGlad commentedon Sep 13, 2026
Just to keep a reference https://rust-lang.github.io/rfcs/0911-const-fn.html
PetrGlad commentedon Sep 13, 2026
It looks like ConstSource would be a marker trait? It seems that even const trait functions, had they been supported would not help since we would want both const and non const sources.
Since DynamicSource would normally be used in only special cases (when the actual source genuinely have spans), and should normally be fed through resampler, I'd prefer not to require mentions of spans unless it is actually necessary.
Maybe
My doubt here is that presence of
current_span_lenchanges meaning of other getters: one may accidentally require FixedSource and expect parameters to not change, but actual implementation may also be a DynamicSource so returned values could potentially change. If everything is derived from dynamic source then users accepting sources would have to either assert (span_len==none) or handle spans anyway.Maybe DynamicSource should be a distinct trait in this case:
Here FixedSource & friends would genuinely not need to care about spans anymore.DynamicSource adapters would know that they should handle spans at compilation time.
Or, maybe, something like
or the variation on this idea:
The last 2 options are probably my favorites. Maybe also duration getter could also be part of
StreamShape. In that casecurrent_span_len()would actually becurrent_shape().duration()(could be stubbed with a default implementation for compatibility):We're not 1.0 yet so we can change the API, but since many projects already use rodio we can have a dedicated branch for breaking changes to polish API before switching.
PetrGlad commentedon Sep 13, 2026
Or, while I am at it...
In short I'd keep DynamicSource and Fixed/Const sources separate. Only case where they can potentially be interchangeable is only while first span lasts. Accepting this is not worth all the risks that we wanted to avoid with this change. I Imagine we should have explicit adapters that accept DynamicSource as input and provide span-less output. We can have a default one with default resampler, and set of some usual channel count convertor presets, users may provide their own if they are unhappy with the defautls. I'd like
Iterator<impl FixedSource>idea in case we want to support this case even after the rewrite. I think some codecs still actually need spans anyway?roderickvd commentedon Sep 20, 2026
Looks nice. The chain gives us const, fixed and dynamic upcasting for free.
Amplifyonly takes const sources, becauseSRandCHsit on the struct. A fixed source with runtime parameters (like a decoder) can't go into it - @PetrGlad is that what you were asking about? How about movingSRandCHfrom the struct to the impl:Two small things:
total_duration()is missing&self(and returnsDurationwhereSourcereturnsOption<Duration>).ConstSourcefor severalSR/CHpairs, so callers would need a turbofish which isn't so ergonomic. Would using default generics make sense?Regarding
current_span_len: a dynamic source can't be passed as aFixedSource, so shouldn't that be OK? We could continue have fixed sources returnNone.PetrGlad commentedon Sep 27, 2026
What I would like do separate is Dynamic versus Fixed/Const sources. I think they should not be interchangeable without explicit conversion. And thus it is perfectly OK, even desirable, for their APIs to be separate/incompatible. In the API sketches above I tried to maintain that. Anyone wanting to connect over this boundary this should do this conversion explicitly - we should provide default conversion connectors both ways for backwards compatibility and for cases Dynamic source is genuinely required. The default in the examples should be span-less API.
Those 2 APIs can share some definitions. For example, description of a span's stream is identical to description of Fixed/Const source's stream, both in shape and semantics. This is what some versions of my API sketches above make explicit. Although it makes new DymanicSource API incompatible too, could still be a good choice for 1.0.
Here is the version that I would prefer spanless-api-prototype-petr/src/source_new.rs:
PetrGlad commentedon Sep 27, 2026
I would like to see actual use cases for ConstSource. I could imagine that number of channels is known at compile time. But sample rate would depend on system's defaults, and duration depends on the audio fragment. These 3 parameters define a stream
{sample rate, channel count, number of frames}which ones should be const? Are there cases where const sources can be helpful in practice? I understand that const versions are introduced to facilitate optimizations, but it looks to me that trying to make those convenient no matter what we complicating code back again. The whole point of span-less API was to simplify and streamline code... I'd prefer to keep const implementations option available for cases where it helps but only make that option convenient to the extent it does not complicate the rest of the code much...@roderickvd What are default generics?
yara-blue commentedon Sep 27, 2026
Thanks for that prototype and detailed response! I've been ill for a few weeks so I'm a little behind. I hope to give you all a more in depth reply later, but there is a bunch of stuff I need to catch up with first!
Some use cases of the top of my head:
I'd say the first is the strongest motivator.