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
|
Chris@420
|
8 This program is free software; you can redistribute it and/or
|
Chris@420
|
9 modify it under the terms of the GNU General Public License as
|
Chris@420
|
10 published by the Free Software Foundation; either version 2 of the
|
Chris@420
|
11 License, or (at your option) any later version. See the file
|
Chris@420
|
12 COPYING included with this distribution for more information.
|
Chris@420
|
13 */
|
Chris@420
|
14
|
Chris@420
|
15 #include "Align.h"
|
Chris@767
|
16
|
Chris@767
|
17 #include "LinearAligner.h"
|
Chris@777
|
18 #include "MATCHAligner.h"
|
Chris@767
|
19 #include "TransformDTWAligner.h"
|
Chris@753
|
20 #include "ExternalProgramAligner.h"
|
Chris@767
|
21
|
Chris@744
|
22 #include "framework/Document.h"
|
Chris@420
|
23
|
Chris@767
|
24 #include "transform/Transform.h"
|
Chris@767
|
25 #include "transform/TransformFactory.h"
|
Chris@767
|
26
|
Chris@768
|
27 #include "base/Pitch.h"
|
Chris@768
|
28
|
Chris@422
|
29 #include <QSettings>
|
Chris@761
|
30 #include <QTimer>
|
Chris@422
|
31
|
Chris@767
|
32 using std::make_shared;
|
Chris@767
|
33
|
Chris@767
|
34 QString
|
Chris@767
|
35 Align::getAlignmentTypeTag(AlignmentType type)
|
Chris@767
|
36 {
|
Chris@767
|
37 switch (type) {
|
Chris@767
|
38 case NoAlignment:
|
Chris@767
|
39 default:
|
Chris@767
|
40 return "no-alignment";
|
Chris@767
|
41 case LinearAlignment:
|
Chris@767
|
42 return "linear-alignment";
|
Chris@767
|
43 case TrimmedLinearAlignment:
|
Chris@767
|
44 return "trimmed-linear-alignment";
|
Chris@767
|
45 case MATCHAlignment:
|
Chris@767
|
46 return "match-alignment";
|
Chris@767
|
47 case MATCHAlignmentWithPitchCompare:
|
Chris@767
|
48 return "match-alignment-with-pitch";
|
Chris@771
|
49 case SungNoteContourAlignment:
|
Chris@771
|
50 return "sung-note-alignment";
|
Chris@767
|
51 case TransformDrivenDTWAlignment:
|
Chris@767
|
52 return "transform-driven-alignment";
|
Chris@767
|
53 case ExternalProgramAlignment:
|
Chris@767
|
54 return "external-program-alignment";
|
Chris@767
|
55 }
|
Chris@767
|
56 }
|
Chris@767
|
57
|
Chris@767
|
58 Align::AlignmentType
|
Chris@767
|
59 Align::getAlignmentTypeForTag(QString tag)
|
Chris@767
|
60 {
|
Chris@767
|
61 for (int i = 0; i <= int(LastAlignmentType); ++i) {
|
Chris@767
|
62 if (tag == getAlignmentTypeTag(AlignmentType(i))) {
|
Chris@767
|
63 return AlignmentType(i);
|
Chris@767
|
64 }
|
Chris@767
|
65 }
|
Chris@767
|
66 return NoAlignment;
|
Chris@767
|
67 }
|
Chris@767
|
68
|
Chris@761
|
69 void
|
Chris@753
|
70 Align::alignModel(Document *doc,
|
Chris@753
|
71 ModelId reference,
|
Chris@761
|
72 ModelId toAlign)
|
Chris@761
|
73 {
|
Chris@767
|
74 if (addAligner(doc, reference, toAlign)) {
|
Chris@767
|
75 m_aligners[toAlign]->begin();
|
Chris@767
|
76 }
|
Chris@761
|
77 }
|
Chris@761
|
78
|
Chris@761
|
79 void
|
Chris@761
|
80 Align::scheduleAlignment(Document *doc,
|
Chris@761
|
81 ModelId reference,
|
Chris@761
|
82 ModelId toAlign)
|
Chris@761
|
83 {
|
Chris@767
|
84 int delay = 700 * int(m_aligners.size());
|
Chris@761
|
85 if (delay > 3500) {
|
Chris@761
|
86 delay = 3500;
|
Chris@761
|
87 }
|
Chris@767
|
88 if (!addAligner(doc, reference, toAlign)) {
|
Chris@767
|
89 return;
|
Chris@767
|
90 }
|
Chris@761
|
91 SVCERR << "Align::scheduleAlignment: delaying " << delay << "ms" << endl;
|
Chris@761
|
92 QTimer::singleShot(delay, m_aligners[toAlign].get(), SLOT(begin()));
|
Chris@761
|
93 }
|
Chris@761
|
94
|
Chris@767
|
95 bool
|
Chris@761
|
96 Align::addAligner(Document *doc,
|
Chris@761
|
97 ModelId reference,
|
Chris@761
|
98 ModelId toAlign)
|
Chris@753
|
99 {
|
Chris@776
|
100 AlignmentType type = getAlignmentPreference();
|
Chris@753
|
101
|
Chris@753
|
102 std::shared_ptr<Aligner> aligner;
|
Chris@753
|
103
|
Chris@773
|
104 if (m_aligners.find(toAlign) != m_aligners.end()) {
|
Chris@773
|
105 // We don't want a callback on removeAligner to happen during
|
Chris@773
|
106 // our own call to addAligner! Disconnect and delete the old
|
Chris@773
|
107 // aligner first
|
Chris@773
|
108 disconnect(m_aligners[toAlign].get(), nullptr, this, nullptr);
|
Chris@773
|
109 m_aligners.erase(toAlign);
|
Chris@773
|
110 }
|
Chris@773
|
111
|
Chris@753
|
112 {
|
Chris@753
|
113 // Replace the aligner with a new one. This also stops any
|
Chris@753
|
114 // previously-running alignment, when the old entry is
|
Chris@753
|
115 // replaced and its aligner destroyed.
|
Chris@753
|
116
|
Chris@753
|
117 QMutexLocker locker(&m_mutex);
|
Chris@767
|
118
|
Chris@767
|
119 switch (type) {
|
Chris@767
|
120
|
Chris@767
|
121 case NoAlignment:
|
Chris@767
|
122 return false;
|
Chris@767
|
123
|
Chris@767
|
124 case LinearAlignment:
|
Chris@767
|
125 case TrimmedLinearAlignment: {
|
Chris@767
|
126 bool trimmed = (type == TrimmedLinearAlignment);
|
Chris@767
|
127 aligner = make_shared<LinearAligner>(doc,
|
Chris@767
|
128 reference,
|
Chris@767
|
129 toAlign,
|
Chris@767
|
130 trimmed);
|
Chris@767
|
131 break;
|
Chris@753
|
132 }
|
Chris@753
|
133
|
Chris@767
|
134 case MATCHAlignment:
|
Chris@767
|
135 case MATCHAlignmentWithPitchCompare: {
|
Chris@767
|
136
|
Chris@767
|
137 bool withTuningDifference =
|
Chris@767
|
138 (type == MATCHAlignmentWithPitchCompare);
|
Chris@767
|
139
|
Chris@777
|
140 aligner = make_shared<MATCHAligner>(doc,
|
Chris@777
|
141 reference,
|
Chris@777
|
142 toAlign,
|
Chris@781
|
143 getUseSubsequenceAlignment(),
|
Chris@777
|
144 withTuningDifference);
|
Chris@767
|
145 break;
|
Chris@767
|
146 }
|
Chris@767
|
147
|
Chris@771
|
148 case SungNoteContourAlignment:
|
Chris@767
|
149 {
|
Chris@767
|
150 auto refModel = ModelById::get(reference);
|
Chris@767
|
151 if (!refModel) return false;
|
Chris@771
|
152
|
Chris@767
|
153 Transform transform = TransformFactory::getInstance()->
|
Chris@771
|
154 getDefaultTransformFor("vamp:pyin:pyin:notes",
|
Chris@767
|
155 refModel->getSampleRate());
|
Chris@767
|
156
|
Chris@767
|
157 aligner = make_shared<TransformDTWAligner>
|
Chris@767
|
158 (doc,
|
Chris@767
|
159 reference,
|
Chris@767
|
160 toAlign,
|
Chris@781
|
161 getUseSubsequenceAlignment(),
|
Chris@767
|
162 transform,
|
Chris@771
|
163 [](double prev, double curr) {
|
Chris@771
|
164 RiseFallDTW::Value v;
|
Chris@771
|
165 if (curr <= 0.0) {
|
Chris@771
|
166 v = { RiseFallDTW::Direction::None, 0.0 };
|
Chris@771
|
167 } else if (prev <= 0.0) {
|
Chris@771
|
168 v = { RiseFallDTW::Direction::Up, 0.0 };
|
Chris@768
|
169 } else {
|
Chris@771
|
170 double prevP = Pitch::getPitchForFrequency(prev);
|
Chris@771
|
171 double currP = Pitch::getPitchForFrequency(curr);
|
Chris@771
|
172 if (currP >= prevP) {
|
Chris@771
|
173 v = { RiseFallDTW::Direction::Up, currP - prevP };
|
Chris@771
|
174 } else {
|
Chris@771
|
175 v = { RiseFallDTW::Direction::Down, prevP - currP };
|
Chris@771
|
176 }
|
Chris@768
|
177 }
|
Chris@771
|
178 return v;
|
Chris@768
|
179 });
|
Chris@767
|
180 break;
|
Chris@767
|
181 }
|
Chris@767
|
182
|
Chris@767
|
183 case TransformDrivenDTWAlignment:
|
Chris@767
|
184 throw std::logic_error("Not yet implemented"); //!!!
|
Chris@767
|
185
|
Chris@767
|
186 case ExternalProgramAlignment: {
|
Chris@776
|
187 aligner = make_shared<ExternalProgramAligner>
|
Chris@776
|
188 (doc,
|
Chris@776
|
189 reference,
|
Chris@776
|
190 toAlign,
|
Chris@776
|
191 getPreferredAlignmentProgram());
|
Chris@767
|
192 }
|
Chris@767
|
193 }
|
Chris@767
|
194
|
Chris@767
|
195 m_aligners[toAlign] = aligner;
|
Chris@753
|
196 }
|
Chris@753
|
197
|
Chris@753
|
198 connect(aligner.get(), SIGNAL(complete(ModelId)),
|
Chris@753
|
199 this, SLOT(alignerComplete(ModelId)));
|
Chris@761
|
200
|
Chris@761
|
201 connect(aligner.get(), SIGNAL(failed(ModelId, QString)),
|
Chris@761
|
202 this, SLOT(alignerFailed(ModelId, QString)));
|
Chris@767
|
203
|
Chris@767
|
204 return true;
|
Chris@767
|
205 }
|
Chris@767
|
206
|
Chris@767
|
207 Align::AlignmentType
|
Chris@776
|
208 Align::getAlignmentPreference()
|
Chris@767
|
209 {
|
Chris@767
|
210 QSettings settings;
|
Chris@767
|
211 settings.beginGroup("Alignment");
|
Chris@767
|
212 QString tag = settings.value
|
Chris@767
|
213 ("alignment-type", getAlignmentTypeTag(MATCHAlignment)).toString();
|
Chris@776
|
214 return getAlignmentTypeForTag(tag);
|
Chris@776
|
215 }
|
Chris@767
|
216
|
Chris@776
|
217 QString
|
Chris@776
|
218 Align::getPreferredAlignmentProgram()
|
Chris@776
|
219 {
|
Chris@776
|
220 QSettings settings;
|
Chris@776
|
221 settings.beginGroup("Alignment");
|
Chris@776
|
222 return settings.value("alignment-program", "").toString();
|
Chris@776
|
223 }
|
Chris@767
|
224
|
Chris@776
|
225 Transform
|
Chris@776
|
226 Align::getPreferredAlignmentTransform()
|
Chris@776
|
227 {
|
Chris@776
|
228 QSettings settings;
|
Chris@776
|
229 settings.beginGroup("Alignment");
|
Chris@776
|
230 QString xml = settings.value("alignment-transform", "").toString();
|
Chris@776
|
231 return Transform(xml);
|
Chris@753
|
232 }
|
Chris@753
|
233
|
Chris@781
|
234 bool
|
Chris@781
|
235 Align::getUseSubsequenceAlignment()
|
Chris@781
|
236 {
|
Chris@781
|
237 QSettings settings;
|
Chris@781
|
238 settings.beginGroup("Alignment");
|
Chris@781
|
239 return settings.value("alignment-subsequence", false).toBool();
|
Chris@781
|
240 }
|
Chris@781
|
241
|
Chris@753
|
242 void
|
Chris@776
|
243 Align::setAlignmentPreference(AlignmentType type)
|
Chris@422
|
244 {
|
Chris@422
|
245 QSettings settings;
|
Chris@767
|
246 settings.beginGroup("Alignment");
|
Chris@767
|
247 QString tag = getAlignmentTypeTag(type);
|
Chris@767
|
248 settings.setValue("alignment-type", tag);
|
Chris@776
|
249 settings.endGroup();
|
Chris@776
|
250 }
|
Chris@767
|
251
|
Chris@776
|
252 void
|
Chris@785
|
253 Align::setDefaultAlignmentPreference(AlignmentType type)
|
Chris@785
|
254 {
|
Chris@785
|
255 QSettings settings;
|
Chris@785
|
256 settings.beginGroup("Alignment");
|
Chris@785
|
257 if (!settings.contains("alignment-type")) {
|
Chris@785
|
258 QString tag = getAlignmentTypeTag(type);
|
Chris@785
|
259 settings.setValue("alignment-type", tag);
|
Chris@785
|
260 }
|
Chris@785
|
261 settings.endGroup();
|
Chris@785
|
262 }
|
Chris@785
|
263
|
Chris@785
|
264 void
|
Chris@776
|
265 Align::setPreferredAlignmentProgram(QString program)
|
Chris@776
|
266 {
|
Chris@776
|
267 QSettings settings;
|
Chris@776
|
268 settings.beginGroup("Alignment");
|
Chris@776
|
269 settings.setValue("alignment-program", program);
|
Chris@776
|
270 settings.endGroup();
|
Chris@776
|
271 }
|
Chris@767
|
272
|
Chris@776
|
273 void
|
Chris@776
|
274 Align::setPreferredAlignmentTransform(Transform transform)
|
Chris@776
|
275 {
|
Chris@776
|
276 QSettings settings;
|
Chris@776
|
277 settings.beginGroup("Alignment");
|
Chris@776
|
278 settings.setValue("alignment-transform", transform.toXmlString());
|
Chris@422
|
279 settings.endGroup();
|
Chris@670
|
280 }
|
Chris@670
|
281
|
Chris@781
|
282 void
|
Chris@781
|
283 Align::setUseSubsequenceAlignment(bool subsequence)
|
Chris@781
|
284 {
|
Chris@781
|
285 QSettings settings;
|
Chris@781
|
286 settings.beginGroup("Alignment");
|
Chris@781
|
287 settings.setValue("alignment-subsequence", subsequence);
|
Chris@781
|
288 settings.endGroup();
|
Chris@781
|
289 }
|
Chris@781
|
290
|
Chris@428
|
291 bool
|
Chris@428
|
292 Align::canAlign()
|
Chris@428
|
293 {
|
Chris@776
|
294 AlignmentType type = getAlignmentPreference();
|
Chris@753
|
295
|
Chris@767
|
296 if (type == ExternalProgramAlignment) {
|
Chris@776
|
297 return ExternalProgramAligner::isAvailable
|
Chris@776
|
298 (getPreferredAlignmentProgram());
|
Chris@753
|
299 } else {
|
Chris@777
|
300 return MATCHAligner::isAvailable();
|
Chris@753
|
301 }
|
Chris@428
|
302 }
|
Chris@428
|
303
|
Chris@702
|
304 void
|
Chris@753
|
305 Align::alignerComplete(ModelId alignmentModel)
|
Chris@702
|
306 {
|
Chris@761
|
307 removeAligner(sender());
|
Chris@761
|
308 emit alignmentComplete(alignmentModel);
|
Chris@761
|
309 }
|
Chris@761
|
310
|
Chris@761
|
311 void
|
Chris@761
|
312 Align::alignerFailed(ModelId toAlign, QString error)
|
Chris@761
|
313 {
|
Chris@761
|
314 removeAligner(sender());
|
Chris@761
|
315 emit alignmentFailed(toAlign, error);
|
Chris@761
|
316 }
|
Chris@761
|
317
|
Chris@761
|
318 void
|
Chris@761
|
319 Align::removeAligner(QObject *obj)
|
Chris@761
|
320 {
|
Chris@761
|
321 Aligner *aligner = qobject_cast<Aligner *>(obj);
|
Chris@753
|
322 if (!aligner) {
|
Chris@761
|
323 SVCERR << "ERROR: Align::removeAligner: Not an Aligner" << endl;
|
Chris@702
|
324 return;
|
Chris@702
|
325 }
|
Chris@702
|
326
|
Chris@761
|
327 QMutexLocker locker (&m_mutex);
|
Chris@702
|
328
|
Chris@761
|
329 for (auto p: m_aligners) {
|
Chris@761
|
330 if (aligner == p.second.get()) {
|
Chris@761
|
331 m_aligners.erase(p.first);
|
Chris@761
|
332 break;
|
Chris@702
|
333 }
|
Chris@702
|
334 }
|
Chris@761
|
335 }
|
Chris@702
|
336
|