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 } => (sweep.next().map(|c| c.im), amp),
166 Self::Periodic { accu, signal, amp } => {
167 (accu.next().map(|p| signal.map(p)), amp)
168 }
169 Self::WhiteNoise { rng, count, amp } => (
170 count.checked_sub(1).map(|m| {
171 *count = m;
172 rng.next_u32() as i32
173 }),
174 amp,
175 ),
176 };
177 Some(a.map(s.unwrap_or_default()))
178 }
179}
180
181impl Config {
182 pub fn build(&self, period: f32, scale: f32) -> Result<Source, Error> {
184 if !(0.0..1.0).contains(&self.symmetry) {
185 return Err(Error::Symmetry);
186 }
187
188 const NYQUIST: f32 = (1u32 << 31) as _;
189 let ftw0 = self.frequency * period * NYQUIST;
190 if !(0.0..2.0 * NYQUIST).contains(&ftw0) {
191 return Err(Error::Frequency);
192 }
193
194 let ftw = [
196 if self.symmetry * NYQUIST > ftw0 {
197 ftw0 / self.symmetry
198 } else {
199 NYQUIST
200 } as i32,
201 if (1.0 - self.symmetry) * NYQUIST > ftw0 {
202 ftw0 / (1.0 - self.symmetry)
203 } else {
204 NYQUIST
205 } as i32,
206 ];
207
208 let offset = self.offset * scale;
209 let amplitude = self.amplitude * scale;
210 fn abs(x: f32) -> f32 {
211 if x.is_sign_negative() { -x } else { x }
212 }
213 if abs(offset) + abs(amplitude) >= 1.0 {
214 return Err(Error::Amplitude);
215 }
216 let amp = Scaler {
217 amp: (amplitude * NYQUIST) as _,
218 offset: (offset * NYQUIST) as _,
219 };
220
221 Ok(match self.signal {
222 signal @ (Signal::Cosine | Signal::Square | Signal::Triangle) => {
223 Source::Periodic {
224 accu: AsymmetricAccu {
225 ftw,
226 pow: (self.phase * NYQUIST) as i32,
227 accu: 0,
228 count: self.length,
229 },
230 signal,
231 amp,
232 }
233 }
234 Signal::SweptSine => Source::SweptSine {
235 sweep: AccuOsc::new(Sweep::new(self.rate, self.state))
236 .take(self.length as _),
237 amp,
238 },
239 Signal::WhiteNoise => Source::WhiteNoise {
240 rng: XorShiftRng::from_seed(Default::default()),
241 count: self.length,
242 amp,
243 },
244 })
245 }
246}