1use std::f64::consts::PI;
34
35pub fn impulse(n_samples: usize, amplitude: f64) -> Vec<f64> {
37 let mut signal = vec![0.0; n_samples];
38 if !signal.is_empty() {
39 signal[0] = amplitude;
40 }
41 signal
42}
43
44pub fn sine(sample_rate: f64, frequency: f64, duration: f64, amplitude: f64) -> Vec<f64> {
46 let n_samples = (duration * sample_rate) as usize;
47 (0..n_samples)
48 .map(|i| {
49 let t = i as f64 / sample_rate;
50 amplitude * (2.0 * PI * frequency * t).sin()
51 })
52 .collect()
53}
54
55pub fn two_tone(sample_rate: f64, f1: f64, f2: f64, duration: f64, amplitude: f64) -> Vec<f64> {
57 let n_samples = (duration * sample_rate) as usize;
58 (0..n_samples)
59 .map(|i| {
60 let t = i as f64 / sample_rate;
61 amplitude * 0.5 * ((2.0 * PI * f1 * t).sin() + (2.0 * PI * f2 * t).sin())
62 })
63 .collect()
64}
65
66pub fn exp_sweep(
71 sample_rate: f64,
72 f_start: f64,
73 f_end: f64,
74 duration: f64,
75 amplitude: f64,
76) -> Vec<f64> {
77 let n_samples = (duration * sample_rate) as usize;
78 let log_ratio = (f_end / f_start).ln();
79
80 (0..n_samples)
81 .map(|i| {
82 let t = i as f64 / sample_rate;
83 let phase = 2.0 * PI * f_start * duration / log_ratio
86 * ((t * log_ratio / duration).exp() - 1.0);
87 amplitude * phase.sin()
88 })
89 .collect()
90}
91
92pub fn silence(n_samples: usize) -> Vec<f64> {
94 vec![0.0; n_samples]
95}
96
97pub fn tone_burst(
102 sample_rate: f64,
103 frequency: f64,
104 amplitude: f64,
105 on_ms: f64,
106 off_ms: f64,
107 repetitions: usize,
108) -> Vec<f64> {
109 let on_samples = (on_ms * sample_rate / 1000.0) as usize;
110 let off_samples = (off_ms * sample_rate / 1000.0) as usize;
111 let period_samples = on_samples + off_samples;
112 let total_samples = period_samples * repetitions;
113
114 let mut signal = vec![0.0; total_samples];
115
116 for rep in 0..repetitions {
117 let start = rep * period_samples;
118 for i in 0..on_samples {
119 let t = i as f64 / sample_rate;
120 signal[start + i] = amplitude * (2.0 * PI * frequency * t).sin();
121 }
122 }
124
125 signal
126}
127
128pub fn dbvu_to_peak(dbvu: f64) -> f64 {
133 10.0_f64.powf(dbvu / 20.0) * 0.7746
134}
135
136pub fn level_sweep(
141 sample_rate: f64,
142 frequency: f64,
143 levels_dbvu: &[f64],
144 duration_per_level: f64,
145) -> Vec<f64> {
146 let samples_per_level = (duration_per_level * sample_rate) as usize;
147 let mut signal = Vec::with_capacity(levels_dbvu.len() * samples_per_level);
148
149 for &level in levels_dbvu {
150 let amplitude = dbvu_to_peak(level);
151 for i in 0..samples_per_level {
152 let t = i as f64 / sample_rate;
153 signal.push(amplitude * (2.0 * PI * frequency * t).sin());
154 }
155 }
156
157 signal
158}
159
160#[derive(Debug, Clone)]
162pub enum SignalSpec {
163 Impulse {
164 amplitude: f64,
165 },
166 Sine {
167 frequency: f64,
168 amplitude: f64,
169 duration: f64,
170 },
171 TwoTone {
172 f1: f64,
173 f2: f64,
174 amplitude: f64,
175 duration: f64,
176 },
177 ExpSweep {
178 f_start: f64,
179 f_end: f64,
180 amplitude: f64,
181 duration: f64,
182 },
183 Silence {
184 duration: f64,
185 },
186 ToneBurst {
187 frequency: f64,
188 amplitude: f64,
189 on_ms: f64,
190 off_ms: f64,
191 repetitions: usize,
192 },
193 LevelSweep {
194 frequency: f64,
195 levels_dbvu: Vec<f64>,
196 duration_per_level: f64,
197 },
198}
199
200impl SignalSpec {
201 pub fn generate(&self, sample_rate: f64) -> Vec<f64> {
203 match self {
204 SignalSpec::Impulse { amplitude } => {
205 impulse((sample_rate * 0.1) as usize, *amplitude) }
207 SignalSpec::Sine {
208 frequency,
209 amplitude,
210 duration,
211 } => sine(sample_rate, *frequency, *duration, *amplitude),
212 SignalSpec::TwoTone {
213 f1,
214 f2,
215 amplitude,
216 duration,
217 } => two_tone(sample_rate, *f1, *f2, *duration, *amplitude),
218 SignalSpec::ExpSweep {
219 f_start,
220 f_end,
221 amplitude,
222 duration,
223 } => exp_sweep(sample_rate, *f_start, *f_end, *duration, *amplitude),
224 SignalSpec::Silence { duration } => silence((duration * sample_rate) as usize),
225 SignalSpec::ToneBurst {
226 frequency,
227 amplitude,
228 on_ms,
229 off_ms,
230 repetitions,
231 } => tone_burst(
232 sample_rate,
233 *frequency,
234 *amplitude,
235 *on_ms,
236 *off_ms,
237 *repetitions,
238 ),
239 SignalSpec::LevelSweep {
240 frequency,
241 levels_dbvu,
242 duration_per_level,
243 } => level_sweep(sample_rate, *frequency, levels_dbvu, *duration_per_level),
244 }
245 }
246}
247
248#[cfg(test)]
249mod tests {
250 use super::*;
251
252 #[test]
253 fn impulse_has_correct_shape() {
254 let sig = impulse(100, 1.0);
255 assert_eq!(sig.len(), 100);
256 assert_eq!(sig[0], 1.0);
257 assert!(sig[1..].iter().all(|&x| x == 0.0));
258 }
259
260 #[test]
261 fn sine_has_correct_frequency() {
262 let sr = 48000.0;
263 let freq = 1000.0;
264 let sig = sine(sr, freq, 0.01, 1.0);
265
266 let zero_crossings: usize = sig.windows(2).filter(|w| w[0] * w[1] < 0.0).count();
268 assert!((18..=22).contains(&zero_crossings));
270 }
271
272 #[test]
273 fn exp_sweep_starts_and_ends_correctly() {
274 let sr = 48000.0;
275 let sig = exp_sweep(sr, 20.0, 20000.0, 1.0, 1.0);
276 assert_eq!(sig.len(), 48000);
277 assert!(sig.iter().all(|&x| x.is_finite()));
279 assert!(sig.iter().map(|x| x.abs()).fold(0.0f64, |a, b| a.max(b)) <= 1.01);
280 }
281
282 #[test]
283 fn tone_burst_has_correct_structure() {
284 let sr = 48000.0;
285 let sig = tone_burst(sr, 1000.0, 1.0, 10.0, 90.0, 2);
286
287 assert_eq!(sig.len(), 9600);
289
290 let first_burst_energy: f64 = sig[0..480].iter().map(|x| x * x).sum();
292 assert!(first_burst_energy > 0.1);
293
294 let first_silence: f64 = sig[480..4800].iter().map(|x| x * x).sum();
296 assert!(first_silence < 1e-10);
297 }
298}