1use std::f32::consts::PI;
4use std::sync::Arc;
5
6use crate::context::BaseAudioContext;
7
8#[derive(Debug, Default, Clone)]
10pub struct PeriodicWaveOptions {
11 pub real: Option<Vec<f32>>,
20 pub imag: Option<Vec<f32>>,
28 pub disable_normalization: bool,
34}
35
36#[derive(Debug, Clone, Default)]
72pub struct PeriodicWave {
73 wavetable: Arc<Vec<f32>>,
74}
75
76const PERIODIC_WAVE_TABLE_LENGTH: usize = 8192;
77
78impl PeriodicWave {
79 pub fn new<C: BaseAudioContext>(_context: &C, options: PeriodicWaveOptions) -> Self {
105 let PeriodicWaveOptions {
106 real,
107 imag,
108 disable_normalization,
109 } = options;
110
111 let (real, imag) = match (real, imag) {
112 (Some(r), Some(i)) => {
113 assert_eq!(
114 r.len(),
115 i.len(),
116 "IndexSizeError - `real` and `imag` length should be equal"
117 );
118 assert!(
119 r.len() >= 2,
120 "IndexSizeError - `real` and `imag` length should at least 2"
121 );
122
123 (r, i)
124 }
125 (Some(r), None) => {
126 assert!(
127 r.len() >= 2,
128 "IndexSizeError - `real` and `imag` length should at least 2"
129 );
130
131 let len = r.len();
132 (r, vec![0.; len])
133 }
134 (None, Some(i)) => {
135 assert!(
136 i.len() >= 2,
137 "IndexSizeError - `real` and `imag` length should at least 2"
138 );
139
140 let len = i.len();
141 (vec![0.; len], i)
142 }
143 _ => (vec![0., 0.], vec![0., 1.]),
147 };
148
149 let normalize = !disable_normalization;
150 let wavetable =
152 Self::generate_wavetable(&real, &imag, normalize, PERIODIC_WAVE_TABLE_LENGTH);
153
154 Self {
155 wavetable: Arc::new(wavetable),
156 }
157 }
158
159 pub(crate) fn as_slice(&self) -> &[f32] {
160 &self.wavetable[..]
161 }
162
163 fn generate_wavetable(reals: &[f32], imags: &[f32], normalize: bool, size: usize) -> Vec<f32> {
165 let mut wavetable = Vec::with_capacity(size);
166 let pi_2 = 2. * PI;
167
168 for i in 0..size {
169 let mut sample = 0.;
170 let phase = pi_2 * i as f32 / size as f32;
171
172 for j in 1..reals.len() {
173 let freq = j as f32;
174 let real = reals[j];
175 let imag = imags[j];
176 let rad = phase * freq;
177 let contrib = real * rad.cos() + imag * rad.sin();
178 sample += contrib;
179 }
180
181 wavetable.push(sample);
182 }
183
184 if normalize {
185 Self::normalize(&mut wavetable);
186 }
187
188 wavetable
189 }
190
191 fn normalize(wavetable: &mut [f32]) {
192 let mut max = 0.;
193
194 for sample in wavetable.iter() {
195 let abs = sample.abs();
196 if abs > max {
197 max = abs;
198 }
199 }
200
201 if max > 0. {
203 let norm_factor = 1. / max;
204
205 for sample in wavetable.iter_mut() {
206 *sample *= norm_factor;
207 }
208 }
209 }
210}
211
212#[cfg(test)]
213mod tests {
214 use float_eq::assert_float_eq;
215 use std::f32::consts::PI;
216
217 use super::{PeriodicWave, PeriodicWaveOptions, PERIODIC_WAVE_TABLE_LENGTH};
218 use crate::context::AudioContext;
219
220 #[test]
221 #[should_panic]
222 fn fails_to_build_when_only_real_is_defined_and_too_short() {
223 let context = AudioContext::default();
224
225 let options = PeriodicWaveOptions {
226 real: Some(vec![0.]),
227 imag: None,
228 disable_normalization: false,
229 };
230
231 let _periodic_wave = PeriodicWave::new(&context, options);
232 }
233
234 #[test]
235 #[should_panic]
236 fn fails_to_build_when_only_imag_is_defined_and_too_short() {
237 let context = AudioContext::default();
238
239 let options = PeriodicWaveOptions {
240 real: None,
241 imag: Some(vec![0.]),
242 disable_normalization: false,
243 };
244
245 let _periodic_wave = PeriodicWave::new(&context, options);
246 }
247
248 #[test]
249 #[should_panic]
250 fn fails_to_build_when_imag_and_real_not_equal_length() {
251 let context = AudioContext::default();
252
253 let options = PeriodicWaveOptions {
254 real: Some(vec![0., 0., 0.]),
255 imag: Some(vec![0., 0.]),
256 disable_normalization: false,
257 };
258
259 let _periodic_wave = PeriodicWave::new(&context, options);
260 }
261
262 #[test]
263 #[should_panic]
264 fn fails_to_build_when_imag_and_real_too_shorts() {
265 let context = AudioContext::default();
266
267 let options = PeriodicWaveOptions {
268 real: Some(vec![0.]),
269 imag: Some(vec![0.]),
270 disable_normalization: false,
271 };
272
273 let _periodic_wave = PeriodicWave::new(&context, options);
274 }
275
276 #[test]
277 fn wavetable_generate_sine() {
278 let reals = [0., 0.];
279 let imags = [0., 1.];
280
281 let result =
282 PeriodicWave::generate_wavetable(&reals, &imags, true, PERIODIC_WAVE_TABLE_LENGTH);
283 let mut expected = Vec::new();
284
285 for i in 0..PERIODIC_WAVE_TABLE_LENGTH {
286 let sample = (i as f32 / PERIODIC_WAVE_TABLE_LENGTH as f32 * 2. * PI).sin();
287 expected.push(sample);
288 }
289
290 assert_float_eq!(result[..], expected[..], abs_all <= 1e-6);
291 }
292
293 #[test]
294 fn wavetable_generate_2f_not_norm() {
295 let reals = [0., 0., 0.];
296 let imags = [0., 0.5, 0.5];
297
298 let result =
299 PeriodicWave::generate_wavetable(&reals, &imags, false, PERIODIC_WAVE_TABLE_LENGTH);
300 let mut expected = Vec::new();
301
302 for i in 0..PERIODIC_WAVE_TABLE_LENGTH {
303 let mut sample = 0.;
304 sample += 0.5 * (1. * i as f32 / PERIODIC_WAVE_TABLE_LENGTH as f32 * 2. * PI).sin();
306 sample += 0.5 * (2. * i as f32 / PERIODIC_WAVE_TABLE_LENGTH as f32 * 2. * PI).sin();
308
309 expected.push(sample);
310 }
311
312 assert_float_eq!(result[..], expected[..], abs_all <= 1e-6);
313 }
314
315 #[test]
316 fn normalize() {
317 {
318 let mut signal = [-0.5, 0.2];
319 PeriodicWave::normalize(&mut signal);
320 let expected = [-1., 0.4];
321
322 assert_float_eq!(signal[..], expected[..], abs_all <= 0.);
323 }
324
325 {
326 let mut signal = [0.5, -0.2];
327 PeriodicWave::normalize(&mut signal);
328 let expected = [1., -0.4];
329
330 assert_float_eq!(signal[..], expected[..], abs_all <= 0.);
331 }
332 }
333
334 #[test]
335 fn wavetable_generate_2f_norm() {
336 let reals = [0., 0., 0.];
337 let imags = [0., 0.5, 0.5];
338
339 let result =
340 PeriodicWave::generate_wavetable(&reals, &imags, true, PERIODIC_WAVE_TABLE_LENGTH);
341 let mut expected = Vec::new();
342
343 for i in 0..PERIODIC_WAVE_TABLE_LENGTH {
344 let mut sample = 0.;
345 sample += 0.5 * (1. * i as f32 / PERIODIC_WAVE_TABLE_LENGTH as f32 * 2. * PI).sin();
347 sample += 0.5 * (2. * i as f32 / PERIODIC_WAVE_TABLE_LENGTH as f32 * 2. * PI).sin();
349
350 expected.push(sample);
351 }
352
353 PeriodicWave::normalize(&mut expected);
354
355 assert_float_eq!(result[..], expected[..], abs_all <= 1e-6);
356 }
357}