Mercurial > hg > svapp
comparison align/ExternalProgramAligner.cpp @ 763:da57ab54f0e8
Merge from branch pitch-align. Doesn't actually do pitch alignment here, but this is the groundwork.
author | Chris Cannam |
---|---|
date | Wed, 13 May 2020 14:10:47 +0100 |
parents | 6429a164b7e1 |
children | a316cb6fed81 |
comparison
equal
deleted
inserted
replaced
760:3a63f1f61bd6 | 763:da57ab54f0e8 |
---|---|
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 void | |
53 ExternalProgramAligner::begin() | |
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; | |
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 emit failed(m_toAlign, | |
82 tr("Failed to find local filepath for wave-file model")); | |
83 return; | |
84 } | |
85 | |
86 auto alignmentModel = | |
87 std::make_shared<AlignmentModel>(m_reference, m_toAlign, ModelId()); | |
88 | |
89 m_alignmentModel = ModelById::add(alignmentModel); | |
90 other->setAlignment(m_alignmentModel); | |
91 | |
92 m_process = new QProcess; | |
93 m_process->setProcessChannelMode(QProcess::ForwardedErrorChannel); | |
94 | |
95 connect(m_process, | |
96 SIGNAL(finished(int, QProcess::ExitStatus)), | |
97 this, | |
98 SLOT(programFinished(int, QProcess::ExitStatus))); | |
99 | |
100 QStringList args; | |
101 args << refPath << otherPath; | |
102 | |
103 SVCERR << "ExternalProgramAligner: Starting program \"" | |
104 << m_program << "\" with args: "; | |
105 for (auto a: args) { | |
106 SVCERR << "\"" << a << "\" "; | |
107 } | |
108 SVCERR << endl; | |
109 | |
110 m_process->start(m_program, args); | |
111 | |
112 bool success = m_process->waitForStarted(); | |
113 | |
114 if (!success) { | |
115 | |
116 SVCERR << "ERROR: ExternalProgramAligner: Program did not start" << endl; | |
117 emit failed(m_toAlign, | |
118 tr("Alignment program \"%1\" did not start") | |
119 .arg(m_program)); | |
120 | |
121 other->setAlignment({}); | |
122 ModelById::release(m_alignmentModel); | |
123 delete m_process; | |
124 m_process = nullptr; | |
125 | |
126 } else { | |
127 m_document->addNonDerivedModel(m_alignmentModel); | |
128 } | |
129 } | |
130 | |
131 void | |
132 ExternalProgramAligner::programFinished(int exitCode, QProcess::ExitStatus status) | |
133 { | |
134 SVCERR << "ExternalProgramAligner::programFinished" << endl; | |
135 | |
136 QProcess *process = qobject_cast<QProcess *>(sender()); | |
137 | |
138 if (process != m_process) { | |
139 SVCERR << "ERROR: ExternalProgramAligner: Emitting process " << process | |
140 << " is not my process!" << endl; | |
141 return; | |
142 } | |
143 | |
144 auto alignmentModel = ModelById::getAs<AlignmentModel>(m_alignmentModel); | |
145 if (!alignmentModel) { | |
146 SVCERR << "ExternalProgramAligner: AlignmentModel no longer exists" | |
147 << endl; | |
148 return; | |
149 } | |
150 | |
151 if (exitCode == 0 && status == 0) { | |
152 | |
153 CSVFormat format; | |
154 format.setModelType(CSVFormat::TwoDimensionalModel); | |
155 format.setTimingType(CSVFormat::ExplicitTiming); | |
156 format.setTimeUnits(CSVFormat::TimeSeconds); | |
157 format.setColumnCount(2); | |
158 // The output format has time in the reference file first, and | |
159 // time in the "other" file in the second column. This is a | |
160 // more natural approach for a command-line alignment tool, | |
161 // but it's the opposite of what we expect for native | |
162 // alignment paths, which map from "other" file to | |
163 // reference. These column purpose settings reflect that. | |
164 format.setColumnPurpose(1, CSVFormat::ColumnStartTime); | |
165 format.setColumnPurpose(0, CSVFormat::ColumnValue); | |
166 format.setAllowQuoting(false); | |
167 format.setSeparator(','); | |
168 | |
169 CSVFileReader reader(process, format, alignmentModel->getSampleRate()); | |
170 if (!reader.isOK()) { | |
171 SVCERR << "ERROR: ExternalProgramAligner: Failed to parse output" | |
172 << endl; | |
173 QString error = tr("Failed to parse output of program: %1") | |
174 .arg(reader.getError()); | |
175 alignmentModel->setError(error); | |
176 emit failed(m_toAlign, error); | |
177 goto done; | |
178 } | |
179 | |
180 //!!! to use ById? | |
181 | |
182 Model *csvOutput = reader.load(); | |
183 | |
184 SparseTimeValueModel *path = | |
185 qobject_cast<SparseTimeValueModel *>(csvOutput); | |
186 if (!path) { | |
187 SVCERR << "ERROR: ExternalProgramAligner: Output did not convert to sparse time-value model" | |
188 << endl; | |
189 QString error = | |
190 tr("Output of alignment program was not in the proper format"); | |
191 alignmentModel->setError(error); | |
192 delete csvOutput; | |
193 emit failed(m_toAlign, error); | |
194 goto done; | |
195 } | |
196 | |
197 if (path->isEmpty()) { | |
198 SVCERR << "ERROR: ExternalProgramAligner: Output contained no mappings" | |
199 << endl; | |
200 QString error = | |
201 tr("Output of alignment program contained no mappings"); | |
202 alignmentModel->setError(error); | |
203 delete path; | |
204 emit failed(m_toAlign, error); | |
205 goto done; | |
206 } | |
207 | |
208 SVCERR << "ExternalProgramAligner: Setting alignment path (" | |
209 << path->getEventCount() << " point(s))" << endl; | |
210 | |
211 auto pathId = | |
212 ModelById::add(std::shared_ptr<SparseTimeValueModel>(path)); | |
213 alignmentModel->setPathFrom(pathId); | |
214 | |
215 emit complete(m_alignmentModel); | |
216 | |
217 ModelById::release(pathId); | |
218 | |
219 } else { | |
220 SVCERR << "ERROR: ExternalProgramAligner: Aligner program " | |
221 << "failed: exit code " << exitCode << ", status " << status | |
222 << endl; | |
223 QString error = tr("Aligner process returned non-zero exit status"); | |
224 alignmentModel->setError(error); | |
225 emit failed(m_toAlign, error); | |
226 } | |
227 | |
228 done: | |
229 delete m_process; | |
230 m_process = nullptr; | |
231 } |