bbx_dsp/blocks/generators/
oscillator.rs

1//! Waveform oscillator block.
2
3use bbx_core::random::XorShiftRng;
4
5#[cfg(feature = "simd")]
6use crate::sample::SIMD_LANES;
7#[cfg(feature = "simd")]
8use crate::waveform::generate_waveform_samples_simd;
9use crate::{
10    block::{Block, DEFAULT_GENERATOR_INPUT_COUNT, DEFAULT_GENERATOR_OUTPUT_COUNT},
11    context::DspContext,
12    parameter::{ModulationOutput, Parameter},
13    sample::Sample,
14    waveform::{Waveform, process_waveform_scalar},
15};
16
17/// A waveform oscillator for generating audio signals.
18///
19/// Supports standard waveforms (sine, square, sawtooth, triangle, pulse, noise).
20/// Frequency can be controlled via parameter modulation or MIDI note messages.
21///
22/// Uses PolyBLEP/PolyBLAMP for band-limited output, reducing aliasing artifacts.
23pub struct OscillatorBlock<S: Sample> {
24    /// Base frequency in Hz (can be modulated).
25    pub frequency: Parameter<S>,
26
27    /// Pitch offset in semitones (for pitch bend/modulation).
28    pub pitch_offset: Parameter<S>,
29
30    base_frequency: S,
31    midi_frequency: Option<S>,
32    phase: f64,
33    waveform: Waveform,
34    rng: XorShiftRng,
35}
36
37impl<S: Sample> OscillatorBlock<S> {
38    /// Create a new oscillator with the given frequency and waveform.
39    pub fn new(frequency: f64, waveform: Waveform, seed: Option<u64>) -> Self {
40        let freq = S::from_f64(frequency);
41        Self {
42            frequency: Parameter::Constant(freq),
43            pitch_offset: Parameter::Constant(S::ZERO),
44            base_frequency: freq,
45            midi_frequency: None,
46            phase: 0.0,
47            waveform,
48            rng: XorShiftRng::new(seed.unwrap_or_default()),
49        }
50    }
51
52    /// Set the MIDI-controlled frequency (called by voice manager on note-on).
53    pub fn set_midi_frequency(&mut self, frequency: S) {
54        self.midi_frequency = Some(frequency);
55    }
56
57    /// Clear the MIDI frequency (called on note-off or when returning to parameter control).
58    pub fn clear_midi_frequency(&mut self) {
59        self.midi_frequency = None;
60    }
61
62    /// Set the waveform type.
63    pub fn set_waveform(&mut self, waveform: Waveform) {
64        self.waveform = waveform;
65    }
66}
67
68impl<S: Sample> Block<S> for OscillatorBlock<S> {
69    fn process(&mut self, _inputs: &[&[S]], outputs: &mut [&mut [S]], modulation_values: &[S], context: &DspContext) {
70        let base = self.midi_frequency.unwrap_or(self.base_frequency);
71
72        let freq_hz = match &self.frequency {
73            Parameter::Constant(f) => self.midi_frequency.unwrap_or(*f),
74            Parameter::Modulated(block_id) => {
75                let mod_value = modulation_values.get(block_id.0).copied().unwrap_or(S::ZERO);
76                base + mod_value
77            }
78        };
79
80        let pitch_offset_semitones = match &self.pitch_offset {
81            Parameter::Constant(offset) => *offset,
82            Parameter::Modulated(block_id) => modulation_values.get(block_id.0).copied().unwrap_or(S::ZERO),
83        };
84
85        let freq = if pitch_offset_semitones != S::ZERO {
86            let multiplier = S::from_f64(2.0f64.powf(pitch_offset_semitones.to_f64() / 12.0));
87            freq_hz * multiplier
88        } else {
89            freq_hz
90        };
91
92        let phase_increment = freq.to_f64() / context.sample_rate * S::TAU.to_f64();
93
94        #[cfg(feature = "simd")]
95        {
96            use crate::waveform::DEFAULT_DUTY_CYCLE;
97
98            if !matches!(self.waveform, Waveform::Noise) {
99                let buffer_size = context.buffer_size;
100                let chunks = buffer_size / SIMD_LANES;
101                let remainder_start = chunks * SIMD_LANES;
102                let chunk_phase_step = phase_increment * SIMD_LANES as f64;
103
104                let base_phase = S::simd_splat(S::from_f64(self.phase));
105                let sample_inc_simd = S::simd_splat(S::from_f64(phase_increment));
106                let mut phases = base_phase + S::simd_lane_offsets() * sample_inc_simd;
107                let chunk_inc_simd = S::simd_splat(S::from_f64(chunk_phase_step));
108                let duty = S::from_f64(DEFAULT_DUTY_CYCLE);
109                let two_pi = S::simd_splat(S::TAU);
110                let inv_two_pi = S::simd_splat(S::INV_TAU);
111                let phase_inc_normalized = S::from_f64(phase_increment * S::INV_TAU.to_f64());
112                let tau = S::TAU.to_f64();
113                let inv_tau = 1.0 / tau;
114
115                for chunk_idx in 0..chunks {
116                    let phases_array = S::simd_to_array(phases);
117                    let phases_normalized: [S; SIMD_LANES] = [
118                        S::from_f64(phases_array[0].to_f64().rem_euclid(tau) * inv_tau),
119                        S::from_f64(phases_array[1].to_f64().rem_euclid(tau) * inv_tau),
120                        S::from_f64(phases_array[2].to_f64().rem_euclid(tau) * inv_tau),
121                        S::from_f64(phases_array[3].to_f64().rem_euclid(tau) * inv_tau),
122                    ];
123
124                    if let Some(samples) = generate_waveform_samples_simd::<S>(
125                        self.waveform,
126                        phases,
127                        phases_normalized,
128                        phase_inc_normalized,
129                        duty,
130                        two_pi,
131                        inv_two_pi,
132                    ) {
133                        let base = chunk_idx * SIMD_LANES;
134                        outputs[0][base..base + SIMD_LANES].copy_from_slice(&samples);
135                    }
136
137                    phases = phases + chunk_inc_simd;
138                }
139
140                self.phase += chunk_phase_step * chunks as f64;
141                self.phase = self.phase.rem_euclid(S::TAU.to_f64());
142
143                process_waveform_scalar(
144                    &mut outputs[0][remainder_start..],
145                    self.waveform,
146                    &mut self.phase,
147                    phase_increment,
148                    &mut self.rng,
149                    1.0,
150                );
151            } else {
152                process_waveform_scalar(
153                    outputs[0],
154                    self.waveform,
155                    &mut self.phase,
156                    phase_increment,
157                    &mut self.rng,
158                    1.0,
159                );
160            }
161        }
162
163        #[cfg(not(feature = "simd"))]
164        {
165            process_waveform_scalar(
166                outputs[0],
167                self.waveform,
168                &mut self.phase,
169                phase_increment,
170                &mut self.rng,
171                1.0,
172            );
173        }
174    }
175
176    #[inline]
177    fn input_count(&self) -> usize {
178        DEFAULT_GENERATOR_INPUT_COUNT
179    }
180
181    #[inline]
182    fn output_count(&self) -> usize {
183        DEFAULT_GENERATOR_OUTPUT_COUNT
184    }
185
186    #[inline]
187    fn modulation_outputs(&self) -> &[ModulationOutput] {
188        &[]
189    }
190}
191
192#[cfg(test)]
193mod tests {
194    use super::*;
195    use crate::channel::ChannelLayout;
196
197    fn test_context(buffer_size: usize) -> DspContext {
198        DspContext {
199            sample_rate: 44100.0,
200            num_channels: 1,
201            buffer_size,
202            current_sample: 0,
203            channel_layout: ChannelLayout::Mono,
204        }
205    }
206
207    fn test_oscillator<S: Sample>(waveform: Waveform, frequency: f64, buffer_size: usize) -> Vec<S> {
208        let mut osc = OscillatorBlock::<S>::new(frequency, waveform, Some(42));
209        let context = test_context(buffer_size);
210        let inputs: [&[S]; 0] = [];
211        let mut output = vec![S::ZERO; buffer_size];
212        let mut outputs: [&mut [S]; 1] = [&mut output];
213        osc.process(&inputs, &mut outputs, &[], &context);
214        output
215    }
216
217    #[test]
218    fn test_oscillator_input_output_counts_f32() {
219        let osc = OscillatorBlock::<f32>::new(440.0, Waveform::Sine, None);
220        assert_eq!(osc.input_count(), DEFAULT_GENERATOR_INPUT_COUNT);
221        assert_eq!(osc.output_count(), DEFAULT_GENERATOR_OUTPUT_COUNT);
222    }
223
224    #[test]
225    fn test_oscillator_input_output_counts_f64() {
226        let osc = OscillatorBlock::<f64>::new(440.0, Waveform::Sine, None);
227        assert_eq!(osc.input_count(), DEFAULT_GENERATOR_INPUT_COUNT);
228        assert_eq!(osc.output_count(), DEFAULT_GENERATOR_OUTPUT_COUNT);
229    }
230
231    #[test]
232    fn test_sine_output_range_f32() {
233        let output = test_oscillator::<f32>(Waveform::Sine, 440.0, 1024);
234        for sample in output {
235            assert!(sample >= -1.05 && sample <= 1.05, "Sine sample {} out of range", sample);
236        }
237    }
238
239    #[test]
240    fn test_sine_output_range_f64() {
241        let output = test_oscillator::<f64>(Waveform::Sine, 440.0, 1024);
242        for sample in output {
243            assert!(sample >= -1.05 && sample <= 1.05, "Sine sample {} out of range", sample);
244        }
245    }
246
247    #[test]
248    fn test_square_output_range_f32() {
249        let output = test_oscillator::<f32>(Waveform::Square, 440.0, 1024);
250        for sample in output {
251            assert!(sample >= -1.1 && sample <= 1.1, "Square sample {} out of range", sample);
252        }
253    }
254
255    #[test]
256    fn test_square_output_range_f64() {
257        let output = test_oscillator::<f64>(Waveform::Square, 440.0, 1024);
258        for sample in output {
259            assert!(sample >= -1.1 && sample <= 1.1, "Square sample {} out of range", sample);
260        }
261    }
262
263    #[test]
264    fn test_sawtooth_output_range_f32() {
265        let output = test_oscillator::<f32>(Waveform::Sawtooth, 440.0, 1024);
266        for sample in output {
267            assert!(
268                sample >= -1.1 && sample <= 1.1,
269                "Sawtooth sample {} out of range",
270                sample
271            );
272        }
273    }
274
275    #[test]
276    fn test_sawtooth_output_range_f64() {
277        let output = test_oscillator::<f64>(Waveform::Sawtooth, 440.0, 1024);
278        for sample in output {
279            assert!(
280                sample >= -1.1 && sample <= 1.1,
281                "Sawtooth sample {} out of range",
282                sample
283            );
284        }
285    }
286
287    #[test]
288    fn test_triangle_output_range_f32() {
289        let output = test_oscillator::<f32>(Waveform::Triangle, 440.0, 1024);
290        for sample in output {
291            assert!(
292                sample >= -1.1 && sample <= 1.1,
293                "Triangle sample {} out of range",
294                sample
295            );
296        }
297    }
298
299    #[test]
300    fn test_triangle_output_range_f64() {
301        let output = test_oscillator::<f64>(Waveform::Triangle, 440.0, 1024);
302        for sample in output {
303            assert!(
304                sample >= -1.1 && sample <= 1.1,
305                "Triangle sample {} out of range",
306                sample
307            );
308        }
309    }
310
311    #[test]
312    fn test_noise_output_range_f32() {
313        let output = test_oscillator::<f32>(Waveform::Noise, 440.0, 1024);
314        for sample in output {
315            assert!(sample >= -1.0 && sample <= 1.0, "Noise sample {} out of range", sample);
316        }
317    }
318
319    #[test]
320    fn test_noise_output_range_f64() {
321        let output = test_oscillator::<f64>(Waveform::Noise, 440.0, 1024);
322        for sample in output {
323            assert!(sample >= -1.0 && sample <= 1.0, "Noise sample {} out of range", sample);
324        }
325    }
326
327    #[test]
328    fn test_sine_produces_signal() {
329        let output = test_oscillator::<f32>(Waveform::Sine, 440.0, 512);
330        let max = output.iter().fold(0.0f32, |acc, &x| acc.max(x.abs()));
331        assert!(max > 0.5, "Sine should produce signal, max={}", max);
332    }
333
334    #[test]
335    fn test_deterministic_with_seed_f32() {
336        let output1 = test_oscillator::<f32>(Waveform::Sawtooth, 440.0, 256);
337        let output2 = test_oscillator::<f32>(Waveform::Sawtooth, 440.0, 256);
338        for (a, b) in output1.iter().zip(output2.iter()) {
339            assert!((a - b).abs() < 1e-6, "Same seed should produce identical output");
340        }
341    }
342
343    #[test]
344    fn test_deterministic_with_seed_f64() {
345        let output1 = test_oscillator::<f64>(Waveform::Sawtooth, 440.0, 256);
346        let output2 = test_oscillator::<f64>(Waveform::Sawtooth, 440.0, 256);
347        for (a, b) in output1.iter().zip(output2.iter()) {
348            assert!((a - b).abs() < 1e-12, "Same seed should produce identical output");
349        }
350    }
351
352    #[test]
353    fn test_phase_continuity_across_buffers_f32() {
354        let mut osc = OscillatorBlock::<f32>::new(440.0, Waveform::Sine, Some(42));
355        let context = test_context(256);
356        let inputs: [&[f32]; 0] = [];
357
358        let mut buffer1 = vec![0.0f32; 256];
359        let mut buffer2 = vec![0.0f32; 256];
360
361        {
362            let mut outputs: [&mut [f32]; 1] = [&mut buffer1];
363            osc.process(&inputs, &mut outputs, &[], &context);
364        }
365        {
366            let mut outputs: [&mut [f32]; 1] = [&mut buffer2];
367            osc.process(&inputs, &mut outputs, &[], &context);
368        }
369
370        let last = buffer1[255];
371        let first = buffer2[0];
372        let diff = (last - first).abs();
373        let samples_per_cycle = 44100.0 / 440.0;
374        let expected_diff_per_sample = 2.0 / samples_per_cycle;
375        assert!(
376            diff < expected_diff_per_sample * 3.0,
377            "Phase discontinuity detected: last={}, first={}, diff={}",
378            last,
379            first,
380            diff
381        );
382    }
383
384    #[test]
385    fn test_phase_continuity_across_buffers_f64() {
386        let mut osc = OscillatorBlock::<f64>::new(440.0, Waveform::Sine, Some(42));
387        let context = test_context(256);
388        let inputs: [&[f64]; 0] = [];
389
390        let mut buffer1 = vec![0.0f64; 256];
391        let mut buffer2 = vec![0.0f64; 256];
392
393        {
394            let mut outputs: [&mut [f64]; 1] = [&mut buffer1];
395            osc.process(&inputs, &mut outputs, &[], &context);
396        }
397        {
398            let mut outputs: [&mut [f64]; 1] = [&mut buffer2];
399            osc.process(&inputs, &mut outputs, &[], &context);
400        }
401
402        let last = buffer1[255];
403        let first = buffer2[0];
404        let diff = (last - first).abs();
405        let samples_per_cycle = 44100.0 / 440.0;
406        let expected_diff_per_sample = 2.0 / samples_per_cycle;
407        assert!(
408            diff < expected_diff_per_sample * 3.0,
409            "Phase discontinuity detected: last={}, first={}, diff={}",
410            last,
411            first,
412            diff
413        );
414    }
415
416    #[test]
417    fn test_frequency_accuracy_via_zero_crossings_f32() {
418        let freq = 440.0;
419        let sample_rate = 44100.0;
420        let num_buffers = 10;
421        let buffer_size = 512;
422        let total_samples = num_buffers * buffer_size;
423
424        let mut osc = OscillatorBlock::<f32>::new(freq, Waveform::Sine, Some(42));
425        let context = test_context(buffer_size);
426        let inputs: [&[f32]; 0] = [];
427
428        let mut all_samples = Vec::with_capacity(total_samples);
429        for _ in 0..num_buffers {
430            let mut buffer = vec![0.0f32; buffer_size];
431            {
432                let mut outputs: [&mut [f32]; 1] = [&mut buffer];
433                osc.process(&inputs, &mut outputs, &[], &context);
434            }
435            all_samples.extend(buffer);
436        }
437
438        let mut zero_crossings = 0;
439        for i in 1..all_samples.len() {
440            if (all_samples[i - 1] < 0.0 && all_samples[i] >= 0.0)
441                || (all_samples[i - 1] >= 0.0 && all_samples[i] < 0.0)
442            {
443                zero_crossings += 1;
444            }
445        }
446
447        let estimated_freq = (zero_crossings as f64 / 2.0) / (total_samples as f64 / sample_rate);
448        let freq_error = (estimated_freq - freq as f64).abs();
449        assert!(
450            freq_error < 5.0,
451            "Frequency error too large: expected {}, got {}, error={}",
452            freq,
453            estimated_freq,
454            freq_error
455        );
456    }
457
458    #[test]
459    fn test_midi_frequency_override_f32() {
460        let mut osc = OscillatorBlock::<f32>::new(440.0, Waveform::Sine, Some(42));
461        osc.set_midi_frequency(880.0);
462
463        let context = test_context(512);
464        let inputs: [&[f32]; 0] = [];
465        let mut output = vec![0.0f32; 512];
466        let mut outputs: [&mut [f32]; 1] = [&mut output];
467        osc.process(&inputs, &mut outputs, &[], &context);
468
469        let max = output.iter().fold(0.0f32, |acc, &x| acc.max(x.abs()));
470        assert!(max > 0.5, "Should produce signal with MIDI frequency");
471    }
472
473    #[test]
474    fn test_clear_midi_frequency_f32() {
475        let mut osc = OscillatorBlock::<f32>::new(440.0, Waveform::Sine, Some(42));
476        osc.set_midi_frequency(880.0);
477        osc.clear_midi_frequency();
478
479        let context = test_context(512);
480        let inputs: [&[f32]; 0] = [];
481        let mut output = vec![0.0f32; 512];
482        let mut outputs: [&mut [f32]; 1] = [&mut output];
483        osc.process(&inputs, &mut outputs, &[], &context);
484
485        let max = output.iter().fold(0.0f32, |acc, &x| acc.max(x.abs()));
486        assert!(max > 0.5, "Should produce signal after clearing MIDI frequency");
487    }
488
489    #[test]
490    fn test_set_waveform_f32() {
491        let mut osc = OscillatorBlock::<f32>::new(440.0, Waveform::Sine, Some(42));
492        osc.set_waveform(Waveform::Square);
493
494        let context = test_context(512);
495        let inputs: [&[f32]; 0] = [];
496        let mut output = vec![0.0f32; 512];
497        let mut outputs: [&mut [f32]; 1] = [&mut output];
498        osc.process(&inputs, &mut outputs, &[], &context);
499
500        let near_one = output.iter().filter(|&&x| (x.abs() - 1.0).abs() < 0.2).count();
501        assert!(
502            near_one > output.len() / 4,
503            "Square wave should have many samples near +/-1"
504        );
505    }
506
507    #[test]
508    fn test_low_frequency_f32() {
509        let output = test_oscillator::<f32>(Waveform::Sine, 20.0, 4096);
510        let max = output.iter().fold(0.0f32, |acc, &x| acc.max(x.abs()));
511        assert!(max > 0.3, "Low frequency should still produce signal");
512    }
513
514    #[test]
515    fn test_high_frequency_f32() {
516        let output = test_oscillator::<f32>(Waveform::Sine, 15000.0, 512);
517        let max = output.iter().fold(0.0f32, |acc, &x| acc.max(x.abs()));
518        assert!(max > 0.5, "High frequency should produce signal");
519    }
520
521    #[test]
522    fn test_nyquist_frequency_f32() {
523        let output = test_oscillator::<f32>(Waveform::Sine, 22000.0, 512);
524        for sample in output {
525            assert!(
526                sample.abs() <= 1.1,
527                "Near-Nyquist frequency should not produce artifacts above 1.0"
528            );
529        }
530    }
531
532    #[test]
533    fn test_pulse_output_range_f32() {
534        let output = test_oscillator::<f32>(Waveform::Pulse, 440.0, 1024);
535        for sample in output {
536            assert!(sample >= -1.1 && sample <= 1.1, "Pulse sample {} out of range", sample);
537        }
538    }
539
540    #[test]
541    fn test_pulse_output_range_f64() {
542        let output = test_oscillator::<f64>(Waveform::Pulse, 440.0, 1024);
543        for sample in output {
544            assert!(sample >= -1.1 && sample <= 1.1, "Pulse sample {} out of range", sample);
545        }
546    }
547
548    #[test]
549    fn test_noise_varies_f32() {
550        let output = test_oscillator::<f32>(Waveform::Noise, 440.0, 256);
551        let first = output[0];
552        let varies = output.iter().any(|&x| (x - first).abs() > 0.01);
553        assert!(varies, "Noise should produce varying values");
554    }
555
556    #[test]
557    fn test_noise_varies_f64() {
558        let output = test_oscillator::<f64>(Waveform::Noise, 440.0, 256);
559        let first = output[0];
560        let varies = output.iter().any(|&x| (x - first).abs() > 0.01);
561        assert!(varies, "Noise should produce varying values");
562    }
563}