1use std::f32::consts::PI;
4use std::sync::Arc;
5
6use crate::context::BaseAudioContext;
7
8use crate::node::TABLE_LENGTH_USIZE;
9
10#[derive(Debug, Default, Clone)]
12pub struct PeriodicWaveOptions {
13 pub real: Option<Vec<f32>>,
22 pub imag: Option<Vec<f32>>,
30 pub disable_normalization: bool,
36}
37
38#[derive(Debug, Clone, Default)]
74pub struct PeriodicWave {
75 wavetable: Arc<Vec<f32>>,
76}
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 = Self::generate_wavetable(&real, &imag, normalize, TABLE_LENGTH_USIZE);
152
153 Self {
154 wavetable: Arc::new(wavetable),
155 }
156 }
157
158 pub(crate) fn as_slice(&self) -> &[f32] {
159 &self.wavetable[..]
160 }
161
162 fn generate_wavetable(reals: &[f32], imags: &[f32], normalize: bool, size: usize) -> Vec<f32> {
164 let mut wavetable = Vec::with_capacity(size);
165 let pi_2 = 2. * PI;
166
167 for i in 0..size {
168 let mut sample = 0.;
169 let phase = pi_2 * i as f32 / size as f32;
170
171 for j in 1..reals.len() {
172 let freq = j as f32;
173 let real = reals[j];
174 let imag = imags[j];
175 let rad = phase * freq;
176 let contrib = real * rad.cos() + imag * rad.sin();
177 sample += contrib;
178 }
179
180 wavetable.push(sample);
181 }
182
183 if normalize {
184 Self::normalize(&mut wavetable);
185 }
186
187 wavetable
188 }
189
190 fn normalize(wavetable: &mut [f32]) {
191 let mut max = 0.;
192
193 for sample in wavetable.iter() {
194 let abs = sample.abs();
195 if abs > max {
196 max = abs;
197 }
198 }
199
200 if max > 0. {
202 let norm_factor = 1. / max;
203
204 for sample in wavetable.iter_mut() {
205 *sample *= norm_factor;
206 }
207 }
208 }
209}
210
211#[cfg(test)]
212mod tests {
213 use float_eq::assert_float_eq;
214 use std::f32::consts::PI;
215
216 use crate::context::AudioContext;
217 use crate::node::{TABLE_LENGTH_F32, TABLE_LENGTH_USIZE};
218
219 use super::{PeriodicWave, PeriodicWaveOptions};
220
221 #[test]
222 #[should_panic]
223 fn fails_to_build_when_only_real_is_defined_and_too_short() {
224 let context = AudioContext::default();
225
226 let options = PeriodicWaveOptions {
227 real: Some(vec![0.]),
228 imag: None,
229 disable_normalization: false,
230 };
231
232 let _periodic_wave = PeriodicWave::new(&context, options);
233 }
234
235 #[test]
236 #[should_panic]
237 fn fails_to_build_when_only_imag_is_defined_and_too_short() {
238 let context = AudioContext::default();
239
240 let options = PeriodicWaveOptions {
241 real: None,
242 imag: Some(vec![0.]),
243 disable_normalization: false,
244 };
245
246 let _periodic_wave = PeriodicWave::new(&context, options);
247 }
248
249 #[test]
250 #[should_panic]
251 fn fails_to_build_when_imag_and_real_not_equal_length() {
252 let context = AudioContext::default();
253
254 let options = PeriodicWaveOptions {
255 real: Some(vec![0., 0., 0.]),
256 imag: Some(vec![0., 0.]),
257 disable_normalization: false,
258 };
259
260 let _periodic_wave = PeriodicWave::new(&context, options);
261 }
262
263 #[test]
264 #[should_panic]
265 fn fails_to_build_when_imag_and_real_too_shorts() {
266 let context = AudioContext::default();
267
268 let options = PeriodicWaveOptions {
269 real: Some(vec![0.]),
270 imag: Some(vec![0.]),
271 disable_normalization: false,
272 };
273
274 let _periodic_wave = PeriodicWave::new(&context, options);
275 }
276
277 #[test]
278 fn wavetable_generate_sine() {
279 let reals = [0., 0.];
280 let imags = [0., 1.];
281
282 let result = PeriodicWave::generate_wavetable(&reals, &imags, true, TABLE_LENGTH_USIZE);
283 let mut expected = Vec::new();
284
285 for i in 0..TABLE_LENGTH_USIZE {
286 let sample = (i as f32 / TABLE_LENGTH_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 = PeriodicWave::generate_wavetable(&reals, &imags, false, TABLE_LENGTH_USIZE);
299 let mut expected = Vec::new();
300
301 for i in 0..TABLE_LENGTH_USIZE {
302 let mut sample = 0.;
303 sample += 0.5 * (1. * i as f32 / TABLE_LENGTH_F32 * 2. * PI).sin();
305 sample += 0.5 * (2. * i as f32 / TABLE_LENGTH_F32 * 2. * PI).sin();
307
308 expected.push(sample);
309 }
310
311 assert_float_eq!(result[..], expected[..], abs_all <= 1e-6);
312 }
313
314 #[test]
315 fn normalize() {
316 {
317 let mut signal = [-0.5, 0.2];
318 PeriodicWave::normalize(&mut signal);
319 let expected = [-1., 0.4];
320
321 assert_float_eq!(signal[..], expected[..], abs_all <= 0.);
322 }
323
324 {
325 let mut signal = [0.5, -0.2];
326 PeriodicWave::normalize(&mut signal);
327 let expected = [1., -0.4];
328
329 assert_float_eq!(signal[..], expected[..], abs_all <= 0.);
330 }
331 }
332
333 #[test]
334 fn wavetable_generate_2f_norm() {
335 let reals = [0., 0., 0.];
336 let imags = [0., 0.5, 0.5];
337
338 let result = PeriodicWave::generate_wavetable(&reals, &imags, true, TABLE_LENGTH_USIZE);
339 let mut expected = Vec::new();
340
341 for i in 0..TABLE_LENGTH_USIZE {
342 let mut sample = 0.;
343 sample += 0.5 * (1. * i as f32 / TABLE_LENGTH_F32 * 2. * PI).sin();
345 sample += 0.5 * (2. * i as f32 / TABLE_LENGTH_F32 * 2. * PI).sin();
347
348 expected.push(sample);
349 }
350
351 PeriodicWave::normalize(&mut expected);
352
353 assert_float_eq!(result[..], expected[..], abs_all <= 1e-6);
354 }
355}