From 3c134bbafee224a3496ae0132154783d5bee5073 Mon Sep 17 00:00:00 2001 From: Daniel Wolf Date: Thu, 8 Dec 2016 12:13:47 +0100 Subject: [PATCH] Anticipating only vowels --- src/animation/mouthAnimation.cpp | 36 ++++++++++++++++++-------------- src/core/Phone.cpp | 4 ++++ src/core/Phone.h | 3 +++ 3 files changed, 27 insertions(+), 16 deletions(-) diff --git a/src/animation/mouthAnimation.cpp b/src/animation/mouthAnimation.cpp index cd4ab73..f43db6a 100644 --- a/src/animation/mouthAnimation.cpp +++ b/src/animation/mouthAnimation.cpp @@ -104,12 +104,14 @@ ContinuousTimeline, AutoJoin> boundedTimelinetoContinuousOptional(co }; } -ContinuousTimeline getShapeSets(const BoundedTimeline& phones) { +using ShapeRule = tuple>; + +ContinuousTimeline getShapeRules(const BoundedTimeline& phones) { // Convert to continuous timeline so that silences aren't skipped when iterating auto continuousPhones = boundedTimelinetoContinuousOptional(phones); - // Create timeline of shape sets - ContinuousTimeline shapeSets(phones.getRange(), {{X}}); + // Create timeline of shape rules + ContinuousTimeline shapeRules(phones.getRange(), {{X}, boost::none}); centiseconds previousDuration = 0_cs; for (const auto& timedPhone : continuousPhones) { optional phone = timedPhone.getValue(); @@ -125,14 +127,14 @@ ContinuousTimeline getShapeSets(const BoundedTimeline& phones) // Copy to timeline. // Later shape sets may overwrite earlier ones if overlapping. for (const auto& timedShapeSet : phoneShapeSets) { - shapeSets.set(timedShapeSet); + shapeRules.set(timedShapeSet.getTimeRange(), {timedShapeSet.getValue(), phone}); } } previousDuration = duration; } - return shapeSets; + return shapeRules; } // Create timeline of shapes using a bidirectional algorithm. @@ -142,24 +144,26 @@ ContinuousTimeline getShapeSets(const BoundedTimeline& phones) // * When speaking, we tend to slur mouth shapes into each other. So we animate from start to end, // always choosing a shape from the current set that resembles the last shape and is somewhat relaxed. // * When speaking, we anticipate vowels, trying to form their shape before the actual vowel. -// So whenever we come across a one-shape set, we backtrack a little, spreating that shape to the left. -JoiningContinuousTimeline animate(const ContinuousTimeline& shapeSets) { - JoiningContinuousTimeline shapes(shapeSets.getRange(), X); +// So whenever we come across a one-shape vowel, we backtrack a little, spreating that shape to the left. +JoiningContinuousTimeline animate(const ContinuousTimeline& shapeRules) { + JoiningContinuousTimeline shapes(shapeRules.getRange(), X); Shape referenceShape = X; // Animate forwards centiseconds lastAnticipatedShapeStart = -1_cs; - for (auto it = shapeSets.begin(); it != shapeSets.end(); ++it) { - const ShapeSet shapeSet = it->getValue(); + for (auto it = shapeRules.begin(); it != shapeRules.end(); ++it) { + const ShapeRule shapeRule = it->getValue(); + const ShapeSet shapeSet = std::get(shapeRule); const Shape shape = getClosestShape(referenceShape, shapeSet); shapes.set(it->getTimeRange(), shape); - const bool anticipateShape = shapeSet.size() == 1 && *shapeSet.begin() != X; + const auto phone = std::get>(shapeRule); + const bool anticipateShape = phone && isVowel(*phone) && shapeSet.size() == 1; if (anticipateShape) { // Animate backwards a little const Shape anticipatedShape = shape; const centiseconds anticipatedShapeStart = it->getStart(); referenceShape = anticipatedShape; - for (auto reverseIt = it; reverseIt != shapeSets.begin(); ) { + for (auto reverseIt = it; reverseIt != shapeRules.begin(); ) { --reverseIt; // Make sure we haven't animated too far back @@ -170,7 +174,7 @@ JoiningContinuousTimeline animate(const ContinuousTimeline& sha if (anticipationDuration > maxAnticipationDuration) break; // Make sure the new, backwards-animated shape still resembles the anticipated shape - const Shape anticipatingShape = getClosestShape(referenceShape, reverseIt->getValue()); + const Shape anticipatingShape = getClosestShape(referenceShape, std::get(reverseIt->getValue())); if (getBasicShape(anticipatingShape) != getBasicShape(anticipatedShape)) break; // Overwrite forward-animated shape with backwards-animated, anticipating shape @@ -187,11 +191,11 @@ JoiningContinuousTimeline animate(const ContinuousTimeline& sha } JoiningContinuousTimeline animate(const BoundedTimeline &phones) { - // Create timeline of shape sets - ContinuousTimeline shapeSets = getShapeSets(phones); + // Create timeline of shape rules + ContinuousTimeline shapeRules = getShapeRules(phones); // Animate - JoiningContinuousTimeline shapes = animate(shapeSets); + JoiningContinuousTimeline shapes = animate(shapeRules); // Animate pauses JoiningTimeline pauses = animatePauses(shapes); diff --git a/src/core/Phone.cpp b/src/core/Phone.cpp index 33e06df..1d564f7 100644 --- a/src/core/Phone.cpp +++ b/src/core/Phone.cpp @@ -86,3 +86,7 @@ std::ostream& operator<<(std::ostream& stream, Phone value) { std::istream& operator>>(std::istream& stream, Phone& value) { return PhoneConverter::get().read(stream, value); } + +bool isVowel(Phone phone) { + return phone <= Phone::LastVowel; +} diff --git a/src/core/Phone.h b/src/core/Phone.h index e57ce2a..cd7e868 100644 --- a/src/core/Phone.h +++ b/src/core/Phone.h @@ -28,6 +28,7 @@ enum class Phone { // ... r-colored ER, // [ɝ] as in h[er], b[ir]d, h[ur]t + LastVowel = ER, ///////////// // Consonants @@ -90,3 +91,5 @@ public: std::ostream& operator<<(std::ostream& stream, Phone value); std::istream& operator>>(std::istream& stream, Phone& value); + +bool isVowel(Phone phone); \ No newline at end of file