Mercurial > hg > svapp
diff framework/Align.cpp @ 673:d62fd61082a1
Merge from branch tuning-difference
author | Chris Cannam |
---|---|
date | Fri, 17 May 2019 09:46:22 +0100 |
parents | ae7584dbd668 |
children | b375fdbb74bc |
line wrap: on
line diff
--- a/framework/Align.cpp Thu Apr 04 16:17:11 2019 +0100 +++ b/framework/Align.cpp Fri May 17 09:46: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,39 @@ 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]. + // + // (We also create a sneaky additional SparseTimeValueModel + // temporarily so we can attach completion information to it - + // this is quite unnecessary from the perspective of simply + // producing the results.) AggregateWaveModel::ChannelSpecList components; @@ -109,9 +146,131 @@ AggregateWaveModel *aggregateModel = new AggregateWaveModel(components); doc->addAggregateModel(aggregateModel); + + AlignmentModel *alignmentModel = + new AlignmentModel(reference, other, nullptr); + + 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()); + + transform.setParameter("maxduration", 50); + transform.setParameter("maxrange", 5); - ModelTransformer::Input aggregate(aggregateModel); + 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); + + connect(tdout, SIGNAL(completionChanged()), + this, SLOT(tuningDifferenceCompletionChanged())); + + TuningDiffRec rec; + rec.input = aggregateModel; + rec.alignment = alignmentModel; + + // This model exists only so that the AlignmentModel can get a + // completion value from somewhere while the tuning difference + // calculation is going on + rec.preparatory = new SparseTimeValueModel + (aggregateModel->getSampleRate(), 1);; + rec.preparatory->setCompletion(0); + alignmentModel->setPathFrom(rec.preparatory); + + m_pendingTuningDiffs[tdout] = rec; + } + + return true; +} + +void +Align::tuningDifferenceCompletionChanged() +{ + QMutexLocker locker (&m_mutex); + + SparseTimeValueModel *td = qobject_cast<SparseTimeValueModel *>(sender()); + if (!td) return; + + if (m_pendingTuningDiffs.find(td) == m_pendingTuningDiffs.end()) { + SVCERR << "ERROR: Align::tuningDifferenceCompletionChanged: Model " + << td << " not found in pending tuning diff map!" << endl; + return; + } + + TuningDiffRec rec = m_pendingTuningDiffs[td]; + + int completion = 0; + bool done = td->isReady(&completion); + + SVCERR << "Align::tuningDifferenceCompletionChanged: done = " << done << ", completion = " << completion << endl; + + if (!done) { + // This will be the completion the alignment model reports, + // before the alignment actually begins. It goes up from 0 to + // 99 (not 100!) and then back to 0 again when we start + // calculating the actual path in the following phase + int clamped = (completion == 100 ? 99 : completion); + SVCERR << "Align::tuningDifferenceCompletionChanged: setting rec.preparatory completion to " << clamped << endl; + rec.preparatory->setCompletion(clamped); + return; + } + + 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); + td->aboutToDelete(); + delete td; + + rec.alignment->setPathFrom(nullptr); + + beginTransformDrivenAlignment + (rec.input, rec.alignment, tuningFrequency); +} + +bool +Align::beginTransformDrivenAlignment(AggregateWaveModel *aggregateModel, + AlignmentModel *alignmentModel, + float tuningFrequency) +{ TransformId id = getAlignmentTransformName(); TransformFactory *tf = TransformFactory::getInstance(); @@ -123,38 +282,42 @@ 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) { - cerr << "Align::alignModel: ERROR: Failed to create alignment path (no MATCH plugin?)" << endl; + 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; } @@ -162,6 +325,8 @@ void Align::alignmentCompletionChanged() { + QMutexLocker locker (&m_mutex); + AlignmentModel *am = qobject_cast<AlignmentModel *>(sender()); if (!am) return; if (am->isReady()) { @@ -172,8 +337,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); @@ -192,7 +360,7 @@ ReadOnlyWaveFileModel *roref = qobject_cast<ReadOnlyWaveFileModel *>(reference); ReadOnlyWaveFileModel *rorm = qobject_cast<ReadOnlyWaveFileModel *>(rm); if (!roref || !rorm) { - cerr << "ERROR: Align::alignModelViaProgram: Can't align non-read-only models via program (no local filename available)" << endl; + SVCERR << "ERROR: Align::alignModelViaProgram: Can't align non-read-only models via program (no local filename available)" << endl; return false; } @@ -200,12 +368,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,16 +383,16 @@ 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(); if (!success) { - cerr << "ERROR: Align::alignModelViaProgram: Program did not start" - << endl; - m_error = "Alignment program could not be started"; - m_processModels.erase(process); + SVCERR << "ERROR: Align::alignModelViaProgram: Program did not start" + << endl; + error = "Alignment program could not be started"; + m_pendingProcesses.erase(process); rm->setAlignment(nullptr); // deletes alignmentModel as well delete process; } @@ -237,17 +403,19 @@ void Align::alignmentProgramFinished(int exitCode, QProcess::ExitStatus status) { - cerr << "Align::alignmentProgramFinished" << endl; + QMutexLocker locker (&m_mutex); + + SVCERR << "Align::alignmentProgramFinished" << endl; QProcess *process = qobject_cast<QProcess *>(sender()); - if (m_processModels.find(process) == m_processModels.end()) { - cerr << "ERROR: Align::alignmentProgramFinished: Process " << process - << " not found in process model map!" << endl; + 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) { @@ -269,10 +437,11 @@ CSVFileReader reader(process, format, alignmentModel->getSampleRate()); if (!reader.isOK()) { - cerr << "ERROR: Align::alignmentProgramFinished: Failed to parse output" - << endl; - m_error = QString("Failed to parse output of program: %1") - .arg(reader.getError()); + SVCERR << "ERROR: Align::alignmentProgramFinished: Failed to parse output" + << endl; + alignmentModel->setError + (QString("Failed to parse output of program: %1") + .arg(reader.getError())); goto done; } @@ -280,35 +449,38 @@ SparseTimeValueModel *path = qobject_cast<SparseTimeValueModel *>(csvOutput); if (!path) { - cerr << "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"); + SVCERR << "ERROR: Align::alignmentProgramFinished: Output did not convert to sparse time-value model" + << endl; + alignmentModel->setError + ("Output of program did not produce sparse time-value model"); goto done; } - if (path->getPoints().empty()) { - cerr << "ERROR: Align::alignmentProgramFinished: Output contained no mappings" - << endl; - m_error = QString("Output of alignment program contained no mappings"); + if (path->isEmpty()) { + SVCERR << "ERROR: Align::alignmentProgramFinished: Output contained no mappings" + << endl; + alignmentModel->setError + ("Output of alignment program contained no mappings"); goto done; } - cerr << "Align::alignmentProgramFinished: Setting alignment path (" - << path->getPoints().size() << " point(s))" << endl; - + SVCERR << "Align::alignmentProgramFinished: Setting alignment path (" + << path->getEventCount() << " point(s))" << endl; + alignmentModel->setPathFrom(path); emit alignmentComplete(alignmentModel); } else { - cerr << "ERROR: Align::alignmentProgramFinished: Aligner program " - << "failed: exit code " << exitCode << ", status " << status - << endl; - m_error = "Aligner process returned non-zero exit status"; + SVCERR << "ERROR: Align::alignmentProgramFinished: Aligner program " + << "failed: exit code " << exitCode << ", status " << status + << endl; + alignmentModel->setError + ("Aligner process returned non-zero exit status"); } done: - m_processModels.erase(process); + m_pendingProcesses.erase(process); delete process; }