Mercurial > hg > svgui
comparison layer/SpectrumLayer.cpp @ 1395:32bbb86094c3
Merge from branch spectrogramparam
author | Chris Cannam |
---|---|
date | Wed, 14 Nov 2018 14:23:17 +0000 |
parents | 4a36f6130056 |
children | 2e316a724336 |
comparison
equal
deleted
inserted
replaced
1380:78eecb19e688 | 1395:32bbb86094c3 |
---|---|
37 m_channel(-1), | 37 m_channel(-1), |
38 m_channelSet(false), | 38 m_channelSet(false), |
39 m_windowSize(4096), | 39 m_windowSize(4096), |
40 m_windowType(HanningWindow), | 40 m_windowType(HanningWindow), |
41 m_windowHopLevel(3), | 41 m_windowHopLevel(3), |
42 m_oversampling(1), | |
42 m_showPeaks(false), | 43 m_showPeaks(false), |
43 m_newFFTNeeded(true) | 44 m_newFFTNeeded(true) |
44 { | 45 { |
45 Preferences *prefs = Preferences::getInstance(); | 46 Preferences *prefs = Preferences::getInstance(); |
46 connect(prefs, SIGNAL(propertyChanged(PropertyContainer::PropertyName)), | 47 connect(prefs, SIGNAL(propertyChanged(PropertyContainer::PropertyName)), |
110 | 111 |
111 if (!m_originModel) { | 112 if (!m_originModel) { |
112 return; | 113 return; |
113 } | 114 } |
114 | 115 |
116 int fftSize = getFFTSize(); | |
117 | |
115 FFTModel *newFFT = new FFTModel(m_originModel, | 118 FFTModel *newFFT = new FFTModel(m_originModel, |
116 m_channel, | 119 m_channel, |
117 m_windowType, | 120 m_windowType, |
118 m_windowSize, | 121 m_windowSize, |
119 getWindowIncrement(), | 122 getWindowIncrement(), |
120 m_windowSize); | 123 fftSize); |
121 | 124 |
122 setSliceableModel(newFFT); | 125 setSliceableModel(newFFT); |
123 | 126 |
124 m_biasCurve.clear(); | 127 m_biasCurve.clear(); |
125 for (int i = 0; i < m_windowSize; ++i) { | 128 for (int i = 0; i < fftSize; ++i) { |
126 m_biasCurve.push_back(1.f / (float(m_windowSize)/2.f)); | 129 m_biasCurve.push_back(1.f / (float(fftSize)/2.f)); |
127 } | 130 } |
128 | 131 |
129 m_newFFTNeeded = false; | 132 m_newFFTNeeded = false; |
130 } | 133 } |
131 | 134 |
133 SpectrumLayer::getProperties() const | 136 SpectrumLayer::getProperties() const |
134 { | 137 { |
135 PropertyList list = SliceLayer::getProperties(); | 138 PropertyList list = SliceLayer::getProperties(); |
136 list.push_back("Window Size"); | 139 list.push_back("Window Size"); |
137 list.push_back("Window Increment"); | 140 list.push_back("Window Increment"); |
141 list.push_back("Oversampling"); | |
138 list.push_back("Show Peak Frequencies"); | 142 list.push_back("Show Peak Frequencies"); |
139 return list; | 143 return list; |
140 } | 144 } |
141 | 145 |
142 QString | 146 QString |
143 SpectrumLayer::getPropertyLabel(const PropertyName &name) const | 147 SpectrumLayer::getPropertyLabel(const PropertyName &name) const |
144 { | 148 { |
145 if (name == "Window Size") return tr("Window Size"); | 149 if (name == "Window Size") return tr("Window Size"); |
146 if (name == "Window Increment") return tr("Window Overlap"); | 150 if (name == "Window Increment") return tr("Window Overlap"); |
151 if (name == "Oversampling") return tr("Oversampling"); | |
147 if (name == "Show Peak Frequencies") return tr("Show Peak Frequencies"); | 152 if (name == "Show Peak Frequencies") return tr("Show Peak Frequencies"); |
148 return SliceLayer::getPropertyLabel(name); | 153 return SliceLayer::getPropertyLabel(name); |
149 } | 154 } |
150 | 155 |
151 QString | 156 QString |
158 Layer::PropertyType | 163 Layer::PropertyType |
159 SpectrumLayer::getPropertyType(const PropertyName &name) const | 164 SpectrumLayer::getPropertyType(const PropertyName &name) const |
160 { | 165 { |
161 if (name == "Window Size") return ValueProperty; | 166 if (name == "Window Size") return ValueProperty; |
162 if (name == "Window Increment") return ValueProperty; | 167 if (name == "Window Increment") return ValueProperty; |
168 if (name == "Oversampling") return ValueProperty; | |
163 if (name == "Show Peak Frequencies") return ToggleProperty; | 169 if (name == "Show Peak Frequencies") return ToggleProperty; |
164 return SliceLayer::getPropertyType(name); | 170 return SliceLayer::getPropertyType(name); |
165 } | 171 } |
166 | 172 |
167 QString | 173 QString |
168 SpectrumLayer::getPropertyGroupName(const PropertyName &name) const | 174 SpectrumLayer::getPropertyGroupName(const PropertyName &name) const |
169 { | 175 { |
170 if (name == "Window Size" || | 176 if (name == "Window Size" || |
171 name == "Window Increment") return tr("Window"); | 177 name == "Window Increment" || |
178 name == "Oversampling") return tr("Window"); | |
172 if (name == "Show Peak Frequencies") return tr("Bins"); | 179 if (name == "Show Peak Frequencies") return tr("Bins"); |
173 return SliceLayer::getPropertyGroupName(name); | 180 return SliceLayer::getPropertyGroupName(name); |
174 } | 181 } |
175 | 182 |
176 int | 183 int |
200 *max = 5; | 207 *max = 5; |
201 *deflt = 2; | 208 *deflt = 2; |
202 | 209 |
203 val = m_windowHopLevel; | 210 val = m_windowHopLevel; |
204 | 211 |
212 } else if (name == "Oversampling") { | |
213 | |
214 *min = 0; | |
215 *max = 3; | |
216 *deflt = 0; | |
217 | |
218 val = 0; | |
219 int ov = m_oversampling; | |
220 while (ov > 1) { ov >>= 1; val ++; } | |
221 | |
205 } else if (name == "Show Peak Frequencies") { | 222 } else if (name == "Show Peak Frequencies") { |
206 | 223 |
207 return m_showPeaks ? 1 : 0; | 224 return m_showPeaks ? 1 : 0; |
208 | 225 |
209 } else { | 226 } else { |
230 case 3: return tr("75 %"); | 247 case 3: return tr("75 %"); |
231 case 4: return tr("87.5 %"); | 248 case 4: return tr("87.5 %"); |
232 case 5: return tr("93.75 %"); | 249 case 5: return tr("93.75 %"); |
233 } | 250 } |
234 } | 251 } |
252 if (name == "Oversampling") { | |
253 switch (value) { | |
254 default: | |
255 case 0: return tr("1x"); | |
256 case 1: return tr("2x"); | |
257 case 2: return tr("4x"); | |
258 case 3: return tr("8x"); | |
259 } | |
260 } | |
235 return SliceLayer::getPropertyValueLabel(name, value); | 261 return SliceLayer::getPropertyValueLabel(name, value); |
236 } | 262 } |
237 | 263 |
238 RangeMapper * | 264 RangeMapper * |
239 SpectrumLayer::getNewPropertyRangeMapper(const PropertyName &name) const | 265 SpectrumLayer::getNewPropertyRangeMapper(const PropertyName &name) const |
246 { | 272 { |
247 if (name == "Window Size") { | 273 if (name == "Window Size") { |
248 setWindowSize(32 << value); | 274 setWindowSize(32 << value); |
249 } else if (name == "Window Increment") { | 275 } else if (name == "Window Increment") { |
250 setWindowHopLevel(value); | 276 setWindowHopLevel(value); |
277 } else if (name == "Oversampling") { | |
278 setOversampling(1 << value); | |
251 } else if (name == "Show Peak Frequencies") { | 279 } else if (name == "Show Peak Frequencies") { |
252 setShowPeaks(value ? true : false); | 280 setShowPeaks(value ? true : false); |
253 } else { | 281 } else { |
254 SliceLayer::setProperty(name, value); | 282 SliceLayer::setProperty(name, value); |
255 } | 283 } |
257 | 285 |
258 void | 286 void |
259 SpectrumLayer::setWindowSize(int ws) | 287 SpectrumLayer::setWindowSize(int ws) |
260 { | 288 { |
261 if (m_windowSize == ws) return; | 289 if (m_windowSize == ws) return; |
290 | |
291 SVDEBUG << "setWindowSize: from " << m_windowSize | |
292 << " to " << ws << ": updating min and max bins from " | |
293 << m_minbin << " and " << m_maxbin << " to "; | |
294 | |
295 m_minbin = int(round((double(m_minbin) / m_windowSize) * ws)); | |
296 m_maxbin = int(round((double(m_maxbin) / m_windowSize) * ws)); | |
297 | |
298 SVDEBUG << m_minbin << " and " << m_maxbin << endl; | |
299 | |
262 m_windowSize = ws; | 300 m_windowSize = ws; |
263 m_newFFTNeeded = true; | 301 m_newFFTNeeded = true; |
264 emit layerParametersChanged(); | 302 emit layerParametersChanged(); |
265 } | 303 } |
266 | 304 |
281 m_newFFTNeeded = true; | 319 m_newFFTNeeded = true; |
282 emit layerParametersChanged(); | 320 emit layerParametersChanged(); |
283 } | 321 } |
284 | 322 |
285 void | 323 void |
324 SpectrumLayer::setOversampling(int oversampling) | |
325 { | |
326 if (m_oversampling == oversampling) return; | |
327 | |
328 SVDEBUG << "setOversampling: from " << m_oversampling | |
329 << " to " << oversampling << ": updating min and max bins from " | |
330 << m_minbin << " and " << m_maxbin << " to "; | |
331 | |
332 m_minbin = int(round((double(m_minbin) / m_oversampling) * oversampling)); | |
333 m_maxbin = int(round((double(m_maxbin) / m_oversampling) * oversampling)); | |
334 | |
335 SVDEBUG << m_minbin << " and " << m_maxbin << endl; | |
336 | |
337 m_oversampling = oversampling; | |
338 m_newFFTNeeded = true; | |
339 | |
340 emit layerParametersChanged(); | |
341 } | |
342 | |
343 int | |
344 SpectrumLayer::getOversampling() const | |
345 { | |
346 return m_oversampling; | |
347 } | |
348 | |
349 void | |
286 SpectrumLayer::setShowPeaks(bool show) | 350 SpectrumLayer::setShowPeaks(bool show) |
287 { | 351 { |
288 if (m_showPeaks == show) return; | 352 if (m_showPeaks == show) return; |
289 m_showPeaks = show; | 353 m_showPeaks = show; |
290 emit layerParametersChanged(); | 354 emit layerParametersChanged(); |
292 | 356 |
293 void | 357 void |
294 SpectrumLayer::preferenceChanged(PropertyContainer::PropertyName name) | 358 SpectrumLayer::preferenceChanged(PropertyContainer::PropertyName name) |
295 { | 359 { |
296 if (name == "Window Type") { | 360 if (name == "Window Type") { |
297 setWindowType(Preferences::getInstance()->getWindowType()); | 361 auto type = Preferences::getInstance()->getWindowType(); |
362 SVDEBUG << "SpectrumLayer::preferenceChanged: Window type changed to " | |
363 << type << endl; | |
364 setWindowType(type); | |
298 return; | 365 return; |
299 } | 366 } |
367 } | |
368 | |
369 double | |
370 SpectrumLayer::getBinForFrequency(double freq) const | |
371 { | |
372 if (!m_sliceableModel) return 0; | |
373 double bin = (freq * getFFTSize()) / m_sliceableModel->getSampleRate(); | |
374 // we assume the frequency of a bin corresponds to the centre of | |
375 // its visual range | |
376 bin += 0.5; | |
377 return bin; | |
378 } | |
379 | |
380 double | |
381 SpectrumLayer::getBinForX(const LayerGeometryProvider *v, double x) const | |
382 { | |
383 if (!m_sliceableModel) return 0; | |
384 double bin = getBinForFrequency(getFrequencyForX(v, x)); | |
385 return bin; | |
300 } | 386 } |
301 | 387 |
302 double | 388 double |
303 SpectrumLayer::getFrequencyForX(const LayerGeometryProvider *v, double x) const | 389 SpectrumLayer::getFrequencyForX(const LayerGeometryProvider *v, double x) const |
304 { | 390 { |
305 if (!m_sliceableModel) return 0; | 391 if (!m_sliceableModel) return 0; |
306 double bin = getBinForX(v, x); | 392 |
393 double fmin = getFrequencyForBin(m_minbin); | |
394 | |
395 if (m_binScale == LogBins && m_minbin == 0) { | |
396 // Avoid too much space going to the first bin, but do so in a | |
397 // way that usually avoids us shifting left/right as the | |
398 // window size or oversampling ratio change - i.e. base this | |
399 // on frequency rather than bin number unless we have a lot of | |
400 // very low-resolution content | |
401 fmin = getFrequencyForBin(0.8); | |
402 if (fmin > 6.0) fmin = 6.0; | |
403 } | |
404 | |
405 double fmax = getFrequencyForBin(m_maxbin); | |
406 | |
407 double freq = getScalePointForX(v, x, fmin, fmax); | |
408 return freq; | |
409 } | |
410 | |
411 double | |
412 SpectrumLayer::getFrequencyForBin(double bin) const | |
413 { | |
414 if (!m_sliceableModel) return 0; | |
307 // we assume the frequency of a bin corresponds to the centre of | 415 // we assume the frequency of a bin corresponds to the centre of |
308 // its visual range | 416 // its visual range |
309 bin -= 0.5; | 417 bin -= 0.5; |
310 return (m_sliceableModel->getSampleRate() * bin) / | 418 double freq = (bin * m_sliceableModel->getSampleRate()) / getFFTSize(); |
311 (m_sliceableModel->getHeight() * 2); | 419 return freq; |
420 } | |
421 | |
422 double | |
423 SpectrumLayer::getXForBin(const LayerGeometryProvider *v, double bin) const | |
424 { | |
425 if (!m_sliceableModel) return 0; | |
426 double x = getXForFrequency(v, getFrequencyForBin(bin)); | |
427 return x; | |
312 } | 428 } |
313 | 429 |
314 double | 430 double |
315 SpectrumLayer::getXForFrequency(const LayerGeometryProvider *v, double freq) const | 431 SpectrumLayer::getXForFrequency(const LayerGeometryProvider *v, double freq) const |
316 { | 432 { |
317 if (!m_sliceableModel) return 0; | 433 if (!m_sliceableModel) return 0; |
318 double bin = (freq * m_sliceableModel->getHeight() * 2) / | 434 |
319 m_sliceableModel->getSampleRate(); | 435 double fmin = getFrequencyForBin(m_minbin); |
320 // we want the centre of the bin range | 436 if (m_binScale == LogBins && m_minbin == 0) { |
321 bin += 0.5; | 437 // See comment in getFrequencyForX above |
322 return getXForBin(v, bin); | 438 fmin = getFrequencyForBin(0.8); |
439 if (fmin > 6.0) fmin = 6.0; | |
440 } | |
441 | |
442 double fmax = getFrequencyForBin(m_maxbin); | |
443 | |
444 double x = getXForScalePoint(v, freq, fmin, fmax); | |
445 return x; | |
323 } | 446 } |
324 | 447 |
325 bool | 448 bool |
326 SpectrumLayer::getXScaleValue(const LayerGeometryProvider *v, int x, | 449 SpectrumLayer::getXScaleValue(const LayerGeometryProvider *v, int x, |
327 double &value, QString &unit) const | 450 double &value, QString &unit) const |
425 paint.drawLine(xorigin, cursorPos.y(), v->getPaintWidth(), cursorPos.y()); | 548 paint.drawLine(xorigin, cursorPos.y(), v->getPaintWidth(), cursorPos.y()); |
426 paint.drawLine(cursorPos.x(), cursorPos.y(), cursorPos.x(), v->getPaintHeight()); | 549 paint.drawLine(cursorPos.x(), cursorPos.y(), cursorPos.x(), v->getPaintHeight()); |
427 | 550 |
428 double fundamental = getFrequencyForX(v, cursorPos.x()); | 551 double fundamental = getFrequencyForX(v, cursorPos.x()); |
429 | 552 |
430 int hoffset = 2; | 553 int hoffset = getHorizontalScaleHeight(v, paint) + |
431 if (m_binScale == LogBins) hoffset = 13; | 554 2 * paint.fontMetrics().height(); |
432 | 555 |
433 PaintAssistant::drawVisibleText(v, paint, | 556 PaintAssistant::drawVisibleText(v, paint, |
434 cursorPos.x() + 2, | 557 cursorPos.x() + 2, |
435 v->getPaintHeight() - 2 - hoffset, | 558 v->getPaintHeight() - 2 - hoffset, |
436 QString("%1 Hz").arg(fundamental), | 559 tr("%1 Hz").arg(fundamental), |
437 PaintAssistant::OutlinedText); | 560 PaintAssistant::OutlinedText); |
438 | 561 |
439 if (Pitch::isFrequencyInMidiRange(fundamental)) { | 562 if (Pitch::isFrequencyInMidiRange(fundamental)) { |
440 QString pitchLabel = Pitch::getPitchLabelForFrequency(fundamental); | 563 QString pitchLabel = Pitch::getPitchLabelForFrequency(fundamental); |
441 PaintAssistant::drawVisibleText(v, paint, | 564 PaintAssistant::drawVisibleText(v, paint, |
445 pitchLabel, | 568 pitchLabel, |
446 PaintAssistant::OutlinedText); | 569 PaintAssistant::OutlinedText); |
447 } | 570 } |
448 | 571 |
449 double value = getValueForY(v, cursorPos.y()); | 572 double value = getValueForY(v, cursorPos.y()); |
450 double thresh = m_threshold; | |
451 double db = thresh; | |
452 if (value > 0.0) db = 10.0 * log10(value); | |
453 if (db < thresh) db = thresh; | |
454 | 573 |
455 PaintAssistant::drawVisibleText(v, paint, | 574 PaintAssistant::drawVisibleText(v, paint, |
456 xorigin + 2, | 575 xorigin + 2, |
457 cursorPos.y() - 2, | 576 cursorPos.y() - 2, |
458 QString("%1 V").arg(value), | 577 QString("%1 V").arg(value), |
459 PaintAssistant::OutlinedText); | 578 PaintAssistant::OutlinedText); |
460 | 579 |
461 PaintAssistant::drawVisibleText(v, paint, | 580 if (value > m_threshold) { |
462 xorigin + 2, | 581 double db = 10.0 * log10(value); |
463 cursorPos.y() + 2 + paint.fontMetrics().ascent(), | 582 PaintAssistant::drawVisibleText(v, paint, |
464 QString("%1 dBV").arg(db), | 583 xorigin + 2, |
465 PaintAssistant::OutlinedText); | 584 cursorPos.y() + 2 + |
585 paint.fontMetrics().ascent(), | |
586 QString("%1 dBV").arg(db), | |
587 PaintAssistant::OutlinedText); | |
588 } | |
466 | 589 |
467 int harmonic = 2; | 590 int harmonic = 2; |
468 | 591 |
469 while (harmonic < 100) { | 592 while (harmonic < 100) { |
470 | 593 |
516 if (minvalue > maxvalue) std::swap(minvalue, maxvalue); | 639 if (minvalue > maxvalue) std::swap(minvalue, maxvalue); |
517 | 640 |
518 QString binstr; | 641 QString binstr; |
519 QString hzstr; | 642 QString hzstr; |
520 int minfreq = int(lrint((minbin * m_sliceableModel->getSampleRate()) / | 643 int minfreq = int(lrint((minbin * m_sliceableModel->getSampleRate()) / |
521 m_windowSize)); | 644 getFFTSize())); |
522 int maxfreq = int(lrint((std::max(maxbin, minbin) | 645 int maxfreq = int(lrint((std::max(maxbin, minbin) |
523 * m_sliceableModel->getSampleRate()) / | 646 * m_sliceableModel->getSampleRate()) / |
524 m_windowSize)); | 647 getFFTSize())); |
525 | 648 |
526 if (maxbin != minbin) { | 649 if (maxbin != minbin) { |
527 binstr = tr("%1 - %2").arg(minbin+1).arg(maxbin+1); | 650 binstr = tr("%1 - %2").arg(minbin+1).arg(maxbin+1); |
528 } else { | 651 } else { |
529 binstr = QString("%1").arg(minbin+1); | 652 binstr = QString("%1").arg(minbin+1); |
600 } | 723 } |
601 | 724 |
602 FFTModel *fft = dynamic_cast<FFTModel *> | 725 FFTModel *fft = dynamic_cast<FFTModel *> |
603 (const_cast<DenseThreeDimensionalModel *>(m_sliceableModel)); | 726 (const_cast<DenseThreeDimensionalModel *>(m_sliceableModel)); |
604 | 727 |
605 double thresh = (pow(10, -6) / m_gain) * (m_windowSize / 2.0); // -60dB adj | 728 double thresh = (pow(10, -6) / m_gain) * (getFFTSize() / 2.0); // -60dB adj |
606 | 729 |
607 int xorigin = getVerticalScaleWidth(v, false, paint) + 1; | 730 int xorigin = getVerticalScaleWidth(v, false, paint) + 1; |
608 int scaleHeight = getHorizontalScaleHeight(v, paint); | 731 int scaleHeight = getHorizontalScaleHeight(v, paint); |
732 | |
733 QPoint localPos; | |
734 bool shouldIlluminate = v->shouldIlluminateLocalFeatures(this, localPos); | |
735 | |
736 // cerr << "shouldIlluminate = " << shouldIlluminate << ", localPos = " << localPos.x() << "," << localPos.y() << endl; | |
609 | 737 |
610 if (fft && m_showPeaks) { | 738 if (fft && m_showPeaks) { |
611 | 739 |
612 // draw peak lines | 740 // draw peak lines |
613 | 741 |
622 ColourMapper(ColourMapper::WhiteOnBlack, m_colourInverted, 0, 1); | 750 ColourMapper(ColourMapper::WhiteOnBlack, m_colourInverted, 0, 1); |
623 | 751 |
624 int peakminbin = 0; | 752 int peakminbin = 0; |
625 int peakmaxbin = fft->getHeight() - 1; | 753 int peakmaxbin = fft->getHeight() - 1; |
626 double peakmaxfreq = Pitch::getFrequencyForPitch(128); | 754 double peakmaxfreq = Pitch::getFrequencyForPitch(128); |
627 peakmaxbin = int(((peakmaxfreq * fft->getHeight() * 2) / fft->getSampleRate())); | 755 peakmaxbin = int(((peakmaxfreq * fft->getHeight() * 2) / |
756 fft->getSampleRate())); | |
628 | 757 |
629 FFTModel::PeakSet peaks = fft->getPeakFrequencies | 758 FFTModel::PeakSet peaks = fft->getPeakFrequencies |
630 (FFTModel::MajorPitchAdaptivePeaks, col, peakminbin, peakmaxbin); | 759 (FFTModel::MajorPitchAdaptivePeaks, col, peakminbin, peakmaxbin); |
631 | 760 |
632 BiasCurve curve; | 761 BiasCurve curve; |
633 getBiasCurve(curve); | 762 getBiasCurve(curve); |
634 int cs = int(curve.size()); | 763 int cs = int(curve.size()); |
635 | 764 |
636 std::vector<double> values; | 765 int px = -1; |
766 | |
767 int fuzz = ViewManager::scalePixelSize(3); | |
768 bool illuminatedSomething = false; | |
637 | 769 |
638 for (int bin = 0; bin < fft->getHeight(); ++bin) { | |
639 double value = m_sliceableModel->getValueAt(col, bin); | |
640 if (bin < cs) value *= curve[bin]; | |
641 values.push_back(value); | |
642 } | |
643 | |
644 for (FFTModel::PeakSet::iterator i = peaks.begin(); | 770 for (FFTModel::PeakSet::iterator i = peaks.begin(); |
645 i != peaks.end(); ++i) { | 771 i != peaks.end(); ++i) { |
646 | 772 |
773 double freq = i->second; | |
774 int x = int(lrint(getXForFrequency(v, freq))); | |
775 if (x == px) { | |
776 continue; | |
777 } | |
778 | |
647 int bin = i->first; | 779 int bin = i->first; |
648 | 780 |
649 // cerr << "bin = " << bin << ", thresh = " << thresh << ", value = " << fft->getMagnitudeAt(col, bin) << endl; | 781 // cerr << "bin = " << bin << ", thresh = " << thresh << ", value = " << fft->getMagnitudeAt(col, bin) << endl; |
650 | 782 |
651 if (!fft->isOverThreshold(col, bin, float(thresh))) continue; | 783 double value = fft->getValueAt(col, bin); |
784 if (value < thresh) continue; | |
785 if (bin < cs) value *= curve[bin]; | |
652 | 786 |
653 double freq = i->second; | |
654 | |
655 int x = int(lrint(getXForFrequency(v, freq))); | |
656 | |
657 double norm = 0.f; | 787 double norm = 0.f; |
658 (void)getYForValue(v, values[bin], norm); // don't need return value, need norm | 788 // we need the norm here for colour map; the y coord is |
659 | 789 // only used to pick a label height if illuminating the |
660 paint.setPen(mapper.map(norm)); | 790 // local point |
791 double y = getYForValue(v, value, norm); | |
792 | |
793 QColor colour = mapper.map(norm); | |
794 | |
795 paint.setPen(QPen(colour, 1)); | |
661 paint.drawLine(x, 0, x, v->getPaintHeight() - scaleHeight - 1); | 796 paint.drawLine(x, 0, x, v->getPaintHeight() - scaleHeight - 1); |
797 | |
798 bool illuminateThis = false; | |
799 if (shouldIlluminate && !illuminatedSomething && | |
800 std::abs(localPos.x() - x) <= fuzz) { | |
801 illuminateThis = true; | |
802 } | |
803 | |
804 if (illuminateThis) { | |
805 int labelY = v->getPaintHeight() - | |
806 getHorizontalScaleHeight(v, paint) - | |
807 paint.fontMetrics().height() * 3; | |
808 QString text = tr("%1 Hz").arg(freq); | |
809 int lw = paint.fontMetrics().width(text); | |
810 int gap = ViewManager::scalePixelSize(3); | |
811 double half = double(gap)/2.0; | |
812 int labelX = x - lw - gap; | |
813 if (labelX < getVerticalScaleWidth(v, false, paint)) { | |
814 labelX = x + gap; | |
815 } | |
816 PaintAssistant::drawVisibleText | |
817 (v, paint, labelX, labelY, | |
818 text, PaintAssistant::OutlinedText); | |
819 if (Pitch::isFrequencyInMidiRange(freq)) { | |
820 QString pitchLabel = Pitch::getPitchLabelForFrequency(freq); | |
821 PaintAssistant::drawVisibleText | |
822 (v, paint, | |
823 labelX, labelY + paint.fontMetrics().ascent() + gap, | |
824 pitchLabel, PaintAssistant::OutlinedText); | |
825 } | |
826 paint.fillRect(QRectF(x - half, labelY + gap, gap, gap), | |
827 colour); | |
828 illuminatedSomething = true; | |
829 } | |
830 | |
831 px = x; | |
662 } | 832 } |
663 | 833 |
664 paint.restore(); | 834 paint.restore(); |
665 } | 835 } |
666 | 836 |
744 SpectrumLayer::toXml(QTextStream &stream, | 914 SpectrumLayer::toXml(QTextStream &stream, |
745 QString indent, QString extraAttributes) const | 915 QString indent, QString extraAttributes) const |
746 { | 916 { |
747 QString s = QString("windowSize=\"%1\" " | 917 QString s = QString("windowSize=\"%1\" " |
748 "windowHopLevel=\"%2\" " | 918 "windowHopLevel=\"%2\" " |
749 "showPeaks=\"%3\" ") | 919 "oversampling=\"%3\" " |
920 "showPeaks=\"%4\" ") | |
750 .arg(m_windowSize) | 921 .arg(m_windowSize) |
751 .arg(m_windowHopLevel) | 922 .arg(m_windowHopLevel) |
923 .arg(m_oversampling) | |
752 .arg(m_showPeaks ? "true" : "false"); | 924 .arg(m_showPeaks ? "true" : "false"); |
753 | 925 |
754 SliceLayer::toXml(stream, indent, extraAttributes + " " + s); | 926 SliceLayer::toXml(stream, indent, extraAttributes + " " + s); |
755 } | 927 } |
756 | 928 |
765 if (ok) setWindowSize(windowSize); | 937 if (ok) setWindowSize(windowSize); |
766 | 938 |
767 int windowHopLevel = attributes.value("windowHopLevel").toUInt(&ok); | 939 int windowHopLevel = attributes.value("windowHopLevel").toUInt(&ok); |
768 if (ok) setWindowHopLevel(windowHopLevel); | 940 if (ok) setWindowHopLevel(windowHopLevel); |
769 | 941 |
942 int oversampling = attributes.value("oversampling").toUInt(&ok); | |
943 if (ok) setOversampling(oversampling); | |
944 | |
770 bool showPeaks = (attributes.value("showPeaks").trimmed() == "true"); | 945 bool showPeaks = (attributes.value("showPeaks").trimmed() == "true"); |
771 setShowPeaks(showPeaks); | 946 setShowPeaks(showPeaks); |
772 } | 947 } |
773 | 948 |
774 | 949 |