servo_media_audio/
analyser_node.rs1use std::cmp;
6use std::f32::consts::PI;
7use std::sync::{Arc, OnceLock};
8
9use malloc_size_of_derive::MallocSizeOf;
10use servo_base::generic_channel::GenericCallback;
11
12use crate::audio_node::{
13 AudioNodeEngine, AudioNodeType, BlockInfo, ChannelInfo, ChannelInterpretation,
14};
15use crate::block::{Block, Chunk, FRAMES_PER_BLOCK_USIZE};
16
17#[derive(AudioNodeCommon)]
18pub(crate) struct AnalyserNode {
19 channel_info: ChannelInfo,
20 callback: Arc<OnceLock<GenericCallback<Block>>>,
21}
22
23impl AnalyserNode {
24 pub fn new(callback: Arc<OnceLock<GenericCallback<Block>>>, channel_info: ChannelInfo) -> Self {
25 Self {
26 callback,
27 channel_info,
28 }
29 }
30}
31
32impl AudioNodeEngine for AnalyserNode {
33 fn node_type(&self) -> AudioNodeType {
34 AudioNodeType::AnalyserNode
35 }
36
37 fn process(&mut self, inputs: Chunk, _: &BlockInfo) -> Chunk {
38 debug_assert!(inputs.len() == 1);
39
40 let mut push = inputs.blocks[0].clone();
41 push.mix(1, ChannelInterpretation::Speakers);
42
43 if let Some(callback) = self.callback.get() {
44 let _ = callback.send(push);
45 }
46
47 inputs
49 }
50}
51
52pub const MAX_FFT_SIZE: usize = 32768;
54pub const MAX_BLOCK_COUNT: usize = MAX_FFT_SIZE / FRAMES_PER_BLOCK_USIZE;
55
56#[derive(MallocSizeOf)]
60pub struct AnalysisEngine {
61 fft_size: usize,
63 smoothing_constant: f64,
64 min_decibels: f64,
65 max_decibels: f64,
66 #[ignore_malloc_size_of = "Do not know how"]
69 data: Box<[f32; MAX_FFT_SIZE]>,
70 current_block: usize,
72 fft_computed: bool,
74 blackman_windows: Vec<f32>,
76 smoothed_fft_data: Vec<f32>,
78 computed_fft_data: Vec<f32>,
80 windowed: Vec<f32>,
83}
84
85impl AnalysisEngine {
86 pub fn new(
87 fft_size: usize,
88 smoothing_constant: f64,
89 min_decibels: f64,
90 max_decibels: f64,
91 ) -> Self {
92 debug_assert!((32..=32768).contains(&fft_size));
93 debug_assert!(fft_size & (fft_size - 1) == 0);
95 debug_assert!((0. ..=1.).contains(&smoothing_constant));
96 debug_assert!(max_decibels > min_decibels);
97 Self {
98 fft_size,
99 smoothing_constant,
100 min_decibels,
101 max_decibels,
102 data: Box::new([0.; MAX_FFT_SIZE]),
103 current_block: MAX_BLOCK_COUNT - 1,
104 fft_computed: false,
105 blackman_windows: Vec::with_capacity(fft_size),
106 computed_fft_data: Vec::with_capacity(fft_size / 2),
107 smoothed_fft_data: Vec::with_capacity(fft_size / 2),
108 windowed: Vec::with_capacity(fft_size),
109 }
110 }
111
112 pub fn set_fft_size(&mut self, fft_size: usize) {
113 debug_assert!((32..=32768).contains(&fft_size));
114 debug_assert!(fft_size & (fft_size - 1) == 0);
116 self.fft_size = fft_size;
117 self.fft_computed = false;
118 }
119
120 pub fn get_fft_size(&self) -> usize {
121 self.fft_size
122 }
123
124 pub fn set_smoothing_constant(&mut self, smoothing_constant: f64) {
125 debug_assert!((0. ..=1.).contains(&smoothing_constant));
126 self.smoothing_constant = smoothing_constant;
127 self.fft_computed = false;
128 }
129
130 pub fn get_smoothing_constant(&self) -> f64 {
131 self.smoothing_constant
132 }
133
134 pub fn set_min_decibels(&mut self, min_decibels: f64) {
135 debug_assert!(min_decibels < self.max_decibels);
136 self.min_decibels = min_decibels;
137 }
138
139 pub fn get_min_decibels(&self) -> f64 {
140 self.min_decibels
141 }
142
143 pub fn set_max_decibels(&mut self, max_decibels: f64) {
144 debug_assert!(self.min_decibels < max_decibels);
145 self.max_decibels = max_decibels;
146 }
147
148 pub fn get_max_decibels(&self) -> f64 {
149 self.max_decibels
150 }
151
152 fn advance(&mut self) {
153 self.current_block += 1;
154 if self.current_block >= MAX_BLOCK_COUNT {
155 self.current_block = 0;
156 }
157 }
158
159 fn curent_block_mut(&mut self) -> &mut [f32] {
161 let index = FRAMES_PER_BLOCK_USIZE * self.current_block;
162 &mut self.data[index..(index + FRAMES_PER_BLOCK_USIZE)]
163 }
164
165 fn convert_index(&self, index: usize) -> usize {
168 let offset = self.fft_size - index;
169 let last_element = (1 + self.current_block) * FRAMES_PER_BLOCK_USIZE - 1;
170 if offset > last_element {
171 MAX_FFT_SIZE - offset + last_element
172 } else {
173 last_element - offset
174 }
175 }
176
177 fn advance_index(&self, index: &mut usize) {
179 *index += 1;
180 if *index >= MAX_FFT_SIZE {
181 *index = 0;
182 }
183 }
184
185 pub fn push(&mut self, mut block: Block) {
186 debug_assert!(block.chan_count() == 1);
187 self.advance();
188 if !block.is_silence() {
189 self.curent_block_mut().copy_from_slice(block.data_mut());
190 }
191 self.fft_computed = false;
192 }
193
194 fn compute_blackman_windows(&mut self) {
196 if self.blackman_windows.len() == self.fft_size {
197 return;
198 }
199 const ALPHA: f32 = 0.16;
200 const ALPHA_0: f32 = (1. - ALPHA) / 2.;
201 const ALPHA_1: f32 = 1. / 2.;
202 const ALPHA_2: f32 = ALPHA / 2.;
203 self.blackman_windows.resize(self.fft_size, 0.);
204 let coeff = PI * 2. / self.fft_size as f32;
205 for n in 0..self.fft_size {
206 self.blackman_windows[n] = ALPHA_0 - ALPHA_1 * (coeff * n as f32).cos() +
207 ALPHA_2 * (2. * coeff * n as f32).cos();
208 }
209 }
210
211 fn apply_blackman_window(&mut self) {
212 self.compute_blackman_windows();
213 self.windowed.resize(self.fft_size, 0.);
214
215 let mut data_idx = self.convert_index(0);
216 for n in 0..self.fft_size {
217 self.windowed[n] = self.blackman_windows[n] * self.data[data_idx];
218 self.advance_index(&mut data_idx);
219 }
220 }
221
222 fn compute_fft(&mut self) {
223 if self.fft_computed {
224 return;
225 }
226 self.fft_computed = true;
227 self.apply_blackman_window();
228 self.computed_fft_data.resize(self.fft_size / 2, 0.);
229 self.smoothed_fft_data.resize(self.fft_size / 2, 0.);
230
231 for k in 0..(self.fft_size / 2) {
232 let mut sum_real = 0.;
233 let mut sum_imaginary = 0.;
234 let factor = -2. * PI * k as f32 / self.fft_size as f32;
235 for n in 0..(self.fft_size) {
236 sum_real += self.windowed[n] * (factor * n as f32).cos();
237 sum_imaginary += self.windowed[n] * (factor * n as f32).sin();
238 }
239 let sum_real = sum_real / self.fft_size as f32;
240 let sum_imaginary = sum_imaginary / self.fft_size as f32;
241 let magnitude = (sum_real * sum_real + sum_imaginary * sum_imaginary).sqrt();
242 self.smoothed_fft_data[k] = (self.smoothing_constant * self.smoothed_fft_data[k] as f64 +
243 (1. - self.smoothing_constant) * magnitude as f64)
244 as f32;
245 self.computed_fft_data[k] = 20. * self.smoothed_fft_data[k].log(10.);
246 }
247 }
248
249 pub fn fill_time_domain_data(&self, dest: &mut [f32]) {
250 let mut data_idx = self.convert_index(0);
251 let end = cmp::min(self.fft_size, dest.len());
252 for entry in &mut dest[0..end] {
253 *entry = self.data[data_idx];
254 self.advance_index(&mut data_idx);
255 }
256 }
257
258 pub fn fill_byte_time_domain_data(&self, dest: &mut [u8]) {
259 let mut data_idx = self.convert_index(0);
260 let end = cmp::min(self.fft_size, dest.len());
261 for entry in &mut dest[0..end] {
262 let result = 128. * (1. + self.data[data_idx]);
263 *entry = clamp_255(result);
264 self.advance_index(&mut data_idx)
265 }
266 }
267
268 pub fn fill_frequency_data(&mut self, dest: &mut [f32]) {
269 self.compute_fft();
270 let len = cmp::min(dest.len(), self.computed_fft_data.len());
271 dest[0..len].copy_from_slice(&self.computed_fft_data[0..len]);
272 }
273
274 pub fn fill_byte_frequency_data(&mut self, dest: &mut [u8]) {
275 self.compute_fft();
276 let len = cmp::min(dest.len(), self.computed_fft_data.len());
277 let ratio = 255. / (self.max_decibels - self.min_decibels);
278 for (index, freq) in dest[0..len].iter_mut().enumerate() {
279 let result = ratio * (self.computed_fft_data[index] as f64 - self.min_decibels);
280 *freq = clamp_255(result as f32);
281 }
282 }
283}
284
285fn clamp_255(val: f32) -> u8 {
286 if val > 255. {
287 255
288 } else if val < 0. {
289 0
290 } else {
291 val as u8
292 }
293}