import * as animation from "./animation.js"; import { CoupleRole, DanceRole, DancerIdentity } from "./danceCommon.js"; import * as common from "./danceCommon.js"; import { Hand, setDistance } from "./rendererConstants.js"; import { nameLibFigureParameters, Move, chooser_pairz } from "./libfigureMapper.js"; import { LowLevelMove, SemanticAnimation, SemanticAnimationKind, animateFromLowLevelMoves } from "./lowLevelMove.js"; import { BalanceWeight, CirclePosition, CircleSide, CircleSideOrCenter, DancerDistance, Facing, HandConnection, HandTo, LongLines, PositionKind, SemanticPosition, ShortLinesPosition, StarGrip, handToDancerToSideInCircleFacingAcross, handsFourImproper, handsInCircle, handsInLine, handsInShortLine, oppositeFacing } from "./interpreterCommon.js"; import { ContraDBDance } from "./danceLibrary.js"; import { errorMoveInterpreterCtor, moveInterpreters } from "./moves/_moveInterpreter.js"; // Import all individual move files. // ls [a-z]*.js | sed 's@.*@import "./moves/\0";@' import "./moves/allemande.js"; import "./moves/balance.js"; import "./moves/balanceTheRing.js"; import "./moves/boxCirculate.js"; import "./moves/boxTheGnat.js"; import "./moves/butterflyWhirl.js"; import "./moves/californiaTwirl.js"; import "./moves/chain.js"; import "./moves/circle.js"; import "./moves/custom.js"; import "./moves/doSiDo.js"; import "./moves/downTheHall.js"; import "./moves/formAnOceanWave.js"; import "./moves/formLongWaves.js"; import "./moves/giveTake.js"; import "./moves/hey.js"; import "./moves/longLines.js"; import "./moves/madRobin.js"; import "./moves/passBy.js"; import "./moves/passThrough.js"; import "./moves/petronella.js"; import "./moves/promenade.js"; import "./moves/pullByDancers.js"; import "./moves/revolvingDoor.js"; import "./moves/rightLeftThrough.js"; import "./moves/rollAway.js"; import "./moves/roryOMore.js"; import "./moves/slice.js"; import "./moves/slideAlongSet.js"; import "./moves/star.js"; import "./moves/starPromenade.js"; import "./moves/swing.js"; import "./moves/turnAlone.js"; import "./moves/upTheHall.js"; function moveAsLowLevelMoves({ move, nextMove, startingPos, numProgessions }: { move: Move; nextMove: Move; startingPos: Map; numProgessions: number; }): Map { const moveInterpreter = moveInterpreters.get(move.move) ?? errorMoveInterpreterCtor; return new moveInterpreter({ move, nextMove, numProgessions }).moveAsLowLevelMoves({ startingPos }); } function danceAsLowLevelMoves(moves: Move[], startingPos: Map): Map { const res = new Map([...startingPos.keys()].map(id => [id, []])); let currentPos = new Map(startingPos); let numProgessions = 0; for (let i = 0; i < moves.length; i++) { const move = moves[i]; const nextMove = i === moves.length - 1 ? moves[0] : moves[i + 1]; try { if (i > 0 && move.beats === 0 && move.move === "slide along set") { const slideLeft: boolean = move.parameters.slide; for (const [id, currPos] of currentPos.entries()) { const slideAmount = (currPos.which.leftRightSide() === CircleSide.Left) === slideLeft ? +0.5 : -0.5; const setOffset = (currPos.setOffset ?? 0) + slideAmount; currentPos.set(id, { ...currPos, setOffset }); const prevMove = res.get(id)!.at(-1)!; prevMove.movementPattern.setSlideAmount = slideAmount; prevMove.endPosition.setOffset = setOffset; } } else { const newMoves = moveAsLowLevelMoves({ move, nextMove, startingPos: currentPos, numProgessions }); for (const [id, newMoveList] of newMoves.entries()) { res.get(id)!.push(...newMoveList); currentPos.set(id, newMoveList.at(-1)!.endPosition); } } } catch (ex) { // catch exception so something can be displayed for (const [id, pos] of currentPos.entries()) { res.get(id)!.push({ beats: move.beats, startPosition: pos, endPosition: pos, movementPattern: { kind: SemanticAnimationKind.StandStill }, move, startBeat: 0, interpreterError: ex instanceof Error ? ex.message : ex, }); } } if (move.progression) numProgessions++; } try { const progression = animateFromLowLevelMoves(res).progression; const progressionInSets = progression.y / setDistance; // fixup end positions to match start of next move // TODO Handle progression. for (const [id, lowLevelMoves] of res.entries()) { for (let i = 0; i < lowLevelMoves.length - 1; i++) { if (!lowLevelMoves[i].endPosition) throw "endPosition is undefined"; lowLevelMoves[i].endPosition = lowLevelMoves[i + 1].startPosition; if (!lowLevelMoves[i].endPosition) throw "endPosition is undefined now"; if (lowLevelMoves[i].movementPattern.kind === SemanticAnimationKind.StandStill) { lowLevelMoves[i].startPosition = lowLevelMoves[i].endPosition; if (i > 0) { lowLevelMoves[i - 1].endPosition = lowLevelMoves[i].startPosition; } } } // If progression isn't detected properly, do nothing. if (progressionInSets === 0) { lowLevelMoves[lowLevelMoves.length - 1].interpreterError = "No progression detected. Not lining up end with start of dance."; } else { const startPos = lowLevelMoves[0].startPosition; lowLevelMoves[lowLevelMoves.length - 1].endPosition = { ...lowLevelMoves[0].startPosition, // progressed setOffset: (startPos.setOffset ?? 0) + (id.coupleRole == CoupleRole.Ones ? 1 : -1) * progressionInSets, }; } } return res; } catch (ex) { res.get(DancerIdentity.OnesLark)![0].interpreterError = "Error detecting progression: " + (ex instanceof Error ? ex.message : ex); return res; } } function StartingPosForFormation(formation: common.StartFormation, dance?: ContraDBDance): Map { const preamble = dance?.preamble ?? ""; if (preamble.includes("Starts in short waves, right hands to neighbors")) { return new Map([...handsFourImproper.entries()].map( ([id, pos]) => ([id, { kind: PositionKind.ShortLines, which: pos.which.toShortLines(Hand.Left), facing: pos.which.isTop() ? Facing.Down : Facing.Up, }]) )); } else if (preamble.includes("face down the hall in lines of 4, 1s splitting the 2s")) { return new Map([...handsFourImproper.entries()].map( ([id, pos]) => ([id, { kind: PositionKind.ShortLines, which: pos.which.unfoldToShortLines(CircleSide.Top), facing: Facing.Down, }]) )); } switch (formation) { case "improper": return handsFourImproper; case "Becket": return new Map([...handsFourImproper.entries()].map( ([id, pos]) => ([id, {...pos, which: pos.which.circleLeft(1)}]) )); case "Becket ccw": return new Map([...handsFourImproper.entries()].map( ([id, pos]) => ([id, {...pos, which: pos.which.circleRight(1)}]) )); case "Sawtooth Becket": // Dancers start becket, then slide one person to the right. https://contradb.com/dances/848 // TODO Not sure this is right. return new Map([ [DancerIdentity.OnesLark, { kind: PositionKind.Circle, which: CirclePosition.BottomLeft, facing: Facing.CenterOfCircle, hands: handsInCircle, }], [DancerIdentity.OnesRobin, { kind: PositionKind.Circle, which: CirclePosition.TopLeft, facing: Facing.CenterOfCircle, hands: handsInCircle, }], [DancerIdentity.TwosLark, { kind: PositionKind.Circle, which: CirclePosition.BottomRight, facing: Facing.CenterOfCircle, hands: handsInCircle, }], [DancerIdentity.TwosRobin, { kind: PositionKind.Circle, which: CirclePosition.TopRight, facing: Facing.CenterOfCircle, hands: handsInCircle, setOffset: 1, }], ]); } } export let mappedDance: Move[]; export let interpretedDance: Map; export let interpretedAnimation: animation.Animation; export function loadDance(dance: ContraDBDance): animation.Animation { mappedDance = dance.figures.map(nameLibFigureParameters); interpretedDance = danceAsLowLevelMoves(mappedDance, StartingPosForFormation(dance.start_type, dance)); interpretedAnimation = animateFromLowLevelMoves(interpretedDance); return interpretedAnimation; }