Chris@1631: /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ Chris@1631: Chris@1631: /* Chris@1631: Sonic Visualiser Chris@1631: An audio file viewer and annotation editor. Chris@1631: Centre for Digital Music, Queen Mary, University of London. Chris@1631: Chris@1631: This program is free software; you can redistribute it and/or Chris@1631: modify it under the terms of the GNU General Public License as Chris@1631: published by the Free Software Foundation; either version 2 of the Chris@1631: License, or (at your option) any later version. See the file Chris@1631: COPYING included with this distribution for more information. Chris@1631: */ Chris@1631: Chris@1631: #include "EventSeries.h" Chris@1631: Chris@1796: #include Chris@1796: Chris@1833: using std::vector; Chris@1833: using std::string; Chris@1833: Chris@1796: EventSeries::EventSeries(const EventSeries &other) : Chris@1796: EventSeries(other, QMutexLocker(&other.m_mutex)) Chris@1796: { Chris@1796: } Chris@1796: Chris@1796: EventSeries::EventSeries(const EventSeries &other, const QMutexLocker &) : Chris@1796: m_events(other.m_events), Chris@1796: m_seams(other.m_seams), Chris@1796: m_finalDurationlessEventFrame(other.m_finalDurationlessEventFrame) Chris@1796: { Chris@1796: } Chris@1796: Chris@1796: EventSeries & Chris@1796: EventSeries::operator=(const EventSeries &other) Chris@1796: { Chris@1796: QMutexLocker locker(&m_mutex), otherLocker(&other.m_mutex); Chris@1796: m_events = other.m_events; Chris@1796: m_seams = other.m_seams; Chris@1796: m_finalDurationlessEventFrame = other.m_finalDurationlessEventFrame; Chris@1796: return *this; Chris@1796: } Chris@1796: Chris@1796: EventSeries & Chris@1796: EventSeries::operator=(EventSeries &&other) Chris@1796: { Chris@1796: QMutexLocker locker(&m_mutex), otherLocker(&other.m_mutex); Chris@1796: m_events = std::move(other.m_events); Chris@1796: m_seams = std::move(other.m_seams); Chris@1796: m_finalDurationlessEventFrame = std::move(other.m_finalDurationlessEventFrame); Chris@1796: return *this; Chris@1796: } Chris@1796: Chris@1796: bool Chris@1796: EventSeries::operator==(const EventSeries &other) const Chris@1796: { Chris@1796: QMutexLocker locker(&m_mutex); Chris@1796: return m_events == other.m_events; Chris@1796: } Chris@1796: Chris@1679: EventSeries Chris@1679: EventSeries::fromEvents(const EventVector &v) Chris@1679: { Chris@1679: EventSeries s; Chris@1679: for (const auto &e: v) { Chris@1679: s.add(e); Chris@1679: } Chris@1679: return s; Chris@1679: } Chris@1679: Chris@1631: bool Chris@1631: EventSeries::isEmpty() const Chris@1631: { Chris@1796: QMutexLocker locker(&m_mutex); Chris@1631: return m_events.empty(); Chris@1631: } Chris@1631: Chris@1631: int Chris@1631: EventSeries::count() const Chris@1631: { Chris@1796: QMutexLocker locker(&m_mutex); Chris@1631: if (m_events.size() > INT_MAX) { Chris@1632: throw std::logic_error("too many events"); Chris@1631: } Chris@1631: return int(m_events.size()); Chris@1631: } Chris@1631: Chris@1631: void Chris@1631: EventSeries::add(const Event &p) Chris@1631: { Chris@1796: QMutexLocker locker(&m_mutex); Chris@1796: Chris@1631: bool isUnique = true; Chris@1631: Chris@1631: auto pitr = lower_bound(m_events.begin(), m_events.end(), p); Chris@1631: if (pitr != m_events.end() && *pitr == p) { Chris@1631: isUnique = false; Chris@1631: } Chris@1631: m_events.insert(pitr, p); Chris@1631: Chris@1640: if (!p.hasDuration() && p.getFrame() > m_finalDurationlessEventFrame) { Chris@1640: m_finalDurationlessEventFrame = p.getFrame(); Chris@1640: } Chris@1640: Chris@1631: if (p.hasDuration() && isUnique) { Chris@1631: Chris@1631: const sv_frame_t frame = p.getFrame(); Chris@1631: const sv_frame_t endFrame = p.getFrame() + p.getDuration(); Chris@1631: Chris@1631: createSeam(frame); Chris@1631: createSeam(endFrame); Chris@1631: Chris@1631: // These calls must both succeed after calling createSeam above Chris@1631: const auto i0 = m_seams.find(frame); Chris@1631: const auto i1 = m_seams.find(endFrame); Chris@1631: Chris@1631: for (auto i = i0; i != i1; ++i) { Chris@1631: if (i == m_seams.end()) { Chris@1631: SVCERR << "ERROR: EventSeries::add: " Chris@1631: << "reached end of seam map" Chris@1631: << endl; Chris@1631: break; Chris@1631: } Chris@1631: i->second.push_back(p); Chris@1631: } Chris@1631: } Chris@1631: Chris@1631: #ifdef DEBUG_EVENT_SERIES Chris@1631: std::cerr << "after add:" << std::endl; Chris@1631: dumpEvents(); Chris@1631: dumpSeams(); Chris@1631: #endif Chris@1631: } Chris@1631: Chris@1631: void Chris@1631: EventSeries::remove(const Event &p) Chris@1631: { Chris@1796: QMutexLocker locker(&m_mutex); Chris@1796: Chris@1631: // If we are removing the last (unique) example of an event, Chris@1631: // then we also need to remove it from the seam map. If this Chris@1631: // is only one of multiple identical events, then we don't. Chris@1631: bool isUnique = true; Chris@1631: Chris@1631: auto pitr = lower_bound(m_events.begin(), m_events.end(), p); Chris@1631: if (pitr == m_events.end() || *pitr != p) { Chris@1631: // we don't know this event Chris@1631: return; Chris@1631: } else { Chris@1631: auto nitr = pitr; Chris@1631: ++nitr; Chris@1631: if (nitr != m_events.end() && *nitr == p) { Chris@1631: isUnique = false; Chris@1631: } Chris@1631: } Chris@1631: Chris@1631: m_events.erase(pitr); Chris@1631: Chris@1640: if (!p.hasDuration() && isUnique && Chris@1640: p.getFrame() == m_finalDurationlessEventFrame) { Chris@1640: m_finalDurationlessEventFrame = 0; Chris@1640: for (auto ritr = m_events.rbegin(); ritr != m_events.rend(); ++ritr) { Chris@1640: if (!ritr->hasDuration()) { Chris@1640: m_finalDurationlessEventFrame = ritr->getFrame(); Chris@1640: break; Chris@1640: } Chris@1640: } Chris@1640: } Chris@1640: Chris@1631: if (p.hasDuration() && isUnique) { Chris@1631: Chris@1631: const sv_frame_t frame = p.getFrame(); Chris@1631: const sv_frame_t endFrame = p.getFrame() + p.getDuration(); Chris@1631: Chris@1631: const auto i0 = m_seams.find(frame); Chris@1631: const auto i1 = m_seams.find(endFrame); Chris@1631: Chris@1631: #ifdef DEBUG_EVENT_SERIES Chris@1631: // This should be impossible if we found p in m_events above Chris@1631: if (i0 == m_seams.end() || i1 == m_seams.end()) { Chris@1631: SVCERR << "ERROR: EventSeries::remove: either frame " << frame Chris@1631: << " or endFrame " << endFrame Chris@1631: << " for event not found in seam map: event is " Chris@1631: << p.toXmlString() << endl; Chris@1631: } Chris@1631: #endif Chris@1631: Chris@1631: // Remove any and all instances of p from the seam map; we Chris@1631: // are only supposed to get here if we are removing the Chris@1631: // last instance of p from the series anyway Chris@1631: Chris@1631: for (auto i = i0; i != i1; ++i) { Chris@1631: if (i == m_seams.end()) { Chris@1631: // This can happen only if we have a negative Chris@1631: // duration, which Event forbids Chris@1631: throw std::logic_error("unexpectedly reached end of map"); Chris@1631: } Chris@1631: for (size_t j = 0; j < i->second.size(); ) { Chris@1631: if (i->second[j] == p) { Chris@1631: i->second.erase(i->second.begin() + j); Chris@1631: } else { Chris@1631: ++j; Chris@1631: } Chris@1631: } Chris@1631: } Chris@1631: Chris@1631: // Tidy up by removing any entries that are now identical Chris@1631: // to their predecessors Chris@1631: Chris@1631: std::vector redundant; Chris@1631: Chris@1631: auto pitr = m_seams.end(); Chris@1631: if (i0 != m_seams.begin()) { Chris@1631: pitr = i0; Chris@1631: --pitr; Chris@1631: } Chris@1631: Chris@1631: for (auto i = i0; i != m_seams.end(); ++i) { Chris@1631: if (pitr != m_seams.end() && Chris@1631: seamsEqual(i->second, pitr->second)) { Chris@1631: redundant.push_back(i->first); Chris@1631: } Chris@1631: pitr = i; Chris@1631: if (i == i1) { Chris@1631: break; Chris@1631: } Chris@1631: } Chris@1631: Chris@1631: for (sv_frame_t f: redundant) { Chris@1631: m_seams.erase(f); Chris@1631: } Chris@1631: Chris@1631: // And remove any empty seams from the start of the map Chris@1631: Chris@1631: while (m_seams.begin() != m_seams.end() && Chris@1631: m_seams.begin()->second.empty()) { Chris@1631: m_seams.erase(m_seams.begin()); Chris@1631: } Chris@1631: } Chris@1631: Chris@1631: #ifdef DEBUG_EVENT_SERIES Chris@1631: std::cerr << "after remove:" << std::endl; Chris@1631: dumpEvents(); Chris@1631: dumpSeams(); Chris@1631: #endif Chris@1631: } Chris@1631: Chris@1631: bool Chris@1631: EventSeries::contains(const Event &p) const Chris@1631: { Chris@1796: QMutexLocker locker(&m_mutex); Chris@1631: return binary_search(m_events.begin(), m_events.end(), p); Chris@1631: } Chris@1631: Chris@1631: void Chris@1631: EventSeries::clear() Chris@1631: { Chris@1796: QMutexLocker locker(&m_mutex); Chris@1631: m_events.clear(); Chris@1631: m_seams.clear(); Chris@1640: m_finalDurationlessEventFrame = 0; Chris@1640: } Chris@1640: Chris@1640: sv_frame_t Chris@1640: EventSeries::getStartFrame() const Chris@1640: { Chris@1796: QMutexLocker locker(&m_mutex); Chris@1640: if (m_events.empty()) return 0; Chris@1640: return m_events.begin()->getFrame(); Chris@1640: } Chris@1640: Chris@1640: sv_frame_t Chris@1640: EventSeries::getEndFrame() const Chris@1640: { Chris@1796: QMutexLocker locker(&m_mutex); Chris@1796: Chris@1640: sv_frame_t latest = 0; Chris@1640: Chris@1640: if (m_events.empty()) return latest; Chris@1640: Chris@1640: latest = m_finalDurationlessEventFrame; Chris@1640: Chris@1640: if (m_seams.empty()) return latest; Chris@1640: Chris@1640: sv_frame_t lastSeam = m_seams.rbegin()->first; Chris@1640: if (lastSeam > latest) { Chris@1640: latest = lastSeam; Chris@1640: } Chris@1640: Chris@1640: return latest; Chris@1631: } Chris@1631: Chris@1631: EventVector Chris@1631: EventSeries::getEventsSpanning(sv_frame_t frame, Chris@1631: sv_frame_t duration) const Chris@1631: { Chris@1796: QMutexLocker locker(&m_mutex); Chris@1796: Chris@1631: EventVector span; Chris@1631: Chris@1631: const sv_frame_t start = frame; Chris@1631: const sv_frame_t end = frame + duration; Chris@1631: Chris@1631: // first find any zero-duration events Chris@1631: Chris@1631: auto pitr = lower_bound(m_events.begin(), m_events.end(), Chris@1631: Event(start)); Chris@1631: while (pitr != m_events.end() && pitr->getFrame() < end) { Chris@1631: if (!pitr->hasDuration()) { Chris@1631: span.push_back(*pitr); Chris@1631: } Chris@1631: ++pitr; Chris@1631: } Chris@1631: Chris@1631: // now any non-zero-duration ones from the seam map Chris@1631: Chris@1631: std::set found; Chris@1631: auto sitr = m_seams.lower_bound(start); Chris@1631: if (sitr == m_seams.end() || sitr->first > start) { Chris@1631: if (sitr != m_seams.begin()) { Chris@1631: --sitr; Chris@1631: } Chris@1631: } Chris@1631: while (sitr != m_seams.end() && sitr->first < end) { Chris@1631: for (const auto &p: sitr->second) { Chris@1631: found.insert(p); Chris@1631: } Chris@1631: ++sitr; Chris@1631: } Chris@1631: for (const auto &p: found) { Chris@1631: auto pitr = lower_bound(m_events.begin(), m_events.end(), p); Chris@1631: while (pitr != m_events.end() && *pitr == p) { Chris@1631: span.push_back(p); Chris@1631: ++pitr; Chris@1631: } Chris@1631: } Chris@1631: Chris@1631: return span; Chris@1631: } Chris@1631: Chris@1631: EventVector Chris@1636: EventSeries::getEventsWithin(sv_frame_t frame, Chris@1654: sv_frame_t duration, Chris@1654: int overspill) const Chris@1636: { Chris@1796: QMutexLocker locker(&m_mutex); Chris@1796: Chris@1636: EventVector span; Chris@1636: Chris@1636: const sv_frame_t start = frame; Chris@1636: const sv_frame_t end = frame + duration; Chris@1636: Chris@1654: // because we don't need to "look back" at events that end within Chris@1654: // but started without, we can do this entirely from m_events. Chris@1654: // The core operation is very simple, it's just overspill that Chris@1654: // complicates it. Chris@1636: Chris@1654: Events::const_iterator reference = Chris@1654: lower_bound(m_events.begin(), m_events.end(), Event(start)); Chris@1654: Chris@1654: Events::const_iterator first = reference; Chris@1654: for (int i = 0; i < overspill; ++i) { Chris@1654: if (first == m_events.begin()) break; Chris@1654: --first; Chris@1654: } Chris@1654: for (int i = 0; i < overspill; ++i) { Chris@1654: if (first == reference) break; Chris@1654: span.push_back(*first); Chris@1654: ++first; Chris@1654: } Chris@1654: Chris@1654: Events::const_iterator pitr = reference; Chris@1654: Events::const_iterator last = reference; Chris@1654: Chris@1636: while (pitr != m_events.end() && pitr->getFrame() < end) { Chris@1654: if (!pitr->hasDuration() || Chris@1654: (pitr->getFrame() + pitr->getDuration() <= end)) { Chris@1636: span.push_back(*pitr); Chris@1654: last = pitr; Chris@1654: ++last; Chris@1636: } Chris@1636: ++pitr; Chris@1636: } Chris@1654: Chris@1654: for (int i = 0; i < overspill; ++i) { Chris@1654: if (last == m_events.end()) break; Chris@1654: span.push_back(*last); Chris@1654: ++last; Chris@1654: } Chris@1654: Chris@1636: return span; Chris@1636: } Chris@1636: Chris@1636: EventVector Chris@1638: EventSeries::getEventsStartingWithin(sv_frame_t frame, Chris@1638: sv_frame_t duration) const Chris@1638: { Chris@1796: QMutexLocker locker(&m_mutex); Chris@1796: Chris@1638: EventVector span; Chris@1638: Chris@1638: const sv_frame_t start = frame; Chris@1638: const sv_frame_t end = frame + duration; Chris@1638: Chris@1638: // because we don't need to "look back" at events that started Chris@1638: // earlier than the start of the given range, we can do this Chris@1638: // entirely from m_events Chris@1638: Chris@1638: auto pitr = lower_bound(m_events.begin(), m_events.end(), Chris@1638: Event(start)); Chris@1638: while (pitr != m_events.end() && pitr->getFrame() < end) { Chris@1638: span.push_back(*pitr); Chris@1638: ++pitr; Chris@1638: } Chris@1638: Chris@1638: return span; Chris@1638: } Chris@1638: Chris@1638: EventVector Chris@1631: EventSeries::getEventsCovering(sv_frame_t frame) const Chris@1631: { Chris@1796: QMutexLocker locker(&m_mutex); Chris@1796: Chris@1631: EventVector cover; Chris@1631: Chris@1631: // first find any zero-duration events Chris@1631: Chris@1631: auto pitr = lower_bound(m_events.begin(), m_events.end(), Chris@1631: Event(frame)); Chris@1631: while (pitr != m_events.end() && pitr->getFrame() == frame) { Chris@1631: if (!pitr->hasDuration()) { Chris@1631: cover.push_back(*pitr); Chris@1631: } Chris@1631: ++pitr; Chris@1631: } Chris@1631: Chris@1631: // now any non-zero-duration ones from the seam map Chris@1631: Chris@1631: std::set found; Chris@1631: auto sitr = m_seams.lower_bound(frame); Chris@1631: if (sitr == m_seams.end() || sitr->first > frame) { Chris@1631: if (sitr != m_seams.begin()) { Chris@1631: --sitr; Chris@1631: } Chris@1631: } Chris@1631: if (sitr != m_seams.end() && sitr->first <= frame) { Chris@1631: for (const auto &p: sitr->second) { Chris@1631: found.insert(p); Chris@1631: } Chris@1631: ++sitr; Chris@1631: } Chris@1631: for (const auto &p: found) { Chris@1631: auto pitr = lower_bound(m_events.begin(), m_events.end(), p); Chris@1631: while (pitr != m_events.end() && *pitr == p) { Chris@1631: cover.push_back(p); Chris@1631: ++pitr; Chris@1631: } Chris@1631: } Chris@1631: Chris@1631: return cover; Chris@1631: } Chris@1631: Chris@1644: EventVector Chris@1644: EventSeries::getAllEvents() const Chris@1644: { Chris@1796: QMutexLocker locker(&m_mutex); Chris@1796: Chris@1644: return m_events; Chris@1644: } Chris@1644: Chris@1632: bool Chris@1632: EventSeries::getEventPreceding(const Event &e, Event &preceding) const Chris@1632: { Chris@1796: QMutexLocker locker(&m_mutex); Chris@1796: Chris@1632: auto pitr = lower_bound(m_events.begin(), m_events.end(), e); Chris@1632: if (pitr == m_events.end() || *pitr != e) { Chris@1632: return false; Chris@1632: } Chris@1632: if (pitr == m_events.begin()) { Chris@1632: return false; Chris@1632: } Chris@1632: --pitr; Chris@1632: preceding = *pitr; Chris@1632: return true; Chris@1632: } Chris@1632: Chris@1632: bool Chris@1632: EventSeries::getEventFollowing(const Event &e, Event &following) const Chris@1632: { Chris@1796: QMutexLocker locker(&m_mutex); Chris@1796: Chris@1632: auto pitr = lower_bound(m_events.begin(), m_events.end(), e); Chris@1632: if (pitr == m_events.end() || *pitr != e) { Chris@1632: return false; Chris@1632: } Chris@1633: while (*pitr == e) { Chris@1633: ++pitr; Chris@1633: if (pitr == m_events.end()) { Chris@1633: return false; Chris@1633: } Chris@1632: } Chris@1632: following = *pitr; Chris@1632: return true; Chris@1632: } Chris@1632: Chris@1653: bool Chris@1653: EventSeries::getNearestEventMatching(sv_frame_t startSearchAt, Chris@1653: std::function predicate, Chris@1653: Direction direction, Chris@1653: Event &found) const Chris@1653: { Chris@1796: QMutexLocker locker(&m_mutex); Chris@1796: Chris@1653: auto pitr = lower_bound(m_events.begin(), m_events.end(), Chris@1653: Event(startSearchAt)); Chris@1653: Chris@1653: while (true) { Chris@1653: Chris@1653: if (direction == Backward) { Chris@1653: if (pitr == m_events.begin()) { Chris@1653: break; Chris@1653: } else { Chris@1653: --pitr; Chris@1653: } Chris@1653: } else { Chris@1653: if (pitr == m_events.end()) { Chris@1653: break; Chris@1653: } Chris@1653: } Chris@1653: Chris@1653: const Event &e = *pitr; Chris@1653: if (predicate(e)) { Chris@1653: found = e; Chris@1653: return true; Chris@1653: } Chris@1653: Chris@1653: if (direction == Forward) { Chris@1653: ++pitr; Chris@1653: } Chris@1653: } Chris@1653: Chris@1653: return false; Chris@1653: } Chris@1653: Chris@1632: Event Chris@1632: EventSeries::getEventByIndex(int index) const Chris@1632: { Chris@1796: QMutexLocker locker(&m_mutex); Chris@1798: if (!in_range_for(m_events, index)) { Chris@1632: throw std::logic_error("index out of range"); Chris@1632: } Chris@1632: return m_events[index]; Chris@1632: } Chris@1632: Chris@1640: int Chris@1640: EventSeries::getIndexForEvent(const Event &e) const Chris@1640: { Chris@1796: QMutexLocker locker(&m_mutex); Chris@1640: auto pitr = lower_bound(m_events.begin(), m_events.end(), e); Chris@1642: auto d = distance(m_events.begin(), pitr); Chris@1642: if (d < 0 || d > INT_MAX) return 0; Chris@1642: return int(d); Chris@1640: } Chris@1640: Chris@1631: void Chris@1631: EventSeries::toXml(QTextStream &out, Chris@1631: QString indent, Chris@1631: QString extraAttributes) const Chris@1631: { Chris@1796: QMutexLocker locker(&m_mutex); Chris@1796: Chris@1796: out << indent << QString("\n") Chris@1796: .arg(getExportId()) Chris@1796: .arg(extraAttributes); Chris@1796: Chris@1796: for (const auto &p: m_events) { Chris@1796: p.toXml(out, indent + " ", "", {}); Chris@1796: } Chris@1796: Chris@1796: out << indent << "\n"; Chris@1674: } Chris@1674: Chris@1674: void Chris@1674: EventSeries::toXml(QTextStream &out, Chris@1674: QString indent, Chris@1674: QString extraAttributes, Chris@1674: Event::ExportNameOptions options) const Chris@1674: { Chris@1796: QMutexLocker locker(&m_mutex); Chris@1796: Chris@1631: out << indent << QString("\n") Chris@1677: .arg(getExportId()) Chris@1631: .arg(extraAttributes); Chris@1631: Chris@1631: for (const auto &p: m_events) { Chris@1674: p.toXml(out, indent + " ", "", options); Chris@1631: } Chris@1631: Chris@1631: out << indent << "\n"; Chris@1631: } Chris@1631: Chris@1833: QVector Chris@1833: EventSeries::getStringExportHeaders(DataExportOptions opts, Chris@1833: Event::ExportNameOptions nopts) const Chris@1815: { Chris@1815: if (m_events.empty()) { Chris@1833: return {}; Chris@1815: } else { Chris@1833: return m_events.begin()->getStringExportHeaders(opts, nopts); Chris@1815: } Chris@1815: } Chris@1815: Chris@1833: QVector> Chris@1833: EventSeries::toStringExportRows(DataExportOptions options, Chris@1833: sv_frame_t startFrame, Chris@1833: sv_frame_t duration, Chris@1833: sv_samplerate_t sampleRate, Chris@1833: sv_frame_t resolution, Chris@1833: Event fillEvent) const Chris@1679: { Chris@1796: QMutexLocker locker(&m_mutex); Chris@1796: Chris@1833: QVector> rows; Chris@1631: Chris@1679: const sv_frame_t end = startFrame + duration; Chris@1679: Chris@1679: auto pitr = lower_bound(m_events.begin(), m_events.end(), Chris@1679: Event(startFrame)); Chris@1679: Chris@1679: if (!(options & DataExportFillGaps)) { Chris@1679: Chris@1679: while (pitr != m_events.end() && pitr->getFrame() < end) { Chris@1833: rows.push_back(pitr->toStringExportRow(options, sampleRate)); Chris@1679: ++pitr; Chris@1679: } Chris@1679: Chris@1679: } else { Chris@1679: Chris@1679: // find frame time of first point in range (if any) Chris@1679: sv_frame_t first = startFrame; Chris@1679: if (pitr != m_events.end()) { Chris@1679: first = pitr->getFrame(); Chris@1679: } Chris@1679: Chris@1679: // project back to first frame time in range according to Chris@1679: // resolution. e.g. if f0 = 2, first = 9, resolution = 4 then Chris@1679: // we start at 5 (because 1 is too early and we need to arrive Chris@1679: // at 9 to match the first actual point). This method is Chris@1679: // stupid but easy to understand: Chris@1679: sv_frame_t f = first; Chris@1679: while (f >= startFrame + resolution) f -= resolution; Chris@1679: Chris@1679: // now progress, either writing the next point (if within Chris@1679: // distance) or a default fill point Chris@1679: while (f < end) { Chris@1679: if (pitr != m_events.end() && pitr->getFrame() <= f) { Chris@1833: rows.push_back(pitr->toStringExportRow Chris@1833: (options & ~DataExportFillGaps, Chris@1833: sampleRate)); Chris@1679: ++pitr; Chris@1679: } else { Chris@1833: rows.push_back(fillEvent.withFrame(f).toStringExportRow Chris@1833: (options & ~DataExportFillGaps, Chris@1833: sampleRate)); Chris@1679: } Chris@1679: f += resolution; Chris@1679: } Chris@1679: } Chris@1679: Chris@1833: return rows; Chris@1679: } Chris@1679: