Skip to main content

rill_core_dsp/generators/
basic.rs

1//! Basic oscillators (Sine, Saw, Square, Triangle)
2
3use crate::generators::{Generator, ModulatableGenerator, SyncableGenerator};
4use crate::vector::prelude::*;
5use rill_core::math::vector::scalar::ScalarVector4;
6use rill_core::math::vector::traits::{Vector, VectorMask, VectorTranscendental};
7use rill_core::traits::algorithm::{Algorithm, AlgorithmCategory, AlgorithmMetadata};
8use rill_core::traits::parameter_write::ParameterWrite;
9use rill_core::traits::{ParamValue, ProcessError, ProcessResult};
10use rill_core::Transcendental;
11use std::f32::consts::PI;
12
13/// Waveform type
14#[derive(Debug, Clone, Copy, PartialEq)]
15pub enum Waveform {
16    /// Pure sine wave
17    Sine,
18    /// Sawtooth wave
19    Saw,
20    /// Square wave
21    Square,
22    /// Triangle wave
23    Triangle,
24    /// Pulse wave with adjustable duty cycle
25    Pulse(f32), // pulse width (0.0 - 1.0)
26}
27
28impl Waveform {
29    /// Get waveform name
30    pub fn name(&self) -> &'static str {
31        match self {
32            Waveform::Sine => "Sine",
33            Waveform::Saw => "Saw",
34            Waveform::Square => "Square",
35            Waveform::Triangle => "Triangle",
36            Waveform::Pulse(_) => "Pulse",
37        }
38    }
39
40    /// Get waveform description
41    pub fn description(&self) -> &'static str {
42        match self {
43            Waveform::Sine => "Pure sine wave - single harmonic",
44            Waveform::Saw => "Sawtooth wave - all harmonics (1/n)",
45            Waveform::Square => "Square wave - odd harmonics (1/n)",
46            Waveform::Triangle => "Triangle wave - odd harmonics (1/n²)",
47            Waveform::Pulse(_) => "Pulse wave with variable width",
48        }
49    }
50}
51
52/// Basic oscillator
53///
54/// Generates various waveforms with support for:
55/// - Real-time frequency changes
56/// - Frequency modulation (FM)
57/// - Anti-aliasing for sawtooth wave
58/// - Phase synchronization
59#[derive(Clone, Copy)]
60pub struct BasicOscillator<T: Transcendental> {
61    /// Waveform type
62    waveform: Waveform,
63    /// Frequency (Hz)
64    frequency: f32,
65    /// Amplitude (0.0 - 1.0)
66    amplitude: ScalarVector1<T>,
67    /// Current phase (0..1)
68    phase: ScalarVector1<T>,
69    /// Phase increment per sample
70    phase_inc: ScalarVector1<T>,
71    /// Sample rate
72    sample_rate: f32,
73    /// Number of completed periods
74    periods: u32,
75    /// Frequency modulation (FM)
76    fm_amount: ScalarVector1<T>,
77}
78
79impl<T: Transcendental> BasicOscillator<T> {
80    /// Create a new oscillator
81    ///
82    /// # Arguments
83    /// * `waveform` - waveform shape
84    /// * `frequency` - frequency in Hz
85    /// * `amplitude` - amplitude (0.0 - 1.0)
86    pub fn new(waveform: Waveform, frequency: f32, amplitude: T) -> Self {
87        let mut osc = Self {
88            waveform,
89            frequency,
90            amplitude: ScalarVector1::splat(amplitude),
91            phase: ScalarVector1::splat(T::ZERO),
92            phase_inc: ScalarVector1::splat(T::ZERO),
93            sample_rate: 44100.0,
94            periods: 0,
95            fm_amount: ScalarVector1::splat(T::ZERO),
96        };
97        osc.update_phase_inc();
98        osc
99    }
100
101    /// Update phase increment based on current frequency
102    #[inline(always)]
103    fn update_phase_inc(&mut self) {
104        self.phase_inc = ScalarVector1::splat(T::from_f32(self.frequency / self.sample_rate));
105    }
106
107    /// SIMD block generation — processes `output` in chunks of 4,
108    /// falling back to scalar for remainder and high-frequency edge cases.
109    fn generate_block_simd(&mut self, output: &mut [T]) {
110        let chunks = output.len() / 4;
111        let _remainder = output.len() % 4;
112
113        if chunks > 0 {
114            let inc = self.phase_inc;
115            let inc4 = inc * ScalarVector1::splat(T::from_usize(4));
116            let one = ScalarVector1::splat(T::ONE);
117            let use_simd = (inc.extract(0) * T::from_usize(4)) < T::ONE;
118
119            if use_simd {
120                let mut phase = self.phase;
121                let amp_v = self.amplitude;
122
123                for chunk in 0..chunks {
124                    let offset = chunk * 4;
125                    let p0 = phase.extract(0);
126                    let inc_t = inc.extract(0);
127
128                    let phases = ScalarVector4::load(&[
129                        p0,
130                        p0 + inc_t,
131                        p0 + inc_t + inc_t,
132                        p0 + inc_t + inc_t + inc_t,
133                    ]);
134
135                    let vals = match self.waveform {
136                        Waveform::Sine => self.simd_sine(&phases, &amp_v),
137                        Waveform::Saw => self.simd_saw_blep(&phases, inc_t, &amp_v),
138                        Waveform::Square => self.simd_square(&phases, &amp_v),
139                        Waveform::Triangle => self.simd_triangle(&phases, &amp_v),
140                        Waveform::Pulse(width) => {
141                            self.simd_pulse(&phases, T::from_f32(width.clamp(0.01, 0.99)), &amp_v)
142                        }
143                    };
144
145                    vals.store(&mut output[offset..offset + 4]);
146
147                    phase = phase + inc4;
148                    if phase.extract(0) >= one.extract(0) {
149                        phase = phase - one;
150                        self.periods += 1;
151                    }
152                }
153                self.phase = phase;
154            } else {
155                // High frequency: fall back to scalar for the block
156                for out in output[..chunks * 4].iter_mut() {
157                    *out = self.generate_scalar();
158                }
159            }
160        }
161
162        // Scalar remainder
163        for out in output[chunks * 4..].iter_mut() {
164            *out = self.generate_scalar();
165        }
166    }
167
168    /// Generate ONE sample via the scalar path (same as old generate()).
169    /// Renamed from `generate()` to avoid confusion with SIMD methods.
170    fn generate_scalar(&mut self) -> T {
171        let effective_inc = self.phase_inc + self.fm_amount;
172        let output_vec = match self.waveform {
173            Waveform::Sine => self.scalar_sine(),
174            Waveform::Saw => self.scalar_saw_bandlimited(),
175            Waveform::Square => self.scalar_square(),
176            Waveform::Triangle => self.scalar_triangle(),
177            Waveform::Pulse(width) => self.scalar_pulse(width),
178        };
179        self.phase = self.phase + effective_inc;
180        let one = ScalarVector1::splat(T::ONE);
181        if self.phase.extract(0) >= one.extract(0) {
182            self.phase = self.phase - one;
183            self.periods += 1;
184        }
185        output_vec.extract(0)
186    }
187
188    // ─── Scalar waveform methods (renamed, same logic) ───
189
190    #[inline(always)]
191    fn scalar_sine(&self) -> ScalarVector1<T> {
192        let phase_rad = self.phase.mul(&ScalarVector1::splat(T::from_f32(2.0 * PI)));
193        phase_rad.sin().mul(&self.amplitude)
194    }
195
196    #[inline(always)]
197    fn scalar_saw_raw(&self) -> ScalarVector1<T> {
198        self.phase
199            .mul(&ScalarVector1::splat(T::from_f32(2.0)))
200            .sub(&ScalarVector1::splat(T::from_f32(1.0)))
201            .mul(&self.amplitude)
202    }
203
204    #[inline(always)]
205    fn scalar_saw_bandlimited(&mut self) -> ScalarVector1<T> {
206        let raw = self.scalar_saw_raw();
207        let next_phase = self.phase.add(&self.phase_inc).extract(0);
208        let one = T::from_f32(1.0);
209        if next_phase >= one {
210            let one_vec = ScalarVector1::splat(one);
211            let t = (one_vec - self.phase) / self.phase_inc;
212            let blep =
213                t * ScalarVector1::splat(T::from_f32(2.0)) - ScalarVector1::splat(T::from_f32(1.0));
214            raw - blep * self.amplitude
215        } else {
216            raw
217        }
218    }
219
220    #[inline(always)]
221    fn scalar_square(&self) -> ScalarVector1<T> {
222        let half = T::from_f32(0.5);
223        if self.phase.extract(0) < half {
224            self.amplitude
225        } else {
226            -self.amplitude
227        }
228    }
229
230    #[inline(always)]
231    fn scalar_triangle(&self) -> ScalarVector1<T> {
232        let half = ScalarVector1::splat(T::from_f32(0.5));
233        let p = self.phase - half;
234        (p.abs() * ScalarVector1::splat(T::from_f32(4.0)) - ScalarVector1::splat(T::from_f32(1.0)))
235            * self.amplitude
236    }
237
238    #[inline(always)]
239    fn scalar_pulse(&self, width: f32) -> ScalarVector1<T> {
240        let width_t = T::from_f32(width.clamp(0.01, 0.99));
241        if self.phase.extract(0) < width_t {
242            self.amplitude
243        } else {
244            -self.amplitude
245        }
246    }
247
248    // ─── SIMD waveform methods (4 lanes at once) ───
249
250    #[inline(always)]
251    fn simd_sine(&self, phases: &ScalarVector4<T>, amp: &ScalarVector1<T>) -> ScalarVector4<T> {
252        let pi2 = ScalarVector4::splat(T::from_f32(2.0 * PI));
253        let rad = phases.mul(&pi2);
254        let raw = rad.sin();
255        let amp_broadcast = ScalarVector4::splat(amp.extract(0));
256        raw.mul(&amp_broadcast)
257    }
258
259    #[inline(always)]
260    fn simd_triangle(&self, phases: &ScalarVector4<T>, amp: &ScalarVector1<T>) -> ScalarVector4<T> {
261        let half = ScalarVector4::splat(T::from_f32(0.5));
262        let four = ScalarVector4::splat(T::from_f32(4.0));
263        let one = ScalarVector4::splat(T::from_f32(1.0));
264        let amp_b = ScalarVector4::splat(amp.extract(0));
265        let p = phases.sub(&half);
266        p.abs().mul(&four).sub(&one).mul(&amp_b)
267    }
268
269    #[inline(always)]
270    fn simd_square(&self, phases: &ScalarVector4<T>, amp: &ScalarVector1<T>) -> ScalarVector4<T> {
271        let half = ScalarVector4::splat(T::from_f32(0.5));
272        let pos = ScalarVector4::splat(amp.extract(0));
273        let neg = ScalarVector4::splat(-amp.extract(0));
274        let mask = phases.lt(&half);
275        <ScalarVector4<T> as VectorMask<T, 4>>::select(&pos, &neg, mask)
276    }
277
278    #[inline(always)]
279    fn simd_pulse(
280        &self,
281        phases: &ScalarVector4<T>,
282        width_t: T,
283        amp: &ScalarVector1<T>,
284    ) -> ScalarVector4<T> {
285        let threshold = ScalarVector4::splat(width_t);
286        let pos = ScalarVector4::splat(amp.extract(0));
287        let neg = ScalarVector4::splat(-amp.extract(0));
288        let mask = phases.lt(&threshold);
289        <ScalarVector4<T> as VectorMask<T, 4>>::select(&pos, &neg, mask)
290    }
291
292    #[inline(always)]
293    fn simd_saw_raw(&self, phases: &ScalarVector4<T>, amp: &ScalarVector1<T>) -> ScalarVector4<T> {
294        let two = ScalarVector4::splat(T::from_f32(2.0));
295        let one = ScalarVector4::splat(T::from_f32(1.0));
296        let amp_b = ScalarVector4::splat(amp.extract(0));
297        phases.mul(&two).sub(&one).mul(&amp_b)
298    }
299
300    #[inline(always)]
301    fn simd_saw_blep(
302        &mut self,
303        phases: &ScalarVector4<T>,
304        inc: T,
305        amp: &ScalarVector1<T>,
306    ) -> ScalarVector4<T> {
307        let raw = self.simd_saw_raw(phases, amp);
308        let one = ScalarVector4::splat(T::ONE);
309        let two = ScalarVector4::splat(T::from_f32(2.0));
310        let inc_v = ScalarVector4::splat(inc);
311        let amp_v = ScalarVector4::splat(amp.extract(0));
312
313        // next_phases = phases + inc for each lane
314        let next = phases.add(&inc_v);
315
316        // Mask: true where next >= 1.0 (discontinuity)
317        let wrap_mask = next.ge(&one);
318
319        // t = (1 - phase) / inc (pre-compute for all lanes, used only where wrapping)
320        let t = one.sub(phases).div(&inc_v);
321
322        // BLEP = 2*t - 1, then scale by amplitude
323        let blep = t.mul(&two).sub(&one).mul(&amp_v);
324
325        let corrected = raw.sub(&blep);
326
327        <ScalarVector4<T> as VectorMask<T, 4>>::select(&corrected, &raw, wrap_mask)
328    }
329
330    /// Backward-compatible public API (used by LFO and existing callers).
331    pub(crate) fn generate(&mut self) -> ScalarVector1<T> {
332        let effective_inc = self.phase_inc + self.fm_amount;
333        let output_vec = match self.waveform {
334            Waveform::Sine => self.scalar_sine(),
335            Waveform::Saw => self.scalar_saw_bandlimited(),
336            Waveform::Square => self.scalar_square(),
337            Waveform::Triangle => self.scalar_triangle(),
338            Waveform::Pulse(width) => self.scalar_pulse(width),
339        };
340        self.phase = self.phase + effective_inc;
341        let one = ScalarVector1::splat(T::from_f32(1.0));
342        if self.phase.extract(0) >= one.extract(0) {
343            self.phase = self.phase - one;
344            self.periods += 1;
345        }
346        output_vec
347    }
348
349    /// Reset phase to 0
350    pub fn reset_phase(&mut self) {
351        self.phase = ScalarVector1::splat(T::ZERO);
352        self.periods = 0;
353    }
354
355    /// Get current phase (0..1)
356    pub fn current_phase(&self) -> T {
357        self.phase.extract(0)
358    }
359
360    /// Get number of completed periods
361    pub fn period_count(&self) -> u32 {
362        self.periods
363    }
364
365    /// Set pulse width (for Pulse waveform)
366    pub fn set_pulse_width(&mut self, width: f32) {
367        if let Waveform::Pulse(_) = self.waveform {
368            self.waveform = Waveform::Pulse(width.clamp(0.01, 0.99));
369        }
370    }
371}
372
373// ==================== Algorithm trait implementation ====================
374
375impl<T: Transcendental> Algorithm<T> for BasicOscillator<T> {
376    fn init(&mut self, sample_rate: f32) {
377        self.sample_rate = sample_rate;
378        self.update_phase_inc();
379        self.phase = ScalarVector1::splat(T::ZERO);
380        self.periods = 0;
381    }
382
383    fn reset(&mut self) {
384        self.phase = ScalarVector1::splat(T::ZERO);
385        self.periods = 0;
386        self.fm_amount = ScalarVector1::splat(T::ZERO);
387    }
388
389    fn process(&mut self, _input: Option<&[T]>, output: &mut [T]) -> ProcessResult<()> {
390        self.generate_block_simd(output);
391        Ok(())
392    }
393
394    fn metadata(&self) -> AlgorithmMetadata {
395        AlgorithmMetadata {
396            name: self.waveform.name(),
397            category: AlgorithmCategory::Generator,
398            description: self.waveform.description(),
399            author: "Rill",
400            version: env!("CARGO_PKG_VERSION"),
401        }
402    }
403}
404
405// ==================== Generator trait implementation ====================
406
407impl<T: Transcendental> Generator<T> for BasicOscillator<T> {
408    fn phase(&self) -> T {
409        self.phase.extract(0)
410    }
411
412    fn set_phase(&mut self, phase: T) {
413        let one = T::from_f32(1.0);
414        let zero = T::ZERO;
415        self.phase = ScalarVector1::splat(if phase > one {
416            one
417        } else if phase < zero {
418            zero
419        } else {
420            phase
421        });
422    }
423
424    fn frequency(&self) -> f32 {
425        self.frequency
426    }
427
428    fn set_frequency(&mut self, freq: f32) {
429        self.frequency = freq.clamp(0.1, 20000.0);
430        self.update_phase_inc();
431    }
432
433    fn amplitude(&self) -> T {
434        self.amplitude.extract(0)
435    }
436
437    fn set_amplitude(&mut self, amp: T) {
438        let one = T::from_f32(1.0);
439        let zero = T::ZERO;
440        self.amplitude = ScalarVector1::splat(if amp > one {
441            one
442        } else if amp < zero {
443            zero
444        } else {
445            amp
446        });
447    }
448}
449
450// ==================== ParameterWrite trait implementation ====================
451
452impl<T: Transcendental> ParameterWrite for BasicOscillator<T> {
453    fn write_parameter(&mut self, name: &str, value: ParamValue) -> ProcessResult<()> {
454        match name {
455            "frequency" => {
456                if let Some(f) = value.as_f32() {
457                    self.set_frequency(f);
458                    return Ok(());
459                }
460                Err(ProcessError::parameter("frequency expects float"))
461            }
462            "amplitude" => {
463                if let Some(a) = value.as_f32() {
464                    self.set_amplitude(T::from_f32(a));
465                    return Ok(());
466                }
467                Err(ProcessError::parameter("amplitude expects float"))
468            }
469            "phase" => {
470                if let Some(p) = value.as_f32() {
471                    self.set_phase(T::from_f32(p));
472                    return Ok(());
473                }
474                Err(ProcessError::parameter("phase expects float"))
475            }
476            "fm_amount" => {
477                if let Some(f) = value.as_f32() {
478                    self.fm_amount = ScalarVector1::splat(T::from_f32(f));
479                    return Ok(());
480                }
481                Err(ProcessError::parameter("fm_amount expects float"))
482            }
483            _ => Err(ProcessError::parameter(format!(
484                "unknown parameter: {name}"
485            ))),
486        }
487    }
488
489    fn read_parameter(&self, name: &str) -> Option<ParamValue> {
490        match name {
491            "frequency" => Some(ParamValue::Float(self.frequency())),
492            "amplitude" => Some(ParamValue::Float(self.amplitude().to_f32())),
493            "phase" => Some(ParamValue::Float(self.phase().to_f32())),
494            _ => None,
495        }
496    }
497}
498
499// ==================== SyncableGenerator trait implementation ====================
500
501impl<T: Transcendental> SyncableGenerator<T> for BasicOscillator<T> {
502    fn sync(&mut self, reset: bool) {
503        if reset {
504            self.phase = ScalarVector1::splat(T::ZERO);
505        }
506    }
507
508    fn periods(&self) -> u32 {
509        self.periods
510    }
511}
512
513// ==================== ModulatableGenerator trait implementation ====================
514
515impl<T: Transcendental> ModulatableGenerator<T> for BasicOscillator<T> {
516    fn modulate_frequency(&mut self, amount: T) {
517        self.fm_amount = ScalarVector1::splat(amount);
518    }
519
520    fn modulation_index(&self) -> T {
521        self.fm_amount.extract(0)
522    }
523
524    fn set_modulation_index(&mut self, index: T) {
525        self.fm_amount = ScalarVector1::splat(index);
526    }
527}
528
529// ==================== Tests ====================
530
531#[cfg(test)]
532mod tests {
533    use super::*;
534    use float_cmp::approx_eq;
535
536    #[test]
537    fn test_sine_oscillator() {
538        let mut osc = BasicOscillator::<f32>::new(Waveform::Sine, 440.0, 0.5);
539        osc.init(44100.0);
540
541        // First sample should be 0
542        let mut output = [0.0f32; 1];
543        osc.process(None, &mut output).unwrap();
544        let sample1 = output[0];
545        assert!(approx_eq!(f32, sample1, 0.0, epsilon = 1e-6));
546
547        // Second sample should not be 0
548        osc.process(None, &mut output).unwrap();
549        let sample2 = output[0];
550        assert!(sample2 != 0.0);
551        assert!((-0.5..=0.5).contains(&sample2));
552    }
553
554    #[test]
555    fn test_saw_oscillator() {
556        let mut osc = BasicOscillator::<f32>::new(Waveform::Saw, 440.0, 0.5);
557        osc.init(44100.0);
558
559        let mut output = [0.0f32; 1];
560        osc.process(None, &mut output).unwrap();
561        let sample = output[0];
562        assert!((-0.5..=0.5).contains(&sample));
563    }
564
565    #[test]
566    fn test_square_oscillator() {
567        let mut osc = BasicOscillator::<f32>::new(Waveform::Square, 440.0, 0.5);
568        osc.init(44100.0);
569
570        let mut output = [0.0f32; 1];
571        osc.process(None, &mut output).unwrap();
572        let sample = output[0];
573        assert!(sample == 0.5 || sample == -0.5);
574    }
575
576    #[test]
577    fn test_triangle_oscillator() {
578        let mut osc = BasicOscillator::<f32>::new(Waveform::Triangle, 440.0, 0.5);
579        osc.init(44100.0);
580
581        let mut output = [0.0f32; 1];
582        osc.process(None, &mut output).unwrap();
583        let sample = output[0];
584        assert!((-0.5..=0.5).contains(&sample));
585    }
586
587    #[test]
588    fn test_pulse_oscillator() {
589        let mut osc = BasicOscillator::<f32>::new(Waveform::Pulse(0.25), 440.0, 0.5);
590        osc.init(44100.0);
591
592        let mut output = [0.0f32; 1];
593        osc.process(None, &mut output).unwrap();
594        let sample = output[0];
595        assert!(sample == 0.5); // At phase 0 should be positive pulse
596    }
597
598    #[test]
599    fn test_frequency_change() {
600        let mut osc = BasicOscillator::<f32>::new(Waveform::Sine, 440.0, 0.5);
601        osc.init(44100.0);
602
603        assert_eq!(osc.frequency(), 440.0);
604
605        osc.set_frequency(880.0);
606        assert_eq!(osc.frequency(), 880.0);
607    }
608
609    #[test]
610    fn test_amplitude_change() {
611        let mut osc = BasicOscillator::<f32>::new(Waveform::Sine, 440.0, 0.5);
612        osc.init(44100.0);
613
614        assert_eq!(osc.amplitude(), 0.5);
615
616        osc.set_amplitude(0.8);
617        assert_eq!(osc.amplitude(), 0.8);
618    }
619
620    #[test]
621    fn test_phase_manipulation() {
622        let mut osc = BasicOscillator::<f32>::new(Waveform::Sine, 440.0, 1.0);
623        osc.init(44100.0);
624
625        osc.set_phase(0.25); // π/2
626        let mut output = [0.0f32; 1];
627        osc.process(None, &mut output).unwrap();
628        let sample = output[0];
629        assert!(approx_eq!(f32, sample, 1.0, epsilon = 1e-4)); // sin(π/2) = 1
630    }
631
632    #[test]
633    fn test_fm_modulation() {
634        let mut osc = BasicOscillator::<f32>::new(Waveform::Sine, 440.0, 1.0);
635        osc.init(44100.0);
636
637        osc.modulate_frequency(0.5);
638        assert_eq!(osc.modulation_index(), 0.5);
639
640        // Verify modulation is applied
641        let mut output = [0.0f32; 1];
642        osc.process(None, &mut output).unwrap();
643        let sample = output[0];
644        assert!((-1.0..=1.0).contains(&sample));
645    }
646
647    #[test]
648    fn test_clone_copy() {
649        let osc1 = BasicOscillator::<f32>::new(Waveform::Sine, 440.0, 0.5);
650        let osc2 = osc1; // Copy via Copy trait
651        let osc3 = Clone::clone(&osc1); // Explicit clone
652
653        assert_eq!(osc1.frequency(), osc2.frequency());
654        assert_eq!(osc1.frequency(), osc3.frequency());
655    }
656}