From f427aa55a4e0cddedf954a19568e20edffa33f22 Mon Sep 17 00:00:00 2001 From: PeaZomboss Date: Tue, 15 Oct 2024 18:00:11 +0800 Subject: [PATCH] Use Result, bound check and add tests --- benches/bench1.rs | 10 ++-- examples/e1.rs | 2 +- examples/perf1.rs | 2 +- examples/two_channels.rs | 2 +- src/lib.rs | 23 +++++--- src/linear.rs | 41 ++++++++++++-- src/sinc.rs | 115 +++++++++++++++++++++++++++++++++------ tests/linear.rs | 3 +- tests/perf.rs | 8 +-- tests/sinc.rs | 8 ++- 10 files changed, 172 insertions(+), 42 deletions(-) diff --git a/benches/bench1.rs b/benches/bench1.rs index f7a0af1..d3042eb 100644 --- a/benches/bench1.rs +++ b/benches/bench1.rs @@ -47,14 +47,15 @@ const TRANS48K: f64 = 4000.0 / 24000.0; #[divan::bench(args=[Conv::C44k48k, Conv::C44k96k, Conv::C48k44k, Conv::C48k96k, Conv::C96k44k, Conv::C96k48k])] fn init_a120(conv: &Conv) -> Manager { - match conv { + let m = match conv { Conv::C44k48k => Manager::new(R44K48K, 120.0, 512, TRANS44K), Conv::C44k96k => Manager::new(R44K96K, 120.0, 512, TRANS44K), Conv::C48k44k => Manager::new(R48K44K, 120.0, 512, TRANS44K), Conv::C48k96k => Manager::new(R48K96K, 120.0, 512, TRANS48K), Conv::C96k44k => Manager::new(R96K44K, 120.0, 512, TRANS44K), Conv::C96k48k => Manager::new(R96K48K, 120.0, 512, TRANS48K), - } + }; + m.unwrap() } #[divan::bench( @@ -74,14 +75,15 @@ fn proc_a120_10ms(bencher: divan::Bencher, conv: &Conv) { #[divan::bench(args=[Conv::C44k48k, Conv::C44k96k, Conv::C48k44k, Conv::C48k96k, Conv::C96k44k, Conv::C96k48k])] fn init_a144(conv: &Conv) -> Manager { - match conv { + let m = match conv { Conv::C44k48k => Manager::new(R44K48K, 144.0, 2048, TRANS44K), Conv::C44k96k => Manager::new(R44K96K, 144.0, 2048, TRANS44K), Conv::C48k44k => Manager::new(R48K44K, 144.0, 2048, TRANS44K), Conv::C48k96k => Manager::new(R48K96K, 144.0, 2048, TRANS48K), Conv::C96k44k => Manager::new(R96K44K, 144.0, 2048, TRANS44K), Conv::C96k48k => Manager::new(R96K48K, 144.0, 2048, TRANS48K), - } + }; + m.unwrap() } #[divan::bench( diff --git a/examples/e1.rs b/examples/e1.rs index de21070..c46a0e4 100644 --- a/examples/e1.rs +++ b/examples/e1.rs @@ -3,7 +3,7 @@ use simple_src::{linear, Convert}; fn main() { let samples1 = [1.0, 2.0, 3.0, 4.0]; let samples2 = [5.0, 6.0, 7.0, 8.0]; - let manager = linear::Manager::new(2.0); + let manager = linear::Manager::new(2.0).unwrap(); let mut cvtr = manager.converter(); for s in cvtr.process(samples1.into_iter()) { println!("{s}"); diff --git a/examples/perf1.rs b/examples/perf1.rs index 00b9e02..6329d6c 100644 --- a/examples/perf1.rs +++ b/examples/perf1.rs @@ -6,7 +6,7 @@ use simple_src::{sinc, Convert}; // cargo flamegraph --profile perf --example perf1 fn main() { let now = std::time::Instant::now(); - let manager = sinc::Manager::new(48000.0 / 44100.0, 150.0, 2048, 2050.0 / 22050.0); + let manager = sinc::Manager::new(48000.0 / 44100.0, 150.0, 2048, 2050.0 / 22050.0).unwrap(); println!("{:?}", now.elapsed()); let now = std::time::Instant::now(); let iter = (0..).map(|x| x as f64).into_iter(); diff --git a/examples/two_channels.rs b/examples/two_channels.rs index 1b1f9bd..633856f 100644 --- a/examples/two_channels.rs +++ b/examples/two_channels.rs @@ -34,7 +34,7 @@ fn convert_to_48k() { sample_format: hound::SampleFormat::Int, }; let mut writer = hound::WavWriter::create(TARGET_FILE, spec).unwrap(); - let manager = sinc::Manager::new(48000.0 / 44100.0, 110.0, 256, 2050.0 / 22050.0); + let manager = sinc::Manager::new(48000.0 / 44100.0, 110.0, 256, 2050.0 / 22050.0).unwrap(); let latency = manager.latency(); let mut converter1 = manager.converter(); let mut converter2 = manager.converter(); diff --git a/src/lib.rs b/src/lib.rs index 4b53963..2818f36 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -6,7 +6,7 @@ //! use simple_src::{sinc, Convert}; //! //! let samples = vec![1.0, 2.0, 3.0, 4.0]; -//! let manager = sinc::Manager::new(2.0, 48.0, 8, 0.1); +//! let manager = sinc::Manager::new(2.0, 48.0, 8, 0.1).unwrap(); //! let mut converter = manager.converter(); //! for s in converter.process(samples.into_iter()) { //! println!("{s}"); @@ -62,6 +62,14 @@ pub trait Convert { } } +#[derive(Debug)] +pub enum Error { + InvalidRatio, + InvalidParam, +} + +pub type Result = std::result::Result; + #[cfg(test)] mod tests { use super::*; @@ -73,10 +81,11 @@ mod tests { #[allow(dead_code)] pub fn new(a: i32) -> Box { if a == 0 { - Box::new(linear::Converter::new(0.5)) + let manager = linear::Manager::new(2.0).unwrap(); + Box::new(manager.converter()) } else { - let filter = std::sync::Arc::new(vec![]); - Box::new(sinc::Converter::new(0.5, 128, 128, filter)) + let manager = sinc::Manager::new(2.0, 48.0, 8, 0.2).unwrap(); + Box::new(manager.converter()) } } } @@ -85,7 +94,7 @@ mod tests { #[ignore = "display only"] fn test1() { let samples = vec![1.0, 2.0, 3.0, 4.0]; - let manager = linear::Manager::new(2.0); + let manager = linear::Manager::new(2.0).unwrap(); let mut cvtr = manager.converter(); for s in cvtr.process(samples.into_iter()) { println!("sample = {s}"); @@ -96,7 +105,7 @@ mod tests { #[ignore = "display only"] fn test2() { let samples = vec![1.0, 2.0, 3.0, 4.0, 5.0]; - let manager = sinc::Manager::with_raw(2.0, 16, 4, 5.0, 1.0); + let manager = sinc::Manager::with_raw(2.0, 16, 4, 5.0, 1.0).unwrap(); for s in manager .converter() .process(samples.into_iter()) @@ -110,7 +119,7 @@ mod tests { #[ignore = "display only"] fn test3() { let samples = vec![1.0, 2.0, 3.0, 4.0, 5.0]; - let manager = sinc::Manager::with_order(2.0, 30.0, 16, 4); + let manager = sinc::Manager::with_order(2.0, 30.0, 16, 4).unwrap(); for s in manager .converter() .process(samples.into_iter()) diff --git a/src/linear.rs b/src/linear.rs index 98b1b62..5ef76d3 100644 --- a/src/linear.rs +++ b/src/linear.rs @@ -1,6 +1,6 @@ //! Linear converter -use super::Convert; +use super::{Convert, Error, Result}; enum State { First, @@ -17,7 +17,7 @@ pub struct Converter { impl Converter { #[inline] - pub fn new(step: f64) -> Self { + fn new(step: f64) -> Self { Self { step, pos: 0.0, @@ -79,8 +79,12 @@ pub struct Manager { impl Manager { #[inline] - pub fn new(ratio: f64) -> Self { - Self { ratio } + pub fn new(ratio: f64) -> Result { + if ratio >= 0.01 && ratio <= 100.0 { + Ok(Self { ratio }) + } else { + Err(Error::InvalidRatio) + } } #[inline] @@ -88,3 +92,32 @@ impl Manager { Converter::new(self.ratio.recip()) } } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_manager_ok() { + let ratio_ok = vec![0.01, 1.0, 10.0, 99.99, 100.0]; + for ratio in ratio_ok { + assert!(Manager::new(ratio).is_ok()); + } + } + + #[test] + fn test_manager_err() { + let ratio_err = vec![ + -1.0, + 0.0, + 100.01, + 1000.0, + f64::INFINITY, + f64::NEG_INFINITY, + f64::NAN, + ]; + for ratio in ratio_err { + assert!(Manager::new(ratio).is_err()); + } + } +} diff --git a/src/sinc.rs b/src/sinc.rs index 3c300ba..ac0712e 100644 --- a/src/sinc.rs +++ b/src/sinc.rs @@ -4,7 +4,7 @@ use std::collections::VecDeque; use std::f64::consts::PI; use std::sync::Arc; -use super::Convert; +use super::{Convert, Error, Result}; #[inline] fn sinc_c(x: f64, cutoff: f64) -> f64 { @@ -93,7 +93,7 @@ pub struct Converter { impl Converter { #[inline] - pub fn new(step: f64, order: u32, quan: u32, filter: Arc>) -> Self { + fn new(step: f64, order: u32, quan: u32, filter: Arc>) -> Self { let taps = (order + 1) as usize; let mut buf = VecDeque::with_capacity(taps); buf.extend(std::iter::repeat(0.0).take(taps)); @@ -178,33 +178,65 @@ impl Manager { /// Create a `Manager` with raw parameters, that means all of these should /// be calculated in advance. /// - /// - ratio: the conversion ratio, fs_new / fs_old - /// - quan: the quantify number, usually power of 2 - /// - order: the order of interpolation FIR filter - /// - kaiser_beta: the beta parameter of kaiser window method - /// - cutoff: the cutoff of FIR filter, according to target sample rate - pub fn with_raw(ratio: f64, quan: u32, order: u32, kaiser_beta: f64, cutoff: f64) -> Self { + /// - ratio: the conversion ratio, fs_new / fs_old, support [0.1, 100.0] + /// - quan: the quantify number, usually power of 2, support [1, 16384] + /// - order: the order of interpolation FIR filter, support [1, 2048] + /// - kaiser_beta: the beta parameter of kaiser window method, support [0.0, 20.0] + /// - cutoff: the cutoff of FIR filter, according to target sample rate, in [0.01, 1.0] + pub fn with_raw( + ratio: f64, + quan: u32, + order: u32, + kaiser_beta: f64, + cutoff: f64, + ) -> Result { + if ratio < 0.01 || ratio > 100.0 { + return Err(Error::InvalidRatio); + } + if quan == 0 + || quan > 16384 + || order == 0 + || order > 2048 + || kaiser_beta < 0.0 + || kaiser_beta > 20.0 + || cutoff < 0.01 + || cutoff > 1.0 + { + return Err(Error::InvalidParam); + } let filter = generate_filter_table(quan, order, kaiser_beta, cutoff); let latency = (ratio * order as f64 * 0.5).round() as usize; - Self { + Ok(Self { ratio, order, quan, latency, filter: Arc::new(filter), - } + }) } /// Create a `Manager` with attenuation, quantify and transition band width. /// /// That means the order will be calculated. /// - /// - ratio: the conversion ratio, fs_new / fs_old - /// - atten: the attenuation in dB - /// - quan: the quantify number, usually power of 2 - /// - trans_width: the transition band width in (0,1) + /// - ratio: the conversion ratio, fs_new / fs_old, support [0.1, 100.0] + /// - atten: the attenuation in dB, support [12.0, 180.0] + /// - quan: the quantify number, usually power of 2, support [1, 16384] + /// - trans_width: the transition band width in [0.01, 1.0] #[inline] - pub fn new(ratio: f64, atten: f64, quan: u32, trans_width: f64) -> Self { + pub fn new(ratio: f64, atten: f64, quan: u32, trans_width: f64) -> Result { + if ratio < 0.01 || ratio > 100.0 { + return Err(Error::InvalidRatio); + } + if atten < 12.0 + || atten > 180.0 + || quan == 0 + || quan > 16384 + || trans_width < 0.01 + || trans_width > 1.0 + { + return Err(Error::InvalidParam); + } let kaiser_beta = calc_kaiser_beta(atten); let order = calc_order(ratio, atten, trans_width); let cutoff = ratio.min(1.0) * (1.0 - 0.5 * trans_width); @@ -214,8 +246,20 @@ impl Manager { /// Create a `Manager` with attenuation, quantify and order /// /// That means the transition band will be calculated. + /// + /// - ratio: [0.1, 100.0] + /// - atten: [12.0, 180.0] + /// - quan: [1, 16384] + /// - order: [1, 2048] #[inline] - pub fn with_order(ratio: f64, atten: f64, quan: u32, order: u32) -> Self { + pub fn with_order(ratio: f64, atten: f64, quan: u32, order: u32) -> Result { + if ratio < 0.01 || ratio > 100.0 { + return Err(Error::InvalidRatio); + } + if atten < 12.0 || atten > 180.0 || quan == 0 || quan > 16384 || order == 0 || order > 2048 + { + return Err(Error::InvalidParam); + } let kaiser_beta = calc_kaiser_beta(atten); let trans_width = calc_trans_width(ratio, atten, order); let cutoff = ratio.min(1.0) * (1.0 - 0.5 * trans_width); @@ -245,3 +289,42 @@ impl Manager { self.order } } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_manager_with_raw() { + assert!(Manager::with_raw(2.0, 48, 32, 5.0, 0.8).is_ok()); + assert!(Manager::with_raw(0.01, 48, 32, 5.0, 0.8).is_ok()); + assert!(Manager::with_raw(100.0, 48, 32, 5.0, 0.8).is_ok()); + assert!(Manager::with_raw(0.009, 48, 32, 5.0, 0.8).is_err()); + assert!(Manager::with_raw(100.1, 48, 32, 5.0, 0.8).is_err()); + assert!(Manager::with_raw(2.0, 0, 32, 5.0, 0.8).is_err()); + assert!(Manager::with_raw(2.0, 48, 0, 5.0, 0.8).is_err()); + assert!(Manager::with_raw(2.0, 48, 32, 5.0, -0.1).is_err()); + assert!(Manager::with_raw(2.0, 48, 32, 5.0, 1.1).is_err()); + assert!(Manager::with_raw(2.0, 48, 32, -0.1, 0.8).is_err()); + } + + #[test] + fn test_manager_new() { + assert!(Manager::new(2.0, 96.0, 128, 0.1).is_ok()); + assert!(Manager::new(2.0, 96.0, 0, 0.1).is_err()); + assert!(Manager::new(2.0, 96.0, 128, 0.0).is_err()); + assert!(Manager::new(2.0, 96.0, 128, 1.1).is_err()); + assert!(Manager::new(2.0, 4.0, 128, 0.1).is_err()); + assert!(Manager::new(2.0, 8.0, 128, 0.1).is_err()); + assert!(Manager::new(2.0, 8.1, 128, 0.1).is_ok()); + } + + #[test] + fn test_manager_with_order() { + assert!(Manager::with_order(2.0, 96.0, 128, 128).is_ok()); + assert!(Manager::with_order(2.0, 96.0, 128, 0).is_err()); + assert!(Manager::with_order(2.0, 96.0, 0, 128).is_err()); + assert!(Manager::with_order(2.0, 8.0, 128, 128).is_err()); + assert!(Manager::with_order(2.0, 8.1, 128, 128).is_ok()); + } +} diff --git a/tests/linear.rs b/tests/linear.rs index 13b2bd1..1a87591 100644 --- a/tests/linear.rs +++ b/tests/linear.rs @@ -22,6 +22,7 @@ fn convert(file_prefix: &str, sr_old: u32, sr_new: u32) { .map(|s| s.unwrap() as f64) .chain(std::iter::repeat(0.0)); Manager::new(ratio) + .unwrap() .converter() .process(in_iter) .take(out_duration) @@ -45,7 +46,7 @@ fn tlinear() { #[test] #[ignore = "display only"] fn tmultithread() { - let manager = Manager::new(2.0); + let manager = Manager::new(2.0).unwrap(); let h1 = std::thread::spawn(move || { let mut converter = manager.converter(); let samples = (0..10).map(|x| x as f64); diff --git a/tests/perf.rs b/tests/perf.rs index c2a9508..fead33d 100644 --- a/tests/perf.rs +++ b/tests/perf.rs @@ -7,7 +7,7 @@ use simple_src::{sinc, Convert}; // cargo flamegraph --profile perf --test perf -- --show-output --ignored --exact t4448 fn t4448() { let now = std::time::Instant::now(); - let manager = sinc::Manager::new(48000.0 / 44100.0, 150.0, 2048, 2050.0 / 22050.0); + let manager = sinc::Manager::new(48000.0 / 44100.0, 150.0, 2048, 2050.0 / 22050.0).unwrap(); println!("{:?}", now.elapsed()); let now = std::time::Instant::now(); let iter = (0..).map(|x| x as f64).into_iter(); @@ -22,7 +22,7 @@ fn t4448() { // cargo flamegraph --profile perf --test perf -- --show-output --ignored --exact t4844 fn t4844() { let now = std::time::Instant::now(); - let manager = sinc::Manager::new(44100.0 / 48000.0, 150.0, 2048, 2050.0 / 22050.0); + let manager = sinc::Manager::new(44100.0 / 48000.0, 150.0, 2048, 2050.0 / 22050.0).unwrap(); println!("{:?}", now.elapsed()); let now = std::time::Instant::now(); let iter = (0..).map(|x| x as f64).into_iter(); @@ -37,7 +37,7 @@ fn t4844() { // cargo flamegraph --profile perf --test perf -- --show-output --ignored --exact t9644 fn t9644() { let now = std::time::Instant::now(); - let manager = sinc::Manager::new(44100.0 / 96000.0, 150.0, 2048, 2050.0 / 22050.0); + let manager = sinc::Manager::new(44100.0 / 96000.0, 150.0, 2048, 2050.0 / 22050.0).unwrap(); println!("{:?}", now.elapsed()); let now = std::time::Instant::now(); let iter = (0..).map(|x| x as f64).into_iter(); @@ -52,7 +52,7 @@ fn t9644() { // cargo flamegraph --profile perf --test perf -- --show-output --ignored --exact t9648 fn t9648() { let now = std::time::Instant::now(); - let manager = sinc::Manager::new(48000.0 / 96000.0, 150.0, 2048, 4000.0 / 24000.0); + let manager = sinc::Manager::new(48000.0 / 96000.0, 150.0, 2048, 4000.0 / 24000.0).unwrap(); println!("{:?}", now.elapsed()); let now = std::time::Instant::now(); let iter = (0..).map(|x| x as f64).into_iter(); diff --git a/tests/sinc.rs b/tests/sinc.rs index d9e0927..c5e68ab 100644 --- a/tests/sinc.rs +++ b/tests/sinc.rs @@ -13,7 +13,8 @@ impl Src { Self { sr_old, sr_new, - manager: sinc::Manager::with_order(sr_new as f64 / sr_old as f64, atten, quan, order), + manager: sinc::Manager::with_order(sr_new as f64 / sr_old as f64, atten, quan, order) + .unwrap(), } } @@ -27,7 +28,8 @@ impl Src { Self { sr_old, sr_new, - manager: sinc::Manager::new(sr_new as f64 / sr_old as f64, atten, quan, trans_width), + manager: sinc::Manager::new(sr_new as f64 / sr_old as f64, atten, quan, trans_width) + .unwrap(), } } } @@ -334,7 +336,7 @@ fn ta150_1_192k_down_order() { #[test] #[ignore = "display only"] fn tmultithread() { - let manager = sinc::Manager::new(2.0, 30.0, 16, 0.1); + let manager = sinc::Manager::new(2.0, 30.0, 16, 0.1).unwrap(); let manager2 = manager.clone(); let h1 = std::thread::spawn(move || { let mut converter = manager.converter();