Chris@126: Chris@126: #include "FeatureExtractor.h" Chris@126: Chris@126: #include Chris@126: #include Chris@126: #include Chris@126: Chris@126: using namespace std; Chris@126: Chris@126: #define BOOST_TEST_DYN_LINK Chris@126: #define BOOST_TEST_MAIN Chris@126: Chris@126: #include Chris@126: Chris@126: static int freq2mid(double freq) Chris@126: { Chris@165: return int(round(57.0 + 12.0 * log(freq / 220.) / log(2.))); Chris@126: } Chris@126: Chris@126: static int freq2chroma(double freq) Chris@126: { Chris@126: return freq2mid(freq) % 12; Chris@126: } Chris@126: Chris@127: static int bin2warped(int bin, int rate, int sz) Chris@127: { Chris@127: // see comments in nonchroma below Chris@127: if (bin <= 33) return bin; Chris@127: double freq = (double(bin) * rate) / sz; Chris@127: int mid = freq2mid(freq); Chris@127: if (mid > 127) mid = 127; Chris@127: int outbin = mid - 77 + 33; Chris@127: return outbin; Chris@127: } Chris@127: Chris@126: BOOST_AUTO_TEST_SUITE(TestFeatureExtractor) Chris@126: Chris@185: void checkAlternateProcessType(FeatureExtractor &fe, feature_t expected, Chris@130: vector real, vector imag) Chris@130: { Chris@130: vector in; Chris@188: for (size_t i = 0; i < real.size(); ++i) { Chris@130: in.push_back(float(real[i])); Chris@130: in.push_back(float(imag[i])); Chris@130: } Chris@185: feature_t alt = fe.process(&in[0]); Chris@130: BOOST_CHECK_EQUAL_COLLECTIONS(alt.begin(), alt.end(), Chris@130: expected.begin(), expected.end()); Chris@130: } Chris@130: Chris@126: BOOST_AUTO_TEST_CASE(chroma) Chris@126: { Chris@129: int szs[] = { 1024, 2048, 4000 }; Chris@129: int rates[] = { 44100, 48000 }; Chris@126: Chris@129: for (int irate = 0; irate < int(sizeof(rates)/sizeof(rates[0])); ++irate) { Chris@126: Chris@129: int rate = rates[irate]; Chris@126: Chris@129: for (int isz = 0; isz < int(sizeof(szs)/sizeof(szs[0])); ++isz) { Chris@129: Chris@129: int sz = szs[isz]; Chris@129: Chris@129: int hs = sz / 2 + 1; Chris@129: int fsz = 13; Chris@129: Chris@216: FeatureExtractor::Parameters params { float(rate) }; Chris@216: params.fftSize = sz; Chris@129: params.useChromaFrequencyMap = true; Chris@196: params.minFrequency = 0; Chris@129: FeatureExtractor fe(params); Chris@129: BOOST_CHECK_EQUAL(fe.getFeatureSize(), fsz); Chris@129: Chris@129: for (int bin = 0; bin < hs; ++bin) { Chris@129: Chris@129: vector real, imag; Chris@129: real.resize(hs, 0.0); Chris@129: imag.resize(hs, 0.0); Chris@126: Chris@129: real[bin] += 10.0; Chris@129: imag[bin] += 10.0; Chris@126: Chris@129: // use two input sweeps, so we can test that they are Chris@129: // properly summed into the output bin Chris@129: real[hs-bin-1] += 5.0; Chris@129: imag[hs-bin-1] += 5.0; Chris@129: Chris@185: feature_t out = fe.process(real, imag); Chris@126: Chris@130: checkAlternateProcessType(fe, out, real, imag); Chris@130: Chris@129: // We expect to find all bins are 0 except for: Chris@129: // Chris@129: // * two bins of 200 and 50 respectively, if the two input Chris@129: // bins are distinct and their output chroma are also distinct Chris@129: // Chris@129: // * one bin of value 250 (= 10^2 + 5^2), if the two input Chris@129: // bins are distinct but their output chroma are not Chris@129: // Chris@129: // * one bin of value 450 (= 15^2 + 15^2), if the input bins Chris@129: // are not distinct (the feature extractor sums energies Chris@129: // rather than magnitudes so as to integrate energy for a Chris@129: // partial in the face of spectral leakage) Chris@129: // Chris@129: // The bin corresponding to each input frequency is Chris@129: // that of its semitone value (with C=0), except that Chris@129: // input bins less than the 17th are shepherded into Chris@129: // the separate bin 0 (see docs in FeatureExtractor.h) Chris@126: Chris@129: double cutoff = (17.0 * rate) / sz; Chris@129: Chris@185: feature_t expected(fsz); Chris@126: Chris@129: double infreq1 = (double(bin) * rate) / sz; Chris@126: Chris@129: if (bin == hs-bin-1) { Chris@129: expected[freq2chroma(infreq1) + 1] += 450; Chris@129: } else { Chris@129: if (infreq1 < cutoff) { Chris@129: expected[0] += 200; Chris@129: } else { Chris@129: expected[freq2chroma(infreq1) + 1] += 200; Chris@129: } Chris@129: double infreq2 = (double(hs-bin-1) * rate) / sz; Chris@129: if (infreq2 < cutoff) { Chris@129: expected[0] += 50; Chris@129: } else { Chris@129: expected[freq2chroma(infreq2) + 1] += 50; Chris@129: } Chris@129: } Chris@126: Chris@129: BOOST_CHECK_EQUAL_COLLECTIONS(out.begin(), out.end(), Chris@129: expected.begin(), expected.end()); Chris@126: } Chris@126: } Chris@126: } Chris@126: } Chris@126: Chris@127: BOOST_AUTO_TEST_CASE(nonchroma) Chris@127: { Chris@127: int rate = 44100; Chris@127: int sz = 2048; Chris@127: int hs = sz / 2 + 1; Chris@127: int fsz = 84; Chris@127: Chris@216: FeatureExtractor::Parameters params { float(rate) }; Chris@216: params.fftSize = sz; Chris@196: params.minFrequency = 0; Chris@127: FeatureExtractor fe(params); Chris@127: BOOST_CHECK_EQUAL(fe.getFeatureSize(), fsz); Chris@127: Chris@127: for (int bin = 0; bin < hs; ++bin) { Chris@127: Chris@127: vector real, imag; Chris@127: real.resize(hs, 0.0); Chris@127: imag.resize(hs, 0.0); Chris@127: Chris@127: real[bin] += 10.0; Chris@127: imag[bin] += 10.0; Chris@127: Chris@127: // use two input sweeps, so we can test that they are properly Chris@127: // summed into the output bin Chris@127: real[hs-bin-1] += 5.0; Chris@127: imag[hs-bin-1] += 5.0; Chris@127: Chris@185: feature_t out = fe.process(real, imag); Chris@127: Chris@130: checkAlternateProcessType(fe, out, real, imag); Chris@130: Chris@127: // We expect to find all bins are 0 except for: Chris@127: // Chris@127: // * two bins of 200 and 50 respectively, if the two input Chris@127: // bins are distinct and their output bins are also distinct Chris@127: // Chris@127: // * one bin of value 250 (= 10^2 + 5^2), if the two input Chris@127: // bins are distinct but their output bins are not Chris@127: // Chris@127: // * one bin of value 450 (= 15^2 + 15^2), if the input bins Chris@128: // are not distinct (the feature extractor sums energies Chris@128: // rather than magnitudes so as to integrate energy for a Chris@128: // partial in the face of spectral leakage) Chris@127: // Chris@127: // The first 34 input bins (i.e. up to and including bin 33, Chris@127: // 733Hz, MIDI pitch 77.something) are mapped linearly, those Chris@127: // above that and up to MIDI pitch 127 (12544Hz) are mapped Chris@127: // logarithmically, remaining bins are all mapped into the Chris@127: // final output bin. Chris@127: // Chris@127: // So MIDI pitches up to and including 77 are mapped linearly Chris@127: // by frequency into 34 bins; those from 78-126 inclusive are Chris@127: // mapped linearly by MIDI pitch into the next 49 bins; Chris@127: // everything above goes into the last bin, for 84 bins total. Chris@127: Chris@185: feature_t expected(fsz); Chris@127: Chris@127: if (bin == hs-bin-1) { Chris@127: expected[bin2warped(bin, rate, sz)] += 450; Chris@127: } else { Chris@127: expected[bin2warped(bin, rate, sz)] += 200; Chris@127: expected[bin2warped(hs-bin-1, rate, sz)] += 50; Chris@127: } Chris@127: Chris@127: BOOST_CHECK_EQUAL_COLLECTIONS(out.begin(), out.end(), Chris@127: expected.begin(), expected.end()); Chris@127: } Chris@127: } Chris@127: Chris@126: BOOST_AUTO_TEST_SUITE_END()