Chris@133: 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@254: #include "ColourMapper.h" Chris@254: Chris@254: #include Chris@133: Chris@133: SpectrumLayer::SpectrumLayer() : Chris@193: m_originModel(0), Chris@153: m_channel(-1), Chris@153: m_channelSet(false), Chris@153: m_windowSize(1024), Chris@153: m_windowType(HanningWindow), Chris@153: m_windowHopLevel(2) 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@193: //!!! delete parent's model Chris@193: // for (size_t i = 0; i < m_fft.size(); ++i) delete m_fft[i]; Chris@133: } Chris@133: Chris@133: void Chris@133: SpectrumLayer::setModel(DenseTimeValueModel *model) Chris@133: { Chris@193: if (m_originModel == model) return; Chris@193: m_originModel = model; Chris@193: setupFFT(); Chris@153: } Chris@153: Chris@153: void Chris@193: SpectrumLayer::setupFFT() Chris@153: { Chris@193: FFTModel *oldFFT = dynamic_cast Chris@193: (const_cast(m_sliceableModel)); Chris@153: Chris@193: if (oldFFT) { Chris@193: setSliceableModel(0); Chris@193: delete oldFFT; 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@193: true); 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@153: Chris@153: void Chris@153: SpectrumLayer::setChannel(int channel) Chris@153: { Chris@153: m_channelSet = true; Chris@153: Chris@193: FFTModel *fft = dynamic_cast Chris@193: (const_cast(m_sliceableModel)); Chris@193: Chris@153: if (m_channel == channel) { Chris@193: if (fft) fft->resume(); Chris@153: return; Chris@153: } Chris@153: Chris@153: m_channel = channel; Chris@153: Chris@193: if (!fft) setupFFT(); Chris@153: Chris@153: emit layerParametersChanged(); 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@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@193: return SliceLayer::getPropertyLabel(name); Chris@153: } Chris@153: 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@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@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@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@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@193: setupFFT(); 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@193: setupFFT(); 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@193: setupFFT(); Chris@153: emit layerParametersChanged(); Chris@153: } Chris@153: Chris@153: 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: bool Chris@254: SpectrumLayer::getCrosshairExtents(View *v, QPainter &, Chris@254: QPoint cursorPos, Chris@254: std::vector &extents) const Chris@254: { Chris@254: QRect vertical(cursorPos.x(), cursorPos.y(), 1, v->height() - cursorPos.y()); Chris@254: extents.push_back(vertical); Chris@254: Chris@254: QRect horizontal(0, cursorPos.y(), v->width(), 12); Chris@254: extents.push_back(horizontal); Chris@254: Chris@254: return true; Chris@254: } Chris@254: Chris@254: float Chris@254: SpectrumLayer::getFrequencyForX(float x, float w) const Chris@254: { Chris@254: float freq = 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@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@254: x = (w - log10f(maxfreq - freq) * w) / log10f(maxfreq); Chris@254: break; Chris@254: } Chris@254: Chris@254: return x; Chris@254: } Chris@254: Chris@254: void Chris@254: SpectrumLayer::paintCrosshairs(View *v, QPainter &paint, Chris@254: QPoint cursorPos) const Chris@254: { Chris@254: paint.save(); 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@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@254: SpectrumLayer::getBiasCurve(BiasCurve &curve) const Chris@254: { Chris@254: curve = m_biasCurve; Chris@254: } Chris@199: Chris@220: QString Chris@220: SpectrumLayer::toXmlString(QString indent, QString extraAttributes) const Chris@220: { Chris@220: QString s; Chris@199: Chris@220: s += QString("windowSize=\"%1\" " Chris@220: "windowHopLevel=\"%2\"") Chris@220: .arg(m_windowSize) Chris@220: .arg(m_windowHopLevel); Chris@220: Chris@220: return SliceLayer::toXmlString(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@220: } Chris@220: Chris@220: