import {
defineVQTimeline,
say,
type VQCustomTimelineEvent,
type VQSpeechEvent,
type VQTimelineEvent,
type VQTimelineInputEvent,
} from "../../lib/VQRemotionLib/timeline";
import type {VQMouthShape} from "../../lib/VQRemotionLib/types";
import type {CharacterId, VoicevoxVoice} from "./characters";
export {characters} from "./characters";
export type {CharacterDefinition, CharacterId, VoicevoxVoice} from "./characters";
export type ExpressionId =
| "default"
| "blink"
| "confident"
| "surprised"
| "exasperated"
| "roundEyes"
| "roundEyesSmall"
| "huh"
| "tears"
| "yabeAhe"
| "nnaa"
| "gesu";
export type DemoExpression = Readonly<{
expressionId: ExpressionId;
label: string;
imagePath: string;
durationSeconds: number;
}>;
export const demoExpressions = [
{
expressionId: "default",
label: "default",
imagePath: "image/zundamon-ohnegus-rework-baby/06-default-nniya.png",
durationSeconds: 1.1,
},
{
expressionId: "blink",
label: "blink",
imagePath: "image/zundamon-ohnegus-rework-baby/13-blink-nniya.png",
durationSeconds: 1.1,
},
{
expressionId: "confident",
label: "confident",
imagePath: "image/zundamon-ohnegus-rework-baby/20-confident-nniya.png",
durationSeconds: 1.1,
},
{
expressionId: "surprised",
label: "surprised",
imagePath: "image/zundamon-ohnegus-rework-baby/27-surprised-nniya.png",
durationSeconds: 1.1,
},
{
expressionId: "exasperated",
label: "exasperated",
imagePath: "image/zundamon-ohnegus-rework-baby/32-exasperated-nniya.png",
durationSeconds: 1.1,
},
{
expressionId: "roundEyes",
label: "round-eyes",
imagePath: "image/zundamon-ohnegus-rework-baby/35-round-eyes-n.png",
durationSeconds: 1.1,
},
{
expressionId: "roundEyesSmall",
label: "round-eyes-small",
imagePath: "image/zundamon-ohnegus-rework-baby/37-round-eyes-small-n.png",
durationSeconds: 1.1,
},
{
expressionId: "huh",
label: "huh",
imagePath: "image/zundamon-ohnegus-rework-baby/39-huh-n.png",
durationSeconds: 1.1,
},
{
expressionId: "tears",
label: "tears",
imagePath: "image/zundamon-ohnegus-rework-baby/41-tears-n.png",
durationSeconds: 1.1,
},
{
expressionId: "yabeAhe",
label: "yabe-ahe",
imagePath: "image/zundamon-ohnegus-rework-baby/43-yabe-ahe-n.png",
durationSeconds: 1.1,
},
{
expressionId: "nnaa",
label: "nnaa",
imagePath: "image/zundamon-ohnegus-rework-baby/45-nnaa-n.png",
durationSeconds: 1.1,
},
{
expressionId: "gesu",
label: "gesu",
imagePath: "image/zundamon-ohnegus-rework-baby/46-gesu.png",
durationSeconds: 1.1,
},
] as const satisfies readonly DemoExpression[];
export type SpeechEvent = VQSpeechEvent<CharacterId, VoicevoxVoice>;
export type ExpressionEvent = VQCustomTimelineEvent<
"expression",
DemoExpression & {id: string}
>;
export type MouthCycleEvent = VQCustomTimelineEvent<
"mouthCycle",
{
id: string;
label: string;
durationSeconds: number;
mouths: readonly VQMouthShape[];
}
>;
export type TimelineEvent = VQTimelineEvent<
CharacterId,
VoicevoxVoice,
ExpressionEvent | MouthCycleEvent
>;
export type TimelineInputEvent = VQTimelineInputEvent<
CharacterId,
VoicevoxVoice,
ExpressionEvent | MouthCycleEvent
>;
// 用途: 表情確認用の静止フル画像イベントをタイムラインへ追加する。
// 使用方法: expression("id", demoExpressions[0]) のように呼び、描画側で imagePath を表示する。
// オプションや引数詳細: expressionData は表情ID、表示ラベル、public相対画像パス、表示秒数を含む。
export const expression = (
id: string,
expressionData: DemoExpression
): ExpressionEvent => ({
type: "expression",
id,
expressionId: expressionData.expressionId,
label: expressionData.label,
imagePath: expressionData.imagePath,
durationSeconds: expressionData.durationSeconds,
});
// 用途: Rhubarb生成結果に依存せず、全口形を明示表示する確認イベントを作る。
// 使用方法: mouthCycle("id") をタイムラインに置き、描画側で mouths を順番に切り替える。
// オプションや引数詳細: durationSeconds は全口形を一巡させる総秒数、mouths は表示順を指定する。
export const mouthCycle = (
id: string,
options: Partial<Pick<MouthCycleEvent, "durationSeconds" | "mouths">> = {}
): MouthCycleEvent => ({
type: "mouthCycle",
id,
label: "rhubarb-mouths",
durationSeconds: options.durationSeconds ?? 2.8,
mouths: options.mouths ?? ["rest", "closed", "a", "i", "u", "e", "o"],
});
export const timeline = defineVQTimeline([
say(
"zundamon-rework-standee-demo-zunda-001",
"zundamon",
"あ、い、う、え、お。ぱ、ぴ、ぷ、ぺ、ぽ。ん、まんま、なのだ。",
{
subtitle:
"default表情のリップシンク確認なのだ。\nあ、い、う、え、お。ぱ、ぴ、ぷ、ぺ、ぽ。",
}
),
mouthCycle("zundamon-rework-standee-demo-mouth-cycle-001"),
...demoExpressions.map((expressionData, index) =>
expression(
`zundamon-rework-standee-demo-expression-${String(index + 1).padStart(
2,
"0"
)}`,
expressionData
)
),
] satisfies readonly TimelineInputEvent[]);