annotate data/model/NoteModel.h @ 1833:21c792334c2e sensible-delimited-data-strings

Rewrite all the DelimitedDataString stuff so as to return vectors of individual cell strings rather than having the classes add the delimiters themselves. Rename accordingly to names based on StringExport. Take advantage of this in the CSV writer code so as to properly quote cells that contain delimiter characters.
author Chris Cannam
date Fri, 03 Apr 2020 17:11:05 +0100
parents c546429d4c2f
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