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