1use std::any::Any;
2
3use crate::context::{AudioContextRegistration, AudioParamId, BaseAudioContext};
4use crate::param::{AudioParam, AudioParamDescriptor, AutomationRate};
5use crate::render::{
6 AudioParamValues, AudioProcessor, AudioRenderQuantum, AudioWorkletGlobalScope,
7};
8use crate::{assert_valid_time_value, RENDER_QUANTUM_SIZE};
9
10use super::{AudioNode, AudioScheduledSourceNode, ChannelConfig};
11
12#[derive(Clone, Debug)]
22pub struct ConstantSourceOptions {
23 pub offset: f32,
25}
26
27impl Default for ConstantSourceOptions {
28 fn default() -> Self {
29 Self { offset: 1. }
30 }
31}
32
33#[derive(Debug, Copy, Clone)]
35enum Schedule {
36 Start(f64),
37 Stop(f64),
38}
39
40#[derive(Debug)]
75pub struct ConstantSourceNode {
76 registration: AudioContextRegistration,
77 channel_config: ChannelConfig,
78 offset: AudioParam,
79 has_start: bool,
80}
81
82impl AudioNode for ConstantSourceNode {
83 fn registration(&self) -> &AudioContextRegistration {
84 &self.registration
85 }
86
87 fn channel_config(&self) -> &ChannelConfig {
88 &self.channel_config
89 }
90
91 fn number_of_inputs(&self) -> usize {
92 0
93 }
94
95 fn number_of_outputs(&self) -> usize {
96 1
97 }
98}
99
100impl AudioScheduledSourceNode for ConstantSourceNode {
101 fn start(&mut self) {
102 let when = self.registration.context().current_time();
103 self.start_at(when);
104 }
105
106 fn start_at(&mut self, when: f64) {
107 assert_valid_time_value(when);
108 assert!(
109 !self.has_start,
110 "InvalidStateError - Cannot call `start` twice"
111 );
112
113 self.has_start = true;
114 self.registration.post_message(Schedule::Start(when));
115 }
116
117 fn stop(&mut self) {
118 let when = self.registration.context().current_time();
119 self.stop_at(when);
120 }
121
122 fn stop_at(&mut self, when: f64) {
123 assert_valid_time_value(when);
124 assert!(self.has_start, "InvalidStateError cannot stop before start");
125
126 self.registration.post_message(Schedule::Stop(when));
127 }
128}
129
130impl ConstantSourceNode {
131 pub fn new<C: BaseAudioContext>(context: &C, options: ConstantSourceOptions) -> Self {
132 context.base().register(move |registration| {
133 let ConstantSourceOptions { offset } = options;
134
135 let param_options = AudioParamDescriptor {
136 name: String::new(),
137 min_value: f32::MIN,
138 max_value: f32::MAX,
139 default_value: 1.,
140 automation_rate: AutomationRate::A,
141 };
142 let (param, proc) = context.create_audio_param(param_options, ®istration);
143 param.set_value(offset);
144
145 let render = ConstantSourceRenderer {
146 offset: proc,
147 start_time: f64::MAX,
148 stop_time: f64::MAX,
149 ended_triggered: false,
150 };
151
152 let node = ConstantSourceNode {
153 registration,
154 channel_config: ChannelConfig::default(),
155 offset: param,
156 has_start: false,
157 };
158
159 (node, Box::new(render))
160 })
161 }
162
163 pub fn offset(&self) -> &AudioParam {
164 &self.offset
165 }
166}
167
168struct ConstantSourceRenderer {
169 offset: AudioParamId,
170 start_time: f64,
171 stop_time: f64,
172 ended_triggered: bool,
173}
174
175impl AudioProcessor for ConstantSourceRenderer {
176 fn process(
177 &mut self,
178 _inputs: &[AudioRenderQuantum],
179 outputs: &mut [AudioRenderQuantum],
180 params: AudioParamValues<'_>,
181 scope: &AudioWorkletGlobalScope,
182 ) -> bool {
183 let output = &mut outputs[0];
185
186 let dt = 1. / scope.sample_rate as f64;
187 let next_block_time = scope.current_time + dt * RENDER_QUANTUM_SIZE as f64;
188
189 if self.start_time >= next_block_time {
190 output.make_silent();
191
192 if self.stop_time <= next_block_time {
193 if !self.ended_triggered {
194 scope.send_ended_event();
195 self.ended_triggered = true;
196 }
197
198 return false;
199 }
200
201 return self.start_time != f64::MAX;
204 }
205
206 output.force_mono();
207
208 let offset = params.get(&self.offset);
209 let output_channel = output.channel_data_mut(0);
210
211 if offset.len() == 1
213 && self.start_time <= scope.current_time
214 && self.stop_time >= next_block_time
215 {
216 output_channel.fill(offset[0]);
217 } else {
218 let mut current_time = scope.current_time;
220
221 output_channel
222 .iter_mut()
223 .zip(offset.iter().cycle())
224 .for_each(|(o, &value)| {
225 if current_time < self.start_time || current_time >= self.stop_time {
226 *o = 0.;
227 } else {
228 *o = value;
232 }
233
234 current_time += dt;
235 });
236 }
237
238 let still_running = self.stop_time > next_block_time;
240
241 if !still_running {
242 if !self.ended_triggered {
245 scope.send_ended_event();
246 self.ended_triggered = true;
247 }
248 }
249
250 still_running
251 }
252
253 fn onmessage(&mut self, msg: &mut dyn Any) {
254 if let Some(schedule) = msg.downcast_ref::<Schedule>() {
255 match *schedule {
256 Schedule::Start(v) => self.start_time = v,
257 Schedule::Stop(v) => self.stop_time = v,
258 }
259 return;
260 }
261
262 log::warn!("ConstantSourceRenderer: Dropping incoming message {msg:?}");
263 }
264
265 fn before_drop(&mut self, scope: &AudioWorkletGlobalScope) {
266 if !self.ended_triggered
267 && (scope.current_time >= self.start_time || scope.current_time >= self.stop_time)
268 {
269 scope.send_ended_event();
270 self.ended_triggered = true;
271 }
272 }
273}
274
275#[cfg(test)]
276mod tests {
277 use crate::context::{BaseAudioContext, OfflineAudioContext};
278 use crate::node::{AudioNode, AudioScheduledSourceNode};
279
280 use float_eq::assert_float_eq;
281
282 use super::*;
283
284 #[test]
285 fn test_audioparam_value_applies_immediately() {
286 let context = OfflineAudioContext::new(1, 128, 48000.);
287 let options = ConstantSourceOptions { offset: 12. };
288 let src = ConstantSourceNode::new(&context, options);
289 assert_float_eq!(src.offset.value(), 12., abs_all <= 0.);
290 }
291
292 #[test]
293 fn test_start_stop() {
294 let sample_rate = 48000.;
295 let start_in_samples = (128 + 1) as f64; let stop_in_samples = (256 + 1) as f64; let mut context = OfflineAudioContext::new(1, 128 * 4, sample_rate);
298
299 let mut src = context.create_constant_source();
300 src.connect(&context.destination());
301
302 src.start_at(start_in_samples / sample_rate as f64);
303 src.stop_at(stop_in_samples / sample_rate as f64);
304
305 let buffer = context.start_rendering_sync();
306 let channel = buffer.get_channel_data(0);
307
308 assert_float_eq!(channel[0..128], vec![0.; 128][..], abs_all <= 0.);
310
311 let mut res = vec![1.; 128];
313 res[0] = 0.;
314 assert_float_eq!(channel[128..256], res[..], abs_all <= 0.);
315
316 let mut res = vec![0.; 128];
318 res[0] = 1.;
319 assert_float_eq!(channel[256..384], res[..], abs_all <= 0.);
320
321 assert_float_eq!(channel[384..512], vec![0.; 128][..], abs_all <= 0.);
323 }
324
325 #[test]
326 fn test_start_in_the_past() {
327 let sample_rate = 48000.;
328 let mut context = OfflineAudioContext::new(1, 2 * 128, sample_rate);
329
330 context.suspend_sync((128. / sample_rate).into(), |context| {
331 let mut src = context.create_constant_source();
332 src.connect(&context.destination());
333 src.start_at(0.);
334 });
335
336 let buffer = context.start_rendering_sync();
337 let channel = buffer.get_channel_data(0);
338
339 assert_float_eq!(channel[0..128], vec![0.; 128][..], abs_all <= 0.);
341 assert_float_eq!(channel[128..], vec![1.; 128][..], abs_all <= 0.);
342 }
343
344 #[test]
345 fn test_start_in_the_future_while_dropped() {
346 let sample_rate = 48000.;
347 let mut context = OfflineAudioContext::new(1, 4 * 128, sample_rate);
348
349 let mut src = context.create_constant_source();
350 src.connect(&context.destination());
351 src.start_at(258. / sample_rate as f64); drop(src); let buffer = context.start_rendering_sync();
355 let channel = buffer.get_channel_data(0);
356
357 assert_float_eq!(channel[0..258], vec![0.; 258][..], abs_all <= 0.);
358 assert_float_eq!(channel[258..], vec![1.; 254][..], abs_all <= 0.);
359 }
360}