annotate tests/TestPhaseVocoder.cpp @ 395:a0829908bb74

Fix overrun in reading inverse complex-to-real FFT input (contrary to docs)
author Chris Cannam <c.cannam@qmul.ac.uk>
date Fri, 09 May 2014 14:35:46 +0100
parents 3c7338aff6a8
children 2de6184b2ce0
rev   line source
c@342 1 /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
c@337 2
c@337 3 #include "dsp/phasevocoder/PhaseVocoder.h"
c@337 4
c@337 5 #include "base/Window.h"
c@337 6
c@342 7 #include <iostream>
c@342 8
c@342 9 using std::cerr;
c@342 10 using std::endl;
c@342 11
c@337 12 #define BOOST_TEST_DYN_LINK
c@337 13 #define BOOST_TEST_MAIN
c@337 14
c@337 15 #include <boost/test/unit_test.hpp>
c@337 16
c@337 17 BOOST_AUTO_TEST_SUITE(TestFFT)
c@337 18
c@337 19 #define COMPARE_CONST(a, n) \
c@337 20 for (int cmp_i = 0; cmp_i < (int)(sizeof(a)/sizeof(a[0])); ++cmp_i) { \
c@342 21 BOOST_CHECK_SMALL(a[cmp_i] - n, 1e-7); \
c@337 22 }
c@337 23
c@337 24 #define COMPARE_ARRAY(a, b) \
c@337 25 for (int cmp_i = 0; cmp_i < (int)(sizeof(a)/sizeof(a[0])); ++cmp_i) { \
c@342 26 BOOST_CHECK_SMALL(a[cmp_i] - b[cmp_i], 1e-7); \
c@337 27 }
c@337 28
c@337 29 #define COMPARE_ARRAY_EXACT(a, b) \
c@337 30 for (int cmp_i = 0; cmp_i < (int)(sizeof(a)/sizeof(a[0])); ++cmp_i) { \
c@337 31 BOOST_CHECK_EQUAL(a[cmp_i], b[cmp_i]); \
c@337 32 }
c@337 33
c@337 34 BOOST_AUTO_TEST_CASE(fullcycle)
c@337 35 {
c@340 36 // Cosine with one cycle exactly equal to pvoc hopsize. This is
c@340 37 // pretty much the most trivial case -- in fact it's
c@340 38 // indistinguishable from totally silent input (in the phase
c@340 39 // values) because the measured phases are zero throughout.
c@340 40
c@340 41 // We aren't windowing the input frame because (for once) it
c@340 42 // actually *is* just a short part of a continuous infinite
c@340 43 // sinusoid.
c@337 44
c@337 45 double frame[] = { 1, 0, -1, 0, 1, 0, -1, 0 };
c@337 46
c@340 47 PhaseVocoder pvoc(8, 4);
c@337 48
c@337 49 // Make these arrays one element too long at each end, so as to
c@337 50 // test for overruns. For frame size 8, we expect 8/2+1 = 5
c@337 51 // mag/phase pairs.
c@337 52 double mag[] = { 999, 999, 999, 999, 999, 999, 999 };
c@337 53 double phase[] = { 999, 999, 999, 999, 999, 999, 999 };
c@340 54 double unw[] = { 999, 999, 999, 999, 999, 999, 999 };
c@337 55
c@344 56 pvoc.processTimeDomain(frame, mag + 1, phase + 1, unw + 1);
c@337 57
c@337 58 double magExpected0[] = { 999, 0, 0, 4, 0, 0, 999 };
c@337 59 COMPARE_ARRAY_EXACT(mag, magExpected0);
c@337 60
c@337 61 double phaseExpected0[] = { 999, 0, 0, 0, 0, 0, 999 };
c@344 62 COMPARE_ARRAY(phase, phaseExpected0);
c@337 63
c@340 64 double unwExpected0[] = { 999, 0, 0, 0, 0, 0, 999 };
c@340 65 COMPARE_ARRAY(unw, unwExpected0);
c@340 66
c@344 67 pvoc.processTimeDomain(frame, mag + 1, phase + 1, unw + 1);
c@337 68
c@337 69 double magExpected1[] = { 999, 0, 0, 4, 0, 0, 999 };
c@337 70 COMPARE_ARRAY_EXACT(mag, magExpected1);
c@337 71
c@340 72 double phaseExpected1[] = { 999, 0, 0, 0, 0, 0, 999 };
c@337 73 COMPARE_ARRAY(phase, phaseExpected1);
c@338 74
c@342 75 // Derivation of unwrapped values:
c@340 76 //
c@340 77 // * Bin 0 (DC) always has phase 0 and expected phase 0
c@340 78 //
c@340 79 // * Bin 1 has expected phase pi (the hop size is half a cycle at
c@340 80 // its frequency), but measured phase 0 (because there is no
c@340 81 // signal in that bin). So it has phase error -pi, which is
c@340 82 // mapped into (-pi,pi] range as pi, giving an unwrapped phase
c@340 83 // of 2*pi.
c@340 84 //
c@344 85 // * Bin 2 has expected phase 2*pi, measured phase 0, hence error
c@344 86 // 0 and unwrapped phase 2*pi.
c@340 87 //
c@340 88 // * Bin 3 is like bin 1: it has expected phase 3*pi, measured
c@340 89 // phase 0, so phase error -pi and unwrapped phase 4*pi.
c@340 90 //
c@344 91 // * Bin 4 (Nyquist) has expected phase 4*pi, measured phase 0,
c@344 92 // hence error 0 and unwrapped phase 4*pi.
c@340 93
c@340 94 double unwExpected1[] = { 999, 0, 2*M_PI, 2*M_PI, 4*M_PI, 4*M_PI, 999 };
c@340 95 COMPARE_ARRAY(unw, unwExpected1);
c@340 96
c@344 97 pvoc.processTimeDomain(frame, mag + 1, phase + 1, unw + 1);
c@338 98
c@338 99 double magExpected2[] = { 999, 0, 0, 4, 0, 0, 999 };
c@338 100 COMPARE_ARRAY_EXACT(mag, magExpected2);
c@338 101
c@340 102 double phaseExpected2[] = { 999, 0, 0, 0, 0, 0, 999 };
c@338 103 COMPARE_ARRAY(phase, phaseExpected2);
c@340 104
c@340 105 double unwExpected2[] = { 999, 0, 4*M_PI, 4*M_PI, 8*M_PI, 8*M_PI, 999 };
c@340 106 COMPARE_ARRAY(unw, unwExpected2);
c@337 107 }
c@337 108
c@342 109 BOOST_AUTO_TEST_CASE(overlapping)
c@342 110 {
c@342 111 // Sine (i.e. cosine starting at phase -pi/2) starting with the
c@342 112 // first sample, introducing a cosine of half the frequency
c@342 113 // starting at the fourth sample, i.e. the second hop. The cosine
c@342 114 // is introduced "by magic", i.e. it doesn't appear in the second
c@342 115 // half of the first frame (it would have quite strange effects on
c@342 116 // the first frame if it did).
c@340 117
c@342 118 double data[32] = { // 3 x 8-sample frames which we pretend are overlapping
c@342 119 0, 1, 0, -1, 0, 1, 0, -1,
c@342 120 1, 1.70710678, 0, -1.70710678, -1, 0.29289322, 0, -0.29289322,
c@342 121 -1, 0.29289322, 0, -0.29289322, 1, 1.70710678, 0, -1.70710678,
c@342 122 };
c@342 123
c@342 124 PhaseVocoder pvoc(8, 4);
c@342 125
c@342 126 // Make these arrays one element too long at each end, so as to
c@342 127 // test for overruns. For frame size 8, we expect 8/2+1 = 5
c@342 128 // mag/phase pairs.
c@342 129 double mag[] = { 999, 999, 999, 999, 999, 999, 999 };
c@342 130 double phase[] = { 999, 999, 999, 999, 999, 999, 999 };
c@342 131 double unw[] = { 999, 999, 999, 999, 999, 999, 999 };
c@342 132
c@344 133 pvoc.processTimeDomain(data, mag + 1, phase + 1, unw + 1);
c@342 134
c@342 135 double magExpected0[] = { 999, 0, 0, 4, 0, 0, 999 };
c@342 136 COMPARE_ARRAY(mag, magExpected0);
c@342 137
c@342 138 double phaseExpected0[] = { 999, 0, 0, -M_PI/2 , 0, 0, 999 };
c@342 139 COMPARE_ARRAY(phase, phaseExpected0);
c@342 140
c@342 141 double unwExpected0[] = { 999, 0, 0, -M_PI/2, 0, 0, 999 };
c@342 142 COMPARE_ARRAY(unw, unwExpected0);
c@342 143
c@344 144 pvoc.processTimeDomain(data + 8, mag + 1, phase + 1, unw + 1);
c@342 145
c@342 146 double magExpected1[] = { 999, 0, 4, 4, 0, 0, 999 };
c@342 147 COMPARE_ARRAY(mag, magExpected1);
c@342 148
c@344 149 // Derivation of unwrapped values:
c@344 150 //
c@344 151 // * Bin 0 (DC) always has phase 0 and expected phase 0
c@344 152 //
c@344 153 // * Bin 1 has a new signal, a cosine starting with phase 0. But
c@344 154 // because of the "FFT shift" which the phase vocoder carries
c@344 155 // out to place zero phase in the centre of the (usually
c@344 156 // windowed) frame, and because a single cycle at this frequency
c@344 157 // spans the whole frame, this bin actually has measured phase
c@344 158 // of either pi or -pi. (The shift doesn't affect those
c@344 159 // higher-frequency bins whose signals fit exact multiples of a
c@344 160 // cycle into a frame.) This maps into (-pi,pi] as pi, which
c@344 161 // matches the expected phase, hence unwrapped phase is also pi.
c@344 162 //
c@344 163 // * Bin 2 has expected phase 3pi/2 (being the previous measured
c@344 164 // phase of -pi/2 plus advance of 2pi). It has the same measured
c@344 165 // phase as last time around, -pi/2, which is consistent with
c@344 166 // the expected phase, so the unwrapped phase is 3pi/2.
c@355 167 //
c@355 168 // * Bin 3 I don't really know about -- the magnitude here is 0,
c@355 169 // but we get non-zero measured phase whose sign is
c@355 170 // implementation-dependent
c@344 171 //
c@344 172 // * Bin 4 (Nyquist) has expected phase 4*pi, measured phase 0,
c@344 173 // hence error 0 and unwrapped phase 4*pi.
c@344 174
c@355 175 phase[1+3] = 0.0; // Because we aren't testing for this one
c@355 176 double phaseExpected1[] = { 999, 0, -M_PI, -M_PI/2, 0, 0, 999 };
c@342 177 COMPARE_ARRAY(phase, phaseExpected1);
c@342 178
c@342 179 double unwExpected1[] = { 999, 0, M_PI, 3*M_PI/2, 3*M_PI, 4*M_PI, 999 };
c@342 180 COMPARE_ARRAY(unw, unwExpected1);
c@342 181
c@344 182 pvoc.processTimeDomain(data + 16, mag + 1, phase + 1, unw + 1);
c@342 183
c@342 184 double magExpected2[] = { 999, 0, 4, 4, 0, 0, 999 };
c@342 185 COMPARE_ARRAY(mag, magExpected2);
c@342 186
c@342 187 double phaseExpected2[] = { 999, 0, 0, -M_PI/2, 0, 0, 999 };
c@342 188 COMPARE_ARRAY(phase, phaseExpected2);
c@342 189
c@342 190 double unwExpected2[] = { 999, 0, 2*M_PI, 7*M_PI/2, 6*M_PI, 8*M_PI, 999 };
c@342 191 COMPARE_ARRAY(unw, unwExpected2);
c@342 192 }
c@340 193
c@337 194 BOOST_AUTO_TEST_SUITE_END()
c@337 195