web_audio_api/node/
gain.rs1use crate::context::{AudioContextRegistration, AudioParamId, BaseAudioContext};
2use crate::param::{AudioParam, AudioParamDescriptor};
3use crate::render::{
4 AudioParamValues, AudioProcessor, AudioRenderQuantum, AudioWorkletGlobalScope,
5};
6
7use super::{AudioNode, AudioNodeOptions, ChannelConfig};
8
9#[derive(Clone, Debug)]
14pub struct GainOptions {
15 pub gain: f32,
16 pub audio_node_options: AudioNodeOptions,
17}
18
19impl Default for GainOptions {
20 fn default() -> Self {
21 Self {
22 gain: 1.,
23 audio_node_options: AudioNodeOptions::default(),
24 }
25 }
26}
27
28#[derive(Debug)]
64pub struct GainNode {
65 registration: AudioContextRegistration,
67 channel_config: ChannelConfig,
69 gain: AudioParam,
71}
72
73impl AudioNode for GainNode {
74 fn registration(&self) -> &AudioContextRegistration {
75 &self.registration
76 }
77
78 fn channel_config(&self) -> &ChannelConfig {
79 &self.channel_config
80 }
81
82 fn number_of_inputs(&self) -> usize {
83 1
84 }
85
86 fn number_of_outputs(&self) -> usize {
87 1
88 }
89}
90
91impl GainNode {
92 pub fn new<C: BaseAudioContext>(context: &C, options: GainOptions) -> Self {
102 context.base().register(move |registration| {
103 let param_opts = AudioParamDescriptor {
104 name: String::new(),
105 min_value: f32::MIN,
106 max_value: f32::MAX,
107 default_value: 1.,
108 automation_rate: crate::param::AutomationRate::A,
109 };
110 let (param, proc) = context.create_audio_param(param_opts, ®istration);
111
112 param.set_value(options.gain);
113
114 let render = GainRenderer { gain: proc };
115
116 let node = GainNode {
117 registration,
118 channel_config: options.audio_node_options.into(),
119 gain: param,
120 };
121
122 (node, Box::new(render))
123 })
124 }
125
126 #[must_use]
134 pub fn gain(&self) -> &AudioParam {
135 &self.gain
136 }
137}
138
139struct GainRenderer {
140 gain: AudioParamId,
141}
142
143impl AudioProcessor for GainRenderer {
144 fn process(
145 &mut self,
146 inputs: &[AudioRenderQuantum],
147 outputs: &mut [AudioRenderQuantum],
148 params: AudioParamValues<'_>,
149 _scope: &AudioWorkletGlobalScope,
150 ) -> bool {
151 let input = &inputs[0];
153 let output = &mut outputs[0];
154
155 if input.is_silent() {
156 output.make_silent();
157 return false;
158 }
159
160 let gain = params.get(&self.gain);
161
162 if gain.len() == 1 {
164 let threshold = 1e-6;
167
168 let diff_to_zero = gain[0].abs();
169 if diff_to_zero <= threshold {
170 output.make_silent();
171 return false;
172 }
173
174 let diff_to_one = (1. - gain[0]).abs();
175 if diff_to_one <= threshold {
176 *output = input.clone();
177 return false;
178 }
179 }
180
181 *output = input.clone();
182
183 if gain.len() == 1 {
184 let g = gain[0];
185
186 output.channels_mut().iter_mut().for_each(|channel| {
187 channel.iter_mut().for_each(|o| *o *= g);
188 });
189 } else {
190 output.channels_mut().iter_mut().for_each(|channel| {
191 channel
192 .iter_mut()
193 .zip(gain.iter().cycle())
194 .for_each(|(o, g)| *o *= g);
195 });
196 }
197
198 false
199 }
200}
201
202#[cfg(test)]
203mod tests {
204 use super::*;
205 use crate::context::OfflineAudioContext;
206 use float_eq::assert_float_eq;
207
208 #[test]
209 fn test_audioparam_value_applies_immediately() {
210 let context = OfflineAudioContext::new(1, 128, 48000.);
211 let options = GainOptions {
212 gain: 0.12,
213 ..Default::default()
214 };
215 let src = GainNode::new(&context, options);
216 assert_float_eq!(src.gain.value(), 0.12, abs_all <= 0.);
217 }
218}