annotate layer/SpectrogramLayer.h @ 38:beb801473743

* Rearrange spectrogram cacheing so that gain, normalization, instantaneous frequency calculations etc can be done from the cached data (increasing the size of the cache, but also the usability).
author Chris Cannam
date Thu, 23 Feb 2006 18:01:31 +0000
parents 21d061e66177
children 5ce844ec854a
rev   line source
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@38 247 return (float(m_phase[y][x]) / 32767.0) * M_PI;
Chris@38 248 }
Chris@31 249
Chris@38 250 bool isLocalPeak(size_t x, size_t y) const {
Chris@38 251 if (y > 0 && m_magnitude[y][x] < m_magnitude[y-1][x]) return false;
Chris@38 252 if (y < m_height-1 && m_magnitude[y][x] < m_magnitude[y+1][x]) return false;
Chris@38 253 return true;
Chris@38 254 }
Chris@31 255
Chris@38 256 bool isOverThreshold(size_t x, size_t y, float threshold) const {
Chris@38 257 if (threshold == 0.0) return true;
Chris@38 258 return getMagnitudeAt(x, y) > threshold;
Chris@38 259 }
Chris@38 260
Chris@38 261 void setNormalizationFactor(size_t x, float factor) {
Chris@38 262 m_factor[x] = factor;
Chris@38 263 }
Chris@38 264
Chris@38 265 void setMagnitudeAt(size_t x, size_t y, float mag) {
Chris@38 266 // norm factor must already be set
Chris@38 267 setNormalizedMagnitudeAt(x, y, mag / m_factor[x]);
Chris@38 268 }
Chris@38 269
Chris@38 270 void setNormalizedMagnitudeAt(size_t x, size_t y, float norm) {
Chris@38 271 m_magnitude[y][x] = uint16_t(norm * 65535.0);
Chris@38 272 }
Chris@38 273
Chris@38 274 void setPhaseAt(size_t x, size_t y, float phase) {
Chris@38 275 // phase in range -pi -> pi
Chris@38 276 m_phase[y][x] = uint16_t((phase * 32767) / M_PI);
Chris@38 277 }
Chris@38 278
Chris@38 279 QColor getColour(unsigned char index) const {
Chris@38 280 return m_colours[index];
Chris@38 281 }
Chris@38 282
Chris@38 283 void setColour(unsigned char index, QColor colour) {
Chris@38 284 m_colours[index] = colour;
Chris@38 285 }
Chris@38 286
Chris@38 287 private:
Chris@31 288 size_t m_width;
Chris@31 289 size_t m_height;
Chris@38 290 uint16_t **m_magnitude;
Chris@38 291 uint16_t **m_phase;
Chris@38 292 float *m_factor;
Chris@31 293 QColor m_colours[256];
Chris@38 294
Chris@38 295 void resize(uint16_t **&, size_t, size_t);
Chris@31 296 };
Chris@38 297
Chris@38 298 enum { NO_VALUE = 0 }; // colour index for unused pixels
Chris@38 299
Chris@31 300 Cache *m_cache;
Chris@31 301 bool m_cacheInvalid;
Chris@31 302
Chris@0 303 class CacheFillThread : public QThread
Chris@0 304 {
Chris@0 305 public:
Chris@0 306 CacheFillThread(SpectrogramLayer &layer) :
Chris@0 307 m_layer(layer), m_fillExtent(0) { }
Chris@0 308
Chris@0 309 size_t getFillExtent() const { return m_fillExtent; }
Chris@0 310 size_t getFillCompletion() const { return m_fillCompletion; }
Chris@0 311 virtual void run();
Chris@0 312
Chris@0 313 protected:
Chris@0 314 SpectrogramLayer &m_layer;
Chris@0 315 size_t m_fillExtent;
Chris@0 316 size_t m_fillCompletion;
Chris@0 317 };
Chris@0 318
Chris@0 319 void fillCache();
Chris@0 320
Chris@0 321 mutable QPixmap *m_pixmapCache;
Chris@0 322 mutable bool m_pixmapCacheInvalid;
Chris@0 323 mutable long m_pixmapCacheStartFrame;
Chris@0 324 mutable size_t m_pixmapCacheZoomLevel;
Chris@0 325
Chris@0 326 QWaitCondition m_condition;
Chris@0 327 mutable QMutex m_mutex;
Chris@0 328
Chris@0 329 CacheFillThread *m_fillThread;
Chris@0 330 QTimer *m_updateTimer;
Chris@0 331 size_t m_lastFillExtent;
Chris@0 332 bool m_exiting;
Chris@0 333
Chris@0 334 void setCacheColourmap();
Chris@9 335 void rotateCacheColourmap(int distance);
Chris@0 336
Chris@38 337 void fillCacheColumn(int column,
Chris@0 338 double *inputBuffer,
Chris@0 339 fftw_complex *outputBuffer,
Chris@0 340 fftw_plan plan,
Chris@9 341 size_t windowSize,
Chris@9 342 size_t windowIncrement,
Chris@38 343 const Window<double> &windower)
Chris@0 344 const;
Chris@0 345
Chris@38 346 static float calculateFrequency(size_t bin,
Chris@38 347 size_t windowSize,
Chris@38 348 size_t windowIncrement,
Chris@38 349 size_t sampleRate,
Chris@38 350 float previousPhase,
Chris@38 351 float currentPhase,
Chris@38 352 bool &steadyState);
Chris@38 353
Chris@38 354 unsigned char getDisplayValue(float input) const;
Chris@38 355
Chris@0 356 bool getYBinRange(int y, float &freqBinMin, float &freqBinMax) const;
Chris@0 357
Chris@0 358 struct LayerRange {
Chris@0 359 long startFrame;
Chris@0 360 int zoomLevel;
Chris@0 361 size_t modelStart;
Chris@0 362 size_t modelEnd;
Chris@0 363 };
Chris@20 364 bool getXBinRange(int x, float &windowMin, float &windowMax) const;
Chris@0 365
Chris@0 366 bool getYBinSourceRange(int y, float &freqMin, float &freqMax) const;
Chris@35 367 bool getAdjustedYBinSourceRange(int x, int y,
Chris@35 368 float &freqMin, float &freqMax,
Chris@35 369 float &adjFreqMin, float &adjFreqMax) const;
Chris@0 370 bool getXBinSourceRange(int x, RealTime &timeMin, RealTime &timeMax) const;
Chris@38 371 bool getXYBinSourceRange(int x, int y, float &min, float &max,
Chris@38 372 float &phaseMin, float &phaseMax) const;
Chris@0 373
Chris@0 374 size_t getWindowIncrement() const {
Chris@0 375 return m_windowSize - m_windowSize * m_windowOverlap / 100;
Chris@0 376 }
Chris@0 377 };
Chris@0 378
Chris@0 379 #endif