changeset 683:0736beb8b852 by-id

Toward updating Document for ModelById
author Chris Cannam
date Wed, 03 Jul 2019 13:01:26 +0100
parents 161063152ddd
children 5e9b1956b609
files framework/Align.cpp framework/Align.h framework/Document.cpp framework/Document.h
diffstat 4 files changed, 451 insertions(+), 491 deletions(-) [+]
line wrap: on
line diff
--- a/framework/Align.cpp	Tue Jul 02 21:10:25 2019 +0100
+++ b/framework/Align.cpp	Wed Jul 03 13:01:26 2019 +0100
@@ -33,7 +33,7 @@
 #include <QApplication>
 
 bool
-Align::alignModel(Document *doc, Model *ref, Model *other, QString &error)
+Align::alignModel(Document *doc, ModelId ref, ModelId other, QString &error)
 {
     QSettings settings;
     settings.beginGroup("Preferences");
@@ -89,18 +89,14 @@
 }
 
 bool
-Align::alignModelViaTransform(Document *doc, Model *ref, Model *other,
+Align::alignModelViaTransform(Document *doc, ModelId ref, ModelId other,
                               QString &error)
 {
     QMutexLocker locker (&m_mutex);
-    
-    RangeSummarisableTimeValueModel *reference = qobject_cast
-        <RangeSummarisableTimeValueModel *>(ref);
-    
-    RangeSummarisableTimeValueModel *rm = qobject_cast
-        <RangeSummarisableTimeValueModel *>(other);
 
-    if (!reference || !rm) return false; // but this should have been tested already
+    auto reference = ModelById::getAs<RangeSummarisableTimeValueModel>(ref);
+    auto rm = ModelById::getAs<RangeSummarisableTimeValueModel>(other);
+    if (!reference || !rm) return false;
    
     // This involves creating either three or four new models:
     //
@@ -130,6 +126,7 @@
     // is attached to the new model we are aligning, which also takes
     // ownership of it. The only one of these models that we need to
     // delete here is the SparseTimeValueModel [2a].
+    //!!! todo: review the above, especially management of AlignmentModel
     //
     // (We also create a sneaky additional SparseTimeValueModel
     // temporarily so we can attach completion information to it -
@@ -144,21 +141,24 @@
     components.push_back(AggregateWaveModel::ModelChannelSpec
                          (rm->getId(), -1));
 
-    AggregateWaveModel *aggregateModel = new AggregateWaveModel(components);
-    doc->addAggregateModel(aggregateModel);
+    auto aggregateModel = std::make_shared<AggregateWaveModel>(components);
+    ModelById::add(aggregateModel);
+    doc->addAggregateModel(aggregateModel->getId());
 
-    AlignmentModel *alignmentModel =
-        new AlignmentModel(reference, other, nullptr);
+    auto alignmentModel = std::make_shared<AlignmentModel>(ref, other,
+                                                           ModelId());
+    ModelById::add(alignmentModel);
 
     TransformId tdId = getTuningDifferenceTransformName();
 
     if (tdId == "") {
         
-        if (beginTransformDrivenAlignment(aggregateModel, alignmentModel)) {
-            rm->setAlignment(alignmentModel);
+        if (beginTransformDrivenAlignment(aggregateModel->getId(),
+                                          alignmentModel->getId())) {
+            rm->setAlignment(alignmentModel->getId());
         } else {
             error = alignmentModel->getError();
-            delete alignmentModel;
+            ModelById::release(alignmentModel);
             return false;
         }
 
@@ -181,82 +181,76 @@
         ModelTransformerFactory *mtf = ModelTransformerFactory::getInstance();
 
         QString message;
-        Model *transformOutput = mtf->transform(transform, aggregateModel, message);
+        ModelId transformOutput = mtf->transform(transform,
+                                                 aggregateModel->getId(),
+                                                 message);
 
-        SparseTimeValueModel *tdout = dynamic_cast<SparseTimeValueModel *>
-            (transformOutput);
-        
+        auto tdout = ModelById::getAs<SparseTimeValueModel>(transformOutput);
         if (!tdout) {
             SVCERR << "Align::alignModel: ERROR: Failed to create tuning-difference output model (no Tuning Difference plugin?)" << endl;
-            delete tdout;
             error = message;
             return false;
         }
 
-        rm->setAlignment(alignmentModel);
+        rm->setAlignment(alignmentModel->getId());
     
-        connect(tdout, SIGNAL(completionChanged()),
+        connect(tdout.get(), SIGNAL(completionChanged()),
                 this, SLOT(tuningDifferenceCompletionChanged()));
 
         TuningDiffRec rec;
-        rec.input = aggregateModel;
-        rec.alignment = alignmentModel;
-
-        connect(aggregateModel, SIGNAL(aboutToBeDeleted()),
-                this, SLOT(aggregateModelAboutToBeDeleted()));
+        rec.input = aggregateModel->getId();
+        rec.alignment = alignmentModel->getId();
         
         // This model exists only so that the AlignmentModel can get a
         // completion value from somewhere while the tuning difference
         // calculation is going on
-        rec.preparatory = new SparseTimeValueModel
-            (aggregateModel->getSampleRate(), 1);;
-        rec.preparatory->setCompletion(0);
+        auto preparatoryModel = std::make_shared<SparseTimeValueModel>
+            (aggregateModel->getSampleRate(), 1);
+        ModelById::add(preparatoryModel);
+        preparatoryModel->setCompletion(0);
+        rec.preparatory = preparatoryModel->getId();
         alignmentModel->setPathFrom(rec.preparatory);
         
-        m_pendingTuningDiffs[tdout] = rec;
+        m_pendingTuningDiffs[transformOutput] = rec;
     }
 
     return true;
 }
 
 void
-Align::aggregateModelAboutToBeDeleted()
-{
-    SVCERR << "Align::aggregateModelAboutToBeDeleted" << endl;
-
-    QObject *s = sender();
-    AggregateWaveModel *awm = qobject_cast<AggregateWaveModel *>(s);
-    if (!awm) return;
-    QMutexLocker locker(&m_mutex);
-
-    SVCERR << "Align::aggregateModelAboutToBeDeleted: awm = " << awm
-           << endl;
-
-    for (const auto &p : m_pendingTuningDiffs) {
-        if (p.second.input == awm) {
-            SVCERR << "we have a record of this, getting rid of it" << endl;
-            m_pendingTuningDiffs.erase(p.first);
-            return;
-        }
-    }
-}
-
-void
 Align::tuningDifferenceCompletionChanged()
 {
     QMutexLocker locker (&m_mutex);
-    
-    SparseTimeValueModel *td = qobject_cast<SparseTimeValueModel *>(sender());
-    if (!td) return;
 
-    if (m_pendingTuningDiffs.find(td) == m_pendingTuningDiffs.end()) {
-        SVCERR << "ERROR: Align::tuningDifferenceCompletionChanged: Model "
-               << td << " not found in pending tuning diff map!" << endl;
+    ModelId tdId;
+    if (Model *modelPtr = qobject_cast<Model *>(sender())) {
+        tdId = modelPtr->getId();
+    } else {
         return;
     }
 
-    TuningDiffRec rec = m_pendingTuningDiffs[td];
+    if (m_pendingTuningDiffs.find(tdId) == m_pendingTuningDiffs.end()) {
+        SVCERR << "ERROR: Align::tuningDifferenceCompletionChanged: Model "
+               << tdId << " not found in pending tuning diff map!" << endl;
+        return;
+    }
 
+    auto td = ModelById::getAs<SparseTimeValueModel>(tdId);
+    if (!td) {
+        SVCERR << "WARNING: Align::tuningDifferenceCompletionChanged: Model "
+               << tdId << " not known as SparseTimeValueModel" << endl;
+        return;
+    }
+
+    TuningDiffRec rec = m_pendingTuningDiffs[tdId];
+
+    auto alignment = ModelById::getAs<AlignmentModel>(rec.alignment);
+    if (!alignment) {
+        SVCERR << "WARNING: Align::tuningDifferenceCompletionChanged:"
+               << "alignment model has disappeared" << endl;
+        return;
+    }
+    
     int completion = 0;
     bool done = td->isReady(&completion);
 
@@ -269,7 +263,11 @@
         // calculating the actual path in the following phase
         int clamped = (completion == 100 ? 99 : completion);
 //        SVCERR << "Align::tuningDifferenceCompletionChanged: setting rec.preparatory completion to " << clamped << endl;
-        rec.preparatory->setCompletion(clamped);
+        auto preparatory = ModelById::getAs<SparseTimeValueModel>
+            (rec.preparatory);
+        if (preparatory) {
+            preparatory->setCompletion(clamped);
+        }
         return;
     }
 
@@ -282,25 +280,32 @@
         SVCERR << "Align::tuningDifferenceCompletionChanged: No tuning frequency reported" << endl;
     }    
 
-    m_pendingTuningDiffs.erase(td);
-    td->aboutToDelete();
-    delete td;
-
-    rec.alignment->setPathFrom(nullptr);
+    m_pendingTuningDiffs.erase(tdId);
+    ModelById::release(tdId);
+    
+    alignment->setPathFrom({});
     
     beginTransformDrivenAlignment
         (rec.input, rec.alignment, tuningFrequency);
 }
 
 bool
-Align::beginTransformDrivenAlignment(AggregateWaveModel *aggregateModel,
-                                     AlignmentModel *alignmentModel,
+Align::beginTransformDrivenAlignment(ModelId aggregateModelId,
+                                     ModelId alignmentModelId,
                                      float tuningFrequency)
 {
     TransformId id = getAlignmentTransformName();
     
     TransformFactory *tf = TransformFactory::getInstance();
 
+    auto aggregateModel = ModelById::getAs<AggregateWaveModel>(aggregateModelId);
+    auto alignmentModel = ModelById::getAs<AlignmentModel>(alignmentModelId);
+
+    if (!aggregateModel || !alignmentModel) {
+        SVCERR << "Align::alignModel: ERROR: One or other of the aggregate & alignment models has disappeared" << endl;
+        return false;
+    }
+    
     Transform transform = tf->getDefaultTransformFor
         (id, aggregateModel->getSampleRate());
 
@@ -317,32 +322,31 @@
     ModelTransformerFactory *mtf = ModelTransformerFactory::getInstance();
 
     QString message;
-    Model *transformOutput = mtf->transform
-        (transform, aggregateModel, message);
+    ModelId transformOutput = mtf->transform
+        (transform, aggregateModelId, message);
 
-    if (!transformOutput) {
+    if (transformOutput.isNone()) {
         transform.setStepSize(0);
         transformOutput = mtf->transform
-            (transform, aggregateModel, message);
+            (transform, aggregateModelId, message);
     }
 
-    SparseTimeValueModel *path = dynamic_cast<SparseTimeValueModel *>
-        (transformOutput);
+    auto path = ModelById::getAs<SparseTimeValueModel>(transformOutput);
 
     //!!! callers will need to be updated to get error from
     //!!! alignment model after initial call
         
     if (!path) {
         SVCERR << "Align::alignModel: ERROR: Failed to create alignment path (no MATCH plugin?)" << endl;
-        delete transformOutput;
+        ModelById::release(transformOutput);
         alignmentModel->setError(message);
         return false;
     }
 
     path->setCompletion(0);
-    alignmentModel->setPathFrom(path);
+    alignmentModel->setPathFrom(transformOutput); //!!! who releases transformOutput?
 
-    connect(alignmentModel, SIGNAL(completionChanged()),
+    connect(alignmentModel.get(), SIGNAL(completionChanged()),
             this, SLOT(alignmentCompletionChanged()));
 
     return true;
@@ -352,28 +356,27 @@
 Align::alignmentCompletionChanged()
 {
     QMutexLocker locker (&m_mutex);
-    
-    AlignmentModel *am = qobject_cast<AlignmentModel *>(sender());
-    if (!am) return;
-    if (am->isReady()) {
-        disconnect(am, SIGNAL(completionChanged()),
-                   this, SLOT(alignmentCompletionChanged()));
-        emit alignmentComplete(am);
+
+    if (AlignmentModel *amPtr = qobject_cast<AlignmentModel *>(sender())) {
+
+        auto am = ModelById::getAs<AlignmentModel>(amPtr->getId());
+        if (am && am->isReady()) {
+            disconnect(am.get(), SIGNAL(completionChanged()),
+                       this, SLOT(alignmentCompletionChanged()));
+            emit alignmentComplete(am->getId());
+        }
     }
 }
 
 bool
-Align::alignModelViaProgram(Document *, Model *ref, Model *other,
+Align::alignModelViaProgram(Document *, ModelId ref, ModelId other,
                             QString program, QString &error)
 {
     QMutexLocker locker (&m_mutex);
-    
-    WaveFileModel *reference = qobject_cast<WaveFileModel *>(ref);
-    WaveFileModel *rm = qobject_cast<WaveFileModel *>(other);
 
-    if (!reference || !rm) {
-        return false; // but this should have been tested already
-    }
+    auto reference = ModelById::getAs<RangeSummarisableTimeValueModel>(ref);
+    auto rm = ModelById::getAs<RangeSummarisableTimeValueModel>(other);
+    if (!reference || !rm) return false;
 
     while (!reference->isReady(nullptr) || !rm->isReady(nullptr)) {
         qApp->processEvents();
@@ -383,8 +386,8 @@
     // model's audio file and the new model's audio file. It returns
     // the path in CSV form through stdout.
 
-    ReadOnlyWaveFileModel *roref = qobject_cast<ReadOnlyWaveFileModel *>(reference);
-    ReadOnlyWaveFileModel *rorm = qobject_cast<ReadOnlyWaveFileModel *>(rm);
+    auto roref = ModelById::getAs<ReadOnlyWaveFileModel>(ref);
+    auto rorm = ModelById::getAs<ReadOnlyWaveFileModel>(other);
     if (!roref || !rorm) {
         SVCERR << "ERROR: Align::alignModelViaProgram: Can't align non-read-only models via program (no local filename available)" << endl;
         return false;
@@ -398,9 +401,10 @@
         return false;
     }
 
-    AlignmentModel *alignmentModel =
-        new AlignmentModel(reference, other, nullptr);
-    rm->setAlignment(alignmentModel);
+    auto alignmentModel = std::make_shared<AlignmentModel>(ref, other,
+                                                           ModelId());
+    ModelById::add(alignmentModel);
+    rm->setAlignment(alignmentModel->getId());
 
     QProcess *process = new QProcess;
     QStringList args;
@@ -409,7 +413,7 @@
     connect(process, SIGNAL(finished(int, QProcess::ExitStatus)),
             this, SLOT(alignmentProgramFinished(int, QProcess::ExitStatus)));
 
-    m_pendingProcesses[process] = alignmentModel;
+    m_pendingProcesses[process] = alignmentModel->getId();
     process->start(program, args);
 
     bool success = process->waitForStarted();
@@ -419,7 +423,8 @@
                << endl;
         error = "Alignment program could not be started";
         m_pendingProcesses.erase(process);
-        rm->setAlignment(nullptr); // deletes alignmentModel as well
+        //!!! who releases alignmentModel? does this? review
+        rm->setAlignment({});
         delete process;
     }
 
@@ -441,7 +446,9 @@
         return;
     }
 
-    AlignmentModel *alignmentModel = m_pendingProcesses[process];
+    ModelId alignmentModelId = m_pendingProcesses[process];
+    auto alignmentModel = ModelById::getAs<AlignmentModel>(alignmentModelId);
+    if (!alignmentModel) return;
     
     if (exitCode == 0 && status == 0) {
 
@@ -471,6 +478,8 @@
             goto done;
         }
 
+        //!!! to use ById?
+        
         Model *csvOutput = reader.load();
 
         SparseTimeValueModel *path = qobject_cast<SparseTimeValueModel *>(csvOutput);
@@ -479,23 +488,26 @@
                    << endl;
             alignmentModel->setError
                 ("Output of program did not produce sparse time-value model");
+            delete csvOutput;
             goto done;
         }
-
+                       
         if (path->isEmpty()) {
             SVCERR << "ERROR: Align::alignmentProgramFinished: Output contained no mappings"
                    << endl;
             alignmentModel->setError
                 ("Output of alignment program contained no mappings");
+            delete path;
             goto done;
         }
 
         SVCERR << "Align::alignmentProgramFinished: Setting alignment path ("
              << path->getEventCount() << " point(s))" << endl;
 
-        alignmentModel->setPathFrom(path);
+        ModelById::add(std::shared_ptr<SparseTimeValueModel>(path));
+        alignmentModel->setPathFrom(path->getId());
 
-        emit alignmentComplete(alignmentModel);
+        emit alignmentComplete(alignmentModelId);
         
     } else {
         SVCERR << "ERROR: Align::alignmentProgramFinished: Aligner program "
--- a/framework/Align.h	Tue Jul 02 21:10:25 2019 +0100
+++ b/framework/Align.h	Wed Jul 03 13:01:26 2019 +0100
@@ -4,7 +4,6 @@
     Sonic Visualiser
     An audio file viewer and annotation editor.
     Centre for Digital Music, Queen Mary, University of London.
-    This file copyright 2006 Chris Cannam and QMUL.
     
     This program is free software; you can redistribute it and/or
     modify it under the terms of the GNU General Public License as
@@ -22,7 +21,8 @@
 #include <QMutex>
 #include <set>
 
-class Model;
+#include "data/model/Model.h"
+
 class AlignmentModel;
 class SparseTimeValueModel;
 class AggregateWaveModel;
@@ -63,18 +63,18 @@
      * lifespan.
      */
     bool alignModel(Document *doc,
-                    Model *reference,
-                    Model *toAlign,
+                    ModelId reference,
+                    ModelId toAlign,
                     QString &error);
     
     bool alignModelViaTransform(Document *doc,
-                                Model *reference,
-                                Model *toAlign,
+                                ModelId reference,
+                                ModelId toAlign,
                                 QString &error);
 
     bool alignModelViaProgram(Document *doc,
-                              Model *reference,
-                              Model *toAlign,
+                              ModelId reference,
+                              ModelId toAlign,
                               QString program,
                               QString &error);
 
@@ -90,35 +90,36 @@
      * reference and other models can be queried from the alignment
      * model.
      */
-    void alignmentComplete(AlignmentModel *alignment);
+    void alignmentComplete(ModelId alignmentModel); // an AlignmentModel
 
 private slots:
     void alignmentCompletionChanged();
     void tuningDifferenceCompletionChanged();
     void alignmentProgramFinished(int, QProcess::ExitStatus);
-    void aggregateModelAboutToBeDeleted();
     
 private:
     static QString getAlignmentTransformName();
     static QString getTuningDifferenceTransformName();
 
-    bool beginTransformDrivenAlignment(AggregateWaveModel *,
-                                       AlignmentModel *,
+    bool beginTransformDrivenAlignment(ModelId, // an AggregateWaveModel
+                                       ModelId, // an AlignmentModel
                                        float tuningFrequency = 0.f);
 
     QMutex m_mutex;
 
     struct TuningDiffRec {
-        AggregateWaveModel *input;
-        AlignmentModel *alignment;
-        SparseTimeValueModel *preparatory;
+        ModelId input; // an AggregateWaveModel
+        ModelId alignment; // an AlignmentModel
+        ModelId preparatory; // a SparseTimeValueModel
     };
 
-    // tuning-difference output model -> data needed for subsequent alignment
-    std::map<SparseTimeValueModel *, TuningDiffRec> m_pendingTuningDiffs;
+    // tuning-difference output model (a SparseTimeValueModel) -> data
+    // needed for subsequent alignment
+    std::map<ModelId, TuningDiffRec> m_pendingTuningDiffs;
 
-    // external alignment subprocess -> model into which to stuff the results
-    std::map<QProcess *, AlignmentModel *> m_pendingProcesses;
+    // external alignment subprocess -> model into which to stuff the
+    // results (an AlignmentModel)
+    std::map<QProcess *, ModelId> m_pendingProcesses;
 };
 
 #endif
--- a/framework/Document.cpp	Tue Jul 02 21:10:25 2019 +0100
+++ b/framework/Document.cpp	Wed Jul 03 13:01:26 2019 +0100
@@ -48,23 +48,17 @@
 //!!! still need to handle command history, documentRestored/documentModified
 
 Document::Document() :
-    m_mainModel(nullptr),
     m_autoAlignment(false),
     m_align(new Align()),
     m_isIncomplete(false)
 {
-    connect(this,
-            SIGNAL(modelAboutToBeDeleted(Model *)),
-            ModelTransformerFactory::getInstance(),
-            SLOT(modelAboutToBeDeleted(Model *)));
-
     connect(ModelTransformerFactory::getInstance(),
             SIGNAL(transformFailed(QString, QString)),
             this,
             SIGNAL(modelGenerationFailed(QString, QString)));
 
-    connect(m_align, SIGNAL(alignmentComplete(AlignmentModel *)),
-            this, SIGNAL(alignmentComplete(AlignmentModel *)));
+    connect(m_align, SIGNAL(alignmentComplete(ModelId)),
+            this, SIGNAL(alignmentComplete(ModelId)));
 }
 
 Document::~Document()
@@ -85,37 +79,17 @@
         deleteLayer(*m_layers.begin(), true);
     }
 
-    if (!m_models.empty()) {
-        SVDEBUG << "Document::~Document: WARNING: " 
-                  << m_models.size() << " model(s) still remain -- "
-                  << "should have been garbage collected when deleting layers"
-                  << endl;
-        for (ModelRecord &rec: m_models) {
-            Model *model = rec.model;
-            if (model == m_mainModel) {
-                // just in case!
-                SVDEBUG << "Document::~Document: WARNING: Main model is also"
-                          << " in models list!" << endl;
-            } else if (model) {
-                model->aboutToDelete();
-                emit modelAboutToBeDeleted(model);
-                delete model;
-            }
-        }
-        m_models.clear();
+    for (auto mr: m_models) {
+        ModelById::release(mr.first);
+    }
+    for (auto m: m_aggregateModels) {
+        ModelById::release(m);
     }
 
-#ifdef DEBUG_DOCUMENT
-    SVDEBUG << "Document::~Document: About to get rid of main model"
-              << endl;
-#endif
-    if (m_mainModel) {
-        m_mainModel->aboutToDelete();
-        emit modelAboutToBeDeleted(m_mainModel);
-    }
-
-    emit mainModelChanged(nullptr);
-    delete m_mainModel;
+    auto mainModel = m_mainModel;
+    m_mainModel = {};
+    emit mainModelChanged(m_mainModel);
+    ModelById::release(mainModel);
 }
 
 Layer *
@@ -148,10 +122,10 @@
 }
 
 Layer *
-Document::createImportedLayer(Model *model)
+Document::createImportedLayer(ModelId modelId)
 {
     LayerFactory::LayerTypeSet types =
-        LayerFactory::getInstance()->getValidLayerTypes(model);
+        LayerFactory::getInstance()->getValidLayerTypes(modelId);
 
     if (types.empty()) {
         SVCERR << "WARNING: Document::importLayer: no valid display layer for model" << endl;
@@ -166,8 +140,8 @@
 
     newLayer->setObjectName(getUniqueLayerName(newLayer->objectName()));
 
-    addImportedModel(model);
-    setModel(newLayer, model);
+    addImportedModel(modelId);
+    setModel(newLayer, modelId);
 
     //!!! and all channels
     setChannel(newLayer, -1);
@@ -186,20 +160,20 @@
 Layer *
 Document::createEmptyLayer(LayerFactory::LayerType type)
 {
-    if (!m_mainModel) return nullptr;
+    if (m_mainModel.isNone()) return nullptr;
 
-    Model *newModel =
+    auto newModel =
         LayerFactory::getInstance()->createEmptyModel(type, m_mainModel);
     if (!newModel) return nullptr;
-
+    
     Layer *newLayer = createLayer(type);
     if (!newLayer) {
-        delete newModel;
         return nullptr;
     }
 
-    addImportedModel(newModel);
-    setModel(newLayer, newModel);
+    ModelById::add(newModel);
+    addImportedModel(newModel->getId());
+    setModel(newLayer, newModel->getId());
 
     return newLayer;
 }
@@ -234,7 +208,8 @@
                               const ModelTransformer::Input &input)
 {
     QString message;
-    vector<Model *> newModels = addDerivedModels(transforms, input, message, nullptr);
+    vector<ModelId> newModels =
+        addDerivedModels(transforms, input, message, nullptr);
 
     if (newModels.empty()) {
         //!!! This identifier may be wrong!
@@ -246,7 +221,7 @@
     }
 
     QStringList names;
-    for (int i = 0; i < (int)newModels.size(); ++i) {
+    for (int i = 0; in_range_for(newModels, i); ++i) {
         names.push_back(getUniqueLayerName
                         (TransformFactory::getInstance()->
                          getTransformFriendlyName
@@ -275,13 +250,13 @@
     }
 
     void
-    moreModelsAvailable(vector<Model *> models) override {
+    moreModelsAvailable(vector<ModelId> models) override {
         SVDEBUG << "AdditionalModelConverter::moreModelsAvailable: " << models.size() << " model(s)" << endl;
         // We can't automatically regenerate the additional models on
-        // reload -- we should delete them instead
+        // reload - so they go in m_additionalModels instead of m_models
         QStringList names;
-        foreach (Model *model, models) {
-            m_doc->addAdditionalModel(model);
+        foreach (ModelId modelId, models) {
+            m_doc->addAdditionalModel(modelId);
             names.push_back(QString());
         }
         vector<Layer *> layers = m_doc->createLayersForDerivedModels
@@ -298,12 +273,15 @@
     }
 
     void cancel() {
+/*!!! todo: restore
         foreach (Layer *layer, m_primary) {
-            Model *model = layer->getModel();
+            ModelId model = layer->getModel();
+            //!!! todo: restore this behaviour
             if (model) {
                 model->abandon();
             }
         }
+*/
     }
 
 private:
@@ -321,11 +299,11 @@
 
     AdditionalModelConverter *amc = new AdditionalModelConverter(this, handler);
     
-    vector<Model *> newModels = addDerivedModels
+    vector<ModelId> newModels = addDerivedModels
         (transforms, input, message, amc);
 
     QStringList names;
-    for (int i = 0; i < (int)newModels.size(); ++i) {
+    for (int i = 0; in_range_for(newModels, i); ++i) {
         names.push_back(getUniqueLayerName
                         (TransformFactory::getInstance()->
                          getTransformFriendlyName
@@ -356,29 +334,30 @@
 }
 
 vector<Layer *>
-Document::createLayersForDerivedModels(vector<Model *> newModels, 
+Document::createLayersForDerivedModels(vector<ModelId> newModels, 
                                        QStringList names)
 {
     vector<Layer *> layers;
     
-    for (int i = 0; i < (int)newModels.size(); ++i) {
+    for (int i = 0; in_range_for(newModels, i); ++i) {
 
-        Model *newModel = newModels[i];
+        ModelId newModelId = newModels[i];
 
         LayerFactory::LayerTypeSet types =
-            LayerFactory::getInstance()->getValidLayerTypes(newModel);
+            LayerFactory::getInstance()->getValidLayerTypes(newModelId);
 
         if (types.empty()) {
             SVCERR << "WARNING: Document::createLayerForTransformer: no valid display layer for output of transform " << names[i] << endl;
             //!!! inadequate cleanup:
-            deleteModelFromList(newModel);
+            //!!! review: ->
+//            deleteModelFromList(newModel);
             return vector<Layer *>();
         }
 
         //!!! for now, just use the first suitable layer type
 
         Layer *newLayer = createLayer(*types.begin());
-        setModel(newLayer, newModel);
+        setModel(newLayer, newModelId);
 
         //!!! We need to clone the model when adding the layer, so that it
         //can be edited without affecting other layers that are based on
@@ -407,13 +386,14 @@
 }
 
 void
-Document::setMainModel(WaveFileModel *model)
+Document::setMainModel(ModelId modelId)
 {
-    Model *oldMainModel = m_mainModel;
-    m_mainModel = model;
+    ModelId oldMainModel = m_mainModel;
+    m_mainModel = modelId;
     
     emit modelAdded(m_mainModel);
-    if (model) {
+    
+    if (auto model = ModelById::get(modelId)) {
         emit activity(tr("Set main model to %1").arg(model->objectName()));
     } else {
         emit activity(tr("Clear main model"));
@@ -433,7 +413,7 @@
               << m_layers.size() << " layers" << endl;
     SVDEBUG << "Models now: ";
     for (const auto &r: m_models) {
-        SVDEBUG << r.model << " ";
+        SVDEBUG << r.first << " ";
     }
     SVDEBUG << endl;
     SVDEBUG << "Old main model: " << oldMainModel << endl;
@@ -441,15 +421,14 @@
 
     for (Layer *layer: m_layers) {
 
-        Model *model = layer->getModel();
+        ModelId modelId = layer->getModel();
 
 #ifdef DEBUG_DOCUMENT
         SVDEBUG << "Document::setMainModel: inspecting model "
-                << (model ? model->objectName(): "(null)") << " in layer "
-                << layer->objectName() << endl;
+                << modelId << " in layer " << layer->objectName() << endl;
 #endif
 
-        if (model == oldMainModel) {
+        if (modelId == oldMainModel) {
 #ifdef DEBUG_DOCUMENT
             SVDEBUG << "... it uses the old main model, replacing" << endl;
 #endif
@@ -457,7 +436,7 @@
             continue;
         }
 
-        if (!model) {
+        if (modelId.isNone()) {
             SVCERR << "WARNING: Document::setMainModel: Null model in layer "
                    << layer << endl;
             // get rid of this hideous degenerate
@@ -465,19 +444,17 @@
             continue;
         }
 
-        auto mitr = findModelInList(model);
-        
-        if (mitr == m_models.end()) {
+        if (m_models.find(modelId) == m_models.end()) {
             SVCERR << "WARNING: Document::setMainModel: Unknown model "
-                   << model << " in layer " << layer << endl;
+                   << modelId << " in layer " << layer << endl;
             // and this one
             obsoleteLayers.push_back(layer);
             continue;
         }
 
-        ModelRecord record = *mitr;
+        ModelRecord record = m_models[modelId];
         
-        if (record.source && (record.source == oldMainModel)) {
+        if (!record.source.isNone() && (record.source == oldMainModel)) {
 
 #ifdef DEBUG_DOCUMENT
             SVDEBUG << "... it uses a model derived from the old main model, regenerating" << endl;
@@ -493,13 +470,13 @@
             //the main model has changed.
 
             QString message;
-            Model *replacementModel =
+            ModelId replacementModel =
                 addDerivedModel(transform,
                                 ModelTransformer::Input
                                 (m_mainModel, record.channel),
                                 message);
             
-            if (!replacementModel) {
+            if (replacementModel.isNone()) {
                 SVCERR << "WARNING: Document::setMainModel: Failed to regenerate model for transform \""
                        << transformId << "\"" << " in layer " << layer << endl;
                 if (failedTransformers.find(transformId)
@@ -517,15 +494,12 @@
                                                   message);
                 }
 #ifdef DEBUG_DOCUMENT
-                SVDEBUG << "Replacing model " << model << " (type "
-                        << typeid(*model).name() << ") with model "
-                        << replacementModel << " (type "
-                        << typeid(*replacementModel).name() << ") in layer "
+                SVDEBUG << "Replacing model " << modelId << ") with model "
+                        << replacementModel << ") in layer "
                         << layer << " (name " << layer->objectName() << ")"
                         << endl;
 
-                RangeSummarisableTimeValueModel *rm =
-                    dynamic_cast<RangeSummarisableTimeValueModel *>(replacementModel);
+                auto rm = ModelById::getAs<RangeSummarisableTimeValueModel>(replacementModel);
                 if (rm) {
                     SVDEBUG << "new model has " << rm->getChannelCount() << " channels " << endl;
                 } else {
@@ -541,41 +515,36 @@
         deleteLayer(obsoleteLayers[k], true);
     }
 
-    std::set<Model *> additionalModels;
+    std::set<ModelId> additionalModels;
     for (const auto &rec : m_models) {
-        if (rec.additional) {
-            additionalModels.insert(rec.model);
+        if (rec.second.additional) {
+            additionalModels.insert(rec.first);
         }
     }
-    for (Model *a: additionalModels) {
-        deleteModelFromList(a);
+    for (ModelId a: additionalModels) {
+        m_models.erase(a);
     }
 
     for (const auto &rec : m_models) {
 
-        Model *m = rec.model;
+        auto m = ModelById::get(rec.first);
+        if (!m) continue;
 
 #ifdef DEBUG_DOCUMENT
-        SVDEBUG << "considering alignment for model " << m << " (name \""
-                  << m->objectName() << "\")" << endl;
+        SVDEBUG << "considering alignment for model " << rec.first << endl;
 #endif
 
         if (m_autoAlignment) {
 
-            alignModel(m);
+            alignModel(rec.first);
 
-        } else if (oldMainModel &&
+        } else if (!oldMainModel.isNone() && 
                    (m->getAlignmentReference() == oldMainModel)) {
 
-            alignModel(m);
+            alignModel(rec.first);
         }
     }
 
-    if (oldMainModel) {
-        oldMainModel->aboutToDelete();
-        emit modelAboutToBeDeleted(oldMainModel);
-    }
-
     if (m_autoAlignment) {
         SVDEBUG << "Document::setMainModel: auto-alignment is on, aligning model if possible" << endl;
         alignModel(m_mainModel);
@@ -585,45 +554,41 @@
 
     emit mainModelChanged(m_mainModel);
 
-    delete oldMainModel;
+    ModelById::release(oldMainModel);
 }
 
 void
 Document::addAlreadyDerivedModel(const Transform &transform,
                                  const ModelTransformer::Input &input,
-                                 Model *outputModelToAdd)
+                                 ModelId outputModelToAdd)
 {
-    if (findModelInList(outputModelToAdd) != m_models.end()) {
+    if (m_models.find(outputModelToAdd) != m_models.end()) {
         SVCERR << "WARNING: Document::addAlreadyDerivedModel: Model already added"
-                  << endl;
+               << endl;
         return;
     }
-
+    
 #ifdef DEBUG_DOCUMENT
-    if (input.getModel()) {
-        SVDEBUG << "Document::addAlreadyDerivedModel: source is " << input.getModel() << " \"" << input.getModel()->objectName() << "\"" << endl;
-    } else {
-        SVDEBUG << "Document::addAlreadyDerivedModel: source is " << input.getModel() << endl;
-    }
+    SVDEBUG << "Document::addAlreadyDerivedModel: source is " << input.getModel() << endl;
 #endif
 
     ModelRecord rec;
-    rec.model = outputModelToAdd;
     rec.source = input.getModel();
     rec.channel = input.getChannel();
     rec.transform = transform;
     rec.additional = false;
-    rec.refcount = 0;
 
-    outputModelToAdd->setSourceModel(input.getModel());
+    if (auto m = ModelById::get(outputModelToAdd)) {
+        m->setSourceModel(input.getModel());
+    }
 
-    m_models.push_back(rec);
+    m_models[outputModelToAdd] = rec;
 
 #ifdef DEBUG_DOCUMENT
     SVDEBUG << "Document::addAlreadyDerivedModel: Added model " << outputModelToAdd << endl;
     SVDEBUG << "Models now: ";
     for (const auto &rec : m_models) {
-        SVDEBUG << rec.model << " ";
+        SVDEBUG << rec.first << " ";
     } 
     SVDEBUG << endl;
 #endif
@@ -631,131 +596,121 @@
     emit modelAdded(outputModelToAdd);
 }
 
-
 void
-Document::addImportedModel(Model *model)
+Document::addImportedModel(ModelId modelId)
 {
-    if (findModelInList(model) != m_models.end()) {
+    if (m_models.find(modelId) != m_models.end()) {
         SVCERR << "WARNING: Document::addImportedModel: Model already added"
-                  << endl;
+               << endl;
         return;
     }
 
     ModelRecord rec;
-    rec.model = model;
-    rec.source = nullptr;
+    rec.source = {};
     rec.channel = 0;
-    rec.refcount = 0;
     rec.additional = false;
 
-    m_models.push_back(rec);
+    m_models[modelId] = rec;
 
 #ifdef DEBUG_DOCUMENT
-    SVDEBUG << "Document::addImportedModel: Added model " << model << endl;
+    SVDEBUG << "Document::addImportedModel: Added model " << modelId << endl;
     SVDEBUG << "Models now: ";
     for (const auto &rec : m_models) {
-        SVDEBUG << rec.model << " ";
+        SVDEBUG << rec.first << " ";
     } 
     SVDEBUG << endl;
 #endif
 
     if (m_autoAlignment) {
         SVDEBUG << "Document::addImportedModel: auto-alignment is on, aligning model if possible" << endl;
-        alignModel(model);
+        alignModel(modelId);
     } else {
         SVDEBUG << "Document(" << this << "): addImportedModel: auto-alignment is off" << endl;
     }
 
-    emit modelAdded(model);
+    emit modelAdded(modelId);
 }
 
 void
-Document::addAdditionalModel(Model *model)
+Document::addAdditionalModel(ModelId modelId)
 {
-    if (findModelInList(model) != m_models.end()) {
+    if (m_models.find(modelId) != m_models.end()) {
         SVCERR << "WARNING: Document::addAdditionalModel: Model already added"
-                  << endl;
+               << endl;
         return;
     }
 
     ModelRecord rec;
-    rec.model = model;
-    rec.source = nullptr;
+    rec.source = {};
     rec.channel = 0;
-    rec.refcount = 0;
     rec.additional = true;
 
-    m_models.push_back(rec);
+    m_models[modelId] = rec;
 
 #ifdef DEBUG_DOCUMENT
-    SVDEBUG << "Document::addAdditionalModel: Added model " << model << endl;
+    SVDEBUG << "Document::addAdditionalModel: Added model " << modelId << endl;
     SVDEBUG << "Models now: ";
     for (const auto &rec : m_models) {
-        SVDEBUG << rec.model << " ";
+        SVDEBUG << rec.first << " ";
     } 
     SVDEBUG << endl;
 #endif
 
     if (m_autoAlignment) {
         SVDEBUG << "Document::addAdditionalModel: auto-alignment is on, aligning model if possible" << endl;
-        alignModel(model);
+        alignModel(modelId);
     }
 
-    emit modelAdded(model);
+    emit modelAdded(modelId);
 }
 
 void
-Document::addAggregateModel(AggregateWaveModel *model)
+Document::addAggregateModel(ModelId modelId)
 {
-    connect(model, SIGNAL(modelInvalidated()),
-            this, SLOT(aggregateModelInvalidated()));
-    m_aggregateModels.insert(model);
-    SVDEBUG << "Document::addAggregateModel(" << model << ")" << endl;
+    m_aggregateModels.insert(modelId);
+    SVDEBUG << "Document::addAggregateModel(" << modelId << ")" << endl;
 }
 
-void
-Document::aggregateModelInvalidated()
-{
-    QObject *s = sender();
-    AggregateWaveModel *aggregate = qobject_cast<AggregateWaveModel *>(s);
-    SVDEBUG << "Document::aggregateModelInvalidated(" << aggregate << ")" << endl;
-    if (aggregate) releaseModel(aggregate);
-}
-
-Model *
+ModelId
 Document::addDerivedModel(const Transform &transform,
                           const ModelTransformer::Input &input,
                           QString &message)
 {
     for (auto &rec : m_models) {
-        if (rec.transform == transform &&
-            rec.source == input.getModel() && 
-            rec.channel == input.getChannel()) {
+        if (rec.second.transform == transform &&
+            rec.second.source == input.getModel() && 
+            rec.second.channel == input.getChannel()) {
             SVDEBUG << "derived model taken from map " << endl;
-            return rec.model;
+            return rec.first;
         }
     }
 
     Transforms tt;
     tt.push_back(transform);
-    vector<Model *> mm = addDerivedModels(tt, input, message, nullptr);
-    if (mm.empty()) return nullptr;
+    vector<ModelId> mm = addDerivedModels(tt, input, message, nullptr);
+    if (mm.empty()) return {};
     else return mm[0];
 }
 
-vector<Model *>
+vector<ModelId>
 Document::addDerivedModels(const Transforms &transforms,
                            const ModelTransformer::Input &input,
                            QString &message,
                            AdditionalModelConverter *amc)
 {
-    vector<Model *> mm = 
+    vector<ModelId> mm = 
         ModelTransformerFactory::getInstance()->transformMultiple
         (transforms, input, message, amc);
 
-    for (int j = 0; j < (int)mm.size(); ++j) {
+    for (int j = 0; in_range_for(mm, j); ++j) {
 
-        Model *model = mm[j];
+        ModelId modelId = mm[j];
+        Transform applied = transforms[j];
+
+        if (modelId.isNone()) {
+            SVCERR << "WARNING: Document::addDerivedModel: no output model for transform " << applied.getIdentifier() << endl;
+            continue;
+        }
 
         // The transform we actually used was presumably identical to
         // the one asked for, except that the version of the plugin
@@ -768,47 +723,50 @@
         //!!! would be nice to short-circuit this -- the version is
         //!!! static data, shouldn't have to construct a plugin for it
         //!!! (which may be expensive in Piper-world)
-        
-        Transform applied = transforms[j];
         applied.setPluginVersion
             (TransformFactory::getInstance()->
              getDefaultTransformFor(applied.getIdentifier(),
                                     applied.getSampleRate())
              .getPluginVersion());
 
-        if (!model) {
-            SVCERR << "WARNING: Document::addDerivedModel: no output model for transform " << applied.getIdentifier() << endl;
-        } else {
-            addAlreadyDerivedModel(applied, input, model);
-        }
+        addAlreadyDerivedModel(applied, input, modelId);
     }
         
     return mm;
 }
 
 void
-Document::releaseModel(Model *model) // Will _not_ release main model!
+Document::releaseModel(ModelId modelId) // Will _not_ release main model!
 {
-    if (model == nullptr) {
+    //!!!
+
+    SVCERR << "Document::releaseModel(" << modelId << "): STILL TO REVIEW" << endl;
+
+#ifdef NOT_DEFINED
+    
+    if (modelId.isNone()) {
         return;
     }
 
+    auto model = ModelById::get(modelId);
+    if (!model) {
+        return;
+    }
+    
 #ifdef DEBUG_DOCUMENT
-    SVDEBUG << "Document::releaseModel(" << model << ", type "
+    SVDEBUG << "Document::releaseModel(" << modelId << ", type "
             << model->getTypeName() << ", name \""
             << model->objectName() << "\")" << endl;
 #endif
     
-    if (model == m_mainModel) {
+    if (modelId == m_mainModel) {
         return;
     }
 
     bool toDelete = false;
     bool isInModelList = false; // should become true for any "normal" model
 
-    ModelList::iterator mitr = findModelInList(model);
-
-    if (mitr != m_models.end()) {
+    if (m_models.find(modelId) != m_models.end()) {
 
         if (mitr->refcount == 0) {
             SVCERR << "WARNING: Document::releaseModel: model " << model
@@ -873,6 +831,8 @@
 #endif
         }
     }
+
+#endif
 }
 
 void
@@ -936,47 +896,44 @@
 }
 
 void
-Document::setModel(Layer *layer, Model *model)
+Document::setModel(Layer *layer, ModelId modelId)
 {
-    if (model && 
-        model != m_mainModel &&
-        findModelInList(model) == m_models.end()) {
+    if (!modelId.isNone() && 
+        modelId != m_mainModel &&
+        m_models.find(modelId) == m_models.end()) {
         SVCERR << "ERROR: Document::setModel: Layer " << layer
-                  << " (\"" << layer->objectName()
-                  << "\") wants to use unregistered model " << model
-                  << ": register the layer's model before setting it!"
-                  << endl;
+               << " (\"" << layer->objectName()
+               << "\") wants to use unregistered model " << modelId
+               << ": register the layer's model before setting it!"
+               << endl;
         return;
     }
 
-    Model *previousModel = layer->getModel();
+    ModelId previousModel = layer->getModel();
 
-    if (previousModel == model) {
+    if (previousModel == modelId) {
         SVDEBUG << "NOTE: Document::setModel: Layer " << layer << " (\""
-                  << layer->objectName()                  << "\") is already set to model "
-                  << model << " (\""
-                  << (model ? model->objectName(): "(null)")
-                  << "\")" << endl;
+                << layer->objectName()
+                << "\") is already set to model "
+                << modelId << endl;
         return;
     }
-
+/*!!!
     if (model && model != m_mainModel) {
         ModelList::iterator mitr = findModelInList(model);
         if (mitr != m_models.end()) {
             mitr->refcount ++;
         }
     }
-
-    if (model && previousModel) {
+*/
+    if (!modelId.isNone() && !previousModel.isNone()) {
         PlayParameterRepository::getInstance()->copyParameters
-            (previousModel, model);
+            (previousModel.untyped, modelId.untyped);
     }
 
-    LayerFactory::getInstance()->setModel(layer, model);
+    LayerFactory::getInstance()->setModel(layer, modelId);
 
-    if (previousModel) {
-        releaseModel(previousModel);
-    }
+    releaseModel(previousModel);
 }
 
 void
@@ -988,8 +945,8 @@
 void
 Document::addLayerToView(View *view, Layer *layer)
 {
-    Model *model = layer->getModel();
-    if (!model) {
+    ModelId modelId = layer->getModel();
+    if (modelId.isNone()) {
 #ifdef DEBUG_DOCUMENT
         SVDEBUG << "Document::addLayerToView: Layer (\""
                   << layer->objectName()
@@ -997,10 +954,10 @@
                   << "normally you want to set the model first" << endl;
 #endif
     } else {
-        if (model != m_mainModel &&
-            findModelInList(model) == m_models.end()) {
+        if (modelId != m_mainModel &&
+            m_models.find(modelId) == m_models.end()) {
             SVCERR << "ERROR: Document::addLayerToView: Layer " << layer
-                      << " has unregistered model " << model
+                      << " has unregistered model " << modelId
                       << " -- register the layer's model before adding the layer!" << endl;
             return;
         }
@@ -1075,37 +1032,38 @@
     }
 }
 
-std::vector<Model *>
+std::vector<ModelId>
 Document::getTransformInputModels()
 {
-    std::vector<Model *> models;
+    std::vector<ModelId> models;
 
-    if (!m_mainModel) return models;
+    if (m_mainModel.isNone()) return models;
 
     models.push_back(m_mainModel);
 
     //!!! This will pick up all models, including those that aren't visible...
 
-    for (ModelRecord &rec: m_models) {
+    for (auto rec: m_models) {
 
-        Model *model = rec.model;
-        if (!model || model == m_mainModel) continue;
-        DenseTimeValueModel *dtvm = dynamic_cast<DenseTimeValueModel *>(model);
-        
+        ModelId modelId = rec.first;
+        if (modelId == m_mainModel) continue;
+
+        auto dtvm = ModelById::getAs<DenseTimeValueModel>(modelId);
         if (dtvm) {
-            models.push_back(dtvm);
+            models.push_back(modelId);
         }
     }
 
     return models;
 }
 
+//!!! what is this used for?
 bool
-Document::isKnownModel(const Model *model) const
+Document::isKnownModel(const ModelId modelId) const
 {
-    if (model == m_mainModel) return true;
-    for (const ModelRecord &rec: m_models) {
-        if (rec.model == model) return true;
+    if (modelId == m_mainModel) return true;
+    for (auto rec: m_models) {
+        if (rec.first == modelId) return true;
     }
     return false;
 }
@@ -1117,30 +1075,29 @@
 }
 
 void
-Document::alignModel(Model *model, bool forceRecalculate)
+Document::alignModel(ModelId modelId, bool forceRecalculate)
 {
-    SVDEBUG << "Document::alignModel(" << model << ", " << forceRecalculate
+    SVDEBUG << "Document::alignModel(" << modelId << ", " << forceRecalculate
             << ") (main model is " << m_mainModel << ")" << endl;
-    
-    RangeSummarisableTimeValueModel *rm = 
-        dynamic_cast<RangeSummarisableTimeValueModel *>(model);
+
+    auto rm = ModelById::getAs<RangeSummarisableTimeValueModel>(modelId);
     if (!rm) {
-        SVDEBUG << "(model " << rm << " is not an alignable sort)" << endl;
+        SVDEBUG << "(model " << modelId << " is not an alignable sort)" << endl;
         return;
     }
 
-    if (!m_mainModel) {
+    if (m_mainModel.isNone()) {
         SVDEBUG << "(no main model to align to)" << endl;
-        if (forceRecalculate && rm->getAlignment()) {
+        if (forceRecalculate && !rm->getAlignment().isNone()) {
             SVDEBUG << "(but model is aligned, and forceRecalculate is true, "
                     << "so resetting alignment to nil)" << endl;
-            rm->setAlignment(nullptr);
+            rm->setAlignment({});
         }
         return;
     }
 
     if (rm->getAlignmentReference() == m_mainModel) {
-        SVDEBUG << "(model " << rm << " is already aligned to main model "
+        SVDEBUG << "(model " << modelId << " is already aligned to main model "
                 << m_mainModel << ")" << endl;
         if (!forceRecalculate) {
             return;
@@ -1150,37 +1107,40 @@
         }
     }
     
-    if (model == m_mainModel) {
+    if (modelId == m_mainModel) {
         // The reference has an empty alignment to itself.  This makes
         // it possible to distinguish between the reference and any
         // unaligned model just by looking at the model itself,
         // without also knowing what the main model is
-        SVDEBUG << "Document::alignModel(" << model
+        SVDEBUG << "Document::alignModel(" << modelId
                 << "): is main model, setting alignment to itself" << endl;
-        rm->setAlignment(new AlignmentModel(model, model, nullptr));
+        auto alignment = std::make_shared<AlignmentModel>(modelId, modelId,
+                                                          ModelId());
+        ModelById::add(alignment);
+        //!!! hang on, who tracks alignment models?
+        rm->setAlignment(alignment->getId());
         return;
     }
 
-    WritableWaveFileModel *w =
-        dynamic_cast<WritableWaveFileModel *>(model);
+    auto w = ModelById::getAs<WritableWaveFileModel>(modelId);
     if (w && w->getWriteProportion() < 100) {
-        SVDEBUG << "Document::alignModel(" << model
+        SVDEBUG << "Document::alignModel(" << modelId
                 << "): model write is not complete, deferring"
                 << endl;
-        connect(w, SIGNAL(writeCompleted()),
+        connect(w.get(), SIGNAL(writeCompleted()),
                 this, SLOT(performDeferredAlignment()));
         return;
     }
 
     SVDEBUG << "Document::alignModel: aligning..." << endl;
-    if (rm->getAlignmentReference() != nullptr) {
+    if (!rm->getAlignmentReference().isNone()) {
         SVDEBUG << "(Note: model " << rm << " is currently aligned to model "
                 << rm->getAlignmentReference() << "; this will replace that)"
                 << endl;
     }
 
     QString err;
-    if (!m_align->alignModel(this, m_mainModel, rm, err)) {
+    if (!m_align->alignModel(this, m_mainModel, modelId, err)) {
         SVCERR << "Alignment failed: " << err << endl;
         emit alignmentFailed(err);
     }
@@ -1189,21 +1149,23 @@
 void
 Document::performDeferredAlignment()
 {
-    QObject *s = sender();
-    Model *m = dynamic_cast<Model *>(s);
-    if (!m) {
+    ModelId modelId;
+    if (Model *m = qobject_cast<Model *>(sender())) {
+        modelId = m->getId();
+    } else {
         SVDEBUG << "Document::performDeferredAlignment: sender is not a Model" << endl;
-    } else {
-        SVDEBUG << "Document::performDeferredAlignment: aligning..." << endl;
-        alignModel(m);
+        return;
     }
+
+    SVDEBUG << "Document::performDeferredAlignment: aligning..." << endl;
+    alignModel(modelId);
 }
 
 void
 Document::alignModels()
 {
-    for (const ModelRecord &rec: m_models) {
-        alignModel(rec.model);
+    for (auto rec: m_models) {
+        alignModel(rec.first);
     }
     alignModel(m_mainModel);
 }
@@ -1211,8 +1173,8 @@
 void
 Document::realignModels()
 {
-    for (const ModelRecord &rec: m_models) {
-        alignModel(rec.model, true);
+    for (auto rec: m_models) {
+        alignModel(rec.first, true);
     }
     alignModel(m_mainModel);
 }
@@ -1362,7 +1324,8 @@
     out << indent + QString("<data%1%2>\n")
         .arg(extraAttributes == "" ? "" : " ").arg(extraAttributes);
 
-    if (m_mainModel) {
+    auto mainModel = ModelById::getAs<WaveFileModel>(m_mainModel);
+    if (mainModel) {
 
 #ifdef DEBUG_DOCUMENT
         SVDEBUG << "Document::toXml: writing main model" << endl;
@@ -1371,16 +1334,17 @@
         if (asTemplate) {
             writePlaceholderMainModel(out, indent + "  ");
         } else {
-            m_mainModel->toXml(out, indent + "  ", "mainModel=\"true\"");
+            mainModel->toXml(out, indent + "  ", "mainModel=\"true\"");
         }
 
         PlayParameters *playParameters =
-            PlayParameterRepository::getInstance()->getPlayParameters(m_mainModel);
+            PlayParameterRepository::getInstance()->getPlayParameters
+            (m_mainModel.untyped);
         if (playParameters) {
             playParameters->toXml
                 (out, indent + "  ",
                  QString("model=\"%1\"")
-                 .arg(m_mainModel->getExportId()));
+                 .arg(mainModel->getExportId()));
         }
     } else {
 #ifdef DEBUG_DOCUMENT
@@ -1391,17 +1355,17 @@
     // Models that are not used in a layer that is in a view should
     // not be written.  Get our list of required models first.
 
-    std::set<const Model *> used;
+    std::set<ModelId> used;
 
     for (LayerViewMap::const_iterator i = m_layerViewMap.begin();
          i != m_layerViewMap.end(); ++i) {
 
         if (i->first && !i->second.empty()) { // Layer exists, is in views
-            Model *m = i->first->getModel();
-            if (m) {
-                used.insert(m);
-                if (m->getSourceModel()) {
-                    used.insert(m->getSourceModel());
+            ModelId modelId = i->first->getModel();
+            if (auto model = ModelById::get(modelId)) {
+                used.insert(modelId);
+                if (!model->getSourceModel().isNone()) {
+                    used.insert(model->getSourceModel());
                 }
             }
         }
@@ -1420,16 +1384,16 @@
     // that, so existing sessions will always have the aggregate
     // models first and we might as well stick with that.
 
-    for (std::set<Model *>::iterator i = m_aggregateModels.begin();
-         i != m_aggregateModels.end(); ++i) {
+    for (auto modelId: m_aggregateModels) {
 
 #ifdef DEBUG_DOCUMENT
-        SVDEBUG << "Document::toXml: checking aggregate model " << *i << endl;
+        SVDEBUG << "Document::toXml: checking aggregate model "
+                << modelId << endl;
 #endif
-        
-        AggregateWaveModel *aggregate = qobject_cast<AggregateWaveModel *>(*i);
+
+        auto aggregate = ModelById::getAs<AggregateWaveModel>(modelId);
         if (!aggregate) continue; 
-        if (used.find(aggregate) == used.end()) {
+        if (used.find(modelId) == used.end()) {
 #ifdef DEBUG_DOCUMENT
             SVDEBUG << "(unused, skipping)" << endl;
 #endif
@@ -1443,7 +1407,7 @@
         aggregate->toXml(out, indent + "  ");
     }
 
-    std::set<Model *> written;
+    std::set<ModelId> written;
 
     // Now write the other models in two passes: first the models that
     // aren't derived from anything (in case they are source
@@ -1455,17 +1419,18 @@
     const int nonDerivedPass = 0, derivedPass = 1;
     for (int pass = nonDerivedPass; pass <= derivedPass; ++pass) {
     
-        for (const ModelRecord &rec: m_models) {
+        for (auto rec: m_models) {
 
-            Model *model = rec.model;
+            ModelId modelId = rec.first;
 
-            if (used.find(model) == used.end()) continue;
+            if (used.find(modelId) == used.end()) continue;
         
+            auto model = ModelById::get(modelId);
+            if (!model) continue;
+            
 #ifdef DEBUG_DOCUMENT
-            SVDEBUG << "Document::toXml: looking at model " << model
-                    << " (" << model->getTypeName() << ", \""
-                    << model->objectName() << "\") [pass = "
-                    << pass << "]" << endl;
+            SVDEBUG << "Document::toXml: looking at model " << modelId
+                    << " [pass = " << pass << "]" << endl;
 #endif
             
             // We need an intelligent way to determine which models
@@ -1485,7 +1450,8 @@
             bool writeModel = true;
             bool haveDerivation = false;
         
-            if (rec.source && rec.transform.getIdentifier() != "") {
+            if (!rec.second.source.isNone() &&
+                rec.second.transform.getIdentifier() != "") {
                 haveDerivation = true;
             }
 
@@ -1502,26 +1468,26 @@
             }            
 
             if (haveDerivation) {
-                if (dynamic_cast<const WritableWaveFileModel *>(model)) {
-                    writeModel = false;
-                } else if (dynamic_cast<const DenseThreeDimensionalModel *>(model)) {
+                if (ModelById::isa<WritableWaveFileModel>(modelId) ||
+                    ModelById::isa<DenseThreeDimensionalModel>(modelId)) {
                     writeModel = false;
                 }
             }
             
             if (writeModel) {
                 model->toXml(out, indent + "  ");
-                written.insert(model);
+                written.insert(modelId);
             }
             
             if (haveDerivation) {
                 writeBackwardCompatibleDerivation(out, indent + "  ",
-                                                  model, rec);
+                                                  modelId, rec.second);
             }
 
             //!!! We should probably own the PlayParameterRepository
             PlayParameters *playParameters =
-                PlayParameterRepository::getInstance()->getPlayParameters(model);
+                PlayParameterRepository::getInstance()->getPlayParameters
+                (modelId.untyped);
             if (playParameters) {
                 playParameters->toXml
                     (out, indent + "  ",
@@ -1537,11 +1503,12 @@
     // this will only work when the alignment is complete, so we
     // should probably wait for it if it isn't already by this point.
 
-    for (std::set<Model *>::const_iterator i = written.begin();
-         i != written.end(); ++i) {
+    for (auto modelId: written) {
 
-        const Model *model = *i;
-        const AlignmentModel *alignment = model->getAlignment();
+        auto model = ModelById::get(modelId);
+        if (!model) continue;
+
+        auto alignment = ModelById::get(model->getAlignment());
         if (!alignment) continue;
 
         alignment->toXml(out, indent + "  ");
@@ -1557,15 +1524,17 @@
 void
 Document::writePlaceholderMainModel(QTextStream &out, QString indent) const
 {
+    auto mainModel = ModelById::get(m_mainModel);
+    if (!mainModel) return;
     out << indent;
     out << QString("<model id=\"%1\" name=\"placeholder\" sampleRate=\"%2\" type=\"wavefile\" file=\":samples/silent.wav\" mainModel=\"true\"/>\n")
-        .arg(m_mainModel->getExportId())
-        .arg(m_mainModel->getSampleRate());
+        .arg(mainModel->getExportId())
+        .arg(mainModel->getSampleRate());
 }
 
 void
 Document::writeBackwardCompatibleDerivation(QTextStream &out, QString indent,
-                                            Model *targetModel,
+                                            ModelId targetModelId,
                                             const ModelRecord &rec) const
 {
     // There is a lot of redundancy in the XML we output here, because
@@ -1592,6 +1561,13 @@
 
     const Transform &transform = rec.transform;
 
+    //!!! in cases like these, where we think we have the model handle
+    //!!! and nobody else should be releasing it, we probably ought to
+    //!!! throw std::logic_error if !targetModel
+    
+    auto targetModel = ModelById::get(targetModelId);
+    if (!targetModel) return;
+    
     // Just for reference, this is what we would write if we didn't
     // have to be backward compatible:
     //
@@ -1623,7 +1599,7 @@
                    "model=\"%2\" channel=\"%3\" domain=\"%4\" "
                    "stepSize=\"%5\" blockSize=\"%6\" %7windowType=\"%8\" "
                    "transform=\"%9\">\n")
-        .arg(rec.source->getExportId())
+        .arg(ModelById::getExportId(rec.source))
         .arg(targetModel->getExportId())
         .arg(rec.channel)
         .arg(TransformFactory::getInstance()->getTransformInputDomain
--- a/framework/Document.h	Tue Jul 02 21:10:25 2019 +0100
+++ b/framework/Document.h	Wed Jul 03 13:01:26 2019 +0100
@@ -95,7 +95,7 @@
      * Create and return a new layer associated with the given model,
      * and register the model as an imported model.
      */
-    Layer *createImportedLayer(Model *);
+    Layer *createImportedLayer(ModelId);
 
     /**
      * Create and return a new layer of the given type, with an
@@ -183,39 +183,38 @@
     /**
      * Set the main model (the source for playback sample rate, etc)
      * to the given wave file model.  This will regenerate any derived
-     * models that were based on the previous main model.
+     * models that were based on the previous main model. The model
+     * must have been added to ModelById already, and Document will
+     * take responsibility for releasing it later.
      */
-    void setMainModel(WaveFileModel *);
+    void setMainModel(ModelId); // a WaveFileModel
 
     /**
      * Get the main model (the source for playback sample rate, etc).
      */
-    WaveFileModel *getMainModel() { return m_mainModel; }
+    ModelId getMainModel() { return m_mainModel; }
+    
+    std::vector<ModelId> getTransformInputModels();
 
-    /**
-     * Get the main model (the source for playback sample rate, etc).
-     */
-    const WaveFileModel *getMainModel() const { return m_mainModel; }
-    
-    std::vector<Model *> getTransformInputModels();
-
-    bool isKnownModel(const Model *) const;
+    //??? investigate & document
+    bool isKnownModel(ModelId) const;
 
     /**
      * Add a derived model associated with the given transform,
      * running the transform and returning the resulting model.
+     * The model is added to ModelById before returning.
      */
-    Model *addDerivedModel(const Transform &transform,
-                           const ModelTransformer::Input &input,
-                           QString &returnedMessage);
+    ModelId addDerivedModel(const Transform &transform,
+                            const ModelTransformer::Input &input,
+                            QString &returnedMessage);
 
     /**
      * Add derived models associated with the given set of related
      * transforms, running the transforms and returning the resulting
-     * models.
+     * models.  The models are added to ModelById before returning.
      */
     friend class AdditionalModelConverter;
-    std::vector<Model *> addDerivedModels(const Transforms &transforms,
+    std::vector<ModelId> addDerivedModels(const Transforms &transforms,
                                           const ModelTransformer::Input &input,
                                           QString &returnedMessage,
                                           AdditionalModelConverter *);
@@ -223,20 +222,27 @@
     /**
      * Add a derived model associated with the given transform.  This
      * is necessary to register any derived model that was not created
-     * by the document using createDerivedModel or createDerivedLayer.
+     * by the document using createDerivedModel or
+     * createDerivedLayer. The model must have been added to ModelById
+     * already, and Document will take responsibility for releasing it
+     * later.
      */
     void addAlreadyDerivedModel(const Transform &transform,
                                 const ModelTransformer::Input &input,
-                                Model *outputModelToAdd);
+                                ModelId outputModelToAdd);
 
     /**
      * Add an imported (non-derived, non-main) model.  This is
      * necessary to register any imported model that is associated
-     * with a layer.
+     * with a layer. The model must have been added to ModelById
+     * already, and Document will take responsibility for releasing it
+     * later.
      */
-    void addImportedModel(Model *);
+    void addImportedModel(ModelId);
     
     /**
+     *!!! todo: do we still need this to be separate?
+     *
      * Add an aggregate model (one which represents a set of component
      * wave models as one model per channel in a single wave
      * model). Aggregate models are unusual in that they are created
@@ -244,15 +250,18 @@
      * probably isn't ideal!) They are recorded separately from other
      * models, and will be deleted if any of their component models
      * are removed.
+     * 
+     * The model must have been added to ModelById already, and
+     * Document will take responsibility for releasing it later.
      */
-    void addAggregateModel(AggregateWaveModel *);
+    void addAggregateModel(ModelId); // an AggregateWaveModel
 
     /**
      * Associate the given model with the given layer.  The model must
      * have already been registered using one of the addXXModel
      * methods above.
      */
-    void setModel(Layer *, Model *);
+    void setModel(Layer *, ModelId);
 
     /**
      * Set the given layer to use the given channel of its model (-1
@@ -320,9 +329,8 @@
     // last removed from a view
     void layerInAView(Layer *, bool);
 
-    void modelAdded(Model *);
-    void mainModelChanged(WaveFileModel *); // emitted after modelAdded
-    void modelAboutToBeDeleted(Model *);
+    void modelAdded(ModelId);
+    void mainModelChanged(ModelId); // a WaveFileModel; emitted after modelAdded
 
     void modelGenerationFailed(QString transformName, QString message);
     void modelGenerationWarning(QString transformName, QString message);
@@ -331,17 +339,17 @@
     void modelRegenerationWarning(QString layerName, QString transformName,
                                   QString message);
 
-    void alignmentComplete(AlignmentModel *);
+    void alignmentComplete(ModelId); // an AlignmentModel
     void alignmentFailed(QString message);
 
     void activity(QString);
 
 protected slots:
-    void aggregateModelInvalidated();
+//!!!    void aggregateModelInvalidated();
     void performDeferredAlignment();
     
 protected:
-    void releaseModel(Model *model);
+    void releaseModel(ModelId model);
 
     /**
      * If model is suitable for alignment, align it against the main
@@ -349,7 +357,7 @@
      * alignment already for the current main model, leave it
      * unchanged unless forceRecalculate is true.
      */
-    void alignModel(Model *, bool forceRecalculate = false);
+    void alignModel(ModelId, bool forceRecalculate = false);
 
     /*
      * Every model that is in use by a layer in the document must be
@@ -362,75 +370,38 @@
      * model is not reference counted for layers, and is not freed
      * unless it is replaced or the document is deleted.
      */
-    WaveFileModel *m_mainModel;
+    ModelId m_mainModel; // a WaveFileModel
 
     struct ModelRecord
     {
         // Information associated with a non-main model.  If this
-        // model is derived from another, then source will be non-NULL
-        // and the transform name will be set appropriately.  If the
-        // transform name is set but source is NULL, then there was a
-        // transform involved but the (target) model has been modified
-        // since being generated from it.
+        // model is derived from another, then source will be
+        // something other than None and the transform name will be
+        // set appropriately.  If the transform is set but source is
+        // None, then there was a transform involved but the (target)
+        // model has been modified since being generated from it.
         
         // This does not use ModelTransformer::Input, because it would
-        // be confusing to have Input objects hanging around with NULL
+        // be confusing to have Input objects hanging around with None
         // models in them.
 
-        Model *model;
-        const Model *source;
+        ModelId source; // may be None
         int channel;
         Transform transform;
         bool additional;
-
-        // Count of the number of layers using this model.
-        int refcount;
     };
 
-    // This used to be a map<Model *, ModelRecord>, but a vector
-    // ensures that models are consistently recorded in order of
-    // creation rather than at the whim of heap allocation, and that's
-    // useful for automated testing. We don't expect ever to have so
-    // many models that there is any significant overhead there.
-    typedef std::vector<ModelRecord> ModelList;
-    ModelList m_models;
-
-    ModelList::iterator findModelInList(Model *m) {
-        for (ModelList::iterator i = m_models.begin();
-             i != m_models.end(); ++i) {
-            if (i->model == m) {
-                return i;
-            }
-        }
-        return m_models.end();
-    }
+    // These must be stored in increasing order of id (as in the
+    // ordered std::map), to ensure repeatability for automated tests
+    std::map<ModelId, ModelRecord> m_models;
+    std::set<ModelId> m_aggregateModels;
     
-    void deleteModelFromList(Model *m) {
-        ModelList keep;
-        bool found = false;
-        for (const ModelRecord &rec: m_models) {
-            if (rec.model == m) {
-                found = true;
-                m->aboutToDelete();
-                emit modelAboutToBeDeleted(m);
-            } else {
-                keep.push_back(rec);
-            }
-        }
-        m_models = keep;
-        if (found) {
-            delete m;
-        }
-    }
-
     /**
      * Add an extra derived model (returned at the end of processing a
      * transform).
      */
-    void addAdditionalModel(Model *);
+    void addAdditionalModel(ModelId);
 
-    std::set<Model *> m_aggregateModels;
-    
     class AddLayerCommand : public Command
     {
     public:
@@ -475,13 +446,13 @@
     void removeFromLayerViewMap(Layer *, View *);
 
     QString getUniqueLayerName(QString candidate);
-    void writeBackwardCompatibleDerivation(QTextStream &, QString, Model *,
+    void writeBackwardCompatibleDerivation(QTextStream &, QString, ModelId,
                                            const ModelRecord &) const;
 
     void toXml(QTextStream &, QString, QString, bool asTemplate) const;
     void writePlaceholderMainModel(QTextStream &, QString) const;
 
-    std::vector<Layer *> createLayersForDerivedModels(std::vector<Model *>,
+    std::vector<Layer *> createLayersForDerivedModels(std::vector<ModelId>,
                                                       QStringList names);
 
     /**