@omote/core 0.9.3 → 0.9.4
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.mts +10 -2
- package/dist/index.d.ts +10 -2
- package/dist/index.js +111 -101
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +111 -101
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/dist/index.mjs
CHANGED
|
@@ -608,12 +608,85 @@ var AudioChunkCoalescer = class {
|
|
|
608
608
|
}
|
|
609
609
|
};
|
|
610
610
|
|
|
611
|
+
// src/inference/BlendshapeSmoother.ts
|
|
612
|
+
var NUM_BLENDSHAPES = 52;
|
|
613
|
+
var BlendshapeSmoother = class {
|
|
614
|
+
constructor(config) {
|
|
615
|
+
/** Whether any target has been set */
|
|
616
|
+
this._hasTarget = false;
|
|
617
|
+
this.halflife = config?.halflife ?? 0.06;
|
|
618
|
+
this.values = new Float32Array(NUM_BLENDSHAPES);
|
|
619
|
+
this.velocities = new Float32Array(NUM_BLENDSHAPES);
|
|
620
|
+
this.targets = new Float32Array(NUM_BLENDSHAPES);
|
|
621
|
+
}
|
|
622
|
+
/** Whether a target frame has been set (false until first setTarget call) */
|
|
623
|
+
get hasTarget() {
|
|
624
|
+
return this._hasTarget;
|
|
625
|
+
}
|
|
626
|
+
/**
|
|
627
|
+
* Set new target frame from inference output.
|
|
628
|
+
* Springs will converge toward these values on subsequent update() calls.
|
|
629
|
+
*/
|
|
630
|
+
setTarget(frame) {
|
|
631
|
+
this.targets.set(frame);
|
|
632
|
+
this._hasTarget = true;
|
|
633
|
+
}
|
|
634
|
+
/**
|
|
635
|
+
* Advance all 52 springs by `dt` seconds and return the smoothed frame.
|
|
636
|
+
*
|
|
637
|
+
* Call this every render frame (e.g., inside requestAnimationFrame).
|
|
638
|
+
* Returns the internal values buffer — do NOT mutate the returned array.
|
|
639
|
+
*
|
|
640
|
+
* @param dt - Time step in seconds (e.g., 1/60 for 60fps)
|
|
641
|
+
* @returns Smoothed blendshape values (Float32Array of 52)
|
|
642
|
+
*/
|
|
643
|
+
update(dt) {
|
|
644
|
+
if (!this._hasTarget) {
|
|
645
|
+
return this.values;
|
|
646
|
+
}
|
|
647
|
+
if (this.halflife <= 0) {
|
|
648
|
+
this.values.set(this.targets);
|
|
649
|
+
this.velocities.fill(0);
|
|
650
|
+
return this.values;
|
|
651
|
+
}
|
|
652
|
+
const damping = Math.LN2 / this.halflife;
|
|
653
|
+
const eydt = Math.exp(-damping * dt);
|
|
654
|
+
for (let i = 0; i < NUM_BLENDSHAPES; i++) {
|
|
655
|
+
const j0 = this.values[i] - this.targets[i];
|
|
656
|
+
const j1 = this.velocities[i] + j0 * damping;
|
|
657
|
+
this.values[i] = eydt * (j0 + j1 * dt) + this.targets[i];
|
|
658
|
+
this.velocities[i] = eydt * (this.velocities[i] - j1 * damping * dt);
|
|
659
|
+
this.values[i] = Math.max(0, Math.min(1, this.values[i]));
|
|
660
|
+
}
|
|
661
|
+
return this.values;
|
|
662
|
+
}
|
|
663
|
+
/**
|
|
664
|
+
* Decay all spring targets to neutral (0).
|
|
665
|
+
*
|
|
666
|
+
* Call when inference stalls (no new frames for threshold duration).
|
|
667
|
+
* The springs will smoothly close the mouth / relax the face over
|
|
668
|
+
* the halflife period rather than freezing.
|
|
669
|
+
*/
|
|
670
|
+
decayToNeutral() {
|
|
671
|
+
this.targets.fill(0);
|
|
672
|
+
}
|
|
673
|
+
/**
|
|
674
|
+
* Reset all state (values, velocities, targets).
|
|
675
|
+
* Call when starting a new playback session.
|
|
676
|
+
*/
|
|
677
|
+
reset() {
|
|
678
|
+
this.values.fill(0);
|
|
679
|
+
this.velocities.fill(0);
|
|
680
|
+
this.targets.fill(0);
|
|
681
|
+
this._hasTarget = false;
|
|
682
|
+
}
|
|
683
|
+
};
|
|
684
|
+
|
|
611
685
|
// src/inference/A2EProcessor.ts
|
|
612
686
|
var logger4 = createLogger("A2EProcessor");
|
|
613
687
|
var FRAME_RATE = 30;
|
|
614
688
|
var DRIP_INTERVAL_MS = 33;
|
|
615
|
-
var
|
|
616
|
-
var DECAY_DURATION_MS = 300;
|
|
689
|
+
var NEUTRAL_THRESHOLD_MS = 1500;
|
|
617
690
|
var _A2EProcessor = class _A2EProcessor {
|
|
618
691
|
constructor(config) {
|
|
619
692
|
this.writeOffset = 0;
|
|
@@ -624,10 +697,10 @@ var _A2EProcessor = class _A2EProcessor {
|
|
|
624
697
|
// Push mode state
|
|
625
698
|
this._latestFrame = null;
|
|
626
699
|
this.dripInterval = null;
|
|
627
|
-
// Last-frame-hold for pull mode (prevents avatar freezing between frames)
|
|
628
|
-
this.lastPulledFrame = null;
|
|
629
700
|
this.lastDequeuedTime = 0;
|
|
630
|
-
this.
|
|
701
|
+
this.lastUpdateTime = 0;
|
|
702
|
+
this.neutralTriggered = false;
|
|
703
|
+
this.outputBuffer = null;
|
|
631
704
|
// Inference serialization
|
|
632
705
|
this.inferenceRunning = false;
|
|
633
706
|
this.pendingChunks = [];
|
|
@@ -640,6 +713,7 @@ var _A2EProcessor = class _A2EProcessor {
|
|
|
640
713
|
this.identityIndex = config.identityIndex ?? 0;
|
|
641
714
|
this.onFrame = config.onFrame;
|
|
642
715
|
this.onError = config.onError;
|
|
716
|
+
this.smoother = new BlendshapeSmoother({ halflife: config.smoothingHalflife ?? 0.06 });
|
|
643
717
|
this.bufferCapacity = this.chunkSize * 2;
|
|
644
718
|
this.buffer = new Float32Array(this.bufferCapacity);
|
|
645
719
|
}
|
|
@@ -739,8 +813,11 @@ var _A2EProcessor = class _A2EProcessor {
|
|
|
739
813
|
this.timestampedQueue = [];
|
|
740
814
|
this.plainQueue = [];
|
|
741
815
|
this._latestFrame = null;
|
|
742
|
-
this.
|
|
816
|
+
this.smoother.reset();
|
|
743
817
|
this.lastDequeuedTime = 0;
|
|
818
|
+
this.lastUpdateTime = 0;
|
|
819
|
+
this.neutralTriggered = false;
|
|
820
|
+
this.outputBuffer = null;
|
|
744
821
|
this.pendingChunks = [];
|
|
745
822
|
this.inferenceRunning = false;
|
|
746
823
|
this.getFrameCallCount = 0;
|
|
@@ -759,6 +836,7 @@ var _A2EProcessor = class _A2EProcessor {
|
|
|
759
836
|
*/
|
|
760
837
|
getFrameForTime(currentTime) {
|
|
761
838
|
this.getFrameCallCount++;
|
|
839
|
+
const now = getClock().now();
|
|
762
840
|
const discardWindow = this.backend.backend === "wasm" ? 1.5 : 0.5;
|
|
763
841
|
let discardCount = 0;
|
|
764
842
|
while (this.timestampedQueue.length > 0 && this.timestampedQueue[0].timestamp < currentTime - discardWindow) {
|
|
@@ -774,13 +852,20 @@ var _A2EProcessor = class _A2EProcessor {
|
|
|
774
852
|
nextFrameTs: this.timestampedQueue.length > 0 ? this.timestampedQueue[0].timestamp.toFixed(3) : "none"
|
|
775
853
|
});
|
|
776
854
|
}
|
|
855
|
+
let newDequeue = false;
|
|
777
856
|
if (this.timestampedQueue.length > 0 && this.timestampedQueue[0].timestamp <= currentTime) {
|
|
778
857
|
const { frame } = this.timestampedQueue.shift();
|
|
779
|
-
|
|
780
|
-
this.
|
|
781
|
-
|
|
858
|
+
const firstTarget = !this.smoother.hasTarget;
|
|
859
|
+
this.smoother.setTarget(frame);
|
|
860
|
+
if (firstTarget) {
|
|
861
|
+
this.smoother.update(1);
|
|
862
|
+
this.lastUpdateTime = now;
|
|
863
|
+
}
|
|
864
|
+
newDequeue = true;
|
|
865
|
+
this.neutralTriggered = false;
|
|
866
|
+
this.lastDequeuedTime = now;
|
|
782
867
|
}
|
|
783
|
-
if (this.timestampedQueue.length > 0 && this.getFrameCallCount % 60 === 0) {
|
|
868
|
+
if (!newDequeue && this.timestampedQueue.length > 0 && this.getFrameCallCount % 60 === 0) {
|
|
784
869
|
logger4.debug("getFrameForTime: waiting for playback time to reach queued frames", {
|
|
785
870
|
queueLen: this.timestampedQueue.length,
|
|
786
871
|
frontTimestamp: this.timestampedQueue[0].timestamp.toFixed(4),
|
|
@@ -788,25 +873,24 @@ var _A2EProcessor = class _A2EProcessor {
|
|
|
788
873
|
delta: (this.timestampedQueue[0].timestamp - currentTime).toFixed(4)
|
|
789
874
|
});
|
|
790
875
|
}
|
|
791
|
-
if (this.
|
|
792
|
-
const elapsed =
|
|
793
|
-
if (elapsed
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
const decayElapsed = elapsed - HOLD_DURATION_MS;
|
|
797
|
-
if (decayElapsed >= DECAY_DURATION_MS) {
|
|
798
|
-
this.lastPulledFrame = null;
|
|
799
|
-
return null;
|
|
800
|
-
}
|
|
801
|
-
const t = decayElapsed / DECAY_DURATION_MS;
|
|
802
|
-
const factor = (1 - t) * (1 - t);
|
|
803
|
-
if (!this.decayBuffer) this.decayBuffer = new Float32Array(52);
|
|
804
|
-
for (let i = 0; i < 52; i++) {
|
|
805
|
-
this.decayBuffer[i] = this.lastPulledFrame[i] * factor;
|
|
876
|
+
if (!newDequeue && this.smoother.hasTarget && !this.neutralTriggered) {
|
|
877
|
+
const elapsed = now - this.lastDequeuedTime;
|
|
878
|
+
if (elapsed >= NEUTRAL_THRESHOLD_MS) {
|
|
879
|
+
this.smoother.decayToNeutral();
|
|
880
|
+
this.neutralTriggered = true;
|
|
806
881
|
}
|
|
807
|
-
return this.decayBuffer;
|
|
808
882
|
}
|
|
809
|
-
|
|
883
|
+
if (!this.smoother.hasTarget) {
|
|
884
|
+
return null;
|
|
885
|
+
}
|
|
886
|
+
const dt = Math.min((now - this.lastUpdateTime) / 1e3, 0.1);
|
|
887
|
+
const smoothed = this.smoother.update(dt);
|
|
888
|
+
this.lastUpdateTime = now;
|
|
889
|
+
if (newDequeue || !this.outputBuffer) {
|
|
890
|
+
this.outputBuffer = new Float32Array(52);
|
|
891
|
+
}
|
|
892
|
+
this.outputBuffer.set(smoothed);
|
|
893
|
+
return this.outputBuffer;
|
|
810
894
|
}
|
|
811
895
|
// ═══════════════════════════════════════════════════════════════════════
|
|
812
896
|
// Frame Output — Push Mode (live mic, game loop)
|
|
@@ -10137,80 +10221,6 @@ var InterruptionHandler = class extends EventEmitter {
|
|
|
10137
10221
|
}
|
|
10138
10222
|
};
|
|
10139
10223
|
|
|
10140
|
-
// src/inference/BlendshapeSmoother.ts
|
|
10141
|
-
var NUM_BLENDSHAPES = 52;
|
|
10142
|
-
var BlendshapeSmoother = class {
|
|
10143
|
-
constructor(config) {
|
|
10144
|
-
/** Whether any target has been set */
|
|
10145
|
-
this._hasTarget = false;
|
|
10146
|
-
this.halflife = config?.halflife ?? 0.06;
|
|
10147
|
-
this.values = new Float32Array(NUM_BLENDSHAPES);
|
|
10148
|
-
this.velocities = new Float32Array(NUM_BLENDSHAPES);
|
|
10149
|
-
this.targets = new Float32Array(NUM_BLENDSHAPES);
|
|
10150
|
-
}
|
|
10151
|
-
/** Whether a target frame has been set (false until first setTarget call) */
|
|
10152
|
-
get hasTarget() {
|
|
10153
|
-
return this._hasTarget;
|
|
10154
|
-
}
|
|
10155
|
-
/**
|
|
10156
|
-
* Set new target frame from inference output.
|
|
10157
|
-
* Springs will converge toward these values on subsequent update() calls.
|
|
10158
|
-
*/
|
|
10159
|
-
setTarget(frame) {
|
|
10160
|
-
this.targets.set(frame);
|
|
10161
|
-
this._hasTarget = true;
|
|
10162
|
-
}
|
|
10163
|
-
/**
|
|
10164
|
-
* Advance all 52 springs by `dt` seconds and return the smoothed frame.
|
|
10165
|
-
*
|
|
10166
|
-
* Call this every render frame (e.g., inside requestAnimationFrame).
|
|
10167
|
-
* Returns the internal values buffer — do NOT mutate the returned array.
|
|
10168
|
-
*
|
|
10169
|
-
* @param dt - Time step in seconds (e.g., 1/60 for 60fps)
|
|
10170
|
-
* @returns Smoothed blendshape values (Float32Array of 52)
|
|
10171
|
-
*/
|
|
10172
|
-
update(dt) {
|
|
10173
|
-
if (!this._hasTarget) {
|
|
10174
|
-
return this.values;
|
|
10175
|
-
}
|
|
10176
|
-
if (this.halflife <= 0) {
|
|
10177
|
-
this.values.set(this.targets);
|
|
10178
|
-
this.velocities.fill(0);
|
|
10179
|
-
return this.values;
|
|
10180
|
-
}
|
|
10181
|
-
const damping = Math.LN2 / this.halflife;
|
|
10182
|
-
const eydt = Math.exp(-damping * dt);
|
|
10183
|
-
for (let i = 0; i < NUM_BLENDSHAPES; i++) {
|
|
10184
|
-
const j0 = this.values[i] - this.targets[i];
|
|
10185
|
-
const j1 = this.velocities[i] + j0 * damping;
|
|
10186
|
-
this.values[i] = eydt * (j0 + j1 * dt) + this.targets[i];
|
|
10187
|
-
this.velocities[i] = eydt * (this.velocities[i] - j1 * damping * dt);
|
|
10188
|
-
this.values[i] = Math.max(0, Math.min(1, this.values[i]));
|
|
10189
|
-
}
|
|
10190
|
-
return this.values;
|
|
10191
|
-
}
|
|
10192
|
-
/**
|
|
10193
|
-
* Decay all spring targets to neutral (0).
|
|
10194
|
-
*
|
|
10195
|
-
* Call when inference stalls (no new frames for threshold duration).
|
|
10196
|
-
* The springs will smoothly close the mouth / relax the face over
|
|
10197
|
-
* the halflife period rather than freezing.
|
|
10198
|
-
*/
|
|
10199
|
-
decayToNeutral() {
|
|
10200
|
-
this.targets.fill(0);
|
|
10201
|
-
}
|
|
10202
|
-
/**
|
|
10203
|
-
* Reset all state (values, velocities, targets).
|
|
10204
|
-
* Call when starting a new playback session.
|
|
10205
|
-
*/
|
|
10206
|
-
reset() {
|
|
10207
|
-
this.values.fill(0);
|
|
10208
|
-
this.velocities.fill(0);
|
|
10209
|
-
this.targets.fill(0);
|
|
10210
|
-
this._hasTarget = false;
|
|
10211
|
-
}
|
|
10212
|
-
};
|
|
10213
|
-
|
|
10214
10224
|
// src/inference/SafariSpeechRecognition.ts
|
|
10215
10225
|
var logger33 = createLogger("SafariSpeech");
|
|
10216
10226
|
var SafariSpeechRecognition = class _SafariSpeechRecognition {
|