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@30: #ifndef _SPECTROGRAM_LAYER_H_ Chris@30: #define _SPECTROGRAM_LAYER_H_ Chris@0: Chris@0: #include "base/Layer.h" Chris@0: #include "base/Window.h" Chris@71: #include "base/RealTime.h" Chris@0: #include "model/PowerOfSqrtTwoZoomConstraint.h" Chris@0: #include "model/DenseTimeValueModel.h" Chris@0: Chris@0: #include Chris@0: #include Chris@0: #include Chris@0: Chris@0: #include Chris@0: Chris@38: #include Chris@38: Chris@0: class View; Chris@0: class QPainter; Chris@0: class QImage; Chris@0: class QPixmap; Chris@0: class QTimer; Chris@0: Chris@0: /** Chris@0: * SpectrogramLayer represents waveform data (obtained from a Chris@0: * DenseTimeValueModel) in spectrogram form. Chris@0: */ Chris@0: Chris@0: class SpectrogramLayer : public Layer, Chris@31: public PowerOfSqrtTwoZoomConstraint Chris@0: { Chris@0: Q_OBJECT Chris@0: Chris@0: public: Chris@37: enum Configuration { FullRangeDb, MelodicRange, MelodicPeaks }; Chris@0: Chris@44: SpectrogramLayer(Configuration = FullRangeDb); Chris@0: ~SpectrogramLayer(); Chris@0: Chris@0: virtual const ZoomConstraint *getZoomConstraint() const { return this; } Chris@0: virtual const Model *getModel() const { return m_model; } Chris@44: virtual void paint(View *v, QPainter &paint, QRect rect) const; Chris@0: Chris@44: virtual int getVerticalScaleWidth(View *v, QPainter &) const; Chris@44: virtual void paintVerticalScale(View *v, QPainter &paint, QRect rect) const; Chris@0: Chris@44: virtual QString getFeatureDescription(View *v, QPoint &) const; Chris@0: Chris@44: virtual bool snapToFeatureFrame(View *v, int &frame, Chris@28: size_t &resolution, Chris@28: SnapType snap) const; Chris@13: Chris@0: void setModel(const DenseTimeValueModel *model); Chris@0: Chris@0: virtual PropertyList getProperties() const; Chris@0: virtual PropertyType getPropertyType(const PropertyName &) const; Chris@0: virtual QString getPropertyGroupName(const PropertyName &) const; Chris@0: virtual int getPropertyRangeAndValue(const PropertyName &, Chris@0: int *min, int *max) const; Chris@0: virtual QString getPropertyValueLabel(const PropertyName &, Chris@0: int value) const; Chris@0: virtual void setProperty(const PropertyName &, int value); Chris@0: Chris@0: /** Chris@0: * Specify the channel to use from the source model. Chris@0: * A value of -1 means to mix all available channels. Chris@0: * The default is channel 0. Chris@0: */ Chris@0: void setChannel(int); Chris@0: int getChannel() const; Chris@0: Chris@0: void setWindowSize(size_t); Chris@0: size_t getWindowSize() const; Chris@0: Chris@0: void setWindowOverlap(size_t percent); Chris@0: size_t getWindowOverlap() const; Chris@0: Chris@0: void setWindowType(WindowType type); Chris@0: WindowType getWindowType() const; Chris@0: Chris@0: /** Chris@0: * Set the gain multiplier for sample values in this view prior to Chris@0: * FFT calculation. Chris@0: * Chris@0: * The default is 1.0. Chris@0: */ Chris@0: void setGain(float gain); Chris@0: float getGain() const; Chris@0: Chris@37: /** Chris@37: * Set the threshold for sample values to be shown in the FFT, Chris@37: * in voltage units. Chris@37: * Chris@37: * The default is 0.0. Chris@37: */ Chris@37: void setThreshold(float threshold); Chris@37: float getThreshold() const; Chris@37: Chris@37: void setMinFrequency(size_t); Chris@37: size_t getMinFrequency() const; Chris@37: Chris@0: void setMaxFrequency(size_t); // 0 -> no maximum Chris@0: size_t getMaxFrequency() const; Chris@0: Chris@37: enum ColourScale { Chris@37: LinearColourScale, Chris@37: MeterColourScale, Chris@37: dBColourScale, Chris@37: PhaseColourScale Chris@37: }; Chris@0: Chris@0: /** Chris@0: * Specify the scale for sample levels. See WaveformLayer for Chris@0: * details of meter and dB scaling. The default is dBColourScale. Chris@0: */ Chris@0: void setColourScale(ColourScale); Chris@0: ColourScale getColourScale() const; Chris@0: Chris@35: enum FrequencyScale { Chris@35: LinearFrequencyScale, Chris@35: LogFrequencyScale Chris@35: }; Chris@0: Chris@0: /** Chris@0: * Specify the scale for the y axis. Chris@0: */ Chris@0: void setFrequencyScale(FrequencyScale); Chris@0: FrequencyScale getFrequencyScale() const; Chris@0: Chris@37: enum BinDisplay { Chris@37: AllBins, Chris@37: PeakBins, Chris@37: PeakFrequencies Chris@35: }; Chris@35: Chris@35: /** Chris@35: * Specify the processing of frequency bins for the y axis. Chris@35: */ Chris@37: void setBinDisplay(BinDisplay); Chris@37: BinDisplay getBinDisplay() const; Chris@35: Chris@36: void setNormalizeColumns(bool n); Chris@36: bool getNormalizeColumns() const; Chris@36: Chris@0: enum ColourScheme { DefaultColours, WhiteOnBlack, BlackOnWhite, Chris@71: RedOnBlue, YellowOnBlack, BlueOnBlack, Rainbow }; Chris@0: Chris@0: void setColourScheme(ColourScheme scheme); Chris@0: ColourScheme getColourScheme() const; Chris@0: Chris@9: /** Chris@9: * Specify the colourmap rotation for the colour scale. Chris@9: */ Chris@9: void setColourRotation(int); Chris@9: int getColourRotation() const; Chris@9: Chris@0: virtual VerticalPosition getPreferredFrameCountPosition() const { Chris@0: return PositionTop; Chris@0: } Chris@0: Chris@15: virtual bool isLayerOpaque() const { return true; } Chris@15: Chris@44: float getYForFrequency(View *v, float frequency) const; Chris@44: float getFrequencyForY(View *v, int y) const; Chris@42: Chris@0: virtual int getCompletion() const; Chris@0: Chris@6: virtual QString toXmlString(QString indent = "", Chris@6: QString extraAttributes = "") const; Chris@6: Chris@11: void setProperties(const QXmlAttributes &attributes); Chris@11: Chris@47: virtual void setLayerDormant(const View *v, bool dormant); Chris@29: Chris@0: protected slots: Chris@0: void cacheInvalid(); Chris@0: void cacheInvalid(size_t startFrame, size_t endFrame); Chris@0: Chris@0: void fillTimerTimedOut(); Chris@0: Chris@0: protected: Chris@0: const DenseTimeValueModel *m_model; // I do not own this Chris@0: Chris@35: int m_channel; Chris@35: size_t m_windowSize; Chris@35: WindowType m_windowType; Chris@35: size_t m_windowOverlap; Chris@35: float m_gain; Chris@37: float m_threshold; Chris@35: int m_colourRotation; Chris@37: size_t m_minFrequency; Chris@35: size_t m_maxFrequency; Chris@35: ColourScale m_colourScale; Chris@35: ColourScheme m_colourScheme; Chris@35: FrequencyScale m_frequencyScale; Chris@37: BinDisplay m_binDisplay; Chris@36: bool m_normalizeColumns; Chris@0: Chris@38: // At the moment we cache one unsigned char per bin for the Chris@38: // magnitude -- which is nothing like precise enough to allow us Chris@38: // to subsequently adjust gain etc without recalculating the Chris@38: // cached values -- plus optionally one unsigned char per bin for Chris@38: // phase-adjusted frequency. Chris@37: Chris@38: // To speed up redrawing after parameter changes, we would like to Chris@38: // cache magnitude in a way that can have gain applied afterwards Chris@38: // and can determine whether something is a peak or not, and also Chris@38: // cache phase rather than only phase-adjusted frequency so that Chris@38: // we don't have to recalculate if switching between phase and Chris@38: // magnitude displays. Chris@38: Chris@38: // This implies probably 16 bits for a normalized magnitude (in Chris@38: // dB?) and at most 16 bits for phase. 16 or 32 bits per bin Chris@38: // instead of 8 or 16. Chris@38: Chris@38: // Each column's magnitudes are expected to be stored normalized Chris@38: // to [0,1] with respect to the column, so the normalization Chris@38: // factor should be calculated before all values in a column, and Chris@38: // set appropriately. Chris@38: Chris@31: class Cache { Chris@31: public: Chris@38: Cache(); // of size zero, call resize() before using Chris@31: ~Cache(); Chris@31: Chris@38: size_t getWidth() const { return m_width; } Chris@38: size_t getHeight() const { return m_height; } Chris@38: Chris@38: void resize(size_t width, size_t height); Chris@38: void reset(); // zero-fill or 1-fill as appropriate without changing size Chris@38: Chris@38: float getMagnitudeAt(size_t x, size_t y) const { Chris@38: return getNormalizedMagnitudeAt(x, y) * m_factor[x]; Chris@38: } Chris@35: Chris@38: float getNormalizedMagnitudeAt(size_t x, size_t y) const { Chris@44: return float(m_magnitude[x][y]) / 65535.0; Chris@38: } Chris@31: Chris@38: float getPhaseAt(size_t x, size_t y) const { Chris@44: int16_t i = (int16_t)m_phase[x][y]; Chris@39: return (float(i) / 32767.0) * M_PI; Chris@38: } Chris@31: Chris@38: bool isLocalPeak(size_t x, size_t y) const { Chris@44: if (y > 0 && m_magnitude[x][y] < m_magnitude[x][y-1]) return false; Chris@44: if (y < m_height-1 && m_magnitude[x][y] < m_magnitude[x][y+1]) return false; Chris@38: return true; Chris@38: } Chris@31: Chris@38: bool isOverThreshold(size_t x, size_t y, float threshold) const { Chris@38: if (threshold == 0.0) return true; Chris@38: return getMagnitudeAt(x, y) > threshold; Chris@38: } Chris@38: Chris@38: void setNormalizationFactor(size_t x, float factor) { Chris@41: if (x < m_width) m_factor[x] = factor; Chris@38: } Chris@38: Chris@38: void setMagnitudeAt(size_t x, size_t y, float mag) { Chris@38: // norm factor must already be set Chris@38: setNormalizedMagnitudeAt(x, y, mag / m_factor[x]); Chris@38: } Chris@38: Chris@38: void setNormalizedMagnitudeAt(size_t x, size_t y, float norm) { Chris@41: if (x < m_width && y < m_height) { Chris@44: m_magnitude[x][y] = uint16_t(norm * 65535.0); Chris@41: } Chris@38: } Chris@38: Chris@38: void setPhaseAt(size_t x, size_t y, float phase) { Chris@38: // phase in range -pi -> pi Chris@41: if (x < m_width && y < m_height) { Chris@44: m_phase[x][y] = uint16_t(int16_t((phase * 32767) / M_PI)); Chris@41: } Chris@38: } Chris@38: Chris@38: QColor getColour(unsigned char index) const { Chris@38: return m_colours[index]; Chris@38: } Chris@38: Chris@38: void setColour(unsigned char index, QColor colour) { Chris@38: m_colours[index] = colour; Chris@38: } Chris@38: Chris@38: private: Chris@31: size_t m_width; Chris@31: size_t m_height; Chris@38: uint16_t **m_magnitude; Chris@38: uint16_t **m_phase; Chris@38: float *m_factor; Chris@31: QColor m_colours[256]; Chris@38: Chris@38: void resize(uint16_t **&, size_t, size_t); Chris@31: }; Chris@38: Chris@38: enum { NO_VALUE = 0 }; // colour index for unused pixels Chris@38: Chris@31: Cache *m_cache; Chris@31: bool m_cacheInvalid; Chris@31: Chris@0: class CacheFillThread : public QThread Chris@0: { Chris@0: public: Chris@0: CacheFillThread(SpectrogramLayer &layer) : Chris@0: m_layer(layer), m_fillExtent(0) { } Chris@0: Chris@0: size_t getFillExtent() const { return m_fillExtent; } Chris@0: size_t getFillCompletion() const { return m_fillCompletion; } Chris@0: virtual void run(); Chris@0: Chris@0: protected: Chris@0: SpectrogramLayer &m_layer; Chris@0: size_t m_fillExtent; Chris@0: size_t m_fillCompletion; Chris@0: }; Chris@0: Chris@0: void fillCache(); Chris@0: Chris@0: mutable QPixmap *m_pixmapCache; Chris@0: mutable bool m_pixmapCacheInvalid; Chris@0: mutable long m_pixmapCacheStartFrame; Chris@0: mutable size_t m_pixmapCacheZoomLevel; Chris@0: Chris@0: QWaitCondition m_condition; Chris@0: mutable QMutex m_mutex; Chris@0: Chris@0: CacheFillThread *m_fillThread; Chris@0: QTimer *m_updateTimer; Chris@44: mutable size_t m_candidateFillStartFrame; Chris@0: size_t m_lastFillExtent; Chris@0: bool m_exiting; Chris@0: Chris@0: void setCacheColourmap(); Chris@9: void rotateCacheColourmap(int distance); Chris@0: Chris@38: void fillCacheColumn(int column, Chris@0: double *inputBuffer, Chris@0: fftw_complex *outputBuffer, Chris@0: fftw_plan plan, Chris@9: size_t windowSize, Chris@9: size_t windowIncrement, Chris@38: const Window &windower) Chris@0: const; Chris@0: Chris@38: static float calculateFrequency(size_t bin, Chris@38: size_t windowSize, Chris@38: size_t windowIncrement, Chris@38: size_t sampleRate, Chris@38: float previousPhase, Chris@38: float currentPhase, Chris@38: bool &steadyState); Chris@38: Chris@38: unsigned char getDisplayValue(float input) const; Chris@40: float getInputForDisplayValue(unsigned char uc) const; Chris@40: Chris@40: int getColourScaleWidth(QPainter &) const; Chris@40: Chris@40: float getEffectiveMinFrequency() const; Chris@40: float getEffectiveMaxFrequency() const; Chris@38: Chris@0: struct LayerRange { Chris@0: long startFrame; Chris@0: int zoomLevel; Chris@0: size_t modelStart; Chris@0: size_t modelEnd; Chris@0: }; Chris@44: bool getXBinRange(View *v, int x, float &windowMin, float &windowMax) const; Chris@44: bool getYBinRange(View *v, int y, float &freqBinMin, float &freqBinMax) const; Chris@0: Chris@44: bool getYBinSourceRange(View *v, int y, float &freqMin, float &freqMax) const; Chris@44: bool getAdjustedYBinSourceRange(View *v, int x, int y, Chris@35: float &freqMin, float &freqMax, Chris@35: float &adjFreqMin, float &adjFreqMax) const; Chris@44: bool getXBinSourceRange(View *v, int x, RealTime &timeMin, RealTime &timeMax) const; Chris@44: bool getXYBinSourceRange(View *v, int x, int y, float &min, float &max, Chris@38: float &phaseMin, float &phaseMax) const; Chris@0: Chris@0: size_t getWindowIncrement() const { Chris@0: return m_windowSize - m_windowSize * m_windowOverlap / 100; Chris@0: } Chris@0: }; Chris@0: Chris@0: #endif