annotate data/model/SparseTimeValueModel.h @ 1873:1d44fdc8196c csv-import-headers

Extend tests to include testing (at least some of) the actual data as well as the layout
author Chris Cannam
date Thu, 18 Jun 2020 13:42:48 +0100
parents 21c792334c2e
children
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@1798 108 QString getScaleUnits() const {
Chris@1798 109 QMutexLocker locker(&m_mutex);
Chris@1798 110 return m_units;
Chris@1798 111 }
Chris@1651 112 void setScaleUnits(QString units) {
Chris@1798 113 QMutexLocker locker(&m_mutex);
Chris@1651 114 m_units = units;
Chris@1651 115 UnitDatabase::getInstance()->registerUnit(units);
Chris@1651 116 }
Chris@1651 117
Chris@1651 118 bool hasTextLabels() const { return m_haveTextLabels; }
Chris@1651 119
Chris@1651 120 float getValueMinimum() const { return m_valueMinimum; }
Chris@1651 121 float getValueMaximum() const { return m_valueMaximum; }
Chris@1651 122
Chris@1671 123 int getCompletion() const override { return m_completion; }
Chris@1651 124
Chris@1651 125 void setCompletion(int completion, bool update = true) {
Chris@1655 126
Chris@1798 127 {
Chris@1651 128 if (m_completion == completion) return;
Chris@1651 129 m_completion = completion;
Chris@1651 130 }
Chris@1651 131
Chris@1651 132 if (update) {
Chris@1651 133 m_notifier.makeDeferredNotifications();
Chris@1651 134 }
Chris@1651 135
Chris@1752 136 emit completionChanged(getId());
Chris@1651 137
Chris@1651 138 if (completion == 100) {
Chris@1651 139 // henceforth:
Chris@1651 140 m_notifier.switchMode(DeferredNotifier::NOTIFY_ALWAYS);
Chris@1752 141 emit modelChanged(getId());
Chris@1651 142 }
Chris@1651 143 }
Chris@1651 144
Chris@1651 145 /**
Chris@1651 146 * Query methods.
Chris@1651 147 */
Chris@1651 148
Chris@1651 149 int getEventCount() const {
Chris@1651 150 return m_events.count();
Chris@1651 151 }
Chris@1651 152 bool isEmpty() const {
Chris@1651 153 return m_events.isEmpty();
Chris@1651 154 }
Chris@1651 155 bool containsEvent(const Event &e) const {
Chris@1651 156 return m_events.contains(e);
Chris@1651 157 }
Chris@1651 158 EventVector getAllEvents() const {
Chris@1651 159 return m_events.getAllEvents();
Chris@1651 160 }
Chris@1651 161 EventVector getEventsSpanning(sv_frame_t f, sv_frame_t duration) const {
Chris@1651 162 return m_events.getEventsSpanning(f, duration);
Chris@1651 163 }
Chris@1656 164 EventVector getEventsCovering(sv_frame_t f) const {
Chris@1656 165 return m_events.getEventsCovering(f);
Chris@1656 166 }
Chris@1655 167 EventVector getEventsWithin(sv_frame_t f, sv_frame_t duration,
Chris@1655 168 int overspill = 0) const {
Chris@1655 169 return m_events.getEventsWithin(f, duration, overspill);
Chris@1651 170 }
Chris@1651 171 EventVector getEventsStartingWithin(sv_frame_t f, sv_frame_t duration) const {
Chris@1651 172 return m_events.getEventsStartingWithin(f, duration);
Chris@1651 173 }
Chris@1656 174 EventVector getEventsStartingAt(sv_frame_t f) const {
Chris@1656 175 return m_events.getEventsStartingAt(f);
Chris@1651 176 }
Chris@1655 177 bool getNearestEventMatching(sv_frame_t startSearchAt,
Chris@1655 178 std::function<bool(Event)> predicate,
Chris@1655 179 EventSeries::Direction direction,
Chris@1655 180 Event &found) const {
Chris@1655 181 return m_events.getNearestEventMatching
Chris@1655 182 (startSearchAt, predicate, direction, found);
Chris@1655 183 }
Chris@1651 184
Chris@1651 185 /**
Chris@1651 186 * Editing methods.
Chris@1651 187 */
Chris@1651 188 void add(Event e) override {
Chris@1651 189
Chris@1651 190 bool allChange = false;
Chris@1651 191
Chris@1798 192 m_events.add(e.withoutDuration()); // can't have duration here
Chris@1651 193
Chris@1798 194 if (e.getLabel() != "") {
Chris@1798 195 m_haveTextLabels = true;
Chris@1798 196 }
Chris@1798 197
Chris@1798 198 float v = e.getValue();
Chris@1798 199 if (!ISNAN(v) && !ISINF(v)) {
Chris@1798 200 if (!m_haveExtents || v < m_valueMinimum) {
Chris@1798 201 m_valueMinimum = v; allChange = true;
Chris@1651 202 }
Chris@1798 203 if (!m_haveExtents || v > m_valueMaximum) {
Chris@1798 204 m_valueMaximum = v; allChange = true;
Chris@1651 205 }
Chris@1798 206 m_haveExtents = true;
Chris@1651 207 }
Chris@1651 208
Chris@1651 209 m_notifier.update(e.getFrame(), m_resolution);
Chris@1651 210
Chris@1651 211 if (allChange) {
Chris@1752 212 emit modelChanged(getId());
Chris@1651 213 }
Chris@1651 214 }
Chris@1651 215
Chris@1651 216 void remove(Event e) override {
Chris@1798 217 m_events.remove(e);
Chris@1752 218 emit modelChangedWithin(getId(),
Chris@1752 219 e.getFrame(), e.getFrame() + m_resolution);
Chris@1651 220 }
Chris@1651 221
Chris@420 222 /**
Chris@420 223 * TabularModel methods.
Chris@420 224 */
Chris@420 225
Chris@1651 226 int getRowCount() const override {
Chris@1651 227 return m_events.count();
Chris@1651 228 }
Chris@1651 229
Chris@1651 230 int getColumnCount() const override {
Chris@420 231 return 4;
Chris@420 232 }
Chris@420 233
Chris@1651 234 bool isColumnTimeValue(int column) const override {
Chris@1651 235 return (column < 2);
Chris@1651 236 }
Chris@1651 237
Chris@1651 238 sv_frame_t getFrameForRow(int row) const override {
Chris@1651 239 if (row < 0 || row >= m_events.count()) {
Chris@1651 240 return 0;
Chris@1651 241 }
Chris@1651 242 Event e = m_events.getEventByIndex(row);
Chris@1651 243 return e.getFrame();
Chris@1651 244 }
Chris@1651 245
Chris@1651 246 int getRowForFrame(sv_frame_t frame) const override {
Chris@1651 247 return m_events.getIndexForEvent(Event(frame));
Chris@1651 248 }
Chris@1651 249
Chris@1651 250 QString getHeading(int column) const override {
Chris@420 251 switch (column) {
Chris@420 252 case 0: return tr("Time");
Chris@420 253 case 1: return tr("Frame");
Chris@420 254 case 2: return tr("Value");
Chris@420 255 case 3: return tr("Label");
Chris@420 256 default: return tr("Unknown");
Chris@420 257 }
Chris@420 258 }
Chris@420 259
Chris@1651 260 SortType getSortType(int column) const override {
Chris@1651 261 if (column == 3) return SortAlphabetical;
Chris@1651 262 return SortNumeric;
Chris@1651 263 }
Chris@1651 264
Chris@1651 265 QVariant getData(int row, int column, int role) const override {
Chris@1651 266
Chris@1651 267 if (row < 0 || row >= m_events.count()) {
Chris@1651 268 return QVariant();
Chris@425 269 }
Chris@425 270
Chris@1651 271 Event e = m_events.getEventByIndex(row);
Chris@420 272
Chris@420 273 switch (column) {
Chris@1651 274 case 0: return adaptFrameForRole(e.getFrame(), getSampleRate(), role);
Chris@1651 275 case 1: return int(e.getFrame());
Chris@1651 276 case 2: return adaptValueForRole(e.getValue(), getScaleUnits(), role);
Chris@1651 277 case 3: return e.getLabel();
Chris@420 278 default: return QVariant();
Chris@420 279 }
Chris@420 280 }
Chris@420 281
Chris@1804 282 bool isEditable() const override { return true; }
Chris@1804 283
Chris@1804 284 Command *getSetDataCommand(int row, int column, const QVariant &value,
Chris@1804 285 int role) override {
Chris@1651 286 if (row < 0 || row >= m_events.count()) return nullptr;
Chris@1651 287 if (role != Qt::EditRole) return nullptr;
Chris@1651 288
Chris@1651 289 Event e0 = m_events.getEventByIndex(row);
Chris@1651 290 Event e1;
Chris@1651 291
Chris@1651 292 switch (column) {
Chris@1651 293 case 0: e1 = e0.withFrame(sv_frame_t(round(value.toDouble() *
Chris@1651 294 getSampleRate()))); break;
Chris@1651 295 case 1: e1 = e0.withFrame(value.toInt()); break;
Chris@1651 296 case 2: e1 = e0.withValue(float(value.toDouble())); break;
Chris@1651 297 case 3: e1 = e0.withLabel(value.toString()); break;
Chris@425 298 }
Chris@425 299
Chris@1742 300 auto command = new ChangeEventsCommand(getId().untyped, tr("Edit Data"));
Chris@1651 301 command->remove(e0);
Chris@1651 302 command->add(e1);
Chris@420 303 return command->finish();
Chris@420 304 }
Chris@1804 305
Chris@1804 306 Command *getInsertRowCommand(int row) override {
Chris@1804 307 if (row < 0 || row >= m_events.count()) return nullptr;
Chris@1804 308 auto command = new ChangeEventsCommand(getId().untyped,
Chris@1804 309 tr("Add Point"));
Chris@1804 310 Event e = m_events.getEventByIndex(row);
Chris@1804 311 command->add(e);
Chris@1804 312 return command->finish();
Chris@1804 313 }
Chris@1804 314
Chris@1804 315 Command *getRemoveRowCommand(int row) override {
Chris@1804 316 if (row < 0 || row >= m_events.count()) return nullptr;
Chris@1804 317 auto command = new ChangeEventsCommand(getId().untyped,
Chris@1804 318 tr("Delete Point"));
Chris@1804 319 Event e = m_events.getEventByIndex(row);
Chris@1804 320 command->remove(e);
Chris@1804 321 return command->finish();
Chris@1804 322 }
Chris@1651 323
Chris@1651 324 /**
Chris@1651 325 * XmlExportable methods.
Chris@1651 326 */
Chris@1651 327 void toXml(QTextStream &out,
Chris@1651 328 QString indent = "",
Chris@1651 329 QString extraAttributes = "") const override {
Chris@420 330
Chris@1651 331 Model::toXml
Chris@1651 332 (out,
Chris@1651 333 indent,
Chris@1651 334 QString("type=\"sparse\" dimensions=\"2\" resolution=\"%1\" "
Chris@1651 335 "notifyOnAdd=\"%2\" dataset=\"%3\" "
Chris@1651 336 "minimum=\"%4\" maximum=\"%5\" "
Chris@1651 337 "units=\"%6\" %7")
Chris@1651 338 .arg(m_resolution)
Chris@1651 339 .arg("true") // always true after model reaches 100% -
Chris@1651 340 // subsequent events are always notified
Chris@1677 341 .arg(m_events.getExportId())
Chris@1651 342 .arg(m_valueMinimum)
Chris@1651 343 .arg(m_valueMaximum)
Chris@1651 344 .arg(encodeEntities(m_units))
Chris@1651 345 .arg(extraAttributes));
Chris@1651 346
Chris@1651 347 m_events.toXml(out, indent, QString("dimensions=\"2\""));
Chris@420 348 }
Chris@1679 349
Chris@1833 350 QVector<QString>
Chris@1833 351 getStringExportHeaders(DataExportOptions options) const override {
Chris@1833 352 return m_events.getStringExportHeaders(options, {});
Chris@1815 353 }
Chris@1815 354
Chris@1833 355 QVector<QVector<QString>>
Chris@1833 356 toStringExportRows(DataExportOptions options,
Chris@1833 357 sv_frame_t startFrame,
Chris@1833 358 sv_frame_t duration) const override {
Chris@1833 359 return m_events.toStringExportRows(options,
Chris@1833 360 startFrame,
Chris@1833 361 duration,
Chris@1833 362 m_sampleRate,
Chris@1833 363 m_resolution,
Chris@1833 364 Event().withValue(0.f));
Chris@1679 365 }
Chris@1651 366
Chris@1651 367 protected:
Chris@1651 368 sv_samplerate_t m_sampleRate;
Chris@1651 369 int m_resolution;
Chris@422 370
Chris@1798 371 std::atomic<float> m_valueMinimum;
Chris@1798 372 std::atomic<float> m_valueMaximum;
Chris@1798 373 std::atomic<bool> m_haveExtents;
Chris@1798 374 std::atomic<bool> m_haveTextLabels;
Chris@1651 375 QString m_units;
Chris@1651 376 DeferredNotifier m_notifier;
Chris@1798 377 std::atomic<int> m_completion;
Chris@1651 378
Chris@1651 379 EventSeries m_events;
Chris@147 380 };
Chris@147 381
Chris@147 382 #endif
Chris@147 383
Chris@147 384
Chris@147 385