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