Skip to content

Commit 88ad33b

Browse files
committed
refactor: use SampleRate type throughout
1 parent 46ebd9e commit 88ad33b

File tree

4 files changed

+43
-58
lines changed

4 files changed

+43
-58
lines changed

src/decoder/flac.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -277,14 +277,15 @@ where
277277
let reader = FlacReader::new_ext(data, READER_OPTIONS).expect("should still be flac");
278278

279279
let spec = reader.streaminfo();
280-
let sample_rate = spec.sample_rate;
280+
let sample_rate = SampleRate::new(spec.sample_rate)
281+
.expect("flac data should never have a zero sample rate");
281282
let max_block_size = spec.max_block_size as usize * spec.channels as usize;
282283

283284
// `samples` in FLAC means "inter-channel samples" aka frames
284285
// so we do not divide by `self.channels` here.
285286
let total_samples = spec.samples;
286287
let total_duration =
287-
total_samples.map(|s| utils::samples_to_duration(s, sample_rate as u64));
288+
total_samples.map(|s| utils::samples_to_duration(s, sample_rate));
288289

289290
Ok(Self {
290291
reader: Some(reader),
@@ -293,8 +294,7 @@ where
293294
current_block_off: 0,
294295
bits_per_sample: BitDepth::new(spec.bits_per_sample)
295296
.expect("flac should never have zero bits per sample"),
296-
sample_rate: SampleRate::new(sample_rate)
297-
.expect("flac data should never have a zero sample rate"),
297+
sample_rate,
298298
channels: ChannelCount::new(
299299
spec.channels
300300
.try_into()

src/decoder/utils.rs

Lines changed: 20 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,9 @@
3030
#[cfg(any(feature = "claxon", feature = "hound"))]
3131
use std::time::Duration;
3232

33+
#[cfg(any(feature = "claxon", feature = "hound"))]
34+
use crate::SampleRate;
35+
3336
#[cfg(any(
3437
feature = "claxon",
3538
feature = "hound",
@@ -62,33 +65,11 @@ use std::io::{Read, Seek, SeekFrom};
6265
///
6366
/// # Edge Cases
6467
///
65-
/// - **Zero sample rate**: Returns `Duration::ZERO` to prevent division by zero
6668
/// - **Zero samples**: Returns `Duration::ZERO` (mathematically correct)
67-
/// - **Large values**: Handles overflow gracefully within Duration limits
68-
///
69-
/// # Examples
70-
///
71-
/// ```ignore
72-
/// use std::time::Duration;
73-
/// # use rodio::decoder::utils::samples_to_duration;
74-
///
75-
/// // 1 second at 44.1kHz
76-
/// assert_eq!(samples_to_duration(44100, 44100), Duration::from_secs(1));
77-
///
78-
/// // 0.5 seconds at 44.1kHz
79-
/// assert_eq!(samples_to_duration(22050, 44100), Duration::from_millis(500));
80-
/// ```
81-
///
82-
/// # Performance
83-
///
84-
/// This function is optimized for common audio sample rates and performs
85-
/// integer arithmetic only, making it suitable for real-time applications.
69+
/// - **Large values**: Handles overflow gracefully within `Duration` limits
8670
#[cfg(any(feature = "claxon", feature = "hound",))]
87-
pub(super) fn samples_to_duration(samples: u64, sample_rate: u64) -> Duration {
88-
if sample_rate == 0 {
89-
return Duration::ZERO;
90-
}
91-
71+
pub(super) fn samples_to_duration(samples: u64, sample_rate: SampleRate) -> Duration {
72+
let sample_rate = sample_rate.get() as u64;
9273
let secs = samples / sample_rate;
9374
let nanos = ((samples % sample_rate) * 1_000_000_000) / sample_rate;
9475
Duration::new(secs, nanos as u32)
@@ -182,34 +163,31 @@ mod tests {
182163
#[test]
183164
fn test_samples_to_duration() {
184165
// Standard CD quality: 1 second at 44.1kHz
185-
assert_eq!(samples_to_duration(44100, 44100), Duration::from_secs(1));
166+
let rate_44_1k = SampleRate::new(44100).unwrap();
167+
assert_eq!(
168+
samples_to_duration(rate_44_1k.get() as u64, rate_44_1k),
169+
Duration::from_secs(1)
170+
);
186171

187172
// Half second at CD quality
188173
assert_eq!(
189-
samples_to_duration(22050, 44100),
174+
samples_to_duration(rate_44_1k.get() as u64 / 2, rate_44_1k),
190175
Duration::from_millis(500)
191176
);
192177

193-
// Professional audio: 1 second at 48kHz
194-
assert_eq!(samples_to_duration(48000, 48000), Duration::from_secs(1));
195-
196-
// High resolution: 1 second at 96kHz
197-
assert_eq!(samples_to_duration(96000, 96000), Duration::from_secs(1));
198-
199178
// Edge case: Zero samples should return zero duration
200-
assert_eq!(samples_to_duration(0, 44100), Duration::ZERO);
201-
202-
// Edge case: Zero sample rate should not panic and return zero
203-
assert_eq!(samples_to_duration(44100, 0), Duration::ZERO);
179+
assert_eq!(samples_to_duration(0, rate_44_1k), Duration::ZERO);
204180

205181
// Precision test: Fractional milliseconds
206182
// 441 samples at 44.1kHz = 10ms exactly
207-
assert_eq!(samples_to_duration(441, 44100), Duration::from_millis(10));
183+
assert_eq!(
184+
samples_to_duration(rate_44_1k.get() as u64 / 100, rate_44_1k),
185+
Duration::from_millis(10)
186+
);
208187

209188
// Very small durations should have nanosecond precision
210-
// 1 sample at 44.1kHz ≈ 22.676 microseconds
211-
let one_sample_duration = samples_to_duration(1, 44100);
212-
assert!(one_sample_duration.as_nanos() > 22000);
213-
assert!(one_sample_duration.as_nanos() < 23000);
189+
// 1 sample at 44.1kHz ≈ 22.675 microseconds
190+
let one_sample_duration = samples_to_duration(1, rate_44_1k);
191+
assert_eq!(one_sample_duration.as_nanos(), 22675);
214192
}
215193
}

src/decoder/vorbis.rs

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -840,6 +840,8 @@ fn read_next_non_empty_packet<R: Read + Seek>(
840840
/// point a final linear scan ensures all granule positions are found. The packet
841841
/// limit during binary search prevents excessive scanning in dense regions.
842842
fn find_last_granule<R: Read + Seek>(data: &mut R, byte_len: u64) -> Option<u64> {
843+
const BINARY_SEARCH_PACKET_LIMIT: usize = 50;
844+
843845
// Save current position
844846
let original_pos = data.stream_position().unwrap_or_default();
845847
let _ = data.rewind();
@@ -853,7 +855,7 @@ fn find_last_granule<R: Read + Seek>(data: &mut R, byte_len: u64) -> Option<u64>
853855
let mid = left + (right - left) / 2;
854856

855857
// Try to find a granule from this position (limited packet scan during binary search)
856-
match find_granule_from_position(data, mid, Some(50)) {
858+
match find_granule_from_position(data, mid, Some(BINARY_SEARCH_PACKET_LIMIT)) {
857859
Some(_granule) => {
858860
// Found a granule, this means there's content at or after this position
859861
best_start_position = mid;
@@ -894,9 +896,11 @@ fn find_last_granule<R: Read + Seek>(data: &mut R, byte_len: u64) -> Option<u64>
894896
///
895897
/// # Packet Limit Rationale
896898
///
899+
/// Maximum number of packets to scan during binary search optimization.
900+
///
897901
/// When used during binary search, the packet limit prevents excessive scanning:
898902
/// - **Typical Ogg pages**: Contain 1-10 packets depending on content
899-
/// - **50 packet limit**: Covers roughly 5-50 pages (~20-400KB depending on bitrate)
903+
/// - **Binary search limit**: Covers roughly 5-50 pages (~20-400KB depending on bitrate)
900904
/// - **Balance**: Finding granules quickly vs. avoiding excessive I/O during binary search
901905
/// - **Final scan**: No limit ensures complete coverage from optimized position
902906
///

src/decoder/wav.rs

Lines changed: 13 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -256,29 +256,32 @@ where
256256
let spec = reader.spec();
257257
let len = reader.len() as u64;
258258
let total_samples = reader.len();
259+
260+
let sample_rate = SampleRate::new(spec.sample_rate)
261+
.expect("wav should have a sample rate higher than zero");
262+
let channels = ChannelCount::new(spec.channels)
263+
.expect("wav should have at least one channel");
264+
let bits_per_sample = BitDepth::new(spec.bits_per_sample.into())
265+
.expect("wav should have a bit depth higher than zero");
266+
259267
let reader = SamplesIterator {
260268
reader,
261269
samples_read: 0,
262270
total_samples,
263271
};
264272

265-
let sample_rate = spec.sample_rate;
266-
let channels = spec.channels;
267-
268273
// len is number of samples, not bytes, so use samples_to_duration
269274
// Note: hound's len() returns total samples across all channels
270-
let samples_per_channel = len / (channels as u64);
271-
let total_duration = utils::samples_to_duration(samples_per_channel, sample_rate as u64);
275+
let samples_per_channel = len / (channels.get() as u64);
276+
let total_duration = utils::samples_to_duration(samples_per_channel, sample_rate);
272277

273278
Ok(Self {
274279
reader,
275280
total_duration,
276-
sample_rate: SampleRate::new(sample_rate)
277-
.expect("wav should have a sample rate higher then zero"),
278-
channels: ChannelCount::new(channels).expect("wav should have a least one channel"),
281+
sample_rate,
282+
channels,
279283
is_seekable: settings.is_seekable,
280-
bits_per_sample: BitDepth::new(spec.bits_per_sample.into())
281-
.expect("wav should have a bit depth higher then zero"),
284+
bits_per_sample,
282285
})
283286
}
284287

0 commit comments

Comments
 (0)