annotate data/model/NoteModel.h @ 1648:86bbccb79c9b single-point

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