Skip to content

Instantly share code, notes, and snippets.

@ericdrobinson
Created January 1, 2023 03:14
Show Gist options
  • Save ericdrobinson/77c975b32af9912525623ae99ac9d100 to your computer and use it in GitHub Desktop.
Save ericdrobinson/77c975b32af9912525623ae99ac9d100 to your computer and use it in GitHub Desktop.
Sample Time Conversion Tests
/// Tests time conversion roundtripping between sample time and seconds using the specified sample
/// rate (`sr`). All possible sample times up-to-and-including the maximum unsigned integer value
/// are tested. This test uses single precision floating point math.
fn test_time_conversion_for_all_sample_lengths_f32(sr: f32) {
let mut sample: u32 = 0;
loop {
let seconds: f32 = sample as f32 / sr;
if sample != (seconds * sr).round() as u32 {
let hours: f32 = (seconds / 3600.).floor();
let minutes: f32 = (seconds / 60.).floor().rem_euclid(60.);
let seconds: f32 = seconds.rem_euclid(60.).floor();
println!("[f32 - {}] Broke on sample number {}, a time of {}:{:02}:{:02}", sr, sample, hours, minutes, seconds);
break;
}
else if sample == std::u32::MAX {
println!("[f32 - {}] Broke without reaching a non-roundtripable sample!", sr);
break;
}
sample += 1;
}
}
/// Tests time conversion roundtripping between sample time and seconds using the specified sample
/// rate (`sr`). All possible sample times up-to-and-including the maximum unsigned integer value
/// are tested. This test uses double precision floating point math.
fn test_time_conversion_for_all_sample_lengths_f64(sr: f64) {
let mut sample: u32 = 0;
loop {
let seconds: f64 = sample as f64 / sr;
if sample != (seconds * sr).round() as u32 {
let hours: f64 = (seconds / 3600.).floor();
let minutes: f64 = (seconds / 60.).floor().rem_euclid(60.);
let seconds: f64 = seconds.rem_euclid(60.).floor();
println!("[f64 - {}] Broke on sample number {}, a time of {}:{:02}:{:02}", sr, sample, hours, minutes, seconds);
break;
}
else if sample == std::u32::MAX {
println!("[f64 - {}] Broke without reaching a non-roundtripable sample!", sr);
break;
}
sample += 1;
}
}
/// A single-run version of this is available on the official Rust Playground. See:
/// - https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=7b61c9d842e8efa9b535f380fd5302ba
fn test_single_percentage(total_samples: i32) -> (i32, i32) {
let mut sample_count: i32 = 0;
while sample_count < total_samples {
let percent: f32 = sample_count as f32 / total_samples as f32;
let new_samp: i32 = (percent * total_samples as f32).round() as i32;
if sample_count != new_samp {
return (sample_count, new_samp);
}
else {
sample_count += 1;
}
}
(-1, -1)
}
/// Tests round-tripping sample times to-from percentage (normalized values in range [0,1]). The
/// function runs in two loops:
///
/// - **Outer Loop:** Test all possible sample lengths up to the specified `max_samples` value.
/// - **Inner Loop:** Test all possible sample positions given the current sample length.
///
/// This function is useful for discovering the first sample length at which a percentage roundtrip
/// calculation using single precision floating point math for normalization encounters a problem
/// (read: roundtripping fails).
///
/// **Note:** This function is _very_ long-running. A double precision test has not been
/// successfully run. A back-of-the-envelope estimation for maximum runtime put an upper bound on
/// a double-precision version of the function at something on the order of ~90 years.
fn test_percentages(max_samples: i32) {
let mut current_test_length: i32 = 1;
while current_test_length < max_samples {
let break_sample: (i32, i32) = test_single_percentage(current_test_length);
if break_sample.0 >= 0 {
println!("First sample length with percentage break is {} at sample position {} (round-tripped as {}).", current_test_length, break_sample.0, break_sample.1);
break;
}
current_test_length += 1;
}
if current_test_length == max_samples {
println!("All lengths up to {} are okay!", max_samples);
}
}
/// Runs a number of tests.
///
/// Output:
///
/// ```txt
/// [f32 - 44100] Broke on sample number 5644828, a time of 0:02:08
/// [f32 - 48000] Broke on sample number 6144007, a time of 0:02:08
/// [f32 - 96000] Broke on sample number 6144007, a time of 0:01:04
/// [f64 - 44100] Broke without reaching a non-roundtripable sample!
/// [f64 - 48000] Broke without reaching a non-roundtripable sample!
/// [f64 - 96000] Broke without reaching a non-roundtripable sample!
/// First sample length with percentage break is 8388611 at sample position 7689560 (round-tripped as 7689561).
/// ```
fn main() {
test_time_conversion_for_all_sample_lengths_f32(44100.);
test_time_conversion_for_all_sample_lengths_f32(48000.);
test_time_conversion_for_all_sample_lengths_f32(96000.);
test_time_conversion_for_all_sample_lengths_f64(44100.);
test_time_conversion_for_all_sample_lengths_f64(48000.);
test_time_conversion_for_all_sample_lengths_f64(96000.);
test_percentages(5 * 60 * 48000);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment