SoDaRadio-5.0.3-master:8901fb5
OSFilter.cxx
Go to the documentation of this file.
1 /*
2  Copyright (c) 2012, Matthew H. Reilly (kb1vc)
3  All rights reserved.
4 
5  Redistribution and use in source and binary forms, with or without
6  modification, are permitted provided that the following conditions are
7  met:
8 
9  Redistributions of source code must retain the above copyright
10  notice, this list of conditions and the following disclaimer.
11  Redistributions in binary form must reproduce the above copyright
12  notice, this list of conditions and the following disclaimer in
13  the documentation and/or other materials provided with the
14  distribution.
15 
16  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
17  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
18  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
19  A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
20  HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
21  SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
22  LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
23  DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
24  THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25  (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
26  OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27 */
28 
29 #include "OSFilter.hxx"
30 
31 #include <iostream>
32 #include <string.h>
33 #include <fftw3.h>
34 #include <math.h>
35 #include <boost/format.hpp>
36 
37 static unsigned int ipow(unsigned int x, unsigned int y)
38 {
39  unsigned int ret;
40  ret = 1;
41  unsigned int i;
42 
43  for(i = 0; i < y; i++) {
44  ret *= x;
45  }
46 
47  return ret;
48 }
49 
50 #if 0
51 SoDa::OSFilter::OSFilter(float * filter_impulse_response,
52  unsigned int filter_length,
53  float filter_gain,
54  unsigned int inout_buffer_length,
55  OSFilter * cascade,
56  unsigned int suggested_transform_length)
57 {
58  // these are the salient dimensions for this Overlap/Save
59  // widget (for terminology, see Lyons pages 719ff
60  Q = filter_length; // to start with.
61  M = inout_buffer_length;
62 
63  if((cascade != NULL) && (cascade->M != M)) cascade = NULL;
64 
65  if(M < 4 * Q) {
66  std::cerr << "Warning -- OSFilter asked to implement a long filter against a short buffer." << std::endl;
67  }
68 
69  // now find N.
70  if(suggested_transform_length > (M + Q - 1)) {
71  N = suggested_transform_length;
72  }
73  else {
74  N = guessN();
75  }
76 
77  // now that we have N, we can back-calculate Q.
78  Q = N - M;
79 
80  tail_index = M - (Q - 1);
81 
82  // now build the transform image for the filter.
83  std::complex<float> * filter_in = (std::complex<float> *) fftwf_malloc(sizeof(std::complex<float>) * N);
84  filter_fft = (std::complex<float> *) fftwf_malloc(sizeof(std::complex<float>) * N);
85 
86  // create a temporary plan
87  fftwf_plan tplan = fftwf_plan_dft_1d(N, (fftwf_complex*) filter_in, (fftwf_complex*) filter_fft,
88  FFTW_FORWARD, FFTW_ESTIMATE);
89 
90  // now build the filter
91  // fill with zeros
92  unsigned int i;
93  for(i = 0; i < N; i++) filter_in[i] = std::complex<float>(0.0,0.0);
94  // fill in the impulse response
95  float gain_corr = 1.0 / ((float) N) * filter_gain;
96  for(i = 0; i < filter_length; i++) filter_in[i] = std::complex<float>(filter_impulse_response[i] * gain_corr, 0.0);
97 
98  // transform the filter.
99  fftwf_execute(tplan);
100 
101  // and forget the plan.
102  fftwf_destroy_plan(tplan);
103 
104  // filter lengths must be equal too
105  if((cascade != NULL) && (cascade->N != N)) cascade = NULL;
106 
107  // if there is a cascaded filter, multiply our filter coeffs by the cascaded filter coeffs
108  if(cascade != NULL) {
109  for(i = 0; i < N; i++) {
110  std::complex<double> a = filter_fft[i];
111  std::complex<double> b = cascade->filter_fft[i];
112  std::complex<double> ff = a * b * ((double) N);
113  filter_fft[i] = std::complex<float>(ff.real(), ff.imag());
114  }
115  }
116 
117 
118  // setup the fft buffers
119  setupFFT();
120 }
121 #endif
122 
123 SoDa::OSFilter::OSFilter(float low_cutoff,
124  float low_pass_edge,
125  float high_pass_edge,
126  float high_cutoff,
127 
128  unsigned int filter_length,
129  float filter_gain,
130  float sample_rate,
131 
132  unsigned int inout_buffer_length,
133  unsigned int suggested_transform_length)
134 {
135  // remember our edges
136  low_edge = (double) low_pass_edge;
137  high_edge = (double) high_pass_edge;
138 
139  // first find our buffer sizes.
140  Q = filter_length;
141  M = inout_buffer_length;
142  if(M < 4 * Q) {
143  std::cerr << "Warning -- OSFilter asked to implement a long filter against a short buffer." << std::endl;
144  }
145 
146  // now find N.
147  if(suggested_transform_length > (M + Q - 1)) {
148  N = suggested_transform_length;
149  }
150  else {
151  N = guessN();
152  }
153 
154  // now back calculate the actual filter length
155  Q = N - M;
156 
157  tail_index = M - (Q - 1);
158 
159  // OK. now we build the filter.
160  // First, build an allpass filter with the appropriate delay
161  std::complex<float> * filter_in = (std::complex<float> *) fftwf_malloc(sizeof(std::complex<float>) * N);
162  filter_fft = (std::complex<float> *) fftwf_malloc(sizeof(std::complex<float>) * N);
163 
164  unsigned int i, j;
165  for(i = 0; i < N; i++) filter_in[i] = std::complex<float>(0.0,0.0);
166  filter_in[Q/2] = std::complex<float>(1.0, 0.0);
167  // we'll use the image of this filter for the phase part of our filter.
168 
169  fftwf_plan fplan = fftwf_plan_dft_1d(N, (fftwf_complex *) filter_in, (fftwf_complex *) filter_fft,
170  FFTW_FORWARD, FFTW_ESTIMATE);
171  fftwf_plan ifplan = fftwf_plan_dft_1d(N, (fftwf_complex *) filter_fft, (fftwf_complex *) filter_in,
172  FFTW_BACKWARD, FFTW_ESTIMATE);
173 
174  // create an image that we can fill in.
175  fftwf_execute(fplan);
176 
177  float freq_step = sample_rate / ((float) N);
178  float fr;
179  // now setup the filter image;
180  for(fr = 0.0, i = 0, j = (N - 1); i < N/2; i++, j--, fr += freq_step) {
181  if(fr < low_cutoff) {
182  filter_fft[i] = std::complex<float>(0.0,0.0);
183  filter_fft[j] = std::complex<float>(0.0,0.0);
184  }
185  else if(fr < low_pass_edge) {
186  float mult = (fr - low_cutoff) / (low_pass_edge - low_cutoff);
187  filter_fft[i] = filter_fft[i] * std::complex<float>(mult, 0.0);
188  filter_fft[j] = filter_fft[j] * std::complex<float>(mult, 0.0);
189  }
190  else if(fr < high_pass_edge) {
191  // keep the all pass value
192  }
193  else if(fr < high_cutoff) {
194  float mult = 1.0 - ((fr - high_pass_edge) / (high_cutoff - high_pass_edge));
195  filter_fft[i] = filter_fft[i] * std::complex<float>(mult, 0.0);
196  filter_fft[j] = filter_fft[j] * std::complex<float>(mult, 0.0);
197  }
198  else {
199  filter_fft[i] = std::complex<float>(0.0,0.0);
200  filter_fft[j] = std::complex<float>(0.0,0.0);
201  }
202  }
203 
204  // inverse transform the filter image
205  fftwf_execute(ifplan);
206 
207  // now we've got a FIR filter, but it needs to be windowed before we can trust it.
208  for(i = 0; i < Q/2; i++) {
209  int ii = (Q/2) - i;
210  float ang = 2.0 * M_PI * ((float) ii) / ((float) Q);
211  std::complex<float> wf(0.5 + 0.5 * cos(ang), 0.0);
212  filter_in[i] = filter_in[i] * wf;
213  filter_in[Q - i] = filter_in[Q - i] * wf;
214  }
215  for(i = Q; i < N; i++) {
216  filter_in[i] = std::complex<float>(0.0, 0.0);
217  }
218 
219  // now create the filter image
220  fftwf_execute(fplan);
221 
222  // now we need to normalize the envelope so that we get the specified gain.
223  float maxval = 0.0;
224  for(i = 0; i < N; i++) {
225  float v = abs(filter_fft[i]);
226  if(v > maxval) maxval = v;
227  }
228 
229  std::complex<float> normalize(filter_gain / (((float) N) * maxval), 0.0);
230 
231  for(i = 0; i < N; i++) {
232  filter_fft[i] = filter_fft[i] * normalize;
233  }
234 
235  // and destroy the plans
236  fftwf_destroy_plan(fplan);
237  fftwf_destroy_plan(ifplan);
238 
239  // and free the impulse response
240  fftwf_free(filter_in);
241 
242  // and setup the FFT buffers
243  setupFFT();
244 }
245 
246 
247 
249 {
250  // give preferences to convenient powers of two.
251  // but let's search for the nearest solution that is
252  // a multiple of 2^a * 3^b * 5^c where b and c are in the range 0..3
253  // and a is in the range 1..16
254  unsigned int N_guess, N_best, E_best;
255  N_best = 2;
256  E_best = 0x80000000;
257  int i;
258  for(i = 1; i <= 0xff; i++) {
259  unsigned int a = i & 0xf;
260  unsigned int b = (i >> 4) & 0x3;
261  unsigned int c = (i >> 6) & 0x3;
262  N_guess = ipow(2, a) * ipow(3, b) * ipow(5, c);
263  if(N_guess >= (M + Q - 1)) {
264  unsigned slop = N_guess - (M + Q - 1);
265  if(slop < E_best) {
266  N_best = N_guess;
267  E_best = slop;
268  }
269  }
270  }
271 
272  return N_best;
273 }
274 
276 {
277  unsigned int i;
278  // now allocate all the storage vectors
279  fft_input = (std::complex<float> *) fftwf_malloc(sizeof(std::complex<float>) * N);
280  fft_output = (std::complex<float> *) fftwf_malloc(sizeof(std::complex<float>) * N);
281  ifft_output = (std::complex<float> *) fftwf_malloc(sizeof(std::complex<float>) * N);
282 
283  // and create the plans
284  forward_plan = fftwf_plan_dft_1d(N,
285  (fftwf_complex *) fft_input,
286  (fftwf_complex *) fft_output,
287  FFTW_FORWARD, FFTW_ESTIMATE);
288  backward_plan = fftwf_plan_dft_1d(N,
289  (fftwf_complex *) fft_output,
290  (fftwf_complex *) ifft_output,
291  FFTW_BACKWARD, FFTW_ESTIMATE);
292 
293  // zero out the start of the fft_input buffer for the first iteration.
294  for(i = 0; i < Q-1; i++) fft_input[i] = std::complex<float>(0.0,0.0);
295 
296 }
297 
298 unsigned int SoDa::OSFilter::apply(float * inbuf, float * outbuf, float outgain, int instride, int outstride)
299 {
300  unsigned int i, j;
301  // copy the input buffer.
302  for(i = 0, j = Q-1; i < (M * instride); i += instride, j++) {
303  fft_input[j] = std::complex<float>(inbuf[i], 0.0);
304  }
305 
306  // now do the forward FFT on the input
307  fftwf_execute(forward_plan);
308 
309  // save the last bits of the input buffer to the Q-1 side of the FFT input vector
310  // Do this now incase in buf and outbuf are the same buffers. (we're going to
311  // over-write most of outbuf with the memcpy at the bottom.... )
312  for(i = (tail_index * instride), j = 0; j < Q-1; i += instride, j++) {
313  fft_input[j] = std::complex<float>(inbuf[i], 0.0);
314  }
315 
316  // apply the filter.
317  for(i = 0; i < N; i++) {
318  fft_output[i] = (fft_output[i] * filter_fft[i]) * outgain;
319  }
320 
321  // now do the backward FFT on the result
322  fftwf_execute(backward_plan);
323 
324 
325  // and copy the result to the output buffer, but discard the
326  // first Q-1 chunks
327  for(i = 0, j = Q-1; i < (M * outstride); i += outstride, j++) {
328  outbuf[i] = ifft_output[j].real();
329  }
330 
331  return M;
332 }
333 
334 unsigned int SoDa::OSFilter::apply(std::complex<float> * inbuf, std::complex<float> * outbuf, float outgain)
335 {
336  // This is the overlap-save FFT filter technique described in Lyons pages 719ff.
337  // first we need to copy the input buffer to the M side of the FFT input vector.
338  memcpy(&(fft_input[Q-1]), inbuf, sizeof(std::complex<float>) * M);
339 
340  // now do the forward FFT on the input
341  fftwf_execute(forward_plan);
342 
343  // save the last bits of the input buffer to the Q-1 side of the FFT input vector
344  // Do this now incase inbuf and outbuf are the same buffers. (we're going to
345  // over-write most of outbuf with the memcpy at the bottom.... )
346  memcpy(fft_input, &(inbuf[tail_index]), sizeof(std::complex<float>) * (Q-1));
347 
348  // apply the filter.
349  unsigned int i;
350  for(i = 0; i < N; i++) {
351  fft_output[i] = (fft_output[i] * filter_fft[i]) * outgain;
352  }
353 
354  // now do the backward FFT on the result
355  fftwf_execute(backward_plan);
356 
357  // and copy the result to the output buffer, but discard the
358  // first Q-1 chunks
359  memcpy(outbuf, &(ifft_output[Q-1]), sizeof(std::complex<float>) * M);
360 
361  return M;
362 }
363 
364 void SoDa::OSFilter::dump(std::ostream & os)
365 {
366  unsigned int i;
367  os << "# idx real imag abs arg" << std::endl;
368  for(i = 0; i < N; i++) {
369  float mag = abs(filter_fft[i]);
370  float phase = arg(filter_fft[i]);
371  os << i << " " << filter_fft[i].real() << " " << filter_fft[i].imag() << " " << mag << " " << phase << std::endl;
372  }
373 }
This is an overlap-and-save frequency domain implementation of a general FIR filter widget...
int guessN()
pick a likely N - FFT length.
Definition: OSFilter.cxx:248
double high_edge
Definition: OSFilter.hxx:123
static unsigned int ipow(unsigned int x, unsigned int y)
Definition: OSFilter.cxx:37
void setupFFT()
Definition: OSFilter.cxx:275
double low_edge
parameters that we keep to support display masks on the spectrogram
Definition: OSFilter.hxx:123
std::complex< float > * fft_input
a copy of the input stream.
Definition: OSFilter.hxx:141
fftwf_plan backward_plan
plans for fftw transform ops
Definition: OSFilter.hxx:150
void dump(std::ostream &os)
dump the filter FFT to the output stream
Definition: OSFilter.cxx:364
std::complex< float > * ifft_output
the output stream + overlap discard
Definition: OSFilter.hxx:143
OSFilter(float low_cutoff, float low_pass_edge, float high_pass_edge, float high_cutoff, unsigned int filter_length, float filter_gain, float sample_rate, unsigned int inout_buffer_length, unsigned int suggested_transform_length=0)
constructor
Definition: OSFilter.cxx:123
unsigned int tail_index
the beginning of the end.
Definition: OSFilter.hxx:138
std::complex< float > * filter_fft
FFT image of the input filter.
Definition: OSFilter.hxx:146
unsigned int apply(std::complex< float > *inbuf, std::complex< float > *outbuf, float outgain=1.0)
run the filter on a complex input stream
Definition: OSFilter.cxx:334
std::complex< float > * fft_output
the transformed input stream + overlap
Definition: OSFilter.hxx:142
fftwf_plan forward_plan
Definition: OSFilter.hxx:150
unsigned int N
the total length of the transform N > (M + Q-1)
Definition: OSFilter.hxx:135
unsigned int M
the input buffer length;
Definition: OSFilter.hxx:133
unsigned int Q
the filter length
Definition: OSFilter.hxx:134