annotate data/model/NoteModel.h @ 1671:82d03c9661f9 single-point

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