annotate data/model/NoteModel.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@1495 15 #ifndef SV_NOTE_MODEL_H
Chris@1495 16 #define SV_NOTE_MODEL_H
Chris@147 17
Chris@1643 18 #include "Model.h"
Chris@1643 19 #include "TabularModel.h"
Chris@1648 20 #include "EventCommands.h"
Chris@1651 21 #include "DeferredNotifier.h"
Chris@1643 22 #include "base/UnitDatabase.h"
Chris@1643 23 #include "base/EventSeries.h"
Chris@1615 24 #include "base/NoteData.h"
Chris@1643 25 #include "base/NoteExportable.h"
Chris@391 26 #include "base/RealTime.h"
Chris@150 27 #include "base/PlayParameterRepository.h"
Chris@852 28 #include "base/Pitch.h"
Chris@1643 29 #include "system/System.h"
Chris@147 30
Chris@1643 31 #include <QMutexLocker>
Chris@441 32
Chris@1643 33 class NoteModel : public Model,
Chris@1643 34 public TabularModel,
Chris@1648 35 public NoteExportable,
Chris@1648 36 public EventEditable
Chris@147 37 {
Chris@423 38 Q_OBJECT
Chris@423 39
Chris@147 40 public:
Chris@1647 41 enum Subtype {
Chris@1647 42 NORMAL_NOTE,
Chris@1647 43 FLEXI_NOTE
Chris@1647 44 };
Chris@1647 45
Chris@1644 46 NoteModel(sv_samplerate_t sampleRate,
Chris@1644 47 int resolution,
Chris@1647 48 bool notifyOnAdd = true,
Chris@1647 49 Subtype subtype = NORMAL_NOTE) :
Chris@1647 50 m_subtype(subtype),
Chris@1643 51 m_sampleRate(sampleRate),
Chris@1643 52 m_resolution(resolution),
Chris@1643 53 m_valueMinimum(0.f),
Chris@1643 54 m_valueMaximum(0.f),
Chris@1643 55 m_haveExtents(false),
Chris@1643 56 m_valueQuantization(0),
Chris@1643 57 m_units(""),
Chris@1651 58 m_notifier(this,
Chris@1752 59 getId(),
Chris@1651 60 notifyOnAdd ?
Chris@1651 61 DeferredNotifier::NOTIFY_ALWAYS :
Chris@1651 62 DeferredNotifier::NOTIFY_DEFERRED),
Chris@1655 63 m_completion(100) {
Chris@1647 64 if (subtype == FLEXI_NOTE) {
Chris@1647 65 m_valueMinimum = 33.f;
Chris@1647 66 m_valueMaximum = 88.f;
Chris@1647 67 }
Chris@1751 68 PlayParameterRepository::getInstance()->addPlayable
Chris@1751 69 (getId().untyped, this);
Chris@256 70 }
Chris@256 71
Chris@1040 72 NoteModel(sv_samplerate_t sampleRate, int resolution,
Chris@1429 73 float valueMinimum, float valueMaximum,
Chris@1647 74 bool notifyOnAdd = true,
Chris@1647 75 Subtype subtype = NORMAL_NOTE) :
Chris@1647 76 m_subtype(subtype),
Chris@1643 77 m_sampleRate(sampleRate),
Chris@1643 78 m_resolution(resolution),
Chris@1643 79 m_valueMinimum(valueMinimum),
Chris@1643 80 m_valueMaximum(valueMaximum),
Chris@1643 81 m_haveExtents(true),
Chris@1643 82 m_valueQuantization(0),
Chris@1643 83 m_units(""),
Chris@1651 84 m_notifier(this,
Chris@1752 85 getId(),
Chris@1651 86 notifyOnAdd ?
Chris@1651 87 DeferredNotifier::NOTIFY_ALWAYS :
Chris@1651 88 DeferredNotifier::NOTIFY_DEFERRED),
Chris@1655 89 m_completion(100) {
Chris@1751 90 PlayParameterRepository::getInstance()->addPlayable
Chris@1751 91 (getId().untyped, this);
Chris@391 92 }
Chris@391 93
Chris@1643 94 virtual ~NoteModel() {
Chris@1751 95 PlayParameterRepository::getInstance()->removePlayable
Chris@1751 96 (getId().untyped);
Chris@147 97 }
Chris@1647 98
Chris@1643 99 QString getTypeName() const override { return tr("Note"); }
Chris@1647 100 Subtype getSubtype() const { return m_subtype; }
Chris@1692 101 bool isSparse() const override { return true; }
Chris@1659 102 bool isOK() const override { return true; }
Chris@1659 103
Chris@1659 104 sv_frame_t getStartFrame() const override {
Chris@1798 105 QMutexLocker locker(&m_mutex);
Chris@1659 106 return m_events.getStartFrame();
Chris@1659 107 }
Chris@1725 108 sv_frame_t getTrueEndFrame() const override {
Chris@1798 109 QMutexLocker locker(&m_mutex);
Chris@1659 110 if (m_events.isEmpty()) return 0;
Chris@1659 111 sv_frame_t e = m_events.getEndFrame();
Chris@1659 112 if (e % m_resolution == 0) return e;
Chris@1659 113 else return (e / m_resolution + 1) * m_resolution;
Chris@1659 114 }
Chris@1643 115
Chris@1643 116 sv_samplerate_t getSampleRate() const override { return m_sampleRate; }
Chris@1644 117 int getResolution() const { return m_resolution; }
Chris@1643 118
Chris@1643 119 bool canPlay() const override { return true; }
Chris@1643 120 QString getDefaultPlayClipId() const override {
Chris@1643 121 return "elecpiano";
Chris@1643 122 }
Chris@1643 123
Chris@1798 124 QString getScaleUnits() const {
Chris@1798 125 QMutexLocker locker(&m_mutex);
Chris@1798 126 return m_units;
Chris@1798 127 }
Chris@1643 128 void setScaleUnits(QString units) {
Chris@1798 129 QMutexLocker locker(&m_mutex);
Chris@1643 130 m_units = units;
Chris@1643 131 UnitDatabase::getInstance()->registerUnit(units);
Chris@1643 132 }
Chris@147 133
Chris@147 134 float getValueQuantization() const { return m_valueQuantization; }
Chris@147 135 void setValueQuantization(float q) { m_valueQuantization = q; }
Chris@147 136
Chris@1643 137 float getValueMinimum() const { return m_valueMinimum; }
Chris@1643 138 float getValueMaximum() const { return m_valueMaximum; }
Chris@1643 139
Chris@1671 140 int getCompletion() const override { return m_completion; }
Chris@345 141
Chris@1643 142 void setCompletion(int completion, bool update = true) {
Chris@391 143
Chris@1798 144 {
Chris@1651 145 if (m_completion == completion) return;
Chris@1651 146 m_completion = completion;
Chris@1643 147 }
Chris@1643 148
Chris@1651 149 if (update) {
Chris@1651 150 m_notifier.makeDeferredNotifications();
Chris@1643 151 }
Chris@1651 152
Chris@1752 153 emit completionChanged(getId());
Chris@1651 154
Chris@1651 155 if (completion == 100) {
Chris@1651 156 // henceforth:
Chris@1651 157 m_notifier.switchMode(DeferredNotifier::NOTIFY_ALWAYS);
Chris@1752 158 emit modelChanged(getId());
Chris@1643 159 }
Chris@391 160 }
Chris@1644 161
Chris@1644 162 /**
Chris@1644 163 * Query methods.
Chris@1644 164 */
Chris@1644 165
Chris@1644 166 int getEventCount() const {
Chris@1644 167 return m_events.count();
Chris@1644 168 }
Chris@1644 169 bool isEmpty() const {
Chris@1644 170 return m_events.isEmpty();
Chris@1644 171 }
Chris@1644 172 bool containsEvent(const Event &e) const {
Chris@1644 173 return m_events.contains(e);
Chris@1644 174 }
Chris@1644 175 EventVector getAllEvents() const {
Chris@1644 176 return m_events.getAllEvents();
Chris@1644 177 }
Chris@1644 178 EventVector getEventsSpanning(sv_frame_t f, sv_frame_t duration) const {
Chris@1644 179 return m_events.getEventsSpanning(f, duration);
Chris@1644 180 }
Chris@1656 181 EventVector getEventsCovering(sv_frame_t f) const {
Chris@1656 182 return m_events.getEventsCovering(f);
Chris@1656 183 }
Chris@1644 184 EventVector getEventsWithin(sv_frame_t f, sv_frame_t duration) const {
Chris@1644 185 return m_events.getEventsWithin(f, duration);
Chris@1644 186 }
Chris@1644 187 EventVector getEventsStartingWithin(sv_frame_t f, sv_frame_t duration) const {
Chris@1644 188 return m_events.getEventsStartingWithin(f, duration);
Chris@1644 189 }
Chris@1656 190 EventVector getEventsStartingAt(sv_frame_t f) const {
Chris@1656 191 return m_events.getEventsStartingAt(f);
Chris@1644 192 }
Chris@1657 193 bool getNearestEventMatching(sv_frame_t startSearchAt,
Chris@1657 194 std::function<bool(Event)> predicate,
Chris@1657 195 EventSeries::Direction direction,
Chris@1657 196 Event &found) const {
Chris@1657 197 return m_events.getNearestEventMatching
Chris@1657 198 (startSearchAt, predicate, direction, found);
Chris@1657 199 }
Chris@1810 200 int getIndexForEvent(const Event &e) {
Chris@1810 201 return m_events.getIndexForEvent(e);
Chris@1810 202 }
Chris@1644 203
Chris@1644 204 /**
Chris@1648 205 * Editing methods.
Chris@1644 206 */
Chris@1648 207 void add(Event e) override {
Chris@1644 208
Chris@1644 209 bool allChange = false;
Chris@1644 210
Chris@1798 211 m_events.add(e);
Chris@1798 212 float v = e.getValue();
Chris@1798 213 if (!ISNAN(v) && !ISINF(v)) {
Chris@1798 214 if (!m_haveExtents || v < m_valueMinimum) {
Chris@1798 215 m_valueMinimum = v; allChange = true;
Chris@1644 216 }
Chris@1798 217 if (!m_haveExtents || v > m_valueMaximum) {
Chris@1798 218 m_valueMaximum = v; allChange = true;
Chris@1798 219 }
Chris@1798 220 m_haveExtents = true;
Chris@1644 221 }
Chris@1644 222
Chris@1651 223 m_notifier.update(e.getFrame(), e.getDuration() + m_resolution);
Chris@1651 224
Chris@1644 225 if (allChange) {
Chris@1752 226 emit modelChanged(getId());
Chris@1644 227 }
Chris@1644 228 }
Chris@1644 229
Chris@1648 230 void remove(Event e) override {
Chris@1798 231 m_events.remove(e);
Chris@1752 232 emit modelChangedWithin(getId(),
Chris@1752 233 e.getFrame(),
Chris@1644 234 e.getFrame() + e.getDuration() + m_resolution);
Chris@147 235 }
Chris@147 236
Chris@424 237 /**
Chris@424 238 * TabularModel methods.
Chris@424 239 */
Chris@1643 240
Chris@1643 241 int getRowCount() const override {
Chris@1643 242 return m_events.count();
Chris@1643 243 }
Chris@424 244
Chris@1643 245 int getColumnCount() const override {
Chris@424 246 return 6;
Chris@424 247 }
Chris@424 248
Chris@1643 249 bool isColumnTimeValue(int column) const override {
Chris@1643 250 // NB duration is not a "time value" -- that's for columns
Chris@1643 251 // whose sort ordering is exactly that of the frame time
Chris@1643 252 return (column < 2);
Chris@1643 253 }
Chris@1643 254
Chris@1643 255 sv_frame_t getFrameForRow(int row) const override {
Chris@1643 256 if (row < 0 || row >= m_events.count()) {
Chris@1643 257 return 0;
Chris@1643 258 }
Chris@1643 259 Event e = m_events.getEventByIndex(row);
Chris@1643 260 return e.getFrame();
Chris@1643 261 }
Chris@1643 262
Chris@1643 263 int getRowForFrame(sv_frame_t frame) const override {
Chris@1643 264 return m_events.getIndexForEvent(Event(frame));
Chris@1643 265 }
Chris@1643 266
Chris@1643 267 QString getHeading(int column) const override {
Chris@424 268 switch (column) {
Chris@424 269 case 0: return tr("Time");
Chris@424 270 case 1: return tr("Frame");
Chris@424 271 case 2: return tr("Pitch");
Chris@424 272 case 3: return tr("Duration");
Chris@424 273 case 4: return tr("Level");
Chris@424 274 case 5: return tr("Label");
Chris@424 275 default: return tr("Unknown");
Chris@424 276 }
Chris@424 277 }
Chris@424 278
Chris@1643 279 QVariant getData(int row, int column, int role) const override {
Chris@1643 280
Chris@1643 281 if (row < 0 || row >= m_events.count()) {
Chris@1643 282 return QVariant();
Chris@425 283 }
Chris@425 284
Chris@1643 285 Event e = m_events.getEventByIndex(row);
Chris@424 286
Chris@424 287 switch (column) {
Chris@1643 288 case 0: return adaptFrameForRole(e.getFrame(), getSampleRate(), role);
Chris@1643 289 case 1: return int(e.getFrame());
Chris@1643 290 case 2: return adaptValueForRole(e.getValue(), getScaleUnits(), role);
Chris@1643 291 case 3: return int(e.getDuration());
Chris@1643 292 case 4: return e.getLevel();
Chris@1643 293 case 5: return e.getLabel();
Chris@424 294 default: return QVariant();
Chris@424 295 }
Chris@424 296 }
Chris@424 297
Chris@1649 298 Command *getSetDataCommand(int row, int column, const QVariant &value, int role) override {
Chris@1649 299
Chris@1643 300 if (row < 0 || row >= m_events.count()) return nullptr;
Chris@1643 301 if (role != Qt::EditRole) return nullptr;
Chris@1643 302
Chris@1643 303 Event e0 = m_events.getEventByIndex(row);
Chris@1643 304 Event e1;
Chris@1643 305
Chris@1643 306 switch (column) {
Chris@1643 307 case 0: e1 = e0.withFrame(sv_frame_t(round(value.toDouble() *
Chris@1643 308 getSampleRate()))); break;
Chris@1643 309 case 1: e1 = e0.withFrame(value.toInt()); break;
Chris@1643 310 case 2: e1 = e0.withValue(float(value.toDouble())); break;
Chris@1643 311 case 3: e1 = e0.withDuration(value.toInt()); break;
Chris@1643 312 case 4: e1 = e0.withLevel(float(value.toDouble())); break;
Chris@1643 313 case 5: e1 = e0.withLabel(value.toString()); break;
Chris@425 314 }
Chris@425 315
Chris@1742 316 auto command = new ChangeEventsCommand(getId().untyped, tr("Edit Data"));
Chris@1644 317 command->remove(e0);
Chris@1644 318 command->add(e1);
Chris@1644 319 return command->finish();
Chris@424 320 }
Chris@424 321
Chris@1580 322 SortType getSortType(int column) const override
Chris@424 323 {
Chris@424 324 if (column == 5) return SortAlphabetical;
Chris@424 325 return SortNumeric;
Chris@424 326 }
Chris@424 327
Chris@1804 328 bool isEditable() const override { return true; }
Chris@1804 329
Chris@1804 330 Command *getInsertRowCommand(int row) override {
Chris@1804 331 if (row < 0 || row >= m_events.count()) return nullptr;
Chris@1804 332 auto command = new ChangeEventsCommand(getId().untyped,
Chris@1804 333 tr("Add Note"));
Chris@1804 334 Event e = m_events.getEventByIndex(row);
Chris@1804 335 command->add(e);
Chris@1804 336 return command->finish();
Chris@1804 337 }
Chris@1804 338
Chris@1804 339 Command *getRemoveRowCommand(int row) override {
Chris@1804 340 if (row < 0 || row >= m_events.count()) return nullptr;
Chris@1804 341 auto command = new ChangeEventsCommand(getId().untyped,
Chris@1804 342 tr("Delete Note"));
Chris@1804 343 Event e = m_events.getEventByIndex(row);
Chris@1804 344 command->remove(e);
Chris@1804 345 return command->finish();
Chris@1804 346 }
Chris@1804 347
Chris@852 348 /**
Chris@852 349 * NoteExportable methods.
Chris@852 350 */
Chris@852 351
Chris@1580 352 NoteList getNotes() const override {
Chris@1643 353 return getNotesStartingWithin(getStartFrame(),
Chris@1643 354 getEndFrame() - getStartFrame());
Chris@852 355 }
Chris@852 356
Chris@1643 357 NoteList getNotesActiveAt(sv_frame_t frame) const override {
Chris@1643 358
Chris@852 359 NoteList notes;
Chris@1643 360 EventVector ee = m_events.getEventsCovering(frame);
Chris@1643 361 for (const auto &e: ee) {
Chris@1643 362 notes.push_back(e.toNoteData(getSampleRate(),
Chris@1643 363 getScaleUnits() != "Hz"));
Chris@1643 364 }
Chris@1643 365 return notes;
Chris@1643 366 }
Chris@1643 367
Chris@1643 368 NoteList getNotesStartingWithin(sv_frame_t startFrame,
Chris@1643 369 sv_frame_t duration) const override {
Chris@852 370
Chris@1643 371 NoteList notes;
Chris@1643 372 EventVector ee = m_events.getEventsStartingWithin(startFrame, duration);
Chris@1643 373 for (const auto &e: ee) {
Chris@1643 374 notes.push_back(e.toNoteData(getSampleRate(),
Chris@1643 375 getScaleUnits() != "Hz"));
Chris@852 376 }
Chris@852 377 return notes;
Chris@852 378 }
Chris@852 379
Chris@1644 380 /**
Chris@1644 381 * XmlExportable methods.
Chris@1644 382 */
Chris@1644 383
Chris@1644 384 void toXml(QTextStream &out,
Chris@1644 385 QString indent = "",
Chris@1644 386 QString extraAttributes = "") const override {
Chris@1644 387
Chris@1644 388 //!!! what is valueQuantization used for?
Chris@1644 389
Chris@1644 390 Model::toXml
Chris@1644 391 (out,
Chris@1644 392 indent,
Chris@1644 393 QString("type=\"sparse\" dimensions=\"3\" resolution=\"%1\" "
Chris@1647 394 "notifyOnAdd=\"%2\" dataset=\"%3\" subtype=\"%4\" "
Chris@1647 395 "valueQuantization=\"%5\" minimum=\"%6\" maximum=\"%7\" "
Chris@1647 396 "units=\"%8\" %9")
Chris@1644 397 .arg(m_resolution)
Chris@1651 398 .arg("true") // always true after model reaches 100% -
Chris@1651 399 // subsequent events are always notified
Chris@1677 400 .arg(m_events.getExportId())
Chris@1647 401 .arg(m_subtype == FLEXI_NOTE ? "flexinote" : "note")
Chris@1644 402 .arg(m_valueQuantization)
Chris@1644 403 .arg(m_valueMinimum)
Chris@1644 404 .arg(m_valueMaximum)
Chris@1651 405 .arg(encodeEntities(m_units))
Chris@1644 406 .arg(extraAttributes));
Chris@1647 407
Chris@1644 408 m_events.toXml(out, indent, QString("dimensions=\"3\""));
Chris@1644 409 }
Chris@1644 410
Chris@1833 411 QVector<QString>
Chris@1833 412 getStringExportHeaders(DataExportOptions options) const override {
Chris@1833 413 return m_events.getStringExportHeaders(options, {});
Chris@1815 414 }
Chris@1815 415
Chris@1833 416 QVector<QVector<QString>>
Chris@1833 417 toStringExportRows(DataExportOptions options,
Chris@1833 418 sv_frame_t startFrame,
Chris@1833 419 sv_frame_t duration) const override {
Chris@1833 420 return m_events.toStringExportRows
Chris@1833 421 (options,
Chris@1679 422 startFrame,
Chris@1679 423 duration,
Chris@1679 424 m_sampleRate,
Chris@1679 425 m_resolution,
Chris@1679 426 Event().withValue(0.f).withDuration(0.f).withLevel(0.f));
Chris@1679 427 }
Chris@1679 428
Chris@147 429 protected:
Chris@1647 430 Subtype m_subtype;
Chris@1643 431 sv_samplerate_t m_sampleRate;
Chris@1643 432 int m_resolution;
Chris@1643 433
Chris@1798 434 std::atomic<float> m_valueMinimum;
Chris@1798 435 std::atomic<float> m_valueMaximum;
Chris@1798 436 std::atomic<bool> m_haveExtents;
Chris@147 437 float m_valueQuantization;
Chris@1643 438 QString m_units;
Chris@1651 439 DeferredNotifier m_notifier;
Chris@1798 440 std::atomic<int> m_completion;
Chris@1643 441
Chris@1643 442 EventSeries m_events;
Chris@147 443 };
Chris@147 444
Chris@147 445 #endif