Skip to content

Conversation

roderickvd
Copy link
Member

@roderickvd roderickvd commented Sep 12, 2025

Builds on our noise generators to add four dithering algorithms:

  • TPDF (Triangular PDF) - Optimal decorrelation (default)
  • RPDF (Rectangular PDF) - Lower noise floor, worse decorrelation
  • GPDF (Gaussian PDF) - Higher noise floor, analog characteristics
  • HighPass - Blue noise to reduce low-frequency artifacts

Gated by new dither feature flag, that's enabled by default.

API

use rodio::source::{dither, DitherAlgorithm};
use rodio::BitDepth;

let source = SineWave::new(440.0);
let dithered = dither(source, BitDepth::new(16).unwrap(), DitherAlgorithm::TPDF);

Parent branch

This is branched off of #786 which introduces Source::bits_per_sample(), because you'll typically want to dither down to:

let output_bits = config.bits_per_sample();  // in cpal master: https://github.com/RustAudio/cpal/pull/1008
let target_bits = source_bits.map_or(output_bits, |src_bits| src_bits.min(output_bits));

I'll mark this PR for review when that PR is merged.

@roderickvd roderickvd requested review from dvdsk and Copilot September 12, 2025 21:05
@roderickvd roderickvd changed the base branch from feat/comprehensive-seeking-and-bit-depth to master September 12, 2025 21:06
Copy link
Contributor

@Copilot Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull Request Overview

This PR introduces audio dithering support to Rodio with four dithering algorithms, while also implementing significant infrastructure improvements across decoders and sources.

Key Changes:

  • Audio dithering feature: Adds TPDF, RPDF, GPDF, and HighPass dithering algorithms for quantization noise management
  • BitDepth type addition: New type for tracking audio bit depth throughout the audio pipeline
  • Decoder improvements: Enhanced seeking, error handling, and configuration for WAV and Vorbis decoders
  • Source trait expansion: Added bits_per_sample() method and improved seeking error handling

Reviewed Changes

Copilot reviewed 89 out of 91 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
src/source/dither.rs New dithering implementation with four algorithms (TPDF, RPDF, GPDF, HighPass)
src/common.rs Adds BitDepth type and updates ChannelCount/SampleRate handling
src/decoder/wav.rs Major WAV decoder improvements with better seeking and configuration
src/decoder/vorbis.rs Enhanced Vorbis decoder with granule-based seeking and duration scanning
src/source/mod.rs Expanded Source trait with bits_per_sample() method and improved SeekError variants
tests/ Updated test files to use Path-based decoder APIs instead of File objects

Tip: Customize your code reviews with copilot-instructions.md. Create the file or learn how to get started.

Comment on lines 865 to 856
// Try to find a granule from this position (limited packet scan during binary search)
match find_granule_from_position(data, mid, Some(50)) {
Copy link
Preview

Copilot AI Sep 12, 2025

Choose a reason for hiding this comment

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

[nitpick] The magic number 50 for packet limit should be defined as a named constant to improve code readability and maintainability.

Copilot uses AI. Check for mistakes.

};
// len is number of samples, not bytes, so use samples_to_duration
// Note: hound's len() returns total samples across all channels
let samples_per_channel = len / (channels as u64);
Copy link
Preview

Copilot AI Sep 12, 2025

Choose a reason for hiding this comment

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

Division by channels as u64 could cause integer truncation issues. The conversion from u16 to u64 for channels should be explicit and the division should handle potential remainders properly.

Suggested change
let samples_per_channel = len / (channels as u64);
let channels_u64 = channels as u64;
if len % channels_u64 != 0 {
eprintln!(
"Warning: total number of samples ({}) is not evenly divisible by number of channels ({}). Truncating to {} samples per channel.",
len, channels, len / channels_u64
);
}
let samples_per_channel = len / channels_u64;

Copilot uses AI. Check for mistakes.

@dvdsk
Copy link
Member

dvdsk commented Sep 13, 2025

Parent branch

This is branched off of #786 which introduces Source::bits_per_sample(), because you'll typically want to dither down to:

😅 😅 For a minute or two I thought you introduced another ~6k PR.

Btw lets add a new rule/convention to keep PR's under 1500 lines. I'll hold myself to that for rewriting the entire audio pipeline.

roderickvd and others added 14 commits September 17, 2025 21:34
…ion for all decoders

Major decoder enhancements implementing universal try_seek() support, bit depth detection,
and changing decoder selection to prioritize alternative decoders over Symphonia.

Key changes:
- Universal seeking: All decoders implement try_seek() with SeekMode configuration
- Bit depth detection: Added Source::bits_per_sample() for lossless formats
- Decoder precedence: Alternative decoders now tried before Symphonia decoders
- Enhanced DecoderBuilder: Added scan_duration, total_duration, seek_mode settings
- Critical fix: Resolved zero span length issue in Symphonia Ogg Vorbis decoder
- Performance: Optimized Vorbis, FLAC, and WAV decoder implementations

BREAKING CHANGES:
- SeekError::NotSupported renamed to SeekError::SeekingNotSupported
- WavDecoder no longer implements ExactSizeIterator
- Alternative decoders now take precedence when multiple format features enabled
- DecoderBuilder::with_coarse_seek deprecated for with_seek_mode

Closes:
- #190
- #775
Without this, minimp3 would incorrectly accept WAV files as MP3 when
both `minimp3` and Symphonia's `wav` features are enabled.
- Improve clarity and conciseness of doc comments for all decoders
- Remove redundant and overly verbose explanations
- Add missing details to trait and method docs
- Update examples to use consistent file paths and types
- Move Settings struct visibility to crate-internal
- Refactor Path/PathBuf decoder constructors for optimal hinting
- Standardize iterator and trait method documentation across formats
- Remove misleading or outdated implementation notes
- Fix Zero source bits_per_sample to return None
- Update seek test attributes for feature consistency
- Introduce BitDepth as a newtype for non-zero u32
- Update Source trait and all implementations to use Option<BitDepth>
- Update decoders, sources, and tests to use BitDepth instead of u32 for bit depth
- Replace NonZero usage for sample rate and channel count with SampleRate and ChannelCount types where appropriate
- Update documentation and examples to reflect new types

fix: change bits_per_sample to return Option<BitDepth>
@roderickvd roderickvd marked this pull request as ready for review September 17, 2025 21:50
@dvdsk dvdsk assigned dvdsk and unassigned dvdsk Sep 20, 2025
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.

2 participants