Mercurial > hg > svgui
comparison layer/SpectrogramLayer.cpp @ 40:3be4438b186d
* More fixes, tidying etc in spectrogram layer. Added a piano keyboard
for the log frequency scale mode.
author | Chris Cannam |
---|---|
date | Fri, 24 Feb 2006 15:46:47 +0000 |
parents | beb801473743 |
children | f2c416cbdaa9 |
comparison
equal
deleted
inserted
replaced
39:5ce844ec854a | 40:3be4438b186d |
---|---|
65 setColourScale(LinearColourScale); | 65 setColourScale(LinearColourScale); |
66 } else if (config == MelodicPeaks) { | 66 } else if (config == MelodicPeaks) { |
67 setWindowSize(4096); | 67 setWindowSize(4096); |
68 setWindowOverlap(90); | 68 setWindowOverlap(90); |
69 setWindowType(BlackmanWindow); | 69 setWindowType(BlackmanWindow); |
70 setMaxFrequency(1500); | 70 setMaxFrequency(2000); |
71 setMinFrequency(40); | 71 setMinFrequency(40); |
72 setFrequencyScale(LogFrequencyScale); | 72 setFrequencyScale(LogFrequencyScale); |
73 setColourScale(dBColourScale); | 73 setColourScale(dBColourScale); |
74 setBinDisplay(PeakFrequencies); | 74 setBinDisplay(PeakFrequencies); |
75 setNormalizeColumns(true); | 75 setNormalizeColumns(true); |
316 case 0: return tr("Default"); | 316 case 0: return tr("Default"); |
317 case 1: return tr("White on Black"); | 317 case 1: return tr("White on Black"); |
318 case 2: return tr("Black on White"); | 318 case 2: return tr("Black on White"); |
319 case 3: return tr("Red on Blue"); | 319 case 3: return tr("Red on Blue"); |
320 case 4: return tr("Yellow on Black"); | 320 case 4: return tr("Yellow on Black"); |
321 case 5: return tr("Red on Black"); | 321 case 5: return tr("Fruit Salad"); |
322 } | 322 } |
323 } | 323 } |
324 if (name == tr("Colour Scale")) { | 324 if (name == tr("Colour Scale")) { |
325 switch (value) { | 325 switch (value) { |
326 default: | 326 default: |
420 case 0: setColourScheme(DefaultColours); break; | 420 case 0: setColourScheme(DefaultColours); break; |
421 case 1: setColourScheme(WhiteOnBlack); break; | 421 case 1: setColourScheme(WhiteOnBlack); break; |
422 case 2: setColourScheme(BlackOnWhite); break; | 422 case 2: setColourScheme(BlackOnWhite); break; |
423 case 3: setColourScheme(RedOnBlue); break; | 423 case 3: setColourScheme(RedOnBlue); break; |
424 case 4: setColourScheme(YellowOnBlack); break; | 424 case 4: setColourScheme(YellowOnBlack); break; |
425 case 5: setColourScheme(RedOnBlack); break; | 425 case 5: setColourScheme(Rainbow); break; |
426 } | 426 } |
427 } else if (name == tr("Window Type")) { | 427 } else if (name == tr("Window Type")) { |
428 setWindowType(WindowType(value)); | 428 setWindowType(WindowType(value)); |
429 } else if (name == tr("Window Size")) { | 429 } else if (name == tr("Window Size")) { |
430 setWindowSize(32 << value); | 430 setWindowSize(32 << value); |
582 } | 582 } |
583 | 583 |
584 void | 584 void |
585 SpectrogramLayer::setGain(float gain) | 585 SpectrogramLayer::setGain(float gain) |
586 { | 586 { |
587 if (m_gain == gain) return; //!!! inadequate for floats! | 587 if (m_gain == gain) return; |
588 | 588 |
589 m_mutex.lock(); | 589 m_mutex.lock(); |
590 m_pixmapCacheInvalid = true; | 590 m_pixmapCacheInvalid = true; |
591 | 591 |
592 m_gain = gain; | 592 m_gain = gain; |
605 } | 605 } |
606 | 606 |
607 void | 607 void |
608 SpectrogramLayer::setThreshold(float threshold) | 608 SpectrogramLayer::setThreshold(float threshold) |
609 { | 609 { |
610 if (m_threshold == threshold) return; //!!! inadequate for floats! | 610 if (m_threshold == threshold) return; |
611 | 611 |
612 m_mutex.lock(); | 612 m_mutex.lock(); |
613 m_pixmapCacheInvalid = true; | 613 m_pixmapCacheInvalid = true; |
614 | 614 |
615 m_threshold = threshold; | 615 m_threshold = threshold; |
955 256 - px, | 955 256 - px, |
956 pixel, | 956 pixel, |
957 pixel / 4); | 957 pixel / 4); |
958 break; | 958 break; |
959 | 959 |
960 case RedOnBlack: | 960 case Rainbow: |
961 colour = QColor::fromHsv(10, pixel, pixel); | 961 hue = 250 - pixel; |
962 if (hue < 0) hue += 256; | |
963 colour = QColor::fromHsv(pixel, 255, 255); | |
962 break; | 964 break; |
963 } | 965 } |
964 | 966 |
965 m_cache->setColour(pixel, colour); | 967 m_cache->setColour(pixel, colour); |
966 } | 968 } |
1117 unsigned char | 1119 unsigned char |
1118 SpectrogramLayer::getDisplayValue(float input) const | 1120 SpectrogramLayer::getDisplayValue(float input) const |
1119 { | 1121 { |
1120 int value; | 1122 int value; |
1121 | 1123 |
1122 if (m_colourScale == PhaseColourScale) { | 1124 switch (m_colourScale) { |
1123 | 1125 |
1124 value = int((input * 127 / M_PI) + 128); | 1126 default: |
1125 | 1127 case LinearColourScale: |
1126 } else { | 1128 value = int |
1127 | 1129 (input * (m_normalizeColumns ? 1.0 : 50.0) * 255.0) + 1; |
1128 switch (m_colourScale) { | 1130 break; |
1129 | 1131 |
1130 default: | 1132 case MeterColourScale: |
1131 case LinearColourScale: | 1133 value = AudioLevel::multiplier_to_preview |
1132 value = int(input * 50 * 255) + 1; | 1134 (input * (m_normalizeColumns ? 1.0 : 50.0), 255) + 1; |
1133 break; | 1135 break; |
1134 | 1136 |
1135 case MeterColourScale: | 1137 case dBColourScale: |
1136 value = AudioLevel::multiplier_to_preview(input * 50, 255) + 1; | 1138 input = 20.0 * log10(input); |
1137 break; | 1139 input = (input + 80.0) / 80.0; |
1138 | 1140 if (input < 0.0) input = 0.0; |
1139 case dBColourScale: | 1141 if (input > 1.0) input = 1.0; |
1140 input = 20.0 * log10(input); | 1142 value = int(input * 255.0) + 1; |
1141 input = (input + 80.0) / 80.0; | 1143 break; |
1142 if (input < 0.0) input = 0.0; | 1144 |
1143 if (input > 1.0) input = 1.0; | 1145 case PhaseColourScale: |
1144 value = int(input * 255) + 1; | 1146 value = int((input * 127.0 / M_PI) + 128); |
1145 } | 1147 break; |
1146 } | 1148 } |
1147 | 1149 |
1148 if (value > UCHAR_MAX) value = UCHAR_MAX; | 1150 if (value > UCHAR_MAX) value = UCHAR_MAX; |
1149 if (value < 0) value = 0; | 1151 if (value < 0) value = 0; |
1150 return value; | 1152 return value; |
1153 } | |
1154 | |
1155 float | |
1156 SpectrogramLayer::getInputForDisplayValue(unsigned char uc) const | |
1157 { | |
1158 int value = uc; | |
1159 float input; | |
1160 | |
1161 switch (m_colourScale) { | |
1162 | |
1163 default: | |
1164 case LinearColourScale: | |
1165 input = float(value - 1) / 255.0 / (m_normalizeColumns ? 1 : 50); | |
1166 break; | |
1167 | |
1168 case MeterColourScale: | |
1169 input = AudioLevel::preview_to_multiplier(value - 1, 255) | |
1170 / (m_normalizeColumns ? 1.0 : 50.0); | |
1171 break; | |
1172 | |
1173 case dBColourScale: | |
1174 input = float(value - 1) / 255.0; | |
1175 input = (input * 80.0) - 80.0; | |
1176 input = powf(10.0, input) / 20.0; | |
1177 value = int(input); | |
1178 break; | |
1179 | |
1180 case PhaseColourScale: | |
1181 input = float(value - 128) * M_PI / 127.0; | |
1182 break; | |
1183 } | |
1184 | |
1185 return input; | |
1151 } | 1186 } |
1152 | 1187 |
1153 | 1188 |
1154 SpectrogramLayer::Cache::Cache() : | 1189 SpectrogramLayer::Cache::Cache() : |
1155 m_width(0), | 1190 m_width(0), |
1218 for (size_t x = 0; x < m_width; ++x) { | 1253 for (size_t x = 0; x < m_width; ++x) { |
1219 for (size_t y = 0; y < m_height; ++y) { | 1254 for (size_t y = 0; y < m_height; ++y) { |
1220 m_magnitude[y][x] = 0; | 1255 m_magnitude[y][x] = 0; |
1221 m_phase[y][x] = 0; | 1256 m_phase[y][x] = 0; |
1222 } | 1257 } |
1223 m_factor[x] = 1.0f; | 1258 m_factor[x] = 1.0; |
1224 } | 1259 } |
1225 } | 1260 } |
1226 | 1261 |
1227 void | 1262 void |
1228 SpectrogramLayer::CacheFillThread::run() | 1263 SpectrogramLayer::CacheFillThread::run() |
1426 | 1461 |
1427 if (!interrupted) m_layer.m_condition.wait(&m_layer.m_mutex, 2000); | 1462 if (!interrupted) m_layer.m_condition.wait(&m_layer.m_mutex, 2000); |
1428 } | 1463 } |
1429 } | 1464 } |
1430 | 1465 |
1466 float | |
1467 SpectrogramLayer::getEffectiveMinFrequency() const | |
1468 { | |
1469 int sr = m_model->getSampleRate(); | |
1470 float minf = float(sr) / m_windowSize; | |
1471 | |
1472 if (m_minFrequency > 0.0) { | |
1473 size_t minbin = size_t((double(m_minFrequency) * m_windowSize) / sr + 0.01); | |
1474 if (minbin < 1) minbin = 1; | |
1475 minf = minbin * sr / m_windowSize; | |
1476 } | |
1477 | |
1478 return minf; | |
1479 } | |
1480 | |
1481 float | |
1482 SpectrogramLayer::getEffectiveMaxFrequency() const | |
1483 { | |
1484 int sr = m_model->getSampleRate(); | |
1485 float maxf = float(sr) / 2; | |
1486 | |
1487 if (m_maxFrequency > 0.0) { | |
1488 size_t maxbin = size_t((double(m_maxFrequency) * m_windowSize) / sr + 0.1); | |
1489 if (maxbin > m_windowSize / 2) maxbin = m_windowSize / 2; | |
1490 maxf = maxbin * sr / m_windowSize; | |
1491 } | |
1492 | |
1493 return maxf; | |
1494 } | |
1495 | |
1431 bool | 1496 bool |
1432 SpectrogramLayer::getYBinRange(int y, float &q0, float &q1) const | 1497 SpectrogramLayer::getYBinRange(int y, float &q0, float &q1) const |
1433 { | 1498 { |
1434 int h = m_view->height(); | 1499 int h = m_view->height(); |
1435 if (y < 0 || y >= h) return false; | 1500 if (y < 0 || y >= h) return false; |
1436 | 1501 |
1437 int sr = m_model->getSampleRate(); | 1502 int sr = m_model->getSampleRate(); |
1438 float minf = float(sr) / m_windowSize; | 1503 float minf = getEffectiveMinFrequency(); |
1439 float maxf = float(sr) / 2; | 1504 float maxf = getEffectiveMaxFrequency(); |
1440 | |
1441 if (m_minFrequency > 0.0) minf = m_minFrequency; | |
1442 if (m_maxFrequency > 0.0) maxf = m_maxFrequency; | |
1443 | 1505 |
1444 bool logarithmic = (m_frequencyScale == LogFrequencyScale); | 1506 bool logarithmic = (m_frequencyScale == LogFrequencyScale); |
1445 | 1507 |
1446 q0 = m_view->getFrequencyForY(y, minf, maxf, logarithmic); | 1508 q0 = m_view->getFrequencyForY(y, minf, maxf, logarithmic); |
1447 q1 = m_view->getFrequencyForY(y - 1, minf, maxf, logarithmic); | 1509 q1 = m_view->getFrequencyForY(y - 1, minf, maxf, logarithmic); |
1448 | 1510 |
1449 // Now map these on to actual bins | 1511 // Now map these on to actual bins |
1450 | 1512 |
1451 int b0 = (q0 * m_windowSize) / sr; | 1513 int b0 = int((q0 * m_windowSize) / sr); |
1452 int b1 = (q1 * m_windowSize) / sr; | 1514 int b1 = int((q1 * m_windowSize) / sr); |
1453 | 1515 |
1516 //!!! this is supposed to return fractions-of-bins, as it were, hence the floats | |
1454 q0 = b0; | 1517 q0 = b0; |
1455 q1 = b1; | 1518 q1 = b1; |
1456 | 1519 |
1457 // q0 = (b0 * sr) / m_windowSize; | 1520 // q0 = (b0 * sr) / m_windowSize; |
1458 // q1 = (b1 * sr) / m_windowSize; | 1521 // q1 = (b1 * sr) / m_windowSize; |
1563 | 1626 |
1564 if (!m_cache->isOverThreshold(s, q, m_threshold)) continue; | 1627 if (!m_cache->isOverThreshold(s, q, m_threshold)) continue; |
1565 | 1628 |
1566 float freq = binfreq; | 1629 float freq = binfreq; |
1567 bool steady = false; | 1630 bool steady = false; |
1568 | 1631 |
1569 if (s < m_cache->getWidth() - 1) { | 1632 if (s < int(m_cache->getWidth()) - 1) { |
1570 | 1633 |
1571 freq = calculateFrequency(q, | 1634 freq = calculateFrequency(q, |
1572 windowSize, | 1635 windowSize, |
1573 windowIncrement, | 1636 windowIncrement, |
1574 sr, | 1637 sr, |
1583 } | 1646 } |
1584 } | 1647 } |
1585 } | 1648 } |
1586 | 1649 |
1587 if (!haveAdj) { | 1650 if (!haveAdj) { |
1588 adjFreqMin = adjFreqMax = 0.0f; | 1651 adjFreqMin = adjFreqMax = 0.0; |
1589 } | 1652 } |
1590 | 1653 |
1591 return haveAdj; | 1654 return haveAdj; |
1592 } | 1655 } |
1593 | 1656 |
1812 if (m_maxFrequency > 0) { | 1875 if (m_maxFrequency > 0) { |
1813 bins = int((double(m_maxFrequency) * m_windowSize) / sr + 0.1); | 1876 bins = int((double(m_maxFrequency) * m_windowSize) / sr + 0.1); |
1814 if (bins > m_windowSize / 2) bins = m_windowSize / 2; | 1877 if (bins > m_windowSize / 2) bins = m_windowSize / 2; |
1815 } | 1878 } |
1816 | 1879 |
1817 size_t minbin = 0; | 1880 size_t minbin = 1; |
1818 if (m_minFrequency > 0) { | 1881 if (m_minFrequency > 0) { |
1819 minbin = int((double(m_minFrequency) * m_windowSize) / sr + 0.1); | 1882 minbin = int((double(m_minFrequency) * m_windowSize) / sr + 0.1); |
1883 if (minbin < 1) minbin = 1; | |
1820 if (minbin >= bins) minbin = bins - 1; | 1884 if (minbin >= bins) minbin = bins - 1; |
1821 } | 1885 } |
1822 | 1886 |
1823 float minFreq = (float(minbin) * sr) / m_windowSize; | 1887 float minFreq = (float(minbin) * sr) / m_windowSize; |
1824 float maxFreq = (float(bins) * sr) / m_windowSize; | 1888 float maxFreq = (float(bins) * sr) / m_windowSize; |
1825 | 1889 |
1826 size_t increment = getWindowIncrement(); | 1890 size_t increment = getWindowIncrement(); |
1891 | |
1892 bool logarithmic = (m_frequencyScale == LogFrequencyScale); | |
1827 | 1893 |
1828 m_mutex.unlock(); | 1894 m_mutex.unlock(); |
1829 | 1895 |
1830 for (int x = 0; x < w; ++x) { | 1896 for (int x = 0; x < w; ++x) { |
1831 | 1897 |
1834 m_mutex.unlock(); | 1900 m_mutex.unlock(); |
1835 break; | 1901 break; |
1836 } | 1902 } |
1837 | 1903 |
1838 for (int y = 0; y < h; ++y) { | 1904 for (int y = 0; y < h; ++y) { |
1839 ymag[y] = 0.0f; | 1905 ymag[y] = 0.0; |
1840 ydiv[y] = 0.0f; | 1906 ydiv[y] = 0.0; |
1841 } | 1907 } |
1842 | 1908 |
1843 float s0 = 0, s1 = 0; | 1909 float s0 = 0, s1 = 0; |
1844 | 1910 |
1845 if (!getXBinRange(x0 + x, s0, s1)) { | 1911 if (!getXBinRange(x0 + x, s0, s1)) { |
1854 int s0i = int(s0 + 0.001); | 1920 int s0i = int(s0 + 0.001); |
1855 int s1i = int(s1); | 1921 int s1i = int(s1); |
1856 | 1922 |
1857 for (size_t q = minbin; q < bins; ++q) { | 1923 for (size_t q = minbin; q < bins; ++q) { |
1858 | 1924 |
1925 float f0 = (float(q) * sr) / m_windowSize; | |
1926 float f1 = (float(q + 1) * sr) / m_windowSize; | |
1927 | |
1928 float y0 = 0, y1 = 0; | |
1929 | |
1930 if (m_binDisplay != PeakFrequencies || | |
1931 s1i >= int(m_cache->getWidth())) { | |
1932 y0 = m_view->getYForFrequency(f1, minFreq, maxFreq, logarithmic); | |
1933 y1 = m_view->getYForFrequency(f0, minFreq, maxFreq, logarithmic); | |
1934 } | |
1935 | |
1859 for (int s = s0i; s <= s1i; ++s) { | 1936 for (int s = s0i; s <= s1i; ++s) { |
1937 | |
1938 if (m_binDisplay == PeakBins || | |
1939 m_binDisplay == PeakFrequencies) { | |
1940 if (!m_cache->isLocalPeak(s, q)) continue; | |
1941 } | |
1942 | |
1943 if (!m_cache->isOverThreshold(s, q, m_threshold)) continue; | |
1860 | 1944 |
1861 float sprop = 1.0; | 1945 float sprop = 1.0; |
1862 if (s == s0i) sprop *= (s + 1) - s0; | 1946 if (s == s0i) sprop *= (s + 1) - s0; |
1863 if (s == s1i) sprop *= s1 - s; | 1947 if (s == s1i) sprop *= s1 - s; |
1864 | |
1865 float f0 = (float(q) * sr) / m_windowSize; | |
1866 float f1 = (float(q + 1) * sr) / m_windowSize; | |
1867 | 1948 |
1868 if (m_binDisplay == PeakFrequencies && | 1949 if (m_binDisplay == PeakFrequencies && |
1869 s < m_cache->getWidth() - 1) { | 1950 s < int(m_cache->getWidth()) - 1) { |
1870 | 1951 |
1871 bool steady = false; | 1952 bool steady = false; |
1872 f0 = f1 = calculateFrequency(q, | 1953 f0 = f1 = calculateFrequency(q, |
1873 m_windowSize, | 1954 m_windowSize, |
1874 increment, | 1955 increment, |
1875 sr, | 1956 sr, |
1876 m_cache->getPhaseAt(s, q), | 1957 m_cache->getPhaseAt(s, q), |
1877 m_cache->getPhaseAt(s+1, q), | 1958 m_cache->getPhaseAt(s+1, q), |
1878 steady); | 1959 steady); |
1960 | |
1961 y0 = y1 = m_view->getYForFrequency | |
1962 (f0, minFreq, maxFreq, logarithmic); | |
1879 } | 1963 } |
1880 | |
1881 float y0 = m_view->getYForFrequency | |
1882 (f1, minFreq, maxFreq, | |
1883 m_frequencyScale == LogFrequencyScale); | |
1884 | |
1885 float y1 = m_view->getYForFrequency | |
1886 (f0, minFreq, maxFreq, | |
1887 m_frequencyScale == LogFrequencyScale); | |
1888 | 1964 |
1889 int y0i = int(y0 + 0.001); | 1965 int y0i = int(y0 + 0.001); |
1890 int y1i = int(y1); | 1966 int y1i = int(y1); |
1891 | 1967 |
1892 for (int y = y0i; y <= y1i; ++y) { | 1968 for (int y = y0i; y <= y1i; ++y) { |
1894 if (y < 0 || y >= h) continue; | 1970 if (y < 0 || y >= h) continue; |
1895 | 1971 |
1896 float yprop = sprop; | 1972 float yprop = sprop; |
1897 if (y == y0i) yprop *= (y + 1) - y0; | 1973 if (y == y0i) yprop *= (y + 1) - y0; |
1898 if (y == y1i) yprop *= y1 - y; | 1974 if (y == y1i) yprop *= y1 - y; |
1899 | |
1900 if (m_binDisplay == PeakBins || | |
1901 m_binDisplay == PeakFrequencies) { | |
1902 if (!m_cache->isLocalPeak(s, q)) continue; | |
1903 } | |
1904 | |
1905 if (!m_cache->isOverThreshold(s, q, m_threshold)) continue; | |
1906 | 1975 |
1907 float value; | 1976 float value; |
1908 | 1977 |
1909 if (m_colourScale == PhaseColourScale) { | 1978 if (m_colourScale == PhaseColourScale) { |
1910 value = m_cache->getPhaseAt(s, q); | 1979 value = m_cache->getPhaseAt(s, q); |
1920 } | 1989 } |
1921 } | 1990 } |
1922 | 1991 |
1923 for (int y = 0; y < h; ++y) { | 1992 for (int y = 0; y < h; ++y) { |
1924 | 1993 |
1925 unsigned char pixel = 0; | |
1926 | |
1927 if (ydiv[y] > 0.0) { | 1994 if (ydiv[y] > 0.0) { |
1995 | |
1996 unsigned char pixel = 0; | |
1997 | |
1928 float avg = ymag[y] / ydiv[y]; | 1998 float avg = ymag[y] / ydiv[y]; |
1929 pixel = getDisplayValue(avg); | 1999 pixel = getDisplayValue(avg); |
2000 | |
2001 assert(x <= scaled.width()); | |
2002 QColor c = m_cache->getColour(pixel); | |
2003 scaled.setPixel(x, y, | |
2004 qRgb(c.red(), c.green(), c.blue())); | |
1930 } | 2005 } |
1931 | |
1932 assert(x <= scaled.width()); | |
1933 QColor c = m_cache->getColour(pixel); | |
1934 scaled.setPixel(x, y, | |
1935 qRgb(c.red(), c.green(), c.blue())); | |
1936 } | 2006 } |
1937 | 2007 |
1938 m_mutex.unlock(); | 2008 m_mutex.unlock(); |
1939 } | 2009 } |
1940 | 2010 |
2088 | 2158 |
2089 return text; | 2159 return text; |
2090 } | 2160 } |
2091 | 2161 |
2092 int | 2162 int |
2163 SpectrogramLayer::getColourScaleWidth(QPainter &paint) const | |
2164 { | |
2165 int cw; | |
2166 | |
2167 switch (m_colourScale) { | |
2168 default: | |
2169 case LinearColourScale: | |
2170 cw = paint.fontMetrics().width(QString("0.00")); | |
2171 break; | |
2172 | |
2173 case MeterColourScale: | |
2174 case dBColourScale: | |
2175 cw = std::max(paint.fontMetrics().width(tr("-Inf")), | |
2176 paint.fontMetrics().width(tr("-90"))); | |
2177 break; | |
2178 | |
2179 case PhaseColourScale: | |
2180 cw = paint.fontMetrics().width(QString("-") + QChar(0x3c0)); | |
2181 break; | |
2182 } | |
2183 | |
2184 return cw; | |
2185 } | |
2186 | |
2187 int | |
2093 SpectrogramLayer::getVerticalScaleWidth(QPainter &paint) const | 2188 SpectrogramLayer::getVerticalScaleWidth(QPainter &paint) const |
2094 { | 2189 { |
2095 if (!m_model || !m_model->isOK()) return 0; | 2190 if (!m_model || !m_model->isOK()) return 0; |
2191 | |
2192 int cw = getColourScaleWidth(paint); | |
2096 | 2193 |
2097 int tw = paint.fontMetrics().width(QString("%1") | 2194 int tw = paint.fontMetrics().width(QString("%1") |
2098 .arg(m_maxFrequency > 0 ? | 2195 .arg(m_maxFrequency > 0 ? |
2099 m_maxFrequency - 1 : | 2196 m_maxFrequency - 1 : |
2100 m_model->getSampleRate() / 2)); | 2197 m_model->getSampleRate() / 2)); |
2101 | 2198 |
2102 int fw = paint.fontMetrics().width(QString("43Hz")); | 2199 int fw = paint.fontMetrics().width(QString("43Hz")); |
2103 if (tw < fw) tw = fw; | 2200 if (tw < fw) tw = fw; |
2104 | 2201 |
2105 return tw + 13; | 2202 int tickw = (m_frequencyScale == LogFrequencyScale ? 10 : 4); |
2203 | |
2204 return cw + tickw + tw + 13; | |
2106 } | 2205 } |
2107 | 2206 |
2108 void | 2207 void |
2109 SpectrogramLayer::paintVerticalScale(QPainter &paint, QRect rect) const | 2208 SpectrogramLayer::paintVerticalScale(QPainter &paint, QRect rect) const |
2110 { | 2209 { |
2111 if (!m_model || !m_model->isOK()) { | 2210 if (!m_model || !m_model->isOK()) { |
2112 return; | 2211 return; |
2113 } | 2212 } |
2114 | 2213 |
2115 int h = rect.height(), w = rect.width(); | 2214 int h = rect.height(), w = rect.width(); |
2215 | |
2216 int tickw = (m_frequencyScale == LogFrequencyScale ? 10 : 4); | |
2217 int pkw = (m_frequencyScale == LogFrequencyScale ? 10 : 0); | |
2116 | 2218 |
2117 size_t bins = m_windowSize / 2; | 2219 size_t bins = m_windowSize / 2; |
2118 int sr = m_model->getSampleRate(); | 2220 int sr = m_model->getSampleRate(); |
2119 | 2221 |
2120 if (m_maxFrequency > 0) { | 2222 if (m_maxFrequency > 0) { |
2121 bins = int((double(m_maxFrequency) * m_windowSize) / sr + 0.1); | 2223 bins = int((double(m_maxFrequency) * m_windowSize) / sr + 0.1); |
2122 if (bins > m_windowSize / 2) bins = m_windowSize / 2; | 2224 if (bins > m_windowSize / 2) bins = m_windowSize / 2; |
2123 } | 2225 } |
2124 | 2226 |
2227 int cw = getColourScaleWidth(paint); | |
2228 | |
2125 int py = -1; | 2229 int py = -1; |
2126 int textHeight = paint.fontMetrics().height(); | 2230 int textHeight = paint.fontMetrics().height(); |
2127 int toff = -textHeight + paint.fontMetrics().ascent() + 2; | 2231 int toff = -textHeight + paint.fontMetrics().ascent() + 2; |
2232 | |
2233 if (m_cache && !m_cacheInvalid && h > textHeight * 2 + 10) { //!!! lock? | |
2234 | |
2235 int ch = h - textHeight * 2 - 8; | |
2236 paint.drawRect(4, textHeight + 4, cw - 1, ch + 1); | |
2237 | |
2238 QString top, bottom; | |
2239 | |
2240 switch (m_colourScale) { | |
2241 default: | |
2242 case LinearColourScale: | |
2243 top = (m_normalizeColumns ? "1.0" : "0.02"); | |
2244 bottom = (m_normalizeColumns ? "0.0" : "0.00"); | |
2245 break; | |
2246 | |
2247 case MeterColourScale: | |
2248 top = (m_normalizeColumns ? QString("0") : | |
2249 QString("%1").arg(int(AudioLevel::multiplier_to_dB(0.02)))); | |
2250 bottom = QString("%1"). | |
2251 arg(int(AudioLevel::multiplier_to_dB | |
2252 (AudioLevel::preview_to_multiplier(0, 255)))); | |
2253 break; | |
2254 | |
2255 case dBColourScale: | |
2256 top = "0"; | |
2257 bottom = "-80"; | |
2258 break; | |
2259 | |
2260 case PhaseColourScale: | |
2261 top = QChar(0x3c0); | |
2262 bottom = "-" + top; | |
2263 break; | |
2264 } | |
2265 | |
2266 paint.drawText((cw + 6 - paint.fontMetrics().width(top)) / 2, | |
2267 2 + textHeight + toff, top); | |
2268 | |
2269 paint.drawText((cw + 6 - paint.fontMetrics().width(bottom)) / 2, | |
2270 h + toff - 3, bottom); | |
2271 | |
2272 paint.save(); | |
2273 paint.setBrush(Qt::NoBrush); | |
2274 for (int i = 0; i < ch; ++i) { | |
2275 int v = (i * 255) / ch + 1; | |
2276 paint.setPen(m_cache->getColour(v)); | |
2277 paint.drawLine(5, 4 + textHeight + ch - i, | |
2278 cw + 2, 4 + textHeight + ch - i); | |
2279 } | |
2280 paint.restore(); | |
2281 } | |
2282 | |
2283 paint.drawLine(cw + 7, 0, cw + 7, h); | |
2128 | 2284 |
2129 int bin = -1; | 2285 int bin = -1; |
2130 | 2286 |
2131 for (int y = 0; y < m_view->height(); ++y) { | 2287 for (int y = 0; y < m_view->height(); ++y) { |
2132 | 2288 |
2140 bin = int(q0); | 2296 bin = int(q0); |
2141 } else { | 2297 } else { |
2142 continue; | 2298 continue; |
2143 } | 2299 } |
2144 | 2300 |
2145 int freq = (sr * (bin + 1)) / m_windowSize; | 2301 int freq = (sr * bin) / m_windowSize; |
2146 | 2302 |
2147 if (py >= 0 && (vy - py) < textHeight - 1) { | 2303 if (py >= 0 && (vy - py) < textHeight - 1) { |
2148 paint.drawLine(w - 4, h - vy, w, h - vy); | 2304 if (m_frequencyScale == LinearFrequencyScale) { |
2305 paint.drawLine(w - tickw, h - vy, w, h - vy); | |
2306 } | |
2149 continue; | 2307 continue; |
2150 } | 2308 } |
2151 | 2309 |
2152 QString text = QString("%1").arg(freq); | 2310 QString text = QString("%1").arg(freq); |
2153 if (bin == 0) text = QString("%1Hz").arg(freq); | 2311 if (bin == 1) text = QString("%1Hz").arg(freq); // bin 0 is DC |
2154 paint.drawLine(0, h - vy, w, h - vy); | 2312 paint.drawLine(cw + 7, h - vy, w - pkw - 1, h - vy); |
2155 | 2313 |
2156 if (h - vy - textHeight >= -2) { | 2314 if (h - vy - textHeight >= -2) { |
2157 int tx = w - 10 - paint.fontMetrics().width(text); | 2315 int tx = w - 3 - paint.fontMetrics().width(text) - std::max(tickw, pkw); |
2158 paint.drawText(tx, h - vy + toff, text); | 2316 paint.drawText(tx, h - vy + toff, text); |
2159 } | 2317 } |
2160 | 2318 |
2161 py = vy; | 2319 py = vy; |
2320 } | |
2321 | |
2322 if (m_frequencyScale == LogFrequencyScale) { | |
2323 | |
2324 paint.drawLine(w - pkw - 1, 0, w - pkw - 1, h); | |
2325 | |
2326 int sr = m_model->getSampleRate();//!!! lock? | |
2327 float minf = getEffectiveMinFrequency(); | |
2328 float maxf = getEffectiveMaxFrequency(); | |
2329 | |
2330 int py = h; | |
2331 paint.setBrush(paint.pen().color()); | |
2332 | |
2333 for (int i = 0; i < 128; ++i) { | |
2334 | |
2335 float f = Pitch::getFrequencyForPitch(i); | |
2336 int y = lrintf(m_view->getYForFrequency(f, minf, maxf, true)); | |
2337 int n = (i % 12); | |
2338 if (n == 1 || n == 3 || n == 6 || n == 8 || n == 10) { | |
2339 // black notes | |
2340 paint.drawLine(w - pkw, y, w, y); | |
2341 paint.drawRect(w - pkw, y - (py-y)/4, pkw/2, 2*((py-y)/4)); | |
2342 } else if (n == 0 || n == 5) { | |
2343 // C, A | |
2344 if (py < h) { | |
2345 paint.drawLine(w - pkw, (y + py) / 2, w, (y + py) / 2); | |
2346 } | |
2347 } | |
2348 | |
2349 py = y; | |
2350 } | |
2162 } | 2351 } |
2163 } | 2352 } |
2164 | 2353 |
2165 QString | 2354 QString |
2166 SpectrogramLayer::toXmlString(QString indent, QString extraAttributes) const | 2355 SpectrogramLayer::toXmlString(QString indent, QString extraAttributes) const |