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