Skip to content

Add ping-pong loop mode - #14

Merged
D-Mad merged 2 commits into
D-Mad:mainfrom
emlcpfx:pr10-pingpong
Jul 14, 2026
Merged

Add ping-pong loop mode#14
D-Mad merged 2 commits into
D-Mad:mainfrom
emlcpfx:pr10-pingpong

Conversation

@emlcpfx

@emlcpfx emlcpfx commented Jul 14, 2026

Copy link
Copy Markdown
Contributor

Summary

Loop was on/off. This makes it a tri-state — Play Once / Loop / Ping-Pong — where ping-pong reverses direction at each end of the range instead of jumping back to the start.

What's included

  • playback/loopmode.py - the frame-advance rule as a pure function, so the stepping behaviour (including the bounce at each end) is unit-testable with no reader, timer, or Qt. Preserves the existing off/loop semantics exactly.
  • playback/player.py - SequencePlayer tracks a playback direction and steps through loopmode.advance(); a scrub resets to forward. set_loop_mode() added to SequencePlayer, MoviePlayer and the MediaPlayer facade; set_loop(bool) is kept and maps onto it.
  • constants/__init__.py - LOOP_MODES.
  • widgets/__init__.py - a "Loop Mode" submenu under Playback. loop_mode is the single source of truth and syncs the menu action, the submenu, and the timeline loop button. The mode (including ping-pong) now survives a media load.

Known limitation (please read)

Ping-pong reverses for image sequences only. FrameDeck's movie playback is time-driven and audio-synchronized — packets decode forward into a queue against a monotonic clock — so it cannot run backwards without backward decoding and breaking A/V sync. Movies therefore treat ping-pong as a normal loop, documented on MoviePlayer.set_loop_mode.

(reviewapp gets ping-pong for free because it frame-steps with a RAM cache and has no audio at all. Making it work for FrameDeck movies would mean a frame-stepping playback mode — a much larger change, happy to do it separately if you want it.)

Test evidence

$ pytest tests/test_loopmode.py
.........                                                                 [100%]
9 passed in 0.02s

Covers preserved off/loop behaviour, the bounce at both ends, a full ping-pong cycle (1-2-3-4-3-2-1-2...), no endpoint repeated on a bounce, and degenerate single-frame/empty ranges.

Verification notes

  • Advance logic is fully unit-tested.
  • Ping-pong on a real EXR sequence in the UI is render-verify pending.

Dependencies

Branched on the test harness (#7).

🤖 Generated with Claude Code

emlcpfx and others added 2 commits July 14, 2026 23:15
Loop was on/off. Make it a tri-state (Play Once / Loop / Ping-Pong), where
ping-pong reverses direction at each end of the range instead of jumping back
to the start.

- playback/loopmode.py: the frame-advance rule as a pure function, so the
  stepping behaviour (including the bounce at each end) is unit testable with no
  reader, timer or Qt. Preserves the existing off/loop semantics exactly.
- playback/player.py: SequencePlayer tracks a playback direction and steps
  through loopmode.advance(); a scrub resets to forward. set_loop_mode() added
  to SequencePlayer, MoviePlayer and the MediaPlayer facade; set_loop(bool) is
  kept and maps onto it.
- constants/__init__.py: LOOP_MODES.
- widgets/__init__.py: a "Loop Mode" submenu under Playback. loop_mode is the
  single source of truth and syncs the menu action, the submenu and the timeline
  loop button. The mode (including ping-pong) now survives a media load.

Limitation: ping-pong reverses for image sequences only. Movie playback here is
time-driven and audio-synchronized (packets decode forward into a queue against
a monotonic clock), so it cannot run backwards without backward decoding and
breaking A/V sync; movies therefore treat ping-pong as a normal loop. This is
documented on MoviePlayer.set_loop_mode. (reviewapp gets ping-pong for free
because it frame-steps with no audio at all.)

Tests: tests/test_loopmode.py covers preserved off/loop behaviour, the bounce at
both ends, a full ping-pong cycle (1-2-3-4-3-2-1-2...), no endpoint repeated on
a bounce, and degenerate single-frame/empty ranges. 9 tests pass.

Depends on the test harness (PR D-Mad#7).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0177bi2WkPjrGytrarheKFDc
Claude-Session-Id: 45a11a50-85fe-4349-b853-e1db9cd20276
@D-Mad
D-Mad merged commit b780df0 into D-Mad:main Jul 14, 2026
3 checks passed
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.

2 participants