miniDSP
A small C library for audio DSP
Loading...
Searching...
No Matches
minidsp_spectrum.c
Go to the documentation of this file.
1
9
10#include "minidsp.h"
11#include "minidsp_internal.h"
12
13/* -----------------------------------------------------------------------
14 * Static (file-scope) variables for spectrum analysis caching
15 *
16 * These are separate from the GCC cache (in minidsp_gcc.c) so that
17 * spectrum analysis and delay estimation can be used independently
18 * without invalidating each other's plans.
19 * -----------------------------------------------------------------------*/
20
21static unsigned _spec_N = 0; /* Cached signal length */
22static double *_spec_in = NULL; /* Local copy of input */
23static fftw_complex *_spec_out = NULL; /* FFT output */
24static fftw_plan _spec_plan = NULL; /* FFTW r2c plan */
25
26/* -----------------------------------------------------------------------
27 * Static variables for STFT Hanning window cache
28 *
29 * The STFT reuses the shared _spec_* r2c plan above. Only the Hanning
30 * window buffer is separate, since different callers may use different N.
31 * -----------------------------------------------------------------------*/
32
33static double *_stft_win = NULL; /* Cached Hanning window */
34static unsigned _stft_win_N = 0; /* Window length corresponding to above */
35
36/* -----------------------------------------------------------------------
37 * Static variables for mel filterbank cache
38 *
39 * MFCC extraction is often called frame-by-frame with fixed parameters.
40 * Cache the triangular mel matrix to avoid rebuilding it on every frame.
41 * -----------------------------------------------------------------------*/
42
43static unsigned _mel_N = 0;
44static unsigned _mel_num_mels = 0;
45static double _mel_sample_rate = 0.0;
46static double _mel_min_freq_hz = 0.0;
47static double _mel_max_freq_hz = 0.0;
48static double *_mel_fb = NULL; /* Row-major: [num_mels][N/2+1] */
49
51#define MD_F0_FFT_PROMINENCE_RATIO 2.5
53#define MD_F0_FFT_GLOBAL_RATIO 0.2
55#define MD_MFCC_LOG_FLOOR 1e-12
56
59static double md_parabolic_offset(double y_left, double y_mid, double y_right)
60{
61 double denom = y_left - 2.0 * y_mid + y_right;
62 if (fabs(denom) < 1e-12) return 0.0;
63
64 double delta = 0.5 * (y_left - y_right) / denom;
65 if (delta < -0.5) delta = -0.5;
66 if (delta > 0.5) delta = 0.5;
67 return delta;
68}
69
71static double md_hz_to_mel(double hz)
72{
73 return 2595.0 * log10(1.0 + hz / 700.0);
74}
75
77static double md_mel_to_hz(double mel)
78{
79 return 700.0 * (pow(10.0, mel / 2595.0) - 1.0);
80}
81
83static void _spec_free(void)
84{
85 if (_spec_out) fftw_free(_spec_out);
86 if (_spec_in) free(_spec_in);
87 _spec_out = NULL;
88 _spec_in = NULL;
89}
90
92static void _spec_teardown(void)
93{
94 if (_spec_plan) fftw_destroy_plan(_spec_plan);
95 _spec_plan = NULL;
96 _spec_free();
97}
98
103static void _spec_setup(unsigned N)
104{
105 if (_spec_N == N) return;
106
108 _spec_N = N;
109
110 _spec_in = calloc(N, sizeof(double));
111 _spec_out = fftw_alloc_complex(N / 2 + 1);
112
113 _spec_plan = fftw_plan_dft_r2c_1d(
114 (int)N, _spec_in, _spec_out,
115 FFTW_ESTIMATE | FFTW_DESTROY_INPUT);
116}
117
119static void _stft_teardown(void)
120{
121 free(_stft_win);
122 _stft_win = NULL;
123 _stft_win_N = 0;
124}
125
127static void _mel_teardown(void)
128{
129 free(_mel_fb);
130 _mel_fb = NULL;
131 _mel_N = 0;
132 _mel_num_mels = 0;
133 _mel_sample_rate = 0.0;
134 _mel_min_freq_hz = 0.0;
135 _mel_max_freq_hz = 0.0;
136}
137
139static void _mel_setup(unsigned N, double sample_rate, unsigned num_mels,
140 double min_freq_hz, double max_freq_hz)
141{
142 double nyquist = 0.5 * sample_rate;
143 double f_lo = fmax(0.0, min_freq_hz);
144 double f_hi = fmin(max_freq_hz, nyquist);
145
146 if (_mel_fb != NULL
147 && _mel_N == N
148 && _mel_num_mels == num_mels
149 && _mel_sample_rate == sample_rate
150 && _mel_min_freq_hz == f_lo
151 && _mel_max_freq_hz == f_hi) {
152 return;
153 }
154
156
157 _mel_N = N;
158 _mel_num_mels = num_mels;
159 _mel_sample_rate = sample_rate;
160 _mel_min_freq_hz = f_lo;
161 _mel_max_freq_hz = f_hi;
162
163 unsigned num_bins = N / 2 + 1;
164 size_t fb_len = (size_t)num_mels * num_bins;
165 _mel_fb = calloc(fb_len, sizeof(double));
166 MD_CHECK_VOID(_mel_fb != NULL, MD_ERR_ALLOC_FAILED, "mel filterbank alloc failed");
167
168 if (f_hi <= f_lo) return;
169
170 unsigned num_points = num_mels + 2;
171 double *mel_points = malloc(num_points * sizeof(double));
172 double *hz_points = malloc(num_points * sizeof(double));
173 MD_CHECK_VOID(mel_points != NULL, MD_ERR_ALLOC_FAILED, "mel_points alloc failed");
174 MD_CHECK_VOID(hz_points != NULL, MD_ERR_ALLOC_FAILED, "hz_points alloc failed");
175
176 double mel_min = md_hz_to_mel(f_lo);
177 double mel_max = md_hz_to_mel(f_hi);
178 if (mel_max <= mel_min) {
179 free(hz_points);
180 free(mel_points);
181 return;
182 }
183
184 double mel_step = (mel_max - mel_min) / (double)(num_mels + 1);
185 for (unsigned i = 0; i < num_points; i++) {
186 mel_points[i] = mel_min + mel_step * (double)i;
187 hz_points[i] = md_mel_to_hz(mel_points[i]);
188 }
189
190 for (unsigned m = 0; m < num_mels; m++) {
191 double f_left = hz_points[m];
192 double f_center = hz_points[m + 1];
193 double f_right = hz_points[m + 2];
194 if (f_center <= f_left || f_right <= f_center) {
195 continue;
196 }
197
198 for (unsigned k = 0; k < num_bins; k++) {
199 double f_bin = (double)k * sample_rate / (double)N;
200 double w = 0.0;
201 if (f_bin > f_left && f_bin < f_right) {
202 if (f_bin <= f_center) {
203 w = (f_bin - f_left) / (f_center - f_left);
204 } else {
205 w = (f_right - f_bin) / (f_right - f_center);
206 }
207 }
208 if (w < 0.0) w = 0.0;
209 if (w > 1.0) w = 1.0;
210 _mel_fb[(size_t)m * num_bins + k] = w;
211 }
212 }
213
214 free(hz_points);
215 free(mel_points);
216}
217
222static void _stft_setup(unsigned N)
223{
224 _spec_setup(N); /* reuse shared r2c plan */
225 if (_stft_win_N == N) return; /* fast path: window already correct */
226
227 free(_stft_win);
228 _stft_win = malloc(N * sizeof(double));
229 MD_CHECK_VOID(_stft_win != NULL, MD_ERR_ALLOC_FAILED, "STFT window alloc failed");
230 MD_Gen_Hann_Win(_stft_win, N);
231 _stft_win_N = N;
232}
233
234/* -----------------------------------------------------------------------
235 * Public API: resource cleanup
236 * -----------------------------------------------------------------------*/
237
238void MD_shutdown(void)
239{
245 _spec_N = 0;
246 fftw_cleanup();
247}
248
249/* -----------------------------------------------------------------------
250 * Public API: FFT / Spectrum Analysis
251 * -----------------------------------------------------------------------*/
252
277void MD_magnitude_spectrum(const double *signal, unsigned N, double *mag_out)
278{
279 MD_CHECK_VOID(signal != NULL, MD_ERR_NULL_POINTER, "signal is NULL");
280 MD_CHECK_VOID(mag_out != NULL, MD_ERR_NULL_POINTER, "mag_out is NULL");
281 MD_CHECK_VOID(N >= 2, MD_ERR_INVALID_SIZE, "N must be >= 2");
282
283 _spec_setup(N);
284
285 /* Copy input into the local buffer (FFTW may overwrite it) */
286 memcpy(_spec_in, signal, N * sizeof(double));
287
288 /* Execute the forward FFT (real -> complex) */
289 fftw_execute(_spec_plan);
290
291 /* Compute magnitude |X(k)| = sqrt(re^2 + im^2) for each bin */
292 unsigned num_bins = N / 2 + 1;
293 for (unsigned k = 0; k < num_bins; k++) {
294 mag_out[k] = cabs(_spec_out[k]);
295 }
296}
297
315void MD_power_spectral_density(const double *signal, unsigned N, double *psd_out)
316{
317 MD_CHECK_VOID(signal != NULL, MD_ERR_NULL_POINTER, "signal is NULL");
318 MD_CHECK_VOID(psd_out != NULL, MD_ERR_NULL_POINTER, "psd_out is NULL");
319 MD_CHECK_VOID(N >= 2, MD_ERR_INVALID_SIZE, "N must be >= 2");
320
321 _spec_setup(N);
322
323 /* Copy input into the local buffer (FFTW may overwrite it) */
324 memcpy(_spec_in, signal, N * sizeof(double));
325
326 /* Execute the forward FFT (real -> complex) */
327 fftw_execute(_spec_plan);
328
329 /* Compute PSD[k] = |X(k)|^2 / N = (re^2 + im^2) / N.
330 * We use creal/cimag instead of cabs to avoid a redundant sqrt --
331 * cabs computes sqrt(re^2 + im^2), and we'd just square it again. */
332 unsigned num_bins = N / 2 + 1;
333 for (unsigned k = 0; k < num_bins; k++) {
334 double re = creal(_spec_out[k]);
335 double im = cimag(_spec_out[k]);
336 psd_out[k] = (re * re + im * im) / (double)N;
337 }
338}
339
357void MD_phase_spectrum(const double *signal, unsigned N, double *phase_out)
358{
359 MD_CHECK_VOID(signal != NULL, MD_ERR_NULL_POINTER, "signal is NULL");
360 MD_CHECK_VOID(phase_out != NULL, MD_ERR_NULL_POINTER, "phase_out is NULL");
361 MD_CHECK_VOID(N >= 2, MD_ERR_INVALID_SIZE, "N must be >= 2");
362
363 _spec_setup(N);
364
365 /* Copy input into the local buffer (FFTW may overwrite it) */
366 memcpy(_spec_in, signal, N * sizeof(double));
367
368 /* Execute the forward FFT (real -> complex) */
369 fftw_execute(_spec_plan);
370
371 /* Compute phi(k) = atan2(Im(X(k)), Re(X(k))) for each bin.
372 * carg() from <complex.h> does exactly this and returns [-pi, pi]. */
373 unsigned num_bins = N / 2 + 1;
374 for (unsigned k = 0; k < num_bins; k++) {
375 phase_out[k] = carg(_spec_out[k]);
376 }
377}
378
379double MD_f0_fft(const double *signal, unsigned N,
380 double sample_rate,
381 double min_freq_hz, double max_freq_hz)
382{
383 MD_CHECK(signal != NULL, MD_ERR_NULL_POINTER, "signal is NULL", 0.0);
384 MD_CHECK(N >= 2, MD_ERR_INVALID_SIZE, "N must be >= 2", 0.0);
385 MD_CHECK(sample_rate > 0.0, MD_ERR_INVALID_RANGE, "sample_rate must be > 0", 0.0);
386 MD_CHECK(min_freq_hz > 0.0, MD_ERR_INVALID_RANGE, "min_freq_hz must be > 0", 0.0);
387 MD_CHECK(max_freq_hz > min_freq_hz, MD_ERR_INVALID_RANGE, "max_freq_hz must be > min_freq_hz", 0.0);
388
389 unsigned k_min = (unsigned)ceil(min_freq_hz * (double)N / sample_rate);
390 unsigned k_max = (unsigned)floor(max_freq_hz * (double)N / sample_rate);
391
392 if (k_min < 1) k_min = 1; /* skip DC */
393 if (k_max > N / 2) k_max = N / 2; /* one-sided Nyquist bound */
394 if (k_min > k_max) return 0.0;
395
396 if (MD_energy(signal, N) == 0.0) return 0.0;
397
398 /* Reuse the shared STFT Hann cache to window this frame. */
399 _stft_setup(N);
400 for (unsigned n = 0; n < N; n++) {
401 _spec_in[n] = signal[n] * _stft_win[n];
402 }
403 fftw_execute(_spec_plan);
404
405 double global_max = 0.0;
406 for (unsigned k = 1; k <= N / 2; k++) {
407 double mag = cabs(_spec_out[k]);
408 if (mag > global_max) global_max = mag;
409 }
410
411 double sum_mag = 0.0;
412 double best_mag = -DBL_MAX;
413 unsigned best_k = 0;
414 unsigned num_bins = 0;
415
416 for (unsigned k = k_min; k <= k_max; k++) {
417 double mag = cabs(_spec_out[k]);
418 sum_mag += mag;
419 num_bins++;
420 if (mag > best_mag) {
421 best_mag = mag;
422 best_k = k;
423 }
424 }
425
426 if (best_k == 0 || best_mag <= 0.0 || num_bins == 0) return 0.0;
427
428 double mean_mag = sum_mag / (double)num_bins;
429 if (mean_mag <= 0.0) return 0.0;
430 if (best_mag < MD_F0_FFT_PROMINENCE_RATIO * mean_mag) return 0.0;
431 if (global_max <= 0.0) return 0.0;
432 if (best_mag < MD_F0_FFT_GLOBAL_RATIO * global_max) return 0.0;
433
434 double k_est = (double)best_k;
435 if (best_k > 0 && best_k < N / 2) {
436 double m_left = cabs(_spec_out[best_k - 1]);
437 double m_mid = cabs(_spec_out[best_k]);
438 double m_right = cabs(_spec_out[best_k + 1]);
439 k_est += md_parabolic_offset(m_left, m_mid, m_right);
440 }
441
442 if (k_est < (double)k_min) k_est = (double)k_min;
443 if (k_est > (double)k_max) k_est = (double)k_max;
444
445 return k_est * sample_rate / (double)N;
446}
447
454unsigned MD_stft_num_frames(unsigned signal_len, unsigned N, unsigned hop)
455{
456 if (signal_len < N) return 0;
457 return (signal_len - N) / hop + 1;
458}
459
479void MD_stft(const double *signal, unsigned signal_len,
480 unsigned N, unsigned hop, double *mag_out)
481{
482 MD_CHECK_VOID(signal != NULL, MD_ERR_NULL_POINTER, "signal is NULL");
483 MD_CHECK_VOID(mag_out != NULL, MD_ERR_NULL_POINTER, "mag_out is NULL");
484 MD_CHECK_VOID(N >= 2, MD_ERR_INVALID_SIZE, "N must be >= 2");
485 MD_CHECK_VOID(hop >= 1, MD_ERR_INVALID_SIZE, "hop must be >= 1");
486
487 unsigned num_frames = MD_stft_num_frames(signal_len, N, hop);
488 if (num_frames == 0) return; /* signal too short for even one frame */
489
490 _stft_setup(N);
491 unsigned num_bins = N / 2 + 1;
492
493 for (unsigned f = 0; f < num_frames; f++) {
494 const double *src = signal + (size_t)f * hop; /* (size_t) avoids overflow */
495
496 /* Apply Hanning window directly into the shared input buffer.
497 * Touching every element anyway, so no separate memcpy is needed. */
498 for (unsigned n = 0; n < N; n++) {
499 _spec_in[n] = src[n] * _stft_win[n];
500 }
501
502 fftw_execute(_spec_plan);
503
504 /* Write magnitude row: |X_f(k)| = sqrt(re^2 + im^2).
505 * cabs() is correct here -- we need the actual sqrt magnitude,
506 * not |z|^2, so the creal/cimag shortcut from PSD does not apply. */
507 double *row = mag_out + (size_t)f * num_bins;
508 for (unsigned k = 0; k < num_bins; k++) {
509 row[k] = cabs(_spec_out[k]);
510 }
511 }
512}
513
514void MD_mel_filterbank(unsigned N, double sample_rate,
515 unsigned num_mels,
516 double min_freq_hz, double max_freq_hz,
517 double *filterbank_out)
518{
519 MD_CHECK_VOID(N >= 2, MD_ERR_INVALID_SIZE, "N must be >= 2");
520 MD_CHECK_VOID(sample_rate > 0.0, MD_ERR_INVALID_RANGE, "sample_rate must be > 0");
521 MD_CHECK_VOID(num_mels > 0, MD_ERR_INVALID_SIZE, "num_mels must be > 0");
522 MD_CHECK_VOID(min_freq_hz < max_freq_hz, MD_ERR_INVALID_RANGE, "min_freq_hz must be < max_freq_hz");
523 MD_CHECK_VOID(filterbank_out != NULL, MD_ERR_NULL_POINTER, "filterbank_out is NULL");
524
525 _mel_setup(N, sample_rate, num_mels, min_freq_hz, max_freq_hz);
526
527 unsigned num_bins = N / 2 + 1;
528 size_t fb_len = (size_t)num_mels * num_bins;
529 memcpy(filterbank_out, _mel_fb, fb_len * sizeof(double));
530}
531
532void MD_mel_energies(const double *signal, unsigned N,
533 double sample_rate, unsigned num_mels,
534 double min_freq_hz, double max_freq_hz,
535 double *mel_out)
536{
537 MD_CHECK_VOID(signal != NULL, MD_ERR_NULL_POINTER, "signal is NULL");
538 MD_CHECK_VOID(mel_out != NULL, MD_ERR_NULL_POINTER, "mel_out is NULL");
539 MD_CHECK_VOID(N >= 2, MD_ERR_INVALID_SIZE, "N must be >= 2");
540 MD_CHECK_VOID(sample_rate > 0.0, MD_ERR_INVALID_RANGE, "sample_rate must be > 0");
541 MD_CHECK_VOID(num_mels > 0, MD_ERR_INVALID_SIZE, "num_mels must be > 0");
542 MD_CHECK_VOID(min_freq_hz < max_freq_hz, MD_ERR_INVALID_RANGE, "min_freq_hz must be < max_freq_hz");
543
544 _stft_setup(N);
545 _mel_setup(N, sample_rate, num_mels, min_freq_hz, max_freq_hz);
546
547 for (unsigned n = 0; n < N; n++) {
548 _spec_in[n] = signal[n] * _stft_win[n];
549 }
550 fftw_execute(_spec_plan);
551
552 unsigned num_bins = N / 2 + 1;
553 for (unsigned m = 0; m < num_mels; m++) {
554 mel_out[m] = 0.0;
555 }
556
557 for (unsigned k = 0; k < num_bins; k++) {
558 double re = creal(_spec_out[k]);
559 double im = cimag(_spec_out[k]);
560 double psd = (re * re + im * im) / (double)N;
561
562 for (unsigned m = 0; m < num_mels; m++) {
563 mel_out[m] += _mel_fb[(size_t)m * num_bins + k] * psd;
564 }
565 }
566}
567
568/* -----------------------------------------------------------------------
569 * Public API: FFT-based brickwall lowpass filter
570 * -----------------------------------------------------------------------*/
571
572void MD_lowpass_brickwall(double *signal, unsigned len,
573 double cutoff_hz, double sample_rate)
574{
575 MD_CHECK_VOID(signal != NULL, MD_ERR_NULL_POINTER, "signal is NULL");
576 MD_CHECK_VOID(len > 0, MD_ERR_INVALID_SIZE, "len must be > 0");
577 MD_CHECK_VOID(cutoff_hz > 0.0, MD_ERR_INVALID_RANGE, "cutoff_hz must be > 0");
578 MD_CHECK_VOID(sample_rate > 0.0, MD_ERR_INVALID_RANGE, "sample_rate must be > 0");
579 MD_CHECK_VOID(cutoff_hz < sample_rate / 2.0, MD_ERR_INVALID_RANGE, "cutoff_hz must be < Nyquist");
580
581 unsigned num_bins = len / 2 + 1;
582
583 /* Allocate buffers for one-off FFT round-trip */
584 double *in_buf = malloc(len * sizeof(double));
585 fftw_complex *freq = fftw_alloc_complex(num_bins);
586 MD_CHECK_VOID(in_buf != NULL, MD_ERR_ALLOC_FAILED, "lowpass input buffer alloc failed");
587 MD_CHECK_VOID(freq != NULL, MD_ERR_ALLOC_FAILED, "lowpass freq buffer alloc failed");
588
589 memcpy(in_buf, signal, len * sizeof(double));
590
591 /* Create one-off plans (FFTW_ESTIMATE — no measurement overhead) */
592 fftw_plan fwd = fftw_plan_dft_r2c_1d(
593 (int)len, in_buf, freq, FFTW_ESTIMATE | FFTW_DESTROY_INPUT);
594 fftw_plan inv = fftw_plan_dft_c2r_1d(
595 (int)len, freq, signal, FFTW_ESTIMATE | FFTW_DESTROY_INPUT);
596 MD_CHECK_VOID(fwd != NULL, MD_ERR_ALLOC_FAILED, "lowpass forward plan alloc failed");
597 MD_CHECK_VOID(inv != NULL, MD_ERR_ALLOC_FAILED, "lowpass inverse plan alloc failed");
598
599 /* Forward FFT */
600 fftw_execute(fwd);
601
602 /* Zero all bins above the cutoff frequency */
603 unsigned cutoff_bin = (unsigned)(cutoff_hz * (double)len / sample_rate);
604 for (unsigned k = cutoff_bin + 1; k < num_bins; k++) {
605 freq[k] = 0.0;
606 }
607
608 /* Inverse FFT (writes directly into signal buffer) */
609 fftw_execute(inv);
610
611 /* FFTW's c2r is unnormalized — divide by N */
612 for (unsigned i = 0; i < len; i++) {
613 signal[i] /= (double)len;
614 }
615
616 fftw_destroy_plan(inv);
617 fftw_destroy_plan(fwd);
618 fftw_free(freq);
619 free(in_buf);
620}
621
622void MD_mfcc(const double *signal, unsigned N,
623 double sample_rate,
624 unsigned num_mels, unsigned num_coeffs,
625 double min_freq_hz, double max_freq_hz,
626 double *mfcc_out)
627{
628 MD_CHECK_VOID(signal != NULL, MD_ERR_NULL_POINTER, "signal is NULL");
629 MD_CHECK_VOID(mfcc_out != NULL, MD_ERR_NULL_POINTER, "mfcc_out is NULL");
630 MD_CHECK_VOID(N >= 2, MD_ERR_INVALID_SIZE, "N must be >= 2");
631 MD_CHECK_VOID(sample_rate > 0.0, MD_ERR_INVALID_RANGE, "sample_rate must be > 0");
632 MD_CHECK_VOID(num_mels > 0, MD_ERR_INVALID_SIZE, "num_mels must be > 0");
633 MD_CHECK_VOID(num_coeffs >= 1 && num_coeffs <= num_mels, MD_ERR_INVALID_RANGE, "num_coeffs must be in [1, num_mels]");
634 MD_CHECK_VOID(min_freq_hz < max_freq_hz, MD_ERR_INVALID_RANGE, "min_freq_hz must be < max_freq_hz");
635
636 double *mel = malloc(num_mels * sizeof(double));
637 double *log_mel = malloc(num_mels * sizeof(double));
638 MD_CHECK_VOID(mel != NULL, MD_ERR_ALLOC_FAILED, "mel buffer alloc failed");
639 MD_CHECK_VOID(log_mel != NULL, MD_ERR_ALLOC_FAILED, "log_mel buffer alloc failed");
640
641 MD_mel_energies(signal, N, sample_rate, num_mels,
642 min_freq_hz, max_freq_hz, mel);
643
644 for (unsigned m = 0; m < num_mels; m++) {
645 log_mel[m] = log(fmax(mel[m], MD_MFCC_LOG_FLOOR));
646 }
647
648 for (unsigned c = 0; c < num_coeffs; c++) {
649 double norm = (c == 0)
650 ? sqrt(1.0 / (double)num_mels)
651 : sqrt(2.0 / (double)num_mels);
652 double sum = 0.0;
653 for (unsigned m = 0; m < num_mels; m++) {
654 double angle = M_PI * (double)c * ((double)m + 0.5)
655 / (double)num_mels;
656 sum += log_mel[m] * cos(angle);
657 }
658 mfcc_out[c] = norm * sum;
659 }
660
661 free(log_mel);
662 free(mel);
663}
A mini library of DSP (Digital Signal Processing) routines.
@ MD_ERR_INVALID_SIZE
A size or count argument is invalid (e.g.
Definition minidsp.h:63
@ MD_ERR_INVALID_RANGE
A range or bound is invalid (e.g.
Definition minidsp.h:64
@ MD_ERR_ALLOC_FAILED
A memory allocation failed.
Definition minidsp.h:65
@ MD_ERR_NULL_POINTER
A required pointer argument is NULL.
Definition minidsp.h:62
void MD_Gen_Hann_Win(double *out, unsigned n)
Generate a Hanning (Hann) window of length n.
double MD_energy(const double *a, unsigned N)
Compute signal energy: sum of squared samples.
void md_gcc_teardown(void)
Tear down the GCC-PHAT FFT cache and reset its cached length.
Internal header for cross-file dependencies within the minidsp module.
void md_steg_teardown(void)
Tear down the BFSK sine carrier cache.
#define MD_CHECK(cond, code, msg, retval)
Check a precondition in a function that returns a value.
#define MD_CHECK_VOID(cond, code, msg)
Check a precondition in a void function.
static void _stft_setup(unsigned N)
Ensure the shared r2c plan is ready for length N, and that the STFT Hanning window buffer matches N.
void MD_power_spectral_density(const double *signal, unsigned N, double *psd_out)
Compute the power spectral density (PSD) of a real-valued signal.
static double md_hz_to_mel(double hz)
HTK mel scale conversion: Hz -> mel.
void MD_mel_energies(const double *signal, unsigned N, double sample_rate, unsigned num_mels, double min_freq_hz, double max_freq_hz, double *mel_out)
Compute mel-band energies from a single frame.
void MD_mfcc(const double *signal, unsigned N, double sample_rate, unsigned num_mels, unsigned num_coeffs, double min_freq_hz, double max_freq_hz, double *mfcc_out)
Compute mel-frequency cepstral coefficients (MFCCs) from a single frame.
static double md_parabolic_offset(double y_left, double y_mid, double y_right)
Three-point parabolic refinement around a discrete peak index.
void MD_phase_spectrum(const double *signal, unsigned N, double *phase_out)
Compute the one-sided phase spectrum of a real-valued signal.
double MD_f0_fft(const double *signal, unsigned N, double sample_rate, double min_freq_hz, double max_freq_hz)
Estimate the fundamental frequency (F0) using FFT peak picking.
#define MD_MFCC_LOG_FLOOR
Floor before logarithm in MFCC log-mel compression.
static double md_mel_to_hz(double mel)
HTK mel scale conversion: mel -> Hz.
static void _spec_free(void)
Free spectrum analysis buffers.
void MD_stft(const double *signal, unsigned signal_len, unsigned N, unsigned hop, double *mag_out)
Compute the Short-Time Fourier Transform (STFT) magnitude matrix.
static void _stft_teardown(void)
Free the cached STFT Hanning window.
static void _spec_teardown(void)
Tear down spectrum analysis plan and buffers.
static void _mel_setup(unsigned N, double sample_rate, unsigned num_mels, double min_freq_hz, double max_freq_hz)
Build the cached mel filterbank for the requested parameters.
static void _spec_setup(unsigned N)
Ensure the spectrum FFT cache matches the requested length.
void MD_lowpass_brickwall(double *signal, unsigned len, double cutoff_hz, double sample_rate)
Apply a brickwall lowpass filter to a signal in-place.
void MD_shutdown(void)
Free all internally cached FFT plans and buffers.
#define MD_F0_FFT_GLOBAL_RATIO
Candidate must also be a meaningful fraction of the frame's global peak.
void MD_magnitude_spectrum(const double *signal, unsigned N, double *mag_out)
Compute the magnitude spectrum of a real-valued signal.
static void _mel_teardown(void)
Free mel filterbank cache.
void MD_mel_filterbank(unsigned N, double sample_rate, unsigned num_mels, double min_freq_hz, double max_freq_hz, double *filterbank_out)
Build a mel-spaced triangular filterbank matrix.
unsigned MD_stft_num_frames(unsigned signal_len, unsigned N, unsigned hop)
Compute the number of complete STFT frames for the given parameters.
#define MD_F0_FFT_PROMINENCE_RATIO
F0-FFT peak must stand out from the average in-range spectrum.