audio-widgets 0.1.0

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

pub type Frequency = f64;
pub type Gain = f64;
pub type Q = f64;
pub type Slope = usize;
pub type Active = bool;

pub type X = f64;
pub type Y = f64;
pub type Radius = f64;

#[derive(Debug, PartialEq, Clone)]
pub struct EQ {
    pub bands: Vec<(EqBand, Active)>,
    pub min_gain: Gain,
    pub max_gain: Gain,
    pub min_frequency: Frequency,
    pub max_frequency: Frequency,
    pub min_q: Q,
    pub max_q: Q,
    pub active: Active,
}

impl EQ {
    pub fn new(
        bands: Vec<(EqBand, Active)>,
        min_gain: f64,
        max_gain: f64,
        min_frequency: f64,
        max_frequency: f64,
        min_q: f64,
        max_q: f64,
        active: Active,
    ) -> EQ {
        EQ {
            bands,
            min_gain,
            max_gain,
            min_frequency,
            max_frequency,
            min_q,
            max_q,
            active,
        }
    }

    pub fn plot(&self, width: f64, height: f64, invert_y: bool) -> EqGraph {
        plotter::plot_eq(self, width, height, invert_y)
    }

    pub fn calc_major_frequency_grid_markers(&self, width: f64) -> Vec<X> {
        let x_conv = self.x_to_frequency_converter(width);

        let exp_start = self.min_frequency.log10().floor() as i32;
        let exp_end = self.max_frequency.log10().ceil() as i32;
        let mut out = Vec::new();
        for exp in exp_start..exp_end {
            let f = 10f64.powi(exp);
            if self.min_frequency < f && f < self.max_frequency {
                let x = x_conv.convert_back(f);
                out.push(x);
            }
        }

        out
    }

    pub fn calc_minor_frequency_grid_markers(&self, width: f64) -> Vec<X> {
        let x_conv = self.x_to_frequency_converter(width);

        let exp_start = self.min_frequency.log10().floor() as i32;
        let exp_end = self.max_frequency.log10().ceil() as i32;
        let mut out = Vec::new();
        for exp in exp_start..exp_end {
            for factor in 2..10 {
                let f = factor as f64 * 10f64.powi(exp);
                if self.min_frequency < f && f < self.max_frequency {
                    let x = x_conv.convert_back(f);
                    out.push(x);
                }
            }
        }

        out
    }

    pub fn update(&mut self, index: usize, change: Parameter) {
        if index >= self.bands.len() {
            return;
        }

        let band = self.bands[index].to_owned();
        if let Some(new_band) = update_band(band.clone(), change) {
            self.bands
                .splice(index..index + 1, std::iter::once(new_band));
        }
    }

    pub fn x_to_frequency_converter(
        &self,
        width: f64,
    ) -> (LinearScale<f64>, LogarithmicScale<f64>) {
        let x_scale = LinearScale::new(0.0, width as f64);
        let freq_scale = LogarithmicScale::new(self.min_frequency, self.max_frequency);
        (x_scale, freq_scale)
    }

    pub fn y_to_gain_converter(
        &self,
        height: f64,
        inverted: bool,
    ) -> (LinearScale<f64>, LinearScale<f64>) {
        let y_scale = if inverted {
            LinearScale::inverted(0.0, height as f64)
        } else {
            LinearScale::new(0.0, height as f64)
        };
        let gain_scale = LinearScale::new(self.min_gain, self.max_gain);
        (y_scale, gain_scale)
    }

    pub fn q_to_radius_converter(
        &self,
        width: f64,
    ) -> (LogarithmicScale<f64>, LogarithmicScale<f64>) {
        let q_scale = LogarithmicScale::new(self.min_q, self.max_q);
        let radius_scale = LogarithmicScale::inverted(width / 60.0, width / 15.0);
        (q_scale, radius_scale)
    }
}

impl Default for EQ {
    fn default() -> Self {
        let mut bands = Vec::new();
        bands.push((
            EqBand::HighPass {
                frequency: 100.0,
                slope: 12,
            },
            true,
        ));
        bands.push((
            EqBand::Bell {
                frequency: 400.0,
                gain: 0.0,
                q: 1.0,
            },
            true,
        ));
        bands.push((
            EqBand::Bell {
                frequency: 1_000.0,
                gain: 0.0,
                q: 1.0,
            },
            true,
        ));
        bands.push((
            EqBand::HighShelf {
                frequency: 4_000.0,
                gain: 0.0,
            },
            true,
        ));

        let min_gain = -12.0;
        let max_gain = 12.0;
        let min_frequency = 20.0;
        let max_frequency = 24_000.0;
        let min_q = 0.1;
        let max_q = 100.0;
        let active = true;

        EQ {
            bands,
            min_gain,
            max_gain,
            min_frequency,
            max_frequency,
            min_q,
            max_q,
            active,
        }
    }
}

#[derive(Debug, PartialEq, Clone)]
pub enum EqBand {
    Bell {
        frequency: Frequency,
        gain: Gain,
        q: Q,
    },
    HighShelf {
        frequency: Frequency,
        gain: Gain,
    },
    LowShelf {
        frequency: Frequency,
        gain: Gain,
    },
    HighPass {
        frequency: Frequency,
        slope: Slope,
    },
    LowPass {
        frequency: Frequency,
        slope: Slope,
    },
}

impl EqBand {
    pub fn plot(
        &self,
        range: impl Iterator<Item = Frequency> + 'static,
    ) -> Box<dyn Iterator<Item = (Frequency, Gain)>> {
        plotter::plot(self, range)
    }

    pub fn frequency(&self) -> Frequency {
        match self {
            EqBand::Bell { frequency, .. } => *frequency,
            EqBand::HighShelf { frequency, .. } => *frequency,
            EqBand::LowShelf { frequency, .. } => *frequency,
            EqBand::HighPass { frequency, .. } => *frequency,
            EqBand::LowPass { frequency, .. } => *frequency,
        }
    }

    pub fn gain(&self) -> Option<Gain> {
        match self {
            EqBand::Bell { gain, .. } => Some(*gain),
            EqBand::HighShelf { gain, .. } => Some(*gain),
            EqBand::LowShelf { gain, .. } => Some(*gain),
            EqBand::HighPass { .. } => None,
            EqBand::LowPass { .. } => None,
        }
    }

    pub fn q(&self) -> Option<Q> {
        if let EqBand::Bell { q, .. } = self {
            Some(*q)
        } else {
            None
        }
    }
}

#[derive(Debug, Clone, PartialEq)]
pub enum Parameter {
    Frequency(f64),
    Gain(f64),
    Q(f64),
    Slope(usize),
    Active(bool),
}

pub struct EqGraph {
    pub band_curves: Vec<(Vec<(X, Y)>, Active)>,
    pub sum: Vec<(X, Y)>,
}

fn update_band(band: (EqBand, bool), change: Parameter) -> Option<(EqBand, bool)> {
    match (band, change) {
        ((EqBand::Bell { gain, q, .. }, active), Parameter::Frequency(frequency)) => {
            Some((EqBand::Bell { frequency, gain, q }, active))
        }
        ((EqBand::Bell { frequency, q, .. }, active), Parameter::Gain(gain)) => {
            Some((EqBand::Bell { frequency, gain, q }, active))
        }
        (
            (
                EqBand::Bell {
                    frequency, gain, ..
                },
                active,
            ),
            Parameter::Q(q),
        ) => Some((EqBand::Bell { frequency, gain, q }, active)),
        ((EqBand::HighShelf { gain, .. }, active), Parameter::Frequency(frequency)) => {
            Some((EqBand::HighShelf { frequency, gain }, active))
        }
        ((EqBand::HighShelf { frequency, .. }, active), Parameter::Gain(gain)) => {
            Some((EqBand::HighShelf { frequency, gain }, active))
        }
        ((EqBand::LowShelf { gain, .. }, active), Parameter::Frequency(frequency)) => {
            Some((EqBand::LowShelf { frequency, gain }, active))
        }
        ((EqBand::LowShelf { frequency, .. }, active), Parameter::Gain(gain)) => {
            Some((EqBand::LowShelf { frequency, gain }, active))
        }
        ((EqBand::HighPass { slope, .. }, active), Parameter::Frequency(frequency)) => {
            Some((EqBand::HighPass { frequency, slope }, active))
        }
        ((EqBand::HighPass { frequency, .. }, active), Parameter::Slope(slope)) => {
            Some((EqBand::HighPass { frequency, slope }, active))
        }
        ((EqBand::LowPass { slope, .. }, active), Parameter::Frequency(frequency)) => {
            Some((EqBand::LowPass { frequency, slope }, active))
        }
        ((EqBand::LowPass { frequency, .. }, active), Parameter::Slope(slope)) => {
            Some((EqBand::LowPass { frequency, slope }, active))
        }
        ((band, _), Parameter::Active(active)) => Some((band, active)),
        _ => None,
    }
}