annotate align/Align.cpp @ 785:e136dd3bb5c6

Permit setting the default alignment preference
author Chris Cannam
date Wed, 05 Aug 2020 16:05:51 +0100
parents b651dc5ff555
children
rev   line source
Chris@420 1 /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
Chris@420 2
Chris@420 3 /*
Chris@420 4 Sonic Visualiser
Chris@420 5 An audio file viewer and annotation editor.
Chris@420 6 Centre for Digital Music, Queen Mary, University of London.
Chris@420 7
Chris@420 8 This program is free software; you can redistribute it and/or
Chris@420 9 modify it under the terms of the GNU General Public License as
Chris@420 10 published by the Free Software Foundation; either version 2 of the
Chris@420 11 License, or (at your option) any later version. See the file
Chris@420 12 COPYING included with this distribution for more information.
Chris@420 13 */
Chris@420 14
Chris@420 15 #include "Align.h"
Chris@767 16
Chris@767 17 #include "LinearAligner.h"
Chris@777 18 #include "MATCHAligner.h"
Chris@767 19 #include "TransformDTWAligner.h"
Chris@753 20 #include "ExternalProgramAligner.h"
Chris@767 21
Chris@744 22 #include "framework/Document.h"
Chris@420 23
Chris@767 24 #include "transform/Transform.h"
Chris@767 25 #include "transform/TransformFactory.h"
Chris@767 26
Chris@768 27 #include "base/Pitch.h"
Chris@768 28
Chris@422 29 #include <QSettings>
Chris@761 30 #include <QTimer>
Chris@422 31
Chris@767 32 using std::make_shared;
Chris@767 33
Chris@767 34 QString
Chris@767 35 Align::getAlignmentTypeTag(AlignmentType type)
Chris@767 36 {
Chris@767 37 switch (type) {
Chris@767 38 case NoAlignment:
Chris@767 39 default:
Chris@767 40 return "no-alignment";
Chris@767 41 case LinearAlignment:
Chris@767 42 return "linear-alignment";
Chris@767 43 case TrimmedLinearAlignment:
Chris@767 44 return "trimmed-linear-alignment";
Chris@767 45 case MATCHAlignment:
Chris@767 46 return "match-alignment";
Chris@767 47 case MATCHAlignmentWithPitchCompare:
Chris@767 48 return "match-alignment-with-pitch";
Chris@771 49 case SungNoteContourAlignment:
Chris@771 50 return "sung-note-alignment";
Chris@767 51 case TransformDrivenDTWAlignment:
Chris@767 52 return "transform-driven-alignment";
Chris@767 53 case ExternalProgramAlignment:
Chris@767 54 return "external-program-alignment";
Chris@767 55 }
Chris@767 56 }
Chris@767 57
Chris@767 58 Align::AlignmentType
Chris@767 59 Align::getAlignmentTypeForTag(QString tag)
Chris@767 60 {
Chris@767 61 for (int i = 0; i <= int(LastAlignmentType); ++i) {
Chris@767 62 if (tag == getAlignmentTypeTag(AlignmentType(i))) {
Chris@767 63 return AlignmentType(i);
Chris@767 64 }
Chris@767 65 }
Chris@767 66 return NoAlignment;
Chris@767 67 }
Chris@767 68
Chris@761 69 void
Chris@753 70 Align::alignModel(Document *doc,
Chris@753 71 ModelId reference,
Chris@761 72 ModelId toAlign)
Chris@761 73 {
Chris@767 74 if (addAligner(doc, reference, toAlign)) {
Chris@767 75 m_aligners[toAlign]->begin();
Chris@767 76 }
Chris@761 77 }
Chris@761 78
Chris@761 79 void
Chris@761 80 Align::scheduleAlignment(Document *doc,
Chris@761 81 ModelId reference,
Chris@761 82 ModelId toAlign)
Chris@761 83 {
Chris@767 84 int delay = 700 * int(m_aligners.size());
Chris@761 85 if (delay > 3500) {
Chris@761 86 delay = 3500;
Chris@761 87 }
Chris@767 88 if (!addAligner(doc, reference, toAlign)) {
Chris@767 89 return;
Chris@767 90 }
Chris@761 91 SVCERR << "Align::scheduleAlignment: delaying " << delay << "ms" << endl;
Chris@761 92 QTimer::singleShot(delay, m_aligners[toAlign].get(), SLOT(begin()));
Chris@761 93 }
Chris@761 94
Chris@767 95 bool
Chris@761 96 Align::addAligner(Document *doc,
Chris@761 97 ModelId reference,
Chris@761 98 ModelId toAlign)
Chris@753 99 {
Chris@776 100 AlignmentType type = getAlignmentPreference();
Chris@753 101
Chris@753 102 std::shared_ptr<Aligner> aligner;
Chris@753 103
Chris@773 104 if (m_aligners.find(toAlign) != m_aligners.end()) {
Chris@773 105 // We don't want a callback on removeAligner to happen during
Chris@773 106 // our own call to addAligner! Disconnect and delete the old
Chris@773 107 // aligner first
Chris@773 108 disconnect(m_aligners[toAlign].get(), nullptr, this, nullptr);
Chris@773 109 m_aligners.erase(toAlign);
Chris@773 110 }
Chris@773 111
Chris@753 112 {
Chris@753 113 // Replace the aligner with a new one. This also stops any
Chris@753 114 // previously-running alignment, when the old entry is
Chris@753 115 // replaced and its aligner destroyed.
Chris@753 116
Chris@753 117 QMutexLocker locker(&m_mutex);
Chris@767 118
Chris@767 119 switch (type) {
Chris@767 120
Chris@767 121 case NoAlignment:
Chris@767 122 return false;
Chris@767 123
Chris@767 124 case LinearAlignment:
Chris@767 125 case TrimmedLinearAlignment: {
Chris@767 126 bool trimmed = (type == TrimmedLinearAlignment);
Chris@767 127 aligner = make_shared<LinearAligner>(doc,
Chris@767 128 reference,
Chris@767 129 toAlign,
Chris@767 130 trimmed);
Chris@767 131 break;
Chris@753 132 }
Chris@753 133
Chris@767 134 case MATCHAlignment:
Chris@767 135 case MATCHAlignmentWithPitchCompare: {
Chris@767 136
Chris@767 137 bool withTuningDifference =
Chris@767 138 (type == MATCHAlignmentWithPitchCompare);
Chris@767 139
Chris@777 140 aligner = make_shared<MATCHAligner>(doc,
Chris@777 141 reference,
Chris@777 142 toAlign,
Chris@781 143 getUseSubsequenceAlignment(),
Chris@777 144 withTuningDifference);
Chris@767 145 break;
Chris@767 146 }
Chris@767 147
Chris@771 148 case SungNoteContourAlignment:
Chris@767 149 {
Chris@767 150 auto refModel = ModelById::get(reference);
Chris@767 151 if (!refModel) return false;
Chris@771 152
Chris@767 153 Transform transform = TransformFactory::getInstance()->
Chris@771 154 getDefaultTransformFor("vamp:pyin:pyin:notes",
Chris@767 155 refModel->getSampleRate());
Chris@767 156
Chris@767 157 aligner = make_shared<TransformDTWAligner>
Chris@767 158 (doc,
Chris@767 159 reference,
Chris@767 160 toAlign,
Chris@781 161 getUseSubsequenceAlignment(),
Chris@767 162 transform,
Chris@771 163 [](double prev, double curr) {
Chris@771 164 RiseFallDTW::Value v;
Chris@771 165 if (curr <= 0.0) {
Chris@771 166 v = { RiseFallDTW::Direction::None, 0.0 };
Chris@771 167 } else if (prev <= 0.0) {
Chris@771 168 v = { RiseFallDTW::Direction::Up, 0.0 };
Chris@768 169 } else {
Chris@771 170 double prevP = Pitch::getPitchForFrequency(prev);
Chris@771 171 double currP = Pitch::getPitchForFrequency(curr);
Chris@771 172 if (currP >= prevP) {
Chris@771 173 v = { RiseFallDTW::Direction::Up, currP - prevP };
Chris@771 174 } else {
Chris@771 175 v = { RiseFallDTW::Direction::Down, prevP - currP };
Chris@771 176 }
Chris@768 177 }
Chris@771 178 return v;
Chris@768 179 });
Chris@767 180 break;
Chris@767 181 }
Chris@767 182
Chris@767 183 case TransformDrivenDTWAlignment:
Chris@767 184 throw std::logic_error("Not yet implemented"); //!!!
Chris@767 185
Chris@767 186 case ExternalProgramAlignment: {
Chris@776 187 aligner = make_shared<ExternalProgramAligner>
Chris@776 188 (doc,
Chris@776 189 reference,
Chris@776 190 toAlign,
Chris@776 191 getPreferredAlignmentProgram());
Chris@767 192 }
Chris@767 193 }
Chris@767 194
Chris@767 195 m_aligners[toAlign] = aligner;
Chris@753 196 }
Chris@753 197
Chris@753 198 connect(aligner.get(), SIGNAL(complete(ModelId)),
Chris@753 199 this, SLOT(alignerComplete(ModelId)));
Chris@761 200
Chris@761 201 connect(aligner.get(), SIGNAL(failed(ModelId, QString)),
Chris@761 202 this, SLOT(alignerFailed(ModelId, QString)));
Chris@767 203
Chris@767 204 return true;
Chris@767 205 }
Chris@767 206
Chris@767 207 Align::AlignmentType
Chris@776 208 Align::getAlignmentPreference()
Chris@767 209 {
Chris@767 210 QSettings settings;
Chris@767 211 settings.beginGroup("Alignment");
Chris@767 212 QString tag = settings.value
Chris@767 213 ("alignment-type", getAlignmentTypeTag(MATCHAlignment)).toString();
Chris@776 214 return getAlignmentTypeForTag(tag);
Chris@776 215 }
Chris@767 216
Chris@776 217 QString
Chris@776 218 Align::getPreferredAlignmentProgram()
Chris@776 219 {
Chris@776 220 QSettings settings;
Chris@776 221 settings.beginGroup("Alignment");
Chris@776 222 return settings.value("alignment-program", "").toString();
Chris@776 223 }
Chris@767 224
Chris@776 225 Transform
Chris@776 226 Align::getPreferredAlignmentTransform()
Chris@776 227 {
Chris@776 228 QSettings settings;
Chris@776 229 settings.beginGroup("Alignment");
Chris@776 230 QString xml = settings.value("alignment-transform", "").toString();
Chris@776 231 return Transform(xml);
Chris@753 232 }
Chris@753 233
Chris@781 234 bool
Chris@781 235 Align::getUseSubsequenceAlignment()
Chris@781 236 {
Chris@781 237 QSettings settings;
Chris@781 238 settings.beginGroup("Alignment");
Chris@781 239 return settings.value("alignment-subsequence", false).toBool();
Chris@781 240 }
Chris@781 241
Chris@753 242 void
Chris@776 243 Align::setAlignmentPreference(AlignmentType type)
Chris@422 244 {
Chris@422 245 QSettings settings;
Chris@767 246 settings.beginGroup("Alignment");
Chris@767 247 QString tag = getAlignmentTypeTag(type);
Chris@767 248 settings.setValue("alignment-type", tag);
Chris@776 249 settings.endGroup();
Chris@776 250 }
Chris@767 251
Chris@776 252 void
Chris@785 253 Align::setDefaultAlignmentPreference(AlignmentType type)
Chris@785 254 {
Chris@785 255 QSettings settings;
Chris@785 256 settings.beginGroup("Alignment");
Chris@785 257 if (!settings.contains("alignment-type")) {
Chris@785 258 QString tag = getAlignmentTypeTag(type);
Chris@785 259 settings.setValue("alignment-type", tag);
Chris@785 260 }
Chris@785 261 settings.endGroup();
Chris@785 262 }
Chris@785 263
Chris@785 264 void
Chris@776 265 Align::setPreferredAlignmentProgram(QString program)
Chris@776 266 {
Chris@776 267 QSettings settings;
Chris@776 268 settings.beginGroup("Alignment");
Chris@776 269 settings.setValue("alignment-program", program);
Chris@776 270 settings.endGroup();
Chris@776 271 }
Chris@767 272
Chris@776 273 void
Chris@776 274 Align::setPreferredAlignmentTransform(Transform transform)
Chris@776 275 {
Chris@776 276 QSettings settings;
Chris@776 277 settings.beginGroup("Alignment");
Chris@776 278 settings.setValue("alignment-transform", transform.toXmlString());
Chris@422 279 settings.endGroup();
Chris@670 280 }
Chris@670 281
Chris@781 282 void
Chris@781 283 Align::setUseSubsequenceAlignment(bool subsequence)
Chris@781 284 {
Chris@781 285 QSettings settings;
Chris@781 286 settings.beginGroup("Alignment");
Chris@781 287 settings.setValue("alignment-subsequence", subsequence);
Chris@781 288 settings.endGroup();
Chris@781 289 }
Chris@781 290
Chris@428 291 bool
Chris@428 292 Align::canAlign()
Chris@428 293 {
Chris@776 294 AlignmentType type = getAlignmentPreference();
Chris@753 295
Chris@767 296 if (type == ExternalProgramAlignment) {
Chris@776 297 return ExternalProgramAligner::isAvailable
Chris@776 298 (getPreferredAlignmentProgram());
Chris@753 299 } else {
Chris@777 300 return MATCHAligner::isAvailable();
Chris@753 301 }
Chris@428 302 }
Chris@428 303
Chris@702 304 void
Chris@753 305 Align::alignerComplete(ModelId alignmentModel)
Chris@702 306 {
Chris@761 307 removeAligner(sender());
Chris@761 308 emit alignmentComplete(alignmentModel);
Chris@761 309 }
Chris@761 310
Chris@761 311 void
Chris@761 312 Align::alignerFailed(ModelId toAlign, QString error)
Chris@761 313 {
Chris@761 314 removeAligner(sender());
Chris@761 315 emit alignmentFailed(toAlign, error);
Chris@761 316 }
Chris@761 317
Chris@761 318 void
Chris@761 319 Align::removeAligner(QObject *obj)
Chris@761 320 {
Chris@761 321 Aligner *aligner = qobject_cast<Aligner *>(obj);
Chris@753 322 if (!aligner) {
Chris@761 323 SVCERR << "ERROR: Align::removeAligner: Not an Aligner" << endl;
Chris@702 324 return;
Chris@702 325 }
Chris@702 326
Chris@761 327 QMutexLocker locker (&m_mutex);
Chris@702 328
Chris@761 329 for (auto p: m_aligners) {
Chris@761 330 if (aligner == p.second.get()) {
Chris@761 331 m_aligners.erase(p.first);
Chris@761 332 break;
Chris@702 333 }
Chris@702 334 }
Chris@761 335 }
Chris@702 336