web_audio_api/node/
analyser.rs

1use crate::analysis::{
2    Analyser, AnalyserRingBuffer, DEFAULT_FFT_SIZE, DEFAULT_MAX_DECIBELS, DEFAULT_MIN_DECIBELS,
3    DEFAULT_SMOOTHING_TIME_CONSTANT,
4};
5use crate::context::{AudioContextRegistration, BaseAudioContext};
6use crate::render::{
7    AudioParamValues, AudioProcessor, AudioRenderQuantum, AudioWorkletGlobalScope,
8};
9
10use super::{AudioNode, AudioNodeOptions, ChannelConfig, ChannelInterpretation};
11
12/// Options for constructing an [`AnalyserNode`]
13// dictionary AnalyserOptions : AudioNodeOptions {
14//   unsigned long fftSize = 2048;
15//   double maxDecibels = -30;
16//   double minDecibels = -100;
17//   double smoothingTimeConstant = 0.8;
18// };
19#[derive(Clone, Debug)]
20pub struct AnalyserOptions {
21    pub fft_size: usize,
22    pub max_decibels: f64,
23    pub min_decibels: f64,
24    pub smoothing_time_constant: f64,
25    pub audio_node_options: AudioNodeOptions,
26}
27
28impl Default for AnalyserOptions {
29    fn default() -> Self {
30        Self {
31            fft_size: DEFAULT_FFT_SIZE,
32            max_decibels: DEFAULT_MAX_DECIBELS,
33            min_decibels: DEFAULT_MIN_DECIBELS,
34            smoothing_time_constant: DEFAULT_SMOOTHING_TIME_CONSTANT,
35            audio_node_options: AudioNodeOptions::default(),
36        }
37    }
38}
39
40/// `AnalyserNode` represents a node able to provide real-time frequency and
41/// time-domain analysis information.
42///
43/// It is an AudioNode that passes the audio stream unchanged from the input to
44/// the output, but allows you to take the generated data, process it, and create
45/// audio visualizations..
46///
47/// - MDN documentation: <https://developer.mozilla.org/en-US/docs/Web/API/AnalyserNode>
48/// - specification: <https://webaudio.github.io/web-audio-api/#AnalyserNode>
49/// - see also: [`BaseAudioContext::create_analyser`]
50///
51/// # Usage
52///
53/// ```no_run
54/// use web_audio_api::context::{BaseAudioContext, AudioContext};
55/// use web_audio_api::node::{AudioNode, AudioScheduledSourceNode};
56///
57/// let context = AudioContext::default();
58///
59/// let mut analyser = context.create_analyser();
60/// analyser.connect(&context.destination());
61///
62/// let mut osc = context.create_oscillator();
63/// osc.frequency().set_value(200.);
64/// osc.connect(&analyser);
65/// osc.start();
66///
67/// let mut bins = vec![0.; analyser.frequency_bin_count()];
68///
69///
70/// loop {
71///     analyser.get_float_frequency_data(&mut bins);
72///     println!("{:?}", &bins[0..20]); // print 20 first bins
73///     std::thread::sleep(std::time::Duration::from_millis(1000));
74/// }
75/// ```
76///
77/// # Examples
78///
79/// - `cargo run --release --example analyser`
80/// - `cd showcase/mic_playback && cargo run --release`
81///
82#[derive(Debug)]
83pub struct AnalyserNode {
84    registration: AudioContextRegistration,
85    channel_config: ChannelConfig,
86    analyser: Analyser,
87}
88
89impl AudioNode for AnalyserNode {
90    fn registration(&self) -> &AudioContextRegistration {
91        &self.registration
92    }
93
94    fn channel_config(&self) -> &ChannelConfig {
95        &self.channel_config
96    }
97
98    fn number_of_inputs(&self) -> usize {
99        1
100    }
101
102    fn number_of_outputs(&self) -> usize {
103        1
104    }
105}
106
107impl AnalyserNode {
108    pub fn new<C: BaseAudioContext>(context: &C, options: AnalyserOptions) -> Self {
109        context.base().register(move |registration| {
110            let fft_size = options.fft_size;
111            let smoothing_time_constant = options.smoothing_time_constant;
112            let min_decibels = options.min_decibels;
113            let max_decibels = options.max_decibels;
114
115            let mut analyser = Analyser::new();
116            analyser.set_fft_size(fft_size);
117            analyser.set_smoothing_time_constant(smoothing_time_constant);
118            analyser.set_decibels(min_decibels, max_decibels);
119
120            let render = AnalyserRenderer {
121                ring_buffer: analyser.get_ring_buffer_clone(),
122            };
123
124            let node = AnalyserNode {
125                registration,
126                channel_config: options.audio_node_options.into(),
127                analyser,
128            };
129
130            (node, Box::new(render))
131        })
132    }
133
134    /// The size of the FFT used for frequency-domain analysis (in sample-frames)
135    ///
136    /// # Panics
137    ///
138    /// This method may panic if the lock to the inner analyser is poisoned
139    pub fn fft_size(&self) -> usize {
140        self.analyser.fft_size()
141    }
142
143    /// Set FFT size
144    ///
145    /// # Panics
146    ///
147    /// This function panics if fft_size is not a power of two or not in the range [32, 32768]
148    pub fn set_fft_size(&mut self, fft_size: usize) {
149        self.analyser.set_fft_size(fft_size);
150    }
151
152    /// Time averaging parameter with the last analysis frame.
153    /// A value from 0 -> 1 where 0 represents no time averaging with the last
154    /// analysis frame. The default value is 0.8.
155    ///
156    /// # Panics
157    ///
158    /// This method may panic if the lock to the inner analyser is poisoned
159    pub fn smoothing_time_constant(&self) -> f64 {
160        self.analyser.smoothing_time_constant()
161    }
162
163    /// Set smoothing time constant
164    ///
165    /// # Panics
166    ///
167    /// This function panics if the value is set to a value less than 0 or more than 1.
168    pub fn set_smoothing_time_constant(&mut self, value: f64) {
169        self.analyser.set_smoothing_time_constant(value);
170    }
171
172    /// Minimum power value in the scaling range for the FFT analysis data for
173    /// conversion to unsigned byte values. The default value is -100.
174    ///
175    /// # Panics
176    ///
177    /// This method may panic if the lock to the inner analyser is poisoned
178    pub fn min_decibels(&self) -> f64 {
179        self.analyser.min_decibels()
180    }
181
182    /// Set min decibels
183    ///
184    /// # Panics
185    ///
186    /// This function panics if the value is set to a value more than or equal
187    /// to max decibels.
188    pub fn set_min_decibels(&mut self, value: f64) {
189        self.analyser.set_decibels(value, self.max_decibels());
190    }
191
192    /// Maximum power value in the scaling range for the FFT analysis data for
193    /// conversion to unsigned byte values. The default value is -30.
194    ///
195    /// # Panics
196    ///
197    /// This method may panic if the lock to the inner analyser is poisoned
198    pub fn max_decibels(&self) -> f64 {
199        self.analyser.max_decibels()
200    }
201
202    /// Set max decibels
203    ///
204    /// # Panics
205    ///
206    /// This function panics if the value is set to a value less than or equal
207    /// to min decibels.
208    pub fn set_max_decibels(&mut self, value: f64) {
209        self.analyser.set_decibels(self.min_decibels(), value);
210    }
211
212    /// Number of bins in the FFT results, is half the FFT size
213    ///
214    /// # Panics
215    ///
216    /// This method may panic if the lock to the inner analyser is poisoned
217    pub fn frequency_bin_count(&self) -> usize {
218        self.analyser.frequency_bin_count()
219    }
220
221    /// Copy the current time domain data as f32 values into the provided buffer
222    ///
223    /// # Panics
224    ///
225    /// This method may panic if the lock to the inner analyser is poisoned
226    pub fn get_float_time_domain_data(&mut self, buffer: &mut [f32]) {
227        self.analyser.get_float_time_domain_data(buffer);
228    }
229
230    /// Copy the current time domain data as u8 values into the provided buffer
231    ///
232    /// # Panics
233    ///
234    /// This method may panic if the lock to the inner analyser is poisoned
235    pub fn get_byte_time_domain_data(&mut self, buffer: &mut [u8]) {
236        self.analyser.get_byte_time_domain_data(buffer);
237    }
238
239    /// Copy the current frequency data into the provided buffer
240    ///
241    /// # Panics
242    ///
243    /// This method may panic if the lock to the inner analyser is poisoned
244    pub fn get_float_frequency_data(&mut self, buffer: &mut [f32]) {
245        let current_time = self.registration.context().current_time();
246        self.analyser.get_float_frequency_data(buffer, current_time);
247    }
248
249    /// Copy the current frequency data scaled between min_decibels and
250    /// max_decibels into the provided buffer
251    ///
252    /// # Panics
253    ///
254    /// This method may panic if the lock to the inner analyser is poisoned
255    pub fn get_byte_frequency_data(&mut self, buffer: &mut [u8]) {
256        let current_time = self.registration.context().current_time();
257        self.analyser.get_byte_frequency_data(buffer, current_time);
258    }
259}
260
261struct AnalyserRenderer {
262    ring_buffer: AnalyserRingBuffer,
263}
264
265impl AudioProcessor for AnalyserRenderer {
266    fn process(
267        &mut self,
268        inputs: &[AudioRenderQuantum],
269        outputs: &mut [AudioRenderQuantum],
270        _params: AudioParamValues<'_>,
271        _scope: &AudioWorkletGlobalScope,
272    ) -> bool {
273        // single input/output node
274        let input = &inputs[0];
275        let output = &mut outputs[0];
276
277        // pass through input
278        *output = input.clone();
279
280        // down mix to mono
281        let mut mono = input.clone();
282        mono.mix(1, ChannelInterpretation::Speakers);
283
284        // add current input to ring buffer
285        let data = mono.channel_data(0).as_ref();
286        self.ring_buffer.write(data);
287
288        // no tail-time
289        false
290    }
291}
292
293#[cfg(test)]
294mod tests {
295    use super::*;
296    use crate::context::{
297        AudioContext, AudioContextOptions, BaseAudioContext, OfflineAudioContext,
298    };
299    use crate::node::{AudioNode, AudioScheduledSourceNode};
300    use float_eq::assert_float_eq;
301
302    #[test]
303    fn test_analyser_after_closed() {
304        let options = AudioContextOptions {
305            sink_id: "none".into(),
306            ..AudioContextOptions::default()
307        };
308        let context = AudioContext::new(options);
309
310        let mut src = context.create_constant_source();
311        src.start();
312
313        let mut analyser = context.create_analyser();
314        src.connect(&analyser);
315
316        // allow buffer to fill
317        std::thread::sleep(std::time::Duration::from_millis(20));
318
319        let mut buffer = vec![0.; 128];
320        analyser.get_float_time_domain_data(&mut buffer);
321        assert_float_eq!(&buffer[..], &[1.; 128][..], abs_all <= 0.); // constant source of 1.
322
323        // close context
324        context.close_sync();
325        std::thread::sleep(std::time::Duration::from_millis(50));
326
327        let mut buffer = vec![0.; 128];
328        analyser.get_float_time_domain_data(&mut buffer); // should not crash or hang
329
330        // should contain the most recent frames available
331        assert_float_eq!(&buffer[..], &[1.; 128][..], abs_all <= 0.);
332    }
333
334    #[test]
335    fn test_construct_decibels() {
336        let context = OfflineAudioContext::new(1, 128, 44_100.);
337        let options = AnalyserOptions {
338            min_decibels: -10.,
339            max_decibels: 20.,
340            ..AnalyserOptions::default()
341        };
342        let _ = AnalyserNode::new(&context, options);
343    }
344}