Chris@420: /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ Chris@420: Chris@420: /* Chris@420: Sonic Visualiser Chris@420: An audio file viewer and annotation editor. Chris@420: Centre for Digital Music, Queen Mary, University of London. Chris@420: Chris@420: This program is free software; you can redistribute it and/or Chris@420: modify it under the terms of the GNU General Public License as Chris@420: published by the Free Software Foundation; either version 2 of the Chris@420: License, or (at your option) any later version. See the file Chris@420: COPYING included with this distribution for more information. Chris@420: */ Chris@420: Chris@420: #include "Align.h" Chris@767: Chris@767: #include "LinearAligner.h" Chris@777: #include "MATCHAligner.h" Chris@767: #include "TransformDTWAligner.h" Chris@753: #include "ExternalProgramAligner.h" Chris@767: Chris@744: #include "framework/Document.h" Chris@420: Chris@767: #include "transform/Transform.h" Chris@767: #include "transform/TransformFactory.h" Chris@767: Chris@768: #include "base/Pitch.h" Chris@768: Chris@422: #include Chris@761: #include Chris@422: Chris@767: using std::make_shared; Chris@767: Chris@767: QString Chris@767: Align::getAlignmentTypeTag(AlignmentType type) Chris@767: { Chris@767: switch (type) { Chris@767: case NoAlignment: Chris@767: default: Chris@767: return "no-alignment"; Chris@767: case LinearAlignment: Chris@767: return "linear-alignment"; Chris@767: case TrimmedLinearAlignment: Chris@767: return "trimmed-linear-alignment"; Chris@767: case MATCHAlignment: Chris@767: return "match-alignment"; Chris@767: case MATCHAlignmentWithPitchCompare: Chris@767: return "match-alignment-with-pitch"; Chris@771: case SungNoteContourAlignment: Chris@771: return "sung-note-alignment"; Chris@767: case TransformDrivenDTWAlignment: Chris@767: return "transform-driven-alignment"; Chris@767: case ExternalProgramAlignment: Chris@767: return "external-program-alignment"; Chris@767: } Chris@767: } Chris@767: Chris@767: Align::AlignmentType Chris@767: Align::getAlignmentTypeForTag(QString tag) Chris@767: { Chris@767: for (int i = 0; i <= int(LastAlignmentType); ++i) { Chris@767: if (tag == getAlignmentTypeTag(AlignmentType(i))) { Chris@767: return AlignmentType(i); Chris@767: } Chris@767: } Chris@767: return NoAlignment; Chris@767: } Chris@767: Chris@761: void Chris@753: Align::alignModel(Document *doc, Chris@753: ModelId reference, Chris@761: ModelId toAlign) Chris@761: { Chris@767: if (addAligner(doc, reference, toAlign)) { Chris@767: m_aligners[toAlign]->begin(); Chris@767: } Chris@761: } Chris@761: Chris@761: void Chris@761: Align::scheduleAlignment(Document *doc, Chris@761: ModelId reference, Chris@761: ModelId toAlign) Chris@761: { Chris@767: int delay = 700 * int(m_aligners.size()); Chris@761: if (delay > 3500) { Chris@761: delay = 3500; Chris@761: } Chris@767: if (!addAligner(doc, reference, toAlign)) { Chris@767: return; Chris@767: } Chris@761: SVCERR << "Align::scheduleAlignment: delaying " << delay << "ms" << endl; Chris@761: QTimer::singleShot(delay, m_aligners[toAlign].get(), SLOT(begin())); Chris@761: } Chris@761: Chris@767: bool Chris@761: Align::addAligner(Document *doc, Chris@761: ModelId reference, Chris@761: ModelId toAlign) Chris@753: { Chris@776: AlignmentType type = getAlignmentPreference(); Chris@753: Chris@753: std::shared_ptr aligner; Chris@753: Chris@773: if (m_aligners.find(toAlign) != m_aligners.end()) { Chris@773: // We don't want a callback on removeAligner to happen during Chris@773: // our own call to addAligner! Disconnect and delete the old Chris@773: // aligner first Chris@773: disconnect(m_aligners[toAlign].get(), nullptr, this, nullptr); Chris@773: m_aligners.erase(toAlign); Chris@773: } Chris@773: Chris@753: { Chris@753: // Replace the aligner with a new one. This also stops any Chris@753: // previously-running alignment, when the old entry is Chris@753: // replaced and its aligner destroyed. Chris@753: Chris@753: QMutexLocker locker(&m_mutex); Chris@767: Chris@767: switch (type) { Chris@767: Chris@767: case NoAlignment: Chris@767: return false; Chris@767: Chris@767: case LinearAlignment: Chris@767: case TrimmedLinearAlignment: { Chris@767: bool trimmed = (type == TrimmedLinearAlignment); Chris@767: aligner = make_shared(doc, Chris@767: reference, Chris@767: toAlign, Chris@767: trimmed); Chris@767: break; Chris@753: } Chris@753: Chris@767: case MATCHAlignment: Chris@767: case MATCHAlignmentWithPitchCompare: { Chris@767: Chris@767: bool withTuningDifference = Chris@767: (type == MATCHAlignmentWithPitchCompare); Chris@767: Chris@777: aligner = make_shared(doc, Chris@777: reference, Chris@777: toAlign, Chris@781: getUseSubsequenceAlignment(), Chris@777: withTuningDifference); Chris@767: break; Chris@767: } Chris@767: Chris@771: case SungNoteContourAlignment: Chris@767: { Chris@767: auto refModel = ModelById::get(reference); Chris@767: if (!refModel) return false; Chris@771: Chris@767: Transform transform = TransformFactory::getInstance()-> Chris@771: getDefaultTransformFor("vamp:pyin:pyin:notes", Chris@767: refModel->getSampleRate()); Chris@767: Chris@767: aligner = make_shared Chris@767: (doc, Chris@767: reference, Chris@767: toAlign, Chris@781: getUseSubsequenceAlignment(), Chris@767: transform, Chris@771: [](double prev, double curr) { Chris@771: RiseFallDTW::Value v; Chris@771: if (curr <= 0.0) { Chris@771: v = { RiseFallDTW::Direction::None, 0.0 }; Chris@771: } else if (prev <= 0.0) { Chris@771: v = { RiseFallDTW::Direction::Up, 0.0 }; Chris@768: } else { Chris@771: double prevP = Pitch::getPitchForFrequency(prev); Chris@771: double currP = Pitch::getPitchForFrequency(curr); Chris@771: if (currP >= prevP) { Chris@771: v = { RiseFallDTW::Direction::Up, currP - prevP }; Chris@771: } else { Chris@771: v = { RiseFallDTW::Direction::Down, prevP - currP }; Chris@771: } Chris@768: } Chris@771: return v; Chris@768: }); Chris@767: break; Chris@767: } Chris@767: Chris@767: case TransformDrivenDTWAlignment: Chris@767: throw std::logic_error("Not yet implemented"); //!!! Chris@767: Chris@767: case ExternalProgramAlignment: { Chris@776: aligner = make_shared Chris@776: (doc, Chris@776: reference, Chris@776: toAlign, Chris@776: getPreferredAlignmentProgram()); Chris@767: } Chris@767: } Chris@767: Chris@767: m_aligners[toAlign] = aligner; Chris@753: } Chris@753: Chris@753: connect(aligner.get(), SIGNAL(complete(ModelId)), Chris@753: this, SLOT(alignerComplete(ModelId))); Chris@761: Chris@761: connect(aligner.get(), SIGNAL(failed(ModelId, QString)), Chris@761: this, SLOT(alignerFailed(ModelId, QString))); Chris@767: Chris@767: return true; Chris@767: } Chris@767: Chris@767: Align::AlignmentType Chris@776: Align::getAlignmentPreference() Chris@767: { Chris@767: QSettings settings; Chris@767: settings.beginGroup("Alignment"); Chris@767: QString tag = settings.value Chris@767: ("alignment-type", getAlignmentTypeTag(MATCHAlignment)).toString(); Chris@776: return getAlignmentTypeForTag(tag); Chris@776: } Chris@767: Chris@776: QString Chris@776: Align::getPreferredAlignmentProgram() Chris@776: { Chris@776: QSettings settings; Chris@776: settings.beginGroup("Alignment"); Chris@776: return settings.value("alignment-program", "").toString(); Chris@776: } Chris@767: Chris@776: Transform Chris@776: Align::getPreferredAlignmentTransform() Chris@776: { Chris@776: QSettings settings; Chris@776: settings.beginGroup("Alignment"); Chris@776: QString xml = settings.value("alignment-transform", "").toString(); Chris@776: return Transform(xml); Chris@753: } Chris@753: Chris@781: bool Chris@781: Align::getUseSubsequenceAlignment() Chris@781: { Chris@781: QSettings settings; Chris@781: settings.beginGroup("Alignment"); Chris@781: return settings.value("alignment-subsequence", false).toBool(); Chris@781: } Chris@781: Chris@753: void Chris@776: Align::setAlignmentPreference(AlignmentType type) Chris@422: { Chris@422: QSettings settings; Chris@767: settings.beginGroup("Alignment"); Chris@767: QString tag = getAlignmentTypeTag(type); Chris@767: settings.setValue("alignment-type", tag); Chris@776: settings.endGroup(); Chris@776: } Chris@767: Chris@776: void Chris@776: Align::setPreferredAlignmentProgram(QString program) Chris@776: { Chris@776: QSettings settings; Chris@776: settings.beginGroup("Alignment"); Chris@776: settings.setValue("alignment-program", program); Chris@776: settings.endGroup(); Chris@776: } Chris@767: Chris@776: void Chris@776: Align::setPreferredAlignmentTransform(Transform transform) Chris@776: { Chris@776: QSettings settings; Chris@776: settings.beginGroup("Alignment"); Chris@776: settings.setValue("alignment-transform", transform.toXmlString()); Chris@422: settings.endGroup(); Chris@670: } Chris@670: Chris@781: void Chris@781: Align::setUseSubsequenceAlignment(bool subsequence) Chris@781: { Chris@781: QSettings settings; Chris@781: settings.beginGroup("Alignment"); Chris@781: settings.setValue("alignment-subsequence", subsequence); Chris@781: settings.endGroup(); Chris@781: } Chris@781: Chris@428: bool Chris@428: Align::canAlign() Chris@428: { Chris@776: AlignmentType type = getAlignmentPreference(); Chris@753: Chris@767: if (type == ExternalProgramAlignment) { Chris@776: return ExternalProgramAligner::isAvailable Chris@776: (getPreferredAlignmentProgram()); Chris@753: } else { Chris@777: return MATCHAligner::isAvailable(); Chris@753: } Chris@428: } Chris@428: Chris@702: void Chris@753: Align::alignerComplete(ModelId alignmentModel) Chris@702: { Chris@761: removeAligner(sender()); Chris@761: emit alignmentComplete(alignmentModel); Chris@761: } Chris@761: Chris@761: void Chris@761: Align::alignerFailed(ModelId toAlign, QString error) Chris@761: { Chris@761: removeAligner(sender()); Chris@761: emit alignmentFailed(toAlign, error); Chris@761: } Chris@761: Chris@761: void Chris@761: Align::removeAligner(QObject *obj) Chris@761: { Chris@761: Aligner *aligner = qobject_cast(obj); Chris@753: if (!aligner) { Chris@761: SVCERR << "ERROR: Align::removeAligner: Not an Aligner" << endl; Chris@702: return; Chris@702: } Chris@702: Chris@761: QMutexLocker locker (&m_mutex); Chris@702: Chris@761: for (auto p: m_aligners) { Chris@761: if (aligner == p.second.get()) { Chris@761: m_aligners.erase(p.first); Chris@761: break; Chris@702: } Chris@702: } Chris@761: } Chris@702: