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#[inline]
55fn advance_phase(phase: &mut f32, phase_delta: f32) {
56 *phase += phase_delta;
58 if *phase >= 1.0 {
59 *phase -= 1.0;
60 }
61}
62
63#[derive(Clone)]
65pub struct OscillatorParams {
66 pub enabled: bool,
68
69 pub waveform: f32,
71
72 pub frequency: f32,
74
75 pub level: f32,
77}
78
79impl Default for OscillatorParams {
80 fn default() -> Self {
81 Self {
82 enabled: false,
83 waveform: 0.0,
84 frequency: 440.0,
85 level: 0.5,
86 }
87 }
88}
89
90impl ProcessorParams for OscillatorParams {
91 fn param_specs() -> &'static [ParamSpec] {
92 static SPECS: [ParamSpec; 4] = [
93 ParamSpec {
94 name: "Enabled",
95 id_suffix: "enabled",
96 range: ParamRange::Stepped { min: 0, max: 1 },
97 default: 0.0,
98 unit: "",
99 group: None,
100 },
101 ParamSpec {
102 name: "Waveform",
103 id_suffix: "waveform",
104 range: ParamRange::Enum {
105 variants: Waveform::VARIANTS,
106 },
107 default: 0.0,
108 unit: "",
109 group: None,
110 },
111 ParamSpec {
112 name: "Frequency",
113 id_suffix: "frequency",
114 range: ParamRange::Skewed {
115 min: 20.0,
116 max: 20_000.0,
117 factor: 2.5,
118 },
119 default: 440.0,
120 unit: "Hz",
121 group: None,
122 },
123 ParamSpec {
124 name: "Level",
125 id_suffix: "level",
126 range: ParamRange::Linear { min: 0.0, max: 1.0 },
127 default: 0.5,
128 unit: "%",
129 group: None,
130 },
131 ];
132
133 &SPECS
134 }
135
136 fn from_param_defaults() -> Self {
137 Self::default()
138 }
139
140 fn apply_plain_values(&mut self, values: &[f32]) {
141 if let Some(enabled) = values.first() {
142 self.enabled = *enabled >= 0.5;
143 }
144 if let Some(waveform) = values.get(1) {
145 self.waveform = *waveform;
146 }
147 if let Some(frequency) = values.get(2) {
148 self.frequency = *frequency;
149 }
150 if let Some(level) = values.get(3) {
151 self.level = *level;
152 }
153 }
154}
155
156#[derive(Default)]
158pub struct Oscillator {
159 sample_rate: f32,
161 phase: f32,
163}
164
165impl Processor for Oscillator {
166 type Params = OscillatorParams;
167
168 fn set_sample_rate(&mut self, sample_rate: f32) {
169 self.sample_rate = sample_rate;
170 }
171
172 fn process(
173 &mut self,
174 buffer: &mut [&mut [f32]],
175 _transport: &Transport,
176 params: &Self::Params,
177 ) {
178 if !params.enabled {
179 return;
180 }
181
182 if self.sample_rate == 0.0 {
184 return;
185 }
186
187 let waveform = Waveform::from_index(params.waveform);
188
189 let phase_delta = params.frequency / self.sample_rate;
191
192 let start_phase = self.phase;
194
195 for channel in buffer.iter_mut() {
196 self.phase = start_phase;
197 for sample in channel.iter_mut() {
198 *sample += generate_waveform_sample(waveform, self.phase) * params.level;
199 advance_phase(&mut self.phase, phase_delta);
200 }
201 }
202 }
203
204 fn reset(&mut self) {
205 self.phase = 0.0;
206 }
207}
208
209#[cfg(test)]
210mod tests {
211 use super::*;
212
213 fn test_params(enabled: bool) -> OscillatorParams {
214 OscillatorParams {
215 enabled,
216 waveform: 0.0,
217 frequency: 440.0,
218 level: 0.5,
219 }
220 }
221
222 fn test_params_with_waveform(enabled: bool, waveform: f32) -> OscillatorParams {
223 OscillatorParams {
224 enabled,
225 waveform,
226 frequency: 440.0,
227 level: 0.5,
228 }
229 }
230
231 #[test]
232 fn waveform_from_index_maps_correctly() {
233 assert_eq!(Waveform::from_index(0.0), Waveform::Sine);
234 assert_eq!(Waveform::from_index(1.0), Waveform::Square);
235 assert_eq!(Waveform::from_index(2.0), Waveform::Saw);
236 assert_eq!(Waveform::from_index(3.0), Waveform::Triangle);
237 }
238
239 #[test]
240 fn waveform_from_index_out_of_range_defaults_to_sine() {
241 assert_eq!(Waveform::from_index(-1.0), Waveform::Sine);
242 assert_eq!(Waveform::from_index(4.0), Waveform::Sine);
243 assert_eq!(Waveform::from_index(100.0), Waveform::Sine);
244 }
245
246 #[test]
247 fn waveform_from_index_rounds_floats() {
248 assert_eq!(Waveform::from_index(0.4), Waveform::Sine);
249 assert_eq!(Waveform::from_index(0.6), Waveform::Square);
250 assert_eq!(Waveform::from_index(1.5), Waveform::Saw);
251 assert_eq!(Waveform::from_index(2.7), Waveform::Triangle);
252 }
253
254 #[test]
255 fn sine_wave_zero_crossing_and_peak() {
256 assert!((generate_waveform_sample(Waveform::Sine, 0.0)).abs() < 1e-5);
257 assert!((generate_waveform_sample(Waveform::Sine, 0.25) - 1.0).abs() < 1e-5);
258 assert!((generate_waveform_sample(Waveform::Sine, 0.5)).abs() < 1e-5);
259 assert!((generate_waveform_sample(Waveform::Sine, 0.75) + 1.0).abs() < 1e-5);
260 }
261
262 #[test]
263 fn square_wave_values() {
264 assert_eq!(generate_waveform_sample(Waveform::Square, 0.0), 1.0);
265 assert_eq!(generate_waveform_sample(Waveform::Square, 0.25), 1.0);
266 assert_eq!(generate_waveform_sample(Waveform::Square, 0.5), -1.0);
267 assert_eq!(generate_waveform_sample(Waveform::Square, 0.75), -1.0);
268 }
269
270 #[test]
271 fn saw_wave_values() {
272 assert!((generate_waveform_sample(Waveform::Saw, 0.0) + 1.0).abs() < 1e-5);
273 assert!((generate_waveform_sample(Waveform::Saw, 0.5)).abs() < 1e-5);
274 assert!((generate_waveform_sample(Waveform::Saw, 1.0) - 1.0).abs() < 1e-5);
275 }
276
277 #[test]
278 fn triangle_wave_values() {
279 assert!((generate_waveform_sample(Waveform::Triangle, 0.0) + 1.0).abs() < 1e-5);
280 assert!((generate_waveform_sample(Waveform::Triangle, 0.25)).abs() < 1e-5);
281 assert!((generate_waveform_sample(Waveform::Triangle, 0.5) - 1.0).abs() < 1e-5);
282 assert!((generate_waveform_sample(Waveform::Triangle, 0.75)).abs() < 1e-5);
283 }
284
285 #[test]
286 fn oscillator_preserves_passthrough_when_disabled() {
287 let mut osc = Oscillator::default();
288 osc.set_sample_rate(48_000.0);
289
290 let mut left = [0.25_f32; 64];
291 let mut right = [-0.5_f32; 64];
292 let left_in = left;
293 let right_in = right;
294 let mut buffer = [&mut left[..], &mut right[..]];
295
296 osc.process(&mut buffer, &Transport::default(), &test_params(false));
297
298 for (actual, expected) in left.iter().zip(left_in.iter()) {
299 assert!((actual - expected).abs() <= f32::EPSILON);
300 }
301
302 for (actual, expected) in right.iter().zip(right_in.iter()) {
303 assert!((actual - expected).abs() <= f32::EPSILON);
304 }
305 }
306
307 #[test]
308 fn oscillator_generates_signal_when_enabled_on_silent_input() {
309 let mut osc = Oscillator::default();
310 osc.set_sample_rate(48_000.0);
311
312 let mut left = [0.0_f32; 128];
313 let mut right = [0.0_f32; 128];
314 let mut buffer = [&mut left[..], &mut right[..]];
315
316 osc.process(&mut buffer, &Transport::default(), &test_params(true));
317
318 let peak_left = left
319 .iter()
320 .fold(0.0_f32, |acc, sample| acc.max(sample.abs()));
321 let peak_right = right
322 .iter()
323 .fold(0.0_f32, |acc, sample| acc.max(sample.abs()));
324
325 assert!(
326 peak_left > 0.01,
327 "expected audible oscillator output on left"
328 );
329 assert!(
330 peak_right > 0.01,
331 "expected audible oscillator output on right"
332 );
333 }
334
335 #[test]
336 fn oscillator_enabled_adds_signal_without_removing_input() {
337 let mut osc_mixed = Oscillator::default();
338 osc_mixed.set_sample_rate(48_000.0);
339
340 let mut left_mixed = [0.2_f32; 128];
341 let mut right_mixed = [-0.15_f32; 128];
342 let left_input = left_mixed;
343 let right_input = right_mixed;
344 let mut mixed_buffer = [&mut left_mixed[..], &mut right_mixed[..]];
345
346 osc_mixed.process(&mut mixed_buffer, &Transport::default(), &test_params(true));
347
348 let mut osc_only = Oscillator::default();
349 osc_only.set_sample_rate(48_000.0);
350
351 let mut left_osc_only = [0.0_f32; 128];
352 let mut right_osc_only = [0.0_f32; 128];
353 let mut osc_only_buffer = [&mut left_osc_only[..], &mut right_osc_only[..]];
354
355 osc_only.process(
356 &mut osc_only_buffer,
357 &Transport::default(),
358 &test_params(true),
359 );
360
361 for i in 0..left_mixed.len() {
362 let additive_component_left = left_mixed[i] - left_input[i];
363 let additive_component_right = right_mixed[i] - right_input[i];
364
365 assert!((additive_component_left - left_osc_only[i]).abs() < 1e-6);
366 assert!((additive_component_right - right_osc_only[i]).abs() < 1e-6);
367 }
368 }
369
370 #[test]
371 fn all_waveforms_produce_signal_when_enabled() {
372 for waveform_index in 0..4 {
373 let mut osc = Oscillator::default();
374 osc.set_sample_rate(48_000.0);
375
376 let mut left = [0.0_f32; 128];
377 let mut right = [0.0_f32; 128];
378 let mut buffer = [&mut left[..], &mut right[..]];
379
380 osc.process(
381 &mut buffer,
382 &Transport::default(),
383 &test_params_with_waveform(true, waveform_index as f32),
384 );
385
386 let peak = left
387 .iter()
388 .fold(0.0_f32, |acc, sample| acc.max(sample.abs()));
389 assert!(
390 peak > 0.01,
391 "waveform index {waveform_index} should produce signal"
392 );
393 }
394 }
395
396 #[test]
397 fn apply_plain_values_updates_all_fields() {
398 let mut params = OscillatorParams::default();
399 params.apply_plain_values(&[1.0, 2.0, 1760.0, 0.9]);
400
401 assert!(params.enabled);
402 assert!((params.waveform - 2.0).abs() < f32::EPSILON);
403 assert!((params.frequency - 1760.0).abs() < f32::EPSILON);
404 assert!((params.level - 0.9).abs() < f32::EPSILON);
405 }
406
407 #[test]
408 fn frequency_param_uses_full_audible_range() {
409 let specs = OscillatorParams::param_specs();
410 let frequency = specs
411 .iter()
412 .find(|spec| spec.id_suffix == "frequency")
413 .expect("frequency spec should exist");
414
415 match frequency.range {
416 ParamRange::Skewed { min, max, .. } => {
417 assert!((min - 20.0).abs() < f64::EPSILON);
418 assert!((max - 20_000.0).abs() < f64::EPSILON);
419 }
420 _ => panic!("frequency should use a skewed range"),
421 }
422 }
423}