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)]
30pub struct GainNode {
31 registration: AudioContextRegistration,
32 channel_config: ChannelConfig,
33 gain: AudioParam,
34}
35
36impl AudioNode for GainNode {
37 fn registration(&self) -> &AudioContextRegistration {
38 &self.registration
39 }
40
41 fn channel_config(&self) -> &ChannelConfig {
42 &self.channel_config
43 }
44
45 fn number_of_inputs(&self) -> usize {
46 1
47 }
48
49 fn number_of_outputs(&self) -> usize {
50 1
51 }
52}
53
54impl GainNode {
55 pub fn new<C: BaseAudioContext>(context: &C, options: GainOptions) -> Self {
56 context.base().register(move |registration| {
57 let param_opts = AudioParamDescriptor {
58 name: String::new(),
59 min_value: f32::MIN,
60 max_value: f32::MAX,
61 default_value: 1.,
62 automation_rate: crate::param::AutomationRate::A,
63 };
64 let (param, proc) = context.create_audio_param(param_opts, ®istration);
65
66 param.set_value(options.gain);
67
68 let render = GainRenderer { gain: proc };
69
70 let node = GainNode {
71 registration,
72 channel_config: options.audio_node_options.into(),
73 gain: param,
74 };
75
76 (node, Box::new(render))
77 })
78 }
79
80 pub fn gain(&self) -> &AudioParam {
81 &self.gain
82 }
83}
84
85struct GainRenderer {
86 gain: AudioParamId,
87}
88
89impl AudioProcessor for GainRenderer {
90 fn process(
91 &mut self,
92 inputs: &[AudioRenderQuantum],
93 outputs: &mut [AudioRenderQuantum],
94 params: AudioParamValues<'_>,
95 _scope: &AudioWorkletGlobalScope,
96 ) -> bool {
97 let input = &inputs[0];
99 let output = &mut outputs[0];
100
101 if input.is_silent() {
102 output.make_silent();
103 return false;
104 }
105
106 let gain = params.get(&self.gain);
107
108 if gain.len() == 1 {
110 let threshold = 1e-6;
113
114 let diff_to_zero = gain[0].abs();
115 if diff_to_zero <= threshold {
116 output.make_silent();
117 return false;
118 }
119
120 let diff_to_one = (1. - gain[0]).abs();
121 if diff_to_one <= threshold {
122 *output = input.clone();
123 return false;
124 }
125 }
126
127 *output = input.clone();
128
129 if gain.len() == 1 {
130 let g = gain[0];
131
132 output.channels_mut().iter_mut().for_each(|channel| {
133 channel.iter_mut().for_each(|o| *o *= g);
134 });
135 } else {
136 output.channels_mut().iter_mut().for_each(|channel| {
137 channel
138 .iter_mut()
139 .zip(gain.iter().cycle())
140 .for_each(|(o, g)| *o *= g);
141 });
142 }
143
144 false
145 }
146}
147
148#[cfg(test)]
149mod tests {
150 use super::*;
151 use crate::context::OfflineAudioContext;
152 use float_eq::assert_float_eq;
153
154 #[test]
155 fn test_audioparam_value_applies_immediately() {
156 let context = OfflineAudioContext::new(1, 128, 48000.);
157 let options = GainOptions {
158 gain: 0.12,
159 ..Default::default()
160 };
161 let src = GainNode::new(&context, options);
162 assert_float_eq!(src.gain.value(), 0.12, abs_all <= 0.);
163 }
164}