diff --git a/src/animation/animationRules.cpp b/src/animation/animationRules.cpp index 683e4ce..77e4ea3 100644 --- a/src/animation/animationRules.cpp +++ b/src/animation/animationRules.cpp @@ -71,45 +71,40 @@ optional> getTween(Shape first, Shape second) { return it != lookup.end() ? it->second : optional>(); } -ShapeRule::ShapeRule(const ShapeSet& regularShapes, const ShapeSet& alternativeShapes) : - regularShapes(regularShapes), - alternativeShapes(alternativeShapes) -{} - -Timeline getShapeRules(Phone phone, centiseconds duration, centiseconds previousDuration) { +Timeline getShapeSets(Phone phone, centiseconds duration, centiseconds previousDuration) { // Returns a timeline with a single shape set - auto single = [duration](ShapeRule value) { - return Timeline {{0_cs, duration, value}}; + auto single = [duration](ShapeSet value) { + return Timeline {{0_cs, duration, value}}; }; // Returns a timeline with two shape sets, timed as a diphthong - auto diphthong = [duration](ShapeRule first, ShapeRule second) { + auto diphthong = [duration](ShapeSet first, ShapeSet second) { centiseconds firstDuration = duration_cast(duration * 0.6); - return Timeline { + return Timeline { {0_cs, firstDuration, first}, {firstDuration, duration, second} }; }; // Returns a timeline with two shape sets, timed as a plosive - auto plosive = [duration, previousDuration](ShapeRule first, ShapeRule second) { + auto plosive = [duration, previousDuration](ShapeSet first, ShapeSet second) { centiseconds minOcclusionDuration = 4_cs; centiseconds maxOcclusionDuration = 12_cs; centiseconds occlusionDuration = clamp(previousDuration / 2, minOcclusionDuration, maxOcclusionDuration); - return Timeline { + return Timeline { {-occlusionDuration, 0_cs, first}, {0_cs, duration, second} }; }; - // Returns the result of `getShapeRules` when called with identical arguments + // Returns the result of `getShapeSets` when called with identical arguments // except for a different phone. auto like = [duration, previousDuration](Phone referencePhone) { - return getShapeRules(referencePhone, duration, previousDuration); + return getShapeSets(referencePhone, duration, previousDuration); }; - static const ShapeRule any{{A, B, C, D, E, F, G, H, X}}; - static const ShapeRule anyOpen{{B, C, D, E, F, G, H}}; + static const ShapeSet any{A, B, C, D, E, F, G, H, X}; + static const ShapeSet anyOpen{B, C, D, E, F, G, H}; // Note: // The shapes {A, B, G, X} are very similar. You should avoid regular shape sets containing more than one of these shapes. @@ -117,52 +112,52 @@ Timeline getShapeRules(Phone phone, centiseconds duration, centisecon // As an exception, a very flexible rule may contain *all* these shapes. switch (phone) { - case Phone::AO: return single({{E}}); - case Phone::AA: return single({{D}}); - case Phone::IY: return single({{B}, {C}}); - case Phone::UW: return single({{F}}); - case Phone::EH: return single({{C}}); - case Phone::IH: return single({{B}, {C}}); - case Phone::UH: return single({{F}}); - case Phone::AH: return single({{C}}); - case Phone::Schwa: return single({{B, C}}); - case Phone::AE: return single({{C}}); - case Phone::EY: return diphthong({{C}}, {{B}, {C}}); - case Phone::AY: return duration < 20_cs ? diphthong({{C}}, {{B}, {C}}) : diphthong({{D}}, {{B}, {C}}); - case Phone::OW: return single({{F}}); - case Phone::AW: return duration < 30_cs ? diphthong({{C}}, {{F}}) : diphthong({{D}}, {{F}}); - case Phone::OY: return diphthong({{E}}, {{B}, {C}}); - case Phone::ER: return duration < 7_cs ? like(Phone::Schwa) : single({{B}}); + case Phone::AO: return single({E}); + case Phone::AA: return single({D}); + case Phone::IY: return single({B}); + case Phone::UW: return single({F}); + case Phone::EH: return single({C}); + case Phone::IH: return single({B}); + case Phone::UH: return single({F}); + case Phone::AH: return single({C}); + case Phone::Schwa: return single({B, C}); + case Phone::AE: return single({C}); + case Phone::EY: return diphthong({C}, {B}); + case Phone::AY: return duration < 20_cs ? diphthong({C}, {B}) : diphthong({D}, {B}); + case Phone::OW: return single({F}); + case Phone::AW: return duration < 30_cs ? diphthong({C}, {F}) : diphthong({D}, {F}); + case Phone::OY: return diphthong({E}, {B}); + case Phone::ER: return duration < 7_cs ? like(Phone::Schwa) : single({B}); case Phone::P: - case Phone::B: return plosive({{A}}, any); + case Phone::B: return plosive({A}, any); case Phone::T: - case Phone::D: return plosive({{B, F}}, anyOpen); + case Phone::D: return plosive({B, F}, anyOpen); case Phone::K: - case Phone::G: return plosive({{B, C, E, F, H}}, anyOpen); + case Phone::G: return plosive({B, C, E, F, H}, anyOpen); case Phone::CH: - case Phone::JH: return single({{B, F}}); + case Phone::JH: return single({B, F}); case Phone::F: - case Phone::V: return single({{G}}); + case Phone::V: return single({G}); case Phone::TH: case Phone::DH: case Phone::S: case Phone::Z: case Phone::SH: - case Phone::ZH: return single({{B, F}}); + case Phone::ZH: return single({B, F}); case Phone::HH: return single(any); // think "m-hm" - case Phone::M: return single({{A}}); - case Phone::N: return single({{B, C, F, H}}); - case Phone::NG: return single({{B, C, E, F}}); - case Phone::L: return duration < 20_cs ? single({{B, C, E, F, H}}) : single({{H}}); - case Phone::R: return single({{B, E, F}}); - case Phone::Y: return single({{B, C, F}}); - case Phone::W: return single({{F}}); + case Phone::M: return single({A}); + case Phone::N: return single({B, C, F, H}); + case Phone::NG: return single({B, C, E, F}); + case Phone::L: return duration < 20_cs ? single({B, C, E, F, H}) : single({H}); + case Phone::R: return single({B, E, F}); + case Phone::Y: return single({B, C, F}); + case Phone::W: return single({F}); case Phone::Breath: case Phone::Cough: - case Phone::Smack: return single({{C}}); - case Phone::Noise: return single({{B}}); + case Phone::Smack: return single({C}); + case Phone::Noise: return single({B}); default: throw std::invalid_argument("Unexpected phone."); } diff --git a/src/animation/animationRules.h b/src/animation/animationRules.h index 04707f4..1ddb9fb 100644 --- a/src/animation/animationRules.h +++ b/src/animation/animationRules.h @@ -14,7 +14,8 @@ Shape relax(Shape shape); // Returns the mouth shape to use for *short* pauses between words. Shape getRelaxedBridge(Shape lhs, Shape rhs); -// A set of mouth shapes that can be used to represent a certain sound +// A set of mouth shapes that can be used to represent a certain sound. +// The actual selection will be performed based on similarity with the previous or next shape. using ShapeSet = std::set; // Gets the shape from a non-empty set of shapes that most closely resembles a reference shape. @@ -35,29 +36,7 @@ enum class TweenTiming { // Returns the tween shape and timing to use to transition between the specified two mouth shapes. boost::optional> getTween(Shape first, Shape second); -// A struct describing the possible shapes to use during a given time range. -struct ShapeRule { - ShapeRule(const ShapeSet& regularShapes, const ShapeSet& alternativeShapes = {}); - - // A set of one or more shapes that may be used to animate a given time range. - // The actual selection will be performed based on similarity with the previous or next shape. - ShapeSet regularShapes; - - // The regular animation algorithm tries to minimize mouth shape changes. As a result, the mouth may sometimes remain static for too long. - // This is a set of zero or more shapes that may be used in these cases. - // In contrast to the regular shapes, this set should only contain shapes that can be used regardless of the surrounding shapes. - ShapeSet alternativeShapes; - - bool operator==(const ShapeRule& rhs) const { - return regularShapes == rhs.regularShapes && alternativeShapes == rhs.alternativeShapes; - } - - bool operator!=(const ShapeRule& rhs) const { - return !operator==(rhs); - } -}; - -// Returns the shape rule(s) to use for a given phone. +// Returns the shape set(s) to use for a given phone. // The resulting timeline will always cover the entire duration of the phone (starting at 0 cs). // It may extend into the negative time range if animation is required prior to the sound being heard. -Timeline getShapeRules(Phone phone, centiseconds duration, centiseconds previousDuration); +Timeline getShapeSets(Phone phone, centiseconds duration, centiseconds previousDuration); diff --git a/src/animation/mouthAnimation.cpp b/src/animation/mouthAnimation.cpp index ec6297c..cd4ab73 100644 --- a/src/animation/mouthAnimation.cpp +++ b/src/animation/mouthAnimation.cpp @@ -104,12 +104,12 @@ ContinuousTimeline, AutoJoin> boundedTimelinetoContinuousOptional(co }; } -ContinuousTimeline getShapeRules(const BoundedTimeline& phones) { +ContinuousTimeline getShapeSets(const BoundedTimeline& phones) { // Convert to continuous timeline so that silences aren't skipped when iterating auto continuousPhones = boundedTimelinetoContinuousOptional(phones); - // Create timeline of shape rules - ContinuousTimeline shapeRules(phones.getRange(), {{X}}); + // Create timeline of shape sets + ContinuousTimeline shapeSets(phones.getRange(), {{X}}); centiseconds previousDuration = 0_cs; for (const auto& timedPhone : continuousPhones) { optional phone = timedPhone.getValue(); @@ -117,25 +117,25 @@ ContinuousTimeline getShapeRules(const BoundedTimeline& phones if (phone) { // Animate one phone - Timeline phoneShapeRules = getShapeRules(*phone, duration, previousDuration); + Timeline phoneShapeSets = getShapeSets(*phone, duration, previousDuration); // Result timing is relative to phone. Make absolute. - phoneShapeRules.shift(timedPhone.getStart()); + phoneShapeSets.shift(timedPhone.getStart()); // Copy to timeline. - // Later shape rules may overwrite earlier ones if overlapping. - for (const auto& timedShapeRule : phoneShapeRules) { - shapeRules.set(timedShapeRule); + // Later shape sets may overwrite earlier ones if overlapping. + for (const auto& timedShapeSet : phoneShapeSets) { + shapeSets.set(timedShapeSet); } } previousDuration = duration; } - return shapeRules; + return shapeSets; } -// Create timeline of shape rules using a bidirectional algorithm. +// Create timeline of shapes using a bidirectional algorithm. // Here's a rough sketch: // // * Most consonants result in shape sets with multiple options; most vowels have only one shape option. @@ -187,13 +187,8 @@ JoiningContinuousTimeline animate(const ContinuousTimeline& sha } JoiningContinuousTimeline animate(const BoundedTimeline &phones) { - // Create timeline of shape rules - ContinuousTimeline shapeRules = getShapeRules(phones); - - // Take only the regular shapes from each shape rule. Alternative shapes will be implemented later. - ContinuousTimeline shapeSets( - shapeRules.getRange(), {{X}}, - shapeRules | transformed([](const Timed& timedRule) { return Timed(timedRule.getTimeRange(), timedRule.getValue().regularShapes); })); + // Create timeline of shape sets + ContinuousTimeline shapeSets = getShapeSets(phones); // Animate JoiningContinuousTimeline shapes = animate(shapeSets);