annotate data/model/SparseOneDimensionalModel.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_ONE_DIMENSIONAL_MODEL_H
Chris@1581 16 #define SV_SPARSE_ONE_DIMENSIONAL_MODEL_H
Chris@147 17
Chris@1658 18 #include "EventCommands.h"
Chris@1658 19 #include "TabularModel.h"
Chris@1658 20 #include "Model.h"
Chris@1658 21 #include "DeferredNotifier.h"
Chris@1658 22
Chris@1615 23 #include "base/NoteData.h"
Chris@1658 24 #include "base/EventSeries.h"
Chris@1643 25 #include "base/NoteExportable.h"
Chris@150 26 #include "base/PlayParameterRepository.h"
Chris@147 27 #include "base/RealTime.h"
Chris@147 28
Chris@1658 29 #include "system/System.h"
Chris@1658 30
Chris@387 31 #include <QStringList>
Chris@387 32
Chris@1658 33 /**
Chris@1658 34 * A model representing a series of time instants with optional labels
Chris@1658 35 * but without values.
Chris@1658 36 */
Chris@1658 37 class SparseOneDimensionalModel : public Model,
Chris@1658 38 public TabularModel,
Chris@1658 39 public EventEditable,
Chris@852 40 public NoteExportable
Chris@147 41 {
Chris@423 42 Q_OBJECT
Chris@423 43
Chris@147 44 public:
Chris@1658 45 SparseOneDimensionalModel(sv_samplerate_t sampleRate,
Chris@1658 46 int resolution,
Chris@1429 47 bool notifyOnAdd = true) :
Chris@1658 48 m_sampleRate(sampleRate),
Chris@1658 49 m_resolution(resolution),
Chris@1660 50 m_haveTextLabels(false),
Chris@1658 51 m_notifier(this,
Chris@1752 52 getId(),
Chris@1658 53 notifyOnAdd ?
Chris@1658 54 DeferredNotifier::NOTIFY_ALWAYS :
Chris@1658 55 DeferredNotifier::NOTIFY_DEFERRED),
Chris@1658 56 m_completion(100) {
Chris@1751 57 PlayParameterRepository::getInstance()->addPlayable
Chris@1751 58 (getId().untyped, this);
Chris@391 59 }
Chris@391 60
Chris@1658 61 virtual ~SparseOneDimensionalModel() {
Chris@1751 62 PlayParameterRepository::getInstance()->removePlayable
Chris@1751 63 (getId().untyped);
Chris@391 64 }
Chris@391 65
Chris@1580 66 QString getTypeName() const override { return tr("Sparse 1-D"); }
Chris@1692 67 bool isSparse() const override { return true; }
Chris@1659 68 bool isOK() const override { return true; }
Chris@420 69
Chris@1659 70 sv_frame_t getStartFrame() const override {
Chris@1659 71 return m_events.getStartFrame();
Chris@1659 72 }
Chris@1725 73 sv_frame_t getTrueEndFrame() const override {
Chris@1659 74 if (m_events.isEmpty()) return 0;
Chris@1659 75 sv_frame_t e = m_events.getEndFrame() + 1;
Chris@1659 76 if (e % m_resolution == 0) return e;
Chris@1659 77 else return (e / m_resolution + 1) * m_resolution;
Chris@1659 78 }
Chris@1659 79
Chris@1658 80 sv_samplerate_t getSampleRate() const override { return m_sampleRate; }
Chris@1658 81 int getResolution() const { return m_resolution; }
Chris@1658 82
Chris@1658 83 bool canPlay() const override { return true; }
Chris@1658 84 QString getDefaultPlayClipId() const override { return "tap"; }
Chris@1658 85
Chris@1660 86 bool hasTextLabels() const { return m_haveTextLabels; }
Chris@1671 87
Chris@1671 88 int getCompletion() const override { return m_completion; }
Chris@1658 89
Chris@1658 90 void setCompletion(int completion, bool update = true) {
Chris@1658 91
Chris@1658 92 { QMutexLocker locker(&m_mutex);
Chris@1658 93 if (m_completion == completion) return;
Chris@1658 94 m_completion = completion;
Chris@1658 95 }
Chris@1658 96
Chris@1658 97 if (update) {
Chris@1658 98 m_notifier.makeDeferredNotifications();
Chris@1658 99 }
Chris@1658 100
Chris@1752 101 emit completionChanged(getId());
Chris@1658 102
Chris@1658 103 if (completion == 100) {
Chris@1658 104 // henceforth:
Chris@1658 105 m_notifier.switchMode(DeferredNotifier::NOTIFY_ALWAYS);
Chris@1752 106 emit modelChanged(getId());
Chris@1658 107 }
Chris@1658 108 }
Chris@1658 109
Chris@1658 110 /**
Chris@1658 111 * Query methods.
Chris@1658 112 */
Chris@1658 113
Chris@1658 114 int getEventCount() const {
Chris@1658 115 return m_events.count();
Chris@1658 116 }
Chris@1658 117 bool isEmpty() const {
Chris@1658 118 return m_events.isEmpty();
Chris@1658 119 }
Chris@1658 120 bool containsEvent(const Event &e) const {
Chris@1658 121 return m_events.contains(e);
Chris@1658 122 }
Chris@1658 123 EventVector getAllEvents() const {
Chris@1658 124 return m_events.getAllEvents();
Chris@1658 125 }
Chris@1658 126 EventVector getEventsSpanning(sv_frame_t f, sv_frame_t duration) const {
Chris@1658 127 return m_events.getEventsSpanning(f, duration);
Chris@1658 128 }
Chris@1658 129 EventVector getEventsCovering(sv_frame_t f) const {
Chris@1658 130 return m_events.getEventsCovering(f);
Chris@1658 131 }
Chris@1658 132 EventVector getEventsWithin(sv_frame_t f, sv_frame_t duration,
Chris@1658 133 int overspill = 0) const {
Chris@1658 134 return m_events.getEventsWithin(f, duration, overspill);
Chris@1658 135 }
Chris@1658 136 EventVector getEventsStartingWithin(sv_frame_t f, sv_frame_t duration) const {
Chris@1658 137 return m_events.getEventsStartingWithin(f, duration);
Chris@1658 138 }
Chris@1658 139 EventVector getEventsStartingAt(sv_frame_t f) const {
Chris@1658 140 return m_events.getEventsStartingAt(f);
Chris@1658 141 }
Chris@1658 142 bool getNearestEventMatching(sv_frame_t startSearchAt,
Chris@1658 143 std::function<bool(Event)> predicate,
Chris@1658 144 EventSeries::Direction direction,
Chris@1658 145 Event &found) const {
Chris@1658 146 return m_events.getNearestEventMatching
Chris@1658 147 (startSearchAt, predicate, direction, found);
Chris@1658 148 }
Chris@1658 149
Chris@1658 150 /**
Chris@1658 151 * Editing methods.
Chris@1658 152 */
Chris@1658 153 void add(Event e) override {
Chris@1658 154
Chris@1658 155 { QMutexLocker locker(&m_mutex);
Chris@1658 156 m_events.add(e.withoutValue().withoutDuration());
Chris@1660 157
Chris@1660 158 if (e.getLabel() != "") {
Chris@1660 159 m_haveTextLabels = true;
Chris@1660 160 }
Chris@1658 161 }
Chris@1658 162
Chris@1658 163 m_notifier.update(e.getFrame(), m_resolution);
Chris@1658 164 }
Chris@1658 165
Chris@1658 166 void remove(Event e) override {
Chris@1658 167 { QMutexLocker locker(&m_mutex);
Chris@1658 168 m_events.remove(e);
Chris@1658 169 }
Chris@1752 170 emit modelChangedWithin(getId(),
Chris@1752 171 e.getFrame(), e.getFrame() + m_resolution);
Chris@1658 172 }
Chris@1658 173
Chris@420 174 /**
Chris@420 175 * TabularModel methods.
Chris@420 176 */
Chris@420 177
Chris@1658 178 int getRowCount() const override {
Chris@1658 179 return m_events.count();
Chris@1658 180 }
Chris@1658 181
Chris@1658 182 int getColumnCount() const override {
Chris@420 183 return 3;
Chris@420 184 }
Chris@420 185
Chris@1658 186 bool isColumnTimeValue(int column) const override {
Chris@1658 187 return (column < 2);
Chris@1658 188 }
Chris@1658 189
Chris@1658 190 sv_frame_t getFrameForRow(int row) const override {
Chris@1658 191 if (row < 0 || row >= m_events.count()) {
Chris@1658 192 return 0;
Chris@1658 193 }
Chris@1658 194 Event e = m_events.getEventByIndex(row);
Chris@1658 195 return e.getFrame();
Chris@1658 196 }
Chris@1658 197
Chris@1658 198 int getRowForFrame(sv_frame_t frame) const override {
Chris@1658 199 return m_events.getIndexForEvent(Event(frame));
Chris@1658 200 }
Chris@1658 201
Chris@1658 202 QString getHeading(int column) const override {
Chris@420 203 switch (column) {
Chris@420 204 case 0: return tr("Time");
Chris@420 205 case 1: return tr("Frame");
Chris@420 206 case 2: return tr("Label");
Chris@420 207 default: return tr("Unknown");
Chris@420 208 }
Chris@420 209 }
Chris@420 210
Chris@1658 211 SortType getSortType(int column) const override {
Chris@1658 212 if (column == 2) return SortAlphabetical;
Chris@1658 213 return SortNumeric;
Chris@1658 214 }
Chris@1658 215
Chris@1658 216 QVariant getData(int row, int column, int role) const override {
Chris@1658 217
Chris@1658 218 if (row < 0 || row >= m_events.count()) {
Chris@1658 219 return QVariant();
Chris@425 220 }
Chris@425 221
Chris@1658 222 Event e = m_events.getEventByIndex(row);
Chris@420 223
Chris@420 224 switch (column) {
Chris@1658 225 case 0: return adaptFrameForRole(e.getFrame(), getSampleRate(), role);
Chris@1658 226 case 1: return int(e.getFrame());
Chris@1658 227 case 2: return e.getLabel();
Chris@420 228 default: return QVariant();
Chris@420 229 }
Chris@420 230 }
Chris@420 231
Chris@1658 232 Command *getSetDataCommand(int row, int column, const QVariant &value, int role) override {
Chris@1658 233 if (row < 0 || row >= m_events.count()) return nullptr;
Chris@1658 234 if (role != Qt::EditRole) return nullptr;
Chris@1658 235
Chris@1658 236 Event e0 = m_events.getEventByIndex(row);
Chris@1658 237 Event e1;
Chris@1658 238
Chris@1658 239 switch (column) {
Chris@1658 240 case 0: e1 = e0.withFrame(sv_frame_t(round(value.toDouble() *
Chris@1658 241 getSampleRate()))); break;
Chris@1658 242 case 1: e1 = e0.withFrame(value.toInt()); break;
Chris@1658 243 case 2: e1 = e0.withLabel(value.toString()); break;
Chris@425 244 }
Chris@425 245
Chris@1742 246 auto command = new ChangeEventsCommand(getId().untyped, tr("Edit Data"));
Chris@1658 247 command->remove(e0);
Chris@1658 248 command->add(e1);
Chris@424 249 return command->finish();
Chris@424 250 }
Chris@424 251
Chris@852 252 /**
Chris@852 253 * NoteExportable methods.
Chris@852 254 */
Chris@852 255
Chris@1580 256 NoteList getNotes() const override {
Chris@1643 257 return getNotesStartingWithin(getStartFrame(),
Chris@1643 258 getEndFrame() - getStartFrame());
Chris@852 259 }
Chris@852 260
Chris@1643 261 NoteList getNotesActiveAt(sv_frame_t frame) const override {
Chris@1643 262 return getNotesStartingWithin(frame, 1);
Chris@1643 263 }
Chris@1643 264
Chris@1643 265 NoteList getNotesStartingWithin(sv_frame_t startFrame,
Chris@1643 266 sv_frame_t duration) const override {
Chris@852 267
Chris@852 268 NoteList notes;
Chris@1658 269 EventVector ee = m_events.getEventsStartingWithin(startFrame, duration);
Chris@1658 270 for (const auto &e: ee) {
Chris@1658 271 notes.push_back(e.toNoteData(getSampleRate(), true));
Chris@852 272 }
Chris@852 273 return notes;
Chris@852 274 }
Chris@1658 275
Chris@1658 276 /**
Chris@1658 277 * XmlExportable methods.
Chris@1658 278 */
Chris@1658 279 void toXml(QTextStream &out,
Chris@1658 280 QString indent = "",
Chris@1658 281 QString extraAttributes = "") const override {
Chris@1658 282
Chris@1658 283 Model::toXml
Chris@1658 284 (out,
Chris@1658 285 indent,
Chris@1658 286 QString("type=\"sparse\" dimensions=\"1\" resolution=\"%1\" "
Chris@1658 287 "notifyOnAdd=\"%2\" dataset=\"%3\" %4")
Chris@1658 288 .arg(m_resolution)
Chris@1658 289 .arg("true") // always true after model reaches 100% -
Chris@1658 290 // subsequent events are always notified
Chris@1677 291 .arg(m_events.getExportId())
Chris@1658 292 .arg(extraAttributes));
Chris@1658 293
Chris@1658 294 m_events.toXml(out, indent, QString("dimensions=\"1\""));
Chris@1658 295 }
Chris@1679 296
Chris@1679 297 QString toDelimitedDataString(QString delimiter,
Chris@1679 298 DataExportOptions options,
Chris@1679 299 sv_frame_t startFrame,
Chris@1679 300 sv_frame_t duration) const override {
Chris@1679 301 return m_events.toDelimitedDataString(delimiter,
Chris@1679 302 options,
Chris@1679 303 startFrame,
Chris@1679 304 duration,
Chris@1679 305 m_sampleRate,
Chris@1679 306 m_resolution,
Chris@1679 307 Event());
Chris@1679 308 }
Chris@1658 309
Chris@1658 310 protected:
Chris@1658 311 sv_samplerate_t m_sampleRate;
Chris@1658 312 int m_resolution;
Chris@1658 313
Chris@1660 314 bool m_haveTextLabels;
Chris@1658 315 DeferredNotifier m_notifier;
Chris@1658 316 int m_completion;
Chris@1658 317
Chris@1658 318 EventSeries m_events;
Chris@1658 319
Chris@1658 320 mutable QMutex m_mutex;
Chris@147 321 };
Chris@147 322
Chris@147 323 #endif
Chris@147 324
Chris@147 325
Chris@147 326