Skip to main content

singe_kernel/
audio.rs

1//! Audio frontend configuration used by audio feature extraction kernels.
2
3use crate::error::{Error, Result};
4
5#[derive(Clone, Copy, Debug, Eq, PartialEq)]
6pub enum WindowKind {
7    Rectangular,
8    PeriodicHann,
9    PeriodicHamming,
10}
11
12#[derive(Clone, Copy, Debug, Eq, PartialEq)]
13pub enum PadMode {
14    Reflect,
15    Zero,
16}
17
18#[derive(Clone, Copy, Debug, Eq, PartialEq)]
19pub enum SpectrumKind {
20    OneSide,
21}
22
23#[derive(Clone, Copy, Debug, Eq, PartialEq)]
24pub enum SpectralNormalization {
25    None,
26}
27
28#[derive(Clone, Copy, Debug, Eq, PartialEq)]
29pub enum AudioFeatureLayout {
30    FramesFirst,
31    MelsFirst,
32}
33
34#[derive(Clone, Copy, Debug, PartialEq)]
35pub struct StftConfig {
36    pub n_fft: usize,
37    pub hop_length: usize,
38    pub win_length: usize,
39    pub center: bool,
40    pub pad_mode: PadMode,
41    pub window: WindowKind,
42    pub spectrum: SpectrumKind,
43    pub normalization: SpectralNormalization,
44    pub drop_last_frame: bool,
45}
46
47impl StftConfig {
48    pub const fn frequency_bin_count(self) -> usize {
49        match self.spectrum {
50            SpectrumKind::OneSide => self.n_fft / 2 + 1,
51        }
52    }
53
54    pub fn centered_input_len(self, input_len: usize) -> Result<usize> {
55        if !self.center {
56            return Ok(input_len);
57        }
58        let pad = self.n_fft / 2;
59        input_len
60            .checked_add(pad)
61            .and_then(|len| len.checked_add(pad))
62            .ok_or(Error::SizeOverflow)
63    }
64
65    pub fn frame_count(self, input_len: usize) -> Result<usize> {
66        validate_stft_config(self)?;
67        let padded_len = self.centered_input_len(input_len)?;
68        if padded_len < self.n_fft {
69            return Ok(0);
70        }
71
72        let mut frames = (padded_len - self.n_fft) / self.hop_length + 1;
73        if self.drop_last_frame {
74            frames = frames.saturating_sub(1);
75        }
76        Ok(frames)
77    }
78}
79
80#[derive(Clone, Copy, Debug, PartialEq)]
81pub struct MelFilterConfig {
82    pub sample_rate: usize,
83    pub frequency_bin_count: usize,
84    pub mel_bin_count: usize,
85    pub min_frequency: f32,
86    pub max_frequency: f32,
87}
88
89#[derive(Clone, Copy, Debug, PartialEq)]
90pub struct LogMelConfig {
91    pub floor: f32,
92    pub reference_max: Option<f32>,
93    pub dynamic_range: Option<f32>,
94    pub offset: f32,
95    pub scale: f32,
96}
97
98#[derive(Clone, Copy, Debug, PartialEq)]
99pub struct AudioFrontendConfig {
100    pub stft: StftConfig,
101    pub mel: MelFilterConfig,
102    pub log_mel: LogMelConfig,
103    pub layout: AudioFeatureLayout,
104}
105
106impl AudioFrontendConfig {
107    pub fn validate(self) -> Result<()> {
108        validate_stft_config(self.stft)?;
109        validate_mel_filter_config(self.mel)?;
110        validate_log_mel_config(self.log_mel)?;
111        if self.stft.frequency_bin_count() != self.mel.frequency_bin_count {
112            return Err(Error::LengthMismatch);
113        }
114        Ok(())
115    }
116}
117
118fn validate_stft_config(config: StftConfig) -> Result<()> {
119    if config.n_fft == 0 || config.hop_length == 0 || config.win_length == 0 {
120        return Err(Error::InvalidLength);
121    }
122    if config.win_length > config.n_fft {
123        return Err(Error::InvalidLength);
124    }
125    if !config.n_fft.is_multiple_of(2) {
126        return Err(Error::UnsupportedParameter {
127            op: "stft".into(),
128            parameter: "n_fft".into(),
129            value: config.n_fft,
130        });
131    }
132    Ok(())
133}
134
135fn validate_mel_filter_config(config: MelFilterConfig) -> Result<()> {
136    if config.sample_rate == 0 || config.frequency_bin_count == 0 || config.mel_bin_count == 0 {
137        return Err(Error::InvalidLength);
138    }
139    let nyquist = config.sample_rate as f32 / 2.0;
140    if !config.min_frequency.is_finite()
141        || !config.max_frequency.is_finite()
142        || config.min_frequency < 0.0
143        || config.max_frequency <= config.min_frequency
144        || config.max_frequency > nyquist
145    {
146        return Err(Error::InvalidLength);
147    }
148    Ok(())
149}
150
151fn validate_log_mel_config(config: LogMelConfig) -> Result<()> {
152    if !config.floor.is_finite()
153        || config.floor <= 0.0
154        || !config.offset.is_finite()
155        || !config.scale.is_finite()
156    {
157        return Err(Error::InvalidLength);
158    }
159    if let Some(dynamic_range) = config.dynamic_range
160        && (!dynamic_range.is_finite() || dynamic_range <= 0.0)
161    {
162        return Err(Error::InvalidLength);
163    }
164    if let Some(reference_max) = config.reference_max
165        && !reference_max.is_finite()
166    {
167        return Err(Error::InvalidLength);
168    }
169    Ok(())
170}