annotate base/Event.h @ 1840:7faa08747f5e

Comments
author Chris Cannam
date Tue, 14 Apr 2020 08:19:23 +0100
parents 21c792334c2e
children b4b11af915f4
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@1615 87 if (m_duration < 0) throw std::logic_error("duration must be >= 0");
Chris@1615 88 }
Chris@1611 89
Chris@1615 90 Event(sv_frame_t frame, float value, sv_frame_t duration,
Chris@1612 91 float level, QString label) :
Chris@1629 92 m_haveValue(true), m_haveLevel(true),
Chris@1629 93 m_haveDuration(true), m_haveReferenceFrame(false),
Chris@1611 94 m_value(value), m_level(level), m_frame(frame),
Chris@1615 95 m_duration(duration), m_referenceFrame(0), m_label(label) {
Chris@1615 96 if (m_duration < 0) throw std::logic_error("duration must be >= 0");
Chris@1615 97 }
Chris@1611 98
Chris@1615 99 Event(const Event &event) =default;
Chris@1647 100
Chris@1647 101 // We would ideally like Event to be immutable - but we have to
Chris@1647 102 // have these because otherwise we can't put Events in vectors
Chris@1647 103 // etc. Let's call it conceptually immutable
Chris@1615 104 Event &operator=(const Event &event) =default;
Chris@1615 105 Event &operator=(Event &&event) =default;
Chris@1611 106
Chris@1611 107 sv_frame_t getFrame() const { return m_frame; }
Chris@1611 108
Chris@1615 109 Event withFrame(sv_frame_t frame) const {
Chris@1615 110 Event p(*this);
Chris@1611 111 p.m_frame = frame;
Chris@1611 112 return p;
Chris@1611 113 }
Chris@1611 114
Chris@1615 115 bool hasValue() const { return m_haveValue; }
Chris@1634 116 float getValue() const { return m_haveValue ? m_value : 0.f; }
Chris@1611 117
Chris@1615 118 Event withValue(float value) const {
Chris@1615 119 Event p(*this);
Chris@1611 120 p.m_haveValue = true;
Chris@1611 121 p.m_value = value;
Chris@1611 122 return p;
Chris@1611 123 }
Chris@1615 124 Event withoutValue() const {
Chris@1615 125 Event p(*this);
Chris@1615 126 p.m_haveValue = false;
Chris@1615 127 p.m_value = 0.f;
Chris@1615 128 return p;
Chris@1615 129 }
Chris@1611 130
Chris@1629 131 bool hasDuration() const { return m_haveDuration; }
Chris@1634 132 sv_frame_t getDuration() const { return m_haveDuration ? m_duration : 0; }
Chris@1611 133
Chris@1615 134 Event withDuration(sv_frame_t duration) const {
Chris@1615 135 Event p(*this);
Chris@1611 136 p.m_duration = duration;
Chris@1629 137 p.m_haveDuration = true;
Chris@1615 138 if (duration < 0) throw std::logic_error("duration must be >= 0");
Chris@1615 139 return p;
Chris@1615 140 }
Chris@1615 141 Event withoutDuration() const {
Chris@1615 142 Event p(*this);
Chris@1629 143 p.m_haveDuration = false;
Chris@1615 144 p.m_duration = 0;
Chris@1611 145 return p;
Chris@1611 146 }
Chris@1620 147
Chris@1620 148 bool hasLabel() const { return m_label != QString(); }
Chris@1611 149 QString getLabel() const { return m_label; }
Chris@1611 150
Chris@1615 151 Event withLabel(QString label) const {
Chris@1615 152 Event p(*this);
Chris@1611 153 p.m_label = label;
Chris@1611 154 return p;
Chris@1611 155 }
Chris@1663 156
Chris@1663 157 bool hasUri() const { return m_uri != QString(); }
Chris@1663 158 QString getURI() const { return m_uri; }
Chris@1663 159
Chris@1663 160 Event withURI(QString uri) const {
Chris@1663 161 Event p(*this);
Chris@1663 162 p.m_uri = uri;
Chris@1663 163 return p;
Chris@1663 164 }
Chris@1611 165
Chris@1615 166 bool hasLevel() const { return m_haveLevel; }
Chris@1634 167 float getLevel() const { return m_haveLevel ? m_level : 0.f; }
Chris@1615 168
Chris@1615 169 Event withLevel(float level) const {
Chris@1615 170 Event p(*this);
Chris@1611 171 p.m_haveLevel = true;
Chris@1611 172 p.m_level = level;
Chris@1611 173 return p;
Chris@1611 174 }
Chris@1615 175 Event withoutLevel() const {
Chris@1615 176 Event p(*this);
Chris@1615 177 p.m_haveLevel = false;
Chris@1615 178 p.m_level = 0.f;
Chris@1615 179 return p;
Chris@1615 180 }
Chris@1611 181
Chris@1615 182 bool hasReferenceFrame() const { return m_haveReferenceFrame; }
Chris@1634 183 sv_frame_t getReferenceFrame() const {
Chris@1634 184 return m_haveReferenceFrame ? m_referenceFrame : m_frame;
Chris@1634 185 }
Chris@1611 186
Chris@1615 187 bool referenceFrameDiffers() const { // from event frame
Chris@1611 188 return m_haveReferenceFrame && (m_referenceFrame != m_frame);
Chris@1611 189 }
Chris@1611 190
Chris@1615 191 Event withReferenceFrame(sv_frame_t frame) const {
Chris@1615 192 Event p(*this);
Chris@1611 193 p.m_haveReferenceFrame = true;
Chris@1611 194 p.m_referenceFrame = frame;
Chris@1611 195 return p;
Chris@1611 196 }
Chris@1615 197 Event withoutReferenceFrame() const {
Chris@1615 198 Event p(*this);
Chris@1615 199 p.m_haveReferenceFrame = false;
Chris@1615 200 p.m_referenceFrame = 0;
Chris@1615 201 return p;
Chris@1615 202 }
Chris@1612 203
Chris@1615 204 bool operator==(const Event &p) const {
Chris@1612 205
Chris@1612 206 if (m_frame != p.m_frame) return false;
Chris@1629 207
Chris@1629 208 if (m_haveDuration != p.m_haveDuration) return false;
Chris@1629 209 if (m_haveDuration && (m_duration != p.m_duration)) return false;
Chris@1612 210
Chris@1612 211 if (m_haveValue != p.m_haveValue) return false;
Chris@1612 212 if (m_haveValue && (m_value != p.m_value)) return false;
Chris@1612 213
Chris@1612 214 if (m_haveLevel != p.m_haveLevel) return false;
Chris@1612 215 if (m_haveLevel && (m_level != p.m_level)) return false;
Chris@1612 216
Chris@1612 217 if (m_haveReferenceFrame != p.m_haveReferenceFrame) return false;
Chris@1612 218 if (m_haveReferenceFrame &&
Chris@1612 219 (m_referenceFrame != p.m_referenceFrame)) return false;
Chris@1612 220
Chris@1612 221 if (m_label != p.m_label) return false;
Chris@1663 222 if (m_uri != p.m_uri) return false;
Chris@1612 223
Chris@1612 224 return true;
Chris@1612 225 }
Chris@1612 226
Chris@1630 227 bool operator!=(const Event &p) const {
Chris@1630 228 return !operator==(p);
Chris@1630 229 }
Chris@1630 230
Chris@1615 231 bool operator<(const Event &p) const {
Chris@1612 232
Chris@1629 233 if (m_frame != p.m_frame) {
Chris@1629 234 return m_frame < p.m_frame;
Chris@1629 235 }
Chris@1612 236
Chris@1615 237 // events without a property sort before events with that property
Chris@1612 238
Chris@1629 239 if (m_haveDuration != p.m_haveDuration) {
Chris@1629 240 return !m_haveDuration;
Chris@1629 241 }
Chris@1629 242 if (m_haveDuration && (m_duration != p.m_duration)) {
Chris@1629 243 return m_duration < p.m_duration;
Chris@1629 244 }
Chris@1629 245
Chris@1629 246 if (m_haveValue != p.m_haveValue) {
Chris@1629 247 return !m_haveValue;
Chris@1629 248 }
Chris@1629 249 if (m_haveValue && (m_value != p.m_value)) {
Chris@1629 250 return m_value < p.m_value;
Chris@1629 251 }
Chris@1612 252
Chris@1629 253 if (m_haveLevel != p.m_haveLevel) {
Chris@1629 254 return !m_haveLevel;
Chris@1629 255 }
Chris@1629 256 if (m_haveLevel && (m_level != p.m_level)) {
Chris@1629 257 return m_level < p.m_level;
Chris@1629 258 }
Chris@1612 259
Chris@1612 260 if (m_haveReferenceFrame != p.m_haveReferenceFrame) {
Chris@1612 261 return !m_haveReferenceFrame;
Chris@1612 262 }
Chris@1612 263 if (m_haveReferenceFrame && (m_referenceFrame != p.m_referenceFrame)) {
Chris@1612 264 return m_referenceFrame < p.m_referenceFrame;
Chris@1612 265 }
Chris@1612 266
Chris@1663 267 if (m_label != p.m_label) {
Chris@1663 268 return m_label < p.m_label;
Chris@1663 269 }
Chris@1663 270 return m_uri < p.m_uri;
Chris@1612 271 }
Chris@1612 272
Chris@1674 273 struct ExportNameOptions {
Chris@1674 274
Chris@1674 275 ExportNameOptions() :
Chris@1789 276 valueAttributeName("value"),
Chris@1789 277 levelAttributeName("level"),
Chris@1674 278 uriAttributeName("uri") { }
Chris@1674 279
Chris@1789 280 QString valueAttributeName;
Chris@1789 281 QString levelAttributeName;
Chris@1674 282 QString uriAttributeName;
Chris@1674 283 };
Chris@1674 284
Chris@1612 285 void toXml(QTextStream &stream,
Chris@1612 286 QString indent = "",
Chris@1674 287 QString extraAttributes = "",
Chris@1674 288 ExportNameOptions opts = ExportNameOptions()) const {
Chris@1612 289
Chris@1615 290 // For I/O purposes these are points, not events
Chris@1612 291 stream << indent << QString("<point frame=\"%1\" ").arg(m_frame);
Chris@1674 292 if (m_haveValue) {
Chris@1674 293 stream << QString("%1=\"%2\" ")
Chris@1789 294 .arg(opts.valueAttributeName).arg(m_value);
Chris@1674 295 }
Chris@1674 296 if (m_haveDuration) {
Chris@1674 297 stream << QString("duration=\"%1\" ").arg(m_duration);
Chris@1674 298 }
Chris@1674 299 if (m_haveLevel) {
Chris@1789 300 stream << QString("%1=\"%2\" ")
Chris@1789 301 .arg(opts.levelAttributeName)
Chris@1789 302 .arg(m_level);
Chris@1674 303 }
Chris@1674 304 if (m_haveReferenceFrame) {
Chris@1674 305 stream << QString("referenceFrame=\"%1\" ")
Chris@1674 306 .arg(m_referenceFrame);
Chris@1674 307 }
Chris@1682 308
Chris@1682 309 stream << QString("label=\"%1\" ")
Chris@1682 310 .arg(XmlExportable::encodeEntities(m_label));
Chris@1682 311
Chris@1663 312 if (m_uri != QString()) {
Chris@1674 313 stream << QString("%1=\"%2\" ")
Chris@1674 314 .arg(opts.uriAttributeName)
Chris@1663 315 .arg(XmlExportable::encodeEntities(m_uri));
Chris@1663 316 }
Chris@1641 317 stream << extraAttributes << "/>\n";
Chris@1612 318 }
Chris@1612 319
Chris@1612 320 QString toXmlString(QString indent = "",
Chris@1612 321 QString extraAttributes = "") const {
Chris@1612 322 QString s;
Chris@1612 323 QTextStream out(&s);
Chris@1612 324 toXml(out, indent, extraAttributes);
Chris@1612 325 out.flush();
Chris@1612 326 return s;
Chris@1612 327 }
Chris@1615 328
Chris@1629 329 NoteData toNoteData(sv_samplerate_t sampleRate,
Chris@1637 330 bool valueIsMidiPitch) const {
Chris@1615 331
Chris@1615 332 sv_frame_t duration;
Chris@1629 333 if (m_haveDuration && m_duration > 0) {
Chris@1615 334 duration = m_duration;
Chris@1615 335 } else {
Chris@1615 336 duration = sv_frame_t(sampleRate / 6); // arbitrary short duration
Chris@1615 337 }
Chris@1615 338
Chris@1615 339 int midiPitch;
Chris@1615 340 float frequency = 0.f;
Chris@1615 341 if (m_haveValue) {
Chris@1615 342 if (valueIsMidiPitch) {
Chris@1615 343 midiPitch = int(roundf(m_value));
Chris@1615 344 } else {
Chris@1615 345 frequency = m_value;
Chris@1615 346 midiPitch = Pitch::getPitchForFrequency(frequency);
Chris@1615 347 }
Chris@1615 348 } else {
Chris@1615 349 midiPitch = 64;
Chris@1615 350 valueIsMidiPitch = true;
Chris@1615 351 }
Chris@1615 352
Chris@1615 353 int velocity = 100;
Chris@1615 354 if (m_haveLevel) {
Chris@1615 355 if (m_level > 0.f && m_level <= 1.f) {
Chris@1615 356 velocity = int(roundf(m_level * 127.f));
Chris@1615 357 }
Chris@1615 358 }
Chris@1615 359
Chris@1615 360 NoteData n(m_frame, duration, midiPitch, velocity);
Chris@1615 361 n.isMidiPitchQuantized = valueIsMidiPitch;
Chris@1615 362 if (!valueIsMidiPitch) {
Chris@1615 363 n.frequency = frequency;
Chris@1615 364 }
Chris@1615 365
Chris@1615 366 return n;
Chris@1615 367 }
Chris@1623 368
Chris@1833 369 QVector<QString>
Chris@1833 370 getStringExportHeaders(DataExportOptions opts,
Chris@1833 371 ExportNameOptions nameOpts) const {
Chris@1815 372
Chris@1815 373 QStringList list;
Chris@1815 374
Chris@1815 375 // These are considered API rather than human-readable text -
Chris@1815 376 // they shouldn't be translated
Chris@1815 377
Chris@1815 378 if (opts & DataExportWriteTimeInFrames) {
Chris@1815 379 list << "frame";
Chris@1815 380 } else {
Chris@1815 381 list << "time";
Chris@1815 382 }
Chris@1815 383
Chris@1815 384 if (m_haveValue) {
Chris@1815 385 list << nameOpts.valueAttributeName;
Chris@1815 386 }
Chris@1815 387
Chris@1815 388 if (m_haveDuration) {
Chris@1815 389 list << "duration";
Chris@1815 390 }
Chris@1815 391
Chris@1815 392 if (m_haveLevel) {
Chris@1816 393 if (!(opts & DataExportOmitLevel)) {
Chris@1815 394 list << nameOpts.levelAttributeName;
Chris@1815 395 }
Chris@1815 396 }
Chris@1815 397
Chris@1815 398 if (m_uri != "") {
Chris@1815 399 list << nameOpts.uriAttributeName;
Chris@1815 400 }
Chris@1815 401
Chris@1815 402 list << "label";
Chris@1833 403
Chris@1833 404 QVector<QString> sv;
Chris@1833 405 for (QString s: list) {
Chris@1833 406 sv.push_back(s.toUpper());
Chris@1833 407 }
Chris@1833 408 return sv;
Chris@1833 409 }
Chris@1833 410
Chris@1833 411 QVector<QString>
Chris@1833 412 toStringExportRow(DataExportOptions opts,
Chris@1833 413 sv_samplerate_t sampleRate) const {
Chris@1815 414
Chris@1629 415 QStringList list;
Chris@1629 416
Chris@1815 417 if (opts & DataExportWriteTimeInFrames) {
Chris@1815 418 list << QString("%1").arg(m_frame);
Chris@1815 419 } else {
Chris@1815 420 list << RealTime::frame2RealTime(m_frame, sampleRate)
Chris@1815 421 .toString().c_str();
Chris@1815 422 }
Chris@1629 423
Chris@1629 424 if (m_haveValue) {
Chris@1629 425 list << QString("%1").arg(m_value);
Chris@1629 426 }
Chris@1629 427
Chris@1629 428 if (m_haveDuration) {
Chris@1815 429 if (opts & DataExportWriteTimeInFrames) {
Chris@1815 430 list << QString("%1").arg(m_duration);
Chris@1815 431 } else {
Chris@1815 432 list << RealTime::frame2RealTime(m_duration, sampleRate)
Chris@1815 433 .toString().c_str();
Chris@1815 434 }
Chris@1629 435 }
Chris@1629 436
Chris@1629 437 if (m_haveLevel) {
Chris@1816 438 if (!(opts & DataExportOmitLevel)) {
Chris@1629 439 list << QString("%1").arg(m_level);
Chris@1629 440 }
Chris@1629 441 }
Chris@1787 442
Chris@1787 443 // Put URI before label, to preserve the ordering previously
Chris@1787 444 // used in the custom Image model exporter. We shouldn't
Chris@1787 445 // change the column ordering unless (until?) we provide a
Chris@1787 446 // facility for the user to customise it
Chris@1787 447 if (m_uri != "") list << m_uri;
Chris@1629 448 if (m_label != "") list << m_label;
Chris@1833 449
Chris@1833 450 return list.toVector();
Chris@1629 451 }
Chris@1799 452
Chris@1623 453 uint hash(uint seed = 0) const {
Chris@1623 454 uint h = qHash(m_label, seed);
Chris@1623 455 if (m_haveValue) h ^= qHash(m_value);
Chris@1623 456 if (m_haveLevel) h ^= qHash(m_level);
Chris@1623 457 h ^= qHash(m_frame);
Chris@1629 458 if (m_haveDuration) h ^= qHash(m_duration);
Chris@1623 459 if (m_haveReferenceFrame) h ^= qHash(m_referenceFrame);
Chris@1663 460 h ^= qHash(m_uri);
Chris@1623 461 return h;
Chris@1623 462 }
Chris@1611 463
Chris@1611 464 private:
Chris@1611 465 // The order of fields here is chosen to minimise overall size of struct.
Chris@1612 466 // We potentially store very many of these objects.
Chris@1611 467 // If you change something, check what difference it makes to packing.
Chris@1611 468 bool m_haveValue : 1;
Chris@1611 469 bool m_haveLevel : 1;
Chris@1629 470 bool m_haveDuration : 1;
Chris@1611 471 bool m_haveReferenceFrame : 1;
Chris@1611 472 float m_value;
Chris@1611 473 float m_level;
Chris@1611 474 sv_frame_t m_frame;
Chris@1611 475 sv_frame_t m_duration;
Chris@1611 476 sv_frame_t m_referenceFrame;
Chris@1611 477 QString m_label;
Chris@1663 478 QString m_uri;
Chris@1611 479 };
Chris@1611 480
Chris@1623 481 inline uint qHash(const Event &e, uint seed = 0) {
Chris@1623 482 return e.hash(seed);
Chris@1623 483 }
Chris@1623 484
Chris@1615 485 typedef std::vector<Event> EventVector;
Chris@1612 486
Chris@1611 487 #endif