changeset 193:57c2350a8c40

* Add slice layers (so you can display a slice of a colour 3d plot as if it were a spectrum) * Make spectrum layer a subclass of slice layer
author Chris Cannam
date Fri, 26 Jan 2007 16:59:57 +0000
parents fcc043f75c41
children d13e209bfa94
files layer/Colour3DPlotLayer.cpp layer/Colour3DPlotLayer.h layer/LayerFactory.cpp layer/LayerFactory.h layer/SliceLayer.cpp layer/SliceLayer.h layer/SliceableLayer.h layer/SpectrogramLayer.cpp layer/SpectrogramLayer.h layer/SpectrumLayer.cpp layer/SpectrumLayer.h layer/layer.pro widgets/Panner.cpp widgets/PropertyBox.cpp
diffstat 14 files changed, 899 insertions(+), 365 deletions(-) [+]
line wrap: on
line diff
--- a/layer/Colour3DPlotLayer.cpp	Wed Jan 24 17:14:24 2007 +0000
+++ b/layer/Colour3DPlotLayer.cpp	Fri Jan 26 16:59:57 2007 +0000
@@ -30,7 +30,6 @@
 
 
 Colour3DPlotLayer::Colour3DPlotLayer() :
-    Layer(),
     m_model(0),
     m_cache(0),
     m_colourScale(LinearScale)
@@ -45,6 +44,8 @@
 void
 Colour3DPlotLayer::setModel(const DenseThreeDimensionalModel *model)
 {
+    if (m_model == model) return;
+    const DenseThreeDimensionalModel *oldModel = m_model;
     m_model = model;
     if (!m_model || !m_model->isOK()) return;
 
@@ -60,6 +61,7 @@
 	    this, SLOT(cacheInvalid(size_t, size_t)));
 
     emit modelReplaced();
+    emit sliceableModelReplaced(oldModel, model);
 }
 
 void
--- a/layer/Colour3DPlotLayer.h	Wed Jan 24 17:14:24 2007 +0000
+++ b/layer/Colour3DPlotLayer.h	Fri Jan 26 16:59:57 2007 +0000
@@ -16,7 +16,7 @@
 #ifndef _COLOUR_3D_PLOT_H_
 #define _COLOUR_3D_PLOT_H_
 
-#include "Layer.h"
+#include "SliceableLayer.h"
 
 #include "data/model/DenseThreeDimensionalModel.h"
 
@@ -37,7 +37,7 @@
  * ever actually used.
  */
 
-class Colour3DPlotLayer : public Layer
+class Colour3DPlotLayer : public SliceableLayer
 {
     Q_OBJECT
 
@@ -88,6 +88,8 @@
     void setColourScale(ColourScale);
     ColourScale getColourScale() const { return m_colourScale; }
 
+    virtual const Model *getSliceableModel() const { return m_model; }
+
 protected slots:
     void cacheInvalid();
     void cacheInvalid(size_t startFrame, size_t endFrame);
--- a/layer/LayerFactory.cpp	Wed Jan 24 17:14:24 2007 +0000
+++ b/layer/LayerFactory.cpp	Fri Jan 26 16:59:57 2007 +0000
@@ -24,6 +24,8 @@
 #include "TextLayer.h"
 #include "Colour3DPlotLayer.h"
 #include "SpectrumLayer.h"
+#include "SliceLayer.h"
+#include "SliceableLayer.h"
 
 #include "data/model/RangeSummarisableTimeValueModel.h"
 #include "data/model/DenseTimeValueModel.h"
@@ -61,6 +63,7 @@
     case Text:         return Layer::tr("Text");
     case Colour3DPlot: return Layer::tr("Colour 3D Plot");
     case Spectrum:     return Layer::tr("Spectrum");
+    case Slice:        return Layer::tr("Time Slice");
 
     case MelodicRangeSpectrogram:
 	// The user can change all the parameters of this after the
@@ -78,6 +81,39 @@
     return Layer::tr("Layer");
 }
 
+bool
+LayerFactory::isLayerSliceable(const Layer *layer)
+{
+    if (dynamic_cast<const SliceableLayer *>(layer)) {
+        if (dynamic_cast<const SpectrogramLayer *>(layer)) {
+
+            //!!! We can create slices of spectrograms, but there's a
+            // problem managing the models.  The source model for the
+            // slice layer has to be one of the spectrogram's FFT
+            // models -- that's fine, except that we can't store &
+            // recall the slice layer with a reference to that model
+            // because the model is internal to the spectrogram layer
+            // and the document has no record of it.  We would need
+            // some other way of managing models that are used in this
+            // way.  For the moment we just don't allow slices of
+            // spectrograms -- and provide a spectrum layer for this
+            // instead.
+            //
+            // This business needs a bit more thought -- either come
+            // up with a sensible way to deal with that stuff, or
+            // simplify the existing slice layer logic so that it
+            // doesn't have to deal with models disappearing on it at
+            // all (and use the normal Document setModel mechanism to
+            // set its sliceable model instead of the fancy pants
+            // nonsense it's doing at the moment).
+
+            return false;
+        }
+        return true;
+    }
+    return false;
+}
+
 LayerFactory::LayerTypeSet
 LayerFactory::getValidLayerTypes(Model *model)
 {
@@ -85,6 +121,11 @@
 
     if (dynamic_cast<DenseThreeDimensionalModel *>(model)) {
 	types.insert(Colour3DPlot);
+        types.insert(Slice);
+    }
+
+    if (dynamic_cast<RangeSummarisableTimeValueModel *>(model)) {
+	types.insert(Waveform);
     }
 
     if (dynamic_cast<DenseTimeValueModel *>(model)) {
@@ -93,10 +134,6 @@
 	types.insert(PeakFrequencySpectrogram);
     }
 
-    if (dynamic_cast<RangeSummarisableTimeValueModel *>(model)) {
-	types.insert(Waveform);
-    }
-
     if (dynamic_cast<SparseOneDimensionalModel *>(model)) {
 	types.insert(TimeInstants);
     }
@@ -147,6 +184,7 @@
     if (dynamic_cast<const TextLayer *>(layer)) return Text;
     if (dynamic_cast<const Colour3DPlotLayer *>(layer)) return Colour3DPlot;
     if (dynamic_cast<const SpectrumLayer *>(layer)) return Spectrum;
+    if (dynamic_cast<const SliceLayer *>(layer)) return Slice;
     return UnknownLayer;
 }
 
@@ -163,6 +201,7 @@
     case Text: return "text";
     case Colour3DPlot: return "colour3d";
     case Spectrum: return "spectrum";
+    case Slice: return "spectrum";
     default: return "unknown";
     }
 }
@@ -180,6 +219,7 @@
     case Text: return "text";
     case Colour3DPlot: return "colour3dplot";
     case Spectrum: return "spectrum";
+    case Slice: return "slice";
     default: return "unknown";
     }
 }
@@ -196,6 +236,7 @@
     if (name == "text") return Text;
     if (name == "colour3dplot") return Colour3DPlot;
     if (name == "spectrum") return Spectrum;
+    if (name == "slice") return Slice;
     return UnknownLayer;
 }
 
@@ -237,6 +278,9 @@
 
     if (trySetModel<SpectrumLayer, DenseTimeValueModel>(layer, model)) 
         return;
+
+//    if (trySetModel<SliceLayer, DenseThreeDimensionalModel>(layer, model)) 
+//        return;
 }
 
 Model *
@@ -325,6 +369,10 @@
         layer = new SpectrumLayer;
         break;
 
+    case Slice:
+        layer = new SliceLayer;
+        break;
+
     case MelodicRangeSpectrogram: 
 	layer = new SpectrogramLayer(SpectrogramLayer::MelodicRange);
 	break;
--- a/layer/LayerFactory.h	Wed Jan 24 17:14:24 2007 +0000
+++ b/layer/LayerFactory.h	Fri Jan 26 16:59:57 2007 +0000
@@ -37,6 +37,7 @@
 	Text,
 	Colour3DPlot,
         Spectrum,
+        Slice,
 
 	// Layers with different initial parameters
 	MelodicRangeSpectrogram,
@@ -60,6 +61,8 @@
 
     QString getLayerPresentationName(LayerType type);
 
+    bool isLayerSliceable(const Layer *);
+
     void setModel(Layer *layer, Model *model);
     Model *createEmptyModel(LayerType type, Model *baseModel);
 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/layer/SliceLayer.cpp	Fri Jan 26 16:59:57 2007 +0000
@@ -0,0 +1,572 @@
+
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */
+
+/*
+    Sonic Visualiser
+    An audio file viewer and annotation editor.
+    Centre for Digital Music, Queen Mary, University of London.
+    This file copyright 2006 QMUL.
+    
+    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 "SliceLayer.h"
+
+#include "view/View.h"
+#include "base/AudioLevel.h"
+#include "base/RangeMapper.h"
+
+#include <QPainter>
+#include <QPainterPath>
+
+SliceLayer::SliceLayer() :
+    m_sliceableModel(0),
+    m_colour(Qt::darkBlue),
+    m_energyScale(dBScale),
+    m_samplingMode(SamplePeak),
+    m_plotStyle(PlotLines),
+    m_binScale(LinearBins),
+    m_normalize(false),
+    m_bias(false);
+    m_gain(1.0)
+{
+}
+
+SliceLayer::~SliceLayer()
+{
+
+}
+
+void
+SliceLayer::setSliceableModel(const Model *model)
+{
+    const DenseThreeDimensionalModel *sliceable =
+        dynamic_cast<const DenseThreeDimensionalModel *>(model);
+
+    if (model && !sliceable) {
+        std::cerr << "WARNING: SliceLayer::setSliceableModel(" << model
+                  << "): model is not a DenseThreeDimensionalModel" << std::endl;
+    }
+
+    if (m_sliceableModel == sliceable) return;
+
+    m_sliceableModel = sliceable;
+
+    connect(m_sliceableModel, SIGNAL(modelChanged()),
+            this, SIGNAL(modelChanged()));
+
+    connect(m_sliceableModel, SIGNAL(modelChanged(size_t, size_t)),
+	    this, SIGNAL(modelChanged(size_t, size_t)));
+
+    connect(m_sliceableModel, SIGNAL(completionChanged()),
+	    this, SIGNAL(modelCompletionChanged()));
+
+    emit modelReplaced();
+}
+
+void
+SliceLayer::sliceableModelReplaced(const Model *orig, const Model *replacement)
+{
+    std::cerr << "SliceLayer::sliceableModelReplaced(" << orig << ", " << replacement << ")" << std::endl;
+
+    if (orig == m_sliceableModel) {
+        setSliceableModel
+            (dynamic_cast<const DenseThreeDimensionalModel *>(replacement));
+    }
+}
+
+void
+SliceLayer::modelAboutToBeDeleted(Model *m)
+{
+    std::cerr << "SliceLayer::modelAboutToBeDeleted(" << m << ")" << std::endl;
+
+    if (m == m_sliceableModel) {
+        setSliceableModel(0);
+    }
+}
+
+void
+SliceLayer::paint(View *v, QPainter &paint, QRect rect) const
+{
+    if (!m_sliceableModel) return;
+
+    int w = (v->width() * 2) / 3;
+    int xorigin = (v->width() / 2) - (w / 2);
+    
+    int h = (v->height() * 2) / 3;
+    int yorigin = (v->height() / 2) + (h / 2);
+
+    paint.save();
+    paint.setPen(m_colour);
+    paint.setRenderHint(QPainter::Antialiasing, false);
+    
+    QPainterPath path;
+    float thresh = -80.f;
+
+    int mh = m_sliceableModel->getHeight();
+
+    float *values = new float[mh];
+    int divisor = 0;
+
+    for (size_t bin = 0; bin < mh; ++bin) {
+        values[bin] = 0.f;
+    }
+
+    size_t f0 = v->getCentreFrame();
+    int f0x = v->getXForFrame(f0);
+    size_t f1 = v->getFrameForX(f0x + 1);
+
+    size_t col0 = f0 / m_sliceableModel->getResolution();
+    size_t col1 = col0;
+    if (m_samplingMode != NearestSample) {
+        col1 = f1 / m_sliceableModel->getResolution();
+    }
+    if (col1 <= col0) col1 = col0 + 1;
+
+    for (size_t col = col0; col < col1; ++col) {
+        for (size_t bin = 0; bin < mh; ++bin) {
+            float value = m_sliceableModel->getValueAt(col, bin);
+            if (m_bias) value *= bin + 1;
+            if (m_samplingMode == SamplePeak) {
+                if (value > values[bin]) values[bin] = value;
+            } else {
+                values[bin] += value;
+            }
+        }
+        ++divisor;
+    }
+
+    float max = 0.f;
+    for (size_t bin = 0; bin < mh; ++bin) {
+        if (m_samplingMode == SampleMean) values[bin] /= divisor;
+        if (values[bin] > max) max = values[bin];
+    }
+    if (max != 0.f && m_normalize) {
+        for (size_t bin = 0; bin < mh; ++bin) {
+            values[bin] /= max;
+        }
+    }
+
+    float py = 0;
+    float nx = xorigin;
+
+    for (size_t bin = 0; bin < mh; ++bin) {
+
+        float x;
+
+        switch (m_binScale) {
+
+        case LinearBins:
+            x = nx;
+            nx = xorigin + (float(w) * (bin + 1)) / mh;
+            break;
+
+        case LogBins:
+            x = nx;
+            nx = xorigin + (float(w) * (log10f(bin + 2) - log10f(1))) /
+                (log10f(mh + 1) - log10f(1));
+            break;
+
+        case InvertedLogBins:
+            x = nx;
+            nx = xorigin + w - (float(w) * (log10f(mh - bin) - log10f(1))) /
+                (log10f(mh) - log10f(1));
+            break;
+        }
+
+        float value = values[bin];
+
+        value *= m_gain;
+        float y = 0.f;
+ 
+        switch (m_energyScale) {
+
+        case dBScale:
+        {
+            float db = thresh;
+            if (value > 0.f) db = 10.f * log10f(value);
+            if (db < thresh) db = thresh;
+            float val = (db - thresh) / -thresh;
+            y = yorigin - (float(h) * val);
+            break;
+        }
+
+        case MeterScale:
+            y = yorigin - AudioLevel::multiplier_to_preview(value, h);
+            break;
+
+        default:
+            y = yorigin - (float(h) * value);
+            break;
+        }
+
+        if (m_plotStyle == PlotLines) {
+
+            if (bin == 0) {
+                path.moveTo(x, y);
+            } else {
+                path.lineTo(x, y);
+            }
+
+        } else if (m_plotStyle == PlotSteps) {
+
+            if (bin == 0) {
+                path.moveTo(x, y);
+            } else {
+                path.lineTo(x, y);
+            }
+            path.lineTo(nx, y);
+
+        } else if (m_plotStyle == PlotBlocks) {
+
+            path.moveTo(x, yorigin);
+            path.lineTo(x, y);
+            path.lineTo(nx, y);
+            path.lineTo(nx, yorigin);
+            path.lineTo(x, yorigin);
+        }
+
+        py = y;
+    }
+
+    paint.drawPath(path);
+    paint.restore();
+
+}
+
+Layer::PropertyList
+SliceLayer::getProperties() const
+{
+    PropertyList list;
+    list.push_back("Colour");
+    list.push_back("Plot Type");
+    list.push_back("Sampling Mode");
+    list.push_back("Scale");
+    list.push_back("Normalize");
+    list.push_back("Gain");
+    list.push_back("Bin Scale");
+
+    return list;
+}
+
+QString
+SliceLayer::getPropertyLabel(const PropertyName &name) const
+{
+    if (name == "Colour") return tr("Colour");
+    if (name == "Plot Type") return tr("Plot Type");
+    if (name == "Energy Scale") return tr("Scale");
+    if (name == "Normalize") return tr("Normalize");
+    if (name == "Gain") return tr("Gain");
+    if (name == "Sampling Mode") return tr("Sampling Mode");
+    if (name == "Bin Scale") return tr("Plot X Scale");
+    return "";
+}
+
+Layer::PropertyType
+SliceLayer::getPropertyType(const PropertyName &name) const
+{
+    if (name == "Gain") return RangeProperty;
+    if (name == "Normalize") return ToggleProperty;
+    return ValueProperty;
+}
+
+QString
+SliceLayer::getPropertyGroupName(const PropertyName &name) const
+{
+    if (name == "Scale" ||
+        name == "Normalize" ||
+        name == "Sampling Mode" ||
+        name == "Gain") return tr("Scale");
+    if (name == "Plot Type" ||
+        name == "Bin Scale") return tr("Plot Type");
+    return QString();
+}
+
+int
+SliceLayer::getPropertyRangeAndValue(const PropertyName &name,
+                                        int *min, int *max) const
+{
+    int deft = 0;
+
+    int garbage0, garbage1;
+    if (!min) min = &garbage0;
+    if (!max) max = &garbage1;
+
+    if (name == "Gain") {
+
+	*min = -50;
+	*max = 50;
+
+        std::cerr << "gain is " << m_gain << ", mode is " << m_samplingMode << std::endl;
+
+	deft = lrint(log10(m_gain) * 20.0);
+	if (deft < *min) deft = *min;
+	if (deft > *max) deft = *max;
+
+    } else if (name == "Normalize") {
+	
+	deft = (m_normalize ? 1 : 0);
+
+    } else if (name == "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 == "Scale") {
+
+	*min = 0;
+	*max = 2;
+
+	deft = (int)m_energyScale;
+
+    } else if (name == "Sampling Mode") {
+
+	*min = 0;
+	*max = 2;
+        
+	deft = (int)m_samplingMode;
+
+    } else if (name == "Plot Type") {
+        
+        *min = 0;
+        *max = 2;
+
+        deft = (int)m_plotStyle;
+
+    } else if (name == "Bin Scale") {
+        
+        *min = 0;
+        *max = 2;
+
+        deft = (int)m_binScale;
+
+    } else {
+	deft = Layer::getPropertyRangeAndValue(name, min, max);
+    }
+
+    return deft;
+}
+
+QString
+SliceLayer::getPropertyValueLabel(const PropertyName &name,
+				    int value) const
+{
+    if (name == "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 == "Scale") {
+	switch (value) {
+	default:
+	case 0: return tr("Linear");
+	case 1: return tr("Meter");
+	case 2: return tr("dB");
+	}
+    }
+    if (name == "Sampling Mode") {
+	switch (value) {
+	default:
+	case 0: return tr("Any");
+	case 1: return tr("Mean");
+	case 2: return tr("Peak");
+	}
+    }
+    if (name == "Plot Type") {
+	switch (value) {
+	default:
+	case 0: return tr("Lines");
+	case 1: return tr("Steps");
+	case 2: return tr("Blocks");
+	}
+    }
+    if (name == "Bin Scale") {
+	switch (value) {
+	default:
+	case 0: return tr("Linear");
+	case 1: return tr("Log");
+	case 2: return tr("Rev Log");
+	}
+    }
+    return tr("<unknown>");
+}
+
+RangeMapper *
+SliceLayer::getNewPropertyRangeMapper(const PropertyName &name) const
+{
+    if (name == "Gain") {
+        return new LinearRangeMapper(-50, 50, -25, 25, tr("dB"));
+    }
+    return 0;
+}
+
+void
+SliceLayer::setProperty(const PropertyName &name, int value)
+{
+    if (name == "Gain") {
+	setGain(pow(10, float(value)/20.0));
+    } else if (name == "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 == "Scale") {
+	switch (value) {
+	default:
+	case 0: setEnergyScale(LinearScale); break;
+	case 1: setEnergyScale(MeterScale); break;
+	case 2: setEnergyScale(dBScale); break;
+	}
+    } else if (name == "Plot Type") {
+	setPlotStyle(PlotStyle(value));
+    } else if (name == "Sampling Mode") {
+	switch (value) {
+	default:
+	case 0: setSamplingMode(NearestSample); break;
+	case 1: setSamplingMode(SampleMean); break;
+	case 2: setSamplingMode(SamplePeak); break;
+	}
+    } else if (name == "Bin Scale") {
+	switch (value) {
+	default:
+	case 0: setBinScale(LinearBins); break;
+	case 1: setBinScale(LogBins); break;
+	case 2: setBinScale(InvertedLogBins); break;
+	}
+    } else if (name == "Normalize") {
+	setNormalize(value ? true : false);
+    }
+}
+
+void
+SliceLayer::setBaseColour(QColor colour)
+{
+    if (m_colour == colour) return;
+    m_colour = colour;
+    emit layerParametersChanged();
+}
+
+void
+SliceLayer::setEnergyScale(EnergyScale scale)
+{
+    if (m_energyScale == scale) return;
+    m_energyScale = scale;
+    emit layerParametersChanged();
+}
+
+void
+SliceLayer::setSamplingMode(SamplingMode mode)
+{
+    if (m_samplingMode == mode) return;
+    m_samplingMode = mode;
+    emit layerParametersChanged();
+}
+
+void
+SliceLayer::setPlotStyle(PlotStyle style)
+{
+    if (m_plotStyle == style) return;
+    m_plotStyle = style;
+    emit layerParametersChanged();
+}
+
+void
+SliceLayer::setBinScale(BinScale scale)
+{
+    if (m_binScale == scale) return;
+    m_binScale = scale;
+    emit layerParametersChanged();
+}
+
+void
+SliceLayer::setNormalize(bool n)
+{
+    if (m_normalize == n) return;
+    m_normalize = n;
+    emit layerParametersChanged();
+}
+
+void
+SliceLayer::setGain(float gain)
+{
+    if (m_gain == gain) return;
+    m_gain = gain;
+    emit layerParametersChanged();
+}
+
+QString
+SliceLayer::toXmlString(QString indent, QString extraAttributes) const
+{
+    QString s;
+    
+    s += QString("colour=\"%1\" "
+		 "energyScale=\"%2\" "
+                 "samplingMode=\"%3\" "
+                 "gain=\"%4\" "
+                 "normalize=\"%5\"")
+	.arg(encodeColour(m_colour))
+	.arg(m_energyScale)
+        .arg(m_samplingMode)
+        .arg(m_gain)
+        .arg(m_normalize ? "true" : "false");
+
+    return Layer::toXmlString(indent, extraAttributes + " " + s);
+}
+
+void
+SliceLayer::setProperties(const QXmlAttributes &attributes)
+{
+    bool ok = false;
+
+    QString colourSpec = attributes.value("colour");
+    if (colourSpec != "") {
+	QColor colour(colourSpec);
+	if (colour.isValid()) {
+	    setBaseColour(QColor(colourSpec));
+	}
+    }
+
+    EnergyScale scale = (EnergyScale)
+	attributes.value("energyScale").toInt(&ok);
+    if (ok) setEnergyScale(scale);
+
+    SamplingMode mode = (SamplingMode)
+	attributes.value("samplingMode").toInt(&ok);
+    if (ok) setSamplingMode(mode);
+
+    float gain = attributes.value("gain").toFloat(&ok);
+    if (ok) setGain(gain);
+
+    bool normalize = (attributes.value("normalize").trimmed() == "true");
+    setNormalize(normalize);
+}
+
+bool
+SliceLayer::getValueExtents(float &min, float &max, bool &logarithmic,
+                               QString &units) const
+{
+    return false;
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/layer/SliceLayer.h	Fri Jan 26 16:59:57 2007 +0000
@@ -0,0 +1,109 @@
+
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */
+
+/*
+    Sonic Visualiser
+    An audio file viewer and annotation editor.
+    Centre for Digital Music, Queen Mary, University of London.
+    This file copyright 2006 QMUL.
+    
+    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.
+*/
+
+#ifndef _SLICE_LAYER_H_
+#define _SLICE_LAYER_H_
+
+#include "Layer.h"
+
+#include "base/Window.h"
+
+#include "data/model/DenseThreeDimensionalModel.h"
+
+#include <QColor>
+
+class SliceLayer : public Layer
+{
+    Q_OBJECT
+
+public:
+    SliceLayer();
+    ~SliceLayer();
+    
+//    virtual void setModel(const Model *model);
+//    virtual const Model *getModel() const { return m_model; }
+    virtual const Model *getModel() const { return 0; }
+
+    void setSliceableModel(const Model *model);    
+
+    virtual void paint(View *v, QPainter &paint, QRect rect) const;
+
+    virtual PropertyList getProperties() const;
+    virtual QString getPropertyLabel(const PropertyName &) 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 RangeMapper *getNewPropertyRangeMapper(const PropertyName &) const;
+    virtual void setProperty(const PropertyName &, int value);
+    virtual void setProperties(const QXmlAttributes &);
+
+    virtual bool getValueExtents(float &min, float &max,
+                                 bool &logarithmic, QString &unit) const;
+
+    virtual bool isLayerScrollable(const View *v) const { return false; }
+
+    enum EnergyScale { LinearScale, MeterScale, dBScale };
+
+    enum SamplingMode { NearestSample, SampleMean, SamplePeak };
+
+    enum PlotStyle { PlotLines, PlotSteps, PlotBlocks };
+
+    enum BinScale { LinearBins, LogBins, InvertedLogBins };
+
+    void setBaseColour(QColor);
+    QColor getBaseColour() const { return m_colour; }
+
+    void setEnergyScale(EnergyScale);
+    EnergyScale getEnergyScale() const { return m_energyScale; }
+
+    void setSamplingMode(SamplingMode);
+    SamplingMode getSamplingMode() const { return m_samplingMode; }
+
+    void setPlotStyle(PlotStyle style);
+    PlotStyle getPlotStyle() const { return m_plotStyle; }
+
+    void setBinScale(BinScale scale);
+    BinScale getBinScale() const { return m_binScale; }
+
+    void setGain(float gain);
+    float getGain() const;
+
+    void setNormalize(bool n);
+    bool getNormalize() const;
+
+    virtual QString toXmlString(QString indent = "",
+				QString extraAttributes = "") const;
+
+public slots:
+    void sliceableModelReplaced(const Model *, const Model *);
+    void modelAboutToBeDeleted(Model *);
+
+protected:
+    const DenseThreeDimensionalModel *m_sliceableModel;
+    QColor                            m_colour;
+    EnergyScale                       m_energyScale;
+    SamplingMode                      m_samplingMode;
+    PlotStyle                         m_plotStyle;
+    BinScale                          m_binScale;
+    bool                              m_normalize;
+    bool                              m_bias;
+    float                             m_gain;
+};
+
+#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/layer/SliceableLayer.h	Fri Jan 26 16:59:57 2007 +0000
@@ -0,0 +1,50 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */
+
+/*
+    Sonic Visualiser
+    An audio file viewer and annotation editor.
+    Centre for Digital Music, Queen Mary, University of London.
+    This file copyright 2007 QMUL.
+    
+    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.
+*/
+
+#ifndef _SLICEABLE_LAYER_H_
+#define _SLICEABLE_LAYER_H_
+
+#include "Layer.h"
+
+/**
+ * Base class for layers that can be sliced, that is, that contain
+ * models appropriate for use in a SliceLayer.
+ */
+
+class SliceableLayer : public Layer
+{
+    Q_OBJECT
+
+public:
+    // Get a model that can be sliced, i.e. a
+    // DenseThreeDimensionalModel.  This may be the layer's usual
+    // model, or it may be a model derived from it (e.g. FFTModel in a
+    // spectrogram that was constructed from a DenseTimeValueModel).
+    // The SliceableLayer retains ownership of the model, and will
+    // emit sliceableModelReplaced if it is about to become invalid.
+    virtual const Model *getSliceableModel() const = 0;
+
+signals:
+    // Emitted when a model that was obtained through
+    // getSliceableModel is about to be deleted.  If replacement is
+    // non-NULL, it may be used instead.
+    void sliceableModelReplaced(const Model *modelToBeReplaced,
+                                const Model *replacement);
+};
+
+#endif
+
+
+
--- a/layer/SpectrogramLayer.cpp	Wed Jan 24 17:14:24 2007 +0000
+++ b/layer/SpectrogramLayer.cpp	Fri Jan 26 16:59:57 2007 +0000
@@ -39,7 +39,6 @@
 //#define DEBUG_SPECTROGRAM_REPAINT 1
 
 SpectrogramLayer::SpectrogramLayer(Configuration config) :
-    Layer(),
     m_model(0),
     m_channel(0),
     m_windowSize(1024),
@@ -62,7 +61,8 @@
     m_lastEmittedZoomStep(-1),
     m_updateTimer(0),
     m_candidateFillStartFrame(0),
-    m_exiting(false)
+    m_exiting(false),
+    m_sliceableModel(0)
 {
     if (config == MelodicRange) {
 	setWindowSize(8192);
@@ -901,6 +901,20 @@
         m_pixmapCaches.erase(v);
 
         if (m_fftModels.find(v) != m_fftModels.end()) {
+
+            if (m_sliceableModel == m_fftModels[v].first) {
+                bool replaced = false;
+                for (ViewFFTMap::iterator i = m_fftModels.begin();
+                     i != m_fftModels.end(); ++i) {
+                    if (i->second.first != m_sliceableModel) {
+                        emit sliceableModelReplaced(m_sliceableModel, i->second.first);
+                        replaced = true;
+                        break;
+                    }
+                }
+                if (!replaced) emit sliceableModelReplaced(m_sliceableModel, 0);
+            }
+
             delete m_fftModels[v].first;
             m_fftModels.erase(v);
         }
@@ -1626,6 +1640,12 @@
             return 0;
         }
 
+        if (!m_sliceableModel) {
+            std::cerr << "SpectrogramLayer: emitting sliceableModelReplaced(0, " << model << ")" << std::endl;
+            ((SpectrogramLayer *)this)->sliceableModelReplaced(0, model);
+            m_sliceableModel = model;
+        }
+
         m_fftModels[v] = FFTFillPair(model, 0);
 
         model->resume();
@@ -1640,6 +1660,15 @@
     return m_fftModels[v].first;
 }
 
+const Model *
+SpectrogramLayer::getSliceableModel() const
+{
+    if (m_sliceableModel) return m_sliceableModel;
+    if (m_fftModels.empty()) return 0;
+    m_sliceableModel = m_fftModels.begin()->second.first;
+    return m_sliceableModel;
+}
+
 void
 SpectrogramLayer::invalidateFFTModels()
 {
@@ -1649,6 +1678,12 @@
     }
     
     m_fftModels.clear();
+
+    if (m_sliceableModel) {
+        std::cerr << "SpectrogramLayer: emitting sliceableModelReplaced(" << m_sliceableModel << ", 0)" << std::endl;
+        emit sliceableModelReplaced(m_sliceableModel, 0);
+        m_sliceableModel = 0;
+    }
 }
 
 void
--- a/layer/SpectrogramLayer.h	Wed Jan 24 17:14:24 2007 +0000
+++ b/layer/SpectrogramLayer.h	Fri Jan 26 16:59:57 2007 +0000
@@ -16,7 +16,7 @@
 #ifndef _SPECTROGRAM_LAYER_H_
 #define _SPECTROGRAM_LAYER_H_
 
-#include "Layer.h"
+#include "SliceableLayer.h"
 #include "base/Window.h"
 #include "base/RealTime.h"
 #include "base/Thread.h"
@@ -43,7 +43,7 @@
  * DenseTimeValueModel) in spectrogram form.
  */
 
-class SpectrogramLayer : public Layer,
+class SpectrogramLayer : public SliceableLayer,
 			 public PowerOfSqrtTwoZoomConstraint
 {
     Q_OBJECT
@@ -215,6 +215,8 @@
     virtual void setVerticalZoomStep(int);
     virtual RangeMapper *getNewVerticalZoomRangeMapper() const;
 
+    virtual const Model *getSliceableModel() const;
+
 protected slots:
     void cacheInvalid();
     void cacheInvalid(size_t startFrame, size_t endFrame);
@@ -337,6 +339,7 @@
     typedef std::map<const View *, FFTFillPair> ViewFFTMap;
     typedef std::vector<float> FloatVector;
     mutable ViewFFTMap m_fftModels;
+    mutable Model *m_sliceableModel;
 
     class MagnitudeRange {
     public:
--- a/layer/SpectrumLayer.cpp	Wed Jan 24 17:14:24 2007 +0000
+++ b/layer/SpectrumLayer.cpp	Fri Jan 26 16:59:57 2007 +0000
@@ -22,18 +22,10 @@
 #include "base/Preferences.h"
 #include "base/RangeMapper.h"
 
-#include <QPainter>
-#include <QPainterPath>
-
 SpectrumLayer::SpectrumLayer() :
-    m_model(0),
-    m_channelMode(MixChannels),
+    m_originModel(0),
     m_channel(-1),
     m_channelSet(false),
-    m_colour(Qt::darkBlue),
-    m_energyScale(dBScale),
-    m_normalize(false),
-    m_gain(1.0),
     m_windowSize(1024),
     m_windowType(HanningWindow),
     m_windowHopLevel(2)
@@ -46,45 +38,40 @@
 
 SpectrumLayer::~SpectrumLayer()
 {
-    for (size_t i = 0; i < m_fft.size(); ++i) delete m_fft[i];
+    //!!! delete parent's model
+//    for (size_t i = 0; i < m_fft.size(); ++i) delete m_fft[i];
 }
 
 void
 SpectrumLayer::setModel(DenseTimeValueModel *model)
 {
-    m_model = model;
-    setupFFTs();
+    if (m_originModel == model) return;
+    m_originModel = model;
+    setupFFT();
 }
 
 void
-SpectrumLayer::setupFFTs()
+SpectrumLayer::setupFFT()
 {
-    for (size_t i = 0; i < m_fft.size(); ++i) delete m_fft[i];
-    m_fft.clear();
-
-    int minChannel = m_channel, maxChannel = m_channel;
+    FFTModel *oldFFT = dynamic_cast<FFTModel *>
+        (const_cast<DenseThreeDimensionalModel *>(m_sliceableModel));
     
-    if (m_channel == -1 &&
-        m_channelMode != MixChannels) {
-        minChannel = 0;
-        maxChannel = 0;
-        if (m_model->getChannelCount() > 1) {
-            maxChannel = m_model->getChannelCount() - 1;
-        }
+    if (oldFFT) {
+        setSliceableModel(0);
+        delete oldFFT;
     }
 
-    for (int c = minChannel; c <= maxChannel; ++c) {
-        
-        m_fft.push_back(new FFTModel(m_model,
-                                     c,
-                                     HanningWindow,
-                                     m_windowSize,
-                                     getWindowIncrement(),
-                                     m_windowSize,
-                                     true));
+    FFTModel *newFFT = new FFTModel(m_originModel,
+                                    m_channel,
+                                    m_windowType,
+                                    m_windowSize,
+                                    getWindowIncrement(),
+                                    m_windowSize,
+                                    true);
 
-        if (m_channelSet) m_fft[m_fft.size()-1]->resume();
-    }
+    setSliceableModel(newFFT);
+
+    newFFT->resume();
 }
 
 void
@@ -92,133 +79,44 @@
 {
     m_channelSet = true;
 
+    FFTModel *fft = dynamic_cast<FFTModel *>
+        (const_cast<DenseThreeDimensionalModel *>(m_sliceableModel));
+
     if (m_channel == channel) {
-        for (size_t i = 0; i < m_fft.size(); ++i) {
-            m_fft[i]->resume();
-        }
+        if (fft) fft->resume();
         return;
     }
 
     m_channel = channel;
 
-    if (!m_fft.empty()) setupFFTs();
+    if (!fft) setupFFT();
 
     emit layerParametersChanged();
 }
 
-void
-SpectrumLayer::paint(View *v, QPainter &paint, QRect rect) const
-{
-    if (m_fft.empty()) return;
-    if (!m_channelSet) {
-        for (size_t i = 0; i < m_fft.size(); ++i) {
-            m_fft[i]->resume();
-        }
-    }
-
-    FFTModel *fft = m_fft[0]; //!!! for now
-
-    int windowIncrement = getWindowIncrement();
-
-    size_t f = v->getCentreFrame();
-
-    int w = (v->width() * 2) / 3;
-    int xorigin = (v->width() / 2) - (w / 2);
-    
-    int h = (v->height() * 2) / 3;
-    int yorigin = (v->height() / 2) + (h / 2);
-
-    size_t column = f / windowIncrement;
-
-    paint.save();
-    paint.setPen(m_colour);
-    paint.setRenderHint(QPainter::Antialiasing, false);
-    
-    QPainterPath path;
-    float thresh = -80.f;
-
-    for (size_t bin = 0; bin < fft->getHeight(); ++bin) {
-
-        float x = xorigin + (float(w) * bin) / fft->getHeight();
-        float mag;
-        if (m_normalize) {
-            mag = fft->getNormalizedMagnitudeAt(column, bin);
-        } else {
-            mag = fft->getMagnitudeAt(column, bin);
-        }
-        mag *= m_gain;
-        float y = 0.f;
- 
-        switch (m_energyScale) {
-
-        case dBScale:
-        {
-            float db = thresh;
-            if (mag > 0.f) db = 10.f * log10f(mag);
-            if (db < thresh) db = thresh;
-            float val = (db - thresh) / -thresh;
-            y = yorigin - (float(h) * val);
-            break;
-        }
-
-        case MeterScale:
-            y = yorigin - AudioLevel::multiplier_to_preview(mag, h);
-            break;
-
-        default:
-            y = yorigin - (float(h) * mag);
-            break;
-        }
-
-        if (bin == 0) {
-            path.moveTo(x, y);
-        } else {
-            path.lineTo(x, y);
-        }
-    }
-
-    paint.drawPath(path);
-    paint.restore();
-
-}
-
 Layer::PropertyList
 SpectrumLayer::getProperties() const
 {
-    PropertyList list;
-    list.push_back("Colour");
-    list.push_back("Scale");
-    list.push_back("Normalize");
-    list.push_back("Gain");
+    PropertyList list = SliceLayer::getProperties();
     list.push_back("Window Size");
     list.push_back("Window Increment");
-
-    if (m_model && m_model->getChannelCount() > 1 && m_channel == -1) {
-        list.push_back("Channels");
-    }
-
     return list;
 }
 
 QString
 SpectrumLayer::getPropertyLabel(const PropertyName &name) const
 {
-    if (name == "Colour") return tr("Colour");
-    if (name == "Energy Scale") return tr("Scale");
-    if (name == "Channels") return tr("Channels");
     if (name == "Window Size") return tr("Window Size");
     if (name == "Window Increment") return tr("Window Overlap");
-    if (name == "Normalize") return tr("Normalize");
-    if (name == "Gain") return tr("Gain");
-    return "";
+    return SliceLayer::getPropertyLabel(name);
 }
 
 Layer::PropertyType
 SpectrumLayer::getPropertyType(const PropertyName &name) const
 {
-    if (name == "Gain") return RangeProperty;
-    if (name == "Normalize") return ToggleProperty;
-    return ValueProperty;
+    if (name == "Window Size") return ValueProperty;
+    if (name == "Window Increment") return ValueProperty;
+    return SliceLayer::getPropertyType(name);
 }
 
 QString
@@ -226,10 +124,7 @@
 {
     if (name == "Window Size" ||
 	name == "Window Increment") return tr("Window");
-    if (name == "Scale" ||
-        name == "Normalize" ||
-        name == "Gain") return tr("Energy Scale");
-    return QString();
+    return SliceLayer::getPropertyGroupName(name);
 }
 
 int
@@ -242,47 +137,7 @@
     if (!min) min = &garbage0;
     if (!max) max = &garbage1;
 
-    if (name == "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 == "Normalize") {
-	
-	deft = (m_normalize ? 1 : 0);
-
-    } else if (name == "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 == "Channels") {
-
-        *min = 0;
-        *max = 2;
-        if (m_channelMode == MixChannels) deft = 1;
-        else if (m_channelMode == OverlayChannels) deft = 2;
-        else deft = 0;
-
-    } else if (name == "Scale") {
-
-	*min = 0;
-	*max = 2;
-
-	deft = (int)m_energyScale;
-
-    } else if (name == "Window Size") {
+    if (name == "Window Size") {
 
 	*min = 0;
 	*max = 10;
@@ -299,7 +154,8 @@
         deft = m_windowHopLevel;
     
     } else {
-	deft = Layer::getPropertyRangeAndValue(name, min, max);
+
+        deft = SliceLayer::getPropertyRangeAndValue(name, min, max);
     }
 
     return deft;
@@ -309,33 +165,6 @@
 SpectrumLayer::getPropertyValueLabel(const PropertyName &name,
 				    int value) const
 {
-    if (name == "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 == "Scale") {
-	switch (value) {
-	default:
-	case 0: return tr("Linear");
-	case 1: return tr("Meter");
-	case 2: return tr("dB");
-	}
-    }
-    if (name == "Channels") {
-        switch (value) {
-        default:
-        case 0: return tr("Separate");
-        case 1: return tr("Mean");
-        case 2: return tr("Overlay");
-        }
-    }
     if (name == "Window Size") {
 	return QString("%1").arg(32 << value);
     }
@@ -350,83 +179,33 @@
 	case 5: return tr("93.75 %");
 	}
     }
-    return tr("<unknown>");
+    return SliceLayer::getPropertyValueLabel(name, value);
 }
 
 RangeMapper *
 SpectrumLayer::getNewPropertyRangeMapper(const PropertyName &name) const
 {
-    if (name == "Gain") {
-        return new LinearRangeMapper(-50, 50, -25, 25, tr("dB"));
-    }
-    return 0;
+    return SliceLayer::getNewPropertyRangeMapper(name);
 }
 
 void
 SpectrumLayer::setProperty(const PropertyName &name, int value)
 {
-    if (name == "Gain") {
-	setGain(pow(10, float(value)/20.0));
-    } else if (name == "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 == "Channels") {
-        if (value == 1) setChannelMode(MixChannels);
-        else if (value == 2) setChannelMode(OverlayChannels);
-        else setChannelMode(SeparateChannels);
-    } else if (name == "Scale") {
-	switch (value) {
-	default:
-	case 0: setEnergyScale(LinearScale); break;
-	case 1: setEnergyScale(MeterScale); break;
-	case 2: setEnergyScale(dBScale); break;
-	}
-    } else if (name == "Window Size") {
+    if (name == "Window Size") {
 	setWindowSize(32 << value);
     } else if (name == "Window Increment") {
         setWindowHopLevel(value);
-    } else if (name == "Normalize") {
-	setNormalize(value ? true : false);
+    } else {
+        SliceLayer::setProperty(name, value);
     }
 }
 
 void
-SpectrumLayer::setBaseColour(QColor colour)
-{
-    if (m_colour == colour) return;
-    m_colour = colour;
-    emit layerParametersChanged();
-}
-
-void
-SpectrumLayer::setChannelMode(ChannelMode channelMode)
-{
-    if (m_channelMode == channelMode) return;
-    m_channelMode = channelMode;
-    emit layerParametersChanged();
-}
-
-void
-SpectrumLayer::setEnergyScale(EnergyScale scale)
-{
-    if (m_energyScale == scale) return;
-    m_energyScale = scale;
-    emit layerParametersChanged();
-}
-
-void
 SpectrumLayer::setWindowSize(size_t ws)
 {
     if (m_windowSize == ws) return;
     m_windowSize = ws;
-    setupFFTs();
+    setupFFT();
     emit layerParametersChanged();
 }
 
@@ -435,7 +214,7 @@
 {
     if (m_windowHopLevel == v) return;
     m_windowHopLevel = v;
-    setupFFTs();
+    setupFFT();
     emit layerParametersChanged();
 }
 
@@ -444,23 +223,7 @@
 {
     if (m_windowType == w) return;
     m_windowType = w;
-    setupFFTs();
-    emit layerParametersChanged();
-}
-
-void
-SpectrumLayer::setNormalize(bool n)
-{
-    if (m_normalize == n) return;
-    m_normalize = n;
-    emit layerParametersChanged();
-}
-
-void
-SpectrumLayer::setGain(float gain)
-{
-    if (m_gain == gain) return;
-    m_gain = gain;
+    setupFFT();
     emit layerParametersChanged();
 }
 
@@ -478,24 +241,12 @@
 {
     QString s;
     
-    s += QString("colour=\"%1\" "
-		 "channelMode=\"%2\" "
-		 "channel=\"%3\" "
-		 "energyScale=\"%4\" "
-		 "windowSize=\"%5\" "
-		 "windowHopLevel=\"%6\" "
-                 "gain=\"%7\" "
-                 "normalize=\"%8\"")
-	.arg(encodeColour(m_colour))
-	.arg(m_channelMode)
-	.arg(m_channel)
-	.arg(m_energyScale)
+    s += QString("windowSize=\"%1\" "
+		 "windowHopLevel=\"%2\"")
         .arg(m_windowSize)
-        .arg(m_windowHopLevel)
-        .arg(m_gain)
-        .arg(m_normalize ? "true" : "false");
+        .arg(m_windowHopLevel);
 
-    return Layer::toXmlString(indent, extraAttributes + " " + s);
+    return SliceLayer::toXmlString(indent, extraAttributes + " " + s);
 }
 
 void
@@ -503,36 +254,11 @@
 {
     bool ok = false;
 
-    QString colourSpec = attributes.value("colour");
-    if (colourSpec != "") {
-	QColor colour(colourSpec);
-	if (colour.isValid()) {
-	    setBaseColour(QColor(colourSpec));
-	}
-    }
-
-    ChannelMode channelMode = (ChannelMode)
-	attributes.value("channelMode").toInt(&ok);
-    if (ok) setChannelMode(channelMode);
-
-    int channel = attributes.value("channel").toInt(&ok);
-    if (ok) setChannel(channel);
-
-    EnergyScale scale = (EnergyScale)
-	attributes.value("energyScale").toInt(&ok);
-    if (ok) setEnergyScale(scale);
-
     size_t windowSize = attributes.value("windowSize").toUInt(&ok);
     if (ok) setWindowSize(windowSize);
 
     size_t windowHopLevel = attributes.value("windowHopLevel").toUInt(&ok);
     if (ok) setWindowHopLevel(windowHopLevel);
-
-    float gain = attributes.value("gain").toFloat(&ok);
-    if (ok) setGain(gain);
-
-    bool normalize = (attributes.value("normalize").trimmed() == "true");
-    setNormalize(normalize);
 }
 
 bool
--- a/layer/SpectrumLayer.h	Wed Jan 24 17:14:24 2007 +0000
+++ b/layer/SpectrumLayer.h	Fri Jan 26 16:59:57 2007 +0000
@@ -17,7 +17,7 @@
 #ifndef _SPECTRUM_LAYER_H_
 #define _SPECTRUM_LAYER_H_
 
-#include "Layer.h"
+#include "SliceLayer.h"
 
 #include "base/Window.h"
 
@@ -27,7 +27,7 @@
 
 class FFTModel;
 
-class SpectrumLayer : public Layer
+class SpectrumLayer : public SliceLayer
 {
     Q_OBJECT
 
@@ -36,8 +36,7 @@
     ~SpectrumLayer();
     
     void setModel(DenseTimeValueModel *model);
-    virtual const Model *getModel() const { return m_model; }
-    virtual void paint(View *v, QPainter &paint, QRect rect) const;
+    virtual const Model *getModel() const { return m_originModel; }
 
     virtual PropertyList getProperties() const;
     virtual QString getPropertyLabel(const PropertyName &) const;
@@ -56,22 +55,9 @@
 
     virtual bool isLayerScrollable(const View *v) const { return false; }
 
-    enum ChannelMode { SeparateChannels, MixChannels, OverlayChannels };
-
-    void setChannelMode(ChannelMode);
-    ChannelMode getChannelCount() const { return m_channelMode; }
-
     void setChannel(int);
     int getChannel() const { return m_channel; }
 
-    enum EnergyScale { LinearScale, MeterScale, dBScale };
-
-    void setBaseColour(QColor);
-    QColor getBaseColour() const { return m_colour; }
-
-    void setEnergyScale(EnergyScale);
-    EnergyScale getEnergyScale() const { return m_energyScale; }
-
     void setWindowSize(size_t);
     size_t getWindowSize() const { return m_windowSize; }
     
@@ -81,12 +67,6 @@
     void setWindowType(WindowType type);
     WindowType getWindowType() const { return m_windowType; }
 
-    void setGain(float gain);
-    float getGain() const;
-
-    void setNormalize(bool n);
-    bool getNormalize() const;
-
     virtual QString toXmlString(QString indent = "",
 				QString extraAttributes = "") const;
 
@@ -94,20 +74,19 @@
     void preferenceChanged(PropertyContainer::PropertyName name);
 
 protected:
-    DenseTimeValueModel    *m_model;
-    std::vector<FFTModel *> m_fft;
-    ChannelMode             m_channelMode;
+    // make this SliceLayer method unavailable to the general public
+//    virtual void setModel(DenseThreeDimensionalModel *model) {
+//        SliceLayer::setModel(model);
+//    }
+
+    DenseTimeValueModel    *m_originModel;
     int                     m_channel;
     bool                    m_channelSet;
-    QColor                  m_colour;
-    EnergyScale             m_energyScale;
-    bool                    m_normalize;
-    float                   m_gain;
     size_t                  m_windowSize;
     WindowType              m_windowType;
     size_t                  m_windowHopLevel;
 
-    void setupFFTs();
+    void setupFFT();
 
     size_t getWindowIncrement() const {
         if (m_windowHopLevel == 0) return m_windowSize;
--- a/layer/layer.pro	Wed Jan 24 17:14:24 2007 +0000
+++ b/layer/layer.pro	Fri Jan 26 16:59:57 2007 +0000
@@ -20,6 +20,8 @@
            NoteLayer.h \
            SpectrogramLayer.h \
            SpectrumLayer.h \
+           SliceLayer.h \
+           SliceableLayer.h \
            TextLayer.h \
            TimeInstantLayer.h \
            TimeRulerLayer.h \
@@ -31,6 +33,7 @@
            NoteLayer.cpp \
            SpectrogramLayer.cpp \
            SpectrumLayer.cpp \
+           SliceLayer.cpp \
            TextLayer.cpp \
            TimeInstantLayer.cpp \
            TimeRulerLayer.cpp \
--- a/widgets/Panner.cpp	Wed Jan 24 17:14:24 2007 +0000
+++ b/widgets/Panner.cpp	Fri Jan 26 16:59:57 2007 +0000
@@ -104,7 +104,7 @@
 void
 Panner::wheelEvent(QWheelEvent *e)
 {
-    if (e->delta() > 0) {
+    if (e->delta() < 0) {
         m_rectY += 0.1;
     } else {
         m_rectY -= 0.1;
--- a/widgets/PropertyBox.cpp	Wed Jan 24 17:14:24 2007 +0000
+++ b/widgets/PropertyBox.cpp	Fri Jan 26 16:59:57 2007 +0000
@@ -118,6 +118,8 @@
 
     Layer *layer = dynamic_cast<Layer *>(m_container);
     if (layer) {
+	disconnect(layer, SIGNAL(modelReplaced()),
+                   this, SLOT(populateViewPlayFrame()));
 	connect(layer, SIGNAL(modelReplaced()),
 		this, SLOT(populateViewPlayFrame()));
     }