1use firewheel_core::node::NodeError;
2use firewheel_core::param::smoother::DEFAULT_GAIN_SPAN;
3use firewheel_core::{
4 channel_config::{ChannelConfig, NonZeroChannelCount},
5 diff::{Diff, Patch},
6 dsp::{
7 filter::smoothing_filter::DEFAULT_SMOOTH_SECONDS,
8 volume::{DEFAULT_MIN_AMP, Volume},
9 },
10 event::ProcEvents,
11 mask::MaskType,
12 node::{
13 AudioNode, AudioNodeInfo, AudioNodeProcessor, ConstructProcessorContext, ProcBuffers,
14 ProcExtra, ProcInfo, ProcStreamCtx, ProcessStatus,
15 },
16 param::smoother::{SmoothedParam, SmootherConfig},
17};
18
19#[derive(Debug, Clone, Copy, PartialEq, Eq)]
21#[cfg_attr(feature = "bevy", derive(bevy_ecs::prelude::Component))]
22#[cfg_attr(feature = "bevy_reflect", derive(bevy_reflect::Reflect))]
23#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
24pub struct VolumeNodeConfig {
25 pub channels: NonZeroChannelCount,
27}
28
29impl Default for VolumeNodeConfig {
30 fn default() -> Self {
31 Self {
32 channels: NonZeroChannelCount::STEREO,
33 }
34 }
35}
36
37#[derive(Diff, Patch, Debug, Clone, Copy, PartialEq)]
39#[cfg_attr(feature = "bevy", derive(bevy_ecs::prelude::Component))]
40#[cfg_attr(feature = "bevy_reflect", derive(bevy_reflect::Reflect))]
41#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
42pub struct VolumeNode {
43 pub volume: Volume,
45
46 pub smooth_seconds: f32,
52 pub min_gain: f32,
58}
59
60impl Default for VolumeNode {
61 fn default() -> Self {
62 Self {
63 volume: Volume::default(),
64 smooth_seconds: DEFAULT_SMOOTH_SECONDS,
65 min_gain: DEFAULT_MIN_AMP,
66 }
67 }
68}
69
70impl VolumeNode {
71 pub const fn from_linear(linear: f32) -> Self {
77 Self {
78 volume: Volume::Linear(linear),
79 smooth_seconds: DEFAULT_SMOOTH_SECONDS,
80 min_gain: DEFAULT_MIN_AMP,
81 }
82 }
83
84 pub const fn from_percent(percent: f32) -> Self {
89 Self {
90 volume: Volume::from_percent(percent),
91 smooth_seconds: DEFAULT_SMOOTH_SECONDS,
92 min_gain: DEFAULT_MIN_AMP,
93 }
94 }
95
96 pub const fn from_decibels(decibels: f32) -> Self {
99 Self {
100 volume: Volume::Decibels(decibels),
101 smooth_seconds: DEFAULT_SMOOTH_SECONDS,
102 min_gain: DEFAULT_MIN_AMP,
103 }
104 }
105
106 pub const fn set_linear(&mut self, linear: f32) {
112 self.volume = Volume::Linear(linear);
113 }
114
115 pub const fn set_percent(&mut self, percent: f32) {
120 self.volume = Volume::from_percent(percent);
121 }
122
123 pub const fn set_decibels(&mut self, decibels: f32) {
126 self.volume = Volume::Decibels(decibels);
127 }
128}
129
130impl AudioNode for VolumeNode {
131 type Configuration = VolumeNodeConfig;
132
133 fn info(&self, config: &Self::Configuration) -> Result<AudioNodeInfo, NodeError> {
134 Ok(AudioNodeInfo::new()
135 .debug_name("volume")
136 .channel_config(ChannelConfig {
137 num_inputs: config.channels.get(),
138 num_outputs: config.channels.get(),
139 }))
140 }
143
144 fn construct_processor(
145 &self,
146 config: &Self::Configuration,
147 cx: ConstructProcessorContext,
148 ) -> Result<impl AudioNodeProcessor, NodeError> {
149 let min_gain = self.min_gain.max(0.0);
150 let gain = self.volume.amp_clamped(min_gain);
151
152 Ok(VolumeProcessor {
153 gain: SmoothedParam::new(
154 gain,
155 DEFAULT_GAIN_SPAN,
156 SmootherConfig {
157 smooth_seconds: self.smooth_seconds,
158 ..Default::default()
159 },
160 cx.stream_info.sample_rate,
161 ),
162 min_gain,
163 num_channels: config.channels.get().get() as usize,
164 prev_input_settled: true,
165 })
166 }
167}
168
169struct VolumeProcessor {
170 gain: SmoothedParam,
171 num_channels: usize,
172
173 min_gain: f32,
174 prev_input_settled: bool,
175}
176
177impl AudioNodeProcessor for VolumeProcessor {
178 fn events(&mut self, info: &ProcInfo, events: &mut ProcEvents, _extra: &mut ProcExtra) {
179 for patch in events.drain_patches::<VolumeNode>() {
180 match patch {
181 VolumeNodePatch::Volume(v) => {
182 let mut gain = v.amp_clamped(self.min_gain);
183 if gain > 0.99999 && gain < 1.00001 {
184 gain = 1.0;
185 }
186 self.gain.set_value(gain);
187
188 if self.prev_input_settled {
189 self.gain.reset_to_target();
191 }
192 }
193 VolumeNodePatch::SmoothSeconds(seconds) => {
194 self.gain.set_smooth_seconds(seconds, info.sample_rate);
195 }
196 VolumeNodePatch::MinGain(min_gain) => {
197 self.min_gain = min_gain.max(0.0);
198 }
199 }
200 }
201 }
202
203 fn bypassed(&mut self, _bypassed: bool) {
204 self.gain.reset_to_target();
205 }
206
207 fn process(
208 &mut self,
209 info: &ProcInfo,
210 buffers: ProcBuffers,
211 extra: &mut ProcExtra,
212 ) -> ProcessStatus {
213 if info.in_silence_mask.all_channels_silent(self.num_channels) {
214 self.gain.reset_to_target();
217 self.prev_input_settled = true;
218
219 return ProcessStatus::ClearAllOutputs;
220 }
221
222 self.prev_input_settled = buffers.inputs_settled_at_zero();
223
224 if self.gain.has_settled() {
225 if self.gain.target_value() <= self.min_gain {
226 return ProcessStatus::ClearAllOutputs;
228 } else if self.gain.target_value() == 1.0 {
229 return ProcessStatus::Bypass;
231 } else {
232 for (ch_i, (out_ch, in_ch)) in buffers
233 .outputs
234 .iter_mut()
235 .zip(buffers.inputs.iter())
236 .enumerate()
237 {
238 if info.in_silence_mask.is_channel_silent(ch_i) {
239 if !info.out_silence_mask.is_channel_silent(ch_i) {
240 out_ch.fill(0.0);
241 }
242 } else {
243 for (os, &is) in out_ch.iter_mut().zip(in_ch.iter()) {
244 *os = is * self.gain.target_value();
245 }
246 }
247 }
248
249 return ProcessStatus::OutputsModifiedWithMask(MaskType::Silence(
250 info.in_silence_mask,
251 ));
252 }
253 }
254
255 if buffers.inputs.len() == 1 {
256 for (os, &is) in buffers.outputs[0].iter_mut().zip(buffers.inputs[0].iter()) {
258 *os = is * self.gain.next_smoothed();
259 }
260 } else if buffers.inputs.len() == 2 {
261 let in0 = &buffers.inputs[0][..info.frames];
264 let in1 = &buffers.inputs[1][..info.frames];
265 let (out0, out1) = buffers.outputs.split_first_mut().unwrap();
266 let out0 = &mut out0[..info.frames];
267 let out1 = &mut out1[0][..info.frames];
268
269 for i in 0..info.frames {
270 let gain = self.gain.next_smoothed();
271
272 out0[i] = in0[i] * gain;
273 out1[i] = in1[i] * gain;
274 }
275 } else {
276 let scratch_buffer = extra.scratch_buffers.first_mut();
277
278 self.gain
279 .process_into_buffer(&mut scratch_buffer[..info.frames]);
280
281 for (ch_i, (out_ch, in_ch)) in buffers
282 .outputs
283 .iter_mut()
284 .zip(buffers.inputs.iter())
285 .enumerate()
286 {
287 if info.in_silence_mask.is_channel_silent(ch_i) {
288 if !info.out_silence_mask.is_channel_silent(ch_i) {
289 out_ch.fill(0.0);
290 }
291 continue;
292 }
293
294 for ((os, &is), &g) in out_ch
295 .iter_mut()
296 .zip(in_ch.iter())
297 .zip(scratch_buffer[..info.frames].iter())
298 {
299 *os = is * g;
300 }
301 }
302 }
303
304 self.gain.settle();
305
306 ProcessStatus::OutputsModified
307 }
308
309 fn new_stream(
310 &mut self,
311 stream_info: &firewheel_core::StreamInfo,
312 _context: &mut ProcStreamCtx,
313 ) {
314 self.gain.update_sample_rate(stream_info.sample_rate);
315 }
316}