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