| 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 |