miniDSP
A small C library for audio DSP
Loading...
Searching...
No Matches
minidsp.c
Go to the documentation of this file.
1
28#include "minidsp.h"
29
30/* -----------------------------------------------------------------------
31 * Static (file-scope) variables for FFT caching
32 *
33 * Creating FFTW plans is expensive, so we cache them. As long as the
34 * signal length N stays the same between calls, we reuse the existing
35 * plans and buffers. If N changes, we tear everything down and rebuild.
36 * -----------------------------------------------------------------------*/
37
38static unsigned _N = 0; /* Current cached signal length */
39static double *siga_loc = nullptr; /* Local copy of input signal A */
40static double *sigb_loc = nullptr; /* Local copy of input signal B */
41static double *lags_loc = nullptr; /* Shifted cross-correlation output */
42static fftw_complex *ffta = nullptr; /* FFT of signal A */
43static fftw_complex *fftb = nullptr; /* FFT of signal B */
44static fftw_complex *xspec = nullptr; /* Cross-spectrum (FFT_B * conj(FFT_A))*/
45static double *xcorr = nullptr; /* Raw cross-correlation (time domain) */
46
47static fftw_plan pa = nullptr; /* FFTW plan: signal A -> FFT */
48static fftw_plan pb = nullptr; /* FFTW plan: signal B -> FFT */
49static fftw_plan px = nullptr; /* FFTW plan: cross-spectrum -> IFFT */
50
51/* -----------------------------------------------------------------------
52 * Internal helpers for FFT buffer management
53 * -----------------------------------------------------------------------*/
54
56static void _xcorr_free(void)
57{
58 if (xspec) fftw_free(xspec);
59 if (fftb) fftw_free(fftb);
60 if (ffta) fftw_free(ffta);
61
62 if (lags_loc) free(lags_loc);
63 if (xcorr) free(xcorr);
64 if (sigb_loc) free(sigb_loc);
65 if (siga_loc) free(siga_loc);
66
67 xspec = nullptr; fftb = nullptr; ffta = nullptr;
68 lags_loc = nullptr; xcorr = nullptr; sigb_loc = nullptr; siga_loc = nullptr;
69}
70
72static void _xcorr_malloc(void)
73{
74 siga_loc = calloc(_N, sizeof(double));
75 sigb_loc = calloc(_N, sizeof(double));
76 xcorr = calloc(_N + 1, sizeof(double)); /* FFTW c2r needs N+1 */
77 lags_loc = calloc(_N, sizeof(double));
78
79 /* FFTW provides its own allocator that guarantees proper alignment
80 * for SIMD instructions (SSE2, AVX, etc.). Always use it for
81 * fftw_complex arrays. */
82 ffta = fftw_alloc_complex(_N);
83 fftb = fftw_alloc_complex(_N);
84 xspec = fftw_alloc_complex(_N);
85}
86
88static void _xcorr_teardown(void)
89{
90 if (pa) fftw_destroy_plan(pa);
91 if (pb) fftw_destroy_plan(pb);
92 if (px) fftw_destroy_plan(px);
93 pa = nullptr; pb = nullptr; px = nullptr;
94
96}
97
109static void _xcorr_setup(void)
110{
113
114 /* FFTW_ESTIMATE: pick a reasonable plan quickly (don't benchmark).
115 * FFTW_DESTROY_INPUT: FFTW may overwrite the input array -- that's
116 * fine because we always copy the data into our local buffers first. */
117 pa = fftw_plan_dft_r2c_1d(_N, siga_loc, ffta, FFTW_ESTIMATE | FFTW_DESTROY_INPUT);
118 pb = fftw_plan_dft_r2c_1d(_N, sigb_loc, fftb, FFTW_ESTIMATE | FFTW_DESTROY_INPUT);
119 px = fftw_plan_dft_c2r_1d(_N, xspec, xcorr, FFTW_ESTIMATE | FFTW_DESTROY_INPUT);
120}
121
126static void _gcc_setup(unsigned N)
127{
128 if (_N != N) {
129 _N = N;
130 _xcorr_setup();
131 }
132}
133
134/* -----------------------------------------------------------------------
135 * Static (file-scope) variables for spectrum analysis caching
136 *
137 * These are separate from the GCC cache above so that spectrum analysis
138 * and delay estimation can be used independently without invalidating
139 * each other's plans.
140 * -----------------------------------------------------------------------*/
141
142static unsigned _spec_N = 0; /* Cached signal length */
143static double *_spec_in = nullptr; /* Local copy of input */
144static fftw_complex *_spec_out = nullptr; /* FFT output */
145static fftw_plan _spec_plan = nullptr; /* FFTW r2c plan */
146
147/* -----------------------------------------------------------------------
148 * Static variables for STFT Hanning window cache
149 *
150 * The STFT reuses the shared _spec_* r2c plan above. Only the Hanning
151 * window buffer is separate, since different callers may use different N.
152 * -----------------------------------------------------------------------*/
153
154static double *_stft_win = nullptr; /* Cached Hanning window */
155static unsigned _stft_win_N = 0; /* Window length corresponding to above */
156
158static void _spec_free(void)
159{
160 if (_spec_out) fftw_free(_spec_out);
161 if (_spec_in) free(_spec_in);
162 _spec_out = nullptr;
163 _spec_in = nullptr;
164}
165
167static void _spec_teardown(void)
168{
169 if (_spec_plan) fftw_destroy_plan(_spec_plan);
170 _spec_plan = nullptr;
171 _spec_free();
172}
173
178static void _spec_setup(unsigned N)
179{
180 if (_spec_N == N) return;
181
183 _spec_N = N;
184
185 _spec_in = calloc(N, sizeof(double));
186 _spec_out = fftw_alloc_complex(N / 2 + 1);
187
188 _spec_plan = fftw_plan_dft_r2c_1d(
189 (int)N, _spec_in, _spec_out,
190 FFTW_ESTIMATE | FFTW_DESTROY_INPUT);
191}
192
194static void _stft_teardown(void)
195{
196 free(_stft_win);
197 _stft_win = nullptr;
198 _stft_win_N = 0;
199}
200
205static void _stft_setup(unsigned N)
206{
207 _spec_setup(N); /* reuse shared r2c plan */
208 if (_stft_win_N == N) return; /* fast path: window already correct */
209
210 free(_stft_win);
211 _stft_win = malloc(N * sizeof(double));
212 assert(_stft_win != nullptr);
213 MD_Gen_Hann_Win(_stft_win, N);
214 _stft_win_N = N;
215}
216
217/* -----------------------------------------------------------------------
218 * Internal helpers for cross-correlation post-processing
219 * -----------------------------------------------------------------------*/
220
232static void _fftshift(const double *in, double *out, unsigned N)
233{
234 /* The split point: how many elements are in the "second half" */
235 unsigned half = (unsigned)floor((double)N / 2.0);
236
237 /* Copy the second half of in[] (negative lags) to the start of out[] */
238 memcpy(out, &in[half], sizeof(double) * (N - half));
239
240 /* Copy the first half of in[] (positive lags) to the end of out[] */
241 memcpy(&out[N - half], in, sizeof(double) * half);
242}
243
252static void _max_index(const double *a, unsigned N,
253 double *max, unsigned *maxi)
254{
255 assert(a != nullptr);
256 assert(N >= 1);
257
258 unsigned best_i = 0;
259 double best_v = a[0];
260
261 for (unsigned i = 1; i < N; i++) {
262 if (a[i] > best_v) {
263 best_v = a[i];
264 best_i = i;
265 }
266 }
267
268 *max = best_v;
269 *maxi = best_i;
270}
271
272/* -----------------------------------------------------------------------
273 * Public API: resource cleanup
274 * -----------------------------------------------------------------------*/
275
276void MD_shutdown(void)
277{
281 _spec_N = 0;
282 fftw_cleanup();
283}
284
285/* -----------------------------------------------------------------------
286 * Public API: basic signal measurements
287 * -----------------------------------------------------------------------*/
288
298double MD_dot(const double *a, const double *b, unsigned N)
299{
300 double d = 0.0;
301 for (unsigned i = 0; i < N; i++) {
302 d += a[i] * b[i];
303 }
304 return d;
305}
306
318double MD_energy(const double *a, unsigned N)
319{
320 assert(a != nullptr);
321 if (N == 1) return a[0] * a[0];
322 return MD_dot(a, a, N);
323}
324
333double MD_power(const double *a, unsigned N)
334{
335 assert(a != nullptr);
336 assert(N > 0);
337 return MD_energy(a, N) / (double)N;
338}
339
349double MD_power_db(const double *a, unsigned N)
350{
351 assert(a != nullptr);
352 assert(N > 0);
353 double p = fmax(1.0e-10, MD_power(a, N));
354 return 10.0 * log10(p);
355}
356
357/* -----------------------------------------------------------------------
358 * Public API: signal scaling and conditioning
359 * -----------------------------------------------------------------------*/
360
369double MD_scale(double in,
370 double oldmin, double oldmax,
371 double newmin, double newmax)
372{
373 return (in - oldmin) * (newmax - newmin) / (oldmax - oldmin) + newmin;
374}
375
379void MD_scale_vec(double *in, double *out, unsigned N,
380 double oldmin, double oldmax,
381 double newmin, double newmax)
382{
383 assert(in != nullptr);
384 assert(out != nullptr);
385 assert(oldmin < oldmax);
386 assert(newmin < newmax);
387 if (N == 0) return;
388
389 double scale = (newmax - newmin) / (oldmax - oldmin);
390 for (unsigned i = 0; i < N; i++) {
391 out[i] = (in[i] - oldmin) * scale + newmin;
392 }
393}
394
402void MD_fit_within_range(double *in, double *out, unsigned N,
403 double newmin, double newmax)
404{
405 assert(in != nullptr);
406 assert(out != nullptr);
407 assert(newmin < newmax);
408 if (N == 0) return;
409
410 /* Find the actual min and max of the input */
411 double in_min = in[0];
412 double in_max = in[0];
413 for (unsigned i = 1; i < N; i++) {
414 if (in[i] < in_min) in_min = in[i];
415 if (in[i] > in_max) in_max = in[i];
416 }
417
418 if (in_min >= newmin && in_max <= newmax) {
419 /* Already fits -- just copy without scaling */
420 for (unsigned i = 0; i < N; i++) {
421 out[i] = in[i];
422 }
423 } else {
424 MD_scale_vec(in, out, N, in_min, in_max, newmin, newmax);
425 }
426}
427
438void MD_adjust_dblevel(const double *in, double *out,
439 unsigned N, double dblevel)
440{
441 /* Convert the target dB level back to linear power */
442 double desired_power = pow(10.0, dblevel / 10.0);
443
444 /* Compute the gain factor:
445 * We want: (1/N) * sum(out[i]^2) = desired_power
446 * Since out[i] = in[i] * gain:
447 * (gain^2 / N) * sum(in[i]^2) = desired_power
448 * gain = sqrt(desired_power * N / energy_in) */
449 double input_energy = MD_energy(in, N);
450 double gain = sqrt((desired_power * (double)N) / input_energy);
451
452 /* Apply the gain and check for out-of-range values */
453 bool out_of_range = false;
454 for (unsigned i = 0; i < N; i++) {
455 out[i] = in[i] * gain;
456 if (out[i] > 1.0 || out[i] < -1.0) {
457 out_of_range = true;
458 }
459 }
460
461 /* If any samples exceed [-1.0, 1.0], rescale everything to fit */
462 if (out_of_range) {
463 MD_fit_within_range(out, out, N, -1.0, 1.0);
464 }
465}
466
483double MD_entropy(const double *a, unsigned N, bool clip)
484{
485 assert(a != nullptr);
486
487 if (N <= 1) return 0.0;
488
489 /* Maximum possible entropy for N bins (uniform distribution) */
490 double max_entropy = log2((double)N);
491
492 /* First pass: compute the total (for normalisation into probabilities) */
493 double total = 0.0;
494 if (clip) {
495 for (unsigned i = 0; i < N; i++) {
496 total += (a[i] < 0.0) ? 0.0 : a[i];
497 }
498 } else {
499 for (unsigned i = 0; i < N; i++) {
500 total += a[i] * a[i];
501 }
502 }
503
504 /* If the total is zero (e.g., all-zeros input), entropy is undefined.
505 * We return 0.0 because there is no meaningful distribution. */
506 if (total == 0.0) return 0.0;
507
508 /* Second pass: compute H = -SUM( p_i * log2(p_i) ) */
509 double entropy = 0.0;
510 for (unsigned i = 0; i < N; i++) {
511 if (a[i] == 0.0) continue;
512 if (clip && a[i] < 0.0) continue;
513
514 double p;
515 if (clip) {
516 p = a[i] / total;
517 } else {
518 p = (a[i] * a[i]) / total;
519 }
520
521 entropy += p * log2(p); /* Note: p*log2(p) is always <= 0 */
522 }
523
524 /* Negate and normalise so the result is in [0, 1] */
525 return -entropy / max_entropy;
526}
527
528/* -----------------------------------------------------------------------
529 * Public API: window generation
530 * -----------------------------------------------------------------------*/
531
543void MD_Gen_Hann_Win(double *out, unsigned n)
544{
545 double n_minus_1 = (double)(n - 1);
546 for (unsigned i = 0; i < n; i++) {
547 out[i] = 0.5 * (1.0 - cos(2.0 * M_PI * (double)i / n_minus_1));
548 }
549}
550
551/* -----------------------------------------------------------------------
552 * Public API: Signal generators
553 * -----------------------------------------------------------------------*/
554
555void MD_sine_wave(double *output, unsigned N, double amplitude,
556 double freq, double sample_rate)
557{
558 assert(output != nullptr);
559 assert(N > 0);
560 assert(sample_rate > 0.0);
561 double phase_step = 2.0 * M_PI * freq / sample_rate;
562 for (unsigned i = 0; i < N; i++)
563 output[i] = amplitude * sin(phase_step * (double)i);
564}
565
566void MD_white_noise(double *output, unsigned N, double amplitude,
567 unsigned seed)
568{
569 assert(output != nullptr);
570 assert(N > 0);
571
572 /* 64-bit LCG (Knuth MMIX constants) for uniform doubles in (0, 1).
573 * Self-contained so we don't need POSIX drand48. */
574 unsigned long long state = (unsigned long long)seed;
575
576 /* Box-Muller transform: convert pairs of uniform random numbers
577 * into pairs of Gaussian random numbers (mean 0, std dev 1),
578 * then scale by the requested amplitude.
579 *
580 * For each pair (u1, u2) drawn from (0, 1]:
581 * z0 = sqrt(-2 ln u1) * cos(2 pi u2)
582 * z1 = sqrt(-2 ln u1) * sin(2 pi u2) */
583 unsigned i = 0;
584 while (i + 1 < N) {
585 state = state * 6364136223846793005ULL + 1442695040888963407ULL;
586 double u1 = (double)(state >> 11) * 0x1.0p-53;
587 if (u1 < DBL_MIN) u1 = DBL_MIN;
588 state = state * 6364136223846793005ULL + 1442695040888963407ULL;
589 double u2 = (double)(state >> 11) * 0x1.0p-53;
590
591 double r = sqrt(-2.0 * log(u1));
592 double theta = 2.0 * M_PI * u2;
593 output[i] = amplitude * r * cos(theta);
594 output[i + 1] = amplitude * r * sin(theta);
595 i += 2;
596 }
597 /* If N is odd, generate one extra pair and use only the first value */
598 if (i < N) {
599 state = state * 6364136223846793005ULL + 1442695040888963407ULL;
600 double u1 = (double)(state >> 11) * 0x1.0p-53;
601 if (u1 < DBL_MIN) u1 = DBL_MIN;
602 state = state * 6364136223846793005ULL + 1442695040888963407ULL;
603 double u2 = (double)(state >> 11) * 0x1.0p-53;
604 output[i] = amplitude * sqrt(-2.0 * log(u1)) * cos(2.0 * M_PI * u2);
605 }
606}
607
608void MD_impulse(double *output, unsigned N, double amplitude, unsigned position)
609{
610 assert(output != nullptr);
611 assert(N > 0);
612 assert(position < N);
613 memset(output, 0, N * sizeof(double));
614 output[position] = amplitude;
615}
616
617void MD_chirp_linear(double *output, unsigned N, double amplitude,
618 double f_start, double f_end, double sample_rate)
619{
620 assert(output != nullptr);
621 assert(N > 0);
622 assert(sample_rate > 0.0);
623
624 if (N == 1) {
625 output[0] = 0.0;
626 return;
627 }
628
629 double T = (double)(N - 1) / sample_rate;
630 double chirp_rate = (f_end - f_start) / T;
631
632 for (unsigned i = 0; i < N; i++) {
633 double t = (double)i / sample_rate;
634 double phase = 2.0 * M_PI * (f_start * t + 0.5 * chirp_rate * t * t);
635 output[i] = amplitude * sin(phase);
636 }
637}
638
639void MD_chirp_log(double *output, unsigned N, double amplitude,
640 double f_start, double f_end, double sample_rate)
641{
642 assert(output != nullptr);
643 assert(N > 0);
644 assert(sample_rate > 0.0);
645 assert(f_start > 0.0);
646 assert(f_end > 0.0);
647 assert(f_start != f_end);
648
649 if (N == 1) {
650 output[0] = 0.0;
651 return;
652 }
653
654 double T = (double)(N - 1) / sample_rate;
655 double ratio = f_end / f_start;
656 double log_ratio = log(ratio);
657
658 for (unsigned i = 0; i < N; i++) {
659 double t = (double)i / sample_rate;
660 double phase = 2.0 * M_PI * f_start * T
661 * (pow(ratio, t / T) - 1.0) / log_ratio;
662 output[i] = amplitude * sin(phase);
663 }
664}
665
666void MD_square_wave(double *output, unsigned N, double amplitude,
667 double freq, double sample_rate)
668{
669 assert(output != nullptr);
670 assert(N > 0);
671 assert(sample_rate > 0.0);
672 double phase_step = 2.0 * M_PI * freq / sample_rate;
673 for (unsigned i = 0; i < N; i++) {
674 double phase = fmod(phase_step * (double)i, 2.0 * M_PI);
675 if (phase < 0.0) phase += 2.0 * M_PI;
676 if (phase == 0.0 || phase == M_PI)
677 output[i] = 0.0;
678 else if (phase < M_PI)
679 output[i] = amplitude;
680 else
681 output[i] = -amplitude;
682 }
683}
684
685void MD_sawtooth_wave(double *output, unsigned N, double amplitude,
686 double freq, double sample_rate)
687{
688 assert(output != nullptr);
689 assert(N > 0);
690 assert(sample_rate > 0.0);
691 double phase_step = 2.0 * M_PI * freq / sample_rate;
692 for (unsigned i = 0; i < N; i++) {
693 double phase = fmod(phase_step * (double)i, 2.0 * M_PI);
694 if (phase < 0.0) phase += 2.0 * M_PI;
695 output[i] = amplitude * (phase / M_PI - 1.0);
696 }
697}
698
699/* -----------------------------------------------------------------------
700 * Public API: FFT / Spectrum Analysis
701 * -----------------------------------------------------------------------*/
702
727void MD_magnitude_spectrum(const double *signal, unsigned N, double *mag_out)
728{
729 assert(signal != nullptr);
730 assert(mag_out != nullptr);
731 assert(N >= 2);
732
733 _spec_setup(N);
734
735 /* Copy input into the local buffer (FFTW may overwrite it) */
736 memcpy(_spec_in, signal, N * sizeof(double));
737
738 /* Execute the forward FFT (real -> complex) */
739 fftw_execute(_spec_plan);
740
741 /* Compute magnitude |X(k)| = sqrt(re^2 + im^2) for each bin */
742 unsigned num_bins = N / 2 + 1;
743 for (unsigned k = 0; k < num_bins; k++) {
744 mag_out[k] = cabs(_spec_out[k]);
745 }
746}
747
765void MD_power_spectral_density(const double *signal, unsigned N, double *psd_out)
766{
767 assert(signal != nullptr);
768 assert(psd_out != nullptr);
769 assert(N >= 2);
770
771 _spec_setup(N);
772
773 /* Copy input into the local buffer (FFTW may overwrite it) */
774 memcpy(_spec_in, signal, N * sizeof(double));
775
776 /* Execute the forward FFT (real -> complex) */
777 fftw_execute(_spec_plan);
778
779 /* Compute PSD[k] = |X(k)|^2 / N = (re^2 + im^2) / N.
780 * We use creal/cimag instead of cabs to avoid a redundant sqrt --
781 * cabs computes sqrt(re^2 + im^2), and we'd just square it again. */
782 unsigned num_bins = N / 2 + 1;
783 for (unsigned k = 0; k < num_bins; k++) {
784 double re = creal(_spec_out[k]);
785 double im = cimag(_spec_out[k]);
786 psd_out[k] = (re * re + im * im) / (double)N;
787 }
788}
789
807void MD_phase_spectrum(const double *signal, unsigned N, double *phase_out)
808{
809 assert(signal != nullptr);
810 assert(phase_out != nullptr);
811 assert(N >= 2);
812
813 _spec_setup(N);
814
815 /* Copy input into the local buffer (FFTW may overwrite it) */
816 memcpy(_spec_in, signal, N * sizeof(double));
817
818 /* Execute the forward FFT (real -> complex) */
819 fftw_execute(_spec_plan);
820
821 /* Compute phi(k) = atan2(Im(X(k)), Re(X(k))) for each bin.
822 * carg() from <complex.h> does exactly this and returns [-pi, pi]. */
823 unsigned num_bins = N / 2 + 1;
824 for (unsigned k = 0; k < num_bins; k++) {
825 phase_out[k] = carg(_spec_out[k]);
826 }
827}
828
835unsigned MD_stft_num_frames(unsigned signal_len, unsigned N, unsigned hop)
836{
837 if (signal_len < N) return 0;
838 return (signal_len - N) / hop + 1;
839}
840
860void MD_stft(const double *signal, unsigned signal_len,
861 unsigned N, unsigned hop, double *mag_out)
862{
863 assert(signal != nullptr);
864 assert(mag_out != nullptr);
865 assert(N >= 2);
866 assert(hop >= 1);
867
868 unsigned num_frames = MD_stft_num_frames(signal_len, N, hop);
869 if (num_frames == 0) return; /* signal too short for even one frame */
870
871 _stft_setup(N);
872 unsigned num_bins = N / 2 + 1;
873
874 for (unsigned f = 0; f < num_frames; f++) {
875 const double *src = signal + (size_t)f * hop; /* (size_t) avoids overflow */
876
877 /* Apply Hanning window directly into the shared input buffer.
878 * Touching every element anyway, so no separate memcpy is needed. */
879 for (unsigned n = 0; n < N; n++) {
880 _spec_in[n] = src[n] * _stft_win[n];
881 }
882
883 fftw_execute(_spec_plan);
884
885 /* Write magnitude row: |X_f(k)| = sqrt(re^2 + im^2).
886 * cabs() is correct here -- we need the actual sqrt magnitude,
887 * not |z|^2, so the creal/cimag shortcut from PSD does not apply. */
888 double *row = mag_out + (size_t)f * num_bins;
889 for (unsigned k = 0; k < num_bins; k++) {
890 row[k] = cabs(_spec_out[k]);
891 }
892 }
893}
894
895/* -----------------------------------------------------------------------
896 * Public API: GCC-PHAT delay estimation
897 * -----------------------------------------------------------------------*/
898
913void MD_get_multiple_delays(const double **sigs, unsigned M, unsigned N,
914 unsigned margin, int weightfunc,
915 int *outdelays)
916{
917 /* Static buffers are reused across calls for efficiency.
918 * They are only re-allocated when the signal length changes. */
919 static unsigned last_N = 0;
920 static double *hann_win = nullptr;
921 static double *t_ref = nullptr;
922 static double *t_sig = nullptr;
923
924 if (M < 2) return;
925
926 /* Re-allocate if the signal length has changed */
927 if (last_N != N) {
928 free(hann_win); /* free(nullptr) is safe in C */
929 free(t_ref);
930 free(t_sig);
931
932 hann_win = malloc(N * sizeof(double));
933 assert(hann_win != nullptr);
934 MD_Gen_Hann_Win(hann_win, N);
935
936 t_ref = malloc(N * sizeof(double));
937 assert(t_ref != nullptr);
938
939 t_sig = malloc(N * sizeof(double));
940 assert(t_sig != nullptr);
941
942 last_N = N;
943 }
944
945 /* Apply the Hanning window to the reference signal */
946 for (unsigned i = 0; i < N; i++) {
947 t_ref[i] = sigs[0][i] * hann_win[i];
948 }
949
950 /* Compute the delay for each non-reference signal */
951 for (unsigned i = 0; i < M - 1; i++) {
952 /* Window the comparison signal */
953 for (unsigned j = 0; j < N; j++) {
954 t_sig[j] = sigs[i + 1][j] * hann_win[j];
955 }
956 outdelays[i] = MD_get_delay(t_ref, t_sig, N, nullptr, margin, weightfunc);
957 }
958}
959
978int MD_get_delay(const double *siga, const double *sigb, unsigned N,
979 double *ent, unsigned margin, int weightfunc)
980{
981 _gcc_setup(N);
982
983 /* Compute the full cross-correlation */
984 MD_gcc(siga, sigb, N, lags_loc, weightfunc);
985
986 /* The zero-lag position in the shifted output */
987 unsigned center = (unsigned)ceil((double)N / 2.0);
988
989 /* Clamp the margin so we don't read outside the array */
990 unsigned m = margin;
991 if (center < m) {
992 m = center;
993 }
994 if (center + m >= N) {
995 m = (N - 1) - center;
996 }
997
998 /* Search the window [center-m, center+m] for the peak */
999 unsigned start = center - m;
1000 unsigned len = 2 * m + 1;
1001 double peak_val;
1002 unsigned peak_i;
1003
1004 _max_index(lags_loc + start, len, &peak_val, &peak_i);
1005
1006 /* Optionally compute the entropy of the search window */
1007 if (ent != nullptr) {
1008 *ent = MD_entropy(lags_loc + start, len, true);
1009 }
1010
1011 /* Convert the peak index to a signed delay relative to zero-lag */
1012 return (int)(peak_i) - (int)(m);
1013}
1014
1028void MD_gcc(const double *siga, const double *sigb, unsigned N,
1029 double *lagvals, int weightfunc)
1030{
1031 _gcc_setup(N);
1032
1033 /* Copy input into local buffers (FFTW may overwrite them) */
1034 memcpy(siga_loc, siga, _N * sizeof(double));
1035 memcpy(sigb_loc, sigb, _N * sizeof(double));
1036
1037 /* Forward FFT of both signals.
1038 *
1039 * For a real-valued input of length N, the FFT output has only
1040 * N/2 + 1 unique complex values (the rest are conjugate-symmetric).
1041 * FFTW's r2c transform exploits this and only writes N/2+1 values. */
1042 fftw_execute(pa);
1043 fftw_execute(pb);
1044
1045 /* Compute the weighted cross-spectrum.
1046 *
1047 * The cross-spectrum is: X[k] = FFT_B[k] * conj(FFT_A[k])
1048 *
1049 * For PHAT weighting, we divide by the magnitude of the cross-spectrum.
1050 * This keeps only the phase information, which produces a much sharper
1051 * peak in the time-domain correlation.
1052 *
1053 * We only loop over the N/2+1 non-redundant frequency bins. */
1054 unsigned num_bins = _N / 2 + 1;
1055
1056 switch (weightfunc) {
1057 case PHAT:
1058 for (unsigned i = 0; i < num_bins; i++) {
1059 double complex cs = fftb[i] * conj(ffta[i]);
1060 /* Divide by magnitude; add DBL_MIN to prevent division by zero */
1061 xspec[i] = cs / (cabs(cs) + DBL_MIN);
1062 }
1063 break;
1064
1065 default: /* SIMP: simple 1/N weighting */
1066 for (unsigned i = 0; i < num_bins; i++) {
1067 double complex cs = fftb[i] * conj(ffta[i]);
1068 xspec[i] = cs / (double)_N;
1069 }
1070 break;
1071 }
1072
1073 /* Inverse FFT of the cross-spectrum -> time-domain cross-correlation */
1074 fftw_execute(px);
1075
1076 /* Rearrange so the zero-lag is at the centre of the output array */
1077 _fftshift(xcorr, lagvals, _N);
1078}
void MD_chirp_linear(double *output, unsigned N, double amplitude, double f_start, double f_end, double sample_rate)
Generate a linear chirp (swept sine with linearly increasing frequency).
Definition minidsp.c:617
static void _xcorr_malloc(void)
Allocate all buffers needed for cross-correlation of length _N.
Definition minidsp.c:72
double MD_power(const double *a, unsigned N)
Signal power: energy divided by the number of samples.
Definition minidsp.c:333
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.
Definition minidsp.c:205
static void _xcorr_free(void)
Free all dynamically allocated FFT buffers.
Definition minidsp.c:56
void MD_power_spectral_density(const double *signal, unsigned N, double *psd_out)
Compute the power spectral density (PSD) of a real-valued signal.
Definition minidsp.c:765
static void _fftshift(const double *in, double *out, unsigned N)
Shift an FFT output so that the zero-lag is in the middle.
Definition minidsp.c:232
void MD_white_noise(double *output, unsigned N, double amplitude, unsigned seed)
Generate Gaussian white noise.
Definition minidsp.c:566
void MD_sine_wave(double *output, unsigned N, double amplitude, double freq, double sample_rate)
Generate a sine wave.
Definition minidsp.c:555
void MD_impulse(double *output, unsigned N, double amplitude, unsigned position)
Generate a discrete impulse (Kronecker delta).
Definition minidsp.c:608
void MD_scale_vec(double *in, double *out, unsigned N, double oldmin, double oldmax, double newmin, double newmax)
Apply MD_scale() to every element of a vector.
Definition minidsp.c:379
void MD_phase_spectrum(const double *signal, unsigned N, double *phase_out)
Compute the one-sided phase spectrum of a real-valued signal.
Definition minidsp.c:807
static void _xcorr_setup(void)
Allocate buffers and create FFTW plans for signals of length _N.
Definition minidsp.c:109
double MD_scale(double in, double oldmin, double oldmax, double newmin, double newmax)
Linearly map a value from one range to another.
Definition minidsp.c:369
void MD_Gen_Hann_Win(double *out, unsigned n)
Generate a Hanning (raised cosine) window.
Definition minidsp.c:543
double MD_energy(const double *a, unsigned N)
Signal energy: the sum of squared samples.
Definition minidsp.c:318
static void _spec_free(void)
Free spectrum analysis buffers.
Definition minidsp.c:158
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.
Definition minidsp.c:860
static void _xcorr_teardown(void)
Destroy existing FFTW plans and free all buffers.
Definition minidsp.c:88
static void _stft_teardown(void)
Free the cached STFT Hanning window.
Definition minidsp.c:194
static void _gcc_setup(unsigned N)
Ensure the cached FFT setup matches the requested signal length.
Definition minidsp.c:126
static void _spec_teardown(void)
Tear down spectrum analysis plan and buffers.
Definition minidsp.c:167
double MD_entropy(const double *a, unsigned N, bool clip)
Compute the normalised entropy of a distribution.
Definition minidsp.c:483
static void _spec_setup(unsigned N)
Ensure the spectrum FFT cache matches the requested length.
Definition minidsp.c:178
void MD_shutdown(void)
Free all internally cached FFT plans and buffers.
Definition minidsp.c:276
double MD_power_db(const double *a, unsigned N)
Signal power expressed in decibels (dB).
Definition minidsp.c:349
void MD_gcc(const double *siga, const double *sigb, unsigned N, double *lagvals, int weightfunc)
Compute the Generalized Cross-Correlation between two signals.
Definition minidsp.c:1028
void MD_get_multiple_delays(const double **sigs, unsigned M, unsigned N, unsigned margin, int weightfunc, int *outdelays)
Estimate delays between a reference signal and several other signals.
Definition minidsp.c:913
double MD_dot(const double *a, const double *b, unsigned N)
Dot product of two vectors a and b, each of length N.
Definition minidsp.c:298
void MD_sawtooth_wave(double *output, unsigned N, double amplitude, double freq, double sample_rate)
Generate a sawtooth wave.
Definition minidsp.c:685
void MD_magnitude_spectrum(const double *signal, unsigned N, double *mag_out)
Compute the magnitude spectrum of a real-valued signal.
Definition minidsp.c:727
static void _max_index(const double *a, unsigned N, double *max, unsigned *maxi)
Find the index and value of the maximum element in an array.
Definition minidsp.c:252
unsigned MD_stft_num_frames(unsigned signal_len, unsigned N, unsigned hop)
Compute the number of complete STFT frames for the given parameters.
Definition minidsp.c:835
void MD_fit_within_range(double *in, double *out, unsigned N, double newmin, double newmax)
Squeeze values into [newmin, newmax] only if they don't already fit.
Definition minidsp.c:402
void MD_square_wave(double *output, unsigned N, double amplitude, double freq, double sample_rate)
Generate a square wave.
Definition minidsp.c:666
int MD_get_delay(const double *siga, const double *sigb, unsigned N, double *ent, unsigned margin, int weightfunc)
Estimate the delay (in samples) between two signals.
Definition minidsp.c:978
void MD_adjust_dblevel(const double *in, double *out, unsigned N, double dblevel)
Automatic Gain Control (AGC): adjust a signal to a target dB level.
Definition minidsp.c:438
void MD_chirp_log(double *output, unsigned N, double amplitude, double f_start, double f_end, double sample_rate)
Generate a logarithmic chirp (swept sine with exponentially increasing frequency).
Definition minidsp.c:639
A mini library of DSP (Digital Signal Processing) routines.
@ PHAT
Phase Transform weighting (sharper peaks, more robust to noise)
Definition minidsp.h:565