1#[cfg(not(feature = "std"))]
2use num_traits::Float;
3
4use bevy_platform::sync::atomic::Ordering;
5use firewheel_core::{
6 atomic_float::AtomicF32,
7 channel_config::{ChannelConfig, ChannelCount},
8 collector::ArcGc,
9 diff::{Diff, Patch},
10 dsp::volume::{amp_to_db, DbMeterNormalizer},
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: ArcGc<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: ArcGc::new(SharedState {
193 peak_gains: core::array::from_fn(|_| AtomicF32::new(0.0)),
194 }),
195 }
196 }
197
198 pub fn peak_gain_db(&self, db_epsilon: 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 <= db_epsilon {
210 f32::NEG_INFINITY
211 } else {
212 db
213 }
214 })
215 }
216}
217
218impl<const NUM_CHANNELS: usize> AudioNode for PeakMeterNode<NUM_CHANNELS> {
219 type Configuration = EmptyConfig;
220
221 fn info(&self, _config: &Self::Configuration) -> AudioNodeInfo {
222 AudioNodeInfo::new()
223 .debug_name("peak_meter")
224 .channel_config(ChannelConfig {
225 num_inputs: ChannelCount::new(NUM_CHANNELS as u32).unwrap(),
226 num_outputs: ChannelCount::new(NUM_CHANNELS as u32).unwrap(),
227 })
228 .custom_state(PeakMeterState::<NUM_CHANNELS>::new())
229 }
230
231 fn construct_processor(
232 &self,
233 _config: &Self::Configuration,
234 cx: ConstructProcessorContext,
235 ) -> impl AudioNodeProcessor {
236 Processor {
237 params: self.clone(),
238 shared_state: ArcGc::clone(
239 &cx.custom_state::<PeakMeterState<NUM_CHANNELS>>()
240 .unwrap()
241 .shared_state,
242 ),
243 }
244 }
245}
246
247struct SharedState<const NUM_CHANNELS: usize> {
248 peak_gains: [AtomicF32; NUM_CHANNELS],
249}
250
251struct Processor<const NUM_CHANNELS: usize> {
252 params: PeakMeterNode<NUM_CHANNELS>,
253 shared_state: ArcGc<SharedState<NUM_CHANNELS>>,
254}
255
256impl<const NUM_CHANNELS: usize> AudioNodeProcessor for Processor<NUM_CHANNELS> {
257 fn process(
258 &mut self,
259 info: &ProcInfo,
260 buffers: ProcBuffers,
261 events: &mut ProcEvents,
262 _extra: &mut ProcExtra,
263 ) -> ProcessStatus {
264 let was_enabled = self.params.enabled;
265
266 for patch in events.drain_patches::<PeakMeterNode<NUM_CHANNELS>>() {
267 self.params.apply(patch);
268 }
269
270 if was_enabled && !self.params.enabled {
271 for ch in self.shared_state.peak_gains.iter() {
272 ch.store(0.0, Ordering::Relaxed);
273 }
274 }
275
276 if !self.params.enabled {
277 return ProcessStatus::Bypass;
278 }
279
280 for (i, (in_ch, peak_shared)) in buffers
281 .inputs
282 .iter()
283 .zip(self.shared_state.peak_gains.iter())
284 .enumerate()
285 {
286 if info.in_silence_mask.is_channel_silent(i) {
287 peak_shared.store(0.0, Ordering::Relaxed);
288 } else {
289 peak_shared.store(
290 firewheel_core::dsp::algo::max_peak(in_ch),
291 Ordering::Relaxed,
292 );
293 }
294 }
295
296 ProcessStatus::Bypass
297 }
298}