annotate align/MATCHAligner.cpp @ 786:1089d65c585d tip

Divert some debug output away from stderr
author Chris Cannam
date Fri, 14 Aug 2020 10:46:44 +0100
parents b651dc5ff555
children
rev   line source
Chris@752 1 /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
Chris@752 2
Chris@752 3 /*
Chris@752 4 Sonic Visualiser
Chris@752 5 An audio file viewer and annotation editor.
Chris@752 6 Centre for Digital Music, Queen Mary, University of London.
Chris@752 7
Chris@752 8 This program is free software; you can redistribute it and/or
Chris@752 9 modify it under the terms of the GNU General Public License as
Chris@752 10 published by the Free Software Foundation; either version 2 of the
Chris@752 11 License, or (at your option) any later version. See the file
Chris@752 12 COPYING included with this distribution for more information.
Chris@752 13 */
Chris@752 14
Chris@777 15 #include "MATCHAligner.h"
Chris@752 16
Chris@752 17 #include "data/model/SparseTimeValueModel.h"
Chris@752 18 #include "data/model/RangeSummarisableTimeValueModel.h"
Chris@752 19 #include "data/model/AlignmentModel.h"
Chris@752 20 #include "data/model/AggregateWaveModel.h"
Chris@752 21
Chris@752 22 #include "framework/Document.h"
Chris@752 23
Chris@752 24 #include "transform/TransformFactory.h"
Chris@752 25 #include "transform/ModelTransformerFactory.h"
Chris@752 26 #include "transform/FeatureExtractionModelTransformer.h"
Chris@752 27
Chris@752 28 #include <QSettings>
Chris@752 29
Chris@777 30 MATCHAligner::MATCHAligner(Document *doc,
Chris@777 31 ModelId reference,
Chris@777 32 ModelId toAlign,
Chris@781 33 bool subsequence,
Chris@777 34 bool withTuningDifference) :
Chris@752 35 m_document(doc),
Chris@752 36 m_reference(reference),
Chris@752 37 m_toAlign(toAlign),
Chris@781 38 m_subsequence(subsequence),
Chris@767 39 m_withTuningDifference(withTuningDifference),
Chris@752 40 m_tuningFrequency(440.f),
Chris@752 41 m_incomplete(true)
Chris@752 42 {
Chris@752 43 }
Chris@752 44
Chris@777 45 MATCHAligner::~MATCHAligner()
Chris@752 46 {
Chris@752 47 if (m_incomplete) {
Chris@752 48 auto other =
Chris@752 49 ModelById::getAs<RangeSummarisableTimeValueModel>(m_toAlign);
Chris@752 50 if (other) {
Chris@752 51 other->setAlignment({});
Chris@752 52 }
Chris@752 53 }
Chris@752 54
Chris@752 55 ModelById::release(m_tuningDiffOutputModel);
Chris@752 56 ModelById::release(m_pathOutputModel);
Chris@752 57 }
Chris@752 58
Chris@752 59 QString
Chris@781 60 MATCHAligner::getAlignmentTransformName(bool subsequence)
Chris@752 61 {
Chris@752 62 QSettings settings;
Chris@752 63 settings.beginGroup("Alignment");
Chris@781 64 TransformId id;
Chris@781 65 if (subsequence) {
Chris@781 66 id = settings.value
Chris@781 67 ("transform-id-subsequence",
Chris@781 68 "vamp:match-vamp-plugin:match-subsequence:path").toString();
Chris@781 69 } else {
Chris@781 70 id = settings.value
Chris@781 71 ("transform-id",
Chris@781 72 "vamp:match-vamp-plugin:match:path").toString();
Chris@781 73 }
Chris@752 74 settings.endGroup();
Chris@752 75 return id;
Chris@752 76 }
Chris@752 77
Chris@752 78 QString
Chris@777 79 MATCHAligner::getTuningDifferenceTransformName()
Chris@752 80 {
Chris@752 81 QSettings settings;
Chris@752 82 settings.beginGroup("Alignment");
Chris@767 83 TransformId id = settings.value
Chris@767 84 ("tuning-difference-transform-id",
Chris@767 85 "vamp:tuning-difference:tuning-difference:tuningfreq")
Chris@767 86 .toString();
Chris@752 87 settings.endGroup();
Chris@752 88 return id;
Chris@752 89 }
Chris@752 90
Chris@752 91 bool
Chris@777 92 MATCHAligner::isAvailable()
Chris@752 93 {
Chris@752 94 TransformFactory *factory = TransformFactory::getInstance();
Chris@781 95 TransformId id = getAlignmentTransformName(false);
Chris@781 96 TransformId subId = getAlignmentTransformName(true);
Chris@752 97 TransformId tdId = getTuningDifferenceTransformName();
Chris@752 98 return factory->haveTransform(id) &&
Chris@781 99 (subId == "" || factory->haveTransform(subId)) &&
Chris@752 100 (tdId == "" || factory->haveTransform(tdId));
Chris@752 101 }
Chris@752 102
Chris@761 103 void
Chris@777 104 MATCHAligner::begin()
Chris@752 105 {
Chris@752 106 auto reference =
Chris@752 107 ModelById::getAs<RangeSummarisableTimeValueModel>(m_reference);
Chris@752 108 auto other =
Chris@752 109 ModelById::getAs<RangeSummarisableTimeValueModel>(m_toAlign);
Chris@752 110
Chris@761 111 if (!reference || !other) return;
Chris@752 112
Chris@752 113 // This involves creating a number of new models:
Chris@752 114 //
Chris@752 115 // 1. an AggregateWaveModel to provide the mixdowns of the main
Chris@752 116 // model and the new model in its two channels, as input to the
Chris@752 117 // MATCH plugin. We just call this one aggregateModel
Chris@752 118 //
Chris@752 119 // 2a. a SparseTimeValueModel which will be automatically created
Chris@752 120 // by FeatureExtractionModelTransformer when running the
Chris@752 121 // TuningDifference plugin to receive the relative tuning of the
Chris@752 122 // second model (if pitch-aware alignment is enabled in the
Chris@752 123 // preferences). This is m_tuningDiffOutputModel.
Chris@752 124 //
Chris@752 125 // 2b. a SparseTimeValueModel which will be automatically created
Chris@752 126 // by FeatureExtractionPluginTransformer when running the MATCH
Chris@752 127 // plugin to perform alignment (so containing the alignment path).
Chris@752 128 // This is m_pathOutputModel.
Chris@752 129 //
Chris@752 130 // 3. an AlignmentModel, which stores the path and carries out
Chris@752 131 // alignment lookups on it. This one is m_alignmentModel.
Chris@752 132 //
Chris@752 133 // Models 1 and 3 are registered with the document, which will
Chris@752 134 // eventually release them. We don't release them here except in
Chris@752 135 // the case where an activity fails before the point where we
Chris@752 136 // would otherwise have registered them with the document.
Chris@752 137 //
Chris@768 138 // Models 2a (m_tuningDiffOutputModel) and 2b (m_pathOutputModel)
Chris@768 139 // are not registered with the document, because they are not
Chris@768 140 // intended to persist. These have to be released by us when
Chris@752 141 // finished with, but their lifespans do not extend beyond the end
Chris@752 142 // of the alignment procedure, so this should be ok.
Chris@752 143
Chris@752 144 AggregateWaveModel::ChannelSpecList components;
Chris@752 145 components.push_back
Chris@752 146 (AggregateWaveModel::ModelChannelSpec(m_reference, -1));
Chris@752 147
Chris@752 148 components.push_back
Chris@752 149 (AggregateWaveModel::ModelChannelSpec(m_toAlign, -1));
Chris@752 150
Chris@752 151 auto aggregateModel = std::make_shared<AggregateWaveModel>(components);
Chris@752 152 m_aggregateModel = ModelById::add(aggregateModel);
Chris@752 153 m_document->addNonDerivedModel(m_aggregateModel);
Chris@752 154
Chris@752 155 auto alignmentModel = std::make_shared<AlignmentModel>
Chris@752 156 (m_reference, m_toAlign, ModelId());
Chris@752 157 m_alignmentModel = ModelById::add(alignmentModel);
Chris@752 158
Chris@767 159 TransformId tdId;
Chris@767 160 if (m_withTuningDifference) {
Chris@767 161 tdId = getTuningDifferenceTransformName();
Chris@767 162 }
Chris@752 163
Chris@752 164 if (tdId == "") {
Chris@752 165
Chris@752 166 if (beginAlignmentPhase()) {
Chris@752 167 other->setAlignment(m_alignmentModel);
Chris@752 168 m_document->addNonDerivedModel(m_alignmentModel);
Chris@752 169 } else {
Chris@761 170 QString error = alignmentModel->getError();
Chris@752 171 ModelById::release(alignmentModel);
Chris@761 172 emit failed(m_toAlign, error);
Chris@761 173 return;
Chris@752 174 }
Chris@752 175
Chris@752 176 } else {
Chris@752 177
Chris@752 178 // Have a tuning-difference transform id, so run it
Chris@752 179 // asynchronously first
Chris@752 180
Chris@752 181 TransformFactory *tf = TransformFactory::getInstance();
Chris@752 182
Chris@752 183 Transform transform = tf->getDefaultTransformFor
Chris@752 184 (tdId, aggregateModel->getSampleRate());
Chris@752 185
Chris@752 186 transform.setParameter("maxduration", 60);
Chris@752 187 transform.setParameter("maxrange", 6);
Chris@752 188 transform.setParameter("finetuning", false);
Chris@752 189
Chris@777 190 SVDEBUG << "MATCHAligner: Tuning difference transform step size " << transform.getStepSize() << ", block size " << transform.getBlockSize() << endl;
Chris@752 191
Chris@752 192 ModelTransformerFactory *mtf = ModelTransformerFactory::getInstance();
Chris@752 193
Chris@752 194 QString message;
Chris@753 195 m_tuningDiffOutputModel = mtf->transform(transform,
Chris@753 196 m_aggregateModel,
Chris@753 197 message);
Chris@752 198
Chris@752 199 auto tuningDiffOutputModel =
Chris@753 200 ModelById::getAs<SparseTimeValueModel>(m_tuningDiffOutputModel);
Chris@752 201 if (!tuningDiffOutputModel) {
Chris@752 202 SVCERR << "Align::alignModel: ERROR: Failed to create tuning-difference output model (no Tuning Difference plugin?)" << endl;
Chris@752 203 ModelById::release(alignmentModel);
Chris@761 204 emit failed(m_toAlign, message);
Chris@761 205 return;
Chris@752 206 }
Chris@752 207
Chris@752 208 other->setAlignment(m_alignmentModel);
Chris@752 209 m_document->addNonDerivedModel(m_alignmentModel);
Chris@752 210
Chris@752 211 connect(tuningDiffOutputModel.get(),
Chris@752 212 SIGNAL(completionChanged(ModelId)),
Chris@752 213 this, SLOT(tuningDifferenceCompletionChanged(ModelId)));
Chris@752 214 }
Chris@752 215 }
Chris@752 216
Chris@752 217 void
Chris@777 218 MATCHAligner::tuningDifferenceCompletionChanged(ModelId tuningDiffOutputModelId)
Chris@752 219 {
Chris@761 220 if (m_tuningDiffOutputModel.isNone()) {
Chris@761 221 // we're done, this is probably a spurious queued event
Chris@761 222 return;
Chris@761 223 }
Chris@761 224
Chris@752 225 if (tuningDiffOutputModelId != m_tuningDiffOutputModel) {
Chris@777 226 SVCERR << "WARNING: MATCHAligner::tuningDifferenceCompletionChanged: Model "
Chris@753 227 << tuningDiffOutputModelId
Chris@753 228 << " is not ours! (ours is "
Chris@753 229 << m_tuningDiffOutputModel << ")" << endl;
Chris@752 230 return;
Chris@752 231 }
Chris@752 232
Chris@752 233 auto tuningDiffOutputModel =
Chris@752 234 ModelById::getAs<SparseTimeValueModel>(m_tuningDiffOutputModel);
Chris@752 235 if (!tuningDiffOutputModel) {
Chris@777 236 SVCERR << "WARNING: MATCHAligner::tuningDifferenceCompletionChanged: Model "
Chris@752 237 << tuningDiffOutputModelId
Chris@752 238 << " not known as SparseTimeValueModel" << endl;
Chris@752 239 return;
Chris@752 240 }
Chris@752 241
Chris@752 242 auto alignmentModel = ModelById::getAs<AlignmentModel>(m_alignmentModel);
Chris@752 243 if (!alignmentModel) {
Chris@777 244 SVCERR << "WARNING: MATCHAligner::tuningDifferenceCompletionChanged:"
Chris@752 245 << "alignment model has disappeared" << endl;
Chris@752 246 return;
Chris@752 247 }
Chris@752 248
Chris@752 249 int completion = 0;
Chris@752 250 bool done = tuningDiffOutputModel->isReady(&completion);
Chris@752 251
Chris@777 252 SVDEBUG << "MATCHAligner::tuningDifferenceCompletionChanged: model "
Chris@761 253 << m_tuningDiffOutputModel << ", completion = " << completion
Chris@761 254 << ", done = " << done << endl;
Chris@761 255
Chris@752 256 if (!done) {
Chris@752 257 // This will be the completion the alignment model reports,
Chris@769 258 // before the alignment actually begins
Chris@769 259 alignmentModel->setCompletion(completion / 2);
Chris@752 260 return;
Chris@752 261 }
Chris@752 262
Chris@752 263 m_tuningFrequency = 440.f;
Chris@752 264
Chris@752 265 if (!tuningDiffOutputModel->isEmpty()) {
Chris@752 266 m_tuningFrequency = tuningDiffOutputModel->getAllEvents()[0].getValue();
Chris@777 267 SVCERR << "MATCHAligner::tuningDifferenceCompletionChanged: Reported tuning frequency = " << m_tuningFrequency << endl;
Chris@752 268 } else {
Chris@777 269 SVCERR << "MATCHAligner::tuningDifferenceCompletionChanged: No tuning frequency reported" << endl;
Chris@752 270 }
Chris@752 271
Chris@752 272 ModelById::release(tuningDiffOutputModel);
Chris@752 273 m_tuningDiffOutputModel = {};
Chris@752 274
Chris@752 275 beginAlignmentPhase();
Chris@752 276 }
Chris@752 277
Chris@752 278 bool
Chris@777 279 MATCHAligner::beginAlignmentPhase()
Chris@752 280 {
Chris@781 281 TransformId id = getAlignmentTransformName(m_subsequence);
Chris@752 282
Chris@777 283 SVDEBUG << "MATCHAligner::beginAlignmentPhase: transform is "
Chris@753 284 << id << endl;
Chris@753 285
Chris@752 286 TransformFactory *tf = TransformFactory::getInstance();
Chris@752 287
Chris@752 288 auto aggregateModel =
Chris@752 289 ModelById::getAs<AggregateWaveModel>(m_aggregateModel);
Chris@752 290 auto alignmentModel =
Chris@752 291 ModelById::getAs<AlignmentModel>(m_alignmentModel);
Chris@752 292
Chris@752 293 if (!aggregateModel || !alignmentModel) {
Chris@777 294 SVCERR << "MATCHAligner::alignModel: ERROR: One or other of the aggregate & alignment models has disappeared" << endl;
Chris@752 295 return false;
Chris@752 296 }
Chris@752 297
Chris@752 298 Transform transform = tf->getDefaultTransformFor
Chris@752 299 (id, aggregateModel->getSampleRate());
Chris@752 300
Chris@752 301 transform.setStepSize(transform.getBlockSize()/2);
Chris@752 302 transform.setParameter("serialise", 1);
Chris@752 303 transform.setParameter("smooth", 0);
Chris@752 304 transform.setParameter("zonewidth", 40);
Chris@752 305 transform.setParameter("noise", true);
Chris@752 306 transform.setParameter("minfreq", 500);
Chris@752 307
Chris@752 308 int cents = 0;
Chris@752 309
Chris@752 310 if (m_tuningFrequency != 0.f) {
Chris@752 311 transform.setParameter("freq2", m_tuningFrequency);
Chris@752 312
Chris@752 313 double centsOffset = 0.f;
Chris@752 314 int pitch = Pitch::getPitchForFrequency(m_tuningFrequency,
Chris@752 315 &centsOffset);
Chris@752 316 cents = int(round((pitch - 69) * 100 + centsOffset));
Chris@777 317 SVCERR << "MATCHAligner: frequency " << m_tuningFrequency
Chris@752 318 << " yields cents offset " << centsOffset
Chris@752 319 << " and pitch " << pitch << " -> cents " << cents << endl;
Chris@752 320 }
Chris@752 321
Chris@752 322 alignmentModel->setRelativePitch(cents);
Chris@752 323
Chris@777 324 SVDEBUG << "MATCHAligner: Alignment transform step size "
Chris@752 325 << transform.getStepSize() << ", block size "
Chris@752 326 << transform.getBlockSize() << endl;
Chris@752 327
Chris@752 328 ModelTransformerFactory *mtf = ModelTransformerFactory::getInstance();
Chris@752 329
Chris@752 330 QString message;
Chris@753 331 m_pathOutputModel = mtf->transform
Chris@752 332 (transform, m_aggregateModel, message);
Chris@752 333
Chris@753 334 if (m_pathOutputModel.isNone()) {
Chris@752 335 transform.setStepSize(0);
Chris@753 336 m_pathOutputModel = mtf->transform
Chris@752 337 (transform, m_aggregateModel, message);
Chris@752 338 }
Chris@752 339
Chris@752 340 auto pathOutputModel =
Chris@753 341 ModelById::getAs<SparseTimeValueModel>(m_pathOutputModel);
Chris@752 342
Chris@752 343 //!!! callers will need to be updated to get error from
Chris@752 344 //!!! alignment model after initial call
Chris@752 345
Chris@752 346 if (!pathOutputModel) {
Chris@777 347 SVCERR << "MATCHAligner: ERROR: Failed to create alignment path (no MATCH plugin?)" << endl;
Chris@752 348 alignmentModel->setError(message);
Chris@752 349 return false;
Chris@752 350 }
Chris@752 351
Chris@752 352 pathOutputModel->setCompletion(0);
Chris@752 353 alignmentModel->setPathFrom(m_pathOutputModel);
Chris@752 354
Chris@769 355 connect(pathOutputModel.get(), SIGNAL(completionChanged(ModelId)),
Chris@752 356 this, SLOT(alignmentCompletionChanged(ModelId)));
Chris@752 357
Chris@752 358 return true;
Chris@752 359 }
Chris@752 360
Chris@752 361 void
Chris@777 362 MATCHAligner::alignmentCompletionChanged(ModelId pathOutputModelId)
Chris@752 363 {
Chris@769 364 if (pathOutputModelId != m_pathOutputModel) {
Chris@777 365 SVCERR << "WARNING: MATCHAligner::alignmentCompletionChanged: Model "
Chris@769 366 << pathOutputModelId
Chris@753 367 << " is not ours! (ours is "
Chris@769 368 << m_pathOutputModel << ")" << endl;
Chris@752 369 return;
Chris@752 370 }
Chris@752 371
Chris@769 372 auto pathOutputModel =
Chris@769 373 ModelById::getAs<SparseTimeValueModel>(m_pathOutputModel);
Chris@769 374 if (!pathOutputModel) {
Chris@777 375 SVCERR << "WARNING: MATCHAligner::alignmentCompletionChanged: Path output model "
Chris@769 376 << m_pathOutputModel << " no longer exists" << endl;
Chris@769 377 return;
Chris@769 378 }
Chris@769 379
Chris@769 380 int completion = 0;
Chris@769 381 bool done = pathOutputModel->isReady(&completion);
Chris@752 382
Chris@769 383 if (m_withTuningDifference) {
Chris@769 384 if (auto alignmentModel =
Chris@769 385 ModelById::getAs<AlignmentModel>(m_alignmentModel)) {
Chris@769 386 if (!done) {
Chris@769 387 int adjustedCompletion = 50 + completion/2;
Chris@769 388 if (adjustedCompletion > 99) {
Chris@769 389 adjustedCompletion = 99;
Chris@769 390 }
Chris@769 391 alignmentModel->setCompletion(adjustedCompletion);
Chris@769 392 } else {
Chris@769 393 alignmentModel->setCompletion(100);
Chris@769 394 }
Chris@769 395 }
Chris@769 396 }
Chris@752 397
Chris@769 398 if (done) {
Chris@752 399 m_incomplete = false;
Chris@752 400
Chris@752 401 ModelById::release(m_pathOutputModel);
Chris@752 402 m_pathOutputModel = {};
Chris@752 403
Chris@752 404 emit complete(m_alignmentModel);
Chris@752 405 }
Chris@752 406 }