@omnimedia/omnitool 1.1.0-77 → 1.1.0-79

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.
Files changed (62) hide show
  1. package/README.md +111 -7
  2. package/package.json +1 -1
  3. package/s/demo/routines/timeline-setup.ts +37 -22
  4. package/s/driver/fns/schematic.ts +2 -0
  5. package/s/driver/parts/compositor.ts +6 -0
  6. package/s/timeline/index.ts +2 -0
  7. package/s/timeline/parts/animations.ts +24 -0
  8. package/s/timeline/parts/filters.ts +3 -34
  9. package/s/timeline/parts/item.ts +24 -1
  10. package/s/timeline/renderers/parts/handy.ts +53 -36
  11. package/s/timeline/renderers/parts/samplers/visual/parts/sample.ts +9 -8
  12. package/s/timeline/renderers/parts/samplers/visual/parts/sequence.ts +4 -4
  13. package/s/timeline/sugar/helpers.ts +77 -5
  14. package/s/timeline/sugar/o.ts +97 -4
  15. package/s/timeline/types.ts +26 -3
  16. package/s/timeline/utils/anim.ts +71 -0
  17. package/s/timeline/utils/terps.ts +81 -0
  18. package/x/demo/demo.bundle.min.js +104 -104
  19. package/x/demo/demo.bundle.min.js.map +4 -4
  20. package/x/demo/routines/timeline-setup.js +16 -4
  21. package/x/demo/routines/timeline-setup.js.map +1 -1
  22. package/x/driver/fns/schematic.d.ts +2 -0
  23. package/x/driver/parts/compositor.js +5 -0
  24. package/x/driver/parts/compositor.js.map +1 -1
  25. package/x/index.html +2 -2
  26. package/x/tests.bundle.min.js +106 -106
  27. package/x/tests.bundle.min.js.map +4 -4
  28. package/x/tests.html +1 -1
  29. package/x/timeline/index.d.ts +2 -0
  30. package/x/timeline/index.js +2 -0
  31. package/x/timeline/index.js.map +1 -1
  32. package/x/timeline/parts/animations.d.ts +25 -0
  33. package/x/timeline/parts/animations.js +12 -0
  34. package/x/timeline/parts/animations.js.map +1 -0
  35. package/x/timeline/parts/filters.d.ts +3 -8
  36. package/x/timeline/parts/filters.js +1 -16
  37. package/x/timeline/parts/filters.js.map +1 -1
  38. package/x/timeline/parts/item.d.ts +24 -5
  39. package/x/timeline/parts/item.js +5 -3
  40. package/x/timeline/parts/item.js.map +1 -1
  41. package/x/timeline/renderers/parts/handy.d.ts +13 -7
  42. package/x/timeline/renderers/parts/handy.js +22 -20
  43. package/x/timeline/renderers/parts/handy.js.map +1 -1
  44. package/x/timeline/renderers/parts/samplers/visual/parts/sample.d.ts +3 -2
  45. package/x/timeline/renderers/parts/samplers/visual/parts/sample.js +6 -5
  46. package/x/timeline/renderers/parts/samplers/visual/parts/sample.js.map +1 -1
  47. package/x/timeline/renderers/parts/samplers/visual/parts/sequence.d.ts +3 -2
  48. package/x/timeline/renderers/parts/samplers/visual/parts/sequence.js +2 -2
  49. package/x/timeline/renderers/parts/samplers/visual/parts/sequence.js.map +1 -1
  50. package/x/timeline/sugar/helpers.d.ts +21 -4
  51. package/x/timeline/sugar/helpers.js +45 -2
  52. package/x/timeline/sugar/helpers.js.map +1 -1
  53. package/x/timeline/sugar/o.d.ts +18 -1
  54. package/x/timeline/sugar/o.js +72 -2
  55. package/x/timeline/sugar/o.js.map +1 -1
  56. package/x/timeline/types.d.ts +12 -2
  57. package/x/timeline/utils/anim.d.ts +5 -0
  58. package/x/timeline/utils/anim.js +44 -0
  59. package/x/timeline/utils/anim.js.map +1 -0
  60. package/x/timeline/utils/terps.d.ts +11 -0
  61. package/x/timeline/utils/terps.js +57 -0
  62. package/x/timeline/utils/terps.js.map +1 -0
@@ -2,19 +2,20 @@
2
2
  import {SampleContext} from "./types.js"
3
3
  import {sampleSequence} from "./sequence.js"
4
4
  import {Ms} from "../../../../../../units/ms.js"
5
- import {computeWorldMatrix} from "../../../handy.js"
5
+ import {Item, Kind, SpatialItem} from "../../../../../parts/item.js"
6
6
  import {FilterSpec, Layer} from "../../../../../../driver/fns/schematic.js"
7
- import {ContainerItem, Item, Kind} from "../../../../../parts/item.js"
7
+ import {AncestorAt, computeOpacity, computeWorldMatrix} from "../../../handy.js"
8
8
 
9
9
  export async function sampleVisual(
10
10
  ctx: SampleContext,
11
11
  item: Item.Any,
12
12
  time: Ms,
13
- ancestors: ContainerItem[]
13
+ ancestors: AncestorAt[]
14
14
  ): Promise<Layer[]> {
15
- const matrix = computeWorldMatrix(ctx.items, ancestors, item)
15
+ const matrix = computeWorldMatrix(ctx.items, ancestors, item, time)
16
+ const alpha = computeOpacity(ctx, item, time)
16
17
  const crop = "spatialId" in item && item.spatialId
17
- ? (ctx.items.get(item.spatialId) as Item.Spatial | undefined)?.crop
18
+ ? (ctx.items.get(item.spatialId) as SpatialItem | undefined)?.crop
18
19
  : undefined
19
20
  const filters = "filterIds" in item && item.filterIds
20
21
  ? item.filterIds
@@ -25,7 +26,7 @@ export async function sampleVisual(
25
26
 
26
27
  switch (item.kind) {
27
28
  case Kind.Stack: {
28
- const nextAnc = [...ancestors, item]
29
+ const nextAnc = [...ancestors, {item, localTime: time}]
29
30
 
30
31
  const layers = await Promise.all(
31
32
  item.childrenIds
@@ -44,7 +45,7 @@ export async function sampleVisual(
44
45
  if (time < 0 || time >= item.duration) return []
45
46
 
46
47
  const frame = await ctx.videoSampler(item, time)
47
- return frame ? [{kind: "image", frame, matrix, crop, filters, id: item.id}] : []
48
+ return frame ? [{kind: "image", frame, matrix, alpha, crop, filters, id: item.id}] : []
48
49
  }
49
50
 
50
51
  case Kind.Text: {
@@ -54,7 +55,7 @@ export async function sampleVisual(
54
55
  ? (ctx.items.get(item.styleId) as Item.TextStyle)?.style
55
56
  : undefined
56
57
 
57
- return [{id: item.id, kind: "text", content: item.content, style, matrix, crop, filters}]
58
+ return [{id: item.id, kind: "text", content: item.content, style, matrix, alpha, crop, filters}]
58
59
  }
59
60
 
60
61
  case Kind.Gap: {
@@ -3,21 +3,21 @@ import {sampleVisual} from "./sample.js"
3
3
  import {SampleContext} from "./types.js"
4
4
  import {sampleTransition} from "./transition.js"
5
5
  import {ms, Ms} from "../../../../../../units/ms.js"
6
- import {computeItemDuration} from "../../../handy.js"
6
+ import {Item, Kind} from "../../../../../parts/item.js"
7
7
  import {Layer} from "../../../../../../driver/fns/schematic.js"
8
- import {ContainerItem, Item, Kind} from "../../../../../parts/item.js"
8
+ import {AncestorAt, computeItemDuration} from "../../../handy.js"
9
9
 
10
10
  export async function sampleSequence(
11
11
  ctx: SampleContext,
12
12
  seq: Item.Sequence,
13
13
  time: Ms,
14
- ancestors: ContainerItem[]
14
+ ancestors: AncestorAt[]
15
15
  ): Promise<Layer[]> {
16
16
  const state = sampleSequenceAt(ctx, seq, time)
17
17
 
18
18
  if (!state) return []
19
19
 
20
- const nextAnc = [...ancestors, seq]
20
+ const nextAnc = [...ancestors, {item: seq, localTime: time}]
21
21
 
22
22
  if (!state.isTransitioning) {
23
23
  return sampleVisual(ctx, state.item, state.localTime, nextAnc)
@@ -2,14 +2,18 @@
2
2
  import {TextStyleOptions} from "pixi.js"
3
3
 
4
4
  import {O} from "./o.js"
5
- import {Transform} from "../types.js"
6
5
  import {Media} from "../parts/media.js"
7
6
  import {TimelineFile} from "../parts/basics.js"
8
7
  import {FilterAction} from "../parts/filters.js"
9
- import {Crop, FilterableItem, Item} from "../parts/item.js"
10
- import {filterTypes, FilterParams, FilterType} from "../parts/filters.js"
8
+ import {visualAnimations} from "../parts/animations.js"
9
+ import {filters, FilterParams, FilterType} from "../parts/filters.js"
10
+ import {Crop, FilterableItem, Item, VisualAnimatableItem} from "../parts/item.js"
11
+ import {Anim, AnimateAction, Interpolation, Keyframes, TrackTransform, Transform, Vec2, VisualAnimations} from "../types.js"
11
12
 
12
13
  export type Build<T extends Item.Any = Item.Any> = (o: O) => T
14
+ type BuildVisualAnimateActions = {
15
+ [TKey in keyof VisualAnimations]-?: BuildAnimateAction
16
+ }
13
17
 
14
18
  function createTimeline(): TimelineFile {
15
19
  return {
@@ -75,13 +79,61 @@ export function spatial(transform?: Transform, crop?: Crop): Build<Item.Spatial>
75
79
  return o => o.spatial(transform, crop)
76
80
  }
77
81
 
82
+ export function animatedSpatial(anim: Anim<TrackTransform>, crop?: Crop): Build<Item.AnimatedSpatial> {
83
+ return o => o.animatedSpatial(anim, crop)
84
+ }
85
+
86
+ export const anim = {
87
+ scalar(terp: Interpolation, track: Keyframes): Anim<Keyframes> {
88
+ return {terp, track}
89
+ },
90
+
91
+ vec2(terp: Interpolation, source: Keyframes<Vec2>): Anim<{x: Keyframes, y: Keyframes}> {
92
+ const track = {x: [] as Keyframes, y: [] as Keyframes}
93
+
94
+ for (const [time, [x, y]] of source) {
95
+ track.x.push([time, x])
96
+ track.y.push([time, y])
97
+ }
98
+
99
+ return {terp, track}
100
+ },
101
+
102
+ transform(terp: Interpolation, source: Keyframes<Transform>): Anim<TrackTransform> {
103
+ const track: TrackTransform = {
104
+ position: {x: [], y: []},
105
+ scale: {x: [], y: []},
106
+ rotation: [],
107
+ }
108
+
109
+ for (const [time, [position, scale, rotation]] of source) {
110
+ track.position.x.push([time, position[0]])
111
+ track.position.y.push([time, position[1]])
112
+ track.scale.x.push([time, scale[0]])
113
+ track.scale.y.push([time, scale[1]])
114
+ track.rotation.push([time, rotation])
115
+ }
116
+
117
+ return {terp, track}
118
+ },
119
+ }
120
+
78
121
  interface BuildFilterAction<TFilter extends FilterType> {
79
122
  <T extends FilterableItem>(item: Build<T>, params?: FilterParams<TFilter>): Build<T>
80
123
  make(params?: FilterParams<TFilter>): Build<Item.Filter<TFilter>>
81
124
  }
82
125
 
126
+ interface BuildAnimateAction {
127
+ <T extends VisualAnimatableItem>(
128
+ item: Build<T>,
129
+ terp: Interpolation,
130
+ track: Keyframes
131
+ ): Build<T>
132
+ make(terp: Interpolation, track: Keyframes): Build<Item.Animation>
133
+ }
134
+
83
135
  type BuildFilterActions = {
84
- [TName in keyof typeof filterTypes]: BuildFilterAction<(typeof filterTypes)[TName]["type"]>
136
+ [TName in keyof typeof filters]: BuildFilterAction<(typeof filters)[TName]["type"]>
85
137
  }
86
138
 
87
139
  function makeFilter<TFilter extends FilterType>(
@@ -96,7 +148,7 @@ function makeFilter<TFilter extends FilterType>(
96
148
  }
97
149
 
98
150
  function makeFilters(): BuildFilterActions {
99
- const names = Object.keys(filterTypes) as (keyof typeof filterTypes)[]
151
+ const names = Object.keys(filters) as (keyof typeof filters)[]
100
152
  const entries = names.map(name => [
101
153
  name,
102
154
  makeFilter(o => o.filter[name] as FilterAction<any>)
@@ -106,6 +158,26 @@ function makeFilters(): BuildFilterActions {
106
158
 
107
159
  export const filter = makeFilters()
108
160
 
161
+ function makeAnimate(
162
+ get: (o: O) => AnimateAction
163
+ ): BuildAnimateAction {
164
+ const action = (<T extends VisualAnimatableItem>(
165
+ item: Build<T>,
166
+ terp: Interpolation,
167
+ track: Keyframes
168
+ ): Build<T> => o => get(o)(item(o), terp, track)) as BuildAnimateAction
169
+ action.make = (terp: Interpolation, track: Keyframes) => o => get(o).make(terp, track)
170
+ return action
171
+ }
172
+
173
+ function makeAnimateActions(): BuildVisualAnimateActions {
174
+ const entries = Object.keys(visualAnimations)
175
+ .map(key => [key, makeAnimate(o => o.animate[key as keyof VisualAnimations] as AnimateAction)])
176
+ return Object.fromEntries(entries) as BuildVisualAnimateActions
177
+ }
178
+
179
+ export const animate = makeAnimateActions()
180
+
109
181
  export function textStyle(style: TextStyleOptions): Build<Item.TextStyle> {
110
182
  return o => o.textStyle(style)
111
183
  }
@@ -3,10 +3,15 @@ import {TextStyleOptions} from "pixi.js"
3
3
 
4
4
  import {Media} from "../parts/media.js"
5
5
  import {Id, TimelineFile} from "../parts/basics.js"
6
- import {Transform, TransformOptions, Vec2} from "../types.js"
7
6
  import {FilterAction, FilterActions} from "../parts/filters.js"
8
- import {Crop, Effect, FilterableItem, Item, Kind} from "../parts/item.js"
9
- import {filterTypes, FilterParams, FilterType} from "../parts/filters.js"
7
+ import {filters, FilterParams, FilterType} from "../parts/filters.js"
8
+ import {visualAnimations} from "../parts/animations.js"
9
+ import {Crop, Effect, FilterableItem, Item, Kind, VisualAnimatableItem} from "../parts/item.js"
10
+ import {Anim, AnimateAction, Interpolation, Keyframes, TrackTransform, Transform, TransformOptions, Vec2, VisualAnimations} from "../types.js"
11
+
12
+ type VisualAnimateActions = {
13
+ [TKey in keyof VisualAnimations]-?: AnimateAction
14
+ }
10
15
 
11
16
  export class O {
12
17
  constructor(public state: {timeline: TimelineFile}) {}
@@ -59,6 +64,62 @@ export class O {
59
64
  return item
60
65
  }
61
66
 
67
+ animatedSpatial = (anim: Anim<TrackTransform>, crop?: Crop): Item.AnimatedSpatial => {
68
+ const item: Item.AnimatedSpatial = {
69
+ id: this.getId(),
70
+ kind: Kind.AnimatedSpatial,
71
+ anim,
72
+ crop,
73
+ enabled: true
74
+ }
75
+ this.register(item)
76
+ return item
77
+ }
78
+
79
+ #registerAnimation = (anims: VisualAnimations): Item.Animation => {
80
+ const item: Item.Animation = {
81
+ id: this.getId(),
82
+ kind: Kind.Animation,
83
+ anims,
84
+ enabled: true
85
+ }
86
+ this.register(item)
87
+ return item
88
+ }
89
+
90
+ anim = {
91
+ scalar: (terp: Interpolation, track: Keyframes): Anim<Keyframes> => ({terp, track}),
92
+
93
+ vec2: (terp: Interpolation, source: Keyframes<Vec2>) => {
94
+ const track = {x: [] as Keyframes, y: [] as Keyframes}
95
+
96
+ for (const [time, [x, y]] of source) {
97
+ track.x.push([time, x])
98
+ track.y.push([time, y])
99
+ }
100
+
101
+ return {terp, track}
102
+ },
103
+
104
+ transform: (terp: Interpolation, source: Keyframes<Transform>): Anim<TrackTransform> => {
105
+ const track: TrackTransform = {
106
+ position: {x: [], y: []},
107
+ scale: {x: [], y: []},
108
+ rotation: [],
109
+ }
110
+
111
+ for (const [time, [position, scale, rotation]] of source) {
112
+ track.position.x.push([time, position[0]])
113
+ track.position.y.push([time, position[1]])
114
+ track.scale.x.push([time, scale[0]])
115
+ track.scale.y.push([time, scale[1]])
116
+ track.rotation.push([time, rotation])
117
+ }
118
+
119
+ return {terp, track}
120
+ },
121
+ }
122
+
62
123
  #makeFilter = <TFilter extends FilterType>(type: TFilter) => {
63
124
  const make = (params?: FilterParams<TFilter>) => {
64
125
  const item: Item.Filter<TFilter> = {
@@ -87,13 +148,45 @@ export class O {
87
148
  }
88
149
 
89
150
  #makeFilters = (): FilterActions => {
90
- const entries = Object.entries(filterTypes)
151
+ const entries = Object.entries(filters)
91
152
  .map(([name, filter]) => [name, this.#makeFilter(filter.type)])
92
153
  return Object.fromEntries(entries) as FilterActions
93
154
  }
94
155
 
95
156
  filter = this.#makeFilters()
96
157
 
158
+ #makeAnimate = <TKey extends keyof VisualAnimations>(key: TKey): AnimateAction => {
159
+ const make = (terp: Interpolation, track: Keyframes) =>
160
+ this.#registerAnimation({
161
+ [key]: this.anim.scalar(terp, track)
162
+ } as Pick<VisualAnimations, TKey>)
163
+
164
+ const action = (<T extends VisualAnimatableItem>(
165
+ item: T,
166
+ terp: Interpolation,
167
+ track: Keyframes
168
+ ): T => {
169
+ const animation = make(terp, track)
170
+ const next = {
171
+ ...item,
172
+ animationId: animation.id
173
+ }
174
+ this.set<T>(item.id, next as Partial<T>)
175
+ return next
176
+ }) as AnimateAction
177
+
178
+ action.make = make
179
+ return action
180
+ }
181
+
182
+ #makeAnimateActions = (): VisualAnimateActions => {
183
+ const entries = Object.keys(visualAnimations)
184
+ .map(key => [key, this.#makeAnimate(key as keyof VisualAnimations)])
185
+ return Object.fromEntries(entries) as VisualAnimateActions
186
+ }
187
+
188
+ animate = this.#makeAnimateActions()
189
+
97
190
  sequence = (...items: Item.Any[]): Item.Sequence => {
98
191
  const item = {
99
192
  id: this.getId(),
@@ -1,5 +1,13 @@
1
+ import {Item, VisualAnimatableItem} from "./parts/item.js"
2
+
3
+ export type Interpolation =
4
+ | "linear"
5
+ | "ease"
6
+ | "easeIn"
7
+ | "easeOut"
8
+ | "bounce"
9
+ | "catmullRom"
1
10
 
2
- export type Interpolation = "linear" | "catmullRom"
3
11
  export type Keyframe<Value = number> = [time: number, value: Value]
4
12
  export type Keyframes<Value = number> = Keyframe<Value>[]
5
13
  export type Vec2 = [x: number, y: number]
@@ -15,14 +23,29 @@ export type Anim<T> = {
15
23
  track: T
16
24
  }
17
25
 
18
- export type Animations = Anim<TrackTransform>
19
-
20
26
  export type TrackTransform = {
21
27
  position: TrackVec2
22
28
  scale: TrackVec2
23
29
  rotation: Keyframes
24
30
  }
25
31
 
32
+ export type ScalarAnimation = Anim<Keyframes>
33
+ export type Vec2Animation = Anim<TrackVec2>
34
+ export type TransformAnimation = Anim<TrackTransform>
35
+ export type VisualAnimations = {
36
+ opacity?: ScalarAnimation
37
+ }
38
+ export interface AnimateAction {
39
+ <T extends VisualAnimatableItem>(
40
+ item: T,
41
+ terp: Interpolation,
42
+ track: Keyframes
43
+ ): T
44
+ make(terp: Interpolation, track: Keyframes): Item.Animation
45
+ }
46
+
47
+ // export type Animations = Anim<TrackTransform>
48
+
26
49
  export type TransformOptions = {
27
50
  position?: Vec2
28
51
  scale?: Vec2
@@ -0,0 +1,71 @@
1
+
2
+ import {resolveTerp} from "./terps.js"
3
+ import {Item, Kind} from "../parts/item.js"
4
+ import {Anim, Keyframes, ScalarAnimation, TrackTransform, Transform} from "../types.js"
5
+
6
+ const resolveScalar =(
7
+ time: number,
8
+ keys: Keyframes,
9
+ terp: Anim<Keyframes>["terp"],
10
+ ): number => {
11
+ if (keys.length === 0)
12
+ return 0
13
+
14
+ if (keys.length === 1)
15
+ return keys[0][1]
16
+
17
+ const sorted = [...keys].sort((a, b) => a[0] - b[0])
18
+
19
+ if (time <= sorted[0][0])
20
+ return sorted[0][1]
21
+
22
+ const last = sorted[sorted.length - 1]
23
+ if (time >= last[0])
24
+ return last[1]
25
+
26
+ let index = 0
27
+ for (let i = 0; i < sorted.length - 1; i++) {
28
+ const a = sorted[i]
29
+ const b = sorted[i + 1]
30
+ if (time >= a[0] && time <= b[0]) {
31
+ index = i
32
+ break
33
+ }
34
+ }
35
+
36
+ const [t2] = sorted[index]
37
+ const [t3] = sorted[index + 1]
38
+ const span = t3 - t2
39
+ const x = span === 0 ? 0 : (time - t2) / span
40
+
41
+ return resolveTerp(terp, x, sorted, index)
42
+ }
43
+
44
+ export const resolveTransformAnimation =(
45
+ time: number,
46
+ anim: Anim<TrackTransform>,
47
+ ): Transform => ([
48
+ [
49
+ resolveScalar(time, anim.track.position.x, anim.terp),
50
+ resolveScalar(time, anim.track.position.y, anim.terp),
51
+ ],
52
+ [
53
+ resolveScalar(time, anim.track.scale.x, anim.terp),
54
+ resolveScalar(time, anim.track.scale.y, anim.terp),
55
+ ],
56
+ resolveScalar(time, anim.track.rotation, anim.terp),
57
+ ])
58
+
59
+ export const resolveScalarAnimation =(
60
+ time: number,
61
+ anim: ScalarAnimation,
62
+ ): number => resolveScalar(time, anim.track, anim.terp)
63
+
64
+ export const resolveSpatialTransform =(
65
+ spatial: Item.Spatial | Item.AnimatedSpatial,
66
+ time: number,
67
+ ): Transform =>
68
+ spatial.kind === Kind.AnimatedSpatial
69
+ ? resolveTransformAnimation(time, spatial.anim)
70
+ : spatial.transform
71
+
@@ -0,0 +1,81 @@
1
+
2
+ import {Interpolation, Keyframes} from "../types.js"
3
+
4
+ type EasingFn = (x: number, a: number, b: number) => number
5
+
6
+ class TerpKeys {
7
+ constructor(
8
+ private keys: Keyframes,
9
+ private index: number,
10
+ ) {}
11
+
12
+ near(offset: number): number {
13
+ const next = this.keys[Math.max(0, Math.min(this.keys.length - 1, this.index + offset))]
14
+ return next[1]
15
+ }
16
+ }
17
+
18
+ type TerpFn = (x: number, keys: TerpKeys) => number
19
+
20
+ const asTerp = (fn: TerpFn): TerpFn => fn
21
+
22
+ const fromEasingFn = (easing: EasingFn): TerpFn =>
23
+ asTerp((x, keys) => easing(x, keys.near(0), keys.near(1)))
24
+
25
+ const lerp = (x: number, a: number, b: number) => a + (b - a) * x
26
+
27
+ export const terps: Record<Interpolation, TerpFn> = {
28
+ linear: fromEasingFn(lerp),
29
+
30
+ ease: fromEasingFn((x, a, b) => {
31
+ const eased = x < 0.5
32
+ ? 4 * x * x * x
33
+ : 1 - Math.pow(-2 * x + 2, 3) / 2
34
+ return lerp(eased, a, b)
35
+ }),
36
+
37
+ easeIn: fromEasingFn((x, a, b) => lerp(x * x * x, a, b)),
38
+
39
+ easeOut: fromEasingFn((x, a, b) => lerp(1 - Math.pow(1 - x, 3), a, b)),
40
+
41
+ bounce: fromEasingFn((x, a, b) => {
42
+ const n1 = 7.5625
43
+ const d1 = 2.75
44
+ const eased = x < 1 / d1
45
+ ? n1 * x * x
46
+ : x < 2 / d1
47
+ ? n1 * (x - 1.5 / d1) ** 2 + 0.75
48
+ : x < 2.5 / d1
49
+ ? n1 * (x - 2.25 / d1) ** 2 + 0.9375
50
+ : n1 * (x - 2.625 / d1) ** 2 + 0.984375
51
+ return lerp(eased, a, b)
52
+ }),
53
+
54
+ catmullRom: asTerp((x, keys) => {
55
+ const p1 = keys.near(-1)
56
+ const p2 = keys.near(0)
57
+ const p3 = keys.near(1)
58
+ const p4 = keys.near(2)
59
+ const x2 = x * x
60
+ const x3 = x2 * x
61
+ return 0.5 * (
62
+ (2 * p2) +
63
+ (-p1 + p3) * x +
64
+ (2 * p1 - 5 * p2 + 4 * p3 - p4) * x2 +
65
+ (-p1 + 3 * p2 - 3 * p3 + p4) * x3
66
+ )
67
+ }),
68
+ }
69
+
70
+ export const resolveTerp =(
71
+ terp: Interpolation,
72
+ x: number,
73
+ keys: Keyframes,
74
+ index: number,
75
+ ): number => {
76
+ const fn = terps[terp]
77
+ if (!fn)
78
+ throw new Error(`unknown terp "${terp}"`)
79
+
80
+ return fn(x, new TerpKeys(keys, index))
81
+ }