annotate data/model/NoteModel.h @ 1647:29a20719796e single-point

Rework NoteModel commands (not entirely successfully); remove FlexiNoteModel as it has always been almost entirely identical to NoteModel (unlike its layer counterpart)
author Chris Cannam
date Thu, 14 Mar 2019 15:31:59 +0000
parents b429750e64a8
children 86bbccb79c9b
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 This file copyright 2006 Chris Cannam.
Chris@147 8
Chris@147 9 This program is free software; you can redistribute it and/or
Chris@147 10 modify it under the terms of the GNU General Public License as
Chris@147 11 published by the Free Software Foundation; either version 2 of the
Chris@147 12 License, or (at your option) any later version. See the file
Chris@147 13 COPYING included with this distribution for more information.
Chris@147 14 */
Chris@147 15
Chris@1495 16 #ifndef SV_NOTE_MODEL_H
Chris@1495 17 #define SV_NOTE_MODEL_H
Chris@147 18
Chris@1643 19 #include "Model.h"
Chris@1643 20 #include "TabularModel.h"
Chris@1643 21 #include "base/UnitDatabase.h"
Chris@1643 22 #include "base/EventSeries.h"
Chris@1615 23 #include "base/NoteData.h"
Chris@1643 24 #include "base/NoteExportable.h"
Chris@391 25 #include "base/RealTime.h"
Chris@150 26 #include "base/PlayParameterRepository.h"
Chris@852 27 #include "base/Pitch.h"
Chris@1643 28 #include "system/System.h"
Chris@147 29
Chris@1643 30 #include <QMutex>
Chris@1643 31 #include <QMutexLocker>
Chris@441 32
Chris@1643 33 class NoteModel : public Model,
Chris@1643 34 public TabularModel,
Chris@1643 35 public NoteExportable
Chris@147 36 {
Chris@423 37 Q_OBJECT
Chris@423 38
Chris@147 39 public:
Chris@1647 40 enum Subtype {
Chris@1647 41 NORMAL_NOTE,
Chris@1647 42 FLEXI_NOTE
Chris@1647 43 };
Chris@1647 44
Chris@1644 45 NoteModel(sv_samplerate_t sampleRate,
Chris@1644 46 int resolution,
Chris@1647 47 bool notifyOnAdd = true,
Chris@1647 48 Subtype subtype = NORMAL_NOTE) :
Chris@1647 49 m_subtype(subtype),
Chris@1643 50 m_sampleRate(sampleRate),
Chris@1643 51 m_resolution(resolution),
Chris@1643 52 m_valueMinimum(0.f),
Chris@1643 53 m_valueMaximum(0.f),
Chris@1643 54 m_haveExtents(false),
Chris@1643 55 m_valueQuantization(0),
Chris@1643 56 m_units(""),
Chris@1643 57 m_extendTo(0),
Chris@1643 58 m_notifyOnAdd(notifyOnAdd),
Chris@1643 59 m_sinceLastNotifyMin(-1),
Chris@1643 60 m_sinceLastNotifyMax(-1),
Chris@1643 61 m_completion(0) {
Chris@1647 62 if (subtype == FLEXI_NOTE) {
Chris@1647 63 m_valueMinimum = 33.f;
Chris@1647 64 m_valueMaximum = 88.f;
Chris@1647 65 }
Chris@1429 66 PlayParameterRepository::getInstance()->addPlayable(this);
Chris@256 67 }
Chris@256 68
Chris@1040 69 NoteModel(sv_samplerate_t sampleRate, int resolution,
Chris@1429 70 float valueMinimum, float valueMaximum,
Chris@1647 71 bool notifyOnAdd = true,
Chris@1647 72 Subtype subtype = NORMAL_NOTE) :
Chris@1647 73 m_subtype(subtype),
Chris@1643 74 m_sampleRate(sampleRate),
Chris@1643 75 m_resolution(resolution),
Chris@1643 76 m_valueMinimum(valueMinimum),
Chris@1643 77 m_valueMaximum(valueMaximum),
Chris@1643 78 m_haveExtents(true),
Chris@1643 79 m_valueQuantization(0),
Chris@1643 80 m_units(""),
Chris@1643 81 m_extendTo(0),
Chris@1643 82 m_notifyOnAdd(notifyOnAdd),
Chris@1643 83 m_sinceLastNotifyMin(-1),
Chris@1643 84 m_sinceLastNotifyMax(-1),
Chris@1643 85 m_completion(0) {
Chris@1429 86 PlayParameterRepository::getInstance()->addPlayable(this);
Chris@391 87 }
Chris@391 88
Chris@1643 89 virtual ~NoteModel() {
Chris@391 90 PlayParameterRepository::getInstance()->removePlayable(this);
Chris@147 91 }
Chris@1647 92
Chris@1643 93 QString getTypeName() const override { return tr("Note"); }
Chris@1647 94 Subtype getSubtype() const { return m_subtype; }
Chris@1643 95
Chris@1643 96 bool isOK() const override { return true; }
Chris@1643 97 sv_frame_t getStartFrame() const override { return m_events.getStartFrame(); }
Chris@1643 98 sv_frame_t getEndFrame() const override { return m_events.getEndFrame(); }
Chris@1643 99 sv_samplerate_t getSampleRate() const override { return m_sampleRate; }
Chris@1644 100 int getResolution() const { return m_resolution; }
Chris@1643 101
Chris@1643 102 bool canPlay() const override { return true; }
Chris@1643 103 QString getDefaultPlayClipId() const override {
Chris@1643 104 return "elecpiano";
Chris@1643 105 }
Chris@1643 106
Chris@1643 107 QString getScaleUnits() const { return m_units; }
Chris@1643 108 void setScaleUnits(QString units) {
Chris@1643 109 m_units = units;
Chris@1643 110 UnitDatabase::getInstance()->registerUnit(units);
Chris@1643 111 }
Chris@147 112
Chris@147 113 float getValueQuantization() const { return m_valueQuantization; }
Chris@147 114 void setValueQuantization(float q) { m_valueQuantization = q; }
Chris@147 115
Chris@1643 116 float getValueMinimum() const { return m_valueMinimum; }
Chris@1643 117 float getValueMaximum() const { return m_valueMaximum; }
Chris@1643 118
Chris@1643 119 int getCompletion() const { return m_completion; }
Chris@345 120
Chris@1643 121 void setCompletion(int completion, bool update = true) {
Chris@391 122
Chris@1643 123 bool emitCompletionChanged = true;
Chris@1643 124 bool emitGeneralModelChanged = false;
Chris@1643 125 bool emitRegionChanged = false;
Chris@1643 126
Chris@1643 127 {
Chris@1643 128 QMutexLocker locker(&m_mutex);
Chris@1643 129
Chris@1643 130 if (m_completion != completion) {
Chris@1643 131 m_completion = completion;
Chris@1643 132
Chris@1643 133 if (completion == 100) {
Chris@1643 134
Chris@1643 135 if (m_notifyOnAdd) {
Chris@1643 136 emitCompletionChanged = false;
Chris@1643 137 }
Chris@1643 138
Chris@1643 139 m_notifyOnAdd = true; // henceforth
Chris@1643 140 emitGeneralModelChanged = true;
Chris@1643 141
Chris@1643 142 } else if (!m_notifyOnAdd) {
Chris@1643 143
Chris@1643 144 if (update &&
Chris@1643 145 m_sinceLastNotifyMin >= 0 &&
Chris@1643 146 m_sinceLastNotifyMax >= 0) {
Chris@1643 147 emitRegionChanged = true;
Chris@1643 148 }
Chris@1643 149 }
Chris@1643 150 }
Chris@1643 151 }
Chris@1643 152
Chris@1643 153 if (emitCompletionChanged) {
Chris@1643 154 emit completionChanged();
Chris@1643 155 }
Chris@1643 156 if (emitGeneralModelChanged) {
Chris@1643 157 emit modelChanged();
Chris@1643 158 }
Chris@1643 159 if (emitRegionChanged) {
Chris@1643 160 emit modelChangedWithin(m_sinceLastNotifyMin, m_sinceLastNotifyMax);
Chris@1643 161 m_sinceLastNotifyMin = m_sinceLastNotifyMax = -1;
Chris@1643 162 }
Chris@391 163 }
Chris@1644 164
Chris@1644 165 /**
Chris@1644 166 * Query methods.
Chris@1644 167 */
Chris@1644 168
Chris@1644 169 int getEventCount() const {
Chris@1644 170 return m_events.count();
Chris@1644 171 }
Chris@1644 172 bool isEmpty() const {
Chris@1644 173 return m_events.isEmpty();
Chris@1644 174 }
Chris@1644 175 bool containsEvent(const Event &e) const {
Chris@1644 176 return m_events.contains(e);
Chris@1644 177 }
Chris@1644 178 EventVector getAllEvents() const {
Chris@1644 179 return m_events.getAllEvents();
Chris@1644 180 }
Chris@1644 181 EventVector getEventsSpanning(sv_frame_t f, sv_frame_t duration) const {
Chris@1644 182 return m_events.getEventsSpanning(f, duration);
Chris@1644 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@1644 190 EventVector getEventsCovering(sv_frame_t f) const {
Chris@1644 191 return m_events.getEventsCovering(f);
Chris@1644 192 }
Chris@1644 193
Chris@1644 194 /**
Chris@1644 195 * Editing commands and methods.
Chris@1644 196 */
Chris@1643 197
Chris@1644 198 class EditCommand : public Command
Chris@1644 199 {
Chris@1644 200 public:
Chris@1644 201 //!!! borrowed ptr
Chris@1644 202 EditCommand(NoteModel *model, QString name) :
Chris@1647 203 m_model(model), m_executed(false), m_name(name) { }
Chris@391 204
Chris@1644 205 QString getName() const override {
Chris@1644 206 return m_name;
Chris@1644 207 }
Chris@1644 208
Chris@1646 209 void setName(QString name) {
Chris@1646 210 m_name = name;
Chris@1646 211 }
Chris@1646 212
Chris@1644 213 void add(Event e) {
Chris@1647 214 m_adding.insert(e);
Chris@1647 215 m_model->add(e);
Chris@1647 216 m_executed = true;
Chris@1644 217 }
Chris@1644 218
Chris@1644 219 void remove(Event e) {
Chris@1647 220 m_removing.insert(e);
Chris@1647 221 m_model->remove(e);
Chris@1647 222 m_executed = true;
Chris@1644 223 }
Chris@1643 224
Chris@1644 225 void execute() override {
Chris@1647 226 if (m_executed) return;
Chris@1647 227 for (const Event &e: m_adding) m_model->add(e);
Chris@1647 228 for (const Event &e: m_removing) m_model->remove(e);
Chris@1647 229 m_executed = true;
Chris@1644 230 }
Chris@1643 231
Chris@1644 232 void unexecute() override {
Chris@1647 233 for (const Event &e: m_removing) m_model->add(e);
Chris@1647 234 for (const Event &e: m_adding) m_model->remove(e);
Chris@1647 235 m_executed = false;
Chris@1644 236 }
Chris@1644 237
Chris@1644 238 EditCommand *finish() {
Chris@1647 239 if (m_adding.empty() && m_removing.empty()) {
Chris@1644 240 delete this;
Chris@1644 241 return nullptr;
Chris@1644 242 } else {
Chris@1644 243 return this;
Chris@1644 244 }
Chris@1644 245 }
Chris@1644 246
Chris@1644 247 private:
Chris@1644 248 NoteModel *m_model;
Chris@1647 249 bool m_executed;
Chris@1647 250 std::set<Event> m_adding;
Chris@1647 251 std::set<Event> m_removing;
Chris@1644 252 QString m_name;
Chris@1644 253 };
Chris@1644 254
Chris@1644 255 void add(Event e) {
Chris@1644 256
Chris@1644 257 bool allChange = false;
Chris@1644 258
Chris@1644 259 {
Chris@1644 260 QMutexLocker locker(&m_mutex);
Chris@1644 261 m_events.add(e);
Chris@1644 262 //!!!??? if (point.getLabel() != "") m_hasTextLabels = true;
Chris@1644 263
Chris@1644 264 float v = e.getValue();
Chris@1644 265 if (!ISNAN(v) && !ISINF(v)) {
Chris@1644 266 if (!m_haveExtents || v < m_valueMinimum) {
Chris@1644 267 m_valueMinimum = v; allChange = true;
Chris@1644 268 }
Chris@1644 269 if (!m_haveExtents || v > m_valueMaximum) {
Chris@1644 270 m_valueMaximum = v; allChange = true;
Chris@1644 271 }
Chris@1644 272 m_haveExtents = true;
Chris@1644 273 }
Chris@1644 274
Chris@1644 275 sv_frame_t f = e.getFrame();
Chris@1644 276
Chris@1644 277 if (!m_notifyOnAdd) {
Chris@1644 278 if (m_sinceLastNotifyMin == -1 || f < m_sinceLastNotifyMin) {
Chris@1644 279 m_sinceLastNotifyMin = f;
Chris@1644 280 }
Chris@1644 281 if (m_sinceLastNotifyMax == -1 || f > m_sinceLastNotifyMax) {
Chris@1644 282 m_sinceLastNotifyMax = f;
Chris@1644 283 }
Chris@1644 284 }
Chris@1644 285 }
Chris@1644 286
Chris@1644 287 if (m_notifyOnAdd) {
Chris@1644 288 emit modelChangedWithin(e.getFrame(),
Chris@1644 289 e.getFrame() + e.getDuration() + m_resolution);
Chris@1644 290 }
Chris@1644 291 if (allChange) {
Chris@1644 292 emit modelChanged();
Chris@1644 293 }
Chris@1644 294 }
Chris@1644 295
Chris@1644 296 void remove(Event e) {
Chris@1644 297 {
Chris@1644 298 QMutexLocker locker(&m_mutex);
Chris@1644 299 m_events.remove(e);
Chris@1644 300 }
Chris@1644 301 emit modelChangedWithin(e.getFrame(),
Chris@1644 302 e.getFrame() + e.getDuration() + m_resolution);
Chris@147 303 }
Chris@147 304
Chris@424 305 /**
Chris@424 306 * TabularModel methods.
Chris@424 307 */
Chris@1643 308
Chris@1643 309 int getRowCount() const override {
Chris@1643 310 return m_events.count();
Chris@1643 311 }
Chris@424 312
Chris@1643 313 int getColumnCount() const override {
Chris@424 314 return 6;
Chris@424 315 }
Chris@424 316
Chris@1643 317 bool isColumnTimeValue(int column) const override {
Chris@1643 318 // NB duration is not a "time value" -- that's for columns
Chris@1643 319 // whose sort ordering is exactly that of the frame time
Chris@1643 320 return (column < 2);
Chris@1643 321 }
Chris@1643 322
Chris@1643 323 sv_frame_t getFrameForRow(int row) const override {
Chris@1643 324 if (row < 0 || row >= m_events.count()) {
Chris@1643 325 return 0;
Chris@1643 326 }
Chris@1643 327 Event e = m_events.getEventByIndex(row);
Chris@1643 328 return e.getFrame();
Chris@1643 329 }
Chris@1643 330
Chris@1643 331 int getRowForFrame(sv_frame_t frame) const override {
Chris@1643 332 return m_events.getIndexForEvent(Event(frame));
Chris@1643 333 }
Chris@1643 334
Chris@1643 335 QString getHeading(int column) const override {
Chris@424 336 switch (column) {
Chris@424 337 case 0: return tr("Time");
Chris@424 338 case 1: return tr("Frame");
Chris@424 339 case 2: return tr("Pitch");
Chris@424 340 case 3: return tr("Duration");
Chris@424 341 case 4: return tr("Level");
Chris@424 342 case 5: return tr("Label");
Chris@424 343 default: return tr("Unknown");
Chris@424 344 }
Chris@424 345 }
Chris@424 346
Chris@1643 347 QVariant getData(int row, int column, int role) const override {
Chris@1643 348
Chris@1643 349 if (row < 0 || row >= m_events.count()) {
Chris@1643 350 return QVariant();
Chris@425 351 }
Chris@425 352
Chris@1643 353 Event e = m_events.getEventByIndex(row);
Chris@424 354
Chris@424 355 switch (column) {
Chris@1643 356 case 0: return adaptFrameForRole(e.getFrame(), getSampleRate(), role);
Chris@1643 357 case 1: return int(e.getFrame());
Chris@1643 358 case 2: return adaptValueForRole(e.getValue(), getScaleUnits(), role);
Chris@1643 359 case 3: return int(e.getDuration());
Chris@1643 360 case 4: return e.getLevel();
Chris@1643 361 case 5: return e.getLabel();
Chris@424 362 default: return QVariant();
Chris@424 363 }
Chris@424 364 }
Chris@424 365
Chris@1580 366 Command *getSetDataCommand(int row, int column, const QVariant &value, int role) override
Chris@424 367 {
Chris@1643 368 if (row < 0 || row >= m_events.count()) return nullptr;
Chris@1643 369 if (role != Qt::EditRole) return nullptr;
Chris@1643 370
Chris@1643 371 Event e0 = m_events.getEventByIndex(row);
Chris@1643 372 Event e1;
Chris@1643 373
Chris@1643 374 switch (column) {
Chris@1643 375 case 0: e1 = e0.withFrame(sv_frame_t(round(value.toDouble() *
Chris@1643 376 getSampleRate()))); break;
Chris@1643 377 case 1: e1 = e0.withFrame(value.toInt()); break;
Chris@1643 378 case 2: e1 = e0.withValue(float(value.toDouble())); break;
Chris@1643 379 case 3: e1 = e0.withDuration(value.toInt()); break;
Chris@1643 380 case 4: e1 = e0.withLevel(float(value.toDouble())); break;
Chris@1643 381 case 5: e1 = e0.withLabel(value.toString()); break;
Chris@425 382 }
Chris@425 383
Chris@424 384 EditCommand *command = new EditCommand(this, tr("Edit Data"));
Chris@1644 385 command->remove(e0);
Chris@1644 386 command->add(e1);
Chris@1644 387 return command->finish();
Chris@424 388 }
Chris@424 389
Chris@1580 390 SortType getSortType(int column) const override
Chris@424 391 {
Chris@424 392 if (column == 5) return SortAlphabetical;
Chris@424 393 return SortNumeric;
Chris@424 394 }
Chris@424 395
Chris@852 396 /**
Chris@852 397 * NoteExportable methods.
Chris@852 398 */
Chris@852 399
Chris@1580 400 NoteList getNotes() const override {
Chris@1643 401 return getNotesStartingWithin(getStartFrame(),
Chris@1643 402 getEndFrame() - getStartFrame());
Chris@852 403 }
Chris@852 404
Chris@1643 405 NoteList getNotesActiveAt(sv_frame_t frame) const override {
Chris@1643 406
Chris@852 407 NoteList notes;
Chris@1643 408 EventVector ee = m_events.getEventsCovering(frame);
Chris@1643 409 for (const auto &e: ee) {
Chris@1643 410 notes.push_back(e.toNoteData(getSampleRate(),
Chris@1643 411 getScaleUnits() != "Hz"));
Chris@1643 412 }
Chris@1643 413 return notes;
Chris@1643 414 }
Chris@1643 415
Chris@1643 416 NoteList getNotesStartingWithin(sv_frame_t startFrame,
Chris@1643 417 sv_frame_t duration) const override {
Chris@852 418
Chris@1643 419 NoteList notes;
Chris@1643 420 EventVector ee = m_events.getEventsStartingWithin(startFrame, duration);
Chris@1643 421 for (const auto &e: ee) {
Chris@1643 422 notes.push_back(e.toNoteData(getSampleRate(),
Chris@1643 423 getScaleUnits() != "Hz"));
Chris@852 424 }
Chris@852 425 return notes;
Chris@852 426 }
Chris@852 427
Chris@1644 428 /**
Chris@1644 429 * XmlExportable methods.
Chris@1644 430 */
Chris@1644 431
Chris@1644 432 void toXml(QTextStream &out,
Chris@1644 433 QString indent = "",
Chris@1644 434 QString extraAttributes = "") const override {
Chris@1644 435
Chris@1644 436 //!!! what is valueQuantization used for?
Chris@1644 437
Chris@1644 438 Model::toXml
Chris@1644 439 (out,
Chris@1644 440 indent,
Chris@1644 441 QString("type=\"sparse\" dimensions=\"3\" resolution=\"%1\" "
Chris@1647 442 "notifyOnAdd=\"%2\" dataset=\"%3\" subtype=\"%4\" "
Chris@1647 443 "valueQuantization=\"%5\" minimum=\"%6\" maximum=\"%7\" "
Chris@1647 444 "units=\"%8\" %9")
Chris@1644 445 .arg(m_resolution)
Chris@1644 446 .arg(m_notifyOnAdd ? "true" : "false")
Chris@1644 447 .arg(getObjectExportId(&m_events))
Chris@1647 448 .arg(m_subtype == FLEXI_NOTE ? "flexinote" : "note")
Chris@1644 449 .arg(m_valueQuantization)
Chris@1644 450 .arg(m_valueMinimum)
Chris@1644 451 .arg(m_valueMaximum)
Chris@1644 452 .arg(m_units)
Chris@1644 453 .arg(extraAttributes));
Chris@1647 454
Chris@1644 455 m_events.toXml(out, indent, QString("dimensions=\"3\""));
Chris@1644 456 }
Chris@1644 457
Chris@147 458 protected:
Chris@1647 459 Subtype m_subtype;
Chris@1643 460 sv_samplerate_t m_sampleRate;
Chris@1643 461 int m_resolution;
Chris@1643 462
Chris@1643 463 float m_valueMinimum;
Chris@1643 464 float m_valueMaximum;
Chris@1643 465 bool m_haveExtents;
Chris@147 466 float m_valueQuantization;
Chris@1643 467 QString m_units;
Chris@1643 468
Chris@1643 469 sv_frame_t m_extendTo;
Chris@1643 470
Chris@1643 471 bool m_notifyOnAdd;
Chris@1643 472 sv_frame_t m_sinceLastNotifyMin;
Chris@1643 473 sv_frame_t m_sinceLastNotifyMax;
Chris@1643 474
Chris@1643 475 EventSeries m_events;
Chris@1643 476
Chris@1643 477 int m_completion;
Chris@1643 478
Chris@1643 479 mutable QMutex m_mutex;
Chris@1643 480
Chris@1643 481 //!!! do we have general docs for ownership and synchronisation of models?
Chris@1643 482 // this might be a good opportunity to stop using bare pointers to them
Chris@147 483 };
Chris@147 484
Chris@147 485 #endif