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
auto continuousPhones = boundedTimelinetoContinuousOptional(phones);
// Create timeline of shape sets
ContinuousTimeline<ShapeSet> shapeSets(phones.getRange(), {{X}});
// Create timeline of shape rules
ContinuousTimeline<ShapeRule> shapeRules(phones.getRange(), {{X}, boost::none});
centiseconds previousDuration = 0_cs;
for (const auto& timedPhone : continuousPhones) {
optional<Phone> phone = timedPhone.getValue();
@ -125,14 +127,14 @@ ContinuousTimeline<ShapeSet> getShapeSets(const BoundedTimeline<Phone>& 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<ShapeSet> getShapeSets(const BoundedTimeline<Phone>& 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<Shape> animate(const ContinuousTimeline<ShapeSet>& shapeSets) {
JoiningContinuousTimeline<Shape> shapes(shapeSets.getRange(), X);
// So whenever we come across a one-shape vowel, we backtrack a little, spreating that shape to the left.
JoiningContinuousTimeline<Shape> animate(const ContinuousTimeline<ShapeRule>& shapeRules) {
JoiningContinuousTimeline<Shape> 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<ShapeSet>(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<optional<Phone>>(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<Shape> animate(const ContinuousTimeline<ShapeSet>& 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<ShapeSet>(reverseIt->getValue()));
if (getBasicShape(anticipatingShape) != getBasicShape(anticipatedShape)) break;
// 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) {
// Create timeline of shape sets
ContinuousTimeline<ShapeSet> shapeSets = getShapeSets(phones);
// Create timeline of shape rules
ContinuousTimeline<ShapeRule> shapeRules = getShapeRules(phones);
// Animate
JoiningContinuousTimeline<Shape> shapes = animate(shapeSets);
JoiningContinuousTimeline<Shape> shapes = animate(shapeRules);
// Animate pauses
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) {
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
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);