1#[allow(unused_imports)]
4use crate::math::FloatMath;
5use crate::transform::cfft_f32;
6use crate::types::Status;
7use crate::window::*;
8
9#[derive(Debug, Clone, Copy, PartialEq, Eq)]
11pub enum WelchWindow {
12 Rectangular,
13 Hamming,
14 Hanning,
15 Blackman,
16 BlackmanHarris,
17 Bartlett,
18 Welch,
19}
20
21pub fn welch_psd_f32(
31 src: &[f32],
32 dst_psd: &mut [f32],
33 fft_len: usize,
34 overlap: usize,
35 sample_rate: f32,
36 window: WelchWindow,
37 return_db: bool,
38) -> Status {
39 let out_bins = fft_len / 2;
40 if fft_len < 4 || (fft_len & (fft_len - 1)) != 0 || fft_len > 512 {
41 return Status::ArgumentError;
42 }
43 if overlap >= fft_len || sample_rate <= 0.0 {
44 return Status::ArgumentError;
45 }
46 if src.len() < fft_len || dst_psd.len() < out_bins {
47 return Status::LengthError;
48 }
49
50 let step = fft_len - overlap;
51 let num_segments = (src.len() - fft_len) / step + 1;
52 if num_segments == 0 {
53 return Status::LengthError;
54 }
55
56 let mut win = [1.0f32; 512];
58 match window {
59 WelchWindow::Rectangular => win[..fft_len].fill(1.0),
60 WelchWindow::Hamming => hamming_f32(&mut win[..fft_len]),
61 WelchWindow::Hanning => hanning_f32(&mut win[..fft_len]),
62 WelchWindow::Blackman => blackman_f32(&mut win[..fft_len]),
63 WelchWindow::BlackmanHarris => blackman_harris_f32(&mut win[..fft_len]),
64 WelchWindow::Bartlett => bartlett_f32(&mut win[..fft_len]),
65 WelchWindow::Welch => welch_f32(&mut win[..fft_len]),
66 }
67
68 let mut win_power = 0.0f32;
70 for i in 0..fft_len {
71 win_power += win[i] * win[i];
72 }
73 if win_power == 0.0 {
74 win_power = 1.0;
75 }
76
77 dst_psd[..out_bins].fill(0.0);
78
79 let mut scratch = [0.0f32; 1024];
80
81 for seg in 0..num_segments {
82 let start_idx = seg * step;
83 for i in 0..fft_len {
84 scratch[2 * i] = src[start_idx + i] * win[i];
85 scratch[2 * i + 1] = 0.0;
86 }
87
88 cfft_f32(&mut scratch[..2 * fft_len], fft_len, 0, 1);
89
90 for k in 0..out_bins {
91 let re = scratch[2 * k];
92 let im = scratch[2 * k + 1];
93 let mag_sq = re * re + im * im;
94 dst_psd[k] += mag_sq;
95 }
96 }
97
98 let norm = 2.0f32 / (num_segments as f32 * sample_rate * win_power);
101 for k in 0..out_bins {
102 let linear_psd = dst_psd[k] * norm;
103 if return_db {
104 let clamped = if linear_psd > 1e-14 {
105 linear_psd
106 } else {
107 1e-14
108 };
109 dst_psd[k] = 10.0 * clamped.log10();
110 } else {
111 dst_psd[k] = linear_psd;
112 }
113 }
114
115 Status::Success
116}
117
118pub fn periodogram_f32(
120 src: &[f32],
121 dst_psd: &mut [f32],
122 fft_len: usize,
123 sample_rate: f32,
124 window: WelchWindow,
125 return_db: bool,
126) -> Status {
127 welch_psd_f32(src, dst_psd, fft_len, 0, sample_rate, window, return_db)
128}
129
130pub fn ar_burg_f32(signal: &[f32], order: usize, ar_coeffs_out: &mut [f32]) -> Result<f32, Status> {
144 let n = signal.len();
145 if order == 0 || order > 32 || n < 2 * order {
146 return Err(Status::ArgumentError);
147 }
148 if ar_coeffs_out.len() < order {
149 return Err(Status::LengthError);
150 }
151
152 let mut f_err = [0.0f32; 256];
153 let mut b_err = [0.0f32; 256];
154 if n > f_err.len() {
155 return Err(Status::ArgumentError);
156 }
157
158 f_err[..n].copy_from_slice(signal);
159 b_err[..n].copy_from_slice(signal);
160
161 let mut total_energy = 0.0f32;
162 for &x in signal {
163 total_energy += x * x;
164 }
165 let mut noise_var = total_energy / n as f32;
166
167 let mut a_prev = [0.0f32; 32];
168
169 for m in 1..=order {
170 let mut num = 0.0f32;
171 let mut den = 0.0f32;
172
173 for i in m..n {
174 let f = f_err[i];
175 let b = b_err[i - 1];
176 num += f * b;
177 den += f * f + b * b;
178 }
179
180 if den.abs() < 1e-12 {
181 break;
182 }
183
184 let k_m = -2.0 * num / den;
185
186 ar_coeffs_out[m - 1] = k_m;
188 for i in 1..m {
189 ar_coeffs_out[i - 1] = a_prev[i - 1] + k_m * a_prev[m - 1 - i];
190 }
191 a_prev[..m].copy_from_slice(&ar_coeffs_out[..m]);
192
193 for i in (m..n).rev() {
195 let f = f_err[i];
196 let b = b_err[i - 1];
197 f_err[i] = f + k_m * b;
198 b_err[i] = b + k_m * f;
199 }
200
201 noise_var *= (1.0 - k_m * k_m).max(0.0);
202 }
203
204 Ok(noise_var)
205}
206
207pub fn ar_psd_f32(
211 ar_coeffs: &[f32],
212 noise_variance: f32,
213 num_bins: usize,
214 psd_out: &mut [f32],
215 return_db: bool,
216) -> Status {
217 if num_bins == 0 || psd_out.len() < num_bins {
218 return Status::LengthError;
219 }
220
221 let p = ar_coeffs.len();
222 let d_omega = core::f32::consts::PI / (num_bins as f32);
223
224 for bin in 0..num_bins {
225 let omega = bin as f32 * d_omega;
226 let mut re = 1.0f32;
227 let mut im = 0.0f32;
228
229 for k in 1..=p {
230 let angle = -(k as f32) * omega;
231 re += ar_coeffs[k - 1] * angle.cos();
232 im += ar_coeffs[k - 1] * angle.sin();
233 }
234
235 let denom = (re * re + im * im).max(1e-12);
236 let p_linear = noise_variance / denom;
237
238 if return_db {
239 psd_out[bin] = 10.0 * p_linear.max(1e-14).log10();
240 } else {
241 psd_out[bin] = p_linear;
242 }
243 }
244
245 Status::Success
246}