c@116
|
1 /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
|
c@116
|
2 /*
|
c@116
|
3 Constant-Q library
|
c@116
|
4 Copyright (c) 2013-2014 Queen Mary, University of London
|
c@116
|
5
|
c@116
|
6 Permission is hereby granted, free of charge, to any person
|
c@116
|
7 obtaining a copy of this software and associated documentation
|
c@116
|
8 files (the "Software"), to deal in the Software without
|
c@116
|
9 restriction, including without limitation the rights to use, copy,
|
c@116
|
10 modify, merge, publish, distribute, sublicense, and/or sell copies
|
c@116
|
11 of the Software, and to permit persons to whom the Software is
|
c@116
|
12 furnished to do so, subject to the following conditions:
|
c@116
|
13
|
c@116
|
14 The above copyright notice and this permission notice shall be
|
c@116
|
15 included in all copies or substantial portions of the Software.
|
c@116
|
16
|
c@116
|
17 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
c@116
|
18 EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
c@116
|
19 MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
c@116
|
20 NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
|
c@116
|
21 CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
|
c@116
|
22 CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
c@116
|
23 WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
c@116
|
24
|
c@116
|
25 Except as contained in this notice, the names of the Centre for
|
c@116
|
26 Digital Music; Queen Mary, University of London; and Chris Cannam
|
c@116
|
27 shall not be used in advertising or otherwise to promote the sale,
|
c@116
|
28 use or other dealings in this Software without prior written
|
c@116
|
29 authorization.
|
c@116
|
30 */
|
c@116
|
31
|
c@116
|
32 #include "CQSpectrogram.h"
|
c@116
|
33
|
c@116
|
34 #include <iostream>
|
c@116
|
35 #include <stdexcept>
|
c@116
|
36
|
c@116
|
37 using std::cerr;
|
c@116
|
38 using std::endl;
|
c@116
|
39
|
c@156
|
40 //#define DEBUG_CQSPECTROGRAM 1
|
c@156
|
41
|
c@127
|
42 CQSpectrogram::CQSpectrogram(CQParameters params,
|
c@127
|
43 Interpolation interpolation) :
|
c@127
|
44 m_cq(params),
|
c@116
|
45 m_interpolation(interpolation)
|
c@116
|
46 {
|
c@116
|
47 }
|
c@116
|
48
|
c@116
|
49 CQSpectrogram::~CQSpectrogram()
|
c@116
|
50 {
|
c@116
|
51 }
|
c@116
|
52
|
c@116
|
53 CQSpectrogram::RealBlock
|
c@116
|
54 CQSpectrogram::process(const RealSequence &td)
|
c@116
|
55 {
|
c@116
|
56 return postProcess(m_cq.process(td), false);
|
c@116
|
57 }
|
c@116
|
58
|
c@116
|
59 CQSpectrogram::RealBlock
|
c@116
|
60 CQSpectrogram::getRemainingOutput()
|
c@116
|
61 {
|
c@116
|
62 return postProcess(m_cq.getRemainingOutput(), true);
|
c@116
|
63 }
|
c@116
|
64
|
c@116
|
65 CQSpectrogram::RealBlock
|
c@116
|
66 CQSpectrogram::postProcess(const ComplexBlock &cq, bool insist)
|
c@116
|
67 {
|
c@116
|
68 int width = cq.size();
|
c@116
|
69
|
c@116
|
70 // convert to magnitudes
|
c@116
|
71 RealBlock spec;
|
c@116
|
72 for (int i = 0; i < width; ++i) {
|
c@116
|
73 int height = cq[i].size();
|
c@116
|
74 RealColumn col(height, 0);
|
c@116
|
75 for (int j = 0; j < height; ++j) {
|
c@156
|
76 #ifdef DEBUG_CQSPECTROGRAM
|
c@147
|
77 if (isnan(cq[i][j].real())) {
|
c@147
|
78 cerr << "WARNING: NaN in real at (" << i << "," << j << ")" << endl;
|
c@147
|
79 }
|
c@147
|
80 if (isnan(cq[i][j].imag())) {
|
c@147
|
81 cerr << "WARNING: NaN in imag at (" << i << "," << j << ")" << endl;
|
c@147
|
82 }
|
c@156
|
83 #endif
|
c@116
|
84 col[j] = abs(cq[i][j]);
|
c@116
|
85 }
|
c@116
|
86 spec.push_back(col);
|
c@116
|
87 }
|
c@116
|
88
|
c@116
|
89 if (m_interpolation == InterpolateZeros) {
|
c@116
|
90 for (int i = 0; i < width; ++i) {
|
c@116
|
91 int sh = spec[i].size();
|
c@116
|
92 int fh = getTotalBins();
|
c@116
|
93 for (int j = sh; j < fh; ++j) {
|
c@116
|
94 spec[i].push_back(0);
|
c@116
|
95 }
|
c@116
|
96 }
|
c@116
|
97 return spec;
|
c@116
|
98 }
|
c@116
|
99
|
c@116
|
100 for (int i = 0; i < width; ++i) {
|
c@116
|
101 m_buffer.push_back(spec[i]);
|
c@116
|
102 }
|
c@116
|
103
|
c@116
|
104 if (m_interpolation == InterpolateHold) {
|
c@116
|
105 return fetchHold(insist);
|
c@116
|
106 } else {
|
c@116
|
107 return fetchLinear(insist);
|
c@116
|
108 }
|
c@116
|
109 }
|
c@116
|
110
|
c@116
|
111 CQSpectrogram::RealBlock
|
c@116
|
112 CQSpectrogram::fetchHold(bool)
|
c@116
|
113 {
|
c@116
|
114 RealBlock out;
|
c@116
|
115
|
c@116
|
116 int width = m_buffer.size();
|
c@116
|
117 int height = getTotalBins();
|
c@116
|
118
|
c@116
|
119 for (int i = 0; i < width; ++i) {
|
c@116
|
120
|
c@116
|
121 RealColumn col = m_buffer[i];
|
c@116
|
122
|
c@116
|
123 int thisHeight = col.size();
|
c@116
|
124 int prevHeight = m_prevColumn.size();
|
c@116
|
125
|
c@116
|
126 for (int j = thisHeight; j < height; ++j) {
|
c@116
|
127 if (j < prevHeight) {
|
c@116
|
128 col.push_back(m_prevColumn[j]);
|
c@116
|
129 } else {
|
c@116
|
130 col.push_back(0.0);
|
c@116
|
131 }
|
c@116
|
132 }
|
c@116
|
133
|
c@116
|
134 m_prevColumn = col;
|
c@116
|
135 out.push_back(col);
|
c@116
|
136 }
|
c@116
|
137
|
c@116
|
138 m_buffer.clear();
|
c@116
|
139
|
c@116
|
140 return out;
|
c@116
|
141 }
|
c@116
|
142
|
c@116
|
143 CQSpectrogram::RealBlock
|
c@116
|
144 CQSpectrogram::fetchLinear(bool insist)
|
c@116
|
145 {
|
c@116
|
146 RealBlock out;
|
c@116
|
147
|
c@116
|
148 //!!! This is surprisingly messy. I must be missing something.
|
c@116
|
149
|
c@116
|
150 // We can only return any data when we have at least one column
|
c@116
|
151 // that has the full height in the buffer, that is not the first
|
c@116
|
152 // column.
|
c@116
|
153 //
|
c@116
|
154 // If the first col has full height, and there is another one
|
c@116
|
155 // later that also does, then we can interpolate between those, up
|
c@116
|
156 // to but not including the second full height column. Then we
|
c@116
|
157 // drop and return the columns we interpolated, leaving the second
|
c@116
|
158 // full-height col as the first col in the buffer. And repeat as
|
c@116
|
159 // long as enough columns are available.
|
c@116
|
160 //
|
c@116
|
161 // If the first col does not have full height, then (so long as
|
c@116
|
162 // we're following the logic above) we must simply have not yet
|
c@116
|
163 // reached the first full-height column in the CQ output, and we
|
c@116
|
164 // can interpolate nothing.
|
c@116
|
165
|
c@116
|
166 int width = m_buffer.size();
|
c@116
|
167 int height = getTotalBins();
|
c@116
|
168
|
c@116
|
169 if (width == 0) return out;
|
c@116
|
170
|
c@116
|
171 int firstFullHeight = -1;
|
c@116
|
172 int secondFullHeight = -1;
|
c@116
|
173
|
c@116
|
174 for (int i = 0; i < width; ++i) {
|
c@116
|
175 if ((int)m_buffer[i].size() == height) {
|
c@116
|
176 if (firstFullHeight == -1) {
|
c@116
|
177 firstFullHeight = i;
|
c@116
|
178 } else if (secondFullHeight == -1) {
|
c@116
|
179 secondFullHeight = i;
|
c@116
|
180 break;
|
c@116
|
181 }
|
c@116
|
182 }
|
c@116
|
183 }
|
c@116
|
184
|
c@116
|
185 // cerr << "fetchLinear: firstFullHeight = " << firstFullHeight << ", secondFullHeight = " << secondFullHeight << endl;
|
c@116
|
186
|
c@116
|
187 if (firstFullHeight < 0) {
|
c@116
|
188 if (insist) {
|
c@116
|
189 return fetchHold(true);
|
c@116
|
190 } else {
|
c@116
|
191 return out;
|
c@116
|
192 }
|
c@116
|
193 } else if (firstFullHeight > 0) {
|
c@116
|
194 // can interpolate nothing, stash up to first full height & recurse
|
c@116
|
195 out = RealBlock(m_buffer.begin(), m_buffer.begin() + firstFullHeight);
|
c@116
|
196 m_buffer = RealBlock(m_buffer.begin() + firstFullHeight, m_buffer.end());
|
c@116
|
197 RealBlock more = fetchLinear(insist);
|
c@116
|
198 out.insert(out.end(), more.begin(), more.end());
|
c@116
|
199 return out;
|
c@116
|
200 } else if (secondFullHeight < 0) {
|
c@116
|
201 // firstFullHeight == 0, but there is no second full height --
|
c@116
|
202 // wait for it unless insist flag is set
|
c@116
|
203 if (insist) {
|
c@116
|
204 return fetchHold(true);
|
c@116
|
205 } else {
|
c@116
|
206 return out;
|
c@116
|
207 }
|
c@116
|
208 } else {
|
c@116
|
209 // firstFullHeight == 0 and secondFullHeight also valid. Can interpolate
|
c@116
|
210 out = linearInterpolated(m_buffer, 0, secondFullHeight);
|
c@116
|
211 m_buffer = RealBlock(m_buffer.begin() + secondFullHeight, m_buffer.end());
|
c@116
|
212 RealBlock more = fetchLinear(insist);
|
c@116
|
213 out.insert(out.end(), more.begin(), more.end());
|
c@116
|
214 return out;
|
c@116
|
215 }
|
c@116
|
216 }
|
c@116
|
217
|
c@116
|
218 CQSpectrogram::RealBlock
|
c@116
|
219 CQSpectrogram::linearInterpolated(const RealBlock &g, int x0, int x1)
|
c@116
|
220 {
|
c@116
|
221 // g must be a grid with full-height columns at x0 and x1
|
c@116
|
222
|
c@116
|
223 if (x0 >= x1) {
|
c@116
|
224 throw std::logic_error("x0 >= x1");
|
c@116
|
225 }
|
c@116
|
226 if (x1 >= (int)g.size()) {
|
c@116
|
227 throw std::logic_error("x1 >= g.size()");
|
c@116
|
228 }
|
c@116
|
229 if (g[x0].size() != g[x1].size()) {
|
c@116
|
230 throw std::logic_error("x0 and x1 are not the same height");
|
c@116
|
231 }
|
c@116
|
232
|
c@116
|
233 int height = g[x0].size();
|
c@116
|
234 int width = x1 - x0;
|
c@116
|
235
|
c@116
|
236 RealBlock out(g.begin() + x0, g.begin() + x1);
|
c@116
|
237
|
c@116
|
238 for (int y = 0; y < height; ++y) {
|
c@116
|
239
|
c@116
|
240 int spacing = width;
|
c@116
|
241 for (int i = 1; i < width; ++i) {
|
c@116
|
242 int thisHeight = g[x0 + i].size();
|
c@116
|
243 if (thisHeight > height) {
|
c@116
|
244 throw std::logic_error("First column not full-height");
|
c@116
|
245 }
|
c@116
|
246 if (thisHeight > y) {
|
c@116
|
247 spacing = i;
|
c@116
|
248 break;
|
c@116
|
249 }
|
c@116
|
250 }
|
c@116
|
251
|
c@116
|
252 if (spacing < 2) continue;
|
c@116
|
253
|
c@116
|
254 for (int i = 0; i + spacing <= width; i += spacing) {
|
c@116
|
255 for (int j = 1; j < spacing; ++j) {
|
c@116
|
256 double proportion = double(j)/double(spacing);
|
c@116
|
257 double interpolated =
|
c@116
|
258 g[x0 + i][y] * (1.0 - proportion) +
|
c@116
|
259 g[x0 + i + spacing][y] * proportion;
|
c@116
|
260 out[i + j].push_back(interpolated);
|
c@116
|
261 }
|
c@116
|
262 }
|
c@116
|
263 }
|
c@116
|
264
|
c@116
|
265 return out;
|
c@116
|
266 }
|
c@116
|
267
|
c@116
|
268
|
c@116
|
269
|