Chris@420
|
1 /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
|
Chris@420
|
2
|
Chris@420
|
3 /*
|
Chris@420
|
4 Sonic Visualiser
|
Chris@420
|
5 An audio file viewer and annotation editor.
|
Chris@420
|
6 Centre for Digital Music, Queen Mary, University of London.
|
Chris@420
|
7 This file copyright 2006 Chris Cannam and QMUL.
|
Chris@420
|
8
|
Chris@420
|
9 This program is free software; you can redistribute it and/or
|
Chris@420
|
10 modify it under the terms of the GNU General Public License as
|
Chris@420
|
11 published by the Free Software Foundation; either version 2 of the
|
Chris@420
|
12 License, or (at your option) any later version. See the file
|
Chris@420
|
13 COPYING included with this distribution for more information.
|
Chris@420
|
14 */
|
Chris@420
|
15
|
Chris@420
|
16 #include "Align.h"
|
Chris@420
|
17
|
Chris@420
|
18 #include "data/model/WaveFileModel.h"
|
Chris@420
|
19 #include "data/model/AggregateWaveModel.h"
|
Chris@420
|
20 #include "data/model/RangeSummarisableTimeValueModel.h"
|
Chris@420
|
21 #include "data/model/SparseTimeValueModel.h"
|
Chris@420
|
22 #include "data/model/AlignmentModel.h"
|
Chris@420
|
23
|
Chris@420
|
24 #include "data/fileio/CSVFileReader.h"
|
Chris@420
|
25
|
Chris@420
|
26 #include "transform/TransformFactory.h"
|
Chris@420
|
27 #include "transform/ModelTransformerFactory.h"
|
Chris@420
|
28 #include "transform/FeatureExtractionModelTransformer.h"
|
Chris@420
|
29
|
Chris@420
|
30 #include <QProcess>
|
Chris@422
|
31 #include <QSettings>
|
Chris@422
|
32
|
Chris@422
|
33 bool
|
Chris@422
|
34 Align::alignModel(Model *ref, Model *other)
|
Chris@422
|
35 {
|
Chris@422
|
36 QSettings settings;
|
Chris@422
|
37 settings.beginGroup("Preferences");
|
Chris@422
|
38 bool useProgram = settings.value("use-external-alignment", false).toBool();
|
Chris@422
|
39 QString program = settings.value("external-alignment-program", "").toString();
|
Chris@422
|
40 settings.endGroup();
|
Chris@422
|
41
|
Chris@422
|
42 if (useProgram && (program != "")) {
|
Chris@422
|
43 return alignModelViaProgram(ref, other, program);
|
Chris@422
|
44 } else {
|
Chris@422
|
45 return alignModelViaTransform(ref, other);
|
Chris@422
|
46 }
|
Chris@422
|
47 }
|
Chris@420
|
48
|
Chris@420
|
49 bool
|
Chris@420
|
50 Align::alignModelViaTransform(Model *ref, Model *other)
|
Chris@420
|
51 {
|
Chris@420
|
52 RangeSummarisableTimeValueModel *reference = qobject_cast
|
Chris@420
|
53 <RangeSummarisableTimeValueModel *>(ref);
|
Chris@420
|
54
|
Chris@420
|
55 RangeSummarisableTimeValueModel *rm = qobject_cast
|
Chris@420
|
56 <RangeSummarisableTimeValueModel *>(other);
|
Chris@420
|
57
|
Chris@420
|
58 if (!reference || !rm) return false; // but this should have been tested already
|
Chris@420
|
59
|
Chris@420
|
60 // This involves creating three new models:
|
Chris@420
|
61
|
Chris@420
|
62 // 1. an AggregateWaveModel to provide the mixdowns of the main
|
Chris@420
|
63 // model and the new model in its two channels, as input to the
|
Chris@420
|
64 // MATCH plugin
|
Chris@420
|
65
|
Chris@420
|
66 // 2. a SparseTimeValueModel, which is the model automatically
|
Chris@420
|
67 // created by FeatureExtractionPluginTransformer when running the
|
Chris@420
|
68 // MATCH plugin (thus containing the alignment path)
|
Chris@420
|
69
|
Chris@420
|
70 // 3. an AlignmentModel, which stores the path model and carries
|
Chris@420
|
71 // out alignment lookups on it.
|
Chris@420
|
72
|
Chris@420
|
73 // The first two of these are provided as arguments to the
|
Chris@420
|
74 // constructor for the third, which takes responsibility for
|
Chris@420
|
75 // deleting them. The AlignmentModel, meanwhile, is passed to the
|
Chris@420
|
76 // new model we are aligning, which also takes responsibility for
|
Chris@420
|
77 // it. We should not have to delete any of these new models here.
|
Chris@420
|
78
|
Chris@420
|
79 AggregateWaveModel::ChannelSpecList components;
|
Chris@420
|
80
|
Chris@420
|
81 components.push_back(AggregateWaveModel::ModelChannelSpec
|
Chris@420
|
82 (reference, -1));
|
Chris@420
|
83
|
Chris@420
|
84 components.push_back(AggregateWaveModel::ModelChannelSpec
|
Chris@420
|
85 (rm, -1));
|
Chris@420
|
86
|
Chris@420
|
87 Model *aggregateModel = new AggregateWaveModel(components);
|
Chris@420
|
88 ModelTransformer::Input aggregate(aggregateModel);
|
Chris@420
|
89
|
Chris@420
|
90 TransformId id = "vamp:match-vamp-plugin:match:path"; //!!! configure
|
Chris@420
|
91
|
Chris@420
|
92 TransformFactory *tf = TransformFactory::getInstance();
|
Chris@420
|
93
|
Chris@420
|
94 Transform transform = tf->getDefaultTransformFor
|
Chris@420
|
95 (id, aggregateModel->getSampleRate());
|
Chris@420
|
96
|
Chris@420
|
97 transform.setStepSize(transform.getBlockSize()/2);
|
Chris@420
|
98 transform.setParameter("serialise", 1);
|
Chris@420
|
99 transform.setParameter("smooth", 0);
|
Chris@420
|
100
|
Chris@420
|
101 SVDEBUG << "Align::alignModel: Alignment transform step size " << transform.getStepSize() << ", block size " << transform.getBlockSize() << endl;
|
Chris@420
|
102
|
Chris@420
|
103 ModelTransformerFactory *mtf = ModelTransformerFactory::getInstance();
|
Chris@420
|
104
|
Chris@420
|
105 QString message;
|
Chris@420
|
106 Model *transformOutput = mtf->transform(transform, aggregate, message);
|
Chris@420
|
107
|
Chris@420
|
108 if (!transformOutput) {
|
Chris@420
|
109 transform.setStepSize(0);
|
Chris@420
|
110 transformOutput = mtf->transform(transform, aggregate, message);
|
Chris@420
|
111 }
|
Chris@420
|
112
|
Chris@420
|
113 SparseTimeValueModel *path = dynamic_cast<SparseTimeValueModel *>
|
Chris@420
|
114 (transformOutput);
|
Chris@420
|
115
|
Chris@420
|
116 if (!path) {
|
Chris@420
|
117 cerr << "Align::alignModel: ERROR: Failed to create alignment path (no MATCH plugin?)" << endl;
|
Chris@420
|
118 delete transformOutput;
|
Chris@420
|
119 delete aggregateModel;
|
Chris@420
|
120 m_error = message;
|
Chris@420
|
121 return false;
|
Chris@420
|
122 }
|
Chris@420
|
123
|
Chris@420
|
124 path->setCompletion(0);
|
Chris@420
|
125
|
Chris@420
|
126 AlignmentModel *alignmentModel = new AlignmentModel
|
Chris@420
|
127 (reference, other, aggregateModel, path);
|
Chris@420
|
128
|
Chris@420
|
129 rm->setAlignment(alignmentModel);
|
Chris@420
|
130
|
Chris@420
|
131 return true;
|
Chris@420
|
132 }
|
Chris@420
|
133
|
Chris@420
|
134 bool
|
Chris@422
|
135 Align::alignModelViaProgram(Model *ref, Model *other, QString program)
|
Chris@420
|
136 {
|
Chris@420
|
137 WaveFileModel *reference = qobject_cast<WaveFileModel *>(ref);
|
Chris@420
|
138 WaveFileModel *rm = qobject_cast<WaveFileModel *>(other);
|
Chris@420
|
139
|
Chris@420
|
140 if (!rm) return false; // but this should have been tested already
|
Chris@420
|
141
|
Chris@420
|
142 // Run an external program, passing to it paths to the main
|
Chris@420
|
143 // model's audio file and the new model's audio file. It returns
|
Chris@420
|
144 // the path in CSV form through stdout.
|
Chris@420
|
145
|
Chris@420
|
146 QString refPath = reference->getLocalFilename();
|
Chris@420
|
147 QString otherPath = rm->getLocalFilename();
|
Chris@420
|
148
|
Chris@420
|
149 if (refPath == "" || otherPath == "") {
|
Chris@420
|
150 m_error = "Failed to find local filepath for wave-file model";
|
Chris@420
|
151 return false;
|
Chris@420
|
152 }
|
Chris@420
|
153
|
Chris@420
|
154 QProcess process;
|
Chris@420
|
155 QStringList args;
|
Chris@420
|
156 args << refPath << otherPath;
|
Chris@420
|
157 process.start(program, args);
|
Chris@420
|
158
|
Chris@420
|
159 process.waitForFinished(60000); //!!! nb timeout, but we can do better than blocking anyway
|
Chris@420
|
160
|
Chris@420
|
161 if (process.exitStatus() == 0) {
|
Chris@420
|
162
|
Chris@420
|
163 CSVFormat format;
|
Chris@420
|
164 format.setModelType(CSVFormat::TwoDimensionalModel);
|
Chris@420
|
165 format.setTimingType(CSVFormat::ExplicitTiming);
|
Chris@420
|
166 format.setTimeUnits(CSVFormat::TimeSeconds);
|
Chris@420
|
167 format.setColumnCount(2);
|
Chris@420
|
168 format.setColumnPurpose(0, CSVFormat::ColumnStartTime);
|
Chris@420
|
169 format.setColumnPurpose(1, CSVFormat::ColumnValue);
|
Chris@420
|
170 format.setAllowQuoting(false);
|
Chris@420
|
171 format.setSeparator(',');
|
Chris@420
|
172
|
Chris@420
|
173 CSVFileReader reader(&process, format, reference->getSampleRate());
|
Chris@420
|
174 if (!reader.isOK()) {
|
Chris@420
|
175 m_error = QString("Failed to parse output of program: %1")
|
Chris@420
|
176 .arg(reader.getError());
|
Chris@420
|
177 return false;
|
Chris@420
|
178 }
|
Chris@420
|
179
|
Chris@420
|
180 Model *csvOutput = reader.load();
|
Chris@420
|
181
|
Chris@420
|
182 SparseTimeValueModel *path = qobject_cast<SparseTimeValueModel *>(csvOutput);
|
Chris@420
|
183 if (!path) {
|
Chris@420
|
184 m_error = QString("Output of program did not produce sparse time-value model");
|
Chris@420
|
185 return false;
|
Chris@420
|
186 }
|
Chris@420
|
187
|
Chris@420
|
188 if (path->getPoints().empty()) {
|
Chris@420
|
189 m_error = QString("Output of alignment program contained no mappings");
|
Chris@420
|
190 return false;
|
Chris@420
|
191 }
|
Chris@420
|
192
|
Chris@420
|
193 AlignmentModel *alignmentModel = new AlignmentModel
|
Chris@420
|
194 (reference, other, 0, path);
|
Chris@420
|
195
|
Chris@420
|
196 rm->setAlignment(alignmentModel);
|
Chris@420
|
197
|
Chris@420
|
198 } else {
|
Chris@420
|
199 m_error = "Aligner process returned non-zero exit status";
|
Chris@420
|
200 return false;
|
Chris@420
|
201 }
|
Chris@420
|
202
|
Chris@420
|
203 cerr << "Align: success" << endl;
|
Chris@420
|
204
|
Chris@420
|
205 return true;
|
Chris@420
|
206 }
|
Chris@420
|
207
|