summaryrefslogtreecommitdiff
path: root/rust/crates/timeline/src/retime.rs
diff options
context:
space:
mode:
authorcashmere <cashmere@autistici.org>2026-07-28 19:41:16 +0200
committercashmere <cashmere@autistici.org>2026-07-28 19:41:16 +0200
commitf5a82152394fde4d8f740f664ab7737b82861618 (patch)
tree5a51b181abead1ce03d2dbfc12b77918d3be0e1b /rust/crates/timeline/src/retime.rs
parente837e7504379e100ff3b56282577ec67bb88bec9 (diff)
downloadopencut-f5a82152394fde4d8f740f664ab7737b82861618.tar.gz
feat: GPUI desktop port foundation
- scene crate: full project/scene/element model, serde round-trip proven against the web SerializedProject shape - transfer crate: .ocp zip container, cross-compat with web fflate archives proven in both directions - storage crate: filesystem project store replacing IndexedDB, .ocp import/export - media crate: ffmpeg-next 8 decoder (frames, audio, thumbnails, waveform summaries) - playback crate: wall-clock anchored, frame-rounded playback clock - gpu crate: native texture readback; compositor renders headless offscreen on Vulkan (spike test) - flake devshell: rust toolchain, GPUI native deps, ffmpeg dev, bindgen clang args; GPUI hello-world builds - port plan in notes/gpui-port.md
Diffstat (limited to 'rust/crates/timeline/src/retime.rs')
-rw-r--r--rust/crates/timeline/src/retime.rs71
1 files changed, 71 insertions, 0 deletions
diff --git a/rust/crates/timeline/src/retime.rs b/rust/crates/timeline/src/retime.rs
new file mode 100644
index 00000000..7215f2ec
--- /dev/null
+++ b/rust/crates/timeline/src/retime.rs
@@ -0,0 +1,71 @@
+use scene::RetimeConfig;
+use time::MediaTime;
+
+pub const DEFAULT_RETIME_RATE: f64 = 1.0;
+pub const MIN_RETIME_RATE: f64 = 0.01;
+pub const MAX_RETIME_RATE: f64 = 5.0;
+
+pub fn clamp_retime_rate(rate: f64) -> f64 {
+ if !rate.is_finite() || rate <= 0.0 {
+ return DEFAULT_RETIME_RATE;
+ }
+
+ rate.clamp(MIN_RETIME_RATE, MAX_RETIME_RATE)
+}
+
+pub fn can_maintain_pitch(rate: f64) -> bool {
+ rate.is_finite() && rate > 0.0
+}
+
+pub fn should_maintain_pitch(rate: f64, maintain_pitch: Option<bool>) -> bool {
+ maintain_pitch == Some(true) && can_maintain_pitch(rate)
+}
+
+fn safe_rate(retime: Option<&RetimeConfig>) -> f64 {
+ clamp_retime_rate(retime.map(|retime| retime.rate).unwrap_or(1.0))
+}
+
+pub fn get_source_time_at_clip_time(clip_time: f64, retime: Option<&RetimeConfig>) -> f64 {
+ clip_time * safe_rate(retime)
+}
+
+pub fn get_clip_time_at_source_time(source_time: f64, retime: Option<&RetimeConfig>) -> f64 {
+ source_time / safe_rate(retime)
+}
+
+pub fn get_effective_rate_at(retime: Option<&RetimeConfig>) -> f64 {
+ safe_rate(retime)
+}
+
+pub fn get_timeline_duration_for_source_span(
+ source_span: f64,
+ retime: Option<&RetimeConfig>,
+) -> f64 {
+ if source_span <= 0.0 {
+ return 0.0;
+ }
+ source_span / safe_rate(retime)
+}
+
+pub fn get_source_span_at_clip_time(clip_time: f64, retime: Option<&RetimeConfig>) -> f64 {
+ get_source_time_at_clip_time(clip_time, retime).max(0.0)
+}
+
+pub fn split_retime_at_clip_time(
+ retime: Option<&RetimeConfig>,
+) -> (Option<RetimeConfig>, Option<RetimeConfig>) {
+ (retime.copied(), retime.copied())
+}
+
+pub fn adjust_retime_for_trim_change(retime: Option<&RetimeConfig>) -> Option<RetimeConfig> {
+ retime.copied()
+}
+
+pub fn round_media_time(time: f64) -> MediaTime {
+ let rounded_magnitude = time.abs().round();
+ if rounded_magnitude == 0.0 {
+ return MediaTime::ZERO;
+ }
+ let magnitude = rounded_magnitude as i64;
+ MediaTime::from_ticks(if time < 0.0 { -magnitude } else { magnitude })
+}