diff transform/FeatureExtractionModelTransformer.cpp @ 1739:565575463752 by-id

Some work on models and transformers
author Chris Cannam
date Wed, 26 Jun 2019 14:59:09 +0100
parents 5b7b01da430a
children fe3f7f8df3a3
line wrap: on
line diff
--- a/transform/FeatureExtractionModelTransformer.cpp	Wed Jun 26 10:21:15 2019 +0100
+++ b/transform/FeatureExtractionModelTransformer.cpp	Wed Jun 26 14:59:09 2019 +0100
@@ -83,7 +83,7 @@
     // initialise based purely on the first transform in the list (but
     // first check that they are actually similar as promised)
 
-    for (int j = 1; j < (int)m_transforms.size(); ++j) {
+    for (int j = 1; in_range_for(m_transforms, j); ++j) {
         if (!areTransformsSimilar(m_transforms[0], m_transforms[j])) {
             m_message = tr("Transforms supplied to a single FeatureExtractionModelTransformer instance must be similar in every respect except plugin output");
             SVCERR << m_message << endl;
@@ -104,7 +104,7 @@
         return false;
     }
 
-    DenseTimeValueModel *input = getConformingInput();
+    auto input = ModelById::getAs<DenseTimeValueModel>(getInputModel());
     if (!input) {
         m_message = tr("Input model for feature extraction plugin \"%1\" is of wrong type (internal error?)").arg(pluginId);
         SVCERR << m_message << endl;
@@ -158,7 +158,9 @@
             SVDEBUG << "Initialisation failed, trying again with preferred step = "
                     << preferredStep << ", block = " << preferredBlock << endl;
             
-            if (!m_plugin->initialise(channelCount, preferredStep, preferredBlock)) {
+            if (!m_plugin->initialise(channelCount,
+                                      preferredStep,
+                                      preferredBlock)) {
 
                 SVDEBUG << "Initialisation failed again" << endl;
                 
@@ -221,20 +223,22 @@
         return false;
     }
 
-    for (int j = 0; j < (int)m_transforms.size(); ++j) {
+    for (int j = 0; in_range_for(m_transforms, j); ++j) {
 
-        for (int i = 0; i < (int)outputs.size(); ++i) {
-//        SVDEBUG << "comparing output " << i << " name \"" << outputs[i].identifier << "\" with expected \"" << m_transform.getOutput() << "\"" << endl;
+        for (int i = 0; in_range_for(outputs, i); ++i) {
+
             if (m_transforms[j].getOutput() == "" ||
-                outputs[i].identifier == m_transforms[j].getOutput().toStdString()) {
+                outputs[i].identifier ==
+                m_transforms[j].getOutput().toStdString()) {
+                
                 m_outputNos.push_back(i);
-                m_descriptors.push_back(new Vamp::Plugin::OutputDescriptor(outputs[i]));
+                m_descriptors.push_back(outputs[i]);
                 m_fixedRateFeatureNos.push_back(-1); // we increment before use
                 break;
             }
         }
 
-        if ((int)m_descriptors.size() <= j) {
+        if (!in_range_for(m_descriptors, j)) {
             m_message = tr("Plugin \"%1\" has no output named \"%2\"")
                 .arg(pluginId)
                 .arg(m_transforms[j].getOutput());
@@ -243,7 +247,7 @@
         }
     }
 
-    for (int j = 0; j < (int)m_transforms.size(); ++j) {
+    for (int j = 0; in_range_for(m_transforms, j); ++j) {
         createOutputModels(j);
     }
 
@@ -271,16 +275,15 @@
         m_message = e.what();
     }
     m_plugin = nullptr;
-        
-    for (int j = 0; j < (int)m_descriptors.size(); ++j) {
-        delete m_descriptors[j];
-    }
+
+    m_descriptors.clear();
 }
 
 void
 FeatureExtractionModelTransformer::createOutputModels(int n)
 {
-    DenseTimeValueModel *input = getConformingInput();
+    auto input = ModelById::getAs<DenseTimeValueModel>(getInputModel());
+    if (!input) return;
     
     PluginRDFDescription description(m_transforms[n].getPluginIdentifier());
     QString outputId = m_transforms[n].getOutput();
@@ -288,20 +291,17 @@
     int binCount = 1;
     float minValue = 0.0, maxValue = 0.0;
     bool haveExtents = false;
-    bool haveBinCount = m_descriptors[n]->hasFixedBinCount;
+    bool haveBinCount = m_descriptors[n].hasFixedBinCount;
 
     if (haveBinCount) {
-        binCount = (int)m_descriptors[n]->binCount;
+        binCount = (int)m_descriptors[n].binCount;
     }
 
     m_needAdditionalModels[n] = false;
 
-//    cerr << "FeatureExtractionModelTransformer: output bin count "
-//              << binCount << endl;
-
-    if (binCount > 0 && m_descriptors[n]->hasKnownExtents) {
-        minValue = m_descriptors[n]->minValue;
-        maxValue = m_descriptors[n]->maxValue;
+    if (binCount > 0 && m_descriptors[n].hasKnownExtents) {
+        minValue = m_descriptors[n].minValue;
+        maxValue = m_descriptors[n].maxValue;
         haveExtents = true;
     }
 
@@ -309,10 +309,10 @@
     sv_samplerate_t outputRate = modelRate;
     int modelResolution = 1;
 
-    if (m_descriptors[n]->sampleType != 
+    if (m_descriptors[n].sampleType != 
         Vamp::Plugin::OutputDescriptor::OneSamplePerStep) {
 
-        outputRate = m_descriptors[n]->sampleRate;
+        outputRate = m_descriptors[n].sampleRate;
 
         //!!! SV doesn't actually support display of models that have
         //!!! different underlying rates together -- so we always set
@@ -328,7 +328,7 @@
         }
     }
 
-    switch (m_descriptors[n]->sampleType) {
+    switch (m_descriptors[n].sampleType) {
 
     case Vamp::Plugin::OutputDescriptor::VariableSampleRate:
         if (outputRate != 0.0) {
@@ -342,7 +342,7 @@
 
     case Vamp::Plugin::OutputDescriptor::FixedSampleRate:
         if (outputRate <= 0.0) {
-            SVDEBUG << "WARNING: Fixed sample-rate plugin reports invalid sample rate " << m_descriptors[n]->sampleRate << "; defaulting to input rate of " << input->getSampleRate() << endl;
+            SVDEBUG << "WARNING: Fixed sample-rate plugin reports invalid sample rate " << m_descriptors[n].sampleRate << "; defaulting to input rate of " << input->getSampleRate() << endl;
             modelResolution = 1;
         } else {
             modelResolution = int(round(modelRate / outputRate));
@@ -353,21 +353,23 @@
 
     bool preDurationPlugin = (m_plugin->getVampApiVersion() < 2);
 
-    Model *out = nullptr;
+    std::shared_ptr<Model> out;
 
     if (binCount == 0 &&
-        (preDurationPlugin || !m_descriptors[n]->hasDuration)) {
+        (preDurationPlugin || !m_descriptors[n].hasDuration)) {
 
         // Anything with no value and no duration is an instant
 
-        out = new SparseOneDimensionalModel(modelRate, modelResolution, false);
+        out = std::make_shared<SparseOneDimensionalModel>
+            (modelRate, modelResolution, false);
+
         QString outputEventTypeURI = description.getOutputEventTypeURI(outputId);
         out->setRDFTypeURI(outputEventTypeURI);
 
     } else if ((preDurationPlugin && binCount > 1 &&
-                (m_descriptors[n]->sampleType ==
+                (m_descriptors[n].sampleType ==
                  Vamp::Plugin::OutputDescriptor::VariableSampleRate)) ||
-               (!preDurationPlugin && m_descriptors[n]->hasDuration)) {
+               (!preDurationPlugin && m_descriptors[n].hasDuration)) {
 
         // For plugins using the old v1 API without explicit duration,
         // we treat anything that has multiple bins (i.e. that has the
@@ -398,9 +400,9 @@
 
         // Regions do not have units of Hz or MIDI things (a sweeping
         // assumption!)
-        if (m_descriptors[n]->unit == "Hz" ||
-            m_descriptors[n]->unit.find("MIDI") != std::string::npos ||
-            m_descriptors[n]->unit.find("midi") != std::string::npos) {
+        if (m_descriptors[n].unit == "Hz" ||
+            m_descriptors[n].unit.find("MIDI") != std::string::npos ||
+            m_descriptors[n].unit.find("midi") != std::string::npos) {
             isNoteModel = true;
         }
 
@@ -420,8 +422,8 @@
                 model = new NoteModel
                     (modelRate, modelResolution, false);
             }
-            model->setScaleUnits(m_descriptors[n]->unit.c_str());
-            out = model;
+            model->setScaleUnits(m_descriptors[n].unit.c_str());
+            out.reset(model);
 
         } else {
 
@@ -433,15 +435,15 @@
                 model = new RegionModel
                     (modelRate, modelResolution, false);
             }
-            model->setScaleUnits(m_descriptors[n]->unit.c_str());
-            out = model;
+            model->setScaleUnits(m_descriptors[n].unit.c_str());
+            out.reset(model);
         }
 
         QString outputEventTypeURI = description.getOutputEventTypeURI(outputId);
         out->setRDFTypeURI(outputEventTypeURI);
 
     } else if (binCount == 1 ||
-               (m_descriptors[n]->sampleType == 
+               (m_descriptors[n].sampleType == 
                 Vamp::Plugin::OutputDescriptor::VariableSampleRate)) {
 
         // Anything that is not a 1D, note, or interval model and that
@@ -482,7 +484,7 @@
         Vamp::Plugin::OutputList outputs = m_plugin->getOutputDescriptors();
         model->setScaleUnits(outputs[m_outputNos[n]].unit.c_str());
 
-        out = model;
+        out.reset(model);
 
         QString outputEventTypeURI = description.getOutputEventTypeURI(outputId);
         out->setRDFTypeURI(outputEventTypeURI);
@@ -499,23 +501,24 @@
              EditableDenseThreeDimensionalModel::BasicMultirateCompression,
              false);
 
-        if (!m_descriptors[n]->binNames.empty()) {
+        if (!m_descriptors[n].binNames.empty()) {
             std::vector<QString> names;
-            for (int i = 0; i < (int)m_descriptors[n]->binNames.size(); ++i) {
-                names.push_back(m_descriptors[n]->binNames[i].c_str());
+            for (int i = 0; i < (int)m_descriptors[n].binNames.size(); ++i) {
+                names.push_back(m_descriptors[n].binNames[i].c_str());
             }
             model->setBinNames(names);
         }
         
-        out = model;
+        out.reset(model);
 
         QString outputSignalTypeURI = description.getOutputSignalTypeURI(outputId);
         out->setRDFTypeURI(outputSignalTypeURI);
     }
 
     if (out) {
-        out->setSourceModel(input);
-        m_outputs.push_back(out);
+        out->setSourceModel(getInputModel());
+        ModelById::add(out);
+        m_outputs.push_back(out->getId());
     }
 }
 
@@ -540,13 +543,9 @@
 FeatureExtractionModelTransformer::getAdditionalOutputModels()
 {
     Models mm;
-    for (AdditionalModelMap::iterator i = m_additionalModels.begin();
-         i != m_additionalModels.end(); ++i) {
-        for (std::map<int, SparseTimeValueModel *>::iterator j =
-                 i->second.begin();
-             j != i->second.end(); ++j) {
-            SparseTimeValueModel *m = j->second;
-            if (m) mm.push_back(m);
+    for (auto mp : m_additionalModels) {
+        for (auto m: mp.second) {
+            mm.push_back(m.second);
         }
     }
     return mm;
@@ -555,34 +554,45 @@
 bool
 FeatureExtractionModelTransformer::willHaveAdditionalOutputModels()
 {
-    for (std::map<int, bool>::const_iterator i =
-             m_needAdditionalModels.begin(); 
-         i != m_needAdditionalModels.end(); ++i) {
-        if (i->second) return true;
+    for (auto p : m_needAdditionalModels) {
+        if (p.second) return true;
     }
     return false;
 }
 
-SparseTimeValueModel *
+ModelId
 FeatureExtractionModelTransformer::getAdditionalModel(int n, int binNo)
 {
-//    std::cerr << "getAdditionalModel(" << n << ", " << binNo << ")" << std::endl;
-
     if (binNo == 0) {
-        std::cerr << "Internal error: binNo == 0 in getAdditionalModel (should be using primary model)" << std::endl;
-        return nullptr;
+        SVCERR << "Internal error: binNo == 0 in getAdditionalModel (should be using primary model, not calling getAdditionalModel)" << endl;
+        return {};
     }
 
-    if (!m_needAdditionalModels[n]) return nullptr;
-    if (!isOutput<SparseTimeValueModel>(n)) return nullptr;
-    if (m_additionalModels[n][binNo]) return m_additionalModels[n][binNo];
+    if (!in_range_for(m_outputs, n)) {
+        SVCERR << "getAdditionalModel: Output " << n << " out of range" << endl;
+        return {};
+    }
 
-    std::cerr << "getAdditionalModel(" << n << ", " << binNo << "): creating" << std::endl;
+    if (!in_range_for(m_needAdditionalModels, n) ||
+        !m_needAdditionalModels[n]) {
+        return {};
+    }
+    
+    if (!m_additionalModels[n][binNo].isNone()) {
+        return m_additionalModels[n][binNo];
+    }
 
-    SparseTimeValueModel *baseModel = getConformingOutput<SparseTimeValueModel>(n);
-    if (!baseModel) return nullptr;
+    SVDEBUG << "getAdditionalModel(" << n << ", " << binNo
+            << "): creating" << endl;
 
-    std::cerr << "getAdditionalModel(" << n << ", " << binNo << "): (from " << baseModel << ")" << std::endl;
+    auto baseModel = ModelById::getAs<SparseTimeValueModel>(m_outputs[n]);
+    if (!baseModel) {
+        SVCERR << "getAdditionalModel: Output model not conformable, or has vanished" << endl;
+        return {};
+    }
+    
+    SVDEBUG << "getAdditionalModel(" << n << ", " << binNo
+            << "): (from " << baseModel << ")" << endl;
 
     SparseTimeValueModel *additional =
         new SparseTimeValueModel(baseModel->getSampleRate(),
@@ -594,21 +604,10 @@
     additional->setScaleUnits(baseModel->getScaleUnits());
     additional->setRDFTypeURI(baseModel->getRDFTypeURI());
 
-    m_additionalModels[n][binNo] = additional;
-    return additional;
-}
-
-DenseTimeValueModel *
-FeatureExtractionModelTransformer::getConformingInput()
-{
-//    SVDEBUG << "FeatureExtractionModelTransformer::getConformingInput: input model is " << getInputModel() << endl;
-
-    DenseTimeValueModel *dtvm =
-        dynamic_cast<DenseTimeValueModel *>(getInputModel());
-    if (!dtvm) {
-        SVDEBUG << "FeatureExtractionModelTransformer::getConformingInput: WARNING: Input model is not conformable to DenseTimeValueModel" << endl;
-    }
-    return dtvm;
+    ModelId additionalId = additional->getId();
+    ModelById::add(std::shared_ptr<SparseTimeValueModel>(additional));
+    m_additionalModels[n][binNo] = additionalId;
+    return additionalId;
 }
 
 void
@@ -624,12 +623,6 @@
         m_message = e.what();
         return;
     }
-    
-    DenseTimeValueModel *input = getConformingInput();
-    if (!input) {
-        abandon();
-        return;
-    }
 
     if (m_outputs.empty()) {
         abandon();
@@ -638,14 +631,30 @@
 
     Transform primaryTransform = m_transforms[0];
 
-    while (!input->isReady() && !m_abandoned) {
-        SVDEBUG << "FeatureExtractionModelTransformer::run: Waiting for input model to be ready..." << endl;
-        usleep(500000);
+    bool ready = false;
+    while (!ready && !m_abandoned) {
+        { // scope so as to release input shared_ptr before sleeping
+            auto input = ModelById::getAs<DenseTimeValueModel>(getInputModel());
+            if (!input) {
+                abandon();
+                return;
+            }
+            ready = input->isReady();
+        }
+        if (!ready) {
+            SVDEBUG << "FeatureExtractionModelTransformer::run: Waiting for input model to be ready..." << endl;
+            usleep(500000);
+        }
     }
-    SVDEBUG << "FeatureExtractionModelTransformer::run: Waited, ready = "
-            << input->isReady() << ", m_abandoned = " << m_abandoned << endl;
     if (m_abandoned) return;
 
+    auto input = ModelById::getAs<DenseTimeValueModel>(getInputModel());
+    if (!input) {
+        SVCERR << "FeatureExtractionModelTransformer::run: Input model not (no longer?) available, abandoning" << endl;
+        abandon();
+        return;
+    }
+
     sv_samplerate_t sampleRate = input->getSampleRate();
 
     int channelCount = input->getChannelCount();
@@ -669,28 +678,28 @@
     if (frequencyDomain) {
         for (int ch = 0; ch < channelCount; ++ch) {
             FFTModel *model = new FFTModel
-                                  (getConformingInput(),
-                                   channelCount == 1 ? m_input.getChannel() : ch,
-                                   primaryTransform.getWindowType(),
-                                   blockSize,
-                                   stepSize,
-                                   blockSize);
+//!!!                (input->getId(),
+                (nullptr,
+                 channelCount == 1 ? m_input.getChannel() : ch,
+                 primaryTransform.getWindowType(),
+                 blockSize,
+                 stepSize,
+                 blockSize);
             if (!model->isOK() || model->getError() != "") {
                 QString err = model->getError();
                 delete model;
-                for (int j = 0; j < (int)m_outputNos.size(); ++j) {
+                for (int j = 0; in_range_for(m_outputNos, j); ++j) {
                     setCompletion(j, 100);
                 }
                 //!!! need a better way to handle this -- previously we were using a QMessageBox but that isn't an appropriate thing to do here either
                 throw AllocationFailed("Failed to create the FFT model for this feature extraction model transformer: error is: " + err);
             }
             fftModels.push_back(model);
-            cerr << "created model for channel " << ch << endl;
         }
     }
 
-    sv_frame_t startFrame = m_input.getModel()->getStartFrame();
-    sv_frame_t endFrame = m_input.getModel()->getEndFrame();
+    sv_frame_t startFrame = input->getStartFrame();
+    sv_frame_t endFrame = input->getEndFrame();
 
     RealTime contextStartRT = primaryTransform.getStartTime();
     RealTime contextDurationRT = primaryTransform.getDuration();
@@ -716,7 +725,7 @@
 
     long prevCompletion = 0;
 
-    for (int j = 0; j < (int)m_outputNos.size(); ++j) {
+    for (int j = 0; in_range_for(m_outputNos, j); ++j) {
         setCompletion(j, 0);
     }
 
@@ -734,10 +743,14 @@
 
             if (frequencyDomain) {
                 if (blockFrame - int(blockSize)/2 >
-                    contextStart + contextDuration) break;
+                    contextStart + contextDuration) {
+                    break;
+                }
             } else {
                 if (blockFrame >= 
-                    contextStart + contextDuration) break;
+                    contextStart + contextDuration) {
+                    break;
+                }
             }
 
 #ifdef DEBUG_FEATURE_EXTRACTION_TRANSFORMER_RUN
@@ -750,7 +763,7 @@
                 ((((blockFrame - contextStart) / stepSize) * 99) /
                  (contextDuration / stepSize + 1));
 
-            // channelCount is either m_input.getModel()->channelCount or 1
+            // channelCount is either input->channelCount or 1
 
             if (frequencyDomain) {
                 for (int ch = 0; ch < channelCount; ++ch) {
@@ -863,8 +876,10 @@
         startFrame = 0;
     }
 
-    DenseTimeValueModel *input = getConformingInput();
-    if (!input) return;
+    auto input = ModelById::getAs<DenseTimeValueModel>(getInputModel());
+    if (!input) {
+        return;
+    }
     
     sv_frame_t got = 0;
 
@@ -907,7 +922,10 @@
                                               sv_frame_t blockFrame,
                                               const Vamp::Plugin::Feature &feature)
 {
-    sv_samplerate_t inputRate = m_input.getModel()->getSampleRate();
+    auto input = ModelById::get(getInputModel());
+    if (!input) return;
+
+    sv_samplerate_t inputRate = input->getSampleRate();
 
 //    cerr << "FeatureExtractionModelTransformer::addFeature: blockFrame = "
 //              << blockFrame << ", hasTimestamp = " << feature.hasTimestamp
@@ -917,7 +935,7 @@
 
     sv_frame_t frame = blockFrame;
 
-    if (m_descriptors[n]->sampleType ==
+    if (m_descriptors[n].sampleType ==
         Vamp::Plugin::OutputDescriptor::VariableSampleRate) {
 
         if (!feature.hasTimestamp) {
@@ -933,10 +951,10 @@
 //        cerr << "variable sample rate: timestamp = " << feature.timestamp
 //             << " at input rate " << inputRate << " -> " << frame << endl;
         
-    } else if (m_descriptors[n]->sampleType ==
+    } else if (m_descriptors[n].sampleType ==
                Vamp::Plugin::OutputDescriptor::FixedSampleRate) {
 
-        sv_samplerate_t rate = m_descriptors[n]->sampleRate;
+        sv_samplerate_t rate = m_descriptors[n].sampleRate;
         if (rate <= 0.0) {
             rate = inputRate;
         }
@@ -949,7 +967,7 @@
         }
 
 //        cerr << "m_fixedRateFeatureNo = " << m_fixedRateFeatureNos[n]
-//             << ", m_descriptor->sampleRate = " << m_descriptors[n]->sampleRate
+//             << ", m_descriptor->sampleRate = " << m_descriptors[n].sampleRate
 //             << ", inputRate = " << inputRate
 //             << " giving frame = ";
         frame = lrint((double(m_fixedRateFeatureNos[n]) / rate) * inputRate);
@@ -971,122 +989,128 @@
     // to, we instead test what sort of model the constructor decided
     // to create.
 
-    if (isOutput<SparseOneDimensionalModel>(n)) {
+    ModelId outputId = m_outputs[n];
+    bool found = false;
+    
+    if (!found) {
+        auto model = ModelById::getAs<SparseOneDimensionalModel>(outputId);
+        if (model) {
+            found = true;
+            model->add(Event(frame, feature.label.c_str()));
+        }
+    }
+    
+    if (!found) {
+        auto model = ModelById::getAs<SparseTimeValueModel>(outputId);
+        if (model) {
+            found = true;
 
-        SparseOneDimensionalModel *model =
-            getConformingOutput<SparseOneDimensionalModel>(n);
-        if (!model) return;
+            for (int i = 0; in_range_for(feature.values, i); ++i) {
 
-        model->add(Event(frame, feature.label.c_str()));
-        
-    } else if (isOutput<SparseTimeValueModel>(n)) {
+                float value = feature.values[i];
 
-        SparseTimeValueModel *model =
-            getConformingOutput<SparseTimeValueModel>(n);
-        if (!model) return;
+                QString label = feature.label.c_str();
+                if (feature.values.size() > 1) {
+                    label = QString("[%1] %2").arg(i+1).arg(label);
+                }
 
-        for (int i = 0; i < (int)feature.values.size(); ++i) {
+                auto targetModel = model;
 
-            float value = feature.values[i];
+                if (m_needAdditionalModels[n] && i > 0) {
+                    targetModel = ModelById::getAs<SparseTimeValueModel>
+                        (getAdditionalModel(n, i));
+                    if (!targetModel) targetModel = model;
+                }
 
-            QString label = feature.label.c_str();
-            if (feature.values.size() > 1) {
-                label = QString("[%1] %2").arg(i+1).arg(label);
+                targetModel->add(Event(frame, value, label));
+            }
+        }
+    }
+    
+    if (!found) {
+        if (ModelById::getAs<NoteModel>(outputId) ||
+            ModelById::getAs<RegionModel>(outputId)) {
+            found = true;
+
+            int index = 0;
+
+            float value = 0.0;
+            if ((int)feature.values.size() > index) {
+                value = feature.values[index++];
             }
 
-            SparseTimeValueModel *targetModel = model;
-
-            if (m_needAdditionalModels[n] && i > 0) {
-                targetModel = getAdditionalModel(n, i);
-                if (!targetModel) targetModel = model;
-//                std::cerr << "adding point to model " << targetModel
-//                          << " for output " << n << " bin " << i << std::endl;
+            sv_frame_t duration = 1;
+            if (feature.hasDuration) {
+                duration = RealTime::realTime2Frame(feature.duration, inputRate);
+            } else {
+                if (in_range_for(feature.values, index)) {
+                    duration = lrintf(feature.values[index++]);
+                }
             }
 
-            targetModel->add(Event(frame, value, label));
-        }
+            auto noteModel = ModelById::getAs<NoteModel>(outputId);
+            if (noteModel) {
 
-    } else if (isOutput<NoteModel>(n) || isOutput<RegionModel>(n)) {
+                float velocity = 100;
+                if ((int)feature.values.size() > index) {
+                    velocity = feature.values[index++];
+                }
+                if (velocity < 0) velocity = 127;
+                if (velocity > 127) velocity = 127;
 
-        int index = 0;
+                noteModel->add(Event(frame, value, // value is pitch
+                                     duration,
+                                     velocity / 127.f,
+                                     feature.label.c_str()));
+            }
 
-        float value = 0.0;
-        if ((int)feature.values.size() > index) {
-            value = feature.values[index++];
-        }
+            auto regionModel = ModelById::getAs<RegionModel>(outputId);
+            if (regionModel) {
 
-        sv_frame_t duration = 1;
-        if (feature.hasDuration) {
-            duration = RealTime::realTime2Frame(feature.duration, inputRate);
-        } else {
-            if (in_range_for(feature.values, index)) {
-                duration = lrintf(feature.values[index++]);
+                if (feature.hasDuration && !feature.values.empty()) {
+
+                    for (int i = 0; in_range_for(feature.values, i); ++i) {
+
+                        float value = feature.values[i];
+
+                        QString label = feature.label.c_str();
+                        if (feature.values.size() > 1) {
+                            label = QString("[%1] %2").arg(i+1).arg(label);
+                        }
+
+                        regionModel->add(Event(frame,
+                                               value,
+                                               duration,
+                                               label));
+                    }
+                } else {
+            
+                    regionModel->add(Event(frame,
+                                           value,
+                                           duration,
+                                           feature.label.c_str()));
+                }
             }
         }
+    }
 
-        if (isOutput<NoteModel>(n)) {
-
-            float velocity = 100;
-            if ((int)feature.values.size() > index) {
-                velocity = feature.values[index++];
-            }
-            if (velocity < 0) velocity = 127;
-            if (velocity > 127) velocity = 127;
-
-            NoteModel *model = getConformingOutput<NoteModel>(n);
-            if (!model) return;
-            model->add(Event(frame, value, // value is pitch
-                             duration,
-                             velocity / 127.f,
-                             feature.label.c_str()));
-        } else {
-
-            RegionModel *model = getConformingOutput<RegionModel>(n);
-            if (!model) return;
-
-            if (feature.hasDuration && !feature.values.empty()) {
-
-                for (int i = 0; i < (int)feature.values.size(); ++i) {
-
-                    float value = feature.values[i];
-
-                    QString label = feature.label.c_str();
-                    if (feature.values.size() > 1) {
-                        label = QString("[%1] %2").arg(i+1).arg(label);
-                    }
-
-                    model->add(Event(frame,
-                                     value,
-                                     duration,
-                                     label));
-                }
+    if (!found) {
+        auto model = ModelById::getAs
+            <EditableDenseThreeDimensionalModel>(outputId);
+        if (model) {
+            found = true;
+        
+            DenseThreeDimensionalModel::Column values = feature.values;
+        
+            if (!feature.hasTimestamp && m_fixedRateFeatureNos[n] >= 0) {
+                model->setColumn(m_fixedRateFeatureNos[n], values);
             } else {
-            
-                model->add(Event(frame,
-                                 value,
-                                 duration,
-                                 feature.label.c_str()));
+                model->setColumn(int(frame / model->getResolution()), values);
             }
         }
-        
-    } else if (isOutput<EditableDenseThreeDimensionalModel>(n)) {
-        
-        DenseThreeDimensionalModel::Column values = feature.values;
-        
-        EditableDenseThreeDimensionalModel *model =
-            getConformingOutput<EditableDenseThreeDimensionalModel>(n);
-        if (!model) return;
+    }
 
-//        cerr << "(note: model resolution = " << model->getResolution() << ")"
-//             << endl;
-
-        if (!feature.hasTimestamp && m_fixedRateFeatureNos[n] >= 0) {
-            model->setColumn(m_fixedRateFeatureNos[n], values);
-        } else {
-            model->setColumn(int(frame / model->getResolution()), values);
-        }
-
-    } else {
+    if (!found) {
         SVDEBUG << "FeatureExtractionModelTransformer::addFeature: Unknown output model type!" << endl;
     }
 }
@@ -1099,43 +1123,47 @@
               << completion << ")" << endl;
 #endif
 
-    if (isOutput<SparseOneDimensionalModel>(n)) {
+    ModelId outputId = m_outputs[n];
+    bool found = false;
+    
+    if (!found) {
+        auto model = ModelById::getAs<SparseOneDimensionalModel>(outputId);
+        if (model) {
+            found = true;
+            model->setCompletion(completion, true);
+        }
+    }
 
-        SparseOneDimensionalModel *model =
-            getConformingOutput<SparseOneDimensionalModel>(n);
-        if (!model) return;
-        if (model->isAbandoning()) abandon();
-        model->setCompletion(completion, true);
+    if (!found) {
+        auto model = ModelById::getAs<SparseTimeValueModel>(outputId);
+        if (model) {
+            found = true;
+            model->setCompletion(completion, true);
+        }
+    }
 
-    } else if (isOutput<SparseTimeValueModel>(n)) {
+    if (!found) {
+        auto model = ModelById::getAs<NoteModel>(outputId);
+        if (model) {
+            found = true;
+            model->setCompletion(completion, true);
+        }
+    }
 
-        SparseTimeValueModel *model =
-            getConformingOutput<SparseTimeValueModel>(n);
-        if (!model) return;
-        if (model->isAbandoning()) abandon();
-        model->setCompletion(completion, true);
+    if (!found) {
+        auto model = ModelById::getAs<RegionModel>(outputId);
+        if (model) {
+            found = true;
+            model->setCompletion(completion, true);
+        }
+    }
 
-    } else if (isOutput<NoteModel>(n)) {
-
-        NoteModel *model = getConformingOutput<NoteModel>(n);
-        if (!model) return;
-        if (model->isAbandoning()) abandon();
-        model->setCompletion(completion, true);
-        
-    } else if (isOutput<RegionModel>(n)) {
-
-        RegionModel *model = getConformingOutput<RegionModel>(n);
-        if (!model) return;
-        if (model->isAbandoning()) abandon();
-        model->setCompletion(completion, true);
-
-    } else if (isOutput<EditableDenseThreeDimensionalModel>(n)) {
-
-        EditableDenseThreeDimensionalModel *model =
-            getConformingOutput<EditableDenseThreeDimensionalModel>(n);
-        if (!model) return;
-        if (model->isAbandoning()) abandon();
-        model->setCompletion(completion, true); //!!!m_context.updates);
+    if (!found) {
+        auto model = ModelById::getAs<EditableDenseThreeDimensionalModel>(outputId);
+        if (model) {
+            found = true;
+            model->setCompletion(completion, true);
+        }
     }
 }