mesh-avatar 0.2.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -2,6 +2,17 @@
2
2
 
3
3
  A WebGL runtime for 2D mesh avatars created with [Mesh Avatar Studio](https://github.com/shinshin86/mesh-avatar-studio). Load a `.mavatar` archive or an avatar folder into a canvas, then control its motion, voice level and expressions.
4
4
 
5
+ For React and Next.js, use [mesh-avatar-react](https://github.com/shinshin86/mesh-avatar-studio/tree/main/packages/react).
6
+
7
+ <p align="center">
8
+ <img src="https://raw.githubusercontent.com/shinshin86/mesh-avatar-studio/main/docs/images/face-tracking.gif" width="400" alt="A Mesh Avatar Studio avatar following a face through a webcam">
9
+ </p>
10
+
11
+ To move an avatar with your own face and voice without installing anything, open the
12
+ [Player](https://shinshin86.github.io/mesh-avatar-studio/play/) in Chrome or Edge.
13
+
14
+ To embed an avatar with HTML, use [mesh-avatar-element](https://github.com/shinshin86/mesh-avatar-studio/tree/main/packages/element). It provides a `<mesh-avatar>` custom element and a standalone CDN script.
15
+
5
16
  ## Install
6
17
 
7
18
  ```sh
@@ -77,6 +88,38 @@ Emotion tags are `happy`, `sad`, `angry`, `surprised`, `relaxed` and `neutral` (
77
88
 
78
89
  For custom loading or tooling, the package also exports `createMeshAvatar`, `openAvatar`, `packAvatar`, `unpackAvatar`, `validateAvatarFiles`, `parseManifest`, `parseRig`, `validateRig` and lighting settings. See the [avatar format reference](https://github.com/shinshin86/mesh-avatar-studio/blob/main/docs/format.md) for archive contents, validation and compatibility rules.
79
90
 
91
+ ## Mouth movement
92
+
93
+ Three optional settings control audio loudness lip sync and kana previews. `granularity`
94
+ changes how readily the mouth separates syllables, `openness` changes the opening amount,
95
+ and `closeSpeed` changes how quickly it closes. Each accepts a finite value from 0 to 1;
96
+ 0.5 preserves the existing behavior.
97
+
98
+ The Studio's five styles use these values:
99
+
100
+ | Style | `granularity` | `openness` | `closeSpeed` |
101
+ | --- | --- | --- | --- |
102
+ | Relaxed | 0 | 0.5 | 0 |
103
+ | Standard | 0.5 | 0.5 | 0.5 |
104
+ | Snappy | 1 | 0.5 | 1 |
105
+ | Subtle | 0.5 | 0 | 0.5 |
106
+ | Expressive | 0.5 | 1 | 0.5 |
107
+
108
+ If your app already supplies a rig and built assets, pass `lipSync` when creating the avatar:
109
+
110
+ ```ts
111
+ import { createMeshAvatar, type LipSyncSettings } from 'mesh-avatar';
112
+
113
+ const lipSync: LipSyncSettings = { granularity: 0, openness: 0.5, closeSpeed: 0 };
114
+ const avatar = await createMeshAvatar(canvas, { rig, assets, lipSync });
115
+ avatar.setLipSync({ openness: 0.75 }); // Other settings keep their current values.
116
+ ```
117
+
118
+ Avatars saved or exported in Studio carry these settings in `avatar.json.lipSync`.
119
+ `loadMeshAvatar` applies them automatically; explicit `options.lipSync` fields override the
120
+ saved values. `setLipSync` is a partial update and works during playback. The optional
121
+ `preset` field records the editor selection only; playback uses the three numbers.
122
+
80
123
  ## License
81
124
 
82
125
  The runtime is MIT licensed; see [LICENSE](./LICENSE). The browser bundle includes [fflate](https://github.com/101arrowz/fflate), with its MIT notice retained in the bundle.
@@ -10,6 +10,8 @@ export function createMeshAvatarImpl(canvas: HTMLCanvasElement, options: import(
10
10
  getParameters(): {};
11
11
  /** 0..1 loudness of the voice being played (e.g. normalised RMS). */
12
12
  setVoiceLevel(v: any): void;
13
+ /** Partial mouth-movement settings; omitted fields retain their current values. */
14
+ setLipSync(settings: any): void;
13
15
  /** true while TTS audio is playing: idle motions pause and the head nods along. */
14
16
  setSpeaking(on: any): void;
15
17
  /** AITuber OnAir emotion tag: happy / sad / angry / surprised / relaxed / neutral (or null). */
@@ -1,6 +1,7 @@
1
1
  import type { LightingSettings } from '../lighting/settings.js';
2
2
  import type { Rig } from '../rig/types.js';
3
3
  import { OVERLAY_EXPRESSIONS } from './expression-overlay.js';
4
+ import type { LipSyncSettings } from './lip-sync.js';
4
5
  export type LiveExpression = keyof typeof OVERLAY_EXPRESSIONS;
5
6
  export declare const LIVE_EXPRESSIONS: LiveExpression[];
6
7
  export interface MeshAvatarOptions {
@@ -12,6 +13,7 @@ export interface MeshAvatarOptions {
12
13
  padSide?: number;
13
14
  fit?: 'contain' | 'cover';
14
15
  preserveMouthForm?: boolean;
16
+ lipSync?: LipSyncSettings;
15
17
  }
16
18
  export interface MeshAvatar {
17
19
  readonly motions: {
@@ -28,6 +30,8 @@ export interface MeshAvatar {
28
30
  setParameters(parameters: Record<string, number>, weight?: number): void;
29
31
  getParameters(): Record<string, number>;
30
32
  setVoiceLevel(value: number): void;
33
+ /** Partial mouth-movement settings; omitted fields retain their current values. */
34
+ setLipSync(settings: LipSyncSettings): void;
31
35
  setSpeaking(on: boolean): void;
32
36
  setEmotion(tag: string | null, options?: {
33
37
  playMotion?: boolean;
@@ -7,7 +7,7 @@ export function kanaToMoras(text: any, mora?: number): {
7
7
  onset: string | null;
8
8
  }[];
9
9
  /** Mouth target at time t (s) of a mora timeline, or null when it is over. */
10
- export function sampleMoras(moras: any, t: any): {
10
+ export function sampleMoras(moras: any, t: any, granularity?: number): {
11
11
  open: number;
12
12
  form: any;
13
13
  vowel: any;
@@ -0,0 +1,21 @@
1
+ export interface LipSyncSettings {
2
+ granularity?: number;
3
+ openness?: number;
4
+ closeSpeed?: number;
5
+ /** Editor selection only; playback uses the numeric settings. */
6
+ preset?: 'calm' | 'default' | 'lively' | 'subtle' | 'big' | 'custom';
7
+ }
8
+ type ResolvedLipSync = Required<Pick<LipSyncSettings, 'granularity' | 'openness' | 'closeSpeed'>>;
9
+ export declare function parseLipSyncSettings(input: unknown): LipSyncSettings;
10
+ export declare function resolveLipSync(input?: LipSyncSettings, base?: ResolvedLipSync): ResolvedLipSync;
11
+ export declare function lipMotionOptions(settings: ResolvedLipSync): {
12
+ arm: number;
13
+ trigger: number;
14
+ relax: number;
15
+ range: number;
16
+ risePower: number;
17
+ openness: number;
18
+ audioRelease: number;
19
+ kanaRelease: number;
20
+ };
21
+ export {};
@@ -192,6 +192,17 @@ export class Motion {
192
192
  mouth: number;
193
193
  vowel: string;
194
194
  vowelForm: number;
195
+ lipSync: Required<Pick<import("./lip-sync.js").LipSyncSettings, "granularity" | "openness" | "closeSpeed">>;
196
+ lipMotion: {
197
+ arm: number;
198
+ trigger: number;
199
+ relax: number;
200
+ range: number;
201
+ risePower: number;
202
+ openness: number;
203
+ audioRelease: number;
204
+ kanaRelease: number;
205
+ };
195
206
  syllableArmed: boolean;
196
207
  voicePeak: number;
197
208
  voiceTrough: number;
@@ -221,6 +232,7 @@ export class Motion {
221
232
  setExpression(k: any): void;
222
233
  hasMotion(id: any): boolean;
223
234
  setVoiceLevel(v: any): void;
235
+ setLipSync(settings: any): void;
224
236
  setSpeaking(on: any): void;
225
237
  /** `playMotion: false` changes only the face (a caller that picks motions itself). */
226
238
  setEmotion(tag: any, { playMotion }?: {
@@ -1,3 +1,4 @@
1
+ import { type LipSyncSettings } from '../engine/lip-sync.js';
1
2
  export interface AvatarManifest {
2
3
  format: 'mesh-avatar';
3
4
  version: 1;
@@ -9,6 +10,7 @@ export interface AvatarManifest {
9
10
  studio?: {
10
11
  version: string;
11
12
  };
13
+ lipSync?: LipSyncSettings;
12
14
  }
13
15
  export declare function parseManifest(input: unknown): AvatarManifest;
14
16
  export declare function defaultManifest(name: string): AvatarManifest;
@@ -1,6 +1,8 @@
1
+ import type { LipSyncSettings } from '../engine/lip-sync.js';
1
2
  type Files = Record<string, Uint8Array>;
2
- export declare function packProjectAvatar(read: (paths: Set<string>) => Promise<Files>, { name, includeSource }: {
3
+ export declare function packProjectAvatar(read: (paths: Set<string>) => Promise<Files>, { name, includeSource, lipSync }: {
3
4
  name: string;
4
5
  includeSource?: boolean;
6
+ lipSync?: LipSyncSettings;
5
7
  }): Promise<Uint8Array>;
6
8
  export {};
package/dist/index.d.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  export { createMeshAvatar, LIVE_EXPRESSIONS } from './engine/index.js';
2
2
  export type { MeshAvatar, MeshAvatarOptions, LiveExpression } from './engine/index.js';
3
+ export type { LipSyncSettings } from './engine/lip-sync.js';
3
4
  export type { Rig, Eye, Strand, Accessory, Hand, Ellipse, Point, Band } from './rig/types.js';
4
5
  export { validateRig, parseRig } from './rig/validate.js';
5
6
  export { PARAMS } from './engine/rig.js';