@aicopycoders/exodus 2026.9.2000-beta.22 → 2026.9.2000-beta.23
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.
|
@@ -962,8 +962,11 @@ the pieces:
|
|
|
962
962
|
reaction beat keep their length.
|
|
963
963
|
- **It fades the audience reaction in and out.** A reaction beat fades in
|
|
964
964
|
across any quiet start before its laugh (a generated laugh clip can hum
|
|
965
|
-
there)
|
|
966
|
-
|
|
965
|
+
there). When a line follows, the picture moves on early and the laugh's own
|
|
966
|
+
remaining sound plays on under that line, quieter and fading out: up to
|
|
967
|
+
0.75 s, and the beat keeps at least 1 s of picture (a 1.5 s clip shows 1 s
|
|
968
|
+
and carries its last 0.5 s). A beat that ends the ad, or has under 0.4 s to
|
|
969
|
+
carry, fades out over its last 0.4 s instead.
|
|
967
970
|
- **It ignores the continuous master.** It reads only `scene-NN.voice` files;
|
|
968
971
|
it never opens `narration.mp3` or `manifest.narration`. A tool that lays the
|
|
969
972
|
master down whole and cuts the pictures to it keeps the read intact.
|
|
@@ -71,8 +71,10 @@ export const REACTION_MAX_SEC = 1.5;
|
|
|
71
71
|
*/
|
|
72
72
|
export const REACTION_TAIL_SEC = 0.75;
|
|
73
73
|
export const REACTION_TAIL_GAIN = 0.35;
|
|
74
|
-
/** A reaction clip
|
|
74
|
+
/** A reaction clip with too little sound for a tail fades out over this long instead (#2666). */
|
|
75
75
|
export const REACTION_FADE_OUT_SEC = 0.4;
|
|
76
|
+
/** The least picture a reaction beat keeps when it gives up its end to a tail (#2700). */
|
|
77
|
+
export const REACTION_MIN_BEAT_SEC = 1.0;
|
|
76
78
|
/**
|
|
77
79
|
* The air a join keeps around its words (#2388): after one clip's last word, and
|
|
78
80
|
* before the next clip's first, less when the same speaker carries on than when
|
|
@@ -539,17 +541,21 @@ function placeReaction(spine, counts, reaction) {
|
|
|
539
541
|
}
|
|
540
542
|
|
|
541
543
|
/**
|
|
542
|
-
* The sound a reaction beat carries under the next segment (#2406)
|
|
543
|
-
*
|
|
544
|
-
* the
|
|
545
|
-
*
|
|
544
|
+
* The sound a reaction beat carries under the next segment (#2406), as a J/L
|
|
545
|
+
* cut: the picture moves on early and the clip's sound from where it stops
|
|
546
|
+
* plays under the next line. A clip that ends with its beat gives up the
|
|
547
|
+
* beat's end, down to REACTION_MIN_BEAT_SEC, rather than replaying sound the
|
|
548
|
+
* beat already played, which was heard as a stutter (#2666, #2700). Shortens
|
|
549
|
+
* the beat, so it runs before the spine's start times are set. Null when
|
|
550
|
+
* under 0.4 s of sound would carry over; the beat fades itself out then.
|
|
546
551
|
*/
|
|
547
|
-
function
|
|
552
|
+
function carveReactionTail(seg, next) {
|
|
548
553
|
const clipLen = seg.clipSeconds ?? seg.seconds;
|
|
549
|
-
const
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
554
|
+
const overlap =
|
|
555
|
+
Math.round(Math.min(REACTION_TAIL_SEC, clipLen - seg.inSec - REACTION_MIN_BEAT_SEC, next.seconds) * 1000) / 1000;
|
|
556
|
+
if (overlap < REACTION_FADE_OUT_SEC) return null;
|
|
557
|
+
seg.seconds = Math.min(seg.seconds, Math.round((clipLen - seg.inSec - overlap) * 1000) / 1000);
|
|
558
|
+
return { fromSec: seg.inSec + seg.seconds, seconds: overlap, gain: REACTION_TAIL_GAIN };
|
|
553
559
|
}
|
|
554
560
|
|
|
555
561
|
/**
|
|
@@ -673,17 +679,20 @@ export function buildTimeline({ manifest, storyboard, skip, probe, exists, readW
|
|
|
673
679
|
dropped.push({ n: c.sceneIndex, reason: `cutaway ${c.sceneIndex}: ${noCue(c)}` });
|
|
674
680
|
}
|
|
675
681
|
}
|
|
682
|
+
spine.forEach((seg, k) => {
|
|
683
|
+
if (!seg.reaction || seg.audio !== "clip") return;
|
|
684
|
+
const tail = spine[k + 1] ? carveReactionTail(seg, spine[k + 1]) : null;
|
|
685
|
+
if (tail) seg.reaction.tail = tail;
|
|
686
|
+
else seg.fadeOutSec = Math.min(REACTION_FADE_OUT_SEC, seg.seconds);
|
|
687
|
+
seg.fadeInSec = quietStart(readLevels(seg.file), seg.seconds);
|
|
688
|
+
});
|
|
676
689
|
let total = 0;
|
|
677
690
|
for (const seg of spine) {
|
|
678
691
|
seg.startSec = total;
|
|
679
692
|
total += seg.seconds;
|
|
680
693
|
}
|
|
681
694
|
spine.forEach((seg, k) => {
|
|
682
|
-
if (
|
|
683
|
-
seg.fadeInSec = quietStart(readLevels(seg.file), seg.seconds);
|
|
684
|
-
const tail = spine[k + 1] ? reactionTail(seg, spine[k + 1]) : null;
|
|
685
|
-
if (tail) seg.reaction.tail = tail;
|
|
686
|
-
else seg.fadeOutSec = Math.min(REACTION_FADE_OUT_SEC, seg.seconds);
|
|
695
|
+
if (seg.reaction?.tail) seg.reaction.tail.atSec = spine[k + 1].startSec;
|
|
687
696
|
});
|
|
688
697
|
|
|
689
698
|
for (const c of waiting.filter((w) => !w.reaction)) {
|