diff base/EventSeries.h @ 1615:24dc8cb42755 single-point

Rename a number of classes and methods (including Point -> Event); comments
author Chris Cannam
date Thu, 07 Mar 2019 15:44:09 +0000
parents base/PointSeries.h@2e14a7876945
children de446dd905e6
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/base/EventSeries.h	Thu Mar 07 15:44:09 2019 +0000
@@ -0,0 +1,241 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */
+
+/*
+    Sonic Visualiser
+    An audio file viewer and annotation editor.
+    Centre for Digital Music, Queen Mary, University of London.
+    
+    This program is free software; you can redistribute it and/or
+    modify it under the terms of the GNU General Public License as
+    published by the Free Software Foundation; either version 2 of the
+    License, or (at your option) any later version.  See the file
+    COPYING included with this distribution for more information.
+*/
+
+#ifndef SV_EVENT_SERIES_H
+#define SV_EVENT_SERIES_H
+
+#include "Event.h"
+
+#include <set>
+
+//#define DEBUG_EVENT_SERIES 1
+
+/**
+ * Container storing a series of events, with or without durations,
+ * and supporting the ability to query which events span a given frame
+ * time. To that end, in addition to the series of events, it stores a
+ * series of "seam points", which are the frame positions at which the
+ * set of simultaneous events changes (i.e. an event of non-zero
+ * duration starts or ends). These are updated when event is added or
+ * removed.
+ */
+class EventSeries
+{
+public:
+    EventSeries() : m_count(0) { }
+    
+    void add(const Event &p) {
+
+        m_events.insert(p);
+        ++m_count;
+
+        if (p.hasDuration()) {
+            sv_frame_t frame = p.getFrame();
+            sv_frame_t endFrame = p.getFrame() + p.getDuration();
+
+            createSeam(frame);
+            createSeam(endFrame);
+            
+            auto i0 = m_seams.find(frame); // must succeed after createSeam
+            auto i1 = m_seams.find(endFrame); // likewise
+
+            for (auto i = i0; i != i1; ++i) {
+                if (i == m_seams.end()) {
+                    SVCERR << "ERROR: EventSeries::add: "
+                           << "reached end of seam map"
+                           << endl;
+                    break;
+                }
+                i->second.insert(p);
+            }
+        }
+
+#ifdef DEBUG_EVENT_SERIES
+        std::cerr << "after add:" << std::endl;
+        dumpEvents();
+        dumpSeams();
+#endif
+    }
+
+    void remove(const Event &p) {
+
+        // erase first itr that matches p; if there is more than one
+        // p, erase(p) would remove all of them, but we only want to
+        // remove (any) one
+        auto pitr = m_events.find(p);
+        if (pitr == m_events.end()) {
+            return; // we don't know this event
+        } else {
+            m_events.erase(pitr);
+            --m_count;
+        }
+
+        if (p.hasDuration()) {
+            sv_frame_t frame = p.getFrame();
+            sv_frame_t endFrame = p.getFrame() + p.getDuration();
+
+            auto i0 = m_seams.find(frame);
+            auto i1 = m_seams.find(endFrame);
+
+#ifdef DEBUG_EVENT_SERIES
+            // This should be impossible if we found p in m_events above
+            if (i0 == m_seams.end() || i1 == m_seams.end()) {
+                SVCERR << "ERROR: EventSeries::remove: either frame " << frame
+                       << " or endFrame " << endFrame
+                       << " for event not found in seam map: event is "
+                       << p.toXmlString() << endl;
+            }
+#endif
+
+            for (auto i = i0; i != i1; ++i) {
+                if (i == m_seams.end()) {
+                    // This can happen only if we have a negative
+                    // duration, which Event forbids, but we don't
+                    // protect against it in this class, so we'll
+                    // leave this check in
+                    SVCERR << "ERROR: EventSeries::remove: "
+                           << "reached end of seam map"
+                           << endl;
+                    break;
+                }
+                // Can't just erase(p) as that would erase all of
+                // them, if there are several identical ones
+                auto si = i->second.find(p);
+                if (si != i->second.end()) {
+                    i->second.erase(si);
+                }
+            }
+
+            // Shall we "garbage-collect" here? We could be leaving
+            // lots of empty event-sets, or consecutive identical
+            // ones, which are a pure irrelevance that take space and
+            // slow us down. But a lot depends on whether callers ever
+            // really delete anything much.
+        }
+
+#ifdef DEBUG_EVENT_SERIES
+        std::cerr << "after remove:" << std::endl;
+        dumpEvents();
+        dumpSeams();
+#endif
+    }
+
+    bool contains(const Event &p) {
+        return m_events.find(p) != m_events.end();
+    }
+
+    int count() const {
+        return m_count;
+    }
+
+    bool isEmpty() const {
+        return m_count == 0;
+    }
+    
+    void clear() {
+        m_events.clear();
+        m_seams.clear();
+        m_count = 0;
+    }
+
+    /**
+     * Retrieve all events that span the given frame. A event without
+     * duration spans a frame if its own frame is equal to it. A event
+     * with duration spans a frame if its start frame is less than or
+     * equal to it and its end frame (start + duration) is greater
+     * than it.
+     */
+    EventVector getEventsSpanning(sv_frame_t frame) const {
+        EventVector span;
+
+        // first find any zero-duration events
+        auto pitr = m_events.lower_bound(Event(frame, QString()));
+        if (pitr != m_events.end()) {
+            while (pitr->getFrame() == frame) {
+                if (!pitr->hasDuration()) {
+                    span.push_back(*pitr);
+                }
+                ++pitr;
+            }
+        }
+
+        // now any non-zero-duration ones from the seam map
+        auto sitr = m_seams.lower_bound(frame);
+        if (sitr == m_seams.end() || sitr->first > frame) {
+            if (sitr != m_seams.begin()) {
+                --sitr;
+            }                
+        }
+        if (sitr != m_seams.end() && sitr->first <= frame) {
+            for (auto p: sitr->second) {
+                span.push_back(p);
+            }
+        }
+        
+        return span;
+    }
+
+private:
+    int m_count;
+
+    typedef std::multiset<Event> EventMultiset;
+    EventMultiset m_events;
+
+    typedef std::map<sv_frame_t, std::multiset<Event>> FrameEventsMap;
+    FrameEventsMap m_seams;
+
+    /** Create a seam at the given frame, copying from the prior seam
+     *  if there is one. If a seam already exists at the given frame,
+     *  leave it untouched.
+     */
+    void createSeam(sv_frame_t frame) {
+        auto itr = m_seams.lower_bound(frame);
+        if (itr == m_seams.end() || itr->first > frame) {
+            if (itr != m_seams.begin()) {
+                --itr;
+            }
+        }
+        if (itr == m_seams.end()) {
+            m_seams[frame] = {};
+        } else if (itr->first < frame) {
+            m_seams[frame] = itr->second;
+        } else if (itr->first > frame) { // itr must be begin()
+            m_seams[frame] = {};
+        }
+    }
+
+#ifdef DEBUG_EVENT_SERIES
+    void dumpEvents() const {
+        std::cerr << "EVENTS [" << std::endl;
+        for (const auto &p: m_events) {
+            std::cerr << p.toXmlString("  ");
+        }
+        std::cerr << "]" << std::endl;
+    }
+    
+    void dumpSeams() const {
+        std::cerr << "SEAMS [" << std::endl;
+        for (const auto &s: m_seams) {
+            std::cerr << "  " << s.first << " -> {" << std::endl;
+            for (const auto &p: s.second) {
+                std::cerr << p.toXmlString("    ");
+            }
+            std::cerr << "  }" << std::endl;
+        }
+        std::cerr << "]" << std::endl;
+    }
+#endif
+};
+
+#endif