Chris@58: /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ Chris@0: Chris@0: /* Chris@59: Sonic Visualiser Chris@59: An audio file viewer and annotation editor. Chris@59: Centre for Digital Music, Queen Mary, University of London. Chris@484: This file copyright 2006-2009 Chris Cannam and QMUL. Chris@0: Chris@59: This program is free software; you can redistribute it and/or Chris@59: modify it under the terms of the GNU General Public License as Chris@59: published by the Free Software Foundation; either version 2 of the Chris@59: License, or (at your option) any later version. See the file Chris@59: COPYING included with this distribution for more information. Chris@0: */ Chris@0: Chris@0: #include "SpectrogramLayer.h" Chris@0: Chris@128: #include "view/View.h" Chris@0: #include "base/Profiler.h" Chris@0: #include "base/AudioLevel.h" Chris@0: #include "base/Window.h" Chris@24: #include "base/Pitch.h" Chris@118: #include "base/Preferences.h" Chris@167: #include "base/RangeMapper.h" Chris@253: #include "base/LogRange.h" Chris@376: #include "widgets/CommandHistory.h" Chris@376: #include "ColourMapper.h" Chris@283: #include "ImageRegionFinder.h" Chris@484: #include "data/model/Dense3DModelPeakCache.h" Chris@690: #include "PianoScale.h" Chris@0: Chris@0: #include Chris@0: #include Chris@0: #include Chris@0: #include Chris@0: #include Chris@92: #include Chris@178: #include Chris@283: #include Chris@316: #include Chris@0: Chris@0: #include Chris@0: Chris@0: #include Chris@0: #include Chris@0: Chris@545: #ifndef __GNUC__ Chris@545: #include Chris@545: #endif Chris@545: Chris@986: //#define DEBUG_SPECTROGRAM_REPAINT 1 Chris@0: Chris@907: using std::vector; Chris@907: Chris@44: SpectrogramLayer::SpectrogramLayer(Configuration config) : Chris@0: m_model(0), Chris@0: m_channel(0), Chris@0: m_windowSize(1024), Chris@0: m_windowType(HanningWindow), Chris@97: m_windowHopLevel(2), Chris@109: m_zeroPadLevel(0), Chris@107: m_fftSize(1024), Chris@0: m_gain(1.0), Chris@215: m_initialGain(1.0), Chris@37: m_threshold(0.0), Chris@215: m_initialThreshold(0.0), Chris@9: m_colourRotation(0), Chris@215: m_initialRotation(0), Chris@119: m_minFrequency(10), Chris@0: m_maxFrequency(8000), Chris@135: m_initialMaxFrequency(8000), Chris@0: m_colourScale(dBColourScale), Chris@197: m_colourMap(0), Chris@0: m_frequencyScale(LinearFrequencyScale), Chris@37: m_binDisplay(AllBins), Chris@862: m_normalization(NoNormalization), Chris@133: m_lastEmittedZoomStep(-1), Chris@390: m_synchronous(false), Chris@608: m_haveDetailedScale(false), Chris@215: m_lastPaintBlockWidth(0), Chris@193: m_exiting(false), Chris@193: m_sliceableModel(0) Chris@0: { Chris@215: if (config == FullRangeDb) { Chris@215: m_initialMaxFrequency = 0; Chris@215: setMaxFrequency(0); Chris@215: } else if (config == MelodicRange) { Chris@0: setWindowSize(8192); Chris@97: setWindowHopLevel(4); Chris@215: m_initialMaxFrequency = 1500; Chris@215: setMaxFrequency(1500); Chris@215: setMinFrequency(40); Chris@0: setColourScale(LinearColourScale); Chris@215: setColourMap(ColourMapper::Sunset); Chris@215: setFrequencyScale(LogFrequencyScale); Chris@224: // setGain(20); Chris@37: } else if (config == MelodicPeaks) { Chris@37: setWindowSize(4096); Chris@97: setWindowHopLevel(5); Chris@135: m_initialMaxFrequency = 2000; Chris@40: setMaxFrequency(2000); Chris@37: setMinFrequency(40); Chris@37: setFrequencyScale(LogFrequencyScale); Chris@215: setColourScale(LinearColourScale); Chris@37: setBinDisplay(PeakFrequencies); Chris@862: setNormalization(NormalizeColumns); Chris@0: } Chris@110: Chris@122: Preferences *prefs = Preferences::getInstance(); Chris@122: connect(prefs, SIGNAL(propertyChanged(PropertyContainer::PropertyName)), Chris@122: this, SLOT(preferenceChanged(PropertyContainer::PropertyName))); Chris@122: setWindowType(prefs->getWindowType()); Chris@122: Chris@197: initialisePalette(); Chris@0: } Chris@0: Chris@0: SpectrogramLayer::~SpectrogramLayer() Chris@0: { Chris@130: invalidateFFTModels(); Chris@0: } Chris@0: Chris@0: void Chris@0: SpectrogramLayer::setModel(const DenseTimeValueModel *model) Chris@0: { Chris@682: // cerr << "SpectrogramLayer(" << this << "): setModel(" << model << ")" << endl; Chris@34: Chris@110: if (model == m_model) return; Chris@110: Chris@0: m_model = model; Chris@130: invalidateFFTModels(); Chris@0: Chris@0: if (!m_model || !m_model->isOK()) return; Chris@0: Chris@320: connectSignals(m_model); Chris@0: Chris@0: connect(m_model, SIGNAL(modelChanged()), this, SLOT(cacheInvalid())); Chris@906: connect(m_model, SIGNAL(modelChangedWithin(sv_frame_t, sv_frame_t)), Chris@906: this, SLOT(cacheInvalid(sv_frame_t, sv_frame_t))); Chris@0: Chris@0: emit modelReplaced(); Chris@110: } Chris@115: Chris@0: Layer::PropertyList Chris@0: SpectrogramLayer::getProperties() const Chris@0: { Chris@0: PropertyList list; Chris@87: list.push_back("Colour"); Chris@87: list.push_back("Colour Scale"); Chris@87: list.push_back("Window Size"); Chris@97: list.push_back("Window Increment"); Chris@862: list.push_back("Normalization"); Chris@87: list.push_back("Bin Display"); Chris@87: list.push_back("Threshold"); Chris@87: list.push_back("Gain"); Chris@87: list.push_back("Colour Rotation"); Chris@153: // list.push_back("Min Frequency"); Chris@153: // list.push_back("Max Frequency"); Chris@87: list.push_back("Frequency Scale"); Chris@153: //// list.push_back("Zero Padding"); Chris@0: return list; Chris@0: } Chris@0: Chris@87: QString Chris@87: SpectrogramLayer::getPropertyLabel(const PropertyName &name) const Chris@87: { Chris@87: if (name == "Colour") return tr("Colour"); Chris@87: if (name == "Colour Scale") return tr("Colour Scale"); Chris@87: if (name == "Window Size") return tr("Window Size"); Chris@112: if (name == "Window Increment") return tr("Window Overlap"); Chris@862: if (name == "Normalization") return tr("Normalization"); Chris@87: if (name == "Bin Display") return tr("Bin Display"); Chris@87: if (name == "Threshold") return tr("Threshold"); Chris@87: if (name == "Gain") return tr("Gain"); Chris@87: if (name == "Colour Rotation") return tr("Colour Rotation"); Chris@87: if (name == "Min Frequency") return tr("Min Frequency"); Chris@87: if (name == "Max Frequency") return tr("Max Frequency"); Chris@87: if (name == "Frequency Scale") return tr("Frequency Scale"); Chris@109: if (name == "Zero Padding") return tr("Smoothing"); Chris@87: return ""; Chris@87: } Chris@87: Chris@335: QString Chris@862: SpectrogramLayer::getPropertyIconName(const PropertyName &) const Chris@335: { Chris@335: return ""; Chris@335: } Chris@335: Chris@0: Layer::PropertyType Chris@0: SpectrogramLayer::getPropertyType(const PropertyName &name) const Chris@0: { Chris@87: if (name == "Gain") return RangeProperty; Chris@87: if (name == "Colour Rotation") return RangeProperty; Chris@87: if (name == "Threshold") return RangeProperty; Chris@109: if (name == "Zero Padding") return ToggleProperty; Chris@0: return ValueProperty; Chris@0: } Chris@0: Chris@0: QString Chris@0: SpectrogramLayer::getPropertyGroupName(const PropertyName &name) const Chris@0: { Chris@153: if (name == "Bin Display" || Chris@153: name == "Frequency Scale") return tr("Bins"); Chris@87: if (name == "Window Size" || Chris@109: name == "Window Increment" || Chris@109: name == "Zero Padding") return tr("Window"); Chris@87: if (name == "Colour" || Chris@87: name == "Threshold" || Chris@87: name == "Colour Rotation") return tr("Colour"); Chris@862: if (name == "Normalization" || Chris@153: name == "Gain" || Chris@87: name == "Colour Scale") return tr("Scale"); Chris@0: return QString(); Chris@0: } Chris@0: Chris@0: int Chris@0: SpectrogramLayer::getPropertyRangeAndValue(const PropertyName &name, Chris@216: int *min, int *max, int *deflt) const Chris@0: { Chris@216: int val = 0; Chris@216: Chris@216: int garbage0, garbage1, garbage2; Chris@55: if (!min) min = &garbage0; Chris@55: if (!max) max = &garbage1; Chris@216: if (!deflt) deflt = &garbage2; Chris@10: Chris@87: if (name == "Gain") { Chris@0: Chris@0: *min = -50; Chris@0: *max = 50; Chris@0: Chris@906: *deflt = int(lrint(log10(m_initialGain) * 20.0)); Chris@216: if (*deflt < *min) *deflt = *min; Chris@216: if (*deflt > *max) *deflt = *max; Chris@216: Chris@906: val = int(lrint(log10(m_gain) * 20.0)); Chris@216: if (val < *min) val = *min; Chris@216: if (val > *max) val = *max; Chris@0: Chris@87: } else if (name == "Threshold") { Chris@37: Chris@37: *min = -50; Chris@37: *max = 0; Chris@37: Chris@906: *deflt = int(lrint(AudioLevel::multiplier_to_dB(m_initialThreshold))); Chris@216: if (*deflt < *min) *deflt = *min; Chris@216: if (*deflt > *max) *deflt = *max; Chris@216: Chris@906: val = int(lrint(AudioLevel::multiplier_to_dB(m_threshold))); Chris@216: if (val < *min) val = *min; Chris@216: if (val > *max) val = *max; Chris@37: Chris@87: } else if (name == "Colour Rotation") { Chris@9: Chris@9: *min = 0; Chris@9: *max = 256; Chris@216: *deflt = m_initialRotation; Chris@216: Chris@216: val = m_colourRotation; Chris@9: Chris@87: } else if (name == "Colour Scale") { Chris@0: Chris@0: *min = 0; Chris@176: *max = 4; Chris@216: *deflt = int(dBColourScale); Chris@216: Chris@216: val = (int)m_colourScale; Chris@0: Chris@87: } else if (name == "Colour") { Chris@0: Chris@0: *min = 0; Chris@196: *max = ColourMapper::getColourMapCount() - 1; Chris@216: *deflt = 0; Chris@216: Chris@216: val = m_colourMap; Chris@0: Chris@87: } else if (name == "Window Size") { Chris@0: Chris@0: *min = 0; Chris@0: *max = 10; Chris@216: *deflt = 5; Chris@0: Chris@216: val = 0; Chris@0: int ws = m_windowSize; Chris@216: while (ws > 32) { ws >>= 1; val ++; } Chris@0: Chris@97: } else if (name == "Window Increment") { Chris@0: Chris@0: *min = 0; Chris@97: *max = 5; Chris@216: *deflt = 2; Chris@216: Chris@216: val = m_windowHopLevel; Chris@0: Chris@109: } else if (name == "Zero Padding") { Chris@109: Chris@109: *min = 0; Chris@109: *max = 1; Chris@216: *deflt = 0; Chris@109: Chris@216: val = m_zeroPadLevel > 0 ? 1 : 0; Chris@109: Chris@87: } else if (name == "Min Frequency") { Chris@37: Chris@37: *min = 0; Chris@37: *max = 9; Chris@216: *deflt = 1; Chris@37: Chris@37: switch (m_minFrequency) { Chris@216: case 0: default: val = 0; break; Chris@216: case 10: val = 1; break; Chris@216: case 20: val = 2; break; Chris@216: case 40: val = 3; break; Chris@216: case 100: val = 4; break; Chris@216: case 250: val = 5; break; Chris@216: case 500: val = 6; break; Chris@216: case 1000: val = 7; break; Chris@216: case 4000: val = 8; break; Chris@216: case 10000: val = 9; break; Chris@37: } Chris@37: Chris@87: } else if (name == "Max Frequency") { Chris@0: Chris@0: *min = 0; Chris@0: *max = 9; Chris@216: *deflt = 6; Chris@0: Chris@0: switch (m_maxFrequency) { Chris@216: case 500: val = 0; break; Chris@216: case 1000: val = 1; break; Chris@216: case 1500: val = 2; break; Chris@216: case 2000: val = 3; break; Chris@216: case 4000: val = 4; break; Chris@216: case 6000: val = 5; break; Chris@216: case 8000: val = 6; break; Chris@216: case 12000: val = 7; break; Chris@216: case 16000: val = 8; break; Chris@216: default: val = 9; break; Chris@0: } Chris@0: Chris@87: } else if (name == "Frequency Scale") { Chris@0: Chris@0: *min = 0; Chris@0: *max = 1; Chris@216: *deflt = int(LinearFrequencyScale); Chris@216: val = (int)m_frequencyScale; Chris@0: Chris@87: } else if (name == "Bin Display") { Chris@35: Chris@35: *min = 0; Chris@35: *max = 2; Chris@216: *deflt = int(AllBins); Chris@216: val = (int)m_binDisplay; Chris@35: Chris@862: } else if (name == "Normalization") { Chris@36: Chris@862: *min = 0; Chris@862: *max = 3; Chris@862: *deflt = int(NoNormalization); Chris@862: val = (int)m_normalization; Chris@120: Chris@0: } else { Chris@216: val = Layer::getPropertyRangeAndValue(name, min, max, deflt); Chris@0: } Chris@0: Chris@216: return val; Chris@0: } Chris@0: Chris@0: QString Chris@0: SpectrogramLayer::getPropertyValueLabel(const PropertyName &name, Chris@9: int value) const Chris@0: { Chris@87: if (name == "Colour") { Chris@196: return ColourMapper::getColourMapName(value); Chris@0: } Chris@87: if (name == "Colour Scale") { Chris@0: switch (value) { Chris@0: default: Chris@37: case 0: return tr("Linear"); Chris@37: case 1: return tr("Meter"); Chris@215: case 2: return tr("dBV^2"); Chris@215: case 3: return tr("dBV"); Chris@119: case 4: return tr("Phase"); Chris@0: } Chris@0: } Chris@862: if (name == "Normalization") { Chris@862: return ""; // icon only Chris@862: } Chris@87: if (name == "Window Size") { Chris@0: return QString("%1").arg(32 << value); Chris@0: } Chris@97: if (name == "Window Increment") { Chris@0: switch (value) { Chris@0: default: Chris@112: case 0: return tr("None"); Chris@112: case 1: return tr("25 %"); Chris@112: case 2: return tr("50 %"); Chris@112: case 3: return tr("75 %"); Chris@112: case 4: return tr("87.5 %"); Chris@112: case 5: return tr("93.75 %"); Chris@0: } Chris@0: } Chris@109: if (name == "Zero Padding") { Chris@109: if (value == 0) return tr("None"); Chris@109: return QString("%1x").arg(value + 1); Chris@109: } Chris@87: if (name == "Min Frequency") { Chris@37: switch (value) { Chris@37: default: Chris@38: case 0: return tr("No min"); Chris@37: case 1: return tr("10 Hz"); Chris@37: case 2: return tr("20 Hz"); Chris@37: case 3: return tr("40 Hz"); Chris@37: case 4: return tr("100 Hz"); Chris@37: case 5: return tr("250 Hz"); Chris@37: case 6: return tr("500 Hz"); Chris@37: case 7: return tr("1 KHz"); Chris@37: case 8: return tr("4 KHz"); Chris@37: case 9: return tr("10 KHz"); Chris@37: } Chris@37: } Chris@87: if (name == "Max Frequency") { Chris@0: switch (value) { Chris@0: default: Chris@0: case 0: return tr("500 Hz"); Chris@0: case 1: return tr("1 KHz"); Chris@0: case 2: return tr("1.5 KHz"); Chris@0: case 3: return tr("2 KHz"); Chris@0: case 4: return tr("4 KHz"); Chris@0: case 5: return tr("6 KHz"); Chris@0: case 6: return tr("8 KHz"); Chris@0: case 7: return tr("12 KHz"); Chris@0: case 8: return tr("16 KHz"); Chris@38: case 9: return tr("No max"); Chris@0: } Chris@0: } Chris@87: if (name == "Frequency Scale") { Chris@0: switch (value) { Chris@0: default: Chris@0: case 0: return tr("Linear"); Chris@0: case 1: return tr("Log"); Chris@0: } Chris@0: } Chris@87: if (name == "Bin Display") { Chris@35: switch (value) { Chris@35: default: Chris@37: case 0: return tr("All Bins"); Chris@37: case 1: return tr("Peak Bins"); Chris@37: case 2: return tr("Frequencies"); Chris@35: } Chris@35: } Chris@0: return tr(""); Chris@0: } Chris@0: Chris@862: QString Chris@862: SpectrogramLayer::getPropertyValueIconName(const PropertyName &name, Chris@862: int value) const Chris@862: { Chris@862: if (name == "Normalization") { Chris@862: switch(value) { Chris@862: default: Chris@862: case 0: return "normalise-none"; Chris@862: case 1: return "normalise-columns"; Chris@862: case 2: return "normalise"; Chris@862: case 3: return "normalise-hybrid"; Chris@862: } Chris@862: } Chris@862: return ""; Chris@862: } Chris@862: Chris@167: RangeMapper * Chris@167: SpectrogramLayer::getNewPropertyRangeMapper(const PropertyName &name) const Chris@167: { Chris@167: if (name == "Gain") { Chris@167: return new LinearRangeMapper(-50, 50, -25, 25, tr("dB")); Chris@167: } Chris@167: if (name == "Threshold") { Chris@167: return new LinearRangeMapper(-50, 0, -50, 0, tr("dB")); Chris@167: } Chris@167: return 0; Chris@167: } Chris@167: Chris@0: void Chris@0: SpectrogramLayer::setProperty(const PropertyName &name, int value) Chris@0: { Chris@87: if (name == "Gain") { Chris@906: setGain(float(pow(10, float(value)/20.0))); Chris@87: } else if (name == "Threshold") { Chris@37: if (value == -50) setThreshold(0.0); Chris@906: else setThreshold(float(AudioLevel::dB_to_multiplier(value))); Chris@87: } else if (name == "Colour Rotation") { Chris@9: setColourRotation(value); Chris@87: } else if (name == "Colour") { Chris@197: setColourMap(value); Chris@87: } else if (name == "Window Size") { Chris@0: setWindowSize(32 << value); Chris@97: } else if (name == "Window Increment") { Chris@97: setWindowHopLevel(value); Chris@109: } else if (name == "Zero Padding") { Chris@109: setZeroPadLevel(value > 0.1 ? 3 : 0); Chris@87: } else if (name == "Min Frequency") { Chris@37: switch (value) { Chris@37: default: Chris@37: case 0: setMinFrequency(0); break; Chris@37: case 1: setMinFrequency(10); break; Chris@37: case 2: setMinFrequency(20); break; Chris@37: case 3: setMinFrequency(40); break; Chris@37: case 4: setMinFrequency(100); break; Chris@37: case 5: setMinFrequency(250); break; Chris@37: case 6: setMinFrequency(500); break; Chris@37: case 7: setMinFrequency(1000); break; Chris@37: case 8: setMinFrequency(4000); break; Chris@37: case 9: setMinFrequency(10000); break; Chris@37: } Chris@133: int vs = getCurrentVerticalZoomStep(); Chris@133: if (vs != m_lastEmittedZoomStep) { Chris@133: emit verticalZoomChanged(); Chris@133: m_lastEmittedZoomStep = vs; Chris@133: } Chris@87: } else if (name == "Max Frequency") { Chris@0: switch (value) { Chris@0: case 0: setMaxFrequency(500); break; Chris@0: case 1: setMaxFrequency(1000); break; Chris@0: case 2: setMaxFrequency(1500); break; Chris@0: case 3: setMaxFrequency(2000); break; Chris@0: case 4: setMaxFrequency(4000); break; Chris@0: case 5: setMaxFrequency(6000); break; Chris@0: case 6: setMaxFrequency(8000); break; Chris@0: case 7: setMaxFrequency(12000); break; Chris@0: case 8: setMaxFrequency(16000); break; Chris@0: default: Chris@0: case 9: setMaxFrequency(0); break; Chris@0: } Chris@133: int vs = getCurrentVerticalZoomStep(); Chris@133: if (vs != m_lastEmittedZoomStep) { Chris@133: emit verticalZoomChanged(); Chris@133: m_lastEmittedZoomStep = vs; Chris@133: } Chris@87: } else if (name == "Colour Scale") { Chris@0: switch (value) { Chris@0: default: Chris@0: case 0: setColourScale(LinearColourScale); break; Chris@0: case 1: setColourScale(MeterColourScale); break; Chris@215: case 2: setColourScale(dBSquaredColourScale); break; Chris@215: case 3: setColourScale(dBColourScale); break; Chris@119: case 4: setColourScale(PhaseColourScale); break; Chris@0: } Chris@87: } else if (name == "Frequency Scale") { Chris@0: switch (value) { Chris@0: default: Chris@0: case 0: setFrequencyScale(LinearFrequencyScale); break; Chris@0: case 1: setFrequencyScale(LogFrequencyScale); break; Chris@0: } Chris@87: } else if (name == "Bin Display") { Chris@35: switch (value) { Chris@35: default: Chris@37: case 0: setBinDisplay(AllBins); break; Chris@37: case 1: setBinDisplay(PeakBins); break; Chris@37: case 2: setBinDisplay(PeakFrequencies); break; Chris@35: } Chris@862: } else if (name == "Normalization") { Chris@862: switch (value) { Chris@862: default: Chris@862: case 0: setNormalization(NoNormalization); break; Chris@862: case 1: setNormalization(NormalizeColumns); break; Chris@862: case 2: setNormalization(NormalizeVisibleArea); break; Chris@862: case 3: setNormalization(NormalizeHybrid); break; Chris@862: } Chris@0: } Chris@0: } Chris@0: Chris@0: void Chris@478: SpectrogramLayer::invalidateImageCaches() Chris@95: { Chris@478: for (ViewImageCache::iterator i = m_imageCaches.begin(); Chris@478: i != m_imageCaches.end(); ++i) { Chris@95: i->second.validArea = QRect(); Chris@95: } Chris@95: } Chris@95: Chris@95: void Chris@906: SpectrogramLayer::invalidateImageCaches(sv_frame_t startFrame, sv_frame_t endFrame) Chris@95: { Chris@478: for (ViewImageCache::iterator i = m_imageCaches.begin(); Chris@478: i != m_imageCaches.end(); ++i) { Chris@131: Chris@95: //!!! when are views removed from the map? on setLayerDormant? Chris@918: const LayerGeometryProvider *v = i->first; Chris@95: Chris@391: #ifdef DEBUG_SPECTROGRAM_REPAINT Chris@985: cerr << "SpectrogramLayer::invalidateImageCaches(" Chris@391: << startFrame << ", " << endFrame << "): view range is " Chris@391: << v->getStartFrame() << ", " << v->getEndFrame() Chris@585: << endl; Chris@391: Chris@682: cerr << "Valid area was: " << i->second.validArea.x() << ", " Chris@391: << i->second.validArea.y() << " " Chris@391: << i->second.validArea.width() << "x" Chris@682: << i->second.validArea.height() << endl; Chris@391: #endif Chris@391: Chris@806: if (int(startFrame) > v->getStartFrame()) { Chris@391: if (startFrame >= v->getEndFrame()) { Chris@391: #ifdef DEBUG_SPECTROGRAM_REPAINT Chris@682: cerr << "Modified start frame is off right of view" << endl; Chris@391: #endif Chris@391: return; Chris@391: } Chris@391: int x = v->getXForFrame(startFrame); Chris@407: #ifdef DEBUG_SPECTROGRAM_REPAINT Chris@985: cerr << "clipping from 0 to " << x-1 << endl; Chris@407: #endif Chris@391: if (x > 1) { Chris@391: i->second.validArea &= Chris@918: QRect(0, 0, x-1, v->getPaintHeight()); Chris@391: } else { Chris@391: i->second.validArea = QRect(); Chris@391: } Chris@391: } else { Chris@806: if (int(endFrame) < v->getStartFrame()) { Chris@391: #ifdef DEBUG_SPECTROGRAM_REPAINT Chris@682: cerr << "Modified end frame is off left of view" << endl; Chris@391: #endif Chris@391: return; Chris@391: } Chris@391: int x = v->getXForFrame(endFrame); Chris@391: #ifdef DEBUG_SPECTROGRAM_REPAINT Chris@988: cerr << "clipping from " << x+1 << " to " << v->getPaintWidth() Chris@585: << endl; Chris@391: #endif Chris@918: if (x < v->getPaintWidth()) { Chris@391: i->second.validArea &= Chris@918: QRect(x+1, 0, v->getPaintWidth()-(x+1), v->getPaintHeight()); Chris@391: } else { Chris@391: i->second.validArea = QRect(); Chris@391: } Chris@95: } Chris@391: Chris@391: #ifdef DEBUG_SPECTROGRAM_REPAINT Chris@682: cerr << "Valid area is now: " << i->second.validArea.x() << ", " Chris@391: << i->second.validArea.y() << " " Chris@391: << i->second.validArea.width() << "x" Chris@682: << i->second.validArea.height() << endl; Chris@391: #endif Chris@95: } Chris@95: } Chris@95: Chris@95: void Chris@122: SpectrogramLayer::preferenceChanged(PropertyContainer::PropertyName name) Chris@122: { Chris@587: SVDEBUG << "SpectrogramLayer::preferenceChanged(" << name << ")" << endl; Chris@122: Chris@122: if (name == "Window Type") { Chris@122: setWindowType(Preferences::getInstance()->getWindowType()); Chris@122: return; Chris@122: } Chris@490: if (name == "Spectrogram Y Smoothing") { Chris@490: invalidateImageCaches(); Chris@490: invalidateMagnitudes(); Chris@490: emit layerParametersChanged(); Chris@490: } Chris@490: if (name == "Spectrogram X Smoothing") { Chris@478: invalidateImageCaches(); Chris@122: invalidateMagnitudes(); Chris@122: emit layerParametersChanged(); Chris@122: } Chris@122: if (name == "Tuning Frequency") { Chris@122: emit layerParametersChanged(); Chris@122: } Chris@122: } Chris@122: Chris@122: void Chris@0: SpectrogramLayer::setChannel(int ch) Chris@0: { Chris@0: if (m_channel == ch) return; Chris@0: Chris@478: invalidateImageCaches(); Chris@0: m_channel = ch; Chris@130: invalidateFFTModels(); Chris@9: Chris@0: emit layerParametersChanged(); Chris@0: } Chris@0: Chris@0: int Chris@0: SpectrogramLayer::getChannel() const Chris@0: { Chris@0: return m_channel; Chris@0: } Chris@0: Chris@0: void Chris@805: SpectrogramLayer::setWindowSize(int ws) Chris@0: { Chris@0: if (m_windowSize == ws) return; Chris@0: Chris@478: invalidateImageCaches(); Chris@0: Chris@0: m_windowSize = ws; Chris@109: m_fftSize = ws * (m_zeroPadLevel + 1); Chris@0: Chris@130: invalidateFFTModels(); Chris@9: Chris@9: emit layerParametersChanged(); Chris@0: } Chris@0: Chris@805: int Chris@0: SpectrogramLayer::getWindowSize() const Chris@0: { Chris@0: return m_windowSize; Chris@0: } Chris@0: Chris@0: void Chris@805: SpectrogramLayer::setWindowHopLevel(int v) Chris@0: { Chris@97: if (m_windowHopLevel == v) return; Chris@0: Chris@478: invalidateImageCaches(); Chris@0: Chris@97: m_windowHopLevel = v; Chris@0: Chris@130: invalidateFFTModels(); Chris@9: Chris@9: emit layerParametersChanged(); Chris@9: Chris@110: // fillCache(); Chris@0: } Chris@0: Chris@805: int Chris@97: SpectrogramLayer::getWindowHopLevel() const Chris@0: { Chris@97: return m_windowHopLevel; Chris@0: } Chris@0: Chris@0: void Chris@805: SpectrogramLayer::setZeroPadLevel(int v) Chris@109: { Chris@109: if (m_zeroPadLevel == v) return; Chris@109: Chris@478: invalidateImageCaches(); Chris@109: Chris@109: m_zeroPadLevel = v; Chris@109: m_fftSize = m_windowSize * (v + 1); Chris@110: Chris@130: invalidateFFTModels(); Chris@109: Chris@109: emit layerParametersChanged(); Chris@109: } Chris@109: Chris@805: int Chris@109: SpectrogramLayer::getZeroPadLevel() const Chris@109: { Chris@109: return m_zeroPadLevel; Chris@109: } Chris@109: Chris@109: void Chris@0: SpectrogramLayer::setWindowType(WindowType w) Chris@0: { Chris@0: if (m_windowType == w) return; Chris@0: Chris@478: invalidateImageCaches(); Chris@0: Chris@0: m_windowType = w; Chris@110: Chris@130: invalidateFFTModels(); Chris@9: Chris@9: emit layerParametersChanged(); Chris@0: } Chris@0: Chris@0: WindowType Chris@0: SpectrogramLayer::getWindowType() const Chris@0: { Chris@0: return m_windowType; Chris@0: } Chris@0: Chris@0: void Chris@0: SpectrogramLayer::setGain(float gain) Chris@0: { Chris@587: // SVDEBUG << "SpectrogramLayer::setGain(" << gain << ") (my gain is now " Chris@585: // << m_gain << ")" << endl; Chris@55: Chris@40: if (m_gain == gain) return; Chris@0: Chris@478: invalidateImageCaches(); Chris@0: Chris@0: m_gain = gain; Chris@0: Chris@9: emit layerParametersChanged(); Chris@0: } Chris@0: Chris@0: float Chris@0: SpectrogramLayer::getGain() const Chris@0: { Chris@0: return m_gain; Chris@0: } Chris@0: Chris@0: void Chris@37: SpectrogramLayer::setThreshold(float threshold) Chris@37: { Chris@40: if (m_threshold == threshold) return; Chris@37: Chris@478: invalidateImageCaches(); Chris@37: Chris@37: m_threshold = threshold; Chris@37: Chris@37: emit layerParametersChanged(); Chris@37: } Chris@37: Chris@37: float Chris@37: SpectrogramLayer::getThreshold() const Chris@37: { Chris@37: return m_threshold; Chris@37: } Chris@37: Chris@37: void Chris@805: SpectrogramLayer::setMinFrequency(int mf) Chris@37: { Chris@37: if (m_minFrequency == mf) return; Chris@37: Chris@587: // SVDEBUG << "SpectrogramLayer::setMinFrequency: " << mf << endl; Chris@187: Chris@478: invalidateImageCaches(); Chris@119: invalidateMagnitudes(); Chris@37: Chris@37: m_minFrequency = mf; Chris@37: Chris@37: emit layerParametersChanged(); Chris@37: } Chris@37: Chris@805: int Chris@37: SpectrogramLayer::getMinFrequency() const Chris@37: { Chris@37: return m_minFrequency; Chris@37: } Chris@37: Chris@37: void Chris@805: SpectrogramLayer::setMaxFrequency(int mf) Chris@0: { Chris@0: if (m_maxFrequency == mf) return; Chris@0: Chris@587: // SVDEBUG << "SpectrogramLayer::setMaxFrequency: " << mf << endl; Chris@187: Chris@478: invalidateImageCaches(); Chris@119: invalidateMagnitudes(); Chris@0: Chris@0: m_maxFrequency = mf; Chris@0: Chris@9: emit layerParametersChanged(); Chris@0: } Chris@0: Chris@805: int Chris@0: SpectrogramLayer::getMaxFrequency() const Chris@0: { Chris@0: return m_maxFrequency; Chris@0: } Chris@0: Chris@0: void Chris@9: SpectrogramLayer::setColourRotation(int r) Chris@9: { Chris@478: invalidateImageCaches(); Chris@9: Chris@9: if (r < 0) r = 0; Chris@9: if (r > 256) r = 256; Chris@9: int distance = r - m_colourRotation; Chris@9: Chris@9: if (distance != 0) { Chris@197: rotatePalette(-distance); Chris@9: m_colourRotation = r; Chris@9: } Chris@9: Chris@9: emit layerParametersChanged(); Chris@9: } Chris@9: Chris@9: void Chris@0: SpectrogramLayer::setColourScale(ColourScale colourScale) Chris@0: { Chris@0: if (m_colourScale == colourScale) return; Chris@0: Chris@478: invalidateImageCaches(); Chris@0: Chris@0: m_colourScale = colourScale; Chris@0: Chris@9: emit layerParametersChanged(); Chris@0: } Chris@0: Chris@0: SpectrogramLayer::ColourScale Chris@0: SpectrogramLayer::getColourScale() const Chris@0: { Chris@0: return m_colourScale; Chris@0: } Chris@0: Chris@0: void Chris@197: SpectrogramLayer::setColourMap(int map) Chris@0: { Chris@197: if (m_colourMap == map) return; Chris@0: Chris@478: invalidateImageCaches(); Chris@0: Chris@197: m_colourMap = map; Chris@197: initialisePalette(); Chris@9: Chris@0: emit layerParametersChanged(); Chris@0: } Chris@0: Chris@196: int Chris@197: SpectrogramLayer::getColourMap() const Chris@0: { Chris@197: return m_colourMap; Chris@0: } Chris@0: Chris@0: void Chris@0: SpectrogramLayer::setFrequencyScale(FrequencyScale frequencyScale) Chris@0: { Chris@0: if (m_frequencyScale == frequencyScale) return; Chris@0: Chris@478: invalidateImageCaches(); Chris@0: m_frequencyScale = frequencyScale; Chris@9: Chris@9: emit layerParametersChanged(); Chris@0: } Chris@0: Chris@0: SpectrogramLayer::FrequencyScale Chris@0: SpectrogramLayer::getFrequencyScale() const Chris@0: { Chris@0: return m_frequencyScale; Chris@0: } Chris@0: Chris@0: void Chris@37: SpectrogramLayer::setBinDisplay(BinDisplay binDisplay) Chris@35: { Chris@37: if (m_binDisplay == binDisplay) return; Chris@35: Chris@478: invalidateImageCaches(); Chris@37: m_binDisplay = binDisplay; Chris@35: Chris@35: emit layerParametersChanged(); Chris@35: } Chris@35: Chris@37: SpectrogramLayer::BinDisplay Chris@37: SpectrogramLayer::getBinDisplay() const Chris@35: { Chris@37: return m_binDisplay; Chris@35: } Chris@35: Chris@35: void Chris@862: SpectrogramLayer::setNormalization(Normalization n) Chris@36: { Chris@862: if (m_normalization == n) return; Chris@36: Chris@478: invalidateImageCaches(); Chris@119: invalidateMagnitudes(); Chris@862: m_normalization = n; Chris@36: Chris@36: emit layerParametersChanged(); Chris@36: } Chris@36: Chris@862: SpectrogramLayer::Normalization Chris@862: SpectrogramLayer::getNormalization() const Chris@36: { Chris@862: return m_normalization; Chris@36: } Chris@36: Chris@36: void Chris@918: SpectrogramLayer::setLayerDormant(const LayerGeometryProvider *v, bool dormant) Chris@29: { Chris@33: if (dormant) { Chris@33: Chris@331: #ifdef DEBUG_SPECTROGRAM_REPAINT Chris@985: cerr << "SpectrogramLayer::setLayerDormant(" << dormant << ")" Chris@585: << endl; Chris@331: #endif Chris@331: Chris@131: if (isLayerDormant(v)) { Chris@131: return; Chris@131: } Chris@131: Chris@131: Layer::setLayerDormant(v, true); Chris@33: Chris@920: const View *view = v->getView(); Chris@920: Chris@478: invalidateImageCaches(); Chris@988: Chris@920: m_imageCaches.erase(view); Chris@920: Chris@920: if (m_fftModels.find(view) != m_fftModels.end()) { Chris@920: Chris@988: if (m_sliceableModel == m_fftModels[view]) { Chris@193: bool replaced = false; Chris@193: for (ViewFFTMap::iterator i = m_fftModels.begin(); Chris@193: i != m_fftModels.end(); ++i) { Chris@985: if (i->second != m_sliceableModel) { Chris@985: emit sliceableModelReplaced(m_sliceableModel, i->second); Chris@193: replaced = true; Chris@193: break; Chris@193: } Chris@193: } Chris@193: if (!replaced) emit sliceableModelReplaced(m_sliceableModel, 0); Chris@193: } Chris@193: Chris@988: delete m_fftModels[view]; Chris@920: m_fftModels.erase(view); Chris@920: Chris@920: delete m_peakCaches[view]; Chris@920: m_peakCaches.erase(view); Chris@114: } Chris@33: Chris@33: } else { Chris@33: Chris@131: Layer::setLayerDormant(v, false); Chris@33: } Chris@29: } Chris@29: Chris@29: void Chris@0: SpectrogramLayer::cacheInvalid() Chris@0: { Chris@391: #ifdef DEBUG_SPECTROGRAM_REPAINT Chris@985: cerr << "SpectrogramLayer::cacheInvalid()" << endl; Chris@391: #endif Chris@391: Chris@478: invalidateImageCaches(); Chris@119: invalidateMagnitudes(); Chris@0: } Chris@0: Chris@0: void Chris@906: SpectrogramLayer::cacheInvalid(sv_frame_t from, sv_frame_t to) Chris@0: { Chris@391: #ifdef DEBUG_SPECTROGRAM_REPAINT Chris@985: cerr << "SpectrogramLayer::cacheInvalid(" << from << ", " << to << ")" << endl; Chris@391: #endif Chris@391: Chris@478: invalidateImageCaches(from, to); Chris@391: invalidateMagnitudes(); Chris@0: } Chris@0: Chris@224: bool Chris@224: SpectrogramLayer::hasLightBackground() const Chris@224: { Chris@287: return ColourMapper(m_colourMap, 1.f, 255.f).hasLightBackground(); Chris@224: } Chris@224: Chris@0: void Chris@197: SpectrogramLayer::initialisePalette() Chris@0: { Chris@10: int formerRotation = m_colourRotation; Chris@10: Chris@197: if (m_colourMap == (int)ColourMapper::BlackOnWhite) { Chris@197: m_palette.setColour(NO_VALUE, Qt::white); Chris@38: } else { Chris@197: m_palette.setColour(NO_VALUE, Qt::black); Chris@38: } Chris@0: Chris@197: ColourMapper mapper(m_colourMap, 1.f, 255.f); Chris@196: Chris@0: for (int pixel = 1; pixel < 256; ++pixel) { Chris@907: m_palette.setColour((unsigned char)pixel, mapper.map(pixel)); Chris@0: } Chris@9: Chris@196: m_crosshairColour = mapper.getContrastingColour(); Chris@196: Chris@9: m_colourRotation = 0; Chris@197: rotatePalette(m_colourRotation - formerRotation); Chris@10: m_colourRotation = formerRotation; Chris@478: Chris@478: m_drawBuffer = QImage(); Chris@9: } Chris@9: Chris@9: void Chris@197: SpectrogramLayer::rotatePalette(int distance) Chris@9: { Chris@31: QColor newPixels[256]; Chris@9: Chris@197: newPixels[NO_VALUE] = m_palette.getColour(NO_VALUE); Chris@9: Chris@9: for (int pixel = 1; pixel < 256; ++pixel) { Chris@9: int target = pixel + distance; Chris@9: while (target < 1) target += 255; Chris@9: while (target > 255) target -= 255; Chris@907: newPixels[target] = m_palette.getColour((unsigned char)pixel); Chris@9: } Chris@9: Chris@9: for (int pixel = 0; pixel < 256; ++pixel) { Chris@907: m_palette.setColour((unsigned char)pixel, newPixels[pixel]); Chris@9: } Chris@478: Chris@478: m_drawBuffer = QImage(); Chris@0: } Chris@0: Chris@38: unsigned char Chris@918: SpectrogramLayer::getDisplayValue(LayerGeometryProvider *v, double input) const Chris@38: { Chris@38: int value; Chris@37: Chris@907: double min = 0.0; Chris@907: double max = 1.0; Chris@120: Chris@862: if (m_normalization == NormalizeVisibleArea) { Chris@120: min = m_viewMags[v].getMin(); Chris@120: max = m_viewMags[v].getMax(); Chris@862: } else if (m_normalization != NormalizeColumns) { Chris@224: if (m_colourScale == LinearColourScale //|| Chris@224: // m_colourScale == MeterColourScale) { Chris@224: ) { Chris@907: max = 0.1; Chris@120: } Chris@120: } Chris@120: Chris@907: double thresh = -80.0; Chris@907: Chris@907: if (max == 0.0) max = 1.0; Chris@907: if (max == min) min = max - 0.0001; Chris@119: Chris@40: switch (m_colourScale) { Chris@40: Chris@40: default: Chris@40: case LinearColourScale: Chris@907: value = int(((input - min) / (max - min)) * 255.0) + 1; Chris@40: break; Chris@40: Chris@40: case MeterColourScale: Chris@210: value = AudioLevel::multiplier_to_preview Chris@210: ((input - min) / (max - min), 254) + 1; Chris@40: break; Chris@119: Chris@210: case dBSquaredColourScale: Chris@215: input = ((input - min) * (input - min)) / ((max - min) * (max - min)); Chris@907: if (input > 0.0) { Chris@907: input = 10.0 * log10(input); Chris@133: } else { Chris@133: input = thresh; Chris@133: } Chris@907: if (min > 0.0) { Chris@907: thresh = 10.0 * log10(min * min); Chris@907: if (thresh < -80.0) thresh = -80.0; Chris@119: } Chris@119: input = (input - thresh) / (-thresh); Chris@907: if (input < 0.0) input = 0.0; Chris@907: if (input > 1.0) input = 1.0; Chris@907: value = int(input * 255.0) + 1; Chris@119: break; Chris@40: Chris@215: case dBColourScale: Chris@215: //!!! experiment with normalizing the visible area this way. Chris@215: //In any case, we need to have some indication of what the dB Chris@215: //scale is relative to. Chris@215: input = (input - min) / (max - min); Chris@907: if (input > 0.0) { Chris@907: input = 10.0 * log10(input); Chris@215: } else { Chris@215: input = thresh; Chris@215: } Chris@907: if (min > 0.0) { Chris@907: thresh = 10.0 * log10(min); Chris@907: if (thresh < -80.0) thresh = -80.0; Chris@215: } Chris@215: input = (input - thresh) / (-thresh); Chris@907: if (input < 0.0) input = 0.0; Chris@907: if (input > 1.0) input = 1.0; Chris@907: value = int(input * 255.0) + 1; Chris@215: break; Chris@215: Chris@40: case PhaseColourScale: Chris@40: value = int((input * 127.0 / M_PI) + 128); Chris@40: break; Chris@0: } Chris@210: Chris@38: if (value > UCHAR_MAX) value = UCHAR_MAX; Chris@38: if (value < 0) value = 0; Chris@907: return (unsigned char)value; Chris@0: } Chris@0: Chris@905: double Chris@40: SpectrogramLayer::getEffectiveMinFrequency() const Chris@40: { Chris@907: sv_samplerate_t sr = m_model->getSampleRate(); Chris@905: double minf = double(sr) / m_fftSize; Chris@40: Chris@40: if (m_minFrequency > 0.0) { Chris@805: int minbin = int((double(m_minFrequency) * m_fftSize) / sr + 0.01); Chris@40: if (minbin < 1) minbin = 1; Chris@107: minf = minbin * sr / m_fftSize; Chris@40: } Chris@40: Chris@40: return minf; Chris@40: } Chris@40: Chris@905: double Chris@40: SpectrogramLayer::getEffectiveMaxFrequency() const Chris@40: { Chris@907: sv_samplerate_t sr = m_model->getSampleRate(); Chris@905: double maxf = double(sr) / 2; Chris@40: Chris@40: if (m_maxFrequency > 0.0) { Chris@805: int maxbin = int((double(m_maxFrequency) * m_fftSize) / sr + 0.1); Chris@107: if (maxbin > m_fftSize / 2) maxbin = m_fftSize / 2; Chris@107: maxf = maxbin * sr / m_fftSize; Chris@40: } Chris@40: Chris@40: return maxf; Chris@40: } Chris@40: Chris@0: bool Chris@918: SpectrogramLayer::getYBinRange(LayerGeometryProvider *v, int y, double &q0, double &q1) const Chris@0: { Chris@382: Profiler profiler("SpectrogramLayer::getYBinRange"); Chris@382: Chris@918: int h = v->getPaintHeight(); Chris@0: if (y < 0 || y >= h) return false; Chris@0: Chris@907: sv_samplerate_t sr = m_model->getSampleRate(); Chris@905: double minf = getEffectiveMinFrequency(); Chris@905: double maxf = getEffectiveMaxFrequency(); Chris@0: Chris@38: bool logarithmic = (m_frequencyScale == LogFrequencyScale); Chris@38: Chris@44: q0 = v->getFrequencyForY(y, minf, maxf, logarithmic); Chris@44: q1 = v->getFrequencyForY(y - 1, minf, maxf, logarithmic); Chris@38: Chris@490: // Now map these on to ("proportions of") actual bins, using raw Chris@490: // FFT size (unsmoothed) Chris@490: Chris@490: q0 = (q0 * m_fftSize) / sr; Chris@490: q1 = (q1 * m_fftSize) / sr; Chris@0: Chris@0: return true; Chris@0: } Chris@486: Chris@486: bool Chris@918: SpectrogramLayer::getSmoothedYBinRange(LayerGeometryProvider *v, int y, double &q0, double &q1) const Chris@486: { Chris@486: Profiler profiler("SpectrogramLayer::getSmoothedYBinRange"); Chris@486: Chris@918: int h = v->getPaintHeight(); Chris@486: if (y < 0 || y >= h) return false; Chris@486: Chris@907: sv_samplerate_t sr = m_model->getSampleRate(); Chris@905: double minf = getEffectiveMinFrequency(); Chris@905: double maxf = getEffectiveMaxFrequency(); Chris@486: Chris@486: bool logarithmic = (m_frequencyScale == LogFrequencyScale); Chris@486: Chris@486: q0 = v->getFrequencyForY(y, minf, maxf, logarithmic); Chris@486: q1 = v->getFrequencyForY(y - 1, minf, maxf, logarithmic); Chris@486: Chris@490: // Now map these on to ("proportions of") actual bins, using raw Chris@490: // FFT size (unsmoothed) Chris@490: Chris@490: q0 = (q0 * getFFTSize(v)) / sr; Chris@490: q1 = (q1 * getFFTSize(v)) / sr; Chris@486: Chris@486: return true; Chris@486: } Chris@38: Chris@0: bool Chris@918: SpectrogramLayer::getXBinRange(LayerGeometryProvider *v, int x, double &s0, double &s1) const Chris@0: { Chris@907: sv_frame_t modelStart = m_model->getStartFrame(); Chris@907: sv_frame_t modelEnd = m_model->getEndFrame(); Chris@0: Chris@0: // Each pixel column covers an exact range of sample frames: Chris@907: sv_frame_t f0 = v->getFrameForX(x) - modelStart; Chris@907: sv_frame_t f1 = v->getFrameForX(x + 1) - modelStart - 1; Chris@20: Chris@41: if (f1 < int(modelStart) || f0 > int(modelEnd)) { Chris@41: return false; Chris@41: } Chris@20: Chris@0: // And that range may be drawn from a possibly non-integral Chris@0: // range of spectrogram windows: Chris@0: Chris@805: int windowIncrement = getWindowIncrement(); Chris@905: s0 = double(f0) / windowIncrement; Chris@905: s1 = double(f1) / windowIncrement; Chris@0: Chris@0: return true; Chris@0: } Chris@0: Chris@0: bool Chris@918: SpectrogramLayer::getXBinSourceRange(LayerGeometryProvider *v, int x, RealTime &min, RealTime &max) const Chris@0: { Chris@905: double s0 = 0, s1 = 0; Chris@44: if (!getXBinRange(v, x, s0, s1)) return false; Chris@0: Chris@0: int s0i = int(s0 + 0.001); Chris@0: int s1i = int(s1); Chris@0: Chris@0: int windowIncrement = getWindowIncrement(); Chris@0: int w0 = s0i * windowIncrement - (m_windowSize - windowIncrement)/2; Chris@0: int w1 = s1i * windowIncrement + windowIncrement + Chris@0: (m_windowSize - windowIncrement)/2 - 1; Chris@0: Chris@0: min = RealTime::frame2RealTime(w0, m_model->getSampleRate()); Chris@0: max = RealTime::frame2RealTime(w1, m_model->getSampleRate()); Chris@0: return true; Chris@0: } Chris@0: Chris@0: bool Chris@918: SpectrogramLayer::getYBinSourceRange(LayerGeometryProvider *v, int y, double &freqMin, double &freqMax) Chris@0: const Chris@0: { Chris@905: double q0 = 0, q1 = 0; Chris@44: if (!getYBinRange(v, y, q0, q1)) return false; Chris@0: Chris@0: int q0i = int(q0 + 0.001); Chris@0: int q1i = int(q1); Chris@0: Chris@907: sv_samplerate_t sr = m_model->getSampleRate(); Chris@0: Chris@0: for (int q = q0i; q <= q1i; ++q) { Chris@121: if (q == q0i) freqMin = (sr * q) / m_fftSize; Chris@121: if (q == q1i) freqMax = (sr * (q+1)) / m_fftSize; Chris@0: } Chris@0: return true; Chris@0: } Chris@35: Chris@35: bool Chris@918: SpectrogramLayer::getAdjustedYBinSourceRange(LayerGeometryProvider *v, int x, int y, Chris@905: double &freqMin, double &freqMax, Chris@905: double &adjFreqMin, double &adjFreqMax) Chris@35: const Chris@35: { Chris@277: if (!m_model || !m_model->isOK() || !m_model->isReady()) { Chris@277: return false; Chris@277: } Chris@277: Chris@130: FFTModel *fft = getFFTModel(v); Chris@114: if (!fft) return false; Chris@110: Chris@905: double s0 = 0, s1 = 0; Chris@44: if (!getXBinRange(v, x, s0, s1)) return false; Chris@35: Chris@905: double q0 = 0, q1 = 0; Chris@44: if (!getYBinRange(v, y, q0, q1)) return false; Chris@35: Chris@35: int s0i = int(s0 + 0.001); Chris@35: int s1i = int(s1); Chris@35: Chris@35: int q0i = int(q0 + 0.001); Chris@35: int q1i = int(q1); Chris@35: Chris@907: sv_samplerate_t sr = m_model->getSampleRate(); Chris@35: Chris@35: bool haveAdj = false; Chris@35: Chris@37: bool peaksOnly = (m_binDisplay == PeakBins || Chris@37: m_binDisplay == PeakFrequencies); Chris@37: Chris@35: for (int q = q0i; q <= q1i; ++q) { Chris@35: Chris@35: for (int s = s0i; s <= s1i; ++s) { Chris@35: Chris@160: if (!fft->isColumnAvailable(s)) continue; Chris@117: Chris@905: double binfreq = (double(sr) * q) / m_windowSize; Chris@35: if (q == q0i) freqMin = binfreq; Chris@35: if (q == q1i) freqMax = binfreq; Chris@37: Chris@114: if (peaksOnly && !fft->isLocalPeak(s, q)) continue; Chris@38: Chris@907: if (!fft->isOverThreshold(s, q, float(m_threshold * double(m_fftSize)/2.0))) continue; Chris@907: Chris@907: double freq = binfreq; Chris@40: Chris@114: if (s < int(fft->getWidth()) - 1) { Chris@38: Chris@277: fft->estimateStableFrequency(s, q, freq); Chris@35: Chris@38: if (!haveAdj || freq < adjFreqMin) adjFreqMin = freq; Chris@38: if (!haveAdj || freq > adjFreqMax) adjFreqMax = freq; Chris@35: Chris@35: haveAdj = true; Chris@35: } Chris@35: } Chris@35: } Chris@35: Chris@35: if (!haveAdj) { Chris@40: adjFreqMin = adjFreqMax = 0.0; Chris@35: } Chris@35: Chris@35: return haveAdj; Chris@35: } Chris@0: Chris@0: bool Chris@918: SpectrogramLayer::getXYBinSourceRange(LayerGeometryProvider *v, int x, int y, Chris@905: double &min, double &max, Chris@905: double &phaseMin, double &phaseMax) const Chris@0: { Chris@277: if (!m_model || !m_model->isOK() || !m_model->isReady()) { Chris@277: return false; Chris@277: } Chris@277: Chris@905: double q0 = 0, q1 = 0; Chris@44: if (!getYBinRange(v, y, q0, q1)) return false; Chris@0: Chris@905: double s0 = 0, s1 = 0; Chris@44: if (!getXBinRange(v, x, s0, s1)) return false; Chris@0: Chris@0: int q0i = int(q0 + 0.001); Chris@0: int q1i = int(q1); Chris@0: Chris@0: int s0i = int(s0 + 0.001); Chris@0: int s1i = int(s1); Chris@0: Chris@37: bool rv = false; Chris@37: Chris@805: int zp = getZeroPadLevel(v); Chris@122: q0i *= zp + 1; Chris@122: q1i *= zp + 1; Chris@122: Chris@130: FFTModel *fft = getFFTModel(v); Chris@0: Chris@114: if (fft) { Chris@114: Chris@114: int cw = fft->getWidth(); Chris@114: int ch = fft->getHeight(); Chris@0: Chris@110: min = 0.0; Chris@110: max = 0.0; Chris@110: phaseMin = 0.0; Chris@110: phaseMax = 0.0; Chris@110: bool have = false; Chris@0: Chris@110: for (int q = q0i; q <= q1i; ++q) { Chris@110: for (int s = s0i; s <= s1i; ++s) { Chris@110: if (s >= 0 && q >= 0 && s < cw && q < ch) { Chris@117: Chris@160: if (!fft->isColumnAvailable(s)) continue; Chris@110: Chris@905: double value; Chris@38: Chris@114: value = fft->getPhaseAt(s, q); Chris@110: if (!have || value < phaseMin) { phaseMin = value; } Chris@110: if (!have || value > phaseMax) { phaseMax = value; } Chris@91: Chris@907: value = fft->getMagnitudeAt(s, q) / (m_fftSize/2.0); Chris@110: if (!have || value < min) { min = value; } Chris@110: if (!have || value > max) { max = value; } Chris@110: Chris@110: have = true; Chris@110: } Chris@110: } Chris@110: } Chris@110: Chris@110: if (have) { Chris@110: rv = true; Chris@110: } Chris@0: } Chris@0: Chris@37: return rv; Chris@0: } Chris@0: Chris@805: int Chris@918: SpectrogramLayer::getZeroPadLevel(const LayerGeometryProvider *v) const Chris@114: { Chris@114: //!!! tidy all this stuff Chris@114: Chris@114: if (m_binDisplay != AllBins) return 0; Chris@221: Chris@221: Preferences::SpectrogramSmoothing smoothing = Chris@221: Preferences::getInstance()->getSpectrogramSmoothing(); Chris@221: Chris@221: if (smoothing == Preferences::NoSpectrogramSmoothing || Chris@221: smoothing == Preferences::SpectrogramInterpolated) return 0; Chris@221: Chris@114: if (m_frequencyScale == LogFrequencyScale) return 3; Chris@114: Chris@907: sv_samplerate_t sr = m_model->getSampleRate(); Chris@114: Chris@805: int maxbin = m_fftSize / 2; Chris@114: if (m_maxFrequency > 0) { Chris@184: maxbin = int((double(m_maxFrequency) * m_fftSize) / sr + 0.1); Chris@184: if (maxbin > m_fftSize / 2) maxbin = m_fftSize / 2; Chris@114: } Chris@114: Chris@805: int minbin = 1; Chris@114: if (m_minFrequency > 0) { Chris@114: minbin = int((double(m_minFrequency) * m_fftSize) / sr + 0.1); Chris@114: if (minbin < 1) minbin = 1; Chris@184: if (minbin >= maxbin) minbin = maxbin - 1; Chris@114: } Chris@114: Chris@905: double perPixel = Chris@918: double(v->getPaintHeight()) / Chris@905: double((maxbin - minbin) / (m_zeroPadLevel + 1)); Chris@118: Chris@118: if (perPixel > 2.8) { Chris@118: return 3; // 4x oversampling Chris@118: } else if (perPixel > 1.5) { Chris@118: return 1; // 2x Chris@114: } else { Chris@118: return 0; // 1x Chris@114: } Chris@114: } Chris@114: Chris@805: int Chris@918: SpectrogramLayer::getFFTSize(const LayerGeometryProvider *v) const Chris@114: { Chris@114: return m_fftSize * (getZeroPadLevel(v) + 1); Chris@114: } Chris@114: Chris@130: FFTModel * Chris@918: SpectrogramLayer::getFFTModel(const LayerGeometryProvider *v) const Chris@114: { Chris@114: if (!m_model) return 0; Chris@114: Chris@805: int fftSize = getFFTSize(v); Chris@114: Chris@920: const View *view = v->getView(); Chris@920: Chris@920: if (m_fftModels.find(view) != m_fftModels.end()) { Chris@988: if (m_fftModels[view] == 0) { Chris@184: #ifdef DEBUG_SPECTROGRAM_REPAINT Chris@985: cerr << "SpectrogramLayer::getFFTModel(" << v << "): Found null model" << endl; Chris@184: #endif Chris@184: return 0; Chris@184: } Chris@988: if (m_fftModels[view]->getHeight() != fftSize / 2 + 1) { Chris@184: #ifdef DEBUG_SPECTROGRAM_REPAINT Chris@1001: cerr << "SpectrogramLayer::getFFTModel(" << v << "): Found a model with the wrong height (" << m_fftModels[view]->getHeight() << ", wanted " << (fftSize / 2 + 1) << ")" << endl; Chris@184: #endif Chris@988: delete m_fftModels[view]; Chris@920: m_fftModels.erase(view); Chris@920: delete m_peakCaches[view]; Chris@920: m_peakCaches.erase(view); Chris@184: } else { Chris@184: #ifdef DEBUG_SPECTROGRAM_REPAINT Chris@988: cerr << "SpectrogramLayer::getFFTModel(" << v << "): Found a good model of height " << m_fftModels[view]->getHeight() << endl; Chris@184: #endif Chris@988: return m_fftModels[view]; Chris@114: } Chris@114: } Chris@114: Chris@920: if (m_fftModels.find(view) == m_fftModels.end()) { Chris@169: Chris@169: FFTModel *model = new FFTModel(m_model, Chris@169: m_channel, Chris@169: m_windowType, Chris@169: m_windowSize, Chris@169: getWindowIncrement(), Chris@975: fftSize); Chris@169: Chris@178: if (!model->isOK()) { Chris@178: QMessageBox::critical Chris@178: (0, tr("FFT cache failed"), Chris@178: tr("Failed to create the FFT model for this spectrogram.\n" Chris@178: "There may be insufficient memory or disc space to continue.")); Chris@178: delete model; Chris@988: m_fftModels[view] = 0; Chris@178: return 0; Chris@178: } Chris@178: Chris@193: if (!m_sliceableModel) { Chris@248: #ifdef DEBUG_SPECTROGRAM Chris@682: cerr << "SpectrogramLayer: emitting sliceableModelReplaced(0, " << model << ")" << endl; Chris@248: #endif Chris@193: ((SpectrogramLayer *)this)->sliceableModelReplaced(0, model); Chris@193: m_sliceableModel = model; Chris@193: } Chris@193: Chris@988: m_fftModels[view] = model; Chris@114: } Chris@114: Chris@988: return m_fftModels[view]; Chris@114: } Chris@114: Chris@484: Dense3DModelPeakCache * Chris@918: SpectrogramLayer::getPeakCache(const LayerGeometryProvider *v) const Chris@484: { Chris@920: const View *view = v->getView(); Chris@920: if (!m_peakCaches[view]) { Chris@484: FFTModel *f = getFFTModel(v); Chris@484: if (!f) return 0; Chris@920: m_peakCaches[view] = new Dense3DModelPeakCache(f, 8); Chris@484: } Chris@920: return m_peakCaches[view]; Chris@484: } Chris@484: Chris@193: const Model * Chris@193: SpectrogramLayer::getSliceableModel() const Chris@193: { Chris@193: if (m_sliceableModel) return m_sliceableModel; Chris@193: if (m_fftModels.empty()) return 0; Chris@985: m_sliceableModel = m_fftModels.begin()->second; Chris@193: return m_sliceableModel; Chris@193: } Chris@193: Chris@114: void Chris@130: SpectrogramLayer::invalidateFFTModels() Chris@114: { Chris@130: for (ViewFFTMap::iterator i = m_fftModels.begin(); Chris@130: i != m_fftModels.end(); ++i) { Chris@985: delete i->second; Chris@114: } Chris@486: for (PeakCacheMap::iterator i = m_peakCaches.begin(); Chris@486: i != m_peakCaches.end(); ++i) { Chris@486: delete i->second; Chris@486: } Chris@114: Chris@130: m_fftModels.clear(); Chris@486: m_peakCaches.clear(); Chris@193: Chris@193: if (m_sliceableModel) { Chris@682: cerr << "SpectrogramLayer: emitting sliceableModelReplaced(" << m_sliceableModel << ", 0)" << endl; Chris@193: emit sliceableModelReplaced(m_sliceableModel, 0); Chris@193: m_sliceableModel = 0; Chris@193: } Chris@114: } Chris@114: Chris@0: void Chris@119: SpectrogramLayer::invalidateMagnitudes() Chris@119: { Chris@119: m_viewMags.clear(); Chris@119: for (std::vector::iterator i = m_columnMags.begin(); Chris@119: i != m_columnMags.end(); ++i) { Chris@119: *i = MagnitudeRange(); Chris@119: } Chris@119: } Chris@119: Chris@119: bool Chris@918: SpectrogramLayer::updateViewMagnitudes(LayerGeometryProvider *v) const Chris@119: { Chris@119: MagnitudeRange mag; Chris@119: Chris@918: int x0 = 0, x1 = v->getPaintWidth(); Chris@905: double s00 = 0, s01 = 0, s10 = 0, s11 = 0; Chris@119: Chris@203: if (!getXBinRange(v, x0, s00, s01)) { Chris@907: s00 = s01 = double(m_model->getStartFrame()) / getWindowIncrement(); Chris@203: } Chris@203: Chris@203: if (!getXBinRange(v, x1, s10, s11)) { Chris@907: s10 = s11 = double(m_model->getEndFrame()) / getWindowIncrement(); Chris@203: } Chris@119: Chris@119: int s0 = int(std::min(s00, s10) + 0.0001); Chris@203: int s1 = int(std::max(s01, s11) + 0.0001); Chris@203: Chris@587: // SVDEBUG << "SpectrogramLayer::updateViewMagnitudes: x0 = " << x0 << ", x1 = " << x1 << ", s00 = " << s00 << ", s11 = " << s11 << " s0 = " << s0 << ", s1 = " << s1 << endl; Chris@119: Chris@248: if (int(m_columnMags.size()) <= s1) { Chris@119: m_columnMags.resize(s1 + 1); Chris@119: } Chris@119: Chris@119: for (int s = s0; s <= s1; ++s) { Chris@119: if (m_columnMags[s].isSet()) { Chris@119: mag.sample(m_columnMags[s]); Chris@119: } Chris@119: } Chris@119: Chris@184: #ifdef DEBUG_SPECTROGRAM_REPAINT Chris@985: cerr << "SpectrogramLayer::updateViewMagnitudes returning from cols " Chris@585: << s0 << " -> " << s1 << " inclusive" << endl; Chris@184: #endif Chris@119: Chris@119: if (!mag.isSet()) return false; Chris@119: if (mag == m_viewMags[v]) return false; Chris@119: m_viewMags[v] = mag; Chris@119: return true; Chris@119: } Chris@119: Chris@119: void Chris@389: SpectrogramLayer::setSynchronousPainting(bool synchronous) Chris@389: { Chris@389: m_synchronous = synchronous; Chris@389: } Chris@389: Chris@389: void Chris@916: SpectrogramLayer::paint(LayerGeometryProvider *v, QPainter &paint, QRect rect) const Chris@0: { Chris@253: // What a lovely, old-fashioned function this is. Chris@253: // It's practically FORTRAN 77 in its clarity and linearity. Chris@253: Chris@334: Profiler profiler("SpectrogramLayer::paint", false); Chris@334: Chris@0: #ifdef DEBUG_SPECTROGRAM_REPAINT Chris@985: cerr << "SpectrogramLayer::paint(): m_model is " << m_model << ", zoom level is " << v->getZoomLevel() << endl; Chris@95: Chris@682: cerr << "rect is " << rect.x() << "," << rect.y() << " " << rect.width() << "x" << rect.height() << endl; Chris@0: #endif Chris@95: Chris@907: sv_frame_t startFrame = v->getStartFrame(); Chris@44: Chris@0: if (!m_model || !m_model->isOK() || !m_model->isReady()) { Chris@0: return; Chris@0: } Chris@0: Chris@47: if (isLayerDormant(v)) { Chris@587: SVDEBUG << "SpectrogramLayer::paint(): Layer is dormant, making it undormant again" << endl; Chris@29: } Chris@29: Chris@48: // Need to do this even if !isLayerDormant, as that could mean v Chris@48: // is not in the dormancy map at all -- we need it to be present Chris@48: // and accountable for when determining whether we need the cache Chris@48: // in the cache-fill thread above. Chris@806: //!!! no inter use cache-fill thread Chris@131: const_cast(this)->Layer::setLayerDormant(v, false); Chris@48: Chris@805: int fftSize = getFFTSize(v); Chris@484: /* Chris@130: FFTModel *fft = getFFTModel(v); Chris@114: if (!fft) { Chris@682: cerr << "ERROR: SpectrogramLayer::paint(): No FFT model, returning" << endl; Chris@0: return; Chris@0: } Chris@484: */ Chris@920: Chris@920: const View *view = v->getView(); Chris@920: Chris@920: ImageCache &cache = m_imageCaches[view]; Chris@95: Chris@95: #ifdef DEBUG_SPECTROGRAM_REPAINT Chris@985: cerr << "SpectrogramLayer::paint(): image cache valid area " << cache. Chris@585: Chris@585: validArea.x() << ", " << cache.validArea.y() << ", " << cache.validArea.width() << "x" << cache.validArea.height() << endl; Chris@95: #endif Chris@95: Chris@44: int zoomLevel = v->getZoomLevel(); Chris@0: Chris@0: int x0 = 0; Chris@918: int x1 = v->getPaintWidth(); Chris@0: Chris@478: bool recreateWholeImageCache = true; Chris@0: Chris@95: x0 = rect.left(); Chris@95: x1 = rect.right() + 1; Chris@481: /* Chris@905: double xPixelRatio = double(fft->getResolution()) / double(zoomLevel); Chris@682: cerr << "xPixelRatio = " << xPixelRatio << endl; Chris@479: if (xPixelRatio < 1.f) xPixelRatio = 1.f; Chris@481: */ Chris@95: if (cache.validArea.width() > 0) { Chris@95: Chris@482: int cw = cache.image.width(); Chris@482: int ch = cache.image.height(); Chris@482: Chris@95: if (int(cache.zoomLevel) == zoomLevel && Chris@918: cw == v->getPaintWidth() && Chris@918: ch == v->getPaintHeight()) { Chris@95: Chris@95: if (v->getXForFrame(cache.startFrame) == Chris@95: v->getXForFrame(startFrame) && Chris@95: cache.validArea.x() <= x0 && Chris@95: cache.validArea.x() + cache.validArea.width() >= x1) { Chris@0: Chris@0: #ifdef DEBUG_SPECTROGRAM_REPAINT Chris@682: cerr << "SpectrogramLayer: image cache good" << endl; Chris@0: #endif Chris@0: Chris@478: paint.drawImage(rect, cache.image, rect); Chris@479: //!!! Chris@479: // paint.drawImage(v->rect(), cache.image, Chris@479: // QRect(QPoint(0, 0), cache.image.size())); Chris@479: Chris@121: illuminateLocalFeatures(v, paint); Chris@0: return; Chris@0: Chris@0: } else { Chris@0: Chris@0: #ifdef DEBUG_SPECTROGRAM_REPAINT Chris@682: cerr << "SpectrogramLayer: image cache partially OK" << endl; Chris@0: #endif Chris@0: Chris@478: recreateWholeImageCache = false; Chris@0: Chris@95: int dx = v->getXForFrame(cache.startFrame) - Chris@44: v->getXForFrame(startFrame); Chris@0: Chris@0: #ifdef DEBUG_SPECTROGRAM_REPAINT Chris@682: cerr << "SpectrogramLayer: dx = " << dx << " (image cache " << cw << "x" << ch << ")" << endl; Chris@0: #endif Chris@0: Chris@95: if (dx != 0 && Chris@482: dx > -cw && Chris@482: dx < cw) { Chris@482: Chris@482: int dxp = dx; Chris@482: if (dxp < 0) dxp = -dxp; Chris@907: size_t copy = (cw - dxp) * sizeof(QRgb); Chris@482: for (int y = 0; y < ch; ++y) { Chris@482: QRgb *line = (QRgb *)cache.image.scanLine(y); Chris@482: if (dx < 0) { Chris@482: memmove(line, line + dxp, copy); Chris@482: } else { Chris@482: memmove(line + dxp, line, copy); Chris@482: } Chris@331: } Chris@0: Chris@95: int px = cache.validArea.x(); Chris@95: int pw = cache.validArea.width(); Chris@0: Chris@0: if (dx < 0) { Chris@482: x0 = cw + dx; Chris@482: x1 = cw; Chris@95: px += dx; Chris@95: if (px < 0) { Chris@95: pw += px; Chris@95: px = 0; Chris@95: if (pw < 0) pw = 0; Chris@95: } Chris@0: } else { Chris@0: x0 = 0; Chris@0: x1 = dx; Chris@95: px += dx; Chris@482: if (px + pw > cw) { Chris@482: pw = int(cw) - px; Chris@95: if (pw < 0) pw = 0; Chris@95: } Chris@0: } Chris@95: Chris@95: cache.validArea = Chris@95: QRect(px, cache.validArea.y(), Chris@95: pw, cache.validArea.height()); Chris@95: Chris@331: #ifdef DEBUG_SPECTROGRAM_REPAINT Chris@682: cerr << "valid area now " Chris@331: << px << "," << cache.validArea.y() Chris@331: << " " << pw << "x" << cache.validArea.height() Chris@682: << endl; Chris@331: #endif Chris@479: /* Chris@478: paint.drawImage(rect & cache.validArea, Chris@478: cache.image, Chris@95: rect & cache.validArea); Chris@479: */ Chris@331: } else if (dx != 0) { Chris@331: Chris@331: // we scrolled too far to be of use Chris@331: Chris@391: #ifdef DEBUG_SPECTROGRAM_REPAINT Chris@682: cerr << "dx == " << dx << ": scrolled too far for cache to be useful" << endl; Chris@391: #endif Chris@391: Chris@331: cache.validArea = QRect(); Chris@478: recreateWholeImageCache = true; Chris@331: } Chris@0: } Chris@0: } else { Chris@0: #ifdef DEBUG_SPECTROGRAM_REPAINT Chris@682: cerr << "SpectrogramLayer: image cache useless" << endl; Chris@224: if (int(cache.zoomLevel) != zoomLevel) { Chris@682: cerr << "(cache zoomLevel " << cache.zoomLevel Chris@682: << " != " << zoomLevel << ")" << endl; Chris@224: } Chris@918: if (cw != v->getPaintWidth()) { Chris@682: cerr << "(cache width " << cw Chris@918: << " != " << v->getPaintWidth(); Chris@224: } Chris@918: if (ch != v->getPaintHeight()) { Chris@682: cerr << "(cache height " << ch Chris@918: << " != " << v->getPaintHeight(); Chris@224: } Chris@0: #endif Chris@95: cache.validArea = QRect(); Chris@478: // recreateWholeImageCache = true; Chris@0: } Chris@0: } Chris@95: Chris@133: if (updateViewMagnitudes(v)) { Chris@184: #ifdef DEBUG_SPECTROGRAM_REPAINT Chris@682: cerr << "SpectrogramLayer: magnitude range changed to [" << m_viewMags[v].getMin() << "->" << m_viewMags[v].getMax() << "]" << endl; Chris@184: #endif Chris@862: if (m_normalization == NormalizeVisibleArea) { Chris@331: cache.validArea = QRect(); Chris@478: recreateWholeImageCache = true; Chris@331: } Chris@133: } else { Chris@184: #ifdef DEBUG_SPECTROGRAM_REPAINT Chris@682: cerr << "No change in magnitude range [" << m_viewMags[v].getMin() << "->" << m_viewMags[v].getMax() << "]" << endl; Chris@184: #endif Chris@133: } Chris@133: Chris@478: if (recreateWholeImageCache) { Chris@95: x0 = 0; Chris@918: x1 = v->getPaintWidth(); Chris@95: } Chris@95: Chris@215: struct timeval tv; Chris@215: (void)gettimeofday(&tv, 0); Chris@215: RealTime mainPaintStart = RealTime::fromTimeval(tv); Chris@215: Chris@215: int paintBlockWidth = m_lastPaintBlockWidth; Chris@215: Chris@389: if (m_synchronous) { Chris@389: if (paintBlockWidth < x1 - x0) { Chris@389: // always paint full width Chris@389: paintBlockWidth = x1 - x0; Chris@389: } Chris@215: } else { Chris@389: if (paintBlockWidth == 0) { Chris@389: paintBlockWidth = (300000 / zoomLevel); Chris@389: } else { Chris@389: RealTime lastTime = m_lastPaintTime; Chris@389: while (lastTime > RealTime::fromMilliseconds(200) && Chris@1007: paintBlockWidth > 100) { Chris@389: paintBlockWidth /= 2; Chris@389: lastTime = lastTime / 2; Chris@389: } Chris@389: while (lastTime < RealTime::fromMilliseconds(90) && Chris@389: paintBlockWidth < 1500) { Chris@389: paintBlockWidth *= 2; Chris@389: lastTime = lastTime * 2; Chris@389: } Chris@215: } Chris@389: Chris@1007: if (paintBlockWidth < 50) paintBlockWidth = 50; Chris@215: } Chris@215: Chris@525: #ifdef DEBUG_SPECTROGRAM_REPAINT Chris@682: cerr << "[" << this << "]: last paint width: " << m_lastPaintBlockWidth << ", last paint time: " << m_lastPaintTime << ", new paint width: " << paintBlockWidth << endl; Chris@525: #endif Chris@224: Chris@224: // We always paint the full height when refreshing the cache. Chris@224: // Smaller heights can be used when painting direct from cache Chris@224: // (further up in this function), but we want to ensure the cache Chris@224: // is coherent without having to worry about vertical matching of Chris@224: // required and valid areas as well as horizontal. Chris@224: Chris@918: int h = v->getPaintHeight(); Chris@215: Chris@96: if (cache.validArea.width() > 0) { Chris@96: Chris@331: // If part of the cache is known to be valid, select a strip Chris@331: // immediately to left or right of the valid part Chris@331: Chris@481: //!!! this really needs to be coordinated with the selection Chris@481: //!!! of m_drawBuffer boundaries in the bufferBinResolution Chris@481: //!!! case below Chris@481: Chris@96: int vx0 = 0, vx1 = 0; Chris@96: vx0 = cache.validArea.x(); Chris@96: vx1 = cache.validArea.x() + cache.validArea.width(); Chris@96: Chris@96: #ifdef DEBUG_SPECTROGRAM_REPAINT Chris@682: cerr << "x0 " << x0 << ", x1 " << x1 << ", vx0 " << vx0 << ", vx1 " << vx1 << ", paintBlockWidth " << paintBlockWidth << endl; Chris@331: #endif Chris@96: if (x0 < vx0) { Chris@96: if (x0 + paintBlockWidth < vx0) { Chris@96: x0 = vx0 - paintBlockWidth; Chris@331: } Chris@331: x1 = vx0; Chris@331: } else if (x0 >= vx1) { Chris@331: x0 = vx1; Chris@331: if (x1 > x0 + paintBlockWidth) { Chris@331: x1 = x0 + paintBlockWidth; Chris@331: } Chris@331: } else { Chris@331: // x0 is within the valid area Chris@331: if (x1 > vx1) { Chris@331: x0 = vx1; Chris@331: if (x0 + paintBlockWidth < x1) { Chris@331: x1 = x0 + paintBlockWidth; Chris@331: } Chris@96: } else { Chris@331: x1 = x0; // it's all valid, paint nothing Chris@95: } Chris@96: } Chris@331: Chris@96: cache.validArea = QRect Chris@96: (std::min(vx0, x0), cache.validArea.y(), Chris@96: std::max(vx1 - std::min(vx0, x0), Chris@337: x1 - std::min(vx0, x0)), Chris@96: cache.validArea.height()); Chris@337: Chris@337: #ifdef DEBUG_SPECTROGRAM_REPAINT Chris@682: cerr << "Valid area becomes " << cache.validArea.x() Chris@337: << ", " << cache.validArea.y() << ", " Chris@337: << cache.validArea.width() << "x" Chris@682: << cache.validArea.height() << endl; Chris@337: #endif Chris@95: Chris@96: } else { Chris@96: if (x1 > x0 + paintBlockWidth) { Chris@133: int sfx = x1; Chris@133: if (startFrame < 0) sfx = v->getXForFrame(0); Chris@133: if (sfx >= x0 && sfx + paintBlockWidth <= x1) { Chris@133: x0 = sfx; Chris@133: x1 = x0 + paintBlockWidth; Chris@133: } else { Chris@133: int mid = (x1 + x0) / 2; Chris@133: x0 = mid - paintBlockWidth/2; Chris@133: x1 = x0 + paintBlockWidth; Chris@133: } Chris@95: } Chris@337: #ifdef DEBUG_SPECTROGRAM_REPAINT Chris@682: cerr << "Valid area becomes " << x0 << ", 0, " << (x1-x0) Chris@682: << "x" << h << endl; Chris@337: #endif Chris@224: cache.validArea = QRect(x0, 0, x1 - x0, h); Chris@95: } Chris@95: Chris@481: /* Chris@480: if (xPixelRatio != 1.f) { Chris@480: x0 = int((int(x0 / xPixelRatio) - 4) * xPixelRatio + 0.0001); Chris@480: x1 = int((int(x1 / xPixelRatio) + 4) * xPixelRatio + 0.0001); Chris@480: } Chris@481: */ Chris@0: int w = x1 - x0; Chris@0: Chris@95: #ifdef DEBUG_SPECTROGRAM_REPAINT Chris@682: cerr << "x0 " << x0 << ", x1 " << x1 << ", w " << w << ", h " << h << endl; Chris@95: #endif Chris@95: Chris@907: sv_samplerate_t sr = m_model->getSampleRate(); Chris@122: Chris@122: // Set minFreq and maxFreq to the frequency extents of the possibly Chris@122: // zero-padded visible bin range, and displayMinFreq and displayMaxFreq Chris@122: // to the actual scale frequency extents (presumably not zero padded). Chris@253: Chris@253: // If we are zero padding, we want to use the zero-padded Chris@253: // equivalents of the bins that we would be using if not zero Chris@253: // padded, to avoid spaces at the top and bottom of the display. Chris@253: Chris@253: // Note fftSize is the actual zero-padded fft size, m_fftSize the Chris@253: // nominal fft size. Chris@35: Chris@805: int maxbin = m_fftSize / 2; Chris@35: if (m_maxFrequency > 0) { Chris@253: maxbin = int((double(m_maxFrequency) * m_fftSize) / sr + 0.001); Chris@253: if (maxbin > m_fftSize / 2) maxbin = m_fftSize / 2; Chris@35: } Chris@111: Chris@805: int minbin = 1; Chris@37: if (m_minFrequency > 0) { Chris@253: minbin = int((double(m_minFrequency) * m_fftSize) / sr + 0.001); Chris@682: // cerr << "m_minFrequency = " << m_minFrequency << " -> minbin = " << minbin << endl; Chris@40: if (minbin < 1) minbin = 1; Chris@184: if (minbin >= maxbin) minbin = maxbin - 1; Chris@37: } Chris@37: Chris@253: int zpl = getZeroPadLevel(v) + 1; Chris@253: minbin = minbin * zpl; Chris@253: maxbin = (maxbin + 1) * zpl - 1; Chris@253: Chris@905: double minFreq = (double(minbin) * sr) / fftSize; Chris@905: double maxFreq = (double(maxbin) * sr) / fftSize; Chris@905: Chris@905: double displayMinFreq = minFreq; Chris@905: double displayMaxFreq = maxFreq; Chris@122: Chris@122: if (fftSize != m_fftSize) { Chris@122: displayMinFreq = getEffectiveMinFrequency(); Chris@122: displayMaxFreq = getEffectiveMaxFrequency(); Chris@122: } Chris@122: Chris@682: // cerr << "(giving actual minFreq " << minFreq << " and display minFreq " << displayMinFreq << ")" << endl; Chris@253: Chris@518: int increment = getWindowIncrement(); Chris@40: Chris@40: bool logarithmic = (m_frequencyScale == LogFrequencyScale); Chris@488: /* Chris@905: double yforbin[maxbin - minbin + 1]; Chris@481: Chris@805: for (int q = minbin; q <= maxbin; ++q) { Chris@905: double f0 = (double(q) * sr) / fftSize; Chris@477: yforbin[q - minbin] = Chris@382: v->getYForFrequency(f0, displayMinFreq, displayMaxFreq, Chris@382: logarithmic); Chris@92: } Chris@488: */ Chris@119: MagnitudeRange overallMag = m_viewMags[v]; Chris@119: bool overallMagChanged = false; Chris@119: Chris@137: #ifdef DEBUG_SPECTROGRAM_REPAINT Chris@905: cerr << ((double(v->getFrameForX(1) - v->getFrameForX(0))) / increment) << " bin(s) per pixel" << endl; Chris@137: #endif Chris@137: Chris@331: if (w == 0) { Chris@587: SVDEBUG << "*** NOTE: w == 0" << endl; Chris@331: } Chris@331: Chris@382: Profiler outerprof("SpectrogramLayer::paint: all cols"); Chris@382: Chris@481: // The draw buffer contains a fragment at either our pixel Chris@481: // resolution (if there is more than one time-bin per pixel) or Chris@481: // time-bin resolution (if a time-bin spans more than one pixel). Chris@481: // We need to ensure that it starts and ends at points where a Chris@481: // time-bin boundary occurs at an exact pixel boundary, and with a Chris@481: // certain amount of overlap across existing pixels so that we can Chris@481: // scale and draw from it without smoothing errors at the edges. Chris@481: Chris@481: // If (getFrameForX(x) / increment) * increment == Chris@481: // getFrameForX(x), then x is a time-bin boundary. We want two Chris@481: // such boundaries at either side of the draw buffer -- one which Chris@481: // we draw up to, and one which we subsequently crop at. Chris@481: Chris@481: bool bufferBinResolution = false; Chris@481: if (increment > zoomLevel) bufferBinResolution = true; Chris@481: Chris@907: sv_frame_t leftBoundaryFrame = -1, leftCropFrame = -1; Chris@907: sv_frame_t rightBoundaryFrame = -1, rightCropFrame = -1; Chris@481: Chris@481: int bufwid; Chris@481: Chris@481: if (bufferBinResolution) { Chris@481: Chris@482: for (int x = x0; ; --x) { Chris@907: sv_frame_t f = v->getFrameForX(x); Chris@481: if ((f / increment) * increment == f) { Chris@481: if (leftCropFrame == -1) leftCropFrame = f; Chris@482: else if (x < x0 - 2) { leftBoundaryFrame = f; break; } Chris@481: } Chris@481: } Chris@482: for (int x = x0 + w; ; ++x) { Chris@907: sv_frame_t f = v->getFrameForX(x); Chris@481: if ((f / increment) * increment == f) { Chris@481: if (rightCropFrame == -1) rightCropFrame = f; Chris@482: else if (x > x0 + w + 2) { rightBoundaryFrame = f; break; } Chris@481: } Chris@481: } Chris@485: #ifdef DEBUG_SPECTROGRAM_REPAINT Chris@481: cerr << "Left: crop: " << leftCropFrame << " (bin " << leftCropFrame/increment << "); boundary: " << leftBoundaryFrame << " (bin " << leftBoundaryFrame/increment << ")" << endl; Chris@481: cerr << "Right: crop: " << rightCropFrame << " (bin " << rightCropFrame/increment << "); boundary: " << rightBoundaryFrame << " (bin " << rightBoundaryFrame/increment << ")" << endl; Chris@485: #endif Chris@481: Chris@907: bufwid = int((rightBoundaryFrame - leftBoundaryFrame) / increment); Chris@481: Chris@481: } else { Chris@481: Chris@481: bufwid = w; Chris@481: } Chris@481: Chris@907: vector binforx(bufwid); Chris@907: vector binfory(h); Chris@907: Chris@484: bool usePeaksCache = false; Chris@484: Chris@481: if (bufferBinResolution) { Chris@481: for (int x = 0; x < bufwid; ++x) { Chris@907: binforx[x] = int(leftBoundaryFrame / increment) + x; Chris@482: // cerr << "binforx[" << x << "] = " << binforx[x] << endl; Chris@481: } Chris@481: m_drawBuffer = QImage(bufwid, h, QImage::Format_Indexed8); Chris@481: } else { Chris@481: for (int x = 0; x < bufwid; ++x) { Chris@905: double s0 = 0, s1 = 0; Chris@481: if (getXBinRange(v, x + x0, s0, s1)) { Chris@481: binforx[x] = int(s0 + 0.0001); Chris@481: } else { Chris@487: binforx[x] = -1; //??? Chris@481: } Chris@481: } Chris@481: if (m_drawBuffer.width() < bufwid || m_drawBuffer.height() < h) { Chris@481: m_drawBuffer = QImage(bufwid, h, QImage::Format_Indexed8); Chris@480: } Chris@484: usePeaksCache = (increment * 8) < zoomLevel; Chris@487: if (m_colourScale == PhaseColourScale) usePeaksCache = false; Chris@480: } Chris@481: Chris@616: // No longer exists in Qt5: m_drawBuffer.setNumColors(256); Chris@481: for (int pixel = 0; pixel < 256; ++pixel) { Chris@907: m_drawBuffer.setColor((unsigned char)pixel, Chris@907: m_palette.getColour((unsigned char)pixel).rgb()); Chris@481: } Chris@481: Chris@481: m_drawBuffer.fill(0); Chris@480: Chris@488: if (m_binDisplay != PeakFrequencies) { Chris@488: Chris@488: for (int y = 0; y < h; ++y) { Chris@905: double q0 = 0, q1 = 0; Chris@488: if (!getSmoothedYBinRange(v, h-y-1, q0, q1)) { Chris@488: binfory[y] = -1; Chris@488: } else { Chris@490: binfory[y] = q0; Chris@488: // cerr << "binfory[" << y << "] = " << binfory[y] << endl; Chris@488: } Chris@480: } Chris@488: Chris@491: paintDrawBuffer(v, bufwid, h, binforx, binfory, usePeaksCache, Chris@491: overallMag, overallMagChanged); Chris@488: Chris@488: } else { Chris@488: Chris@488: paintDrawBufferPeakFrequencies(v, bufwid, h, binforx, Chris@488: minbin, maxbin, Chris@488: displayMinFreq, displayMaxFreq, Chris@491: logarithmic, Chris@491: overallMag, overallMagChanged); Chris@480: } Chris@481: Chris@480: /* Chris@479: for (int x = 0; x < w / xPixelRatio; ++x) { Chris@35: Chris@382: Profiler innerprof("SpectrogramLayer::paint: 1 pixel column"); Chris@382: Chris@478: runOutOfData = !paintColumnValues(v, fft, x0, x, Chris@478: minbin, maxbin, Chris@478: displayMinFreq, displayMaxFreq, Chris@479: xPixelRatio, Chris@478: h, yforbin); Chris@477: Chris@331: if (runOutOfData) { Chris@331: #ifdef DEBUG_SPECTROGRAM_REPAINT Chris@682: cerr << "Run out of data -- dropping out of loop" << endl; Chris@331: #endif Chris@331: break; Chris@331: } Chris@35: } Chris@480: */ Chris@331: #ifdef DEBUG_SPECTROGRAM_REPAINT Chris@682: // cerr << pixels << " pixels drawn" << endl; Chris@331: #endif Chris@331: Chris@119: if (overallMagChanged) { Chris@119: m_viewMags[v] = overallMag; Chris@209: #ifdef DEBUG_SPECTROGRAM_REPAINT Chris@682: cerr << "Overall mag is now [" << m_viewMags[v].getMin() << "->" << m_viewMags[v].getMax() << "] - will be updating" << endl; Chris@209: #endif Chris@119: } else { Chris@209: #ifdef DEBUG_SPECTROGRAM_REPAINT Chris@682: cerr << "Overall mag unchanged at [" << m_viewMags[v].getMin() << "->" << m_viewMags[v].getMax() << "]" << endl; Chris@209: #endif Chris@119: } Chris@119: Chris@382: outerprof.end(); Chris@382: Chris@382: Profiler profiler2("SpectrogramLayer::paint: draw image"); Chris@137: Chris@478: if (recreateWholeImageCache) { Chris@407: #ifdef DEBUG_SPECTROGRAM_REPAINT Chris@988: cerr << "Recreating image cache: width = " << v->getPaintWidth() Chris@585: << ", height = " << h << endl; Chris@407: #endif Chris@918: cache.image = QImage(v->getPaintWidth(), h, QImage::Format_ARGB32_Premultiplied); Chris@0: } Chris@0: Chris@331: if (w > 0) { Chris@224: #ifdef DEBUG_SPECTROGRAM_REPAINT Chris@985: cerr << "Painting " << w << "x" << h Chris@331: << " from draw buffer at " << 0 << "," << 0 Chris@480: << " to " << w << "x" << h << " on cache at " Chris@585: << x0 << "," << 0 << endl; Chris@224: #endif Chris@224: Chris@478: QPainter cachePainter(&cache.image); Chris@481: Chris@481: if (bufferBinResolution) { Chris@481: int scaledLeft = v->getXForFrame(leftBoundaryFrame); Chris@481: int scaledRight = v->getXForFrame(rightBoundaryFrame); Chris@485: #ifdef DEBUG_SPECTROGRAM_REPAINT Chris@985: cerr << "Rescaling image from " << bufwid Chris@481: << "x" << h << " to " Chris@481: << scaledRight-scaledLeft << "x" << h << endl; Chris@485: #endif Chris@490: Preferences::SpectrogramXSmoothing xsmoothing = Chris@490: Preferences::getInstance()->getSpectrogramXSmoothing(); Chris@587: // SVDEBUG << "xsmoothing == " << xsmoothing << endl; Chris@481: QImage scaled = m_drawBuffer.scaled Chris@481: (scaledRight - scaledLeft, h, Chris@490: Qt::IgnoreAspectRatio, Chris@490: ((xsmoothing == Preferences::SpectrogramXInterpolated) ? Chris@490: Qt::SmoothTransformation : Qt::FastTransformation)); Chris@481: int scaledLeftCrop = v->getXForFrame(leftCropFrame); Chris@481: int scaledRightCrop = v->getXForFrame(rightCropFrame); Chris@485: #ifdef DEBUG_SPECTROGRAM_REPAINT Chris@985: cerr << "Drawing image region of width " << scaledRightCrop - scaledLeftCrop << " to " Chris@481: << scaledLeftCrop << " from " << scaledLeftCrop - scaledLeft << endl; Chris@485: #endif Chris@481: cachePainter.drawImage Chris@481: (QRect(scaledLeftCrop, 0, Chris@481: scaledRightCrop - scaledLeftCrop, h), Chris@481: scaled, Chris@481: QRect(scaledLeftCrop - scaledLeft, 0, Chris@481: scaledRightCrop - scaledLeftCrop, h)); Chris@481: } else { Chris@481: cachePainter.drawImage(QRect(x0, 0, w, h), Chris@481: m_drawBuffer, Chris@481: QRect(0, 0, w, h)); Chris@481: } Chris@481: Chris@331: cachePainter.end(); Chris@331: } Chris@331: Chris@337: QRect pr = rect & cache.validArea; Chris@337: Chris@337: #ifdef DEBUG_SPECTROGRAM_REPAINT Chris@985: cerr << "Painting " << pr.width() << "x" << pr.height() Chris@337: << " from cache at " << pr.x() << "," << pr.y() Chris@585: << " to window" << endl; Chris@337: #endif Chris@337: Chris@478: paint.drawImage(pr.x(), pr.y(), cache.image, Chris@479: pr.x(), pr.y(), pr.width(), pr.height()); Chris@479: //!!! Chris@479: // paint.drawImage(v->rect(), cache.image, Chris@479: // QRect(QPoint(0, 0), cache.image.size())); Chris@337: Chris@331: cache.startFrame = startFrame; Chris@331: cache.zoomLevel = zoomLevel; Chris@119: Chris@389: if (!m_synchronous) { Chris@389: Chris@862: if ((m_normalization != NormalizeVisibleArea) || !overallMagChanged) { Chris@0: Chris@389: if (cache.validArea.x() > 0) { Chris@95: #ifdef DEBUG_SPECTROGRAM_REPAINT Chris@985: cerr << "SpectrogramLayer::paint() updating left (0, " Chris@585: << cache.validArea.x() << ")" << endl; Chris@95: #endif Chris@918: v->getView()->update(0, 0, cache.validArea.x(), h); Chris@389: } Chris@389: Chris@389: if (cache.validArea.x() + cache.validArea.width() < Chris@478: cache.image.width()) { Chris@389: #ifdef DEBUG_SPECTROGRAM_REPAINT Chris@985: cerr << "SpectrogramLayer::paint() updating right (" Chris@389: << cache.validArea.x() + cache.validArea.width() Chris@389: << ", " Chris@478: << cache.image.width() - (cache.validArea.x() + Chris@389: cache.validArea.width()) Chris@585: << ")" << endl; Chris@389: #endif Chris@918: v->getView()->update(cache.validArea.x() + cache.validArea.width(), Chris@389: 0, Chris@478: cache.image.width() - (cache.validArea.x() + Chris@389: cache.validArea.width()), Chris@389: h); Chris@389: } Chris@389: } else { Chris@389: // overallMagChanged Chris@682: cerr << "\noverallMagChanged - updating all\n" << endl; Chris@389: cache.validArea = QRect(); Chris@918: v->getView()->update(); Chris@119: } Chris@95: } Chris@0: Chris@121: illuminateLocalFeatures(v, paint); Chris@120: Chris@0: #ifdef DEBUG_SPECTROGRAM_REPAINT Chris@985: cerr << "SpectrogramLayer::paint() returning" << endl; Chris@0: #endif Chris@131: Chris@389: if (!m_synchronous) { Chris@389: m_lastPaintBlockWidth = paintBlockWidth; Chris@389: (void)gettimeofday(&tv, 0); Chris@389: m_lastPaintTime = RealTime::fromTimeval(tv) - mainPaintStart; Chris@389: } Chris@0: } Chris@0: Chris@480: bool Chris@918: SpectrogramLayer::paintDrawBufferPeakFrequencies(LayerGeometryProvider *v, Chris@488: int w, Chris@488: int h, Chris@907: const vector &binforx, Chris@488: int minbin, Chris@488: int maxbin, Chris@905: double displayMinFreq, Chris@905: double displayMaxFreq, Chris@491: bool logarithmic, Chris@491: MagnitudeRange &overallMag, Chris@491: bool &overallMagChanged) const Chris@488: { Chris@488: Profiler profiler("SpectrogramLayer::paintDrawBufferPeakFrequencies"); Chris@488: Chris@488: #ifdef DEBUG_SPECTROGRAM_REPAINT Chris@488: cerr << "minbin " << minbin << ", maxbin " << maxbin << "; w " << w << ", h " << h << endl; Chris@488: #endif Chris@488: if (minbin < 0) minbin = 0; Chris@488: if (maxbin < 0) maxbin = minbin+1; Chris@488: Chris@488: FFTModel *fft = getFFTModel(v); Chris@488: if (!fft) return false; Chris@488: Chris@488: FFTModel::PeakSet peakfreqs; Chris@488: Chris@848: int psx = -1; Chris@545: Chris@545: #ifdef __GNUC__ Chris@488: float values[maxbin - minbin + 1]; Chris@545: #else Chris@545: float *values = (float *)alloca((maxbin - minbin + 1) * sizeof(float)); Chris@545: #endif Chris@488: Chris@488: for (int x = 0; x < w; ++x) { Chris@488: Chris@488: if (binforx[x] < 0) continue; Chris@488: Chris@488: int sx0 = binforx[x]; Chris@488: int sx1 = sx0; Chris@488: if (x+1 < w) sx1 = binforx[x+1]; Chris@488: if (sx0 < 0) sx0 = sx1 - 1; Chris@488: if (sx0 < 0) continue; Chris@488: if (sx1 <= sx0) sx1 = sx0 + 1; Chris@488: Chris@488: for (int sx = sx0; sx < sx1; ++sx) { Chris@488: Chris@488: if (sx < 0 || sx >= int(fft->getWidth())) continue; Chris@488: Chris@488: if (!m_synchronous) { Chris@488: if (!fft->isColumnAvailable(sx)) { Chris@488: #ifdef DEBUG_SPECTROGRAM_REPAINT Chris@682: cerr << "Met unavailable column at col " << sx << endl; Chris@488: #endif Chris@488: return false; Chris@488: } Chris@488: } Chris@488: Chris@488: MagnitudeRange mag; Chris@488: Chris@488: if (sx != psx) { Chris@488: peakfreqs = fft->getPeakFrequencies(FFTModel::AllPeaks, sx, Chris@488: minbin, maxbin - 1); Chris@488: if (m_colourScale == PhaseColourScale) { Chris@488: fft->getPhasesAt(sx, values, minbin, maxbin - minbin + 1); Chris@862: } else if (m_normalization == NormalizeColumns) { Chris@488: fft->getNormalizedMagnitudesAt(sx, values, minbin, maxbin - minbin + 1); Chris@862: } else if (m_normalization == NormalizeHybrid) { Chris@1008: float max = fft->getNormalizedMagnitudesAt(sx, values, minbin, maxbin - minbin + 1); Chris@719: if (max > 0.f) { Chris@719: for (int i = minbin; i <= maxbin; ++i) { Chris@1008: values[i - minbin] = float(values[i - minbin] * Chris@1008: log10f(max)); Chris@719: } Chris@719: } Chris@488: } else { Chris@488: fft->getMagnitudesAt(sx, values, minbin, maxbin - minbin + 1); Chris@488: } Chris@488: psx = sx; Chris@488: } Chris@488: Chris@488: for (FFTModel::PeakSet::const_iterator pi = peakfreqs.begin(); Chris@488: pi != peakfreqs.end(); ++pi) { Chris@488: Chris@488: int bin = pi->first; Chris@907: double freq = pi->second; Chris@488: Chris@488: if (bin < minbin) continue; Chris@488: if (bin > maxbin) break; Chris@488: Chris@907: double value = values[bin - minbin]; Chris@488: Chris@488: if (m_colourScale != PhaseColourScale) { Chris@862: if (m_normalization != NormalizeColumns) { Chris@907: value /= (m_fftSize/2.0); Chris@488: } Chris@907: mag.sample(float(value)); Chris@488: value *= m_gain; Chris@488: } Chris@488: Chris@905: double y = v->getYForFrequency Chris@488: (freq, displayMinFreq, displayMaxFreq, logarithmic); Chris@488: Chris@558: int iy = int(y + 0.5); Chris@558: if (iy < 0 || iy >= h) continue; Chris@558: Chris@558: m_drawBuffer.setPixel(x, iy, getDisplayValue(v, value)); Chris@488: } Chris@488: Chris@488: if (mag.isSet()) { Chris@488: if (sx >= int(m_columnMags.size())) { Chris@540: #ifdef DEBUG_SPECTROGRAM Chris@682: cerr << "INTERNAL ERROR: " << sx << " >= " Chris@488: << m_columnMags.size() Chris@488: << " at SpectrogramLayer.cpp::paintDrawBuffer" Chris@682: << endl; Chris@540: #endif Chris@490: } else { Chris@490: m_columnMags[sx].sample(mag); Chris@491: if (overallMag.sample(mag)) overallMagChanged = true; Chris@488: } Chris@488: } Chris@488: } Chris@488: } Chris@488: Chris@488: return true; Chris@488: } Chris@488: Chris@488: bool Chris@918: SpectrogramLayer::paintDrawBuffer(LayerGeometryProvider *v, Chris@481: int w, Chris@481: int h, Chris@907: const vector &binforx, Chris@907: const vector &binfory, Chris@491: bool usePeaksCache, Chris@491: MagnitudeRange &overallMag, Chris@491: bool &overallMagChanged) const Chris@480: { Chris@481: Profiler profiler("SpectrogramLayer::paintDrawBuffer"); Chris@480: Chris@490: int minbin = int(binfory[0] + 0.0001); Chris@907: int maxbin = int(binfory[h-1]); Chris@480: Chris@485: #ifdef DEBUG_SPECTROGRAM_REPAINT Chris@481: cerr << "minbin " << minbin << ", maxbin " << maxbin << "; w " << w << ", h " << h << endl; Chris@485: #endif Chris@480: if (minbin < 0) minbin = 0; Chris@480: if (maxbin < 0) maxbin = minbin+1; Chris@480: Chris@484: DenseThreeDimensionalModel *sourceModel = 0; Chris@484: FFTModel *fft = 0; Chris@484: int divisor = 1; Chris@485: #ifdef DEBUG_SPECTROGRAM_REPAINT Chris@484: cerr << "Note: bin display = " << m_binDisplay << ", w = " << w << ", binforx[" << w-1 << "] = " << binforx[w-1] << ", binforx[0] = " << binforx[0] << endl; Chris@485: #endif Chris@484: if (usePeaksCache) { //!!! Chris@484: sourceModel = getPeakCache(v); Chris@484: divisor = 8;//!!! Chris@484: minbin = 0; Chris@484: maxbin = sourceModel->getHeight(); Chris@484: } else { Chris@484: sourceModel = fft = getFFTModel(v); Chris@484: } Chris@484: Chris@484: if (!sourceModel) return false; Chris@484: Chris@490: bool interpolate = false; Chris@490: Preferences::SpectrogramSmoothing smoothing = Chris@490: Preferences::getInstance()->getSpectrogramSmoothing(); Chris@490: if (smoothing == Preferences::SpectrogramInterpolated || Chris@490: smoothing == Preferences::SpectrogramZeroPaddedAndInterpolated) { Chris@490: if (m_binDisplay != PeakBins && Chris@490: m_binDisplay != PeakFrequencies) { Chris@490: interpolate = true; Chris@490: } Chris@490: } Chris@490: Chris@480: int psx = -1; Chris@545: Chris@545: #ifdef __GNUC__ Chris@490: float autoarray[maxbin - minbin + 1]; Chris@545: float peaks[h]; Chris@545: #else Chris@545: float *autoarray = (float *)alloca((maxbin - minbin + 1) * sizeof(float)); Chris@545: float *peaks = (float *)alloca(h * sizeof(float)); Chris@545: #endif Chris@545: Chris@490: const float *values = autoarray; Chris@484: DenseThreeDimensionalModel::Column c; Chris@480: Chris@480: for (int x = 0; x < w; ++x) { Chris@480: Chris@482: if (binforx[x] < 0) continue; Chris@482: Chris@488: // float columnGain = m_gain; Chris@487: float columnMax = 0.f; Chris@487: Chris@484: int sx0 = binforx[x] / divisor; Chris@483: int sx1 = sx0; Chris@484: if (x+1 < w) sx1 = binforx[x+1] / divisor; Chris@483: if (sx0 < 0) sx0 = sx1 - 1; Chris@483: if (sx0 < 0) continue; Chris@483: if (sx1 <= sx0) sx1 = sx0 + 1; Chris@483: Chris@483: for (int y = 0; y < h; ++y) peaks[y] = 0.f; Chris@480: Chris@483: for (int sx = sx0; sx < sx1; ++sx) { Chris@483: Chris@518: #ifdef DEBUG_SPECTROGRAM_REPAINT Chris@682: // cerr << "sx = " << sx << endl; Chris@518: #endif Chris@518: Chris@484: if (sx < 0 || sx >= int(sourceModel->getWidth())) continue; Chris@483: Chris@483: if (!m_synchronous) { Chris@484: if (!sourceModel->isColumnAvailable(sx)) { Chris@480: #ifdef DEBUG_SPECTROGRAM_REPAINT Chris@682: cerr << "Met unavailable column at col " << sx << endl; Chris@480: #endif Chris@483: return false; Chris@480: } Chris@483: } Chris@483: Chris@488: MagnitudeRange mag; Chris@488: Chris@483: if (sx != psx) { Chris@484: if (fft) { Chris@485: #ifdef DEBUG_SPECTROGRAM_REPAINT Chris@985: cerr << "Retrieving column " << sx << " from fft directly" << endl; Chris@485: #endif Chris@487: if (m_colourScale == PhaseColourScale) { Chris@490: fft->getPhasesAt(sx, autoarray, minbin, maxbin - minbin + 1); Chris@862: } else if (m_normalization == NormalizeColumns) { Chris@490: fft->getNormalizedMagnitudesAt(sx, autoarray, minbin, maxbin - minbin + 1); Chris@862: } else if (m_normalization == NormalizeHybrid) { Chris@1008: float max = fft->getNormalizedMagnitudesAt(sx, autoarray, minbin, maxbin - minbin + 1); Chris@988: float scale = log10f(max + 1.f); Chris@998: // cout << "sx = " << sx << ", max = " << max << ", log10(max) = " << log10(max) << ", scale = " << scale << endl; Chris@719: for (int i = minbin; i <= maxbin; ++i) { Chris@862: autoarray[i - minbin] *= scale; Chris@719: } Chris@487: } else { Chris@490: fft->getMagnitudesAt(sx, autoarray, minbin, maxbin - minbin + 1); Chris@487: } Chris@484: } else { Chris@485: #ifdef DEBUG_SPECTROGRAM_REPAINT Chris@985: cerr << "Retrieving column " << sx << " from peaks cache" << endl; Chris@485: #endif Chris@484: c = sourceModel->getColumn(sx); Chris@862: if (m_normalization == NormalizeColumns || Chris@862: m_normalization == NormalizeHybrid) { Chris@487: for (int y = 0; y < h; ++y) { Chris@487: if (c[y] > columnMax) columnMax = c[y]; Chris@487: } Chris@487: } Chris@490: values = c.constData() + minbin; Chris@484: } Chris@483: psx = sx; Chris@483: } Chris@483: Chris@483: for (int y = 0; y < h; ++y) { Chris@480: Chris@905: double sy0 = binfory[y]; Chris@905: double sy1 = sy0 + 1; Chris@481: if (y+1 < h) sy1 = binfory[y+1]; Chris@490: Chris@907: double value = 0.0; Chris@907: Chris@907: if (interpolate && fabs(sy1 - sy0) < 1.0) { Chris@490: Chris@905: double centre = (sy0 + sy1) / 2; Chris@907: double dist = (centre - 0.5) - rint(centre - 0.5); Chris@490: int bin = int(centre); Chris@490: int other = (dist < 0 ? (bin-1) : (bin+1)); Chris@490: if (bin < minbin) bin = minbin; Chris@490: if (bin > maxbin) bin = maxbin; Chris@490: if (other < minbin || other > maxbin) other = bin; Chris@907: double prop = 1.0 - fabs(dist); Chris@905: Chris@905: double v0 = values[bin - minbin]; Chris@905: double v1 = values[other - minbin]; Chris@490: if (m_binDisplay == PeakBins) { Chris@490: if (bin == minbin || bin == maxbin || Chris@490: v0 < values[bin-minbin-1] || Chris@907: v0 < values[bin-minbin+1]) v0 = 0.0; Chris@490: if (other == minbin || other == maxbin || Chris@490: v1 < values[other-minbin-1] || Chris@907: v1 < values[other-minbin+1]) v1 = 0.0; Chris@489: } Chris@907: if (v0 == 0.0 && v1 == 0.0) continue; Chris@907: value = prop * v0 + (1.0 - prop) * v1; Chris@484: Chris@488: if (m_colourScale != PhaseColourScale) { Chris@862: if (m_normalization != NormalizeColumns && Chris@862: m_normalization != NormalizeHybrid) { Chris@907: value /= (m_fftSize/2.0); Chris@488: } Chris@907: mag.sample(float(value)); Chris@488: value *= m_gain; Chris@488: } Chris@488: Chris@907: peaks[y] = float(value); Chris@490: Chris@490: } else { Chris@490: Chris@490: int by0 = int(sy0 + 0.0001); Chris@490: int by1 = int(sy1 + 0.0001); Chris@490: if (by1 < by0 + 1) by1 = by0 + 1; Chris@490: Chris@490: for (int bin = by0; bin < by1; ++bin) { Chris@490: Chris@490: value = values[bin - minbin]; Chris@490: if (m_binDisplay == PeakBins) { Chris@490: if (bin == minbin || bin == maxbin || Chris@490: value < values[bin-minbin-1] || Chris@490: value < values[bin-minbin+1]) continue; Chris@480: } Chris@490: Chris@490: if (m_colourScale != PhaseColourScale) { Chris@862: if (m_normalization != NormalizeColumns && Chris@862: m_normalization != NormalizeHybrid) { Chris@907: value /= (m_fftSize/2.0); Chris@490: } Chris@907: mag.sample(float(value)); Chris@490: value *= m_gain; Chris@490: } Chris@490: Chris@907: if (value > peaks[y]) { Chris@907: peaks[y] = float(value); //!!! not right for phase! Chris@907: } Chris@480: } Chris@480: } Chris@483: } Chris@488: Chris@488: if (mag.isSet()) { Chris@488: if (sx >= int(m_columnMags.size())) { Chris@540: #ifdef DEBUG_SPECTROGRAM Chris@682: cerr << "INTERNAL ERROR: " << sx << " >= " Chris@488: << m_columnMags.size() Chris@488: << " at SpectrogramLayer.cpp::paintDrawBuffer" Chris@682: << endl; Chris@540: #endif Chris@490: } else { Chris@490: m_columnMags[sx].sample(mag); Chris@491: if (overallMag.sample(mag)) overallMagChanged = true; Chris@488: } Chris@488: } Chris@483: } Chris@483: Chris@483: for (int y = 0; y < h; ++y) { Chris@483: Chris@905: double peak = peaks[y]; Chris@483: Chris@488: if (m_colourScale != PhaseColourScale && Chris@862: (m_normalization == NormalizeColumns || Chris@862: m_normalization == NormalizeHybrid) && Chris@488: columnMax > 0.f) { Chris@488: peak /= columnMax; Chris@862: if (m_normalization == NormalizeHybrid) { Chris@862: peak *= log10(columnMax + 1.f); Chris@719: } Chris@480: } Chris@483: Chris@483: unsigned char peakpix = getDisplayValue(v, peak); Chris@480: Chris@480: m_drawBuffer.setPixel(x, h-y-1, peakpix); Chris@480: } Chris@480: } Chris@480: Chris@480: return true; Chris@480: } Chris@477: Chris@121: void Chris@918: SpectrogramLayer::illuminateLocalFeatures(LayerGeometryProvider *v, QPainter &paint) const Chris@121: { Chris@382: Profiler profiler("SpectrogramLayer::illuminateLocalFeatures"); Chris@382: Chris@121: QPoint localPos; Chris@121: if (!v->shouldIlluminateLocalFeatures(this, localPos) || !m_model) { Chris@121: return; Chris@121: } Chris@121: Chris@682: // cerr << "SpectrogramLayer: illuminateLocalFeatures(" Chris@682: // << localPos.x() << "," << localPos.y() << ")" << endl; Chris@121: Chris@905: double s0, s1; Chris@905: double f0, f1; Chris@121: Chris@121: if (getXBinRange(v, localPos.x(), s0, s1) && Chris@121: getYBinSourceRange(v, localPos.y(), f0, f1)) { Chris@121: Chris@121: int s0i = int(s0 + 0.001); Chris@121: int s1i = int(s1); Chris@121: Chris@121: int x0 = v->getXForFrame(s0i * getWindowIncrement()); Chris@121: int x1 = v->getXForFrame((s1i + 1) * getWindowIncrement()); Chris@121: Chris@248: int y1 = int(getYForFrequency(v, f1)); Chris@248: int y0 = int(getYForFrequency(v, f0)); Chris@121: Chris@682: // cerr << "SpectrogramLayer: illuminate " Chris@682: // << x0 << "," << y1 << " -> " << x1 << "," << y0 << endl; Chris@121: Chris@287: paint.setPen(v->getForeground()); Chris@133: Chris@133: //!!! should we be using paintCrosshairs for this? Chris@133: Chris@121: paint.drawRect(x0, y1, x1 - x0 + 1, y0 - y1 + 1); Chris@121: } Chris@121: } Chris@121: Chris@905: double Chris@918: SpectrogramLayer::getYForFrequency(const LayerGeometryProvider *v, double frequency) const Chris@42: { Chris@44: return v->getYForFrequency(frequency, Chris@44: getEffectiveMinFrequency(), Chris@44: getEffectiveMaxFrequency(), Chris@44: m_frequencyScale == LogFrequencyScale); Chris@42: } Chris@42: Chris@905: double Chris@918: SpectrogramLayer::getFrequencyForY(const LayerGeometryProvider *v, int y) const Chris@42: { Chris@44: return v->getFrequencyForY(y, Chris@44: getEffectiveMinFrequency(), Chris@44: getEffectiveMaxFrequency(), Chris@44: m_frequencyScale == LogFrequencyScale); Chris@42: } Chris@42: Chris@0: int Chris@918: SpectrogramLayer::getCompletion(LayerGeometryProvider *v) const Chris@0: { Chris@920: const View *view = v->getView(); Chris@920: Chris@920: if (m_fftModels.find(view) == m_fftModels.end()) return 100; Chris@920: Chris@988: int completion = m_fftModels[view]->getCompletion(); Chris@224: #ifdef DEBUG_SPECTROGRAM_REPAINT Chris@985: cerr << "SpectrogramLayer::getCompletion: completion = " << completion << endl; Chris@224: #endif Chris@0: return completion; Chris@0: } Chris@0: Chris@583: QString Chris@918: SpectrogramLayer::getError(LayerGeometryProvider *v) const Chris@583: { Chris@920: const View *view = v->getView(); Chris@920: if (m_fftModels.find(view) == m_fftModels.end()) return ""; Chris@988: return m_fftModels[view]->getError(); Chris@583: } Chris@583: Chris@28: bool Chris@905: SpectrogramLayer::getValueExtents(double &min, double &max, Chris@101: bool &logarithmic, QString &unit) const Chris@79: { Chris@133: if (!m_model) return false; Chris@133: Chris@907: sv_samplerate_t sr = m_model->getSampleRate(); Chris@905: min = double(sr) / m_fftSize; Chris@905: max = double(sr) / 2; Chris@133: Chris@101: logarithmic = (m_frequencyScale == LogFrequencyScale); Chris@79: unit = "Hz"; Chris@79: return true; Chris@79: } Chris@79: Chris@79: bool Chris@905: SpectrogramLayer::getDisplayExtents(double &min, double &max) const Chris@101: { Chris@101: min = getEffectiveMinFrequency(); Chris@101: max = getEffectiveMaxFrequency(); Chris@253: Chris@587: // SVDEBUG << "SpectrogramLayer::getDisplayExtents: " << min << "->" << max << endl; Chris@101: return true; Chris@101: } Chris@101: Chris@101: bool Chris@905: SpectrogramLayer::setDisplayExtents(double min, double max) Chris@120: { Chris@120: if (!m_model) return false; Chris@187: Chris@587: // SVDEBUG << "SpectrogramLayer::setDisplayExtents: " << min << "->" << max << endl; Chris@187: Chris@120: if (min < 0) min = 0; Chris@907: if (max > m_model->getSampleRate()/2.0) max = m_model->getSampleRate()/2.0; Chris@120: Chris@907: int minf = int(lrint(min)); Chris@907: int maxf = int(lrint(max)); Chris@120: Chris@120: if (m_minFrequency == minf && m_maxFrequency == maxf) return true; Chris@120: Chris@478: invalidateImageCaches(); Chris@120: invalidateMagnitudes(); Chris@120: Chris@120: m_minFrequency = minf; Chris@120: m_maxFrequency = maxf; Chris@120: Chris@120: emit layerParametersChanged(); Chris@120: Chris@133: int vs = getCurrentVerticalZoomStep(); Chris@133: if (vs != m_lastEmittedZoomStep) { Chris@133: emit verticalZoomChanged(); Chris@133: m_lastEmittedZoomStep = vs; Chris@133: } Chris@133: Chris@120: return true; Chris@120: } Chris@120: Chris@120: bool Chris@918: SpectrogramLayer::getYScaleValue(const LayerGeometryProvider *v, int y, Chris@905: double &value, QString &unit) const Chris@261: { Chris@261: value = getFrequencyForY(v, y); Chris@261: unit = "Hz"; Chris@261: return true; Chris@261: } Chris@261: Chris@261: bool Chris@918: SpectrogramLayer::snapToFeatureFrame(LayerGeometryProvider *, Chris@907: sv_frame_t &frame, Chris@805: int &resolution, Chris@28: SnapType snap) const Chris@13: { Chris@13: resolution = getWindowIncrement(); Chris@907: sv_frame_t left = (frame / resolution) * resolution; Chris@907: sv_frame_t right = left + resolution; Chris@28: Chris@28: switch (snap) { Chris@28: case SnapLeft: frame = left; break; Chris@28: case SnapRight: frame = right; break; Chris@28: case SnapNearest: Chris@28: case SnapNeighbouring: Chris@28: if (frame - left > right - frame) frame = right; Chris@28: else frame = left; Chris@28: break; Chris@28: } Chris@28: Chris@28: return true; Chris@28: } Chris@13: Chris@283: void Chris@918: SpectrogramLayer::measureDoubleClick(LayerGeometryProvider *v, QMouseEvent *e) Chris@283: { Chris@920: const View *view = v->getView(); Chris@920: ImageCache &cache = m_imageCaches[view]; Chris@478: Chris@682: cerr << "cache width: " << cache.image.width() << ", height: " Chris@920: << cache.image.height() << endl; Chris@478: Chris@478: QImage image = cache.image; Chris@283: Chris@283: ImageRegionFinder finder; Chris@283: QRect rect = finder.findRegionExtents(&image, e->pos()); Chris@283: if (rect.isValid()) { Chris@283: MeasureRect mr; Chris@283: setMeasureRectFromPixrect(v, mr, rect); Chris@283: CommandHistory::getInstance()->addCommand Chris@283: (new AddMeasurementRectCommand(this, mr)); Chris@283: } Chris@283: } Chris@283: Chris@77: bool Chris@918: SpectrogramLayer::getCrosshairExtents(LayerGeometryProvider *v, QPainter &paint, Chris@77: QPoint cursorPos, Chris@77: std::vector &extents) const Chris@77: { Chris@918: QRect vertical(cursorPos.x() - 12, 0, 12, v->getPaintHeight()); Chris@77: extents.push_back(vertical); Chris@77: Chris@77: QRect horizontal(0, cursorPos.y(), cursorPos.x(), 1); Chris@77: extents.push_back(horizontal); Chris@77: Chris@608: int sw = getVerticalScaleWidth(v, m_haveDetailedScale, paint); Chris@264: Chris@280: QRect freq(sw, cursorPos.y() - paint.fontMetrics().ascent() - 2, Chris@280: paint.fontMetrics().width("123456 Hz") + 2, Chris@280: paint.fontMetrics().height()); Chris@280: extents.push_back(freq); Chris@264: Chris@279: QRect pitch(sw, cursorPos.y() + 2, Chris@279: paint.fontMetrics().width("C#10+50c") + 2, Chris@279: paint.fontMetrics().height()); Chris@279: extents.push_back(pitch); Chris@279: Chris@280: QRect rt(cursorPos.x(), Chris@918: v->getPaintHeight() - paint.fontMetrics().height() - 2, Chris@280: paint.fontMetrics().width("1234.567 s"), Chris@280: paint.fontMetrics().height()); Chris@280: extents.push_back(rt); Chris@280: Chris@280: int w(paint.fontMetrics().width("1234567890") + 2); Chris@280: QRect frame(cursorPos.x() - w - 2, Chris@918: v->getPaintHeight() - paint.fontMetrics().height() - 2, Chris@280: w, Chris@280: paint.fontMetrics().height()); Chris@280: extents.push_back(frame); Chris@280: Chris@77: return true; Chris@77: } Chris@77: Chris@77: void Chris@918: SpectrogramLayer::paintCrosshairs(LayerGeometryProvider *v, QPainter &paint, Chris@77: QPoint cursorPos) const Chris@77: { Chris@77: paint.save(); Chris@283: Chris@608: int sw = getVerticalScaleWidth(v, m_haveDetailedScale, paint); Chris@283: 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@77: paint.setPen(m_crosshairColour); Chris@77: Chris@77: paint.drawLine(0, cursorPos.y(), cursorPos.x() - 1, cursorPos.y()); Chris@918: paint.drawLine(cursorPos.x(), 0, cursorPos.x(), v->getPaintHeight()); Chris@77: Chris@905: double fundamental = getFrequencyForY(v, cursorPos.y()); Chris@77: Chris@278: v->drawVisibleText(paint, Chris@278: sw + 2, Chris@278: cursorPos.y() - 2, Chris@278: QString("%1 Hz").arg(fundamental), Chris@278: View::OutlinedText); Chris@278: Chris@279: if (Pitch::isFrequencyInMidiRange(fundamental)) { Chris@279: QString pitchLabel = Pitch::getPitchLabelForFrequency(fundamental); Chris@279: v->drawVisibleText(paint, Chris@279: sw + 2, Chris@279: cursorPos.y() + paint.fontMetrics().ascent() + 2, Chris@279: pitchLabel, Chris@279: View::OutlinedText); Chris@279: } Chris@279: Chris@907: sv_frame_t frame = v->getFrameForX(cursorPos.x()); Chris@279: RealTime rt = RealTime::frame2RealTime(frame, m_model->getSampleRate()); Chris@280: QString rtLabel = QString("%1 s").arg(rt.toText(true).c_str()); Chris@280: QString frameLabel = QString("%1").arg(frame); Chris@280: v->drawVisibleText(paint, Chris@280: cursorPos.x() - paint.fontMetrics().width(frameLabel) - 2, Chris@918: v->getPaintHeight() - 2, Chris@280: frameLabel, Chris@280: View::OutlinedText); Chris@280: v->drawVisibleText(paint, Chris@280: cursorPos.x() + 2, Chris@918: v->getPaintHeight() - 2, Chris@280: rtLabel, Chris@280: View::OutlinedText); Chris@264: Chris@77: int harmonic = 2; Chris@77: Chris@77: while (harmonic < 100) { Chris@77: Chris@907: int hy = int(lrint(getYForFrequency(v, fundamental * harmonic))); Chris@918: if (hy < 0 || hy > v->getPaintHeight()) break; Chris@77: Chris@77: int len = 7; Chris@77: Chris@77: if (harmonic % 2 == 0) { Chris@77: if (harmonic % 4 == 0) { Chris@77: len = 12; Chris@77: } else { Chris@77: len = 10; Chris@77: } Chris@77: } Chris@77: Chris@77: paint.drawLine(cursorPos.x() - len, Chris@907: hy, Chris@77: cursorPos.x(), Chris@907: hy); Chris@77: Chris@77: ++harmonic; Chris@77: } Chris@77: Chris@77: paint.restore(); Chris@77: } Chris@77: Chris@25: QString Chris@918: SpectrogramLayer::getFeatureDescription(LayerGeometryProvider *v, QPoint &pos) const Chris@25: { Chris@25: int x = pos.x(); Chris@25: int y = pos.y(); Chris@0: Chris@25: if (!m_model || !m_model->isOK()) return ""; Chris@0: Chris@905: double magMin = 0, magMax = 0; Chris@905: double phaseMin = 0, phaseMax = 0; Chris@905: double freqMin = 0, freqMax = 0; Chris@905: double adjFreqMin = 0, adjFreqMax = 0; Chris@25: QString pitchMin, pitchMax; Chris@0: RealTime rtMin, rtMax; Chris@0: Chris@38: bool haveValues = false; Chris@0: Chris@44: if (!getXBinSourceRange(v, x, rtMin, rtMax)) { Chris@38: return ""; Chris@38: } Chris@44: if (getXYBinSourceRange(v, x, y, magMin, magMax, phaseMin, phaseMax)) { Chris@38: haveValues = true; Chris@38: } Chris@0: Chris@35: QString adjFreqText = "", adjPitchText = ""; Chris@35: Chris@38: if (m_binDisplay == PeakFrequencies) { Chris@35: Chris@44: if (!getAdjustedYBinSourceRange(v, x, y, freqMin, freqMax, Chris@38: adjFreqMin, adjFreqMax)) { Chris@38: return ""; Chris@38: } Chris@35: Chris@35: if (adjFreqMin != adjFreqMax) { Chris@65: adjFreqText = tr("Peak Frequency:\t%1 - %2 Hz\n") Chris@35: .arg(adjFreqMin).arg(adjFreqMax); Chris@35: } else { Chris@65: adjFreqText = tr("Peak Frequency:\t%1 Hz\n") Chris@35: .arg(adjFreqMin); Chris@38: } Chris@38: Chris@38: QString pmin = Pitch::getPitchLabelForFrequency(adjFreqMin); Chris@38: QString pmax = Pitch::getPitchLabelForFrequency(adjFreqMax); Chris@38: Chris@38: if (pmin != pmax) { Chris@65: adjPitchText = tr("Peak Pitch:\t%3 - %4\n").arg(pmin).arg(pmax); Chris@38: } else { Chris@65: adjPitchText = tr("Peak Pitch:\t%2\n").arg(pmin); Chris@35: } Chris@35: Chris@35: } else { Chris@35: Chris@44: if (!getYBinSourceRange(v, y, freqMin, freqMax)) return ""; Chris@35: } Chris@35: Chris@25: QString text; Chris@25: Chris@25: if (rtMin != rtMax) { Chris@25: text += tr("Time:\t%1 - %2\n") Chris@25: .arg(rtMin.toText(true).c_str()) Chris@25: .arg(rtMax.toText(true).c_str()); Chris@25: } else { Chris@25: text += tr("Time:\t%1\n") Chris@25: .arg(rtMin.toText(true).c_str()); Chris@0: } Chris@0: Chris@25: if (freqMin != freqMax) { Chris@65: text += tr("%1Bin Frequency:\t%2 - %3 Hz\n%4Bin Pitch:\t%5 - %6\n") Chris@65: .arg(adjFreqText) Chris@25: .arg(freqMin) Chris@25: .arg(freqMax) Chris@65: .arg(adjPitchText) Chris@65: .arg(Pitch::getPitchLabelForFrequency(freqMin)) Chris@65: .arg(Pitch::getPitchLabelForFrequency(freqMax)); Chris@65: } else { Chris@65: text += tr("%1Bin Frequency:\t%2 Hz\n%3Bin Pitch:\t%4\n") Chris@35: .arg(adjFreqText) Chris@25: .arg(freqMin) Chris@65: .arg(adjPitchText) Chris@65: .arg(Pitch::getPitchLabelForFrequency(freqMin)); Chris@25: } Chris@25: Chris@38: if (haveValues) { Chris@905: double dbMin = AudioLevel::multiplier_to_dB(magMin); Chris@905: double dbMax = AudioLevel::multiplier_to_dB(magMax); Chris@43: QString dbMinString; Chris@43: QString dbMaxString; Chris@43: if (dbMin == AudioLevel::DB_FLOOR) { Chris@43: dbMinString = tr("-Inf"); Chris@43: } else { Chris@907: dbMinString = QString("%1").arg(lrint(dbMin)); Chris@43: } Chris@43: if (dbMax == AudioLevel::DB_FLOOR) { Chris@43: dbMaxString = tr("-Inf"); Chris@43: } else { Chris@907: dbMaxString = QString("%1").arg(lrint(dbMax)); Chris@43: } Chris@907: if (lrint(dbMin) != lrint(dbMax)) { Chris@199: text += tr("dB:\t%1 - %2").arg(dbMinString).arg(dbMaxString); Chris@25: } else { Chris@199: text += tr("dB:\t%1").arg(dbMinString); Chris@25: } Chris@38: if (phaseMin != phaseMax) { Chris@38: text += tr("\nPhase:\t%1 - %2").arg(phaseMin).arg(phaseMax); Chris@38: } else { Chris@38: text += tr("\nPhase:\t%1").arg(phaseMin); Chris@38: } Chris@25: } Chris@25: Chris@25: return text; Chris@0: } Chris@25: Chris@0: int Chris@40: SpectrogramLayer::getColourScaleWidth(QPainter &paint) const Chris@40: { Chris@40: int cw; Chris@40: Chris@119: cw = paint.fontMetrics().width("-80dB"); Chris@119: Chris@40: return cw; Chris@40: } Chris@40: Chris@40: int Chris@918: SpectrogramLayer::getVerticalScaleWidth(LayerGeometryProvider *, bool detailed, QPainter &paint) const Chris@0: { Chris@0: if (!m_model || !m_model->isOK()) return 0; Chris@0: Chris@607: int cw = 0; Chris@607: if (detailed) cw = getColourScaleWidth(paint); Chris@40: Chris@0: int tw = paint.fontMetrics().width(QString("%1") Chris@0: .arg(m_maxFrequency > 0 ? Chris@0: m_maxFrequency - 1 : Chris@0: m_model->getSampleRate() / 2)); Chris@0: Chris@234: int fw = paint.fontMetrics().width(tr("43Hz")); Chris@0: if (tw < fw) tw = fw; Chris@40: Chris@40: int tickw = (m_frequencyScale == LogFrequencyScale ? 10 : 4); Chris@0: Chris@40: return cw + tickw + tw + 13; Chris@0: } Chris@0: Chris@0: void Chris@918: SpectrogramLayer::paintVerticalScale(LayerGeometryProvider *v, bool detailed, QPainter &paint, QRect rect) const Chris@0: { Chris@0: if (!m_model || !m_model->isOK()) { Chris@0: return; Chris@0: } Chris@0: Chris@382: Profiler profiler("SpectrogramLayer::paintVerticalScale"); Chris@122: Chris@120: //!!! cache this? Chris@120: Chris@0: int h = rect.height(), w = rect.width(); Chris@0: Chris@40: int tickw = (m_frequencyScale == LogFrequencyScale ? 10 : 4); Chris@40: int pkw = (m_frequencyScale == LogFrequencyScale ? 10 : 0); Chris@40: Chris@805: int bins = m_fftSize / 2; Chris@907: sv_samplerate_t sr = m_model->getSampleRate(); Chris@0: Chris@0: if (m_maxFrequency > 0) { Chris@107: bins = int((double(m_maxFrequency) * m_fftSize) / sr + 0.1); Chris@107: if (bins > m_fftSize / 2) bins = m_fftSize / 2; Chris@0: } Chris@0: Chris@607: int cw = 0; Chris@607: Chris@607: if (detailed) cw = getColourScaleWidth(paint); Chris@119: int cbw = paint.fontMetrics().width("dB"); Chris@40: Chris@0: int py = -1; Chris@0: int textHeight = paint.fontMetrics().height(); Chris@0: int toff = -textHeight + paint.fontMetrics().ascent() + 2; Chris@0: Chris@607: if (detailed && (h > textHeight * 3 + 10)) { Chris@119: Chris@119: int topLines = 2; Chris@119: if (m_colourScale == PhaseColourScale) topLines = 1; Chris@119: Chris@119: int ch = h - textHeight * (topLines + 1) - 8; Chris@119: // paint.drawRect(4, textHeight + 4, cw - 1, ch + 1); Chris@119: paint.drawRect(4 + cw - cbw, textHeight * topLines + 4, cbw - 1, ch + 1); Chris@40: Chris@40: QString top, bottom; Chris@905: double min = m_viewMags[v].getMin(); Chris@905: double max = m_viewMags[v].getMax(); Chris@905: Chris@905: double dBmin = AudioLevel::multiplier_to_dB(min); Chris@905: double dBmax = AudioLevel::multiplier_to_dB(max); Chris@119: Chris@120: if (dBmax < -60.f) dBmax = -60.f; Chris@907: else top = QString("%1").arg(lrint(dBmax)); Chris@120: Chris@120: if (dBmin < dBmax - 60.f) dBmin = dBmax - 60.f; Chris@907: bottom = QString("%1").arg(lrint(dBmin)); Chris@119: Chris@119: //!!! & phase etc Chris@119: Chris@119: if (m_colourScale != PhaseColourScale) { Chris@119: paint.drawText((cw + 6 - paint.fontMetrics().width("dBFS")) / 2, Chris@119: 2 + textHeight + toff, "dBFS"); Chris@119: } Chris@119: Chris@119: // paint.drawText((cw + 6 - paint.fontMetrics().width(top)) / 2, Chris@119: paint.drawText(3 + cw - cbw - paint.fontMetrics().width(top), Chris@119: 2 + textHeight * topLines + toff + textHeight/2, top); Chris@119: Chris@119: paint.drawText(3 + cw - cbw - paint.fontMetrics().width(bottom), Chris@119: h + toff - 3 - textHeight/2, bottom); Chris@40: Chris@40: paint.save(); Chris@40: paint.setBrush(Qt::NoBrush); Chris@119: Chris@119: int lasty = 0; Chris@119: int lastdb = 0; Chris@119: Chris@40: for (int i = 0; i < ch; ++i) { Chris@119: Chris@905: double dBval = dBmin + (((dBmax - dBmin) * i) / (ch - 1)); Chris@119: int idb = int(dBval); Chris@119: Chris@905: double value = AudioLevel::dB_to_multiplier(dBval); Chris@119: int colour = getDisplayValue(v, value * m_gain); Chris@210: Chris@907: paint.setPen(m_palette.getColour((unsigned char)colour)); Chris@119: Chris@119: int y = textHeight * topLines + 4 + ch - i; Chris@119: Chris@119: paint.drawLine(5 + cw - cbw, y, cw + 2, y); Chris@119: Chris@119: if (i == 0) { Chris@119: lasty = y; Chris@119: lastdb = idb; Chris@119: } else if (i < ch - paint.fontMetrics().ascent() && Chris@120: idb != lastdb && Chris@119: ((abs(y - lasty) > textHeight && Chris@119: idb % 10 == 0) || Chris@119: (abs(y - lasty) > paint.fontMetrics().ascent() && Chris@119: idb % 5 == 0))) { Chris@287: paint.setPen(v->getBackground()); Chris@119: QString text = QString("%1").arg(idb); Chris@119: paint.drawText(3 + cw - cbw - paint.fontMetrics().width(text), Chris@119: y + toff + textHeight/2, text); Chris@287: paint.setPen(v->getForeground()); Chris@119: paint.drawLine(5 + cw - cbw, y, 8 + cw - cbw, y); Chris@119: lasty = y; Chris@119: lastdb = idb; Chris@119: } Chris@40: } Chris@40: paint.restore(); Chris@40: } Chris@40: Chris@40: paint.drawLine(cw + 7, 0, cw + 7, h); Chris@40: Chris@0: int bin = -1; Chris@0: Chris@918: for (int y = 0; y < v->getPaintHeight(); ++y) { Chris@0: Chris@905: double q0, q1; Chris@918: if (!getYBinRange(v, v->getPaintHeight() - y, q0, q1)) continue; Chris@0: Chris@0: int vy; Chris@0: Chris@0: if (int(q0) > bin) { Chris@0: vy = y; Chris@0: bin = int(q0); Chris@0: } else { Chris@0: continue; Chris@0: } Chris@0: Chris@907: int freq = int((sr * bin) / m_fftSize); Chris@0: Chris@0: if (py >= 0 && (vy - py) < textHeight - 1) { Chris@40: if (m_frequencyScale == LinearFrequencyScale) { Chris@40: paint.drawLine(w - tickw, h - vy, w, h - vy); Chris@40: } Chris@0: continue; Chris@0: } Chris@0: Chris@0: QString text = QString("%1").arg(freq); Chris@234: if (bin == 1) text = tr("%1Hz").arg(freq); // bin 0 is DC Chris@40: paint.drawLine(cw + 7, h - vy, w - pkw - 1, h - vy); Chris@0: Chris@0: if (h - vy - textHeight >= -2) { Chris@40: int tx = w - 3 - paint.fontMetrics().width(text) - std::max(tickw, pkw); Chris@0: paint.drawText(tx, h - vy + toff, text); Chris@0: } Chris@0: Chris@0: py = vy; Chris@0: } Chris@40: Chris@40: if (m_frequencyScale == LogFrequencyScale) { Chris@40: Chris@277: // piano keyboard Chris@277: Chris@690: PianoScale().paintPianoVertical Chris@690: (v, paint, QRect(w - pkw - 1, 0, pkw, h), Chris@690: getEffectiveMinFrequency(), getEffectiveMaxFrequency()); Chris@40: } Chris@608: Chris@608: m_haveDetailedScale = detailed; Chris@0: } Chris@0: Chris@187: class SpectrogramRangeMapper : public RangeMapper Chris@187: { Chris@187: public: Chris@901: SpectrogramRangeMapper(sv_samplerate_t sr, int /* fftsize */) : Chris@901: m_dist(sr / 2), Chris@901: m_s2(sqrt(sqrt(2))) { } Chris@187: ~SpectrogramRangeMapper() { } Chris@187: Chris@901: virtual int getPositionForValue(double value) const { Chris@901: Chris@901: double dist = m_dist; Chris@187: Chris@187: int n = 0; Chris@187: Chris@901: while (dist > (value + 0.00001) && dist > 0.1) { Chris@187: dist /= m_s2; Chris@187: ++n; Chris@187: } Chris@187: Chris@187: return n; Chris@187: } Chris@724: Chris@901: virtual int getPositionForValueUnclamped(double value) const { Chris@724: // We don't really support this Chris@724: return getPositionForValue(value); Chris@724: } Chris@187: Chris@901: virtual double getValueForPosition(int position) const { Chris@187: Chris@187: // Vertical zoom step 0 shows the entire range from DC -> Chris@187: // Nyquist frequency. Step 1 shows 2^(1/4) of the range of Chris@187: // step 0, and so on until the visible range is smaller than Chris@187: // the frequency step between bins at the current fft size. Chris@187: Chris@901: double dist = m_dist; Chris@187: Chris@187: int n = 0; Chris@187: while (n < position) { Chris@187: dist /= m_s2; Chris@187: ++n; Chris@187: } Chris@187: Chris@187: return dist; Chris@187: } Chris@187: Chris@901: virtual double getValueForPositionUnclamped(int position) const { Chris@724: // We don't really support this Chris@724: return getValueForPosition(position); Chris@724: } Chris@724: Chris@187: virtual QString getUnit() const { return "Hz"; } Chris@187: Chris@187: protected: Chris@901: double m_dist; Chris@901: double m_s2; Chris@187: }; Chris@187: Chris@133: int Chris@133: SpectrogramLayer::getVerticalZoomSteps(int &defaultStep) const Chris@133: { Chris@135: if (!m_model) return 0; Chris@187: Chris@907: sv_samplerate_t sr = m_model->getSampleRate(); Chris@187: Chris@187: SpectrogramRangeMapper mapper(sr, m_fftSize); Chris@187: Chris@905: // int maxStep = mapper.getPositionForValue((double(sr) / m_fftSize) + 0.001); Chris@187: int maxStep = mapper.getPositionForValue(0); Chris@905: int minStep = mapper.getPositionForValue(double(sr) / 2); Chris@250: Chris@805: int initialMax = m_initialMaxFrequency; Chris@907: if (initialMax == 0) initialMax = int(sr / 2); Chris@250: Chris@250: defaultStep = mapper.getPositionForValue(initialMax) - minStep; Chris@250: Chris@587: // SVDEBUG << "SpectrogramLayer::getVerticalZoomSteps: " << maxStep - minStep << " (" << maxStep <<"-" << minStep << "), default is " << defaultStep << " (from initial max freq " << initialMax << ")" << endl; Chris@187: Chris@187: return maxStep - minStep; Chris@133: } Chris@133: Chris@133: int Chris@133: SpectrogramLayer::getCurrentVerticalZoomStep() const Chris@133: { Chris@133: if (!m_model) return 0; Chris@133: Chris@905: double dmin, dmax; Chris@133: getDisplayExtents(dmin, dmax); Chris@133: Chris@187: SpectrogramRangeMapper mapper(m_model->getSampleRate(), m_fftSize); Chris@187: int n = mapper.getPositionForValue(dmax - dmin); Chris@587: // SVDEBUG << "SpectrogramLayer::getCurrentVerticalZoomStep: " << n << endl; Chris@133: return n; Chris@133: } Chris@133: Chris@133: void Chris@133: SpectrogramLayer::setVerticalZoomStep(int step) Chris@133: { Chris@187: if (!m_model) return; Chris@187: Chris@905: double dmin = m_minFrequency, dmax = m_maxFrequency; Chris@253: // getDisplayExtents(dmin, dmax); Chris@253: Chris@682: // cerr << "current range " << dmin << " -> " << dmax << ", range " << dmax-dmin << ", mid " << (dmax + dmin)/2 << endl; Chris@133: Chris@907: sv_samplerate_t sr = m_model->getSampleRate(); Chris@187: SpectrogramRangeMapper mapper(sr, m_fftSize); Chris@905: double newdist = mapper.getValueForPosition(step); Chris@905: Chris@905: double newmin, newmax; Chris@253: Chris@253: if (m_frequencyScale == LogFrequencyScale) { Chris@253: Chris@253: // need to pick newmin and newmax such that Chris@253: // Chris@253: // (log(newmin) + log(newmax)) / 2 == logmid Chris@253: // and Chris@253: // newmax - newmin = newdist Chris@253: // Chris@253: // so log(newmax - newdist) + log(newmax) == 2logmid Chris@253: // log(newmax(newmax - newdist)) == 2logmid Chris@253: // newmax.newmax - newmax.newdist == exp(2logmid) Chris@253: // newmax^2 + (-newdist)newmax + -exp(2logmid) == 0 Chris@253: // quadratic with a = 1, b = -newdist, c = -exp(2logmid), all known Chris@253: // Chris@253: // positive root Chris@253: // newmax = (newdist + sqrt(newdist^2 + 4exp(2logmid))) / 2 Chris@253: // Chris@253: // but logmid = (log(dmin) + log(dmax)) / 2 Chris@253: // so exp(2logmid) = exp(log(dmin) + log(dmax)) Chris@253: // = exp(log(dmin.dmax)) Chris@253: // = dmin.dmax Chris@253: // so newmax = (newdist + sqrtf(newdist^2 + 4dmin.dmax)) / 2 Chris@253: Chris@907: newmax = (newdist + sqrt(newdist*newdist + 4*dmin*dmax)) / 2; Chris@253: newmin = newmax - newdist; Chris@253: Chris@682: // cerr << "newmin = " << newmin << ", newmax = " << newmax << endl; Chris@253: Chris@253: } else { Chris@905: double dmid = (dmax + dmin) / 2; Chris@253: newmin = dmid - newdist / 2; Chris@253: newmax = dmid + newdist / 2; Chris@253: } Chris@187: Chris@905: double mmin, mmax; Chris@187: mmin = 0; Chris@905: mmax = double(sr) / 2; Chris@133: Chris@187: if (newmin < mmin) { Chris@187: newmax += (mmin - newmin); Chris@187: newmin = mmin; Chris@187: } Chris@187: if (newmax > mmax) { Chris@187: newmax = mmax; Chris@187: } Chris@133: Chris@587: // SVDEBUG << "SpectrogramLayer::setVerticalZoomStep: " << step << ": " << newmin << " -> " << newmax << " (range " << newdist << ")" << endl; Chris@253: Chris@907: setMinFrequency(int(lrint(newmin))); Chris@907: setMaxFrequency(int(lrint(newmax))); Chris@187: } Chris@187: Chris@187: RangeMapper * Chris@187: SpectrogramLayer::getNewVerticalZoomRangeMapper() const Chris@187: { Chris@187: if (!m_model) return 0; Chris@187: return new SpectrogramRangeMapper(m_model->getSampleRate(), m_fftSize); Chris@133: } Chris@133: Chris@273: void Chris@918: SpectrogramLayer::updateMeasureRectYCoords(LayerGeometryProvider *v, const MeasureRect &r) const Chris@273: { Chris@273: int y0 = 0; Chris@907: if (r.startY > 0.0) y0 = int(getYForFrequency(v, r.startY)); Chris@273: Chris@273: int y1 = y0; Chris@907: if (r.endY > 0.0) y1 = int(getYForFrequency(v, r.endY)); Chris@273: Chris@587: // SVDEBUG << "SpectrogramLayer::updateMeasureRectYCoords: start " << r.startY << " -> " << y0 << ", end " << r.endY << " -> " << y1 << endl; Chris@273: Chris@273: r.pixrect = QRect(r.pixrect.x(), y0, r.pixrect.width(), y1 - y0); Chris@273: } Chris@273: Chris@273: void Chris@918: SpectrogramLayer::setMeasureRectYCoord(LayerGeometryProvider *v, MeasureRect &r, bool start, int y) const Chris@273: { Chris@273: if (start) { Chris@273: r.startY = getFrequencyForY(v, y); Chris@273: r.endY = r.startY; Chris@273: } else { Chris@273: r.endY = getFrequencyForY(v, y); Chris@273: } Chris@587: // SVDEBUG << "SpectrogramLayer::setMeasureRectYCoord: start " << r.startY << " <- " << y << ", end " << r.endY << " <- " << y << endl; Chris@273: Chris@273: } Chris@273: Chris@316: void Chris@316: SpectrogramLayer::toXml(QTextStream &stream, Chris@316: QString indent, QString extraAttributes) const Chris@6: { Chris@6: QString s; Chris@6: Chris@6: s += QString("channel=\"%1\" " Chris@6: "windowSize=\"%2\" " Chris@153: "windowHopLevel=\"%3\" " Chris@153: "gain=\"%4\" " Chris@153: "threshold=\"%5\" ") Chris@6: .arg(m_channel) Chris@6: .arg(m_windowSize) Chris@97: .arg(m_windowHopLevel) Chris@37: .arg(m_gain) Chris@37: .arg(m_threshold); Chris@37: Chris@37: s += QString("minFrequency=\"%1\" " Chris@37: "maxFrequency=\"%2\" " Chris@37: "colourScale=\"%3\" " Chris@37: "colourScheme=\"%4\" " Chris@37: "colourRotation=\"%5\" " Chris@37: "frequencyScale=\"%6\" " Chris@761: "binDisplay=\"%7\" ") Chris@37: .arg(m_minFrequency) Chris@6: .arg(m_maxFrequency) Chris@6: .arg(m_colourScale) Chris@197: .arg(m_colourMap) Chris@37: .arg(m_colourRotation) Chris@35: .arg(m_frequencyScale) Chris@761: .arg(m_binDisplay); Chris@761: Chris@761: s += QString("normalizeColumns=\"%1\" " Chris@761: "normalizeVisibleArea=\"%2\" " Chris@761: "normalizeHybrid=\"%3\" ") Chris@862: .arg(m_normalization == NormalizeColumns ? "true" : "false") Chris@862: .arg(m_normalization == NormalizeVisibleArea ? "true" : "false") Chris@862: .arg(m_normalization == NormalizeHybrid ? "true" : "false"); Chris@6: Chris@316: Layer::toXml(stream, indent, extraAttributes + " " + s); Chris@6: } Chris@6: Chris@11: void Chris@11: SpectrogramLayer::setProperties(const QXmlAttributes &attributes) Chris@11: { Chris@11: bool ok = false; Chris@11: Chris@11: int channel = attributes.value("channel").toInt(&ok); Chris@11: if (ok) setChannel(channel); Chris@11: Chris@805: int windowSize = attributes.value("windowSize").toUInt(&ok); Chris@11: if (ok) setWindowSize(windowSize); Chris@11: Chris@805: int windowHopLevel = attributes.value("windowHopLevel").toUInt(&ok); Chris@97: if (ok) setWindowHopLevel(windowHopLevel); Chris@97: else { Chris@805: int windowOverlap = attributes.value("windowOverlap").toUInt(&ok); Chris@97: // a percentage value Chris@97: if (ok) { Chris@97: if (windowOverlap == 0) setWindowHopLevel(0); Chris@97: else if (windowOverlap == 25) setWindowHopLevel(1); Chris@97: else if (windowOverlap == 50) setWindowHopLevel(2); Chris@97: else if (windowOverlap == 75) setWindowHopLevel(3); Chris@97: else if (windowOverlap == 90) setWindowHopLevel(4); Chris@97: } Chris@97: } Chris@11: Chris@11: float gain = attributes.value("gain").toFloat(&ok); Chris@11: if (ok) setGain(gain); Chris@11: Chris@37: float threshold = attributes.value("threshold").toFloat(&ok); Chris@37: if (ok) setThreshold(threshold); Chris@37: Chris@805: int minFrequency = attributes.value("minFrequency").toUInt(&ok); Chris@187: if (ok) { Chris@587: SVDEBUG << "SpectrogramLayer::setProperties: setting min freq to " << minFrequency << endl; Chris@187: setMinFrequency(minFrequency); Chris@187: } Chris@37: Chris@805: int maxFrequency = attributes.value("maxFrequency").toUInt(&ok); Chris@187: if (ok) { Chris@587: SVDEBUG << "SpectrogramLayer::setProperties: setting max freq to " << maxFrequency << endl; Chris@187: setMaxFrequency(maxFrequency); Chris@187: } Chris@11: Chris@11: ColourScale colourScale = (ColourScale) Chris@11: attributes.value("colourScale").toInt(&ok); Chris@11: if (ok) setColourScale(colourScale); Chris@11: Chris@197: int colourMap = attributes.value("colourScheme").toInt(&ok); Chris@197: if (ok) setColourMap(colourMap); Chris@11: Chris@37: int colourRotation = attributes.value("colourRotation").toInt(&ok); Chris@37: if (ok) setColourRotation(colourRotation); Chris@37: Chris@11: FrequencyScale frequencyScale = (FrequencyScale) Chris@11: attributes.value("frequencyScale").toInt(&ok); Chris@11: if (ok) setFrequencyScale(frequencyScale); Chris@35: Chris@37: BinDisplay binDisplay = (BinDisplay) Chris@37: attributes.value("binDisplay").toInt(&ok); Chris@37: if (ok) setBinDisplay(binDisplay); Chris@36: Chris@36: bool normalizeColumns = Chris@36: (attributes.value("normalizeColumns").trimmed() == "true"); Chris@862: if (normalizeColumns) { Chris@862: setNormalization(NormalizeColumns); Chris@862: } Chris@153: Chris@153: bool normalizeVisibleArea = Chris@153: (attributes.value("normalizeVisibleArea").trimmed() == "true"); Chris@862: if (normalizeVisibleArea) { Chris@862: setNormalization(NormalizeVisibleArea); Chris@862: } Chris@761: Chris@761: bool normalizeHybrid = Chris@761: (attributes.value("normalizeHybrid").trimmed() == "true"); Chris@862: if (normalizeHybrid) { Chris@862: setNormalization(NormalizeHybrid); Chris@862: } Chris@11: } Chris@11: