Mercurial > hg > svcore
comparison data/model/FFTModel.cpp @ 275:522f82311e4e
* Pull peak-picker out of SpectrumLayer and into FFTModel; use combined
peak-picker and frequency estimator for SpectrogramLayer (makes the
peak frequency spectrogram a bit quicker)
* Add more information to spectrum and spectrogram crosshairs
author | Chris Cannam |
---|---|
date | Wed, 04 Jul 2007 15:29:16 +0000 |
parents | 29b70bdaacdc |
children | daf89d31f45c |
comparison
equal
deleted
inserted
replaced
274:e412f65884ee | 275:522f82311e4e |
---|---|
15 | 15 |
16 #include "FFTModel.h" | 16 #include "FFTModel.h" |
17 #include "DenseTimeValueModel.h" | 17 #include "DenseTimeValueModel.h" |
18 | 18 |
19 #include "base/Profiler.h" | 19 #include "base/Profiler.h" |
20 #include "base/Pitch.h" | |
20 | 21 |
21 #include <cassert> | 22 #include <cassert> |
22 | 23 |
23 FFTModel::FFTModel(const DenseTimeValueModel *model, | 24 FFTModel::FFTModel(const DenseTimeValueModel *model, |
24 int channel, | 25 int channel, |
101 if (!sr) return ""; | 102 if (!sr) return ""; |
102 QString name = tr("%1 Hz").arg((n * sr) / ((getHeight()-1) * 2)); | 103 QString name = tr("%1 Hz").arg((n * sr) / ((getHeight()-1) * 2)); |
103 return name; | 104 return name; |
104 } | 105 } |
105 | 106 |
107 bool | |
108 FFTModel::estimateStableFrequency(size_t x, size_t y, float &frequency) | |
109 { | |
110 if (!isOK()) return false; | |
111 | |
112 size_t sampleRate = m_server->getModel()->getSampleRate(); | |
113 | |
114 size_t fftSize = m_server->getFFTSize() >> m_yshift; | |
115 frequency = (float(y) * sampleRate) / fftSize; | |
116 | |
117 if (x+1 >= getWidth()) return false; | |
118 | |
119 // At frequency f, a phase shift of 2pi (one cycle) happens in 1/f sec. | |
120 // At hopsize h and sample rate sr, one hop happens in h/sr sec. | |
121 // At window size w, for bin b, f is b*sr/w. | |
122 // thus 2pi phase shift happens in w/(b*sr) sec. | |
123 // We need to know what phase shift we expect from h/sr sec. | |
124 // -> 2pi * ((h/sr) / (w/(b*sr))) | |
125 // = 2pi * ((h * b * sr) / (w * sr)) | |
126 // = 2pi * (h * b) / w. | |
127 | |
128 float oldPhase = getPhaseAt(x, y); | |
129 float newPhase = getPhaseAt(x+1, y); | |
130 | |
131 size_t incr = getResolution(); | |
132 | |
133 float expectedPhase = oldPhase + (2.0 * M_PI * y * incr) / fftSize; | |
134 | |
135 float phaseError = princargf(newPhase - expectedPhase); | |
136 | |
137 // bool stable = (fabsf(phaseError) < (1.1f * (m_windowIncrement * M_PI) / m_fftSize)); | |
138 | |
139 // The new frequency estimate based on the phase error resulting | |
140 // from assuming the "native" frequency of this bin | |
141 | |
142 frequency = | |
143 (sampleRate * (expectedPhase + phaseError - oldPhase)) / | |
144 (2 * M_PI * incr); | |
145 | |
146 return true; | |
147 } | |
148 | |
149 FFTModel::PeakLocationSet | |
150 FFTModel::getPeaks(PeakPickType type, size_t x, size_t ymin, size_t ymax) | |
151 { | |
152 FFTModel::PeakLocationSet peaks; | |
153 if (!isOK()) return peaks; | |
154 | |
155 if (ymax == 0 || ymax > getHeight() - 1) { | |
156 ymax = getHeight() - 1; | |
157 } | |
158 | |
159 Column values; | |
160 | |
161 if (type == AllPeaks) { | |
162 for (size_t y = ymin; y <= ymax; ++y) { | |
163 values.push_back(getMagnitudeAt(x, y)); | |
164 } | |
165 size_t i = 0; | |
166 for (size_t bin = ymin; bin <= ymax; ++bin) { | |
167 if ((i == 0 || values[i] > values[i-1]) && | |
168 (i == values.size()-1 || values[i] >= values[i+1])) { | |
169 peaks.insert(bin); | |
170 } | |
171 ++i; | |
172 } | |
173 return peaks; | |
174 } | |
175 | |
176 getColumn(x, values); | |
177 | |
178 // For peak picking we use a moving median window, picking the | |
179 // highest value within each continuous region of values that | |
180 // exceed the median. For pitch adaptivity, we adjust the window | |
181 // size to a roughly constant pitch range (about four tones). | |
182 | |
183 size_t sampleRate = getSampleRate(); | |
184 | |
185 std::deque<float> window; | |
186 std::vector<size_t> inrange; | |
187 size_t medianWinSize = getPeakPickWindowSize(type, sampleRate, ymin); | |
188 size_t halfWin = medianWinSize/2; | |
189 | |
190 size_t binmin; | |
191 if (ymin > halfWin) binmin = ymin - halfWin; | |
192 else binmin = 0; | |
193 | |
194 size_t binmax; | |
195 if (ymax + halfWin < values.size()) binmax = ymax + halfWin; | |
196 else binmax = values.size()-1; | |
197 | |
198 for (size_t bin = binmin; bin <= binmax; ++bin) { | |
199 | |
200 float value = values[bin]; | |
201 | |
202 window.push_back(value); | |
203 | |
204 medianWinSize = getPeakPickWindowSize(type, sampleRate, bin); | |
205 halfWin = medianWinSize/2; | |
206 | |
207 while (window.size() > medianWinSize) window.pop_front(); | |
208 | |
209 if (type == MajorPitchAdaptivePeaks) { | |
210 if (ymax + halfWin < values.size()) binmax = ymax + halfWin; | |
211 else binmax = values.size()-1; | |
212 } | |
213 | |
214 std::deque<float> sorted(window); | |
215 std::sort(sorted.begin(), sorted.end()); | |
216 float median = sorted[sorted.size()/2]; | |
217 | |
218 if (value > median) { | |
219 inrange.push_back(bin); | |
220 } | |
221 | |
222 if (value <= median || bin+1 == values.size()) { | |
223 size_t peakbin = 0; | |
224 float peakval = 0.f; | |
225 if (!inrange.empty()) { | |
226 for (size_t i = 0; i < inrange.size(); ++i) { | |
227 if (i == 0 || values[inrange[i]] > peakval) { | |
228 peakval = values[inrange[i]]; | |
229 peakbin = inrange[i]; | |
230 } | |
231 } | |
232 inrange.clear(); | |
233 if (peakbin >= ymin && peakbin <= ymax) { | |
234 peaks.insert(peakbin); | |
235 } | |
236 } | |
237 } | |
238 } | |
239 | |
240 return peaks; | |
241 } | |
242 | |
243 size_t | |
244 FFTModel::getPeakPickWindowSize(PeakPickType type, size_t sampleRate, size_t bin) const | |
245 { | |
246 if (type == MajorPeaks) return 10; | |
247 if (bin == 0) return 3; | |
248 size_t fftSize = m_server->getFFTSize() >> m_yshift; | |
249 float binfreq = (sampleRate * bin) / fftSize; | |
250 float hifreq = Pitch::getFrequencyForPitch(73, 0, binfreq); | |
251 int hibin = lrintf((hifreq * fftSize) / sampleRate); | |
252 int medianWinSize = hibin - bin; | |
253 if (medianWinSize < 3) medianWinSize = 3; | |
254 return medianWinSize; | |
255 } | |
256 | |
257 FFTModel::PeakSet | |
258 FFTModel::getPeakFrequencies(PeakPickType type, size_t x, | |
259 size_t ymin, size_t ymax) | |
260 { | |
261 PeakSet peaks; | |
262 if (!isOK()) return peaks; | |
263 PeakLocationSet locations = getPeaks(type, x, ymin, ymax); | |
264 | |
265 size_t sampleRate = getSampleRate(); | |
266 size_t fftSize = m_server->getFFTSize() >> m_yshift; | |
267 size_t incr = getResolution(); | |
268 | |
269 // This duplicates some of the work of estimateStableFrequency to | |
270 // allow us to retrieve the phases in two separate vertical | |
271 // columns, instead of jumping back and forth between columns x and | |
272 // x+1, which may be significantly slower if re-seeking is needed | |
273 | |
274 std::vector<float> phases; | |
275 for (PeakLocationSet::iterator i = locations.begin(); | |
276 i != locations.end(); ++i) { | |
277 phases.push_back(getPhaseAt(x, *i)); | |
278 } | |
279 | |
280 size_t phaseIndex = 0; | |
281 for (PeakLocationSet::iterator i = locations.begin(); | |
282 i != locations.end(); ++i) { | |
283 float oldPhase = phases[phaseIndex]; | |
284 float newPhase = getPhaseAt(x+1, *i); | |
285 float expectedPhase = oldPhase + (2.0 * M_PI * *i * incr) / fftSize; | |
286 float phaseError = princargf(newPhase - expectedPhase); | |
287 float frequency = | |
288 (sampleRate * (expectedPhase + phaseError - oldPhase)) | |
289 / (2 * M_PI * incr); | |
290 // bool stable = (fabsf(phaseError) < (1.1f * (incr * M_PI) / fftSize)); | |
291 // if (stable) | |
292 peaks[*i] = frequency; | |
293 ++phaseIndex; | |
294 } | |
295 | |
296 return peaks; | |
297 } | |
298 | |
106 Model * | 299 Model * |
107 FFTModel::clone() const | 300 FFTModel::clone() const |
108 { | 301 { |
109 return new FFTModel(*this); | 302 return new FFTModel(*this); |
110 } | 303 } |