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@753: #include "TransformAligner.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@767: case SungPitchContourAlignment: Chris@767: return "sung-pitch-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@767: QString additionalData; Chris@767: AlignmentType type = getAlignmentPreference(additionalData); Chris@753: Chris@753: std::shared_ptr aligner; Chris@753: 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@767: aligner = make_shared(doc, Chris@767: reference, Chris@767: toAlign, Chris@767: withTuningDifference); Chris@767: break; Chris@767: } Chris@767: Chris@767: case SungPitchContourAlignment: Chris@767: { Chris@767: auto refModel = ModelById::get(reference); Chris@767: if (!refModel) return false; Chris@767: Chris@767: Transform transform = TransformFactory::getInstance()-> Chris@767: getDefaultTransformFor("vamp:pyin:pyin:smoothedpitchtrack", Chris@767: refModel->getSampleRate()); Chris@767: Chris@767: transform.setParameter("outputunvoiced", 2.f); Chris@767: Chris@767: aligner = make_shared Chris@767: (doc, Chris@767: reference, Chris@767: toAlign, Chris@767: transform, Chris@768: TransformDTWAligner::RiseFall, Chris@768: [](double freq) { Chris@768: if (freq < 0.0) { Chris@768: return 0.0; Chris@768: } else { Chris@768: return double(Pitch::getPitchForFrequency(freq)); Chris@768: } 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@767: aligner = make_shared(doc, Chris@767: reference, Chris@767: toAlign, Chris@767: additionalData); 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@767: Align::getAlignmentPreference(QString &additionalData) Chris@767: { Chris@767: QSettings settings; Chris@767: settings.beginGroup("Alignment"); Chris@767: Chris@767: QString tag = settings.value Chris@767: ("alignment-type", getAlignmentTypeTag(MATCHAlignment)).toString(); Chris@767: Chris@767: AlignmentType type = getAlignmentTypeForTag(tag); Chris@767: Chris@767: if (type == TransformDrivenDTWAlignment) { Chris@767: additionalData = settings.value("alignment-transform", "").toString(); Chris@767: } else if (type == ExternalProgramAlignment) { Chris@767: additionalData = settings.value("alignment-program", "").toString(); Chris@767: } Chris@767: Chris@767: settings.endGroup(); Chris@767: return type; Chris@753: } Chris@753: Chris@753: void Chris@767: Align::setAlignmentPreference(AlignmentType type, QString additionalData) Chris@422: { Chris@422: QSettings settings; Chris@767: settings.beginGroup("Alignment"); Chris@767: Chris@767: QString tag = getAlignmentTypeTag(type); Chris@767: settings.setValue("alignment-type", tag); Chris@767: Chris@767: if (type == TransformDrivenDTWAlignment) { Chris@767: settings.setValue("alignment-transform", additionalData); Chris@767: } else if (type == ExternalProgramAlignment) { Chris@767: settings.setValue("alignment-program", additionalData); Chris@767: } Chris@767: Chris@422: settings.endGroup(); Chris@670: } Chris@670: Chris@428: bool Chris@428: Align::canAlign() Chris@428: { Chris@767: QString additionalData; Chris@767: AlignmentType type = getAlignmentPreference(additionalData); Chris@753: Chris@767: if (type == ExternalProgramAlignment) { Chris@767: return ExternalProgramAligner::isAvailable(additionalData); Chris@753: } else { Chris@753: return TransformAligner::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: