annotate data/model/NoteModel.h @ 1752:6d09d68165a4 by-id

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