annotate framework/Align.cpp @ 422:33fae747db7e alignment_view

User preference for alignment program
author Chris Cannam
date Thu, 20 Nov 2014 14:08:01 +0000
parents 662aef012679
children f32a64149602
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@420 154 QProcess process;
Chris@420 155 QStringList args;
Chris@420 156 args << refPath << otherPath;
Chris@420 157 process.start(program, args);
Chris@420 158
Chris@420 159 process.waitForFinished(60000); //!!! nb timeout, but we can do better than blocking anyway
Chris@420 160
Chris@420 161 if (process.exitStatus() == 0) {
Chris@420 162
Chris@420 163 CSVFormat format;
Chris@420 164 format.setModelType(CSVFormat::TwoDimensionalModel);
Chris@420 165 format.setTimingType(CSVFormat::ExplicitTiming);
Chris@420 166 format.setTimeUnits(CSVFormat::TimeSeconds);
Chris@420 167 format.setColumnCount(2);
Chris@420 168 format.setColumnPurpose(0, CSVFormat::ColumnStartTime);
Chris@420 169 format.setColumnPurpose(1, CSVFormat::ColumnValue);
Chris@420 170 format.setAllowQuoting(false);
Chris@420 171 format.setSeparator(',');
Chris@420 172
Chris@420 173 CSVFileReader reader(&process, format, reference->getSampleRate());
Chris@420 174 if (!reader.isOK()) {
Chris@420 175 m_error = QString("Failed to parse output of program: %1")
Chris@420 176 .arg(reader.getError());
Chris@420 177 return false;
Chris@420 178 }
Chris@420 179
Chris@420 180 Model *csvOutput = reader.load();
Chris@420 181
Chris@420 182 SparseTimeValueModel *path = qobject_cast<SparseTimeValueModel *>(csvOutput);
Chris@420 183 if (!path) {
Chris@420 184 m_error = QString("Output of program did not produce sparse time-value model");
Chris@420 185 return false;
Chris@420 186 }
Chris@420 187
Chris@420 188 if (path->getPoints().empty()) {
Chris@420 189 m_error = QString("Output of alignment program contained no mappings");
Chris@420 190 return false;
Chris@420 191 }
Chris@420 192
Chris@420 193 AlignmentModel *alignmentModel = new AlignmentModel
Chris@420 194 (reference, other, 0, path);
Chris@420 195
Chris@420 196 rm->setAlignment(alignmentModel);
Chris@420 197
Chris@420 198 } else {
Chris@420 199 m_error = "Aligner process returned non-zero exit status";
Chris@420 200 return false;
Chris@420 201 }
Chris@420 202
Chris@420 203 cerr << "Align: success" << endl;
Chris@420 204
Chris@420 205 return true;
Chris@420 206 }
Chris@420 207