annotate data/model/NoteModel.h @ 1643:7a23dfe65d66 single-point

Update NoteModel to use EventSeries. This is incomplete and won't provide enough to update svgui for it yet; must also refactor to avoid duplication of nasty APIs when updating the rest of the models
author Chris Cannam
date Wed, 13 Mar 2019 14:50:10 +0000
parents 24dc8cb42755
children 513192aa9b03
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@1040 40 NoteModel(sv_samplerate_t sampleRate, int resolution,
Chris@1429 41 bool notifyOnAdd = true) :
Chris@1643 42 m_sampleRate(sampleRate),
Chris@1643 43 m_resolution(resolution),
Chris@1643 44 m_valueMinimum(0.f),
Chris@1643 45 m_valueMaximum(0.f),
Chris@1643 46 m_haveExtents(false),
Chris@1643 47 m_valueQuantization(0),
Chris@1643 48 m_units(""),
Chris@1643 49 m_extendTo(0),
Chris@1643 50 m_notifyOnAdd(notifyOnAdd),
Chris@1643 51 m_sinceLastNotifyMin(-1),
Chris@1643 52 m_sinceLastNotifyMax(-1),
Chris@1643 53 m_completion(0) {
Chris@1429 54 PlayParameterRepository::getInstance()->addPlayable(this);
Chris@256 55 }
Chris@256 56
Chris@1040 57 NoteModel(sv_samplerate_t sampleRate, int resolution,
Chris@1429 58 float valueMinimum, float valueMaximum,
Chris@1429 59 bool notifyOnAdd = true) :
Chris@1643 60 m_sampleRate(sampleRate),
Chris@1643 61 m_resolution(resolution),
Chris@1643 62 m_valueMinimum(valueMinimum),
Chris@1643 63 m_valueMaximum(valueMaximum),
Chris@1643 64 m_haveExtents(true),
Chris@1643 65 m_valueQuantization(0),
Chris@1643 66 m_units(""),
Chris@1643 67 m_extendTo(0),
Chris@1643 68 m_notifyOnAdd(notifyOnAdd),
Chris@1643 69 m_sinceLastNotifyMin(-1),
Chris@1643 70 m_sinceLastNotifyMax(-1),
Chris@1643 71 m_completion(0) {
Chris@1429 72 PlayParameterRepository::getInstance()->addPlayable(this);
Chris@391 73 }
Chris@391 74
Chris@1643 75 virtual ~NoteModel() {
Chris@391 76 PlayParameterRepository::getInstance()->removePlayable(this);
Chris@147 77 }
Chris@1643 78
Chris@1643 79 QString getTypeName() const override { return tr("Note"); }
Chris@1643 80
Chris@1643 81 bool isOK() const override { return true; }
Chris@1643 82 sv_frame_t getStartFrame() const override { return m_events.getStartFrame(); }
Chris@1643 83 sv_frame_t getEndFrame() const override { return m_events.getEndFrame(); }
Chris@1643 84 sv_samplerate_t getSampleRate() const override { return m_sampleRate; }
Chris@1643 85
Chris@1643 86 bool canPlay() const override { return true; }
Chris@1643 87 QString getDefaultPlayClipId() const override {
Chris@1643 88 return "elecpiano";
Chris@1643 89 }
Chris@1643 90
Chris@1643 91 QString getScaleUnits() const { return m_units; }
Chris@1643 92 void setScaleUnits(QString units) {
Chris@1643 93 m_units = units;
Chris@1643 94 UnitDatabase::getInstance()->registerUnit(units);
Chris@1643 95 }
Chris@147 96
Chris@147 97 float getValueQuantization() const { return m_valueQuantization; }
Chris@147 98 void setValueQuantization(float q) { m_valueQuantization = q; }
Chris@147 99
Chris@1643 100 float getValueMinimum() const { return m_valueMinimum; }
Chris@1643 101 float getValueMaximum() const { return m_valueMaximum; }
Chris@1643 102
Chris@1643 103 int getCompletion() const { return m_completion; }
Chris@345 104
Chris@1643 105 void setCompletion(int completion, bool update = true) {
Chris@391 106
Chris@1643 107 bool emitCompletionChanged = true;
Chris@1643 108 bool emitGeneralModelChanged = false;
Chris@1643 109 bool emitRegionChanged = false;
Chris@1643 110
Chris@1643 111 {
Chris@1643 112 QMutexLocker locker(&m_mutex);
Chris@1643 113
Chris@1643 114 if (m_completion != completion) {
Chris@1643 115 m_completion = completion;
Chris@1643 116
Chris@1643 117 if (completion == 100) {
Chris@1643 118
Chris@1643 119 if (m_notifyOnAdd) {
Chris@1643 120 emitCompletionChanged = false;
Chris@1643 121 }
Chris@1643 122
Chris@1643 123 m_notifyOnAdd = true; // henceforth
Chris@1643 124 emitGeneralModelChanged = true;
Chris@1643 125
Chris@1643 126 } else if (!m_notifyOnAdd) {
Chris@1643 127
Chris@1643 128 if (update &&
Chris@1643 129 m_sinceLastNotifyMin >= 0 &&
Chris@1643 130 m_sinceLastNotifyMax >= 0) {
Chris@1643 131 emitRegionChanged = true;
Chris@1643 132 }
Chris@1643 133 }
Chris@1643 134 }
Chris@1643 135 }
Chris@1643 136
Chris@1643 137 if (emitCompletionChanged) {
Chris@1643 138 emit completionChanged();
Chris@1643 139 }
Chris@1643 140 if (emitGeneralModelChanged) {
Chris@1643 141 emit modelChanged();
Chris@1643 142 }
Chris@1643 143 if (emitRegionChanged) {
Chris@1643 144 emit modelChangedWithin(m_sinceLastNotifyMin, m_sinceLastNotifyMax);
Chris@1643 145 m_sinceLastNotifyMin = m_sinceLastNotifyMax = -1;
Chris@1643 146 }
Chris@391 147 }
Chris@1643 148
Chris@1643 149 void toXml(QTextStream &out,
Chris@1643 150 QString indent = "",
Chris@1643 151 QString extraAttributes = "") const override {
Chris@391 152
Chris@1643 153 //!!! what is valueQuantization used for?
Chris@1643 154
Chris@1643 155 Model::toXml
Chris@1429 156 (out,
Chris@288 157 indent,
Chris@1643 158 QString("type=\"sparse\" dimensions=\"3\" resolution=\"%1\" "
Chris@1643 159 "notifyOnAdd=\"%2\" dataset=\"%3\" subtype=\"note\" "
Chris@1643 160 "valueQuantization=\"%4\" minimum=\"%5\" maximum=\"%6\" "
Chris@1643 161 "units=\"%7\" %8")
Chris@1643 162 .arg(m_resolution)
Chris@1643 163 .arg(m_notifyOnAdd ? "true" : "false")
Chris@1643 164 .arg(getObjectExportId(&m_events))
Chris@1643 165 .arg(m_valueQuantization)
Chris@1643 166 .arg(m_valueMinimum)
Chris@1643 167 .arg(m_valueMaximum)
Chris@1643 168 .arg(m_units)
Chris@1643 169 .arg(extraAttributes));
Chris@1643 170
Chris@1643 171 m_events.toXml(out, indent, QString("dimensions=\"3\""));
Chris@147 172 }
Chris@147 173
Chris@424 174 /**
Chris@424 175 * TabularModel methods.
Chris@424 176 */
Chris@1643 177
Chris@1643 178 int getRowCount() const override {
Chris@1643 179 return m_events.count();
Chris@1643 180 }
Chris@424 181
Chris@1643 182 int getColumnCount() const override {
Chris@424 183 return 6;
Chris@424 184 }
Chris@424 185
Chris@1643 186 bool isColumnTimeValue(int column) const override {
Chris@1643 187 // NB duration is not a "time value" -- that's for columns
Chris@1643 188 // whose sort ordering is exactly that of the frame time
Chris@1643 189 return (column < 2);
Chris@1643 190 }
Chris@1643 191
Chris@1643 192 sv_frame_t getFrameForRow(int row) const override {
Chris@1643 193 if (row < 0 || row >= m_events.count()) {
Chris@1643 194 return 0;
Chris@1643 195 }
Chris@1643 196 Event e = m_events.getEventByIndex(row);
Chris@1643 197 return e.getFrame();
Chris@1643 198 }
Chris@1643 199
Chris@1643 200 int getRowForFrame(sv_frame_t frame) const override {
Chris@1643 201 return m_events.getIndexForEvent(Event(frame));
Chris@1643 202 }
Chris@1643 203
Chris@1643 204 QString getHeading(int column) const override {
Chris@424 205 switch (column) {
Chris@424 206 case 0: return tr("Time");
Chris@424 207 case 1: return tr("Frame");
Chris@424 208 case 2: return tr("Pitch");
Chris@424 209 case 3: return tr("Duration");
Chris@424 210 case 4: return tr("Level");
Chris@424 211 case 5: return tr("Label");
Chris@424 212 default: return tr("Unknown");
Chris@424 213 }
Chris@424 214 }
Chris@424 215
Chris@1643 216 QVariant getData(int row, int column, int role) const override {
Chris@1643 217
Chris@1643 218 if (row < 0 || row >= m_events.count()) {
Chris@1643 219 return QVariant();
Chris@425 220 }
Chris@425 221
Chris@1643 222 Event e = m_events.getEventByIndex(row);
Chris@424 223
Chris@424 224 switch (column) {
Chris@1643 225 case 0: return adaptFrameForRole(e.getFrame(), getSampleRate(), role);
Chris@1643 226 case 1: return int(e.getFrame());
Chris@1643 227 case 2: return adaptValueForRole(e.getValue(), getScaleUnits(), role);
Chris@1643 228 case 3: return int(e.getDuration());
Chris@1643 229 case 4: return e.getLevel();
Chris@1643 230 case 5: return e.getLabel();
Chris@424 231 default: return QVariant();
Chris@424 232 }
Chris@424 233 }
Chris@424 234
Chris@1643 235 class EditCommand : public Command
Chris@1643 236 {
Chris@1643 237 public:
Chris@1643 238 //!!! borrowed ptr
Chris@1643 239 EditCommand(NoteModel *model, QString name) :
Chris@1643 240 m_model(model), m_name(name) { }
Chris@1643 241
Chris@1643 242 QString getName() const override {
Chris@1643 243 return m_name;
Chris@1643 244 }
Chris@1643 245
Chris@1643 246 void addPoint(Event e) {
Chris@1643 247 m_add.insert(e);
Chris@1643 248 }
Chris@1643 249 void deletePoint(Event e) {
Chris@1643 250 m_remove.insert(e);
Chris@1643 251 }
Chris@1643 252
Chris@1643 253 void execute() override {
Chris@1643 254 for (const Event &e: m_add) m_model->addPoint(e);
Chris@1643 255 for (const Event &e: m_remove) m_model->deletePoint(e);
Chris@1643 256 }
Chris@1643 257
Chris@1643 258 void unexecute() override {
Chris@1643 259 for (const Event &e: m_remove) m_model->addPoint(e);
Chris@1643 260 for (const Event &e: m_add) m_model->deletePoint(e);
Chris@1643 261 }
Chris@1643 262
Chris@1643 263 private:
Chris@1643 264 NoteModel *m_model;
Chris@1643 265 std::set<Event> m_add;
Chris@1643 266 std::set<Event> m_remove;
Chris@1643 267 QString m_name;
Chris@1643 268 };
Chris@1643 269
Chris@1643 270 //!!! rename Point to Note throughout? Just because we can now?
Chris@1643 271 void addPoint(Event e) {
Chris@1643 272
Chris@1643 273 bool allChange = false;
Chris@1643 274
Chris@1643 275 {
Chris@1643 276 QMutexLocker locker(&m_mutex);
Chris@1643 277 m_events.add(e);
Chris@1643 278 //!!!??? if (point.getLabel() != "") m_hasTextLabels = true;
Chris@1643 279
Chris@1643 280 float v = e.getValue();
Chris@1643 281 if (!ISNAN(v) && !ISINF(v)) {
Chris@1643 282 if (!m_haveExtents || v < m_valueMinimum) {
Chris@1643 283 m_valueMinimum = v; allChange = true;
Chris@1643 284 }
Chris@1643 285 if (!m_haveExtents || v > m_valueMaximum) {
Chris@1643 286 m_valueMaximum = v; allChange = true;
Chris@1643 287 }
Chris@1643 288 m_haveExtents = true;
Chris@1643 289 }
Chris@1643 290
Chris@1643 291 sv_frame_t f = e.getFrame();
Chris@1643 292
Chris@1643 293 if (!m_notifyOnAdd) {
Chris@1643 294 if (m_sinceLastNotifyMin == -1 || f < m_sinceLastNotifyMin) {
Chris@1643 295 m_sinceLastNotifyMin = f;
Chris@1643 296 }
Chris@1643 297 if (m_sinceLastNotifyMax == -1 || f > m_sinceLastNotifyMax) {
Chris@1643 298 m_sinceLastNotifyMax = f;
Chris@1643 299 }
Chris@1643 300 }
Chris@1643 301 }
Chris@1643 302
Chris@1643 303 if (m_notifyOnAdd) {
Chris@1643 304 emit modelChangedWithin(e.getFrame(),
Chris@1643 305 e.getFrame() + e.getDuration() + m_resolution);
Chris@1643 306 }
Chris@1643 307 if (allChange) {
Chris@1643 308 emit modelChanged();
Chris@1643 309 }
Chris@1643 310 }
Chris@1643 311
Chris@1643 312 void deletePoint(Event e) {
Chris@1643 313 {
Chris@1643 314 QMutexLocker locker(&m_mutex);
Chris@1643 315 m_events.remove(e);
Chris@1643 316 }
Chris@1643 317 emit modelChangedWithin(e.getFrame(),
Chris@1643 318 e.getFrame() + e.getDuration() + m_resolution);
Chris@1643 319 }
Chris@1643 320
Chris@1643 321 EventVector getPoints() const /*!!! override? - and/or rename? */ {
Chris@1643 322 EventVector ee;
Chris@1643 323 for (int i = 0; i < m_events.count(); ++i) {
Chris@1643 324 ee.push_back(m_events.getEventByIndex(i));
Chris@1643 325 }
Chris@1643 326 return ee;
Chris@1643 327 }
Chris@1643 328
Chris@1643 329 //!!! bleah
Chris@1643 330 EventVector getPoints(sv_frame_t start, sv_frame_t end) const {
Chris@1643 331 return m_events.getEventsSpanning(start, end - start);
Chris@1643 332 }
Chris@1643 333
Chris@1643 334 int getPointCount() const {
Chris@1643 335 return m_events.count();
Chris@1643 336 }
Chris@1643 337
Chris@1643 338 bool isEmpty() const {
Chris@1643 339 return m_events.isEmpty();
Chris@1643 340 }
Chris@1643 341
Chris@1643 342 bool containsPoint(const Event &e) const {
Chris@1643 343 return m_events.contains(e);
Chris@1643 344 }
Chris@1643 345
Chris@1580 346 Command *getSetDataCommand(int row, int column, const QVariant &value, int role) override
Chris@424 347 {
Chris@1643 348 if (row < 0 || row >= m_events.count()) return nullptr;
Chris@1643 349 if (role != Qt::EditRole) return nullptr;
Chris@1643 350
Chris@1643 351 Event e0 = m_events.getEventByIndex(row);
Chris@1643 352 Event e1;
Chris@1643 353
Chris@1643 354 switch (column) {
Chris@1643 355 case 0: e1 = e0.withFrame(sv_frame_t(round(value.toDouble() *
Chris@1643 356 getSampleRate()))); break;
Chris@1643 357 case 1: e1 = e0.withFrame(value.toInt()); break;
Chris@1643 358 case 2: e1 = e0.withValue(float(value.toDouble())); break;
Chris@1643 359 case 3: e1 = e0.withDuration(value.toInt()); break;
Chris@1643 360 case 4: e1 = e0.withLevel(float(value.toDouble())); break;
Chris@1643 361 case 5: e1 = e0.withLabel(value.toString()); break;
Chris@425 362 }
Chris@425 363
Chris@424 364 EditCommand *command = new EditCommand(this, tr("Edit Data"));
Chris@1643 365 command->deletePoint(e0);
Chris@1643 366 command->addPoint(e1);
Chris@1643 367 return command;
Chris@424 368 }
Chris@424 369
Chris@1580 370 SortType getSortType(int column) const override
Chris@424 371 {
Chris@424 372 if (column == 5) return SortAlphabetical;
Chris@424 373 return SortNumeric;
Chris@424 374 }
Chris@424 375
Chris@852 376 /**
Chris@852 377 * NoteExportable methods.
Chris@852 378 */
Chris@852 379
Chris@1580 380 NoteList getNotes() const override {
Chris@1643 381 return getNotesStartingWithin(getStartFrame(),
Chris@1643 382 getEndFrame() - getStartFrame());
Chris@852 383 }
Chris@852 384
Chris@1643 385 NoteList getNotesActiveAt(sv_frame_t frame) const override {
Chris@1643 386
Chris@852 387 NoteList notes;
Chris@1643 388 EventVector ee = m_events.getEventsCovering(frame);
Chris@1643 389 for (const auto &e: ee) {
Chris@1643 390 notes.push_back(e.toNoteData(getSampleRate(),
Chris@1643 391 getScaleUnits() != "Hz"));
Chris@1643 392 }
Chris@1643 393 return notes;
Chris@1643 394 }
Chris@1643 395
Chris@1643 396 NoteList getNotesStartingWithin(sv_frame_t startFrame,
Chris@1643 397 sv_frame_t duration) const override {
Chris@852 398
Chris@1643 399 NoteList notes;
Chris@1643 400 EventVector ee = m_events.getEventsStartingWithin(startFrame, duration);
Chris@1643 401 for (const auto &e: ee) {
Chris@1643 402 notes.push_back(e.toNoteData(getSampleRate(),
Chris@1643 403 getScaleUnits() != "Hz"));
Chris@852 404 }
Chris@852 405 return notes;
Chris@852 406 }
Chris@852 407
Chris@147 408 protected:
Chris@1643 409 sv_samplerate_t m_sampleRate;
Chris@1643 410 int m_resolution;
Chris@1643 411
Chris@1643 412 float m_valueMinimum;
Chris@1643 413 float m_valueMaximum;
Chris@1643 414 bool m_haveExtents;
Chris@147 415 float m_valueQuantization;
Chris@1643 416 QString m_units;
Chris@1643 417
Chris@1643 418 sv_frame_t m_extendTo;
Chris@1643 419
Chris@1643 420 bool m_notifyOnAdd;
Chris@1643 421 sv_frame_t m_sinceLastNotifyMin;
Chris@1643 422 sv_frame_t m_sinceLastNotifyMax;
Chris@1643 423
Chris@1643 424 EventSeries m_events;
Chris@1643 425
Chris@1643 426 int m_completion;
Chris@1643 427
Chris@1643 428 mutable QMutex m_mutex;
Chris@1643 429
Chris@1643 430 //!!! do we have general docs for ownership and synchronisation of models?
Chris@1643 431 // this might be a good opportunity to stop using bare pointers to them
Chris@147 432 };
Chris@147 433
Chris@147 434 #endif