1pub mod expr;
10mod vars;
11
12use std::f64::consts::PI;
13use std::ops::Range;
14
15use serde_json::{Map, Value};
16
17use crate::{Error, Result};
18pub use expr::{Compiled, ExprError};
19pub use vars::Vars;
20
21#[derive(Debug, Clone, Copy, PartialEq, Default)]
23pub enum Ease {
24 #[default]
25 Linear,
26 Hold,
28 Ease,
30 In,
32 Out,
34 InOut,
36 Back,
38 Smoothstep,
40 Bezier([f64; 4]),
42}
43
44impl Ease {
45 pub fn from_name(s: &str) -> Option<Self> {
48 Some(match s {
49 "linear" => Self::Linear,
50 "hold" => Self::Hold,
51 "ease" => Self::Ease,
52 "in" | "ease_in" => Self::In,
53 "out" | "ease_out" => Self::Out,
54 "in_out" | "ease_in_out" => Self::InOut,
55 "back" | "overshoot" => Self::Back,
56 "smoothstep" => Self::Smoothstep,
57 _ => return None,
58 })
59 }
60
61 pub fn parse(v: &Value) -> Option<Self> {
63 if let Some(a) = v.as_array() {
64 let c: Vec<f64> = a.iter().filter_map(Value::as_f64).collect();
65 let unit = |x: f64| (0.0..=1.0).contains(&x);
66 return match c[..] {
67 [x1, y1, x2, y2] if c.len() == a.len() && unit(x1) && unit(x2) => {
68 Some(Self::Bezier([x1, y1, x2, y2]))
69 }
70 _ => None,
71 };
72 }
73 Self::from_name(v.as_str()?)
74 }
75
76 pub fn at(self, u: f64) -> f64 {
78 let u = u.clamp(0.0, 1.0);
79 match self {
80 Self::Linear => u,
81 Self::Hold => {
82 if u >= 1.0 {
83 1.0
84 } else {
85 0.0
86 }
87 }
88 Self::Ease => 0.5 - 0.5 * (PI * u).cos(),
89 Self::In => u * u * u,
90 Self::Out => 1.0 - (1.0 - u).powi(3),
91 Self::InOut if u < 0.5 => 4.0 * u * u * u,
92 Self::InOut => 1.0 - (-2.0 * u + 2.0).powi(3) / 2.0,
93 Self::Back => {
94 let (c1, c3) = (1.701_58, 2.701_58);
95 1.0 + c3 * (u - 1.0).powi(3) + c1 * (u - 1.0).powi(2)
96 }
97 Self::Smoothstep => u * u * (3.0 - 2.0 * u),
98 Self::Bezier(c) => bezier(c, u),
99 }
100 }
101}
102
103fn bezier([x1, y1, x2, y2]: [f64; 4], x: f64) -> f64 {
106 let (x1, x2) = (x1.clamp(0.0, 1.0), x2.clamp(0.0, 1.0));
107 let curve = |a: f64, b: f64, t: f64| {
108 3.0 * a * t * (1.0 - t).powi(2) + 3.0 * b * t * t * (1.0 - t) + t.powi(3)
109 };
110 let (mut lo, mut hi, mut t) = (0.0, 1.0, x);
111 for _ in 0..32 {
112 let v = curve(x1, x2, t) - x;
113 if v.abs() < 1e-7 {
114 break;
115 }
116 if v > 0.0 {
117 hi = t;
118 } else {
119 lo = t;
120 }
121 t = f64::midpoint(lo, hi);
122 }
123 curve(y1, y2, t)
124}
125
126pub trait Animatable: Copy + std::fmt::Debug + PartialEq {
128 const N: usize;
130 fn lerp(a: Self, b: Self, k: f64) -> Self;
131 fn component(self, i: usize) -> f64;
132 fn from_fn(f: impl FnMut(usize) -> f64) -> Self;
133
134 fn from_json(v: &Value) -> Option<Self> {
136 match v {
137 Value::Number(n) if Self::N == 1 => n.as_f64().map(|x| Self::from_fn(|_| x)),
138 Value::Array(a) if a.len() == Self::N => {
139 let c: Vec<f64> = a.iter().map(Value::as_f64).collect::<Option<_>>()?;
140 Some(Self::from_fn(|i| c[i]))
141 }
142 Value::String(s) if Self::N == 3 || Self::N == 4 => {
143 colour(s).map(|c| Self::from_fn(|i| c[i]))
144 }
145 _ => None,
146 }
147 }
148}
149
150impl Animatable for f64 {
151 const N: usize = 1;
152 fn lerp(a: Self, b: Self, k: f64) -> Self {
153 a + (b - a) * k
154 }
155 fn component(self, _: usize) -> f64 {
156 self
157 }
158 fn from_fn(mut f: impl FnMut(usize) -> f64) -> Self {
159 f(0)
160 }
161}
162
163impl<const N: usize> Animatable for [f64; N] {
164 const N: usize = N;
165 fn lerp(a: Self, b: Self, k: f64) -> Self {
166 std::array::from_fn(|i| a[i] + (b[i] - a[i]) * k)
167 }
168 fn component(self, i: usize) -> f64 {
169 self[i]
170 }
171 fn from_fn(f: impl FnMut(usize) -> f64) -> Self {
172 std::array::from_fn(f)
173 }
174}
175
176pub fn colour(s: &str) -> Option<[f64; 4]> {
178 match s {
179 "black" => return Some([0.0, 0.0, 0.0, 1.0]),
180 "white" => return Some([1.0; 4]),
181 _ => {}
182 }
183 let h = s.strip_prefix('#')?;
184 if !(h.len() == 6 || h.len() == 8) || !h.is_ascii() {
185 return None;
186 }
187 let byte = |i: usize| {
188 u8::from_str_radix(&h[i..i + 2], 16)
189 .ok()
190 .map(|b| f64::from(b) / 255.0)
191 };
192 Some([
193 byte(0)?,
194 byte(2)?,
195 byte(4)?,
196 if h.len() == 8 { byte(6)? } else { 1.0 },
197 ])
198}
199
200#[derive(Debug, Clone, Copy, PartialEq)]
203pub struct Key<T> {
204 pub t: f64,
205 pub v: T,
206 pub ease: Ease,
207}
208
209pub fn keys_at<T: Animatable>(keys: &[Key<T>], t: f64) -> Option<T> {
211 let first = keys.first()?;
212 if t.is_nan() || t <= first.t {
213 return Some(first.v);
214 }
215 let i = keys.partition_point(|k| k.t <= t) - 1;
217 let a = &keys[i];
218 let Some(b) = keys.get(i + 1) else {
219 return Some(a.v);
220 };
221 let span = b.t - a.t;
222 if span <= 0.0 {
223 return Some(b.v);
224 }
225 Some(T::lerp(a.v, b.v, a.ease.at((t - a.t) / span)))
226}
227
228#[derive(Debug, Clone, Copy, PartialEq, Eq)]
230pub enum Clock {
231 Source,
234 Clip,
237 Layer,
240}
241
242macro_rules! builtins {
243 ($($v:ident $name:literal,)*) => {
244 #[derive(Debug, Clone, Copy, PartialEq, Eq)]
246 #[repr(u16)]
247 pub enum Builtin { $($v,)* }
248 impl Builtin {
249 pub const ALL: &[Builtin] = &[$(Self::$v,)*];
250 pub const COUNT: usize = Self::ALL.len();
251 pub fn name(self) -> &'static str {
252 match self { $(Self::$v => $name,)* }
253 }
254 pub fn from_name(s: &str) -> Option<Self> {
255 match s { $($name => Some(Self::$v),)* _ => None }
256 }
257 }
258 };
259}
260
261builtins! {
262 T "t", Lt "lt", St "st", N "n", Ln "ln", Fps "fps", CanvasW "W", CanvasH "H",
263 W "w", H "h", Sw "sw", Sh "sh", In "in", Out "out", Dur "dur", Total "T", Value "value",
264}
265
266impl Builtin {
267 fn on(self, clock: Clock) -> bool {
269 match clock {
270 Clock::Layer => true,
271 Clock::Source => self == Self::St,
272 Clock::Clip => matches!(self, Self::Lt | Self::Dur),
273 }
274 }
275}
276
277#[derive(Debug, Clone, Copy)]
280pub struct Scope<'a> {
281 pub clock: Clock,
282 pub value: bool,
283 pub vars: Option<&'a Vars>,
284}
285
286impl<'a> Scope<'a> {
287 pub fn new(clock: Clock) -> Self {
288 Self {
289 clock,
290 value: false,
291 vars: None,
292 }
293 }
294
295 pub fn with_vars(clock: Clock, vars: &'a Vars) -> Self {
296 Self {
297 clock,
298 value: false,
299 vars: Some(vars),
300 }
301 }
302
303 pub fn slot(&self, name: &str) -> Option<u16> {
305 #[allow(clippy::cast_possible_truncation)]
306 if let Some(b) = Builtin::from_name(name) {
307 let ok = if b == Builtin::Value {
308 self.value
309 } else {
310 b.on(self.clock)
311 };
312 return ok.then_some(b as u16);
313 }
314 #[allow(clippy::cast_possible_truncation)]
315 self.vars?.slot(name).map(|i| (Builtin::COUNT + i) as u16)
316 }
317
318 pub(crate) fn unknown(&self, name: &str) -> String {
319 match Builtin::from_name(name) {
320 Some(Builtin::Value) => {
321 "`value` is only defined in the {\"keys\", \"expr\"} form".into()
322 }
323 Some(_) => format!(
324 "`{name}` isn't known on this parameter's clock ({:?})",
325 self.clock
326 ),
327 None => format!("unknown variable `{name}`"),
328 }
329 }
330}
331
332#[derive(Debug, Clone)]
334pub struct Frame<'a> {
335 pub b: [f64; Builtin::COUNT],
336 pub vars: &'a [f64],
337}
338
339impl Frame<'_> {
340 pub fn at(clock: Clock, t: f64, dur: f64) -> Frame<'static> {
343 let mut b = [0.0; Builtin::COUNT];
344 match clock {
345 Clock::Source => b[Builtin::St as usize] = t,
346 Clock::Clip | Clock::Layer => b[Builtin::Lt as usize] = t,
347 }
348 b[Builtin::Dur as usize] = dur;
349 Frame { b, vars: &[] }
350 }
351
352 pub fn get(&self, slot: u16) -> f64 {
353 let s = usize::from(slot);
354 match self.b.get(s) {
355 Some(v) => *v,
356 None => self
357 .vars
358 .get(s - Builtin::COUNT)
359 .copied()
360 .unwrap_or(f64::NAN),
361 }
362 }
363
364 pub fn var(&self, b: Builtin) -> f64 {
365 self.b[b as usize]
366 }
367
368 fn key_time(&self, clock: Clock) -> f64 {
370 match clock {
371 Clock::Source => self.var(Builtin::St),
372 Clock::Clip | Clock::Layer => self.var(Builtin::Lt),
373 }
374 }
375}
376
377#[derive(Debug, Clone, Copy, Default)]
380pub struct Env<'a> {
381 pub fps: f64,
382 pub canvas: [f64; 2],
384 pub size: [f64; 2],
386 pub source: [f64; 2],
388 pub in_: f64,
389 pub out: f64,
390 pub total: f64,
392 pub source_in: f64,
393 pub vars: Option<&'a Vars>,
394}
395
396impl Env<'_> {
397 #[allow(clippy::cast_precision_loss)]
399 pub fn builtins(&self, n: i64) -> [f64; Builtin::COUNT] {
400 let fps = self.rate();
401 self.fill(n as f64 / fps, n as f64)
402 }
403
404 pub fn builtins_at(&self, t: f64) -> [f64; Builtin::COUNT] {
407 self.fill(t, t * self.rate())
408 }
409
410 fn rate(&self) -> f64 {
411 if self.fps > 0.0 { self.fps } else { 24.0 }
412 }
413
414 fn fill(&self, t: f64, n: f64) -> [f64; Builtin::COUNT] {
415 let fps = self.rate();
416 let lt = t - self.in_;
417 let mut b = [0.0; Builtin::COUNT];
418 for (k, v) in [
419 (Builtin::T, t),
420 (Builtin::Lt, lt),
421 (Builtin::St, self.source_in + lt),
422 (Builtin::N, n),
423 (Builtin::Ln, n - (self.in_ * fps).round()),
424 (Builtin::Fps, fps),
425 (Builtin::CanvasW, self.canvas[0]),
426 (Builtin::CanvasH, self.canvas[1]),
427 (Builtin::W, self.size[0]),
428 (Builtin::H, self.size[1]),
429 (Builtin::Sw, self.source[0]),
430 (Builtin::Sh, self.source[1]),
431 (Builtin::In, self.in_),
432 (Builtin::Out, self.out),
433 (Builtin::Dur, self.out - self.in_),
434 (Builtin::Total, self.total),
435 ] {
436 b[k as usize] = v;
437 }
438 b
439 }
440}
441
442#[derive(Debug, Clone, PartialEq)]
445pub enum Track<T> {
446 Const(T),
447 Keys(Vec<Key<T>>),
449 Expr(Vec<Compiled>),
451 KeysExpr(Vec<Key<T>>, Vec<Compiled>),
453 Sampled(Vec<T>),
455}
456
457impl<T: Default> Default for Track<T> {
458 fn default() -> Self {
459 Self::Const(T::default())
460 }
461}
462
463pub fn is_keys(v: &Value) -> bool {
465 v.as_array()
466 .and_then(|a| a.first())
467 .is_some_and(|k| k.get("t").is_some() && k.get("v").is_some())
468}
469
470pub fn seconds(v: &Value) -> Option<f64> {
472 if let Some(s) = v.as_f64() {
473 return Some(s);
474 }
475 let rate = v.get("rate")?.as_f64()?;
476 let value = v.get("value")?.as_f64()?;
477 (rate > 0.0).then(|| value / rate)
478}
479
480fn invalid(what: &str, v: &Value) -> Error {
481 Error::Track {
482 path: what.to_owned(),
483 msg: format!("invalid value {v}"),
484 }
485}
486
487fn compile(src: &str, scope: &Scope, what: &str) -> Result<Compiled> {
488 Compiled::new(src, scope).map_err(|e| Error::Expr {
489 path: what.to_owned(),
490 col: e.col,
491 msg: e.msg,
492 src: src.to_owned(),
493 })
494}
495
496fn sorted<T>(mut keys: Vec<Key<T>>) -> Vec<Key<T>> {
498 keys.sort_by(|a, b| a.t.total_cmp(&b.t));
499 keys
500}
501
502impl<T: Animatable> Track<T> {
503 pub fn parse(v: &Value, clock: Clock, what: &str) -> Result<Self> {
506 Self::parse_in(v, &Scope::new(clock), what)
507 }
508
509 pub fn parse_in(v: &Value, scope: &Scope, what: &str) -> Result<Self> {
511 if let Some(c) = T::from_json(v) {
512 return Ok(Self::Const(c));
513 }
514 if is_keys(v) {
515 return Ok(Self::Keys(Self::parse_keys(v, what)?));
516 }
517 let exprs = match v {
518 Value::String(s) if T::N == 1 => vec![compile(s, scope, what)?],
519 Value::Array(a) if a.len() == T::N => a
520 .iter()
521 .enumerate()
522 .map(|(i, c)| match c {
523 Value::Number(n) => n
524 .as_f64()
525 .map(Compiled::constant)
526 .ok_or_else(|| invalid(what, v)),
527 Value::String(s) => compile(s, scope, &format!("{what}[{i}]")),
528 _ => Err(invalid(what, v)),
529 })
530 .collect::<Result<_>>()?,
531 Value::Object(o) if o.contains_key("keys") && o.contains_key("expr") => {
532 return Self::parse_keys_expr(o, scope, what);
533 }
534 _ => return Err(invalid(what, v)),
535 };
536 Ok(Self::from_exprs(exprs))
537 }
538
539 fn from_exprs(exprs: Vec<Compiled>) -> Self {
540 match exprs
541 .iter()
542 .map(Compiled::as_const)
543 .collect::<Option<Vec<_>>>()
544 {
545 Some(c) => Self::Const(T::from_fn(|i| c[i])),
546 None => Self::Expr(exprs),
547 }
548 }
549
550 fn parse_keys_expr(o: &Map<String, Value>, scope: &Scope, what: &str) -> Result<Self> {
551 let kv = &o["keys"];
552 if !is_keys(kv) {
553 return Err(invalid(&format!("{what}.keys"), kv));
554 }
555 let keys = Self::parse_keys(kv, what)?;
556 let s = Scope {
557 value: true,
558 ..*scope
559 };
560 let what = format!("{what}.expr");
561 let exprs = match &o["expr"] {
562 Value::String(e) => {
563 let c = compile(e, &s, &what)?;
564 vec![c; T::N]
565 }
566 Value::Array(a) if a.len() == T::N => a
567 .iter()
568 .enumerate()
569 .map(|(i, e)| match e {
570 Value::String(e) => compile(e, &s, &format!("{what}[{i}]")),
571 Value::Number(n) => n
572 .as_f64()
573 .map(Compiled::constant)
574 .ok_or_else(|| invalid(&what, e)),
575 _ => Err(invalid(&what, e)),
576 })
577 .collect::<Result<_>>()?,
578 e => return Err(invalid(&what, e)),
579 };
580 Ok(Self::KeysExpr(keys, exprs))
581 }
582
583 fn parse_keys(v: &Value, what: &str) -> Result<Vec<Key<T>>> {
584 let mut keys = Vec::new();
585 for k in v.as_array().into_iter().flatten() {
586 let ease = match k.get("interp").or_else(|| k.get("ease")) {
587 None => Ease::Linear,
588 Some(e) => Ease::parse(e).ok_or_else(|| invalid(what, v))?,
589 };
590 keys.push(Key {
591 t: k.get("t")
592 .and_then(seconds)
593 .ok_or_else(|| invalid(what, v))?,
594 v: k.get("v")
595 .and_then(T::from_json)
596 .ok_or_else(|| invalid(what, v))?,
597 ease,
598 });
599 }
600 Ok(sorted(keys))
601 }
602
603 pub fn from_keys(keys: Vec<Key<T>>) -> Self {
605 Self::Keys(sorted(keys))
606 }
607
608 pub fn is_animated(&self) -> bool {
610 match self {
611 Self::Const(_) => false,
612 Self::Keys(k) => k.len() > 1,
613 Self::Sampled(s) => s.windows(2).any(|w| w[0] != w[1]),
614 Self::Expr(..) | Self::KeysExpr(..) => true,
615 }
616 }
617
618 pub fn keys(&self) -> Option<&[Key<T>]> {
620 match self {
621 Self::Keys(k) | Self::KeysExpr(k, ..) => Some(k),
622 _ => None,
623 }
624 }
625
626 fn keyed(keys: &[Key<T>], clock: Clock, f: &Frame) -> Option<T> {
627 let t = f.key_time(clock);
628 if clock == Clock::Source || keys.iter().all(|k| k.t >= 0.0) {
629 return keys_at(keys, t);
630 }
631 let dur = f.var(Builtin::Dur);
633 let k: Vec<Key<T>> = keys
634 .iter()
635 .map(|k| Key {
636 t: if k.t < 0.0 { dur + k.t } else { k.t },
637 ..*k
638 })
639 .collect();
640 keys_at(&sorted(k), t)
641 }
642
643 pub fn try_eval(&self, clock: Clock, frame: &Frame) -> Result<Option<T>> {
646 let finite = |v: f64, c: &Compiled| {
647 if v.is_finite() {
648 Ok(v)
649 } else {
650 Err(Error::Track {
651 path: c.source().to_owned(),
652 msg: format!(
653 "not finite ({v}) at t = {}, lt = {}, st = {}, n = {}",
654 frame.var(Builtin::T),
655 frame.var(Builtin::Lt),
656 frame.var(Builtin::St),
657 frame.var(Builtin::N)
658 ),
659 })
660 }
661 };
662 const { assert!(T::N <= 4, "tracks have at most 4 components") };
663 let comps = |e: &[Compiled], f: &Frame| -> Result<T> {
664 let mut out = [0.0; 4];
665 for (i, c) in e.iter().enumerate().take(4) {
666 out[i] = finite(c.eval(f), c)?;
667 }
668 Ok(T::from_fn(|i| out[i]))
669 };
670 Ok(match self {
671 Self::Const(v) => Some(*v),
672 Self::Keys(k) => Self::keyed(k, clock, frame),
673 Self::Expr(e) => Some(comps(e, frame)?),
674 Self::KeysExpr(k, e) => {
675 let Some(v) = Self::keyed(k, clock, frame) else {
676 return Ok(None);
677 };
678 let mut out = [0.0; 4];
679 for (i, c) in e.iter().enumerate().take(4) {
680 let mut f = frame.clone();
681 f.b[Builtin::Value as usize] = v.component(i);
682 out[i] = finite(c.eval(&f), c)?;
683 }
684 Some(T::from_fn(|i| out[i]))
685 }
686 #[allow(clippy::cast_possible_truncation, clippy::cast_sign_loss)]
687 Self::Sampled(s) => {
688 let n = frame.var(Builtin::N).max(0.0) as usize;
689 s.get(n).or(s.last()).copied()
690 }
691 })
692 }
693
694 pub fn eval(&self, frame: &Frame) -> Result<T> {
696 self.try_eval(Clock::Layer, frame)?
697 .ok_or_else(|| Error::Track {
698 path: String::new(),
699 msg: "no keys".into(),
700 })
701 }
702
703 pub fn at(&self, clock: Clock, t: f64, dur: f64) -> Option<T> {
706 self.try_eval(clock, &Frame::at(clock, t, dur))
707 .ok()
708 .flatten()
709 }
710
711 pub fn sample(&self, clock: Clock, frames: Range<i64>, env: &Env) -> Result<Vec<T>> {
714 let start = frames.start;
715 frames
716 .map(|n| {
717 let b = env.builtins(n);
718 let vars = match env.vars {
719 Some(v) => v.eval(&b)?,
720 None => Vec::new(),
721 };
722 let mut frame = Frame { b, vars: &vars };
723 #[allow(clippy::cast_precision_loss)]
724 if let Self::Sampled(_) = self {
725 frame.b[Builtin::N as usize] = (n - start) as f64;
726 }
727 self.try_eval(clock, &frame)?.ok_or_else(|| Error::Track {
728 path: String::new(),
729 msg: "no keys".into(),
730 })
731 })
732 .collect()
733 }
734}
735
736#[cfg(test)]
737mod tests;