changeset 696:155008f1bf10

Merge from branch by-id
author Chris Cannam
date Wed, 17 Jul 2019 14:25:41 +0100
parents a82b9d410393 (current diff) bd6e2fc53377 (diff)
children a27a6113fdd7
files
diffstat 14 files changed, 1247 insertions(+), 1278 deletions(-) [+]
line wrap: on
line diff
--- a/audio/AudioCallbackPlaySource.cpp	Fri Jun 14 17:19:37 2019 +0100
+++ b/audio/AudioCallbackPlaySource.cpp	Wed Jul 17 14:25:41 2019 +0100
@@ -100,8 +100,8 @@
             m_viewManager, SLOT(playStatusChanged(bool)));
 
     connect(PlayParameterRepository::getInstance(),
-            SIGNAL(playParametersChanged(PlayParameters *)),
-            this, SLOT(playParametersChanged(PlayParameters *)));
+            SIGNAL(playParametersChanged(int)),
+            this, SLOT(playParametersChanged(int)));
 
     connect(Preferences::getInstance(),
             SIGNAL(propertyChanged(PropertyContainer::PropertyName)),
@@ -151,15 +151,19 @@
 }
 
 void
-AudioCallbackPlaySource::addModel(Model *model)
+AudioCallbackPlaySource::addModel(ModelId modelId)
 {
-    if (m_models.find(model) != m_models.end()) return;
+    if (m_models.find(modelId) != m_models.end()) return;
 
-    bool willPlay = m_audioGenerator->addModel(model);
+    bool willPlay = m_audioGenerator->addModel(modelId);
+
+    auto model = ModelById::get(modelId);
+    if (!model) return;
 
     m_mutex.lock();
 
-    m_models.insert(model);
+    m_models.insert(modelId);
+
     if (model->getEndFrame() > m_lastModelEndFrame) {
         m_lastModelEndFrame = model->getEndFrame();
     }
@@ -167,7 +171,7 @@
     bool buffersIncreased = false, srChanged = false;
 
     int modelChannels = 1;
-    ReadOnlyWaveFileModel *rowfm = qobject_cast<ReadOnlyWaveFileModel *>(model);
+    auto rowfm = std::dynamic_pointer_cast<ReadOnlyWaveFileModel>(model);
     if (rowfm) modelChannels = rowfm->getChannelCount();
     if (modelChannels > m_sourceChannelCount) {
         m_sourceChannelCount = modelChannels;
@@ -194,20 +198,19 @@
 
             bool conflicting = false;
 
-            for (std::set<Model *>::const_iterator i = m_models.begin();
-                 i != m_models.end(); ++i) {
+            for (ModelId otherId: m_models) {
                 // Only read-only wave file models should be
                 // considered conflicting -- writable wave file models
                 // are derived and we shouldn't take their rates into
                 // account.  Also, don't give any particular weight to
                 // a file that's already playing at the wrong rate
                 // anyway
-                ReadOnlyWaveFileModel *other =
-                    qobject_cast<ReadOnlyWaveFileModel *>(*i);
-                if (other && other != rowfm &&
+                if (otherId == modelId) continue;
+                auto other = ModelById::getAs<ReadOnlyWaveFileModel>(otherId);
+                if (other &&
                     other->getSampleRate() != model->getSampleRate() &&
                     other->getSampleRate() == m_sourceSampleRate) {
-                    SVDEBUG << "AudioCallbackPlaySource::addModel: Conflicting wave file model " << *i << " found" << endl;
+                    SVDEBUG << "AudioCallbackPlaySource::addModel: Conflicting wave file model " << otherId << " found" << endl;
                     conflicting = true;
                     break;
                 }
@@ -291,8 +294,8 @@
     SVDEBUG << "AudioCallbackPlaySource::addModel: now have " << m_models.size() << " model(s)" << endl;
 #endif
 
-    connect(model, SIGNAL(modelChangedWithin(sv_frame_t, sv_frame_t)),
-            this, SLOT(modelChangedWithin(sv_frame_t, sv_frame_t)));
+    connect(model.get(), SIGNAL(modelChangedWithin(ModelId, sv_frame_t, sv_frame_t)),
+            this, SLOT(modelChangedWithin(ModelId, sv_frame_t, sv_frame_t)));
 
 #ifdef DEBUG_AUDIO_PLAY_SOURCE
     cout << "AudioCallbackPlaySource::addModel: awakening thread" << endl;
@@ -302,7 +305,7 @@
 }
 
 void
-AudioCallbackPlaySource::modelChangedWithin(sv_frame_t 
+AudioCallbackPlaySource::modelChangedWithin(ModelId, sv_frame_t 
 #ifdef DEBUG_AUDIO_PLAY_SOURCE
                                             startFrame
 #endif
@@ -318,37 +321,31 @@
 }
 
 void
-AudioCallbackPlaySource::removeModel(Model *model)
+AudioCallbackPlaySource::removeModel(ModelId modelId)
 {
+    auto model = ModelById::get(modelId);
+    if (!model) return;
+    
     m_mutex.lock();
 
 #ifdef DEBUG_AUDIO_PLAY_SOURCE
-    cout << "AudioCallbackPlaySource::removeModel(" << model << ")" << endl;
+    cout << "AudioCallbackPlaySource::removeModel(" << modelId << ")" << endl;
 #endif
 
-    disconnect(model, SIGNAL(modelChangedWithin(sv_frame_t, sv_frame_t)),
-               this, SLOT(modelChangedWithin(sv_frame_t, sv_frame_t)));
+    disconnect(model.get(), SIGNAL(modelChangedWithin(ModelId, sv_frame_t, sv_frame_t)),
+               this, SLOT(modelChangedWithin(ModelId, sv_frame_t, sv_frame_t)));
 
-    m_models.erase(model);
-
-    // I don't think we have to do this any more: if a new model is
-    // loaded at a different rate, we'll hit the non-conflicting path
-    // in addModel and the rate will be updated without problems; but
-    // if a new model is loaded at the rate that we were using for the
-    // last one, then we save work by not having reset this here
-    //
-//    if (m_models.empty()) {
-//        m_sourceSampleRate = 0;
-//    }
+    m_models.erase(modelId);
 
     sv_frame_t lastEnd = 0;
-    for (std::set<Model *>::const_iterator i = m_models.begin();
-         i != m_models.end(); ++i) {
+    for (ModelId otherId: m_models) {
 #ifdef DEBUG_AUDIO_PLAY_SOURCE
-        cout << "AudioCallbackPlaySource::removeModel(" << model << "): checking end frame on model " << *i << endl;
+        cout << "AudioCallbackPlaySource::removeModel(" << modelId << "): checking end frame on model " << otherId << endl;
 #endif
-        if ((*i)->getEndFrame() > lastEnd) {
-            lastEnd = (*i)->getEndFrame();
+        if (auto other = ModelById::get(otherId)) {
+            if (other->getEndFrame() > lastEnd) {
+                lastEnd = other->getEndFrame();
+            }
         }
 #ifdef DEBUG_AUDIO_PLAY_SOURCE
         cout << "(done, lastEnd now " << lastEnd << ")" << endl;
@@ -356,7 +353,7 @@
     }
     m_lastModelEndFrame = lastEnd;
 
-    m_audioGenerator->removeModel(model);
+    m_audioGenerator->removeModel(modelId);
 
     if (m_models.empty()) {
         m_sourceSampleRate = 0;
@@ -579,13 +576,13 @@
 }
 
 void
-AudioCallbackPlaySource::playParametersChanged(PlayParameters *)
+AudioCallbackPlaySource::playParametersChanged(int)
 {
     clearRingBuffers();
 }
 
 void
-AudioCallbackPlaySource::preferenceChanged(PropertyContainer::PropertyName )
+AudioCallbackPlaySource::preferenceChanged(PropertyContainer::PropertyName)
 {
 }
 
@@ -1014,7 +1011,7 @@
 }
 
 void
-AudioCallbackPlaySource::setSoloModelSet(std::set<Model *> s)
+AudioCallbackPlaySource::setSoloModelSet(std::set<ModelId> s)
 {
     m_audioGenerator->setSoloModelSet(s);
     clearRingBuffers();
@@ -1664,7 +1661,7 @@
             }
         }
 
-        for (std::set<Model *>::iterator mi = m_models.begin();
+        for (std::set<ModelId>::iterator mi = m_models.begin();
              mi != m_models.end(); ++mi) {
             
             (void) m_audioGenerator->mixModel(*mi, chunkStart, 
--- a/audio/AudioCallbackPlaySource.h	Fri Jun 14 17:19:37 2019 +0100
+++ b/audio/AudioCallbackPlaySource.h	Wed Jul 17 14:25:41 2019 +0100
@@ -29,6 +29,7 @@
 
 #include "base/Thread.h"
 #include "base/RealTime.h"
+#include "data/model/Model.h"
 
 #include <samplerate.h>
 
@@ -73,12 +74,12 @@
      * models.  The models must match in sample rate, but they don't
      * have to have identical numbers of channels.
      */
-    virtual void addModel(Model *model);
+    virtual void addModel(ModelId model);
 
     /**
      * Remove a model.
      */
-    virtual void removeModel(Model *model);
+    virtual void removeModel(ModelId model);
 
     /**
      * Remove all models.  (Silence will ensue.)
@@ -292,7 +293,7 @@
     /**
      * Specify that only the given set of models should be played.
      */
-    void setSoloModelSet(std::set<Model *>s);
+    void setSoloModelSet(std::set<ModelId>s);
 
     /**
      * Specify that all models should be played as normal (if not
@@ -325,9 +326,9 @@
     void selectionChanged();
     void playLoopModeChanged();
     void playSelectionModeChanged();
-    void playParametersChanged(PlayParameters *);
+    void playParametersChanged(int);
     void preferenceChanged(PropertyContainer::PropertyName);
-    void modelChangedWithin(sv_frame_t startFrame, sv_frame_t endFrame);
+    void modelChangedWithin(ModelId, sv_frame_t startFrame, sv_frame_t endFrame);
 
 protected:
     ViewManagerBase                  *m_viewManager;
@@ -344,7 +345,7 @@
         }
     };
 
-    std::set<Model *>                 m_models;
+    std::set<ModelId>                 m_models;
     RingBufferVector                 *m_readBuffers;
     RingBufferVector                 *m_writeBuffers;
     sv_frame_t                        m_readBufferFill;
--- a/audio/AudioGenerator.cpp	Fri Jun 14 17:19:37 2019 +0100
+++ b/audio/AudioGenerator.cpp	Wed Jul 17 14:25:41 2019 +0100
@@ -56,9 +56,9 @@
     initialiseSampleDir();
 
     connect(PlayParameterRepository::getInstance(),
-            SIGNAL(playClipIdChanged(const Playable *, QString)),
+            SIGNAL(playClipIdChanged(int, QString)),
             this,
-            SLOT(playClipIdChanged(const Playable *, QString)));
+            SLOT(playClipIdChanged(int, QString)));
 }
 
 AudioGenerator::~AudioGenerator()
@@ -111,16 +111,19 @@
 }
 
 bool
-AudioGenerator::addModel(Model *model)
+AudioGenerator::addModel(ModelId modelId)
 {
+    auto model = ModelById::get(modelId);
+    if (!model) return false;
+    if (!model->canPlay()) return false;
+    
     if (m_sourceSampleRate == 0) {
 
         m_sourceSampleRate = model->getSampleRate();
 
     } else {
 
-        DenseTimeValueModel *dtvm =
-            dynamic_cast<DenseTimeValueModel *>(model);
+        auto dtvm = std::dynamic_pointer_cast<DenseTimeValueModel>(model);
 
         if (dtvm) {
             m_sourceSampleRate = model->getSampleRate();
@@ -128,28 +131,31 @@
         }
     }
 
-    const Playable *playable = model;
-    if (!playable || !playable->canPlay()) return 0;
+    auto parameters =
+        PlayParameterRepository::getInstance()->getPlayParameters
+        (modelId.untyped);
 
-    PlayParameters *parameters =
-        PlayParameterRepository::getInstance()->getPlayParameters(playable);
+    if (!parameters) {
+        SVCERR << "WARNING: Model with canPlay true is not known to PlayParameterRepository" << endl;
+        return false;
+    }
 
     bool willPlay = !parameters->isPlayMuted();
     
-    if (usesClipMixer(model)) {
-        ClipMixer *mixer = makeClipMixerFor(model);
+    if (usesClipMixer(modelId)) {
+        ClipMixer *mixer = makeClipMixerFor(modelId);
         if (mixer) {
             QMutexLocker locker(&m_mutex);
-            m_clipMixerMap[model->getId()] = mixer;
+            m_clipMixerMap[modelId] = mixer;
             return willPlay;
         }
     }
 
-    if (usesContinuousSynth(model)) {
-        ContinuousSynth *synth = makeSynthFor(model);
+    if (usesContinuousSynth(modelId)) {
+        ContinuousSynth *synth = makeSynthFor(modelId);
         if (synth) {
             QMutexLocker locker(&m_mutex);
-            m_continuousSynthMap[model->getId()] = synth;
+            m_continuousSynthMap[modelId] = synth;
             return willPlay;
         }
     }
@@ -158,74 +164,68 @@
 }
 
 void
-AudioGenerator::playClipIdChanged(const Playable *playable, QString)
+AudioGenerator::playClipIdChanged(int playableId, QString)
 {
-    const Model *model = dynamic_cast<const Model *>(playable);
-    if (!model) {
-        cerr << "WARNING: AudioGenerator::playClipIdChanged: playable "
-                  << playable << " is not a supported model type"
-                  << endl;
+    ModelId modelId;
+    modelId.untyped = playableId;
+    
+    if (m_clipMixerMap.find(modelId) == m_clipMixerMap.end()) {
         return;
     }
 
-    if (m_clipMixerMap.find(model->getId()) == m_clipMixerMap.end()) {
-        return;
-    }
-
-    ClipMixer *mixer = makeClipMixerFor(model);
+    ClipMixer *mixer = makeClipMixerFor(modelId);
     if (mixer) {
         QMutexLocker locker(&m_mutex);
-        m_clipMixerMap[model->getId()] = mixer;
+        ClipMixer *oldMixer = m_clipMixerMap[modelId];
+        m_clipMixerMap[modelId] = mixer;
+        delete oldMixer;
     }
 }
 
 bool
-AudioGenerator::usesClipMixer(const Model *model)
+AudioGenerator::usesClipMixer(ModelId modelId)
 {
     bool clip = 
-        (qobject_cast<const SparseOneDimensionalModel *>(model) ||
-         qobject_cast<const NoteModel *>(model));
+        (ModelById::isa<SparseOneDimensionalModel>(modelId) ||
+         ModelById::isa<NoteModel>(modelId));
     return clip;
 }
 
 bool
-AudioGenerator::wantsQuieterClips(const Model *model)
+AudioGenerator::wantsQuieterClips(ModelId modelId)
 {
     // basically, anything that usually has sustain (like notes) or
     // often has multiple sounds at once (like notes) wants to use a
     // quieter level than simple click tracks
-    bool does = (qobject_cast<const NoteModel *>(model));
+    bool does = (ModelById::isa<NoteModel>(modelId));
     return does;
 }
 
 bool
-AudioGenerator::usesContinuousSynth(const Model *model)
+AudioGenerator::usesContinuousSynth(ModelId modelId)
 {
-    bool cont = 
-        (qobject_cast<const SparseTimeValueModel *>(model));
+    bool cont = (ModelById::isa<SparseTimeValueModel>(modelId));
     return cont;
 }
 
 ClipMixer *
-AudioGenerator::makeClipMixerFor(const Model *model)
+AudioGenerator::makeClipMixerFor(ModelId modelId)
 {
     QString clipId;
 
-    const Playable *playable = model;
-    if (!playable || !playable->canPlay()) return nullptr;
-
-    PlayParameters *parameters =
-        PlayParameterRepository::getInstance()->getPlayParameters(playable);
+    auto parameters =
+        PlayParameterRepository::getInstance()->getPlayParameters
+        (modelId.untyped);
     if (parameters) {
         clipId = parameters->getPlayClipId();
     }
 
 #ifdef DEBUG_AUDIO_GENERATOR
-    std::cerr << "AudioGenerator::makeClipMixerFor(" << model << "): sample id = " << clipId << std::endl;
+    std::cerr << "AudioGenerator::makeClipMixerFor(" << modelId << "): sample id = " << clipId << std::endl;
 #endif
 
     if (clipId == "") {
-        SVDEBUG << "AudioGenerator::makeClipMixerFor(" << model << "): no sample, skipping" << endl;
+        SVDEBUG << "AudioGenerator::makeClipMixerFor(" << modelId << "): no sample, skipping" << endl;
         return nullptr;
     }
 
@@ -237,7 +237,7 @@
 
     QString clipPath = QString("%1/%2.wav").arg(m_sampleDir).arg(clipId);
 
-    double level = wantsQuieterClips(model) ? 0.5 : 1.0;
+    double level = wantsQuieterClips(modelId) ? 0.5 : 1.0;
     if (!mixer->loadClipData(clipPath, clipF0, level)) {
         delete mixer;
         return nullptr;
@@ -251,11 +251,8 @@
 }
 
 ContinuousSynth *
-AudioGenerator::makeSynthFor(const Model *model)
+AudioGenerator::makeSynthFor(ModelId)
 {
-    const Playable *playable = model;
-    if (!playable || !playable->canPlay()) return nullptr;
-
     ContinuousSynth *synth = new ContinuousSynth(m_targetChannelCount,
                                                  m_sourceSampleRate,
                                                  m_processingBlockSize,
@@ -269,20 +266,16 @@
 }
 
 void
-AudioGenerator::removeModel(Model *model)
+AudioGenerator::removeModel(ModelId modelId)
 {
-    SparseOneDimensionalModel *sodm =
-        dynamic_cast<SparseOneDimensionalModel *>(model);
-    if (!sodm) return; // nothing to do
-
     QMutexLocker locker(&m_mutex);
 
-    if (m_clipMixerMap.find(sodm->getId()) == m_clipMixerMap.end()) {
+    if (m_clipMixerMap.find(modelId) == m_clipMixerMap.end()) {
         return;
     }
 
-    ClipMixer *mixer = m_clipMixerMap[sodm->getId()];
-    m_clipMixerMap.erase(sodm->getId());
+    ClipMixer *mixer = m_clipMixerMap[modelId];
+    m_clipMixerMap.erase(modelId);
     delete mixer;
 }
 
@@ -339,7 +332,7 @@
 }
 
 void
-AudioGenerator::setSoloModelSet(std::set<Model *> s)
+AudioGenerator::setSoloModelSet(std::set<ModelId> s)
 {
     QMutexLocker locker(&m_mutex);
 
@@ -357,7 +350,7 @@
 }
 
 sv_frame_t
-AudioGenerator::mixModel(Model *model,
+AudioGenerator::mixModel(ModelId modelId,
                          sv_frame_t startFrame, sv_frame_t frameCount,
                          float **buffer,
                          sv_frame_t fadeIn, sv_frame_t fadeOut)
@@ -369,25 +362,26 @@
 
     QMutexLocker locker(&m_mutex);
 
-    Playable *playable = model;
-    if (!playable || !playable->canPlay()) return frameCount;
+    auto model = ModelById::get(modelId);
+    if (!model || !model->canPlay()) return frameCount;
 
-    PlayParameters *parameters =
-        PlayParameterRepository::getInstance()->getPlayParameters(playable);
+    auto parameters =
+        PlayParameterRepository::getInstance()->getPlayParameters
+        (modelId.untyped);
     if (!parameters) return frameCount;
 
     bool playing = !parameters->isPlayMuted();
     if (!playing) {
 #ifdef DEBUG_AUDIO_GENERATOR
-        cout << "AudioGenerator::mixModel(" << model << "): muted" << endl;
+        cout << "AudioGenerator::mixModel(" << modelId << "): muted" << endl;
 #endif
         return frameCount;
     }
 
     if (m_soloing) {
-        if (m_soloModelSet.find(model) == m_soloModelSet.end()) {
+        if (m_soloModelSet.find(modelId) == m_soloModelSet.end()) {
 #ifdef DEBUG_AUDIO_GENERATOR
-            cout << "AudioGenerator::mixModel(" << model << "): not one of the solo'd models" << endl;
+            cout << "AudioGenerator::mixModel(" << modelId << "): not one of the solo'd models" << endl;
 #endif
             return frameCount;
         }
@@ -396,35 +390,37 @@
     float gain = parameters->getPlayGain();
     float pan = parameters->getPlayPan();
 
-    DenseTimeValueModel *dtvm = dynamic_cast<DenseTimeValueModel *>(model);
-    if (dtvm) {
-        return mixDenseTimeValueModel(dtvm, startFrame, frameCount,
+    if (std::dynamic_pointer_cast<DenseTimeValueModel>(model)) {
+        return mixDenseTimeValueModel(modelId, startFrame, frameCount,
                                       buffer, gain, pan, fadeIn, fadeOut);
     }
 
-    if (usesClipMixer(model)) {
-        return mixClipModel(model, startFrame, frameCount,
+    if (usesClipMixer(modelId)) {
+        return mixClipModel(modelId, startFrame, frameCount,
                             buffer, gain, pan);
     }
 
-    if (usesContinuousSynth(model)) {
-        return mixContinuousSynthModel(model, startFrame, frameCount,
+    if (usesContinuousSynth(modelId)) {
+        return mixContinuousSynthModel(modelId, startFrame, frameCount,
                                        buffer, gain, pan);
     }
 
-    std::cerr << "AudioGenerator::mixModel: WARNING: Model " << model << " of type " << model->getTypeName() << " is marked as playable, but I have no mechanism to play it" << std::endl;
+    std::cerr << "AudioGenerator::mixModel: WARNING: Model " << modelId << " of type " << model->getTypeName() << " is marked as playable, but I have no mechanism to play it" << std::endl;
 
     return frameCount;
 }
 
 sv_frame_t
-AudioGenerator::mixDenseTimeValueModel(DenseTimeValueModel *dtvm,
+AudioGenerator::mixDenseTimeValueModel(ModelId modelId,
                                        sv_frame_t startFrame, sv_frame_t frames,
                                        float **buffer, float gain, float pan,
                                        sv_frame_t fadeIn, sv_frame_t fadeOut)
 {
     sv_frame_t maxFrames = frames + std::max(fadeIn, fadeOut);
 
+    auto dtvm = ModelById::getAs<DenseTimeValueModel>(modelId);
+    if (!dtvm) return 0;
+    
     int modelChannels = dtvm->getChannelCount();
 
     if (m_channelBufSiz < maxFrames || m_channelBufCount < modelChannels) {
@@ -519,13 +515,15 @@
 }
   
 sv_frame_t
-AudioGenerator::mixClipModel(Model *model,
+AudioGenerator::mixClipModel(ModelId modelId,
                              sv_frame_t startFrame, sv_frame_t frames,
                              float **buffer, float gain, float pan)
 {
-    ClipMixer *clipMixer = m_clipMixerMap[model->getId()];
+    ClipMixer *clipMixer = m_clipMixerMap[modelId];
     if (!clipMixer) return 0;
 
+    auto exportable = ModelById::getAs<NoteExportable>(modelId);
+    
     int blocks = int(frames / m_processingBlockSize);
     
     //!!! todo: the below -- it matters
@@ -551,7 +549,7 @@
     ClipMixer::NoteStart on;
     ClipMixer::NoteEnd off;
 
-    NoteOffSet &noteOffs = m_noteOffs[model->getId()];
+    NoteOffSet &noteOffs = m_noteOffs[modelId];
 
     float **bufferIndexes = new float *[m_targetChannelCount];
 
@@ -562,7 +560,6 @@
         sv_frame_t reqStart = startFrame + i * m_processingBlockSize;
 
         NoteList notes;
-        NoteExportable *exportable = dynamic_cast<NoteExportable *>(model);
         if (exportable) {
             notes = exportable->getNotesStartingWithin(reqStart,
                                                        m_processingBlockSize);
@@ -677,18 +674,19 @@
 }
 
 sv_frame_t
-AudioGenerator::mixContinuousSynthModel(Model *model,
+AudioGenerator::mixContinuousSynthModel(ModelId modelId,
                                         sv_frame_t startFrame,
                                         sv_frame_t frames,
                                         float **buffer,
                                         float gain, 
                                         float pan)
 {
-    ContinuousSynth *synth = m_continuousSynthMap[model->getId()];
+    ContinuousSynth *synth = m_continuousSynthMap[modelId];
     if (!synth) return 0;
 
     // only type we support here at the moment
-    SparseTimeValueModel *stvm = qobject_cast<SparseTimeValueModel *>(model);
+    auto stvm = ModelById::getAs<SparseTimeValueModel>(modelId);
+    if (!stvm) return 0;
     if (stvm->getScaleUnits() != "Hz") return 0;
 
     int blocks = int(frames / m_processingBlockSize);
--- a/audio/AudioGenerator.h	Fri Jun 14 17:19:37 2019 +0100
+++ b/audio/AudioGenerator.h	Wed Jul 17 14:25:41 2019 +0100
@@ -48,12 +48,12 @@
      * played.  The model will be added regardless of the return
      * value.
      */
-    virtual bool addModel(Model *model);
+    virtual bool addModel(ModelId model);
 
     /**
      * Remove a model.
      */
-    virtual void removeModel(Model *model);
+    virtual void removeModel(ModelId model);
 
     /**
      * Remove all models.
@@ -81,7 +81,7 @@
     /**
      * Mix a single model into an output buffer.
      */
-    virtual sv_frame_t mixModel(Model *model,
+    virtual sv_frame_t mixModel(ModelId model,
                                 sv_frame_t startFrame,
                                 sv_frame_t frameCount,
                                 float **buffer,
@@ -91,7 +91,7 @@
     /**
      * Specify that only the given set of models should be played.
      */
-    virtual void setSoloModelSet(std::set<Model *>s);
+    virtual void setSoloModelSet(std::set<ModelId>s);
 
     /**
      * Specify that all models should be played as normal (if not
@@ -100,7 +100,7 @@
     virtual void clearSoloModelSet();
 
 protected slots:
-    void playClipIdChanged(const Playable *, QString);
+    void playClipIdChanged(int playableId, QString);
 
 protected:
     sv_samplerate_t m_sourceSampleRate;
@@ -108,7 +108,7 @@
     int m_waveType;
 
     bool m_soloing;
-    std::set<Model *> m_soloModelSet;
+    std::set<ModelId> m_soloModelSet;
 
     struct NoteOff {
 
@@ -140,12 +140,12 @@
     };
 
 
-    typedef std::map<const ModelId, ClipMixer *> ClipMixerMap;
+    typedef std::map<ModelId, ClipMixer *> ClipMixerMap;
 
     typedef std::multiset<NoteOff, NoteOff::Comparator> NoteOffSet;
-    typedef std::map<const ModelId, NoteOffSet> NoteOffMap;
+    typedef std::map<ModelId, NoteOffSet> NoteOffMap;
 
-    typedef std::map<const ModelId, ContinuousSynth *> ContinuousSynthMap;
+    typedef std::map<ModelId, ContinuousSynth *> ContinuousSynthMap;
 
     QMutex m_mutex;
 
@@ -155,25 +155,25 @@
 
     ContinuousSynthMap m_continuousSynthMap;
 
-    bool usesClipMixer(const Model *);
-    bool wantsQuieterClips(const Model *);
-    bool usesContinuousSynth(const Model *);
+    bool usesClipMixer(ModelId);
+    bool wantsQuieterClips(ModelId);
+    bool usesContinuousSynth(ModelId);
 
-    ClipMixer *makeClipMixerFor(const Model *model);
-    ContinuousSynth *makeSynthFor(const Model *model);
+    ClipMixer *makeClipMixerFor(ModelId model);
+    ContinuousSynth *makeSynthFor(ModelId model);
 
     static void initialiseSampleDir();
 
     virtual sv_frame_t mixDenseTimeValueModel
-    (DenseTimeValueModel *model, sv_frame_t startFrame, sv_frame_t frameCount,
+    (ModelId model, sv_frame_t startFrame, sv_frame_t frameCount,
      float **buffer, float gain, float pan, sv_frame_t fadeIn, sv_frame_t fadeOut);
 
     virtual sv_frame_t mixClipModel
-    (Model *model, sv_frame_t startFrame, sv_frame_t frameCount,
+    (ModelId model, sv_frame_t startFrame, sv_frame_t frameCount,
      float **buffer, float gain, float pan);
 
     virtual sv_frame_t mixContinuousSynthModel
-    (Model *model, sv_frame_t startFrame, sv_frame_t frameCount,
+    (ModelId model, sv_frame_t startFrame, sv_frame_t frameCount,
      float **buffer, float gain, float pan);
     
     static const sv_frame_t m_processingBlockSize;
--- a/framework/Align.cpp	Fri Jun 14 17:19:37 2019 +0100
+++ b/framework/Align.cpp	Wed Jul 17 14:25:41 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,76 +89,89 @@
 }
 
 bool
-Align::alignModelViaTransform(Document *doc, Model *ref, Model *other,
+Align::alignModelViaTransform(Document *doc,
+                              ModelId referenceId,
+                              ModelId otherId,
                               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>(referenceId);
+    auto other =
+        ModelById::getAs<RangeSummarisableTimeValueModel>(otherId);
+
+    if (!reference || !other) return false;
    
-    // This involves creating either three or four new models:
+    // This involves creating a number of new models:
     //
     // 1. an AggregateWaveModel to provide the mixdowns of the main
     // model and the new model in its two channels, as input to the
-    // MATCH plugin
+    // MATCH plugin. We just call this one aggregateModel
     //
     // 2a. a SparseTimeValueModel which will be automatically created
     // by FeatureExtractionModelTransformer when running the
     // TuningDifference plugin to receive the relative tuning of the
     // second model (if pitch-aware alignment is enabled in the
-    // preferences)
+    // preferences). We call this tuningDiffOutputModel.
     //
     // 2b. a SparseTimeValueModel which will be automatically created
     // by FeatureExtractionPluginTransformer when running the MATCH
-    // plugin to perform alignment (so containing the alignment path)
+    // plugin to perform alignment (so containing the alignment path).
+    // We call this one pathOutputModel.
     //
-    // 3. an AlignmentModel, which stores the path model and carries
-    // out alignment lookups on it.
+    // 2c. a SparseTimeValueModel used solely to provide faked
+    // completion information to the AlignmentModel while a
+    // TuningDifference calculation is going on. We call this
+    // preparatoryModel.
     //
-    // The AggregateWaveModel [1] is registered with the document,
-    // which deletes it when it is invalidated (when one of its
-    // components is deleted). The SparseTimeValueModel [2a] is reused
-    // by us when starting the alignment process proper, and is then
-    // deleted by us. The SparseTimeValueModel [2b] is passed to the
-    // AlignmentModel, which takes ownership of it. The AlignmentModel
-    // 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].
+    // 3. an AlignmentModel, which stores the path and carries out
+    // alignment lookups on it. We just call this one alignmentModel.
     //
-    // (We also create a sneaky additional SparseTimeValueModel
-    // temporarily so we can attach completion information to it -
-    // this is quite unnecessary from the perspective of simply
-    // producing the results.)
+    // Models 1 and 3 are registered with the document, which will
+    // eventually release them. We don't release them here except in
+    // the case where an activity fails before the point where we
+    // would otherwise have registered them with the document.
+    //
+    // Models 2a (tuningDiffOutputModel), 2b (pathOutputModel) and 2c
+    // (preparatoryModel) are not registered with the document. Model
+    // 2b (pathOutputModel) is not registered because we do not have a
+    // stable reference to the document at the point where it is
+    // created. Model 2c (preparatoryModel) is not registered because
+    // it is a bodge that we are embarrassed about, so we try to
+    // manage it ourselves without anyone else noticing. Model 2a is
+    // not registered for symmetry with the other two. These have to
+    // be released by us when finished with, but their lifespans do
+    // not extend beyond the end of the alignment procedure, so this
+    // should be ok.
 
     AggregateWaveModel::ChannelSpecList components;
 
-    components.push_back(AggregateWaveModel::ModelChannelSpec
-                         (reference, -1));
+    components.push_back
+        (AggregateWaveModel::ModelChannelSpec(referenceId, -1));
 
-    components.push_back(AggregateWaveModel::ModelChannelSpec
-                         (rm, -1));
+    components.push_back
+        (AggregateWaveModel::ModelChannelSpec(otherId, -1));
 
-    AggregateWaveModel *aggregateModel = new AggregateWaveModel(components);
-    doc->addAggregateModel(aggregateModel);
+    auto aggregateModel = std::make_shared<AggregateWaveModel>(components);
+    auto aggregateModelId = ModelById::add(aggregateModel);
+    doc->addNonDerivedModel(aggregateModelId);
 
-    AlignmentModel *alignmentModel =
-        new AlignmentModel(reference, other, nullptr);
+    auto alignmentModel = std::make_shared<AlignmentModel>
+        (referenceId, otherId, ModelId());
+    auto alignmentModelId = ModelById::add(alignmentModel);
 
     TransformId tdId = getTuningDifferenceTransformName();
 
     if (tdId == "") {
         
-        if (beginTransformDrivenAlignment(aggregateModel, alignmentModel)) {
-            rm->setAlignment(alignmentModel);
+        if (beginTransformDrivenAlignment(aggregateModelId,
+                                          alignmentModelId)) {
+            other->setAlignment(alignmentModelId);
+            doc->addNonDerivedModel(alignmentModelId);
         } else {
             error = alignmentModel->getError();
-            delete alignmentModel;
+            ModelById::release(alignmentModel);
             return false;
         }
 
@@ -181,86 +194,79 @@
         ModelTransformerFactory *mtf = ModelTransformerFactory::getInstance();
 
         QString message;
-        Model *transformOutput = mtf->transform(transform, aggregateModel, message);
+        ModelId tuningDiffOutputModelId = mtf->transform(transform,
+                                                         aggregateModelId,
+                                                         message);
 
-        SparseTimeValueModel *tdout = dynamic_cast<SparseTimeValueModel *>
-            (transformOutput);
-        
-        if (!tdout) {
+        auto tuningDiffOutputModel =
+            ModelById::getAs<SparseTimeValueModel>(tuningDiffOutputModelId);
+        if (!tuningDiffOutputModel) {
             SVCERR << "Align::alignModel: ERROR: Failed to create tuning-difference output model (no Tuning Difference plugin?)" << endl;
-            delete tdout;
             error = message;
+            ModelById::release(alignmentModel);
             return false;
         }
 
-        rm->setAlignment(alignmentModel);
+        other->setAlignment(alignmentModelId);
+        doc->addNonDerivedModel(alignmentModelId);
     
-        connect(tdout, SIGNAL(completionChanged()),
-                this, SLOT(tuningDifferenceCompletionChanged()));
+        connect(tuningDiffOutputModel.get(),
+                SIGNAL(completionChanged(ModelId)),
+                this, SLOT(tuningDifferenceCompletionChanged(ModelId)));
 
         TuningDiffRec rec;
-        rec.input = aggregateModel;
-        rec.alignment = alignmentModel;
-
-        connect(aggregateModel, SIGNAL(aboutToBeDeleted()),
-                this, SLOT(aggregateModelAboutToBeDeleted()));
+        rec.input = aggregateModelId;
+        rec.alignment = alignmentModelId;
         
         // 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);
+        auto preparatoryModelId = ModelById::add(preparatoryModel);
+        preparatoryModel->setCompletion(0);
+        rec.preparatory = preparatoryModelId;
         alignmentModel->setPathFrom(rec.preparatory);
         
-        m_pendingTuningDiffs[tdout] = rec;
+        m_pendingTuningDiffs[tuningDiffOutputModelId] = rec;
     }
 
     return true;
 }
 
 void
-Align::aggregateModelAboutToBeDeleted()
+Align::tuningDifferenceCompletionChanged(ModelId tuningDiffOutputModelId)
 {
-    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()) {
+    if (m_pendingTuningDiffs.find(tuningDiffOutputModelId) ==
+        m_pendingTuningDiffs.end()) {
         SVCERR << "ERROR: Align::tuningDifferenceCompletionChanged: Model "
-               << td << " not found in pending tuning diff map!" << endl;
+               << tuningDiffOutputModelId
+               << " not found in pending tuning diff map!" << endl;
         return;
     }
 
-    TuningDiffRec rec = m_pendingTuningDiffs[td];
+    auto tuningDiffOutputModel =
+        ModelById::getAs<SparseTimeValueModel>(tuningDiffOutputModelId);
+    if (!tuningDiffOutputModel) {
+        SVCERR << "WARNING: Align::tuningDifferenceCompletionChanged: Model "
+               << tuningDiffOutputModelId
+               << " not known as SparseTimeValueModel" << endl;
+        return;
+    }
 
+    TuningDiffRec rec = m_pendingTuningDiffs[tuningDiffOutputModelId];
+
+    auto alignmentModel = ModelById::getAs<AlignmentModel>(rec.alignment);
+    if (!alignmentModel) {
+        SVCERR << "WARNING: Align::tuningDifferenceCompletionChanged:"
+               << "alignment model has disappeared" << endl;
+        return;
+    }
+    
     int completion = 0;
-    bool done = td->isReady(&completion);
-
-//    SVCERR << "Align::tuningDifferenceCompletionChanged: done = " << done << ", completion = " << completion << endl;
+    bool done = tuningDiffOutputModel->isReady(&completion);
 
     if (!done) {
         // This will be the completion the alignment model reports,
@@ -268,39 +274,54 @@
         // 99 (not 100!) and then back to 0 again when we start
         // 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 preparatoryModel =
+            ModelById::getAs<SparseTimeValueModel>(rec.preparatory);
+        if (preparatoryModel) {
+            preparatoryModel->setCompletion(clamped);
+        }
         return;
     }
 
     float tuningFrequency = 440.f;
     
-    if (!td->isEmpty()) {
-        tuningFrequency = td->getAllEvents()[0].getValue();
+    if (!tuningDiffOutputModel->isEmpty()) {
+        tuningFrequency = tuningDiffOutputModel->getAllEvents()[0].getValue();
         SVCERR << "Align::tuningDifferenceCompletionChanged: Reported tuning frequency = " << tuningFrequency << endl;
     } else {
         SVCERR << "Align::tuningDifferenceCompletionChanged: No tuning frequency reported" << endl;
     }    
 
-    m_pendingTuningDiffs.erase(td);
-    td->aboutToDelete();
-    delete td;
-
-    rec.alignment->setPathFrom(nullptr);
+    ModelById::release(tuningDiffOutputModel);
+    
+    alignmentModel->setPathFrom({}); // replace preparatoryModel
+    ModelById::release(rec.preparatory);
+    rec.preparatory = {};
+    
+    m_pendingTuningDiffs.erase(tuningDiffOutputModelId);
     
     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,90 +338,97 @@
     ModelTransformerFactory *mtf = ModelTransformerFactory::getInstance();
 
     QString message;
-    Model *transformOutput = mtf->transform
-        (transform, aggregateModel, message);
+    ModelId pathOutputModelId = mtf->transform
+        (transform, aggregateModelId, message);
 
-    if (!transformOutput) {
+    if (pathOutputModelId.isNone()) {
         transform.setStepSize(0);
-        transformOutput = mtf->transform
-            (transform, aggregateModel, message);
+        pathOutputModelId = mtf->transform
+            (transform, aggregateModelId, message);
     }
 
-    SparseTimeValueModel *path = dynamic_cast<SparseTimeValueModel *>
-        (transformOutput);
+    auto pathOutputModel =
+        ModelById::getAs<SparseTimeValueModel>(pathOutputModelId);
 
     //!!! callers will need to be updated to get error from
     //!!! alignment model after initial call
         
-    if (!path) {
+    if (!pathOutputModel) {
         SVCERR << "Align::alignModel: ERROR: Failed to create alignment path (no MATCH plugin?)" << endl;
-        delete transformOutput;
         alignmentModel->setError(message);
         return false;
     }
 
-    path->setCompletion(0);
-    alignmentModel->setPathFrom(path);
+    pathOutputModel->setCompletion(0);
+    alignmentModel->setPathFrom(pathOutputModelId);
 
-    connect(alignmentModel, SIGNAL(completionChanged()),
-            this, SLOT(alignmentCompletionChanged()));
+    m_pendingAlignments[alignmentModelId] = pathOutputModelId;
+
+    connect(alignmentModel.get(), SIGNAL(completionChanged(ModelId)),
+            this, SLOT(alignmentCompletionChanged(ModelId)));
 
     return true;
 }
 
 void
-Align::alignmentCompletionChanged()
+Align::alignmentCompletionChanged(ModelId alignmentModelId)
 {
     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);
+
+    auto alignmentModel = ModelById::getAs<AlignmentModel>(alignmentModelId);
+
+    if (alignmentModel && alignmentModel->isReady()) {
+
+        if (m_pendingAlignments.find(alignmentModelId) !=
+            m_pendingAlignments.end()) {
+            ModelId pathOutputModelId = m_pendingAlignments[alignmentModelId];
+            ModelById::release(pathOutputModelId);
+            m_pendingAlignments.erase(alignmentModelId);
+        }
+        
+        disconnect(alignmentModel.get(),
+                   SIGNAL(completionChanged(ModelId)),
+                   this, SLOT(alignmentCompletionChanged(ModelId)));
+        emit alignmentComplete(alignmentModelId);
     }
 }
 
 bool
-Align::alignModelViaProgram(Document *, Model *ref, Model *other,
-                            QString program, QString &error)
+Align::alignModelViaProgram(Document *doc,
+                            ModelId referenceId,
+                            ModelId otherId,
+                            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
-    }
-
-    while (!reference->isReady(nullptr) || !rm->isReady(nullptr)) {
-        qApp->processEvents();
-    }
-    
     // Run an external program, passing to it paths to the main
     // 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);
-    if (!roref || !rorm) {
+    auto reference = ModelById::getAs<ReadOnlyWaveFileModel>(referenceId);
+    auto other = ModelById::getAs<ReadOnlyWaveFileModel>(otherId);
+    if (!reference || !other) {
         SVCERR << "ERROR: Align::alignModelViaProgram: Can't align non-read-only models via program (no local filename available)" << endl;
         return false;
     }
+
+    while (!reference->isReady(nullptr) || !other->isReady(nullptr)) {
+        qApp->processEvents();
+    }
     
-    QString refPath = roref->getLocalFilename();
-    QString otherPath = rorm->getLocalFilename();
+    QString refPath = reference->getLocalFilename();
+    QString otherPath = other->getLocalFilename();
 
     if (refPath == "" || otherPath == "") {
         error = "Failed to find local filepath for wave-file model";
         return false;
     }
 
-    AlignmentModel *alignmentModel =
-        new AlignmentModel(reference, other, nullptr);
-    rm->setAlignment(alignmentModel);
+    auto alignmentModel =
+        std::make_shared<AlignmentModel>(referenceId, otherId, ModelId());
+    auto alignmentModelId = ModelById::add(alignmentModel);
+    other->setAlignment(alignmentModelId);
 
     QProcess *process = new QProcess;
     QStringList args;
@@ -409,7 +437,7 @@
     connect(process, SIGNAL(finished(int, QProcess::ExitStatus)),
             this, SLOT(alignmentProgramFinished(int, QProcess::ExitStatus)));
 
-    m_pendingProcesses[process] = alignmentModel;
+    m_pendingProcesses[process] = alignmentModelId;
     process->start(program, args);
 
     bool success = process->waitForStarted();
@@ -419,10 +447,12 @@
                << endl;
         error = "Alignment program could not be started";
         m_pendingProcesses.erase(process);
-        rm->setAlignment(nullptr); // deletes alignmentModel as well
+        other->setAlignment({});
+        ModelById::release(alignmentModelId);
         delete process;
     }
 
+    doc->addNonDerivedModel(alignmentModelId);
     return success;
 }
 
@@ -441,7 +471,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,31 +503,40 @@
             goto done;
         }
 
+        //!!! to use ById?
+        
         Model *csvOutput = reader.load();
 
-        SparseTimeValueModel *path = qobject_cast<SparseTimeValueModel *>(csvOutput);
+        SparseTimeValueModel *path =
+            qobject_cast<SparseTimeValueModel *>(csvOutput);
         if (!path) {
             SVCERR << "ERROR: Align::alignmentProgramFinished: Output did not convert to sparse time-value model"
                    << 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);
+        auto pathId =
+            ModelById::add(std::shared_ptr<SparseTimeValueModel>(path));
+        alignmentModel->setPathFrom(pathId);
 
-        emit alignmentComplete(alignmentModel);
+        emit alignmentComplete(alignmentModelId);
+
+        ModelById::release(pathId);
         
     } else {
         SVCERR << "ERROR: Align::alignmentProgramFinished: Aligner program "
--- a/framework/Align.h	Fri Jun 14 17:19:37 2019 +0100
+++ b/framework/Align.h	Wed Jul 17 14:25:41 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,39 @@
      * 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 alignmentCompletionChanged(ModelId);
+    void tuningDifferenceCompletionChanged(ModelId);
     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;
+    // alignment model id -> path output model id
+    std::map<ModelId, ModelId> m_pendingAlignments;
+    
+    // external alignment subprocess -> model into which to stuff the
+    // results (an AlignmentModel)
+    std::map<QProcess *, ModelId> m_pendingProcesses;
 };
 
 #endif
--- a/framework/Document.cpp	Fri Jun 14 17:19:37 2019 +0100
+++ b/framework/Document.cpp	Wed Jul 17 14:25:41 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()
@@ -79,43 +73,42 @@
     CommandHistory::getInstance()->clear();
     
 #ifdef DEBUG_DOCUMENT
-    SVDEBUG << "Document::~Document: about to delete layers" << endl;
+    SVCERR << "Document::~Document: about to delete layers" << endl;
 #endif
     while (!m_layers.empty()) {
         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();
+#ifdef DEBUG_DOCUMENT
+    SVCERR << "Document::~Document: about to release normal models" << endl;
+#endif
+    for (auto mr: m_models) {
+        ModelById::release(mr.first);
     }
 
 #ifdef DEBUG_DOCUMENT
-    SVDEBUG << "Document::~Document: About to get rid of main model"
-              << endl;
+    SVCERR << "Document::~Document: about to release aggregate models" << endl;
 #endif
-    if (m_mainModel) {
-        m_mainModel->aboutToDelete();
-        emit modelAboutToBeDeleted(m_mainModel);
+    for (auto m: m_aggregateModels) {
+        ModelById::release(m);
     }
 
-    emit mainModelChanged(nullptr);
-    delete m_mainModel;
+#ifdef DEBUG_DOCUMENT
+    SVCERR << "Document::~Document: about to release alignment models" << endl;
+#endif
+    for (auto m: m_alignmentModels) {
+        ModelById::release(m);
+    }
+
+#ifdef DEBUG_DOCUMENT
+    SVCERR << "Document::~Document: about to release main model" << endl;
+#endif
+    if (!m_mainModel.isNone()) {
+        ModelById::release(m_mainModel);
+    }
+    
+    m_mainModel = {};
+    emit mainModelChanged({});
 }
 
 Layer *
@@ -148,10 +141,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 +159,8 @@
 
     newLayer->setObjectName(getUniqueLayerName(newLayer->objectName()));
 
-    addImportedModel(model);
-    setModel(newLayer, model);
+    addNonDerivedModel(modelId);
+    setModel(newLayer, modelId);
 
     //!!! and all channels
     setChannel(newLayer, -1);
@@ -186,20 +179,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);
+    auto newModelId = ModelById::add(newModel);
+    addNonDerivedModel(newModelId);
+    setModel(newLayer, newModelId);
 
     return newLayer;
 }
@@ -234,7 +227,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 +240,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 +269,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
@@ -297,15 +291,6 @@
         delete this;
     }
 
-    void cancel() {
-        foreach (Layer *layer, m_primary) {
-            Model *model = layer->getModel();
-            if (model) {
-                model->abandon();
-            }
-        }
-    }
-
 private:
     Document *m_doc;
     vector<Layer *> m_primary;
@@ -321,11 +306,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
@@ -348,37 +333,29 @@
     return amc;
 }
 
-void
-Document::cancelAsyncLayerCreation(Document::LayerCreationAsyncHandle h)
-{
-    AdditionalModelConverter *conv = static_cast<AdditionalModelConverter *>(h);
-    conv->cancel();
-}
-
 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);
+            releaseModel(newModelId);
             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 +384,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 +411,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 +419,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 +434,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 +442,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 +468,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 +492,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 +513,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 +552,50 @@
 
     emit mainModelChanged(m_mainModel);
 
-    delete oldMainModel;
+    if (!oldMainModel.isNone()) {
+
+        // Remove the playable explicitly - the main model's dtor will
+        // do this, but just in case something is still hanging onto a
+        // shared_ptr to the old main model so it doesn't get deleted
+        PlayParameterRepository::getInstance()->removePlayable
+            (oldMainModel.untyped);
+
+        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 +603,130 @@
     emit modelAdded(outputModelToAdd);
 }
 
-
 void
-Document::addImportedModel(Model *model)
+Document::addNonDerivedModel(ModelId modelId)
 {
-    if (findModelInList(model) != m_models.end()) {
-        SVCERR << "WARNING: Document::addImportedModel: Model already added"
-                  << endl;
+    if (ModelById::isa<AggregateWaveModel>(modelId)) {
+#ifdef DEBUG_DOCUMENT
+        SVCERR << "Document::addNonDerivedModel: Model " << modelId << " is an aggregate model, adding it to aggregates" << endl;
+#endif
+        m_aggregateModels.insert(modelId);
+        return;
+    }
+    if (ModelById::isa<AlignmentModel>(modelId)) {
+#ifdef DEBUG_DOCUMENT
+        SVCERR << "Document::addNonDerivedModel: Model " << modelId << " is an alignment model, adding it to alignments" << endl;
+#endif
+        m_alignmentModels.insert(modelId);
+        return;
+    }
+    
+    if (m_models.find(modelId) != m_models.end()) {
+        SVCERR << "WARNING: Document::addNonDerivedModel: Model already added"
+               << 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;
+    SVCERR << "Document::addNonDerivedModel: Added model " << modelId << endl;
+    SVCERR << "Models now: ";
+    for (const auto &rec : m_models) {
+        SVCERR << rec.first << " ";
+    } 
+    SVCERR << endl;
+#endif
+
+    if (m_autoAlignment) {
+        SVDEBUG << "Document::addNonDerivedModel: auto-alignment is on, aligning model if possible" << endl;
+        alignModel(modelId);
+    } else {
+        SVDEBUG << "Document(" << this << "): addNonDerivedModel: auto-alignment is off" << endl;
+    }
+
+    emit modelAdded(modelId);
+}
+
+void
+Document::addAdditionalModel(ModelId modelId)
+{
+    if (m_models.find(modelId) != m_models.end()) {
+        SVCERR << "WARNING: Document::addAdditionalModel: Model already added"
+               << endl;
+        return;
+    }
+
+    ModelRecord rec;
+    rec.source = {};
+    rec.channel = 0;
+    rec.additional = true;
+
+    m_models[modelId] = rec;
+
+#ifdef DEBUG_DOCUMENT
+    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::addImportedModel: auto-alignment is on, aligning model if possible" << endl;
-        alignModel(model);
-    } else {
-        SVDEBUG << "Document(" << this << "): addImportedModel: auto-alignment is off" << endl;
+    if (m_autoAlignment &&
+        ModelById::isa<RangeSummarisableTimeValueModel>(modelId)) {
+        SVDEBUG << "Document::addAdditionalModel: auto-alignment is on and model is an alignable type, aligning it if possible" << endl;
+        alignModel(modelId);
     }
 
-    emit modelAdded(model);
+    emit modelAdded(modelId);
 }
 
-void
-Document::addAdditionalModel(Model *model)
-{
-    if (findModelInList(model) != m_models.end()) {
-        SVCERR << "WARNING: Document::addAdditionalModel: Model already added"
-                  << endl;
-        return;
-    }
-
-    ModelRecord rec;
-    rec.model = model;
-    rec.source = nullptr;
-    rec.channel = 0;
-    rec.refcount = 0;
-    rec.additional = true;
-
-    m_models.push_back(rec);
-
-#ifdef DEBUG_DOCUMENT
-    SVDEBUG << "Document::addAdditionalModel: Added model " << model << endl;
-    SVDEBUG << "Models now: ";
-    for (const auto &rec : m_models) {
-        SVDEBUG << rec.model << " ";
-    } 
-    SVDEBUG << endl;
-#endif
-
-    if (m_autoAlignment) {
-        SVDEBUG << "Document::addAdditionalModel: auto-alignment is on, aligning model if possible" << endl;
-        alignModel(model);
-    }
-
-    emit modelAdded(model);
-}
-
-void
-Document::addAggregateModel(AggregateWaveModel *model)
-{
-    connect(model, SIGNAL(modelInvalidated()),
-            this, SLOT(aggregateModelInvalidated()));
-    m_aggregateModels.insert(model);
-    SVDEBUG << "Document::addAggregateModel(" << model << ")" << 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,111 +739,96 @@
         //!!! 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)
 {
-    if (model == nullptr) {
+    // This is called when a layer has been deleted or has replaced
+    // its model, in order to reclaim storage for the old model. It
+    // could be a no-op without making any functional difference, as
+    // all the models stored in the ById pool are released when the
+    // document is deleted. But models can sometimes be large, so if
+    // we know no other layer is using one, we should release it. If
+    // we happen to release one that is being used, the ModelById
+    // borrowed-pointer mechanism will at least prevent memory errors,
+    // although the other code will have to stop whatever it's doing.
+
+    if (auto model = ModelById::get(modelId)) {
+        SVCERR << "Document::releaseModel(" << modelId << "), name "
+               << model->objectName() << ", type "
+               << typeid(*model.get()).name() << endl;
+    } else {
+        SVCERR << "Document::releaseModel(" << modelId << ")" << endl;
+    }
+    
+    if (modelId.isNone()) {
+        return;
+    }
+    
+#ifdef DEBUG_DOCUMENT
+    SVCERR << "Document::releaseModel(" << modelId << ")" << endl;
+#endif
+
+    if (modelId == m_mainModel) {
+#ifdef DEBUG_DOCUMENT
+        SVCERR << "Document::releaseModel: It's the main model, ignoring"
+               << endl;
+#endif
         return;
     }
 
+    if (m_models.find(modelId) == m_models.end()) {
+        // No point in releasing aggregate and alignment models,
+        // they're not large
 #ifdef DEBUG_DOCUMENT
-    SVDEBUG << "Document::releaseModel(" << model << ", type "
-            << model->getTypeName() << ", name \""
-            << model->objectName() << "\")" << endl;
+        SVCERR << "Document::releaseModel: It's not a regular layer model, ignoring" << endl;
 #endif
-    
-    if (model == 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 (mitr->refcount == 0) {
-            SVCERR << "WARNING: Document::releaseModel: model " << model
-                   << " reference count is zero already!" << endl;
-        } else {
+    for (auto layer: m_layers) {
+        if (layer->getModel() == modelId) {
 #ifdef DEBUG_DOCUMENT
-            SVDEBUG << "Lowering refcount from " << mitr->refcount << endl;
+            SVCERR << "Document::releaseModel: It's still in use in at least one layer (e.g. " << layer << ", \"" << layer->getLayerPresentationName() << "\"), ignoring" << endl;
 #endif
-            if (--mitr->refcount == 0) {
-                toDelete = true;
-            }
+            return;
         }
-        isInModelList = true;
-
-    } else if (m_aggregateModels.find(model) != m_aggregateModels.end()) {
-#ifdef DEBUG_DOCUMENT
-        SVDEBUG << "Document::releaseModel: is an aggregate model" << endl;
-#endif
-        toDelete = true;
-    } else { 
-        SVCERR << "WARNING: Document::releaseModel: Unfound model "
-               << model << endl;
-        toDelete = true;
     }
 
-    if (toDelete) {
+#ifdef DEBUG_DOCUMENT
+    SVCERR << "Document::releaseModel: Seems to be OK to release this one"
+           << endl;
+#endif
 
-        int sourceCount = 0;
+    int sourceCount = 0;
 
-        for (auto &rec: m_models) {
-            if (rec.source == model) {
-                ++sourceCount;
-                rec.source = nullptr;
-            }
-        }
-
-        if (sourceCount > 0) {
-            SVDEBUG << "Document::releaseModel: Deleting model "
-                    << model << " even though it is source for "
-                    << sourceCount << " other derived model(s) -- resetting "
-                    << "their source fields appropriately" << endl;
-        }
-
-        if (isInModelList) {
-            deleteModelFromList(model);
-
-#ifdef DEBUG_DOCUMENT
-            SVDEBUG << "Document::releaseModel: Deleted model " << model << endl;
-            SVDEBUG << "Models now: ";
-            for (const auto &r: m_models) {
-                SVDEBUG << r.model << " ";
-            } 
-            SVDEBUG << endl;
-#endif
-        } else {
-            model->aboutToDelete();
-            emit modelAboutToBeDeleted(model);
-            delete model;
-
-#ifdef DEBUG_DOCUMENT
-            SVDEBUG << "Document::releaseModel: Deleted awkward model " << model << endl;
-#endif
+    for (auto &m: m_models) {
+        if (m.second.source == modelId) {
+            ++sourceCount;
+            m.second.source = {};
         }
     }
+
+    if (sourceCount > 0) {
+        SVCERR << "Document::releaseModel: Request to release model "
+               << modelId << " even though it was source for "
+               << sourceCount << " other derived model(s) -- have cleared "
+               << "their source fields" << endl;
+    }
+
+    m_models.erase(modelId);
+    ModelById::release(modelId);
 }
 
 void
@@ -936,47 +892,37 @@
 }
 
 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 (!modelId.isNone() && !previousModel.isNone()) {
+        PlayParameterRepository::getInstance()->copyParameters
+            (previousModel.untyped, modelId.untyped);
     }
 
-    if (model && previousModel) {
-        PlayParameterRepository::getInstance()->copyParameters
-            (previousModel, model);
-    }
+    LayerFactory::getInstance()->setModel(layer, modelId);
 
-    LayerFactory::getInstance()->setModel(layer, model);
-
-    if (previousModel) {
-        releaseModel(previousModel);
-    }
+    releaseModel(previousModel);
 }
 
 void
@@ -988,8 +934,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 +943,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,25 +1021,25 @@
     }
 }
 
-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);
         }
     }
 
@@ -1101,11 +1047,11 @@
 }
 
 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 +1063,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,60 +1095,58 @@
         }
     }
     
-    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());
+
+        ModelId alignmentModelId = ModelById::add(alignment);
+        rm->setAlignment(alignmentModelId);
+        m_alignmentModels.insert(alignmentModelId);
         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()),
-                this, SLOT(performDeferredAlignment()));
+        connect(w.get(), SIGNAL(writeCompleted(ModelId)),
+                this, SLOT(performDeferredAlignment(ModelId)));
         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);
     }
 }
 
 void
-Document::performDeferredAlignment()
+Document::performDeferredAlignment(ModelId modelId)
 {
-    QObject *s = sender();
-    Model *m = dynamic_cast<Model *>(s);
-    if (!m) {
-        SVDEBUG << "Document::performDeferredAlignment: sender is not a Model" << endl;
-    } else {
-        SVDEBUG << "Document::performDeferredAlignment: aligning..." << endl;
-        alignModel(m);
-    }
+    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 +1154,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 +1305,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 +1315,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);
+        auto playParameters =
+            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 +1336,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 +1365,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 +1388,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 +1400,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 +1431,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 +1449,25 @@
             }            
 
             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);
+            auto playParameters =
+                PlayParameterRepository::getInstance()->getPlayParameters
+                (modelId.untyped);
             if (playParameters) {
                 playParameters->toXml
                     (out, indent + "  ",
@@ -1537,11 +1483,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 +1504,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
@@ -1591,7 +1540,10 @@
     // 'type="transform"' in the derivation element.
 
     const Transform &transform = rec.transform;
-
+    
+    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 +1575,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	Fri Jun 14 17:19:37 2019 +0100
+++ b/framework/Document.h	Wed Jul 17 14:25:41 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
@@ -158,21 +158,13 @@
      * transformer process, and the layers are returned through a
      * subsequent call to the provided handler (which must be
      * non-null). The handle returned will be passed through to the
-     * handler callback, and may be also used for cancelling the task.
+     * handler callback.
      */
     LayerCreationAsyncHandle createDerivedLayersAsync(const Transforms &,
                                                       const ModelTransformer::Input &,
                                                       LayerCreationHandler *handler);
 
     /**
-     * Indicate that the async layer creation task associated with the
-     * given handle should be cancelled. There is no guarantee about
-     * what this will mean, and the handler callback may still be
-     * called.
-     */
-    void cancelAsyncLayerCreation(LayerCreationAsyncHandle handle);
-
-    /**
      * Delete the given layer, and also its associated model if no
      * longer used by any other layer.  In general, this should be the
      * only method used to delete layers -- doing so directly is a bit
@@ -183,39 +175,42 @@
     /**
      * 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).
+     * Return true if the model id is known to be the main model or
+     * one of the other existing models that can be shown in a new
+     * layer.
      */
-    const WaveFileModel *getMainModel() const { return m_mainModel; }
-    
-    std::vector<Model *> getTransformInputModels();
-
-    bool isKnownModel(const Model *) const;
+    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,36 +218,33 @@
     /**
      * 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. Document will take responsibility for
+     * releasing the model 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.
+     * Add an imported model, i.e. any model (other than the main
+     * model) that has been created by any means other than as the
+     * output of a transform.  This is necessary to register any
+     * imported model that is to be associated with a layer, and also
+     * to make sure that the model is released by the Document
+     * later. Aggregate models, alignment models, and miscellaneous
+     * temporary models should also be added in this way, unless the
+     * temporary models are large enough to need managing in a way
+     * that guarantees the shortest possible lifespan.
      */
-    void addImportedModel(Model *);
-    
-    /**
-     * 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
-     * for a single transform each and have no refcount. (This
-     * probably isn't ideal!) They are recorded separately from other
-     * models, and will be deleted if any of their component models
-     * are removed.
-     */
-    void addAggregateModel(AggregateWaveModel *);
+    void addNonDerivedModel(ModelId);
 
     /**
      * 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 +312,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 +322,16 @@
     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 performDeferredAlignment();
+    void performDeferredAlignment(ModelId);
     
 protected:
-    void releaseModel(Model *model);
+    void releaseModel(ModelId model);
 
     /**
      * If model is suitable for alignment, align it against the main
@@ -349,7 +339,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 +352,40 @@
      * 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;
+    // 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;
 
-    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();
-    }
+    std::set<ModelId> m_aggregateModels;
+    std::set<ModelId> m_alignmentModels;
     
-    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 +430,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);
 
     /**
--- a/framework/MainWindowBase.cpp	Fri Jun 14 17:19:37 2019 +0100
+++ b/framework/MainWindowBase.cpp	Wed Jul 17 14:25:41 2019 +0100
@@ -63,7 +63,6 @@
 
 #include "base/RecentFiles.h"
 
-#include "base/PlayParameterRepository.h"
 #include "base/XmlExportable.h"
 #include "base/Profiler.h"
 #include "base/Preferences.h"
@@ -105,7 +104,7 @@
 #include <QCheckBox>
 #include <QRegExp>
 #include <QScrollArea>
-#include <QDesktopWidget>
+#include <QScreen>
 #include <QSignalMapper>
 
 #include <iostream>
@@ -179,6 +178,7 @@
     
     qRegisterMetaType<sv_frame_t>("sv_frame_t");
     qRegisterMetaType<sv_samplerate_t>("sv_samplerate_t");
+    qRegisterMetaType<ModelId>("ModelId");
 
 #ifdef Q_WS_X11
     XSetErrorHandler(handle_x11_error);
@@ -507,8 +507,8 @@
 void
 MainWindowBase::resizeConstrained(QSize size)
 {
-    QDesktopWidget *desktop = QApplication::desktop();
-    QRect available = desktop->availableGeometry();
+    QScreen *screen = QApplication::primaryScreen();
+    QRect available = screen->availableGeometry();
     QSize actual(std::min(size.width(), available.width()),
                  std::min(size.height(), available.height()));
     resize(actual);
@@ -575,7 +575,7 @@
     FileFinder *ff = FileFinder::getInstance();
 
     if (type == FileFinder::AnyFile) {
-        if (getMainModel() != nullptr &&
+        if (!getMainModelId().isNone() &&
             m_paneStack != nullptr &&
             m_paneStack->getCurrentPane() != nullptr) { // can import a layer
             return ff->getOpenFileName(FileFinder::AnyFile, m_sessionFile);
@@ -668,7 +668,7 @@
         (haveCurrentPane &&
          (currentLayer != nullptr));
     bool haveMainModel =
-        (getMainModel() != nullptr);
+        (!getMainModelId().isNone());
     bool havePlayTarget =
         (m_playTarget != nullptr || m_audioIO != nullptr);
     bool haveSelection = 
@@ -693,7 +693,7 @@
          !m_viewManager->getClipboard().empty());
     bool haveTabularLayer =
         (haveCurrentLayer &&
-         dynamic_cast<TabularModel *>(currentLayer->getModel()));
+         ModelById::isa<TabularModel>(currentLayer->getModel()));
 
     emit canAddPane(haveMainModel);
     emit canDeleteCurrentPane(haveCurrentPane);
@@ -839,7 +839,7 @@
     if (m_viewManager->getPlaySoloMode()) {
         currentPaneChanged(m_paneStack->getCurrentPane());
     } else {
-        m_viewManager->setPlaybackModel(nullptr);
+        m_viewManager->setPlaybackModel({});
         if (m_playSource) {
             m_playSource->clearSoloModelSet();
         }
@@ -857,11 +857,13 @@
     if (!(m_viewManager &&
           m_playSource &&
           m_viewManager->getPlaySoloMode())) {
-        if (m_viewManager) m_viewManager->setPlaybackModel(nullptr);
+        if (m_viewManager) {
+            m_viewManager->setPlaybackModel(ModelId());
+        }
         return;
     }
 
-    Model *prevPlaybackModel = m_viewManager->getPlaybackModel();
+    ModelId prevPlaybackModel = m_viewManager->getPlaybackModel();
 
     // What we want here is not the currently playing frame (unless we
     // are about to clear out the audio playback buffers -- which may
@@ -878,21 +880,20 @@
     View::ModelSet soloModels = p->getModels();
     
     View::ModelSet sources;
-    for (View::ModelSet::iterator mi = soloModels.begin();
-         mi != soloModels.end(); ++mi) {
+    for (ModelId modelId: sources) {
         // If a model in this pane is derived from something else,
         // then we want to play that model as well -- if the model
         // that's derived from it is not something that is itself
         // individually playable (e.g. a waveform)
-        if (*mi &&
-            !dynamic_cast<RangeSummarisableTimeValueModel *>(*mi) &&
-            (*mi)->getSourceModel()) {
-            sources.insert((*mi)->getSourceModel());
+        if (auto model = ModelById::get(modelId)) {
+            if (!ModelById::isa<RangeSummarisableTimeValueModel>(modelId) &&
+                !model->getSourceModel().isNone()) {
+                sources.insert(model->getSourceModel());
+            }
         }
     }
-    for (View::ModelSet::iterator mi = sources.begin();
-         mi != sources.end(); ++mi) {
-        soloModels.insert(*mi);
+    for (ModelId modelId: sources) {
+        soloModels.insert(modelId);
     }
 
     //!!! Need an "atomic" way of telling the play source that the
@@ -900,23 +901,22 @@
     //the play source should be making the setPlaybackModel call to
     //ViewManager
 
-    for (View::ModelSet::iterator mi = soloModels.begin();
-         mi != soloModels.end(); ++mi) {
-        if (dynamic_cast<RangeSummarisableTimeValueModel *>(*mi)) {
-            m_viewManager->setPlaybackModel(*mi);
+    ModelId newPlaybackModel;
+    
+    for (ModelId modelId: soloModels) {
+        if (ModelById::isa<RangeSummarisableTimeValueModel>(modelId)) {
+            m_viewManager->setPlaybackModel(modelId);
+            newPlaybackModel = modelId;
         }
     }
-    
-    RangeSummarisableTimeValueModel *a = 
-        dynamic_cast<RangeSummarisableTimeValueModel *>(prevPlaybackModel);
-    RangeSummarisableTimeValueModel *b = 
-        dynamic_cast<RangeSummarisableTimeValueModel *>(m_viewManager->
-                                                        getPlaybackModel());
 
     m_playSource->setSoloModelSet(soloModels);
 
-    if (a && b && (a != b)) {
-        if (m_playSource->isPlaying()) m_playSource->play(frame);
+    if (!prevPlaybackModel.isNone() && !newPlaybackModel.isNone() &&
+        prevPlaybackModel != newPlaybackModel) {
+        if (m_playSource->isPlaying()) {
+            m_playSource->play(frame);
+        }
     }
 }
 
@@ -958,7 +958,6 @@
 void
 MainWindowBase::selectAll()
 {
-    if (!getMainModel()) return;
     m_viewManager->setSelection(Selection(getModelsStartFrame(),
                                           getModelsEndFrame()));
 }
@@ -966,23 +965,21 @@
 void
 MainWindowBase::selectToStart()
 {
-    if (!getMainModel()) return;
-    m_viewManager->setSelection(Selection(getMainModel()->getStartFrame(),
+    m_viewManager->setSelection(Selection(getModelsStartFrame(),
                                           m_viewManager->getGlobalCentreFrame()));
 }
 
 void
 MainWindowBase::selectToEnd()
 {
-    if (!getMainModel()) return;
     m_viewManager->setSelection(Selection(m_viewManager->getGlobalCentreFrame(),
-                                          getMainModel()->getEndFrame()));
+                                          getModelsEndFrame()));
 }
 
 void
 MainWindowBase::selectVisible()
 {
-    Model *model = getMainModel();
+    auto model = getMainModel();
     if (!model) return;
 
     Pane *currentPane = m_paneStack->getCurrentPane();
@@ -990,11 +987,17 @@
 
     sv_frame_t startFrame, endFrame;
 
-    if (currentPane->getStartFrame() < 0) startFrame = 0;
-    else startFrame = currentPane->getStartFrame();
-
-    if (currentPane->getEndFrame() > model->getEndFrame()) endFrame = model->getEndFrame();
-    else endFrame = currentPane->getEndFrame();
+    if (currentPane->getStartFrame() < 0) {
+        startFrame = 0;
+    } else {
+        startFrame = currentPane->getStartFrame();
+    }
+
+    if (currentPane->getEndFrame() > model->getEndFrame()) {
+        endFrame = model->getEndFrame();
+    } else {
+        endFrame = currentPane->getEndFrame();
+    }
 
     m_viewManager->setSelection(Selection(startFrame, endFrame));
 }
@@ -1208,9 +1211,8 @@
 
     if (layer) {
     
-        Model *model = layer->getModel();
-        SparseOneDimensionalModel *sodm =
-            dynamic_cast<SparseOneDimensionalModel *>(model);
+        ModelId model = layer->getModel();
+        auto sodm = ModelById::getAs<SparseOneDimensionalModel>(model);
 
         if (sodm) {
             Event point(frame, "");
@@ -1218,7 +1220,7 @@
             bool havePrevPoint = false;
 
             ChangeEventsCommand *command =
-                new ChangeEventsCommand(sodm, tr("Add Point"));
+                new ChangeEventsCommand(model.untyped, tr("Add Point"));
 
             if (m_labeller) {
 
@@ -1255,7 +1257,9 @@
                                   .toText(false).c_str()));
 
             Command *c = command->finish();
-            if (c) CommandHistory::getInstance()->addCommand(c, false);
+            if (c) {
+                CommandHistory::getInstance()->addCommand(c, false);
+            }
         }
     }
 }
@@ -1300,13 +1304,16 @@
     Layer *layer = pane->getSelectedLayer();
     if (!layer) return;
 
-    RegionModel *rm = dynamic_cast<RegionModel *>(layer->getModel());
+    ModelId modelId = layer->getModel();
+    
+    auto rm = ModelById::getAs<RegionModel>(modelId);
     if (rm) {
         Event point(alignedStart,
                     rm->getValueMaximum() + 1,
                     alignedDuration,
                     "");
-        ChangeEventsCommand *command = new ChangeEventsCommand(rm, name);
+        ChangeEventsCommand *command = new ChangeEventsCommand
+            (modelId.untyped, name);
         command->add(point);
         c = command->finish();
     }
@@ -1316,14 +1323,15 @@
         return;
     }
 
-    NoteModel *nm = dynamic_cast<NoteModel *>(layer->getModel());
+    auto nm = ModelById::getAs<NoteModel>(modelId);
     if (nm) {
         Event point(alignedStart,
                     nm->getValueMinimum(),
                     alignedDuration,
                     1.f,
                     "");
-        ChangeEventsCommand *command = new ChangeEventsCommand(nm, name);
+        ChangeEventsCommand *command = new ChangeEventsCommand
+            (modelId.untyped, name);
         command->add(point);
         c = command->finish();
     }
@@ -1344,20 +1352,19 @@
     if (!layer) return;
 
     MultiSelection ms(m_viewManager->getSelection());
+
+    ModelId modelId = layer->getModel();
     
-    Model *model = layer->getModel();
-    SparseOneDimensionalModel *sodm = dynamic_cast<SparseOneDimensionalModel *>
-        (model);
+    auto sodm = ModelById::getAs<SparseOneDimensionalModel>(modelId);
     if (!sodm) return;
 
     if (!m_labeller) return;
 
     Labeller labeller(*m_labeller);
     labeller.setSampleRate(sodm->getSampleRate());
-/*!!! to be updated after SODM API update
-    Command *c = labeller.labelAll<SparseOneDimensionalModel::Point>(*sodm, &ms);
+
+    Command *c = labeller.labelAll(modelId.untyped, &ms, sodm->getAllEvents());
     if (c) CommandHistory::getInstance()->addCommand(c, false);
-*/
 }
 
 void
@@ -1370,10 +1377,10 @@
     if (!layer) return;
 
     MultiSelection ms(m_viewManager->getSelection());
+
+    ModelId modelId = layer->getModel();
     
-    Model *model = layer->getModel();
-    SparseOneDimensionalModel *sodm =
-        dynamic_cast<SparseOneDimensionalModel *>(model);
+    auto sodm = ModelById::getAs<SparseOneDimensionalModel>(modelId);
     if (!sodm) return;
 
     if (!m_labeller) return;
@@ -1381,12 +1388,8 @@
     Labeller labeller(*m_labeller);
     labeller.setSampleRate(sodm->getSampleRate());
 
-    (void)n;
-/*!!! to be updated after SODM API update
-    Command *c = labeller.subdivide<SparseOneDimensionalModel::Point>
-        (*sodm, &ms, n);
+    Command *c = labeller.subdivide(modelId.untyped, &ms, sodm->getAllEvents(), n);
     if (c) CommandHistory::getInstance()->addCommand(c, false);
-*/
 }
 
 void
@@ -1399,10 +1402,10 @@
     if (!layer) return;
 
     MultiSelection ms(m_viewManager->getSelection());
+
+    ModelId modelId = layer->getModel();
     
-    Model *model = layer->getModel();
-    SparseOneDimensionalModel *sodm =
-        dynamic_cast<SparseOneDimensionalModel *>(model);
+    auto sodm = ModelById::getAs<SparseOneDimensionalModel>(modelId);
     if (!sodm) return;
 
     if (!m_labeller) return;
@@ -1410,12 +1413,8 @@
     Labeller labeller(*m_labeller);
     labeller.setSampleRate(sodm->getSampleRate());
 
-    (void)n;
-/*!!! to be updated after SODM API update
-    Command *c = labeller.winnow<SparseOneDimensionalModel::Point>
-        (*sodm, &ms, n);
+    Command *c = labeller.winnow(modelId.untyped, &ms, sodm->getAllEvents(), n);
     if (c) CommandHistory::getInstance()->addCommand(c, false);
-*/
 }
 
 MainWindowBase::FileOpenStatus
@@ -1565,10 +1564,8 @@
         SVDEBUG << "Yes, preserving incoming file rate" << endl;
     }
     
-    ReadOnlyWaveFileModel *newModel = new ReadOnlyWaveFileModel(source, rate);
-
+    auto newModel = std::make_shared<ReadOnlyWaveFileModel>(source, rate);
     if (!newModel->isOK()) {
-        delete newModel;
         m_openingAudioFile = false;
         if (source.wasCancelled()) {
             return FileOpenCancelled;
@@ -1577,12 +1574,13 @@
         }
     }
 
-    return addOpenedAudioModel(source, newModel, mode, templateName, true);
+    auto newModelId = ModelById::add(newModel);
+    return addOpenedAudioModel(source, newModelId, mode, templateName, true);
 }
 
 MainWindowBase::FileOpenStatus
 MainWindowBase::addOpenedAudioModel(FileSource source,
-                                    WaveFileModel *newModel,
+                                    ModelId newModel,
                                     AudioFileOpenMode mode,
                                     QString templateName,
                                     bool registerSource)
@@ -1608,7 +1606,7 @@
                  items, lastMode, &ok);
             
             if (!ok || item.isEmpty()) {
-                delete newModel;
+                ModelById::release(newModel);
                 m_openingAudioFile = false;
                 return FileOpenCancelled;
             }
@@ -1635,7 +1633,7 @@
         if (pane) {
             if (getMainModel()) {
                 View::ModelSet models(pane->getModels());
-                if (models.find(getMainModel()) != models.end()) {
+                if (models.find(getMainModelId()) != models.end()) {
                     // Current pane contains main model: replace that
                     mode = ReplaceMainModel;
                 }
@@ -1652,7 +1650,7 @@
         }
     }
 
-    if (mode == CreateAdditionalModel && !getMainModel()) {
+    if (mode == CreateAdditionalModel && getMainModelId().isNone()) {
         SVDEBUG << "Mode is CreateAdditionalModel but we have no main model, switching to ReplaceSession mode" << endl;
         mode = ReplaceSession;
     }
@@ -1690,12 +1688,10 @@
     
     if (mode == ReplaceMainModel) {
 
-        Model *prevMain = getMainModel();
-        if (prevMain) {
+        ModelId prevMain = getMainModelId();
+        if (!prevMain.isNone()) {
             m_playSource->removeModel(prevMain);
-            PlayParameterRepository::getInstance()->removePlayable(prevMain);
         }
-        PlayParameterRepository::getInstance()->addPlayable(newModel);
 
         SVDEBUG << "SV about to call setMainModel(" << newModel << "): prevMain is " << prevMain << endl;
 
@@ -1728,7 +1724,7 @@
         CommandHistory::getInstance()->startCompoundOperation
             (tr("Import \"%1\"").arg(source.getBasename()), true);
 
-        m_document->addImportedModel(newModel);
+        m_document->addNonDerivedModel(newModel);
 
         AddPaneCommand *command = new AddPaneCommand(this);
         CommandHistory::getInstance()->addCommand(command);
@@ -1772,7 +1768,7 @@
         CommandHistory::getInstance()->startCompoundOperation
             (tr("Import \"%1\"").arg(source.getBasename()), true);
 
-        m_document->addImportedModel(newModel);
+        m_document->addNonDerivedModel(newModel);
 
         if (replace) {
             m_document->removeLayerFromView(pane, replace);
@@ -1932,30 +1928,33 @@
 
             MIDIFileImportDialog midiDlg(this);
 
-            Model *model = DataFileReaderFactory::loadNonCSV
+            Model *newModelPtr = DataFileReaderFactory::loadNonCSV
                 (path, &midiDlg, getMainModel()->getSampleRate());
         
-            if (!model) {
+            if (!newModelPtr) {
                 CSVFormatDialog *dialog =
                     new CSVFormatDialog(this,
                                         path,
                                         getMainModel()->getSampleRate(),
                                         5);
                 if (dialog->exec() == QDialog::Accepted) {
-                    model = DataFileReaderFactory::loadCSV
+                    newModelPtr = DataFileReaderFactory::loadCSV
                         (path, dialog->getFormat(),
                          getMainModel()->getSampleRate());
                 }
                 delete dialog;
             }
 
-            if (model) {
+            if (newModelPtr) {
 
                 SVDEBUG << "MainWindowBase::openLayer: Have model" << endl;
 
                 emit activity(tr("Import MIDI file \"%1\"").arg(source.getLocation()));
 
-                Layer *newLayer = m_document->createImportedLayer(model);
+                ModelId modelId = 
+                    ModelById::add(std::shared_ptr<Model>(newModelPtr));
+                
+                Layer *newLayer = m_document->createImportedLayer(modelId);
 
                 if (newLayer) {
 
@@ -1996,7 +1995,7 @@
         return FileOpenWrongMode;
     }
 
-    if (!m_document->getMainModel()) {
+    if (!getMainModel()) {
         return FileOpenWrongMode;
     }
 
@@ -2369,11 +2368,11 @@
         return FileOpenFailed;
     }
 
-    std::vector<Model *> models = importer.getDataModels(&dialog);
+    std::vector<ModelId> modelIds = importer.getDataModels(&dialog);
 
     dialog.setMessage(tr("Importing from RDF..."));
 
-    if (models.empty()) {
+    if (modelIds.empty()) {
         QMessageBox::critical
             (this, tr("Failed to import RDF"),
              tr("<b>Failed to import RDF</b><p>No suitable data models found for import from RDF document at \"%1\"</p>").arg(source.getLocation()));
@@ -2381,15 +2380,12 @@
     }
 
     emit activity(tr("Import RDF document \"%1\"").arg(source.getLocation()));
-
-    std::set<Model *> added;
-
-    for (int i = 0; i < (int)models.size(); ++i) {
-
-        Model *m = models[i];
-        WaveFileModel *w = dynamic_cast<WaveFileModel *>(m);
-
-        if (w) {
+    
+    std::set<ModelId> added;
+
+    for (auto modelId: modelIds) {
+        
+        if (ModelById::isa<WaveFileModel>(modelId)) {
 
             Pane *pane = addPaneToStack();
             Layer *layer = nullptr;
@@ -2399,24 +2395,29 @@
             }
 
             if (!getMainModel()) {
-                m_document->setMainModel(w);
+                m_document->setMainModel(modelId);
                 layer = m_document->createMainModelLayer(LayerFactory::Waveform);
             } else {
-                layer = m_document->createImportedLayer(w);
+                layer = m_document->createImportedLayer(modelId);
             }
 
             m_document->addLayerToView(pane, layer);
 
-            added.insert(w);
-            
-            for (int j = 0; j < (int)models.size(); ++j) {
-
-                Model *dm = models[j];
-
-                if (dm == m) continue;
-                if (dm->getSourceModel() != m) continue;
-
-                layer = m_document->createImportedLayer(dm);
+            added.insert(modelId);
+
+            for (auto otherId: modelIds) {
+
+                if (otherId == modelId) continue;
+
+                bool isDependent = false;
+                if (auto dm = ModelById::get(otherId)) {
+                    if (dm->getSourceModel() == modelId) {
+                        isDependent = true;
+                    }
+                }
+                if (!isDependent) continue;
+
+                layer = m_document->createImportedLayer(otherId);
 
                 if (layer->isLayerOpaque() ||
                     dynamic_cast<Colour3DPlotLayer *>(layer)) {
@@ -2462,18 +2463,16 @@
                     m_document->addLayerToView(pane, layer);
                 }
 
-                added.insert(dm);
+                added.insert(otherId);
             }
         }
     }
 
-    for (int i = 0; i < (int)models.size(); ++i) {
-
-        Model *m = models[i];
-
-        if (added.find(m) == added.end()) {
+    for (auto modelId : modelIds) {
+        
+        if (added.find(modelId) == added.end()) {
             
-            Layer *layer = m_document->createImportedLayer(m);
+            Layer *layer = m_document->createImportedLayer(modelId);
             if (!layer) return FileOpenFailed;
 
             Pane *singleLayerPane = addPaneToStack();
@@ -2651,18 +2650,17 @@
     }
 }
 
-WaveFileModel *
-MainWindowBase::getMainModel()
+ModelId
+MainWindowBase::getMainModelId() const
 {
-    if (!m_document) return nullptr;
+    if (!m_document) return {};
     return m_document->getMainModel();
 }
 
-const WaveFileModel *
+std::shared_ptr<WaveFileModel>
 MainWindowBase::getMainModel() const
 {
-    if (!m_document) return nullptr;
-    return m_document->getMainModel();
+    return ModelById::getAs<WaveFileModel>(getMainModelId());
 }
 
 void
@@ -2679,19 +2677,17 @@
     connect(m_document, SIGNAL(layerInAView(Layer *, bool)),
             this, SLOT(layerInAView(Layer *, bool)));
 
-    connect(m_document, SIGNAL(modelAdded(Model *)),
-            this, SLOT(modelAdded(Model *)));
-    connect(m_document, SIGNAL(mainModelChanged(WaveFileModel *)),
-            this, SLOT(mainModelChanged(WaveFileModel *)));
-    connect(m_document, SIGNAL(modelAboutToBeDeleted(Model *)),
-            this, SLOT(modelAboutToBeDeleted(Model *)));
+    connect(m_document, SIGNAL(modelAdded(ModelId )),
+            this, SLOT(modelAdded(ModelId )));
+    connect(m_document, SIGNAL(mainModelChanged(ModelId)),
+            this, SLOT(mainModelChanged(ModelId)));
 
     connect(m_document, SIGNAL(modelGenerationFailed(QString, QString)),
             this, SLOT(modelGenerationFailed(QString, QString)));
     connect(m_document, SIGNAL(modelRegenerationWarning(QString, QString, QString)),
             this, SLOT(modelRegenerationWarning(QString, QString, QString)));
-    connect(m_document, SIGNAL(alignmentComplete(AlignmentModel *)),
-            this, SLOT(alignmentComplete(AlignmentModel *)));
+    connect(m_document, SIGNAL(alignmentComplete(ModelId)),
+            this, SLOT(alignmentComplete(ModelId)));
     connect(m_document, SIGNAL(alignmentFailed(QString)),
             this, SLOT(alignmentFailed(QString)));
 
@@ -2791,7 +2787,11 @@
 
     QString suffix = QFileInfo(path).suffix().toLower();
 
-    Model *model = layer->getModel();
+    auto model = ModelById::get(layer->getModel());
+    if (!model) {
+        error = tr("Internal error: unknown model");
+        return false;
+    }
 
     if (suffix == "xml" || suffix == "svl") {
 
@@ -2819,12 +2819,12 @@
 
     } else if (suffix == "mid" || suffix == "midi") {
 
-        NoteModel *nm = dynamic_cast<NoteModel *>(model);
+        auto nm = ModelById::getAs<NoteModel>(layer->getModel());
 
         if (!nm) {
             error = tr("Can't export non-note layers to MIDI");
         } else {
-            MIDIFileWriter writer(path, nm, nm->getSampleRate());
+            MIDIFileWriter writer(path, nm.get(), nm->getSampleRate());
             writer.write();
             if (!writer.isOK()) {
                 error = writer.getError();
@@ -2833,10 +2833,10 @@
 
     } else if (suffix == "ttl" || suffix == "n3") {
 
-        if (!RDFExporter::canExportModel(model)) {
+        if (!RDFExporter::canExportModel(model.get())) {
             error = tr("Sorry, cannot export this layer type to RDF (supported types are: region, note, text, time instants, time values)");
         } else {
-            RDFExporter exporter(path, model);
+            RDFExporter exporter(path, model.get());
             exporter.write();
             if (!exporter.isOK()) {
                 error = exporter.getError();
@@ -2845,7 +2845,7 @@
 
     } else {
 
-        CSVFileWriter writer(path, model,
+        CSVFileWriter writer(path, model.get(),
                              ((suffix == "csv") ? "," : "\t"));
         writer.write();
 
@@ -2923,7 +2923,7 @@
     Pane *currentPane = m_paneStack->getCurrentPane();
     if (!currentPane) return;
 
-    Model *model = getMainModel();
+    auto model = getMainModel();
     if (!model) return;
     
     sv_frame_t start = model->getStartFrame();
@@ -3243,8 +3243,8 @@
     SVCERR << "MainWindowBase::record: about to resume" << endl;
     m_audioIO->resume();
 
-    WritableWaveFileModel *model = m_recordTarget->startRecording();
-    if (!model) {
+    WritableWaveFileModel *modelPtr = m_recordTarget->startRecording();
+    if (!modelPtr) {
         SVCERR << "ERROR: MainWindowBase::record: Recording failed" << endl;
         QMessageBox::critical
             (this, tr("Recording failed"),
@@ -3253,18 +3253,20 @@
         return;
     }
 
-    if (!model->isOK()) {
+    if (!modelPtr->isOK()) {
         SVCERR << "MainWindowBase::record: Model not OK, stopping and suspending" << endl;
         m_recordTarget->stopRecording();
         m_audioIO->suspend();
         if (action) action->setChecked(false);
-        delete model;
+        delete modelPtr;
         return;
     }
 
     SVCERR << "MainWindowBase::record: Model is OK, continuing..." << endl;
+
+    QString location = modelPtr->getLocation();
     
-    PlayParameterRepository::getInstance()->addPlayable(model);
+    auto modelId = ModelById::add(std::shared_ptr<Model>(modelPtr));
 
     if (m_audioRecordMode == RecordReplaceSession || !getMainModel()) {
 
@@ -3279,7 +3281,7 @@
                 SVCERR << "MainWindowBase::record: Session template open cancelled, stopping and suspending" << endl;
                 m_recordTarget->stopRecording();
                 m_audioIO->suspend();
-                PlayParameterRepository::getInstance()->removePlayable(model);
+                ModelById::release(modelId);
                 return;
             }
             if (tplStatus != FileOpenFailed) {
@@ -3292,17 +3294,16 @@
             createDocument();
         }
         
-        Model *prevMain = getMainModel();
-        if (prevMain) {
+        ModelId prevMain = getMainModelId();
+        if (!prevMain.isNone()) {
             m_playSource->removeModel(prevMain);
-            PlayParameterRepository::getInstance()->removePlayable(prevMain);
         }
         
-        m_document->setMainModel(model);
+        m_document->setMainModel(modelId);
         setupMenus();
         findTimeRulerLayer();
 
-        m_originalLocation = model->getLocation();
+        m_originalLocation = location;
         
         if (loadedTemplate || (m_sessionFile == "")) {
             CommandHistory::getInstance()->clear();
@@ -3317,7 +3318,7 @@
         CommandHistory::getInstance()->startCompoundOperation
             (tr("Import Recorded Audio"), true);
 
-        m_document->addImportedModel(model);
+        m_document->addNonDerivedModel(modelId);
 
         AddPaneCommand *command = new AddPaneCommand(this);
         CommandHistory::getInstance()->addCommand(command);
@@ -3328,7 +3329,7 @@
             m_document->addLayerToView(pane, m_timeRulerLayer);
         }
 
-        Layer *newLayer = m_document->createImportedLayer(model);
+        Layer *newLayer = m_document->createImportedLayer(modelId);
 
         if (newLayer) {
             m_document->addLayerToView(pane, newLayer);
@@ -3338,7 +3339,7 @@
     }
 
     updateMenuStates();
-    m_recentFiles.addFile(model->getLocation());
+    m_recentFiles.addFile(location);
     currentPaneChanged(m_paneStack->getCurrentPane());
     
     emit audioFileLoaded();
@@ -3724,10 +3725,7 @@
     if (pane) layer = pane->getSelectedLayer();
     if (!layer) return;
 
-    Model *model = layer->getModel();
-    if (!model) return;
-
-    TabularModel *tabular = dynamic_cast<TabularModel *>(model);
+    auto tabular = ModelById::getAs<TabularModel>(layer->getModel());
     if (!tabular) {
         //!!! how to prevent this function from being active if not
         //appropriate model type?  or will we ultimately support
@@ -3746,7 +3744,8 @@
 
     QString title = layer->getLayerPresentationName();
 
-    ModelDataTableDialog *dialog = new ModelDataTableDialog(tabular, title, this);
+    ModelDataTableDialog *dialog = new ModelDataTableDialog
+        (layer->getModel(), title, this);
     dialog->setAttribute(Qt::WA_DeleteOnClose);
     
     connectLayerEditDialog(dialog);
@@ -4005,10 +4004,10 @@
     if (!inAView) removeLayerEditDialog(layer);
 
     // Check whether we need to add or remove model from play source
-    Model *model = layer->getModel();
-    if (model) {
+    ModelId modelId = layer->getModel();
+    if (!modelId.isNone()) {
         if (inAView) {
-            m_playSource->addModel(model);
+            m_playSource->addModel(modelId);
         } else {
             bool found = false;
             for (int i = 0; i < m_paneStack->getPaneCount(); ++i) {
@@ -4018,7 +4017,7 @@
                     Layer *pl = pane->getLayer(j);
                     if (pl &&
                         !dynamic_cast<TimeRulerLayer *>(pl) &&
-                        (pl->getModel() == model)) {
+                        (pl->getModel() == modelId)) {
                         found = true;
                         break;
                     }
@@ -4026,7 +4025,7 @@
                 if (found) break;
             }
             if (!found) {
-                m_playSource->removeModel(model);
+                m_playSource->removeModel(modelId);
             }
         }
     }
@@ -4052,18 +4051,19 @@
 }
 
 void
-MainWindowBase::modelAdded(Model *model)
+MainWindowBase::modelAdded(ModelId model)
 {
 //    SVDEBUG << "MainWindowBase::modelAdded(" << model << ")" << endl;
-        std::cerr << "\nAdding model " << model->getTypeName() << " to playsource " << std::endl;
+    std::cerr << "\nAdding model " << model << " to playsource " << std::endl;
     m_playSource->addModel(model);
 }
 
 void
-MainWindowBase::mainModelChanged(WaveFileModel *model)
+MainWindowBase::mainModelChanged(ModelId modelId)
 {
 //    SVDEBUG << "MainWindowBase::mainModelChanged(" << model << ")" << endl;
     updateDescriptionLabel();
+    auto model = ModelById::getAs<WaveFileModel>(modelId);
     if (model) m_viewManager->setMainModelSampleRate(model->getSampleRate());
     if (model && !(m_playTarget || m_audioIO) &&
         (m_soundOptions & WithAudioOutput)) {
@@ -4072,16 +4072,6 @@
 }
 
 void
-MainWindowBase::modelAboutToBeDeleted(Model *model)
-{
-//    SVDEBUG << "MainWindowBase::modelAboutToBeDeleted(" << model << ")" << endl;
-    if (model == m_viewManager->getPlaybackModel()) {
-        m_viewManager->setPlaybackModel(nullptr);
-    }
-    m_playSource->removeModel(model);
-}
-
-void
 MainWindowBase::paneDeleteButtonClicked(Pane *pane)
 {
     bool found = false;
@@ -4118,9 +4108,9 @@
 }
 
 void
-MainWindowBase::alignmentComplete(AlignmentModel *model)
+MainWindowBase::alignmentComplete(ModelId alignmentModelId)
 {
-    cerr << "MainWindowBase::alignmentComplete(" << model << ")" << endl;
+    cerr << "MainWindowBase::alignmentComplete(" << alignmentModelId << ")" << endl;
 }
 
 void
--- a/framework/MainWindowBase.h	Fri Jun 14 17:19:37 2019 +0100
+++ b/framework/MainWindowBase.h	Wed Jul 17 14:25:41 2019 +0100
@@ -35,6 +35,8 @@
 #include "data/fileio/FileSource.h"
 #include "data/osc/OSCQueue.h"
 #include "data/osc/OSCMessageCallback.h"
+#include "data/model/Model.h"
+
 #include <map>
 
 class Document;
@@ -302,9 +304,8 @@
     virtual void layerAboutToBeDeleted(Layer *);
     virtual void layerInAView(Layer *, bool);
 
-    virtual void mainModelChanged(WaveFileModel *);
-    virtual void modelAdded(Model *);
-    virtual void modelAboutToBeDeleted(Model *);
+    virtual void mainModelChanged(ModelId);
+    virtual void modelAdded(ModelId);
 
     virtual void updateMenuStates();
     virtual void updateDescriptionLabel() = 0;
@@ -315,7 +316,7 @@
     virtual void modelRegenerationFailed(QString, QString, QString) = 0;
     virtual void modelRegenerationWarning(QString, QString, QString) = 0;
 
-    virtual void alignmentComplete(AlignmentModel *);
+    virtual void alignmentComplete(ModelId);
     virtual void alignmentFailed(QString) = 0;
 
     virtual void rightButtonMenuRequested(Pane *, QPoint point) = 0;
@@ -418,12 +419,12 @@
     mutable QLabel *m_statusLabel;
     QLabel *getStatusLabel() const;
 
-    WaveFileModel *getMainModel();
-    const WaveFileModel *getMainModel() const;
+    ModelId getMainModelId() const;
+    std::shared_ptr<WaveFileModel> getMainModel() const;
     void createDocument();
 
     FileOpenStatus addOpenedAudioModel(FileSource source,
-                                       WaveFileModel *model,
+                                       ModelId model,
                                        AudioFileOpenMode mode,
                                        QString templateName,
                                        bool registerSource);
--- a/framework/SVFileReader.cpp	Fri Jun 14 17:19:37 2019 +0100
+++ b/framework/SVFileReader.cpp	Wed Jul 17 14:25:41 2019 +0100
@@ -58,12 +58,9 @@
     m_paneCallback(callback),
     m_location(location),
     m_currentPane(nullptr),
+    m_currentDataset(XmlExportable::NO_ID),
     m_currentLayer(nullptr),
-    m_currentDataset(nullptr),
-    m_currentDerivedModel(nullptr),
-    m_currentDerivedModelId(-1),
-    m_currentPlayParameters(nullptr),
-    m_currentTransformSource(nullptr),
+    m_pendingDerivedModel(XmlExportable::NO_ID),
     m_currentTransformChannel(0),
     m_currentTransformIsNewStyle(true),
     m_datasetSeparator(" "),
@@ -108,24 +105,31 @@
                   << endl;
     }
 
-    std::set<Model *> unaddedModels;
+    std::set<ModelId> unaddedModels;
 
-    for (std::map<int, Model *>::iterator i = m_models.begin();
-         i != m_models.end(); ++i) {
-        if (m_addedModels.find(i->second) == m_addedModels.end()) {
-            unaddedModels.insert(i->second);
+    for (auto i: m_models) {
+        if (m_addedModels.find(i.second) == m_addedModels.end()) {
+            unaddedModels.insert(i.second);
         }
     }
 
     if (!unaddedModels.empty()) {
         SVCERR << "WARNING: SV-XML: File contained "
-                  << unaddedModels.size() << " unused models"
-                  << endl;
-        while (!unaddedModels.empty()) {
-            delete *unaddedModels.begin();
-            unaddedModels.erase(unaddedModels.begin());
+               << unaddedModels.size() << " unused models"
+               << endl;
+        for (auto m: unaddedModels) {
+            ModelById::release(m);
         }
-    }        
+    }
+
+    if (!m_paths.empty()) {
+        SVCERR << "WARNING: SV-XML: File contained "
+               << m_paths.size() << " unused paths"
+               << endl;
+        for (auto p: m_paths) {
+            delete p.second;
+        }
+    }
 }
 
 bool
@@ -277,15 +281,13 @@
 
     if (name == "dataset") {
 
-        if (m_currentDataset) {
+        if (m_currentDataset != XmlExportable::NO_ID) {
             
             bool foundInAwaiting = false;
 
-            for (std::map<int, int>::iterator i = m_awaitingDatasets.begin();
-                 i != m_awaitingDatasets.end(); ++i) {
-                if (haveModel(i->second) &&
-                    m_models[i->second] == m_currentDataset) {
-                    m_awaitingDatasets.erase(i);
+            for (auto i: m_awaitingDatasets) {
+                if (i.second == m_currentDataset) {
+                    m_awaitingDatasets.erase(i.first);
                     foundInAwaiting = true;
                     break;
                 }
@@ -296,7 +298,7 @@
             }
         }
 
-        m_currentDataset = nullptr;
+        m_currentDataset = XmlExportable::NO_ID;
 
     } else if (name == "data") {
 
@@ -305,23 +307,23 @@
 
     } else if (name == "derivation") {
 
-        if (!m_currentDerivedModel) {
-            if (m_currentDerivedModelId < 0) {
-                SVCERR << "WARNING: SV-XML: Bad derivation output model id "
-                          << m_currentDerivedModelId << endl;
-            } else if (haveModel(m_currentDerivedModelId)) {
+        if (m_currentDerivedModel.isNone()) {
+            if (m_pendingDerivedModel == XmlExportable::NO_ID) {
+                SVCERR << "WARNING: SV-XML: No valid output model id "
+                       << "for derivation" << endl;
+            } else if (haveModel(m_pendingDerivedModel)) {
                 SVCERR << "WARNING: SV-XML: Derivation has existing model "
-                          << m_currentDerivedModelId
-                          << " as target, not regenerating" << endl;
+                       << m_pendingDerivedModel
+                       << " as target, not regenerating" << endl;
             } else {
                 QString message;
-                m_currentDerivedModel = m_models[m_currentDerivedModelId] =
+                m_currentDerivedModel = m_models[m_pendingDerivedModel] =
                     m_document->addDerivedModel
                     (m_currentTransform,
                      ModelTransformer::Input(m_currentTransformSource,
                                              m_currentTransformChannel),
                      message);
-                if (!m_currentDerivedModel) {
+                if (m_currentDerivedModel.isNone()) {
                     emit modelRegenerationFailed(tr("(derived model in SV-XML)"),
                                                  m_currentTransform.getIdentifier(),
                                                  message);
@@ -340,9 +342,9 @@
         }
 
         m_addedModels.insert(m_currentDerivedModel);
-        m_currentDerivedModel = nullptr;
-        m_currentDerivedModelId = -1;
-        m_currentTransformSource = nullptr;
+        m_currentDerivedModel = {};
+        m_pendingDerivedModel = XmlExportable::NO_ID;
+        m_currentTransformSource = {};
         m_currentTransform = Transform();
         m_currentTransformChannel = -1;
 
@@ -355,7 +357,7 @@
     } else if (name == "selections") {
         m_inSelections = false;
     } else if (name == "playparameters") {
-        m_currentPlayParameters = nullptr;
+        m_currentPlayParameters = {};
     }
 
     return true;
@@ -406,7 +408,7 @@
 void
 SVFileReader::makeAggregateModels()
 {
-    std::map<int, PendingAggregateRec> stillPending;
+    std::map<ExportId, PendingAggregateRec> stillPending;
     
     for (auto p: m_pendingAggregates) {
 
@@ -415,20 +417,27 @@
         bool skip = false;
 
         AggregateWaveModel::ChannelSpecList specs;
-        for (int componentId: rec.components) {
+        for (ExportId componentId: rec.components) {
             bool found = false;
             if (m_models.find(componentId) != m_models.end()) {
-                RangeSummarisableTimeValueModel *rs =
-                    dynamic_cast<RangeSummarisableTimeValueModel *>
-                    (m_models[componentId]);
+                ModelId modelId = m_models[componentId];
+                auto rs = ModelById::getAs<RangeSummarisableTimeValueModel>
+                    (modelId);
                 if (rs) {
                     specs.push_back(AggregateWaveModel::ModelChannelSpec
-                                    (rs, -1));
+                                    (modelId, -1));
                     found = true;
+                } else {
+                    SVDEBUG << "SVFileReader::makeAggregateModels: "
+                            << "Component model id " << componentId
+                            << "in aggregate model id " << id
+                            << "does not appear to be convertible to "
+                            << "RangeSummarisableTimeValueModel"
+                            << endl;
                 }
             }
             if (!found) {
-                SVDEBUG << "SVFileReader::makeAggregateModels:"
+                SVDEBUG << "SVFileReader::makeAggregateModels: "
                         << "Unknown component model id "
                         << componentId << " in aggregate model id " << id
                         << ", hoping we won't be needing it just yet"
@@ -440,13 +449,13 @@
         if (skip) {
             stillPending[id] = rec;
         } else {
-            AggregateWaveModel *model = new AggregateWaveModel(specs);
+            auto model = std::make_shared<AggregateWaveModel>(specs);
             model->setObjectName(rec.name);
+            m_models[id] = ModelById::add(model);
 
-            SVDEBUG << "SVFileReader::makeAggregateModels: created aggregate model id "
-                    << id << " with " << specs.size() << " components" << endl;
-        
-            m_models[id] = model;
+            SVDEBUG << "SVFileReader::makeAggregateModels: created aggregate "
+                    << "model id " << id << " with " << specs.size()
+                    << " components" << endl;
         }
     }
 
@@ -457,32 +466,21 @@
 SVFileReader::addUnaddedModels()
 {
     makeAggregateModels();
-    
-    for (std::map<int, Model *>::iterator i = m_models.begin();
-         i != m_models.end(); ++i) {
 
-        Model *model = i->second;
+    for (auto i: m_models) {
 
-        if (m_addedModels.find(model) != m_addedModels.end()) {
+        ModelId modelId = i.second;
+
+        if (m_addedModels.find(modelId) != m_addedModels.end()) {
             // already added this one
             continue;
         }
+
+        m_document->addNonDerivedModel(modelId);
         
-        // don't want to add path and alignment models to the
-        // document, because their lifespans are entirely dictated by
-        // the models that "own" them even though they were read
-        // independently from the .sv file.  (pity we don't have a
-        // nicer way to handle this)
-        if (!dynamic_cast<PathModel *>(model) &&
-            !dynamic_cast<AlignmentModel *>(model)) {
-            
-            m_document->addImportedModel(model);
-        }
-        
-        // but we add all models including path and alignment ones to
-        // the added set, so they don't get deleted from our own
-        // destructor
-        m_addedModels.insert(model);
+        // make a note of all models that have been added to the
+        // document, so they don't get released by our own destructor
+        m_addedModels.insert(modelId);
     }
 }
 
@@ -538,7 +536,8 @@
             } else if (rate == 0 &&
                        !isMainModel &&
                        Preferences::getInstance()->getResampleOnLoad()) {
-                WaveFileModel *mm = m_document->getMainModel();
+                auto mm = ModelById::getAs<WaveFileModel>
+                    (m_document->getMainModel());
                 if (mm) rate = mm->getSampleRate();
             }
 
@@ -555,10 +554,13 @@
         }
 
         model->setObjectName(name);
-        m_models[id] = model;
+
+        ModelId modelId = ModelById::add(std::shared_ptr<Model>(model));
+        m_models[id] = modelId;
+        
         if (isMainModel) {
-            m_document->setMainModel(model);
-            m_addedModels.insert(model);
+            m_document->setMainModel(modelId);
+            m_addedModels.insert(modelId);
         }
         // Derived models will be added when their derivation
         // is found.
@@ -570,13 +572,13 @@
         QString components = attributes.value("components");
         QStringList componentIdStrings = components.split(",");
         std::vector<int> componentIds;
-        for (auto cid: componentIdStrings) {
+        for (auto cidStr: componentIdStrings) {
             bool ok = false;
-            int id = cid.toInt(&ok);
+            int cid = cidStr.toInt(&ok);
             if (!ok) {
-                SVCERR << "SVFileReader::readModel: Failed to convert component model id from part \"" << cid << "\" in \"" << components << "\"" << endl;
+                SVCERR << "SVFileReader::readModel: Failed to convert component model id from part \"" << cidStr << "\" in \"" << components << "\"" << endl;
             } else {
-                componentIds.push_back(id);
+                componentIds.push_back(cid);
             }
         }
         PendingAggregateRec rec { name, sampleRate, componentIds };
@@ -603,10 +605,12 @@
             READ_MANDATORY(int, windowSize, toInt);
             READ_MANDATORY(int, yBinCount, toInt);
             
-            EditableDenseThreeDimensionalModel *model =
-                new EditableDenseThreeDimensionalModel
+            auto model = std::make_shared<EditableDenseThreeDimensionalModel>
                 (sampleRate, windowSize, yBinCount,
                  EditableDenseThreeDimensionalModel::NoCompression);
+
+            model->setObjectName(name);
+            m_models[id] = ModelById::add(model);
             
             float minimum = attributes.value("minimum").trimmed().toFloat(&ok);
             if (ok) model->setMinimumLevel(minimum);
@@ -620,8 +624,6 @@
             int startFrame = attributes.value("startFrame").trimmed().toInt(&ok);
             if (ok) model->setStartFrame(startFrame);
 
-            model->setObjectName(name);
-            m_models[id] = model;
             return true;
 
         } else {
@@ -636,21 +638,21 @@
         if (dimensions == 1) {
             
             READ_MANDATORY(int, resolution, toInt);
-
+            
             if (attributes.value("subtype") == "image") {
 
                 bool notifyOnAdd = (attributes.value("notifyOnAdd") == "true");
-                ImageModel *model = new ImageModel(sampleRate, resolution,
-                                                   notifyOnAdd);
+                auto model = std::make_shared<ImageModel>
+                    (sampleRate, resolution, notifyOnAdd);
                 model->setObjectName(name);
-                m_models[id] = model;
+                m_models[id] = ModelById::add(model);
 
             } else {
 
-                SparseOneDimensionalModel *model = new SparseOneDimensionalModel
+                auto model = std::make_shared<SparseOneDimensionalModel>
                     (sampleRate, resolution);
                 model->setObjectName(name);
-                m_models[id] = model;
+                m_models[id] = ModelById::add(model);
             }
 
             int dataset = attributes.value("dataset").trimmed().toInt(&ok);
@@ -677,73 +679,74 @@
 
             if (dimensions == 2) {
                 if (attributes.value("subtype") == "text") {
-                    TextModel *model = new TextModel
+                    auto model = std::make_shared<TextModel>
                         (sampleRate, resolution, notifyOnAdd);
                     model->setObjectName(name);
-                    m_models[id] = model;
+                    m_models[id] = ModelById::add(model);
                 } else if (attributes.value("subtype") == "path") {
-                    PathModel *model = new PathModel
-                        (sampleRate, resolution, notifyOnAdd);
-                    model->setObjectName(name);
-                    m_models[id] = model;
+                    // Paths are no longer actually models
+                    Path *path = new Path(sampleRate, resolution);
+                    m_paths[id] = path;
                 } else {
-                    SparseTimeValueModel *model;
+                    std::shared_ptr<SparseTimeValueModel> model;
                     if (haveMinMax) {
-                        model = new SparseTimeValueModel
-                            (sampleRate, resolution, minimum, maximum, notifyOnAdd);
+                        model = std::make_shared<SparseTimeValueModel>
+                            (sampleRate, resolution, minimum, maximum,
+                             notifyOnAdd);
                     } else {
-                        model = new SparseTimeValueModel
+                        model = std::make_shared<SparseTimeValueModel>
                             (sampleRate, resolution, notifyOnAdd);
                     }
                     model->setScaleUnits(units);
                     model->setObjectName(name);
-                    m_models[id] = model;
+                    m_models[id] = ModelById::add(model);
                 }
             } else {
                 if (attributes.value("subtype") == "region") {
-                    RegionModel *model;
+                    std::shared_ptr<RegionModel> model;
                     if (haveMinMax) {
-                        model = new RegionModel
-                            (sampleRate, resolution, minimum, maximum, notifyOnAdd);
+                        model = std::make_shared<RegionModel>
+                            (sampleRate, resolution, minimum, maximum,
+                             notifyOnAdd);
                     } else {
-                        model = new RegionModel
+                        model = std::make_shared<RegionModel>
                             (sampleRate, resolution, notifyOnAdd);
                     }
                     model->setValueQuantization(valueQuantization);
                     model->setScaleUnits(units);
                     model->setObjectName(name);
-                    m_models[id] = model;
+                    m_models[id] = ModelById::add(model);
                 } else if (attributes.value("subtype") == "flexinote") {
-                    NoteModel *model;
+                    std::shared_ptr<NoteModel> model;
                     if (haveMinMax) {
-                        model = new NoteModel
+                        model = std::make_shared<NoteModel>
                             (sampleRate, resolution, minimum, maximum,
                              notifyOnAdd,
                              NoteModel::FLEXI_NOTE);
                     } else {
-                        model = new NoteModel
+                        model = std::make_shared<NoteModel>
                             (sampleRate, resolution, notifyOnAdd,
                              NoteModel::FLEXI_NOTE);
                     }
                     model->setValueQuantization(valueQuantization);
                     model->setScaleUnits(units);
                     model->setObjectName(name);
-                    m_models[id] = model;
+                    m_models[id] = ModelById::add(model);
                 } else {
                     // note models written out by SV 1.3 and earlier
                     // have no subtype, so we can't test that
-                    NoteModel *model;
+                    std::shared_ptr<NoteModel> model;
                     if (haveMinMax) {
-                        model = new NoteModel
+                        model = std::make_shared<NoteModel>
                             (sampleRate, resolution, minimum, maximum, notifyOnAdd);
                     } else {
-                        model = new NoteModel
+                        model = std::make_shared<NoteModel>
                             (sampleRate, resolution, notifyOnAdd);
                     }
                     model->setValueQuantization(valueQuantization);
                     model->setScaleUnits(units);
                     model->setObjectName(name);
-                    m_models[id] = model;
+                    m_models[id] = ModelById::add(model);
                 }
             }
 
@@ -764,7 +767,8 @@
         READ_MANDATORY(int, aligned, toInt);
         READ_MANDATORY(int, path, toInt);
 
-        Model *refModel = nullptr, *alignedModel = nullptr, *pathModel = nullptr;
+        ModelId refModel, alignedModel;
+        Path *pathPtr = nullptr;
 
         if (m_models.find(reference) != m_models.end()) {
             refModel = m_models[reference];
@@ -782,31 +786,30 @@
                       << endl;
         }
 
-        if (m_models.find(path) != m_models.end()) {
-            pathModel = m_models[path];
+        if (m_paths.find(path) != m_paths.end()) {
+            pathPtr = m_paths[path];
         } else {
-            SVCERR << "WARNING: SV-XML: Unknown path model id "
+            SVCERR << "WARNING: SV-XML: Unknown path id "
                       << path << " in alignment model id " << id
                       << endl;
         }
 
-        if (refModel && alignedModel && pathModel) {
-            AlignmentModel *model = new AlignmentModel
-                (refModel, alignedModel, nullptr);
-            PathModel *pm = dynamic_cast<PathModel *>(pathModel);
-            if (!pm) {
-                SVCERR << "WARNING: SV-XML: Model id " << path
-                          << " referenced as path for alignment " << id
-                          << " is not a path model" << endl;
-            } else {
-                model->setPath(pm);
-                pm->setCompletion(100);
+        if (!refModel.isNone() && !alignedModel.isNone() && pathPtr) {
+            auto model = std::make_shared<AlignmentModel>
+                (refModel, alignedModel, ModelId());
+            model->setPath(*pathPtr);
+            model->setObjectName(name);
+            m_models[id] = ModelById::add(model);
+            if (auto am = ModelById::get(alignedModel)) {
+                am->setAlignment(m_models[id]);
             }
-            model->setObjectName(name);
-            m_models[id] = model;
-            alignedModel->setAlignment(model);
             return true;
         }
+
+        if (pathPtr) {
+            delete pathPtr;
+            m_paths.erase(path);
+        }
         
     } else {
 
@@ -972,11 +975,10 @@
 
         if (modelOk) {
             if (haveModel(modelId)) {
-                Model *model = m_models[modelId];
-                m_document->setModel(layer, model);
+                m_document->setModel(layer, m_models[modelId]);
             } else {
                 SVCERR << "WARNING: SV-XML: Unknown model id " << modelId
-                          << " in layer definition" << endl;
+                       << " in layer definition" << endl;
                 if (!layer->canExistWithoutModel()) {
                     // Don't add a layer with an unknown model id
                     // unless it explicitly supports this state
@@ -1028,14 +1030,18 @@
         return false;
     }
     
-    int modelId = m_awaitingDatasets[id];
+    int awaitingId = m_awaitingDatasets[id];
+
+    ModelId modelId;
+    Path *path = nullptr;
     
-    Model *model = nullptr;
-    if (haveModel(modelId)) {
-        model = m_models[modelId];
+    if (haveModel(awaitingId)) {
+        modelId = m_models[awaitingId];
+    } else if (m_paths.find(awaitingId) != m_paths.end()) {
+        path = m_paths[awaitingId];
     } else {
-        SVCERR << "WARNING: SV-XML: Internal error: Unknown model " << modelId
-                  << " expecting dataset " << id << endl;
+        SVCERR << "WARNING: SV-XML: Internal error: Unknown model or path "
+               << modelId << " awaiting dataset " << id << endl;
         return false;
     }
 
@@ -1043,33 +1049,37 @@
 
     switch (dimensions) {
     case 1:
-        if (dynamic_cast<SparseOneDimensionalModel *>(model)) good = true;
-        else if (dynamic_cast<ImageModel *>(model)) good = true;
+        good =
+            (ModelById::isa<SparseOneDimensionalModel>(modelId) ||
+             ModelById::isa<ImageModel>(modelId));
         break;
 
     case 2:
-        if (dynamic_cast<SparseTimeValueModel *>(model)) good = true;
-        else if (dynamic_cast<TextModel *>(model)) good = true;
-        else if (dynamic_cast<PathModel *>(model)) good = true;
+        good =
+            (ModelById::isa<SparseTimeValueModel>(modelId) ||
+             ModelById::isa<TextModel>(modelId) ||
+             path);
         break;
 
     case 3:
-        if (dynamic_cast<NoteModel *>(model)) good = true;
-        else if (dynamic_cast<RegionModel *>(model)) good = true;
-        else if (dynamic_cast<EditableDenseThreeDimensionalModel *>(model)) {
+        if (ModelById::isa<EditableDenseThreeDimensionalModel>(modelId)) {
+            good = true;
             m_datasetSeparator = attributes.value("separator");
-            good = true;
+        } else {
+            good =
+                (ModelById::isa<NoteModel>(modelId) ||
+                 ModelById::isa<RegionModel>(modelId));
         }
         break;
     }
 
     if (!good) {
         SVCERR << "WARNING: SV-XML: Model id " << modelId << " has wrong number of dimensions or inappropriate type for " << dimensions << "-D dataset " << id << endl;
-        m_currentDataset = nullptr;
+        m_currentDataset = XmlExportable::NO_ID;
         return false;
     }
 
-    m_currentDataset = model;
+    m_currentDataset = awaitingId;
     return true;
 }
 
@@ -1080,23 +1090,28 @@
 
     READ_MANDATORY(int, frame, toInt);
 
-//    SVDEBUG << "SVFileReader::addPointToDataset: frame = " << frame << endl;
+    if (m_paths.find(m_currentDataset) != m_paths.end()) {
+        Path *path = m_paths[m_currentDataset];
+        int mapframe = attributes.value("mapframe").trimmed().toInt(&ok);
+        path->add(PathPoint(frame, mapframe));
+        return ok;
+    }
 
-    SparseOneDimensionalModel *sodm = dynamic_cast<SparseOneDimensionalModel *>
-        (m_currentDataset);
+    if (!haveModel(m_currentDataset)) {
+        SVCERR << "WARNING: SV-XML: Point element found in non-point dataset"
+               << endl;
+        return false;
+    }
+        
+    ModelId modelId = m_models[m_currentDataset];        
 
-    if (sodm) {
-//        SVCERR << "Current dataset is a sparse one dimensional model" << endl;
+    if (auto sodm = ModelById::getAs<SparseOneDimensionalModel>(modelId)) {
         QString label = attributes.value("label");
         sodm->add(Event(frame, label));
         return true;
     }
 
-    SparseTimeValueModel *stvm = dynamic_cast<SparseTimeValueModel *>
-        (m_currentDataset);
-
-    if (stvm) {
-//        SVCERR << "Current dataset is a sparse time-value model" << endl;
+    if (auto stvm = ModelById::getAs<SparseTimeValueModel>(modelId)) {
         float value = 0.0;
         value = attributes.value("value").trimmed().toFloat(&ok);
         QString label = attributes.value("label");
@@ -1104,10 +1119,7 @@
         return ok;
     }
         
-    NoteModel *nm = dynamic_cast<NoteModel *>(m_currentDataset);
-
-    if (nm) {
-//        SVCERR << "Current dataset is a note model" << endl;
+    if (auto nm = ModelById::getAs<NoteModel>(modelId)) {
         float value = 0.0;
         value = attributes.value("value").trimmed().toFloat(&ok);
         int duration = 0;
@@ -1122,10 +1134,7 @@
         return ok;
     }
 
-    RegionModel *rm = dynamic_cast<RegionModel *>(m_currentDataset);
-
-    if (rm) {
-//        SVCERR << "Current dataset is a region model" << endl;
+    if (auto rm = ModelById::getAs<RegionModel>(modelId)) {
         float value = 0.0;
         value = attributes.value("value").trimmed().toFloat(&ok);
         int duration = 0;
@@ -1135,40 +1144,23 @@
         return ok;
     }
 
-    TextModel *tm = dynamic_cast<TextModel *>(m_currentDataset);
-
-    if (tm) {
-//        SVCERR << "Current dataset is a text model" << endl;
+    if (auto tm = ModelById::getAs<TextModel>(modelId)) {
         float height = 0.0;
         height = attributes.value("height").trimmed().toFloat(&ok);
         QString label = attributes.value("label");
-//        SVDEBUG << "SVFileReader::addPointToDataset: TextModel: frame = " << frame << ", height = " << height << ", label = " << label << ", ok = " << ok << endl;
         tm->add(Event(frame, height, label));
         return ok;
     }
 
-    PathModel *pm = dynamic_cast<PathModel *>(m_currentDataset);
-
-    if (pm) {
-//        SVCERR << "Current dataset is a path model" << endl;
-        int mapframe = attributes.value("mapframe").trimmed().toInt(&ok);
-//        SVDEBUG << "SVFileReader::addPointToDataset: PathModel: frame = " << frame << ", mapframe = " << mapframe << ", ok = " << ok << endl;
-        pm->add(PathPoint(frame, mapframe));
-        return ok;
-    }
-
-    ImageModel *im = dynamic_cast<ImageModel *>(m_currentDataset);
-
-    if (im) {
-//        SVCERR << "Current dataset is an image model" << endl;
+    if (auto im = ModelById::getAs<ImageModel>(modelId)) {
         QString image = attributes.value("image");
         QString label = attributes.value("label");
-//        SVDEBUG << "SVFileReader::addPointToDataset: ImageModel: frame = " << frame << ", image = " << image << ", label = " << label << ", ok = " << ok << endl;
         im->add(Event(frame).withURI(image).withLabel(label));
         return ok;
     }
 
-    SVCERR << "WARNING: SV-XML: Point element found in non-point dataset" << endl;
+    SVCERR << "WARNING: SV-XML: Point element found in non-point dataset"
+           << endl;
 
     return false;
 }
@@ -1176,11 +1168,16 @@
 bool
 SVFileReader::addBinToDataset(const QXmlAttributes &attributes)
 {
-    EditableDenseThreeDimensionalModel *dtdm = 
-        dynamic_cast<EditableDenseThreeDimensionalModel *>
-        (m_currentDataset);
+    if (!haveModel(m_currentDataset)) {
+        SVCERR << "WARNING: SV-XML: Bin definition found in incompatible dataset"
+               << endl;
+        return false;
+    }
+        
+    ModelId modelId = m_models[m_currentDataset];        
 
-    if (dtdm) {
+    if (auto dtdm = ModelById::getAs<EditableDenseThreeDimensionalModel>
+        (modelId)) {
 
         bool ok = false;
         int n = attributes.value("number").trimmed().toInt(&ok);
@@ -1191,12 +1188,12 @@
         }
 
         QString name = attributes.value("name");
-
         dtdm->setBinName(n, name);
         return true;
     }
 
-    SVCERR << "WARNING: SV-XML: Bin definition found in incompatible dataset" << endl;
+    SVCERR << "WARNING: SV-XML: Bin definition found in incompatible dataset"
+           << endl;
 
     return false;
 }
@@ -1225,13 +1222,17 @@
 bool
 SVFileReader::readRowData(const QString &text)
 {
-    EditableDenseThreeDimensionalModel *dtdm =
-        dynamic_cast<EditableDenseThreeDimensionalModel *>
-        (m_currentDataset);
-
+    if (!haveModel(m_currentDataset)) {
+        SVCERR << "WARNING: SV-XML: Row data found in non-row dataset" << endl;
+        return false;
+    }
+        
+    ModelId modelId = m_models[m_currentDataset];        
     bool warned = false;
 
-    if (dtdm) {
+    if (auto dtdm = ModelById::getAs<EditableDenseThreeDimensionalModel>
+        (modelId)) {
+
         QStringList data = text.split(m_datasetSeparator);
 
         DenseThreeDimensionalModel::Column values;
@@ -1262,30 +1263,29 @@
     }
 
     SVCERR << "WARNING: SV-XML: Row data found in non-row dataset" << endl;
-
     return false;
 }
 
 bool
 SVFileReader::readDerivation(const QXmlAttributes &attributes)
 {
-    int modelId = 0;
+    int modelExportId = 0;
     bool modelOk = false;
-    modelId = attributes.value("model").trimmed().toInt(&modelOk);
+    modelExportId = attributes.value("model").trimmed().toInt(&modelOk);
 
     if (!modelOk) {
         SVCERR << "WARNING: SV-XML: No model id specified for derivation" << endl;
         return false;
     }
 
-    if (haveModel(modelId)) {
-        m_currentDerivedModel = m_models[modelId];
+    if (haveModel(modelExportId)) {
+        m_currentDerivedModel = m_models[modelExportId];
     } else {
         // we'll regenerate the model when the derivation element ends
-        m_currentDerivedModel = nullptr;
+        m_currentDerivedModel = {};
     }
     
-    m_currentDerivedModelId = modelId;
+    m_pendingDerivedModel = modelExportId;
     
     int sourceId = 0;
     bool sourceOk = false;
@@ -1330,7 +1330,10 @@
     int windowType = attributes.value("windowType").trimmed().toInt(&ok);
     if (ok) m_currentTransform.setWindowType(WindowType(windowType));
 
-    if (!m_currentTransformSource) return true;
+    auto currentTransformSourceModel = ModelById::get(m_currentTransformSource);
+    if (!currentTransformSourceModel) return true;
+
+    sv_samplerate_t sampleRate = currentTransformSourceModel->getSampleRate();
 
     QString startFrameStr = attributes.value("startFrame");
     QString durationStr = attributes.value("duration");
@@ -1348,12 +1351,10 @@
     }
 
     m_currentTransform.setStartTime
-        (RealTime::frame2RealTime
-         (startFrame, m_currentTransformSource->getSampleRate()));
+        (RealTime::frame2RealTime(startFrame, sampleRate));
 
     m_currentTransform.setDuration
-        (RealTime::frame2RealTime
-         (duration, m_currentTransformSource->getSampleRate()));
+        (RealTime::frame2RealTime(duration, sampleRate));
 
     return true;
 }
@@ -1361,27 +1362,27 @@
 bool
 SVFileReader::readPlayParameters(const QXmlAttributes &attributes)
 {
-    m_currentPlayParameters = nullptr;
+    m_currentPlayParameters = {};
 
-    int modelId = 0;
+    int modelExportId = 0;
     bool modelOk = false;
-    modelId = attributes.value("model").trimmed().toInt(&modelOk);
+    modelExportId = attributes.value("model").trimmed().toInt(&modelOk);
 
     if (!modelOk) {
         SVCERR << "WARNING: SV-XML: No model id specified for play parameters" << endl;
         return false;
     }
 
-    if (haveModel(modelId)) {
+    if (haveModel(modelExportId)) {
 
         bool ok = false;
 
-        PlayParameters *parameters = PlayParameterRepository::getInstance()->
-            getPlayParameters(m_models[modelId]);
+        auto parameters = PlayParameterRepository::getInstance()->
+            getPlayParameters(m_models[modelExportId].untyped);
 
         if (!parameters) {
             SVCERR << "WARNING: SV-XML: Play parameters for model "
-                      << modelId
+                      << modelExportId
                       << " not found - has model been added to document?"
                       << endl;
             return false;
@@ -1401,12 +1402,12 @@
         
         m_currentPlayParameters = parameters;
 
-//        SVCERR << "Current play parameters for model: " << m_models[modelId] << ": " << m_currentPlayParameters << endl;
+//        SVCERR << "Current play parameters for model: " << m_models[modelExportId] << ": " << m_currentPlayParameters << endl;
 
     } else {
 
-        SVCERR << "WARNING: SV-XML: Unknown model " << modelId
-                  << " for play parameters" << endl;
+        SVCERR << "WARNING: SV-XML: Unknown model " << modelExportId
+               << " for play parameters" << endl;
         return false;
     }
 
@@ -1416,7 +1417,7 @@
 bool
 SVFileReader::readPlugin(const QXmlAttributes &attributes)
 {
-    if (m_currentDerivedModelId >= 0) {
+    if (m_pendingDerivedModel != XmlExportable::NO_ID) {
         return readPluginForTransform(attributes);
     } else if (m_currentPlayParameters) {
         return readPluginForPlayback(attributes);
@@ -1467,7 +1468,7 @@
 bool
 SVFileReader::readTransform(const QXmlAttributes &attributes)
 {
-    if (m_currentDerivedModelId < 0) {
+    if (m_pendingDerivedModel == XmlExportable::NO_ID) {
         SVCERR << "WARNING: SV-XML: Transform found outside derivation" << endl;
         return false;
     }
@@ -1480,7 +1481,7 @@
 bool
 SVFileReader::readParameter(const QXmlAttributes &attributes)
 {
-    if (m_currentDerivedModelId < 0) {
+    if (m_pendingDerivedModel == XmlExportable::NO_ID) {
         SVCERR << "WARNING: SV-XML: Parameter found outside derivation" << endl;
         return false;
     }
--- a/framework/SVFileReader.h	Fri Jun 14 17:19:37 2019 +0100
+++ b/framework/SVFileReader.h	Wed Jul 17 14:25:41 2019 +0100
@@ -25,6 +25,7 @@
 
 class Pane;
 class Model;
+class Path;
 class Document;
 class PlayParameters;
 
@@ -182,15 +183,15 @@
     void setCurrentPane(Pane *pane) { m_currentPane = pane; }
     
     bool startElement(const QString &namespaceURI,
-                              const QString &localName,
-                              const QString &qName,
-                              const QXmlAttributes& atts) override;
-
+                      const QString &localName,
+                      const QString &qName,
+                      const QXmlAttributes& atts) override;
+    
     bool characters(const QString &) override;
 
     bool endElement(const QString &namespaceURI,
-                            const QString &localName,
-                            const QString &qName) override;
+                    const QString &localName,
+                    const QString &qName) override;
 
     bool error(const QXmlParseException &exception) override;
     bool fatalError(const QXmlParseException &exception) override;
@@ -233,32 +234,57 @@
     void makeAggregateModels();
     void addUnaddedModels();
 
-    bool haveModel(int id) {
-        return (m_models.find(id) != m_models.end()) && m_models[id];
+    // We use the term "pending" of things that have been referred to
+    // but not yet constructed because their definitions are
+    // incomplete. They are just referred to with an ExportId.  Models
+    // that have been constructed are always added straight away to
+    // ById and are referred to with a ModelId (everywhere where
+    // previously we would have used a Model *). m_models maps from
+    // ExportId (as read from the file) to complete Models, or to a
+    // ModelId of None for any model that could not be constructed for
+    // some reason.
+
+    typedef XmlExportable::ExportId ExportId;
+    
+    bool haveModel(ExportId id) {
+        return (m_models.find(id) != m_models.end()) && !m_models[id].isNone();
     }
-
+    
     struct PendingAggregateRec {
         QString name;
         sv_samplerate_t sampleRate;
-        std::vector<int> components;
+        std::vector<ExportId> components;
     };
     
     Document *m_document;
     SVFileReaderPaneCallback &m_paneCallback;
     QString m_location;
     Pane *m_currentPane;
-    std::map<int, Layer *> m_layers;
-    std::map<int, Model *> m_models;
-    std::set<Model *> m_addedModels;
-    std::map<int, PendingAggregateRec> m_pendingAggregates;
-    std::map<int, int> m_awaitingDatasets; // map dataset id -> model id
+    std::map<ExportId, Layer *> m_layers;
+    std::map<ExportId, ModelId> m_models;
+    std::map<ExportId, Path *> m_paths;
+    std::set<ModelId> m_addedModels; // i.e. added to Document, not just ById
+    std::map<ExportId, PendingAggregateRec> m_pendingAggregates;
+
+    // A model element often contains a dataset id, and the dataset
+    // then follows it. When the model is read, an entry in this map
+    // is added, mapping from the dataset's export id (the actual
+    // dataset has not been read yet) back to the export id of the
+    // object that needs it. We map to export id rather than model id,
+    // because the object might be a path rather than a model.
+    std::map<ExportId, ExportId> m_awaitingDatasets;
+
+    // And then this is the model or path that a dataset element is
+    // currently being read into, i.e. the value looked up from
+    // m_awaitingDatasets at the point where the dataset is found.
+    ExportId m_currentDataset;
+
     Layer *m_currentLayer;
-    Model *m_currentDataset;
-    Model *m_currentDerivedModel;
-    int m_currentDerivedModelId;
-    PlayParameters *m_currentPlayParameters;
+    ModelId m_currentDerivedModel;
+    ExportId m_pendingDerivedModel;
+    std::shared_ptr<PlayParameters> m_currentPlayParameters;
     Transform m_currentTransform;
-    Model *m_currentTransformSource;
+    ModelId m_currentTransformSource;
     int m_currentTransformChannel;
     bool m_currentTransformIsNewStyle;
     QString m_datasetSeparator;
--- a/framework/TransformUserConfigurator.cpp	Fri Jun 14 17:19:37 2019 +0100
+++ b/framework/TransformUserConfigurator.cpp	Wed Jul 17 14:25:41 2019 +0100
@@ -60,11 +60,11 @@
 TransformUserConfigurator::configure(ModelTransformer::Input &input,
                                      Transform &transform,
                                      Vamp::PluginBase *plugin,
-                                     Model *&inputModel,
+                                     ModelId &inputModel,
                                      AudioPlaySource *source,
                                      sv_frame_t startFrame,
                                      sv_frame_t duration,
-                                     const QMap<QString, Model *> &modelMap,
+                                     const QMap<QString, ModelId> &modelMap,
                                      QStringList candidateModelNames,
                                      QString defaultModelName)
 {
@@ -121,8 +121,6 @@
         std::vector<Vamp::Plugin::OutputDescriptor> od =
             vp->getOutputDescriptors();
 
-//        cerr << "configure: looking for output: " << output << endl;
-
         if (od.size() > 1) {
             for (size_t i = 0; i < od.size(); ++i) {
                 if (od[i].identifier == output.toStdString()) {
@@ -135,9 +133,9 @@
     }
     
     int sourceChannels = 1;
-    if (dynamic_cast<DenseTimeValueModel *>(inputModel)) {
-        sourceChannels = dynamic_cast<DenseTimeValueModel *>(inputModel)
-            ->getChannelCount();
+
+    if (auto dtvm = ModelById::getAs<DenseTimeValueModel>(inputModel)) {
+        sourceChannels = dtvm->getChannelCount();
     }
 
     int minChannels = 1, maxChannels = sourceChannels;
@@ -203,12 +201,17 @@
     //around all this misc stuff, but that's for tomorrow
     //(whenever that may be)
 
+    sv_samplerate_t sampleRate = 0;
+    if (auto m = ModelById::get(inputModel)) {
+        sampleRate = m->getSampleRate();
+    }
+    
     if (startFrame != 0 || duration != 0) {
-        if (dialog->getSelectionOnly()) {
-            transform.setStartTime(RealTime::frame2RealTime
-                                   (startFrame, inputModel->getSampleRate()));
-            transform.setDuration(RealTime::frame2RealTime
-                                  (duration, inputModel->getSampleRate()));
+        if (dialog->getSelectionOnly() && sampleRate != 0) {
+            transform.setStartTime
+                (RealTime::frame2RealTime(startFrame, sampleRate));
+            transform.setDuration
+                (RealTime::frame2RealTime(duration, sampleRate));
         }
     }
 
--- a/framework/TransformUserConfigurator.h	Fri Jun 14 17:19:37 2019 +0100
+++ b/framework/TransformUserConfigurator.h	Wed Jul 17 14:25:41 2019 +0100
@@ -23,15 +23,15 @@
     // This is of course absolutely gross
 
     bool configure(ModelTransformer::Input &input,
-                           Transform &transform,
-                           Vamp::PluginBase *plugin,
-                           Model *&inputModel,
-                           AudioPlaySource *source,
-                           sv_frame_t startFrame,
-                           sv_frame_t duration,
-                           const QMap<QString, Model *> &modelMap,
-                           QStringList candidateModelNames,
-                           QString defaultModelName) override;
+                   Transform &transform,
+                   Vamp::PluginBase *plugin,
+                   ModelId &inputModel,
+                   AudioPlaySource *source,
+                   sv_frame_t startFrame,
+                   sv_frame_t duration,
+                   const QMap<QString, ModelId> &modelMap,
+                   QStringList candidateModelNames,
+                   QString defaultModelName) override;
 
     static void setParentWidget(QWidget *);