annotate data/model/SparseTimeValueModel.h @ 1752:6d09d68165a4 by-id

Further review of ById: make IDs only available when adding a model to the ById store, not by querying the item directly. This means any id encountered in the wild must have been added to the store at some point (even if later released), which simplifies reasoning about lifecycles
author Chris Cannam
date Fri, 05 Jul 2019 15:28:07 +0100
parents 77543124651b
children 13bd41bd8a17
rev   line source
Chris@147 1 /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
Chris@147 2
Chris@147 3 /*
Chris@147 4 Sonic Visualiser
Chris@147 5 An audio file viewer and annotation editor.
Chris@147 6 Centre for Digital Music, Queen Mary, University of London.
Chris@147 7
Chris@147 8 This program is free software; you can redistribute it and/or
Chris@147 9 modify it under the terms of the GNU General Public License as
Chris@147 10 published by the Free Software Foundation; either version 2 of the
Chris@147 11 License, or (at your option) any later version. See the file
Chris@147 12 COPYING included with this distribution for more information.
Chris@147 13 */
Chris@147 14
Chris@1581 15 #ifndef SV_SPARSE_TIME_VALUE_MODEL_H
Chris@1581 16 #define SV_SPARSE_TIME_VALUE_MODEL_H
Chris@147 17
Chris@1651 18 #include "EventCommands.h"
Chris@1651 19 #include "TabularModel.h"
Chris@1651 20 #include "Model.h"
Chris@1651 21 #include "DeferredNotifier.h"
Chris@1651 22
Chris@1651 23 #include "base/RealTime.h"
Chris@1651 24 #include "base/EventSeries.h"
Chris@1651 25 #include "base/UnitDatabase.h"
Chris@150 26 #include "base/PlayParameterRepository.h"
Chris@1651 27
Chris@1651 28 #include "system/System.h"
Chris@147 29
Chris@147 30 /**
Chris@1651 31 * A model representing a wiggly-line plot with points at arbitrary
Chris@1651 32 * intervals of the model resolution.
Chris@147 33 */
Chris@1651 34 class SparseTimeValueModel : public Model,
Chris@1651 35 public TabularModel,
Chris@1651 36 public EventEditable
Chris@147 37 {
Chris@423 38 Q_OBJECT
Chris@423 39
Chris@147 40 public:
Chris@1651 41 SparseTimeValueModel(sv_samplerate_t sampleRate,
Chris@1651 42 int resolution,
Chris@1429 43 bool notifyOnAdd = true) :
Chris@1651 44 m_sampleRate(sampleRate),
Chris@1651 45 m_resolution(resolution),
Chris@1651 46 m_valueMinimum(0.f),
Chris@1651 47 m_valueMaximum(0.f),
Chris@1651 48 m_haveExtents(false),
Chris@1651 49 m_haveTextLabels(false),
Chris@1651 50 m_notifier(this,
Chris@1752 51 getId(),
Chris@1651 52 notifyOnAdd ?
Chris@1651 53 DeferredNotifier::NOTIFY_ALWAYS :
Chris@1651 54 DeferredNotifier::NOTIFY_DEFERRED),
Chris@1655 55 m_completion(100) {
Chris@868 56 // Model is playable, but may not sound (if units not Hz or
Chris@868 57 // range unsuitable)
Chris@1751 58 PlayParameterRepository::getInstance()->addPlayable
Chris@1751 59 (getId().untyped, this);
Chris@256 60 }
Chris@256 61
Chris@1040 62 SparseTimeValueModel(sv_samplerate_t sampleRate, int resolution,
Chris@1429 63 float valueMinimum, float valueMaximum,
Chris@1429 64 bool notifyOnAdd = true) :
Chris@1651 65 m_sampleRate(sampleRate),
Chris@1651 66 m_resolution(resolution),
Chris@1651 67 m_valueMinimum(valueMinimum),
Chris@1651 68 m_valueMaximum(valueMaximum),
Chris@1684 69 m_haveExtents(true),
Chris@1651 70 m_haveTextLabels(false),
Chris@1651 71 m_notifier(this,
Chris@1752 72 getId(),
Chris@1651 73 notifyOnAdd ?
Chris@1651 74 DeferredNotifier::NOTIFY_ALWAYS :
Chris@1651 75 DeferredNotifier::NOTIFY_DEFERRED),
Chris@1655 76 m_completion(100) {
Chris@868 77 // Model is playable, but may not sound (if units not Hz or
Chris@868 78 // range unsuitable)
Chris@1751 79 PlayParameterRepository::getInstance()->addPlayable
Chris@1751 80 (getId().untyped, this);
Chris@868 81 }
Chris@868 82
Chris@1651 83 virtual ~SparseTimeValueModel() {
Chris@1751 84 PlayParameterRepository::getInstance()->removePlayable
Chris@1751 85 (getId().untyped);
Chris@147 86 }
Chris@345 87
Chris@1580 88 QString getTypeName() const override { return tr("Sparse Time-Value"); }
Chris@1692 89 bool isSparse() const override { return true; }
Chris@1659 90 bool isOK() const override { return true; }
Chris@420 91
Chris@1659 92 sv_frame_t getStartFrame() const override {
Chris@1659 93 return m_events.getStartFrame();
Chris@1659 94 }
Chris@1725 95 sv_frame_t getTrueEndFrame() const override {
Chris@1659 96 if (m_events.isEmpty()) return 0;
Chris@1659 97 sv_frame_t e = m_events.getEndFrame() + 1;
Chris@1659 98 if (e % m_resolution == 0) return e;
Chris@1659 99 else return (e / m_resolution + 1) * m_resolution;
Chris@1659 100 }
Chris@1659 101
Chris@1651 102 sv_samplerate_t getSampleRate() const override { return m_sampleRate; }
Chris@1651 103 int getResolution() const { return m_resolution; }
Chris@1651 104
Chris@1580 105 bool canPlay() const override { return true; }
Chris@1580 106 bool getDefaultPlayAudible() const override { return false; } // user must unmute
Chris@868 107
Chris@1651 108 QString getScaleUnits() const { return m_units; }
Chris@1651 109 void setScaleUnits(QString units) {
Chris@1651 110 m_units = units;
Chris@1651 111 UnitDatabase::getInstance()->registerUnit(units);
Chris@1651 112 }
Chris@1651 113
Chris@1651 114 bool hasTextLabels() const { return m_haveTextLabels; }
Chris@1651 115
Chris@1651 116 float getValueMinimum() const { return m_valueMinimum; }
Chris@1651 117 float getValueMaximum() const { return m_valueMaximum; }
Chris@1651 118
Chris@1671 119 int getCompletion() const override { return m_completion; }
Chris@1651 120
Chris@1651 121 void setCompletion(int completion, bool update = true) {
Chris@1655 122
Chris@1651 123 { QMutexLocker locker(&m_mutex);
Chris@1651 124 if (m_completion == completion) return;
Chris@1651 125 m_completion = completion;
Chris@1651 126 }
Chris@1651 127
Chris@1651 128 if (update) {
Chris@1651 129 m_notifier.makeDeferredNotifications();
Chris@1651 130 }
Chris@1651 131
Chris@1752 132 emit completionChanged(getId());
Chris@1651 133
Chris@1651 134 if (completion == 100) {
Chris@1651 135 // henceforth:
Chris@1651 136 m_notifier.switchMode(DeferredNotifier::NOTIFY_ALWAYS);
Chris@1752 137 emit modelChanged(getId());
Chris@1651 138 }
Chris@1651 139 }
Chris@1651 140
Chris@1651 141 /**
Chris@1651 142 * Query methods.
Chris@1651 143 */
Chris@1651 144
Chris@1651 145 int getEventCount() const {
Chris@1651 146 return m_events.count();
Chris@1651 147 }
Chris@1651 148 bool isEmpty() const {
Chris@1651 149 return m_events.isEmpty();
Chris@1651 150 }
Chris@1651 151 bool containsEvent(const Event &e) const {
Chris@1651 152 return m_events.contains(e);
Chris@1651 153 }
Chris@1651 154 EventVector getAllEvents() const {
Chris@1651 155 return m_events.getAllEvents();
Chris@1651 156 }
Chris@1651 157 EventVector getEventsSpanning(sv_frame_t f, sv_frame_t duration) const {
Chris@1651 158 return m_events.getEventsSpanning(f, duration);
Chris@1651 159 }
Chris@1656 160 EventVector getEventsCovering(sv_frame_t f) const {
Chris@1656 161 return m_events.getEventsCovering(f);
Chris@1656 162 }
Chris@1655 163 EventVector getEventsWithin(sv_frame_t f, sv_frame_t duration,
Chris@1655 164 int overspill = 0) const {
Chris@1655 165 return m_events.getEventsWithin(f, duration, overspill);
Chris@1651 166 }
Chris@1651 167 EventVector getEventsStartingWithin(sv_frame_t f, sv_frame_t duration) const {
Chris@1651 168 return m_events.getEventsStartingWithin(f, duration);
Chris@1651 169 }
Chris@1656 170 EventVector getEventsStartingAt(sv_frame_t f) const {
Chris@1656 171 return m_events.getEventsStartingAt(f);
Chris@1651 172 }
Chris@1655 173 bool getNearestEventMatching(sv_frame_t startSearchAt,
Chris@1655 174 std::function<bool(Event)> predicate,
Chris@1655 175 EventSeries::Direction direction,
Chris@1655 176 Event &found) const {
Chris@1655 177 return m_events.getNearestEventMatching
Chris@1655 178 (startSearchAt, predicate, direction, found);
Chris@1655 179 }
Chris@1651 180
Chris@1651 181 /**
Chris@1651 182 * Editing methods.
Chris@1651 183 */
Chris@1651 184 void add(Event e) override {
Chris@1651 185
Chris@1651 186 bool allChange = false;
Chris@1651 187
Chris@1658 188 { QMutexLocker locker(&m_mutex);
Chris@1655 189 m_events.add(e.withoutDuration()); // can't have duration here
Chris@1651 190
Chris@1651 191 if (e.getLabel() != "") {
Chris@1651 192 m_haveTextLabels = true;
Chris@1651 193 }
Chris@1651 194
Chris@1651 195 float v = e.getValue();
Chris@1651 196 if (!ISNAN(v) && !ISINF(v)) {
Chris@1651 197 if (!m_haveExtents || v < m_valueMinimum) {
Chris@1651 198 m_valueMinimum = v; allChange = true;
Chris@1651 199 }
Chris@1651 200 if (!m_haveExtents || v > m_valueMaximum) {
Chris@1651 201 m_valueMaximum = v; allChange = true;
Chris@1651 202 }
Chris@1651 203 m_haveExtents = true;
Chris@1651 204 }
Chris@1651 205 }
Chris@1651 206
Chris@1651 207 m_notifier.update(e.getFrame(), m_resolution);
Chris@1651 208
Chris@1651 209 if (allChange) {
Chris@1752 210 emit modelChanged(getId());
Chris@1651 211 }
Chris@1651 212 }
Chris@1651 213
Chris@1651 214 void remove(Event e) override {
Chris@1651 215 {
Chris@1651 216 QMutexLocker locker(&m_mutex);
Chris@1651 217 m_events.remove(e);
Chris@1651 218 }
Chris@1752 219 emit modelChangedWithin(getId(),
Chris@1752 220 e.getFrame(), e.getFrame() + m_resolution);
Chris@1651 221 }
Chris@1651 222
Chris@420 223 /**
Chris@420 224 * TabularModel methods.
Chris@420 225 */
Chris@420 226
Chris@1651 227 int getRowCount() const override {
Chris@1651 228 return m_events.count();
Chris@1651 229 }
Chris@1651 230
Chris@1651 231 int getColumnCount() const override {
Chris@420 232 return 4;
Chris@420 233 }
Chris@420 234
Chris@1651 235 bool isColumnTimeValue(int column) const override {
Chris@1651 236 return (column < 2);
Chris@1651 237 }
Chris@1651 238
Chris@1651 239 sv_frame_t getFrameForRow(int row) const override {
Chris@1651 240 if (row < 0 || row >= m_events.count()) {
Chris@1651 241 return 0;
Chris@1651 242 }
Chris@1651 243 Event e = m_events.getEventByIndex(row);
Chris@1651 244 return e.getFrame();
Chris@1651 245 }
Chris@1651 246
Chris@1651 247 int getRowForFrame(sv_frame_t frame) const override {
Chris@1651 248 return m_events.getIndexForEvent(Event(frame));
Chris@1651 249 }
Chris@1651 250
Chris@1651 251 QString getHeading(int column) const override {
Chris@420 252 switch (column) {
Chris@420 253 case 0: return tr("Time");
Chris@420 254 case 1: return tr("Frame");
Chris@420 255 case 2: return tr("Value");
Chris@420 256 case 3: return tr("Label");
Chris@420 257 default: return tr("Unknown");
Chris@420 258 }
Chris@420 259 }
Chris@420 260
Chris@1651 261 SortType getSortType(int column) const override {
Chris@1651 262 if (column == 3) return SortAlphabetical;
Chris@1651 263 return SortNumeric;
Chris@1651 264 }
Chris@1651 265
Chris@1651 266 QVariant getData(int row, int column, int role) const override {
Chris@1651 267
Chris@1651 268 if (row < 0 || row >= m_events.count()) {
Chris@1651 269 return QVariant();
Chris@425 270 }
Chris@425 271
Chris@1651 272 Event e = m_events.getEventByIndex(row);
Chris@420 273
Chris@420 274 switch (column) {
Chris@1651 275 case 0: return adaptFrameForRole(e.getFrame(), getSampleRate(), role);
Chris@1651 276 case 1: return int(e.getFrame());
Chris@1651 277 case 2: return adaptValueForRole(e.getValue(), getScaleUnits(), role);
Chris@1651 278 case 3: return e.getLabel();
Chris@420 279 default: return QVariant();
Chris@420 280 }
Chris@420 281 }
Chris@420 282
Chris@1651 283 Command *getSetDataCommand(int row, int column, const QVariant &value, int role) override {
Chris@1651 284 if (row < 0 || row >= m_events.count()) return nullptr;
Chris@1651 285 if (role != Qt::EditRole) return nullptr;
Chris@1651 286
Chris@1651 287 Event e0 = m_events.getEventByIndex(row);
Chris@1651 288 Event e1;
Chris@1651 289
Chris@1651 290 switch (column) {
Chris@1651 291 case 0: e1 = e0.withFrame(sv_frame_t(round(value.toDouble() *
Chris@1651 292 getSampleRate()))); break;
Chris@1651 293 case 1: e1 = e0.withFrame(value.toInt()); break;
Chris@1651 294 case 2: e1 = e0.withValue(float(value.toDouble())); break;
Chris@1651 295 case 3: e1 = e0.withLabel(value.toString()); break;
Chris@425 296 }
Chris@425 297
Chris@1742 298 auto command = new ChangeEventsCommand(getId().untyped, tr("Edit Data"));
Chris@1651 299 command->remove(e0);
Chris@1651 300 command->add(e1);
Chris@420 301 return command->finish();
Chris@420 302 }
Chris@1651 303
Chris@1651 304 /**
Chris@1651 305 * XmlExportable methods.
Chris@1651 306 */
Chris@1651 307 void toXml(QTextStream &out,
Chris@1651 308 QString indent = "",
Chris@1651 309 QString extraAttributes = "") const override {
Chris@420 310
Chris@1651 311 Model::toXml
Chris@1651 312 (out,
Chris@1651 313 indent,
Chris@1651 314 QString("type=\"sparse\" dimensions=\"2\" resolution=\"%1\" "
Chris@1651 315 "notifyOnAdd=\"%2\" dataset=\"%3\" "
Chris@1651 316 "minimum=\"%4\" maximum=\"%5\" "
Chris@1651 317 "units=\"%6\" %7")
Chris@1651 318 .arg(m_resolution)
Chris@1651 319 .arg("true") // always true after model reaches 100% -
Chris@1651 320 // subsequent events are always notified
Chris@1677 321 .arg(m_events.getExportId())
Chris@1651 322 .arg(m_valueMinimum)
Chris@1651 323 .arg(m_valueMaximum)
Chris@1651 324 .arg(encodeEntities(m_units))
Chris@1651 325 .arg(extraAttributes));
Chris@1651 326
Chris@1651 327 m_events.toXml(out, indent, QString("dimensions=\"2\""));
Chris@420 328 }
Chris@1679 329
Chris@1679 330 QString toDelimitedDataString(QString delimiter,
Chris@1679 331 DataExportOptions options,
Chris@1679 332 sv_frame_t startFrame,
Chris@1679 333 sv_frame_t duration) const override {
Chris@1679 334 return m_events.toDelimitedDataString(delimiter,
Chris@1679 335 options,
Chris@1679 336 startFrame,
Chris@1679 337 duration,
Chris@1679 338 m_sampleRate,
Chris@1679 339 m_resolution,
Chris@1679 340 Event().withValue(0.f));
Chris@1679 341 }
Chris@1651 342
Chris@1651 343 protected:
Chris@1651 344 sv_samplerate_t m_sampleRate;
Chris@1651 345 int m_resolution;
Chris@422 346
Chris@1651 347 float m_valueMinimum;
Chris@1651 348 float m_valueMaximum;
Chris@1651 349 bool m_haveExtents;
Chris@1651 350 bool m_haveTextLabels;
Chris@1651 351 QString m_units;
Chris@1651 352 DeferredNotifier m_notifier;
Chris@1651 353 int m_completion;
Chris@1651 354
Chris@1651 355 EventSeries m_events;
Chris@1651 356
Chris@1651 357 mutable QMutex m_mutex;
Chris@147 358 };
Chris@147 359
Chris@147 360
Chris@147 361 #endif
Chris@147 362
Chris@147 363
Chris@147 364