Mercurial > hg > svcore
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