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()
|