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