| Chris@0 | 1 /* -*- c-basic-offset: 4 -*-  vi:set ts=8 sts=4 sw=4: */ | 
| Chris@0 | 2 | 
| Chris@0 | 3 /* | 
| Chris@0 | 4     A waveform viewer and audio annotation editor. | 
| Chris@5 | 5     Chris Cannam, Queen Mary University of London, 2005-2006 | 
| Chris@0 | 6 | 
| Chris@0 | 7     This is experimental software.  Not for distribution. | 
| Chris@0 | 8 */ | 
| Chris@0 | 9 | 
| Chris@30 | 10 #ifndef _SPECTROGRAM_LAYER_H_ | 
| Chris@30 | 11 #define _SPECTROGRAM_LAYER_H_ | 
| Chris@0 | 12 | 
| Chris@0 | 13 #include "base/Layer.h" | 
| Chris@0 | 14 #include "base/Window.h" | 
| Chris@0 | 15 #include "model/PowerOfSqrtTwoZoomConstraint.h" | 
| Chris@0 | 16 #include "model/DenseTimeValueModel.h" | 
| Chris@0 | 17 | 
| Chris@0 | 18 #include <QThread> | 
| Chris@0 | 19 #include <QMutex> | 
| Chris@0 | 20 #include <QWaitCondition> | 
| Chris@0 | 21 | 
| Chris@0 | 22 #include <fftw3.h> | 
| Chris@0 | 23 | 
| Chris@38 | 24 #include <stdint.h> | 
| Chris@38 | 25 | 
| Chris@0 | 26 class View; | 
| Chris@0 | 27 class QPainter; | 
| Chris@0 | 28 class QImage; | 
| Chris@0 | 29 class QPixmap; | 
| Chris@0 | 30 class QTimer; | 
| Chris@0 | 31 class RealTime; | 
| Chris@0 | 32 | 
| Chris@0 | 33 /** | 
| Chris@0 | 34  * SpectrogramLayer represents waveform data (obtained from a | 
| Chris@0 | 35  * DenseTimeValueModel) in spectrogram form. | 
| Chris@0 | 36  */ | 
| Chris@0 | 37 | 
| Chris@0 | 38 class SpectrogramLayer : public Layer, | 
| Chris@31 | 39 			 public PowerOfSqrtTwoZoomConstraint | 
| Chris@0 | 40 { | 
| Chris@0 | 41     Q_OBJECT | 
| Chris@0 | 42 | 
| Chris@0 | 43 public: | 
| Chris@37 | 44     enum Configuration { FullRangeDb, MelodicRange, MelodicPeaks }; | 
| Chris@0 | 45 | 
| Chris@0 | 46     SpectrogramLayer(View *w, Configuration = FullRangeDb); | 
| Chris@0 | 47     ~SpectrogramLayer(); | 
| Chris@0 | 48 | 
| Chris@0 | 49     virtual const ZoomConstraint *getZoomConstraint() const { return this; } | 
| Chris@0 | 50     virtual const Model *getModel() const { return m_model; } | 
| Chris@0 | 51     virtual void paint(QPainter &paint, QRect rect) const; | 
| Chris@0 | 52 | 
| Chris@0 | 53     virtual int getVerticalScaleWidth(QPainter &) const; | 
| Chris@0 | 54     virtual void paintVerticalScale(QPainter &paint, QRect rect) const; | 
| Chris@0 | 55 | 
| Chris@25 | 56     virtual QString getFeatureDescription(QPoint &) const; | 
| Chris@0 | 57 | 
| Chris@28 | 58     virtual bool snapToFeatureFrame(int &frame, | 
| Chris@28 | 59 				    size_t &resolution, | 
| Chris@28 | 60 				    SnapType snap) const; | 
| Chris@13 | 61 | 
| Chris@0 | 62     void setModel(const DenseTimeValueModel *model); | 
| Chris@0 | 63 | 
| Chris@0 | 64     virtual PropertyList getProperties() const; | 
| Chris@0 | 65     virtual PropertyType getPropertyType(const PropertyName &) const; | 
| Chris@0 | 66     virtual QString getPropertyGroupName(const PropertyName &) const; | 
| Chris@0 | 67     virtual int getPropertyRangeAndValue(const PropertyName &, | 
| Chris@0 | 68 					   int *min, int *max) const; | 
| Chris@0 | 69     virtual QString getPropertyValueLabel(const PropertyName &, | 
| Chris@0 | 70 					  int value) const; | 
| Chris@0 | 71     virtual void setProperty(const PropertyName &, int value); | 
| Chris@0 | 72 | 
| Chris@0 | 73     /** | 
| Chris@0 | 74      * Specify the channel to use from the source model. | 
| Chris@0 | 75      * A value of -1 means to mix all available channels. | 
| Chris@0 | 76      * The default is channel 0. | 
| Chris@0 | 77      */ | 
| Chris@0 | 78     void setChannel(int); | 
| Chris@0 | 79     int getChannel() const; | 
| Chris@0 | 80 | 
| Chris@0 | 81     void setWindowSize(size_t); | 
| Chris@0 | 82     size_t getWindowSize() const; | 
| Chris@0 | 83 | 
| Chris@0 | 84     void setWindowOverlap(size_t percent); | 
| Chris@0 | 85     size_t getWindowOverlap() const; | 
| Chris@0 | 86 | 
| Chris@0 | 87     void setWindowType(WindowType type); | 
| Chris@0 | 88     WindowType getWindowType() const; | 
| Chris@0 | 89 | 
| Chris@0 | 90     /** | 
| Chris@0 | 91      * Set the gain multiplier for sample values in this view prior to | 
| Chris@0 | 92      * FFT calculation. | 
| Chris@0 | 93      * | 
| Chris@0 | 94      * The default is 1.0. | 
| Chris@0 | 95      */ | 
| Chris@0 | 96     void setGain(float gain); | 
| Chris@0 | 97     float getGain() const; | 
| Chris@0 | 98 | 
| Chris@37 | 99     /** | 
| Chris@37 | 100      * Set the threshold for sample values to be shown in the FFT, | 
| Chris@37 | 101      * in voltage units. | 
| Chris@37 | 102      * | 
| Chris@37 | 103      * The default is 0.0. | 
| Chris@37 | 104      */ | 
| Chris@37 | 105     void setThreshold(float threshold); | 
| Chris@37 | 106     float getThreshold() const; | 
| Chris@37 | 107 | 
| Chris@37 | 108     void setMinFrequency(size_t); | 
| Chris@37 | 109     size_t getMinFrequency() const; | 
| Chris@37 | 110 | 
| Chris@0 | 111     void setMaxFrequency(size_t); // 0 -> no maximum | 
| Chris@0 | 112     size_t getMaxFrequency() const; | 
| Chris@0 | 113 | 
| Chris@37 | 114     enum ColourScale { | 
| Chris@37 | 115 	LinearColourScale, | 
| Chris@37 | 116 	MeterColourScale, | 
| Chris@37 | 117 	dBColourScale, | 
| Chris@37 | 118 	PhaseColourScale | 
| Chris@37 | 119     }; | 
| Chris@0 | 120 | 
| Chris@0 | 121     /** | 
| Chris@0 | 122      * Specify the scale for sample levels.  See WaveformLayer for | 
| Chris@0 | 123      * details of meter and dB scaling.  The default is dBColourScale. | 
| Chris@0 | 124      */ | 
| Chris@0 | 125     void setColourScale(ColourScale); | 
| Chris@0 | 126     ColourScale getColourScale() const; | 
| Chris@0 | 127 | 
| Chris@35 | 128     enum FrequencyScale { | 
| Chris@35 | 129 	LinearFrequencyScale, | 
| Chris@35 | 130 	LogFrequencyScale | 
| Chris@35 | 131     }; | 
| Chris@0 | 132 | 
| Chris@0 | 133     /** | 
| Chris@0 | 134      * Specify the scale for the y axis. | 
| Chris@0 | 135      */ | 
| Chris@0 | 136     void setFrequencyScale(FrequencyScale); | 
| Chris@0 | 137     FrequencyScale getFrequencyScale() const; | 
| Chris@0 | 138 | 
| Chris@37 | 139     enum BinDisplay { | 
| Chris@37 | 140 	AllBins, | 
| Chris@37 | 141 	PeakBins, | 
| Chris@37 | 142 	PeakFrequencies | 
| Chris@35 | 143     }; | 
| Chris@35 | 144 | 
| Chris@35 | 145     /** | 
| Chris@35 | 146      * Specify the processing of frequency bins for the y axis. | 
| Chris@35 | 147      */ | 
| Chris@37 | 148     void setBinDisplay(BinDisplay); | 
| Chris@37 | 149     BinDisplay getBinDisplay() const; | 
| Chris@35 | 150 | 
| Chris@36 | 151     void setNormalizeColumns(bool n); | 
| Chris@36 | 152     bool getNormalizeColumns() const; | 
| Chris@36 | 153 | 
| Chris@0 | 154     enum ColourScheme { DefaultColours, WhiteOnBlack, BlackOnWhite, | 
| Chris@0 | 155 			RedOnBlue, YellowOnBlack, RedOnBlack }; | 
| Chris@0 | 156 | 
| Chris@0 | 157     void setColourScheme(ColourScheme scheme); | 
| Chris@0 | 158     ColourScheme getColourScheme() const; | 
| Chris@0 | 159 | 
| Chris@9 | 160     /** | 
| Chris@9 | 161      * Specify the colourmap rotation for the colour scale. | 
| Chris@9 | 162      */ | 
| Chris@9 | 163     void setColourRotation(int); | 
| Chris@9 | 164     int getColourRotation() const; | 
| Chris@9 | 165 | 
| Chris@0 | 166     virtual VerticalPosition getPreferredFrameCountPosition() const { | 
| Chris@0 | 167 	return PositionTop; | 
| Chris@0 | 168     } | 
| Chris@0 | 169 | 
| Chris@15 | 170     virtual bool isLayerOpaque() const { return true; } | 
| Chris@15 | 171 | 
| Chris@0 | 172     virtual int getCompletion() const; | 
| Chris@0 | 173 | 
| Chris@6 | 174     virtual QString toXmlString(QString indent = "", | 
| Chris@6 | 175 				QString extraAttributes = "") const; | 
| Chris@6 | 176 | 
| Chris@11 | 177     void setProperties(const QXmlAttributes &attributes); | 
| Chris@11 | 178 | 
| Chris@33 | 179     virtual void setLayerDormant(bool dormant); | 
| Chris@29 | 180 | 
| Chris@0 | 181 protected slots: | 
| Chris@0 | 182     void cacheInvalid(); | 
| Chris@0 | 183     void cacheInvalid(size_t startFrame, size_t endFrame); | 
| Chris@0 | 184 | 
| Chris@0 | 185     void fillTimerTimedOut(); | 
| Chris@0 | 186 | 
| Chris@0 | 187 protected: | 
| Chris@0 | 188     const DenseTimeValueModel *m_model; // I do not own this | 
| Chris@0 | 189 | 
| Chris@35 | 190     int                 m_channel; | 
| Chris@35 | 191     size_t              m_windowSize; | 
| Chris@35 | 192     WindowType          m_windowType; | 
| Chris@35 | 193     size_t              m_windowOverlap; | 
| Chris@35 | 194     float               m_gain; | 
| Chris@37 | 195     float               m_threshold; | 
| Chris@35 | 196     int                 m_colourRotation; | 
| Chris@37 | 197     size_t              m_minFrequency; | 
| Chris@35 | 198     size_t              m_maxFrequency; | 
| Chris@35 | 199     ColourScale         m_colourScale; | 
| Chris@35 | 200     ColourScheme        m_colourScheme; | 
| Chris@35 | 201     FrequencyScale      m_frequencyScale; | 
| Chris@37 | 202     BinDisplay          m_binDisplay; | 
| Chris@36 | 203     bool                m_normalizeColumns; | 
| Chris@0 | 204 | 
| Chris@38 | 205     // At the moment we cache one unsigned char per bin for the | 
| Chris@38 | 206     // magnitude -- which is nothing like precise enough to allow us | 
| Chris@38 | 207     // to subsequently adjust gain etc without recalculating the | 
| Chris@38 | 208     // cached values -- plus optionally one unsigned char per bin for | 
| Chris@38 | 209     // phase-adjusted frequency. | 
| Chris@37 | 210 | 
| Chris@38 | 211     // To speed up redrawing after parameter changes, we would like to | 
| Chris@38 | 212     // cache magnitude in a way that can have gain applied afterwards | 
| Chris@38 | 213     // and can determine whether something is a peak or not, and also | 
| Chris@38 | 214     // cache phase rather than only phase-adjusted frequency so that | 
| Chris@38 | 215     // we don't have to recalculate if switching between phase and | 
| Chris@38 | 216     // magnitude displays. | 
| Chris@38 | 217 | 
| Chris@38 | 218     // This implies probably 16 bits for a normalized magnitude (in | 
| Chris@38 | 219     // dB?) and at most 16 bits for phase.  16 or 32 bits per bin | 
| Chris@38 | 220     // instead of 8 or 16. | 
| Chris@38 | 221 | 
| Chris@38 | 222     // Each column's magnitudes are expected to be stored normalized | 
| Chris@38 | 223     // to [0,1] with respect to the column, so the normalization | 
| Chris@38 | 224     // factor should be calculated before all values in a column, and | 
| Chris@38 | 225     // set appropriately. | 
| Chris@38 | 226 | 
| Chris@31 | 227     class Cache { | 
| Chris@31 | 228     public: | 
| Chris@38 | 229 	Cache(); // of size zero, call resize() before using | 
| Chris@31 | 230 	~Cache(); | 
| Chris@31 | 231 | 
| Chris@38 | 232 	size_t getWidth() const { return m_width; } | 
| Chris@38 | 233 	size_t getHeight() const { return m_height; } | 
| Chris@38 | 234 | 
| Chris@38 | 235 	void resize(size_t width, size_t height); | 
| Chris@38 | 236 	void reset(); // zero-fill or 1-fill as appropriate without changing size | 
| Chris@38 | 237 | 
| Chris@38 | 238 	float getMagnitudeAt(size_t x, size_t y) const { | 
| Chris@38 | 239 	    return getNormalizedMagnitudeAt(x, y) * m_factor[x]; | 
| Chris@38 | 240 	} | 
| Chris@35 | 241 | 
| Chris@38 | 242 	float getNormalizedMagnitudeAt(size_t x, size_t y) const { | 
| Chris@38 | 243 	    return float(m_magnitude[y][x]) / 65535.0; | 
| Chris@38 | 244 	} | 
| Chris@31 | 245 | 
| Chris@38 | 246 	float getPhaseAt(size_t x, size_t y) const { | 
| Chris@39 | 247 	    int16_t i = (int16_t)m_phase[y][x]; | 
| Chris@39 | 248 	    return (float(i) / 32767.0) * M_PI; | 
| Chris@38 | 249 	} | 
| Chris@31 | 250 | 
| Chris@38 | 251 	bool isLocalPeak(size_t x, size_t y) const { | 
| Chris@38 | 252 	    if (y > 0 && m_magnitude[y][x] < m_magnitude[y-1][x]) return false; | 
| Chris@38 | 253 	    if (y < m_height-1 && m_magnitude[y][x] < m_magnitude[y+1][x]) return false; | 
| Chris@38 | 254 	    return true; | 
| Chris@38 | 255 	} | 
| Chris@31 | 256 | 
| Chris@38 | 257 	bool isOverThreshold(size_t x, size_t y, float threshold) const { | 
| Chris@38 | 258 	    if (threshold == 0.0) return true; | 
| Chris@38 | 259 	    return getMagnitudeAt(x, y) > threshold; | 
| Chris@38 | 260 	} | 
| Chris@38 | 261 | 
| Chris@38 | 262 	void setNormalizationFactor(size_t x, float factor) { | 
| Chris@38 | 263 	    m_factor[x] = factor; | 
| Chris@38 | 264 	} | 
| Chris@38 | 265 | 
| Chris@38 | 266 	void setMagnitudeAt(size_t x, size_t y, float mag) { | 
| Chris@38 | 267 	    // norm factor must already be set | 
| Chris@38 | 268 	    setNormalizedMagnitudeAt(x, y, mag / m_factor[x]); | 
| Chris@38 | 269 	} | 
| Chris@38 | 270 | 
| Chris@38 | 271 	void setNormalizedMagnitudeAt(size_t x, size_t y, float norm) { | 
| Chris@38 | 272 	    m_magnitude[y][x] = uint16_t(norm * 65535.0); | 
| Chris@38 | 273 	} | 
| Chris@38 | 274 | 
| Chris@38 | 275 	void setPhaseAt(size_t x, size_t y, float phase) { | 
| Chris@38 | 276 	    // phase in range -pi -> pi | 
| Chris@39 | 277 	    m_phase[y][x] = uint16_t(int16_t((phase * 32767) / M_PI)); | 
| Chris@38 | 278 	} | 
| Chris@38 | 279 | 
| Chris@38 | 280 	QColor getColour(unsigned char index) const { | 
| Chris@38 | 281 	    return m_colours[index]; | 
| Chris@38 | 282 	} | 
| Chris@38 | 283 | 
| Chris@38 | 284 	void setColour(unsigned char index, QColor colour) { | 
| Chris@38 | 285 	    m_colours[index] = colour; | 
| Chris@38 | 286 	} | 
| Chris@38 | 287 | 
| Chris@38 | 288     private: | 
| Chris@31 | 289 	size_t m_width; | 
| Chris@31 | 290 	size_t m_height; | 
| Chris@38 | 291 	uint16_t **m_magnitude; | 
| Chris@38 | 292 	uint16_t **m_phase; | 
| Chris@38 | 293 	float *m_factor; | 
| Chris@31 | 294 	QColor m_colours[256]; | 
| Chris@38 | 295 | 
| Chris@38 | 296 	void resize(uint16_t **&, size_t, size_t); | 
| Chris@31 | 297     }; | 
| Chris@38 | 298 | 
| Chris@38 | 299     enum { NO_VALUE = 0 }; // colour index for unused pixels | 
| Chris@38 | 300 | 
| Chris@31 | 301     Cache *m_cache; | 
| Chris@31 | 302     bool m_cacheInvalid; | 
| Chris@31 | 303 | 
| Chris@0 | 304     class CacheFillThread : public QThread | 
| Chris@0 | 305     { | 
| Chris@0 | 306     public: | 
| Chris@0 | 307 	CacheFillThread(SpectrogramLayer &layer) : | 
| Chris@0 | 308 	    m_layer(layer), m_fillExtent(0) { } | 
| Chris@0 | 309 | 
| Chris@0 | 310 	size_t getFillExtent() const { return m_fillExtent; } | 
| Chris@0 | 311 	size_t getFillCompletion() const { return m_fillCompletion; } | 
| Chris@0 | 312 	virtual void run(); | 
| Chris@0 | 313 | 
| Chris@0 | 314     protected: | 
| Chris@0 | 315 	SpectrogramLayer &m_layer; | 
| Chris@0 | 316 	size_t m_fillExtent; | 
| Chris@0 | 317 	size_t m_fillCompletion; | 
| Chris@0 | 318     }; | 
| Chris@0 | 319 | 
| Chris@0 | 320     void fillCache(); | 
| Chris@0 | 321 | 
| Chris@0 | 322     mutable QPixmap *m_pixmapCache; | 
| Chris@0 | 323     mutable bool m_pixmapCacheInvalid; | 
| Chris@0 | 324     mutable long m_pixmapCacheStartFrame; | 
| Chris@0 | 325     mutable size_t m_pixmapCacheZoomLevel; | 
| Chris@0 | 326 | 
| Chris@0 | 327     QWaitCondition m_condition; | 
| Chris@0 | 328     mutable QMutex m_mutex; | 
| Chris@0 | 329 | 
| Chris@0 | 330     CacheFillThread *m_fillThread; | 
| Chris@0 | 331     QTimer *m_updateTimer; | 
| Chris@0 | 332     size_t m_lastFillExtent; | 
| Chris@0 | 333     bool m_exiting; | 
| Chris@0 | 334 | 
| Chris@0 | 335     void setCacheColourmap(); | 
| Chris@9 | 336     void rotateCacheColourmap(int distance); | 
| Chris@0 | 337 | 
| Chris@38 | 338     void fillCacheColumn(int column, | 
| Chris@0 | 339 			 double *inputBuffer, | 
| Chris@0 | 340 			 fftw_complex *outputBuffer, | 
| Chris@0 | 341 			 fftw_plan plan, | 
| Chris@9 | 342 			 size_t windowSize, | 
| Chris@9 | 343 			 size_t windowIncrement, | 
| Chris@38 | 344 			 const Window<double> &windower) | 
| Chris@0 | 345 	const; | 
| Chris@0 | 346 | 
| Chris@38 | 347     static float calculateFrequency(size_t bin, | 
| Chris@38 | 348 				    size_t windowSize, | 
| Chris@38 | 349 				    size_t windowIncrement, | 
| Chris@38 | 350 				    size_t sampleRate, | 
| Chris@38 | 351 				    float previousPhase, | 
| Chris@38 | 352 				    float currentPhase, | 
| Chris@38 | 353 				    bool &steadyState); | 
| Chris@38 | 354 | 
| Chris@38 | 355     unsigned char getDisplayValue(float input) const; | 
| Chris@38 | 356 | 
| Chris@0 | 357     bool getYBinRange(int y, float &freqBinMin, float &freqBinMax) const; | 
| Chris@0 | 358 | 
| Chris@0 | 359     struct LayerRange { | 
| Chris@0 | 360 	long   startFrame; | 
| Chris@0 | 361 	int    zoomLevel; | 
| Chris@0 | 362 	size_t modelStart; | 
| Chris@0 | 363 	size_t modelEnd; | 
| Chris@0 | 364     }; | 
| Chris@20 | 365     bool getXBinRange(int x, float &windowMin, float &windowMax) const; | 
| Chris@0 | 366 | 
| Chris@0 | 367     bool getYBinSourceRange(int y, float &freqMin, float &freqMax) const; | 
| Chris@35 | 368     bool getAdjustedYBinSourceRange(int x, int y, | 
| Chris@35 | 369 				    float &freqMin, float &freqMax, | 
| Chris@35 | 370 				    float &adjFreqMin, float &adjFreqMax) const; | 
| Chris@0 | 371     bool getXBinSourceRange(int x, RealTime &timeMin, RealTime &timeMax) const; | 
| Chris@38 | 372     bool getXYBinSourceRange(int x, int y, float &min, float &max, | 
| Chris@38 | 373 			     float &phaseMin, float &phaseMax) const; | 
| Chris@0 | 374 | 
| Chris@0 | 375     size_t getWindowIncrement() const { | 
| Chris@0 | 376 	return m_windowSize - m_windowSize * m_windowOverlap / 100; | 
| Chris@0 | 377     } | 
| Chris@0 | 378 }; | 
| Chris@0 | 379 | 
| Chris@0 | 380 #endif |