Refactoring

* Rewriting Timeline<T> to be sparse, i.e., allow gaps
* Added specialized subclasses BoundedTimeline<T> and ContinuousTimeline<T>
* Timed<T> and TimeRange: has-a, not is-a
* Introducing Timed<void>
This commit is contained in:
Daniel Wolf 2016-05-02 20:31:59 +02:00
parent 9eef09145e
commit 2f31c5aa61
23 changed files with 784 additions and 243 deletions

View File

@ -118,6 +118,8 @@ set(SOURCE_FILES
src/Timed.h src/Timed.h
src/TimeRange.cpp src/TimeRange.h src/TimeRange.cpp src/TimeRange.h
src/Timeline.h src/Timeline.h
src/BoundedTimeline.h
src/ContinuousTimeline.h
src/pairs.h src/pairs.h
src/Exporter.cpp src/Exporter.h src/Exporter.cpp src/Exporter.h
) )
@ -130,6 +132,8 @@ target_compile_options(rhubarb PUBLIC ${enableWarningsFlags})
set(TEST_FILES set(TEST_FILES
tests/stringToolsTests.cpp tests/stringToolsTests.cpp
tests/TimelineTests.cpp tests/TimelineTests.cpp
tests/BoundedTimelineTests.cpp
tests/ContinuousTimelineTests.cpp
tests/pairsTests.cpp tests/pairsTests.cpp
src/stringTools.cpp src/stringTools.h src/stringTools.cpp src/stringTools.h
src/Timeline.h src/Timeline.h

66
src/BoundedTimeline.h Normal file
View File

@ -0,0 +1,66 @@
#pragma once
#include "Timeline.h"
template<typename T>
class BoundedTimeline : public Timeline<T> {
using Timeline<T>::time_type;
using Timeline<T>::equals;
public:
using Timeline<T>::iterator;
using Timeline<T>::end;
explicit BoundedTimeline(TimeRange range) :
range(range)
{}
template<typename InputIterator>
BoundedTimeline(TimeRange range, InputIterator first, InputIterator last) :
range(range)
{
for (auto it = first; it != last; ++it) {
// Virtual function call in constructor. Derived constructors shouldn't call this one!
BoundedTimeline<T>::set(*it);
}
}
BoundedTimeline(TimeRange range, std::initializer_list<Timed<T>> initializerList) :
BoundedTimeline(range, initializerList.begin(), initializerList.end())
{}
TimeRange getRange() const override {
return range;
}
using Timeline<T>::set;
iterator set(Timed<T> timedValue) override {
// Exit if the value's range is completely out of bounds
if (timedValue.getEnd() <= range.getStart() || timedValue.getStart() >= range.getEnd()) {
return end();
}
// Clip the value's range to bounds
TimeRange& valueRange = timedValue.getTimeRange();
valueRange.resize(max(range.getStart(), valueRange.getStart()), min(range.getEnd(), valueRange.getEnd()));
return Timeline<T>::set(timedValue);
}
void shift(time_type offset) override {
Timeline<T>::shift(offset);
range.shift(offset);
}
bool operator==(const BoundedTimeline& rhs) const {
return Timeline<T>::equals(rhs) && range == rhs.range;
}
bool operator!=(const BoundedTimeline& rhs) const {
return !operator==(rhs);
}
private:
TimeRange range;
};

39
src/ContinuousTimeline.h Normal file
View File

@ -0,0 +1,39 @@
#pragma once
#include "BoundedTimeline.h"
template<typename T>
class ContinuousTimeline : public BoundedTimeline<T> {
public:
ContinuousTimeline(TimeRange range, T defaultValue) :
BoundedTimeline(range),
defaultValue(defaultValue)
{
// Virtual function call in constructor. Derived constructors shouldn't call this one!
ContinuousTimeline<T>::clear(range);
}
template<typename InputIterator>
ContinuousTimeline(TimeRange range, T defaultValue, InputIterator first, InputIterator last) :
ContinuousTimeline(range, defaultValue)
{
// Virtual function calls in constructor. Derived constructors shouldn't call this one!
for (auto it = first; it != last; ++it) {
ContinuousTimeline<T>::set(*it);
}
}
ContinuousTimeline(TimeRange range, T defaultValue, std::initializer_list<Timed<T>> initializerList) :
ContinuousTimeline(range, defaultValue, initializerList.begin(), initializerList.end())
{}
using BoundedTimeline<T>::clear;
void clear(const TimeRange& range) override {
set(Timed<T>(range, defaultValue));
}
private:
T defaultValue;
};

View File

@ -1,5 +1,4 @@
#include "Exporter.h" #include "Exporter.h"
#include <logging.h>
#include <boost/property_tree/ptree.hpp> #include <boost/property_tree/ptree.hpp>
#include <boost/property_tree/xml_parser.hpp> #include <boost/property_tree/xml_parser.hpp>
#include <tools.h> #include <tools.h>
@ -43,7 +42,7 @@ std::vector<Timed<Shape>> dummyShapeIfEmpty(const Timeline<Shape>& shapes) {
return result; return result;
} }
void TSVExporter::exportShapes(const boost::filesystem::path& inputFilePath, const Timeline<Shape>& shapes, std::ostream& outputStream) { void TSVExporter::exportShapes(const boost::filesystem::path& inputFilePath, const ContinuousTimeline<Shape>& shapes, std::ostream& outputStream) {
UNUSED(inputFilePath); UNUSED(inputFilePath);
// Output shapes with start times // Output shapes with start times
@ -55,7 +54,7 @@ void TSVExporter::exportShapes(const boost::filesystem::path& inputFilePath, con
outputStream << formatDuration(shapes.getRange().getEnd()) << "\t" << Shape::A << "\n"; outputStream << formatDuration(shapes.getRange().getEnd()) << "\t" << Shape::A << "\n";
} }
void XMLExporter::exportShapes(const boost::filesystem::path& inputFilePath, const Timeline<Shape>& shapes, std::ostream& outputStream) { void XMLExporter::exportShapes(const boost::filesystem::path& inputFilePath, const ContinuousTimeline<Shape>& shapes, std::ostream& outputStream) {
ptree tree; ptree tree;
// Add metadata // Add metadata
@ -94,7 +93,7 @@ string escapeJSONString(const string& s) {
return result; return result;
} }
void JSONExporter::exportShapes(const boost::filesystem::path& inputFilePath, const Timeline<Shape>& shapes, std::ostream& outputStream) { void JSONExporter::exportShapes(const boost::filesystem::path& inputFilePath, const ContinuousTimeline<Shape>& shapes, std::ostream& outputStream) {
// Export as JSON. // Export as JSON.
// I'm not using a library because the code is short enough without one and it lets me control the formatting. // I'm not using a library because the code is short enough without one and it lets me control the formatting.
outputStream << "{\n"; outputStream << "{\n";

View File

@ -1,7 +1,7 @@
#pragma once #pragma once
#include <Shape.h> #include <Shape.h>
#include <Timeline.h> #include "ContinuousTimeline.h"
#include <boost/filesystem/path.hpp> #include <boost/filesystem/path.hpp>
#include <iostream> #include <iostream>
@ -26,20 +26,20 @@ std::istream& operator>>(std::istream& stream, ExportFormat& value);
class Exporter { class Exporter {
public: public:
virtual ~Exporter() {} virtual ~Exporter() {}
virtual void exportShapes(const boost::filesystem::path& inputFilePath, const Timeline<Shape>& shapes, std::ostream& outputStream) = 0; virtual void exportShapes(const boost::filesystem::path& inputFilePath, const ContinuousTimeline<Shape>& shapes, std::ostream& outputStream) = 0;
}; };
class TSVExporter : public Exporter { class TSVExporter : public Exporter {
public: public:
void exportShapes(const boost::filesystem::path& inputFilePath, const Timeline<Shape>& shapes, std::ostream& outputStream) override; void exportShapes(const boost::filesystem::path& inputFilePath, const ContinuousTimeline<Shape>& shapes, std::ostream& outputStream) override;
}; };
class XMLExporter : public Exporter { class XMLExporter : public Exporter {
public: public:
void exportShapes(const boost::filesystem::path& inputFilePath, const Timeline<Shape>& shapes, std::ostream& outputStream) override; void exportShapes(const boost::filesystem::path& inputFilePath, const ContinuousTimeline<Shape>& shapes, std::ostream& outputStream) override;
}; };
class JSONExporter : public Exporter { class JSONExporter : public Exporter {
public: public:
void exportShapes(const boost::filesystem::path& inputFilePath, const Timeline<Shape>& shapes, std::ostream& outputStream) override; void exportShapes(const boost::filesystem::path& inputFilePath, const ContinuousTimeline<Shape>& shapes, std::ostream& outputStream) override;
}; };

View File

@ -2,6 +2,7 @@
#include "Phone.h" #include "Phone.h"
using std::string; using std::string;
using boost::optional;
PhoneConverter& PhoneConverter::get() { PhoneConverter& PhoneConverter::get() {
static PhoneConverter converter; static PhoneConverter converter;
@ -14,7 +15,6 @@ string PhoneConverter::getTypeName() {
EnumConverter<Phone>::member_data PhoneConverter::getMemberData() { EnumConverter<Phone>::member_data PhoneConverter::getMemberData() {
return member_data{ return member_data{
{ Phone::None, "None" },
{ Phone::Unknown, "Unknown" }, { Phone::Unknown, "Unknown" },
{ Phone::AO, "AO" }, { Phone::AO, "AO" },
{ Phone::AA, "AA" }, { Phone::AA, "AA" },
@ -58,8 +58,7 @@ EnumConverter<Phone>::member_data PhoneConverter::getMemberData() {
}; };
} }
boost::optional<Phone> PhoneConverter::tryParse(const string& s) { optional<Phone> PhoneConverter::tryParse(const string& s) {
if (s == "SIL") return Phone::None;
auto result = EnumConverter<Phone>::tryParse(s); auto result = EnumConverter<Phone>::tryParse(s);
return result ? result : Phone::Unknown; return result ? result : Phone::Unknown;
} }

View File

@ -4,7 +4,6 @@
// Defines a subset of the Arpabet // Defines a subset of the Arpabet
enum class Phone { enum class Phone {
None,
Unknown, Unknown,
///////// /////////

View File

@ -7,7 +7,6 @@
// For reference, see http://sunewatts.dk/lipsync/lipsync/article_02.php // For reference, see http://sunewatts.dk/lipsync/lipsync/article_02.php
// For visual examples, see https://flic.kr/s/aHsj86KR4J. Their shapes "BMP".."L" map to A..H. // For visual examples, see https://flic.kr/s/aHsj86KR4J. Their shapes "BMP".."L" map to A..H.
enum class Shape { enum class Shape {
Invalid = -1,
A, // Closed mouth (silence, M, B, P) A, // Closed mouth (silence, M, B, P)
B, // Clenched teeth (most vowels, m[e]n) B, // Clenched teeth (most vowels, m[e]n)
C, // Mouth slightly open (b[ir]d, s[ay], w[i]n...) C, // Mouth slightly open (b[ir]d, s[ay], w[i]n...)

View File

@ -4,6 +4,11 @@
using time_type = TimeRange::time_type; using time_type = TimeRange::time_type;
TimeRange TimeRange::zero() {
static TimeRange zero(time_type::zero(), time_type::zero());
return zero;
}
TimeRange::TimeRange(time_type start, time_type end) : TimeRange::TimeRange(time_type start, time_type end) :
start(start), start(start),
end(end) end(end)
@ -23,6 +28,10 @@ time_type TimeRange::getLength() const {
return end - start; return end - start;
} }
bool TimeRange::empty() const {
return start == end;
}
void TimeRange::resize(const TimeRange& newRange) { void TimeRange::resize(const TimeRange& newRange) {
start = newRange.start; start = newRange.start;
end = newRange.end; end = newRange.end;

View File

@ -5,6 +5,8 @@ class TimeRange {
public: public:
using time_type = centiseconds; using time_type = centiseconds;
static TimeRange zero();
TimeRange(time_type start, time_type end); TimeRange(time_type start, time_type end);
TimeRange(const TimeRange&) = default; TimeRange(const TimeRange&) = default;
TimeRange(TimeRange&&) = default; TimeRange(TimeRange&&) = default;
@ -15,6 +17,7 @@ public:
time_type getStart() const; time_type getStart() const;
time_type getEnd() const; time_type getEnd() const;
time_type getLength() const; time_type getLength() const;
bool empty() const;
void resize(const TimeRange& newRange); void resize(const TimeRange& newRange);
void resize(time_type start, time_type end); void resize(time_type start, time_type end);

View File

@ -4,15 +4,14 @@
#include <iostream> #include <iostream>
template<typename TValue> template<typename TValue>
class Timed : public TimeRange { class Timed {
public: public:
Timed(time_type start, time_type end, const TValue& value) : Timed(TimeRange::time_type start, TimeRange::time_type end, const TValue& value) :
TimeRange(start, end), Timed(TimeRange(start, end), value)
value(value)
{} {}
Timed(const TimeRange& timeRange, const TValue& value) : Timed(const TimeRange& timeRange, const TValue& value) :
TimeRange(timeRange), timeRange(timeRange),
value(value) value(value)
{} {}
@ -22,6 +21,26 @@ public:
Timed& operator=(const Timed&) = default; Timed& operator=(const Timed&) = default;
Timed& operator=(Timed&&) = default; Timed& operator=(Timed&&) = default;
TimeRange& getTimeRange() {
return timeRange;
}
const TimeRange& getTimeRange() const {
return timeRange;
}
TimeRange::time_type getStart() const {
return timeRange.getStart();
}
TimeRange::time_type getEnd() const {
return timeRange.getEnd();
}
void setTimeRange(const TimeRange& timeRange) {
this->timeRange = timeRange;
}
TValue& getValue() { TValue& getValue() {
return value; return value;
} }
@ -30,12 +49,12 @@ public:
return value; return value;
} }
void setValue(TValue value) { void setValue(const TValue& value) {
this->value = value; this->value = value;
} }
bool operator==(const Timed& rhs) const { bool operator==(const Timed& rhs) const {
return TimeRange::operator==(rhs) && value == rhs.value; return timeRange == rhs.timeRange && value == rhs.value;
} }
bool operator!=(const Timed& rhs) const { bool operator!=(const Timed& rhs) const {
@ -43,6 +62,7 @@ public:
} }
private: private:
TimeRange timeRange;
TValue value; TValue value;
}; };
@ -50,3 +70,57 @@ template<typename T>
std::ostream& operator<<(std::ostream& stream, const Timed<T>& timedValue) { std::ostream& operator<<(std::ostream& stream, const Timed<T>& timedValue) {
return stream << "Timed(" << timedValue.getStart() << ", " << timedValue.getEnd() << ", " << timedValue.getValue() << ")"; return stream << "Timed(" << timedValue.getStart() << ", " << timedValue.getEnd() << ", " << timedValue.getValue() << ")";
} }
template<>
class Timed<void> {
public:
Timed(TimeRange::time_type start, TimeRange::time_type end) :
Timed(TimeRange(start, end))
{}
Timed(const TimeRange& timeRange) :
timeRange(timeRange)
{}
Timed(const Timed&) = default;
Timed(Timed&&) = default;
Timed& operator=(const Timed&) = default;
Timed& operator=(Timed&&) = default;
TimeRange& getTimeRange() {
return timeRange;
}
const TimeRange& getTimeRange() const {
return timeRange;
}
TimeRange::time_type getStart() const {
return timeRange.getStart();
}
TimeRange::time_type getEnd() const {
return timeRange.getEnd();
}
void setTimeRange(const TimeRange& timeRange) {
this->timeRange = timeRange;
}
bool operator==(const Timed& rhs) const {
return timeRange == rhs.timeRange;
}
bool operator!=(const Timed& rhs) const {
return !operator==(rhs);
}
private:
TimeRange timeRange;
};
template<typename T>
std::ostream& operator<<(std::ostream& stream, const Timed<void>& timedValue) {
return stream << "Timed<void>(" << timedValue.getTimeRange().getStart() << ", " << timedValue.getTimeRange().getEnd() << ")";
}

View File

@ -1,7 +1,30 @@
#pragma once #pragma once
#include "Timed.h" #include "Timed.h"
#include <set> #include <set>
#include <algorithm> #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> template<typename T>
class Timeline { class Timeline {
@ -16,6 +39,9 @@ private:
bool operator()(const time_type& lhs, const Timed<T>& rhs) const { bool operator()(const time_type& lhs, const Timed<T>& rhs) const {
return lhs < rhs.getStart(); return lhs < rhs.getStart();
} }
bool operator()(const Timed<T>& lhs, const time_type& rhs) const {
return lhs.getStart() < rhs;
}
using is_transparent = int; using is_transparent = int;
}; };
@ -29,14 +55,28 @@ public:
class reference { class reference {
public: public:
using const_reference = const T&; operator boost::optional<const T&>() const {
auto optional = timeline.get(time);
operator const_reference() const { return optional ? optional->getValue() : boost::optional<const T&>();
return timeline.get(time).getValue();
} }
reference& operator=(const T& value) { operator boost::optional<T>() const {
timeline.set(time, time + time_type(1), value); 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; return *this;
} }
@ -52,36 +92,22 @@ public:
time_type time; time_type time;
}; };
explicit Timeline(const Timed<T> timedValue) : Timeline() {}
elements(),
range(timedValue)
{
if (timedValue.getLength() != time_type::zero()) {
elements.insert(timedValue);
}
};
explicit Timeline(const TimeRange& timeRange, const T& value = T()) :
Timeline(Timed<T>(timeRange, value))
{ }
Timeline(time_type start, time_type end, const T& value = T()) :
Timeline(Timed<T>(start, end, value))
{}
template<typename InputIterator> template<typename InputIterator>
Timeline(InputIterator first, InputIterator last, const T& value = T()) : Timeline(InputIterator first, InputIterator last) {
Timeline(getRange(first, last), value)
{
for (auto it = first; it != last; ++it) { for (auto it = first; it != last; ++it) {
set(*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, const T& value = T()) : explicit Timeline(std::initializer_list<Timed<T>> initializerList) :
Timeline(initializerList.begin(), initializerList.end(), value) Timeline(initializerList.begin(), initializerList.end())
{} {}
virtual ~Timeline() {}
bool empty() const { bool empty() const {
return elements.empty(); return elements.empty();
} }
@ -90,8 +116,10 @@ public:
return elements.size(); return elements.size();
} }
const TimeRange& getRange() const { virtual TimeRange getRange() const {
return range; return empty()
? TimeRange(time_type::zero(), time_type::zero())
: TimeRange(begin()->getStart(), rbegin()->getEnd());
} }
iterator begin() const { iterator begin() const {
@ -110,79 +138,101 @@ public:
return elements.rend(); return elements.rend();
} }
iterator find(time_type time) const { iterator find(time_type time, FindMode findMode = FindMode::SampleRight) const {
if (time < range.getStart() || time >= range.getEnd()) { switch (findMode) {
return elements.end(); 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); iterator it = elements.upper_bound(time);
--it;
// Go one element back
if (it != begin()) {
iterator left = it;
--left;
if (left->getEnd() > time) return left;
}
return it; return it;
} }
default:
throw std::invalid_argument("Unexpected find mode.");
}
}
const Timed<T>& get(time_type time) const { boost::optional<const Timed<T>&> get(time_type time) const {
iterator it = find(time); iterator it = find(time);
if (it == elements.end()) { return (it != end()) ? *it : boost::optional<const Timed<T>&>();
throw std::invalid_argument("Argument out of range.");
}
return *it;
} }
iterator set(Timed<T> timedValue) { virtual void clear(const TimeRange& range) {
// Make sure the timed value overlaps with our range // Make sure the time range is not empty
if (timedValue.getEnd() <= range.getStart() || timedValue.getStart() >= range.getEnd()) { if (range.empty()) return;
return elements.end();
}
// Make sure the timed value is not empty
if (timedValue.getLength() == time_type::zero()) {
return elements.end();
}
// Trim the timed value to our range
timedValue.resize(
std::max(timedValue.getStart(), range.getStart()),
std::min(timedValue.getEnd(), range.getEnd()));
// Extend the timed value if it touches elements with equal value
bool isFlushLeft = timedValue.getStart() == range.getStart();
if (!isFlushLeft) {
iterator elementBefore = find(timedValue.getStart() - time_type(1));
if (elementBefore->getValue() == timedValue.getValue()) {
timedValue.resize(elementBefore->getStart(), timedValue.getEnd());
}
}
bool isFlushRight = timedValue.getEnd() == range.getEnd();
if (!isFlushRight) {
iterator elementAfter = find(timedValue.getEnd());
if (elementAfter->getValue() == timedValue.getValue()) {
timedValue.resize(timedValue.getStart(), elementAfter->getEnd());
}
}
// Split overlapping elements // Split overlapping elements
splitAt(timedValue.getStart()); splitAt(range.getStart());
splitAt(timedValue.getEnd()); splitAt(range.getEnd());
// Erase overlapping elements // Erase overlapping elements
elements.erase(find(timedValue.getStart()), find(timedValue.getEnd())); 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 // Add timed value
return elements.insert(timedValue).first; return elements.insert(timedValue).first;
} }
iterator set(const TimeRange& timeRange, const T& value) { 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)); return set(Timed<T>(timeRange, value));
} }
iterator set(time_type start, time_type end, const T& 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)); return set(Timed<T>(start, end, value));
} }
reference operator[](time_type time) { template<typename TElement = T>
if (time < range.getStart() || time >= range.getEnd()) { std::enable_if_t<std::is_void<TElement>::value, iterator>
throw std::invalid_argument("Argument out of range."); set(time_type start, time_type end) {
return set(Timed<void>(start, end));
} }
reference operator[](time_type time) {
return reference(*this, time); return reference(*this, time);
} }
@ -191,13 +241,12 @@ public:
return reference(*this, time); return reference(*this, time);
} }
void shift(time_type offset) { virtual void shift(time_type offset) {
if (offset == time_type::zero()) return; if (offset == time_type::zero()) return;
range.shift(offset);
set_type newElements; set_type newElements;
for (Timed<T> element : elements) { for (Timed<T> element : elements) {
element.shift(offset); element.getTimeRange().shift(offset);
newElements.insert(element); newElements.insert(element);
} }
elements = std::move(newElements); elements = std::move(newElements);
@ -209,44 +258,34 @@ public:
Timeline& operator=(Timeline&&) = default; Timeline& operator=(Timeline&&) = default;
bool operator==(const Timeline& rhs) const { bool operator==(const Timeline& rhs) const {
return range == rhs.range && elements == rhs.elements; return equals(rhs);
} }
bool operator!=(const Timeline& rhs) const { bool operator!=(const Timeline& rhs) const {
return !operator==(rhs); return !equals(rhs);
}
protected:
bool equals(const Timeline& rhs) const {
return elements == rhs.elements;
} }
private: private:
template<typename InputIterator>
static TimeRange getRange(InputIterator first, InputIterator last) {
if (first == last) {
return TimeRange(time_type::zero(), time_type::zero());
}
time_type start = time_type::max();
time_type end = time_type::min();
for (auto it = first; it != last; ++it) {
start = std::min(start, it->getStart());
end = std::max(end, it->getEnd());
}
return TimeRange(start, end);
}
void splitAt(time_type splitTime) { void splitAt(time_type splitTime) {
if (splitTime == range.getStart() || splitTime == range.getEnd()) return;
iterator elementBefore = find(splitTime - time_type(1)); iterator elementBefore = find(splitTime - time_type(1));
iterator elementAfter = find(splitTime); iterator elementAfter = find(splitTime);
if (elementBefore != elementAfter) return; if (elementBefore != elementAfter || elementBefore == end()) return;
Timed<T> tmp = *elementBefore; Timed<T> first = *elementBefore;
Timed<T> second = *elementBefore;
elements.erase(elementBefore); elements.erase(elementBefore);
elements.insert(Timed<T>(tmp.getStart(), splitTime, tmp.getValue())); first.getTimeRange().resize(first.getStart(), splitTime);
elements.insert(Timed<T>(splitTime, tmp.getEnd(), tmp.getValue())); elements.insert(first);
second.getTimeRange().resize(splitTime, second.getEnd());
elements.insert(second);
} }
set_type elements; set_type elements;
TimeRange range;
}; };
template<typename T> template<typename T>

View File

@ -2,7 +2,6 @@
#include <string.h> #include <string.h>
#include "WaveFileReader.h" #include "WaveFileReader.h"
#include "ioTools.h" #include "ioTools.h"
#include <array>
using std::runtime_error; using std::runtime_error;
using fmt::format; using fmt::format;

View File

@ -3,6 +3,7 @@
#include <audio/SampleRateConverter.h> #include <audio/SampleRateConverter.h>
#include <boost/optional/optional.hpp> #include <boost/optional/optional.hpp>
#include <logging.h> #include <logging.h>
#include <pairs.h>
using std::numeric_limits; using std::numeric_limits;
using std::vector; using std::vector;
@ -17,7 +18,7 @@ float getRMS(AudioStream& audioStream, int maxSampleCount = numeric_limits<int>:
return sampleCount > 0 ? static_cast<float>(std::sqrt(sum / sampleCount)) : 0.0f; return sampleCount > 0 ? static_cast<float>(std::sqrt(sum / sampleCount)) : 0.0f;
} }
Timeline<bool> detectVoiceActivity(std::unique_ptr<AudioStream> audioStream) { BoundedTimeline<void> detectVoiceActivity(std::unique_ptr<AudioStream> audioStream) {
// Make sure audio stream has no DC offset // Make sure audio stream has no DC offset
audioStream = removeDCOffset(std::move(audioStream)); audioStream = removeDCOffset(std::move(audioStream));
@ -29,26 +30,32 @@ Timeline<bool> detectVoiceActivity(std::unique_ptr<AudioStream> audioStream) {
// Detect activity // Detect activity
const float rms = getRMS(*audioStream->clone(true)); const float rms = getRMS(*audioStream->clone(true));
const float cutoff = rms / 50; const float cutoff = rms / 50;
Timeline<bool> activity(audioStream->getTruncatedRange()); BoundedTimeline<void> activity(audioStream->getTruncatedRange());
for (centiseconds time = centiseconds::zero(); !audioStream->endOfStream(); ++time) { for (centiseconds time = centiseconds::zero(); !audioStream->endOfStream(); ++time) {
float currentRMS = getRMS(*audioStream, sampleRate / 100); float currentRMS = getRMS(*audioStream, sampleRate / 100);
bool active = currentRMS > cutoff; bool active = currentRMS > cutoff;
if (active) { if (active) {
activity[time] = true; activity.set(time, time + centiseconds(1));
} }
} }
// Pad each activity to prevent cropping
const centiseconds padding(3);
for (const auto& element : BoundedTimeline<void>(activity)) {
activity.set(element.getStart() - padding, element.getEnd() + padding);
}
// Fill small gaps in activity // Fill small gaps in activity
const centiseconds maxGap(10); const centiseconds maxGap(5);
for (const auto& element : Timeline<bool>(activity)) { for (const auto& pair : getPairs(activity)) {
if (!element.getValue() && element.getLength() <= maxGap) { if (pair.second.getStart() - pair.first.getEnd() <= maxGap) {
activity.set(static_cast<TimeRange>(element), true); activity.set(pair.first.getEnd(), pair.second.getStart());
} }
} }
// Log // Log
for (const auto& element : activity) { for (const auto& utterance : activity) {
logging::logTimedEvent("utterance", static_cast<TimeRange>(element), std::string()); logging::logTimedEvent("utterance", utterance.getTimeRange(), std::string());
} }
return activity; return activity;

View File

@ -1,6 +1,6 @@
#pragma once #pragma once
#include <memory> #include <memory>
#include "AudioStream.h" #include "AudioStream.h"
#include <Timeline.h> #include <BoundedTimeline.h>
Timeline<bool> detectVoiceActivity(std::unique_ptr<AudioStream> audioStream); BoundedTimeline<void> detectVoiceActivity(std::unique_ptr<AudioStream> audioStream);

View File

@ -10,8 +10,8 @@
#include "ProgressBar.h" #include "ProgressBar.h"
#include "logging.h" #include "logging.h"
#include <gsl_util.h> #include <gsl_util.h>
#include "Timeline.h"
#include "Exporter.h" #include "Exporter.h"
#include "ContinuousTimeline.h"
using std::exception; using std::exception;
using std::string; using std::string;
@ -113,7 +113,7 @@ int main(int argc, char *argv[]) {
const int columnWidth = 30; const int columnWidth = 30;
std::cerr << std::left; std::cerr << std::left;
std::cerr << std::setw(columnWidth) << "Analyzing input file"; std::cerr << std::setw(columnWidth) << "Analyzing input file";
Timeline<Phone> phones{}; BoundedTimeline<Phone> phones(TimeRange::zero());
{ {
ProgressBar progressBar; ProgressBar progressBar;
phones = detectPhones( phones = detectPhones(
@ -125,7 +125,7 @@ int main(int argc, char *argv[]) {
// Generate mouth shapes // Generate mouth shapes
std::cerr << std::setw(columnWidth) << "Generating mouth shapes"; std::cerr << std::setw(columnWidth) << "Generating mouth shapes";
Timeline<Shape> shapes = animate(phones); ContinuousTimeline<Shape> shapes = animate(phones);
std::cerr << "Done" << std::endl; std::cerr << "Done" << std::endl;
std::cerr << std::endl; std::cerr << std::endl;

View File

@ -5,7 +5,6 @@ using std::map;
Shape getShape(Phone phone) { Shape getShape(Phone phone) {
switch (phone) { switch (phone) {
case Phone::None:
case Phone::P: case Phone::P:
case Phone::B: case Phone::B:
case Phone::M: case Phone::M:
@ -67,11 +66,14 @@ Shape getShape(Phone phone) {
} }
} }
Timeline<Shape> animate(const Timeline<Phone> &phones) { ContinuousTimeline<Shape> animate(const BoundedTimeline<Phone> &phones) {
Timeline<Shape> shapes(phones.getRange()); ContinuousTimeline<Shape> shapes(phones.getRange(), Shape::A);
for (auto& timedPhone : phones) { for (const auto& timedPhone : phones) {
Timed<Shape> timedShape(static_cast<TimeRange>(timedPhone), getShape(timedPhone.getValue())); Timed<Shape> timedShape(timedPhone.getTimeRange(), getShape(timedPhone.getValue()));
shapes.set(timedShape); shapes.set(timedShape);
}
for (const auto& timedShape : shapes) {
logging::logTimedEvent("shape", timedShape); logging::logTimedEvent("shape", timedShape);
} }

View File

@ -2,6 +2,6 @@
#include "Phone.h" #include "Phone.h"
#include "Shape.h" #include "Shape.h"
#include "Timeline.h" #include "ContinuousTimeline.h"
Timeline<Shape> animate(const Timeline<Phone>& phones); ContinuousTimeline<Shape> animate(const BoundedTimeline<Phone>& phones);

View File

@ -214,7 +214,12 @@ vector<s3wid_t> getWordIds(const vector<string>& words, dict_t& dictionary) {
return result; return result;
} }
Timeline<Phone> getPhoneAlignment(const vector<s3wid_t>& wordIds, unique_ptr<AudioStream> audioStream, ps_decoder_t& recognizer, ProgressSink& progressSink) { BoundedTimeline<Phone> getPhoneAlignment(
const vector<s3wid_t>& wordIds,
unique_ptr<AudioStream> audioStream,
ps_decoder_t& recognizer,
ProgressSink& progressSink)
{
// Create alignment list // Create alignment list
lambda_unique_ptr<ps_alignment_t> alignment( lambda_unique_ptr<ps_alignment_t> alignment(
ps_alignment_init(recognizer.d2p), ps_alignment_init(recognizer.d2p),
@ -265,12 +270,14 @@ Timeline<Phone> getPhoneAlignment(const vector<s3wid_t>& wordIds, unique_ptr<Aud
// Extract phones with timestamps // Extract phones with timestamps
char** phoneNames = recognizer.dict->mdef->ciname; char** phoneNames = recognizer.dict->mdef->ciname;
Timeline<Phone> result(audioStream->getTruncatedRange()); BoundedTimeline<Phone> result(audioStream->getTruncatedRange());
for (ps_alignment_iter_t* it = ps_alignment_phones(alignment.get()); it; it = ps_alignment_iter_next(it)) { for (ps_alignment_iter_t* it = ps_alignment_phones(alignment.get()); it; it = ps_alignment_iter_next(it)) {
// Get phone // Get phone
ps_alignment_entry_t* phoneEntry = ps_alignment_iter_get(it); ps_alignment_entry_t* phoneEntry = ps_alignment_iter_get(it);
s3cipid_t phoneId = phoneEntry->id.pid.cipid; s3cipid_t phoneId = phoneEntry->id.pid.cipid;
char* phoneName = phoneNames[phoneId]; string phoneName = phoneNames[phoneId];
if (phoneName == "SIL") continue;
// Add entry // Add entry
centiseconds start(phoneEntry->start); centiseconds start(phoneEntry->start);
@ -283,14 +290,15 @@ Timeline<Phone> getPhoneAlignment(const vector<s3wid_t>& wordIds, unique_ptr<Aud
return result; return result;
} }
Timeline<Phone> detectPhones( BoundedTimeline<Phone> detectPhones(
unique_ptr<AudioStream> audioStream, unique_ptr<AudioStream> audioStream,
boost::optional<std::string> dialog, boost::optional<std::string> dialog,
ProgressSink& progressSink) ProgressSink& progressSink)
{ {
// Pocketsphinx doesn't like empty input // Pocketsphinx doesn't like empty input
if (audioStream->getTruncatedRange().getLength() == centiseconds::zero()) { TimeRange audioRange = audioStream->getTruncatedRange();
return Timeline<Phone>{}; if (audioRange.empty()) {
return BoundedTimeline<Phone>(audioRange);
} }
// Discard Pocketsphinx output // Discard Pocketsphinx output
@ -323,7 +331,7 @@ Timeline<Phone> detectPhones(
vector<s3wid_t> wordIds = getWordIds(words, *recognizer->dict); vector<s3wid_t> wordIds = getWordIds(words, *recognizer->dict);
// Align the word's phones with speech // Align the word's phones with speech
Timeline<Phone> result = getPhoneAlignment(wordIds, std::move(audioStream), *recognizer.get(), alignmentProgressSink); BoundedTimeline<Phone> result = getPhoneAlignment(wordIds, std::move(audioStream), *recognizer.get(), alignmentProgressSink);
return result; return result;
} }
catch (...) { catch (...) {

View File

@ -5,9 +5,9 @@
#include "Phone.h" #include "Phone.h"
#include "progressBar.h" #include "progressBar.h"
#include <boost/optional/optional.hpp> #include <boost/optional/optional.hpp>
#include "Timeline.h" #include "BoundedTimeline.h"
Timeline<Phone> detectPhones( BoundedTimeline<Phone> detectPhones(
std::unique_ptr<AudioStream> audioStream, std::unique_ptr<AudioStream> audioStream,
boost::optional<std::string> dialog, boost::optional<std::string> dialog,
ProgressSink& progressSink); ProgressSink& progressSink);

View File

@ -0,0 +1,105 @@
#include <gmock/gmock.h>
#include "BoundedTimeline.h"
using namespace testing;
using cs = centiseconds;
using std::vector;
using boost::optional;
using std::initializer_list;
TEST(BoundedTimeline, constructors_initializeState) {
TimeRange range(cs(-5), cs(55));
auto args = {
Timed<int>(cs(-10), cs(30), 1),
Timed<int>(cs(10), cs(40), 2),
Timed<int>(cs(50), cs(60), 3)
};
auto expected = {
Timed<int>(cs(-5), cs(10), 1),
Timed<int>(cs(10), cs(40), 2),
Timed<int>(cs(50), cs(55), 3)
};
EXPECT_THAT(
BoundedTimeline<int>(range, args.begin(), args.end()),
ElementsAreArray(expected)
);
EXPECT_THAT(
BoundedTimeline<int>(range, args),
ElementsAreArray(expected)
);
}
TEST(BoundedTimeline, empty) {
BoundedTimeline<int> empty(TimeRange(cs(0), cs(10)));
EXPECT_TRUE(empty.empty());
EXPECT_THAT(empty, IsEmpty());
BoundedTimeline<int> nonEmpty(TimeRange(cs(0), cs(10)), { Timed<int>(cs(1), cs(2), 1) });
EXPECT_FALSE(nonEmpty.empty());
EXPECT_THAT(nonEmpty, Not(IsEmpty()));
}
TEST(BoundedTimeline, getRange) {
TimeRange range(cs(0), cs(10));
BoundedTimeline<int> empty(range);
EXPECT_EQ(range, empty.getRange());
BoundedTimeline<int> nonEmpty(range, { Timed<int>(cs(1), cs(2), 1) });
EXPECT_EQ(range, nonEmpty.getRange());
}
TEST(BoundedTimeline, setAndClear) {
TimeRange range(cs(0), cs(10));
BoundedTimeline<int> timeline(range);
// Out of range
timeline.set(cs(-10), cs(-1), 1);
timeline.set(TimeRange(cs(-5), cs(-1)), 2);
timeline.set(Timed<int>(cs(10), cs(15), 3));
// Overlapping
timeline.set(cs(-2), cs(5), 4);
timeline.set(TimeRange(cs(-1), cs(1)), 5);
timeline.set(Timed<int>(cs(8), cs(12), 6));
// Within
timeline.set(cs(5), cs(9), 7);
timeline.set(TimeRange(cs(6), cs(7)), 8);
timeline.set(Timed<int>(cs(7), cs(8), 9));
auto expected = {
Timed<int>(cs(0), cs(1), 5),
Timed<int>(cs(1), cs(5), 4),
Timed<int>(cs(5), cs(6), 7),
Timed<int>(cs(6), cs(7), 8),
Timed<int>(cs(7), cs(8), 9),
Timed<int>(cs(8), cs(9), 7),
Timed<int>(cs(9), cs(10), 6)
};
EXPECT_THAT(timeline, ElementsAreArray(expected));
}
TEST(BoundedTimeline, shift) {
BoundedTimeline<int> timeline(TimeRange(cs(0), cs(10)), { { cs(1), cs(2), 1 }, { cs(2), cs(5), 2 }, { cs(7), cs(9), 3 } });
BoundedTimeline<int> expected(TimeRange(cs(2), cs(12)), { { cs(3), cs(4), 1 }, { cs(4), cs(7), 2 }, { cs(9), cs(11), 3 } });
timeline.shift(cs(2));
EXPECT_EQ(expected, timeline);
}
TEST(BoundedTimeline, equality) {
vector<BoundedTimeline<int>> timelines = {
BoundedTimeline<int>(TimeRange(cs(0), cs(10))),
BoundedTimeline<int>(TimeRange(cs(0), cs(10)), { { cs(1), cs(2), 1 } }),
BoundedTimeline<int>(TimeRange(cs(1), cs(10)), { { cs(1), cs(2), 1 } })
};
for (size_t i = 0; i < timelines.size(); ++i) {
for (size_t j = 0; j < timelines.size(); ++j) {
if (i == j) {
EXPECT_EQ(timelines[i], BoundedTimeline<int>(timelines[j])) << "i: " << i << ", j: " << j;
} else {
EXPECT_NE(timelines[i], timelines[j]) << "i: " << i << ", j: " << j;
}
}
}
}

View File

@ -0,0 +1,105 @@
#include <gmock/gmock.h>
#include "ContinuousTimeline.h"
using namespace testing;
using cs = centiseconds;
using std::vector;
using boost::optional;
using std::initializer_list;
TEST(ContinuousTimeline, constructors_initializeState) {
TimeRange range(cs(-5), cs(55));
int defaultValue = -1;
auto args = {
Timed<int>(cs(-10), cs(30), 1),
Timed<int>(cs(10), cs(40), 2),
Timed<int>(cs(50), cs(60), 3)
};
auto expected = {
Timed<int>(cs(-5), cs(10), 1),
Timed<int>(cs(10), cs(40), 2),
Timed<int>(cs(40), cs(50), defaultValue),
Timed<int>(cs(50), cs(55), 3)
};
EXPECT_THAT(
ContinuousTimeline<int>(range, defaultValue, args.begin(), args.end()),
ElementsAreArray(expected)
);
EXPECT_THAT(
ContinuousTimeline<int>(range, defaultValue, args),
ElementsAreArray(expected)
);
}
TEST(ContinuousTimeline, empty) {
ContinuousTimeline<int> empty(TimeRange(cs(10), cs(10)), -1);
EXPECT_TRUE(empty.empty());
EXPECT_THAT(empty, IsEmpty());
ContinuousTimeline<int> nonEmpty1(TimeRange(cs(0), cs(10)), -1);
EXPECT_FALSE(nonEmpty1.empty());
EXPECT_THAT(nonEmpty1, Not(IsEmpty()));
ContinuousTimeline<int> nonEmpty2(TimeRange(cs(0), cs(10)), -1, { Timed<int>(cs(1), cs(2), 1) });
EXPECT_FALSE(nonEmpty2.empty());
EXPECT_THAT(nonEmpty2, Not(IsEmpty()));
}
TEST(ContinuousTimeline, setAndClear) {
TimeRange range(cs(0), cs(10));
int defaultValue = -1;
ContinuousTimeline<int> timeline(range, defaultValue);
// Out of range
timeline.set(cs(-10), cs(-1), 1);
timeline.set(TimeRange(cs(-5), cs(-1)), 2);
timeline.set(Timed<int>(cs(10), cs(15), 3));
// Overlapping
timeline.set(cs(-2), cs(3), 4);
timeline.set(TimeRange(cs(-1), cs(1)), 5);
timeline.set(Timed<int>(cs(8), cs(12), 6));
// Within
timeline.set(cs(5), cs(9), 7);
timeline.set(TimeRange(cs(6), cs(7)), 8);
timeline.set(Timed<int>(cs(7), cs(8), 9));
auto expected = {
Timed<int>(cs(0), cs(1), 5),
Timed<int>(cs(1), cs(3), 4),
Timed<int>(cs(3), cs(5), defaultValue),
Timed<int>(cs(5), cs(6), 7),
Timed<int>(cs(6), cs(7), 8),
Timed<int>(cs(7), cs(8), 9),
Timed<int>(cs(8), cs(9), 7),
Timed<int>(cs(9), cs(10), 6)
};
EXPECT_THAT(timeline, ElementsAreArray(expected));
}
TEST(ContinuousTimeline, shift) {
ContinuousTimeline<int> timeline(TimeRange(cs(0), cs(10)), -1, { { cs(1), cs(2), 1 },{ cs(2), cs(5), 2 },{ cs(7), cs(9), 3 } });
ContinuousTimeline<int> expected(TimeRange(cs(2), cs(12)), -1, { { cs(3), cs(4), 1 },{ cs(4), cs(7), 2 },{ cs(9), cs(11), 3 } });
timeline.shift(cs(2));
EXPECT_EQ(expected, timeline);
}
TEST(ContinuousTimeline, equality) {
vector<ContinuousTimeline<int>> timelines = {
ContinuousTimeline<int>(TimeRange(cs(0), cs(10)), -1),
ContinuousTimeline<int>(TimeRange(cs(0), cs(10)), 1),
ContinuousTimeline<int>(TimeRange(cs(0), cs(10)), -1, { { cs(1), cs(2), 1 } }),
ContinuousTimeline<int>(TimeRange(cs(1), cs(10)), -1, { { cs(1), cs(2), 1 } })
};
for (size_t i = 0; i < timelines.size(); ++i) {
for (size_t j = 0; j < timelines.size(); ++j) {
if (i == j) {
EXPECT_EQ(timelines[i], ContinuousTimeline<int>(timelines[j])) << "i: " << i << ", j: " << j;
} else {
EXPECT_NE(timelines[i], timelines[j]) << "i: " << i << ", j: " << j;
}
}
}
}

View File

@ -6,20 +6,11 @@
using namespace testing; using namespace testing;
using cs = centiseconds; using cs = centiseconds;
using std::vector; using std::vector;
using boost::optional;
using std::initializer_list;
using boost::none;
TEST(Timeline, constructors_initializeState) { TEST(Timeline, constructors_initializeState) {
EXPECT_THAT(
Timeline<int>(Timed<int>(cs(10), cs(30), 42)),
ElementsAre(Timed<int>(cs(10), cs(30), 42))
);
EXPECT_THAT(
Timeline<int>(TimeRange(cs(10), cs(30)), 42),
ElementsAre(Timed<int>(cs(10), cs(30), 42))
);
EXPECT_THAT(
Timeline<int>(cs(10), cs(30), 42),
ElementsAre(Timed<int>(cs(10), cs(30), 42))
);
auto args = { auto args = {
Timed<int>(cs(-10), cs(30), 1), Timed<int>(cs(-10), cs(30), 1),
Timed<int>(cs(10), cs(40), 2), Timed<int>(cs(10), cs(40), 2),
@ -28,120 +19,176 @@ TEST(Timeline, constructors_initializeState) {
auto expected = { auto expected = {
Timed<int>(cs(-10), cs(10), 1), Timed<int>(cs(-10), cs(10), 1),
Timed<int>(cs(10), cs(40), 2), Timed<int>(cs(10), cs(40), 2),
Timed<int>(cs(40), cs(50), 42),
Timed<int>(cs(50), cs(60), 3) Timed<int>(cs(50), cs(60), 3)
}; };
EXPECT_THAT( EXPECT_THAT(
Timeline<int>(args.begin(), args.end(), 42), Timeline<int>(args.begin(), args.end()),
ElementsAreArray(expected) ElementsAreArray(expected)
); );
EXPECT_THAT( EXPECT_THAT(
Timeline<int>(args, 42), Timeline<int>(args),
ElementsAreArray(expected) ElementsAreArray(expected)
); );
} }
TEST(Timeline, constructors_throwForInvalidArgs) {
EXPECT_THROW(
Timeline<int>(cs(10), cs(9)),
std::invalid_argument
);
}
TEST(Timeline, empty) { TEST(Timeline, empty) {
Timeline<int> empty0;
EXPECT_TRUE(empty0.empty());
EXPECT_THAT(empty0, IsEmpty());
Timeline<int> empty1{}; Timeline<int> empty1{};
EXPECT_TRUE(empty1.empty()); EXPECT_TRUE(empty1.empty());
EXPECT_THAT(empty1, IsEmpty()); EXPECT_THAT(empty1, IsEmpty());
Timeline<int> empty2(cs(1), cs(1)); Timeline<int> empty2{ Timed<int>(cs(1), cs(1), 1) };
EXPECT_TRUE(empty2.empty()); EXPECT_TRUE(empty2.empty());
EXPECT_THAT(empty2, IsEmpty()); EXPECT_THAT(empty2, IsEmpty());
Timeline<int> nonEmpty(cs(1), cs(2)); Timeline<int> nonEmpty{ Timed<int>(cs(1), cs(2), 1) };
EXPECT_FALSE(nonEmpty.empty()); EXPECT_FALSE(nonEmpty.empty());
EXPECT_THAT(nonEmpty, Not(IsEmpty())); EXPECT_THAT(nonEmpty, Not(IsEmpty()));
} }
TEST(Timeline, size) { TEST(Timeline, size) {
Timeline<int> empty0;
EXPECT_EQ(0, empty0.size());
EXPECT_THAT(empty0, SizeIs(0));
Timeline<int> empty1{}; Timeline<int> empty1{};
EXPECT_EQ(0, empty1.size()); EXPECT_EQ(0, empty1.size());
EXPECT_THAT(empty1, SizeIs(0)); EXPECT_THAT(empty1, SizeIs(0));
Timeline<int> empty2(cs(1), cs(1)); Timeline<int> empty2{ Timed<int>(cs(1), cs(1), 1) };
EXPECT_EQ(0, empty2.size()); EXPECT_EQ(0, empty2.size());
EXPECT_THAT(empty2, SizeIs(0)); EXPECT_THAT(empty2, SizeIs(0));
Timeline<int> size1(cs(1), cs(10)); Timeline<int> size1{ Timed<int>(cs(1), cs(10), 1) };
EXPECT_EQ(1, size1.size()); EXPECT_EQ(1, size1.size());
EXPECT_THAT(size1, SizeIs(1)); EXPECT_THAT(size1, SizeIs(1));
Timeline<int> size2{Timed<int>(cs(-10), cs(10), 1), Timed<int>(cs(10), cs(11), 5)}; Timeline<int> size2{ Timed<int>(cs(-10), cs(10), 1), Timed<int>(cs(10), cs(11), 5) };
EXPECT_EQ(2, size2.size()); EXPECT_EQ(2, size2.size());
EXPECT_THAT(size2, SizeIs(2)); EXPECT_THAT(size2, SizeIs(2));
} }
TEST(Timeline, getRange) { TEST(Timeline, getRange) {
Timeline<int> empty0;
EXPECT_EQ(TimeRange(cs(0), cs(0)), empty0.getRange());
Timeline<int> empty1{}; Timeline<int> empty1{};
EXPECT_EQ(TimeRange(cs(0), cs(0)), empty1.getRange()); EXPECT_EQ(TimeRange(cs(0), cs(0)), empty1.getRange());
Timeline<int> empty2(cs(1), cs(1)); Timeline<int> empty2{ Timed<int>(cs(1), cs(1), 1) };
EXPECT_EQ(TimeRange(cs(1), cs(1)), empty2.getRange()); EXPECT_EQ(TimeRange(cs(0), cs(0)), empty2.getRange());
Timeline<int> nonEmpty1(cs(1), cs(10)); Timeline<int> nonEmpty1{ Timed<int>(cs(1), cs(10), 1) };
EXPECT_EQ(TimeRange(cs(1), cs(10)), nonEmpty1.getRange()); EXPECT_EQ(TimeRange(cs(1), cs(10)), nonEmpty1.getRange());
Timeline<int> nonEmpty2{ Timed<int>(cs(-10), cs(10), 1), Timed<int>(cs(10), cs(11), 5) }; Timeline<int> nonEmpty2{ Timed<int>(cs(-10), cs(5), 1), Timed<int>(cs(10), cs(11), 5) };
EXPECT_EQ(TimeRange(cs(-10), cs(11)), nonEmpty2.getRange()); EXPECT_EQ(TimeRange(cs(-10), cs(11)), nonEmpty2.getRange());
} }
TEST(Timeline, iterators) { TEST(Timeline, iterators) {
Timeline<int> timeline{ Timed<int>(cs(-5), cs(0), 10), Timed<int>(cs(5), cs(15), 9) }; Timeline<int> timeline{ Timed<int>(cs(-5), cs(0), 10), Timed<int>(cs(5), cs(15), 9) };
auto expected = { Timed<int>(cs(-5), cs(0), 10), Timed<int>(cs(0), cs(5), 0), Timed<int>(cs(5), cs(15), 9) }; auto expected = { Timed<int>(cs(-5), cs(0), 10), Timed<int>(cs(5), cs(15), 9) };
EXPECT_THAT(timeline, ElementsAreArray(expected)); EXPECT_THAT(timeline, ElementsAreArray(expected));
vector<Timed<int>> reversedActual; vector<Timed<int>> reversedActual;
std::copy(timeline.rbegin(), timeline.rend(), back_inserter(reversedActual)); copy(timeline.rbegin(), timeline.rend(), back_inserter(reversedActual));
vector<Timed<int>> reversedExpected; vector<Timed<int>> reversedExpected;
std::reverse_copy(expected.begin(), expected.end(), back_inserter(reversedExpected)); reverse_copy(expected.begin(), expected.end(), back_inserter(reversedExpected));
EXPECT_THAT(reversedActual, ElementsAreArray(reversedExpected)); EXPECT_THAT(reversedActual, ElementsAreArray(reversedExpected));
} }
void testFind(const Timeline<int>& timeline, FindMode findMode, const initializer_list<Timed<int>*> expectedResults) {
int i = -1;
for (Timed<int>* expectedResult : expectedResults) {
auto it = timeline.find(cs(++i), findMode);
if (expectedResult != nullptr) {
EXPECT_NE(it, timeline.end()) << "Timeline: " << timeline << "; findMode: " << static_cast<int>(findMode) << "; i: " << i;
if (it != timeline.end()) {
EXPECT_EQ(*expectedResult, *it) << "Timeline: " << timeline << "; findMode: " << static_cast<int>(findMode) << "; i: " << i;
}
} else {
EXPECT_EQ(timeline.end(), it) << "Timeline: " << timeline << "; findMode: " << static_cast<int>(findMode) << "; i: " << i;
}
}
}
TEST(Timeline, find) { TEST(Timeline, find) {
vector<Timed<int>> elements = { Timed<int> a = Timed<int>(cs(1), cs(2), 1);
Timed<int>(cs(1), cs(2), 1), // #0 Timed<int> b = Timed<int>(cs(2), cs(5), 2);
Timed<int>(cs(2), cs(4), 2), // #1 Timed<int> c = Timed<int>(cs(7), cs(9), 3);
Timed<int>(cs(4), cs(5), 3) // #2 Timeline<int> timeline{ a, b, c };
};
Timeline<int> timeline(elements.begin(), elements.end()); testFind(timeline, FindMode::SampleLeft, { nullptr, nullptr, &a, &b, &b, &b, nullptr, nullptr, &c, &c, nullptr });
EXPECT_EQ(timeline.end(), timeline.find(cs(-1))); testFind(timeline, FindMode::SampleRight, { nullptr, &a, &b, &b, &b, nullptr, nullptr, &c, &c, nullptr, nullptr });
EXPECT_EQ(timeline.end(), timeline.find(cs(0))); testFind(timeline, FindMode::SearchLeft, { nullptr, nullptr, &a, &b, &b, &b, &b, &b, &c, &c, &c });
EXPECT_EQ(elements[0], *timeline.find(cs(1))); testFind(timeline, FindMode::SearchRight, { &a, &a, &b, &b, &b, &c, &c, &c, &c, nullptr, nullptr });
EXPECT_EQ(elements[1], *timeline.find(cs(2)));
EXPECT_EQ(elements[1], *timeline.find(cs(3)));
EXPECT_EQ(elements[2], *timeline.find(cs(4)));
EXPECT_EQ(timeline.end(), timeline.find(cs(5)));
} }
TEST(Timeline, get) { TEST(Timeline, get) {
vector<Timed<int>> elements = { Timed<int> a = Timed<int>(cs(1), cs(2), 1);
Timed<int>(cs(1), cs(2), 1), // #0 Timed<int> b = Timed<int>(cs(2), cs(5), 2);
Timed<int>(cs(2), cs(4), 2), // #1 Timed<int> c = Timed<int>(cs(7), cs(9), 3);
Timed<int>(cs(4), cs(5), 3) // #2 Timeline<int> timeline{ a, b, c };
};
Timeline<int> timeline(elements.begin(), elements.end()); initializer_list<Timed<int>*> expectedResults = { nullptr, &a, &b, &b, &b, nullptr, nullptr, &c, &c, nullptr, nullptr };
EXPECT_THROW(timeline.get(cs(-1)), std::invalid_argument); int i = -1;
EXPECT_THROW(timeline.get(cs(0)), std::invalid_argument); for (Timed<int>* expectedResult : expectedResults) {
EXPECT_EQ(elements[0], timeline.get(cs(1))); optional<const Timed<int>&> value = timeline.get(cs(++i));
EXPECT_EQ(elements[1], timeline.get(cs(2))); if (expectedResult != nullptr) {
EXPECT_EQ(elements[1], timeline.get(cs(3))); EXPECT_TRUE(value) << "i: " << i;
EXPECT_EQ(elements[2], timeline.get(cs(4))); if (value) {
EXPECT_THROW(timeline.get(cs(5)), std::invalid_argument); EXPECT_EQ(*expectedResult, *value) << "i: " << i;
}
} else {
EXPECT_FALSE(value) << "i: " << i;
}
}
}
TEST(Timeline, clear) {
Timeline<int> original{ { cs(1), cs(2), 1 }, { cs(2), cs(5), 2 }, { cs(7), cs(9), 3 } };
{
auto timeline = original;
timeline.clear(cs(-10), cs(10));
EXPECT_THAT(timeline, IsEmpty());
}
{
auto timeline = original;
timeline.clear(cs(1), cs(2));
Timeline<int> expected{ { cs(2), cs(5), 2 }, { cs(7), cs(9), 3 } };
EXPECT_EQ(expected, timeline);
}
{
auto timeline = original;
timeline.clear(cs(3), cs(4));
Timeline<int> expected{ { cs(1), cs(2), 1 }, { cs(2), cs(3), 2 }, { cs(4), cs(5), 2}, { cs(7), cs(9), 3} };
EXPECT_EQ(expected, timeline);
}
{
auto timeline = original;
timeline.clear(cs(6), cs(8));
Timeline<int> expected{ { cs(1), cs(2), 1 }, { cs(2), cs(5), 2 }, { cs(8), cs(9), 3 } };
EXPECT_EQ(expected, timeline);
}
{
auto timeline = original;
timeline.clear(cs(8), cs(10));
Timeline<int> expected{ { cs(1), cs(2), 1 }, { cs(2), cs(5), 2 }, { cs(7), cs(8), 3 } };
EXPECT_EQ(expected, timeline);
}
} }
void testSetter(std::function<void(const Timed<int>&, Timeline<int>&)> set) { void testSetter(std::function<void(const Timed<int>&, Timeline<int>&)> set) {
const Timed<int> initial(cs(0), cs(10), 42); Timeline<int> timeline;
Timeline<int> timeline(initial); vector<optional<int>> expectedValues(20, none);
vector<int> expectedValues(10, 42);
auto newElements = { auto newElements = {
Timed<int>(cs(1), cs(2), 4), Timed<int>(cs(1), cs(2), 4),
Timed<int>(cs(3), cs(6), 4), Timed<int>(cs(3), cs(6), 4),
@ -161,35 +208,45 @@ void testSetter(std::function<void(const Timed<int>&, Timeline<int>&)> set) {
Timed<int>(cs(6), cs(8), 15), Timed<int>(cs(6), cs(8), 15),
Timed<int>(cs(6), cs(8), 16) Timed<int>(cs(6), cs(8), 16)
}; };
int newElementIndex = -1;
for (const auto& newElement : newElements) { for (const auto& newElement : newElements) {
++newElementIndex;
// Set element in timeline // Set element in timeline
set(newElement, timeline); set(newElement, timeline);
// Update expected value for every index // Update expected value for every index
cs elementStart = max(newElement.getStart(), cs(0)); cs elementStart = max(newElement.getStart(), cs(0));
cs elementEnd = min(newElement.getEnd(), cs(10)); cs elementEnd = newElement.getEnd();
for (cs t = elementStart; t < elementEnd; ++t) { for (cs t = elementStart; t < elementEnd; ++t) {
expectedValues[t.count()] = newElement.getValue(); expectedValues[t.count()] = newElement.getValue();
} }
// Check timeline via indexer // Check timeline via indexer
for (cs t = cs(0); t < cs(10); ++t) { for (cs t = cs(0); t < cs(10); ++t) {
EXPECT_EQ(expectedValues[t.count()], timeline[t]); optional<const int&> actual = timeline[t];
EXPECT_EQ(expectedValues[t.count()], actual ? optional<int>(*actual) : none);
} }
// Check timeline via iterators // Check timeline via iterators
int lastValue = std::numeric_limits<int>::min(); Timed<int> lastElement(cs::min(), cs::min(), std::numeric_limits<int>::min());
for (const auto& element : timeline) { for (const auto& element : timeline) {
// No element shound have zero-length // No element shound have zero-length
EXPECT_LT(cs(0), element.getLength()); EXPECT_LT(cs(0), element.getTimeRange().getLength());
// No two adjacent elements should have the same value; they should have been merged // No two adjacent elements should have the same value; they should have been merged
EXPECT_NE(lastValue, element.getValue()); if (element.getStart() == lastElement.getEnd()) {
lastValue = element.getValue(); EXPECT_NE(lastElement.getValue(), element.getValue());
}
lastElement = element;
// Element should match expected values // Element should match expected values
for (cs t = element.getStart(); t < element.getEnd(); ++t) { for (cs t = std::max(cs::zero(), element.getStart()); t < element.getEnd(); ++t) {
EXPECT_EQ(expectedValues[t.count()], element.getValue()); optional<int> expectedValue = expectedValues[t.count()];
EXPECT_TRUE(expectedValue) << "Index " << t.count() << " should not have a value, but is within element " << element << ". "
<< "newElementIndex: " << newElementIndex;
if (expectedValue) {
EXPECT_EQ(*expectedValue, element.getValue());
}
} }
} }
} }
@ -200,39 +257,67 @@ TEST(Timeline, set) {
timeline.set(element); timeline.set(element);
}); });
testSetter([](const Timed<int>& element, Timeline<int>& timeline) { testSetter([](const Timed<int>& element, Timeline<int>& timeline) {
timeline.set(element, element.getValue()); timeline.set(element.getTimeRange(), element.getValue());
}); });
testSetter([](const Timed<int>& element, Timeline<int>& timeline) { testSetter([](const Timed<int>& element, Timeline<int>& timeline) {
timeline.set(element.getStart(), element.getEnd(), element.getValue()); timeline.set(element.getStart(), element.getEnd(), element.getValue());
}); });
} }
TEST(Timeline, indexer_get) {
Timeline<int> timeline{ { cs(1), cs(2), 1 }, { cs(2), cs(4), 2 }, { cs(6), cs(9), 3 } };
vector<optional<int>> expectedValues{ none, 1, 2, 2, none, none, 3, 3, 3 };
for (cs t = cs(0); t < cs(9); ++t) {
{
optional<const int&> actual = timeline[t];
EXPECT_EQ(expectedValues[t.count()], actual ? optional<int>(*actual) : none);
}
{
optional<int> actual = timeline[t];
EXPECT_EQ(expectedValues[t.count()], actual ? optional<int>(*actual) : none);
}
if (expectedValues[t.count()]) {
{
const int& actual = timeline[t];
EXPECT_EQ(*expectedValues[t.count()], actual);
}
{
int actual = timeline[t];
EXPECT_EQ(*expectedValues[t.count()], actual);
}
}
}
}
TEST(Timeline, indexer_set) { TEST(Timeline, indexer_set) {
testSetter([](const Timed<int>& element, Timeline<int>& timeline) { testSetter([](const Timed<int>& element, Timeline<int>& timeline) {
for (cs t = element.getStart(); t < element.getEnd(); ++t) { for (cs t = element.getStart(); t < element.getEnd(); ++t) {
if (t >= timeline.getRange().getStart() && t < timeline.getRange().getEnd()) {
timeline[t] = element.getValue(); timeline[t] = element.getValue();
} else {
EXPECT_THROW(timeline[t] = element.getValue(), std::invalid_argument);
}
} }
}); });
} }
TEST(Timeline, shift) {
Timeline<int> timeline{ { cs(1), cs(2), 1 },{ cs(2), cs(5), 2 },{ cs(7), cs(9), 3 } };
Timeline<int> expected{ { cs(3), cs(4), 1 },{ cs(4), cs(7), 2 },{ cs(9), cs(11), 3 } };
timeline.shift(cs(2));
EXPECT_EQ(expected, timeline);
}
TEST(Timeline, equality) { TEST(Timeline, equality) {
vector<Timeline<int>> timelines = { vector<Timeline<int>> timelines = {
Timeline<int>{}, Timeline<int>{},
Timeline<int>(cs(1), cs(1)), Timeline<int>{ { cs(1), cs(2), 0 } },
Timeline<int>(cs(1), cs(2)), Timeline<int>{ { cs(1), cs(2), 1 } },
Timeline<int>(cs(-10), cs(0)) Timeline<int>{ { cs(-10), cs(0), 0 } }
}; };
for (size_t i = 0; i < timelines.size(); ++i) { for (size_t i = 0; i < timelines.size(); ++i) {
for (size_t j = 0; j < timelines.size(); ++j) { for (size_t j = 0; j < timelines.size(); ++j) {
if (i == j) { if (i == j) {
EXPECT_EQ(timelines[i], Timeline<int>(timelines[j])); EXPECT_EQ(timelines[i], Timeline<int>(timelines[j])) << "i: " << i << ", j: " << j;
} else { } else {
EXPECT_NE(timelines[i], timelines[j]); EXPECT_NE(timelines[i], timelines[j]) << "i: " << i << ", j: " << j;
} }
} }
} }