# HG changeset patch # User Chris Cannam # Date 1552918640 0 # Node ID 7a56bb85030f9922a87c222a42a9aad424a76109 # Parent bbfb5a1e4b84dd99ddf5119176663535fd43c70d Introduce deferred notifier, + start converting sparse time-value model (perhaps we should rename it too) diff -r bbfb5a1e4b84 -r 7a56bb85030f data/fileio/CSVFileReader.cpp --- a/data/fileio/CSVFileReader.cpp Mon Mar 18 09:37:46 2019 +0000 +++ b/data/fileio/CSVFileReader.cpp Mon Mar 18 14:17:20 2019 +0000 @@ -442,8 +442,8 @@ } else if (modelType == CSVFormat::TwoDimensionalModel) { - SparseTimeValueModel::Point point(frameNo, value, label); - model2->addPoint(point); + Event point(frameNo, value, label); + model2->add(point); } else if (modelType == CSVFormat::TwoDimensionalModelWithDuration) { diff -r bbfb5a1e4b84 -r 7a56bb85030f data/model/AlignmentModel.cpp --- a/data/model/AlignmentModel.cpp Mon Mar 18 09:37:46 2019 +0000 +++ b/data/model/AlignmentModel.cpp Mon Mar 18 14:17:20 2019 +0000 @@ -238,12 +238,11 @@ m_path->clear(); - SparseTimeValueModel::PointList points = m_rawPath->getPoints(); - - for (SparseTimeValueModel::PointList::const_iterator i = points.begin(); - i != points.end(); ++i) { - sv_frame_t frame = i->frame; - double value = i->value; + EventVector points = m_rawPath->getAllEvents(); + + for (const auto &p: points) { + sv_frame_t frame = p.getFrame(); + double value = p.getValue(); sv_frame_t rframe = lrint(value * m_aligned->getSampleRate()); m_path->addPoint(PathPoint(frame, rframe)); } diff -r bbfb5a1e4b84 -r 7a56bb85030f data/model/DeferredNotifier.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/data/model/DeferredNotifier.h Mon Mar 18 14:17:20 2019 +0000 @@ -0,0 +1,76 @@ +/* -*- 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_DEFERRED_NOTIFIER_H +#define SV_DEFERRED_NOTIFIER_H + +#include "Model.h" + +#include "base/Extents.h" + +#include +#include + +class DeferredNotifier +{ +public: + enum Mode { + NOTIFY_ALWAYS, + NOTIFY_DEFERRED + }; + + DeferredNotifier(Model *m, Mode mode) : m_model(m), m_mode(mode) { } + + Mode getMode() const { + return m_mode; + } + void switchMode(Mode newMode) { + m_mode = newMode; + } + + void update(sv_frame_t frame, sv_frame_t duration) { + if (m_mode == NOTIFY_ALWAYS) { + m_model->modelChangedWithin(frame, frame + duration); + } else { + QMutexLocker locker(&m_mutex); + m_extents.sample(frame); + m_extents.sample(frame + duration); + } + } + + void makeDeferredNotifications() { + bool shouldEmit = false; + sv_frame_t from, to; + { QMutexLocker locker(&m_mutex); + if (m_extents.isSet()) { + shouldEmit = true; + from = m_extents.getMin(); + to = m_extents.getMax(); + } + } + if (shouldEmit) { + m_model->modelChangedWithin(from, to); + QMutexLocker locker(&m_mutex); + m_extents.reset(); + } + } + +private: + Model *m_model; + Mode m_mode; + QMutex m_mutex; + Extents m_extents; +}; + +#endif diff -r bbfb5a1e4b84 -r 7a56bb85030f data/model/NoteModel.h --- a/data/model/NoteModel.h Mon Mar 18 09:37:46 2019 +0000 +++ b/data/model/NoteModel.h Mon Mar 18 14:17:20 2019 +0000 @@ -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. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as @@ -19,6 +18,7 @@ #include "Model.h" #include "TabularModel.h" #include "EventCommands.h" +#include "DeferredNotifier.h" #include "base/UnitDatabase.h" #include "base/EventSeries.h" #include "base/NoteData.h" @@ -57,9 +57,10 @@ m_valueQuantization(0), m_units(""), m_extendTo(0), - m_notifyOnAdd(notifyOnAdd), - m_sinceLastNotifyMin(-1), - m_sinceLastNotifyMax(-1), + m_notifier(this, + notifyOnAdd ? + DeferredNotifier::NOTIFY_ALWAYS : + DeferredNotifier::NOTIFY_DEFERRED), m_completion(0) { if (subtype == FLEXI_NOTE) { m_valueMinimum = 33.f; @@ -81,9 +82,10 @@ m_valueQuantization(0), m_units(""), m_extendTo(0), - m_notifyOnAdd(notifyOnAdd), - m_sinceLastNotifyMin(-1), - m_sinceLastNotifyMax(-1), + m_notifier(this, + notifyOnAdd ? + DeferredNotifier::NOTIFY_ALWAYS : + DeferredNotifier::NOTIFY_DEFERRED), m_completion(0) { PlayParameterRepository::getInstance()->addPlayable(this); } @@ -122,46 +124,22 @@ void setCompletion(int completion, bool update = true) { - bool emitCompletionChanged = true; - bool emitGeneralModelChanged = false; - bool emitRegionChanged = false; - - { - QMutexLocker locker(&m_mutex); - - if (m_completion != completion) { - m_completion = completion; - - if (completion == 100) { - - if (m_notifyOnAdd) { - emitCompletionChanged = false; - } - - m_notifyOnAdd = true; // henceforth - emitGeneralModelChanged = true; - - } else if (!m_notifyOnAdd) { - - if (update && - m_sinceLastNotifyMin >= 0 && - m_sinceLastNotifyMax >= 0) { - emitRegionChanged = true; - } - } - } + { QMutexLocker locker(&m_mutex); + if (m_completion == completion) return; + m_completion = completion; } - if (emitCompletionChanged) { - emit completionChanged(); + if (update) { + m_notifier.makeDeferredNotifications(); } - if (emitGeneralModelChanged) { + + emit completionChanged(); + + if (completion == 100) { + // henceforth: + m_notifier.switchMode(DeferredNotifier::NOTIFY_ALWAYS); emit modelChanged(); } - if (emitRegionChanged) { - emit modelChangedWithin(m_sinceLastNotifyMin, m_sinceLastNotifyMax); - m_sinceLastNotifyMin = m_sinceLastNotifyMax = -1; - } } /** @@ -203,7 +181,6 @@ { QMutexLocker locker(&m_mutex); m_events.add(e); -//!!!??? if (point.getLabel() != "") m_hasTextLabels = true; float v = e.getValue(); if (!ISNAN(v) && !ISINF(v)) { @@ -215,23 +192,10 @@ } m_haveExtents = true; } - - sv_frame_t f = e.getFrame(); - - if (!m_notifyOnAdd) { - if (m_sinceLastNotifyMin == -1 || f < m_sinceLastNotifyMin) { - m_sinceLastNotifyMin = f; - } - if (m_sinceLastNotifyMax == -1 || f > m_sinceLastNotifyMax) { - m_sinceLastNotifyMax = f; - } - } } - if (m_notifyOnAdd) { - emit modelChangedWithin(e.getFrame(), - e.getFrame() + e.getDuration() + m_resolution); - } + m_notifier.update(e.getFrame(), e.getDuration() + m_resolution); + if (allChange) { emit modelChanged(); } @@ -388,13 +352,14 @@ "valueQuantization=\"%5\" minimum=\"%6\" maximum=\"%7\" " "units=\"%8\" %9") .arg(m_resolution) - .arg(m_notifyOnAdd ? "true" : "false") + .arg("true") // always true after model reaches 100% - + // subsequent events are always notified .arg(getObjectExportId(&m_events)) .arg(m_subtype == FLEXI_NOTE ? "flexinote" : "note") .arg(m_valueQuantization) .arg(m_valueMinimum) .arg(m_valueMaximum) - .arg(m_units) + .arg(encodeEntities(m_units)) .arg(extraAttributes)); m_events.toXml(out, indent, QString("dimensions=\"3\"")); @@ -410,17 +375,12 @@ bool m_haveExtents; float m_valueQuantization; QString m_units; - sv_frame_t m_extendTo; - - bool m_notifyOnAdd; - sv_frame_t m_sinceLastNotifyMin; - sv_frame_t m_sinceLastNotifyMax; + DeferredNotifier m_notifier; + int m_completion; EventSeries m_events; - int m_completion; - mutable QMutex m_mutex; //!!! do we have general docs for ownership and synchronisation of models? diff -r bbfb5a1e4b84 -r 7a56bb85030f data/model/RegionModel.h --- a/data/model/RegionModel.h Mon Mar 18 09:37:46 2019 +0000 +++ b/data/model/RegionModel.h Mon Mar 18 14:17:20 2019 +0000 @@ -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. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as @@ -19,6 +18,7 @@ #include "EventCommands.h" #include "TabularModel.h" #include "Model.h" +#include "DeferredNotifier.h" #include "base/RealTime.h" #include "base/EventSeries.h" @@ -49,9 +49,10 @@ m_haveExtents(false), m_valueQuantization(0), m_haveDistinctValues(false), - m_notifyOnAdd(notifyOnAdd), - m_sinceLastNotifyMin(-1), - m_sinceLastNotifyMax(-1), + m_notifier(this, + notifyOnAdd ? + DeferredNotifier::NOTIFY_ALWAYS : + DeferredNotifier::NOTIFY_DEFERRED), m_completion(0) { } @@ -65,9 +66,10 @@ m_haveExtents(false), m_valueQuantization(0), m_haveDistinctValues(false), - m_notifyOnAdd(notifyOnAdd), - m_sinceLastNotifyMin(-1), - m_sinceLastNotifyMax(-1), + m_notifier(this, + notifyOnAdd ? + DeferredNotifier::NOTIFY_ALWAYS : + DeferredNotifier::NOTIFY_DEFERRED), m_completion(0) { } @@ -100,46 +102,22 @@ void setCompletion(int completion, bool update = true) { - bool emitCompletionChanged = true; - bool emitGeneralModelChanged = false; - bool emitRegionChanged = false; - - { - QMutexLocker locker(&m_mutex); - - if (m_completion != completion) { - m_completion = completion; - - if (completion == 100) { - - if (m_notifyOnAdd) { - emitCompletionChanged = false; - } - - m_notifyOnAdd = true; // henceforth - emitGeneralModelChanged = true; - - } else if (!m_notifyOnAdd) { - - if (update && - m_sinceLastNotifyMin >= 0 && - m_sinceLastNotifyMax >= 0) { - emitRegionChanged = true; - } - } - } + { QMutexLocker locker(&m_mutex); + if (m_completion == completion) return; + m_completion = completion; } - if (emitCompletionChanged) { - emit completionChanged(); + if (update) { + m_notifier.makeDeferredNotifications(); } - if (emitGeneralModelChanged) { + + emit completionChanged(); + + if (completion == 100) { + // henceforth: + m_notifier.switchMode(DeferredNotifier::NOTIFY_ALWAYS); emit modelChanged(); } - if (emitRegionChanged) { - emit modelChangedWithin(m_sinceLastNotifyMin, m_sinceLastNotifyMax); - m_sinceLastNotifyMin = m_sinceLastNotifyMax = -1; - } } /** @@ -197,23 +175,10 @@ if (e.hasValue() && e.getValue() != 0.f) { m_haveDistinctValues = true; } - - sv_frame_t f = e.getFrame(); - - if (!m_notifyOnAdd) { - if (m_sinceLastNotifyMin == -1 || f < m_sinceLastNotifyMin) { - m_sinceLastNotifyMin = f; - } - if (m_sinceLastNotifyMax == -1 || f > m_sinceLastNotifyMax) { - m_sinceLastNotifyMax = f; - } - } } - if (m_notifyOnAdd) { - emit modelChangedWithin(e.getFrame(), - e.getFrame() + e.getDuration() + m_resolution); - } + m_notifier.update(e.getFrame(), e.getDuration() + m_resolution); + if (allChange) { emit modelChanged(); } @@ -269,6 +234,11 @@ } } + SortType getSortType(int column) const override { + if (column == 4) return SortAlphabetical; + return SortNumeric; + } + QVariant getData(int row, int column, int role) const override { if (row < 0 || row >= m_events.count()) { @@ -311,12 +281,6 @@ return command->finish(); } - SortType getSortType(int column) const override - { - if (column == 4) return SortAlphabetical; - return SortNumeric; - } - /** * XmlExportable methods. @@ -331,14 +295,16 @@ QString("type=\"sparse\" dimensions=\"3\" resolution=\"%1\" " "notifyOnAdd=\"%2\" dataset=\"%3\" subtype=\"%4\" " "valueQuantization=\"%5\" minimum=\"%6\" maximum=\"%7\" " - "%8") + "units=\"%8\" %9") .arg(m_resolution) - .arg(m_notifyOnAdd ? "true" : "false") + .arg("true") // always true after model reaches 100% - + // subsequent events are always notified .arg(getObjectExportId(&m_events)) .arg("region") .arg(m_valueQuantization) .arg(m_valueMinimum) .arg(m_valueMaximum) + .arg(encodeEntities(m_units)) .arg(extraAttributes)); m_events.toXml(out, indent, QString("dimensions=\"3\"")); @@ -354,15 +320,11 @@ float m_valueQuantization; bool m_haveDistinctValues; QString m_units; - - bool m_notifyOnAdd; - sv_frame_t m_sinceLastNotifyMin; - sv_frame_t m_sinceLastNotifyMax; + DeferredNotifier m_notifier; + int m_completion; EventSeries m_events; - int m_completion; - mutable QMutex m_mutex; }; diff -r bbfb5a1e4b84 -r 7a56bb85030f data/model/SparseTimeValueModel.h --- a/data/model/SparseTimeValueModel.h Mon Mar 18 09:37:46 2019 +0000 +++ b/data/model/SparseTimeValueModel.h Mon Mar 18 14:17:20 2019 +0000 @@ -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. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as @@ -16,76 +15,43 @@ #ifndef SV_SPARSE_TIME_VALUE_MODEL_H #define SV_SPARSE_TIME_VALUE_MODEL_H -#include "SparseValueModel.h" +#include "EventCommands.h" +#include "TabularModel.h" +#include "Model.h" +#include "DeferredNotifier.h" + +#include "base/RealTime.h" +#include "base/EventSeries.h" +#include "base/UnitDatabase.h" #include "base/PlayParameterRepository.h" -#include "base/RealTime.h" + +#include "system/System.h" /** - * Time/value point type for use in a SparseModel or SparseValueModel. - * With this point type, the model basically represents a wiggly-line - * plot with points at arbitrary intervals of the model resolution. + * A model representing a wiggly-line plot with points at arbitrary + * intervals of the model resolution. */ - -struct TimeValuePoint -{ -public: - TimeValuePoint(sv_frame_t _frame) : frame(_frame), value(0.0f) { } - TimeValuePoint(sv_frame_t _frame, float _value, QString _label) : - frame(_frame), value(_value), label(_label) { } - - int getDimensions() const { return 2; } - - sv_frame_t frame; - float value; - QString label; - - QString getLabel() const { return label; } - - void toXml(QTextStream &stream, QString indent = "", - QString extraAttributes = "") const - { - stream << QString("%1\n") - .arg(indent).arg(frame).arg(value).arg(XmlExportable::encodeEntities(label)) - .arg(extraAttributes); - } - - QString toDelimitedDataString(QString delimiter, DataExportOptions, sv_samplerate_t sampleRate) const - { - QStringList list; - list << RealTime::frame2RealTime(frame, sampleRate).toString().c_str(); - list << QString("%1").arg(value); - if (label != "") list << label; - return list.join(delimiter); - } - - struct Comparator { - bool operator()(const TimeValuePoint &p1, - const TimeValuePoint &p2) const { - if (p1.frame != p2.frame) return p1.frame < p2.frame; - if (p1.value != p2.value) return p1.value < p2.value; - return p1.label < p2.label; - } - }; - - struct OrderComparator { - bool operator()(const TimeValuePoint &p1, - const TimeValuePoint &p2) const { - return p1.frame < p2.frame; - } - }; -}; - - -class SparseTimeValueModel : public SparseValueModel +class SparseTimeValueModel : public Model, + public TabularModel, + public EventEditable { Q_OBJECT public: - SparseTimeValueModel(sv_samplerate_t sampleRate, int resolution, + SparseTimeValueModel(sv_samplerate_t sampleRate, + int resolution, bool notifyOnAdd = true) : - SparseValueModel(sampleRate, resolution, - notifyOnAdd) - { + m_sampleRate(sampleRate), + m_resolution(resolution), + m_valueMinimum(0.f), + m_valueMaximum(0.f), + m_haveExtents(false), + m_haveTextLabels(false), + m_notifier(this, + notifyOnAdd ? + DeferredNotifier::NOTIFY_ALWAYS : + DeferredNotifier::NOTIFY_DEFERRED), + m_completion(0) { // Model is playable, but may not sound (if units not Hz or // range unsuitable) PlayParameterRepository::getInstance()->addPlayable(this); @@ -94,36 +60,172 @@ SparseTimeValueModel(sv_samplerate_t sampleRate, int resolution, float valueMinimum, float valueMaximum, bool notifyOnAdd = true) : - SparseValueModel(sampleRate, resolution, - valueMinimum, valueMaximum, - notifyOnAdd) - { + m_sampleRate(sampleRate), + m_resolution(resolution), + m_valueMinimum(valueMinimum), + m_valueMaximum(valueMaximum), + m_haveExtents(false), + m_haveTextLabels(false), + m_notifier(this, + notifyOnAdd ? + DeferredNotifier::NOTIFY_ALWAYS : + DeferredNotifier::NOTIFY_DEFERRED), + m_completion(0) { // Model is playable, but may not sound (if units not Hz or // range unsuitable) PlayParameterRepository::getInstance()->addPlayable(this); } - virtual ~SparseTimeValueModel() - { + virtual ~SparseTimeValueModel() { PlayParameterRepository::getInstance()->removePlayable(this); } QString getTypeName() const override { return tr("Sparse Time-Value"); } + bool isOK() const override { return true; } + sv_frame_t getStartFrame() const override { return m_events.getStartFrame(); } + sv_frame_t getEndFrame() const override { return m_events.getEndFrame(); } + sv_samplerate_t getSampleRate() const override { return m_sampleRate; } + int getResolution() const { return m_resolution; } + bool canPlay() const override { return true; } bool getDefaultPlayAudible() const override { return false; } // user must unmute + QString getScaleUnits() const { return m_units; } + void setScaleUnits(QString units) { + m_units = units; + UnitDatabase::getInstance()->registerUnit(units); + } + + bool hasTextLabels() const { return m_haveTextLabels; } + + float getValueMinimum() const { return m_valueMinimum; } + float getValueMaximum() const { return m_valueMaximum; } + + int getCompletion() const { return m_completion; } + + void setCompletion(int completion, bool update = true) { + + { QMutexLocker locker(&m_mutex); + if (m_completion == completion) return; + m_completion = completion; + } + + if (update) { + m_notifier.makeDeferredNotifications(); + } + + emit completionChanged(); + + if (completion == 100) { + // henceforth: + m_notifier.switchMode(DeferredNotifier::NOTIFY_ALWAYS); + emit modelChanged(); + } + } + + /** + * Query methods. + */ + + int getEventCount() const { + return m_events.count(); + } + bool isEmpty() const { + return m_events.isEmpty(); + } + bool containsEvent(const Event &e) const { + return m_events.contains(e); + } + EventVector getAllEvents() const { + return m_events.getAllEvents(); + } + EventVector getEventsSpanning(sv_frame_t f, sv_frame_t duration) const { + return m_events.getEventsSpanning(f, duration); + } + EventVector getEventsWithin(sv_frame_t f, sv_frame_t duration) const { + return m_events.getEventsWithin(f, duration); + } + EventVector getEventsStartingWithin(sv_frame_t f, sv_frame_t duration) const { + return m_events.getEventsStartingWithin(f, duration); + } + EventVector getEventsCovering(sv_frame_t f) const { + return m_events.getEventsCovering(f); + } + + /** + * Editing methods. + */ + void add(Event e) override { + + bool allChange = false; + + { + QMutexLocker locker(&m_mutex); + m_events.add(e); + + if (e.getLabel() != "") { + m_haveTextLabels = true; + } + + float v = e.getValue(); + if (!ISNAN(v) && !ISINF(v)) { + if (!m_haveExtents || v < m_valueMinimum) { + m_valueMinimum = v; allChange = true; + } + if (!m_haveExtents || v > m_valueMaximum) { + m_valueMaximum = v; allChange = true; + } + m_haveExtents = true; + } + } + + m_notifier.update(e.getFrame(), m_resolution); + + if (allChange) { + emit modelChanged(); + } + } + + void remove(Event e) override { + { + QMutexLocker locker(&m_mutex); + m_events.remove(e); + } + emit modelChangedWithin(e.getFrame(), e.getFrame() + m_resolution); + } + /** * TabularModel methods. */ - int getColumnCount() const override - { + int getRowCount() const override { + return m_events.count(); + } + + int getColumnCount() const override { return 4; } - QString getHeading(int column) const override - { + bool isColumnTimeValue(int column) const override { + // NB duration is not a "time value" -- that's for columns + // whose sort ordering is exactly that of the frame time + return (column < 2); + } + + sv_frame_t getFrameForRow(int row) const override { + if (row < 0 || row >= m_events.count()) { + return 0; + } + Event e = m_events.getEventByIndex(row); + return e.getFrame(); + } + + int getRowForFrame(sv_frame_t frame) const override { + return m_events.getIndexForEvent(Event(frame)); + } + + QString getHeading(int column) const override { switch (column) { case 0: return tr("Time"); case 1: return tr("Frame"); @@ -133,59 +235,91 @@ } } - QVariant getData(int row, int column, int role) const override - { - if (column < 2) { - return SparseValueModel::getData - (row, column, role); + SortType getSortType(int column) const override { + if (column == 3) return SortAlphabetical; + return SortNumeric; + } + + QVariant getData(int row, int column, int role) const override { + + if (row < 0 || row >= m_events.count()) { + return QVariant(); } - PointListConstIterator i = getPointListIteratorForRow(row); - if (i == m_points.end()) return QVariant(); + Event e = m_events.getEventByIndex(row); switch (column) { - case 2: - if (role == Qt::EditRole || role == SortRole) return i->value; - else return QString("%1 %2").arg(i->value).arg(getScaleUnits()); - case 3: return i->label; + case 0: return adaptFrameForRole(e.getFrame(), getSampleRate(), role); + case 1: return int(e.getFrame()); + case 2: return adaptValueForRole(e.getValue(), getScaleUnits(), role); + case 3: return e.getLabel(); default: return QVariant(); } } - Command *getSetDataCommand(int row, int column, const QVariant &value, int role) override - { - if (column < 2) { - return SparseValueModel::getSetDataCommand - (row, column, value, role); + Command *getSetDataCommand(int row, int column, const QVariant &value, int role) override { + if (row < 0 || row >= m_events.count()) return nullptr; + if (role != Qt::EditRole) return nullptr; + + Event e0 = m_events.getEventByIndex(row); + Event e1; + + switch (column) { + case 0: e1 = e0.withFrame(sv_frame_t(round(value.toDouble() * + getSampleRate()))); break; + case 1: e1 = e0.withFrame(value.toInt()); break; + case 2: e1 = e0.withValue(float(value.toDouble())); break; + case 3: e1 = e0.withLabel(value.toString()); break; } - if (role != Qt::EditRole) return 0; - PointListConstIterator i = getPointListIteratorForRow(row); - if (i == m_points.end()) return 0; - EditCommand *command = new EditCommand(this, tr("Edit Data")); - - Point point(*i); - command->deletePoint(point); - - switch (column) { - case 2: point.value = float(value.toDouble()); break; - case 3: point.label = value.toString(); break; - } - - command->addPoint(point); + ChangeEventsCommand *command = + new ChangeEventsCommand(this, tr("Edit Data")); + command->remove(e0); + command->add(e1); return command->finish(); } + + /** + * XmlExportable methods. + */ + void toXml(QTextStream &out, + QString indent = "", + QString extraAttributes = "") const override { - bool isColumnTimeValue(int column) const override - { - return (column < 2); + Model::toXml + (out, + indent, + QString("type=\"sparse\" dimensions=\"2\" resolution=\"%1\" " + "notifyOnAdd=\"%2\" dataset=\"%3\" " + "minimum=\"%4\" maximum=\"%5\" " + "units=\"%6\" %7") + .arg(m_resolution) + .arg("true") // always true after model reaches 100% - + // subsequent events are always notified + .arg(getObjectExportId(&m_events)) + .arg(m_valueMinimum) + .arg(m_valueMaximum) + .arg(encodeEntities(m_units)) + .arg(extraAttributes)); + + m_events.toXml(out, indent, QString("dimensions=\"2\"")); } + +protected: + sv_samplerate_t m_sampleRate; + int m_resolution; - SortType getSortType(int column) const override - { - if (column == 3) return SortAlphabetical; - return SortNumeric; - } + float m_valueMinimum; + float m_valueMaximum; + bool m_haveExtents; + bool m_haveTextLabels; + QString m_units; + DeferredNotifier m_notifier; + int m_completion; + + EventSeries m_events; + + mutable QMutex m_mutex; }; diff -r bbfb5a1e4b84 -r 7a56bb85030f data/model/test/TestSparseModels.h --- a/data/model/test/TestSparseModels.h Mon Mar 18 09:37:46 2019 +0000 +++ b/data/model/test/TestSparseModels.h Mon Mar 18 14:17:20 2019 +0000 @@ -205,8 +205,13 @@ QTextStream str(&xml, QIODevice::WriteOnly); m.toXml(str); str.flush(); + + //!!! This is not guaranteed - object export ids are in order + //!!! of model pointer value, which is not trustworthy - + //!!! replace them with something else + QString expected = - "\n" + "\n" "\n" " \n" " \n" diff -r bbfb5a1e4b84 -r 7a56bb85030f files.pri --- a/files.pri Mon Mar 18 09:37:46 2019 +0000 +++ b/files.pri Mon Mar 18 14:17:20 2019 +0000 @@ -80,6 +80,7 @@ data/model/Dense3DModelPeakCache.h \ data/model/DenseThreeDimensionalModel.h \ data/model/DenseTimeValueModel.h \ + data/model/DeferredNotifier.h \ data/model/EditableDenseThreeDimensionalModel.h \ data/model/EventCommands.h \ data/model/FFTModel.h \ diff -r bbfb5a1e4b84 -r 7a56bb85030f rdf/RDFExporter.cpp --- a/rdf/RDFExporter.cpp Mon Mar 18 09:37:46 2019 +0000 +++ b/rdf/RDFExporter.cpp Mon Mar 18 14:17:20 2019 +0000 @@ -136,13 +136,12 @@ if (m) { f.hasTimestamp = true; f.hasDuration = false; - const SparseTimeValueModel::PointList &pl(m->getPoints()); - for (SparseTimeValueModel::PointList::const_iterator i = pl.begin(); - i != pl.end(); ++i) { - f.timestamp = RealTime::frame2RealTime(i->frame, sr).toVampRealTime(); + EventVector ee(m->getAllEvents()); + for (auto e: ee) { + f.timestamp = RealTime::frame2RealTime(e.getFrame(), sr).toVampRealTime(); f.values.clear(); - f.values.push_back(i->value); - f.label = i->label.toStdString(); + f.values.push_back(e.getValue()); + f.label = e.getLabel().toStdString(); m_fw->write(trackId, transform, output, features, summaryType); } return; diff -r bbfb5a1e4b84 -r 7a56bb85030f rdf/RDFImporter.cpp --- a/rdf/RDFImporter.cpp Mon Mar 18 09:37:46 2019 +0000 +++ b/rdf/RDFImporter.cpp Mon Mar 18 14:17:20 2019 +0000 @@ -347,8 +347,8 @@ for (int j = 0; j < values.size(); ++j) { float f = values[j].toFloat(); - SparseTimeValueModel::Point point(j * hopSize, f, ""); - m->addPoint(point); + Event e(j * hopSize, f, ""); + m->add(e); } getDenseModelTitle(m, feature, type); @@ -728,9 +728,8 @@ SparseTimeValueModel *stvm = dynamic_cast(model); if (stvm) { - SparseTimeValueModel::Point point - (ftime, values.empty() ? 0.f : values[0], label); - stvm->addPoint(point); + Event e(ftime, values.empty() ? 0.f : values[0], label); + stvm->add(e); return; } diff -r bbfb5a1e4b84 -r 7a56bb85030f transform/FeatureExtractionModelTransformer.cpp --- a/transform/FeatureExtractionModelTransformer.cpp Mon Mar 18 09:37:46 2019 +0000 +++ b/transform/FeatureExtractionModelTransformer.cpp Mon Mar 18 14:17:20 2019 +0000 @@ -1004,8 +1004,7 @@ // << " for output " << n << " bin " << i << std::endl; } - targetModel->addPoint - (SparseTimeValueModel::Point(frame, value, label)); + targetModel->add(Event(frame, value, label)); } } else if (isOutput(n) || isOutput(n)) { diff -r bbfb5a1e4b84 -r 7a56bb85030f transform/RealTimeEffectModelTransformer.cpp --- a/transform/RealTimeEffectModelTransformer.cpp Mon Mar 18 09:37:46 2019 +0000 +++ b/transform/RealTimeEffectModelTransformer.cpp Mon Mar 18 14:17:20 2019 +0000 @@ -266,8 +266,7 @@ if (pointFrame > latency) pointFrame -= latency; else pointFrame = 0; - stvm->addPoint(SparseTimeValueModel::Point - (pointFrame, value, "")); + stvm->add(Event(pointFrame, value, "")); } else if (wwfm) {