dasp_rs/signal_processing/time_domain.rs
1use crate::audio_io::AudioError;
2use ndarray::Array1;
3
4/// Computes the autocorrelation of a signal.
5///
6/// # Arguments
7/// * `y` - Input signal as a slice of `f32`
8/// * `max_size` - Optional maximum lag size (defaults to signal length)
9/// * `axis` - Optional axis parameter (currently unused, included for compatibility)
10///
11/// # Returns
12/// Returns a `Vec<f32>` containing the autocorrelation values for lags from 0 to `max_size - 1`.
13///
14/// # Examples
15/// ```
16/// let signal = vec![1.0, 2.0, 3.0];
17/// let autocorr = autocorrelate(&signal, Some(2), None);
18/// assert_eq!(autocorr, vec![14.0, 8.0]); // [1*1 + 2*2 + 3*3, 1*2 + 2*3]
19/// ```
20pub fn autocorrelate(y: &[f32], max_size: Option<usize>, axis: Option<isize>) -> Vec<f32> {
21 let max_lag = max_size.unwrap_or(y.len());
22 let mut result = Vec::with_capacity(max_lag);
23 for lag in 0..max_lag {
24 let mut sum = 0.0;
25 for i in 0..(y.len() - lag) {
26 sum += y[i] * y[i + lag];
27 }
28 result.push(sum);
29 }
30 result
31}
32
33/// Computes Linear Predictive Coding (LPC) coefficients using the autocorrelation method.
34///
35/// # Arguments
36/// * `y` - Input signal as a slice of `f32`
37/// * `order` - LPC order (number of coefficients to compute, excluding the leading 1.0)
38///
39/// # Returns
40/// Returns a `Result` containing a `Vec<f32>` of LPC coefficients,
41/// or an `AudioError` if the signal length is too short.
42///
43/// # Errors
44/// * `AudioError::InvalidRange` - If `y.len()` is less than or equal to `order`.
45///
46/// # Examples
47/// ```
48/// let signal = vec![1.0, 2.0, 3.0, 4.0];
49/// let coeffs = lpc(&signal, 2).unwrap();
50/// ```
51pub fn lpc(y: &[f32], order: usize) -> Result<Vec<f32>, AudioError> {
52 if y.len() <= order {
53 return Err(AudioError::InvalidRange);
54 }
55 let r = autocorrelate(y, Some(order + 1), None);
56 let mut a = vec![0.0; order + 1];
57 a[0] = 1.0;
58 let mut e = r[0];
59
60 for i in 1..=order {
61 let mut k = 0.0;
62 for j in 0..i {
63 k += a[j] * r[i - j];
64 }
65 k = -k / e;
66 for j in 0..i {
67 a[j] -= k * a[i - 1 - j];
68 }
69 a[i] = k;
70 e *= 1.0 - k * k;
71 }
72 Ok(a)
73}
74
75/// Detects zero crossings in a signal.
76///
77/// # Arguments
78/// * `y` - Input signal as a slice of `f32`
79/// * `threshold` - Optional threshold value for zero crossing (defaults to 0.0)
80/// * `pad` - Optional flag to pad with a zero crossing at index 0 if none are found (defaults to false)
81///
82/// # Returns
83/// Returns a `Vec<usize>` containing the indices where zero crossings occur.
84///
85/// # Examples
86/// ```
87/// let signal = vec![1.0, -1.0, 2.0, -2.0];
88/// let crossings = zero_crossings(&signal, None, None);
89/// assert_eq!(crossings, vec![1, 3]);
90/// ```
91pub fn zero_crossings(y: &[f32], threshold: Option<f32>, pad: Option<bool>) -> Vec<usize> {
92 let thresh = threshold.unwrap_or(0.0);
93 let mut crossings = Vec::new();
94 let mut prev_sign = y[0] >= thresh;
95 for (i, &sample) in y.iter().enumerate().skip(1) {
96 let sign = sample >= thresh;
97 if sign != prev_sign {
98 crossings.push(i);
99 }
100 prev_sign = sign;
101 }
102 if pad.unwrap_or(false) && crossings.is_empty() {
103 crossings.push(0);
104 }
105 crossings
106}
107
108/// Applies μ-law compression to a signal.
109///
110/// # Arguments
111/// * `x` - Input signal as a slice of `f32`
112/// * `mu` - Optional μ-law parameter (defaults to 255.0)
113/// * `quantize` - Optional flag to quantize the output to 8-bit levels (defaults to false)
114///
115/// # Returns
116/// Returns a `Vec<f32>` containing the compressed signal.
117///
118/// # Examples
119/// ```
120/// let signal = vec![0.5, -0.5];
121/// let compressed = mu_compress(&signal, None, None);
122/// ```
123pub fn mu_compress(x: &[f32], mu: Option<f32>, quantize: Option<bool>) -> Vec<f32> {
124 let mu_val = mu.unwrap_or(255.0);
125 x.iter().map(|&v| {
126 let sign = if v >= 0.0 { 1.0 } else { -1.0 };
127 let compressed = sign * (1.0 + mu_val.abs() * v.abs()).ln() / mu_val.ln();
128 if quantize.unwrap_or(false) {
129 (compressed * 255.0).round() / 255.0
130 } else {
131 compressed
132 }
133 }).collect()
134}
135
136/// Applies μ-law expansion to a compressed signal.
137///
138/// # Arguments
139/// * `x` - Input compressed signal as a slice of `f32`
140/// * `mu` - Optional μ-law parameter (defaults to 255.0)
141/// * `quantize` - Optional flag (unused, included for symmetry with `mu_compress`)
142///
143/// # Returns
144/// Returns a `Vec<f32>` containing the expanded signal.
145///
146/// # Examples
147/// ```
148/// let compressed = vec![0.5, -0.5];
149/// let expanded = mu_expand(&compressed, None, None);
150/// ```
151pub fn mu_expand(x: &[f32], mu: Option<f32>, quantize: Option<bool>) -> Vec<f32> {
152 let mu_val = mu.unwrap_or(255.0);
153 x.iter().map(|&v| {
154 let sign = if v >= 0.0 { 1.0 } else { -1.0 };
155 sign * (mu_val.ln() * v.abs()).exp() / mu_val
156 }).collect()
157}
158
159/// Computes the logarithmic energy of framed audio.
160///
161/// # Arguments
162/// * `y` - Input signal as a slice of `f32`
163/// * `frame_length` - Optional frame length in samples (defaults to 2048)
164/// * `hop_length` - Optional hop length in samples (defaults to frame_length / 4)
165///
166/// # Returns
167/// Returns an `Array1<f32>` containing the log energy for each frame.
168///
169/// # Examples
170/// ```
171/// let signal = vec![0.1, 0.2, 0.3, 0.4, 0.5];
172/// let energy = log_energy(&signal, Some(2), Some(1));
173/// ```
174pub fn log_energy(
175 y: &[f32],
176 frame_length: Option<usize>,
177 hop_length: Option<usize>,
178) -> Array1<f32> {
179 let frame_len = frame_length.unwrap_or(2048);
180 let hop = hop_length.unwrap_or(frame_len / 4);
181 let n_frames = (y.len() - frame_len) / hop + 1;
182 let mut energy = Array1::zeros(n_frames);
183
184 for i in 0..n_frames {
185 let start = i * hop;
186 let frame = &y[start..(start + frame_len).min(y.len())];
187 let e = frame.iter().map(|&x| x.powi(2)).sum::<f32>();
188 energy[i] = (e + 1e-10).ln();
189 }
190
191 energy
192}