1use caw_builder_proc_macros::builder;
2use caw_core::{Buf, Sig, SigCtx, SigT};
3use itertools::izip;
4use rand::{rngs::StdRng, Rng, SeedableRng};
5use wide::f32x8;
6
7#[derive(Debug, Clone, Copy)]
8pub enum SuperSawInit {
9 Random,
10 RandomWithSeed(u64),
11 Const(f32),
12}
13
14pub struct SuperSaw<F, D>
15where
16 F: SigT<Item = f32>,
17 D: SigT<Item = f32>,
18{
19 freq_hz: F,
20 detune_ratio: D,
21 state_01: Vec<f32x8>,
22 mask: Vec<f32x8>,
26 offset_per_f32x8: Vec<f32>,
29 num_oscillators: usize,
30 buf: Vec<f32>,
31}
32
33impl<F, D> SigT for SuperSaw<F, D>
34where
35 F: SigT<Item = f32>,
36 D: SigT<Item = f32>,
37{
38 type Item = f32;
39
40 fn sample(&mut self, ctx: &SigCtx) -> impl Buf<Self::Item> {
41 const RANGE_0_7: [f32; 8] = [0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0];
42 let range_0_7: f32x8 = RANGE_0_7.into();
43 let scale_factor = 2.0 / (self.num_oscillators as f32);
45 let freq_hz = self.freq_hz.sample(ctx);
46 let detune_ratio = self.detune_ratio.sample(ctx);
47 self.buf.resize(ctx.num_samples, 0.0);
48 for (sample, freq_hz, detune_ratio) in izip! {
49 self.buf.iter_mut(),
50 freq_hz.iter(),
51 detune_ratio.iter(),
52 } {
53 *sample = 0.0;
54 let (mut freq_hz, detune_step_hz) = if self.num_oscillators == 1 {
55 (f32x8::splat(freq_hz), 0.0)
56 } else {
57 let detune_hz = freq_hz * detune_ratio;
58 let detune_step_hz =
60 detune_hz / ((self.num_oscillators - 1) as f32);
61 let detune_offsets_hz = range_0_7 * detune_step_hz;
62 let min_freq_hz = freq_hz - (detune_hz / 2.0);
63 (detune_offsets_hz + min_freq_hz, detune_step_hz)
64 };
65 for ((state_01, mask), offset) in self
66 .state_01
67 .iter_mut()
68 .zip(self.mask.iter())
69 .zip(self.offset_per_f32x8.iter())
70 {
71 let state_delta = freq_hz / ctx.sample_rate_hz;
72 *state_01 += state_delta * mask;
74 *state_01 = *state_01 - (*state_01 - 0.5).round();
77 let total_with_offset = state_01.reduce_add() - offset;
78 *sample += total_with_offset * scale_factor;
79 freq_hz = freq_hz + (detune_step_hz * 8.0);
80 }
81 }
82 &self.buf
83 }
84}
85
86impl<F, D> SuperSaw<F, D>
87where
88 F: SigT<Item = f32>,
89 D: SigT<Item = f32>,
90{
91 pub fn new(
92 freq_hz: F,
93 detune_ratio: D,
94 num_oscillators: usize,
95 init: SuperSawInit,
96 ) -> Sig<Self> {
97 assert!(
98 num_oscillators >= 1,
99 "There must be at least one oscillator"
100 );
101 let num_f32x8s = num_oscillators / 8;
102 let mut state_01 = vec![f32x8::ZERO; num_f32x8s];
103 let mut mask = vec![f32x8::ONE; num_f32x8s];
104 let mut offset_per_f32x8 = vec![4.0; num_f32x8s];
105 if num_oscillators % 8 != 0 {
106 state_01.push(f32x8::ZERO);
107 let mut final_mask = [0.0; 8];
108 for x in final_mask.iter_mut().take(num_oscillators % 8) {
109 *x = 1.0;
110 }
111 mask.push(final_mask.into());
112 offset_per_f32x8.push((num_oscillators % 8) as f32 / 2.0);
113 }
114 match init {
115 SuperSawInit::Const(c) => {
116 for (x, mask) in state_01.iter_mut().zip(mask.iter()) {
117 *x = f32x8::splat(c);
118 *x *= mask;
119 }
120 }
121 SuperSawInit::Random => {
122 let mut rng = StdRng::from_entropy();
123 for (x, mask) in state_01.iter_mut().zip(mask.iter()) {
124 *x = rng.gen::<[f32; 8]>().into();
125 *x *= mask;
126 }
127 }
128 SuperSawInit::RandomWithSeed(seed) => {
129 let mut rng = StdRng::seed_from_u64(seed);
130 for (x, mask) in state_01.iter_mut().zip(mask.iter()) {
131 *x = rng.gen::<[f32; 8]>().into();
132 *x *= mask;
133 }
134 }
135 }
136 Sig(Self {
137 freq_hz,
138 detune_ratio,
139 state_01,
140 mask,
141 offset_per_f32x8,
142 num_oscillators,
143 buf: Vec::new(),
144 })
145 }
146}
147
148builder! {
149 #[constructor = "super_saw"]
150 #[constructor_doc = "The mean of multiple saw wave oscillators with slight differences in frequency"]
151 #[build_fn = "SuperSaw::new"]
152 #[build_ty = "Sig<SuperSaw<F, D>>"]
153 #[generic_setter_type_name = "X"]
154 pub struct SuperSawBuilder {
155 #[generic_with_constraint = "SigT<Item = f32>"]
156 #[generic_name = "F"]
157 freq_hz: _,
158 #[generic_with_constraint = "SigT<Item = f32>"]
159 #[default = 0.01]
160 #[generic_name = "D"]
161 detune_ratio: f32,
162 #[default = 16]
163 num_oscillators: usize,
164 #[default = SuperSawInit::Random]
165 init: SuperSawInit,
166 }
167}