1#[cfg(not(feature = "std"))]
2use num_traits::Float;
3
4use bevy_platform::sync::{Arc, atomic::Ordering};
5use firewheel_core::node::NodeError;
6use firewheel_core::{
7 atomic_float::AtomicF32,
8 channel_config::{ChannelConfig, ChannelCount},
9 diff::{Diff, Patch},
10 dsp::volume::{DbMeterNormalizer, amp_to_db},
11 event::ProcEvents,
12 node::{
13 AudioNode, AudioNodeInfo, AudioNodeProcessor, ConstructProcessorContext, EmptyConfig,
14 ProcBuffers, ProcExtra, ProcInfo, ProcessStatus,
15 },
16};
17
18#[derive(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 PeakMeterSmootherConfig {
24 pub decay_rate: f32,
28 pub refresh_rate: f32,
33 pub clip_hold_frames: usize,
38}
39
40impl Default for PeakMeterSmootherConfig {
41 fn default() -> Self {
42 Self {
43 decay_rate: 0.3,
44 refresh_rate: 60.0,
45 clip_hold_frames: 60,
46 }
47 }
48}
49
50pub type PeakMeterMonoSmoother = PeakMeterSmoother<1>;
51pub type PeakMeterStereoSmoother = PeakMeterSmoother<2>;
52
53#[derive(Debug, Clone, Copy)]
56pub struct PeakMeterSmoother<const NUM_CHANNELS: usize = 2> {
57 smoothed_peaks: [f32; NUM_CHANNELS],
59 clipped_frames_left: [usize; NUM_CHANNELS],
60 level_decay: f32,
61 frame_interval: f32,
62 frame_counter: f32,
63 clip_hold_frames: usize,
64}
65
66impl<const NUM_CHANNELS: usize> PeakMeterSmoother<NUM_CHANNELS> {
67 pub fn new(config: PeakMeterSmootherConfig) -> Self {
68 assert!(config.decay_rate > 0.0);
69 assert!(config.refresh_rate > 0.0);
70 assert!(config.clip_hold_frames > 0);
71
72 Self {
73 smoothed_peaks: [-100.0; NUM_CHANNELS],
74 clipped_frames_left: [0; NUM_CHANNELS],
75 level_decay: 1.0 - (-1.0 / (config.refresh_rate * config.decay_rate)).exp(),
76 frame_interval: config.refresh_rate.recip(),
77 frame_counter: 0.0,
78 clip_hold_frames: config.clip_hold_frames,
79 }
80 }
81
82 pub fn reset(&mut self) {
83 self.smoothed_peaks = [-100.0; NUM_CHANNELS];
84 self.clipped_frames_left = [0; NUM_CHANNELS];
85 }
86
87 pub fn update(&mut self, peaks_db: [f32; NUM_CHANNELS], delta_seconds: f32) {
88 for ((smoothed_peak, &in_peak), clipped_frames_left) in self
89 .smoothed_peaks
90 .iter_mut()
91 .zip(peaks_db.iter())
92 .zip(self.clipped_frames_left.iter_mut())
93 {
94 if in_peak >= *smoothed_peak {
95 *smoothed_peak = in_peak;
96
97 if in_peak > 0.0 {
98 *clipped_frames_left = self.clip_hold_frames;
99 }
100 }
101 }
102
103 self.frame_counter += delta_seconds;
104
105 if (self.frame_counter - self.frame_interval).abs() < 0.0001 {
107 self.frame_counter = self.frame_interval;
108 }
109
110 while self.frame_counter >= self.frame_interval {
111 self.frame_counter -= self.frame_interval;
112
113 if (self.frame_counter - self.frame_interval).abs() < 0.0001 {
115 self.frame_counter = self.frame_interval;
116 }
117
118 for ((smoothed_peak, &in_peak), clipped_frames_left) in self
119 .smoothed_peaks
120 .iter_mut()
121 .zip(peaks_db.iter())
122 .zip(self.clipped_frames_left.iter_mut())
123 {
124 if in_peak + 0.001 < *smoothed_peak {
125 *smoothed_peak += ((in_peak - *smoothed_peak) * self.level_decay).max(-100.0);
126 }
127
128 if *smoothed_peak > 0.0 {
129 *clipped_frames_left = self.clip_hold_frames;
130 } else if *clipped_frames_left > 0 {
131 *clipped_frames_left -= 1;
132 }
133 }
134 }
135 }
136
137 pub fn has_clipped(&self) -> [bool; NUM_CHANNELS] {
138 core::array::from_fn(|i| self.clipped_frames_left[i] > 0)
139 }
140
141 pub fn smoothed_peaks_db(&self) -> &[f32; NUM_CHANNELS] {
142 &self.smoothed_peaks
143 }
144
145 pub fn smoothed_peak_db_mono(&self) -> f32 {
146 let mut max_value: f32 = -100.0;
147 for ch in self.smoothed_peaks {
148 max_value = max_value.max(ch);
149 }
150
151 max_value
152 }
153
154 pub fn smoothed_peaks_normalized(&self, normalizer: &DbMeterNormalizer) -> [f32; NUM_CHANNELS] {
156 core::array::from_fn(|i| normalizer.normalize(self.smoothed_peaks[i]))
157 }
158
159 pub fn smoothed_peaks_normalized_mono(&self, normalizer: &DbMeterNormalizer) -> f32 {
160 normalizer.normalize(self.smoothed_peak_db_mono())
161 }
162}
163
164pub type PeakMeterMonoNode = PeakMeterNode<1>;
165pub type PeakMeterStereoNode = PeakMeterNode<2>;
166
167#[derive(Diff, Patch, Debug, Clone, Copy, PartialEq, Eq)]
170#[cfg_attr(feature = "bevy", derive(bevy_ecs::prelude::Component))]
171#[cfg_attr(feature = "bevy_reflect", derive(bevy_reflect::Reflect))]
172#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
173pub struct PeakMeterNode<const NUM_CHANNELS: usize = 2> {
174 pub enabled: bool,
175}
176
177pub type PeakMeterMonoState = PeakMeterState<1>;
178pub type PeakMeterStereoState = PeakMeterState<2>;
179
180#[derive(Clone)]
182pub struct PeakMeterState<const NUM_CHANNELS: usize = 2> {
183 shared_state: Arc<SharedState<NUM_CHANNELS>>,
184}
185
186impl<const NUM_CHANNELS: usize> PeakMeterState<NUM_CHANNELS> {
187 fn new() -> Self {
188 assert_ne!(NUM_CHANNELS, 0);
189 assert!(NUM_CHANNELS <= 64);
190
191 Self {
192 shared_state: Arc::new(SharedState {
193 peak_gains: core::array::from_fn(|_| AtomicF32::new(0.0)),
194 }),
195 }
196 }
197
198 pub fn peak_gain_db(&self, min_db: f32) -> [f32; NUM_CHANNELS] {
207 core::array::from_fn(|i| {
208 let db = amp_to_db(self.shared_state.peak_gains[i].load(Ordering::Relaxed));
209 if db <= min_db { f32::NEG_INFINITY } else { db }
210 })
211 }
212}
213
214impl<const NUM_CHANNELS: usize> AudioNode for PeakMeterNode<NUM_CHANNELS> {
215 type Configuration = EmptyConfig;
216
217 fn info(&self, _config: &Self::Configuration) -> Result<AudioNodeInfo, NodeError> {
218 Ok(AudioNodeInfo::new()
219 .debug_name("peak_meter")
220 .channel_config(ChannelConfig {
221 num_inputs: ChannelCount::new(NUM_CHANNELS as u32).unwrap(),
222 num_outputs: ChannelCount::new(NUM_CHANNELS as u32).unwrap(),
223 })
224 .custom_state(PeakMeterState::<NUM_CHANNELS>::new()))
225 }
226
227 fn construct_processor(
228 &self,
229 _config: &Self::Configuration,
230 cx: ConstructProcessorContext,
231 ) -> Result<impl AudioNodeProcessor, NodeError> {
232 Ok(Processor {
233 params: *self,
234 shared_state: Arc::clone(
235 &cx.custom_state::<PeakMeterState<NUM_CHANNELS>>()
236 .unwrap()
237 .shared_state,
238 ),
239 })
240 }
241}
242
243struct SharedState<const NUM_CHANNELS: usize> {
244 peak_gains: [AtomicF32; NUM_CHANNELS],
245}
246
247struct Processor<const NUM_CHANNELS: usize> {
248 params: PeakMeterNode<NUM_CHANNELS>,
249 shared_state: Arc<SharedState<NUM_CHANNELS>>,
250}
251
252impl<const NUM_CHANNELS: usize> Processor<NUM_CHANNELS> {
253 fn reset(&mut self) {
254 for ch in self.shared_state.peak_gains.iter() {
255 ch.store(0.0, Ordering::Relaxed);
256 }
257 }
258}
259
260impl<const NUM_CHANNELS: usize> AudioNodeProcessor for Processor<NUM_CHANNELS> {
261 fn events(&mut self, _info: &ProcInfo, events: &mut ProcEvents, _extra: &mut ProcExtra) {
262 let was_enabled = self.params.enabled;
263
264 for patch in events.drain_patches::<PeakMeterNode<NUM_CHANNELS>>() {
265 self.params.apply(patch);
266 }
267
268 if was_enabled && !self.params.enabled {
269 self.reset();
270 }
271 }
272
273 fn bypassed(&mut self, _bypassed: bool) {
274 self.reset();
275 }
276
277 fn process(
278 &mut self,
279 info: &ProcInfo,
280 buffers: ProcBuffers,
281 _extra: &mut ProcExtra,
282 ) -> ProcessStatus {
283 if !self.params.enabled {
284 return ProcessStatus::Bypass;
285 }
286
287 for (i, (in_ch, peak_shared)) in buffers
288 .inputs
289 .iter()
290 .zip(self.shared_state.peak_gains.iter())
291 .enumerate()
292 {
293 if info.in_silence_mask.is_channel_silent(i) {
294 peak_shared.store(0.0, Ordering::Relaxed);
295 } else {
296 peak_shared.store(
297 firewheel_core::dsp::algo::max_peak(in_ch),
298 Ordering::Relaxed,
299 );
300 }
301 }
302
303 ProcessStatus::Bypass
304 }
305}