annotate test/TestFeatureExtractor.cpp @ 129:dad9fdc32a6a refactors

Generalise chroma test at least to other frame/samplerate etc
author Chris Cannam
date Thu, 11 Dec 2014 12:54:46 +0000
parents 3f32a88ee15a
children 8e240bbea845
rev   line source
Chris@126 1
Chris@126 2 #include "FeatureExtractor.h"
Chris@126 3
Chris@126 4 #include <vector>
Chris@126 5 #include <iostream>
Chris@126 6 #include <cmath>
Chris@126 7
Chris@126 8 using namespace std;
Chris@126 9
Chris@126 10 #define BOOST_TEST_DYN_LINK
Chris@126 11 #define BOOST_TEST_MAIN
Chris@126 12
Chris@126 13 #include <boost/test/unit_test.hpp>
Chris@126 14
Chris@126 15 static int freq2mid(double freq)
Chris@126 16 {
Chris@126 17 return round(57.0 + 12.0 * log(freq / 220.) / log(2.));
Chris@126 18 }
Chris@126 19
Chris@126 20 static int freq2chroma(double freq)
Chris@126 21 {
Chris@126 22 return freq2mid(freq) % 12;
Chris@126 23 }
Chris@126 24
Chris@127 25 static int bin2warped(int bin, int rate, int sz)
Chris@127 26 {
Chris@127 27 // see comments in nonchroma below
Chris@127 28 if (bin <= 33) return bin;
Chris@127 29 double freq = (double(bin) * rate) / sz;
Chris@127 30 int mid = freq2mid(freq);
Chris@127 31 if (mid > 127) mid = 127;
Chris@127 32 int outbin = mid - 77 + 33;
Chris@127 33 return outbin;
Chris@127 34 }
Chris@127 35
Chris@126 36 BOOST_AUTO_TEST_SUITE(TestFeatureExtractor)
Chris@126 37
Chris@126 38 BOOST_AUTO_TEST_CASE(chroma)
Chris@126 39 {
Chris@129 40 int szs[] = { 1024, 2048, 4000 };
Chris@129 41 int rates[] = { 44100, 48000 };
Chris@126 42
Chris@129 43 for (int irate = 0; irate < int(sizeof(rates)/sizeof(rates[0])); ++irate) {
Chris@126 44
Chris@129 45 int rate = rates[irate];
Chris@126 46
Chris@129 47 for (int isz = 0; isz < int(sizeof(szs)/sizeof(szs[0])); ++isz) {
Chris@129 48
Chris@129 49 int sz = szs[isz];
Chris@129 50
Chris@129 51 int hs = sz / 2 + 1;
Chris@129 52 int fsz = 13;
Chris@129 53
Chris@129 54 FeatureExtractor::Parameters params(rate, sz);
Chris@129 55 params.useChromaFrequencyMap = true;
Chris@129 56 FeatureExtractor fe(params);
Chris@129 57 BOOST_CHECK_EQUAL(fe.getFeatureSize(), fsz);
Chris@129 58
Chris@129 59 for (int bin = 0; bin < hs; ++bin) {
Chris@129 60
Chris@129 61 vector<double> real, imag;
Chris@129 62 real.resize(hs, 0.0);
Chris@129 63 imag.resize(hs, 0.0);
Chris@126 64
Chris@129 65 real[bin] += 10.0;
Chris@129 66 imag[bin] += 10.0;
Chris@126 67
Chris@129 68 // use two input sweeps, so we can test that they are
Chris@129 69 // properly summed into the output bin
Chris@129 70 real[hs-bin-1] += 5.0;
Chris@129 71 imag[hs-bin-1] += 5.0;
Chris@129 72
Chris@129 73 vector<double> out = fe.process(real, imag);
Chris@126 74
Chris@129 75 // We expect to find all bins are 0 except for:
Chris@129 76 //
Chris@129 77 // * two bins of 200 and 50 respectively, if the two input
Chris@129 78 // bins are distinct and their output chroma are also distinct
Chris@129 79 //
Chris@129 80 // * one bin of value 250 (= 10^2 + 5^2), if the two input
Chris@129 81 // bins are distinct but their output chroma are not
Chris@129 82 //
Chris@129 83 // * one bin of value 450 (= 15^2 + 15^2), if the input bins
Chris@129 84 // are not distinct (the feature extractor sums energies
Chris@129 85 // rather than magnitudes so as to integrate energy for a
Chris@129 86 // partial in the face of spectral leakage)
Chris@129 87 //
Chris@129 88 // The bin corresponding to each input frequency is
Chris@129 89 // that of its semitone value (with C=0), except that
Chris@129 90 // input bins less than the 17th are shepherded into
Chris@129 91 // the separate bin 0 (see docs in FeatureExtractor.h)
Chris@126 92
Chris@129 93 double cutoff = (17.0 * rate) / sz;
Chris@129 94
Chris@129 95 vector<double> expected(fsz);
Chris@126 96
Chris@129 97 double infreq1 = (double(bin) * rate) / sz;
Chris@126 98
Chris@129 99 if (bin == hs-bin-1) {
Chris@129 100 expected[freq2chroma(infreq1) + 1] += 450;
Chris@129 101 } else {
Chris@129 102 if (infreq1 < cutoff) {
Chris@129 103 expected[0] += 200;
Chris@129 104 } else {
Chris@129 105 expected[freq2chroma(infreq1) + 1] += 200;
Chris@129 106 }
Chris@129 107 double infreq2 = (double(hs-bin-1) * rate) / sz;
Chris@129 108 if (infreq2 < cutoff) {
Chris@129 109 expected[0] += 50;
Chris@129 110 } else {
Chris@129 111 expected[freq2chroma(infreq2) + 1] += 50;
Chris@129 112 }
Chris@129 113 }
Chris@126 114
Chris@129 115 BOOST_CHECK_EQUAL_COLLECTIONS(out.begin(), out.end(),
Chris@129 116 expected.begin(), expected.end());
Chris@126 117 }
Chris@126 118 }
Chris@126 119 }
Chris@126 120 }
Chris@126 121
Chris@127 122 BOOST_AUTO_TEST_CASE(nonchroma)
Chris@127 123 {
Chris@127 124 int rate = 44100;
Chris@127 125 int sz = 2048;
Chris@127 126 int hs = sz / 2 + 1;
Chris@127 127 int fsz = 84;
Chris@127 128
Chris@127 129 FeatureExtractor::Parameters params(rate, sz);
Chris@127 130 FeatureExtractor fe(params);
Chris@127 131 BOOST_CHECK_EQUAL(fe.getFeatureSize(), fsz);
Chris@127 132
Chris@127 133 for (int bin = 0; bin < hs; ++bin) {
Chris@127 134
Chris@127 135 vector<double> real, imag;
Chris@127 136 real.resize(hs, 0.0);
Chris@127 137 imag.resize(hs, 0.0);
Chris@127 138
Chris@127 139 real[bin] += 10.0;
Chris@127 140 imag[bin] += 10.0;
Chris@127 141
Chris@127 142 // use two input sweeps, so we can test that they are properly
Chris@127 143 // summed into the output bin
Chris@127 144 real[hs-bin-1] += 5.0;
Chris@127 145 imag[hs-bin-1] += 5.0;
Chris@127 146
Chris@127 147 vector<double> out = fe.process(real, imag);
Chris@127 148
Chris@127 149 // We expect to find all bins are 0 except for:
Chris@127 150 //
Chris@127 151 // * two bins of 200 and 50 respectively, if the two input
Chris@127 152 // bins are distinct and their output bins are also distinct
Chris@127 153 //
Chris@127 154 // * one bin of value 250 (= 10^2 + 5^2), if the two input
Chris@127 155 // bins are distinct but their output bins are not
Chris@127 156 //
Chris@127 157 // * one bin of value 450 (= 15^2 + 15^2), if the input bins
Chris@128 158 // are not distinct (the feature extractor sums energies
Chris@128 159 // rather than magnitudes so as to integrate energy for a
Chris@128 160 // partial in the face of spectral leakage)
Chris@127 161 //
Chris@127 162 // The first 34 input bins (i.e. up to and including bin 33,
Chris@127 163 // 733Hz, MIDI pitch 77.something) are mapped linearly, those
Chris@127 164 // above that and up to MIDI pitch 127 (12544Hz) are mapped
Chris@127 165 // logarithmically, remaining bins are all mapped into the
Chris@127 166 // final output bin.
Chris@127 167 //
Chris@127 168 // So MIDI pitches up to and including 77 are mapped linearly
Chris@127 169 // by frequency into 34 bins; those from 78-126 inclusive are
Chris@127 170 // mapped linearly by MIDI pitch into the next 49 bins;
Chris@127 171 // everything above goes into the last bin, for 84 bins total.
Chris@127 172
Chris@127 173 vector<double> expected(fsz);
Chris@127 174
Chris@127 175 if (bin == hs-bin-1) {
Chris@127 176 expected[bin2warped(bin, rate, sz)] += 450;
Chris@127 177 } else {
Chris@127 178 expected[bin2warped(bin, rate, sz)] += 200;
Chris@127 179 expected[bin2warped(hs-bin-1, rate, sz)] += 50;
Chris@127 180 }
Chris@127 181
Chris@127 182 BOOST_CHECK_EQUAL_COLLECTIONS(out.begin(), out.end(),
Chris@127 183 expected.begin(), expected.end());
Chris@127 184 }
Chris@127 185 }
Chris@127 186
Chris@126 187 BOOST_AUTO_TEST_SUITE_END()