annotate base/Event.h @ 1866:b4b11af915f4

If duration < 0, swap start time and duration rather than throwing an exception - this is too deep to be throwing an exception here, we end up with bugs like #1989 (Crash when trying to import CSV file with certain unexpected data in it) - and the meaning of negative duration is not actually ambiguous
author Chris Cannam
date Thu, 11 Jun 2020 14:07:56 +0100
parents 21c792334c2e
children
rev   line source
Chris@1611 1 /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
Chris@1611 2
Chris@1611 3 /*
Chris@1611 4 Sonic Visualiser
Chris@1611 5 An audio file viewer and annotation editor.
Chris@1611 6 Centre for Digital Music, Queen Mary, University of London.
Chris@1611 7 This file copyright 2006 Chris Cannam.
Chris@1611 8
Chris@1611 9 This program is free software; you can redistribute it and/or
Chris@1611 10 modify it under the terms of the GNU General Public License as
Chris@1611 11 published by the Free Software Foundation; either version 2 of the
Chris@1611 12 License, or (at your option) any later version. See the file
Chris@1611 13 COPYING included with this distribution for more information.
Chris@1611 14 */
Chris@1611 15
Chris@1615 16 #ifndef SV_EVENT_H
Chris@1615 17 #define SV_EVENT_H
Chris@1615 18
Chris@1615 19 #include "BaseTypes.h"
Chris@1615 20 #include "NoteData.h"
Chris@1615 21 #include "XmlExportable.h"
Chris@1629 22 #include "DataExportOptions.h"
Chris@1615 23
Chris@1615 24 #include <vector>
Chris@1615 25 #include <stdexcept>
Chris@1611 26
Chris@1611 27 #include <QString>
Chris@1611 28
Chris@1800 29 #if (QT_VERSION < QT_VERSION_CHECK(5, 3, 0))
Chris@1803 30 #ifdef __GNUC__
Chris@1803 31 #pragma GCC diagnostic ignored "-Wunused-function"
Chris@1803 32 #endif
Chris@1803 33 static uint qHash(float key, uint seed = 0) {
Chris@1801 34 uint h = seed;
Chris@1801 35 const uchar *p = reinterpret_cast<const uchar *>(&key);
Chris@1801 36 for (size_t i = 0; i < sizeof(key); ++i) {
Chris@1801 37 h = 31 * h + p[i];
Chris@1801 38 }
Chris@1801 39 return h;
Chris@1800 40 }
Chris@1800 41 #endif
Chris@1800 42
Chris@1615 43 /**
Chris@1647 44 * An immutable(-ish) type used for point and event representation in
Chris@1647 45 * sparse models, as well as for interchange within the clipboard. An
Chris@1647 46 * event always has a frame and (possibly empty) label, and optionally
Chris@1647 47 * has numerical value, level, duration in frames, and a mapped
Chris@1647 48 * reference frame. Event has an operator< defining a total ordering,
Chris@1647 49 * by frame first and then by the other properties.
Chris@1615 50 *
Chris@1615 51 * Event is based on the Clipboard::Point type up to SV v3.2.1 and is
Chris@1615 52 * intended also to replace the custom point types previously found in
Chris@1615 53 * sparse models.
Chris@1615 54 */
Chris@1615 55 class Event
Chris@1611 56 {
Chris@1611 57 public:
Chris@1623 58 Event() :
Chris@1629 59 m_haveValue(false), m_haveLevel(false),
Chris@1629 60 m_haveDuration(false), m_haveReferenceFrame(false),
Chris@1623 61 m_value(0.f), m_level(0.f), m_frame(0),
Chris@1623 62 m_duration(0), m_referenceFrame(0), m_label() { }
Chris@1623 63
Chris@1615 64 Event(sv_frame_t frame) :
Chris@1629 65 m_haveValue(false), m_haveLevel(false),
Chris@1629 66 m_haveDuration(false), m_haveReferenceFrame(false),
Chris@1615 67 m_value(0.f), m_level(0.f), m_frame(frame),
Chris@1615 68 m_duration(0), m_referenceFrame(0), m_label() { }
Chris@1615 69
Chris@1615 70 Event(sv_frame_t frame, QString label) :
Chris@1629 71 m_haveValue(false), m_haveLevel(false),
Chris@1629 72 m_haveDuration(false), m_haveReferenceFrame(false),
Chris@1611 73 m_value(0.f), m_level(0.f), m_frame(frame),
Chris@1611 74 m_duration(0), m_referenceFrame(0), m_label(label) { }
Chris@1611 75
Chris@1615 76 Event(sv_frame_t frame, float value, QString label) :
Chris@1629 77 m_haveValue(true), m_haveLevel(false),
Chris@1629 78 m_haveDuration(false), m_haveReferenceFrame(false),
Chris@1611 79 m_value(value), m_level(0.f), m_frame(frame),
Chris@1611 80 m_duration(0), m_referenceFrame(0), m_label(label) { }
Chris@1611 81
Chris@1615 82 Event(sv_frame_t frame, float value, sv_frame_t duration, QString label) :
Chris@1629 83 m_haveValue(true), m_haveLevel(false),
Chris@1629 84 m_haveDuration(true), m_haveReferenceFrame(false),
Chris@1611 85 m_value(value), m_level(0.f), m_frame(frame),
Chris@1615 86 m_duration(duration), m_referenceFrame(0), m_label(label) {
Chris@1866 87 if (m_duration < 0) {
Chris@1866 88 m_frame += m_duration;
Chris@1866 89 m_duration = -m_duration;
Chris@1866 90 }
Chris@1615 91 }
Chris@1611 92
Chris@1615 93 Event(sv_frame_t frame, float value, sv_frame_t duration,
Chris@1612 94 float level, QString label) :
Chris@1629 95 m_haveValue(true), m_haveLevel(true),
Chris@1629 96 m_haveDuration(true), m_haveReferenceFrame(false),
Chris@1611 97 m_value(value), m_level(level), m_frame(frame),
Chris@1615 98 m_duration(duration), m_referenceFrame(0), m_label(label) {
Chris@1866 99 if (m_duration < 0) {
Chris@1866 100 m_frame += m_duration;
Chris@1866 101 m_duration = -m_duration;
Chris@1866 102 }
Chris@1615 103 }
Chris@1611 104
Chris@1615 105 Event(const Event &event) =default;
Chris@1647 106
Chris@1647 107 // We would ideally like Event to be immutable - but we have to
Chris@1647 108 // have these because otherwise we can't put Events in vectors
Chris@1647 109 // etc. Let's call it conceptually immutable
Chris@1615 110 Event &operator=(const Event &event) =default;
Chris@1615 111 Event &operator=(Event &&event) =default;
Chris@1611 112
Chris@1611 113 sv_frame_t getFrame() const { return m_frame; }
Chris@1611 114
Chris@1615 115 Event withFrame(sv_frame_t frame) const {
Chris@1615 116 Event p(*this);
Chris@1611 117 p.m_frame = frame;
Chris@1611 118 return p;
Chris@1611 119 }
Chris@1611 120
Chris@1615 121 bool hasValue() const { return m_haveValue; }
Chris@1634 122 float getValue() const { return m_haveValue ? m_value : 0.f; }
Chris@1611 123
Chris@1615 124 Event withValue(float value) const {
Chris@1615 125 Event p(*this);
Chris@1611 126 p.m_haveValue = true;
Chris@1611 127 p.m_value = value;
Chris@1611 128 return p;
Chris@1611 129 }
Chris@1615 130 Event withoutValue() const {
Chris@1615 131 Event p(*this);
Chris@1615 132 p.m_haveValue = false;
Chris@1615 133 p.m_value = 0.f;
Chris@1615 134 return p;
Chris@1615 135 }
Chris@1611 136
Chris@1629 137 bool hasDuration() const { return m_haveDuration; }
Chris@1634 138 sv_frame_t getDuration() const { return m_haveDuration ? m_duration : 0; }
Chris@1611 139
Chris@1615 140 Event withDuration(sv_frame_t duration) const {
Chris@1615 141 Event p(*this);
Chris@1611 142 p.m_duration = duration;
Chris@1629 143 p.m_haveDuration = true;
Chris@1866 144 if (p.m_duration < 0) {
Chris@1866 145 p.m_frame += p.m_duration;
Chris@1866 146 p.m_duration = -p.m_duration;
Chris@1866 147 }
Chris@1615 148 return p;
Chris@1615 149 }
Chris@1615 150 Event withoutDuration() const {
Chris@1615 151 Event p(*this);
Chris@1629 152 p.m_haveDuration = false;
Chris@1615 153 p.m_duration = 0;
Chris@1611 154 return p;
Chris@1611 155 }
Chris@1620 156
Chris@1620 157 bool hasLabel() const { return m_label != QString(); }
Chris@1611 158 QString getLabel() const { return m_label; }
Chris@1611 159
Chris@1615 160 Event withLabel(QString label) const {
Chris@1615 161 Event p(*this);
Chris@1611 162 p.m_label = label;
Chris@1611 163 return p;
Chris@1611 164 }
Chris@1663 165
Chris@1663 166 bool hasUri() const { return m_uri != QString(); }
Chris@1663 167 QString getURI() const { return m_uri; }
Chris@1663 168
Chris@1663 169 Event withURI(QString uri) const {
Chris@1663 170 Event p(*this);
Chris@1663 171 p.m_uri = uri;
Chris@1663 172 return p;
Chris@1663 173 }
Chris@1611 174
Chris@1615 175 bool hasLevel() const { return m_haveLevel; }
Chris@1634 176 float getLevel() const { return m_haveLevel ? m_level : 0.f; }
Chris@1615 177
Chris@1615 178 Event withLevel(float level) const {
Chris@1615 179 Event p(*this);
Chris@1611 180 p.m_haveLevel = true;
Chris@1611 181 p.m_level = level;
Chris@1611 182 return p;
Chris@1611 183 }
Chris@1615 184 Event withoutLevel() const {
Chris@1615 185 Event p(*this);
Chris@1615 186 p.m_haveLevel = false;
Chris@1615 187 p.m_level = 0.f;
Chris@1615 188 return p;
Chris@1615 189 }
Chris@1611 190
Chris@1615 191 bool hasReferenceFrame() const { return m_haveReferenceFrame; }
Chris@1634 192 sv_frame_t getReferenceFrame() const {
Chris@1634 193 return m_haveReferenceFrame ? m_referenceFrame : m_frame;
Chris@1634 194 }
Chris@1611 195
Chris@1615 196 bool referenceFrameDiffers() const { // from event frame
Chris@1611 197 return m_haveReferenceFrame && (m_referenceFrame != m_frame);
Chris@1611 198 }
Chris@1611 199
Chris@1615 200 Event withReferenceFrame(sv_frame_t frame) const {
Chris@1615 201 Event p(*this);
Chris@1611 202 p.m_haveReferenceFrame = true;
Chris@1611 203 p.m_referenceFrame = frame;
Chris@1611 204 return p;
Chris@1611 205 }
Chris@1615 206 Event withoutReferenceFrame() const {
Chris@1615 207 Event p(*this);
Chris@1615 208 p.m_haveReferenceFrame = false;
Chris@1615 209 p.m_referenceFrame = 0;
Chris@1615 210 return p;
Chris@1615 211 }
Chris@1612 212
Chris@1615 213 bool operator==(const Event &p) const {
Chris@1612 214
Chris@1612 215 if (m_frame != p.m_frame) return false;
Chris@1629 216
Chris@1629 217 if (m_haveDuration != p.m_haveDuration) return false;
Chris@1629 218 if (m_haveDuration && (m_duration != p.m_duration)) return false;
Chris@1612 219
Chris@1612 220 if (m_haveValue != p.m_haveValue) return false;
Chris@1612 221 if (m_haveValue && (m_value != p.m_value)) return false;
Chris@1612 222
Chris@1612 223 if (m_haveLevel != p.m_haveLevel) return false;
Chris@1612 224 if (m_haveLevel && (m_level != p.m_level)) return false;
Chris@1612 225
Chris@1612 226 if (m_haveReferenceFrame != p.m_haveReferenceFrame) return false;
Chris@1612 227 if (m_haveReferenceFrame &&
Chris@1612 228 (m_referenceFrame != p.m_referenceFrame)) return false;
Chris@1612 229
Chris@1612 230 if (m_label != p.m_label) return false;
Chris@1663 231 if (m_uri != p.m_uri) return false;
Chris@1612 232
Chris@1612 233 return true;
Chris@1612 234 }
Chris@1612 235
Chris@1630 236 bool operator!=(const Event &p) const {
Chris@1630 237 return !operator==(p);
Chris@1630 238 }
Chris@1630 239
Chris@1615 240 bool operator<(const Event &p) const {
Chris@1612 241
Chris@1629 242 if (m_frame != p.m_frame) {
Chris@1629 243 return m_frame < p.m_frame;
Chris@1629 244 }
Chris@1612 245
Chris@1615 246 // events without a property sort before events with that property
Chris@1612 247
Chris@1629 248 if (m_haveDuration != p.m_haveDuration) {
Chris@1629 249 return !m_haveDuration;
Chris@1629 250 }
Chris@1629 251 if (m_haveDuration && (m_duration != p.m_duration)) {
Chris@1629 252 return m_duration < p.m_duration;
Chris@1629 253 }
Chris@1629 254
Chris@1629 255 if (m_haveValue != p.m_haveValue) {
Chris@1629 256 return !m_haveValue;
Chris@1629 257 }
Chris@1629 258 if (m_haveValue && (m_value != p.m_value)) {
Chris@1629 259 return m_value < p.m_value;
Chris@1629 260 }
Chris@1612 261
Chris@1629 262 if (m_haveLevel != p.m_haveLevel) {
Chris@1629 263 return !m_haveLevel;
Chris@1629 264 }
Chris@1629 265 if (m_haveLevel && (m_level != p.m_level)) {
Chris@1629 266 return m_level < p.m_level;
Chris@1629 267 }
Chris@1612 268
Chris@1612 269 if (m_haveReferenceFrame != p.m_haveReferenceFrame) {
Chris@1612 270 return !m_haveReferenceFrame;
Chris@1612 271 }
Chris@1612 272 if (m_haveReferenceFrame && (m_referenceFrame != p.m_referenceFrame)) {
Chris@1612 273 return m_referenceFrame < p.m_referenceFrame;
Chris@1612 274 }
Chris@1612 275
Chris@1663 276 if (m_label != p.m_label) {
Chris@1663 277 return m_label < p.m_label;
Chris@1663 278 }
Chris@1663 279 return m_uri < p.m_uri;
Chris@1612 280 }
Chris@1612 281
Chris@1674 282 struct ExportNameOptions {
Chris@1674 283
Chris@1674 284 ExportNameOptions() :
Chris@1789 285 valueAttributeName("value"),
Chris@1789 286 levelAttributeName("level"),
Chris@1674 287 uriAttributeName("uri") { }
Chris@1674 288
Chris@1789 289 QString valueAttributeName;
Chris@1789 290 QString levelAttributeName;
Chris@1674 291 QString uriAttributeName;
Chris@1674 292 };
Chris@1674 293
Chris@1612 294 void toXml(QTextStream &stream,
Chris@1612 295 QString indent = "",
Chris@1674 296 QString extraAttributes = "",
Chris@1674 297 ExportNameOptions opts = ExportNameOptions()) const {
Chris@1612 298
Chris@1615 299 // For I/O purposes these are points, not events
Chris@1612 300 stream << indent << QString("<point frame=\"%1\" ").arg(m_frame);
Chris@1674 301 if (m_haveValue) {
Chris@1674 302 stream << QString("%1=\"%2\" ")
Chris@1789 303 .arg(opts.valueAttributeName).arg(m_value);
Chris@1674 304 }
Chris@1674 305 if (m_haveDuration) {
Chris@1674 306 stream << QString("duration=\"%1\" ").arg(m_duration);
Chris@1674 307 }
Chris@1674 308 if (m_haveLevel) {
Chris@1789 309 stream << QString("%1=\"%2\" ")
Chris@1789 310 .arg(opts.levelAttributeName)
Chris@1789 311 .arg(m_level);
Chris@1674 312 }
Chris@1674 313 if (m_haveReferenceFrame) {
Chris@1674 314 stream << QString("referenceFrame=\"%1\" ")
Chris@1674 315 .arg(m_referenceFrame);
Chris@1674 316 }
Chris@1682 317
Chris@1682 318 stream << QString("label=\"%1\" ")
Chris@1682 319 .arg(XmlExportable::encodeEntities(m_label));
Chris@1682 320
Chris@1663 321 if (m_uri != QString()) {
Chris@1674 322 stream << QString("%1=\"%2\" ")
Chris@1674 323 .arg(opts.uriAttributeName)
Chris@1663 324 .arg(XmlExportable::encodeEntities(m_uri));
Chris@1663 325 }
Chris@1641 326 stream << extraAttributes << "/>\n";
Chris@1612 327 }
Chris@1612 328
Chris@1612 329 QString toXmlString(QString indent = "",
Chris@1612 330 QString extraAttributes = "") const {
Chris@1612 331 QString s;
Chris@1612 332 QTextStream out(&s);
Chris@1612 333 toXml(out, indent, extraAttributes);
Chris@1612 334 out.flush();
Chris@1612 335 return s;
Chris@1612 336 }
Chris@1615 337
Chris@1629 338 NoteData toNoteData(sv_samplerate_t sampleRate,
Chris@1637 339 bool valueIsMidiPitch) const {
Chris@1615 340
Chris@1615 341 sv_frame_t duration;
Chris@1629 342 if (m_haveDuration && m_duration > 0) {
Chris@1615 343 duration = m_duration;
Chris@1615 344 } else {
Chris@1615 345 duration = sv_frame_t(sampleRate / 6); // arbitrary short duration
Chris@1615 346 }
Chris@1615 347
Chris@1615 348 int midiPitch;
Chris@1615 349 float frequency = 0.f;
Chris@1615 350 if (m_haveValue) {
Chris@1615 351 if (valueIsMidiPitch) {
Chris@1615 352 midiPitch = int(roundf(m_value));
Chris@1615 353 } else {
Chris@1615 354 frequency = m_value;
Chris@1615 355 midiPitch = Pitch::getPitchForFrequency(frequency);
Chris@1615 356 }
Chris@1615 357 } else {
Chris@1615 358 midiPitch = 64;
Chris@1615 359 valueIsMidiPitch = true;
Chris@1615 360 }
Chris@1615 361
Chris@1615 362 int velocity = 100;
Chris@1615 363 if (m_haveLevel) {
Chris@1615 364 if (m_level > 0.f && m_level <= 1.f) {
Chris@1615 365 velocity = int(roundf(m_level * 127.f));
Chris@1615 366 }
Chris@1615 367 }
Chris@1615 368
Chris@1615 369 NoteData n(m_frame, duration, midiPitch, velocity);
Chris@1615 370 n.isMidiPitchQuantized = valueIsMidiPitch;
Chris@1615 371 if (!valueIsMidiPitch) {
Chris@1615 372 n.frequency = frequency;
Chris@1615 373 }
Chris@1615 374
Chris@1615 375 return n;
Chris@1615 376 }
Chris@1623 377
Chris@1833 378 QVector<QString>
Chris@1833 379 getStringExportHeaders(DataExportOptions opts,
Chris@1833 380 ExportNameOptions nameOpts) const {
Chris@1815 381
Chris@1815 382 QStringList list;
Chris@1815 383
Chris@1815 384 // These are considered API rather than human-readable text -
Chris@1815 385 // they shouldn't be translated
Chris@1815 386
Chris@1815 387 if (opts & DataExportWriteTimeInFrames) {
Chris@1815 388 list << "frame";
Chris@1815 389 } else {
Chris@1815 390 list << "time";
Chris@1815 391 }
Chris@1815 392
Chris@1815 393 if (m_haveValue) {
Chris@1815 394 list << nameOpts.valueAttributeName;
Chris@1815 395 }
Chris@1815 396
Chris@1815 397 if (m_haveDuration) {
Chris@1815 398 list << "duration";
Chris@1815 399 }
Chris@1815 400
Chris@1815 401 if (m_haveLevel) {
Chris@1816 402 if (!(opts & DataExportOmitLevel)) {
Chris@1815 403 list << nameOpts.levelAttributeName;
Chris@1815 404 }
Chris@1815 405 }
Chris@1815 406
Chris@1815 407 if (m_uri != "") {
Chris@1815 408 list << nameOpts.uriAttributeName;
Chris@1815 409 }
Chris@1815 410
Chris@1815 411 list << "label";
Chris@1833 412
Chris@1833 413 QVector<QString> sv;
Chris@1833 414 for (QString s: list) {
Chris@1833 415 sv.push_back(s.toUpper());
Chris@1833 416 }
Chris@1833 417 return sv;
Chris@1833 418 }
Chris@1833 419
Chris@1833 420 QVector<QString>
Chris@1833 421 toStringExportRow(DataExportOptions opts,
Chris@1833 422 sv_samplerate_t sampleRate) const {
Chris@1815 423
Chris@1629 424 QStringList list;
Chris@1629 425
Chris@1815 426 if (opts & DataExportWriteTimeInFrames) {
Chris@1815 427 list << QString("%1").arg(m_frame);
Chris@1815 428 } else {
Chris@1815 429 list << RealTime::frame2RealTime(m_frame, sampleRate)
Chris@1815 430 .toString().c_str();
Chris@1815 431 }
Chris@1629 432
Chris@1629 433 if (m_haveValue) {
Chris@1629 434 list << QString("%1").arg(m_value);
Chris@1629 435 }
Chris@1629 436
Chris@1629 437 if (m_haveDuration) {
Chris@1815 438 if (opts & DataExportWriteTimeInFrames) {
Chris@1815 439 list << QString("%1").arg(m_duration);
Chris@1815 440 } else {
Chris@1815 441 list << RealTime::frame2RealTime(m_duration, sampleRate)
Chris@1815 442 .toString().c_str();
Chris@1815 443 }
Chris@1629 444 }
Chris@1629 445
Chris@1629 446 if (m_haveLevel) {
Chris@1816 447 if (!(opts & DataExportOmitLevel)) {
Chris@1629 448 list << QString("%1").arg(m_level);
Chris@1629 449 }
Chris@1629 450 }
Chris@1787 451
Chris@1787 452 // Put URI before label, to preserve the ordering previously
Chris@1787 453 // used in the custom Image model exporter. We shouldn't
Chris@1787 454 // change the column ordering unless (until?) we provide a
Chris@1787 455 // facility for the user to customise it
Chris@1787 456 if (m_uri != "") list << m_uri;
Chris@1629 457 if (m_label != "") list << m_label;
Chris@1833 458
Chris@1833 459 return list.toVector();
Chris@1629 460 }
Chris@1799 461
Chris@1623 462 uint hash(uint seed = 0) const {
Chris@1623 463 uint h = qHash(m_label, seed);
Chris@1623 464 if (m_haveValue) h ^= qHash(m_value);
Chris@1623 465 if (m_haveLevel) h ^= qHash(m_level);
Chris@1623 466 h ^= qHash(m_frame);
Chris@1629 467 if (m_haveDuration) h ^= qHash(m_duration);
Chris@1623 468 if (m_haveReferenceFrame) h ^= qHash(m_referenceFrame);
Chris@1663 469 h ^= qHash(m_uri);
Chris@1623 470 return h;
Chris@1623 471 }
Chris@1611 472
Chris@1611 473 private:
Chris@1611 474 // The order of fields here is chosen to minimise overall size of struct.
Chris@1612 475 // We potentially store very many of these objects.
Chris@1611 476 // If you change something, check what difference it makes to packing.
Chris@1611 477 bool m_haveValue : 1;
Chris@1611 478 bool m_haveLevel : 1;
Chris@1629 479 bool m_haveDuration : 1;
Chris@1611 480 bool m_haveReferenceFrame : 1;
Chris@1611 481 float m_value;
Chris@1611 482 float m_level;
Chris@1611 483 sv_frame_t m_frame;
Chris@1611 484 sv_frame_t m_duration;
Chris@1611 485 sv_frame_t m_referenceFrame;
Chris@1611 486 QString m_label;
Chris@1663 487 QString m_uri;
Chris@1611 488 };
Chris@1611 489
Chris@1623 490 inline uint qHash(const Event &e, uint seed = 0) {
Chris@1623 491 return e.hash(seed);
Chris@1623 492 }
Chris@1623 493
Chris@1615 494 typedef std::vector<Event> EventVector;
Chris@1612 495
Chris@1611 496 #endif