annotate align/TransformDTWAligner.cpp @ 769:a316cb6fed81 pitch-align

Fixes to notification and completion in aligners
author Chris Cannam
date Thu, 28 May 2020 17:04:36 +0100
parents 1b1960009be6
children 1d6cca5a5621
rev   line source
Chris@767 1 /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
Chris@767 2
Chris@767 3 /*
Chris@767 4 Sonic Visualiser
Chris@767 5 An audio file viewer and annotation editor.
Chris@767 6 Centre for Digital Music, Queen Mary, University of London.
Chris@767 7
Chris@767 8 This program is free software; you can redistribute it and/or
Chris@767 9 modify it under the terms of the GNU General Public License as
Chris@767 10 published by the Free Software Foundation; either version 2 of the
Chris@767 11 License, or (at your option) any later version. See the file
Chris@767 12 COPYING included with this distribution for more information.
Chris@767 13 */
Chris@767 14
Chris@767 15 #include "TransformDTWAligner.h"
Chris@767 16 #include "DTW.h"
Chris@767 17
Chris@767 18 #include "data/model/SparseTimeValueModel.h"
Chris@767 19 #include "data/model/RangeSummarisableTimeValueModel.h"
Chris@767 20 #include "data/model/AlignmentModel.h"
Chris@767 21 #include "data/model/AggregateWaveModel.h"
Chris@767 22
Chris@767 23 #include "framework/Document.h"
Chris@767 24
Chris@767 25 #include "transform/ModelTransformerFactory.h"
Chris@767 26 #include "transform/FeatureExtractionModelTransformer.h"
Chris@767 27
Chris@767 28 #include <QSettings>
Chris@767 29 #include <QMutex>
Chris@767 30 #include <QMutexLocker>
Chris@767 31
Chris@767 32 using std::vector;
Chris@767 33
Chris@767 34 TransformDTWAligner::TransformDTWAligner(Document *doc,
Chris@767 35 ModelId reference,
Chris@767 36 ModelId toAlign,
Chris@767 37 Transform transform,
Chris@767 38 DTWType dtwType) :
Chris@767 39 m_document(doc),
Chris@767 40 m_reference(reference),
Chris@767 41 m_toAlign(toAlign),
Chris@767 42 m_transform(transform),
Chris@767 43 m_dtwType(dtwType),
Chris@768 44 m_incomplete(true),
Chris@768 45 m_outputPreprocessor([](double x) { return x; })
Chris@768 46 {
Chris@768 47 }
Chris@768 48
Chris@768 49 TransformDTWAligner::TransformDTWAligner(Document *doc,
Chris@768 50 ModelId reference,
Chris@768 51 ModelId toAlign,
Chris@768 52 Transform transform,
Chris@768 53 DTWType dtwType,
Chris@768 54 std::function<double(double)>
Chris@768 55 outputPreprocessor) :
Chris@768 56 m_document(doc),
Chris@768 57 m_reference(reference),
Chris@768 58 m_toAlign(toAlign),
Chris@768 59 m_transform(transform),
Chris@768 60 m_dtwType(dtwType),
Chris@768 61 m_incomplete(true),
Chris@768 62 m_outputPreprocessor(outputPreprocessor)
Chris@767 63 {
Chris@767 64 }
Chris@767 65
Chris@767 66 TransformDTWAligner::~TransformDTWAligner()
Chris@767 67 {
Chris@767 68 if (m_incomplete) {
Chris@767 69 if (auto toAlign = ModelById::get(m_toAlign)) {
Chris@767 70 toAlign->setAlignment({});
Chris@767 71 }
Chris@767 72 }
Chris@767 73
Chris@767 74 ModelById::release(m_referenceOutputModel);
Chris@767 75 ModelById::release(m_toAlignOutputModel);
Chris@767 76 }
Chris@767 77
Chris@767 78 bool
Chris@767 79 TransformDTWAligner::isAvailable()
Chris@767 80 {
Chris@767 81 //!!! needs to be isAvailable(QString transformId)?
Chris@767 82 return true;
Chris@767 83 }
Chris@767 84
Chris@767 85 void
Chris@767 86 TransformDTWAligner::begin()
Chris@767 87 {
Chris@767 88 auto reference =
Chris@767 89 ModelById::getAs<RangeSummarisableTimeValueModel>(m_reference);
Chris@767 90 auto toAlign =
Chris@767 91 ModelById::getAs<RangeSummarisableTimeValueModel>(m_toAlign);
Chris@767 92
Chris@767 93 if (!reference || !toAlign) return;
Chris@767 94
Chris@767 95 SVCERR << "TransformDTWAligner[" << this << "]: begin(): aligning "
Chris@767 96 << m_toAlign << " against reference " << m_reference << endl;
Chris@767 97
Chris@767 98 ModelTransformerFactory *mtf = ModelTransformerFactory::getInstance();
Chris@767 99
Chris@767 100 QString message;
Chris@767 101
Chris@767 102 m_referenceOutputModel = mtf->transform(m_transform, m_reference, message);
Chris@767 103 auto referenceOutputModel = ModelById::get(m_referenceOutputModel);
Chris@767 104 if (!referenceOutputModel) {
Chris@767 105 SVCERR << "Align::alignModel: ERROR: Failed to create reference output model (no plugin?)" << endl;
Chris@767 106 emit failed(m_toAlign, message);
Chris@767 107 return;
Chris@767 108 }
Chris@767 109
Chris@767 110 SVCERR << "TransformDTWAligner[" << this << "]: begin(): transform id "
Chris@767 111 << m_transform.getIdentifier()
Chris@767 112 << " is running on reference model" << endl;
Chris@767 113
Chris@767 114 message = "";
Chris@767 115
Chris@767 116 m_toAlignOutputModel = mtf->transform(m_transform, m_toAlign, message);
Chris@767 117 auto toAlignOutputModel = ModelById::get(m_toAlignOutputModel);
Chris@767 118 if (!toAlignOutputModel) {
Chris@767 119 SVCERR << "Align::alignModel: ERROR: Failed to create toAlign output model (no plugin?)" << endl;
Chris@767 120 emit failed(m_toAlign, message);
Chris@767 121 return;
Chris@767 122 }
Chris@767 123
Chris@767 124 SVCERR << "TransformDTWAligner[" << this << "]: begin(): transform id "
Chris@767 125 << m_transform.getIdentifier()
Chris@767 126 << " is running on toAlign model" << endl;
Chris@767 127
Chris@767 128 connect(referenceOutputModel.get(), SIGNAL(completionChanged(ModelId)),
Chris@767 129 this, SLOT(completionChanged(ModelId)));
Chris@767 130 connect(toAlignOutputModel.get(), SIGNAL(completionChanged(ModelId)),
Chris@767 131 this, SLOT(completionChanged(ModelId)));
Chris@767 132
Chris@767 133 auto alignmentModel = std::make_shared<AlignmentModel>
Chris@768 134 (m_reference, m_toAlign, ModelId());
Chris@767 135 m_alignmentModel = ModelById::add(alignmentModel);
Chris@767 136
Chris@767 137 toAlign->setAlignment(m_alignmentModel);
Chris@767 138 m_document->addNonDerivedModel(m_alignmentModel);
Chris@767 139
Chris@767 140 // we wouldn't normally expect these to be true here, but...
Chris@767 141 int completion = 0;
Chris@767 142 if (referenceOutputModel->isReady(&completion) &&
Chris@767 143 toAlignOutputModel->isReady(&completion)) {
Chris@767 144 SVCERR << "TransformDTWAligner[" << this << "]: begin(): output models "
Chris@767 145 << "are ready already! calling performAlignment" << endl;
Chris@767 146 if (performAlignment()) {
Chris@767 147 emit complete(m_alignmentModel);
Chris@767 148 } else {
Chris@767 149 emit failed(m_toAlign, tr("Failed to calculate alignment using DTW"));
Chris@767 150 }
Chris@767 151 }
Chris@767 152 }
Chris@767 153
Chris@767 154 void
Chris@767 155 TransformDTWAligner::completionChanged(ModelId id)
Chris@767 156 {
Chris@767 157 if (!m_incomplete) {
Chris@767 158 return;
Chris@767 159 }
Chris@767 160
Chris@767 161 SVCERR << "TransformDTWAligner[" << this << "]: completionChanged: "
Chris@767 162 << "model " << id << endl;
Chris@767 163
Chris@767 164 auto referenceOutputModel = ModelById::get(m_referenceOutputModel);
Chris@767 165 auto toAlignOutputModel = ModelById::get(m_toAlignOutputModel);
Chris@768 166 auto alignmentModel = ModelById::getAs<AlignmentModel>(m_alignmentModel);
Chris@767 167
Chris@768 168 if (!referenceOutputModel || !toAlignOutputModel || !alignmentModel) {
Chris@767 169 return;
Chris@767 170 }
Chris@767 171
Chris@767 172 int referenceCompletion = 0, toAlignCompletion = 0;
Chris@767 173 bool referenceReady = referenceOutputModel->isReady(&referenceCompletion);
Chris@767 174 bool toAlignReady = toAlignOutputModel->isReady(&toAlignCompletion);
Chris@767 175
Chris@767 176 if (referenceReady && toAlignReady) {
Chris@767 177
Chris@767 178 SVCERR << "TransformDTWAligner[" << this << "]: completionChanged: "
Chris@767 179 << "ready, calling performAlignment" << endl;
Chris@767 180
Chris@768 181 alignmentModel->setCompletion(95);
Chris@767 182
Chris@767 183 if (performAlignment()) {
Chris@767 184 emit complete(m_alignmentModel);
Chris@767 185 } else {
Chris@767 186 emit failed(m_toAlign, tr("Alignment of transform outputs failed"));
Chris@767 187 }
Chris@767 188
Chris@767 189 } else {
Chris@767 190
Chris@767 191 SVCERR << "TransformDTWAligner[" << this << "]: completionChanged: "
Chris@767 192 << "not ready yet: reference completion " << referenceCompletion
Chris@767 193 << ", toAlign completion " << toAlignCompletion << endl;
Chris@767 194
Chris@768 195 int completion = std::min(referenceCompletion,
Chris@768 196 toAlignCompletion);
Chris@768 197 completion = (completion * 94) / 100;
Chris@768 198 alignmentModel->setCompletion(completion);
Chris@767 199 }
Chris@767 200 }
Chris@767 201
Chris@767 202 bool
Chris@767 203 TransformDTWAligner::performAlignment()
Chris@767 204 {
Chris@767 205 if (m_dtwType == Magnitude) {
Chris@767 206 return performAlignmentMagnitude();
Chris@767 207 } else {
Chris@767 208 return performAlignmentRiseFall();
Chris@767 209 }
Chris@767 210 }
Chris@767 211
Chris@767 212 bool
Chris@767 213 TransformDTWAligner::performAlignmentMagnitude()
Chris@767 214 {
Chris@767 215 auto referenceOutputSTVM = ModelById::getAs<SparseTimeValueModel>
Chris@767 216 (m_referenceOutputModel);
Chris@767 217 auto toAlignOutputSTVM = ModelById::getAs<SparseTimeValueModel>
Chris@767 218 (m_toAlignOutputModel);
Chris@767 219 auto alignmentModel = ModelById::getAs<AlignmentModel>
Chris@767 220 (m_alignmentModel);
Chris@767 221
Chris@767 222 if (!referenceOutputSTVM || !toAlignOutputSTVM) {
Chris@767 223 //!!! what?
Chris@767 224 return false;
Chris@767 225 }
Chris@767 226
Chris@767 227 if (!alignmentModel) {
Chris@767 228 return false;
Chris@767 229 }
Chris@767 230
Chris@767 231 vector<double> s1, s2;
Chris@767 232
Chris@767 233 {
Chris@767 234 auto events = referenceOutputSTVM->getAllEvents();
Chris@767 235 for (auto e: events) {
Chris@768 236 s1.push_back(m_outputPreprocessor(e.getValue()));
Chris@767 237 }
Chris@767 238 events = toAlignOutputSTVM->getAllEvents();
Chris@767 239 for (auto e: events) {
Chris@768 240 s2.push_back(m_outputPreprocessor(e.getValue()));
Chris@767 241 }
Chris@767 242 }
Chris@767 243
Chris@769 244 SVCERR << "TransformDTWAligner[" << this << "]: performAlignmentMagnitude: "
Chris@767 245 << "Have " << s1.size() << " events from reference, "
Chris@767 246 << s2.size() << " from toAlign" << endl;
Chris@767 247
Chris@767 248 MagnitudeDTW dtw;
Chris@767 249 vector<size_t> alignment;
Chris@767 250
Chris@767 251 {
Chris@767 252 SVCERR << "TransformDTWAligner[" << this
Chris@767 253 << "]: serialising DTW to avoid over-allocation" << endl;
Chris@767 254 static QMutex mutex;
Chris@767 255 QMutexLocker locker(&mutex);
Chris@767 256
Chris@767 257 alignment = dtw.alignSeries(s1, s2);
Chris@767 258 }
Chris@767 259
Chris@769 260 SVCERR << "TransformDTWAligner[" << this << "]: performAlignmentMagnitude: "
Chris@767 261 << "DTW produced " << alignment.size() << " points:" << endl;
Chris@767 262 for (int i = 0; i < alignment.size() && i < 100; ++i) {
Chris@767 263 SVCERR << alignment[i] << " ";
Chris@767 264 }
Chris@767 265 SVCERR << endl;
Chris@767 266
Chris@768 267 alignmentModel->setCompletion(100);
Chris@767 268
Chris@767 269 sv_frame_t resolution = referenceOutputSTVM->getResolution();
Chris@767 270 sv_frame_t sourceFrame = 0;
Chris@767 271
Chris@767 272 Path path(referenceOutputSTVM->getSampleRate(), resolution);
Chris@767 273
Chris@767 274 for (size_t m: alignment) {
Chris@767 275 path.add(PathPoint(sourceFrame, sv_frame_t(m) * resolution));
Chris@767 276 sourceFrame += resolution;
Chris@767 277 }
Chris@767 278
Chris@767 279 alignmentModel->setPath(path);
Chris@767 280
Chris@769 281 SVCERR << "TransformDTWAligner[" << this << "]: performAlignmentMagnitude: Done"
Chris@767 282 << endl;
Chris@767 283
Chris@767 284 m_incomplete = false;
Chris@767 285 return true;
Chris@767 286 }
Chris@767 287
Chris@767 288 bool
Chris@767 289 TransformDTWAligner::performAlignmentRiseFall()
Chris@767 290 {
Chris@767 291 auto referenceOutputSTVM = ModelById::getAs<SparseTimeValueModel>
Chris@767 292 (m_referenceOutputModel);
Chris@767 293 auto toAlignOutputSTVM = ModelById::getAs<SparseTimeValueModel>
Chris@767 294 (m_toAlignOutputModel);
Chris@767 295 auto alignmentModel = ModelById::getAs<AlignmentModel>
Chris@767 296 (m_alignmentModel);
Chris@767 297
Chris@767 298 if (!referenceOutputSTVM || !toAlignOutputSTVM) {
Chris@767 299 //!!! what?
Chris@767 300 return false;
Chris@767 301 }
Chris@767 302
Chris@767 303 if (!alignmentModel) {
Chris@767 304 return false;
Chris@767 305 }
Chris@768 306
Chris@768 307 auto convertEvents =
Chris@768 308 [this](const EventVector &ee) {
Chris@768 309 vector<RiseFallDTW::Value> s;
Chris@768 310 double prev = 0.0;
Chris@768 311 for (auto e: ee) {
Chris@768 312 double v = m_outputPreprocessor(e.getValue());
Chris@768 313 if (v == prev || s.empty()) {
Chris@768 314 s.push_back({ RiseFallDTW::Direction::None, 0.0 });
Chris@768 315 } else if (v > prev) {
Chris@768 316 s.push_back({ RiseFallDTW::Direction::Up, v - prev });
Chris@768 317 } else {
Chris@768 318 s.push_back({ RiseFallDTW::Direction::Down, prev - v });
Chris@768 319 }
Chris@768 320 }
Chris@768 321 return s;
Chris@768 322 };
Chris@767 323
Chris@768 324 vector<RiseFallDTW::Value> s1 =
Chris@768 325 convertEvents(referenceOutputSTVM->getAllEvents());
Chris@767 326
Chris@768 327 vector<RiseFallDTW::Value> s2 =
Chris@768 328 convertEvents(toAlignOutputSTVM->getAllEvents());
Chris@767 329
Chris@769 330 SVCERR << "TransformDTWAligner[" << this << "]: performAlignmentRiseFall: "
Chris@767 331 << "Have " << s1.size() << " events from reference, "
Chris@767 332 << s2.size() << " from toAlign" << endl;
Chris@767 333
Chris@767 334 RiseFallDTW dtw;
Chris@767 335
Chris@767 336 vector<size_t> alignment;
Chris@767 337
Chris@767 338 {
Chris@767 339 SVCERR << "TransformDTWAligner[" << this
Chris@767 340 << "]: serialising DTW to avoid over-allocation" << endl;
Chris@767 341 static QMutex mutex;
Chris@767 342 QMutexLocker locker(&mutex);
Chris@767 343
Chris@767 344 alignment = dtw.alignSeries(s1, s2);
Chris@767 345 }
Chris@767 346
Chris@769 347 SVCERR << "TransformDTWAligner[" << this << "]: performAlignmentRiseFall: "
Chris@767 348 << "DTW produced " << alignment.size() << " points:" << endl;
Chris@767 349 for (int i = 0; i < alignment.size() && i < 100; ++i) {
Chris@767 350 SVCERR << alignment[i] << " ";
Chris@767 351 }
Chris@767 352 SVCERR << endl;
Chris@767 353
Chris@768 354 alignmentModel->setCompletion(100);
Chris@767 355
Chris@767 356 sv_frame_t resolution = referenceOutputSTVM->getResolution();
Chris@767 357 sv_frame_t sourceFrame = 0;
Chris@767 358
Chris@767 359 Path path(referenceOutputSTVM->getSampleRate(), resolution);
Chris@767 360
Chris@767 361 for (size_t m: alignment) {
Chris@767 362 path.add(PathPoint(sourceFrame, sv_frame_t(m) * resolution));
Chris@767 363 sourceFrame += resolution;
Chris@767 364 }
Chris@767 365
Chris@767 366 alignmentModel->setPath(path);
Chris@767 367
Chris@769 368 SVCERR << "TransformDTWAligner[" << this << "]: performAlignmentRiseFall: Done"
Chris@767 369 << endl;
Chris@767 370
Chris@767 371 m_incomplete = false;
Chris@767 372 return true;
Chris@767 373 }