firewheel_nodes/
beep_test.rs

1#[cfg(not(feature = "std"))]
2use num_traits::Float;
3
4use firewheel_core::{
5    channel_config::{ChannelConfig, ChannelCount},
6    diff::{Diff, Patch},
7    dsp::volume::{Volume, DEFAULT_AMP_EPSILON},
8    event::ProcEvents,
9    node::{
10        AudioNode, AudioNodeInfo, AudioNodeProcessor, ConstructProcessorContext, EmptyConfig,
11        ProcBuffers, ProcExtra, ProcInfo, ProcessStatus,
12    },
13};
14
15/// A simple node that outputs a sine wave, used for testing purposes.
16///
17/// Note that because this node is for testing purposes, it does not
18/// bother with parameter smoothing.
19#[derive(Diff, Patch, Debug, Clone, Copy, PartialEq)]
20#[cfg_attr(feature = "bevy", derive(bevy_ecs::prelude::Component))]
21#[cfg_attr(feature = "bevy_reflect", derive(bevy_reflect::Reflect))]
22#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
23pub struct BeepTestNode {
24    /// The frequency of the sine wave in the range `[20.0, 20_000.0]`. A good
25    /// value for testing is `440` (middle C).
26    pub freq_hz: f32,
27
28    /// The overall volume.
29    ///
30    /// NOTE, a sine wave at `Volume::Linear(1.0) or Volume::Decibels(0.0)` volume
31    /// is *LOUD*, prefer to use a value `Volume::Linear(0.5) or
32    /// Volume::Decibels(-12.0)`.
33    pub volume: Volume,
34
35    /// Whether or not the node is currently enabled.
36    pub enabled: bool,
37}
38
39impl Default for BeepTestNode {
40    fn default() -> Self {
41        Self {
42            freq_hz: 440.0,
43            volume: Volume::Linear(0.5),
44            enabled: true,
45        }
46    }
47}
48
49impl AudioNode for BeepTestNode {
50    type Configuration = EmptyConfig;
51
52    fn info(&self, _config: &Self::Configuration) -> AudioNodeInfo {
53        AudioNodeInfo::new()
54            .debug_name("beep_test")
55            .channel_config(ChannelConfig {
56                num_inputs: ChannelCount::ZERO,
57                num_outputs: ChannelCount::MONO,
58            })
59    }
60
61    fn construct_processor(
62        &self,
63        _config: &Self::Configuration,
64        cx: ConstructProcessorContext,
65    ) -> impl AudioNodeProcessor {
66        Processor {
67            phasor: 0.0,
68            phasor_inc: self.freq_hz.clamp(20.0, 20_000.0)
69                * cx.stream_info.sample_rate_recip as f32,
70            gain: self.volume.amp_clamped(DEFAULT_AMP_EPSILON),
71            enabled: self.enabled,
72        }
73    }
74}
75
76struct Processor {
77    phasor: f32,
78    phasor_inc: f32,
79    gain: f32,
80    enabled: bool,
81}
82
83impl AudioNodeProcessor for Processor {
84    fn process(
85        &mut self,
86        info: &ProcInfo,
87        buffers: ProcBuffers,
88        events: &mut ProcEvents,
89        _extra: &mut ProcExtra,
90    ) -> ProcessStatus {
91        let Some(out) = buffers.outputs.first_mut() else {
92            return ProcessStatus::ClearAllOutputs;
93        };
94
95        for patch in events.drain_patches::<BeepTestNode>() {
96            match patch {
97                BeepTestNodePatch::FreqHz(f) => {
98                    self.phasor_inc = f.clamp(20.0, 20_000.0) * info.sample_rate_recip as f32;
99                }
100                BeepTestNodePatch::Volume(v) => {
101                    self.gain = v.amp_clamped(DEFAULT_AMP_EPSILON);
102                }
103                BeepTestNodePatch::Enabled(e) => self.enabled = e,
104            }
105        }
106
107        if !self.enabled {
108            return ProcessStatus::ClearAllOutputs;
109        }
110
111        for s in out.iter_mut() {
112            *s = (self.phasor * core::f32::consts::TAU).sin() * self.gain;
113            self.phasor = (self.phasor + self.phasor_inc).fract();
114        }
115
116        ProcessStatus::OutputsModified
117    }
118}