annotate base/EventSeries.h @ 1617:bdc19a09a1f9 single-point

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