Chris@133: /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ Chris@133: Chris@133: /* Chris@133: Sonic Visualiser Chris@133: An audio file viewer and annotation editor. Chris@133: Centre for Digital Music, Queen Mary, University of London. Chris@195: This file copyright 2006-2007 QMUL. Chris@133: Chris@133: This program is free software; you can redistribute it and/or Chris@133: modify it under the terms of the GNU General Public License as Chris@133: published by the Free Software Foundation; either version 2 of the Chris@133: License, or (at your option) any later version. See the file Chris@133: COPYING included with this distribution for more information. Chris@133: */ Chris@133: Chris@133: #include "SpectrumLayer.h" Chris@133: Chris@133: #include "data/model/FFTModel.h" Chris@133: #include "view/View.h" Chris@153: #include "base/AudioLevel.h" Chris@153: #include "base/Preferences.h" Chris@167: #include "base/RangeMapper.h" Chris@277: #include "base/Pitch.h" Chris@1147: #include "base/Strings.h" Chris@1078: Chris@376: #include "ColourMapper.h" Chris@1078: #include "PaintAssistant.h" Chris@1276: #include "PianoScale.h" Chris@1281: #include "HorizontalFrequencyScale.h" Chris@254: Chris@254: #include Chris@316: #include Chris@316: Chris@133: Chris@133: SpectrumLayer::SpectrumLayer() : Chris@193: m_originModel(0), Chris@153: m_channel(-1), Chris@153: m_channelSet(false), Chris@290: m_windowSize(4096), Chris@153: m_windowType(HanningWindow), Chris@290: m_windowHopLevel(3), Chris@284: m_showPeaks(false), Chris@275: m_newFFTNeeded(true) Chris@133: { Chris@153: Preferences *prefs = Preferences::getInstance(); Chris@153: connect(prefs, SIGNAL(propertyChanged(PropertyContainer::PropertyName)), Chris@153: this, SLOT(preferenceChanged(PropertyContainer::PropertyName))); Chris@153: setWindowType(prefs->getWindowType()); Chris@195: Chris@195: setBinScale(LogBins); Chris@133: } Chris@133: Chris@133: SpectrumLayer::~SpectrumLayer() Chris@133: { Chris@349: Model *m = const_cast Chris@349: (static_cast(m_sliceableModel)); Chris@458: if (m) m->aboutToDelete(); Chris@349: m_sliceableModel = 0; Chris@349: delete m; Chris@133: } Chris@133: Chris@133: void Chris@133: SpectrumLayer::setModel(DenseTimeValueModel *model) Chris@133: { Chris@587: SVDEBUG << "SpectrumLayer::setModel(" << model << ") from " << m_originModel << endl; Chris@345: Chris@193: if (m_originModel == model) return; Chris@349: Chris@193: m_originModel = model; Chris@277: Chris@350: if (m_sliceableModel) { Chris@350: Model *m = const_cast Chris@350: (static_cast(m_sliceableModel)); Chris@350: m->aboutToDelete(); Chris@350: setSliceableModel(0); Chris@350: delete m; Chris@350: } Chris@350: Chris@349: m_newFFTNeeded = true; Chris@349: Chris@349: emit layerParametersChanged(); Chris@349: } Chris@349: Chris@349: void Chris@349: SpectrumLayer::setChannel(int channel) Chris@349: { Chris@587: SVDEBUG << "SpectrumLayer::setChannel(" << channel << ") from " << m_channel << endl; Chris@349: Chris@349: m_channelSet = true; Chris@349: Chris@349: if (m_channel == channel) return; Chris@349: Chris@349: m_channel = channel; Chris@349: Chris@349: m_newFFTNeeded = true; Chris@349: Chris@349: emit layerParametersChanged(); Chris@153: } Chris@153: Chris@153: void Chris@193: SpectrumLayer::setupFFT() Chris@153: { Chris@349: if (m_sliceableModel) { Chris@349: Model *m = const_cast Chris@349: (static_cast(m_sliceableModel)); Chris@349: m->aboutToDelete(); Chris@193: setSliceableModel(0); Chris@349: delete m; Chris@349: } Chris@349: Chris@349: if (!m_originModel) { Chris@349: return; Chris@153: } Chris@153: Chris@193: FFTModel *newFFT = new FFTModel(m_originModel, Chris@193: m_channel, Chris@193: m_windowType, Chris@193: m_windowSize, Chris@193: getWindowIncrement(), Chris@975: m_windowSize); Chris@153: Chris@193: setSliceableModel(newFFT); Chris@193: Chris@254: m_biasCurve.clear(); Chris@805: for (int i = 0; i < m_windowSize; ++i) { Chris@254: m_biasCurve.push_back(1.f / (float(m_windowSize)/2.f)); Chris@254: } Chris@254: Chris@349: m_newFFTNeeded = false; Chris@133: } Chris@133: Chris@153: Layer::PropertyList Chris@153: SpectrumLayer::getProperties() const Chris@153: { Chris@193: PropertyList list = SliceLayer::getProperties(); Chris@153: list.push_back("Window Size"); Chris@153: list.push_back("Window Increment"); Chris@284: list.push_back("Show Peak Frequencies"); Chris@153: return list; Chris@153: } Chris@153: Chris@153: QString Chris@153: SpectrumLayer::getPropertyLabel(const PropertyName &name) const Chris@153: { Chris@153: if (name == "Window Size") return tr("Window Size"); Chris@153: if (name == "Window Increment") return tr("Window Overlap"); Chris@284: if (name == "Show Peak Frequencies") return tr("Show Peak Frequencies"); Chris@193: return SliceLayer::getPropertyLabel(name); Chris@153: } Chris@153: Chris@335: QString Chris@335: SpectrumLayer::getPropertyIconName(const PropertyName &name) const Chris@335: { Chris@335: if (name == "Show Peak Frequencies") return "show-peaks"; Chris@335: return SliceLayer::getPropertyIconName(name); Chris@335: } Chris@335: Chris@153: Layer::PropertyType Chris@153: SpectrumLayer::getPropertyType(const PropertyName &name) const Chris@153: { Chris@193: if (name == "Window Size") return ValueProperty; Chris@193: if (name == "Window Increment") return ValueProperty; Chris@284: if (name == "Show Peak Frequencies") return ToggleProperty; Chris@193: return SliceLayer::getPropertyType(name); Chris@153: } Chris@153: Chris@153: QString Chris@153: SpectrumLayer::getPropertyGroupName(const PropertyName &name) const Chris@153: { Chris@153: if (name == "Window Size" || Chris@1266: name == "Window Increment") return tr("Window"); Chris@557: if (name == "Show Peak Frequencies") return tr("Bins"); Chris@193: return SliceLayer::getPropertyGroupName(name); Chris@153: } Chris@153: Chris@153: int Chris@153: SpectrumLayer::getPropertyRangeAndValue(const PropertyName &name, Chris@216: int *min, int *max, int *deflt) const Chris@153: { Chris@216: int val = 0; Chris@153: Chris@216: int garbage0, garbage1, garbage2; Chris@153: if (!min) min = &garbage0; Chris@153: if (!max) max = &garbage1; Chris@216: if (!deflt) deflt = &garbage2; Chris@153: Chris@193: if (name == "Window Size") { Chris@153: Chris@1266: *min = 0; Chris@1266: *max = 15; Chris@216: *deflt = 5; Chris@1266: Chris@1266: val = 0; Chris@1266: int ws = m_windowSize; Chris@1266: while (ws > 32) { ws >>= 1; val ++; } Chris@153: Chris@153: } else if (name == "Window Increment") { Chris@1266: Chris@1266: *min = 0; Chris@1266: *max = 5; Chris@216: *deflt = 2; Chris@1266: Chris@216: val = m_windowHopLevel; Chris@153: Chris@284: } else if (name == "Show Peak Frequencies") { Chris@284: Chris@284: return m_showPeaks ? 1 : 0; Chris@284: Chris@153: } else { Chris@193: Chris@216: val = SliceLayer::getPropertyRangeAndValue(name, min, max, deflt); Chris@153: } Chris@153: Chris@216: return val; Chris@153: } Chris@153: Chris@153: QString Chris@153: SpectrumLayer::getPropertyValueLabel(const PropertyName &name, Chris@1266: int value) const Chris@153: { Chris@153: if (name == "Window Size") { Chris@1266: return QString("%1").arg(32 << value); Chris@153: } Chris@153: if (name == "Window Increment") { Chris@1266: switch (value) { Chris@1266: default: Chris@1266: case 0: return tr("None"); Chris@1266: case 1: return tr("25 %"); Chris@1266: case 2: return tr("50 %"); Chris@1266: case 3: return tr("75 %"); Chris@1266: case 4: return tr("87.5 %"); Chris@1266: case 5: return tr("93.75 %"); Chris@1266: } Chris@153: } Chris@193: return SliceLayer::getPropertyValueLabel(name, value); Chris@153: } Chris@153: Chris@167: RangeMapper * Chris@167: SpectrumLayer::getNewPropertyRangeMapper(const PropertyName &name) const Chris@167: { Chris@193: return SliceLayer::getNewPropertyRangeMapper(name); Chris@167: } Chris@167: Chris@133: void Chris@153: SpectrumLayer::setProperty(const PropertyName &name, int value) Chris@133: { Chris@193: if (name == "Window Size") { Chris@1266: setWindowSize(32 << value); Chris@153: } else if (name == "Window Increment") { Chris@153: setWindowHopLevel(value); Chris@284: } else if (name == "Show Peak Frequencies") { Chris@284: setShowPeaks(value ? true : false); Chris@193: } else { Chris@193: SliceLayer::setProperty(name, value); Chris@153: } Chris@153: } Chris@153: Chris@153: void Chris@805: SpectrumLayer::setWindowSize(int ws) Chris@153: { Chris@153: if (m_windowSize == ws) return; Chris@153: m_windowSize = ws; Chris@275: m_newFFTNeeded = true; Chris@153: emit layerParametersChanged(); Chris@153: } Chris@153: Chris@153: void Chris@805: SpectrumLayer::setWindowHopLevel(int v) Chris@153: { Chris@153: if (m_windowHopLevel == v) return; Chris@153: m_windowHopLevel = v; Chris@275: m_newFFTNeeded = true; Chris@153: emit layerParametersChanged(); Chris@153: } Chris@153: Chris@153: void Chris@153: SpectrumLayer::setWindowType(WindowType w) Chris@153: { Chris@153: if (m_windowType == w) return; Chris@153: m_windowType = w; Chris@275: m_newFFTNeeded = true; Chris@153: emit layerParametersChanged(); Chris@153: } Chris@153: Chris@153: void Chris@284: SpectrumLayer::setShowPeaks(bool show) Chris@284: { Chris@284: if (m_showPeaks == show) return; Chris@284: m_showPeaks = show; Chris@284: emit layerParametersChanged(); Chris@284: } Chris@284: Chris@284: void Chris@153: SpectrumLayer::preferenceChanged(PropertyContainer::PropertyName name) Chris@153: { Chris@153: if (name == "Window Type") { Chris@153: setWindowType(Preferences::getInstance()->getWindowType()); Chris@153: return; Chris@153: } Chris@153: } Chris@153: Chris@1238: double Chris@1238: SpectrumLayer::getFrequencyForX(const LayerGeometryProvider *v, double x) const Chris@133: { Chris@1238: if (!m_sliceableModel) return 0; Chris@1238: double bin = getBinForX(v, x); Chris@1281: // we assume the frequency of a bin corresponds to the centre of Chris@1281: // its visual range Chris@1281: bin -= 0.5; Chris@1238: return (m_sliceableModel->getSampleRate() * bin) / Chris@1238: (m_sliceableModel->getHeight() * 2); Chris@133: } Chris@133: Chris@908: double Chris@1238: SpectrumLayer::getXForFrequency(const LayerGeometryProvider *v, double freq) const Chris@265: { Chris@280: if (!m_sliceableModel) return 0; Chris@1238: double bin = (freq * m_sliceableModel->getHeight() * 2) / Chris@1238: m_sliceableModel->getSampleRate(); Chris@1281: // we want the centre of the bin range Chris@1281: bin += 0.5; Chris@1238: return getXForBin(v, bin); Chris@254: } Chris@254: Chris@260: bool Chris@918: SpectrumLayer::getXScaleValue(const LayerGeometryProvider *v, int x, Chris@908: double &value, QString &unit) const Chris@260: { Chris@1238: value = getFrequencyForX(v, x); Chris@260: unit = "Hz"; Chris@260: return true; Chris@260: } Chris@260: Chris@264: bool Chris@918: SpectrumLayer::getYScaleValue(const LayerGeometryProvider *v, int y, Chris@908: double &value, QString &unit) const Chris@274: { Chris@1238: value = getValueForY(v, y); Chris@274: Chris@274: if (m_energyScale == dBScale || m_energyScale == MeterScale) { Chris@274: Chris@908: if (value > 0.0) { Chris@908: value = 10.0 * log10(value); Chris@284: if (value < m_threshold) value = m_threshold; Chris@284: } else value = m_threshold; Chris@274: Chris@274: unit = "dBV"; Chris@274: Chris@274: } else { Chris@274: unit = "V"; Chris@274: } Chris@274: Chris@274: return true; Chris@274: } Chris@274: Chris@274: bool Chris@918: SpectrumLayer::getYScaleDifference(const LayerGeometryProvider *v, int y0, int y1, Chris@908: double &diff, QString &unit) const Chris@274: { Chris@274: bool rv = SliceLayer::getYScaleDifference(v, y0, y1, diff, unit); Chris@274: if (rv && (unit == "dBV")) unit = "dB"; Chris@274: return rv; Chris@274: } Chris@274: Chris@274: Chris@274: bool Chris@918: SpectrumLayer::getCrosshairExtents(LayerGeometryProvider *v, QPainter &paint, Chris@264: QPoint cursorPos, Chris@264: std::vector &extents) const Chris@264: { Chris@918: QRect vertical(cursorPos.x(), cursorPos.y(), 1, v->getPaintHeight() - cursorPos.y()); Chris@264: extents.push_back(vertical); Chris@264: Chris@918: QRect horizontal(0, cursorPos.y(), v->getPaintWidth(), 12); Chris@264: extents.push_back(horizontal); Chris@264: Chris@280: int hoffset = 2; Chris@280: if (m_binScale == LogBins) hoffset = 13; Chris@278: Chris@607: int sw = getVerticalScaleWidth(v, false, paint); Chris@280: Chris@280: QRect value(sw, cursorPos.y() - paint.fontMetrics().ascent() - 2, Chris@280: paint.fontMetrics().width("0.0000001 V") + 2, Chris@264: paint.fontMetrics().height()); Chris@280: extents.push_back(value); Chris@280: Chris@280: QRect log(sw, cursorPos.y() + 2, Chris@280: paint.fontMetrics().width("-80.000 dBV") + 2, Chris@280: paint.fontMetrics().height()); Chris@280: extents.push_back(log); Chris@280: Chris@280: QRect freq(cursorPos.x(), Chris@918: v->getPaintHeight() - paint.fontMetrics().height() - hoffset, Chris@280: paint.fontMetrics().width("123456 Hz") + 2, Chris@280: paint.fontMetrics().height()); Chris@280: extents.push_back(freq); Chris@264: Chris@278: int w(paint.fontMetrics().width("C#10+50c") + 2); Chris@278: QRect pitch(cursorPos.x() - w, Chris@918: v->getPaintHeight() - paint.fontMetrics().height() - hoffset, Chris@278: w, Chris@278: paint.fontMetrics().height()); Chris@278: extents.push_back(pitch); Chris@278: Chris@264: return true; Chris@264: } Chris@264: Chris@254: void Chris@918: SpectrumLayer::paintCrosshairs(LayerGeometryProvider *v, QPainter &paint, Chris@254: QPoint cursorPos) const Chris@254: { Chris@280: if (!m_sliceableModel) return; Chris@280: Chris@254: paint.save(); Chris@282: QFont fn = paint.font(); Chris@282: if (fn.pointSize() > 8) { Chris@282: fn.setPointSize(fn.pointSize() - 1); Chris@282: paint.setFont(fn); Chris@282: } Chris@254: Chris@254: ColourMapper mapper(m_colourMap, 0, 1); Chris@254: paint.setPen(mapper.getContrastingColour()); Chris@254: Chris@1238: int xorigin = m_xorigins[v->getId()]; Chris@918: paint.drawLine(xorigin, cursorPos.y(), v->getPaintWidth(), cursorPos.y()); Chris@918: paint.drawLine(cursorPos.x(), cursorPos.y(), cursorPos.x(), v->getPaintHeight()); Chris@254: Chris@1238: double fundamental = getFrequencyForX(v, cursorPos.x()); Chris@254: Chris@280: int hoffset = 2; Chris@280: if (m_binScale == LogBins) hoffset = 13; Chris@278: Chris@1078: PaintAssistant::drawVisibleText(v, paint, Chris@1238: cursorPos.x() + 2, Chris@1238: v->getPaintHeight() - 2 - hoffset, Chris@1238: QString("%1 Hz").arg(fundamental), Chris@1238: PaintAssistant::OutlinedText); Chris@278: Chris@278: if (Pitch::isFrequencyInMidiRange(fundamental)) { Chris@278: QString pitchLabel = Pitch::getPitchLabelForFrequency(fundamental); Chris@1078: PaintAssistant::drawVisibleText(v, paint, Chris@1238: cursorPos.x() - Chris@1238: paint.fontMetrics().width(pitchLabel) - 2, Chris@1238: v->getPaintHeight() - 2 - hoffset, Chris@1238: pitchLabel, Chris@1238: PaintAssistant::OutlinedText); Chris@278: } Chris@264: Chris@1238: double value = getValueForY(v, cursorPos.y()); Chris@908: double thresh = m_threshold; Chris@908: double db = thresh; Chris@908: if (value > 0.0) db = 10.0 * log10(value); Chris@280: if (db < thresh) db = thresh; Chris@280: Chris@1078: PaintAssistant::drawVisibleText(v, paint, Chris@280: xorigin + 2, Chris@280: cursorPos.y() - 2, Chris@280: QString("%1 V").arg(value), Chris@1078: PaintAssistant::OutlinedText); Chris@280: Chris@1078: PaintAssistant::drawVisibleText(v, paint, Chris@280: xorigin + 2, Chris@280: cursorPos.y() + 2 + paint.fontMetrics().ascent(), Chris@280: QString("%1 dBV").arg(db), Chris@1078: PaintAssistant::OutlinedText); Chris@280: Chris@254: int harmonic = 2; Chris@254: Chris@254: while (harmonic < 100) { Chris@254: Chris@1238: int hx = int(lrint(getXForFrequency(v, fundamental * harmonic))); Chris@254: Chris@918: if (hx < xorigin || hx > v->getPaintWidth()) break; Chris@254: Chris@254: int len = 7; Chris@254: Chris@254: if (harmonic % 2 == 0) { Chris@254: if (harmonic % 4 == 0) { Chris@254: len = 12; Chris@254: } else { Chris@254: len = 10; Chris@254: } Chris@254: } Chris@254: Chris@908: paint.drawLine(hx, Chris@254: cursorPos.y(), Chris@908: hx, Chris@254: cursorPos.y() + len); Chris@254: Chris@254: ++harmonic; Chris@254: } Chris@254: Chris@254: paint.restore(); Chris@254: } Chris@254: Chris@199: QString Chris@918: SpectrumLayer::getFeatureDescription(LayerGeometryProvider *v, QPoint &p) const Chris@199: { Chris@199: if (!m_sliceableModel) return ""; Chris@199: Chris@199: int minbin = 0, maxbin = 0, range = 0; Chris@805: QString genericDesc = SliceLayer::getFeatureDescriptionAux Chris@199: (v, p, false, minbin, maxbin, range); Chris@199: Chris@199: if (genericDesc == "") return ""; Chris@199: Chris@1238: int i0 = minbin - m_minbin; Chris@1238: int i1 = maxbin - m_minbin; Chris@1238: Chris@1238: float minvalue = 0.0; Chris@1238: if (in_range_for(m_values, i0)) minvalue = m_values[i0]; Chris@199: Chris@1238: float maxvalue = minvalue; Chris@1238: if (in_range_for(m_values, i1)) maxvalue = m_values[i1]; Chris@1238: Chris@199: if (minvalue > maxvalue) std::swap(minvalue, maxvalue); Chris@199: Chris@199: QString binstr; Chris@199: QString hzstr; Chris@908: int minfreq = int(lrint((minbin * m_sliceableModel->getSampleRate()) / Chris@908: m_windowSize)); Chris@1256: int maxfreq = int(lrint((std::max(maxbin, minbin) Chris@908: * m_sliceableModel->getSampleRate()) / Chris@908: m_windowSize)); Chris@199: Chris@199: if (maxbin != minbin) { Chris@199: binstr = tr("%1 - %2").arg(minbin+1).arg(maxbin+1); Chris@199: } else { Chris@199: binstr = QString("%1").arg(minbin+1); Chris@199: } Chris@199: if (minfreq != maxfreq) { Chris@199: hzstr = tr("%1 - %2 Hz").arg(minfreq).arg(maxfreq); Chris@199: } else { Chris@199: hzstr = tr("%1 Hz").arg(minfreq); Chris@199: } Chris@199: Chris@199: QString valuestr; Chris@199: if (maxvalue != minvalue) { Chris@199: valuestr = tr("%1 - %2").arg(minvalue).arg(maxvalue); Chris@199: } else { Chris@199: valuestr = QString("%1").arg(minvalue); Chris@199: } Chris@199: Chris@199: QString dbstr; Chris@908: double mindb = AudioLevel::multiplier_to_dB(minvalue); Chris@908: double maxdb = AudioLevel::multiplier_to_dB(maxvalue); Chris@199: QString mindbstr; Chris@199: QString maxdbstr; Chris@199: if (mindb == AudioLevel::DB_FLOOR) { Chris@1147: mindbstr = Strings::minus_infinity; Chris@199: } else { Chris@908: mindbstr = QString("%1").arg(lrint(mindb)); Chris@199: } Chris@199: if (maxdb == AudioLevel::DB_FLOOR) { Chris@1147: maxdbstr = Strings::minus_infinity; Chris@199: } else { Chris@908: maxdbstr = QString("%1").arg(lrint(maxdb)); Chris@199: } Chris@908: if (lrint(mindb) != lrint(maxdb)) { Chris@199: dbstr = tr("%1 - %2").arg(mindbstr).arg(maxdbstr); Chris@199: } else { Chris@199: dbstr = tr("%1").arg(mindbstr); Chris@199: } Chris@199: Chris@199: QString description; Chris@199: Chris@248: if (range > int(m_sliceableModel->getResolution())) { Chris@199: description = tr("%1\nBin:\t%2 (%3)\n%4 value:\t%5\ndB:\t%6") Chris@199: .arg(genericDesc) Chris@199: .arg(binstr) Chris@199: .arg(hzstr) Chris@199: .arg(m_samplingMode == NearestSample ? tr("First") : Chris@199: m_samplingMode == SampleMean ? tr("Mean") : tr("Peak")) Chris@199: .arg(valuestr) Chris@199: .arg(dbstr); Chris@199: } else { Chris@199: description = tr("%1\nBin:\t%2 (%3)\nValue:\t%4\ndB:\t%5") Chris@199: .arg(genericDesc) Chris@199: .arg(binstr) Chris@199: .arg(hzstr) Chris@199: .arg(valuestr) Chris@199: .arg(dbstr); Chris@199: } Chris@199: Chris@199: return description; Chris@199: } Chris@199: Chris@254: void Chris@916: SpectrumLayer::paint(LayerGeometryProvider *v, QPainter &paint, QRect rect) const Chris@275: { Chris@275: if (!m_originModel || !m_originModel->isOK() || Chris@349: !m_originModel->isReady()) { Chris@587: SVDEBUG << "SpectrumLayer::paint: no origin model, or origin model not OK or not ready" << endl; Chris@349: return; Chris@349: } Chris@275: Chris@275: if (m_newFFTNeeded) { Chris@587: SVDEBUG << "SpectrumLayer::paint: new FFT needed, calling setupFFT" << endl; Chris@275: const_cast(this)->setupFFT(); //ugh Chris@275: } Chris@277: Chris@277: FFTModel *fft = dynamic_cast Chris@277: (const_cast(m_sliceableModel)); Chris@277: Chris@908: double thresh = (pow(10, -6) / m_gain) * (m_windowSize / 2.0); // -60dB adj Chris@277: Chris@607: int xorigin = getVerticalScaleWidth(v, false, paint) + 1; Chris@1281: int scaleHeight = getHorizontalScaleHeight(v, paint); Chris@345: Chris@284: if (fft && m_showPeaks) { Chris@277: Chris@277: // draw peak lines Chris@277: Chris@908: int col = int(v->getCentreFrame() / fft->getResolution()); Chris@277: Chris@277: paint.save(); Chris@277: paint.setRenderHint(QPainter::Antialiasing, false); Chris@277: Chris@1281: ColourMapper mapper = Chris@1281: hasLightBackground() ? Chris@1281: ColourMapper(ColourMapper::BlackOnWhite, 0, 1) : Chris@1281: ColourMapper(ColourMapper::WhiteOnBlack, 0, 1); Chris@1281: Chris@290: int peakminbin = 0; Chris@290: int peakmaxbin = fft->getHeight() - 1; Chris@908: double peakmaxfreq = Pitch::getFrequencyForPitch(128); Chris@908: peakmaxbin = int(((peakmaxfreq * fft->getHeight() * 2) / fft->getSampleRate())); Chris@290: Chris@280: FFTModel::PeakSet peaks = fft->getPeakFrequencies Chris@290: (FFTModel::MajorPitchAdaptivePeaks, col, peakminbin, peakmaxbin); Chris@280: Chris@277: BiasCurve curve; Chris@277: getBiasCurve(curve); Chris@908: int cs = int(curve.size()); Chris@280: Chris@908: std::vector values; Chris@277: Chris@805: for (int bin = 0; bin < fft->getHeight(); ++bin) { Chris@908: double value = m_sliceableModel->getValueAt(col, bin); Chris@280: if (bin < cs) value *= curve[bin]; Chris@280: values.push_back(value); Chris@280: } Chris@280: Chris@280: for (FFTModel::PeakSet::iterator i = peaks.begin(); Chris@280: i != peaks.end(); ++i) { Chris@280: Chris@805: int bin = i->first; Chris@277: Chris@682: // cerr << "bin = " << bin << ", thresh = " << thresh << ", value = " << fft->getMagnitudeAt(col, bin) << endl; Chris@280: Chris@908: if (!fft->isOverThreshold(col, bin, float(thresh))) continue; Chris@277: Chris@908: double freq = i->second; Chris@280: Chris@1238: int x = int(lrint(getXForFrequency(v, freq))); Chris@277: Chris@908: double norm = 0.f; Chris@1238: (void)getYForValue(v, values[bin], norm); // don't need return value, need norm Chris@277: Chris@277: paint.setPen(mapper.map(norm)); Chris@1281: paint.drawLine(x, 0, x, v->getPaintHeight() - scaleHeight - 1); Chris@277: } Chris@277: Chris@277: paint.restore(); Chris@277: } Chris@275: Chris@1281: paint.save(); Chris@1281: Chris@275: SliceLayer::paint(v, paint, rect); Chris@1281: Chris@1281: paintHorizontalScale(v, paint, xorigin); Chris@277: Chris@1281: paint.restore(); Chris@1281: } Chris@1281: Chris@1281: int Chris@1281: SpectrumLayer::getHorizontalScaleHeight(LayerGeometryProvider *v, Chris@1281: QPainter &paint) const Chris@1281: { Chris@1281: int pkh = int(paint.fontMetrics().height() * 0.7 + 0.5); Chris@1281: if (pkh < 10) pkh = 10; Chris@1281: Chris@1281: int scaleh = HorizontalFrequencyScale().getHeight(v, paint); Chris@1281: Chris@1281: return pkh + scaleh; Chris@1281: } Chris@1281: Chris@1281: void Chris@1281: SpectrumLayer::paintHorizontalScale(LayerGeometryProvider *v, Chris@1281: QPainter &paint, Chris@1281: int xorigin) const Chris@1281: { Chris@278: //!!! All of this stuff relating to depicting frequencies Chris@1238: // (keyboard, crosshairs etc) should be applicable to any slice Chris@1238: // layer whose model has a vertical scale unit of Hz. However, Chris@1238: // the dense 3d model at the moment doesn't record its vertical Chris@1238: // scale unit -- we need to fix that and hoist this code as Chris@1238: // appropriate. Same really goes for any code in SpectrogramLayer Chris@1238: // that could be relevant to Colour3DPlotLayer with unit Hz, but Chris@1238: // that's a bigger proposition. Chris@278: Chris@1281: if (!v->getViewManager()->shouldShowHorizontalValueScale()) { Chris@1281: return; Chris@1281: } Chris@1281: Chris@1281: int totalScaleHeight = getHorizontalScaleHeight(v, paint); // inc piano Chris@1281: int freqScaleHeight = HorizontalFrequencyScale().getHeight(v, paint); Chris@1281: int paintHeight = v->getPaintHeight(); Chris@1281: int paintWidth = v->getPaintWidth(); Chris@277: Chris@1238: PianoScale().paintPianoHorizontal Chris@1276: (v, this, paint, Chris@1281: QRect(xorigin, paintHeight - totalScaleHeight - 1, Chris@1281: paintWidth - 1, totalScaleHeight - freqScaleHeight)); Chris@345: Chris@1281: int scaleLeft = int(getXForBin(v, 1)); Chris@1281: Chris@1281: paint.drawLine(int(getXForBin(v, 0)), paintHeight - freqScaleHeight, Chris@1281: scaleLeft, paintHeight - freqScaleHeight); Chris@1281: Chris@1281: QString hz = tr("Hz"); Chris@1281: int hzw = paint.fontMetrics().width(hz); Chris@1281: if (scaleLeft > hzw + 5) { Chris@1281: paint.drawText Chris@1281: (scaleLeft - hzw - 5, Chris@1281: paintHeight - freqScaleHeight + paint.fontMetrics().ascent() + 5, Chris@1281: hz); Chris@1281: } Chris@1281: Chris@1281: HorizontalFrequencyScale().paintScale Chris@1276: (v, this, paint, Chris@1281: QRect(scaleLeft, paintHeight - freqScaleHeight, Chris@1281: paintWidth, totalScaleHeight), Chris@1281: m_binScale == LogBins); Chris@275: } Chris@275: Chris@275: void Chris@254: SpectrumLayer::getBiasCurve(BiasCurve &curve) const Chris@254: { Chris@254: curve = m_biasCurve; Chris@254: } Chris@199: Chris@316: void Chris@316: SpectrumLayer::toXml(QTextStream &stream, Chris@316: QString indent, QString extraAttributes) const Chris@220: { Chris@316: QString s = QString("windowSize=\"%1\" " Chris@456: "windowHopLevel=\"%2\" " Chris@456: "showPeaks=\"%3\" ") Chris@220: .arg(m_windowSize) Chris@456: .arg(m_windowHopLevel) Chris@456: .arg(m_showPeaks ? "true" : "false"); Chris@220: Chris@316: SliceLayer::toXml(stream, indent, extraAttributes + " " + s); Chris@220: } Chris@220: Chris@220: void Chris@220: SpectrumLayer::setProperties(const QXmlAttributes &attributes) Chris@220: { Chris@220: SliceLayer::setProperties(attributes); Chris@220: Chris@220: bool ok = false; Chris@220: Chris@805: int windowSize = attributes.value("windowSize").toUInt(&ok); Chris@220: if (ok) setWindowSize(windowSize); Chris@220: Chris@805: int windowHopLevel = attributes.value("windowHopLevel").toUInt(&ok); Chris@220: if (ok) setWindowHopLevel(windowHopLevel); Chris@456: Chris@456: bool showPeaks = (attributes.value("showPeaks").trimmed() == "true"); Chris@456: setShowPeaks(showPeaks); Chris@220: } Chris@220: Chris@220: