@laplace.live/pixijs-live2d 0.3.0 → 0.5.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/dist/index.d.ts +34 -0
- package/dist/index.js +155 -9
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
package/dist/index.d.ts
CHANGED
|
@@ -3240,6 +3240,14 @@ export declare enum MotionPreloadStrategy {
|
|
|
3240
3240
|
/** No preload. */
|
|
3241
3241
|
NONE = "NONE"
|
|
3242
3242
|
}
|
|
3243
|
+
/**
|
|
3244
|
+
* What plays when nothing else does: a motion to loop, `'idle-group'` for a random motion of
|
|
3245
|
+
* the idle group (the default), or `null` to hold still.
|
|
3246
|
+
*/
|
|
3247
|
+
export type IdleMotion = {
|
|
3248
|
+
group: string;
|
|
3249
|
+
index: number;
|
|
3250
|
+
} | 'idle-group' | null;
|
|
3243
3251
|
/**
|
|
3244
3252
|
* Handles the motion playback for Live2D models.
|
|
3245
3253
|
* Responsible for loading, playing, and managing motion states and audio.
|
|
@@ -3322,6 +3330,17 @@ export declare abstract class MotionManager<Motion = unknown, MotionSpec = unkno
|
|
|
3322
3330
|
* @type {boolean}
|
|
3323
3331
|
*/
|
|
3324
3332
|
destroyed: boolean;
|
|
3333
|
+
/** The idle selection; see {@link setIdleMotion}. */
|
|
3334
|
+
idleMotion: IdleMotion;
|
|
3335
|
+
/**
|
|
3336
|
+
* Choose what plays when nothing else does. A running idle gives way at once and a loading one
|
|
3337
|
+
* is dropped; a higher-priority motion in flight hands over to the new selection when it ends.
|
|
3338
|
+
*/
|
|
3339
|
+
setIdleMotion(motion: IdleMotion): void;
|
|
3340
|
+
/** Stop the idle so the next update starts the selection. Cubism eases its parameters home too. */
|
|
3341
|
+
protected releaseIdle(): void;
|
|
3342
|
+
/** Start the idle selection at IDLE priority; a chosen motion loops regardless of its own flag. */
|
|
3343
|
+
protected startIdleMotion(): Promise<boolean>;
|
|
3325
3344
|
/**
|
|
3326
3345
|
* Reference to the parent InternalModel.
|
|
3327
3346
|
* @type {InternalModel}
|
|
@@ -3698,6 +3717,8 @@ export declare const VOLUME = 0.5;
|
|
|
3698
3717
|
* Manages all the sounds.
|
|
3699
3718
|
*/
|
|
3700
3719
|
export declare class SoundManager {
|
|
3720
|
+
/** Select the speaker used by current and future motion audio and speak() calls. */
|
|
3721
|
+
static setOutputDevice(deviceId: string): Promise<void>;
|
|
3701
3722
|
/**
|
|
3702
3723
|
* Audio elements playing or pending to play. Finished audios will be removed automatically.
|
|
3703
3724
|
*/
|
|
@@ -6915,6 +6936,10 @@ export declare class CubismMotionManager extends MotionManager<CubismMotion, Mot
|
|
|
6915
6936
|
expressionManager?: CubismExpressionManager;
|
|
6916
6937
|
eyeBlinkIds: string[];
|
|
6917
6938
|
lipSyncIds: string[];
|
|
6939
|
+
/** What each motion's curves write, read once when it is created. */
|
|
6940
|
+
private readonly meta;
|
|
6941
|
+
/** Eases the parameters a released idle animated back to rest; see {@link releaseIdle}. */
|
|
6942
|
+
private release;
|
|
6918
6943
|
constructor(parent: CubismInternalModel);
|
|
6919
6944
|
protected init(options?: MotionManagerOptions): void;
|
|
6920
6945
|
isFinished(): boolean;
|
|
@@ -6924,6 +6949,15 @@ export declare class CubismMotionManager extends MotionManager<CubismMotion, Mot
|
|
|
6924
6949
|
getMotionFile(definition: Motion): string;
|
|
6925
6950
|
protected getMotionName(definition: Motion): string;
|
|
6926
6951
|
protected getSoundFile(definition: Motion): string | undefined;
|
|
6952
|
+
/** Whether the playing motion animates the eyes itself, so the engine's auto-blink must yield to it. */
|
|
6953
|
+
currentMotionBlinks(): boolean;
|
|
6954
|
+
/** The parameters the playing motion writes, for a host driver handing them over to it; null when none plays. */
|
|
6955
|
+
currentMotionParameters(): ReadonlySet<string> | null;
|
|
6956
|
+
private currentMotion;
|
|
6957
|
+
private currentMeta;
|
|
6958
|
+
protected releaseIdle(): void;
|
|
6959
|
+
/** One frame of the ease-to-rest, ahead of the motions so a new idle's fade-in blends from it. */
|
|
6960
|
+
private updateRelease;
|
|
6927
6961
|
protected updateParameters(model: CubismModel, now: DOMHighResTimeStamp): boolean;
|
|
6928
6962
|
destroy(): void;
|
|
6929
6963
|
motionLastFrame(group: string, index: number, { expression }?: {
|
package/dist/index.js
CHANGED
|
@@ -3567,6 +3567,10 @@ var MotionState = class {
|
|
|
3567
3567
|
*/
|
|
3568
3568
|
start(motion, group, index, priority) {
|
|
3569
3569
|
if (priority === 1) {
|
|
3570
|
+
if (group !== this.reservedIdleGroup || index !== this.reservedIdleIndex) {
|
|
3571
|
+
logger.log(this.tag, "Cannot start idle motion because it is no longer reserved.", this.dump(group, index));
|
|
3572
|
+
return false;
|
|
3573
|
+
}
|
|
3570
3574
|
this.setReservedIdle(void 0, void 0);
|
|
3571
3575
|
if (this.currentPriority !== 0) {
|
|
3572
3576
|
logger.log(this.tag, "Cannot start idle motion because another motion is playing.", this.dump(group, index));
|
|
@@ -3663,17 +3667,33 @@ const VOLUME = .5;
|
|
|
3663
3667
|
const SOUND_ALIAS_PREFIX = "live2d-sound-";
|
|
3664
3668
|
let soundId = 0;
|
|
3665
3669
|
let pixiSoundPromise;
|
|
3666
|
-
const configuredLibraries = /* @__PURE__ */ new
|
|
3670
|
+
const configuredLibraries = /* @__PURE__ */ new Set();
|
|
3671
|
+
let outputDeviceId = "";
|
|
3672
|
+
let outputError = null;
|
|
3673
|
+
/** Device changes and first-use routing run in order, so a library never settles on a stale device. */
|
|
3674
|
+
let routing = Promise.resolve();
|
|
3675
|
+
function queueRouting(task) {
|
|
3676
|
+
const run = routing.then(task);
|
|
3677
|
+
routing = run.catch(() => void 0);
|
|
3678
|
+
return run;
|
|
3679
|
+
}
|
|
3680
|
+
async function routeContext(context, deviceId) {
|
|
3681
|
+
const routable = context;
|
|
3682
|
+
if (routable.setSinkId) await routable.setSinkId(deviceId);
|
|
3683
|
+
else if (deviceId) throw new Error("Audio output selection is not supported");
|
|
3684
|
+
}
|
|
3667
3685
|
function configureSoundLibrary(library) {
|
|
3668
|
-
|
|
3686
|
+
return queueRouting(async () => {
|
|
3687
|
+
if (configuredLibraries.has(library)) return library;
|
|
3669
3688
|
try {
|
|
3670
3689
|
library.disableAutoPause = true;
|
|
3671
3690
|
} catch (e) {
|
|
3672
3691
|
logger.warn(TAG$2, "Failed to disable @pixi/sound auto pause.", e);
|
|
3673
3692
|
}
|
|
3693
|
+
if (outputDeviceId) await routeContext(library.context.audioContext, outputDeviceId);
|
|
3674
3694
|
configuredLibraries.add(library);
|
|
3675
|
-
|
|
3676
|
-
|
|
3695
|
+
return library;
|
|
3696
|
+
});
|
|
3677
3697
|
}
|
|
3678
3698
|
function getGlobalSoundLibrary() {
|
|
3679
3699
|
return typeof PIXI !== "undefined" ? PIXI?.sound : void 0;
|
|
@@ -3681,11 +3701,12 @@ function getGlobalSoundLibrary() {
|
|
|
3681
3701
|
async function resolveSoundLibrary() {
|
|
3682
3702
|
const globalLibrary = getGlobalSoundLibrary();
|
|
3683
3703
|
if (globalLibrary) return configureSoundLibrary(globalLibrary);
|
|
3684
|
-
pixiSoundPromise ??= import("@pixi/sound").then(({ sound }) =>
|
|
3704
|
+
pixiSoundPromise ??= import("@pixi/sound").then(({ sound }) => sound).catch((e) => {
|
|
3685
3705
|
logger.warn(TAG$2, "@pixi/sound is not available. Load pixi-sound.js before using motion sounds, speak(), or lip sync.", e);
|
|
3686
3706
|
return null;
|
|
3687
3707
|
});
|
|
3688
|
-
|
|
3708
|
+
const library = await pixiSoundPromise;
|
|
3709
|
+
return library ? configureSoundLibrary(library) : null;
|
|
3689
3710
|
}
|
|
3690
3711
|
function removeSound(library, alias) {
|
|
3691
3712
|
try {
|
|
@@ -3710,6 +3731,25 @@ function getAudioBuffer(audio) {
|
|
|
3710
3731
|
* Manages all the sounds.
|
|
3711
3732
|
*/
|
|
3712
3733
|
var SoundManager = class SoundManager {
|
|
3734
|
+
/** Select the speaker used by current and future motion audio and speak() calls. */
|
|
3735
|
+
static setOutputDevice(deviceId) {
|
|
3736
|
+
return queueRouting(async () => {
|
|
3737
|
+
const changed = [];
|
|
3738
|
+
try {
|
|
3739
|
+
for (const library of configuredLibraries) {
|
|
3740
|
+
const context = library.context.audioContext;
|
|
3741
|
+
await routeContext(context, deviceId);
|
|
3742
|
+
changed.push(context);
|
|
3743
|
+
}
|
|
3744
|
+
outputDeviceId = deviceId;
|
|
3745
|
+
outputError = null;
|
|
3746
|
+
} catch (error) {
|
|
3747
|
+
outputError = error;
|
|
3748
|
+
await Promise.allSettled(changed.map((context) => routeContext(context, outputDeviceId)));
|
|
3749
|
+
throw error;
|
|
3750
|
+
}
|
|
3751
|
+
});
|
|
3752
|
+
}
|
|
3713
3753
|
/**
|
|
3714
3754
|
* Audio elements playing or pending to play. Finished audios will be removed automatically.
|
|
3715
3755
|
*/
|
|
@@ -3740,6 +3780,7 @@ var SoundManager = class SoundManager {
|
|
|
3740
3780
|
let library = null;
|
|
3741
3781
|
let alias;
|
|
3742
3782
|
try {
|
|
3783
|
+
if (outputError) throw outputError;
|
|
3743
3784
|
library = await resolveSoundLibrary();
|
|
3744
3785
|
if (!library) throw new Error("@pixi/sound is not available");
|
|
3745
3786
|
alias = `${SOUND_ALIAS_PREFIX}${soundId++}`;
|
|
@@ -3768,6 +3809,7 @@ var SoundManager = class SoundManager {
|
|
|
3768
3809
|
}
|
|
3769
3810
|
});
|
|
3770
3811
|
});
|
|
3812
|
+
if (outputError) throw outputError;
|
|
3771
3813
|
SoundManager.aliases.set(audio, {
|
|
3772
3814
|
alias: soundAlias,
|
|
3773
3815
|
library: soundLibrary
|
|
@@ -3787,6 +3829,10 @@ var SoundManager = class SoundManager {
|
|
|
3787
3829
|
* @param onFinish - Callback invoked when the playback has finished.
|
|
3788
3830
|
*/
|
|
3789
3831
|
static play(audio, onFinish) {
|
|
3832
|
+
if (outputError) {
|
|
3833
|
+
SoundManager.dispose(audio);
|
|
3834
|
+
return;
|
|
3835
|
+
}
|
|
3790
3836
|
audio.play({
|
|
3791
3837
|
singleInstance: true,
|
|
3792
3838
|
complete: () => {
|
|
@@ -3870,6 +3916,10 @@ let MotionPreloadStrategy = /* @__PURE__ */ function(MotionPreloadStrategy) {
|
|
|
3870
3916
|
MotionPreloadStrategy["NONE"] = "NONE";
|
|
3871
3917
|
return MotionPreloadStrategy;
|
|
3872
3918
|
}({});
|
|
3919
|
+
function sameIdleMotion(a, b) {
|
|
3920
|
+
if (typeof a !== "object" || typeof b !== "object" || a === null || b === null) return a === b;
|
|
3921
|
+
return a.group === b.group && a.index === b.index;
|
|
3922
|
+
}
|
|
3873
3923
|
/**
|
|
3874
3924
|
* Handles the motion playback for Live2D models.
|
|
3875
3925
|
* Responsible for loading, playing, and managing motion states and audio.
|
|
@@ -3924,6 +3974,29 @@ var MotionManager = class extends EventEmitter {
|
|
|
3924
3974
|
* @type {boolean}
|
|
3925
3975
|
*/
|
|
3926
3976
|
destroyed = false;
|
|
3977
|
+
/** The idle selection; see {@link setIdleMotion}. */
|
|
3978
|
+
idleMotion = "idle-group";
|
|
3979
|
+
/**
|
|
3980
|
+
* Choose what plays when nothing else does. A running idle gives way at once and a loading one
|
|
3981
|
+
* is dropped; a higher-priority motion in flight hands over to the new selection when it ends.
|
|
3982
|
+
*/
|
|
3983
|
+
setIdleMotion(motion) {
|
|
3984
|
+
if (sameIdleMotion(this.idleMotion, motion)) return;
|
|
3985
|
+
this.idleMotion = motion;
|
|
3986
|
+
this.state.setReservedIdle(void 0, void 0);
|
|
3987
|
+
if (this.state.currentPriority === 1) this.releaseIdle();
|
|
3988
|
+
}
|
|
3989
|
+
/** Stop the idle so the next update starts the selection. Cubism eases its parameters home too. */
|
|
3990
|
+
releaseIdle() {
|
|
3991
|
+
this.stopAllMotions();
|
|
3992
|
+
}
|
|
3993
|
+
/** Start the idle selection at IDLE priority; a chosen motion loops regardless of its own flag. */
|
|
3994
|
+
startIdleMotion() {
|
|
3995
|
+
const idle = this.idleMotion;
|
|
3996
|
+
if (idle === null) return Promise.resolve(false);
|
|
3997
|
+
if (idle === "idle-group") return this.startRandomMotion(this.groups.idle, 1);
|
|
3998
|
+
return this.startMotion(idle.group, idle.index, 1, { loop: true });
|
|
3999
|
+
}
|
|
3927
4000
|
/**
|
|
3928
4001
|
* Reference to the parent InternalModel.
|
|
3929
4002
|
* @type {InternalModel}
|
|
@@ -4222,7 +4295,7 @@ var MotionManager = class extends EventEmitter {
|
|
|
4222
4295
|
}
|
|
4223
4296
|
if (this.state.shouldOverrideExpression()) this.expressionManager?.restoreExpression();
|
|
4224
4297
|
this.state.complete();
|
|
4225
|
-
if (this.state.shouldRequestIdleMotion()) this.
|
|
4298
|
+
if (this.idleMotion !== null && this.state.shouldRequestIdleMotion()) this.startIdleMotion();
|
|
4226
4299
|
}
|
|
4227
4300
|
return this.updateParameters(model, now);
|
|
4228
4301
|
}
|
|
@@ -14651,6 +14724,10 @@ var CubismMotionManager = class extends MotionManager {
|
|
|
14651
14724
|
expressionManager;
|
|
14652
14725
|
eyeBlinkIds;
|
|
14653
14726
|
lipSyncIds = ["ParamMouthOpenY"];
|
|
14727
|
+
/** What each motion's curves write, read once when it is created. */
|
|
14728
|
+
meta = /* @__PURE__ */ new WeakMap();
|
|
14729
|
+
/** Eases the parameters a released idle animated back to rest; see {@link releaseIdle}. */
|
|
14730
|
+
release = null;
|
|
14654
14731
|
constructor(parent) {
|
|
14655
14732
|
super(parent);
|
|
14656
14733
|
this.definitions = parent.settings.motions ?? {};
|
|
@@ -14680,6 +14757,7 @@ var CubismMotionManager = class extends MotionManager {
|
|
|
14680
14757
|
});
|
|
14681
14758
|
motion._motionData.curves = filtered;
|
|
14682
14759
|
motion._motionData.curveCount = filtered.length;
|
|
14760
|
+
this.meta.set(motion, motionMeta(motion, this.eyeBlinkIds));
|
|
14683
14761
|
}
|
|
14684
14762
|
this.queueManager.stopAllMotions();
|
|
14685
14763
|
return this.queueManager.startMotion(motion, false);
|
|
@@ -14692,6 +14770,7 @@ var CubismMotionManager = class extends MotionManager {
|
|
|
14692
14770
|
const motion = CubismMotion.create(data, data.byteLength, void 0, void 0, shouldCheckMotionConsistency);
|
|
14693
14771
|
const json = new CubismMotionJson(data, data.byteLength);
|
|
14694
14772
|
motion.setLoop(json.isMotionLoop());
|
|
14773
|
+
this.meta.set(motion, motionMeta(motion, this.eyeBlinkIds));
|
|
14695
14774
|
const defaultFadingDuration = (group === this.groups.idle ? config.idleMotionFadingDuration : config.motionFadingDuration) / 1e3;
|
|
14696
14775
|
if (json.getMotionFadeInTime() === void 0) motion.setFadeInTime(definition.FadeInTime > 0 ? definition.FadeInTime : defaultFadingDuration);
|
|
14697
14776
|
if (json.getMotionFadeOutTime() === void 0) motion.setFadeOutTime(definition.FadeOutTime > 0 ? definition.FadeOutTime : defaultFadingDuration);
|
|
@@ -14707,7 +14786,57 @@ var CubismMotionManager = class extends MotionManager {
|
|
|
14707
14786
|
getSoundFile(definition) {
|
|
14708
14787
|
return definition.Sound;
|
|
14709
14788
|
}
|
|
14789
|
+
/** Whether the playing motion animates the eyes itself, so the engine's auto-blink must yield to it. */
|
|
14790
|
+
currentMotionBlinks() {
|
|
14791
|
+
return this.currentMeta()?.blinks ?? false;
|
|
14792
|
+
}
|
|
14793
|
+
/** The parameters the playing motion writes, for a host driver handing them over to it; null when none plays. */
|
|
14794
|
+
currentMotionParameters() {
|
|
14795
|
+
return this.currentMeta()?.animated ?? null;
|
|
14796
|
+
}
|
|
14797
|
+
currentMotion() {
|
|
14798
|
+
const { currentGroup, currentIndex } = this.state;
|
|
14799
|
+
if (currentGroup === void 0 || currentIndex === void 0) return null;
|
|
14800
|
+
return this.motionGroups[currentGroup]?.[currentIndex] ?? null;
|
|
14801
|
+
}
|
|
14802
|
+
currentMeta() {
|
|
14803
|
+
const motion = this.currentMotion();
|
|
14804
|
+
return motion ? this.meta.get(motion) ?? null : null;
|
|
14805
|
+
}
|
|
14806
|
+
releaseIdle() {
|
|
14807
|
+
const motion = this.currentMotion();
|
|
14808
|
+
super.releaseIdle();
|
|
14809
|
+
if (motion) this.release = {
|
|
14810
|
+
ids: this.meta.get(motion)?.parameterIds ?? [],
|
|
14811
|
+
seconds: motion.getFadeOutTime(),
|
|
14812
|
+
ramp: null
|
|
14813
|
+
};
|
|
14814
|
+
}
|
|
14815
|
+
/** One frame of the ease-to-rest, ahead of the motions so a new idle's fade-in blends from it. */
|
|
14816
|
+
updateRelease(model, now) {
|
|
14817
|
+
const r = this.release;
|
|
14818
|
+
if (!r) return;
|
|
14819
|
+
if (r.ramp === null) {
|
|
14820
|
+
const count = model.getParameterCount();
|
|
14821
|
+
const indices = r.ids.map((id) => model.getParameterIndex(id)).filter((index) => index < count);
|
|
14822
|
+
r.ramp = {
|
|
14823
|
+
indices,
|
|
14824
|
+
from: indices.map((index) => model.getParameterValueByIndex(index)),
|
|
14825
|
+
startedAt: now
|
|
14826
|
+
};
|
|
14827
|
+
}
|
|
14828
|
+
const { indices, from, startedAt } = r.ramp;
|
|
14829
|
+
const t = r.seconds > 0 ? Math.min(1, (now - startedAt) / r.seconds) : 1;
|
|
14830
|
+
const w = CubismMath.getEasingSine(t);
|
|
14831
|
+
for (let i = 0; i < indices.length; i++) {
|
|
14832
|
+
const index = indices[i];
|
|
14833
|
+
const start = from[i];
|
|
14834
|
+
model.setParameterValueByIndex(index, start + (model.getParameterDefaultValue(index) - start) * w);
|
|
14835
|
+
}
|
|
14836
|
+
if (t >= 1) this.release = null;
|
|
14837
|
+
}
|
|
14710
14838
|
updateParameters(model, now) {
|
|
14839
|
+
this.updateRelease(model, now);
|
|
14711
14840
|
return this.queueManager.doUpdateMotion(model, now);
|
|
14712
14841
|
}
|
|
14713
14842
|
destroy() {
|
|
@@ -14728,6 +14857,23 @@ var CubismMotionManager = class extends MotionManager {
|
|
|
14728
14857
|
return ids.map((id) => idManager.getId(id));
|
|
14729
14858
|
}
|
|
14730
14859
|
};
|
|
14860
|
+
function motionMeta(motion, eyeBlinkIds) {
|
|
14861
|
+
const parameterIds = [];
|
|
14862
|
+
const animated = /* @__PURE__ */ new Set();
|
|
14863
|
+
let blinks = false;
|
|
14864
|
+
for (const curve of motion._motionData.curves) {
|
|
14865
|
+
const id = getCurveId$1(curve);
|
|
14866
|
+
if (curve.type === 1) {
|
|
14867
|
+
parameterIds.push(curve.id);
|
|
14868
|
+
if (id !== void 0) animated.add(id);
|
|
14869
|
+
} else if (curve.type === 0 && id === "EyeBlink") blinks = true;
|
|
14870
|
+
}
|
|
14871
|
+
return {
|
|
14872
|
+
parameterIds,
|
|
14873
|
+
animated,
|
|
14874
|
+
blinks: blinks || eyeBlinkIds.some((id) => animated.has(id))
|
|
14875
|
+
};
|
|
14876
|
+
}
|
|
14731
14877
|
function getCurveId$1(curve) {
|
|
14732
14878
|
if (!curve || typeof curve !== "object") return;
|
|
14733
14879
|
const id = curve.id;
|
|
@@ -15280,10 +15426,10 @@ var CubismInternalModel = class extends InternalModel {
|
|
|
15280
15426
|
dt /= 1e3;
|
|
15281
15427
|
now /= 1e3;
|
|
15282
15428
|
const model = this.coreModel;
|
|
15283
|
-
|
|
15429
|
+
this.updateMotions(model, now);
|
|
15284
15430
|
model.saveParameters();
|
|
15285
15431
|
this.motionManager.expressionManager?.update(model, now);
|
|
15286
|
-
if (!
|
|
15432
|
+
if (!this.motionManager.currentMotionBlinks()) this.eyeBlink?.updateParameters?.(model, dt);
|
|
15287
15433
|
this.updateFocus();
|
|
15288
15434
|
this.updateNaturalMovements(dt * 1e3, now * 1e3);
|
|
15289
15435
|
if (this.lipSync && this.motionManager.currentAudio) {
|