miniDSP
A small C library for audio DSP
Loading...
Searching...
No Matches
minidsp_fir.c
Go to the documentation of this file.
1
6
7#include "minidsp.h"
8#include "minidsp_internal.h"
9
11static unsigned next_pow2(unsigned n)
12{
13 unsigned p = 1;
14 while (p < n) p <<= 1;
15 return p;
16}
17
18unsigned MD_convolution_num_samples(unsigned signal_len, unsigned kernel_len)
19{
20 MD_CHECK(signal_len > 0, MD_ERR_INVALID_SIZE, "signal_len must be > 0", 0);
21 MD_CHECK(kernel_len > 0, MD_ERR_INVALID_SIZE, "kernel_len must be > 0", 0);
22 return signal_len + kernel_len - 1;
23}
24
25void MD_convolution_time(const double *signal, unsigned signal_len,
26 const double *kernel, unsigned kernel_len,
27 double *out)
28{
29 MD_CHECK_VOID(signal != NULL, MD_ERR_NULL_POINTER, "signal must not be NULL");
30 MD_CHECK_VOID(kernel != NULL, MD_ERR_NULL_POINTER, "kernel must not be NULL");
31 MD_CHECK_VOID(out != NULL, MD_ERR_NULL_POINTER, "out must not be NULL");
32 MD_CHECK_VOID(signal_len > 0, MD_ERR_INVALID_SIZE, "signal_len must be > 0");
33 MD_CHECK_VOID(kernel_len > 0, MD_ERR_INVALID_SIZE, "kernel_len must be > 0");
34
35 unsigned out_len = MD_convolution_num_samples(signal_len, kernel_len);
36 for (unsigned n = 0; n < out_len; n++) {
37 double acc = 0.0;
38 for (unsigned k = 0; k < kernel_len; k++) {
39 if (k > n) break; /* signal index would be negative */
40 unsigned si = n - k;
41 if (si >= signal_len) continue; /* past end of input signal */
42 acc += signal[si] * kernel[k];
43 }
44 out[n] = acc;
45 }
46}
47
48void MD_moving_average(const double *signal, unsigned signal_len,
49 unsigned window_len, double *out)
50{
51 MD_CHECK_VOID(signal != NULL, MD_ERR_NULL_POINTER, "signal must not be NULL");
52 MD_CHECK_VOID(out != NULL, MD_ERR_NULL_POINTER, "out must not be NULL");
53 MD_CHECK_VOID(signal_len > 0, MD_ERR_INVALID_SIZE, "signal_len must be > 0");
54 MD_CHECK_VOID(window_len > 0, MD_ERR_INVALID_SIZE, "window_len must be > 0");
55
56 double sum = 0.0;
57 double inv_win = 1.0 / (double)window_len;
58
59 for (unsigned n = 0; n < signal_len; n++) {
60 sum += signal[n];
61 if (n >= window_len) {
62 sum -= signal[n - window_len];
63 }
64 out[n] = sum * inv_win;
65 }
66}
67
68void MD_fir_filter(const double *signal, unsigned signal_len,
69 const double *coeffs, unsigned num_taps,
70 double *out)
71{
72 MD_CHECK_VOID(signal != NULL, MD_ERR_NULL_POINTER, "signal must not be NULL");
73 MD_CHECK_VOID(coeffs != NULL, MD_ERR_NULL_POINTER, "coeffs must not be NULL");
74 MD_CHECK_VOID(out != NULL, MD_ERR_NULL_POINTER, "out must not be NULL");
75 MD_CHECK_VOID(signal_len > 0, MD_ERR_INVALID_SIZE, "signal_len must be > 0");
76 MD_CHECK_VOID(num_taps > 0, MD_ERR_INVALID_SIZE, "num_taps must be > 0");
77
78 for (unsigned n = 0; n < signal_len; n++) {
79 double acc = 0.0;
80 for (unsigned k = 0; k < num_taps; k++) {
81 if (k > n) break; /* zero-padded causal startup */
82 acc += coeffs[k] * signal[n - k];
83 }
84 out[n] = acc;
85 }
86}
87
88void MD_design_lowpass_fir(double *coeffs, unsigned num_taps,
89 double cutoff_freq, double sample_rate,
90 double kaiser_beta)
91{
92 MD_CHECK_VOID(coeffs != NULL, MD_ERR_NULL_POINTER, "coeffs must not be NULL");
93 MD_CHECK_VOID(num_taps > 0, MD_ERR_INVALID_SIZE, "num_taps must be > 0");
94 MD_CHECK_VOID(sample_rate > 0.0, MD_ERR_INVALID_RANGE, "sample_rate must be > 0");
95 MD_CHECK_VOID(cutoff_freq > 0.0, MD_ERR_INVALID_RANGE, "cutoff_freq must be > 0");
96 MD_CHECK_VOID(cutoff_freq < sample_rate / 2.0, MD_ERR_INVALID_RANGE, "cutoff_freq must be < sample_rate/2");
97
98 double fc = cutoff_freq / sample_rate; /* normalized cutoff */
99 double center = (double)(num_taps - 1) / 2.0;
100
101 /* Generate Kaiser window */
102 double *kaiser = malloc(num_taps * sizeof(double));
103 MD_CHECK_VOID(kaiser != NULL, MD_ERR_ALLOC_FAILED, "kaiser window allocation failed");
104 MD_Gen_Kaiser_Win(kaiser, num_taps, kaiser_beta);
105
106 /* Windowed sinc */
107 double sum = 0.0;
108 for (unsigned i = 0; i < num_taps; i++) {
109 double x = (double)i - center;
110 coeffs[i] = 2.0 * fc * MD_sinc(2.0 * fc * x) * kaiser[i];
111 sum += coeffs[i];
112 }
113
114 free(kaiser);
115
116 /* Normalize for unity DC gain */
117 if (fabs(sum) > 1e-30) {
118 for (unsigned i = 0; i < num_taps; i++) {
119 coeffs[i] /= sum;
120 }
121 }
122}
123
124void MD_convolution_fft_ola(const double *signal, unsigned signal_len,
125 const double *kernel, unsigned kernel_len,
126 double *out)
127{
128 MD_CHECK_VOID(signal != NULL, MD_ERR_NULL_POINTER, "signal must not be NULL");
129 MD_CHECK_VOID(kernel != NULL, MD_ERR_NULL_POINTER, "kernel must not be NULL");
130 MD_CHECK_VOID(out != NULL, MD_ERR_NULL_POINTER, "out must not be NULL");
131 MD_CHECK_VOID(signal_len > 0, MD_ERR_INVALID_SIZE, "signal_len must be > 0");
132 MD_CHECK_VOID(kernel_len > 0, MD_ERR_INVALID_SIZE, "kernel_len must be > 0");
133
134 unsigned out_len = MD_convolution_num_samples(signal_len, kernel_len);
135 memset(out, 0, out_len * sizeof(double));
136
137 unsigned nfft = next_pow2(2U * kernel_len);
138 if (nfft < 64U) nfft = 64U;
139 unsigned block_len = nfft - kernel_len + 1U;
140
141 unsigned num_bins = nfft / 2U + 1U;
142
143 double *h_time = calloc(nfft, sizeof(double));
144 double *x_time = calloc(nfft, sizeof(double));
145 double *y_time = calloc(nfft, sizeof(double));
146 fftw_complex *H = fftw_alloc_complex(num_bins);
147 fftw_complex *X = fftw_alloc_complex(num_bins);
148 fftw_complex *Y = fftw_alloc_complex(num_bins);
149 MD_CHECK_VOID(h_time != NULL, MD_ERR_ALLOC_FAILED, "h_time allocation failed");
150 MD_CHECK_VOID(x_time != NULL, MD_ERR_ALLOC_FAILED, "x_time allocation failed");
151 MD_CHECK_VOID(y_time != NULL, MD_ERR_ALLOC_FAILED, "y_time allocation failed");
152 MD_CHECK_VOID(H != NULL, MD_ERR_ALLOC_FAILED, "H allocation failed");
153 MD_CHECK_VOID(X != NULL, MD_ERR_ALLOC_FAILED, "X allocation failed");
154 MD_CHECK_VOID(Y != NULL, MD_ERR_ALLOC_FAILED, "Y allocation failed");
155
156 memcpy(h_time, kernel, kernel_len * sizeof(double));
157
158 fftw_plan plan_h = fftw_plan_dft_r2c_1d((int)nfft, h_time, H, FFTW_ESTIMATE);
159 fftw_plan plan_x = fftw_plan_dft_r2c_1d((int)nfft, x_time, X, FFTW_ESTIMATE);
160 fftw_plan plan_y = fftw_plan_dft_c2r_1d((int)nfft, Y, y_time, FFTW_ESTIMATE);
161 MD_CHECK_VOID(plan_h != NULL, MD_ERR_ALLOC_FAILED, "plan_h allocation failed");
162 MD_CHECK_VOID(plan_x != NULL, MD_ERR_ALLOC_FAILED, "plan_x allocation failed");
163 MD_CHECK_VOID(plan_y != NULL, MD_ERR_ALLOC_FAILED, "plan_y allocation failed");
164
165 fftw_execute(plan_h); /* kernel FFT once */
166
167 for (unsigned start = 0; start < signal_len; start += block_len) {
168 unsigned in_len = signal_len - start;
169 if (in_len > block_len) in_len = block_len;
170
171 memset(x_time, 0, nfft * sizeof(double));
172 memcpy(x_time, signal + start, in_len * sizeof(double));
173
174 fftw_execute(plan_x);
175
176 for (unsigned k = 0; k < num_bins; k++) {
177 double xr = creal(X[k]);
178 double xi = cimag(X[k]);
179 double hr = creal(H[k]);
180 double hi = cimag(H[k]);
181 Y[k] = (xr * hr - xi * hi) + I * (xr * hi + xi * hr);
182 }
183
184 fftw_execute(plan_y);
185
186 double norm = 1.0 / (double)nfft;
187 for (unsigned n = 0; n < nfft; n++) {
188 unsigned oi = start + n;
189 if (oi >= out_len) break;
190 out[oi] += y_time[n] * norm;
191 }
192 }
193
194 fftw_destroy_plan(plan_h);
195 fftw_destroy_plan(plan_x);
196 fftw_destroy_plan(plan_y);
197 fftw_free(Y);
198 fftw_free(X);
199 fftw_free(H);
200 free(y_time);
201 free(x_time);
202 free(h_time);
203}
A mini library of DSP (Digital Signal Processing) routines.
void MD_Gen_Kaiser_Win(double *out, unsigned n, double beta)
Generate a Kaiser window of length n with shape parameter beta.
@ 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
double MD_sinc(double x)
Normalized sinc function: .
static unsigned next_pow2(unsigned n)
Return the next power-of-two >= n (n must be > 0).
Definition minidsp_fir.c:11
void MD_convolution_fft_ola(const double *signal, unsigned signal_len, const double *kernel, unsigned kernel_len, double *out)
Full linear convolution using FFT overlap-add (offline).
unsigned MD_convolution_num_samples(unsigned signal_len, unsigned kernel_len)
Compute the output length of a full linear convolution.
Definition minidsp_fir.c:18
void MD_design_lowpass_fir(double *coeffs, unsigned num_taps, double cutoff_freq, double sample_rate, double kaiser_beta)
Design a Kaiser-windowed sinc lowpass FIR filter.
Definition minidsp_fir.c:88
void MD_fir_filter(const double *signal, unsigned signal_len, const double *coeffs, unsigned num_taps, double *out)
Apply a causal FIR filter with arbitrary coefficients.
Definition minidsp_fir.c:68
void MD_convolution_time(const double *signal, unsigned signal_len, const double *kernel, unsigned kernel_len, double *out)
Time-domain full linear convolution (direct sum-of-products).
Definition minidsp_fir.c:25
void MD_moving_average(const double *signal, unsigned signal_len, unsigned window_len, double *out)
Causal moving-average FIR filter with zero-padded startup.
Definition minidsp_fir.c:48
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.