-
-
Notifications
You must be signed in to change notification settings - Fork 339
Expand file tree
/
Copy pathvocalAnimation.ts
More file actions
132 lines (117 loc) · 3.79 KB
/
vocalAnimation.ts
File metadata and controls
132 lines (117 loc) · 3.79 KB
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
130
131
132
import { WebGAL } from '@/Core/WebGAL';
interface IAudioContextWrapper {
audioContext: AudioContext | null;
source: MediaElementAudioSourceNode | null;
analyser: AnalyserNode | undefined;
dataArray: Uint8Array | undefined;
audioLevelInterval: ReturnType<typeof setInterval>;
blinkTimerID: ReturnType<typeof setTimeout>;
maxAudioLevel: number;
}
// Initialize the object based on the interface
export const audioContextWrapper: IAudioContextWrapper = {
audioContext: null,
source: null,
analyser: undefined,
dataArray: undefined,
audioLevelInterval: setInterval(() => {}, 0), // dummy interval
blinkTimerID: setTimeout(() => {}, 0), // dummy timeout
maxAudioLevel: 0,
};
export const ensureAudioContextReady = async (): Promise<boolean> => {
if (!audioContextWrapper.audioContext) {
const AudioContextCtor =
window.AudioContext ??
(window as unknown as { webkitAudioContext?: typeof AudioContext }).webkitAudioContext;
if (!AudioContextCtor) {
return false;
}
audioContextWrapper.audioContext = new AudioContextCtor();
}
if (audioContextWrapper.audioContext.state === 'suspended') {
try {
await audioContextWrapper.audioContext.resume();
} catch {
return false;
}
}
return audioContextWrapper.audioContext.state === 'running';
};
export const resetMaxAudioLevel = () => {
audioContextWrapper.maxAudioLevel = 0;
};
export const updateThresholds = (audioLevel: number) => {
audioContextWrapper.maxAudioLevel = Math.max(audioLevel, audioContextWrapper.maxAudioLevel);
return {
OPEN_THRESHOLD: audioContextWrapper.maxAudioLevel * 0.75,
HALF_OPEN_THRESHOLD: audioContextWrapper.maxAudioLevel * 0.5,
};
};
export const performBlinkAnimation = (params: {
key: string;
animationItem: any;
pos: string;
animationEndTime: number;
}) => {
let isBlinking = false;
function blink() {
if (isBlinking || (params.animationEndTime && Date.now() > params.animationEndTime)) return;
isBlinking = true;
WebGAL.gameplay.pixiStage?.performBlinkAnimation(params.key, params.animationItem, 'closed', params.pos);
audioContextWrapper.blinkTimerID = setTimeout(() => {
WebGAL.gameplay.pixiStage?.performBlinkAnimation(params.key, params.animationItem, 'open', params.pos);
isBlinking = false;
const nextBlinkTime = Math.random() * 300 + 3500;
audioContextWrapper.blinkTimerID = setTimeout(blink, nextBlinkTime);
}, 200);
}
blink();
};
// Updated getAudioLevel function
export const getAudioLevel = (
analyser: AnalyserNode,
dataArray: Uint8Array,
bufferLength: number,
): number => {
analyser.getByteFrequencyData(dataArray as any);
let sum = 0;
for (let i = 0; i < bufferLength; i++) {
sum += dataArray[i];
}
return sum / bufferLength;
};
export const performMouthAnimation = (params: {
audioLevel: number;
OPEN_THRESHOLD: number;
HALF_OPEN_THRESHOLD: number;
currentMouthValue: number;
lerpSpeed: number;
key: string;
animationItem: any;
pos: string;
}) => {
const { audioLevel, OPEN_THRESHOLD, HALF_OPEN_THRESHOLD, currentMouthValue, lerpSpeed, key, animationItem, pos } =
params;
let targetValue;
if (audioLevel > OPEN_THRESHOLD) {
targetValue = 1; // open
} else if (audioLevel > HALF_OPEN_THRESHOLD) {
targetValue = 0.5; // half_open
} else {
targetValue = 0; // closed
}
// Lerp
const mouthValue = currentMouthValue + (targetValue - currentMouthValue) * lerpSpeed;
WebGAL.gameplay.pixiStage?.setModelMouthY(key, audioLevel);
let mouthState;
if (mouthValue > 0.75) {
mouthState = 'open';
} else if (mouthValue > 0.25) {
mouthState = 'half_open';
} else {
mouthState = 'closed';
}
if (animationItem !== undefined) {
WebGAL.gameplay.pixiStage?.performMouthSyncAnimation(key, animationItem, mouthState, pos);
}
};