annotate base/EventSeries.cpp @ 1700:c1208b211d8c single-point

Ensure test fails rather than crashing if this reader doesn't get created
author Chris Cannam <cannam@all-day-breakfast.com>
date Fri, 03 May 2019 15:02:09 +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