1pub mod build;
22pub mod compose;
24pub mod eval;
26pub mod node;
28pub mod sound;
30
31#[cfg(test)]
32mod tests {
33 use super::{
34 build::{IntoAnimNode, PlaybackExt, Unplaced},
35 compose::{AnimIterExt, lagged::LaggedFill, sequence::AnimSequence, stack::AnimStack},
36 eval::{Eval, EvalExt, Static, StaticAnim, pure::Pure},
37 node::{AnimNode, AnimationInfoKind, NodeContent},
38 };
39 use crate::{
40 Extract,
41 core_item::{CoreItem, DynItem, vitem::VItem},
42 lagged, seq, stack,
43 };
44
45 struct ShiftX(f32);
47
48 impl Eval for ShiftX {
49 type Output = VItem;
50
51 fn eval_alpha(&self, _alpha: f64) -> Self::Output {
52 let mut item = VItem::default();
53 item.points[0].x = self.0;
54 item
55 }
56 }
57
58 struct ShiftAlpha(f32);
60
61 impl Eval for ShiftAlpha {
62 type Output = VItem;
63
64 fn eval_alpha(&self, alpha: f64) -> Self::Output {
65 let mut item = VItem::default();
66 item.points[0].x = self.0 + alpha as f32;
67 item
68 }
69 }
70
71 fn leaf(x: f32, duration: f64) -> impl Unplaced {
72 ShiftX(x).with_duration(duration)
73 }
74
75 fn progress_leaf(offset: f32) -> impl Unplaced {
76 ShiftAlpha(offset)
77 }
78
79 fn evaluated_xs(items: Vec<DynItem>) -> Vec<f32> {
80 items
81 .into_iter()
82 .flat_map(|item| item.extract())
83 .filter_map(|item| match item {
84 CoreItem::VItem(item) => Some(item.points[0].x),
85 CoreItem::CameraFrame(_) | CoreItem::MeshItem(_) => None,
86 })
87 .collect()
88 }
89
90 fn sampled_xs(animation: &AnimNode, sec: f64) -> Vec<f32> {
91 let mut items = Vec::new();
92 animation.eval_at(sec, &mut items);
93 evaluated_xs(items)
94 }
95
96 #[test]
97 fn apply_alpha_to_writes_the_requested_progress_state() {
98 let mut item = VItem::default();
99
100 let animation = ShiftAlpha(0.0).apply_alpha_to(&mut item, 0.25);
101 assert_eq!(item.points[0].x, 0.25);
102
103 let animation = animation.apply_to(&mut item);
104 assert_eq!(item.points[0].x, 1.0);
105 assert_eq!(animation.eval_alpha(0.5).points[0].x, 0.5);
106 }
107
108 #[test]
109 fn pure_wraps_a_closure_into_eval() {
110 let mut item = VItem::default();
111 Pure::new(|alpha: f64| {
112 let mut item = VItem::default();
113 item.points[0].x = alpha as f32;
114 item
115 })
116 .apply_alpha_to(&mut item, 0.5);
117 assert_eq!(item.points[0].x, 0.5);
118 }
119
120 #[test]
121 fn containers_lower_children_to_expected_timelines() {
122 let animation = Static(VItem::default()).into_anim_node();
124 assert_eq!(animation.time_range(), 0.0..1.0);
125 let animation = Static(VItem::default()).with_duration(2.0).into_anim_node();
126 assert_eq!(animation.time_range(), 0.0..2.0);
127
128 let sequence = seq![leaf(1.0, 2.0), leaf(2.0, 3.0)];
131 assert_eq!(sequence.built_animations()[0].time_range(), 0.0..2.0);
132 assert_eq!(sequence.built_animations()[1].time_range(), 2.0..5.0);
133 let stack = stack![leaf(1.0, 2.0), leaf(2.0, 3.0).at(1.0)];
134 assert_eq!(stack.duration_secs(), 4.0);
135 assert_eq!(stack.built_animations()[0].time_range(), 0.0..2.0);
136 assert_eq!(stack.built_animations()[1].time_range(), 1.0..4.0);
137
138 let mut dynamic = AnimStack::new();
139 dynamic.push(leaf(1.0, 1.0)).push(leaf(2.0, 3.0));
140 assert_eq!(dynamic.duration_secs(), 3.0);
141 assert_eq!(dynamic.built_animations()[0].time_range(), 0.0..1.0);
142 assert_eq!(dynamic.built_animations()[1].time_range(), 0.0..3.0);
143
144 let stack: AnimStack = vec![leaf(1.0, 1.0), leaf(2.0, 2.0)].into_iter().collect();
146 assert_eq!(stack.duration_secs(), 2.0);
147 assert_eq!(stack.built_animations()[1].time_range(), 0.0..2.0);
148 let sequence: AnimSequence = vec![leaf(1.0, 1.0), leaf(2.0, 2.0)].into_iter().collect();
149 assert_eq!(sequence.duration_secs(), 3.0);
150 assert_eq!(sequence.built_animations()[1].time_range(), 1.0..3.0);
151 let lagged = vec![leaf(1.0, 1.0), leaf(2.0, 1.0)]
152 .into_iter()
153 .into_lagged(0.5);
154 assert_eq!(lagged.built_animations()[1].time_range(), 0.5..1.5);
155 }
156
157 #[test]
158 fn offsets_shift_the_whole_container_timeline() {
159 let animation = leaf(1.0, 2.0).at(3.0).into_anim_node();
160 assert_eq!(animation.time_range(), 3.0..5.0);
161
162 let sequence = seq![leaf(1.0, 2.0), leaf(2.0, 3.0)];
163 assert_eq!(sequence.at(5.0).into_anim_node().time_range(), 5.0..10.0);
164
165 let stack = stack![leaf(1.0, 2.0), leaf(2.0, 3.0).at(1.0)];
166 assert_eq!(stack.at(10.0).into_anim_node().time_range(), 10.0..14.0);
167
168 let mut sequence = AnimSequence::new();
170 sequence
171 .push(leaf(1.0, 2.0))
172 .forward(1.0)
173 .push(leaf(2.0, 1.0));
174 let info = sequence.at(10.0).into_anim_node().animation_info();
175 assert_eq!(info.range, 10.0..14.0);
176 assert_eq!(info.children[0].range, 0.0..2.0);
177 assert_eq!(info.children[1].range, 3.0..4.0);
178 }
179
180 #[test]
181 fn parametrized_sequence_remaps_the_group_timeline() {
182 use crate::utils::rate_functions::ease_in_quad;
183
184 let animation = seq![progress_leaf(0.0), progress_leaf(10.0)]
185 .with_duration(4.0)
186 .with_rate_func(ease_in_quad);
187 let animation = animation.into_anim_node();
188
189 assert_eq!(animation.time_range(), 0.0..4.0);
190 assert_eq!(sampled_xs(&animation, 2.0), vec![0.5]);
191 let info = animation.animation_info();
192 assert_eq!(info.range, 0.0..4.0);
193 assert_eq!(info.content_duration_secs, 2.0);
194 assert_eq!(info.children.len(), 2);
195 assert_eq!(info.children[0].range, 0.0..1.0);
196 assert_eq!(info.children[1].range, 1.0..2.0);
197 }
198
199 #[test]
200 fn extend_appends_direct_children_and_preserves_local_gaps() {
201 let mut source = AnimSequence::new();
202 source
203 .push(leaf(2.0, 1.0))
204 .forward(2.0)
205 .push(leaf(3.0, 1.0));
206
207 let mut sequence = AnimSequence::new();
208 sequence.push(leaf(1.0, 2.0)).extend(source);
209 assert_eq!(sequence.cursor_sec(), 6.0);
210 assert_eq!(sequence.built_animations().len(), 3);
211 assert_eq!(sequence.built_animations()[0].time_range(), 0.0..2.0);
212 assert_eq!(sequence.built_animations()[1].time_range(), 2.0..3.0);
213 assert_eq!(sequence.built_animations()[2].time_range(), 5.0..6.0);
214
215 let source = stack![leaf(2.0, 1.0), leaf(3.0, 2.0).at(1.0)];
216 let mut stack = stack![leaf(1.0, 4.0)];
217 stack.extend(source);
218 assert_eq!(stack.duration_secs(), 4.0);
219 assert_eq!(stack.built_animations().len(), 3);
220 assert_eq!(stack.built_animations()[1].time_range(), 0.0..1.0);
221 assert_eq!(stack.built_animations()[2].time_range(), 1.0..3.0);
222 }
223
224 #[test]
225 fn composition_macros_build_dynamic_containers_without_an_arity_limit() {
226 let empty_sequence: AnimSequence = seq![];
227 let empty_stack: AnimStack = stack![];
228 assert_eq!(empty_sequence.duration_secs(), 0.0);
229 assert_eq!(empty_stack.duration_secs(), 0.0);
230
231 let sequence: AnimSequence = seq![
232 leaf(1.0, 1.0),
233 leaf(2.0, 1.0),
234 leaf(3.0, 1.0),
235 leaf(4.0, 1.0),
236 leaf(5.0, 1.0),
237 leaf(6.0, 1.0),
238 leaf(7.0, 1.0),
239 leaf(8.0, 1.0),
240 leaf(9.0, 1.0),
241 ];
242 assert_eq!(sequence.duration_secs(), 9.0);
243 assert_eq!(sequence.built_animations().len(), 9);
244
245 let stack: AnimStack = stack![
246 leaf(1.0, 1.0),
247 leaf(2.0, 2.0),
248 leaf(3.0, 3.0),
249 leaf(4.0, 4.0),
250 leaf(5.0, 5.0),
251 leaf(6.0, 6.0),
252 leaf(7.0, 7.0),
253 leaf(8.0, 8.0),
254 leaf(9.0, 9.0),
255 ];
256 assert_eq!(stack.duration_secs(), 9.0);
257 assert_eq!(stack.built_animations().len(), 9);
258 }
259
260 #[test]
261 fn holds_sample_active_items_and_repeat_without_nesting() {
262 let mut sequence = AnimSequence::new();
264 sequence
265 .push(stack![leaf(1.0, 1.0), leaf(2.0, 2.0)])
266 .hold(1.0);
267 assert_eq!(sequence.built_animations().len(), 2);
268 assert_eq!(sampled_xs(&sequence.built_animations()[1], 2.5), vec![2.0]);
269
270 let mut sequence = AnimSequence::new();
272 sequence.push(leaf(3.0, 1.0)).hold(1.0).hold(2.0);
273 assert_eq!(sequence.cursor_sec(), 4.0);
274 assert_eq!(sequence.built_animations().len(), 3);
275 assert_eq!(sequence.built_animations()[1].time_range(), 1.0..2.0);
276 assert_eq!(sequence.built_animations()[2].time_range(), 2.0..4.0);
277 assert_eq!(sampled_xs(&sequence.built_animations()[2], 3.5), vec![3.0]);
278
279 let mut sequence = AnimSequence::new();
281 sequence
282 .push(stack![leaf(1.0, 1.0), leaf(2.0, 1.0)])
283 .hold(1.0)
284 .hold(1.0);
285 let mut first = Vec::new();
286 sequence.built_animations()[1].eval_at(1.5, &mut first);
287 let mut second = Vec::new();
288 sequence.built_animations()[2].eval_at(2.5, &mut second);
289 assert_eq!(first.len(), 2);
290 assert_eq!(second.len(), 2);
291 assert_eq!(evaluated_xs(second), vec![1.0, 2.0]);
292 }
293
294 #[test]
295 fn holds_use_final_evaluations_and_forward_does_not_hold() {
296 let mut shown = VItem::default();
297 shown.points[0].x = 5.0;
298
299 let mut hidden = AnimSequence::new();
300 hidden.push(leaf(1.0, 1.0)).push(shown.hide()).hold(1.0);
301 assert_eq!(hidden.built_animations().len(), 2);
302 let mut hidden_items = Vec::new();
303 hidden.built_animations()[1].eval_at(1.0, &mut hidden_items);
304 assert!(hidden_items.is_empty());
305
306 let mut restored = AnimSequence::new();
307 restored.push(leaf(1.0, 1.0)).push(shown.show()).hold(1.0);
308 assert_eq!(sampled_xs(&restored.built_animations()[2], 1.5), vec![5.0]);
309
310 let mut sequence = AnimSequence::new();
312 sequence.push(leaf(4.0, 1.0)).forward(1.0).hold(1.0);
313 assert_eq!(sequence.cursor_sec(), 3.0);
314 assert_eq!(sequence.built_animations().len(), 1);
315
316 let mut shown = VItem::default();
318 shown.points[0].x = 7.0;
319 let inner = seq![leaf(1.0, 1.0), shown.show()];
320 let mut outer = AnimSequence::new();
321 outer.push(inner).hold(1.0);
322 assert_eq!(outer.built_animations().len(), 2);
323 assert_eq!(sampled_xs(&outer.built_animations()[1], 1.5), vec![7.0]);
324
325 let hidden_inner = seq![leaf(1.0, 1.0), shown.hide()];
326 let mut hidden_outer = AnimSequence::new();
327 hidden_outer.push(hidden_inner).hold(1.0);
328 assert_eq!(hidden_outer.built_animations().len(), 1);
329 }
330
331 #[test]
332 fn lagged_staggers_and_fills_window_edges() {
333 let lagged = lagged![0.5; leaf(1.0, 1.0), leaf(2.0, 2.0), leaf(3.0, 1.0)];
335 assert_eq!(lagged.built_animations()[0].time_range(), 0.0..1.0);
336 assert_eq!(lagged.built_animations()[1].time_range(), 0.5..2.5);
337 assert_eq!(lagged.built_animations()[2].time_range(), 1.5..2.5);
338 assert_eq!(lagged.duration_secs(), 2.5);
339
340 let as_sequence = lagged![1.0; leaf(1.0, 1.0), leaf(2.0, 1.0)];
342 assert_eq!(as_sequence.built_animations()[1].time_range(), 1.0..2.0);
343 let as_stack = lagged![0.0; leaf(1.0, 1.0), leaf(2.0, 2.0)];
344 assert_eq!(as_stack.built_animations()[1].time_range(), 0.0..2.0);
345 assert_eq!(as_stack.duration_secs(), 2.0);
346
347 let animation = lagged![
349 0.5;
350 progress_leaf(0.0).with_duration(1.0),
351 progress_leaf(10.0).with_duration(1.0)
352 ]
353 .into_anim_node();
354 assert_eq!(animation.time_range(), 0.0..1.5);
355 assert_eq!(sampled_xs(&animation, 0.25), vec![0.25, 10.0]);
356 assert_eq!(sampled_xs(&animation, 1.0), vec![1.0, 10.5]);
357 assert_eq!(sampled_xs(&animation, 1.5), vec![1.0, 11.0]);
358
359 let empty_leading = lagged![
361 0.5;
362 progress_leaf(0.0).with_duration(1.0),
363 progress_leaf(10.0).with_duration(1.0)
364 ]
365 .with_leading(LaggedFill::Empty)
366 .into_anim_node();
367 assert_eq!(sampled_xs(&empty_leading, 0.25), vec![0.25]);
368 assert_eq!(sampled_xs(&empty_leading, 1.0), vec![1.0, 10.5]);
369
370 let mut shown = VItem::default();
372 shown.points[0].x = 2.0;
373 let hidden = lagged![0.5; seq![leaf(2.0, 1.0), shown.hide()]].into_anim_node();
374 assert_eq!(sampled_xs(&hidden, 0.5), vec![2.0]);
375 let mut items = Vec::new();
376 hidden.eval_at(1.0, &mut items);
377 assert!(items.is_empty());
378 }
379
380 #[test]
381 fn lagged_fill_structure_is_materialized_as_per_item_tracks() {
382 let animation = lagged![
383 0.5;
384 progress_leaf(0.0).with_duration(1.0),
385 progress_leaf(10.0).with_duration(1.0),
386 ]
387 .into_anim_node();
388 let info = animation.animation_info();
389 assert_eq!(info.children.len(), 2);
392
393 let first = &info.children[0];
394 assert_eq!(first.kind, AnimationInfoKind::Sequence);
395 assert_eq!(first.range, 0.0..1.5);
396 assert_eq!(first.children.len(), 2);
397 assert_eq!(first.children[0].kind, AnimationInfoKind::Eval);
398 assert_eq!(first.children[0].range, 0.0..1.0);
399 assert_eq!(first.children[1].kind, AnimationInfoKind::Static);
400 assert_eq!(first.children[1].range, 1.0..1.5);
401
402 let second = &info.children[1];
403 assert_eq!(second.kind, AnimationInfoKind::Sequence);
404 assert_eq!(second.range, 0.0..1.5);
405 assert_eq!(second.children.len(), 2);
406 assert_eq!(second.children[0].kind, AnimationInfoKind::Static);
407 assert_eq!(second.children[0].range, 0.0..0.5);
408 assert_eq!(second.children[1].kind, AnimationInfoKind::Eval);
409 assert_eq!(second.children[1].range, 0.5..1.5);
410 }
411
412 use super::sound::Sound;
415 use crate::RanimScene;
416 use crate::audio::{AudioClip, MASTER_SAMPLE_RATE};
417 use crate::utils::rate_functions::ease_in_quad;
418
419 fn tone(secs: f64) -> AudioClip {
420 let pcm = vec![0.5f32; (secs * 48_000.0) as usize];
422 AudioClip::from_pcm(pcm, 48_000, 1)
423 }
424
425 fn audible_region(scene: RanimScene) -> (f64, f64, f64) {
429 let sealed = scene.seal();
430 let total = sealed.total_secs();
431 let evaluator = sealed.into_evaluator(120.0);
432 let buf = evaluator.mix_audio(total, MASTER_SAMPLE_RATE);
433 let first = buf
434 .iter()
435 .position(|s| s.abs() > 1e-4)
436 .expect("expected audible samples");
437 let last = buf.len()
438 - 1
439 - buf
440 .iter()
441 .rev()
442 .position(|s| s.abs() > 1e-4)
443 .expect("non-empty");
444 (
445 total,
446 first as f64 / 2.0 / MASTER_SAMPLE_RATE as f64,
447 last as f64 / 2.0 / MASTER_SAMPLE_RATE as f64,
448 )
449 }
450
451 #[test]
452 fn sound_windows_follow_placement() {
453 let mut scene = RanimScene::new();
455 scene.play(seq![leaf(1.0, 1.0), Sound::new(tone(2.0))]);
456 let (total, start, end) = audible_region(scene);
457 assert!((total - 3.0).abs() < 1e-9);
458 assert!((start - 1.0).abs() < 0.01);
459 assert!((end - 3.0).abs() < 0.01);
460
461 let mut scene = RanimScene::new();
463 scene.play(stack![Sound::new(tone(1.0)).at(2.0)]);
464 let (_, start, end) = audible_region(scene);
465 assert!((start - 2.0).abs() < 0.01);
466 assert!((end - 3.0).abs() < 0.01);
467 }
468
469 #[test]
470 fn sound_frames_push_no_items() {
471 let mut scene = RanimScene::new();
472 scene.play(Sound::new(tone(2.0)));
473 let sealed = scene.seal();
474
475 assert_eq!(sealed.eval_at_sec(1.0).count(), 0);
476 }
477
478 #[test]
479 fn disabled_sound_is_excluded() {
480 let mut scene = RanimScene::new();
481 scene.play(stack![Sound::new(tone(1.0)).with_enabled(false)]);
482 let evaluator = scene.seal().into_evaluator(120.0);
483 assert!(evaluator.has_audio());
484 assert!(
485 evaluator
486 .mix_audio(1.0, MASTER_SAMPLE_RATE)
487 .iter()
488 .all(|s| s.abs() < 1e-4)
489 );
490 }
491
492 #[test]
493 fn scene_without_sound_has_no_audio() {
494 let mut scene = RanimScene::new();
495 scene.play(leaf(1.0, 1.0));
496 assert!(!scene.seal().into_evaluator(120.0).has_audio());
497 }
498
499 #[test]
500 fn container_duration_override_rescales_sound() {
501 let inner = seq![Sound::new(tone(2.0))];
505 let mut scene = RanimScene::new();
506 scene.play(inner.with_duration(1.0));
507 let (_, start, end) = audible_region(scene);
508 assert!((start - 0.0).abs() < 0.01);
509 assert!((end - 1.0).abs() < 0.01);
510 }
511
512 #[test]
513 fn container_rate_func_warps_the_sound_window() {
514 let inner = seq![leaf(1.0, 1.0), Sound::new(tone(1.0))];
518 let mut scene = RanimScene::new();
519 scene.play(inner.with_rate_func(ease_in_quad));
520
521 let (_, start, end) = audible_region(scene);
522 let expected_start = 2.0 * (0.5f64).sqrt();
523 assert!((start - expected_start).abs() < 0.01, "start {start}");
524 assert!((end - 2.0).abs() < 0.01, "end {end}");
525 }
526
527 #[test]
528 fn sound_rate_func_warps_its_content_progress() {
529 let clip = AudioClip::from_pcm(
532 (0..48_000).map(|i| i as f32 / 48_000.0).collect::<Vec<_>>(),
533 48_000,
534 1,
535 );
536 let mut scene = RanimScene::new();
537 scene.play(Sound::new(clip).with_rate_func(ease_in_quad));
538 let evaluator = scene.seal().into_evaluator(120.0);
539 let buf = evaluator.mix_audio(1.0, 48_000);
540 let mid = buf[(0.5 * 48_000.0) as usize * 2];
542 assert!((mid - 0.25).abs() < 0.01, "amplitude at 0.5s was {mid}");
543 }
544
545 #[test]
546 fn warped_container_of_warped_sounds_mixes_all_of_them() {
547 let clip = AudioClip::from_pcm(vec![0.25f32; 48_000], 48_000, 1);
551 let mut stack = AnimStack::new();
552 for _ in 0..10 {
553 stack.push(Sound::new(clip.clone()).with_rate_func(ease_in_quad));
554 }
555 let mut scene = RanimScene::new();
556 scene.play(stack.with_rate_func(ease_in_quad));
557 let buf = scene.seal().into_evaluator(120.0).mix_audio(0.5, 48_000);
558 for frame in [0usize, 12_000, 23_999] {
559 assert!(
560 (buf[frame * 2] - 2.5).abs() < 1e-3,
561 "frame {frame}: {} (expected 10 × 0.25)",
562 buf[frame * 2]
563 );
564 }
565 }
566
567 #[test]
568 fn nested_containers_compose_the_sound_window() {
569 let inner = seq![leaf(1.0, 1.0), Sound::new(tone(1.0))];
570 let mut scene = RanimScene::new();
571 scene.play(seq![inner, Sound::new(tone(0.5))]);
572 let evaluator = scene.seal().into_evaluator(120.0);
573 let buf = evaluator.mix_audio(evaluator.total_secs(), MASTER_SAMPLE_RATE);
574 let at = |sec: f64| buf[(sec * MASTER_SAMPLE_RATE as f64) as usize * 2];
575 assert!(at(0.5).abs() < 1e-4);
578 assert!(at(1.5).abs() > 1e-4);
579 assert!(at(2.25).abs() > 1e-4);
580 }
581
582 #[test]
583 fn sound_with_a_tail_extends_the_scene_to_its_window() {
584 let clip_len = 48_001; let clip = AudioClip::from_pcm(vec![0.5f32; clip_len], 48_000, 1);
590 let mut scene = RanimScene::new();
591 scene.play(stack![
592 Static(VItem::default()).with_duration(1.0),
593 Sound::new(clip)
594 ]);
595 assert!((scene.seal().total_secs() - clip_len as f64 / 48_000.0).abs() < 1e-9);
596 }
597
598 #[test]
599 fn linear_rate_is_structural() {
600 let cells = [
606 AnimSequence::new().into_anim_node(),
607 AnimStack::new().into_anim_node(),
608 Sound::new(tone(0.01)).into_anim_node(),
609 leaf(1.0, 1.0).into_anim_node(),
610 ];
611 for cell in &cells {
612 assert!(
613 cell.rate_func.is_none(),
614 "a default-built cell must carry the structural linear rate"
615 );
616 }
617 let warped = leaf(1.0, 1.0).with_rate_func(ease_in_quad).into_anim_node();
618 assert!(warped.rate_func.is_some());
619 }
620
621 #[test]
622 fn baked_audio_matches_point_semantics() {
623 let ramp = |i: usize| 0.4 * i as f32 / 48_000.0;
630 let stereo = AudioClip::from_pcm(
631 (0..48_000)
632 .flat_map(|i| [ramp(i), ramp(i)])
633 .collect::<Vec<_>>(),
634 48_000,
635 2,
636 );
637 let warped = seq![
638 Static(VItem::default()).with_duration(1.0),
639 Sound::new(tone(1.0))
640 ]
641 .with_rate_func(ease_in_quad);
642
643 let mut scene = RanimScene::new();
644 scene.play(stack![
645 seq![
646 Sound::new(AudioClip::sine(440.0, 1.0, 0.5))
647 .with_fade_in(0.25)
648 .with_gain(0.8),
649 Sound::new(stereo).with_duration(0.5),
651 ],
652 Sound::new(AudioClip::sine(880.0, 2.0, 0.3)).at(0.5),
653 seq![Sound::new(tone(2.0))].with_duration(1.7),
654 warped,
655 Sound::new(tone(1.0)).with_rate_func(ease_in_quad),
656 Static(VItem::default()).with_duration(4.0),
657 ]);
658 let sealed = scene.seal();
659 let baked = sealed.audio().clone();
660 let evaluator = sealed.into_evaluator(120.0);
661 assert_eq!(baked, evaluator.audio().clone());
662
663 fn cell_at(cell: &AnimNode, x: f64) -> [f32; 2] {
664 if !cell.enabled || x < cell.time_range.start || x >= cell.time_range.end {
665 return [0.0; 2];
666 }
667 let raw = (x - cell.time_range.start) / cell.duration_secs();
668 let own = cell.internal_time_secs * cell.rate_func.map_or(raw, |rate| rate(raw));
669 match &cell.content {
670 NodeContent::Audio(track) => track.sample_at(own),
671 _ => {
672 let mut acc = [0.0f32; 2];
673 for child in cell.children() {
674 let [l, r] = cell_at(child, own);
675 acc[0] += l;
676 acc[1] += r;
677 }
678 acc
679 }
680 }
681 }
682
683 let frames = baked.len() / 2;
684 for frame in 0..frames {
685 let x = frame as f64 / MASTER_SAMPLE_RATE as f64;
686 let mut acc = [0.0f32; 2];
687 for cell in evaluator.cells() {
688 let [l, r] = cell_at(cell, x);
689 acc[0] += l;
690 acc[1] += r;
691 }
692 assert!(
693 (baked[frame * 2] - acc[0]).abs() < 1e-6
694 && (baked[frame * 2 + 1] - acc[1]).abs() < 1e-6,
695 "frame {frame}: baked [{}, {}] vs walk {acc:?}",
696 baked[frame * 2],
697 baked[frame * 2 + 1]
698 );
699 }
700 }
701}