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@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@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@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@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@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@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@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@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@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@0: 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@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@0: emit layerParametersChanged(); Chris@0: Chris@0: m_mutex.unlock(); Chris@0: fillCache(); Chris@0: 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: emit layerParametersChanged(); Chris@0: Chris@0: m_mutex.unlock(); Chris@0: fillCache(); Chris@0: 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: emit layerParametersChanged(); Chris@0: Chris@0: m_mutex.unlock(); 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: emit layerParametersChanged(); Chris@0: Chris@0: m_mutex.unlock(); 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: emit layerParametersChanged(); Chris@0: Chris@0: m_mutex.unlock(); 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: emit layerParametersChanged(); Chris@0: Chris@0: m_mutex.unlock(); 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@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: emit layerParametersChanged(); Chris@0: Chris@0: m_mutex.unlock(); Chris@0: fillCache(); 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@0: emit layerParametersChanged(); Chris@0: Chris@0: m_mutex.unlock(); 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: emit layerParametersChanged(); Chris@0: Chris@0: m_mutex.unlock(); 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@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@0: m_cache->setNumColors(256); Chris@0: Chris@0: m_cache->setColor(0, qRgb(255, 255, 255)); 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@0: m_cache->setColor Chris@0: (pixel, qRgb(colour.red(), colour.green(), colour.blue())); Chris@0: } 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@0: const Window &windower, Chris@0: bool lock) const Chris@0: { Chris@0: size_t increment = m_windowSize - m_windowSize * m_windowOverlap / 100; Chris@0: int startFrame = increment * column; Chris@0: int endFrame = startFrame + m_windowSize; Chris@0: Chris@0: startFrame -= int(m_windowSize - increment) / 2; Chris@0: endFrame -= int(m_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@0: while (got + pfx < m_windowSize) { Chris@0: input[got + pfx] = 0.0; Chris@0: ++got; Chris@0: } Chris@0: Chris@0: if (m_gain != 1.0) { Chris@0: for (size_t i = 0; i < m_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@0: for (size_t i = 0; i < m_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@0: mag /= m_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@1: if (column < m_cache->width() && (int)i < m_cache->height()) { Chris@0: m_cache->setPixel(column, i, value + 1); // 0 is "unset" Chris@0: } Chris@0: } Chris@0: Chris@0: if (lock) m_mutex.unlock(); Chris@0: return !interrupted; Chris@0: } Chris@0: 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@1: 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@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@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@0: m_layer.m_cache = new QImage((end - start) / windowIncrement + 1, Chris@1: windowSize / 2, Chris@1: QImage::Format_Indexed8); Chris@0: Chris@0: m_layer.setCacheColourmap(); Chris@0: 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@0: Window windower(m_layer.m_windowType, m_layer.m_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@0: input, output, plan, 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@0: input, output, plan, 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@0: input, output, plan, 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@0: SpectrogramLayer::getXBinRange(int x, float &s0, float &s1, LayerRange *range) const Chris@0: { Chris@0: long startFrame; Chris@0: int zoomLevel; Chris@0: size_t modelStart; Chris@0: size_t modelEnd; Chris@0: Chris@0: if (range) { Chris@0: startFrame = range->startFrame; Chris@0: zoomLevel = range->zoomLevel; Chris@0: modelStart = range->modelStart; Chris@0: modelEnd = range->modelEnd; Chris@0: } else { Chris@0: startFrame = m_view->getStartFrame(); Chris@0: zoomLevel = m_view->getZoomLevel(); Chris@0: modelStart = m_model->getStartFrame(); Chris@0: modelEnd = m_model->getEndFrame(); Chris@0: } Chris@0: Chris@0: // Each pixel column covers an exact range of sample frames: Chris@0: int f0 = startFrame + x * zoomLevel - modelStart; Chris@0: int f1 = f0 + zoomLevel - 1; Chris@0: Chris@0: if (f1 < int(modelStart) || f0 > int(modelEnd)) return false; Chris@0: 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: 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@0: int cw = m_cache->width(); Chris@0: int ch = m_cache->height(); 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@0: int value = m_cache->scanLine(q)[s]; 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@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@0: if (m_pixmapCacheStartFrame / zoomLevel == Chris@0: startFrame / zoomLevel) { 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@0: int dx = (m_pixmapCacheStartFrame - startFrame) / zoomLevel; 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: LayerRange range = { m_view->getStartFrame(), m_view->getZoomLevel(), Chris@0: m_model->getStartFrame(), m_model->getEndFrame() }; 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@0: int cw = m_cache->width(); Chris@0: int ch = m_cache->height(); 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@0: if (!getXBinRange(x0 + x, s0, s1, &range)) { 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@0: total += qprop * m_cache->scanLine(q)[s]; 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@0: scaled.setPixel(x, y, m_cache->color(pixel)); 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: //!!! drawLocalFeatureDescription(paint); 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@0: QRect Chris@0: SpectrogramLayer::getFeatureDescriptionRect(QPainter &paint, QPoint pos) const Chris@0: { Chris@0: if (!m_model || !m_model->isOK()) return QRect(); Chris@0: Chris@0: QString timeLabel = tr("Time: "); Chris@0: QString freqLabel = tr("Hz: "); Chris@0: QString dBLabel = tr("dB: "); Chris@0: Chris@0: // assume time is widest Chris@0: RealTime rtMin, rtMax; Chris@0: if (!getXBinSourceRange(pos.x(), rtMin, rtMax)) return QRect(); Chris@0: QString timeMinText = QString("%1").arg(rtMin.toText(true).c_str()); Chris@0: QString timeMaxText = QString(" - %1").arg(rtMax.toText(true).c_str()); Chris@0: Chris@0: QFontMetrics metrics = paint.fontMetrics(); Chris@0: Chris@0: int labelwidth = Chris@0: std::max(std::max(metrics.width(timeLabel), Chris@0: metrics.width(freqLabel)), Chris@0: metrics.width(dBLabel)); Chris@0: Chris@0: int boxwidth = labelwidth + Chris@0: metrics.width(timeMinText) + metrics.width(timeMaxText); Chris@0: Chris@0: int fontHeight = metrics.height(); Chris@0: int boxheight = fontHeight * 3 + 4; Chris@0: Chris@0: return QRect(0, 0, boxwidth + 20, boxheight + 15); Chris@0: } Chris@0: Chris@0: void Chris@0: SpectrogramLayer::paintLocalFeatureDescription(QPainter &paint, Chris@0: QRect rect, QPoint pos) const Chris@0: { Chris@0: int x = pos.x(); Chris@0: int y = pos.y(); Chris@0: Chris@0: if (!m_model || !m_model->isOK()) return; Chris@0: Chris@0: float dbMin = 0, dbMax = 0; Chris@0: float freqMin = 0, freqMax = 0; Chris@0: RealTime rtMin, rtMax; Chris@0: Chris@0: bool haveDb = false; Chris@0: Chris@0: if (!getXBinSourceRange(x, rtMin, rtMax)) return; Chris@0: if (!getYBinSourceRange(y, freqMin, freqMax)) return; Chris@0: if (getXYBinSourceRange(x, y, dbMin, dbMax)) haveDb = true; Chris@0: Chris@0: QString timeLabel = tr("Time: "); Chris@0: QString freqLabel = tr("Hz: "); Chris@0: QString dBLabel = tr("dB: "); Chris@0: Chris@0: QString timeMinText = QString("%1").arg(rtMin.toText(true).c_str()); Chris@0: QString timeMaxText = QString(" - %1").arg(rtMax.toText(true).c_str()); Chris@0: Chris@0: QString freqMinText = QString("%1").arg(freqMin); Chris@0: QString freqMaxText = ""; Chris@0: if (freqMax != freqMin) { Chris@0: freqMaxText = QString(" - %1").arg(freqMax); Chris@0: } Chris@0: Chris@0: QString dBMinText = ""; Chris@0: QString dBMaxText = ""; Chris@0: Chris@0: if (haveDb) { Chris@0: int dbmxi = int(dbMax - 0.001); Chris@0: int dbmni = int(dbMin - 0.001); Chris@0: dBMinText = QString("%1").arg(dbmni); Chris@0: if (dbmxi != dbmni) dBMaxText = QString(" - %1").arg(dbmxi); Chris@0: } Chris@0: Chris@0: QFontMetrics metrics = paint.fontMetrics(); Chris@0: Chris@0: int labelwidth = Chris@0: std::max(std::max(metrics.width(timeLabel), Chris@0: metrics.width(freqLabel)), Chris@0: metrics.width(dBLabel)); Chris@0: Chris@0: int minwidth = Chris@0: std::max(std::max(metrics.width(timeMinText), Chris@0: metrics.width(freqMinText)), Chris@0: metrics.width(dBMinText)); Chris@0: Chris@0: int maxwidth = Chris@0: std::max(std::max(metrics.width(timeMaxText), Chris@0: metrics.width(freqMaxText)), Chris@0: metrics.width(dBMaxText)); Chris@0: Chris@0: int boxwidth = labelwidth + minwidth + maxwidth; Chris@0: Chris@0: int fontAscent = metrics.ascent(); Chris@0: int fontHeight = metrics.height(); Chris@0: Chris@0: int boxheight = fontHeight * 3 + 4; Chris@0: Chris@0: // paint.setPen(Qt::white); Chris@0: // paint.setBrush(Qt::NoBrush); Chris@0: Chris@0: //!!! int xbase = m_view->width() - boxwidth - 20; Chris@0: int xbase = rect.x() + 5; Chris@0: int ybase = rect.y() + 5; Chris@0: Chris@0: paint.drawRect(xbase, ybase, boxwidth + 10, Chris@0: boxheight + 10 - metrics.descent() + 1); Chris@0: Chris@0: paint.drawText(xbase + 5 + labelwidth - metrics.width(timeLabel), Chris@0: ybase + 5 + fontAscent, timeLabel); Chris@0: Chris@0: paint.drawText(xbase + 5 + labelwidth - metrics.width(freqLabel), Chris@0: ybase + 7 + fontAscent + fontHeight, freqLabel); Chris@0: Chris@0: paint.drawText(xbase + 5 + labelwidth - metrics.width(dBLabel), Chris@0: ybase + 9 + fontAscent + fontHeight * 2, dBLabel); Chris@0: Chris@0: paint.drawText(xbase + 5 + labelwidth + minwidth - metrics.width(timeMinText), Chris@0: ybase + 5 + fontAscent, timeMinText); Chris@0: Chris@0: paint.drawText(xbase + 5 + labelwidth + minwidth - metrics.width(freqMinText), Chris@0: ybase + 7 + fontAscent + fontHeight, freqMinText); Chris@0: Chris@0: paint.drawText(xbase + 5 + labelwidth + minwidth - metrics.width(dBMinText), Chris@0: ybase + 9 + fontAscent + fontHeight * 2, dBMinText); Chris@0: Chris@0: paint.drawText(xbase + 5 + labelwidth + minwidth, Chris@0: ybase + 5 + fontAscent, timeMaxText); Chris@0: Chris@0: paint.drawText(xbase + 5 + labelwidth + minwidth, Chris@0: ybase + 7 + fontAscent + fontHeight, freqMaxText); Chris@0: Chris@0: paint.drawText(xbase + 5 + labelwidth + minwidth, Chris@0: ybase + 9 + fontAscent + fontHeight * 2, dBMaxText); Chris@0: } Chris@0: Chris@0: /*!!! Chris@0: Chris@0: bool Chris@0: SpectrogramLayer::identifyLocalFeatures(bool on, int x, int y) Chris@0: { Chris@0: return true; //!!! Chris@0: Chris@0: m_identify = on; Chris@0: m_identifyX = x; Chris@0: m_identifyY = y; Chris@0: Chris@0: m_view->update(); Chris@0: */ Chris@0: /* Chris@0: if (!m_model || !m_model->isOK()) return false; Chris@0: Chris@0: std::cerr << "SpectrogramLayer::identifyLocalFeatures(" << on << "," << x << "," << y << ")" << std::endl; Chris@0: Chris@0: float dbMin = 0, dbMax = 0; Chris@0: float freqMin = 0, freqMax = 0; Chris@0: RealTime rtMin, rtMax; Chris@0: Chris@0: if (getXBinSourceRange(x, rtMin, rtMax)) { Chris@0: std::cerr << "Times: " << rtMin << " -> " << rtMax << std::endl; Chris@0: } else return false; Chris@0: Chris@0: if (getYBinSourceRange(y, freqMin, freqMax)) { Chris@0: std::cerr << "Frequencies: " << freqMin << " -> " << freqMax << std::endl; Chris@0: } else return false; Chris@0: Chris@0: if (getXYBinSourceRange(x, y, dbMin, dbMax)) { Chris@0: std::cerr << "dB: " << dbMin << " -> " << dbMax << std::endl; Chris@0: } Chris@0: Chris@0: m_identifyX = x; Chris@0: m_identifyY = y; Chris@0: m_identify = true; Chris@0: */ Chris@0: /*!!! Chris@0: return true; Chris@0: } Chris@0: */ 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@0: #ifdef INCLUDE_MOCFILES Chris@0: #include "SpectrogramLayer.moc.cpp" Chris@0: #endif Chris@0: