Mercurial > hg > svgui
view layer/SpectrumLayer.cpp @ 1551:e79731086b0f
Fixes to NoteLayer, particularly to calculation of vertical scale when model unit is not Hz. To avoid inconsistency we now behave as if the unit is always Hz from the point of view of the external API and display, converting at the point where we obtain values from the events themselves. Also various fixes to editing.
author | Chris Cannam |
---|---|
date | Thu, 21 Nov 2019 14:02:57 +0000 |
parents | e540aa5d89cd |
children |
line wrap: on
line source
/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ /* Sonic Visualiser An audio file viewer and annotation editor. Centre for Digital Music, Queen Mary, University of London. This file copyright 2006-2007 QMUL. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. See the file COPYING included with this distribution for more information. */ #include "SpectrumLayer.h" #include "data/model/FFTModel.h" #include "view/View.h" #include "base/AudioLevel.h" #include "base/Preferences.h" #include "base/RangeMapper.h" #include "base/Pitch.h" #include "base/Strings.h" #include "ColourMapper.h" #include "PaintAssistant.h" #include "PianoScale.h" #include "HorizontalFrequencyScale.h" #include <QPainter> #include <QTextStream> SpectrumLayer::SpectrumLayer() : m_channel(-1), m_channelSet(false), m_windowSize(4096), m_windowType(HanningWindow), m_windowHopLevel(3), m_oversampling(1), m_showPeaks(false), m_newFFTNeeded(true), m_freqOfMinBin(0.0) { m_binAlignment = BinsCentredOnScalePoints; Preferences *prefs = Preferences::getInstance(); connect(prefs, SIGNAL(propertyChanged(PropertyContainer::PropertyName)), this, SLOT(preferenceChanged(PropertyContainer::PropertyName))); setWindowType(prefs->getWindowType()); setBinScale(LogBins); } SpectrumLayer::~SpectrumLayer() { ModelById::release(m_sliceableModel); } void SpectrumLayer::setModel(ModelId modelId) { auto newModel = ModelById::getAs<DenseTimeValueModel>(modelId); if (!modelId.isNone() && !newModel) { throw std::logic_error("Not a DenseTimeValueModel"); } if (m_originModel == modelId) return; m_originModel = modelId; m_newFFTNeeded = true; emit layerParametersChanged(); } void SpectrumLayer::setChannel(int channel) { SVDEBUG << "SpectrumLayer::setChannel(" << channel << ") from " << m_channel << endl; m_channelSet = true; if (m_channel == channel) return; m_channel = channel; m_newFFTNeeded = true; emit layerParametersChanged(); } void SpectrumLayer::setupFFT() { ModelById::release(m_sliceableModel); m_sliceableModel = {}; if (m_originModel.isNone()) { return; } int fftSize = getFFTSize(); auto newFFT = std::make_shared<FFTModel>(m_originModel, m_channel, m_windowType, m_windowSize, getWindowIncrement(), fftSize); if (m_minbin == 0 && m_maxbin == 0) { m_minbin = 1; m_freqOfMinBin = double(m_minbin * newFFT->getSampleRate()) / getFFTSize(); m_maxbin = newFFT->getHeight(); } setSliceableModel(ModelById::add(newFFT)); m_biasCurve.clear(); for (int i = 0; i < fftSize; ++i) { // Scale by the window size, not the FFT size, because we // don't want to scale down by all the zero bins m_biasCurve.push_back(1.f / (float(m_windowSize)/2.f)); } m_newFFTNeeded = false; } Layer::PropertyList SpectrumLayer::getProperties() const { PropertyList list = SliceLayer::getProperties(); list.push_back("Window Size"); list.push_back("Window Increment"); list.push_back("Oversampling"); list.push_back("Show Peak Frequencies"); return list; } QString SpectrumLayer::getPropertyLabel(const PropertyName &name) const { if (name == "Window Size") return tr("Window Size"); if (name == "Window Increment") return tr("Window Overlap"); if (name == "Oversampling") return tr("Oversampling"); if (name == "Show Peak Frequencies") return tr("Show Peak Frequencies"); return SliceLayer::getPropertyLabel(name); } QString SpectrumLayer::getPropertyIconName(const PropertyName &name) const { if (name == "Show Peak Frequencies") return "show-peaks"; return SliceLayer::getPropertyIconName(name); } Layer::PropertyType SpectrumLayer::getPropertyType(const PropertyName &name) const { if (name == "Window Size") return ValueProperty; if (name == "Window Increment") return ValueProperty; if (name == "Oversampling") return ValueProperty; if (name == "Show Peak Frequencies") return ToggleProperty; return SliceLayer::getPropertyType(name); } QString SpectrumLayer::getPropertyGroupName(const PropertyName &name) const { if (name == "Window Size" || name == "Window Increment" || name == "Oversampling") return tr("Window"); if (name == "Show Peak Frequencies") return tr("Bins"); return SliceLayer::getPropertyGroupName(name); } int SpectrumLayer::getPropertyRangeAndValue(const PropertyName &name, int *min, int *max, int *deflt) const { int val = 0; int garbage0, garbage1, garbage2; if (!min) min = &garbage0; if (!max) max = &garbage1; if (!deflt) deflt = &garbage2; if (name == "Window Size") { *min = 0; *max = 15; *deflt = 5; val = 0; int ws = m_windowSize; while (ws > 32) { ws >>= 1; val ++; } } else if (name == "Window Increment") { *min = 0; *max = 5; *deflt = 2; val = m_windowHopLevel; } else if (name == "Oversampling") { *min = 0; *max = 3; *deflt = 0; val = 0; int ov = m_oversampling; while (ov > 1) { ov >>= 1; val ++; } } else if (name == "Show Peak Frequencies") { return m_showPeaks ? 1 : 0; } else { val = SliceLayer::getPropertyRangeAndValue(name, min, max, deflt); } return val; } QString SpectrumLayer::getPropertyValueLabel(const PropertyName &name, int value) const { if (name == "Window Size") { return QString("%1").arg(32 << value); } if (name == "Window Increment") { switch (value) { default: case 0: return tr("None"); case 1: return tr("25 %"); case 2: return tr("50 %"); case 3: return tr("75 %"); case 4: return tr("87.5 %"); case 5: return tr("93.75 %"); } } if (name == "Oversampling") { switch (value) { default: case 0: return tr("1x"); case 1: return tr("2x"); case 2: return tr("4x"); case 3: return tr("8x"); } } return SliceLayer::getPropertyValueLabel(name, value); } RangeMapper * SpectrumLayer::getNewPropertyRangeMapper(const PropertyName &name) const { return SliceLayer::getNewPropertyRangeMapper(name); } void SpectrumLayer::setProperty(const PropertyName &name, int value) { if (name == "Window Size") { setWindowSize(32 << value); } else if (name == "Window Increment") { setWindowHopLevel(value); } else if (name == "Oversampling") { setOversampling(1 << value); } else if (name == "Show Peak Frequencies") { setShowPeaks(value ? true : false); } else { SliceLayer::setProperty(name, value); } } void SpectrumLayer::setWindowSize(int ws) { if (m_windowSize == ws) return; SVDEBUG << "setWindowSize: from " << m_windowSize << " to " << ws << ": updating min and max bins from " << m_minbin << " and " << m_maxbin << " to "; int previousWs = m_windowSize; m_windowSize = ws; m_minbin = int(round(getBinForFrequency(m_freqOfMinBin))); m_maxbin = int(round((double(m_maxbin) / previousWs) * m_windowSize)); int h = getFFTSize() / 2 + 1; if (m_minbin > h) m_minbin = h; if (m_maxbin > h) m_maxbin = h; SVDEBUG << m_minbin << " and " << m_maxbin << endl; m_newFFTNeeded = true; emit layerParametersChanged(); } void SpectrumLayer::setWindowHopLevel(int v) { if (m_windowHopLevel == v) return; m_windowHopLevel = v; m_newFFTNeeded = true; emit layerParametersChanged(); } void SpectrumLayer::setWindowType(WindowType w) { if (m_windowType == w) return; m_windowType = w; m_newFFTNeeded = true; emit layerParametersChanged(); } void SpectrumLayer::setOversampling(int oversampling) { if (m_oversampling == oversampling) return; SVDEBUG << "setOversampling: from " << m_oversampling << " to " << oversampling << ": updating min and max bins from " << m_minbin << " and " << m_maxbin << " to "; int previousOversampling = m_oversampling; m_oversampling = oversampling; m_minbin = int(round(getBinForFrequency(m_freqOfMinBin))); m_maxbin = int(round((double(m_maxbin) / previousOversampling) * m_oversampling)); int h = getFFTSize() / 2 + 1; if (m_minbin > h) m_minbin = h; if (m_maxbin > h) m_maxbin = h; SVDEBUG << m_minbin << " and " << m_maxbin << endl; m_newFFTNeeded = true; emit layerParametersChanged(); } int SpectrumLayer::getOversampling() const { return m_oversampling; } void SpectrumLayer::setShowPeaks(bool show) { if (m_showPeaks == show) return; m_showPeaks = show; emit layerParametersChanged(); } void SpectrumLayer::preferenceChanged(PropertyContainer::PropertyName name) { if (name == "Window Type") { auto type = Preferences::getInstance()->getWindowType(); SVDEBUG << "SpectrumLayer::preferenceChanged: Window type changed to " << type << endl; setWindowType(type); return; } } bool SpectrumLayer::setDisplayExtents(double min, double max) { bool result = SliceLayer::setDisplayExtents(min, max); if (result) { m_freqOfMinBin = getFrequencyForBin(m_minbin); } return result; } double SpectrumLayer::getBinForFrequency(double freq) const { auto sliceableModel = ModelById::getAs<DenseThreeDimensionalModel> (m_sliceableModel); if (!sliceableModel) return 0; double bin = (freq * getFFTSize()) / sliceableModel->getSampleRate(); return bin; } double SpectrumLayer::getBinForX(const LayerGeometryProvider *v, double x) const { auto sliceableModel = ModelById::getAs<DenseThreeDimensionalModel> (m_sliceableModel); if (!sliceableModel) return 0; double bin = getBinForFrequency(getFrequencyForX(v, x)); return bin; } double SpectrumLayer::getFrequencyForX(const LayerGeometryProvider *v, double x) const { auto sliceableModel = ModelById::getAs<DenseThreeDimensionalModel> (m_sliceableModel); if (!sliceableModel) return 0; double fmin = getFrequencyForBin(m_minbin); double fmax = getFrequencyForBin(m_maxbin); double freq = getScalePointForX(v, x, fmin, fmax); return freq; } double SpectrumLayer::getFrequencyForBin(double bin) const { auto sliceableModel = ModelById::getAs<DenseThreeDimensionalModel> (m_sliceableModel); if (!sliceableModel) return 0; double freq = (bin * sliceableModel->getSampleRate()) / getFFTSize(); return freq; } double SpectrumLayer::getXForBin(const LayerGeometryProvider *v, double bin) const { auto sliceableModel = ModelById::getAs<DenseThreeDimensionalModel> (m_sliceableModel); if (!sliceableModel) return 0; double x = getXForFrequency(v, getFrequencyForBin(bin)); return x; } double SpectrumLayer::getXForFrequency(const LayerGeometryProvider *v, double freq) const { auto sliceableModel = ModelById::getAs<DenseThreeDimensionalModel> (m_sliceableModel); if (!sliceableModel) return 0; double fmin = getFrequencyForBin(m_minbin); double fmax = getFrequencyForBin(m_maxbin); double x = getXForScalePoint(v, freq, fmin, fmax); return x; } bool SpectrumLayer::getXScaleValue(const LayerGeometryProvider *v, int x, double &value, QString &unit) const { value = getFrequencyForX(v, x); unit = "Hz"; return true; } bool SpectrumLayer::getYScaleValue(const LayerGeometryProvider *v, int y, double &value, QString &unit) const { value = getValueForY(v, y); if (m_energyScale == dBScale || m_energyScale == MeterScale) { if (value > 0.0) { value = 10.0 * log10(value); if (value < m_threshold) { value = m_threshold; } } else { value = m_threshold; } unit = "dBV"; } else { unit = "V"; } return true; } bool SpectrumLayer::getYScaleDifference(const LayerGeometryProvider *v, int y0, int y1, double &diff, QString &unit) const { bool rv = SliceLayer::getYScaleDifference(v, y0, y1, diff, unit); if (rv && (unit == "dBV")) unit = "dB"; return rv; } bool SpectrumLayer::getCrosshairExtents(LayerGeometryProvider *v, QPainter &paint, QPoint cursorPos, std::vector<QRect> &extents) const { QRect vertical(cursorPos.x(), cursorPos.y(), 1, v->getPaintHeight() - cursorPos.y()); extents.push_back(vertical); QRect horizontal(0, cursorPos.y(), v->getPaintWidth(), 12); extents.push_back(horizontal); int hoffset = 2; if (m_binScale == LogBins) hoffset = 13; int sw = getVerticalScaleWidth(v, false, paint); // Qt 5.13 deprecates QFontMetrics::width(), but its suggested // replacement (horizontalAdvance) was only added in Qt 5.11 // which is too new for us #pragma GCC diagnostic ignored "-Wdeprecated-declarations" QRect value(sw, cursorPos.y() - paint.fontMetrics().ascent() - 2, paint.fontMetrics().width("0.0000001 V") + 2, paint.fontMetrics().height()); extents.push_back(value); QRect log(sw, cursorPos.y() + 2, paint.fontMetrics().width("-80.000 dBV") + 2, paint.fontMetrics().height()); extents.push_back(log); QRect freq(cursorPos.x(), v->getPaintHeight() - paint.fontMetrics().height() - hoffset, paint.fontMetrics().width("123456 Hz") + 2, paint.fontMetrics().height()); extents.push_back(freq); int w(paint.fontMetrics().width("C#10+50c") + 2); QRect pitch(cursorPos.x() - w, v->getPaintHeight() - paint.fontMetrics().height() - hoffset, w, paint.fontMetrics().height()); extents.push_back(pitch); return true; } void SpectrumLayer::paintCrosshairs(LayerGeometryProvider *v, QPainter &paint, QPoint cursorPos) const { auto sliceableModel = ModelById::getAs<DenseThreeDimensionalModel> (m_sliceableModel); if (!sliceableModel) return; paint.save(); QFont fn = paint.font(); if (fn.pointSize() > 8) { fn.setPointSize(fn.pointSize() - 1); paint.setFont(fn); } ColourMapper mapper(m_colourMap, m_colourInverted, 0, 1); paint.setPen(mapper.getContrastingColour()); int xorigin = m_xorigins[v->getId()]; paint.drawLine(xorigin, cursorPos.y(), v->getPaintWidth(), cursorPos.y()); paint.drawLine(cursorPos.x(), cursorPos.y(), cursorPos.x(), v->getPaintHeight()); double fundamental = getFrequencyForX(v, cursorPos.x()); int hoffset = getHorizontalScaleHeight(v, paint) + 2 * paint.fontMetrics().height(); PaintAssistant::drawVisibleText(v, paint, cursorPos.x() + 2, v->getPaintHeight() - 2 - hoffset, tr("%1 Hz").arg(fundamental), PaintAssistant::OutlinedText); if (Pitch::isFrequencyInMidiRange(fundamental)) { QString pitchLabel = Pitch::getPitchLabelForFrequency(fundamental); PaintAssistant::drawVisibleText(v, paint, cursorPos.x() - paint.fontMetrics().width(pitchLabel) - 2, v->getPaintHeight() - 2 - hoffset, pitchLabel, PaintAssistant::OutlinedText); } double value = getValueForY(v, cursorPos.y()); PaintAssistant::drawVisibleText(v, paint, xorigin + 2, cursorPos.y() - 2, QString("%1 V").arg(value), PaintAssistant::OutlinedText); if (value > m_threshold) { double db = 10.0 * log10(value); PaintAssistant::drawVisibleText(v, paint, xorigin + 2, cursorPos.y() + 2 + paint.fontMetrics().ascent(), QString("%1 dBV").arg(db), PaintAssistant::OutlinedText); } int harmonic = 2; while (harmonic < 100) { int hx = int(lrint(getXForFrequency(v, fundamental * harmonic))); if (hx < xorigin || hx > v->getPaintWidth()) break; int len = 7; if (harmonic % 2 == 0) { if (harmonic % 4 == 0) { len = 12; } else { len = 10; } } paint.drawLine(hx, cursorPos.y(), hx, cursorPos.y() + len); ++harmonic; } paint.restore(); } QString SpectrumLayer::getFeatureDescription(LayerGeometryProvider *v, QPoint &p) const { auto sliceableModel = ModelById::getAs<DenseThreeDimensionalModel> (m_sliceableModel); if (!sliceableModel) return ""; int minbin = 0, maxbin = 0, range = 0; QString genericDesc = SliceLayer::getFeatureDescriptionAux (v, p, false, minbin, maxbin, range); if (genericDesc == "") return ""; int i0 = minbin - m_minbin; int i1 = maxbin - m_minbin; float minvalue = 0.0; if (in_range_for(m_values, i0)) minvalue = m_values[i0]; float maxvalue = minvalue; if (in_range_for(m_values, i1)) maxvalue = m_values[i1]; if (minvalue > maxvalue) std::swap(minvalue, maxvalue); QString binstr; QString hzstr; int minfreq = int(lrint((minbin * sliceableModel->getSampleRate()) / getFFTSize())); int maxfreq = int(lrint((std::max(maxbin, minbin) * sliceableModel->getSampleRate()) / getFFTSize())); if (maxbin != minbin) { binstr = tr("%1 - %2").arg(minbin+1).arg(maxbin+1); } else { binstr = QString("%1").arg(minbin+1); } if (minfreq != maxfreq) { hzstr = tr("%1 - %2 Hz").arg(minfreq).arg(maxfreq); } else { hzstr = tr("%1 Hz").arg(minfreq); } QString valuestr; if (maxvalue != minvalue) { valuestr = tr("%1 - %2").arg(minvalue).arg(maxvalue); } else { valuestr = QString("%1").arg(minvalue); } QString dbstr; double mindb = AudioLevel::multiplier_to_dB(minvalue); double maxdb = AudioLevel::multiplier_to_dB(maxvalue); QString mindbstr; QString maxdbstr; if (mindb == AudioLevel::DB_FLOOR) { mindbstr = Strings::minus_infinity; } else { mindbstr = QString("%1").arg(lrint(mindb)); } if (maxdb == AudioLevel::DB_FLOOR) { maxdbstr = Strings::minus_infinity; } else { maxdbstr = QString("%1").arg(lrint(maxdb)); } if (lrint(mindb) != lrint(maxdb)) { dbstr = tr("%1 - %2").arg(mindbstr).arg(maxdbstr); } else { dbstr = tr("%1").arg(mindbstr); } QString description; if (range > int(sliceableModel->getResolution())) { description = tr("%1\nBin:\t%2 (%3)\n%4 value:\t%5\ndB:\t%6") .arg(genericDesc) .arg(binstr) .arg(hzstr) .arg(m_samplingMode == NearestSample ? tr("First") : m_samplingMode == SampleMean ? tr("Mean") : tr("Peak")) .arg(valuestr) .arg(dbstr); } else { description = tr("%1\nBin:\t%2 (%3)\nValue:\t%4\ndB:\t%5") .arg(genericDesc) .arg(binstr) .arg(hzstr) .arg(valuestr) .arg(dbstr); } return description; } void SpectrumLayer::paint(LayerGeometryProvider *v, QPainter &paint, QRect rect) const { auto originModel = ModelById::get(m_originModel); if (!originModel || !originModel->isOK() || !originModel->isReady()) { SVDEBUG << "SpectrumLayer::paint: no origin model, or origin model not OK or not ready" << endl; return; } if (m_newFFTNeeded) { SVDEBUG << "SpectrumLayer::paint: new FFT needed, calling setupFFT" << endl; const_cast<SpectrumLayer *>(this)->setupFFT(); //ugh } auto fft = ModelById::getAs<FFTModel>(m_sliceableModel); if (!fft) return; double thresh = (pow(10, -6) / m_gain) * (getFFTSize() / 2.0); // -60dB adj int xorigin = getVerticalScaleWidth(v, false, paint) + 1; int scaleHeight = getHorizontalScaleHeight(v, paint); QPoint localPos; bool shouldIlluminate = v->shouldIlluminateLocalFeatures(this, localPos); int illuminateX = 0; double illuminateFreq = 0.0; double illuminateLevel = 0.0; ColourMapper mapper = hasLightBackground() ? ColourMapper(ColourMapper::BlackOnWhite, m_colourInverted, 0, 1) : ColourMapper(ColourMapper::WhiteOnBlack, m_colourInverted, 0, 1); // cerr << "shouldIlluminate = " << shouldIlluminate << ", localPos = " << localPos.x() << "," << localPos.y() << endl; if (fft && m_showPeaks) { // draw peak lines int col = int(v->getCentreFrame() / fft->getResolution()); paint.save(); paint.setRenderHint(QPainter::Antialiasing, false); int peakminbin = 0; int peakmaxbin = fft->getHeight() - 1; double peakmaxfreq = Pitch::getFrequencyForPitch(128); peakmaxbin = int(((peakmaxfreq * fft->getHeight() * 2) / fft->getSampleRate())); FFTModel::PeakSet peaks = fft->getPeakFrequencies (FFTModel::MajorPitchAdaptivePeaks, col, peakminbin, peakmaxbin); BiasCurve curve; getBiasCurve(curve); int cs = int(curve.size()); int px = -1; int fuzz = ViewManager::scalePixelSize(3); for (FFTModel::PeakSet::iterator i = peaks.begin(); i != peaks.end(); ++i) { double freq = i->second; int x = int(lrint(getXForFrequency(v, freq))); if (x == px) { continue; } int bin = i->first; // cerr << "bin = " << bin << ", thresh = " << thresh << ", value = " << fft->getMagnitudeAt(col, bin) << endl; double value = fft->getValueAt(col, bin); if (value < thresh) continue; if (bin < cs) value *= curve[bin]; double norm = 0.f; // we only need the norm here, for the colour map (void)getYForValue(v, value, norm); QColor colour = mapper.map(norm); paint.setPen(QPen(colour, 1)); paint.drawLine(x, 0, x, v->getPaintHeight() - scaleHeight - 1); if (shouldIlluminate && std::abs(localPos.x() - x) <= fuzz) { illuminateX = x; illuminateFreq = freq; illuminateLevel = norm; } px = x; } paint.restore(); } paint.save(); SliceLayer::paint(v, paint, rect); paintHorizontalScale(v, paint, xorigin); paint.restore(); if (illuminateFreq > 0.0) { QColor colour = mapper.map(illuminateLevel); paint.setPen(QPen(colour, 1)); int labelY = v->getPaintHeight() - getHorizontalScaleHeight(v, paint) - paint.fontMetrics().height() * 4; QString text = tr("%1 Hz").arg(illuminateFreq); int lw = paint.fontMetrics().width(text); int gap = ViewManager::scalePixelSize(v->getXForViewX(3)); double half = double(gap)/2.0; int labelX = illuminateX - lw - gap; if (labelX < getVerticalScaleWidth(v, false, paint)) { labelX = illuminateX + gap; } PaintAssistant::drawVisibleText (v, paint, labelX, labelY, text, PaintAssistant::OutlinedText); if (Pitch::isFrequencyInMidiRange(illuminateFreq)) { QString pitchLabel = Pitch::getPitchLabelForFrequency (illuminateFreq); PaintAssistant::drawVisibleText (v, paint, labelX, labelY + paint.fontMetrics().ascent() + gap, pitchLabel, PaintAssistant::OutlinedText); } paint.fillRect(QRectF(illuminateX - half, labelY + gap, gap, gap), colour); } } int SpectrumLayer::getHorizontalScaleHeight(LayerGeometryProvider *v, QPainter &paint) const { int pkh = int(paint.fontMetrics().height() * 0.7 + 0.5); if (pkh < 10) pkh = 10; int scaleh = HorizontalFrequencyScale().getHeight(v, paint); return pkh + scaleh; } void SpectrumLayer::paintHorizontalScale(LayerGeometryProvider *v, QPainter &paint, int xorigin) const { //!!! All of this stuff relating to depicting frequencies // (keyboard, crosshairs etc) should be applicable to any slice // layer whose model has a vertical scale unit of Hz. However, // the dense 3d model at the moment doesn't record its vertical // scale unit -- we need to fix that and hoist this code as // appropriate. Same really goes for any code in SpectrogramLayer // that could be relevant to Colour3DPlotLayer with unit Hz, but // that's a bigger proposition. if (!v->getViewManager()->shouldShowHorizontalValueScale()) { return; } int totalScaleHeight = getHorizontalScaleHeight(v, paint); // inc piano int freqScaleHeight = HorizontalFrequencyScale().getHeight(v, paint); int paintHeight = v->getPaintHeight(); int paintWidth = v->getPaintWidth(); PianoScale().paintPianoHorizontal (v, this, paint, QRect(xorigin, paintHeight - totalScaleHeight - 1, paintWidth - 1, totalScaleHeight - freqScaleHeight)); int scaleLeft = int(getXForBin(v, 1)); paint.drawLine(int(getXForBin(v, 0)), paintHeight - freqScaleHeight, scaleLeft, paintHeight - freqScaleHeight); QString hz = tr("Hz"); int hzw = paint.fontMetrics().width(hz); if (scaleLeft > hzw + 5) { paint.drawText (scaleLeft - hzw - 5, paintHeight - freqScaleHeight + paint.fontMetrics().ascent() + 5, hz); } HorizontalFrequencyScale().paintScale (v, this, paint, QRect(scaleLeft, paintHeight - freqScaleHeight, paintWidth, totalScaleHeight), m_binScale == LogBins); } void SpectrumLayer::getBiasCurve(BiasCurve &curve) const { curve = m_biasCurve; } void SpectrumLayer::toXml(QTextStream &stream, QString indent, QString extraAttributes) const { QString s = QString("windowSize=\"%1\" " "windowHopLevel=\"%2\" " "oversampling=\"%3\" " "showPeaks=\"%4\" ") .arg(m_windowSize) .arg(m_windowHopLevel) .arg(m_oversampling) .arg(m_showPeaks ? "true" : "false"); SliceLayer::toXml(stream, indent, extraAttributes + " " + s); } void SpectrumLayer::setProperties(const QXmlAttributes &attributes) { SliceLayer::setProperties(attributes); bool ok = false; int windowSize = attributes.value("windowSize").toUInt(&ok); if (ok) setWindowSize(windowSize); int windowHopLevel = attributes.value("windowHopLevel").toUInt(&ok); if (ok) setWindowHopLevel(windowHopLevel); int oversampling = attributes.value("oversampling").toUInt(&ok); if (ok) setOversampling(oversampling); bool showPeaks = (attributes.value("showPeaks").trimmed() == "true"); setShowPeaks(showPeaks); }