Skip to main content

rill_core_dsp/generators/
wavetable.rs

1use crate::algorithm::{Algorithm, AlgorithmCategory, AlgorithmMetadata};
2use crate::generators::{Generator, InterpolatedReader};
3use crate::vector::prelude::*;
4use rill_core::traits::{ActionContext, ProcessResult};
5use rill_core::Transcendental;
6
7/// Wavetable oscillator built on [`InterpolatedReader`].
8///
9/// The compile-time constant `SIZE` determines table resolution but the
10/// underlying storage is heap-allocated, sharing the same interpolation
11/// engine with [`SamplePlayer`](crate::generators::SamplePlayer).
12pub struct WavetableOscillator<T: Transcendental, const SIZE: usize> {
13    reader: InterpolatedReader<T>,
14    frequency: f32,
15    amplitude: ScalarVector1<T>,
16    sample_rate: f32,
17}
18
19impl<T: Transcendental, const SIZE: usize> WavetableOscillator<T, SIZE> {
20    /// Create from an explicit table.
21    pub fn new(table: [T; SIZE], frequency: f32) -> Self {
22        let mut reader = InterpolatedReader::new(table.to_vec());
23        reader.set_wrap(true);
24        let mut osc = Self {
25            reader,
26            frequency,
27            amplitude: ScalarVector1::splat(T::from_f32(1.0)),
28            sample_rate: 44100.0,
29        };
30        osc.update_rate();
31        osc
32    }
33
34    /// Create a sine wavetable.
35    pub fn sine(frequency: f32) -> Self {
36        let mut table = [T::ZERO; SIZE];
37        for i in 0..SIZE {
38            let phase = (i as f32 / SIZE as f32) * 2.0 * core::f32::consts::PI;
39            table[i] = T::from_f32(phase.sin());
40        }
41        Self::new(table, frequency)
42    }
43
44    /// Create a sawtooth wavetable.
45    pub fn saw(frequency: f32) -> Self {
46        let mut table = [T::ZERO; SIZE];
47        for i in 0..SIZE {
48            table[i] = T::from_f32(2.0 * i as f32 / SIZE as f32 - 1.0);
49        }
50        Self::new(table, frequency)
51    }
52
53    /// Replace the wavetable data.
54    pub fn set_table(&mut self, table: [T; SIZE]) {
55        self.reader.set_buffer(table.to_vec());
56    }
57
58    /// Enable cubic interpolation (default: linear).
59    pub fn set_cubic(&mut self, cubic: bool) {
60        self.reader.set_cubic(cubic);
61    }
62
63    /// Whether cubic interpolation is enabled.
64    pub fn is_cubic(&self) -> bool {
65        self.reader.is_cubic()
66    }
67
68    fn update_rate(&mut self) {
69        let rate = self.frequency as f64 * SIZE as f64 / self.sample_rate as f64;
70        self.reader.set_rate(rate);
71    }
72}
73
74impl<T: Transcendental, const SIZE: usize> Algorithm<T> for WavetableOscillator<T, SIZE> {
75    fn init(&mut self, sample_rate: f32) {
76        self.sample_rate = sample_rate;
77        self.update_rate();
78        self.reader.set_position(0.0);
79    }
80
81    fn reset(&mut self) {
82        self.reader.set_position(0.0);
83    }
84
85    fn process(
86        &mut self,
87        _input: Option<&[T]>,
88        output: &mut [T],
89        _ctx: &ActionContext,
90    ) -> ProcessResult<()> {
91        let amp = self.amplitude.extract(0);
92        self.reader.render_block(output);
93        if amp != T::from_f32(1.0) {
94            for s in output.iter_mut() {
95                *s = *s * amp;
96            }
97        }
98        Ok(())
99    }
100
101    fn metadata(&self) -> AlgorithmMetadata {
102        AlgorithmMetadata {
103            name: "Wavetable Oscillator",
104            category: AlgorithmCategory::Generator,
105            description: "Wavetable oscillator with linear / cubic interpolation".into(),
106            author: "Rill",
107            version: env!("CARGO_PKG_VERSION"),
108        }
109    }
110}
111
112impl<T: Transcendental, const SIZE: usize> Generator<T> for WavetableOscillator<T, SIZE> {
113    fn phase(&self) -> T {
114        let pos = self.reader.position();
115        let len = SIZE as f64;
116        T::from_f64((pos % len) / len)
117    }
118
119    fn set_phase(&mut self, phase: T) {
120        let p = phase.to_f64().clamp(0.0, 1.0);
121        self.reader.set_position(p * SIZE as f64);
122    }
123
124    fn reset_phase(&mut self) {
125        self.reader.set_position(0.0);
126    }
127
128    fn frequency(&self) -> f32 {
129        self.frequency
130    }
131
132    fn set_frequency(&mut self, freq: f32) {
133        self.frequency = freq;
134        self.update_rate();
135    }
136
137    fn amplitude(&self) -> T {
138        self.amplitude.extract(0)
139    }
140
141    fn set_amplitude(&mut self, amp: T) {
142        self.amplitude = ScalarVector1::splat(amp.clamp(T::ZERO, T::from_f32(1.0)));
143    }
144}