changeset 423:f32a64149602 alignment_view

Make alignment using an external program asynchronous
author Chris Cannam
date Thu, 20 Nov 2014 15:46:19 +0000
parents 33fae747db7e
children d044682967ca
files framework/Align.cpp framework/Align.h framework/Document.cpp framework/Document.h framework/MainWindowBase.cpp
diffstat 5 files changed, 101 insertions(+), 28 deletions(-) [+]
line wrap: on
line diff
--- a/framework/Align.cpp	Thu Nov 20 14:08:01 2014 +0000
+++ b/framework/Align.cpp	Thu Nov 20 15:46:19 2014 +0000
@@ -151,14 +151,51 @@
 	return false;
     }
 
-    QProcess process;
+    m_error = "";
+    
+    AlignmentModel *alignmentModel = new AlignmentModel(reference, other, 0, 0);
+    rm->setAlignment(alignmentModel);
+
+    QProcess *process = new QProcess;
     QStringList args;
     args << refPath << otherPath;
-    process.start(program, args);
+    
+    connect(process, SIGNAL(finished(int, QProcess::ExitStatus)),
+            this, SLOT(alignmentProgramFinished(int, QProcess::ExitStatus)));
 
-    process.waitForFinished(60000); //!!! nb timeout, but we can do better than blocking anyway
+    m_processModels[process] = alignmentModel;
+    process->start(program, args);
 
-    if (process.exitStatus() == 0) {
+    bool success = process->waitForStarted();
+
+    if (!success) {
+        cerr << "ERROR: Align::alignModelViaProgram: Program did not start"
+             << endl;
+        m_error = "Alignment program could not be started";
+        m_processModels.erase(process);
+        delete alignmentModel;
+        delete process;
+    }
+
+    return success;
+}
+
+void
+Align::alignmentProgramFinished(int exitCode, QProcess::ExitStatus status)
+{
+    cerr << "Align::alignmentProgramFinished" << endl;
+    
+    QProcess *process = qobject_cast<QProcess *>(sender());
+
+    if (m_processModels.find(process) == m_processModels.end()) {
+        cerr << "ERROR: Align::alignmentProgramFinished: Process " << process
+             << " not found in process model map!" << endl;
+        return;
+    }
+
+    AlignmentModel *alignmentModel = m_processModels[process];
+    
+    if (exitCode == 0 && status == 0) {
 
 	CSVFormat format;
 	format.setModelType(CSVFormat::TwoDimensionalModel);
@@ -170,38 +207,46 @@
 	format.setAllowQuoting(false);
 	format.setSeparator(',');
 
-	CSVFileReader reader(&process, format, reference->getSampleRate());
+	CSVFileReader reader(process, format, alignmentModel->getSampleRate());
 	if (!reader.isOK()) {
+            cerr << "ERROR: Align::alignmentProgramFinished: Failed to parse output"
+                 << endl;
 	    m_error = QString("Failed to parse output of program: %1")
 		.arg(reader.getError());
-	    return false;
+            goto done;
 	}
 
 	Model *csvOutput = reader.load();
 
 	SparseTimeValueModel *path = qobject_cast<SparseTimeValueModel *>(csvOutput);
 	if (!path) {
+            cerr << "ERROR: Align::alignmentProgramFinished: Output did not convert to sparse time-value model"
+                 << endl;
 	    m_error = QString("Output of program did not produce sparse time-value model");
-	    return false;
+            goto done;
 	}
 
 	if (path->getPoints().empty()) {
+            cerr << "ERROR: Align::alignmentProgramFinished: Output contained no mappings"
+                 << endl;
 	    m_error = QString("Output of alignment program contained no mappings");
-	    return false;
+            goto done;
 	}
-	
-	AlignmentModel *alignmentModel = new AlignmentModel
-	    (reference, other, 0, path);
 
-	rm->setAlignment(alignmentModel);
+        cerr << "Align::alignmentProgramFinished: Setting alignment path ("
+             << path->getPoints().size() << " point(s))" << endl;
+        
+        alignmentModel->setPathFrom(path);
 
     } else {
+        cerr << "ERROR: Align::alignmentProgramFinished: Aligner program "
+             << "failed: exit code " << exitCode << ", status " << status
+             << endl;
 	m_error = "Aligner process returned non-zero exit status";
-	return false;
     }
 
-    cerr << "Align: success" << endl;
-    
-    return true;
+done:
+    m_processModels.erase(process);
+    delete process;
 }
 
--- a/framework/Align.h	Thu Nov 20 14:08:01 2014 +0000
+++ b/framework/Align.h	Thu Nov 20 15:46:19 2014 +0000
@@ -17,23 +17,50 @@
 #define ALIGN_H
 
 #include <QString>
+#include <QObject>
+#include <QProcess>
+#include <set>
 
 class Model;
+class AlignmentModel;
 
-class Align
+class Align : public QObject
 {
+    Q_OBJECT
+    
 public:
     Align() : m_error("") { }
 
+    /**
+     * Align the "other" model to the reference, attaching an
+     * AlignmentModel to it. Alignment is carried out by the method
+     * configured in the user preferences (either a plugin transform
+     * or an external process) and is done asynchronously. 
+     *
+     * A single Align object may carry out many simultanous alignment
+     * calls -- you do not need to create a new Align object each
+     * time, nor to wait for an alignment to be complete before
+     * starting a new one.
+     * 
+     * The Align object must survive after this call, for at least as
+     * long as the alignment takes. There is currently no way in this
+     * API to discover when an alignment is complete -- the
+     * expectation is that the Align object will simply share the
+     * process or document lifespan.
+     */
     bool alignModel(Model *reference, Model *other); // via user preference
     
     bool alignModelViaTransform(Model *reference, Model *other);
     bool alignModelViaProgram(Model *reference, Model *other, QString program);
 
     QString getError() const { return m_error; }
-
+        
+private slots:
+    void alignmentProgramFinished(int, QProcess::ExitStatus);
+    
 private:
     QString m_error;
+    std::map<QProcess *, AlignmentModel *> m_processModels;
 };
 
 #endif
--- a/framework/Document.cpp	Thu Nov 20 14:08:01 2014 +0000
+++ b/framework/Document.cpp	Thu Nov 20 15:46:19 2014 +0000
@@ -38,10 +38,8 @@
 #include <iostream>
 #include <typeinfo>
 
-// For alignment:
-#include "data/model/AggregateWaveModel.h"
-#include "data/model/SparseTimeValueModel.h"
 #include "data/model/AlignmentModel.h"
+#include "Align.h"
 
 using std::vector;
 
@@ -51,7 +49,8 @@
 
 Document::Document() :
     m_mainModel(0),
-    m_autoAlignment(false)
+    m_autoAlignment(false),
+    m_align(new Align())
 {
     connect(this, SIGNAL(modelAboutToBeDeleted(Model *)),
             ModelTransformerFactory::getInstance(),
@@ -1086,10 +1085,9 @@
         return;
     }
 
-    Align a;
-    if (!a.alignModel(m_mainModel, rm)) {
-        cerr << "Alignment failed: " << a.getError() << endl;
-        emit alignmentFailed(a.getError());
+    if (!m_align->alignModel(m_mainModel, rm)) {
+        cerr << "Alignment failed: " << m_align->getError() << endl;
+        emit alignmentFailed(m_align->getError());
     }
 }
 
--- a/framework/Document.h	Thu Nov 20 14:08:01 2014 +0000
+++ b/framework/Document.h	Thu Nov 20 15:46:19 2014 +0000
@@ -32,6 +32,8 @@
 
 class AdditionalModelConverter;
 
+class Align;
+
 /**
  * A Sonic Visualiser document consists of a set of data models, and
  * also the visualisation layers used to display them.  Changes to the
@@ -423,6 +425,7 @@
     LayerSet m_layers;
 
     bool m_autoAlignment;
+    Align *m_align;
 };
 
 #endif
--- a/framework/MainWindowBase.cpp	Thu Nov 20 14:08:01 2014 +0000
+++ b/framework/MainWindowBase.cpp	Thu Nov 20 15:46:19 2014 +0000
@@ -2210,8 +2210,8 @@
             this, SLOT(modelGenerationFailed(QString, QString)));
     connect(m_document, SIGNAL(modelRegenerationWarning(QString, QString, QString)),
             this, SLOT(modelRegenerationWarning(QString, QString, QString)));
-    connect(m_document, SIGNAL(alignmentFailed(QString, QString)),
-            this, SLOT(alignmentFailed(QString, QString)));
+    connect(m_document, SIGNAL(alignmentFailed(QString)),
+            this, SLOT(alignmentFailed(QString)));
 
     emit replacedDocument();
 }