1#![forbid(unsafe_code)]
7#![allow(clippy::cast_sign_loss)]
8#![allow(clippy::cast_possible_wrap)]
9#![allow(clippy::cast_lossless)]
10#![allow(clippy::needless_pass_by_value)]
11#![allow(clippy::needless_range_loop)]
12#![allow(clippy::get_first)]
13#![allow(clippy::doc_markdown)]
14
15use bytes::{Bytes, BytesMut};
16use std::f64::consts::PI;
17
18use crate::error::{GraphError, GraphResult};
19use crate::frame::FilterFrame;
20use crate::node::{Node, NodeId, NodeState, NodeType};
21use crate::port::{AudioPortFormat, InputPort, OutputPort, PortFormat, PortId, PortType};
22
23use oximedia_audio::{AudioBuffer, AudioFrame, ChannelLayout};
24use oximedia_core::SampleFormat;
25
26pub const MAX_BANDS: usize = 10;
28
29#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
31pub enum BandType {
32 LowShelf,
34 HighShelf,
36 #[default]
38 Peaking,
39 LowPass,
41 HighPass,
43 BandPass,
45 Notch,
47 AllPass,
49}
50
51#[derive(Clone, Debug)]
53pub struct EqBand {
54 pub band_type: BandType,
56 pub frequency: f64,
58 pub gain_db: f64,
60 pub q: f64,
62 pub enabled: bool,
64}
65
66impl Default for EqBand {
67 fn default() -> Self {
68 Self {
69 band_type: BandType::Peaking,
70 frequency: 1000.0,
71 gain_db: 0.0,
72 q: 1.0,
73 enabled: true,
74 }
75 }
76}
77
78impl EqBand {
79 #[must_use]
81 pub fn new(band_type: BandType, frequency: f64, gain_db: f64, q: f64) -> Self {
82 Self {
83 band_type,
84 frequency,
85 gain_db,
86 q,
87 enabled: true,
88 }
89 }
90
91 #[must_use]
93 pub fn low_shelf(frequency: f64, gain_db: f64) -> Self {
94 Self::new(BandType::LowShelf, frequency, gain_db, 0.707)
95 }
96
97 #[must_use]
99 pub fn high_shelf(frequency: f64, gain_db: f64) -> Self {
100 Self::new(BandType::HighShelf, frequency, gain_db, 0.707)
101 }
102
103 #[must_use]
105 pub fn peaking(frequency: f64, gain_db: f64, q: f64) -> Self {
106 Self::new(BandType::Peaking, frequency, gain_db, q)
107 }
108
109 #[must_use]
111 pub fn low_pass(frequency: f64, q: f64) -> Self {
112 Self::new(BandType::LowPass, frequency, 0.0, q)
113 }
114
115 #[must_use]
117 pub fn high_pass(frequency: f64, q: f64) -> Self {
118 Self::new(BandType::HighPass, frequency, 0.0, q)
119 }
120
121 #[must_use]
123 pub fn notch(frequency: f64, q: f64) -> Self {
124 Self::new(BandType::Notch, frequency, 0.0, q)
125 }
126
127 #[must_use]
129 pub fn enabled(mut self, enabled: bool) -> Self {
130 self.enabled = enabled;
131 self
132 }
133}
134
135#[derive(Clone, Debug, Default)]
137struct BiquadCoefficients {
138 b0: f64,
139 b1: f64,
140 b2: f64,
141 a1: f64,
142 a2: f64,
143}
144
145impl BiquadCoefficients {
146 fn calculate(band: &EqBand, sample_rate: f64) -> Self {
148 let w0 = 2.0 * PI * band.frequency / sample_rate;
149 let cos_w0 = w0.cos();
150 let sin_w0 = w0.sin();
151 let alpha = sin_w0 / (2.0 * band.q);
152 let a = 10.0_f64.powf(band.gain_db / 40.0);
153
154 let (b0, b1, b2, a0, a1, a2) = match band.band_type {
155 BandType::LowShelf => {
156 let two_sqrt_a_alpha = 2.0 * a.sqrt() * alpha;
157 let a_plus_1 = a + 1.0;
158 let a_minus_1 = a - 1.0;
159
160 let b0 = a * (a_plus_1 - a_minus_1 * cos_w0 + two_sqrt_a_alpha);
161 let b1 = 2.0 * a * (a_minus_1 - a_plus_1 * cos_w0);
162 let b2 = a * (a_plus_1 - a_minus_1 * cos_w0 - two_sqrt_a_alpha);
163 let a0 = a_plus_1 + a_minus_1 * cos_w0 + two_sqrt_a_alpha;
164 let a1 = -2.0 * (a_minus_1 + a_plus_1 * cos_w0);
165 let a2 = a_plus_1 + a_minus_1 * cos_w0 - two_sqrt_a_alpha;
166
167 (b0, b1, b2, a0, a1, a2)
168 }
169 BandType::HighShelf => {
170 let two_sqrt_a_alpha = 2.0 * a.sqrt() * alpha;
171 let a_plus_1 = a + 1.0;
172 let a_minus_1 = a - 1.0;
173
174 let b0 = a * (a_plus_1 + a_minus_1 * cos_w0 + two_sqrt_a_alpha);
175 let b1 = -2.0 * a * (a_minus_1 + a_plus_1 * cos_w0);
176 let b2 = a * (a_plus_1 + a_minus_1 * cos_w0 - two_sqrt_a_alpha);
177 let a0 = a_plus_1 - a_minus_1 * cos_w0 + two_sqrt_a_alpha;
178 let a1 = 2.0 * (a_minus_1 - a_plus_1 * cos_w0);
179 let a2 = a_plus_1 - a_minus_1 * cos_w0 - two_sqrt_a_alpha;
180
181 (b0, b1, b2, a0, a1, a2)
182 }
183 BandType::Peaking => {
184 let alpha_a = alpha * a;
185 let alpha_over_a = alpha / a;
186
187 let b0 = 1.0 + alpha_a;
188 let b1 = -2.0 * cos_w0;
189 let b2 = 1.0 - alpha_a;
190 let a0 = 1.0 + alpha_over_a;
191 let a1 = -2.0 * cos_w0;
192 let a2 = 1.0 - alpha_over_a;
193
194 (b0, b1, b2, a0, a1, a2)
195 }
196 BandType::LowPass => {
197 let b0 = (1.0 - cos_w0) / 2.0;
198 let b1 = 1.0 - cos_w0;
199 let b2 = (1.0 - cos_w0) / 2.0;
200 let a0 = 1.0 + alpha;
201 let a1 = -2.0 * cos_w0;
202 let a2 = 1.0 - alpha;
203
204 (b0, b1, b2, a0, a1, a2)
205 }
206 BandType::HighPass => {
207 let b0 = (1.0 + cos_w0) / 2.0;
208 let b1 = -(1.0 + cos_w0);
209 let b2 = (1.0 + cos_w0) / 2.0;
210 let a0 = 1.0 + alpha;
211 let a1 = -2.0 * cos_w0;
212 let a2 = 1.0 - alpha;
213
214 (b0, b1, b2, a0, a1, a2)
215 }
216 BandType::BandPass => {
217 let b0 = alpha;
218 let b1 = 0.0;
219 let b2 = -alpha;
220 let a0 = 1.0 + alpha;
221 let a1 = -2.0 * cos_w0;
222 let a2 = 1.0 - alpha;
223
224 (b0, b1, b2, a0, a1, a2)
225 }
226 BandType::Notch => {
227 let b0 = 1.0;
228 let b1 = -2.0 * cos_w0;
229 let b2 = 1.0;
230 let a0 = 1.0 + alpha;
231 let a1 = -2.0 * cos_w0;
232 let a2 = 1.0 - alpha;
233
234 (b0, b1, b2, a0, a1, a2)
235 }
236 BandType::AllPass => {
237 let b0 = 1.0 - alpha;
238 let b1 = -2.0 * cos_w0;
239 let b2 = 1.0 + alpha;
240 let a0 = 1.0 + alpha;
241 let a1 = -2.0 * cos_w0;
242 let a2 = 1.0 - alpha;
243
244 (b0, b1, b2, a0, a1, a2)
245 }
246 };
247
248 Self {
249 b0: b0 / a0,
250 b1: b1 / a0,
251 b2: b2 / a0,
252 a1: a1 / a0,
253 a2: a2 / a0,
254 }
255 }
256}
257
258#[derive(Clone, Debug, Default)]
260struct BiquadState {
261 x1: f64,
262 x2: f64,
263 y1: f64,
264 y2: f64,
265}
266
267impl BiquadState {
268 fn process(&mut self, x: f64, coeffs: &BiquadCoefficients) -> f64 {
270 let y = coeffs.b0 * x + coeffs.b1 * self.x1 + coeffs.b2 * self.x2
271 - coeffs.a1 * self.y1
272 - coeffs.a2 * self.y2;
273
274 self.x2 = self.x1;
275 self.x1 = x;
276 self.y2 = self.y1;
277 self.y1 = y;
278
279 y
280 }
281
282 fn reset(&mut self) {
284 self.x1 = 0.0;
285 self.x2 = 0.0;
286 self.y1 = 0.0;
287 self.y2 = 0.0;
288 }
289}
290
291#[derive(Clone, Debug, Default)]
293pub struct EqualizerConfig {
294 pub bands: Vec<EqBand>,
296}
297
298impl EqualizerConfig {
299 #[must_use]
301 pub fn new() -> Self {
302 Self { bands: Vec::new() }
303 }
304
305 #[must_use]
307 pub fn with_band(mut self, band: EqBand) -> Self {
308 if self.bands.len() < MAX_BANDS {
309 self.bands.push(band);
310 }
311 self
312 }
313
314 #[must_use]
316 pub fn three_band(low_gain: f64, mid_gain: f64, high_gain: f64) -> Self {
317 Self::new()
318 .with_band(EqBand::low_shelf(250.0, low_gain))
319 .with_band(EqBand::peaking(1000.0, mid_gain, 1.0))
320 .with_band(EqBand::high_shelf(4000.0, high_gain))
321 }
322
323 #[must_use]
325 #[allow(clippy::too_many_arguments)]
326 pub fn graphic_10_band(
327 g31: f64,
328 g62: f64,
329 g125: f64,
330 g250: f64,
331 g500: f64,
332 g1k: f64,
333 g2k: f64,
334 g4k: f64,
335 g8k: f64,
336 g16k: f64,
337 ) -> Self {
338 let q = 1.414; Self::new()
340 .with_band(EqBand::peaking(31.0, g31, q))
341 .with_band(EqBand::peaking(62.0, g62, q))
342 .with_band(EqBand::peaking(125.0, g125, q))
343 .with_band(EqBand::peaking(250.0, g250, q))
344 .with_band(EqBand::peaking(500.0, g500, q))
345 .with_band(EqBand::peaking(1000.0, g1k, q))
346 .with_band(EqBand::peaking(2000.0, g2k, q))
347 .with_band(EqBand::peaking(4000.0, g4k, q))
348 .with_band(EqBand::peaking(8000.0, g8k, q))
349 .with_band(EqBand::peaking(16000.0, g16k, q))
350 }
351}
352
353struct EqualizerState {
355 coefficients: Vec<BiquadCoefficients>,
357 states: Vec<Vec<BiquadState>>,
359 sample_rate: f64,
361}
362
363impl EqualizerState {
364 fn new(config: &EqualizerConfig, sample_rate: f64, channels: usize) -> Self {
366 let coefficients: Vec<_> = config
367 .bands
368 .iter()
369 .map(|band| BiquadCoefficients::calculate(band, sample_rate))
370 .collect();
371
372 let states = vec![vec![BiquadState::default(); config.bands.len()]; channels];
373
374 Self {
375 coefficients,
376 states,
377 sample_rate,
378 }
379 }
380
381 fn update_coefficients(&mut self, config: &EqualizerConfig) {
383 self.coefficients.clear();
384 for band in &config.bands {
385 self.coefficients
386 .push(BiquadCoefficients::calculate(band, self.sample_rate));
387 }
388
389 let band_count = config.bands.len();
391 for channel_states in &mut self.states {
392 while channel_states.len() < band_count {
393 channel_states.push(BiquadState::default());
394 }
395 channel_states.truncate(band_count);
396 }
397 }
398
399 fn process(&mut self, samples: &mut [Vec<f64>], bands: &[EqBand]) {
401 for (ch, channel) in samples.iter_mut().enumerate() {
402 if ch >= self.states.len() {
403 break;
404 }
405
406 for sample in channel.iter_mut() {
407 let mut value = *sample;
408
409 for (band_idx, band) in bands.iter().enumerate() {
410 if !band.enabled {
411 continue;
412 }
413
414 if band_idx < self.coefficients.len() && band_idx < self.states[ch].len() {
415 value =
416 self.states[ch][band_idx].process(value, &self.coefficients[band_idx]);
417 }
418 }
419
420 *sample = value;
421 }
422 }
423 }
424
425 fn reset(&mut self) {
427 for channel_states in &mut self.states {
428 for state in channel_states {
429 state.reset();
430 }
431 }
432 }
433}
434
435pub struct EqualizerFilter {
450 id: NodeId,
451 name: String,
452 state: NodeState,
453 config: EqualizerConfig,
454 eq_state: Option<EqualizerState>,
455 inputs: Vec<InputPort>,
456 outputs: Vec<OutputPort>,
457}
458
459impl EqualizerFilter {
460 #[must_use]
462 pub fn new(id: NodeId, name: impl Into<String>, config: EqualizerConfig) -> Self {
463 let audio_format = PortFormat::Audio(AudioPortFormat::any());
464
465 Self {
466 id,
467 name: name.into(),
468 state: NodeState::Idle,
469 config,
470 eq_state: None,
471 inputs: vec![InputPort::new(PortId(0), "input", PortType::Audio)
472 .with_format(audio_format.clone())],
473 outputs: vec![
474 OutputPort::new(PortId(0), "output", PortType::Audio).with_format(audio_format)
475 ],
476 }
477 }
478
479 #[must_use]
481 pub fn config(&self) -> &EqualizerConfig {
482 &self.config
483 }
484
485 pub fn set_config(&mut self, config: EqualizerConfig) {
487 self.config = config;
488 if let Some(ref mut eq_state) = self.eq_state {
489 eq_state.update_coefficients(&self.config);
490 }
491 }
492
493 pub fn add_band(&mut self, band: EqBand) {
495 if self.config.bands.len() < MAX_BANDS {
496 self.config.bands.push(band);
497 if let Some(ref mut eq_state) = self.eq_state {
498 eq_state.update_coefficients(&self.config);
499 }
500 }
501 }
502
503 pub fn remove_band(&mut self, index: usize) {
505 if index < self.config.bands.len() {
506 self.config.bands.remove(index);
507 if let Some(ref mut eq_state) = self.eq_state {
508 eq_state.update_coefficients(&self.config);
509 }
510 }
511 }
512
513 pub fn set_band(&mut self, index: usize, band: EqBand) {
515 if index < self.config.bands.len() {
516 self.config.bands[index] = band;
517 if let Some(ref mut eq_state) = self.eq_state {
518 eq_state.update_coefficients(&self.config);
519 }
520 }
521 }
522
523 fn frame_to_samples(frame: &AudioFrame) -> Vec<Vec<f64>> {
525 let channels = frame.channels.count();
526 let sample_count = frame.sample_count();
527
528 if sample_count == 0 {
529 return vec![Vec::new(); channels];
530 }
531
532 let mut output = vec![Vec::with_capacity(sample_count); channels];
533
534 match &frame.samples {
535 AudioBuffer::Interleaved(data) => {
536 Self::convert_interleaved(data, frame.format, channels, &mut output);
537 }
538 AudioBuffer::Planar(planes) => {
539 Self::convert_planar(planes, frame.format, &mut output);
540 }
541 }
542
543 output
544 }
545
546 fn convert_interleaved(
548 data: &Bytes,
549 format: SampleFormat,
550 channels: usize,
551 output: &mut [Vec<f64>],
552 ) {
553 let bytes_per_sample = format.bytes_per_sample();
554 if bytes_per_sample == 0 || channels == 0 {
555 return;
556 }
557
558 let sample_count = data.len() / (bytes_per_sample * channels);
559
560 for i in 0..sample_count {
561 for ch in 0..channels {
562 let offset = (i * channels + ch) * bytes_per_sample;
563 if offset + bytes_per_sample <= data.len() {
564 let sample =
565 Self::bytes_to_f64(&data[offset..offset + bytes_per_sample], format);
566 output[ch].push(sample);
567 }
568 }
569 }
570 }
571
572 fn convert_planar(planes: &[Bytes], format: SampleFormat, output: &mut [Vec<f64>]) {
574 let bytes_per_sample = format.bytes_per_sample();
575 if bytes_per_sample == 0 {
576 return;
577 }
578
579 for (ch, plane) in planes.iter().enumerate() {
580 if ch >= output.len() {
581 break;
582 }
583 let sample_count = plane.len() / bytes_per_sample;
584 for i in 0..sample_count {
585 let offset = i * bytes_per_sample;
586 if offset + bytes_per_sample <= plane.len() {
587 let sample =
588 Self::bytes_to_f64(&plane[offset..offset + bytes_per_sample], format);
589 output[ch].push(sample);
590 }
591 }
592 }
593 }
594
595 fn bytes_to_f64(bytes: &[u8], format: SampleFormat) -> f64 {
597 match format {
598 SampleFormat::U8 => {
599 if bytes.is_empty() {
600 return 0.0;
601 }
602 (f64::from(bytes[0]) - 128.0) / 128.0
603 }
604 SampleFormat::S16 => {
605 if bytes.len() < 2 {
606 return 0.0;
607 }
608 let sample = i16::from_le_bytes([bytes[0], bytes[1]]);
609 f64::from(sample) / f64::from(i16::MAX)
610 }
611 SampleFormat::S32 => {
612 if bytes.len() < 4 {
613 return 0.0;
614 }
615 let sample = i32::from_le_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]);
616 f64::from(sample) / f64::from(i32::MAX)
617 }
618 SampleFormat::F32 => {
619 if bytes.len() < 4 {
620 return 0.0;
621 }
622 f64::from(f32::from_le_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]))
623 }
624 SampleFormat::F64 => {
625 if bytes.len() < 8 {
626 return 0.0;
627 }
628 f64::from_le_bytes([
629 bytes[0], bytes[1], bytes[2], bytes[3], bytes[4], bytes[5], bytes[6], bytes[7],
630 ])
631 }
632 _ => 0.0,
633 }
634 }
635
636 fn samples_to_frame(
638 samples: Vec<Vec<f64>>,
639 format: SampleFormat,
640 sample_rate: u32,
641 channels: ChannelLayout,
642 ) -> AudioFrame {
643 let channel_count = channels.count();
644 if samples.is_empty() || samples[0].is_empty() || channel_count == 0 {
645 return AudioFrame::new(format, sample_rate, channels);
646 }
647
648 let sample_count = samples[0].len();
649 let bytes_per_sample = format.bytes_per_sample();
650 let mut buffer = BytesMut::with_capacity(sample_count * channel_count * bytes_per_sample);
651
652 for i in 0..sample_count {
653 for ch in 0..channel_count {
654 let sample = if ch < samples.len() && i < samples[ch].len() {
655 samples[ch][i]
656 } else {
657 0.0
658 };
659 Self::f64_to_bytes(sample, format, &mut buffer);
660 }
661 }
662
663 let mut frame = AudioFrame::new(format, sample_rate, channels);
664 frame.samples = AudioBuffer::Interleaved(buffer.freeze());
665 frame
666 }
667
668 fn f64_to_bytes(sample: f64, format: SampleFormat, buffer: &mut BytesMut) {
670 let clamped = sample.clamp(-1.0, 1.0);
671
672 match format {
673 SampleFormat::U8 => {
674 let value = ((clamped * 128.0) + 128.0) as u8;
675 buffer.extend_from_slice(&[value]);
676 }
677 SampleFormat::S16 => {
678 let value = (clamped * f64::from(i16::MAX)) as i16;
679 buffer.extend_from_slice(&value.to_le_bytes());
680 }
681 SampleFormat::S32 => {
682 let value = (clamped * f64::from(i32::MAX)) as i32;
683 buffer.extend_from_slice(&value.to_le_bytes());
684 }
685 SampleFormat::F32 => {
686 #[allow(clippy::cast_possible_truncation)]
687 let value = clamped as f32;
688 buffer.extend_from_slice(&value.to_le_bytes());
689 }
690 SampleFormat::F64 => {
691 buffer.extend_from_slice(&clamped.to_le_bytes());
692 }
693 _ => {}
694 }
695 }
696}
697
698impl Node for EqualizerFilter {
699 fn id(&self) -> NodeId {
700 self.id
701 }
702
703 fn name(&self) -> &str {
704 &self.name
705 }
706
707 fn node_type(&self) -> NodeType {
708 NodeType::Filter
709 }
710
711 fn state(&self) -> NodeState {
712 self.state
713 }
714
715 fn set_state(&mut self, state: NodeState) -> GraphResult<()> {
716 if !self.state.can_transition_to(state) {
717 return Err(GraphError::InvalidStateTransition {
718 node: self.id,
719 from: self.state.to_string(),
720 to: state.to_string(),
721 });
722 }
723 self.state = state;
724 Ok(())
725 }
726
727 fn inputs(&self) -> &[InputPort] {
728 &self.inputs
729 }
730
731 fn outputs(&self) -> &[OutputPort] {
732 &self.outputs
733 }
734
735 fn process(&mut self, input: Option<FilterFrame>) -> GraphResult<Option<FilterFrame>> {
736 let frame = match input {
737 Some(FilterFrame::Audio(frame)) => frame,
738 Some(_) => {
739 return Err(GraphError::PortTypeMismatch {
740 expected: "Audio".to_string(),
741 actual: "Video".to_string(),
742 });
743 }
744 None => return Ok(None),
745 };
746
747 if self.eq_state.is_none() {
749 let channels = frame.channels.count();
750 self.eq_state = Some(EqualizerState::new(
751 &self.config,
752 f64::from(frame.sample_rate),
753 channels,
754 ));
755 }
756
757 let mut samples = Self::frame_to_samples(&frame);
759
760 if let Some(ref mut eq_state) = self.eq_state {
762 eq_state.process(&mut samples, &self.config.bands);
763 }
764
765 let output_frame = Self::samples_to_frame(
767 samples,
768 frame.format,
769 frame.sample_rate,
770 frame.channels.clone(),
771 );
772
773 Ok(Some(FilterFrame::Audio(output_frame)))
774 }
775
776 fn reset(&mut self) -> GraphResult<()> {
777 if let Some(ref mut eq_state) = self.eq_state {
778 eq_state.reset();
779 }
780 self.set_state(NodeState::Idle)
781 }
782}
783
784#[cfg(test)]
785mod tests {
786 use super::*;
787
788 #[test]
789 fn test_band_type_default() {
790 assert_eq!(BandType::default(), BandType::Peaking);
791 }
792
793 #[test]
794 fn test_eq_band_creation() {
795 let band = EqBand::new(BandType::Peaking, 1000.0, 6.0, 1.0);
796 assert_eq!(band.band_type, BandType::Peaking);
797 assert!((band.frequency - 1000.0).abs() < f64::EPSILON);
798 assert!((band.gain_db - 6.0).abs() < f64::EPSILON);
799 assert!(band.enabled);
800 }
801
802 #[test]
803 fn test_eq_band_presets() {
804 let low_shelf = EqBand::low_shelf(250.0, 3.0);
805 assert_eq!(low_shelf.band_type, BandType::LowShelf);
806
807 let high_shelf = EqBand::high_shelf(4000.0, -3.0);
808 assert_eq!(high_shelf.band_type, BandType::HighShelf);
809
810 let peaking = EqBand::peaking(1000.0, 6.0, 2.0);
811 assert_eq!(peaking.band_type, BandType::Peaking);
812
813 let low_pass = EqBand::low_pass(8000.0, 0.707);
814 assert_eq!(low_pass.band_type, BandType::LowPass);
815
816 let high_pass = EqBand::high_pass(80.0, 0.707);
817 assert_eq!(high_pass.band_type, BandType::HighPass);
818
819 let notch = EqBand::notch(60.0, 10.0);
820 assert_eq!(notch.band_type, BandType::Notch);
821 }
822
823 #[test]
824 fn test_eq_band_enabled() {
825 let band = EqBand::peaking(1000.0, 6.0, 1.0).enabled(false);
826 assert!(!band.enabled);
827 }
828
829 #[test]
830 fn test_equalizer_config() {
831 let config = EqualizerConfig::new()
832 .with_band(EqBand::low_shelf(250.0, 3.0))
833 .with_band(EqBand::peaking(1000.0, 0.0, 1.0))
834 .with_band(EqBand::high_shelf(4000.0, -3.0));
835
836 assert_eq!(config.bands.len(), 3);
837 }
838
839 #[test]
840 fn test_three_band_preset() {
841 let config = EqualizerConfig::three_band(3.0, 0.0, -3.0);
842 assert_eq!(config.bands.len(), 3);
843 assert_eq!(config.bands[0].band_type, BandType::LowShelf);
844 assert_eq!(config.bands[1].band_type, BandType::Peaking);
845 assert_eq!(config.bands[2].band_type, BandType::HighShelf);
846 }
847
848 #[test]
849 fn test_graphic_10_band() {
850 let config =
851 EqualizerConfig::graphic_10_band(0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0);
852 assert_eq!(config.bands.len(), 10);
853 }
854
855 #[test]
856 fn test_biquad_coefficients() {
857 let band = EqBand::peaking(1000.0, 6.0, 1.0);
858 let coeffs = BiquadCoefficients::calculate(&band, 48000.0);
859
860 assert!(coeffs.b0.is_finite());
862 assert!(coeffs.b1.is_finite());
863 assert!(coeffs.b2.is_finite());
864 assert!(coeffs.a1.is_finite());
865 assert!(coeffs.a2.is_finite());
866 }
867
868 #[test]
869 fn test_biquad_state() {
870 let band = EqBand::peaking(1000.0, 0.0, 1.0); let coeffs = BiquadCoefficients::calculate(&band, 48000.0);
872 let mut state = BiquadState::default();
873
874 for _ in 0..100 {
876 let output = state.process(0.5, &coeffs);
877 assert!(output.is_finite());
878 }
879
880 state.reset();
882 assert!(state.x1.abs() < f64::EPSILON);
883 assert!(state.y1.abs() < f64::EPSILON);
884 }
885
886 #[test]
887 fn test_equalizer_filter_creation() {
888 let config = EqualizerConfig::three_band(0.0, 0.0, 0.0);
889 let filter = EqualizerFilter::new(NodeId(1), "eq", config);
890
891 assert_eq!(filter.id(), NodeId(1));
892 assert_eq!(filter.name(), "eq");
893 assert_eq!(filter.node_type(), NodeType::Filter);
894 }
895
896 #[test]
897 fn test_equalizer_filter_ports() {
898 let config = EqualizerConfig::default();
899 let filter = EqualizerFilter::new(NodeId(0), "test", config);
900
901 assert_eq!(filter.inputs().len(), 1);
902 assert_eq!(filter.outputs().len(), 1);
903 assert_eq!(filter.inputs()[0].port_type, PortType::Audio);
904 }
905
906 #[test]
907 fn test_add_remove_band() {
908 let config = EqualizerConfig::new();
909 let mut filter = EqualizerFilter::new(NodeId(0), "test", config);
910
911 filter.add_band(EqBand::peaking(1000.0, 6.0, 1.0));
912 assert_eq!(filter.config().bands.len(), 1);
913
914 filter.remove_band(0);
915 assert!(filter.config().bands.is_empty());
916 }
917
918 #[test]
919 fn test_set_band() {
920 let config = EqualizerConfig::new().with_band(EqBand::peaking(1000.0, 0.0, 1.0));
921 let mut filter = EqualizerFilter::new(NodeId(0), "test", config);
922
923 filter.set_band(0, EqBand::peaking(2000.0, 6.0, 2.0));
924
925 assert!((filter.config().bands[0].frequency - 2000.0).abs() < f64::EPSILON);
926 assert!((filter.config().bands[0].gain_db - 6.0).abs() < f64::EPSILON);
927 }
928
929 #[test]
930 fn test_process_none() {
931 let config = EqualizerConfig::default();
932 let mut filter = EqualizerFilter::new(NodeId(0), "test", config);
933
934 let result = filter.process(None).expect("process should succeed");
935 assert!(result.is_none());
936 }
937
938 #[test]
939 fn test_process_audio() {
940 let config = EqualizerConfig::three_band(0.0, 0.0, 0.0); let mut filter = EqualizerFilter::new(NodeId(0), "test", config);
942
943 let mut frame = AudioFrame::new(SampleFormat::F32, 48000, ChannelLayout::Mono);
944 let mut samples = BytesMut::new();
945 for _ in 0..100 {
946 samples.extend_from_slice(&0.5f32.to_le_bytes());
947 }
948 frame.samples = AudioBuffer::Interleaved(samples.freeze());
949
950 let result = filter
951 .process(Some(FilterFrame::Audio(frame)))
952 .expect("process should succeed");
953 assert!(result.is_some());
954 }
955
956 #[test]
957 fn test_state_transitions() {
958 let config = EqualizerConfig::default();
959 let mut filter = EqualizerFilter::new(NodeId(0), "test", config);
960
961 assert!(filter.set_state(NodeState::Processing).is_ok());
962 assert_eq!(filter.state(), NodeState::Processing);
963
964 assert!(filter.reset().is_ok());
965 assert_eq!(filter.state(), NodeState::Idle);
966 }
967
968 #[test]
969 fn test_all_band_types_coefficients() {
970 let sample_rate = 48000.0;
971 let test_bands = vec![
972 EqBand::new(BandType::LowShelf, 250.0, 6.0, 0.707),
973 EqBand::new(BandType::HighShelf, 4000.0, 6.0, 0.707),
974 EqBand::new(BandType::Peaking, 1000.0, 6.0, 1.0),
975 EqBand::new(BandType::LowPass, 8000.0, 0.0, 0.707),
976 EqBand::new(BandType::HighPass, 80.0, 0.0, 0.707),
977 EqBand::new(BandType::BandPass, 1000.0, 0.0, 1.0),
978 EqBand::new(BandType::Notch, 60.0, 0.0, 10.0),
979 EqBand::new(BandType::AllPass, 1000.0, 0.0, 0.707),
980 ];
981
982 for band in &test_bands {
983 let coeffs = BiquadCoefficients::calculate(band, sample_rate);
984 assert!(
985 coeffs.b0.is_finite(),
986 "b0 not finite for {:?}",
987 band.band_type
988 );
989 assert!(
990 coeffs.b1.is_finite(),
991 "b1 not finite for {:?}",
992 band.band_type
993 );
994 assert!(
995 coeffs.b2.is_finite(),
996 "b2 not finite for {:?}",
997 band.band_type
998 );
999 assert!(
1000 coeffs.a1.is_finite(),
1001 "a1 not finite for {:?}",
1002 band.band_type
1003 );
1004 assert!(
1005 coeffs.a2.is_finite(),
1006 "a2 not finite for {:?}",
1007 band.band_type
1008 );
1009 }
1010 }
1011
1012 #[test]
1013 fn test_max_bands_limit() {
1014 let mut config = EqualizerConfig::new();
1015 for i in 0..MAX_BANDS + 5 {
1016 config = config.with_band(EqBand::peaking(100.0 * (i + 1) as f64, 0.0, 1.0));
1017 }
1018
1019 assert_eq!(config.bands.len(), MAX_BANDS);
1020 }
1021}