Skip to content

Commit

Permalink
Support rational ratio
Browse files Browse the repository at this point in the history
  • Loading branch information
DrPeaboss committed Oct 17, 2024
1 parent 01070ce commit 0c46eeb
Show file tree
Hide file tree
Showing 3 changed files with 73 additions and 58 deletions.
14 changes: 10 additions & 4 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<T> = std::result::Result<T, Error>;

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::*;
Expand Down
25 changes: 13 additions & 12 deletions src/linear.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

use num_rational::Rational64;

use crate::supported_ratio;

use super::{Convert, Error, Result};

enum State {
Expand All @@ -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);
Expand Down Expand Up @@ -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<Self> {
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)
}
}

Expand All @@ -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());
}
Expand All @@ -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,
Expand Down
92 changes: 50 additions & 42 deletions src/sinc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ use std::sync::Arc;

use num_rational::Rational64;

use crate::supported_ratio;

use super::{Convert, Error, Result};

#[inline]
Expand Down Expand Up @@ -97,10 +99,9 @@ pub struct Converter {

impl Converter {
#[inline]
fn new(step: f64, order: u32, quan: u32, filter: Arc<Vec<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, order: u32, quan: u32, filter: Arc<Vec<f64>>) -> 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);
Expand Down Expand Up @@ -178,54 +179,43 @@ 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,
filter: Arc<Vec<f64>>,
}

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<Self> {
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,
Expand All @@ -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<Self> {
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<Self> {
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)
Expand All @@ -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<Self> {
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)
Expand All @@ -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.
Expand Down Expand Up @@ -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]
Expand Down

0 comments on commit 0c46eeb

Please sign in to comment.