changeset 761:6429a164b7e1 pitch-align

Schedule alignments with a small delay to avoid too much UI unresponsiveness. Also overhaul error reporting to use signals throughout.
author Chris Cannam
date Wed, 06 May 2020 11:45:27 +0100
parents f32df46d0c84
children da57ab54f0e8 dd742e566e60
files align/Align.cpp align/Align.h align/Aligner.h align/ExternalProgramAligner.cpp align/ExternalProgramAligner.h align/TransformAligner.cpp align/TransformAligner.h framework/Document.cpp framework/Document.h framework/MainWindowBase.cpp framework/MainWindowBase.h
diffstat 11 files changed, 144 insertions(+), 67 deletions(-) [+]
line wrap: on
line diff
--- a/align/Align.cpp	Mon Apr 27 14:59:56 2020 +0100
+++ b/align/Align.cpp	Wed May 06 11:45:27 2020 +0100
@@ -18,12 +18,35 @@
 #include "framework/Document.h"
 
 #include <QSettings>
+#include <QTimer>
 
-bool
+void
 Align::alignModel(Document *doc,
                   ModelId reference,
-                  ModelId toAlign,
-                  QString &error)
+                  ModelId toAlign)
+{
+    addAligner(doc, reference, toAlign);
+    m_aligners[toAlign]->begin();
+}
+
+void
+Align::scheduleAlignment(Document *doc,
+                         ModelId reference,
+                         ModelId toAlign)
+{
+    addAligner(doc, reference, toAlign);
+    int delay = 500 + 500 * int(m_aligners.size());
+    if (delay > 3500) {
+        delay = 3500;
+    }
+    SVCERR << "Align::scheduleAlignment: delaying " << delay << "ms" << endl;
+    QTimer::singleShot(delay, m_aligners[toAlign].get(), SLOT(begin()));
+}
+
+void
+Align::addAligner(Document *doc,
+                  ModelId reference,
+                  ModelId toAlign)
 {
     bool useProgram;
     QString program;
@@ -56,8 +79,9 @@
 
     connect(aligner.get(), SIGNAL(complete(ModelId)),
             this, SLOT(alignerComplete(ModelId)));
-    
-    return aligner->begin(error);
+
+    connect(aligner.get(), SIGNAL(failed(ModelId, QString)),
+            this, SLOT(alignerFailed(ModelId, QString)));
 }
 
 void
@@ -87,23 +111,33 @@
 void
 Align::alignerComplete(ModelId alignmentModel)
 {
-    Aligner *aligner = qobject_cast<Aligner *>(sender());
+    removeAligner(sender());
+    emit alignmentComplete(alignmentModel);
+}
+
+void
+Align::alignerFailed(ModelId toAlign, QString error)
+{
+    removeAligner(sender());
+    emit alignmentFailed(toAlign, error);
+}
+
+void
+Align::removeAligner(QObject *obj)
+{
+    Aligner *aligner = qobject_cast<Aligner *>(obj);
     if (!aligner) {
-        SVCERR << "ERROR: Align::alignerComplete: Caller is not an Aligner"
-               << endl;
+        SVCERR << "ERROR: Align::removeAligner: Not an Aligner" << endl;
         return;
     }
 
-    {
-        QMutexLocker locker (&m_mutex);
+    QMutexLocker locker (&m_mutex);
 
-        for (auto p: m_aligners) {
-            if (aligner == p.second.get()) {
-                m_aligners.erase(p.first);
-                break;
-            }
+    for (auto p: m_aligners) {
+        if (aligner == p.second.get()) {
+            m_aligners.erase(p.first);
+            break;
         }
     }
+}
 
-    emit alignmentComplete(alignmentModel);
-}
--- a/align/Align.h	Mon Apr 27 14:59:56 2020 +0100
+++ b/align/Align.h	Wed May 06 11:45:27 2020 +0100
@@ -39,16 +39,15 @@
      * configured in the user preferences (either a plugin transform
      * or an external process) and is done asynchronously. 
      *
-     * The return value indicates whether the alignment procedure
-     * started successfully. If it is true, then an AlignmentModel has
+     * Any errors are reported by firing the alignmentFailed
+     * signal. Note that the signal may be fired during the call to
+     * this function, if the aligner fails to start at all.
+     *
+     * If alignment starts successfully, then an AlignmentModel has
      * been constructed and attached to the toAlign model, and you can
      * query that model to discover the alignment progress, eventual
-     * outcome, and any error message generated during alignment. (The
-     * AlignmentModel is subsequently owned by the toAlign model.)
-     * Conversely if alignModel returns false, no AlignmentModel has
-     * been created, and the error return argument will contain an
-     * error report about whatever problem prevented this from
-     * happening.
+     * outcome, and also (separately from the alignmentFailed signal
+     * here) any error message generated during alignment.
      *
      * A single Align object may carry out many simultanous alignment
      * calls -- you do not need to create a new Align object each
@@ -60,12 +59,26 @@
      * Align object will simply share the process or document
      * lifespan.
      */
-    bool alignModel(Document *doc,
+    void alignModel(Document *doc,
                     ModelId reference,
-                    ModelId toAlign,
-                    QString &error);
+                    ModelId toAlign);
 
     /**
+     * As alignModel, except that the alignment does not begin
+     * immediately, but is instead placed behind an event callback
+     * with a small delay. Useful to avoid an unresponsive GUI when
+     * firing off alignments while doing something else as well. Any
+     * error is reported by firing the alignmentFailed signal.
+     *
+     * Scheduled alignments are not queued or serialised - many could
+     * happen at once. They are just delayed a little for UI
+     * responsiveness.
+     */
+    void scheduleAlignment(Document *doc,
+                           ModelId reference,
+                           ModelId toAlign);
+    
+    /**
      * Return true if the alignment facility is available (relevant
      * plugin installed, etc).
      */
@@ -79,8 +92,15 @@
      */
     void alignmentComplete(ModelId alignmentModel); // an AlignmentModel
 
+    /**
+     * Emitted when an alignment fails. The model is the toAlign model
+     * that was passed to the call to alignModel or scheduleAlignment.
+     */
+    void alignmentFailed(ModelId toAlign, QString errorText);
+
 private slots:
     void alignerComplete(ModelId alignmentModel); // an AlignmentModel
+    void alignerFailed(ModelId toAlign, QString errorText);
     
 private:
     QMutex m_mutex;
@@ -91,6 +111,9 @@
     // we don't key this on the whole (reference, toAlign) pair
     std::map<ModelId, std::shared_ptr<Aligner>> m_aligners;
 
+    void addAligner(Document *doc, ModelId reference, ModelId toAlign);
+    void removeAligner(QObject *);
+
     static void getAlignerPreference(bool &useProgram, QString &program);
 };
 
--- a/align/Aligner.h	Mon Apr 27 14:59:56 2020 +0100
+++ b/align/Aligner.h	Wed May 06 11:45:27 2020 +0100
@@ -26,7 +26,8 @@
 public:
     virtual ~Aligner() { }
 
-    virtual bool begin(QString &error) = 0;
+public slots:
+    virtual void begin() = 0;
 
 signals:
     /**
@@ -34,6 +35,11 @@
      * and toAlign models can be queried from the alignment model.
      */
     void complete(ModelId alignmentModel); // an AlignmentModel
+
+    /**
+     * Emitted when alignment fails.
+     */
+    void failed(ModelId toAlign, QString errorText); // the toAlign model
 };
 
 #endif
--- a/align/ExternalProgramAligner.cpp	Mon Apr 27 14:59:56 2020 +0100
+++ b/align/ExternalProgramAligner.cpp	Wed May 06 11:45:27 2020 +0100
@@ -49,8 +49,8 @@
     return file.exists() && file.isExecutable();
 }
 
-bool
-ExternalProgramAligner::begin(QString &error)
+void
+ExternalProgramAligner::begin()
 {
     // Run an external program, passing to it paths to the main
     // model's audio file and the new model's audio file. It returns
@@ -60,7 +60,7 @@
     auto other = ModelById::getAs<ReadOnlyWaveFileModel>(m_toAlign);
     if (!reference || !other) {
         SVCERR << "ERROR: ExternalProgramAligner: Can't align non-read-only models via program (no local filename available)" << endl;
-        return false;
+        return;
     }
 
     while (!reference->isReady(nullptr) || !other->isReady(nullptr)) {
@@ -78,8 +78,9 @@
     }
 
     if (refPath == "" || otherPath == "") {
-        error = "Failed to find local filepath for wave-file model";
-        return false;
+        emit failed(m_toAlign,
+                    tr("Failed to find local filepath for wave-file model"));
+        return;
     }
 
     auto alignmentModel =
@@ -113,7 +114,9 @@
     if (!success) {
         
         SVCERR << "ERROR: ExternalProgramAligner: Program did not start" << endl;
-        error = "Alignment program \"" + m_program + "\" did not start";
+        emit failed(m_toAlign,
+                    tr("Alignment program \"%1\" did not start")
+                    .arg(m_program));
         
         other->setAlignment({});
         ModelById::release(m_alignmentModel);
@@ -123,8 +126,6 @@
     } else {
         m_document->addNonDerivedModel(m_alignmentModel);
     }
-
-    return success;
 }
 
 void
@@ -169,9 +170,10 @@
         if (!reader.isOK()) {
             SVCERR << "ERROR: ExternalProgramAligner: Failed to parse output"
                    << endl;
-            alignmentModel->setError
-                (QString("Failed to parse output of program: %1")
-                 .arg(reader.getError()));
+            QString error = tr("Failed to parse output of program: %1")
+                .arg(reader.getError());
+            alignmentModel->setError(error);
+            emit failed(m_toAlign, error);
             goto done;
         }
 
@@ -184,18 +186,22 @@
         if (!path) {
             SVCERR << "ERROR: ExternalProgramAligner: Output did not convert to sparse time-value model"
                    << endl;
-            alignmentModel->setError
-                ("Output of program did not produce sparse time-value model");
+            QString error =
+                tr("Output of alignment program was not in the proper format");
+            alignmentModel->setError(error);
             delete csvOutput;
+            emit failed(m_toAlign, error);
             goto done;
         }
                        
         if (path->isEmpty()) {
             SVCERR << "ERROR: ExternalProgramAligner: Output contained no mappings"
                    << endl;
-            alignmentModel->setError
-                ("Output of alignment program contained no mappings");
+            QString error = 
+                tr("Output of alignment program contained no mappings");
+            alignmentModel->setError(error);
             delete path;
+            emit failed(m_toAlign, error);
             goto done;
         }
 
@@ -214,8 +220,9 @@
         SVCERR << "ERROR: ExternalProgramAligner: Aligner program "
                << "failed: exit code " << exitCode << ", status " << status
                << endl;
-        alignmentModel->setError
-            ("Aligner process returned non-zero exit status");
+        QString error = tr("Aligner process returned non-zero exit status");
+        alignmentModel->setError(error);
+        emit failed(m_toAlign, error);
     }
 
 done:
--- a/align/ExternalProgramAligner.h	Mon Apr 27 14:59:56 2020 +0100
+++ b/align/ExternalProgramAligner.h	Wed May 06 11:45:27 2020 +0100
@@ -36,7 +36,7 @@
     // Destroy the aligner, cleanly cancelling any ongoing alignment
     ~ExternalProgramAligner();
 
-    bool begin(QString &error) override;
+    void begin() override;
 
     static bool isAvailable(QString program);
 
--- a/align/TransformAligner.cpp	Mon Apr 27 14:59:56 2020 +0100
+++ b/align/TransformAligner.cpp	Wed May 06 11:45:27 2020 +0100
@@ -93,15 +93,15 @@
         (tdId == "" || factory->haveTransform(tdId));
 }
 
-bool
-TransformAligner::begin(QString &error)
+void
+TransformAligner::begin()
 {
     auto reference =
         ModelById::getAs<RangeSummarisableTimeValueModel>(m_reference);
     auto other =
         ModelById::getAs<RangeSummarisableTimeValueModel>(m_toAlign);
 
-    if (!reference || !other) return false;
+    if (!reference || !other) return;
 
     // This involves creating a number of new models:
     //
@@ -165,9 +165,10 @@
             other->setAlignment(m_alignmentModel);
             m_document->addNonDerivedModel(m_alignmentModel);
         } else {
-            error = alignmentModel->getError();
+            QString error = alignmentModel->getError();
             ModelById::release(alignmentModel);
-            return false;
+            emit failed(m_toAlign, error);
+            return;
         }
 
     } else {
@@ -197,9 +198,9 @@
             ModelById::getAs<SparseTimeValueModel>(m_tuningDiffOutputModel);
         if (!tuningDiffOutputModel) {
             SVCERR << "Align::alignModel: ERROR: Failed to create tuning-difference output model (no Tuning Difference plugin?)" << endl;
-            error = message;
             ModelById::release(alignmentModel);
-            return false;
+            emit failed(m_toAlign, message);
+            return;
         }
 
         other->setAlignment(m_alignmentModel);
@@ -218,13 +219,16 @@
         progressModel->setCompletion(0);
         alignmentModel->setPathFrom(m_tuningDiffProgressModel);
     }
-
-    return true;
 }
 
 void
 TransformAligner::tuningDifferenceCompletionChanged(ModelId tuningDiffOutputModelId)
 {
+    if (m_tuningDiffOutputModel.isNone()) {
+        // we're done, this is probably a spurious queued event
+        return;
+    }
+        
     if (tuningDiffOutputModelId != m_tuningDiffOutputModel) {
         SVCERR << "WARNING: TransformAligner::tuningDifferenceCompletionChanged: Model "
                << tuningDiffOutputModelId
@@ -252,6 +256,10 @@
     int completion = 0;
     bool done = tuningDiffOutputModel->isReady(&completion);
 
+    SVDEBUG << "TransformAligner::tuningDifferenceCompletionChanged: model "
+            << m_tuningDiffOutputModel << ", completion = " << completion
+            << ", done = " << done << endl;
+    
     if (!done) {
         // This will be the completion the alignment model reports,
         // before the alignment actually begins. It goes up from 0 to
--- a/align/TransformAligner.h	Mon Apr 27 14:59:56 2020 +0100
+++ b/align/TransformAligner.h	Wed May 06 11:45:27 2020 +0100
@@ -32,7 +32,7 @@
     // Destroy the aligner, cleanly cancelling any ongoing alignment
     ~TransformAligner();
 
-    bool begin(QString &error) override;
+    void begin() override;
 
     static bool isAvailable();
 
--- a/framework/Document.cpp	Mon Apr 27 14:59:56 2020 +0100
+++ b/framework/Document.cpp	Wed May 06 11:45:27 2020 +0100
@@ -57,6 +57,9 @@
 
     connect(m_align, SIGNAL(alignmentComplete(ModelId)),
             this, SIGNAL(alignmentComplete(ModelId)));
+
+    connect(m_align, SIGNAL(alignmentFailed(ModelId, QString)),
+            this, SIGNAL(alignmentFailed(ModelId, QString)));
 }
 
 Document::~Document()
@@ -555,7 +558,7 @@
     }
 
     if (m_autoAlignment) {
-        SVDEBUG << "Document::setMainModel: auto-alignment is on, aligning model if possible" << endl;
+        SVDEBUG << "Document::setMainModel: auto-alignment is on, aligning main model if applicable" << endl;
         alignModel(m_mainModel);
     } else {
         SVDEBUG << "Document::setMainModel: auto-alignment is off" << endl;
@@ -1145,11 +1148,7 @@
                 << endl;
     }
 
-    QString err;
-    if (!m_align->alignModel(this, m_mainModel, modelId, err)) {
-        SVCERR << "Alignment failed: " << err << endl;
-        emit alignmentFailed(err);
-    }
+    m_align->scheduleAlignment(this, m_mainModel, modelId);
 }
 
 void
--- a/framework/Document.h	Mon Apr 27 14:59:56 2020 +0100
+++ b/framework/Document.h	Wed May 06 11:45:27 2020 +0100
@@ -331,7 +331,7 @@
                                   QString message);
 
     void alignmentComplete(ModelId); // an AlignmentModel
-    void alignmentFailed(QString message);
+    void alignmentFailed(ModelId, QString message); // an AlignmentModel
 
     void activity(QString);
 
--- a/framework/MainWindowBase.cpp	Mon Apr 27 14:59:56 2020 +0100
+++ b/framework/MainWindowBase.cpp	Wed May 06 11:45:27 2020 +0100
@@ -2714,8 +2714,8 @@
             this, SLOT(modelRegenerationWarning(QString, QString, QString)));
     connect(m_document, SIGNAL(alignmentComplete(ModelId)),
             this, SLOT(alignmentComplete(ModelId)));
-    connect(m_document, SIGNAL(alignmentFailed(QString)),
-            this, SLOT(alignmentFailed(QString)));
+    connect(m_document, SIGNAL(alignmentFailed(ModelId, QString)),
+            this, SLOT(alignmentFailed(ModelId, QString)));
 
     m_document->setAutoAlignment(m_viewManager->getAlignMode());
 
--- a/framework/MainWindowBase.h	Mon Apr 27 14:59:56 2020 +0100
+++ b/framework/MainWindowBase.h	Wed May 06 11:45:27 2020 +0100
@@ -361,7 +361,7 @@
     virtual void modelRegenerationWarning(QString, QString, QString) = 0;
 
     virtual void alignmentComplete(ModelId);
-    virtual void alignmentFailed(QString) = 0;
+    virtual void alignmentFailed(ModelId, QString) = 0;
 
     virtual void paneRightButtonMenuRequested(Pane *, QPoint point) = 0;
     virtual void panePropertiesRightButtonMenuRequested(Pane *, QPoint point) = 0;