1use std::collections::VecDeque;
4
5struct SimpleRng(u64);
7impl SimpleRng {
8 fn new(seed: u64) -> Self { Self(seed.wrapping_add(1)) }
9 fn next_u64(&mut self) -> u64 {
10 self.0 ^= self.0 << 13;
11 self.0 ^= self.0 >> 7;
12 self.0 ^= self.0 << 17;
13 self.0
14 }
15 fn next_f32(&mut self) -> f32 { (self.next_u64() >> 40) as f32 / (1u64 << 24) as f32 }
16}
17
18#[derive(Debug, Clone)]
20pub struct AudioBuffer {
21 pub samples: Vec<Vec<f32>>,
22 pub sample_rate: u32,
23}
24
25impl AudioBuffer {
26 pub fn channels(&self) -> usize { self.samples.len() }
27 pub fn len(&self) -> usize { self.samples.first().map(|c| c.len()).unwrap_or(0) }
28 pub fn duration_secs(&self) -> f32 { self.len() as f32 / self.sample_rate as f32 }
29 pub fn to_mono(&self) -> Vec<f32> {
30 if self.channels() == 1 { return self.samples[0].clone(); }
31 let n = self.len();
32 let mut mono = vec![0.0; n];
33 let scale = 1.0 / self.channels() as f32;
34 for ch in &self.samples { for (i, &s) in ch.iter().enumerate() { mono[i] += s * scale; } }
35 mono
36 }
37}
38
39pub fn load_wav(path: &str) -> Result<AudioBuffer, String> {
42 let reader = hound::WavReader::open(path).map_err(|e| format!("WAV read: {}", e))?;
43 let spec = reader.spec();
44 let channels = spec.channels as usize;
45 let raw: Vec<f32> = match spec.sample_format {
46 hound::SampleFormat::Float => reader.into_samples::<f32>().map(|s| s.unwrap_or(0.0)).collect(),
47 hound::SampleFormat::Int => {
48 let max = (1i64 << (spec.bits_per_sample - 1)) as f32;
49 reader.into_samples::<i32>().map(|s| s.unwrap_or(0) as f32 / max).collect()
50 }
51 };
52 let mut samples = vec![Vec::new(); channels];
53 for (i, &s) in raw.iter().enumerate() { samples[i % channels].push(s); }
54 Ok(AudioBuffer { samples, sample_rate: spec.sample_rate })
55}
56
57pub fn write_wav(path: &str, buf: &AudioBuffer) -> Result<(), String> {
58 let spec = hound::WavSpec { channels: buf.channels() as u16, sample_rate: buf.sample_rate, bits_per_sample: 32, sample_format: hound::SampleFormat::Float };
59 let mut w = hound::WavWriter::create(path, spec).map_err(|e| format!("WAV write: {}", e))?;
60 for i in 0..buf.len() { for ch in &buf.samples { w.write_sample(ch[i]).map_err(|e| format!("{}", e))?; } }
61 w.finalize().map_err(|e| format!("{}", e))
62}
63
64pub fn write_game_audio(path: &str, buf: &AudioBuffer) -> Result<(), String> {
68 let spec = hound::WavSpec {
69 channels: buf.channels() as u16,
70 sample_rate: buf.sample_rate,
71 bits_per_sample: 16,
72 sample_format: hound::SampleFormat::Int,
73 };
74 let mut w = hound::WavWriter::create(path, spec).map_err(|e| format!("WAV write: {}", e))?;
75 for i in 0..buf.len() {
76 for ch in &buf.samples {
77 let sample = (ch[i].clamp(-1.0, 1.0) * 32767.0) as i16;
78 w.write_sample(sample).map_err(|e| format!("{}", e))?;
79 }
80 }
81 w.finalize().map_err(|e| format!("{}", e))
82}
83
84pub fn load_audio(path: &str) -> Result<AudioBuffer, String> {
86 use symphonia::core::audio::SampleBuffer;
87 use symphonia::core::codecs::DecoderOptions;
88 use symphonia::core::formats::FormatOptions;
89 use symphonia::core::io::MediaSourceStream;
90 use symphonia::core::meta::MetadataOptions;
91 use symphonia::core::probe::Hint;
92
93 let file = std::fs::File::open(path).map_err(|e| format!("open {}: {}", path, e))?;
95 let mss = MediaSourceStream::new(Box::new(file), Default::default());
96
97 let mut hint = Hint::new();
99 if let Some(ext) = std::path::Path::new(path).extension().and_then(|e| e.to_str()) {
100 hint.with_extension(ext);
101 }
102
103 let probed = symphonia::default::get_probe()
104 .format(&hint, mss, &FormatOptions::default(), &MetadataOptions::default())
105 .map_err(|e| format!("probe {}: {}", path, e))?;
106
107 let mut format_reader = probed.format;
108
109 let track = format_reader
111 .default_track()
112 .ok_or_else(|| "no default track".to_string())?;
113 let sample_rate = track.codec_params.sample_rate.ok_or("no sample rate")?;
114 let n_channels = track
115 .codec_params
116 .channels
117 .map(|c| c.count())
118 .unwrap_or(1);
119 let track_id = track.id;
120
121 let mut decoder = symphonia::default::get_codecs()
122 .make(&track.codec_params, &DecoderOptions::default())
123 .map_err(|e| format!("decoder: {}", e))?;
124
125 let mut interleaved = Vec::<f32>::new();
127 loop {
128 let packet = match format_reader.next_packet() {
129 Ok(p) => p,
130 Err(symphonia::core::errors::Error::IoError(ref e))
131 if e.kind() == std::io::ErrorKind::UnexpectedEof =>
132 {
133 break;
134 }
135 Err(_) => break,
136 };
137 if packet.track_id() != track_id {
138 continue;
139 }
140 let decoded = match decoder.decode(&packet) {
141 Ok(d) => d,
142 Err(_) => continue,
143 };
144 let spec = *decoded.spec();
145 let duration = decoded.capacity();
146 let mut sample_buf = SampleBuffer::<f32>::new(duration as u64, spec);
147 sample_buf.copy_interleaved_ref(decoded);
148 interleaved.extend_from_slice(sample_buf.samples());
149 }
150
151 let mut samples = vec![Vec::new(); n_channels];
153 for (i, &s) in interleaved.iter().enumerate() {
154 samples[i % n_channels].push(s);
155 }
156
157 Ok(AudioBuffer {
159 samples,
160 sample_rate,
161 })
162}
163
164pub fn time_stretch(buf: &AudioBuffer, factor: f64) -> Result<AudioBuffer, String> {
167 let new_len = (buf.len() as f64 * factor) as usize;
168 let samples = buf.samples.iter().map(|ch| {
169 (0..new_len).map(|i| {
170 let src = i as f64 / factor;
171 let idx = src as usize;
172 let frac = (src - idx as f64) as f32;
173 let a = ch.get(idx).copied().unwrap_or(0.0);
174 let b = ch.get(idx + 1).copied().unwrap_or(a);
175 a + (b - a) * frac
176 }).collect()
177 }).collect();
178 Ok(AudioBuffer { samples, sample_rate: buf.sample_rate })
179}
180
181pub fn pitch_shift(buf: &AudioBuffer, semitones: f32) -> Result<AudioBuffer, String> {
182 let ratio = 2.0_f64.powf(semitones as f64 / 12.0);
183 let mut stretched = time_stretch(buf, ratio)?;
184 stretched.sample_rate = buf.sample_rate;
185 Ok(stretched)
186}
187
188pub fn reverb(mut buf: AudioBuffer, room_size: f32, damping: f32, mix: f32) -> AudioBuffer {
189 let sr = buf.sample_rate as f32;
190 let comb_lengths = [(0.0297 * room_size * sr) as usize, (0.0371 * room_size * sr) as usize,
191 (0.0411 * room_size * sr) as usize, (0.0437 * room_size * sr) as usize];
192 let ap_lengths = [(0.0050 * sr) as usize, (0.0017 * sr) as usize];
193 for ch in &mut buf.samples {
194 let mut comb_out = vec![0.0f32; ch.len()];
195 for &len in &comb_lengths {
196 let mut delay = VecDeque::from(vec![0.0f32; len.max(1)]);
197 let mut fs = 0.0f32;
198 for i in 0..ch.len() {
199 let d = delay.pop_front().unwrap_or(0.0);
200 fs = d * (1.0 - damping) + fs * damping;
201 delay.push_back(ch[i] + fs * 0.7);
202 comb_out[i] += d;
203 }
204 }
205 for &len in &ap_lengths {
206 let mut delay = VecDeque::from(vec![0.0f32; len.max(1)]);
207 for i in 0..comb_out.len() {
208 let d = delay.pop_front().unwrap_or(0.0);
209 let inp = comb_out[i];
210 comb_out[i] = d - 0.5 * inp;
211 delay.push_back(inp + 0.5 * d);
212 }
213 }
214 for i in 0..ch.len() { ch[i] = ch[i] * (1.0 - mix) + comb_out[i] * mix * 0.25; }
215 }
216 buf
217}
218
219pub fn delay(mut buf: AudioBuffer, time_ms: f32, feedback: f32, mix: f32) -> AudioBuffer {
220 let dl = (time_ms * 0.001 * buf.sample_rate as f32) as usize;
221 let dl = dl.max(1);
222 for ch in &mut buf.samples {
223 let mut ring = vec![0.0f32; dl];
224 let mut pos = 0usize;
225 for i in 0..ch.len() {
226 let d = ring[pos % dl];
227 ring[pos % dl] = ch[i] + d * feedback;
228 ch[i] = ch[i] * (1.0 - mix) + d * mix;
229 pos += 1;
230 }
231 }
232 buf
233}
234
235pub fn lowpass(mut buf: AudioBuffer, cutoff_hz: f32) -> AudioBuffer {
236 let alpha = {
237 let rc = 1.0 / (2.0 * std::f32::consts::PI * cutoff_hz);
238 let dt = 1.0 / buf.sample_rate as f32;
239 dt / (rc + dt)
240 };
241 for ch in &mut buf.samples { let mut p = 0.0f32; for s in ch.iter_mut() { *s = p + alpha * (*s - p); p = *s; } }
242 buf
243}
244
245pub fn highpass(mut buf: AudioBuffer, cutoff_hz: f32) -> AudioBuffer {
246 let alpha = {
247 let rc = 1.0 / (2.0 * std::f32::consts::PI * cutoff_hz);
248 let dt = 1.0 / buf.sample_rate as f32;
249 rc / (rc + dt)
250 };
251 for ch in &mut buf.samples {
252 let (mut pi, mut po) = (0.0f32, 0.0f32);
253 for s in ch.iter_mut() { let i = *s; *s = alpha * (po + i - pi); pi = i; po = *s; }
254 }
255 buf
256}
257
258pub fn granular(buf: &AudioBuffer, grain_ms: f32, density: f32, scatter: f32, seed: u64) -> AudioBuffer {
259 let mut rng = SimpleRng::new(seed);
260 let gl = (grain_ms * 0.001 * buf.sample_rate as f32) as usize;
261 let n = buf.len();
262 let mut out = AudioBuffer { samples: vec![vec![0.0; n]; buf.channels()], sample_rate: buf.sample_rate };
263 let num = (density * n as f32 / gl.max(1) as f32) as usize;
264 for _ in 0..num {
265 let ss = (rng.next_f32() * n.saturating_sub(gl) as f32) as usize;
266 let ds = ((rng.next_f32() * n as f32) as usize + (rng.next_f32() * scatter * n as f32) as usize) % n;
267 for i in 0..gl.min(n - ds) {
268 let env = hann(i, gl);
269 for c in 0..buf.channels() { if ss + i < buf.samples[c].len() { out.samples[c][ds + i] += buf.samples[c][ss + i] * env; } }
270 }
271 }
272 out
273}
274
275pub fn reverse(buf: &AudioBuffer) -> AudioBuffer {
276 let mut out = buf.clone();
277 for ch in &mut out.samples { ch.reverse(); }
278 out
279}
280
281pub fn phase_vocoder(buf: &AudioBuffer, stretch_factor: f32) -> AudioBuffer {
285 let win = 2048usize;
286 let hop_in = win / 4;
287 let hop_out = (hop_in as f32 * stretch_factor) as usize;
288 let hop_out = hop_out.max(1);
289 let new_len = (buf.len() as f32 * stretch_factor) as usize;
290 let mut out_samples = vec![vec![0.0f32; new_len]; buf.channels()];
291 for (ci, ch) in buf.samples.iter().enumerate() {
292 let mut out_pos = 0usize;
293 let mut in_pos = 0usize;
294 while in_pos + win <= ch.len() && out_pos + win <= new_len {
295 for j in 0..win {
296 let env = hann(j, win);
297 out_samples[ci][out_pos + j] += ch[in_pos + j] * env;
298 }
299 in_pos += hop_in;
300 out_pos += hop_out;
301 }
302 }
303 AudioBuffer { samples: out_samples, sample_rate: buf.sample_rate }
304}
305
306pub fn notch_filter(buf: &AudioBuffer, freq_hz: f32, q: f32, _gain_db: f32) -> AudioBuffer {
308 let mut out = buf.clone();
309 let w0 = 2.0 * std::f32::consts::PI * freq_hz / buf.sample_rate as f32;
310 let alpha = w0.sin() / (2.0 * q);
311 let b0 = 1.0; let b1 = -2.0 * w0.cos(); let b2 = 1.0;
312 let a0 = 1.0 + alpha; let a1 = -2.0 * w0.cos(); let a2 = 1.0 - alpha;
313 for ch in &mut out.samples { biquad_stateless(ch, b0/a0, b1/a0, b2/a0, a1/a0, a2/a0); }
314 out
315}
316
317pub fn lfo_modulate(buf: &AudioBuffer, rate_hz: f32, depth: f32) -> AudioBuffer {
319 let mut out = buf.clone();
320 let sr = buf.sample_rate as f32;
321 for ch in &mut out.samples {
322 for (i, s) in ch.iter_mut().enumerate() {
323 let lfo = (2.0 * std::f32::consts::PI * rate_hz * i as f32 / sr).sin();
324 *s *= 1.0 + lfo * depth;
325 }
326 }
327 out
328}
329
330pub fn reverse_preswell(buf: &AudioBuffer, segment_ms: f32) -> AudioBuffer {
332 let seg_len = (segment_ms * 0.001 * buf.sample_rate as f32) as usize;
333 if seg_len == 0 || seg_len >= buf.len() { return buf.clone(); }
334 let mut out = AudioBuffer { samples: Vec::new(), sample_rate: buf.sample_rate };
335 for ch in &buf.samples {
336 let tail_start = ch.len() - seg_len;
337 let mut tail: Vec<f32> = ch[tail_start..].to_vec();
338 tail.reverse();
339 for (i, s) in tail.iter_mut().enumerate() { *s *= hann(i, seg_len); }
341 let mut combined = tail;
342 combined.extend_from_slice(ch);
343 out.samples.push(combined);
344 }
345 out
346}
347
348pub fn bitcrush(buf: &AudioBuffer, bit_depth: u32, target_rate: u32) -> AudioBuffer {
350 let mut out = buf.clone();
351 let levels = 2.0f32.powi(bit_depth.clamp(1, 32) as i32);
352 let hold_every = (buf.sample_rate / target_rate.max(1)).max(1) as usize;
353 for ch in &mut out.samples {
354 let mut held = 0.0f32;
355 for (i, s) in ch.iter_mut().enumerate() {
356 if i % hold_every == 0 { held = (*s * levels).round() / levels; }
357 *s = held;
358 }
359 }
360 out
361}
362
363pub fn bandpass(buf: &AudioBuffer, low_hz: f32, high_hz: f32) -> AudioBuffer {
365 highpass(lowpass(buf.clone(), high_hz), low_hz)
366}
367
368pub fn eq_3band(buf: &mut AudioBuffer, low_db: f32, mid_db: f32, high_db: f32, state: &mut [[BiquadState; 2]; 3]) {
374 if low_db.abs() < 0.5 && mid_db.abs() < 0.5 && high_db.abs() < 0.5 {
375 return;
376 }
377
378 let sr = buf.sample_rate as f32;
379
380 if low_db.abs() >= 0.5 {
382 let c = biquad_low_shelf(200.0, low_db, 0.7, sr);
383 let coeffs = [c.0, c.1, c.2, 1.0, c.3, c.4];
385 for (ch_idx, ch) in buf.samples.iter_mut().enumerate() {
386 biquad(ch, &coeffs, &mut state[0][ch_idx.min(1)]);
387 }
388 }
389
390 if mid_db.abs() >= 0.5 {
392 let c = biquad_peaking(1000.0, mid_db, 0.7, sr);
393 let coeffs = [c.0, c.1, c.2, 1.0, c.3, c.4];
394 for (ch_idx, ch) in buf.samples.iter_mut().enumerate() {
395 biquad(ch, &coeffs, &mut state[1][ch_idx.min(1)]);
396 }
397 }
398
399 if high_db.abs() >= 0.5 {
401 let c = biquad_high_shelf(3000.0, high_db, 0.7, sr);
402 let coeffs = [c.0, c.1, c.2, 1.0, c.3, c.4];
403 for (ch_idx, ch) in buf.samples.iter_mut().enumerate() {
404 biquad(ch, &coeffs, &mut state[2][ch_idx.min(1)]);
405 }
406 }
407}
408
409pub fn eq_3band_offline(buf: &AudioBuffer, low_db: f32, mid_db: f32, high_db: f32) -> AudioBuffer {
411 let mut out = buf.clone();
412 let mut state: [[BiquadState; 2]; 3] = Default::default();
413 eq_3band(&mut out, low_db, mid_db, high_db, &mut state);
414 out
415}
416
417#[derive(Default, Clone)]
421pub struct BiquadState {
422 pub x1: f32, pub x2: f32,
423 pub y1: f32, pub y2: f32,
424}
425
426fn hann(i: usize, len: usize) -> f32 {
427 if len <= 1 { return 1.0; }
428 0.5 * (1.0 - (2.0 * std::f32::consts::PI * i as f32 / (len - 1) as f32).cos())
429}
430
431fn biquad_peaking(freq: f32, db_gain: f32, q: f32, sr: f32) -> (f32, f32, f32, f32, f32) {
433 let a = 10.0_f32.powf(db_gain / 40.0);
434 let w0 = 2.0 * std::f32::consts::PI * freq / sr;
435 let alpha = w0.sin() / (2.0 * q);
436 let a0 = 1.0 + alpha / a;
437 ( (1.0 + alpha * a) / a0,
438 (-2.0 * w0.cos()) / a0,
439 (1.0 - alpha * a) / a0,
440 (-2.0 * w0.cos()) / a0,
441 (1.0 - alpha / a) / a0 )
442}
443
444fn biquad_low_shelf(freq: f32, db_gain: f32, q: f32, sr: f32) -> (f32, f32, f32, f32, f32) {
446 let a = 10.0_f32.powf(db_gain / 40.0);
447 let w0 = 2.0 * std::f32::consts::PI * freq / sr;
448 let alpha = w0.sin() / (2.0 * q);
449 let two_sqrt_a_alpha = 2.0 * a.sqrt() * alpha;
450 let a0 = (a + 1.0) + (a - 1.0) * w0.cos() + two_sqrt_a_alpha;
451 ( (a * ((a + 1.0) - (a - 1.0) * w0.cos() + two_sqrt_a_alpha)) / a0,
452 (2.0 * a * ((a - 1.0) - (a + 1.0) * w0.cos())) / a0,
453 (a * ((a + 1.0) - (a - 1.0) * w0.cos() - two_sqrt_a_alpha)) / a0,
454 (-2.0 * ((a - 1.0) + (a + 1.0) * w0.cos())) / a0,
455 ((a + 1.0) + (a - 1.0) * w0.cos() - two_sqrt_a_alpha) / a0 )
456}
457
458fn biquad_high_shelf(freq: f32, db_gain: f32, q: f32, sr: f32) -> (f32, f32, f32, f32, f32) {
460 let a = 10.0_f32.powf(db_gain / 40.0);
461 let w0 = 2.0 * std::f32::consts::PI * freq / sr;
462 let alpha = w0.sin() / (2.0 * q);
463 let two_sqrt_a_alpha = 2.0 * a.sqrt() * alpha;
464 let a0 = (a + 1.0) - (a - 1.0) * w0.cos() + two_sqrt_a_alpha;
465 ( (a * ((a + 1.0) + (a - 1.0) * w0.cos() + two_sqrt_a_alpha)) / a0,
466 (-2.0 * a * ((a - 1.0) + (a + 1.0) * w0.cos())) / a0,
467 (a * ((a + 1.0) + (a - 1.0) * w0.cos() - two_sqrt_a_alpha)) / a0,
468 (2.0 * ((a - 1.0) - (a + 1.0) * w0.cos())) / a0,
469 ((a + 1.0) - (a - 1.0) * w0.cos() - two_sqrt_a_alpha) / a0 )
470}
471
472pub fn biquad(ch: &mut [f32], coeffs: &[f32; 6], state: &mut BiquadState) {
474 let (b0, b1, b2, a0, a1, a2) = (coeffs[0], coeffs[1], coeffs[2], coeffs[3], coeffs[4], coeffs[5]);
475 for s in ch.iter_mut() {
476 let x0 = *s;
477 let y0 = (b0 * x0 + b1 * state.x1 + b2 * state.x2 - a1 * state.y1 - a2 * state.y2) / a0;
478 state.x2 = state.x1; state.x1 = x0;
479 state.y2 = state.y1; state.y1 = y0;
480 *s = y0;
481 }
482}
483
484pub fn resample(buf: &AudioBuffer, target_rate: u32) -> AudioBuffer {
487 if buf.sample_rate == target_rate {
488 return buf.clone();
489 }
490 let ratio = target_rate as f64 / buf.sample_rate as f64;
491 let new_len = (buf.len() as f64 * ratio) as usize;
492 let mut out_samples = Vec::with_capacity(buf.channels());
493
494 for ch in &buf.samples {
495 let mut resampled = Vec::with_capacity(new_len);
496 for i in 0..new_len {
497 let src_pos = i as f64 / ratio;
498 let idx = src_pos as usize;
499 let frac = (src_pos - idx as f64) as f32;
500 let a = ch.get(idx).copied().unwrap_or(0.0);
501 let b = ch.get(idx + 1).copied().unwrap_or(a);
502 resampled.push(a + (b - a) * frac);
503 }
504 out_samples.push(resampled);
505 }
506
507 AudioBuffer {
508 samples: out_samples,
509 sample_rate: target_rate,
510 }
511}
512
513fn biquad_stateless(ch: &mut [f32], b0: f32, b1: f32, b2: f32, a1: f32, a2: f32) {
515 let (mut x1, mut x2, mut y1, mut y2) = (0.0f32, 0.0f32, 0.0f32, 0.0f32);
516 for s in ch.iter_mut() {
517 let x0 = *s;
518 let y0 = b0 * x0 + b1 * x1 + b2 * x2 - a1 * y1 - a2 * y2;
519 x2 = x1; x1 = x0; y2 = y1; y1 = y0;
520 *s = y0;
521 }
522}
523
524pub fn compute_waveform_bands(buf: &AudioBuffer, resolution: usize) -> Vec<[f32; 3]> {
528 let mono = buf.to_mono();
529 if mono.is_empty() || resolution == 0 { return vec![]; }
530 let sr = buf.sample_rate as f32;
531 let chunk = resolution.max(1);
532 let n_windows = (mono.len() + chunk - 1) / chunk;
533
534 let lp200 = biquad_lp_coeffs(200.0, 0.707, sr);
537 let hp200 = biquad_hp_coeffs(200.0, 0.707, sr);
538 let lp4000 = biquad_lp_coeffs(4000.0, 0.707, sr);
539 let hp4000 = biquad_hp_coeffs(4000.0, 0.707, sr);
540
541 let mut low_sig = mono.clone();
543 let mut mid_sig = mono.clone();
544 let mut high_sig = mono.clone();
545
546 biquad_apply(&mut low_sig, &lp200);
547
548 biquad_apply(&mut mid_sig, &hp200);
549 biquad_apply(&mut mid_sig, &lp4000);
550
551 biquad_apply(&mut high_sig, &hp4000);
552
553 let mut result = Vec::with_capacity(n_windows);
555 let mut max_e = [0.0f32; 3];
556 for w in 0..n_windows {
557 let start = w * chunk;
558 let end = (start + chunk).min(mono.len());
559 let n = (end - start) as f32;
560 if n <= 0.0 { result.push([0.0; 3]); continue; }
561 let lo: f32 = low_sig[start..end].iter().map(|s| s * s).sum::<f32>() / n;
562 let mi: f32 = mid_sig[start..end].iter().map(|s| s * s).sum::<f32>() / n;
563 let hi: f32 = high_sig[start..end].iter().map(|s| s * s).sum::<f32>() / n;
564 let e = [lo.sqrt(), mi.sqrt(), hi.sqrt()];
565 for b in 0..3 { if e[b] > max_e[b] { max_e[b] = e[b]; } }
566 result.push(e);
567 }
568 for r in &mut result {
570 for b in 0..3 {
571 if max_e[b] > 0.0 { r[b] /= max_e[b]; }
572 }
573 }
574 result
575}
576
577fn biquad_lp_coeffs(freq: f32, q: f32, sr: f32) -> [f32; 5] {
579 let w0 = 2.0 * std::f32::consts::PI * freq / sr;
580 let alpha = w0.sin() / (2.0 * q);
581 let cos_w0 = w0.cos();
582 let a0 = 1.0 + alpha;
583 let b0 = ((1.0 - cos_w0) / 2.0) / a0;
584 let b1 = (1.0 - cos_w0) / a0;
585 let b2 = b0;
586 let a1 = (-2.0 * cos_w0) / a0;
587 let a2 = (1.0 - alpha) / a0;
588 [b0, b1, b2, a1, a2]
589}
590
591fn biquad_hp_coeffs(freq: f32, q: f32, sr: f32) -> [f32; 5] {
593 let w0 = 2.0 * std::f32::consts::PI * freq / sr;
594 let alpha = w0.sin() / (2.0 * q);
595 let cos_w0 = w0.cos();
596 let a0 = 1.0 + alpha;
597 let b0 = ((1.0 + cos_w0) / 2.0) / a0;
598 let b1 = (-(1.0 + cos_w0)) / a0;
599 let b2 = b0;
600 let a1 = (-2.0 * cos_w0) / a0;
601 let a2 = (1.0 - alpha) / a0;
602 [b0, b1, b2, a1, a2]
603}
604
605fn biquad_apply(samples: &mut [f32], coeffs: &[f32; 5]) {
607 let (b0, b1, b2, a1, a2) = (coeffs[0], coeffs[1], coeffs[2], coeffs[3], coeffs[4]);
608 let (mut x1, mut x2, mut y1, mut y2) = (0.0f32, 0.0f32, 0.0f32, 0.0f32);
609 for s in samples.iter_mut() {
610 let x0 = *s;
611 let y0 = b0 * x0 + b1 * x1 + b2 * x2 - a1 * y1 - a2 * y2;
612 x2 = x1; x1 = x0;
613 y2 = y1; y1 = y0;
614 *s = y0;
615 }
616}
617
618#[cfg(test)]
619mod tests {
620 use super::*;
621
622 fn sine(freq: f32, dur: f32, sr: u32) -> AudioBuffer {
623 let n = (dur * sr as f32) as usize;
624 let s = (0..n).map(|i| (2.0 * std::f32::consts::PI * freq * i as f32 / sr as f32).sin()).collect();
625 AudioBuffer { samples: vec![s], sample_rate: sr }
626 }
627
628 #[test] fn reverb_len() { let b = sine(440.0, 0.5, 44100); let n = b.len(); assert_eq!(reverb(b, 1.0, 0.5, 0.3).len(), n); }
629 #[test] fn lp_attenuates() { let b = sine(8000.0, 0.1, 44100); let b2 = sine(8000.0, 0.1, 44100); let o = lowpass(b, 200.0);
630 let ei: f32 = b2.samples[0].iter().map(|s| s*s).sum(); let eo: f32 = o.samples[0].iter().map(|s| s*s).sum();
631 assert!(eo < ei * 0.5); }
632 #[test] fn rev_involution() { let b = sine(440.0, 0.1, 44100); assert_eq!(b.samples[0], reverse(&reverse(&b)).samples[0]); }
633 #[test] fn notch_removes_freq() { let b = sine(1000.0, 0.2, 44100); let o = notch_filter(&b, 1000.0, 10.0, 0.0);
634 let ei: f32 = b.samples[0].iter().map(|s| s*s).sum(); let eo: f32 = o.samples[0].iter().map(|s| s*s).sum();
635 assert!(eo < ei * 0.3, "notch should remove target freq"); }
636 #[test] fn bitcrush_quantizes() { let b = sine(440.0, 0.1, 44100); let o = bitcrush(&b, 4, 8000);
637 let unique: std::collections::HashSet<u32> = o.samples[0].iter().map(|s| s.to_bits()).collect();
638 assert!(unique.len() < b.len() / 2, "bitcrush should reduce unique values"); }
639 #[test] fn bandpass_works() { let b = sine(440.0, 0.1, 44100); let o = bandpass(&b, 200.0, 800.0);
640 assert_eq!(o.len(), b.len()); }
641 #[test] fn phase_vocoder_stretches() { let b = sine(440.0, 0.5, 44100); let o = phase_vocoder(&b, 2.0);
642 assert!(o.len() > b.len(), "vocoder should stretch"); }
643 #[test] fn preswell_prepends() { let b = sine(440.0, 0.5, 44100); let o = reverse_preswell(&b, 100.0);
644 assert!(o.len() > b.len(), "preswell should add samples"); }
645}