annotate align/TransformDTWAligner.cpp @ 767:dd742e566e60 pitch-align

Make a start on further alignment methods
author Chris Cannam
date Thu, 21 May 2020 16:21:57 +0100
parents
children 1b1960009be6
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_referenceTransformComplete(false),
Chris@767 43 m_toAlignTransformComplete(false),
Chris@767 44 m_transform(transform),
Chris@767 45 m_dtwType(dtwType),
Chris@767 46 m_incomplete(true)
Chris@767 47 {
Chris@767 48 }
Chris@767 49
Chris@767 50 TransformDTWAligner::~TransformDTWAligner()
Chris@767 51 {
Chris@767 52 if (m_incomplete) {
Chris@767 53 if (auto toAlign = ModelById::get(m_toAlign)) {
Chris@767 54 toAlign->setAlignment({});
Chris@767 55 }
Chris@767 56 }
Chris@767 57
Chris@767 58 ModelById::release(m_referenceOutputModel);
Chris@767 59 ModelById::release(m_toAlignOutputModel);
Chris@767 60 ModelById::release(m_alignmentProgressModel);
Chris@767 61 }
Chris@767 62
Chris@767 63 bool
Chris@767 64 TransformDTWAligner::isAvailable()
Chris@767 65 {
Chris@767 66 //!!! needs to be isAvailable(QString transformId)?
Chris@767 67 return true;
Chris@767 68 }
Chris@767 69
Chris@767 70 void
Chris@767 71 TransformDTWAligner::begin()
Chris@767 72 {
Chris@767 73 auto reference =
Chris@767 74 ModelById::getAs<RangeSummarisableTimeValueModel>(m_reference);
Chris@767 75 auto toAlign =
Chris@767 76 ModelById::getAs<RangeSummarisableTimeValueModel>(m_toAlign);
Chris@767 77
Chris@767 78 if (!reference || !toAlign) return;
Chris@767 79
Chris@767 80 SVCERR << "TransformDTWAligner[" << this << "]: begin(): aligning "
Chris@767 81 << m_toAlign << " against reference " << m_reference << endl;
Chris@767 82
Chris@767 83 ModelTransformerFactory *mtf = ModelTransformerFactory::getInstance();
Chris@767 84
Chris@767 85 QString message;
Chris@767 86
Chris@767 87 m_referenceOutputModel = mtf->transform(m_transform, m_reference, message);
Chris@767 88 auto referenceOutputModel = ModelById::get(m_referenceOutputModel);
Chris@767 89 if (!referenceOutputModel) {
Chris@767 90 SVCERR << "Align::alignModel: ERROR: Failed to create reference output model (no plugin?)" << endl;
Chris@767 91 emit failed(m_toAlign, message);
Chris@767 92 return;
Chris@767 93 }
Chris@767 94
Chris@767 95 SVCERR << "TransformDTWAligner[" << this << "]: begin(): transform id "
Chris@767 96 << m_transform.getIdentifier()
Chris@767 97 << " is running on reference model" << endl;
Chris@767 98
Chris@767 99 message = "";
Chris@767 100
Chris@767 101 m_toAlignOutputModel = mtf->transform(m_transform, m_toAlign, message);
Chris@767 102 auto toAlignOutputModel = ModelById::get(m_toAlignOutputModel);
Chris@767 103 if (!toAlignOutputModel) {
Chris@767 104 SVCERR << "Align::alignModel: ERROR: Failed to create toAlign output model (no plugin?)" << endl;
Chris@767 105 emit failed(m_toAlign, message);
Chris@767 106 return;
Chris@767 107 }
Chris@767 108
Chris@767 109 SVCERR << "TransformDTWAligner[" << this << "]: begin(): transform id "
Chris@767 110 << m_transform.getIdentifier()
Chris@767 111 << " is running on toAlign model" << endl;
Chris@767 112
Chris@767 113 connect(referenceOutputModel.get(), SIGNAL(completionChanged(ModelId)),
Chris@767 114 this, SLOT(completionChanged(ModelId)));
Chris@767 115 connect(toAlignOutputModel.get(), SIGNAL(completionChanged(ModelId)),
Chris@767 116 this, SLOT(completionChanged(ModelId)));
Chris@767 117
Chris@767 118 auto alignmentProgressModel = std::make_shared<SparseTimeValueModel>
Chris@767 119 (reference->getSampleRate(), m_transform.getStepSize(), false);
Chris@767 120 alignmentProgressModel->setCompletion(0);
Chris@767 121 m_alignmentProgressModel = ModelById::add(alignmentProgressModel);
Chris@767 122
Chris@767 123 auto alignmentModel = std::make_shared<AlignmentModel>
Chris@767 124 (m_reference, m_toAlign, m_alignmentProgressModel);
Chris@767 125 m_alignmentModel = ModelById::add(alignmentModel);
Chris@767 126
Chris@767 127 toAlign->setAlignment(m_alignmentModel);
Chris@767 128 m_document->addNonDerivedModel(m_alignmentModel);
Chris@767 129
Chris@767 130 // we wouldn't normally expect these to be true here, but...
Chris@767 131 int completion = 0;
Chris@767 132 if (referenceOutputModel->isReady(&completion) &&
Chris@767 133 toAlignOutputModel->isReady(&completion)) {
Chris@767 134 SVCERR << "TransformDTWAligner[" << this << "]: begin(): output models "
Chris@767 135 << "are ready already! calling performAlignment" << endl;
Chris@767 136 if (performAlignment()) {
Chris@767 137 emit complete(m_alignmentModel);
Chris@767 138 } else {
Chris@767 139 emit failed(m_toAlign, tr("Failed to calculate alignment using DTW"));
Chris@767 140 }
Chris@767 141 }
Chris@767 142 }
Chris@767 143
Chris@767 144 void
Chris@767 145 TransformDTWAligner::completionChanged(ModelId id)
Chris@767 146 {
Chris@767 147 if (!m_incomplete) {
Chris@767 148 return;
Chris@767 149 }
Chris@767 150
Chris@767 151 SVCERR << "TransformDTWAligner[" << this << "]: completionChanged: "
Chris@767 152 << "model " << id << endl;
Chris@767 153
Chris@767 154 auto referenceOutputModel = ModelById::get(m_referenceOutputModel);
Chris@767 155 auto toAlignOutputModel = ModelById::get(m_toAlignOutputModel);
Chris@767 156
Chris@767 157 if (!referenceOutputModel || !toAlignOutputModel) {
Chris@767 158 return;
Chris@767 159 }
Chris@767 160
Chris@767 161 int referenceCompletion = 0, toAlignCompletion = 0;
Chris@767 162 bool referenceReady = referenceOutputModel->isReady(&referenceCompletion);
Chris@767 163 bool toAlignReady = toAlignOutputModel->isReady(&toAlignCompletion);
Chris@767 164
Chris@767 165 auto alignmentProgressModel =
Chris@767 166 ModelById::getAs<SparseTimeValueModel>(m_alignmentProgressModel);
Chris@767 167
Chris@767 168 if (referenceReady && toAlignReady) {
Chris@767 169
Chris@767 170 SVCERR << "TransformDTWAligner[" << this << "]: completionChanged: "
Chris@767 171 << "ready, calling performAlignment" << endl;
Chris@767 172
Chris@767 173 if (alignmentProgressModel) {
Chris@767 174 alignmentProgressModel->setCompletion(95);
Chris@767 175 }
Chris@767 176
Chris@767 177 if (performAlignment()) {
Chris@767 178 emit complete(m_alignmentModel);
Chris@767 179 } else {
Chris@767 180 emit failed(m_toAlign, tr("Alignment of transform outputs failed"));
Chris@767 181 }
Chris@767 182
Chris@767 183 } else {
Chris@767 184
Chris@767 185 SVCERR << "TransformDTWAligner[" << this << "]: completionChanged: "
Chris@767 186 << "not ready yet: reference completion " << referenceCompletion
Chris@767 187 << ", toAlign completion " << toAlignCompletion << endl;
Chris@767 188
Chris@767 189 if (alignmentProgressModel) {
Chris@767 190 int completion = std::min(referenceCompletion,
Chris@767 191 toAlignCompletion);
Chris@767 192 completion = (completion * 94) / 100;
Chris@767 193 alignmentProgressModel->setCompletion(completion);
Chris@767 194 }
Chris@767 195 }
Chris@767 196 }
Chris@767 197
Chris@767 198 bool
Chris@767 199 TransformDTWAligner::performAlignment()
Chris@767 200 {
Chris@767 201 if (m_dtwType == Magnitude) {
Chris@767 202 return performAlignmentMagnitude();
Chris@767 203 } else {
Chris@767 204 return performAlignmentRiseFall();
Chris@767 205 }
Chris@767 206 }
Chris@767 207
Chris@767 208 bool
Chris@767 209 TransformDTWAligner::performAlignmentMagnitude()
Chris@767 210 {
Chris@767 211 auto referenceOutputSTVM = ModelById::getAs<SparseTimeValueModel>
Chris@767 212 (m_referenceOutputModel);
Chris@767 213 auto toAlignOutputSTVM = ModelById::getAs<SparseTimeValueModel>
Chris@767 214 (m_toAlignOutputModel);
Chris@767 215 auto alignmentModel = ModelById::getAs<AlignmentModel>
Chris@767 216 (m_alignmentModel);
Chris@767 217
Chris@767 218 if (!referenceOutputSTVM || !toAlignOutputSTVM) {
Chris@767 219 //!!! what?
Chris@767 220 return false;
Chris@767 221 }
Chris@767 222
Chris@767 223 if (!alignmentModel) {
Chris@767 224 return false;
Chris@767 225 }
Chris@767 226
Chris@767 227 vector<double> s1, s2;
Chris@767 228
Chris@767 229 {
Chris@767 230 auto events = referenceOutputSTVM->getAllEvents();
Chris@767 231 for (auto e: events) {
Chris@767 232 s1.push_back(e.getValue());
Chris@767 233 }
Chris@767 234 events = toAlignOutputSTVM->getAllEvents();
Chris@767 235 for (auto e: events) {
Chris@767 236 s2.push_back(e.getValue());
Chris@767 237 }
Chris@767 238 }
Chris@767 239
Chris@767 240 SVCERR << "TransformDTWAligner[" << this << "]: performAlignment: "
Chris@767 241 << "Have " << s1.size() << " events from reference, "
Chris@767 242 << s2.size() << " from toAlign" << endl;
Chris@767 243
Chris@767 244 MagnitudeDTW dtw;
Chris@767 245 vector<size_t> alignment;
Chris@767 246
Chris@767 247 {
Chris@767 248 SVCERR << "TransformDTWAligner[" << this
Chris@767 249 << "]: serialising DTW to avoid over-allocation" << endl;
Chris@767 250 static QMutex mutex;
Chris@767 251 QMutexLocker locker(&mutex);
Chris@767 252
Chris@767 253 alignment = dtw.alignSeries(s1, s2);
Chris@767 254 }
Chris@767 255
Chris@767 256 SVCERR << "TransformDTWAligner[" << this << "]: performAlignment: "
Chris@767 257 << "DTW produced " << alignment.size() << " points:" << endl;
Chris@767 258 for (int i = 0; i < alignment.size() && i < 100; ++i) {
Chris@767 259 SVCERR << alignment[i] << " ";
Chris@767 260 }
Chris@767 261 SVCERR << endl;
Chris@767 262
Chris@767 263 auto alignmentProgressModel =
Chris@767 264 ModelById::getAs<SparseTimeValueModel>(m_alignmentProgressModel);
Chris@767 265 if (alignmentProgressModel) {
Chris@767 266 alignmentProgressModel->setCompletion(100);
Chris@767 267 }
Chris@767 268
Chris@767 269 // clear the alignment progress model
Chris@767 270 alignmentModel->setPathFrom(ModelId());
Chris@767 271
Chris@767 272 sv_frame_t resolution = referenceOutputSTVM->getResolution();
Chris@767 273 sv_frame_t sourceFrame = 0;
Chris@767 274
Chris@767 275 Path path(referenceOutputSTVM->getSampleRate(), resolution);
Chris@767 276
Chris@767 277 for (size_t m: alignment) {
Chris@767 278 path.add(PathPoint(sourceFrame, sv_frame_t(m) * resolution));
Chris@767 279 sourceFrame += resolution;
Chris@767 280 }
Chris@767 281
Chris@767 282 alignmentModel->setPath(path);
Chris@767 283
Chris@767 284 SVCERR << "TransformDTWAligner[" << this << "]: performAlignment: Done"
Chris@767 285 << endl;
Chris@767 286
Chris@767 287 m_incomplete = false;
Chris@767 288 return true;
Chris@767 289 }
Chris@767 290
Chris@767 291 bool
Chris@767 292 TransformDTWAligner::performAlignmentRiseFall()
Chris@767 293 {
Chris@767 294 auto referenceOutputSTVM = ModelById::getAs<SparseTimeValueModel>
Chris@767 295 (m_referenceOutputModel);
Chris@767 296 auto toAlignOutputSTVM = ModelById::getAs<SparseTimeValueModel>
Chris@767 297 (m_toAlignOutputModel);
Chris@767 298 auto alignmentModel = ModelById::getAs<AlignmentModel>
Chris@767 299 (m_alignmentModel);
Chris@767 300
Chris@767 301 if (!referenceOutputSTVM || !toAlignOutputSTVM) {
Chris@767 302 //!!! what?
Chris@767 303 return false;
Chris@767 304 }
Chris@767 305
Chris@767 306 if (!alignmentModel) {
Chris@767 307 return false;
Chris@767 308 }
Chris@767 309
Chris@767 310 vector<RiseFallDTW::Value> s1, s2;
Chris@767 311 double prev1 = 0.0, prev2 = 0.0;
Chris@767 312
Chris@767 313 {
Chris@767 314 auto events = referenceOutputSTVM->getAllEvents();
Chris@767 315 for (auto e: events) {
Chris@767 316 double v = e.getValue();
Chris@767 317 //!!! the original does this using MIDI pitch for the
Chris@767 318 //!!! pYin transform... rework with a lambda passed in
Chris@767 319 //!!! for modification maybe? + factor out s1/s2 of course
Chris@767 320 if (v > prev1) {
Chris@767 321 s1.push_back({ RiseFallDTW::Direction::Up, v - prev1 });
Chris@767 322 } else {
Chris@767 323 s1.push_back({ RiseFallDTW::Direction::Down, prev1 - v });
Chris@767 324 }
Chris@767 325 prev1 = v;
Chris@767 326 }
Chris@767 327 events = toAlignOutputSTVM->getAllEvents();
Chris@767 328 for (auto e: events) {
Chris@767 329 double v = e.getValue();
Chris@767 330 //!!! as above
Chris@767 331 if (v > prev2) {
Chris@767 332 s2.push_back({ RiseFallDTW::Direction::Up, v - prev2 });
Chris@767 333 } else {
Chris@767 334 s2.push_back({ RiseFallDTW::Direction::Down, prev2 - v });
Chris@767 335 }
Chris@767 336 prev2 = v;
Chris@767 337 }
Chris@767 338 }
Chris@767 339
Chris@767 340 SVCERR << "TransformDTWAligner[" << this << "]: performAlignment: "
Chris@767 341 << "Have " << s1.size() << " events from reference, "
Chris@767 342 << s2.size() << " from toAlign" << endl;
Chris@767 343
Chris@767 344 RiseFallDTW dtw;
Chris@767 345
Chris@767 346 vector<size_t> alignment;
Chris@767 347
Chris@767 348 {
Chris@767 349 SVCERR << "TransformDTWAligner[" << this
Chris@767 350 << "]: serialising DTW to avoid over-allocation" << endl;
Chris@767 351 static QMutex mutex;
Chris@767 352 QMutexLocker locker(&mutex);
Chris@767 353
Chris@767 354 alignment = dtw.alignSeries(s1, s2);
Chris@767 355 }
Chris@767 356
Chris@767 357 SVCERR << "TransformDTWAligner[" << this << "]: performAlignment: "
Chris@767 358 << "DTW produced " << alignment.size() << " points:" << endl;
Chris@767 359 for (int i = 0; i < alignment.size() && i < 100; ++i) {
Chris@767 360 SVCERR << alignment[i] << " ";
Chris@767 361 }
Chris@767 362 SVCERR << endl;
Chris@767 363
Chris@767 364 auto alignmentProgressModel =
Chris@767 365 ModelById::getAs<SparseTimeValueModel>(m_alignmentProgressModel);
Chris@767 366 if (alignmentProgressModel) {
Chris@767 367 alignmentProgressModel->setCompletion(100);
Chris@767 368 }
Chris@767 369
Chris@767 370 // clear the alignment progress model
Chris@767 371 alignmentModel->setPathFrom(ModelId());
Chris@767 372
Chris@767 373 sv_frame_t resolution = referenceOutputSTVM->getResolution();
Chris@767 374 sv_frame_t sourceFrame = 0;
Chris@767 375
Chris@767 376 Path path(referenceOutputSTVM->getSampleRate(), resolution);
Chris@767 377
Chris@767 378 for (size_t m: alignment) {
Chris@767 379 path.add(PathPoint(sourceFrame, sv_frame_t(m) * resolution));
Chris@767 380 sourceFrame += resolution;
Chris@767 381 }
Chris@767 382
Chris@767 383 alignmentModel->setPath(path);
Chris@767 384
Chris@767 385 SVCERR << "TransformDTWAligner[" << this << "]: performAlignment: Done"
Chris@767 386 << endl;
Chris@767 387
Chris@767 388 m_incomplete = false;
Chris@767 389 return true;
Chris@767 390 }