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@376: #include "ColourMapper.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@193: m_windowSize, Chris@327: false, Chris@327: StorageAdviser::Criteria Chris@327: (StorageAdviser::SpeedCritical | Chris@327: StorageAdviser::FrequentLookupLikely)); Chris@153: Chris@193: setSliceableModel(newFFT); Chris@193: Chris@254: m_biasCurve.clear(); Chris@254: for (size_t i = 0; i < m_windowSize; ++i) { Chris@254: m_biasCurve.push_back(1.f / (float(m_windowSize)/2.f)); Chris@254: } Chris@254: Chris@193: newFFT->resume(); Chris@153: 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@153: 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@153: *min = 0; Chris@254: *max = 15; Chris@216: *deflt = 5; Chris@153: Chris@216: val = 0; Chris@153: int ws = m_windowSize; Chris@216: while (ws > 32) { ws >>= 1; val ++; } Chris@153: Chris@153: } else if (name == "Window Increment") { Chris@153: Chris@153: *min = 0; Chris@153: *max = 5; Chris@216: *deflt = 2; Chris@153: 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@153: int value) const Chris@153: { Chris@153: if (name == "Window Size") { Chris@153: return QString("%1").arg(32 << value); Chris@153: } Chris@153: if (name == "Window Increment") { Chris@153: switch (value) { Chris@153: default: Chris@153: case 0: return tr("None"); Chris@153: case 1: return tr("25 %"); Chris@153: case 2: return tr("50 %"); Chris@153: case 3: return tr("75 %"); Chris@153: case 4: return tr("87.5 %"); Chris@153: case 5: return tr("93.75 %"); Chris@153: } 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@153: 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@153: SpectrumLayer::setWindowSize(size_t 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@153: SpectrumLayer::setWindowHopLevel(size_t 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@133: bool Chris@248: SpectrumLayer::getValueExtents(float &, float &, bool &, QString &) const Chris@133: { Chris@133: return false; Chris@133: } Chris@133: Chris@254: float Chris@265: SpectrumLayer::getXForBin(int bin, int totalBins, float w) const Chris@265: { Chris@265: if (!m_sliceableModel) return SliceLayer::getXForBin(bin, totalBins, w); Chris@265: Chris@265: float sampleRate = m_sliceableModel->getSampleRate(); Chris@265: float binfreq = (sampleRate * bin) / (totalBins * 2); Chris@265: Chris@265: return getXForFrequency(binfreq, w); Chris@265: } Chris@265: Chris@265: int Chris@265: SpectrumLayer::getBinForX(float x, int totalBins, float w) const Chris@265: { Chris@265: if (!m_sliceableModel) return SliceLayer::getBinForX(x, totalBins, w); Chris@265: Chris@265: float sampleRate = m_sliceableModel->getSampleRate(); Chris@265: float binfreq = getFrequencyForX(x, w); Chris@265: Chris@265: return int((binfreq * totalBins * 2) / sampleRate); Chris@265: } Chris@265: Chris@265: float Chris@254: SpectrumLayer::getFrequencyForX(float x, float w) const Chris@254: { Chris@254: float freq = 0; Chris@280: if (!m_sliceableModel) return 0; Chris@254: Chris@254: int sampleRate = m_sliceableModel->getSampleRate(); Chris@254: Chris@254: float maxfreq = float(sampleRate) / 2; Chris@254: Chris@254: switch (m_binScale) { Chris@254: Chris@254: case LinearBins: Chris@254: freq = ((x * maxfreq) / w); Chris@254: break; Chris@254: Chris@254: case LogBins: Chris@254: freq = powf(10.f, (x * log10f(maxfreq)) / w); Chris@254: break; Chris@254: Chris@254: case InvertedLogBins: Chris@254: freq = maxfreq - powf(10.f, ((w - x) * log10f(maxfreq)) / w); Chris@254: break; Chris@254: } Chris@254: Chris@254: return freq; Chris@254: } Chris@254: Chris@254: float Chris@254: SpectrumLayer::getXForFrequency(float freq, float w) const Chris@254: { Chris@254: float x = 0; Chris@280: if (!m_sliceableModel) return x; Chris@254: Chris@254: int sampleRate = m_sliceableModel->getSampleRate(); Chris@254: Chris@254: float maxfreq = float(sampleRate) / 2; Chris@254: Chris@254: switch (m_binScale) { Chris@254: Chris@254: case LinearBins: Chris@254: x = (freq * w) / maxfreq; Chris@254: break; Chris@254: Chris@254: case LogBins: Chris@254: x = (log10f(freq) * w) / log10f(maxfreq); Chris@254: break; Chris@254: Chris@254: case InvertedLogBins: Chris@274: if (maxfreq == freq) x = w; Chris@274: else x = w - (log10f(maxfreq - freq) * w) / log10f(maxfreq); Chris@254: break; Chris@254: } Chris@254: Chris@254: return x; Chris@254: } Chris@254: Chris@260: bool Chris@267: SpectrumLayer::getXScaleValue(const View *v, int x, Chris@260: float &value, QString &unit) const Chris@260: { Chris@267: if (m_xorigins.find(v) == m_xorigins.end()) return false; Chris@267: int xorigin = m_xorigins.find(v)->second; Chris@267: value = getFrequencyForX(x - xorigin, v->width() - xorigin - 1); Chris@260: unit = "Hz"; Chris@260: return true; Chris@260: } Chris@260: Chris@264: bool Chris@274: SpectrumLayer::getYScaleValue(const View *v, int y, Chris@274: float &value, QString &unit) const Chris@274: { Chris@274: value = getValueForY(y, v); Chris@274: Chris@274: if (m_energyScale == dBScale || m_energyScale == MeterScale) { Chris@274: Chris@274: if (value > 0.f) { Chris@274: value = 10.f * log10f(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@274: SpectrumLayer::getYScaleDifference(const View *v, int y0, int y1, Chris@274: float &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@264: SpectrumLayer::getCrosshairExtents(View *v, QPainter &paint, Chris@264: QPoint cursorPos, Chris@264: std::vector &extents) const Chris@264: { Chris@264: QRect vertical(cursorPos.x(), cursorPos.y(), 1, v->height() - cursorPos.y()); Chris@264: extents.push_back(vertical); Chris@264: Chris@264: QRect horizontal(0, cursorPos.y(), v->width(), 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@280: int sw = getVerticalScaleWidth(v, 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@280: v->height() - 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@278: v->height() - 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@254: SpectrumLayer::paintCrosshairs(View *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@254: int xorigin = m_xorigins[v]; Chris@254: int w = v->width() - xorigin - 1; Chris@254: Chris@254: paint.drawLine(xorigin, cursorPos.y(), v->width(), cursorPos.y()); Chris@254: paint.drawLine(cursorPos.x(), cursorPos.y(), cursorPos.x(), v->height()); Chris@254: Chris@254: float fundamental = getFrequencyForX(cursorPos.x() - xorigin, w); Chris@254: Chris@280: int hoffset = 2; Chris@280: if (m_binScale == LogBins) hoffset = 13; Chris@278: Chris@278: v->drawVisibleText(paint, Chris@278: cursorPos.x() + 2, Chris@278: v->height() - 2 - hoffset, Chris@278: QString("%1 Hz").arg(fundamental), Chris@278: View::OutlinedText); Chris@278: Chris@278: if (Pitch::isFrequencyInMidiRange(fundamental)) { Chris@278: QString pitchLabel = Pitch::getPitchLabelForFrequency(fundamental); Chris@278: v->drawVisibleText(paint, Chris@278: cursorPos.x() - paint.fontMetrics().width(pitchLabel) - 2, Chris@278: v->height() - 2 - hoffset, Chris@278: pitchLabel, Chris@278: View::OutlinedText); Chris@278: } Chris@264: Chris@280: float value = getValueForY(cursorPos.y(), v); Chris@284: float thresh = m_threshold; Chris@280: float db = thresh; Chris@280: if (value > 0.f) db = 10.f * log10f(value); Chris@280: if (db < thresh) db = thresh; Chris@280: Chris@280: v->drawVisibleText(paint, Chris@280: xorigin + 2, Chris@280: cursorPos.y() - 2, Chris@280: QString("%1 V").arg(value), Chris@280: View::OutlinedText); Chris@280: Chris@280: v->drawVisibleText(paint, Chris@280: xorigin + 2, Chris@280: cursorPos.y() + 2 + paint.fontMetrics().ascent(), Chris@280: QString("%1 dBV").arg(db), Chris@280: View::OutlinedText); Chris@280: Chris@254: int harmonic = 2; Chris@254: Chris@254: while (harmonic < 100) { Chris@254: Chris@254: float hx = lrintf(getXForFrequency(fundamental * harmonic, w)); Chris@254: hx += xorigin; Chris@254: Chris@254: if (hx < xorigin || hx > v->width()) 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@254: paint.drawLine(int(hx), Chris@254: cursorPos.y(), Chris@254: int(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@199: SpectrumLayer::getFeatureDescription(View *v, QPoint &p) const Chris@199: { Chris@199: if (!m_sliceableModel) return ""; Chris@199: Chris@199: int minbin = 0, maxbin = 0, range = 0; Chris@199: QString genericDesc = SliceLayer::getFeatureDescription Chris@199: (v, p, false, minbin, maxbin, range); Chris@199: Chris@199: if (genericDesc == "") return ""; Chris@199: Chris@199: float minvalue = 0.f; Chris@248: if (minbin < int(m_values.size())) minvalue = m_values[minbin]; Chris@199: Chris@199: float maxvalue = minvalue; Chris@248: if (maxbin < int(m_values.size())) maxvalue = m_values[maxbin]; Chris@199: Chris@199: if (minvalue > maxvalue) std::swap(minvalue, maxvalue); Chris@199: Chris@199: QString binstr; Chris@199: QString hzstr; Chris@199: int minfreq = lrintf((minbin * m_sliceableModel->getSampleRate()) / Chris@199: m_windowSize); Chris@199: int maxfreq = lrintf((std::max(maxbin, minbin+1) Chris@199: * m_sliceableModel->getSampleRate()) / Chris@199: 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@199: float mindb = AudioLevel::multiplier_to_dB(minvalue); Chris@199: float maxdb = AudioLevel::multiplier_to_dB(maxvalue); Chris@199: QString mindbstr; Chris@199: QString maxdbstr; Chris@199: if (mindb == AudioLevel::DB_FLOOR) { Chris@199: mindbstr = tr("-Inf"); Chris@199: } else { Chris@199: mindbstr = QString("%1").arg(lrintf(mindb)); Chris@199: } Chris@199: if (maxdb == AudioLevel::DB_FLOOR) { Chris@199: maxdbstr = tr("-Inf"); Chris@199: } else { Chris@199: maxdbstr = QString("%1").arg(lrintf(maxdb)); Chris@199: } Chris@199: if (lrintf(mindb) != lrintf(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@275: SpectrumLayer::paint(View *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@280: float thresh = (powf(10, -6) / m_gain) * (m_windowSize / 2.f); // -60dB adj Chris@277: Chris@277: int xorigin = getVerticalScaleWidth(v, paint) + 1; Chris@277: int w = v->width() - xorigin - 1; Chris@277: Chris@278: int pkh = 0; Chris@287: //!!! if (m_binScale == LogBins) { Chris@287: pkh = 10; Chris@287: //!!! } Chris@278: Chris@345: paint.save(); Chris@345: Chris@284: if (fft && m_showPeaks) { Chris@277: Chris@277: // draw peak lines Chris@277: Chris@587: // SVDEBUG << "Showing peaks..." << endl; Chris@456: Chris@277: size_t col = v->getCentreFrame() / fft->getResolution(); Chris@277: Chris@277: paint.save(); Chris@277: paint.setRenderHint(QPainter::Antialiasing, false); Chris@277: paint.setPen(QColor(160, 160, 160)); //!!! Chris@277: Chris@290: int peakminbin = 0; Chris@290: int peakmaxbin = fft->getHeight() - 1; Chris@290: float peakmaxfreq = Pitch::getFrequencyForPitch(128); Chris@290: peakmaxbin = ((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@280: ColourMapper mapper(ColourMapper::BlackOnWhite, 0, 1); Chris@277: Chris@277: BiasCurve curve; Chris@277: getBiasCurve(curve); Chris@277: size_t cs = curve.size(); Chris@280: Chris@280: std::vector values; Chris@277: Chris@277: for (size_t bin = 0; bin < fft->getHeight(); ++bin) { Chris@280: float 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@280: size_t bin = i->first; Chris@277: Chris@280: // std::cerr << "bin = " << bin << ", thresh = " << thresh << ", value = " << fft->getMagnitudeAt(col, bin) << std::endl; Chris@280: Chris@277: if (!fft->isOverThreshold(col, bin, thresh)) continue; Chris@277: Chris@280: float freq = i->second; Chris@280: Chris@277: int x = lrintf(getXForFrequency(freq, w)); Chris@277: Chris@277: float norm = 0.f; Chris@280: float y = getYForValue(values[bin], v, norm); // don't need y, need norm Chris@277: Chris@277: paint.setPen(mapper.map(norm)); Chris@278: paint.drawLine(xorigin + x, 0, xorigin + x, v->height() - pkh - 1); Chris@277: } Chris@277: Chris@277: paint.restore(); Chris@277: } Chris@275: Chris@275: SliceLayer::paint(v, paint, rect); Chris@277: Chris@278: //!!! All of this stuff relating to depicting frequencies Chris@278: //(keyboard, crosshairs etc) should be applicable to any slice Chris@278: //layer whose model has a vertical scale unit of Hz. However, the Chris@278: //dense 3d model at the moment doesn't record its vertical scale Chris@278: //unit -- we need to fix that and hoist this code as appropriate. Chris@278: //Same really goes for any code in SpectrogramLayer that could be Chris@278: //relevant to Colour3DPlotLayer with unit Hz, but that's a bigger Chris@278: //proposition. Chris@278: Chris@287: // if (m_binScale == LogBins) { Chris@277: Chris@287: // int pkh = 10; Chris@277: int h = v->height(); Chris@277: Chris@277: // piano keyboard Chris@277: //!!! should be in a new paintHorizontalScale()? Chris@278: // nice to have a piano keyboard class, of course Chris@277: Chris@278: paint.drawLine(xorigin, h - pkh - 1, w + xorigin, h - pkh - 1); Chris@277: Chris@277: int px = xorigin, ppx = xorigin; Chris@278: paint.setBrush(paint.pen().color()); Chris@277: Chris@277: for (int i = 0; i < 128; ++i) { Chris@277: Chris@277: float f = Pitch::getFrequencyForPitch(i); Chris@277: int x = lrintf(getXForFrequency(f, w)); Chris@278: Chris@278: x += xorigin; Chris@277: Chris@278: if (i == 0) { Chris@278: px = ppx = x; Chris@278: } Chris@278: if (i == 1) { Chris@278: ppx = px - (x - px); Chris@278: } Chris@278: Chris@278: if (x < xorigin) { Chris@278: ppx = px; Chris@278: px = x; Chris@277: continue; Chris@277: } Chris@278: Chris@278: if (x > w) { Chris@278: break; Chris@278: } Chris@277: Chris@277: int n = (i % 12); Chris@277: Chris@277: if (n == 1) { Chris@277: // C# -- fill the C from here Chris@284: QColor col = Qt::gray; Chris@284: if (i == 61) { // filling middle C Chris@284: col = Qt::blue; Chris@284: col = col.light(150); Chris@284: } Chris@277: if (x - ppx > 2) { Chris@278: paint.fillRect((px + ppx) / 2 + 1, Chris@277: h - pkh, Chris@278: x - (px + ppx) / 2 - 1, Chris@277: pkh, Chris@284: col); Chris@277: } Chris@277: } Chris@277: Chris@277: if (n == 1 || n == 3 || n == 6 || n == 8 || n == 10) { Chris@277: // black notes Chris@277: paint.drawLine(x, h - pkh, x, h); Chris@278: int rw = lrintf(float(x - px) / 4) * 2; Chris@277: if (rw < 2) rw = 2; Chris@278: paint.drawRect(x - rw/2, h - pkh, rw, pkh/2); Chris@277: } else if (n == 0 || n == 5) { Chris@277: // C, F Chris@277: if (px < w) { Chris@277: paint.drawLine((x + px) / 2, h - pkh, (x + px) / 2, h); Chris@277: } Chris@277: } Chris@277: Chris@277: ppx = px; Chris@277: px = x; Chris@277: } Chris@287: // } Chris@345: Chris@345: paint.restore(); 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@220: size_t windowSize = attributes.value("windowSize").toUInt(&ok); Chris@220: if (ok) setWindowSize(windowSize); Chris@220: Chris@220: size_t 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: