1use bbx_core::random::XorShiftRng;
4
5#[cfg(feature = "simd")]
6use crate::sample::SIMD_LANES;
7#[cfg(feature = "simd")]
8use crate::waveform::generate_waveform_samples_simd;
9use crate::{
10 block::{Block, DEFAULT_GENERATOR_INPUT_COUNT, DEFAULT_GENERATOR_OUTPUT_COUNT},
11 context::DspContext,
12 parameter::{ModulationOutput, Parameter},
13 sample::Sample,
14 waveform::{Waveform, process_waveform_scalar},
15};
16
17pub struct OscillatorBlock<S: Sample> {
24 pub frequency: Parameter<S>,
26
27 pub pitch_offset: Parameter<S>,
29
30 base_frequency: S,
31 midi_frequency: Option<S>,
32 phase: f64,
33 waveform: Waveform,
34 rng: XorShiftRng,
35}
36
37impl<S: Sample> OscillatorBlock<S> {
38 pub fn new(frequency: f64, waveform: Waveform, seed: Option<u64>) -> Self {
40 let freq = S::from_f64(frequency);
41 Self {
42 frequency: Parameter::Constant(freq),
43 pitch_offset: Parameter::Constant(S::ZERO),
44 base_frequency: freq,
45 midi_frequency: None,
46 phase: 0.0,
47 waveform,
48 rng: XorShiftRng::new(seed.unwrap_or_default()),
49 }
50 }
51
52 pub fn set_midi_frequency(&mut self, frequency: S) {
54 self.midi_frequency = Some(frequency);
55 }
56
57 pub fn clear_midi_frequency(&mut self) {
59 self.midi_frequency = None;
60 }
61
62 pub fn set_waveform(&mut self, waveform: Waveform) {
64 self.waveform = waveform;
65 }
66}
67
68impl<S: Sample> Block<S> for OscillatorBlock<S> {
69 fn process(&mut self, _inputs: &[&[S]], outputs: &mut [&mut [S]], modulation_values: &[S], context: &DspContext) {
70 let base = self.midi_frequency.unwrap_or(self.base_frequency);
71
72 let freq_hz = match &self.frequency {
73 Parameter::Constant(f) => self.midi_frequency.unwrap_or(*f),
74 Parameter::Modulated(block_id) => {
75 let mod_value = modulation_values.get(block_id.0).copied().unwrap_or(S::ZERO);
76 base + mod_value
77 }
78 };
79
80 let pitch_offset_semitones = match &self.pitch_offset {
81 Parameter::Constant(offset) => *offset,
82 Parameter::Modulated(block_id) => modulation_values.get(block_id.0).copied().unwrap_or(S::ZERO),
83 };
84
85 let freq = if pitch_offset_semitones != S::ZERO {
86 let multiplier = S::from_f64(2.0f64.powf(pitch_offset_semitones.to_f64() / 12.0));
87 freq_hz * multiplier
88 } else {
89 freq_hz
90 };
91
92 let phase_increment = freq.to_f64() / context.sample_rate * S::TAU.to_f64();
93
94 #[cfg(feature = "simd")]
95 {
96 use crate::waveform::DEFAULT_DUTY_CYCLE;
97
98 if !matches!(self.waveform, Waveform::Noise) {
99 let buffer_size = context.buffer_size;
100 let chunks = buffer_size / SIMD_LANES;
101 let remainder_start = chunks * SIMD_LANES;
102 let chunk_phase_step = phase_increment * SIMD_LANES as f64;
103
104 let base_phase = S::simd_splat(S::from_f64(self.phase));
105 let sample_inc_simd = S::simd_splat(S::from_f64(phase_increment));
106 let mut phases = base_phase + S::simd_lane_offsets() * sample_inc_simd;
107 let chunk_inc_simd = S::simd_splat(S::from_f64(chunk_phase_step));
108 let duty = S::from_f64(DEFAULT_DUTY_CYCLE);
109 let two_pi = S::simd_splat(S::TAU);
110 let inv_two_pi = S::simd_splat(S::INV_TAU);
111 let phase_inc_normalized = S::from_f64(phase_increment * S::INV_TAU.to_f64());
112 let tau = S::TAU.to_f64();
113 let inv_tau = 1.0 / tau;
114
115 for chunk_idx in 0..chunks {
116 let phases_array = S::simd_to_array(phases);
117 let phases_normalized: [S; SIMD_LANES] = [
118 S::from_f64(phases_array[0].to_f64().rem_euclid(tau) * inv_tau),
119 S::from_f64(phases_array[1].to_f64().rem_euclid(tau) * inv_tau),
120 S::from_f64(phases_array[2].to_f64().rem_euclid(tau) * inv_tau),
121 S::from_f64(phases_array[3].to_f64().rem_euclid(tau) * inv_tau),
122 ];
123
124 if let Some(samples) = generate_waveform_samples_simd::<S>(
125 self.waveform,
126 phases,
127 phases_normalized,
128 phase_inc_normalized,
129 duty,
130 two_pi,
131 inv_two_pi,
132 ) {
133 let base = chunk_idx * SIMD_LANES;
134 outputs[0][base..base + SIMD_LANES].copy_from_slice(&samples);
135 }
136
137 phases = phases + chunk_inc_simd;
138 }
139
140 self.phase += chunk_phase_step * chunks as f64;
141 self.phase = self.phase.rem_euclid(S::TAU.to_f64());
142
143 process_waveform_scalar(
144 &mut outputs[0][remainder_start..],
145 self.waveform,
146 &mut self.phase,
147 phase_increment,
148 &mut self.rng,
149 1.0,
150 );
151 } else {
152 process_waveform_scalar(
153 outputs[0],
154 self.waveform,
155 &mut self.phase,
156 phase_increment,
157 &mut self.rng,
158 1.0,
159 );
160 }
161 }
162
163 #[cfg(not(feature = "simd"))]
164 {
165 process_waveform_scalar(
166 outputs[0],
167 self.waveform,
168 &mut self.phase,
169 phase_increment,
170 &mut self.rng,
171 1.0,
172 );
173 }
174 }
175
176 #[inline]
177 fn input_count(&self) -> usize {
178 DEFAULT_GENERATOR_INPUT_COUNT
179 }
180
181 #[inline]
182 fn output_count(&self) -> usize {
183 DEFAULT_GENERATOR_OUTPUT_COUNT
184 }
185
186 #[inline]
187 fn modulation_outputs(&self) -> &[ModulationOutput] {
188 &[]
189 }
190}
191
192#[cfg(test)]
193mod tests {
194 use super::*;
195 use crate::channel::ChannelLayout;
196
197 fn test_context(buffer_size: usize) -> DspContext {
198 DspContext {
199 sample_rate: 44100.0,
200 num_channels: 1,
201 buffer_size,
202 current_sample: 0,
203 channel_layout: ChannelLayout::Mono,
204 }
205 }
206
207 fn test_oscillator<S: Sample>(waveform: Waveform, frequency: f64, buffer_size: usize) -> Vec<S> {
208 let mut osc = OscillatorBlock::<S>::new(frequency, waveform, Some(42));
209 let context = test_context(buffer_size);
210 let inputs: [&[S]; 0] = [];
211 let mut output = vec![S::ZERO; buffer_size];
212 let mut outputs: [&mut [S]; 1] = [&mut output];
213 osc.process(&inputs, &mut outputs, &[], &context);
214 output
215 }
216
217 #[test]
218 fn test_oscillator_input_output_counts_f32() {
219 let osc = OscillatorBlock::<f32>::new(440.0, Waveform::Sine, None);
220 assert_eq!(osc.input_count(), DEFAULT_GENERATOR_INPUT_COUNT);
221 assert_eq!(osc.output_count(), DEFAULT_GENERATOR_OUTPUT_COUNT);
222 }
223
224 #[test]
225 fn test_oscillator_input_output_counts_f64() {
226 let osc = OscillatorBlock::<f64>::new(440.0, Waveform::Sine, None);
227 assert_eq!(osc.input_count(), DEFAULT_GENERATOR_INPUT_COUNT);
228 assert_eq!(osc.output_count(), DEFAULT_GENERATOR_OUTPUT_COUNT);
229 }
230
231 #[test]
232 fn test_sine_output_range_f32() {
233 let output = test_oscillator::<f32>(Waveform::Sine, 440.0, 1024);
234 for sample in output {
235 assert!(sample >= -1.05 && sample <= 1.05, "Sine sample {} out of range", sample);
236 }
237 }
238
239 #[test]
240 fn test_sine_output_range_f64() {
241 let output = test_oscillator::<f64>(Waveform::Sine, 440.0, 1024);
242 for sample in output {
243 assert!(sample >= -1.05 && sample <= 1.05, "Sine sample {} out of range", sample);
244 }
245 }
246
247 #[test]
248 fn test_square_output_range_f32() {
249 let output = test_oscillator::<f32>(Waveform::Square, 440.0, 1024);
250 for sample in output {
251 assert!(sample >= -1.1 && sample <= 1.1, "Square sample {} out of range", sample);
252 }
253 }
254
255 #[test]
256 fn test_square_output_range_f64() {
257 let output = test_oscillator::<f64>(Waveform::Square, 440.0, 1024);
258 for sample in output {
259 assert!(sample >= -1.1 && sample <= 1.1, "Square sample {} out of range", sample);
260 }
261 }
262
263 #[test]
264 fn test_sawtooth_output_range_f32() {
265 let output = test_oscillator::<f32>(Waveform::Sawtooth, 440.0, 1024);
266 for sample in output {
267 assert!(
268 sample >= -1.1 && sample <= 1.1,
269 "Sawtooth sample {} out of range",
270 sample
271 );
272 }
273 }
274
275 #[test]
276 fn test_sawtooth_output_range_f64() {
277 let output = test_oscillator::<f64>(Waveform::Sawtooth, 440.0, 1024);
278 for sample in output {
279 assert!(
280 sample >= -1.1 && sample <= 1.1,
281 "Sawtooth sample {} out of range",
282 sample
283 );
284 }
285 }
286
287 #[test]
288 fn test_triangle_output_range_f32() {
289 let output = test_oscillator::<f32>(Waveform::Triangle, 440.0, 1024);
290 for sample in output {
291 assert!(
292 sample >= -1.1 && sample <= 1.1,
293 "Triangle sample {} out of range",
294 sample
295 );
296 }
297 }
298
299 #[test]
300 fn test_triangle_output_range_f64() {
301 let output = test_oscillator::<f64>(Waveform::Triangle, 440.0, 1024);
302 for sample in output {
303 assert!(
304 sample >= -1.1 && sample <= 1.1,
305 "Triangle sample {} out of range",
306 sample
307 );
308 }
309 }
310
311 #[test]
312 fn test_noise_output_range_f32() {
313 let output = test_oscillator::<f32>(Waveform::Noise, 440.0, 1024);
314 for sample in output {
315 assert!(sample >= -1.0 && sample <= 1.0, "Noise sample {} out of range", sample);
316 }
317 }
318
319 #[test]
320 fn test_noise_output_range_f64() {
321 let output = test_oscillator::<f64>(Waveform::Noise, 440.0, 1024);
322 for sample in output {
323 assert!(sample >= -1.0 && sample <= 1.0, "Noise sample {} out of range", sample);
324 }
325 }
326
327 #[test]
328 fn test_sine_produces_signal() {
329 let output = test_oscillator::<f32>(Waveform::Sine, 440.0, 512);
330 let max = output.iter().fold(0.0f32, |acc, &x| acc.max(x.abs()));
331 assert!(max > 0.5, "Sine should produce signal, max={}", max);
332 }
333
334 #[test]
335 fn test_deterministic_with_seed_f32() {
336 let output1 = test_oscillator::<f32>(Waveform::Sawtooth, 440.0, 256);
337 let output2 = test_oscillator::<f32>(Waveform::Sawtooth, 440.0, 256);
338 for (a, b) in output1.iter().zip(output2.iter()) {
339 assert!((a - b).abs() < 1e-6, "Same seed should produce identical output");
340 }
341 }
342
343 #[test]
344 fn test_deterministic_with_seed_f64() {
345 let output1 = test_oscillator::<f64>(Waveform::Sawtooth, 440.0, 256);
346 let output2 = test_oscillator::<f64>(Waveform::Sawtooth, 440.0, 256);
347 for (a, b) in output1.iter().zip(output2.iter()) {
348 assert!((a - b).abs() < 1e-12, "Same seed should produce identical output");
349 }
350 }
351
352 #[test]
353 fn test_phase_continuity_across_buffers_f32() {
354 let mut osc = OscillatorBlock::<f32>::new(440.0, Waveform::Sine, Some(42));
355 let context = test_context(256);
356 let inputs: [&[f32]; 0] = [];
357
358 let mut buffer1 = vec![0.0f32; 256];
359 let mut buffer2 = vec![0.0f32; 256];
360
361 {
362 let mut outputs: [&mut [f32]; 1] = [&mut buffer1];
363 osc.process(&inputs, &mut outputs, &[], &context);
364 }
365 {
366 let mut outputs: [&mut [f32]; 1] = [&mut buffer2];
367 osc.process(&inputs, &mut outputs, &[], &context);
368 }
369
370 let last = buffer1[255];
371 let first = buffer2[0];
372 let diff = (last - first).abs();
373 let samples_per_cycle = 44100.0 / 440.0;
374 let expected_diff_per_sample = 2.0 / samples_per_cycle;
375 assert!(
376 diff < expected_diff_per_sample * 3.0,
377 "Phase discontinuity detected: last={}, first={}, diff={}",
378 last,
379 first,
380 diff
381 );
382 }
383
384 #[test]
385 fn test_phase_continuity_across_buffers_f64() {
386 let mut osc = OscillatorBlock::<f64>::new(440.0, Waveform::Sine, Some(42));
387 let context = test_context(256);
388 let inputs: [&[f64]; 0] = [];
389
390 let mut buffer1 = vec![0.0f64; 256];
391 let mut buffer2 = vec![0.0f64; 256];
392
393 {
394 let mut outputs: [&mut [f64]; 1] = [&mut buffer1];
395 osc.process(&inputs, &mut outputs, &[], &context);
396 }
397 {
398 let mut outputs: [&mut [f64]; 1] = [&mut buffer2];
399 osc.process(&inputs, &mut outputs, &[], &context);
400 }
401
402 let last = buffer1[255];
403 let first = buffer2[0];
404 let diff = (last - first).abs();
405 let samples_per_cycle = 44100.0 / 440.0;
406 let expected_diff_per_sample = 2.0 / samples_per_cycle;
407 assert!(
408 diff < expected_diff_per_sample * 3.0,
409 "Phase discontinuity detected: last={}, first={}, diff={}",
410 last,
411 first,
412 diff
413 );
414 }
415
416 #[test]
417 fn test_frequency_accuracy_via_zero_crossings_f32() {
418 let freq = 440.0;
419 let sample_rate = 44100.0;
420 let num_buffers = 10;
421 let buffer_size = 512;
422 let total_samples = num_buffers * buffer_size;
423
424 let mut osc = OscillatorBlock::<f32>::new(freq, Waveform::Sine, Some(42));
425 let context = test_context(buffer_size);
426 let inputs: [&[f32]; 0] = [];
427
428 let mut all_samples = Vec::with_capacity(total_samples);
429 for _ in 0..num_buffers {
430 let mut buffer = vec![0.0f32; buffer_size];
431 {
432 let mut outputs: [&mut [f32]; 1] = [&mut buffer];
433 osc.process(&inputs, &mut outputs, &[], &context);
434 }
435 all_samples.extend(buffer);
436 }
437
438 let mut zero_crossings = 0;
439 for i in 1..all_samples.len() {
440 if (all_samples[i - 1] < 0.0 && all_samples[i] >= 0.0)
441 || (all_samples[i - 1] >= 0.0 && all_samples[i] < 0.0)
442 {
443 zero_crossings += 1;
444 }
445 }
446
447 let estimated_freq = (zero_crossings as f64 / 2.0) / (total_samples as f64 / sample_rate);
448 let freq_error = (estimated_freq - freq as f64).abs();
449 assert!(
450 freq_error < 5.0,
451 "Frequency error too large: expected {}, got {}, error={}",
452 freq,
453 estimated_freq,
454 freq_error
455 );
456 }
457
458 #[test]
459 fn test_midi_frequency_override_f32() {
460 let mut osc = OscillatorBlock::<f32>::new(440.0, Waveform::Sine, Some(42));
461 osc.set_midi_frequency(880.0);
462
463 let context = test_context(512);
464 let inputs: [&[f32]; 0] = [];
465 let mut output = vec![0.0f32; 512];
466 let mut outputs: [&mut [f32]; 1] = [&mut output];
467 osc.process(&inputs, &mut outputs, &[], &context);
468
469 let max = output.iter().fold(0.0f32, |acc, &x| acc.max(x.abs()));
470 assert!(max > 0.5, "Should produce signal with MIDI frequency");
471 }
472
473 #[test]
474 fn test_clear_midi_frequency_f32() {
475 let mut osc = OscillatorBlock::<f32>::new(440.0, Waveform::Sine, Some(42));
476 osc.set_midi_frequency(880.0);
477 osc.clear_midi_frequency();
478
479 let context = test_context(512);
480 let inputs: [&[f32]; 0] = [];
481 let mut output = vec![0.0f32; 512];
482 let mut outputs: [&mut [f32]; 1] = [&mut output];
483 osc.process(&inputs, &mut outputs, &[], &context);
484
485 let max = output.iter().fold(0.0f32, |acc, &x| acc.max(x.abs()));
486 assert!(max > 0.5, "Should produce signal after clearing MIDI frequency");
487 }
488
489 #[test]
490 fn test_set_waveform_f32() {
491 let mut osc = OscillatorBlock::<f32>::new(440.0, Waveform::Sine, Some(42));
492 osc.set_waveform(Waveform::Square);
493
494 let context = test_context(512);
495 let inputs: [&[f32]; 0] = [];
496 let mut output = vec![0.0f32; 512];
497 let mut outputs: [&mut [f32]; 1] = [&mut output];
498 osc.process(&inputs, &mut outputs, &[], &context);
499
500 let near_one = output.iter().filter(|&&x| (x.abs() - 1.0).abs() < 0.2).count();
501 assert!(
502 near_one > output.len() / 4,
503 "Square wave should have many samples near +/-1"
504 );
505 }
506
507 #[test]
508 fn test_low_frequency_f32() {
509 let output = test_oscillator::<f32>(Waveform::Sine, 20.0, 4096);
510 let max = output.iter().fold(0.0f32, |acc, &x| acc.max(x.abs()));
511 assert!(max > 0.3, "Low frequency should still produce signal");
512 }
513
514 #[test]
515 fn test_high_frequency_f32() {
516 let output = test_oscillator::<f32>(Waveform::Sine, 15000.0, 512);
517 let max = output.iter().fold(0.0f32, |acc, &x| acc.max(x.abs()));
518 assert!(max > 0.5, "High frequency should produce signal");
519 }
520
521 #[test]
522 fn test_nyquist_frequency_f32() {
523 let output = test_oscillator::<f32>(Waveform::Sine, 22000.0, 512);
524 for sample in output {
525 assert!(
526 sample.abs() <= 1.1,
527 "Near-Nyquist frequency should not produce artifacts above 1.0"
528 );
529 }
530 }
531
532 #[test]
533 fn test_pulse_output_range_f32() {
534 let output = test_oscillator::<f32>(Waveform::Pulse, 440.0, 1024);
535 for sample in output {
536 assert!(sample >= -1.1 && sample <= 1.1, "Pulse sample {} out of range", sample);
537 }
538 }
539
540 #[test]
541 fn test_pulse_output_range_f64() {
542 let output = test_oscillator::<f64>(Waveform::Pulse, 440.0, 1024);
543 for sample in output {
544 assert!(sample >= -1.1 && sample <= 1.1, "Pulse sample {} out of range", sample);
545 }
546 }
547
548 #[test]
549 fn test_noise_varies_f32() {
550 let output = test_oscillator::<f32>(Waveform::Noise, 440.0, 256);
551 let first = output[0];
552 let varies = output.iter().any(|&x| (x - first).abs() > 0.01);
553 assert!(varies, "Noise should produce varying values");
554 }
555
556 #[test]
557 fn test_noise_varies_f64() {
558 let output = test_oscillator::<f64>(Waveform::Noise, 440.0, 256);
559 let first = output[0];
560 let varies = output.iter().any(|&x| (x - first).abs() > 0.01);
561 assert!(varies, "Noise should produce varying values");
562 }
563}