annotate framework/Align.cpp @ 425:5882462fa747 alignment_view

Seems more logical for the external alignment program to emit reference,other rather than other,reference
author Chris Cannam
date Thu, 20 Nov 2014 17:17:45 +0000
parents d044682967ca
children b23db4cef02f
rev   line source
Chris@420 1 /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
Chris@420 2
Chris@420 3 /*
Chris@420 4 Sonic Visualiser
Chris@420 5 An audio file viewer and annotation editor.
Chris@420 6 Centre for Digital Music, Queen Mary, University of London.
Chris@420 7 This file copyright 2006 Chris Cannam and QMUL.
Chris@420 8
Chris@420 9 This program is free software; you can redistribute it and/or
Chris@420 10 modify it under the terms of the GNU General Public License as
Chris@420 11 published by the Free Software Foundation; either version 2 of the
Chris@420 12 License, or (at your option) any later version. See the file
Chris@420 13 COPYING included with this distribution for more information.
Chris@420 14 */
Chris@420 15
Chris@420 16 #include "Align.h"
Chris@420 17
Chris@420 18 #include "data/model/WaveFileModel.h"
Chris@420 19 #include "data/model/AggregateWaveModel.h"
Chris@420 20 #include "data/model/RangeSummarisableTimeValueModel.h"
Chris@420 21 #include "data/model/SparseTimeValueModel.h"
Chris@420 22 #include "data/model/AlignmentModel.h"
Chris@420 23
Chris@420 24 #include "data/fileio/CSVFileReader.h"
Chris@420 25
Chris@420 26 #include "transform/TransformFactory.h"
Chris@420 27 #include "transform/ModelTransformerFactory.h"
Chris@420 28 #include "transform/FeatureExtractionModelTransformer.h"
Chris@420 29
Chris@420 30 #include <QProcess>
Chris@422 31 #include <QSettings>
Chris@422 32
Chris@422 33 bool
Chris@422 34 Align::alignModel(Model *ref, Model *other)
Chris@422 35 {
Chris@422 36 QSettings settings;
Chris@422 37 settings.beginGroup("Preferences");
Chris@422 38 bool useProgram = settings.value("use-external-alignment", false).toBool();
Chris@422 39 QString program = settings.value("external-alignment-program", "").toString();
Chris@422 40 settings.endGroup();
Chris@422 41
Chris@422 42 if (useProgram && (program != "")) {
Chris@422 43 return alignModelViaProgram(ref, other, program);
Chris@422 44 } else {
Chris@422 45 return alignModelViaTransform(ref, other);
Chris@422 46 }
Chris@422 47 }
Chris@420 48
Chris@420 49 bool
Chris@420 50 Align::alignModelViaTransform(Model *ref, Model *other)
Chris@420 51 {
Chris@420 52 RangeSummarisableTimeValueModel *reference = qobject_cast
Chris@420 53 <RangeSummarisableTimeValueModel *>(ref);
Chris@420 54
Chris@420 55 RangeSummarisableTimeValueModel *rm = qobject_cast
Chris@420 56 <RangeSummarisableTimeValueModel *>(other);
Chris@420 57
Chris@420 58 if (!reference || !rm) return false; // but this should have been tested already
Chris@420 59
Chris@420 60 // This involves creating three new models:
Chris@420 61
Chris@420 62 // 1. an AggregateWaveModel to provide the mixdowns of the main
Chris@420 63 // model and the new model in its two channels, as input to the
Chris@420 64 // MATCH plugin
Chris@420 65
Chris@420 66 // 2. a SparseTimeValueModel, which is the model automatically
Chris@420 67 // created by FeatureExtractionPluginTransformer when running the
Chris@420 68 // MATCH plugin (thus containing the alignment path)
Chris@420 69
Chris@420 70 // 3. an AlignmentModel, which stores the path model and carries
Chris@420 71 // out alignment lookups on it.
Chris@420 72
Chris@420 73 // The first two of these are provided as arguments to the
Chris@420 74 // constructor for the third, which takes responsibility for
Chris@420 75 // deleting them. The AlignmentModel, meanwhile, is passed to the
Chris@420 76 // new model we are aligning, which also takes responsibility for
Chris@420 77 // it. We should not have to delete any of these new models here.
Chris@420 78
Chris@420 79 AggregateWaveModel::ChannelSpecList components;
Chris@420 80
Chris@420 81 components.push_back(AggregateWaveModel::ModelChannelSpec
Chris@420 82 (reference, -1));
Chris@420 83
Chris@420 84 components.push_back(AggregateWaveModel::ModelChannelSpec
Chris@420 85 (rm, -1));
Chris@420 86
Chris@420 87 Model *aggregateModel = new AggregateWaveModel(components);
Chris@420 88 ModelTransformer::Input aggregate(aggregateModel);
Chris@420 89
Chris@420 90 TransformId id = "vamp:match-vamp-plugin:match:path"; //!!! configure
Chris@420 91
Chris@420 92 TransformFactory *tf = TransformFactory::getInstance();
Chris@420 93
Chris@420 94 Transform transform = tf->getDefaultTransformFor
Chris@420 95 (id, aggregateModel->getSampleRate());
Chris@420 96
Chris@420 97 transform.setStepSize(transform.getBlockSize()/2);
Chris@420 98 transform.setParameter("serialise", 1);
Chris@420 99 transform.setParameter("smooth", 0);
Chris@420 100
Chris@420 101 SVDEBUG << "Align::alignModel: Alignment transform step size " << transform.getStepSize() << ", block size " << transform.getBlockSize() << endl;
Chris@420 102
Chris@420 103 ModelTransformerFactory *mtf = ModelTransformerFactory::getInstance();
Chris@420 104
Chris@420 105 QString message;
Chris@420 106 Model *transformOutput = mtf->transform(transform, aggregate, message);
Chris@420 107
Chris@420 108 if (!transformOutput) {
Chris@420 109 transform.setStepSize(0);
Chris@420 110 transformOutput = mtf->transform(transform, aggregate, message);
Chris@420 111 }
Chris@420 112
Chris@420 113 SparseTimeValueModel *path = dynamic_cast<SparseTimeValueModel *>
Chris@420 114 (transformOutput);
Chris@420 115
Chris@420 116 if (!path) {
Chris@420 117 cerr << "Align::alignModel: ERROR: Failed to create alignment path (no MATCH plugin?)" << endl;
Chris@420 118 delete transformOutput;
Chris@420 119 delete aggregateModel;
Chris@420 120 m_error = message;
Chris@420 121 return false;
Chris@420 122 }
Chris@420 123
Chris@420 124 path->setCompletion(0);
Chris@420 125
Chris@420 126 AlignmentModel *alignmentModel = new AlignmentModel
Chris@420 127 (reference, other, aggregateModel, path);
Chris@420 128
Chris@420 129 rm->setAlignment(alignmentModel);
Chris@420 130
Chris@420 131 return true;
Chris@420 132 }
Chris@420 133
Chris@420 134 bool
Chris@422 135 Align::alignModelViaProgram(Model *ref, Model *other, QString program)
Chris@420 136 {
Chris@420 137 WaveFileModel *reference = qobject_cast<WaveFileModel *>(ref);
Chris@420 138 WaveFileModel *rm = qobject_cast<WaveFileModel *>(other);
Chris@420 139
Chris@420 140 if (!rm) return false; // but this should have been tested already
Chris@420 141
Chris@420 142 // Run an external program, passing to it paths to the main
Chris@420 143 // model's audio file and the new model's audio file. It returns
Chris@420 144 // the path in CSV form through stdout.
Chris@420 145
Chris@420 146 QString refPath = reference->getLocalFilename();
Chris@420 147 QString otherPath = rm->getLocalFilename();
Chris@420 148
Chris@420 149 if (refPath == "" || otherPath == "") {
Chris@420 150 m_error = "Failed to find local filepath for wave-file model";
Chris@420 151 return false;
Chris@420 152 }
Chris@420 153
Chris@423 154 m_error = "";
Chris@423 155
Chris@423 156 AlignmentModel *alignmentModel = new AlignmentModel(reference, other, 0, 0);
Chris@423 157 rm->setAlignment(alignmentModel);
Chris@423 158
Chris@423 159 QProcess *process = new QProcess;
Chris@420 160 QStringList args;
Chris@420 161 args << refPath << otherPath;
Chris@423 162
Chris@423 163 connect(process, SIGNAL(finished(int, QProcess::ExitStatus)),
Chris@423 164 this, SLOT(alignmentProgramFinished(int, QProcess::ExitStatus)));
Chris@420 165
Chris@423 166 m_processModels[process] = alignmentModel;
Chris@423 167 process->start(program, args);
Chris@420 168
Chris@423 169 bool success = process->waitForStarted();
Chris@423 170
Chris@423 171 if (!success) {
Chris@423 172 cerr << "ERROR: Align::alignModelViaProgram: Program did not start"
Chris@423 173 << endl;
Chris@423 174 m_error = "Alignment program could not be started";
Chris@423 175 m_processModels.erase(process);
Chris@424 176 rm->setAlignment(0); // deletes alignmentModel as well
Chris@423 177 delete process;
Chris@423 178 }
Chris@423 179
Chris@423 180 return success;
Chris@423 181 }
Chris@423 182
Chris@423 183 void
Chris@423 184 Align::alignmentProgramFinished(int exitCode, QProcess::ExitStatus status)
Chris@423 185 {
Chris@423 186 cerr << "Align::alignmentProgramFinished" << endl;
Chris@423 187
Chris@423 188 QProcess *process = qobject_cast<QProcess *>(sender());
Chris@423 189
Chris@423 190 if (m_processModels.find(process) == m_processModels.end()) {
Chris@423 191 cerr << "ERROR: Align::alignmentProgramFinished: Process " << process
Chris@423 192 << " not found in process model map!" << endl;
Chris@423 193 return;
Chris@423 194 }
Chris@423 195
Chris@423 196 AlignmentModel *alignmentModel = m_processModels[process];
Chris@423 197
Chris@423 198 if (exitCode == 0 && status == 0) {
Chris@420 199
Chris@420 200 CSVFormat format;
Chris@420 201 format.setModelType(CSVFormat::TwoDimensionalModel);
Chris@420 202 format.setTimingType(CSVFormat::ExplicitTiming);
Chris@420 203 format.setTimeUnits(CSVFormat::TimeSeconds);
Chris@420 204 format.setColumnCount(2);
Chris@425 205 // The output format has time in the reference file first, and
Chris@425 206 // time in the "other" file in the second column. This is a
Chris@425 207 // more natural approach for a command-line alignment tool,
Chris@425 208 // but it's the opposite of what we expect for native
Chris@425 209 // alignment paths, which map from "other" file to
Chris@425 210 // reference. These column purpose settings reflect that.
Chris@425 211 format.setColumnPurpose(1, CSVFormat::ColumnStartTime);
Chris@425 212 format.setColumnPurpose(0, CSVFormat::ColumnValue);
Chris@420 213 format.setAllowQuoting(false);
Chris@420 214 format.setSeparator(',');
Chris@420 215
Chris@423 216 CSVFileReader reader(process, format, alignmentModel->getSampleRate());
Chris@420 217 if (!reader.isOK()) {
Chris@423 218 cerr << "ERROR: Align::alignmentProgramFinished: Failed to parse output"
Chris@423 219 << endl;
Chris@420 220 m_error = QString("Failed to parse output of program: %1")
Chris@420 221 .arg(reader.getError());
Chris@423 222 goto done;
Chris@420 223 }
Chris@420 224
Chris@420 225 Model *csvOutput = reader.load();
Chris@420 226
Chris@420 227 SparseTimeValueModel *path = qobject_cast<SparseTimeValueModel *>(csvOutput);
Chris@420 228 if (!path) {
Chris@423 229 cerr << "ERROR: Align::alignmentProgramFinished: Output did not convert to sparse time-value model"
Chris@423 230 << endl;
Chris@420 231 m_error = QString("Output of program did not produce sparse time-value model");
Chris@423 232 goto done;
Chris@420 233 }
Chris@420 234
Chris@420 235 if (path->getPoints().empty()) {
Chris@423 236 cerr << "ERROR: Align::alignmentProgramFinished: Output contained no mappings"
Chris@423 237 << endl;
Chris@420 238 m_error = QString("Output of alignment program contained no mappings");
Chris@423 239 goto done;
Chris@420 240 }
Chris@420 241
Chris@423 242 cerr << "Align::alignmentProgramFinished: Setting alignment path ("
Chris@423 243 << path->getPoints().size() << " point(s))" << endl;
Chris@423 244
Chris@423 245 alignmentModel->setPathFrom(path);
Chris@420 246
Chris@420 247 } else {
Chris@423 248 cerr << "ERROR: Align::alignmentProgramFinished: Aligner program "
Chris@423 249 << "failed: exit code " << exitCode << ", status " << status
Chris@423 250 << endl;
Chris@420 251 m_error = "Aligner process returned non-zero exit status";
Chris@420 252 }
Chris@420 253
Chris@423 254 done:
Chris@423 255 m_processModels.erase(process);
Chris@423 256 delete process;
Chris@420 257 }
Chris@420 258