annotate tests/TestPhaseVocoder.cpp @ 129:6ec45e85ed81 kissfft

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