annotate data/model/BoxModel.h @ 1793:f0ffc88a36b3 time-frequency-boxes

Add duration-and-extent type, which corresponds to a box model
author Chris Cannam
date Wed, 25 Sep 2019 11:06:59 +0100
parents cb45c0a1dfe1
children 343ef2a866a4
rev   line source
Chris@1785 1 /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
Chris@1785 2
Chris@1785 3 /*
Chris@1785 4 Sonic Visualiser
Chris@1785 5 An audio file viewer and annotation editor.
Chris@1785 6 Centre for Digital Music, Queen Mary, University of London.
Chris@1785 7
Chris@1785 8 This program is free software; you can redistribute it and/or
Chris@1785 9 modify it under the terms of the GNU General Public License as
Chris@1785 10 published by the Free Software Foundation; either version 2 of the
Chris@1785 11 License, or (at your option) any later version. See the file
Chris@1785 12 COPYING included with this distribution for more information.
Chris@1785 13 */
Chris@1785 14
Chris@1791 15 #ifndef SV_BOX_MODEL_H
Chris@1791 16 #define SV_BOX_MODEL_H
Chris@1785 17
Chris@1785 18 #include "EventCommands.h"
Chris@1785 19 #include "TabularModel.h"
Chris@1785 20 #include "Model.h"
Chris@1785 21 #include "DeferredNotifier.h"
Chris@1785 22
Chris@1785 23 #include "base/RealTime.h"
Chris@1785 24 #include "base/EventSeries.h"
Chris@1785 25 #include "base/UnitDatabase.h"
Chris@1785 26
Chris@1785 27 #include "system/System.h"
Chris@1785 28
Chris@1785 29 #include <QMutex>
Chris@1785 30
Chris@1785 31 /**
Chris@1791 32 * BoxModel -- a model for annotations having start time, duration,
Chris@1791 33 * and a value range. We use Events as usual for these, but treat the
Chris@1791 34 * "value" as the lower value and "level" as the difference between
Chris@1791 35 * lower and upper values, which is expected to be non-negative (if it
Chris@1791 36 * is negative, abs(level) will be used).
Chris@1791 37 *
Chris@1791 38 * This is expected to be used most often for time-frequency boxes.
Chris@1785 39 */
Chris@1791 40 class BoxModel : public Model,
Chris@1791 41 public TabularModel,
Chris@1791 42 public EventEditable
Chris@1785 43 {
Chris@1785 44 Q_OBJECT
Chris@1785 45
Chris@1785 46 public:
Chris@1791 47 BoxModel(sv_samplerate_t sampleRate,
Chris@1792 48 int resolution,
Chris@1792 49 bool notifyOnAdd = true) :
Chris@1785 50 m_sampleRate(sampleRate),
Chris@1785 51 m_resolution(resolution),
Chris@1791 52 m_valueMinimum(0.f),
Chris@1791 53 m_valueMaximum(0.f),
Chris@1785 54 m_haveExtents(false),
Chris@1785 55 m_notifier(this,
Chris@1785 56 getId(),
Chris@1785 57 notifyOnAdd ?
Chris@1785 58 DeferredNotifier::NOTIFY_ALWAYS :
Chris@1785 59 DeferredNotifier::NOTIFY_DEFERRED),
Chris@1785 60 m_completion(100) {
Chris@1785 61 }
Chris@1785 62
Chris@1791 63 BoxModel(sv_samplerate_t sampleRate, int resolution,
Chris@1792 64 float valueMinimum, float valueMaximum,
Chris@1792 65 bool notifyOnAdd = true) :
Chris@1785 66 m_sampleRate(sampleRate),
Chris@1785 67 m_resolution(resolution),
Chris@1791 68 m_valueMinimum(valueMinimum),
Chris@1791 69 m_valueMaximum(valueMaximum),
Chris@1785 70 m_haveExtents(true),
Chris@1785 71 m_notifier(this,
Chris@1785 72 getId(),
Chris@1785 73 notifyOnAdd ?
Chris@1785 74 DeferredNotifier::NOTIFY_ALWAYS :
Chris@1785 75 DeferredNotifier::NOTIFY_DEFERRED),
Chris@1785 76 m_completion(100) {
Chris@1785 77 }
Chris@1785 78
Chris@1791 79 virtual ~BoxModel() {
Chris@1785 80 }
Chris@1785 81
Chris@1791 82 QString getTypeName() const override { return tr("Box"); }
Chris@1785 83 bool isSparse() const override { return true; }
Chris@1785 84 bool isOK() const override { return true; }
Chris@1785 85
Chris@1785 86 sv_frame_t getStartFrame() const override {
Chris@1785 87 return m_events.getStartFrame();
Chris@1785 88 }
Chris@1785 89 sv_frame_t getTrueEndFrame() const override {
Chris@1785 90 if (m_events.isEmpty()) return 0;
Chris@1785 91 sv_frame_t e = m_events.getEndFrame();
Chris@1785 92 if (e % m_resolution == 0) return e;
Chris@1785 93 else return (e / m_resolution + 1) * m_resolution;
Chris@1785 94 }
Chris@1785 95
Chris@1785 96 sv_samplerate_t getSampleRate() const override { return m_sampleRate; }
Chris@1785 97 int getResolution() const { return m_resolution; }
Chris@1785 98
Chris@1791 99 QString getScaleUnits() const { return m_units; }
Chris@1791 100 void setScaleUnits(QString units) {
Chris@1791 101 m_units = units;
Chris@1791 102 UnitDatabase::getInstance()->registerUnit(units);
Chris@1791 103 }
Chris@1785 104
Chris@1791 105 float getValueMinimum() const { return m_valueMinimum; }
Chris@1791 106 float getValueMaximum() const { return m_valueMaximum; }
Chris@1785 107
Chris@1785 108 int getCompletion() const override { return m_completion; }
Chris@1785 109
Chris@1785 110 void setCompletion(int completion, bool update = true) {
Chris@1785 111
Chris@1785 112 { QMutexLocker locker(&m_mutex);
Chris@1785 113 if (m_completion == completion) return;
Chris@1785 114 m_completion = completion;
Chris@1785 115 }
Chris@1785 116
Chris@1785 117 if (update) {
Chris@1785 118 m_notifier.makeDeferredNotifications();
Chris@1785 119 }
Chris@1785 120
Chris@1785 121 emit completionChanged(getId());
Chris@1785 122
Chris@1785 123 if (completion == 100) {
Chris@1785 124 // henceforth:
Chris@1785 125 m_notifier.switchMode(DeferredNotifier::NOTIFY_ALWAYS);
Chris@1785 126 emit modelChanged(getId());
Chris@1785 127 }
Chris@1785 128 }
Chris@1785 129
Chris@1785 130 /**
Chris@1785 131 * Query methods.
Chris@1785 132 */
Chris@1785 133 int getEventCount() const {
Chris@1785 134 return m_events.count();
Chris@1785 135 }
Chris@1785 136 bool isEmpty() const {
Chris@1785 137 return m_events.isEmpty();
Chris@1785 138 }
Chris@1785 139 bool containsEvent(const Event &e) const {
Chris@1785 140 return m_events.contains(e);
Chris@1785 141 }
Chris@1785 142 EventVector getAllEvents() const {
Chris@1785 143 return m_events.getAllEvents();
Chris@1785 144 }
Chris@1785 145 EventVector getEventsSpanning(sv_frame_t f, sv_frame_t duration) const {
Chris@1785 146 return m_events.getEventsSpanning(f, duration);
Chris@1785 147 }
Chris@1785 148 EventVector getEventsCovering(sv_frame_t f) const {
Chris@1785 149 return m_events.getEventsCovering(f);
Chris@1785 150 }
Chris@1785 151 EventVector getEventsWithin(sv_frame_t f, sv_frame_t duration) const {
Chris@1785 152 return m_events.getEventsWithin(f, duration);
Chris@1785 153 }
Chris@1785 154 EventVector getEventsStartingWithin(sv_frame_t f, sv_frame_t duration) const {
Chris@1785 155 return m_events.getEventsStartingWithin(f, duration);
Chris@1785 156 }
Chris@1785 157 EventVector getEventsStartingAt(sv_frame_t f) const {
Chris@1785 158 return m_events.getEventsStartingAt(f);
Chris@1785 159 }
Chris@1785 160 bool getNearestEventMatching(sv_frame_t startSearchAt,
Chris@1785 161 std::function<bool(Event)> predicate,
Chris@1785 162 EventSeries::Direction direction,
Chris@1785 163 Event &found) const {
Chris@1785 164 return m_events.getNearestEventMatching
Chris@1785 165 (startSearchAt, predicate, direction, found);
Chris@1785 166 }
Chris@1785 167
Chris@1785 168 /**
Chris@1785 169 * Editing methods.
Chris@1785 170 */
Chris@1785 171 void add(Event e) override {
Chris@1785 172
Chris@1785 173 bool allChange = false;
Chris@1785 174
Chris@1785 175 {
Chris@1785 176 QMutexLocker locker(&m_mutex);
Chris@1785 177 m_events.add(e);
Chris@1785 178
Chris@1785 179 float f0 = e.getValue();
Chris@1785 180 float f1 = f0 + fabsf(e.getLevel());
Chris@1785 181
Chris@1791 182 if (!m_haveExtents || f0 < m_valueMinimum) {
Chris@1791 183 m_valueMinimum = f0; allChange = true;
Chris@1785 184 }
Chris@1791 185 if (!m_haveExtents || f1 > m_valueMaximum) {
Chris@1791 186 m_valueMaximum = f1; allChange = true;
Chris@1785 187 }
Chris@1785 188 m_haveExtents = true;
Chris@1785 189 }
Chris@1785 190
Chris@1785 191 m_notifier.update(e.getFrame(), e.getDuration() + m_resolution);
Chris@1785 192
Chris@1785 193 if (allChange) {
Chris@1785 194 emit modelChanged(getId());
Chris@1785 195 }
Chris@1785 196 }
Chris@1785 197
Chris@1785 198 void remove(Event e) override {
Chris@1785 199 {
Chris@1785 200 QMutexLocker locker(&m_mutex);
Chris@1785 201 m_events.remove(e);
Chris@1785 202 }
Chris@1785 203 emit modelChangedWithin(getId(),
Chris@1785 204 e.getFrame(),
Chris@1785 205 e.getFrame() + e.getDuration() + m_resolution);
Chris@1785 206 }
Chris@1785 207
Chris@1785 208 /**
Chris@1785 209 * TabularModel methods.
Chris@1785 210 */
Chris@1785 211
Chris@1785 212 int getRowCount() const override {
Chris@1785 213 return m_events.count();
Chris@1785 214 }
Chris@1785 215
Chris@1785 216 int getColumnCount() const override {
Chris@1786 217 return 6;
Chris@1785 218 }
Chris@1785 219
Chris@1785 220 bool isColumnTimeValue(int column) const override {
Chris@1785 221 // NB duration is not a "time value" -- that's for columns
Chris@1785 222 // whose sort ordering is exactly that of the frame time
Chris@1785 223 return (column < 2);
Chris@1785 224 }
Chris@1785 225
Chris@1785 226 sv_frame_t getFrameForRow(int row) const override {
Chris@1785 227 if (row < 0 || row >= m_events.count()) {
Chris@1785 228 return 0;
Chris@1785 229 }
Chris@1785 230 Event e = m_events.getEventByIndex(row);
Chris@1785 231 return e.getFrame();
Chris@1785 232 }
Chris@1785 233
Chris@1785 234 int getRowForFrame(sv_frame_t frame) const override {
Chris@1785 235 return m_events.getIndexForEvent(Event(frame));
Chris@1785 236 }
Chris@1785 237
Chris@1785 238 QString getHeading(int column) const override {
Chris@1785 239 switch (column) {
Chris@1785 240 case 0: return tr("Time");
Chris@1785 241 case 1: return tr("Frame");
Chris@1785 242 case 2: return tr("Duration");
Chris@1786 243 case 3: return tr("Min Freq");
Chris@1786 244 case 4: return tr("Max Freq");
Chris@1785 245 case 5: return tr("Label");
Chris@1785 246 default: return tr("Unknown");
Chris@1785 247 }
Chris@1785 248 }
Chris@1785 249
Chris@1785 250 SortType getSortType(int column) const override {
Chris@1785 251 if (column == 5) return SortAlphabetical;
Chris@1785 252 return SortNumeric;
Chris@1785 253 }
Chris@1785 254
Chris@1785 255 QVariant getData(int row, int column, int role) const override {
Chris@1785 256
Chris@1785 257 if (row < 0 || row >= m_events.count()) {
Chris@1785 258 return QVariant();
Chris@1785 259 }
Chris@1785 260
Chris@1785 261 Event e = m_events.getEventByIndex(row);
Chris@1785 262
Chris@1785 263 switch (column) {
Chris@1785 264 case 0: return adaptFrameForRole(e.getFrame(), getSampleRate(), role);
Chris@1785 265 case 1: return int(e.getFrame());
Chris@1785 266 case 2: return int(e.getDuration());
Chris@1785 267 case 3: return adaptValueForRole(e.getValue(), getScaleUnits(), role);
Chris@1785 268 case 4: return adaptValueForRole(e.getValue() + fabsf(e.getLevel()),
Chris@1785 269 getScaleUnits(), role);
Chris@1785 270 case 5: return e.getLabel();
Chris@1785 271 default: return QVariant();
Chris@1785 272 }
Chris@1785 273 }
Chris@1785 274
Chris@1785 275 Command *getSetDataCommand(int row, int column, const QVariant &value,
Chris@1785 276 int role) override {
Chris@1785 277
Chris@1785 278 if (row < 0 || row >= m_events.count()) return nullptr;
Chris@1785 279 if (role != Qt::EditRole) return nullptr;
Chris@1785 280
Chris@1785 281 Event e0 = m_events.getEventByIndex(row);
Chris@1785 282 Event e1;
Chris@1785 283
Chris@1785 284 switch (column) {
Chris@1785 285 case 0: e1 = e0.withFrame(sv_frame_t(round(value.toDouble() *
Chris@1785 286 getSampleRate()))); break;
Chris@1785 287 case 1: e1 = e0.withFrame(value.toInt()); break;
Chris@1785 288 case 2: e1 = e0.withDuration(value.toInt()); break;
Chris@1785 289 case 3: e1 = e0.withValue(float(value.toDouble())); break;
Chris@1785 290 case 4: e1 = e0.withLevel(fabsf(float(value.toDouble()) -
Chris@1785 291 e0.getValue())); break;
Chris@1785 292 case 5: e1 = e0.withLabel(value.toString()); break;
Chris@1785 293 }
Chris@1785 294
Chris@1785 295 auto command = new ChangeEventsCommand(getId().untyped, tr("Edit Data"));
Chris@1785 296 command->remove(e0);
Chris@1785 297 command->add(e1);
Chris@1785 298 return command->finish();
Chris@1785 299 }
Chris@1785 300
Chris@1785 301
Chris@1785 302 /**
Chris@1785 303 * XmlExportable methods.
Chris@1785 304 */
Chris@1785 305 void toXml(QTextStream &out,
Chris@1785 306 QString indent = "",
Chris@1785 307 QString extraAttributes = "") const override {
Chris@1785 308
Chris@1785 309 Model::toXml
Chris@1785 310 (out,
Chris@1785 311 indent,
Chris@1789 312 QString("type=\"sparse\" dimensions=\"2\" resolution=\"%1\" "
Chris@1785 313 "notifyOnAdd=\"%2\" dataset=\"%3\" subtype=\"%4\" "
Chris@1785 314 "minimum=\"%5\" maximum=\"%6\" units=\"%7\" %8")
Chris@1785 315 .arg(m_resolution)
Chris@1785 316 .arg("true") // always true after model reaches 100% -
Chris@1785 317 // subsequent events are always notified
Chris@1785 318 .arg(m_events.getExportId())
Chris@1791 319 .arg("box")
Chris@1791 320 .arg(m_valueMinimum)
Chris@1791 321 .arg(m_valueMaximum)
Chris@1785 322 .arg(encodeEntities(m_units))
Chris@1785 323 .arg(extraAttributes));
Chris@1789 324
Chris@1789 325 Event::ExportNameOptions options;
Chris@1789 326 options.levelAttributeName = "extent";
Chris@1785 327
Chris@1789 328 m_events.toXml(out, indent, QString("dimensions=\"2\""), options);
Chris@1785 329 }
Chris@1785 330
Chris@1785 331 QString toDelimitedDataString(QString delimiter,
Chris@1789 332 DataExportOptions,
Chris@1785 333 sv_frame_t startFrame,
Chris@1785 334 sv_frame_t duration) const override {
Chris@1786 335
Chris@1786 336 // We need a custom format here
Chris@1786 337
Chris@1786 338 EventVector ee = m_events.getEventsSpanning(startFrame, duration);
Chris@1786 339
Chris@1786 340 QString s;
Chris@1786 341
Chris@1786 342 for (auto e: ee) {
Chris@1786 343
Chris@1786 344 QStringList list;
Chris@1786 345
Chris@1786 346 list << RealTime::frame2RealTime
Chris@1786 347 (e.getFrame(), getSampleRate())
Chris@1786 348 .toString().c_str()
Chris@1786 349 << RealTime::frame2RealTime
Chris@1786 350 (e.getFrame() + e.getDuration(), getSampleRate())
Chris@1786 351 .toString().c_str()
Chris@1786 352 << QString("%1").arg(e.getValue())
Chris@1786 353 << QString("%1").arg(e.getValue() + fabsf(e.getLevel()));
Chris@1786 354
Chris@1786 355 if (e.getLabel() != "") {
Chris@1786 356 list << e.getLabel();
Chris@1786 357 }
Chris@1786 358
Chris@1786 359 s += list.join(delimiter) + "\n";
Chris@1786 360 }
Chris@1786 361
Chris@1786 362 return s;
Chris@1785 363 }
Chris@1785 364
Chris@1785 365 protected:
Chris@1785 366 sv_samplerate_t m_sampleRate;
Chris@1785 367 int m_resolution;
Chris@1785 368
Chris@1791 369 float m_valueMinimum;
Chris@1791 370 float m_valueMaximum;
Chris@1785 371 bool m_haveExtents;
Chris@1785 372 QString m_units;
Chris@1785 373 DeferredNotifier m_notifier;
Chris@1785 374 int m_completion;
Chris@1785 375
Chris@1785 376 EventSeries m_events;
Chris@1785 377
Chris@1785 378 mutable QMutex m_mutex;
Chris@1785 379 };
Chris@1785 380
Chris@1785 381 #endif