shady_audio/
sample_processor.rs1use cpal::SampleRate;
2use realfft::{num_complex::Complex32, RealFftPlanner};
3
4use crate::fetcher::Fetcher;
5
6pub 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 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 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}