changeset 1473:886c1cd48f9d by-id

Further layer updates for ModelById
author Chris Cannam
date Tue, 02 Jul 2019 11:49:52 +0100
parents dbff4b290bf0
children 36ad3cdabf55
files layer/Colour3DPlotLayer.cpp layer/Colour3DPlotLayer.h layer/Colour3DPlotRenderer.cpp layer/Colour3DPlotRenderer.h layer/SpectrogramLayer.cpp layer/SpectrogramLayer.h layer/SpectrumLayer.cpp
diffstat 7 files changed, 259 insertions(+), 189 deletions(-) [+]
line wrap: on
line diff
--- a/layer/Colour3DPlotLayer.cpp	Mon Jul 01 14:25:53 2019 +0100
+++ b/layer/Colour3DPlotLayer.cpp	Tue Jul 02 11:49:52 2019 +0100
@@ -183,8 +183,10 @@
     invalidateRenderers();
     invalidateMagnitudes();
 
-    delete m_peakCache;
-    m_peakCache = nullptr;
+    if (!m_peakCache.isNone()) {
+        ModelById::release(m_peakCache);
+        m_peakCache = {};
+    }
 }
 
 void
@@ -206,11 +208,14 @@
     m_viewMags.clear();
 }
 
-Dense3DModelPeakCache *
+ModelId
 Colour3DPlotLayer::getPeakCache() const
 {
-    if (!m_peakCache) {
-        m_peakCache = new Dense3DModelPeakCache(m_model, m_peakCacheDivisor);
+    if (m_peakCache.isNone()) {
+        auto peakCache = std::make_shared<Dense3DModelPeakCache>
+            (m_model, m_peakCacheDivisor);
+        ModelById::add(peakCache);
+        m_peakCache = peakCache->getId();
     }
     return m_peakCache;
 }
--- a/layer/Colour3DPlotLayer.h	Mon Jul 01 14:25:53 2019 +0100
+++ b/layer/Colour3DPlotLayer.h	Tue Jul 02 11:49:52 2019 +0100
@@ -187,10 +187,10 @@
     static std::pair<ColumnNormalization, bool> convertToColumnNorm(int value);
     static int convertFromColumnNorm(ColumnNormalization norm, bool visible);
 
-    mutable Dense3DModelPeakCache *m_peakCache;
+    mutable ModelId m_peakCache;
     const int m_peakCacheDivisor;
     void invalidatePeakCache();
-    Dense3DModelPeakCache *getPeakCache() const;
+    ModelId getPeakCache() const;
 
     typedef std::map<int, MagnitudeRange> ViewMagMap; // key is view id
     mutable ViewMagMap m_viewMags;
--- a/layer/Colour3DPlotRenderer.cpp	Mon Jul 01 14:25:53 2019 +0100
+++ b/layer/Colour3DPlotRenderer.cpp	Tue Jul 02 11:49:52 2019 +0100
@@ -426,7 +426,12 @@
     if (fullColumn.empty()) {
         
         if (peakCacheIndex >= 0) {
-            fullColumn = m_sources.peakCaches[peakCacheIndex]->getColumn(sx);
+            auto peakCache = ModelById::getAs<Dense3DModelPeakCache>
+                (m_sources.peakCaches[peakCacheIndex]);
+            if (!peakCache) {
+                return vector<float>(nbins, 0.f);
+            }                
+            fullColumn = peakCache->getColumn(sx);
         } else {
             auto model = ModelById::getAs<DenseThreeDimensionalModel>
                 (m_sources.source);
@@ -621,7 +626,10 @@
     int binResolution = model->getResolution();
     
     for (int ix = 0; in_range_for(m_sources.peakCaches, ix); ++ix) {
-        int bpp = m_sources.peakCaches[ix]->getColumnsPerPeak();
+        auto peakCache = ModelById::getAs<Dense3DModelPeakCache>
+            (m_sources.peakCaches[ix]);
+        if (!peakCache) continue;
+        int bpp = peakCache->getColumnsPerPeak();
         ZoomLevel equivZoom(ZoomLevel::FramesPerPixel, binResolution * bpp);
 #ifdef DEBUG_COLOUR_PLOT_REPAINT
         SVDEBUG << "getPreferredPeakCache: zoomLevel = " << zoomLevel
@@ -956,14 +964,24 @@
     Profiler profiler("Colour3DPlotRenderer::renderDrawBuffer");
     
     int divisor = 1;
-    auto model = ModelById::getAs<DenseThreeDimensionalModel>(m_sources.source);
-    if (!model) return 0;
+
+    std::shared_ptr<DenseThreeDimensionalModel> sourceModel;
+
+    if (peakCacheIndex >= 0) {
+        auto peakCache = ModelById::getAs<Dense3DModelPeakCache>
+            (m_sources.peakCaches[peakCacheIndex]);
+        if (peakCache) {
+            divisor = peakCache->getColumnsPerPeak();
+            sourceModel = peakCache;
+        }
+    }
+
+    if (!sourceModel) {
+        sourceModel = ModelById::getAs<DenseThreeDimensionalModel>
+            (m_sources.source);
+    }
     
-    const DenseThreeDimensionalModel *sourceModel = model.get();
-    if (peakCacheIndex >= 0) {
-        divisor = m_sources.peakCaches[peakCacheIndex]->getColumnsPerPeak();
-        sourceModel = m_sources.peakCaches[peakCacheIndex];
-    }
+    if (!sourceModel) return 0;
 
 #ifdef DEBUG_COLOUR_PLOT_REPAINT
     SVDEBUG << "renderDrawBuffer: w = " << w << ", h = " << h
--- a/layer/Colour3DPlotRenderer.h	Mon Jul 01 14:25:53 2019 +0100
+++ b/layer/Colour3DPlotRenderer.h	Tue Jul 02 11:49:52 2019 +0100
@@ -54,8 +54,8 @@
         // These must all outlive this class
         const VerticalBinLayer *verticalBinLayer; // always
         ModelId source; // always; a DenseThreeDimensionalModel
-        ModelId fft; // optionally
-        std::vector<Dense3DModelPeakCache *> peakCaches; // zero or more
+        ModelId fft; // optionally; an FFTModel; used for phase/peak-freq modes
+        std::vector<ModelId> peakCaches; // zero or more
     };        
 
     struct Parameters {
--- a/layer/SpectrogramLayer.cpp	Mon Jul 01 14:25:53 2019 +0100
+++ b/layer/SpectrogramLayer.cpp	Tue Jul 02 11:49:52 2019 +0100
@@ -82,8 +82,6 @@
     m_synchronous(false),
     m_haveDetailedScale(false),
     m_exiting(false),
-    m_wholeCache(nullptr),
-    m_peakCache(nullptr),
     m_peakCacheDivisor(8)
 {
     QString colourConfigName = "spectrogram-colour";
@@ -138,17 +136,13 @@
 void
 SpectrogramLayer::deleteDerivedModels()
 {
-    if (m_fftModel) m_fftModel->aboutToDelete();
-    if (m_peakCache) m_peakCache->aboutToDelete();
-    if (m_wholeCache) m_wholeCache->aboutToDelete();
-
-    delete m_fftModel;
-    delete m_peakCache;
-    delete m_wholeCache;
-
-    m_fftModel = nullptr;
-    m_peakCache = nullptr;
-    m_wholeCache = nullptr;
+    ModelById::release(m_fftModel);
+    ModelById::release(m_peakCache);
+    ModelById::release(m_wholeCache);
+
+    m_fftModel = {};
+    m_peakCache = {};
+    m_wholeCache = {};
 }
 
 pair<ColourScaleType, double>
@@ -1107,7 +1101,10 @@
 double
 SpectrogramLayer::getEffectiveMinFrequency() const
 {
-    sv_samplerate_t sr = m_model->getSampleRate();
+    auto model = ModelById::getAs<DenseTimeValueModel>(m_model);
+    if (!model) return 0.0;
+    
+    sv_samplerate_t sr = model->getSampleRate();
     double minf = double(sr) / getFFTSize();
 
     if (m_minFrequency > 0.0) {
@@ -1122,7 +1119,10 @@
 double
 SpectrogramLayer::getEffectiveMaxFrequency() const
 {
-    sv_samplerate_t sr = m_model->getSampleRate();
+    auto model = ModelById::getAs<DenseTimeValueModel>(m_model);
+    if (!model) return 0.0;
+    
+    sv_samplerate_t sr = model->getSampleRate();
     double maxf = double(sr) / 2;
 
     if (m_maxFrequency > 0.0) {
@@ -1148,10 +1148,13 @@
 double
 SpectrogramLayer::getYForBin(const LayerGeometryProvider *v, double bin) const
 {
+    auto model = ModelById::getAs<DenseTimeValueModel>(m_model);
+    if (!model) return 0.0;
+    
     double minf = getEffectiveMinFrequency();
     double maxf = getEffectiveMaxFrequency();
     bool logarithmic = (m_binScale == BinScale::Log);
-    sv_samplerate_t sr = m_model->getSampleRate();
+    sv_samplerate_t sr = model->getSampleRate();
 
     double freq = (bin * sr) / getFFTSize();
     
@@ -1163,7 +1166,10 @@
 double
 SpectrogramLayer::getBinForY(const LayerGeometryProvider *v, double y) const
 {
-    sv_samplerate_t sr = m_model->getSampleRate();
+    auto model = ModelById::getAs<DenseTimeValueModel>(m_model);
+    if (!model) return 0.0;
+
+    sv_samplerate_t sr = model->getSampleRate();
     double minf = getEffectiveMinFrequency();
     double maxf = getEffectiveMaxFrequency();
 
@@ -1180,8 +1186,11 @@
 bool
 SpectrogramLayer::getXBinRange(LayerGeometryProvider *v, int x, double &s0, double &s1) const
 {
-    sv_frame_t modelStart = m_model->getStartFrame();
-    sv_frame_t modelEnd = m_model->getEndFrame();
+    auto model = ModelById::getAs<DenseTimeValueModel>(m_model);
+    if (!model) return false;
+
+    sv_frame_t modelStart = model->getStartFrame();
+    sv_frame_t modelEnd = model->getEndFrame();
 
     // Each pixel column covers an exact range of sample frames:
     sv_frame_t f0 = v->getFrameForX(x) - modelStart;
@@ -1204,6 +1213,9 @@
 bool
 SpectrogramLayer::getXBinSourceRange(LayerGeometryProvider *v, int x, RealTime &min, RealTime &max) const
 {
+    auto model = ModelById::getAs<DenseTimeValueModel>(m_model);
+    if (!model) return false;
+
     double s0 = 0, s1 = 0;
     if (!getXBinRange(v, x, s0, s1)) return false;
     
@@ -1215,8 +1227,8 @@
     int w1 = s1i * windowIncrement + windowIncrement +
         (m_windowSize - windowIncrement)/2 - 1;
     
-    min = RealTime::frame2RealTime(w0, m_model->getSampleRate());
-    max = RealTime::frame2RealTime(w1, m_model->getSampleRate());
+    min = RealTime::frame2RealTime(w0, model->getSampleRate());
+    max = RealTime::frame2RealTime(w1, model->getSampleRate());
     return true;
 }
 
@@ -1224,13 +1236,16 @@
 SpectrogramLayer::getYBinSourceRange(LayerGeometryProvider *v, int y, double &freqMin, double &freqMax)
 const
 {
+    auto model = ModelById::getAs<DenseTimeValueModel>(m_model);
+    if (!model) return false;
+
     double q0 = 0, q1 = 0;
     if (!getYBinRange(v, y, q0, q1)) return false;
 
     int q0i = int(q0 + 0.001);
     int q1i = int(q1);
 
-    sv_samplerate_t sr = m_model->getSampleRate();
+    sv_samplerate_t sr = model->getSampleRate();
 
     for (int q = q0i; q <= q1i; ++q) {
         if (q == q0i) freqMin = (sr * q) / getFFTSize();
@@ -1245,11 +1260,12 @@
                                              double &adjFreqMin, double &adjFreqMax)
 const
 {
-    if (!m_model || !m_model->isOK() || !m_model->isReady()) {
+    auto model = ModelById::getAs<DenseTimeValueModel>(m_model);
+    if (!model || !model->isOK() || !model->isReady()) {
         return false;
     }
 
-    FFTModel *fft = getFFTModel();
+    auto fft = ModelById::getAs<FFTModel>(m_fftModel);
     if (!fft) return false;
 
     double s0 = 0, s1 = 0;
@@ -1264,7 +1280,7 @@
     int q0i = int(q0 + 0.001);
     int q1i = int(q1);
 
-    sv_samplerate_t sr = m_model->getSampleRate();
+    sv_samplerate_t sr = model->getSampleRate();
 
     bool haveAdj = false;
 
@@ -1312,7 +1328,8 @@
                                       double &min, double &max,
                                       double &phaseMin, double &phaseMax) const
 {
-    if (!m_model || !m_model->isOK() || !m_model->isReady()) {
+    auto model = ModelById::getAs<DenseTimeValueModel>(m_model);
+    if (!model || !model->isOK() || !model->isReady()) {
         return false;
     }
 
@@ -1330,7 +1347,7 @@
 
     bool rv = false;
 
-    FFTModel *fft = getFFTModel();
+    auto fft = ModelById::getAs<FFTModel>(m_fftModel);
 
     if (fft) {
 
@@ -1375,55 +1392,55 @@
 {
     SVDEBUG << "SpectrogramLayer::recreateFFTModel called" << endl;
 
-    if (!m_model || !m_model->isOK()) {
-        emit sliceableModelReplaced(m_fftModel, nullptr);
-        deleteDerivedModels();
-        return;
+    { // scope, avoid hanging on to this pointer
+        auto model = ModelById::getAs<DenseTimeValueModel>(m_model);
+        if (!model || !model->isOK()) {
+            deleteDerivedModels();
+            return;
+        }
     }
-
-    if (m_fftModel) m_fftModel->aboutToDelete();
     
-    if (m_peakCache) m_peakCache->aboutToDelete();
-    delete m_peakCache;
-    m_peakCache = nullptr;
-
-    if (m_wholeCache) m_wholeCache->aboutToDelete();
-    delete m_wholeCache;
-    m_wholeCache = nullptr;
-    
-    FFTModel *newModel = new FFTModel(m_model,
-                                      m_channel,
-                                      m_windowType,
-                                      m_windowSize,
-                                      getWindowIncrement(),
-                                      getFFTSize());
-
-    if (!newModel->isOK()) {
+    deleteDerivedModels();
+
+    auto newFFTModel = std::make_shared<FFTModel>(m_model,
+                                                  m_channel,
+                                                  m_windowType,
+                                                  m_windowSize,
+                                                  getWindowIncrement(),
+                                                  getFFTSize());
+
+    if (!newFFTModel->isOK()) {
         QMessageBox::critical
             (nullptr, tr("FFT cache failed"),
              tr("Failed to create the FFT model for this spectrogram.\n"
                 "There may be insufficient memory or disc space to continue."));
-        delete newModel;
-        delete m_fftModel;
-        m_fftModel = nullptr;
         return;
     }
 
-    FFTModel *oldModel = m_fftModel;
-    m_fftModel = newModel;
+    ModelById::add(newFFTModel);
+    m_fftModel = newFFTModel->getId();
 
     bool createWholeCache = false;
     checkCacheSpace(&m_peakCacheDivisor, &createWholeCache);
     
     if (createWholeCache) {
-        m_wholeCache = new Dense3DModelPeakCache(m_fftModel, 1);
-        m_peakCache = new Dense3DModelPeakCache(m_wholeCache, m_peakCacheDivisor);
+
+        auto whole = std::make_shared<Dense3DModelPeakCache>(m_fftModel, 1);
+        ModelById::add(whole);
+        m_wholeCache = whole->getId();
+
+        auto peaks = std::make_shared<Dense3DModelPeakCache>(m_wholeCache,
+                                                             m_peakCacheDivisor);
+        ModelById::add(peaks);
+        m_peakCache = peaks->getId();
+
     } else {
-        m_peakCache = new Dense3DModelPeakCache(m_fftModel, m_peakCacheDivisor);
+
+        auto peaks = std::make_shared<Dense3DModelPeakCache>(m_fftModel,
+                                                             m_peakCacheDivisor);
+        ModelById::add(peaks);
+        m_peakCache = peaks->getId();
     }
-
-    emit sliceableModelReplaced(oldModel, m_fftModel);
-    delete oldModel;
 }
 
 void
@@ -1432,12 +1449,13 @@
 {
     *suggestedPeakDivisor = 8;
     *createWholeCache = false;
-    
-    if (!m_fftModel) return;
+
+    auto fftModel = ModelById::getAs<FFTModel>(m_fftModel);
+    if (!fftModel) return;
 
     size_t sz =
-        size_t(m_fftModel->getWidth()) *
-        size_t(m_fftModel->getHeight()) *
+        size_t(fftModel->getWidth()) *
+        size_t(fftModel->getHeight()) *
         sizeof(float);
 
     try {
@@ -1467,7 +1485,7 @@
     }
 }
 
-const Model *
+ModelId
 SpectrogramLayer::getSliceableModel() const
 {
     return m_fftModel;
@@ -1497,10 +1515,10 @@
 
         Colour3DPlotRenderer::Sources sources;
         sources.verticalBinLayer = this;
-        sources.fft = getFFTModel();
+        sources.fft = m_fftModel;
         sources.source = sources.fft;
-        if (m_peakCache) sources.peakCaches.push_back(m_peakCache);
-        if (m_wholeCache) sources.peakCaches.push_back(m_wholeCache);
+        if (!m_peakCache.isNone()) sources.peakCaches.push_back(m_peakCache);
+        if (!m_wholeCache.isNone()) sources.peakCaches.push_back(m_wholeCache);
 
         ColourScale::Parameters cparams;
         cparams.colourMap = m_colourMap;
@@ -1636,7 +1654,8 @@
     cerr << "SpectrogramLayer::paint(): rect is " << rect.x() << "," << rect.y() << " " << rect.width() << "x" << rect.height() << endl;
 #endif
 
-    if (!m_model || !m_model->isOK() || !m_model->isReady()) {
+    auto model = ModelById::getAs<DenseTimeValueModel>(m_model);
+    if (!model || !model->isOK() || !model->isReady()) {
         return;
     }
 
@@ -1650,8 +1669,10 @@
 {
     Profiler profiler("SpectrogramLayer::illuminateLocalFeatures");
 
+    auto model = ModelById::getAs<DenseTimeValueModel>(m_model);
+    
     QPoint localPos;
-    if (!v->shouldIlluminateLocalFeatures(this, localPos) || !m_model) {
+    if (!v->shouldIlluminateLocalFeatures(this, localPos) || !model) {
         return;
     }
 
@@ -1709,8 +1730,9 @@
 int
 SpectrogramLayer::getCompletion(LayerGeometryProvider *) const
 {
-    if (!m_fftModel) return 100;
-    int completion = m_fftModel->getCompletion();
+    auto fftModel = ModelById::getAs<FFTModel>(m_fftModel);
+    if (!fftModel) return 100;
+    int completion = fftModel->getCompletion();
 #ifdef DEBUG_SPECTROGRAM_REPAINT
     cerr << "SpectrogramLayer::getCompletion: completion = " << completion << endl;
 #endif
@@ -1720,17 +1742,19 @@
 QString
 SpectrogramLayer::getError(LayerGeometryProvider *) const
 {
-    if (!m_fftModel) return "";
-    return m_fftModel->getError();
+    auto fftModel = ModelById::getAs<FFTModel>(m_fftModel);
+    if (!fftModel) return "";
+    return fftModel->getError();
 }
 
 bool
 SpectrogramLayer::getValueExtents(double &min, double &max,
                                   bool &logarithmic, QString &unit) const
 {
-    if (!m_model) return false;
-
-    sv_samplerate_t sr = m_model->getSampleRate();
+    auto model = ModelById::getAs<DenseTimeValueModel>(m_model);
+    if (!model) return false;
+
+    sv_samplerate_t sr = model->getSampleRate();
     min = double(sr) / getFFTSize();
     max = double(sr) / 2;
     
@@ -1752,12 +1776,13 @@
 bool
 SpectrogramLayer::setDisplayExtents(double min, double max)
 {
-    if (!m_model) return false;
+    auto model = ModelById::getAs<DenseTimeValueModel>(m_model);
+    if (!model) return false;
 
 //    SVDEBUG << "SpectrogramLayer::setDisplayExtents: " << min << "->" << max << endl;
 
     if (min < 0) min = 0;
-    if (max > m_model->getSampleRate()/2.0) max = m_model->getSampleRate()/2.0;
+    if (max > model->getSampleRate()/2.0) max = model->getSampleRate()/2.0;
     
     int minf = int(lrint(min));
     int maxf = int(lrint(max));
@@ -1832,6 +1857,11 @@
                                       QPoint cursorPos,
                                       vector<QRect> &extents) const
 {
+    // Qt 5.13 deprecates QFontMetrics::width(), but its suggested
+    // replacement (horizontalAdvance) was only added in Qt 5.11
+    // which is too new for us
+#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
+
     QRect vertical(cursorPos.x() - 12, 0, 12, v->getPaintHeight());
     extents.push_back(vertical);
 
@@ -1870,6 +1900,9 @@
 SpectrogramLayer::paintCrosshairs(LayerGeometryProvider *v, QPainter &paint,
                                   QPoint cursorPos) const
 {
+    auto model = ModelById::getAs<DenseTimeValueModel>(m_model);
+    if (!model) return;
+
     paint.save();
     
     int sw = getVerticalScaleWidth(v, m_haveDetailedScale, paint);
@@ -1886,35 +1919,39 @@
     
     double fundamental = getFrequencyForY(v, cursorPos.y());
 
-    PaintAssistant::drawVisibleText(v, paint,
-                       sw + 2,
-                       cursorPos.y() - 2,
-                       QString("%1 Hz").arg(fundamental),
-                       PaintAssistant::OutlinedText);
+    PaintAssistant::drawVisibleText
+        (v, paint,
+         sw + 2,
+         cursorPos.y() - 2,
+         QString("%1 Hz").arg(fundamental),
+         PaintAssistant::OutlinedText);
 
     if (Pitch::isFrequencyInMidiRange(fundamental)) {
         QString pitchLabel = Pitch::getPitchLabelForFrequency(fundamental);
-        PaintAssistant::drawVisibleText(v, paint,
-                           sw + 2,
-                           cursorPos.y() + paint.fontMetrics().ascent() + 2,
-                           pitchLabel,
-                           PaintAssistant::OutlinedText);
+        PaintAssistant::drawVisibleText
+            (v, paint,
+             sw + 2,
+             cursorPos.y() + paint.fontMetrics().ascent() + 2,
+             pitchLabel,
+             PaintAssistant::OutlinedText);
     }
 
     sv_frame_t frame = v->getFrameForX(cursorPos.x());
-    RealTime rt = RealTime::frame2RealTime(frame, m_model->getSampleRate());
+    RealTime rt = RealTime::frame2RealTime(frame, model->getSampleRate());
     QString rtLabel = QString("%1 s").arg(rt.toText(true).c_str());
     QString frameLabel = QString("%1").arg(frame);
-    PaintAssistant::drawVisibleText(v, paint,
-                       cursorPos.x() - paint.fontMetrics().width(frameLabel) - 2,
-                       v->getPaintHeight() - 2,
-                       frameLabel,
-                       PaintAssistant::OutlinedText);
-    PaintAssistant::drawVisibleText(v, paint,
-                       cursorPos.x() + 2,
-                       v->getPaintHeight() - 2,
-                       rtLabel,
-                       PaintAssistant::OutlinedText);
+    PaintAssistant::drawVisibleText
+        (v, paint,
+         cursorPos.x() - paint.fontMetrics().width(frameLabel) - 2,
+         v->getPaintHeight() - 2,
+         frameLabel,
+         PaintAssistant::OutlinedText);
+    PaintAssistant::drawVisibleText
+        (v, paint,
+         cursorPos.x() + 2,
+         v->getPaintHeight() - 2,
+         rtLabel,
+         PaintAssistant::OutlinedText);
 
     int harmonic = 2;
 
@@ -1950,7 +1987,8 @@
     int x = pos.x();
     int y = pos.y();
 
-    if (!m_model || !m_model->isOK()) return "";
+    auto model = ModelById::getAs<DenseTimeValueModel>(m_model);
+    if (!model || !model->isOK()) return "";
 
     double magMin = 0, magMax = 0;
     double phaseMin = 0, phaseMax = 0;
@@ -2069,7 +2107,8 @@
 int
 SpectrogramLayer::getVerticalScaleWidth(LayerGeometryProvider *, bool detailed, QPainter &paint) const
 {
-    if (!m_model || !m_model->isOK()) return 0;
+    auto model = ModelById::getAs<DenseTimeValueModel>(m_model);
+    if (!model || !model->isOK()) return 0;
 
     int cw = 0;
     if (detailed) cw = getColourScaleWidth(paint);
@@ -2077,7 +2116,7 @@
     int tw = paint.fontMetrics().width(QString("%1")
                                      .arg(m_maxFrequency > 0 ?
                                           m_maxFrequency - 1 :
-                                          m_model->getSampleRate() / 2));
+                                          model->getSampleRate() / 2));
 
     int fw = paint.fontMetrics().width(tr("43Hz"));
     if (tw < fw) tw = fw;
@@ -2091,7 +2130,8 @@
 SpectrogramLayer::paintVerticalScale(LayerGeometryProvider *v, bool detailed,
                                      QPainter &paint, QRect rect) const
 {
-    if (!m_model || !m_model->isOK()) {
+    auto model = ModelById::getAs<DenseTimeValueModel>(m_model);
+    if (!model || !model->isOK()) {
         return;
     }
 
@@ -2111,7 +2151,7 @@
     int pkw = (m_binScale == BinScale::Log ? 10 : 0);
 
     int bins = getFFTSize() / 2;
-    sv_samplerate_t sr = m_model->getSampleRate();
+    sv_samplerate_t sr = model->getSampleRate();
 
     if (m_maxFrequency > 0) {
         bins = int((double(m_maxFrequency) * getFFTSize()) / sr + 0.1);
@@ -2381,9 +2421,10 @@
 int
 SpectrogramLayer::getVerticalZoomSteps(int &defaultStep) const
 {
-    if (!m_model) return 0;
-
-    sv_samplerate_t sr = m_model->getSampleRate();
+    auto model = ModelById::getAs<DenseTimeValueModel>(m_model);
+    if (!model) return 0;
+
+    sv_samplerate_t sr = model->getSampleRate();
 
     SpectrogramRangeMapper mapper(sr, getFFTSize());
 
@@ -2404,12 +2445,13 @@
 int
 SpectrogramLayer::getCurrentVerticalZoomStep() const
 {
-    if (!m_model) return 0;
+    auto model = ModelById::getAs<DenseTimeValueModel>(m_model);
+    if (!model) return 0;
 
     double dmin, dmax;
     getDisplayExtents(dmin, dmax);
     
-    SpectrogramRangeMapper mapper(m_model->getSampleRate(), getFFTSize());
+    SpectrogramRangeMapper mapper(model->getSampleRate(), getFFTSize());
     int n = mapper.getPositionForValue(dmax - dmin);
 //    SVDEBUG << "SpectrogramLayer::getCurrentVerticalZoomStep: " << n << endl;
     return n;
@@ -2418,14 +2460,15 @@
 void
 SpectrogramLayer::setVerticalZoomStep(int step)
 {
-    if (!m_model) return;
+    auto model = ModelById::getAs<DenseTimeValueModel>(m_model);
+    if (!model) return;
 
     double dmin = m_minFrequency, dmax = m_maxFrequency;
 //    getDisplayExtents(dmin, dmax);
 
 //    cerr << "current range " << dmin << " -> " << dmax << ", range " << dmax-dmin << ", mid " << (dmax + dmin)/2 << endl;
     
-    sv_samplerate_t sr = m_model->getSampleRate();
+    sv_samplerate_t sr = model->getSampleRate();
     SpectrogramRangeMapper mapper(sr, getFFTSize());
     double newdist = mapper.getValueForPosition(step);
 
@@ -2486,8 +2529,9 @@
 RangeMapper *
 SpectrogramLayer::getNewVerticalZoomRangeMapper() const
 {
-    if (!m_model) return nullptr;
-    return new SpectrogramRangeMapper(m_model->getSampleRate(), getFFTSize());
+    auto model = ModelById::getAs<DenseTimeValueModel>(m_model);
+    if (!model) return nullptr;
+    return new SpectrogramRangeMapper(model->getSampleRate(), getFFTSize());
 }
 
 void
--- a/layer/SpectrogramLayer.h	Mon Jul 01 14:25:53 2019 +0100
+++ b/layer/SpectrogramLayer.h	Tue Jul 02 11:49:52 2019 +0100
@@ -306,10 +306,11 @@
 
     int getFFTSize() const; // m_windowSize * getOversampling()
 
+    // We take responsibility for registering/deregistering these
+    // models and caches with ModelById
     ModelId m_fftModel; // an FFTModel
-    Dense3DModelPeakCache *m_wholeCache;
-    Dense3DModelPeakCache *m_peakCache;
-    Dense3DModelPeakCache *getPeakCache() const { return m_peakCache; }
+    ModelId m_wholeCache; // a Dense3DModelPeakCache
+    ModelId m_peakCache; // a Dense3DModelPeakCache
     int m_peakCacheDivisor;
     void checkCacheSpace(int *suggestedPeakDivisor,
                          bool *createWholeCache) const;
--- a/layer/SpectrumLayer.cpp	Mon Jul 01 14:25:53 2019 +0100
+++ b/layer/SpectrumLayer.cpp	Tue Jul 02 11:49:52 2019 +0100
@@ -33,7 +33,6 @@
 
 
 SpectrumLayer::SpectrumLayer() :
-    m_originModel(nullptr),
     m_channel(-1),
     m_channelSet(false),
     m_windowSize(4096),
@@ -56,11 +55,7 @@
 
 SpectrumLayer::~SpectrumLayer()
 {
-    Model *m = const_cast<Model *>
-        (static_cast<const Model *>(m_sliceableModel));
-    if (m) m->aboutToDelete();
-    m_sliceableModel = nullptr;
-    delete m;
+    ModelById::release(m_sliceableModel);
 }
 
 void
@@ -74,20 +69,6 @@
     if (m_originModel == modelId) return;
     m_originModel = modelId;
 
-    if (newModel) {
-        //...
-    }
-    
-    //!!! todo - all of this
-    
-    if (m_sliceableModel) {
-        Model *m = const_cast<Model *>
-            (static_cast<const Model *>(m_sliceableModel));
-        m->aboutToDelete();
-        setSliceableModel(nullptr);
-        delete m;
-    }
-
     m_newFFTNeeded = true;
 
     emit layerParametersChanged();
@@ -112,26 +93,21 @@
 void
 SpectrumLayer::setupFFT()
 {
-    if (m_sliceableModel) {
-        Model *m = const_cast<Model *>
-            (static_cast<const Model *>(m_sliceableModel));
-        m->aboutToDelete();
-        setSliceableModel(nullptr);
-        delete m;
-    }
+    ModelById::release(m_sliceableModel);
+    m_sliceableModel = {};
 
-    if (!m_originModel) {
+    if (m_originModel.isNone()) {
         return;
     }
 
     int fftSize = getFFTSize();
 
-    FFTModel *newFFT = new FFTModel(m_originModel,
-                                    m_channel,
-                                    m_windowType,
-                                    m_windowSize,
-                                    getWindowIncrement(),
-                                    fftSize);
+    auto newFFT = std::make_shared<FFTModel>(m_originModel,
+                                             m_channel,
+                                             m_windowType,
+                                             m_windowSize,
+                                             getWindowIncrement(),
+                                             fftSize);
 
     if (m_minbin == 0 && m_maxbin == 0) {
         m_minbin = 1;
@@ -140,7 +116,8 @@
         m_maxbin = newFFT->getHeight();
     }
 
-    setSliceableModel(newFFT);
+    ModelById::add(newFFT);
+    setSliceableModel(newFFT->getId());
 
     m_biasCurve.clear();
     for (int i = 0; i < fftSize; ++i) {
@@ -411,15 +388,19 @@
 double
 SpectrumLayer::getBinForFrequency(double freq) const
 {
-    if (!m_sliceableModel) return 0;
-    double bin = (freq * getFFTSize()) / m_sliceableModel->getSampleRate();
+    auto sliceableModel = ModelById::getAs<DenseThreeDimensionalModel>
+        (m_sliceableModel);
+    if (!sliceableModel) return 0;
+    double bin = (freq * getFFTSize()) / sliceableModel->getSampleRate();
     return bin;
 }
 
 double
 SpectrumLayer::getBinForX(const LayerGeometryProvider *v, double x) const
 {
-    if (!m_sliceableModel) return 0;
+    auto sliceableModel = ModelById::getAs<DenseThreeDimensionalModel>
+        (m_sliceableModel);
+    if (!sliceableModel) return 0;
     double bin = getBinForFrequency(getFrequencyForX(v, x));
     return bin;
 }
@@ -427,7 +408,9 @@
 double
 SpectrumLayer::getFrequencyForX(const LayerGeometryProvider *v, double x) const
 {
-    if (!m_sliceableModel) return 0;
+    auto sliceableModel = ModelById::getAs<DenseThreeDimensionalModel>
+        (m_sliceableModel);
+    if (!sliceableModel) return 0;
 
     double fmin = getFrequencyForBin(m_minbin);
     double fmax = getFrequencyForBin(m_maxbin);
@@ -439,15 +422,19 @@
 double
 SpectrumLayer::getFrequencyForBin(double bin) const
 {
-    if (!m_sliceableModel) return 0;
-    double freq = (bin * m_sliceableModel->getSampleRate()) / getFFTSize();
+    auto sliceableModel = ModelById::getAs<DenseThreeDimensionalModel>
+        (m_sliceableModel);
+    if (!sliceableModel) return 0;
+    double freq = (bin * sliceableModel->getSampleRate()) / getFFTSize();
     return freq;
 }
 
 double
 SpectrumLayer::getXForBin(const LayerGeometryProvider *v, double bin) const
 {
-    if (!m_sliceableModel) return 0;
+    auto sliceableModel = ModelById::getAs<DenseThreeDimensionalModel>
+        (m_sliceableModel);
+    if (!sliceableModel) return 0;
     double x = getXForFrequency(v, getFrequencyForBin(bin));
     return x;
 }
@@ -455,7 +442,9 @@
 double
 SpectrumLayer::getXForFrequency(const LayerGeometryProvider *v, double freq) const
 {
-    if (!m_sliceableModel) return 0;
+    auto sliceableModel = ModelById::getAs<DenseThreeDimensionalModel>
+        (m_sliceableModel);
+    if (!sliceableModel) return 0;
 
     double fmin = getFrequencyForBin(m_minbin);
     double fmax = getFrequencyForBin(m_maxbin);
@@ -483,8 +472,12 @@
 
         if (value > 0.0) {
             value = 10.0 * log10(value);
-            if (value < m_threshold) value = m_threshold;
-        } else value = m_threshold;
+            if (value < m_threshold) {
+                value = m_threshold;
+            }
+        } else {
+            value = m_threshold;
+        }
 
         unit = "dBV";
 
@@ -521,6 +514,11 @@
 
     int sw = getVerticalScaleWidth(v, false, paint);
 
+    // Qt 5.13 deprecates QFontMetrics::width(), but its suggested
+    // replacement (horizontalAdvance) was only added in Qt 5.11
+    // which is too new for us
+#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
+
     QRect value(sw, cursorPos.y() - paint.fontMetrics().ascent() - 2,
                 paint.fontMetrics().width("0.0000001 V") + 2,
                 paint.fontMetrics().height());
@@ -551,7 +549,9 @@
 SpectrumLayer::paintCrosshairs(LayerGeometryProvider *v, QPainter &paint,
                                QPoint cursorPos) const
 {
-    if (!m_sliceableModel) return;
+    auto sliceableModel = ModelById::getAs<DenseThreeDimensionalModel>
+        (m_sliceableModel);
+    if (!sliceableModel) return;
 
     paint.save();
     QFont fn = paint.font();
@@ -638,7 +638,9 @@
 QString
 SpectrumLayer::getFeatureDescription(LayerGeometryProvider *v, QPoint &p) const
 {
-    if (!m_sliceableModel) return "";
+    auto sliceableModel = ModelById::getAs<DenseThreeDimensionalModel>
+        (m_sliceableModel);
+    if (!sliceableModel) return "";
 
     int minbin = 0, maxbin = 0, range = 0;
     QString genericDesc = SliceLayer::getFeatureDescriptionAux
@@ -659,10 +661,10 @@
     
     QString binstr;
     QString hzstr;
-    int minfreq = int(lrint((minbin * m_sliceableModel->getSampleRate()) /
+    int minfreq = int(lrint((minbin * sliceableModel->getSampleRate()) /
                             getFFTSize()));
     int maxfreq = int(lrint((std::max(maxbin, minbin)
-                             * m_sliceableModel->getSampleRate()) /
+                             * sliceableModel->getSampleRate()) /
                             getFFTSize()));
 
     if (maxbin != minbin) {
@@ -706,7 +708,7 @@
 
     QString description;
 
-    if (range > int(m_sliceableModel->getResolution())) {
+    if (range > int(sliceableModel->getResolution())) {
         description = tr("%1\nBin:\t%2 (%3)\n%4 value:\t%5\ndB:\t%6")
             .arg(genericDesc)
             .arg(binstr)
@@ -730,8 +732,8 @@
 void
 SpectrumLayer::paint(LayerGeometryProvider *v, QPainter &paint, QRect rect) const
 {
-    if (!m_originModel || !m_originModel->isOK() ||
-        !m_originModel->isReady()) {
+    auto originModel = ModelById::get(m_originModel);
+    if (!originModel || !originModel->isOK() || !originModel->isReady()) {
         SVDEBUG << "SpectrumLayer::paint: no origin model, or origin model not OK or not ready" << endl;
         return;
     }
@@ -741,8 +743,8 @@
         const_cast<SpectrumLayer *>(this)->setupFFT(); //ugh
     }
 
-    FFTModel *fft = dynamic_cast<FFTModel *>
-        (const_cast<DenseThreeDimensionalModel *>(m_sliceableModel));
+    auto fft = ModelById::getAs<FFTModel>(m_sliceableModel);
+    if (!fft) return;
 
     double thresh = (pow(10, -6) / m_gain) * (getFFTSize() / 2.0); // -60dB adj