Chris@1612
|
1 /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
|
Chris@1612
|
2
|
Chris@1612
|
3 /*
|
Chris@1612
|
4 Sonic Visualiser
|
Chris@1612
|
5 An audio file viewer and annotation editor.
|
Chris@1612
|
6 Centre for Digital Music, Queen Mary, University of London.
|
Chris@1612
|
7
|
Chris@1612
|
8 This program is free software; you can redistribute it and/or
|
Chris@1612
|
9 modify it under the terms of the GNU General Public License as
|
Chris@1612
|
10 published by the Free Software Foundation; either version 2 of the
|
Chris@1612
|
11 License, or (at your option) any later version. See the file
|
Chris@1612
|
12 COPYING included with this distribution for more information.
|
Chris@1612
|
13 */
|
Chris@1612
|
14
|
Chris@1615
|
15 #ifndef SV_EVENT_SERIES_H
|
Chris@1615
|
16 #define SV_EVENT_SERIES_H
|
Chris@1612
|
17
|
Chris@1615
|
18 #include "Event.h"
|
Chris@1612
|
19
|
Chris@1612
|
20 #include <set>
|
Chris@1612
|
21
|
Chris@1624
|
22 #include <QHash>
|
Chris@1624
|
23 #include <QMap>
|
Chris@1624
|
24
|
Chris@1615
|
25 //#define DEBUG_EVENT_SERIES 1
|
Chris@1614
|
26
|
Chris@1615
|
27 /**
|
Chris@1615
|
28 * Container storing a series of events, with or without durations,
|
Chris@1616
|
29 * and supporting the ability to query which events are active at a
|
Chris@1616
|
30 * given frame or within a span of frames.
|
Chris@1616
|
31 *
|
Chris@1616
|
32 * To that end, in addition to the series of events, it stores a
|
Chris@1616
|
33 * series of "seams", which are frame positions at which the set of
|
Chris@1616
|
34 * simultaneous events changes (i.e. an event of non-zero duration
|
Chris@1616
|
35 * starts or ends) associated with a set of the events that are active
|
Chris@1616
|
36 * at or from that position. These are updated when an event is added
|
Chris@1616
|
37 * or removed.
|
Chris@1618
|
38 *
|
Chris@1618
|
39 * Performance is highly dependent on the extent of overlapping events
|
Chris@1618
|
40 * and the order in which events are added. Each event (with duration)
|
Chris@1618
|
41 * that is added requires updating all the seams within the extent of
|
Chris@1618
|
42 * that event, taking a number of ordered-set updates proportional to
|
Chris@1618
|
43 * the number of events already existing within its extent. Add events
|
Chris@1618
|
44 * in order of start frame if possible.
|
Chris@1615
|
45 */
|
Chris@1615
|
46 class EventSeries
|
Chris@1612
|
47 {
|
Chris@1612
|
48 public:
|
Chris@1615
|
49 EventSeries() : m_count(0) { }
|
Chris@1616
|
50 ~EventSeries() =default;
|
Chris@1616
|
51
|
Chris@1616
|
52 EventSeries(const EventSeries &) =default;
|
Chris@1616
|
53 EventSeries &operator=(const EventSeries &) =default;
|
Chris@1616
|
54 EventSeries &operator=(EventSeries &&) =default;
|
Chris@1616
|
55
|
Chris@1616
|
56 bool operator==(const EventSeries &other) const {
|
Chris@1616
|
57 return m_events == other.m_events;
|
Chris@1616
|
58 }
|
Chris@1612
|
59
|
Chris@1615
|
60 void add(const Event &p) {
|
Chris@1612
|
61
|
Chris@1616
|
62 ++m_events[p];
|
Chris@1612
|
63 ++m_count;
|
Chris@1612
|
64
|
Chris@1615
|
65 if (p.hasDuration()) {
|
Chris@1617
|
66
|
Chris@1617
|
67 const sv_frame_t frame = p.getFrame();
|
Chris@1617
|
68 const sv_frame_t endFrame = p.getFrame() + p.getDuration();
|
Chris@1612
|
69
|
Chris@1614
|
70 createSeam(frame);
|
Chris@1614
|
71 createSeam(endFrame);
|
Chris@1616
|
72
|
Chris@1616
|
73 // These calls must both succeed after calling createSeam above
|
Chris@1616
|
74 const auto i0 = m_seams.find(frame);
|
Chris@1616
|
75 const auto i1 = m_seams.find(endFrame);
|
Chris@1614
|
76
|
Chris@1614
|
77 for (auto i = i0; i != i1; ++i) {
|
Chris@1614
|
78 if (i == m_seams.end()) {
|
Chris@1615
|
79 SVCERR << "ERROR: EventSeries::add: "
|
Chris@1614
|
80 << "reached end of seam map"
|
Chris@1614
|
81 << endl;
|
Chris@1614
|
82 break;
|
Chris@1612
|
83 }
|
Chris@1624
|
84 i.value().insert(p);
|
Chris@1612
|
85 }
|
Chris@1614
|
86 }
|
Chris@1612
|
87
|
Chris@1615
|
88 #ifdef DEBUG_EVENT_SERIES
|
Chris@1614
|
89 std::cerr << "after add:" << std::endl;
|
Chris@1615
|
90 dumpEvents();
|
Chris@1614
|
91 dumpSeams();
|
Chris@1614
|
92 #endif
|
Chris@1612
|
93 }
|
Chris@1612
|
94
|
Chris@1615
|
95 void remove(const Event &p) {
|
Chris@1612
|
96
|
Chris@1616
|
97 // If we are removing the last (unique) example of an event,
|
Chris@1616
|
98 // then we also need to remove it from the seam map. If this
|
Chris@1616
|
99 // is only one of multiple identical events, then we don't.
|
Chris@1616
|
100 bool isUnique = false;
|
Chris@1616
|
101
|
Chris@1615
|
102 auto pitr = m_events.find(p);
|
Chris@1615
|
103 if (pitr == m_events.end()) {
|
Chris@1615
|
104 return; // we don't know this event
|
Chris@1612
|
105 } else {
|
Chris@1624
|
106 if (--(pitr.value()) == 0) {
|
Chris@1616
|
107 isUnique = true;
|
Chris@1616
|
108 m_events.erase(pitr);
|
Chris@1616
|
109 }
|
Chris@1612
|
110 --m_count;
|
Chris@1612
|
111 }
|
Chris@1612
|
112
|
Chris@1616
|
113 if (p.hasDuration() && isUnique) {
|
Chris@1616
|
114
|
Chris@1617
|
115 const sv_frame_t frame = p.getFrame();
|
Chris@1617
|
116 const sv_frame_t endFrame = p.getFrame() + p.getDuration();
|
Chris@1612
|
117
|
Chris@1616
|
118 const auto i0 = m_seams.find(frame);
|
Chris@1616
|
119 const auto i1 = m_seams.find(endFrame);
|
Chris@1614
|
120
|
Chris@1615
|
121 #ifdef DEBUG_EVENT_SERIES
|
Chris@1615
|
122 // This should be impossible if we found p in m_events above
|
Chris@1614
|
123 if (i0 == m_seams.end() || i1 == m_seams.end()) {
|
Chris@1615
|
124 SVCERR << "ERROR: EventSeries::remove: either frame " << frame
|
Chris@1614
|
125 << " or endFrame " << endFrame
|
Chris@1615
|
126 << " for event not found in seam map: event is "
|
Chris@1612
|
127 << p.toXmlString() << endl;
|
Chris@1612
|
128 }
|
Chris@1614
|
129 #endif
|
Chris@1612
|
130
|
Chris@1614
|
131 for (auto i = i0; i != i1; ++i) {
|
Chris@1614
|
132 if (i == m_seams.end()) {
|
Chris@1615
|
133 // This can happen only if we have a negative
|
Chris@1616
|
134 // duration, which Event forbids
|
Chris@1616
|
135 throw std::logic_error("unexpectedly reached end of map");
|
Chris@1616
|
136 }
|
Chris@1616
|
137
|
Chris@1624
|
138 i.value().remove(p);
|
Chris@1616
|
139 }
|
Chris@1616
|
140
|
Chris@1616
|
141 // Tidy up by removing any entries that are now identical
|
Chris@1616
|
142 // to their predecessors
|
Chris@1616
|
143
|
Chris@1616
|
144 std::vector<sv_frame_t> redundant;
|
Chris@1616
|
145
|
Chris@1616
|
146 auto pitr = m_seams.end();
|
Chris@1616
|
147 if (i0 != m_seams.begin()) {
|
Chris@1616
|
148 pitr = i0;
|
Chris@1616
|
149 --pitr;
|
Chris@1616
|
150 }
|
Chris@1616
|
151
|
Chris@1616
|
152 for (auto i = i0; i != m_seams.end(); ++i) {
|
Chris@1624
|
153 if (pitr != m_seams.end() && i.value() == pitr.value()) {
|
Chris@1624
|
154 redundant.push_back(i.key());
|
Chris@1616
|
155 }
|
Chris@1616
|
156 pitr = i;
|
Chris@1616
|
157 if (i == i1) {
|
Chris@1614
|
158 break;
|
Chris@1614
|
159 }
|
Chris@1612
|
160 }
|
Chris@1612
|
161
|
Chris@1616
|
162 for (sv_frame_t f: redundant) {
|
Chris@1624
|
163 m_seams.remove(f);
|
Chris@1616
|
164 }
|
Chris@1612
|
165 }
|
Chris@1614
|
166
|
Chris@1615
|
167 #ifdef DEBUG_EVENT_SERIES
|
Chris@1614
|
168 std::cerr << "after remove:" << std::endl;
|
Chris@1615
|
169 dumpEvents();
|
Chris@1614
|
170 dumpSeams();
|
Chris@1614
|
171 #endif
|
Chris@1612
|
172 }
|
Chris@1612
|
173
|
Chris@1615
|
174 bool contains(const Event &p) {
|
Chris@1615
|
175 return m_events.find(p) != m_events.end();
|
Chris@1612
|
176 }
|
Chris@1612
|
177
|
Chris@1612
|
178 int count() const {
|
Chris@1612
|
179 return m_count;
|
Chris@1612
|
180 }
|
Chris@1612
|
181
|
Chris@1612
|
182 bool isEmpty() const {
|
Chris@1612
|
183 return m_count == 0;
|
Chris@1612
|
184 }
|
Chris@1612
|
185
|
Chris@1612
|
186 void clear() {
|
Chris@1615
|
187 m_events.clear();
|
Chris@1612
|
188 m_seams.clear();
|
Chris@1612
|
189 m_count = 0;
|
Chris@1612
|
190 }
|
Chris@1612
|
191
|
Chris@1612
|
192 /**
|
Chris@1616
|
193 * Retrieve all events any part of which falls within the span in
|
Chris@1616
|
194 * frames defined by the given frame f and duration d.
|
Chris@1616
|
195 *
|
Chris@1616
|
196 * - An event without duration is within the span if its own frame
|
Chris@1616
|
197 * is greater than or equal to f and less than f + d.
|
Chris@1616
|
198 *
|
Chris@1616
|
199 * - An event with duration is within the span if its start frame
|
Chris@1616
|
200 * is less than f + d and its start frame plus its duration is
|
Chris@1616
|
201 * greater than f.
|
Chris@1616
|
202 *
|
Chris@1619
|
203 * Note: Passing a duration of zero is seldom useful here; you
|
Chris@1619
|
204 * probably want getEventsCovering instead. getEventsSpanning(f,
|
Chris@1619
|
205 * 0) is not equivalent to getEventsCovering(f). The latter
|
Chris@1619
|
206 * includes durationless events at f and events starting at f,
|
Chris@1619
|
207 * both of which are excluded from the former.
|
Chris@1616
|
208 */
|
Chris@1617
|
209 EventVector getEventsSpanning(sv_frame_t frame,
|
Chris@1617
|
210 sv_frame_t duration) const {
|
Chris@1616
|
211 EventVector span;
|
Chris@1616
|
212
|
Chris@1617
|
213 const sv_frame_t start = frame;
|
Chris@1617
|
214 const sv_frame_t end = frame + duration;
|
Chris@1616
|
215
|
Chris@1616
|
216 // first find any zero-duration events
|
Chris@1616
|
217
|
Chris@1624
|
218 auto pitr = m_events.lowerBound(Event(start, QString()));
|
Chris@1624
|
219 while (pitr != m_events.end() && pitr.key().getFrame() < end) {
|
Chris@1624
|
220 if (!pitr.key().hasDuration()) {
|
Chris@1624
|
221 for (int i = 0; i < pitr.value(); ++i) {
|
Chris@1624
|
222 span.push_back(pitr.key());
|
Chris@1616
|
223 }
|
Chris@1616
|
224 }
|
Chris@1616
|
225 ++pitr;
|
Chris@1616
|
226 }
|
Chris@1616
|
227
|
Chris@1616
|
228 // now any non-zero-duration ones from the seam map
|
Chris@1616
|
229
|
Chris@1616
|
230 std::set<Event> found;
|
Chris@1624
|
231 auto sitr = m_seams.lowerBound(start);
|
Chris@1624
|
232 if (sitr == m_seams.end() || sitr.key() > start) {
|
Chris@1616
|
233 if (sitr != m_seams.begin()) {
|
Chris@1616
|
234 --sitr;
|
Chris@1616
|
235 }
|
Chris@1616
|
236 }
|
Chris@1624
|
237 while (sitr != m_seams.end() && sitr.key() < end) {
|
Chris@1624
|
238 for (const auto &p: sitr.value()) {
|
Chris@1616
|
239 found.insert(p);
|
Chris@1616
|
240 }
|
Chris@1616
|
241 ++sitr;
|
Chris@1616
|
242 }
|
Chris@1616
|
243 for (const auto &p: found) {
|
Chris@1624
|
244 int n = m_events.value(p);
|
Chris@1616
|
245 if (n < 1) {
|
Chris@1616
|
246 throw std::logic_error("event is in seams but not events");
|
Chris@1616
|
247 }
|
Chris@1616
|
248 for (int i = 0; i < n; ++i) {
|
Chris@1616
|
249 span.push_back(p);
|
Chris@1616
|
250 }
|
Chris@1616
|
251 }
|
Chris@1616
|
252
|
Chris@1616
|
253 return span;
|
Chris@1616
|
254 }
|
Chris@1616
|
255
|
Chris@1616
|
256 /**
|
Chris@1616
|
257 * Retrieve all events that cover the given frame. An event without
|
Chris@1616
|
258 * duration covers a frame if its own frame is equal to it. An event
|
Chris@1616
|
259 * with duration covers a frame if its start frame is less than or
|
Chris@1612
|
260 * equal to it and its end frame (start + duration) is greater
|
Chris@1612
|
261 * than it.
|
Chris@1612
|
262 */
|
Chris@1616
|
263 EventVector getEventsCovering(sv_frame_t frame) const {
|
Chris@1616
|
264 EventVector cover;
|
Chris@1614
|
265
|
Chris@1615
|
266 // first find any zero-duration events
|
Chris@1616
|
267
|
Chris@1624
|
268 auto pitr = m_events.lowerBound(Event(frame, QString()));
|
Chris@1624
|
269 while (pitr != m_events.end() && pitr.key().getFrame() == frame) {
|
Chris@1624
|
270 if (!pitr.key().hasDuration()) {
|
Chris@1624
|
271 for (int i = 0; i < pitr.value(); ++i) {
|
Chris@1624
|
272 cover.push_back(pitr.key());
|
Chris@1614
|
273 }
|
Chris@1614
|
274 }
|
Chris@1616
|
275 ++pitr;
|
Chris@1614
|
276 }
|
Chris@1614
|
277
|
Chris@1624
|
278 // now any non-zero-duration ones from the seam map. We insert
|
Chris@1624
|
279 // these into a std::set to recover the ordering, lost by QSet
|
Chris@1616
|
280
|
Chris@1624
|
281 std::set<Event> found;
|
Chris@1624
|
282 auto sitr = m_seams.lowerBound(frame);
|
Chris@1624
|
283 if (sitr == m_seams.end() || sitr.key() > frame) {
|
Chris@1614
|
284 if (sitr != m_seams.begin()) {
|
Chris@1614
|
285 --sitr;
|
Chris@1614
|
286 }
|
Chris@1614
|
287 }
|
Chris@1624
|
288 if (sitr != m_seams.end() && sitr.key() <= frame) {
|
Chris@1624
|
289 for (const auto &p: sitr.value()) {
|
Chris@1624
|
290 found.insert(p);
|
Chris@1624
|
291 }
|
Chris@1624
|
292 ++sitr;
|
Chris@1624
|
293 }
|
Chris@1624
|
294 for (const auto &p: found) {
|
Chris@1624
|
295 int n = m_events.value(p);
|
Chris@1624
|
296 if (n < 1) {
|
Chris@1624
|
297 throw std::logic_error("event is in seams but not events");
|
Chris@1624
|
298 }
|
Chris@1624
|
299 for (int i = 0; i < n; ++i) {
|
Chris@1624
|
300 cover.push_back(p);
|
Chris@1614
|
301 }
|
Chris@1614
|
302 }
|
Chris@1614
|
303
|
Chris@1616
|
304 return cover;
|
Chris@1612
|
305 }
|
Chris@1612
|
306
|
Chris@1612
|
307 private:
|
Chris@1616
|
308 /**
|
Chris@1616
|
309 * Total number of events in the series. Will exceed
|
Chris@1616
|
310 * m_events.size() if the series contains duplicate events.
|
Chris@1616
|
311 */
|
Chris@1612
|
312 int m_count;
|
Chris@1624
|
313
|
Chris@1616
|
314 /**
|
Chris@1616
|
315 * The (ordered) Events map acts as a list of all the events in
|
Chris@1616
|
316 * the series. For reasons of backward compatibility, we have to
|
Chris@1616
|
317 * handle series containing multiple instances of identical
|
Chris@1616
|
318 * events; the int indexed against each event records the number
|
Chris@1616
|
319 * of instances of that event. We do this in preference to using a
|
Chris@1616
|
320 * multiset, in order to support the case in which we have
|
Chris@1616
|
321 * obtained a set of distinct events (from the seam container
|
Chris@1616
|
322 * below) and then need to return the correct number of each.
|
Chris@1616
|
323 *
|
Chris@1616
|
324 * Because events are immutable, we never have to move one to a
|
Chris@1616
|
325 * different "key" in this map - we only add or delete them or
|
Chris@1616
|
326 * change their counts.
|
Chris@1616
|
327 */
|
Chris@1624
|
328 typedef QMap<Event, int> Events;
|
Chris@1616
|
329 Events m_events;
|
Chris@1614
|
330
|
Chris@1616
|
331 /**
|
Chris@1616
|
332 * The FrameEventMap maps from frame number to a set of events. In
|
Chris@1616
|
333 * the seam map this is used to represent the events that are
|
Chris@1616
|
334 * active at that frame, either because they begin at that frame
|
Chris@1616
|
335 * or because they are continuing from an earlier frame. There is
|
Chris@1616
|
336 * an entry here for each frame at which an event starts or ends,
|
Chris@1616
|
337 * with the event appearing in all entries from its start time
|
Chris@1616
|
338 * onward and disappearing again at its end frame.
|
Chris@1616
|
339 *
|
Chris@1616
|
340 * Only events with duration appear in this map; point events
|
Chris@1616
|
341 * appear only in m_events.
|
Chris@1616
|
342 */
|
Chris@1624
|
343 typedef QMap<sv_frame_t, QSet<Event>> FrameEventMap;
|
Chris@1616
|
344 FrameEventMap m_seams;
|
Chris@1614
|
345
|
Chris@1614
|
346 /** Create a seam at the given frame, copying from the prior seam
|
Chris@1614
|
347 * if there is one. If a seam already exists at the given frame,
|
Chris@1614
|
348 * leave it untouched.
|
Chris@1614
|
349 */
|
Chris@1614
|
350 void createSeam(sv_frame_t frame) {
|
Chris@1624
|
351 auto itr = m_seams.lowerBound(frame);
|
Chris@1624
|
352 if (itr == m_seams.end() || itr.key() > frame) {
|
Chris@1614
|
353 if (itr != m_seams.begin()) {
|
Chris@1614
|
354 --itr;
|
Chris@1614
|
355 }
|
Chris@1614
|
356 }
|
Chris@1614
|
357 if (itr == m_seams.end()) {
|
Chris@1614
|
358 m_seams[frame] = {};
|
Chris@1624
|
359 } else if (itr.key() < frame) {
|
Chris@1624
|
360 m_seams[frame] = itr.value();
|
Chris@1624
|
361 } else if (itr.key() > frame) { // itr must be begin()
|
Chris@1614
|
362 m_seams[frame] = {};
|
Chris@1614
|
363 }
|
Chris@1614
|
364 }
|
Chris@1614
|
365
|
Chris@1615
|
366 #ifdef DEBUG_EVENT_SERIES
|
Chris@1615
|
367 void dumpEvents() const {
|
Chris@1618
|
368 std::cerr << "EVENTS (" << m_events.size() << ") [" << std::endl;
|
Chris@1616
|
369 for (const auto &i: m_events) {
|
Chris@1618
|
370 std::cerr << " " << i.second << "x " << i.first.toXmlString();
|
Chris@1614
|
371 }
|
Chris@1614
|
372 std::cerr << "]" << std::endl;
|
Chris@1614
|
373 }
|
Chris@1614
|
374
|
Chris@1614
|
375 void dumpSeams() const {
|
Chris@1618
|
376 std::cerr << "SEAMS (" << m_seams.size() << ") [" << std::endl;
|
Chris@1614
|
377 for (const auto &s: m_seams) {
|
Chris@1614
|
378 std::cerr << " " << s.first << " -> {" << std::endl;
|
Chris@1614
|
379 for (const auto &p: s.second) {
|
Chris@1614
|
380 std::cerr << p.toXmlString(" ");
|
Chris@1614
|
381 }
|
Chris@1614
|
382 std::cerr << " }" << std::endl;
|
Chris@1614
|
383 }
|
Chris@1614
|
384 std::cerr << "]" << std::endl;
|
Chris@1614
|
385 }
|
Chris@1614
|
386 #endif
|
Chris@1612
|
387 };
|
Chris@1612
|
388
|
Chris@1612
|
389 #endif
|