mirror of
https://github.com/storytold/effectcraft.git
synced 2026-11-09 14:14:18 +00:00
Preview: a picture behind its sound never shows past the sound's frame
`latest_cached_before` stopped only on the sound's exact frame, which a picture off the plan's frame step steps over and a sound behind the picture in a non-looping preview never reaches, so it could return a frame ahead of the sound. The walk now stops at the first frame that isn't between the picture and the sound in play order (across the loop's end once the sound has wrapped). Also brings `Playback::starting`'s doc up to date. Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
c083447be6
commit
541e249e2e
@@ -27,6 +27,14 @@ fn startup_frames(ahead: i64, window: i64, budget: usize, frame_bytes: usize) ->
|
||||
ahead.min(window).min(fit).max(1)
|
||||
}
|
||||
|
||||
/// Whether frame `f` comes after the current frame `cur` and before the sound's frame `target` in
|
||||
/// play order. A looping preview whose sound is at a lower frame than the picture has wrapped
|
||||
/// to the loop's start, so the frames up to the end and then from the start count; otherwise
|
||||
/// (and always without a loop) only the frames strictly between them do.
|
||||
fn between_picture_and_sound(cur: i64, f: i64, target: i64, looping: bool) -> bool {
|
||||
if target >= cur || !looping { cur < f && f < target } else { f > cur || f < target }
|
||||
}
|
||||
|
||||
impl EffectcraftApp {
|
||||
/// RAM preview: advance at the preview's frame rate when frames are cached; otherwise hold
|
||||
/// and keep rendering ahead (the green cache bar fills, then playback runs in real time).
|
||||
@@ -318,14 +326,16 @@ impl EffectcraftApp {
|
||||
}
|
||||
|
||||
/// The last cached frame after the current one and before `target`, in play order: what a
|
||||
/// preview behind its sound shows (`None` when no such frame is cached).
|
||||
/// preview behind its sound shows (`None` when no such frame is cached). The walk stops at
|
||||
/// the first frame that isn't before the sound's, which it can step over without meeting
|
||||
/// (a picture off the plan's frame step) or never reach (a sound behind the picture).
|
||||
fn latest_cached_before(&self, pass: &PlaybackPass<'_>, target: i64) -> Option<i64> {
|
||||
let PlaybackPass { plan, series, cur, window, .. } = *pass;
|
||||
let PlaybackPass { plan, series, cur, window, looping, .. } = *pass;
|
||||
let cached = self.frames.cached_frames(&series);
|
||||
let mut best = None;
|
||||
for k in 1..=(window.last - window.first + 1).max(1) {
|
||||
let Some(f) = plan.map_or(Some(cur + k), |p| p.frame_after(cur, k)) else { break };
|
||||
if f == target {
|
||||
if !between_picture_and_sound(cur, f, target, looping) {
|
||||
break;
|
||||
}
|
||||
if cached.binary_search(&f).is_ok() {
|
||||
@@ -495,7 +505,28 @@ impl EffectcraftApp {
|
||||
|
||||
#[cfg(test)]
|
||||
mod startup_tests {
|
||||
use super::startup_frames;
|
||||
use super::{between_picture_and_sound, startup_frames};
|
||||
|
||||
/// The frames a preview behind its sound may show are only those before the sound's frame in
|
||||
/// play order, so the picture is never ahead of the sound (#2473).
|
||||
#[test]
|
||||
fn a_dropped_frame_preview_never_shows_past_the_sound() {
|
||||
// Sound ahead of the picture: only the frames between them.
|
||||
assert!(between_picture_and_sound(10, 12, 14, false));
|
||||
assert!(!between_picture_and_sound(10, 14, 14, false), "not the sound's own frame");
|
||||
assert!(!between_picture_and_sound(10, 15, 14, true), "nor one past it, stepped over off the grid");
|
||||
assert!(!between_picture_and_sound(10, 10, 14, true), "nor the current frame");
|
||||
// A looping preview whose sound wrapped to the start: the end of the loop, then its start.
|
||||
assert!(between_picture_and_sound(90, 95, 3, true));
|
||||
assert!(between_picture_and_sound(90, 1, 3, true));
|
||||
assert!(!between_picture_and_sound(90, 3, 3, true));
|
||||
assert!(!between_picture_and_sound(90, 50, 3, true), "the wrapped walk stops before reaching the picture again");
|
||||
// Without a loop, a sound behind the picture allows nothing ahead of it.
|
||||
assert!(!between_picture_and_sound(90, 95, 3, false));
|
||||
assert!(!between_picture_and_sound(90, 1, 3, false));
|
||||
// The sound on the picture's frame: nothing.
|
||||
assert!(!between_picture_and_sound(10, 11, 10, true));
|
||||
}
|
||||
|
||||
/// The frames a preview with sound waits for are always requested and fit together (#947).
|
||||
#[test]
|
||||
|
||||
@@ -207,7 +207,8 @@ pub struct Playback {
|
||||
/// frames ahead render (the prefetch window, at most what fits in the RAM preview), then
|
||||
/// sound and picture start together from it. Without this the picture played on silently
|
||||
/// while they rendered and the sound started at the later frame, so the opening was lost.
|
||||
/// A stall later on still falls back to [`Playback::audio_held`].
|
||||
/// Once the sound plays, a stall no longer stops it: the picture drops frames to follow it
|
||||
/// (#2473).
|
||||
pub starting: bool,
|
||||
/// When the preview started (seconds): [`Playback::starting`] gives up after `STARTUP_LIMIT`.
|
||||
pub starting_since: f64,
|
||||
|
||||
Reference in New Issue
Block a user