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@59: This file copyright 2006 Chris Cannam. 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@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@85: #include "base/FFTCache.h" Chris@86: #include "base/FFTFileCache.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@75: static double mod(double x, double y) Chris@75: { Chris@75: double a = floor(x / y); Chris@75: double b = x - (y * a); Chris@75: return b; Chris@75: } Chris@75: Chris@75: static double princarg(double ang) Chris@75: { Chris@75: return mod(ang + M_PI, -2 * M_PI) + M_PI; Chris@75: } Chris@75: Chris@0: Chris@44: SpectrogramLayer::SpectrogramLayer(Configuration config) : Chris@44: Layer(), 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@37: m_threshold(0.0), Chris@9: m_colourRotation(0), Chris@37: m_minFrequency(0), Chris@0: m_maxFrequency(8000), Chris@0: m_colourScale(dBColourScale), Chris@0: m_colourScheme(DefaultColours), Chris@0: m_frequencyScale(LinearFrequencyScale), Chris@37: m_binDisplay(AllBins), Chris@36: m_normalizeColumns(false), Chris@0: m_cache(0), Chris@86: m_writeCache(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@44: m_candidateFillStartFrame(0), Chris@0: m_lastFillExtent(0), 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@37: } else if (config == MelodicPeaks) { Chris@37: setWindowSize(4096); Chris@37: setWindowOverlap(90); Chris@37: setWindowType(BlackmanWindow); Chris@40: setMaxFrequency(2000); Chris@37: setMinFrequency(40); Chris@37: setFrequencyScale(LogFrequencyScale); Chris@41: setColourScale(MeterColourScale); Chris@37: setBinDisplay(PeakFrequencies); Chris@37: setNormalizeColumns(true); Chris@0: } 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@86: delete m_writeCache; Chris@0: delete m_cache; Chris@0: } Chris@0: Chris@0: void Chris@0: SpectrogramLayer::setModel(const DenseTimeValueModel *model) Chris@0: { Chris@34: std::cerr << "SpectrogramLayer(" << this << "): setModel(" << model << ")" << std::endl; Chris@34: Chris@0: m_mutex.lock(); Chris@35: m_cacheInvalid = true; Chris@0: m_model = model; 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@82: list.push_back(tr("Normalize Columns")); Chris@37: list.push_back(tr("Bin Display")); Chris@37: list.push_back(tr("Threshold")); Chris@0: list.push_back(tr("Gain")); Chris@9: list.push_back(tr("Colour Rotation")); Chris@37: list.push_back(tr("Min Frequency")); 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@82: if (name == tr("Normalize Columns")) return ToggleProperty; Chris@37: if (name == tr("Threshold")) 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@35: name == tr("Window Type") || Chris@0: name == tr("Window Overlap")) return tr("Window"); Chris@35: if (name == tr("Colour") || Chris@38: name == tr("Gain") || Chris@38: name == tr("Threshold") || Chris@35: name == tr("Colour Rotation")) return tr("Colour"); Chris@82: if (name == tr("Normalize Columns") || Chris@37: name == tr("Bin Display") || Chris@0: name == tr("Colour Scale")) return tr("Scale"); Chris@0: if (name == tr("Max Frequency") || Chris@37: name == tr("Min Frequency") || Chris@35: name == tr("Frequency Scale") || Chris@37: name == tr("Frequency Adjustment")) return tr("Range"); Chris@0: return QString(); Chris@0: } Chris@0: Chris@0: int Chris@0: SpectrogramLayer::getPropertyRangeAndValue(const PropertyName &name, Chris@55: int *min, int *max) const Chris@0: { Chris@0: int deft = 0; Chris@0: Chris@55: int garbage0, garbage1; Chris@55: if (!min) min = &garbage0; Chris@55: if (!max) max = &garbage1; 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@37: } else if (name == tr("Threshold")) { Chris@37: Chris@37: *min = -50; Chris@37: *max = 0; Chris@37: Chris@37: deft = lrintf(AudioLevel::multiplier_to_dB(m_threshold)); Chris@37: if (deft < *min) deft = *min; Chris@37: if (deft > *max) deft = *max; Chris@37: 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@71: *max = 6; 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@37: } else if (name == tr("Min Frequency")) { Chris@37: Chris@37: *min = 0; Chris@37: *max = 9; Chris@37: Chris@37: switch (m_minFrequency) { Chris@37: case 0: default: deft = 0; break; Chris@37: case 10: deft = 1; break; Chris@37: case 20: deft = 2; break; Chris@37: case 40: deft = 3; break; Chris@37: case 100: deft = 4; break; Chris@37: case 250: deft = 5; break; Chris@37: case 500: deft = 6; break; Chris@37: case 1000: deft = 7; break; Chris@37: case 4000: deft = 8; break; Chris@37: case 10000: deft = 9; break; Chris@37: } Chris@37: 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@37: } else if (name == tr("Bin Display")) { Chris@35: Chris@35: *min = 0; Chris@35: *max = 2; Chris@37: deft = (int)m_binDisplay; Chris@35: Chris@82: } else if (name == tr("Normalize Columns")) { Chris@36: Chris@36: deft = (m_normalizeColumns ? 1 : 0); Chris@36: 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@71: case 5: return tr("Blue on Black"); Chris@71: case 6: return tr("Fruit Salad"); Chris@0: } Chris@0: } Chris@0: if (name == tr("Colour Scale")) { Chris@0: switch (value) { Chris@0: default: Chris@37: case 0: return tr("Linear"); Chris@37: case 1: return tr("Meter"); Chris@37: case 2: return tr("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@35: case RectangularWindow: return tr("Rectangle"); 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@35: case 0: return tr("0%"); Chris@35: case 1: return tr("25%"); Chris@35: case 2: return tr("50%"); Chris@35: case 3: return tr("75%"); Chris@35: case 4: return tr("90%"); Chris@0: } Chris@0: } Chris@37: if (name == tr("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@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@38: case 9: return tr("No max"); 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@37: if (name == tr("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@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@37: } else if (name == tr("Threshold")) { Chris@37: if (value == -50) setThreshold(0.0); Chris@37: else setThreshold(AudioLevel::dB_to_multiplier(value)); Chris@9: } else if (name == tr("Colour Rotation")) { Chris@9: setColourRotation(value); Chris@0: } else if (name == tr("Colour")) { 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@71: case 5: setColourScheme(BlueOnBlack); break; Chris@71: case 6: setColourScheme(Rainbow); 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@37: } else if (name == tr("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@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@37: } else if (name == tr("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@82: } else if (name == "Normalize Columns") { Chris@36: setNormalizeColumns(value ? true : false); 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@55: std::cerr << "SpectrogramLayer::setGain(" << gain << ") (my gain is now " Chris@55: << m_gain << ")" << std::endl; Chris@55: Chris@40: if (m_gain == gain) return; Chris@0: Chris@0: m_mutex.lock(); 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@37: SpectrogramLayer::setThreshold(float threshold) Chris@37: { Chris@40: if (m_threshold == threshold) return; Chris@37: Chris@37: m_mutex.lock(); Chris@37: m_pixmapCacheInvalid = true; Chris@37: Chris@37: m_threshold = threshold; Chris@37: Chris@37: m_mutex.unlock(); Chris@37: Chris@37: emit layerParametersChanged(); Chris@37: Chris@37: fillCache(); 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@37: SpectrogramLayer::setMinFrequency(size_t mf) Chris@37: { Chris@37: if (m_minFrequency == mf) return; Chris@37: Chris@37: m_mutex.lock(); Chris@37: m_pixmapCacheInvalid = true; Chris@37: Chris@37: m_minFrequency = mf; Chris@37: Chris@37: m_mutex.unlock(); Chris@37: Chris@37: emit layerParametersChanged(); Chris@37: } Chris@37: Chris@37: size_t Chris@37: SpectrogramLayer::getMinFrequency() const Chris@37: { Chris@37: return m_minFrequency; Chris@37: } Chris@37: Chris@37: 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@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: 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_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: 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@35: 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@37: SpectrogramLayer::setBinDisplay(BinDisplay binDisplay) Chris@35: { Chris@37: if (m_binDisplay == binDisplay) return; Chris@35: Chris@35: m_mutex.lock(); Chris@35: Chris@35: m_pixmapCacheInvalid = true; Chris@35: Chris@37: m_binDisplay = binDisplay; Chris@35: Chris@35: m_mutex.unlock(); Chris@35: Chris@35: fillCache(); 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@36: SpectrogramLayer::setNormalizeColumns(bool n) Chris@36: { Chris@36: if (m_normalizeColumns == n) return; Chris@36: m_mutex.lock(); Chris@36: Chris@36: m_pixmapCacheInvalid = true; Chris@36: m_normalizeColumns = n; Chris@36: m_mutex.unlock(); Chris@36: Chris@36: fillCache(); Chris@36: emit layerParametersChanged(); Chris@36: } Chris@36: Chris@36: bool Chris@36: SpectrogramLayer::getNormalizeColumns() const Chris@36: { Chris@36: return m_normalizeColumns; Chris@36: } Chris@36: Chris@36: void Chris@47: SpectrogramLayer::setLayerDormant(const View *v, bool dormant) Chris@29: { Chris@47: QMutexLocker locker(&m_mutex); Chris@47: Chris@47: if (dormant == m_dormancy[v]) return; Chris@33: Chris@33: if (dormant) { Chris@33: Chris@47: m_dormancy[v] = true; Chris@33: Chris@34: // delete m_cache; Chris@34: // m_cache = 0; Chris@33: Chris@34: m_cacheInvalid = true; Chris@33: m_pixmapCacheInvalid = true; Chris@33: delete m_pixmapCache; Chris@33: m_pixmapCache = 0; Chris@33: Chris@33: } else { Chris@33: Chris@47: m_dormancy[v] = false; Chris@33: } 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: 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@55: m_pixmapCacheInvalid = true; Chris@0: emit modelChanged(); 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@55: m_pixmapCacheInvalid = true; Chris@0: emit modelChanged(m_lastFillExtent, fillExtent); Chris@0: m_lastFillExtent = fillExtent; Chris@0: } Chris@0: } else { Chris@44: // if (v) { Chris@0: size_t sf = 0; Chris@44: //!!! if (v->getStartFrame() > 0) sf = v->getStartFrame(); Chris@0: #ifdef DEBUG_SPECTROGRAM_REPAINT Chris@0: std::cerr << "SpectrogramLayer: going backwards, emitting modelChanged(" Chris@44: << sf << "," << m_model->getEndFrame() << ")" << std::endl; Chris@0: #endif Chris@55: m_pixmapCacheInvalid = true; Chris@44: emit modelChanged(sf, m_model->getEndFrame()); Chris@44: // } 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@38: if (m_colourScheme == BlackOnWhite) { Chris@86: m_colourMap.setColour(NO_VALUE, Qt::white); Chris@38: } else { Chris@86: m_colourMap.setColour(NO_VALUE, Qt::black); Chris@38: } 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@77: m_crosshairColour = QColor(255, 150, 50); Chris@77: // m_crosshairColour = QColor::fromHsv(240, 160, 255); Chris@0: break; Chris@0: Chris@0: case WhiteOnBlack: Chris@0: colour = QColor(pixel, pixel, pixel); Chris@77: m_crosshairColour = Qt::red; Chris@0: break; Chris@0: Chris@0: case BlackOnWhite: Chris@0: colour = QColor(256-pixel, 256-pixel, 256-pixel); Chris@77: m_crosshairColour = Qt::darkGreen; 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@77: m_crosshairColour = Qt::green; 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@77: m_crosshairColour = QColor::fromHsv(240, 255, 255); Chris@0: break; Chris@0: Chris@71: case BlueOnBlack: Chris@71: colour = QColor::fromHsv Chris@71: (240, pixel > 226 ? 256 - (pixel - 226) * 8 : 255, Chris@71: (pixel * pixel) / 255); Chris@77: m_crosshairColour = Qt::red; Chris@71: break; Chris@71: Chris@40: case Rainbow: Chris@40: hue = 250 - pixel; Chris@40: if (hue < 0) hue += 256; Chris@40: colour = QColor::fromHsv(pixel, 255, 255); Chris@77: m_crosshairColour = Qt::white; Chris@0: break; Chris@0: } Chris@0: Chris@86: m_colourMap.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@86: newPixels[NO_VALUE] = m_colourMap.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@86: newPixels[target] = m_colourMap.getColour(pixel); Chris@9: } Chris@9: Chris@9: for (int pixel = 0; pixel < 256; ++pixel) { Chris@86: m_colourMap.setColour(pixel, newPixels[pixel]); Chris@9: } Chris@0: } Chris@0: Chris@38: float Chris@38: SpectrogramLayer::calculateFrequency(size_t bin, Chris@38: size_t windowSize, Chris@38: size_t windowIncrement, Chris@38: size_t sampleRate, Chris@38: float oldPhase, Chris@38: float newPhase, Chris@38: bool &steadyState) Chris@38: { Chris@38: // At frequency f, phase shift of 2pi (one cycle) happens in 1/f sec. Chris@38: // At hopsize h and sample rate sr, one hop happens in h/sr sec. Chris@38: // At window size w, for bin b, f is b*sr/w. Chris@38: // thus 2pi phase shift happens in w/(b*sr) sec. Chris@38: // We need to know what phase shift we expect from h/sr sec. Chris@38: // -> 2pi * ((h/sr) / (w/(b*sr))) Chris@38: // = 2pi * ((h * b * sr) / (w * sr)) Chris@38: // = 2pi * (h * b) / w. Chris@38: Chris@38: float frequency = (float(bin) * sampleRate) / windowSize; Chris@38: Chris@38: float expectedPhase = Chris@38: oldPhase + (2.0 * M_PI * bin * windowIncrement) / windowSize; Chris@38: Chris@75: float phaseError = princarg(newPhase - expectedPhase); Chris@38: Chris@38: if (fabs(phaseError) < (1.1 * (windowIncrement * M_PI) / windowSize)) { Chris@38: Chris@38: // The new frequency estimate based on the phase error Chris@38: // resulting from assuming the "native" frequency of this bin Chris@38: Chris@38: float newFrequency = Chris@38: (sampleRate * (expectedPhase + phaseError - oldPhase)) / Chris@38: (2 * M_PI * windowIncrement); Chris@38: Chris@38: steadyState = true; Chris@38: return newFrequency; Chris@38: } Chris@38: Chris@38: steadyState = false; Chris@38: return frequency; Chris@38: } Chris@38: Chris@38: void 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@86: float *workbuffer, Chris@38: const Window &windower) const Chris@0: { Chris@38: //!!! we _do_ need a lock for these references to the model Chris@38: // though, don't we? Chris@35: 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@37: if (m_channel == -1) { Chris@37: int channels = m_model->getChannelCount(); Chris@37: if (channels > 1) { Chris@37: for (size_t i = 0; i < windowSize; ++i) { Chris@37: input[i] /= channels; Chris@37: } Chris@37: } Chris@37: } Chris@37: Chris@0: windower.cut(input); Chris@0: Chris@35: for (size_t i = 0; i < windowSize/2; ++i) { Chris@35: double temp = input[i]; Chris@35: input[i] = input[i + windowSize/2]; Chris@35: input[i + windowSize/2] = temp; Chris@35: } Chris@35: Chris@0: fftw_execute(plan); Chris@0: Chris@38: double factor = 0.0; Chris@0: Chris@38: // Calculate magnitude and phase from real and imaginary in Chris@38: // output[i][0] and output[i][1] respectively, and store the phase Chris@38: // straight into cache and the magnitude back into output[i][0] Chris@38: // (because we'll need to know the normalization factor, Chris@38: // i.e. maximum magnitude in this column, before we can store it) Chris@37: Chris@38: for (size_t i = 0; i < windowSize/2; ++i) { Chris@35: Chris@36: double mag = sqrt(output[i][0] * output[i][0] + Chris@36: output[i][1] * output[i][1]); Chris@38: mag /= windowSize / 2; Chris@37: Chris@38: if (mag > factor) factor = mag; Chris@37: Chris@38: double phase = atan2(output[i][1], output[i][0]); Chris@75: phase = princarg(phase); Chris@37: Chris@86: workbuffer[i] = mag; Chris@86: workbuffer[i + windowSize/2] = phase; Chris@38: } Chris@35: Chris@86: m_writeCache->setColumnAt(column, workbuffer, Chris@86: workbuffer + windowSize/2, factor); Chris@38: } Chris@35: Chris@38: unsigned char Chris@38: SpectrogramLayer::getDisplayValue(float input) const Chris@38: { Chris@38: int value; Chris@37: Chris@40: switch (m_colourScale) { Chris@40: Chris@40: default: Chris@40: case LinearColourScale: Chris@40: value = int Chris@40: (input * (m_normalizeColumns ? 1.0 : 50.0) * 255.0) + 1; Chris@40: break; Chris@40: Chris@40: case MeterColourScale: Chris@40: value = AudioLevel::multiplier_to_preview Chris@40: (input * (m_normalizeColumns ? 1.0 : 50.0), 255) + 1; Chris@40: break; Chris@40: Chris@40: case dBColourScale: Chris@40: input = 20.0 * log10(input); Chris@40: input = (input + 80.0) / 80.0; Chris@40: if (input < 0.0) input = 0.0; Chris@40: if (input > 1.0) input = 1.0; Chris@40: value = int(input * 255.0) + 1; Chris@40: break; Chris@40: Chris@40: case PhaseColourScale: Chris@40: value = int((input * 127.0 / M_PI) + 128); Chris@40: break; Chris@0: } Chris@38: Chris@38: if (value > UCHAR_MAX) value = UCHAR_MAX; Chris@38: if (value < 0) value = 0; Chris@38: return value; Chris@0: } Chris@0: Chris@40: float Chris@40: SpectrogramLayer::getInputForDisplayValue(unsigned char uc) const Chris@40: { Chris@40: int value = uc; Chris@40: float input; Chris@40: Chris@40: switch (m_colourScale) { Chris@40: Chris@40: default: Chris@40: case LinearColourScale: Chris@40: input = float(value - 1) / 255.0 / (m_normalizeColumns ? 1 : 50); Chris@40: break; Chris@40: Chris@40: case MeterColourScale: Chris@40: input = AudioLevel::preview_to_multiplier(value - 1, 255) Chris@40: / (m_normalizeColumns ? 1.0 : 50.0); Chris@40: break; Chris@40: Chris@40: case dBColourScale: Chris@40: input = float(value - 1) / 255.0; Chris@40: input = (input * 80.0) - 80.0; Chris@40: input = powf(10.0, input) / 20.0; Chris@40: value = int(input); Chris@40: break; Chris@40: Chris@40: case PhaseColourScale: Chris@40: input = float(value - 128) * M_PI / 127.0; Chris@40: break; Chris@40: } Chris@40: Chris@40: return input; Chris@40: } Chris@40: 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@48: bool haveUndormantViews = false; Chris@48: Chris@48: for (std::map::iterator i = Chris@48: m_layer.m_dormancy.begin(); Chris@48: i != m_layer.m_dormancy.end(); ++i) { Chris@48: Chris@48: if (!i->second) { Chris@48: haveUndormantViews = true; Chris@48: break; Chris@48: } Chris@48: } Chris@48: Chris@48: if (!haveUndormantViews) { Chris@48: Chris@48: if (m_layer.m_cacheInvalid && m_layer.m_cache) { Chris@48: std::cerr << "All views dormant, freeing spectrogram cache" Chris@48: << std::endl; Chris@47: Chris@34: delete m_layer.m_cache; Chris@34: m_layer.m_cache = 0; Chris@34: } Chris@34: Chris@34: } else if (m_layer.m_model && m_layer.m_cacheInvalid) { 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@48: if (m_layer.m_exiting) break; Chris@0: } Chris@0: Chris@48: if (m_layer.m_exiting) break; Chris@48: 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@41: std::cerr << "start = " << start << ", end = " << end << std::endl; Chris@41: 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@44: size_t visibleStart = m_layer.m_candidateFillStartFrame; Chris@44: visibleStart = (visibleStart / windowIncrement) * windowIncrement; Chris@0: Chris@9: size_t width = (end - start) / windowIncrement + 1; Chris@9: size_t height = windowSize / 2; Chris@35: Chris@86: //!!! if (!m_layer.m_cache) { Chris@86: // m_layer.m_cache = new FFTMemoryCache; Chris@86: // } Chris@86: if (!m_layer.m_writeCache) { Chris@86: m_layer.m_writeCache = new FFTFileCache Chris@86: (QString("%1").arg(getObjectExportId(&m_layer)), Chris@86: MatrixFileCache::ReadWrite); Chris@86: } Chris@86: m_layer.m_writeCache->resize(width, height); Chris@86: if (m_layer.m_cache) delete m_layer.m_cache; Chris@86: m_layer.m_cache = new FFTFileCache Chris@86: (QString("%1").arg(getObjectExportId(&m_layer)), Chris@86: MatrixFileCache::ReadOnly); Chris@86: Chris@0: m_layer.setCacheColourmap(); Chris@86: //!!! m_layer.m_writeCache->reset(); Chris@35: Chris@33: // We don't need a lock when writing to or reading from Chris@38: // the pixels in the cache. We do need to ensure we have Chris@38: // the width and height of the cache and the FFT Chris@38: // parameters known before we unlock, in case they change Chris@38: // in the model while we aren't holding a lock. It's safe Chris@38: // for us to continue to use the "old" values if that Chris@38: // happens, because they will continue to match the Chris@80: // dimensions of the actual cache (which this thread Chris@80: // manages, not the layer's). 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@86: float *workbuffer = (float *) Chris@86: fftw_malloc(windowSize * sizeof(float)); Chris@86: 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@37: 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@44: bool doVisibleFirst = (visibleStart != start); Chris@0: Chris@0: if (doVisibleFirst) { Chris@0: Chris@44: for (size_t f = visibleStart; f < end; f += windowIncrement) { Chris@0: Chris@38: m_layer.fillCacheColumn(int((f - start) / windowIncrement), Chris@38: input, output, plan, Chris@38: windowSize, windowIncrement, Chris@86: workbuffer, windower); Chris@38: Chris@38: 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@38: if (++counter == updateAt) { Chris@37: 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@38: m_layer.fillCacheColumn(int((f - start) / windowIncrement), Chris@38: input, output, plan, Chris@38: windowSize, windowIncrement, Chris@86: workbuffer, windower); Chris@38: Chris@38: 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@44: 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@86: fftw_free(workbuffer); 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@40: float Chris@40: SpectrogramLayer::getEffectiveMinFrequency() const Chris@40: { Chris@40: int sr = m_model->getSampleRate(); Chris@40: float minf = float(sr) / m_windowSize; Chris@40: Chris@40: if (m_minFrequency > 0.0) { Chris@40: size_t minbin = size_t((double(m_minFrequency) * m_windowSize) / sr + 0.01); Chris@40: if (minbin < 1) minbin = 1; Chris@40: minf = minbin * sr / m_windowSize; Chris@40: } Chris@40: Chris@40: return minf; Chris@40: } Chris@40: Chris@40: float Chris@40: SpectrogramLayer::getEffectiveMaxFrequency() const Chris@40: { Chris@40: int sr = m_model->getSampleRate(); Chris@40: float maxf = float(sr) / 2; Chris@40: Chris@40: if (m_maxFrequency > 0.0) { Chris@40: size_t maxbin = size_t((double(m_maxFrequency) * m_windowSize) / sr + 0.1); Chris@40: if (maxbin > m_windowSize / 2) maxbin = m_windowSize / 2; Chris@40: maxf = maxbin * sr / m_windowSize; Chris@40: } Chris@40: Chris@40: return maxf; Chris@40: } Chris@40: Chris@0: bool Chris@44: SpectrogramLayer::getYBinRange(View *v, int y, float &q0, float &q1) const Chris@0: { Chris@44: int h = v->height(); Chris@0: if (y < 0 || y >= h) return false; Chris@0: Chris@38: int sr = m_model->getSampleRate(); Chris@40: float minf = getEffectiveMinFrequency(); Chris@40: float 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@38: // Now map these on to actual bins Chris@38: Chris@40: int b0 = int((q0 * m_windowSize) / sr); Chris@40: int b1 = int((q1 * m_windowSize) / sr); Chris@0: Chris@40: //!!! this is supposed to return fractions-of-bins, as it were, hence the floats Chris@38: q0 = b0; Chris@38: q1 = b1; Chris@38: Chris@38: // q0 = (b0 * sr) / m_windowSize; Chris@38: // q1 = (b1 * sr) / m_windowSize; Chris@0: Chris@0: return true; Chris@0: } Chris@38: Chris@0: bool Chris@44: SpectrogramLayer::getXBinRange(View *v, 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@44: int f0 = v->getFrameForX(x) - modelStart; Chris@44: int 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@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@44: SpectrogramLayer::getXBinSourceRange(View *v, int x, RealTime &min, RealTime &max) const Chris@0: { Chris@0: float 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@44: SpectrogramLayer::getYBinSourceRange(View *v, int y, float &freqMin, float &freqMax) Chris@0: const Chris@0: { Chris@0: float 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@0: int sr = m_model->getSampleRate(); Chris@0: Chris@0: for (int q = q0i; q <= q1i; ++q) { Chris@35: int binfreq = (sr * q) / 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@35: Chris@35: bool Chris@44: SpectrogramLayer::getAdjustedYBinSourceRange(View *v, int x, int y, Chris@35: float &freqMin, float &freqMax, Chris@35: float &adjFreqMin, float &adjFreqMax) Chris@35: const Chris@35: { Chris@35: float s0 = 0, s1 = 0; Chris@44: if (!getXBinRange(v, x, s0, s1)) return false; Chris@35: Chris@35: float 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@35: int sr = m_model->getSampleRate(); Chris@35: Chris@38: size_t windowSize = m_windowSize; Chris@38: size_t windowIncrement = getWindowIncrement(); Chris@38: 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@35: float binfreq = (sr * q) / m_windowSize; Chris@35: if (q == q0i) freqMin = binfreq; Chris@35: if (q == q1i) freqMax = binfreq; Chris@37: Chris@38: if (!m_cache || m_cacheInvalid) break; //!!! lock? Chris@38: Chris@38: if (peaksOnly && !m_cache->isLocalPeak(s, q)) continue; Chris@38: Chris@38: if (!m_cache->isOverThreshold(s, q, m_threshold)) continue; Chris@38: Chris@38: float freq = binfreq; Chris@38: bool steady = false; Chris@40: Chris@40: if (s < int(m_cache->getWidth()) - 1) { Chris@38: Chris@38: freq = calculateFrequency(q, Chris@38: windowSize, Chris@38: windowIncrement, Chris@38: sr, Chris@38: m_cache->getPhaseAt(s, q), Chris@38: m_cache->getPhaseAt(s+1, q), Chris@38: steady); 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@44: SpectrogramLayer::getXYBinSourceRange(View *v, int x, int y, Chris@38: float &min, float &max, Chris@38: float &phaseMin, float &phaseMax) const Chris@0: { Chris@0: float q0 = 0, q1 = 0; Chris@44: if (!getYBinRange(v, y, q0, q1)) return false; Chris@0: Chris@0: float 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@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@38: min = 0.0; Chris@38: max = 0.0; Chris@38: phaseMin = 0.0; Chris@38: phaseMax = 0.0; Chris@38: bool have = false; 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@38: Chris@38: float value; Chris@38: Chris@38: value = m_cache->getPhaseAt(s, q); Chris@38: if (!have || value < phaseMin) { phaseMin = value; } Chris@38: if (!have || value > phaseMax) { phaseMax = value; } Chris@38: Chris@38: value = m_cache->getMagnitudeAt(s, q); Chris@38: if (!have || value < min) { min = value; } Chris@38: if (!have || value > max) { max = value; } Chris@38: Chris@38: have = true; Chris@0: } Chris@0: } Chris@0: } Chris@0: Chris@38: if (have) { Chris@37: rv = true; Chris@37: } Chris@0: } Chris@0: Chris@0: m_mutex.unlock(); Chris@0: } Chris@0: Chris@37: return rv; Chris@0: } Chris@0: Chris@0: void Chris@44: SpectrogramLayer::paint(View *v, QPainter &paint, QRect rect) const Chris@0: { Chris@55: if (m_colourScheme == BlackOnWhite) { Chris@55: v->setLightBackground(true); Chris@55: } else { Chris@55: v->setLightBackground(false); Chris@55: } Chris@55: Chris@0: // Profiler profiler("SpectrogramLayer::paint", true); Chris@0: #ifdef DEBUG_SPECTROGRAM_REPAINT Chris@44: std::cerr << "SpectrogramLayer::paint(): m_model is " << m_model << ", zoom level is " << v->getZoomLevel() << ", m_updateTimer " << m_updateTimer << ", pixmap cache invalid " << m_pixmapCacheInvalid << std::endl; Chris@0: #endif Chris@45: Chris@45: long sf = v->getStartFrame(); Chris@45: if (sf < 0) m_candidateFillStartFrame = 0; Chris@45: else m_candidateFillStartFrame = sf; 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@48: std::cerr << "SpectrogramLayer::paint(): Layer is dormant, making it undormant again" << std::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@48: m_dormancy[v] = false; Chris@48: Chris@0: #ifdef DEBUG_SPECTROGRAM_REPAINT Chris@0: std::cerr << "SpectrogramLayer::paint(): About to lock" << std::endl; Chris@0: #endif Chris@0: Chris@37: m_mutex.lock(); 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@44: long startFrame = v->getStartFrame(); Chris@44: int zoomLevel = v->getZoomLevel(); Chris@0: Chris@0: int x0 = 0; Chris@44: int x1 = v->width(); Chris@0: int y0 = 0; Chris@44: int y1 = v->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@44: m_pixmapCache->width() == v->width() && Chris@44: m_pixmapCache->height() == v->height()) { Chris@44: Chris@44: if (v->getXForFrame(m_pixmapCacheStartFrame) == Chris@44: v->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@44: int dx = v->getXForFrame(m_pixmapCacheStartFrame) - Chris@44: v->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@86: scaled.fill(m_colourMap.getColour(0).rgb()); Chris@35: Chris@35: float ymag[h]; Chris@35: float ydiv[h]; Chris@37: Chris@37: int sr = m_model->getSampleRate(); Chris@35: Chris@35: size_t bins = m_windowSize / 2; Chris@35: if (m_maxFrequency > 0) { Chris@35: bins = int((double(m_maxFrequency) * m_windowSize) / sr + 0.1); Chris@35: if (bins > m_windowSize / 2) bins = m_windowSize / 2; Chris@35: } Chris@35: Chris@40: size_t minbin = 1; Chris@37: if (m_minFrequency > 0) { Chris@37: minbin = int((double(m_minFrequency) * m_windowSize) / sr + 0.1); Chris@40: if (minbin < 1) minbin = 1; Chris@37: if (minbin >= bins) minbin = bins - 1; Chris@37: } Chris@37: Chris@37: float minFreq = (float(minbin) * sr) / m_windowSize; Chris@35: float maxFreq = (float(bins) * sr) / m_windowSize; Chris@0: Chris@38: size_t increment = getWindowIncrement(); Chris@40: Chris@40: bool logarithmic = (m_frequencyScale == LogFrequencyScale); Chris@38: Chris@0: m_mutex.unlock(); Chris@0: Chris@35: for (int x = 0; x < w; ++x) { Chris@35: Chris@35: m_mutex.lock(); Chris@35: if (m_cacheInvalid) { Chris@35: m_mutex.unlock(); Chris@35: break; Chris@35: } Chris@35: Chris@35: for (int y = 0; y < h; ++y) { Chris@40: ymag[y] = 0.0; Chris@40: ydiv[y] = 0.0; Chris@35: } Chris@35: Chris@35: float s0 = 0, s1 = 0; Chris@35: Chris@44: if (!getXBinRange(v, x0 + x, s0, s1)) { Chris@35: assert(x <= scaled.width()); Chris@35: m_mutex.unlock(); Chris@35: continue; Chris@35: } Chris@35: Chris@35: int s0i = int(s0 + 0.001); Chris@35: int s1i = int(s1); Chris@35: Chris@45: if (s1i >= m_cache->getWidth()) { Chris@45: if (s0i >= m_cache->getWidth()) { Chris@45: m_mutex.unlock(); Chris@45: continue; Chris@45: } else { Chris@45: s1i = s0i; Chris@45: } Chris@45: } Chris@45: Chris@38: for (size_t q = minbin; q < bins; ++q) { Chris@35: Chris@40: float f0 = (float(q) * sr) / m_windowSize; Chris@40: float f1 = (float(q + 1) * sr) / m_windowSize; Chris@40: Chris@40: float y0 = 0, y1 = 0; Chris@40: Chris@45: if (m_binDisplay != PeakFrequencies) { Chris@44: y0 = v->getYForFrequency(f1, minFreq, maxFreq, logarithmic); Chris@44: y1 = v->getYForFrequency(f0, minFreq, maxFreq, logarithmic); Chris@40: } Chris@40: Chris@35: for (int s = s0i; s <= s1i; ++s) { Chris@35: Chris@40: if (m_binDisplay == PeakBins || Chris@40: m_binDisplay == PeakFrequencies) { Chris@40: if (!m_cache->isLocalPeak(s, q)) continue; Chris@40: } Chris@40: Chris@40: if (!m_cache->isOverThreshold(s, q, m_threshold)) continue; Chris@40: Chris@35: float sprop = 1.0; Chris@35: if (s == s0i) sprop *= (s + 1) - s0; Chris@35: if (s == s1i) sprop *= s1 - s; Chris@35: Chris@38: if (m_binDisplay == PeakFrequencies && Chris@40: s < int(m_cache->getWidth()) - 1) { Chris@35: Chris@38: bool steady = false; Chris@38: f0 = f1 = calculateFrequency(q, Chris@38: m_windowSize, Chris@38: increment, Chris@38: sr, Chris@38: m_cache->getPhaseAt(s, q), Chris@38: m_cache->getPhaseAt(s+1, q), Chris@38: steady); Chris@40: Chris@44: y0 = y1 = v->getYForFrequency Chris@40: (f0, minFreq, maxFreq, logarithmic); Chris@35: } Chris@38: Chris@35: int y0i = int(y0 + 0.001); Chris@35: int y1i = int(y1); Chris@35: Chris@35: for (int y = y0i; y <= y1i; ++y) { Chris@35: Chris@35: if (y < 0 || y >= h) continue; Chris@35: Chris@35: float yprop = sprop; Chris@35: if (y == y0i) yprop *= (y + 1) - y0; Chris@35: if (y == y1i) yprop *= y1 - y; Chris@37: Chris@38: float value; Chris@38: Chris@38: if (m_colourScale == PhaseColourScale) { Chris@38: value = m_cache->getPhaseAt(s, q); Chris@38: } else if (m_normalizeColumns) { Chris@38: value = m_cache->getNormalizedMagnitudeAt(s, q) * m_gain; Chris@38: } else { Chris@38: value = m_cache->getMagnitudeAt(s, q) * m_gain; Chris@38: } Chris@37: Chris@37: ymag[y] += yprop * value; Chris@35: ydiv[y] += yprop; Chris@35: } Chris@35: } Chris@35: } Chris@35: Chris@35: for (int y = 0; y < h; ++y) { Chris@35: Chris@35: if (ydiv[y] > 0.0) { Chris@40: Chris@40: unsigned char pixel = 0; Chris@40: Chris@38: float avg = ymag[y] / ydiv[y]; Chris@38: pixel = getDisplayValue(avg); Chris@40: Chris@40: assert(x <= scaled.width()); Chris@86: QColor c = m_colourMap.getColour(pixel); Chris@40: scaled.setPixel(x, y, Chris@40: qRgb(c.red(), c.green(), c.blue())); Chris@35: } Chris@35: } Chris@35: Chris@35: m_mutex.unlock(); Chris@35: } Chris@35: 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@42: float Chris@44: SpectrogramLayer::getYForFrequency(View *v, float 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@42: float Chris@44: SpectrogramLayer::getFrequencyForY(View *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@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@79: SpectrogramLayer::getValueExtents(float &min, float &max, QString &unit) const Chris@79: { Chris@79: min = getEffectiveMinFrequency(); Chris@79: max = getEffectiveMaxFrequency(); Chris@79: unit = "Hz"; Chris@79: return true; Chris@79: } Chris@79: Chris@79: bool Chris@44: SpectrogramLayer::snapToFeatureFrame(View *v, 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@77: bool Chris@77: SpectrogramLayer::getCrosshairExtents(View *v, QPainter &paint, Chris@77: QPoint cursorPos, Chris@77: std::vector &extents) const Chris@77: { Chris@77: QRect vertical(cursorPos.x() - 12, 0, 12, v->height()); 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@77: return true; Chris@77: } Chris@77: Chris@77: void Chris@77: SpectrogramLayer::paintCrosshairs(View *v, QPainter &paint, Chris@77: QPoint cursorPos) const Chris@77: { Chris@77: paint.save(); Chris@77: paint.setPen(m_crosshairColour); Chris@77: Chris@77: paint.drawLine(0, cursorPos.y(), cursorPos.x() - 1, cursorPos.y()); Chris@77: paint.drawLine(cursorPos.x(), 0, cursorPos.x(), v->height()); Chris@77: Chris@77: float fundamental = getFrequencyForY(v, cursorPos.y()); Chris@77: Chris@77: int harmonic = 2; Chris@77: Chris@77: while (harmonic < 100) { Chris@77: Chris@77: float hy = lrintf(getYForFrequency(v, fundamental * harmonic)); Chris@77: if (hy < 0 || hy > v->height()) 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@77: hy, Chris@77: cursorPos.x(), Chris@77: hy); Chris@77: Chris@77: ++harmonic; Chris@77: } Chris@77: Chris@77: paint.restore(); Chris@77: } Chris@77: Chris@25: QString Chris@44: SpectrogramLayer::getFeatureDescription(View *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@38: float magMin = 0, magMax = 0; Chris@38: float phaseMin = 0, phaseMax = 0; Chris@0: float freqMin = 0, freqMax = 0; Chris@35: float 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@38: float dbMin = AudioLevel::multiplier_to_dB(magMin); Chris@38: float 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@43: dbMinString = QString("%1").arg(lrintf(dbMin)); Chris@43: } Chris@43: if (dbMax == AudioLevel::DB_FLOOR) { Chris@43: dbMaxString = tr("-Inf"); Chris@43: } else { Chris@43: dbMaxString = QString("%1").arg(lrintf(dbMax)); Chris@43: } 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@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@40: switch (m_colourScale) { Chris@40: default: Chris@40: case LinearColourScale: Chris@40: cw = paint.fontMetrics().width(QString("0.00")); Chris@40: break; Chris@40: Chris@40: case MeterColourScale: Chris@40: case dBColourScale: Chris@40: cw = std::max(paint.fontMetrics().width(tr("-Inf")), Chris@40: paint.fontMetrics().width(tr("-90"))); Chris@40: break; Chris@40: Chris@40: case PhaseColourScale: Chris@40: cw = paint.fontMetrics().width(QString("-") + QChar(0x3c0)); Chris@40: break; Chris@40: } Chris@40: Chris@40: return cw; Chris@40: } Chris@40: Chris@40: int Chris@44: SpectrogramLayer::getVerticalScaleWidth(View *v, QPainter &paint) const Chris@0: { Chris@0: if (!m_model || !m_model->isOK()) return 0; Chris@0: Chris@40: int 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@0: int fw = paint.fontMetrics().width(QString("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@44: SpectrogramLayer::paintVerticalScale(View *v, 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@40: int tickw = (m_frequencyScale == LogFrequencyScale ? 10 : 4); Chris@40: int pkw = (m_frequencyScale == LogFrequencyScale ? 10 : 0); Chris@40: 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@40: int cw = getColourScaleWidth(paint); 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@40: if (m_cache && !m_cacheInvalid && h > textHeight * 2 + 10) { //!!! lock? Chris@40: Chris@40: int ch = h - textHeight * 2 - 8; Chris@40: paint.drawRect(4, textHeight + 4, cw - 1, ch + 1); Chris@40: Chris@40: QString top, bottom; Chris@40: Chris@40: switch (m_colourScale) { Chris@40: default: Chris@40: case LinearColourScale: Chris@40: top = (m_normalizeColumns ? "1.0" : "0.02"); Chris@40: bottom = (m_normalizeColumns ? "0.0" : "0.00"); Chris@40: break; Chris@40: Chris@40: case MeterColourScale: Chris@40: top = (m_normalizeColumns ? QString("0") : Chris@40: QString("%1").arg(int(AudioLevel::multiplier_to_dB(0.02)))); Chris@40: bottom = QString("%1"). Chris@40: arg(int(AudioLevel::multiplier_to_dB Chris@40: (AudioLevel::preview_to_multiplier(0, 255)))); Chris@40: break; Chris@40: Chris@40: case dBColourScale: Chris@40: top = "0"; Chris@40: bottom = "-80"; Chris@40: break; Chris@40: Chris@40: case PhaseColourScale: Chris@40: top = QChar(0x3c0); Chris@40: bottom = "-" + top; Chris@40: break; Chris@40: } Chris@40: Chris@40: paint.drawText((cw + 6 - paint.fontMetrics().width(top)) / 2, Chris@40: 2 + textHeight + toff, top); Chris@40: Chris@40: paint.drawText((cw + 6 - paint.fontMetrics().width(bottom)) / 2, Chris@40: h + toff - 3, bottom); Chris@40: Chris@40: paint.save(); Chris@40: paint.setBrush(Qt::NoBrush); Chris@40: for (int i = 0; i < ch; ++i) { Chris@40: int v = (i * 255) / ch + 1; Chris@86: paint.setPen(m_colourMap.getColour(v)); Chris@40: paint.drawLine(5, 4 + textHeight + ch - i, Chris@40: cw + 2, 4 + textHeight + ch - i); 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@44: for (int y = 0; y < v->height(); ++y) { Chris@0: Chris@0: float q0, q1; Chris@44: if (!getYBinRange(v, v->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@40: int freq = (sr * bin) / m_windowSize; 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@40: if (bin == 1) text = QString("%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@40: paint.drawLine(w - pkw - 1, 0, w - pkw - 1, h); Chris@40: Chris@40: int sr = m_model->getSampleRate();//!!! lock? Chris@40: float minf = getEffectiveMinFrequency(); Chris@40: float maxf = getEffectiveMaxFrequency(); Chris@40: Chris@40: int py = h; Chris@40: paint.setBrush(paint.pen().color()); Chris@40: Chris@40: for (int i = 0; i < 128; ++i) { Chris@40: Chris@40: float f = Pitch::getFrequencyForPitch(i); Chris@44: int y = lrintf(v->getYForFrequency(f, minf, maxf, true)); Chris@40: int n = (i % 12); Chris@40: if (n == 1 || n == 3 || n == 6 || n == 8 || n == 10) { Chris@40: // black notes Chris@40: paint.drawLine(w - pkw, y, w, y); Chris@41: int rh = ((py - y) / 4) * 2; Chris@41: if (rh < 2) rh = 2; Chris@41: paint.drawRect(w - pkw, y - (py-y)/4, pkw/2, rh); Chris@40: } else if (n == 0 || n == 5) { Chris@40: // C, A Chris@40: if (py < h) { Chris@40: paint.drawLine(w - pkw, (y + py) / 2, w, (y + py) / 2); Chris@40: } Chris@40: } Chris@40: Chris@40: py = y; Chris@40: } Chris@40: } 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@37: "gain=\"%5\" " Chris@37: "threshold=\"%6\" ") Chris@6: .arg(m_channel) Chris@6: .arg(m_windowSize) Chris@6: .arg(m_windowType) Chris@6: .arg(m_windowOverlap) 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@37: "binDisplay=\"%7\" " Chris@37: "normalizeColumns=\"%8\"") Chris@37: .arg(m_minFrequency) Chris@6: .arg(m_maxFrequency) Chris@6: .arg(m_colourScale) Chris@6: .arg(m_colourScheme) Chris@37: .arg(m_colourRotation) Chris@35: .arg(m_frequencyScale) Chris@37: .arg(m_binDisplay) Chris@36: .arg(m_normalizeColumns ? "true" : "false"); 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@37: float threshold = attributes.value("threshold").toFloat(&ok); Chris@37: if (ok) setThreshold(threshold); Chris@37: Chris@37: size_t minFrequency = attributes.value("minFrequency").toUInt(&ok); Chris@37: if (ok) setMinFrequency(minFrequency); Chris@37: 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@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@36: setNormalizeColumns(normalizeColumns); Chris@11: } Chris@11: Chris@11: Chris@0: #ifdef INCLUDE_MOCFILES Chris@0: #include "SpectrogramLayer.moc.cpp" Chris@0: #endif Chris@0: