audio_widgets/eq/
plotter.rs

1use crate::eq::common::*;
2use scales::prelude::*;
3
4const COEFFS: [[f64; 6]; 7] = [
5    [1f64, 0f64, 0f64, 0f64, 0f64, 0f64],
6    [1.4142f64, 0f64, 0f64, 1f64, 0f64, 0f64],
7    [1f64, 1f64, 0f64, 0f64, 1f64, 0f64],
8    [1.8478f64, 0.7654f64, 0f64, 1f64, 1f64, 0f64],
9    [1f64, 1.6180f64, 0.6180f64, 0f64, 1f64, 1f64],
10    [1.3617f64, 1.3617f64, 0f64, 0.6180f64, 0.6180f64, 0f64],
11    [1.4142f64, 1.4142f64, 0f64, 1f64, 1f64, 0f64],
12];
13
14pub fn plot_eq(eq: &EQ, width: f64, height: f64, invert_y: bool) -> EqGraph {
15    let x_conv = eq.x_to_frequency_converter(width);
16    let y_conv = eq.y_to_gain_converter(height, invert_y);
17
18    let xc = x_conv.clone();
19
20    let fs = (0..width as usize).map(move |x| xc.convert(x as f64));
21
22    let band_curves = eq.bands.iter().map(|(band, a)| (band.plot(fs.clone()), *a));
23    let sum = merge_all(band_curves.clone());
24
25    let band_curves: Vec<(Vec<(X, Y)>, Active)> = band_curves
26        .map(|(curve, active)| (all_to_x_y(curve, &x_conv, &y_conv), active))
27        .collect();
28
29    let sum: Vec<(X, Y)> = if let Some(sum) = sum {
30        all_to_x_y(sum, &x_conv, &y_conv)
31    } else {
32        Vec::new()
33    };
34
35    EqGraph { band_curves, sum }
36}
37
38pub fn plot(
39    eq_band: &EqBand,
40    range: impl Iterator<Item = Frequency> + 'static,
41) -> Box<dyn Iterator<Item = (Frequency, Gain)>> {
42    match eq_band {
43        EqBand::Bell { frequency, gain, q } => plot_bell(range, *frequency, *gain, *q),
44        EqBand::HighShelf { frequency, gain } => plot_high_shelf(range, *frequency, *gain),
45        EqBand::LowShelf { frequency, gain } => plot_low_shelf(range, *frequency, *gain),
46        EqBand::HighPass { frequency, slope } => plot_high_pass(range, *frequency, *slope),
47        EqBand::LowPass { frequency, slope } => plot_low_pass(range, *frequency, *slope),
48    }
49}
50
51fn plot_bell(
52    range: impl Iterator<Item = Frequency> + 'static,
53    frequency: Frequency,
54    gain: Gain,
55    q: Q,
56) -> Box<dyn Iterator<Item = (Frequency, Gain)>> {
57    Box::new(range.map(move |f| (f, calc_bell_gain(f, frequency, gain, q))))
58}
59
60fn calc_bell_gain(f: Frequency, frequency: Frequency, gain: Gain, q: Q) -> Gain {
61    let p = to_power(gain);
62    let pr = to_pr(p);
63
64    let f0 = frequency / f;
65    let f1 = f0.powi(2);
66    let f2 = (1.0 - f1).powi(2);
67    let q2 = (1.0 / q).powi(2);
68
69    let n = f2.powi(2) + (q2 * pr * f1).powi(2) + (f2 * f1 * pr.powi(2) * q2) + (f2 * f1 * q2);
70    let d = (f2 + q2 * f1).powi(2);
71
72    let p_out = if p >= 1.0 {
73        (n / d).sqrt()
74    } else {
75        (d / n).sqrt()
76    };
77
78    to_decibel(p_out)
79}
80
81fn plot_high_shelf(
82    range: impl Iterator<Item = Frequency> + 'static,
83    frequency: Frequency,
84    gain: Gain,
85) -> Box<dyn Iterator<Item = (Frequency, Gain)>> {
86    Box::new(range.map(move |f| (f, calc_high_shelf_gain(f, frequency, gain))))
87}
88
89fn calc_high_shelf_gain(f: Frequency, frequency: Frequency, gain: Gain) -> Gain {
90    let p = to_power(gain);
91
92    let f0 = frequency / f;
93    let f1 = f0.powi(2);
94    let f2 = (1.0 - f1).powi(2);
95
96    let d = (f2 + 2.0 * f1).powi(2);
97    let p_out = if p >= 1.0 {
98        let f3 = (p - f1).powi(2);
99        let n = (f3 * f2) + (4.0 * p * f1 * f1) + (2.0 * p * f1 * f2) + (2.0 * f1 * f3);
100        (n / d).sqrt()
101    } else {
102        let pr = to_pr(p);
103        let f3 = (pr - f1).powi(2);
104        let n = (f2 * f3) + (4.0 * pr * f1 * f1) + (2.0 * f1 * f3) + (2.0 * pr * f1 * f2);
105        (d / n).sqrt()
106    };
107
108    to_decibel(p_out)
109}
110
111fn plot_low_shelf(
112    range: impl Iterator<Item = Frequency> + 'static,
113    frequency: Frequency,
114    gain: Gain,
115) -> Box<dyn Iterator<Item = (Frequency, Gain)>> {
116    Box::new(range.map(move |f| (f, calc_low_shelf_gain(f, frequency, gain))))
117}
118
119fn calc_low_shelf_gain(f: Frequency, frequency: Frequency, gain: Gain) -> Gain {
120    let p = to_power(gain);
121
122    let f0 = frequency / f;
123    let f1 = f0.powi(2);
124    let f2 = (1.0 - f1).powi(2);
125
126    let d = f2 + 2.0 * f1;
127    let p_out = if p >= 1.0 {
128        let n = (1.0 - p * f1).powi(2) + (2.0 * p) * f1;
129        (n / d).sqrt()
130    } else {
131        let pr = to_pr(p);
132        let n = (1.0 - pr * f1).powi(2) + (2.0 * pr) * f1;
133        (d / n).sqrt()
134    };
135
136    to_decibel(p_out)
137}
138
139fn plot_high_pass(
140    range: impl Iterator<Item = Frequency> + 'static,
141    frequency: Frequency,
142    slope: Slope,
143) -> Box<dyn Iterator<Item = (Frequency, Gain)>> {
144    Box::new(range.map(move |f| (f, calc_high_pass_gain(f, frequency, slope))))
145}
146
147fn calc_high_pass_gain(f: Frequency, frequency: Frequency, slope: Slope) -> Gain {
148    let f0 = frequency / f;
149    let f1 = f0.powi(2);
150    let f2 = f1.powi(2);
151    let mut d = 1.0;
152
153    let order = slope / 6;
154    let ord_off = if order == 0 { 1 } else { order };
155
156    for k in 0..(order + 1) / 2 {
157        let a = COEFFS[ord_off - 1][k];
158        let b = COEFFS[ord_off - 1][k + 3];
159        d *= 1.0 + (a.powi(2) - 2.0 * b) * f1 + b.powi(2) * f2;
160    }
161
162    let p_out = (1.0 / d).sqrt();
163
164    to_decibel(p_out)
165}
166
167fn plot_low_pass(
168    range: impl Iterator<Item = Frequency> + 'static,
169    frequency: Frequency,
170    slope: Slope,
171) -> Box<dyn Iterator<Item = (Frequency, Gain)>> {
172    Box::new(range.map(move |f| (f, calc_low_pass_gain(f, frequency, slope))))
173}
174
175fn calc_low_pass_gain(f: Frequency, frequency: Frequency, slope: Slope) -> Gain {
176    let f0 = frequency / f;
177    let f1 = (1.0 / f0).powi(2);
178    let f2 = f1.powi(2);
179    let mut d = 1.0;
180
181    let order = slope / 6;
182    let ord_off = if order == 0 { 1 } else { order };
183
184    for k in 0..(order + 1) / 2 {
185        let a = COEFFS[ord_off - 1][k];
186        let b = COEFFS[ord_off - 1][k + 3];
187        d *= 1.0 + (a.powi(2) - 2.0 * b) * f1 + b.powi(2) * f2;
188    }
189
190    let p_out = (1.0 / d).sqrt();
191
192    to_decibel(p_out)
193}
194
195fn to_power(gain: f64) -> f64 {
196    10f64.powf(gain / 20.0)
197}
198
199fn to_pr(power: f64) -> f64 {
200    if power >= 1.0 {
201        power
202    } else {
203        1.0 / power
204    }
205}
206
207fn to_decibel(power: f64) -> f64 {
208    20.0 * power.log10()
209}
210
211fn merge_all(
212    band_curves: impl Iterator<Item = (Box<dyn Iterator<Item = (Frequency, Gain)>>, Active)>,
213) -> Option<Box<dyn Iterator<Item = (Frequency, Gain)>>> {
214    band_curves.fold(None, |a, b| {
215        if let Some(a) = a {
216            if b.1 {
217                Some(merge(a, b.0))
218            } else {
219                Some(a)
220            }
221        } else if b.1 {
222            Some(b.0)
223        } else {
224            None
225        }
226    })
227}
228
229fn merge(
230    a: Box<dyn std::iter::Iterator<Item = (f64, f64)>>,
231    b: Box<dyn std::iter::Iterator<Item = (f64, f64)>>,
232) -> Box<dyn std::iter::Iterator<Item = (f64, f64)>> {
233    Box::new(
234        a.into_iter()
235            .zip(b.into_iter())
236            .map(|((f, g1), (_, g2))| (f, g1 + g2)),
237    )
238}
239
240fn all_to_x_y(
241    fgs: impl Iterator<Item = (Frequency, Gain)>,
242    fc: &impl Converter<X, Frequency>,
243    gc: &impl Converter<Y, Gain>,
244) -> Vec<(X, Y)> {
245    fgs.map(|(f, g)| to_x_y(f, g, fc, gc)).collect()
246}
247
248fn to_x_y(
249    f: Frequency,
250    g: Gain,
251    fc: &impl Converter<X, Frequency>,
252    gc: &impl Converter<Y, Gain>,
253) -> (X, Y) {
254    let x = fc.convert_back(f);
255    let y = gc.convert_back(g);
256    (x, y)
257}