Mercurial > hg > svgui
comparison layer/SpectrogramLayer.cpp @ 1086:163cb9b98104 spectrogram-minor-refactor
Simplify the oversampling/zero-padding logic. FFT model selection no longer depends on the view.
author | Chris Cannam |
---|---|
date | Fri, 01 Jul 2016 18:30:42 +0100 |
parents | 179ea8a2f650 |
children | 6d990a24ac1b |
comparison
equal
deleted
inserted
replaced
1085:179ea8a2f650 | 1086:163cb9b98104 |
---|---|
59 m_model(0), | 59 m_model(0), |
60 m_channel(0), | 60 m_channel(0), |
61 m_windowSize(1024), | 61 m_windowSize(1024), |
62 m_windowType(HanningWindow), | 62 m_windowType(HanningWindow), |
63 m_windowHopLevel(2), | 63 m_windowHopLevel(2), |
64 m_zeroPadLevel(0), | |
65 m_fftSize(1024), | 64 m_fftSize(1024), |
66 m_gain(1.0), | 65 m_gain(1.0), |
67 m_initialGain(1.0), | 66 m_initialGain(1.0), |
68 m_threshold(0.0), | 67 m_threshold(0.0), |
69 m_initialThreshold(0.0), | 68 m_initialThreshold(0.0), |
169 list.push_back("Gain"); | 168 list.push_back("Gain"); |
170 list.push_back("Colour Rotation"); | 169 list.push_back("Colour Rotation"); |
171 // list.push_back("Min Frequency"); | 170 // list.push_back("Min Frequency"); |
172 // list.push_back("Max Frequency"); | 171 // list.push_back("Max Frequency"); |
173 list.push_back("Frequency Scale"); | 172 list.push_back("Frequency Scale"); |
174 //// list.push_back("Zero Padding"); | |
175 return list; | 173 return list; |
176 } | 174 } |
177 | 175 |
178 QString | 176 QString |
179 SpectrogramLayer::getPropertyLabel(const PropertyName &name) const | 177 SpectrogramLayer::getPropertyLabel(const PropertyName &name) const |
188 if (name == "Gain") return tr("Gain"); | 186 if (name == "Gain") return tr("Gain"); |
189 if (name == "Colour Rotation") return tr("Colour Rotation"); | 187 if (name == "Colour Rotation") return tr("Colour Rotation"); |
190 if (name == "Min Frequency") return tr("Min Frequency"); | 188 if (name == "Min Frequency") return tr("Min Frequency"); |
191 if (name == "Max Frequency") return tr("Max Frequency"); | 189 if (name == "Max Frequency") return tr("Max Frequency"); |
192 if (name == "Frequency Scale") return tr("Frequency Scale"); | 190 if (name == "Frequency Scale") return tr("Frequency Scale"); |
193 if (name == "Zero Padding") return tr("Smoothing"); | |
194 return ""; | 191 return ""; |
195 } | 192 } |
196 | 193 |
197 QString | 194 QString |
198 SpectrogramLayer::getPropertyIconName(const PropertyName &) const | 195 SpectrogramLayer::getPropertyIconName(const PropertyName &) const |
204 SpectrogramLayer::getPropertyType(const PropertyName &name) const | 201 SpectrogramLayer::getPropertyType(const PropertyName &name) const |
205 { | 202 { |
206 if (name == "Gain") return RangeProperty; | 203 if (name == "Gain") return RangeProperty; |
207 if (name == "Colour Rotation") return RangeProperty; | 204 if (name == "Colour Rotation") return RangeProperty; |
208 if (name == "Threshold") return RangeProperty; | 205 if (name == "Threshold") return RangeProperty; |
209 if (name == "Zero Padding") return ToggleProperty; | |
210 return ValueProperty; | 206 return ValueProperty; |
211 } | 207 } |
212 | 208 |
213 QString | 209 QString |
214 SpectrogramLayer::getPropertyGroupName(const PropertyName &name) const | 210 SpectrogramLayer::getPropertyGroupName(const PropertyName &name) const |
215 { | 211 { |
216 if (name == "Bin Display" || | 212 if (name == "Bin Display" || |
217 name == "Frequency Scale") return tr("Bins"); | 213 name == "Frequency Scale") return tr("Bins"); |
218 if (name == "Window Size" || | 214 if (name == "Window Size" || |
219 name == "Window Increment" || | 215 name == "Window Increment") return tr("Window"); |
220 name == "Zero Padding") return tr("Window"); | |
221 if (name == "Colour" || | 216 if (name == "Colour" || |
222 name == "Threshold" || | 217 name == "Threshold" || |
223 name == "Colour Rotation") return tr("Colour"); | 218 name == "Colour Rotation") return tr("Colour"); |
224 if (name == "Normalization" || | 219 if (name == "Normalization" || |
225 name == "Gain" || | 220 name == "Gain" || |
303 *min = 0; | 298 *min = 0; |
304 *max = 5; | 299 *max = 5; |
305 *deflt = 2; | 300 *deflt = 2; |
306 | 301 |
307 val = m_windowHopLevel; | 302 val = m_windowHopLevel; |
308 | |
309 } else if (name == "Zero Padding") { | |
310 | |
311 *min = 0; | |
312 *max = 1; | |
313 *deflt = 0; | |
314 | |
315 val = m_zeroPadLevel > 0 ? 1 : 0; | |
316 | 303 |
317 } else if (name == "Min Frequency") { | 304 } else if (name == "Min Frequency") { |
318 | 305 |
319 *min = 0; | 306 *min = 0; |
320 *max = 9; | 307 *max = 9; |
411 case 2: return tr("50 %"); | 398 case 2: return tr("50 %"); |
412 case 3: return tr("75 %"); | 399 case 3: return tr("75 %"); |
413 case 4: return tr("87.5 %"); | 400 case 4: return tr("87.5 %"); |
414 case 5: return tr("93.75 %"); | 401 case 5: return tr("93.75 %"); |
415 } | 402 } |
416 } | |
417 if (name == "Zero Padding") { | |
418 if (value == 0) return tr("None"); | |
419 return QString("%1x").arg(value + 1); | |
420 } | 403 } |
421 if (name == "Min Frequency") { | 404 if (name == "Min Frequency") { |
422 switch (value) { | 405 switch (value) { |
423 default: | 406 default: |
424 case 0: return tr("No min"); | 407 case 0: return tr("No min"); |
508 setColourMap(value); | 491 setColourMap(value); |
509 } else if (name == "Window Size") { | 492 } else if (name == "Window Size") { |
510 setWindowSize(32 << value); | 493 setWindowSize(32 << value); |
511 } else if (name == "Window Increment") { | 494 } else if (name == "Window Increment") { |
512 setWindowHopLevel(value); | 495 setWindowHopLevel(value); |
513 } else if (name == "Zero Padding") { | |
514 setZeroPadLevel(value > 0.1 ? 3 : 0); | |
515 } else if (name == "Min Frequency") { | 496 } else if (name == "Min Frequency") { |
516 switch (value) { | 497 switch (value) { |
517 default: | 498 default: |
518 case 0: setMinFrequency(0); break; | 499 case 0: setMinFrequency(0); break; |
519 case 1: setMinFrequency(10); break; | 500 case 1: setMinFrequency(10); break; |
603 if (name == "Window Type") { | 584 if (name == "Window Type") { |
604 setWindowType(Preferences::getInstance()->getWindowType()); | 585 setWindowType(Preferences::getInstance()->getWindowType()); |
605 return; | 586 return; |
606 } | 587 } |
607 if (name == "Spectrogram Y Smoothing") { | 588 if (name == "Spectrogram Y Smoothing") { |
589 setWindowSize(m_windowSize); | |
608 invalidateImageCaches(); | 590 invalidateImageCaches(); |
609 invalidateMagnitudes(); | 591 invalidateMagnitudes(); |
610 emit layerParametersChanged(); | 592 emit layerParametersChanged(); |
611 } | 593 } |
612 if (name == "Spectrogram X Smoothing") { | 594 if (name == "Spectrogram X Smoothing") { |
635 SpectrogramLayer::getChannel() const | 617 SpectrogramLayer::getChannel() const |
636 { | 618 { |
637 return m_channel; | 619 return m_channel; |
638 } | 620 } |
639 | 621 |
622 int | |
623 SpectrogramLayer::getFFTOversampling() const | |
624 { | |
625 if (m_binDisplay != AllBins) { | |
626 return 1; | |
627 } | |
628 | |
629 Preferences::SpectrogramSmoothing smoothing = | |
630 Preferences::getInstance()->getSpectrogramSmoothing(); | |
631 | |
632 if (smoothing == Preferences::NoSpectrogramSmoothing || | |
633 smoothing == Preferences::SpectrogramInterpolated) { | |
634 return 1; | |
635 } | |
636 | |
637 return 4; | |
638 } | |
639 | |
640 void | 640 void |
641 SpectrogramLayer::setWindowSize(int ws) | 641 SpectrogramLayer::setWindowSize(int ws) |
642 { | 642 { |
643 if (m_windowSize == ws) return; | 643 int fftSize = ws * getFFTOversampling(); |
644 | |
645 if (m_windowSize == ws && m_fftSize == fftSize) return; | |
644 | 646 |
645 invalidateImageCaches(); | 647 invalidateImageCaches(); |
646 | 648 |
647 m_windowSize = ws; | 649 m_windowSize = ws; |
648 m_fftSize = ws * (m_zeroPadLevel + 1); | 650 m_fftSize = fftSize; |
649 | 651 |
650 invalidateFFTModels(); | 652 invalidateFFTModels(); |
651 | 653 |
652 emit layerParametersChanged(); | 654 emit layerParametersChanged(); |
653 } | 655 } |
676 | 678 |
677 int | 679 int |
678 SpectrogramLayer::getWindowHopLevel() const | 680 SpectrogramLayer::getWindowHopLevel() const |
679 { | 681 { |
680 return m_windowHopLevel; | 682 return m_windowHopLevel; |
681 } | |
682 | |
683 void | |
684 SpectrogramLayer::setZeroPadLevel(int v) | |
685 { | |
686 if (m_zeroPadLevel == v) return; | |
687 | |
688 invalidateImageCaches(); | |
689 | |
690 m_zeroPadLevel = v; | |
691 m_fftSize = m_windowSize * (v + 1); | |
692 | |
693 invalidateFFTModels(); | |
694 | |
695 emit layerParametersChanged(); | |
696 } | |
697 | |
698 int | |
699 SpectrogramLayer::getZeroPadLevel() const | |
700 { | |
701 return m_zeroPadLevel; | |
702 } | 683 } |
703 | 684 |
704 void | 685 void |
705 SpectrogramLayer::setWindowType(WindowType w) | 686 SpectrogramLayer::setWindowType(WindowType w) |
706 { | 687 { |
1173 bool logarithmic = (m_frequencyScale == LogFrequencyScale); | 1154 bool logarithmic = (m_frequencyScale == LogFrequencyScale); |
1174 | 1155 |
1175 q0 = v->getFrequencyForY(y, minf, maxf, logarithmic); | 1156 q0 = v->getFrequencyForY(y, minf, maxf, logarithmic); |
1176 q1 = v->getFrequencyForY(y - 1, minf, maxf, logarithmic); | 1157 q1 = v->getFrequencyForY(y - 1, minf, maxf, logarithmic); |
1177 | 1158 |
1178 // Now map these on to ("proportions of") actual bins, using raw | 1159 // Now map these on to ("proportions of") actual bins |
1179 // FFT size (unsmoothed) | |
1180 | 1160 |
1181 q0 = (q0 * m_fftSize) / sr; | 1161 q0 = (q0 * m_fftSize) / sr; |
1182 q1 = (q1 * m_fftSize) / sr; | 1162 q1 = (q1 * m_fftSize) / sr; |
1183 | |
1184 return true; | |
1185 } | |
1186 | |
1187 bool | |
1188 SpectrogramLayer::getSmoothedYBinRange(LayerGeometryProvider *v, int y, double &q0, double &q1) const | |
1189 { | |
1190 Profiler profiler("SpectrogramLayer::getSmoothedYBinRange"); | |
1191 | |
1192 int h = v->getPaintHeight(); | |
1193 if (y < 0 || y >= h) return false; | |
1194 | |
1195 sv_samplerate_t sr = m_model->getSampleRate(); | |
1196 double minf = getEffectiveMinFrequency(); | |
1197 double maxf = getEffectiveMaxFrequency(); | |
1198 | |
1199 bool logarithmic = (m_frequencyScale == LogFrequencyScale); | |
1200 | |
1201 q0 = v->getFrequencyForY(y, minf, maxf, logarithmic); | |
1202 q1 = v->getFrequencyForY(y - 1, minf, maxf, logarithmic); | |
1203 | |
1204 // Now map these on to ("proportions of") actual bins, using raw | |
1205 // FFT size (unsmoothed) | |
1206 | |
1207 q0 = (q0 * getFFTSize(v)) / sr; | |
1208 q1 = (q1 * getFFTSize(v)) / sr; | |
1209 | 1163 |
1210 return true; | 1164 return true; |
1211 } | 1165 } |
1212 | 1166 |
1213 double | 1167 double |
1231 | 1185 |
1232 bool logarithmic = (m_frequencyScale == LogFrequencyScale); | 1186 bool logarithmic = (m_frequencyScale == LogFrequencyScale); |
1233 | 1187 |
1234 double q = v->getFrequencyForY(y, minf, maxf, logarithmic); | 1188 double q = v->getFrequencyForY(y, minf, maxf, logarithmic); |
1235 | 1189 |
1236 // Now map on to ("proportions of") actual bins, using raw FFT | 1190 // Now map on to ("proportions of") actual bins |
1237 // size (unsmoothed) | |
1238 | 1191 |
1239 q = (q * getFFTSize(v)) / sr; | 1192 q = (q * getFFTSize(v)) / sr; |
1240 | 1193 |
1241 return q; | 1194 return q; |
1242 } | 1195 } |
1343 if (q == q0i) freqMin = binfreq; | 1296 if (q == q0i) freqMin = binfreq; |
1344 if (q == q1i) freqMax = binfreq; | 1297 if (q == q1i) freqMax = binfreq; |
1345 | 1298 |
1346 if (peaksOnly && !fft->isLocalPeak(s, q)) continue; | 1299 if (peaksOnly && !fft->isLocalPeak(s, q)) continue; |
1347 | 1300 |
1348 if (!fft->isOverThreshold(s, q, float(m_threshold * double(m_fftSize)/2.0))) continue; | 1301 if (!fft->isOverThreshold |
1302 (s, q, float(m_threshold * double(m_fftSize)/2.0))) { | |
1303 continue; | |
1304 } | |
1349 | 1305 |
1350 double freq = binfreq; | 1306 double freq = binfreq; |
1351 | 1307 |
1352 if (s < int(fft->getWidth()) - 1) { | 1308 if (s < int(fft->getWidth()) - 1) { |
1353 | 1309 |
1388 | 1344 |
1389 int s0i = int(s0 + 0.001); | 1345 int s0i = int(s0 + 0.001); |
1390 int s1i = int(s1); | 1346 int s1i = int(s1); |
1391 | 1347 |
1392 bool rv = false; | 1348 bool rv = false; |
1393 | |
1394 int zp = getZeroPadLevel(v); | |
1395 q0i *= zp + 1; | |
1396 q1i *= zp + 1; | |
1397 | 1349 |
1398 FFTModel *fft = getFFTModel(v); | 1350 FFTModel *fft = getFFTModel(v); |
1399 | 1351 |
1400 if (fft) { | 1352 if (fft) { |
1401 | 1353 |
1434 | 1386 |
1435 return rv; | 1387 return rv; |
1436 } | 1388 } |
1437 | 1389 |
1438 int | 1390 int |
1439 SpectrogramLayer::getZeroPadLevel(const LayerGeometryProvider *v) const | 1391 SpectrogramLayer::getFFTSize(const LayerGeometryProvider *) const |
1440 { | 1392 { |
1441 //!!! tidy all this stuff | 1393 //!!! |
1442 | 1394 return m_fftSize; |
1443 if (m_binDisplay != AllBins) return 0; | |
1444 | |
1445 Preferences::SpectrogramSmoothing smoothing = | |
1446 Preferences::getInstance()->getSpectrogramSmoothing(); | |
1447 | |
1448 if (smoothing == Preferences::NoSpectrogramSmoothing || | |
1449 smoothing == Preferences::SpectrogramInterpolated) return 0; | |
1450 | |
1451 if (m_frequencyScale == LogFrequencyScale) return 3; | |
1452 | |
1453 sv_samplerate_t sr = m_model->getSampleRate(); | |
1454 | |
1455 int maxbin = m_fftSize / 2; | |
1456 if (m_maxFrequency > 0) { | |
1457 maxbin = int((double(m_maxFrequency) * m_fftSize) / sr + 0.1); | |
1458 if (maxbin > m_fftSize / 2) maxbin = m_fftSize / 2; | |
1459 } | |
1460 | |
1461 int minbin = 1; | |
1462 if (m_minFrequency > 0) { | |
1463 minbin = int((double(m_minFrequency) * m_fftSize) / sr + 0.1); | |
1464 if (minbin < 1) minbin = 1; | |
1465 if (minbin >= maxbin) minbin = maxbin - 1; | |
1466 } | |
1467 | |
1468 double perPixel = | |
1469 double(v->getPaintHeight()) / | |
1470 double((maxbin - minbin) / (m_zeroPadLevel + 1)); | |
1471 | |
1472 if (perPixel > 2.8) { | |
1473 return 3; // 4x oversampling | |
1474 } else if (perPixel > 1.5) { | |
1475 return 1; // 2x | |
1476 } else { | |
1477 return 0; // 1x | |
1478 } | |
1479 } | |
1480 | |
1481 int | |
1482 SpectrogramLayer::getFFTSize(const LayerGeometryProvider *v) const | |
1483 { | |
1484 return m_fftSize * (getZeroPadLevel(v) + 1); | |
1485 } | 1395 } |
1486 | 1396 |
1487 FFTModel * | 1397 FFTModel * |
1488 SpectrogramLayer::getFFTModel(const LayerGeometryProvider *v) const | 1398 SpectrogramLayer::getFFTModel(const LayerGeometryProvider *v) const |
1489 { | 1399 { |
1832 | 1742 |
1833 // Set minFreq and maxFreq to the frequency extents of the possibly | 1743 // Set minFreq and maxFreq to the frequency extents of the possibly |
1834 // zero-padded visible bin range, and displayMinFreq and displayMaxFreq | 1744 // zero-padded visible bin range, and displayMinFreq and displayMaxFreq |
1835 // to the actual scale frequency extents (presumably not zero padded). | 1745 // to the actual scale frequency extents (presumably not zero padded). |
1836 | 1746 |
1837 // If we are zero padding, we want to use the zero-padded | 1747 // If we are zero padding (i.e. oversampling) we want to use the |
1838 // equivalents of the bins that we would be using if not zero | 1748 // zero-padded equivalents of the bins that we would be using if |
1839 // padded, to avoid spaces at the top and bottom of the display. | 1749 // not zero padded, to avoid spaces at the top and bottom of the |
1840 | 1750 // display. |
1841 // Note fftSize is the actual zero-padded fft size, m_fftSize the | |
1842 // nominal fft size. | |
1843 | 1751 |
1844 int maxbin = m_fftSize / 2; | 1752 int maxbin = m_fftSize / 2; |
1845 if (m_maxFrequency > 0) { | 1753 if (m_maxFrequency > 0) { |
1846 maxbin = int((double(m_maxFrequency) * m_fftSize) / sr + 0.001); | 1754 maxbin = int((double(m_maxFrequency) * m_fftSize) / sr + 0.001); |
1847 if (maxbin > m_fftSize / 2) maxbin = m_fftSize / 2; | 1755 if (maxbin > m_fftSize / 2) maxbin = m_fftSize / 2; |
1853 // cerr << "m_minFrequency = " << m_minFrequency << " -> minbin = " << minbin << endl; | 1761 // cerr << "m_minFrequency = " << m_minFrequency << " -> minbin = " << minbin << endl; |
1854 if (minbin < 1) minbin = 1; | 1762 if (minbin < 1) minbin = 1; |
1855 if (minbin >= maxbin) minbin = maxbin - 1; | 1763 if (minbin >= maxbin) minbin = maxbin - 1; |
1856 } | 1764 } |
1857 | 1765 |
1858 int zpl = getZeroPadLevel(v) + 1; | 1766 int over = getFFTOversampling(); |
1859 minbin = minbin * zpl; | 1767 minbin = minbin * over; |
1860 maxbin = (maxbin + 1) * zpl - 1; | 1768 maxbin = (maxbin + 1) * over - 1; |
1861 | 1769 |
1862 double minFreq = (double(minbin) * sr) / fftSize; | 1770 double minFreq = (double(minbin) * sr) / fftSize; |
1863 double maxFreq = (double(maxbin) * sr) / fftSize; | 1771 double maxFreq = (double(maxbin) * sr) / fftSize; |
1864 | 1772 |
1865 double displayMinFreq = minFreq; | 1773 double displayMinFreq = minFreq; |
2006 | 1914 |
2007 if (m_binDisplay != PeakFrequencies) { | 1915 if (m_binDisplay != PeakFrequencies) { |
2008 | 1916 |
2009 for (int y = 0; y < h; ++y) { | 1917 for (int y = 0; y < h; ++y) { |
2010 double q0 = 0, q1 = 0; | 1918 double q0 = 0, q1 = 0; |
2011 if (!getSmoothedYBinRange(v, h-y-1, q0, q1)) { | 1919 if (!getYBinRange(v, h-y-1, q0, q1)) { |
2012 binfory[y] = -1; | 1920 binfory[y] = -1; |
2013 } else { | 1921 } else { |
2014 binfory[y] = q0; | 1922 binfory[y] = q0; |
2015 } | 1923 } |
2016 } | 1924 } |