# HG changeset patch # User Chris Cannam # Date 1136910796 0 # Node ID 2a4f26e85b4cbb602ea9c7362f687dfdbd7b1f50 initial import diff -r 000000000000 -r 2a4f26e85b4c layer/Colour3DPlotLayer.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/layer/Colour3DPlotLayer.cpp Tue Jan 10 16:33:16 2006 +0000 @@ -0,0 +1,268 @@ +/* -*- c-basic-offset: 4 -*- vi:set ts=8 sts=4 sw=4: */ + +/* + A waveform viewer and audio annotation editor. + Chris Cannam, Queen Mary University of London, 2005 + + This is experimental software. Not for distribution. +*/ + +#include "Colour3DPlotLayer.h" + +#include "base/View.h" +#include "base/Profiler.h" + +#include +#include +#include + +#include + +#include + + +Colour3DPlotLayer::Colour3DPlotLayer(View *w) : + Layer(w), + m_model(0), + m_cache(0) +{ + m_view->addLayer(this); +} + +Colour3DPlotLayer::~Colour3DPlotLayer() +{ +} + +void +Colour3DPlotLayer::setModel(const DenseThreeDimensionalModel *model) +{ + m_model = model; + if (!m_model || !m_model->isOK()) return; + + connect(m_model, SIGNAL(modelChanged()), this, SIGNAL(modelChanged())); + connect(m_model, SIGNAL(modelChanged(size_t, size_t)), + this, SIGNAL(modelChanged(size_t, size_t))); + + connect(m_model, SIGNAL(completionChanged()), + this, SIGNAL(modelCompletionChanged())); + + connect(m_model, SIGNAL(modelChanged()), this, SLOT(cacheInvalid())); + connect(m_model, SIGNAL(modelChanged(size_t, size_t)), + this, SLOT(cacheInvalid(size_t, size_t))); + + emit modelReplaced(); +} + +void +Colour3DPlotLayer::cacheInvalid() +{ + delete m_cache; + m_cache = 0; +} + +void +Colour3DPlotLayer::cacheInvalid(size_t, size_t) +{ + cacheInvalid(); +} + +void +Colour3DPlotLayer::paint(QPainter &paint, QRect rect) const +{ +// Profiler profiler("Colour3DPlotLayer::paint"); +// std::cerr << "Colour3DPlotLayer::paint(): m_model is " << m_model << ", zoom level is " << m_view->getZoomLevel() << std::endl; + + //!!! This doesn't yet accommodate the fact that the model may + //have a different sample rate from an underlying model. At the + //moment our paint mechanism assumes all models have the same + //sample rate. If that isn't the case, they won't align and the + //time ruler will match whichever model was used to construct it. + //Obviously it is not going to be the case in general that models + //will have the same samplerate, so we need a pane samplerate as + //well which we trivially realign to. (We can probably require + //the waveform and spectrogram layers to display at the pane + //samplerate.) + + int completion = 0; + if (!m_model || !m_model->isOK() || !m_model->isReady(&completion)) { + if (completion > 0) { + paint.fillRect(0, 10, m_view->width() * completion / 100, + 10, QColor(120, 120, 120)); + } + return; + } + + long startFrame = m_view->getStartFrame(); + int zoomLevel = m_view->getZoomLevel(); + + size_t modelStart = m_model->getStartFrame(); + size_t modelEnd = m_model->getEndFrame(); + size_t modelWindow = m_model->getWindowSize(); + + if (!m_cache) { + + m_cache = new QImage((modelEnd - modelStart) / modelWindow + 1, + m_model->getYBinCount(), + QImage::Format_Indexed8); + + m_cache->setNumColors(256); + DenseThreeDimensionalModel::BinValueSet values; +/* + for (int pixel = 0; pixel < 256; ++pixel) { + int hue = 256 - pixel; +// int hue = 220 - pixel; +// if (hue < 0) hue += 360; + QColor color = QColor::fromHsv(hue, pixel/2 + 128, pixel); + m_cache->setColor(pixel, qRgb(color.red(), color.green(), color.blue())); + } +*/ + + float min = m_model->getMinimumLevel(); + float max = m_model->getMaximumLevel(); + + if (max == min) max = min + 1.0; + +// int min = lrintf(m_model->getMinimumLevel()); +// int max = lrintf(m_model->getMaximumLevel()); + for (int value = 0; value < 256; ++value) { +// int spread = ((value - min) * 256) / (max - min); +// int hue = 256 - spread; +// QColor color = QColor::fromHsv(hue, spread/2 + 128, spread); + int hue = 256 - value; + QColor color = QColor::fromHsv(hue, value/2 + 128, value); + m_cache->setColor(value, qRgba(color.red(), color.green(), color.blue(), 80)); +// std::cerr << "Colour3DPlotLayer: Index " << value << ": hue " << hue << std::endl; + } + + m_cache->fill(min); + + for (size_t f = modelStart; f <= modelEnd; f += modelWindow) { + + values.clear(); + m_model->getBinValues(f, values); + + for (size_t y = 0; y < m_model->getYBinCount(); ++y) { + + float value = min; + if (y < values.size()) value = values[y]; + + //!!! divide-by-zero! + int pixel = int(((value - min) * 256) / (max - min)); + + m_cache->setPixel(f / modelWindow, y, pixel); + } + } + } + + int x0 = rect.left(); + int x1 = rect.right() + 1; + +// int y0 = rect.top(); +// int y1 = rect.bottom(); + int w = x1 - x0; + int h = m_view->height(); + + // The cache is from the model's start frame to the model's end + // frame at the model's window increment frames per pixel. We + // want to draw from our start frame + x0 * zoomLevel to our start + // frame + x1 * zoomLevel at zoomLevel frames per pixel. + + //!!! Strictly speaking we want quite different paint mechanisms + //for models that have more than one bin per pixel in either + //direction. This one is only really appropriate for models with + //far fewer bins in both directions. + + int sx0 = ((startFrame + x0 * zoomLevel) - int(modelStart)) / + int(modelWindow); + int sx1 = ((startFrame + x1 * zoomLevel) - int(modelStart)) / + int(modelWindow); + int sw = sx1 - sx0; + int sh = m_model->getYBinCount(); + +/* + std::cerr << "Colour3DPlotLayer::paint: w " << w << ", h " << h << ", sx0 " << sx0 << ", sx1 " << sx1 << ", sw " << sw << ", sh " << sh << std::endl; + std::cerr << "Colour3DPlotLayer: sample rate is " << m_model->getSampleRate() << ", window size " << m_model->getWindowSize() << std::endl; +*/ + + for (int sx = sx0 - 1; sx <= sx1; ++sx) { + + int fx = sx * int(modelWindow); + + if (fx + modelWindow < int(modelStart) || + fx > int(modelEnd)) continue; + + for (int sy = 0; sy < sh; ++sy) { + + int rx0 = ((fx + int(modelStart)) + - int(startFrame)) / zoomLevel; + int rx1 = ((fx + int(modelWindow) + int(modelStart)) + - int(startFrame)) / zoomLevel; + + int ry0 = h - (sy * h) / sh - 1; + int ry1 = h - ((sy + 1) * h) / sh - 2; + QRgb pixel = qRgb(255, 255, 255); + if (sx >= 0 && sx < m_cache->width() && + sy >= 0 && sy < m_cache->height()) { + pixel = m_cache->pixel(sx, sy); + } + + QColor pen(255, 255, 255, 80); +// QColor pen(pixel); + QColor brush(pixel); + brush.setAlpha(160); +// paint.setPen(pen); + paint.setPen(Qt::NoPen); + paint.setBrush(brush); + + int w = rx1 - rx0; + if (w < 1) w = 1; + paint.drawRect(rx0, ry0 - h / sh - 1, w, h / sh + 1); + + if (sx >= 0 && sx < m_cache->width() && + sy >= 0 && sy < m_cache->height()) { + int dv = m_cache->pixelIndex(sx, sy); + if (dv != 0 && paint.fontMetrics().height() < (h / sh)) { + QString text = QString("%1").arg(dv); + if (paint.fontMetrics().width(text) < w - 3) { + paint.setPen(Qt::white); + paint.drawText(rx0 + 2, + ry0 - h / sh - 1 + 2 + paint.fontMetrics().ascent(), + QString("%1").arg(dv)); + } + } + } + } + } + +/* + QRect targetRect(x0, 0, w, h); + QRect sourceRect(sx0, 0, sw, sh); + + QImage scaled(w, h, QImage::Format_RGB32); + + for (int x = 0; x < w; ++x) { + for (int y = 0; y < h; ++y) { + + + + int sx = sx0 + (x * sw) / w; + int sy = sh - (y * sh) / h - 1; +// std::cerr << "Colour3DPlotLayer::paint: sx " << sx << ", sy " << sy << ", cache w " << m_cache->width() << ", cache h " << m_cache->height() << std::endl; + if (sx >= 0 && sy >= 0 && + sx < m_cache->width() && sy < m_cache->height()) { + scaled.setPixel(x, y, m_cache->pixel(sx, sy)); + } else { + scaled.setPixel(x, y, qRgba(255, 255, 255, 80)); + } + } + } + + paint.drawImage(x0, 0, scaled); +*/ +} + +#ifdef INCLUDE_MOCFILES +#include "Colour3DPlotLayer.moc.cpp" +#endif + + diff -r 000000000000 -r 2a4f26e85b4c layer/Colour3DPlotLayer.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/layer/Colour3DPlotLayer.h Tue Jan 10 16:33:16 2006 +0000 @@ -0,0 +1,71 @@ +/* -*- c-basic-offset: 4 -*- vi:set ts=8 sts=4 sw=4: */ + +/* + A waveform viewer and audio annotation editor. + Chris Cannam, Queen Mary University of London, 2005 + + This is experimental software. Not for distribution. +*/ + +#ifndef _COLOUR_3D_PLOT_H_ +#define _COLOUR_3D_PLOT_H_ + +#include "base/Layer.h" + +#include "model/DenseThreeDimensionalModel.h" + +class View; +class QPainter; +class QImage; + +/** + * This is a view that displays dense 3-D data (time, some sort of + * binned y-axis range, value) as a colour plot with value mapped to + * colour range. Its source is a DenseThreeDimensionalModel. + * + * This was the original implementation for the spectrogram view, but + * it was replaced with a more efficient implementation that derived + * the spectrogram itself from a DenseTimeValueModel instead of using + * a three-dimensional model. This class is retained in case it + * becomes useful, but it will probably need some cleaning up if it's + * ever actually used. + */ + +class Colour3DPlotLayer : public Layer +{ + Q_OBJECT + +public: + Colour3DPlotLayer(View *w); + ~Colour3DPlotLayer(); + + virtual const ZoomConstraint *getZoomConstraint() const { return m_model; } + virtual const Model *getModel() const { return m_model; } + virtual void paint(QPainter &paint, QRect rect) const; + + void setModel(const DenseThreeDimensionalModel *model); + + +/* + virtual PropertyList getProperties() const; + virtual PropertyType getPropertyType(const PropertyName &) const; + virtual int getPropertyRangeAndValue(const PropertyName &, + int *min, int *max) const; + virtual QString getPropertyValueLabel(const PropertyName &, + int value) const; + virtual void setProperty(const PropertyName &, int value); +*/ + + virtual QString getPropertyContainerIconName() const { return "colour3d"; } + +protected slots: + void cacheInvalid(); + void cacheInvalid(size_t startFrame, size_t endFrame); + +protected: + const DenseThreeDimensionalModel *m_model; // I do not own this + + mutable QImage *m_cache; +}; + +#endif diff -r 000000000000 -r 2a4f26e85b4c layer/LayerFactory.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/layer/LayerFactory.cpp Tue Jan 10 16:33:16 2006 +0000 @@ -0,0 +1,180 @@ +/* -*- c-basic-offset: 4 -*- vi:set ts=8 sts=4 sw=4: */ + +/* + A waveform viewer and audio annotation editor. + Chris Cannam, Queen Mary University of London, 2005 + + This is experimental software. Not for distribution. +*/ + +#include "LayerFactory.h" + +#include "WaveformLayer.h" +#include "SpectrogramLayer.h" +#include "TimeRulerLayer.h" +#include "TimeInstantLayer.h" +#include "TimeValueLayer.h" +#include "Colour3DPlotLayer.h" + +#include "model/RangeSummarisableTimeValueModel.h" +#include "model/DenseTimeValueModel.h" +#include "model/SparseOneDimensionalModel.h" +#include "model/SparseTimeValueModel.h" +#include "model/DenseThreeDimensionalModel.h" + +LayerFactory * +LayerFactory::m_instance = new LayerFactory; + +LayerFactory * +LayerFactory::instance() +{ + return m_instance; +} + +LayerFactory::~LayerFactory() +{ +} + +QString +LayerFactory::getLayerPresentationName(LayerType type) +{ + switch (type) { + case Waveform: return Layer::tr("Waveform"); + case Spectrogram: return Layer::tr("Spectrogram"); + case TimeRuler: return Layer::tr("Ruler"); + case TimeInstants: return Layer::tr("Time Instants"); + case TimeValues: return Layer::tr("Time Values"); + case Colour3DPlot: return Layer::tr("Colour 3D Plot"); + + case MelodicRangeSpectrogram: + // The user can change all the parameters of this after the + // fact -- there's nothing permanently melodic-range about it + // that should be encoded in its name + return Layer::tr("Spectrogram"); + } + + return Layer::tr("Layer"); +} + +LayerFactory::LayerTypeSet +LayerFactory::getValidLayerTypes(Model *model) +{ + LayerTypeSet types; + + if (dynamic_cast(model)) { + types.insert(Colour3DPlot); + } + + if (dynamic_cast(model)) { + types.insert(Spectrogram); + types.insert(MelodicRangeSpectrogram); + } + + if (dynamic_cast(model)) { + types.insert(Waveform); + } + + if (dynamic_cast(model)) { + types.insert(TimeInstants); + } + + if (dynamic_cast(model)) { + types.insert(TimeValues); + } + + // We don't count TimeRuler here as it doesn't actually display + // the data, although it can be backed by any model + + return types; +} + +LayerFactory::LayerType +LayerFactory::getLayerType(Layer *layer) +{ + if (dynamic_cast(layer)) return Waveform; + if (dynamic_cast(layer)) return Spectrogram; + if (dynamic_cast(layer)) return TimeRuler; + if (dynamic_cast(layer)) return TimeInstants; + if (dynamic_cast(layer)) return TimeValues; + if (dynamic_cast(layer)) return Colour3DPlot; + return UnknownLayer; +} + +void +LayerFactory::setModel(Layer *layer, Model *model) +{ + if (trySetModel(layer, model)) + return; + + if (trySetModel(layer, model)) + return; + + if (trySetModel(layer, model)) + return; + + if (trySetModel(layer, model)) + return; + + if (trySetModel(layer, model)) + return; + + if (trySetModel(layer, model)) + return; + + if (trySetModel(layer, model)) + return; +} + +Layer * +LayerFactory::createLayer(LayerType type, View *view, + Model *model, int channel) +{ + Layer *layer = 0; + + switch (type) { + + case Waveform: + layer = new WaveformLayer(view); + static_cast(layer)->setChannel(channel); + break; + + case Spectrogram: + layer = new SpectrogramLayer(view); + static_cast(layer)->setChannel(channel); + break; + + case TimeRuler: + layer = new TimeRulerLayer(view); + break; + + case TimeInstants: + layer = new TimeInstantLayer(view); + break; + + case TimeValues: + layer = new TimeValueLayer(view); + break; + + case Colour3DPlot: + layer = new Colour3DPlotLayer(view); + break; + + case MelodicRangeSpectrogram: + layer = new SpectrogramLayer(view, SpectrogramLayer::MelodicRange); + static_cast(layer)->setChannel(channel); + break; + } + + if (!layer) { + std::cerr << "LayerFactory::createLayer: Unknown layer type " + << type << std::endl; + } else { + setModel(layer, model); + std::cerr << "LayerFactory::createLayer: Setting object name " + << getLayerPresentationName(type).toStdString() << " on " << layer << std::endl; + layer->setObjectName(getLayerPresentationName(type)); + } + + return layer; +} + diff -r 000000000000 -r 2a4f26e85b4c layer/LayerFactory.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/layer/LayerFactory.h Tue Jan 10 16:33:16 2006 +0000 @@ -0,0 +1,70 @@ +/* -*- c-basic-offset: 4 -*- vi:set ts=8 sts=4 sw=4: */ + +/* + A waveform viewer and audio annotation editor. + Chris Cannam, Queen Mary University of London, 2005 + + This is experimental software. Not for distribution. +*/ + +#ifndef _LAYER_FACTORY_H_ +#define _LAYER_FACTORY_H_ + +#include +#include + +class Layer; +class View; +class Model; + +class LayerFactory +{ +public: + enum LayerType { + + // Standard layers + Waveform, + Spectrogram, + TimeRuler, + TimeInstants, + TimeValues, + Colour3DPlot, + + // Layers with different initial parameters + MelodicRangeSpectrogram, + + // Not-a-layer-type + UnknownLayer = 255 + }; + + static LayerFactory *instance(); + + virtual ~LayerFactory(); + + typedef std::set LayerTypeSet; + LayerTypeSet getValidLayerTypes(Model *model); + + LayerType getLayerType(Layer *); + + Layer *createLayer(LayerType type, View *view, + Model *model = 0, int channel = -1); + + QString getLayerPresentationName(LayerType type); + + void setModel(Layer *layer, Model *model); + +protected: + template + bool trySetModel(Layer *layerBase, Model *modelBase) { + LayerClass *layer = dynamic_cast(layerBase); + if (!layer) return false; + ModelClass *model = dynamic_cast(modelBase); + layer->setModel(model); + return true; + } + + static LayerFactory *m_instance; +}; + +#endif + diff -r 000000000000 -r 2a4f26e85b4c layer/SpectrogramLayer.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/layer/SpectrogramLayer.cpp Tue Jan 10 16:33:16 2006 +0000 @@ -0,0 +1,1670 @@ +/* -*- c-basic-offset: 4 -*- vi:set ts=8 sts=4 sw=4: */ + +/* + A waveform viewer and audio annotation editor. + Chris Cannam, Queen Mary University of London, 2005 + + This is experimental software. Not for distribution. +*/ + +#include "SpectrogramLayer.h" + +#include "base/View.h" +#include "base/Profiler.h" +#include "base/AudioLevel.h" +#include "base/Window.h" + +#include +#include +#include +#include +#include + +#include + +#include +#include + +//#define DEBUG_SPECTROGRAM_REPAINT 1 + + +SpectrogramLayer::SpectrogramLayer(View *w, Configuration config) : + Layer(w), + m_model(0), + m_channel(0), + m_windowSize(1024), + m_windowType(HanningWindow), + m_windowOverlap(50), + m_gain(1.0), + m_maxFrequency(8000), + m_colourScale(dBColourScale), + m_colourScheme(DefaultColours), + m_frequencyScale(LinearFrequencyScale), + m_cache(0), + m_cacheInvalid(true), + m_maxCachedFrequency(0), + m_pixmapCache(0), + m_pixmapCacheInvalid(true), + m_fillThread(0), + m_updateTimer(0), + m_lastFillExtent(0), + m_exiting(false) +{ + if (config == MelodicRange) { + setWindowSize(8192); + setWindowOverlap(90); + setWindowType(ParzenWindow); + setMaxFrequency(1000); + setColourScale(LinearColourScale); + } + + if (m_view) m_view->setLightBackground(false); + m_view->addLayer(this); +} + +SpectrogramLayer::~SpectrogramLayer() +{ + delete m_updateTimer; + m_updateTimer = 0; + + m_exiting = true; + m_condition.wakeAll(); + if (m_fillThread) m_fillThread->wait(); + delete m_fillThread; + + delete m_cache; +} + +void +SpectrogramLayer::setModel(const DenseTimeValueModel *model) +{ + m_mutex.lock(); + m_model = model; + m_mutex.unlock(); + + if (!m_model || !m_model->isOK()) return; + + connect(m_model, SIGNAL(modelChanged()), this, SIGNAL(modelChanged())); + connect(m_model, SIGNAL(modelChanged(size_t, size_t)), + this, SIGNAL(modelChanged(size_t, size_t))); + + connect(m_model, SIGNAL(completionChanged()), + this, SIGNAL(modelCompletionChanged())); + + connect(m_model, SIGNAL(modelChanged()), this, SLOT(cacheInvalid())); + connect(m_model, SIGNAL(modelChanged(size_t, size_t)), + this, SLOT(cacheInvalid(size_t, size_t))); + + emit modelReplaced(); + fillCache(); +} + +Layer::PropertyList +SpectrogramLayer::getProperties() const +{ + PropertyList list; + list.push_back(tr("Colour")); + list.push_back(tr("Colour Scale")); + list.push_back(tr("Window Type")); + list.push_back(tr("Window Size")); + list.push_back(tr("Window Overlap")); + list.push_back(tr("Gain")); + list.push_back(tr("Max Frequency")); + list.push_back(tr("Frequency Scale")); + return list; +} + +Layer::PropertyType +SpectrogramLayer::getPropertyType(const PropertyName &name) const +{ + if (name == tr("Gain")) return RangeProperty; + return ValueProperty; +} + +QString +SpectrogramLayer::getPropertyGroupName(const PropertyName &name) const +{ + if (name == tr("Window Size") || + name == tr("Window Overlap")) return tr("Window"); + if (name == tr("Gain") || + name == tr("Colour Scale")) return tr("Scale"); + if (name == tr("Max Frequency") || + name == tr("Frequency Scale")) return tr("Frequency"); + return QString(); +} + +int +SpectrogramLayer::getPropertyRangeAndValue(const PropertyName &name, + int *min, int *max) const +{ + int deft = 0; + + if (name == tr("Gain")) { + + *min = -50; + *max = 50; + + deft = lrint(log10(m_gain) * 20.0); + if (deft < *min) deft = *min; + if (deft > *max) deft = *max; + + } else if (name == tr("Colour Scale")) { + + *min = 0; + *max = 3; + + deft = (int)m_colourScale; + + } else if (name == tr("Colour")) { + + *min = 0; + *max = 5; + + deft = (int)m_colourScheme; + + } else if (name == tr("Window Type")) { + + *min = 0; + *max = 6; + + deft = (int)m_windowType; + + } else if (name == tr("Window Size")) { + + *min = 0; + *max = 10; + + deft = 0; + int ws = m_windowSize; + while (ws > 32) { ws >>= 1; deft ++; } + + } else if (name == tr("Window Overlap")) { + + *min = 0; + *max = 4; + + deft = m_windowOverlap / 25; + if (m_windowOverlap == 90) deft = 4; + + } else if (name == tr("Max Frequency")) { + + *min = 0; + *max = 9; + + switch (m_maxFrequency) { + case 500: deft = 0; break; + case 1000: deft = 1; break; + case 1500: deft = 2; break; + case 2000: deft = 3; break; + case 4000: deft = 4; break; + case 6000: deft = 5; break; + case 8000: deft = 6; break; + case 12000: deft = 7; break; + case 16000: deft = 8; break; + default: deft = 9; break; + } + + } else if (name == tr("Frequency Scale")) { + + *min = 0; + *max = 1; + deft = (int)m_frequencyScale; + + } else { + deft = Layer::getPropertyRangeAndValue(name, min, max); + } + + return deft; +} + +QString +SpectrogramLayer::getPropertyValueLabel(const PropertyName &name, + int value) const +{ + if (name == tr("Colour")) { + switch (value) { + default: + case 0: return tr("Default"); + case 1: return tr("White on Black"); + case 2: return tr("Black on White"); + case 3: return tr("Red on Blue"); + case 4: return tr("Yellow on Black"); + case 5: return tr("Red on Black"); + } + } + if (name == tr("Colour Scale")) { + switch (value) { + default: + case 0: return tr("Level Linear"); + case 1: return tr("Level Meter"); + case 2: return tr("Level dB"); + case 3: return tr("Phase"); + } + } + if (name == tr("Window Type")) { + switch ((WindowType)value) { + default: + case RectangularWindow: return tr("Rectangular"); + case BartlettWindow: return tr("Bartlett"); + case HammingWindow: return tr("Hamming"); + case HanningWindow: return tr("Hanning"); + case BlackmanWindow: return tr("Blackman"); + case GaussianWindow: return tr("Gaussian"); + case ParzenWindow: return tr("Parzen"); + } + } + if (name == tr("Window Size")) { + return QString("%1").arg(32 << value); + } + if (name == tr("Window Overlap")) { + switch (value) { + default: + case 0: return tr("None"); + case 1: return tr("25 %"); + case 2: return tr("50 %"); + case 3: return tr("75 %"); + case 4: return tr("90 %"); + } + } + if (name == tr("Max Frequency")) { + switch (value) { + default: + case 0: return tr("500 Hz"); + case 1: return tr("1 KHz"); + case 2: return tr("1.5 KHz"); + case 3: return tr("2 KHz"); + case 4: return tr("4 KHz"); + case 5: return tr("6 KHz"); + case 6: return tr("8 KHz"); + case 7: return tr("12 KHz"); + case 8: return tr("16 KHz"); + case 9: return tr("All"); + } + } + if (name == tr("Frequency Scale")) { + switch (value) { + default: + case 0: return tr("Linear"); + case 1: return tr("Log"); + } + } + return tr(""); +} + +void +SpectrogramLayer::setProperty(const PropertyName &name, int value) +{ + if (name == tr("Gain")) { + setGain(pow(10, float(value)/20.0)); + } else if (name == tr("Colour")) { + if (m_view) m_view->setLightBackground(value == 2); + switch (value) { + default: + case 0: setColourScheme(DefaultColours); break; + case 1: setColourScheme(WhiteOnBlack); break; + case 2: setColourScheme(BlackOnWhite); break; + case 3: setColourScheme(RedOnBlue); break; + case 4: setColourScheme(YellowOnBlack); break; + case 5: setColourScheme(RedOnBlack); break; + } + } else if (name == tr("Window Type")) { + setWindowType(WindowType(value)); + } else if (name == tr("Window Size")) { + setWindowSize(32 << value); + } else if (name == tr("Window Overlap")) { + if (value == 4) setWindowOverlap(90); + else setWindowOverlap(25 * value); + } else if (name == tr("Max Frequency")) { + switch (value) { + case 0: setMaxFrequency(500); break; + case 1: setMaxFrequency(1000); break; + case 2: setMaxFrequency(1500); break; + case 3: setMaxFrequency(2000); break; + case 4: setMaxFrequency(4000); break; + case 5: setMaxFrequency(6000); break; + case 6: setMaxFrequency(8000); break; + case 7: setMaxFrequency(12000); break; + case 8: setMaxFrequency(16000); break; + default: + case 9: setMaxFrequency(0); break; + } + } else if (name == tr("Colour Scale")) { + switch (value) { + default: + case 0: setColourScale(LinearColourScale); break; + case 1: setColourScale(MeterColourScale); break; + case 2: setColourScale(dBColourScale); break; + case 3: setColourScale(PhaseColourScale); break; + } + } else if (name == tr("Frequency Scale")) { + switch (value) { + default: + case 0: setFrequencyScale(LinearFrequencyScale); break; + case 1: setFrequencyScale(LogFrequencyScale); break; + } + } +} + +void +SpectrogramLayer::setChannel(int ch) +{ + if (m_channel == ch) return; + + m_mutex.lock(); + m_cacheInvalid = true; + m_pixmapCacheInvalid = true; + + m_channel = ch; + emit layerParametersChanged(); + + m_mutex.unlock(); + fillCache(); + +} + +int +SpectrogramLayer::getChannel() const +{ + return m_channel; +} + +void +SpectrogramLayer::setWindowSize(size_t ws) +{ + if (m_windowSize == ws) return; + + m_mutex.lock(); + m_cacheInvalid = true; + m_pixmapCacheInvalid = true; + + m_windowSize = ws; + emit layerParametersChanged(); + + m_mutex.unlock(); + fillCache(); + +} + +size_t +SpectrogramLayer::getWindowSize() const +{ + return m_windowSize; +} + +void +SpectrogramLayer::setWindowOverlap(size_t wi) +{ + if (m_windowOverlap == wi) return; + + m_mutex.lock(); + m_cacheInvalid = true; + m_pixmapCacheInvalid = true; + + m_windowOverlap = wi; + emit layerParametersChanged(); + + m_mutex.unlock(); + fillCache(); +} + +size_t +SpectrogramLayer::getWindowOverlap() const +{ + return m_windowOverlap; +} + +void +SpectrogramLayer::setWindowType(WindowType w) +{ + if (m_windowType == w) return; + + m_mutex.lock(); + m_cacheInvalid = true; + m_pixmapCacheInvalid = true; + + m_windowType = w; + emit layerParametersChanged(); + + m_mutex.unlock(); + fillCache(); +} + +WindowType +SpectrogramLayer::getWindowType() const +{ + return m_windowType; +} + +void +SpectrogramLayer::setGain(float gain) +{ + if (m_gain == gain) return; //!!! inadequate for floats! + + m_mutex.lock(); + m_cacheInvalid = true; + m_pixmapCacheInvalid = true; + + m_gain = gain; + emit layerParametersChanged(); + + m_mutex.unlock(); + fillCache(); +} + +float +SpectrogramLayer::getGain() const +{ + return m_gain; +} + +void +SpectrogramLayer::setMaxFrequency(size_t mf) +{ + if (m_maxFrequency == mf) return; + + m_mutex.lock(); + + // don't need to invalidate main cache here... + + m_pixmapCacheInvalid = true; + + m_maxFrequency = mf; + emit layerParametersChanged(); + + m_mutex.unlock(); + + // ... but we do still need to do this, in case m_maxFrequency + // now > m_maxCachedFrequency + fillCache(); +} + +size_t +SpectrogramLayer::getMaxFrequency() const +{ + return m_maxFrequency; +} + +void +SpectrogramLayer::setColourScale(ColourScale colourScale) +{ + if (m_colourScale == colourScale) return; + + m_mutex.lock(); + m_cacheInvalid = true; + m_pixmapCacheInvalid = true; + + m_colourScale = colourScale; + emit layerParametersChanged(); + + m_mutex.unlock(); + fillCache(); +} + +SpectrogramLayer::ColourScale +SpectrogramLayer::getColourScale() const +{ + return m_colourScale; +} + +void +SpectrogramLayer::setColourScheme(ColourScheme scheme) +{ + if (m_colourScheme == scheme) return; + + m_mutex.lock(); + // don't need to invalidate main cache here + m_pixmapCacheInvalid = true; + + m_colourScheme = scheme; + setCacheColourmap(); + emit layerParametersChanged(); + + m_mutex.unlock(); +} + +SpectrogramLayer::ColourScheme +SpectrogramLayer::getColourScheme() const +{ + return m_colourScheme; +} + +void +SpectrogramLayer::setFrequencyScale(FrequencyScale frequencyScale) +{ + if (m_frequencyScale == frequencyScale) return; + + m_mutex.lock(); + // don't need to invalidate main cache here + m_pixmapCacheInvalid = true; + + m_frequencyScale = frequencyScale; + emit layerParametersChanged(); + + m_mutex.unlock(); +} + +SpectrogramLayer::FrequencyScale +SpectrogramLayer::getFrequencyScale() const +{ + return m_frequencyScale; +} + +void +SpectrogramLayer::cacheInvalid() +{ + m_cacheInvalid = true; + m_pixmapCacheInvalid = true; + m_cachedInitialVisibleArea = false; + fillCache(); +} + +void +SpectrogramLayer::cacheInvalid(size_t, size_t) +{ + // for now (or forever?) + cacheInvalid(); +} + +void +SpectrogramLayer::fillCache() +{ +#ifdef DEBUG_SPECTROGRAM_REPAINT + std::cerr << "SpectrogramLayer::fillCache" << std::endl; +#endif + QMutexLocker locker(&m_mutex); + + m_lastFillExtent = 0; + + delete m_updateTimer; + m_updateTimer = new QTimer(this); + connect(m_updateTimer, SIGNAL(timeout()), this, SLOT(fillTimerTimedOut())); + m_updateTimer->start(200); + + if (!m_fillThread) { + std::cerr << "SpectrogramLayer::fillCache creating thread" << std::endl; + m_fillThread = new CacheFillThread(*this); + m_fillThread->start(); + } + + m_condition.wakeAll(); +} + +void +SpectrogramLayer::fillTimerTimedOut() +{ + if (m_fillThread && m_model) { + size_t fillExtent = m_fillThread->getFillExtent(); +#ifdef DEBUG_SPECTROGRAM_REPAINT + std::cerr << "SpectrogramLayer::fillTimerTimedOut: extent " << fillExtent << ", last " << m_lastFillExtent << ", total " << m_model->getEndFrame() << std::endl; +#endif + if (fillExtent >= m_lastFillExtent) { + if (fillExtent >= m_model->getEndFrame() && m_lastFillExtent > 0) { +#ifdef DEBUG_SPECTROGRAM_REPAINT + std::cerr << "complete!" << std::endl; +#endif + emit modelChanged(); + m_pixmapCacheInvalid = true; + delete m_updateTimer; + m_updateTimer = 0; + m_lastFillExtent = 0; + } else if (fillExtent > m_lastFillExtent) { +#ifdef DEBUG_SPECTROGRAM_REPAINT + std::cerr << "SpectrogramLayer: emitting modelChanged(" + << m_lastFillExtent << "," << fillExtent << ")" << std::endl; +#endif + emit modelChanged(m_lastFillExtent, fillExtent); + m_pixmapCacheInvalid = true; + m_lastFillExtent = fillExtent; + } + } else { + if (m_view) { + size_t sf = 0; + if (m_view->getStartFrame() > 0) sf = m_view->getStartFrame(); +#ifdef DEBUG_SPECTROGRAM_REPAINT + std::cerr << "SpectrogramLayer: going backwards, emitting modelChanged(" + << sf << "," << m_view->getEndFrame() << ")" << std::endl; +#endif + emit modelChanged(sf, m_view->getEndFrame()); + m_pixmapCacheInvalid = true; + } + m_lastFillExtent = fillExtent; + } + } +} + +void +SpectrogramLayer::setCacheColourmap() +{ + if (m_cacheInvalid || !m_cache) return; + + m_cache->setNumColors(256); + + m_cache->setColor(0, qRgb(255, 255, 255)); + + for (int pixel = 1; pixel < 256; ++pixel) { + + QColor colour; + int hue, px; + + switch (m_colourScheme) { + + default: + case DefaultColours: + hue = 256 - pixel; + colour = QColor::fromHsv(hue, pixel/2 + 128, pixel); + break; + + case WhiteOnBlack: + colour = QColor(pixel, pixel, pixel); + break; + + case BlackOnWhite: + colour = QColor(256-pixel, 256-pixel, 256-pixel); + break; + + case RedOnBlue: + colour = QColor(pixel > 128 ? (pixel - 128) * 2 : 0, 0, + pixel < 128 ? pixel : (256 - pixel)); + break; + + case YellowOnBlack: + px = 256 - pixel; + colour = QColor(px < 64 ? 255 - px/2 : + px < 128 ? 224 - (px - 64) : + px < 192 ? 160 - (px - 128) * 3 / 2 : + 256 - px, + pixel, + pixel / 4); + break; + + case RedOnBlack: + colour = QColor::fromHsv(10, pixel, pixel); + break; + } + + m_cache->setColor + (pixel, qRgb(colour.red(), colour.green(), colour.blue())); + } +} + +bool +SpectrogramLayer::fillCacheColumn(int column, double *input, + fftw_complex *output, + fftw_plan plan, + const Window &windower, + bool lock) const +{ + size_t increment = m_windowSize - m_windowSize * m_windowOverlap / 100; + int startFrame = increment * column; + int endFrame = startFrame + m_windowSize; + + startFrame -= int(m_windowSize - increment) / 2; + endFrame -= int(m_windowSize - increment) / 2; + size_t pfx = 0; + + if (startFrame < 0) { + pfx = size_t(-startFrame); + for (size_t i = 0; i < pfx; ++i) { + input[i] = 0.0; + } + } + + size_t got = m_model->getValues(m_channel, startFrame + pfx, + endFrame, input + pfx); + while (got + pfx < m_windowSize) { + input[got + pfx] = 0.0; + ++got; + } + + if (m_gain != 1.0) { + for (size_t i = 0; i < m_windowSize; ++i) { + input[i] *= m_gain; + } + } + + windower.cut(input); + + fftw_execute(plan); + + if (lock) m_mutex.lock(); + bool interrupted = false; + + for (size_t i = 0; i < m_windowSize / 2; ++i) { + + if (int(i) >= m_cache->height()) break; + + int value = 0; + + if (m_colourScale == PhaseColourScale) { + + double phase = atan2(-output[i][1], output[i][0]); + value = int((phase * 128 / M_PI) + 128); + + } else { + double mag = sqrt(output[i][0] * output[i][0] + + output[i][1] * output[i][1]); + mag /= m_windowSize / 2; + + switch (m_colourScale) { + + default: + case LinearColourScale: + value = int(mag * 50 * 256); + break; + + case MeterColourScale: + value = AudioLevel::multiplier_to_preview(mag * 50, 256); + break; + + case dBColourScale: + mag = 20.0 * log10(mag); + mag = (mag + 80.0) / 80.0; + if (mag < 0.0) mag = 0.0; + if (mag > 1.0) mag = 1.0; + value = int(mag * 256); + } + } + + if (value > 254) value = 254; + if (value < 0) value = 0; + + if (m_cacheInvalid || m_exiting) { + interrupted = true; + break; + } + + if (column < m_cache->width()) { + m_cache->setPixel(column, i, value + 1); // 0 is "unset" + } + } + + if (lock) m_mutex.unlock(); + return !interrupted; +} + +void +SpectrogramLayer::CacheFillThread::run() +{ +// std::cerr << "SpectrogramLayer::CacheFillThread::run" << std::endl; + + m_layer.m_mutex.lock(); + + while (!m_layer.m_exiting) { + + bool interrupted = false; + +// std::cerr << "SpectrogramLayer::CacheFillThread::run in loop" << std::endl; + + if (m_layer.m_model && + (m_layer.m_cacheInvalid || + m_layer.m_maxFrequency > m_layer.m_maxCachedFrequency)) { + +// std::cerr << "SpectrogramLayer::CacheFillThread::run: something to do" << std::endl; + + while (!m_layer.m_model->isReady()) { + m_layer.m_condition.wait(&m_layer.m_mutex, 100); + } + + size_t minFreq = 0; + if (!m_layer.m_cacheInvalid) { + minFreq = m_layer.m_maxCachedFrequency; + } + + m_layer.m_cachedInitialVisibleArea = false; + m_layer.m_cacheInvalid = false; + m_fillExtent = 0; + m_fillCompletion = 0; + + std::cerr << "SpectrogramLayer::CacheFillThread::run: model is ready" << std::endl; + + size_t start = m_layer.m_model->getStartFrame(); + size_t end = m_layer.m_model->getEndFrame(); + size_t windowSize = m_layer.m_windowSize; + size_t windowIncrement = m_layer.getWindowIncrement(); + + size_t visibleStart = start; + size_t visibleEnd = end; + + if (m_layer.m_view) { + if (m_layer.m_view->getStartFrame() < 0) { + visibleStart = 0; + } else { + visibleStart = m_layer.m_view->getStartFrame(); + visibleStart = (visibleStart / windowIncrement) * + windowIncrement; + } + visibleEnd = m_layer.m_view->getEndFrame(); + } + + delete m_layer.m_cache; + size_t bins = windowSize / 2; + if (m_layer.m_maxFrequency > 0) { + int sr = m_layer.m_model->getSampleRate(); + bins = int((double(m_layer.m_maxFrequency) * windowSize) / sr + 0.1); + if (bins > windowSize / 2) bins = windowSize / 2; + } + m_layer.m_cache = new QImage((end - start) / windowIncrement + 1, + bins, //!!! + QImage::Format_Indexed8); + + m_layer.setCacheColourmap(); + + m_layer.m_cache->fill(0); + m_layer.m_mutex.unlock(); + + double *input = (double *) + fftw_malloc(windowSize * sizeof(double)); + + fftw_complex *output = (fftw_complex *) + fftw_malloc(windowSize * sizeof(fftw_complex)); + + fftw_plan plan = fftw_plan_dft_r2c_1d(windowSize, input, + output, FFTW_MEASURE); + + Window windower(m_layer.m_windowType, m_layer.m_windowSize); + + if (!plan) { + std::cerr << "WARNING: fftw_plan(" << windowSize << ") failed!" << std::endl; + fftw_free(input); + fftw_free(output); + m_layer.m_mutex.lock(); + continue; + } + + int counter = 0; + int updateAt = (end / windowIncrement) / 20; + if (updateAt < 100) updateAt = 100; + + bool doVisibleFirst = (visibleStart != start && visibleEnd != end); + + if (doVisibleFirst) { + + m_layer.m_mutex.lock(); + + for (size_t f = visibleStart; f < visibleEnd; f += windowIncrement) { + + m_layer.fillCacheColumn(int((f - start) / windowIncrement), + input, output, plan, windower, false); + + m_layer.m_mutex.unlock(); + m_layer.m_mutex.lock(); + + if (m_layer.m_cacheInvalid || m_layer.m_exiting) { + interrupted = true; + m_fillExtent = 0; + break; + } + + if (++counter == updateAt) { + if (f < end) m_fillExtent = f; + m_fillCompletion = size_t(100 * fabsf(float(f - visibleStart) / + float(end - start))); + counter = 0; + } + } + + m_layer.m_mutex.unlock(); + } + + m_layer.m_cachedInitialVisibleArea = true; + + if (!interrupted && doVisibleFirst) { + + for (size_t f = visibleEnd; f < end; f += windowIncrement) { + + if (!m_layer.fillCacheColumn(int((f - start) / windowIncrement), + input, output, plan, windower, true)) { + interrupted = true; + m_fillExtent = 0; + break; + } + + + if (++counter == updateAt) { + if (f < end) m_fillExtent = f; + m_fillCompletion = size_t(100 * fabsf(float(f - visibleStart) / + float(end - start))); + counter = 0; + } + } + } + + if (!interrupted) { + + size_t remainingEnd = end; + if (doVisibleFirst) { + remainingEnd = visibleStart; + if (remainingEnd > start) --remainingEnd; + else remainingEnd = start; + } + size_t baseCompletion = m_fillCompletion; + + for (size_t f = start; f < remainingEnd; f += windowIncrement) { + + if (!m_layer.fillCacheColumn(int((f - start) / windowIncrement), + input, output, plan, windower, true)) { + interrupted = true; + m_fillExtent = 0; + break; + } + + if (++counter == updateAt) { + m_fillExtent = f; + m_fillCompletion = baseCompletion + + size_t(100 * fabsf(float(f - start) / + float(end - start))); + counter = 0; + } + } + } + + fftw_destroy_plan(plan); + fftw_free(output); + fftw_free(input); + + if (!interrupted) { + m_fillExtent = end; + m_fillCompletion = 100; + } + + m_layer.m_mutex.lock(); + } + + if (!interrupted) m_layer.m_condition.wait(&m_layer.m_mutex, 2000); + } +} + +bool +SpectrogramLayer::getYBinRange(int y, float &q0, float &q1) const +{ + int h = m_view->height(); + if (y < 0 || y >= h) return false; + + // Each pixel in a column is drawn from a possibly non- + // integral set of frequency bins. + + if (m_frequencyScale == LinearFrequencyScale) { + + size_t bins = m_windowSize / 2; + + if (m_maxFrequency > 0) { + int sr = m_model->getSampleRate(); + bins = int((double(m_maxFrequency) * m_windowSize) / sr + 0.1); + if (bins > m_windowSize / 2) bins = m_windowSize / 2; + } + + q0 = float(h - y - 1) * bins / h; + q1 = float(h - y) * bins / h; + + } else { + + // This is all most ad-hoc. I'm not at my brightest. + + int sr = m_model->getSampleRate(); + + float maxf = m_maxFrequency; + if (maxf == 0.0) maxf = float(sr) / 2; + + float minf = float(sr) / m_windowSize; + + float maxlogf = log10f(maxf); + float minlogf = log10f(minf); + + float logf0 = minlogf + ((maxlogf - minlogf) * (h - y - 1)) / h; + float logf1 = minlogf + ((maxlogf - minlogf) * (h - y)) / h; + + float f0 = pow(10.f, logf0); + float f1 = pow(10.f, logf1); + + q0 = ((f0 * m_windowSize) / sr) - 1; + q1 = ((f1 * m_windowSize) / sr) - 1; + +// std::cout << "y=" << y << " h=" << h << " maxf=" << maxf << " maxlogf=" +// << maxlogf << " logf0=" << logf0 << " f0=" << f0 << " q0=" +// << q0 << std::endl; + } + + return true; +} + +bool +SpectrogramLayer::getXBinRange(int x, float &s0, float &s1, LayerRange *range) const +{ + long startFrame; + int zoomLevel; + size_t modelStart; + size_t modelEnd; + + if (range) { + startFrame = range->startFrame; + zoomLevel = range->zoomLevel; + modelStart = range->modelStart; + modelEnd = range->modelEnd; + } else { + startFrame = m_view->getStartFrame(); + zoomLevel = m_view->getZoomLevel(); + modelStart = m_model->getStartFrame(); + modelEnd = m_model->getEndFrame(); + } + + // Each pixel column covers an exact range of sample frames: + int f0 = startFrame + x * zoomLevel - modelStart; + int f1 = f0 + zoomLevel - 1; + + if (f1 < int(modelStart) || f0 > int(modelEnd)) return false; + + // And that range may be drawn from a possibly non-integral + // range of spectrogram windows: + + size_t windowIncrement = getWindowIncrement(); + + s0 = float(f0) / windowIncrement; + s1 = float(f1) / windowIncrement; + + return true; +} + +bool +SpectrogramLayer::getXBinSourceRange(int x, RealTime &min, RealTime &max) const +{ + float s0 = 0, s1 = 0; + if (!getXBinRange(x, s0, s1)) return false; + + int s0i = int(s0 + 0.001); + int s1i = int(s1); + + int windowIncrement = getWindowIncrement(); + int w0 = s0i * windowIncrement - (m_windowSize - windowIncrement)/2; + int w1 = s1i * windowIncrement + windowIncrement + + (m_windowSize - windowIncrement)/2 - 1; + + min = RealTime::frame2RealTime(w0, m_model->getSampleRate()); + max = RealTime::frame2RealTime(w1, m_model->getSampleRate()); + return true; +} + +bool +SpectrogramLayer::getYBinSourceRange(int y, float &freqMin, float &freqMax) +const +{ + float q0 = 0, q1 = 0; + if (!getYBinRange(y, q0, q1)) return false; + + int q0i = int(q0 + 0.001); + int q1i = int(q1); + + int sr = m_model->getSampleRate(); + + for (int q = q0i; q <= q1i; ++q) { + int binfreq = (sr * (q + 1)) / m_windowSize; + if (q == q0i) freqMin = binfreq; + if (q == q1i) freqMax = binfreq; + } + return true; +} + +bool +SpectrogramLayer::getXYBinSourceRange(int x, int y, float &dbMin, float &dbMax) const +{ + float q0 = 0, q1 = 0; + if (!getYBinRange(y, q0, q1)) return false; + + float s0 = 0, s1 = 0; + if (!getXBinRange(x, s0, s1)) return false; + + int q0i = int(q0 + 0.001); + int q1i = int(q1); + + int s0i = int(s0 + 0.001); + int s1i = int(s1); + + if (m_mutex.tryLock()) { + if (m_cache && !m_cacheInvalid) { + + int cw = m_cache->width(); + int ch = m_cache->height(); + + int min = -1, max = -1; + + for (int q = q0i; q <= q1i; ++q) { + for (int s = s0i; s <= s1i; ++s) { + if (s >= 0 && q >= 0 && s < cw && q < ch) { + int value = m_cache->scanLine(q)[s]; + if (min == -1 || value < min) min = value; + if (max == -1 || value > max) max = value; + } + } + } + + if (min < 0) return false; + + dbMin = (float(min) / 256.0) * 80.0 - 80.0; + dbMax = (float(max + 1) / 256.0) * 80.0 - 80.1; + + m_mutex.unlock(); + return true; + } + + m_mutex.unlock(); + } + + return false; +} + +void +SpectrogramLayer::paint(QPainter &paint, QRect rect) const +{ +// Profiler profiler("SpectrogramLayer::paint", true); +#ifdef DEBUG_SPECTROGRAM_REPAINT + 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; +#endif + + if (!m_model || !m_model->isOK() || !m_model->isReady()) { + return; + } + +#ifdef DEBUG_SPECTROGRAM_REPAINT + std::cerr << "SpectrogramLayer::paint(): About to lock" << std::endl; +#endif + +/* + if (m_cachedInitialVisibleArea) { + if (!m_mutex.tryLock()) { + m_view->update(); + return; + } + } else { +*/ + m_mutex.lock(); +// } + +#ifdef DEBUG_SPECTROGRAM_REPAINT + std::cerr << "SpectrogramLayer::paint(): locked" << std::endl; +#endif + + if (m_cacheInvalid) { // lock the mutex before checking this + m_mutex.unlock(); +#ifdef DEBUG_SPECTROGRAM_REPAINT + std::cerr << "SpectrogramLayer::paint(): Cache invalid, returning" << std::endl; +#endif + return; + } + + bool stillCacheing = (m_updateTimer != 0); + +#ifdef DEBUG_SPECTROGRAM_REPAINT + std::cerr << "SpectrogramLayer::paint(): Still cacheing = " << stillCacheing << std::endl; +#endif + + long startFrame = m_view->getStartFrame(); + int zoomLevel = m_view->getZoomLevel(); + + int x0 = 0; + int x1 = m_view->width(); + int y0 = 0; + int y1 = m_view->height(); + + bool recreateWholePixmapCache = true; + + if (!m_pixmapCacheInvalid) { + + //!!! This cache may have been obsoleted entirely by the + //scrolling cache in View. Perhaps experiment with + //removing it and see if it makes things even quicker (or else + //make it optional) + + if (int(m_pixmapCacheZoomLevel) == zoomLevel && + m_pixmapCache->width() == m_view->width() && + m_pixmapCache->height() == m_view->height()) { + + if (m_pixmapCacheStartFrame / zoomLevel == + startFrame / zoomLevel) { + +#ifdef DEBUG_SPECTROGRAM_REPAINT + std::cerr << "SpectrogramLayer: pixmap cache good" << std::endl; +#endif + + m_mutex.unlock(); + paint.drawPixmap(rect, *m_pixmapCache, rect); + return; + + } else { + +#ifdef DEBUG_SPECTROGRAM_REPAINT + std::cerr << "SpectrogramLayer: pixmap cache partially OK" << std::endl; +#endif + + recreateWholePixmapCache = false; + + int dx = (m_pixmapCacheStartFrame - startFrame) / zoomLevel; + +#ifdef DEBUG_SPECTROGRAM_REPAINT + std::cerr << "SpectrogramLayer: dx = " << dx << " (pixmap cache " << m_pixmapCache->width() << "x" << m_pixmapCache->height() << ")" << std::endl; +#endif + + if (dx > -m_pixmapCache->width() && dx < m_pixmapCache->width()) { + +#if defined(Q_WS_WIN32) || defined(Q_WS_MAC) + // Copying a pixmap to itself doesn't work + // properly on Windows or Mac (it only works when + // moving in one direction). + + //!!! Need a utility function for this + + static QPixmap *tmpPixmap = 0; + if (!tmpPixmap || + tmpPixmap->width() != m_pixmapCache->width() || + tmpPixmap->height() != m_pixmapCache->height()) { + delete tmpPixmap; + tmpPixmap = new QPixmap(m_pixmapCache->width(), + m_pixmapCache->height()); + } + QPainter cachePainter; + cachePainter.begin(tmpPixmap); + cachePainter.drawPixmap(0, 0, *m_pixmapCache); + cachePainter.end(); + cachePainter.begin(m_pixmapCache); + cachePainter.drawPixmap(dx, 0, *tmpPixmap); + cachePainter.end(); +#else + QPainter cachePainter(m_pixmapCache); + cachePainter.drawPixmap(dx, 0, *m_pixmapCache); + cachePainter.end(); +#endif + + paint.drawPixmap(rect, *m_pixmapCache, rect); + + if (dx < 0) { + x0 = m_pixmapCache->width() + dx; + x1 = m_pixmapCache->width(); + } else { + x0 = 0; + x1 = dx; + } + } + } + } else { +#ifdef DEBUG_SPECTROGRAM_REPAINT + std::cerr << "SpectrogramLayer: pixmap cache useless" << std::endl; +#endif + } + } + + if (stillCacheing) { + x0 = rect.left(); + x1 = rect.right() + 1; + y0 = rect.top(); + y1 = rect.bottom() + 1; + } + + int w = x1 - x0; + int h = y1 - y0; + +// std::cerr << "x0 " << x0 << ", x1 " << x1 << ", w " << w << ", h " << h << std::endl; + + QImage scaled(w, h, QImage::Format_RGB32); + + LayerRange range = { m_view->getStartFrame(), m_view->getZoomLevel(), + m_model->getStartFrame(), m_model->getEndFrame() }; + + m_mutex.unlock(); + + for (int y = 0; y < h; ++y) { + + m_mutex.lock(); + if (m_cacheInvalid) { + m_mutex.unlock(); + break; + } + + int cw = m_cache->width(); + int ch = m_cache->height(); + + float q0 = 0, q1 = 0; + + if (!getYBinRange(y0 + y, q0, q1)) { + for (int x = 0; x < w; ++x) { + assert(x <= scaled.width()); + scaled.setPixel(x, y, qRgb(0, 0, 0)); + } + m_mutex.unlock(); + continue; + } + + int q0i = int(q0 + 0.001); + int q1i = int(q1); + + for (int x = 0; x < w; ++x) { + + float s0 = 0, s1 = 0; + + if (!getXBinRange(x0 + x, s0, s1, &range)) { + assert(x <= scaled.width()); + scaled.setPixel(x, y, qRgb(0, 0, 0)); + continue; + } + + int s0i = int(s0 + 0.001); + int s1i = int(s1); + + float total = 0, divisor = 0; + + for (int s = s0i; s <= s1i; ++s) { + + float sprop = 1.0; + if (s == s0i) sprop *= (s + 1) - s0; + if (s == s1i) sprop *= s1 - s; + + for (int q = q0i; q <= q1i; ++q) { + + float qprop = sprop; + if (q == q0i) qprop *= (q + 1) - q0; + if (q == q1i) qprop *= q1 - q; + + if (s >= 0 && q >= 0 && s < cw && q < ch) { + total += qprop * m_cache->scanLine(q)[s]; + divisor += qprop; + } + } + } + + if (divisor > 0.0) { + int pixel = int(total / divisor); + if (pixel > 255) pixel = 255; + if (pixel < 1) pixel = 1; + assert(x <= scaled.width()); + scaled.setPixel(x, y, m_cache->color(pixel)); + } else { + assert(x <= scaled.width()); + scaled.setPixel(x, y, qRgb(0, 0, 0)); + } + } + + m_mutex.unlock(); + } + + paint.drawImage(x0, y0, scaled); + + if (recreateWholePixmapCache) { + delete m_pixmapCache; + m_pixmapCache = new QPixmap(w, h); + } + + QPainter cachePainter(m_pixmapCache); + cachePainter.drawImage(x0, y0, scaled); + cachePainter.end(); + + m_pixmapCacheInvalid = false; + m_pixmapCacheStartFrame = startFrame; + m_pixmapCacheZoomLevel = zoomLevel; + +#ifdef DEBUG_SPECTROGRAM_REPAINT + std::cerr << "SpectrogramLayer::paint() returning" << std::endl; +#endif + +//!!! drawLocalFeatureDescription(paint); +} + +int +SpectrogramLayer::getCompletion() const +{ + if (m_updateTimer == 0) return 100; + size_t completion = m_fillThread->getFillCompletion(); +// std::cerr << "SpectrogramLayer::getCompletion: completion = " << completion << std::endl; + return completion; +} + +QRect +SpectrogramLayer::getFeatureDescriptionRect(QPainter &paint, QPoint pos) const +{ + if (!m_model || !m_model->isOK()) return QRect(); + + QString timeLabel = tr("Time: "); + QString freqLabel = tr("Hz: "); + QString dBLabel = tr("dB: "); + + // assume time is widest + RealTime rtMin, rtMax; + if (!getXBinSourceRange(pos.x(), rtMin, rtMax)) return QRect(); + QString timeMinText = QString("%1").arg(rtMin.toText(true).c_str()); + QString timeMaxText = QString(" - %1").arg(rtMax.toText(true).c_str()); + + QFontMetrics metrics = paint.fontMetrics(); + + int labelwidth = + std::max(std::max(metrics.width(timeLabel), + metrics.width(freqLabel)), + metrics.width(dBLabel)); + + int boxwidth = labelwidth + + metrics.width(timeMinText) + metrics.width(timeMaxText); + + int fontHeight = metrics.height(); + int boxheight = fontHeight * 3 + 4; + + return QRect(0, 0, boxwidth + 20, boxheight + 15); +} + +void +SpectrogramLayer::paintLocalFeatureDescription(QPainter &paint, + QRect rect, QPoint pos) const +{ + int x = pos.x(); + int y = pos.y(); + + if (!m_model || !m_model->isOK()) return; + + float dbMin = 0, dbMax = 0; + float freqMin = 0, freqMax = 0; + RealTime rtMin, rtMax; + + bool haveDb = false; + + if (!getXBinSourceRange(x, rtMin, rtMax)) return; + if (!getYBinSourceRange(y, freqMin, freqMax)) return; + if (getXYBinSourceRange(x, y, dbMin, dbMax)) haveDb = true; + + QString timeLabel = tr("Time: "); + QString freqLabel = tr("Hz: "); + QString dBLabel = tr("dB: "); + + QString timeMinText = QString("%1").arg(rtMin.toText(true).c_str()); + QString timeMaxText = QString(" - %1").arg(rtMax.toText(true).c_str()); + + QString freqMinText = QString("%1").arg(freqMin); + QString freqMaxText = ""; + if (freqMax != freqMin) { + freqMaxText = QString(" - %1").arg(freqMax); + } + + QString dBMinText = ""; + QString dBMaxText = ""; + + if (haveDb) { + int dbmxi = int(dbMax - 0.001); + int dbmni = int(dbMin - 0.001); + dBMinText = QString("%1").arg(dbmni); + if (dbmxi != dbmni) dBMaxText = QString(" - %1").arg(dbmxi); + } + + QFontMetrics metrics = paint.fontMetrics(); + + int labelwidth = + std::max(std::max(metrics.width(timeLabel), + metrics.width(freqLabel)), + metrics.width(dBLabel)); + + int minwidth = + std::max(std::max(metrics.width(timeMinText), + metrics.width(freqMinText)), + metrics.width(dBMinText)); + + int maxwidth = + std::max(std::max(metrics.width(timeMaxText), + metrics.width(freqMaxText)), + metrics.width(dBMaxText)); + + int boxwidth = labelwidth + minwidth + maxwidth; + + int fontAscent = metrics.ascent(); + int fontHeight = metrics.height(); + + int boxheight = fontHeight * 3 + 4; + +// paint.setPen(Qt::white); +// paint.setBrush(Qt::NoBrush); + +//!!! int xbase = m_view->width() - boxwidth - 20; + int xbase = rect.x() + 5; + int ybase = rect.y() + 5; + + paint.drawRect(xbase, ybase, boxwidth + 10, + boxheight + 10 - metrics.descent() + 1); + + paint.drawText(xbase + 5 + labelwidth - metrics.width(timeLabel), + ybase + 5 + fontAscent, timeLabel); + + paint.drawText(xbase + 5 + labelwidth - metrics.width(freqLabel), + ybase + 7 + fontAscent + fontHeight, freqLabel); + + paint.drawText(xbase + 5 + labelwidth - metrics.width(dBLabel), + ybase + 9 + fontAscent + fontHeight * 2, dBLabel); + + paint.drawText(xbase + 5 + labelwidth + minwidth - metrics.width(timeMinText), + ybase + 5 + fontAscent, timeMinText); + + paint.drawText(xbase + 5 + labelwidth + minwidth - metrics.width(freqMinText), + ybase + 7 + fontAscent + fontHeight, freqMinText); + + paint.drawText(xbase + 5 + labelwidth + minwidth - metrics.width(dBMinText), + ybase + 9 + fontAscent + fontHeight * 2, dBMinText); + + paint.drawText(xbase + 5 + labelwidth + minwidth, + ybase + 5 + fontAscent, timeMaxText); + + paint.drawText(xbase + 5 + labelwidth + minwidth, + ybase + 7 + fontAscent + fontHeight, freqMaxText); + + paint.drawText(xbase + 5 + labelwidth + minwidth, + ybase + 9 + fontAscent + fontHeight * 2, dBMaxText); +} + +/*!!! + +bool +SpectrogramLayer::identifyLocalFeatures(bool on, int x, int y) +{ + return true; //!!! + + m_identify = on; + m_identifyX = x; + m_identifyY = y; + + m_view->update(); +*/ +/* + if (!m_model || !m_model->isOK()) return false; + + std::cerr << "SpectrogramLayer::identifyLocalFeatures(" << on << "," << x << "," << y << ")" << std::endl; + + float dbMin = 0, dbMax = 0; + float freqMin = 0, freqMax = 0; + RealTime rtMin, rtMax; + + if (getXBinSourceRange(x, rtMin, rtMax)) { + std::cerr << "Times: " << rtMin << " -> " << rtMax << std::endl; + } else return false; + + if (getYBinSourceRange(y, freqMin, freqMax)) { + std::cerr << "Frequencies: " << freqMin << " -> " << freqMax << std::endl; + } else return false; + + if (getXYBinSourceRange(x, y, dbMin, dbMax)) { + std::cerr << "dB: " << dbMin << " -> " << dbMax << std::endl; + } + + m_identifyX = x; + m_identifyY = y; + m_identify = true; +*/ + /*!!! + return true; +} + */ +int +SpectrogramLayer::getVerticalScaleWidth(QPainter &paint) const +{ + if (!m_model || !m_model->isOK()) return 0; + + int tw = paint.fontMetrics().width(QString("%1") + .arg(m_maxFrequency > 0 ? + m_maxFrequency - 1 : + m_model->getSampleRate() / 2)); + + int fw = paint.fontMetrics().width(QString("43Hz")); + if (tw < fw) tw = fw; + + return tw + 13; +} + +void +SpectrogramLayer::paintVerticalScale(QPainter &paint, QRect rect) const +{ + if (!m_model || !m_model->isOK()) { + return; + } + + int h = rect.height(), w = rect.width(); + + size_t bins = m_windowSize / 2; + int sr = m_model->getSampleRate(); + + if (m_maxFrequency > 0) { + bins = int((double(m_maxFrequency) * m_windowSize) / sr + 0.1); + if (bins > m_windowSize / 2) bins = m_windowSize / 2; + } + + int py = -1; + int textHeight = paint.fontMetrics().height(); + int toff = -textHeight + paint.fontMetrics().ascent() + 2; + + int bin = -1; + + for (int y = 0; y < m_view->height(); ++y) { + + float q0, q1; + if (!getYBinRange(m_view->height() - y, q0, q1)) continue; + + int vy; + + if (int(q0) > bin) { + vy = y; + bin = int(q0); + } else { + continue; + } + + int freq = (sr * (bin + 1)) / m_windowSize; + + if (py >= 0 && (vy - py) < textHeight - 1) { + paint.drawLine(w - 4, h - vy, w, h - vy); + continue; + } + + QString text = QString("%1").arg(freq); + if (bin == 0) text = QString("%1Hz").arg(freq); + paint.drawLine(0, h - vy, w, h - vy); + + if (h - vy - textHeight >= -2) { + int tx = w - 10 - paint.fontMetrics().width(text); + paint.drawText(tx, h - vy + toff, text); + } + + py = vy; + } +} + +#ifdef INCLUDE_MOCFILES +#include "SpectrogramLayer.moc.cpp" +#endif + diff -r 000000000000 -r 2a4f26e85b4c layer/SpectrogramLayer.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/layer/SpectrogramLayer.h Tue Jan 10 16:33:16 2006 +0000 @@ -0,0 +1,215 @@ +/* -*- c-basic-offset: 4 -*- vi:set ts=8 sts=4 sw=4: */ + +/* + A waveform viewer and audio annotation editor. + Chris Cannam, Queen Mary University of London, 2005 + + This is experimental software. Not for distribution. +*/ + +#ifndef _SPECTROGRAM_VIEW_H_ +#define _SPECTROGRAM_VIEW_H_ + +#include "base/Layer.h" +#include "base/Window.h" +#include "model/PowerOfSqrtTwoZoomConstraint.h" +#include "model/DenseTimeValueModel.h" + +#include +#include +#include + +#include + +class View; +class QPainter; +class QImage; +class QPixmap; +class QTimer; +class RealTime; + +/** + * SpectrogramLayer represents waveform data (obtained from a + * DenseTimeValueModel) in spectrogram form. + */ + +class SpectrogramLayer : public Layer, + public PowerOfSqrtTwoZoomConstraint +{ + Q_OBJECT + +public: + enum Configuration { FullRangeDb, MelodicRange }; + + SpectrogramLayer(View *w, Configuration = FullRangeDb); + ~SpectrogramLayer(); + + virtual const ZoomConstraint *getZoomConstraint() const { return this; } + virtual const Model *getModel() const { return m_model; } + virtual void paint(QPainter &paint, QRect rect) const; + + virtual int getVerticalScaleWidth(QPainter &) const; + virtual void paintVerticalScale(QPainter &paint, QRect rect) const; + + virtual QRect getFeatureDescriptionRect(QPainter &, QPoint) const; + virtual void paintLocalFeatureDescription(QPainter &, QRect, QPoint) const; + + void setModel(const DenseTimeValueModel *model); + + virtual PropertyList getProperties() const; + virtual PropertyType getPropertyType(const PropertyName &) const; + virtual QString getPropertyGroupName(const PropertyName &) const; + virtual int getPropertyRangeAndValue(const PropertyName &, + int *min, int *max) const; + virtual QString getPropertyValueLabel(const PropertyName &, + int value) const; + virtual void setProperty(const PropertyName &, int value); + + /** + * Specify the channel to use from the source model. + * A value of -1 means to mix all available channels. + * The default is channel 0. + */ + void setChannel(int); + int getChannel() const; + + void setWindowSize(size_t); + size_t getWindowSize() const; + + void setWindowOverlap(size_t percent); + size_t getWindowOverlap() const; + + void setWindowType(WindowType type); + WindowType getWindowType() const; + + /** + * Set the gain multiplier for sample values in this view prior to + * FFT calculation. + * + * The default is 1.0. + */ + void setGain(float gain); + float getGain() const; + + void setMaxFrequency(size_t); // 0 -> no maximum + size_t getMaxFrequency() const; + + enum ColourScale { LinearColourScale, MeterColourScale, dBColourScale, + PhaseColourScale }; + + /** + * Specify the scale for sample levels. See WaveformLayer for + * details of meter and dB scaling. The default is dBColourScale. + */ + void setColourScale(ColourScale); + ColourScale getColourScale() const; + + enum FrequencyScale { LinearFrequencyScale, LogFrequencyScale }; + + /** + * Specify the scale for the y axis. + */ + void setFrequencyScale(FrequencyScale); + FrequencyScale getFrequencyScale() const; + + enum ColourScheme { DefaultColours, WhiteOnBlack, BlackOnWhite, + RedOnBlue, YellowOnBlack, RedOnBlack }; + + void setColourScheme(ColourScheme scheme); + ColourScheme getColourScheme() const; + + virtual VerticalPosition getPreferredFrameCountPosition() const { + return PositionTop; + } + + virtual int getCompletion() const; + + virtual QString getPropertyContainerIconName() const { return "spectrogram"; } + +protected slots: + void cacheInvalid(); + void cacheInvalid(size_t startFrame, size_t endFrame); + + void fillTimerTimedOut(); + +protected: + const DenseTimeValueModel *m_model; // I do not own this + + int m_channel; + size_t m_windowSize; + WindowType m_windowType; + size_t m_windowOverlap; + float m_gain; + size_t m_maxFrequency; + ColourScale m_colourScale; + ColourScheme m_colourScheme; + FrequencyScale m_frequencyScale; + + class CacheFillThread : public QThread + { + public: + CacheFillThread(SpectrogramLayer &layer) : + m_layer(layer), m_fillExtent(0) { } + + size_t getFillExtent() const { return m_fillExtent; } + size_t getFillCompletion() const { return m_fillCompletion; } + virtual void run(); + + protected: + SpectrogramLayer &m_layer; + size_t m_fillExtent; + size_t m_fillCompletion; + }; + + void fillCache(); + + QImage *m_cache; + bool m_cacheInvalid; + size_t m_maxCachedFrequency; + + mutable QPixmap *m_pixmapCache; + mutable bool m_pixmapCacheInvalid; + mutable long m_pixmapCacheStartFrame; + mutable size_t m_pixmapCacheZoomLevel; + + QWaitCondition m_condition; + mutable QMutex m_mutex; + + CacheFillThread *m_fillThread; + QTimer *m_updateTimer; + size_t m_lastFillExtent; + bool m_cachedInitialVisibleArea; + bool m_exiting; + + void setCacheColourmap(); + + bool fillCacheColumn(int column, + double *inputBuffer, + fftw_complex *outputBuffer, + fftw_plan plan, + const Window &windower, + bool lock) + const; + + bool getYBinRange(int y, float &freqBinMin, float &freqBinMax) const; + + struct LayerRange { + long startFrame; + int zoomLevel; + size_t modelStart; + size_t modelEnd; + }; + /// LayerRange is only passed in to save lookup time + bool getXBinRange(int x, float &windowMin, float &windowMax, + LayerRange *range = 0) const; + + bool getYBinSourceRange(int y, float &freqMin, float &freqMax) const; + bool getXBinSourceRange(int x, RealTime &timeMin, RealTime &timeMax) const; + bool getXYBinSourceRange(int x, int y, float &dbMin, float &dbMax) const; + + size_t getWindowIncrement() const { + return m_windowSize - m_windowSize * m_windowOverlap / 100; + } +}; + +#endif diff -r 000000000000 -r 2a4f26e85b4c layer/TimeInstantLayer.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/layer/TimeInstantLayer.cpp Tue Jan 10 16:33:16 2006 +0000 @@ -0,0 +1,305 @@ +/* -*- c-basic-offset: 4 -*- vi:set ts=8 sts=4 sw=4: */ + +/* + A waveform viewer and audio annotation editor. + Chris Cannam, Queen Mary University of London, 2005 + + This is experimental software. Not for distribution. +*/ + +#include "TimeInstantLayer.h" + +#include "base/Model.h" +#include "base/RealTime.h" +#include "base/View.h" +#include "base/Profiler.h" + +#include "model/SparseOneDimensionalModel.h" + +#include + +#include + +TimeInstantLayer::TimeInstantLayer(View *w) : + Layer(w), + m_model(0), + m_colour(QColor(200, 50, 255)) +{ + m_view->addLayer(this); +} + +void +TimeInstantLayer::setModel(SparseOneDimensionalModel *model) +{ + if (m_model == model) return; + m_model = model; + + connect(m_model, SIGNAL(modelChanged()), this, SIGNAL(modelChanged())); + connect(m_model, SIGNAL(modelChanged(size_t, size_t)), + this, SIGNAL(modelChanged(size_t, size_t))); + + connect(m_model, SIGNAL(completionChanged()), + this, SIGNAL(modelCompletionChanged())); + + std::cerr << "TimeInstantLayer::setModel(" << model << ")" << std::endl; + + emit modelReplaced(); +} + +Layer::PropertyList +TimeInstantLayer::getProperties() const +{ + PropertyList list; + list.push_back(tr("Colour")); + return list; +} + +Layer::PropertyType +TimeInstantLayer::getPropertyType(const PropertyName &name) const +{ + return ValueProperty; +} + +int +TimeInstantLayer::getPropertyRangeAndValue(const PropertyName &name, + int *min, int *max) const +{ + int deft = 0; + + if (name == tr("Colour")) { + + *min = 0; + *max = 5; + + if (m_colour == Qt::black) deft = 0; + else if (m_colour == Qt::darkRed) deft = 1; + else if (m_colour == Qt::darkBlue) deft = 2; + else if (m_colour == Qt::darkGreen) deft = 3; + else if (m_colour == QColor(200, 50, 255)) deft = 4; + else if (m_colour == QColor(255, 150, 50)) deft = 5; + + } else { + + deft = Layer::getPropertyRangeAndValue(name, min, max); + } + + return deft; +} + +QString +TimeInstantLayer::getPropertyValueLabel(const PropertyName &name, + int value) const +{ + if (name == tr("Colour")) { + switch (value) { + default: + case 0: return tr("Black"); + case 1: return tr("Red"); + case 2: return tr("Blue"); + case 3: return tr("Green"); + case 4: return tr("Purple"); + case 5: return tr("Orange"); + } + } + return tr(""); +} + +void +TimeInstantLayer::setProperty(const PropertyName &name, int value) +{ + if (name == tr("Colour")) { + switch (value) { + default: + case 0: setBaseColour(Qt::black); break; + case 1: setBaseColour(Qt::darkRed); break; + case 2: setBaseColour(Qt::darkBlue); break; + case 3: setBaseColour(Qt::darkGreen); break; + case 4: setBaseColour(QColor(200, 50, 255)); break; + case 5: setBaseColour(QColor(255, 150, 50)); break; + } + } +} + +void +TimeInstantLayer::setBaseColour(QColor colour) +{ + if (m_colour == colour) return; + m_colour = colour; + emit layerParametersChanged(); +} + +bool +TimeInstantLayer::isLayerScrollable() const +{ + QPoint discard; + return !m_view->shouldIlluminateLocalFeatures(this, discard); +} + +QRect +TimeInstantLayer::getFeatureDescriptionRect(QPainter &paint, QPoint pos) const +{ + return QRect(0, 0, + std::max(100, paint.fontMetrics().width(tr("No local points"))), + 50); //!!! cruddy +} + +SparseOneDimensionalModel::PointList +TimeInstantLayer::getLocalPoints(int x) const +{ + if (!m_model) return SparseOneDimensionalModel::PointList(); + + long startFrame = m_view->getStartFrame(); + long endFrame = m_view->getEndFrame(); + int zoomLevel = m_view->getZoomLevel(); + long frame = startFrame + x * zoomLevel; + + SparseOneDimensionalModel::PointList onPoints = + m_model->getPoints(frame); + + if (!onPoints.empty()) { + return onPoints; + } + + SparseOneDimensionalModel::PointList prevPoints = + m_model->getPreviousPoints(frame); + SparseOneDimensionalModel::PointList nextPoints = + m_model->getNextPoints(frame); + + SparseOneDimensionalModel::PointList usePoints = prevPoints; + + if (prevPoints.empty()) { + usePoints = nextPoints; + } else if (prevPoints.begin()->frame < startFrame && + !(nextPoints.begin()->frame > endFrame)) { + usePoints = nextPoints; + } else if (nextPoints.begin()->frame - frame < + frame - prevPoints.begin()->frame) { + usePoints = nextPoints; + } + + return usePoints; +} + +void +TimeInstantLayer::paintLocalFeatureDescription(QPainter &paint, QRect rect, + QPoint pos) const +{ + //!!! bleagh + + int x = pos.x(); + + if (!m_model || !m_model->getSampleRate()) return; + + SparseOneDimensionalModel::PointList points = getLocalPoints(x); + + QFontMetrics metrics = paint.fontMetrics(); + int xbase = rect.x() + 5; + int ybase = rect.y() + 5; + + if (points.empty()) { + QString label = tr("No local points"); + if (!m_model->isReady()) { + label = tr("In progress"); + } + paint.drawText(xbase + 5, ybase + 5 + metrics.ascent(), label); + return; + } + + long useFrame = points.begin()->frame; + + RealTime rt = RealTime::frame2RealTime(useFrame, m_model->getSampleRate()); + QString timeText = QString("%1").arg(rt.toText(true).c_str()); + + int timewidth = metrics.width(timeText); + int labelwidth = metrics.width(points.begin()->label); + + int boxheight = metrics.height() * 2 + 3; + int boxwidth = std::max(timewidth, labelwidth); + + paint.drawRect(xbase, ybase, boxwidth + 10, + boxheight + 10 - metrics.descent() + 1); + + paint.drawText(xbase + 5, ybase + 5 + metrics.ascent(), timeText); + paint.drawText(xbase + 5, ybase + 7 + metrics.ascent() + metrics.height(), + points.begin()->label); +} + +void +TimeInstantLayer::paint(QPainter &paint, QRect rect) const +{ + if (!m_model || !m_model->isOK()) return; + +// Profiler profiler("TimeInstantLayer::paint", true); + + long startFrame = m_view->getStartFrame(); + int zoomLevel = m_view->getZoomLevel(); + + int x0 = rect.left(), x1 = rect.right(); + long frame0 = startFrame + x0 * zoomLevel; + long frame1 = startFrame + x1 * zoomLevel; + + SparseOneDimensionalModel::PointList points(m_model->getPoints + (frame0, frame1)); + + paint.setPen(m_colour); + + QColor brushColour(m_colour); + brushColour.setAlpha(100); + paint.setBrush(brushColour); + +// std::cerr << "TimeInstantLayer::paint: resolution is " +// << m_model->getResolution() << " frames" << std::endl; + + QPoint localPos; + long illuminateFrame = -1; + + if (m_view->shouldIlluminateLocalFeatures(this, localPos)) { + SparseOneDimensionalModel::PointList localPoints = + getLocalPoints(localPos.x()); + if (!localPoints.empty()) illuminateFrame = localPoints.begin()->frame; + } + + for (SparseOneDimensionalModel::PointList::const_iterator i = points.begin(); + i != points.end(); ++i) { + + const SparseOneDimensionalModel::Point &p(*i); + + int x = (p.frame - startFrame) / zoomLevel; + int w = m_model->getResolution() / zoomLevel; + + if (w < 1) w = 1; + if (p.frame == illuminateFrame) { + paint.setPen(Qt::black); //!!! + } else { + paint.setPen(brushColour); + } + paint.drawRect(x, 0, w - 1, m_view->height() - 1); + paint.setPen(m_colour); + + if (p.label != "") { + + // only draw if there's enough room from here to the next point + + int lw = paint.fontMetrics().width(p.label); + bool good = true; + + SparseOneDimensionalModel::PointList::const_iterator j = i; + if (++j != points.end()) { + int nx = (j->frame - startFrame) / zoomLevel; + if (nx >= x && nx - x - w - 3 <= lw) good = false; + } + + if (good) { + paint.drawText(x + w + 2, + m_view->height() - paint.fontMetrics().height(), + p.label); + } + } + } +} + + +#ifdef INCLUDE_MOCFILES +#include "TimeInstantLayer.moc.cpp" +#endif + diff -r 000000000000 -r 2a4f26e85b4c layer/TimeInstantLayer.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/layer/TimeInstantLayer.h Tue Jan 10 16:33:16 2006 +0000 @@ -0,0 +1,62 @@ +/* -*- c-basic-offset: 4 -*- vi:set ts=8 sts=4 sw=4: */ + +/* + A waveform viewer and audio annotation editor. + Chris Cannam, Queen Mary University of London, 2005 + + This is experimental software. Not for distribution. +*/ + +#ifndef _TIME_INSTANT_VIEW_H_ +#define _TIME_INSTANT_VIEW_H_ + +#include "base/Layer.h" +#include "model/SparseOneDimensionalModel.h" + +#include +#include + +class View; +class QPainter; + +class TimeInstantLayer : public Layer +{ + Q_OBJECT + +public: + TimeInstantLayer(View *w); + + virtual void paint(QPainter &paint, QRect rect) const; + + virtual QRect getFeatureDescriptionRect(QPainter &, QPoint) const; + virtual void paintLocalFeatureDescription(QPainter &, QRect, QPoint) const; + + virtual const Model *getModel() const { return m_model; } + void setModel(SparseOneDimensionalModel *model); + + virtual PropertyList getProperties() const; + virtual PropertyType getPropertyType(const PropertyName &) const; + virtual int getPropertyRangeAndValue(const PropertyName &, + int *min, int *max) const; + virtual QString getPropertyValueLabel(const PropertyName &, + int value) const; + virtual void setProperty(const PropertyName &, int value); + + void setBaseColour(QColor); + QColor getBaseColour() const { return m_colour; } + + virtual QString getPropertyContainerIconName() const { return "instants"; } + + virtual bool isLayerScrollable() const; + + virtual int getCompletion() const { return m_model->getCompletion(); } + +protected: + SparseOneDimensionalModel::PointList getLocalPoints(int) const; + + SparseOneDimensionalModel *m_model; + QColor m_colour; +}; + +#endif + diff -r 000000000000 -r 2a4f26e85b4c layer/TimeRulerLayer.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/layer/TimeRulerLayer.cpp Tue Jan 10 16:33:16 2006 +0000 @@ -0,0 +1,288 @@ +/* -*- c-basic-offset: 4 -*- vi:set ts=8 sts=4 sw=4: */ + +/* + A waveform viewer and audio annotation editor. + Chris Cannam, Queen Mary University of London, 2005 + + This is experimental software. Not for distribution. +*/ + +#include "TimeRulerLayer.h" + +#include "base/Model.h" +#include "base/RealTime.h" +#include "base/View.h" + +#include + +#include + +using std::cerr; +using std::endl; + +TimeRulerLayer::TimeRulerLayer(View *w) : + Layer(w), + m_model(0), + m_colour(Qt::black), + m_labelHeight(LabelTop) +{ + m_view->addLayer(this); +} + +void +TimeRulerLayer::setModel(Model *model) +{ + if (m_model == model) return; + m_model = model; + emit modelReplaced(); +} + +void +TimeRulerLayer::setBaseColour(QColor colour) +{ + if (m_colour == colour) return; + m_colour = colour; + emit layerParametersChanged(); +} + +Layer::PropertyList +TimeRulerLayer::getProperties() const +{ + PropertyList list; + list.push_back(tr("Colour")); + return list; +} + +Layer::PropertyType +TimeRulerLayer::getPropertyType(const PropertyName &name) const +{ + return ValueProperty; +} + +int +TimeRulerLayer::getPropertyRangeAndValue(const PropertyName &name, + int *min, int *max) const +{ + int deft = 0; + + if (name == tr("Colour")) { + + *min = 0; + *max = 5; + + if (m_colour == Qt::black) deft = 0; + else if (m_colour == Qt::darkRed) deft = 1; + else if (m_colour == Qt::darkBlue) deft = 2; + else if (m_colour == Qt::darkGreen) deft = 3; + else if (m_colour == QColor(200, 50, 255)) deft = 4; + else if (m_colour == QColor(255, 150, 50)) deft = 5; + + } else { + + deft = Layer::getPropertyRangeAndValue(name, min, max); + } + + return deft; +} + +QString +TimeRulerLayer::getPropertyValueLabel(const PropertyName &name, + int value) const +{ + if (name == tr("Colour")) { + switch (value) { + default: + case 0: return tr("Black"); + case 1: return tr("Red"); + case 2: return tr("Blue"); + case 3: return tr("Green"); + case 4: return tr("Purple"); + case 5: return tr("Orange"); + } + } + return tr(""); +} + +void +TimeRulerLayer::setProperty(const PropertyName &name, int value) +{ + if (name == tr("Colour")) { + switch (value) { + default: + case 0: setBaseColour(Qt::black); break; + case 1: setBaseColour(Qt::darkRed); break; + case 2: setBaseColour(Qt::darkBlue); break; + case 3: setBaseColour(Qt::darkGreen); break; + case 4: setBaseColour(QColor(200, 50, 255)); break; + case 5: setBaseColour(QColor(255, 150, 50)); break; + } + } +} + +void +TimeRulerLayer::paint(QPainter &paint, QRect rect) const +{ +// std::cerr << "TimeRulerLayer::paint (" << rect.x() << "," << rect.y() +// << ") [" << rect.width() << "x" << rect.height() << "]" << std::endl; + + if (!m_model || !m_model->isOK()) return; + + int sampleRate = m_model->getSampleRate(); + if (!sampleRate) return; + + long startFrame = m_view->getStartFrame(); + long endFrame = m_view->getEndFrame(); + + int zoomLevel = m_view->getZoomLevel(); + + long rectStart = startFrame + (rect.x() - 100) * zoomLevel; + long rectEnd = startFrame + (rect.x() + rect.width() + 100) * zoomLevel; + if (rectStart < startFrame) rectStart = startFrame; + if (rectEnd > endFrame) rectEnd = endFrame; + +// std::cerr << "TimeRulerLayer::paint: calling paint.save()" << std::endl; + paint.save(); +//!!! paint.setClipRect(m_view->rect()); + + int minPixelSpacing = 50; + + RealTime rtStart = RealTime::frame2RealTime(startFrame, sampleRate); + RealTime rtEnd = RealTime::frame2RealTime(endFrame, sampleRate); +// cerr << "startFrame " << startFrame << ", endFrame " << m_view->getEndFrame() << ", rtStart " << rtStart << ", rtEnd " << rtEnd << endl; + int count = m_view->width() / minPixelSpacing; + if (count < 1) count = 1; + RealTime rtGap = (rtEnd - rtStart) / count; +// cerr << "rtGap is " << rtGap << endl; + + int incms; + bool quarter = false; + + if (rtGap.sec > 0) { + incms = 1000; + int s = rtGap.sec; + if (s > 0) { incms *= 5; s /= 5; } + if (s > 0) { incms *= 2; s /= 2; } + if (s > 0) { incms *= 6; s /= 6; quarter = true; } + if (s > 0) { incms *= 5; s /= 5; quarter = false; } + if (s > 0) { incms *= 2; s /= 2; } + if (s > 0) { incms *= 6; s /= 6; quarter = true; } + while (s > 0) { + incms *= 10; + s /= 10; + quarter = false; + } + } else { + incms = 1; + int ms = rtGap.msec(); + if (ms > 0) { incms *= 10; ms /= 10; } + if (ms > 0) { incms *= 10; ms /= 10; } + if (ms > 0) { incms *= 5; ms /= 5; } + if (ms > 0) { incms *= 2; ms /= 2; } + } +// cerr << "incms is " << incms << endl; + + RealTime rt = RealTime::frame2RealTime(rectStart, sampleRate); + long ms = rt.sec * 1000 + rt.msec(); + ms = (ms / incms) * incms - incms; + + RealTime incRt = RealTime(incms / 1000, (incms % 1000) * 1000000); + long incFrame = RealTime::realTime2Frame(incRt, sampleRate); + int incX = incFrame / zoomLevel; + int ticks = 10; + if (incX < minPixelSpacing * 2) { + ticks = quarter ? 4 : 5; + } + + QRect oldClipRect = rect; + QRect newClipRect(oldClipRect.x() - 25, oldClipRect.y(), + oldClipRect.width() + 50, oldClipRect.height()); + paint.setClipRect(newClipRect); + + QColor greyColour(m_colour); + if (m_colour == Qt::black) { + greyColour = QColor(200,200,200); + } else { + greyColour = m_colour.light(150); + } + + while (1) { + + rt = RealTime(ms / 1000, (ms % 1000) * 1000000); + ms += incms; + + long frame = RealTime::realTime2Frame(rt, sampleRate); + if (frame >= rectEnd) break; + + int x = (frame - startFrame) / zoomLevel; + if (x < rect.x() || x >= rect.x() + rect.width()) continue; + + paint.setPen(greyColour); + paint.drawLine(x, 0, x, m_view->height()); + + paint.setPen(m_colour); + paint.drawLine(x, 0, x, 5); + paint.drawLine(x, m_view->height() - 6, x, m_view->height() - 1); + + QString text(QString::fromStdString(rt.toText())); + + int y; + QFontMetrics metrics = paint.fontMetrics(); + switch (m_labelHeight) { + default: + case LabelTop: + y = 6 + metrics.ascent(); + break; + case LabelMiddle: + y = m_view->height() / 2 - metrics.height() / 2 + metrics.ascent(); + break; + case LabelBottom: + y = m_view->height() - metrics.height() + metrics.ascent() - 6; + } + + int tw = metrics.width(text); + + paint.setPen(m_view->palette().background().color()); + + //!!! simple drawing function for this please + //!!! and need getContrastingColour() in widget, or use the + //palette properly -- get the base class able to draw text + //using the proper colour (or this technique) automatically + for (int dx = -1; dx <= 1; ++dx) { + for (int dy = -1; dy <= 1; ++dy) { + if ((dx && dy) || !(dx || dy)) continue; + paint.drawText(x + 2 - tw / 2 + dx, y + dy, text); + } + } + + paint.setPen(m_colour); + paint.drawText(x + 2 - tw / 2, y, text); + + paint.setPen(greyColour); + + for (int i = 1; i < ticks; ++i) { + rt = rt + (incRt / ticks); + frame = RealTime::realTime2Frame(rt, sampleRate); + x = (frame - startFrame) / zoomLevel; + int sz = 5; + if (ticks == 10) { + if ((i % 2) == 1) { + if (i == 5) { + paint.drawLine(x, 0, x, m_view->height()); + } else sz = 3; + } else { + sz = 7; + } + } + paint.drawLine(x, 0, x, sz); + paint.drawLine(x, m_view->height() - sz - 1, x, m_view->height() - 1); + } + } + + paint.restore(); +} + + +#ifdef INCLUDE_MOCFILES +#include "TimeRulerLayer.moc.cpp" +#endif + diff -r 000000000000 -r 2a4f26e85b4c layer/TimeRulerLayer.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/layer/TimeRulerLayer.h Tue Jan 10 16:33:16 2006 +0000 @@ -0,0 +1,57 @@ +/* -*- c-basic-offset: 4 -*- vi:set ts=8 sts=4 sw=4: */ + +/* + A waveform viewer and audio annotation editor. + Chris Cannam, Queen Mary University of London, 2005 + + This is experimental software. Not for distribution. +*/ + +#ifndef _TIME_RULER_H_ +#define _TIME_RULER_H_ + +#include "base/Layer.h" + +#include +#include + +class View; +class Model; +class QPainter; + +class TimeRulerLayer : public Layer +{ + Q_OBJECT + +public: + TimeRulerLayer(View *w); + + virtual void paint(QPainter &paint, QRect rect) const; + + void setModel(Model *); + virtual const Model *getModel() const { return m_model; } + + void setBaseColour(QColor); + QColor getBaseColour() const { return m_colour; } + + enum LabelHeight { LabelTop, LabelMiddle, LabelBottom }; + void setLabelHeight(LabelHeight h) { m_labelHeight = h; } + LabelHeight getLabelHeight() const { return m_labelHeight; } + + virtual PropertyList getProperties() const; + virtual PropertyType getPropertyType(const PropertyName &) const; + virtual int getPropertyRangeAndValue(const PropertyName &, + int *min, int *max) const; + virtual QString getPropertyValueLabel(const PropertyName &, + int value) const; + virtual void setProperty(const PropertyName &, int value); + + virtual QString getPropertyContainerIconName() const { return "timeruler"; } + +protected: + Model *m_model; + QColor m_colour; + LabelHeight m_labelHeight; +}; + +#endif diff -r 000000000000 -r 2a4f26e85b4c layer/TimeValueLayer.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/layer/TimeValueLayer.cpp Tue Jan 10 16:33:16 2006 +0000 @@ -0,0 +1,378 @@ +/* -*- c-basic-offset: 4 -*- vi:set ts=8 sts=4 sw=4: */ + +/* + A waveform viewer and audio annotation editor. + Chris Cannam, Queen Mary University of London, 2005 + + This is experimental software. Not for distribution. +*/ + +#include "TimeValueLayer.h" + +#include "base/Model.h" +#include "base/RealTime.h" +#include "base/Profiler.h" +#include "base/View.h" + +#include "model/SparseTimeValueModel.h" + +#include + +#include +#include + +TimeValueLayer::TimeValueLayer(View *w) : + Layer(w), + m_model(0), + m_colour(Qt::black), + m_plotStyle(PlotLines) +{ + m_view->addLayer(this); +} + +void +TimeValueLayer::setModel(SparseTimeValueModel *model) +{ + if (m_model == model) return; + m_model = model; + + connect(m_model, SIGNAL(modelChanged()), this, SIGNAL(modelChanged())); + connect(m_model, SIGNAL(modelChanged(size_t, size_t)), + this, SIGNAL(modelChanged(size_t, size_t))); + + connect(m_model, SIGNAL(completionChanged()), + this, SIGNAL(modelCompletionChanged())); + + std::cerr << "TimeValueLayer::setModel(" << model << ")" << std::endl; + + emit modelReplaced(); +} + +Layer::PropertyList +TimeValueLayer::getProperties() const +{ + PropertyList list; + list.push_back(tr("Colour")); + list.push_back(tr("Plot Type")); + return list; +} + +Layer::PropertyType +TimeValueLayer::getPropertyType(const PropertyName &name) const +{ + return ValueProperty; +} + +int +TimeValueLayer::getPropertyRangeAndValue(const PropertyName &name, + int *min, int *max) const +{ + //!!! factor this colour handling stuff out into a colour manager class + + int deft = 0; + + if (name == tr("Colour")) { + + *min = 0; + *max = 5; + + if (m_colour == Qt::black) deft = 0; + else if (m_colour == Qt::darkRed) deft = 1; + else if (m_colour == Qt::darkBlue) deft = 2; + else if (m_colour == Qt::darkGreen) deft = 3; + else if (m_colour == QColor(200, 50, 255)) deft = 4; + else if (m_colour == QColor(255, 150, 50)) deft = 5; + + } else if (name == tr("Plot Type")) { + + *min = 0; + *max = 2; + + deft = int(m_plotStyle); + + } else { + + deft = Layer::getPropertyRangeAndValue(name, min, max); + } + + return deft; +} + +QString +TimeValueLayer::getPropertyValueLabel(const PropertyName &name, + int value) const +{ + if (name == tr("Colour")) { + switch (value) { + default: + case 0: return tr("Black"); + case 1: return tr("Red"); + case 2: return tr("Blue"); + case 3: return tr("Green"); + case 4: return tr("Purple"); + case 5: return tr("Orange"); + } + } else if (name == tr("Plot Type")) { + switch (value) { + default: + case 0: return tr("Points"); + case 1: return tr("Stems"); + case 2: return tr("Lines"); + } + } + return tr(""); +} + +void +TimeValueLayer::setProperty(const PropertyName &name, int value) +{ + if (name == tr("Colour")) { + switch (value) { + default: + case 0: setBaseColour(Qt::black); break; + case 1: setBaseColour(Qt::darkRed); break; + case 2: setBaseColour(Qt::darkBlue); break; + case 3: setBaseColour(Qt::darkGreen); break; + case 4: setBaseColour(QColor(200, 50, 255)); break; + case 5: setBaseColour(QColor(255, 150, 50)); break; + } + } else if (name == tr("Plot Type")) { + setPlotStyle(PlotStyle(value)); + } +} + +void +TimeValueLayer::setBaseColour(QColor colour) +{ + if (m_colour == colour) return; + m_colour = colour; + emit layerParametersChanged(); +} + +void +TimeValueLayer::setPlotStyle(PlotStyle style) +{ + if (m_plotStyle == style) return; + m_plotStyle = style; + emit layerParametersChanged(); +} + +bool +TimeValueLayer::isLayerScrollable() const +{ + QPoint discard; + return !m_view->shouldIlluminateLocalFeatures(this, discard); +} + +QRect +TimeValueLayer::getFeatureDescriptionRect(QPainter &paint, QPoint pos) const +{ + return QRect(0, 0, + std::max(100, paint.fontMetrics().width(tr("No local points"))), + 70); //!!! +} + +//!!! too much in common with TimeInstantLayer + +SparseTimeValueModel::PointList +TimeValueLayer::getLocalPoints(int x) const +{ + if (!m_model) return SparseTimeValueModel::PointList(); + + long startFrame = m_view->getStartFrame(); + long endFrame = m_view->getEndFrame(); + int zoomLevel = m_view->getZoomLevel(); + long frame = startFrame + x * zoomLevel; + + SparseTimeValueModel::PointList onPoints = + m_model->getPoints(frame); + + if (!onPoints.empty()) { + return onPoints; + } + + SparseTimeValueModel::PointList prevPoints = + m_model->getPreviousPoints(frame); + SparseTimeValueModel::PointList nextPoints = + m_model->getNextPoints(frame); + + SparseTimeValueModel::PointList usePoints = prevPoints; + + if (prevPoints.empty()) { + usePoints = nextPoints; + } else if (prevPoints.begin()->frame < startFrame && + !(nextPoints.begin()->frame > endFrame)) { + usePoints = nextPoints; + } else if (nextPoints.begin()->frame - frame < + frame - prevPoints.begin()->frame) { + usePoints = nextPoints; + } + + return usePoints; +} + +void +TimeValueLayer::paintLocalFeatureDescription(QPainter &paint, QRect rect, + QPoint pos) const +{ + //!!! bleagh + + int x = pos.x(); + + if (!m_model || !m_model->getSampleRate()) return; + + SparseTimeValueModel::PointList points = getLocalPoints(x); + + QFontMetrics metrics = paint.fontMetrics(); + int xbase = rect.x() + 5; + int ybase = rect.y() + 5; + + if (points.empty()) { + QString label = tr("No local points"); + if (!m_model->isReady()) { + label = tr("In progress"); + } + paint.drawText(xbase + 5, ybase + 5 + metrics.ascent(), label); + return; + } + + long useFrame = points.begin()->frame; + + RealTime rt = RealTime::frame2RealTime(useFrame, m_model->getSampleRate()); + QString timeText = QString("%1").arg(rt.toText(true).c_str()); + QString valueText = QString("%1").arg(points.begin()->value); + + int timewidth = metrics.width(timeText); + int valuewidth = metrics.width(valueText); + int labelwidth = metrics.width(points.begin()->label); + + int boxheight = metrics.height() * 3 + 4; + int boxwidth = std::max(std::max(timewidth, labelwidth), valuewidth); + + paint.drawRect(xbase, ybase, boxwidth + 10, + boxheight + 10 - metrics.descent() + 1); + + paint.drawText(xbase + 5, ybase + 5 + metrics.ascent(), timeText); + paint.drawText(xbase + 5, ybase + 7 + metrics.ascent() + metrics.height(), + valueText); + paint.drawText(xbase + 5, ybase + 9 + metrics.ascent() + 2*metrics.height(), + points.begin()->label); +} + +void +TimeValueLayer::paint(QPainter &paint, QRect rect) const +{ + if (!m_model || !m_model->isOK()) return; + + int sampleRate = m_model->getSampleRate(); + if (!sampleRate) return; + +// Profiler profiler("TimeValueLayer::paint", true); + + long startFrame = m_view->getStartFrame(); + int zoomLevel = m_view->getZoomLevel(); + + int x0 = rect.left(), x1 = rect.right(); + long frame0 = startFrame + x0 * zoomLevel; + long frame1 = startFrame + x1 * zoomLevel; + + SparseTimeValueModel::PointList points(m_model->getPoints + (frame0, frame1)); + + paint.setPen(m_colour); + + QColor brushColour(m_colour); + brushColour.setAlpha(80); + paint.setBrush(brushColour); + +// std::cerr << "TimeValueLayer::paint: resolution is " +// << m_model->getResolution() << " frames" << std::endl; + + float min = m_model->getValueMinimum(); + float max = m_model->getValueMaximum(); + if (max == min) max = min + 1.0; + + int origin = int(nearbyint(m_view->height() - + (-min * m_view->height()) / (max - min))); + + QPoint localPos; + long illuminateFrame = -1; + + if (m_view->shouldIlluminateLocalFeatures(this, localPos)) { + SparseTimeValueModel::PointList localPoints = + getLocalPoints(localPos.x()); + if (!localPoints.empty()) illuminateFrame = localPoints.begin()->frame; + } + + for (SparseTimeValueModel::PointList::const_iterator i = points.begin(); + i != points.end(); ++i) { + + const SparseTimeValueModel::Point &p(*i); + + int x = (p.frame - startFrame) / zoomLevel; + int y = int(nearbyint(m_view->height() - + ((p.value - min) * m_view->height()) / + (max - min))); + int w = m_model->getResolution() / zoomLevel; + + if (w < 1) w = 1; + + paint.setPen(m_colour); + paint.setBrush(brushColour); + + if (m_plotStyle == PlotStems) { + paint.setPen(brushColour); + if (y < origin - 1) { + paint.drawRect(x + w/2, y + 1, 1, origin - y); + } else if (y > origin + 1) { + paint.drawRect(x + w/2, origin, 1, y - origin - 1); + } + paint.setPen(m_colour); + } + + if (illuminateFrame == p.frame) { + //!!! aside from the problem of choosing a colour, it'd be + //better to save the highlighted rects and draw them at + //the end perhaps + paint.setPen(Qt::black);//!!! + paint.setBrush(Qt::black);//!!! + } + + paint.drawRect(x, y - 1, w, 2); + +// if (w > 1) { +// paint.setPen(brushColour); +// paint.drawRect(x, y - 1, w - 1, 2); +// paint.setPen(m_colour); +// } +// paint.drawLine(x, 0, x, m_view->height()); + + if (m_plotStyle == PlotLines) { + + paint.setPen(brushColour); + SparseTimeValueModel::PointList::const_iterator j = i; + ++j; + if (j != points.end()) { + const SparseTimeValueModel::Point &q(*j); + int nx = (q.frame - startFrame) / zoomLevel; + int ny = int(nearbyint(m_view->height() - + ((q.value - min) * m_view->height()) / + (max - min))); + paint.drawLine(x + w, y, nx, ny); + } + } + + +/// if (p.label != "") { +/// paint.drawText(x + 5, y - paint.fontMetrics().height() + paint.fontMetrics().ascent(), p.label); +/// } + } + + +} + + +#ifdef INCLUDE_MOCFILES +#include "TimeValueLayer.moc.cpp" +#endif + diff -r 000000000000 -r 2a4f26e85b4c layer/TimeValueLayer.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/layer/TimeValueLayer.h Tue Jan 10 16:33:16 2006 +0000 @@ -0,0 +1,68 @@ +/* -*- c-basic-offset: 4 -*- vi:set ts=8 sts=4 sw=4: */ + +/* + A waveform viewer and audio annotation editor. + Chris Cannam, Queen Mary University of London, 2005 + + This is experimental software. Not for distribution. +*/ + +#ifndef _TIME_VALUE_VIEW_H_ +#define _TIME_VALUE_VIEW_H_ + +#include "base/Layer.h" +#include "model/SparseTimeValueModel.h" + +#include +#include + +class View; +class QPainter; + +class TimeValueLayer : public Layer +{ + Q_OBJECT + +public: + TimeValueLayer(View *w); + + virtual void paint(QPainter &paint, QRect rect) const; + + virtual QRect getFeatureDescriptionRect(QPainter &, QPoint) const; + virtual void paintLocalFeatureDescription(QPainter &, QRect, QPoint) const; + + virtual const Model *getModel() const { return m_model; } + void setModel(SparseTimeValueModel *model); + + virtual PropertyList getProperties() const; + virtual PropertyType getPropertyType(const PropertyName &) const; + virtual int getPropertyRangeAndValue(const PropertyName &, + int *min, int *max) const; + virtual QString getPropertyValueLabel(const PropertyName &, + int value) const; + virtual void setProperty(const PropertyName &, int value); + + void setBaseColour(QColor); + QColor getBaseColour() const { return m_colour; } + + enum PlotStyle { PlotPoints, PlotStems, PlotLines }; + + void setPlotStyle(PlotStyle style); + PlotStyle getPlotStyle() const { return m_plotStyle; } + + virtual QString getPropertyContainerIconName() const { return "values"; } + + virtual bool isLayerScrollable() const; + + virtual int getCompletion() const { return m_model->getCompletion(); } + +protected: + SparseTimeValueModel::PointList getLocalPoints(int) const; + + SparseTimeValueModel *m_model; + QColor m_colour; + PlotStyle m_plotStyle; +}; + +#endif + diff -r 000000000000 -r 2a4f26e85b4c layer/WaveformLayer.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/layer/WaveformLayer.cpp Tue Jan 10 16:33:16 2006 +0000 @@ -0,0 +1,741 @@ +/* -*- c-basic-offset: 4 -*- vi:set ts=8 sts=4 sw=4: */ + +/* + A waveform viewer and audio annotation editor. + Chris Cannam, Queen Mary University of London, 2005 + + This is experimental software. Not for distribution. +*/ + +#include "WaveformLayer.h" + +#include "base/AudioLevel.h" +#include "base/View.h" +#include "base/Profiler.h" + +#include +#include + +#include +#include + +using std::cerr; +using std::endl; + +WaveformLayer::WaveformLayer(View *w) : + Layer(w), + m_model(0), + m_gain(1.0f), + m_colour(Qt::black), + m_showMeans(true), + m_greyscale(true), + m_channelMode(SeparateChannels), + m_channel(-1), + m_scale(LinearScale), + m_aggressive(false), + m_cache(0), + m_cacheValid(false) +{ + m_view->addLayer(this); +} + +WaveformLayer::~WaveformLayer() +{ + delete m_cache; +} + +void +WaveformLayer::setModel(const RangeSummarisableTimeValueModel *model) +{ + m_model = model; + m_cacheValid = false; + if (!m_model || !m_model->isOK()) return; + + connect(m_model, SIGNAL(modelChanged()), this, SIGNAL(modelChanged())); + connect(m_model, SIGNAL(modelChanged(size_t, size_t)), + this, SIGNAL(modelChanged(size_t, size_t))); + + connect(m_model, SIGNAL(completionChanged()), + this, SIGNAL(modelCompletionChanged())); + + emit modelReplaced(); +} + +Layer::PropertyList +WaveformLayer::getProperties() const +{ + PropertyList list; + list.push_back(tr("Colour")); + list.push_back(tr("Scale")); + list.push_back(tr("Gain")); + list.push_back(tr("Merge Channels")); + return list; +} + +Layer::PropertyType +WaveformLayer::getPropertyType(const PropertyName &name) const +{ + if (name == tr("Gain")) return RangeProperty; + if (name == tr("Colour")) return ValueProperty; + if (name == tr("Merge Channels")) return ToggleProperty; + if (name == tr("Scale")) return ValueProperty; + return InvalidProperty; +} + +QString +WaveformLayer::getPropertyGroupName(const PropertyName &name) const +{ + if (name == tr("Gain") || + name == tr("Scale")) return tr("Scale"); + return QString(); +} + +int +WaveformLayer::getPropertyRangeAndValue(const PropertyName &name, + int *min, int *max) const +{ + int deft = 0; + + if (name == tr("Gain")) { + + *min = -50; + *max = 50; + + deft = int(nearbyint(log10(m_gain) * 20.0)); + if (deft < *min) deft = *min; + if (deft > *max) deft = *max; + + } else if (name == tr("Colour")) { + + *min = 0; + *max = 5; + + if (m_colour == Qt::black) deft = 0; + else if (m_colour == Qt::darkRed) deft = 1; + else if (m_colour == Qt::darkBlue) deft = 2; + else if (m_colour == Qt::darkGreen) deft = 3; + else if (m_colour == QColor(200, 50, 255)) deft = 4; + else if (m_colour == QColor(255, 150, 50)) deft = 5; + + } else if (name == tr("Merge Channels")) { + + deft = ((m_channelMode == MergeChannels) ? 1 : 0); + + } else if (name == tr("Scale")) { + + *min = 0; + *max = 2; + + deft = (int)m_scale; + + } else { + deft = Layer::getPropertyRangeAndValue(name, min, max); + } + + return deft; +} + +QString +WaveformLayer::getPropertyValueLabel(const PropertyName &name, + int value) const +{ + if (name == tr("Colour")) { + switch (value) { + default: + case 0: return tr("Black"); + case 1: return tr("Red"); + case 2: return tr("Blue"); + case 3: return tr("Green"); + case 4: return tr("Purple"); + case 5: return tr("Orange"); + } + } + if (name == tr("Scale")) { + switch (value) { + default: + case 0: return tr("Linear"); + case 1: return tr("Meter"); + case 2: return tr("dB"); + } + } + return tr(""); +} + +void +WaveformLayer::setProperty(const PropertyName &name, int value) +{ + if (name == tr("Gain")) { + setGain(pow(10, float(value)/20.0)); + } else if (name == tr("Colour")) { + switch (value) { + default: + case 0: setBaseColour(Qt::black); break; + case 1: setBaseColour(Qt::darkRed); break; + case 2: setBaseColour(Qt::darkBlue); break; + case 3: setBaseColour(Qt::darkGreen); break; + case 4: setBaseColour(QColor(200, 50, 255)); break; + case 5: setBaseColour(QColor(255, 150, 50)); break; + } + } else if (name == tr("Merge Channels")) { + setChannelMode(value ? MergeChannels : SeparateChannels); + } else if (name == tr("Scale")) { + switch (value) { + default: + case 0: setScale(LinearScale); break; + case 1: setScale(MeterScale); break; + case 2: setScale(dBScale); break; + } + } +} + +/* + +int +WaveformLayer::getProperty(const PropertyName &name) +{ + if (name == "Gain") { + return int((getGain() - 1.0) * 10.0 + 0.01); + } + if (name == "Colour") { + +*/ + +void +WaveformLayer::setGain(float gain) //!!! inadequate for floats! +{ + if (m_gain == gain) return; + m_gain = gain; + m_cacheValid = false; + emit layerParametersChanged(); +} + +void +WaveformLayer::setBaseColour(QColor colour) +{ + if (m_colour == colour) return; + m_colour = colour; + m_cacheValid = false; + emit layerParametersChanged(); +} + +void +WaveformLayer::setShowMeans(bool showMeans) +{ + if (m_showMeans == showMeans) return; + m_showMeans = showMeans; + m_cacheValid = false; + emit layerParametersChanged(); +} + +void +WaveformLayer::setUseGreyscale(bool useGreyscale) +{ + if (m_greyscale == useGreyscale) return; + m_greyscale = useGreyscale; + m_cacheValid = false; + emit layerParametersChanged(); +} + +void +WaveformLayer::setChannelMode(ChannelMode channelMode) +{ + if (m_channelMode == channelMode) return; + m_channelMode = channelMode; + m_cacheValid = false; + emit layerParametersChanged(); +} + +void +WaveformLayer::setChannel(int channel) +{ + std::cerr << "WaveformLayer::setChannel(" << channel << ")" << std::endl; + + if (m_channel == channel) return; + m_channel = channel; + m_cacheValid = false; + emit layerParametersChanged(); +} + +void +WaveformLayer::setScale(Scale scale) +{ + if (m_scale == scale) return; + m_scale = scale; + m_cacheValid = false; + emit layerParametersChanged(); +} + +void +WaveformLayer::setAggressiveCacheing(bool aggressive) +{ + if (m_aggressive == aggressive) return; + m_aggressive = aggressive; + m_cacheValid = false; + emit layerParametersChanged(); +} + +int +WaveformLayer::getCompletion() const +{ + int completion = 100; + if (!m_model || !m_model->isOK()) return completion; + if (m_model->isReady(&completion)) return 100; + return completion; +} + +int +WaveformLayer::dBscale(float sample, int m) const +{ + if (sample < 0.0) return -dBscale(-sample, m); + float dB = AudioLevel::multiplier_to_dB(sample); + if (dB < -50.0) return 0; + if (dB > 0.0) return m; + return int(((dB + 50.0) * m) / 50.0 + 0.1); +} + +size_t +WaveformLayer::getChannelArrangement(size_t &min, size_t &max, bool &merging) + const +{ + if (!m_model || !m_model->isOK()) return 0; + + size_t channels = m_model->getChannelCount(); + if (channels == 0) return 0; + + size_t rawChannels = channels; + + if (m_channel == -1) { + min = 0; + if (m_channelMode == MergeChannels) { + max = 0; + channels = 1; + } else { + max = channels - 1; + } + } else { + min = m_channel; + max = m_channel; + rawChannels = 1; + channels = 1; + } + + merging = (m_channelMode == MergeChannels && rawChannels > 1); + +// std::cerr << "WaveformLayer::getChannelArrangement: min " << min << ", max " << max << ", merging " << merging << ", channels " << channels << std::endl; + + return channels; +} + +void +WaveformLayer::paint(QPainter &viewPainter, QRect rect) const +{ + if (!m_model || !m_model->isOK()) { + return; + } + + long startFrame = m_view->getStartFrame(); + int zoomLevel = m_view->getZoomLevel(); + + + Profiler profiler("WaveformLayer::paint", true); + std::cerr << "WaveformLayer::paint (" << rect.x() << "," << rect.y() + << ") [" << rect.width() << "x" << rect.height() << "]: zoom " << zoomLevel << ", start " << startFrame << std::endl; + + + size_t channels = 0, minChannel = 0, maxChannel = 0; + bool mergingChannels = false; + + channels = getChannelArrangement(minChannel, maxChannel, mergingChannels); + if (channels == 0) return; + + int w = m_view->width(); + int h = m_view->height(); + + bool ready = m_model->isReady(); + QPainter *paint; + + if (m_aggressive) { + + if (m_cacheValid && (zoomLevel != m_cacheZoomLevel)) { + m_cacheValid = false; + } + + if (m_cacheValid) { + viewPainter.drawPixmap(rect, *m_cache, rect); + return; + } + + if (!m_cache || m_cache->width() != w || m_cache->height() != h) { + delete m_cache; + m_cache = new QPixmap(w, h); + } + + paint = new QPainter(m_cache); + + paint->setPen(Qt::NoPen); + paint->setBrush(m_view->palette().background()); + paint->drawRect(rect); + + paint->setPen(Qt::black); + paint->setBrush(Qt::NoBrush); + + } else { + paint = &viewPainter; + } + + int x0 = 0, x1 = w - 1; + int y0 = 0, y1 = h - 1; + + x0 = rect.left(); + x1 = rect.right(); + y0 = rect.top(); + y1 = rect.bottom(); + + long frame0 = startFrame + x0 * zoomLevel; + long frame1 = startFrame + (x1 + 1) * zoomLevel; + +// std::cerr << "Painting waveform from " << frame0 << " to " << frame1 << " (" << (x1-x0+1) << " pixels at zoom " << zoomLevel << ")" << std::endl; + + RangeSummarisableTimeValueModel::RangeBlock ranges; + RangeSummarisableTimeValueModel::RangeBlock otherChannelRanges; + RangeSummarisableTimeValueModel::Range range; + + QColor greys[3]; + if (m_colour == Qt::black) { + for (int i = 0; i < 3; ++i) { + int level = 192 - 64 * i; + greys[i] = QColor(level, level, level); + } + } else { + int factor = (m_view->hasLightBackground() ? 120 : 80); + greys[2] = m_colour.light(factor); + greys[1] = greys[2].light(factor); + greys[0] = greys[1].light(factor); + } + + QColor midColour = m_colour; + if (midColour == Qt::black) { + midColour = Qt::gray; + } else if (m_view->hasLightBackground()) { + midColour = midColour.light(150); + } else { + midColour = midColour.light(50); + } + + for (size_t ch = minChannel; ch <= maxChannel; ++ch) { + + int prevRangeBottom = -1, prevRangeTop = -1; + + int m = (h / channels) / 2; + int my = m + (((ch - minChannel) * h) / channels); + +// std::cerr << "ch = " << ch << ", channels = " << channels << ", m = " << m << ", my = " << my << ", h = " << h << std::endl; + + if (my - m > y1 || my + m < y0) continue; + + paint->setPen(greys[0]); + paint->drawLine(x0, my, x1, my); + + if (frame1 <= 0) continue; + + size_t modelZoomLevel = zoomLevel; + + ranges = m_model->getRanges + (ch, frame0 < 0 ? 0 : frame0, frame1, modelZoomLevel); + + if (mergingChannels) { + otherChannelRanges = m_model->getRanges + (1, frame0 < 0 ? 0 : frame0, frame1, modelZoomLevel); + } + + for (int x = x0; x <= x1; ++x) { + + range = RangeSummarisableTimeValueModel::Range(); + size_t index = x - x0; + size_t maxIndex = index; + + if (frame0 < 0) { + if (index < size_t(-frame0 / zoomLevel)) { + continue; + } else { + index -= -frame0 / zoomLevel; + maxIndex = index; + } + } + + if (int(modelZoomLevel) != zoomLevel) { + + index = size_t((double(index) * zoomLevel) / modelZoomLevel); + + if (int(modelZoomLevel) < zoomLevel) { + // Peaks may be missed! The model should avoid + // this by rounding zoom levels up rather than + // down, but we'd better cope in case it doesn't + maxIndex = index; + } else { + maxIndex = size_t((double(index + 1) * zoomLevel) + / modelZoomLevel) - 1; + } + } + + if (index < ranges.size()) { + + range = ranges[index]; + + if (maxIndex > index && maxIndex < ranges.size()) { + range.max = std::max(range.max, ranges[maxIndex].max); + range.min = std::min(range.min, ranges[maxIndex].min); + range.absmean = (range.absmean + + ranges[maxIndex].absmean) / 2; + } + + } else { + continue; + } + + int rangeBottom = 0, rangeTop = 0, meanBottom = 0, meanTop = 0; + + if (mergingChannels) { + + if (index < otherChannelRanges.size()) { + + range.max = fabsf(range.max); + range.min = -fabsf(otherChannelRanges[index].max); + range.absmean = (range.absmean + + otherChannelRanges[index].absmean) / 2; + + if (maxIndex > index && maxIndex < ranges.size()) { + // let's not concern ourselves about the mean + range.min = std::min + (range.min, + -fabsf(otherChannelRanges[maxIndex].max)); + } + } + } + + int greyLevels = 1; + if (m_greyscale && (m_scale == LinearScale)) greyLevels = 4; + + switch (m_scale) { + + case LinearScale: + rangeBottom = int( m * greyLevels * range.min * m_gain); + rangeTop = int( m * greyLevels * range.max * m_gain); + meanBottom = int(-m * range.absmean * m_gain); + meanTop = int( m * range.absmean * m_gain); + break; + + case dBScale: + rangeBottom = dBscale(range.min * m_gain, m * greyLevels); + rangeTop = dBscale(range.max * m_gain, m * greyLevels); + meanBottom = -dBscale(range.absmean * m_gain, m); + meanTop = dBscale(range.absmean * m_gain, m); + break; + + case MeterScale: + rangeBottom = AudioLevel::multiplier_to_preview(range.min * m_gain, m * greyLevels); + rangeTop = AudioLevel::multiplier_to_preview(range.max * m_gain, m * greyLevels); + meanBottom = -AudioLevel::multiplier_to_preview(range.absmean * m_gain, m); + meanTop = AudioLevel::multiplier_to_preview(range.absmean * m_gain, m); + } + + int topFill = (rangeTop < 0 ? -rangeTop : rangeTop) % greyLevels; + int bottomFill = (rangeBottom < 0 ? -rangeBottom : rangeBottom) % greyLevels; + rangeTop = rangeTop / greyLevels; + rangeBottom = rangeBottom / greyLevels; + + bool clipped = false; + if (rangeTop < -m) { rangeTop = -m; clipped = true; } + if (rangeTop > m) { rangeTop = m; clipped = true; } + if (rangeBottom < -m) { rangeBottom = -m; clipped = true; } + if (rangeBottom > m) { rangeBottom = m; clipped = true; } + + rangeBottom = my - rangeBottom; + rangeTop = my - rangeTop; + meanBottom = my - meanBottom; + meanTop = my - meanTop; + + if (meanBottom > rangeBottom) meanBottom = rangeBottom; + if (meanTop < rangeTop) meanTop = rangeTop; + + bool drawMean = m_showMeans; + if (meanTop == rangeTop) { + if (meanTop < meanBottom) ++meanTop; + else drawMean = false; + } + if (meanBottom == rangeBottom) { + if (meanBottom > meanTop) --meanBottom; + else drawMean = false; + } + + if (x != x0 && prevRangeBottom != -1) { + if (prevRangeBottom > rangeBottom && + prevRangeTop > rangeBottom) { + paint->setPen(midColour); + paint->drawLine(x-1, prevRangeTop, x, rangeBottom); + paint->setPen(m_colour); + paint->drawPoint(x-1, prevRangeTop); + } else if (prevRangeBottom < rangeTop && + prevRangeTop < rangeTop) { + paint->setPen(midColour); + paint->drawLine(x-1, prevRangeBottom, x, rangeTop); + paint->setPen(m_colour); + paint->drawPoint(x-1, prevRangeBottom); + } + } + + if (ready) { + if (clipped || + range.min * m_gain <= -1.0 || + range.max * m_gain >= 1.0) { + paint->setPen(Qt::red); + } else { + paint->setPen(m_colour); + } + } else { + paint->setPen(midColour); + } + + paint->drawLine(x, rangeBottom, x, rangeTop); + + if (m_greyscale && (m_scale == LinearScale) && ready) { + if (!clipped) { + if (rangeTop < rangeBottom) { + if (topFill > 0 && + (!drawMean || (rangeTop < meanTop - 1))) { + paint->setPen(greys[topFill - 1]); + paint->drawPoint(x, rangeTop - 1); + } + if (bottomFill > 0 && + (!drawMean || (rangeBottom > meanBottom + 1))) { + paint->setPen(greys[bottomFill - 1]); + paint->drawPoint(x, rangeBottom + 1); + } + } + } + } + + if (drawMean) { + paint->setPen(midColour); + paint->drawLine(x, meanBottom, x, meanTop); + } + + prevRangeBottom = rangeBottom; + prevRangeTop = rangeTop; + } + } + + if (m_aggressive) { + if (ready && rect == m_view->rect()) { + m_cacheValid = true; + m_cacheZoomLevel = zoomLevel; + } + paint->end(); + delete paint; + viewPainter.drawPixmap(rect, *m_cache, rect); + } +} + +int +WaveformLayer::getVerticalScaleWidth(QPainter &paint) const +{ + if (m_scale == LinearScale) { + return paint.fontMetrics().width("0.0") + 13; + } else { + return std::max(paint.fontMetrics().width(tr("0dB")), + paint.fontMetrics().width(tr("-Inf"))) + 13; + } +} + +void +WaveformLayer::paintVerticalScale(QPainter &paint, QRect rect) const +{ + if (!m_model || !m_model->isOK()) { + return; + } + + size_t channels = 0, minChannel = 0, maxChannel = 0; + bool mergingChannels = false; + + channels = getChannelArrangement(minChannel, maxChannel, mergingChannels); + if (channels == 0) return; + + int h = rect.height(), w = rect.width(); + int textHeight = paint.fontMetrics().height(); + int toff = -textHeight/2 + paint.fontMetrics().ascent() + 1; + + for (size_t ch = minChannel; ch <= maxChannel; ++ch) { + + int m = (h / channels) / 2; + int my = m + (((ch - minChannel) * h) / channels); + int py = -1; + + for (int i = 0; i <= 10; ++i) { + + int vy = 0; + QString text = ""; + + if (m_scale == LinearScale) { + + vy = int((m * i * m_gain) / 10); + + text = QString("%1").arg(float(i) / 10.0); + if (i == 0) text = "0.0"; + if (i == 10) text = "1.0"; + + } else { + + int db; + bool minvalue = false; + + if (m_scale == MeterScale) { + static int dbs[] = { -50, -40, -30, -20, -15, + -10, -5, -3, -2, -1, 0 }; + db = dbs[i]; + if (db == -50) minvalue = true; + vy = AudioLevel::multiplier_to_preview + (AudioLevel::dB_to_multiplier(db) * m_gain, m); + } else { + db = -100 + i * 10; + if (db == -100) minvalue = true; + vy = dBscale + (AudioLevel::dB_to_multiplier(db) * m_gain, m); + } + + text = QString("%1").arg(db); + if (db == 0) text = tr("0dB"); + if (minvalue) { + text = tr("-Inf"); + vy = 0; + } + } + + if (vy < 0) vy = -vy; + if (vy >= m - 1) continue; + + if (py >= 0 && (vy - py) < textHeight - 1) { + paint.drawLine(w - 4, my - vy, w, my - vy); + if (vy > 0) paint.drawLine(w - 4, my + vy, w, my + vy); + continue; + } + + paint.drawLine(w - 7, my - vy, w, my - vy); + if (vy > 0) paint.drawLine(w - 7, my + vy, w, my + vy); + + int tx = 3; + if (m_scale != LinearScale) { + tx = w - 10 - paint.fontMetrics().width(text); + } + + paint.drawText(tx, my - vy + toff, text); + if (vy > 0) paint.drawText(tx, my + vy + toff, text); + + py = vy; + } + } +} + +#ifdef INCLUDE_MOCFILES +#include "WaveformLayer.moc.cpp" +#endif + diff -r 000000000000 -r 2a4f26e85b4c layer/WaveformLayer.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/layer/WaveformLayer.h Tue Jan 10 16:33:16 2006 +0000 @@ -0,0 +1,182 @@ +/* -*- c-basic-offset: 4 -*- vi:set ts=8 sts=4 sw=4: */ + +/* + A waveform viewer and audio annotation editor. + Chris Cannam, Queen Mary University of London, 2005 + + This is experimental software. Not for distribution. +*/ + +#ifndef _WAVEFORM_VIEW_H_ +#define _WAVEFORM_VIEW_H_ + +#include +#include + +#include "base/Layer.h" + +#include "model/RangeSummarisableTimeValueModel.h" + +class View; +class QPainter; +class QPixmap; + +class WaveformLayer : public Layer +{ + Q_OBJECT + +public: + WaveformLayer(View *w); + ~WaveformLayer(); + + virtual const ZoomConstraint *getZoomConstraint() const { return m_model; } + virtual const Model *getModel() const { return m_model; } + virtual void paint(QPainter &paint, QRect rect) const; + + virtual int getVerticalScaleWidth(QPainter &) const; + virtual void paintVerticalScale(QPainter &paint, QRect rect) const; + + void setModel(const RangeSummarisableTimeValueModel *model); + + virtual PropertyList getProperties() const; + virtual PropertyType getPropertyType(const PropertyName &) const; + virtual QString getPropertyGroupName(const PropertyName &) const; + virtual int getPropertyRangeAndValue(const PropertyName &, + int *min, int *max) const; + virtual QString getPropertyValueLabel(const PropertyName &, + int value) const; + virtual void setProperty(const PropertyName &, int value); + + /** + * Set the gain multiplier for sample values in this view. + * + * The default is 1.0. + */ + void setGain(float gain); + float getGain() const { return m_gain; } + + /** + * Set the basic display colour for waveforms. + * + * The default is black. + *!!! NB should default to white if the associated View !hasLightBackground() + */ + void setBaseColour(QColor); + QColor getBaseColour() const { return m_colour; } + + /** + * Set whether to display mean values as a lighter-coloured area + * beneath the peaks. Rendering will be slightly faster without + * but arguably prettier with. + * + * The default is to display means. + */ + void setShowMeans(bool); + bool getShowMeans() const { return m_showMeans; } + + /** + * Set whether to use shades of grey (or of the base colour) to + * provide additional perceived vertical resolution (i.e. using + * half-filled pixels to represent levels that only just meet the + * pixel unit boundary). This provides a small improvement in + * waveform quality at a small cost in rendering speed. + * + * The default is to use greyscale. + */ + void setUseGreyscale(bool); + bool getUseGreyscale() const { return m_greyscale; } + + + enum ChannelMode { SeparateChannels, MergeChannels }; + + /** + * Specify whether multi-channel audio data should be displayed + * with a separate axis per channel (SeparateChannels), or with a + * single synthetic axis showing channel 0 above the axis and + * channel 1 below (MergeChannels). + * + * MergeChannels does not work for files with more than 2 + * channels. + * + * The default is SeparateChannels. + */ + void setChannelMode(ChannelMode); + ChannelMode getChannelMode() const { return m_channelMode; } + + + /** + * Specify the channel to use from the source model. A value of + * -1 means to show all available channels (laid out to the + * channel mode). The default is -1. + */ + void setChannel(int); + int getChannel() const { return m_channel; } + + + enum Scale { LinearScale, MeterScale, dBScale }; + + /** + * Specify the vertical scale for sample levels. With LinearScale, + * the scale is directly proportional to the raw [-1, +1) + * floating-point audio sample values. With dBScale the + * vertical scale is proportional to dB level (truncated at + * -50dB). MeterScale provides a hybrid variable scale based on + * IEC meter scale, intended to provide a clear overview at + * relatively small heights. + * + * Note that the effective gain (see setGain()) is applied before + * vertical scaling. + * + * The default is LinearScale. + */ + void setScale(Scale); + Scale getScale() const { return m_scale; } + + /** + * Enable or disable aggressive pixmap cacheing. If enabled, + * waveforms will be rendered to an off-screen pixmap and + * refreshed from there instead of being redrawn from the peak + * data each time. This may be faster if the data and zoom level + * do not change often, but it may be slower for frequently zoomed + * data and it will only work if the waveform is the "bottom" + * layer on the displayed widget, as each refresh will erase + * anything beneath the waveform. + * + * This is intended specifically for a panner widget display in + * which the waveform never moves, zooms, or changes, but some + * graphic such as a panner outline is frequently redrawn over the + * waveform. This situation would necessitate a lot of waveform + * refresh if the default cacheing strategy was used. + * + * The default is not to use aggressive cacheing. + */ + void setAggressiveCacheing(bool); + bool getAggressiveCacheing() const { return m_aggressive; } + + virtual int getCompletion() const; + + virtual QString getPropertyContainerIconName() const { return "waveform"; } + +protected: + int dBscale(float sample, int m) const; + + const RangeSummarisableTimeValueModel *m_model; // I do not own this + + /// Return value is number of channels displayed + size_t getChannelArrangement(size_t &min, size_t &max, bool &merging) const; + + float m_gain; + QColor m_colour; + bool m_showMeans; + bool m_greyscale; + ChannelMode m_channelMode; + int m_channel; + Scale m_scale; + bool m_aggressive; + + mutable QPixmap *m_cache; + mutable bool m_cacheValid; + mutable int m_cacheZoomLevel; +}; + +#endif diff -r 000000000000 -r 2a4f26e85b4c widgets/AudioDial.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/widgets/AudioDial.cpp Tue Jan 10 16:33:16 2006 +0000 @@ -0,0 +1,341 @@ +/* -*- c-basic-offset: 4 -*- vi:set ts=8 sts=4 sw=4: */ + +/* + A waveform viewer and audio annotation editor. + Chris Cannam, Queen Mary University of London, 2005 + + This is experimental software. Not for distribution. +*/ + +/** + * A rotary dial widget. + * + * Based on an original design by Thorsten Wilms. + * + * Implemented as a widget for the Rosegarden MIDI and audio sequencer + * and notation editor by Chris Cannam. + * + * Extracted into a standalone Qt3 widget by Pedro Lopez-Cabanillas + * and adapted for use in QSynth. + * + * Ported to Qt4 by Chris Cannam. + * + * This file copyright 2003-2005 Chris Cannam, copyright 2005 Pedro + * Lopez-Cabanillas. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. See the file + * COPYING included with this distribution for more information. + */ + +#include "AudioDial.h" + +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +using std::endl; +using std::cerr; + + +//!!! Pedro updated his version to use my up/down response code from RG -- need to grab that code in preference to this version from Rui + + +//------------------------------------------------------------------------- +// AudioDial - Instance knob widget class. +// + +#define AUDIO_DIAL_MIN (0.25 * M_PI) +#define AUDIO_DIAL_MAX (1.75 * M_PI) +#define AUDIO_DIAL_RANGE (AUDIO_DIAL_MAX - AUDIO_DIAL_MIN) + + +// Constructor. +AudioDial::AudioDial(QWidget *parent) : + QDial(parent), + m_knobColor(Qt::black), m_meterColor(Qt::white) +{ + m_mouseDial = false; + m_mousePressed = false; +} + + +// Destructor. +AudioDial::~AudioDial (void) +{ +} + + +void AudioDial::paintEvent(QPaintEvent *) +{ + QPainter paint; + + float angle = AUDIO_DIAL_MIN // offset + + (AUDIO_DIAL_RANGE * + (float(QDial::value() - QDial::minimum()) / + (float(QDial::maximum() - QDial::minimum())))); + int degrees = int(angle * 180.0 / M_PI); + + int ns = notchSize(); + int numTicks = 1 + (maximum() + ns - minimum()) / ns; + + QColor knobColor(m_knobColor); + if (knobColor == Qt::black) + knobColor = palette().mid().color(); + + QColor meterColor(m_meterColor); + if (!isEnabled()) + meterColor = palette().mid().color(); + else if (m_meterColor == Qt::white) + meterColor = palette().highlight().color(); + + int m_size = width() < height() ? width() : height(); + int scale = 1; + int width = m_size - 2*scale, height = m_size - 2*scale; + + paint.begin(this); + paint.setRenderHint(QPainter::Antialiasing, true); + paint.translate(1, 1); + + QPen pen; + QColor c; + + // Knob body and face... + + c = knobColor; + pen.setColor(knobColor); + pen.setWidth(scale * 2); + pen.setCapStyle(Qt::FlatCap); + + paint.setPen(pen); + paint.setBrush(c); + + int indent = (int)(width * 0.15 + 1); + + paint.drawEllipse(indent-1, indent-1, width-2*indent, width-2*indent); + + pen.setWidth(3 * scale); + int pos = indent-1 + (width-2*indent) / 20; + int darkWidth = (width-2*indent) * 3 / 4; + while (darkWidth) { + c = c.light(102); + pen.setColor(c); + paint.setPen(pen); + paint.drawEllipse(pos, pos, darkWidth, darkWidth); + if (!--darkWidth) break; + paint.drawEllipse(pos, pos, darkWidth, darkWidth); + if (!--darkWidth) break; + paint.drawEllipse(pos, pos, darkWidth, darkWidth); + ++pos; --darkWidth; + } + + // Tick notches... + + if ( true/* notchesVisible() */) { +// std::cerr << "Notches visible" << std::endl; + pen.setColor(palette().dark().color()); + pen.setWidth(scale); + paint.setPen(pen); + for (int i = 0; i < numTicks; ++i) { + int div = numTicks; + if (div > 1) --div; + drawTick(paint, AUDIO_DIAL_MIN + (AUDIO_DIAL_MAX - AUDIO_DIAL_MIN) * i / div, + width, true); + } + } + + // The bright metering bit... + + c = meterColor; + pen.setColor(c); + pen.setWidth(indent); + paint.setPen(pen); + +// std::cerr << "degrees " << degrees << ", gives us " << -(degrees - 45) * 16 << std::endl; + + int arcLen = -(degrees - 45) * 16; + if (arcLen == 0) arcLen = -16; + + paint.drawArc(indent/2, indent/2, + width-indent, width-indent, (180 + 45) * 16, arcLen); + + paint.setBrush(Qt::NoBrush); + + // Shadowing... + + pen.setWidth(scale); + paint.setPen(pen); + + // Knob shadow... + + int shadowAngle = -720; + c = knobColor.dark(); + for (int arc = 120; arc < 2880; arc += 240) { + pen.setColor(c); + paint.setPen(pen); + paint.drawArc(indent, indent, + width-2*indent, width-2*indent, shadowAngle + arc, 240); + paint.drawArc(indent, indent, + width-2*indent, width-2*indent, shadowAngle - arc, 240); + c = c.light(110); + } + + // Scale shadow... + + shadowAngle = 2160; + c = palette().dark().color(); + for (int arc = 120; arc < 2880; arc += 240) { + pen.setColor(c); + paint.setPen(pen); + paint.drawArc(scale/2, scale/2, + width-scale, width-scale, shadowAngle + arc, 240); + paint.drawArc(scale/2, scale/2, + width-scale, width-scale, shadowAngle - arc, 240); + c = c.light(108); + } + + // Undraw the bottom part... + + pen.setColor(palette().background().color()); + pen.setWidth(scale * 4); + paint.setPen(pen); + paint.drawArc(scale/2, scale/2, + width-scale, width-scale, -45 * 16, -92 * 16); + + // Scale ends... + + pen.setColor(palette().dark().color()); + pen.setWidth(scale); + paint.setPen(pen); + for (int i = 0; i < numTicks; ++i) { + if (i != 0 && i != numTicks - 1) continue; + int div = numTicks; + if (div > 1) --div; + drawTick(paint, AUDIO_DIAL_MIN + (AUDIO_DIAL_MAX - AUDIO_DIAL_MIN) * i / div, + width, false); + } + + // Pointer notch... + + float hyp = float(width) / 2.0; + float len = hyp - indent; + --len; + + float x0 = hyp; + float y0 = hyp; + + float x = hyp - len * sin(angle); + float y = hyp + len * cos(angle); + + c = palette().dark().color(); + pen.setColor(isEnabled() ? c.dark(130) : c); + pen.setWidth(scale * 2); + paint.setPen(pen); + paint.drawLine(int(x0), int(y0), int(x), int(y)); + + paint.end(); +} + + +void AudioDial::drawTick(QPainter &paint, + float angle, int size, bool internal) +{ + float hyp = float(size) / 2.0; + float x0 = hyp - (hyp - 1) * sin(angle); + float y0 = hyp + (hyp - 1) * cos(angle); + +// cerr << "drawTick: angle " << angle << ", size " << size << ", internal " << internal << endl; + + if (internal) { + + float len = hyp / 4; + float x1 = hyp - (hyp - len) * sin(angle); + float y1 = hyp + (hyp - len) * cos(angle); + + paint.drawLine(int(x0), int(y0), int(x1), int(y1)); + + } else { + + float len = hyp / 4; + float x1 = hyp - (hyp + len) * sin(angle); + float y1 = hyp + (hyp + len) * cos(angle); + + paint.drawLine(int(x0), int(y0), int(x1), int(y1)); + } +} + + +void AudioDial::setKnobColor(const QColor& color) +{ + m_knobColor = color; + update(); +} + + +void AudioDial::setMeterColor(const QColor& color) +{ + m_meterColor = color; + update(); +} + + +void AudioDial::setMouseDial(bool mouseDial) +{ + m_mouseDial = mouseDial; +} + + +// Alternate mouse behavior event handlers. +void AudioDial::mousePressEvent(QMouseEvent *mouseEvent) +{ + if (m_mouseDial) { + QDial::mousePressEvent(mouseEvent); + } else if (mouseEvent->button() == Qt::LeftButton) { + m_mousePressed = true; + m_posMouse = mouseEvent->pos(); + } +} + + +void AudioDial::mouseMoveEvent(QMouseEvent *mouseEvent) +{ + if (m_mouseDial) { + QDial::mouseMoveEvent(mouseEvent); + } else if (m_mousePressed) { + const QPoint& posMouse = mouseEvent->pos(); + int v = QDial::value() + + (posMouse.x() - m_posMouse.x()) + + (m_posMouse.y() - posMouse.y()); + if (v > QDial::maximum()) + v = QDial::maximum(); + else + if (v < QDial::minimum()) + v = QDial::minimum(); + m_posMouse = posMouse; + QDial::setValue(v); + } +} + + +void AudioDial::mouseReleaseEvent(QMouseEvent *mouseEvent) +{ + if (m_mouseDial) { + QDial::mouseReleaseEvent(mouseEvent); + } else if (m_mousePressed) { + m_mousePressed = false; + } +} + +#ifdef INCLUDE_MOCFILES +#include "AudioDial.moc.cpp" +#endif + diff -r 000000000000 -r 2a4f26e85b4c widgets/AudioDial.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/widgets/AudioDial.h Tue Jan 10 16:33:16 2006 +0000 @@ -0,0 +1,109 @@ +/* -*- c-basic-offset: 4 -*- vi:set ts=8 sts=4 sw=4: */ + +/* + A waveform viewer and audio annotation editor. + Chris Cannam, Queen Mary University of London, 2005 + + This is experimental software. Not for distribution. +*/ + +#ifndef _AUDIO_DIAL_H_ +#define _AUDIO_DIAL_H_ + +/** + * A rotary dial widget. + * + * Based on an original design by Thorsten Wilms. + * + * Implemented as a widget for the Rosegarden MIDI and audio sequencer + * and notation editor by Chris Cannam. + * + * Extracted into a standalone Qt3 widget by Pedro Lopez-Cabanillas + * and adapted for use in QSynth. + * + * Ported to Qt4 by Chris Cannam. + * + * This file copyright 2003-2005 Chris Cannam, copyright 2005 Pedro + * Lopez-Cabanillas. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. See the file + * COPYING included with this distribution for more information. + */ + +#include +#include + +/** + * AudioDial is a nicer-looking QDial that by default reacts to mouse + * movement on horizontal and vertical axes instead of in a radial + * motion. Move the mouse up or right to increment the value, down or + * left to decrement it. AudioDial also responds to the mouse wheel. + * + * The programming interface for this widget is compatible with QDial, + * with the addition of properties for the knob colour and meter + * colour and a boolean property mouseDial that determines whether to + * respond to radial mouse motion in the same way as QDial (the + * default is no). + */ + +class AudioDial : public QDial +{ + Q_OBJECT + Q_PROPERTY( QColor knobColor READ getKnobColor WRITE setKnobColor ) + Q_PROPERTY( QColor meterColor READ getMeterColor WRITE setMeterColor ) + Q_PROPERTY( bool mouseDial READ getMouseDial WRITE setMouseDial ) + +public: + AudioDial(QWidget *parent = 0); + ~AudioDial(); + + const QColor& getKnobColor() const { return m_knobColor; } + const QColor& getMeterColor() const { return m_meterColor; } + bool getMouseDial() const { return m_mouseDial; } + +public slots: + /** + * Set the colour of the knob. The default is to inherit the + * colour from the widget's palette. + */ + void setKnobColor(const QColor &color); + + /** + * Set the colour of the meter (the highlighted area around the + * knob that shows the current value). The default is to inherit + * the colour from the widget's palette. + */ + void setMeterColor(const QColor &color); + + /** + * Specify that the dial should respond to radial mouse movements + * in the same way as QDial. + */ + void setMouseDial(bool mouseDial); + +protected: + void drawTick(QPainter &paint, float angle, int size, bool internal); + virtual void paintEvent(QPaintEvent *); + + // Alternate mouse behavior event handlers. + virtual void mousePressEvent(QMouseEvent *pMouseEvent); + virtual void mouseMoveEvent(QMouseEvent *pMouseEvent); + virtual void mouseReleaseEvent(QMouseEvent *pMouseEvent); + +private: + QColor m_knobColor; + QColor m_meterColor; + + // Alternate mouse behavior tracking. + bool m_mouseDial; + bool m_mousePressed; + QPoint m_posMouse; +}; + + +#endif // __AudioDial_h + +// end of AudioDial.h diff -r 000000000000 -r 2a4f26e85b4c widgets/Fader.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/widgets/Fader.cpp Tue Jan 10 16:33:16 2006 +0000 @@ -0,0 +1,260 @@ +/* -*- c-basic-offset: 4 -*- vi:set ts=8 sts=4 sw=4: */ + +/* + A waveform viewer and audio annotation editor. + Chris Cannam, Queen Mary University of London, 2005 + + This is experimental software. Not for distribution. +*/ + +/** + * Horizontal audio fader and meter widget. + * + * Based on the vertical fader and meter widget from the Hydrogen drum + * machine. (Any poor taste that has crept in during the + * modifications for this application is entirely my own, however.) + * The following copyright notice applies to code from this file, and + * also to the files in icons/fader_*.png (also modified by me). --cc + */ + +/** + * Hydrogen + * Copyright(c) 2002-2005 by Alex >Comix< Cominu [comix@users.sourceforge.net] + * + * http://www.hydrogen-music.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY, without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + + +#include "Fader.h" + +#include "base/AudioLevel.h" + +#include +#include +#include +#include +#include + +Fader::Fader(QWidget *parent, bool withoutKnob) : + QWidget(parent), + m_withoutKnob(withoutKnob), + m_value(1.0), + m_peakLeft(0.0), + m_peakRight(0.0) +{ + setMinimumSize(116, 23); + setMaximumSize(116, 23); + resize(116, 23); + + QString background_path = ":/icons/fader_background.png"; + bool ok = m_back.load(background_path); + if (ok == false) { + std::cerr << "Fader: Error loading pixmap" << std::endl; + } + + QString leds_path = ":/icons/fader_leds.png"; + ok = m_leds.load(leds_path); + if (ok == false) { + std::cerr << "Error loading pixmap" << std::endl; + } + + QString knob_path = ":/icons/fader_knob.png"; + ok = m_knob.load(knob_path); + if (ok == false) { + std::cerr << "Error loading pixmap" << std::endl; + } + + QString clip_path = ":/icons/fader_knob_red.png"; + ok = m_clip.load(clip_path); + if (ok == false) { + std::cerr << "Error loading pixmap" << std::endl; + } +} + +Fader::~Fader() +{ + +} + +void +Fader::mouseMoveEvent(QMouseEvent *ev) +{ + int x = ev->x() - 6; + const int max_x = 116 - 12; + + int value = x; + + if (value > max_x) { + value = max_x; + } else if (value < 0) { + value = 0; + } + +// float fval = float(value) / float(max_x); + float fval = AudioLevel::fader_to_multiplier + (value, max_x, AudioLevel::LongFader); + + setValue(fval); + emit valueChanged(fval); + + update(); +} + + +void +Fader::mouseDoubleClickEvent(QMouseEvent *) +{ + setValue(1.0); + emit valueChanged(1.0); + update(); +} + +void +Fader::mousePressEvent(QMouseEvent *ev) +{ + int x = ev->x() - 6; + const int max_x = 116 - 12; + + int value = x; + + if (value > max_x) { + value = max_x; + } else if (value < 0) { + value = 0; + } + + float fval = AudioLevel::fader_to_multiplier + (value, max_x, AudioLevel::LongFader); + + setValue(fval); + emit valueChanged(fval); + + update(); +} + + +void +Fader::wheelEvent(QWheelEvent *ev) +{ + ev->accept(); + + //!!! needs improvement + + if (ev->delta() > 0) { + setValue(m_value * 1.1); + } else { + setValue(m_value / 1.1); + } + + update(); + emit valueChanged(getValue()); +} + + +void +Fader::setValue(float v) +{ + float max = AudioLevel::dB_to_multiplier(10.0); + + if (v > max) { + v = max; + } else if (v < 0.0) { + v = 0.0; + } + + if (m_value != v) { + m_value = v; + float db = AudioLevel::multiplier_to_dB(m_value); + if (db <= AudioLevel::DB_FLOOR) { + setToolTip(tr("Level: Off")); + } else { + setToolTip(tr("Level: %1%2.%3%4 dB") + .arg(db < 0.0 ? "-" : "") + .arg(abs(int(db))) + .arg(abs(int(db * 10.0) % 10)) + .arg(abs(int(db * 100.0) % 10))); + } + update(); + } +} + + +float +Fader::getValue() +{ + return m_value; +} + + + +void +Fader::setPeakLeft(float peak) +{ + if (this->m_peakLeft != peak) { + this->m_peakLeft = peak; + update(); + } +} + + +void +Fader::setPeakRight(float peak) +{ + if (this->m_peakRight != peak) { + this->m_peakRight = peak; + update(); + } +} + + +void +Fader::paintEvent(QPaintEvent *) +{ + QPainter painter(this); + + // background + painter.drawPixmap(rect(), m_back, QRect(0, 0, 116, 23)); + + int offset_L = AudioLevel::multiplier_to_fader(m_peakLeft, 116, + AudioLevel::IEC268LongMeter); + + painter.drawPixmap(QRect(0, 0, offset_L, 11), m_leds, + QRect(0, 0, offset_L, 11)); + + int offset_R = AudioLevel::multiplier_to_fader(m_peakRight, 116, + AudioLevel::IEC268LongMeter); + + painter.drawPixmap(QRect(0, 11, offset_R, 11), m_leds, + QRect(0, 11, offset_R, 11)); + + if (m_withoutKnob == false) { + + static const uint knob_width = 29; + static const uint knob_height = 9; + + int x = AudioLevel::multiplier_to_fader(m_value, 116 - knob_width, + AudioLevel::LongFader); + + bool clipping = (m_peakLeft > 1.0 || m_peakRight > 1.0); + + painter.drawPixmap(QRect(x, 7, knob_width, knob_height), + clipping ? m_clip : m_knob, + QRect(0, 0, knob_width, knob_height)); + } +} + + diff -r 000000000000 -r 2a4f26e85b4c widgets/Fader.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/widgets/Fader.h Tue Jan 10 16:33:16 2006 +0000 @@ -0,0 +1,87 @@ +/* -*- c-basic-offset: 4 -*- vi:set ts=8 sts=4 sw=4: */ + +/* + A waveform viewer and audio annotation editor. + Chris Cannam, Queen Mary University of London, 2005 + + This is experimental software. Not for distribution. +*/ + +#ifndef FADER_H +#define FADER_H + +/** + * Horizontal audio fader and meter widget. + * Based on the vertical fader and meter widget from: + * + * Hydrogen + * Copyright(c) 2002-2005 by Alex >Comix< Cominu [comix@users.sourceforge.net] + * + * http://www.hydrogen-music.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY, without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * $Id: Fader.h,v 1.14 2005/08/10 08:03:30 comix Exp $ + */ + + +#include +#include + +#include +#include +#include +#include +#include + +class Fader : public QWidget +{ + Q_OBJECT + +public: + Fader(QWidget *parent, bool withoutKnob = false); + ~Fader(); + + void setValue(float newValue); + float getValue(); + + void setPeakLeft(float); + float getPeakLeft() { return m_peakLeft; } + + void setPeakRight(float); + float getPeakRight() { return m_peakRight; } + + virtual void mousePressEvent(QMouseEvent *ev); + virtual void mouseDoubleClickEvent(QMouseEvent *ev); + virtual void mouseMoveEvent(QMouseEvent *ev); + virtual void wheelEvent( QWheelEvent *ev ); + virtual void paintEvent(QPaintEvent *ev); + +signals: + void valueChanged(float); // 0.0 -> 1.0 + +private: + bool m_withoutKnob; + float m_value; + float m_peakLeft; + float m_peakRight; + + QPixmap m_back; + QPixmap m_leds; + QPixmap m_knob; + QPixmap m_clip; +}; + +#endif diff -r 000000000000 -r 2a4f26e85b4c widgets/Pane.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/widgets/Pane.cpp Tue Jan 10 16:33:16 2006 +0000 @@ -0,0 +1,397 @@ +/* -*- c-basic-offset: 4 -*- vi:set ts=8 sts=4 sw=4: */ + +/* + A waveform viewer and audio annotation editor. + Chris Cannam, Queen Mary University of London, 2005 + + This is experimental software. Not for distribution. +*/ + +#include "widgets/Pane.h" +#include "base/Layer.h" +#include "base/Model.h" +#include "base/ZoomConstraint.h" +#include "base/RealTime.h" +#include "base/Profiler.h" + +#include +#include +#include +#include + +using std::cerr; +using std::endl; + +Pane::Pane(QWidget *w) : + View(w, true), + m_identifyFeatures(false), + m_clickedInRange(false), + m_shiftPressed(false), + m_centreLineVisible(true) +{ + setObjectName("Pane"); + setMouseTracking(true); +} + +bool +Pane::shouldIlluminateLocalFeatures(const Layer *layer, QPoint &pos) +{ + for (LayerList::iterator vi = m_layers.end(); vi != m_layers.begin(); ) { + --vi; + if (layer != *vi) return false; + pos = m_identifyPoint; + return m_identifyFeatures; + } + + return false; +} + +void +Pane::setCentreLineVisible(bool visible) +{ + m_centreLineVisible = visible; + update(); +} + +void +Pane::paintEvent(QPaintEvent *e) +{ + QPainter paint; + + QRect r(rect()); + + if (e) { + r = e->rect(); + } +/* + paint.begin(this); + paint.setClipRect(r); + + if (hasLightBackground()) { + paint.setPen(Qt::white); + paint.setBrush(Qt::white); + } else { + paint.setPen(Qt::black); + paint.setBrush(Qt::black); + } + paint.drawRect(r); + + paint.end(); +*/ + View::paintEvent(e); + + paint.begin(this); + + if (e) { + paint.setClipRect(r); + } + + for (LayerList::iterator vi = m_layers.end(); vi != m_layers.begin(); ) { + --vi; + + int sw = (*vi)->getVerticalScaleWidth(paint); + + if (sw > 0 && r.left() < sw) { + +// Profiler profiler("Pane::paintEvent - painting vertical scale", true); + +// std::cerr << "Pane::paintEvent: calling paint.save() in vertical scale block" << std::endl; + paint.save(); + + paint.setPen(Qt::black); + paint.setBrush(Qt::white); + paint.drawRect(0, 0, sw, height()); + + paint.setBrush(Qt::NoBrush); + (*vi)->paintVerticalScale(paint, QRect(0, 0, sw, height())); + + paint.restore(); + } + + if (m_identifyFeatures) { + QRect descRect = (*vi)->getFeatureDescriptionRect(paint, + m_identifyPoint); + if (descRect.width() > 0 && descRect.height() > 0 && + r.left() + r.width() >= width() - descRect.width() && + r.top() < descRect.height()) { + +// Profiler profiler("Pane::paintEvent - painting local feature description", true); + +// std::cerr << "Pane::paintEvent: calling paint.save() in feature description block" << std::endl; + paint.save(); + + paint.setPen(Qt::black); + paint.setBrush(Qt::white); + + QRect rect(width() - descRect.width() - 1, 0, + descRect.width(), descRect.height()); + + paint.drawRect(rect); + + paint.setBrush(Qt::NoBrush); + (*vi)->paintLocalFeatureDescription(paint, rect, m_identifyPoint); + + paint.restore(); + } + } + + break; + } + + if (m_centreLineVisible) { + + if (hasLightBackground()) { + paint.setPen(QColor(50, 50, 50)); + } else { + paint.setPen(QColor(200, 200, 200)); + } + paint.setBrush(Qt::NoBrush); + paint.drawLine(width() / 2, 0, width() / 2, height() - 1); + +// QFont font(paint.font()); +// font.setBold(true); +// paint.setFont(font); + + int sampleRate = getModelsSampleRate(); + int y = height() - paint.fontMetrics().height() + + paint.fontMetrics().ascent() - 6; + + LayerList::iterator vi = m_layers.end(); + + if (vi != m_layers.begin()) { + + switch ((*--vi)->getPreferredFrameCountPosition()) { + + case Layer::PositionTop: + y = paint.fontMetrics().ascent() + 6; + break; + + case Layer::PositionMiddle: + y = (height() - paint.fontMetrics().height()) / 2 + + paint.fontMetrics().ascent(); + break; + + case Layer::PositionBottom: + // y already set correctly + break; + } + } + + if (sampleRate) { + + QString text(QString::fromStdString + (RealTime::frame2RealTime + (m_centreFrame, sampleRate).toText(true))); + + int tw = paint.fontMetrics().width(text); + int x = width()/2 - 4 - tw; + + if (hasLightBackground()) { + paint.setPen(palette().background().color()); + for (int dx = -1; dx <= 1; ++dx) { + for (int dy = -1; dy <= 1; ++dy) { + if ((dx && dy) || !(dx || dy)) continue; + paint.drawText(x + dx, y + dy, text); + } + } + paint.setPen(QColor(50, 50, 50)); + } else { + paint.setPen(QColor(200, 200, 200)); + } + + paint.drawText(x, y, text); + } + + QString text = QString("%1").arg(m_centreFrame); + + int tw = paint.fontMetrics().width(text); + int x = width()/2 + 4; + + if (hasLightBackground()) { + paint.setPen(palette().background().color()); + for (int dx = -1; dx <= 1; ++dx) { + for (int dy = -1; dy <= 1; ++dy) { + if ((dx && dy) || !(dx || dy)) continue; + paint.drawText(x + dx, y + dy, text); + } + } + paint.setPen(QColor(50, 50, 50)); + } else { + paint.setPen(QColor(200, 200, 200)); + } + paint.drawText(x, y, text); + } + + if (m_clickedInRange && m_shiftPressed) { + paint.setPen(Qt::blue); + paint.drawRect(m_clickPos.x(), m_clickPos.y(), + m_mousePos.x() - m_clickPos.x(), + m_mousePos.y() - m_clickPos.y()); + } + + paint.end(); +} + +void +Pane::mousePressEvent(QMouseEvent *e) +{ + m_clickPos = e->pos(); + m_clickedInRange = true; + m_shiftPressed = (e->modifiers() & Qt::ShiftModifier); + m_dragCentreFrame = m_centreFrame; + + emit paneInteractedWith(); +} + +void +Pane::mouseReleaseEvent(QMouseEvent *e) +{ + if (m_clickedInRange) { + mouseMoveEvent(e); + } + if (m_shiftPressed) { + + int x0 = std::min(m_clickPos.x(), m_mousePos.x()); + int x1 = std::max(m_clickPos.x(), m_mousePos.x()); + int w = x1 - x0; + + long newStartFrame = getStartFrame() + m_zoomLevel * x0; + + if (newStartFrame <= -long(width() * m_zoomLevel)) { + newStartFrame = -long(width() * m_zoomLevel) + 1; + } + + if (newStartFrame >= long(getModelsEndFrame())) { + newStartFrame = getModelsEndFrame() - 1; + } + + float ratio = float(w) / float(width()); +// std::cerr << "ratio: " << ratio << std::endl; + size_t newZoomLevel = (size_t)nearbyint(m_zoomLevel * ratio); + if (newZoomLevel < 1) newZoomLevel = 1; + +// std::cerr << "start: " << m_startFrame << ", level " << m_zoomLevel << std::endl; + setZoomLevel(getZoomConstraintBlockSize(newZoomLevel)); + setStartFrame(newStartFrame); + + //cerr << "mouseReleaseEvent: start frame now " << m_startFrame << endl; +// update(); + } + m_clickedInRange = false; + + emit paneInteractedWith(); +} + +void +Pane::mouseMoveEvent(QMouseEvent *e) +{ + if (!m_clickedInRange) { + +// std::cerr << "Pane: calling identifyLocalFeatures" << std::endl; + +//!!! identifyLocalFeatures(true, e->x(), e->y()); + + bool previouslyIdentifying = m_identifyFeatures; + QPoint prevPoint = m_identifyPoint; + + m_identifyFeatures = true; + m_identifyPoint = e->pos(); + + if (m_identifyFeatures != previouslyIdentifying || + m_identifyPoint != prevPoint) { + update(); + } + + } else if (m_shiftPressed) { + + m_mousePos = e->pos(); + update(); + + } else { + + long xoff = int(e->x()) - int(m_clickPos.x()); + long frameOff = xoff * m_zoomLevel; + + size_t newCentreFrame = m_dragCentreFrame; + + if (frameOff < 0) { + newCentreFrame -= frameOff; + } else if (newCentreFrame >= size_t(frameOff)) { + newCentreFrame -= frameOff; + } else { + newCentreFrame = 0; + } + + if (newCentreFrame >= getModelsEndFrame()) { + newCentreFrame = getModelsEndFrame(); + if (newCentreFrame > 0) --newCentreFrame; + } + + if (std::max(m_centreFrame, newCentreFrame) - + std::min(m_centreFrame, newCentreFrame) > size_t(m_zoomLevel)) { + setCentreFrame(newCentreFrame); + } + } +} + +void +Pane::mouseDoubleClickEvent(QMouseEvent *e) +{ + std::cerr << "mouseDoubleClickEvent" << std::endl; +} + +void +Pane::leaveEvent(QEvent *) +{ + bool previouslyIdentifying = m_identifyFeatures; + m_identifyFeatures = false; + if (previouslyIdentifying) update(); +} + +void +Pane::wheelEvent(QWheelEvent *e) +{ + //std::cerr << "wheelEvent, delta " << e->delta() << std::endl; + + int newZoomLevel = m_zoomLevel; + + int count = e->delta(); + + if (count > 0) { + if (count >= 120) count /= 120; + else count = 1; + } + + if (count < 0) { + if (count <= -120) count /= 120; + else count = -1; + } + + while (count > 0) { + if (newZoomLevel <= 2) { + newZoomLevel = 1; + break; + } + newZoomLevel = getZoomConstraintBlockSize(newZoomLevel - 1, + ZoomConstraint::RoundDown); + --count; + } + + while (count < 0) { + newZoomLevel = getZoomConstraintBlockSize(newZoomLevel + 1, + ZoomConstraint::RoundUp); + ++count; + } + + if (newZoomLevel != m_zoomLevel) { + setZoomLevel(newZoomLevel); + } + + emit paneInteractedWith(); +} + + +#ifdef INCLUDE_MOCFILES +#include "Pane.moc.cpp" +#endif + diff -r 000000000000 -r 2a4f26e85b4c widgets/Pane.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/widgets/Pane.h Tue Jan 10 16:33:16 2006 +0000 @@ -0,0 +1,60 @@ + +/* -*- c-basic-offset: 4 -*- vi:set ts=8 sts=4 sw=4: */ + +/* + A waveform viewer and audio annotation editor. + Chris Cannam, Queen Mary University of London, 2005 + + This is experimental software. Not for distribution. +*/ + +#ifndef _PANE_H_ +#define _PANE_H_ + +#include +#include + +#include "base/ZoomConstraint.h" +#include "base/View.h" + +class QWidget; +class QPaintEvent; +class Layer; + +class Pane : public View +{ + Q_OBJECT + +public: + Pane(QWidget *parent = 0); + virtual QString getPropertyContainerIconName() const { return "pane"; } + + virtual bool shouldIlluminateLocalFeatures(const Layer *layer, QPoint &pos); + + void setCentreLineVisible(bool visible); + bool getCentreLineVisible() const { return m_centreLineVisible; } + +signals: + void paneInteractedWith(); + +protected: + virtual void paintEvent(QPaintEvent *e); + virtual void mousePressEvent(QMouseEvent *e); + virtual void mouseReleaseEvent(QMouseEvent *e); + virtual void mouseMoveEvent(QMouseEvent *e); + virtual void mouseDoubleClickEvent(QMouseEvent *e); + virtual void leaveEvent(QEvent *e); + virtual void wheelEvent(QWheelEvent *e); + + bool m_identifyFeatures; + QPoint m_identifyPoint; + QPoint m_clickPos; + QPoint m_mousePos; + bool m_clickedInRange; + bool m_shiftPressed; + size_t m_dragCentreFrame; + bool m_centreLineVisible; +}; + +#endif + diff -r 000000000000 -r 2a4f26e85b4c widgets/PaneStack.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/widgets/PaneStack.cpp Tue Jan 10 16:33:16 2006 +0000 @@ -0,0 +1,239 @@ + +/* -*- c-basic-offset: 4 -*- vi:set ts=8 sts=4 sw=4: */ + +/* + A waveform viewer and audio annotation editor. + Chris Cannam, Queen Mary University of London, 2005 + + This is experimental software. Not for distribution. +*/ + +#include "PaneStack.h" + +#include "widgets/Pane.h" +#include "widgets/PropertyStack.h" +#include "base/Layer.h" +#include "base/ViewManager.h" + +#include +#include +#include +#include +#include + +#include + +PaneStack::PaneStack(QWidget *parent, ViewManager *viewManager) : + QSplitter(parent), + m_currentPane(0), + m_viewManager(viewManager) +{ + setOrientation(Qt::Vertical); + setOpaqueResize(false); +} + +Pane * +PaneStack::addPane(bool suppressPropertyBox) +{ + QFrame *frame = new QFrame; + QHBoxLayout *layout = new QHBoxLayout; + layout->setMargin(0); + layout->setSpacing(2); + + QLabel *currentIndicator = new QLabel(frame); + currentIndicator->setFixedWidth(QPainter(this).fontMetrics().width("x")); + layout->addWidget(currentIndicator); + layout->setStretchFactor(currentIndicator, 1); + currentIndicator->setScaledContents(true); + m_currentIndicators.push_back(currentIndicator); + + Pane *pane = new Pane(frame); + pane->setViewManager(m_viewManager); + layout->addWidget(pane); + layout->setStretchFactor(pane, 10); + m_panes.push_back(pane); + + QWidget *properties = 0; + if (suppressPropertyBox) { + properties = new QFrame(); + } else { + properties = new PropertyStack(frame, pane); + connect(properties, SIGNAL(propertyContainerSelected(PropertyContainer *)), + this, SLOT(propertyContainerSelected(PropertyContainer *))); + } + layout->addWidget(properties); + layout->setStretchFactor(properties, 1); + m_propertyStacks.push_back(properties); + + frame->setLayout(layout); + addWidget(frame); + + connect(pane, SIGNAL(propertyContainerAdded(PropertyContainer *)), + this, SLOT(propertyContainerAdded(PropertyContainer *))); + connect(pane, SIGNAL(propertyContainerRemoved(PropertyContainer *)), + this, SLOT(propertyContainerRemoved(PropertyContainer *))); + connect(pane, SIGNAL(paneInteractedWith()), + this, SLOT(paneInteractedWith())); + + if (!m_currentPane) { + setCurrentPane(pane); + } + + return pane; +} + +Pane * +PaneStack::getPane(int n) +{ + return m_panes[n]; +} + +void +PaneStack::deletePane(Pane *pane) +{ + int n = 0; + std::vector::iterator i = m_panes.begin(); + std::vector::iterator j = m_propertyStacks.begin(); + std::vector::iterator k = m_currentIndicators.begin(); + + while (i != m_panes.end()) { + if (*i == pane) break; + ++i; + ++j; + ++k; + ++n; + } + if (n >= int(m_panes.size())) return; + + m_panes.erase(i); + m_propertyStacks.erase(j); + m_currentIndicators.erase(k); + delete widget(n); + + if (m_currentPane == pane) { + if (m_panes.size() > 0) { + setCurrentPane(m_panes[0]); + } else { + setCurrentPane(0); + } + } +} + +int +PaneStack::getPaneCount() const +{ + return m_panes.size(); +} + +void +PaneStack::setCurrentPane(Pane *pane) // may be null +{ + if (m_currentPane == pane) return; + + std::vector::iterator i = m_panes.begin(); + std::vector::iterator k = m_currentIndicators.begin(); + + // We used to do this by setting the foreground and background + // role, but it seems the background role is ignored and the + // background drawn transparent in Qt 4.1 -- I can't quite see why + + QPixmap selectedMap(1, 1); + selectedMap.fill(QApplication::palette().color(QPalette::Foreground)); + + QPixmap unselectedMap(1, 1); + unselectedMap.fill(QApplication::palette().color(QPalette::Background)); + + while (i != m_panes.end()) { + if (*i == pane) { + (*k)->setPixmap(selectedMap); + } else { + (*k)->setPixmap(unselectedMap); + } + ++i; + ++k; + } + m_currentPane = pane; + + emit currentPaneChanged(m_currentPane); +} + +Pane * +PaneStack::getCurrentPane() +{ + return m_currentPane; +} + +void +PaneStack::propertyContainerAdded(PropertyContainer *) +{ + sizePropertyStacks(); +} + +void +PaneStack::propertyContainerRemoved(PropertyContainer *) +{ + sizePropertyStacks(); +} + +void +PaneStack::propertyContainerSelected(PropertyContainer *pc) +{ + std::vector::iterator i = m_panes.begin(); + std::vector::iterator j = m_propertyStacks.begin(); + + while (i != m_panes.end()) { + PropertyStack *stack = dynamic_cast(*j); + if (stack && stack->containsContainer(pc)) { + setCurrentPane(*i); + break; + } + ++i; + ++j; + } +} + +void +PaneStack::paneInteractedWith() +{ + Pane *pane = dynamic_cast(sender()); + if (!pane) return; + setCurrentPane(pane); +} + +void +PaneStack::sizePropertyStacks() +{ + int maxMinWidth = 0; + + for (unsigned int i = 0; i < m_propertyStacks.size(); ++i) { + if (!m_propertyStacks[i]) continue; + std::cerr << "PaneStack::sizePropertyStacks: " << i << ": min " + << m_propertyStacks[i]->minimumSizeHint().width() << ", current " + << m_propertyStacks[i]->width() << std::endl; + + if (m_propertyStacks[i]->minimumSizeHint().width() > maxMinWidth) { + maxMinWidth = m_propertyStacks[i]->minimumSizeHint().width(); + } + } + + std::cerr << "PaneStack::sizePropertyStacks: max min width " << maxMinWidth << std::endl; + +#ifdef Q_WS_MAC + // This is necessary to compensate for cb->setMinimumSize(10, 10) + // in PropertyBox in the Mac version (to avoid a mysterious crash) + int setWidth = maxMinWidth * 3 / 2; +#else + int setWidth = maxMinWidth; +#endif + + for (unsigned int i = 0; i < m_propertyStacks.size(); ++i) { + if (!m_propertyStacks[i]) continue; + m_propertyStacks[i]->setMinimumWidth(setWidth); + } +} + + +#ifdef INCLUDE_MOCFILES +#include "PaneStack.moc.cpp" +#endif + diff -r 000000000000 -r 2a4f26e85b4c widgets/PaneStack.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/widgets/PaneStack.h Tue Jan 10 16:33:16 2006 +0000 @@ -0,0 +1,59 @@ + +/* -*- c-basic-offset: 4 -*- vi:set ts=8 sts=4 sw=4: */ + +/* + A waveform viewer and audio annotation editor. + Chris Cannam, Queen Mary University of London, 2005 + + This is experimental software. Not for distribution. +*/ + +#ifndef _PANESTACK_H_ +#define _PANESTACK_H_ + +#include + +class QWidget; +class QLabel; +class Pane; +class ViewManager; +class PropertyContainer; +class PropertyStack; + +class PaneStack : public QSplitter +{ + Q_OBJECT + +public: + PaneStack(QWidget *parent, ViewManager *viewManager); + + Pane *addPane(bool suppressPropertyBox = false); // I own the returned value + Pane *getPane(int n); // I own the returned value + void deletePane(Pane *pane); // Deletes the pane and all its views + int getPaneCount() const; + + void setCurrentPane(Pane *pane); + Pane *getCurrentPane(); + +signals: + void currentPaneChanged(Pane *pane); + +public slots: + void propertyContainerAdded(PropertyContainer *); + void propertyContainerRemoved(PropertyContainer *); + void propertyContainerSelected(PropertyContainer *); + void paneInteractedWith(); + +protected: + Pane *m_currentPane; + //!!! should be a single vector of structs + std::vector m_panes; // I own these + std::vector m_propertyStacks; // I own these + std::vector m_currentIndicators; // I own these + ViewManager *m_viewManager; // I don't own this + + void sizePropertyStacks(); +}; + +#endif + diff -r 000000000000 -r 2a4f26e85b4c widgets/Panner.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/widgets/Panner.cpp Tue Jan 10 16:33:16 2006 +0000 @@ -0,0 +1,236 @@ +/* -*- c-basic-offset: 4 -*- vi:set ts=8 sts=4 sw=4: */ + +/* + A waveform viewer and audio annotation editor. + Chris Cannam, Queen Mary University of London, 2005 + + This is experimental software. Not for distribution. +*/ + +#include "Panner.h" +#include "base/Layer.h" +#include "base/Model.h" +#include "base/ZoomConstraint.h" + +#include +#include +#include + +using std::cerr; +using std::endl; + +Panner::Panner(QWidget *w) : + View(w, false), + m_clickedInRange(false) +{ + setObjectName(tr("Panner")); + m_followPan = false; + m_followZoom = false; +} + +void +Panner::modelChanged(size_t startFrame, size_t endFrame) +{ + View::modelChanged(startFrame, endFrame); +} + +void +Panner::modelReplaced() +{ + View::modelReplaced(); +} + +void +Panner::registerView(View *widget) +{ + m_widgets[widget] = WidgetRec(0, -1); + update(); +} + +void +Panner::unregisterView(View *widget) +{ + m_widgets.erase(widget); + update(); +} + +void +Panner::viewManagerCentreFrameChanged(void *p, unsigned long f, bool) +{ +// std::cerr << "Panner[" << this << "]::viewManagerCentreFrameChanged(" +// << p << ", " << f << ")" << std::endl; + + if (p == this) return; + if (m_widgets.find(p) != m_widgets.end()) { + m_widgets[p].first = f; + update(); + } +} + +void +Panner::viewManagerZoomLevelChanged(void *p, unsigned long z, bool) +{ + if (p == this) return; + if (m_widgets.find(p) != m_widgets.end()) { + m_widgets[p].second = z; + update(); + } +} + +void +Panner::viewManagerPlaybackFrameChanged(unsigned long f) +{ + bool changed = false; + + if (m_playPointerFrame / m_zoomLevel != f / m_zoomLevel) changed = true; + m_playPointerFrame = f; + + for (WidgetMap::iterator i = m_widgets.begin(); i != m_widgets.end(); ++i) { + unsigned long of = i->second.first; + i->second.first = f; + if (of / m_zoomLevel != f / m_zoomLevel) changed = true; + } + + if (changed) update(); +} + +void +Panner::paintEvent(QPaintEvent *e) +{ +/*!!! + // Force View to recalculate zoom in case the size of the + // widget has changed. (We need a better name/mechanism for this) + m_newModel = true; +*/ + + // Recalculate zoom in case the size of the widget has changed. + + size_t startFrame = getModelsStartFrame(); + size_t frameCount = getModelsEndFrame() - getModelsStartFrame(); + int zoomLevel = frameCount / width(); + if (zoomLevel < 1) zoomLevel = 1; + zoomLevel = getZoomConstraintBlockSize(zoomLevel, + ZoomConstraint::RoundUp); + if (zoomLevel != m_zoomLevel) { + m_zoomLevel = zoomLevel; + emit zoomLevelChanged(this, m_zoomLevel, m_followZoom); + } + size_t centreFrame = startFrame + m_zoomLevel * (width() / 2); + if (centreFrame > (startFrame + getModelsEndFrame())/2) { + centreFrame = (startFrame + getModelsEndFrame())/2; + } + if (centreFrame != m_centreFrame) { + m_centreFrame = centreFrame; + emit centreFrameChanged(this, m_centreFrame, false); + } + + View::paintEvent(e); + + QPainter paint; + paint.begin(this); + + QRect r(rect()); + + if (e) { + r = e->rect(); + paint.setClipRect(r); + } + + paint.setPen(Qt::black); + + int y = 0; + long prevCentre = 0; + long prevZoom = -1; + + for (WidgetMap::iterator i = m_widgets.begin(); i != m_widgets.end(); ++i) { + if (!i->first) continue; + + View *w = (View *)i->first; + if (i->second.second < 0) i->second.second = w->getZoomLevel(); + if (i->second.second < 0) continue; + + long c = (long)i->second.first; + long z = (long)i->second.second; + + long f0 = c - (w->width() / 2) * z; + long f1 = c + (w->width() / 2) * z; + + int x0 = (f0 - long(getCentreFrame())) / getZoomLevel() + width()/2; + int x1 = (f1 - long(getCentreFrame())) / getZoomLevel() + width()/2 - 1; + + if (c != prevCentre || z != prevZoom) { + y += height() / 10 + 1; + prevCentre = c; + prevZoom = z; + } + + paint.drawRect(x0, y, x1 - x0, height() - 2 * y); + } + + paint.end(); +} + +void +Panner::mousePressEvent(QMouseEvent *e) +{ + m_clickPos = e->pos(); + for (WidgetMap::iterator i = m_widgets.begin(); i != m_widgets.end(); ++i) { + if (i->first && i->second.second >= 0) { + m_clickedInRange = true; + m_dragCentreFrame = i->second.first; + } + } +} + +void +Panner::mouseReleaseEvent(QMouseEvent *e) +{ + if (m_clickedInRange) { + mouseMoveEvent(e); + } + m_clickedInRange = false; +} + +void +Panner::mouseMoveEvent(QMouseEvent *e) +{ + if (!m_clickedInRange) return; + +/*!!! + long newFrame = getStartFrame() + e->x() * m_zoomLevel; + + if (newFrame < 0) newFrame = 0; + if (newFrame >= getModelsEndFrame()) { + newFrame = getModelsEndFrame(); + if (newFrame > 0) --newFrame; + } + emit centreFrameChanged(this, newFrame, true); +*/ + + long xoff = int(e->x()) - int(m_clickPos.x()); + long frameOff = xoff * m_zoomLevel; + + size_t newCentreFrame = m_dragCentreFrame; + if (frameOff > 0) { + newCentreFrame += frameOff; + } else if (newCentreFrame >= size_t(-frameOff)) { + newCentreFrame += frameOff; + } else { + newCentreFrame = 0; + } + + if (newCentreFrame >= getModelsEndFrame()) { + newCentreFrame = getModelsEndFrame(); + if (newCentreFrame > 0) --newCentreFrame; + } + + if (std::max(m_centreFrame, newCentreFrame) - + std::min(m_centreFrame, newCentreFrame) > size_t(m_zoomLevel)) { + emit centreFrameChanged(this, newCentreFrame, true); + } +} + +#ifdef INCLUDE_MOCFILES +#include "Panner.moc.cpp" +#endif + diff -r 000000000000 -r 2a4f26e85b4c widgets/Panner.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/widgets/Panner.h Tue Jan 10 16:33:16 2006 +0000 @@ -0,0 +1,61 @@ +/* -*- c-basic-offset: 4 -*- vi:set ts=8 sts=4 sw=4: */ + +/* + A waveform viewer and audio annotation editor. + Chris Cannam, Queen Mary University of London, 2005 + + This is experimental software. Not for distribution. +*/ + +#ifndef _PAN_WIDGET_H_ +#define _PAN_WIDGET_H_ + +#include "base/View.h" + +#include + +class QWidget; +class QPaintEvent; +class Layer; +class View; + +#include + +class Panner : public View +{ + Q_OBJECT + +public: + Panner(QWidget *parent = 0); + + void registerView(View *widget); + void unregisterView(View *widget); + + virtual QString getPropertyContainerIconName() const { return "panner"; } + +public slots: + virtual void modelChanged(size_t startFrame, size_t endFrame); + virtual void modelReplaced(); + + virtual void viewManagerCentreFrameChanged(void *, unsigned long, bool); + virtual void viewManagerZoomLevelChanged(void *, unsigned long, bool); + virtual void viewManagerPlaybackFrameChanged(unsigned long); + +protected: + virtual void paintEvent(QPaintEvent *e); + virtual void mousePressEvent(QMouseEvent *e); + virtual void mouseReleaseEvent(QMouseEvent *e); + virtual void mouseMoveEvent(QMouseEvent *e); + + QPoint m_clickPos; + QPoint m_mousePos; + bool m_clickedInRange; + size_t m_dragCentreFrame; + + typedef std::pair WidgetRec; // centre, zoom (-1 = invalid) + typedef std::map WidgetMap; + WidgetMap m_widgets; +}; + +#endif + diff -r 000000000000 -r 2a4f26e85b4c widgets/PropertyBox.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/widgets/PropertyBox.cpp Tue Jan 10 16:33:16 2006 +0000 @@ -0,0 +1,272 @@ +/* -*- c-basic-offset: 4 -*- vi:set ts=8 sts=4 sw=4: */ + +/* + A waveform viewer and audio annotation editor. + Chris Cannam, Queen Mary University of London, 2005 + + This is experimental software. Not for distribution. +*/ + +#include "PropertyBox.h" + +#include "base/PropertyContainer.h" + +#include "AudioDial.h" + +#include +#include +#include +#include +#include + +#include +#include + +//#define DEBUG_PROPERTY_BOX 1 + +PropertyBox::PropertyBox(PropertyContainer *container) : + m_container(container) +{ +#ifdef DEBUG_PROPERTY_BOX + std::cerr << "PropertyBox[" << this << "(\"" << + container->getPropertyContainerName().toStdString() << "\")]::PropertyBox" << std::endl; +#endif + + m_layout = new QGridLayout; + setLayout(m_layout); + + PropertyContainer::PropertyList properties = container->getProperties(); + + blockSignals(true); + + size_t i; + + for (i = 0; i < properties.size(); ++i) { + updatePropertyEditor(properties[i]); + } + + blockSignals(false); + + m_layout->setRowStretch(m_layout->rowCount(), 10); + +#ifdef DEBUG_PROPERTY_BOX + std::cerr << "PropertyBox[" << this << "]::PropertyBox returning" << std::endl; +#endif +} + +PropertyBox::~PropertyBox() +{ +#ifdef DEBUG_PROPERTY_BOX + std::cerr << "PropertyBox[" << this << "(\"" << m_container->getPropertyContainerName().toStdString() << "\")]::~PropertyBox" << std::endl; +#endif +} + +void +PropertyBox::updatePropertyEditor(PropertyContainer::PropertyName name) +{ + PropertyContainer::PropertyType type = m_container->getPropertyType(name); + int row = m_layout->rowCount(); + + int min = 0, max = 0, value = 0; + value = m_container->getPropertyRangeAndValue(name, &min, &max); + + bool have = (m_propertyControllers.find(name) != + m_propertyControllers.end()); + + QString groupName = m_container->getPropertyGroupName(name); + +#ifdef DEBUG_PROPERTY_BOX + std::cerr << "PropertyBox[" << this + << "(\"" << m_container->getPropertyContainerName().toStdString() + << "\")]"; + std::cerr << "::updatePropertyEditor(\"" << name.toStdString() << "\"):"; + std::cerr << " value " << value << ", have " << have << ", group \"" + << groupName.toStdString() << "\"" << std::endl; +#endif + + bool inGroup = (groupName != QString()); + + if (!have) { + if (inGroup) { + if (m_groupLayouts.find(groupName) == m_groupLayouts.end()) { +#ifdef DEBUG_PROPERTY_BOX + std::cerr << "PropertyBox: adding label \"" << groupName.toStdString() << "\" and frame for group for \"" << name.toStdString() << "\"" << std::endl; +#endif + m_layout->addWidget(new QLabel(groupName, this), row, 0); + QFrame *frame = new QFrame(this); + m_layout->addWidget(frame, row, 1, 1, 2); + m_groupLayouts[groupName] = new QHBoxLayout; + m_groupLayouts[groupName]->setMargin(0); + frame->setLayout(m_groupLayouts[groupName]); + } + } else { +#ifdef DEBUG_PROPERTY_BOX + std::cerr << "PropertyBox: adding label \"" << name.toStdString() << "\"" << std::endl; +#endif + m_layout->addWidget(new QLabel(name, this), row, 0); + } + } + + switch (type) { + + case PropertyContainer::ToggleProperty: + { + QCheckBox *cb; + + if (have) { + cb = dynamic_cast(m_propertyControllers[name]); + assert(cb); + } else { +#ifdef DEBUG_PROPERTY_BOX + std::cerr << "PropertyBox: creating new checkbox" << std::endl; +#endif + cb = new QCheckBox(); + cb->setObjectName(name); + connect(cb, SIGNAL(stateChanged(int)), + this, SLOT(propertyControllerChanged(int))); + if (inGroup) { + cb->setToolTip(name); + m_groupLayouts[groupName]->addWidget(cb); + } else { + m_layout->addWidget(cb, row, 1, 1, 2); + } + m_propertyControllers[name] = cb; + } + + if (cb->isChecked() != (value > 0)) cb->setChecked(value > 0); + break; + } + + case PropertyContainer::RangeProperty: + { + AudioDial *dial; + + if (have) { + dial = dynamic_cast(m_propertyControllers[name]); + assert(dial); + } else { +#ifdef DEBUG_PROPERTY_BOX + std::cerr << "PropertyBox: creating new dial" << std::endl; +#endif + dial = new AudioDial(); + dial->setObjectName(name); + dial->setMinimum(min); + dial->setMaximum(max); + dial->setPageStep(1); + dial->setNotchesVisible(true); + connect(dial, SIGNAL(valueChanged(int)), + this, SLOT(propertyControllerChanged(int))); + + if (inGroup) { + dial->setFixedWidth(24); + dial->setFixedHeight(24); + dial->setToolTip(name); + m_groupLayouts[groupName]->addWidget(dial); + } else { + dial->setFixedWidth(32); + dial->setFixedHeight(32); + m_layout->addWidget(dial, row, 1); + QLabel *label = new QLabel(this); + connect(dial, SIGNAL(valueChanged(int)), + label, SLOT(setNum(int))); + label->setNum(value); + m_layout->addWidget(label, row, 2); + } + + m_propertyControllers[name] = dial; + } + + if (dial->value() != value) dial->setValue(value); + break; + } + + case PropertyContainer::ValueProperty: + { + QComboBox *cb; + + if (have) { + cb = dynamic_cast(m_propertyControllers[name]); + assert(cb); + } else { +#ifdef DEBUG_PROPERTY_BOX + std::cerr << "PropertyBox: creating new combobox" << std::endl; +#endif + + cb = new QComboBox(); + cb->setObjectName(name); + for (int i = min; i <= max; ++i) { + cb->addItem(m_container->getPropertyValueLabel(name, i)); + } + connect(cb, SIGNAL(activated(int)), + this, SLOT(propertyControllerChanged(int))); + if (inGroup) { + cb->setToolTip(name); + m_groupLayouts[groupName]->addWidget(cb); + } else { + m_layout->addWidget(cb, row, 1, 1, 2); + } + m_propertyControllers[name] = cb; + } + + if (cb->currentIndex() != value) cb->setCurrentIndex(value); + +#ifdef Q_WS_MAC + // Crashes on startup without this, for some reason + cb->setMinimumSize(QSize(10, 10)); +#endif + + break; + } + + default: + break; + } +} + +void +PropertyBox::propertyContainerPropertyChanged(PropertyContainer *pc) +{ + if (pc != m_container) return; + + PropertyContainer::PropertyList properties = m_container->getProperties(); + size_t i; + + blockSignals(true); + + for (i = 0; i < properties.size(); ++i) { + updatePropertyEditor(properties[i]); + } + + blockSignals(false); +} + +void +PropertyBox::propertyControllerChanged(int value) +{ + QObject *obj = sender(); + QString name = obj->objectName(); + +// std::cerr << "PropertyBox::propertyControllerChanged(" << name.toStdString() +// << ", " << value << ")" << std::endl; + + PropertyContainer::PropertyType type = m_container->getPropertyType(name); + + if (type != PropertyContainer::InvalidProperty) { + m_container->setProperty(name, value); + } + + if (type == PropertyContainer::RangeProperty) { + AudioDial *dial = dynamic_cast(m_propertyControllers[name]); + if (dial) { + dial->setToolTip(QString("%1: %2").arg(name).arg(value)); + //!!! unfortunately this doesn't update an already-visible tooltip + } + } +} + + + +#ifdef INCLUDE_MOCFILES +#include "PropertyBox.moc.cpp" +#endif + diff -r 000000000000 -r 2a4f26e85b4c widgets/PropertyBox.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/widgets/PropertyBox.h Tue Jan 10 16:33:16 2006 +0000 @@ -0,0 +1,45 @@ +/* -*- c-basic-offset: 4 -*- vi:set ts=8 sts=4 sw=4: */ + +/* + A waveform viewer and audio annotation editor. + Chris Cannam, Queen Mary University of London, 2005 + + This is experimental software. Not for distribution. +*/ + +#ifndef _PROPERTY_BOX_H_ +#define _PROPERTY_BOX_H_ + +#include "base/PropertyContainer.h" + +#include +#include + +class QGridLayout; + +class PropertyBox : public QFrame +{ + Q_OBJECT + +public: + PropertyBox(PropertyContainer *); + ~PropertyBox(); + + PropertyContainer *getContainer() { return m_container; } + +public slots: + void propertyContainerPropertyChanged(PropertyContainer *); + +protected slots: + void propertyControllerChanged(int); + +protected: + void updatePropertyEditor(PropertyContainer::PropertyName); + + QGridLayout *m_layout; + PropertyContainer *m_container; + std::map m_groupLayouts; + std::map m_propertyControllers; +}; + +#endif diff -r 000000000000 -r 2a4f26e85b4c widgets/PropertyStack.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/widgets/PropertyStack.cpp Tue Jan 10 16:33:16 2006 +0000 @@ -0,0 +1,139 @@ +/* -*- c-basic-offset: 4 -*- vi:set ts=8 sts=4 sw=4: */ + +/* + A waveform viewer and audio annotation editor. + Chris Cannam, Queen Mary University of London, 2005 + + This is experimental software. Not for distribution. +*/ + +#include "PropertyStack.h" +#include "PropertyBox.h" +#include "base/PropertyContainer.h" +#include "base/View.h" + +#include +#include + +#include + +#define DEBUG_PROPERTY_STACK 1 + +PropertyStack::PropertyStack(QWidget *parent, View *client) : + QTabWidget(parent), + m_client(client) +{ + repopulate(); + + connect(this, SIGNAL(currentChanged(int)), + this, SLOT(selectedContainerChanged(int))); + + connect(m_client, SIGNAL(propertyContainerAdded(PropertyContainer *)), + this, SLOT(propertyContainerAdded(PropertyContainer *))); + + connect(m_client, SIGNAL(propertyContainerRemoved(PropertyContainer *)), + this, SLOT(propertyContainerRemoved(PropertyContainer *))); + + connect(m_client, SIGNAL(propertyContainerPropertyChanged(PropertyContainer *)), + this, SLOT(propertyContainerPropertyChanged(PropertyContainer *))); + + connect(m_client, SIGNAL(propertyContainerNameChanged(PropertyContainer *)), + this, SLOT(propertyContainerNameChanged(PropertyContainer *))); + + connect(this, SIGNAL(propertyContainerSelected(PropertyContainer *)), + m_client, SLOT(propertyContainerSelected(PropertyContainer *))); +} + +void +PropertyStack::repopulate() +{ + blockSignals(true); + +#ifdef DEBUG_PROPERTY_STACK + std::cerr << "PropertyStack::repopulate" << std::endl; +#endif + + while (count() > 0) { + removeTab(0); + } + for (size_t i = 0; i < m_boxes.size(); ++i) { + delete m_boxes[i]; + } + m_boxes.clear(); + + for (size_t i = 0; i < m_client->getPropertyContainerCount(); ++i) { + + PropertyContainer *container = m_client->getPropertyContainer(i); + QString name = container->getPropertyContainerName(); + + QString iconName = container->getPropertyContainerIconName(); + + PropertyBox *box = new PropertyBox(container); + + QIcon icon(QString(":/icons/%1.png").arg(iconName)); + if (icon.isNull()) { + addTab(box, name); + } else { + addTab(box, icon, QString("&%1").arg(i + 1)); + setTabToolTip(count() - 1, name); + } + + m_boxes.push_back(box); + } + + blockSignals(false); +} + +bool +PropertyStack::containsContainer(PropertyContainer *pc) const +{ + for (size_t i = 0; i < m_client->getPropertyContainerCount(); ++i) { + PropertyContainer *container = m_client->getPropertyContainer(i); + if (pc == container) return true; + } + + return false; +} + +void +PropertyStack::propertyContainerAdded(PropertyContainer *) +{ + if (sender() != m_client) return; + repopulate(); +} + +void +PropertyStack::propertyContainerRemoved(PropertyContainer *) +{ + if (sender() != m_client) return; + repopulate(); +} + +void +PropertyStack::propertyContainerPropertyChanged(PropertyContainer *pc) +{ + for (unsigned int i = 0; i < m_boxes.size(); ++i) { + if (pc == m_boxes[i]->getContainer()) { + m_boxes[i]->propertyContainerPropertyChanged(pc); + } + } +} + +void +PropertyStack::propertyContainerNameChanged(PropertyContainer *pc) +{ + if (sender() != m_client) return; + repopulate(); +} + +void +PropertyStack::selectedContainerChanged(int n) +{ + if (n >= int(m_boxes.size())) return; + emit propertyContainerSelected(m_boxes[n]->getContainer()); +} + +#ifdef INCLUDE_MOCFILES +#include "PropertyStack.moc.cpp" +#endif + diff -r 000000000000 -r 2a4f26e85b4c widgets/PropertyStack.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/widgets/PropertyStack.h Tue Jan 10 16:33:16 2006 +0000 @@ -0,0 +1,51 @@ +/* -*- c-basic-offset: 4 -*- vi:set ts=8 sts=4 sw=4: */ + +/* + A waveform viewer and audio annotation editor. + Chris Cannam, Queen Mary University of London, 2005 + + This is experimental software. Not for distribution. +*/ + +#ifndef _PROPERTY_STACK_H_ +#define _PROPERTY_STACK_H_ + +#include +#include +#include + +class Layer; +class View; +class PropertyBox; +class PropertyContainer; + +class PropertyStack : public QTabWidget +{ + Q_OBJECT + +public: + PropertyStack(QWidget *parent, View *client); + + bool containsContainer(PropertyContainer *container) const; + +signals: + void propertyContainerSelected(PropertyContainer *container); + +public slots: + void propertyContainerAdded(PropertyContainer *); + void propertyContainerRemoved(PropertyContainer *); + void propertyContainerPropertyChanged(PropertyContainer *); + void propertyContainerNameChanged(PropertyContainer *); + +protected slots: + void selectedContainerChanged(int); + +protected: + View *m_client; + std::vector m_boxes; + + void repopulate(); + void updateValues(PropertyContainer *); +}; + +#endif