Mercurial > hg > svapp
changeset 673:d62fd61082a1
Merge from branch tuning-difference
author | Chris Cannam |
---|---|
date | Fri, 17 May 2019 09:46:22 +0100 |
parents | e19c609a7bec (current diff) ae7584dbd668 (diff) |
children | b375fdbb74bc |
files | |
diffstat | 10 files changed, 998 insertions(+), 437 deletions(-) [+] |
line wrap: on
line diff
--- a/audio/AudioGenerator.cpp Thu Apr 04 16:17:11 2019 +0100 +++ b/audio/AudioGenerator.cpp Fri May 17 09:46:22 2019 +0100 @@ -22,11 +22,10 @@ #include "base/Exceptions.h" #include "data/model/NoteModel.h" -#include "data/model/FlexiNoteModel.h" #include "data/model/DenseTimeValueModel.h" #include "data/model/SparseTimeValueModel.h" #include "data/model/SparseOneDimensionalModel.h" -#include "data/model/NoteData.h" +#include "base/NoteData.h" #include "ClipMixer.h" #include "ContinuousSynth.h" @@ -185,8 +184,7 @@ { bool clip = (qobject_cast<const SparseOneDimensionalModel *>(model) || - qobject_cast<const NoteModel *>(model) || - qobject_cast<const FlexiNoteModel *>(model)); + qobject_cast<const NoteModel *>(model)); return clip; } @@ -196,9 +194,7 @@ // basically, anything that usually has sustain (like notes) or // often has multiple sounds at once (like notes) wants to use a // quieter level than simple click tracks - bool does = - (qobject_cast<const NoteModel *>(model) || - qobject_cast<const FlexiNoteModel *>(model)); + bool does = (qobject_cast<const NoteModel *>(model)); return does; } @@ -559,6 +555,8 @@ float **bufferIndexes = new float *[m_targetChannelCount]; + //!!! + for first block, prime with notes already active + for (int i = 0; i < blocks; ++i) { sv_frame_t reqStart = startFrame + i * m_processingBlockSize; @@ -566,8 +564,8 @@ NoteList notes; NoteExportable *exportable = dynamic_cast<NoteExportable *>(model); if (exportable) { - notes = exportable->getNotesWithin(reqStart, - reqStart + m_processingBlockSize); + notes = exportable->getNotesStartingWithin(reqStart, + m_processingBlockSize); } std::vector<ClipMixer::NoteStart> starts; @@ -714,32 +712,28 @@ bufferIndexes[c] = buffer[c] + i * m_processingBlockSize; } - SparseTimeValueModel::PointList points = - stvm->getPoints(reqStart, reqStart + m_processingBlockSize); + EventVector points = + stvm->getEventsStartingWithin(reqStart, m_processingBlockSize); // by default, repeat last frequency float f0 = 0.f; - // go straight to the last freq that is genuinely in this range - for (SparseTimeValueModel::PointList::const_iterator itr = points.end(); - itr != points.begin(); ) { - --itr; - if (itr->frame >= reqStart && - itr->frame < reqStart + m_processingBlockSize) { - f0 = itr->value; - break; - } + // go straight to the last freq in this range + if (!points.empty()) { + f0 = points.rbegin()->getValue(); } - // if we found no such frequency and the next point is further + // if there is no such frequency and the next point is further // away than twice the model resolution, go silent (same // criterion TimeValueLayer uses for ending a discrete curve // segment) if (f0 == 0.f) { - SparseTimeValueModel::PointList nextPoints = - stvm->getNextPoints(reqStart + m_processingBlockSize); - if (nextPoints.empty() || - nextPoints.begin()->frame > reqStart + 2 * stvm->getResolution()) { + Event nextP; + if (!stvm->getNearestEventMatching(reqStart + m_processingBlockSize, + [](Event) { return true; }, + EventSeries::Forward, + nextP) || + nextP.getFrame() > reqStart + 2 * stvm->getResolution()) { f0 = -1.f; } }
--- a/files.pri Thu Apr 04 16:17:11 2019 +0100 +++ b/files.pri Fri May 17 09:46:22 2019 +0100 @@ -9,6 +9,7 @@ framework/Align.h \ framework/Document.h \ framework/MainWindowBase.h \ + framework/OSCScript.h \ framework/SVFileReader.h \ framework/TransformUserConfigurator.h \ framework/VersionTester.h
--- 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; }
--- a/framework/Align.h Thu Apr 04 16:17:11 2019 +0100 +++ b/framework/Align.h Fri May 17 09:46: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,30 @@ 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; + + struct TuningDiffRec { + AggregateWaveModel *input; + AlignmentModel *alignment; + SparseTimeValueModel *preparatory; + }; + + // tuning-difference output model -> data needed for subsequent alignment + std::map<SparseTimeValueModel *, TuningDiffRec> m_pendingTuningDiffs; + + // external alignment subprocess -> model into which to stuff the results + std::map<QProcess *, AlignmentModel *> m_pendingProcesses; }; #endif
--- a/framework/Document.cpp Thu Apr 04 16:17:11 2019 +0100 +++ b/framework/Document.cpp Fri May 17 09:46:22 2019 +0100 @@ -21,7 +21,6 @@ #include "data/model/WritableWaveFileModel.h" #include "data/model/DenseThreeDimensionalModel.h" #include "data/model/DenseTimeValueModel.h" -#include "data/model/FlexiNoteModel.h" #include "data/model/AggregateWaveModel.h" #include "layer/Layer.h" @@ -75,7 +74,7 @@ //the document, be nice to fix that #ifdef DEBUG_DOCUMENT - cerr << "\n\nDocument::~Document: about to clear command history" << endl; + SVDEBUG << "\n\nDocument::~Document: about to clear command history" << endl; #endif CommandHistory::getInstance()->clear(); @@ -91,8 +90,8 @@ << m_models.size() << " model(s) still remain -- " << "should have been garbage collected when deleting layers" << endl; - while (!m_models.empty()) { - Model *model = m_models.begin()->first; + for (ModelRecord &rec: m_models) { + Model *model = rec.model; if (model == m_mainModel) { // just in case! SVDEBUG << "Document::~Document: WARNING: Main model is also" @@ -102,8 +101,8 @@ emit modelAboutToBeDeleted(model); delete model; } - m_models.erase(m_models.begin()); } + m_models.clear(); } #ifdef DEBUG_DOCUMENT @@ -127,7 +126,7 @@ newLayer->setObjectName(getUniqueLayerName(newLayer->objectName())); - m_layers.insert(newLayer); + m_layers.push_back(newLayer); #ifdef DEBUG_DOCUMENT SVDEBUG << "Document::createLayer: Added layer of type " << type @@ -155,7 +154,7 @@ LayerFactory::getInstance()->getValidLayerTypes(model); if (types.empty()) { - cerr << "WARNING: Document::importLayer: no valid display layer for model" << endl; + SVCERR << "WARNING: Document::importLayer: no valid display layer for model" << endl; return nullptr; } @@ -173,7 +172,7 @@ //!!! and all channels setChannel(newLayer, -1); - m_layers.insert(newLayer); + m_layers.push_back(newLayer); #ifdef DEBUG_DOCUMENT SVDEBUG << "Document::createImportedLayer: Added layer of type " << type @@ -277,7 +276,7 @@ void moreModelsAvailable(vector<Model *> models) override { - std::cerr << "AdditionalModelConverter::moreModelsAvailable: " << models.size() << " model(s)" << std::endl; + SVDEBUG << "AdditionalModelConverter::moreModelsAvailable: " << models.size() << " model(s)" << endl; // We can't automatically regenerate the additional models on // reload -- we should delete them instead QStringList names; @@ -293,7 +292,7 @@ void noMoreModelsAvailable() override { - std::cerr << "AdditionalModelConverter::noMoreModelsAvailable" << std::endl; + SVDEBUG << "AdditionalModelConverter::noMoreModelsAvailable" << endl; m_handler->layersCreated(this, m_primary, vector<Layer *>()); delete this; } @@ -370,12 +369,9 @@ LayerFactory::getInstance()->getValidLayerTypes(newModel); if (types.empty()) { - cerr << "WARNING: Document::createLayerForTransformer: no valid display layer for output of transform " << names[i] << endl; + SVCERR << "WARNING: Document::createLayerForTransformer: no valid display layer for output of transform " << names[i] << endl; //!!! inadequate cleanup: - newModel->aboutToDelete(); - emit modelAboutToBeDeleted(newModel); - m_models.erase(newModel); - delete newModel; + deleteModelFromList(newModel); return vector<Layer *>(); } @@ -433,62 +429,64 @@ // delete any of the models. #ifdef DEBUG_DOCUMENT - cerr << "Document::setMainModel: Have " + SVDEBUG << "Document::setMainModel: Have " << m_layers.size() << " layers" << endl; - cerr << "Models now: "; - for (ModelMap::const_iterator i = m_models.begin(); i != m_models.end(); ++i) { - cerr << i->first << " "; - } - cerr << endl; - cerr << "Old main model: " << oldMainModel << endl; + SVDEBUG << "Models now: "; + for (const auto &r: m_models) { + SVDEBUG << r.model << " "; + } + SVDEBUG << endl; + SVDEBUG << "Old main model: " << oldMainModel << endl; #endif - for (LayerSet::iterator i = m_layers.begin(); i != m_layers.end(); ++i) { + for (Layer *layer: m_layers) { - Layer *layer = *i; Model *model = layer->getModel(); #ifdef DEBUG_DOCUMENT - cerr << "Document::setMainModel: inspecting model " - << (model ? model->objectName(): "(null)") << " in layer " - << layer->objectName() << endl; + SVDEBUG << "Document::setMainModel: inspecting model " + << (model ? model->objectName(): "(null)") << " in layer " + << layer->objectName() << endl; #endif if (model == oldMainModel) { #ifdef DEBUG_DOCUMENT - cerr << "... it uses the old main model, replacing" << endl; + SVDEBUG << "... it uses the old main model, replacing" << endl; #endif LayerFactory::getInstance()->setModel(layer, m_mainModel); continue; } if (!model) { - cerr << "WARNING: Document::setMainModel: Null model in layer " - << layer << endl; + SVCERR << "WARNING: Document::setMainModel: Null model in layer " + << layer << endl; // get rid of this hideous degenerate obsoleteLayers.push_back(layer); continue; } - if (m_models.find(model) == m_models.end()) { - cerr << "WARNING: Document::setMainModel: Unknown model " - << model << " in layer " << layer << endl; + auto mitr = findModelInList(model); + + if (mitr == m_models.end()) { + SVCERR << "WARNING: Document::setMainModel: Unknown model " + << model << " in layer " << layer << endl; // and this one obsoleteLayers.push_back(layer); continue; } - - if (m_models[model].source && - (m_models[model].source == oldMainModel)) { + + ModelRecord record = *mitr; + + if (record.source && (record.source == oldMainModel)) { #ifdef DEBUG_DOCUMENT - cerr << "... it uses a model derived from the old main model, regenerating" << endl; + SVDEBUG << "... it uses a model derived from the old main model, regenerating" << endl; #endif // This model was derived from the previous main // model: regenerate it. - const Transform &transform = m_models[model].transform; + const Transform &transform = record.transform; QString transformId = transform.getIdentifier(); //!!! We have a problem here if the number of channels in @@ -498,12 +496,12 @@ Model *replacementModel = addDerivedModel(transform, ModelTransformer::Input - (m_mainModel, m_models[model].channel), + (m_mainModel, record.channel), message); if (!replacementModel) { - cerr << "WARNING: Document::setMainModel: Failed to regenerate model for transform \"" - << transformId << "\"" << " in layer " << layer << endl; + SVCERR << "WARNING: Document::setMainModel: Failed to regenerate model for transform \"" + << transformId << "\"" << " in layer " << layer << endl; if (failedTransformers.find(transformId) == failedTransformers.end()) { emit modelRegenerationFailed(layer->objectName(), @@ -519,19 +517,19 @@ message); } #ifdef DEBUG_DOCUMENT - cerr << "Replacing model " << model << " (type " - << typeid(*model).name() << ") with model " - << replacementModel << " (type " - << typeid(*replacementModel).name() << ") in layer " - << layer << " (name " << layer->objectName() << ")" - << endl; + SVDEBUG << "Replacing model " << model << " (type " + << typeid(*model).name() << ") with model " + << replacementModel << " (type " + << typeid(*replacementModel).name() << ") in layer " + << layer << " (name " << layer->objectName() << ")" + << endl; RangeSummarisableTimeValueModel *rm = dynamic_cast<RangeSummarisableTimeValueModel *>(replacementModel); if (rm) { - cerr << "new model has " << rm->getChannelCount() << " channels " << endl; + SVDEBUG << "new model has " << rm->getChannelCount() << " channels " << endl; } else { - cerr << "new model " << replacementModel << " is not a RangeSummarisableTimeValueModel!" << endl; + SVDEBUG << "new model " << replacementModel << " is not a RangeSummarisableTimeValueModel!" << endl; } #endif setModel(layer, replacementModel); @@ -543,18 +541,19 @@ deleteLayer(obsoleteLayers[k], true); } - for (ModelMap::iterator i = m_models.begin(); i != m_models.end(); ++i) { - if (i->second.additional) { - Model *m = i->first; - m->aboutToDelete(); - emit modelAboutToBeDeleted(m); - delete m; + std::set<Model *> additionalModels; + for (const auto &rec : m_models) { + if (rec.additional) { + additionalModels.insert(rec.model); } } + for (Model *a: additionalModels) { + deleteModelFromList(a); + } - for (ModelMap::iterator i = m_models.begin(); i != m_models.end(); ++i) { + for (const auto &rec : m_models) { - Model *m = i->first; + Model *m = rec.model; #ifdef DEBUG_DOCUMENT SVDEBUG << "considering alignment for model " << m << " (name \"" @@ -580,6 +579,8 @@ if (m_autoAlignment) { SVDEBUG << "Document::setMainModel: auto-alignment is on, aligning model if possible" << endl; alignModel(m_mainModel); + } else { + SVDEBUG << "Document::setMainModel: auto-alignment is off" << endl; } emit mainModelChanged(m_mainModel); @@ -592,21 +593,22 @@ const ModelTransformer::Input &input, Model *outputModelToAdd) { - if (m_models.find(outputModelToAdd) != m_models.end()) { - cerr << "WARNING: Document::addAlreadyDerivedModel: Model already added" + if (findModelInList(outputModelToAdd) != m_models.end()) { + SVCERR << "WARNING: Document::addAlreadyDerivedModel: Model already added" << endl; return; } #ifdef DEBUG_DOCUMENT if (input.getModel()) { - cerr << "Document::addAlreadyDerivedModel: source is " << input.getModel() << " \"" << input.getModel()->objectName() << "\"" << endl; + SVDEBUG << "Document::addAlreadyDerivedModel: source is " << input.getModel() << " \"" << input.getModel()->objectName() << "\"" << endl; } else { - cerr << "Document::addAlreadyDerivedModel: source is " << input.getModel() << endl; + SVDEBUG << "Document::addAlreadyDerivedModel: source is " << input.getModel() << endl; } #endif ModelRecord rec; + rec.model = outputModelToAdd; rec.source = input.getModel(); rec.channel = input.getChannel(); rec.transform = transform; @@ -615,15 +617,15 @@ outputModelToAdd->setSourceModel(input.getModel()); - m_models[outputModelToAdd] = rec; + m_models.push_back(rec); #ifdef DEBUG_DOCUMENT - cerr << "Document::addAlreadyDerivedModel: Added model " << outputModelToAdd << endl; - cerr << "Models now: "; - for (ModelMap::const_iterator i = m_models.begin(); i != m_models.end(); ++i) { - cerr << i->first << " "; + SVDEBUG << "Document::addAlreadyDerivedModel: Added model " << outputModelToAdd << endl; + SVDEBUG << "Models now: "; + for (const auto &rec : m_models) { + SVDEBUG << rec.model << " "; } - cerr << endl; + SVDEBUG << endl; #endif emit modelAdded(outputModelToAdd); @@ -633,27 +635,28 @@ void Document::addImportedModel(Model *model) { - if (m_models.find(model) != m_models.end()) { - cerr << "WARNING: Document::addImportedModel: Model already added" + if (findModelInList(model) != m_models.end()) { + SVCERR << "WARNING: Document::addImportedModel: Model already added" << endl; return; } ModelRecord rec; + rec.model = model; rec.source = nullptr; rec.channel = 0; rec.refcount = 0; rec.additional = false; - m_models[model] = rec; + m_models.push_back(rec); #ifdef DEBUG_DOCUMENT SVDEBUG << "Document::addImportedModel: Added model " << model << endl; - cerr << "Models now: "; - for (ModelMap::const_iterator i = m_models.begin(); i != m_models.end(); ++i) { - cerr << i->first << " "; + SVDEBUG << "Models now: "; + for (const auto &rec : m_models) { + SVDEBUG << rec.model << " "; } - cerr << endl; + SVDEBUG << endl; #endif if (m_autoAlignment) { @@ -669,27 +672,28 @@ void Document::addAdditionalModel(Model *model) { - if (m_models.find(model) != m_models.end()) { - cerr << "WARNING: Document::addAdditionalModel: Model already added" + if (findModelInList(model) != m_models.end()) { + SVCERR << "WARNING: Document::addAdditionalModel: Model already added" << endl; return; } ModelRecord rec; + rec.model = model; rec.source = nullptr; rec.channel = 0; rec.refcount = 0; rec.additional = true; - m_models[model] = rec; + m_models.push_back(rec); #ifdef DEBUG_DOCUMENT SVDEBUG << "Document::addAdditionalModel: Added model " << model << endl; - cerr << "Models now: "; - for (ModelMap::const_iterator i = m_models.begin(); i != m_models.end(); ++i) { - cerr << i->first << " "; + SVDEBUG << "Models now: "; + for (const auto &rec : m_models) { + SVDEBUG << rec.model << " "; } - cerr << endl; + SVDEBUG << endl; #endif if (m_autoAlignment) { @@ -706,6 +710,7 @@ connect(model, SIGNAL(modelInvalidated()), this, SLOT(aggregateModelInvalidated())); m_aggregateModels.insert(model); + SVDEBUG << "Document::addAggregateModel(" << model << ")" << endl; } void @@ -713,6 +718,7 @@ { QObject *s = sender(); AggregateWaveModel *aggregate = qobject_cast<AggregateWaveModel *>(s); + SVDEBUG << "Document::aggregateModelInvalidated(" << aggregate << ")" << endl; if (aggregate) releaseModel(aggregate); } @@ -721,12 +727,12 @@ const ModelTransformer::Input &input, QString &message) { - for (ModelMap::iterator i = m_models.begin(); i != m_models.end(); ++i) { - if (i->second.transform == transform && - i->second.source == input.getModel() && - i->second.channel == input.getChannel()) { - std::cerr << "derived model taken from map " << std::endl; - return i->first; + for (auto &rec : m_models) { + if (rec.transform == transform && + rec.source == input.getModel() && + rec.channel == input.getChannel()) { + SVDEBUG << "derived model taken from map " << endl; + return rec.model; } } @@ -771,7 +777,7 @@ .getPluginVersion()); if (!model) { - cerr << "WARNING: Document::addDerivedModel: no output model for transform " << applied.getIdentifier() << endl; + SVCERR << "WARNING: Document::addDerivedModel: no output model for transform " << applied.getIdentifier() << endl; } else { addAlreadyDerivedModel(applied, input, model); } @@ -799,12 +805,17 @@ bool toDelete = false; - if (m_models.find(model) != m_models.end()) { - if (m_models[model].refcount == 0) { + ModelList::iterator mitr = findModelInList(model); + + if (mitr != m_models.end()) { + if (mitr->refcount == 0) { SVCERR << "WARNING: Document::releaseModel: model " << model << " reference count is zero already!" << endl; } else { - if (--m_models[model].refcount == 0) { +#ifdef DEBUG_DOCUMENT + SVDEBUG << "Lowering refcount from " << mitr->refcount << endl; +#endif + if (--mitr->refcount == 0) { toDelete = true; } } @@ -823,10 +834,10 @@ int sourceCount = 0; - for (ModelMap::iterator i = m_models.begin(); i != m_models.end(); ++i) { - if (i->second.source == model) { + for (auto &rec: m_models) { + if (rec.source == model) { ++sourceCount; - i->second.source = nullptr; + rec.source = nullptr; } } @@ -837,20 +848,16 @@ << "their source fields appropriately" << endl; } - model->aboutToDelete(); - emit modelAboutToBeDeleted(model); - m_models.erase(model); + deleteModelFromList(model); #ifdef DEBUG_DOCUMENT SVDEBUG << "Document::releaseModel: Deleted model " << model << endl; - cerr << "Models now: "; - for (ModelMap::const_iterator i = m_models.begin(); i != m_models.end(); ++i) { - cerr << i->first << " "; + SVDEBUG << "Models now: "; + for (const auto &r: m_models) { + SVDEBUG << r.model << " "; } - cerr << endl; + SVDEBUG << endl; #endif - - delete model; } } @@ -860,17 +867,13 @@ if (m_layerViewMap.find(layer) != m_layerViewMap.end() && m_layerViewMap[layer].size() > 0) { - cerr << "WARNING: Document::deleteLayer: Layer " - << layer << " [" << layer->objectName() << "]" - << " is still used in " << m_layerViewMap[layer].size() - << " views!" << endl; - if (force) { -#ifdef DEBUG_DOCUMENT - cerr << "(force flag set -- deleting from all views)" << endl; -#endif - + SVDEBUG << "NOTE: Document::deleteLayer: Layer " + << layer << " [" << layer->objectName() << "]" + << " is still used in " << m_layerViewMap[layer].size() + << " views. Force flag set, so removing from them" << endl; + for (std::set<View *>::iterator j = m_layerViewMap[layer].begin(); j != m_layerViewMap[layer].end(); ++j) { // don't use removeLayerFromView, as it issues a command @@ -881,11 +884,25 @@ m_layerViewMap.erase(layer); } else { + + SVCERR << "WARNING: Document::deleteLayer: Layer " + << layer << " [" << layer->objectName() << "]" + << " is still used in " << m_layerViewMap[layer].size() + << " views! Force flag is not set, so not deleting" << endl; + return; } } - if (m_layers.find(layer) == m_layers.end()) { + bool found = false; + for (auto itr = m_layers.begin(); itr != m_layers.end(); ++itr) { + if (*itr == layer) { + found = true; + m_layers.erase(itr); + break; + } + } + if (!found) { SVDEBUG << "Document::deleteLayer: Layer " << layer << " (typeid " << typeid(layer).name() << ") does not exist, or has already been deleted " @@ -893,8 +910,6 @@ return; } - m_layers.erase(layer); - #ifdef DEBUG_DOCUMENT SVDEBUG << "Document::deleteLayer: Removing (and about to release model), now have " << m_layers.size() << " layers" << endl; @@ -911,8 +926,8 @@ { if (model && model != m_mainModel && - m_models.find(model) == m_models.end()) { - cerr << "ERROR: Document::setModel: Layer " << layer + findModelInList(model) == m_models.end()) { + SVCERR << "ERROR: Document::setModel: Layer " << layer << " (\"" << layer->objectName() << "\") wants to use unregistered model " << model << ": register the layer's model before setting it!" @@ -932,7 +947,10 @@ } if (model && model != m_mainModel) { - m_models[model].refcount ++; + ModelList::iterator mitr = findModelInList(model); + if (mitr != m_models.end()) { + mitr->refcount ++; + } } if (model && previousModel) { @@ -941,7 +959,6 @@ } LayerFactory::getInstance()->setModel(layer, model); - // std::cerr << "layer type: " << LayerFactory::getInstance()->getLayerTypeName(LayerFactory::getInstance()->getLayerType(layer)) << std::endl; if (previousModel) { releaseModel(previousModel); @@ -961,13 +978,14 @@ if (!model) { #ifdef DEBUG_DOCUMENT SVDEBUG << "Document::addLayerToView: Layer (\"" - << layer->objectName() << "\") with no model being added to view: " + << layer->objectName() + << "\") with no model being added to view: " << "normally you want to set the model first" << endl; #endif } else { if (model != m_mainModel && - m_models.find(model) == m_models.end()) { - cerr << "ERROR: Document::addLayerToView: Layer " << layer + findModelInList(model) == m_models.end()) { + SVCERR << "ERROR: Document::addLayerToView: Layer " << layer << " has unregistered model " << model << " -- register the layer's model before adding the layer!" << endl; return; @@ -993,7 +1011,7 @@ if (m_layerViewMap[layer].find(view) != m_layerViewMap[layer].end()) { - cerr << "WARNING: Document::addToLayerViewMap:" + SVCERR << "WARNING: Document::addToLayerViewMap:" << " Layer " << layer << " -> view " << view << " already in" << " layer view map -- internal inconsistency" << endl; } @@ -1008,7 +1026,7 @@ { if (m_layerViewMap[layer].find(view) == m_layerViewMap[layer].end()) { - cerr << "WARNING: Document::removeFromLayerViewMap:" + SVCERR << "WARNING: Document::removeFromLayerViewMap:" << " Layer " << layer << " -> view " << view << " not in" << " layer view map -- internal inconsistency" << endl; } @@ -1032,7 +1050,7 @@ bool duplicate = false; - for (LayerSet::iterator i = m_layers.begin(); i != m_layers.end(); ++i) { + for (auto i = m_layers.begin(); i != m_layers.end(); ++i) { if ((*i)->objectName() == adjusted) { duplicate = true; break; @@ -1054,9 +1072,9 @@ //!!! This will pick up all models, including those that aren't visible... - for (ModelMap::iterator i = m_models.begin(); i != m_models.end(); ++i) { + for (ModelRecord &rec: m_models) { - Model *model = i->first; + Model *model = rec.model; if (!model || model == m_mainModel) continue; DenseTimeValueModel *dtvm = dynamic_cast<DenseTimeValueModel *>(model); @@ -1072,7 +1090,10 @@ Document::isKnownModel(const Model *model) const { if (model == m_mainModel) return true; - return (m_models.find(const_cast<Model *>(model)) != m_models.end()); + for (const ModelRecord &rec: m_models) { + if (rec.model == model) return true; + } + return false; } bool @@ -1082,25 +1103,37 @@ } void -Document::alignModel(Model *model) +Document::alignModel(Model *model, bool forceRecalculate) { - SVDEBUG << "Document::alignModel(" << model << ")" << endl; - - if (!m_mainModel) { - SVDEBUG << "(no main model to align to)" << endl; - return; - } + SVDEBUG << "Document::alignModel(" << model << ", " << forceRecalculate + << ") (main model is " << m_mainModel << ")" << endl; RangeSummarisableTimeValueModel *rm = dynamic_cast<RangeSummarisableTimeValueModel *>(model); if (!rm) { - SVDEBUG << "(main model is not alignable-to)" << endl; + SVDEBUG << "(model " << rm << " is not an alignable sort)" << endl; + return; + } + + if (!m_mainModel) { + SVDEBUG << "(no main model to align to)" << endl; + if (forceRecalculate && rm->getAlignment()) { + SVDEBUG << "(but model is aligned, and forceRecalculate is true, " + << "so resetting alignment to nil)" << endl; + rm->setAlignment(nullptr); + } return; } if (rm->getAlignmentReference() == m_mainModel) { - SVDEBUG << "(model " << rm << " is already aligned to main model " << m_mainModel << ")" << endl; - return; + SVDEBUG << "(model " << rm << " is already aligned to main model " + << m_mainModel << ")" << endl; + if (!forceRecalculate) { + return; + } else { + SVDEBUG << "(but forceRecalculate is true, so realigning anyway)" + << endl; + } } if (model == m_mainModel) { @@ -1108,22 +1141,40 @@ // it possible to distinguish between the reference and any // unaligned model just by looking at the model itself, // without also knowing what the main model is - SVDEBUG << "Document::alignModel(" << model << "): is main model, setting appropriately" << endl; + SVDEBUG << "Document::alignModel(" << model + << "): is main model, setting alignment to itself" << endl; rm->setAlignment(new AlignmentModel(model, model, nullptr)); return; } - if (!m_align->alignModel(this, m_mainModel, rm)) { - SVCERR << "Alignment failed: " << m_align->getError() << endl; - emit alignmentFailed(m_align->getError()); + SVDEBUG << "Document::alignModel: aligning..." << endl; + if (rm->getAlignmentReference() != nullptr) { + SVDEBUG << "(Note: model " << rm << " is currently aligned to model " + << rm->getAlignmentReference() << "; this will replace that)" + << endl; + } + + QString err; + if (!m_align->alignModel(this, m_mainModel, rm, err)) { + SVCERR << "Alignment failed: " << err << endl; + emit alignmentFailed(err); } } void Document::alignModels() { - for (ModelMap::iterator i = m_models.begin(); i != m_models.end(); ++i) { - alignModel(i->first); + for (const ModelRecord &rec: m_models) { + alignModel(rec.model); + } + alignModel(m_mainModel); +} + +void +Document::realignModels() +{ + for (const ModelRecord &rec: m_models) { + alignModel(rec.model, true); } alignModel(m_mainModel); } @@ -1275,6 +1326,10 @@ if (m_mainModel) { +#ifdef DEBUG_DOCUMENT + SVDEBUG << "Document::toXml: writing main model" << endl; +#endif + if (asTemplate) { writePlaceholderMainModel(out, indent + " "); } else { @@ -1287,8 +1342,12 @@ playParameters->toXml (out, indent + " ", QString("model=\"%1\"") - .arg(XmlExportable::getObjectExportId(m_mainModel))); + .arg(m_mainModel->getExportId())); } + } else { +#ifdef DEBUG_DOCUMENT + SVDEBUG << "Document::toXml: have no main model to write" << endl; +#endif } // Models that are not used in a layer that is in a view should @@ -1326,16 +1385,22 @@ for (std::set<Model *>::iterator i = m_aggregateModels.begin(); i != m_aggregateModels.end(); ++i) { - SVDEBUG << "checking aggregate model " << *i << endl; +#ifdef DEBUG_DOCUMENT + SVDEBUG << "Document::toXml: checking aggregate model " << *i << endl; +#endif AggregateWaveModel *aggregate = qobject_cast<AggregateWaveModel *>(*i); if (!aggregate) continue; if (used.find(aggregate) == used.end()) { +#ifdef DEBUG_DOCUMENT SVDEBUG << "(unused, skipping)" << endl; +#endif continue; } +#ifdef DEBUG_DOCUMENT SVDEBUG << "(used, writing)" << endl; +#endif aggregate->toXml(out, indent + " "); } @@ -1352,14 +1417,19 @@ const int nonDerivedPass = 0, derivedPass = 1; for (int pass = nonDerivedPass; pass <= derivedPass; ++pass) { - for (ModelMap::const_iterator i = m_models.begin(); - i != m_models.end(); ++i) { + for (const ModelRecord &rec: m_models) { - Model *model = i->first; - const ModelRecord &rec = i->second; + Model *model = rec.model; if (used.find(model) == used.end()) continue; +#ifdef DEBUG_DOCUMENT + SVDEBUG << "Document::toXml: looking at model " << model + << " (" << model->getTypeName() << ", \"" + << model->objectName() << "\") [pass = " + << pass << "]" << endl; +#endif + // We need an intelligent way to determine which models // need to be streamed (i.e. have been edited, or are // small) and which should not be (i.e. remain as @@ -1418,7 +1488,7 @@ playParameters->toXml (out, indent + " ", QString("model=\"%1\"") - .arg(XmlExportable::getObjectExportId(model))); + .arg(model->getExportId())); } } } @@ -1439,9 +1509,7 @@ alignment->toXml(out, indent + " "); } - for (LayerSet::const_iterator i = m_layers.begin(); - i != m_layers.end(); ++i) { - + for (auto i = m_layers.begin(); i != m_layers.end(); ++i) { (*i)->toXml(out, indent + " "); } @@ -1453,7 +1521,7 @@ { out << indent; out << QString("<model id=\"%1\" name=\"placeholder\" sampleRate=\"%2\" type=\"wavefile\" file=\":samples/silent.wav\" mainModel=\"true\"/>\n") - .arg(getObjectExportId(m_mainModel)) + .arg(m_mainModel->getExportId()) .arg(m_mainModel->getSampleRate()); } @@ -1492,8 +1560,8 @@ // out << indent // << QString("<derivation type=\"transform\" source=\"%1\" " // "model=\"%2\" channel=\"%3\">\n") - // .arg(XmlExportable::getObjectExportId(rec.source)) - // .arg(XmlExportable::getObjectExportId(targetModel)) + // .arg(rec.source->getExportId()) + // .arg(targetModel->getExportId()) // .arg(rec.channel); // // transform.toXml(out, indent + " "); @@ -1517,8 +1585,8 @@ "model=\"%2\" channel=\"%3\" domain=\"%4\" " "stepSize=\"%5\" blockSize=\"%6\" %7windowType=\"%8\" " "transform=\"%9\">\n") - .arg(XmlExportable::getObjectExportId(rec.source)) - .arg(XmlExportable::getObjectExportId(targetModel)) + .arg(rec.source->getExportId()) + .arg(targetModel->getExportId()) .arg(rec.channel) .arg(TransformFactory::getInstance()->getTransformInputDomain (transform.getIdentifier()))
--- a/framework/Document.h Thu Apr 04 16:17:11 2019 +0100 +++ b/framework/Document.h Fri May 17 09:46:22 2019 +0100 @@ -294,6 +294,12 @@ void alignModels(); /** + * Re-generate alignments for all appropriate models against the + * main model. Existing alignments will be re-calculated. + */ + void realignModels(); + + /** * Return true if any external files (most obviously audio) failed * to be found on load, so that the document is incomplete * compared to its saved description. @@ -338,11 +344,11 @@ /** * If model is suitable for alignment, align it against the main - * model and store the alignment in the model. (If the model has - * an alignment already for the current main model, leave it - * unchanged.) + * model and store the alignment in the model. If the model has an + * alignment already for the current main model, leave it + * unchanged unless forceRecalculate is true. */ - void alignModel(Model *); + void alignModel(Model *, bool forceRecalculate = false); /* * Every model that is in use by a layer in the document must be @@ -370,6 +376,7 @@ // be confusing to have Input objects hanging around with NULL // models in them. + Model *model; const Model *source; int channel; Transform transform; @@ -379,8 +386,41 @@ int refcount; }; - typedef std::map<Model *, ModelRecord> ModelMap; - ModelMap m_models; + // This used to be a map<Model *, ModelRecord>, but a vector + // ensures that models are consistently recorded in order of + // creation rather than at the whim of heap allocation, and that's + // useful for automated testing. We don't expect ever to have so + // many models that there is any significant overhead there. + typedef std::vector<ModelRecord> ModelList; + ModelList m_models; + + ModelList::iterator findModelInList(Model *m) { + for (ModelList::iterator i = m_models.begin(); + i != m_models.end(); ++i) { + if (i->model == m) { + return i; + } + } + return m_models.end(); + } + + void deleteModelFromList(Model *m) { + ModelList keep; + bool found = false; + for (const ModelRecord &rec: m_models) { + if (rec.model == m) { + found = true; + m->aboutToDelete(); + emit modelAboutToBeDeleted(m); + } else { + keep.push_back(rec); + } + } + m_models = keep; + if (found) { + delete m; + } + } /** * Add an extra derived model (returned at the end of processing a @@ -447,8 +487,8 @@ * And these are the layers. We also control the lifespans of * these (usually through the commands used to add and remove them). */ - typedef std::set<Layer *> LayerSet; - LayerSet m_layers; + typedef std::vector<Layer *> LayerList; + LayerList m_layers; bool m_autoAlignment; Align *m_align;
--- a/framework/MainWindowBase.cpp Thu Apr 04 16:17:11 2019 +0100 +++ b/framework/MainWindowBase.cpp Fri May 17 09:46:22 2019 +0100 @@ -22,7 +22,6 @@ #include "data/model/WritableWaveFileModel.h" #include "data/model/SparseOneDimensionalModel.h" #include "data/model/NoteModel.h" -#include "data/model/FlexiNoteModel.h" #include "data/model/Labeller.h" #include "data/model/TabularModel.h" #include "view/ViewManager.h" @@ -55,10 +54,12 @@ #include "data/fileio/PlaylistFileReader.h" #include "data/fileio/WavFileWriter.h" #include "data/fileio/MIDIFileWriter.h" +#include "data/fileio/CSVFileWriter.h" #include "data/fileio/BZipFileDevice.h" #include "data/fileio/FileSource.h" #include "data/fileio/AudioFileReaderFactory.h" #include "rdf/RDFImporter.h" +#include "rdf/RDFExporter.h" #include "base/RecentFiles.h" @@ -72,6 +73,7 @@ #include "data/osc/OSCQueue.h" #include "data/midi/MIDIInput.h" +#include "OSCScript.h" #include "system/System.h" @@ -149,6 +151,7 @@ m_audioIO(nullptr), m_oscQueue(nullptr), m_oscQueueStarter(nullptr), + m_oscScript(nullptr), m_midiInput(nullptr), m_recentFiles("RecentFiles", 20), m_recentTransforms("RecentTransforms", 20), @@ -328,6 +331,17 @@ delete m_viewManager; delete m_midiInput; + if (m_oscScript) { + disconnect(m_oscScript, nullptr, nullptr, nullptr); + m_oscScript->abandon(); + m_oscScript->wait(1000); + if (m_oscScript->isRunning()) { + m_oscScript->terminate(); + m_oscScript->wait(1000); + } + delete m_oscScript; + } + if (m_oscQueueStarter) { disconnect(m_oscQueueStarter, nullptr, nullptr, nullptr); m_oscQueueStarter->wait(1000); @@ -501,9 +515,9 @@ } void -MainWindowBase::startOSCQueue() +MainWindowBase::startOSCQueue(bool withNetworkPort) { - m_oscQueueStarter = new OSCQueueStarter(this); + m_oscQueueStarter = new OSCQueueStarter(this, withNetworkPort); connect(m_oscQueueStarter, SIGNAL(finished()), this, SLOT(oscReady())); m_oscQueueStarter->start(); } @@ -516,10 +530,45 @@ QTimer *oscTimer = new QTimer(this); connect(oscTimer, SIGNAL(timeout()), this, SLOT(pollOSC())); oscTimer->start(1000); - SVCERR << "Finished setting up OSC interface" << endl; + + if (m_oscQueue->hasPort()) { + SVDEBUG << "Finished setting up OSC interface" << endl; + } else { + SVDEBUG << "Finished setting up internal-only OSC queue" << endl; + } + + if (m_oscScriptFile != QString()) { + startOSCScript(); + } } } +void +MainWindowBase::startOSCScript() +{ + m_oscScript = new OSCScript(m_oscScriptFile, m_oscQueue); + connect(m_oscScript, SIGNAL(finished()), + this, SLOT(oscScriptFinished())); + m_oscScriptFile = QString(); + m_oscScript->start(); +} + +void +MainWindowBase::cueOSCScript(QString fileName) +{ + m_oscScriptFile = fileName; + if (m_oscQueue && m_oscQueue->isOK()) { + startOSCScript(); + } +} + +void +MainWindowBase::oscScriptFinished() +{ + delete m_oscScript; + m_oscScript = 0; +} + QString MainWindowBase::getOpenFileName(FileFinder::FileType type) { @@ -699,16 +748,46 @@ } void +MainWindowBase::updateWindowTitle() +{ + QString title; + + if (m_sessionFile != "") { + if (m_originalLocation != "" && + m_originalLocation != m_sessionFile) { // session + location + title = tr("%1: %2 [%3]") + .arg(QApplication::applicationName()) + .arg(QFileInfo(m_sessionFile).fileName()) + .arg(m_originalLocation); + } else { // session only + title = tr("%1: %2") + .arg(QApplication::applicationName()) + .arg(QFileInfo(m_sessionFile).fileName()); + } + } else { + if (m_originalLocation != "") { // location only + title = tr("%1: %2") + .arg(QApplication::applicationName()) + .arg(m_originalLocation); + } else { // neither + title = QApplication::applicationName(); + } + } + + if (m_documentModified) { + title = tr("%1 (modified)").arg(title); + } + + setWindowTitle(title); +} + +void MainWindowBase::documentModified() { // SVDEBUG << "MainWindowBase::documentModified" << endl; - if (!m_documentModified) { - //!!! this in subclass implementation? - setWindowTitle(tr("%1 (modified)").arg(windowTitle())); - } - m_documentModified = true; + updateWindowTitle(); updateMenuStates(); } @@ -717,14 +796,8 @@ { // SVDEBUG << "MainWindowBase::documentRestored" << endl; - if (m_documentModified) { - //!!! this in subclass implementation? - QString wt(windowTitle()); - wt.replace(tr(" (modified)"), ""); - setWindowTitle(wt); - } - m_documentModified = false; + updateWindowTitle(); updateMenuStates(); } @@ -1136,46 +1209,44 @@ if (layer) { Model *model = layer->getModel(); - SparseOneDimensionalModel *sodm = dynamic_cast<SparseOneDimensionalModel *> - (model); + SparseOneDimensionalModel *sodm = + dynamic_cast<SparseOneDimensionalModel *>(model); if (sodm) { - SparseOneDimensionalModel::Point point(frame, ""); - - SparseOneDimensionalModel::Point prevPoint(0); + Event point(frame, ""); + Event prevPoint(0); bool havePrevPoint = false; - SparseOneDimensionalModel::EditCommand *command = - new SparseOneDimensionalModel::EditCommand(sodm, tr("Add Point")); + ChangeEventsCommand *command = + new ChangeEventsCommand(sodm, tr("Add Point")); if (m_labeller) { if (m_labeller->requiresPrevPoint()) { - - SparseOneDimensionalModel::PointList prevPoints = - sodm->getPreviousPoints(frame); - - if (!prevPoints.empty()) { - prevPoint = *prevPoints.begin(); + + if (sodm->getNearestEventMatching + (frame, + [](Event) { return true; }, + EventSeries::Backward, + prevPoint)) { havePrevPoint = true; } } m_labeller->setSampleRate(sodm->getSampleRate()); - if (m_labeller->actingOnPrevPoint() && havePrevPoint) { - command->deletePoint(prevPoint); - } - - m_labeller->label<SparseOneDimensionalModel::Point> + Labeller::Relabelling relabelling = m_labeller->label (point, havePrevPoint ? &prevPoint : nullptr); - if (m_labeller->actingOnPrevPoint() && havePrevPoint) { - command->addPoint(prevPoint); + if (relabelling.first == Labeller::AppliesToPreviousEvent) { + command->remove(prevPoint); + command->add(relabelling.second); + } else { + point = relabelling.second; } } - command->addPoint(point); + command->add(point); command->setName(tr("Add Point at %1 s") .arg(RealTime::frame2RealTime @@ -1231,14 +1302,12 @@ RegionModel *rm = dynamic_cast<RegionModel *>(layer->getModel()); if (rm) { - RegionModel::Point point(alignedStart, - rm->getValueMaximum() + 1, - alignedDuration, - ""); - RegionModel::EditCommand *command = - new RegionModel::EditCommand(rm, tr("Add Point")); - command->addPoint(point); - command->setName(name); + Event point(alignedStart, + rm->getValueMaximum() + 1, + alignedDuration, + ""); + ChangeEventsCommand *command = new ChangeEventsCommand(rm, name); + command->add(point); c = command->finish(); } @@ -1249,15 +1318,13 @@ NoteModel *nm = dynamic_cast<NoteModel *>(layer->getModel()); if (nm) { - NoteModel::Point point(alignedStart, - nm->getValueMinimum(), - alignedDuration, - 1.f, - ""); - NoteModel::EditCommand *command = - new NoteModel::EditCommand(nm, tr("Add Point")); - command->addPoint(point); - command->setName(name); + Event point(alignedStart, + nm->getValueMinimum(), + alignedDuration, + 1.f, + ""); + ChangeEventsCommand *command = new ChangeEventsCommand(nm, name); + command->add(point); c = command->finish(); } @@ -1265,25 +1332,6 @@ CommandHistory::getInstance()->addCommand(c, false); return; } - - FlexiNoteModel *fnm = dynamic_cast<FlexiNoteModel *>(layer->getModel()); - if (fnm) { - FlexiNoteModel::Point point(alignedStart, - fnm->getValueMinimum(), - alignedDuration, - 1.f, - ""); - FlexiNoteModel::EditCommand *command = - new FlexiNoteModel::EditCommand(fnm, tr("Add Point")); - command->addPoint(point); - command->setName(name); - c = command->finish(); - } - - if (c) { - CommandHistory::getInstance()->addCommand(c, false); - return; - } } void @@ -1306,9 +1354,10 @@ Labeller labeller(*m_labeller); labeller.setSampleRate(sodm->getSampleRate()); - +/*!!! to be updated after SODM API update Command *c = labeller.labelAll<SparseOneDimensionalModel::Point>(*sodm, &ms); if (c) CommandHistory::getInstance()->addCommand(c, false); +*/ } void @@ -1332,9 +1381,12 @@ Labeller labeller(*m_labeller); labeller.setSampleRate(sodm->getSampleRate()); + (void)n; +/*!!! to be updated after SODM API update Command *c = labeller.subdivide<SparseOneDimensionalModel::Point> (*sodm, &ms, n); if (c) CommandHistory::getInstance()->addCommand(c, false); +*/ } void @@ -1358,9 +1410,12 @@ Labeller labeller(*m_labeller); labeller.setSampleRate(sodm->getSampleRate()); + (void)n; +/*!!! to be updated after SODM API update Command *c = labeller.winnow<SparseOneDimensionalModel::Point> (*sodm, &ms, n); if (c) CommandHistory::getInstance()->addCommand(c, false); +*/ } MainWindowBase::FileOpenStatus @@ -1632,7 +1687,7 @@ } emit activity(tr("Import audio file \"%1\"").arg(source.getLocation())); - + if (mode == ReplaceMainModel) { Model *prevMain = getMainModel(); @@ -1648,22 +1703,15 @@ setupMenus(); + m_originalLocation = source.getLocation(); + if (loadedTemplate || (m_sessionFile == "")) { - //!!! shouldn't be dealing directly with title from here -- call a method - setWindowTitle(tr("%1: %2") - .arg(QApplication::applicationName()) - .arg(source.getLocation())); CommandHistory::getInstance()->clear(); CommandHistory::getInstance()->documentSaved(); m_documentModified = false; } else { - setWindowTitle(tr("%1: %2 [%3]") - .arg(QApplication::applicationName()) - .arg(QFileInfo(m_sessionFile).fileName()) - .arg(source.getLocation())); if (m_documentModified) { m_documentModified = false; - documentModified(); // so as to restore "(modified)" window title } } @@ -1671,6 +1719,8 @@ m_audioFile = source.getLocalFilename(); } + updateWindowTitle(); + } else if (mode == CreateAdditionalModel) { SVCERR << "Mode is CreateAdditionalModel" << endl; @@ -2133,10 +2183,6 @@ emit activity(tr("Import session file \"%1\"").arg(source.getLocation())); - setWindowTitle(tr("%1: %2") - .arg(QApplication::applicationName()) - .arg(source.getLocation())); - if (!source.isRemote() && !m_document->isIncomplete()) { // Setting the session file path enables the Save (as // opposed to Save As...) option. We can't do this if we @@ -2153,6 +2199,7 @@ QMessageBox::Ok); } + updateWindowTitle(); setupMenus(); findTimeRulerLayer(); @@ -2169,12 +2216,13 @@ source.getLocalFilename()); } + m_originalLocation = source.getLocation(); + emit sessionLoaded(); - } else { - setWindowTitle(QApplication::applicationName()); + updateWindowTitle(); } - + return ok ? FileOpenSucceeded : FileOpenFailed; } @@ -2240,8 +2288,6 @@ bool ok = (error == ""); - setWindowTitle(QApplication::applicationName()); - if (ok) { emit activity(tr("Open session template \"%1\"").arg(source.getLocation())); @@ -2257,6 +2303,8 @@ emit sessionLoaded(); } + updateWindowTitle(); + return ok ? FileOpenSucceeded : FileOpenFailed; } @@ -2279,13 +2327,11 @@ setupMenus(); findTimeRulerLayer(); - - setWindowTitle(tr("%1: %2") - .arg(QApplication::applicationName()) - .arg(source.getLocation())); + CommandHistory::getInstance()->clear(); CommandHistory::getInstance()->documentSaved(); m_documentModified = false; + updateWindowTitle(); emit sessionLoaded(); @@ -2649,6 +2695,8 @@ connect(m_document, SIGNAL(alignmentFailed(QString)), this, SLOT(alignmentFailed(QString))); + m_document->setAutoAlignment(m_viewManager->getAlignMode()); + emit replacedDocument(); } @@ -2736,6 +2784,79 @@ } } +bool +MainWindowBase::exportLayerTo(Layer *layer, QString path, QString &error) +{ + if (QFileInfo(path).suffix() == "") path += ".svl"; + + QString suffix = QFileInfo(path).suffix().toLower(); + + Model *model = layer->getModel(); + + if (suffix == "xml" || suffix == "svl") { + + QFile file(path); + if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) { + error = tr("Failed to open file %1 for writing").arg(path); + } else { + QTextStream out(&file); + out.setCodec(QTextCodec::codecForName("UTF-8")); + out << "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" + << "<!DOCTYPE sonic-visualiser>\n" + << "<sv>\n" + << " <data>\n"; + + model->toXml(out, " "); + + out << " </data>\n" + << " <display>\n"; + + layer->toXml(out, " "); + + out << " </display>\n" + << "</sv>\n"; + } + + } else if (suffix == "mid" || suffix == "midi") { + + NoteModel *nm = dynamic_cast<NoteModel *>(model); + + if (!nm) { + error = tr("Can't export non-note layers to MIDI"); + } else { + MIDIFileWriter writer(path, nm, nm->getSampleRate()); + writer.write(); + if (!writer.isOK()) { + error = writer.getError(); + } + } + + } else if (suffix == "ttl" || suffix == "n3") { + + if (!RDFExporter::canExportModel(model)) { + error = tr("Sorry, cannot export this layer type to RDF (supported types are: region, note, text, time instants, time values)"); + } else { + RDFExporter exporter(path, model); + exporter.write(); + if (!exporter.isOK()) { + error = exporter.getError(); + } + } + + } else { + + CSVFileWriter writer(path, model, + ((suffix == "csv") ? "," : "\t")); + writer.write(); + + if (!writer.isOK()) { + error = writer.getError(); + } + } + + return (error == ""); +} + void MainWindowBase::toXml(QTextStream &out, bool asTemplate) { @@ -3181,25 +3302,16 @@ setupMenus(); findTimeRulerLayer(); + m_originalLocation = model->getLocation(); + if (loadedTemplate || (m_sessionFile == "")) { - //!!! shouldn't be dealing directly with title from here -- call a method - setWindowTitle(tr("%1: %2") - .arg(QApplication::applicationName()) - .arg(model->getLocation())); CommandHistory::getInstance()->clear(); CommandHistory::getInstance()->documentSaved(); - m_documentModified = false; - } else { - setWindowTitle(tr("%1: %2 [%3]") - .arg(QApplication::applicationName()) - .arg(QFileInfo(m_sessionFile).fileName()) - .arg(model->getLocation())); - if (m_documentModified) { - m_documentModified = false; - documentModified(); // so as to restore "(modified)" window title - } } + m_documentModified = false; + updateWindowTitle(); + } else { CommandHistory::getInstance()->startCompoundOperation
--- a/framework/MainWindowBase.h Thu Apr 04 16:17:11 2019 +0100 +++ b/framework/MainWindowBase.h Fri May 17 09:46:22 2019 +0100 @@ -34,6 +34,7 @@ #include "data/fileio/FileFinder.h" #include "data/fileio/FileSource.h" #include "data/osc/OSCQueue.h" +#include "data/osc/OSCMessageCallback.h" #include <map> class Document; @@ -58,6 +59,7 @@ class QTreeView; class QPushButton; class OSCMessage; +class OSCScript; class MIDIInput; class KeyReference; class Labeller; @@ -81,7 +83,9 @@ * to use different subclasses retaining the same general structure. */ -class MainWindowBase : public QMainWindow, public FrameTimer +class MainWindowBase : public QMainWindow, + public FrameTimer, + public OSCMessageCallback { Q_OBJECT @@ -135,6 +139,10 @@ virtual bool saveSessionFile(QString path); virtual bool saveSessionTemplate(QString path); + virtual bool exportLayerTo(Layer *layer, QString path, QString &error); + + void cueOSCScript(QString filename); + /// Implementation of FrameTimer interface method sv_frame_t getFrame() const override; @@ -300,6 +308,7 @@ virtual void updateMenuStates(); virtual void updateDescriptionLabel() = 0; + virtual void updateWindowTitle(); virtual void modelGenerationFailed(QString, QString) = 0; virtual void modelGenerationWarning(QString, QString) = 0; @@ -320,7 +329,7 @@ virtual void oscReady(); virtual void pollOSC(); - virtual void handleOSCMessage(const OSCMessage &) = 0; + virtual void oscScriptFinished(); virtual void contextHelpChanged(const QString &); virtual void inProgressSelectionChanged(); @@ -337,15 +346,23 @@ virtual void menuActionMapperInvoked(QObject *); protected: - QString m_sessionFile; - QString m_audioFile; - Document *m_document; + QString m_sessionFile; + QString m_audioFile; + Document *m_document; - PaneStack *m_paneStack; - ViewManager *m_viewManager; - Layer *m_timeRulerLayer; + // This is used in the window title. It's the upstream location + // (maybe a URL) the user provided as source of the main model. It + // should be set in cases where there is no current session file + // and m_sessionFile is empty, or where a new main model has been + // imported into an existing session. It should be used only for + // user presentation, never parsed - treat it as an opaque label + QString m_originalLocation; - SoundOptions m_soundOptions; + PaneStack *m_paneStack; + ViewManager *m_viewManager; + Layer *m_timeRulerLayer; + + SoundOptions m_soundOptions; AudioCallbackPlaySource *m_playSource; AudioCallbackRecordTarget *m_recordTarget; @@ -356,18 +373,27 @@ class OSCQueueStarter : public QThread { public: - OSCQueueStarter(MainWindowBase *mwb) : QThread(mwb), m_mwb(mwb) { } + OSCQueueStarter(MainWindowBase *mwb, bool withNetworkPort) : + QThread(mwb), m_mwb(mwb), m_withPort(withNetworkPort) { } + void run() override { - OSCQueue *queue = new OSCQueue(); // can take a long time + // NB creating the queue object can take a long time + OSCQueue *queue = new OSCQueue(m_withPort); m_mwb->m_oscQueue = queue; } + private: MainWindowBase *m_mwb; + bool m_withPort; }; OSCQueue *m_oscQueue; OSCQueueStarter *m_oscQueueStarter; - void startOSCQueue(); + OSCScript *m_oscScript; + QString m_oscScriptFile; + + void startOSCQueue(bool withNetworkPort); + void startOSCScript(); MIDIInput *m_midiInput;
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/framework/OSCScript.h Fri May 17 09:46:22 2019 +0100 @@ -0,0 +1,131 @@ +/* -*- 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_OSC_SCRIPT_H +#define SV_OSC_SCRIPT_H + +#include <QThread> +#include <QFile> +#include <QTextStream> + +#include "base/Debug.h" +#include "base/StringBits.h" +#include "data/osc/OSCQueue.h" +#include "data/osc/OSCMessage.h" + +#include <stdexcept> + +class OSCScript : public QThread +{ + Q_OBJECT + +public: + OSCScript(QString filename, OSCQueue *queue) : + m_filename(filename), + m_queue(queue), + m_abandoning(false) { + } + + void run() override { + + if (!m_queue) { + SVCERR << "OSCScript: No OSC queue available" << endl; + throw std::runtime_error("OSC queue not running"); + } + + QFile f; + QString reportedFilename; + + if (m_filename == "-") { + f.open(stdin, QFile::ReadOnly | QFile::Text); + reportedFilename = "<stdin>"; + } else { + f.setFileName(m_filename); + if (!f.open(QFile::ReadOnly | QFile::Text)) { + SVCERR << "OSCScript: Failed to open script file \"" + << m_filename << "\" for reading" << endl; + throw std::runtime_error("OSC script file not found"); + } + reportedFilename = m_filename; + } + + QTextStream str(&f); + int lineno = 0; + + while (!str.atEnd() && !m_abandoning) { + + ++lineno; + + QString line = str.readLine().trimmed(); + if (line == QString()) continue; + + if (line[0] == '#') { + continue; + + } else if (line[0].isDigit()) { + bool ok = false; + float pause = line.toFloat(&ok); + if (ok) { + SVCERR << "OSCScript: " + << reportedFilename << ":" << lineno + << ": pausing for " << pause << " sec" << endl; + msleep(unsigned(round(pause * 1000.0f))); + continue; + } else { + SVCERR << "OSCScript: " + << reportedFilename << ":" << lineno + << ": warning: failed to parse sleep time, ignoring" + << endl; + continue; + } + + } else if (line[0] == '/' && line.size() > 1) { + QStringList parts = StringBits::splitQuoted(line, ' '); + if (parts.empty()) { + SVCERR << "OSCScript: " + << reportedFilename << ":" << lineno + << ": warning: empty command spec, ignoring" + << endl; + continue; + } + OSCMessage message; + message.setMethod(parts[0].mid(1)); + for (int i = 1; i < parts.size(); ++i) { + message.addArg(parts[i]); + } + SVCERR << "OSCScript: " << reportedFilename << ":" << lineno + << ": invoking: \"" << parts[0] << "\"" << endl; + m_queue->postMessage(message); + + } else { + SVCERR << "OSCScript: " << reportedFilename << ":" << lineno + << ": warning: message expected, ignoring" << endl; + } + } + + SVCERR << "OSCScript: " << reportedFilename << ": finished" << endl; + } + + void abandon() { + m_abandoning = true; + } + +private: + QString m_filename; + OSCQueue *m_queue; // I do not own this + bool m_abandoning; +}; + +#endif +
--- a/framework/SVFileReader.cpp Thu Apr 04 16:17:11 2019 +0100 +++ b/framework/SVFileReader.cpp Fri May 17 09:46:22 2019 +0100 @@ -31,7 +31,6 @@ #include "data/model/SparseOneDimensionalModel.h" #include "data/model/SparseTimeValueModel.h" #include "data/model/NoteModel.h" -#include "data/model/FlexiNoteModel.h" #include "data/model/RegionModel.h" #include "data/model/TextModel.h" #include "data/model/ImageModel.h" @@ -459,28 +458,30 @@ { makeAggregateModels(); - std::set<Model *> unaddedModels; - for (std::map<int, Model *>::iterator i = m_models.begin(); i != m_models.end(); ++i) { - if (m_addedModels.find(i->second) == m_addedModels.end()) { - unaddedModels.insert(i->second); + + Model *model = i->second; + + if (m_addedModels.find(model) != m_addedModels.end()) { + // already added this one + continue; } - } - - for (std::set<Model *>::iterator i = unaddedModels.begin(); - i != unaddedModels.end(); ++i) { - Model *model = *i; - // don't want to add these models, because their lifespans - // are entirely dictated by the models that "own" them even - // though they were read independently from the .sv file. - // (pity we don't have a nicer way) + + // don't want to add path and alignment models to the + // document, because their lifespans are entirely dictated by + // the models that "own" them even though they were read + // independently from the .sv file. (pity we don't have a + // nicer way to handle this) if (!dynamic_cast<PathModel *>(model) && !dynamic_cast<AlignmentModel *>(model)) { + m_document->addImportedModel(model); } - // but we add all models here, so they don't get deleted - // when the file loader is destroyed + + // but we add all models including path and alignment ones to + // the added set, so they don't get deleted from our own + // destructor m_addedModels.insert(model); } } @@ -713,13 +714,16 @@ model->setObjectName(name); m_models[id] = model; } else if (attributes.value("subtype") == "flexinote") { - FlexiNoteModel *model; + NoteModel *model; if (haveMinMax) { - model = new FlexiNoteModel - (sampleRate, resolution, minimum, maximum, notifyOnAdd); + model = new NoteModel + (sampleRate, resolution, minimum, maximum, + notifyOnAdd, + NoteModel::FLEXI_NOTE); } else { - model = new FlexiNoteModel - (sampleRate, resolution, notifyOnAdd); + model = new NoteModel + (sampleRate, resolution, notifyOnAdd, + NoteModel::FLEXI_NOTE); } model->setValueQuantization(valueQuantization); model->setScaleUnits(units); @@ -1051,7 +1055,6 @@ case 3: if (dynamic_cast<NoteModel *>(model)) good = true; - else if (dynamic_cast<FlexiNoteModel *>(model)) good = true; else if (dynamic_cast<RegionModel *>(model)) good = true; else if (dynamic_cast<EditableDenseThreeDimensionalModel *>(model)) { m_datasetSeparator = attributes.value("separator"); @@ -1085,7 +1088,7 @@ if (sodm) { // SVCERR << "Current dataset is a sparse one dimensional model" << endl; QString label = attributes.value("label"); - sodm->addPoint(SparseOneDimensionalModel::Point(frame, label)); + sodm->add(Event(frame, label)); return true; } @@ -1097,7 +1100,7 @@ float value = 0.0; value = attributes.value("value").trimmed().toFloat(&ok); QString label = attributes.value("label"); - stvm->addPoint(SparseTimeValueModel::Point(frame, value, label)); + stvm->add(Event(frame, value, label)); return ok; } @@ -1115,25 +1118,7 @@ level = 1.f; ok = true; } - nm->addPoint(NoteModel::Point(frame, value, duration, level, label)); - return ok; - } - - FlexiNoteModel *fnm = dynamic_cast<FlexiNoteModel *>(m_currentDataset); - - if (fnm) { -// SVCERR << "Current dataset is a flexinote model" << endl; - float value = 0.0; - value = attributes.value("value").trimmed().toFloat(&ok); - int duration = 0; - duration = attributes.value("duration").trimmed().toInt(&ok); - QString label = attributes.value("label"); - float level = attributes.value("level").trimmed().toFloat(&ok); - if (!ok) { // level is optional - level = 1.f; - ok = true; - } - fnm->addPoint(FlexiNoteModel::Point(frame, value, duration, level, label)); + nm->add(Event(frame, value, duration, level, label)); return ok; } @@ -1146,7 +1131,7 @@ int duration = 0; duration = attributes.value("duration").trimmed().toInt(&ok); QString label = attributes.value("label"); - rm->addPoint(RegionModel::Point(frame, value, duration, label)); + rm->add(Event(frame, value, duration, label)); return ok; } @@ -1158,7 +1143,7 @@ height = attributes.value("height").trimmed().toFloat(&ok); QString label = attributes.value("label"); // SVDEBUG << "SVFileReader::addPointToDataset: TextModel: frame = " << frame << ", height = " << height << ", label = " << label << ", ok = " << ok << endl; - tm->addPoint(TextModel::Point(frame, height, label)); + tm->add(Event(frame, height, label)); return ok; } @@ -1168,7 +1153,7 @@ // SVCERR << "Current dataset is a path model" << endl; int mapframe = attributes.value("mapframe").trimmed().toInt(&ok); // SVDEBUG << "SVFileReader::addPointToDataset: PathModel: frame = " << frame << ", mapframe = " << mapframe << ", ok = " << ok << endl; - pm->addPoint(PathModel::Point(frame, mapframe)); + pm->add(PathPoint(frame, mapframe)); return ok; } @@ -1179,7 +1164,7 @@ QString image = attributes.value("image"); QString label = attributes.value("label"); // SVDEBUG << "SVFileReader::addPointToDataset: ImageModel: frame = " << frame << ", image = " << image << ", label = " << label << ", ok = " << ok << endl; - im->addPoint(ImageModel::Point(frame, image, label)); + im->add(Event(frame).withURI(image).withLabel(label)); return ok; }