1pub use super::volume::VolumeNodeConfig;
2use firewheel_core::node::NodeError;
3use firewheel_core::param::smoother::DEFAULT_GAIN_SPAN;
4use firewheel_core::{
5 channel_config::{ChannelConfig, ChannelCount},
6 diff::{Diff, Patch},
7 dsp::{
8 fade::FadeCurve,
9 filter::smoothing_filter::DEFAULT_SMOOTH_SECONDS,
10 volume::{DEFAULT_MIN_AMP, Volume},
11 },
12 event::ProcEvents,
13 mask::MaskType,
14 node::{
15 AudioNode, AudioNodeInfo, AudioNodeProcessor, ConstructProcessorContext, ProcBuffers,
16 ProcExtra, ProcInfo, ProcStreamCtx, ProcessStatus,
17 },
18 param::smoother::{SmoothedParam, SmootherConfig},
19};
20
21#[derive(Diff, Patch, Debug, Clone, Copy, PartialEq)]
23#[cfg_attr(feature = "bevy", derive(bevy_ecs::prelude::Component))]
24#[cfg_attr(feature = "bevy_reflect", derive(bevy_reflect::Reflect))]
25#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
26pub struct VolumePanNode {
27 pub volume: Volume,
29 pub pan: f32,
32 pub pan_law: FadeCurve,
36
37 pub smooth_seconds: f32,
43 pub min_gain: f32,
49}
50
51impl VolumePanNode {
52 pub const fn from_volume_pan(volume: Volume, pan: f32) -> Self {
58 Self {
59 volume,
60 pan,
61 pan_law: FadeCurve::EqualPower3dB,
62 smooth_seconds: DEFAULT_SMOOTH_SECONDS,
63 min_gain: DEFAULT_MIN_AMP,
64 }
65 }
66
67 pub const fn from_pan(pan: f32) -> Self {
74 Self {
75 volume: Volume::UNITY_GAIN,
76 pan,
77 pan_law: FadeCurve::EqualPower3dB,
78 smooth_seconds: DEFAULT_SMOOTH_SECONDS,
79 min_gain: DEFAULT_MIN_AMP,
80 }
81 }
82
83 pub const fn from_volume(volume: Volume) -> Self {
87 Self {
88 volume,
89 pan: 0.0,
90 pan_law: FadeCurve::EqualPower3dB,
91 smooth_seconds: DEFAULT_SMOOTH_SECONDS,
92 min_gain: DEFAULT_MIN_AMP,
93 }
94 }
95
96 pub const fn set_volume_linear(&mut self, linear: f32) {
102 self.volume = Volume::Linear(linear);
103 }
104
105 pub const fn set_volume_percent(&mut self, percent: f32) {
110 self.volume = Volume::from_percent(percent);
111 }
112
113 pub const fn set_volume_decibels(&mut self, decibels: f32) {
116 self.volume = Volume::Decibels(decibels);
117 }
118
119 pub fn compute_gains(&self, min_amp: f32) -> (f32, f32) {
120 let global_gain = self.volume.amp_clamped(min_amp);
121
122 let (mut gain_l, mut gain_r) = self.pan_law.compute_gains_neg1_to_1(self.pan);
123
124 gain_l *= global_gain;
125 gain_r *= global_gain;
126
127 if gain_l > 0.99999 && gain_l < 1.00001 {
128 gain_l = 1.0;
129 }
130 if gain_r > 0.99999 && gain_r < 1.00001 {
131 gain_r = 1.0;
132 }
133
134 (gain_l, gain_r)
135 }
136}
137
138impl Default for VolumePanNode {
139 fn default() -> Self {
140 Self {
141 volume: Volume::default(),
142 pan: 0.0,
143 pan_law: FadeCurve::default(),
144 smooth_seconds: DEFAULT_SMOOTH_SECONDS,
145 min_gain: DEFAULT_MIN_AMP,
146 }
147 }
148}
149
150impl AudioNode for VolumePanNode {
151 type Configuration = VolumeNodeConfig;
152
153 fn info(&self, _config: &Self::Configuration) -> Result<AudioNodeInfo, NodeError> {
154 Ok(AudioNodeInfo::new()
155 .debug_name("volume_pan")
156 .channel_config(ChannelConfig {
157 num_inputs: ChannelCount::STEREO,
158 num_outputs: ChannelCount::STEREO,
159 }))
160 }
163
164 fn construct_processor(
165 &self,
166 _config: &Self::Configuration,
167 cx: ConstructProcessorContext,
168 ) -> Result<impl AudioNodeProcessor, NodeError> {
169 let min_gain = self.min_gain.max(0.0);
170
171 let (gain_l, gain_r) = self.compute_gains(self.min_gain);
172
173 Ok(Processor {
174 gain_l: SmoothedParam::new(
175 gain_l,
176 DEFAULT_GAIN_SPAN,
177 SmootherConfig {
178 smooth_seconds: self.smooth_seconds,
179 ..Default::default()
180 },
181 cx.stream_info.sample_rate,
182 ),
183 gain_r: SmoothedParam::new(
184 gain_r,
185 DEFAULT_GAIN_SPAN,
186 SmootherConfig {
187 smooth_seconds: self.smooth_seconds,
188 ..Default::default()
189 },
190 cx.stream_info.sample_rate,
191 ),
192 params: *self,
193 min_gain,
194 prev_input_settled: true,
195 })
196 }
197}
198
199struct Processor {
200 gain_l: SmoothedParam,
201 gain_r: SmoothedParam,
202
203 params: VolumePanNode,
204
205 min_gain: f32,
206 prev_input_settled: bool,
207}
208
209impl AudioNodeProcessor for Processor {
210 fn events(&mut self, info: &ProcInfo, events: &mut ProcEvents, _extra: &mut ProcExtra) {
211 let mut updated = false;
212 for mut patch in events.drain_patches::<VolumePanNode>() {
213 match &mut patch {
214 VolumePanNodePatch::Pan(p) => {
215 *p = p.clamp(-1.0, 1.0);
216 }
217 VolumePanNodePatch::SmoothSeconds(seconds) => {
218 self.gain_l.set_smooth_seconds(*seconds, info.sample_rate);
219 self.gain_r.set_smooth_seconds(*seconds, info.sample_rate);
220 }
221 VolumePanNodePatch::MinGain(min_gain) => {
222 self.min_gain = (*min_gain).max(0.0);
223 }
224 _ => {}
225 }
226
227 self.params.apply(patch);
228 updated = true;
229 }
230
231 if updated {
232 let (gain_l, gain_r) = self.params.compute_gains(self.min_gain);
233 self.gain_l.set_value(gain_l);
234 self.gain_r.set_value(gain_r);
235
236 if self.prev_input_settled {
237 self.gain_l.reset_to_target();
239 self.gain_r.reset_to_target();
240 }
241 }
242 }
243
244 fn bypassed(&mut self, _bypassed: bool) {
245 self.gain_l.reset_to_target();
246 self.gain_r.reset_to_target();
247 }
248
249 fn process(
250 &mut self,
251 info: &ProcInfo,
252 buffers: ProcBuffers,
253 _extra: &mut ProcExtra,
254 ) -> ProcessStatus {
255 if info.in_silence_mask.all_channels_silent(2) {
256 self.gain_l.reset_to_target();
257 self.gain_r.reset_to_target();
258 self.prev_input_settled = true;
259
260 return ProcessStatus::ClearAllOutputs;
261 }
262
263 self.prev_input_settled = buffers.inputs_settled_at_zero();
264
265 let in1 = &buffers.inputs[0][..info.frames];
266 let in2 = &buffers.inputs[1][..info.frames];
267 let (out1, out2) = buffers.outputs.split_first_mut().unwrap();
268 let out1 = &mut out1[..info.frames];
269 let out2 = &mut out2[0][..info.frames];
270
271 if self.gain_l.has_settled() && self.gain_r.has_settled() {
272 if self.gain_l.target_value() <= self.min_gain
273 && self.gain_r.target_value() <= self.min_gain
274 {
275 self.gain_l.reset_to_target();
276 self.gain_r.reset_to_target();
277
278 ProcessStatus::ClearAllOutputs
279 } else {
280 for i in 0..info.frames {
281 out1[i] = in1[i] * self.gain_l.target_value();
282 out2[i] = in2[i] * self.gain_r.target_value();
283 }
284
285 ProcessStatus::OutputsModifiedWithMask(MaskType::Silence(info.in_silence_mask))
286 }
287 } else {
288 for i in 0..info.frames {
289 let gain_l = self.gain_l.next_smoothed();
290 let gain_r = self.gain_r.next_smoothed();
291
292 out1[i] = in1[i] * gain_l;
293 out2[i] = in2[i] * gain_r;
294 }
295
296 self.gain_l.settle();
297 self.gain_r.settle();
298
299 ProcessStatus::OutputsModified
300 }
301 }
302
303 fn new_stream(
304 &mut self,
305 stream_info: &firewheel_core::StreamInfo,
306 _context: &mut ProcStreamCtx,
307 ) {
308 self.gain_l.update_sample_rate(stream_info.sample_rate);
309 self.gain_r.update_sample_rate(stream_info.sample_rate);
310 }
311}