c@131
|
1 /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
|
c@131
|
2
|
c@131
|
3 #include "cq/CQSpectrogram.h"
|
c@131
|
4
|
c@131
|
5 #include "dsp/Window.h"
|
c@131
|
6
|
c@131
|
7 #include <cmath>
|
c@131
|
8 #include <vector>
|
c@132
|
9 #include <iostream>
|
c@131
|
10
|
c@131
|
11 using std::vector;
|
c@132
|
12 using std::cerr;
|
c@132
|
13 using std::endl;
|
c@131
|
14
|
c@131
|
15 #define BOOST_TEST_DYN_LINK
|
c@131
|
16 #define BOOST_TEST_MAIN
|
c@131
|
17
|
c@131
|
18 #include <boost/test/unit_test.hpp>
|
c@131
|
19
|
c@131
|
20 BOOST_AUTO_TEST_SUITE(TestCQFrequency)
|
c@131
|
21
|
c@131
|
22 // The principle here is to feed a single windowed sinusoid into a
|
c@131
|
23 // small CQ transform and check that the output has its peak bin at
|
c@133
|
24 // the correct frequency.
|
c@131
|
25
|
c@131
|
26 // Set up fs/2 = 50, frequency range 10 -> 40 i.e. 2 octaves, fixed
|
c@131
|
27 // duration of 2 seconds
|
c@131
|
28 static const double sampleRate = 100;
|
c@139
|
29 static const double cqmin = 11.8921;
|
c@131
|
30 static const double cqmax = 40;
|
c@131
|
31 static const double bpo = 4;
|
c@131
|
32 static const int duration = sampleRate * 2;
|
c@131
|
33
|
c@132
|
34 // Threshold below which to ignore a column completely
|
c@131
|
35 static const double threshold = 0.08;
|
c@131
|
36
|
c@132
|
37 int
|
c@132
|
38 binForFrequency(double freq)
|
c@132
|
39 {
|
c@139
|
40 int bin = (bpo * 2) - round(bpo * log2(freq / cqmin)) - 1;
|
c@132
|
41 return bin;
|
c@132
|
42 }
|
c@132
|
43
|
c@131
|
44 void
|
c@135
|
45 checkCQFreqColumn(int i, vector<double> column,
|
c@135
|
46 double freq, CQSpectrogram::Interpolation interp)
|
c@131
|
47 {
|
c@132
|
48 double maxval = 0.0;
|
c@132
|
49 int maxidx = -1;
|
c@132
|
50 int height = column.size();
|
c@139
|
51
|
c@139
|
52 int nonZeroHeight = ((i % 2 == 1) ? height/2 : height);
|
c@139
|
53
|
c@139
|
54 for (int j = 0; j < nonZeroHeight; ++j) {
|
c@132
|
55 if (j == 0 || column[j] > maxval) {
|
c@132
|
56 maxval = column[j];
|
c@132
|
57 maxidx = j;
|
c@132
|
58 }
|
c@132
|
59 }
|
c@135
|
60
|
c@132
|
61 int expected = binForFrequency(freq);
|
c@132
|
62 if (maxval < threshold) {
|
c@132
|
63 return; // ignore these columns at start and end
|
c@135
|
64 } else if (expected < nonZeroHeight && maxidx != expected) {
|
c@139
|
65 cerr << "ERROR: In column " << i << " with interpolation " << interp
|
c@139
|
66 << ", maximum value for frequency " << freq
|
c@139
|
67 << "\n found at index " << maxidx
|
c@139
|
68 << " (expected index " << expected << ")" << endl;
|
c@135
|
69 cerr << "column contains: ";
|
c@135
|
70 for (int j = 0; j < height; ++j) {
|
c@135
|
71 cerr << column[j] << " ";
|
c@133
|
72 }
|
c@135
|
73 cerr << endl;
|
c@132
|
74 BOOST_CHECK_EQUAL(maxidx, expected);
|
c@132
|
75 }
|
c@131
|
76 }
|
c@131
|
77
|
c@131
|
78 void
|
c@144
|
79 testCQFrequencyWith(CQParameters params,
|
c@144
|
80 CQSpectrogram::Interpolation interp,
|
c@144
|
81 double freq)
|
c@144
|
82 {
|
c@144
|
83 CQSpectrogram cq(params, interp);
|
c@144
|
84
|
c@144
|
85 BOOST_CHECK_EQUAL(cq.getBinsPerOctave(), bpo);
|
c@144
|
86 BOOST_CHECK_EQUAL(cq.getOctaves(), 2);
|
c@144
|
87 BOOST_CHECK_CLOSE(cq.getBinFrequency(0), 40, 1e-10);
|
c@144
|
88 BOOST_CHECK_CLOSE(cq.getBinFrequency(4), 20, 1e-10);
|
c@144
|
89 BOOST_CHECK_CLOSE(cq.getBinFrequency(7), cqmin, 1e-3);
|
c@144
|
90
|
c@144
|
91 vector<double> input;
|
c@144
|
92 for (int i = 0; i < duration; ++i) {
|
c@144
|
93 input.push_back(sin((i * 2 * M_PI * freq) / sampleRate));
|
c@144
|
94 }
|
c@144
|
95 Window<double>(HanningWindow, duration).cut(input.data());
|
c@144
|
96
|
c@144
|
97 CQSpectrogram::RealBlock output = cq.process(input);
|
c@144
|
98 CQSpectrogram::RealBlock rest = cq.getRemainingOutput();
|
c@144
|
99 output.insert(output.end(), rest.begin(), rest.end());
|
c@144
|
100
|
c@144
|
101 BOOST_CHECK_EQUAL(output[0].size(),
|
c@144
|
102 cq.getBinsPerOctave() * cq.getOctaves());
|
c@144
|
103
|
c@144
|
104 for (int i = 0; i < int(output.size()); ++i) {
|
c@144
|
105 checkCQFreqColumn(i, output[i], freq, interp);
|
c@144
|
106 }
|
c@144
|
107 }
|
c@144
|
108
|
c@144
|
109 void
|
c@131
|
110 testCQFrequency(double freq)
|
c@131
|
111 {
|
c@135
|
112 vector<CQSpectrogram::Interpolation> interpolationTypes;
|
c@135
|
113 interpolationTypes.push_back(CQSpectrogram::InterpolateZeros);
|
c@135
|
114 interpolationTypes.push_back(CQSpectrogram::InterpolateHold);
|
c@135
|
115 interpolationTypes.push_back(CQSpectrogram::InterpolateLinear);
|
c@131
|
116
|
c@135
|
117 for (int k = 0; k < int(interpolationTypes.size()); ++k) {
|
c@135
|
118 CQSpectrogram::Interpolation interp = interpolationTypes[k];
|
c@135
|
119 CQParameters params(sampleRate, cqmin, cqmax, bpo);
|
c@144
|
120 testCQFrequencyWith(params, interp, freq);
|
c@132
|
121 }
|
c@131
|
122 }
|
c@131
|
123
|
c@133
|
124 BOOST_AUTO_TEST_CASE(freq_11) { testCQFrequency(11); }
|
c@140
|
125 BOOST_AUTO_TEST_CASE(freq_17) { testCQFrequency(17); }
|
c@140
|
126 BOOST_AUTO_TEST_CASE(freq_24) { testCQFrequency(24); }
|
c@139
|
127 BOOST_AUTO_TEST_CASE(freq_27) { testCQFrequency(27); }
|
c@139
|
128 BOOST_AUTO_TEST_CASE(freq_33) { testCQFrequency(33); }
|
c@131
|
129 BOOST_AUTO_TEST_CASE(freq_40) { testCQFrequency(40); }
|
c@131
|
130
|
c@131
|
131 BOOST_AUTO_TEST_SUITE_END()
|
c@131
|
132
|