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