changeset 147:3a13b0d4934e

* Reorganising code base. This revision will not compile.
author Chris Cannam
date Mon, 31 Jul 2006 11:44:37 +0000
parents f90fad823cea
children 1a42221a1522
files base/Layer.cpp base/Layer.h base/View.cpp base/View.h base/ViewManager.cpp base/ViewManager.h data/model/DenseThreeDimensionalModel.cpp data/model/DenseThreeDimensionalModel.h data/model/DenseTimeValueModel.cpp data/model/DenseTimeValueModel.h data/model/NoteModel.cpp data/model/NoteModel.h data/model/PowerOfSqrtTwoZoomConstraint.cpp data/model/PowerOfSqrtTwoZoomConstraint.h data/model/PowerOfTwoZoomConstraint.cpp data/model/PowerOfTwoZoomConstraint.h data/model/RangeSummarisableTimeValueModel.cpp data/model/RangeSummarisableTimeValueModel.h data/model/SparseModel.h data/model/SparseOneDimensionalModel.h data/model/SparseTimeValueModel.h data/model/SparseValueModel.h data/model/TextModel.h data/model/WaveFileModel.cpp data/model/WaveFileModel.h
diffstat 25 files changed, 2698 insertions(+), 2841 deletions(-) [+]
line wrap: on
line diff
--- a/base/Layer.cpp	Fri Jul 28 11:09:36 2006 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,105 +0,0 @@
-/* -*- 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 Chris Cannam.
-    
-    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 "Layer.h"
-#include "View.h"
-#include "Model.h"
-
-#include <iostream>
-
-#include "layer/LayerFactory.h" //!!! shouldn't be including this here -- does that suggest we need to move this into layer/ ?
-#include "PlayParameterRepository.h"
-
-Layer::Layer()
-{
-}
-
-Layer::~Layer()
-{
-//    std::cerr << "Layer::~Layer(" << this << ")" << std::endl;
-}
-
-QString
-Layer::getPropertyContainerIconName() const
-{
-    return LayerFactory::getInstance()->getLayerIconName
-	(LayerFactory::getInstance()->getLayerType(this));
-}
-
-QString
-Layer::getLayerPresentationName() const
-{
-    QString layerName = objectName();
-    QString modelName;
-    if (getModel()) modelName = getModel()->objectName();
-	    
-    QString text;
-    if (modelName != "") {
-	text = QString("%1: %2").arg(modelName).arg(layerName);
-    } else {
-	text = layerName;
-    }
-	
-    return text;
-}
-
-void
-Layer::setObjectName(const QString &name)
-{
-    QObject::setObjectName(name);
-    emit layerNameChanged();
-}
-
-QString
-Layer::toXmlString(QString indent, QString extraAttributes) const
-{
-    QString s;
-    
-    s += indent;
-
-    s += QString("<layer id=\"%2\" type=\"%1\" name=\"%3\" model=\"%4\" %5/>\n")
-	.arg(encodeEntities(LayerFactory::getInstance()->getLayerTypeName
-                            (LayerFactory::getInstance()->getLayerType(this))))
-	.arg(getObjectExportId(this))
-	.arg(encodeEntities(objectName()))
-	.arg(getObjectExportId(getModel()))
-	.arg(extraAttributes);
-
-    return s;
-}
-
-PlayParameters *
-Layer::getPlayParameters() 
-{
-//    std::cerr << "Layer (" << this << ", " << objectName().toStdString() << ")::getPlayParameters: model is "<< getModel() << std::endl;
-    const Model *model = getModel();
-    if (model) {
-	return PlayParameterRepository::getInstance()->getPlayParameters(model);
-    }
-    return 0;
-}
-
-void
-Layer::showLayer(View *view, bool show)
-{
-    setLayerDormant(view, !show);
-    emit layerParametersChanged();
-}
-
-
-#ifdef INCLUDE_MOCFILES
-#include "Layer.moc.cpp"
-#endif
-
--- a/base/Layer.h	Fri Jul 28 11:09:36 2006 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,301 +0,0 @@
-
-/* -*- 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 Chris Cannam.
-    
-    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 _LAYER_H_
-#define _LAYER_H_
-
-#include "PropertyContainer.h"
-#include "XmlExportable.h"
-#include "Selection.h"
-
-#include <QObject>
-#include <QRect>
-#include <QXmlAttributes>
-
-#include <map>
-
-class ZoomConstraint;
-class Model;
-class QPainter;
-class View;
-class QMouseEvent;
-class Clipboard;
-
-/**
- * The base class for visual representations of the data found in a
- * Model.  Layers are expected to be able to draw themselves onto a
- * View, and may also be editable.
- */
-
-class Layer : public PropertyContainer,
-	      public XmlExportable
-{
-    Q_OBJECT
-
-public:
-    Layer();
-    virtual ~Layer();
-
-    virtual const Model *getModel() const = 0;
-    virtual Model *getModel() {
-	return const_cast<Model *>(const_cast<const Layer *>(this)->getModel());
-    }
-
-    virtual const ZoomConstraint *getZoomConstraint() const { return 0; }
-    virtual void paint(View *, QPainter &, QRect) const = 0;   
-
-    enum VerticalPosition {
-	PositionTop, PositionMiddle, PositionBottom
-    };
-    virtual VerticalPosition getPreferredTimeRulerPosition() const {
-	return PositionMiddle;
-    }
-    virtual VerticalPosition getPreferredFrameCountPosition() const {
-	return PositionBottom;
-    }
-
-    virtual QString getPropertyContainerIconName() const;
-
-    virtual QString getPropertyContainerName() const {
-	return objectName();
-    }
-
-    virtual QString getLayerPresentationName() const;
-
-    virtual int getVerticalScaleWidth(View *, QPainter &) const { return 0; }
-    virtual void paintVerticalScale(View *, QPainter &, QRect) const { }
-
-    virtual bool getCrosshairExtents(View *, QPainter &, QPoint /* cursorPos */,
-                                     std::vector<QRect> &) const {
-        return false;
-    }
-    virtual void paintCrosshairs(View *, QPainter &, QPoint) const { }
-
-    virtual QString getFeatureDescription(View *, QPoint &) const {
-	return "";
-    }
-
-    enum SnapType {
-	SnapLeft,
-	SnapRight,
-	SnapNearest,
-	SnapNeighbouring
-    };
-
-    /**
-     * Adjust the given frame to snap to the nearest feature, if
-     * possible.
-     *
-     * If snap is SnapLeft or SnapRight, adjust the frame to match
-     * that of the nearest feature in the given direction regardless
-     * of how far away it is.  If snap is SnapNearest, adjust the
-     * frame to that of the nearest feature in either direction.  If
-     * snap is SnapNeighbouring, adjust the frame to that of the
-     * nearest feature if it is close, and leave it alone (returning
-     * false) otherwise.  SnapNeighbouring should always choose the
-     * same feature that would be used in an editing operation through
-     * calls to editStart etc.
-     *
-     * Return true if a suitable feature was found and frame adjusted
-     * accordingly.  Return false if no suitable feature was
-     * available.  Also return the resolution of the model in this
-     * layer in sample frames.
-     */
-    virtual bool snapToFeatureFrame(View *   /* v */,
-				    int &    /* frame */,
-				    size_t &resolution,
-				    SnapType /* snap */) const {
-	resolution = 1;
-	return false;
-    }
-
-    // Draw and edit modes:
-    //
-    // Layer needs to get actual mouse events, I guess.  Draw mode is
-    // probably the easier.
-
-    virtual void drawStart(View *, QMouseEvent *) { }
-    virtual void drawDrag(View *, QMouseEvent *) { }
-    virtual void drawEnd(View *, QMouseEvent *) { }
-
-    virtual void editStart(View *, QMouseEvent *) { }
-    virtual void editDrag(View *, QMouseEvent *) { }
-    virtual void editEnd(View *, QMouseEvent *) { }
-
-    virtual void editOpen(View *, QMouseEvent *) { } // on double-click
-
-    virtual void moveSelection(Selection, size_t /* newStartFrame */) { }
-    virtual void resizeSelection(Selection, Selection /* newSize */) { }
-    virtual void deleteSelection(Selection) { }
-
-    virtual void copy(Selection, Clipboard & /* to */) { }
-
-    /**
-     * Paste from the given clipboard onto the layer at the given
-     * frame offset.  If interactive is true, the layer may ask the
-     * user about paste options through a dialog if desired, and may
-     * return false if the user cancelled the paste operation.  This
-     * function should return true if a paste actually occurred.
-     */
-    virtual bool paste(const Clipboard & /* from */,
-                       int /* frameOffset */,
-                       bool /* interactive */) { return false; }
-
-    // Text mode:
-    //
-    // Label nearest feature.  We need to get the feature coordinates
-    // and current label from the layer, and then the pane can pop up
-    // a little text entry dialog at the right location.  Or we edit
-    // in place?  Probably the dialog is easier.
-
-    /**
-     * This should return true if the layer can safely be scrolled
-     * automatically by a given view (simply copying the existing data
-     * and then refreshing the exposed area) without altering its
-     * meaning.  For the view widget as a whole this is usually not
-     * possible because of invariant (non-scrolling) material
-     * displayed over the top, but the widget may be able to optimise
-     * scrolling better if it is known that individual views can be
-     * scrolled safely in this way.
-     */
-    virtual bool isLayerScrollable(const View *) const { return true; }
-
-    /**
-     * This should return true if the layer completely obscures any
-     * underlying layers.  It's used to determine whether the view can
-     * safely draw any selection rectangles under the layer instead of
-     * over it, in the case where the layer is not scrollable and
-     * therefore needs to be redrawn each time (so that the selection
-     * rectangle can be cached).
-     */
-    virtual bool isLayerOpaque() const { return false; }
-
-    /**
-     * This should return true if the layer can be edited by the user.
-     * If this is the case, the appropriate edit tools may be made
-     * available by the application and the layer's drawStart/Drag/End
-     * and editStart/Drag/End methods should be implemented.
-     */
-    virtual bool isLayerEditable() const { return false; }
-
-    /**
-     * Return the proportion of background work complete in drawing
-     * this view, as a percentage -- in most cases this will be the
-     * value returned by pointer from a call to the underlying model's
-     * isReady(int *) call.  The widget may choose to show a progress
-     * meter if it finds that this returns < 100 at any given moment.
-     */
-    virtual int getCompletion(View *) const { return 100; }
-
-    virtual void setObjectName(const QString &name);
-
-    /**
-     * Convert the layer's data (though not those of the model it
-     * refers to) into an XML string for file output.  This class
-     * implements the basic name/type/model-id output; subclasses will
-     * typically call this superclass implementation with extra
-     * attributes describing their particular properties.
-     */
-    virtual QString toXmlString(QString indent = "",
-				QString extraAttributes = "") const;
-
-    /**
-     * Set the particular properties of a layer (those specific to the
-     * subclass) from a set of XML attributes.  This is the effective
-     * inverse of the toXmlString method.
-     */
-    virtual void setProperties(const QXmlAttributes &) = 0;
-
-    /**
-     * Indicate that a layer is not currently visible in the given
-     * view and is not expected to become visible in the near future
-     * (for example because the user has explicitly removed or hidden
-     * it).  The layer may respond by (for example) freeing any cache
-     * memory it is using, until next time its paint method is called,
-     * when it should set itself un-dormant again.
-     */
-    virtual void setLayerDormant(const View *v, bool dormant) {
-	m_dormancy[v] = dormant;
-    }
-
-    /**
-     * Return whether the layer is dormant (i.e. hidden) in the given
-     * view.
-     */
-    virtual bool isLayerDormant(const View *v) const {
-	if (m_dormancy.find(v) == m_dormancy.end()) return false;
-	return m_dormancy.find(v)->second;
-    }
-
-    virtual PlayParameters *getPlayParameters();
-
-    virtual bool needsTextLabelHeight() const { return false; }
-
-    /**
-     * Return the minimum and maximum values for the y axis of the
-     * model in this layer, as well as whether the layer is configured
-     * to use a logarithmic y axis display.  Also return the unit for
-     * these values if known.
-     *
-     * This function returns the "normal" extents for the layer, not
-     * necessarily the extents actually in use in the display.
-     */
-    virtual bool getValueExtents(float &min, float &max,
-                                 bool &logarithmic, QString &unit) const = 0;
-
-    /**
-     * Return the minimum and maximum values within the displayed
-     * range for the y axis, if only a subset of the whole range of
-     * the model (returned by getValueExtents) is being displayed.
-     * Return false if the layer is not imposing a particular display
-     * extent (using the normal layer extents or deferring to whatever
-     * is in use for the same units elsewhere in the view).
-     */
-    virtual bool getDisplayExtents(float & /* min */,
-                                   float & /* max */) const {
-        return false;
-    }
-
-    /**
-     * Set the displayed minimum and maximum values for the y axis to
-     * the given range, if supported.  Return false if not supported
-     * on this layer (and set nothing).  In most cases, layers that
-     * return false for getDisplayExtents should also return false for
-     * this function.
-     */
-    virtual bool setDisplayExtents(float /* min */,
-                                   float /* max */) {
-        return false;
-    }
-
-public slots:
-    void showLayer(View *, bool show);
-
-signals:
-    void modelChanged();
-    void modelCompletionChanged();
-    void modelChanged(size_t startFrame, size_t endFrame);
-    void modelReplaced();
-
-    void layerParametersChanged();
-    void layerNameChanged();
-
-protected:
-    mutable std::map<const void *, bool> m_dormancy;
-};
-
-#endif
-
--- a/base/View.cpp	Fri Jul 28 11:09:36 2006 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,1524 +0,0 @@
-/* -*- 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 Chris Cannam.
-    
-    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 "base/View.h"
-#include "base/Layer.h"
-#include "base/Model.h"
-#include "base/ZoomConstraint.h"
-#include "base/Profiler.h"
-
-#include "layer/TimeRulerLayer.h" //!!! damn, shouldn't be including that here
-#include "model/PowerOfSqrtTwoZoomConstraint.h" //!!! likewise
-
-#include <QPainter>
-#include <QPaintEvent>
-#include <QRect>
-#include <QApplication>
-
-#include <iostream>
-#include <cassert>
-#include <math.h>
-
-//#define DEBUG_VIEW_WIDGET_PAINT 1
-
-using std::cerr;
-using std::endl;
-
-View::View(QWidget *w, bool showProgress) :
-    QFrame(w),
-    m_centreFrame(0),
-    m_zoomLevel(1024),
-    m_followPan(true),
-    m_followZoom(true),
-    m_followPlay(PlaybackScrollPage),
-    m_lightBackground(true),
-    m_showProgress(showProgress),
-    m_cache(0),
-    m_cacheCentreFrame(0),
-    m_cacheZoomLevel(1024),
-    m_selectionCached(false),
-    m_deleting(false),
-    m_haveSelectedLayer(false),
-    m_manager(0),
-    m_propertyContainer(new ViewPropertyContainer(this))
-{
-//    QWidget::setAttribute(Qt::WA_PaintOnScreen);
-}
-
-View::~View()
-{
-//    std::cerr << "View::~View(" << this << ")" << std::endl;
-
-    m_deleting = true;
-    delete m_propertyContainer;
-}
-
-PropertyContainer::PropertyList
-View::getProperties() const
-{
-    PropertyContainer::PropertyList list;
-    list.push_back("Global Scroll");
-    list.push_back("Global Zoom");
-    list.push_back("Follow Playback");
-    return list;
-}
-
-QString
-View::getPropertyLabel(const PropertyName &pn) const
-{
-    if (pn == "Global Scroll") return tr("Global Scroll");
-    if (pn == "Global Zoom") return tr("Global Zoom");
-    if (pn == "Follow Playback") return tr("Follow Playback");
-    return "";
-}
-
-PropertyContainer::PropertyType
-View::getPropertyType(const PropertyContainer::PropertyName &name) const
-{
-    if (name == "Global Scroll") return PropertyContainer::ToggleProperty;
-    if (name == "Global Zoom") return PropertyContainer::ToggleProperty;
-    if (name == "Follow Playback") return PropertyContainer::ValueProperty;
-    return PropertyContainer::InvalidProperty;
-}
-
-int
-View::getPropertyRangeAndValue(const PropertyContainer::PropertyName &name,
-			       int *min, int *max) const
-{
-    if (name == "Global Scroll") return m_followPan;
-    if (name == "Global Zoom") return m_followZoom;
-    if (name == "Follow Playback") {
-	if (min) *min = 0;
-	if (max) *max = 2;
-	return int(m_followPlay);
-    }
-    if (min) *min = 0;
-    if (max) *max = 0;
-    return 0;
-}
-
-QString
-View::getPropertyValueLabel(const PropertyContainer::PropertyName &name,
-			    int value) const
-{
-    if (name == "Follow Playback") {
-	switch (value) {
-	default:
-	case 0: return tr("Scroll");
-	case 1: return tr("Page");
-	case 2: return tr("Off");
-	}
-    }
-    return tr("<unknown>");
-}
-
-void
-View::setProperty(const PropertyContainer::PropertyName &name, int value)
-{
-    if (name == "Global Scroll") {
-	setFollowGlobalPan(value != 0);
-    } else if (name == "Global Zoom") {
-	setFollowGlobalZoom(value != 0);
-    } else if (name == "Follow Playback") {
-	switch (value) {
-	default:
-	case 0: setPlaybackFollow(PlaybackScrollContinuous); break;
-	case 1: setPlaybackFollow(PlaybackScrollPage); break;
-	case 2: setPlaybackFollow(PlaybackIgnore); break;
-	}
-    }
-}
-
-size_t
-View::getPropertyContainerCount() const
-{
-    return m_layers.size() + 1; // the 1 is for me
-}
-
-const PropertyContainer *
-View::getPropertyContainer(size_t i) const
-{
-    return (const PropertyContainer *)(((View *)this)->
-				       getPropertyContainer(i));
-}
-
-PropertyContainer *
-View::getPropertyContainer(size_t i)
-{
-    if (i == 0) return m_propertyContainer;
-    return m_layers[i-1];
-}
-
-bool
-View::getValueExtents(QString unit, float &min, float &max, bool &log) const
-{
-    bool have = false;
-
-    for (LayerList::const_iterator i = m_layers.begin();
-         i != m_layers.end(); ++i) { 
-
-        QString layerUnit;
-        float layerMin = 0.0, layerMax = 0.0;
-        float displayMin = 0.0, displayMax = 0.0;
-        bool layerLog = false;
-
-        if ((*i)->getValueExtents(layerMin, layerMax, layerLog, layerUnit) &&
-            layerUnit.toLower() == unit.toLower()) {
-
-            if ((*i)->getDisplayExtents(displayMin, displayMax)) {
-
-                min = displayMin;
-                max = displayMax;
-                log = layerLog;
-                have = true;
-                break;
-
-            } else {
-
-                if (!have || layerMin < min) min = layerMin;
-                if (!have || layerMax > max) max = layerMax;
-                if (layerLog) log = true;
-                have = true;
-            }
-        }
-    }
-
-    return have;
-}
-
-int
-View::getTextLabelHeight(const Layer *layer, QPainter &paint) const
-{
-    std::map<int, Layer *> sortedLayers;
-
-    for (LayerList::const_iterator i = m_layers.begin();
-         i != m_layers.end(); ++i) { 
-        if ((*i)->needsTextLabelHeight()) {
-            sortedLayers[getObjectExportId(*i)] = *i;
-        }
-    }
-
-    int y = 15 + paint.fontMetrics().ascent();
-
-    for (std::map<int, Layer *>::const_iterator i = sortedLayers.begin();
-         i != sortedLayers.end(); ++i) {
-        if (i->second == layer) return y;
-        y += paint.fontMetrics().height();
-    }
-
-    return y;
-}
-
-void
-View::propertyContainerSelected(View *client, PropertyContainer *pc)
-{
-    if (client != this) return;
-    
-    if (pc == m_propertyContainer) {
-	if (m_haveSelectedLayer) {
-	    m_haveSelectedLayer = false;
-	    update();
-	}
-	return;
-    }
-
-    delete m_cache;
-    m_cache = 0;
-
-    Layer *selectedLayer = 0;
-
-    for (LayerList::iterator i = m_layers.begin(); i != m_layers.end(); ++i) {
-	if (*i == pc) {
-	    selectedLayer = *i;
-	    m_layers.erase(i);
-	    break;
-	}
-    }
-
-    if (selectedLayer) {
-	m_haveSelectedLayer = true;
-	m_layers.push_back(selectedLayer);
-	update();
-    } else {
-	m_haveSelectedLayer = false;
-    }
-}
-
-void
-View::toolModeChanged()
-{
-//    std::cerr << "View::toolModeChanged(" << m_manager->getToolMode() << ")" << std::endl;
-}
-
-long
-View::getStartFrame() const
-{
-    size_t w2 = (width() / 2) * m_zoomLevel;
-    size_t frame = m_centreFrame;
-    if (frame >= w2) {
-	frame -= w2;
-	return (frame / m_zoomLevel * m_zoomLevel);
-    } else {
-	frame = w2 - frame;
-	frame = frame / m_zoomLevel * m_zoomLevel;
-	return -(long)frame - m_zoomLevel;
-    }
-}
-
-size_t
-View::getEndFrame() const
-{
-    return getFrameForX(width()) - 1;
-}
-
-void
-View::setStartFrame(long f)
-{
-    setCentreFrame(f + m_zoomLevel * (width() / 2));
-}
-
-bool
-View::setCentreFrame(size_t f, bool e)
-{
-    bool changeVisible = false;
-
-    if (m_centreFrame != f) {
-
-	int formerPixel = m_centreFrame / m_zoomLevel;
-
-	m_centreFrame = f;
-
-	int newPixel = m_centreFrame / m_zoomLevel;
-	
-	if (newPixel != formerPixel) {
-
-#ifdef DEBUG_VIEW_WIDGET_PAINT
-	    std::cout << "View(" << this << ")::setCentreFrame: newPixel " << newPixel << ", formerPixel " << formerPixel << std::endl;
-#endif
-	    update();
-
-	    changeVisible = true;
-	}
-
-	if (e) emit centreFrameChanged(this, f, m_followPan);
-    }
-
-    return changeVisible;
-}
-
-int
-View::getXForFrame(long frame) const
-{
-    return (frame - getStartFrame()) / m_zoomLevel;
-}
-
-long
-View::getFrameForX(int x) const
-{
-    return (long(x) * long(m_zoomLevel)) + getStartFrame();
-}
-
-float
-View::getYForFrequency(float frequency,
-		       float minf,
-		       float maxf, 
-		       bool logarithmic) const
-{
-    int h = height();
-
-    if (logarithmic) {
-
-	static float lastminf = 0.0, lastmaxf = 0.0;
-	static float logminf = 0.0, logmaxf = 0.0;
-
-	if (lastminf != minf) {
-	    lastminf = (minf == 0.0 ? 1.0 : minf);
-	    logminf = log10f(minf);
-	}
-	if (lastmaxf != maxf) {
-	    lastmaxf = (maxf < lastminf ? lastminf : maxf);
-	    logmaxf = log10f(maxf);
-	}
-
-	if (logminf == logmaxf) return 0;
-	return h - (h * (log10f(frequency) - logminf)) / (logmaxf - logminf);
-
-    } else {
-	
-	if (minf == maxf) return 0;
-	return h - (h * (frequency - minf)) / (maxf - minf);
-    }
-}
-
-float
-View::getFrequencyForY(int y,
-		       float minf,
-		       float maxf,
-		       bool logarithmic) const
-{
-    int h = height();
-
-    if (logarithmic) {
-
-	static float lastminf = 0.0, lastmaxf = 0.0;
-	static float logminf = 0.0, logmaxf = 0.0;
-
-	if (lastminf != minf) {
-	    lastminf = (minf == 0.0 ? 1.0 : minf);
-	    logminf = log10f(minf);
-	}
-	if (lastmaxf != maxf) {
-	    lastmaxf = (maxf < lastminf ? lastminf : maxf);
-	    logmaxf = log10f(maxf);
-	}
-
-	if (logminf == logmaxf) return 0;
-	return pow(10.f, logminf + ((logmaxf - logminf) * (h - y)) / h);
-
-    } else {
-
-	if (minf == maxf) return 0;
-	return minf + ((h - y) * (maxf - minf)) / h;
-    }
-}
-
-int
-View::getZoomLevel() const
-{
-#ifdef DEBUG_VIEW_WIDGET_PAINT
-	std::cout << "zoom level: " << m_zoomLevel << std::endl;
-#endif
-    return m_zoomLevel;
-}
-
-void
-View::setZoomLevel(size_t z)
-{
-    if (m_zoomLevel != int(z)) {
-	m_zoomLevel = z;
-	emit zoomLevelChanged(this, z, m_followZoom);
-	update();
-    }
-}
-
-View::LayerProgressBar::LayerProgressBar(QWidget *parent) :
-    QProgressBar(parent)
-{
-    QFont f(font());
-    f.setPointSize(f.pointSize() * 8 / 10);
-    setFont(f);
-}
-
-void
-View::addLayer(Layer *layer)
-{
-    delete m_cache;
-    m_cache = 0;
-
-    m_layers.push_back(layer);
-
-    m_progressBars[layer] = new LayerProgressBar(this);
-    m_progressBars[layer]->setMinimum(0);
-    m_progressBars[layer]->setMaximum(100);
-    m_progressBars[layer]->setMinimumWidth(80);
-    m_progressBars[layer]->hide();
-    
-    connect(layer, SIGNAL(layerParametersChanged()),
-	    this,    SLOT(layerParametersChanged()));
-    connect(layer, SIGNAL(layerNameChanged()),
-	    this,    SLOT(layerNameChanged()));
-    connect(layer, SIGNAL(modelChanged()),
-	    this,    SLOT(modelChanged()));
-    connect(layer, SIGNAL(modelCompletionChanged()),
-	    this,    SLOT(modelCompletionChanged()));
-    connect(layer, SIGNAL(modelChanged(size_t, size_t)),
-	    this,    SLOT(modelChanged(size_t, size_t)));
-    connect(layer, SIGNAL(modelReplaced()),
-	    this,    SLOT(modelReplaced()));
-
-    update();
-
-    emit propertyContainerAdded(layer);
-}
-
-void
-View::removeLayer(Layer *layer)
-{
-    if (m_deleting) {
-	return;
-    }
-
-    delete m_cache;
-    m_cache = 0;
-
-    for (LayerList::iterator i = m_layers.begin(); i != m_layers.end(); ++i) {
-	if (*i == layer) {
-	    m_layers.erase(i);
-	    if (m_progressBars.find(layer) != m_progressBars.end()) {
-		delete m_progressBars[layer];
-		m_progressBars.erase(layer);
-	    }
-	    break;
-	}
-    }
-    
-    update();
-
-    emit propertyContainerRemoved(layer);
-}
-
-Layer *
-View::getSelectedLayer()
-{
-    if (m_haveSelectedLayer && !m_layers.empty()) {
-	return getLayer(getLayerCount() - 1);
-    } else {
-	return 0;
-    }
-}
-
-const Layer *
-View::getSelectedLayer() const
-{
-    return const_cast<const Layer *>(const_cast<View *>(this)->getSelectedLayer());
-}
-
-void
-View::setViewManager(ViewManager *manager)
-{
-    if (m_manager) {
-	m_manager->disconnect(this, SLOT(viewManagerCentreFrameChanged(void *, unsigned long, bool)));
-	m_manager->disconnect(this, SLOT(viewManagerZoomLevelChanged(void *, unsigned long, bool)));
-	disconnect(m_manager, SIGNAL(centreFrameChanged(void *, unsigned long, bool)));
-	disconnect(m_manager, SIGNAL(zoomLevelChanged(void *, unsigned long, bool)));
-	disconnect(m_manager, SIGNAL(toolModeChanged()));
-	disconnect(m_manager, SIGNAL(selectionChanged()));
-	disconnect(m_manager, SIGNAL(inProgressSelectionChanged()));
-    }
-
-    m_manager = manager;
-    if (m_followPan) setCentreFrame(m_manager->getGlobalCentreFrame(), false);
-    if (m_followZoom) setZoomLevel(m_manager->getGlobalZoom());
-
-    connect(m_manager, SIGNAL(centreFrameChanged(void *, unsigned long, bool)),
-	    this, SLOT(viewManagerCentreFrameChanged(void *, unsigned long, bool)));
-    connect(m_manager, SIGNAL(playbackFrameChanged(unsigned long)),
-	    this, SLOT(viewManagerPlaybackFrameChanged(unsigned long)));
-    connect(m_manager, SIGNAL(zoomLevelChanged(void *, unsigned long, bool)),
-	    this, SLOT(viewManagerZoomLevelChanged(void *, unsigned long, bool)));
-    connect(m_manager, SIGNAL(toolModeChanged()),
-	    this, SLOT(toolModeChanged()));
-    connect(m_manager, SIGNAL(selectionChanged()),
-	    this, SLOT(selectionChanged()));
-    connect(m_manager, SIGNAL(inProgressSelectionChanged()),
-	    this, SLOT(selectionChanged()));
-    connect(m_manager, SIGNAL(overlayModeChanged()),
-            this, SLOT(update()));
-
-    connect(this, SIGNAL(centreFrameChanged(void *, unsigned long, bool)),
-	    m_manager, SIGNAL(centreFrameChanged(void *, unsigned long, bool)));
-    connect(this, SIGNAL(zoomLevelChanged(void *, unsigned long, bool)),
-	    m_manager, SIGNAL(zoomLevelChanged(void *, unsigned long, bool)));
-
-    toolModeChanged();
-}
-
-void
-View::setFollowGlobalPan(bool f)
-{
-    m_followPan = f;
-    emit propertyContainerPropertyChanged(m_propertyContainer);
-}
-
-void
-View::setFollowGlobalZoom(bool f)
-{
-    m_followZoom = f;
-    emit propertyContainerPropertyChanged(m_propertyContainer);
-}
-
-void
-View::drawVisibleText(QPainter &paint, int x, int y, QString text, TextStyle style)
-{
-    if (style == OutlinedText) {
-
-	QColor origPenColour = paint.pen().color();
-	QColor penColour = origPenColour;
-	QColor surroundColour = Qt::white;  //palette().background().color();
-
-	if (!hasLightBackground()) {
-	    int h, s, v;
-	    penColour.getHsv(&h, &s, &v);
-	    penColour = QColor::fromHsv(h, s, 255 - v);
-	    surroundColour = Qt::black;
-	}
-
-	paint.setPen(surroundColour);
-
-	for (int dx = -1; dx <= 1; ++dx) {
-	    for (int dy = -1; dy <= 1; ++dy) {
-		if (!(dx || dy)) continue;
-		paint.drawText(x + dx, y + dy, text);
-	    }
-	}
-
-	paint.setPen(penColour);
-
-	paint.drawText(x, y, text);
-
-	paint.setPen(origPenColour);
-
-    } else {
-
-	std::cerr << "ERROR: View::drawVisibleText: Boxed style not yet implemented!" << std::endl;
-    }
-}
-
-void
-View::setPlaybackFollow(PlaybackFollowMode m)
-{
-    m_followPlay = m;
-    emit propertyContainerPropertyChanged(m_propertyContainer);
-}
-
-void
-View::modelChanged()
-{
-    QObject *obj = sender();
-
-#ifdef DEBUG_VIEW_WIDGET_PAINT
-    std::cerr << "View(" << this << ")::modelChanged()" << std::endl;
-#endif
-    
-    // If the model that has changed is not used by any of the cached
-    // layers, we won't need to recreate the cache
-    
-    bool recreate = false;
-
-    bool discard;
-    LayerList scrollables = getScrollableBackLayers(false, discard);
-    for (LayerList::const_iterator i = scrollables.begin();
-	 i != scrollables.end(); ++i) {
-	if (*i == obj || (*i)->getModel() == obj) {
-	    recreate = true;
-	    break;
-	}
-    }
-
-    if (recreate) {
-	delete m_cache;
-	m_cache = 0;
-    }
-
-    checkProgress(obj);
-
-    update();
-}
-
-void
-View::modelChanged(size_t startFrame, size_t endFrame)
-{
-    QObject *obj = sender();
-
-    long myStartFrame = getStartFrame();
-    size_t myEndFrame = getEndFrame();
-
-#ifdef DEBUG_VIEW_WIDGET_PAINT
-    std::cerr << "View(" << this << ")::modelChanged(" << startFrame << "," << endFrame << ") [me " << myStartFrame << "," << myEndFrame << "]" << std::endl;
-#endif
-
-    if (myStartFrame > 0 && endFrame < size_t(myStartFrame)) {
-	checkProgress(obj);
-	return;
-    }
-    if (startFrame > myEndFrame) {
-	checkProgress(obj);
-	return;
-    }
-
-    // If the model that has changed is not used by any of the cached
-    // layers, we won't need to recreate the cache
-    
-    bool recreate = false;
-
-    bool discard;
-    LayerList scrollables = getScrollableBackLayers(false, discard);
-    for (LayerList::const_iterator i = scrollables.begin();
-	 i != scrollables.end(); ++i) {
-	if (*i == obj || (*i)->getModel() == obj) {
-	    recreate = true;
-	    break;
-	}
-    }
-
-    if (recreate) {
-	delete m_cache;
-	m_cache = 0;
-    }
-
-    if (long(startFrame) < myStartFrame) startFrame = myStartFrame;
-    if (endFrame > myEndFrame) endFrame = myEndFrame;
-
-    int x0 = getXForFrame(startFrame);
-    int x1 = getXForFrame(endFrame + 1);
-    if (x1 < x0) x1 = x0;
-
-    checkProgress(obj);
-
-    update();
-//!!!    update(x0, 0, x1 - x0 + 1, height());
-}    
-
-void
-View::modelCompletionChanged()
-{
-    QObject *obj = sender();
-    checkProgress(obj);
-}
-
-void
-View::modelReplaced()
-{
-#ifdef DEBUG_VIEW_WIDGET_PAINT
-    std::cerr << "View(" << this << ")::modelReplaced()" << std::endl;
-#endif
-    delete m_cache;
-    m_cache = 0;
-
-    update();
-}
-
-void
-View::layerParametersChanged()
-{
-    Layer *layer = dynamic_cast<Layer *>(sender());
-
-#ifdef DEBUG_VIEW_WIDGET_PAINT
-    std::cerr << "View::layerParametersChanged()" << std::endl;
-#endif
-
-    delete m_cache;
-    m_cache = 0;
-    update();
-
-    if (layer) {
-	emit propertyContainerPropertyChanged(layer);
-    }
-}
-
-void
-View::layerNameChanged()
-{
-    Layer *layer = dynamic_cast<Layer *>(sender());
-    if (layer) emit propertyContainerNameChanged(layer);
-}
-
-void
-View::viewManagerCentreFrameChanged(void *p, unsigned long f, bool locked)
-{
-    if (m_followPan && p != this && locked) {
-	if (m_manager && (sender() == m_manager)) {
-#ifdef DEBUG_VIEW_WIDGET_PAINT
-	    std::cerr << this << ": manager frame changed " << f << " from " << p << std::endl;
-#endif
-	    setCentreFrame(f);
-	    if (p == this) repaint();
-	}
-    }
-}
-
-void
-View::viewManagerPlaybackFrameChanged(unsigned long f)
-{
-    if (m_manager) {
-	if (sender() != m_manager) return;
-    }
-
-    if (m_playPointerFrame == f) return;
-    bool visible = (getXForFrame(m_playPointerFrame) != getXForFrame(f));
-    size_t oldPlayPointerFrame = m_playPointerFrame;
-    m_playPointerFrame = f;
-    if (!visible) return;
-
-    switch (m_followPlay) {
-
-    case PlaybackScrollContinuous:
-	if (QApplication::mouseButtons() == Qt::NoButton) {
-	    setCentreFrame(f, false);
-	}
-	break;
-
-    case PlaybackScrollPage:
-    { 
-	int xold = getXForFrame(oldPlayPointerFrame);
-	update(xold - 1, 0, 3, height());
-
-	long w = getEndFrame() - getStartFrame();
-	w -= w/5;
-	long sf = (f / w) * w - w/8;
-
-	if (m_manager &&
-	    m_manager->isPlaying() &&
-	    m_manager->getPlaySelectionMode()) {
-	    MultiSelection::SelectionList selections = m_manager->getSelections();
-	    if (!selections.empty()) {
-		size_t selectionStart = selections.begin()->getStartFrame();
-		if (sf < long(selectionStart) - w / 10) {
-		    sf = long(selectionStart) - w / 10;
-		}
-	    }
-	}
-
-#ifdef DEBUG_VIEW_WIDGET_PAINT
-	std::cerr << "PlaybackScrollPage: f = " << f << ", sf = " << sf << ", start frame "
-		  << getStartFrame() << std::endl;
-#endif
-
-	// We don't consider scrolling unless the pointer is outside
-	// the clearly visible range already
-
-	int xnew = getXForFrame(m_playPointerFrame);
-
-#ifdef DEBUG_VIEW_WIDGET_PAINT
-	std::cerr << "xnew = " << xnew << ", width = " << width() << std::endl;
-#endif
-
-	if (xnew < width()/8 || xnew > (width()*7)/8) {
-	    if (QApplication::mouseButtons() == Qt::NoButton) {
-		long offset = getFrameForX(width()/2) - getStartFrame();
-		long newCentre = sf + offset;
-		bool changed = setCentreFrame(newCentre, false);
-		if (changed) {
-		    xold = getXForFrame(oldPlayPointerFrame);
-		    update(xold - 1, 0, 3, height());
-		}
-	    }
-	}
-
-	update(xnew - 1, 0, 3, height());
-
-	break;
-    }
-
-    case PlaybackIgnore:
-	if (long(f) >= getStartFrame() && f < getEndFrame()) {
-	    update();
-	}
-	break;
-    }
-}
-
-void
-View::viewManagerZoomLevelChanged(void *p, unsigned long z, bool locked)
-{
-    if (m_followZoom && p != this && locked) {
-	if (m_manager && (sender() == m_manager)) {
-	    setZoomLevel(z);
-	    if (p == this) repaint();
-	}
-    }
-}
-
-void
-View::selectionChanged()
-{
-    if (m_selectionCached) {
-	delete m_cache;
-	m_cache = 0;
-	m_selectionCached = false;
-    }
-    update();
-}
-
-size_t
-View::getModelsStartFrame() const
-{
-    bool first = true;
-    size_t startFrame = 0;
-
-    for (LayerList::const_iterator i = m_layers.begin(); i != m_layers.end(); ++i) {
-
-	if ((*i)->getModel() && (*i)->getModel()->isOK()) {
-
-	    size_t thisStartFrame = (*i)->getModel()->getStartFrame();
-
-	    if (first || thisStartFrame < startFrame) {
-		startFrame = thisStartFrame;
-	    }
-	    first = false;
-	}
-    }
-    return startFrame;
-}
-
-size_t
-View::getModelsEndFrame() const
-{
-    bool first = true;
-    size_t endFrame = 0;
-
-    for (LayerList::const_iterator i = m_layers.begin(); i != m_layers.end(); ++i) {
-
-	if ((*i)->getModel() && (*i)->getModel()->isOK()) {
-
-	    size_t thisEndFrame = (*i)->getModel()->getEndFrame();
-
-	    if (first || thisEndFrame > endFrame) {
-		endFrame = thisEndFrame;
-	    }
-	    first = false;
-	}
-    }
-
-    if (first) return getModelsStartFrame();
-    return endFrame;
-}
-
-int
-View::getModelsSampleRate() const
-{
-    //!!! Just go for the first, for now.  If we were supporting
-    // multiple samplerates, we'd probably want to do frame/time
-    // conversion in the model
-
-    for (LayerList::const_iterator i = m_layers.begin(); i != m_layers.end(); ++i) {
-	if ((*i)->getModel() && (*i)->getModel()->isOK()) {
-	    return (*i)->getModel()->getSampleRate();
-	}
-    }
-    return 0;
-}
-
-bool
-View::areLayersScrollable() const
-{
-    // True iff all views are scrollable
-    for (LayerList::const_iterator i = m_layers.begin(); i != m_layers.end(); ++i) {
-	if (!(*i)->isLayerScrollable(this)) return false;
-    }
-    return true;
-}
-
-View::LayerList
-View::getScrollableBackLayers(bool testChanged, bool &changed) const
-{
-    changed = false;
-
-    // We want a list of all the scrollable layers that are behind the
-    // backmost non-scrollable layer.
-
-    LayerList scrollables;
-    bool metUnscrollable = false;
-
-    for (LayerList::const_iterator i = m_layers.begin(); i != m_layers.end(); ++i) {
-	if ((*i)->isLayerDormant(this)) continue;
-	if ((*i)->isLayerOpaque()) {
-	    // You can't see anything behind an opaque layer!
-	    scrollables.clear();
-            if (metUnscrollable) break;
-	}
-	if (!metUnscrollable && (*i)->isLayerScrollable(this)) {
-            scrollables.push_back(*i);
-        } else {
-            metUnscrollable = true;
-        }
-    }
-
-    if (testChanged && scrollables != m_lastScrollableBackLayers) {
-	m_lastScrollableBackLayers = scrollables;
-	changed = true;
-    }
-    return scrollables;
-}
-
-View::LayerList
-View::getNonScrollableFrontLayers(bool testChanged, bool &changed) const
-{
-    changed = false;
-    LayerList nonScrollables;
-
-    // Everything in front of the first non-scrollable from the back
-    // should also be considered non-scrollable
-
-    bool started = false;
-
-    for (LayerList::const_iterator i = m_layers.begin(); i != m_layers.end(); ++i) {
-	if ((*i)->isLayerDormant(this)) continue;
-	if (!started && (*i)->isLayerScrollable(this)) {
-	    continue;
-	}
-	started = true;
-	if ((*i)->isLayerOpaque()) {
-	    // You can't see anything behind an opaque layer!
-	    nonScrollables.clear();
-	}
-	nonScrollables.push_back(*i);
-    }
-
-    if (testChanged && nonScrollables != m_lastNonScrollableBackLayers) {
-	m_lastNonScrollableBackLayers = nonScrollables;
-	changed = true;
-    }
-
-    return nonScrollables;
-}
-
-size_t
-View::getZoomConstraintBlockSize(size_t blockSize,
-				 ZoomConstraint::RoundingDirection dir)
-    const
-{
-    size_t candidate = blockSize;
-    bool haveCandidate = false;
-
-    PowerOfSqrtTwoZoomConstraint defaultZoomConstraint;
-
-    for (LayerList::const_iterator i = m_layers.begin(); i != m_layers.end(); ++i) {
-
-	const ZoomConstraint *zoomConstraint = (*i)->getZoomConstraint();
-	if (!zoomConstraint) zoomConstraint = &defaultZoomConstraint;
-
-	size_t thisBlockSize =
-	    zoomConstraint->getNearestBlockSize(blockSize, dir);
-
-	// Go for the block size that's furthest from the one
-	// passed in.  Most of the time, that's what we want.
-	if (!haveCandidate ||
-	    (thisBlockSize > blockSize && thisBlockSize > candidate) ||
-	    (thisBlockSize < blockSize && thisBlockSize < candidate)) {
-	    candidate = thisBlockSize;
-	    haveCandidate = true;
-	}
-    }
-
-    return candidate;
-}
-
-void
-View::zoom(bool in)
-{
-    int newZoomLevel = m_zoomLevel;
-
-    if (in) {
-	newZoomLevel = getZoomConstraintBlockSize(newZoomLevel - 1, 
-						  ZoomConstraint::RoundDown);
-    } else {
-	newZoomLevel = getZoomConstraintBlockSize(newZoomLevel + 1,
-						  ZoomConstraint::RoundUp);
-    }
-
-    if (newZoomLevel != m_zoomLevel) {
-	setZoomLevel(newZoomLevel);
-    }
-}
-
-void
-View::scroll(bool right, bool lots)
-{
-    long delta;
-    if (lots) {
-	delta = (getEndFrame() - getStartFrame()) / 2;
-    } else {
-	delta = (getEndFrame() - getStartFrame()) / 20;
-    }
-    if (right) delta = -delta;
-
-    if (int(m_centreFrame) < delta) {
-	setCentreFrame(0);
-    } else if (int(m_centreFrame) - delta >= int(getModelsEndFrame())) {
-	setCentreFrame(getModelsEndFrame());
-    } else {
-	setCentreFrame(m_centreFrame - delta);
-    }
-}
-
-void
-View::checkProgress(void *object)
-{
-    if (!m_showProgress) return;
-
-    int ph = height();
-
-    for (ProgressMap::const_iterator i = m_progressBars.begin();
-	 i != m_progressBars.end(); ++i) {
-
-	if (i->first == object) {
-
-	    int completion = i->first->getCompletion(this);
-
-	    if (completion >= 100) {
-
-		i->second->hide();
-
-	    } else {
-
-		i->second->setText(i->first->getPropertyContainerName());
-		i->second->setValue(completion);
-		i->second->move(0, ph - i->second->height());
-
-		i->second->show();
-		i->second->update();
-
-		ph -= i->second->height();
-	    }
-	} else {
-	    if (i->second->isVisible()) {
-		ph -= i->second->height();
-	    }
-	}
-    }
-}
-
-void
-View::paintEvent(QPaintEvent *e)
-{
-//    Profiler prof("View::paintEvent", false);
-//    std::cerr << "View::paintEvent" << std::endl;
-
-    if (m_layers.empty()) {
-	QFrame::paintEvent(e);
-	return;
-    }
-
-    // ensure our constraints are met
-    m_zoomLevel = getZoomConstraintBlockSize(m_zoomLevel,
-					     ZoomConstraint::RoundUp);
-
-    QPainter paint;
-    bool repaintCache = false;
-    bool paintedCacheRect = false;
-
-    QRect cacheRect(rect());
-
-    if (e) {
-	cacheRect &= e->rect();
-#ifdef DEBUG_VIEW_WIDGET_PAINT
-	std::cerr << "paint rect " << cacheRect.width() << "x" << cacheRect.height()
-		  << ", my rect " << width() << "x" << height() << std::endl;
-#endif
-    }
-
-    QRect nonCacheRect(cacheRect);
-
-    // If not all layers are scrollable, but some of the back layers
-    // are, we should store only those in the cache.
-
-    bool layersChanged = false;
-    LayerList scrollables = getScrollableBackLayers(true, layersChanged);
-    LayerList nonScrollables = getNonScrollableFrontLayers(true, layersChanged);
-    bool selectionCacheable = nonScrollables.empty();
-    bool haveSelections = m_manager && !m_manager->getSelections().empty();
-    bool selectionDrawn = false;
-
-    // If all the non-scrollable layers are non-opaque, then we draw
-    // the selection rectangle behind them and cache it.  If any are
-    // opaque, however, we can't cache.
-    //
-    if (!selectionCacheable) {
-	selectionCacheable = true;
-	for (LayerList::const_iterator i = nonScrollables.begin();
-	     i != nonScrollables.end(); ++i) {
-	    if ((*i)->isLayerOpaque()) {
-		selectionCacheable = false;
-		break;
-	    }
-	}
-    }
-
-    if (selectionCacheable) {
-	QPoint localPos;
-	bool closeToLeft, closeToRight;
-	if (shouldIlluminateLocalSelection(localPos, closeToLeft, closeToRight)) {
-	    selectionCacheable = false;
-	}
-    }
-
-#ifdef DEBUG_VIEW_WIDGET_PAINT
-    std::cerr << "View(" << this << ")::paintEvent: have " << scrollables.size()
-	      << " scrollable back layers and " << nonScrollables.size()
-	      << " non-scrollable front layers" << std::endl;
-    std::cerr << "haveSelections " << haveSelections << ", selectionCacheable "
-	      << selectionCacheable << ", m_selectionCached " << m_selectionCached << std::endl;
-#endif
-
-    if (layersChanged || scrollables.empty() ||
-	(haveSelections && (selectionCacheable != m_selectionCached))) {
-	delete m_cache;
-	m_cache = 0;
-	m_selectionCached = false;
-    }
-
-    if (!scrollables.empty()) {
-	if (!m_cache ||
-	    m_cacheZoomLevel != m_zoomLevel ||
-	    width() != m_cache->width() ||
-	    height() != m_cache->height()) {
-
-	    // cache is not valid
-
-	    if (cacheRect.width() < width()/10) {
-#ifdef DEBUG_VIEW_WIDGET_PAINT
-		std::cerr << "View(" << this << ")::paintEvent: small repaint, not bothering to recreate cache" << std::endl;
-#endif
-	    } else {
-		delete m_cache;
-		m_cache = new QPixmap(width(), height());
-#ifdef DEBUG_VIEW_WIDGET_PAINT
-		std::cerr << "View(" << this << ")::paintEvent: recreated cache" << std::endl;
-#endif
-		cacheRect = rect();
-		repaintCache = true;
-	    }
-
-	} else if (m_cacheCentreFrame != m_centreFrame) {
-
-	    long dx =
-		getXForFrame(m_cacheCentreFrame) -
-		getXForFrame(m_centreFrame);
-
-	    if (dx > -width() && dx < 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)
-		static QPixmap *tmpPixmap = 0;
-		if (!tmpPixmap ||
-		    tmpPixmap->width() != width() ||
-		    tmpPixmap->height() != height()) {
-		    delete tmpPixmap;
-		    tmpPixmap = new QPixmap(width(), height());
-		}
-		paint.begin(tmpPixmap);
-		paint.drawPixmap(0, 0, *m_cache);
-		paint.end();
-		paint.begin(m_cache);
-		paint.drawPixmap(dx, 0, *tmpPixmap);
-		paint.end();
-#else
-		// But it seems to be fine on X11
-		paint.begin(m_cache);
-		paint.drawPixmap(dx, 0, *m_cache);
-		paint.end();
-#endif
-
-		if (dx < 0) {
-		    cacheRect = QRect(width() + dx, 0, -dx, height());
-		} else {
-		    cacheRect = QRect(0, 0, dx, height());
-		}
-#ifdef DEBUG_VIEW_WIDGET_PAINT
-		std::cerr << "View(" << this << ")::paintEvent: scrolled cache by " << dx << std::endl;
-#endif
-	    } else {
-		cacheRect = rect();
-#ifdef DEBUG_VIEW_WIDGET_PAINT
-		std::cerr << "View(" << this << ")::paintEvent: scrolling too far" << std::endl;
-#endif
-	    }
-	    repaintCache = true;
-
-	} else {
-#ifdef DEBUG_VIEW_WIDGET_PAINT
-	    std::cerr << "View(" << this << ")::paintEvent: cache is good" << std::endl;
-#endif
-	    paint.begin(this);
-	    paint.drawPixmap(cacheRect, *m_cache, cacheRect);
-	    paint.end();
-	    QFrame::paintEvent(e);
-	    paintedCacheRect = true;
-	}
-
-	m_cacheCentreFrame = m_centreFrame;
-	m_cacheZoomLevel = m_zoomLevel;
-    }
-
-#ifdef DEBUG_VIEW_WIDGET_PAINT
-//    std::cerr << "View(" << this << ")::paintEvent: cacheRect " << cacheRect << ", nonCacheRect " << (nonCacheRect | cacheRect) << ", repaintCache " << repaintCache << ", paintedCacheRect " << paintedCacheRect << std::endl;
-#endif
-
-    // Scrollable (cacheable) items first
-
-    if (!paintedCacheRect) {
-
-	if (repaintCache) paint.begin(m_cache);
-	else paint.begin(this);
-
-	paint.setClipRect(cacheRect);
-	
-	if (hasLightBackground()) {
-	    paint.setPen(Qt::white);
-	    paint.setBrush(Qt::white);
-	} else {
-	    paint.setPen(Qt::black);
-	    paint.setBrush(Qt::black);
-	}
-	paint.drawRect(cacheRect);
-
-	paint.setPen(Qt::black);
-	paint.setBrush(Qt::NoBrush);
-	
-	for (LayerList::iterator i = scrollables.begin(); i != scrollables.end(); ++i) {
-	    paint.setRenderHint(QPainter::Antialiasing, false);
-	    paint.save();
-	    (*i)->paint(this, paint, cacheRect);
-	    paint.restore();
-	}
-
-	if (haveSelections && selectionCacheable) {
-	    drawSelections(paint);
-	    m_selectionCached = repaintCache;
-	    selectionDrawn = true;
-	}
-	
-	paint.end();
-
-	if (repaintCache) {
-	    cacheRect |= (e ? e->rect() : rect());
-	    paint.begin(this);
-	    paint.drawPixmap(cacheRect, *m_cache, cacheRect);
-	    paint.end();
-	}
-    }
-
-    // Now non-cacheable items.  We always need to redraw the
-    // non-cacheable items across at least the area we drew of the
-    // cacheable items.
-
-    nonCacheRect |= cacheRect;
-
-    paint.begin(this);
-    paint.setClipRect(nonCacheRect);
-
-    if (scrollables.empty()) {
-	if (hasLightBackground()) {
-	    paint.setPen(Qt::white);
-	    paint.setBrush(Qt::white);
-	} else {
-	    paint.setPen(Qt::black);
-	    paint.setBrush(Qt::black);
-	}
-	paint.drawRect(nonCacheRect);
-    }
-	
-    paint.setPen(Qt::black);
-    paint.setBrush(Qt::NoBrush);
-	
-    for (LayerList::iterator i = nonScrollables.begin(); i != nonScrollables.end(); ++i) {
-//        Profiler profiler2("View::paintEvent non-cacheable");
-	(*i)->paint(this, paint, nonCacheRect);
-    }
-	
-    paint.end();
-
-    paint.begin(this);
-    if (e) paint.setClipRect(e->rect());
-    if (!m_selectionCached) {
-	drawSelections(paint);
-    }
-    paint.end();
-
-    if (m_followPlay != PlaybackScrollContinuous) {
-
-	paint.begin(this);
-
-	if (long(m_playPointerFrame) > getStartFrame() &&
-	    m_playPointerFrame < getEndFrame()) {
-
-	    int playx = getXForFrame(m_playPointerFrame);
-
-	    paint.setPen(Qt::black);
-	    paint.drawLine(playx - 1, 0, playx - 1, height() - 1);
-	    paint.drawLine(playx + 1, 0, playx + 1, height() - 1);
-	    paint.drawPoint(playx, 0);
-	    paint.drawPoint(playx, height() - 1);
-	    paint.setPen(Qt::white);
-	    paint.drawLine(playx, 1, playx, height() - 2);
-	}
-
-	paint.end();
-    }
-
-    QFrame::paintEvent(e);
-}
-
-void
-View::drawSelections(QPainter &paint)
-{
-    MultiSelection::SelectionList selections;
-
-    if (m_manager) {
-	selections = m_manager->getSelections();
-	if (m_manager->haveInProgressSelection()) {
-	    bool exclusive;
-	    Selection inProgressSelection =
-		m_manager->getInProgressSelection(exclusive);
-	    if (exclusive) selections.clear();
-	    selections.insert(inProgressSelection);
-	}
-    }
-
-    paint.save();
-    paint.setBrush(QColor(150, 150, 255, 80));
-
-    int sampleRate = getModelsSampleRate();
-
-    QPoint localPos;
-    long illuminateFrame = -1;
-    bool closeToLeft, closeToRight;
-
-    if (shouldIlluminateLocalSelection(localPos, closeToLeft, closeToRight)) {
-	illuminateFrame = getFrameForX(localPos.x());
-    }
-
-    const QFontMetrics &metrics = paint.fontMetrics();
-
-    for (MultiSelection::SelectionList::iterator i = selections.begin();
-	 i != selections.end(); ++i) {
-
-	int p0 = getXForFrame(i->getStartFrame());
-	int p1 = getXForFrame(i->getEndFrame());
-
-	if (p1 < 0 || p0 > width()) continue;
-
-#ifdef DEBUG_VIEW_WIDGET_PAINT
-	std::cerr << "View::drawSelections: " << p0 << ",-1 [" << (p1-p0) << "x" << (height()+1) << "]" << std::endl;
-#endif
-
-	bool illuminateThis =
-	    (illuminateFrame >= 0 && i->contains(illuminateFrame));
-
-	paint.setPen(QColor(150, 150, 255));
-	paint.drawRect(p0, -1, p1 - p0, height() + 1);
-
-	if (illuminateThis) {
-	    paint.save();
-	    if (hasLightBackground()) {
-		paint.setPen(QPen(Qt::black, 2));
-	    } else {
-		paint.setPen(QPen(Qt::white, 2));
-	    }
-	    if (closeToLeft) {
-		paint.drawLine(p0, 1, p1, 1);
-		paint.drawLine(p0, 0, p0, height());
-		paint.drawLine(p0, height() - 1, p1, height() - 1);
-	    } else if (closeToRight) {
-		paint.drawLine(p0, 1, p1, 1);
-		paint.drawLine(p1, 0, p1, height());
-		paint.drawLine(p0, height() - 1, p1, height() - 1);
-	    } else {
-		paint.setBrush(Qt::NoBrush);
-		paint.drawRect(p0, 1, p1 - p0, height() - 2);
-	    }
-	    paint.restore();
-	}
-
-	if (sampleRate && shouldLabelSelections() && m_manager &&
-            m_manager->getOverlayMode() != ViewManager::NoOverlays) {
-	    
-	    QString startText = QString("%1 / %2")
-		.arg(QString::fromStdString
-		     (RealTime::frame2RealTime
-		      (i->getStartFrame(), sampleRate).toText(true)))
-		.arg(i->getStartFrame());
-	    
-	    QString endText = QString(" %1 / %2")
-		.arg(QString::fromStdString
-		     (RealTime::frame2RealTime
-		      (i->getEndFrame(), sampleRate).toText(true)))
-		.arg(i->getEndFrame());
-	    
-	    QString durationText = QString("(%1 / %2) ")
-		.arg(QString::fromStdString
-		     (RealTime::frame2RealTime
-		      (i->getEndFrame() - i->getStartFrame(), sampleRate)
-		      .toText(true)))
-		.arg(i->getEndFrame() - i->getStartFrame());
-
-	    int sw = metrics.width(startText),
-		ew = metrics.width(endText),
-		dw = metrics.width(durationText);
-
-	    int sy = metrics.ascent() + metrics.height() + 4;
-	    int ey = sy;
-	    int dy = sy + metrics.height();
-
-	    int sx = p0 + 2;
-	    int ex = sx;
-	    int dx = sx;
-
-	    if (sw + ew > (p1 - p0)) {
-		ey += metrics.height();
-		dy += metrics.height();
-	    }
-
-	    if (ew < (p1 - p0)) {
-		ex = p1 - 2 - ew;
-	    }
-
-	    if (dw < (p1 - p0)) {
-		dx = p1 - 2 - dw;
-	    }
-
-	    paint.drawText(sx, sy, startText);
-	    paint.drawText(ex, ey, endText);
-	    paint.drawText(dx, dy, durationText);
-	}
-    }
-
-    paint.restore();
-}
-
-QString
-View::toXmlString(QString indent, QString extraAttributes) const
-{
-    QString s;
-
-    s += indent;
-
-    s += QString("<view "
-		 "centre=\"%1\" "
-		 "zoom=\"%2\" "
-		 "followPan=\"%3\" "
-		 "followZoom=\"%4\" "
-		 "tracking=\"%5\" "
-		 "light=\"%6\" %7>\n")
-	.arg(m_centreFrame)
-	.arg(m_zoomLevel)
-	.arg(m_followPan)
-	.arg(m_followZoom)
-	.arg(m_followPlay == PlaybackScrollContinuous ? "scroll" :
-	     m_followPlay == PlaybackScrollPage ? "page" : "ignore")
-	.arg(m_lightBackground)
-	.arg(extraAttributes);
-
-    for (size_t i = 0; i < m_layers.size(); ++i) {
-	s += m_layers[i]->toXmlString(indent + "  ");
-    }
-
-    s += indent + "</view>\n";
-
-    return s;
-}
-
-ViewPropertyContainer::ViewPropertyContainer(View *v) :
-    m_v(v)
-{
-    connect(m_v, SIGNAL(propertyChanged(PropertyContainer::PropertyName)),
-	    this, SIGNAL(propertyChanged(PropertyContainer::PropertyName)));
-}
-
-#ifdef INCLUDE_MOCFILES
-#include "View.moc.cpp"
-#endif
-
--- a/base/View.h	Fri Jul 28 11:09:36 2006 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,362 +0,0 @@
-/* -*- 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 Chris Cannam.
-    
-    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 _CANVAS_H_
-#define _CANVAS_H_
-
-#include <QFrame>
-#include <QProgressBar>
-
-#include "base/ZoomConstraint.h"
-#include "base/PropertyContainer.h"
-#include "base/ViewManager.h"
-#include "base/XmlExportable.h"
-
-// #define DEBUG_VIEW_WIDGET_PAINT 1
-
-class Layer;
-class ViewPropertyContainer;
-
-#include <map>
-
-/**
- * View is the base class of widgets that display one or more
- * overlaid views of data against a horizontal time scale. 
- *
- * A View may have any number of attached Layers, each of which
- * is expected to have one data Model (although multiple views may
- * share the same model).
- *
- * A View may be panned in time and zoomed, although the
- * mechanisms for doing so (as well as any other operations and
- * properties available) depend on the subclass.
- */
-
-class View : public QFrame,
-	     public XmlExportable
-{
-    Q_OBJECT
-
-public:
-    /**
-     * Deleting a View does not delete any of its layers.  They should
-     * be managed elsewhere (e.g. by the Document).
-     */
-    virtual ~View();
-
-    /**
-     * Retrieve the first visible sample frame on the widget.
-     * This is a calculated value based on the centre-frame, widget
-     * width and zoom level.  The result may be negative.
-     */
-    virtual long getStartFrame() const;
-
-    /**
-     * Set the widget pan based on the given first visible frame.  The
-     * frame value may be negative.
-     */
-    virtual void setStartFrame(long);
-
-    /**
-     * Return the centre frame of the visible widget.  This is an
-     * exact value that does not depend on the zoom block size.  Other
-     * frame values (start, end) are calculated from this based on the
-     * zoom and other factors.
-     */
-    virtual size_t getCentreFrame() const { return m_centreFrame; }
-
-    /**
-     * Set the centre frame of the visible widget.
-     */
-    virtual void setCentreFrame(size_t f) { setCentreFrame(f, true); }
-
-    /**
-     * Retrieve the last visible sample frame on the widget.
-     * This is a calculated value based on the centre-frame, widget
-     * width and zoom level.
-     */
-    virtual size_t getEndFrame() const;
-
-    /**
-     * Return the pixel x-coordinate corresponding to a given sample
-     * frame (which may be negative).
-     */
-    int getXForFrame(long frame) const;
-
-    /**
-     * Return the closest frame to the given pixel x-coordinate.
-     */
-    long getFrameForX(int x) const;
-
-    /**
-     * Return the pixel y-coordinate corresponding to a given
-     * frequency, if the frequency range is as specified.  This does
-     * not imply any policy about layer frequency ranges, but it might
-     * be useful for layers to match theirs up if desired.
-     *
-     * Not thread-safe in logarithmic mode.  Call only from GUI thread.
-     */
-    float getYForFrequency(float frequency, float minFreq, float maxFreq, 
-			   bool logarithmic) const;
-
-    /**
-     * Return the closest frequency to the given pixel y-coordinate,
-     * if the frequency range is as specified.
-     *
-     * Not thread-safe in logarithmic mode.  Call only from GUI thread.
-     */
-    float getFrequencyForY(int y, float minFreq, float maxFreq,
-			   bool logarithmic) const;
-
-    /**
-     * Return the zoom level, i.e. the number of frames per pixel
-     */
-    int getZoomLevel() const;
-
-    /**
-     * Set the zoom level, i.e. the number of frames per pixel.  The
-     * centre frame will be unchanged; the start and end frames will
-     * change.
-     */
-    virtual void setZoomLevel(size_t z);
-
-    /**
-     * Zoom in or out.
-     */
-    virtual void zoom(bool in);
-
-    /**
-     * Scroll left or right by a smallish or largish amount.
-     */
-    virtual void scroll(bool right, bool lots);
-
-    virtual void addLayer(Layer *v);
-    virtual void removeLayer(Layer *v); // does not delete the layer
-    virtual int getLayerCount() const { return m_layers.size(); }
-
-    /**
-     * Return a layer, counted in stacking order.  That is, layer 0 is
-     * the bottom layer and layer "getLayerCount()-1" is the top one.
-     */
-    virtual Layer *getLayer(int n) { return m_layers[n]; }
-
-    /**
-     * Return the layer last selected by the user.  This is normally
-     * the top layer, the same as getLayer(getLayerCount()-1).
-     * However, if the user has selected the pane itself more recently
-     * than any of the layers on it, this function will return 0.  It
-     * will also return 0 if there are no layers.
-     */
-    virtual Layer *getSelectedLayer();
-    virtual const Layer *getSelectedLayer() const;
-
-    virtual void setViewManager(ViewManager *m);
-    virtual ViewManager *getViewManager() const { return m_manager; }
-
-    virtual void setFollowGlobalPan(bool f);
-    virtual bool getFollowGlobalPan() const { return m_followPan; }
-
-    virtual void setFollowGlobalZoom(bool f);
-    virtual bool getFollowGlobalZoom() const { return m_followZoom; }
-
-    virtual void setLightBackground(bool lb) { m_lightBackground = lb; }
-    virtual bool hasLightBackground() const { return m_lightBackground; }
-
-    enum TextStyle {
-	BoxedText,
-	OutlinedText
-    };
-
-    virtual void drawVisibleText(QPainter &p, int x, int y,
-				 QString text, TextStyle style);
-
-    virtual bool shouldIlluminateLocalFeatures(const Layer *, QPoint &) const {
-	return false;
-    }
-    virtual bool shouldIlluminateLocalSelection(QPoint &, bool &, bool &) const {
-	return false;
-    }
-
-    enum PlaybackFollowMode {
-	PlaybackScrollContinuous,
-	PlaybackScrollPage,
-	PlaybackIgnore
-    };
-    virtual void setPlaybackFollow(PlaybackFollowMode m);
-    virtual PlaybackFollowMode getPlaybackFollow() const { return m_followPlay; }
-
-    typedef PropertyContainer::PropertyName PropertyName;
-
-    // We implement the PropertyContainer API, although we don't
-    // actually subclass PropertyContainer.  We have our own
-    // PropertyContainer that we can return on request that just
-    // delegates back to us.
-    virtual PropertyContainer::PropertyList getProperties() const;
-    virtual QString getPropertyLabel(const PropertyName &) const;
-    virtual PropertyContainer::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 getPropertyContainerName() const {
-	return objectName();
-    }
-    virtual QString getPropertyContainerIconName() const = 0;
-
-    virtual size_t getPropertyContainerCount() const;
-
-    virtual const PropertyContainer *getPropertyContainer(size_t i) const;
-    virtual PropertyContainer *getPropertyContainer(size_t i);
-
-    virtual int getTextLabelHeight(const Layer *layer, QPainter &) const;
-
-    virtual bool getValueExtents(QString unit, float &min, float &max,
-                                 bool &log) const;
-
-    virtual QString toXmlString(QString indent = "",
-				QString extraAttributes = "") const;
-
-    size_t getModelsStartFrame() const;
-    size_t getModelsEndFrame() const;
-
-signals:
-    void propertyContainerAdded(PropertyContainer *pc);
-    void propertyContainerRemoved(PropertyContainer *pc);
-    void propertyContainerPropertyChanged(PropertyContainer *pc);
-    void propertyContainerNameChanged(PropertyContainer *pc);
-    void propertyChanged(PropertyContainer::PropertyName);
-
-    void centreFrameChanged(void *, unsigned long, bool);
-    void zoomLevelChanged(void *, unsigned long, bool);
-
-public slots:
-    virtual void modelChanged();
-    virtual void modelChanged(size_t startFrame, size_t endFrame);
-    virtual void modelCompletionChanged();
-    virtual void modelReplaced();
-    virtual void layerParametersChanged();
-    virtual void layerNameChanged();
-
-    virtual void viewManagerCentreFrameChanged(void *, unsigned long, bool);
-    virtual void viewManagerPlaybackFrameChanged(unsigned long);
-    virtual void viewManagerZoomLevelChanged(void *, unsigned long, bool);
-
-    virtual void propertyContainerSelected(View *, PropertyContainer *pc);
-
-    virtual void selectionChanged();
-    virtual void toolModeChanged();
-
-protected:
-    View(QWidget *, bool showProgress);
-    virtual void paintEvent(QPaintEvent *e);
-    virtual void drawSelections(QPainter &);
-    virtual bool shouldLabelSelections() const { return true; }
-
-    typedef std::vector<Layer *> LayerList;
-
-    int getModelsSampleRate() const;
-    bool areLayersScrollable() const;
-    LayerList getScrollableBackLayers(bool testChanged, bool &changed) const;
-    LayerList getNonScrollableFrontLayers(bool testChanged, bool &changed) const;
-    size_t getZoomConstraintBlockSize(size_t blockSize,
-				      ZoomConstraint::RoundingDirection dir =
-				      ZoomConstraint::RoundNearest) const;
-
-    bool setCentreFrame(size_t f, bool doEmit);
-
-    void checkProgress(void *object);
-
-    size_t              m_centreFrame;
-    int                 m_zoomLevel;
-    bool                m_followPan;
-    bool                m_followZoom;
-    PlaybackFollowMode  m_followPlay;
-    size_t              m_playPointerFrame;
-    bool                m_lightBackground;
-    bool                m_showProgress;
-
-    QPixmap            *m_cache;
-    size_t              m_cacheCentreFrame;
-    int                 m_cacheZoomLevel;
-    bool                m_selectionCached;
-
-    bool                m_deleting;
-
-    LayerList           m_layers; // I don't own these, but see dtor note above
-    bool                m_haveSelectedLayer;
-
-    // caches for use in getScrollableBackLayers, getNonScrollableFrontLayers
-    mutable LayerList m_lastScrollableBackLayers;
-    mutable LayerList m_lastNonScrollableBackLayers;
-
-    class LayerProgressBar : public QProgressBar {
-    public:
-	LayerProgressBar(QWidget *parent);
-	virtual QString text() const { return m_text; }
-	virtual void setText(QString text) { m_text = text; }
-    protected:
-	QString m_text;
-    };
-
-    typedef std::map<Layer *, LayerProgressBar *> ProgressMap;
-    ProgressMap m_progressBars; // I own the ProgressBars
-
-    ViewManager *m_manager; // I don't own this
-    ViewPropertyContainer *m_propertyContainer; // I own this
-};
-
-
-// Use this for delegation, because we can't subclass from
-// PropertyContainer (which is a QObject) ourselves because of
-// ambiguity with QFrame parent
-
-class ViewPropertyContainer : public PropertyContainer
-{
-    Q_OBJECT
-
-public:
-    ViewPropertyContainer(View *v);
-    PropertyList getProperties() const { return m_v->getProperties(); }
-    QString getPropertyLabel(const PropertyName &n) const {
-        return m_v->getPropertyLabel(n);
-    }
-    PropertyType getPropertyType(const PropertyName &n) const {
-	return m_v->getPropertyType(n);
-    }
-    int getPropertyRangeAndValue(const PropertyName &n, int *min, int *max) const {
-	return m_v->getPropertyRangeAndValue(n, min, max);
-    }
-    QString getPropertyValueLabel(const PropertyName &n, int value) const {
-	return m_v->getPropertyValueLabel(n, value);
-    }
-    QString getPropertyContainerName() const {
-	return m_v->getPropertyContainerName();
-    }
-    QString getPropertyContainerIconName() const {
-	return m_v->getPropertyContainerIconName();
-    }
-
-public slots:
-    virtual void setProperty(const PropertyName &n, int value) {
-	m_v->setProperty(n, value);
-    }
-
-protected:
-    View *m_v;
-};
-
-#endif
-
--- a/base/ViewManager.cpp	Fri Jul 28 11:09:36 2006 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,361 +0,0 @@
-/* -*- 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 Chris Cannam.
-    
-    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 "ViewManager.h"
-#include "AudioPlaySource.h"
-#include "Model.h"
-#include "CommandHistory.h"
-
-#include <iostream>
-
-// #define DEBUG_VIEW_MANAGER 1
-
-ViewManager::ViewManager() :
-    m_playSource(0),
-    m_globalCentreFrame(0),
-    m_globalZoom(1024),
-    m_playbackFrame(0),
-    m_mainModelSampleRate(0),
-    m_lastLeft(0), 
-    m_lastRight(0),
-    m_inProgressExclusive(true),
-    m_toolMode(NavigateMode),
-    m_playLoopMode(false),
-    m_playSelectionMode(false),
-    m_overlayMode(BasicOverlays)
-{
-    connect(this, 
-	    SIGNAL(centreFrameChanged(void *, unsigned long, bool)),
-	    SLOT(considerSeek(void *, unsigned long, bool)));
-
-    connect(this, 
-	    SIGNAL(zoomLevelChanged(void *, unsigned long, bool)),
-	    SLOT(considerZoomChange(void *, unsigned long, bool)));
-}
-
-ViewManager::~ViewManager()
-{
-}
-
-unsigned long
-ViewManager::getGlobalCentreFrame() const
-{
-#ifdef DEBUG_VIEW_MANAGER
-    std::cout << "ViewManager::getGlobalCentreFrame: returning " << m_globalCentreFrame << std::endl;
-#endif
-    return m_globalCentreFrame;
-}
-
-unsigned long
-ViewManager::getGlobalZoom() const
-{
-#ifdef DEBUG_VIEW_MANAGER
-    std::cout << "ViewManager::getGlobalZoom: returning " << m_globalZoom << std::endl;
-#endif
-    return m_globalZoom;
-}
-
-unsigned long
-ViewManager::getPlaybackFrame() const
-{
-    if (m_playSource && m_playSource->isPlaying()) {
-	m_playbackFrame = m_playSource->getCurrentPlayingFrame();
-    }
-    return m_playbackFrame;
-}
-
-void
-ViewManager::setPlaybackFrame(unsigned long f)
-{
-    if (m_playbackFrame != f) {
-	m_playbackFrame = f;
-	emit playbackFrameChanged(f);
-	if (m_playSource && m_playSource->isPlaying()) {
-	    m_playSource->play(f);
-	}
-    }
-}
-
-bool
-ViewManager::haveInProgressSelection() const
-{
-    return !m_inProgressSelection.isEmpty();
-}
-
-const Selection &
-ViewManager::getInProgressSelection(bool &exclusive) const
-{
-    exclusive = m_inProgressExclusive;
-    return m_inProgressSelection;
-}
-
-void
-ViewManager::setInProgressSelection(const Selection &selection, bool exclusive)
-{
-    m_inProgressExclusive = exclusive;
-    m_inProgressSelection = selection;
-    if (exclusive) clearSelections();
-    emit inProgressSelectionChanged();
-}
-
-void
-ViewManager::clearInProgressSelection()
-{
-    m_inProgressSelection = Selection();
-    emit inProgressSelectionChanged();
-}
-
-const MultiSelection &
-ViewManager::getSelection() const
-{
-    return m_selections;
-}
-
-const MultiSelection::SelectionList &
-ViewManager::getSelections() const
-{
-    return m_selections.getSelections();
-}
-
-void
-ViewManager::setSelection(const Selection &selection)
-{
-    MultiSelection ms(m_selections);
-    ms.setSelection(selection);
-    setSelections(ms);
-}
-
-void
-ViewManager::addSelection(const Selection &selection)
-{
-    MultiSelection ms(m_selections);
-    ms.addSelection(selection);
-    setSelections(ms);
-}
-
-void
-ViewManager::removeSelection(const Selection &selection)
-{
-    MultiSelection ms(m_selections);
-    ms.removeSelection(selection);
-    setSelections(ms);
-}
-
-void
-ViewManager::clearSelections()
-{
-    MultiSelection ms(m_selections);
-    ms.clearSelections();
-    setSelections(ms);
-}
-
-void
-ViewManager::setSelections(const MultiSelection &ms)
-{
-    if (m_selections.getSelections() == ms.getSelections()) return;
-    SetSelectionCommand *command = new SetSelectionCommand(this, ms);
-    CommandHistory::getInstance()->addCommand(command);
-}
-
-void
-ViewManager::signalSelectionChange()
-{
-    emit selectionChanged();
-}
-
-ViewManager::SetSelectionCommand::SetSelectionCommand(ViewManager *vm,
-						      const MultiSelection &ms) :
-    m_vm(vm),
-    m_oldSelection(vm->m_selections),
-    m_newSelection(ms)
-{
-}
-
-ViewManager::SetSelectionCommand::~SetSelectionCommand() { }
-
-void
-ViewManager::SetSelectionCommand::execute()
-{
-    m_vm->m_selections = m_newSelection;
-    m_vm->signalSelectionChange();
-}
-
-void
-ViewManager::SetSelectionCommand::unexecute()
-{
-    m_vm->m_selections = m_oldSelection;
-    m_vm->signalSelectionChange();
-}
-
-QString
-ViewManager::SetSelectionCommand::getName() const
-{
-    if (m_newSelection.getSelections().empty()) return tr("Clear Selection");
-    else return tr("Select");
-}
-
-Selection
-ViewManager::getContainingSelection(size_t frame, bool defaultToFollowing) const
-{
-    return m_selections.getContainingSelection(frame, defaultToFollowing);
-}
-
-void
-ViewManager::setToolMode(ToolMode mode)
-{
-    m_toolMode = mode;
-
-    emit toolModeChanged();
-}
-
-void
-ViewManager::setPlayLoopMode(bool mode)
-{
-    m_playLoopMode = mode;
-
-    emit playLoopModeChanged();
-}
-
-void
-ViewManager::setPlaySelectionMode(bool mode)
-{
-    m_playSelectionMode = mode;
-
-    emit playSelectionModeChanged();
-}
-
-size_t
-ViewManager::getPlaybackSampleRate() const
-{
-    if (m_playSource) {
-	return m_playSource->getTargetSampleRate();
-    }
-    return 0;
-}
-
-void
-ViewManager::setAudioPlaySource(AudioPlaySource *source)
-{
-    if (!m_playSource) {
-	QTimer::singleShot(100, this, SLOT(checkPlayStatus()));
-    }
-    m_playSource = source;
-}
-
-void
-ViewManager::playStatusChanged(bool playing)
-{
-    checkPlayStatus();
-}
-
-void
-ViewManager::checkPlayStatus()
-{
-    if (m_playSource && m_playSource->isPlaying()) {
-
-	float left = 0, right = 0;
-	if (m_playSource->getOutputLevels(left, right)) {
-	    if (left != m_lastLeft || right != m_lastRight) {
-		emit outputLevelsChanged(left, right);
-		m_lastLeft = left;
-		m_lastRight = right;
-	    }
-	}
-
-	m_playbackFrame = m_playSource->getCurrentPlayingFrame();
-
-#ifdef DEBUG_VIEW_MANAGER
-	std::cout << "ViewManager::checkPlayStatus: Playing, frame " << m_playbackFrame << ", levels " << m_lastLeft << "," << m_lastRight << std::endl;
-#endif
-
-	emit playbackFrameChanged(m_playbackFrame);
-
-	QTimer::singleShot(20, this, SLOT(checkPlayStatus()));
-
-    } else {
-
-	QTimer::singleShot(100, this, SLOT(checkPlayStatus()));
-	
-	if (m_lastLeft != 0.0 || m_lastRight != 0.0) {
-	    emit outputLevelsChanged(0.0, 0.0);
-	    m_lastLeft = 0.0;
-	    m_lastRight = 0.0;
-	}
-
-#ifdef DEBUG_VIEW_MANAGER
-//	std::cout << "ViewManager::checkPlayStatus: Not playing" << std::endl;
-#endif
-    }
-}
-
-bool
-ViewManager::isPlaying() const
-{
-    return m_playSource && m_playSource->isPlaying();
-}
-
-void
-ViewManager::considerSeek(void *p, unsigned long f, bool locked)
-{
-    if (locked) {
-	m_globalCentreFrame = f;
-    }
-
-#ifdef DEBUG_VIEW_MANAGER 
-    std::cout << "ViewManager::considerSeek(" << p << ", " << f << ", " << locked << ")" << std::endl;
-#endif
-
-    if (p == this || !locked) return;
-
-    if (m_playSource && m_playSource->isPlaying()) {
-	unsigned long playFrame = m_playSource->getCurrentPlayingFrame();
-	unsigned long diff = std::max(f, playFrame) - std::min(f, playFrame);
-	if (diff > 20000) {
-	    m_playbackFrame = f;
-	    m_playSource->play(f);
-#ifdef DEBUG_VIEW_MANAGER 
-	    std::cout << "ViewManager::considerSeek: reseeking from " << playFrame << " to " << f << std::endl;
-#endif
-	}
-    } else {
-	m_playbackFrame = f; //!!! only if that view is in scroll mode?
-    }
-}
-
-void
-ViewManager::considerZoomChange(void *p, unsigned long z, bool locked)
-{
-    if (locked) {
-	m_globalZoom = z;
-    }
-
-#ifdef DEBUG_VIEW_MANAGER 
-    std::cout << "ViewManager::considerZoomChange(" << p << ", " << z << ", " << locked << ")" << std::endl;
-#endif
-}
-
-void
-ViewManager::setOverlayMode(OverlayMode mode)
-{
-    if (m_overlayMode != mode) {
-        m_overlayMode = mode;
-        emit overlayModeChanged();
-    }
-}
-
-#ifdef INCLUDE_MOCFILES
-#include "ViewManager.moc.cpp"
-#endif
-
--- a/base/ViewManager.h	Fri Jul 28 11:09:36 2006 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,188 +0,0 @@
-/* -*- 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 Chris Cannam.
-    
-    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 _VIEW_MANAGER_H_
-#define _VIEW_MANAGER_H_
-
-#include <QObject>
-#include <QTimer>
-
-#include <map>
-
-#include "Selection.h"
-#include "Command.h"
-#include "Clipboard.h"
-
-class AudioPlaySource;
-class Model;
-
-/**
- * The ViewManager manages properties that may need to be synchronised
- * between separate Views.  For example, it handles signals associated
- * with changes to the global pan and zoom, and it handles selections.
- *
- * Views should be implemented in such a way as to work
- * correctly whether they are supplied with a ViewManager or not.
- */
-
-class ViewManager : public QObject
-{
-    Q_OBJECT
-
-public:
-    ViewManager();
-    virtual ~ViewManager();
-
-    void setAudioPlaySource(AudioPlaySource *source);
-
-    bool isPlaying() const;
-
-    unsigned long getGlobalCentreFrame() const;
-    unsigned long getGlobalZoom() const;
-
-    unsigned long getPlaybackFrame() const;
-    void setPlaybackFrame(unsigned long frame);
-
-    bool haveInProgressSelection() const;
-    const Selection &getInProgressSelection(bool &exclusive) const;
-    void setInProgressSelection(const Selection &selection, bool exclusive);
-    void clearInProgressSelection();
-
-    const MultiSelection &getSelection() const;
-
-    const MultiSelection::SelectionList &getSelections() const;
-    void setSelection(const Selection &selection);
-    void addSelection(const Selection &selection);
-    void removeSelection(const Selection &selection);
-    void clearSelections();
-
-    /**
-     * Return the selection that contains a given frame.
-     * If defaultToFollowing is true, and if the frame is not in a
-     * selected area, return the next selection after the given frame.
-     * Return the empty selection if no appropriate selection is found.
-     */
-    Selection getContainingSelection(size_t frame, bool defaultToFollowing) const;
-
-    Clipboard &getClipboard() { return m_clipboard; }
-
-    enum ToolMode {
-	NavigateMode,
-	SelectMode,
-        EditMode,
-	DrawMode
-    };
-    ToolMode getToolMode() const { return m_toolMode; }
-    void setToolMode(ToolMode mode);
-
-    bool getPlayLoopMode() const { return m_playLoopMode; }
-    void setPlayLoopMode(bool on);
-
-    bool getPlaySelectionMode() const { return m_playSelectionMode; }
-    void setPlaySelectionMode(bool on);
-
-    size_t getPlaybackSampleRate() const;
-    size_t getMainModelSampleRate() const { return m_mainModelSampleRate; }
-    void setMainModelSampleRate(size_t sr) { m_mainModelSampleRate = sr; }
-
-    enum OverlayMode {
-        NoOverlays,
-        BasicOverlays,
-        AllOverlays
-    };
-    void setOverlayMode(OverlayMode mode);
-    OverlayMode getOverlayMode() const { return m_overlayMode; }
-
-signals:
-    /** Emitted when a widget pans.  The originator identifies the widget. */
-    void centreFrameChanged(void *originator, unsigned long frame, bool locked);
-
-    /** Emitted when a widget zooms.  The originator identifies the widget. */
-    void zoomLevelChanged(void *originator, unsigned long zoom, bool locked);
-
-    /** Emitted when the playback frame changes. */
-    void playbackFrameChanged(unsigned long frame);
-
-    /** Emitted when the output levels change. Values in range 0.0 -> 1.0. */
-    void outputLevelsChanged(float left, float right);
-
-    /** Emitted when the selection has changed. */
-    void selectionChanged();
-
-    /** Emitted when the in-progress (rubberbanding) selection has changed. */
-    void inProgressSelectionChanged();
-
-    /** Emitted when the tool mode has been changed. */
-    void toolModeChanged();
-
-    /** Emitted when the play loop mode has been changed. */
-    void playLoopModeChanged();
-
-    /** Emitted when the play selection mode has been changed. */
-    void playSelectionModeChanged();
-
-    /** Emitted when the overlay mode has been changed. */
-    void overlayModeChanged();
-
-protected slots:
-    void checkPlayStatus();
-    void playStatusChanged(bool playing);
-    void considerSeek(void *, unsigned long, bool);
-    void considerZoomChange(void *, unsigned long, bool);
-
-protected:
-    AudioPlaySource *m_playSource;
-    unsigned long m_globalCentreFrame;
-    unsigned long m_globalZoom;
-    mutable unsigned long m_playbackFrame;
-    size_t m_mainModelSampleRate;
-
-    float m_lastLeft;
-    float m_lastRight;
-
-    MultiSelection m_selections;
-    Selection m_inProgressSelection;
-    bool m_inProgressExclusive;
-
-    Clipboard m_clipboard;
-
-    ToolMode m_toolMode;
-
-    bool m_playLoopMode;
-    bool m_playSelectionMode;
-
-    void setSelections(const MultiSelection &ms);
-    void signalSelectionChange();
-
-    class SetSelectionCommand : public Command
-    {
-    public:
-	SetSelectionCommand(ViewManager *vm, const MultiSelection &ms);
-	virtual ~SetSelectionCommand();
-	virtual void execute();
-	virtual void unexecute();
-	virtual QString getName() const;
-
-    protected:
-	ViewManager *m_vm;
-	MultiSelection m_oldSelection;
-	MultiSelection m_newSelection;
-    };
-
-    OverlayMode m_overlayMode;
-};
-
-#endif
-
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/data/model/DenseThreeDimensionalModel.cpp	Mon Jul 31 11:44:37 2006 +0000
@@ -0,0 +1,314 @@
+/* -*- 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 Chris Cannam.
+    
+    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 "DenseThreeDimensionalModel.h"
+
+#include <QTextStream>
+
+DenseThreeDimensionalModel::DenseThreeDimensionalModel(size_t sampleRate,
+						       size_t windowSize,
+						       size_t yBinCount,
+						       bool notifyOnAdd) :
+    m_sampleRate(sampleRate),
+    m_windowSize(windowSize),
+    m_yBinCount(yBinCount),
+    m_minimum(0.0),
+    m_maximum(0.0),
+    m_notifyOnAdd(notifyOnAdd),
+    m_sinceLastNotifyMin(-1),
+    m_sinceLastNotifyMax(-1),
+    m_completion(100)
+{
+}    
+
+bool
+DenseThreeDimensionalModel::isOK() const
+{
+    return true;
+}
+
+size_t
+DenseThreeDimensionalModel::getSampleRate() const
+{
+    return m_sampleRate;
+}
+
+size_t
+DenseThreeDimensionalModel::getStartFrame() const
+{
+    return 0;
+}
+
+size_t
+DenseThreeDimensionalModel::getEndFrame() const
+{
+    return m_windowSize * m_data.size() + (m_windowSize - 1);
+}
+
+Model *
+DenseThreeDimensionalModel::clone() const
+{
+    DenseThreeDimensionalModel *model = new DenseThreeDimensionalModel
+	(m_sampleRate, m_windowSize, m_yBinCount);
+
+    model->m_minimum = m_minimum;
+    model->m_maximum = m_maximum;
+
+    for (size_t i = 0; i < m_data.size(); ++i) {
+	model->setBinValues(i * m_windowSize, m_data[i]);
+    }
+
+    return model;
+}
+
+size_t
+DenseThreeDimensionalModel::getWindowSize() const
+{
+    return m_windowSize;
+}
+
+void
+DenseThreeDimensionalModel::setWindowSize(size_t sz)
+{
+    m_windowSize = sz;
+}
+
+size_t
+DenseThreeDimensionalModel::getYBinCount() const
+{
+    return m_yBinCount;
+}
+
+void
+DenseThreeDimensionalModel::setYBinCount(size_t sz)
+{
+    m_yBinCount = sz;
+}
+
+float
+DenseThreeDimensionalModel::getMinimumLevel() const
+{
+    return m_minimum;
+}
+
+void
+DenseThreeDimensionalModel::setMinimumLevel(float level)
+{
+    m_minimum = level;
+}
+
+float
+DenseThreeDimensionalModel::getMaximumLevel() const
+{
+    return m_maximum;
+}
+
+void
+DenseThreeDimensionalModel::setMaximumLevel(float level)
+{
+    m_maximum = level;
+}
+
+void
+DenseThreeDimensionalModel::getBinValues(long windowStart,
+					 BinValueSet &result) const
+{
+    QMutexLocker locker(&m_mutex);
+    
+    long index = windowStart / m_windowSize;
+
+    if (index >= 0 && index < long(m_data.size())) {
+	result = m_data[index];
+    } else {
+	result.clear();
+    }
+
+    while (result.size() < m_yBinCount) result.push_back(m_minimum);
+}
+
+float
+DenseThreeDimensionalModel::getBinValue(long windowStart,
+					size_t n) const
+{
+    QMutexLocker locker(&m_mutex);
+    
+    long index = windowStart / m_windowSize;
+
+    if (index >= 0 && index < long(m_data.size())) {
+	const BinValueSet &s = m_data[index];
+	if (n < s.size()) return s[n];
+    }
+
+    return m_minimum;
+}
+
+void
+DenseThreeDimensionalModel::setBinValues(long windowStart,
+					 const BinValueSet &values)
+{
+    QMutexLocker locker(&m_mutex);
+
+    long index = windowStart / m_windowSize;
+
+    while (index >= long(m_data.size())) {
+	m_data.push_back(BinValueSet());
+    }
+
+    bool newExtents = (m_data.empty() && (m_minimum == m_maximum));
+    bool allChange = false;
+
+    for (size_t i = 0; i < values.size(); ++i) {
+	if (newExtents || values[i] < m_minimum) {
+	    m_minimum = values[i];
+	    allChange = true;
+	}
+	if (newExtents || values[i] > m_maximum) {
+	    m_maximum = values[i];
+	    allChange = true;
+	}
+    }
+
+    m_data[index] = values;
+
+    if (m_notifyOnAdd) {
+	if (allChange) {
+	    emit modelChanged();
+	} else {
+	    emit modelChanged(windowStart, windowStart + m_windowSize);
+	}
+    } else {
+	if (allChange) {
+	    m_sinceLastNotifyMin = -1;
+	    m_sinceLastNotifyMax = -1;
+	    emit modelChanged();
+	} else {
+	    if (m_sinceLastNotifyMin == -1 ||
+		windowStart < m_sinceLastNotifyMin) {
+		m_sinceLastNotifyMin = windowStart;
+	    }
+	    if (m_sinceLastNotifyMax == -1 ||
+		windowStart > m_sinceLastNotifyMax) {
+		m_sinceLastNotifyMax = windowStart;
+	    }
+	}
+    }
+}
+
+QString
+DenseThreeDimensionalModel::getBinName(size_t n) const
+{
+    if (m_binNames.size() > n) return m_binNames[n];
+    else return "";
+}
+
+void
+DenseThreeDimensionalModel::setBinName(size_t n, QString name)
+{
+    while (m_binNames.size() <= n) m_binNames.push_back("");
+    m_binNames[n] = name;
+    emit modelChanged();
+}
+
+void
+DenseThreeDimensionalModel::setBinNames(std::vector<QString> names)
+{
+    m_binNames = names;
+    emit modelChanged();
+}
+
+void
+DenseThreeDimensionalModel::setCompletion(int completion)
+{
+    if (m_completion != completion) {
+	m_completion = completion;
+
+	if (completion == 100) {
+
+	    m_notifyOnAdd = true; // henceforth
+	    emit modelChanged();
+
+	} else if (!m_notifyOnAdd) {
+
+	    if (m_sinceLastNotifyMin >= 0 &&
+		m_sinceLastNotifyMax >= 0) {
+		emit modelChanged(m_sinceLastNotifyMin,
+				  m_sinceLastNotifyMax + m_windowSize);
+		m_sinceLastNotifyMin = m_sinceLastNotifyMax = -1;
+	    } else {
+		emit completionChanged();
+	    }
+	} else {
+	    emit completionChanged();
+	}	    
+    }
+}
+
+void
+DenseThreeDimensionalModel::toXml(QTextStream &out,
+                                  QString indent,
+                                  QString extraAttributes) const
+{
+    out << Model::toXmlString
+	(indent, QString("type=\"dense\" dimensions=\"3\" windowSize=\"%1\" yBinCount=\"%2\" minimum=\"%3\" maximum=\"%4\" dataset=\"%5\" %6")
+	 .arg(m_windowSize)
+	 .arg(m_yBinCount)
+	 .arg(m_minimum)
+	 .arg(m_maximum)
+	 .arg(getObjectExportId(&m_data))
+	 .arg(extraAttributes));
+
+    out << indent;
+    out << QString("<dataset id=\"%1\" dimensions=\"3\" separator=\" \">\n")
+	.arg(getObjectExportId(&m_data));
+
+    for (size_t i = 0; i < m_binNames.size(); ++i) {
+	if (m_binNames[i] != "") {
+	    out << indent + "  ";
+	    out << QString("<bin number=\"%1\" name=\"%2\"/>\n")
+		.arg(i).arg(m_binNames[i]);
+	}
+    }
+
+    for (size_t i = 0; i < m_data.size(); ++i) {
+	out << indent + "  ";
+	out << QString("<row n=\"%1\">").arg(i);
+	for (size_t j = 0; j < m_data[i].size(); ++j) {
+	    if (j > 0) out << " ";
+	    out << m_data[i][j];
+	}
+	out << QString("</row>\n");
+    }
+
+    out << indent + "</dataset>\n";
+}
+
+QString
+DenseThreeDimensionalModel::toXmlString(QString indent,
+					QString extraAttributes) const
+{
+    QString s;
+
+    {
+        QTextStream out(&s);
+        toXml(out, indent, extraAttributes);
+    }
+
+    return s;
+}
+
+#ifdef INCLUDE_MOCFILES
+#include "DenseThreeDimensionalModel.moc.cpp"
+#endif
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/data/model/DenseThreeDimensionalModel.h	Mon Jul 31 11:44:37 2006 +0000
@@ -0,0 +1,139 @@
+/* -*- 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 Chris Cannam.
+    
+    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 _DENSE_THREE_DIMENSIONAL_MODEL_H_
+#define _DENSE_THREE_DIMENSIONAL_MODEL_H_
+
+#include "base/Model.h"
+#include "base/ZoomConstraint.h"
+
+#include <QMutex>
+#include <vector>
+
+class DenseThreeDimensionalModel : public Model,
+				   virtual public ZoomConstraint,
+				   virtual public QObject
+{
+    Q_OBJECT
+
+public:
+    //!!! need to reconcile terminology -- windowSize here, resolution in sparse models
+    DenseThreeDimensionalModel(size_t sampleRate,
+			       size_t windowSize,
+			       size_t yBinCount,
+			       bool notifyOnAdd = true);
+
+    virtual bool isOK() const;
+
+    virtual size_t getSampleRate() const;
+    virtual size_t getStartFrame() const;
+    virtual size_t getEndFrame() const;
+
+    virtual Model *clone() const;
+    
+
+    /**
+     * Return the number of sample frames covered by each set of bins.
+     */
+    virtual size_t getWindowSize() const;
+
+    /**
+     * Set the number of sample frames covered by each set of bins.
+     */
+    virtual void setWindowSize(size_t sz);
+
+    /**
+     * Return the number of bins in each set of bins.
+     */
+    virtual size_t getYBinCount() const; 
+
+    /**
+     * Set the number of bins in each set of bins.
+     */
+    virtual void setYBinCount(size_t sz);
+
+    /**
+     * Return the minimum value of the value in each bin.
+     */
+    virtual float getMinimumLevel() const;
+
+    /**
+     * Set the minimum value of the value in a bin.
+     */
+    virtual void setMinimumLevel(float sz);
+
+    /**
+     * Return the maximum value of the value in each bin.
+     */
+    virtual float getMaximumLevel() const;
+
+    /**
+     * Set the maximum value of the value in a bin.
+     */
+    virtual void setMaximumLevel(float sz);
+
+    typedef std::vector<float> BinValueSet;
+
+    /**
+     * Get the set of bin values at the given sample frame (i.e. the
+     * windowStartFrame/getWindowSize()'th set of bins).
+     */
+    virtual void getBinValues(long windowStartFrame, BinValueSet &result) const;
+
+    /**
+     * Get a single value, the one at the n'th bin of the set of bins
+     * starting at the given sample frame.
+     */
+    virtual float getBinValue(long windowStartFrame, size_t n) const;
+
+    /**
+     * Set the entire set of bin values at the given sample frame.
+     */
+    virtual void setBinValues(long windowStartFrame, const BinValueSet &values);
+
+    virtual QString getBinName(size_t n) const;
+    virtual void setBinName(size_t n, QString);
+    virtual void setBinNames(std::vector<QString> names);
+
+    virtual void setCompletion(int completion);
+    virtual int getCompletion() const { return m_completion; }
+
+    virtual void toXml(QTextStream &out,
+                       QString indent = "",
+                       QString extraAttributes = "") const;
+
+    virtual QString toXmlString(QString indent = "",
+				QString extraAttributes = "") const;
+
+protected:
+    typedef std::vector<BinValueSet> ValueMatrix;
+    ValueMatrix m_data;
+
+    std::vector<QString> m_binNames;
+
+    size_t m_sampleRate;
+    size_t m_windowSize;
+    size_t m_yBinCount;
+    float m_minimum;
+    float m_maximum;
+    bool m_notifyOnAdd;
+    long m_sinceLastNotifyMin;
+    long m_sinceLastNotifyMax;
+    int m_completion;
+
+    mutable QMutex m_mutex;
+};
+
+#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/data/model/DenseTimeValueModel.cpp	Mon Jul 31 11:44:37 2006 +0000
@@ -0,0 +1,28 @@
+/* -*- 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 Chris Cannam.
+    
+    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 "DenseTimeValueModel.h"
+#include "PlayParameterRepository.h"
+
+DenseTimeValueModel::DenseTimeValueModel()
+{
+    PlayParameterRepository::getInstance()->addModel(this);
+}
+	
+
+#ifdef INCLUDE_MOCFILES
+#include "DenseTimeValueModel.moc.cpp"
+#endif
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/data/model/DenseTimeValueModel.h	Mon Jul 31 11:44:37 2006 +0000
@@ -0,0 +1,76 @@
+/* -*- 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 Chris Cannam.
+    
+    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 _DENSE_TIME_VALUE_MODEL_H_
+#define _DENSE_TIME_VALUE_MODEL_H_
+
+#include <QObject>
+
+#include "base/Model.h"
+
+/**
+ * Base class for models containing dense two-dimensional data (value
+ * against time).  For example, audio waveform data.
+ */
+
+class DenseTimeValueModel : public Model,
+			    virtual public QObject
+{
+    Q_OBJECT
+
+public:
+    DenseTimeValueModel();
+
+    /**
+     * Return the minimum possible value found in this model type.
+     * (That is, the minimum that would be valid, not the minimum
+     * actually found in a particular model).
+     */
+    virtual float getValueMinimum() const = 0;
+
+    /**
+     * Return the minimum possible value found in this model type.
+     * (That is, the minimum that would be valid, not the minimum
+     * actually found in a particular model).
+     */
+    virtual float getValueMaximum() const = 0;
+
+    /**
+     * Return the number of distinct channels for this model.
+     */
+    virtual size_t getChannelCount() const = 0;
+
+    /**
+     * Get the specified set of samples from the given channel of the
+     * model in single-precision floating-point format.  Return the
+     * number of samples actually retrieved.
+     * If the channel is given as -1, mix all available channels and
+     * return the result.
+     */
+    virtual size_t getValues(int channel, size_t start, size_t end,
+			     float *buffer) const = 0;
+
+    /**
+     * Get the specified set of samples from the given channel of the
+     * model in double-precision floating-point format.  Return the
+     * number of samples actually retrieved.
+     * If the channel is given as -1, mix all available channels and
+     * return the result.
+     */
+    virtual size_t getValues(int channel, size_t start, size_t end,
+			     double *buffer) const = 0;
+};
+
+#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/data/model/NoteModel.cpp	Mon Jul 31 11:44:37 2006 +0000
@@ -0,0 +1,75 @@
+/* -*- 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 Chris Cannam.
+    
+    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 "NoteModel.h"
+
+NoteModel::PointList
+NoteModel::getPoints(long start, long end) const
+{
+    if (start > end) return PointList();
+    QMutexLocker locker(&m_mutex);
+
+    Note endPoint(end);
+    
+    PointListIterator endItr = m_points.upper_bound(endPoint);
+
+    if (endItr != m_points.end()) ++endItr;
+    if (endItr != m_points.end()) ++endItr;
+
+    PointList rv;
+
+    for (PointListIterator i = endItr; i != m_points.begin(); ) {
+        --i;
+        if (i->frame < start) {
+            if (i->frame + i->duration >= start) {
+                rv.insert(*i);
+            }
+        } else if (i->frame <= end) {
+            rv.insert(*i);
+        }
+    }
+
+    return rv;
+}
+
+NoteModel::PointList
+NoteModel::getPoints(long frame) const
+{
+    QMutexLocker locker(&m_mutex);
+
+    if (m_resolution == 0) return PointList();
+
+    long start = (frame / m_resolution) * m_resolution;
+    long end = start + m_resolution;
+
+    Note endPoint(end);
+    
+    PointListIterator endItr = m_points.upper_bound(endPoint);
+
+    PointList rv;
+
+    for (PointListIterator i = endItr; i != m_points.begin(); ) {
+        --i;
+        if (i->frame < start) {
+            if (i->frame + i->duration >= start) {
+                rv.insert(*i);
+            }
+        } else if (i->frame <= end) {
+            rv.insert(*i);
+        }
+    }
+
+    return rv;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/data/model/NoteModel.h	Mon Jul 31 11:44:37 2006 +0000
@@ -0,0 +1,127 @@
+/* -*- 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 Chris Cannam.
+    
+    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 _NOTE_MODEL_H_
+#define _NOTE_MODEL_H_
+
+#include "SparseValueModel.h"
+#include "PlayParameterRepository.h"
+#include "base/RealTime.h"
+
+/**
+ * Note type for use in a SparseModel or SparseValueModel.  All we
+ * mean by a "note" is something that has an onset time, a single
+ * value, and a duration.  Like other points, it can also have a
+ * label.  With this point type, the model can be thought of as
+ * representing a simple MIDI-type piano roll, except that the y
+ * coordinates (values) do not have to be discrete integers.
+ */
+
+struct Note
+{
+public:
+    Note(long _frame) : frame(_frame), value(0.0f), duration(0) { }
+    Note(long _frame, float _value, size_t _duration, QString _label) :
+	frame(_frame), value(_value), duration(_duration), label(_label) { }
+
+    int getDimensions() const { return 3; }
+
+    long frame;
+    float value;
+    size_t duration;
+    QString label;
+
+    QString toXmlString(QString indent = "",
+			QString extraAttributes = "") const
+    {
+	return QString("%1<point frame=\"%2\" value=\"%3\" duration=\"%4\" label=\"%5\" %6/>\n")
+	    .arg(indent).arg(frame).arg(value).arg(duration).arg(label).arg(extraAttributes);
+    }
+
+    QString toDelimitedDataString(QString delimiter, size_t sampleRate) const
+    {
+        QStringList list;
+        list << RealTime::frame2RealTime(frame, sampleRate).toString().c_str();
+        list << QString("%1").arg(value);
+        list << QString("%1").arg(duration);
+        list << label;
+        return list.join(delimiter);
+    }
+
+    struct Comparator {
+	bool operator()(const Note &p1,
+			const Note &p2) const {
+	    if (p1.frame != p2.frame) return p1.frame < p2.frame;
+	    if (p1.value != p2.value) return p1.value < p2.value;
+	    if (p1.duration != p2.duration) return p1.duration < p2.duration;
+	    return p1.label < p2.label;
+	}
+    };
+    
+    struct OrderComparator {
+	bool operator()(const Note &p1,
+			const Note &p2) const {
+	    return p1.frame < p2.frame;
+	}
+    };
+};
+
+
+class NoteModel : public SparseValueModel<Note>
+{
+public:
+    NoteModel(size_t sampleRate, size_t resolution,
+	      float valueMinimum, float valueMaximum,
+	      bool notifyOnAdd = true) :
+	SparseValueModel<Note>(sampleRate, resolution,
+			       valueMinimum, valueMaximum,
+			       notifyOnAdd),
+	m_valueQuantization(0)
+    {
+	PlayParameterRepository::getInstance()->addModel(this);
+    }
+
+    float getValueQuantization() const { return m_valueQuantization; }
+    void setValueQuantization(float q) { m_valueQuantization = q; }
+
+    /**
+     * Notes have a duration, so this returns all points that span any
+     * of the given range (as well as the usual additional few before
+     * and after).  Consequently this can be very slow (optimised data
+     * structures still to be done!).
+     */
+    virtual PointList getPoints(long start, long end) const;
+
+    /**
+     * Notes have a duration, so this returns all points that span the
+     * given frame.  Consequently this can be very slow (optimised
+     * data structures still to be done!).
+     */
+    virtual PointList getPoints(long frame) const;
+
+    virtual QString toXmlString(QString indent = "",
+				QString extraAttributes = "") const
+    {
+	return SparseValueModel<Note>::toXmlString
+	    (indent,
+	     QString("%1 valueQuantization=\"%2\"")
+	     .arg(extraAttributes).arg(m_valueQuantization));
+    }
+
+protected:
+    float m_valueQuantization;
+};
+
+#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/data/model/PowerOfSqrtTwoZoomConstraint.cpp	Mon Jul 31 11:44:37 2006 +0000
@@ -0,0 +1,104 @@
+/* -*- 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 Chris Cannam.
+    
+    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 "PowerOfSqrtTwoZoomConstraint.h"
+
+#include <iostream>
+#include <cmath>
+
+
+size_t
+PowerOfSqrtTwoZoomConstraint::getNearestBlockSize(size_t blockSize,
+						  RoundingDirection dir) const
+{
+    int type, power;
+    size_t rv = getNearestBlockSize(blockSize, type, power, dir);
+    return rv;
+}
+
+size_t
+PowerOfSqrtTwoZoomConstraint::getNearestBlockSize(size_t blockSize,
+						  int &type, 
+						  int &power,
+						  RoundingDirection dir) const
+{
+//    std::cerr << "given " << blockSize << std::endl;
+
+    size_t minCachePower = getMinCachePower();
+
+    if (blockSize < (1U << minCachePower)) {
+	type = -1;
+	power = 0;
+	float val = 1.0, prevVal = 1.0;
+	while (val + 0.01 < blockSize) {
+	    prevVal = val;
+	    val *= sqrt(2);
+	}
+	size_t rval;
+	if (dir == RoundUp) rval = size_t(val + 0.01);
+	else if (dir == RoundDown) rval = size_t(prevVal + 0.01);
+	else if (val - blockSize < blockSize - prevVal) rval = size_t(val + 0.01);
+	else rval = size_t(prevVal + 0.01);
+//	std::cerr << "returning " << rval << std::endl;
+	return rval;
+    }
+
+    unsigned int prevBase = (1 << minCachePower);
+    unsigned int prevPower = minCachePower;
+    unsigned int prevType = 0;
+
+    size_t result = 0;
+
+    for (unsigned int i = 0; ; ++i) {
+
+	power = minCachePower + i/2;
+	type = i % 2;
+
+	unsigned int base;
+	if (type == 0) {
+	    base = (1 << power);
+	} else {
+	    base = (((unsigned int)((1 << minCachePower) * sqrt(2) + 0.01))
+		    << (power - minCachePower));
+	}
+
+//	std::cerr << "Testing base " << base << std::endl;
+	if (base >= blockSize) {
+	    if (dir == RoundNearest) {
+		if (base - blockSize < blockSize - prevBase) {
+		    dir = RoundUp;
+		} else {
+		    dir = RoundDown;
+		}
+	    }
+	    if (dir == RoundUp) {
+		result = base;
+		break;
+	    } else {
+		type = prevType;
+		power = prevPower;
+		result = prevBase;
+		break;
+	    }
+	}
+
+	prevType = type;
+	prevPower = power;
+	prevBase = base;
+    }
+
+    if (result > getMaxZoomLevel()) result = getMaxZoomLevel();
+    return result;
+}   
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/data/model/PowerOfSqrtTwoZoomConstraint.h	Mon Jul 31 11:44:37 2006 +0000
@@ -0,0 +1,39 @@
+/* -*- 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 Chris Cannam.
+    
+    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 _POWER_OF_SQRT_TWO_ZOOM_CONSTRAINT_H_
+#define _POWER_OF_SQRT_TWO_ZOOM_CONSTRAINT_H_
+
+#include "base/ZoomConstraint.h"
+
+class PowerOfSqrtTwoZoomConstraint : virtual public ZoomConstraint
+{
+public:
+    virtual size_t getNearestBlockSize(size_t requestedBlockSize,
+				       RoundingDirection dir = RoundNearest)
+	const;
+    
+protected:
+    virtual size_t getNearestBlockSize(size_t requestedBlockSize,
+				       int &type,
+				       int &power,
+				       RoundingDirection dir = RoundNearest)
+	const;
+	
+    virtual size_t getMinCachePower() const { return 6; }
+};
+
+#endif
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/data/model/PowerOfTwoZoomConstraint.cpp	Mon Jul 31 11:44:37 2006 +0000
@@ -0,0 +1,47 @@
+/* -*- 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 Chris Cannam.
+    
+    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 "PowerOfTwoZoomConstraint.h"
+
+size_t
+PowerOfTwoZoomConstraint::getNearestBlockSize(size_t req,
+					      RoundingDirection dir) const
+{
+    size_t result = 0;
+
+    for (size_t bs = 1; ; bs *= 2) {
+	if (bs >= req) {
+	    if (dir == RoundNearest) {
+		if (bs - req < req - bs/2) {
+		    result = bs;
+		    break;
+		} else {
+		    result = bs/2;
+		    break;
+		}
+	    } else if (dir == RoundDown) {
+		result = bs/2;
+		break;
+	    } else {
+		result = bs;
+		break;
+	    }
+	}
+    }
+
+    if (result > getMaxZoomLevel()) result = getMaxZoomLevel();
+    return result;
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/data/model/PowerOfTwoZoomConstraint.h	Mon Jul 31 11:44:37 2006 +0000
@@ -0,0 +1,35 @@
+/* -*- 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 Chris Cannam.
+    
+    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 _POWER_OF_TWO_ZOOM_CONSTRAINT_H_
+#define _POWER_OF_TWO_ZOOM_CONSTRAINT_H_
+
+#include "base/ZoomConstraint.h"
+
+class PowerOfTwoZoomConstraint : virtual public ZoomConstraint
+{
+public:
+    virtual size_t getNearestBlockSize(size_t requestedBlockSize,
+				       RoundingDirection dir = RoundNearest)
+	const;
+/*
+    virtual size_t getNextBlockSize(size_t blockSize,
+				    RoundingDirection dir = RoundNearest)
+	const;
+*/
+};
+
+#endif
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/data/model/RangeSummarisableTimeValueModel.cpp	Mon Jul 31 11:44:37 2006 +0000
@@ -0,0 +1,5 @@
+
+#ifdef INCLUDE_MOCFILES
+#include "RangeSummarisableTimeValueModel.moc.cpp"
+#endif
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/data/model/RangeSummarisableTimeValueModel.h	Mon Jul 31 11:44:37 2006 +0000
@@ -0,0 +1,75 @@
+/* -*- 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 Chris Cannam.
+    
+    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 _RANGE_SUMMARISABLE_TIME_VALUE_MODEL_H_
+#define _RANGE_SUMMARISABLE_TIME_VALUE_MODEL_H_
+
+#include <QObject>
+
+#include "DenseTimeValueModel.h"
+#include "base/ZoomConstraint.h"
+
+/**
+ * Base class for models containing dense two-dimensional data (value
+ * against time) that may be meaningfully represented in a zoomed view
+ * using min/max range summaries.  Audio waveform data is an obvious
+ * example: think "peaks and minima" for "ranges".
+ */
+
+class RangeSummarisableTimeValueModel : public DenseTimeValueModel,
+					virtual public ZoomConstraint,
+					virtual public QObject
+{
+    Q_OBJECT
+
+public:
+    struct Range
+    {
+        float min;
+        float max;
+        float absmean;
+        Range() : 
+            min(0.f), max(0.f), absmean(0.f) { }
+        Range(const Range &r) : 
+            min(r.min), max(r.max), absmean(r.absmean) { }
+        Range(float min_, float max_, float absmean_) :
+            min(min_), max(max_), absmean(absmean_) { }
+    };
+
+    typedef std::vector<Range> RangeBlock;
+
+    /**
+     * Return ranges between the given start and end frames,
+     * summarised at the given block size.  ((end - start + 1) /
+     * blockSize) ranges should ideally be returned.
+     *
+     * If the given block size is not supported by this model
+     * (according to its zoom constraint), also modify the blockSize
+     * parameter so as to return the block size that was actually
+     * obtained.
+     */
+    virtual RangeBlock getRanges(size_t channel, size_t start, size_t end,
+				 size_t &blockSize) const = 0;
+
+    /**
+     * Return the range between the given start and end frames,
+     * summarised at a block size equal to the distance between start
+     * and end frames.
+     */
+    virtual Range getRange(size_t channel, size_t start, size_t end) const = 0;
+};
+
+#endif
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/data/model/SparseModel.h	Mon Jul 31 11:44:37 2006 +0000
@@ -0,0 +1,641 @@
+/* -*- 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 Chris Cannam.
+    
+    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 _SPARSE_MODEL_H_
+#define _SPARSE_MODEL_H_
+
+#include "base/Model.h"
+#include "base/Command.h"
+#include "base/CommandHistory.h"
+
+#include <iostream>
+
+#include <set>
+#include <QMutex>
+#include <QTextStream>
+
+
+/**
+ * Model containing sparse data (points with some properties).  The
+ * properties depend on the point type.
+ */
+
+template <typename PointType>
+class SparseModel : public Model
+{
+public:
+    SparseModel(size_t sampleRate, size_t resolution,
+		bool notifyOnAdd = true);
+    virtual ~SparseModel() { }
+    
+    virtual bool isOK() const { return true; }
+    virtual size_t getStartFrame() const;
+    virtual size_t getEndFrame() const;
+    virtual size_t getSampleRate() const { return m_sampleRate; }
+
+    virtual Model *clone() const;
+
+    // Number of frames of the underlying sample rate that this model
+    // is capable of resolving to.  For example, if m_resolution == 10
+    // then every point in this model will be at a multiple of 10
+    // sample frames and should be considered to cover a window ending
+    // 10 sample frames later.
+    virtual size_t getResolution() const {
+        return m_resolution ? m_resolution : 1;
+    }
+    virtual void setResolution(size_t resolution);
+
+    typedef PointType Point;
+    typedef std::multiset<PointType,
+			  typename PointType::OrderComparator> PointList;
+    typedef typename PointList::iterator PointListIterator;
+
+    /**
+     * Return whether the model is empty or not.
+     */
+    virtual bool isEmpty() const;
+
+    /**
+     * Get the total number of points in the model.
+     */
+    virtual size_t getPointCount() const;
+
+    /**
+     * Get all of the points in this model between the given
+     * boundaries (in frames), as well as up to two points before and
+     * after the boundaries.  If you need exact boundaries, check the
+     * point coordinates in the returned list.
+     */
+    virtual PointList getPoints(long start, long end) const;
+
+    /**
+     * Get all points that cover the given frame number, taking the
+     * resolution of the model into account.
+     */
+    virtual PointList getPoints(long frame) const;
+
+    /**
+     * Return all points that share the nearest frame number prior to
+     * the given one at which there are any points.
+     */
+    virtual PointList getPreviousPoints(long frame) const;
+
+    /**
+     * Return all points that share the nearest frame number
+     * subsequent to the given one at which there are any points.
+     */
+    virtual PointList getNextPoints(long frame) const;
+
+    /**
+     * Remove all points.
+     */
+    virtual void clear();
+
+    /**
+     * Add a point.
+     */
+    virtual void addPoint(const PointType &point);
+
+    /** 
+     * Remove a point.  Points are not necessarily unique, so this
+     * function will remove the first point that compares equal to the
+     * supplied one using Point::Comparator.  Other identical points
+     * may remain in the model.
+     */
+    virtual void deletePoint(const PointType &point);
+
+    virtual void setCompletion(int completion);
+    virtual int getCompletion() const { return m_completion; }
+
+    virtual bool hasTextLabels() const { return m_hasTextLabels; }
+
+    virtual void toXml(QTextStream &out,
+                       QString indent = "",
+                       QString extraAttributes = "") const;
+
+    virtual QString toXmlString(QString indent = "",
+				QString extraAttributes = "") const;
+
+    virtual QString toDelimitedDataString(QString delimiter) const
+    { 
+        QString s;
+        for (PointListIterator i = m_points.begin(); i != m_points.end(); ++i) {
+            s += i->toDelimitedDataString(delimiter, m_sampleRate) + "\n";
+        }
+        return s;
+    }
+
+    /**
+     * Command to add a point, with undo.
+     */
+    class AddPointCommand : public Command
+    {
+    public:
+	AddPointCommand(SparseModel<PointType> *model,
+			const PointType &point,
+                        QString name = "") :
+	    m_model(model), m_point(point), m_name(name) { }
+
+	virtual QString getName() const {
+            return (m_name == "" ? tr("Add Point") : m_name);
+        }
+
+	virtual void execute() { m_model->addPoint(m_point); }
+	virtual void unexecute() { m_model->deletePoint(m_point); }
+
+	const PointType &getPoint() const { return m_point; }
+
+    private:
+	SparseModel<PointType> *m_model;
+	PointType m_point;
+        QString m_name;
+    };
+
+
+    /**
+     * Command to remove a point, with undo.
+     */
+    class DeletePointCommand : public Command
+    {
+    public:
+	DeletePointCommand(SparseModel<PointType> *model,
+			   const PointType &point) :
+	    m_model(model), m_point(point) { }
+
+	virtual QString getName() const { return tr("Delete Point"); }
+
+	virtual void execute() { m_model->deletePoint(m_point); }
+	virtual void unexecute() { m_model->addPoint(m_point); }
+
+	const PointType &getPoint() const { return m_point; }
+
+    private:
+	SparseModel<PointType> *m_model;
+	PointType m_point;
+    };
+
+    
+    /**
+     * Command to add or remove a series of points, with undo.
+     * Consecutive add/remove pairs for the same point are collapsed.
+     */
+    class EditCommand : public MacroCommand
+    {
+    public:
+	EditCommand(SparseModel<PointType> *model, QString commandName);
+
+	virtual void addPoint(const PointType &point);
+	virtual void deletePoint(const PointType &point);
+
+	/**
+	 * Stack an arbitrary other command in the same sequence.
+	 */
+	virtual void addCommand(Command *command) { addCommand(command, true); }
+
+	/**
+	 * If any points have been added or deleted, add this command
+	 * to the command history.  Otherwise delete the command.
+	 */
+	virtual void finish();
+
+    protected:
+	virtual void addCommand(Command *command, bool executeFirst);
+
+	SparseModel<PointType> *m_model;
+    };
+
+
+    /**
+     * Command to relabel a point.
+     */
+    class RelabelCommand : public Command
+    {
+    public:
+	RelabelCommand(SparseModel<PointType> *model,
+		       const PointType &point,
+		       QString newLabel) :
+	    m_model(model), m_oldPoint(point), m_newPoint(point) {
+	    m_newPoint.label = newLabel;
+	}
+
+	virtual QString getName() const { return tr("Re-Label Point"); }
+
+	virtual void execute() { 
+	    m_model->deletePoint(m_oldPoint);
+	    m_model->addPoint(m_newPoint);
+	    std::swap(m_oldPoint, m_newPoint);
+	}
+
+	virtual void unexecute() { execute(); }
+
+    private:
+	SparseModel<PointType> *m_model;
+	PointType m_oldPoint;
+	PointType m_newPoint;
+    };
+
+    
+
+protected:
+    size_t m_sampleRate;
+    size_t m_resolution;
+    bool m_notifyOnAdd;
+    long m_sinceLastNotifyMin;
+    long m_sinceLastNotifyMax;
+    bool m_hasTextLabels;
+
+    PointList m_points;
+    size_t m_pointCount;
+    mutable QMutex m_mutex;
+    int m_completion;
+};
+
+
+template <typename PointType>
+SparseModel<PointType>::SparseModel(size_t sampleRate,
+                                    size_t resolution,
+                                    bool notifyOnAdd) :
+    m_sampleRate(sampleRate),
+    m_resolution(resolution),
+    m_notifyOnAdd(notifyOnAdd),
+    m_sinceLastNotifyMin(-1),
+    m_sinceLastNotifyMax(-1),
+    m_hasTextLabels(false),
+    m_pointCount(0),
+    m_completion(100)
+{
+}
+
+template <typename PointType>
+size_t
+SparseModel<PointType>::getStartFrame() const
+{
+    QMutexLocker locker(&m_mutex);
+    size_t f = 0;
+    if (!m_points.empty()) {
+	f = m_points.begin()->frame;
+    }
+    return f;
+}
+
+template <typename PointType>
+size_t
+SparseModel<PointType>::getEndFrame() const
+{
+    QMutexLocker locker(&m_mutex);
+    size_t f = 0;
+    if (!m_points.empty()) {
+	PointListIterator i(m_points.end());
+	f = (--i)->frame;
+    }
+    return f;
+}
+
+template <typename PointType>
+Model *
+SparseModel<PointType>::clone() const
+{
+    SparseModel<PointType> *model =
+	new SparseModel<PointType>(m_sampleRate, m_resolution, m_notifyOnAdd);
+    model->m_points = m_points;
+    model->m_pointCount = m_pointCount;
+    return model;
+}
+
+template <typename PointType>
+bool
+SparseModel<PointType>::isEmpty() const
+{
+    return m_pointCount == 0;
+}
+
+template <typename PointType>
+size_t
+SparseModel<PointType>::getPointCount() const
+{
+    return m_pointCount;
+}
+
+template <typename PointType>
+typename SparseModel<PointType>::PointList
+SparseModel<PointType>::getPoints(long start, long end) const
+{
+    if (start > end) return PointList();
+    QMutexLocker locker(&m_mutex);
+
+    PointType startPoint(start), endPoint(end);
+    
+    PointListIterator startItr = m_points.lower_bound(startPoint);
+    PointListIterator   endItr = m_points.upper_bound(endPoint);
+
+    if (startItr != m_points.begin()) --startItr;
+    if (startItr != m_points.begin()) --startItr;
+    if (endItr != m_points.end()) ++endItr;
+    if (endItr != m_points.end()) ++endItr;
+
+    PointList rv;
+
+    for (PointListIterator i = startItr; i != endItr; ++i) {
+	rv.insert(*i);
+    }
+
+    return rv;
+}
+
+template <typename PointType>
+typename SparseModel<PointType>::PointList
+SparseModel<PointType>::getPoints(long frame) const
+{
+    QMutexLocker locker(&m_mutex);
+
+    if (m_resolution == 0) return PointList();
+
+    long start = (frame / m_resolution) * m_resolution;
+    long end = start + m_resolution;
+
+    PointType startPoint(start), endPoint(end);
+    
+    PointListIterator startItr = m_points.lower_bound(startPoint);
+    PointListIterator   endItr = m_points.upper_bound(endPoint);
+
+    PointList rv;
+
+    for (PointListIterator i = startItr; i != endItr; ++i) {
+	rv.insert(*i);
+    }
+
+    return rv;
+}
+
+template <typename PointType>
+typename SparseModel<PointType>::PointList
+SparseModel<PointType>::getPreviousPoints(long originFrame) const
+{
+    QMutexLocker locker(&m_mutex);
+
+    PointType lookupPoint(originFrame);
+    PointList rv;
+
+    PointListIterator i = m_points.lower_bound(lookupPoint);
+    if (i == m_points.begin()) return rv;
+
+    --i;
+    long frame = i->frame;
+    while (i->frame == frame) {
+	rv.insert(*i);
+	if (i == m_points.begin()) break;
+	--i;
+    }
+
+    return rv;
+}
+ 
+template <typename PointType>
+typename SparseModel<PointType>::PointList
+SparseModel<PointType>::getNextPoints(long originFrame) const
+{
+    QMutexLocker locker(&m_mutex);
+
+    PointType lookupPoint(originFrame);
+    PointList rv;
+
+    PointListIterator i = m_points.upper_bound(lookupPoint);
+    if (i == m_points.end()) return rv;
+
+    long frame = i->frame;
+    while (i != m_points.end() && i->frame == frame) {
+	rv.insert(*i);
+	++i;
+    }
+
+    return rv;
+}
+
+template <typename PointType>
+void
+SparseModel<PointType>::setResolution(size_t resolution)
+{
+    {
+	QMutexLocker locker(&m_mutex);
+	m_resolution = resolution;
+    }
+    emit modelChanged();
+}
+
+template <typename PointType>
+void
+SparseModel<PointType>::clear()
+{
+    {
+	QMutexLocker locker(&m_mutex);
+	m_points.clear();
+        m_pointCount = 0;
+    }
+    emit modelChanged();
+}
+
+template <typename PointType>
+void
+SparseModel<PointType>::addPoint(const PointType &point)
+{
+//    std::cout << "SparseModel<Point>::addPoint(" << point.frame << ", "
+//	      << point.value << ")" << std::endl;
+
+    {
+	QMutexLocker locker(&m_mutex);
+	m_points.insert(point);
+        m_pointCount++;
+        if (point.label != "") m_hasTextLabels = true;
+    }
+
+    // Even though this model is nominally sparse, there may still be
+    // too many signals going on here (especially as they'll probably
+    // be queued from one thread to another), which is why we need the
+    // notifyOnAdd as an option rather than a necessity (the
+    // alternative is to notify on setCompletion).
+
+    if (m_notifyOnAdd) {
+	emit modelChanged(point.frame, point.frame + m_resolution);
+    } else {
+	if (m_sinceLastNotifyMin == -1 ||
+	    point.frame < m_sinceLastNotifyMin) {
+	    m_sinceLastNotifyMin = point.frame;
+	}
+	if (m_sinceLastNotifyMax == -1 ||
+	    point.frame > m_sinceLastNotifyMax) {
+	    m_sinceLastNotifyMax = point.frame;
+	}
+    }
+}
+
+template <typename PointType>
+void
+SparseModel<PointType>::deletePoint(const PointType &point)
+{
+    {
+	QMutexLocker locker(&m_mutex);
+
+	PointListIterator i = m_points.lower_bound(point);
+	typename PointType::Comparator comparator;
+	while (i != m_points.end()) {
+	    if (i->frame > point.frame) break;
+	    if (!comparator(*i, point) && !comparator(point, *i)) {
+		m_points.erase(i);
+                m_pointCount--;
+		break;
+	    }
+	    ++i;
+	}
+    }
+//    std::cout << "SparseOneDimensionalModel: emit modelChanged("
+//	      << point.frame << ")" << std::endl;
+    emit modelChanged(point.frame, point.frame + m_resolution);
+}
+
+template <typename PointType>
+void
+SparseModel<PointType>::setCompletion(int completion)
+{
+    if (m_completion != completion) {
+	m_completion = completion;
+
+	if (completion == 100) {
+
+	    m_notifyOnAdd = true; // henceforth
+	    emit modelChanged();
+
+	} else if (!m_notifyOnAdd) {
+
+	    if (m_sinceLastNotifyMin >= 0 &&
+		m_sinceLastNotifyMax >= 0) {
+		emit modelChanged(m_sinceLastNotifyMin, m_sinceLastNotifyMax);
+		m_sinceLastNotifyMin = m_sinceLastNotifyMax = -1;
+	    } else {
+		emit completionChanged();
+	    }
+	} else {
+	    emit completionChanged();
+	}	    
+    }
+}
+
+template <typename PointType>
+void
+SparseModel<PointType>::toXml(QTextStream &out,
+                              QString indent,
+                              QString extraAttributes) const
+{
+    Model::toXml
+	(out,
+         indent,
+	 QString("type=\"sparse\" dimensions=\"%1\" resolution=\"%2\" notifyOnAdd=\"%3\" dataset=\"%4\" %5")
+	 .arg(PointType(0).getDimensions())
+	 .arg(m_resolution)
+	 .arg(m_notifyOnAdd ? "true" : "false")
+	 .arg(getObjectExportId(&m_points))
+	 .arg(extraAttributes));
+
+    out << indent;
+    out << QString("<dataset id=\"%1\" dimensions=\"%2\">\n")
+	.arg(getObjectExportId(&m_points))
+	.arg(PointType(0).getDimensions());
+
+    for (PointListIterator i = m_points.begin(); i != m_points.end(); ++i) {
+	out << i->toXmlString(indent + "  ");
+    }
+
+    out << indent;
+    out << "</dataset>\n";
+}
+
+template <typename PointType>
+QString
+SparseModel<PointType>::toXmlString(QString indent,
+				    QString extraAttributes) const
+{
+    QString s;
+
+    {
+        QTextStream out(&s);
+        toXml(out, indent, extraAttributes);
+    }
+
+    return s;
+}
+    
+template <typename PointType>
+SparseModel<PointType>::EditCommand::EditCommand(SparseModel *model,
+                                                 QString commandName) :
+    MacroCommand(commandName),
+    m_model(model)
+{
+}
+
+template <typename PointType>
+void
+SparseModel<PointType>::EditCommand::addPoint(const PointType &point)
+{
+    addCommand(new AddPointCommand(m_model, point), true);
+}
+
+template <typename PointType>
+void
+SparseModel<PointType>::EditCommand::deletePoint(const PointType &point)
+{
+    addCommand(new DeletePointCommand(m_model, point), true);
+}
+
+template <typename PointType>
+void
+SparseModel<PointType>::EditCommand::finish()
+{
+    if (!m_commands.empty()) {
+	CommandHistory::getInstance()->addCommand(this, false);
+    } else {
+        delete this;
+    }
+}
+
+template <typename PointType>
+void
+SparseModel<PointType>::EditCommand::addCommand(Command *command,
+						bool executeFirst)
+{
+    if (executeFirst) command->execute();
+
+    if (!m_commands.empty()) {
+	DeletePointCommand *dpc = dynamic_cast<DeletePointCommand *>(command);
+	if (dpc) {
+	    AddPointCommand *apc = dynamic_cast<AddPointCommand *>
+		(m_commands[m_commands.size() - 1]);
+	    typename PointType::Comparator comparator;
+	    if (apc) {
+		if (!comparator(apc->getPoint(), dpc->getPoint()) &&
+		    !comparator(dpc->getPoint(), apc->getPoint())) {
+		    deleteCommand(apc);
+		    return;
+		}
+	    }
+	}
+    }
+
+    MacroCommand::addCommand(command);
+}
+
+
+#endif
+
+
+    
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/data/model/SparseOneDimensionalModel.h	Mon Jul 31 11:44:37 2006 +0000
@@ -0,0 +1,91 @@
+/* -*- 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 Chris Cannam.
+    
+    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 _SPARSE_ONE_DIMENSIONAL_MODEL_H_
+#define _SPARSE_ONE_DIMENSIONAL_MODEL_H_
+
+#include "SparseModel.h"
+#include "PlayParameterRepository.h"
+#include "base/RealTime.h"
+
+struct OneDimensionalPoint
+{
+public:
+    OneDimensionalPoint(long _frame) : frame(_frame) { }
+    OneDimensionalPoint(long _frame, QString _label) : frame(_frame), label(_label) { }
+
+    int getDimensions() const { return 1; }
+    
+    long frame;
+    QString label;
+    
+    QString toXmlString(QString indent = "",
+			QString extraAttributes = "") const
+    {
+	return QString("%1<point frame=\"%2\" label=\"%3\" %4/>\n")
+	    .arg(indent).arg(frame).arg(label).arg(extraAttributes);
+    }
+
+    QString toDelimitedDataString(QString delimiter, size_t sampleRate) const
+    {
+        QStringList list;
+        list << RealTime::frame2RealTime(frame, sampleRate).toString().c_str();
+        list << label;
+        return list.join(delimiter);
+    }
+
+    struct Comparator {
+	bool operator()(const OneDimensionalPoint &p1,
+			const OneDimensionalPoint &p2) const {
+	    if (p1.frame != p2.frame) return p1.frame < p2.frame;
+	    return p1.label < p2.label;
+	}
+    };
+    
+    struct OrderComparator {
+	bool operator()(const OneDimensionalPoint &p1,
+			const OneDimensionalPoint &p2) const {
+	    return p1.frame < p2.frame;
+	}
+    };
+};
+
+
+class SparseOneDimensionalModel : public SparseModel<OneDimensionalPoint>
+{
+public:
+    SparseOneDimensionalModel(size_t sampleRate, size_t resolution,
+			      bool notifyOnAdd = true) :
+	SparseModel<OneDimensionalPoint>(sampleRate, resolution, notifyOnAdd)
+    {
+	PlayParameterRepository::getInstance()->addModel(this);
+    }
+
+    int getIndexOf(const Point &point) {
+	// slow
+	int i = 0;
+	Point::Comparator comparator;
+	for (PointList::const_iterator j = m_points.begin();
+	     j != m_points.end(); ++j, ++i) {
+	    if (!comparator(*j, point) && !comparator(point, *j)) return i;
+	}
+	return -1;
+    }
+};
+
+#endif
+
+
+    
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/data/model/SparseTimeValueModel.h	Mon Jul 31 11:44:37 2006 +0000
@@ -0,0 +1,94 @@
+/* -*- 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 Chris Cannam.
+    
+    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 _SPARSE_TIME_VALUE_MODEL_H_
+#define _SPARSE_TIME_VALUE_MODEL_H_
+
+#include "SparseValueModel.h"
+#include "PlayParameterRepository.h"
+#include "base/RealTime.h"
+
+/**
+ * Time/value point type for use in a SparseModel or SparseValueModel.
+ * With this point type, the model basically represents a wiggly-line
+ * plot with points at arbitrary intervals of the model resolution.
+ */
+
+struct TimeValuePoint
+{
+public:
+    TimeValuePoint(long _frame) : frame(_frame), value(0.0f) { }
+    TimeValuePoint(long _frame, float _value, QString _label) : 
+	frame(_frame), value(_value), label(_label) { }
+
+    int getDimensions() const { return 2; }
+    
+    long frame;
+    float value;
+    QString label;
+    
+    QString toXmlString(QString indent = "",
+			QString extraAttributes = "") const
+    {
+	return QString("%1<point frame=\"%2\" value=\"%3\" label=\"%4\" %5/>\n")
+	    .arg(indent).arg(frame).arg(value).arg(label).arg(extraAttributes);
+    }
+
+    QString toDelimitedDataString(QString delimiter, size_t sampleRate) const
+    {
+        QStringList list;
+        list << RealTime::frame2RealTime(frame, sampleRate).toString().c_str();
+        list << QString("%1").arg(value);
+        list << label;
+        return list.join(delimiter);
+    }
+
+    struct Comparator {
+	bool operator()(const TimeValuePoint &p1,
+			const TimeValuePoint &p2) const {
+	    if (p1.frame != p2.frame) return p1.frame < p2.frame;
+	    if (p1.value != p2.value) return p1.value < p2.value;
+	    return p1.label < p2.label;
+	}
+    };
+    
+    struct OrderComparator {
+	bool operator()(const TimeValuePoint &p1,
+			const TimeValuePoint &p2) const {
+	    return p1.frame < p2.frame;
+	}
+    };
+};
+
+
+class SparseTimeValueModel : public SparseValueModel<TimeValuePoint>
+{
+public:
+    SparseTimeValueModel(size_t sampleRate, size_t resolution,
+			 float valueMinimum, float valueMaximum,
+			 bool notifyOnAdd = true) :
+	SparseValueModel<TimeValuePoint>(sampleRate, resolution,
+					 valueMinimum, valueMaximum,
+					 notifyOnAdd)
+    {
+	PlayParameterRepository::getInstance()->addModel(this);
+    }
+};
+
+
+#endif
+
+
+    
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/data/model/SparseValueModel.h	Mon Jul 31 11:44:37 2006 +0000
@@ -0,0 +1,111 @@
+/* -*- 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 Chris Cannam.
+    
+    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 _SPARSE_VALUE_MODEL_H_
+#define _SPARSE_VALUE_MODEL_H_
+
+#include "SparseModel.h"
+#include "UnitDatabase.h"
+
+/**
+ * Model containing sparse data (points with some properties) of which
+ * one of the properties is an arbitrary float value.  The other
+ * properties depend on the point type.
+ */
+
+template <typename PointType>
+class SparseValueModel : public SparseModel<PointType>
+{
+public:
+    SparseValueModel(size_t sampleRate, size_t resolution,
+		     float valueMinimum, float valueMaximum,
+		     bool notifyOnAdd = true) :
+	SparseModel<PointType>(sampleRate, resolution, notifyOnAdd),
+	m_valueMinimum(valueMinimum),
+	m_valueMaximum(valueMaximum)
+    { }
+
+    using SparseModel<PointType>::m_points;
+    using SparseModel<PointType>::modelChanged;
+
+    virtual float getValueMinimum() const { return m_valueMinimum; }
+    virtual float getValueMaximum() const { return m_valueMaximum; }
+
+    virtual QString getScaleUnits() const { return m_units; }
+    virtual void setScaleUnits(QString units) {
+        m_units = units;
+        UnitDatabase::getInstance()->registerUnit(units);
+    }
+
+    virtual void addPoint(const PointType &point)
+    {
+	bool allChange = false;
+	if (m_points.empty() || point.value < m_valueMinimum) {
+	    m_valueMinimum = point.value; allChange = true;
+	}
+	if (m_points.empty() || point.value > m_valueMaximum) {
+	    m_valueMaximum = point.value; allChange = true;
+	}
+
+	SparseModel<PointType>::addPoint(point);
+	if (allChange) emit modelChanged();
+    }
+
+    virtual void deletePoint(const PointType &point)
+    {
+	SparseModel<PointType>::deletePoint(point);
+
+	if (point.value == m_valueMinimum ||
+	    point.value == m_valueMaximum) {
+
+	    float formerMin = m_valueMinimum, formerMax = m_valueMaximum;
+
+	    for (typename SparseModel<PointType>::PointList::const_iterator i
+		     = m_points.begin();
+		 i != m_points.end(); ++i) {
+
+		if (i == m_points.begin() || i->value < m_valueMinimum) {
+		    m_valueMinimum = i->value;
+		} 
+		if (i == m_points.begin() || i->value > m_valueMaximum) {
+		    m_valueMaximum = i->value;
+		} 
+	    }
+
+	    if (formerMin != m_valueMinimum || formerMax != m_valueMaximum) {
+		emit modelChanged();
+	    }
+	}
+    }
+
+    virtual QString toXmlString(QString indent = "",
+				QString extraAttributes = "") const
+    {
+	return SparseModel<PointType>::toXmlString
+	    (indent,
+	     QString("%1 minimum=\"%2\" maximum=\"%3\" units=\"%4\"")
+	     .arg(extraAttributes).arg(m_valueMinimum).arg(m_valueMaximum)
+             .arg(this->encodeEntities(m_units)));
+    }
+
+protected:
+    float m_valueMinimum;
+    float m_valueMaximum;
+    QString m_units;
+};
+
+
+#endif
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/data/model/TextModel.h	Mon Jul 31 11:44:37 2006 +0000
@@ -0,0 +1,98 @@
+/* -*- 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 Chris Cannam.
+    
+    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 _TEXT_MODEL_H_
+#define _TEXT_MODEL_H_
+
+#include "SparseModel.h"
+#include "base/RealTime.h"
+
+/**
+ * Text point type for use in a SparseModel.  This represents a piece
+ * of text at a given time and y-value in the [0,1) range (indicative
+ * of height on the window).  Intended for casual textual annotations.
+ */
+
+struct TextPoint
+{
+public:
+    TextPoint(long _frame) : frame(_frame), height(0.0f) { }
+    TextPoint(long _frame, float _height, QString _label) : 
+	frame(_frame), height(_height), label(_label) { }
+
+    int getDimensions() const { return 2; }
+    
+    long frame;
+    float height;
+    QString label;
+    
+    QString toXmlString(QString indent = "",
+			QString extraAttributes = "") const
+    {
+	return QString("%1<point frame=\"%2\" height=\"%3\" label=\"%4\" %5/>\n")
+	    .arg(indent).arg(frame).arg(height).arg(label).arg(extraAttributes);
+    }
+
+    QString toDelimitedDataString(QString delimiter, size_t sampleRate) const
+    {
+        QStringList list;
+        list << RealTime::frame2RealTime(frame, sampleRate).toString().c_str();
+        list << QString("%1").arg(height);
+        list << label;
+        return list.join(delimiter);
+    }
+
+    struct Comparator {
+	bool operator()(const TextPoint &p1,
+			const TextPoint &p2) const {
+	    if (p1.frame != p2.frame) return p1.frame < p2.frame;
+	    if (p1.height != p2.height) return p1.height < p2.height;
+	    return p1.label < p2.label;
+	}
+    };
+    
+    struct OrderComparator {
+	bool operator()(const TextPoint &p1,
+			const TextPoint &p2) const {
+	    return p1.frame < p2.frame;
+	}
+    };
+};
+
+
+// Make this a class rather than a typedef so it can be predeclared.
+
+class TextModel : public SparseModel<TextPoint>
+{
+public:
+    TextModel(size_t sampleRate, size_t resolution, bool notifyOnAdd = true) :
+	SparseModel<TextPoint>(sampleRate, resolution, notifyOnAdd)
+    { }
+
+    virtual QString toXmlString(QString indent = "",
+				QString extraAttributes = "") const
+    {
+	return SparseModel<TextPoint>::toXmlString
+	    (indent,
+	     QString("%1 subtype=\"text\"")
+	     .arg(extraAttributes));
+    }
+};
+
+
+#endif
+
+
+    
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/data/model/WaveFileModel.cpp	Mon Jul 31 11:44:37 2006 +0000
@@ -0,0 +1,497 @@
+/* -*- 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 Chris Cannam.
+    
+    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 "model/WaveFileModel.h"
+
+#include "fileio/AudioFileReader.h"
+#include "fileio/AudioFileReaderFactory.h"
+
+#include "base/System.h"
+
+#include <QMessageBox>
+#include <QFileInfo>
+
+#include <iostream>
+#include <unistd.h>
+#include <math.h>
+#include <sndfile.h>
+
+#include <cassert>
+
+using std::cerr;
+using std::endl;
+
+WaveFileModel::WaveFileModel(QString path) :
+    m_path(path),
+    m_fillThread(0),
+    m_updateTimer(0),
+    m_lastFillExtent(0),
+    m_exiting(false)
+{
+    m_reader = AudioFileReaderFactory::createReader(path);
+    setObjectName(QFileInfo(path).fileName());
+    if (isOK()) fillCache();
+}
+
+WaveFileModel::~WaveFileModel()
+{
+    m_exiting = true;
+    if (m_fillThread) m_fillThread->wait();
+    delete m_reader;
+    m_reader = 0;
+}
+
+bool
+WaveFileModel::isOK() const
+{
+    return m_reader && m_reader->isOK();
+}
+
+bool
+WaveFileModel::isReady(int *completion) const
+{
+    bool ready = (isOK() && (m_fillThread == 0));
+    double c = double(m_lastFillExtent) / double(getEndFrame() - getStartFrame());
+    if (completion) *completion = int(c * 100.0 + 0.01);
+    return ready;
+}
+
+Model *
+WaveFileModel::clone() const
+{
+    WaveFileModel *model = new WaveFileModel(m_path);
+    return model;
+}
+
+size_t
+WaveFileModel::getFrameCount() const
+{
+    if (!m_reader) return 0;
+    return m_reader->getFrameCount();
+}
+
+size_t
+WaveFileModel::getChannelCount() const
+{
+    if (!m_reader) return 0;
+    return m_reader->getChannelCount();
+}
+
+size_t
+WaveFileModel::getSampleRate() const 
+{
+    if (!m_reader) return 0;
+    return m_reader->getSampleRate();
+}
+
+size_t
+WaveFileModel::getValues(int channel, size_t start, size_t end,
+			 float *buffer) const
+{
+    // Always read these directly from the file. 
+    // This is used for e.g. audio playback.
+    // Could be much more efficient (although compiler optimisation will help)
+
+    if (end < start) {
+	std::cerr << "ERROR: WaveFileModel::getValues[float]: end < start ("
+		  << end << " < " << start << ")" << std::endl;
+	assert(end >= start);
+    }
+
+    if (!m_reader || !m_reader->isOK()) return 0;
+
+    SampleBlock frames;
+    m_reader->getInterleavedFrames(start, end - start, frames);
+
+    size_t i = 0;
+
+    int ch0 = channel, ch1 = channel, channels = getChannelCount();
+    if (channel == -1) {
+	ch0 = 0;
+	ch1 = channels - 1;
+    }
+    
+    while (i < end - start) {
+
+	buffer[i] = 0.0;
+
+	for (int ch = ch0; ch <= ch1; ++ch) {
+
+	    size_t index = i * channels + ch;
+	    if (index >= frames.size()) break;
+            
+	    float sample = frames[index];
+	    buffer[i] += sample;
+	}
+
+	++i;
+    }
+
+    return i;
+}
+
+size_t
+WaveFileModel::getValues(int channel, size_t start, size_t end,
+			 double *buffer) const
+{
+    if (end < start) {
+	std::cerr << "ERROR: WaveFileModel::getValues[double]: end < start ("
+		  << end << " < " << start << ")" << std::endl;
+	assert(end >= start);
+    }
+
+    if (!m_reader || !m_reader->isOK()) return 0;
+
+    SampleBlock frames;
+    m_reader->getInterleavedFrames(start, end - start, frames);
+
+    size_t i = 0;
+
+    int ch0 = channel, ch1 = channel, channels = getChannelCount();
+    if (channel == -1) {
+	ch0 = 0;
+	ch1 = channels - 1;
+    }
+
+    while (i < end - start) {
+
+	buffer[i] = 0.0;
+
+	for (int ch = ch0; ch <= ch1; ++ch) {
+
+	    size_t index = i * channels + ch;
+	    if (index >= frames.size()) break;
+            
+	    float sample = frames[index];
+	    buffer[i] += sample;
+	}
+
+	++i;
+    }
+
+    return i;
+}
+
+WaveFileModel::RangeBlock
+WaveFileModel::getRanges(size_t channel, size_t start, size_t end,
+			 size_t &blockSize) const
+{
+    RangeBlock ranges;
+    if (!isOK()) return ranges;
+
+    if (end <= start) {
+	std::cerr << "WARNING: Internal error: end <= start in WaveFileModel::getRanges (end = " << end << ", start = " << start << ", blocksize = " << blockSize << ")" << std::endl;
+	return ranges;
+    }
+
+    int cacheType = 0;
+    int power = getMinCachePower();
+    blockSize = getNearestBlockSize(blockSize, cacheType, power,
+				    ZoomConstraint::RoundUp);
+
+    size_t channels = getChannelCount();
+
+    if (cacheType != 0 && cacheType != 1) {
+
+	// We need to read directly from the file.  We haven't got
+	// this cached.  Hope the requested area is small.  This is
+	// not optimal -- we'll end up reading the same frames twice
+	// for stereo files, in two separate calls to this method.
+	// We could fairly trivially handle this for most cases that
+	// matter by putting a single cache in getInterleavedFrames
+	// for short queries.
+
+	SampleBlock frames;
+	m_reader->getInterleavedFrames(start, end - start, frames);
+	float max = 0.0, min = 0.0, total = 0.0;
+	size_t i = 0, count = 0;
+
+	while (i < end - start) {
+
+	    size_t index = i * channels + channel;
+	    if (index >= frames.size()) break;
+            
+	    float sample = frames[index];
+            if (sample > max || count == 0) max = sample;
+	    if (sample < min || count == 0) min = sample;
+            total += fabsf(sample);
+	    
+	    ++i;
+            ++count;
+            
+            if (count == blockSize) {
+                ranges.push_back(Range(min, max, total / count));
+                min = max = total = 0.0f;
+                count = 0;
+	    }
+	}
+
+	if (count > 0) {
+            ranges.push_back(Range(min, max, total / count));
+	}
+
+	return ranges;
+
+    } else {
+
+	QMutexLocker locker(&m_mutex);
+    
+	const RangeBlock &cache = m_cache[cacheType];
+
+	size_t cacheBlock, div;
+        
+	if (cacheType == 0) {
+	    cacheBlock = (1 << getMinCachePower());
+            div = (1 << power) / cacheBlock;
+	} else {
+	    cacheBlock = ((unsigned int)((1 << getMinCachePower()) * sqrt(2) + 0.01));
+            div = ((unsigned int)((1 << power) * sqrt(2) + 0.01)) / cacheBlock;
+	}
+
+	size_t startIndex = start / cacheBlock;
+	size_t endIndex = end / cacheBlock;
+
+	float max = 0.0, min = 0.0, total = 0.0;
+	size_t i = 0, count = 0;
+
+	//cerr << "blockSize is " << blockSize << ", cacheBlock " << cacheBlock << ", start " << start << ", end " << end << ", power is " << power << ", div is " << div << ", startIndex " << startIndex << ", endIndex " << endIndex << endl;
+
+	for (i = 0; i < endIndex - startIndex; ) {
+        
+	    size_t index = (i + startIndex) * channels + channel;
+	    if (index >= cache.size()) break;
+            
+            const Range &range = cache[index];
+            if (range.max > max || count == 0) max = range.max;
+            if (range.min < min || count == 0) min = range.min;
+            total += range.absmean;
+            
+	    ++i;
+            ++count;
+            
+	    if (count == div) {
+		ranges.push_back(Range(min, max, total / count));
+                min = max = total = 0.0f;
+                count = 0;
+	    }
+	}
+		
+	if (count > 0) {
+            ranges.push_back(Range(min, max, total / count));
+	}
+    }
+
+    //cerr << "returning " << ranges.size() << " ranges" << endl;
+    return ranges;
+}
+
+WaveFileModel::Range
+WaveFileModel::getRange(size_t channel, size_t start, size_t end) const
+{
+    Range range;
+    if (!isOK()) return range;
+
+    if (end <= start) {
+	std::cerr << "WARNING: Internal error: end <= start in WaveFileModel::getRange (end = " << end << ", start = " << start << ")" << std::endl;
+	return range;
+    }
+
+    size_t blockSize;
+    for (blockSize = 1; blockSize <= end - start; blockSize *= 2);
+    blockSize /= 2;
+
+    bool first = false;
+
+    size_t blockStart = (start / blockSize) * blockSize;
+    size_t blockEnd = (end / blockSize) * blockSize;
+
+    if (blockStart < start) blockStart += blockSize;
+        
+    if (blockEnd > blockStart) {
+        RangeBlock ranges = getRanges(channel, blockStart, blockEnd, blockSize);
+        for (size_t i = 0; i < ranges.size(); ++i) {
+            if (first || ranges[i].min < range.min) range.min = ranges[i].min;
+            if (first || ranges[i].max > range.max) range.max = ranges[i].max;
+            if (first || ranges[i].absmean < range.absmean) range.absmean = ranges[i].absmean;
+            first = false;
+        }
+    }
+
+    if (blockStart > start) {
+        Range startRange = getRange(channel, start, blockStart);
+        range.min = std::min(range.min, startRange.min);
+        range.max = std::max(range.max, startRange.max);
+        range.absmean = std::min(range.absmean, startRange.absmean);
+    }
+
+    if (blockEnd < end) {
+        Range endRange = getRange(channel, blockEnd, end);
+        range.min = std::min(range.min, endRange.min);
+        range.max = std::max(range.max, endRange.max);
+        range.absmean = std::min(range.absmean, endRange.absmean);
+    }
+
+    return range;
+}
+
+void
+WaveFileModel::fillCache()
+{
+    m_mutex.lock();
+    m_updateTimer = new QTimer(this);
+    connect(m_updateTimer, SIGNAL(timeout()), this, SLOT(fillTimerTimedOut()));
+    m_updateTimer->start(100);
+    m_fillThread = new RangeCacheFillThread(*this);
+    connect(m_fillThread, SIGNAL(finished()), this, SLOT(cacheFilled()));
+    m_mutex.unlock();
+    m_fillThread->start();
+}   
+
+void
+WaveFileModel::fillTimerTimedOut()
+{
+    if (m_fillThread) {
+	size_t fillExtent = m_fillThread->getFillExtent();
+	if (fillExtent > m_lastFillExtent) {
+	    emit modelChanged(m_lastFillExtent, fillExtent);
+	    m_lastFillExtent = fillExtent;
+	}
+    } else {
+	emit modelChanged();
+    }
+}
+
+void
+WaveFileModel::cacheFilled()
+{
+    m_mutex.lock();
+    delete m_fillThread;
+    m_fillThread = 0;
+    delete m_updateTimer;
+    m_updateTimer = 0;
+    m_mutex.unlock();
+    emit modelChanged();
+//    cerr << "WaveFileModel::cacheFilled" << endl;
+}
+
+void
+WaveFileModel::RangeCacheFillThread::run()
+{
+    size_t cacheBlockSize[2];
+    cacheBlockSize[0] = (1 << m_model.getMinCachePower());
+    cacheBlockSize[1] = ((unsigned int)((1 << m_model.getMinCachePower()) *
+                                        sqrt(2) + 0.01));
+    
+    size_t frame = 0;
+    size_t readBlockSize = 16384;
+    SampleBlock block;
+
+    if (!m_model.isOK()) return;
+    
+    size_t channels = m_model.getChannelCount();
+    size_t frames = m_model.getFrameCount();
+
+    Range *range = new Range[2 * channels];
+    size_t count[2];
+    count[0] = count[1] = 0;
+    
+    while (frame < frames) {
+
+	m_model.m_reader->getInterleavedFrames(frame, readBlockSize, block);
+
+        for (size_t i = 0; i < readBlockSize; ++i) {
+		
+	    for (size_t ch = 0; ch < size_t(channels); ++ch) {
+
+                size_t index = channels * i + ch;
+		if (index >= block.size()) continue;
+                float sample = block[index];
+                
+                for (size_t ct = 0; ct < 2; ++ct) {
+                
+                    size_t rangeIndex = ch * 2 + ct;
+                    
+                    if (sample > range[rangeIndex].max || count[ct] == 0) {
+                        range[rangeIndex].max = sample;
+                    }
+                    if (sample < range[rangeIndex].min || count[ct] == 0) {
+                        range[rangeIndex].min = sample;
+                    }
+                    range[rangeIndex].absmean += fabsf(sample);
+                }
+	    }
+            
+	    QMutexLocker locker(&m_model.m_mutex);
+            for (size_t ct = 0; ct < 2; ++ct) {
+                if (++count[ct] == cacheBlockSize[ct]) {
+                    for (size_t ch = 0; ch < size_t(channels); ++ch) {
+                        size_t rangeIndex = ch * 2 + ct;
+                        range[rangeIndex].absmean /= count[ct];
+                        m_model.m_cache[ct].push_back(range[rangeIndex]);
+                        range[rangeIndex] = Range();
+                    }
+                    count[ct] = 0;
+                }
+            }
+            
+            ++frame;
+        }
+
+	if (m_model.m_exiting) break;
+
+	m_fillExtent = frame;
+    }
+
+    QMutexLocker locker(&m_model.m_mutex);
+    for (size_t ct = 0; ct < 2; ++ct) {
+        if (count[ct] > 0) {
+            for (size_t ch = 0; ch < size_t(channels); ++ch) {
+                size_t rangeIndex = ch * 2 + ct;
+                range[rangeIndex].absmean /= count[ct];
+                m_model.m_cache[ct].push_back(range[rangeIndex]);
+                range[rangeIndex] = Range();
+            }
+            count[ct] = 0;
+        }
+
+	const Range &rr = *m_model.m_cache[ct].begin();
+	MUNLOCK(&rr, m_model.m_cache[ct].capacity() * sizeof(Range));
+    }
+    
+    delete[] range;
+
+    m_fillExtent = frames;
+        
+//    for (size_t ct = 0; ct < 2; ++ct) {
+//        cerr << "Cache type " << ct << " now contains " << m_model.m_cache[ct].size() << " ranges" << endl;
+//    }
+}
+
+QString
+WaveFileModel::toXmlString(QString indent,
+			   QString extraAttributes) const
+{
+    return Model::toXmlString(indent,
+			      QString("type=\"wavefile\" file=\"%1\" %2")
+			      .arg(m_path).arg(extraAttributes));
+}
+    
+
+#ifdef INCLUDE_MOCFILES
+#ifdef INCLUDE_MOCFILES
+#include "WaveFileModel.moc.cpp"
+#endif
+#endif
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/data/model/WaveFileModel.h	Mon Jul 31 11:44:37 2006 +0000
@@ -0,0 +1,102 @@
+/* -*- 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 Chris Cannam.
+    
+    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 _WAVE_FILE_MODEL_H_
+#define _WAVE_FILE_MODEL_H_
+
+#include "Thread.h"
+#include <QMutex>
+#include <QTimer>
+
+#include "RangeSummarisableTimeValueModel.h"
+#include "PowerOfSqrtTwoZoomConstraint.h"
+
+#include <stdlib.h>
+
+class AudioFileReader;
+
+class WaveFileModel : public RangeSummarisableTimeValueModel,
+		      virtual public PowerOfSqrtTwoZoomConstraint
+{
+    Q_OBJECT
+
+public:
+    WaveFileModel(QString path);
+    ~WaveFileModel();
+
+    bool isOK() const;
+    bool isReady(int *) const;
+
+    size_t getFrameCount() const;
+    size_t getChannelCount() const;
+    size_t getSampleRate() const;
+
+    virtual Model *clone() const;
+
+    float getValueMinimum() const { return -1.0f; }
+    float getValueMaximum() const { return  1.0f; }
+
+    virtual size_t getStartFrame() const { return 0; }
+    virtual size_t getEndFrame() const { return getFrameCount(); }
+
+    virtual size_t getValues(int channel, size_t start, size_t end,
+			     float *buffer) const;
+
+    virtual size_t getValues(int channel, size_t start, size_t end,
+			     double *buffer) const;
+
+    virtual RangeBlock getRanges(size_t channel, size_t start, size_t end,
+				 size_t &blockSize) const;
+
+    virtual Range getRange(size_t channel, size_t start, size_t end) const;
+
+    virtual QString toXmlString(QString indent = "",
+				QString extraAttributes = "") const;
+
+protected slots:
+    void fillTimerTimedOut();
+    void cacheFilled();
+    
+protected:
+    void initialize();
+
+    class RangeCacheFillThread : public Thread
+    {
+    public:
+        RangeCacheFillThread(WaveFileModel &model) :
+	    m_model(model), m_fillExtent(0) { }
+    
+	size_t getFillExtent() const { return m_fillExtent; }
+        virtual void run();
+
+    protected:
+        WaveFileModel &m_model;
+	size_t m_fillExtent;
+    };
+         
+    void fillCache();
+    
+    QString m_path;
+    AudioFileReader *m_reader;
+
+    RangeBlock m_cache[2]; // interleaved at two base resolutions
+    mutable QMutex m_mutex;
+    RangeCacheFillThread *m_fillThread;
+    QTimer *m_updateTimer;
+    size_t m_lastFillExtent;
+    bool m_exiting;
+};    
+
+#endif