/
tween.ts
114 lines (112 loc) · 2.88 KB
/
tween.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
import type { Fn2 } from "@thi.ng/api";
import { isNumber } from "@thi.ng/checks";
import { dedupe, reducer, scan } from "@thi.ng/transducers";
import { CloseMode, ISubscribable } from "./api";
import { fromInterval } from "./from/interval";
import { fromRAF } from "./from/raf";
import { sync } from "./stream-sync";
/**
* Takes an existing stream/subscription `src` and attaches new
* subscription which interpolates between incoming values from `src`
* using the given `mix` function.
*
* @remarks
* The returned construct produces values at a rate controlled by the
* `clock` stream or frequency. If omitted, `clock` defaults to
* {@link fromRAF} (~60Hz). If the `clock` is given as number, creates a
* {@link fromInterval} or else uses the given `clock` stream directly.
* In general, the frequency of the `clock` should always be higher than
* that of `src` or else interpolation will have undefined behavior.
*
* If `stop` is given as well, no values will be passed downstream if
* that function returns true. This can be used to limit traffic once
* the tween target value has been reached.
*
* The returned subscription closes automatically when either `src` or
* `clock` are exhausted.
*
* @example
* ```ts
* val = stream();
*
* tween(
* // consume from `val` stream
* val,
* // initial start value to interpolate from
* 0,
* // interpolation fn (LERP)
* (a, b) => a + (b - a) * 0.5,
* // stop emitting values if difference to previous result < 0.01
* (a, b) => Math.abs(a - b) < 0.01
* ).subscribe(trace("tweened"))
*
* a.next(10)
* // 5
* // 7.5
* // ...
* // 9.98046875
*
* a.next(100)
* // 55
* // 77.5
* // ...
* // 99.989013671875
* ```
*
* @param src -
* @param initial -
* @param mix -
* @param stop -
* @param clock -
*/
export const tween = <T>(
src: ISubscribable<T>,
initial: T,
mix: Fn2<T, T, T>,
stop?: Fn2<T, T, boolean>,
clock?: ISubscribable<any> | number
) =>
sync({
src: {
src,
_:
clock == null
? fromRAF()
: isNumber(clock)
? fromInterval(clock)
: clock,
},
closeIn: CloseMode.FIRST,
}).transform(
scan(
reducer(
() => initial,
(acc, { src }) => mix(acc, src)
)
),
dedupe(stop || (() => false))
);
/**
* Convenience version of {@link tween} for its most common use case, tweening
* of numeric streams.
*
* @param src -
* @param init -
* @param speed -
* @param eps -
* @param clock -
*/
export const tweenNumber = (
src: ISubscribable<number>,
init = 0,
speed = 0.05,
eps = 1e-3,
clock?: ISubscribable<any> | number
) =>
tween(
src,
init,
(a, b) => a + (b - a) * speed,
(a, b) => Math.abs(a - b) < eps,
clock
);