Mercurial > hg > svcore
comparison data/model/FFTModel.cpp @ 1780:6d6740b075c3
Support optional max frequency setting, useful when we want to store caches of very constrained frequency ranges (as in melodic-range spectrogram, potentially)
author | Chris Cannam |
---|---|
date | Thu, 12 Sep 2019 11:52:19 +0100 |
parents | fadd9f8aaa27 |
children | 4eac4bf35b45 |
comparison
equal
deleted
inserted
replaced
1779:85903b0e9b42 | 1780:6d6740b075c3 |
---|---|
37 WindowType windowType, | 37 WindowType windowType, |
38 int windowSize, | 38 int windowSize, |
39 int windowIncrement, | 39 int windowIncrement, |
40 int fftSize) : | 40 int fftSize) : |
41 m_model(modelId), | 41 m_model(modelId), |
42 m_sampleRate(0), | |
42 m_channel(channel), | 43 m_channel(channel), |
43 m_windowType(windowType), | 44 m_windowType(windowType), |
44 m_windowSize(windowSize), | 45 m_windowSize(windowSize), |
45 m_windowIncrement(windowIncrement), | 46 m_windowIncrement(windowIncrement), |
46 m_fftSize(fftSize), | 47 m_fftSize(fftSize), |
47 m_windower(windowType, windowSize), | 48 m_windower(windowType, windowSize), |
48 m_fft(fftSize), | 49 m_fft(fftSize), |
50 m_maximumFrequency(0.0), | |
49 m_cacheWriteIndex(0), | 51 m_cacheWriteIndex(0), |
50 m_cacheSize(3) | 52 m_cacheSize(3) |
51 { | 53 { |
52 while (m_cached.size() < m_cacheSize) { | 54 while (m_cached.size() < m_cacheSize) { |
53 m_cached.push_back({ -1, cvec(m_fftSize / 2 + 1) }); | 55 m_cached.push_back({ -1, cvec(m_fftSize / 2 + 1) }); |
61 | 63 |
62 m_fft.initFloat(); | 64 m_fft.initFloat(); |
63 | 65 |
64 auto model = ModelById::getAs<DenseTimeValueModel>(m_model); | 66 auto model = ModelById::getAs<DenseTimeValueModel>(m_model); |
65 if (model) { | 67 if (model) { |
68 m_sampleRate = model->getSampleRate(); | |
69 | |
66 connect(model.get(), SIGNAL(modelChanged(ModelId)), | 70 connect(model.get(), SIGNAL(modelChanged(ModelId)), |
67 this, SIGNAL(modelChanged(ModelId))); | 71 this, SIGNAL(modelChanged(ModelId))); |
68 connect(model.get(), SIGNAL(modelChangedWithin(ModelId, sv_frame_t, sv_frame_t)), | 72 connect(model.get(), SIGNAL(modelChangedWithin(ModelId, sv_frame_t, sv_frame_t)), |
69 this, SIGNAL(modelChangedWithin(ModelId, sv_frame_t, sv_frame_t))); | 73 this, SIGNAL(modelChangedWithin(ModelId, sv_frame_t, sv_frame_t))); |
70 } | 74 } |
93 } | 97 } |
94 | 98 |
95 sv_samplerate_t | 99 sv_samplerate_t |
96 FFTModel::getSampleRate() const | 100 FFTModel::getSampleRate() const |
97 { | 101 { |
98 auto model = ModelById::getAs<DenseTimeValueModel>(m_model); | 102 return m_sampleRate; |
99 if (model) return model->getSampleRate(); | 103 } |
100 else return 0; | 104 |
105 void | |
106 FFTModel::setMaximumFrequency(double freq) | |
107 { | |
108 m_maximumFrequency = freq; | |
101 } | 109 } |
102 | 110 |
103 int | 111 int |
104 FFTModel::getWidth() const | 112 FFTModel::getWidth() const |
105 { | 113 { |
110 } | 118 } |
111 | 119 |
112 int | 120 int |
113 FFTModel::getHeight() const | 121 FFTModel::getHeight() const |
114 { | 122 { |
115 return m_fftSize / 2 + 1; | 123 int height = m_fftSize / 2 + 1; |
124 if (m_maximumFrequency != 0.0) { | |
125 int maxBin = int(ceil(m_maximumFrequency * m_fftSize) / m_sampleRate); | |
126 if (maxBin >= 0 && maxBin < height) { | |
127 return maxBin + 1; | |
128 } | |
129 } | |
130 return height; | |
116 } | 131 } |
117 | 132 |
118 QString | 133 QString |
119 FFTModel::getBinName(int n) const | 134 FFTModel::getBinName(int n) const |
120 { | 135 { |
121 sv_samplerate_t sr = getSampleRate(); | 136 sv_samplerate_t sr = getSampleRate(); |
122 if (!sr) return ""; | 137 if (!sr) return ""; |
123 QString name = tr("%1 Hz").arg((n * sr) / ((getHeight()-1) * 2)); | 138 QString name = tr("%1 Hz").arg((double(n) * sr) / m_fftSize); |
124 return name; | 139 return name; |
125 } | 140 } |
126 | 141 |
127 FFTModel::Column | 142 FFTModel::Column |
128 FFTModel::getColumn(int x) const | 143 FFTModel::getColumn(int x) const |
176 } | 191 } |
177 | 192 |
178 void | 193 void |
179 FFTModel::getValuesAt(int x, int y, float &re, float &im) const | 194 FFTModel::getValuesAt(int x, int y, float &re, float &im) const |
180 { | 195 { |
196 if (x < 0 || x >= getWidth() || y < 0 || y >= getHeight()) { | |
197 re = 0.f; | |
198 im = 0.f; | |
199 return; | |
200 } | |
181 auto col = getFFTColumn(x); | 201 auto col = getFFTColumn(x); |
182 re = col[y].real(); | 202 re = col[y].real(); |
183 im = col[y].imag(); | 203 im = col[y].imag(); |
184 } | 204 } |
185 | 205 |
337 } | 357 } |
338 | 358 |
339 return data; | 359 return data; |
340 } | 360 } |
341 | 361 |
342 const FFTModel::cvec & | 362 FFTModel::cvec |
343 FFTModel::getFFTColumn(int n) const | 363 FFTModel::getFFTColumn(int n) const |
344 { | 364 { |
365 int h = getHeight(); | |
366 bool truncate = (h < m_fftSize / 2 + 1); | |
367 | |
345 // The small cache (i.e. the m_cached deque) is for cases where | 368 // The small cache (i.e. the m_cached deque) is for cases where |
346 // values are looked up individually, and for e.g. peak-frequency | 369 // values are looked up individually, and for e.g. peak-frequency |
347 // spectrograms where values from two consecutive columns are | 370 // spectrograms where values from two consecutive columns are |
348 // needed at once. This cache gets essentially no hits when | 371 // needed at once. This cache gets essentially no hits when |
349 // scrolling through a magnitude spectrogram, but 95%+ hits with a | 372 // scrolling through a magnitude spectrogram, but 95%+ hits with a |
350 // peak-frequency spectrogram or spectrum. | 373 // peak-frequency spectrogram or spectrum. |
351 for (const auto &incache : m_cached) { | 374 for (const auto &incache : m_cached) { |
352 if (incache.n == n) { | 375 if (incache.n == n) { |
353 inSmallCache.hit(); | 376 inSmallCache.hit(); |
354 return incache.col; | 377 if (!truncate) { |
378 return incache.col; | |
379 } else { | |
380 return cvec(incache.col.begin(), incache.col.begin() + h); | |
381 } | |
355 } | 382 } |
356 } | 383 } |
357 inSmallCache.miss(); | 384 inSmallCache.miss(); |
358 | 385 |
359 Profiler profiler("FFTModel::getFFTColumn (cache miss)"); | 386 Profiler profiler("FFTModel::getFFTColumn (cache miss)"); |
369 | 396 |
370 m_cached[m_cacheWriteIndex].n = n; | 397 m_cached[m_cacheWriteIndex].n = n; |
371 | 398 |
372 m_cacheWriteIndex = (m_cacheWriteIndex + 1) % m_cacheSize; | 399 m_cacheWriteIndex = (m_cacheWriteIndex + 1) % m_cacheSize; |
373 | 400 |
374 return col; | 401 if (!truncate) { |
402 return col; | |
403 } else { | |
404 return cvec(col.begin(), col.begin() + h); | |
405 } | |
375 } | 406 } |
376 | 407 |
377 bool | 408 bool |
378 FFTModel::estimateStableFrequency(int x, int y, double &frequency) | 409 FFTModel::estimateStableFrequency(int x, int y, double &frequency) |
379 { | 410 { |