Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 22 additions & 0 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -670,6 +670,7 @@ impl App {
let next = (info.current_song + 1).min(info.songs);
if next != info.current_song {
let _ = self.cmd_tx.send(PlayerCmd::SetSubtune(next));
self.clear_advance_status();
self.update_entry_subtune(next);
}
}
Expand All @@ -680,6 +681,7 @@ impl App {
let prev = info.current_song.saturating_sub(1).max(1);
if prev != info.current_song {
let _ = self.cmd_tx.send(PlayerCmd::SetSubtune(prev));
self.clear_advance_status();
self.update_entry_subtune(prev);
}
}
Expand Down Expand Up @@ -2035,6 +2037,23 @@ impl App {

// ── Internal helpers ──────────────────────────────────────────────────

/// Reset all timing-related status fields the auto-advance check reads.
///
/// Must be called whenever we queue a `Play` or `SetSubtune` command —
/// without this, `Tick` (every 33 ms) keeps reading the previous song's
/// elapsed time while the player thread is busy processing the queued
/// command (notably U64's `sid_play` REST call, ~300 ms). Each Tick
/// re-fires SetSubtune on the same stale data, queueing 5-15 extra
/// transitions before the player can respond — visibly skipping
/// subtunes at ~3 per second.
fn clear_advance_status(&mut self) {
self.status.elapsed = Duration::ZERO;
self.status.u64_screen_elapsed_secs = None;
self.status.u64_screen_read_at = None;
self.status.u64_screen_total_secs = None;
self.silence_frames = 0;
}

fn play_track(&mut self, idx: usize) {
if let Some(entry) = self.playlist.entries.get(idx) {
if self.config.skip_rsid && entry.is_rsid {
Expand Down Expand Up @@ -2098,6 +2117,7 @@ impl App {
audio_port,
restart_usb_on_load: self.config.restart_usb_on_load,
});
self.clear_advance_status();
// entry borrow ends here; now safe to call &mut self method.
self.refresh_stil_entry();
}
Expand Down Expand Up @@ -2369,6 +2389,7 @@ impl App {
}
});
let _ = self.cmd_tx.send(PlayerCmd::SetSubtune(next_song));
self.clear_advance_status();
if let Some(e) = self.playlist.entries.get_mut(cur_idx) {
e.selected_song = next_song;
e.duration_secs = next_dur;
Expand Down Expand Up @@ -2513,6 +2534,7 @@ impl App {
}
remote::RemoteCmd::SetSubtune(n) => {
let _ = self.cmd_tx.send(PlayerCmd::SetSubtune(n));
self.clear_advance_status();
}
}
}
Expand Down
38 changes: 35 additions & 3 deletions src/player/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -438,13 +438,34 @@ fn player_loop(
// rather than host wall-clock + start latency.
// The on-screen clock has 1 s resolution, so
// any cadence finer than that is wasted.
//
// Skip the poll for the first ~1 s of any fresh
// PlayContext: after SetSubtune the C64 is mid-
// reset and screen RAM at $3F98 still holds the
// *previous* subsong's elapsed digits. Reading
// them as the new song would repeatedly trigger
// auto-advance and cascade through every
// remaining subsong. Wall-clock fallback covers
// the grace window (it starts at 0 here).
let frame_hz = (1_000_000 / ctx.frame_us.max(1)) as u32;
let poll_period = (frame_hz / 2).max(1);
if ctx.frame_count % poll_period == 0 {
let grace_frames = frame_hz;
if ctx.frame_count >= grace_frames
&& ctx.frame_count % poll_period == 0
{
if let Some(ref mut br) = bridge {
if let Some(secs) = br.read_screen_elapsed() {
ctx.u64_screen_elapsed_secs = Some(secs);
ctx.u64_screen_read_at = Some(Instant::now());
// Defence in depth: a fresh ctx's
// wall-clock is the upper bound on
// legitimate elapsed time. Anything
// > +5 s ahead is stale screen
// content from before the most
// recent sid_play() landed.
let wall_secs = ctx.elapsed.as_secs() as u32;
if secs <= wall_secs.saturating_add(5) {
ctx.u64_screen_elapsed_secs = Some(secs);
ctx.u64_screen_read_at = Some(Instant::now());
}
}
// Total only changes per song; only fetch
// until we've successfully captured it.
Expand Down Expand Up @@ -821,6 +842,17 @@ fn handle_cmd(
match br.resume_machine() {
Ok(()) => {
*state = PlayState::Playing;
// Discard the U64 screen-timer cache: while
// the machine was paused the screen-clock was
// frozen but the host's `Instant` kept ticking,
// so the existing `read_at` would inflate the
// interpolated elapsed by however long we were
// paused. Force the next poll to re-base from
// a fresh read.
if let Some(ref mut ctx) = play_ctx {
ctx.u64_screen_elapsed_secs = None;
ctx.u64_screen_read_at = None;
}
eprintln!("[phosphor] U64 machine resumed");
}
Err(e) => {
Expand Down
Loading