annotate align/ExternalProgramAligner.cpp @ 752:32654e402f8b pitch-align

Pull out ExternalProgramAligner and TransformAligner from Align - currently duplicating the code, the pulled-out classes are not yet in use
author Chris Cannam
date Thu, 23 Apr 2020 17:11:26 +0100
parents
children 6429a164b7e1
rev   line source
Chris@752 1 /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
Chris@752 2
Chris@752 3 /*
Chris@752 4 Sonic Visualiser
Chris@752 5 An audio file viewer and annotation editor.
Chris@752 6 Centre for Digital Music, Queen Mary, University of London.
Chris@752 7
Chris@752 8 This program is free software; you can redistribute it and/or
Chris@752 9 modify it under the terms of the GNU General Public License as
Chris@752 10 published by the Free Software Foundation; either version 2 of the
Chris@752 11 License, or (at your option) any later version. See the file
Chris@752 12 COPYING included with this distribution for more information.
Chris@752 13 */
Chris@752 14
Chris@752 15 #include "ExternalProgramAligner.h"
Chris@752 16
Chris@752 17 #include <QFileInfo>
Chris@752 18 #include <QApplication>
Chris@752 19
Chris@752 20 #include "data/model/ReadOnlyWaveFileModel.h"
Chris@752 21 #include "data/model/SparseTimeValueModel.h"
Chris@752 22 #include "data/model/AlignmentModel.h"
Chris@752 23
Chris@752 24 #include "data/fileio/CSVFileReader.h"
Chris@752 25
Chris@752 26 #include "framework/Document.h"
Chris@752 27
Chris@752 28 ExternalProgramAligner::ExternalProgramAligner(Document *doc,
Chris@752 29 ModelId reference,
Chris@752 30 ModelId toAlign,
Chris@752 31 QString program) :
Chris@752 32 m_document(doc),
Chris@752 33 m_reference(reference),
Chris@752 34 m_toAlign(toAlign),
Chris@752 35 m_program(program),
Chris@752 36 m_process(nullptr)
Chris@752 37 {
Chris@752 38 }
Chris@752 39
Chris@752 40 ExternalProgramAligner::~ExternalProgramAligner()
Chris@752 41 {
Chris@752 42 delete m_process;
Chris@752 43 }
Chris@752 44
Chris@752 45 bool
Chris@752 46 ExternalProgramAligner::isAvailable(QString program)
Chris@752 47 {
Chris@752 48 QFileInfo file(program);
Chris@752 49 return file.exists() && file.isExecutable();
Chris@752 50 }
Chris@752 51
Chris@752 52 bool
Chris@752 53 ExternalProgramAligner::begin(QString &error)
Chris@752 54 {
Chris@752 55 // Run an external program, passing to it paths to the main
Chris@752 56 // model's audio file and the new model's audio file. It returns
Chris@752 57 // the path in CSV form through stdout.
Chris@752 58
Chris@752 59 auto reference = ModelById::getAs<ReadOnlyWaveFileModel>(m_reference);
Chris@752 60 auto other = ModelById::getAs<ReadOnlyWaveFileModel>(m_toAlign);
Chris@752 61 if (!reference || !other) {
Chris@752 62 SVCERR << "ERROR: ExternalProgramAligner: Can't align non-read-only models via program (no local filename available)" << endl;
Chris@752 63 return false;
Chris@752 64 }
Chris@752 65
Chris@752 66 while (!reference->isReady(nullptr) || !other->isReady(nullptr)) {
Chris@752 67 qApp->processEvents();
Chris@752 68 }
Chris@752 69
Chris@752 70 QString refPath = reference->getLocalFilename();
Chris@752 71 if (refPath == "") {
Chris@752 72 refPath = FileSource(reference->getLocation()).getLocalFilename();
Chris@752 73 }
Chris@752 74
Chris@752 75 QString otherPath = other->getLocalFilename();
Chris@752 76 if (otherPath == "") {
Chris@752 77 otherPath = FileSource(other->getLocation()).getLocalFilename();
Chris@752 78 }
Chris@752 79
Chris@752 80 if (refPath == "" || otherPath == "") {
Chris@752 81 error = "Failed to find local filepath for wave-file model";
Chris@752 82 return false;
Chris@752 83 }
Chris@752 84
Chris@752 85 auto alignmentModel =
Chris@752 86 std::make_shared<AlignmentModel>(m_reference, m_toAlign, ModelId());
Chris@752 87
Chris@752 88 m_alignmentModel = ModelById::add(alignmentModel);
Chris@752 89 other->setAlignment(m_alignmentModel);
Chris@752 90
Chris@752 91 m_process = new QProcess;
Chris@752 92 m_process->setProcessChannelMode(QProcess::ForwardedErrorChannel);
Chris@752 93
Chris@752 94 connect(m_process,
Chris@752 95 SIGNAL(finished(int, QProcess::ExitStatus)),
Chris@752 96 this,
Chris@752 97 SLOT(programFinished(int, QProcess::ExitStatus)));
Chris@752 98
Chris@752 99 QStringList args;
Chris@752 100 args << refPath << otherPath;
Chris@752 101
Chris@752 102 SVCERR << "ExternalProgramAligner: Starting program \""
Chris@752 103 << m_program << "\" with args: ";
Chris@752 104 for (auto a: args) {
Chris@752 105 SVCERR << "\"" << a << "\" ";
Chris@752 106 }
Chris@752 107 SVCERR << endl;
Chris@752 108
Chris@752 109 m_process->start(m_program, args);
Chris@752 110
Chris@752 111 bool success = m_process->waitForStarted();
Chris@752 112
Chris@752 113 if (!success) {
Chris@752 114
Chris@752 115 SVCERR << "ERROR: ExternalProgramAligner: Program did not start" << endl;
Chris@752 116 error = "Alignment program \"" + m_program + "\" did not start";
Chris@752 117
Chris@752 118 other->setAlignment({});
Chris@752 119 ModelById::release(m_alignmentModel);
Chris@752 120 delete m_process;
Chris@752 121 m_process = nullptr;
Chris@752 122
Chris@752 123 } else {
Chris@752 124 m_document->addNonDerivedModel(m_alignmentModel);
Chris@752 125 }
Chris@752 126
Chris@752 127 return success;
Chris@752 128 }
Chris@752 129
Chris@752 130 void
Chris@752 131 ExternalProgramAligner::programFinished(int exitCode, QProcess::ExitStatus status)
Chris@752 132 {
Chris@752 133 SVCERR << "ExternalProgramAligner::programFinished" << endl;
Chris@752 134
Chris@752 135 QProcess *process = qobject_cast<QProcess *>(sender());
Chris@752 136
Chris@752 137 if (process != m_process) {
Chris@752 138 SVCERR << "ERROR: ExternalProgramAligner: Emitting process " << process
Chris@752 139 << " is not my process!" << endl;
Chris@752 140 return;
Chris@752 141 }
Chris@752 142
Chris@752 143 auto alignmentModel = ModelById::getAs<AlignmentModel>(m_alignmentModel);
Chris@752 144 if (!alignmentModel) {
Chris@752 145 SVCERR << "ExternalProgramAligner: AlignmentModel no longer exists"
Chris@752 146 << endl;
Chris@752 147 return;
Chris@752 148 }
Chris@752 149
Chris@752 150 if (exitCode == 0 && status == 0) {
Chris@752 151
Chris@752 152 CSVFormat format;
Chris@752 153 format.setModelType(CSVFormat::TwoDimensionalModel);
Chris@752 154 format.setTimingType(CSVFormat::ExplicitTiming);
Chris@752 155 format.setTimeUnits(CSVFormat::TimeSeconds);
Chris@752 156 format.setColumnCount(2);
Chris@752 157 // The output format has time in the reference file first, and
Chris@752 158 // time in the "other" file in the second column. This is a
Chris@752 159 // more natural approach for a command-line alignment tool,
Chris@752 160 // but it's the opposite of what we expect for native
Chris@752 161 // alignment paths, which map from "other" file to
Chris@752 162 // reference. These column purpose settings reflect that.
Chris@752 163 format.setColumnPurpose(1, CSVFormat::ColumnStartTime);
Chris@752 164 format.setColumnPurpose(0, CSVFormat::ColumnValue);
Chris@752 165 format.setAllowQuoting(false);
Chris@752 166 format.setSeparator(',');
Chris@752 167
Chris@752 168 CSVFileReader reader(process, format, alignmentModel->getSampleRate());
Chris@752 169 if (!reader.isOK()) {
Chris@752 170 SVCERR << "ERROR: ExternalProgramAligner: Failed to parse output"
Chris@752 171 << endl;
Chris@752 172 alignmentModel->setError
Chris@752 173 (QString("Failed to parse output of program: %1")
Chris@752 174 .arg(reader.getError()));
Chris@752 175 goto done;
Chris@752 176 }
Chris@752 177
Chris@752 178 //!!! to use ById?
Chris@752 179
Chris@752 180 Model *csvOutput = reader.load();
Chris@752 181
Chris@752 182 SparseTimeValueModel *path =
Chris@752 183 qobject_cast<SparseTimeValueModel *>(csvOutput);
Chris@752 184 if (!path) {
Chris@752 185 SVCERR << "ERROR: ExternalProgramAligner: Output did not convert to sparse time-value model"
Chris@752 186 << endl;
Chris@752 187 alignmentModel->setError
Chris@752 188 ("Output of program did not produce sparse time-value model");
Chris@752 189 delete csvOutput;
Chris@752 190 goto done;
Chris@752 191 }
Chris@752 192
Chris@752 193 if (path->isEmpty()) {
Chris@752 194 SVCERR << "ERROR: ExternalProgramAligner: Output contained no mappings"
Chris@752 195 << endl;
Chris@752 196 alignmentModel->setError
Chris@752 197 ("Output of alignment program contained no mappings");
Chris@752 198 delete path;
Chris@752 199 goto done;
Chris@752 200 }
Chris@752 201
Chris@752 202 SVCERR << "ExternalProgramAligner: Setting alignment path ("
Chris@752 203 << path->getEventCount() << " point(s))" << endl;
Chris@752 204
Chris@752 205 auto pathId =
Chris@752 206 ModelById::add(std::shared_ptr<SparseTimeValueModel>(path));
Chris@752 207 alignmentModel->setPathFrom(pathId);
Chris@752 208
Chris@752 209 emit complete(m_alignmentModel);
Chris@752 210
Chris@752 211 ModelById::release(pathId);
Chris@752 212
Chris@752 213 } else {
Chris@752 214 SVCERR << "ERROR: ExternalProgramAligner: Aligner program "
Chris@752 215 << "failed: exit code " << exitCode << ", status " << status
Chris@752 216 << endl;
Chris@752 217 alignmentModel->setError
Chris@752 218 ("Aligner process returned non-zero exit status");
Chris@752 219 }
Chris@752 220
Chris@752 221 done:
Chris@752 222 delete m_process;
Chris@752 223 m_process = nullptr;
Chris@752 224 }