Mercurial > hg > svcore
comparison data/model/FFTModel.cpp @ 1837:1b688ab5f1b3
Unify various vectors to our base floatvec_t type; store columns in fft model cache at their desired height so we can return a reference (speeding up the peak-frequency spectrogram in particular)
author | Chris Cannam |
---|---|
date | Thu, 09 Apr 2020 11:22:55 +0100 |
parents | dd51797e528e |
children | 915d316a5609 |
comparison
equal
deleted
inserted
replaced
1835:804dd0c06f0e | 1837:1b688ab5f1b3 |
---|---|
49 m_fft(fftSize), | 49 m_fft(fftSize), |
50 m_maximumFrequency(0.0), | 50 m_maximumFrequency(0.0), |
51 m_cacheWriteIndex(0), | 51 m_cacheWriteIndex(0), |
52 m_cacheSize(3) | 52 m_cacheSize(3) |
53 { | 53 { |
54 while (m_cached.size() < m_cacheSize) { | 54 clearCaches(); |
55 m_cached.push_back({ -1, cvec(m_fftSize / 2 + 1) }); | |
56 } | |
57 | 55 |
58 if (m_windowSize > m_fftSize) { | 56 if (m_windowSize > m_fftSize) { |
59 SVCERR << "ERROR: FFTModel::FFTModel: window size (" << m_windowSize | 57 SVCERR << "ERROR: FFTModel::FFTModel: window size (" << m_windowSize |
60 << ") may not exceed FFT size (" << m_fftSize << ")" << endl; | 58 << ") may not exceed FFT size (" << m_fftSize << ")" << endl; |
61 throw invalid_argument("FFTModel window size may not exceed FFT size"); | 59 throw invalid_argument("FFTModel window size may not exceed FFT size"); |
78 | 76 |
79 FFTModel::~FFTModel() | 77 FFTModel::~FFTModel() |
80 { | 78 { |
81 } | 79 } |
82 | 80 |
81 void | |
82 FFTModel::clearCaches() | |
83 { | |
84 m_cached.clear(); | |
85 while (m_cached.size() < m_cacheSize) { | |
86 m_cached.push_back({ -1, complexvec_t(m_fftSize / 2 + 1) }); | |
87 } | |
88 m_cacheWriteIndex = 0; | |
89 m_savedData.range = { 0, 0 }; | |
90 } | |
91 | |
83 bool | 92 bool |
84 FFTModel::isOK() const | 93 FFTModel::isOK() const |
85 { | 94 { |
86 auto model = ModelById::getAs<DenseTimeValueModel>(m_model); | 95 auto model = ModelById::getAs<DenseTimeValueModel>(m_model); |
87 if (!model) { | 96 if (!model) { |
108 | 117 |
109 void | 118 void |
110 FFTModel::setMaximumFrequency(double freq) | 119 FFTModel::setMaximumFrequency(double freq) |
111 { | 120 { |
112 m_maximumFrequency = freq; | 121 m_maximumFrequency = freq; |
122 clearCaches(); | |
113 } | 123 } |
114 | 124 |
115 int | 125 int |
116 FFTModel::getWidth() const | 126 FFTModel::getWidth() const |
117 { | 127 { |
244 imags[i] = col[minbin + i].imag(); | 254 imags[i] = col[minbin + i].imag(); |
245 } | 255 } |
246 return true; | 256 return true; |
247 } | 257 } |
248 | 258 |
249 FFTModel::fvec | 259 floatvec_t |
250 FFTModel::getSourceSamples(int column) const | 260 FFTModel::getSourceSamples(int column) const |
251 { | 261 { |
252 // m_fftSize may be greater than m_windowSize, but not the reverse | 262 // m_fftSize may be greater than m_windowSize, but not the reverse |
253 | 263 |
254 // cerr << "getSourceSamples(" << column << ")" << endl; | 264 // cerr << "getSourceSamples(" << column << ")" << endl; |
260 | 270 |
261 if (off == 0) { | 271 if (off == 0) { |
262 return data; | 272 return data; |
263 } else { | 273 } else { |
264 vector<float> pad(off, 0.f); | 274 vector<float> pad(off, 0.f); |
265 fvec padded; | 275 floatvec_t padded; |
266 padded.reserve(m_fftSize); | 276 padded.reserve(m_fftSize); |
267 padded.insert(padded.end(), pad.begin(), pad.end()); | 277 padded.insert(padded.end(), pad.begin(), pad.end()); |
268 padded.insert(padded.end(), data.begin(), data.end()); | 278 padded.insert(padded.end(), data.begin(), data.end()); |
269 padded.insert(padded.end(), pad.begin(), pad.end()); | 279 padded.insert(padded.end(), pad.begin(), pad.end()); |
270 return padded; | 280 return padded; |
271 } | 281 } |
272 } | 282 } |
273 | 283 |
274 FFTModel::fvec | 284 floatvec_t |
275 FFTModel::getSourceData(pair<sv_frame_t, sv_frame_t> range) const | 285 FFTModel::getSourceData(pair<sv_frame_t, sv_frame_t> range) const |
276 { | 286 { |
277 // cerr << "getSourceData(" << range.first << "," << range.second | 287 // cerr << "getSourceData(" << range.first << "," << range.second |
278 // << "): saved range is (" << m_savedData.range.first | 288 // << "): saved range is (" << m_savedData.range.first |
279 // << "," << m_savedData.range.second << ")" << endl; | 289 // << "," << m_savedData.range.second << ")" << endl; |
291 | 301 |
292 inSourceCache.partial(); | 302 inSourceCache.partial(); |
293 | 303 |
294 sv_frame_t discard = range.first - m_savedData.range.first; | 304 sv_frame_t discard = range.first - m_savedData.range.first; |
295 | 305 |
296 fvec data; | 306 floatvec_t data; |
297 data.reserve(range.second - range.first); | 307 data.reserve(range.second - range.first); |
298 | 308 |
299 data.insert(data.end(), | 309 data.insert(data.end(), |
300 m_savedData.data.begin() + discard, | 310 m_savedData.data.begin() + discard, |
301 m_savedData.data.end()); | 311 m_savedData.data.end()); |
302 | 312 |
303 fvec rest = getSourceDataUncached | 313 floatvec_t rest = getSourceDataUncached |
304 ({ m_savedData.range.second, range.second }); | 314 ({ m_savedData.range.second, range.second }); |
305 | 315 |
306 data.insert(data.end(), rest.begin(), rest.end()); | 316 data.insert(data.end(), rest.begin(), rest.end()); |
307 | 317 |
308 m_savedData = { range, data }; | 318 m_savedData = { range, data }; |
316 m_savedData = { range, data }; | 326 m_savedData = { range, data }; |
317 return data; | 327 return data; |
318 } | 328 } |
319 } | 329 } |
320 | 330 |
321 FFTModel::fvec | 331 floatvec_t |
322 FFTModel::getSourceDataUncached(pair<sv_frame_t, sv_frame_t> range) const | 332 FFTModel::getSourceDataUncached(pair<sv_frame_t, sv_frame_t> range) const |
323 { | 333 { |
324 Profiler profiler("FFTModel::getSourceDataUncached"); | 334 Profiler profiler("FFTModel::getSourceDataUncached"); |
325 | 335 |
326 auto model = ModelById::getAs<DenseTimeValueModel>(m_model); | 336 auto model = ModelById::getAs<DenseTimeValueModel>(m_model); |
364 } | 374 } |
365 | 375 |
366 return data; | 376 return data; |
367 } | 377 } |
368 | 378 |
369 FFTModel::cvec | 379 const complexvec_t & |
370 FFTModel::getFFTColumn(int n) const | 380 FFTModel::getFFTColumn(int n) const |
371 { | 381 { |
372 int h = getHeight(); | |
373 bool truncate = (h < m_fftSize / 2 + 1); | |
374 | |
375 // The small cache (i.e. the m_cached deque) is for cases where | 382 // The small cache (i.e. the m_cached deque) is for cases where |
376 // values are looked up individually, and for e.g. peak-frequency | 383 // values are looked up individually, and for e.g. peak-frequency |
377 // spectrograms where values from two consecutive columns are | 384 // spectrograms where values from two consecutive columns are |
378 // needed at once. This cache gets essentially no hits when | 385 // needed at once. This cache gets essentially no hits when |
379 // scrolling through a magnitude spectrogram, but 95%+ hits with a | 386 // scrolling through a magnitude spectrogram, but 95%+ hits with a |
380 // peak-frequency spectrogram or spectrum. | 387 // peak-frequency spectrogram or spectrum. |
381 for (const auto &incache : m_cached) { | 388 for (const auto &incache : m_cached) { |
382 if (incache.n == n) { | 389 if (incache.n == n) { |
383 inSmallCache.hit(); | 390 inSmallCache.hit(); |
384 if (!truncate) { | 391 return incache.col; |
385 return incache.col; | |
386 } else { | |
387 return cvec(incache.col.begin(), incache.col.begin() + h); | |
388 } | |
389 } | 392 } |
390 } | 393 } |
391 inSmallCache.miss(); | 394 inSmallCache.miss(); |
392 | 395 |
393 Profiler profiler("FFTModel::getFFTColumn (cache miss)"); | 396 Profiler profiler("FFTModel::getFFTColumn (cache miss)"); |
394 | 397 |
395 auto samples = getSourceSamples(n); | 398 auto samples = getSourceSamples(n); |
396 m_windower.cut(samples.data() + (m_fftSize - m_windowSize) / 2); | 399 m_windower.cut(samples.data() + (m_fftSize - m_windowSize) / 2); |
397 breakfastquay::v_fftshift(samples.data(), m_fftSize); | 400 breakfastquay::v_fftshift(samples.data(), m_fftSize); |
398 | 401 |
399 cvec &col = m_cached[m_cacheWriteIndex].col; | 402 complexvec_t &col = m_cached[m_cacheWriteIndex].col; |
403 | |
404 // expand to large enough for fft destination, if truncated previously | |
405 col.resize(m_fftSize / 2 + 1); | |
400 | 406 |
401 m_fft.forwardInterleaved(samples.data(), | 407 m_fft.forwardInterleaved(samples.data(), |
402 reinterpret_cast<float *>(col.data())); | 408 reinterpret_cast<float *>(col.data())); |
403 | 409 |
410 // keep only the number of elements we need - so that we can | |
411 // return a const ref without having to resize on a cache hit | |
412 col.resize(getHeight()); | |
413 | |
404 m_cached[m_cacheWriteIndex].n = n; | 414 m_cached[m_cacheWriteIndex].n = n; |
405 | 415 |
406 m_cacheWriteIndex = (m_cacheWriteIndex + 1) % m_cacheSize; | 416 m_cacheWriteIndex = (m_cacheWriteIndex + 1) % m_cacheSize; |
407 | 417 |
408 if (!truncate) { | 418 return col; |
409 return col; | |
410 } else { | |
411 return cvec(col.begin(), col.begin() + h); | |
412 } | |
413 } | 419 } |
414 | 420 |
415 bool | 421 bool |
416 FFTModel::estimateStableFrequency(int x, int y, double &frequency) | 422 FFTModel::estimateStableFrequency(int x, int y, double &frequency) |
417 { | 423 { |