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