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 start_stop_count: u8,
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_eq!(
109 self.start_stop_count, 0,
110 "InvalidStateError - Cannot call `start` twice"
111 );
112
113 self.start_stop_count += 1;
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_eq!(
125 self.start_stop_count, 1,
126 "InvalidStateError cannot stop before start"
127 );
128
129 self.start_stop_count += 1;
130 self.registration.post_message(Schedule::Stop(when));
131 }
132}
133
134impl ConstantSourceNode {
135 pub fn new<C: BaseAudioContext>(context: &C, options: ConstantSourceOptions) -> Self {
136 context.base().register(move |registration| {
137 let ConstantSourceOptions { offset } = options;
138
139 let param_options = AudioParamDescriptor {
140 name: String::new(),
141 min_value: f32::MIN,
142 max_value: f32::MAX,
143 default_value: 1.,
144 automation_rate: AutomationRate::A,
145 };
146 let (param, proc) = context.create_audio_param(param_options, ®istration);
147 param.set_value(offset);
148
149 let render = ConstantSourceRenderer {
150 offset: proc,
151 start_time: f64::MAX,
152 stop_time: f64::MAX,
153 ended_triggered: false,
154 };
155
156 let node = ConstantSourceNode {
157 registration,
158 channel_config: ChannelConfig::default(),
159 offset: param,
160 start_stop_count: 0,
161 };
162
163 (node, Box::new(render))
164 })
165 }
166
167 pub fn offset(&self) -> &AudioParam {
168 &self.offset
169 }
170}
171
172struct ConstantSourceRenderer {
173 offset: AudioParamId,
174 start_time: f64,
175 stop_time: f64,
176 ended_triggered: bool,
177}
178
179impl AudioProcessor for ConstantSourceRenderer {
180 fn process(
181 &mut self,
182 _inputs: &[AudioRenderQuantum],
183 outputs: &mut [AudioRenderQuantum],
184 params: AudioParamValues<'_>,
185 scope: &AudioWorkletGlobalScope,
186 ) -> bool {
187 let output = &mut outputs[0];
189
190 let dt = 1. / scope.sample_rate as f64;
191 let next_block_time = scope.current_time + dt * RENDER_QUANTUM_SIZE as f64;
192
193 if self.start_time >= next_block_time {
194 output.make_silent();
195 return self.start_time != f64::MAX;
198 }
199
200 output.force_mono();
201
202 let offset = params.get(&self.offset);
203 let output_channel = output.channel_data_mut(0);
204
205 if offset.len() == 1
207 && self.start_time <= scope.current_time
208 && self.stop_time >= next_block_time
209 {
210 output_channel.fill(offset[0]);
211 } else {
212 let mut current_time = scope.current_time;
214
215 output_channel
216 .iter_mut()
217 .zip(offset.iter().cycle())
218 .for_each(|(o, &value)| {
219 if current_time < self.start_time || current_time >= self.stop_time {
220 *o = 0.;
221 } else {
222 *o = value;
226 }
227
228 current_time += dt;
229 });
230 }
231
232 let still_running = self.stop_time >= next_block_time;
234
235 if !still_running {
236 if !self.ended_triggered {
239 scope.send_ended_event();
240 self.ended_triggered = true;
241 }
242 }
243
244 still_running
245 }
246
247 fn onmessage(&mut self, msg: &mut dyn Any) {
248 if let Some(schedule) = msg.downcast_ref::<Schedule>() {
249 match *schedule {
250 Schedule::Start(v) => self.start_time = v,
251 Schedule::Stop(v) => self.stop_time = v,
252 }
253 return;
254 }
255
256 log::warn!("ConstantSourceRenderer: Dropping incoming message {msg:?}");
257 }
258
259 fn before_drop(&mut self, scope: &AudioWorkletGlobalScope) {
260 if !self.ended_triggered && scope.current_time >= self.start_time {
261 scope.send_ended_event();
262 self.ended_triggered = true;
263 }
264 }
265}
266
267#[cfg(test)]
268mod tests {
269 use crate::context::{BaseAudioContext, OfflineAudioContext};
270 use crate::node::{AudioNode, AudioScheduledSourceNode};
271
272 use float_eq::assert_float_eq;
273
274 use super::*;
275
276 #[test]
277 fn test_audioparam_value_applies_immediately() {
278 let context = OfflineAudioContext::new(1, 128, 48000.);
279 let options = ConstantSourceOptions { offset: 12. };
280 let src = ConstantSourceNode::new(&context, options);
281 assert_float_eq!(src.offset.value(), 12., abs_all <= 0.);
282 }
283
284 #[test]
285 fn test_start_stop() {
286 let sample_rate = 48000.;
287 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);
290
291 let mut src = context.create_constant_source();
292 src.connect(&context.destination());
293
294 src.start_at(start_in_samples / sample_rate as f64);
295 src.stop_at(stop_in_samples / sample_rate as f64);
296
297 let buffer = context.start_rendering_sync();
298 let channel = buffer.get_channel_data(0);
299
300 assert_float_eq!(channel[0..128], vec![0.; 128][..], abs_all <= 0.);
302
303 let mut res = vec![1.; 128];
305 res[0] = 0.;
306 assert_float_eq!(channel[128..256], res[..], abs_all <= 0.);
307
308 let mut res = vec![0.; 128];
310 res[0] = 1.;
311 assert_float_eq!(channel[256..384], res[..], abs_all <= 0.);
312
313 assert_float_eq!(channel[384..512], vec![0.; 128][..], abs_all <= 0.);
315 }
316
317 #[test]
318 fn test_start_in_the_past() {
319 let sample_rate = 48000.;
320 let mut context = OfflineAudioContext::new(1, 2 * 128, sample_rate);
321
322 context.suspend_sync((128. / sample_rate).into(), |context| {
323 let mut src = context.create_constant_source();
324 src.connect(&context.destination());
325 src.start_at(0.);
326 });
327
328 let buffer = context.start_rendering_sync();
329 let channel = buffer.get_channel_data(0);
330
331 assert_float_eq!(channel[0..128], vec![0.; 128][..], abs_all <= 0.);
333 assert_float_eq!(channel[128..], vec![1.; 128][..], abs_all <= 0.);
334 }
335
336 #[test]
337 fn test_start_in_the_future_while_dropped() {
338 let sample_rate = 48000.;
339 let mut context = OfflineAudioContext::new(1, 4 * 128, sample_rate);
340
341 let mut src = context.create_constant_source();
342 src.connect(&context.destination());
343 src.start_at(258. / sample_rate as f64); drop(src); let buffer = context.start_rendering_sync();
347 let channel = buffer.get_channel_data(0);
348
349 assert_float_eq!(channel[0..258], vec![0.; 258][..], abs_all <= 0.);
350 assert_float_eq!(channel[258..], vec![1.; 254][..], abs_all <= 0.);
351 }
352}