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