1use firewheel_core::{
2 channel_config::{ChannelConfig, NonZeroChannelCount},
3 diff::{Diff, Patch},
4 dsp::{
5 filter::smoothing_filter::DEFAULT_SMOOTH_SECONDS,
6 volume::{Volume, DEFAULT_AMP_EPSILON},
7 },
8 event::ProcEvents,
9 mask::MaskType,
10 node::{
11 AudioNode, AudioNodeInfo, AudioNodeProcessor, ConstructProcessorContext, ProcBuffers,
12 ProcExtra, ProcInfo, ProcStreamCtx, ProcessStatus,
13 },
14 param::smoother::{SmoothedParam, SmootherConfig},
15};
16
17#[derive(Debug, Clone, Copy, PartialEq, Eq)]
19#[cfg_attr(feature = "bevy", derive(bevy_ecs::prelude::Component))]
20#[cfg_attr(feature = "bevy_reflect", derive(bevy_reflect::Reflect))]
21#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
22pub struct VolumeNodeConfig {
23 pub channels: NonZeroChannelCount,
25}
26
27impl Default for VolumeNodeConfig {
28 fn default() -> Self {
29 Self {
30 channels: NonZeroChannelCount::STEREO,
31 }
32 }
33}
34
35#[derive(Diff, Patch, Debug, Clone, Copy, PartialEq)]
37#[cfg_attr(feature = "bevy", derive(bevy_ecs::prelude::Component))]
38#[cfg_attr(feature = "bevy_reflect", derive(bevy_reflect::Reflect))]
39#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
40pub struct VolumeNode {
41 pub volume: Volume,
43
44 pub smooth_seconds: f32,
48 pub min_gain: f32,
54}
55
56impl Default for VolumeNode {
57 fn default() -> Self {
58 Self {
59 volume: Volume::default(),
60 smooth_seconds: DEFAULT_SMOOTH_SECONDS,
61 min_gain: DEFAULT_AMP_EPSILON,
62 }
63 }
64}
65
66impl VolumeNode {
67 pub const fn from_linear(linear: f32) -> Self {
73 Self {
74 volume: Volume::Linear(linear),
75 smooth_seconds: DEFAULT_SMOOTH_SECONDS,
76 min_gain: DEFAULT_AMP_EPSILON,
77 }
78 }
79
80 pub const fn from_percent(percent: f32) -> Self {
85 Self {
86 volume: Volume::from_percent(percent),
87 smooth_seconds: DEFAULT_SMOOTH_SECONDS,
88 min_gain: DEFAULT_AMP_EPSILON,
89 }
90 }
91
92 pub const fn from_decibels(decibels: f32) -> Self {
95 Self {
96 volume: Volume::Decibels(decibels),
97 smooth_seconds: DEFAULT_SMOOTH_SECONDS,
98 min_gain: DEFAULT_AMP_EPSILON,
99 }
100 }
101
102 pub const fn set_linear(&mut self, linear: f32) {
108 self.volume = Volume::Linear(linear);
109 }
110
111 pub const fn set_percent(&mut self, percent: f32) {
116 self.volume = Volume::from_percent(percent);
117 }
118
119 pub const fn set_decibels(&mut self, decibels: f32) {
122 self.volume = Volume::Decibels(decibels);
123 }
124}
125
126impl AudioNode for VolumeNode {
127 type Configuration = VolumeNodeConfig;
128
129 fn info(&self, config: &Self::Configuration) -> AudioNodeInfo {
130 AudioNodeInfo::new()
131 .debug_name("volume")
132 .channel_config(ChannelConfig {
133 num_inputs: config.channels.get(),
134 num_outputs: config.channels.get(),
135 })
136 }
137
138 fn construct_processor(
139 &self,
140 _config: &Self::Configuration,
141 cx: ConstructProcessorContext,
142 ) -> impl AudioNodeProcessor {
143 let min_gain = self.min_gain.max(0.0);
144 let gain = self.volume.amp_clamped(min_gain);
145
146 VolumeProcessor {
147 gain: SmoothedParam::new(
148 gain,
149 SmootherConfig {
150 smooth_seconds: self.smooth_seconds,
151 ..Default::default()
152 },
153 cx.stream_info.sample_rate,
154 ),
155 min_gain,
156 }
157 }
158}
159
160struct VolumeProcessor {
161 gain: SmoothedParam,
162
163 min_gain: f32,
164}
165
166impl AudioNodeProcessor for VolumeProcessor {
167 fn process(
168 &mut self,
169 info: &ProcInfo,
170 buffers: ProcBuffers,
171 events: &mut ProcEvents,
172 extra: &mut ProcExtra,
173 ) -> ProcessStatus {
174 for patch in events.drain_patches::<VolumeNode>() {
175 match patch {
176 VolumeNodePatch::Volume(v) => {
177 let mut gain = v.amp_clamped(self.min_gain);
178 if gain > 0.99999 && gain < 1.00001 {
179 gain = 1.0;
180 }
181 self.gain.set_value(gain);
182
183 if info.prev_output_was_silent {
184 self.gain.reset_to_target();
186 }
187 }
188 VolumeNodePatch::SmoothSeconds(seconds) => {
189 self.gain.set_smooth_seconds(seconds, info.sample_rate);
190 }
191 VolumeNodePatch::MinGain(min_gain) => {
192 self.min_gain = min_gain.max(0.0);
193 }
194 }
195 }
196
197 if info
198 .in_silence_mask
199 .all_channels_silent(buffers.inputs.len())
200 {
201 self.gain.reset_to_target();
204
205 return ProcessStatus::ClearAllOutputs;
206 }
207
208 if self.gain.has_settled() {
209 if self.gain.target_value() <= self.min_gain {
210 return ProcessStatus::ClearAllOutputs;
212 } else if self.gain.target_value() == 1.0 {
213 return ProcessStatus::Bypass;
215 } else {
216 for (ch_i, (out_ch, in_ch)) in buffers
217 .outputs
218 .iter_mut()
219 .zip(buffers.inputs.iter())
220 .enumerate()
221 {
222 if info.in_silence_mask.is_channel_silent(ch_i) {
223 if !info.out_silence_mask.is_channel_silent(ch_i) {
224 out_ch.fill(0.0);
225 }
226 } else {
227 for (os, &is) in out_ch.iter_mut().zip(in_ch.iter()) {
228 *os = is * self.gain.target_value();
229 }
230 }
231 }
232
233 return ProcessStatus::OutputsModifiedWithMask(MaskType::Silence(
234 info.in_silence_mask,
235 ));
236 }
237 }
238
239 if buffers.inputs.len() == 1 {
240 for (os, &is) in buffers.outputs[0].iter_mut().zip(buffers.inputs[0].iter()) {
242 *os = is * self.gain.next_smoothed();
243 }
244 } else if buffers.inputs.len() == 2 {
245 let in0 = &buffers.inputs[0][..info.frames];
248 let in1 = &buffers.inputs[1][..info.frames];
249 let (out0, out1) = buffers.outputs.split_first_mut().unwrap();
250 let out0 = &mut out0[..info.frames];
251 let out1 = &mut out1[0][..info.frames];
252
253 for i in 0..info.frames {
254 let gain = self.gain.next_smoothed();
255
256 out0[i] = in0[i] * gain;
257 out1[i] = in1[i] * gain;
258 }
259 } else {
260 let scratch_buffer = extra.scratch_buffers.first_mut();
261
262 self.gain
263 .process_into_buffer(&mut scratch_buffer[..info.frames]);
264
265 for (ch_i, (out_ch, in_ch)) in buffers
266 .outputs
267 .iter_mut()
268 .zip(buffers.inputs.iter())
269 .enumerate()
270 {
271 if info.in_silence_mask.is_channel_silent(ch_i) {
272 if !info.out_silence_mask.is_channel_silent(ch_i) {
273 out_ch.fill(0.0);
274 }
275 continue;
276 }
277
278 for ((os, &is), &g) in out_ch
279 .iter_mut()
280 .zip(in_ch.iter())
281 .zip(scratch_buffer[..info.frames].iter())
282 {
283 *os = is * g;
284 }
285 }
286 }
287
288 self.gain.settle();
289
290 ProcessStatus::OutputsModified
291 }
292
293 fn new_stream(
294 &mut self,
295 stream_info: &firewheel_core::StreamInfo,
296 _context: &mut ProcStreamCtx,
297 ) {
298 self.gain.update_sample_rate(stream_info.sample_rate);
299 }
300}