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