From c65c8b4eb390c8992f1973f55d9ffcf3f8669c52 Mon Sep 17 00:00:00 2001 From: Daniel Wolf Date: Fri, 5 Aug 2016 19:34:57 +0200 Subject: [PATCH] Better animation of pauses in speech --- src/Shape.h | 4 ++++ src/mouthAnimation.cpp | 36 +++++++++++++++++++++++++++++++ src/tools.h | 49 +++++++++++++++++++++++++++++++++++++++++- 3 files changed, 88 insertions(+), 1 deletion(-) diff --git a/src/Shape.h b/src/Shape.h index 53e6ada..d7bef40 100644 --- a/src/Shape.h +++ b/src/Shape.h @@ -29,3 +29,7 @@ protected: std::ostream& operator<<(std::ostream& stream, Shape value); std::istream& operator>>(std::istream& stream, Shape& value); + +inline bool isClosed(Shape shape) { + return shape == Shape::A || shape == Shape::X; +} \ No newline at end of file diff --git a/src/mouthAnimation.cpp b/src/mouthAnimation.cpp index 02165af..6872873 100644 --- a/src/mouthAnimation.cpp +++ b/src/mouthAnimation.cpp @@ -164,6 +164,36 @@ Timeline createTweens(ContinuousTimeline shapes) { return tweens; } +Timeline animatePauses(const ContinuousTimeline& shapes) { + Timeline result; + + // Don't close mouth for short pauses + for (const auto& timedShape : shapes) { + if (timedShape.getValue() != X) continue; + + const centiseconds maxPausedOpenMouthDuration = 35cs; + const TimeRange timeRange = timedShape.getTimeRange(); + if (timeRange.getLength() <= maxPausedOpenMouthDuration) { + result.set(timeRange, B); + } + } + + // Keep mouth open into pause if it just opened + for_each_adjacent(shapes.begin(), shapes.end(), [&](const Timed& secondLast, const Timed& last, const Timed& pause) { + if (pause.getValue() != X) return; + + centiseconds lastLength = last.getTimeRange().getLength(); + const centiseconds minOpenDuration = 20cs; + if (isClosed(secondLast.getValue()) && !isClosed(last.getValue()) && lastLength < minOpenDuration) { + const centiseconds minSpillDuration = 20cs; + centiseconds spillDuration = std::min(minSpillDuration, pause.getTimeRange().getLength()); + result.set(pause.getStart(), pause.getStart() + spillDuration, B); + } + }); + + return result; +} + ContinuousTimeline animate(const BoundedTimeline &phones) { // Convert phones to continuous timeline so that silences aren't skipped when iterating ContinuousTimeline> continuousPhones(phones.getRange(), boost::none); @@ -204,6 +234,12 @@ ContinuousTimeline animate(const BoundedTimeline &phones) { shapes.set(it->getTimeRange(), shape); } + // Animate pauses + Timeline pauses = animatePauses(shapes); + for (const auto& pause : pauses) { + shapes.set(pause); + } + // Create inbetweens for smoother animation Timeline tweens = createTweens(shapes); for (const auto& tween : tweens) { diff --git a/src/tools.h b/src/tools.h index 85d7827..056eee0 100644 --- a/src/tools.h +++ b/src/tools.h @@ -3,6 +3,7 @@ #include #include #include +#include #define UNUSED(x) ((void)(x)) @@ -11,4 +12,50 @@ using lambda_unique_ptr = std::unique_ptr>; std::string formatDuration(std::chrono::duration seconds); -std::string formatTime(time_t time, const std::string& format); \ No newline at end of file +std::string formatTime(time_t time, const std::string& format); + +template +void for_each_adjacent( + iterator_type begin, + iterator_type end, + std::function>&)> f) +{ + // Get the first n values + iterator_type it = begin; + using element_type = std::reference_wrapper; + std::deque values; + for (unsigned i = 0; i < n; ++i) { + if (it == end) return; + values.push_back(std::ref(*it)); + if (i < n - 1) ++it; + } + + for (; it != end; ++it) { + f(values); + + values.pop_front(); + values.push_back(*it); + } +} + +template +void for_each_adjacent( + iterator_type begin, + iterator_type end, + std::function f) +{ + for_each_adjacent<2>(begin, end, [&](const std::deque>& args) { + f(args[0], args[1]); + }); +} + +template +void for_each_adjacent( + iterator_type begin, + iterator_type end, + std::function f) +{ + for_each_adjacent<3>(begin, end, [&](const std::deque>& args) { + f(args[0], args[1], args[2]); + }); +}