rhubarb-lip-sync/src/Timeline.h

302 lines
7.5 KiB
C++

#pragma once
#include "Timed.h"
#include <set>
#include <boost/optional.hpp>
#include <type_traits>
#include "tools.h"
enum class FindMode {
SampleLeft,
SampleRight,
SearchLeft,
SearchRight
};
namespace internal {
template<typename T>
bool valueEquals(const Timed<T>& lhs, const Timed<T>& rhs) {
return lhs.getValue() == rhs.getValue();
}
template<>
inline bool valueEquals<void>(const Timed<void>& lhs, const Timed<void>& rhs) {
UNUSED(lhs);
UNUSED(rhs);
return true;
}
}
template<typename T>
class Timeline {
public:
using time_type = TimeRange::time_type;
private:
struct compare {
bool operator()(const Timed<T>& lhs, const Timed<T>& rhs) const {
return lhs.getStart() < rhs.getStart();
}
bool operator()(const time_type& lhs, const Timed<T>& rhs) const {
return lhs < rhs.getStart();
}
bool operator()(const Timed<T>& lhs, const time_type& rhs) const {
return lhs.getStart() < rhs;
}
using is_transparent = int;
};
public:
using set_type = std::set<Timed<T>, compare>;
using const_iterator = typename set_type::const_iterator;
using iterator = const_iterator;
using reverse_iterator = typename set_type::reverse_iterator;
using size_type = size_t;
using value_type = Timed<T>;
class reference {
public:
operator boost::optional<const T&>() const {
auto optional = timeline.get(time);
return optional ? optional->getValue() : boost::optional<const T&>();
}
operator boost::optional<T>() const {
auto optional = timeline.get(time);
return optional ? optional->getValue() : boost::optional<T>();
}
operator const T&() const {
auto optional = timeline.get(time);
assert(optional);
return optional->getValue();
}
reference& operator=(boost::optional<const T&> value) {
if (value) {
timeline.set(time, time + time_type(1), *value);
} else {
timeline.clear(time, time + time_type(1));
}
return *this;
}
private:
friend class Timeline;
reference(Timeline& timeline, time_type time) :
timeline(timeline),
time(time)
{}
Timeline& timeline;
time_type time;
};
Timeline() {}
template<typename InputIterator>
Timeline(InputIterator first, InputIterator last) {
for (auto it = first; it != last; ++it) {
// Virtual function call in constructor. Derived constructors don't call this one.
Timeline<T>::set(*it);
}
}
explicit Timeline(std::initializer_list<Timed<T>> initializerList) :
Timeline(initializerList.begin(), initializerList.end())
{}
virtual ~Timeline() {}
bool empty() const {
return elements.empty();
}
size_type size() const {
return elements.size();
}
virtual TimeRange getRange() const {
return empty()
? TimeRange(time_type::zero(), time_type::zero())
: TimeRange(begin()->getStart(), rbegin()->getEnd());
}
iterator begin() const {
return elements.begin();
}
iterator end() const {
return elements.end();
}
reverse_iterator rbegin() const {
return elements.rbegin();
}
reverse_iterator rend() const {
return elements.rend();
}
iterator find(time_type time, FindMode findMode = FindMode::SampleRight) const {
switch (findMode) {
case FindMode::SampleLeft: {
iterator left = find(time, FindMode::SearchLeft);
return left != end() && left->getEnd() >= time ? left : end();
}
case FindMode::SampleRight: {
iterator right = find(time, FindMode::SearchRight);
return right != end() && right->getStart() <= time ? right : end();
}
case FindMode::SearchLeft: {
// Get first element starting >= time
iterator it = elements.lower_bound(time);
// Go one element back
return it != begin() ? --it : end();
}
case FindMode::SearchRight: {
// Get first element starting > time
iterator it = elements.upper_bound(time);
// Go one element back
if (it != begin()) {
iterator left = it;
--left;
if (left->getEnd() > time) return left;
}
return it;
}
default:
throw std::invalid_argument("Unexpected find mode.");
}
}
boost::optional<const Timed<T>&> get(time_type time) const {
iterator it = find(time);
return (it != end()) ? *it : boost::optional<const Timed<T>&>();
}
virtual void clear(const TimeRange& range) {
// Make sure the time range is not empty
if (range.empty()) return;
// Split overlapping elements
splitAt(range.getStart());
splitAt(range.getEnd());
// Erase overlapping elements
elements.erase(find(range.getStart(), FindMode::SearchRight), find(range.getEnd(), FindMode::SearchRight));
}
void clear(time_type start, time_type end) {
clear(TimeRange(start, end));
}
virtual iterator set(Timed<T> timedValue) {
// Make sure the timed value is not empty
if (timedValue.getTimeRange().empty()) {
return end();
}
// Extend the timed value if it touches elements with equal value
iterator elementBefore = find(timedValue.getStart(), FindMode::SampleLeft);
if (elementBefore != end() && ::internal::valueEquals(*elementBefore, timedValue)) {
timedValue.getTimeRange().resize(elementBefore->getStart(), timedValue.getEnd());
}
iterator elementAfter = find(timedValue.getEnd(), FindMode::SampleRight);
if (elementAfter != end() && ::internal::valueEquals(*elementAfter, timedValue)) {
timedValue.getTimeRange().resize(timedValue.getStart(), elementAfter->getEnd());
}
// Erase overlapping elements
Timeline::clear(timedValue.getTimeRange());
// Add timed value
return elements.insert(timedValue).first;
}
template<typename TElement = T>
iterator set(const TimeRange& timeRange, const std::enable_if_t<!std::is_void<TElement>::value, T>& value) {
return set(Timed<T>(timeRange, value));
}
template<typename TElement = T>
iterator set(time_type start, time_type end, const std::enable_if_t<!std::is_void<TElement>::value, T>& value) {
return set(Timed<T>(start, end, value));
}
template<typename TElement = T>
std::enable_if_t<std::is_void<TElement>::value, iterator>
set(time_type start, time_type end) {
return set(Timed<void>(start, end));
}
reference operator[](time_type time) {
return reference(*this, time);
}
// ReSharper disable once CppConstValueFunctionReturnType
const reference operator[](time_type time) const {
return reference(*this, time);
}
virtual void shift(time_type offset) {
if (offset == time_type::zero()) return;
set_type newElements;
for (Timed<T> element : elements) {
element.getTimeRange().shift(offset);
newElements.insert(element);
}
elements = std::move(newElements);
}
Timeline(const Timeline&) = default;
Timeline(Timeline&&) = default;
Timeline& operator=(const Timeline&) = default;
Timeline& operator=(Timeline&&) = default;
bool operator==(const Timeline& rhs) const {
return equals(rhs);
}
bool operator!=(const Timeline& rhs) const {
return !equals(rhs);
}
protected:
bool equals(const Timeline& rhs) const {
return elements == rhs.elements;
}
private:
void splitAt(time_type splitTime) {
iterator elementBefore = find(splitTime - time_type(1));
iterator elementAfter = find(splitTime);
if (elementBefore != elementAfter || elementBefore == end()) return;
Timed<T> first = *elementBefore;
Timed<T> second = *elementBefore;
elements.erase(elementBefore);
first.getTimeRange().resize(first.getStart(), splitTime);
elements.insert(first);
second.getTimeRange().resize(splitTime, second.getEnd());
elements.insert(second);
}
set_type elements;
};
template<typename T>
std::ostream& operator<<(std::ostream& stream, const Timeline<T>& timeline) {
stream << "Timeline{";
bool isFirst = true;
for (auto element : timeline) {
if (!isFirst) stream << ", ";
isFirst = false;
stream << element;
}
return stream << "}";
}