shady_audio/
sample_processor.rs

1use cpal::SampleRate;
2use realfft::{num_complex::Complex32, RealFftPlanner};
3
4use crate::fetcher::Fetcher;
5
6/// Prepares the samples of the fetcher for the [crate::BarProcessor].
7pub struct SampleProcessor {
8    planner: RealFftPlanner<f32>,
9    hann_window: Box<[f32]>,
10
11    fft_in_raw: Box<[f32]>,
12
13    channels: Box<[FftContext]>,
14
15    fft_size: usize,
16    fetcher: Box<dyn Fetcher>,
17}
18
19impl SampleProcessor {
20    /// Creates a new instance with the given fetcher where the audio samples are fetched from.
21    pub fn new(fetcher: Box<dyn Fetcher>) -> Self {
22        let fft_size = {
23            let sample_rate = fetcher.sample_rate().0;
24            let factor = if sample_rate < 8_125 {
25                1
26            } else if sample_rate <= 16_250 {
27                2
28            } else if sample_rate <= 32_500 {
29                4
30            } else if sample_rate <= 75_000 {
31                8
32            } else if sample_rate <= 150_000 {
33                16
34            } else if sample_rate <= 300_000 {
35                32
36            } else {
37                64
38            };
39
40            factor * 128
41        };
42        let fft_out_size = fft_size / 2 + 1;
43
44        let hann_window = apodize::hanning_iter(fft_size)
45            .map(|val| val as f32)
46            .collect::<Vec<f32>>()
47            .into_boxed_slice();
48
49        let fft_in_raw = vec![0.; fft_size].into_boxed_slice();
50
51        let channels = vec![FftContext::new(fft_size, fft_out_size); fetcher.channels() as usize]
52            .into_boxed_slice();
53
54        Self {
55            planner: RealFftPlanner::new(),
56            hann_window,
57            fft_in_raw,
58
59            channels,
60
61            fft_size,
62            fetcher,
63        }
64    }
65
66    /// Tell the processor to take some samples of the fetcher and prepare them
67    /// for the [crate::BarProcessor]s.
68    pub fn process_next_samples(&mut self) {
69        self.fetcher.fetch_samples(&mut self.fft_in_raw);
70
71        let amount_channels = self.fetcher.channels() as usize;
72        for (sample_idx, samples) in self.fft_in_raw.chunks_exact(amount_channels).enumerate() {
73            for (channel_idx, channel) in self.channels.iter_mut().enumerate() {
74                channel.fft_in[sample_idx] = samples[channel_idx] * self.hann_window[sample_idx];
75            }
76        }
77
78        let fft = self.planner.plan_fft_forward(self.fft_size);
79        for channel in self.channels.iter_mut() {
80            fft.process_with_scratch(
81                channel.fft_in.as_mut(),
82                channel.fft_out.as_mut(),
83                channel.scratch_buffer.as_mut(),
84            )
85            .unwrap();
86        }
87    }
88}
89
90impl SampleProcessor {
91    pub(crate) fn fft_size(&self) -> usize {
92        self.fft_size
93    }
94
95    pub(crate) fn fft_out(&self) -> &[FftContext] {
96        &self.channels
97    }
98
99    pub(crate) fn sample_rate(&self) -> SampleRate {
100        self.fetcher.sample_rate()
101    }
102
103    pub(crate) fn amount_channels(&self) -> usize {
104        self.channels.len()
105    }
106}
107
108#[derive(Debug, Clone)]
109pub struct FftContext {
110    fft_in: Box<[f32]>,
111    pub fft_out: Box<[Complex32]>,
112    scratch_buffer: Box<[Complex32]>,
113}
114
115impl FftContext {
116    fn new(fft_size: usize, fft_out_size: usize) -> Self {
117        let fft_in = vec![0.; fft_size].into_boxed_slice();
118        let fft_out = vec![Complex32::ZERO; fft_out_size].into_boxed_slice();
119        let scratch_buffer = fft_out.clone();
120
121        Self {
122            fft_in,
123            fft_out,
124            scratch_buffer,
125        }
126    }
127}