annotate base/EventSeries.cpp @ 1815:c546429d4c2f

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