Skip to content

SMPTE Timecodes & Timelines, Sequences and Keyframes. #22

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 34 commits into
base: main
Choose a base branch
from

Conversation

TheNachoBIT
Copy link
Contributor

@TheNachoBIT TheNachoBIT commented Sep 23, 2024

I'm making this a draft for now, this PR is to document the progress i've been making so far with this experiment.

This experiment can also fail. If you see any kind of flaw, please feel free to point it out.

The final goals of this (if succeeds) are:

  • Allow Interpoli to be more general-purpose, so it can be used for animation, video editing, motion graphics, videogames, sound design, etc.
  • Allow for both developers and people with expertise in other artistic areas to have the same concepts.
  • Allow for potential optimizations with interpolations at a large-scale (lots of characters being animated at once in a videogame, a big timeline that contains a lot of properties, etc.)

Progress.

  • Learn how to rebase this PR to upstream Interpoli.
  • Rebase.
  • SMPTE Timecodes (Basics).
    • Basics.
    • Make it run on full integers (frames being isize instead of f64).
    • Add syncronization based on nanoframes (to allow 23.97fps, 23.98fps, 29.97fps, ... to run properly).
    • Add '==' functionality (specially for asserts).
    • Make as_string() prettier (print out '00:00:00:00' instead of '0:0:0:0').
    • Jump to specific parts of the Timecode.
    • Add Utilities (will be useful for Timelines & Tweening).
  • Timelines (Basics).
    • Get basic time functionality.
    • Create new(...) functions.
    • Add Sequences to the mix.
    • Add Timeline Nesting
    • Add AnimationEngine for Tweening
    • Get Animated Values
  • Timelines (Extras).
  • Sequence (Basics).
    • Add tree functionality.
    • Get and/or create keyframes.
    • Be able to use it with all types available.
    • Add ways to get keyframes between timestamps.
    • Add multiple keyframe support.
  • Keyframes.
    • Basic storage functionality.
    • Add States. (Easing, Hold, etc.) (idk what to do here, ask Bruce or Aaron)
    • Add functions to make API easier. (States need to be implemented for this)
  • AnimationEngine (Basics).
    • Basic Tweening Functionality.
    • Add states.
  • SMPTE Timecodes (Extras)
    • Sync based on denominators (Get help from @xorgy for this). (Probably in a later PR)
    • Better utility implementations.
    • Search for edge-use cases.
  • For review
    • Fix errors and apply recommendations detected via clippy
    • Make sure to apply cargo fmt (i always forget about that command ;_;)
    • Remove unwraps or functionality that can trigger a panic.

TO-DO In Review

  • Start documenting.
  • Wait for Code Reviews (i need everyone's w i s d o m).

SMPTE Timecodes (Update 2).

SMPTE Timecodes are time units that are set like a clock: HH:MM:SS:FF (Hours, Minutes, Seconds, Frames).

For convenience, there's a macro that helps visualize this concept better in code:

tcode_hmsf!(01:23:45:01) // A timecode with Hours (h), Minutes (m), Seconds (s) and Frames (f).

(Sadly, Rust's macros don't allow me to use ":" so i have to use ";" instead, if there's a workaround for this, please let me know)

EDIT: I fixed it!

Timecodes can be used as a Timestamp, or as a value for a Timeline, by setting the Framerate:

tcode_hmsf_framerate!(00:01:02:56, Framerate::Fixed(20.0))

There's Framerate::Fixed(n) and Framerate::Interpolated(n). Once Timelines become a thing, fixed framerates will round up to the nearest frame when the tween is calculated, which will be useful for frame-by-frame animations. Interpolated frames on the other hand, will interpolate regardless of the framerate you're running (unless you explicitly set "hold" frames).

Timecodes with a framerate can advance and reverse by frames, seconds, minutes, hours and use Duration (Instant coming soon).

Example: Play one second frame-by-frame.

let mut time = tcode_hmsf_framerate!(00:00:00:00, Framerate::Fixed(24.0));

for i in 0..24 {
    time.next_frame();
}

println!("{:?}", time.as_string()); // Outputs "00:00:01:00 (24.0)"

Example: Add by Duration.

use std::time::Duration;

let mut time = tcode_hmsf_framerate!(00:00:00:00, Framerate::Fixed(24.0));

time.add_by_duration(Duration::from_millis(999));

println!("{:?}", time.as_string()); // Outputs "00:00:00:23 (24.0)"

Example: Sub by Duration.

use std::time::Duration;

let mut time = tcode_hmsf_framerate!(01:00:00:00, Framerate::Fixed(24.0)); // 1 hour

time.sub_by_duration(Duration::from_secs(1800));

println!("{:?}", time.as_string()); // Outputs "00:30:00:00 (24.0)" (30 minutes)

The draft repository contains more examples in form of tests inside of lib.rs.

This is all of the progress for now, for any questions, please feel free to ask :)

@TheNachoBIT TheNachoBIT marked this pull request as draft September 23, 2024 13:37
@xorgy xorgy changed the title SMPTE Timecodes & Timelines, Layers and Keyframes. (Rebased) SMPTE Timecodes & Timelines, Layers and Keyframes. Sep 23, 2024
@TheNachoBIT TheNachoBIT changed the title SMPTE Timecodes & Timelines, Layers and Keyframes. SMPTE Timecodes & Timelines, Sequences and Keyframes. Sep 27, 2024
@TheNachoBIT TheNachoBIT marked this pull request as ready for review October 30, 2024 12:45
@TheNachoBIT
Copy link
Contributor Author

TheNachoBIT commented Oct 31, 2024

Alright, i think this is ready for review! :D

In here i'll show how the Timelines & Sequences API works:

Timelines

A timeline is like a director: It takes care of storing the sequences and it contains a "Sequence Player" that allows you to play all of the animations that are stored in a specified framerate.

let mut timeline_one = Timeline::new(Framerate::Fixed(24.0)); // Fixed Timeline
let mut timeline_two = Timeline::new(Framerate::Interpolated(24.0)); // Interpolated Timeline

You can play forwards, and backwards based on what you want, and you can use Timestamps, Timecodes and Durations.

let mut timeline = Timeline::new(Framerate::Interpolated(24.0));

// If you're in a game/real-time context, you can use
// your engine's delta time to play the timeline.
timeline.add_by_duration(time.delta());

// And also play it backwards.
timeline.sub_by_duration(time.delta());

Sequences

Sequences store all of the keyframes of the animation of a specific variable.

This is an example for creating sequences in a timeline:

let mut t = Timeline::new(Framerate::Interpolated(12.0));

// Let's create a Sequence called "count", that'll be an 'f64'.
let s: &mut Sequence<f64> = t.new_sequence("count").unwrap();

// "count" will be an animation that goes from 0 to 1 in one second.
s.add_keyframe_at_timestamp(Keyframe { value: 0.0 }, tcode_hms!(00:00:00));
s.add_keyframe_at_timestamp(Keyframe { value: 1.0 }, tcode_hms!(00:00:01));

As long as the type contains interpoli's Tween trait, it can be animated.

Here's an example with kurbo's Vec2:

let s: &mut Sequence<Vec2> = t.new_sequence("position").unwrap();

s.add_keyframe_at_timestamp(
    Keyframe {
        value: Vec2::new(200.0, 200.0),
    },
    &tcode_hms!(00:00:00),
);
s.add_keyframe_at_timestamp(
    Keyframe {
        value: Vec2::new(300.0, 200.0),
    },
    &tcode_hms!(00:00:01),
);
s.add_keyframe_at_timestamp(
    Keyframe {
        value: Vec2::new(800.0, 400.0),
    },
    &tcode_hms!(00:00:02),
);
s.add_keyframe_at_timestamp(
    Keyframe {
        value: Vec2::new(700.0, 500.0),
    },
    &tcode_hms!(00:00:03),
);

To finally use the animation's result, by taking the Vec2 example, you can get the value via tween_by_name():

let player_position: Vec2 = t.tween_by_name::<Vec2>("position");

If you're aiming for performance, you can get the sequence's pointer and store it as a reference.

let pointer = t.get_sequence_pointer("position").unwrap();

// More code here...

let player_position: Vec2 = t.tween_by_pointer::<Vec2>(pointer);

Here's an example of the Timelines that you can see and run for yourself in here.
(TODO for myself: Add instructions and record video).

Static Timelines

These work like Timelines, but the size of the Sequences are always known at compile-time, so they can only have one type (or you can use an enum if you want to store different types).

let mut timeline: StaticTimeline<Vec2> = StaticTimeline::new(Framerate::Fixed(24.0));
let sequence = timeline.new_sequence("sequence").unwrap();
sequence.add_keyframes_at_timestamp(vec![
    (
        Keyframe {
            value: Vec2::new(0.0, 1.0),
        },
        &tcode_hmsf!(00:00:01:00),
    ),
    (
        Keyframe {
            value: Vec2::new(1.0, 1.0),
        },
        &tcode_hmsf!(00:00:02:00),
    ),
    (
        Keyframe {
            value: Vec2::new(1.0, 2.0),
        },
        &tcode_hmsf!(00:00:03:00),
    ),
]);

This gives even more performance at the cost of flexibility, so if you're in a situation where you need to squeeze as much performance as you want, you can use these.

Stuff that i didn't add yet (Otherwise this PR was going to be really big to review).

  • Animating on one's, two's, three's, etc.
  • Different Keyframe States (Easing, Hold, Trigger)
  • Editing the sequences while the timeline is running (doesn't work 100%).
  • OpenTimelineIO compatibility.
  • Syncing based on denominators (i don't know if its necessary, because nanoframes help a ton with the syncing).

@TheNachoBIT
Copy link
Contributor Author

I'm gonna need a ton of help and wisdom from everyone, because there's probably a lot of stuff that i'm doing wrong or can be handled much better. So any kind of input is appreciated c:

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

1 participant