Anticipating only vowels

This commit is contained in:
Daniel Wolf 2016-12-08 12:13:47 +01:00
parent 0cf5f7f365
commit 3c134bbafe
3 changed files with 27 additions and 16 deletions

View File

@ -104,12 +104,14 @@ ContinuousTimeline<optional<T>, AutoJoin> boundedTimelinetoContinuousOptional(co
}; };
} }
ContinuousTimeline<ShapeSet> getShapeSets(const BoundedTimeline<Phone>& phones) { using ShapeRule = tuple<ShapeSet, optional<Phone>>;
ContinuousTimeline<ShapeRule> getShapeRules(const BoundedTimeline<Phone>& phones) {
// Convert to continuous timeline so that silences aren't skipped when iterating // Convert to continuous timeline so that silences aren't skipped when iterating
auto continuousPhones = boundedTimelinetoContinuousOptional(phones); auto continuousPhones = boundedTimelinetoContinuousOptional(phones);
// Create timeline of shape sets // Create timeline of shape rules
ContinuousTimeline<ShapeSet> shapeSets(phones.getRange(), {{X}}); ContinuousTimeline<ShapeRule> shapeRules(phones.getRange(), {{X}, boost::none});
centiseconds previousDuration = 0_cs; centiseconds previousDuration = 0_cs;
for (const auto& timedPhone : continuousPhones) { for (const auto& timedPhone : continuousPhones) {
optional<Phone> phone = timedPhone.getValue(); optional<Phone> phone = timedPhone.getValue();
@ -125,14 +127,14 @@ ContinuousTimeline<ShapeSet> getShapeSets(const BoundedTimeline<Phone>& phones)
// Copy to timeline. // Copy to timeline.
// Later shape sets may overwrite earlier ones if overlapping. // Later shape sets may overwrite earlier ones if overlapping.
for (const auto& timedShapeSet : phoneShapeSets) { for (const auto& timedShapeSet : phoneShapeSets) {
shapeSets.set(timedShapeSet); shapeRules.set(timedShapeSet.getTimeRange(), {timedShapeSet.getValue(), phone});
} }
} }
previousDuration = duration; previousDuration = duration;
} }
return shapeSets; return shapeRules;
} }
// Create timeline of shapes using a bidirectional algorithm. // Create timeline of shapes using a bidirectional algorithm.
@ -142,24 +144,26 @@ ContinuousTimeline<ShapeSet> getShapeSets(const BoundedTimeline<Phone>& phones)
// * When speaking, we tend to slur mouth shapes into each other. So we animate from start to end, // * 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. // 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. // * 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. // So whenever we come across a one-shape vowel, we backtrack a little, spreating that shape to the left.
JoiningContinuousTimeline<Shape> animate(const ContinuousTimeline<ShapeSet>& shapeSets) { JoiningContinuousTimeline<Shape> animate(const ContinuousTimeline<ShapeRule>& shapeRules) {
JoiningContinuousTimeline<Shape> shapes(shapeSets.getRange(), X); JoiningContinuousTimeline<Shape> shapes(shapeRules.getRange(), X);
Shape referenceShape = X; Shape referenceShape = X;
// Animate forwards // Animate forwards
centiseconds lastAnticipatedShapeStart = -1_cs; centiseconds lastAnticipatedShapeStart = -1_cs;
for (auto it = shapeSets.begin(); it != shapeSets.end(); ++it) { for (auto it = shapeRules.begin(); it != shapeRules.end(); ++it) {
const ShapeSet shapeSet = it->getValue(); const ShapeRule shapeRule = it->getValue();
const ShapeSet shapeSet = std::get<ShapeSet>(shapeRule);
const Shape shape = getClosestShape(referenceShape, shapeSet); const Shape shape = getClosestShape(referenceShape, shapeSet);
shapes.set(it->getTimeRange(), shape); shapes.set(it->getTimeRange(), shape);
const bool anticipateShape = shapeSet.size() == 1 && *shapeSet.begin() != X; const auto phone = std::get<optional<Phone>>(shapeRule);
const bool anticipateShape = phone && isVowel(*phone) && shapeSet.size() == 1;
if (anticipateShape) { if (anticipateShape) {
// Animate backwards a little // Animate backwards a little
const Shape anticipatedShape = shape; const Shape anticipatedShape = shape;
const centiseconds anticipatedShapeStart = it->getStart(); const centiseconds anticipatedShapeStart = it->getStart();
referenceShape = anticipatedShape; referenceShape = anticipatedShape;
for (auto reverseIt = it; reverseIt != shapeSets.begin(); ) { for (auto reverseIt = it; reverseIt != shapeRules.begin(); ) {
--reverseIt; --reverseIt;
// Make sure we haven't animated too far back // Make sure we haven't animated too far back
@ -170,7 +174,7 @@ JoiningContinuousTimeline<Shape> animate(const ContinuousTimeline<ShapeSet>& sha
if (anticipationDuration > maxAnticipationDuration) break; if (anticipationDuration > maxAnticipationDuration) break;
// Make sure the new, backwards-animated shape still resembles the anticipated shape // 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<ShapeSet>(reverseIt->getValue()));
if (getBasicShape(anticipatingShape) != getBasicShape(anticipatedShape)) break; if (getBasicShape(anticipatingShape) != getBasicShape(anticipatedShape)) break;
// Overwrite forward-animated shape with backwards-animated, anticipating shape // Overwrite forward-animated shape with backwards-animated, anticipating shape
@ -187,11 +191,11 @@ JoiningContinuousTimeline<Shape> animate(const ContinuousTimeline<ShapeSet>& sha
} }
JoiningContinuousTimeline<Shape> animate(const BoundedTimeline<Phone> &phones) { JoiningContinuousTimeline<Shape> animate(const BoundedTimeline<Phone> &phones) {
// Create timeline of shape sets // Create timeline of shape rules
ContinuousTimeline<ShapeSet> shapeSets = getShapeSets(phones); ContinuousTimeline<ShapeRule> shapeRules = getShapeRules(phones);
// Animate // Animate
JoiningContinuousTimeline<Shape> shapes = animate(shapeSets); JoiningContinuousTimeline<Shape> shapes = animate(shapeRules);
// Animate pauses // Animate pauses
JoiningTimeline<Shape> pauses = animatePauses(shapes); JoiningTimeline<Shape> pauses = animatePauses(shapes);

View File

@ -86,3 +86,7 @@ std::ostream& operator<<(std::ostream& stream, Phone value) {
std::istream& operator>>(std::istream& stream, Phone& value) { std::istream& operator>>(std::istream& stream, Phone& value) {
return PhoneConverter::get().read(stream, value); return PhoneConverter::get().read(stream, value);
} }
bool isVowel(Phone phone) {
return phone <= Phone::LastVowel;
}

View File

@ -28,6 +28,7 @@ enum class Phone {
// ... r-colored // ... r-colored
ER, // [ɝ] as in h[er], b[ir]d, h[ur]t ER, // [ɝ] as in h[er], b[ir]d, h[ur]t
LastVowel = ER,
///////////// /////////////
// Consonants // Consonants
@ -90,3 +91,5 @@ public:
std::ostream& operator<<(std::ostream& stream, Phone value); std::ostream& operator<<(std::ostream& stream, Phone value);
std::istream& operator>>(std::istream& stream, Phone& value); std::istream& operator>>(std::istream& stream, Phone& value);
bool isVowel(Phone phone);