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@165
|
17 return int(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@185
|
38 void checkAlternateProcessType(FeatureExtractor &fe, feature_t expected,
|
Chris@130
|
39 vector<double> real, vector<double> imag)
|
Chris@130
|
40 {
|
Chris@130
|
41 vector<float> in;
|
Chris@188
|
42 for (size_t i = 0; i < real.size(); ++i) {
|
Chris@130
|
43 in.push_back(float(real[i]));
|
Chris@130
|
44 in.push_back(float(imag[i]));
|
Chris@130
|
45 }
|
Chris@185
|
46 feature_t alt = fe.process(&in[0]);
|
Chris@130
|
47 BOOST_CHECK_EQUAL_COLLECTIONS(alt.begin(), alt.end(),
|
Chris@130
|
48 expected.begin(), expected.end());
|
Chris@130
|
49 }
|
Chris@130
|
50
|
Chris@126
|
51 BOOST_AUTO_TEST_CASE(chroma)
|
Chris@126
|
52 {
|
Chris@129
|
53 int szs[] = { 1024, 2048, 4000 };
|
Chris@129
|
54 int rates[] = { 44100, 48000 };
|
Chris@126
|
55
|
Chris@129
|
56 for (int irate = 0; irate < int(sizeof(rates)/sizeof(rates[0])); ++irate) {
|
Chris@126
|
57
|
Chris@129
|
58 int rate = rates[irate];
|
Chris@126
|
59
|
Chris@129
|
60 for (int isz = 0; isz < int(sizeof(szs)/sizeof(szs[0])); ++isz) {
|
Chris@129
|
61
|
Chris@129
|
62 int sz = szs[isz];
|
Chris@129
|
63
|
Chris@129
|
64 int hs = sz / 2 + 1;
|
Chris@129
|
65 int fsz = 13;
|
Chris@129
|
66
|
Chris@188
|
67 FeatureExtractor::Parameters params(float(rate), sz);
|
Chris@129
|
68 params.useChromaFrequencyMap = true;
|
Chris@196
|
69 params.minFrequency = 0;
|
Chris@129
|
70 FeatureExtractor fe(params);
|
Chris@129
|
71 BOOST_CHECK_EQUAL(fe.getFeatureSize(), fsz);
|
Chris@129
|
72
|
Chris@129
|
73 for (int bin = 0; bin < hs; ++bin) {
|
Chris@129
|
74
|
Chris@129
|
75 vector<double> real, imag;
|
Chris@129
|
76 real.resize(hs, 0.0);
|
Chris@129
|
77 imag.resize(hs, 0.0);
|
Chris@126
|
78
|
Chris@129
|
79 real[bin] += 10.0;
|
Chris@129
|
80 imag[bin] += 10.0;
|
Chris@126
|
81
|
Chris@129
|
82 // use two input sweeps, so we can test that they are
|
Chris@129
|
83 // properly summed into the output bin
|
Chris@129
|
84 real[hs-bin-1] += 5.0;
|
Chris@129
|
85 imag[hs-bin-1] += 5.0;
|
Chris@129
|
86
|
Chris@185
|
87 feature_t out = fe.process(real, imag);
|
Chris@126
|
88
|
Chris@130
|
89 checkAlternateProcessType(fe, out, real, imag);
|
Chris@130
|
90
|
Chris@129
|
91 // We expect to find all bins are 0 except for:
|
Chris@129
|
92 //
|
Chris@129
|
93 // * two bins of 200 and 50 respectively, if the two input
|
Chris@129
|
94 // bins are distinct and their output chroma are also distinct
|
Chris@129
|
95 //
|
Chris@129
|
96 // * one bin of value 250 (= 10^2 + 5^2), if the two input
|
Chris@129
|
97 // bins are distinct but their output chroma are not
|
Chris@129
|
98 //
|
Chris@129
|
99 // * one bin of value 450 (= 15^2 + 15^2), if the input bins
|
Chris@129
|
100 // are not distinct (the feature extractor sums energies
|
Chris@129
|
101 // rather than magnitudes so as to integrate energy for a
|
Chris@129
|
102 // partial in the face of spectral leakage)
|
Chris@129
|
103 //
|
Chris@129
|
104 // The bin corresponding to each input frequency is
|
Chris@129
|
105 // that of its semitone value (with C=0), except that
|
Chris@129
|
106 // input bins less than the 17th are shepherded into
|
Chris@129
|
107 // the separate bin 0 (see docs in FeatureExtractor.h)
|
Chris@126
|
108
|
Chris@129
|
109 double cutoff = (17.0 * rate) / sz;
|
Chris@129
|
110
|
Chris@185
|
111 feature_t expected(fsz);
|
Chris@126
|
112
|
Chris@129
|
113 double infreq1 = (double(bin) * rate) / sz;
|
Chris@126
|
114
|
Chris@129
|
115 if (bin == hs-bin-1) {
|
Chris@129
|
116 expected[freq2chroma(infreq1) + 1] += 450;
|
Chris@129
|
117 } else {
|
Chris@129
|
118 if (infreq1 < cutoff) {
|
Chris@129
|
119 expected[0] += 200;
|
Chris@129
|
120 } else {
|
Chris@129
|
121 expected[freq2chroma(infreq1) + 1] += 200;
|
Chris@129
|
122 }
|
Chris@129
|
123 double infreq2 = (double(hs-bin-1) * rate) / sz;
|
Chris@129
|
124 if (infreq2 < cutoff) {
|
Chris@129
|
125 expected[0] += 50;
|
Chris@129
|
126 } else {
|
Chris@129
|
127 expected[freq2chroma(infreq2) + 1] += 50;
|
Chris@129
|
128 }
|
Chris@129
|
129 }
|
Chris@126
|
130
|
Chris@129
|
131 BOOST_CHECK_EQUAL_COLLECTIONS(out.begin(), out.end(),
|
Chris@129
|
132 expected.begin(), expected.end());
|
Chris@126
|
133 }
|
Chris@126
|
134 }
|
Chris@126
|
135 }
|
Chris@126
|
136 }
|
Chris@126
|
137
|
Chris@127
|
138 BOOST_AUTO_TEST_CASE(nonchroma)
|
Chris@127
|
139 {
|
Chris@127
|
140 int rate = 44100;
|
Chris@127
|
141 int sz = 2048;
|
Chris@127
|
142 int hs = sz / 2 + 1;
|
Chris@127
|
143 int fsz = 84;
|
Chris@127
|
144
|
Chris@188
|
145 FeatureExtractor::Parameters params(float(rate), sz);
|
Chris@196
|
146 params.minFrequency = 0;
|
Chris@127
|
147 FeatureExtractor fe(params);
|
Chris@127
|
148 BOOST_CHECK_EQUAL(fe.getFeatureSize(), fsz);
|
Chris@127
|
149
|
Chris@127
|
150 for (int bin = 0; bin < hs; ++bin) {
|
Chris@127
|
151
|
Chris@127
|
152 vector<double> real, imag;
|
Chris@127
|
153 real.resize(hs, 0.0);
|
Chris@127
|
154 imag.resize(hs, 0.0);
|
Chris@127
|
155
|
Chris@127
|
156 real[bin] += 10.0;
|
Chris@127
|
157 imag[bin] += 10.0;
|
Chris@127
|
158
|
Chris@127
|
159 // use two input sweeps, so we can test that they are properly
|
Chris@127
|
160 // summed into the output bin
|
Chris@127
|
161 real[hs-bin-1] += 5.0;
|
Chris@127
|
162 imag[hs-bin-1] += 5.0;
|
Chris@127
|
163
|
Chris@185
|
164 feature_t out = fe.process(real, imag);
|
Chris@127
|
165
|
Chris@130
|
166 checkAlternateProcessType(fe, out, real, imag);
|
Chris@130
|
167
|
Chris@127
|
168 // We expect to find all bins are 0 except for:
|
Chris@127
|
169 //
|
Chris@127
|
170 // * two bins of 200 and 50 respectively, if the two input
|
Chris@127
|
171 // bins are distinct and their output bins are also distinct
|
Chris@127
|
172 //
|
Chris@127
|
173 // * one bin of value 250 (= 10^2 + 5^2), if the two input
|
Chris@127
|
174 // bins are distinct but their output bins are not
|
Chris@127
|
175 //
|
Chris@127
|
176 // * one bin of value 450 (= 15^2 + 15^2), if the input bins
|
Chris@128
|
177 // are not distinct (the feature extractor sums energies
|
Chris@128
|
178 // rather than magnitudes so as to integrate energy for a
|
Chris@128
|
179 // partial in the face of spectral leakage)
|
Chris@127
|
180 //
|
Chris@127
|
181 // The first 34 input bins (i.e. up to and including bin 33,
|
Chris@127
|
182 // 733Hz, MIDI pitch 77.something) are mapped linearly, those
|
Chris@127
|
183 // above that and up to MIDI pitch 127 (12544Hz) are mapped
|
Chris@127
|
184 // logarithmically, remaining bins are all mapped into the
|
Chris@127
|
185 // final output bin.
|
Chris@127
|
186 //
|
Chris@127
|
187 // So MIDI pitches up to and including 77 are mapped linearly
|
Chris@127
|
188 // by frequency into 34 bins; those from 78-126 inclusive are
|
Chris@127
|
189 // mapped linearly by MIDI pitch into the next 49 bins;
|
Chris@127
|
190 // everything above goes into the last bin, for 84 bins total.
|
Chris@127
|
191
|
Chris@185
|
192 feature_t expected(fsz);
|
Chris@127
|
193
|
Chris@127
|
194 if (bin == hs-bin-1) {
|
Chris@127
|
195 expected[bin2warped(bin, rate, sz)] += 450;
|
Chris@127
|
196 } else {
|
Chris@127
|
197 expected[bin2warped(bin, rate, sz)] += 200;
|
Chris@127
|
198 expected[bin2warped(hs-bin-1, rate, sz)] += 50;
|
Chris@127
|
199 }
|
Chris@127
|
200
|
Chris@127
|
201 BOOST_CHECK_EQUAL_COLLECTIONS(out.begin(), out.end(),
|
Chris@127
|
202 expected.begin(), expected.end());
|
Chris@127
|
203 }
|
Chris@127
|
204 }
|
Chris@127
|
205
|
Chris@126
|
206 BOOST_AUTO_TEST_SUITE_END()
|