-
Notifications
You must be signed in to change notification settings - Fork 3
/
model.js
129 lines (107 loc) · 3.32 KB
/
model.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
let canvas;
let ctx;
function loadImage(imageId) {
return new Promise(resolve => {
const image = document.getElementById(imageId);
if (image.complete) {
return resolve(image);
}
image.onload = () => {
resolve(image);
};
});
}
const imageCache = new Map();
function loadImageBySrc(imageUrl) {
if (imageCache.has(imageUrl)) {
return Promise.resolve(imageCache.get(imageUrl))
}
return new Promise(resolve => {
const image = new Image();
image.onload = () => {
imageCache.set(imageUrl, image);
resolve(image);
};
image.src = imageUrl;
});
}
async function drawImage(imageId) {
const image = await loadImage(imageId);
ctx.drawImage(image, 0, 0);
}
function clearEyes() {
ctx.fillStyle = "rgb(90, 81, 74)";
ctx.fillRect(167, 156, 40, 44);
ctx.fillRect(293, 156, 40, 44);
}
function sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
async function eyeBlink() {
const leftEye = await loadImage("eye-l");
const rightEye = await loadImage("eye-r");
const targetHeight = Math.floor(leftEye.height * 0.2);
let height = leftEye.height;
const ANIMATION_STEP = 20;
while (height > targetHeight) {
await sleep(ANIMATION_STEP);
height *= 0.8;
clearEyes();
ctx.drawImage(leftEye, 0, (leftEye.height - height) / 3, leftEye.width, height);
ctx.drawImage(rightEye, 0, (rightEye.height - height) / 3, rightEye.width, height);
}
await sleep(ANIMATION_STEP);
clearEyes();
ctx.drawImage(leftEye, 0, (leftEye.height - targetHeight) / 3, leftEye.width, targetHeight);
ctx.drawImage(rightEye, 0, (rightEye.height - targetHeight) / 3, rightEye.width, targetHeight);
await sleep(75);
clearEyes();
await drawImage("eye-l-closed");
await drawImage("eye-r-closed");
await sleep(120);
clearEyes();
await drawImage("eye-l");
await drawImage("eye-r");
}
document.addEventListener("DOMContentLoaded", async function() {
canvas = document.getElementById("canvas");
canvas.width = 512;
canvas.height = 512;
ctx = canvas.getContext("2d");
await drawImage("body");
await drawImage("eye-l");
await drawImage("eye-r");
const image = await loadImageBySrc(`assets/mouth-v2/0.png`);
ctx.drawImage(image, 0, 0);
eyeBlink();
const BLINK_INTERVAL = 3500;
setInterval(() => {
eyeBlink();
}, BLINK_INTERVAL);
});
let TRANSITION_DELAY = 60;
async function drawMouthFrame(frameId) {
const image = await loadImageBySrc(`assets/mouth-v2/${frameId}.png`);
ctx.fillStyle = `rgb(90, 81, 74)`;
ctx.fillRect(200, 165, 100, 75);
ctx.drawImage(image, 220, 185);
}
let ttsAudio;
async function playAudio(name) {
if (ttsAudio) {
ttsAudio.pause();
}
ttsAudio = new Audio(`${name}.wav`);
const response = await fetch(new Request(`${name}.json`), {
method: "GET",
mode: "no-cors"
});
const visemeData = await response.json();
ttsAudio.ontimeupdate = (event) => {
const currentFrame = visemeData.find(frameData => {
return frameData.offset - (TRANSITION_DELAY / 2) >= ttsAudio.currentTime * 1000;
});
drawMouthFrame(currentFrame?.id ?? 0);
};
ttsAudio.play();
}