annotate align/Align.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 1b1960009be6
children 699b5b130ea2
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@753 18 #include "TransformAligner.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@767 100 QString additionalData;
Chris@767 101 AlignmentType type = getAlignmentPreference(additionalData);
Chris@753 102
Chris@753 103 std::shared_ptr<Aligner> aligner;
Chris@753 104
Chris@753 105 {
Chris@753 106 // Replace the aligner with a new one. This also stops any
Chris@753 107 // previously-running alignment, when the old entry is
Chris@753 108 // replaced and its aligner destroyed.
Chris@753 109
Chris@753 110 QMutexLocker locker(&m_mutex);
Chris@767 111
Chris@767 112 switch (type) {
Chris@767 113
Chris@767 114 case NoAlignment:
Chris@767 115 return false;
Chris@767 116
Chris@767 117 case LinearAlignment:
Chris@767 118 case TrimmedLinearAlignment: {
Chris@767 119 bool trimmed = (type == TrimmedLinearAlignment);
Chris@767 120 aligner = make_shared<LinearAligner>(doc,
Chris@767 121 reference,
Chris@767 122 toAlign,
Chris@767 123 trimmed);
Chris@767 124 break;
Chris@753 125 }
Chris@753 126
Chris@767 127 case MATCHAlignment:
Chris@767 128 case MATCHAlignmentWithPitchCompare: {
Chris@767 129
Chris@767 130 bool withTuningDifference =
Chris@767 131 (type == MATCHAlignmentWithPitchCompare);
Chris@767 132
Chris@767 133 aligner = make_shared<TransformAligner>(doc,
Chris@767 134 reference,
Chris@767 135 toAlign,
Chris@767 136 withTuningDifference);
Chris@767 137 break;
Chris@767 138 }
Chris@767 139
Chris@771 140 case SungNoteContourAlignment:
Chris@767 141 {
Chris@767 142 auto refModel = ModelById::get(reference);
Chris@767 143 if (!refModel) return false;
Chris@771 144
Chris@767 145 Transform transform = TransformFactory::getInstance()->
Chris@771 146 getDefaultTransformFor("vamp:pyin:pyin:notes",
Chris@767 147 refModel->getSampleRate());
Chris@767 148
Chris@767 149 aligner = make_shared<TransformDTWAligner>
Chris@767 150 (doc,
Chris@767 151 reference,
Chris@767 152 toAlign,
Chris@767 153 transform,
Chris@771 154 [](double prev, double curr) {
Chris@771 155 RiseFallDTW::Value v;
Chris@771 156 if (curr <= 0.0) {
Chris@771 157 v = { RiseFallDTW::Direction::None, 0.0 };
Chris@771 158 } else if (prev <= 0.0) {
Chris@771 159 v = { RiseFallDTW::Direction::Up, 0.0 };
Chris@768 160 } else {
Chris@771 161 double prevP = Pitch::getPitchForFrequency(prev);
Chris@771 162 double currP = Pitch::getPitchForFrequency(curr);
Chris@771 163 if (currP >= prevP) {
Chris@771 164 v = { RiseFallDTW::Direction::Up, currP - prevP };
Chris@771 165 } else {
Chris@771 166 v = { RiseFallDTW::Direction::Down, prevP - currP };
Chris@771 167 }
Chris@768 168 }
Chris@771 169 return v;
Chris@768 170 });
Chris@767 171 break;
Chris@767 172 }
Chris@767 173
Chris@767 174 case TransformDrivenDTWAlignment:
Chris@767 175 throw std::logic_error("Not yet implemented"); //!!!
Chris@767 176
Chris@767 177 case ExternalProgramAlignment: {
Chris@767 178 aligner = make_shared<ExternalProgramAligner>(doc,
Chris@767 179 reference,
Chris@767 180 toAlign,
Chris@767 181 additionalData);
Chris@767 182 }
Chris@767 183 }
Chris@767 184
Chris@767 185 m_aligners[toAlign] = aligner;
Chris@753 186 }
Chris@753 187
Chris@753 188 connect(aligner.get(), SIGNAL(complete(ModelId)),
Chris@753 189 this, SLOT(alignerComplete(ModelId)));
Chris@761 190
Chris@761 191 connect(aligner.get(), SIGNAL(failed(ModelId, QString)),
Chris@761 192 this, SLOT(alignerFailed(ModelId, QString)));
Chris@767 193
Chris@767 194 return true;
Chris@767 195 }
Chris@767 196
Chris@767 197 Align::AlignmentType
Chris@767 198 Align::getAlignmentPreference(QString &additionalData)
Chris@767 199 {
Chris@767 200 QSettings settings;
Chris@767 201 settings.beginGroup("Alignment");
Chris@767 202
Chris@767 203 QString tag = settings.value
Chris@767 204 ("alignment-type", getAlignmentTypeTag(MATCHAlignment)).toString();
Chris@767 205
Chris@767 206 AlignmentType type = getAlignmentTypeForTag(tag);
Chris@767 207
Chris@767 208 if (type == TransformDrivenDTWAlignment) {
Chris@767 209 additionalData = settings.value("alignment-transform", "").toString();
Chris@767 210 } else if (type == ExternalProgramAlignment) {
Chris@767 211 additionalData = settings.value("alignment-program", "").toString();
Chris@767 212 }
Chris@767 213
Chris@767 214 settings.endGroup();
Chris@767 215 return type;
Chris@753 216 }
Chris@753 217
Chris@753 218 void
Chris@767 219 Align::setAlignmentPreference(AlignmentType type, QString additionalData)
Chris@422 220 {
Chris@422 221 QSettings settings;
Chris@767 222 settings.beginGroup("Alignment");
Chris@767 223
Chris@767 224 QString tag = getAlignmentTypeTag(type);
Chris@767 225 settings.setValue("alignment-type", tag);
Chris@767 226
Chris@767 227 if (type == TransformDrivenDTWAlignment) {
Chris@767 228 settings.setValue("alignment-transform", additionalData);
Chris@767 229 } else if (type == ExternalProgramAlignment) {
Chris@767 230 settings.setValue("alignment-program", additionalData);
Chris@767 231 }
Chris@767 232
Chris@422 233 settings.endGroup();
Chris@670 234 }
Chris@670 235
Chris@428 236 bool
Chris@428 237 Align::canAlign()
Chris@428 238 {
Chris@767 239 QString additionalData;
Chris@767 240 AlignmentType type = getAlignmentPreference(additionalData);
Chris@753 241
Chris@767 242 if (type == ExternalProgramAlignment) {
Chris@767 243 return ExternalProgramAligner::isAvailable(additionalData);
Chris@753 244 } else {
Chris@753 245 return TransformAligner::isAvailable();
Chris@753 246 }
Chris@428 247 }
Chris@428 248
Chris@702 249 void
Chris@753 250 Align::alignerComplete(ModelId alignmentModel)
Chris@702 251 {
Chris@761 252 removeAligner(sender());
Chris@761 253 emit alignmentComplete(alignmentModel);
Chris@761 254 }
Chris@761 255
Chris@761 256 void
Chris@761 257 Align::alignerFailed(ModelId toAlign, QString error)
Chris@761 258 {
Chris@761 259 removeAligner(sender());
Chris@761 260 emit alignmentFailed(toAlign, error);
Chris@761 261 }
Chris@761 262
Chris@761 263 void
Chris@761 264 Align::removeAligner(QObject *obj)
Chris@761 265 {
Chris@761 266 Aligner *aligner = qobject_cast<Aligner *>(obj);
Chris@753 267 if (!aligner) {
Chris@761 268 SVCERR << "ERROR: Align::removeAligner: Not an Aligner" << endl;
Chris@702 269 return;
Chris@702 270 }
Chris@702 271
Chris@761 272 QMutexLocker locker (&m_mutex);
Chris@702 273
Chris@761 274 for (auto p: m_aligners) {
Chris@761 275 if (aligner == p.second.get()) {
Chris@761 276 m_aligners.erase(p.first);
Chris@761 277 break;
Chris@702 278 }
Chris@702 279 }
Chris@761 280 }
Chris@702 281