SoDaRadio-5.0.3-master:8901fb5
HilbertTransformer.cxx
Go to the documentation of this file.
1 /*
2  Copyright (c) 2012,2013,2014 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 "HilbertTransformer.hxx"
30 
31 #include <iostream>
32 #include <string.h>
33 #include <fftw3.h>
34 #include <boost/format.hpp>
35 
36 int dbgctr = 0;
37 
38 static unsigned int ipow(unsigned int x, unsigned int y) __attribute__ ((unused));
39 static unsigned int ipow(unsigned int x, unsigned int y)
40 {
41  unsigned int ret;
42  ret = 1;
43  unsigned int i;
44 
45  for(i = 0; i < y; i++) {
46  ret *= x;
47  }
48 
49  return ret;
50 }
51 
52 SoDa::HilbertTransformer::HilbertTransformer(unsigned int inout_buffer_length,
53  unsigned int filter_length) :
54  SoDaBase("HilbertTransformer")
55 {
56  // these are the salient dimensions for this Overlap/Save
57  // widget (for terminology, see Lyons pages 719ff
58  M = inout_buffer_length;
59 
60  // now find N.
61  N = 4 * filter_length;
62  while(N < (M + filter_length)) {
63  N = N * 2;
64  }
65  // now that we have N, we can back-calculate Q.
66  Q = (N - M) + 1;
67 
68  // std::cerr << "\n\nHILBERT picked N = " << N << " Q = " << Q << " M = " << M << std::endl;
69 
70 
71  std::complex<float> htu[N], htl[N];
72 
73  // create the impulse response images
74  // There is probably a simpler way, but the obvious real/imag swap
75  // scheme doesn't work at all well.
76  HTu_filter = (std::complex<float> *) fftwf_malloc(sizeof(std::complex<float>) * N);
77  HTl_filter = (std::complex<float> *) fftwf_malloc(sizeof(std::complex<float>) * N);
78  Pass_U_filter = (std::complex<float> *) fftwf_malloc(sizeof(std::complex<float>) * N);
79  Pass_L_filter = (std::complex<float> *) fftwf_malloc(sizeof(std::complex<float>) * N);
80  fftwf_plan HTu_plan = fftwf_plan_dft_1d(N,
81  (fftwf_complex *) htu, (fftwf_complex *) HTu_filter,
82  FFTW_FORWARD, FFTW_ESTIMATE);
83  if(HTu_plan == NULL) {
84  throw SoDaException("Hilbert had trouble creating HT upper plan...\n");
85  }
86  fftwf_plan HTl_plan = fftwf_plan_dft_1d(N,
87  (fftwf_complex *) htl, (fftwf_complex *) HTl_filter,
88  FFTW_FORWARD, FFTW_ESTIMATE);
89  if(HTl_plan == NULL) {
90  throw SoDaException("Hilbert had trouble creating HT lower plan...\n");
91  }
92 
93  // now build the time domain image of the filter.
94  unsigned int i, j;
95  for(i = 0; i < N; i++) {
96  htu[i] = htl[i] = std::complex<float>(0.0, 0.0);
97  }
98  // this is actually a scaled ht -- removing the Fs * 2 / pi scaling factor.
99  for(i = 0, j = (Q / 2); i < (Q / 2); i++) {
100  if((i & 1) != 0) {
101  htu[j + i] = std::complex<float>(1.0 / ((float) i), 0.0);
102  htu[j - i] = std::complex<float>(-1.0 / ((float) i), 0.0);
103  htl[j + i] = std::complex<float>(-1.0 / ((float) i), 0.0);
104  htl[j - i] = std::complex<float>(1.0 / ((float) i), 0.0);
105  }
106  }
107  fftwf_execute(HTu_plan);
108  fftwf_execute(HTl_plan);
109  // now we have the HT filter image
110  fftwf_destroy_plan(HTu_plan);
111  fftwf_destroy_plan(HTl_plan);
112 
113  // now do the delay filter
114  fftwf_plan dly_plan = fftwf_plan_dft_1d(N,
115  (fftwf_complex *) htu, (fftwf_complex *) Pass_U_filter,
116  FFTW_FORWARD, FFTW_ESTIMATE);
117  // load up a new impulse response
118  for(i = 0; i < N; i++) htu[i] = std::complex<float>(0.0, 0.0);
119  htu[Q/2] = std::complex<float>(1.0, 0.0);
120  // now create the passthrough filter
121  fftwf_execute(dly_plan);
122  fftwf_destroy_plan(dly_plan);
123 
124  // Do some equalization on the pass filter to fix the low frequency response
125  // to match the response of the HT filter.
126  for(i = 0; i < N; i++) {
127  float tumag = abs(HTu_filter[i]);
128  float tlmag = abs(HTl_filter[i]);
129  float pmag = abs(Pass_U_filter[i]);
130  float uadj = 1.0;
131  float ladj = 1.0;
132  if(pmag > 0.001) {
133  uadj = tumag / pmag;
134  ladj = tlmag / pmag;
135  if (uadj > 2) uadj = 1.0;
136  if (ladj > 2) ladj = 1.0;
137  }
138  Pass_L_filter[i] = ladj * Pass_U_filter[i];
139  Pass_U_filter[i] = uadj * Pass_U_filter[i];
140  }
141 
142 
143  // calculate the magnitudes of the two filters.
144  float hmag, pmag;
145  hmag = 0.0;
146  pmag = 0.0;
147  for(i = 0; i < N; i++) {
148  hmag += HTu_filter[i].real() * HTu_filter[i].real()
149  + HTu_filter[i].imag() * HTu_filter[i].imag();
150  pmag += Pass_U_filter[i].real() * Pass_U_filter[i].real()
151  + Pass_U_filter[i].imag() * Pass_U_filter[i].imag();
152  }
153 
154  // now allocate all the storage vectors
155  fft_I_input = (std::complex<float> *) fftwf_malloc(sizeof(std::complex<float>) * (N + 128));
156  fft_Q_input = (std::complex<float> *) fftwf_malloc(sizeof(std::complex<float>) * (N + 128));
157  fft_I_output = (std::complex<float> *) fftwf_malloc(sizeof(std::complex<float>) * (N + 128));
158  fft_Q_output = (std::complex<float> *) fftwf_malloc(sizeof(std::complex<float>) * (N + 128));
159  ifft_I_input = (std::complex<float> *) fftwf_malloc(sizeof(std::complex<float>) * (N + 128));
160  ifft_Q_input = (std::complex<float> *) fftwf_malloc(sizeof(std::complex<float>) * (N + 128));
161  ifft_I_output = (std::complex<float> *) fftwf_malloc(sizeof(std::complex<float>) * (N + 128));
162  ifft_Q_output = (std::complex<float> *) fftwf_malloc(sizeof(std::complex<float>) * (N + 128));
163 
164  // and create the plans
165  forward_I_plan = fftwf_plan_dft_1d(N,
166  (fftwf_complex *) fft_I_input, (fftwf_complex *) fft_I_output,
167  FFTW_FORWARD, FFTW_ESTIMATE);
168  if(forward_I_plan == NULL) {
169  throw SoDaException("Hilbert had trouble creating forward I plan...\n");
170  }
171 
172  forward_Q_plan = fftwf_plan_dft_1d(N,
173  (fftwf_complex *) fft_Q_input,
174  (fftwf_complex *) fft_Q_output,
175  FFTW_FORWARD, FFTW_ESTIMATE);
176  if(forward_Q_plan == NULL) {
177  throw SoDaException("Hilbert had trouble creating forward Q plan...\n");
178  }
179 
180  backward_I_plan = fftwf_plan_dft_1d(N, (fftwf_complex *) ifft_I_input,
181  (fftwf_complex *) ifft_I_output,
182  FFTW_BACKWARD, FFTW_ESTIMATE);
183  if(backward_I_plan == NULL) {
184  throw SoDaException("Hilbert had trouble creating backward I plan...\n");
185  }
186  backward_Q_plan = fftwf_plan_dft_1d(N, (fftwf_complex *) ifft_Q_input,
187  (fftwf_complex *) ifft_Q_output,
188  FFTW_BACKWARD, FFTW_ESTIMATE);
189  if(backward_Q_plan == NULL) {
190  throw SoDaException("Hilbert had trouble creating backward Q plan...\n");
191  }
192  // zero out the start of the fft_input buffer for the first iteration.
193  for(i = 0; i <= Q-1; i++) {
194  fft_I_input[i] = 0.0;
195  fft_Q_input[i] = 0.0;
196  }
197 
198  // finally, set the transform gain (1/N)
199  // passthrough_gain = 1.0 / ((float) N);
200  // H_transform_gain = 2.0 / (M_PI * 0.966 * ((float) N)); // 0.966 is a fudge factor
201  H_transform_gain = 2.0 / (M_PI * ((float) N));
203 }
204 
205 unsigned int SoDa::HilbertTransformer::apply(std::complex<float> * inbuf,
206  std::complex<float> * outbuf,
207  bool pos_sided, float gain)
208 {
209  unsigned int i, j;
210 
211  std::complex<float> *HT_F;
212  std::complex<float> *PA_F;
213 
214  if(pos_sided) {
215  HT_F = HTl_filter;
216  PA_F = Pass_L_filter;
217  }
218  else {
219  HT_F = HTu_filter;
220  PA_F = Pass_U_filter;
221  }
222  // Note we're using overlap-and-save see the OSFilter implementation
223  // or Lyons pages 719ff
224  // copy the I channel to the tail of the input buffer.
225  memcpy(&(fft_I_input[Q-1]), inbuf, sizeof(std::complex<float>) * M);
226 
227  // do a pass through
228  fftwf_execute(forward_I_plan);
229 
230  // now save the tail of the input to the save buffer
231  memcpy(fft_I_input, &(inbuf[1 + (M - Q)]), sizeof(std::complex<float>) * (Q - 1));
232 
233  // now apply the delay filter and the hilbert transform
234  for(i = 0; i < N; i++) {
235  ifft_Q_input[i] = fft_I_output[i] * HT_F[i];
236  ifft_I_input[i] = fft_I_output[i] * PA_F[i];
237  }
238 
239  // do the inverse fft for the I and Q channels
240  fftwf_execute(backward_I_plan);
241  fftwf_execute(backward_Q_plan);
242 
243  // now put the two channels together
244  // Note that we're shifting the normal sampling window. This is because the
245  // quadrature sampler is just not quite right for
246  for(i = 0, j = Q-1; i < M; i++, j++) {
247  // seems like it worked once... but apparent shift is same for either sideband
248  outbuf[i] = std::complex<float>(ifft_I_output[j].real() * passthrough_gain * gain,
249  ifft_Q_output[j].real() * H_transform_gain * gain);
250  }
251 
252  dbgctr++;
253 
254 
255  return M;
256 }
257 
258 
259 unsigned int SoDa::HilbertTransformer::apply(float * inbuf,
260  std::complex<float> * outbuf,
261  bool pos_sided, float gain)
262 {
263  unsigned int i;
264  // This creates an analytic signal from a single input buffer.
265 
266  // Note we're using overlap-and-save see the OSFilter implementation
267  // or Lyons pages 719ff
268  std::complex<float> cinbuf[M];
269 
270 
271  // copy the I channel to the tail of the input buffer.
272  for(i = 0; i < M; i++) {
273  cinbuf[i] = std::complex<float>(inbuf[i], 0.0);
274  }
275 
276  // call the complex HT
277  return apply(cinbuf, outbuf, pos_sided, gain);
278 
279 
280  return M;
281 }
282 
283 
284 unsigned int SoDa::HilbertTransformer::applyIQ(std::complex<float> * inbuf,
285  std::complex<float> * outbuf,
286  float gain)
287 {
288  unsigned int i, j;
289  // This creates an analytic signal from a single input buffer.
290 
291  // Note we're using overlap-and-save see the OSFilter implementation
292  // or Lyons pages 719ff
293 
294  // copy the I channel to the tail of the I input buffer.
295  // copy the I channel to the tail of the Q input buffer.
296  for(i = 0; i < M; i++) {
297  fft_I_input[i + (Q-1)] = std::complex<float>(inbuf[i].real(), 0.0);
298  fft_Q_input[i + (Q-1)] = std::complex<float>(inbuf[i].imag(), 0.0);
299  }
300 
301  // do a the I (passthrough) and Q channel FFTs
302  fftwf_execute(forward_I_plan);
303  fftwf_execute(forward_Q_plan);
304 
305 
306  // now save the tail of the input to the save buffer
307  for(i = 0; i < (Q - 1); i++) {
308  fft_I_input[i] = std::complex<float>(inbuf[i + 1 + (M - Q)].real(), 0.0);
309  fft_Q_input[i] = std::complex<float>(inbuf[i + 1 + (M - Q)].imag(), 0.0);
310  }
311 
312  // now apply the delay filter (to I) and the hilbert transform (to Q)
313  for(i = 0; i < N; i++) {
314  ifft_Q_input[i] = fft_Q_output[i] * HTu_filter[i];
316  }
317 
318  // do the inverse fft for the I and Q channels
319  fftwf_execute(backward_I_plan);
320  fftwf_execute(backward_Q_plan);
321 
322  // now put the two channels together
323  // Note that we're shifting the normal sampling window. This is because the
324  // quadrature sampler is just not quite right for
325  for(i = 0, j = Q-1; i < M; i++, j++) {
326  outbuf[i] = std::complex<float>(ifft_I_output[j].real() * passthrough_gain * gain,
327  ifft_Q_output[j].real() * H_transform_gain * gain);
328  }
329 
330  dbgctr++;
331 
332  return M;
333 }
334 
335 
336 
337 std::ostream & SoDa::HilbertTransformer::dump(std::ostream & os)
338 {
339  unsigned int i, j;
340  for(i = 0; i < N; i++) {
341  j = i;
342  float mag = std::abs(HTl_filter[i]);
343  float ang = std::arg(HTl_filter[i]);
344  float pmag = std::abs(Pass_L_filter[i]);
345  float pang = std::arg(Pass_L_filter[i]);
346  os << boost::format("%d %f %f %f %f %f %f %f %f\n")
347  % j % HTu_filter[i].real() % HTu_filter[i].imag() % mag % ang %
348  Pass_U_filter[i].real() % Pass_U_filter[i].imag() % pmag % pang;
349  }
350  return os;
351 }
std::complex< float > * Pass_U_filter
The DFT image of a Q/2 delay transform – used in USB.
std::complex< float > * Pass_L_filter
The DFT image of a Q/2 delay transform – used in LSB.
std::complex< float > * ifft_I_output
unsigned int N
the total length of the transform N > (M + Q-1)
The SoDa Exception class.
Definition: SoDaBase.hxx:217
std::complex< float > * HTu_filter
The DFT image of the hilbert transform – upper sideband.
std::complex< float > * ifft_I_input
unsigned int applyIQ(std::complex< float > *inbuf, std::complex< float > *outbuf, float gain=1.0)
Perform a hilbert transform on the QUADRATURE signal in the input buffer.
std::complex< float > * ifft_Q_output
std::complex< float > * HTl_filter
The DFT image of the hilbert transform – lower sideband.
std::complex< float > * fft_Q_input
unsigned int apply(std::complex< float > *inbuf, std::complex< float > *outbuf, bool pos_sided=true, float gain=1.0)
Perform a hilbert transform on the INPHASE signal in the input buffer.
HilbertTransformer(unsigned int inout_buffer_length, unsigned int filter_length=256)
constructor – build a Hilbert Transformer
std::complex< float > * fft_I_input
float H_transform_gain
the gain of the Hilbert Transform path
std::complex< float > * ifft_Q_input
float passthrough_gain
the gain of the direct passthrough path.
static unsigned int ipow(unsigned int x, unsigned int y) __attribute__((unused))
unsigned int Q
the filter length
unsigned int M
these are the salient dimensions for this Overlap/Save widget (for terminology, see Lyons pages 719ff...
int dbgctr
The SoDa Base class.
Definition: SoDaBase.hxx:167
std::complex< float > * fft_Q_output
std::complex< float > * fft_I_output
std::ostream & dump(std::ostream &os)