/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
var facekit = LK.import("@upit/facekit.v1");
/****
* Classes
****/
var Mask = Container.expand(function () {
var self = Container.call(this);
// Store maskGraphics on self to access its properties like height if needed later
self.maskGraphics = self.attachAsset('maskImage', {
anchorX: 0.5,
anchorY: 0.4
});
self.animationFinished = false; // Flag to track if the intro animation is done
var targetX = GAME_WIDTH / 2;
var targetY = GAME_HEIGHT / 3;
var smoothingFactor = 0.15; // Higher value for more responsive tracking
var prevX = null;
var prevY = null;
// Scale tracking variables (from dragon example)
var scaleHistory = new Array(5).fill(1); // Start with default scale of 1
var scaleIndex = 0;
var currentScale = 1;
// Rotation variables - EXACTLY from working example
var targetTilt = 0;
var tiltSmoothingFactor = 0.11;
var tiltScaleFactor = 0.09;
self.show = function (targetX, targetY, duration) {
self.x = targetX;
self.y = -self.maskGraphics.height / 2 - 50;
tween(self, {
y: targetY
}, {
duration: duration,
easing: tween.easeOutSine,
onFinish: function onFinish() {
self.animationFinished = true;
}
});
};
// Function copied EXACTLY from working DragonHead example
function calculateFaceTilt() {
if (facekit.leftEye && facekit.rightEye && facekit.mouthCenter) {
// Calculate midpoint between eyes
var eyeMidX = (facekit.leftEye.x + facekit.rightEye.x) / 2;
var eyeMidY = (facekit.leftEye.y + facekit.rightEye.y) / 2;
// Calculate angle between eye midpoint and mouth, negated to fix direction
var dx = facekit.mouthCenter.x - eyeMidX;
var dy = facekit.mouthCenter.y - eyeMidY;
var angle = -(Math.atan2(dx, dy) * (180 / Math.PI));
// Reduced angle impact
return Math.max(-15, Math.min(15, angle * 0.15));
}
return 0; // Default to straight when face points aren't available
}
// Enhanced update method with smooth position tracking and dynamic scaling
self.update = function () {
if (!self.animationFinished) {
return;
}
// Smooth position tracking (from dragon example)
if (facekit.leftEye && facekit.rightEye) {
// --- SCALE CALCULATION ---
var eyeDistance = Math.abs(facekit.rightEye.x - facekit.leftEye.x);
var newScaleFactor = eyeDistance / 300; // Base scale factor from eye distance
scaleHistory[scaleIndex] = newScaleFactor;
scaleIndex = (scaleIndex + 1) % scaleHistory.length;
var avgScale = scaleHistory.reduce(function (a, b) {
return a + b;
}, 0) / scaleHistory.length;
var targetScaleValue = avgScale; // Smoothed target scale
currentScale = currentScale * 0.85 + targetScaleValue * 0.15; // Final smoothed currentScale
// --- POSITION CALCULATION WITH Y-COMPENSATION ---
var eyeMidX = (facekit.leftEye.x + facekit.rightEye.x) / 2;
var eyeMidY = (facekit.leftEye.y + facekit.rightEye.y) / 2;
targetX = eyeMidX; // Target X is the midpoint between eyes
// Base target Y positions the anchor (0.4 from top of graphic) relative to eyes
var baseTargetY = eyeMidY - self.maskGraphics.height * 0.20 - 50;
// Y-compensation: lower the mask when below standard scale (< 1.0), raise it slightly when above (> 1.0)
var yOffsetCompensation = 0;
if (currentScale < 1) {
yOffsetCompensation = self.maskGraphics.height * 0.4 * (1 - currentScale);
} else if (currentScale > 1) {
// Raise mask slightly as it grows above base scale (gentle effect)
yOffsetCompensation = -self.maskGraphics.height * 0.08 * (currentScale - 1);
}
targetY = baseTargetY + yOffsetCompensation; // Final targetY including compensation
// Initialize previous positions if not set
if (prevX === null) {
prevX = targetX;
prevY = targetY;
}
// Weighted average for smooth tracking
var newX = prevX * (1 - smoothingFactor) + targetX * smoothingFactor;
var newY = prevY * (1 - smoothingFactor) + targetY * smoothingFactor;
self.x = newX;
self.y = newY;
// Update previous positions
prevX = newX;
prevY = newY;
// --- APPLY SCALE TO MASK GRAPHICS ---
self.maskGraphics.scaleX = currentScale;
self.maskGraphics.scaleY = currentScale;
}
// Rotation tracking - COPIED EXACTLY from working example
if (facekit.leftEye && facekit.rightEye) {
targetTilt = calculateFaceTilt() * tiltScaleFactor;
// Limit rotation to ±15 degrees - DON'T convert to radians here
targetTilt = Math.max(-15, Math.min(15, targetTilt));
self.rotation += (targetTilt - self.rotation) * tiltSmoothingFactor;
}
};
return self;
});
var MouthAnimator = Container.expand(function () {
var self = Container.call(this);
// Load all viseme assets (keeping your existing setup)
self.visemes = {
closed: self.attachAsset('visemeClosed', {
anchorX: 0.5,
anchorY: 0.5,
visible: false
}),
AEI: self.attachAsset('visemeAEI', {
anchorX: 0.5,
anchorY: 0.5,
visible: false
}),
BMP: self.attachAsset('visemeBMP', {
anchorX: 0.5,
anchorY: 0.5,
visible: false
}),
CDGKNRSTXYZ: self.attachAsset('visemeCDGKNRSTXYZ', {
anchorX: 0.5,
anchorY: 0.5,
visible: false
}),
CHJSH: self.attachAsset('visemeCHJSH', {
anchorX: 0.5,
anchorY: 0.5,
visible: false
}),
EE: self.attachAsset('visemeEE', {
anchorX: 0.5,
anchorY: 0.5,
visible: false
}),
FV: self.attachAsset('visemeFV', {
anchorX: 0.5,
anchorY: 0.5,
visible: false
}),
L: self.attachAsset('visemeL', {
anchorX: 0.5,
anchorY: 0.5,
visible: false
}),
N: self.attachAsset('visemeN', {
anchorX: 0.5,
anchorY: 0.5,
visible: false
}),
O: self.attachAsset('visemeO', {
anchorX: 0.5,
anchorY: 0.5,
visible: false
}),
TH: self.attachAsset('visemeTH', {
anchorX: 0.5,
anchorY: 0.5,
visible: false
}),
U: self.attachAsset('visemeU', {
anchorX: 0.5,
anchorY: 0.5,
visible: false
}),
QW: self.attachAsset('visimeQW', {
anchorX: 0.5,
anchorY: 0.5,
visible: false
})
};
self.currentViseme = 'closed';
self.visemes.closed.visible = true;
// Automated lip sync data (converted from your JSON)
self.automatedLipSync = [{
start: 0.00,
end: 5.03,
value: "X"
}, {
start: 5.03,
end: 5.16,
value: "B"
}, {
start: 5.16,
end: 5.23,
value: "E"
}, {
start: 5.23,
end: 5.30,
value: "F"
}, {
start: 5.30,
end: 5.40,
value: "A"
}, {
start: 5.40,
end: 5.54,
value: "C"
}, {
start: 5.54,
end: 5.61,
value: "B"
}, {
start: 5.61,
end: 5.82,
value: "C"
}, {
start: 5.82,
end: 5.96,
value: "B"
}, {
start: 5.96,
end: 6.03,
value: "C"
}, {
start: 6.03,
end: 6.31,
value: "B"
}, {
start: 6.31,
end: 6.52,
value: "F"
}, {
start: 6.52,
end: 6.63,
value: "G"
}, {
start: 6.63,
end: 7.43,
value: "E"
}, {
start: 7.43,
end: 9.85,
value: "X"
}, {
start: 9.85,
end: 10.03,
value: "E"
}, {
start: 10.03,
end: 10.24,
value: "C"
}, {
start: 10.24,
end: 10.38,
value: "A"
}, {
start: 10.38,
end: 10.52,
value: "F"
}, {
start: 10.52,
end: 10.59,
value: "D"
}, {
start: 10.59,
end: 11.15,
value: "C"
}, {
start: 11.15,
end: 11.23,
value: "A"
}, {
start: 11.23,
end: 11.54,
value: "D"
}, {
start: 11.54,
end: 11.62,
value: "C"
}, {
start: 11.62,
end: 12.77,
value: "G"
}, {
start: 12.77,
end: 13.77,
value: "X"
}, {
start: 13.77,
end: 13.83,
value: "N"
}, {
start: 13.83,
end: 14.44,
value: "O"
}, {
start: 14.44,
end: 14.79,
value: "C"
}, {
start: 14.79,
end: 15.01,
value: "F"
}, {
start: 15.01,
end: 15.09,
value: "A"
}, {
start: 15.09,
end: 15.13,
value: "B"
}, {
start: 15.13,
end: 15.38,
value: "F"
}, {
start: 15.38,
end: 15.45,
value: "B"
}, {
start: 15.45,
end: 16.08,
value: "E"
}, {
start: 16.08,
end: 16.29,
value: "B"
}, {
start: 16.29,
end: 17.16,
value: "X"
}, {
start: 17.16,
end: 17.23,
value: "C"
}, {
start: 17.23,
end: 17.29,
value: "B"
}, {
start: 17.29,
end: 17.79,
value: "N"
}, {
start: 17.79,
end: 18.99,
value: 'E'
}, {
start: 18.99,
end: 19.53,
value: "G"
}, {
start: 19.53,
end: 22.08,
value: "X"
}, {
start: 22.08,
end: 22.42,
value: "E"
}, {
start: 22.42,
end: 22.77,
value: "B"
}, {
start: 22.77,
end: 22.85,
value: "A"
}, {
start: 22.85,
end: 23.02,
value: "N"
}, {
start: 23.02,
end: 23.30,
value: "C"
}, {
start: 23.30,
end: 23.65,
value: "C"
}, {
start: 23.65,
end: 23.86,
value: "B"
}, {
start: 23.86,
end: 24.49,
value: "E"
}, {
start: 24.49,
end: 24.91,
value: "L"
}, {
start: 24.91,
end: 24.98,
value: "E"
}, {
start: 24.98,
end: 25.05,
value: "N"
}, {
start: 25.05,
end: 25.33,
value: "E"
}, {
start: 25.33,
end: 25.75,
value: "B"
}, {
start: 25.75,
end: 25.82,
value: "D"
}, {
start: 25.82,
end: 25.96,
value: "B"
}, {
start: 25.96,
end: 26.17,
value: "F"
}, {
start: 26.17,
end: 26.45,
value: "E"
}, {
start: 26.45,
end: 27.08,
value: "B"
}, {
start: 27.08,
end: 27.43,
value: "E"
}, {
start: 27.43,
end: 27.57,
value: "F"
}, {
start: 27.57,
end: 27.71,
value: "C"
}, {
start: 27.71,
end: 28.48,
value: "O"
}, {
start: 28.48,
end: 32.70,
value: "X"
}, {
start: 32.70,
end: 32.82,
value: "B"
}, {
start: 32.82,
end: 33.03,
value: "O"
}, {
start: 33.03,
end: 33.45,
value: "E"
}, {
start: 33.45,
end: 33.66,
value: "E"
}, {
start: 33.66,
end: 33.73,
value: "C"
}, {
start: 33.73,
end: 33.87,
value: "B"
}, {
start: 33.87,
end: 34.01,
value: "E"
}, {
start: 34.01,
end: 34.29,
value: "C"
}, {
start: 34.29,
end: 34.36,
value: "B"
}, {
start: 34.36,
end: 34.50,
value: "E"
}, {
start: 34.50,
end: 34.71,
value: "B"
}, {
start: 34.71,
end: 34.85,
value: "E"
}, {
start: 34.85,
end: 35.13,
value: "C"
}, {
start: 35.13,
end: 35.20,
value: "C"
}, {
start: 35.20,
end: 35.48,
value: "B"
}, {
start: 35.48,
end: 35.55,
value: "C"
}, {
start: 35.55,
end: 35.76,
value: "E"
}, {
start: 35.76,
end: 35.90,
value: "F"
}, {
start: 35.90,
end: 36.60,
value: "B"
}, {
start: 36.60,
end: 36.71,
value: "A"
}, {
start: 36.71,
end: 36.89,
value: "O"
}, {
start: 36.89,
end: 36.94,
value: "N"
}, {
start: 36.94,
end: 37.47,
value: "B"
}, {
start: 37.47,
end: 37.55,
value: "A"
}, {
start: 37.55,
end: 37.65,
value: "E"
}, {
start: 37.65,
end: 37.72,
value: "B"
}, {
start: 37.72,
end: 37.92,
value: "F"
}, {
start: 37.92,
end: 38.00,
value: "E"
}, {
start: 38.00,
end: 40.10,
value: "O"
}, {
start: 40.10,
end: 43.42,
value: "X"
}, {
start: 43.42,
end: 43.67,
value: "C"
}, {
start: 43.67,
end: 43.74,
value: "O"
}, {
start: 43.74,
end: 43.88,
value: "F"
}, {
start: 43.88,
end: 44.23,
value: "A"
}, {
start: 44.23,
end: 44.44,
value: "C"
}, {
start: 44.44,
end: 44.58,
value: "B"
}, {
start: 44.58,
end: 44.79,
value: "C"
}, {
start: 44.79,
end: 45.07,
value: "O"
}, {
start: 45.07,
end: 45.35,
value: "L"
}, {
start: 45.35,
end: 45.63,
value: "C"
}, {
start: 45.63,
end: 45.70,
value: "B"
}, {
start: 45.70,
end: 46.12,
value: "E"
}, {
start: 46.12,
end: 46.40,
value: "E"
}, {
start: 46.40,
end: 46.45,
value: "B"
}, {
start: 46.45,
end: 47.13,
value: "F"
}, {
start: 47.13,
end: 47.27,
value: "C"
}, {
start: 47.27,
end: 47.34,
value: "B"
}, {
start: 47.34,
end: 47.55,
value: "E"
}, {
start: 47.55,
end: 47.76,
value: "C"
}, {
start: 47.76,
end: 47.83,
value: "B"
}, {
start: 47.83,
end: 48.04,
value: "D"
}, {
start: 48.04,
end: 48.11,
value: "B"
}, {
start: 48.11,
end: 48.81,
value: "F"
}, {
start: 48.81,
end: 48.88,
value: "A"
}, {
start: 48.88,
end: 48.95,
value: "L"
}, {
start: 48.95,
end: 49.02,
value: "E"
}, {
start: 49.02,
end: 50.28,
value: "B"
}, {
start: 50.28,
end: 50.61,
value: "A"
}, {
start: 50.61,
end: 53.74,
value: "X"
}, {
start: 53.74,
end: 54.23,
value: "C"
}, {
start: 54.23,
end: 54.31,
value: "A"
}, {
start: 54.31,
end: 54.51,
value: "B"
}, {
start: 54.51,
end: 54.79,
value: "E"
}, {
start: 54.79,
end: 54.90,
value: "A"
}, {
start: 54.90,
end: 55.01,
value: "B"
}, {
start: 55.01,
end: 55.12,
value: "A"
}, {
start: 55.12,
end: 55.18,
value: "C"
}, {
start: 55.18,
end: 55.52,
value: "B"
}, {
start: 55.52,
end: 55.59,
value: "C"
}, {
start: 55.59,
end: 55.87,
value: "B"
}, {
start: 55.87,
end: 56.22,
value: "F"
}, {
start: 56.22,
end: 56.36,
value: "B"
}, {
start: 56.36,
end: 56.66,
value: "F"
}, {
start: 56.66,
end: 56.74,
value: "E"
}, {
start: 56.74,
end: 56.89,
value: "C"
}, {
start: 56.89,
end: 57.17,
value: "A"
}, {
start: 57.17,
end: 57.69,
value: "N"
}, {
start: 57.69,
end: 57.92,
value: "C"
}, {
start: 57.92,
end: 58.20,
value: "B"
}, {
start: 58.20,
end: 59.04,
value: "F"
}, {
start: 59.04,
end: 59.18,
value: "B"
}, {
start: 59.18,
end: 59.39,
value: "E"
}, {
start: 59.39,
end: 59.53,
value: "C"
}, {
start: 59.53,
end: 59.98,
value: "O"
}, {
start: 59.98,
end: 60.06,
value: "N"
}, {
start: 60.06,
end: 60.37,
value: "B"
}, {
start: 60.37,
end: 60.51,
value: "C"
}, {
start: 60.51,
end: 60.65,
value: "E"
}, {
start: 60.65,
end: 62.19,
value: "N"
}, {
start: 62.19,
end: 62.33,
value: "X"
}, {
start: 62.33,
end: 62.55,
value: "B"
}, {
start: 62.55,
end: 63.24,
value: "X"
}, {
start: 63.24,
end: 63.38,
value: "B"
}, {
start: 63.38,
end: 63.73,
value: "E"
}, {
start: 63.73,
end: 63.87,
value: "C"
}, {
start: 63.87,
end: 63.99,
value: "A"
}, {
start: 63.99,
end: 64.11,
value: "C"
}, {
start: 64.11,
end: 64.46,
value: "B"
}, {
start: 64.46,
end: 64.60,
value: "F"
}, {
start: 64.60,
end: 64.67,
value: "B"
}, {
start: 64.67,
end: 64.88,
value: "F"
}, {
start: 64.88,
end: 64.95,
value: "C"
}, {
start: 64.95,
end: 65.88,
value: "B"
}, {
start: 65.88,
end: 66.43,
value: "X"
}, {
start: 66.43,
end: 66.62,
value: "D"
}, {
start: 66.62,
end: 66.81,
value: "C"
}, {
start: 66.81,
end: 66.86,
value: "E"
}, {
start: 66.86,
end: 67.11,
value: "C"
}, {
start: 67.11,
end: 68.93,
value: "B"
}, {
start: 68.93,
end: 69.14,
value: "X"
}, {
start: 69.14,
end: 70.99,
value: "A"
}, {
start: 70.99,
end: 71.13,
value: "F"
}, {
start: 71.13,
end: 72.63,
value: "A"
}, {
start: 72.63,
end: 72.71,
value: "C"
}, {
start: 72.71,
end: 73.16,
value: "B"
}, {
start: 73.16,
end: 75.30,
value: "X"
}, {
start: 75.30,
end: 75.57,
value: "B"
}, {
start: 75.57,
end: 75.71,
value: "E"
}, {
start: 75.71,
end: 75.99,
value: "B"
}, {
start: 75.99,
end: 76.27,
value: "C"
}, {
start: 76.27,
end: 76.34,
value: "B"
}, {
start: 76.34,
end: 76.83,
value: "F"
}, {
start: 76.83,
end: 77.04,
value: "E"
}, {
start: 77.04,
end: 77.11,
value: "C"
}, {
start: 77.11,
end: 77.18,
value: "H"
}, {
start: 77.18,
end: 77.37,
value: "E"
}, {
start: 77.37,
end: 77.42,
value: "C"
}, {
start: 77.42,
end: 77.81,
value: "B"
}, {
start: 77.81,
end: 79.14,
value: "F"
}, {
start: 79.14,
end: 79.48,
value: "B"
}, {
start: 79.48,
end: 80.18,
value: "E"
}, {
start: 80.18,
end: 80.46,
value: "C"
}, {
start: 80.46,
end: 83.12,
value: "B"
}, {
start: 83.12,
end: 84.24,
value: "E"
}, {
start: 84.24,
end: 84.45,
value: "B"
}, {
start: 84.45,
end: 85.37,
value: "X"
}, {
start: 85.37,
end: 85.55,
value: "F"
}, {
start: 85.55,
end: 86.08,
value: "B"
}, {
start: 86.08,
end: 86.62,
value: "F"
}, {
start: 86.62,
end: 86.67,
value: "C"
}, {
start: 86.67,
end: 87.00,
value: "E"
}, {
start: 87.00,
end: 87.28,
value: "F"
}, {
start: 87.28,
end: 87.42,
value: "D"
}, {
start: 87.42,
end: 87.70,
value: "C"
}, {
start: 87.70,
end: 88.54,
value: "B"
}, {
start: 88.54,
end: 89.42,
value: "D"
}, {
start: 89.42,
end: 89.49,
value: "C"
}, {
start: 89.49,
end: 89.73,
value: "B"
}, {
start: 89.73,
end: 90.46,
value: "X"
}, {
start: 90.46,
end: 90.81,
value: "E"
}, {
start: 90.81,
end: 91.27,
value: "F"
}, {
start: 91.27,
end: 91.34,
value: "E"
}, {
start: 91.34,
end: 91.41,
value: "F"
}, {
start: 91.41,
end: 92.11,
value: "B"
}, {
start: 92.11,
end: 93.07,
value: "X"
}, {
start: 93.07,
end: 93.33,
value: "C"
}, {
start: 93.33,
end: 93.40,
value: "E"
}, {
start: 93.40,
end: 93.78,
value: "F"
}, {
start: 93.78,
end: 93.85,
value: "B"
}, {
start: 93.85,
end: 94.16,
value: "D"
}, {
start: 94.16,
end: 94.24,
value: "C"
}, {
start: 94.24,
end: 95.04,
value: "B"
}, {
start: 95.04,
end: 95.79,
value: "X"
}, {
start: 95.79,
end: 95.89,
value: "C"
}, {
start: 95.89,
end: 95.96,
value: "B"
}, {
start: 95.96,
end: 96.05,
value: "A"
}, {
start: 96.05,
end: 96.31,
value: "C"
}, {
start: 96.31,
end: 96.49,
value: "B"
}, {
start: 96.49,
end: 97.18,
value: "D"
}, {
start: 97.18,
end: 97.26,
value: "C"
}, {
start: 97.26,
end: 98.43,
value: "X"
}, {
start: 98.43,
end: 98.54,
value: "E"
}, {
start: 98.54,
end: 98.61,
value: "F"
}, {
start: 98.61,
end: 98.68,
value: "C"
}, {
start: 98.68,
end: 98.75,
value: "B"
}, {
start: 98.75,
end: 98.82,
value: "F"
}, {
start: 98.82,
end: 99.10,
value: "C"
}, {
start: 99.10,
end: 99.18,
value: "A"
}, {
start: 99.18,
end: 99.64,
value: "C"
}, {
start: 99.64,
end: 99.85,
value: "B"
}, {
start: 99.85,
end: 101.13,
value: "X"
}, {
start: 101.13,
end: 101.26,
value: "D"
}, {
start: 101.26,
end: 101.33,
value: "B"
}, {
start: 101.33,
end: 101.45,
value: "A"
}, {
start: 101.45,
end: 101.86,
value: "C"
}, {
start: 101.86,
end: 102.38,
value: "B"
}, {
start: 102.38,
end: 102.46,
value: "A"
}, {
start: 102.46,
end: 103.19,
value: "N"
}, {
start: 103.19,
end: 103.47,
value: "B"
}, {
start: 103.47,
end: 104.52,
value: "A"
}, {
start: 104.52,
end: 105.15,
value: "C"
}, {
start: 105.15,
end: 105.57,
value: "B"
}, {
start: 105.57,
end: 108.41,
value: "X"
}, {
start: 108.41,
end: 108.56,
value: "A"
}, {
start: 108.56,
end: 108.63,
value: "C"
}, {
start: 108.63,
end: 108.84,
value: "B"
}, {
start: 108.84,
end: 108.98,
value: "A"
}, {
start: 108.98,
end: 109.05,
value: "L"
}, {
start: 109.05,
end: 109.33,
value: "A"
}, {
start: 109.33,
end: 109.68,
value: "B"
}, {
start: 109.68,
end: 109.82,
value: "E"
}, {
start: 109.82,
end: 109.89,
value: "B"
}, {
start: 109.89,
end: 110.03,
value: "O"
}, {
start: 110.03,
end: 110.17,
value: "F"
}, {
start: 110.17,
end: 110.24,
value: "B"
}, {
start: 110.24,
end: 110.52,
value: "A"
}, {
start: 110.52,
end: 110.59,
value: "D"
}, {
start: 110.59,
end: 111.01,
value: "N"
}, {
start: 111.01,
end: 111.08,
value: "E"
}, {
start: 111.08,
end: 111.57,
value: "C"
}, {
start: 111.57,
end: 111.64,
value: "B"
}, {
start: 111.64,
end: 111.99,
value: "E"
}, {
start: 111.99,
end: 113.77,
value: "X"
}, {
start: 113.77,
end: 114.16,
value: "B"
}, {
start: 114.16,
end: 114.65,
value: "E"
}, {
start: 114.65,
end: 114.93,
value: "C"
}, {
start: 114.93,
end: 115.00,
value: "C"
}, {
start: 115.00,
end: 115.24,
value: "A"
}, {
start: 115.24,
end: 115.46,
value: "N"
}, {
start: 115.46,
end: 115.60,
value: "C"
}, {
start: 115.60,
end: 115.74,
value: "B"
}, {
start: 115.74,
end: 115.88,
value: "E"
}, {
start: 115.88,
end: 116.09,
value: "A"
}, {
start: 116.09,
end: 116.17,
value: "B"
}, {
start: 116.17,
end: 116.24,
value: "E"
}, {
start: 116.24,
end: 116.73,
value: "B"
}, {
start: 116.73,
end: 117.57,
value: "X"
}, {
start: 117.57,
end: 117.80,
value: "B"
}, {
start: 117.80,
end: 118.81,
value: "X"
}, {
start: 118.81,
end: 119.28,
value: "B"
}, {
start: 119.28,
end: 119.35,
value: "F"
}, {
start: 119.35,
end: 119.49,
value: "E"
}, {
start: 119.49,
end: 119.65,
value: "A"
}, {
start: 119.65,
end: 119.88,
value: "C"
}, {
start: 119.88,
end: 119.95,
value: "E"
}, {
start: 119.95,
end: 120.16,
value: "B"
}, {
start: 120.16,
end: 120.30,
value: "E"
}, {
start: 120.30,
end: 120.37,
value: "B"
}, {
start: 120.37,
end: 120.65,
value: "E"
}, {
start: 120.65,
end: 120.79,
value: "F"
}, {
start: 120.79,
end: 120.93,
value: "B"
}, {
start: 120.93,
end: 121.00,
value: "E"
}, {
start: 121.00,
end: 121.28,
value: "L"
}, {
start: 121.28,
end: 121.49,
value: "O"
}, {
start: 121.49,
end: 121.56,
value: "F"
}, {
start: 121.56,
end: 121.70,
value: "B"
}, {
start: 121.70,
end: 121.98,
value: "A"
}, {
start: 121.98,
end: 122.19,
value: "E"
}, {
start: 122.19,
end: 122.61,
value: "C"
}, {
start: 122.61,
end: 123.56,
value: "B"
}, {
start: 123.56,
end: 123.77,
value: "X"
}, {
start: 123.77,
end: 123.87,
value: "B"
}, {
start: 123.87,
end: 124.02,
value: "A"
}, {
start: 124.02,
end: 124.16,
value: "L"
}, {
start: 124.16,
end: 124.30,
value: "F"
}, {
start: 124.30,
end: 124.72,
value: "B"
}, {
start: 124.72,
end: 125.00,
value: "E"
}, {
start: 125.00,
end: 125.99,
value: "D"
}, {
start: 125.99,
end: 126.06,
value: "C"
}, {
start: 126.06,
end: 126.34,
value: "I"
}, {
start: 126.34,
end: 126.48,
value: "B"
}, {
start: 126.48,
end: 126.62,
value: "E"
}, {
start: 126.62,
end: 126.69,
value: "F"
}, {
start: 126.69,
end: 126.83,
value: "B"
}, {
start: 126.83,
end: 128.26,
value: "A"
}, {
start: 128.26,
end: 128.43,
value: "C"
}, {
start: 128.43,
end: 129.70,
value: "X"
}, {
start: 129.70,
end: 129.85,
value: "A"
}, {
start: 129.85,
end: 129.96,
value: "L"
}, {
start: 129.96,
end: 130.03,
value: "F"
}, {
start: 130.03,
end: 130.17,
value: "B"
}, {
start: 130.17,
end: 130.38,
value: "E"
}, {
start: 130.38,
end: 130.52,
value: "D"
}, {
start: 130.52,
end: 130.59,
value: "B"
}, {
start: 130.59,
end: 130.87,
value: "I"
}, {
start: 130.87,
end: 131.08,
value: "B"
}, {
start: 131.08,
end: 131.15,
value: "D"
}, {
start: 131.15,
end: 131.29,
value: "O"
}, {
start: 131.29,
end: 131.36,
value: "B"
}, {
start: 131.36,
end: 131.50,
value: "B"
}, {
start: 131.50,
end: 131.81,
value: "A"
}, {
start: 131.81,
end: 131.89,
value: "C"
}, {
start: 131.89,
end: 132.20,
value: "B"
}, {
start: 132.20,
end: 132.48,
value: "E"
}, {
start: 132.48,
end: 132.62,
value: "X"
}, {
start: 132.62,
end: 132.69,
value: "B"
}, {
start: 132.69,
end: 132.90,
value: "U"
}, {
start: 132.90,
end: 133.25,
value: "B"
}, {
start: 133.25,
end: 133.45,
value: "F"
}, {
start: 133.45,
end: 133.53,
value: "E"
}, {
start: 133.53,
end: 133.88,
value: "B"
}, {
start: 133.88,
end: 134.02,
value: "U"
}, {
start: 134.02,
end: 134.14,
value: "X"
}, {
start: 134.14,
end: 134.29,
value: "B"
}, {
start: 134.29,
end: 134.36,
value: "A"
}, {
start: 134.36,
end: 134.50,
value: "B"
}, {
start: 134.50,
end: 134.71,
value: "D"
}, {
start: 134.71,
end: 134.85,
value: "E"
}, {
start: 134.85,
end: 135.06,
value: "N"
}, {
start: 135.06,
end: 135.20,
value: "C"
}, {
start: 135.20,
end: 135.27,
value: "B"
}, {
start: 135.27,
end: 135.69,
value: "A"
}, {
start: 135.69,
end: 135.83,
value: "B"
}, {
start: 135.83,
end: 136.53,
value: "E"
}, {
start: 136.53,
end: 136.67,
value: "B"
}, {
start: 136.67,
end: 140.08,
value: "X"
}, {
start: 140.08,
end: 140.22,
value: "D"
}, {
start: 140.22,
end: 140.29,
value: "B"
}, {
start: 140.29,
end: 140.57,
value: "E"
}, {
start: 140.57,
end: 141.20,
value: "F"
}, {
start: 141.20,
end: 141.41,
value: "B"
}, {
start: 141.41,
end: 141.76,
value: "A"
}, {
start: 141.76,
end: 142.11,
value: "C"
}, {
start: 142.11,
end: 142.18,
value: "N"
}, {
start: 142.18,
end: 142.25,
value: "E"
}, {
start: 142.25,
end: 142.74,
value: "C"
}, {
start: 142.74,
end: 142.93,
value: "B"
}, {
start: 142.93,
end: 142.98,
value: "A"
}, {
start: 142.98,
end: 143.21,
value: "C"
}, {
start: 143.21,
end: 143.97,
value: "D"
}, {
start: 143.97,
end: 144.11,
value: "B"
}, {
start: 144.11,
end: 144.27,
value: "X"
}, {
start: 144.27,
end: 144.40,
value: "B"
}, {
start: 144.40,
end: 145.11,
value: "X"
}, {
start: 145.11,
end: 145.58,
value: "B"
}, {
start: 145.58,
end: 145.72,
value: "F"
}, {
start: 145.72,
end: 145.80,
value: "A"
}, {
start: 145.80,
end: 145.87,
value: "C"
}, {
start: 145.87,
end: 146.18,
value: "D"
}, {
start: 146.18,
end: 146.25,
value: "C"
}, {
start: 146.25,
end: 146.49,
value: "B"
}, {
start: 146.49,
end: 146.56,
value: "A"
}, {
start: 146.56,
end: 146.77,
value: "L"
}, {
start: 146.77,
end: 146.85,
value: "A"
}, {
start: 146.85,
end: 147.26,
value: "E"
}, {
start: 147.26,
end: 147.47,
value: "B"
}, {
start: 147.47,
end: 147.75,
value: "F"
}, {
start: 147.75,
end: 147.82,
value: "E"
}, {
start: 147.82,
end: 148.15,
value: "D"
}, {
start: 148.15,
end: 148.20,
value: "C"
}, {
start: 148.20,
end: 148.38,
value: "B"
}, {
start: 148.38,
end: 148.45,
value: "F"
}, {
start: 148.45,
end: 150.48,
value: "E"
}, {
start: 150.48,
end: 150.90,
value: "B"
}, {
start: 150.90,
end: 151.41,
value: "X"
}, {
start: 151.41,
end: 151.76,
value: "E"
}, {
start: 151.76,
end: 151.90,
value: "F"
}, {
start: 151.90,
end: 152.15,
value: "A"
}, {
start: 152.15,
end: 152.40,
value: "E"
}, {
start: 152.40,
end: 152.54,
value: "F"
}, {
start: 152.54,
end: 152.61,
value: "B"
}, {
start: 152.61,
end: 152.89,
value: "E"
}, {
start: 152.89,
end: 153.13,
value: "F"
}, {
start: 153.13,
end: 153.19,
value: "C"
}, {
start: 153.19,
end: 153.38,
value: "E"
}, {
start: 153.38,
end: 153.45,
value: "F"
}, {
start: 153.45,
end: 154.69,
value: "X"
}, {
start: 154.69,
end: 154.96,
value: "C"
}, {
start: 154.96,
end: 155.16,
value: "A"
}, {
start: 155.16,
end: 155.54,
value: "F"
}, {
start: 155.54,
end: 155.62,
value: "B"
}, {
start: 155.62,
end: 156.49,
value: "E"
}, {
start: 156.49,
end: 156.63,
value: "B"
}, {
start: 156.63,
end: 157.19,
value: "E"
}, {
start: 157.19,
end: 157.47,
value: "L"
}, {
start: 157.47,
end: 157.68,
value: "O"
}, {
start: 157.68,
end: 158.18,
value: "B"
}, {
start: 158.18,
end: 158.22,
value: "B"
}, {
start: 158.22,
end: 159.21,
value: "A"
}, {
start: 159.21,
end: 159.83,
value: "E"
}, {
start: 159.83,
end: 160.25,
value: "C"
}, {
start: 160.25,
end: 160.81,
value: "B"
}, {
start: 160.81,
end: 163.32,
value: "X"
}, {
start: 163.32,
end: 163.41,
value: "B"
}, {
start: 163.41,
end: 163.49,
value: "A"
}, {
start: 163.49,
end: 163.68,
value: "L"
}, {
start: 163.68,
end: 163.73,
value: "F"
}, {
start: 163.73,
end: 163.91,
value: "B"
}, {
start: 163.91,
end: 164.12,
value: "E"
}, {
start: 164.12,
end: 164.71,
value: "D"
}, {
start: 164.71,
end: 164.92,
value: "C"
}, {
start: 164.92,
end: 165.13,
value: "I"
}, {
start: 165.13,
end: 165.48,
value: "D"
}, {
start: 165.48,
end: 165.62,
value: "O"
}, {
start: 165.62,
end: 165.90,
value: "M"
}, {
start: 165.90,
end: 165.97,
value: "C"
}, {
start: 165.97,
end: 167.23,
value: "A"
}, {
start: 167.23,
end: 167.68,
value: "X"
}, {
start: 167.68,
end: 168.61,
value: "A"
}, {
start: 168.61,
end: 168.91,
value: "L"
}, {
start: 168.91,
end: 168.98,
value: "F"
}, {
start: 168.98,
end: 169.40,
value: "B"
}, {
start: 169.40,
end: 169.73,
value: "E"
}, {
start: 169.73,
end: 172.56,
value: "D"
}, {
start: 172.56,
end: 172.64,
value: "C"
}, {
start: 172.64,
end: 173.40,
value: "X"
}, {
start: 173.40,
end: 173.53,
value: "B"
}, {
start: 173.53,
end: 174.56,
value: "U"
}, {
start: 174.56,
end: 174.63,
value: "F"
}, {
start: 174.63,
end: 174.70,
value: "G"
}, {
start: 174.70,
end: 174.98,
value: "E"
}, {
start: 174.98,
end: 175.26,
value: "H"
}, {
start: 175.26,
end: 175.68,
value: "C"
}, {
start: 175.68,
end: 176.10,
value: "B"
}, {
start: 176.10,
end: 176.24,
value: "E"
}, {
start: 176.24,
end: 176.45,
value: "F"
}, {
start: 176.45,
end: 176.52,
value: "B"
}, {
start: 176.52,
end: 177.64,
value: "O"
}, {
start: 177.64,
end: 177.85,
value: "X"
}, {
start: 177.85,
end: 177.97,
value: "C"
}, {
start: 177.97,
end: 178.27,
value: "X"
}, {
start: 178.27,
end: 178.40,
value: "B"
}, {
start: 178.40,
end: 178.54,
value: "A"
}, {
start: 178.54,
end: 178.68,
value: "R"
}, {
start: 178.68,
end: 179.17,
value: "E"
}, {
start: 179.17,
end: 179.94,
value: "B"
}, {
start: 179.94,
end: 181.13,
value: "X"
}, {
start: 181.13,
end: 181.25,
value: "D"
}, {
start: 181.25,
end: 181.39,
value: "B"
}, {
start: 181.39,
end: 181.94,
value: "E"
}, {
start: 181.94,
end: 182.33,
value: "A"
}, {
start: 182.33,
end: 182.41,
value: "B"
}, {
start: 182.41,
end: 182.86,
value: "B"
}, {
start: 182.86,
end: 182.98,
value: "X"
}, {
start: 182.98,
end: 183.17,
value: "B"
}, {
start: 183.17,
end: 183.81,
value: "X"
}, {
start: 183.81,
end: 183.96,
value: "C"
}, {
start: 183.96,
end: 184.18,
value: "A"
}, {
start: 184.18,
end: 184.63,
value: "N"
}, {
start: 184.63,
end: 184.71,
value: "A"
}, {
start: 184.71,
end: 185.44,
value: "B"
}, {
start: 185.44,
end: 186.32,
value: "X"
}, {
start: 186.32,
end: 186.49,
value: "B"
}, {
start: 186.49,
end: 186.56,
value: "A"
}, {
start: 186.56,
end: 186.63,
value: "T"
}, {
start: 186.63,
end: 186.78,
value: "B"
}, {
start: 186.78,
end: 187.00,
value: "A"
}, {
start: 187.00,
end: 188.12,
value: "N"
}, {
start: 188.12,
end: 189.10,
value: "X"
}, {
start: 189.10,
end: 189.33,
value: "N"
}, {
start: 189.33,
end: 189.47,
value: "A"
}, {
start: 189.47,
end: 189.66,
value: "N"
}, {
start: 189.66,
end: 189.71,
value: "A"
}, {
start: 189.71,
end: 190.80,
value: "N"
}, {
start: 190.80,
end: 191.22,
value: "A"
}, {
start: 191.22,
end: 191.46,
value: "N"
}, {
start: 191.46,
end: 191.50,
value: "A"
}, {
start: 191.50,
end: 191.54,
value: "N"
}, {
start: 191.54,
end: 192.52,
value: "A"
}, {
start: 192.52,
end: 192.69,
value: "N"
}, {
start: 192.69,
end: 192.83,
value: "A"
}, {
start: 192.83,
end: 193.25,
value: "B"
}, {
start: 193.25,
end: 193.50,
value: "A"
}, {
start: 193.50,
end: 193.71,
value: "T"
}, {
start: 193.71,
end: 193.82,
value: "B"
}, {
start: 193.82,
end: 194.03,
value: "A"
}, {
start: 194.03,
end: 194.09,
value: "N"
}, {
start: 194.09,
end: 195.53,
value: "B"
}, {
start: 195.53,
end: 195.77,
value: "X"
}, {
start: 195.77,
end: 195.95,
value: "N"
}, {
start: 195.95,
end: 196.65,
value: "A"
}, {
start: 196.65,
end: 198.41,
value: "N"
}, {
start: 198.41,
end: 198.66,
value: "A"
}, {
start: 198.66,
end: 198.80,
value: "N"
}, {
start: 198.80,
end: 199.08,
value: "A"
}, {
start: 199.08,
end: 199.15,
value: "N"
}, {
start: 199.15,
end: 199.43,
value: "A"
}, {
start: 199.43,
end: 199.85,
value: "N"
}, {
start: 199.85,
end: 199.99,
value: "A"
}, {
start: 199.99,
end: 200.20,
value: "N"
}, {
start: 200.20,
end: 200.41,
value: "A"
}, {
start: 200.41,
end: 201.15,
value: "B"
}, {
start: 201.15,
end: 202.25,
value: "A"
}, {
start: 202.25,
end: 202.33,
value: "T"
}, {
start: 202.33,
end: 203.09,
value: "B"
}, {
start: 203.09,
end: 203.19,
value: "A"
}, {
start: 203.19,
end: 203.26,
value: "N"
}, {
start: 203.26,
end: 203.33,
value: "B"
}, {
start: 203.33,
end: 203.89,
value: "A"
}, {
start: 203.89,
end: 206.72,
value: "X"
}];
// Viseme mapping
self.visemeMapping = {
'X': 'closed',
'A': 'AEI',
'B': 'BMP',
'C': 'CDGKNRSTXYZ',
'D': 'AEI',
'E': 'EE',
'F': 'FV',
'G': 'CDGKNRSTXYZ',
'H': 'TH',
// Added TH viseme
'L': 'L',
// Added L viseme
'N': 'N',
// Added N viseme
'O': 'O',
// Added O viseme
'Q': 'QW',
// Added QW viseme
'U': 'U' // Added U viseme
};
self.songStartTime = null;
self.isLipSyncActive = false;
self.currentLipSyncIndex = 0;
// Position tracking variables (keeping your existing setup)
var targetX = GAME_WIDTH / 2;
var targetY = GAME_HEIGHT / 2 + 200;
var smoothingFactor = 0.18;
var prevX = null;
var prevY = null;
// Scale tracking variables (keeping your existing setup)
var scaleHistory = new Array(5).fill(1);
var scaleIndex = 0;
var currentScale = 1;
// Rotation variables (keeping your existing setup)
var targetTilt = 0;
var tiltSmoothingFactor = 0.11;
var tiltScaleFactor = 0.09;
function calculateFaceTilt() {
if (facekit.leftEye && facekit.rightEye && facekit.mouthCenter) {
var eyeMidX = (facekit.leftEye.x + facekit.rightEye.x) / 2;
var eyeMidY = (facekit.leftEye.y + facekit.rightEye.y) / 2;
var dx = facekit.mouthCenter.x - eyeMidX;
var dy = facekit.mouthCenter.y - eyeMidY;
var angle = -(Math.atan2(dx, dy) * (180 / Math.PI));
return Math.max(-15, Math.min(15, angle * 0.15));
}
return 0;
}
self.startLipSync = function () {
self.songStartTime = Date.now();
self.isLipSyncActive = true;
self.currentLipSyncIndex = 0;
};
self.setViseme = function (visemeName) {
if (self.currentViseme !== visemeName && self.visemes[visemeName]) {
if (self.visemes[self.currentViseme]) {
self.visemes[self.currentViseme].visible = false;
}
self.visemes[visemeName].visible = true;
self.currentViseme = visemeName;
}
};
self.updateAutomatedLipSync = function () {
if (!self.isLipSyncActive || !self.songStartTime || self.automatedLipSync.length === 0) {
return;
}
var currentTimeSeconds = (Date.now() - self.songStartTime) / 1000.0;
var targetVisemeKey = 'closed';
// Start searching from currentLipSyncIndex for optimization
for (var i = self.currentLipSyncIndex; i < self.automatedLipSync.length; i++) {
var cue = self.automatedLipSync[i];
if (currentTimeSeconds >= cue.start && currentTimeSeconds < cue.end) {
var automatedCode = cue.value;
targetVisemeKey = self.visemeMapping[automatedCode] || 'closed';
self.currentLipSyncIndex = i;
break;
}
if (currentTimeSeconds >= cue.end) {
if (i === self.automatedLipSync.length - 1) {
var automatedCode = cue.value;
targetVisemeKey = self.visemeMapping[automatedCode] || 'closed';
}
} else if (currentTimeSeconds < cue.start) {
break;
}
}
if (self.currentLipSyncIndex === self.automatedLipSync.length - 1 && currentTimeSeconds >= self.automatedLipSync[self.automatedLipSync.length - 1].end) {
targetVisemeKey = 'closed';
}
self.setViseme(targetVisemeKey);
};
self.update = function () {
// Update automated lip sync
self.updateAutomatedLipSync();
// Smooth position tracking
if (facekit.mouthCenter) {
targetX = facekit.mouthCenter.x;
targetY = facekit.mouthCenter.y;
if (prevX === null) {
prevX = targetX;
prevY = targetY;
}
var newX = prevX * (1 - smoothingFactor) + targetX * smoothingFactor;
var newY = prevY * (1 - smoothingFactor) + targetY * smoothingFactor;
self.x = newX;
self.y = newY;
prevX = newX;
prevY = newY;
}
// Dynamic scale adjustment
if (facekit.leftEye && facekit.rightEye) {
var eyeDistance = Math.abs(facekit.rightEye.x - facekit.leftEye.x);
var newScale = eyeDistance / 250;
scaleHistory[scaleIndex] = newScale;
scaleIndex = (scaleIndex + 1) % scaleHistory.length;
var avgScale = scaleHistory.reduce(function (a, b) {
return a + b;
}, 0) / scaleHistory.length;
var targetScale = avgScale;
currentScale = currentScale * 0.85 + targetScale * 0.15;
Object.keys(self.visemes).forEach(function (key) {
self.visemes[key].scaleX = currentScale;
self.visemes[key].scaleY = currentScale;
});
}
// Rotation tracking
if (facekit.leftEye && facekit.rightEye) {
targetTilt = calculateFaceTilt() * tiltScaleFactor;
targetTilt = Math.max(-15, Math.min(15, targetTilt));
self.rotation += (targetTilt - self.rotation) * tiltSmoothingFactor;
}
};
return self;
});
var StartButton = Container.expand(function () {
var self = Container.call(this);
self.buttonGraphics = self.attachAsset('startButtonImage', {
anchorX: 0.5,
anchorY: 0.5
});
self.down = function () {
// This will call the globally defined startGame function
// when the button is pressed.
if (typeof handleStartButtonPressed === 'function') {
handleStartButtonPressed();
}
};
return self;
});
var SubtitleDisplay = Container.expand(function () {
var self = Container.call(this);
// Create text display properties
self.currentText = "";
self.textDisplay = null;
self.subtitleData = [{
start: 4.8,
end: 7.5,
text: "From the ashes of tragedy"
}, {
start: 10.0,
end: 12.5,
text: "A guardian rises"
}, {
start: 14.0,
end: 16.0,
text: "Not the hero they deserve"
}, {
start: 17.5,
end: 19.5,
text: "But the one they need"
}, {
start: 22.5,
end: 24.5,
text: "Parents fallen in an alley cold"
}, {
start: 25.0,
end: 29.0,
text: "A boy's heart turned to stone"
}, {
start: 33.0,
end: 35.0,
text: "Fortune built on others' pain"
}, {
start: 36.0,
end: 40.0,
text: "Now I claim the night as my own"
}, {
start: 43.5,
end: 46.0,
text: "Gotham calls to me in whispers dire"
}, {
start: 47.0,
end: 51.0,
text: "Criminals scatter at my arrival"
}, {
start: 53.5,
end: 56.0,
text: "The cape becomes my second skin"
}, {
start: 57.0,
end: 62.0,
text: "This mask - my true survival"
},
// Chorus
{
start: 63.5,
end: 69.0,
text: "From the darkness I emerge"
}, {
start: 75.0,
end: 79.0,
text: "Justice is what I serve"
}, {
start: 85.5,
end: 90.0,
text: "When evil plagues these streets"
}, {
start: 90.5,
end: 92.0,
text: "I'll be there"
}, {
start: 93.5,
end: 94.5,
text: "I'll be there"
}, {
start: 96.0,
end: 97.0,
text: "I'm Batman"
}, {
start: 98.5,
end: 100.0,
text: "I'm Batman"
}, {
start: 101.5,
end: 102.5,
text: "I'm Batman"
}, {
start: 103.0,
end: 105.0,
text: "I'm Batman"
},
// Verse 2
{
start: 109.0,
end: 112.5,
text: "A gallery of madness I've faced"
}, {
start: 114.0,
end: 116.5,
text: "The Joker with his twisted grin"
}, {
start: 120.0,
end: 123.8,
text: "Riddler's puzzles, Penguin's schemes"
}, {
start: 124.0,
end: 128.5,
text: "Two-Face's coin, the battle within"
}, {
start: 130.0,
end: 132.0,
text: "My allies few, my burden great"
}, {
start: 133.0,
end: 137.0,
text: "The cave below, my sanctuary"
}, {
start: 140.5,
end: 144.5,
text: "Alfred's wisdom guides my path"
}, {
start: 145.0,
end: 150.5,
text: "Through this life so solitary"
},
// Chorus repeat
{
start: 151.5,
end: 157.0,
text: "From the darkness I emerge"
}, {
start: 163.5,
end: 167.5,
text: "Justice is what I serve"
}, {
start: 173.5,
end: 178.0,
text: "When evil plagues these streets"
}, {
start: 178.5,
end: 180.0,
text: "I'll be there"
}, {
start: 181.0,
end: 182.5,
text: "I'll be there"
}, {
start: 184.0,
end: 185.0,
text: "I'm Batman"
}, {
start: 186.5,
end: 188.0,
text: "I'm Batman"
}, {
start: 189.0,
end: 190.5,
text: "I'm Batman"
}, {
start: 191.5,
end: 193.0,
text: "I'm Batman"
},
// Outro
{
start: 194.0,
end: 197.0,
text: "Na na na na na na na na"
}, {
start: 197.0,
end: 200.0,
text: "Na na na na na na na na"
}, {
start: 198.0,
end: 198.5,
text: "Batman"
}, {
start: 199.0,
end: 202.0,
text: "Na na na na na na na na"
}, {
start: 202.0,
end: 205.0,
text: "Na na na na na na na na"
}, {
start: 203.0,
end: 204.5,
text: "Batman!"
}];
self.songStartTime = null;
self.isActive = false;
self.currentSubtitleIndex = 0;
// Text styling properties
self.textStyle = {
fontFamily: 'Arial',
// Font family for Text2
fontSize: 96,
// Font size (doubled)
// fontWeight: 'bold', // fontWeight is part of font string in Text2 if specific bold font is used
color: '#FFFFFF',
// Fill color
stroke: '#000000',
// Stroke color
strokeWidth: 3,
// Mapped to strokeThickness in Text2
textAlign: 'center' // Mapped to align in Text2
// textBaseline: 'middle', // Handled by anchor in Text2
// shadowColor: '#000000', // Shadow not directly supported by LK Text2
// shadowBlur: 8,
// shadowOffsetX: 2,
// shadowOffsetY: 2
};
self.createTextGraphics = function (text) {
if (self.textDisplay) {
// If a previous text object exists
self.textDisplay.destroy(); // Destroy it
self.textDisplay = null; // Clear the reference
}
if (!text || text.trim() === "") {
// If new text is empty, do nothing further
return;
}
// Create new Text2 object
self.textDisplay = new Text2(text, {
font: self.textStyle.fontFamily,
size: self.textStyle.fontSize,
fill: self.textStyle.color,
stroke: self.textStyle.stroke,
strokeThickness: self.textStyle.strokeWidth,
align: self.textStyle.textAlign
});
self.textDisplay.anchor.set(0.5, 0.5); // Center the text within its bounds
self.addChild(self.textDisplay); // Add the new text display to the container
};
self.startSubtitles = function () {
self.songStartTime = Date.now();
self.isActive = true;
self.currentSubtitleIndex = 0;
};
self.updateSubtitles = function () {
if (!self.isActive || !self.songStartTime || self.subtitleData.length === 0) {
return;
}
var currentTimeSeconds = (Date.now() - self.songStartTime) / 1000.0;
var newText = "";
var foundSubtitle = false;
// Find current subtitle
// Iterate from the current index for efficiency or from start if reset needed
for (var i = self.currentSubtitleIndex; i < self.subtitleData.length; i++) {
var subtitle = self.subtitleData[i];
if (currentTimeSeconds >= subtitle.start && currentTimeSeconds < subtitle.end) {
newText = subtitle.text;
self.currentSubtitleIndex = i; // Keep track of the current subtitle index
foundSubtitle = true;
break;
}
// If current time is less than the start of this subtitle, means no future subtitle will match yet.
if (currentTimeSeconds < subtitle.start) {
break;
}
// If time has passed this subtitle's end, it might be the one if nothing else matches
if (currentTimeSeconds >= subtitle.end) {
// If it's the last subtitle and we've passed its start time, it might still be active until next change or song end
// This logic clears the subtitle if we've passed its end time and it's not picked up by another.
}
}
// If no subtitle is active for the current time (e.g., between cues), clear the text.
if (!foundSubtitle && currentTimeSeconds > 0) {
// Check currentTimeSeconds > 0 to avoid clearing at very start
var shouldClear = true;
// Check if we are before the first subtitle or after the last one
if (self.subtitleData.length > 0) {
if (currentTimeSeconds < self.subtitleData[0].start) {
shouldClear = true;
} else {
// Check if we are between subtitles
for (var k = 0; k < self.subtitleData.length; k++) {
if (currentTimeSeconds >= self.subtitleData[k].start && currentTimeSeconds < self.subtitleData[k].end) {
shouldClear = false; // We are in a subtitle, don't clear
break;
}
}
}
}
if (shouldClear) {
newText = "";
}
}
// Update display if text changed
if (newText !== self.currentText) {
self.currentText = newText;
self.createTextGraphics(newText);
}
};
self.setPosition = function (x, y) {
self.x = x;
self.y = y;
};
self.update = function () {
self.updateSubtitles();
};
return self;
});
/****
* Initialize Game
****/
// Facekit provides the camera feed as background, so no explicit backgroundColor needed.
var game = new LK.Game({});
/****
* Game Code
****/
// Game state variables
var isGameActive = false;
var startButtonInstance;
var maskInstance;
var mouthAnimatorInstance;
var subtitleInstance;
var songDuration = 206.72; // Song duration in seconds based on lip sync data
var musicStartTime = null;
var songCompleted = false;
// Game constants
var GAME_WIDTH = 2048;
var GAME_HEIGHT = 2732;
var AUTOMATED_VISEME_MAPPING = {
'X': 'closed',
// Silence/rest position
'A': 'AEI',
// Open vowels (father, palm)
'B': 'BMP',
// Lip closure (b, m, p)
'C': 'CDGKNRSTXYZ',
// Consonants (c, d, g, k, n, r, s, t, x, y, z)
'D': 'AEI',
// Mid vowels (day, face)
'E': 'EE',
// High front vowels (see, fleece)
'F': 'FV',
// Lip-teeth contact (f, v)
'G': 'CDGKNRSTXYZ',
// Back consonants (g, ng)
'H': 'CHJSH' // Fricatives (h, ch, j, sh)
};
function handleStartButtonPressed() {
if (isGameActive) {
return;
}
isGameActive = true;
// Remove start button
if (startButtonInstance && startButtonInstance.parent) {
startButtonInstance.destroy();
startButtonInstance = null;
}
// Create and show mask
maskInstance = new Mask();
game.addChild(maskInstance);
var maskGraphicHeight = maskInstance.maskGraphics.height;
var yOffset = -50;
var maskTargetX, maskTargetY;
if (facekit.leftEye && facekit.rightEye && facekit.leftEye.x !== 0 && facekit.rightEye.x !== 0) {
maskTargetX = (facekit.leftEye.x + facekit.rightEye.x) / 2;
maskTargetY = (facekit.leftEye.y + facekit.rightEye.y) / 2 - maskGraphicHeight * 0.20 + yOffset;
} else {
maskTargetX = GAME_WIDTH / 2;
maskTargetY = GAME_HEIGHT / 3 + yOffset;
}
// Create mouth animator
mouthAnimatorInstance = new MouthAnimator();
var mouthInitialX = facekit.mouthCenter && facekit.mouthCenter.x !== 0 ? facekit.mouthCenter.x : GAME_WIDTH / 2;
var mouthInitialY = facekit.mouthCenter && facekit.mouthCenter.y !== 0 ? facekit.mouthCenter.y : GAME_HEIGHT / 2 + 200;
mouthAnimatorInstance.x = mouthInitialX;
mouthAnimatorInstance.y = mouthInitialY;
game.addChild(mouthAnimatorInstance);
// Create subtitle display
subtitleInstance = new SubtitleDisplay();
subtitleInstance.setPosition(GAME_WIDTH / 2, GAME_HEIGHT - 124 - GAME_HEIGHT * 0.1); // Position moved up 10% from previous position
game.addChild(subtitleInstance);
// Show mask with callback to start music and lip-sync when animation completes
maskInstance.show(maskTargetX, maskTargetY, 1200);
// Start music, lip-sync, and subtitles after mask animation completes (1.2 seconds)
LK.setTimeout(function () {
LK.playMusic('batmansong');
musicStartTime = Date.now(); // Track when music started
songCompleted = false; // Reset completion flag
if (mouthAnimatorInstance) {
// Ensure mouthAnimatorInstance still exists
mouthAnimatorInstance.startLipSync();
}
if (subtitleInstance) {
// Ensure subtitleInstance still exists
subtitleInstance.startSubtitles();
}
}, 1200);
}
// Initial game setup
function initializeGameScreen() {
isGameActive = false; // Reset game state
// Create and position the start button
startButtonInstance = new StartButton();
// Center the button horizontally
startButtonInstance.x = GAME_WIDTH / 2;
// Position it towards the bottom of the screen
var buttonHeight = startButtonInstance.buttonGraphics.height;
startButtonInstance.y = GAME_HEIGHT - buttonHeight / 2 - 100; // 100px padding from bottom
game.addChild(startButtonInstance);
}
// Call initial setup
initializeGameScreen();
// Game update loop
game.update = function () {
if (isGameActive) {
// Check if song has completed and stop music
if (musicStartTime && !songCompleted) {
var currentMusicTime = (Date.now() - musicStartTime) / 1000.0;
if (currentMusicTime >= songDuration) {
LK.stopMusic();
songCompleted = true;
}
}
// Update mouth animator with enhanced tracking
if (mouthAnimatorInstance) {
// mouthAnimatorInstance.updateViseme(facekit.volume); // Viseme update is now handled by updateLipSync within MouthAnimator's update
mouthAnimatorInstance.update(); // This now includes smooth position tracking, scaling, and lip-sync
}
// Update mask with enhanced tracking
if (maskInstance) {
maskInstance.update(); // This now includes smooth position tracking and scaling
}
if (subtitleInstance) {
subtitleInstance.update();
}
}
}; /****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
var facekit = LK.import("@upit/facekit.v1");
/****
* Classes
****/
var Mask = Container.expand(function () {
var self = Container.call(this);
// Store maskGraphics on self to access its properties like height if needed later
self.maskGraphics = self.attachAsset('maskImage', {
anchorX: 0.5,
anchorY: 0.4
});
self.animationFinished = false; // Flag to track if the intro animation is done
var targetX = GAME_WIDTH / 2;
var targetY = GAME_HEIGHT / 3;
var smoothingFactor = 0.15; // Higher value for more responsive tracking
var prevX = null;
var prevY = null;
// Scale tracking variables (from dragon example)
var scaleHistory = new Array(5).fill(1); // Start with default scale of 1
var scaleIndex = 0;
var currentScale = 1;
// Rotation variables - EXACTLY from working example
var targetTilt = 0;
var tiltSmoothingFactor = 0.11;
var tiltScaleFactor = 0.09;
self.show = function (targetX, targetY, duration) {
self.x = targetX;
self.y = -self.maskGraphics.height / 2 - 50;
tween(self, {
y: targetY
}, {
duration: duration,
easing: tween.easeOutSine,
onFinish: function onFinish() {
self.animationFinished = true;
}
});
};
// Function copied EXACTLY from working DragonHead example
function calculateFaceTilt() {
if (facekit.leftEye && facekit.rightEye && facekit.mouthCenter) {
// Calculate midpoint between eyes
var eyeMidX = (facekit.leftEye.x + facekit.rightEye.x) / 2;
var eyeMidY = (facekit.leftEye.y + facekit.rightEye.y) / 2;
// Calculate angle between eye midpoint and mouth, negated to fix direction
var dx = facekit.mouthCenter.x - eyeMidX;
var dy = facekit.mouthCenter.y - eyeMidY;
var angle = -(Math.atan2(dx, dy) * (180 / Math.PI));
// Reduced angle impact
return Math.max(-15, Math.min(15, angle * 0.15));
}
return 0; // Default to straight when face points aren't available
}
// Enhanced update method with smooth position tracking and dynamic scaling
self.update = function () {
if (!self.animationFinished) {
return;
}
// Smooth position tracking (from dragon example)
if (facekit.leftEye && facekit.rightEye) {
// --- SCALE CALCULATION ---
var eyeDistance = Math.abs(facekit.rightEye.x - facekit.leftEye.x);
var newScaleFactor = eyeDistance / 300; // Base scale factor from eye distance
scaleHistory[scaleIndex] = newScaleFactor;
scaleIndex = (scaleIndex + 1) % scaleHistory.length;
var avgScale = scaleHistory.reduce(function (a, b) {
return a + b;
}, 0) / scaleHistory.length;
var targetScaleValue = avgScale; // Smoothed target scale
currentScale = currentScale * 0.85 + targetScaleValue * 0.15; // Final smoothed currentScale
// --- POSITION CALCULATION WITH Y-COMPENSATION ---
var eyeMidX = (facekit.leftEye.x + facekit.rightEye.x) / 2;
var eyeMidY = (facekit.leftEye.y + facekit.rightEye.y) / 2;
targetX = eyeMidX; // Target X is the midpoint between eyes
// Base target Y positions the anchor (0.4 from top of graphic) relative to eyes
var baseTargetY = eyeMidY - self.maskGraphics.height * 0.20 - 50;
// Y-compensation: lower the mask when below standard scale (< 1.0), raise it slightly when above (> 1.0)
var yOffsetCompensation = 0;
if (currentScale < 1) {
yOffsetCompensation = self.maskGraphics.height * 0.4 * (1 - currentScale);
} else if (currentScale > 1) {
// Raise mask slightly as it grows above base scale (gentle effect)
yOffsetCompensation = -self.maskGraphics.height * 0.08 * (currentScale - 1);
}
targetY = baseTargetY + yOffsetCompensation; // Final targetY including compensation
// Initialize previous positions if not set
if (prevX === null) {
prevX = targetX;
prevY = targetY;
}
// Weighted average for smooth tracking
var newX = prevX * (1 - smoothingFactor) + targetX * smoothingFactor;
var newY = prevY * (1 - smoothingFactor) + targetY * smoothingFactor;
self.x = newX;
self.y = newY;
// Update previous positions
prevX = newX;
prevY = newY;
// --- APPLY SCALE TO MASK GRAPHICS ---
self.maskGraphics.scaleX = currentScale;
self.maskGraphics.scaleY = currentScale;
}
// Rotation tracking - COPIED EXACTLY from working example
if (facekit.leftEye && facekit.rightEye) {
targetTilt = calculateFaceTilt() * tiltScaleFactor;
// Limit rotation to ±15 degrees - DON'T convert to radians here
targetTilt = Math.max(-15, Math.min(15, targetTilt));
self.rotation += (targetTilt - self.rotation) * tiltSmoothingFactor;
}
};
return self;
});
var MouthAnimator = Container.expand(function () {
var self = Container.call(this);
// Load all viseme assets (keeping your existing setup)
self.visemes = {
closed: self.attachAsset('visemeClosed', {
anchorX: 0.5,
anchorY: 0.5,
visible: false
}),
AEI: self.attachAsset('visemeAEI', {
anchorX: 0.5,
anchorY: 0.5,
visible: false
}),
BMP: self.attachAsset('visemeBMP', {
anchorX: 0.5,
anchorY: 0.5,
visible: false
}),
CDGKNRSTXYZ: self.attachAsset('visemeCDGKNRSTXYZ', {
anchorX: 0.5,
anchorY: 0.5,
visible: false
}),
CHJSH: self.attachAsset('visemeCHJSH', {
anchorX: 0.5,
anchorY: 0.5,
visible: false
}),
EE: self.attachAsset('visemeEE', {
anchorX: 0.5,
anchorY: 0.5,
visible: false
}),
FV: self.attachAsset('visemeFV', {
anchorX: 0.5,
anchorY: 0.5,
visible: false
}),
L: self.attachAsset('visemeL', {
anchorX: 0.5,
anchorY: 0.5,
visible: false
}),
N: self.attachAsset('visemeN', {
anchorX: 0.5,
anchorY: 0.5,
visible: false
}),
O: self.attachAsset('visemeO', {
anchorX: 0.5,
anchorY: 0.5,
visible: false
}),
TH: self.attachAsset('visemeTH', {
anchorX: 0.5,
anchorY: 0.5,
visible: false
}),
U: self.attachAsset('visemeU', {
anchorX: 0.5,
anchorY: 0.5,
visible: false
}),
QW: self.attachAsset('visimeQW', {
anchorX: 0.5,
anchorY: 0.5,
visible: false
})
};
self.currentViseme = 'closed';
self.visemes.closed.visible = true;
// Automated lip sync data (converted from your JSON)
self.automatedLipSync = [{
start: 0.00,
end: 5.03,
value: "X"
}, {
start: 5.03,
end: 5.16,
value: "B"
}, {
start: 5.16,
end: 5.23,
value: "E"
}, {
start: 5.23,
end: 5.30,
value: "F"
}, {
start: 5.30,
end: 5.40,
value: "A"
}, {
start: 5.40,
end: 5.54,
value: "C"
}, {
start: 5.54,
end: 5.61,
value: "B"
}, {
start: 5.61,
end: 5.82,
value: "C"
}, {
start: 5.82,
end: 5.96,
value: "B"
}, {
start: 5.96,
end: 6.03,
value: "C"
}, {
start: 6.03,
end: 6.31,
value: "B"
}, {
start: 6.31,
end: 6.52,
value: "F"
}, {
start: 6.52,
end: 6.63,
value: "G"
}, {
start: 6.63,
end: 7.43,
value: "E"
}, {
start: 7.43,
end: 9.85,
value: "X"
}, {
start: 9.85,
end: 10.03,
value: "E"
}, {
start: 10.03,
end: 10.24,
value: "C"
}, {
start: 10.24,
end: 10.38,
value: "A"
}, {
start: 10.38,
end: 10.52,
value: "F"
}, {
start: 10.52,
end: 10.59,
value: "D"
}, {
start: 10.59,
end: 11.15,
value: "C"
}, {
start: 11.15,
end: 11.23,
value: "A"
}, {
start: 11.23,
end: 11.54,
value: "D"
}, {
start: 11.54,
end: 11.62,
value: "C"
}, {
start: 11.62,
end: 12.77,
value: "G"
}, {
start: 12.77,
end: 13.77,
value: "X"
}, {
start: 13.77,
end: 13.83,
value: "N"
}, {
start: 13.83,
end: 14.44,
value: "O"
}, {
start: 14.44,
end: 14.79,
value: "C"
}, {
start: 14.79,
end: 15.01,
value: "F"
}, {
start: 15.01,
end: 15.09,
value: "A"
}, {
start: 15.09,
end: 15.13,
value: "B"
}, {
start: 15.13,
end: 15.38,
value: "F"
}, {
start: 15.38,
end: 15.45,
value: "B"
}, {
start: 15.45,
end: 16.08,
value: "E"
}, {
start: 16.08,
end: 16.29,
value: "B"
}, {
start: 16.29,
end: 17.16,
value: "X"
}, {
start: 17.16,
end: 17.23,
value: "C"
}, {
start: 17.23,
end: 17.29,
value: "B"
}, {
start: 17.29,
end: 17.79,
value: "N"
}, {
start: 17.79,
end: 18.99,
value: 'E'
}, {
start: 18.99,
end: 19.53,
value: "G"
}, {
start: 19.53,
end: 22.08,
value: "X"
}, {
start: 22.08,
end: 22.42,
value: "E"
}, {
start: 22.42,
end: 22.77,
value: "B"
}, {
start: 22.77,
end: 22.85,
value: "A"
}, {
start: 22.85,
end: 23.02,
value: "N"
}, {
start: 23.02,
end: 23.30,
value: "C"
}, {
start: 23.30,
end: 23.65,
value: "C"
}, {
start: 23.65,
end: 23.86,
value: "B"
}, {
start: 23.86,
end: 24.49,
value: "E"
}, {
start: 24.49,
end: 24.91,
value: "L"
}, {
start: 24.91,
end: 24.98,
value: "E"
}, {
start: 24.98,
end: 25.05,
value: "N"
}, {
start: 25.05,
end: 25.33,
value: "E"
}, {
start: 25.33,
end: 25.75,
value: "B"
}, {
start: 25.75,
end: 25.82,
value: "D"
}, {
start: 25.82,
end: 25.96,
value: "B"
}, {
start: 25.96,
end: 26.17,
value: "F"
}, {
start: 26.17,
end: 26.45,
value: "E"
}, {
start: 26.45,
end: 27.08,
value: "B"
}, {
start: 27.08,
end: 27.43,
value: "E"
}, {
start: 27.43,
end: 27.57,
value: "F"
}, {
start: 27.57,
end: 27.71,
value: "C"
}, {
start: 27.71,
end: 28.48,
value: "O"
}, {
start: 28.48,
end: 32.70,
value: "X"
}, {
start: 32.70,
end: 32.82,
value: "B"
}, {
start: 32.82,
end: 33.03,
value: "O"
}, {
start: 33.03,
end: 33.45,
value: "E"
}, {
start: 33.45,
end: 33.66,
value: "E"
}, {
start: 33.66,
end: 33.73,
value: "C"
}, {
start: 33.73,
end: 33.87,
value: "B"
}, {
start: 33.87,
end: 34.01,
value: "E"
}, {
start: 34.01,
end: 34.29,
value: "C"
}, {
start: 34.29,
end: 34.36,
value: "B"
}, {
start: 34.36,
end: 34.50,
value: "E"
}, {
start: 34.50,
end: 34.71,
value: "B"
}, {
start: 34.71,
end: 34.85,
value: "E"
}, {
start: 34.85,
end: 35.13,
value: "C"
}, {
start: 35.13,
end: 35.20,
value: "C"
}, {
start: 35.20,
end: 35.48,
value: "B"
}, {
start: 35.48,
end: 35.55,
value: "C"
}, {
start: 35.55,
end: 35.76,
value: "E"
}, {
start: 35.76,
end: 35.90,
value: "F"
}, {
start: 35.90,
end: 36.60,
value: "B"
}, {
start: 36.60,
end: 36.71,
value: "A"
}, {
start: 36.71,
end: 36.89,
value: "O"
}, {
start: 36.89,
end: 36.94,
value: "N"
}, {
start: 36.94,
end: 37.47,
value: "B"
}, {
start: 37.47,
end: 37.55,
value: "A"
}, {
start: 37.55,
end: 37.65,
value: "E"
}, {
start: 37.65,
end: 37.72,
value: "B"
}, {
start: 37.72,
end: 37.92,
value: "F"
}, {
start: 37.92,
end: 38.00,
value: "E"
}, {
start: 38.00,
end: 40.10,
value: "O"
}, {
start: 40.10,
end: 43.42,
value: "X"
}, {
start: 43.42,
end: 43.67,
value: "C"
}, {
start: 43.67,
end: 43.74,
value: "O"
}, {
start: 43.74,
end: 43.88,
value: "F"
}, {
start: 43.88,
end: 44.23,
value: "A"
}, {
start: 44.23,
end: 44.44,
value: "C"
}, {
start: 44.44,
end: 44.58,
value: "B"
}, {
start: 44.58,
end: 44.79,
value: "C"
}, {
start: 44.79,
end: 45.07,
value: "O"
}, {
start: 45.07,
end: 45.35,
value: "L"
}, {
start: 45.35,
end: 45.63,
value: "C"
}, {
start: 45.63,
end: 45.70,
value: "B"
}, {
start: 45.70,
end: 46.12,
value: "E"
}, {
start: 46.12,
end: 46.40,
value: "E"
}, {
start: 46.40,
end: 46.45,
value: "B"
}, {
start: 46.45,
end: 47.13,
value: "F"
}, {
start: 47.13,
end: 47.27,
value: "C"
}, {
start: 47.27,
end: 47.34,
value: "B"
}, {
start: 47.34,
end: 47.55,
value: "E"
}, {
start: 47.55,
end: 47.76,
value: "C"
}, {
start: 47.76,
end: 47.83,
value: "B"
}, {
start: 47.83,
end: 48.04,
value: "D"
}, {
start: 48.04,
end: 48.11,
value: "B"
}, {
start: 48.11,
end: 48.81,
value: "F"
}, {
start: 48.81,
end: 48.88,
value: "A"
}, {
start: 48.88,
end: 48.95,
value: "L"
}, {
start: 48.95,
end: 49.02,
value: "E"
}, {
start: 49.02,
end: 50.28,
value: "B"
}, {
start: 50.28,
end: 50.61,
value: "A"
}, {
start: 50.61,
end: 53.74,
value: "X"
}, {
start: 53.74,
end: 54.23,
value: "C"
}, {
start: 54.23,
end: 54.31,
value: "A"
}, {
start: 54.31,
end: 54.51,
value: "B"
}, {
start: 54.51,
end: 54.79,
value: "E"
}, {
start: 54.79,
end: 54.90,
value: "A"
}, {
start: 54.90,
end: 55.01,
value: "B"
}, {
start: 55.01,
end: 55.12,
value: "A"
}, {
start: 55.12,
end: 55.18,
value: "C"
}, {
start: 55.18,
end: 55.52,
value: "B"
}, {
start: 55.52,
end: 55.59,
value: "C"
}, {
start: 55.59,
end: 55.87,
value: "B"
}, {
start: 55.87,
end: 56.22,
value: "F"
}, {
start: 56.22,
end: 56.36,
value: "B"
}, {
start: 56.36,
end: 56.66,
value: "F"
}, {
start: 56.66,
end: 56.74,
value: "E"
}, {
start: 56.74,
end: 56.89,
value: "C"
}, {
start: 56.89,
end: 57.17,
value: "A"
}, {
start: 57.17,
end: 57.69,
value: "N"
}, {
start: 57.69,
end: 57.92,
value: "C"
}, {
start: 57.92,
end: 58.20,
value: "B"
}, {
start: 58.20,
end: 59.04,
value: "F"
}, {
start: 59.04,
end: 59.18,
value: "B"
}, {
start: 59.18,
end: 59.39,
value: "E"
}, {
start: 59.39,
end: 59.53,
value: "C"
}, {
start: 59.53,
end: 59.98,
value: "O"
}, {
start: 59.98,
end: 60.06,
value: "N"
}, {
start: 60.06,
end: 60.37,
value: "B"
}, {
start: 60.37,
end: 60.51,
value: "C"
}, {
start: 60.51,
end: 60.65,
value: "E"
}, {
start: 60.65,
end: 62.19,
value: "N"
}, {
start: 62.19,
end: 62.33,
value: "X"
}, {
start: 62.33,
end: 62.55,
value: "B"
}, {
start: 62.55,
end: 63.24,
value: "X"
}, {
start: 63.24,
end: 63.38,
value: "B"
}, {
start: 63.38,
end: 63.73,
value: "E"
}, {
start: 63.73,
end: 63.87,
value: "C"
}, {
start: 63.87,
end: 63.99,
value: "A"
}, {
start: 63.99,
end: 64.11,
value: "C"
}, {
start: 64.11,
end: 64.46,
value: "B"
}, {
start: 64.46,
end: 64.60,
value: "F"
}, {
start: 64.60,
end: 64.67,
value: "B"
}, {
start: 64.67,
end: 64.88,
value: "F"
}, {
start: 64.88,
end: 64.95,
value: "C"
}, {
start: 64.95,
end: 65.88,
value: "B"
}, {
start: 65.88,
end: 66.43,
value: "X"
}, {
start: 66.43,
end: 66.62,
value: "D"
}, {
start: 66.62,
end: 66.81,
value: "C"
}, {
start: 66.81,
end: 66.86,
value: "E"
}, {
start: 66.86,
end: 67.11,
value: "C"
}, {
start: 67.11,
end: 68.93,
value: "B"
}, {
start: 68.93,
end: 69.14,
value: "X"
}, {
start: 69.14,
end: 70.99,
value: "A"
}, {
start: 70.99,
end: 71.13,
value: "F"
}, {
start: 71.13,
end: 72.63,
value: "A"
}, {
start: 72.63,
end: 72.71,
value: "C"
}, {
start: 72.71,
end: 73.16,
value: "B"
}, {
start: 73.16,
end: 75.30,
value: "X"
}, {
start: 75.30,
end: 75.57,
value: "B"
}, {
start: 75.57,
end: 75.71,
value: "E"
}, {
start: 75.71,
end: 75.99,
value: "B"
}, {
start: 75.99,
end: 76.27,
value: "C"
}, {
start: 76.27,
end: 76.34,
value: "B"
}, {
start: 76.34,
end: 76.83,
value: "F"
}, {
start: 76.83,
end: 77.04,
value: "E"
}, {
start: 77.04,
end: 77.11,
value: "C"
}, {
start: 77.11,
end: 77.18,
value: "H"
}, {
start: 77.18,
end: 77.37,
value: "E"
}, {
start: 77.37,
end: 77.42,
value: "C"
}, {
start: 77.42,
end: 77.81,
value: "B"
}, {
start: 77.81,
end: 79.14,
value: "F"
}, {
start: 79.14,
end: 79.48,
value: "B"
}, {
start: 79.48,
end: 80.18,
value: "E"
}, {
start: 80.18,
end: 80.46,
value: "C"
}, {
start: 80.46,
end: 83.12,
value: "B"
}, {
start: 83.12,
end: 84.24,
value: "E"
}, {
start: 84.24,
end: 84.45,
value: "B"
}, {
start: 84.45,
end: 85.37,
value: "X"
}, {
start: 85.37,
end: 85.55,
value: "F"
}, {
start: 85.55,
end: 86.08,
value: "B"
}, {
start: 86.08,
end: 86.62,
value: "F"
}, {
start: 86.62,
end: 86.67,
value: "C"
}, {
start: 86.67,
end: 87.00,
value: "E"
}, {
start: 87.00,
end: 87.28,
value: "F"
}, {
start: 87.28,
end: 87.42,
value: "D"
}, {
start: 87.42,
end: 87.70,
value: "C"
}, {
start: 87.70,
end: 88.54,
value: "B"
}, {
start: 88.54,
end: 89.42,
value: "D"
}, {
start: 89.42,
end: 89.49,
value: "C"
}, {
start: 89.49,
end: 89.73,
value: "B"
}, {
start: 89.73,
end: 90.46,
value: "X"
}, {
start: 90.46,
end: 90.81,
value: "E"
}, {
start: 90.81,
end: 91.27,
value: "F"
}, {
start: 91.27,
end: 91.34,
value: "E"
}, {
start: 91.34,
end: 91.41,
value: "F"
}, {
start: 91.41,
end: 92.11,
value: "B"
}, {
start: 92.11,
end: 93.07,
value: "X"
}, {
start: 93.07,
end: 93.33,
value: "C"
}, {
start: 93.33,
end: 93.40,
value: "E"
}, {
start: 93.40,
end: 93.78,
value: "F"
}, {
start: 93.78,
end: 93.85,
value: "B"
}, {
start: 93.85,
end: 94.16,
value: "D"
}, {
start: 94.16,
end: 94.24,
value: "C"
}, {
start: 94.24,
end: 95.04,
value: "B"
}, {
start: 95.04,
end: 95.79,
value: "X"
}, {
start: 95.79,
end: 95.89,
value: "C"
}, {
start: 95.89,
end: 95.96,
value: "B"
}, {
start: 95.96,
end: 96.05,
value: "A"
}, {
start: 96.05,
end: 96.31,
value: "C"
}, {
start: 96.31,
end: 96.49,
value: "B"
}, {
start: 96.49,
end: 97.18,
value: "D"
}, {
start: 97.18,
end: 97.26,
value: "C"
}, {
start: 97.26,
end: 98.43,
value: "X"
}, {
start: 98.43,
end: 98.54,
value: "E"
}, {
start: 98.54,
end: 98.61,
value: "F"
}, {
start: 98.61,
end: 98.68,
value: "C"
}, {
start: 98.68,
end: 98.75,
value: "B"
}, {
start: 98.75,
end: 98.82,
value: "F"
}, {
start: 98.82,
end: 99.10,
value: "C"
}, {
start: 99.10,
end: 99.18,
value: "A"
}, {
start: 99.18,
end: 99.64,
value: "C"
}, {
start: 99.64,
end: 99.85,
value: "B"
}, {
start: 99.85,
end: 101.13,
value: "X"
}, {
start: 101.13,
end: 101.26,
value: "D"
}, {
start: 101.26,
end: 101.33,
value: "B"
}, {
start: 101.33,
end: 101.45,
value: "A"
}, {
start: 101.45,
end: 101.86,
value: "C"
}, {
start: 101.86,
end: 102.38,
value: "B"
}, {
start: 102.38,
end: 102.46,
value: "A"
}, {
start: 102.46,
end: 103.19,
value: "N"
}, {
start: 103.19,
end: 103.47,
value: "B"
}, {
start: 103.47,
end: 104.52,
value: "A"
}, {
start: 104.52,
end: 105.15,
value: "C"
}, {
start: 105.15,
end: 105.57,
value: "B"
}, {
start: 105.57,
end: 108.41,
value: "X"
}, {
start: 108.41,
end: 108.56,
value: "A"
}, {
start: 108.56,
end: 108.63,
value: "C"
}, {
start: 108.63,
end: 108.84,
value: "B"
}, {
start: 108.84,
end: 108.98,
value: "A"
}, {
start: 108.98,
end: 109.05,
value: "L"
}, {
start: 109.05,
end: 109.33,
value: "A"
}, {
start: 109.33,
end: 109.68,
value: "B"
}, {
start: 109.68,
end: 109.82,
value: "E"
}, {
start: 109.82,
end: 109.89,
value: "B"
}, {
start: 109.89,
end: 110.03,
value: "O"
}, {
start: 110.03,
end: 110.17,
value: "F"
}, {
start: 110.17,
end: 110.24,
value: "B"
}, {
start: 110.24,
end: 110.52,
value: "A"
}, {
start: 110.52,
end: 110.59,
value: "D"
}, {
start: 110.59,
end: 111.01,
value: "N"
}, {
start: 111.01,
end: 111.08,
value: "E"
}, {
start: 111.08,
end: 111.57,
value: "C"
}, {
start: 111.57,
end: 111.64,
value: "B"
}, {
start: 111.64,
end: 111.99,
value: "E"
}, {
start: 111.99,
end: 113.77,
value: "X"
}, {
start: 113.77,
end: 114.16,
value: "B"
}, {
start: 114.16,
end: 114.65,
value: "E"
}, {
start: 114.65,
end: 114.93,
value: "C"
}, {
start: 114.93,
end: 115.00,
value: "C"
}, {
start: 115.00,
end: 115.24,
value: "A"
}, {
start: 115.24,
end: 115.46,
value: "N"
}, {
start: 115.46,
end: 115.60,
value: "C"
}, {
start: 115.60,
end: 115.74,
value: "B"
}, {
start: 115.74,
end: 115.88,
value: "E"
}, {
start: 115.88,
end: 116.09,
value: "A"
}, {
start: 116.09,
end: 116.17,
value: "B"
}, {
start: 116.17,
end: 116.24,
value: "E"
}, {
start: 116.24,
end: 116.73,
value: "B"
}, {
start: 116.73,
end: 117.57,
value: "X"
}, {
start: 117.57,
end: 117.80,
value: "B"
}, {
start: 117.80,
end: 118.81,
value: "X"
}, {
start: 118.81,
end: 119.28,
value: "B"
}, {
start: 119.28,
end: 119.35,
value: "F"
}, {
start: 119.35,
end: 119.49,
value: "E"
}, {
start: 119.49,
end: 119.65,
value: "A"
}, {
start: 119.65,
end: 119.88,
value: "C"
}, {
start: 119.88,
end: 119.95,
value: "E"
}, {
start: 119.95,
end: 120.16,
value: "B"
}, {
start: 120.16,
end: 120.30,
value: "E"
}, {
start: 120.30,
end: 120.37,
value: "B"
}, {
start: 120.37,
end: 120.65,
value: "E"
}, {
start: 120.65,
end: 120.79,
value: "F"
}, {
start: 120.79,
end: 120.93,
value: "B"
}, {
start: 120.93,
end: 121.00,
value: "E"
}, {
start: 121.00,
end: 121.28,
value: "L"
}, {
start: 121.28,
end: 121.49,
value: "O"
}, {
start: 121.49,
end: 121.56,
value: "F"
}, {
start: 121.56,
end: 121.70,
value: "B"
}, {
start: 121.70,
end: 121.98,
value: "A"
}, {
start: 121.98,
end: 122.19,
value: "E"
}, {
start: 122.19,
end: 122.61,
value: "C"
}, {
start: 122.61,
end: 123.56,
value: "B"
}, {
start: 123.56,
end: 123.77,
value: "X"
}, {
start: 123.77,
end: 123.87,
value: "B"
}, {
start: 123.87,
end: 124.02,
value: "A"
}, {
start: 124.02,
end: 124.16,
value: "L"
}, {
start: 124.16,
end: 124.30,
value: "F"
}, {
start: 124.30,
end: 124.72,
value: "B"
}, {
start: 124.72,
end: 125.00,
value: "E"
}, {
start: 125.00,
end: 125.99,
value: "D"
}, {
start: 125.99,
end: 126.06,
value: "C"
}, {
start: 126.06,
end: 126.34,
value: "I"
}, {
start: 126.34,
end: 126.48,
value: "B"
}, {
start: 126.48,
end: 126.62,
value: "E"
}, {
start: 126.62,
end: 126.69,
value: "F"
}, {
start: 126.69,
end: 126.83,
value: "B"
}, {
start: 126.83,
end: 128.26,
value: "A"
}, {
start: 128.26,
end: 128.43,
value: "C"
}, {
start: 128.43,
end: 129.70,
value: "X"
}, {
start: 129.70,
end: 129.85,
value: "A"
}, {
start: 129.85,
end: 129.96,
value: "L"
}, {
start: 129.96,
end: 130.03,
value: "F"
}, {
start: 130.03,
end: 130.17,
value: "B"
}, {
start: 130.17,
end: 130.38,
value: "E"
}, {
start: 130.38,
end: 130.52,
value: "D"
}, {
start: 130.52,
end: 130.59,
value: "B"
}, {
start: 130.59,
end: 130.87,
value: "I"
}, {
start: 130.87,
end: 131.08,
value: "B"
}, {
start: 131.08,
end: 131.15,
value: "D"
}, {
start: 131.15,
end: 131.29,
value: "O"
}, {
start: 131.29,
end: 131.36,
value: "B"
}, {
start: 131.36,
end: 131.50,
value: "B"
}, {
start: 131.50,
end: 131.81,
value: "A"
}, {
start: 131.81,
end: 131.89,
value: "C"
}, {
start: 131.89,
end: 132.20,
value: "B"
}, {
start: 132.20,
end: 132.48,
value: "E"
}, {
start: 132.48,
end: 132.62,
value: "X"
}, {
start: 132.62,
end: 132.69,
value: "B"
}, {
start: 132.69,
end: 132.90,
value: "U"
}, {
start: 132.90,
end: 133.25,
value: "B"
}, {
start: 133.25,
end: 133.45,
value: "F"
}, {
start: 133.45,
end: 133.53,
value: "E"
}, {
start: 133.53,
end: 133.88,
value: "B"
}, {
start: 133.88,
end: 134.02,
value: "U"
}, {
start: 134.02,
end: 134.14,
value: "X"
}, {
start: 134.14,
end: 134.29,
value: "B"
}, {
start: 134.29,
end: 134.36,
value: "A"
}, {
start: 134.36,
end: 134.50,
value: "B"
}, {
start: 134.50,
end: 134.71,
value: "D"
}, {
start: 134.71,
end: 134.85,
value: "E"
}, {
start: 134.85,
end: 135.06,
value: "N"
}, {
start: 135.06,
end: 135.20,
value: "C"
}, {
start: 135.20,
end: 135.27,
value: "B"
}, {
start: 135.27,
end: 135.69,
value: "A"
}, {
start: 135.69,
end: 135.83,
value: "B"
}, {
start: 135.83,
end: 136.53,
value: "E"
}, {
start: 136.53,
end: 136.67,
value: "B"
}, {
start: 136.67,
end: 140.08,
value: "X"
}, {
start: 140.08,
end: 140.22,
value: "D"
}, {
start: 140.22,
end: 140.29,
value: "B"
}, {
start: 140.29,
end: 140.57,
value: "E"
}, {
start: 140.57,
end: 141.20,
value: "F"
}, {
start: 141.20,
end: 141.41,
value: "B"
}, {
start: 141.41,
end: 141.76,
value: "A"
}, {
start: 141.76,
end: 142.11,
value: "C"
}, {
start: 142.11,
end: 142.18,
value: "N"
}, {
start: 142.18,
end: 142.25,
value: "E"
}, {
start: 142.25,
end: 142.74,
value: "C"
}, {
start: 142.74,
end: 142.93,
value: "B"
}, {
start: 142.93,
end: 142.98,
value: "A"
}, {
start: 142.98,
end: 143.21,
value: "C"
}, {
start: 143.21,
end: 143.97,
value: "D"
}, {
start: 143.97,
end: 144.11,
value: "B"
}, {
start: 144.11,
end: 144.27,
value: "X"
}, {
start: 144.27,
end: 144.40,
value: "B"
}, {
start: 144.40,
end: 145.11,
value: "X"
}, {
start: 145.11,
end: 145.58,
value: "B"
}, {
start: 145.58,
end: 145.72,
value: "F"
}, {
start: 145.72,
end: 145.80,
value: "A"
}, {
start: 145.80,
end: 145.87,
value: "C"
}, {
start: 145.87,
end: 146.18,
value: "D"
}, {
start: 146.18,
end: 146.25,
value: "C"
}, {
start: 146.25,
end: 146.49,
value: "B"
}, {
start: 146.49,
end: 146.56,
value: "A"
}, {
start: 146.56,
end: 146.77,
value: "L"
}, {
start: 146.77,
end: 146.85,
value: "A"
}, {
start: 146.85,
end: 147.26,
value: "E"
}, {
start: 147.26,
end: 147.47,
value: "B"
}, {
start: 147.47,
end: 147.75,
value: "F"
}, {
start: 147.75,
end: 147.82,
value: "E"
}, {
start: 147.82,
end: 148.15,
value: "D"
}, {
start: 148.15,
end: 148.20,
value: "C"
}, {
start: 148.20,
end: 148.38,
value: "B"
}, {
start: 148.38,
end: 148.45,
value: "F"
}, {
start: 148.45,
end: 150.48,
value: "E"
}, {
start: 150.48,
end: 150.90,
value: "B"
}, {
start: 150.90,
end: 151.41,
value: "X"
}, {
start: 151.41,
end: 151.76,
value: "E"
}, {
start: 151.76,
end: 151.90,
value: "F"
}, {
start: 151.90,
end: 152.15,
value: "A"
}, {
start: 152.15,
end: 152.40,
value: "E"
}, {
start: 152.40,
end: 152.54,
value: "F"
}, {
start: 152.54,
end: 152.61,
value: "B"
}, {
start: 152.61,
end: 152.89,
value: "E"
}, {
start: 152.89,
end: 153.13,
value: "F"
}, {
start: 153.13,
end: 153.19,
value: "C"
}, {
start: 153.19,
end: 153.38,
value: "E"
}, {
start: 153.38,
end: 153.45,
value: "F"
}, {
start: 153.45,
end: 154.69,
value: "X"
}, {
start: 154.69,
end: 154.96,
value: "C"
}, {
start: 154.96,
end: 155.16,
value: "A"
}, {
start: 155.16,
end: 155.54,
value: "F"
}, {
start: 155.54,
end: 155.62,
value: "B"
}, {
start: 155.62,
end: 156.49,
value: "E"
}, {
start: 156.49,
end: 156.63,
value: "B"
}, {
start: 156.63,
end: 157.19,
value: "E"
}, {
start: 157.19,
end: 157.47,
value: "L"
}, {
start: 157.47,
end: 157.68,
value: "O"
}, {
start: 157.68,
end: 158.18,
value: "B"
}, {
start: 158.18,
end: 158.22,
value: "B"
}, {
start: 158.22,
end: 159.21,
value: "A"
}, {
start: 159.21,
end: 159.83,
value: "E"
}, {
start: 159.83,
end: 160.25,
value: "C"
}, {
start: 160.25,
end: 160.81,
value: "B"
}, {
start: 160.81,
end: 163.32,
value: "X"
}, {
start: 163.32,
end: 163.41,
value: "B"
}, {
start: 163.41,
end: 163.49,
value: "A"
}, {
start: 163.49,
end: 163.68,
value: "L"
}, {
start: 163.68,
end: 163.73,
value: "F"
}, {
start: 163.73,
end: 163.91,
value: "B"
}, {
start: 163.91,
end: 164.12,
value: "E"
}, {
start: 164.12,
end: 164.71,
value: "D"
}, {
start: 164.71,
end: 164.92,
value: "C"
}, {
start: 164.92,
end: 165.13,
value: "I"
}, {
start: 165.13,
end: 165.48,
value: "D"
}, {
start: 165.48,
end: 165.62,
value: "O"
}, {
start: 165.62,
end: 165.90,
value: "M"
}, {
start: 165.90,
end: 165.97,
value: "C"
}, {
start: 165.97,
end: 167.23,
value: "A"
}, {
start: 167.23,
end: 167.68,
value: "X"
}, {
start: 167.68,
end: 168.61,
value: "A"
}, {
start: 168.61,
end: 168.91,
value: "L"
}, {
start: 168.91,
end: 168.98,
value: "F"
}, {
start: 168.98,
end: 169.40,
value: "B"
}, {
start: 169.40,
end: 169.73,
value: "E"
}, {
start: 169.73,
end: 172.56,
value: "D"
}, {
start: 172.56,
end: 172.64,
value: "C"
}, {
start: 172.64,
end: 173.40,
value: "X"
}, {
start: 173.40,
end: 173.53,
value: "B"
}, {
start: 173.53,
end: 174.56,
value: "U"
}, {
start: 174.56,
end: 174.63,
value: "F"
}, {
start: 174.63,
end: 174.70,
value: "G"
}, {
start: 174.70,
end: 174.98,
value: "E"
}, {
start: 174.98,
end: 175.26,
value: "H"
}, {
start: 175.26,
end: 175.68,
value: "C"
}, {
start: 175.68,
end: 176.10,
value: "B"
}, {
start: 176.10,
end: 176.24,
value: "E"
}, {
start: 176.24,
end: 176.45,
value: "F"
}, {
start: 176.45,
end: 176.52,
value: "B"
}, {
start: 176.52,
end: 177.64,
value: "O"
}, {
start: 177.64,
end: 177.85,
value: "X"
}, {
start: 177.85,
end: 177.97,
value: "C"
}, {
start: 177.97,
end: 178.27,
value: "X"
}, {
start: 178.27,
end: 178.40,
value: "B"
}, {
start: 178.40,
end: 178.54,
value: "A"
}, {
start: 178.54,
end: 178.68,
value: "R"
}, {
start: 178.68,
end: 179.17,
value: "E"
}, {
start: 179.17,
end: 179.94,
value: "B"
}, {
start: 179.94,
end: 181.13,
value: "X"
}, {
start: 181.13,
end: 181.25,
value: "D"
}, {
start: 181.25,
end: 181.39,
value: "B"
}, {
start: 181.39,
end: 181.94,
value: "E"
}, {
start: 181.94,
end: 182.33,
value: "A"
}, {
start: 182.33,
end: 182.41,
value: "B"
}, {
start: 182.41,
end: 182.86,
value: "B"
}, {
start: 182.86,
end: 182.98,
value: "X"
}, {
start: 182.98,
end: 183.17,
value: "B"
}, {
start: 183.17,
end: 183.81,
value: "X"
}, {
start: 183.81,
end: 183.96,
value: "C"
}, {
start: 183.96,
end: 184.18,
value: "A"
}, {
start: 184.18,
end: 184.63,
value: "N"
}, {
start: 184.63,
end: 184.71,
value: "A"
}, {
start: 184.71,
end: 185.44,
value: "B"
}, {
start: 185.44,
end: 186.32,
value: "X"
}, {
start: 186.32,
end: 186.49,
value: "B"
}, {
start: 186.49,
end: 186.56,
value: "A"
}, {
start: 186.56,
end: 186.63,
value: "T"
}, {
start: 186.63,
end: 186.78,
value: "B"
}, {
start: 186.78,
end: 187.00,
value: "A"
}, {
start: 187.00,
end: 188.12,
value: "N"
}, {
start: 188.12,
end: 189.10,
value: "X"
}, {
start: 189.10,
end: 189.33,
value: "N"
}, {
start: 189.33,
end: 189.47,
value: "A"
}, {
start: 189.47,
end: 189.66,
value: "N"
}, {
start: 189.66,
end: 189.71,
value: "A"
}, {
start: 189.71,
end: 190.80,
value: "N"
}, {
start: 190.80,
end: 191.22,
value: "A"
}, {
start: 191.22,
end: 191.46,
value: "N"
}, {
start: 191.46,
end: 191.50,
value: "A"
}, {
start: 191.50,
end: 191.54,
value: "N"
}, {
start: 191.54,
end: 192.52,
value: "A"
}, {
start: 192.52,
end: 192.69,
value: "N"
}, {
start: 192.69,
end: 192.83,
value: "A"
}, {
start: 192.83,
end: 193.25,
value: "B"
}, {
start: 193.25,
end: 193.50,
value: "A"
}, {
start: 193.50,
end: 193.71,
value: "T"
}, {
start: 193.71,
end: 193.82,
value: "B"
}, {
start: 193.82,
end: 194.03,
value: "A"
}, {
start: 194.03,
end: 194.09,
value: "N"
}, {
start: 194.09,
end: 195.53,
value: "B"
}, {
start: 195.53,
end: 195.77,
value: "X"
}, {
start: 195.77,
end: 195.95,
value: "N"
}, {
start: 195.95,
end: 196.65,
value: "A"
}, {
start: 196.65,
end: 198.41,
value: "N"
}, {
start: 198.41,
end: 198.66,
value: "A"
}, {
start: 198.66,
end: 198.80,
value: "N"
}, {
start: 198.80,
end: 199.08,
value: "A"
}, {
start: 199.08,
end: 199.15,
value: "N"
}, {
start: 199.15,
end: 199.43,
value: "A"
}, {
start: 199.43,
end: 199.85,
value: "N"
}, {
start: 199.85,
end: 199.99,
value: "A"
}, {
start: 199.99,
end: 200.20,
value: "N"
}, {
start: 200.20,
end: 200.41,
value: "A"
}, {
start: 200.41,
end: 201.15,
value: "B"
}, {
start: 201.15,
end: 202.25,
value: "A"
}, {
start: 202.25,
end: 202.33,
value: "T"
}, {
start: 202.33,
end: 203.09,
value: "B"
}, {
start: 203.09,
end: 203.19,
value: "A"
}, {
start: 203.19,
end: 203.26,
value: "N"
}, {
start: 203.26,
end: 203.33,
value: "B"
}, {
start: 203.33,
end: 203.89,
value: "A"
}, {
start: 203.89,
end: 206.72,
value: "X"
}];
// Viseme mapping
self.visemeMapping = {
'X': 'closed',
'A': 'AEI',
'B': 'BMP',
'C': 'CDGKNRSTXYZ',
'D': 'AEI',
'E': 'EE',
'F': 'FV',
'G': 'CDGKNRSTXYZ',
'H': 'TH',
// Added TH viseme
'L': 'L',
// Added L viseme
'N': 'N',
// Added N viseme
'O': 'O',
// Added O viseme
'Q': 'QW',
// Added QW viseme
'U': 'U' // Added U viseme
};
self.songStartTime = null;
self.isLipSyncActive = false;
self.currentLipSyncIndex = 0;
// Position tracking variables (keeping your existing setup)
var targetX = GAME_WIDTH / 2;
var targetY = GAME_HEIGHT / 2 + 200;
var smoothingFactor = 0.18;
var prevX = null;
var prevY = null;
// Scale tracking variables (keeping your existing setup)
var scaleHistory = new Array(5).fill(1);
var scaleIndex = 0;
var currentScale = 1;
// Rotation variables (keeping your existing setup)
var targetTilt = 0;
var tiltSmoothingFactor = 0.11;
var tiltScaleFactor = 0.09;
function calculateFaceTilt() {
if (facekit.leftEye && facekit.rightEye && facekit.mouthCenter) {
var eyeMidX = (facekit.leftEye.x + facekit.rightEye.x) / 2;
var eyeMidY = (facekit.leftEye.y + facekit.rightEye.y) / 2;
var dx = facekit.mouthCenter.x - eyeMidX;
var dy = facekit.mouthCenter.y - eyeMidY;
var angle = -(Math.atan2(dx, dy) * (180 / Math.PI));
return Math.max(-15, Math.min(15, angle * 0.15));
}
return 0;
}
self.startLipSync = function () {
self.songStartTime = Date.now();
self.isLipSyncActive = true;
self.currentLipSyncIndex = 0;
};
self.setViseme = function (visemeName) {
if (self.currentViseme !== visemeName && self.visemes[visemeName]) {
if (self.visemes[self.currentViseme]) {
self.visemes[self.currentViseme].visible = false;
}
self.visemes[visemeName].visible = true;
self.currentViseme = visemeName;
}
};
self.updateAutomatedLipSync = function () {
if (!self.isLipSyncActive || !self.songStartTime || self.automatedLipSync.length === 0) {
return;
}
var currentTimeSeconds = (Date.now() - self.songStartTime) / 1000.0;
var targetVisemeKey = 'closed';
// Start searching from currentLipSyncIndex for optimization
for (var i = self.currentLipSyncIndex; i < self.automatedLipSync.length; i++) {
var cue = self.automatedLipSync[i];
if (currentTimeSeconds >= cue.start && currentTimeSeconds < cue.end) {
var automatedCode = cue.value;
targetVisemeKey = self.visemeMapping[automatedCode] || 'closed';
self.currentLipSyncIndex = i;
break;
}
if (currentTimeSeconds >= cue.end) {
if (i === self.automatedLipSync.length - 1) {
var automatedCode = cue.value;
targetVisemeKey = self.visemeMapping[automatedCode] || 'closed';
}
} else if (currentTimeSeconds < cue.start) {
break;
}
}
if (self.currentLipSyncIndex === self.automatedLipSync.length - 1 && currentTimeSeconds >= self.automatedLipSync[self.automatedLipSync.length - 1].end) {
targetVisemeKey = 'closed';
}
self.setViseme(targetVisemeKey);
};
self.update = function () {
// Update automated lip sync
self.updateAutomatedLipSync();
// Smooth position tracking
if (facekit.mouthCenter) {
targetX = facekit.mouthCenter.x;
targetY = facekit.mouthCenter.y;
if (prevX === null) {
prevX = targetX;
prevY = targetY;
}
var newX = prevX * (1 - smoothingFactor) + targetX * smoothingFactor;
var newY = prevY * (1 - smoothingFactor) + targetY * smoothingFactor;
self.x = newX;
self.y = newY;
prevX = newX;
prevY = newY;
}
// Dynamic scale adjustment
if (facekit.leftEye && facekit.rightEye) {
var eyeDistance = Math.abs(facekit.rightEye.x - facekit.leftEye.x);
var newScale = eyeDistance / 250;
scaleHistory[scaleIndex] = newScale;
scaleIndex = (scaleIndex + 1) % scaleHistory.length;
var avgScale = scaleHistory.reduce(function (a, b) {
return a + b;
}, 0) / scaleHistory.length;
var targetScale = avgScale;
currentScale = currentScale * 0.85 + targetScale * 0.15;
Object.keys(self.visemes).forEach(function (key) {
self.visemes[key].scaleX = currentScale;
self.visemes[key].scaleY = currentScale;
});
}
// Rotation tracking
if (facekit.leftEye && facekit.rightEye) {
targetTilt = calculateFaceTilt() * tiltScaleFactor;
targetTilt = Math.max(-15, Math.min(15, targetTilt));
self.rotation += (targetTilt - self.rotation) * tiltSmoothingFactor;
}
};
return self;
});
var StartButton = Container.expand(function () {
var self = Container.call(this);
self.buttonGraphics = self.attachAsset('startButtonImage', {
anchorX: 0.5,
anchorY: 0.5
});
self.down = function () {
// This will call the globally defined startGame function
// when the button is pressed.
if (typeof handleStartButtonPressed === 'function') {
handleStartButtonPressed();
}
};
return self;
});
var SubtitleDisplay = Container.expand(function () {
var self = Container.call(this);
// Create text display properties
self.currentText = "";
self.textDisplay = null;
self.subtitleData = [{
start: 4.8,
end: 7.5,
text: "From the ashes of tragedy"
}, {
start: 10.0,
end: 12.5,
text: "A guardian rises"
}, {
start: 14.0,
end: 16.0,
text: "Not the hero they deserve"
}, {
start: 17.5,
end: 19.5,
text: "But the one they need"
}, {
start: 22.5,
end: 24.5,
text: "Parents fallen in an alley cold"
}, {
start: 25.0,
end: 29.0,
text: "A boy's heart turned to stone"
}, {
start: 33.0,
end: 35.0,
text: "Fortune built on others' pain"
}, {
start: 36.0,
end: 40.0,
text: "Now I claim the night as my own"
}, {
start: 43.5,
end: 46.0,
text: "Gotham calls to me in whispers dire"
}, {
start: 47.0,
end: 51.0,
text: "Criminals scatter at my arrival"
}, {
start: 53.5,
end: 56.0,
text: "The cape becomes my second skin"
}, {
start: 57.0,
end: 62.0,
text: "This mask - my true survival"
},
// Chorus
{
start: 63.5,
end: 69.0,
text: "From the darkness I emerge"
}, {
start: 75.0,
end: 79.0,
text: "Justice is what I serve"
}, {
start: 85.5,
end: 90.0,
text: "When evil plagues these streets"
}, {
start: 90.5,
end: 92.0,
text: "I'll be there"
}, {
start: 93.5,
end: 94.5,
text: "I'll be there"
}, {
start: 96.0,
end: 97.0,
text: "I'm Batman"
}, {
start: 98.5,
end: 100.0,
text: "I'm Batman"
}, {
start: 101.5,
end: 102.5,
text: "I'm Batman"
}, {
start: 103.0,
end: 105.0,
text: "I'm Batman"
},
// Verse 2
{
start: 109.0,
end: 112.5,
text: "A gallery of madness I've faced"
}, {
start: 114.0,
end: 116.5,
text: "The Joker with his twisted grin"
}, {
start: 120.0,
end: 123.8,
text: "Riddler's puzzles, Penguin's schemes"
}, {
start: 124.0,
end: 128.5,
text: "Two-Face's coin, the battle within"
}, {
start: 130.0,
end: 132.0,
text: "My allies few, my burden great"
}, {
start: 133.0,
end: 137.0,
text: "The cave below, my sanctuary"
}, {
start: 140.5,
end: 144.5,
text: "Alfred's wisdom guides my path"
}, {
start: 145.0,
end: 150.5,
text: "Through this life so solitary"
},
// Chorus repeat
{
start: 151.5,
end: 157.0,
text: "From the darkness I emerge"
}, {
start: 163.5,
end: 167.5,
text: "Justice is what I serve"
}, {
start: 173.5,
end: 178.0,
text: "When evil plagues these streets"
}, {
start: 178.5,
end: 180.0,
text: "I'll be there"
}, {
start: 181.0,
end: 182.5,
text: "I'll be there"
}, {
start: 184.0,
end: 185.0,
text: "I'm Batman"
}, {
start: 186.5,
end: 188.0,
text: "I'm Batman"
}, {
start: 189.0,
end: 190.5,
text: "I'm Batman"
}, {
start: 191.5,
end: 193.0,
text: "I'm Batman"
},
// Outro
{
start: 194.0,
end: 197.0,
text: "Na na na na na na na na"
}, {
start: 197.0,
end: 200.0,
text: "Na na na na na na na na"
}, {
start: 198.0,
end: 198.5,
text: "Batman"
}, {
start: 199.0,
end: 202.0,
text: "Na na na na na na na na"
}, {
start: 202.0,
end: 205.0,
text: "Na na na na na na na na"
}, {
start: 203.0,
end: 204.5,
text: "Batman!"
}];
self.songStartTime = null;
self.isActive = false;
self.currentSubtitleIndex = 0;
// Text styling properties
self.textStyle = {
fontFamily: 'Arial',
// Font family for Text2
fontSize: 96,
// Font size (doubled)
// fontWeight: 'bold', // fontWeight is part of font string in Text2 if specific bold font is used
color: '#FFFFFF',
// Fill color
stroke: '#000000',
// Stroke color
strokeWidth: 3,
// Mapped to strokeThickness in Text2
textAlign: 'center' // Mapped to align in Text2
// textBaseline: 'middle', // Handled by anchor in Text2
// shadowColor: '#000000', // Shadow not directly supported by LK Text2
// shadowBlur: 8,
// shadowOffsetX: 2,
// shadowOffsetY: 2
};
self.createTextGraphics = function (text) {
if (self.textDisplay) {
// If a previous text object exists
self.textDisplay.destroy(); // Destroy it
self.textDisplay = null; // Clear the reference
}
if (!text || text.trim() === "") {
// If new text is empty, do nothing further
return;
}
// Create new Text2 object
self.textDisplay = new Text2(text, {
font: self.textStyle.fontFamily,
size: self.textStyle.fontSize,
fill: self.textStyle.color,
stroke: self.textStyle.stroke,
strokeThickness: self.textStyle.strokeWidth,
align: self.textStyle.textAlign
});
self.textDisplay.anchor.set(0.5, 0.5); // Center the text within its bounds
self.addChild(self.textDisplay); // Add the new text display to the container
};
self.startSubtitles = function () {
self.songStartTime = Date.now();
self.isActive = true;
self.currentSubtitleIndex = 0;
};
self.updateSubtitles = function () {
if (!self.isActive || !self.songStartTime || self.subtitleData.length === 0) {
return;
}
var currentTimeSeconds = (Date.now() - self.songStartTime) / 1000.0;
var newText = "";
var foundSubtitle = false;
// Find current subtitle
// Iterate from the current index for efficiency or from start if reset needed
for (var i = self.currentSubtitleIndex; i < self.subtitleData.length; i++) {
var subtitle = self.subtitleData[i];
if (currentTimeSeconds >= subtitle.start && currentTimeSeconds < subtitle.end) {
newText = subtitle.text;
self.currentSubtitleIndex = i; // Keep track of the current subtitle index
foundSubtitle = true;
break;
}
// If current time is less than the start of this subtitle, means no future subtitle will match yet.
if (currentTimeSeconds < subtitle.start) {
break;
}
// If time has passed this subtitle's end, it might be the one if nothing else matches
if (currentTimeSeconds >= subtitle.end) {
// If it's the last subtitle and we've passed its start time, it might still be active until next change or song end
// This logic clears the subtitle if we've passed its end time and it's not picked up by another.
}
}
// If no subtitle is active for the current time (e.g., between cues), clear the text.
if (!foundSubtitle && currentTimeSeconds > 0) {
// Check currentTimeSeconds > 0 to avoid clearing at very start
var shouldClear = true;
// Check if we are before the first subtitle or after the last one
if (self.subtitleData.length > 0) {
if (currentTimeSeconds < self.subtitleData[0].start) {
shouldClear = true;
} else {
// Check if we are between subtitles
for (var k = 0; k < self.subtitleData.length; k++) {
if (currentTimeSeconds >= self.subtitleData[k].start && currentTimeSeconds < self.subtitleData[k].end) {
shouldClear = false; // We are in a subtitle, don't clear
break;
}
}
}
}
if (shouldClear) {
newText = "";
}
}
// Update display if text changed
if (newText !== self.currentText) {
self.currentText = newText;
self.createTextGraphics(newText);
}
};
self.setPosition = function (x, y) {
self.x = x;
self.y = y;
};
self.update = function () {
self.updateSubtitles();
};
return self;
});
/****
* Initialize Game
****/
// Facekit provides the camera feed as background, so no explicit backgroundColor needed.
var game = new LK.Game({});
/****
* Game Code
****/
// Game state variables
var isGameActive = false;
var startButtonInstance;
var maskInstance;
var mouthAnimatorInstance;
var subtitleInstance;
var songDuration = 206.72; // Song duration in seconds based on lip sync data
var musicStartTime = null;
var songCompleted = false;
// Game constants
var GAME_WIDTH = 2048;
var GAME_HEIGHT = 2732;
var AUTOMATED_VISEME_MAPPING = {
'X': 'closed',
// Silence/rest position
'A': 'AEI',
// Open vowels (father, palm)
'B': 'BMP',
// Lip closure (b, m, p)
'C': 'CDGKNRSTXYZ',
// Consonants (c, d, g, k, n, r, s, t, x, y, z)
'D': 'AEI',
// Mid vowels (day, face)
'E': 'EE',
// High front vowels (see, fleece)
'F': 'FV',
// Lip-teeth contact (f, v)
'G': 'CDGKNRSTXYZ',
// Back consonants (g, ng)
'H': 'CHJSH' // Fricatives (h, ch, j, sh)
};
function handleStartButtonPressed() {
if (isGameActive) {
return;
}
isGameActive = true;
// Remove start button
if (startButtonInstance && startButtonInstance.parent) {
startButtonInstance.destroy();
startButtonInstance = null;
}
// Create and show mask
maskInstance = new Mask();
game.addChild(maskInstance);
var maskGraphicHeight = maskInstance.maskGraphics.height;
var yOffset = -50;
var maskTargetX, maskTargetY;
if (facekit.leftEye && facekit.rightEye && facekit.leftEye.x !== 0 && facekit.rightEye.x !== 0) {
maskTargetX = (facekit.leftEye.x + facekit.rightEye.x) / 2;
maskTargetY = (facekit.leftEye.y + facekit.rightEye.y) / 2 - maskGraphicHeight * 0.20 + yOffset;
} else {
maskTargetX = GAME_WIDTH / 2;
maskTargetY = GAME_HEIGHT / 3 + yOffset;
}
// Create mouth animator
mouthAnimatorInstance = new MouthAnimator();
var mouthInitialX = facekit.mouthCenter && facekit.mouthCenter.x !== 0 ? facekit.mouthCenter.x : GAME_WIDTH / 2;
var mouthInitialY = facekit.mouthCenter && facekit.mouthCenter.y !== 0 ? facekit.mouthCenter.y : GAME_HEIGHT / 2 + 200;
mouthAnimatorInstance.x = mouthInitialX;
mouthAnimatorInstance.y = mouthInitialY;
game.addChild(mouthAnimatorInstance);
// Create subtitle display
subtitleInstance = new SubtitleDisplay();
subtitleInstance.setPosition(GAME_WIDTH / 2, GAME_HEIGHT - 124 - GAME_HEIGHT * 0.1); // Position moved up 10% from previous position
game.addChild(subtitleInstance);
// Show mask with callback to start music and lip-sync when animation completes
maskInstance.show(maskTargetX, maskTargetY, 1200);
// Start music, lip-sync, and subtitles after mask animation completes (1.2 seconds)
LK.setTimeout(function () {
LK.playMusic('batmansong');
musicStartTime = Date.now(); // Track when music started
songCompleted = false; // Reset completion flag
if (mouthAnimatorInstance) {
// Ensure mouthAnimatorInstance still exists
mouthAnimatorInstance.startLipSync();
}
if (subtitleInstance) {
// Ensure subtitleInstance still exists
subtitleInstance.startSubtitles();
}
}, 1200);
}
// Initial game setup
function initializeGameScreen() {
isGameActive = false; // Reset game state
// Create and position the start button
startButtonInstance = new StartButton();
// Center the button horizontally
startButtonInstance.x = GAME_WIDTH / 2;
// Position it towards the bottom of the screen
var buttonHeight = startButtonInstance.buttonGraphics.height;
startButtonInstance.y = GAME_HEIGHT - buttonHeight / 2 - 100; // 100px padding from bottom
game.addChild(startButtonInstance);
}
// Call initial setup
initializeGameScreen();
// Game update loop
game.update = function () {
if (isGameActive) {
// Check if song has completed and stop music
if (musicStartTime && !songCompleted) {
var currentMusicTime = (Date.now() - musicStartTime) / 1000.0;
if (currentMusicTime >= songDuration) {
LK.stopMusic();
songCompleted = true;
}
}
// Update mouth animator with enhanced tracking
if (mouthAnimatorInstance) {
// mouthAnimatorInstance.updateViseme(facekit.volume); // Viseme update is now handled by updateLipSync within MouthAnimator's update
mouthAnimatorInstance.update(); // This now includes smooth position tracking, scaling, and lip-sync
}
// Update mask with enhanced tracking
if (maskInstance) {
maskInstance.update(); // This now includes smooth position tracking and scaling
}
if (subtitleInstance) {
subtitleInstance.update();
}
}
};