miniDSP
A small C library for audio DSP
Loading...
Searching...
No Matches
minidsp_gcc.c
Go to the documentation of this file.
1
24#include "minidsp.h"
25#include "minidsp_internal.h"
26
27/* -----------------------------------------------------------------------
28 * Static (file-scope) variables for FFT caching
29 *
30 * Creating FFTW plans is expensive, so we cache them. As long as the
31 * signal length N stays the same between calls, we reuse the existing
32 * plans and buffers. If N changes, we tear everything down and rebuild.
33 * -----------------------------------------------------------------------*/
34
35static unsigned _N = 0; /* Current cached signal length */
36static double *siga_loc = nullptr; /* Local copy of input signal A */
37static double *sigb_loc = nullptr; /* Local copy of input signal B */
38static double *lags_loc = nullptr; /* Shifted cross-correlation output */
39static fftw_complex *ffta = nullptr; /* FFT of signal A */
40static fftw_complex *fftb = nullptr; /* FFT of signal B */
41static fftw_complex *xspec = nullptr; /* Cross-spectrum (FFT_B * conj(FFT_A))*/
42static double *xcorr = nullptr; /* Raw cross-correlation (time domain) */
43
44static fftw_plan pa = nullptr; /* FFTW plan: signal A -> FFT */
45static fftw_plan pb = nullptr; /* FFTW plan: signal B -> FFT */
46static fftw_plan px = nullptr; /* FFTW plan: cross-spectrum -> IFFT */
47
48/* -----------------------------------------------------------------------
49 * Internal helpers for FFT buffer management
50 * -----------------------------------------------------------------------*/
51
53static void _xcorr_free(void)
54{
55 if (xspec) fftw_free(xspec);
56 if (fftb) fftw_free(fftb);
57 if (ffta) fftw_free(ffta);
58
59 if (lags_loc) free(lags_loc);
60 if (xcorr) free(xcorr);
61 if (sigb_loc) free(sigb_loc);
62 if (siga_loc) free(siga_loc);
63
64 xspec = nullptr; fftb = nullptr; ffta = nullptr;
65 lags_loc = nullptr; xcorr = nullptr; sigb_loc = nullptr; siga_loc = nullptr;
66}
67
69static void _xcorr_malloc(void)
70{
71 siga_loc = calloc(_N, sizeof(double));
72 sigb_loc = calloc(_N, sizeof(double));
73 xcorr = calloc(_N + 1, sizeof(double)); /* FFTW c2r needs N+1 */
74 lags_loc = calloc(_N, sizeof(double));
75
76 /* FFTW provides its own allocator that guarantees proper alignment
77 * for SIMD instructions (SSE2, AVX, etc.). Always use it for
78 * fftw_complex arrays. */
79 ffta = fftw_alloc_complex(_N);
80 fftb = fftw_alloc_complex(_N);
81 xspec = fftw_alloc_complex(_N);
82}
83
91static void _xcorr_teardown(void)
92{
93 if (pa) fftw_destroy_plan(pa);
94 if (pb) fftw_destroy_plan(pb);
95 if (px) fftw_destroy_plan(px);
96 pa = nullptr; pb = nullptr; px = nullptr;
97
99}
100
112static void _xcorr_setup(void)
113{
116
117 /* FFTW_ESTIMATE: pick a reasonable plan quickly (don't benchmark).
118 * FFTW_DESTROY_INPUT: FFTW may overwrite the input array -- that's
119 * fine because we always copy the data into our local buffers first. */
120 pa = fftw_plan_dft_r2c_1d(_N, siga_loc, ffta, FFTW_ESTIMATE | FFTW_DESTROY_INPUT);
121 pb = fftw_plan_dft_r2c_1d(_N, sigb_loc, fftb, FFTW_ESTIMATE | FFTW_DESTROY_INPUT);
122 px = fftw_plan_dft_c2r_1d(_N, xspec, xcorr, FFTW_ESTIMATE | FFTW_DESTROY_INPUT);
123}
124
129static void _gcc_setup(unsigned N)
130{
131 if (_N != N) {
132 _N = N;
133 _xcorr_setup();
134 }
135}
136
137/* -----------------------------------------------------------------------
138 * Cross-file teardown wrapper (declared in minidsp_internal.h)
139 *
140 * MD_shutdown() in minidsp_spectrum.c calls this to tear down the GCC
141 * cache. Unlike _xcorr_teardown() (which must NOT reset _N because
142 * _xcorr_setup sets _N before calling it), this wrapper safely resets
143 * _N because it is only called at shutdown time.
144 * -----------------------------------------------------------------------*/
145
147{
149 _N = 0;
150}
151
152/* -----------------------------------------------------------------------
153 * Internal helpers for cross-correlation post-processing
154 * -----------------------------------------------------------------------*/
155
167static void _fftshift(const double *in, double *out, unsigned N)
168{
169 /* The split point: how many elements are in the "second half" */
170 unsigned half = (unsigned)floor((double)N / 2.0);
171
172 /* Copy the second half of in[] (negative lags) to the start of out[] */
173 memcpy(out, &in[half], sizeof(double) * (N - half));
174
175 /* Copy the first half of in[] (positive lags) to the end of out[] */
176 memcpy(&out[N - half], in, sizeof(double) * half);
177}
178
187static void _max_index(const double *a, unsigned N,
188 double *max, unsigned *maxi)
189{
190 assert(a != nullptr);
191 assert(N >= 1);
192
193 unsigned best_i = 0;
194 double best_v = a[0];
195
196 for (unsigned i = 1; i < N; i++) {
197 if (a[i] > best_v) {
198 best_v = a[i];
199 best_i = i;
200 }
201 }
202
203 *max = best_v;
204 *maxi = best_i;
205}
206
207/* -----------------------------------------------------------------------
208 * Public API: GCC-PHAT delay estimation
209 * -----------------------------------------------------------------------*/
210
225void MD_get_multiple_delays(const double **sigs, unsigned M, unsigned N,
226 unsigned margin, int weightfunc,
227 int *outdelays)
228{
229 /* Static buffers are reused across calls for efficiency.
230 * They are only re-allocated when the signal length changes. */
231 static unsigned last_N = 0;
232 static double *hann_win = nullptr;
233 static double *t_ref = nullptr;
234 static double *t_sig = nullptr;
235
236 if (M < 2) return;
237
238 /* Re-allocate if the signal length has changed */
239 if (last_N != N) {
240 free(hann_win); /* free(nullptr) is safe in C */
241 free(t_ref);
242 free(t_sig);
243
244 hann_win = malloc(N * sizeof(double));
245 assert(hann_win != nullptr);
246 MD_Gen_Hann_Win(hann_win, N);
247
248 t_ref = malloc(N * sizeof(double));
249 assert(t_ref != nullptr);
250
251 t_sig = malloc(N * sizeof(double));
252 assert(t_sig != nullptr);
253
254 last_N = N;
255 }
256
257 /* Apply the Hanning window to the reference signal */
258 for (unsigned i = 0; i < N; i++) {
259 t_ref[i] = sigs[0][i] * hann_win[i];
260 }
261
262 /* Compute the delay for each non-reference signal */
263 for (unsigned i = 0; i < M - 1; i++) {
264 /* Window the comparison signal */
265 for (unsigned j = 0; j < N; j++) {
266 t_sig[j] = sigs[i + 1][j] * hann_win[j];
267 }
268 outdelays[i] = MD_get_delay(t_ref, t_sig, N, nullptr, margin, weightfunc);
269 }
270}
271
290int MD_get_delay(const double *siga, const double *sigb, unsigned N,
291 double *ent, unsigned margin, int weightfunc)
292{
293 _gcc_setup(N);
294
295 /* Compute the full cross-correlation */
296 MD_gcc(siga, sigb, N, lags_loc, weightfunc);
297
298 /* The zero-lag position in the shifted output */
299 unsigned center = (unsigned)ceil((double)N / 2.0);
300
301 /* Clamp the margin so we don't read outside the array */
302 unsigned m = margin;
303 if (center < m) {
304 m = center;
305 }
306 if (center + m >= N) {
307 m = (N - 1) - center;
308 }
309
310 /* Search the window [center-m, center+m] for the peak */
311 unsigned start = center - m;
312 unsigned len = 2 * m + 1;
313 double peak_val;
314 unsigned peak_i;
315
316 _max_index(lags_loc + start, len, &peak_val, &peak_i);
317
318 /* Optionally compute the entropy of the search window */
319 if (ent != nullptr) {
320 *ent = MD_entropy(lags_loc + start, len, true);
321 }
322
323 /* Convert the peak index to a signed delay relative to zero-lag */
324 return (int)(peak_i) - (int)(m);
325}
326
340void MD_gcc(const double *siga, const double *sigb, unsigned N,
341 double *lagvals, int weightfunc)
342{
343 _gcc_setup(N);
344
345 /* Copy input into local buffers (FFTW may overwrite them) */
346 memcpy(siga_loc, siga, _N * sizeof(double));
347 memcpy(sigb_loc, sigb, _N * sizeof(double));
348
349 /* Forward FFT of both signals.
350 *
351 * For a real-valued input of length N, the FFT output has only
352 * N/2 + 1 unique complex values (the rest are conjugate-symmetric).
353 * FFTW's r2c transform exploits this and only writes N/2+1 values. */
354 fftw_execute(pa);
355 fftw_execute(pb);
356
357 /* Compute the weighted cross-spectrum.
358 *
359 * The cross-spectrum is: X[k] = FFT_B[k] * conj(FFT_A[k])
360 *
361 * For PHAT weighting, we divide by the magnitude of the cross-spectrum.
362 * This keeps only the phase information, which produces a much sharper
363 * peak in the time-domain correlation.
364 *
365 * We only loop over the N/2+1 non-redundant frequency bins. */
366 unsigned num_bins = _N / 2 + 1;
367
368 switch (weightfunc) {
369 case PHAT:
370 for (unsigned i = 0; i < num_bins; i++) {
371 double complex cs = fftb[i] * conj(ffta[i]);
372 /* Divide by magnitude; add DBL_MIN to prevent division by zero */
373 xspec[i] = cs / (cabs(cs) + DBL_MIN);
374 }
375 break;
376
377 default: /* SIMP: simple 1/N weighting */
378 for (unsigned i = 0; i < num_bins; i++) {
379 double complex cs = fftb[i] * conj(ffta[i]);
380 xspec[i] = cs / (double)_N;
381 }
382 break;
383 }
384
385 /* Inverse FFT of the cross-spectrum -> time-domain cross-correlation */
386 fftw_execute(px);
387
388 /* Rearrange so the zero-lag is at the centre of the output array */
389 _fftshift(xcorr, lagvals, _N);
390}
A mini library of DSP (Digital Signal Processing) routines.
@ PHAT
Phase Transform weighting (sharper peaks, more robust to noise)
Definition minidsp.h:1237
void MD_Gen_Hann_Win(double *out, unsigned n)
Generate a Hanning (Hann) window of length n.
double MD_entropy(const double *a, unsigned N, bool clip)
Compute the normalized entropy of a distribution.
static void _xcorr_malloc(void)
Allocate all buffers needed for cross-correlation of length _N.
Definition minidsp_gcc.c:69
static void _xcorr_free(void)
Free all dynamically allocated FFT buffers.
Definition minidsp_gcc.c:53
static void _fftshift(const double *in, double *out, unsigned N)
Shift an FFT output so that the zero-lag is in the middle.
static void _xcorr_setup(void)
Allocate buffers and create FFTW plans for signals of length _N.
static void _xcorr_teardown(void)
Destroy existing FFTW plans and free all buffers.
Definition minidsp_gcc.c:91
static void _gcc_setup(unsigned N)
Ensure the cached FFT setup matches the requested signal length.
void MD_gcc(const double *siga, const double *sigb, unsigned N, double *lagvals, int weightfunc)
Compute the Generalized Cross-Correlation between two signals.
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.
void md_gcc_teardown(void)
Tear down the GCC-PHAT FFT cache and reset its cached length.
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.
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.
Internal header for cross-file dependencies within the minidsp module.