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:
parent
9eef09145e
commit
2f31c5aa61
|
@ -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
|
||||||
|
|
|
@ -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;
|
||||||
|
};
|
|
@ -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;
|
||||||
|
};
|
|
@ -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";
|
||||||
|
|
|
@ -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;
|
||||||
};
|
};
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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,
|
||||||
|
|
||||||
/////////
|
/////////
|
||||||
|
|
|
@ -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...)
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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);
|
||||||
|
|
88
src/Timed.h
88
src/Timed.h
|
@ -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() << ")";
|
||||||
|
}
|
||||||
|
|
251
src/Timeline.h
251
src/Timeline.h
|
@ -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>
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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 (...) {
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue