annotate data/model/NoteModel.h @ 1651:7a56bb85030f single-point

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