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