annotate base/EventSeries.cpp @ 1752:6d09d68165a4 by-id

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