Chris@0: /* -*- c-basic-offset: 4 -*- vi:set ts=8 sts=4 sw=4: */ Chris@0: Chris@0: /* Chris@0: A waveform viewer and audio annotation editor. Chris@5: Chris Cannam, Queen Mary University of London, 2005-2006 Chris@0: Chris@0: This is experimental software. Not for distribution. Chris@0: */ Chris@0: Chris@0: #include "SpectrogramLayer.h" Chris@0: Chris@0: #include "base/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@0: Chris@0: #include Chris@0: #include Chris@0: #include Chris@0: #include Chris@0: #include Chris@0: Chris@0: #include Chris@0: Chris@0: #include Chris@0: #include Chris@0: Chris@0: //#define DEBUG_SPECTROGRAM_REPAINT 1 Chris@0: Chris@0: Chris@0: SpectrogramLayer::SpectrogramLayer(View *w, Configuration config) : Chris@0: Layer(w), Chris@0: m_model(0), Chris@0: m_channel(0), Chris@0: m_windowSize(1024), Chris@0: m_windowType(HanningWindow), Chris@0: m_windowOverlap(50), Chris@0: m_gain(1.0), Chris@9: m_colourRotation(0), Chris@0: m_maxFrequency(8000), Chris@0: m_colourScale(dBColourScale), Chris@0: m_colourScheme(DefaultColours), Chris@0: m_frequencyScale(LinearFrequencyScale), Chris@0: m_cache(0), Chris@0: m_cacheInvalid(true), Chris@0: m_pixmapCache(0), Chris@0: m_pixmapCacheInvalid(true), Chris@0: m_fillThread(0), Chris@0: m_updateTimer(0), Chris@0: m_lastFillExtent(0), Chris@29: m_dormant(false), Chris@0: m_exiting(false) Chris@0: { Chris@0: if (config == MelodicRange) { Chris@0: setWindowSize(8192); Chris@0: setWindowOverlap(90); Chris@0: setWindowType(ParzenWindow); Chris@0: setMaxFrequency(1000); Chris@0: setColourScale(LinearColourScale); Chris@0: } Chris@0: Chris@0: if (m_view) m_view->setLightBackground(false); Chris@0: m_view->addLayer(this); Chris@0: } Chris@0: Chris@0: SpectrogramLayer::~SpectrogramLayer() Chris@0: { Chris@0: delete m_updateTimer; Chris@0: m_updateTimer = 0; Chris@0: Chris@0: m_exiting = true; Chris@0: m_condition.wakeAll(); Chris@0: if (m_fillThread) m_fillThread->wait(); Chris@0: delete m_fillThread; Chris@0: Chris@0: delete m_cache; Chris@0: } Chris@0: Chris@0: void Chris@0: SpectrogramLayer::setModel(const DenseTimeValueModel *model) Chris@0: { Chris@0: m_mutex.lock(); Chris@0: m_model = model; Chris@31: delete m_cache; Chris@31: m_cache = 0; Chris@0: m_mutex.unlock(); Chris@0: Chris@0: if (!m_model || !m_model->isOK()) return; Chris@0: Chris@0: connect(m_model, SIGNAL(modelChanged()), this, SIGNAL(modelChanged())); Chris@0: connect(m_model, SIGNAL(modelChanged(size_t, size_t)), Chris@0: this, SIGNAL(modelChanged(size_t, size_t))); Chris@0: Chris@0: connect(m_model, SIGNAL(completionChanged()), Chris@0: this, SIGNAL(modelCompletionChanged())); Chris@0: Chris@0: connect(m_model, SIGNAL(modelChanged()), this, SLOT(cacheInvalid())); Chris@0: connect(m_model, SIGNAL(modelChanged(size_t, size_t)), Chris@0: this, SLOT(cacheInvalid(size_t, size_t))); Chris@0: Chris@0: emit modelReplaced(); Chris@0: fillCache(); Chris@0: } Chris@0: Chris@0: Layer::PropertyList Chris@0: SpectrogramLayer::getProperties() const Chris@0: { Chris@0: PropertyList list; Chris@0: list.push_back(tr("Colour")); Chris@0: list.push_back(tr("Colour Scale")); Chris@0: list.push_back(tr("Window Type")); Chris@0: list.push_back(tr("Window Size")); Chris@0: list.push_back(tr("Window Overlap")); Chris@0: list.push_back(tr("Gain")); Chris@9: list.push_back(tr("Colour Rotation")); Chris@0: list.push_back(tr("Max Frequency")); Chris@0: list.push_back(tr("Frequency Scale")); Chris@0: return list; Chris@0: } Chris@0: Chris@0: Layer::PropertyType Chris@0: SpectrogramLayer::getPropertyType(const PropertyName &name) const Chris@0: { Chris@0: if (name == tr("Gain")) return RangeProperty; Chris@9: if (name == tr("Colour Rotation")) return RangeProperty; Chris@0: return ValueProperty; Chris@0: } Chris@0: Chris@0: QString Chris@0: SpectrogramLayer::getPropertyGroupName(const PropertyName &name) const Chris@0: { Chris@0: if (name == tr("Window Size") || Chris@0: name == tr("Window Overlap")) return tr("Window"); Chris@0: if (name == tr("Gain") || Chris@9: name == tr("Colour Rotation") || Chris@0: name == tr("Colour Scale")) return tr("Scale"); Chris@0: if (name == tr("Max Frequency") || Chris@0: name == tr("Frequency Scale")) return tr("Frequency"); Chris@0: return QString(); Chris@0: } Chris@0: Chris@0: int Chris@0: SpectrogramLayer::getPropertyRangeAndValue(const PropertyName &name, Chris@0: int *min, int *max) const Chris@0: { Chris@0: int deft = 0; Chris@0: Chris@10: int throwaway; Chris@10: if (!min) min = &throwaway; Chris@10: if (!max) max = &throwaway; Chris@10: Chris@0: if (name == tr("Gain")) { Chris@0: Chris@0: *min = -50; Chris@0: *max = 50; Chris@0: Chris@0: deft = lrint(log10(m_gain) * 20.0); Chris@0: if (deft < *min) deft = *min; Chris@0: if (deft > *max) deft = *max; Chris@0: Chris@9: } else if (name == tr("Colour Rotation")) { Chris@9: Chris@9: *min = 0; Chris@9: *max = 256; Chris@9: Chris@9: deft = m_colourRotation; Chris@9: Chris@0: } else if (name == tr("Colour Scale")) { Chris@0: Chris@0: *min = 0; Chris@0: *max = 3; Chris@0: Chris@0: deft = (int)m_colourScale; Chris@0: Chris@0: } else if (name == tr("Colour")) { Chris@0: Chris@0: *min = 0; Chris@0: *max = 5; Chris@0: Chris@0: deft = (int)m_colourScheme; Chris@0: Chris@0: } else if (name == tr("Window Type")) { Chris@0: Chris@0: *min = 0; Chris@0: *max = 6; Chris@0: Chris@0: deft = (int)m_windowType; Chris@0: Chris@0: } else if (name == tr("Window Size")) { Chris@0: Chris@0: *min = 0; Chris@0: *max = 10; Chris@0: Chris@0: deft = 0; Chris@0: int ws = m_windowSize; Chris@0: while (ws > 32) { ws >>= 1; deft ++; } Chris@0: Chris@0: } else if (name == tr("Window Overlap")) { Chris@0: Chris@0: *min = 0; Chris@0: *max = 4; Chris@0: Chris@0: deft = m_windowOverlap / 25; Chris@0: if (m_windowOverlap == 90) deft = 4; Chris@0: Chris@0: } else if (name == tr("Max Frequency")) { Chris@0: Chris@0: *min = 0; Chris@0: *max = 9; Chris@0: Chris@0: switch (m_maxFrequency) { Chris@0: case 500: deft = 0; break; Chris@0: case 1000: deft = 1; break; Chris@0: case 1500: deft = 2; break; Chris@0: case 2000: deft = 3; break; Chris@0: case 4000: deft = 4; break; Chris@0: case 6000: deft = 5; break; Chris@0: case 8000: deft = 6; break; Chris@0: case 12000: deft = 7; break; Chris@0: case 16000: deft = 8; break; Chris@0: default: deft = 9; break; Chris@0: } Chris@0: Chris@0: } else if (name == tr("Frequency Scale")) { Chris@0: Chris@0: *min = 0; Chris@0: *max = 1; Chris@0: deft = (int)m_frequencyScale; Chris@0: Chris@0: } else { Chris@0: deft = Layer::getPropertyRangeAndValue(name, min, max); Chris@0: } Chris@0: Chris@0: return deft; Chris@0: } Chris@0: Chris@0: QString Chris@0: SpectrogramLayer::getPropertyValueLabel(const PropertyName &name, Chris@9: int value) const Chris@0: { Chris@0: if (name == tr("Colour")) { Chris@0: switch (value) { Chris@0: default: Chris@0: case 0: return tr("Default"); Chris@0: case 1: return tr("White on Black"); Chris@0: case 2: return tr("Black on White"); Chris@0: case 3: return tr("Red on Blue"); Chris@0: case 4: return tr("Yellow on Black"); Chris@0: case 5: return tr("Red on Black"); Chris@0: } Chris@0: } Chris@0: if (name == tr("Colour Scale")) { Chris@0: switch (value) { Chris@0: default: Chris@0: case 0: return tr("Level Linear"); Chris@0: case 1: return tr("Level Meter"); Chris@0: case 2: return tr("Level dB"); Chris@0: case 3: return tr("Phase"); Chris@0: } Chris@0: } Chris@0: if (name == tr("Window Type")) { Chris@0: switch ((WindowType)value) { Chris@0: default: Chris@0: case RectangularWindow: return tr("Rectangular"); Chris@0: case BartlettWindow: return tr("Bartlett"); Chris@0: case HammingWindow: return tr("Hamming"); Chris@0: case HanningWindow: return tr("Hanning"); Chris@0: case BlackmanWindow: return tr("Blackman"); Chris@0: case GaussianWindow: return tr("Gaussian"); Chris@0: case ParzenWindow: return tr("Parzen"); Chris@0: } Chris@0: } Chris@0: if (name == tr("Window Size")) { Chris@0: return QString("%1").arg(32 << value); Chris@0: } Chris@0: if (name == tr("Window Overlap")) { Chris@0: switch (value) { Chris@0: default: Chris@0: case 0: return tr("None"); Chris@0: case 1: return tr("25 %"); Chris@0: case 2: return tr("50 %"); Chris@0: case 3: return tr("75 %"); Chris@0: case 4: return tr("90 %"); Chris@0: } Chris@0: } Chris@0: if (name == tr("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@0: case 9: return tr("All"); Chris@0: } Chris@0: } Chris@0: if (name == tr("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@0: return tr(""); Chris@0: } Chris@0: Chris@0: void Chris@0: SpectrogramLayer::setProperty(const PropertyName &name, int value) Chris@0: { Chris@0: if (name == tr("Gain")) { Chris@0: setGain(pow(10, float(value)/20.0)); Chris@9: } else if (name == tr("Colour Rotation")) { Chris@9: setColourRotation(value); Chris@0: } else if (name == tr("Colour")) { Chris@0: if (m_view) m_view->setLightBackground(value == 2); Chris@0: switch (value) { Chris@0: default: Chris@0: case 0: setColourScheme(DefaultColours); break; Chris@0: case 1: setColourScheme(WhiteOnBlack); break; Chris@0: case 2: setColourScheme(BlackOnWhite); break; Chris@0: case 3: setColourScheme(RedOnBlue); break; Chris@0: case 4: setColourScheme(YellowOnBlack); break; Chris@0: case 5: setColourScheme(RedOnBlack); break; Chris@0: } Chris@0: } else if (name == tr("Window Type")) { Chris@0: setWindowType(WindowType(value)); Chris@0: } else if (name == tr("Window Size")) { Chris@0: setWindowSize(32 << value); Chris@0: } else if (name == tr("Window Overlap")) { Chris@0: if (value == 4) setWindowOverlap(90); Chris@0: else setWindowOverlap(25 * value); Chris@0: } else if (name == tr("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@0: } else if (name == tr("Colour Scale")) { Chris@0: switch (value) { Chris@0: default: Chris@0: case 0: setColourScale(LinearColourScale); break; Chris@0: case 1: setColourScale(MeterColourScale); break; Chris@0: case 2: setColourScale(dBColourScale); break; Chris@0: case 3: setColourScale(PhaseColourScale); break; Chris@0: } Chris@0: } else if (name == tr("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@0: } Chris@0: } Chris@0: Chris@0: void Chris@0: SpectrogramLayer::setChannel(int ch) Chris@0: { Chris@0: if (m_channel == ch) return; Chris@0: Chris@0: m_mutex.lock(); Chris@0: m_cacheInvalid = true; Chris@0: m_pixmapCacheInvalid = true; Chris@0: Chris@0: m_channel = ch; Chris@9: Chris@9: m_mutex.unlock(); Chris@9: Chris@0: emit layerParametersChanged(); Chris@9: Chris@0: fillCache(); 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@0: SpectrogramLayer::setWindowSize(size_t ws) Chris@0: { Chris@0: if (m_windowSize == ws) return; Chris@0: Chris@0: m_mutex.lock(); Chris@0: m_cacheInvalid = true; Chris@0: m_pixmapCacheInvalid = true; Chris@0: Chris@0: m_windowSize = ws; Chris@0: Chris@0: m_mutex.unlock(); Chris@9: Chris@9: emit layerParametersChanged(); Chris@9: Chris@0: fillCache(); Chris@0: } Chris@0: Chris@0: size_t Chris@0: SpectrogramLayer::getWindowSize() const Chris@0: { Chris@0: return m_windowSize; Chris@0: } Chris@0: Chris@0: void Chris@0: SpectrogramLayer::setWindowOverlap(size_t wi) Chris@0: { Chris@0: if (m_windowOverlap == wi) return; Chris@0: Chris@0: m_mutex.lock(); Chris@0: m_cacheInvalid = true; Chris@0: m_pixmapCacheInvalid = true; Chris@0: Chris@0: m_windowOverlap = wi; Chris@0: Chris@0: m_mutex.unlock(); Chris@9: Chris@9: emit layerParametersChanged(); Chris@9: Chris@0: fillCache(); Chris@0: } Chris@0: Chris@0: size_t Chris@0: SpectrogramLayer::getWindowOverlap() const Chris@0: { Chris@0: return m_windowOverlap; Chris@0: } Chris@0: Chris@0: void Chris@0: SpectrogramLayer::setWindowType(WindowType w) Chris@0: { Chris@0: if (m_windowType == w) return; Chris@0: Chris@0: m_mutex.lock(); Chris@0: m_cacheInvalid = true; Chris@0: m_pixmapCacheInvalid = true; Chris@0: Chris@0: m_windowType = w; Chris@0: Chris@0: m_mutex.unlock(); Chris@9: Chris@9: emit layerParametersChanged(); Chris@9: Chris@0: fillCache(); 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@0: if (m_gain == gain) return; //!!! inadequate for floats! Chris@0: Chris@0: m_mutex.lock(); Chris@0: m_cacheInvalid = true; Chris@0: m_pixmapCacheInvalid = true; Chris@0: Chris@0: m_gain = gain; Chris@0: Chris@0: m_mutex.unlock(); Chris@9: Chris@9: emit layerParametersChanged(); Chris@9: Chris@0: fillCache(); 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@0: SpectrogramLayer::setMaxFrequency(size_t mf) Chris@0: { Chris@0: if (m_maxFrequency == mf) return; Chris@0: Chris@0: m_mutex.lock(); Chris@1: // don't need to invalidate main cache here Chris@0: m_pixmapCacheInvalid = true; Chris@0: Chris@0: m_maxFrequency = mf; Chris@0: Chris@0: m_mutex.unlock(); Chris@9: Chris@9: emit layerParametersChanged(); Chris@0: } Chris@0: Chris@0: size_t 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@9: m_mutex.lock(); Chris@9: // don't need to invalidate main cache here Chris@9: m_pixmapCacheInvalid = true; 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@9: rotateCacheColourmap(-distance); Chris@9: m_colourRotation = r; Chris@9: } Chris@9: Chris@9: m_mutex.unlock(); 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@0: m_mutex.lock(); Chris@0: m_cacheInvalid = true; Chris@0: m_pixmapCacheInvalid = true; Chris@0: Chris@0: m_colourScale = colourScale; Chris@0: Chris@0: m_mutex.unlock(); Chris@0: fillCache(); Chris@9: 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@0: SpectrogramLayer::setColourScheme(ColourScheme scheme) Chris@0: { Chris@0: if (m_colourScheme == scheme) return; Chris@0: Chris@0: m_mutex.lock(); Chris@0: // don't need to invalidate main cache here Chris@0: m_pixmapCacheInvalid = true; Chris@0: Chris@0: m_colourScheme = scheme; Chris@0: setCacheColourmap(); Chris@9: Chris@9: m_mutex.unlock(); Chris@9: Chris@0: emit layerParametersChanged(); Chris@0: } Chris@0: Chris@0: SpectrogramLayer::ColourScheme Chris@0: SpectrogramLayer::getColourScheme() const Chris@0: { Chris@0: return m_colourScheme; 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@0: m_mutex.lock(); Chris@0: // don't need to invalidate main cache here Chris@0: m_pixmapCacheInvalid = true; Chris@0: Chris@0: m_frequencyScale = frequencyScale; Chris@0: Chris@0: m_mutex.unlock(); 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@29: SpectrogramLayer::setLayerDormant() Chris@29: { Chris@29: m_mutex.lock(); Chris@29: m_dormant = true; Chris@29: delete m_cache; Chris@29: m_cache = 0; Chris@29: m_pixmapCacheInvalid = true; Chris@29: delete m_pixmapCache; Chris@29: m_pixmapCache = 0; Chris@29: m_mutex.unlock(); Chris@29: } Chris@29: Chris@29: void Chris@0: SpectrogramLayer::cacheInvalid() Chris@0: { Chris@0: m_cacheInvalid = true; Chris@0: m_pixmapCacheInvalid = true; Chris@0: m_cachedInitialVisibleArea = false; Chris@0: fillCache(); Chris@0: } Chris@0: Chris@0: void Chris@0: SpectrogramLayer::cacheInvalid(size_t, size_t) Chris@0: { Chris@0: // for now (or forever?) Chris@0: cacheInvalid(); Chris@0: } Chris@0: Chris@0: void Chris@0: SpectrogramLayer::fillCache() Chris@0: { Chris@0: #ifdef DEBUG_SPECTROGRAM_REPAINT Chris@0: std::cerr << "SpectrogramLayer::fillCache" << std::endl; Chris@0: #endif Chris@0: QMutexLocker locker(&m_mutex); Chris@0: Chris@0: m_lastFillExtent = 0; Chris@0: Chris@0: delete m_updateTimer; Chris@0: m_updateTimer = new QTimer(this); Chris@0: connect(m_updateTimer, SIGNAL(timeout()), this, SLOT(fillTimerTimedOut())); Chris@0: m_updateTimer->start(200); Chris@0: Chris@0: if (!m_fillThread) { Chris@0: std::cerr << "SpectrogramLayer::fillCache creating thread" << std::endl; Chris@0: m_fillThread = new CacheFillThread(*this); Chris@0: m_fillThread->start(); Chris@0: } Chris@0: Chris@0: m_condition.wakeAll(); Chris@0: } Chris@0: Chris@0: void Chris@0: SpectrogramLayer::fillTimerTimedOut() Chris@0: { Chris@0: if (m_fillThread && m_model) { Chris@0: size_t fillExtent = m_fillThread->getFillExtent(); Chris@0: #ifdef DEBUG_SPECTROGRAM_REPAINT Chris@0: std::cerr << "SpectrogramLayer::fillTimerTimedOut: extent " << fillExtent << ", last " << m_lastFillExtent << ", total " << m_model->getEndFrame() << std::endl; Chris@0: #endif Chris@0: if (fillExtent >= m_lastFillExtent) { Chris@0: if (fillExtent >= m_model->getEndFrame() && m_lastFillExtent > 0) { Chris@0: #ifdef DEBUG_SPECTROGRAM_REPAINT Chris@0: std::cerr << "complete!" << std::endl; Chris@0: #endif Chris@0: emit modelChanged(); Chris@0: m_pixmapCacheInvalid = true; Chris@0: delete m_updateTimer; Chris@0: m_updateTimer = 0; Chris@0: m_lastFillExtent = 0; Chris@0: } else if (fillExtent > m_lastFillExtent) { Chris@0: #ifdef DEBUG_SPECTROGRAM_REPAINT Chris@0: std::cerr << "SpectrogramLayer: emitting modelChanged(" Chris@0: << m_lastFillExtent << "," << fillExtent << ")" << std::endl; Chris@0: #endif Chris@0: emit modelChanged(m_lastFillExtent, fillExtent); Chris@0: m_pixmapCacheInvalid = true; Chris@0: m_lastFillExtent = fillExtent; Chris@0: } Chris@0: } else { Chris@0: if (m_view) { Chris@0: size_t sf = 0; Chris@0: if (m_view->getStartFrame() > 0) sf = m_view->getStartFrame(); Chris@0: #ifdef DEBUG_SPECTROGRAM_REPAINT Chris@0: std::cerr << "SpectrogramLayer: going backwards, emitting modelChanged(" Chris@0: << sf << "," << m_view->getEndFrame() << ")" << std::endl; Chris@0: #endif Chris@0: emit modelChanged(sf, m_view->getEndFrame()); Chris@0: m_pixmapCacheInvalid = true; Chris@0: } Chris@0: m_lastFillExtent = fillExtent; Chris@0: } Chris@0: } Chris@0: } Chris@0: Chris@0: void Chris@0: SpectrogramLayer::setCacheColourmap() Chris@0: { Chris@0: if (m_cacheInvalid || !m_cache) return; Chris@0: Chris@10: int formerRotation = m_colourRotation; Chris@10: Chris@31: // m_cache->setNumColors(256); Chris@0: Chris@31: // m_cache->setColour(0, qRgb(255, 255, 255)); Chris@31: m_cache->setColour(0, Qt::white); Chris@0: Chris@0: for (int pixel = 1; pixel < 256; ++pixel) { Chris@0: Chris@0: QColor colour; Chris@0: int hue, px; Chris@0: Chris@0: switch (m_colourScheme) { Chris@0: Chris@0: default: Chris@0: case DefaultColours: Chris@0: hue = 256 - pixel; Chris@0: colour = QColor::fromHsv(hue, pixel/2 + 128, pixel); Chris@0: break; Chris@0: Chris@0: case WhiteOnBlack: Chris@0: colour = QColor(pixel, pixel, pixel); Chris@0: break; Chris@0: Chris@0: case BlackOnWhite: Chris@0: colour = QColor(256-pixel, 256-pixel, 256-pixel); Chris@0: break; Chris@0: Chris@0: case RedOnBlue: Chris@0: colour = QColor(pixel > 128 ? (pixel - 128) * 2 : 0, 0, Chris@0: pixel < 128 ? pixel : (256 - pixel)); Chris@0: break; Chris@0: Chris@0: case YellowOnBlack: Chris@0: px = 256 - pixel; Chris@0: colour = QColor(px < 64 ? 255 - px/2 : Chris@0: px < 128 ? 224 - (px - 64) : Chris@0: px < 192 ? 160 - (px - 128) * 3 / 2 : Chris@0: 256 - px, Chris@0: pixel, Chris@0: pixel / 4); Chris@0: break; Chris@0: Chris@0: case RedOnBlack: Chris@0: colour = QColor::fromHsv(10, pixel, pixel); Chris@0: break; Chris@0: } Chris@0: Chris@31: // m_cache->setColor Chris@31: // (pixel, qRgb(colour.red(), colour.green(), colour.blue())); Chris@31: m_cache->setColour(pixel, colour); Chris@0: } Chris@9: Chris@9: m_colourRotation = 0; Chris@10: rotateCacheColourmap(m_colourRotation - formerRotation); Chris@10: m_colourRotation = formerRotation; Chris@9: } Chris@9: Chris@9: void Chris@9: SpectrogramLayer::rotateCacheColourmap(int distance) Chris@9: { Chris@10: if (!m_cache) return; Chris@10: Chris@31: QColor newPixels[256]; Chris@9: Chris@31: newPixels[0] = m_cache->getColour(0); 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@31: newPixels[target] = m_cache->getColour(pixel); Chris@9: } Chris@9: Chris@9: for (int pixel = 0; pixel < 256; ++pixel) { Chris@31: m_cache->setColour(pixel, newPixels[pixel]); Chris@9: } Chris@0: } Chris@0: Chris@0: bool Chris@0: SpectrogramLayer::fillCacheColumn(int column, double *input, Chris@0: fftw_complex *output, Chris@0: fftw_plan plan, Chris@9: size_t windowSize, Chris@9: size_t increment, Chris@0: const Window &windower, Chris@0: bool lock) const Chris@0: { Chris@0: int startFrame = increment * column; Chris@9: int endFrame = startFrame + windowSize; Chris@0: Chris@9: startFrame -= int(windowSize - increment) / 2; Chris@9: endFrame -= int(windowSize - increment) / 2; Chris@0: size_t pfx = 0; Chris@0: Chris@0: if (startFrame < 0) { Chris@0: pfx = size_t(-startFrame); Chris@0: for (size_t i = 0; i < pfx; ++i) { Chris@0: input[i] = 0.0; Chris@0: } Chris@0: } Chris@0: Chris@0: size_t got = m_model->getValues(m_channel, startFrame + pfx, Chris@0: endFrame, input + pfx); Chris@9: while (got + pfx < windowSize) { Chris@0: input[got + pfx] = 0.0; Chris@0: ++got; Chris@0: } Chris@0: Chris@0: if (m_gain != 1.0) { Chris@9: for (size_t i = 0; i < windowSize; ++i) { Chris@0: input[i] *= m_gain; Chris@0: } Chris@0: } Chris@0: Chris@0: windower.cut(input); Chris@0: Chris@0: fftw_execute(plan); Chris@0: Chris@0: if (lock) m_mutex.lock(); Chris@0: bool interrupted = false; Chris@0: Chris@9: for (size_t i = 0; i < windowSize / 2; ++i) { Chris@0: Chris@0: int value = 0; Chris@0: Chris@0: if (m_colourScale == PhaseColourScale) { Chris@0: Chris@0: double phase = atan2(-output[i][1], output[i][0]); Chris@0: value = int((phase * 128 / M_PI) + 128); Chris@0: Chris@0: } else { Chris@1: Chris@0: double mag = sqrt(output[i][0] * output[i][0] + Chris@0: output[i][1] * output[i][1]); Chris@9: mag /= windowSize / 2; Chris@0: Chris@0: switch (m_colourScale) { Chris@0: Chris@0: default: Chris@0: case LinearColourScale: Chris@0: value = int(mag * 50 * 256); Chris@0: break; Chris@0: Chris@0: case MeterColourScale: Chris@0: value = AudioLevel::multiplier_to_preview(mag * 50, 256); Chris@0: break; Chris@0: Chris@0: case dBColourScale: Chris@0: mag = 20.0 * log10(mag); Chris@0: mag = (mag + 80.0) / 80.0; Chris@0: if (mag < 0.0) mag = 0.0; Chris@0: if (mag > 1.0) mag = 1.0; Chris@0: value = int(mag * 256); Chris@0: } Chris@0: } Chris@0: Chris@0: if (value > 254) value = 254; Chris@0: if (value < 0) value = 0; Chris@0: Chris@0: if (m_cacheInvalid || m_exiting) { Chris@0: interrupted = true; Chris@0: break; Chris@0: } Chris@0: Chris@31: m_cache->setValueAt(column, i, value + 1); Chris@0: } Chris@0: Chris@0: if (lock) m_mutex.unlock(); Chris@0: return !interrupted; Chris@0: } Chris@0: Chris@31: SpectrogramLayer::Cache::Cache(size_t width, size_t height) : Chris@31: m_width(width), Chris@31: m_height(height) Chris@31: { Chris@31: m_values = new unsigned char[m_width * m_height]; Chris@31: MUNLOCK(m_values, m_width * m_height * sizeof(unsigned char)); Chris@31: } Chris@31: Chris@31: SpectrogramLayer::Cache::~Cache() Chris@31: { Chris@31: delete[] m_values; Chris@31: } Chris@31: Chris@31: size_t Chris@31: SpectrogramLayer::Cache::getWidth() const Chris@31: { Chris@31: return m_width; Chris@31: } Chris@31: Chris@31: size_t Chris@31: SpectrogramLayer::Cache::getHeight() const Chris@31: { Chris@31: return m_height; Chris@31: } Chris@31: Chris@31: unsigned char Chris@31: SpectrogramLayer::Cache::getValueAt(size_t x, size_t y) const Chris@31: { Chris@31: if (x >= m_width || y >= m_height) return 0; Chris@31: return m_values[y * m_width + x]; Chris@31: } Chris@31: Chris@31: void Chris@31: SpectrogramLayer::Cache::setValueAt(size_t x, size_t y, unsigned char value) Chris@31: { Chris@31: if (x >= m_width || y >= m_height) return; Chris@31: m_values[y * m_width + x] = value; Chris@31: } Chris@31: Chris@31: QColor Chris@31: SpectrogramLayer::Cache::getColour(unsigned char index) const Chris@31: { Chris@31: return m_colours[index]; Chris@31: } Chris@31: Chris@31: void Chris@31: SpectrogramLayer::Cache::setColour(unsigned char index, QColor colour) Chris@31: { Chris@31: m_colours[index] = colour; Chris@31: } Chris@31: Chris@31: void Chris@31: SpectrogramLayer::Cache::fill(unsigned char value) Chris@31: { Chris@31: for (size_t i = 0; i < m_width * m_height; ++i) { Chris@31: m_values[i] = value; Chris@31: } Chris@31: } Chris@31: Chris@0: void Chris@0: SpectrogramLayer::CacheFillThread::run() Chris@0: { Chris@0: // std::cerr << "SpectrogramLayer::CacheFillThread::run" << std::endl; Chris@0: Chris@0: m_layer.m_mutex.lock(); Chris@0: Chris@0: while (!m_layer.m_exiting) { Chris@0: Chris@0: bool interrupted = false; Chris@0: Chris@0: // std::cerr << "SpectrogramLayer::CacheFillThread::run in loop" << std::endl; Chris@0: Chris@29: if (m_layer.m_model && m_layer.m_cacheInvalid && !m_layer.m_dormant) { Chris@0: Chris@0: // std::cerr << "SpectrogramLayer::CacheFillThread::run: something to do" << std::endl; Chris@0: Chris@0: while (!m_layer.m_model->isReady()) { Chris@0: m_layer.m_condition.wait(&m_layer.m_mutex, 100); Chris@0: } Chris@0: Chris@0: m_layer.m_cachedInitialVisibleArea = false; Chris@0: m_layer.m_cacheInvalid = false; Chris@0: m_fillExtent = 0; Chris@0: m_fillCompletion = 0; Chris@0: Chris@0: std::cerr << "SpectrogramLayer::CacheFillThread::run: model is ready" << std::endl; Chris@0: Chris@0: size_t start = m_layer.m_model->getStartFrame(); Chris@0: size_t end = m_layer.m_model->getEndFrame(); Chris@9: Chris@9: WindowType windowType = m_layer.m_windowType; Chris@0: size_t windowSize = m_layer.m_windowSize; Chris@0: size_t windowIncrement = m_layer.getWindowIncrement(); Chris@0: Chris@0: size_t visibleStart = start; Chris@0: size_t visibleEnd = end; Chris@0: Chris@0: if (m_layer.m_view) { Chris@0: if (m_layer.m_view->getStartFrame() < 0) { Chris@0: visibleStart = 0; Chris@0: } else { Chris@0: visibleStart = m_layer.m_view->getStartFrame(); Chris@0: visibleStart = (visibleStart / windowIncrement) * Chris@0: windowIncrement; Chris@0: } Chris@0: visibleEnd = m_layer.m_view->getEndFrame(); Chris@0: } Chris@0: Chris@0: delete m_layer.m_cache; Chris@9: size_t width = (end - start) / windowIncrement + 1; Chris@9: size_t height = windowSize / 2; Chris@31: m_layer.m_cache = new Cache(width, height); Chris@9: Chris@0: m_layer.setCacheColourmap(); Chris@10: Chris@0: m_layer.m_cache->fill(0); Chris@0: m_layer.m_mutex.unlock(); Chris@0: Chris@0: double *input = (double *) Chris@0: fftw_malloc(windowSize * sizeof(double)); Chris@0: Chris@0: fftw_complex *output = (fftw_complex *) Chris@0: fftw_malloc(windowSize * sizeof(fftw_complex)); Chris@0: Chris@0: fftw_plan plan = fftw_plan_dft_r2c_1d(windowSize, input, Chris@1: output, FFTW_ESTIMATE); Chris@0: Chris@9: Window windower(windowType, windowSize); Chris@0: Chris@0: if (!plan) { Chris@1: std::cerr << "WARNING: fftw_plan_dft_r2c_1d(" << windowSize << ") failed!" << std::endl; Chris@0: fftw_free(input); Chris@0: fftw_free(output); Chris@0: m_layer.m_mutex.lock(); Chris@0: continue; Chris@0: } Chris@0: Chris@0: int counter = 0; Chris@0: int updateAt = (end / windowIncrement) / 20; Chris@0: if (updateAt < 100) updateAt = 100; Chris@0: Chris@0: bool doVisibleFirst = (visibleStart != start && visibleEnd != end); Chris@0: Chris@0: if (doVisibleFirst) { Chris@0: Chris@0: m_layer.m_mutex.lock(); Chris@0: Chris@0: for (size_t f = visibleStart; f < visibleEnd; f += windowIncrement) { Chris@0: Chris@0: m_layer.fillCacheColumn(int((f - start) / windowIncrement), Chris@9: input, output, plan, Chris@9: windowSize, windowIncrement, Chris@9: windower, false); Chris@0: Chris@0: m_layer.m_mutex.unlock(); Chris@0: m_layer.m_mutex.lock(); Chris@0: Chris@0: if (m_layer.m_cacheInvalid || m_layer.m_exiting) { Chris@0: interrupted = true; Chris@0: m_fillExtent = 0; Chris@0: break; Chris@0: } Chris@0: Chris@0: if (++counter == updateAt) { Chris@0: if (f < end) m_fillExtent = f; Chris@0: m_fillCompletion = size_t(100 * fabsf(float(f - visibleStart) / Chris@0: float(end - start))); Chris@0: counter = 0; Chris@0: } Chris@0: } Chris@0: Chris@0: m_layer.m_mutex.unlock(); Chris@0: } Chris@0: Chris@0: m_layer.m_cachedInitialVisibleArea = true; Chris@0: Chris@0: if (!interrupted && doVisibleFirst) { Chris@0: Chris@0: for (size_t f = visibleEnd; f < end; f += windowIncrement) { Chris@0: Chris@0: if (!m_layer.fillCacheColumn(int((f - start) / windowIncrement), Chris@9: input, output, plan, Chris@9: windowSize, windowIncrement, Chris@9: windower, true)) { Chris@0: interrupted = true; Chris@0: m_fillExtent = 0; Chris@0: break; Chris@0: } Chris@0: Chris@0: Chris@0: if (++counter == updateAt) { Chris@0: if (f < end) m_fillExtent = f; Chris@0: m_fillCompletion = size_t(100 * fabsf(float(f - visibleStart) / Chris@0: float(end - start))); Chris@0: counter = 0; Chris@0: } Chris@0: } Chris@0: } Chris@0: Chris@0: if (!interrupted) { Chris@0: Chris@0: size_t remainingEnd = end; Chris@0: if (doVisibleFirst) { Chris@0: remainingEnd = visibleStart; Chris@0: if (remainingEnd > start) --remainingEnd; Chris@0: else remainingEnd = start; Chris@0: } Chris@0: size_t baseCompletion = m_fillCompletion; Chris@0: Chris@0: for (size_t f = start; f < remainingEnd; f += windowIncrement) { Chris@0: Chris@0: if (!m_layer.fillCacheColumn(int((f - start) / windowIncrement), Chris@9: input, output, plan, Chris@9: windowSize, windowIncrement, Chris@9: windower, true)) { Chris@0: interrupted = true; Chris@0: m_fillExtent = 0; Chris@0: break; Chris@0: } Chris@0: Chris@0: if (++counter == updateAt) { Chris@0: m_fillExtent = f; Chris@0: m_fillCompletion = baseCompletion + Chris@0: size_t(100 * fabsf(float(f - start) / Chris@0: float(end - start))); Chris@0: counter = 0; Chris@0: } Chris@0: } Chris@0: } Chris@0: Chris@0: fftw_destroy_plan(plan); Chris@0: fftw_free(output); Chris@0: fftw_free(input); Chris@0: Chris@0: if (!interrupted) { Chris@0: m_fillExtent = end; Chris@0: m_fillCompletion = 100; Chris@0: } Chris@0: Chris@0: m_layer.m_mutex.lock(); Chris@0: } Chris@0: Chris@0: if (!interrupted) m_layer.m_condition.wait(&m_layer.m_mutex, 2000); Chris@0: } Chris@0: } Chris@0: Chris@0: bool Chris@0: SpectrogramLayer::getYBinRange(int y, float &q0, float &q1) const Chris@0: { Chris@0: int h = m_view->height(); Chris@0: if (y < 0 || y >= h) return false; Chris@0: Chris@0: // Each pixel in a column is drawn from a possibly non- Chris@0: // integral set of frequency bins. Chris@0: Chris@0: if (m_frequencyScale == LinearFrequencyScale) { Chris@0: Chris@0: size_t bins = m_windowSize / 2; Chris@0: Chris@0: if (m_maxFrequency > 0) { Chris@0: int sr = m_model->getSampleRate(); Chris@0: bins = int((double(m_maxFrequency) * m_windowSize) / sr + 0.1); Chris@0: if (bins > m_windowSize / 2) bins = m_windowSize / 2; Chris@0: } Chris@0: Chris@0: q0 = float(h - y - 1) * bins / h; Chris@0: q1 = float(h - y) * bins / h; Chris@0: Chris@0: } else { Chris@0: Chris@0: // This is all most ad-hoc. I'm not at my brightest. Chris@0: Chris@0: int sr = m_model->getSampleRate(); Chris@0: Chris@0: float maxf = m_maxFrequency; Chris@0: if (maxf == 0.0) maxf = float(sr) / 2; Chris@0: Chris@0: float minf = float(sr) / m_windowSize; Chris@0: Chris@0: float maxlogf = log10f(maxf); Chris@0: float minlogf = log10f(minf); Chris@0: Chris@0: float logf0 = minlogf + ((maxlogf - minlogf) * (h - y - 1)) / h; Chris@0: float logf1 = minlogf + ((maxlogf - minlogf) * (h - y)) / h; Chris@0: Chris@0: float f0 = pow(10.f, logf0); Chris@0: float f1 = pow(10.f, logf1); Chris@0: Chris@0: q0 = ((f0 * m_windowSize) / sr) - 1; Chris@0: q1 = ((f1 * m_windowSize) / sr) - 1; Chris@0: Chris@0: // std::cout << "y=" << y << " h=" << h << " maxf=" << maxf << " maxlogf=" Chris@0: // << maxlogf << " logf0=" << logf0 << " f0=" << f0 << " q0=" Chris@0: // << q0 << std::endl; Chris@0: } Chris@0: Chris@0: return true; Chris@0: } Chris@0: Chris@0: bool Chris@20: SpectrogramLayer::getXBinRange(int x, float &s0, float &s1) const Chris@0: { Chris@21: size_t modelStart = m_model->getStartFrame(); Chris@21: size_t modelEnd = m_model->getEndFrame(); Chris@0: Chris@0: // Each pixel column covers an exact range of sample frames: Chris@20: int f0 = getFrameForX(x) - modelStart; Chris@20: int f1 = getFrameForX(x + 1) - modelStart - 1; Chris@20: Chris@0: if (f1 < int(modelStart) || f0 > int(modelEnd)) return false; Chris@20: Chris@0: // And that range may be drawn from a possibly non-integral Chris@0: // range of spectrogram windows: Chris@0: Chris@0: size_t windowIncrement = getWindowIncrement(); Chris@0: s0 = float(f0) / windowIncrement; Chris@0: s1 = float(f1) / windowIncrement; Chris@0: Chris@0: return true; Chris@0: } Chris@0: Chris@0: bool Chris@0: SpectrogramLayer::getXBinSourceRange(int x, RealTime &min, RealTime &max) const Chris@0: { Chris@0: float s0 = 0, s1 = 0; Chris@0: if (!getXBinRange(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@0: SpectrogramLayer::getYBinSourceRange(int y, float &freqMin, float &freqMax) Chris@0: const Chris@0: { Chris@0: float q0 = 0, q1 = 0; Chris@0: if (!getYBinRange(y, q0, q1)) return false; Chris@0: Chris@0: int q0i = int(q0 + 0.001); Chris@0: int q1i = int(q1); Chris@0: Chris@0: int sr = m_model->getSampleRate(); Chris@0: Chris@0: for (int q = q0i; q <= q1i; ++q) { Chris@0: int binfreq = (sr * (q + 1)) / m_windowSize; Chris@0: if (q == q0i) freqMin = binfreq; Chris@0: if (q == q1i) freqMax = binfreq; Chris@0: } Chris@0: return true; Chris@0: } Chris@0: Chris@0: bool Chris@0: SpectrogramLayer::getXYBinSourceRange(int x, int y, float &dbMin, float &dbMax) const Chris@0: { Chris@0: float q0 = 0, q1 = 0; Chris@0: if (!getYBinRange(y, q0, q1)) return false; Chris@0: Chris@0: float s0 = 0, s1 = 0; Chris@0: if (!getXBinRange(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@0: if (m_mutex.tryLock()) { Chris@0: if (m_cache && !m_cacheInvalid) { Chris@0: Chris@31: int cw = m_cache->getWidth(); Chris@31: int ch = m_cache->getHeight(); Chris@0: Chris@0: int min = -1, max = -1; Chris@0: Chris@0: for (int q = q0i; q <= q1i; ++q) { Chris@0: for (int s = s0i; s <= s1i; ++s) { Chris@0: if (s >= 0 && q >= 0 && s < cw && q < ch) { Chris@31: int value = int(m_cache->getValueAt(s, q)); Chris@0: if (min == -1 || value < min) min = value; Chris@0: if (max == -1 || value > max) max = value; Chris@0: } Chris@0: } Chris@0: } Chris@0: Chris@0: if (min < 0) return false; Chris@0: Chris@0: dbMin = (float(min) / 256.0) * 80.0 - 80.0; Chris@0: dbMax = (float(max + 1) / 256.0) * 80.0 - 80.1; Chris@0: Chris@0: m_mutex.unlock(); Chris@0: return true; Chris@0: } Chris@0: Chris@0: m_mutex.unlock(); Chris@0: } Chris@0: Chris@0: return false; Chris@0: } Chris@0: Chris@0: void Chris@0: SpectrogramLayer::paint(QPainter &paint, QRect rect) const Chris@0: { Chris@0: // Profiler profiler("SpectrogramLayer::paint", true); Chris@0: #ifdef DEBUG_SPECTROGRAM_REPAINT Chris@0: std::cerr << "SpectrogramLayer::paint(): m_model is " << m_model << ", zoom level is " << m_view->getZoomLevel() << ", m_updateTimer " << m_updateTimer << ", pixmap cache invalid " << m_pixmapCacheInvalid << std::endl; Chris@0: #endif Chris@0: Chris@0: if (!m_model || !m_model->isOK() || !m_model->isReady()) { Chris@0: return; Chris@0: } Chris@0: Chris@29: if (m_dormant) { Chris@29: std::cerr << "SpectrogramLayer::paint(): Layer is dormant, de-hibernating" << std::endl; Chris@29: m_dormant = false; Chris@29: ((SpectrogramLayer *)this)->fillCache(); Chris@29: return; Chris@29: } Chris@29: Chris@0: #ifdef DEBUG_SPECTROGRAM_REPAINT Chris@0: std::cerr << "SpectrogramLayer::paint(): About to lock" << std::endl; Chris@0: #endif Chris@0: Chris@0: /* Chris@0: if (m_cachedInitialVisibleArea) { Chris@0: if (!m_mutex.tryLock()) { Chris@0: m_view->update(); Chris@0: return; Chris@0: } Chris@0: } else { Chris@0: */ Chris@0: m_mutex.lock(); Chris@0: // } Chris@0: Chris@0: #ifdef DEBUG_SPECTROGRAM_REPAINT Chris@0: std::cerr << "SpectrogramLayer::paint(): locked" << std::endl; Chris@0: #endif Chris@0: Chris@0: if (m_cacheInvalid) { // lock the mutex before checking this Chris@0: m_mutex.unlock(); Chris@0: #ifdef DEBUG_SPECTROGRAM_REPAINT Chris@0: std::cerr << "SpectrogramLayer::paint(): Cache invalid, returning" << std::endl; Chris@0: #endif Chris@0: return; Chris@0: } Chris@0: Chris@0: bool stillCacheing = (m_updateTimer != 0); Chris@0: Chris@0: #ifdef DEBUG_SPECTROGRAM_REPAINT Chris@0: std::cerr << "SpectrogramLayer::paint(): Still cacheing = " << stillCacheing << std::endl; Chris@0: #endif Chris@0: Chris@0: long startFrame = m_view->getStartFrame(); Chris@0: int zoomLevel = m_view->getZoomLevel(); Chris@0: Chris@0: int x0 = 0; Chris@0: int x1 = m_view->width(); Chris@0: int y0 = 0; Chris@0: int y1 = m_view->height(); Chris@0: Chris@0: bool recreateWholePixmapCache = true; Chris@0: Chris@0: if (!m_pixmapCacheInvalid) { Chris@0: Chris@0: //!!! This cache may have been obsoleted entirely by the Chris@0: //scrolling cache in View. Perhaps experiment with Chris@0: //removing it and see if it makes things even quicker (or else Chris@0: //make it optional) Chris@0: Chris@0: if (int(m_pixmapCacheZoomLevel) == zoomLevel && Chris@0: m_pixmapCache->width() == m_view->width() && Chris@0: m_pixmapCache->height() == m_view->height()) { Chris@0: Chris@20: if (getXForFrame(m_pixmapCacheStartFrame) == Chris@20: getXForFrame(startFrame)) { Chris@0: Chris@0: #ifdef DEBUG_SPECTROGRAM_REPAINT Chris@0: std::cerr << "SpectrogramLayer: pixmap cache good" << std::endl; Chris@0: #endif Chris@0: Chris@0: m_mutex.unlock(); Chris@0: paint.drawPixmap(rect, *m_pixmapCache, rect); Chris@0: return; Chris@0: Chris@0: } else { Chris@0: Chris@0: #ifdef DEBUG_SPECTROGRAM_REPAINT Chris@0: std::cerr << "SpectrogramLayer: pixmap cache partially OK" << std::endl; Chris@0: #endif Chris@0: Chris@0: recreateWholePixmapCache = false; Chris@0: Chris@20: int dx = getXForFrame(m_pixmapCacheStartFrame) - Chris@20: getXForFrame(startFrame); Chris@0: Chris@0: #ifdef DEBUG_SPECTROGRAM_REPAINT Chris@0: std::cerr << "SpectrogramLayer: dx = " << dx << " (pixmap cache " << m_pixmapCache->width() << "x" << m_pixmapCache->height() << ")" << std::endl; Chris@0: #endif Chris@0: Chris@0: if (dx > -m_pixmapCache->width() && dx < m_pixmapCache->width()) { Chris@0: Chris@0: #if defined(Q_WS_WIN32) || defined(Q_WS_MAC) Chris@0: // Copying a pixmap to itself doesn't work Chris@0: // properly on Windows or Mac (it only works when Chris@0: // moving in one direction). Chris@0: Chris@0: //!!! Need a utility function for this Chris@0: Chris@0: static QPixmap *tmpPixmap = 0; Chris@0: if (!tmpPixmap || Chris@0: tmpPixmap->width() != m_pixmapCache->width() || Chris@0: tmpPixmap->height() != m_pixmapCache->height()) { Chris@0: delete tmpPixmap; Chris@0: tmpPixmap = new QPixmap(m_pixmapCache->width(), Chris@0: m_pixmapCache->height()); Chris@0: } Chris@0: QPainter cachePainter; Chris@0: cachePainter.begin(tmpPixmap); Chris@0: cachePainter.drawPixmap(0, 0, *m_pixmapCache); Chris@0: cachePainter.end(); Chris@0: cachePainter.begin(m_pixmapCache); Chris@0: cachePainter.drawPixmap(dx, 0, *tmpPixmap); Chris@0: cachePainter.end(); Chris@0: #else Chris@0: QPainter cachePainter(m_pixmapCache); Chris@0: cachePainter.drawPixmap(dx, 0, *m_pixmapCache); Chris@0: cachePainter.end(); Chris@0: #endif Chris@0: Chris@0: paint.drawPixmap(rect, *m_pixmapCache, rect); Chris@0: Chris@0: if (dx < 0) { Chris@0: x0 = m_pixmapCache->width() + dx; Chris@0: x1 = m_pixmapCache->width(); Chris@0: } else { Chris@0: x0 = 0; Chris@0: x1 = dx; Chris@0: } Chris@0: } Chris@0: } Chris@0: } else { Chris@0: #ifdef DEBUG_SPECTROGRAM_REPAINT Chris@0: std::cerr << "SpectrogramLayer: pixmap cache useless" << std::endl; Chris@0: #endif Chris@0: } Chris@0: } Chris@0: Chris@0: if (stillCacheing) { Chris@0: x0 = rect.left(); Chris@0: x1 = rect.right() + 1; Chris@0: y0 = rect.top(); Chris@0: y1 = rect.bottom() + 1; Chris@0: } Chris@0: Chris@0: int w = x1 - x0; Chris@0: int h = y1 - y0; Chris@0: Chris@0: // std::cerr << "x0 " << x0 << ", x1 " << x1 << ", w " << w << ", h " << h << std::endl; Chris@0: Chris@0: QImage scaled(w, h, QImage::Format_RGB32); Chris@0: Chris@0: m_mutex.unlock(); Chris@0: Chris@0: for (int y = 0; y < h; ++y) { Chris@0: Chris@0: m_mutex.lock(); Chris@0: if (m_cacheInvalid) { Chris@0: m_mutex.unlock(); Chris@0: break; Chris@0: } Chris@0: Chris@31: int cw = m_cache->getWidth(); Chris@31: int ch = m_cache->getHeight(); Chris@0: Chris@0: float q0 = 0, q1 = 0; Chris@0: Chris@0: if (!getYBinRange(y0 + y, q0, q1)) { Chris@0: for (int x = 0; x < w; ++x) { Chris@0: assert(x <= scaled.width()); Chris@0: scaled.setPixel(x, y, qRgb(0, 0, 0)); Chris@0: } Chris@0: m_mutex.unlock(); Chris@0: continue; Chris@0: } Chris@0: Chris@0: int q0i = int(q0 + 0.001); Chris@0: int q1i = int(q1); Chris@0: Chris@0: for (int x = 0; x < w; ++x) { Chris@0: Chris@0: float s0 = 0, s1 = 0; Chris@0: Chris@20: if (!getXBinRange(x0 + x, s0, s1)) { Chris@0: assert(x <= scaled.width()); Chris@0: scaled.setPixel(x, y, qRgb(0, 0, 0)); Chris@0: continue; Chris@0: } Chris@0: Chris@0: int s0i = int(s0 + 0.001); Chris@0: int s1i = int(s1); Chris@0: Chris@0: float total = 0, divisor = 0; Chris@0: Chris@0: for (int s = s0i; s <= s1i; ++s) { Chris@0: Chris@0: float sprop = 1.0; Chris@0: if (s == s0i) sprop *= (s + 1) - s0; Chris@0: if (s == s1i) sprop *= s1 - s; Chris@0: Chris@0: for (int q = q0i; q <= q1i; ++q) { Chris@0: Chris@0: float qprop = sprop; Chris@0: if (q == q0i) qprop *= (q + 1) - q0; Chris@0: if (q == q1i) qprop *= q1 - q; Chris@0: Chris@0: if (s >= 0 && q >= 0 && s < cw && q < ch) { Chris@31: total += qprop * m_cache->getValueAt(s, q); Chris@0: divisor += qprop; Chris@0: } Chris@0: } Chris@0: } Chris@0: Chris@0: if (divisor > 0.0) { Chris@0: int pixel = int(total / divisor); Chris@0: if (pixel > 255) pixel = 255; Chris@0: if (pixel < 1) pixel = 1; Chris@0: assert(x <= scaled.width()); Chris@31: QColor c = m_cache->getColour(pixel); Chris@31: scaled.setPixel(x, y, Chris@31: qRgb(c.red(), c.green(), c.blue())); Chris@31: /* Chris@9: float pixel = total / divisor; Chris@9: float lq = pixel - int(pixel); Chris@9: float hq = int(pixel) + 1 - pixel; Chris@9: int pixNum = int(pixel); Chris@9: QRgb low = m_cache->color(pixNum > 255 ? 255 : pixNum); Chris@9: QRgb high = m_cache->color(pixNum > 254 ? 255 : pixNum + 1); Chris@9: QRgb mixed = qRgb Chris@9: (qRed(low) * lq + qRed(high) * hq + 0.01, Chris@9: qGreen(low) * lq + qGreen(high) * hq + 0.01, Chris@9: qBlue(low) * lq + qBlue(high) * hq + 0.01); Chris@9: scaled.setPixel(x, y, mixed); Chris@31: */ Chris@0: } else { Chris@0: assert(x <= scaled.width()); Chris@0: scaled.setPixel(x, y, qRgb(0, 0, 0)); Chris@0: } Chris@0: } Chris@0: Chris@0: m_mutex.unlock(); Chris@0: } Chris@0: Chris@0: paint.drawImage(x0, y0, scaled); Chris@0: Chris@0: if (recreateWholePixmapCache) { Chris@0: delete m_pixmapCache; Chris@0: m_pixmapCache = new QPixmap(w, h); Chris@0: } Chris@0: Chris@0: QPainter cachePainter(m_pixmapCache); Chris@0: cachePainter.drawImage(x0, y0, scaled); Chris@0: cachePainter.end(); Chris@0: Chris@0: m_pixmapCacheInvalid = false; Chris@0: m_pixmapCacheStartFrame = startFrame; Chris@0: m_pixmapCacheZoomLevel = zoomLevel; Chris@0: Chris@0: #ifdef DEBUG_SPECTROGRAM_REPAINT Chris@0: std::cerr << "SpectrogramLayer::paint() returning" << std::endl; Chris@0: #endif Chris@0: } Chris@0: Chris@0: int Chris@0: SpectrogramLayer::getCompletion() const Chris@0: { Chris@0: if (m_updateTimer == 0) return 100; Chris@0: size_t completion = m_fillThread->getFillCompletion(); Chris@0: // std::cerr << "SpectrogramLayer::getCompletion: completion = " << completion << std::endl; Chris@0: return completion; Chris@0: } Chris@0: Chris@28: bool Chris@28: SpectrogramLayer::snapToFeatureFrame(int &frame, Chris@28: size_t &resolution, Chris@28: SnapType snap) const Chris@13: { Chris@13: resolution = getWindowIncrement(); Chris@28: int left = (frame / resolution) * resolution; Chris@28: int 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@25: QString Chris@25: SpectrogramLayer::getFeatureDescription(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@0: float dbMin = 0, dbMax = 0; Chris@0: float freqMin = 0, freqMax = 0; Chris@25: QString pitchMin, pitchMax; Chris@0: RealTime rtMin, rtMax; Chris@0: Chris@25: bool haveDb = false; Chris@0: Chris@25: if (!getXBinSourceRange(x, rtMin, rtMax)) return ""; Chris@25: if (!getYBinSourceRange(y, freqMin, freqMax)) return ""; Chris@25: if (getXYBinSourceRange(x, y, dbMin, dbMax)) haveDb = true; Chris@0: Chris@25: //!!! want to actually do a one-off FFT to recalculate the dB value! Chris@25: 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@25: text += tr("Frequency:\t%1 - %2 Hz\nPitch:\t%3 - %4\n") Chris@25: .arg(freqMin) Chris@25: .arg(freqMax) Chris@25: .arg(Pitch::getPitchLabelForFrequency(freqMin)) Chris@25: .arg(Pitch::getPitchLabelForFrequency(freqMax)); Chris@25: } else { Chris@25: text += tr("Frequency:\t%1 Hz\nPitch:\t%2\n") Chris@25: .arg(freqMin) Chris@25: .arg(Pitch::getPitchLabelForFrequency(freqMin)); Chris@25: } Chris@25: Chris@25: if (haveDb) { Chris@25: if (lrintf(dbMin) != lrintf(dbMax)) { Chris@25: text += tr("dB:\t%1 - %2").arg(lrintf(dbMin)).arg(lrintf(dbMax)); Chris@25: } else { Chris@25: text += tr("dB:\t%1").arg(lrintf(dbMin)); Chris@25: } Chris@25: } Chris@25: Chris@25: return text; Chris@0: } Chris@25: Chris@0: int Chris@0: SpectrogramLayer::getVerticalScaleWidth(QPainter &paint) const Chris@0: { Chris@0: if (!m_model || !m_model->isOK()) return 0; Chris@0: 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@0: int fw = paint.fontMetrics().width(QString("43Hz")); Chris@0: if (tw < fw) tw = fw; Chris@0: Chris@0: return tw + 13; Chris@0: } Chris@0: Chris@0: void Chris@0: SpectrogramLayer::paintVerticalScale(QPainter &paint, QRect rect) const Chris@0: { Chris@0: if (!m_model || !m_model->isOK()) { Chris@0: return; Chris@0: } Chris@0: Chris@0: int h = rect.height(), w = rect.width(); Chris@0: Chris@0: size_t bins = m_windowSize / 2; Chris@0: int sr = m_model->getSampleRate(); Chris@0: Chris@0: if (m_maxFrequency > 0) { Chris@0: bins = int((double(m_maxFrequency) * m_windowSize) / sr + 0.1); Chris@0: if (bins > m_windowSize / 2) bins = m_windowSize / 2; Chris@0: } Chris@0: Chris@0: int py = -1; Chris@0: int textHeight = paint.fontMetrics().height(); Chris@0: int toff = -textHeight + paint.fontMetrics().ascent() + 2; Chris@0: Chris@0: int bin = -1; Chris@0: Chris@0: for (int y = 0; y < m_view->height(); ++y) { Chris@0: Chris@0: float q0, q1; Chris@0: if (!getYBinRange(m_view->height() - 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@0: int freq = (sr * (bin + 1)) / m_windowSize; Chris@0: Chris@0: if (py >= 0 && (vy - py) < textHeight - 1) { Chris@0: paint.drawLine(w - 4, h - vy, w, h - vy); Chris@0: continue; Chris@0: } Chris@0: Chris@0: QString text = QString("%1").arg(freq); Chris@0: if (bin == 0) text = QString("%1Hz").arg(freq); Chris@0: paint.drawLine(0, h - vy, w, h - vy); Chris@0: Chris@0: if (h - vy - textHeight >= -2) { Chris@0: int tx = w - 10 - paint.fontMetrics().width(text); Chris@0: paint.drawText(tx, h - vy + toff, text); Chris@0: } Chris@0: Chris@0: py = vy; Chris@0: } Chris@0: } Chris@0: Chris@6: QString Chris@6: SpectrogramLayer::toXmlString(QString indent, QString extraAttributes) const Chris@6: { Chris@6: QString s; Chris@6: Chris@6: s += QString("channel=\"%1\" " Chris@6: "windowSize=\"%2\" " Chris@6: "windowType=\"%3\" " Chris@6: "windowOverlap=\"%4\" " Chris@6: "gain=\"%5\" " Chris@6: "maxFrequency=\"%6\" " Chris@6: "colourScale=\"%7\" " Chris@6: "colourScheme=\"%8\" " Chris@6: "frequencyScale=\"%9\"") Chris@6: .arg(m_channel) Chris@6: .arg(m_windowSize) Chris@6: .arg(m_windowType) Chris@6: .arg(m_windowOverlap) Chris@6: .arg(m_gain) Chris@6: .arg(m_maxFrequency) Chris@6: .arg(m_colourScale) Chris@6: .arg(m_colourScheme) Chris@6: .arg(m_frequencyScale); Chris@6: Chris@6: return Layer::toXmlString(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@11: size_t windowSize = attributes.value("windowSize").toUInt(&ok); Chris@11: if (ok) setWindowSize(windowSize); Chris@11: Chris@11: WindowType windowType = (WindowType) Chris@11: attributes.value("windowType").toInt(&ok); Chris@11: if (ok) setWindowType(windowType); Chris@11: Chris@11: size_t windowOverlap = attributes.value("windowOverlap").toUInt(&ok); Chris@11: if (ok) setWindowOverlap(windowOverlap); Chris@11: Chris@11: float gain = attributes.value("gain").toFloat(&ok); Chris@11: if (ok) setGain(gain); Chris@11: Chris@11: size_t maxFrequency = attributes.value("maxFrequency").toUInt(&ok); Chris@11: if (ok) setMaxFrequency(maxFrequency); Chris@11: Chris@11: ColourScale colourScale = (ColourScale) Chris@11: attributes.value("colourScale").toInt(&ok); Chris@11: if (ok) setColourScale(colourScale); Chris@11: Chris@11: ColourScheme colourScheme = (ColourScheme) Chris@11: attributes.value("colourScheme").toInt(&ok); Chris@11: if (ok) setColourScheme(colourScheme); Chris@11: Chris@11: FrequencyScale frequencyScale = (FrequencyScale) Chris@11: attributes.value("frequencyScale").toInt(&ok); Chris@11: if (ok) setFrequencyScale(frequencyScale); Chris@11: } Chris@11: Chris@11: Chris@0: #ifdef INCLUDE_MOCFILES Chris@0: #include "SpectrogramLayer.moc.cpp" Chris@0: #endif Chris@0: