1use core::iter::FusedIterator;
2
3use crate::{Complex, cossin::cossin};
4use num_traits::{FloatConst, real::Real};
5
6const Q: f32 = (1i64 << 32) as f32;
7
8#[derive(Clone, Debug, PartialEq, PartialOrd)]
10pub struct Sweep {
11 pub rate: i32,
13 pub state: i64,
17}
18
19impl Iterator for Sweep {
21 type Item = i64;
22
23 #[inline]
24 fn next(&mut self) -> Option<Self::Item> {
25 const BIAS: i64 = 1 << 31;
26 let s = self.state;
27 match self
28 .state
29 .checked_add(self.rate as i64 * ((s + BIAS) >> 32))
30 {
31 Some(s) => self.state = s,
32 None => self.state = 0,
33 }
34 Some(s)
35 }
36}
37
38impl Sweep {
39 #[inline]
41 pub fn new(rate: i32, state: i64) -> Self {
42 Self { rate, state }
43 }
44
45 #[inline]
47 pub fn rate(&self) -> f64 {
48 Real::ln_1p(self.rate as f64 / Q as f64)
49 }
50
51 #[inline]
53 pub fn delay(&self, harmonic: f64) -> f64 {
54 Real::ln(harmonic) / self.rate()
55 }
56
57 #[inline]
59 pub fn octave(&self) -> f64 {
60 f64::LN_2() / self.rate()
61 }
62
63 #[inline]
65 pub fn decade(&self) -> f64 {
66 f64::LN_10() / self.rate()
67 }
68
69 #[inline]
71 pub fn state(&self) -> f64 {
72 self.cycles() * self.rate()
73 }
74
75 #[inline]
77 pub fn cycles(&self) -> f64 {
78 self.state as f64 / (Q as f64 * self.rate as f64)
79 }
80
81 #[inline]
83 pub fn continuous(&self, t: f64) -> f64 {
84 self.cycles() * Real::exp(self.rate() * t)
85 }
86
87 pub fn inverse_filter(&self, mut f: f32) -> Complex<f32> {
98 let rate = Real::ln_1p(self.rate as f32 / Q);
99 f /= rate;
100 let amp = 2.0 * rate * f.sqrt();
101 let inv_cycles = Q * self.rate as f32 / self.state as f32;
102 let turns = 0.125 - f * (1.0 - Real::ln(f * inv_cycles));
103 let (im, re) = Real::sin_cos(f32::TAU() * turns);
104 Complex::new(amp * re, amp * im)
105 }
106
107 pub fn fit(stop: f32, harmonics: f32, cycles: f32) -> Result<Self, SweepError> {
113 if !(0.0..=0.5).contains(&stop) {
114 return Err(SweepError::Stop);
115 }
116 let rate = Real::round(Q * Real::exp_m1(stop / (cycles * harmonics))) as i32;
117 let state = (rate as i64 * cycles as i64) << 32;
118 if state <= 0 {
119 return Err(SweepError::Start);
120 }
121 Ok(Self::new(rate, state))
122 }
123}
124
125#[derive(Clone, Copy, Debug, PartialEq, PartialOrd, thiserror::Error)]
127pub enum SweepError {
128 #[error("Start out of bounds")]
130 Start,
131 #[error("Stop out of bounds")]
133 Stop,
134}
135
136#[derive(Clone, Debug, PartialEq, PartialOrd)]
138pub struct AccuOsc<T> {
139 sweep: T,
140 pub state: i64,
142}
143
144impl<T> AccuOsc<T> {
145 #[inline]
147 pub fn new(sweep: T) -> Self {
148 Self { sweep, state: 0 }
149 }
150}
151
152impl<T: Iterator<Item = i64>> Iterator for AccuOsc<T> {
154 type Item = Complex<i32>;
155
156 #[inline]
157 fn next(&mut self) -> Option<Self::Item> {
158 self.sweep.next().map(|f| {
159 let s = self.state;
160 self.state = s.wrapping_add(f);
161 let (re, im) = cossin((s >> 32) as _);
162 Complex::new(re, im)
163 })
164 }
165
166 #[inline]
167 fn size_hint(&self) -> (usize, Option<usize>) {
168 self.sweep.size_hint()
169 }
170}
171
172impl<T: FusedIterator + Iterator<Item = i64>> FusedIterator for AccuOsc<T> {}
173
174#[cfg(test)]
175mod test {
176 use super::*;
177 use crate::testing::*;
178
179 #[test]
180 fn test() {
181 let stop = 0.3;
182 let harmonics = 3000.0;
183 let cycles = 3.0;
184 let sweep = Sweep::fit(stop, harmonics, cycles).unwrap();
185 assert_eq!(sweep.rate, 0x22f40);
186 let len = sweep.delay(harmonics as _);
187 assert!(isclose(len, 240190.96, 0.0, 1e-2));
188 assert!(isclose(sweep.cycles(), cycles as f64, 0.0, 1e-2));
190 assert_eq!(sweep.state(), sweep.continuous(0.0) * sweep.rate());
191 assert!((stop * 0.99..=1.01 * stop).contains(&(sweep.state() as f32 * harmonics)));
193 assert!(
194 (stop * 0.99..=1.01 * stop)
195 .contains(&((sweep.continuous(len as _) * sweep.rate()) as f32))
196 );
197 for h in 0..harmonics as i32 {
200 let p = sweep.continuous(sweep.delay(h as _) as _);
201 assert!(isclose(p, h as f64 * cycles as f64, 0.0, 1e-10));
202 }
203 let sweep0 = sweep.clone();
204 for (t, p) in sweep
205 .scan(0i64, |p, f| {
206 let p0 = *p;
207 *p = p0.wrapping_add(f);
208 Some(p0 as f64 / Q.powi(2) as f64)
209 })
210 .take(len as _)
211 .enumerate()
212 {
213 let err = p - sweep0.continuous(t as _);
215 assert!(isclose(err - err.round(), 0.0, 0.0, 5e-5));
216 }
217 }
218}