miniDSP
A small C library for audio DSP
Loading...
Searching...
No Matches
minidsp_core.c
Go to the documentation of this file.
1
9
10#include "minidsp.h"
11#include "minidsp_internal.h"
12
13/* -----------------------------------------------------------------------
14 * Public API: basic signal measurements
15 * -----------------------------------------------------------------------*/
16
26double MD_dot(const double *a, const double *b, unsigned N)
27{
28 double d = 0.0;
29 for (unsigned i = 0; i < N; i++) {
30 d += a[i] * b[i];
31 }
32 return d;
33}
34
46double MD_energy(const double *a, unsigned N)
47{
48 MD_CHECK(a != NULL, MD_ERR_NULL_POINTER, "a is NULL", 0.0);
49 if (N == 1) return a[0] * a[0];
50 return MD_dot(a, a, N);
51}
52
61double MD_power(const double *a, unsigned N)
62{
63 MD_CHECK(a != NULL, MD_ERR_NULL_POINTER, "a is NULL", 0.0);
64 MD_CHECK(N > 0, MD_ERR_INVALID_SIZE, "N must be > 0", 0.0);
65 return MD_energy(a, N) / (double)N;
66}
67
77double MD_power_db(const double *a, unsigned N)
78{
79 MD_CHECK(a != NULL, MD_ERR_NULL_POINTER, "a is NULL", 0.0);
80 MD_CHECK(N > 0, MD_ERR_INVALID_SIZE, "N must be > 0", 0.0);
81 double p = fmax(1.0e-10, MD_power(a, N));
82 return 10.0 * log10(p);
83}
84
85/* -----------------------------------------------------------------------
86 * Public API: signal scaling and conditioning
87 * -----------------------------------------------------------------------*/
88
97double MD_scale(double in,
98 double oldmin, double oldmax,
99 double newmin, double newmax)
100{
101 return (in - oldmin) * (newmax - newmin) / (oldmax - oldmin) + newmin;
102}
103
107void MD_scale_vec(double *in, double *out, unsigned N,
108 double oldmin, double oldmax,
109 double newmin, double newmax)
110{
111 MD_CHECK_VOID(in != NULL, MD_ERR_NULL_POINTER, "in is NULL");
112 MD_CHECK_VOID(out != NULL, MD_ERR_NULL_POINTER, "out is NULL");
113 MD_CHECK_VOID(oldmin < oldmax, MD_ERR_INVALID_RANGE, "oldmin must be < oldmax");
114 MD_CHECK_VOID(newmin < newmax, MD_ERR_INVALID_RANGE, "newmin must be < newmax");
115 if (N == 0) return;
116
117 double scale = (newmax - newmin) / (oldmax - oldmin);
118 for (unsigned i = 0; i < N; i++) {
119 out[i] = (in[i] - oldmin) * scale + newmin;
120 }
121}
122
130void MD_fit_within_range(double *in, double *out, unsigned N,
131 double newmin, double newmax)
132{
133 MD_CHECK_VOID(in != NULL, MD_ERR_NULL_POINTER, "in is NULL");
134 MD_CHECK_VOID(out != NULL, MD_ERR_NULL_POINTER, "out is NULL");
135 MD_CHECK_VOID(newmin < newmax, MD_ERR_INVALID_RANGE, "newmin must be < newmax");
136 if (N == 0) return;
137
138 /* Find the actual min and max of the input */
139 double in_min = in[0];
140 double in_max = in[0];
141 for (unsigned i = 1; i < N; i++) {
142 if (in[i] < in_min) in_min = in[i];
143 if (in[i] > in_max) in_max = in[i];
144 }
145
146 if (in_min >= newmin && in_max <= newmax) {
147 /* Already fits -- just copy without scaling */
148 for (unsigned i = 0; i < N; i++) {
149 out[i] = in[i];
150 }
151 } else {
152 MD_scale_vec(in, out, N, in_min, in_max, newmin, newmax);
153 }
154}
155
166void MD_adjust_dblevel(const double *in, double *out,
167 unsigned N, double dblevel)
168{
169 /* Convert the target dB level back to linear power */
170 double desired_power = pow(10.0, dblevel / 10.0);
171
172 /* Compute the gain factor:
173 * We want: (1/N) * sum(out[i]^2) = desired_power
174 * Since out[i] = in[i] * gain:
175 * (gain^2 / N) * sum(in[i]^2) = desired_power
176 * gain = sqrt(desired_power * N / energy_in) */
177 double input_energy = MD_energy(in, N);
178
179 /* Guard: cannot amplify silence — copy input and return */
180 if (input_energy == 0.0) {
181 for (unsigned i = 0; i < N; i++) out[i] = in[i];
182 return;
183 }
184
185 double gain = sqrt((desired_power * (double)N) / input_energy);
186
187 /* Apply the gain and check for out-of-range values */
188 bool out_of_range = false;
189 for (unsigned i = 0; i < N; i++) {
190 out[i] = in[i] * gain;
191 if (out[i] > 1.0 || out[i] < -1.0) {
192 out_of_range = true;
193 }
194 }
195
196 /* If any samples exceed [-1.0, 1.0], rescale everything to fit */
197 if (out_of_range) {
198 MD_fit_within_range(out, out, N, -1.0, 1.0);
199 }
200}
201
218double MD_entropy(const double *a, unsigned N, bool clip)
219{
220 MD_CHECK(a != NULL, MD_ERR_NULL_POINTER, "a is NULL", 0.0);
221
222 if (N <= 1) return 0.0;
223
224 /* Maximum possible entropy for N bins (uniform distribution) */
225 double max_entropy = log2((double)N);
226
227 /* First pass: compute the total (for normalisation into probabilities) */
228 double total = 0.0;
229 if (clip) {
230 for (unsigned i = 0; i < N; i++) {
231 total += (a[i] < 0.0) ? 0.0 : a[i];
232 }
233 } else {
234 for (unsigned i = 0; i < N; i++) {
235 total += a[i] * a[i];
236 }
237 }
238
239 /* If the total is zero (e.g., all-zeros input), entropy is undefined.
240 * We return 0.0 because there is no meaningful distribution. */
241 if (total == 0.0) return 0.0;
242
243 /* Second pass: compute H = -SUM( p_i * log2(p_i) ) */
244 double entropy = 0.0;
245 for (unsigned i = 0; i < N; i++) {
246 if (a[i] == 0.0) continue;
247 if (clip && a[i] < 0.0) continue;
248
249 double p;
250 if (clip) {
251 p = a[i] / total;
252 } else {
253 p = (a[i] * a[i]) / total;
254 }
255
256 entropy += p * log2(p); /* Note: p*log2(p) is always <= 0 */
257 }
258
259 /* Negate and normalise so the result is in [0, 1] */
260 return -entropy / max_entropy;
261}
262
263/* -----------------------------------------------------------------------
264 * Public API: signal analysis
265 * -----------------------------------------------------------------------*/
266
268#define MD_F0_ACF_PEAK_THRESHOLD 0.15
269
272static double md_parabolic_offset(double y_left, double y_mid, double y_right)
273{
274 double denom = y_left - 2.0 * y_mid + y_right;
275 if (fabs(denom) < 1e-12) return 0.0;
276
277 double delta = 0.5 * (y_left - y_right) / denom;
278 if (delta < -0.5) delta = -0.5;
279 if (delta > 0.5) delta = 0.5;
280 return delta;
281}
282
288double MD_rms(const double *a, unsigned N)
289{
290 MD_CHECK(a != NULL, MD_ERR_NULL_POINTER, "a is NULL", 0.0);
291 MD_CHECK(N > 0, MD_ERR_INVALID_SIZE, "N must be > 0", 0.0);
292 return sqrt(MD_power(a, N));
293}
294
301double MD_zero_crossing_rate(const double *a, unsigned N)
302{
303 MD_CHECK(a != NULL, MD_ERR_NULL_POINTER, "a is NULL", 0.0);
304 MD_CHECK(N > 1, MD_ERR_INVALID_SIZE, "N must be > 1", 0.0);
305 unsigned crossings = 0;
306 for (unsigned i = 1; i < N; i++) {
307 if ((a[i] < 0.0) != (a[i - 1] < 0.0))
308 crossings++;
309 }
310 return (double)crossings / (double)(N - 1);
311}
312
321void MD_autocorrelation(const double *a, unsigned N,
322 double *out, unsigned max_lag)
323{
324 MD_CHECK_VOID(a != NULL, MD_ERR_NULL_POINTER, "a is NULL");
325 MD_CHECK_VOID(out != NULL, MD_ERR_NULL_POINTER, "out is NULL");
326 MD_CHECK_VOID(N > 0, MD_ERR_INVALID_SIZE, "N must be > 0");
327 MD_CHECK_VOID(max_lag > 0 && max_lag < N, MD_ERR_INVALID_RANGE, "max_lag must be in (0, N)");
328
329 double r0 = MD_energy(a, N);
330 if (r0 == 0.0) {
331 memset(out, 0, max_lag * sizeof(double));
332 return;
333 }
334 for (unsigned tau = 0; tau < max_lag; tau++) {
335 out[tau] = MD_dot(a, a + tau, N - tau) / r0;
336 }
337}
338
349void MD_peak_detect(const double *a, unsigned N, double threshold,
350 unsigned min_distance, unsigned *peaks_out,
351 unsigned *num_peaks_out)
352{
353 MD_CHECK_VOID(a != NULL, MD_ERR_NULL_POINTER, "a is NULL");
354 MD_CHECK_VOID(peaks_out != NULL, MD_ERR_NULL_POINTER, "peaks_out is NULL");
355 MD_CHECK_VOID(num_peaks_out != NULL, MD_ERR_NULL_POINTER, "num_peaks_out is NULL");
356 MD_CHECK_VOID(min_distance >= 1, MD_ERR_INVALID_SIZE, "min_distance must be >= 1");
357
358 unsigned count = 0;
359 unsigned last_peak = 0;
360
361 for (unsigned i = 1; i + 1 < N; i++) {
362 if (a[i] > a[i - 1] && a[i] > a[i + 1] && a[i] >= threshold) {
363 if (count == 0 || i - last_peak >= min_distance) {
364 peaks_out[count++] = i;
365 last_peak = i;
366 }
367 }
368 }
369 *num_peaks_out = count;
370}
371
372double MD_f0_autocorrelation(const double *signal, unsigned N,
373 double sample_rate,
374 double min_freq_hz, double max_freq_hz)
375{
376 MD_CHECK(signal != NULL, MD_ERR_NULL_POINTER, "signal is NULL", 0.0);
377 MD_CHECK(N >= 2, MD_ERR_INVALID_SIZE, "N must be >= 2", 0.0);
378 MD_CHECK(sample_rate > 0.0, MD_ERR_INVALID_RANGE, "sample_rate must be > 0", 0.0);
379 MD_CHECK(min_freq_hz > 0.0, MD_ERR_INVALID_RANGE, "min_freq_hz must be > 0", 0.0);
380 MD_CHECK(max_freq_hz > min_freq_hz, MD_ERR_INVALID_RANGE, "max_freq_hz must be > min_freq_hz", 0.0);
381
382 unsigned lag_min = (unsigned)floor(sample_rate / max_freq_hz);
383 unsigned lag_max = (unsigned)ceil(sample_rate / min_freq_hz);
384
385 if (lag_min < 1) lag_min = 1;
386 if (lag_max > N - 2) lag_max = N - 2;
387 if (lag_min > lag_max) return 0.0;
388 if (lag_max < 2) return 0.0; /* need lag-1 and lag+1 neighbors */
389
390 double *acf = malloc((lag_max + 1) * sizeof(double));
391 if (!acf) return 0.0;
392
393 MD_autocorrelation(signal, N, acf, lag_max + 1);
394
395 unsigned start = (lag_min < 1) ? 1 : lag_min;
396 unsigned stop = (lag_max > N - 2) ? (N - 2) : lag_max;
397
398 double best_peak = -DBL_MAX;
399 unsigned best_lag = 0;
400
401 for (unsigned lag = start; lag <= stop; lag++) {
402 double y = acf[lag];
403 if (y < MD_F0_ACF_PEAK_THRESHOLD) continue;
404 if (y <= acf[lag - 1] || y <= acf[lag + 1]) continue;
405
406 if (y > best_peak) {
407 best_peak = y;
408 best_lag = lag;
409 }
410 }
411
412 if (best_lag == 0) {
413 free(acf);
414 return 0.0;
415 }
416
417 double lag_est = (double)best_lag;
418 if (best_lag > 0 && best_lag + 1 <= lag_max) {
419 lag_est += md_parabolic_offset(acf[best_lag - 1],
420 acf[best_lag],
421 acf[best_lag + 1]);
422 }
423 free(acf);
424
425 if (lag_est <= 0.0) return 0.0;
426 return sample_rate / lag_est;
427}
428
436void MD_mix(const double *a, const double *b, double *out,
437 unsigned N, double w_a, double w_b)
438{
439 MD_CHECK_VOID(a != NULL, MD_ERR_NULL_POINTER, "a is NULL");
440 MD_CHECK_VOID(b != NULL, MD_ERR_NULL_POINTER, "b is NULL");
441 MD_CHECK_VOID(out != NULL, MD_ERR_NULL_POINTER, "out is NULL");
442 for (unsigned i = 0; i < N; i++) {
443 out[i] = w_a * a[i] + w_b * b[i];
444 }
445}
446
447/* -----------------------------------------------------------------------
448 * Public API: simple effects
449 * -----------------------------------------------------------------------*/
450
451void MD_delay_echo(const double *in, double *out, unsigned N,
452 unsigned delay_samples, double feedback,
453 double dry, double wet)
454{
455 MD_CHECK_VOID(in != NULL, MD_ERR_NULL_POINTER, "in is NULL");
456 MD_CHECK_VOID(out != NULL, MD_ERR_NULL_POINTER, "out is NULL");
457 MD_CHECK_VOID(N > 0, MD_ERR_INVALID_SIZE, "N must be > 0");
458 MD_CHECK_VOID(delay_samples > 0, MD_ERR_INVALID_SIZE, "delay_samples must be > 0");
459 MD_CHECK_VOID(fabs(feedback) < 1.0, MD_ERR_INVALID_RANGE, "feedback must be in (-1, 1)");
460
461 double *delay = calloc(delay_samples, sizeof(double));
462 MD_CHECK_VOID(delay != NULL, MD_ERR_ALLOC_FAILED, "calloc failed");
463
464 unsigned idx = 0;
465 for (unsigned n = 0; n < N; n++) {
466 double x = in[n];
467 double d = delay[idx];
468 out[n] = dry * x + wet * d;
469 delay[idx] = x + feedback * d;
470 idx++;
471 if (idx == delay_samples) idx = 0;
472 }
473
474 free(delay);
475}
476
477void MD_tremolo(const double *in, double *out, unsigned N,
478 double rate_hz, double depth, double sample_rate)
479{
480 MD_CHECK_VOID(in != NULL, MD_ERR_NULL_POINTER, "in is NULL");
481 MD_CHECK_VOID(out != NULL, MD_ERR_NULL_POINTER, "out is NULL");
482 MD_CHECK_VOID(N > 0, MD_ERR_INVALID_SIZE, "N must be > 0");
483 MD_CHECK_VOID(sample_rate > 0.0, MD_ERR_INVALID_RANGE, "sample_rate must be > 0");
484 MD_CHECK_VOID(rate_hz >= 0.0, MD_ERR_INVALID_RANGE, "rate_hz must be >= 0");
485 MD_CHECK_VOID(depth >= 0.0 && depth <= 1.0, MD_ERR_INVALID_RANGE, "depth must be in [0, 1]");
486
487 double phase_step = 2.0 * M_PI * rate_hz / sample_rate;
488 for (unsigned n = 0; n < N; n++) {
489 double lfo = 0.5 * (1.0 + sin(phase_step * (double)n));
490 double gain = (1.0 - depth) + depth * lfo;
491 out[n] = in[n] * gain;
492 }
493}
494
495void MD_comb_reverb(const double *in, double *out, unsigned N,
496 unsigned delay_samples, double feedback,
497 double dry, double wet)
498{
499 MD_CHECK_VOID(in != NULL, MD_ERR_NULL_POINTER, "in is NULL");
500 MD_CHECK_VOID(out != NULL, MD_ERR_NULL_POINTER, "out is NULL");
501 MD_CHECK_VOID(N > 0, MD_ERR_INVALID_SIZE, "N must be > 0");
502 MD_CHECK_VOID(delay_samples > 0, MD_ERR_INVALID_SIZE, "delay_samples must be > 0");
503 MD_CHECK_VOID(fabs(feedback) < 1.0, MD_ERR_INVALID_RANGE, "feedback must be in (-1, 1)");
504
505 double *comb = calloc(delay_samples, sizeof(double));
506 MD_CHECK_VOID(comb != NULL, MD_ERR_ALLOC_FAILED, "calloc failed");
507
508 unsigned idx = 0;
509 for (unsigned n = 0; n < N; n++) {
510 double x = in[n];
511 double delayed = comb[idx];
512 double y_comb = x + feedback * delayed;
513 comb[idx] = y_comb;
514 out[n] = dry * x + wet * y_comb;
515 idx++;
516 if (idx == delay_samples) idx = 0;
517 }
518
519 free(comb);
520}
521
522/* -----------------------------------------------------------------------
523 * Public API: math utilities
524 * -----------------------------------------------------------------------*/
525
532double MD_bessel_i0(double x)
533{
534 double sum = 1.0;
535 double term = 1.0;
536 double half_x = x / 2.0;
537
538 for (unsigned k = 1; k < 300; k++) {
539 term *= (half_x / (double)k);
540 double term_sq = term * term;
541 sum += term_sq;
542 if (term_sq < 1e-15 * sum) break;
543 }
544 return sum;
545}
546
552double MD_sinc(double x)
553{
554 if (fabs(x) < 1e-12) return 1.0;
555 double px = M_PI * x;
556 return sin(px) / px;
557}
558
559/* -----------------------------------------------------------------------
560 * Public API: window generation
561 * -----------------------------------------------------------------------*/
562
574void MD_Gen_Hann_Win(double *out, unsigned n)
575{
576 MD_CHECK_VOID(out != NULL, MD_ERR_NULL_POINTER, "out is NULL");
577 MD_CHECK_VOID(n > 0, MD_ERR_INVALID_SIZE, "n must be > 0");
578
579 if (n == 1) {
580 out[0] = 1.0;
581 return;
582 }
583
584 double n_minus_1 = (double)(n - 1);
585 for (unsigned i = 0; i < n; i++) {
586 out[i] = 0.5 * (1.0 - cos(2.0 * M_PI * (double)i / n_minus_1));
587 }
588}
589
595void MD_Gen_Hamming_Win(double *out, unsigned n)
596{
597 MD_CHECK_VOID(out != NULL, MD_ERR_NULL_POINTER, "out is NULL");
598 MD_CHECK_VOID(n > 0, MD_ERR_INVALID_SIZE, "n must be > 0");
599
600 if (n == 1) {
601 out[0] = 1.0;
602 return;
603 }
604
605 double n_minus_1 = (double)(n - 1);
606 for (unsigned i = 0; i < n; i++) {
607 out[i] = 0.54 - 0.46 * cos(2.0 * M_PI * (double)i / n_minus_1);
608 }
609}
610
619void MD_Gen_Blackman_Win(double *out, unsigned n)
620{
621 MD_CHECK_VOID(out != NULL, MD_ERR_NULL_POINTER, "out is NULL");
622 MD_CHECK_VOID(n > 0, MD_ERR_INVALID_SIZE, "n must be > 0");
623
624 if (n == 1) {
625 out[0] = 1.0;
626 return;
627 }
628
629 double n_minus_1 = (double)(n - 1);
630 for (unsigned i = 0; i < n; i++) {
631 double p = 2.0 * M_PI * (double)i / n_minus_1;
632 out[i] = 0.42 - 0.5 * cos(p) + 0.08 * cos(2.0 * p);
633 }
634}
635
639void MD_Gen_Rect_Win(double *out, unsigned n)
640{
641 MD_CHECK_VOID(out != NULL, MD_ERR_NULL_POINTER, "out is NULL");
642 MD_CHECK_VOID(n > 0, MD_ERR_INVALID_SIZE, "n must be > 0");
643 for (unsigned i = 0; i < n; i++) {
644 out[i] = 1.0;
645 }
646}
647
658void MD_Gen_Kaiser_Win(double *out, unsigned n, double beta)
659{
660 MD_CHECK_VOID(out != NULL, MD_ERR_NULL_POINTER, "out is NULL");
661 MD_CHECK_VOID(n > 0, MD_ERR_INVALID_SIZE, "n must be > 0");
662
663 if (n == 1) {
664 out[0] = 1.0;
665 return;
666 }
667
668 double inv_i0_beta = 1.0 / MD_bessel_i0(beta);
669 double n_minus_1 = (double)(n - 1);
670
671 for (unsigned i = 0; i < n; i++) {
672 double t = 2.0 * (double)i / n_minus_1 - 1.0; /* in [-1, 1] */
673 double arg = 1.0 - t * t;
674 if (arg < 0.0) arg = 0.0; /* clamp floating-point rounding */
675 out[i] = MD_bessel_i0(beta * sqrt(arg)) * inv_i0_beta;
676 }
677}
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_Kaiser_Win(double *out, unsigned n, double beta)
Generate a Kaiser window.
double MD_power(const double *a, unsigned N)
Signal power: energy divided by the number of samples.
#define MD_F0_ACF_PEAK_THRESHOLD
Minimum normalised autocorrelation peak height accepted as voiced F0.
void MD_Gen_Blackman_Win(double *out, unsigned n)
Generate a Blackman window.
double MD_zero_crossing_rate(const double *a, unsigned N)
Zero-crossing rate: fraction of adjacent sample pairs that differ in sign.
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.
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_Gen_Hamming_Win(double *out, unsigned n)
Generate a Hamming window.
double MD_bessel_i0(double x)
Zeroth-order modified Bessel function of the first kind.
void MD_tremolo(const double *in, double *out, unsigned N, double rate_hz, double depth, double sample_rate)
Tremolo effect (amplitude modulation by a sinusoidal LFO).
double MD_scale(double in, double oldmin, double oldmax, double newmin, double newmax)
Linearly map a value from one range to another.
void MD_Gen_Hann_Win(double *out, unsigned n)
Generate a Hanning (raised cosine) window.
double MD_energy(const double *a, unsigned N)
Signal energy: the sum of squared samples.
void MD_peak_detect(const double *a, unsigned N, double threshold, unsigned min_distance, unsigned *peaks_out, unsigned *num_peaks_out)
Peak detection: find local maxima above a threshold.
void MD_mix(const double *a, const double *b, double *out, unsigned N, double w_a, double w_b)
Signal mixing: weighted sum of two signals.
double MD_entropy(const double *a, unsigned N, bool clip)
Compute the normalised entropy of a distribution.
double MD_f0_autocorrelation(const double *signal, unsigned N, double sample_rate, double min_freq_hz, double max_freq_hz)
Estimate the fundamental frequency (F0) using autocorrelation.
double MD_sinc(double x)
Normalized sinc function: sin(πx) / (πx).
double MD_power_db(const double *a, unsigned N)
Signal power expressed in decibels (dB).
void MD_delay_echo(const double *in, double *out, unsigned N, unsigned delay_samples, double feedback, double dry, double wet)
Delay line / echo effect using a circular buffer with feedback.
double MD_dot(const double *a, const double *b, unsigned N)
Dot product of two vectors a and b, each of length N.
void MD_Gen_Rect_Win(double *out, unsigned n)
Generate a rectangular window (all ones).
void MD_comb_reverb(const double *in, double *out, unsigned N, unsigned delay_samples, double feedback, double dry, double wet)
Comb-filter reverb (feedback comb filter with dry/wet mix).
void MD_autocorrelation(const double *a, unsigned N, double *out, unsigned max_lag)
Normalised autocorrelation for lags 0..max_lag-1.
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.
double MD_rms(const double *a, unsigned N)
Root mean square: the standard measure of signal "loudness".
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.
Internal header for cross-file dependencies within the minidsp module.
#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.