diff align/Align.cpp @ 778:83a7b10b7415

Merge from branch pitch-align
author Chris Cannam
date Fri, 26 Jun 2020 13:48:52 +0100
parents 87d33e79855b
children b651dc5ff555
line wrap: on
line diff
--- a/align/Align.cpp	Wed Jun 03 13:58:29 2020 +0100
+++ b/align/Align.cpp	Fri Jun 26 13:48:52 2020 +0100
@@ -13,20 +13,67 @@
 */
 
 #include "Align.h"
-#include "TransformAligner.h"
+
+#include "LinearAligner.h"
+#include "MATCHAligner.h"
+#include "TransformDTWAligner.h"
 #include "ExternalProgramAligner.h"
+
 #include "framework/Document.h"
 
+#include "transform/Transform.h"
+#include "transform/TransformFactory.h"
+
+#include "base/Pitch.h"
+
 #include <QSettings>
 #include <QTimer>
 
+using std::make_shared;
+
+QString
+Align::getAlignmentTypeTag(AlignmentType type)
+{
+    switch (type) {
+    case NoAlignment:
+    default:
+        return "no-alignment";
+    case LinearAlignment:
+        return "linear-alignment";
+    case TrimmedLinearAlignment:
+        return "trimmed-linear-alignment";
+    case MATCHAlignment:
+        return "match-alignment";
+    case MATCHAlignmentWithPitchCompare:
+        return "match-alignment-with-pitch";
+    case SungNoteContourAlignment:
+        return "sung-note-alignment";
+    case TransformDrivenDTWAlignment:
+        return "transform-driven-alignment";
+    case ExternalProgramAlignment:
+        return "external-program-alignment";
+    }
+}
+
+Align::AlignmentType
+Align::getAlignmentTypeForTag(QString tag)
+{
+    for (int i = 0; i <= int(LastAlignmentType); ++i) {
+        if (tag == getAlignmentTypeTag(AlignmentType(i))) {
+            return AlignmentType(i);
+        }
+    }
+    return NoAlignment;
+}
+
 void
 Align::alignModel(Document *doc,
                   ModelId reference,
                   ModelId toAlign)
 {
-    addAligner(doc, reference, toAlign);
-    m_aligners[toAlign]->begin();
+    if (addAligner(doc, reference, toAlign)) {
+        m_aligners[toAlign]->begin();
+    }
 }
 
 void
@@ -34,47 +81,116 @@
                          ModelId reference,
                          ModelId toAlign)
 {
-    addAligner(doc, reference, toAlign);
-    int delay = 500 + 500 * int(m_aligners.size());
+    int delay = 700 * int(m_aligners.size());
     if (delay > 3500) {
         delay = 3500;
     }
+    if (!addAligner(doc, reference, toAlign)) {
+        return;
+    }
     SVCERR << "Align::scheduleAlignment: delaying " << delay << "ms" << endl;
     QTimer::singleShot(delay, m_aligners[toAlign].get(), SLOT(begin()));
 }
 
-void
+bool
 Align::addAligner(Document *doc,
                   ModelId reference,
                   ModelId toAlign)
 {
-    bool useProgram;
-    QString program;
-    getAlignerPreference(useProgram, program);
+    AlignmentType type = getAlignmentPreference();
     
     std::shared_ptr<Aligner> aligner;
 
+    if (m_aligners.find(toAlign) != m_aligners.end()) {
+        // We don't want a callback on removeAligner to happen during
+        // our own call to addAligner! Disconnect and delete the old
+        // aligner first
+        disconnect(m_aligners[toAlign].get(), nullptr, this, nullptr);
+        m_aligners.erase(toAlign);
+    }
+    
     {
         // Replace the aligner with a new one. This also stops any
         // previously-running alignment, when the old entry is
         // replaced and its aligner destroyed.
         
         QMutexLocker locker(&m_mutex);
-    
-        if (useProgram && (program != "")) {
-            m_aligners[toAlign] =
-                std::make_shared<ExternalProgramAligner>(doc,
-                                                         reference,
-                                                         toAlign,
-                                                         program);
-        } else {
-            m_aligners[toAlign] =
-                std::make_shared<TransformAligner>(doc,
-                                                   reference,
-                                                   toAlign);
+
+        switch (type) {
+
+        case NoAlignment:
+            return false;
+
+        case LinearAlignment:
+        case TrimmedLinearAlignment: {
+            bool trimmed = (type == TrimmedLinearAlignment);
+            aligner = make_shared<LinearAligner>(doc,
+                                                 reference,
+                                                 toAlign,
+                                                 trimmed);
+            break;
         }
 
-        aligner = m_aligners[toAlign];
+        case MATCHAlignment:
+        case MATCHAlignmentWithPitchCompare: {
+
+            bool withTuningDifference =
+                (type == MATCHAlignmentWithPitchCompare);
+            
+            aligner = make_shared<MATCHAligner>(doc,
+                                                reference,
+                                                toAlign,
+                                                withTuningDifference);
+            break;
+        }
+
+        case SungNoteContourAlignment:
+        {
+            auto refModel = ModelById::get(reference);
+            if (!refModel) return false;
+
+            Transform transform = TransformFactory::getInstance()->
+                getDefaultTransformFor("vamp:pyin:pyin:notes",
+                                       refModel->getSampleRate());
+
+            aligner = make_shared<TransformDTWAligner>
+                (doc,
+                 reference,
+                 toAlign,
+                 transform,
+                 [](double prev, double curr) {
+                     RiseFallDTW::Value v;
+                     if (curr <= 0.0) {
+                         v = { RiseFallDTW::Direction::None, 0.0 };
+                     } else if (prev <= 0.0) {
+                         v = { RiseFallDTW::Direction::Up, 0.0 };
+                     } else {
+                         double prevP = Pitch::getPitchForFrequency(prev);
+                         double currP = Pitch::getPitchForFrequency(curr);
+                         if (currP >= prevP) {
+                             v = { RiseFallDTW::Direction::Up, currP - prevP };
+                         } else {
+                             v = { RiseFallDTW::Direction::Down, prevP - currP };
+                         }
+                     }
+                     return v;
+                 });
+            break;
+        }
+        
+        case TransformDrivenDTWAlignment:
+            throw std::logic_error("Not yet implemented"); //!!!
+
+        case ExternalProgramAlignment: {
+            aligner = make_shared<ExternalProgramAligner>
+                (doc,
+                 reference,
+                 toAlign,
+                 getPreferredAlignmentProgram());
+        }
+        }
+
+        m_aligners[toAlign] = aligner;
     }
 
     connect(aligner.get(), SIGNAL(complete(ModelId)),
@@ -82,29 +198,75 @@
 
     connect(aligner.get(), SIGNAL(failed(ModelId, QString)),
             this, SLOT(alignerFailed(ModelId, QString)));
+
+    return true;
+}
+
+Align::AlignmentType
+Align::getAlignmentPreference()
+{
+    QSettings settings;
+    settings.beginGroup("Alignment");
+    QString tag = settings.value
+        ("alignment-type", getAlignmentTypeTag(MATCHAlignment)).toString();
+    return getAlignmentTypeForTag(tag);
+}
+
+QString
+Align::getPreferredAlignmentProgram()
+{
+    QSettings settings;
+    settings.beginGroup("Alignment");
+    return settings.value("alignment-program", "").toString();
+}
+
+Transform
+Align::getPreferredAlignmentTransform()
+{
+    QSettings settings;
+    settings.beginGroup("Alignment");
+    QString xml = settings.value("alignment-transform", "").toString();
+    return Transform(xml);
 }
 
 void
-Align::getAlignerPreference(bool &useProgram, QString &program)
+Align::setAlignmentPreference(AlignmentType type)
 {
     QSettings settings;
-    settings.beginGroup("Preferences");
-    useProgram = settings.value("use-external-alignment", false).toBool();
-    program = settings.value("external-alignment-program", "").toString();
+    settings.beginGroup("Alignment");
+    QString tag = getAlignmentTypeTag(type);
+    settings.setValue("alignment-type", tag);
+    settings.endGroup();
+}
+
+void
+Align::setPreferredAlignmentProgram(QString program)
+{
+    QSettings settings;
+    settings.beginGroup("Alignment");
+    settings.setValue("alignment-program", program);
+    settings.endGroup();
+}
+
+void
+Align::setPreferredAlignmentTransform(Transform transform)
+{
+    QSettings settings;
+    settings.beginGroup("Alignment");
+    settings.setValue("alignment-transform", transform.toXmlString());
     settings.endGroup();
 }
 
 bool
 Align::canAlign() 
 {
-    bool useProgram;
-    QString program;
-    getAlignerPreference(useProgram, program);
+    AlignmentType type = getAlignmentPreference();
 
-    if (useProgram) {
-        return ExternalProgramAligner::isAvailable(program);
+    if (type == ExternalProgramAlignment) {
+        return ExternalProgramAligner::isAvailable
+            (getPreferredAlignmentProgram());
     } else {
-        return TransformAligner::isAvailable();
+        return MATCHAligner::isAvailable();
     }
 }