Mercurial > hg > svapp
changeset 767:dd742e566e60 pitch-align
Make a start on further alignment methods
author | Chris Cannam |
---|---|
date | Thu, 21 May 2020 16:21:57 +0100 |
parents | 6429a164b7e1 |
children | 1b1960009be6 |
files | align/Align.cpp align/Align.h align/LinearAligner.cpp align/LinearAligner.h align/TransformAligner.cpp align/TransformAligner.h align/TransformDTWAligner.cpp align/TransformDTWAligner.h files.pri |
diffstat | 9 files changed, 802 insertions(+), 48 deletions(-) [+] |
line wrap: on
line diff
--- a/align/Align.cpp Wed May 06 11:45:27 2020 +0100 +++ b/align/Align.cpp Thu May 21 16:21:57 2020 +0100 @@ -13,20 +13,65 @@ */ #include "Align.h" + +#include "LinearAligner.h" #include "TransformAligner.h" +#include "TransformDTWAligner.h" #include "ExternalProgramAligner.h" + #include "framework/Document.h" +#include "transform/Transform.h" +#include "transform/TransformFactory.h" + #include <QSettings> #include <QTimer> +using std::make_shared; + +QString +Align::getAlignmentTypeTag(AlignmentType type) +{ + switch (type) { + case NoAlignment: + default: + return "no-alignment"; + case LinearAlignment: + return "linear-alignment"; + case TrimmedLinearAlignment: + return "trimmed-linear-alignment"; + case MATCHAlignment: + return "match-alignment"; + case MATCHAlignmentWithPitchCompare: + return "match-alignment-with-pitch"; + case SungPitchContourAlignment: + return "sung-pitch-alignment"; + case TransformDrivenDTWAlignment: + return "transform-driven-alignment"; + case ExternalProgramAlignment: + return "external-program-alignment"; + } +} + +Align::AlignmentType +Align::getAlignmentTypeForTag(QString tag) +{ + for (int i = 0; i <= int(LastAlignmentType); ++i) { + if (tag == getAlignmentTypeTag(AlignmentType(i))) { + return AlignmentType(i); + } + } + return NoAlignment; +} + void Align::alignModel(Document *doc, ModelId reference, ModelId toAlign) { - addAligner(doc, reference, toAlign); - m_aligners[toAlign]->begin(); + if (addAligner(doc, reference, toAlign)) { + m_aligners[toAlign]->begin(); + } } void @@ -34,23 +79,24 @@ ModelId reference, ModelId toAlign) { - addAligner(doc, reference, toAlign); - int delay = 500 + 500 * int(m_aligners.size()); + int delay = 700 * int(m_aligners.size()); if (delay > 3500) { delay = 3500; } + if (!addAligner(doc, reference, toAlign)) { + return; + } SVCERR << "Align::scheduleAlignment: delaying " << delay << "ms" << endl; QTimer::singleShot(delay, m_aligners[toAlign].get(), SLOT(begin())); } -void +bool Align::addAligner(Document *doc, ModelId reference, ModelId toAlign) { - bool useProgram; - QString program; - getAlignerPreference(useProgram, program); + QString additionalData; + AlignmentType type = getAlignmentPreference(additionalData); std::shared_ptr<Aligner> aligner; @@ -60,21 +106,67 @@ // replaced and its aligner destroyed. QMutexLocker locker(&m_mutex); - - if (useProgram && (program != "")) { - m_aligners[toAlign] = - std::make_shared<ExternalProgramAligner>(doc, - reference, - toAlign, - program); - } else { - m_aligners[toAlign] = - std::make_shared<TransformAligner>(doc, - reference, - toAlign); + + switch (type) { + + case NoAlignment: + return false; + + case LinearAlignment: + case TrimmedLinearAlignment: { + bool trimmed = (type == TrimmedLinearAlignment); + aligner = make_shared<LinearAligner>(doc, + reference, + toAlign, + trimmed); + break; } - aligner = m_aligners[toAlign]; + case MATCHAlignment: + case MATCHAlignmentWithPitchCompare: { + + bool withTuningDifference = + (type == MATCHAlignmentWithPitchCompare); + + aligner = make_shared<TransformAligner>(doc, + reference, + toAlign, + withTuningDifference); + break; + } + + case SungPitchContourAlignment: + { + auto refModel = ModelById::get(reference); + if (!refModel) return false; + + Transform transform = TransformFactory::getInstance()-> + getDefaultTransformFor("vamp:pyin:pyin:smoothedpitchtrack", + refModel->getSampleRate()); + + transform.setParameter("outputunvoiced", 2.f); + + aligner = make_shared<TransformDTWAligner> + (doc, + reference, + toAlign, + transform, + TransformDTWAligner::RiseFall); + break; + } + + case TransformDrivenDTWAlignment: + throw std::logic_error("Not yet implemented"); //!!! + + case ExternalProgramAlignment: { + aligner = make_shared<ExternalProgramAligner>(doc, + reference, + toAlign, + additionalData); + } + } + + m_aligners[toAlign] = aligner; } connect(aligner.get(), SIGNAL(complete(ModelId)), @@ -82,27 +174,57 @@ connect(aligner.get(), SIGNAL(failed(ModelId, QString)), this, SLOT(alignerFailed(ModelId, QString))); + + return true; +} + +Align::AlignmentType +Align::getAlignmentPreference(QString &additionalData) +{ + QSettings settings; + settings.beginGroup("Alignment"); + + QString tag = settings.value + ("alignment-type", getAlignmentTypeTag(MATCHAlignment)).toString(); + + AlignmentType type = getAlignmentTypeForTag(tag); + + if (type == TransformDrivenDTWAlignment) { + additionalData = settings.value("alignment-transform", "").toString(); + } else if (type == ExternalProgramAlignment) { + additionalData = settings.value("alignment-program", "").toString(); + } + + settings.endGroup(); + return type; } void -Align::getAlignerPreference(bool &useProgram, QString &program) +Align::setAlignmentPreference(AlignmentType type, QString additionalData) { QSettings settings; - settings.beginGroup("Preferences"); - useProgram = settings.value("use-external-alignment", false).toBool(); - program = settings.value("external-alignment-program", "").toString(); + settings.beginGroup("Alignment"); + + QString tag = getAlignmentTypeTag(type); + settings.setValue("alignment-type", tag); + + if (type == TransformDrivenDTWAlignment) { + settings.setValue("alignment-transform", additionalData); + } else if (type == ExternalProgramAlignment) { + settings.setValue("alignment-program", additionalData); + } + settings.endGroup(); } bool Align::canAlign() { - bool useProgram; - QString program; - getAlignerPreference(useProgram, program); + QString additionalData; + AlignmentType type = getAlignmentPreference(additionalData); - if (useProgram) { - return ExternalProgramAligner::isAvailable(program); + if (type == ExternalProgramAlignment) { + return ExternalProgramAligner::isAvailable(additionalData); } else { return TransformAligner::isAvailable(); }
--- a/align/Align.h Wed May 06 11:45:27 2020 +0100 +++ b/align/Align.h Thu May 21 16:21:57 2020 +0100 @@ -33,6 +33,26 @@ public: Align() { } + enum AlignmentType { + NoAlignment, + LinearAlignment, + TrimmedLinearAlignment, + MATCHAlignment, + MATCHAlignmentWithPitchCompare, + SungPitchContourAlignment, + TransformDrivenDTWAlignment, + ExternalProgramAlignment, + + LastAlignmentType = ExternalProgramAlignment + }; + + static QString getAlignmentTypeTag(AlignmentType type); + static AlignmentType getAlignmentTypeForTag(QString tag); + + static AlignmentType getAlignmentPreference(QString &additionalData); + static void setAlignmentPreference(AlignmentType type, + QString additionalData = ""); + /** * Align the "other" model to the reference, attaching an * AlignmentModel to it. Alignment is carried out by the method @@ -84,6 +104,8 @@ */ static bool canAlign(); + //!!! + check whether specific alignment types are available + signals: /** * Emitted when an alignment is successfully completed. The @@ -111,10 +133,8 @@ // we don't key this on the whole (reference, toAlign) pair std::map<ModelId, std::shared_ptr<Aligner>> m_aligners; - void addAligner(Document *doc, ModelId reference, ModelId toAlign); + bool addAligner(Document *doc, ModelId reference, ModelId toAlign); void removeAligner(QObject *); - - static void getAlignerPreference(bool &useProgram, QString &program); }; #endif
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/align/LinearAligner.cpp Thu May 21 16:21:57 2020 +0100 @@ -0,0 +1,96 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Sonic Visualiser + An audio file viewer and annotation editor. + Centre for Digital Music, Queen Mary, University of London. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. See the file + COPYING included with this distribution for more information. +*/ + +#include "LinearAligner.h" + +#include "system/System.h" + +#include "data/model/Path.h" +#include "data/model/AlignmentModel.h" + +#include "framework/Document.h" + +LinearAligner::LinearAligner(Document *doc, + ModelId reference, + ModelId toAlign, + bool trimmed) : + m_document(doc), + m_reference(reference), + m_toAlign(toAlign), + m_trimmed(trimmed) +{ +} + +LinearAligner::~LinearAligner() +{ +} + +void +LinearAligner::begin() +{ + bool ready = false; + while (!ready) { + { // scope so as to release input shared_ptr before sleeping + auto reference = ModelById::get(m_reference); + auto toAlign = ModelById::get(m_toAlign); + if (!reference || !reference->isOK() || + !toAlign || !toAlign->isOK()) { + return; + } + ready = reference->isReady() && toAlign->isReady(); + } + if (!ready) { + SVDEBUG << "LinearAligner: Waiting for models..." << endl; + usleep(500000); + } + } + + auto reference = ModelById::get(m_reference); + auto toAlign = ModelById::get(m_toAlign); + + if (!reference || !reference->isOK() || + !toAlign || !toAlign->isOK()) { + return; + } + + sv_frame_t s0 = reference->getStartFrame(), s1 = toAlign->getStartFrame(); + sv_frame_t e0 = reference->getEndFrame(), e1 = toAlign->getEndFrame(); + sv_frame_t d0 = e0 - s0, d1 = e1 - s1; + + if (d1 == 0) { + return; + } + + double ratio = double(d0) / double(d1); + sv_frame_t resolution = 1024; + + Path path(reference->getSampleRate(), resolution); + + for (sv_frame_t f = s1; f < e1; f += resolution) { + sv_frame_t target = sv_frame_t(double(f - s1) * ratio); + path.add(PathPoint(f, target)); + } + + auto alignment = std::make_shared<AlignmentModel>(m_reference, + m_toAlign, + ModelId()); + + auto alignmentModelId = ModelById::add(alignment); + + alignment->setPath(path); + toAlign->setAlignment(alignmentModelId); + m_document->addNonDerivedModel(alignmentModelId); +} + +//!!! + trimmed
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/align/LinearAligner.h Thu May 21 16:21:57 2020 +0100 @@ -0,0 +1,48 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Sonic Visualiser + An audio file viewer and annotation editor. + Centre for Digital Music, Queen Mary, University of London. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. See the file + COPYING included with this distribution for more information. +*/ + +#ifndef SV_LINEAR_ALIGNER_H +#define SV_LINEAR_ALIGNER_H + +#include "Aligner.h" + +class AlignmentModel; +class Document; + +class LinearAligner : public Aligner +{ + Q_OBJECT + +public: + LinearAligner(Document *doc, + ModelId reference, + ModelId toAlign, + bool trimmed); + + ~LinearAligner(); + + void begin() override; + + static bool isAvailable() { + return true; + } + +private: + Document *m_document; + ModelId m_reference; + ModelId m_toAlign; + bool m_trimmed; +}; + +#endif
--- a/align/TransformAligner.cpp Wed May 06 11:45:27 2020 +0100 +++ b/align/TransformAligner.cpp Thu May 21 16:21:57 2020 +0100 @@ -29,10 +29,12 @@ TransformAligner::TransformAligner(Document *doc, ModelId reference, - ModelId toAlign) : + ModelId toAlign, + bool withTuningDifference) : m_document(doc), m_reference(reference), m_toAlign(toAlign), + m_withTuningDifference(withTuningDifference), m_tuningFrequency(440.f), m_incomplete(true) { @@ -58,9 +60,9 @@ { QSettings settings; settings.beginGroup("Alignment"); - TransformId id = - settings.value("transform-id", - "vamp:match-vamp-plugin:match:path").toString(); + TransformId id = settings.value + ("transform-id", + "vamp:match-vamp-plugin:match:path").toString(); settings.endGroup(); return id; } @@ -70,15 +72,10 @@ { QSettings settings; settings.beginGroup("Alignment"); - bool performPitchCompensation = - settings.value("align-pitch-aware", false).toBool(); - QString id = ""; - if (performPitchCompensation) { - id = settings.value - ("tuning-difference-transform-id", - "vamp:tuning-difference:tuning-difference:tuningfreq") - .toString(); - } + TransformId id = settings.value + ("tuning-difference-transform-id", + "vamp:tuning-difference:tuning-difference:tuningfreq") + .toString(); settings.endGroup(); return id; } @@ -157,7 +154,10 @@ (m_reference, m_toAlign, ModelId()); m_alignmentModel = ModelById::add(alignmentModel); - TransformId tdId = getTuningDifferenceTransformName(); + TransformId tdId; + if (m_withTuningDifference) { + tdId = getTuningDifferenceTransformName(); + } if (tdId == "") {
--- a/align/TransformAligner.h Wed May 06 11:45:27 2020 +0100 +++ b/align/TransformAligner.h Thu May 21 16:21:57 2020 +0100 @@ -27,7 +27,10 @@ public: TransformAligner(Document *doc, ModelId reference, - ModelId toAlign); + ModelId toAlign, + bool withTuningDifference); + + //!!! pass in transform id // Destroy the aligner, cleanly cancelling any ongoing alignment ~TransformAligner(); @@ -54,6 +57,7 @@ ModelId m_tuningDiffProgressModel; // SparseTimeValueModel, unreg'd with doc ModelId m_tuningDiffOutputModel; // SparseTimeValueModel, unreg'd with doc ModelId m_pathOutputModel; // SparseTimeValueModel, unreg'd with doc + bool m_withTuningDifference; float m_tuningFrequency; bool m_incomplete; };
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/align/TransformDTWAligner.cpp Thu May 21 16:21:57 2020 +0100 @@ -0,0 +1,390 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Sonic Visualiser + An audio file viewer and annotation editor. + Centre for Digital Music, Queen Mary, University of London. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. See the file + COPYING included with this distribution for more information. +*/ + +#include "TransformDTWAligner.h" +#include "DTW.h" + +#include "data/model/SparseTimeValueModel.h" +#include "data/model/RangeSummarisableTimeValueModel.h" +#include "data/model/AlignmentModel.h" +#include "data/model/AggregateWaveModel.h" + +#include "framework/Document.h" + +#include "transform/ModelTransformerFactory.h" +#include "transform/FeatureExtractionModelTransformer.h" + +#include <QSettings> +#include <QMutex> +#include <QMutexLocker> + +using std::vector; + +TransformDTWAligner::TransformDTWAligner(Document *doc, + ModelId reference, + ModelId toAlign, + Transform transform, + DTWType dtwType) : + m_document(doc), + m_reference(reference), + m_toAlign(toAlign), + m_referenceTransformComplete(false), + m_toAlignTransformComplete(false), + m_transform(transform), + m_dtwType(dtwType), + m_incomplete(true) +{ +} + +TransformDTWAligner::~TransformDTWAligner() +{ + if (m_incomplete) { + if (auto toAlign = ModelById::get(m_toAlign)) { + toAlign->setAlignment({}); + } + } + + ModelById::release(m_referenceOutputModel); + ModelById::release(m_toAlignOutputModel); + ModelById::release(m_alignmentProgressModel); +} + +bool +TransformDTWAligner::isAvailable() +{ + //!!! needs to be isAvailable(QString transformId)? + return true; +} + +void +TransformDTWAligner::begin() +{ + auto reference = + ModelById::getAs<RangeSummarisableTimeValueModel>(m_reference); + auto toAlign = + ModelById::getAs<RangeSummarisableTimeValueModel>(m_toAlign); + + if (!reference || !toAlign) return; + + SVCERR << "TransformDTWAligner[" << this << "]: begin(): aligning " + << m_toAlign << " against reference " << m_reference << endl; + + ModelTransformerFactory *mtf = ModelTransformerFactory::getInstance(); + + QString message; + + m_referenceOutputModel = mtf->transform(m_transform, m_reference, message); + auto referenceOutputModel = ModelById::get(m_referenceOutputModel); + if (!referenceOutputModel) { + SVCERR << "Align::alignModel: ERROR: Failed to create reference output model (no plugin?)" << endl; + emit failed(m_toAlign, message); + return; + } + + SVCERR << "TransformDTWAligner[" << this << "]: begin(): transform id " + << m_transform.getIdentifier() + << " is running on reference model" << endl; + + message = ""; + + m_toAlignOutputModel = mtf->transform(m_transform, m_toAlign, message); + auto toAlignOutputModel = ModelById::get(m_toAlignOutputModel); + if (!toAlignOutputModel) { + SVCERR << "Align::alignModel: ERROR: Failed to create toAlign output model (no plugin?)" << endl; + emit failed(m_toAlign, message); + return; + } + + SVCERR << "TransformDTWAligner[" << this << "]: begin(): transform id " + << m_transform.getIdentifier() + << " is running on toAlign model" << endl; + + connect(referenceOutputModel.get(), SIGNAL(completionChanged(ModelId)), + this, SLOT(completionChanged(ModelId))); + connect(toAlignOutputModel.get(), SIGNAL(completionChanged(ModelId)), + this, SLOT(completionChanged(ModelId))); + + auto alignmentProgressModel = std::make_shared<SparseTimeValueModel> + (reference->getSampleRate(), m_transform.getStepSize(), false); + alignmentProgressModel->setCompletion(0); + m_alignmentProgressModel = ModelById::add(alignmentProgressModel); + + auto alignmentModel = std::make_shared<AlignmentModel> + (m_reference, m_toAlign, m_alignmentProgressModel); + m_alignmentModel = ModelById::add(alignmentModel); + + toAlign->setAlignment(m_alignmentModel); + m_document->addNonDerivedModel(m_alignmentModel); + + // we wouldn't normally expect these to be true here, but... + int completion = 0; + if (referenceOutputModel->isReady(&completion) && + toAlignOutputModel->isReady(&completion)) { + SVCERR << "TransformDTWAligner[" << this << "]: begin(): output models " + << "are ready already! calling performAlignment" << endl; + if (performAlignment()) { + emit complete(m_alignmentModel); + } else { + emit failed(m_toAlign, tr("Failed to calculate alignment using DTW")); + } + } +} + +void +TransformDTWAligner::completionChanged(ModelId id) +{ + if (!m_incomplete) { + return; + } + + SVCERR << "TransformDTWAligner[" << this << "]: completionChanged: " + << "model " << id << endl; + + auto referenceOutputModel = ModelById::get(m_referenceOutputModel); + auto toAlignOutputModel = ModelById::get(m_toAlignOutputModel); + + if (!referenceOutputModel || !toAlignOutputModel) { + return; + } + + int referenceCompletion = 0, toAlignCompletion = 0; + bool referenceReady = referenceOutputModel->isReady(&referenceCompletion); + bool toAlignReady = toAlignOutputModel->isReady(&toAlignCompletion); + + auto alignmentProgressModel = + ModelById::getAs<SparseTimeValueModel>(m_alignmentProgressModel); + + if (referenceReady && toAlignReady) { + + SVCERR << "TransformDTWAligner[" << this << "]: completionChanged: " + << "ready, calling performAlignment" << endl; + + if (alignmentProgressModel) { + alignmentProgressModel->setCompletion(95); + } + + if (performAlignment()) { + emit complete(m_alignmentModel); + } else { + emit failed(m_toAlign, tr("Alignment of transform outputs failed")); + } + + } else { + + SVCERR << "TransformDTWAligner[" << this << "]: completionChanged: " + << "not ready yet: reference completion " << referenceCompletion + << ", toAlign completion " << toAlignCompletion << endl; + + if (alignmentProgressModel) { + int completion = std::min(referenceCompletion, + toAlignCompletion); + completion = (completion * 94) / 100; + alignmentProgressModel->setCompletion(completion); + } + } +} + +bool +TransformDTWAligner::performAlignment() +{ + if (m_dtwType == Magnitude) { + return performAlignmentMagnitude(); + } else { + return performAlignmentRiseFall(); + } +} + +bool +TransformDTWAligner::performAlignmentMagnitude() +{ + auto referenceOutputSTVM = ModelById::getAs<SparseTimeValueModel> + (m_referenceOutputModel); + auto toAlignOutputSTVM = ModelById::getAs<SparseTimeValueModel> + (m_toAlignOutputModel); + auto alignmentModel = ModelById::getAs<AlignmentModel> + (m_alignmentModel); + + if (!referenceOutputSTVM || !toAlignOutputSTVM) { + //!!! what? + return false; + } + + if (!alignmentModel) { + return false; + } + + vector<double> s1, s2; + + { + auto events = referenceOutputSTVM->getAllEvents(); + for (auto e: events) { + s1.push_back(e.getValue()); + } + events = toAlignOutputSTVM->getAllEvents(); + for (auto e: events) { + s2.push_back(e.getValue()); + } + } + + SVCERR << "TransformDTWAligner[" << this << "]: performAlignment: " + << "Have " << s1.size() << " events from reference, " + << s2.size() << " from toAlign" << endl; + + MagnitudeDTW dtw; + vector<size_t> alignment; + + { + SVCERR << "TransformDTWAligner[" << this + << "]: serialising DTW to avoid over-allocation" << endl; + static QMutex mutex; + QMutexLocker locker(&mutex); + + alignment = dtw.alignSeries(s1, s2); + } + + SVCERR << "TransformDTWAligner[" << this << "]: performAlignment: " + << "DTW produced " << alignment.size() << " points:" << endl; + for (int i = 0; i < alignment.size() && i < 100; ++i) { + SVCERR << alignment[i] << " "; + } + SVCERR << endl; + + auto alignmentProgressModel = + ModelById::getAs<SparseTimeValueModel>(m_alignmentProgressModel); + if (alignmentProgressModel) { + alignmentProgressModel->setCompletion(100); + } + + // clear the alignment progress model + alignmentModel->setPathFrom(ModelId()); + + sv_frame_t resolution = referenceOutputSTVM->getResolution(); + sv_frame_t sourceFrame = 0; + + Path path(referenceOutputSTVM->getSampleRate(), resolution); + + for (size_t m: alignment) { + path.add(PathPoint(sourceFrame, sv_frame_t(m) * resolution)); + sourceFrame += resolution; + } + + alignmentModel->setPath(path); + + SVCERR << "TransformDTWAligner[" << this << "]: performAlignment: Done" + << endl; + + m_incomplete = false; + return true; +} + +bool +TransformDTWAligner::performAlignmentRiseFall() +{ + auto referenceOutputSTVM = ModelById::getAs<SparseTimeValueModel> + (m_referenceOutputModel); + auto toAlignOutputSTVM = ModelById::getAs<SparseTimeValueModel> + (m_toAlignOutputModel); + auto alignmentModel = ModelById::getAs<AlignmentModel> + (m_alignmentModel); + + if (!referenceOutputSTVM || !toAlignOutputSTVM) { + //!!! what? + return false; + } + + if (!alignmentModel) { + return false; + } + + vector<RiseFallDTW::Value> s1, s2; + double prev1 = 0.0, prev2 = 0.0; + + { + auto events = referenceOutputSTVM->getAllEvents(); + for (auto e: events) { + double v = e.getValue(); + //!!! the original does this using MIDI pitch for the + //!!! pYin transform... rework with a lambda passed in + //!!! for modification maybe? + factor out s1/s2 of course + if (v > prev1) { + s1.push_back({ RiseFallDTW::Direction::Up, v - prev1 }); + } else { + s1.push_back({ RiseFallDTW::Direction::Down, prev1 - v }); + } + prev1 = v; + } + events = toAlignOutputSTVM->getAllEvents(); + for (auto e: events) { + double v = e.getValue(); + //!!! as above + if (v > prev2) { + s2.push_back({ RiseFallDTW::Direction::Up, v - prev2 }); + } else { + s2.push_back({ RiseFallDTW::Direction::Down, prev2 - v }); + } + prev2 = v; + } + } + + SVCERR << "TransformDTWAligner[" << this << "]: performAlignment: " + << "Have " << s1.size() << " events from reference, " + << s2.size() << " from toAlign" << endl; + + RiseFallDTW dtw; + + vector<size_t> alignment; + + { + SVCERR << "TransformDTWAligner[" << this + << "]: serialising DTW to avoid over-allocation" << endl; + static QMutex mutex; + QMutexLocker locker(&mutex); + + alignment = dtw.alignSeries(s1, s2); + } + + SVCERR << "TransformDTWAligner[" << this << "]: performAlignment: " + << "DTW produced " << alignment.size() << " points:" << endl; + for (int i = 0; i < alignment.size() && i < 100; ++i) { + SVCERR << alignment[i] << " "; + } + SVCERR << endl; + + auto alignmentProgressModel = + ModelById::getAs<SparseTimeValueModel>(m_alignmentProgressModel); + if (alignmentProgressModel) { + alignmentProgressModel->setCompletion(100); + } + + // clear the alignment progress model + alignmentModel->setPathFrom(ModelId()); + + sv_frame_t resolution = referenceOutputSTVM->getResolution(); + sv_frame_t sourceFrame = 0; + + Path path(referenceOutputSTVM->getSampleRate(), resolution); + + for (size_t m: alignment) { + path.add(PathPoint(sourceFrame, sv_frame_t(m) * resolution)); + sourceFrame += resolution; + } + + alignmentModel->setPath(path); + + SVCERR << "TransformDTWAligner[" << this << "]: performAlignment: Done" + << endl; + + m_incomplete = false; + return true; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/align/TransformDTWAligner.h Thu May 21 16:21:57 2020 +0100 @@ -0,0 +1,70 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Sonic Visualiser + An audio file viewer and annotation editor. + Centre for Digital Music, Queen Mary, University of London. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. See the file + COPYING included with this distribution for more information. +*/ + +#ifndef SV_TRANSFORM_DTW_ALIGNER_H +#define SV_TRANSFORM_DTW_ALIGNER_H + +#include "Aligner.h" + +#include "transform/Transform.h" + +class AlignmentModel; +class Document; + +class TransformDTWAligner : public Aligner +{ + Q_OBJECT + +public: + enum DTWType { + Magnitude, + RiseFall + }; + + TransformDTWAligner(Document *doc, + ModelId reference, + ModelId toAlign, + Transform transform, + DTWType dtwType); + + // Destroy the aligner, cleanly cancelling any ongoing alignment + ~TransformDTWAligner(); + + void begin() override; + + static bool isAvailable(); + +private slots: + void completionChanged(ModelId); + +private: + bool performAlignment(); + bool performAlignmentMagnitude(); + bool performAlignmentRiseFall(); + + Document *m_document; + ModelId m_reference; + ModelId m_toAlign; + ModelId m_referenceOutputModel; + ModelId m_toAlignOutputModel; + ModelId m_alignmentProgressModel; + ModelId m_alignmentModel; + bool m_referenceTransformComplete; + bool m_toAlignTransformComplete; + Transform m_transform; + DTWType m_dtwType; + bool m_incomplete; +}; + +#endif
--- a/files.pri Wed May 06 11:45:27 2020 +0100 +++ b/files.pri Thu May 21 16:21:57 2020 +0100 @@ -3,7 +3,9 @@ align/Align.h \ align/Aligner.h \ align/ExternalProgramAligner.h \ + align/LinearAligner.h \ align/TransformAligner.h \ + align/TransformDTWAligner.h \ audio/AudioCallbackPlaySource.h \ audio/AudioCallbackRecordTarget.h \ audio/AudioGenerator.h \ @@ -22,7 +24,9 @@ SVAPP_SOURCES += \ align/Align.cpp \ align/ExternalProgramAligner.cpp \ + align/LinearAligner.cpp \ align/TransformAligner.cpp \ + align/TransformDTWAligner.cpp \ audio/AudioCallbackPlaySource.cpp \ audio/AudioCallbackRecordTarget.cpp \ audio/AudioGenerator.cpp \