1use wavecraft_dsp::{ParamRange, ParamSpec, Processor, ProcessorParams, Transport};
4
5#[derive(Debug, Clone, Copy, Default, PartialEq)]
7pub enum Waveform {
8 #[default]
9 Sine,
10 Square,
11 Saw,
12 Triangle,
13}
14
15impl Waveform {
16 pub const VARIANTS: &'static [&'static str] = &["Sine", "Square", "Saw", "Triangle"];
18
19 pub fn from_index(index: f32) -> Self {
22 match index.round() as u32 {
23 0 => Self::Sine,
24 1 => Self::Square,
25 2 => Self::Saw,
26 3 => Self::Triangle,
27 _ => Self::Sine,
28 }
29 }
30}
31
32pub fn generate_waveform_sample(waveform: Waveform, phase: f32) -> f32 {
34 match waveform {
35 Waveform::Sine => (phase * std::f32::consts::TAU).sin(),
36 Waveform::Square => {
37 if phase < 0.5 {
38 1.0
39 } else {
40 -1.0
41 }
42 }
43 Waveform::Saw => 2.0 * phase - 1.0,
44 Waveform::Triangle => {
45 if phase < 0.5 {
46 4.0 * phase - 1.0
47 } else {
48 -4.0 * phase + 3.0
49 }
50 }
51 }
52}
53
54#[derive(Clone)]
56pub struct OscillatorParams {
57 pub enabled: bool,
59
60 pub waveform: f32,
62
63 pub frequency: f32,
65
66 pub level: f32,
68}
69
70impl Default for OscillatorParams {
71 fn default() -> Self {
72 Self {
73 enabled: false,
74 waveform: 0.0,
75 frequency: 440.0,
76 level: 0.5,
77 }
78 }
79}
80
81impl ProcessorParams for OscillatorParams {
82 fn param_specs() -> &'static [ParamSpec] {
83 static SPECS: [ParamSpec; 4] = [
84 ParamSpec {
85 name: "Enabled",
86 id_suffix: "enabled",
87 range: ParamRange::Stepped { min: 0, max: 1 },
88 default: 0.0,
89 unit: "",
90 group: None,
91 },
92 ParamSpec {
93 name: "Waveform",
94 id_suffix: "waveform",
95 range: ParamRange::Enum {
96 variants: Waveform::VARIANTS,
97 },
98 default: 0.0,
99 unit: "",
100 group: None,
101 },
102 ParamSpec {
103 name: "Frequency",
104 id_suffix: "frequency",
105 range: ParamRange::Skewed {
106 min: 20.0,
107 max: 5000.0,
108 factor: 2.5,
109 },
110 default: 440.0,
111 unit: "Hz",
112 group: None,
113 },
114 ParamSpec {
115 name: "Level",
116 id_suffix: "level",
117 range: ParamRange::Linear { min: 0.0, max: 1.0 },
118 default: 0.5,
119 unit: "%",
120 group: None,
121 },
122 ];
123
124 &SPECS
125 }
126
127 fn from_param_defaults() -> Self {
128 Self::default()
129 }
130}
131
132#[derive(Default)]
134pub struct Oscillator {
135 sample_rate: f32,
137 phase: f32,
139}
140
141impl Processor for Oscillator {
142 type Params = OscillatorParams;
143
144 fn set_sample_rate(&mut self, sample_rate: f32) {
145 self.sample_rate = sample_rate;
146 }
147
148 fn process(
149 &mut self,
150 buffer: &mut [&mut [f32]],
151 _transport: &Transport,
152 params: &Self::Params,
153 ) {
154 if !params.enabled {
155 for channel in buffer.iter_mut() {
156 channel.fill(0.0);
157 }
158 return;
159 }
160
161 if self.sample_rate == 0.0 {
163 return;
164 }
165
166 let waveform = Waveform::from_index(params.waveform);
167
168 let phase_delta = params.frequency / self.sample_rate;
170
171 let start_phase = self.phase;
173
174 for channel in buffer.iter_mut() {
175 self.phase = start_phase;
176 for sample in channel.iter_mut() {
177 *sample = generate_waveform_sample(waveform, self.phase) * params.level;
178
179 self.phase += phase_delta;
181 if self.phase >= 1.0 {
182 self.phase -= 1.0;
183 }
184 }
185 }
186 }
187
188 fn reset(&mut self) {
189 self.phase = 0.0;
190 }
191}
192
193#[cfg(test)]
194mod tests {
195 use super::*;
196
197 fn test_params(enabled: bool) -> OscillatorParams {
198 OscillatorParams {
199 enabled,
200 waveform: 0.0,
201 frequency: 440.0,
202 level: 0.5,
203 }
204 }
205
206 fn test_params_with_waveform(enabled: bool, waveform: f32) -> OscillatorParams {
207 OscillatorParams {
208 enabled,
209 waveform,
210 frequency: 440.0,
211 level: 0.5,
212 }
213 }
214
215 #[test]
216 fn waveform_from_index_maps_correctly() {
217 assert_eq!(Waveform::from_index(0.0), Waveform::Sine);
218 assert_eq!(Waveform::from_index(1.0), Waveform::Square);
219 assert_eq!(Waveform::from_index(2.0), Waveform::Saw);
220 assert_eq!(Waveform::from_index(3.0), Waveform::Triangle);
221 }
222
223 #[test]
224 fn waveform_from_index_out_of_range_defaults_to_sine() {
225 assert_eq!(Waveform::from_index(-1.0), Waveform::Sine);
226 assert_eq!(Waveform::from_index(4.0), Waveform::Sine);
227 assert_eq!(Waveform::from_index(100.0), Waveform::Sine);
228 }
229
230 #[test]
231 fn waveform_from_index_rounds_floats() {
232 assert_eq!(Waveform::from_index(0.4), Waveform::Sine);
233 assert_eq!(Waveform::from_index(0.6), Waveform::Square);
234 assert_eq!(Waveform::from_index(1.5), Waveform::Saw);
235 assert_eq!(Waveform::from_index(2.7), Waveform::Triangle);
236 }
237
238 #[test]
239 fn sine_wave_zero_crossing_and_peak() {
240 assert!((generate_waveform_sample(Waveform::Sine, 0.0)).abs() < 1e-5);
241 assert!((generate_waveform_sample(Waveform::Sine, 0.25) - 1.0).abs() < 1e-5);
242 assert!((generate_waveform_sample(Waveform::Sine, 0.5)).abs() < 1e-5);
243 assert!((generate_waveform_sample(Waveform::Sine, 0.75) + 1.0).abs() < 1e-5);
244 }
245
246 #[test]
247 fn square_wave_values() {
248 assert_eq!(generate_waveform_sample(Waveform::Square, 0.0), 1.0);
249 assert_eq!(generate_waveform_sample(Waveform::Square, 0.25), 1.0);
250 assert_eq!(generate_waveform_sample(Waveform::Square, 0.5), -1.0);
251 assert_eq!(generate_waveform_sample(Waveform::Square, 0.75), -1.0);
252 }
253
254 #[test]
255 fn saw_wave_values() {
256 assert!((generate_waveform_sample(Waveform::Saw, 0.0) + 1.0).abs() < 1e-5);
257 assert!((generate_waveform_sample(Waveform::Saw, 0.5)).abs() < 1e-5);
258 assert!((generate_waveform_sample(Waveform::Saw, 1.0) - 1.0).abs() < 1e-5);
259 }
260
261 #[test]
262 fn triangle_wave_values() {
263 assert!((generate_waveform_sample(Waveform::Triangle, 0.0) + 1.0).abs() < 1e-5);
264 assert!((generate_waveform_sample(Waveform::Triangle, 0.25)).abs() < 1e-5);
265 assert!((generate_waveform_sample(Waveform::Triangle, 0.5) - 1.0).abs() < 1e-5);
266 assert!((generate_waveform_sample(Waveform::Triangle, 0.75)).abs() < 1e-5);
267 }
268
269 #[test]
270 fn oscillator_outputs_silence_when_disabled() {
271 let mut osc = Oscillator::default();
272 osc.set_sample_rate(48_000.0);
273
274 let mut left = [1.0_f32; 64];
275 let mut right = [1.0_f32; 64];
276 let mut buffer = [&mut left[..], &mut right[..]];
277
278 osc.process(&mut buffer, &Transport::default(), &test_params(false));
279
280 assert!(left.iter().all(|s| s.abs() <= f32::EPSILON));
281 assert!(right.iter().all(|s| s.abs() <= f32::EPSILON));
282 }
283
284 #[test]
285 fn oscillator_outputs_signal_when_enabled() {
286 let mut osc = Oscillator::default();
287 osc.set_sample_rate(48_000.0);
288
289 let mut left = [0.0_f32; 128];
290 let mut right = [0.0_f32; 128];
291 let mut buffer = [&mut left[..], &mut right[..]];
292
293 osc.process(&mut buffer, &Transport::default(), &test_params(true));
294
295 let peak_left = left
296 .iter()
297 .fold(0.0_f32, |acc, sample| acc.max(sample.abs()));
298 let peak_right = right
299 .iter()
300 .fold(0.0_f32, |acc, sample| acc.max(sample.abs()));
301
302 assert!(
303 peak_left > 0.01,
304 "expected audible oscillator output on left"
305 );
306 assert!(
307 peak_right > 0.01,
308 "expected audible oscillator output on right"
309 );
310 }
311
312 #[test]
313 fn all_waveforms_produce_signal_when_enabled() {
314 for waveform_index in 0..4 {
315 let mut osc = Oscillator::default();
316 osc.set_sample_rate(48_000.0);
317
318 let mut left = [0.0_f32; 128];
319 let mut right = [0.0_f32; 128];
320 let mut buffer = [&mut left[..], &mut right[..]];
321
322 osc.process(
323 &mut buffer,
324 &Transport::default(),
325 &test_params_with_waveform(true, waveform_index as f32),
326 );
327
328 let peak = left
329 .iter()
330 .fold(0.0_f32, |acc, sample| acc.max(sample.abs()));
331 assert!(
332 peak > 0.01,
333 "waveform index {waveform_index} should produce signal"
334 );
335 }
336 }
337}