Chris@7
|
1 /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
|
Chris@7
|
2 /*
|
Chris@7
|
3 Low-frequency spectrogram
|
Chris@7
|
4 Copyright (c) 2014 Queen Mary, University of London
|
Chris@7
|
5
|
Chris@7
|
6 Permission is hereby granted, free of charge, to any person
|
Chris@7
|
7 obtaining a copy of this software and associated documentation
|
Chris@7
|
8 files (the "Software"), to deal in the Software without
|
Chris@7
|
9 restriction, including without limitation the rights to use, copy,
|
Chris@7
|
10 modify, merge, publish, distribute, sublicense, and/or sell copies
|
Chris@7
|
11 of the Software, and to permit persons to whom the Software is
|
Chris@7
|
12 furnished to do so, subject to the following conditions:
|
Chris@7
|
13
|
Chris@7
|
14 The above copyright notice and this permission notice shall be
|
Chris@7
|
15 included in all copies or substantial portions of the Software.
|
Chris@7
|
16
|
Chris@7
|
17 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
Chris@7
|
18 EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
Chris@7
|
19 MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
Chris@7
|
20 NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
|
Chris@7
|
21 CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
|
Chris@7
|
22 CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
Chris@7
|
23 WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
Chris@7
|
24
|
Chris@7
|
25 Except as contained in this notice, the names of the Centre for
|
Chris@7
|
26 Digital Music; Queen Mary, University of London; and Chris Cannam
|
Chris@7
|
27 shall not be used in advertising or otherwise to promote the sale,
|
Chris@7
|
28 use or other dealings in this Software without prior written
|
Chris@7
|
29 authorization.
|
Chris@7
|
30 */
|
Chris@0
|
31
|
Chris@0
|
32 #include "LowFreq.h"
|
Chris@0
|
33
|
Chris@2
|
34 #include "dsp/rateconversion/Resampler.h"
|
Chris@2
|
35 #include "dsp/transforms/FFT.h"
|
Chris@2
|
36
|
Chris@2
|
37 #include <cmath>
|
Chris@4
|
38 #include <cstdio>
|
Chris@2
|
39
|
Chris@2
|
40 using std::cerr;
|
Chris@6
|
41 using std::cout;
|
Chris@2
|
42 using std::endl;
|
Chris@2
|
43 using std::vector;
|
Chris@2
|
44
|
Chris@5
|
45 static float minFmin = 0;
|
Chris@5
|
46 static float maxFmin = 500;
|
Chris@11
|
47 static float defaultFmin = 0.5;
|
Chris@5
|
48
|
Chris@5
|
49 static float minFmax = 0.1;
|
Chris@5
|
50 static float maxFmax = 500;
|
Chris@11
|
51 static float defaultFmax = 40;
|
Chris@2
|
52
|
Chris@2
|
53 static int minN = 1;
|
Chris@7
|
54 static int maxN = 2048;
|
Chris@11
|
55 static int defaultN = 79;
|
Chris@0
|
56
|
Chris@0
|
57 LowFreq::LowFreq(float inputSampleRate) :
|
Chris@0
|
58 Plugin(inputSampleRate),
|
Chris@5
|
59 m_fmin(defaultFmin),
|
Chris@5
|
60 m_fmax(defaultFmax),
|
Chris@2
|
61 m_n(defaultN),
|
Chris@2
|
62 m_blockSize(0),
|
Chris@8
|
63 m_roundedInputRate(round(inputSampleRate)),
|
Chris@9
|
64 m_drop(0),
|
Chris@9
|
65 m_pad(0),
|
Chris@2
|
66 m_resampler(0),
|
Chris@4
|
67 m_fft(0),
|
Chris@4
|
68 m_window(0)
|
Chris@0
|
69 {
|
Chris@7
|
70 m_nonIntegralInputRate = false;
|
Chris@7
|
71 if (fabs(double(m_inputSampleRate) - round(m_inputSampleRate)) > 1e-6) {
|
Chris@7
|
72 m_nonIntegralInputRate = true;
|
Chris@7
|
73 }
|
Chris@7
|
74
|
Chris@7
|
75 if (inputSampleRate / 4 < maxFmax) {
|
Chris@7
|
76 maxFmax = inputSampleRate / 4;
|
Chris@7
|
77 }
|
Chris@0
|
78 }
|
Chris@0
|
79
|
Chris@0
|
80 LowFreq::~LowFreq()
|
Chris@0
|
81 {
|
Chris@2
|
82 delete m_resampler;
|
Chris@2
|
83 delete m_fft;
|
Chris@4
|
84 delete m_window;
|
Chris@0
|
85 }
|
Chris@0
|
86
|
Chris@0
|
87 string
|
Chris@0
|
88 LowFreq::getIdentifier() const
|
Chris@0
|
89 {
|
Chris@0
|
90 return "lowfreq";
|
Chris@0
|
91 }
|
Chris@0
|
92
|
Chris@0
|
93 string
|
Chris@0
|
94 LowFreq::getName() const
|
Chris@0
|
95 {
|
Chris@0
|
96 return "Low-frequency Spectrogram";
|
Chris@0
|
97 }
|
Chris@0
|
98
|
Chris@0
|
99 string
|
Chris@0
|
100 LowFreq::getDescription() const
|
Chris@0
|
101 {
|
Chris@0
|
102 //!!! Return something helpful here!
|
Chris@0
|
103 return "";
|
Chris@0
|
104 }
|
Chris@0
|
105
|
Chris@0
|
106 string
|
Chris@0
|
107 LowFreq::getMaker() const
|
Chris@0
|
108 {
|
Chris@0
|
109 return "Queen Mary, University of London";
|
Chris@0
|
110 }
|
Chris@0
|
111
|
Chris@0
|
112 int
|
Chris@0
|
113 LowFreq::getPluginVersion() const
|
Chris@0
|
114 {
|
Chris@0
|
115 return 1;
|
Chris@0
|
116 }
|
Chris@0
|
117
|
Chris@0
|
118 string
|
Chris@0
|
119 LowFreq::getCopyright() const
|
Chris@0
|
120 {
|
Chris@0
|
121 return "GPL";
|
Chris@0
|
122 }
|
Chris@0
|
123
|
Chris@0
|
124 LowFreq::InputDomain
|
Chris@0
|
125 LowFreq::getInputDomain() const
|
Chris@0
|
126 {
|
Chris@0
|
127 return TimeDomain;
|
Chris@0
|
128 }
|
Chris@0
|
129
|
Chris@0
|
130 size_t
|
Chris@0
|
131 LowFreq::getPreferredBlockSize() const
|
Chris@0
|
132 {
|
Chris@2
|
133 return 1024;
|
Chris@0
|
134 }
|
Chris@0
|
135
|
Chris@0
|
136 size_t
|
Chris@0
|
137 LowFreq::getPreferredStepSize() const
|
Chris@0
|
138 {
|
Chris@2
|
139 return 1024;
|
Chris@0
|
140 }
|
Chris@0
|
141
|
Chris@0
|
142 size_t
|
Chris@0
|
143 LowFreq::getMinChannelCount() const
|
Chris@0
|
144 {
|
Chris@0
|
145 return 1;
|
Chris@0
|
146 }
|
Chris@0
|
147
|
Chris@0
|
148 size_t
|
Chris@0
|
149 LowFreq::getMaxChannelCount() const
|
Chris@0
|
150 {
|
Chris@0
|
151 return 1;
|
Chris@0
|
152 }
|
Chris@0
|
153
|
Chris@0
|
154 LowFreq::ParameterList
|
Chris@0
|
155 LowFreq::getParameterDescriptors() const
|
Chris@0
|
156 {
|
Chris@0
|
157 ParameterList list;
|
Chris@0
|
158
|
Chris@0
|
159 ParameterDescriptor d;
|
Chris@5
|
160 d.identifier = "fmin";
|
Chris@5
|
161 d.name = "Minimum frequency";
|
Chris@5
|
162 d.description = "Low bound for frequencies to include in the spectrogram. This will be rounded up to the next highest bin frequency based on the highest frequency and number of bins.";
|
Chris@5
|
163 d.unit = "Hz";
|
Chris@5
|
164 d.minValue = minFmin;
|
Chris@5
|
165 d.maxValue = maxFmin;
|
Chris@5
|
166 d.defaultValue = defaultFmin;
|
Chris@5
|
167 d.isQuantized = false;
|
Chris@5
|
168 list.push_back(d);
|
Chris@5
|
169
|
Chris@5
|
170 d.identifier = "fmax";
|
Chris@5
|
171 d.name = "Maximum frequency";
|
Chris@5
|
172 d.description = "Highest frequency component to include in the spectrogram.";
|
Chris@5
|
173 d.unit = "Hz";
|
Chris@5
|
174 d.minValue = minFmax;
|
Chris@5
|
175 d.maxValue = maxFmax;
|
Chris@5
|
176 d.defaultValue = defaultFmax;
|
Chris@0
|
177 d.isQuantized = false;
|
Chris@0
|
178 list.push_back(d);
|
Chris@0
|
179
|
Chris@2
|
180 d.identifier = "n";
|
Chris@2
|
181 d.name = "Number of bins";
|
Chris@5
|
182 d.description = "Number of spectrogram bins to return.";
|
Chris@2
|
183 d.unit = "";
|
Chris@2
|
184 d.minValue = minN;
|
Chris@2
|
185 d.maxValue = maxN;
|
Chris@2
|
186 d.defaultValue = defaultN;
|
Chris@2
|
187 d.isQuantized = true;
|
Chris@2
|
188 d.quantizeStep = 1;
|
Chris@0
|
189 list.push_back(d);
|
Chris@0
|
190
|
Chris@0
|
191 return list;
|
Chris@0
|
192 }
|
Chris@0
|
193
|
Chris@0
|
194 float
|
Chris@0
|
195 LowFreq::getParameter(string identifier) const
|
Chris@0
|
196 {
|
Chris@5
|
197 if (identifier == "fmin") {
|
Chris@5
|
198 return m_fmin;
|
Chris@5
|
199 } else if (identifier == "fmax") {
|
Chris@5
|
200 return m_fmax;
|
Chris@2
|
201 } else if (identifier == "n") {
|
Chris@2
|
202 return m_n;
|
Chris@0
|
203 }
|
Chris@0
|
204 return 0;
|
Chris@0
|
205 }
|
Chris@0
|
206
|
Chris@0
|
207 void
|
Chris@0
|
208 LowFreq::setParameter(string identifier, float value)
|
Chris@0
|
209 {
|
Chris@5
|
210 if (identifier == "fmin") {
|
Chris@5
|
211 m_fmin = value;
|
Chris@5
|
212 } else if (identifier == "fmax") {
|
Chris@5
|
213 m_fmax = value;
|
Chris@2
|
214 } else if (identifier == "n") {
|
Chris@2
|
215 m_n = int(value + 0.01);
|
Chris@0
|
216 }
|
Chris@0
|
217 }
|
Chris@0
|
218
|
Chris@0
|
219 LowFreq::ProgramList
|
Chris@0
|
220 LowFreq::getPrograms() const
|
Chris@0
|
221 {
|
Chris@0
|
222 ProgramList list;
|
Chris@0
|
223 return list;
|
Chris@0
|
224 }
|
Chris@0
|
225
|
Chris@0
|
226 string
|
Chris@0
|
227 LowFreq::getCurrentProgram() const
|
Chris@0
|
228 {
|
Chris@0
|
229 return ""; // no programs
|
Chris@0
|
230 }
|
Chris@0
|
231
|
Chris@0
|
232 void
|
Chris@0
|
233 LowFreq::selectProgram(string name)
|
Chris@0
|
234 {
|
Chris@0
|
235 }
|
Chris@0
|
236
|
Chris@0
|
237 LowFreq::OutputList
|
Chris@0
|
238 LowFreq::getOutputDescriptors() const
|
Chris@0
|
239 {
|
Chris@0
|
240 OutputList list;
|
Chris@0
|
241
|
Chris@0
|
242 OutputDescriptor d;
|
Chris@0
|
243 d.identifier = "spectrogram";
|
Chris@0
|
244 d.name = "Spectrogram";
|
Chris@0
|
245 d.description = "";
|
Chris@0
|
246 d.unit = "";
|
Chris@0
|
247 d.hasFixedBinCount = true;
|
Chris@6
|
248 d.binCount = m_n;
|
Chris@4
|
249
|
Chris@0
|
250 d.hasKnownExtents = false;
|
Chris@0
|
251 d.isQuantized = false;
|
Chris@4
|
252 d.sampleType = OutputDescriptor::FixedSampleRate;
|
Chris@7
|
253 d.sampleRate = getOutputSampleRate();
|
Chris@4
|
254
|
Chris@10
|
255 // cerr << "output descriptor effective sample rate = " << d.sampleRate << endl;
|
Chris@10
|
256 // cerr << "input sample rate = " << m_inputSampleRate << endl;
|
Chris@10
|
257 // cerr << "input rate / output rate = " << m_inputSampleRate / d.sampleRate << endl;
|
Chris@4
|
258
|
Chris@4
|
259 char namebuf[50];
|
Chris@6
|
260 for (int i = 0; i < m_n; ++i) {
|
Chris@5
|
261 sprintf(namebuf, "%.3gHz", double(getOutputBinFrequency(i)));
|
Chris@4
|
262 d.binNames.push_back(namebuf);
|
Chris@4
|
263 }
|
Chris@4
|
264
|
Chris@0
|
265 d.hasDuration = false;
|
Chris@0
|
266 list.push_back(d);
|
Chris@0
|
267
|
Chris@0
|
268 return list;
|
Chris@0
|
269 }
|
Chris@0
|
270
|
Chris@0
|
271 bool
|
Chris@0
|
272 LowFreq::initialise(size_t channels, size_t stepSize, size_t blockSize)
|
Chris@0
|
273 {
|
Chris@0
|
274 if (channels < getMinChannelCount() ||
|
Chris@2
|
275 channels > getMaxChannelCount()) {
|
Chris@2
|
276 cerr << "LowFreq::initialise: ERROR: channels " << channels
|
Chris@2
|
277 << " out of acceptable range " << getMinChannelCount()
|
Chris@2
|
278 << " -> " << getMaxChannelCount() << endl;
|
Chris@2
|
279 return false;
|
Chris@2
|
280 }
|
Chris@0
|
281
|
Chris@2
|
282 if (stepSize != blockSize) {
|
Chris@2
|
283 // We don't actually care what the block size is, but there
|
Chris@2
|
284 // must be no overlap
|
Chris@2
|
285 cerr << "LowFreq::initialise: ERROR: step size and block size must be "
|
Chris@2
|
286 << "equal (" << stepSize << " != " << blockSize << ")" << endl;
|
Chris@2
|
287 return false;
|
Chris@2
|
288 }
|
Chris@2
|
289
|
Chris@2
|
290 if (m_n < minN || m_n > maxN) {
|
Chris@2
|
291 cerr << "LowFreq::initialise: ERROR: bin count " << m_n
|
Chris@2
|
292 << " out of acceptable range " << minN
|
Chris@2
|
293 << " -> " << maxN << endl;
|
Chris@2
|
294 return false;
|
Chris@2
|
295 }
|
Chris@2
|
296
|
Chris@5
|
297 if (m_fmin < minFmin || m_fmin > maxFmin) {
|
Chris@5
|
298 cerr << "LowFreq::initialise: ERROR: lowest frequency " << m_fmin
|
Chris@5
|
299 << " out of acceptable range " << minFmin
|
Chris@5
|
300 << " -> " << maxFmin << endl;
|
Chris@5
|
301 return false;
|
Chris@5
|
302 }
|
Chris@5
|
303
|
Chris@5
|
304 if (m_fmax < minFmax || m_fmax > maxFmax) {
|
Chris@5
|
305 cerr << "LowFreq::initialise: ERROR: highest frequency " << m_fmax
|
Chris@5
|
306 << " out of acceptable range " << minFmax
|
Chris@5
|
307 << " -> " << maxFmax << endl;
|
Chris@2
|
308 return false;
|
Chris@2
|
309 }
|
Chris@2
|
310
|
Chris@7
|
311 if (m_nonIntegralInputRate) {
|
Chris@2
|
312 cerr << "LowFreq::initialise: WARNING: input sample rate "
|
Chris@2
|
313 << m_inputSampleRate << " is non-integral, output frequencies "
|
Chris@2
|
314 << "will be skewed by rounding it to nearest integer" << endl;
|
Chris@2
|
315 }
|
Chris@2
|
316
|
Chris@2
|
317 m_blockSize = blockSize;
|
Chris@2
|
318
|
Chris@2
|
319 reset();
|
Chris@0
|
320
|
Chris@0
|
321 return true;
|
Chris@0
|
322 }
|
Chris@0
|
323
|
Chris@0
|
324 void
|
Chris@0
|
325 LowFreq::reset()
|
Chris@0
|
326 {
|
Chris@2
|
327 delete m_resampler;
|
Chris@2
|
328 delete m_fft;
|
Chris@4
|
329 delete m_window;
|
Chris@2
|
330
|
Chris@5
|
331 // We want to return a number of bins n between frequencies fmin
|
Chris@5
|
332 // and fmax.
|
Chris@5
|
333
|
Chris@10
|
334 // The bin frequency for bin i of an n-point fft is fs/n*i (where
|
Chris@6
|
335 // fs is the sample rate), with the top half aliasing the bottom
|
Chris@6
|
336 // half. To get a max bin frequency of fmax, in theory we can
|
Chris@6
|
337 // downsample to a sample rate of fmax*2 and then run an m-point
|
Chris@6
|
338 // FFT, where m is sufficient to give us n bins remaining between
|
Chris@6
|
339 // fmin and fmax. We get m by calculating fmax / ((fmax-fmin)/n).
|
Chris@5
|
340
|
Chris@5
|
341 // However, our resampler introduces artifacts close to its cutoff
|
Chris@5
|
342 // frequency within the filter transition band so we probably
|
Chris@6
|
343 // shouldn't aim to resample to fmax*2. Instead we resample to
|
Chris@6
|
344 // fmax*4 and carry out an FFT of twice the length. (We could
|
Chris@6
|
345 // resample closer to fmax, but the sums make my brain hurt.)
|
Chris@2
|
346
|
Chris@8
|
347 m_resampler = new Resampler(m_roundedInputRate,
|
Chris@4
|
348 getTargetSampleRate());
|
Chris@9
|
349
|
Chris@5
|
350 m_fft = new FFT(getFFTSize());
|
Chris@5
|
351 m_window = new Window<double>(HanningWindow, getFFTSize());
|
Chris@4
|
352 m_buffer = std::vector<double>();
|
Chris@2
|
353
|
Chris@10
|
354 /*
|
Chris@5
|
355 cerr << "LowFreq::reset: freq range " << m_fmin << " to " << m_fmax << endl;
|
Chris@4
|
356 cerr << "LowFreq::reset: block size " << m_blockSize
|
Chris@4
|
357 << ", input sample rate " << m_inputSampleRate << endl;
|
Chris@4
|
358 cerr << "LowFreq::reset: target rate " << getTargetSampleRate()
|
Chris@5
|
359 << ", target step " << getTargetStepSize()
|
Chris@4
|
360 << ", resampler latency " << m_resampler->getLatency()
|
Chris@5
|
361 << ", fft size " << getFFTSize() << endl;
|
Chris@10
|
362 */
|
Chris@5
|
363
|
Chris@9
|
364 // Resampler's declared latency is its output latency. We need to
|
Chris@9
|
365 // drop that number of samples from the start of its output...
|
Chris@9
|
366 m_drop = m_resampler->getLatency();
|
Chris@9
|
367
|
Chris@9
|
368 // ...and add enough padding at the end of its input to ensure at
|
Chris@9
|
369 // least that number of samples come back.
|
Chris@9
|
370 m_pad = int(ceil((double(m_drop) * m_roundedInputRate) /
|
Chris@9
|
371 getTargetSampleRate()));
|
Chris@9
|
372
|
Chris@9
|
373 // We also need to make sure that the FFT frames are properly
|
Chris@9
|
374 // aligned. That is, the first returned column is expected to show
|
Chris@9
|
375 // an FFT frame centred on time zero. So we need to pre-pad the
|
Chris@9
|
376 // buffer with half the FFT length.
|
Chris@9
|
377 for (int i = 0; i < getFFTSize()/2; ++i) {
|
Chris@9
|
378 m_buffer.push_back(0.0);
|
Chris@9
|
379 }
|
Chris@5
|
380 }
|
Chris@5
|
381
|
Chris@5
|
382 int
|
Chris@5
|
383 LowFreq::getTargetSampleRate() const
|
Chris@5
|
384 {
|
Chris@6
|
385 int tfs = int(ceil(m_fmax)) * 4;
|
Chris@10
|
386 // cerr << "LowFreq::getTargetSampleRate: for range " << m_fmin << " -> " << m_fmax
|
Chris@10
|
387 // << " target rate is " << tfs << endl;
|
Chris@6
|
388 return tfs;
|
Chris@5
|
389 }
|
Chris@5
|
390
|
Chris@7
|
391 float
|
Chris@7
|
392 LowFreq::getOutputSampleRate() const
|
Chris@7
|
393 {
|
Chris@7
|
394 return float(getTargetSampleRate()) / float(getTargetStepSize());
|
Chris@7
|
395 }
|
Chris@7
|
396
|
Chris@7
|
397 static int gcd(int a, int b)
|
Chris@7
|
398 {
|
Chris@7
|
399 int c = a % b;
|
Chris@7
|
400 if (c == 0) {
|
Chris@7
|
401 return b;
|
Chris@7
|
402 } else {
|
Chris@7
|
403 return gcd(b, c);
|
Chris@7
|
404 }
|
Chris@7
|
405 }
|
Chris@7
|
406
|
Chris@5
|
407 int
|
Chris@5
|
408 LowFreq::getTargetStepSize() const
|
Chris@5
|
409 {
|
Chris@7
|
410 // We need to make sure that m_inputSampleRate / output sample
|
Chris@7
|
411 // rate is an integer, otherwise hosts like SV will end up with
|
Chris@7
|
412 // rounding error when counting columns with non-integral width in
|
Chris@7
|
413 // terms of the input file sample rate.
|
Chris@7
|
414 //
|
Chris@8
|
415 // This is tricky if we allow the user to configure the step size
|
Chris@8
|
416 // (e.g. by specifying a frame overlap for the fft) because the
|
Chris@8
|
417 // output sample rate is target rate / target step size, and we
|
Chris@8
|
418 // can't easily mess with the target rate, so we need some control
|
Chris@8
|
419 // over the step size.
|
Chris@7
|
420 //
|
Chris@8
|
421 // The simplest way to do this is always use the smallest possible
|
Chris@8
|
422 // step size that allows for an integral number of samples (at the
|
Chris@8
|
423 // input sample rate) per output frame. That is, targetRate / g,
|
Chris@8
|
424 // where g is the gcd of m_inputSampleRate and targetRate.
|
Chris@7
|
425
|
Chris@8
|
426 int g = gcd(m_roundedInputRate, getTargetSampleRate());
|
Chris@8
|
427 int step = getTargetSampleRate() / g;
|
Chris@8
|
428 if (step < 1) step = 1;
|
Chris@6
|
429 return step;
|
Chris@5
|
430 }
|
Chris@5
|
431
|
Chris@5
|
432 int
|
Chris@5
|
433 LowFreq::getFFTSize() const
|
Chris@5
|
434 {
|
Chris@8
|
435 // See note in reset() above
|
Chris@6
|
436 int fftSize = 4 * int(ceil(m_fmax / ((m_fmax - m_fmin) / m_n)));
|
Chris@10
|
437 // cerr << "LowFreq::getFFTSize: for range " << m_fmin << " -> " << m_fmax
|
Chris@10
|
438 // << " and n = " << m_n << ", fft size is " << fftSize << endl;
|
Chris@5
|
439 return fftSize;
|
Chris@5
|
440 }
|
Chris@5
|
441
|
Chris@5
|
442 int
|
Chris@5
|
443 LowFreq::getFirstOutputBin() const
|
Chris@5
|
444 {
|
Chris@10
|
445 // Frequency of fft bin i is fs/n*i. We want the first bin whose
|
Chris@10
|
446 // frequency is at least m_fmin.
|
Chris@10
|
447 int first = int(ceil((m_fmin * getFFTSize()) / getTargetSampleRate()));
|
Chris@10
|
448 // cerr << "LowFreq::getFirstOutputBin: for range " << m_fmin << " -> " << m_fmax
|
Chris@10
|
449 // << " and n = " << m_n << ", bin is " << first << endl;
|
Chris@5
|
450 return first;
|
Chris@5
|
451 }
|
Chris@5
|
452
|
Chris@5
|
453 float
|
Chris@5
|
454 LowFreq::getOutputBinFrequency(int i) const
|
Chris@5
|
455 {
|
Chris@10
|
456 // That is, frequency of the i'th output bin that we actually
|
Chris@10
|
457 // return -- the (i + getFirstOutputBin())'th bin in the fft
|
Chris@5
|
458 return (float(getTargetSampleRate()) / getFFTSize())
|
Chris@5
|
459 * (i + getFirstOutputBin());
|
Chris@0
|
460 }
|
Chris@0
|
461
|
Chris@0
|
462 LowFreq::FeatureSet
|
Chris@0
|
463 LowFreq::process(const float *const *inputBuffers, Vamp::RealTime timestamp)
|
Chris@0
|
464 {
|
Chris@2
|
465 double *data = new double[m_blockSize];
|
Chris@2
|
466 for (int i = 0; i < m_blockSize; ++i) {
|
Chris@2
|
467 data[i] = inputBuffers[0][i];
|
Chris@2
|
468 }
|
Chris@2
|
469
|
Chris@2
|
470 vector<double> resampled = m_resampler->process(data, m_blockSize);
|
Chris@2
|
471 m_buffer.insert(m_buffer.end(), resampled.begin(), resampled.end());
|
Chris@2
|
472
|
Chris@9
|
473 if (m_drop > 0) {
|
Chris@9
|
474 int dropHere = m_drop;
|
Chris@9
|
475 if (dropHere > int(m_buffer.size())) {
|
Chris@9
|
476 dropHere = int(m_buffer.size());
|
Chris@9
|
477 }
|
Chris@9
|
478 advanceBy(dropHere);
|
Chris@9
|
479 m_drop -= dropHere;
|
Chris@9
|
480 }
|
Chris@9
|
481
|
Chris@2
|
482 delete[] data;
|
Chris@2
|
483
|
Chris@2
|
484 FeatureSet fs;
|
Chris@2
|
485
|
Chris@5
|
486 while (int(m_buffer.size()) >= getFFTSize()) {
|
Chris@2
|
487 Feature f = processColumn();
|
Chris@2
|
488 fs[0].push_back(f);
|
Chris@4
|
489 advance();
|
Chris@2
|
490 }
|
Chris@2
|
491
|
Chris@2
|
492 return fs;
|
Chris@0
|
493 }
|
Chris@0
|
494
|
Chris@0
|
495 LowFreq::FeatureSet
|
Chris@0
|
496 LowFreq::getRemainingFeatures()
|
Chris@0
|
497 {
|
Chris@2
|
498 FeatureSet fs;
|
Chris@2
|
499
|
Chris@9
|
500 double *padding = new double[m_pad];
|
Chris@9
|
501 for (int i = 0; i < m_pad; ++i) {
|
Chris@9
|
502 padding[i] = 0.0;
|
Chris@9
|
503 }
|
Chris@9
|
504 vector<double> lastBit = m_resampler->process(padding, m_pad);
|
Chris@9
|
505 m_buffer.insert(m_buffer.end(), lastBit.begin(), lastBit.end());
|
Chris@9
|
506
|
Chris@9
|
507 for (int i = 0; i < getFFTSize() * 2 - getTargetStepSize(); ++i) {
|
Chris@9
|
508 m_buffer.push_back(0.0);
|
Chris@4
|
509 }
|
Chris@4
|
510
|
Chris@5
|
511 while (int(m_buffer.size()) >= getFFTSize()) {
|
Chris@2
|
512 Feature f = processColumn();
|
Chris@2
|
513 fs[0].push_back(f);
|
Chris@4
|
514 advance();
|
Chris@2
|
515 }
|
Chris@2
|
516
|
Chris@2
|
517 return fs;
|
Chris@0
|
518 }
|
Chris@0
|
519
|
Chris@2
|
520 LowFreq::Feature
|
Chris@2
|
521 LowFreq::processColumn()
|
Chris@2
|
522 {
|
Chris@2
|
523 Feature f;
|
Chris@5
|
524
|
Chris@5
|
525 int sz = getFFTSize();
|
Chris@2
|
526
|
Chris@5
|
527 double *realOut = new double[sz];
|
Chris@5
|
528 double *imagOut = new double[sz];
|
Chris@2
|
529
|
Chris@5
|
530 double *windowed = new double[sz];
|
Chris@4
|
531 m_window->cut(m_buffer.data(), windowed);
|
Chris@3
|
532
|
Chris@4
|
533 m_fft->process(false, windowed, 0, realOut, imagOut);
|
Chris@2
|
534
|
Chris@6
|
535 int base = getFirstOutputBin();
|
Chris@6
|
536 for (int i = 0; i < m_n; ++i) {
|
Chris@6
|
537 int ix = base + i;
|
Chris@6
|
538 float mag = (realOut[ix] * realOut[ix] + imagOut[ix] * imagOut[ix]);
|
Chris@6
|
539 f.values.push_back(mag);
|
Chris@2
|
540 }
|
Chris@2
|
541
|
Chris@4
|
542 return f;
|
Chris@4
|
543 }
|
Chris@2
|
544
|
Chris@4
|
545 void
|
Chris@4
|
546 LowFreq::advance()
|
Chris@4
|
547 {
|
Chris@9
|
548 advanceBy(getTargetStepSize());
|
Chris@9
|
549 }
|
Chris@9
|
550
|
Chris@9
|
551 void
|
Chris@9
|
552 LowFreq::advanceBy(int n)
|
Chris@9
|
553 {
|
Chris@9
|
554 std::vector<double> advanced(m_buffer.data() + n,
|
Chris@2
|
555 m_buffer.data() + m_buffer.size());
|
Chris@2
|
556
|
Chris@2
|
557 m_buffer = advanced;
|
Chris@2
|
558 }
|
Chris@2
|
559
|