shady_audio/bar_processor/
mod.rs

1mod config;
2
3use std::{num::NonZero, ops::Range};
4
5use config::BarDistribution;
6pub use config::{BarProcessorConfig, InterpolationVariant};
7use cpal::SampleRate;
8use realfft::num_complex::Complex32;
9use tracing::debug;
10
11use crate::{
12    interpolation::{
13        CubicSplineInterpolation, Interpolater, InterpolationInner, LinearInterpolation,
14        NothingInterpolation, SupportingPoint,
15    },
16    SampleProcessor, MAX_HUMAN_FREQUENCY, MIN_HUMAN_FREQUENCY,
17};
18
19type ChannelInterpolator = InterpolatorCtx;
20type ChannelBars = Box<[f32]>;
21
22struct InterpolatorCtx {
23    interpolator: Box<dyn Interpolater>,
24    supporting_point_fft_ranges: Box<[Range<usize>]>,
25
26    normalize_factor: f32,
27    sensitivity: f32,
28
29    prev: Box<[f32]>,
30    peak: Box<[f32]>,
31    fall: Box<[f32]>,
32    mem: Box<[f32]>,
33}
34
35impl InterpolatorCtx {
36    fn new(config: &BarProcessorConfig, sample_rate: SampleRate, fft_size: usize) -> Self {
37        let (interpolator, supporting_point_fft_ranges) =
38            Self::new_interpolation_data(config, sample_rate, fft_size);
39
40        let peak = vec![0f32; u16::from(config.amount_bars) as usize].into_boxed_slice();
41        let fall = peak.clone();
42        let mem = peak.clone();
43        let prev = peak.clone();
44
45        Self {
46            interpolator,
47            supporting_point_fft_ranges,
48            normalize_factor: 1.,
49            sensitivity: config.sensitivity,
50
51            prev,
52            peak,
53            fall,
54            mem,
55        }
56    }
57
58    /// Calculates the indexes for the fft output on how to distribute them to each bar.
59    fn new_interpolation_data(
60        config: &BarProcessorConfig,
61        sample_rate: SampleRate,
62        sample_len: usize,
63    ) -> (Box<dyn Interpolater>, Box<[Range<usize>]>) {
64        // == preparations
65        let weights = (0..config.amount_bars.get())
66            .map(|index| exp_fun((index + 1) as f32 / (config.amount_bars.get() + 1) as f32))
67            .collect::<Vec<f32>>();
68        debug!("Weights: {:?}", weights);
69
70        let amount_bins = {
71            let freq_resolution = sample_rate.0 as f32 / sample_len as f32;
72            debug!("Freq resolution: {}", freq_resolution);
73
74            // the relevant index range of the fft output which we should use for the bars
75            let bin_range = Range {
76                start: ((u16::from(config.freq_range.start) as f32 / freq_resolution) as usize)
77                    .max(1),
78                end: (u16::from(config.freq_range.end) as f32 / freq_resolution).ceil() as usize,
79            };
80            debug!("Bin range: {:?}", bin_range);
81            bin_range.len()
82        };
83        debug!("Available bins: {}", amount_bins);
84
85        // == supporting points
86        let (supporting_points, supporting_point_fft_ranges) = {
87            let mut supporting_points = Vec::new();
88            let mut supporting_point_fft_ranges = Vec::new();
89
90            let mut prev_fft_range = 0..0;
91            for (bar_idx, weight) in weights.iter().enumerate() {
92                let end =
93                    ((weight / MAX_HUMAN_FREQUENCY as f32) * amount_bins as f32).ceil() as usize;
94
95                let new_fft_range = prev_fft_range.end..end;
96                let is_supporting_point =
97                    new_fft_range != prev_fft_range && !new_fft_range.is_empty();
98                if is_supporting_point {
99                    supporting_points.push(SupportingPoint { x: bar_idx, y: 0. });
100
101                    supporting_point_fft_ranges.push(new_fft_range.clone());
102                }
103
104                prev_fft_range = new_fft_range;
105            }
106
107            // re-adjust the supporting points if needed
108            match config.bar_distribution {
109                BarDistribution::Uniform => {
110                    let step = config.amount_bars.get() as f32 / supporting_points.len() as f32;
111                    let supporting_points_len = supporting_points.len();
112                    for (idx, supporting_point) in supporting_points
113                        [..supporting_points_len.saturating_sub(1)]
114                        .iter_mut()
115                        .enumerate()
116                    {
117                        supporting_point.x = (idx as f32 * step) as usize;
118                    }
119                }
120                BarDistribution::Natural => {}
121            }
122
123            (supporting_points, supporting_point_fft_ranges)
124        };
125
126        // create the interpolator
127        let interpolator: Box<dyn Interpolater> = match config.interpolation {
128            InterpolationVariant::None => NothingInterpolation::boxed(supporting_points),
129            InterpolationVariant::Linear => LinearInterpolation::boxed(supporting_points),
130            InterpolationVariant::CubicSpline => CubicSplineInterpolation::boxed(supporting_points),
131        };
132
133        (interpolator, supporting_point_fft_ranges.into_boxed_slice())
134    }
135
136    fn update_supporting_points(&mut self, fft_out: &[Complex32]) {
137        let mut overshoot = false;
138        let mut is_silent = true;
139
140        let amount_bars = self.amount_bars();
141
142        for (bar_idx, (supporting_point, fft_range)) in self
143            .interpolator
144            .supporting_points_mut()
145            .zip(self.supporting_point_fft_ranges.iter_mut())
146            .enumerate()
147        {
148            let x = supporting_point.x;
149            let prev_magnitude = supporting_point.y;
150            let mut next_magnitude = {
151                let mut raw_bar_val = fft_out[fft_range.clone()]
152                    .iter()
153                    .map(|out| {
154                        let mag = out.norm_sqr();
155                        if mag > 0. {
156                            is_silent = false;
157                        }
158                        mag
159                    })
160                    .max_by(|a, b| a.total_cmp(b))
161                    .unwrap();
162
163                raw_bar_val = raw_bar_val.sqrt();
164
165                raw_bar_val
166                    * self.normalize_factor
167                    * 10f32.powf((x as f32 / amount_bars as f32) - 1.)
168            };
169
170            debug_assert!(!prev_magnitude.is_nan());
171            debug_assert!(!next_magnitude.is_nan());
172
173            // shoutout to `cava` for their computation on how to make the falling look smooth.
174            if next_magnitude < self.prev[bar_idx] {
175                let grav_mod = 1f32.powf(2.5) * 1.54 / self.sensitivity;
176                next_magnitude = self.peak[bar_idx]
177                    * (1. - (self.fall[bar_idx] * self.fall[bar_idx] * grav_mod));
178
179                if next_magnitude < 0. {
180                    next_magnitude = 0.;
181                }
182                self.fall[bar_idx] += 0.028;
183            } else {
184                self.peak[bar_idx] = next_magnitude;
185                self.fall[bar_idx] = 0.0;
186            }
187            self.prev[bar_idx] = next_magnitude;
188
189            supporting_point.y = self.mem[bar_idx] * 0.77 + next_magnitude;
190            self.mem[bar_idx] = supporting_point.y;
191
192            if supporting_point.y > 1. {
193                overshoot = true;
194            }
195        }
196
197        if overshoot {
198            self.normalize_factor *= 0.98;
199        } else if !is_silent {
200            self.normalize_factor *= 1.002;
201        }
202    }
203
204    fn amount_bars(&self) -> usize {
205        self.prev.len()
206    }
207}
208
209/// The struct which computates the bar values of the samples of the fetcher.
210pub struct BarProcessor {
211    bar_values: Box<[Box<[f32]>]>,
212    channels: Box<[InterpolatorCtx]>,
213
214    config: BarProcessorConfig,
215    sample_rate: SampleRate,
216    sample_len: usize,
217}
218
219impl BarProcessor {
220    /// Creates a new instance.
221    ///
222    /// See the examples of this crate to see it's usage.
223    pub fn new(processor: &SampleProcessor, config: BarProcessorConfig) -> Self {
224        let sample_rate = processor.sample_rate();
225        let sample_len = processor.fft_size();
226        let amount_channels = processor.amount_channels();
227
228        let (channels, bar_values) =
229            Self::get_channels_and_bar_values(&config, amount_channels, sample_rate, sample_len);
230
231        Self {
232            config,
233            channels,
234            bar_values,
235
236            sample_rate,
237            sample_len,
238        }
239    }
240
241    /// Returns the bar values for each channel.
242    ///
243    /// If you access the returned value like this: `bar_processor.process_bars(&processor)[i][j]` then this would mean:
244    /// You are accessing the `j`th bar value of the `i`th audio channel.
245    pub fn process_bars(&mut self, processor: &SampleProcessor) -> &[Box<[f32]>] {
246        for ((channel_idx, channel), fft_ctx) in self
247            .channels
248            .iter_mut()
249            .enumerate()
250            .zip(processor.fft_out().iter())
251        {
252            channel.update_supporting_points(&fft_ctx.fft_out);
253
254            channel
255                .interpolator
256                .interpolate(&mut self.bar_values[channel_idx]);
257        }
258
259        &self.bar_values
260    }
261
262    pub fn config(&self) -> &BarProcessorConfig {
263        &self.config
264    }
265
266    /// Change the amount of bars which should be returned.
267    ///
268    /// # Example
269    /// ```rust
270    /// use shady_audio::{SampleProcessor, BarProcessor, BarProcessorConfig, fetcher::DummyFetcher};
271    ///
272    /// let mut sample_processor = SampleProcessor::new(DummyFetcher::new(1));
273    /// let mut bar_processor = BarProcessor::new(
274    ///     &sample_processor,
275    ///     BarProcessorConfig {
276    ///         amount_bars: std::num::NonZero::new(10).unwrap(),
277    ///         ..Default::default()
278    ///     }
279    /// );
280    /// sample_processor.process_next_samples();
281    ///
282    /// let bars = bar_processor.process_bars(&sample_processor);
283    /// // the dummy just has one channel
284    /// assert_eq!(bars.len(), 1);
285    /// // but it should have ten bars
286    /// assert_eq!(bars[0].len(), 10);
287    ///
288    /// // change the amount of bars
289    /// bar_processor.set_amount_bars(std::num::NonZero::new(20).unwrap());
290    /// let bars = bar_processor.process_bars(&sample_processor);
291    /// assert_eq!(bars.len(), 1);
292    /// assert_eq!(bars[0].len(), 20);
293    /// ```
294    pub fn set_amount_bars(&mut self, amount_bars: NonZero<u16>) {
295        self.config.amount_bars = amount_bars;
296        let amount_channels = self.channels.len();
297
298        let (channels, bar_values) = Self::get_channels_and_bar_values(
299            &self.config,
300            amount_channels,
301            self.sample_rate,
302            self.sample_len,
303        );
304
305        self.channels = channels;
306        self.bar_values = bar_values;
307    }
308
309    fn get_channels_and_bar_values(
310        config: &BarProcessorConfig,
311        amount_channels: usize,
312        sample_rate: SampleRate,
313        sample_len: usize,
314    ) -> (Box<[ChannelInterpolator]>, Box<[ChannelBars]>) {
315        let mut channels = Vec::with_capacity(amount_channels);
316        let bar_values =
317            vec![vec![0f32; config.amount_bars.get() as usize].into_boxed_slice(); amount_channels];
318
319        for _ in 0..amount_channels {
320            channels.push(InterpolatorCtx::new(config, sample_rate, sample_len));
321        }
322
323        (channels.into_boxed_slice(), bar_values.into_boxed_slice())
324    }
325}
326
327fn exp_fun(x: f32) -> f32 {
328    debug_assert!(0. <= x);
329    debug_assert!(x <= 1.);
330
331    let max_mel_value = mel(MAX_HUMAN_FREQUENCY as f32);
332    let min_mel_value = mel(MIN_HUMAN_FREQUENCY as f32);
333
334    // map [0, 1] => [min-mel-value, max-mel-value]
335    let mapped_x = x * (max_mel_value - min_mel_value) + min_mel_value;
336    inv_mel(mapped_x)
337}
338
339// https://en.wikipedia.org/wiki/Mel_scale
340fn mel(x: f32) -> f32 {
341    debug_assert!(MIN_HUMAN_FREQUENCY as f32 <= x);
342    debug_assert!(x <= MAX_HUMAN_FREQUENCY as f32);
343
344    2595. * (1. + x / 700.).log10()
345}
346
347fn inv_mel(x: f32) -> f32 {
348    let min_mel_value = mel(MIN_HUMAN_FREQUENCY as f32);
349    let max_mel_value = mel(MAX_HUMAN_FREQUENCY as f32);
350
351    debug_assert!(min_mel_value <= x);
352    debug_assert!(x <= max_mel_value);
353
354    700. * (10f32.powf(x / 2595.) - 1.)
355}