From 0531566fbf5ed231ae0261074abafedf09f59435 Mon Sep 17 00:00:00 2001 From: aqua Date: Sat, 7 Mar 2026 22:56:01 +0900 Subject: [PATCH 01/21] chore: add new directory for PsdCharacter --- src/lib/character/psd-character.tsx | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100644 src/lib/character/psd-character.tsx diff --git a/src/lib/character/psd-character.tsx b/src/lib/character/psd-character.tsx new file mode 100644 index 0000000..bb1a8e4 --- /dev/null +++ b/src/lib/character/psd-character.tsx @@ -0,0 +1,13 @@ +import React from "react" + +type PsdCharacterProps = { + psd: string + children: React.ReactNode +} + +export const PsdCharacter = ({ + psd, + children +}: PsdCharacterProps) => { + return null +} From a226a1f17fc92411d5e497ff735f8cb3bc6c9202 Mon Sep 17 00:00:00 2001 From: aqua Date: Sat, 7 Mar 2026 23:17:01 +0900 Subject: [PATCH 02/21] chore: add index.ts --- src/lib/character/index.ts | 1 + 1 file changed, 1 insertion(+) create mode 100644 src/lib/character/index.ts diff --git a/src/lib/character/index.ts b/src/lib/character/index.ts new file mode 100644 index 0000000..6b6cfb3 --- /dev/null +++ b/src/lib/character/index.ts @@ -0,0 +1 @@ +export * from "./psd-character" From 3a32d9bc89f34d852372e5413750e47bab564424 Mon Sep 17 00:00:00 2001 From: aqua Date: Sat, 7 Mar 2026 23:42:29 +0900 Subject: [PATCH 03/21] feature: add ast --- src/lib/character/ast.ts | 75 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 75 insertions(+) create mode 100644 src/lib/character/ast.ts diff --git a/src/lib/character/ast.ts b/src/lib/character/ast.ts new file mode 100644 index 0000000..24662be --- /dev/null +++ b/src/lib/character/ast.ts @@ -0,0 +1,75 @@ +import type { Variable } from "../animation" + +export type CharacterChild = + | MotionSequenceNode + | DeclareVariableNode + | VoiceNode + | MotionNode + +export type MotionSequenceChild = + | BlockNode + | DeclareVariableNode + | VoiceNode + | MotionNode + +export type DeclareVariableChild = + | DeclareVariableNode + | DeclareAnimationNode + +export type BlockChild = + | MotionSequenceNode + | DeclareVariableNode + | VoiceNode + | MotionNode + +export type DeclareAnimationChild = + | MotionSequenceNode + | DeclareVariableNode + | VoiceNode + | MotionNode + +export type VoiceChild = + | MotionNode + + + + +export interface CharacterNode { + type: "Character" + children: CharacterChild[] +} + +export interface MotionSequenceNode { + type: "MotionSequence" + children: MotionSequenceChild[] +} + +export interface DeclareVariableNode { + type: "DeclareVariable" + variableName: string + initValue: any + children: DeclareVariableChild[] +} + +export interface BlockNode { + type: "Block" + body: BlockChild[] +} + +export interface DeclareAnimationNode { + type: "Animation" + f: (ctx: any, variable: Record>) => Promise + body: DeclareAnimationChild[] +} + +export interface VoiceNode { + type: "Voice" + voice: string + children: VoiceChild[] +} + +export interface MotionNode { + type: "Motion" + motion: (variables: Record>, frames: number[]) => Record +} + From d89cce5be4bd7e770b7e20af13d2a6fe8f9fa55e Mon Sep 17 00:00:00 2001 From: aqua Date: Sat, 7 Mar 2026 23:53:51 +0900 Subject: [PATCH 04/21] feature: add psd character component --- src/lib/character/defineDSL.ts | 12 ++++++++ src/lib/character/psd-character-component.ts | 31 ++++++++++++++++++++ 2 files changed, 43 insertions(+) create mode 100644 src/lib/character/defineDSL.ts create mode 100644 src/lib/character/psd-character-component.ts diff --git a/src/lib/character/defineDSL.ts b/src/lib/character/defineDSL.ts new file mode 100644 index 0000000..9f51c67 --- /dev/null +++ b/src/lib/character/defineDSL.ts @@ -0,0 +1,12 @@ +import type { ReactElement } from "react" + +export type DslComponent

= { + (props: P): ReactElement | null + __dslType: string +} + +export function defineDSL

(type: string): DslComponent

{ + const C = ((_: P) => null) as DslComponent

+ C.__dslType = type + return C +} diff --git a/src/lib/character/psd-character-component.ts b/src/lib/character/psd-character-component.ts new file mode 100644 index 0000000..0f10ba4 --- /dev/null +++ b/src/lib/character/psd-character-component.ts @@ -0,0 +1,31 @@ +import type { Variable } from "../animation" +import { defineDSL } from "./defineDSL" + +export const MotionSequence = defineDSL<{ + children: React.ReactNode +}>("MotionSequence") + +export const DeclareVariable = defineDSL<{ + variableName: string + initValue: any + children: React.ReactNode +}>("DeclareVariable") + +export const Block = defineDSL<{ + children: React.ReactNode +}>("Block") + +export const Animation = defineDSL<{ + f: (ctx: any, variable: Record>) => Promise + children?: React.ReactNode +}>("Animation") + +export const Voice = defineDSL<{ + src: string + volume?: number + children?: React.ReactNode +}>("Voice") + +export const Motion = defineDSL<{ + motion: (variables: Record>, frames: number[]) => Record +}>("Motion") From 97b474755c7b0b222f51fdf1c5b7f66d01cb9a24 Mon Sep 17 00:00:00 2001 From: aqua Date: Sun, 8 Mar 2026 00:03:21 +0900 Subject: [PATCH 05/21] fix: export components --- src/lib/character/index.ts | 1 + src/lib/character/psd-character-component.ts | 6 +++--- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/lib/character/index.ts b/src/lib/character/index.ts index 6b6cfb3..b34fccb 100644 --- a/src/lib/character/index.ts +++ b/src/lib/character/index.ts @@ -1 +1,2 @@ export * from "./psd-character" +export * from "./psd-character-component" diff --git a/src/lib/character/psd-character-component.ts b/src/lib/character/psd-character-component.ts index 0f10ba4..f9bc08b 100644 --- a/src/lib/character/psd-character-component.ts +++ b/src/lib/character/psd-character-component.ts @@ -15,13 +15,13 @@ export const Block = defineDSL<{ children: React.ReactNode }>("Block") -export const Animation = defineDSL<{ +export const DeclareAnimation = defineDSL<{ f: (ctx: any, variable: Record>) => Promise children?: React.ReactNode -}>("Animation") +}>("DeclareAnimation") export const Voice = defineDSL<{ - src: string + voice: string volume?: number children?: React.ReactNode }>("Voice") From ed7ec455c6c2438297b3d629147c5b65ea964d66 Mon Sep 17 00:00:00 2001 From: aqua Date: Sun, 8 Mar 2026 01:20:43 +0900 Subject: [PATCH 06/21] fix: fix DeclareVariable --- src/lib/character/ast.ts | 2 +- src/lib/character/psd-character-component.ts | 21 ++++++++++++++------ 2 files changed, 16 insertions(+), 7 deletions(-) diff --git a/src/lib/character/ast.ts b/src/lib/character/ast.ts index 24662be..0d967eb 100644 --- a/src/lib/character/ast.ts +++ b/src/lib/character/ast.ts @@ -48,7 +48,7 @@ export interface DeclareVariableNode { type: "DeclareVariable" variableName: string initValue: any - children: DeclareVariableChild[] + children: DeclareVariableChild } export interface BlockNode { diff --git a/src/lib/character/psd-character-component.ts b/src/lib/character/psd-character-component.ts index f9bc08b..80080d2 100644 --- a/src/lib/character/psd-character-component.ts +++ b/src/lib/character/psd-character-component.ts @@ -1,31 +1,40 @@ import type { Variable } from "../animation" import { defineDSL } from "./defineDSL" +export const PsdCharacterElement = { + MotionSequence: "MotionSequence", + DeclareVariable: "DeclareVariable", + Block: "Block", + DeclareAnimation: "DeclareAnimation", + Voice: "Voice", + Motion: "Motion", +} + export const MotionSequence = defineDSL<{ children: React.ReactNode -}>("MotionSequence") +}>(PsdCharacterElement.MotionSequence) export const DeclareVariable = defineDSL<{ variableName: string initValue: any children: React.ReactNode -}>("DeclareVariable") +}>(PsdCharacterElement.DeclareVariable) export const Block = defineDSL<{ children: React.ReactNode -}>("Block") +}>(PsdCharacterElement.Block) export const DeclareAnimation = defineDSL<{ f: (ctx: any, variable: Record>) => Promise children?: React.ReactNode -}>("DeclareAnimation") +}>(PsdCharacterElement.DeclareAnimation) export const Voice = defineDSL<{ voice: string volume?: number children?: React.ReactNode -}>("Voice") +}>(PsdCharacterElement.Voice) export const Motion = defineDSL<{ motion: (variables: Record>, frames: number[]) => Record -}>("Motion") +}>(PsdCharacterElement.Motion) From e5b906873253dfbc80b47638d3bcaefa0fb22375 Mon Sep 17 00:00:00 2001 From: aqua Date: Sun, 8 Mar 2026 10:00:33 +0900 Subject: [PATCH 07/21] feature: add parser --- src/lib/character/ast.ts | 29 +- src/lib/character/parser.tsx | 319 +++++++++++++++++++ src/lib/character/psd-character-component.ts | 10 +- src/lib/character/psd-character.tsx | 62 +++- 4 files changed, 398 insertions(+), 22 deletions(-) create mode 100644 src/lib/character/parser.tsx diff --git a/src/lib/character/ast.ts b/src/lib/character/ast.ts index 0d967eb..1a53e58 100644 --- a/src/lib/character/ast.ts +++ b/src/lib/character/ast.ts @@ -1,5 +1,16 @@ import type { Variable } from "../animation" +export const PsdCharacterElement = { + Character: "Character", + MotionSequence: "MotionSequence", + DeclareVariable: "DeclareVariable", + Block: "Block", + DeclareAnimation: "DeclareAnimation", + Voice: "Voice", + Motion: "Motion", +} as const + + export type CharacterChild = | MotionSequenceNode | DeclareVariableNode @@ -35,41 +46,41 @@ export type VoiceChild = export interface CharacterNode { - type: "Character" + type: typeof PsdCharacterElement.Character children: CharacterChild[] } export interface MotionSequenceNode { - type: "MotionSequence" + type: typeof PsdCharacterElement.MotionSequence children: MotionSequenceChild[] } export interface DeclareVariableNode { - type: "DeclareVariable" + type: typeof PsdCharacterElement.DeclareVariable variableName: string initValue: any children: DeclareVariableChild } export interface BlockNode { - type: "Block" - body: BlockChild[] + type: typeof PsdCharacterElement.Block + children: BlockChild[] } export interface DeclareAnimationNode { - type: "Animation" + type: typeof PsdCharacterElement.DeclareAnimation f: (ctx: any, variable: Record>) => Promise - body: DeclareAnimationChild[] + children: DeclareAnimationChild[] } export interface VoiceNode { - type: "Voice" + type: typeof PsdCharacterElement.Voice voice: string children: VoiceChild[] } export interface MotionNode { - type: "Motion" + type: typeof PsdCharacterElement.Motion motion: (variables: Record>, frames: number[]) => Record } diff --git a/src/lib/character/parser.tsx b/src/lib/character/parser.tsx new file mode 100644 index 0000000..6beee8f --- /dev/null +++ b/src/lib/character/parser.tsx @@ -0,0 +1,319 @@ +import React, { isValidElement, type ReactElement, type ReactNode } from "react" +import type { BlockChild, BlockNode, CharacterChild, CharacterNode, DeclareAnimationChild, DeclareAnimationNode, DeclareVariableChild, DeclareVariableNode, MotionNode, MotionSequenceChild, MotionSequenceNode, VoiceChild, VoiceNode } from "./ast" +import { PsdCharacterElement as PsdElm } from "./ast" + +type AnyElement = ReactElement + + +export function parsePsdCharacter( + children: ReactNode, +): CharacterNode { + const body = parsePsdCharacterChildren(children) + return { + type: PsdElm.Character, + children: body, + } +} + +function parsePsdCharacterChildren( + children: ReactNode, +): CharacterChild[] { + const result: CharacterChild[] = [] + + React.Children.forEach(children, (child) => { + if (!isValidElement(child)) return + + const type = getDslType(child) + + switch (type) { + case PsdElm.MotionSequence: + result.push(parseMotionSequence(child)) + break + + case PsdElm.DeclareVariable: + result.push(parseDeclareVariable(child)) + break + + case PsdElm.Voice: + result.push(parseVoice(child)) + break + case PsdElm.Motion: + result.push(parseMotion(child)) + break + case "function": + const expanded = child.type(child.props) + const expandedAst = parsePsdCharacterChildren(expanded) + result.push(...expandedAst) + break + + default: + throw new Error(`Invalid DSL type in root: ${type}`) + } + }) + + return result +} + +function parseMotionSequence( + self: AnyElement, +): MotionSequenceNode { + const { children } = self.props + const body = parseMotionSequenceChildren(children) + return { + type: PsdElm.MotionSequence, + children: body, + } +} + +function parseMotionSequenceChildren( + children: ReactNode, +): MotionSequenceChild[] { + const result: MotionSequenceChild[] = [] + + React.Children.forEach(children, (child) => { + if (!isValidElement(child)) return + + const type = getDslType(child) + + switch (type) { + case PsdElm.Block: + result.push(parseBlock(child)) + break + + case PsdElm.DeclareVariable: + result.push(parseDeclareVariable(child)) + break + + case PsdElm.Voice: + result.push(parseVoice(child)) + break + case PsdElm.Motion: + result.push(parseMotion(child)) + break + case "function": + const expanded = child.type(child.props) + const expandedAst = parseMotionSequenceChildren(expanded) + result.push(...expandedAst) + break + + default: + throw new Error(`Invalid DSL type in ${PsdElm.MotionSequence}: ${type}`) + } + }) + + return result +} + +function parseDeclareVariable( + self: AnyElement, +): DeclareVariableNode { + const { variableName, initValue, children } = self.props + const body = parseDeclareVariableChild(children) + + return { + type: PsdElm.DeclareVariable, + variableName, + initValue, + children: body, + } +} + + +function parseDeclareVariableChild( + children: ReactNode, +): DeclareVariableChild { + + const single = React.Children.toArray(children) + if (single.length == 1) { + const child = single[0] + + if (!isValidElement(child)) { + throw new Error(`Invalid Element in ${PsdElm.DeclareVariable}`) + } + + const type = getDslType(child) + + + switch (type) { + case PsdElm.DeclareVariable: + return parseDeclareVariable(child) + + case PsdElm.DeclareAnimation: + return parseDeclareAnimation(child) + case "function": + const expanded = child.type(child.props) + const expandedAst = parseDeclareVariable(expanded) + return expandedAst + + default: + throw new Error(`Invalid DSL type in ${PsdElm.DeclareVariable}: ${type}`) + } + } else { + throw new Error(`${PsdElm.DeclareVariable} take just one element`) + } +} + +function parseBlock( + self: AnyElement, +): BlockNode { + const { children } = self.props + const body = parseBlockChildren(children) + return { + type: PsdElm.Block, + children: body, + } +} + +function parseBlockChildren( + children: ReactNode, +): BlockChild[] { + const result: BlockChild[] = [] + + React.Children.forEach(children, (child) => { + if (!isValidElement(child)) return + + const type = getDslType(child) + + switch (type) { + case PsdElm.MotionSequence: + result.push(parseMotionSequence(child)) + break + + case PsdElm.DeclareVariable: + result.push(parseDeclareVariable(child)) + break + + case PsdElm.Voice: + result.push(parseVoice(child)) + break + case PsdElm.Motion: + result.push(parseMotion(child)) + break + case "function": + const expanded = child.type(child.props) + const expandedAst = parseBlockChildren(expanded) + result.push(...expandedAst) + break + + default: + throw new Error(`Invalid DSL type in ${PsdElm.Block}: ${type}`) + } + }) + + return result +} + +function parseDeclareAnimation( + self: AnyElement, +): DeclareAnimationNode { + const { f, children } = self.props + const body = parseDeclareAnimationChildren(children) + return { + type: PsdElm.DeclareAnimation, + f, + children: body, + } +} + +function parseDeclareAnimationChildren( + children: ReactNode, +): DeclareAnimationChild[] { + const result: DeclareAnimationChild[] = [] + + React.Children.forEach(children, (child) => { + if (!isValidElement(child)) return + + const type = getDslType(child) + + switch (type) { + case PsdElm.MotionSequence: + result.push(parseMotionSequence(child)) + break + + case PsdElm.DeclareVariable: + result.push(parseDeclareVariable(child)) + break + + case PsdElm.Voice: + result.push(parseVoice(child)) + break + case PsdElm.Motion: + result.push(parseMotion(child)) + break + case "function": + const expanded = child.type(child.props) + const expandedAst = parseDeclareAnimationChildren(expanded) + result.push(...expandedAst) + break + + default: + throw new Error(`Invalid DSL type in ${PsdElm.DeclareAnimation}: ${type}`) + } + }) + + return result +} + +function parseVoice( + self: AnyElement, +): VoiceNode { + const { voice, children } = self.props + const body = parseVoiceChildren(children) + return { + type: PsdElm.Voice, + voice, + children: body, + } +} + +function parseVoiceChildren( + children: ReactNode, +): VoiceChild[] { + const result: VoiceChild[] = [] + + React.Children.forEach(children, (child) => { + if (!isValidElement(child)) return + + const type = getDslType(child) + + switch (type) { + case PsdElm.Motion: + result.push(parseMotion(child)) + break + case "function": + const expanded = child.type(child.props) + const expandedAst = parseVoiceChildren(expanded) + result.push(...expandedAst) + break + + default: + throw new Error(`Invalid DSL type in ${PsdElm.Voice}: ${type}`) + } + }) + + return result +} + +function parseMotion( + self: AnyElement, +): MotionNode { + const { motion } = self.props + return { + type: PsdElm.Motion, + motion + } +} + + +function getDslType(el: AnyElement): string | undefined { + const type = el.type as any + + if (type?.__dslType) { + return type.__dslType + } + if (typeof type === "function") { + return "function" + } + + return undefined +} diff --git a/src/lib/character/psd-character-component.ts b/src/lib/character/psd-character-component.ts index 80080d2..3b37357 100644 --- a/src/lib/character/psd-character-component.ts +++ b/src/lib/character/psd-character-component.ts @@ -1,14 +1,6 @@ import type { Variable } from "../animation" import { defineDSL } from "./defineDSL" - -export const PsdCharacterElement = { - MotionSequence: "MotionSequence", - DeclareVariable: "DeclareVariable", - Block: "Block", - DeclareAnimation: "DeclareAnimation", - Voice: "Voice", - Motion: "Motion", -} +import { PsdCharacterElement } from "./ast" export const MotionSequence = defineDSL<{ children: React.ReactNode diff --git a/src/lib/character/psd-character.tsx b/src/lib/character/psd-character.tsx index bb1a8e4..fcf59d8 100644 --- a/src/lib/character/psd-character.tsx +++ b/src/lib/character/psd-character.tsx @@ -1,13 +1,67 @@ -import React from "react" +import React, { useEffect, useState } from "react" + +import { PsdCharacterElement as PsdElm, type CharacterNode } from "./ast" +import { readPsd, type Psd } from "ag-psd" +import { parsePsdCharacter } from "./parser" type PsdCharacterProps = { psd: string children: React.ReactNode } +type PsdPath = { + path: string +} + export const PsdCharacter = ({ - psd, - children + psd, + children }: PsdCharacterProps) => { - return null + const [myPsd, setPsd] = useState(undefined) + const [ast, setAst] = useState(undefined) + + useEffect(() => { + fetchPsd(normalizePsdPath(psd)).then(p => setPsd(p)) + setAst(parsePsdCharacter(children)) + }, []) + + return null +} + +const psdCache = new Map() +const psdPending = new Map>() + +const fetchPsd = async (psd: PsdPath): Promise => { + const cached = psdCache.get(psd.path) + if (cached != null) return cached + + const pending = psdPending.get(psd.path) + if (pending) return pending + + const next = (async () => { + const res = await fetch(buildPsdUrl(psd)) + if (!res.ok) { + throw new Error("failed to fetch psd file") + } + + const file = readPsd(await res.arrayBuffer()) + psdCache.set(psd.path, file) + return file + })().finally(() => { + psdPending.delete(psd.path) + }) + + psdPending.set(psd.path, next) + return next +} + +const normalizePsdPath = (psd: PsdPath | string): PsdPath => { + if (typeof psd === "string") return { path: psd } + return psd +} + +const buildPsdUrl = (path: PsdPath) => { + const url = new URL("http://localhost:3000/file") + url.searchParams.set("path", path) + return url.toString() } From 519773cfe539df820cffbe5e4fb2a3c1f71b3552 Mon Sep 17 00:00:00 2001 From: aqua Date: Sun, 8 Mar 2026 10:05:16 +0900 Subject: [PATCH 08/21] fix: fix fetch url --- src/lib/character/psd-character.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/lib/character/psd-character.tsx b/src/lib/character/psd-character.tsx index fcf59d8..afead0e 100644 --- a/src/lib/character/psd-character.tsx +++ b/src/lib/character/psd-character.tsx @@ -60,8 +60,8 @@ const normalizePsdPath = (psd: PsdPath | string): PsdPath => { return psd } -const buildPsdUrl = (path: PsdPath) => { +const buildPsdUrl = (pad: PsdPath) => { const url = new URL("http://localhost:3000/file") - url.searchParams.set("path", path) + url.searchParams.set("path", pad.path) return url.toString() } From 7dabdd98a0dd4e4596fdc79bbd60ab367ba3cc25 Mon Sep 17 00:00:00 2001 From: aqua Date: Sun, 8 Mar 2026 14:06:22 +0900 Subject: [PATCH 09/21] feature: add runtime --- src/lib/character/psd-character-component.ts | 2 +- src/lib/character/psd-character.tsx | 464 ++++++++++++++++++- 2 files changed, 462 insertions(+), 4 deletions(-) diff --git a/src/lib/character/psd-character-component.ts b/src/lib/character/psd-character-component.ts index 3b37357..855a272 100644 --- a/src/lib/character/psd-character-component.ts +++ b/src/lib/character/psd-character-component.ts @@ -18,7 +18,7 @@ export const Block = defineDSL<{ export const DeclareAnimation = defineDSL<{ f: (ctx: any, variable: Record>) => Promise - children?: React.ReactNode + children: React.ReactNode }>(PsdCharacterElement.DeclareAnimation) export const Voice = defineDSL<{ diff --git a/src/lib/character/psd-character.tsx b/src/lib/character/psd-character.tsx index afead0e..dda75b3 100644 --- a/src/lib/character/psd-character.tsx +++ b/src/lib/character/psd-character.tsx @@ -1,8 +1,13 @@ -import React, { useEffect, useState } from "react" +import React, { useEffect, useMemo, useRef, useState } from "react" -import { PsdCharacterElement as PsdElm, type CharacterNode } from "./ast" +import { PsdCharacterElement as PsdElm, type BlockNode, type CharacterNode, type DeclareAnimationNode, type DeclareVariableNode, type MotionNode, type MotionSequenceNode, type VoiceNode } from "./ast" import { readPsd, type Psd } from "ag-psd" import { parsePsdCharacter } from "./parser" +import { renderPsd } from "ag-psd-psdtool" +import { useAnimation, useVariable, type Variable } from "../animation" +import { useCurrentFrame, useGlobalCurrentFrame } from "../frame" +import { Sound } from "../sound/sound" +import { Clip, ClipSequence } from "../clip" type PsdCharacterProps = { psd: string @@ -13,6 +18,14 @@ type PsdPath = { path: string } +type PsdOptions = Record +type OptionRegister = () => { + update: (opt: Record) => void + getter: () => Record + unregister: () => void +} + + export const PsdCharacter = ({ psd, children @@ -20,14 +33,459 @@ export const PsdCharacter = ({ const [myPsd, setPsd] = useState(undefined) const [ast, setAst] = useState(undefined) + const registry = useRef(new Map()) + const order = useRef([]) + + const [options, setOptions] = useState({}) + + const canvas = useRef(null) + useEffect(() => { fetchPsd(normalizePsdPath(psd)).then(p => setPsd(p)) setAst(parsePsdCharacter(children)) }, []) - return null + useEffect(() => { + if (typeof myPsd !== "undefined" && canvas.current) { + renderPsd(myPsd, options, { canvas: canvas.current }) + } + }, [myPsd, options, canvas]) + + const recompute = () => { + const merged = Object.assign({}, ...registry.current.values()) + setOptions(merged) + } + + const register = () => { + const id = crypto.randomUUID() + + registry.current.set(id, {}) + order.current.push(id) + + const update = (opt: PsdOptions) => { + registry.current.set(id, opt) + recompute() + } + + const unregister = () => { + registry.current.delete(id) + order.current = order.current.filter(x => x !== id) + recompute() + } + + const getter = () => { + const index = order.current.indexOf(id) + + const prevIds = order.current.slice(0, index) + + const prevOptions = prevIds.map(i => registry.current.get(i) ?? {}) + + return Object.assign({}, ...prevOptions) + } + + return { + update, + getter, + unregister, + } + } + + + return ( + <> + + {ast?.children.map((child, i) => { + switch (child.type) { + case PsdElm.MotionSequence: + return + case PsdElm.DeclareVariable: + return + case PsdElm.Voice: + return + case PsdElm.Motion: + return + default: + return

+ } + })} + + ) +} + +type MotionSequenceRuntimeProps = { + ast: MotionSequenceNode + variables: Record> + register: OptionRegister +} + +const MotionSequenceRuntime = ({ + ast, + variables, + register +}: MotionSequenceRuntimeProps) => { + const {update, getter, unregister} = register() + const curRegister: OptionRegister = () => { + return {update, getter, unregister} + } + + return ( + + {ast.children.map(child => { + switch (child.type) { + case PsdElm.DeclareVariable: + return + case PsdElm.Block: + return + case PsdElm.Voice: + return + case PsdElm.Motion: + return + default: + return
+ } + }).map((child, i) => {child} )} + + ) +} + +type DeclareVariableRuntimeProps = { + ast: DeclareVariableNode + variables: Record> + initializingVariables: Record> + register: OptionRegister +} + +const DeclareVariableRuntime = ({ + ast, + variables, + initializingVariables, + register +}: DeclareVariableRuntimeProps) => { + const variable = useVariable(ast.initValue) + const newInitVariables = {[ast.variableName]: variable, ...initializingVariables} + + switch (ast.children.type) { + case PsdElm.DeclareVariable: + return + case PsdElm.DeclareAnimation: + return + default: + return
+ } } +type BlockRuntimeProps = { + ast: BlockNode + variables: Record> + register: OptionRegister +} + +const BlockRuntime = ({ + ast, + variables, + register +}: BlockRuntimeProps) => { + const {update, getter: superGetter, unregister} = register() + + useEffect(() => { + return () => unregister() + }, []) + + const curRegistry = useRef(new Map()) + const order = useRef([]) + + const [options, setOptions] = useState({}) + + const recompute = () => { + const merged = Object.assign({}, ...curRegistry.current.values()) + setOptions(merged) + } + + const curRegister = () => { + const id = crypto.randomUUID() + + curRegistry.current.set(id, {}) + order.current.push(id) + + const update = (opt: PsdOptions) => { + curRegistry.current.set(id, opt) + recompute() + } + + const unregister = () => { + curRegistry.current.delete(id) + order.current = order.current.filter(x => x !== id) + recompute() + } + + const getter = () => { + const index = order.current.indexOf(id) + + const prevIds = order.current.slice(0, index) + + const prevOptions = prevIds.map(i => curRegistry.current.get(i) ?? {}) + + return Object.assign(superGetter(), ...prevOptions) + } + + return { + update, + getter, + unregister, + } + } + + useEffect(() => { + update(options) + }, [options]) + + + return ( + <> + {ast.children.map((child, i) => { + switch (child.type) { + case PsdElm.MotionSequence: + return + case PsdElm.DeclareVariable: + return + case PsdElm.Voice: + return + case PsdElm.Motion: + return + default: + return
+ } + })} + + ) +} + +type DeclareAnimationRuntimeProps = { + ast: DeclareAnimationNode + variables: Record> + initializingVariables: Record> + register: OptionRegister +} + +const DeclareAnimationRuntime = ({ + ast, + variables, + initializingVariables, + register +}: DeclareAnimationRuntimeProps) => { + + useAnimation(async (ctx) => { + ast.f(ctx, initializingVariables) + }, []) + + const curVariables = Object.assign(variables, initializingVariables) + + const {update, getter: superGetter, unregister} = register() + + useEffect(() => { + return () => unregister() + }, []) + + const curRegistry = useRef(new Map()) + const order = useRef([]) + + const [options, setOptions] = useState({}) + + const recompute = () => { + const merged = Object.assign({}, ...curRegistry.current.values()) + setOptions(merged) + } + + const curRegister = () => { + const id = crypto.randomUUID() + + curRegistry.current.set(id, {}) + order.current.push(id) + + const update = (opt: PsdOptions) => { + curRegistry.current.set(id, opt) + recompute() + } + + const unregister = () => { + curRegistry.current.delete(id) + order.current = order.current.filter(x => x !== id) + recompute() + } + + const getter = () => { + const index = order.current.indexOf(id) + + const prevIds = order.current.slice(0, index) + + const prevOptions = prevIds.map(i => curRegistry.current.get(i) ?? {}) + + return Object.assign(superGetter(), ...prevOptions) + } + + return { + update, + getter, + unregister, + } + } + + useEffect(() => { + update(options) + }, [options]) + + return ( + <> + {ast.children.map((child, i) => { + switch (child.type) { + case PsdElm.MotionSequence: + return + case PsdElm.DeclareVariable: + return + case PsdElm.Voice: + return + case PsdElm.Motion: + return + default: + return
+ } + })} + + ) +} + +type VoiceRuntimeProps = { + ast: VoiceNode, + variables: Record> + register: OptionRegister +} + +const VoiceRuntime = ({ + ast, + variables, + register +}: VoiceRuntimeProps) => { + return +} + +type MotionRuntimeProps = { + ast: MotionNode, + variables: Record> + register: OptionRegister +} + +const MotionRuntime = ({ + ast, + variables, + register +}: MotionRuntimeProps) => { + const { update, getter, unregister } = useMemo(() => register(), [register]) + + useEffect(() => { + return () => unregister() + }, []) + + const localTime = useCurrentFrame() + const globalTime = useGlobalCurrentFrame() + + useEffect(() => { + update(ast.motion(variables, [localTime, globalTime])) + }, [localTime, globalTime]) + + return
+} + + const psdCache = new Map() const psdPending = new Map>() From 186f19a6c8c5042442cc1d1da884244f34d99444 Mon Sep 17 00:00:00 2001 From: aqua Date: Sun, 8 Mar 2026 14:23:04 +0900 Subject: [PATCH 10/21] feature: add voice volume tune --- src/lib/character/ast.ts | 1 + src/lib/character/parser.tsx | 3 ++- src/lib/character/psd-character-component.ts | 2 +- src/lib/character/psd-character.tsx | 16 +++++++++++++++- 4 files changed, 19 insertions(+), 3 deletions(-) diff --git a/src/lib/character/ast.ts b/src/lib/character/ast.ts index 1a53e58..18940b6 100644 --- a/src/lib/character/ast.ts +++ b/src/lib/character/ast.ts @@ -76,6 +76,7 @@ export interface DeclareAnimationNode { export interface VoiceNode { type: typeof PsdCharacterElement.Voice voice: string + volume: undefined | number | ((variables: Record>, frames: number[]) => number) children: VoiceChild[] } diff --git a/src/lib/character/parser.tsx b/src/lib/character/parser.tsx index 6beee8f..ea86c47 100644 --- a/src/lib/character/parser.tsx +++ b/src/lib/character/parser.tsx @@ -257,11 +257,12 @@ function parseDeclareAnimationChildren( function parseVoice( self: AnyElement, ): VoiceNode { - const { voice, children } = self.props + const { voice, volume, children } = self.props const body = parseVoiceChildren(children) return { type: PsdElm.Voice, voice, + volume: volume ?? undefined, children: body, } } diff --git a/src/lib/character/psd-character-component.ts b/src/lib/character/psd-character-component.ts index 855a272..1859f93 100644 --- a/src/lib/character/psd-character-component.ts +++ b/src/lib/character/psd-character-component.ts @@ -23,7 +23,7 @@ export const DeclareAnimation = defineDSL<{ export const Voice = defineDSL<{ voice: string - volume?: number + volume?: number | ((variables: Record>, frames: number[]) => number) children?: React.ReactNode }>(PsdCharacterElement.Voice) diff --git a/src/lib/character/psd-character.tsx b/src/lib/character/psd-character.tsx index dda75b3..e789355 100644 --- a/src/lib/character/psd-character.tsx +++ b/src/lib/character/psd-character.tsx @@ -455,7 +455,21 @@ const VoiceRuntime = ({ variables, register }: VoiceRuntimeProps) => { - return + const local_frame = useCurrentFrame() + const global_frame = useGlobalCurrentFrame() + const frames = [local_frame, global_frame] + + const volume = + typeof ast.volume === "function" + ? ast.volume(variables, frames) + : ast.volume + + return ( + + ) } type MotionRuntimeProps = { From 07926cec0191aca2cd467fdbae5ccd1fe84bde30 Mon Sep 17 00:00:00 2001 From: aqua Date: Sun, 8 Mar 2026 14:31:43 +0900 Subject: [PATCH 11/21] feature: fix voice volume --- src/lib/character/psd-character-component.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/character/psd-character-component.ts b/src/lib/character/psd-character-component.ts index 1859f93..855a272 100644 --- a/src/lib/character/psd-character-component.ts +++ b/src/lib/character/psd-character-component.ts @@ -23,7 +23,7 @@ export const DeclareAnimation = defineDSL<{ export const Voice = defineDSL<{ voice: string - volume?: number | ((variables: Record>, frames: number[]) => number) + volume?: number children?: React.ReactNode }>(PsdCharacterElement.Voice) From 8653b8fdb2dc3f396bee04264f16f190f1e90dda Mon Sep 17 00:00:00 2001 From: aqua Date: Sun, 8 Mar 2026 17:38:56 +0900 Subject: [PATCH 12/21] fix: fix register cache --- src/lib/character/index.ts | 1 + src/lib/character/psd-character.tsx | 61 ++++++++------- src/lib/character/util-motions.tsx | 110 ++++++++++++++++++++++++++++ 3 files changed, 146 insertions(+), 26 deletions(-) create mode 100644 src/lib/character/util-motions.tsx diff --git a/src/lib/character/index.ts b/src/lib/character/index.ts index b34fccb..568f16d 100644 --- a/src/lib/character/index.ts +++ b/src/lib/character/index.ts @@ -1,2 +1,3 @@ export * from "./psd-character" export * from "./psd-character-component" +export * from "./util-motions" diff --git a/src/lib/character/psd-character.tsx b/src/lib/character/psd-character.tsx index e789355..e0cc098 100644 --- a/src/lib/character/psd-character.tsx +++ b/src/lib/character/psd-character.tsx @@ -1,4 +1,4 @@ -import React, { useEffect, useMemo, useRef, useState } from "react" +import React, { useCallback, useEffect, useMemo, useRef, useState } from "react" import { PsdCharacterElement as PsdElm, type BlockNode, type CharacterNode, type DeclareAnimationNode, type DeclareVariableNode, type MotionNode, type MotionSequenceNode, type VoiceNode } from "./ast" import { readPsd, type Psd } from "ag-psd" @@ -51,10 +51,10 @@ export const PsdCharacter = ({ } }, [myPsd, options, canvas]) - const recompute = () => { + const recompute = useCallback(() => { const merged = Object.assign({}, ...registry.current.values()) setOptions(merged) - } + }, []) const register = () => { const id = crypto.randomUUID() @@ -126,7 +126,7 @@ export const PsdCharacter = ({ register={register} /> default: - return
+ return null } })} @@ -144,10 +144,16 @@ const MotionSequenceRuntime = ({ variables, register }: MotionSequenceRuntimeProps) => { - const {update, getter, unregister} = register() - const curRegister: OptionRegister = () => { - return {update, getter, unregister} - } + const reg = useRef(register()) + const {update, getter, unregister} = reg.current + + useEffect(() => { + return () => unregister() + }, []) + + const curRegister: OptionRegister = useCallback(() => { + return {update, getter, unregister: () => {}} + }, []) return ( @@ -179,7 +185,7 @@ const MotionSequenceRuntime = ({ register={curRegister} /> default: - return
+ return null } }).map((child, i) => {child} )} @@ -218,7 +224,7 @@ const DeclareVariableRuntime = ({ register={register} /> default: - return
+ return null } } @@ -233,7 +239,8 @@ const BlockRuntime = ({ variables, register }: BlockRuntimeProps) => { - const {update, getter: superGetter, unregister} = register() + const reg = useRef(register()) + const {update, getter: superGetter, unregister} = reg.current useEffect(() => { return () => unregister() @@ -244,12 +251,12 @@ const BlockRuntime = ({ const [options, setOptions] = useState({}) - const recompute = () => { + const recompute = useCallback(() => { const merged = Object.assign({}, ...curRegistry.current.values()) setOptions(merged) - } + }, []) - const curRegister = () => { + const curRegister = useCallback(() => { const id = crypto.randomUUID() curRegistry.current.set(id, {}) @@ -281,7 +288,7 @@ const BlockRuntime = ({ getter, unregister, } - } + }, []) useEffect(() => { update(options) @@ -322,7 +329,7 @@ const BlockRuntime = ({ register={curRegister} /> default: - return
+ return null } })} @@ -349,7 +356,8 @@ const DeclareAnimationRuntime = ({ const curVariables = Object.assign(variables, initializingVariables) - const {update, getter: superGetter, unregister} = register() + const reg = useRef(register()) + const {update, getter: superGetter, unregister} = reg.current useEffect(() => { return () => unregister() @@ -360,12 +368,12 @@ const DeclareAnimationRuntime = ({ const [options, setOptions] = useState({}) - const recompute = () => { + const recompute = useCallback(() => { const merged = Object.assign({}, ...curRegistry.current.values()) setOptions(merged) - } + }, []) - const curRegister = () => { + const curRegister = useCallback(() => { const id = crypto.randomUUID() curRegistry.current.set(id, {}) @@ -397,7 +405,7 @@ const DeclareAnimationRuntime = ({ getter, unregister, } - } + }, []) useEffect(() => { update(options) @@ -437,7 +445,7 @@ const DeclareAnimationRuntime = ({ register={curRegister} /> default: - return
+ return null } })} @@ -458,12 +466,12 @@ const VoiceRuntime = ({ const local_frame = useCurrentFrame() const global_frame = useGlobalCurrentFrame() const frames = [local_frame, global_frame] - + const volume = typeof ast.volume === "function" ? ast.volume(variables, frames) : ast.volume - + return ( { - const { update, getter, unregister } = useMemo(() => register(), [register]) + const reg = useRef(register()) + const { update, getter, unregister } = reg.current useEffect(() => { return () => unregister() @@ -496,7 +505,7 @@ const MotionRuntime = ({ update(ast.motion(variables, [localTime, globalTime])) }, [localTime, globalTime]) - return
+ return null } diff --git a/src/lib/character/util-motions.tsx b/src/lib/character/util-motions.tsx new file mode 100644 index 0000000..b8ffc21 --- /dev/null +++ b/src/lib/character/util-motions.tsx @@ -0,0 +1,110 @@ +import type { warn } from "console" +import { framesToSeconds } from "../audio" +import { useCurrentFrame } from "../frame" +import { Motion } from "./psd-character-component" + + +export type BasicPsdOptions = { + eye: EyeOptions + mouth: MouthOptions +} + +export type EyeOptions = { kind: "enum"; options: EyeEnum } | { kind: "bool"; options: EyeBool } +export type MouthOptions = { kind: "enum"; options: MouthEnum } | { kind: "bool"; options: MouthBool } + +type EyeShape = "Open" | "HalfOpen" | "HalfClosed" | "Closed" + +export type EyeEnum = { + // enum本体 + Eye: string + Default: string +} & Record + +export type EyeBool = { + Default: string +} & Record + +type MouthShape = "a" | "i" | "u" | "e" | "o" | "x" + +export type MouthEnum = { + Mouth: string + Default: string +} & Record + +export type MouthBool = { + Default: string +} & Record + +type HasKey = { + [P in K]: V +} & Record + +export type LipSyncData = HasKey<"mouthCues", {start: number, end: number, value: string}[]> + +export type LipSyncProps = { + data: LipSyncData +} + +export const createLipSync = (mouthOptions: MouthOptions) => { + return ({ data }: LipSyncProps) => { + return { + const t = framesToSeconds(frames[0]) + let shape: MouthShape | undefined = undefined + for (let section of data.mouthCues) { + if (section.start <= t && t < section.end) { + shape = lipSyncValueToMouthShape(section.value) + break + } + } + + if (!shape) { + return {} + } + + + if (mouthOptions.kind === "enum") { + return { + [mouthOptions.options.Mouth]: mouthOptions.options[shape] + } + } else if (mouthOptions.options[shape] == mouthOptions.options.Default) { + return { + [mouthOptions.options.Default]: true + } + } else { + const opt = { + [mouthOptions.options.Default]: false, + [mouthOptions.options[shape]]: true + } + + return opt + } + + }} /> + } +} + +const lipSyncValueToMouthShape = (value: string): MouthShape => { + switch (value) { + case "A": + return "a" + case "B": + return "i" + case "C": + return "e" + case "D": + return "a" + case "E": + return "o" + case "F": + return "u" + case "G": + return "i" + case "H": + return "u" + case "X": + return "x" + default: + return "x" + } +} + From e4cd0fd439f4e43982a6886ebf7677acfbec48e6 Mon Sep 17 00:00:00 2001 From: aqua Date: Sun, 8 Mar 2026 18:08:58 +0900 Subject: [PATCH 13/21] feature: add blink --- src/lib/character/util-motions.tsx | 78 ++++++++++++++++++++++++++++++ 1 file changed, 78 insertions(+) diff --git a/src/lib/character/util-motions.tsx b/src/lib/character/util-motions.tsx index b8ffc21..719cf0b 100644 --- a/src/lib/character/util-motions.tsx +++ b/src/lib/character/util-motions.tsx @@ -39,6 +39,9 @@ type HasKey = { [P in K]: V } & Record + +// lip sync -------------------------------- + export type LipSyncData = HasKey<"mouthCues", {start: number, end: number, value: string}[]> export type LipSyncProps = { @@ -108,3 +111,78 @@ const lipSyncValueToMouthShape = (value: string): MouthShape => { } } +// blink -------------------------------- +// +export type BlinkData = HasKey<"blinkCues", {start: number, end: number, value: string}[]> + +export type BlinkProps = { + data: BlinkData +} + +export const createBlink = (eyeOptions: EyeOptions) => { + return ({ data }: BlinkProps) => { + return { + + const t = framesToSeconds(frames[1]) + const sections = data.blinkCues + + let lo = 0 + let hi = sections.length - 1 + let idx = -1 + + while (lo <= hi) { + const mid = (lo + hi) >> 1 + + if (sections[mid].start <= t) { + idx = mid + lo = mid + 1 + } else { + hi = mid - 1 + } + } + + let shape: EyeShape | undefined = undefined + if (idx !== -1 && t < sections[idx].end) { + shape = BlinkValueToEyeShape(sections[idx].value) + } + + if (!shape) { + return {} + } + + + if (eyeOptions.kind === "enum") { + return { + [eyeOptions.options.Eye]: eyeOptions.options[shape] + } + } else if (eyeOptions.options[shape] == eyeOptions.options.Default) { + return { + [eyeOptions.options.Default]: true + } + } else { + const opt = { + [eyeOptions.options.Default]: false, + [eyeOptions.options[shape]]: true + } + + return opt + } + + }} /> + } +} + +const BlinkValueToEyeShape = (value: string): EyeShape => { + switch (value) { + case "A": + return "Open" + case "B": + return "HalfOpen" + case "C": + return "HalfClosed" + case "D": + return "Closed" + default: + return "Open" + } +} From 8dc4c22b1fb0079e51f3a0ec041d4f06cff44072 Mon Sep 17 00:00:00 2001 From: aqua Date: Sun, 8 Mar 2026 20:26:54 +0900 Subject: [PATCH 14/21] fix: performance tune --- src/lib/character/psd-character.tsx | 34 +++++++++++++++++------------ 1 file changed, 20 insertions(+), 14 deletions(-) diff --git a/src/lib/character/psd-character.tsx b/src/lib/character/psd-character.tsx index e0cc098..1cac4fd 100644 --- a/src/lib/character/psd-character.tsx +++ b/src/lib/character/psd-character.tsx @@ -36,7 +36,7 @@ export const PsdCharacter = ({ const registry = useRef(new Map()) const order = useRef([]) - const [options, setOptions] = useState({}) + const options = useRef({}) const canvas = useRef(null) @@ -45,18 +45,22 @@ export const PsdCharacter = ({ setAst(parsePsdCharacter(children)) }, []) + // 毎フレーム実行 + const frame = useCurrentFrame() useEffect(() => { if (typeof myPsd !== "undefined" && canvas.current) { - renderPsd(myPsd, options, { canvas: canvas.current }) + renderPsd(myPsd, options.current, { canvas: canvas.current }) } - }, [myPsd, options, canvas]) + }, [frame]) + // registryをmergeしてoptionsを変更 const recompute = useCallback(() => { const merged = Object.assign({}, ...registry.current.values()) - setOptions(merged) + options.current = merged }, []) - const register = () => { + // registerを分配し、registryに記録 + const register = useCallback(() => { const id = crypto.randomUUID() registry.current.set(id, {}) @@ -88,7 +92,7 @@ export const PsdCharacter = ({ getter, unregister, } - } + }, []) return ( @@ -249,11 +253,11 @@ const BlockRuntime = ({ const curRegistry = useRef(new Map()) const order = useRef([]) - const [options, setOptions] = useState({}) + const options = useRef({}) const recompute = useCallback(() => { const merged = Object.assign({}, ...curRegistry.current.values()) - setOptions(merged) + options.current = merged }, []) const curRegister = useCallback(() => { @@ -290,9 +294,10 @@ const BlockRuntime = ({ } }, []) + const frame = useCurrentFrame() useEffect(() => { - update(options) - }, [options]) + update(options.current) + }, [frame]) return ( @@ -354,7 +359,7 @@ const DeclareAnimationRuntime = ({ ast.f(ctx, initializingVariables) }, []) - const curVariables = Object.assign(variables, initializingVariables) + const curVariables = {...variables, ...initializingVariables} const reg = useRef(register()) const {update, getter: superGetter, unregister} = reg.current @@ -366,11 +371,11 @@ const DeclareAnimationRuntime = ({ const curRegistry = useRef(new Map()) const order = useRef([]) - const [options, setOptions] = useState({}) + const options = useRef({}) const recompute = useCallback(() => { const merged = Object.assign({}, ...curRegistry.current.values()) - setOptions(merged) + options.current = merged }, []) const curRegister = useCallback(() => { @@ -407,9 +412,10 @@ const DeclareAnimationRuntime = ({ } }, []) + const frame = useCurrentFrame() useEffect(() => { update(options) - }, [options]) + }, [frame]) return ( <> From d658b9d44f1ad392b73982c2d02935d1bb0a4275 Mon Sep 17 00:00:00 2001 From: aqua Date: Sun, 8 Mar 2026 20:30:56 +0900 Subject: [PATCH 15/21] fix: animation error --- src/lib/character/psd-character.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/character/psd-character.tsx b/src/lib/character/psd-character.tsx index 1cac4fd..4f3fd6d 100644 --- a/src/lib/character/psd-character.tsx +++ b/src/lib/character/psd-character.tsx @@ -414,7 +414,7 @@ const DeclareAnimationRuntime = ({ const frame = useCurrentFrame() useEffect(() => { - update(options) + update(options.current) }, [frame]) return ( From 62f2452d554e7addccd49e7d59941ad1f396c3cb Mon Sep 17 00:00:00 2001 From: aqua Date: Sun, 8 Mar 2026 22:40:08 +0900 Subject: [PATCH 16/21] fix: add package for psd --- package.json | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index 0d33137..666a8d8 100644 --- a/package.json +++ b/package.json @@ -30,6 +30,8 @@ "@ffmpeg-installer/ffmpeg": "^1.1.0", "@ffprobe-installer/ffprobe": "^2.1.2", "@types/opentype.js": "^1.3.4", + "ag-psd": "^30.1.0", + "ag-psd-psdtool": "^1.1.10", "mathjax-full": "^3.2.1", "opentype.js": "^1.3.4", "prismjs": "^1.30.0", @@ -37,7 +39,9 @@ "react": "^19.2.0", "react-dom": "^19.2.0", "react-router-dom": "^6.28.0", - "three": "^0.166.1" + "three": "^0.166.1", + "ag-psd": "^30.1.0", + "ag-psd-psdtool": "^1.1.10" }, "devDependencies": { "@eslint/js": "^9.39.1", From 88bf1e027bd58aa432bae3e1aff4d19aadeeb1ce Mon Sep 17 00:00:00 2001 From: aqua Date: Sun, 8 Mar 2026 23:25:28 +0900 Subject: [PATCH 17/21] add: add comment --- src/lib/character/psd-character-component.ts | 8 ++++++++ src/lib/character/psd-character.tsx | 2 ++ src/lib/character/util-motions.tsx | 4 +++- 3 files changed, 13 insertions(+), 1 deletion(-) diff --git a/src/lib/character/psd-character-component.ts b/src/lib/character/psd-character-component.ts index 855a272..66edc38 100644 --- a/src/lib/character/psd-character-component.ts +++ b/src/lib/character/psd-character-component.ts @@ -2,31 +2,39 @@ import type { Variable } from "../animation" import { defineDSL } from "./defineDSL" import { PsdCharacterElement } from "./ast" +// 要素を直列化する export const MotionSequence = defineDSL<{ children: React.ReactNode }>(PsdCharacterElement.MotionSequence) +// Variableを宣言する export const DeclareVariable = defineDSL<{ variableName: string initValue: any children: React.ReactNode }>(PsdCharacterElement.DeclareVariable) +// MotionSequence直下で使用し、Block内を並列化する export const Block = defineDSL<{ children: React.ReactNode }>(PsdCharacterElement.Block) +// 宣言されたVariableをアニメーションとして登録する export const DeclareAnimation = defineDSL<{ f: (ctx: any, variable: Record>) => Promise children: React.ReactNode }>(PsdCharacterElement.DeclareAnimation) +// 音声を配置する(ファイルのみ) export const Voice = defineDSL<{ voice: string volume?: number children?: React.ReactNode }>(PsdCharacterElement.Voice) +// psdファイルのオプションを制御し、動きをつける +// frames[0]: useCurrentFrame +// frames[frames.length - 1]: useGlobalCurrentFrame export const Motion = defineDSL<{ motion: (variables: Record>, frames: number[]) => Record }>(PsdCharacterElement.Motion) diff --git a/src/lib/character/psd-character.tsx b/src/lib/character/psd-character.tsx index 4f3fd6d..c8a52e4 100644 --- a/src/lib/character/psd-character.tsx +++ b/src/lib/character/psd-character.tsx @@ -33,6 +33,7 @@ export const PsdCharacter = ({ const [myPsd, setPsd] = useState(undefined) const [ast, setAst] = useState(undefined) + // オプションをレイヤーごとに管理する const registry = useRef(new Map()) const order = useRef([]) @@ -155,6 +156,7 @@ const MotionSequenceRuntime = ({ return () => unregister() }, []) + // 直列のため同じregisterを使う const curRegister: OptionRegister = useCallback(() => { return {update, getter, unregister: () => {}} }, []) diff --git a/src/lib/character/util-motions.tsx b/src/lib/character/util-motions.tsx index 719cf0b..6e3ded8 100644 --- a/src/lib/character/util-motions.tsx +++ b/src/lib/character/util-motions.tsx @@ -48,6 +48,7 @@ export type LipSyncProps = { data: LipSyncData } +// psdに対応する辞書を用いてあいうえお口パクするコンポーネントを返す export const createLipSync = (mouthOptions: MouthOptions) => { return ({ data }: LipSyncProps) => { return { @@ -112,13 +113,14 @@ const lipSyncValueToMouthShape = (value: string): MouthShape => { } // blink -------------------------------- -// + export type BlinkData = HasKey<"blinkCues", {start: number, end: number, value: string}[]> export type BlinkProps = { data: BlinkData } +// psdに対応する辞書を用いて目パチするコンポーネントを返す export const createBlink = (eyeOptions: EyeOptions) => { return ({ data }: BlinkProps) => { return { From 0a0c5c3804a39dd7c7b596b6154d1c9af3499e4a Mon Sep 17 00:00:00 2001 From: aqua Date: Sun, 8 Mar 2026 23:30:53 +0900 Subject: [PATCH 18/21] chore: delete duplicated package --- package-lock.json | 372 ++++++++++++++++++++++++++++++---------------- 1 file changed, 240 insertions(+), 132 deletions(-) diff --git a/package-lock.json b/package-lock.json index 7f258af..da0a034 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,6 +11,8 @@ "@ffmpeg-installer/ffmpeg": "^1.1.0", "@ffprobe-installer/ffprobe": "^2.1.2", "@types/opentype.js": "^1.3.4", + "ag-psd": "^30.1.0", + "ag-psd-psdtool": "^1.1.10", "mathjax-full": "^3.2.1", "opentype.js": "^1.3.4", "prismjs": "^1.30.0", @@ -1410,308 +1412,325 @@ "license": "MIT" }, "node_modules/@rollup/rollup-android-arm-eabi": { - "version": "4.53.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.53.3.tgz", - "integrity": "sha512-mRSi+4cBjrRLoaal2PnqH82Wqyb+d3HsPUN/W+WslCXsZsyHa9ZeQQX/pQsZaVIWDkPcpV6jJ+3KLbTbgnwv8w==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.59.0.tgz", + "integrity": "sha512-upnNBkA6ZH2VKGcBj9Fyl9IGNPULcjXRlg0LLeaioQWueH30p6IXtJEbKAgvyv+mJaMxSm1l6xwDXYjpEMiLMg==", "cpu": [ "arm" ], "dev": true, - "license": "MIT", "optional": true, "os": [ "android" ] }, "node_modules/@rollup/rollup-android-arm64": { - "version": "4.53.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.53.3.tgz", - "integrity": "sha512-CbDGaMpdE9sh7sCmTrTUyllhrg65t6SwhjlMJsLr+J8YjFuPmCEjbBSx4Z/e4SmDyH3aB5hGaJUP2ltV/vcs4w==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.59.0.tgz", + "integrity": "sha512-hZ+Zxj3SySm4A/DylsDKZAeVg0mvi++0PYVceVyX7hemkw7OreKdCvW2oQ3T1FMZvCaQXqOTHb8qmBShoqk69Q==", "cpu": [ "arm64" ], "dev": true, - "license": "MIT", "optional": true, "os": [ "android" ] }, "node_modules/@rollup/rollup-darwin-arm64": { - "version": "4.53.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.53.3.tgz", - "integrity": "sha512-Nr7SlQeqIBpOV6BHHGZgYBuSdanCXuw09hon14MGOLGmXAFYjx1wNvquVPmpZnl0tLjg25dEdr4IQ6GgyToCUA==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.59.0.tgz", + "integrity": "sha512-W2Psnbh1J8ZJw0xKAd8zdNgF9HRLkdWwwdWqubSVk0pUuQkoHnv7rx4GiF9rT4t5DIZGAsConRE3AxCdJ4m8rg==", "cpu": [ "arm64" ], "dev": true, - "license": "MIT", "optional": true, "os": [ "darwin" ] }, "node_modules/@rollup/rollup-darwin-x64": { - "version": "4.53.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.53.3.tgz", - "integrity": "sha512-DZ8N4CSNfl965CmPktJ8oBnfYr3F8dTTNBQkRlffnUarJ2ohudQD17sZBa097J8xhQ26AwhHJ5mvUyQW8ddTsQ==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.59.0.tgz", + "integrity": "sha512-ZW2KkwlS4lwTv7ZVsYDiARfFCnSGhzYPdiOU4IM2fDbL+QGlyAbjgSFuqNRbSthybLbIJ915UtZBtmuLrQAT/w==", "cpu": [ "x64" ], "dev": true, - "license": "MIT", "optional": true, "os": [ "darwin" ] }, "node_modules/@rollup/rollup-freebsd-arm64": { - "version": "4.53.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.53.3.tgz", - "integrity": "sha512-yMTrCrK92aGyi7GuDNtGn2sNW+Gdb4vErx4t3Gv/Tr+1zRb8ax4z8GWVRfr3Jw8zJWvpGHNpss3vVlbF58DZ4w==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.59.0.tgz", + "integrity": "sha512-EsKaJ5ytAu9jI3lonzn3BgG8iRBjV4LxZexygcQbpiU0wU0ATxhNVEpXKfUa0pS05gTcSDMKpn3Sx+QB9RlTTA==", "cpu": [ "arm64" ], "dev": true, - "license": "MIT", "optional": true, "os": [ "freebsd" ] }, "node_modules/@rollup/rollup-freebsd-x64": { - "version": "4.53.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.53.3.tgz", - "integrity": "sha512-lMfF8X7QhdQzseM6XaX0vbno2m3hlyZFhwcndRMw8fbAGUGL3WFMBdK0hbUBIUYcEcMhVLr1SIamDeuLBnXS+Q==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.59.0.tgz", + "integrity": "sha512-d3DuZi2KzTMjImrxoHIAODUZYoUUMsuUiY4SRRcJy6NJoZ6iIqWnJu9IScV9jXysyGMVuW+KNzZvBLOcpdl3Vg==", "cpu": [ "x64" ], "dev": true, - "license": "MIT", "optional": true, "os": [ "freebsd" ] }, "node_modules/@rollup/rollup-linux-arm-gnueabihf": { - "version": "4.53.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.53.3.tgz", - "integrity": "sha512-k9oD15soC/Ln6d2Wv/JOFPzZXIAIFLp6B+i14KhxAfnq76ajt0EhYc5YPeX6W1xJkAdItcVT+JhKl1QZh44/qw==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.59.0.tgz", + "integrity": "sha512-t4ONHboXi/3E0rT6OZl1pKbl2Vgxf9vJfWgmUoCEVQVxhW6Cw/c8I6hbbu7DAvgp82RKiH7TpLwxnJeKv2pbsw==", "cpu": [ "arm" ], "dev": true, - "license": "MIT", "optional": true, "os": [ "linux" ] }, "node_modules/@rollup/rollup-linux-arm-musleabihf": { - "version": "4.53.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.53.3.tgz", - "integrity": "sha512-vTNlKq+N6CK/8UktsrFuc+/7NlEYVxgaEgRXVUVK258Z5ymho29skzW1sutgYjqNnquGwVUObAaxae8rZ6YMhg==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.59.0.tgz", + "integrity": "sha512-CikFT7aYPA2ufMD086cVORBYGHffBo4K8MQ4uPS/ZnY54GKj36i196u8U+aDVT2LX4eSMbyHtyOh7D7Zvk2VvA==", "cpu": [ "arm" ], "dev": true, - "license": "MIT", "optional": true, "os": [ "linux" ] }, "node_modules/@rollup/rollup-linux-arm64-gnu": { - "version": "4.53.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.53.3.tgz", - "integrity": "sha512-RGrFLWgMhSxRs/EWJMIFM1O5Mzuz3Xy3/mnxJp/5cVhZ2XoCAxJnmNsEyeMJtpK+wu0FJFWz+QF4mjCA7AUQ3w==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.59.0.tgz", + "integrity": "sha512-jYgUGk5aLd1nUb1CtQ8E+t5JhLc9x5WdBKew9ZgAXg7DBk0ZHErLHdXM24rfX+bKrFe+Xp5YuJo54I5HFjGDAA==", "cpu": [ "arm64" ], "dev": true, - "license": "MIT", "optional": true, "os": [ "linux" ] }, "node_modules/@rollup/rollup-linux-arm64-musl": { - "version": "4.53.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.53.3.tgz", - "integrity": "sha512-kASyvfBEWYPEwe0Qv4nfu6pNkITLTb32p4yTgzFCocHnJLAHs+9LjUu9ONIhvfT/5lv4YS5muBHyuV84epBo/A==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.59.0.tgz", + "integrity": "sha512-peZRVEdnFWZ5Bh2KeumKG9ty7aCXzzEsHShOZEFiCQlDEepP1dpUl/SrUNXNg13UmZl+gzVDPsiCwnV1uI0RUA==", "cpu": [ "arm64" ], "dev": true, - "license": "MIT", "optional": true, "os": [ "linux" ] }, "node_modules/@rollup/rollup-linux-loong64-gnu": { - "version": "4.53.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.53.3.tgz", - "integrity": "sha512-JiuKcp2teLJwQ7vkJ95EwESWkNRFJD7TQgYmCnrPtlu50b4XvT5MOmurWNrCj3IFdyjBQ5p9vnrX4JM6I8OE7g==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.59.0.tgz", + "integrity": "sha512-gbUSW/97f7+r4gHy3Jlup8zDG190AuodsWnNiXErp9mT90iCy9NKKU0Xwx5k8VlRAIV2uU9CsMnEFg/xXaOfXg==", + "cpu": [ + "loong64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-musl": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.59.0.tgz", + "integrity": "sha512-yTRONe79E+o0FWFijasoTjtzG9EBedFXJMl888NBEDCDV9I2wGbFFfJQQe63OijbFCUZqxpHz1GzpbtSFikJ4Q==", "cpu": [ "loong64" ], "dev": true, - "license": "MIT", "optional": true, "os": [ "linux" ] }, "node_modules/@rollup/rollup-linux-ppc64-gnu": { - "version": "4.53.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.53.3.tgz", - "integrity": "sha512-EoGSa8nd6d3T7zLuqdojxC20oBfNT8nexBbB/rkxgKj5T5vhpAQKKnD+h3UkoMuTyXkP5jTjK/ccNRmQrPNDuw==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.59.0.tgz", + "integrity": "sha512-sw1o3tfyk12k3OEpRddF68a1unZ5VCN7zoTNtSn2KndUE+ea3m3ROOKRCZxEpmT9nsGnogpFP9x6mnLTCaoLkA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-musl": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.59.0.tgz", + "integrity": "sha512-+2kLtQ4xT3AiIxkzFVFXfsmlZiG5FXYW7ZyIIvGA7Bdeuh9Z0aN4hVyXS/G1E9bTP/vqszNIN/pUKCk/BTHsKA==", "cpu": [ "ppc64" ], "dev": true, - "license": "MIT", "optional": true, "os": [ "linux" ] }, "node_modules/@rollup/rollup-linux-riscv64-gnu": { - "version": "4.53.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.53.3.tgz", - "integrity": "sha512-4s+Wped2IHXHPnAEbIB0YWBv7SDohqxobiiPA1FIWZpX+w9o2i4LezzH/NkFUl8LRci/8udci6cLq+jJQlh+0g==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.59.0.tgz", + "integrity": "sha512-NDYMpsXYJJaj+I7UdwIuHHNxXZ/b/N2hR15NyH3m2qAtb/hHPA4g4SuuvrdxetTdndfj9b1WOmy73kcPRoERUg==", "cpu": [ "riscv64" ], "dev": true, - "license": "MIT", "optional": true, "os": [ "linux" ] }, "node_modules/@rollup/rollup-linux-riscv64-musl": { - "version": "4.53.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.53.3.tgz", - "integrity": "sha512-68k2g7+0vs2u9CxDt5ktXTngsxOQkSEV/xBbwlqYcUrAVh6P9EgMZvFsnHy4SEiUl46Xf0IObWVbMvPrr2gw8A==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.59.0.tgz", + "integrity": "sha512-nLckB8WOqHIf1bhymk+oHxvM9D3tyPndZH8i8+35p/1YiVoVswPid2yLzgX7ZJP0KQvnkhM4H6QZ5m0LzbyIAg==", "cpu": [ "riscv64" ], "dev": true, - "license": "MIT", "optional": true, "os": [ "linux" ] }, "node_modules/@rollup/rollup-linux-s390x-gnu": { - "version": "4.53.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.53.3.tgz", - "integrity": "sha512-VYsFMpULAz87ZW6BVYw3I6sWesGpsP9OPcyKe8ofdg9LHxSbRMd7zrVrr5xi/3kMZtpWL/wC+UIJWJYVX5uTKg==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.59.0.tgz", + "integrity": "sha512-oF87Ie3uAIvORFBpwnCvUzdeYUqi2wY6jRFWJAy1qus/udHFYIkplYRW+wo+GRUP4sKzYdmE1Y3+rY5Gc4ZO+w==", "cpu": [ "s390x" ], "dev": true, - "license": "MIT", "optional": true, "os": [ "linux" ] }, "node_modules/@rollup/rollup-linux-x64-gnu": { - "version": "4.53.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.53.3.tgz", - "integrity": "sha512-3EhFi1FU6YL8HTUJZ51imGJWEX//ajQPfqWLI3BQq4TlvHy4X0MOr5q3D2Zof/ka0d5FNdPwZXm3Yyib/UEd+w==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.59.0.tgz", + "integrity": "sha512-3AHmtQq/ppNuUspKAlvA8HtLybkDflkMuLK4DPo77DfthRb71V84/c4MlWJXixZz4uruIH4uaa07IqoAkG64fg==", "cpu": [ "x64" ], "dev": true, - "license": "MIT", "optional": true, "os": [ "linux" ] }, "node_modules/@rollup/rollup-linux-x64-musl": { - "version": "4.53.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.53.3.tgz", - "integrity": "sha512-eoROhjcc6HbZCJr+tvVT8X4fW3/5g/WkGvvmwz/88sDtSJzO7r/blvoBDgISDiCjDRZmHpwud7h+6Q9JxFwq1Q==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.59.0.tgz", + "integrity": "sha512-2UdiwS/9cTAx7qIUZB/fWtToJwvt0Vbo0zmnYt7ED35KPg13Q0ym1g442THLC7VyI6JfYTP4PiSOWyoMdV2/xg==", "cpu": [ "x64" ], "dev": true, - "license": "MIT", "optional": true, "os": [ "linux" ] }, + "node_modules/@rollup/rollup-openbsd-x64": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.59.0.tgz", + "integrity": "sha512-M3bLRAVk6GOwFlPTIxVBSYKUaqfLrn8l0psKinkCFxl4lQvOSz8ZrKDz2gxcBwHFpci0B6rttydI4IpS4IS/jQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "openbsd" + ] + }, "node_modules/@rollup/rollup-openharmony-arm64": { - "version": "4.53.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.53.3.tgz", - "integrity": "sha512-OueLAWgrNSPGAdUdIjSWXw+u/02BRTcnfw9PN41D2vq/JSEPnJnVuBgw18VkN8wcd4fjUs+jFHVM4t9+kBSNLw==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.59.0.tgz", + "integrity": "sha512-tt9KBJqaqp5i5HUZzoafHZX8b5Q2Fe7UjYERADll83O4fGqJ49O1FsL6LpdzVFQcpwvnyd0i+K/VSwu/o/nWlA==", "cpu": [ "arm64" ], "dev": true, - "license": "MIT", "optional": true, "os": [ "openharmony" ] }, "node_modules/@rollup/rollup-win32-arm64-msvc": { - "version": "4.53.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.53.3.tgz", - "integrity": "sha512-GOFuKpsxR/whszbF/bzydebLiXIHSgsEUp6M0JI8dWvi+fFa1TD6YQa4aSZHtpmh2/uAlj/Dy+nmby3TJ3pkTw==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.59.0.tgz", + "integrity": "sha512-V5B6mG7OrGTwnxaNUzZTDTjDS7F75PO1ae6MJYdiMu60sq0CqN5CVeVsbhPxalupvTX8gXVSU9gq+Rx1/hvu6A==", "cpu": [ "arm64" ], "dev": true, - "license": "MIT", "optional": true, "os": [ "win32" ] }, "node_modules/@rollup/rollup-win32-ia32-msvc": { - "version": "4.53.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.53.3.tgz", - "integrity": "sha512-iah+THLcBJdpfZ1TstDFbKNznlzoxa8fmnFYK4V67HvmuNYkVdAywJSoteUszvBQ9/HqN2+9AZghbajMsFT+oA==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.59.0.tgz", + "integrity": "sha512-UKFMHPuM9R0iBegwzKF4y0C4J9u8C6MEJgFuXTBerMk7EJ92GFVFYBfOZaSGLu6COf7FxpQNqhNS4c4icUPqxA==", "cpu": [ "ia32" ], "dev": true, - "license": "MIT", "optional": true, "os": [ "win32" ] }, "node_modules/@rollup/rollup-win32-x64-gnu": { - "version": "4.53.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.53.3.tgz", - "integrity": "sha512-J9QDiOIZlZLdcot5NXEepDkstocktoVjkaKUtqzgzpt2yWjGlbYiKyp05rWwk4nypbYUNoFAztEgixoLaSETkg==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.59.0.tgz", + "integrity": "sha512-laBkYlSS1n2L8fSo1thDNGrCTQMmxjYY5G0WFWjFFYZkKPjsMBsgJfGf4TLxXrF6RyhI60L8TMOjBMvXiTcxeA==", "cpu": [ "x64" ], "dev": true, - "license": "MIT", "optional": true, "os": [ "win32" ] }, "node_modules/@rollup/rollup-win32-x64-msvc": { - "version": "4.53.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.53.3.tgz", - "integrity": "sha512-UhTd8u31dXadv0MopwGgNOBpUVROFKWVQgAg5N1ESyCz8AuBcMqm4AuTjrwgQKGDfoFuz02EuMRHQIw/frmYKQ==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.59.0.tgz", + "integrity": "sha512-2HRCml6OztYXyJXAvdDXPKcawukWY2GpR5/nxKp4iBgiO3wcoEGkAaqctIbZcNB6KlUQBIqt8VYkNSj2397EfA==", "cpu": [ "x64" ], "dev": true, - "license": "MIT", "optional": true, "os": [ "win32" @@ -2135,13 +2154,12 @@ } }, "node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch": { - "version": "9.0.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", - "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "version": "9.0.9", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.9.tgz", + "integrity": "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==", "dev": true, - "license": "ISC", "dependencies": { - "brace-expansion": "^2.0.1" + "brace-expansion": "^2.0.2" }, "engines": { "node": ">=16 || 14 >=14.17" @@ -2278,6 +2296,46 @@ "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, + "node_modules/ag-psd": { + "version": "30.1.0", + "resolved": "https://registry.npmjs.org/ag-psd/-/ag-psd-30.1.0.tgz", + "integrity": "sha512-1ce6o84aC+oVyl83A35HHUniGjwA3piHmGem3J2odBOFRq5p7i4htaco94vePLfOcingZu4fsyospJLwPagkhg==", + "dependencies": { + "base64-js": "1.5.1", + "pako": "2.1.0" + } + }, + "node_modules/ag-psd-psdtool": { + "version": "1.1.10", + "resolved": "https://registry.npmjs.org/ag-psd-psdtool/-/ag-psd-psdtool-1.1.10.tgz", + "integrity": "sha512-UgyFPtgToJImsCHl9szHMxqYR0VfPZm3b7TIrbrIk7GrNLaAARqjQvTptGVRMsm1WgTDUTJZwd3kOk83wEWnfg==", + "hasInstallScript": true, + "dependencies": { + "ag-psd": "^30.1.0", + "ajv": "^8.18.0", + "es-toolkit": "^1.45.1" + } + }, + "node_modules/ag-psd-psdtool/node_modules/ajv": { + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.18.0.tgz", + "integrity": "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ag-psd-psdtool/node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==" + }, "node_modules/agent-base": { "version": "7.1.4", "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz", @@ -2288,11 +2346,10 @@ } }, "node_modules/ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "version": "6.14.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.14.0.tgz", + "integrity": "sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw==", "dev": true, - "license": "MIT", "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", @@ -2477,6 +2534,25 @@ "bare-path": "^3.0.0" } }, + "node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, "node_modules/baseline-browser-mapping": { "version": "2.9.2", "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.9.2.tgz", @@ -2488,10 +2564,9 @@ } }, "node_modules/basic-ftp": { - "version": "5.0.5", - "resolved": "https://registry.npmjs.org/basic-ftp/-/basic-ftp-5.0.5.tgz", - "integrity": "sha512-4Bcg1P8xhUuqcii/S0Z9wiHIrQVPMermM1any+MX5GeGD7faD3/msQUDGLol9wOcz4/jbg/WJnGqoJF6LiBdtg==", - "license": "MIT", + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/basic-ftp/-/basic-ftp-5.2.0.tgz", + "integrity": "sha512-VoMINM2rqJwJgfdHq6RiUudKt2BV+FY5ZFezP/ypmwayk68+NzzAQy4XXLlqsGD4MCzq3DrmNFD/uUmBJuGoXw==", "engines": { "node": ">=10.0.0" } @@ -3147,6 +3222,11 @@ "node": ">= 0.4" } }, + "node_modules/es-toolkit": { + "version": "1.45.1", + "resolved": "https://registry.npmjs.org/es-toolkit/-/es-toolkit-1.45.1.tgz", + "integrity": "sha512-/jhoOj/Fx+A+IIyDNOvO3TItGmlMKhtX8ISAHKE90c4b/k1tqaqEZ+uUqfpU8DMnW5cgNJv606zS55jGvza0Xw==" + }, "node_modules/es6-error": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/es6-error/-/es6-error-4.1.1.tgz", @@ -3477,7 +3557,6 @@ "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", - "dev": true, "license": "MIT" }, "node_modules/fast-fifo": { @@ -3500,6 +3579,21 @@ "dev": true, "license": "MIT" }, + "node_modules/fast-uri": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.0.tgz", + "integrity": "sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ] + }, "node_modules/fd-slicer": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.1.0.tgz", @@ -4364,11 +4458,10 @@ } }, "node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", "dev": true, - "license": "ISC", "dependencies": { "brace-expansion": "^1.1.7" }, @@ -4587,6 +4680,11 @@ "node": ">= 14" } }, + "node_modules/pako": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/pako/-/pako-2.1.0.tgz", + "integrity": "sha512-w+eufiZ1WuJYgPXbV/PO3NCMEc3xqylkKHzp8bxp1uW4qaSNQUkwmLLEc3kKsfz8lpV1F8Ht3U1Cm+9Srog2ug==" + }, "node_modules/parent-module": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", @@ -4897,6 +4995,14 @@ "node": ">=0.10.0" } }, + "node_modules/require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/resolve-alpn": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/resolve-alpn/-/resolve-alpn-1.2.1.tgz", @@ -4946,11 +5052,10 @@ } }, "node_modules/rollup": { - "version": "4.53.3", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.53.3.tgz", - "integrity": "sha512-w8GmOxZfBmKknvdXU1sdM9NHcoQejwF/4mNgj2JuEEdRaHwwF12K7e9eXn1nLZ07ad+du76mkVsyeb2rKGllsA==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.59.0.tgz", + "integrity": "sha512-2oMpl67a3zCH9H79LeMcbDhXW/UmWG/y2zuqnF2jQq5uq9TbM9TVyXvA4+t+ne2IIkBdrLpAaRQAvo7YI/Yyeg==", "dev": true, - "license": "MIT", "dependencies": { "@types/estree": "1.0.8" }, @@ -4962,28 +5067,31 @@ "npm": ">=8.0.0" }, "optionalDependencies": { - "@rollup/rollup-android-arm-eabi": "4.53.3", - "@rollup/rollup-android-arm64": "4.53.3", - "@rollup/rollup-darwin-arm64": "4.53.3", - "@rollup/rollup-darwin-x64": "4.53.3", - "@rollup/rollup-freebsd-arm64": "4.53.3", - "@rollup/rollup-freebsd-x64": "4.53.3", - "@rollup/rollup-linux-arm-gnueabihf": "4.53.3", - "@rollup/rollup-linux-arm-musleabihf": "4.53.3", - "@rollup/rollup-linux-arm64-gnu": "4.53.3", - "@rollup/rollup-linux-arm64-musl": "4.53.3", - "@rollup/rollup-linux-loong64-gnu": "4.53.3", - "@rollup/rollup-linux-ppc64-gnu": "4.53.3", - "@rollup/rollup-linux-riscv64-gnu": "4.53.3", - "@rollup/rollup-linux-riscv64-musl": "4.53.3", - "@rollup/rollup-linux-s390x-gnu": "4.53.3", - "@rollup/rollup-linux-x64-gnu": "4.53.3", - "@rollup/rollup-linux-x64-musl": "4.53.3", - "@rollup/rollup-openharmony-arm64": "4.53.3", - "@rollup/rollup-win32-arm64-msvc": "4.53.3", - "@rollup/rollup-win32-ia32-msvc": "4.53.3", - "@rollup/rollup-win32-x64-gnu": "4.53.3", - "@rollup/rollup-win32-x64-msvc": "4.53.3", + "@rollup/rollup-android-arm-eabi": "4.59.0", + "@rollup/rollup-android-arm64": "4.59.0", + "@rollup/rollup-darwin-arm64": "4.59.0", + "@rollup/rollup-darwin-x64": "4.59.0", + "@rollup/rollup-freebsd-arm64": "4.59.0", + "@rollup/rollup-freebsd-x64": "4.59.0", + "@rollup/rollup-linux-arm-gnueabihf": "4.59.0", + "@rollup/rollup-linux-arm-musleabihf": "4.59.0", + "@rollup/rollup-linux-arm64-gnu": "4.59.0", + "@rollup/rollup-linux-arm64-musl": "4.59.0", + "@rollup/rollup-linux-loong64-gnu": "4.59.0", + "@rollup/rollup-linux-loong64-musl": "4.59.0", + "@rollup/rollup-linux-ppc64-gnu": "4.59.0", + "@rollup/rollup-linux-ppc64-musl": "4.59.0", + "@rollup/rollup-linux-riscv64-gnu": "4.59.0", + "@rollup/rollup-linux-riscv64-musl": "4.59.0", + "@rollup/rollup-linux-s390x-gnu": "4.59.0", + "@rollup/rollup-linux-x64-gnu": "4.59.0", + "@rollup/rollup-linux-x64-musl": "4.59.0", + "@rollup/rollup-openbsd-x64": "4.59.0", + "@rollup/rollup-openharmony-arm64": "4.59.0", + "@rollup/rollup-win32-arm64-msvc": "4.59.0", + "@rollup/rollup-win32-ia32-msvc": "4.59.0", + "@rollup/rollup-win32-x64-gnu": "4.59.0", + "@rollup/rollup-win32-x64-msvc": "4.59.0", "fsevents": "~2.3.2" } }, From 2eb640b578712913882c8613e02ff553d5ee5744 Mon Sep 17 00:00:00 2001 From: aqua Date: Tue, 10 Mar 2026 22:22:36 +0900 Subject: [PATCH 19/21] fix: duplicated register --- package.json | 4 +--- src/lib/character/psd-character.tsx | 24 ++++++++++++++++++------ 2 files changed, 19 insertions(+), 9 deletions(-) diff --git a/package.json b/package.json index 666a8d8..f740a47 100644 --- a/package.json +++ b/package.json @@ -39,9 +39,7 @@ "react": "^19.2.0", "react-dom": "^19.2.0", "react-router-dom": "^6.28.0", - "three": "^0.166.1", - "ag-psd": "^30.1.0", - "ag-psd-psdtool": "^1.1.10" + "three": "^0.166.1" }, "devDependencies": { "@eslint/js": "^9.39.1", diff --git a/src/lib/character/psd-character.tsx b/src/lib/character/psd-character.tsx index c8a52e4..8d0185a 100644 --- a/src/lib/character/psd-character.tsx +++ b/src/lib/character/psd-character.tsx @@ -44,7 +44,7 @@ export const PsdCharacter = ({ useEffect(() => { fetchPsd(normalizePsdPath(psd)).then(p => setPsd(p)) setAst(parsePsdCharacter(children)) - }, []) + }, [psd]) // 毎フレーム実行 const frame = useCurrentFrame() @@ -149,7 +149,10 @@ const MotionSequenceRuntime = ({ variables, register }: MotionSequenceRuntimeProps) => { - const reg = useRef(register()) + const reg = useRef>(undefined) + if (!reg.current) { + reg.current = register() + } const {update, getter, unregister} = reg.current useEffect(() => { @@ -245,7 +248,10 @@ const BlockRuntime = ({ variables, register }: BlockRuntimeProps) => { - const reg = useRef(register()) + const reg = useRef>(undefined) + if (!reg.current) { + reg.current = register() + } const {update, getter: superGetter, unregister} = reg.current useEffect(() => { @@ -363,7 +369,10 @@ const DeclareAnimationRuntime = ({ const curVariables = {...variables, ...initializingVariables} - const reg = useRef(register()) + const reg = useRef>(undefined) + if (!reg.current) { + reg.current = register() + } const {update, getter: superGetter, unregister} = reg.current useEffect(() => { @@ -499,7 +508,10 @@ const MotionRuntime = ({ variables, register }: MotionRuntimeProps) => { - const reg = useRef(register()) + const reg = useRef>(undefined) + if (!reg.current) { + reg.current = register() + } const { update, getter, unregister } = reg.current useEffect(() => { @@ -511,7 +523,7 @@ const MotionRuntime = ({ useEffect(() => { update(ast.motion(variables, [localTime, globalTime])) - }, [localTime, globalTime]) + }, [localTime]) return null } From fcbae2aca6101bd09c1a7b7038a5543241654561 Mon Sep 17 00:00:00 2001 From: aqua Date: Thu, 12 Mar 2026 23:04:26 +0900 Subject: [PATCH 20/21] fix: add className --- src/lib/character/psd-character.tsx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/lib/character/psd-character.tsx b/src/lib/character/psd-character.tsx index 8d0185a..5468b5f 100644 --- a/src/lib/character/psd-character.tsx +++ b/src/lib/character/psd-character.tsx @@ -11,6 +11,7 @@ import { Clip, ClipSequence } from "../clip" type PsdCharacterProps = { psd: string + className?: string children: React.ReactNode } @@ -28,6 +29,7 @@ type OptionRegister = () => { export const PsdCharacter = ({ psd, + className, children }: PsdCharacterProps) => { const [myPsd, setPsd] = useState(undefined) @@ -98,7 +100,7 @@ export const PsdCharacter = ({ return ( <> - + {ast?.children.map((child, i) => { switch (child.type) { case PsdElm.MotionSequence: From a2358da7fdc5a439fce1a30be3717af54383d042 Mon Sep 17 00:00:00 2001 From: aqua Date: Fri, 13 Mar 2026 23:40:00 +0900 Subject: [PATCH 21/21] fix: add sound props --- src/lib/character/ast.ts | 5 +++++ src/lib/character/parser.tsx | 6 +++++- src/lib/character/psd-character-component.ts | 5 +++++ src/lib/character/psd-character.tsx | 4 ++++ 4 files changed, 19 insertions(+), 1 deletion(-) diff --git a/src/lib/character/ast.ts b/src/lib/character/ast.ts index 18940b6..10f2b6e 100644 --- a/src/lib/character/ast.ts +++ b/src/lib/character/ast.ts @@ -1,4 +1,5 @@ import type { Variable } from "../animation" +import type { Trim } from "../trim" export const PsdCharacterElement = { Character: "Character", @@ -76,7 +77,11 @@ export interface DeclareAnimationNode { export interface VoiceNode { type: typeof PsdCharacterElement.Voice voice: string + trim?: Trim + fadeInFrames?: number + fadeOutFrames?: number volume: undefined | number | ((variables: Record>, frames: number[]) => number) + showWaveform?: boolean children: VoiceChild[] } diff --git a/src/lib/character/parser.tsx b/src/lib/character/parser.tsx index ea86c47..b755e6a 100644 --- a/src/lib/character/parser.tsx +++ b/src/lib/character/parser.tsx @@ -257,12 +257,16 @@ function parseDeclareAnimationChildren( function parseVoice( self: AnyElement, ): VoiceNode { - const { voice, volume, children } = self.props + const { voice, trim, fadeInFrames, fadeOutFrames, volume, showWaveform, children } = self.props const body = parseVoiceChildren(children) return { type: PsdElm.Voice, voice, + trim, + fadeInFrames, + fadeOutFrames, volume: volume ?? undefined, + showWaveform, children: body, } } diff --git a/src/lib/character/psd-character-component.ts b/src/lib/character/psd-character-component.ts index 66edc38..3d4dc82 100644 --- a/src/lib/character/psd-character-component.ts +++ b/src/lib/character/psd-character-component.ts @@ -1,4 +1,5 @@ import type { Variable } from "../animation" +import type { Trim } from "../trim" import { defineDSL } from "./defineDSL" import { PsdCharacterElement } from "./ast" @@ -28,7 +29,11 @@ export const DeclareAnimation = defineDSL<{ // 音声を配置する(ファイルのみ) export const Voice = defineDSL<{ voice: string + trim?: Trim + fadeInFrames?: number + fadeOutFrames?: number volume?: number + showWaveform?: boolean children?: React.ReactNode }>(PsdCharacterElement.Voice) diff --git a/src/lib/character/psd-character.tsx b/src/lib/character/psd-character.tsx index 5468b5f..1ece8ab 100644 --- a/src/lib/character/psd-character.tsx +++ b/src/lib/character/psd-character.tsx @@ -494,7 +494,11 @@ const VoiceRuntime = ({ return ( ) }