annotate align/TransformAligner.cpp @ 756:39808338e771 pitch-align

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