SoDaRadio-5.0.3-master:8901fb5
Hilbert_Test.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 #define SAMPLE_RATE 48000
30 #include <complex>
31 #include <iostream>
32 #include <fstream>
33 #include <stdlib.h>
34 #include "OSFilter.hxx"
35 #include <time.h>
36 #include <fftw3.h>
37 #include <math.h>
38 #include <boost/format.hpp>
39 
40 #include <stdio.h>
41 #include <fcntl.h>
42 #include <sys/types.h>
43 #include <sys/stat.h>
44 #include "HilbertTransformer.hxx"
45 
46 using namespace std;
47 #include <sys/time.h>
48 
49 namespace SoDaTest {
50 class Histogram {
51 public:
52  Histogram(unsigned int nb, float _min, float _max) {
53  num_buckets = nb;
54  min = _min;
55  max = _max;
56 
57  bucket_size = (_max - _min) / ((float) num_buckets);
58  buckets = new unsigned int[num_buckets];
59  underflow = 0; overflow = 0;
60  unsigned int i;
61  for(i = 0; i < num_buckets; i++) buckets[i] = 0;
62  }
63 
64  void record(float samp) {
65  if(samp < min) underflow++;
66  else if(samp > max) overflow++;
67  else {
68  unsigned int idx = (unsigned int) floor((samp - min) / bucket_size);
69  buckets[idx]++;
70  }
71  }
72 
73  void dump(ostream & os, const std::string & tag) {
74  os << boost::format("%s %f %d\n") % tag % (min - bucket_size) % underflow;
75  unsigned int i;
76  for(i = 0; i < num_buckets; i++) {
77  if(buckets[i] == 0) continue;
78  float b = min + ((float) i) * bucket_size;
79  os << boost::format("%s %f %d\n") % tag % b % buckets[i];
80  }
81  os << boost::format("%s %f %d\n") % tag % (max + bucket_size) % overflow;
82  }
83 
84  unsigned int num_buckets;
85  float min;
86  float max;
87  unsigned int underflow, overflow;
88  unsigned int * buckets;
89  float bucket_size;
90 };
91 }
92 
93 double curtime()
94 {
95  double ret;
96  struct timeval tp;
97  gettimeofday(&tp, NULL);
98  ret = ((double) tp.tv_sec) + 1e-6*((double)tp.tv_usec);
99 
100  return ret;
101 }
102 
103 
104 
105 
106 #define BUFLEN 1024
107 int dumpTest(int argc, char * argv[])
108 {
109  (void) argc; (void) argv;
112  float inbuf[BUFLEN];
113  std::complex<float> cinbuf[BUFLEN];
114  std::complex<float> outbuf[BUFLEN];
115  std::complex<float> coutbuf[BUFLEN];
116  std::complex<float> checkbuf[BUFLEN];
117 
118  int i, j, k;
119 
120  // load up the input buffer
121  const int num_freqs = 8;
122  float angles[num_freqs];
123  float ang_incs[num_freqs];
124 
125  srandom(0x13255);
126 
127 
128  for(i = 0; i < num_freqs; i++) {
129  angles[i] = 0.0;
130  long irand = random();
131  ang_incs[i] = M_PI * ((float) (3.535 * ((irand % 8) + 15))) / (0.82 * ((float) (BUFLEN)));
132  }
133 
134  float gain;
135  for(k = 0; k < 20; k++) {
136 
137  for(i = 0; i < BUFLEN; i++) {
138  checkbuf[i] = std::complex<float>(0.0, 0.0);
139  inbuf[i] = 0.0;
140  cinbuf[i] = std::complex<float>(0.0, 0.0);
141  }
142 
143  for(i = 0; i < BUFLEN; i++) {
144  if(k < 3) {
145  float x = ((float) (i + BUFLEN + k * BUFLEN)) / (4.0 * ((float) BUFLEN));
146  gain = 0.5 * (1.0 - cos(M_PI * x));
147  }
148  else {
149  gain = 1.0;
150  }
151  for(j = 0; j < num_freqs; j++) {
152  inbuf[i] += gain * sin(angles[j]);
153  cinbuf[i] += std::complex<float>(gain * sin(angles[j]),0.0);
154  checkbuf[i] += std::complex<float>(gain * sin(angles[j]), -1.0 * gain * cos(angles[j]));
155  angles[j] += ang_incs[j];
156  if(angles[j] > M_PI) angles[j] -= (2.0 * M_PI);
157  }
158  }
159 
160  // now do a hilbert transform.
161  htR.apply(inbuf, outbuf, false, 1.0);
162  htC.apply(cinbuf, coutbuf, true, 1.0);
163 
164  if(k > -1) {
165  for(i = 0; i < BUFLEN; i++) {
166  std::cout << boost::format("ZZZ: %d %f %f %f %f %f %f\n")
167  % (i + k * BUFLEN) % outbuf[i].real() % outbuf[i].imag() % coutbuf[i].real() % coutbuf[i].imag() % checkbuf[i].real() % checkbuf[i].imag();
168  }
169  }
170  }
171 
172 
173  // Now do a modulation test...
175  gain = 0.0;
176  for(k = 0; k < 20; k++) {
177  for(i = 0; i < BUFLEN; i++) {
178  checkbuf[i] = std::complex<float>(0.0, 0.0);
179  cinbuf[i] = std::complex<float>(0.0, 0.0);
180  }
181 
182  for(i = 0; i < BUFLEN; i++) {
183  if(k < 3) {
184  float x = ((float) (i + BUFLEN + k * BUFLEN)) / (4.0 * ((float) BUFLEN));
185  gain = 0.5 * (1.0 - cos(M_PI * x));
186  }
187  else {
188  gain = 1.0;
189  }
190  for(j = 0; j < num_freqs; j++) {
191  cinbuf[i] += gain * std::complex<float>(sin(angles[j]), cos(angles[j]));
192  // cinbuf[i].real() += gain * sin(angles[j]);
193  // cinbuf[i].imag() += gain * cos(angles[j]);
194  checkbuf[i] += gain * std::complex<float>(sin(angles[j]), -1.0 * cos(angles[j]));
195  // checkbuf[i].real() = checkbuf[i].real() + gain * sin(angles[j]);
196  // checkbuf[i].imag() = checkbuf[i].imag() - gain * cos(angles[j]);
197  angles[j] += ang_incs[j];
198  if(angles[j] > M_PI) angles[j] -= (2.0 * M_PI);
199  }
200  }
201 
202  // now do a hilbert transform.
203  htDemod.applyIQ(cinbuf, outbuf, 1.0);
204 
205  // dump the output
206  if(k > -1) {
207  for(i = 0; i < BUFLEN; i++) {
208  std::cout << boost::format("MMM: %d %f %f %f %f %f %f\n")
209  % (i + k * BUFLEN) % outbuf[i].real() % outbuf[i].imag() % cinbuf[i].real() % cinbuf[i].imag() % checkbuf[i].real() % checkbuf[i].imag();
210  }
211  }
212  }
213  return 0;
214 }
215 
216 
227 int doSSBTest(int argc, char * argv[])
228 {
229  (void) argc; (void) argv;
230  // create an HT for an audio buffer size of 2304
231  // (This corresponds to the magic choice in Params.hxx.)
232  int buflen = 2304;
233  // make sure the HT impulse response is at least 256 samples long.
234  SoDa::HilbertTransformer HT_ui(buflen, 256);
235  SoDa::HilbertTransformer HT_li(buflen, 256);
236  SoDa::HilbertTransformer HT_usb(buflen, 256);
237  SoDa::HilbertTransformer HT_lsb(buflen, 256);
238 
239  // create a test input buffer
240  float inbuf[buflen];
241  // output buffers
242  std::complex<float> analytic_U_inbuf[buflen];
243  std::complex<float> analytic_L_inbuf[buflen];
244  std::complex<float> usb_outbuf[buflen];
245  std::complex<float> lsb_outbuf[buflen];
246 
247  // now setup an input test signal parameters.
248  // We're going to use a signal made up of eight
249  // more or less random frequencies
250  const int num_freqs = 16;
251  float angles[num_freqs];
252  float ang_incs[num_freqs];
253 
254  srandom(0x13255);
255 
256  int i, j, k;
257  for(i = 0; i < num_freqs; i++) {
258  angles[i] = 0.0;
259  long irand = random();
260  // we want arg_inc (phase advance per step) to vary
261  // over the range 0 to pi / 8 (0.125 * Fnyquist).
262  float frand = ((float) (irand & 0xffff)) / (8.0 * 65536.0);
263  ang_incs[i] = frand * M_PI;
264  }
265 
266  ang_incs[0] = 0.01 * M_PI;
267 
268  // build the error histograms
269  SoDaTest::Histogram usb_sum_err(1000, -0.2, 0.2);
270  SoDaTest::Histogram lsb_diff_err(1000, -0.2, 0.2);
271 
272  // now do the test over 1000 buffers
273  int trial_count = 10;
274  int ignore_buffer_count = 1; // ignore the first buffer, or so to get over the startup transient.
275  for(k = 0; k < trial_count; k++) {
276  // load the input buffer.
277  for(j = 0; j < buflen; j++) {
278  inbuf[j] = 0.0;
279  }
280  for(i = 0; i < num_freqs; i++) {
281  for(j = 0; j < buflen; j++) {
282  inbuf[j] += sin(angles[i]);
283  angles[i] += ang_incs[i];
284  }
285  }
286 
287  // perform the transform.
288  HT_ui.apply(inbuf, analytic_U_inbuf, false, 1.0);
289  HT_li.apply(inbuf, analytic_L_inbuf, true, 1.0);
290  HT_usb.applyIQ(analytic_U_inbuf, usb_outbuf, 1.0);
291  HT_lsb.applyIQ(analytic_L_inbuf, lsb_outbuf, 1.0);
292 
293 
294  // now calculate the errors
295  if(k >= ignore_buffer_count) {
296  float norm = 0.0;
297  for(j = 0; j < buflen; j++) {
298  norm += (inbuf[j] * inbuf[j]);
299  }
300  norm = sqrt(norm);
301  for(j = 0; j < buflen; j++) {
302  float usb_sum, lsb_diff;
303  usb_sum = usb_outbuf[j].real() + usb_outbuf[j].imag();
304  lsb_diff = lsb_outbuf[j].real() - lsb_outbuf[j].imag();
305 
306  usb_sum_err.record(usb_sum / norm);
307  lsb_diff_err.record(lsb_diff / norm);
308 #if 0
309  std::cerr << boost::format("%d %f %f %f %f %f %f %f %f %f %f\n") %
310  (j + k * buflen) %
311  usb_sum % lsb_diff %
312  usb_outbuf[j].real() % usb_outbuf[j].imag() %
313  lsb_outbuf[j].real() % lsb_outbuf[j].imag() %
314  analytic_U_inbuf[j].real() % analytic_U_inbuf[j].imag() %
315  analytic_L_inbuf[j].real() % analytic_L_inbuf[j].imag()
316  ;
317 #endif
318  }
319  }
320  }
321 
322  std::cout << boost::format("# Dumps histograms of USB and LSB normalized error\n# -- all should be clustered around 0.0 -- normalized to mean squared amplitude.\n");
323  usb_sum_err.dump(std::cout, std::string("USB: "));
324  lsb_diff_err.dump(std::cout, std::string("LSB: "));
325 
326  return 1;
327 }
328 
333 int doPMTest(int argc, char * argv[])
334 {
335  (void) argc; (void) argv;
336  // make a PM signal and find it.
337  // create an HT for an audio buffer size of 2304
338  // (This corresponds to the magic choice in Params.hxx.)
339  int buflen = 2304;
340  // make sure the HT impulse response is at least 256 samples long.
341  SoDa::HilbertTransformer HT(buflen, 256);
342 
343  // create a test input modulating buffer
344  float inbuf[buflen];
345  // create the FM widget
346  float rf_inbuf[buflen];
347 
348  // output buffers
349  std::complex<float> transform_buf[buflen];
350 
351  // now setup an input test signal parameters.
352  // We're going to use a signal made up of eight
353  // more or less random frequencies
354  const int num_freqs = 16;
355  float angles[num_freqs];
356  float ang_incs[num_freqs];
357 
358  srandom(0x13255);
359 
360  int i, j, k;
361  for(i = 0; i < num_freqs; i++) {
362  angles[i] = 0.0;
363  long irand = random();
364  // we want arg_inc (phase advance per step) to vary
365  // over the range 0 to pi / 64
366  float frand = ((float) (irand & 0xffff)) / (64.0 * 65536.0);
367  ang_incs[i] = frand * M_PI;
368  }
369 
370  ang_incs[0] = 0.01 * M_PI;
371 
372  // what is the phase modulation shift?
373  float phase_incr = M_PI / 100.0;
374 
375  // what is the carrier frequency ?
376  // make it fsamp / 4;
377  float carrier_freq = M_PI / 20.0;
378  float tx_carrier_angle = 0.0;
379  float rx_carrier_angle = 0.0;
380 
381  // now do the test over 1000 buffers
382  int trial_count = 10;
383  int ignore_buffer_count = 1; // ignore the first buffer, or so to get over the startup transient.
384  for(k = 0; k < trial_count; k++) {
385  // load the input buffer.
386  for(j = 0; j < buflen; j++) {
387  inbuf[j] = 0.0;
388  }
389  for(i = 0; i < num_freqs; i++) {
390  for(j = 0; j < buflen; j++) {
391  inbuf[j] += sin(angles[i]);
392  angles[i] += ang_incs[i];
393  }
394  }
395 
396  for(j = 0; j < buflen; j++) {
397  rf_inbuf[j] = sin(tx_carrier_angle + inbuf[j] * phase_incr);
398  tx_carrier_angle += carrier_freq;
399  }
400 
401  // perform the transform.
402  HT.apply(rf_inbuf, transform_buf, false, 1.0);
403 
404  // now shift down to baseband
405  for(j = 0; j < buflen; j++) {
406  std::complex<float> rx_carrier = std::complex<float>(sin(rx_carrier_angle), cos(rx_carrier_angle));
407  rx_carrier_angle += carrier_freq;
408  transform_buf[j] = transform_buf[j] * rx_carrier;
409  }
410 
411  // now calculate the errors
412  if(k >= ignore_buffer_count) {
413  float norm = 0.0;
414  for(j = 0; j < buflen; j++) {
415  norm += (inbuf[j] * inbuf[j]);
416  }
417  norm = sqrt(norm);
418  for(j = 0; j < buflen; j++) {
419  float demod;
420  // demodulate the FM carrier
421  demod = atan2(transform_buf[j].imag(), transform_buf[j].real());
422 #if 1
423  std::cerr << boost::format("PM %d %f %f %f %f %f\n") %
424  (j + k * buflen) %
425  inbuf[j] %
426  demod %
427  rf_inbuf[j] %
428  transform_buf[j].real() % transform_buf[j].imag();
429 #endif
430  }
431  }
432  }
433 
434  return 1;
435 }
436 
437 int main(int argc, char * argv[])
438 {
439  // Test out the Hilbert Transformer module
440 
441  // Rather than doing a comparison with a reference,
442  // the HT is tested out by ensuring that the transformer
443  // produces results whose properties are those we expect
444  // from a Hilbert transform network.
445  //
446  // 1. Applying HT twice to a real valued signal f(t) will
447  // produce a delayed version of -1 * f(t + d) at its output.
448  // The HT module's complex result, should be (a + ib) =
449  // f(t + d) + i * -1 * f(t + d). Therefore, we should see
450  // that (a + b) == 0 and (a - b) = 2a.
451  //
452  // 2. When HT(f(t)) -> a + ib, applying HT to a phase
453  // modulated signal f(t) = sin(\omega t + \phi(t))
454  // should recover \phi(t) = atan(b/a).
455  //
456 
457  doSSBTest(argc, argv);
458 
459  doPMTest(argc, argv);
460 }
461 
int doPMTest(int argc, char *argv[])
doPMTest – run a Phase Modulated signal through a hilbert transform shift to baseband, then demodulate.
This is an overlap-and-save frequency domain implementation of a general FIR filter widget...
In several places we have a real valued signal x(t) that needs to be converted to an analytic signal ...
STL namespace.
unsigned int underflow
void dump(ostream &os, const std::string &tag)
unsigned int num_buckets
double curtime()
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.
unsigned int * buckets
Histogram(unsigned int nb, float _min, float _max)
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.
int dumpTest(int argc, char *argv[])
int doSSBTest(int argc, char *argv[])
Applying HT twice to a real valued signal f(t) will produce a delayed version of -1 * f(t + d) at its...
int main(int argc, char *argv[])
void record(float samp)
#define BUFLEN