annotate base/EventSeries.cpp @ 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 c546429d4c2f
children
rev   line source
Chris@1631 1 /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
Chris@1631 2
Chris@1631 3 /*
Chris@1631 4 Sonic Visualiser
Chris@1631 5 An audio file viewer and annotation editor.
Chris@1631 6 Centre for Digital Music, Queen Mary, University of London.
Chris@1631 7
Chris@1631 8 This program is free software; you can redistribute it and/or
Chris@1631 9 modify it under the terms of the GNU General Public License as
Chris@1631 10 published by the Free Software Foundation; either version 2 of the
Chris@1631 11 License, or (at your option) any later version. See the file
Chris@1631 12 COPYING included with this distribution for more information.
Chris@1631 13 */
Chris@1631 14
Chris@1631 15 #include "EventSeries.h"
Chris@1631 16
Chris@1796 17 #include <QMutexLocker>
Chris@1796 18
Chris@1833 19 using std::vector;
Chris@1833 20 using std::string;
Chris@1833 21
Chris@1796 22 EventSeries::EventSeries(const EventSeries &other) :
Chris@1796 23 EventSeries(other, QMutexLocker(&other.m_mutex))
Chris@1796 24 {
Chris@1796 25 }
Chris@1796 26
Chris@1796 27 EventSeries::EventSeries(const EventSeries &other, const QMutexLocker &) :
Chris@1796 28 m_events(other.m_events),
Chris@1796 29 m_seams(other.m_seams),
Chris@1796 30 m_finalDurationlessEventFrame(other.m_finalDurationlessEventFrame)
Chris@1796 31 {
Chris@1796 32 }
Chris@1796 33
Chris@1796 34 EventSeries &
Chris@1796 35 EventSeries::operator=(const EventSeries &other)
Chris@1796 36 {
Chris@1796 37 QMutexLocker locker(&m_mutex), otherLocker(&other.m_mutex);
Chris@1796 38 m_events = other.m_events;
Chris@1796 39 m_seams = other.m_seams;
Chris@1796 40 m_finalDurationlessEventFrame = other.m_finalDurationlessEventFrame;
Chris@1796 41 return *this;
Chris@1796 42 }
Chris@1796 43
Chris@1796 44 EventSeries &
Chris@1796 45 EventSeries::operator=(EventSeries &&other)
Chris@1796 46 {
Chris@1796 47 QMutexLocker locker(&m_mutex), otherLocker(&other.m_mutex);
Chris@1796 48 m_events = std::move(other.m_events);
Chris@1796 49 m_seams = std::move(other.m_seams);
Chris@1796 50 m_finalDurationlessEventFrame = std::move(other.m_finalDurationlessEventFrame);
Chris@1796 51 return *this;
Chris@1796 52 }
Chris@1796 53
Chris@1796 54 bool
Chris@1796 55 EventSeries::operator==(const EventSeries &other) const
Chris@1796 56 {
Chris@1796 57 QMutexLocker locker(&m_mutex);
Chris@1796 58 return m_events == other.m_events;
Chris@1796 59 }
Chris@1796 60
Chris@1679 61 EventSeries
Chris@1679 62 EventSeries::fromEvents(const EventVector &v)
Chris@1679 63 {
Chris@1679 64 EventSeries s;
Chris@1679 65 for (const auto &e: v) {
Chris@1679 66 s.add(e);
Chris@1679 67 }
Chris@1679 68 return s;
Chris@1679 69 }
Chris@1679 70
Chris@1631 71 bool
Chris@1631 72 EventSeries::isEmpty() const
Chris@1631 73 {
Chris@1796 74 QMutexLocker locker(&m_mutex);
Chris@1631 75 return m_events.empty();
Chris@1631 76 }
Chris@1631 77
Chris@1631 78 int
Chris@1631 79 EventSeries::count() const
Chris@1631 80 {
Chris@1796 81 QMutexLocker locker(&m_mutex);
Chris@1631 82 if (m_events.size() > INT_MAX) {
Chris@1632 83 throw std::logic_error("too many events");
Chris@1631 84 }
Chris@1631 85 return int(m_events.size());
Chris@1631 86 }
Chris@1631 87
Chris@1631 88 void
Chris@1631 89 EventSeries::add(const Event &p)
Chris@1631 90 {
Chris@1796 91 QMutexLocker locker(&m_mutex);
Chris@1796 92
Chris@1631 93 bool isUnique = true;
Chris@1631 94
Chris@1631 95 auto pitr = lower_bound(m_events.begin(), m_events.end(), p);
Chris@1631 96 if (pitr != m_events.end() && *pitr == p) {
Chris@1631 97 isUnique = false;
Chris@1631 98 }
Chris@1631 99 m_events.insert(pitr, p);
Chris@1631 100
Chris@1640 101 if (!p.hasDuration() && p.getFrame() > m_finalDurationlessEventFrame) {
Chris@1640 102 m_finalDurationlessEventFrame = p.getFrame();
Chris@1640 103 }
Chris@1640 104
Chris@1631 105 if (p.hasDuration() && isUnique) {
Chris@1631 106
Chris@1631 107 const sv_frame_t frame = p.getFrame();
Chris@1631 108 const sv_frame_t endFrame = p.getFrame() + p.getDuration();
Chris@1631 109
Chris@1631 110 createSeam(frame);
Chris@1631 111 createSeam(endFrame);
Chris@1631 112
Chris@1631 113 // These calls must both succeed after calling createSeam above
Chris@1631 114 const auto i0 = m_seams.find(frame);
Chris@1631 115 const auto i1 = m_seams.find(endFrame);
Chris@1631 116
Chris@1631 117 for (auto i = i0; i != i1; ++i) {
Chris@1631 118 if (i == m_seams.end()) {
Chris@1631 119 SVCERR << "ERROR: EventSeries::add: "
Chris@1631 120 << "reached end of seam map"
Chris@1631 121 << endl;
Chris@1631 122 break;
Chris@1631 123 }
Chris@1631 124 i->second.push_back(p);
Chris@1631 125 }
Chris@1631 126 }
Chris@1631 127
Chris@1631 128 #ifdef DEBUG_EVENT_SERIES
Chris@1631 129 std::cerr << "after add:" << std::endl;
Chris@1631 130 dumpEvents();
Chris@1631 131 dumpSeams();
Chris@1631 132 #endif
Chris@1631 133 }
Chris@1631 134
Chris@1631 135 void
Chris@1631 136 EventSeries::remove(const Event &p)
Chris@1631 137 {
Chris@1796 138 QMutexLocker locker(&m_mutex);
Chris@1796 139
Chris@1631 140 // If we are removing the last (unique) example of an event,
Chris@1631 141 // then we also need to remove it from the seam map. If this
Chris@1631 142 // is only one of multiple identical events, then we don't.
Chris@1631 143 bool isUnique = true;
Chris@1631 144
Chris@1631 145 auto pitr = lower_bound(m_events.begin(), m_events.end(), p);
Chris@1631 146 if (pitr == m_events.end() || *pitr != p) {
Chris@1631 147 // we don't know this event
Chris@1631 148 return;
Chris@1631 149 } else {
Chris@1631 150 auto nitr = pitr;
Chris@1631 151 ++nitr;
Chris@1631 152 if (nitr != m_events.end() && *nitr == p) {
Chris@1631 153 isUnique = false;
Chris@1631 154 }
Chris@1631 155 }
Chris@1631 156
Chris@1631 157 m_events.erase(pitr);
Chris@1631 158
Chris@1640 159 if (!p.hasDuration() && isUnique &&
Chris@1640 160 p.getFrame() == m_finalDurationlessEventFrame) {
Chris@1640 161 m_finalDurationlessEventFrame = 0;
Chris@1640 162 for (auto ritr = m_events.rbegin(); ritr != m_events.rend(); ++ritr) {
Chris@1640 163 if (!ritr->hasDuration()) {
Chris@1640 164 m_finalDurationlessEventFrame = ritr->getFrame();
Chris@1640 165 break;
Chris@1640 166 }
Chris@1640 167 }
Chris@1640 168 }
Chris@1640 169
Chris@1631 170 if (p.hasDuration() && isUnique) {
Chris@1631 171
Chris@1631 172 const sv_frame_t frame = p.getFrame();
Chris@1631 173 const sv_frame_t endFrame = p.getFrame() + p.getDuration();
Chris@1631 174
Chris@1631 175 const auto i0 = m_seams.find(frame);
Chris@1631 176 const auto i1 = m_seams.find(endFrame);
Chris@1631 177
Chris@1631 178 #ifdef DEBUG_EVENT_SERIES
Chris@1631 179 // This should be impossible if we found p in m_events above
Chris@1631 180 if (i0 == m_seams.end() || i1 == m_seams.end()) {
Chris@1631 181 SVCERR << "ERROR: EventSeries::remove: either frame " << frame
Chris@1631 182 << " or endFrame " << endFrame
Chris@1631 183 << " for event not found in seam map: event is "
Chris@1631 184 << p.toXmlString() << endl;
Chris@1631 185 }
Chris@1631 186 #endif
Chris@1631 187
Chris@1631 188 // Remove any and all instances of p from the seam map; we
Chris@1631 189 // are only supposed to get here if we are removing the
Chris@1631 190 // last instance of p from the series anyway
Chris@1631 191
Chris@1631 192 for (auto i = i0; i != i1; ++i) {
Chris@1631 193 if (i == m_seams.end()) {
Chris@1631 194 // This can happen only if we have a negative
Chris@1631 195 // duration, which Event forbids
Chris@1631 196 throw std::logic_error("unexpectedly reached end of map");
Chris@1631 197 }
Chris@1631 198 for (size_t j = 0; j < i->second.size(); ) {
Chris@1631 199 if (i->second[j] == p) {
Chris@1631 200 i->second.erase(i->second.begin() + j);
Chris@1631 201 } else {
Chris@1631 202 ++j;
Chris@1631 203 }
Chris@1631 204 }
Chris@1631 205 }
Chris@1631 206
Chris@1631 207 // Tidy up by removing any entries that are now identical
Chris@1631 208 // to their predecessors
Chris@1631 209
Chris@1631 210 std::vector<sv_frame_t> redundant;
Chris@1631 211
Chris@1631 212 auto pitr = m_seams.end();
Chris@1631 213 if (i0 != m_seams.begin()) {
Chris@1631 214 pitr = i0;
Chris@1631 215 --pitr;
Chris@1631 216 }
Chris@1631 217
Chris@1631 218 for (auto i = i0; i != m_seams.end(); ++i) {
Chris@1631 219 if (pitr != m_seams.end() &&
Chris@1631 220 seamsEqual(i->second, pitr->second)) {
Chris@1631 221 redundant.push_back(i->first);
Chris@1631 222 }
Chris@1631 223 pitr = i;
Chris@1631 224 if (i == i1) {
Chris@1631 225 break;
Chris@1631 226 }
Chris@1631 227 }
Chris@1631 228
Chris@1631 229 for (sv_frame_t f: redundant) {
Chris@1631 230 m_seams.erase(f);
Chris@1631 231 }
Chris@1631 232
Chris@1631 233 // And remove any empty seams from the start of the map
Chris@1631 234
Chris@1631 235 while (m_seams.begin() != m_seams.end() &&
Chris@1631 236 m_seams.begin()->second.empty()) {
Chris@1631 237 m_seams.erase(m_seams.begin());
Chris@1631 238 }
Chris@1631 239 }
Chris@1631 240
Chris@1631 241 #ifdef DEBUG_EVENT_SERIES
Chris@1631 242 std::cerr << "after remove:" << std::endl;
Chris@1631 243 dumpEvents();
Chris@1631 244 dumpSeams();
Chris@1631 245 #endif
Chris@1631 246 }
Chris@1631 247
Chris@1631 248 bool
Chris@1631 249 EventSeries::contains(const Event &p) const
Chris@1631 250 {
Chris@1796 251 QMutexLocker locker(&m_mutex);
Chris@1631 252 return binary_search(m_events.begin(), m_events.end(), p);
Chris@1631 253 }
Chris@1631 254
Chris@1631 255 void
Chris@1631 256 EventSeries::clear()
Chris@1631 257 {
Chris@1796 258 QMutexLocker locker(&m_mutex);
Chris@1631 259 m_events.clear();
Chris@1631 260 m_seams.clear();
Chris@1640 261 m_finalDurationlessEventFrame = 0;
Chris@1640 262 }
Chris@1640 263
Chris@1640 264 sv_frame_t
Chris@1640 265 EventSeries::getStartFrame() const
Chris@1640 266 {
Chris@1796 267 QMutexLocker locker(&m_mutex);
Chris@1640 268 if (m_events.empty()) return 0;
Chris@1640 269 return m_events.begin()->getFrame();
Chris@1640 270 }
Chris@1640 271
Chris@1640 272 sv_frame_t
Chris@1640 273 EventSeries::getEndFrame() const
Chris@1640 274 {
Chris@1796 275 QMutexLocker locker(&m_mutex);
Chris@1796 276
Chris@1640 277 sv_frame_t latest = 0;
Chris@1640 278
Chris@1640 279 if (m_events.empty()) return latest;
Chris@1640 280
Chris@1640 281 latest = m_finalDurationlessEventFrame;
Chris@1640 282
Chris@1640 283 if (m_seams.empty()) return latest;
Chris@1640 284
Chris@1640 285 sv_frame_t lastSeam = m_seams.rbegin()->first;
Chris@1640 286 if (lastSeam > latest) {
Chris@1640 287 latest = lastSeam;
Chris@1640 288 }
Chris@1640 289
Chris@1640 290 return latest;
Chris@1631 291 }
Chris@1631 292
Chris@1631 293 EventVector
Chris@1631 294 EventSeries::getEventsSpanning(sv_frame_t frame,
Chris@1631 295 sv_frame_t duration) const
Chris@1631 296 {
Chris@1796 297 QMutexLocker locker(&m_mutex);
Chris@1796 298
Chris@1631 299 EventVector span;
Chris@1631 300
Chris@1631 301 const sv_frame_t start = frame;
Chris@1631 302 const sv_frame_t end = frame + duration;
Chris@1631 303
Chris@1631 304 // first find any zero-duration events
Chris@1631 305
Chris@1631 306 auto pitr = lower_bound(m_events.begin(), m_events.end(),
Chris@1631 307 Event(start));
Chris@1631 308 while (pitr != m_events.end() && pitr->getFrame() < end) {
Chris@1631 309 if (!pitr->hasDuration()) {
Chris@1631 310 span.push_back(*pitr);
Chris@1631 311 }
Chris@1631 312 ++pitr;
Chris@1631 313 }
Chris@1631 314
Chris@1631 315 // now any non-zero-duration ones from the seam map
Chris@1631 316
Chris@1631 317 std::set<Event> found;
Chris@1631 318 auto sitr = m_seams.lower_bound(start);
Chris@1631 319 if (sitr == m_seams.end() || sitr->first > start) {
Chris@1631 320 if (sitr != m_seams.begin()) {
Chris@1631 321 --sitr;
Chris@1631 322 }
Chris@1631 323 }
Chris@1631 324 while (sitr != m_seams.end() && sitr->first < end) {
Chris@1631 325 for (const auto &p: sitr->second) {
Chris@1631 326 found.insert(p);
Chris@1631 327 }
Chris@1631 328 ++sitr;
Chris@1631 329 }
Chris@1631 330 for (const auto &p: found) {
Chris@1631 331 auto pitr = lower_bound(m_events.begin(), m_events.end(), p);
Chris@1631 332 while (pitr != m_events.end() && *pitr == p) {
Chris@1631 333 span.push_back(p);
Chris@1631 334 ++pitr;
Chris@1631 335 }
Chris@1631 336 }
Chris@1631 337
Chris@1631 338 return span;
Chris@1631 339 }
Chris@1631 340
Chris@1631 341 EventVector
Chris@1636 342 EventSeries::getEventsWithin(sv_frame_t frame,
Chris@1654 343 sv_frame_t duration,
Chris@1654 344 int overspill) const
Chris@1636 345 {
Chris@1796 346 QMutexLocker locker(&m_mutex);
Chris@1796 347
Chris@1636 348 EventVector span;
Chris@1636 349
Chris@1636 350 const sv_frame_t start = frame;
Chris@1636 351 const sv_frame_t end = frame + duration;
Chris@1636 352
Chris@1654 353 // because we don't need to "look back" at events that end within
Chris@1654 354 // but started without, we can do this entirely from m_events.
Chris@1654 355 // The core operation is very simple, it's just overspill that
Chris@1654 356 // complicates it.
Chris@1636 357
Chris@1654 358 Events::const_iterator reference =
Chris@1654 359 lower_bound(m_events.begin(), m_events.end(), Event(start));
Chris@1654 360
Chris@1654 361 Events::const_iterator first = reference;
Chris@1654 362 for (int i = 0; i < overspill; ++i) {
Chris@1654 363 if (first == m_events.begin()) break;
Chris@1654 364 --first;
Chris@1654 365 }
Chris@1654 366 for (int i = 0; i < overspill; ++i) {
Chris@1654 367 if (first == reference) break;
Chris@1654 368 span.push_back(*first);
Chris@1654 369 ++first;
Chris@1654 370 }
Chris@1654 371
Chris@1654 372 Events::const_iterator pitr = reference;
Chris@1654 373 Events::const_iterator last = reference;
Chris@1654 374
Chris@1636 375 while (pitr != m_events.end() && pitr->getFrame() < end) {
Chris@1654 376 if (!pitr->hasDuration() ||
Chris@1654 377 (pitr->getFrame() + pitr->getDuration() <= end)) {
Chris@1636 378 span.push_back(*pitr);
Chris@1654 379 last = pitr;
Chris@1654 380 ++last;
Chris@1636 381 }
Chris@1636 382 ++pitr;
Chris@1636 383 }
Chris@1654 384
Chris@1654 385 for (int i = 0; i < overspill; ++i) {
Chris@1654 386 if (last == m_events.end()) break;
Chris@1654 387 span.push_back(*last);
Chris@1654 388 ++last;
Chris@1654 389 }
Chris@1654 390
Chris@1636 391 return span;
Chris@1636 392 }
Chris@1636 393
Chris@1636 394 EventVector
Chris@1638 395 EventSeries::getEventsStartingWithin(sv_frame_t frame,
Chris@1638 396 sv_frame_t duration) const
Chris@1638 397 {
Chris@1796 398 QMutexLocker locker(&m_mutex);
Chris@1796 399
Chris@1638 400 EventVector span;
Chris@1638 401
Chris@1638 402 const sv_frame_t start = frame;
Chris@1638 403 const sv_frame_t end = frame + duration;
Chris@1638 404
Chris@1638 405 // because we don't need to "look back" at events that started
Chris@1638 406 // earlier than the start of the given range, we can do this
Chris@1638 407 // entirely from m_events
Chris@1638 408
Chris@1638 409 auto pitr = lower_bound(m_events.begin(), m_events.end(),
Chris@1638 410 Event(start));
Chris@1638 411 while (pitr != m_events.end() && pitr->getFrame() < end) {
Chris@1638 412 span.push_back(*pitr);
Chris@1638 413 ++pitr;
Chris@1638 414 }
Chris@1638 415
Chris@1638 416 return span;
Chris@1638 417 }
Chris@1638 418
Chris@1638 419 EventVector
Chris@1631 420 EventSeries::getEventsCovering(sv_frame_t frame) const
Chris@1631 421 {
Chris@1796 422 QMutexLocker locker(&m_mutex);
Chris@1796 423
Chris@1631 424 EventVector cover;
Chris@1631 425
Chris@1631 426 // first find any zero-duration events
Chris@1631 427
Chris@1631 428 auto pitr = lower_bound(m_events.begin(), m_events.end(),
Chris@1631 429 Event(frame));
Chris@1631 430 while (pitr != m_events.end() && pitr->getFrame() == frame) {
Chris@1631 431 if (!pitr->hasDuration()) {
Chris@1631 432 cover.push_back(*pitr);
Chris@1631 433 }
Chris@1631 434 ++pitr;
Chris@1631 435 }
Chris@1631 436
Chris@1631 437 // now any non-zero-duration ones from the seam map
Chris@1631 438
Chris@1631 439 std::set<Event> found;
Chris@1631 440 auto sitr = m_seams.lower_bound(frame);
Chris@1631 441 if (sitr == m_seams.end() || sitr->first > frame) {
Chris@1631 442 if (sitr != m_seams.begin()) {
Chris@1631 443 --sitr;
Chris@1631 444 }
Chris@1631 445 }
Chris@1631 446 if (sitr != m_seams.end() && sitr->first <= frame) {
Chris@1631 447 for (const auto &p: sitr->second) {
Chris@1631 448 found.insert(p);
Chris@1631 449 }
Chris@1631 450 ++sitr;
Chris@1631 451 }
Chris@1631 452 for (const auto &p: found) {
Chris@1631 453 auto pitr = lower_bound(m_events.begin(), m_events.end(), p);
Chris@1631 454 while (pitr != m_events.end() && *pitr == p) {
Chris@1631 455 cover.push_back(p);
Chris@1631 456 ++pitr;
Chris@1631 457 }
Chris@1631 458 }
Chris@1631 459
Chris@1631 460 return cover;
Chris@1631 461 }
Chris@1631 462
Chris@1644 463 EventVector
Chris@1644 464 EventSeries::getAllEvents() const
Chris@1644 465 {
Chris@1796 466 QMutexLocker locker(&m_mutex);
Chris@1796 467
Chris@1644 468 return m_events;
Chris@1644 469 }
Chris@1644 470
Chris@1632 471 bool
Chris@1632 472 EventSeries::getEventPreceding(const Event &e, Event &preceding) const
Chris@1632 473 {
Chris@1796 474 QMutexLocker locker(&m_mutex);
Chris@1796 475
Chris@1632 476 auto pitr = lower_bound(m_events.begin(), m_events.end(), e);
Chris@1632 477 if (pitr == m_events.end() || *pitr != e) {
Chris@1632 478 return false;
Chris@1632 479 }
Chris@1632 480 if (pitr == m_events.begin()) {
Chris@1632 481 return false;
Chris@1632 482 }
Chris@1632 483 --pitr;
Chris@1632 484 preceding = *pitr;
Chris@1632 485 return true;
Chris@1632 486 }
Chris@1632 487
Chris@1632 488 bool
Chris@1632 489 EventSeries::getEventFollowing(const Event &e, Event &following) const
Chris@1632 490 {
Chris@1796 491 QMutexLocker locker(&m_mutex);
Chris@1796 492
Chris@1632 493 auto pitr = lower_bound(m_events.begin(), m_events.end(), e);
Chris@1632 494 if (pitr == m_events.end() || *pitr != e) {
Chris@1632 495 return false;
Chris@1632 496 }
Chris@1633 497 while (*pitr == e) {
Chris@1633 498 ++pitr;
Chris@1633 499 if (pitr == m_events.end()) {
Chris@1633 500 return false;
Chris@1633 501 }
Chris@1632 502 }
Chris@1632 503 following = *pitr;
Chris@1632 504 return true;
Chris@1632 505 }
Chris@1632 506
Chris@1653 507 bool
Chris@1653 508 EventSeries::getNearestEventMatching(sv_frame_t startSearchAt,
Chris@1653 509 std::function<bool(const Event &)> predicate,
Chris@1653 510 Direction direction,
Chris@1653 511 Event &found) const
Chris@1653 512 {
Chris@1796 513 QMutexLocker locker(&m_mutex);
Chris@1796 514
Chris@1653 515 auto pitr = lower_bound(m_events.begin(), m_events.end(),
Chris@1653 516 Event(startSearchAt));
Chris@1653 517
Chris@1653 518 while (true) {
Chris@1653 519
Chris@1653 520 if (direction == Backward) {
Chris@1653 521 if (pitr == m_events.begin()) {
Chris@1653 522 break;
Chris@1653 523 } else {
Chris@1653 524 --pitr;
Chris@1653 525 }
Chris@1653 526 } else {
Chris@1653 527 if (pitr == m_events.end()) {
Chris@1653 528 break;
Chris@1653 529 }
Chris@1653 530 }
Chris@1653 531
Chris@1653 532 const Event &e = *pitr;
Chris@1653 533 if (predicate(e)) {
Chris@1653 534 found = e;
Chris@1653 535 return true;
Chris@1653 536 }
Chris@1653 537
Chris@1653 538 if (direction == Forward) {
Chris@1653 539 ++pitr;
Chris@1653 540 }
Chris@1653 541 }
Chris@1653 542
Chris@1653 543 return false;
Chris@1653 544 }
Chris@1653 545
Chris@1632 546 Event
Chris@1632 547 EventSeries::getEventByIndex(int index) const
Chris@1632 548 {
Chris@1796 549 QMutexLocker locker(&m_mutex);
Chris@1798 550 if (!in_range_for(m_events, index)) {
Chris@1632 551 throw std::logic_error("index out of range");
Chris@1632 552 }
Chris@1632 553 return m_events[index];
Chris@1632 554 }
Chris@1632 555
Chris@1640 556 int
Chris@1640 557 EventSeries::getIndexForEvent(const Event &e) const
Chris@1640 558 {
Chris@1796 559 QMutexLocker locker(&m_mutex);
Chris@1640 560 auto pitr = lower_bound(m_events.begin(), m_events.end(), e);
Chris@1642 561 auto d = distance(m_events.begin(), pitr);
Chris@1642 562 if (d < 0 || d > INT_MAX) return 0;
Chris@1642 563 return int(d);
Chris@1640 564 }
Chris@1640 565
Chris@1631 566 void
Chris@1631 567 EventSeries::toXml(QTextStream &out,
Chris@1631 568 QString indent,
Chris@1631 569 QString extraAttributes) const
Chris@1631 570 {
Chris@1796 571 QMutexLocker locker(&m_mutex);
Chris@1796 572
Chris@1796 573 out << indent << QString("<dataset id=\"%1\" %2>\n")
Chris@1796 574 .arg(getExportId())
Chris@1796 575 .arg(extraAttributes);
Chris@1796 576
Chris@1796 577 for (const auto &p: m_events) {
Chris@1796 578 p.toXml(out, indent + " ", "", {});
Chris@1796 579 }
Chris@1796 580
Chris@1796 581 out << indent << "</dataset>\n";
Chris@1674 582 }
Chris@1674 583
Chris@1674 584 void
Chris@1674 585 EventSeries::toXml(QTextStream &out,
Chris@1674 586 QString indent,
Chris@1674 587 QString extraAttributes,
Chris@1674 588 Event::ExportNameOptions options) const
Chris@1674 589 {
Chris@1796 590 QMutexLocker locker(&m_mutex);
Chris@1796 591
Chris@1631 592 out << indent << QString("<dataset id=\"%1\" %2>\n")
Chris@1677 593 .arg(getExportId())
Chris@1631 594 .arg(extraAttributes);
Chris@1631 595
Chris@1631 596 for (const auto &p: m_events) {
Chris@1674 597 p.toXml(out, indent + " ", "", options);
Chris@1631 598 }
Chris@1631 599
Chris@1631 600 out << indent << "</dataset>\n";
Chris@1631 601 }
Chris@1631 602
Chris@1833 603 QVector<QString>
Chris@1833 604 EventSeries::getStringExportHeaders(DataExportOptions opts,
Chris@1833 605 Event::ExportNameOptions nopts) const
Chris@1815 606 {
Chris@1815 607 if (m_events.empty()) {
Chris@1833 608 return {};
Chris@1815 609 } else {
Chris@1833 610 return m_events.begin()->getStringExportHeaders(opts, nopts);
Chris@1815 611 }
Chris@1815 612 }
Chris@1815 613
Chris@1833 614 QVector<QVector<QString>>
Chris@1833 615 EventSeries::toStringExportRows(DataExportOptions options,
Chris@1833 616 sv_frame_t startFrame,
Chris@1833 617 sv_frame_t duration,
Chris@1833 618 sv_samplerate_t sampleRate,
Chris@1833 619 sv_frame_t resolution,
Chris@1833 620 Event fillEvent) const
Chris@1679 621 {
Chris@1796 622 QMutexLocker locker(&m_mutex);
Chris@1796 623
Chris@1833 624 QVector<QVector<QString>> rows;
Chris@1631 625
Chris@1679 626 const sv_frame_t end = startFrame + duration;
Chris@1679 627
Chris@1679 628 auto pitr = lower_bound(m_events.begin(), m_events.end(),
Chris@1679 629 Event(startFrame));
Chris@1679 630
Chris@1679 631 if (!(options & DataExportFillGaps)) {
Chris@1679 632
Chris@1679 633 while (pitr != m_events.end() && pitr->getFrame() < end) {
Chris@1833 634 rows.push_back(pitr->toStringExportRow(options, sampleRate));
Chris@1679 635 ++pitr;
Chris@1679 636 }
Chris@1679 637
Chris@1679 638 } else {
Chris@1679 639
Chris@1679 640 // find frame time of first point in range (if any)
Chris@1679 641 sv_frame_t first = startFrame;
Chris@1679 642 if (pitr != m_events.end()) {
Chris@1679 643 first = pitr->getFrame();
Chris@1679 644 }
Chris@1679 645
Chris@1679 646 // project back to first frame time in range according to
Chris@1679 647 // resolution. e.g. if f0 = 2, first = 9, resolution = 4 then
Chris@1679 648 // we start at 5 (because 1 is too early and we need to arrive
Chris@1679 649 // at 9 to match the first actual point). This method is
Chris@1679 650 // stupid but easy to understand:
Chris@1679 651 sv_frame_t f = first;
Chris@1679 652 while (f >= startFrame + resolution) f -= resolution;
Chris@1679 653
Chris@1679 654 // now progress, either writing the next point (if within
Chris@1679 655 // distance) or a default fill point
Chris@1679 656 while (f < end) {
Chris@1679 657 if (pitr != m_events.end() && pitr->getFrame() <= f) {
Chris@1833 658 rows.push_back(pitr->toStringExportRow
Chris@1833 659 (options & ~DataExportFillGaps,
Chris@1833 660 sampleRate));
Chris@1679 661 ++pitr;
Chris@1679 662 } else {
Chris@1833 663 rows.push_back(fillEvent.withFrame(f).toStringExportRow
Chris@1833 664 (options & ~DataExportFillGaps,
Chris@1833 665 sampleRate));
Chris@1679 666 }
Chris@1679 667 f += resolution;
Chris@1679 668 }
Chris@1679 669 }
Chris@1679 670
Chris@1833 671 return rows;
Chris@1679 672 }
Chris@1679 673