From f4da56efea548a4c1a36b2064f81ec1b353387b3 Mon Sep 17 00:00:00 2001 From: Connor McLaughlin <stenzek@gmail.com> Date: Mon, 14 Jun 2021 14:55:37 +1000 Subject: [PATCH] CDROM: Simulate the time it takes to change speeds --- src/core/cdrom.cpp | 140 +++++++++++++++++++++++++++++++++++---------- src/core/cdrom.h | 11 ++-- 2 files changed, 116 insertions(+), 35 deletions(-) diff --git a/src/core/cdrom.cpp b/src/core/cdrom.cpp index 63af29145..8b5665dbe 100644 --- a/src/core/cdrom.cpp +++ b/src/core/cdrom.cpp @@ -18,6 +18,11 @@ Log_SetChannel(CDROM); #include <emmintrin.h> #endif +static constexpr std::array<const char*, 15> s_drive_state_names = { + {"Idle", "Opening Shell", "Resetting", "Seeking (Physical)", "Seeking (Logical)", "Reading ID", "Reading TOC", + "Reading", "Playing", "Pausing", "Stopping", "Changing Session", "Spinning Up", "Seeking (Implicit)", + "Changing Speed/Implicit TOC Read"}}; + struct CommandInfo { const char* name; @@ -120,7 +125,6 @@ void CDROM::Reset() m_secondary_status.shell_open = !CanReadMedia(); m_mode.bits = 0; m_mode.read_raw_sector = true; - m_current_double_speed = false; m_interrupt_enable_register = INTERRUPT_REGISTER_MASK; m_interrupt_flag_register = 0; m_pending_async_interrupt = 0; @@ -168,8 +172,10 @@ void CDROM::Reset() SetHoldPosition(0, true); } -void CDROM::SoftReset() +void CDROM::SoftReset(TickCount ticks_late) { + const bool was_double_speed = m_mode.double_speed; + ClearCommandSecondResponse(); ClearDriveState(); m_secondary_status.bits = 0; @@ -203,14 +209,37 @@ void CDROM::SoftReset() UpdateStatusRegister(); - if (m_current_lba != 0) + if (HasMedia()) { - const TickCount seek_ticks = GetTicksForSeek(0); - m_drive_state = DriveState::SeekingImplicit; - m_drive_event->SetIntervalAndSchedule(seek_ticks); - m_reader.QueueReadSector(0); - m_seek_start_lba = m_current_lba; - m_seek_end_lba = 0; + const TickCount toc_read_ticks = GetTicksForTOCRead(); + const TickCount speed_change_ticks = was_double_speed ? GetTicksForSpeedChange() : 0; + const TickCount seek_ticks = (m_current_lba != 0) ? GetTicksForSeek(0) : 0; + const TickCount total_ticks = toc_read_ticks + speed_change_ticks + seek_ticks - ticks_late; + + if (was_double_speed) + { + Log_DevPrintf("CDROM was double speed on reset, switching to single speed in %d ticks, reading TOC in %d ticks, " + "seeking in %d ticks", + speed_change_ticks, toc_read_ticks, seek_ticks); + } + else + { + Log_DevPrintf("CDROM reading TOC on reset in %d ticks and seeking in %d ticks", toc_read_ticks, seek_ticks); + } + + if (m_current_lba != 0) + { + m_drive_state = DriveState::SeekingImplicit; + m_drive_event->SetIntervalAndSchedule(total_ticks); + m_reader.QueueReadSector(0); + m_seek_start_lba = m_current_lba; + m_seek_end_lba = 0; + } + else + { + m_drive_state = DriveState::ChangingSpeedOrTOCRead; + m_drive_event->Schedule(total_ticks); + } } } @@ -222,7 +251,10 @@ bool CDROM::DoState(StateWrapper& sw) sw.Do(&m_status.bits); sw.Do(&m_secondary_status.bits); sw.Do(&m_mode.bits); - sw.Do(&m_current_double_speed); + + bool current_double_speed = m_mode.double_speed; + sw.Do(¤t_double_speed); + sw.Do(&m_interrupt_enable_register); sw.Do(&m_interrupt_flag_register); sw.Do(&m_pending_async_interrupt); @@ -734,7 +766,7 @@ TickCount CDROM::GetTicksForRead() return m_mode.double_speed ? (tps / 150) : (tps / 75); } -TickCount CDROM::GetTicksForSeek(CDImage::LBA new_lba) +TickCount CDROM::GetTicksForSeek(CDImage::LBA new_lba, bool ignore_speed_change) { static constexpr TickCount MIN_TICKS = 20000; @@ -747,7 +779,7 @@ TickCount CDROM::GetTicksForSeek(CDImage::LBA new_lba) else UpdatePhysicalPosition(); - const TickCount tps = System::GetTicksPerSecond(); + const TickCount tps = System::MASTER_CLOCK; const CDImage::LBA current_lba = m_secondary_status.motor_on ? (IsSeeking() ? m_seek_end_lba : m_physical_lba) : 0; const u32 lba_diff = static_cast<u32>((new_lba > current_lba) ? (new_lba - current_lba) : (current_lba - new_lba)); @@ -762,7 +794,7 @@ TickCount CDROM::GetTicksForSeek(CDImage::LBA new_lba) if (lba_diff < 32) { - ticks += static_cast<u32>(GetTicksForRead()) * std::min<u32>(BASE_SECTORS_PER_TRACK, lba_diff); + ticks += static_cast<u32>(GetTicksForRead()) * std::min<u32>(BASE_SECTORS_PER_TRACK, lba_diff) * 2; } else { @@ -780,26 +812,44 @@ TickCount CDROM::GetTicksForSeek(CDImage::LBA new_lba) ticks += static_cast<u32>((u64(tps) * 300) / 1000); } - if (m_mode.double_speed != m_current_double_speed) + if (m_drive_state == DriveState::ChangingSpeedOrTOCRead && !ignore_speed_change) { - Log_DevPrintf("Switched from %s to %s speed", m_current_double_speed ? "double" : "single", - m_mode.double_speed ? "double" : "single"); - m_current_double_speed = m_mode.double_speed; + // we're still reading the TOC, so add that time in + const TickCount remaining_change_ticks = m_drive_event->GetTicksUntilNextExecution(); + ticks += remaining_change_ticks; - // Approximate time for the motor to change speed? - ticks += static_cast<u32>(static_cast<double>(tps) * 0.1); + Log_DevPrintf("Seek time for %u LBAs: %d (%d for speed change/implicit TOC read)", lba_diff, ticks, + remaining_change_ticks); + } + else + { + Log_DevPrintf("Seek time for %u LBAs: %d", lba_diff, ticks); } if (g_settings.cdrom_seek_speedup > 1) ticks = std::min<u32>(ticks / g_settings.cdrom_seek_speedup, MIN_TICKS); - Log_DevPrintf("Seek time for %u LBAs: %u", lba_diff, ticks); - return static_cast<u32>(ticks); + return System::ScaleTicksToOverclock(static_cast<TickCount>(ticks)); } TickCount CDROM::GetTicksForStop(bool motor_was_on) { - return motor_was_on ? (m_mode.double_speed ? 25000000 : 13000000) : 7000; + return System::ScaleTicksToOverclock(motor_was_on ? (m_mode.double_speed ? 25000000 : 13000000) : 7000); +} + +TickCount CDROM::GetTicksForSpeedChange() +{ + static constexpr u32 ticks_single_to_double = static_cast<u32>(0.8 * static_cast<double>(System::MASTER_CLOCK)); + static constexpr u32 ticks_double_to_single = static_cast<u32>(1.0 * static_cast<double>(System::MASTER_CLOCK)); + return System::ScaleTicksToOverclock(m_mode.double_speed ? ticks_single_to_double : ticks_double_to_single); +} + +TickCount CDROM::GetTicksForTOCRead() +{ + if (!HasMedia()) + return 0; + + return System::GetTicksPerSecond(); } CDImage::LBA CDROM::GetNextSectorToBeRead() @@ -940,7 +990,7 @@ void CDROM::ExecuteCommand(TickCount ticks_late) { SendACKAndStat(); SetHoldPosition(0, true); - QueueCommandSecondResponse(Command::ReadTOC, System::GetTicksPerSecond() / 2); // half a second + QueueCommandSecondResponse(Command::ReadTOC, GetTicksForTOCRead()); } EndCommand(); @@ -963,11 +1013,32 @@ void CDROM::ExecuteCommand(TickCount ticks_late) case Command::Setmode: { const u8 mode = m_param_fifo.Peek(0); - Log_DebugPrintf("CDROM setmode command 0x%02X", ZeroExtend32(mode)); + const bool speed_change = (mode & 0x80) != (m_mode.bits & 0x80); + Log_DevPrintf("CDROM setmode command 0x%02X", ZeroExtend32(mode)); m_mode.bits = mode; SendACKAndStat(); EndCommand(); + + if (speed_change) + { + // if we're seeking or reading, we need to add time to the current seek/read + const TickCount change_ticks = GetTicksForSpeedChange(); + if (m_drive_state != DriveState::Idle) + { + Log_DevPrintf("Drive is %s, delaying event by %d ticks for speed change to %s-speed", + s_drive_state_names[static_cast<u8>(m_drive_state)], change_ticks, + m_mode.double_speed ? "double" : "single"); + m_drive_event->Delay(change_ticks); + } + else + { + Log_DevPrintf("Drive is idle, speed change takes %d ticks", change_ticks); + m_drive_state = DriveState::ChangingSpeedOrTOCRead; + m_drive_event->Schedule(change_ticks); + } + } + return; } @@ -1204,7 +1275,7 @@ void CDROM::ExecuteCommand(TickCount ticks_late) if (IsSeeking()) UpdatePositionWhileSeeking(); - SoftReset(); + SoftReset(ticks_late); QueueCommandSecondResponse(Command::Reset, RESET_TICKS); return; @@ -1574,6 +1645,10 @@ void CDROM::ExecuteDrive(TickCount ticks_late) DoSpinUpComplete(); break; + case DriveState::ChangingSpeedOrTOCRead: + DoSpeedChangeOrImplicitTOCReadComplete(); + break; + // old states, no longer used, but kept for save state compatibility case DriveState::UNUSED_ReadingID: { @@ -1717,7 +1792,7 @@ void CDROM::BeginSeeking(bool logical, bool read_after_seek, bool play_after_see m_setloc_position.frame, m_setloc_position.ToLBA(), logical ? "logical" : "physical"); const CDImage::LBA seek_lba = m_setloc_position.ToLBA(); - const TickCount seek_time = GetTicksForSeek(seek_lba); + const TickCount seek_time = GetTicksForSeek(seek_lba, play_after_seek); m_secondary_status.ClearActiveBits(); m_secondary_status.motor_on = true; @@ -1982,6 +2057,13 @@ void CDROM::DoSpinUpComplete() m_secondary_status.motor_on = true; } +void CDROM::DoSpeedChangeOrImplicitTOCReadComplete() +{ + Log_DebugPrintf("Speed change/implicit TOC read complete"); + m_drive_state = DriveState::Idle; + m_drive_event->Deactivate(); +} + void CDROM::DoIDRead() { Log_DebugPrintf("ID read complete"); @@ -2593,10 +2675,6 @@ void CDROM::DrawDebugWindow() if (ImGui::CollapsingHeader("Status/Mode", ImGuiTreeNodeFlags_DefaultOpen)) { - static constexpr std::array<const char*, 14> drive_state_names = { - {"Idle", "Opening Shell", "Resetting", "Seeking (Physical)", "Seeking (Logical)", "Reading ID", "Reading TOC", - "Reading", "Playing", "Pausing", "Stopping", "Changing Session", "Spinning Up", "Seeking (Implicit)"}}; - ImGui::Columns(3); ImGui::Text("Status"); @@ -2701,7 +2779,7 @@ void CDROM::DrawDebugWindow() else { ImGui::TextColored(active_color, "Drive: %s (%d ticks remaining)", - drive_state_names[static_cast<u8>(m_drive_state)], + s_drive_state_names[static_cast<u8>(m_drive_state)], m_drive_event->IsActive() ? m_drive_event->GetTicksUntilNextExecution() : 0); } diff --git a/src/core/cdrom.h b/src/core/cdrom.h index 63d559164..48e55203f 100644 --- a/src/core/cdrom.h +++ b/src/core/cdrom.h @@ -153,7 +153,8 @@ private: UNUSED_Stopping, ChangingSession, SpinningUp, - SeekingImplicit + SeekingImplicit, + ChangingSpeedOrTOCRead }; union StatusRegister @@ -225,7 +226,7 @@ private: BitField<u8, bool, 7, 1> BFRD; }; - void SoftReset(); + void SoftReset(TickCount ticks_late); ALWAYS_INLINE bool IsDriveIdle() const { return m_drive_state == DriveState::Idle; } ALWAYS_INLINE bool IsMotorOn() const { return m_secondary_status.motor_on; } @@ -271,8 +272,10 @@ private: TickCount GetTicksForSpinUp(); TickCount GetTicksForIDRead(); TickCount GetTicksForRead(); - TickCount GetTicksForSeek(CDImage::LBA new_lba); + TickCount GetTicksForSeek(CDImage::LBA new_lba, bool ignore_speed_change = false); TickCount GetTicksForStop(bool motor_was_on); + TickCount GetTicksForSpeedChange(); + TickCount GetTicksForTOCRead(); CDImage::LBA GetNextSectorToBeRead(); bool CompleteSeek(); @@ -294,6 +297,7 @@ private: void DoStatSecondResponse(); void DoChangeSessionComplete(); void DoSpinUpComplete(); + void DoSpeedChangeOrImplicitTOCReadComplete(); void DoIDRead(); void DoSectorRead(); void ProcessDataSectorHeader(const u8* raw_sector); @@ -327,7 +331,6 @@ private: StatusRegister m_status = {}; SecondaryStatusRegister m_secondary_status = {}; ModeRegister m_mode = {}; - bool m_current_double_speed = false; u8 m_interrupt_enable_register = INTERRUPT_REGISTER_MASK; u8 m_interrupt_flag_register = 0;