annotate base/Event.h @ 1833:21c792334c2e sensible-delimited-data-strings

Rewrite all the DelimitedDataString stuff so as to return vectors of individual cell strings rather than having the classes add the delimiters themselves. Rename accordingly to names based on StringExport. Take advantage of this in the CSV writer code so as to properly quote cells that contain delimiter characters.
author Chris Cannam
date Fri, 03 Apr 2020 17:11:05 +0100
parents fd5a87f3c5b4
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