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
|