1#![no_std]
2
3use core::iter::Take;
4
5use idsp::{AccuOsc, Sweep};
6use miniconf::Tree;
7use rand_core::{RngCore, SeedableRng};
8use rand_xorshift::XorShiftRng;
9use serde::{Deserialize, Serialize};
10
11#[derive(Copy, Clone, Debug, Deserialize, Serialize)]
13pub enum Signal {
14 Cosine,
15 Square,
16 Triangle,
17 WhiteNoise,
18 SweptSine,
19}
20
21impl Signal {
22 #[inline]
23 fn map(&self, x: i32) -> i32 {
24 match self {
25 Self::Cosine => idsp::cossin(x).0,
26 Self::Square => {
27 if x.is_negative() {
28 -i32::MAX
29 } else {
30 i32::MAX
31 }
32 }
33 Self::Triangle => i32::MIN + (x.saturating_abs() << 1),
34 _ => unimplemented!(),
35 }
36 }
37}
38
39#[derive(Clone, Debug, Tree, Serialize, Deserialize)]
41#[tree(meta(doc, typename))]
42pub struct Config {
43 #[tree(with=miniconf::leaf)]
45 signal: Signal,
46
47 frequency: f32,
49
50 symmetry: f32,
54
55 amplitude: f32,
57
58 offset: f32,
60
61 phase: f32,
63
64 length: u32,
66
67 state: i64,
69
70 rate: i32,
72}
73
74impl Default for Config {
75 fn default() -> Self {
76 Self {
77 frequency: 1.0e3,
78 symmetry: 0.5,
79 signal: Signal::Cosine,
80 amplitude: 0.0,
81 phase: 0.0,
82 offset: 0.0,
83 state: 0,
84 rate: 0,
85 length: 0,
86 }
87 }
88}
89
90#[derive(Clone, Debug)]
91pub struct AsymmetricAccu {
92 ftw: [i32; 2],
93 pow: i32,
94 accu: i32,
95 count: u32,
96}
97
98impl Iterator for AsymmetricAccu {
99 type Item = i32;
100 fn next(&mut self) -> Option<Self::Item> {
101 let sign = self.accu.is_negative();
102 self.accu = self.accu.wrapping_add(self.ftw[sign as usize]);
103 self.count
104 .checked_sub(sign as u32 ^ self.accu.is_negative() as u32)
105 .map(|c| {
106 self.count = c;
107 self.accu.wrapping_add(self.pow)
108 })
109 }
110}
111
112#[derive(Clone, Debug)]
113pub struct Scaler {
114 amp: i32,
115 offset: i32,
116}
117
118impl Scaler {
119 fn map(&self, x: i32) -> i32 {
120 (((x as i64 * self.amp as i64) >> 31) as i32)
121 .saturating_add(self.offset)
122 }
123}
124
125#[derive(Copy, Clone, Debug, thiserror::Error)]
127pub enum Error {
128 #[error("Invalid amplitude")]
130 Amplitude,
131 #[error("Invalid symmetry")]
133 Symmetry,
134 #[error("Invalid frequency")]
136 Frequency,
137 #[error("Sweep would wrap")]
139 Wrap,
140}
141
142#[derive(Clone, Debug)]
143pub enum Source {
144 SweptSine {
145 sweep: Take<AccuOsc<Sweep>>,
146 amp: Scaler,
147 },
148 Periodic {
149 accu: AsymmetricAccu,
150 signal: Signal,
151 amp: Scaler,
152 },
153 WhiteNoise {
154 rng: XorShiftRng,
155 count: u32,
156 amp: Scaler,
157 },
158}
159
160impl Iterator for Source {
161 type Item = i32;
162 #[inline]
163 fn next(&mut self) -> Option<Self::Item> {
164 let (s, a) = match self {
165 Self::SweptSine { sweep, amp } => {
166 (sweep.next().map(|c| c.im()), amp)
167 }
168 Self::Periodic { accu, signal, amp } => {
169 (accu.next().map(|p| signal.map(p)), amp)
170 }
171 Self::WhiteNoise { rng, count, amp } => (
172 count.checked_sub(1).map(|m| {
173 *count = m;
174 rng.next_u32() as i32
175 }),
176 amp,
177 ),
178 };
179 Some(a.map(s.unwrap_or_default()))
180 }
181}
182
183impl Config {
184 pub fn build(&self, period: f32, scale: f32) -> Result<Source, Error> {
186 if !(0.0..1.0).contains(&self.symmetry) {
187 return Err(Error::Symmetry);
188 }
189
190 const NYQUIST: f32 = (1u32 << 31) as _;
191 let ftw0 = self.frequency * period * NYQUIST;
192 if !(0.0..2.0 * NYQUIST).contains(&ftw0) {
193 return Err(Error::Frequency);
194 }
195
196 let ftw = [
198 if self.symmetry * NYQUIST > ftw0 {
199 ftw0 / self.symmetry
200 } else {
201 NYQUIST
202 } as i32,
203 if (1.0 - self.symmetry) * NYQUIST > ftw0 {
204 ftw0 / (1.0 - self.symmetry)
205 } else {
206 NYQUIST
207 } as i32,
208 ];
209
210 let offset = self.offset * scale;
211 let amplitude = self.amplitude * scale;
212 fn abs(x: f32) -> f32 {
213 if x.is_sign_negative() { -x } else { x }
214 }
215 if abs(offset) + abs(amplitude) >= 1.0 {
216 return Err(Error::Amplitude);
217 }
218 let amp = Scaler {
219 amp: (amplitude * NYQUIST) as _,
220 offset: (offset * NYQUIST) as _,
221 };
222
223 Ok(match self.signal {
224 signal @ (Signal::Cosine | Signal::Square | Signal::Triangle) => {
225 Source::Periodic {
226 accu: AsymmetricAccu {
227 ftw,
228 pow: (self.phase * NYQUIST) as i32,
229 accu: 0,
230 count: self.length,
231 },
232 signal,
233 amp,
234 }
235 }
236 Signal::SweptSine => Source::SweptSine {
237 sweep: AccuOsc::new(Sweep::new(self.rate, self.state))
238 .take(self.length as _),
239 amp,
240 },
241 Signal::WhiteNoise => Source::WhiteNoise {
242 rng: XorShiftRng::from_seed(Default::default()),
243 count: self.length,
244 amp,
245 },
246 })
247 }
248}