miniDSP
A small C library for audio DSP
Loading...
Searching...
No Matches
minidsp_core.c
Go to the documentation of this file.
1
10#include "minidsp.h"
11
12/* -----------------------------------------------------------------------
13 * Public API: basic signal measurements
14 * -----------------------------------------------------------------------*/
15
25double MD_dot(const double *a, const double *b, unsigned N)
26{
27 double d = 0.0;
28 for (unsigned i = 0; i < N; i++) {
29 d += a[i] * b[i];
30 }
31 return d;
32}
33
45double MD_energy(const double *a, unsigned N)
46{
47 assert(a != nullptr);
48 if (N == 1) return a[0] * a[0];
49 return MD_dot(a, a, N);
50}
51
60double MD_power(const double *a, unsigned N)
61{
62 assert(a != nullptr);
63 assert(N > 0);
64 return MD_energy(a, N) / (double)N;
65}
66
76double MD_power_db(const double *a, unsigned N)
77{
78 assert(a != nullptr);
79 assert(N > 0);
80 double p = fmax(1.0e-10, MD_power(a, N));
81 return 10.0 * log10(p);
82}
83
84/* -----------------------------------------------------------------------
85 * Public API: signal scaling and conditioning
86 * -----------------------------------------------------------------------*/
87
96double MD_scale(double in,
97 double oldmin, double oldmax,
98 double newmin, double newmax)
99{
100 return (in - oldmin) * (newmax - newmin) / (oldmax - oldmin) + newmin;
101}
102
106void MD_scale_vec(double *in, double *out, unsigned N,
107 double oldmin, double oldmax,
108 double newmin, double newmax)
109{
110 assert(in != nullptr);
111 assert(out != nullptr);
112 assert(oldmin < oldmax);
113 assert(newmin < newmax);
114 if (N == 0) return;
115
116 double scale = (newmax - newmin) / (oldmax - oldmin);
117 for (unsigned i = 0; i < N; i++) {
118 out[i] = (in[i] - oldmin) * scale + newmin;
119 }
120}
121
129void MD_fit_within_range(double *in, double *out, unsigned N,
130 double newmin, double newmax)
131{
132 assert(in != nullptr);
133 assert(out != nullptr);
134 assert(newmin < newmax);
135 if (N == 0) return;
136
137 /* Find the actual min and max of the input */
138 double in_min = in[0];
139 double in_max = in[0];
140 for (unsigned i = 1; i < N; i++) {
141 if (in[i] < in_min) in_min = in[i];
142 if (in[i] > in_max) in_max = in[i];
143 }
144
145 if (in_min >= newmin && in_max <= newmax) {
146 /* Already fits -- just copy without scaling */
147 for (unsigned i = 0; i < N; i++) {
148 out[i] = in[i];
149 }
150 } else {
151 MD_scale_vec(in, out, N, in_min, in_max, newmin, newmax);
152 }
153}
154
165void MD_adjust_dblevel(const double *in, double *out,
166 unsigned N, double dblevel)
167{
168 /* Convert the target dB level back to linear power */
169 double desired_power = pow(10.0, dblevel / 10.0);
170
171 /* Compute the gain factor:
172 * We want: (1/N) * sum(out[i]^2) = desired_power
173 * Since out[i] = in[i] * gain:
174 * (gain^2 / N) * sum(in[i]^2) = desired_power
175 * gain = sqrt(desired_power * N / energy_in) */
176 double input_energy = MD_energy(in, N);
177 double gain = sqrt((desired_power * (double)N) / input_energy);
178
179 /* Apply the gain and check for out-of-range values */
180 bool out_of_range = false;
181 for (unsigned i = 0; i < N; i++) {
182 out[i] = in[i] * gain;
183 if (out[i] > 1.0 || out[i] < -1.0) {
184 out_of_range = true;
185 }
186 }
187
188 /* If any samples exceed [-1.0, 1.0], rescale everything to fit */
189 if (out_of_range) {
190 MD_fit_within_range(out, out, N, -1.0, 1.0);
191 }
192}
193
210double MD_entropy(const double *a, unsigned N, bool clip)
211{
212 assert(a != nullptr);
213
214 if (N <= 1) return 0.0;
215
216 /* Maximum possible entropy for N bins (uniform distribution) */
217 double max_entropy = log2((double)N);
218
219 /* First pass: compute the total (for normalisation into probabilities) */
220 double total = 0.0;
221 if (clip) {
222 for (unsigned i = 0; i < N; i++) {
223 total += (a[i] < 0.0) ? 0.0 : a[i];
224 }
225 } else {
226 for (unsigned i = 0; i < N; i++) {
227 total += a[i] * a[i];
228 }
229 }
230
231 /* If the total is zero (e.g., all-zeros input), entropy is undefined.
232 * We return 0.0 because there is no meaningful distribution. */
233 if (total == 0.0) return 0.0;
234
235 /* Second pass: compute H = -SUM( p_i * log2(p_i) ) */
236 double entropy = 0.0;
237 for (unsigned i = 0; i < N; i++) {
238 if (a[i] == 0.0) continue;
239 if (clip && a[i] < 0.0) continue;
240
241 double p;
242 if (clip) {
243 p = a[i] / total;
244 } else {
245 p = (a[i] * a[i]) / total;
246 }
247
248 entropy += p * log2(p); /* Note: p*log2(p) is always <= 0 */
249 }
250
251 /* Negate and normalise so the result is in [0, 1] */
252 return -entropy / max_entropy;
253}
254
255/* -----------------------------------------------------------------------
256 * Public API: signal analysis
257 * -----------------------------------------------------------------------*/
258
260#define MD_F0_ACF_PEAK_THRESHOLD 0.15
261
264static double md_parabolic_offset(double y_left, double y_mid, double y_right)
265{
266 double denom = y_left - 2.0 * y_mid + y_right;
267 if (fabs(denom) < 1e-12) return 0.0;
268
269 double delta = 0.5 * (y_left - y_right) / denom;
270 if (delta < -0.5) delta = -0.5;
271 if (delta > 0.5) delta = 0.5;
272 return delta;
273}
274
280double MD_rms(const double *a, unsigned N)
281{
282 assert(a != nullptr);
283 assert(N > 0);
284 return sqrt(MD_power(a, N));
285}
286
293double MD_zero_crossing_rate(const double *a, unsigned N)
294{
295 assert(a != nullptr);
296 assert(N > 1);
297 unsigned crossings = 0;
298 for (unsigned i = 1; i < N; i++) {
299 if ((a[i] < 0.0) != (a[i - 1] < 0.0))
300 crossings++;
301 }
302 return (double)crossings / (double)(N - 1);
303}
304
313void MD_autocorrelation(const double *a, unsigned N,
314 double *out, unsigned max_lag)
315{
316 assert(a != nullptr);
317 assert(out != nullptr);
318 assert(N > 0);
319 assert(max_lag > 0 && max_lag < N);
320
321 double r0 = MD_energy(a, N);
322 if (r0 == 0.0) {
323 memset(out, 0, max_lag * sizeof(double));
324 return;
325 }
326 for (unsigned tau = 0; tau < max_lag; tau++) {
327 out[tau] = MD_dot(a, a + tau, N - tau) / r0;
328 }
329}
330
341void MD_peak_detect(const double *a, unsigned N, double threshold,
342 unsigned min_distance, unsigned *peaks_out,
343 unsigned *num_peaks_out)
344{
345 assert(a != nullptr);
346 assert(peaks_out != nullptr);
347 assert(num_peaks_out != nullptr);
348 assert(min_distance >= 1);
349
350 unsigned count = 0;
351 unsigned last_peak = 0;
352
353 for (unsigned i = 1; i + 1 < N; i++) {
354 if (a[i] > a[i - 1] && a[i] > a[i + 1] && a[i] >= threshold) {
355 if (count == 0 || i - last_peak >= min_distance) {
356 peaks_out[count++] = i;
357 last_peak = i;
358 }
359 }
360 }
361 *num_peaks_out = count;
362}
363
364double MD_f0_autocorrelation(const double *signal, unsigned N,
365 double sample_rate,
366 double min_freq_hz, double max_freq_hz)
367{
368 assert(signal != nullptr);
369 assert(N >= 2);
370 assert(sample_rate > 0.0);
371 assert(min_freq_hz > 0.0);
372 assert(max_freq_hz > min_freq_hz);
373
374 unsigned lag_min = (unsigned)floor(sample_rate / max_freq_hz);
375 unsigned lag_max = (unsigned)ceil(sample_rate / min_freq_hz);
376
377 if (lag_min < 1) lag_min = 1;
378 if (lag_max > N - 2) lag_max = N - 2;
379 if (lag_min > lag_max) return 0.0;
380 if (lag_max < 2) return 0.0; /* need lag-1 and lag+1 neighbors */
381
382 double *acf = malloc((lag_max + 1) * sizeof(double));
383 if (!acf) return 0.0;
384
385 MD_autocorrelation(signal, N, acf, lag_max + 1);
386
387 unsigned start = (lag_min < 1) ? 1 : lag_min;
388 unsigned stop = (lag_max > N - 2) ? (N - 2) : lag_max;
389
390 double best_peak = -DBL_MAX;
391 unsigned best_lag = 0;
392
393 for (unsigned lag = start; lag <= stop; lag++) {
394 double y = acf[lag];
395 if (y < MD_F0_ACF_PEAK_THRESHOLD) continue;
396 if (y <= acf[lag - 1] || y <= acf[lag + 1]) continue;
397
398 if (y > best_peak) {
399 best_peak = y;
400 best_lag = lag;
401 }
402 }
403
404 if (best_lag == 0) {
405 free(acf);
406 return 0.0;
407 }
408
409 double lag_est = (double)best_lag;
410 if (best_lag > 0 && best_lag + 1 <= lag_max) {
411 lag_est += md_parabolic_offset(acf[best_lag - 1],
412 acf[best_lag],
413 acf[best_lag + 1]);
414 }
415 free(acf);
416
417 if (lag_est <= 0.0) return 0.0;
418 return sample_rate / lag_est;
419}
420
428void MD_mix(const double *a, const double *b, double *out,
429 unsigned N, double w_a, double w_b)
430{
431 assert(a != nullptr);
432 assert(b != nullptr);
433 assert(out != nullptr);
434 for (unsigned i = 0; i < N; i++) {
435 out[i] = w_a * a[i] + w_b * b[i];
436 }
437}
438
439/* -----------------------------------------------------------------------
440 * Public API: simple effects
441 * -----------------------------------------------------------------------*/
442
443void MD_delay_echo(const double *in, double *out, unsigned N,
444 unsigned delay_samples, double feedback,
445 double dry, double wet)
446{
447 assert(in != nullptr);
448 assert(out != nullptr);
449 assert(N > 0);
450 assert(delay_samples > 0);
451 assert(fabs(feedback) < 1.0);
452
453 double *delay = calloc(delay_samples, sizeof(double));
454 assert(delay != nullptr);
455
456 unsigned idx = 0;
457 for (unsigned n = 0; n < N; n++) {
458 double x = in[n];
459 double d = delay[idx];
460 out[n] = dry * x + wet * d;
461 delay[idx] = x + feedback * d;
462 idx++;
463 if (idx == delay_samples) idx = 0;
464 }
465
466 free(delay);
467}
468
469void MD_tremolo(const double *in, double *out, unsigned N,
470 double rate_hz, double depth, double sample_rate)
471{
472 assert(in != nullptr);
473 assert(out != nullptr);
474 assert(N > 0);
475 assert(sample_rate > 0.0);
476 assert(rate_hz >= 0.0);
477 assert(depth >= 0.0 && depth <= 1.0);
478
479 double phase_step = 2.0 * M_PI * rate_hz / sample_rate;
480 for (unsigned n = 0; n < N; n++) {
481 double lfo = 0.5 * (1.0 + sin(phase_step * (double)n));
482 double gain = (1.0 - depth) + depth * lfo;
483 out[n] = in[n] * gain;
484 }
485}
486
487void MD_comb_reverb(const double *in, double *out, unsigned N,
488 unsigned delay_samples, double feedback,
489 double dry, double wet)
490{
491 assert(in != nullptr);
492 assert(out != nullptr);
493 assert(N > 0);
494 assert(delay_samples > 0);
495 assert(fabs(feedback) < 1.0);
496
497 double *comb = calloc(delay_samples, sizeof(double));
498 assert(comb != nullptr);
499
500 unsigned idx = 0;
501 for (unsigned n = 0; n < N; n++) {
502 double x = in[n];
503 double delayed = comb[idx];
504 double y_comb = x + feedback * delayed;
505 comb[idx] = y_comb;
506 out[n] = dry * x + wet * y_comb;
507 idx++;
508 if (idx == delay_samples) idx = 0;
509 }
510
511 free(comb);
512}
513
514/* -----------------------------------------------------------------------
515 * Public API: window generation
516 * -----------------------------------------------------------------------*/
517
529void MD_Gen_Hann_Win(double *out, unsigned n)
530{
531 assert(out != nullptr);
532 assert(n > 0);
533
534 if (n == 1) {
535 out[0] = 1.0;
536 return;
537 }
538
539 double n_minus_1 = (double)(n - 1);
540 for (unsigned i = 0; i < n; i++) {
541 out[i] = 0.5 * (1.0 - cos(2.0 * M_PI * (double)i / n_minus_1));
542 }
543}
544
550void MD_Gen_Hamming_Win(double *out, unsigned n)
551{
552 assert(out != nullptr);
553 assert(n > 0);
554
555 if (n == 1) {
556 out[0] = 1.0;
557 return;
558 }
559
560 double n_minus_1 = (double)(n - 1);
561 for (unsigned i = 0; i < n; i++) {
562 out[i] = 0.54 - 0.46 * cos(2.0 * M_PI * (double)i / n_minus_1);
563 }
564}
565
574void MD_Gen_Blackman_Win(double *out, unsigned n)
575{
576 assert(out != nullptr);
577 assert(n > 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 double p = 2.0 * M_PI * (double)i / n_minus_1;
587 out[i] = 0.42 - 0.5 * cos(p) + 0.08 * cos(2.0 * p);
588 }
589}
590
594void MD_Gen_Rect_Win(double *out, unsigned n)
595{
596 assert(out != nullptr);
597 assert(n > 0);
598 for (unsigned i = 0; i < n; i++) {
599 out[i] = 1.0;
600 }
601}
A mini library of DSP (Digital Signal Processing) routines.
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.
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_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.