Chris@117: /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ Chris@112: Chris@112: #include "dsp/phasevocoder/PhaseVocoder.h" Chris@112: Chris@112: #include "base/Window.h" Chris@112: Chris@117: #include Chris@117: Chris@117: using std::cerr; Chris@117: using std::endl; Chris@117: Chris@112: #define BOOST_TEST_DYN_LINK Chris@112: #define BOOST_TEST_MAIN Chris@112: Chris@112: #include Chris@112: Chris@112: BOOST_AUTO_TEST_SUITE(TestFFT) Chris@112: Chris@112: #define COMPARE_CONST(a, n) \ Chris@112: for (int cmp_i = 0; cmp_i < (int)(sizeof(a)/sizeof(a[0])); ++cmp_i) { \ Chris@117: BOOST_CHECK_SMALL(a[cmp_i] - n, 1e-7); \ Chris@112: } Chris@112: Chris@112: #define COMPARE_ARRAY(a, b) \ Chris@112: for (int cmp_i = 0; cmp_i < (int)(sizeof(a)/sizeof(a[0])); ++cmp_i) { \ Chris@117: BOOST_CHECK_SMALL(a[cmp_i] - b[cmp_i], 1e-7); \ Chris@112: } Chris@112: Chris@112: #define COMPARE_ARRAY_EXACT(a, b) \ Chris@112: for (int cmp_i = 0; cmp_i < (int)(sizeof(a)/sizeof(a[0])); ++cmp_i) { \ Chris@112: BOOST_CHECK_EQUAL(a[cmp_i], b[cmp_i]); \ Chris@112: } Chris@112: Chris@112: BOOST_AUTO_TEST_CASE(fullcycle) Chris@112: { Chris@115: // Cosine with one cycle exactly equal to pvoc hopsize. This is Chris@115: // pretty much the most trivial case -- in fact it's Chris@115: // indistinguishable from totally silent input (in the phase Chris@115: // values) because the measured phases are zero throughout. Chris@115: Chris@115: // We aren't windowing the input frame because (for once) it Chris@115: // actually *is* just a short part of a continuous infinite Chris@115: // sinusoid. Chris@112: Chris@112: double frame[] = { 1, 0, -1, 0, 1, 0, -1, 0 }; Chris@112: Chris@115: PhaseVocoder pvoc(8, 4); Chris@112: Chris@112: // Make these arrays one element too long at each end, so as to Chris@112: // test for overruns. For frame size 8, we expect 8/2+1 = 5 Chris@112: // mag/phase pairs. Chris@112: double mag[] = { 999, 999, 999, 999, 999, 999, 999 }; Chris@112: double phase[] = { 999, 999, 999, 999, 999, 999, 999 }; Chris@115: double unw[] = { 999, 999, 999, 999, 999, 999, 999 }; Chris@112: Chris@119: pvoc.processTimeDomain(frame, mag + 1, phase + 1, unw + 1); Chris@112: Chris@112: double magExpected0[] = { 999, 0, 0, 4, 0, 0, 999 }; Chris@112: COMPARE_ARRAY_EXACT(mag, magExpected0); Chris@112: Chris@112: double phaseExpected0[] = { 999, 0, 0, 0, 0, 0, 999 }; Chris@119: COMPARE_ARRAY(phase, phaseExpected0); Chris@112: Chris@115: double unwExpected0[] = { 999, 0, 0, 0, 0, 0, 999 }; Chris@115: COMPARE_ARRAY(unw, unwExpected0); Chris@115: Chris@119: pvoc.processTimeDomain(frame, mag + 1, phase + 1, unw + 1); Chris@112: Chris@112: double magExpected1[] = { 999, 0, 0, 4, 0, 0, 999 }; Chris@112: COMPARE_ARRAY_EXACT(mag, magExpected1); Chris@112: Chris@115: double phaseExpected1[] = { 999, 0, 0, 0, 0, 0, 999 }; Chris@112: COMPARE_ARRAY(phase, phaseExpected1); Chris@113: Chris@117: // Derivation of unwrapped values: Chris@115: // Chris@115: // * Bin 0 (DC) always has phase 0 and expected phase 0 Chris@115: // Chris@115: // * Bin 1 has expected phase pi (the hop size is half a cycle at Chris@115: // its frequency), but measured phase 0 (because there is no Chris@115: // signal in that bin). So it has phase error -pi, which is Chris@115: // mapped into (-pi,pi] range as pi, giving an unwrapped phase Chris@115: // of 2*pi. Chris@115: // Chris@119: // * Bin 2 has expected phase 2*pi, measured phase 0, hence error Chris@119: // 0 and unwrapped phase 2*pi. Chris@115: // Chris@115: // * Bin 3 is like bin 1: it has expected phase 3*pi, measured Chris@115: // phase 0, so phase error -pi and unwrapped phase 4*pi. Chris@115: // Chris@119: // * Bin 4 (Nyquist) has expected phase 4*pi, measured phase 0, Chris@119: // hence error 0 and unwrapped phase 4*pi. Chris@115: Chris@115: double unwExpected1[] = { 999, 0, 2*M_PI, 2*M_PI, 4*M_PI, 4*M_PI, 999 }; Chris@115: COMPARE_ARRAY(unw, unwExpected1); Chris@115: Chris@119: pvoc.processTimeDomain(frame, mag + 1, phase + 1, unw + 1); Chris@113: Chris@113: double magExpected2[] = { 999, 0, 0, 4, 0, 0, 999 }; Chris@113: COMPARE_ARRAY_EXACT(mag, magExpected2); Chris@113: Chris@115: double phaseExpected2[] = { 999, 0, 0, 0, 0, 0, 999 }; Chris@113: COMPARE_ARRAY(phase, phaseExpected2); Chris@115: Chris@115: double unwExpected2[] = { 999, 0, 4*M_PI, 4*M_PI, 8*M_PI, 8*M_PI, 999 }; Chris@115: COMPARE_ARRAY(unw, unwExpected2); Chris@112: } Chris@112: Chris@117: BOOST_AUTO_TEST_CASE(overlapping) Chris@117: { Chris@117: // Sine (i.e. cosine starting at phase -pi/2) starting with the Chris@117: // first sample, introducing a cosine of half the frequency Chris@117: // starting at the fourth sample, i.e. the second hop. The cosine Chris@117: // is introduced "by magic", i.e. it doesn't appear in the second Chris@117: // half of the first frame (it would have quite strange effects on Chris@117: // the first frame if it did). Chris@115: Chris@117: double data[32] = { // 3 x 8-sample frames which we pretend are overlapping Chris@117: 0, 1, 0, -1, 0, 1, 0, -1, Chris@117: 1, 1.70710678, 0, -1.70710678, -1, 0.29289322, 0, -0.29289322, Chris@117: -1, 0.29289322, 0, -0.29289322, 1, 1.70710678, 0, -1.70710678, Chris@117: }; Chris@117: Chris@117: PhaseVocoder pvoc(8, 4); Chris@117: Chris@117: // Make these arrays one element too long at each end, so as to Chris@117: // test for overruns. For frame size 8, we expect 8/2+1 = 5 Chris@117: // mag/phase pairs. Chris@117: double mag[] = { 999, 999, 999, 999, 999, 999, 999 }; Chris@117: double phase[] = { 999, 999, 999, 999, 999, 999, 999 }; Chris@117: double unw[] = { 999, 999, 999, 999, 999, 999, 999 }; Chris@117: Chris@119: pvoc.processTimeDomain(data, mag + 1, phase + 1, unw + 1); Chris@117: Chris@117: double magExpected0[] = { 999, 0, 0, 4, 0, 0, 999 }; Chris@117: COMPARE_ARRAY(mag, magExpected0); Chris@117: Chris@117: double phaseExpected0[] = { 999, 0, 0, -M_PI/2 , 0, 0, 999 }; Chris@117: COMPARE_ARRAY(phase, phaseExpected0); Chris@117: Chris@117: double unwExpected0[] = { 999, 0, 0, -M_PI/2, 0, 0, 999 }; Chris@117: COMPARE_ARRAY(unw, unwExpected0); Chris@117: Chris@119: pvoc.processTimeDomain(data + 8, mag + 1, phase + 1, unw + 1); Chris@117: Chris@117: double magExpected1[] = { 999, 0, 4, 4, 0, 0, 999 }; Chris@117: COMPARE_ARRAY(mag, magExpected1); Chris@117: Chris@119: // Derivation of unwrapped values: Chris@119: // Chris@119: // * Bin 0 (DC) always has phase 0 and expected phase 0 Chris@119: // Chris@119: // * Bin 1 has a new signal, a cosine starting with phase 0. But Chris@119: // because of the "FFT shift" which the phase vocoder carries Chris@119: // out to place zero phase in the centre of the (usually Chris@119: // windowed) frame, and because a single cycle at this frequency Chris@119: // spans the whole frame, this bin actually has measured phase Chris@119: // of either pi or -pi. (The shift doesn't affect those Chris@119: // higher-frequency bins whose signals fit exact multiples of a Chris@119: // cycle into a frame.) This maps into (-pi,pi] as pi, which Chris@119: // matches the expected phase, hence unwrapped phase is also pi. Chris@119: // Chris@119: // * Bin 2 has expected phase 3pi/2 (being the previous measured Chris@119: // phase of -pi/2 plus advance of 2pi). It has the same measured Chris@119: // phase as last time around, -pi/2, which is consistent with Chris@119: // the expected phase, so the unwrapped phase is 3pi/2. Chris@119: //!!! Chris@119: // * Bin 3 is a bit of a puzzle -- it has an effectively zero Chris@119: // magnitude but a non-zero measured phase. Spectral leakage? Chris@119: // Chris@119: // * Bin 4 (Nyquist) has expected phase 4*pi, measured phase 0, Chris@119: // hence error 0 and unwrapped phase 4*pi. Chris@119: Chris@117: double phaseExpected1[] = { 999, 0, -M_PI, -M_PI/2, M_PI, 0, 999 }; Chris@117: COMPARE_ARRAY(phase, phaseExpected1); Chris@117: Chris@117: double unwExpected1[] = { 999, 0, M_PI, 3*M_PI/2, 3*M_PI, 4*M_PI, 999 }; Chris@117: COMPARE_ARRAY(unw, unwExpected1); Chris@117: Chris@119: pvoc.processTimeDomain(data + 16, mag + 1, phase + 1, unw + 1); Chris@117: Chris@117: double magExpected2[] = { 999, 0, 4, 4, 0, 0, 999 }; Chris@117: COMPARE_ARRAY(mag, magExpected2); Chris@117: Chris@117: double phaseExpected2[] = { 999, 0, 0, -M_PI/2, 0, 0, 999 }; Chris@117: COMPARE_ARRAY(phase, phaseExpected2); Chris@117: Chris@117: double unwExpected2[] = { 999, 0, 2*M_PI, 7*M_PI/2, 6*M_PI, 8*M_PI, 999 }; Chris@117: COMPARE_ARRAY(unw, unwExpected2); Chris@117: } Chris@115: Chris@112: BOOST_AUTO_TEST_SUITE_END() Chris@112: