audio-widgets 0.1.0

A collection of audio related UI widgets for Rust.
Documentation
use crate::eq::common::*;
use scales::prelude::*;

const COEFFS: [[f64; 6]; 7] = [
    [1f64, 0f64, 0f64, 0f64, 0f64, 0f64],
    [1.4142f64, 0f64, 0f64, 1f64, 0f64, 0f64],
    [1f64, 1f64, 0f64, 0f64, 1f64, 0f64],
    [1.8478f64, 0.7654f64, 0f64, 1f64, 1f64, 0f64],
    [1f64, 1.6180f64, 0.6180f64, 0f64, 1f64, 1f64],
    [1.3617f64, 1.3617f64, 0f64, 0.6180f64, 0.6180f64, 0f64],
    [1.4142f64, 1.4142f64, 0f64, 1f64, 1f64, 0f64],
];

pub fn plot_eq(eq: &EQ, width: f64, height: f64, invert_y: bool) -> EqGraph {
    let x_conv = eq.x_to_frequency_converter(width);
    let y_conv = eq.y_to_gain_converter(height, invert_y);

    let xc = x_conv.clone();

    let fs = (0..width as usize).map(move |x| xc.convert(x as f64));

    let band_curves = eq.bands.iter().map(|(band, a)| (band.plot(fs.clone()), *a));
    let sum = merge_all(band_curves.clone());

    let band_curves: Vec<(Vec<(X, Y)>, Active)> = band_curves
        .map(|(curve, active)| (all_to_x_y(curve, &x_conv, &y_conv), active))
        .collect();

    let sum: Vec<(X, Y)> = if let Some(sum) = sum {
        all_to_x_y(sum, &x_conv, &y_conv)
    } else {
        Vec::new()
    };

    EqGraph { band_curves, sum }
}

pub fn plot(
    eq_band: &EqBand,
    range: impl Iterator<Item = Frequency> + 'static,
) -> Box<dyn Iterator<Item = (Frequency, Gain)>> {
    match eq_band {
        EqBand::Bell { frequency, gain, q } => plot_bell(range, *frequency, *gain, *q),
        EqBand::HighShelf { frequency, gain } => plot_high_shelf(range, *frequency, *gain),
        EqBand::LowShelf { frequency, gain } => plot_low_shelf(range, *frequency, *gain),
        EqBand::HighPass { frequency, slope } => plot_high_pass(range, *frequency, *slope),
        EqBand::LowPass { frequency, slope } => plot_low_pass(range, *frequency, *slope),
    }
}

fn plot_bell(
    range: impl Iterator<Item = Frequency> + 'static,
    frequency: Frequency,
    gain: Gain,
    q: Q,
) -> Box<dyn Iterator<Item = (Frequency, Gain)>> {
    Box::new(range.map(move |f| (f, calc_bell_gain(f, frequency, gain, q))))
}

fn calc_bell_gain(f: Frequency, frequency: Frequency, gain: Gain, q: Q) -> Gain {
    let p = to_power(gain);
    let pr = to_pr(p);

    let f0 = frequency / f;
    let f1 = f0.powi(2);
    let f2 = (1.0 - f1).powi(2);
    let q2 = (1.0 / q).powi(2);

    let n = f2.powi(2) + (q2 * pr * f1).powi(2) + (f2 * f1 * pr.powi(2) * q2) + (f2 * f1 * q2);
    let d = (f2 + q2 * f1).powi(2);

    let p_out = if p >= 1.0 {
        (n / d).sqrt()
    } else {
        (d / n).sqrt()
    };

    to_decibel(p_out)
}

fn plot_high_shelf(
    range: impl Iterator<Item = Frequency> + 'static,
    frequency: Frequency,
    gain: Gain,
) -> Box<dyn Iterator<Item = (Frequency, Gain)>> {
    Box::new(range.map(move |f| (f, calc_high_shelf_gain(f, frequency, gain))))
}

fn calc_high_shelf_gain(f: Frequency, frequency: Frequency, gain: Gain) -> Gain {
    let p = to_power(gain);

    let f0 = frequency / f;
    let f1 = f0.powi(2);
    let f2 = (1.0 - f1).powi(2);

    let d = (f2 + 2.0 * f1).powi(2);
    let p_out = if p >= 1.0 {
        let f3 = (p - f1).powi(2);
        let n = (f3 * f2) + (4.0 * p * f1 * f1) + (2.0 * p * f1 * f2) + (2.0 * f1 * f3);
        (n / d).sqrt()
    } else {
        let pr = to_pr(p);
        let f3 = (pr - f1).powi(2);
        let n = (f2 * f3) + (4.0 * pr * f1 * f1) + (2.0 * f1 * f3) + (2.0 * pr * f1 * f2);
        (d / n).sqrt()
    };

    to_decibel(p_out)
}

fn plot_low_shelf(
    range: impl Iterator<Item = Frequency> + 'static,
    frequency: Frequency,
    gain: Gain,
) -> Box<dyn Iterator<Item = (Frequency, Gain)>> {
    Box::new(range.map(move |f| (f, calc_low_shelf_gain(f, frequency, gain))))
}

fn calc_low_shelf_gain(f: Frequency, frequency: Frequency, gain: Gain) -> Gain {
    let p = to_power(gain);

    let f0 = frequency / f;
    let f1 = f0.powi(2);
    let f2 = (1.0 - f1).powi(2);

    let d = f2 + 2.0 * f1;
    let p_out = if p >= 1.0 {
        let n = (1.0 - p * f1).powi(2) + (2.0 * p) * f1;
        (n / d).sqrt()
    } else {
        let pr = to_pr(p);
        let n = (1.0 - pr * f1).powi(2) + (2.0 * pr) * f1;
        (d / n).sqrt()
    };

    to_decibel(p_out)
}

fn plot_high_pass(
    range: impl Iterator<Item = Frequency> + 'static,
    frequency: Frequency,
    slope: Slope,
) -> Box<dyn Iterator<Item = (Frequency, Gain)>> {
    Box::new(range.map(move |f| (f, calc_high_pass_gain(f, frequency, slope))))
}

fn calc_high_pass_gain(f: Frequency, frequency: Frequency, slope: Slope) -> Gain {
    let f0 = frequency / f;
    let f1 = f0.powi(2);
    let f2 = f1.powi(2);
    let mut d = 1.0;

    let order = slope / 6;
    let ord_off = if order == 0 { 1 } else { order };

    for k in 0..(order + 1) / 2 {
        let a = COEFFS[ord_off - 1][k];
        let b = COEFFS[ord_off - 1][k + 3];
        d *= 1.0 + (a.powi(2) - 2.0 * b) * f1 + b.powi(2) * f2;
    }

    let p_out = (1.0 / d).sqrt();

    to_decibel(p_out)
}

fn plot_low_pass(
    range: impl Iterator<Item = Frequency> + 'static,
    frequency: Frequency,
    slope: Slope,
) -> Box<dyn Iterator<Item = (Frequency, Gain)>> {
    Box::new(range.map(move |f| (f, calc_low_pass_gain(f, frequency, slope))))
}

fn calc_low_pass_gain(f: Frequency, frequency: Frequency, slope: Slope) -> Gain {
    let f0 = frequency / f;
    let f1 = (1.0 / f0).powi(2);
    let f2 = f1.powi(2);
    let mut d = 1.0;

    let order = slope / 6;
    let ord_off = if order == 0 { 1 } else { order };

    for k in 0..(order + 1) / 2 {
        let a = COEFFS[ord_off - 1][k];
        let b = COEFFS[ord_off - 1][k + 3];
        d *= 1.0 + (a.powi(2) - 2.0 * b) * f1 + b.powi(2) * f2;
    }

    let p_out = (1.0 / d).sqrt();

    to_decibel(p_out)
}

fn to_power(gain: f64) -> f64 {
    10f64.powf(gain / 20.0)
}

fn to_pr(power: f64) -> f64 {
    if power >= 1.0 {
        power
    } else {
        1.0 / power
    }
}

fn to_decibel(power: f64) -> f64 {
    20.0 * power.log10()
}

fn merge_all(
    band_curves: impl Iterator<Item = (Box<dyn Iterator<Item = (Frequency, Gain)>>, Active)>,
) -> Option<Box<dyn Iterator<Item = (Frequency, Gain)>>> {
    band_curves.fold(None, |a, b| {
        if let Some(a) = a {
            if b.1 {
                Some(merge(a, b.0))
            } else {
                Some(a)
            }
        } else if b.1 {
            Some(b.0)
        } else {
            None
        }
    })
}

fn merge(
    a: Box<dyn std::iter::Iterator<Item = (f64, f64)>>,
    b: Box<dyn std::iter::Iterator<Item = (f64, f64)>>,
) -> Box<dyn std::iter::Iterator<Item = (f64, f64)>> {
    Box::new(
        a.into_iter()
            .zip(b.into_iter())
            .map(|((f, g1), (_, g2))| (f, g1 + g2)),
    )
}

fn all_to_x_y(
    fgs: impl Iterator<Item = (Frequency, Gain)>,
    fc: &impl Converter<X, Frequency>,
    gc: &impl Converter<Y, Gain>,
) -> Vec<(X, Y)> {
    fgs.map(|(f, g)| to_x_y(f, g, fc, gc)).collect()
}

fn to_x_y(
    f: Frequency,
    g: Gain,
    fc: &impl Converter<X, Frequency>,
    gc: &impl Converter<Y, Gain>,
) -> (X, Y) {
    let x = fc.convert_back(f);
    let y = gc.convert_back(g);
    (x, y)
}