annotate align/TransformAligner.cpp @ 771:1d6cca5a5621 pitch-align

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