@glissade/core 0.6.0 → 0.7.0-pre.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 +10 -2
- package/dist/index.js +53 -22
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -423,6 +423,14 @@ interface CompiledTimeline {
|
|
|
423
423
|
/** Audio clips rebased to the root time axis (§5.3); sync timeScale scales playbackRate. */
|
|
424
424
|
audio: AudioClip[];
|
|
425
425
|
}
|
|
426
|
+
/**
|
|
427
|
+
* Sample-indexed clip offset — the single A/V-sync source of truth (§5.3),
|
|
428
|
+
* `round(at * sampleRate)`. Both the CLI (FFmpeg) and the browser
|
|
429
|
+
* (OfflineAudioContext) mixers derive their delay from this, so A/V offsets
|
|
430
|
+
* agree across paths by construction rather than one rounding to milliseconds
|
|
431
|
+
* and the other to raw float seconds. Default rate is the canonical mix grid.
|
|
432
|
+
*/
|
|
433
|
+
declare function audioOffsetSamples(at: number, sampleRate?: number): number;
|
|
426
434
|
declare function compileTimeline(doc: Timeline): CompiledTimeline;
|
|
427
435
|
//#endregion
|
|
428
436
|
//#region src/targetRef.d.ts
|
|
@@ -567,7 +575,7 @@ interface SidecarDoc {
|
|
|
567
575
|
sidecarVersion: 1;
|
|
568
576
|
/** Editor-owned tracks, replacing same-target code tracks wholesale. */
|
|
569
577
|
tracks: Track[];
|
|
570
|
-
/** Editor-
|
|
578
|
+
/** Editor-created labels; code labels are authoritative and win on a name collision (§6.2). */
|
|
571
579
|
labels?: Record<string, number>;
|
|
572
580
|
}
|
|
573
581
|
declare class SidecarVersionError extends Error {
|
|
@@ -595,4 +603,4 @@ declare function normalizeEditedKeys(keys: Key[]): Key[];
|
|
|
595
603
|
*/
|
|
596
604
|
declare function mergeSidecar(code: Timeline, sidecar: SidecarDoc | null | undefined): Timeline;
|
|
597
605
|
//#endregion
|
|
598
|
-
export { type AssetRef, type AudioClip, type BakeConfig, BakeError, type BindTarget, type BindableSignal, type BoundTimeline, type CheckpointedBakeConfig, type CheckpointedSim, type ChildEntry, CircularDependencyError, ColorParseError, type CompiledTimeline, type CurveSampler, DEFAULT_EASE, type DevWarning, type EaseSpec, type EasingFn, type Equals, type GainEnvelope, type HandoffKind, type Json, type Key, type KeyOpts, type Marker, type OkLab, type PathContour, type PathValue, type Playhead, type Position, PositionError, type ReadonlySignal, type RetargetSpring, type Rgba, type Rng, type SidecarDoc, SidecarVersionError, type Signal, type SignalOptions, type SpringConfig, type SpringEase, TARGET_PATH, type TargetCarrier, type Timeline, type TimelineBuilder, type TimelineInit, TimelineValidationError, type Track, TrackValidationError, type TweenOpts, type TweenTarget, UnboundTargetError, UnknownEasingError, UnknownValueTypeError, UnresolvableTargetError, type ValueType, type ValueTypeId, ValueTypeInferenceError, type Vec2, type Vec2Signal, WriteDuringEvaluationError, bake, bakeCheckpointed, beginReadPhase, bindTimeline, booleanType, buildTimeline, colorType, compileTimeline, computed, createPlayhead, cubicBezier, cubicBezierDerivative, easingDerivatives, easings, emitDevWarning, emptySidecar, endReadPhase, evaluateAt, formatColor, getTimelineCallbacks, getValueType, inReadPhase, inferValueType, key, lerpColor, mergeSidecar, namedEasing, normalizeEditedKeys, numberType, oklabToRgba, parseColor, pathType, random, registerValueType, resolveEase, resolveEaseDerivative, resolveTweenTarget, rgbaToOklab, sampleTrack, setDevWarning, signal, spring, springEasing, springEasingDerivative, springPresets, springTo, stagger, stringType, timeline, track, untracked, validateTrack, vec2Equals, vec2Signal, vec2Type, velocityAt };
|
|
606
|
+
export { type AssetRef, type AudioClip, type BakeConfig, BakeError, type BindTarget, type BindableSignal, type BoundTimeline, type CheckpointedBakeConfig, type CheckpointedSim, type ChildEntry, CircularDependencyError, ColorParseError, type CompiledTimeline, type CurveSampler, DEFAULT_EASE, type DevWarning, type EaseSpec, type EasingFn, type Equals, type GainEnvelope, type HandoffKind, type Json, type Key, type KeyOpts, type Marker, type OkLab, type PathContour, type PathValue, type Playhead, type Position, PositionError, type ReadonlySignal, type RetargetSpring, type Rgba, type Rng, type SidecarDoc, SidecarVersionError, type Signal, type SignalOptions, type SpringConfig, type SpringEase, TARGET_PATH, type TargetCarrier, type Timeline, type TimelineBuilder, type TimelineInit, TimelineValidationError, type Track, TrackValidationError, type TweenOpts, type TweenTarget, UnboundTargetError, UnknownEasingError, UnknownValueTypeError, UnresolvableTargetError, type ValueType, type ValueTypeId, ValueTypeInferenceError, type Vec2, type Vec2Signal, WriteDuringEvaluationError, audioOffsetSamples, bake, bakeCheckpointed, beginReadPhase, bindTimeline, booleanType, buildTimeline, colorType, compileTimeline, computed, createPlayhead, cubicBezier, cubicBezierDerivative, easingDerivatives, easings, emitDevWarning, emptySidecar, endReadPhase, evaluateAt, formatColor, getTimelineCallbacks, getValueType, inReadPhase, inferValueType, key, lerpColor, mergeSidecar, namedEasing, normalizeEditedKeys, numberType, oklabToRgba, parseColor, pathType, random, registerValueType, resolveEase, resolveEaseDerivative, resolveTweenTarget, rgbaToOklab, sampleTrack, setDevWarning, signal, spring, springEasing, springEasingDerivative, springPresets, springTo, stagger, stringType, timeline, track, untracked, validateTrack, vec2Equals, vec2Signal, vec2Type, velocityAt };
|
package/dist/index.js
CHANGED
|
@@ -865,8 +865,11 @@ var TrackValidationError = class extends Error {
|
|
|
865
865
|
}
|
|
866
866
|
};
|
|
867
867
|
function validateTrack(track) {
|
|
868
|
-
getValueType(track.type);
|
|
868
|
+
const vt = getValueType(track.type);
|
|
869
869
|
if (track.keys.length === 0) throw new TrackValidationError(track.target, "must have at least one key");
|
|
870
|
+
if (vt.defaultHandoff === "cut") {
|
|
871
|
+
for (const k of track.keys) if (k.interp !== "hold") k.interp = "hold";
|
|
872
|
+
}
|
|
870
873
|
for (let i = 1; i < track.keys.length; i++) {
|
|
871
874
|
const prev = track.keys[i - 1];
|
|
872
875
|
const cur = track.keys[i];
|
|
@@ -960,7 +963,7 @@ function resolveEaseDerivative(spec) {
|
|
|
960
963
|
warnedNumericDerivative.add(spec);
|
|
961
964
|
emitDevWarning(`easing '${spec}' has no registered derivative; velocity uses a numeric fallback — register one in easingDerivatives for exact interruption handoff`);
|
|
962
965
|
}
|
|
963
|
-
const h =
|
|
966
|
+
const h = 1 / 1024;
|
|
964
967
|
return (u) => (fn(Math.min(1, u + h)) - fn(Math.max(0, u - h))) / (Math.min(1, u + h) - Math.max(0, u - h));
|
|
965
968
|
}
|
|
966
969
|
if (spec.kind === "cubicBezier") return cubicBezierDerivative(...spec.pts);
|
|
@@ -1048,7 +1051,13 @@ function sampleTrack(tr, t) {
|
|
|
1048
1051
|
const vt = getValueType(tr.type);
|
|
1049
1052
|
const p = (t - prev.t) / (arrival.t - prev.t);
|
|
1050
1053
|
let easedT = easeFor(tr, s, i)(p);
|
|
1051
|
-
if (!vt.extrapolates
|
|
1054
|
+
if (!vt.extrapolates && (easedT < 0 || easedT > 1)) {
|
|
1055
|
+
if (!s.warnedClamp) {
|
|
1056
|
+
s.warnedClamp = true;
|
|
1057
|
+
emitDevWarning(`track '${tr.target}' (type '${tr.type}') clamped an out-of-range eased value — non-extrapolating types can't overshoot, so a spring/overshooting ease on this track is flattened`);
|
|
1058
|
+
}
|
|
1059
|
+
easedT = Math.min(1, Math.max(0, easedT));
|
|
1060
|
+
}
|
|
1052
1061
|
return vt.lerp(prev.value, arrival.value, easedT);
|
|
1053
1062
|
}
|
|
1054
1063
|
//#endregion
|
|
@@ -1089,13 +1098,23 @@ function validateSpringKeys(tr) {
|
|
|
1089
1098
|
}
|
|
1090
1099
|
}
|
|
1091
1100
|
}
|
|
1101
|
+
/**
|
|
1102
|
+
* Sample-indexed clip offset — the single A/V-sync source of truth (§5.3),
|
|
1103
|
+
* `round(at * sampleRate)`. Both the CLI (FFmpeg) and the browser
|
|
1104
|
+
* (OfflineAudioContext) mixers derive their delay from this, so A/V offsets
|
|
1105
|
+
* agree across paths by construction rather than one rounding to milliseconds
|
|
1106
|
+
* and the other to raw float seconds. Default rate is the canonical mix grid.
|
|
1107
|
+
*/
|
|
1108
|
+
function audioOffsetSamples(at, sampleRate = 48e3) {
|
|
1109
|
+
return Math.round(at * sampleRate);
|
|
1110
|
+
}
|
|
1092
1111
|
function rebaseKeys(keys, at, timeScale) {
|
|
1093
1112
|
return keys.map((k) => ({
|
|
1094
1113
|
...k,
|
|
1095
1114
|
t: at + k.t / timeScale
|
|
1096
1115
|
}));
|
|
1097
1116
|
}
|
|
1098
|
-
function flatten(doc, at, timeScale,
|
|
1117
|
+
function flatten(doc, at, timeScale, unit, out, counter) {
|
|
1099
1118
|
for (const tr of doc.tracks) {
|
|
1100
1119
|
validateTrack(tr);
|
|
1101
1120
|
validateSpringKeys(tr);
|
|
@@ -1104,14 +1123,15 @@ function flatten(doc, at, timeScale, opaque, out) {
|
|
|
1104
1123
|
...tr,
|
|
1105
1124
|
keys: rebaseKeys(tr.keys, at, timeScale)
|
|
1106
1125
|
},
|
|
1107
|
-
|
|
1126
|
+
unit
|
|
1108
1127
|
});
|
|
1109
1128
|
}
|
|
1110
1129
|
for (const child of doc.children ?? []) {
|
|
1111
1130
|
const scale = child.mode === "sync" ? child.timeScale ?? 1 : 1;
|
|
1112
1131
|
if (child.mode === "add" && child.timeScale !== void 0) throw new TimelineValidationError("timeScale is only valid on mode:'sync' children (§2.3)");
|
|
1113
1132
|
if (scale <= 0) throw new TimelineValidationError("sync timeScale must be > 0");
|
|
1114
|
-
|
|
1133
|
+
const childUnit = child.mode === "sync" ? ++counter.n : unit;
|
|
1134
|
+
flatten(child.timeline, at + child.at / timeScale, timeScale * scale, childUnit, out, counter);
|
|
1115
1135
|
}
|
|
1116
1136
|
}
|
|
1117
1137
|
/**
|
|
@@ -1121,25 +1141,31 @@ function flatten(doc, at, timeScale, opaque, out) {
|
|
|
1121
1141
|
*/
|
|
1122
1142
|
function coalesce(entries) {
|
|
1123
1143
|
const byTarget = /* @__PURE__ */ new Map();
|
|
1124
|
-
for (const { track: tr } of entries) {
|
|
1144
|
+
for (const { track: tr, unit } of entries) {
|
|
1125
1145
|
const existing = byTarget.get(tr.target);
|
|
1126
1146
|
if (!existing) {
|
|
1127
1147
|
byTarget.set(tr.target, {
|
|
1128
|
-
|
|
1129
|
-
|
|
1148
|
+
track: {
|
|
1149
|
+
...tr,
|
|
1150
|
+
keys: [...tr.keys]
|
|
1151
|
+
},
|
|
1152
|
+
unit
|
|
1130
1153
|
});
|
|
1131
1154
|
continue;
|
|
1132
1155
|
}
|
|
1133
|
-
if (existing.
|
|
1156
|
+
if (existing.unit !== unit) throw new TimelineValidationError(`target '${tr.target}' is animated by a sync (opaque) child and another timeline unit; sync children must own disjoint targets (no last-writer-wins across the sync boundary, §2.3)`);
|
|
1157
|
+
if (existing.track.type !== tr.type) throw new TimelineValidationError(`target '${tr.target}' has conflicting value types '${existing.track.type}' and '${tr.type}'`);
|
|
1134
1158
|
const start = tr.keys[0].t;
|
|
1135
1159
|
const end = tr.keys[tr.keys.length - 1].t;
|
|
1136
|
-
const existingStart = existing.keys[0].t;
|
|
1137
|
-
const existingEnd = existing.keys[existing.keys.length - 1].t;
|
|
1138
|
-
const kept = existing.keys.filter((k) => k.t < start || k.t > end);
|
|
1139
|
-
if (existingStart <= end && start <= existingEnd) emitDevWarning(`overlapping tracks for '${tr.target}' in [${start}, ${end}]: later insertion wins (${existing.keys.length - kept.length} earlier key(s) dropped)`);
|
|
1140
|
-
existing.keys = [...kept, ...tr.keys].sort((a, b) => a.t - b.t);
|
|
1160
|
+
const existingStart = existing.track.keys[0].t;
|
|
1161
|
+
const existingEnd = existing.track.keys[existing.track.keys.length - 1].t;
|
|
1162
|
+
const kept = existing.track.keys.filter((k) => k.t < start || k.t > end);
|
|
1163
|
+
if (existingStart <= end && start <= existingEnd) emitDevWarning(`overlapping tracks for '${tr.target}' in [${start}, ${end}]: later insertion wins (${existing.track.keys.length - kept.length} earlier key(s) dropped)`);
|
|
1164
|
+
existing.track.keys = [...kept, ...tr.keys].sort((a, b) => a.t - b.t);
|
|
1141
1165
|
}
|
|
1142
|
-
|
|
1166
|
+
const result = /* @__PURE__ */ new Map();
|
|
1167
|
+
for (const [target, { track }] of byTarget) result.set(target, track);
|
|
1168
|
+
return result;
|
|
1143
1169
|
}
|
|
1144
1170
|
function childExtent(child) {
|
|
1145
1171
|
const scale = child.mode === "sync" ? child.timeScale ?? 1 : 1;
|
|
@@ -1159,7 +1185,7 @@ function computeDuration(doc) {
|
|
|
1159
1185
|
function compileTimeline(doc) {
|
|
1160
1186
|
if (doc.version !== 1) throw new TimelineValidationError(`unsupported timeline document version ${String(doc.version)}`);
|
|
1161
1187
|
const flat = [];
|
|
1162
|
-
flatten(doc, 0, 1,
|
|
1188
|
+
flatten(doc, 0, 1, 0, flat, { n: 0 });
|
|
1163
1189
|
const tracks = coalesce(flat);
|
|
1164
1190
|
const labels = { ...doc.labels };
|
|
1165
1191
|
const markers = [...doc.markers ?? []];
|
|
@@ -1667,11 +1693,16 @@ function mergeSidecar(code, sidecar) {
|
|
|
1667
1693
|
...code,
|
|
1668
1694
|
tracks
|
|
1669
1695
|
};
|
|
1670
|
-
if (sidecar.labels && Object.keys(sidecar.labels).length > 0)
|
|
1671
|
-
|
|
1672
|
-
|
|
1673
|
-
|
|
1696
|
+
if (sidecar.labels && Object.keys(sidecar.labels).length > 0) {
|
|
1697
|
+
const codeLabels = code.labels ?? {};
|
|
1698
|
+
const shadowed = Object.keys(sidecar.labels).filter((n) => n in codeLabels);
|
|
1699
|
+
if (shadowed.length) emitDevWarning(`sidecar label(s) ${shadowed.join(", ")} collide with code labels; code wins (§6.2)`);
|
|
1700
|
+
merged.labels = {
|
|
1701
|
+
...sidecar.labels,
|
|
1702
|
+
...codeLabels
|
|
1703
|
+
};
|
|
1704
|
+
}
|
|
1674
1705
|
return merged;
|
|
1675
1706
|
}
|
|
1676
1707
|
//#endregion
|
|
1677
|
-
export { BakeError, CircularDependencyError, ColorParseError, DEFAULT_EASE, PositionError, SidecarVersionError, TARGET_PATH, TimelineValidationError, TrackValidationError, UnboundTargetError, UnknownEasingError, UnknownValueTypeError, UnresolvableTargetError, ValueTypeInferenceError, WriteDuringEvaluationError, bake, bakeCheckpointed, beginReadPhase, bindTimeline, booleanType, buildTimeline, colorType, compileTimeline, computed, createPlayhead, cubicBezier, cubicBezierDerivative, easingDerivatives, easings, emitDevWarning, emptySidecar, endReadPhase, evaluateAt, formatColor, getTimelineCallbacks, getValueType, inReadPhase, inferValueType, key, lerpColor, mergeSidecar, namedEasing, normalizeEditedKeys, numberType, oklabToRgba, parseColor, pathType, random, registerValueType, resolveEase, resolveEaseDerivative, resolveTweenTarget, rgbaToOklab, sampleTrack, setDevWarning, signal, spring, springEasing, springEasingDerivative, springPresets, springTo, stagger, stringType, timeline, track, untracked, validateTrack, vec2Equals, vec2Signal, vec2Type, velocityAt };
|
|
1708
|
+
export { BakeError, CircularDependencyError, ColorParseError, DEFAULT_EASE, PositionError, SidecarVersionError, TARGET_PATH, TimelineValidationError, TrackValidationError, UnboundTargetError, UnknownEasingError, UnknownValueTypeError, UnresolvableTargetError, ValueTypeInferenceError, WriteDuringEvaluationError, audioOffsetSamples, bake, bakeCheckpointed, beginReadPhase, bindTimeline, booleanType, buildTimeline, colorType, compileTimeline, computed, createPlayhead, cubicBezier, cubicBezierDerivative, easingDerivatives, easings, emitDevWarning, emptySidecar, endReadPhase, evaluateAt, formatColor, getTimelineCallbacks, getValueType, inReadPhase, inferValueType, key, lerpColor, mergeSidecar, namedEasing, normalizeEditedKeys, numberType, oklabToRgba, parseColor, pathType, random, registerValueType, resolveEase, resolveEaseDerivative, resolveTweenTarget, rgbaToOklab, sampleTrack, setDevWarning, signal, spring, springEasing, springEasingDerivative, springPresets, springTo, stagger, stringType, timeline, track, untracked, validateTrack, vec2Equals, vec2Signal, vec2Type, velocityAt };
|
package/package.json
CHANGED