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 data: Box<[f32; MAX_FFT_SIZE]>,
69 current_block: usize,
71 fft_computed: bool,
73 blackman_windows: Vec<f32>,
75 smoothed_fft_data: Vec<f32>,
77 computed_fft_data: Vec<f32>,
79 windowed: Vec<f32>,
82}
83
84impl AnalysisEngine {
85 pub fn new(
86 fft_size: usize,
87 smoothing_constant: f64,
88 min_decibels: f64,
89 max_decibels: f64,
90 ) -> Self {
91 debug_assert!((32..=32768).contains(&fft_size));
92 debug_assert!(fft_size & (fft_size - 1) == 0);
94 debug_assert!((0. ..=1.).contains(&smoothing_constant));
95 debug_assert!(max_decibels > min_decibels);
96 Self {
97 fft_size,
98 smoothing_constant,
99 min_decibels,
100 max_decibels,
101 data: Box::new([0.; MAX_FFT_SIZE]),
102 current_block: MAX_BLOCK_COUNT - 1,
103 fft_computed: false,
104 blackman_windows: Vec::with_capacity(fft_size),
105 computed_fft_data: Vec::with_capacity(fft_size / 2),
106 smoothed_fft_data: Vec::with_capacity(fft_size / 2),
107 windowed: Vec::with_capacity(fft_size),
108 }
109 }
110
111 pub fn set_fft_size(&mut self, fft_size: usize) {
112 debug_assert!((32..=32768).contains(&fft_size));
113 debug_assert!(fft_size & (fft_size - 1) == 0);
115 self.fft_size = fft_size;
116 self.fft_computed = false;
117 }
118
119 pub fn get_fft_size(&self) -> usize {
120 self.fft_size
121 }
122
123 pub fn set_smoothing_constant(&mut self, smoothing_constant: f64) {
124 debug_assert!((0. ..=1.).contains(&smoothing_constant));
125 self.smoothing_constant = smoothing_constant;
126 self.fft_computed = false;
127 }
128
129 pub fn get_smoothing_constant(&self) -> f64 {
130 self.smoothing_constant
131 }
132
133 pub fn set_min_decibels(&mut self, min_decibels: f64) {
134 debug_assert!(min_decibels < self.max_decibels);
135 self.min_decibels = min_decibels;
136 }
137
138 pub fn get_min_decibels(&self) -> f64 {
139 self.min_decibels
140 }
141
142 pub fn set_max_decibels(&mut self, max_decibels: f64) {
143 debug_assert!(self.min_decibels < max_decibels);
144 self.max_decibels = max_decibels;
145 }
146
147 pub fn get_max_decibels(&self) -> f64 {
148 self.max_decibels
149 }
150
151 fn advance(&mut self) {
152 self.current_block += 1;
153 if self.current_block >= MAX_BLOCK_COUNT {
154 self.current_block = 0;
155 }
156 }
157
158 fn curent_block_mut(&mut self) -> &mut [f32] {
160 let index = FRAMES_PER_BLOCK_USIZE * self.current_block;
161 &mut self.data[index..(index + FRAMES_PER_BLOCK_USIZE)]
162 }
163
164 fn convert_index(&self, index: usize) -> usize {
167 let offset = self.fft_size - index;
168 let last_element = (1 + self.current_block) * FRAMES_PER_BLOCK_USIZE - 1;
169 if offset > last_element {
170 MAX_FFT_SIZE - offset + last_element
171 } else {
172 last_element - offset
173 }
174 }
175
176 fn advance_index(&self, index: &mut usize) {
178 *index += 1;
179 if *index >= MAX_FFT_SIZE {
180 *index = 0;
181 }
182 }
183
184 pub fn push(&mut self, mut block: Block) {
185 debug_assert!(block.chan_count() == 1);
186 self.advance();
187 if !block.is_silence() {
188 self.curent_block_mut().copy_from_slice(block.data_mut());
189 }
190 self.fft_computed = false;
191 }
192
193 fn compute_blackman_windows(&mut self) {
195 if self.blackman_windows.len() == self.fft_size {
196 return;
197 }
198 const ALPHA: f32 = 0.16;
199 const ALPHA_0: f32 = (1. - ALPHA) / 2.;
200 const ALPHA_1: f32 = 1. / 2.;
201 const ALPHA_2: f32 = ALPHA / 2.;
202 self.blackman_windows.resize(self.fft_size, 0.);
203 let coeff = PI * 2. / self.fft_size as f32;
204 for n in 0..self.fft_size {
205 self.blackman_windows[n] = ALPHA_0 - ALPHA_1 * (coeff * n as f32).cos() +
206 ALPHA_2 * (2. * coeff * n as f32).cos();
207 }
208 }
209
210 fn apply_blackman_window(&mut self) {
211 self.compute_blackman_windows();
212 self.windowed.resize(self.fft_size, 0.);
213
214 let mut data_idx = self.convert_index(0);
215 for n in 0..self.fft_size {
216 self.windowed[n] = self.blackman_windows[n] * self.data[data_idx];
217 self.advance_index(&mut data_idx);
218 }
219 }
220
221 fn compute_fft(&mut self) {
222 if self.fft_computed {
223 return;
224 }
225 self.fft_computed = true;
226 self.apply_blackman_window();
227 self.computed_fft_data.resize(self.fft_size / 2, 0.);
228 self.smoothed_fft_data.resize(self.fft_size / 2, 0.);
229
230 for k in 0..(self.fft_size / 2) {
231 let mut sum_real = 0.;
232 let mut sum_imaginary = 0.;
233 let factor = -2. * PI * k as f32 / self.fft_size as f32;
234 for n in 0..(self.fft_size) {
235 sum_real += self.windowed[n] * (factor * n as f32).cos();
236 sum_imaginary += self.windowed[n] * (factor * n as f32).sin();
237 }
238 let sum_real = sum_real / self.fft_size as f32;
239 let sum_imaginary = sum_imaginary / self.fft_size as f32;
240 let magnitude = (sum_real * sum_real + sum_imaginary * sum_imaginary).sqrt();
241 self.smoothed_fft_data[k] = (self.smoothing_constant * self.smoothed_fft_data[k] as f64 +
242 (1. - self.smoothing_constant) * magnitude as f64)
243 as f32;
244 self.computed_fft_data[k] = 20. * self.smoothed_fft_data[k].log(10.);
245 }
246 }
247
248 pub fn fill_time_domain_data(&self, dest: &mut [f32]) {
249 let mut data_idx = self.convert_index(0);
250 let end = cmp::min(self.fft_size, dest.len());
251 for entry in &mut dest[0..end] {
252 *entry = self.data[data_idx];
253 self.advance_index(&mut data_idx);
254 }
255 }
256
257 pub fn fill_byte_time_domain_data(&self, dest: &mut [u8]) {
258 let mut data_idx = self.convert_index(0);
259 let end = cmp::min(self.fft_size, dest.len());
260 for entry in &mut dest[0..end] {
261 let result = 128. * (1. + self.data[data_idx]);
262 *entry = clamp_255(result);
263 self.advance_index(&mut data_idx)
264 }
265 }
266
267 pub fn fill_frequency_data(&mut self, dest: &mut [f32]) {
268 self.compute_fft();
269 let len = cmp::min(dest.len(), self.computed_fft_data.len());
270 dest[0..len].copy_from_slice(&self.computed_fft_data[0..len]);
271 }
272
273 pub fn fill_byte_frequency_data(&mut self, dest: &mut [u8]) {
274 self.compute_fft();
275 let len = cmp::min(dest.len(), self.computed_fft_data.len());
276 let ratio = 255. / (self.max_decibels - self.min_decibels);
277 for (index, freq) in dest[0..len].iter_mut().enumerate() {
278 let result = ratio * (self.computed_fft_data[index] as f64 - self.min_decibels);
279 *freq = clamp_255(result as f32);
280 }
281 }
282}
283
284fn clamp_255(val: f32) -> u8 {
285 if val > 255. {
286 255
287 } else if val < 0. {
288 0
289 } else {
290 val as u8
291 }
292}