1use std::any::Any;
2use std::f32::consts::PI;
3use std::fmt::Debug;
4use std::sync::OnceLock;
5
6use crate::context::{AudioContextRegistration, AudioParamId, BaseAudioContext};
7use crate::param::{AudioParam, AudioParamDescriptor, AutomationRate};
8use crate::render::{
9 AudioParamValues, AudioProcessor, AudioRenderQuantum, AudioWorkletGlobalScope,
10};
11use crate::PeriodicWave;
12use crate::{assert_valid_time_value, RENDER_QUANTUM_SIZE};
13
14use super::{AudioNode, AudioNodeOptions, AudioScheduledSourceNode, ChannelConfig};
15
16const SINE_TABLE_LENGTH_USIZE: usize = 2048;
17const SINE_TABLE_LENGTH_F32: f32 = SINE_TABLE_LENGTH_USIZE as f32;
18
19fn precomputed_sine_table() -> &'static [f32] {
21 static INSTANCE: OnceLock<Vec<f32>> = OnceLock::new();
22 INSTANCE.get_or_init(|| {
23 (0..SINE_TABLE_LENGTH_USIZE)
25 .map(|x| ((x as f32) * 2.0 * PI * (1. / (SINE_TABLE_LENGTH_F32))).sin())
26 .collect()
27 })
28}
29
30fn get_computed_freq(freq: f32, detune: f32) -> f64 {
31 freq as f64 * (detune as f64 / 1200.).exp2()
32}
33
34#[derive(Clone, Debug)]
47pub struct OscillatorOptions {
48 pub type_: OscillatorType,
50 pub frequency: f32,
52 pub detune: f32,
54 pub periodic_wave: Option<PeriodicWave>,
56 pub audio_node_options: AudioNodeOptions,
58}
59
60impl Default for OscillatorOptions {
61 fn default() -> Self {
62 Self {
63 type_: OscillatorType::default(),
64 frequency: 440.,
65 detune: 0.,
66 periodic_wave: None,
67 audio_node_options: AudioNodeOptions::default(),
68 }
69 }
70}
71
72#[derive(Debug, Copy, Clone, PartialEq, Eq, Default)]
74pub enum OscillatorType {
75 #[default]
77 Sine,
78 Square,
80 Sawtooth,
82 Triangle,
84 Custom,
86}
87
88impl From<u32> for OscillatorType {
89 fn from(i: u32) -> Self {
90 match i {
91 0 => OscillatorType::Sine,
92 1 => OscillatorType::Square,
93 2 => OscillatorType::Sawtooth,
94 3 => OscillatorType::Triangle,
95 4 => OscillatorType::Custom,
96 _ => unreachable!(),
97 }
98 }
99}
100
101#[derive(Debug, Copy, Clone)]
103enum Schedule {
104 Start(f64),
105 Stop(f64),
106}
107
108#[derive(Debug)]
138pub struct OscillatorNode {
139 registration: AudioContextRegistration,
141 channel_config: ChannelConfig,
143 frequency: AudioParam,
145 detune: AudioParam,
147 type_: OscillatorType,
149 has_start: bool,
151}
152
153impl AudioNode for OscillatorNode {
154 fn registration(&self) -> &AudioContextRegistration {
155 &self.registration
156 }
157
158 fn channel_config(&self) -> &ChannelConfig {
159 &self.channel_config
160 }
161
162 fn number_of_inputs(&self) -> usize {
164 0
165 }
166
167 fn number_of_outputs(&self) -> usize {
169 1
170 }
171}
172
173impl AudioScheduledSourceNode for OscillatorNode {
174 fn start(&mut self) {
175 let when = self.registration.context().current_time();
176 self.start_at(when);
177 }
178
179 fn start_at(&mut self, when: f64) {
180 assert_valid_time_value(when);
181 assert!(
182 !self.has_start,
183 "InvalidStateError - Cannot call `start` twice"
184 );
185
186 self.has_start = true;
187 self.registration.post_message(Schedule::Start(when));
188 }
189
190 fn stop(&mut self) {
191 let when = self.registration.context().current_time();
192 self.stop_at(when);
193 }
194
195 fn stop_at(&mut self, when: f64) {
196 assert_valid_time_value(when);
197 assert!(self.has_start, "InvalidStateError cannot stop before start");
198
199 self.registration.post_message(Schedule::Stop(when));
200 }
201}
202
203impl OscillatorNode {
204 pub fn new<C: BaseAudioContext>(context: &C, options: OscillatorOptions) -> Self {
211 let OscillatorOptions {
212 type_,
213 frequency,
214 detune,
215 audio_node_options: channel_config,
216 periodic_wave,
217 } = options;
218
219 let mut node = context.base().register(move |registration| {
220 let sample_rate = context.sample_rate();
221 let nyquist = sample_rate / 2.;
222
223 let freq_param_options = AudioParamDescriptor {
225 name: String::new(),
226 min_value: -nyquist,
227 max_value: nyquist,
228 default_value: 440.,
229 automation_rate: AutomationRate::A,
230 };
231 let (f_param, f_proc) = context.create_audio_param(freq_param_options, ®istration);
232 f_param.set_value(frequency);
233
234 let det_param_options = AudioParamDescriptor {
236 name: String::new(),
237 min_value: -153_600.,
238 max_value: 153_600.,
239 default_value: 0.,
240 automation_rate: AutomationRate::A,
241 };
242 let (det_param, det_proc) =
243 context.create_audio_param(det_param_options, ®istration);
244 det_param.set_value(detune);
245
246 let renderer = OscillatorRenderer {
247 type_,
248 frequency: f_proc,
249 detune: det_proc,
250 phase: 0.,
251 start_time: f64::MAX,
252 stop_time: f64::MAX,
253 started: false,
254 periodic_wave: None,
255 ended_triggered: false,
256 sine_table: precomputed_sine_table(),
257 };
258
259 let node = Self {
260 registration,
261 channel_config: channel_config.into(),
262 frequency: f_param,
263 detune: det_param,
264 type_,
265 has_start: false,
266 };
267
268 (node, Box::new(renderer))
269 });
270
271 if let Some(p_wave) = periodic_wave {
273 node.set_periodic_wave(p_wave);
274 }
275
276 node
277 }
278
279 #[must_use]
284 pub fn frequency(&self) -> &AudioParam {
285 &self.frequency
286 }
287
288 #[must_use]
295 pub fn detune(&self) -> &AudioParam {
296 &self.detune
297 }
298
299 #[must_use]
301 pub fn type_(&self) -> OscillatorType {
302 self.type_
303 }
304
305 pub fn set_type(&mut self, type_: OscillatorType) {
315 assert_ne!(
316 type_,
317 OscillatorType::Custom,
318 "InvalidStateError: Custom type cannot be set manually"
319 );
320
321 if self.type_ == OscillatorType::Custom {
323 return;
324 }
325
326 self.type_ = type_;
327 self.registration.post_message(type_);
328 }
329
330 pub fn set_periodic_wave(&mut self, periodic_wave: PeriodicWave) {
335 self.type_ = OscillatorType::Custom;
336 self.registration.post_message(periodic_wave);
337 }
338}
339
340struct OscillatorRenderer {
342 type_: OscillatorType,
344 frequency: AudioParamId,
346 detune: AudioParamId,
348 phase: f64,
350 start_time: f64,
352 stop_time: f64,
354 started: bool,
356 periodic_wave: Option<PeriodicWave>,
358 ended_triggered: bool,
360 sine_table: &'static [f32],
362}
363
364impl AudioProcessor for OscillatorRenderer {
365 fn process(
366 &mut self,
367 _inputs: &[AudioRenderQuantum],
368 outputs: &mut [AudioRenderQuantum],
369 params: AudioParamValues<'_>,
370 scope: &AudioWorkletGlobalScope,
371 ) -> bool {
372 let output = &mut outputs[0];
374 output.set_number_of_channels(1);
376
377 let sample_rate = scope.sample_rate as f64;
378 let dt = 1. / sample_rate;
379 let num_frames = RENDER_QUANTUM_SIZE;
380 let next_block_time = scope.current_time + dt * num_frames as f64;
381
382 if self.stop_time <= scope.current_time {
383 output.make_silent();
384
385 if !self.ended_triggered {
386 scope.send_ended_event();
387 self.ended_triggered = true;
388 }
389
390 return false;
391 } else if self.start_time >= next_block_time {
392 output.make_silent();
393
394 if self.stop_time <= next_block_time {
395 if !self.ended_triggered {
396 scope.send_ended_event();
397 self.ended_triggered = true;
398 }
399
400 return false;
401 }
402
403 return self.start_time != f64::MAX;
406 }
407
408 let channel_data = output.channel_data_mut(0);
409 let frequency_values = params.get(&self.frequency);
410 let detune_values = params.get(&self.detune);
411
412 let mut current_time = scope.current_time;
413
414 if !self.started && self.start_time < current_time {
420 self.start_time = current_time;
421 }
422
423 let nyquist = sample_rate / 2.;
424
425 if frequency_values.len() == 1 && detune_values.len() == 1 {
427 let freq = frequency_values[0];
428 let detune = detune_values[0];
429 let computed_freq = get_computed_freq(freq, detune);
430 let phase_incr = computed_freq / sample_rate;
431 let outside_nyquist = computed_freq.abs() >= nyquist;
432 let fully_active = self.started
433 && self.start_time <= scope.current_time
434 && self.stop_time >= next_block_time;
435
436 if fully_active && !outside_nyquist {
437 channel_data.iter_mut().for_each(|output| {
438 *output = self.generate_waveform_sample(phase_incr);
439 self.phase = Self::unroll_phase(self.phase + phase_incr);
440 });
441 } else {
442 channel_data.iter_mut().for_each(|output| {
443 current_time =
444 self.generate_sample(output, outside_nyquist, phase_incr, current_time, dt);
445 });
446 }
447 } else {
448 channel_data
449 .iter_mut()
450 .zip(frequency_values.iter().cycle())
451 .zip(detune_values.iter().cycle())
452 .for_each(|((output, &freq), &detune)| {
453 let computed_freq = get_computed_freq(freq, detune);
454 let phase_incr = computed_freq / sample_rate;
455 let outside_nyquist = computed_freq.abs() >= nyquist;
456 current_time =
457 self.generate_sample(output, outside_nyquist, phase_incr, current_time, dt)
458 });
459 }
460
461 if self.stop_time <= next_block_time {
462 if !self.ended_triggered {
463 scope.send_ended_event();
464 self.ended_triggered = true;
465 }
466
467 return false;
468 }
469
470 true
471 }
472
473 fn onmessage(&mut self, msg: &mut dyn Any) {
474 if let Some(&type_) = msg.downcast_ref::<OscillatorType>() {
475 self.type_ = type_;
476 return;
477 }
478
479 if let Some(&schedule) = msg.downcast_ref::<Schedule>() {
480 match schedule {
481 Schedule::Start(v) => self.start_time = v,
482 Schedule::Stop(v) => self.stop_time = v,
483 }
484 return;
485 }
486
487 if let Some(periodic_wave) = msg.downcast_mut::<PeriodicWave>() {
488 if let Some(current_periodic_wave) = &mut self.periodic_wave {
489 std::mem::swap(current_periodic_wave, periodic_wave)
491 } else {
492 self.periodic_wave = Some(std::mem::take(periodic_wave));
494 }
495 self.type_ = OscillatorType::Custom; return;
497 }
498
499 log::warn!("OscillatorRenderer: Dropping incoming message {msg:?}");
500 }
501
502 fn before_drop(&mut self, scope: &AudioWorkletGlobalScope) {
503 if !self.ended_triggered
504 && (scope.current_time >= self.start_time || scope.current_time >= self.stop_time)
505 {
506 scope.send_ended_event();
507 self.ended_triggered = true;
508 }
509 }
510}
511impl OscillatorRenderer {
512 #[inline]
513 fn generate_sample(
514 &mut self,
515 output: &mut f32,
516 outside_nyquist: bool,
517 phase_incr: f64,
518 current_time: f64,
519 dt: f64,
520 ) -> f64 {
521 if current_time < self.start_time || current_time >= self.stop_time {
522 *output = 0.;
523 return current_time + dt;
524 }
525
526 if !self.started {
528 if current_time > self.start_time {
531 let ratio = (current_time - self.start_time) / dt;
532 self.phase = if outside_nyquist {
533 Self::unroll_phase_unbounded(phase_incr * ratio)
534 } else {
535 Self::unroll_phase(phase_incr * ratio)
536 };
537 }
538
539 self.started = true;
540 }
541
542 *output = if outside_nyquist {
543 0.
547 } else {
548 self.generate_waveform_sample(phase_incr)
549 };
550
551 self.phase = if outside_nyquist {
552 Self::unroll_phase_unbounded(self.phase + phase_incr)
553 } else {
554 Self::unroll_phase(self.phase + phase_incr)
555 };
556
557 current_time + dt
558 }
559
560 #[inline]
561 fn generate_waveform_sample(&mut self, phase_incr: f64) -> f32 {
562 match self.type_ {
563 OscillatorType::Sine => self.generate_sine(),
564 OscillatorType::Sawtooth => self.generate_sawtooth(phase_incr),
565 OscillatorType::Square => self.generate_square(phase_incr),
566 OscillatorType::Triangle => self.generate_triangle(),
567 OscillatorType::Custom => self.generate_custom(),
568 }
569 }
570
571 #[inline]
572 fn generate_sine(&mut self) -> f32 {
573 let position = self.phase * SINE_TABLE_LENGTH_USIZE as f64;
574 let floored = position.floor();
575
576 let prev_index = floored as usize;
577 let mut next_index = prev_index + 1;
578 if next_index == SINE_TABLE_LENGTH_USIZE {
579 next_index = 0;
580 }
581
582 let k = (position - floored) as f32;
584 self.sine_table[prev_index].mul_add(1. - k, self.sine_table[next_index] * k)
585 }
586
587 #[inline]
588 fn generate_sawtooth(&mut self, phase_incr: f64) -> f32 {
589 let phase = Self::unroll_phase(self.phase + 0.5);
591 let mut sample = 2.0 * phase - 1.0;
592 sample -= Self::poly_blep(phase, phase_incr, cfg!(test));
593
594 sample as f32
595 }
596
597 #[inline]
598 fn generate_square(&mut self, phase_incr: f64) -> f32 {
599 let mut sample = if self.phase < 0.5 { 1.0 } else { -1.0 };
600 sample += Self::poly_blep(self.phase, phase_incr, cfg!(test));
601
602 let shift_phase = Self::unroll_phase(self.phase + 0.5);
603 sample -= Self::poly_blep(shift_phase, phase_incr, cfg!(test));
604
605 sample as f32
606 }
607
608 #[inline]
609 fn generate_triangle(&mut self) -> f32 {
610 let mut sample = -4. * self.phase + 2.;
611
612 if sample > 1. {
613 sample = 2. - sample;
614 } else if sample < -1. {
615 sample = -2. - sample;
616 }
617
618 sample as f32
619 }
620
621 #[inline]
622 fn generate_custom(&mut self) -> f32 {
623 let periodic_wave = self.periodic_wave.as_ref().unwrap().as_slice();
624 let table_length = periodic_wave.len();
625 let position = self.phase * table_length as f64;
626 let floored = position.floor();
627
628 let prev_index = floored as usize;
629 let mut next_index = prev_index + 1;
630 if next_index == table_length {
631 next_index = 0;
632 }
633
634 let k = (position - floored) as f32;
636 periodic_wave[prev_index].mul_add(1. - k, periodic_wave[next_index] * k)
637 }
638
639 #[inline]
647 fn poly_blep(mut t: f64, dt: f64, is_test: bool) -> f64 {
648 if is_test {
649 0.
650 } else if t < dt {
651 t /= dt;
652 t + t - t * t - 1.0
653 } else if t > 1.0 - dt {
654 t = (t - 1.0) / dt;
655 t.mul_add(t, t) + t + 1.0
656 } else {
657 0.0
658 }
659 }
660
661 #[inline]
662 fn unroll_phase(phase: f64) -> f64 {
663 if phase >= 1. {
664 phase - 1.
665 } else if phase < 0. {
666 phase + 1.
667 } else {
668 phase
669 }
670 }
671
672 #[inline]
673 fn unroll_phase_unbounded(phase: f64) -> f64 {
674 phase.rem_euclid(1.)
675 }
676}
677
678#[cfg(test)]
679mod tests {
680 use float_eq::assert_float_eq;
681 use std::f64::consts::PI;
682
683 use crate::context::{BaseAudioContext, OfflineAudioContext};
684 use crate::node::{AudioNode, AudioScheduledSourceNode};
685 use crate::periodic_wave::{PeriodicWave, PeriodicWaveOptions};
686 use crate::RENDER_QUANTUM_SIZE;
687
688 use super::{OscillatorNode, OscillatorOptions, OscillatorRenderer, OscillatorType};
689
690 #[test]
691 fn assert_osc_default_build_with_factory_func() {
692 let default_freq = 440.;
693 let default_det = 0.;
694 let default_type = OscillatorType::Sine;
695
696 let mut context = OfflineAudioContext::new(2, 1, 44_100.);
697
698 let mut osc = context.create_oscillator();
699
700 let freq = osc.frequency.value();
701 assert_float_eq!(freq, default_freq, abs_all <= 0.);
702
703 let det = osc.detune.value();
704 assert_float_eq!(det, default_det, abs_all <= 0.);
705
706 assert_eq!(osc.type_(), default_type);
707
708 osc.start();
710 osc.connect(&context.destination());
711 let _ = context.start_rendering_sync();
712 }
713
714 #[test]
715 fn assert_osc_default_build() {
716 let default_freq = 440.;
717 let default_det = 0.;
718 let default_type = OscillatorType::Sine;
719
720 let mut context = OfflineAudioContext::new(2, 1, 44_100.);
721
722 let mut osc = OscillatorNode::new(&context, OscillatorOptions::default());
723
724 let freq = osc.frequency.value();
725 assert_float_eq!(freq, default_freq, abs_all <= 0.);
726
727 let det = osc.detune.value();
728 assert_float_eq!(det, default_det, abs_all <= 0.);
729
730 assert_eq!(osc.type_(), default_type);
731
732 osc.start();
734 osc.connect(&context.destination());
735 let _ = context.start_rendering_sync();
736 }
737
738 #[test]
739 #[should_panic]
740 fn set_type_to_custom_should_panic() {
741 let context = OfflineAudioContext::new(2, 1, 44_100.);
742 let mut osc = OscillatorNode::new(&context, OscillatorOptions::default());
743 osc.set_type(OscillatorType::Custom);
744 }
745
746 #[test]
747 fn type_is_custom_when_periodic_wave_is_some() {
748 let expected_type = OscillatorType::Custom;
749
750 let mut context = OfflineAudioContext::new(2, 1, 44_100.);
751
752 let periodic_wave = PeriodicWave::new(&context, PeriodicWaveOptions::default());
753
754 let options = OscillatorOptions {
755 periodic_wave: Some(periodic_wave),
756 ..OscillatorOptions::default()
757 };
758
759 let mut osc = OscillatorNode::new(&context, options);
760
761 assert_eq!(osc.type_(), expected_type);
762
763 osc.start();
765 osc.connect(&context.destination());
766 let _ = context.start_rendering_sync();
767 }
768
769 #[test]
770 fn set_type_is_ignored_when_periodic_wave_is_some() {
771 let expected_type = OscillatorType::Custom;
772
773 let mut context = OfflineAudioContext::new(2, 1, 44_100.);
774
775 let periodic_wave = PeriodicWave::new(&context, PeriodicWaveOptions::default());
776
777 let options = OscillatorOptions {
778 periodic_wave: Some(periodic_wave),
779 ..OscillatorOptions::default()
780 };
781
782 let mut osc = OscillatorNode::new(&context, options);
783
784 osc.set_type(OscillatorType::Sine);
785 assert_eq!(osc.type_(), expected_type);
786
787 osc.start();
789 osc.connect(&context.destination());
790 let _ = context.start_rendering_sync();
791 }
792
793 #[test]
807 fn sine_raw() {
808 for i in 0..5 {
810 let freq = 10_f32.powf(i as f32);
811 let sample_rate = 44_100;
812
813 let mut context = OfflineAudioContext::new(1, sample_rate, sample_rate as f32);
814
815 let mut osc = context.create_oscillator();
816 osc.connect(&context.destination());
817 osc.frequency().set_value(freq);
818 osc.start_at(0.);
819
820 let output = context.start_rendering_sync();
821 let result = output.get_channel_data(0);
822
823 let mut expected = Vec::<f32>::with_capacity(sample_rate);
824 let mut phase: f64 = 0.;
825 let phase_incr = freq as f64 / sample_rate as f64;
826
827 for _i in 0..sample_rate {
828 let sample = (phase * 2. * PI).sin();
829
830 expected.push(sample as f32);
831
832 phase += phase_incr;
833 if phase >= 1. {
834 phase -= 1.;
835 }
836 }
837
838 assert_float_eq!(result[..], expected[..], abs_all <= 1e-5);
839 }
840 }
841
842 #[test]
843 fn sine_raw_exact_phase() {
844 for i in 0..5 {
846 let freq = 10_f32.powf(i as f32);
847 let sample_rate = 44_100;
848
849 let mut context = OfflineAudioContext::new(1, sample_rate, sample_rate as f32);
850
851 let mut osc = context.create_oscillator();
852 osc.connect(&context.destination());
853 osc.frequency().set_value(freq);
854 osc.start_at(0.);
855
856 let output = context.start_rendering_sync();
857 let result = output.get_channel_data(0);
858 let mut expected = Vec::<f32>::with_capacity(sample_rate);
859
860 for i in 0..sample_rate {
861 let phase = freq as f64 * i as f64 / sample_rate as f64;
862 let sample = (phase * 2. * PI).sin();
863 expected.push(sample as f32);
865 }
866
867 assert_float_eq!(result[..], expected[..], abs_all <= 1e-5);
868 }
869 }
870
871 #[test]
872 fn square_raw() {
873 for i in 0..5 {
875 let freq = 10_f32.powf(i as f32);
876 let sample_rate = 44100;
877
878 let mut context = OfflineAudioContext::new(1, sample_rate, sample_rate as f32);
879
880 let mut osc = context.create_oscillator();
881 osc.connect(&context.destination());
882 osc.frequency().set_value(freq);
883 osc.set_type(OscillatorType::Square);
884 osc.start_at(0.);
885
886 let output = context.start_rendering_sync();
887 let result = output.get_channel_data(0);
888
889 let mut expected = Vec::<f32>::with_capacity(sample_rate);
890 let mut phase: f64 = 0.;
891 let phase_incr = freq as f64 / sample_rate as f64;
892
893 for _i in 0..sample_rate {
894 let sample = if phase < 0.5 { 1. } else { -1. };
896
897 expected.push(sample as f32);
898
899 phase += phase_incr;
900 if phase >= 1. {
901 phase -= 1.;
902 }
903 }
904
905 assert_float_eq!(result[..], expected[..], abs_all <= 1e-10);
906 }
907 }
908
909 #[test]
910 fn triangle_raw() {
911 for i in 0..5 {
913 let freq = 10_f32.powf(i as f32);
914 let sample_rate = 44_100;
915
916 let mut context = OfflineAudioContext::new(1, sample_rate, sample_rate as f32);
917
918 let mut osc = context.create_oscillator();
919 osc.connect(&context.destination());
920 osc.frequency().set_value(freq);
921 osc.set_type(OscillatorType::Triangle);
922 osc.start_at(0.);
923
924 let output = context.start_rendering_sync();
925 let result = output.get_channel_data(0);
926
927 let mut expected = Vec::<f32>::with_capacity(sample_rate);
928 let mut phase: f64 = 0.;
929 let phase_incr = freq as f64 / sample_rate as f64;
930
931 for _i in 0..sample_rate {
932 let mut sample = -4. * phase + 2.;
937
938 if sample > 1. {
939 sample = 2. - sample;
940 } else if sample < -1. {
941 sample = -2. - sample;
942 }
943
944 expected.push(sample as f32);
945
946 phase += phase_incr;
947 if phase >= 1. {
948 phase -= 1.;
949 }
950 }
951
952 assert_float_eq!(result[..], expected[..], abs_all <= 1e-10);
953 }
954 }
955
956 #[test]
957 fn sawtooth_raw() {
958 for i in 0..5 {
960 let freq = 10_f32.powf(i as f32);
961 let sample_rate = 44_100;
962
963 let mut context = OfflineAudioContext::new(1, sample_rate, sample_rate as f32);
964
965 let mut osc = context.create_oscillator();
966 osc.connect(&context.destination());
967 osc.frequency().set_value(freq);
968 osc.set_type(OscillatorType::Sawtooth);
969 osc.start_at(0.);
970
971 let output = context.start_rendering_sync();
972 let result = output.get_channel_data(0);
973
974 let mut expected = Vec::<f32>::with_capacity(sample_rate);
975 let mut phase: f64 = 0.;
976 let phase_incr = freq as f64 / sample_rate as f64;
977
978 for _i in 0..sample_rate {
979 let mut offset_phase = phase + 0.5;
983 if offset_phase >= 1. {
984 offset_phase -= 1.;
985 }
986 let sample = 2. * offset_phase - 1.;
987
988 expected.push(sample as f32);
989
990 phase += phase_incr;
991 if phase >= 1. {
992 phase -= 1.;
993 }
994 }
995
996 assert_float_eq!(result[..], expected[..], abs_all <= 1e-10);
997 }
998 }
999
1000 #[test]
1001 fn periodic_wave_1f() {
1003 for i in 0..5 {
1005 let freq = 10_f32.powf(i as f32);
1006 let sample_rate = 44_100;
1007
1008 let mut context = OfflineAudioContext::new(1, sample_rate, sample_rate as f32);
1009
1010 let options = PeriodicWaveOptions {
1011 real: Some(vec![0., 0.]),
1012 imag: Some(vec![0., 1.]), disable_normalization: false,
1014 };
1015
1016 let periodic_wave = context.create_periodic_wave(options);
1017
1018 let mut osc = context.create_oscillator();
1019 osc.connect(&context.destination());
1020 osc.set_periodic_wave(periodic_wave);
1021 osc.frequency().set_value(freq);
1022 osc.set_type(OscillatorType::Sawtooth);
1023 osc.start_at(0.);
1024
1025 let output = context.start_rendering_sync();
1026 let result = output.get_channel_data(0);
1027
1028 let mut expected = Vec::<f32>::with_capacity(sample_rate);
1029 let mut phase: f64 = 0.;
1030 let phase_incr = freq as f64 / sample_rate as f64;
1031
1032 for _i in 0..sample_rate {
1033 let sample = (phase * 2. * PI).sin();
1034
1035 expected.push(sample as f32);
1036
1037 phase += phase_incr;
1038 if phase >= 1. {
1039 phase -= 1.;
1040 }
1041 }
1042
1043 assert_float_eq!(result[..], expected[..], abs_all <= 1e-5);
1044 }
1045 }
1046
1047 #[test]
1048 fn periodic_wave_2f() {
1049 for i in 0..5 {
1051 let freq = 10_f32.powf(i as f32);
1052 let sample_rate = 44_100;
1053
1054 let mut context = OfflineAudioContext::new(1, sample_rate, sample_rate as f32);
1055
1056 let options = PeriodicWaveOptions {
1057 real: Some(vec![0., 0., 0.]),
1058 imag: Some(vec![0., 0.5, 0.5]),
1059 disable_normalization: true,
1061 };
1062
1063 let periodic_wave = context.create_periodic_wave(options);
1064
1065 let mut osc = context.create_oscillator();
1066 osc.connect(&context.destination());
1067 osc.set_periodic_wave(periodic_wave);
1068 osc.frequency().set_value(freq);
1069 osc.start_at(0.);
1070
1071 let output = context.start_rendering_sync();
1072 let result = output.get_channel_data(0);
1073
1074 let mut expected = Vec::<f32>::with_capacity(sample_rate);
1075 let mut phase: f64 = 0.;
1076 let phase_incr = freq as f64 / sample_rate as f64;
1077
1078 for _i in 0..sample_rate {
1079 let mut sample = 0.;
1080 sample += 0.5 * (1. * phase * 2. * PI).sin();
1081 sample += 0.5 * (2. * phase * 2. * PI).sin();
1082
1083 expected.push(sample as f32);
1084
1085 phase += phase_incr;
1086 if phase >= 1. {
1087 phase -= 1.;
1088 }
1089 }
1090
1091 assert_float_eq!(result[..], expected[..], abs_all <= 1e-5);
1092 }
1093 }
1094
1095 #[test]
1096 fn polyblep_isolated() {
1097 {
1103 let mut signal = [1., 1., 1., 1., -1., -1., -1., -1.];
1104 let len = signal.len() as f64;
1105 let dt = 1. / len;
1106
1107 for (index, s) in signal.iter_mut().enumerate() {
1108 let phase = index as f64 / len;
1109
1110 *s += OscillatorRenderer::poly_blep(phase, dt, false);
1111 *s -= OscillatorRenderer::poly_blep((phase + 0.5) % 1., dt, false);
1112 }
1113
1114 let expected = [0., 1., 1., 1., 0., -1., -1., -1.];
1115
1116 assert_float_eq!(signal[..], expected[..], abs_all <= 0.);
1117 }
1118
1119 {
1121 let mut signal = [0., 0.25, 0.75, 1., -1., -0.75, -0.5, -0.25];
1122 let len = signal.len() as f64;
1123 let dt = 1. / len;
1124
1125 for (index, s) in signal.iter_mut().enumerate() {
1126 let phase = index as f64 / len;
1127 *s -= OscillatorRenderer::poly_blep((phase + 0.5) % 1., dt, false);
1128 }
1129
1130 let expected = [0., 0.25, 0.75, 1., 0., -0.75, -0.5, -0.25];
1131 assert_float_eq!(signal[..], expected[..], abs_all <= 0.);
1132 }
1133 }
1134
1135 #[test]
1136 fn osc_sub_quantum_start() {
1137 let freq = 1.25;
1138 let sample_rate = 44_100;
1139
1140 let mut context = OfflineAudioContext::new(1, sample_rate, sample_rate as f32);
1141 let mut osc = context.create_oscillator();
1142 osc.connect(&context.destination());
1143 osc.frequency().set_value(freq);
1144 osc.start_at(2. / sample_rate as f64);
1145
1146 let output = context.start_rendering_sync();
1147 let result = output.get_channel_data(0);
1148
1149 let mut expected = Vec::<f32>::with_capacity(sample_rate);
1150 let mut phase: f64 = 0.;
1151 let phase_incr = freq as f64 / sample_rate as f64;
1152
1153 expected.push(0.);
1154 expected.push(0.);
1155
1156 for _i in 2..sample_rate {
1157 let sample = (phase * 2. * PI).sin();
1158 phase += phase_incr;
1159 expected.push(sample as f32);
1160 }
1161
1162 assert_float_eq!(result[..], expected[..], abs_all <= 1e-5);
1163 }
1164
1165 #[test]
1168 fn osc_sub_sample_start() {
1169 let freq = 1.;
1170 let sample_rate = 96000;
1171
1172 let mut context = OfflineAudioContext::new(1, sample_rate, sample_rate as f32);
1173 let mut osc = context.create_oscillator();
1174 osc.connect(&context.destination());
1175 osc.frequency().set_value(freq);
1176 osc.start_at(1.3 / sample_rate as f64);
1178
1179 let output = context.start_rendering_sync();
1180 let result = output.get_channel_data(0);
1181
1182 let mut expected = Vec::<f32>::with_capacity(sample_rate);
1183 let phase_incr = freq as f64 / sample_rate as f64;
1184 let mut phase: f64 = 0.7 * phase_incr;
1186
1187 expected.push(0.);
1188 expected.push(0.);
1189
1190 for _i in 2..sample_rate {
1191 let sample = (phase * 2. * PI).sin();
1192 phase += phase_incr;
1193 expected.push(sample as f32);
1194 }
1195
1196 assert_float_eq!(result[..], expected[..], abs_all <= 1e-5);
1197 }
1198
1199 #[test]
1200 fn osc_sub_quantum_stop() {
1201 let freq = 2345.6;
1202 let sample_rate = 44_100;
1203
1204 let mut context = OfflineAudioContext::new(1, sample_rate, sample_rate as f32);
1205 let mut osc = context.create_oscillator();
1206 osc.connect(&context.destination());
1207 osc.frequency().set_value(freq);
1208 osc.start_at(0.);
1209 osc.stop_at(6. / sample_rate as f64);
1210
1211 let output = context.start_rendering_sync();
1212 let result = output.get_channel_data(0);
1213
1214 let mut expected = Vec::<f32>::with_capacity(sample_rate);
1215 let mut phase: f64 = 0.;
1216 let phase_incr = freq as f64 / sample_rate as f64;
1217
1218 for i in 0..sample_rate {
1219 if i < 6 {
1220 let sample = (phase * 2. * PI).sin();
1221 phase += phase_incr;
1222 expected.push(sample as f32);
1223 } else {
1224 expected.push(0.);
1225 }
1226 }
1227
1228 assert_float_eq!(result[..], expected[..], abs_all <= 1e-5);
1229 }
1230
1231 #[test]
1232 fn osc_stop_disarms_future_start() {
1233 let sample_rate = 44_100;
1234 let future_start = 2. / sample_rate as f64;
1235
1236 let mut context = OfflineAudioContext::new(1, 128, sample_rate as f32);
1237 let mut osc = context.create_oscillator();
1238 osc.connect(&context.destination());
1239 osc.start_at(future_start);
1240 osc.stop();
1241
1242 let output = context.start_rendering_sync();
1243 let result = output.get_channel_data(0);
1244
1245 assert_float_eq!(result[..], vec![0.; 128][..], abs_all <= 0.);
1246 }
1247
1248 #[test]
1249 fn osc_stop_before_start_triggers_onended_without_waiting_for_start_time() {
1250 use std::sync::atomic::{AtomicBool, Ordering};
1251 use std::sync::Arc;
1252
1253 let sample_rate = 44_100.;
1254 let future_start = 2. * RENDER_QUANTUM_SIZE as f64 / sample_rate;
1255 let suspend_at = RENDER_QUANTUM_SIZE as f64 / sample_rate;
1256
1257 let ended = Arc::new(AtomicBool::new(false));
1258 let ended_in_callback = Arc::clone(&ended);
1259 let ended_after_render = Arc::clone(&ended);
1260
1261 let mut context = OfflineAudioContext::new(1, RENDER_QUANTUM_SIZE * 4, sample_rate as f32);
1262 let mut osc = context.create_oscillator();
1263 osc.connect(&context.destination());
1264 osc.start_at(future_start);
1265 osc.set_onended(move |_| {
1266 ended_in_callback.store(true, Ordering::Relaxed);
1267 });
1268 osc.stop();
1269
1270 context.suspend_sync(suspend_at, move |_| {
1271 assert!(ended_after_render.load(Ordering::Relaxed));
1272 });
1273
1274 let _ = context.start_rendering_sync();
1275 assert!(ended.load(Ordering::Relaxed));
1276 }
1277
1278 #[test]
1279 fn osc_sub_sample_stop() {
1280 let freq = 8910.1;
1281 let sample_rate = 44_100;
1282
1283 let mut context = OfflineAudioContext::new(1, sample_rate, sample_rate as f32);
1284 let mut osc = context.create_oscillator();
1285 osc.connect(&context.destination());
1286 osc.frequency().set_value(freq);
1287 osc.start_at(0.);
1288 osc.stop_at(19.4 / sample_rate as f64);
1289
1290 let output = context.start_rendering_sync();
1291 let result = output.get_channel_data(0);
1292
1293 let mut expected = Vec::<f32>::with_capacity(sample_rate);
1294 let mut phase: f64 = 0.;
1295 let phase_incr = freq as f64 / sample_rate as f64;
1296
1297 for i in 0..sample_rate {
1298 if i < 20 {
1299 let sample = (phase * 2. * PI).sin();
1300 phase += phase_incr;
1301 expected.push(sample as f32);
1302 } else {
1303 expected.push(0.);
1304 }
1305 }
1306
1307 assert_float_eq!(result[..], expected[..], abs_all <= 1e-5);
1308 }
1309
1310 #[test]
1311 fn test_start_in_the_past() {
1312 let freq = 8910.1;
1313 let sample_rate = 44_100;
1314
1315 let mut context = OfflineAudioContext::new(1, sample_rate, sample_rate as f32);
1316
1317 context.suspend_sync(128. / sample_rate as f64, move |context| {
1318 let mut osc = context.create_oscillator();
1319 osc.connect(&context.destination());
1320 osc.frequency().set_value(freq);
1321 osc.start_at(0.);
1322 });
1323
1324 let output = context.start_rendering_sync();
1325 let result = output.get_channel_data(0);
1326
1327 let mut expected = Vec::<f32>::with_capacity(sample_rate);
1328 let mut phase: f64 = 0.;
1329 let phase_incr = freq as f64 / sample_rate as f64;
1330
1331 for i in 0..sample_rate {
1332 if i < 128 {
1333 expected.push(0.);
1334 } else {
1335 let sample = (phase * 2. * PI).sin();
1336 expected.push(sample as f32);
1337 phase += phase_incr;
1338 }
1339 }
1340
1341 assert_float_eq!(result[..], expected[..], abs_all <= 1e-5);
1342 }
1343
1344 #[test]
1345 fn compute_freq_above_nyquist_outputs_zero() {
1346 let freq = 20000.;
1347 let detune = 1200.; let sample_rate = 44_100;
1349
1350 let mut context = OfflineAudioContext::new(1, 128, sample_rate as f32);
1351
1352 let mut osc = context.create_oscillator();
1353 osc.connect(&context.destination());
1354 osc.frequency().set_value(freq);
1355 osc.detune().set_value(detune);
1356 osc.start_at(0.);
1357
1358 let output = context.start_rendering_sync();
1359 let result = output.get_channel_data(0);
1360
1361 assert_float_eq!(result[..], [0.; 128], abs_all <= 1e-5);
1362 }
1363
1364 #[test]
1365 fn compute_freq_below_negative_nyquist_outputs_zero() {
1366 let freq = -20000.;
1367 let detune = 1200.; let sample_rate = 44_100;
1369
1370 let mut context = OfflineAudioContext::new(1, 128, sample_rate as f32);
1371
1372 let mut osc = context.create_oscillator();
1373 osc.connect(&context.destination());
1374 osc.frequency().set_value(freq);
1375 osc.detune().set_value(detune);
1376 osc.start_at(0.);
1377
1378 let output = context.start_rendering_sync();
1379 let result = output.get_channel_data(0);
1380
1381 assert_float_eq!(result[..], [0.; 128], abs_all <= 1e-5);
1382 }
1383
1384 #[test]
1385 fn oscillator_can_reenter_audible_range_after_large_phase_increments() {
1386 let sample_rate = 44_100;
1387 let mut context = OfflineAudioContext::new(1, 256, sample_rate as f32);
1388
1389 let mut osc = context.create_oscillator();
1390 osc.connect(&context.destination());
1391 osc.frequency().set_value(20_000.);
1392 osc.detune().set_value(2400.); osc.detune()
1394 .set_value_at_time(0., RENDER_QUANTUM_SIZE as f64 / sample_rate as f64);
1395 osc.start_at(0.);
1396
1397 let output = context.start_rendering_sync();
1398 let result = output.get_channel_data(0);
1399
1400 assert_float_eq!(
1401 result[..RENDER_QUANTUM_SIZE],
1402 [0.; RENDER_QUANTUM_SIZE],
1403 abs_all <= 1e-5
1404 );
1405 assert!(result[RENDER_QUANTUM_SIZE..].iter().all(|v| v.is_finite()));
1406 assert!(result[RENDER_QUANTUM_SIZE..].iter().any(|&v| v != 0.));
1407 }
1408
1409 #[test]
1410 fn oscillator_delayed_start_renders_first_fully_active_block() {
1411 let sample_rate = 44_100;
1412 let start_time = RENDER_QUANTUM_SIZE as f64 / sample_rate as f64;
1413 let mut context = OfflineAudioContext::new(1, RENDER_QUANTUM_SIZE * 2, sample_rate as f32);
1414
1415 let mut osc = context.create_oscillator();
1416 osc.connect(&context.destination());
1417 osc.start_at(start_time);
1418
1419 let output = context.start_rendering_sync();
1420 let result = output.get_channel_data(0);
1421
1422 assert_float_eq!(
1423 result[..RENDER_QUANTUM_SIZE],
1424 [0.; RENDER_QUANTUM_SIZE],
1425 abs_all <= 1e-5
1426 );
1427 assert!(result[RENDER_QUANTUM_SIZE..].iter().any(|&v| v != 0.));
1428 }
1429
1430 #[test]
1431 fn sine_negative_frequency() {
1432 let freq = -100.;
1433 let sample_rate = 44_100;
1434 let length = sample_rate as usize;
1435
1436 let mut context = OfflineAudioContext::new(1, length, sample_rate as f32);
1437
1438 let mut osc = context.create_oscillator();
1439 osc.connect(&context.destination());
1440 osc.frequency().set_value(freq);
1441 osc.start_at(0.);
1442
1443 let output = context.start_rendering_sync();
1444 let result = output.get_channel_data(0);
1445 let mut expected = Vec::<f32>::with_capacity(length);
1446
1447 for i in 0..length {
1448 let phase = freq as f64 * i as f64 / sample_rate as f64;
1449 let sample = (phase * 2. * PI).sin();
1450 expected.push(sample as f32);
1452 }
1453
1454 assert_float_eq!(result[..], expected[..], abs_all <= 1e-5);
1455 }
1456}