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