diff --git a/src/lib.rs b/src/lib.rs index 8edd2eb..6099e4b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -62,17 +62,23 @@ pub trait Convert { } } -const MIN_RATIO: f64 = 0.01; -const MAX_RATIO: f64 = 100.0; - #[derive(Debug)] pub enum Error { - InvalidRatio, + UnsupportedRatio, InvalidParam, } pub type Result = std::result::Result; +use num_rational::Rational64; + +fn supported_ratio(ratio: Rational64) -> bool { + ratio > Rational64::default() + && *ratio.numer() <= 1024 + && ratio.ceil().to_integer() <= 16 + && ratio.recip().ceil().to_integer() <= 16 +} + #[cfg(test)] mod tests { use super::*; diff --git a/src/linear.rs b/src/linear.rs index 8030530..c22a7a4 100644 --- a/src/linear.rs +++ b/src/linear.rs @@ -2,6 +2,8 @@ use num_rational::Rational64; +use crate::supported_ratio; + use super::{Convert, Error, Result}; enum State { @@ -21,10 +23,9 @@ pub struct Converter { impl Converter { #[inline] - fn new(step: f64) -> Self { - let rstep = Rational64::approximate_float(step).unwrap(); - let numer = *rstep.numer() as usize; - let denom = *rstep.denom() as usize; + fn new(step: Rational64) -> Self { + let numer = *step.numer() as usize; + let denom = *step.denom() as usize; let mut coefs = Vec::with_capacity(denom); for i in 0..denom { coefs.push(i as f64 / denom as f64); @@ -86,20 +87,19 @@ impl Convert for Converter { } } -use super::{MAX_RATIO, MIN_RATIO}; - #[derive(Clone, Copy)] pub struct Manager { - ratio: f64, + ratio: Rational64, } impl Manager { #[inline] pub fn new(ratio: f64) -> Result { - if (MIN_RATIO..=MAX_RATIO).contains(&ratio) { + let ratio = Rational64::approximate_float(ratio).unwrap_or_default(); + if supported_ratio(ratio) { Ok(Self { ratio }) } else { - Err(Error::InvalidRatio) + Err(Error::UnsupportedRatio) } } @@ -115,7 +115,7 @@ mod tests { #[test] fn test_manager_ok() { - let ratio_ok = vec![0.01, 1.0, 10.0, 99.99, 100.0]; + let ratio_ok = vec![0.0625, 0.063, 1.0, 15.9, 16.0]; for ratio in ratio_ok { assert!(Manager::new(ratio).is_ok()); } @@ -126,8 +126,9 @@ mod tests { let ratio_err = vec![ -1.0, 0.0, - 100.01, - 1000.0, + 0.0624, + 16.01, + 0.123456, f64::INFINITY, f64::NEG_INFINITY, f64::NAN, diff --git a/src/sinc.rs b/src/sinc.rs index f33d48d..181d5ae 100644 --- a/src/sinc.rs +++ b/src/sinc.rs @@ -6,6 +6,8 @@ use std::sync::Arc; use num_rational::Rational64; +use crate::supported_ratio; + use super::{Convert, Error, Result}; #[inline] @@ -97,10 +99,9 @@ pub struct Converter { impl Converter { #[inline] - fn new(step: f64, order: u32, quan: u32, filter: Arc>) -> Self { - let rstep = Rational64::approximate_float(step).unwrap(); - let numer = *rstep.numer() as usize; - let denom = *rstep.denom() as usize; + fn new(step: Rational64, order: u32, quan: u32, filter: Arc>) -> Self { + let numer = *step.numer() as usize; + let denom = *step.denom() as usize; let mut coefs = Vec::with_capacity(denom); for i in 0..denom { coefs.push(i as f64 / denom as f64); @@ -178,20 +179,16 @@ impl Convert for Converter { } } -use super::{MAX_RATIO, MIN_RATIO}; - const MIN_ORDER: u32 = 1; const MAX_ORDER: u32 = 2048; const MIN_QUAN: u32 = 1; const MAX_QUAN: u32 = 16384; -const MIN_CUTOFF: f64 = 0.01; -const MAX_CUTOFF: f64 = 1.0; const MIN_ATTEN: f64 = 12.0; const MAX_ATTEN: f64 = 180.0; #[derive(Clone)] pub struct Manager { - ratio: f64, + ratio: Rational64, order: u32, quan: u32, latency: usize, @@ -199,33 +196,26 @@ pub struct Manager { } 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, 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, + fn with_raw_internal( + ratio: Rational64, quan: u32, order: u32, kaiser_beta: f64, cutoff: f64, ) -> Result { - if !(MIN_RATIO..=MAX_RATIO).contains(&ratio) { - return Err(Error::InvalidRatio); + if !supported_ratio(ratio) { + return Err(Error::UnsupportedRatio); } if !(MIN_QUAN..=MAX_QUAN).contains(&quan) || !(MIN_ORDER..=MAX_ORDER).contains(&order) || !(0.0..=20.0).contains(&kaiser_beta) - || !(MIN_CUTOFF..=MAX_CUTOFF).contains(&cutoff) + || !(0.01..=1.0).contains(&cutoff) { 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; + let fratio = *ratio.numer() as f64 / *ratio.denom() as f64; + let latency = (fratio * order as f64 * 0.5).round() as usize; Ok(Self { ratio, order, @@ -235,18 +225,38 @@ 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, support `[0.1, 10.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 { + let ratio = Rational64::approximate_float(ratio).unwrap_or_default(); + Self::with_raw_internal(ratio, quan, order, kaiser_beta, cutoff) + } + /// 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, 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] + /// - ratio: the conversion ratio, fs_new / fs_old, support `[0.1, 10.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) -> Result { - if !(MIN_RATIO..=MAX_RATIO).contains(&ratio) { - return Err(Error::InvalidRatio); + let ratio_i64 = Rational64::approximate_float(ratio).unwrap_or_default(); + if !supported_ratio(ratio_i64) { + return Err(Error::UnsupportedRatio); } if !(MIN_ATTEN..=MAX_ATTEN).contains(&atten) || !(MIN_QUAN..=MAX_QUAN).contains(&quan) @@ -257,21 +267,22 @@ impl Manager { 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); - Self::with_raw(ratio, quan, order, kaiser_beta, cutoff) + Self::with_raw_internal(ratio_i64, quan, order, kaiser_beta, cutoff) } /// 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] + /// - ratio: `[0.1, 10.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) -> Result { - if !(MIN_RATIO..=MAX_RATIO).contains(&ratio) { - return Err(Error::InvalidRatio); + let ratio_i64 = Rational64::approximate_float(ratio).unwrap_or_default(); + if !supported_ratio(ratio_i64) { + return Err(Error::UnsupportedRatio); } if !(MIN_ATTEN..=MAX_ATTEN).contains(&atten) || !(MIN_QUAN..=MAX_QUAN).contains(&quan) @@ -282,7 +293,7 @@ impl Manager { 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); - Self::with_raw(ratio, quan, order, kaiser_beta, cutoff) + Self::with_raw_internal(ratio_i64, quan, order, kaiser_beta, cutoff) } /// Create a `Converter` which actually implement the interpolation. @@ -316,15 +327,12 @@ mod tests { #[test] fn test_manager_with_raw() { assert!(Manager::with_raw(2.0, 32, 32, 5.0, 0.8).is_ok()); - assert!(Manager::with_raw(0.01, 32, 32, 5.0, 0.8).is_ok()); - assert!(Manager::with_raw(100.0, 32, 32, 5.0, 0.8).is_ok()); - assert!(Manager::with_raw(0.009, 32, 32, 5.0, 0.8).is_err()); - assert!(Manager::with_raw(100.1, 32, 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, 32, 0, 5.0, 0.8).is_err()); - assert!(Manager::with_raw(2.0, 32, 32, 5.0, -0.1).is_err()); + assert!(Manager::with_raw(2.0, 32, 32, 5.0, 0.0).is_err()); assert!(Manager::with_raw(2.0, 32, 32, 5.0, 1.1).is_err()); assert!(Manager::with_raw(2.0, 32, 32, -0.1, 0.8).is_err()); + assert!(Manager::with_raw(2.0, 32, 32, 20.1, 0.8).is_err()); } #[test]