Mercurial > hg > svapp
changeset 670:0960e27c3232 tuning-difference
Experiment with optionally taking tuning difference into account for alignment
author | Chris Cannam |
---|---|
date | Wed, 15 May 2019 17:52:22 +0100 |
parents | 331be52cd473 |
children | b6cafe05017d |
files | framework/Align.cpp framework/Align.h framework/Document.cpp |
diffstat | 3 files changed, 222 insertions(+), 55 deletions(-) [+] |
line wrap: on
line diff
--- a/framework/Align.cpp Tue May 14 14:51:09 2019 +0100 +++ b/framework/Align.cpp Wed May 15 17:52:22 2019 +0100 @@ -4,7 +4,6 @@ Sonic Visualiser An audio file viewer and annotation editor. Centre for Digital Music, Queen Mary, University of London. - This file copyright 2006 Chris Cannam and QMUL. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as @@ -34,7 +33,7 @@ #include <QApplication> bool -Align::alignModel(Document *doc, Model *ref, Model *other) +Align::alignModel(Document *doc, Model *ref, Model *other, QString &error) { QSettings settings; settings.beginGroup("Preferences"); @@ -43,9 +42,9 @@ settings.endGroup(); if (useProgram && (program != "")) { - return alignModelViaProgram(doc, ref, other, program); + return alignModelViaProgram(doc, ref, other, program, error); } else { - return alignModelViaTransform(doc, ref, other); + return alignModelViaTransform(doc, ref, other, error); } } @@ -61,17 +60,40 @@ return id; } +QString +Align::getTuningDifferenceTransformName() +{ + 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(); +// } + settings.endGroup(); + return id; +} + bool Align::canAlign() { + TransformFactory *factory = TransformFactory::getInstance(); TransformId id = getAlignmentTransformName(); - TransformFactory *factory = TransformFactory::getInstance(); - return factory->haveTransform(id); + TransformId tdId = getTuningDifferenceTransformName(); + return factory->haveTransform(id) && + (tdId == "" || factory->haveTransform(tdId)); } bool -Align::alignModelViaTransform(Document *doc, Model *ref, Model *other) +Align::alignModelViaTransform(Document *doc, Model *ref, Model *other, + QString &error) { + QMutexLocker locker (&m_mutex); + RangeSummarisableTimeValueModel *reference = qobject_cast <RangeSummarisableTimeValueModel *>(ref); @@ -80,24 +102,34 @@ if (!reference || !rm) return false; // but this should have been tested already - // This involves creating three new models: + // This involves creating either three or four new models: // 1. an AggregateWaveModel to provide the mixdowns of the main // model and the new model in its two channels, as input to the // MATCH plugin - // 2. a SparseTimeValueModel, which is the model automatically - // created by FeatureExtractionPluginTransformer when running the - // MATCH plugin (thus containing the alignment path) + // 2a. a SparseTimeValueModel which will be automatically created + // by FeatureExtractionModelTransformer when running the + // TuningDifference plugin to receive the relative tuning of the + // second model (if pitch-aware alignment is enabled in the + // preferences) + + // 2b. a SparseTimeValueModel which will be automatically created + // by FeatureExtractionPluginTransformer when running the MATCH + // plugin to perform alignment (so containing the alignment path) // 3. an AlignmentModel, which stores the path model and carries // out alignment lookups on it. - // The first two of these are provided as arguments to the - // constructor for the third, which takes responsibility for - // deleting them. The AlignmentModel, meanwhile, is passed to the - // new model we are aligning, which also takes responsibility for - // it. We should not have to delete any of these new models here. + // The AggregateWaveModel [1] is registered with the document, + // which deletes it when it is invalidated (when one of its + // components is deleted). The SparseTimeValueModel [2a] is reused + // by us when starting the alignment process proper, and is then + // deleted by us. The SparseTimeValueModel [2b] is passed to the + // AlignmentModel, which takes ownership of it. The AlignmentModel + // is attached to the new model we are aligning, which also takes + // ownership of it. The only one of these models that we need to + // delete here is the SparseTimeValueModel [2a]. AggregateWaveModel::ChannelSpecList components; @@ -109,9 +141,70 @@ AggregateWaveModel *aggregateModel = new AggregateWaveModel(components); doc->addAggregateModel(aggregateModel); + + AlignmentModel *alignmentModel = + new AlignmentModel(reference, other, nullptr); + + connect(alignmentModel, SIGNAL(completionChanged()), + this, SLOT(alignmentCompletionChanged())); + + TransformId tdId = getTuningDifferenceTransformName(); + + if (tdId == "") { + + if (beginTransformDrivenAlignment(aggregateModel, alignmentModel)) { + rm->setAlignment(alignmentModel); + } else { + error = alignmentModel->getError(); + delete alignmentModel; + return false; + } + + } else { + + // Have a tuning-difference transform id, so run it + // asynchronously first + + TransformFactory *tf = TransformFactory::getInstance(); + + Transform transform = tf->getDefaultTransformFor + (tdId, aggregateModel->getSampleRate()); + + SVDEBUG << "Align::alignModel: Tuning difference transform step size " << transform.getStepSize() << ", block size " << transform.getBlockSize() << endl; + + ModelTransformerFactory *mtf = ModelTransformerFactory::getInstance(); + + QString message; + Model *transformOutput = mtf->transform(transform, aggregateModel, message); + + SparseTimeValueModel *tdout = dynamic_cast<SparseTimeValueModel *> + (transformOutput); + + if (!tdout) { + SVCERR << "Align::alignModel: ERROR: Failed to create tuning-difference output model (no Tuning Difference plugin?)" << endl; + delete tdout; + error = message; + return false; + } + + rm->setAlignment(alignmentModel); - ModelTransformer::Input aggregate(aggregateModel); + connect(tdout, SIGNAL(completionChanged()), + this, SLOT(tuningDifferenceCompletionChanged())); + m_pendingTuningDiffs[tdout] = + std::pair<AggregateWaveModel *, AlignmentModel *> + (aggregateModel, alignmentModel); + } + + return true; +} + +bool +Align::beginTransformDrivenAlignment(AggregateWaveModel *aggregateModel, + AlignmentModel *alignmentModel, + float tuningFrequency) +{ TransformId id = getAlignmentTransformName(); TransformFactory *tf = TransformFactory::getInstance(); @@ -123,45 +216,87 @@ transform.setParameter("serialise", 1); transform.setParameter("smooth", 0); + if (tuningFrequency != 0.f) { + transform.setParameter("freq2", tuningFrequency); + } + SVDEBUG << "Align::alignModel: Alignment transform step size " << transform.getStepSize() << ", block size " << transform.getBlockSize() << endl; ModelTransformerFactory *mtf = ModelTransformerFactory::getInstance(); QString message; - Model *transformOutput = mtf->transform(transform, aggregate, message); + Model *transformOutput = mtf->transform + (transform, aggregateModel, message); if (!transformOutput) { transform.setStepSize(0); - transformOutput = mtf->transform(transform, aggregate, message); + transformOutput = mtf->transform + (transform, aggregateModel, message); } SparseTimeValueModel *path = dynamic_cast<SparseTimeValueModel *> (transformOutput); + //!!! callers will need to be updated to get error from + //!!! alignment model after initial call + if (!path) { SVCERR << "Align::alignModel: ERROR: Failed to create alignment path (no MATCH plugin?)" << endl; delete transformOutput; - delete aggregateModel; - m_error = message; + alignmentModel->setError(message); return false; } path->setCompletion(0); - - AlignmentModel *alignmentModel = new AlignmentModel - (reference, other, path); + alignmentModel->setPathFrom(path); connect(alignmentModel, SIGNAL(completionChanged()), this, SLOT(alignmentCompletionChanged())); - - rm->setAlignment(alignmentModel); return true; } void +Align::tuningDifferenceCompletionChanged() +{ + QMutexLocker locker (&m_mutex); + + SparseTimeValueModel *td = qobject_cast<SparseTimeValueModel *>(sender()); + if (!td) return; + if (!td->isReady()) return; + + disconnect(td, SIGNAL(completionChanged()), + this, SLOT(alignmentCompletionChanged())); + + if (m_pendingTuningDiffs.find(td) == m_pendingTuningDiffs.end()) { + SVCERR << "ERROR: Align::tuningDifferenceCompletionChanged: Model " + << td << " not found in pending tuning diff map!" << endl; + return; + } + + std::pair<AggregateWaveModel *, AlignmentModel *> models = + m_pendingTuningDiffs[td]; + + float tuningFrequency = 440.f; + + if (!td->isEmpty()) { + tuningFrequency = td->getAllEvents()[0].getValue(); + SVCERR << "Align::tuningDifferenceCompletionChanged: Reported tuning frequency = " << tuningFrequency << endl; + } else { + SVCERR << "Align::tuningDifferenceCompletionChanged: No tuning frequency reported" << endl; + } + + m_pendingTuningDiffs.erase(td); + + beginTransformDrivenAlignment + (models.first, models.second, tuningFrequency); +} + +void Align::alignmentCompletionChanged() { + QMutexLocker locker (&m_mutex); + AlignmentModel *am = qobject_cast<AlignmentModel *>(sender()); if (!am) return; if (am->isReady()) { @@ -172,8 +307,11 @@ } bool -Align::alignModelViaProgram(Document *, Model *ref, Model *other, QString program) +Align::alignModelViaProgram(Document *, Model *ref, Model *other, + QString program, QString &error) { + QMutexLocker locker (&m_mutex); + WaveFileModel *reference = qobject_cast<WaveFileModel *>(ref); WaveFileModel *rm = qobject_cast<WaveFileModel *>(other); @@ -200,12 +338,10 @@ QString otherPath = rorm->getLocalFilename(); if (refPath == "" || otherPath == "") { - m_error = "Failed to find local filepath for wave-file model"; + error = "Failed to find local filepath for wave-file model"; return false; } - m_error = ""; - AlignmentModel *alignmentModel = new AlignmentModel(reference, other, nullptr); rm->setAlignment(alignmentModel); @@ -217,7 +353,7 @@ connect(process, SIGNAL(finished(int, QProcess::ExitStatus)), this, SLOT(alignmentProgramFinished(int, QProcess::ExitStatus))); - m_processModels[process] = alignmentModel; + m_pendingProcesses[process] = alignmentModel; process->start(program, args); bool success = process->waitForStarted(); @@ -225,8 +361,8 @@ if (!success) { SVCERR << "ERROR: Align::alignModelViaProgram: Program did not start" << endl; - m_error = "Alignment program could not be started"; - m_processModels.erase(process); + error = "Alignment program could not be started"; + m_pendingProcesses.erase(process); rm->setAlignment(nullptr); // deletes alignmentModel as well delete process; } @@ -237,17 +373,19 @@ void Align::alignmentProgramFinished(int exitCode, QProcess::ExitStatus status) { + QMutexLocker locker (&m_mutex); + SVCERR << "Align::alignmentProgramFinished" << endl; QProcess *process = qobject_cast<QProcess *>(sender()); - if (m_processModels.find(process) == m_processModels.end()) { + if (m_pendingProcesses.find(process) == m_pendingProcesses.end()) { SVCERR << "ERROR: Align::alignmentProgramFinished: Process " << process << " not found in process model map!" << endl; return; } - AlignmentModel *alignmentModel = m_processModels[process]; + AlignmentModel *alignmentModel = m_pendingProcesses[process]; if (exitCode == 0 && status == 0) { @@ -271,8 +409,9 @@ if (!reader.isOK()) { SVCERR << "ERROR: Align::alignmentProgramFinished: Failed to parse output" << endl; - m_error = QString("Failed to parse output of program: %1") - .arg(reader.getError()); + alignmentModel->setError + (QString("Failed to parse output of program: %1") + .arg(reader.getError())); goto done; } @@ -282,14 +421,16 @@ if (!path) { SVCERR << "ERROR: Align::alignmentProgramFinished: Output did not convert to sparse time-value model" << endl; - m_error = QString("Output of program did not produce sparse time-value model"); + alignmentModel->setError + ("Output of program did not produce sparse time-value model"); goto done; } if (path->isEmpty()) { SVCERR << "ERROR: Align::alignmentProgramFinished: Output contained no mappings" << endl; - m_error = QString("Output of alignment program contained no mappings"); + alignmentModel->setError + ("Output of alignment program contained no mappings"); goto done; } @@ -304,11 +445,12 @@ SVCERR << "ERROR: Align::alignmentProgramFinished: Aligner program " << "failed: exit code " << exitCode << ", status " << status << endl; - m_error = "Aligner process returned non-zero exit status"; + alignmentModel->setError + ("Aligner process returned non-zero exit status"); } done: - m_processModels.erase(process); + m_pendingProcesses.erase(process); delete process; }
--- a/framework/Align.h Tue May 14 14:51:09 2019 +0100 +++ b/framework/Align.h Wed May 15 17:52:22 2019 +0100 @@ -19,10 +19,13 @@ #include <QString> #include <QObject> #include <QProcess> +#include <QMutex> #include <set> class Model; class AlignmentModel; +class SparseTimeValueModel; +class AggregateWaveModel; class Document; class Align : public QObject @@ -30,7 +33,7 @@ Q_OBJECT public: - Align() : m_error("") { } + Align() { } /** * Align the "other" model to the reference, attaching an @@ -38,6 +41,17 @@ * configured in the user preferences (either a plugin transform * or an external process) and is done asynchronously. * + * The return value indicates whether the alignment procedure + * started successfully. If it is true, then an AlignmentModel has + * been constructed and attached to the toAlign model, and you can + * query that model to discover the alignment progress, eventual + * outcome, and any error message generated during alignment. (The + * AlignmentModel is subsequently owned by the toAlign model.) + * Conversely if alignModel returns false, no AlignmentModel has + * been created, and the error return argument will contain an + * error report about whatever problem prevented this from + * happening. + * * A single Align object may carry out many simultanous alignment * calls -- you do not need to create a new Align object each * time, nor to wait for an alignment to be complete before @@ -50,24 +64,25 @@ */ bool alignModel(Document *doc, Model *reference, - Model *other); // via user preference + Model *toAlign, + QString &error); bool alignModelViaTransform(Document *doc, Model *reference, - Model *other); + Model *toAlign, + QString &error); bool alignModelViaProgram(Document *doc, Model *reference, - Model *other, - QString program); + Model *toAlign, + QString program, + QString &error); /** * Return true if the alignment facility is available (relevant * plugin installed, etc). */ static bool canAlign(); - - QString getError() const { return m_error; } signals: /** @@ -79,13 +94,22 @@ private slots: void alignmentCompletionChanged(); + void tuningDifferenceCompletionChanged(); void alignmentProgramFinished(int, QProcess::ExitStatus); private: static QString getAlignmentTransformName(); - - QString m_error; - std::map<QProcess *, AlignmentModel *> m_processModels; + static QString getTuningDifferenceTransformName(); + + bool beginTransformDrivenAlignment(AggregateWaveModel *, + AlignmentModel *, + float tuningFrequency = 0.f); + + QMutex m_mutex; + std::map<QProcess *, AlignmentModel *> m_pendingProcesses; + std::map<SparseTimeValueModel *, + std::pair<AggregateWaveModel *, + AlignmentModel *>> m_pendingTuningDiffs; }; #endif
--- a/framework/Document.cpp Tue May 14 14:51:09 2019 +0100 +++ b/framework/Document.cpp Wed May 15 17:52:22 2019 +0100 @@ -1138,10 +1138,11 @@ << rm->getAlignmentReference() << "; this will replace that)" << endl; } - - if (!m_align->alignModel(this, m_mainModel, rm)) { - SVCERR << "Alignment failed: " << m_align->getError() << endl; - emit alignmentFailed(m_align->getError()); + + QString err; + if (!m_align->alignModel(this, m_mainModel, rm, err)) { + SVCERR << "Alignment failed: " << err << endl; + emit alignmentFailed(err); } }