annotate base/EventSeries.cpp @ 1867:2654bf447a84

CSV reader tests and fixes - avoid creating null events for lines in which the timings could not be read
author Chris Cannam
date Thu, 11 Jun 2020 14:09:59 +0100
parents 21c792334c2e
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