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@131
|
29 static const double cqmin = 10;
|
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@132
|
40 int bin = (2 * bpo) - round(bpo * log2(freq / cqmin));
|
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@132
|
51 for (int j = 0; j < height; ++j) {
|
c@132
|
52 if (j == 0 || column[j] > maxval) {
|
c@132
|
53 maxval = column[j];
|
c@132
|
54 maxidx = j;
|
c@132
|
55 }
|
c@132
|
56 }
|
c@135
|
57
|
c@135
|
58 int nonZeroHeight = height;
|
c@135
|
59 if (interp == CQSpectrogram::InterpolateZeros && i % 2 == 1) {
|
c@135
|
60 nonZeroHeight = height / 2;
|
c@135
|
61 }
|
c@135
|
62
|
c@132
|
63 int expected = binForFrequency(freq);
|
c@132
|
64 if (maxval < threshold) {
|
c@132
|
65 return; // ignore these columns at start and end
|
c@135
|
66 } else if (expected < nonZeroHeight && maxidx != expected) {
|
c@135
|
67 cerr << "ERROR: In column " << i << ", maximum value for frequency "
|
c@135
|
68 << freq << " found at index " << maxidx << endl
|
c@135
|
69 << "(expected index " << expected << ")" << endl;
|
c@135
|
70 cerr << "column contains: ";
|
c@135
|
71 for (int j = 0; j < height; ++j) {
|
c@135
|
72 cerr << column[j] << " ";
|
c@133
|
73 }
|
c@135
|
74 cerr << endl;
|
c@132
|
75 BOOST_CHECK_EQUAL(maxidx, expected);
|
c@132
|
76 }
|
c@131
|
77 }
|
c@131
|
78
|
c@131
|
79 void
|
c@131
|
80 testCQFrequency(double freq)
|
c@131
|
81 {
|
c@135
|
82 vector<CQSpectrogram::Interpolation> interpolationTypes;
|
c@135
|
83 interpolationTypes.push_back(CQSpectrogram::InterpolateZeros);
|
c@135
|
84 interpolationTypes.push_back(CQSpectrogram::InterpolateHold);
|
c@135
|
85 interpolationTypes.push_back(CQSpectrogram::InterpolateLinear);
|
c@131
|
86
|
c@135
|
87 for (int k = 0; k < int(interpolationTypes.size()); ++k) {
|
c@132
|
88
|
c@135
|
89 CQSpectrogram::Interpolation interp = interpolationTypes[k];
|
c@131
|
90
|
c@135
|
91 CQParameters params(sampleRate, cqmin, cqmax, bpo);
|
c@135
|
92 CQSpectrogram cq(params, interp);
|
c@131
|
93
|
c@135
|
94 BOOST_CHECK_EQUAL(cq.getBinsPerOctave(), bpo);
|
c@135
|
95 BOOST_CHECK_EQUAL(cq.getOctaves(), 2);
|
c@132
|
96
|
c@135
|
97 vector<double> input;
|
c@135
|
98 for (int i = 0; i < duration; ++i) {
|
c@135
|
99 input.push_back(sin((i * 2 * M_PI * freq) / sampleRate));
|
c@135
|
100 }
|
c@135
|
101 Window<double>(HanningWindow, duration).cut(input.data());
|
c@135
|
102
|
c@135
|
103 CQSpectrogram::RealBlock output = cq.process(input);
|
c@135
|
104 CQSpectrogram::RealBlock rest = cq.getRemainingOutput();
|
c@135
|
105 output.insert(output.end(), rest.begin(), rest.end());
|
c@135
|
106
|
c@135
|
107 BOOST_CHECK_EQUAL(output[0].size(),
|
c@135
|
108 cq.getBinsPerOctave() * cq.getOctaves());
|
c@135
|
109
|
c@135
|
110 for (int i = 0; i < int(output.size()); ++i) {
|
c@135
|
111 checkCQFreqColumn(i, output[i], freq, interp);
|
c@135
|
112 }
|
c@132
|
113 }
|
c@131
|
114 }
|
c@131
|
115
|
c@133
|
116 BOOST_AUTO_TEST_CASE(freq_11) { testCQFrequency(11); }
|
c@131
|
117 BOOST_AUTO_TEST_CASE(freq_15) { testCQFrequency(15); }
|
c@131
|
118 BOOST_AUTO_TEST_CASE(freq_20) { testCQFrequency(20); }
|
c@131
|
119 BOOST_AUTO_TEST_CASE(freq_25) { testCQFrequency(25); }
|
c@131
|
120 BOOST_AUTO_TEST_CASE(freq_30) { testCQFrequency(30); }
|
c@131
|
121 BOOST_AUTO_TEST_CASE(freq_35) { testCQFrequency(35); }
|
c@131
|
122 BOOST_AUTO_TEST_CASE(freq_40) { testCQFrequency(40); }
|
c@131
|
123
|
c@131
|
124 BOOST_AUTO_TEST_SUITE_END()
|
c@131
|
125
|