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