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@182:     This file copyright 2006 Chris Cannam and QMUL.
Chris@0:     
Chris@59:     This program is free software; you can redistribute it and/or
Chris@59:     modify it under the terms of the GNU General Public License as
Chris@59:     published by the Free Software Foundation; either version 2 of the
Chris@59:     License, or (at your option) any later version.  See the file
Chris@59:     COPYING included with this distribution for more information.
Chris@0: */
Chris@0: 
Chris@30: #ifndef _SPECTROGRAM_LAYER_H_
Chris@30: #define _SPECTROGRAM_LAYER_H_
Chris@0: 
Chris@193: #include "SliceableLayer.h"
Chris@0: #include "base/Window.h"
Chris@71: #include "base/RealTime.h"
Chris@92: #include "base/Thread.h"
Chris@122: #include "base/PropertyContainer.h"
Chris@128: #include "data/model/PowerOfSqrtTwoZoomConstraint.h"
Chris@128: #include "data/model/DenseTimeValueModel.h"
Chris@130: #include "data/model/FFTModel.h"
Chris@0: 
Chris@0: #include <QMutex>
Chris@0: #include <QWaitCondition>
Chris@95: #include <QImage>
Chris@95: #include <QPixmap>
Chris@0: 
Chris@0: class View;
Chris@0: class QPainter;
Chris@0: class QImage;
Chris@0: class QPixmap;
Chris@0: class QTimer;
Chris@130: class FFTModel;
Chris@484: class Dense3DModelPeakCache;
Chris@114: 
Chris@0: 
Chris@0: /**
Chris@0:  * SpectrogramLayer represents waveform data (obtained from a
Chris@0:  * DenseTimeValueModel) in spectrogram form.
Chris@0:  */
Chris@0: 
Chris@193: class SpectrogramLayer : public SliceableLayer,
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@389:     virtual void setSynchronousPainting(bool synchronous);
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@77:     virtual bool getCrosshairExtents(View *, QPainter &, QPoint cursorPos,
Chris@77:                                      std::vector<QRect> &extents) const;
Chris@77:     virtual void paintCrosshairs(View *, QPainter &, QPoint) const;
Chris@77: 
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@283:     virtual void measureDoubleClick(View *, QMouseEvent *);
Chris@283: 
Chris@224:     virtual bool hasLightBackground() const;
Chris@224: 
Chris@0:     void setModel(const DenseTimeValueModel *model);
Chris@0: 
Chris@0:     virtual PropertyList getProperties() const;
Chris@87:     virtual QString getPropertyLabel(const PropertyName &) const;
Chris@335:     virtual QString getPropertyIconName(const PropertyName &) 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@216:                                          int *min, int *max, int *deflt) const;
Chris@0:     virtual QString getPropertyValueLabel(const PropertyName &,
Chris@0: 					  int value) const;
Chris@167:     virtual RangeMapper *getNewPropertyRangeMapper(const PropertyName &) 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@109:     void setWindowHopLevel(size_t level);
Chris@97:     size_t getWindowHopLevel() const;
Chris@0: 
Chris@0:     void setWindowType(WindowType type);
Chris@0:     WindowType getWindowType() const;
Chris@0: 
Chris@109:     void setZeroPadLevel(size_t level);
Chris@109:     size_t getZeroPadLevel() const;
Chris@109: 
Chris@0:     /**
Chris@110:      * Set the gain multiplier for sample values in this view.
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@110:      * Set the threshold for sample values to qualify for being shown
Chris@110:      * in the FFT, 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@215:         dBSquaredColourScale,
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@120:     void setNormalizeVisibleArea(bool n);
Chris@120:     bool getNormalizeVisibleArea() const;
Chris@120: 
Chris@197:     void setColourMap(int map);
Chris@197:     int getColourMap() 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@287: 
Chris@287:     virtual ColourSignificance getLayerColourSignificance() const {
Chris@287:         return ColourHasMeaningfulValue;
Chris@287:     }
Chris@15: 
Chris@267:     float getYForFrequency(const View *v, float frequency) const;
Chris@267:     float getFrequencyForY(const View *v, int y) const;
Chris@42: 
Chris@115:     virtual int getCompletion(View *v) const;
Chris@0: 
Chris@101:     virtual bool getValueExtents(float &min, float &max,
Chris@101:                                  bool &logarithmic, QString &unit) const;
Chris@101: 
Chris@101:     virtual bool getDisplayExtents(float &min, float &max) const;
Chris@79: 
Chris@120:     virtual bool setDisplayExtents(float min, float max);
Chris@120: 
Chris@267:     virtual bool getYScaleValue(const View *, int, float &, QString &) const;
Chris@261: 
Chris@316:     virtual void toXml(QTextStream &stream, QString indent = "",
Chris@316:                        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@248:     virtual bool isLayerScrollable(const View *) const { return false; }
Chris@94: 
Chris@133:     virtual int getVerticalZoomSteps(int &defaultStep) const;
Chris@133:     virtual int getCurrentVerticalZoomStep() const;
Chris@133:     virtual void setVerticalZoomStep(int);
Chris@187:     virtual RangeMapper *getNewVerticalZoomRangeMapper() const;
Chris@133: 
Chris@193:     virtual const Model *getSliceableModel() const;
Chris@193: 
Chris@0: protected slots:
Chris@0:     void cacheInvalid();
Chris@0:     void cacheInvalid(size_t startFrame, size_t endFrame);
Chris@122:     
Chris@122:     void preferenceChanged(PropertyContainer::PropertyName name);
Chris@0: 
Chris@0:     void fillTimerTimedOut();
Chris@0: 
Chris@0: protected:
Chris@0:     const DenseTimeValueModel *m_model; // I do not own this
Chris@484: 
Chris@35:     int                 m_channel;
Chris@35:     size_t              m_windowSize;
Chris@35:     WindowType          m_windowType;
Chris@97:     size_t              m_windowHopLevel;
Chris@109:     size_t              m_zeroPadLevel;
Chris@107:     size_t              m_fftSize;
Chris@35:     float               m_gain;
Chris@215:     float               m_initialGain;
Chris@37:     float               m_threshold;
Chris@215:     float               m_initialThreshold;
Chris@35:     int                 m_colourRotation;
Chris@215:     int                 m_initialRotation;
Chris@37:     size_t              m_minFrequency;
Chris@35:     size_t              m_maxFrequency;
Chris@135:     size_t              m_initialMaxFrequency;
Chris@35:     ColourScale         m_colourScale;
Chris@197:     int                 m_colourMap;
Chris@77:     QColor              m_crosshairColour;
Chris@35:     FrequencyScale      m_frequencyScale;
Chris@37:     BinDisplay          m_binDisplay;
Chris@36:     bool                m_normalizeColumns;
Chris@120:     bool                m_normalizeVisibleArea;
Chris@133:     int                 m_lastEmittedZoomStep;
Chris@389:     bool                m_synchronous;
Chris@0: 
Chris@215:     mutable int         m_lastPaintBlockWidth;
Chris@215:     mutable RealTime    m_lastPaintTime;
Chris@215: 
Chris@38:     enum { NO_VALUE = 0 }; // colour index for unused pixels
Chris@38: 
Chris@197:     class Palette
Chris@86:     {
Chris@86:     public:
Chris@86:         QColor getColour(unsigned char index) const {
Chris@86:             return m_colours[index];
Chris@86:         }
Chris@86:     
Chris@86:         void setColour(unsigned char index, QColor colour) {
Chris@86:             m_colours[index] = colour;
Chris@86:         }
Chris@86: 
Chris@86:     private:
Chris@86:         QColor m_colours[256];
Chris@86:     };
Chris@86: 
Chris@197:     Palette m_palette;
Chris@31: 
Chris@477:     /**
Chris@478:      * ImageCache covers the area of the view, at view resolution.
Chris@477:      * Not all of it is necessarily valid at once (it is refreshed
Chris@477:      * in parts when scrolling, for example).
Chris@477:      */
Chris@478:     struct ImageCache
Chris@95:     {
Chris@478:         QImage image;
Chris@95:         QRect validArea;
Chris@95:         long startFrame;
Chris@95:         size_t zoomLevel;
Chris@95:     };
Chris@478:     typedef std::map<const View *, ImageCache> ViewImageCache;
Chris@478:     void invalidateImageCaches();
Chris@478:     void invalidateImageCaches(size_t startFrame, size_t endFrame);
Chris@478:     mutable ViewImageCache m_imageCaches;
Chris@477: 
Chris@477:     /**
Chris@477:      * When painting, we draw directly onto the draw buffer and then
Chris@478:      * copy this to the part of the image cache that needed refreshing
Chris@478:      * before copying the image cache onto the window.  (Remind me why
Chris@477:      * we don't draw directly onto the cache?)
Chris@477:      */
Chris@95:     mutable QImage m_drawBuffer;
Chris@0: 
Chris@114:     mutable QTimer *m_updateTimer;
Chris@110: 
Chris@44:     mutable size_t m_candidateFillStartFrame;
Chris@0:     bool m_exiting;
Chris@0: 
Chris@197:     void initialisePalette();
Chris@197:     void rotatePalette(int distance);
Chris@0: 
Chris@119:     unsigned char getDisplayValue(View *v, float input) const;
Chris@40:     float getInputForDisplayValue(unsigned char uc) const;
Chris@40: 
Chris@40:     int getColourScaleWidth(QPainter &) const;
Chris@40: 
Chris@121:     void illuminateLocalFeatures(View *v, QPainter &painter) const;
Chris@121: 
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@486: 
Chris@486:     // Note that the getYBin... methods return the nominal bin in the
Chris@486:     // un-smoothed spectrogram.  This is not necessarily the same bin
Chris@486:     // as is pulled from the spectrogram and drawn at the given
Chris@486:     // position, if the spectrogram has oversampling smoothing.  Use
Chris@486:     // getSmoothedYBinRange to obtain that.
Chris@486: 
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@486:     bool getSmoothedYBinRange(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@97:         if (m_windowHopLevel == 0) return m_windowSize;
Chris@97:         else if (m_windowHopLevel == 1) return (m_windowSize * 3) / 4;
Chris@97:         else return m_windowSize / (1 << (m_windowHopLevel - 1));
Chris@0:     }
Chris@113: 
Chris@114:     size_t getZeroPadLevel(const View *v) const;
Chris@114:     size_t getFFTSize(const View *v) const;
Chris@130:     FFTModel *getFFTModel(const View *v) const;
Chris@484:     Dense3DModelPeakCache *getPeakCache(const View *v) const;
Chris@130:     void invalidateFFTModels();
Chris@115: 
Chris@130:     typedef std::pair<FFTModel *, int> FFTFillPair; // model, last fill
Chris@115:     typedef std::map<const View *, FFTFillPair> ViewFFTMap;
Chris@484:     typedef std::map<const View *, Dense3DModelPeakCache *> PeakCacheMap;
Chris@130:     mutable ViewFFTMap m_fftModels;
Chris@484:     mutable PeakCacheMap m_peakCaches;
Chris@193:     mutable Model *m_sliceableModel;
Chris@119: 
Chris@119:     class MagnitudeRange {
Chris@119:     public:
Chris@119:         MagnitudeRange() : m_min(0), m_max(0) { }
Chris@119:         bool operator==(const MagnitudeRange &r) {
Chris@119:             return r.m_min == m_min && r.m_max == m_max;
Chris@119:         }
Chris@119:         bool isSet() const { return (m_min != 0 || m_max != 0); }
Chris@119:         void set(float min, float max) {
Chris@119:             m_min = convert(min);
Chris@119:             m_max = convert(max);
Chris@119:             if (m_max < m_min) m_max = m_min;
Chris@119:         }
Chris@119:         bool sample(float f) {
Chris@119:             unsigned int ui = convert(f);
Chris@119:             bool changed = false;
Chris@119:             if (isSet()) {
Chris@119:                 if (ui < m_min) { m_min = ui; changed = true; }
Chris@119:                 if (ui > m_max) { m_max = ui; changed = true; }
Chris@119:             } else {
Chris@119:                 m_max = m_min = ui;
Chris@119:                 changed = true;
Chris@119:             }
Chris@119:             return changed;
Chris@119:         }            
Chris@119:         bool sample(const MagnitudeRange &r) {
Chris@119:             bool changed = false;
Chris@119:             if (isSet()) {
Chris@119:                 if (r.m_min < m_min) { m_min = r.m_min; changed = true; }
Chris@119:                 if (r.m_max > m_max) { m_max = r.m_max; changed = true; }
Chris@119:             } else {
Chris@119:                 m_min = r.m_min;
Chris@119:                 m_max = r.m_max;
Chris@119:                 changed = true;
Chris@119:             }
Chris@119:             return changed;
Chris@119:         }            
Chris@119:         float getMin() const { return float(m_min) / UINT_MAX; }
Chris@119:         float getMax() const { return float(m_max) / UINT_MAX; }
Chris@119:     private:
Chris@119:         unsigned int m_min;
Chris@119:         unsigned int m_max;
Chris@119:         unsigned int convert(float f) {
Chris@119:             if (f < 0.f) f = 0.f;
Chris@119:             if (f > 1.f) f = 1.f;
Chris@119:             return (unsigned int)(f * UINT_MAX);
Chris@119:         }
Chris@119:     };
Chris@119: 
Chris@119:     typedef std::map<const View *, MagnitudeRange> ViewMagMap;
Chris@119:     mutable ViewMagMap m_viewMags;
Chris@119:     mutable std::vector<MagnitudeRange> m_columnMags;
Chris@119:     void invalidateMagnitudes();
Chris@119:     bool updateViewMagnitudes(View *v) const;
Chris@484:     bool paintDrawBuffer(View *v, int w, int h,
Chris@490:                          int *binforx, float *binfory,
Chris@491:                          bool usePeaksCache,
Chris@491:                          MagnitudeRange &overallMag,
Chris@491:                          bool &overallMagChanged) const;
Chris@488:     bool paintDrawBufferPeakFrequencies(View *v, int w, int h,
Chris@488:                                         int *binforx,
Chris@488:                                         int minbin,
Chris@488:                                         int maxbin,
Chris@488:                                         float displayMinFreq,
Chris@488:                                         float displayMaxFreq,
Chris@491:                                         bool logarithmic,
Chris@491:                                         MagnitudeRange &overallMag,
Chris@491:                                         bool &overallMagChanged) const;
Chris@273: 
Chris@273:     virtual void updateMeasureRectYCoords(View *v, const MeasureRect &r) const;
Chris@273:     virtual void setMeasureRectYCoord(View *v, MeasureRect &r, bool start, int y) const;
Chris@0: };
Chris@0: 
Chris@0: #endif