changeset 1469:11a150e65ee1 by-id

Some work on updating layers for ModelId bits
author Chris Cannam
date Thu, 27 Jun 2019 13:16:25 +0100
parents de41a11cabc2
children 696e569ff21b
files layer/Colour3DPlotLayer.cpp layer/Colour3DPlotLayer.h layer/Colour3DPlotRenderer.cpp layer/Colour3DPlotRenderer.h layer/FlexiNoteLayer.cpp layer/FlexiNoteLayer.h layer/ImageLayer.cpp layer/ImageLayer.h layer/Layer.cpp layer/Layer.h layer/SliceableLayer.h
diffstat 11 files changed, 492 insertions(+), 368 deletions(-) [+]
line wrap: on
line diff
--- a/layer/Colour3DPlotLayer.cpp	Thu Jun 13 15:35:01 2019 +0100
+++ b/layer/Colour3DPlotLayer.cpp	Thu Jun 27 13:16:25 2019 +0100
@@ -43,7 +43,6 @@
 
 
 Colour3DPlotLayer::Colour3DPlotLayer() :
-    m_model(nullptr),
     m_colourScale(ColourScaleType::Linear),
     m_colourScaleSet(false),
     m_colourMap(0),
@@ -59,20 +58,26 @@
     m_miny(0),
     m_maxy(0),
     m_synchronous(false),
-    m_peakCache(nullptr),
     m_peakCacheDivisor(8)
 {
     QSettings settings;
     settings.beginGroup("Preferences");
-    setColourMap(settings.value("colour-3d-plot-colour", ColourMapper::Green).toInt());
+    setColourMap(settings.value("colour-3d-plot-colour",
+                                ColourMapper::Green).toInt());
     settings.endGroup();
 }
 
 Colour3DPlotLayer::~Colour3DPlotLayer()
 {
     invalidateRenderers();
-    if (m_peakCache) m_peakCache->aboutToDelete();
-    delete m_peakCache;
+}
+
+const ZoomConstraint *
+Colour3DPlotLayer::getZoomConstraint() const 
+{
+    auto model = ModelById::get(m_model);
+    if (model) return model->getZoomConstraint();
+    else return nullptr;
 }
 
 ColourScaleType
@@ -136,20 +141,23 @@
 }
 
 void
-Colour3DPlotLayer::setModel(const DenseThreeDimensionalModel *model)
+Colour3DPlotLayer::setModel(ModelId modelId)
 {
-    SVDEBUG << "Colour3DPlotLayer::setModel(" << model << ")" << endl;
+    SVDEBUG << "Colour3DPlotLayer::setModel(" << modelId << ")" << endl;
+
+    if (m_model == modelId) return;
     
-    if (m_model == model) return;
-    const DenseThreeDimensionalModel *oldModel = m_model;
-    m_model = model;
-    if (!m_model || !m_model->isOK()) return;
+    auto model = ModelById::getAs<DenseThreeDimensionalModel>(modelId);
+    if (!model) throw std::logic_error("Not a DenseThreeDimensionalModel");
+    
+//!!!    const DenseThreeDimensionalModel *oldModel = m_model;
+    m_model = modelId;
 
     connectSignals(m_model);
 
-    connect(m_model, SIGNAL(modelChanged()),
+    connect(model.get(), SIGNAL(modelChanged()),
             this, SLOT(handleModelChanged()));
-    connect(m_model, SIGNAL(modelChangedWithin(sv_frame_t, sv_frame_t)),
+    connect(model.get(), SIGNAL(modelChangedWithin(sv_frame_t, sv_frame_t)),
             this, SLOT(handleModelChangedWithin(sv_frame_t, sv_frame_t)));
 
     m_peakResolution = 256;
@@ -164,7 +172,7 @@
     invalidatePeakCache();
 
     emit modelReplaced();
-    emit sliceableModelReplaced(oldModel, model);
+//!!!    emit sliceableModelReplaced(oldModel, model);
 }
 
 void
@@ -173,8 +181,7 @@
     // renderers use the peak cache, so we must invalidate those too
     invalidateRenderers();
     invalidateMagnitudes();
-    
-    if (m_peakCache) m_peakCache->aboutToDelete();
+
     delete m_peakCache;
     m_peakCache = nullptr;
 }
@@ -211,8 +218,9 @@
 Colour3DPlotLayer::handleModelChanged()
 {
     if (!m_colourScaleSet && m_colourScale == ColourScaleType::Linear) {
-        if (m_model) {
-            if (m_model->shouldUseLogValueScale()) {
+        auto model = ModelById::getAs<DenseThreeDimensionalModel>(m_model);
+        if (model) {
+            if (model->shouldUseLogValueScale()) {
                 setColourScale(ColourScaleType::Log);
             } else {
                 m_colourScaleSet = true;
@@ -227,8 +235,9 @@
 Colour3DPlotLayer::handleModelChangedWithin(sv_frame_t startFrame, sv_frame_t endFrame)
 {
     if (!m_colourScaleSet && m_colourScale == ColourScaleType::Linear) {
-        if (m_model && m_model->getWidth() > 50) {
-            if (m_model->shouldUseLogValueScale()) {
+        auto model = ModelById::getAs<DenseThreeDimensionalModel>(m_model);
+        if (model && model->getWidth() > 50) {
+            if (model->shouldUseLogValueScale()) {
                 setColourScale(ColourScaleType::Log);
             } else {
                 m_colourScaleSet = true;
@@ -649,14 +658,23 @@
     return false;
 }
 
+int
+Colour3DPlotLayer::getCompletion(LayerGeometryProvider *) const
+{
+    auto model = ModelById::get(m_model);
+    if (model) return model->getCompletion();
+    else return 0;
+}
+
 bool
 Colour3DPlotLayer::getValueExtents(double &min, double &max,
                                    bool &logarithmic, QString &unit) const
 {
-    if (!m_model) return false;
+    auto model = ModelById::getAs<DenseThreeDimensionalModel>(m_model);
+    if (!model) return false;
 
     min = 0;
-    max = double(m_model->getHeight());
+    max = double(model->getHeight());
 
     logarithmic = (m_binScale == BinScale::Log);
     unit = "";
@@ -667,9 +685,10 @@
 bool
 Colour3DPlotLayer::getDisplayExtents(double &min, double &max) const
 {
-    if (!m_model) return false;
+    auto model = ModelById::getAs<DenseThreeDimensionalModel>(m_model);
+    if (!model) return false;
 
-    double hmax = double(m_model->getHeight());
+    double hmax = double(model->getHeight());
     
     min = m_miny;
     max = m_maxy;
@@ -686,8 +705,6 @@
 bool
 Colour3DPlotLayer::setDisplayExtents(double min, double max)
 {
-    if (!m_model) return false;
-
     m_miny = int(lrint(min));
     m_maxy = int(lrint(max));
     
@@ -707,37 +724,40 @@
 int
 Colour3DPlotLayer::getVerticalZoomSteps(int &defaultStep) const
 {
-    if (!m_model) return 0;
+    auto model = ModelById::getAs<DenseThreeDimensionalModel>(m_model);
+    if (!model) return 0;
 
     defaultStep = 0;
-    int h = m_model->getHeight();
+    int h = model->getHeight();
     return h;
 }
 
 int
 Colour3DPlotLayer::getCurrentVerticalZoomStep() const
 {
-    if (!m_model) return 0;
+    auto model = ModelById::getAs<DenseThreeDimensionalModel>(m_model);
+    if (!model) return 0;
 
     double min, max;
     getDisplayExtents(min, max);
-    return m_model->getHeight() - int(lrint(max - min));
+    return model->getHeight() - int(lrint(max - min));
 }
 
 void
 Colour3DPlotLayer::setVerticalZoomStep(int step)
 {
-    if (!m_model) return;
+    auto model = ModelById::getAs<DenseThreeDimensionalModel>(m_model);
+    if (!model) return;
 
 //    SVDEBUG << "Colour3DPlotLayer::setVerticalZoomStep(" <<step <<"): before: miny = " << m_miny << ", maxy = " << m_maxy << endl;
 
-    int dist = m_model->getHeight() - step;
+    int dist = model->getHeight() - step;
     if (dist < 1) dist = 1;
     double centre = m_miny + (m_maxy - m_miny) / 2.0;
     m_miny = int(lrint(centre - dist/2.0));
     if (m_miny < 0) m_miny = 0;
     m_maxy = m_miny + dist;
-    if (m_maxy > m_model->getHeight()) m_maxy = m_model->getHeight();
+    if (m_maxy > model->getHeight()) m_maxy = model->getHeight();
 
     invalidateRenderers();
     
@@ -749,18 +769,23 @@
 RangeMapper *
 Colour3DPlotLayer::getNewVerticalZoomRangeMapper() const
 {
-    if (!m_model) return nullptr;
+    //!!! most of our uses of model in these functions is just to
+    //!!! retrieve the model's height - perhaps we should cache it
+    
+    auto model = ModelById::getAs<DenseThreeDimensionalModel>(m_model);
+    if (!model) return nullptr;
 
-    return new LinearRangeMapper(0, m_model->getHeight(),
-                                 0, m_model->getHeight(), "");
+    return new LinearRangeMapper(0, model->getHeight(),
+                                 0, model->getHeight(), "");
 }
 
 double
 Colour3DPlotLayer::getYForBin(const LayerGeometryProvider *v, double bin) const
 {
     double y = bin;
-    if (!m_model) return y;
-    double mn = 0, mx = m_model->getHeight();
+    auto model = ModelById::getAs<DenseThreeDimensionalModel>(m_model);
+    if (!model) return y;
+    double mn = 0, mx = model->getHeight();
     getDisplayExtents(mn, mx);
     double h = v->getPaintHeight();
     if (m_binScale == BinScale::Linear) {
@@ -777,8 +802,9 @@
 Colour3DPlotLayer::getBinForY(const LayerGeometryProvider *v, double y) const
 {
     double bin = y;
-    if (!m_model) return bin;
-    double mn = 0, mx = m_model->getHeight();
+    auto model = ModelById::getAs<DenseThreeDimensionalModel>(m_model);
+    if (!model) return bin;
+    double mn = 0, mx = model->getHeight();
     getDisplayExtents(mn, mx);
     double h = v->getPaintHeight();
     if (m_binScale == BinScale::Linear) {
@@ -798,17 +824,18 @@
 QString
 Colour3DPlotLayer::getFeatureDescription(LayerGeometryProvider *v, QPoint &pos) const
 {
-    if (!m_model) return "";
+    auto model = ModelById::getAs<DenseThreeDimensionalModel>(m_model);
+    if (!model) return "";
 
     int x = pos.x();
     int y = pos.y();
 
-    sv_frame_t modelStart = m_model->getStartFrame();
-    int modelResolution = m_model->getResolution();
+    sv_frame_t modelStart = model->getStartFrame();
+    int modelResolution = model->getResolution();
 
     double srRatio =
         v->getViewManager()->getMainModelSampleRate() /
-        m_model->getSampleRate();
+        model->getSampleRate();
 
     int sx0 = int((double(v->getFrameForX(x)) / srRatio - double(modelStart)) /
                   modelResolution);
@@ -816,7 +843,7 @@
     int f0 = sx0 * modelResolution;
     int f1 =  f0 + modelResolution;
 
-    int sh = m_model->getHeight();
+    int sh = model->getHeight();
 
     int symin = m_miny;
     int symax = m_maxy;
@@ -832,26 +859,26 @@
 
     int sy = getIBinForY(v, y);
 
-    if (sy < 0 || sy >= m_model->getHeight()) {
+    if (sy < 0 || sy >= model->getHeight()) {
         return "";
     }
 
     if (m_invertVertical) {
-        sy = m_model->getHeight() - sy - 1;
+        sy = model->getHeight() - sy - 1;
     }
 
-    float value = m_model->getValueAt(sx0, sy);
+    float value = model->getValueAt(sx0, sy);
 
 //    cerr << "bin value (" << sx0 << "," << sy << ") is " << value << endl;
     
-    QString binName = m_model->getBinName(sy);
+    QString binName = model->getBinName(sy);
     if (binName == "") binName = QString("[%1]").arg(sy + 1);
     else binName = QString("%1 [%2]").arg(binName).arg(sy + 1);
 
     QString text = tr("Time:\t%1 - %2\nBin:\t%3\nValue:\t%4")
-        .arg(RealTime::frame2RealTime(f0, m_model->getSampleRate())
+        .arg(RealTime::frame2RealTime(f0, model->getSampleRate())
              .toText(true).c_str())
-        .arg(RealTime::frame2RealTime(f1, m_model->getSampleRate())
+        .arg(RealTime::frame2RealTime(f1, model->getSampleRate())
              .toText(true).c_str())
         .arg(binName)
         .arg(value);
@@ -870,15 +897,16 @@
 int
 Colour3DPlotLayer::getVerticalScaleWidth(LayerGeometryProvider *, bool, QPainter &paint) const
 {
-    if (!m_model) return 0;
+    auto model = ModelById::getAs<DenseThreeDimensionalModel>(m_model);
+    if (!model) return 0;
 
-    QString sampleText = QString("[%1]").arg(m_model->getHeight());
+    QString sampleText = QString("[%1]").arg(model->getHeight());
     int tw = paint.fontMetrics().width(sampleText);
     bool another = false;
 
-    for (int i = 0; i < m_model->getHeight(); ++i) {
-        if (m_model->getBinName(i).length() > sampleText.length()) {
-            sampleText = m_model->getBinName(i);
+    for (int i = 0; i < model->getHeight(); ++i) {
+        if (model->getBinName(i).length() > sampleText.length()) {
+            sampleText = model->getBinName(i);
             another = true;
         }
     }
@@ -892,7 +920,8 @@
 void
 Colour3DPlotLayer::paintVerticalScale(LayerGeometryProvider *v, bool, QPainter &paint, QRect rect) const
 {
-    if (!m_model) return;
+    auto model = ModelById::getAs<DenseThreeDimensionalModel>(m_model);
+    if (!model) return;
 
     int h = rect.height(), w = rect.width();
 
@@ -948,7 +977,7 @@
 
     paint.setPen(v->getForeground());
 
-    int sh = m_model->getHeight();
+    int sh = model->getHeight();
 
     int symin = m_miny;
     int symax = m_maxy;
@@ -994,10 +1023,10 @@
 
             int idx = i - 1;
             if (m_invertVertical) {
-                idx = m_model->getHeight() - idx - 1;
+                idx = model->getHeight() - idx - 1;
             }
 
-            QString text = m_model->getBinName(idx);
+            QString text = model->getBinName(idx);
             if (text == "") text = QString("[%1]").arg(idx + 1);
 
             int ty = y0 + (h/2) - (paint.fontMetrics().height()/2) +
@@ -1013,13 +1042,15 @@
 Colour3DPlotRenderer *
 Colour3DPlotLayer::getRenderer(const LayerGeometryProvider *v) const
 {
+    auto model = ModelById::getAs<DenseThreeDimensionalModel>(m_model);
+    if (!model) return nullptr;
+    
     int viewId = v->getId();
     
     if (m_renderers.find(viewId) == m_renderers.end()) {
 
         Colour3DPlotRenderer::Sources sources;
         sources.verticalBinLayer = this;
-        sources.fft = nullptr;
         sources.source = m_model;
         sources.peakCaches.push_back(getPeakCache());
 
@@ -1037,16 +1068,16 @@
             maxValue = m_viewMags[viewId].getMax();
         } else if (m_normalization == ColumnNormalization::Hybrid) {
             minValue = 0;
-            maxValue = log10(m_model->getMaximumLevel() + 1.0);
+            maxValue = log10(model->getMaximumLevel() + 1.0);
         } else if (m_normalization == ColumnNormalization::None) {
-            minValue = m_model->getMinimumLevel();
-            maxValue = m_model->getMaximumLevel();
+            minValue = model->getMinimumLevel();
+            maxValue = model->getMaximumLevel();
         }
 
         SVDEBUG << "Colour3DPlotLayer: rebuilding renderer, value range is "
                 << minValue << " -> " << maxValue
-                << " (model min = " << m_model->getMinimumLevel()
-                << ", max = " << m_model->getMaximumLevel() << ")"
+                << " (model min = " << model->getMinimumLevel()
+                << ", max = " << model->getMaximumLevel() << ")"
                 << endl;
 
         if (maxValue <= minValue) {
@@ -1139,9 +1170,10 @@
 void
 Colour3DPlotLayer::paint(LayerGeometryProvider *v, QPainter &paint, QRect rect) const
 {
+    auto model = ModelById::getAs<DenseThreeDimensionalModel>(m_model);
 /*
-    if (m_model) {
-        SVDEBUG << "Colour3DPlotLayer::paint: model says shouldUseLogValueScale = " << m_model->shouldUseLogValueScale() << endl;
+    if (model) {
+        SVDEBUG << "Colour3DPlotLayer::paint: model says shouldUseLogValueScale = " << model->shouldUseLogValueScale() << endl;
     }
 */
     Profiler profiler("Colour3DPlotLayer::paint");
@@ -1150,7 +1182,7 @@
 #endif
 
     int completion = 0;
-    if (!m_model || !m_model->isOK() || !m_model->isReady(&completion)) {
+    if (!model || !model->isOK() || !model->isReady(&completion)) {
         if (completion > 0) {
             paint.fillRect(0, 10, v->getPaintWidth() * completion / 100,
                            10, QColor(120, 120, 120));
@@ -1158,7 +1190,7 @@
         return;
     }
 
-    if (m_model->getWidth() == 0) {
+    if (model->getWidth() == 0) {
 #ifdef DEBUG_COLOUR_3D_PLOT_LAYER_PAINT
         SVDEBUG << "Colour3DPlotLayer::paint(): model width == 0, "
              << "nothing to paint (yet)" << endl;
@@ -1174,11 +1206,12 @@
                                       int &resolution,
                                       SnapType snap) const
 {
-    if (!m_model) {
+    auto model = ModelById::getAs<DenseThreeDimensionalModel>(m_model);
+    if (!model) {
         return Layer::snapToFeatureFrame(v, frame, resolution, snap);
     }
 
-    resolution = m_model->getResolution();
+    resolution = model->getResolution();
     sv_frame_t left = (frame / resolution) * resolution;
     sv_frame_t right = left + resolution;
 
--- a/layer/Colour3DPlotLayer.h	Thu Jun 13 15:35:01 2019 +0100
+++ b/layer/Colour3DPlotLayer.h	Thu Jun 27 13:16:25 2019 +0100
@@ -46,23 +46,28 @@
     Colour3DPlotLayer();
     ~Colour3DPlotLayer();
 
-    const ZoomConstraint *getZoomConstraint() const override {
-        return m_model ? m_model->getZoomConstraint() : 0;
-    }
-    const Model *getModel() const override { return m_model; }
-    void paint(LayerGeometryProvider *v, QPainter &paint, QRect rect) const override;
+    ModelId getModel() const override { return m_model; }
+
+    const ZoomConstraint *getZoomConstraint() const override;
+    
+    void paint(LayerGeometryProvider *v,
+               QPainter &paint, QRect rect) const override;
     void setSynchronousPainting(bool synchronous) override;
 
-    int getVerticalScaleWidth(LayerGeometryProvider *v, bool, QPainter &) const override;
-    void paintVerticalScale(LayerGeometryProvider *v, bool, QPainter &paint, QRect rect) const override;
+    int getVerticalScaleWidth(LayerGeometryProvider *v,
+                              bool, QPainter &) const override;
+    void paintVerticalScale(LayerGeometryProvider *v,
+                            bool, QPainter &paint, QRect rect) const override;
 
-    QString getFeatureDescription(LayerGeometryProvider *v, QPoint &) const override;
+    QString getFeatureDescription(LayerGeometryProvider *v,
+                                  QPoint &) const override;
 
     bool snapToFeatureFrame(LayerGeometryProvider *v, sv_frame_t &frame, 
-                                    int &resolution,
-                                    SnapType snap) const override;
+                            int &resolution,
+                            SnapType snap) const override;
 
-    void setLayerDormant(const LayerGeometryProvider *v, bool dormant) override;
+    void setLayerDormant(const LayerGeometryProvider *v,
+                         bool dormant) override;
 
     bool isLayerScrollable(const LayerGeometryProvider *v) const override;
 
@@ -70,9 +75,9 @@
         return ColourHasMeaningfulValue;
     }
 
-    void setModel(const DenseThreeDimensionalModel *model);
+    void setModel(ModelId model); // a DenseThreeDimensionalModel
 
-    int getCompletion(LayerGeometryProvider *) const override { return m_model->getCompletion(); }
+    int getCompletion(LayerGeometryProvider *) const override;
 
     PropertyList getProperties() const override;
     PropertyType getPropertyType(const PropertyName &) const override;
@@ -80,11 +85,11 @@
     QString getPropertyIconName(const PropertyName &) const override;
     QString getPropertyGroupName(const PropertyName &) const override;
     int getPropertyRangeAndValue(const PropertyName &,
-                                         int *min, int *max, int *deflt) const override;
+                                 int *min, int *max, int *deflt) const override;
     QString getPropertyValueLabel(const PropertyName &,
-                                          int value) const override;
+                                  int value) const override;
     QString getPropertyValueIconName(const PropertyName &,
-                                             int value) const override;
+                                     int value) const override;
     RangeMapper *getNewPropertyRangeMapper(const PropertyName &) const override;
     void setProperty(const PropertyName &, int value) override;
     void setProperties(const QXmlAttributes &) override;
@@ -132,30 +137,30 @@
     bool hasLightBackground() const override;
 
     bool getValueExtents(double &min, double &max,
-                                 bool &logarithmic, QString &unit) const override;
+                         bool &logarithmic, QString &unit) const override;
 
     bool getDisplayExtents(double &min, double &max) const override;
     bool setDisplayExtents(double min, double max) override;
 
     bool getYScaleValue(const LayerGeometryProvider *, int /* y */,
-                                double &/* value */, QString &/* unit */) const override;
+                        double &/* value */, QString &/* unit */) const override;
 
     int getVerticalZoomSteps(int &defaultStep) const override;
     int getCurrentVerticalZoomStep() const override;
     void setVerticalZoomStep(int) override;
     RangeMapper *getNewVerticalZoomRangeMapper() const override;
 
-    const Model *getSliceableModel() const override { return m_model; }
+    ModelId getSliceableModel() const override { return m_model; }
 
     void toXml(QTextStream &stream, QString indent = "",
-                       QString extraAttributes = "") const override;
+               QString extraAttributes = "") const override;
 
 protected slots:
     void handleModelChanged();
     void handleModelChangedWithin(sv_frame_t, sv_frame_t);
 
 protected:
-    const DenseThreeDimensionalModel *m_model; // I do not own this
+    ModelId m_model; // A DenseThreeDimensionalModel
     
     ColourScaleType m_colourScale;
     bool m_colourScaleSet;
@@ -184,8 +189,8 @@
 
     mutable Dense3DModelPeakCache *m_peakCache;
     const int m_peakCacheDivisor;
+    void invalidatePeakCache();
     Dense3DModelPeakCache *getPeakCache() const;
-    void invalidatePeakCache();
 
     typedef std::map<int, MagnitudeRange> ViewMagMap; // key is view id
     mutable ViewMagMap m_viewMags;
--- a/layer/Colour3DPlotRenderer.cpp	Thu Jun 13 15:35:01 2019 +0100
+++ b/layer/Colour3DPlotRenderer.cpp	Thu Jun 27 13:16:25 2019 +0100
@@ -334,7 +334,7 @@
 Colour3DPlotRenderer::RenderType
 Colour3DPlotRenderer::decideRenderType(const LayerGeometryProvider *v) const
 {
-    const DenseThreeDimensionalModel *model = m_sources.source;
+    auto model = ModelById::getAs<DenseThreeDimensionalModel>(m_sources.source);
     if (!model || !v || !(v->getViewManager())) {
         return DrawBufferPixelResolution; // or anything
     }
@@ -398,7 +398,7 @@
     }
 
     if (m_params.colourScale.getScale() == ColourScaleType::Phase &&
-        m_sources.fft) {
+        !m_sources.fft.isNone()) {
         return column;
     } else {
         column = ColumnOp::applyGain(column, m_params.scaleFactor);
@@ -414,27 +414,31 @@
     Profiler profiler("Colour3DPlotRenderer::getColumn");
 
     ColumnOp::Column column;
+    ColumnOp::Column fullColumn;
 
-    if (m_params.colourScale.getScale() == ColourScaleType::Phase &&
-        m_sources.fft) {
-
-        ColumnOp::Column fullColumn = m_sources.fft->getPhases(sx);
-
-        column = vector<float>(fullColumn.data() + minbin,
-                               fullColumn.data() + minbin + nbins);
-
-    } else {
-
-        ColumnOp::Column fullColumn =
-            (peakCacheIndex >= 0 ?
-             m_sources.peakCaches[peakCacheIndex] :
-             m_sources.source)
-            ->getColumn(sx);
-                
-        column = vector<float>(fullColumn.data() + minbin,
-                               fullColumn.data() + minbin + nbins);
+    if (m_params.colourScale.getScale() == ColourScaleType::Phase) {
+        auto fftModel = ModelById::getAs<FFTModel>(m_sources.fft);
+        if (fftModel) {
+            fullColumn = fftModel->getPhases(sx);
+        }
     }
 
+    if (fullColumn.empty()) {
+        
+        if (peakCacheIndex >= 0) {
+            fullColumn = m_sources.peakCaches[peakCacheIndex]->getColumn(sx);
+        } else {
+            auto model = ModelById::getAs<DenseThreeDimensionalModel>
+                (m_sources.source);
+            if (!model) {
+                return vector<float>(nbins, 0.f);
+            }
+            fullColumn = model->getColumn(sx);
+        }
+    }
+
+    column = vector<float>(fullColumn.data() + minbin,
+                           fullColumn.data() + minbin + nbins);
     return column;
 }
 
@@ -451,7 +455,8 @@
     bool illuminate = v->shouldIlluminateLocalFeatures
         (m_sources.verticalBinLayer, illuminatePos);
 
-    const DenseThreeDimensionalModel *model = m_sources.source;
+    auto model = ModelById::getAs<DenseThreeDimensionalModel>(m_sources.source);
+    if (!model) return magRange;
     
     int x0 = rect.left();
     int x1 = rect.right() + 1;
@@ -602,7 +607,7 @@
     peakCacheIndex = -1;
     binsPerPeak = -1;
 
-    const DenseThreeDimensionalModel *model = m_sources.source;
+    auto model = ModelById::getAs<DenseThreeDimensionalModel>(m_sources.source);
     if (!model) return;
     if (m_params.binDisplay == BinDisplay::PeakFrequencies) return;
     if (m_params.colourScale.getScale() == ColourScaleType::Phase) return;
@@ -653,10 +658,8 @@
     // buffer is at the same resolution as the target in the cache, so
     // no extra scaling needed.
 
-    const DenseThreeDimensionalModel *model = m_sources.source;
-    if (!model || !model->isOK() || !model->isReady()) {
-        throw std::logic_error("no source model provided, or model not ready");
-    }
+    auto model = ModelById::getAs<DenseThreeDimensionalModel>(m_sources.source);
+    if (!model) return;
 
     int h = v->getPaintHeight();
 
@@ -794,10 +797,8 @@
     // buffer is at bin resolution, i.e. buffer x == source column
     // number. We use toolkit smooth scaling for interpolation.
 
-    const DenseThreeDimensionalModel *model = m_sources.source;
-    if (!model || !model->isOK() || !model->isReady()) {
-        throw std::logic_error("no source model provided, or model not ready");
-    }
+    auto model = ModelById::getAs<DenseThreeDimensionalModel>(m_sources.source);
+    if (!model) return;
 
     // The draw buffer will contain a fragment at bin resolution. We
     // need to ensure that it starts and ends at points where a
@@ -950,7 +951,10 @@
     Profiler profiler("Colour3DPlotRenderer::renderDrawBuffer");
     
     int divisor = 1;
-    const DenseThreeDimensionalModel *sourceModel = m_sources.source;
+    auto model = ModelById::getAs<DenseThreeDimensionalModel>(m_sources.source);
+    if (!model) return 0;
+    
+    const DenseThreeDimensionalModel *sourceModel = model.get();
     if (peakCacheIndex >= 0) {
         divisor = m_sources.peakCaches[peakCacheIndex]->getColumnsPerPeak();
         sourceModel = m_sources.peakCaches[peakCacheIndex];
@@ -1125,7 +1129,8 @@
                       RenderTimer::SlowRender :
                       RenderTimer::NoTimeout);
 
-    const FFTModel *fft = m_sources.fft;
+    auto fft = ModelById::getAs<FFTModel>(m_sources.fft);
+    if (!fft) return 0;
 
     int sh = fft->getHeight();
     
--- a/layer/Colour3DPlotRenderer.h	Thu Jun 13 15:35:01 2019 +0100
+++ b/layer/Colour3DPlotRenderer.h	Thu Jun 27 13:16:25 2019 +0100
@@ -23,16 +23,16 @@
 #include "base/ColumnOp.h"
 #include "base/MagnitudeRange.h"
 
+#include "data/model/Model.h"
+
 #include <QRect>
 #include <QPainter>
 #include <QImage>
 
 class LayerGeometryProvider;
 class VerticalBinLayer;
-class DenseThreeDimensionalModel;
+class RenderTimer;
 class Dense3DModelPeakCache;
-class FFTModel;
-class RenderTimer;
 
 enum class BinDisplay {
     AllBins,
@@ -49,12 +49,12 @@
 {
 public:
     struct Sources {
-        Sources() : verticalBinLayer(0), source(0), fft(0) { }
+        Sources() : verticalBinLayer(0) { }
         
         // These must all outlive this class
-        const VerticalBinLayer *verticalBinLayer;  // always
-        const DenseThreeDimensionalModel *source;  // always
-        const FFTModel *fft;                       // optionally
+        const VerticalBinLayer *verticalBinLayer; // always
+        ModelId source; // always; a DenseThreeDimensionalModel
+        ModelId fft; // optionally
         std::vector<Dense3DModelPeakCache *> peakCaches; // zero or more
     };        
 
--- a/layer/FlexiNoteLayer.cpp	Thu Jun 13 15:35:01 2019 +0100
+++ b/layer/FlexiNoteLayer.cpp	Thu Jun 27 13:16:25 2019 +0100
@@ -53,7 +53,6 @@
 
 FlexiNoteLayer::FlexiNoteLayer() :
     SingleColourLayer(),
-    m_model(nullptr),
     m_editing(false),
     m_intelligentActions(true),
     m_dragPointX(0),
@@ -73,16 +72,17 @@
 }
 
 void
-FlexiNoteLayer::setModel(NoteModel *model) 
+FlexiNoteLayer::setModel(ModelId modelId) 
 {
-    if (m_model == model) return;
-    m_model = model;
+    if (m_model == modelId) return;
+    
+    auto model = ModelById::getAs<NoteModel>(modelId);
+    if (!model) throw std::logic_error("Not a NoteModel");
+
+    m_model = modelId;
 
     connectSignals(m_model);
 
-    // m_scaleMinimum = 0;
-    // m_scaleMaximum = 0;
-
     emit modelReplaced();
 }
 
@@ -123,7 +123,8 @@
 QString
 FlexiNoteLayer::getScaleUnits() const
 {
-    if (m_model) return m_model->getScaleUnits();
+    auto model = ModelById::getAs<NoteModel>(m_model);
+    if (model) return model->getScaleUnits();
     else return "";
 }
 
@@ -144,7 +145,8 @@
     } else if (name == "Scale Units") {
 
         if (deflt) *deflt = 0;
-        if (m_model) {
+        auto model = ModelById::getAs<NoteModel>(m_model);
+        if (model) {
             val = UnitDatabase::getInstance()->getUnitId
                 (getScaleUnits());
         }
@@ -179,8 +181,9 @@
     if (name == "Vertical Scale") {
         setVerticalScale(VerticalScale(value));
     } else if (name == "Scale Units") {
-        if (m_model) {
-            m_model->setScaleUnits
+        auto model = ModelById::getAs<NoteModel>(m_model);
+        if (model) {
+            model->setScaleUnits
                 (UnitDatabase::getInstance()->getUnitById(value));
             emit modelChanged();
         }
@@ -215,13 +218,22 @@
 //    return false;
 }
 
+int
+FlexiNoteLayer::getCompletion(LayerGeometryProvider *) const
+{
+    auto model = ModelById::get(m_model);
+    if (model) return model->getCompletion();
+    else return 0;
+}
+
 bool
 FlexiNoteLayer::getValueExtents(double &min, double &max,
                                 bool &logarithmic, QString &unit) const
 {
-    if (!m_model) return false;
-    min = m_model->getValueMinimum();
-    max = m_model->getValueMaximum();
+    auto model = ModelById::getAs<NoteModel>(m_model);
+    if (!model) return false;
+    min = model->getValueMinimum();
+    max = model->getValueMaximum();
 
     if (shouldConvertMIDIToHz()) {
         unit = "Hz";
@@ -238,7 +250,8 @@
 bool
 FlexiNoteLayer::getDisplayExtents(double &min, double &max) const
 {
-    if (!m_model || shouldAutoAlign()) {
+    auto model = ModelById::getAs<NoteModel>(m_model);
+    if (!model || shouldAutoAlign()) {
 //        std::cerr << "No model or shouldAutoAlign()" << std::endl;
         return false;
     }
@@ -250,8 +263,8 @@
     }
 
     if (m_scaleMinimum == m_scaleMaximum) {
-        min = m_model->getValueMinimum();
-        max = m_model->getValueMaximum();
+        min = model->getValueMinimum();
+        max = model->getValueMaximum();
     } else {
         min = m_scaleMinimum;
         max = m_scaleMaximum;
@@ -272,7 +285,8 @@
 bool
 FlexiNoteLayer::setDisplayExtents(double min, double max)
 {
-    if (!m_model) return false;
+    auto model = ModelById::getAs<NoteModel>(m_model);
+    if (!model) return false;
 
     if (min == max) {
         if (min == 0.f) {
@@ -297,7 +311,8 @@
 FlexiNoteLayer::getVerticalZoomSteps(int &defaultStep) const
 {
     if (shouldAutoAlign()) return 0;
-    if (!m_model) return 0;
+    auto model = ModelById::getAs<NoteModel>(m_model);
+    if (!model) return 0;
 
     defaultStep = 0;
     return 100;
@@ -307,7 +322,8 @@
 FlexiNoteLayer::getCurrentVerticalZoomStep() const
 {
     if (shouldAutoAlign()) return 0;
-    if (!m_model) return 0;
+    auto model = ModelById::getAs<NoteModel>(m_model);
+    if (!model) return 0;
 
     RangeMapper *mapper = getNewVerticalZoomRangeMapper();
     if (!mapper) return 0;
@@ -328,7 +344,8 @@
 FlexiNoteLayer::setVerticalZoomStep(int step)
 {
     if (shouldAutoAlign()) return;
-    if (!m_model) return;
+    auto model = ModelById::getAs<NoteModel>(m_model);
+    if (!model) return;
 
     RangeMapper *mapper = getNewVerticalZoomRangeMapper();
     if (!mapper) return;
@@ -378,7 +395,8 @@
 RangeMapper *
 FlexiNoteLayer::getNewVerticalZoomRangeMapper() const
 {
-    if (!m_model) return nullptr;
+    auto model = ModelById::getAs<NoteModel>(m_model);
+    if (!model) return nullptr;
     
     RangeMapper *mapper;
 
@@ -401,21 +419,22 @@
 EventVector
 FlexiNoteLayer::getLocalPoints(LayerGeometryProvider *v, int x) const
 {
-    if (!m_model) return {};
+    auto model = ModelById::getAs<NoteModel>(m_model);
+    if (!model) return {};
     
     sv_frame_t frame = v->getFrameForX(x);
 
-    EventVector local = m_model->getEventsCovering(frame);
+    EventVector local = model->getEventsCovering(frame);
     if (!local.empty()) return local;
 
     int fuzz = ViewManager::scalePixelSize(2);
     sv_frame_t start = v->getFrameForX(x - fuzz);
     sv_frame_t end = v->getFrameForX(x + fuzz);
 
-    local = m_model->getEventsStartingWithin(frame, end - frame);
+    local = model->getEventsStartingWithin(frame, end - frame);
     if (!local.empty()) return local;
 
-    local = m_model->getEventsSpanning(start, frame - start);
+    local = model->getEventsSpanning(start, frame - start);
     if (!local.empty()) return local;
 
     return {};
@@ -424,11 +443,12 @@
 bool
 FlexiNoteLayer::getPointToDrag(LayerGeometryProvider *v, int x, int y, Event &point) const
 {
-    if (!m_model) return false;
+    auto model = ModelById::getAs<NoteModel>(m_model);
+    if (!model) return false;
 
     sv_frame_t frame = v->getFrameForX(x);
 
-    EventVector onPoints = m_model->getEventsCovering(frame);
+    EventVector onPoints = model->getEventsCovering(frame);
     if (onPoints.empty()) return false;
 
     int nearestDistance = -1;
@@ -448,11 +468,12 @@
 FlexiNoteLayer::getNoteToEdit(LayerGeometryProvider *v, int x, int y, Event &point) const
 {
     // GF: find the note that is closest to the cursor
-    if (!m_model) return false;
+    auto model = ModelById::getAs<NoteModel>(m_model);
+    if (!model) return false;
 
     sv_frame_t frame = v->getFrameForX(x);
 
-    EventVector onPoints = m_model->getEventsCovering(frame);
+    EventVector onPoints = model->getEventsCovering(frame);
     if (onPoints.empty()) return false;
 
     int nearestDistance = -1;
@@ -473,12 +494,13 @@
 {
     int x = pos.x();
 
-    if (!m_model || !m_model->getSampleRate()) return "";
+    auto model = ModelById::getAs<NoteModel>(m_model);
+    if (!model || !model->getSampleRate()) return "";
 
     EventVector points = getLocalPoints(v, x);
 
     if (points.empty()) {
-        if (!m_model->isReady()) {
+        if (!model->isReady()) {
             return tr("In progress");
         } else {
             return tr("No local points");
@@ -493,9 +515,9 @@
         int y = getYForValue(v, i->getValue());
         int h = NOTE_HEIGHT; // GF: larger notes
 
-        if (m_model->getValueQuantization() != 0.0) {
+        if (model->getValueQuantization() != 0.0) {
             h = y - getYForValue
-                (v, i->getValue() + m_model->getValueQuantization());
+                (v, i->getValue() + model->getValueQuantization());
             if (h < NOTE_HEIGHT) h = NOTE_HEIGHT;
         }
 
@@ -509,9 +531,9 @@
     if (i == points.end()) return tr("No local points");
 
     RealTime rt = RealTime::frame2RealTime(note.getFrame(),
-                                           m_model->getSampleRate());
+                                           model->getSampleRate());
     RealTime rd = RealTime::frame2RealTime(note.getDuration(),
-                                           m_model->getSampleRate());
+                                           model->getSampleRate());
     
     QString pitchText;
 
@@ -562,11 +584,12 @@
                                    int &resolution,
                                    SnapType snap) const
 {
-    if (!m_model) {
+    auto model = ModelById::getAs<NoteModel>(m_model);
+    if (!model) {
         return Layer::snapToFeatureFrame(v, frame, resolution, snap);
     }
 
-    resolution = m_model->getResolution();
+    resolution = model->getResolution();
     EventVector points;
 
     if (snap == SnapNeighbouring) {
@@ -577,7 +600,7 @@
         return true;
     }    
 
-    points = m_model->getEventsCovering(frame);
+    points = model->getEventsCovering(frame);
     sv_frame_t snapped = frame;
     bool found = false;
 
@@ -650,8 +673,9 @@
 
         if (!v->getValueExtents(queryUnits, min, max, log)) {
 
-            min = m_model->getValueMinimum();
-            max = m_model->getValueMaximum();
+            auto model = ModelById::getAs<NoteModel>(m_model);
+            min = model->getValueMinimum();
+            max = model->getValueMaximum();
 
             if (shouldConvertMIDIToHz()) {
                 min = Pitch::getFrequencyForPitch(int(lrint(min)));
@@ -752,16 +776,16 @@
 bool
 FlexiNoteLayer::shouldAutoAlign() const
 {
-    if (!m_model) return false;
     return (m_verticalScale == AutoAlignScale);
 }
 
 void
 FlexiNoteLayer::paint(LayerGeometryProvider *v, QPainter &paint, QRect rect) const
 {
-    if (!m_model || !m_model->isOK()) return;
+    auto model = ModelById::getAs<NoteModel>(m_model);
+    if (!model || !model->isOK()) return;
 
-    sv_samplerate_t sampleRate = m_model->getSampleRate();
+    sv_samplerate_t sampleRate = model->getSampleRate();
     if (!sampleRate) return;
 
 //    Profiler profiler("FlexiNoteLayer::paint", true);
@@ -770,7 +794,7 @@
     sv_frame_t frame0 = v->getFrameForX(x0);
     sv_frame_t frame1 = v->getFrameForX(x1);
 
-    EventVector points(m_model->getEventsSpanning(frame0, frame1 - frame0));
+    EventVector points(model->getEventsSpanning(frame0, frame1 - frame0));
     if (points.empty()) return;
 
     paint.setPen(getBaseQColor());
@@ -779,10 +803,10 @@
     brushColour.setAlpha(80);
 
 //    SVDEBUG << "FlexiNoteLayer::paint: resolution is "
-//        << m_model->getResolution() << " frames" << endl;
+//        << model->getResolution() << " frames" << endl;
 
-    double min = m_model->getValueMinimum();
-    double max = m_model->getValueMaximum();
+    double min = model->getValueMinimum();
+    double max = model->getValueMaximum();
     if (max == min) max = min + 1.0;
 
     QPoint localPos;
@@ -810,8 +834,8 @@
         int w = v->getXForFrame(p.getFrame() + p.getDuration()) - x;
         int h = NOTE_HEIGHT; //GF: larger notes
     
-        if (m_model->getValueQuantization() != 0.0) {
-            h = y - getYForValue(v, p.getValue() + m_model->getValueQuantization());
+        if (model->getValueQuantization() != 0.0) {
+            h = y - getYForValue(v, p.getValue() + model->getValueQuantization());
             if (h < NOTE_HEIGHT) h = NOTE_HEIGHT; //GF: larger notes
         }
 
@@ -827,7 +851,7 @@
             paint.setPen(v->getForeground());
         
             QString vlabel = tr("freq: %1%2")
-                .arg(p.getValue()).arg(m_model->getScaleUnits());
+                .arg(p.getValue()).arg(model->getScaleUnits());
             PaintAssistant::drawVisibleText
                 (v, paint, 
                  x,
@@ -837,7 +861,7 @@
 
             QString hlabel = tr("dur: %1")
                 .arg(RealTime::frame2RealTime
-                     (p.getDuration(), m_model->getSampleRate()).toText(true)
+                     (p.getDuration(), model->getSampleRate()).toText(true)
                      .c_str());
             PaintAssistant::drawVisibleText
                 (v, paint, 
@@ -869,7 +893,7 @@
 int
 FlexiNoteLayer::getVerticalScaleWidth(LayerGeometryProvider *v, bool, QPainter &paint) const
 {
-    if (!m_model || shouldAutoAlign()) {
+    if (shouldAutoAlign()) {
         return 0;
     } else  {
         if (m_verticalScale == LogScale || m_verticalScale == MIDIRangeScale) {
@@ -883,7 +907,8 @@
 void
 FlexiNoteLayer::paintVerticalScale(LayerGeometryProvider *v, bool, QPainter &paint, QRect) const
 {
-    if (!m_model || m_model->isEmpty()) return;
+    auto model = ModelById::getAs<NoteModel>(m_model);
+    if (!model || model->isEmpty()) return;
 
     QString unit;
     double min, max;
@@ -923,11 +948,12 @@
 {
 //    SVDEBUG << "FlexiNoteLayer::drawStart(" << e->x() << "," << e->y() << ")" << endl;
 
-    if (!m_model) return;
+    auto model = ModelById::getAs<NoteModel>(m_model);
+    if (!model) return;
 
     sv_frame_t frame = v->getFrameForX(e->x());
     if (frame < 0) frame = 0;
-    frame = frame / m_model->getResolution() * m_model->getResolution();
+    frame = frame / model->getResolution() * model->getResolution();
 
     double value = getValueForY(v, e->y());
 
@@ -935,7 +961,7 @@
     m_originalPoint = m_editingPoint;
 
     if (m_editingCommand) finish(m_editingCommand);
-    m_editingCommand = new ChangeEventsCommand(m_model, tr("Draw Point"));
+    m_editingCommand = new ChangeEventsCommand<Model>(m_model, tr("Draw Point"));
     m_editingCommand->add(m_editingPoint);
 
     m_editing = true;
@@ -946,11 +972,12 @@
 {
 //    SVDEBUG << "FlexiNoteLayer::drawDrag(" << e->x() << "," << e->y() << ")" << endl;
 
-    if (!m_model || !m_editing) return;
+    auto model = ModelById::getAs<NoteModel>(m_model);
+    if (!model || !m_editing) return;
 
     sv_frame_t frame = v->getFrameForX(e->x());
     if (frame < 0) frame = 0;
-    frame = frame / m_model->getResolution() * m_model->getResolution();
+    frame = frame / model->getResolution() * model->getResolution();
 
     double newValue = getValueForY(v, e->y());
 
@@ -975,7 +1002,8 @@
 FlexiNoteLayer::drawEnd(LayerGeometryProvider *, QMouseEvent *)
 {
 //    SVDEBUG << "FlexiNoteLayer::drawEnd(" << e->x() << "," << e->y() << ")" << endl;
-    if (!m_model || !m_editing) return;
+    auto model = ModelById::getAs<NoteModel>(m_model);
+    if (!model || !m_editing) return;
     finish(m_editingCommand);
     m_editingCommand = nullptr;
     m_editing = false;
@@ -984,7 +1012,8 @@
 void
 FlexiNoteLayer::eraseStart(LayerGeometryProvider *v, QMouseEvent *e)
 {
-    if (!m_model) return;
+    auto model = ModelById::getAs<NoteModel>(m_model);
+    if (!model) return;
 
     if (!getPointToDrag(v, e->x(), e->y(), m_editingPoint)) return;
 
@@ -1004,15 +1033,15 @@
 void
 FlexiNoteLayer::eraseEnd(LayerGeometryProvider *v, QMouseEvent *e)
 {
-    if (!m_model || !m_editing) return;
-
+    if (!m_editing) return;
     m_editing = false;
 
     Event p(0);
     if (!getPointToDrag(v, e->x(), e->y(), p)) return;
-    if (p.getFrame() != m_editingPoint.getFrame() || p.getValue() != m_editingPoint.getValue()) return;
+    if (p.getFrame() != m_editingPoint.getFrame() ||
+        p.getValue() != m_editingPoint.getValue()) return;
 
-    m_editingCommand = new ChangeEventsCommand(m_model, tr("Erase Point"));
+    m_editingCommand = new ChangeEventsCommand<Model>(m_model, tr("Erase Point"));
     m_editingCommand->remove(m_editingPoint);
     finish(m_editingCommand);
     m_editingCommand = nullptr;
@@ -1025,7 +1054,8 @@
 //    SVDEBUG << "FlexiNoteLayer::editStart(" << e->x() << "," << e->y() << ")" << endl;
     std::cerr << "FlexiNoteLayer::editStart(" << e->x() << "," << e->y() << ")" << std::endl;
 
-    if (!m_model) return;
+    auto model = ModelById::getAs<NoteModel>(m_model);
+    if (!model) return;
 
     if (!getPointToDrag(v, e->x(), e->y(), m_editingPoint)) return;
     m_originalPoint = m_editingPoint;
@@ -1056,7 +1086,7 @@
     m_greatestLeftNeighbourFrame = -1;
     m_smallestRightNeighbourFrame = std::numeric_limits<int>::max();
 
-    EventVector allEvents = m_model->getAllEvents();
+    EventVector allEvents = model->getAllEvents();
     
     for (auto currentNote: allEvents) {
         
@@ -1082,7 +1112,8 @@
 //    SVDEBUG << "FlexiNoteLayer::editDrag(" << e->x() << "," << e->y() << ")" << endl;
     std::cerr << "FlexiNoteLayer::editDrag(" << e->x() << "," << e->y() << ")" << std::endl;
 
-    if (!m_model || !m_editing) return;
+    auto model = ModelById::getAs<NoteModel>(m_model);
+    if (!model || !m_editing) return;
 
     int xdist = e->x() - m_dragStartX;
     int ydist = e->y() - m_dragStartY;
@@ -1091,12 +1122,13 @@
 
     sv_frame_t dragFrame = v->getFrameForX(newx);
     if (dragFrame < 0) dragFrame = 0;
-    dragFrame = dragFrame / m_model->getResolution() * m_model->getResolution();
+    dragFrame = dragFrame / model->getResolution() * model->getResolution();
     
     double value = getValueForY(v, newy);
 
     if (!m_editingCommand) {
-        m_editingCommand = new ChangeEventsCommand(m_model, tr("Drag Point"));
+        m_editingCommand =
+            new ChangeEventsCommand<Model>(m_model, tr("Drag Point"));
     }
     m_editingCommand->remove(m_editingPoint);
 
@@ -1182,7 +1214,8 @@
     std::cerr << "FlexiNoteLayer::editEnd("
               << e->x() << "," << e->y() << ")" << std::endl;
     
-    if (!m_model || !m_editing) return;
+    auto model = ModelById::getAs<NoteModel>(m_model);
+    if (!model || !m_editing) return;
 
     if (m_editingCommand) {
 
@@ -1218,9 +1251,11 @@
 void
 FlexiNoteLayer::splitStart(LayerGeometryProvider *v, QMouseEvent *e)
 {
+    auto model = ModelById::getAs<NoteModel>(m_model);
+    if (!model) return;
+
     // GF: note splitting starts (!! remove printing soon)
     std::cerr << "splitStart (n.b. editStart will be called later, if the user drags the mouse)" << std::endl;
-    if (!m_model) return;
 
     if (!getPointToDrag(v, e->x(), e->y(), m_editingPoint)) return;
     // m_originalPoint = m_editingPoint;
@@ -1241,9 +1276,10 @@
 void
 FlexiNoteLayer::splitEnd(LayerGeometryProvider *v, QMouseEvent *e)
 {
+    auto model = ModelById::getAs<NoteModel>(m_model);
     // GF: note splitting ends. (!! remove printing soon)
     std::cerr << "splitEnd" << std::endl;
-    if (!m_model || !m_editing || m_editMode != SplitNote) return;
+    if (!model || !m_editing || m_editMode != SplitNote) return;
 
     int xdist = e->x() - m_dragStartX;
     int ydist = e->y() - m_dragStartY;
@@ -1266,13 +1302,15 @@
 void
 FlexiNoteLayer::splitNotesAt(LayerGeometryProvider *v, sv_frame_t frame, QMouseEvent *e)
 {
-    EventVector onPoints = m_model->getEventsCovering(frame);
+    auto model = ModelById::getAs<NoteModel>(m_model);
+    if (!model) return;
+    
+    EventVector onPoints = model->getEventsCovering(frame);
     if (onPoints.empty()) return;
     
     Event note(*onPoints.begin());
 
-    ChangeEventsCommand *command = new ChangeEventsCommand
-        (m_model, tr("Edit Point"));
+    auto command = new ChangeEventsCommand<Model>(m_model, tr("Edit Point"));
     command->remove(note);
 
     if (!e || !(e->modifiers() & Qt::ShiftModifier)) {
@@ -1306,15 +1344,16 @@
 void
 FlexiNoteLayer::addNote(LayerGeometryProvider *v, QMouseEvent *e)
 {
+    auto model = ModelById::getAs<NoteModel>(m_model);
     std::cerr << "addNote" << std::endl;
-    if (!m_model) return;
+    if (!model) return;
 
     sv_frame_t duration = 10000;
     
     sv_frame_t frame = v->getFrameForX(e->x());
     double value = getValueForY(v, e->y());
     
-    EventVector noteList = m_model->getAllEvents();
+    EventVector noteList = model->getAllEvents();
 
     if (m_intelligentActions) {
         sv_frame_t smallestRightNeighbourFrame = 0;
@@ -1333,16 +1372,15 @@
     }
 
     if (!m_intelligentActions || 
-        (m_model->getEventsCovering(frame).empty() && duration > 0)) {
+        (model->getEventsCovering(frame).empty() && duration > 0)) {
         Event newNote(frame, float(value), duration, 100.f, tr("new note"));
-        ChangeEventsCommand *command = new ChangeEventsCommand
-            (m_model, tr("Add Point"));
+        auto command = new ChangeEventsCommand<Model>(m_model, tr("Add Point"));
         command->add(newNote);
         finish(command);
     }
 }
 
-SparseTimeValueModel *
+ModelId
 FlexiNoteLayer::getAssociatedPitchModel(LayerGeometryProvider *v) const
 {
     // Better than we used to do, but still not very satisfactory
@@ -1354,29 +1392,28 @@
         if (layer &&
             layer->getLayerPresentationName() != "candidate") {
 //            cerr << "FlexiNoteLayer::getAssociatedPitchModel: looks like our layer is " << layer << endl;
-            SparseTimeValueModel *model = qobject_cast<SparseTimeValueModel *>
-                (layer->getModel());
-//            cerr << "FlexiNoteLayer::getAssociatedPitchModel: and its model is " << model << endl;
+            auto modelId = layer->getModel();
+            auto model = ModelById::getAs<SparseTimeValueModel>(modelId);
             if (model && model->getScaleUnits() == "Hz") {
-                cerr << "FlexiNoteLayer::getAssociatedPitchModel: it's good, returning " << model << endl;
-                return model;
+//                cerr << "FlexiNoteLayer::getAssociatedPitchModel: it's good, returning " << model << endl;
+                return modelId;
             }
         }
     }
-    cerr << "FlexiNoteLayer::getAssociatedPitchModel: failed to find a model" << endl;
-    return nullptr;
+//    cerr << "FlexiNoteLayer::getAssociatedPitchModel: failed to find a model" << endl;
+    return {};
 }
 
 void
 FlexiNoteLayer::snapSelectedNotesToPitchTrack(LayerGeometryProvider *v, Selection s)
 {
-    if (!m_model) return;
+    auto model = ModelById::getAs<NoteModel>(m_model);
+    if (!model) return;
 
     EventVector points =
-        m_model->getEventsStartingWithin(s.getStartFrame(), s.getDuration());
+        model->getEventsStartingWithin(s.getStartFrame(), s.getDuration());
 
-    ChangeEventsCommand *command = new ChangeEventsCommand
-        (m_model, tr("Snap Notes"));
+    auto command = new ChangeEventsCommand<Model>(m_model, tr("Snap Notes"));
 
     cerr << "snapSelectedNotesToPitchTrack: selection is from " << s.getStartFrame() << " to " << s.getEndFrame() << endl;
 
@@ -1408,18 +1445,20 @@
 void
 FlexiNoteLayer::mergeNotes(LayerGeometryProvider *v, Selection s, bool inclusive)
 {
+    auto model = ModelById::getAs<NoteModel>(m_model);
+    if (!model) return;
+    
     EventVector points;
     if (inclusive) {
-        points = m_model->getEventsSpanning(s.getStartFrame(), s.getDuration());
+        points = model->getEventsSpanning(s.getStartFrame(), s.getDuration());
     } else {
-        points = m_model->getEventsWithin(s.getStartFrame(), s.getDuration());
+        points = model->getEventsWithin(s.getStartFrame(), s.getDuration());
     }
         
     EventVector::iterator i = points.begin();
     if (i == points.end()) return;
 
-    ChangeEventsCommand *command = 
-        new ChangeEventsCommand(m_model, tr("Merge Notes"));
+    auto command = new ChangeEventsCommand<Model>(m_model, tr("Merge Notes"));
 
     Event newNote(*i);
 
@@ -1446,7 +1485,8 @@
 bool
 FlexiNoteLayer::updateNoteValueFromPitchCurve(LayerGeometryProvider *v, Event &note) const
 {
-    SparseTimeValueModel *model = getAssociatedPitchModel(v);
+    ModelId modelId = getAssociatedPitchModel(v);
+    auto model = ModelById::getAs<SparseTimeValueModel>(modelId);
     if (!model) return false;
         
     std::cerr << model->getTypeName() << std::endl;
@@ -1525,8 +1565,7 @@
 void
 FlexiNoteLayer::getRelativeMousePosition(LayerGeometryProvider *v, Event &note, int x, int y, bool &closeToLeft, bool &closeToRight, bool &closeToTop, bool &closeToBottom) const
 {
-    // GF: TODO: consoloidate the tolerance values
-    if (!m_model) return;
+    // GF: TODO: consolidate the tolerance values
 
     int ctol = 0;
     int noteStartX = v->getXForFrame(note.getFrame());
@@ -1555,7 +1594,8 @@
 FlexiNoteLayer::editOpen(LayerGeometryProvider *v, QMouseEvent *e)
 {
     std::cerr << "Opening note editor dialog" << std::endl;
-    if (!m_model) return false;
+    auto model = ModelById::getAs<NoteModel>(m_model);
+    if (!model) return false;
 
     Event note(0);
     if (!getPointToDrag(v, e->x(), e->y(), note)) return false;
@@ -1563,7 +1603,7 @@
 //    Event note = *points.begin();
 
     ItemEditDialog *dialog = new ItemEditDialog
-        (m_model->getSampleRate(),
+        (model->getSampleRate(),
          ItemEditDialog::ShowTime |
          ItemEditDialog::ShowDuration |
          ItemEditDialog::ShowValue |
@@ -1583,8 +1623,7 @@
             .withDuration(dialog->getFrameDuration())
             .withLabel(dialog->getText());
         
-        ChangeEventsCommand *command = new ChangeEventsCommand
-            (m_model, tr("Edit Point"));
+        auto command = new ChangeEventsCommand<Model>(m_model, tr("Edit Point"));
         command->remove(note);
         command->add(newNote);
         finish(command);
@@ -1597,13 +1636,13 @@
 void
 FlexiNoteLayer::moveSelection(Selection s, sv_frame_t newStartFrame)
 {
-    if (!m_model) return;
-
-    ChangeEventsCommand *command =
-        new ChangeEventsCommand(m_model, tr("Drag Selection"));
+    auto model = ModelById::getAs<NoteModel>(m_model);
+    if (!model) return;
+    
+    auto command = new ChangeEventsCommand<Model>(m_model, tr("Drag Selection"));
 
     EventVector points =
-        m_model->getEventsStartingWithin(s.getStartFrame(), s.getDuration());
+        model->getEventsStartingWithin(s.getStartFrame(), s.getDuration());
 
     for (Event p: points) {
         command->remove(p);
@@ -1618,13 +1657,13 @@
 void
 FlexiNoteLayer::resizeSelection(Selection s, Selection newSize)
 {
-    if (!m_model || !s.getDuration()) return;
+    auto model = ModelById::getAs<NoteModel>(m_model);
+    if (!model || !s.getDuration()) return;
 
-    ChangeEventsCommand *command =
-        new ChangeEventsCommand(m_model, tr("Resize Selection"));
+    auto command = new ChangeEventsCommand<Model>(m_model, tr("Resize Selection"));
 
     EventVector points =
-        m_model->getEventsStartingWithin(s.getStartFrame(), s.getDuration());
+        model->getEventsStartingWithin(s.getStartFrame(), s.getDuration());
 
     double ratio = double(newSize.getDuration()) / double(s.getDuration());
     double oldStart = double(s.getStartFrame());
@@ -1648,13 +1687,14 @@
 void
 FlexiNoteLayer::deleteSelection(Selection s)
 {
-    if (!m_model) return;
+    auto model = ModelById::getAs<NoteModel>(m_model);
+    if (!model) return;
 
-    ChangeEventsCommand *command =
-        new ChangeEventsCommand(m_model, tr("Delete Selected Points"));
+    auto command =
+        new ChangeEventsCommand<Model>(m_model, tr("Delete Selected Points"));
 
     EventVector points =
-        m_model->getEventsStartingWithin(s.getStartFrame(), s.getDuration());
+        model->getEventsStartingWithin(s.getStartFrame(), s.getDuration());
 
     for (Event p: points) {
         command->remove(p);
@@ -1666,13 +1706,14 @@
 void
 FlexiNoteLayer::deleteSelectionInclusive(Selection s)
 {
-    if (!m_model) return;
+    auto model = ModelById::getAs<NoteModel>(m_model);
+    if (!model) return;
 
-    ChangeEventsCommand *command =
-        new ChangeEventsCommand(m_model, tr("Delete Selected Points"));
+    auto command =
+        new ChangeEventsCommand<Model>(m_model, tr("Delete Selected Points"));
 
     EventVector points =
-        m_model->getEventsSpanning(s.getStartFrame(), s.getDuration());
+        model->getEventsSpanning(s.getStartFrame(), s.getDuration());
 
     for (Event p: points) {
         command->remove(p);
@@ -1684,10 +1725,11 @@
 void
 FlexiNoteLayer::copy(LayerGeometryProvider *v, Selection s, Clipboard &to)
 {
-    if (!m_model) return;
+    auto model = ModelById::getAs<NoteModel>(m_model);
+    if (!model) return;
 
     EventVector points =
-        m_model->getEventsStartingWithin(s.getStartFrame(), s.getDuration());
+        model->getEventsStartingWithin(s.getStartFrame(), s.getDuration());
 
     for (Event p: points) {
         to.addPoint(p.withReferenceFrame(alignToReference(v, p.getFrame())));
@@ -1697,7 +1739,8 @@
 bool
 FlexiNoteLayer::paste(LayerGeometryProvider *v, const Clipboard &from, sv_frame_t /*frameOffset */, bool /* interactive */)
 {
-    if (!m_model) return false;
+    auto model = ModelById::getAs<NoteModel>(m_model);
+    if (!model) return false;
 
     const EventVector &points = from.getPoints();
 
@@ -1720,8 +1763,7 @@
         }
     }
 
-    ChangeEventsCommand *command =
-        new ChangeEventsCommand(m_model, tr("Paste"));
+    auto command = new ChangeEventsCommand<Model>(m_model, tr("Paste"));
 
     for (EventVector::const_iterator i = points.begin();
          i != points.end(); ++i) {
@@ -1745,8 +1787,8 @@
         Event p = *i;
         Event newPoint = p;
         if (!p.hasValue()) {
-            newPoint = newPoint.withValue((m_model->getValueMinimum() +
-                                           m_model->getValueMaximum()) / 2);
+            newPoint = newPoint.withValue((model->getValueMinimum() +
+                                           model->getValueMaximum()) / 2);
         }
         if (!p.hasDuration()) {
             sv_frame_t nextFrame = frame;
@@ -1758,7 +1800,7 @@
                 nextFrame = j->getFrame();
             }
             if (nextFrame == frame) {
-                newPoint = newPoint.withDuration(m_model->getResolution());
+                newPoint = newPoint.withDuration(model->getResolution());
             } else {
                 newPoint = newPoint.withDuration(nextFrame - frame);
             }
@@ -1789,13 +1831,11 @@
         if (lrintf(p.getValue()) == pitch) {
             m_pendingNoteOns.erase(i);
             Event note = p.withDuration(frame - p.getFrame());
-            if (m_model) {
-                ChangeEventsCommand *c = new ChangeEventsCommand
-                    (m_model, tr("Record Note"));
-                c->add(note);
-                // execute and bundle:
-                CommandHistory::getInstance()->addCommand(c, true, true);
-            }
+            auto c = new ChangeEventsCommand<Model>
+                (m_model, tr("Record Note"));
+            c->add(note);
+            // execute and bundle:
+            CommandHistory::getInstance()->addCommand(c, true, true);
             break;
         }
     }
@@ -1840,10 +1880,13 @@
 void
 FlexiNoteLayer::setVerticalRangeToNoteRange(LayerGeometryProvider *v)
 {
+    auto model = ModelById::getAs<NoteModel>(m_model);
+    if (!model) return;
+    
     double minf = std::numeric_limits<double>::max();
     double maxf = 0;
     bool hasNotes = 0;
-    EventVector allPoints = m_model->getAllEvents();
+    EventVector allPoints = model->getAllEvents();
     for (EventVector::const_iterator i = allPoints.begin();
          i != allPoints.end(); ++i) {
         hasNotes = 1;
--- a/layer/FlexiNoteLayer.h	Thu Jun 13 15:35:01 2019 +0100
+++ b/layer/FlexiNoteLayer.h	Thu Jun 27 13:16:25 2019 +0100
@@ -81,8 +81,8 @@
     void snapSelectedNotesToPitchTrack(LayerGeometryProvider *v, Selection s);
     void mergeNotes(LayerGeometryProvider *v, Selection s, bool inclusive);
 
-    const Model *getModel() const override { return m_model; }
-    void setModel(NoteModel *model);
+    ModelId getModel() const override { return m_model; }
+    void setModel(ModelId model); // a NoteModel please
 
     PropertyList getProperties() const override;
     QString getPropertyLabel(const PropertyName &) const override;
@@ -118,10 +118,10 @@
 
     bool isLayerEditable() const override { return true; }
 
-    int getCompletion(LayerGeometryProvider *) const override { return m_model->getCompletion(); }
+    int getCompletion(LayerGeometryProvider *) const override;
 
     bool getValueExtents(double &min, double &max,
-                                 bool &log, QString &unit) const override;
+                         bool &log, QString &unit) const override;
 
     bool getDisplayExtents(double &min, double &max) const override;
     bool setDisplayExtents(double min, double max) override;
@@ -176,11 +176,11 @@
     bool getPointToDrag(LayerGeometryProvider *v, int x, int y, Event &) const;
     bool getNoteToEdit(LayerGeometryProvider *v, int x, int y, Event &) const;
     void getRelativeMousePosition(LayerGeometryProvider *v, Event &note, int x, int y, bool &closeToLeft, bool &closeToRight, bool &closeToTop, bool &closeToBottom) const;
-    SparseTimeValueModel *getAssociatedPitchModel(LayerGeometryProvider *v) const;
+    ModelId getAssociatedPitchModel(LayerGeometryProvider *v) const;
     bool updateNoteValueFromPitchCurve(LayerGeometryProvider *v, Event &note) const;
     void splitNotesAt(LayerGeometryProvider *v, sv_frame_t frame, QMouseEvent *e);
 
-    NoteModel *m_model;
+    ModelId m_model;
     bool m_editing;
     bool m_intelligentActions;
     int m_dragPointX;
@@ -191,7 +191,7 @@
     Event m_editingPoint;
     sv_frame_t m_greatestLeftNeighbourFrame;
     sv_frame_t m_smallestRightNeighbourFrame;
-    ChangeEventsCommand *m_editingCommand;
+    ChangeEventsCommand<Model> *m_editingCommand;
     VerticalScale m_verticalScale;
     EditMode m_editMode;
 
@@ -203,7 +203,7 @@
 
     bool shouldAutoAlign() const;
 
-    void finish(ChangeEventsCommand *command) {
+    void finish(ChangeEventsCommand<Model> *command) {
         Command *c = command->finish();
         if (c) CommandHistory::getInstance()->addCommand(c, false);
     }
--- a/layer/ImageLayer.cpp	Thu Jun 13 15:35:01 2019 +0100
+++ b/layer/ImageLayer.cpp	Thu Jun 27 13:16:25 2019 +0100
@@ -43,8 +43,6 @@
 ImageLayer::m_imageMapMutex;
 
 ImageLayer::ImageLayer() :
-    Layer(),
-    m_model(nullptr),
     m_editing(false),
     m_editingCommand(nullptr)
 {
@@ -58,11 +56,23 @@
     }
 }
 
+int
+ImageLayer::getCompletion(LayerGeometryProvider *) const
+{
+    auto model = ModelById::get(m_model);
+    if (model) return model->getCompletion();
+    else return 0;
+}
+
 void
-ImageLayer::setModel(ImageModel *model)
+ImageLayer::setModel(ModelId modelId)
 {
-    if (m_model == model) return;
-    m_model = model;
+    if (m_model == modelId) return;
+    
+    auto model = ModelById::getAs<ImageModel>(modelId);
+    if (!model) throw std::logic_error("Not an ImageModel");
+    
+    m_model = modelId;
 
     connectSignals(m_model);
 
@@ -122,10 +132,11 @@
 EventVector
 ImageLayer::getLocalPoints(LayerGeometryProvider *v, int x, int ) const
 {
-    if (!m_model) return {};
+    auto model = ModelById::getAs<ImageModel>(m_model);
+    if (!model) return {};
 
 //    SVDEBUG << "ImageLayer::getLocalPoints(" << x << "," << y << "):";
-    EventVector points(m_model->getAllEvents());
+    EventVector points(model->getAllEvents());
 
     EventVector rv;
 
@@ -169,12 +180,13 @@
 {
     int x = pos.x();
 
-    if (!m_model || !m_model->getSampleRate()) return "";
+    auto model = ModelById::getAs<ImageModel>(m_model);
+    if (!model || !model->getSampleRate()) return "";
 
     EventVector points = getLocalPoints(v, x, pos.y());
 
     if (points.empty()) {
-        if (!m_model->isReady()) {
+        if (!model->isReady()) {
             return tr("In progress");
         } else {
             return "";
@@ -183,7 +195,7 @@
 
 //    int useFrame = points.begin()->frame;
 
-//    RealTime rt = RealTime::frame2RealTime(useFrame, m_model->getSampleRate());
+//    RealTime rt = RealTime::frame2RealTime(useFrame, model->getSampleRate());
 
     QString text;
 /*    
@@ -208,11 +220,12 @@
                                int &resolution,
                                SnapType snap) const
 {
-    if (!m_model) {
+    auto model = ModelById::getAs<ImageModel>(m_model);
+    if (!model) {
         return Layer::snapToFeatureFrame(v, frame, resolution, snap);
     }
 
-    resolution = m_model->getResolution();
+    resolution = model->getResolution();
 
     if (snap == SnapNeighbouring) {
         EventVector points = getLocalPoints(v, v->getXForFrame(frame), -1);
@@ -222,7 +235,7 @@
     }    
 
     Event e;
-    if (m_model->getNearestEventMatching
+    if (model->getNearestEventMatching
         (frame,
          [](Event) { return true; },
          snap == SnapLeft ? EventSeries::Backward : EventSeries::Forward,
@@ -237,9 +250,10 @@
 void
 ImageLayer::paint(LayerGeometryProvider *v, QPainter &paint, QRect rect) const
 {
-    if (!m_model || !m_model->isOK()) return;
+    auto model = ModelById::getAs<ImageModel>(m_model);
+    if (!model || !model->isOK()) return;
 
-    sv_samplerate_t sampleRate = m_model->getSampleRate();
+    sv_samplerate_t sampleRate = model->getSampleRate();
     if (!sampleRate) return;
 
 //    Profiler profiler("ImageLayer::paint", true);
@@ -250,7 +264,7 @@
     sv_frame_t frame0 = v->getFrameForX(x0);
     sv_frame_t frame1 = v->getFrameForX(x1);
 
-    EventVector points(m_model->getEventsWithin(frame0, frame1 - frame0, 2));
+    EventVector points(model->getEventsWithin(frame0, frame1 - frame0, 2));
     if (points.empty()) return;
 
     paint.save();
@@ -513,20 +527,21 @@
 {
 //    SVDEBUG << "ImageLayer::drawStart(" << e->x() << "," << e->y() << ")" << endl;
 
-    if (!m_model) {
+    auto model = ModelById::getAs<ImageModel>(m_model);
+    if (!model) {
         SVDEBUG << "ImageLayer::drawStart: no model" << endl;
         return;
     }
 
     sv_frame_t frame = v->getFrameForX(e->x());
     if (frame < 0) frame = 0;
-    frame = frame / m_model->getResolution() * m_model->getResolution();
+    frame = frame / model->getResolution() * model->getResolution();
 
     m_editingPoint = Event(frame);
     m_originalPoint = m_editingPoint;
 
     if (m_editingCommand) finish(m_editingCommand);
-    m_editingCommand = new ChangeEventsCommand(m_model, "Add Image");
+    m_editingCommand = new ChangeEventsCommand<Model>(m_model, "Add Image");
     m_editingCommand->add(m_editingPoint);
 
     m_editing = true;
@@ -537,11 +552,12 @@
 {
 //    SVDEBUG << "ImageLayer::drawDrag(" << e->x() << "," << e->y() << ")" << endl;
 
-    if (!m_model || !m_editing) return;
+    auto model = ModelById::getAs<ImageModel>(m_model);
+    if (!model || !m_editing) return;
 
     sv_frame_t frame = v->getFrameForX(e->x());
     if (frame < 0) frame = 0;
-    frame = frame / m_model->getResolution() * m_model->getResolution();
+    frame = frame / model->getResolution() * model->getResolution();
 
     m_editingCommand->remove(m_editingPoint);
     m_editingPoint = m_editingPoint
@@ -553,7 +569,8 @@
 ImageLayer::drawEnd(LayerGeometryProvider *, QMouseEvent *)
 {
 //    SVDEBUG << "ImageLayer::drawEnd(" << e->x() << "," << e->y() << ")" << endl;
-    if (!m_model || !m_editing) return;
+    auto model = ModelById::getAs<ImageModel>(m_model);
+    if (!model || !m_editing) return;
 
     ImageDialog dialog(tr("Select image"), "", "");
 
@@ -586,8 +603,8 @@
     }
 
     Event point = Event(frame).withURI(url);
-    ChangeEventsCommand *command =
-        new ChangeEventsCommand(m_model, "Add Image");
+    auto command =
+        new ChangeEventsCommand<Model>(m_model, "Add Image");
     command->add(point);
     finish(command);
     return true;
@@ -598,7 +615,8 @@
 {
 //    SVDEBUG << "ImageLayer::editStart(" << e->x() << "," << e->y() << ")" << endl;
 
-    if (!m_model) return;
+    auto model = ModelById::getAs<ImageModel>(m_model);
+    if (!model) return;
 
     EventVector points = getLocalPoints(v, e->x(), e->y());
     if (points.empty()) return;
@@ -618,16 +636,17 @@
 void
 ImageLayer::editDrag(LayerGeometryProvider *v, QMouseEvent *e)
 {
-    if (!m_model || !m_editing) return;
+    auto model = ModelById::getAs<ImageModel>(m_model);
+    if (!model || !m_editing) return;
 
     sv_frame_t frameDiff = v->getFrameForX(e->x()) - v->getFrameForX(m_editOrigin.x());
     sv_frame_t frame = m_originalPoint.getFrame() + frameDiff;
 
     if (frame < 0) frame = 0;
-    frame = (frame / m_model->getResolution()) * m_model->getResolution();
+    frame = (frame / model->getResolution()) * model->getResolution();
 
     if (!m_editingCommand) {
-        m_editingCommand = new ChangeEventsCommand(m_model, tr("Move Image"));
+        m_editingCommand = new ChangeEventsCommand<Model>(m_model, tr("Move Image"));
     }
 
     m_editingCommand->remove(m_editingPoint);
@@ -640,7 +659,8 @@
 ImageLayer::editEnd(LayerGeometryProvider *, QMouseEvent *)
 {
 //    SVDEBUG << "ImageLayer::editEnd(" << e->x() << "," << e->y() << ")" << endl;
-    if (!m_model || !m_editing) return;
+    auto model = ModelById::getAs<ImageModel>(m_model);
+    if (!model || !m_editing) return;
 
     if (m_editingCommand) {
         finish(m_editingCommand);
@@ -653,7 +673,8 @@
 bool
 ImageLayer::editOpen(LayerGeometryProvider *v, QMouseEvent *e)
 {
-    if (!m_model) return false;
+    auto model = ModelById::getAs<ImageModel>(m_model);
+    if (!model) return false;
 
     EventVector points = getLocalPoints(v, e->x(), e->y());
     if (points.empty()) return false;
@@ -669,8 +690,8 @@
 
         checkAddSource(dialog.getImage());
 
-        ChangeEventsCommand *command =
-            new ChangeEventsCommand(m_model, tr("Edit Image"));
+        auto command =
+            new ChangeEventsCommand<Model>(m_model, tr("Edit Image"));
         command->remove(*points.begin());
         command->add(points.begin()->
                      withURI(dialog.getImage()).withLabel(dialog.getLabel()));
@@ -683,13 +704,14 @@
 void
 ImageLayer::moveSelection(Selection s, sv_frame_t newStartFrame)
 {
-    if (!m_model) return;
+    auto model = ModelById::getAs<ImageModel>(m_model);
+    if (!model) return;
 
-    ChangeEventsCommand *command =
-        new ChangeEventsCommand(m_model, tr("Drag Selection"));
+    auto command =
+        new ChangeEventsCommand<Model>(m_model, tr("Drag Selection"));
 
     EventVector points =
-        m_model->getEventsStartingWithin(s.getStartFrame(), s.getDuration());
+        model->getEventsStartingWithin(s.getStartFrame(), s.getDuration());
 
     for (Event p: points) {
         command->remove(p);
@@ -704,13 +726,14 @@
 void
 ImageLayer::resizeSelection(Selection s, Selection newSize)
 {
-    if (!m_model) return;
+    auto model = ModelById::getAs<ImageModel>(m_model);
+    if (!model) return;
 
-    ChangeEventsCommand *command =
-        new ChangeEventsCommand(m_model, tr("Resize Selection"));
+    auto command =
+        new ChangeEventsCommand<Model>(m_model, tr("Resize Selection"));
 
     EventVector points =
-        m_model->getEventsStartingWithin(s.getStartFrame(), s.getDuration());
+        model->getEventsStartingWithin(s.getStartFrame(), s.getDuration());
 
     double ratio = double(newSize.getDuration()) / double(s.getDuration());
     double oldStart = double(s.getStartFrame());
@@ -732,13 +755,14 @@
 void
 ImageLayer::deleteSelection(Selection s)
 {
-    if (!m_model) return;
+    auto model = ModelById::getAs<ImageModel>(m_model);
+    if (!model) return;
 
-    ChangeEventsCommand *command =
-        new ChangeEventsCommand(m_model, tr("Delete Selection"));
+    auto command =
+        new ChangeEventsCommand<Model>(m_model, tr("Delete Selection"));
 
     EventVector points =
-        m_model->getEventsStartingWithin(s.getStartFrame(), s.getDuration());
+        model->getEventsStartingWithin(s.getStartFrame(), s.getDuration());
 
     for (Event p: points) {
         command->remove(p);
@@ -750,10 +774,11 @@
 void
 ImageLayer::copy(LayerGeometryProvider *v, Selection s, Clipboard &to)
 {
-    if (!m_model) return;
+    auto model = ModelById::getAs<ImageModel>(m_model);
+    if (!model) return;
 
     EventVector points =
-        m_model->getEventsStartingWithin(s.getStartFrame(), s.getDuration());
+        model->getEventsStartingWithin(s.getStartFrame(), s.getDuration());
 
     for (Event p: points) {
         to.addPoint(p.withReferenceFrame(alignToReference(v, p.getFrame())));
@@ -763,7 +788,8 @@
 bool
 ImageLayer::paste(LayerGeometryProvider *v, const Clipboard &from, sv_frame_t /* frameOffset */, bool /* interactive */)
 {
-    if (!m_model) return false;
+    auto model = ModelById::getAs<ImageModel>(m_model);
+    if (!model) return false;
 
     const EventVector &points = from.getPoints();
 
@@ -786,8 +812,7 @@
         }
     }
 
-    ChangeEventsCommand *command =
-        new ChangeEventsCommand(m_model, tr("Paste"));
+    auto command = new ChangeEventsCommand<Model>(m_model, tr("Paste"));
 
     for (EventVector::const_iterator i = points.begin();
          i != points.end(); ++i) {
@@ -863,7 +888,8 @@
 void
 ImageLayer::checkAddSources()
 {
-    const EventVector &points(m_model->getAllEvents());
+    auto model = ModelById::getAs<ImageModel>(m_model);
+    const EventVector &points(model->getAllEvents());
 
     for (EventVector::const_iterator i = points.begin();
          i != points.end(); ++i) {
--- a/layer/ImageLayer.h	Thu Jun 13 15:35:01 2019 +0100
+++ b/layer/ImageLayer.h	Thu Jun 27 13:16:25 2019 +0100
@@ -43,8 +43,8 @@
     QString getFeatureDescription(LayerGeometryProvider *v, QPoint &) const override;
 
     bool snapToFeatureFrame(LayerGeometryProvider *v, sv_frame_t &frame,
-                                    int &resolution,
-                                    SnapType snap) const override;
+                            int &resolution,
+                            SnapType snap) const override;
 
     void drawStart(LayerGeometryProvider *v, QMouseEvent *) override;
     void drawDrag(LayerGeometryProvider *v, QMouseEvent *) override;
@@ -64,16 +64,16 @@
 
     bool editOpen(LayerGeometryProvider *, QMouseEvent *) override; // on double-click
 
-    const Model *getModel() const override { return m_model; }
-    void setModel(ImageModel *model);
+    ModelId getModel() const override { return m_model; }
+    void setModel(ModelId model); // an ImageModel please
 
     PropertyList getProperties() const override;
     QString getPropertyLabel(const PropertyName &) const override;
     PropertyType getPropertyType(const PropertyName &) const override;
     int getPropertyRangeAndValue(const PropertyName &,
-                                         int *min, int *max, int *deflt) const override;
+                                 int *min, int *max, int *deflt) const override;
     QString getPropertyValueLabel(const PropertyName &,
-                                          int value) const override;
+                                  int value) const override;
     void setProperty(const PropertyName &, int value) override;
 
     ColourSignificance getLayerColourSignificance() const override {
@@ -84,13 +84,13 @@
 
     bool isLayerEditable() const override { return true; }
 
-    int getCompletion(LayerGeometryProvider *) const override { return m_model->getCompletion(); }
+    int getCompletion(LayerGeometryProvider *) const override;
 
     bool getValueExtents(double &min, double &max,
-                                 bool &logarithmic, QString &unit) const override;
+                         bool &logarithmic, QString &unit) const override;
 
     void toXml(QTextStream &stream, QString indent = "",
-                       QString extraAttributes = "") const override;
+               QString extraAttributes = "") const override;
 
     int getVerticalScaleWidth(LayerGeometryProvider *, bool, QPainter &) const override { return 0; }
 
@@ -127,14 +127,14 @@
     QString getLocalFilename(QString img) const;
     void checkAddSource(QString img) const;
 
-    ImageModel *m_model;
+    ModelId m_model; // an ImageModel
     bool m_editing;
     QPoint m_editOrigin;
     Event m_originalPoint;
     Event m_editingPoint;
-    ChangeEventsCommand *m_editingCommand;
+    ChangeEventsCommand<Model> *m_editingCommand;
 
-    void finish(ChangeEventsCommand *command) {
+    void finish(ChangeEventsCommand<Model> *command) {
         Command *c = command->finish();
         if (c) CommandHistory::getInstance()->addCommand(c, false);
     }
--- a/layer/Layer.cpp	Thu Jun 13 15:35:01 2019 +0100
+++ b/layer/Layer.cpp	Thu Jun 27 13:16:25 2019 +0100
@@ -46,18 +46,21 @@
 }
 
 void
-Layer::connectSignals(const Model *model)
+Layer::connectSignals(ModelId modelId)
 {
-    connect(model, SIGNAL(modelChanged()),
+    auto model = ModelById::get(modelId);
+    if (!model) return;
+    
+    connect(model.get(), SIGNAL(modelChanged()),
             this, SIGNAL(modelChanged()));
 
-    connect(model, SIGNAL(modelChangedWithin(sv_frame_t, sv_frame_t)),
+    connect(model.get(), SIGNAL(modelChangedWithin(sv_frame_t, sv_frame_t)),
             this, SIGNAL(modelChangedWithin(sv_frame_t, sv_frame_t)));
 
-    connect(model, SIGNAL(completionChanged()),
+    connect(model.get(), SIGNAL(completionChanged()),
             this, SIGNAL(modelCompletionChanged()));
 
-    connect(model, SIGNAL(alignmentCompletionChanged()),
+    connect(model.get(), SIGNAL(alignmentCompletionChanged()),
             this, SIGNAL(modelAlignmentCompletionChanged()));
 }
 
@@ -84,7 +87,8 @@
         (factory->getLayerType(this));
 
     QString modelName;
-    if (getModel()) modelName = getModel()->objectName();
+    auto model = ModelById::get(getModel());
+    if (model) modelName = model->objectName();
             
     QString text;
     if (modelName != "") {
@@ -107,7 +111,7 @@
 Layer::getPlayParameters() 
 {
 //    cerr << "Layer (" << this << ", " << objectName() << ")::getPlayParameters: model is "<< getModel() << endl;
-    const Model *model = getModel();
+    auto model = ModelById::get(getModel());
     if (model) {
         return PlayParameterRepository::getInstance()->getPlayParameters(model);
     }
@@ -143,10 +147,10 @@
 {
     if (!hasTimeXAxis()) return false;
 
-    const Model *m = getModel();
-    if (!m) return false;
+    auto model = ModelById::get(getModel());
+    if (!model) return false;
 
-    value = double(v->getFrameForX(x)) / m->getSampleRate();
+    value = double(v->getFrameForX(x)) / model->getSampleRate();
     unit = "s";
     return true;
 }
@@ -168,10 +172,9 @@
 sv_frame_t
 Layer::alignToReference(LayerGeometryProvider *v, sv_frame_t frame) const
 {
-    const Model *m = getModel();
-    SVDEBUG << "Layer::alignToReference(" << frame << "): model = " << m << ", alignment reference = " << (m ? m->getAlignmentReference() : nullptr) << endl;
-    if (m && m->getAlignmentReference()) {
-        return m->alignToReference(frame);
+    auto model = ModelById::get(getModel());
+    if (model && !model->getAlignmentReference().isNone()) {
+        return model->alignToReference(frame);
     } else {
         return v->getView()->alignToReference(frame);
     }
@@ -180,10 +183,9 @@
 sv_frame_t
 Layer::alignFromReference(LayerGeometryProvider *v, sv_frame_t frame) const
 {
-    const Model *m = getModel();
-    SVDEBUG << "Layer::alignFromReference(" << frame << "): model = " << m << ", alignment reference = " << (m ? m->getAlignmentReference() : nullptr) << endl;
-    if (m && m->getAlignmentReference()) {
-        return m->alignFromReference(frame);
+    auto model = ModelById::get(getModel());
+    if (model && !model->getAlignmentReference().isNone()) {
+        return model->alignFromReference(frame);
     } else {
         return v->getView()->alignFromReference(frame);
     }
@@ -644,12 +646,16 @@
             .arg(extraAttributes).arg(encodeEntities(m_presentationName));
     }
 
+    int modelExportId = -1;
+    auto model = ModelById::get(getModel());
+    if (model) modelExportId = model->getExportId();
+    
     stream << QString("<layer id=\"%2\" type=\"%1\" name=\"%3\" model=\"%4\" %5")
         .arg(encodeEntities(LayerFactory::getInstance()->getLayerTypeName
                             (LayerFactory::getInstance()->getLayerType(this))))
         .arg(getExportId())
         .arg(encodeEntities(objectName()))
-        .arg(getModel() ? getModel()->getExportId() : -1)
+        .arg(modelExportId)
         .arg(extraAttributes);
 
     if (m_measureRects.empty()) {
@@ -678,12 +684,16 @@
             .arg(extraAttributes).arg(encodeEntities(m_presentationName));
     }
 
+    int modelExportId = -1;
+    auto model = ModelById::get(getModel());
+    if (model) modelExportId = model->getExportId();
+
     stream << QString("<layer id=\"%2\" type=\"%1\" name=\"%3\" model=\"%4\" %5/>\n")
         .arg(encodeEntities(LayerFactory::getInstance()->getLayerTypeName
                             (LayerFactory::getInstance()->getLayerType(this))))
         .arg(getExportId())
         .arg(encodeEntities(objectName()))
-        .arg(getModel() ? getModel()->getExportId() : -1)
+        .arg(modelExportId)
         .arg(extraAttributes);
 }
 
--- a/layer/Layer.h	Thu Jun 13 15:35:01 2019 +0100
+++ b/layer/Layer.h	Thu Jun 27 13:16:25 2019 +0100
@@ -20,6 +20,8 @@
 #include "base/XmlExportable.h"
 #include "base/Selection.h"
 
+#include "data/model/Model.h"
+
 #include "widgets/CommandHistory.h"
 
 #include "system/System.h"
@@ -36,7 +38,6 @@
 #include <iostream>
 
 class ZoomConstraint;
-class Model;
 class QPainter;
 class View;
 class LayerGeometryProvider;
@@ -59,10 +60,10 @@
     Layer();
     virtual ~Layer();
 
-    virtual const Model *getModel() const = 0;
-    Model *getModel() {
-        return const_cast<Model *>(const_cast<const Layer *>(this)->getModel());
-    }
+    /**
+     * Return the ID of the model represented in this layer.
+     */
+    virtual ModelId getModel() const = 0;
     
     /**
      * Return a zoom constraint object defining the supported zoom
@@ -570,7 +571,7 @@
     void verticalZoomChanged();
 
 protected:
-    void connectSignals(const Model *);
+    void connectSignals(ModelId);
 
     virtual sv_frame_t alignToReference(LayerGeometryProvider *v, sv_frame_t frame) const;
     virtual sv_frame_t alignFromReference(LayerGeometryProvider *v, sv_frame_t frame) const;
--- a/layer/SliceableLayer.h	Thu Jun 13 15:35:01 2019 +0100
+++ b/layer/SliceableLayer.h	Thu Jun 27 13:16:25 2019 +0100
@@ -34,14 +34,15 @@
     // spectrogram that was constructed from a DenseTimeValueModel).
     // The SliceableLayer retains ownership of the model, and will
     // emit sliceableModelReplaced if it is about to become invalid.
-    virtual const Model *getSliceableModel() const = 0;
-
+    virtual ModelId getSliceableModel() const = 0;
+/*!!!
 signals:
     // Emitted when a model that was obtained through
     // getSliceableModel is about to be deleted.  If replacement is
     // non-NULL, it may be used instead.
     void sliceableModelReplaced(const Model *modelToBeReplaced,
                                 const Model *replacement);
+*/
 };
 
 #endif