Skip to content

Conversation

@roderickvd
Copy link
Member

This started as a code quality journey when I noticed that in some places we still had Item = f32 hardcoded where we should have been using the Sample type alias. While fixing those inconsistencies, I realized we could make sample precision configurable at compile time.

I know we've discussed that 32-bit floating point should be sufficient for all audio applications, and I agree that's true for the vast majority of use cases. However, similar to how CamillaDSP does it, rodio can now be built with either f32 (default) or f64 samples using the 64bit feature flag. The 64-bit mode helps when precision drift accumulates, like when chaining many audio operations together.

Replace hardcoded f32 types with Sample/Float type aliases throughout
the codebase for consistency. Add compile-time configurability for
sample precision via a 64bit feature flag.

Tise 64bit mode addresses precision drift in long-running signal
generators and accumulated operations, following an approach similar
to CamillaDSP's compile-time precision selection.

This comment was marked as resolved.

@roderickvd roderickvd force-pushed the feat/sample-precision branch from 9ef64b1 to 1089228 Compare December 28, 2025 20:00
@roderickvd roderickvd force-pushed the feat/sample-precision branch from d9cd210 to 282da56 Compare December 28, 2025 20:34
@yara-blue
Copy link
Member

Nothing against spreading the use of the Sample alias further, however changing so many public APIs from f32 to Float makes me hesitate.

I know we've discussed that 32-bit floating point should be sufficient for all audio applications, and I agree that's true for the vast majority of use cases.
...
The 64-bit mode helps when precision drift accumulates, like when chaining many audio operations together.

I'm having trouble imagining how to lose 8 bits of precision (24bit mantissa need 16bit for transparent audio). Could you give some examples (we can also reuse those in the docs).

I could see how naively mixing 512 songs each at higher then 50% gain could overflow but is that now a fault in the mixer algorithm then?

@roderickvd
Copy link
Member Author

Nothing against spreading the use of the Sample alias further, however changing so many public APIs from f32 to Float makes me hesitate.

I can kind of see how in public documentation it could throw users off for a second, only to see that it's just a type alias to f32 or f64 in the end. Stuff like source.reverb(Duration::from_millis(40), 0.7) or source.distortion(4.0, 0.3) continues to compile with no change. So will source.distortion(foo, bar) with foo and bar in f32 and Rodio default features.

Advanced users can type their foo and bar as rodio::Float or do source.distortion(foo as _, bar as _) if they want to be compatible with both 32 and 64-bit Rodio. But they don't need to.

I'm having trouble imagining how to lose 8 bits of precision (24bit mantissa need 16bit for transparent audio). Could you give some examples (we can also reuse those in the docs).

The anchor shouldn't be "is 16-bit transparent?" (it is), but rather "does our processing preserve the input fidelity?" If someone feeds in 24-bit audio, they shouldn't get lower quality back out, particularly when recording, editing, or mixing.

Each floating-point operation rounds to f32's 24-bit precision, accumulating error as sqrt(N) x epsilon where N is the number of operations, with a number of bits lost equal to log2(N) / 2. So it shows that fidelity is reduced below 24-bit even after a single f32 operation, and reduced with a full bit with four operations. That's after one source.low_pass(x) for example, which does 5 multiply-adds per sample.

Another case is long-running operations that accumulate error over time. For example, SignalGenerator phase drifts 180 degrees within one hour in f32. That doesn't matter for a single tone, but when mixing multiple generators or doing modulation, they drift out of sync.

Finally, by analogy, CamillaDSP (a reference Rust DSP library) actually defaults to f64 and professional DAWs use 64-bit engines. This is why I propose to default Rodio to f32 with zero impact on typical use cases, and advanced users can flip on --features 64bit to preserve full fidelity.

@yara-blue
Copy link
Member

Each floating-point operation rounds to f32's 24-bit precision, accumulating error as sqrt(N) x epsilon where N is the number of operations, with a number of bits lost equal to log2(N) / 2. So it shows that fidelity is reduced below 24-bit even after a single f32 operation, and reduced with a full bit with four operations. That's after one source.low_pass(x) for example, which does 5 multiply-adds per sample.

Thank you for that proof, its better then anything I could find with a quick google last night. The audio world is so full of bigger number thus better that it can be hard to decern whats makes sense.

This is clearly a change worth having. I also would not mind having this example in the code. Both so any future maintainer does not rip it out and such that users can understand when its needed. If you agree could you please add that? I'm also good with merging as is though!

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants