Skip to main content

oximedia_graph/filters/audio/
eq.rs

1//! Audio equalizer filter.
2//!
3//! This module provides a parametric equalizer with multiple band types
4//! using biquad filter coefficients.
5
6#![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
26/// Maximum number of EQ bands supported.
27pub const MAX_BANDS: usize = 10;
28
29/// Type of EQ band filter.
30#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
31pub enum BandType {
32    /// Low shelf filter - boost/cut below frequency.
33    LowShelf,
34    /// High shelf filter - boost/cut above frequency.
35    HighShelf,
36    /// Peaking filter - boost/cut around center frequency.
37    #[default]
38    Peaking,
39    /// Low pass filter - attenuate above frequency.
40    LowPass,
41    /// High pass filter - attenuate below frequency.
42    HighPass,
43    /// Band pass filter - pass around center frequency.
44    BandPass,
45    /// Notch filter - attenuate around center frequency.
46    Notch,
47    /// All pass filter - phase shift only.
48    AllPass,
49}
50
51/// A single EQ band configuration.
52#[derive(Clone, Debug)]
53pub struct EqBand {
54    /// Type of filter.
55    pub band_type: BandType,
56    /// Center frequency in Hz.
57    pub frequency: f64,
58    /// Gain in dB (for peaking and shelf filters).
59    pub gain_db: f64,
60    /// Q factor (bandwidth control).
61    pub q: f64,
62    /// Whether this band is enabled.
63    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    /// Create a new EQ band.
80    #[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    /// Create a low shelf band.
92    #[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    /// Create a high shelf band.
98    #[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    /// Create a peaking band.
104    #[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    /// Create a low pass band.
110    #[must_use]
111    pub fn low_pass(frequency: f64, q: f64) -> Self {
112        Self::new(BandType::LowPass, frequency, 0.0, q)
113    }
114
115    /// Create a high pass band.
116    #[must_use]
117    pub fn high_pass(frequency: f64, q: f64) -> Self {
118        Self::new(BandType::HighPass, frequency, 0.0, q)
119    }
120
121    /// Create a notch filter band.
122    #[must_use]
123    pub fn notch(frequency: f64, q: f64) -> Self {
124        Self::new(BandType::Notch, frequency, 0.0, q)
125    }
126
127    /// Set the enabled state.
128    #[must_use]
129    pub fn enabled(mut self, enabled: bool) -> Self {
130        self.enabled = enabled;
131        self
132    }
133}
134
135/// Biquad filter coefficients.
136#[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    /// Calculate coefficients for the given band type.
147    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/// Biquad filter state for one channel.
259#[derive(Clone, Debug, Default)]
260struct BiquadState {
261    x1: f64,
262    x2: f64,
263    y1: f64,
264    y2: f64,
265}
266
267impl BiquadState {
268    /// Process one sample through the biquad filter.
269    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    /// Reset filter state.
283    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/// Configuration for the equalizer filter.
292#[derive(Clone, Debug, Default)]
293pub struct EqualizerConfig {
294    /// EQ bands.
295    pub bands: Vec<EqBand>,
296}
297
298impl EqualizerConfig {
299    /// Create a new equalizer configuration.
300    #[must_use]
301    pub fn new() -> Self {
302        Self { bands: Vec::new() }
303    }
304
305    /// Add a band.
306    #[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    /// Create a 3-band EQ preset.
315    #[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    /// Create a 10-band graphic EQ preset.
324    #[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; // ~1 octave bandwidth
339        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
353/// Equalizer filter state.
354struct EqualizerState {
355    /// Biquad coefficients per band.
356    coefficients: Vec<BiquadCoefficients>,
357    /// Biquad state per band per channel.
358    states: Vec<Vec<BiquadState>>,
359    /// Sample rate used for coefficient calculation.
360    sample_rate: f64,
361}
362
363impl EqualizerState {
364    /// Create new equalizer state.
365    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    /// Update coefficients for changed bands.
382    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        // Adjust state arrays
390        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    /// Process samples through all bands.
400    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    /// Reset all filter states.
426    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
435/// Audio parametric equalizer filter.
436///
437/// This filter provides a multi-band parametric equalizer with various
438/// filter types including peaking, shelf, and pass filters.
439///
440/// # Example
441///
442/// ```ignore
443/// use oximedia_graph::filters::audio::eq::{EqualizerFilter, EqualizerConfig, EqBand, BandType};
444///
445/// // Create a 3-band EQ with bass boost and treble cut
446/// let config = EqualizerConfig::three_band(6.0, 0.0, -3.0);
447/// let filter = EqualizerFilter::new(NodeId(0), "eq", config);
448/// ```
449pub 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    /// Create a new equalizer filter.
461    #[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    /// Get the current configuration.
480    #[must_use]
481    pub fn config(&self) -> &EqualizerConfig {
482        &self.config
483    }
484
485    /// Update the configuration.
486    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    /// Add a band.
494    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    /// Remove a band by index.
504    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    /// Set band parameters.
514    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    /// Convert audio frame to f64 samples per channel.
524    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    /// Convert interleaved samples.
547    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    /// Convert planar samples.
573    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    /// Convert bytes to f64 sample.
596    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    /// Convert f64 samples to audio frame.
637    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    /// Convert f64 sample to bytes.
669    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        // Initialize EQ state if needed
748        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        // Convert to f64 samples
758        let mut samples = Self::frame_to_samples(&frame);
759
760        // Apply EQ processing
761        if let Some(ref mut eq_state) = self.eq_state {
762            eq_state.process(&mut samples, &self.config.bands);
763        }
764
765        // Convert back to frame
766        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        // Coefficients should be finite
861        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); // Unity gain
871        let coeffs = BiquadCoefficients::calculate(&band, 48000.0);
872        let mut state = BiquadState::default();
873
874        // Process some samples
875        for _ in 0..100 {
876            let output = state.process(0.5, &coeffs);
877            assert!(output.is_finite());
878        }
879
880        // Reset should clear state
881        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); // Flat EQ
941        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}