diff --git a/src/common/CMakeLists.txt b/src/common/CMakeLists.txt index 10589274f..3188ce53a 100644 --- a/src/common/CMakeLists.txt +++ b/src/common/CMakeLists.txt @@ -39,7 +39,7 @@ add_library(common ${SRCS}) target_include_directories(common PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/..") target_include_directories(common PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/..") -target_link_libraries(common YBaseLib glad libsamplerate Threads::Threads) +target_link_libraries(common YBaseLib glad libsamplerate libcue Threads::Threads) if(ENABLE_OPENGL) target_sources(common PRIVATE display_renderer_gl.cpp display_renderer_gl.h) diff --git a/src/common/cd_image.cpp b/src/common/cd_image.cpp index bf81991ef..3c5a6cc87 100644 --- a/src/common/cd_image.cpp +++ b/src/common/cd_image.cpp @@ -5,58 +5,80 @@ Log_SetChannel(CDImage); CDImage::CDImage() = default; -CDImage::~CDImage() -{ - if (m_data_file) - m_data_file->Release(); -} +CDImage::~CDImage() = default; -bool CDImage::Open(const char* path) +std::unique_ptr CDImage::Open(const char* filename) { - Assert(!m_data_file); - - if (!ByteStream_OpenFileStream(path, BYTESTREAM_OPEN_READ | BYTESTREAM_OPEN_SEEKABLE, &m_data_file)) + const char* extension = std::strrchr(filename, '.'); + if (!extension) { - Log_ErrorPrintf("Failed to open '%s'", path); - return false; + Log_ErrorPrintf("Invalid filename: '%s'", filename); + return nullptr; } - m_filename = path; - m_lba_count = m_data_file->GetSize() / RAW_SECTOR_SIZE; +#ifdef _MSC_VER +#define CASE_COMPARE _stricmp +#else +#define CASE_COMPARE strcasecmp +#endif + + if (CASE_COMPARE(extension, ".cue") == 0) + return OpenCueSheetImage(filename); + else if (CASE_COMPARE(extension, ".bin") == 0) + return OpenBinImage(filename); + +#undef CASE_COMPARE + + Log_ErrorPrintf("Unknown extension '%s' from filename '%s'", extension, filename); + return nullptr; +} + +bool CDImage::Seek(LBA lba) +{ + const Index* new_index; + if (m_current_index && lba >= m_current_index->start_lba_on_disc && + (lba - m_current_index->start_lba_on_disc) < m_current_index->length) + { + new_index = m_current_index; + } + else + { + new_index = GetIndexForDiscPosition(lba); + if (!new_index) + return false; + } + + const u32 new_index_offset = lba - new_index->start_lba_on_disc; + if (new_index_offset >= new_index->length) + return false; + + const u64 new_file_offset = new_index->file_offset + (u64(new_index_offset) * new_index->file_sector_size); + if (new_index->file && std::fseek(new_index->file, static_cast(new_file_offset), SEEK_SET) != 0) + return false; + + m_current_index = new_index; + m_position_on_disc = lba; + m_position_in_index = new_index_offset; + m_position_in_track = new_index->start_lba_in_track + new_index_offset; return true; } -bool CDImage::Seek(u64 lba) +bool CDImage::Seek(u32 track_number, const Position& pos_in_track) { - if (lba >= m_lba_count) + if (track_number < 1 || track_number > m_tracks.size()) return false; - if (!m_data_file->SeekAbsolute(lba * RAW_SECTOR_SIZE)) + const Track& track = m_tracks[track_number - 1]; + const u32 pos_lba = pos_in_track.ToLBA(); + if (pos_lba >= track.length) return false; - m_current_lba = lba; - return true; + return Seek(track.start_lba + pos_lba); } -bool CDImage::Seek(u32 minute, u32 second, u32 frame) +bool CDImage::Seek(const Position& pos) { - return Seek(MSFToLBA(m_pregap_seconds, minute, second, frame)); -} - -u32 CDImage::Read(ReadMode read_mode, u64 lba, u32 sector_count, void* buffer) -{ - if (!Seek(lba)) - return false; - - return Read(read_mode, sector_count, buffer); -} - -u32 CDImage::Read(ReadMode read_mode, u32 minute, u32 second, u32 frame, u32 sector_count, void* buffer) -{ - if (!Seek(minute, second, frame)) - return false; - - return Read(read_mode, sector_count, buffer); + return Seek(pos.ToLBA()); } u32 CDImage::Read(ReadMode read_mode, u32 sector_count, void* buffer) @@ -65,15 +87,20 @@ u32 CDImage::Read(ReadMode read_mode, u32 sector_count, void* buffer) u32 sectors_read = 0; for (; sectors_read < sector_count; sectors_read++) { - if (m_current_lba == m_lba_count) - break; + if (m_position_in_index == m_current_index->length) + { + if (!Seek(m_position_on_disc)) + break; + } + + Assert(m_current_index->file); // get raw sector char raw_sector[RAW_SECTOR_SIZE]; - if (!m_data_file->Read2(raw_sector, RAW_SECTOR_SIZE)) + if (std::fread(raw_sector, RAW_SECTOR_SIZE, 1, m_current_index->file) != 1) { - Log_ErrorPrintf("Read of LBA %llu failed", m_current_lba); - m_data_file->SeekAbsolute(m_current_lba * RAW_SECTOR_SIZE); + Log_ErrorPrintf("Read of LBA %u failed", m_position_on_disc); + Seek(m_position_on_disc); return false; } @@ -99,9 +126,40 @@ u32 CDImage::Read(ReadMode read_mode, u32 sector_count, void* buffer) break; } - m_current_lba++; + m_position_on_disc++; + m_position_in_index++; + m_position_in_track++; sectors_read++; } return sectors_read; } + +const CDImage::Index* CDImage::GetIndexForDiscPosition(LBA pos) +{ + for (const Index& index : m_indices) + { + if (pos < index.start_lba_on_disc) + continue; + + const LBA index_offset = pos - index.start_lba_on_disc; + if (pos >= index.length) + continue; + + return &index; + } + + return nullptr; +} + +const CDImage::Index* CDImage::GetIndexForTrackPosition(u32 track_number, LBA track_pos) +{ + if (track_number < 1 || track_number > m_tracks.size()) + return nullptr; + + const Track& track = m_tracks[track_number - 1]; + if (track_pos >= track.length) + return false; + + return GetIndexForDiscPosition(track.start_lba + track_pos); +} diff --git a/src/common/cd_image.h b/src/common/cd_image.h index 708f8ad7a..46f61d86f 100644 --- a/src/common/cd_image.h +++ b/src/common/cd_image.h @@ -1,7 +1,9 @@ #pragma once #include "bitfield.h" #include "types.h" +#include #include +#include class ByteStream; @@ -9,7 +11,9 @@ class CDImage { public: CDImage(); - ~CDImage(); + virtual ~CDImage(); + + using LBA = u32; enum : u32 { @@ -37,53 +41,104 @@ public: u8 sector_mode; }; - // Conversion helpers. - static constexpr u64 MSFToLBA(u32 pregap_seconds, u32 minute, u32 second, u32 frame) + struct Position { - return ZeroExtend64(minute) * FRAMES_PER_MINUTE + ZeroExtend64(second) * FRAMES_PER_SECOND + ZeroExtend64(frame) - - ZeroExtend64(pregap_seconds) * FRAMES_PER_SECOND; - } - static constexpr std::tuple LBAToMSF(u32 pregap_seconds, u64 lba) - { - const u64 offset = (lba + (pregap_seconds * FRAMES_PER_SECOND) % FRAMES_PER_MINUTE); - const u32 minute = Truncate32(lba / FRAMES_PER_MINUTE); - const u32 second = Truncate32(offset / FRAMES_PER_SECOND); - const u32 frame = Truncate32(offset % FRAMES_PER_SECOND); - return std::make_tuple(minute, second, frame); - } + u8 minute; + u8 second; + u8 frame; + + static constexpr Position FromBCD(u8 minute, u8 second, u8 frame) + { + return Position{BCDToDecimal(minute), BCDToDecimal(second), BCDToDecimal(frame)}; + } + + static constexpr Position FromLBA(LBA lba) + { + const u8 frame = Truncate8(lba % FRAMES_PER_SECOND); + lba /= FRAMES_PER_SECOND; + + const u8 second = Truncate8(lba % SECONDS_PER_MINUTE); + lba /= SECONDS_PER_MINUTE; + + const u8 minute = Truncate8(lba); + + return Position{minute, second, frame}; + } + + LBA ToLBA() const + { + return ZeroExtend32(minute) * FRAMES_PER_MINUTE + ZeroExtend32(second) * FRAMES_PER_SECOND + ZeroExtend32(frame); + } + + Position operator+(const Position& rhs) { return FromLBA(ToLBA() + rhs.ToLBA()); } + Position& operator+=(const Position& pos) + { + *this = *this + pos; + return *this; + } + }; + + // Opening disc image. + static std::unique_ptr Open(const char* filename); + static std::unique_ptr OpenBinImage(const char* filename); + static std::unique_ptr OpenCueSheetImage(const char* filename); // Accessors. const std::string& GetFileName() const { return m_filename; } - u32 GetPregapSeconds() const { return m_pregap_seconds; } - u64 GetCurrentLBA() const { return m_current_lba; } - std::tuple GetPositionMSF() const { return LBAToMSF(m_pregap_seconds, m_current_lba); } - u64 GetLBACount() const { return m_lba_count; } - - bool Open(const char* path); + LBA GetPositionOnDisc() const { return m_position_on_disc; } + Position GetMSFPositionOnDisc() const { return Position::FromLBA(m_position_on_disc); } + LBA GetPositionInTrack() const { return m_position_in_track; } + Position GetMSFPositionInTrack() const { return Position::FromLBA(m_position_in_track); } + LBA GetLBACount() const { return m_lba_count; } + u32 GetTrackNumber() const { return m_current_index->track_number; } // Seek to data LBA. - bool Seek(u64 lba); + bool Seek(LBA lba); - // Seek to audio timestamp (MSF). - bool Seek(u32 minute, u32 second, u32 frame); + // Seek to disc position (MSF). + bool Seek(const Position& pos); - // Seek and read at the same time. - u32 Read(ReadMode read_mode, u64 lba, u32 sector_count, void* buffer); - u32 Read(ReadMode read_mode, u32 minute, u32 second, u32 frame, u32 sector_count, void* buffer); + // Seek to track and position. + bool Seek(u32 track_number, const Position& pos_in_track); // Read from the current LBA. Returns the number of sectors read. u32 Read(ReadMode read_mode, u32 sector_count, void* buffer); -private: +protected: + struct Track + { + u32 track_number; + LBA start_lba; + u32 first_index; + u32 length; + }; + + struct Index + { + u64 file_offset; + std::FILE* file; + u32 file_sector_size; + LBA start_lba_on_disc; + u32 track_number; + LBA start_lba_in_track; + u32 length; + bool is_pregap; + }; + + const Index* GetIndexForDiscPosition(LBA pos); + const Index* GetIndexForTrackPosition(u32 track_number, LBA track_pos); + std::string m_filename; + u32 m_lba_count = 0; - // TODO: Multiple data files from cue sheet - ByteStream* m_data_file = nullptr; + std::vector m_tracks; + std::vector m_indices; - // Pregap size. - u32 m_pregap_seconds = 2; + // Position on disc. + LBA m_position_on_disc = 0; - // Current LBA/total LBAs. - u64 m_current_lba = 0; - u64 m_lba_count = 0; + // Position in track/index. + const Index* m_current_index = nullptr; + LBA m_position_in_index = 0; + LBA m_position_in_track = 0; }; diff --git a/src/common/cd_image_bin.cpp b/src/common/cd_image_bin.cpp new file mode 100644 index 000000000..4751c4940 --- /dev/null +++ b/src/common/cd_image_bin.cpp @@ -0,0 +1,77 @@ +#include "YBaseLib/Log.h" +#include "cd_image.h" +Log_SetChannel(CDImageBin); + +class CDImageBin : public CDImage +{ +public: + CDImageBin(); + ~CDImageBin() override; + + bool Open(const char* filename); + +private: + std::FILE* m_fp = nullptr; +}; + +CDImageBin::CDImageBin() = default; + +CDImageBin::~CDImageBin() +{ + if (m_fp) + std::fclose(m_fp); +} + +bool CDImageBin::Open(const char* filename) +{ + m_fp = std::fopen(filename, "rb"); + if (!m_fp) + { + Log_ErrorPrintf("Failed to open binfile '%s'", filename); + return false; + } + + const u32 track_sector_size = RAW_SECTOR_SIZE; + + // determine the length from the file + std::fseek(m_fp, 0, SEEK_END); + const u32 file_size = static_cast(std::ftell(m_fp)); + std::fseek(m_fp, 0, SEEK_SET); + + m_lba_count = file_size / track_sector_size; + + // Two seconds default pregap. + const u32 pregap_frames = 2 * FRAMES_PER_SECOND; + Index pregap_index = {}; + pregap_index.start_lba_on_disc = 0; + pregap_index.start_lba_in_track = static_cast(-static_cast(pregap_frames)); + pregap_index.length = pregap_frames; + pregap_index.track_number = 1; + pregap_index.is_pregap = true; + m_indices.push_back(pregap_index); + + // Data index. + Index data_index = {}; + data_index.file = m_fp; + data_index.file_offset = 0; + data_index.file_sector_size = track_sector_size; + data_index.start_lba_on_disc = pregap_index.length; + data_index.track_number = 1; + data_index.start_lba_in_track = 0; + data_index.length = m_lba_count; + m_indices.push_back(data_index); + + // Assume a single track. + m_tracks.push_back(Track{static_cast(1), data_index.start_lba_on_disc, static_cast(0), m_lba_count}); + + return Seek(1, Position{0, 0, 0}); +} + +std::unique_ptr CDImage::OpenBinImage(const char* filename) +{ + std::unique_ptr image = std::make_unique(); + if (!image->Open(filename)) + return {}; + + return image; +} diff --git a/src/common/cd_image_cuesheet.cpp b/src/common/cd_image_cuesheet.cpp new file mode 100644 index 000000000..77822bcb4 --- /dev/null +++ b/src/common/cd_image_cuesheet.cpp @@ -0,0 +1,184 @@ +#include "YBaseLib/Log.h" +#include "cd_image.h" +#include +#include +Log_SetChannel(CDImageCueSheet); + +class CDImageCueSheet : public CDImage +{ +public: + CDImageCueSheet(); + ~CDImageCueSheet() override; + + bool OpenAndParse(const char* filename); + +private: + Cd* m_cd = nullptr; + std::map m_files; +}; + +CDImageCueSheet::CDImageCueSheet() = default; + +CDImageCueSheet::~CDImageCueSheet() +{ + std::for_each(m_files.begin(), m_files.end(), [](const auto& it) { std::fclose(it.second); }); + cd_delete(m_cd); +} + +static std::string GetPathDirectory(const char* path) +{ + const char* forwardslash_ptr = std::strrchr(path, '/'); + const char* backslash_ptr = std::strrchr(path, '\\'); + const char* slash_ptr; + if (forwardslash_ptr && backslash_ptr) + slash_ptr = std::min(forwardslash_ptr, backslash_ptr); + else if (backslash_ptr) + slash_ptr = backslash_ptr; + else if (forwardslash_ptr) + slash_ptr = forwardslash_ptr; + else + return {}; + + std::string str; + str.append(path, slash_ptr - path + 1); + return str; +} + +bool CDImageCueSheet::OpenAndParse(const char* filename) +{ + std::FILE* cue_fp = std::fopen(filename, "rb"); + if (!cue_fp) + { + Log_ErrorPrintf("Failed to open cuesheet '%s'", filename); + return false; + } + + m_cd = cue_parse_file(cue_fp); + std::fclose(cue_fp); + if (!m_cd) + { + Log_ErrorPrintf("Failed to parse cuesheet '%s'", filename); + return false; + } + + // get the directory of the filename + std::string basepath = GetPathDirectory(filename); + + u32 disc_lba = 0; + + // for each track.. + const int num_tracks = cd_get_ntrack(m_cd); + for (int track_num = 1; track_num <= num_tracks; track_num++) + { + const ::Track* track = cd_get_track(m_cd, track_num); + const std::string track_filename = track_get_filename(track); + long track_start = track_get_start(track); + long track_length = track_get_length(track); + + auto it = m_files.find(track_filename); + if (it == m_files.end()) + { + std::string track_full_filename = basepath + track_filename; + std::FILE* track_fp = std::fopen(track_full_filename.c_str(), "rb"); + if (!track_fp) + { + Log_ErrorPrintf("Failed to open track filename '%s' (from '%s' and '%s')", track_full_filename.c_str(), + track_filename.c_str(), filename); + return false; + } + + it = m_files.emplace(track_filename, track_fp).first; + } + + // TODO: FIXME + const u32 track_sector_size = RAW_SECTOR_SIZE; + if (track_length < 0) + { + // determine the length from the file + std::fseek(it->second, 0, SEEK_END); + long file_size = std::ftell(it->second); + std::fseek(it->second, 0, SEEK_SET); + + file_size /= track_sector_size; + Assert(track_start < file_size); + track_length = file_size - track_start; + } + + // two seconds pregap is assumed if not specified + long pregap_frames = track_get_zero_pre(track); + if (pregap_frames < 0) + pregap_frames = 2 * FRAMES_PER_SECOND; + + // create the index for the pregap + if (pregap_frames > 0) + { + Index pregap_index = {}; + pregap_index.start_lba_on_disc = disc_lba; + pregap_index.start_lba_in_track = static_cast(static_cast(-pregap_frames)); + pregap_index.length = pregap_frames; + pregap_index.track_number = track_num; + pregap_index.is_pregap = true; + m_indices.push_back(pregap_index); + + disc_lba += pregap_index.length; + } + + // add the track itself + m_tracks.push_back( + Track{static_cast(track_num), disc_lba, static_cast(m_indices.size()), static_cast(track_length)}); + + // how many indices in this track? + Index last_index; + last_index.start_lba_on_disc = disc_lba; + last_index.track_number = track_num; + last_index.file = it->second; + last_index.file_sector_size = track_sector_size; + last_index.file_offset = 0; + + long last_index_offset = track_start; + for (int index_num = 1;; index_num++) + { + long index_offset = track_get_index(track, index_num); + if (index_offset < 0) + break; + + // add an index between the track indices + if (index_offset > last_index_offset) + { + last_index.length = index_offset - last_index_offset; + m_indices.push_back(last_index); + + disc_lba += last_index.length; + last_index.start_lba_in_track += last_index.length; + last_index.start_lba_on_disc = disc_lba; + last_index.length = 0; + } + + last_index.file_offset = index_offset * last_index.file_sector_size; + last_index_offset = index_offset; + } + + // and the last index is added here + const long track_end_index = track_start + track_length; + DebugAssert(track_end_index >= last_index_offset); + if (track_end_index > last_index_offset) + { + last_index.length = track_end_index - last_index_offset; + m_indices.push_back(last_index); + + disc_lba += last_index.length; + } + } + + m_lba_count = disc_lba; + return Seek(1, Position{0, 0, 0}); +} + +std::unique_ptr CDImage::OpenCueSheetImage(const char* filename) +{ + std::unique_ptr image = std::make_unique(); + if (!image->OpenAndParse(filename)) + return {}; + + return image; +} diff --git a/src/common/common.vcxproj b/src/common/common.vcxproj index 7dfcf0ec5..e72a4a951 100644 --- a/src/common/common.vcxproj +++ b/src/common/common.vcxproj @@ -62,6 +62,8 @@ + + @@ -87,6 +89,9 @@ {43540154-9e1e-409c-834f-b84be5621388} + + {6a4208ed-e3dc-41e1-81cd-f61025fc285a} + {2f2a2b7b-60b3-478c-921e-3633b3c45c3f} @@ -231,7 +236,7 @@ WIN32;_DEBUG;_LIB;%(PreprocessorDefinitions) true ProgramDatabase - $(SolutionDir)dep\YBaseLib\Include;$(SolutionDir)dep\libsamplerate\include;$(SolutionDir)dep\glad\include;$(SolutionDir)src;%(AdditionalIncludeDirectories) + $(SolutionDir)dep\YBaseLib\Include;$(SolutionDir)dep\libsamplerate\include;$(SolutionDir)dep\glad\include;$(SolutionDir)dep\libcue\include;$(SolutionDir)src;%(AdditionalIncludeDirectories) true stdcpp17 @@ -252,7 +257,7 @@ _ITERATOR_DEBUG_LEVEL=1;WIN32;_DEBUGFAST;_DEBUG;_LIB;%(PreprocessorDefinitions) true ProgramDatabase - $(SolutionDir)dep\YBaseLib\Include;$(SolutionDir)dep\libsamplerate\include;$(SolutionDir)dep\glad\include;$(SolutionDir)src;%(AdditionalIncludeDirectories) + $(SolutionDir)dep\YBaseLib\Include;$(SolutionDir)dep\libsamplerate\include;$(SolutionDir)dep\glad\include;$(SolutionDir)dep\libcue\include;$(SolutionDir)src;%(AdditionalIncludeDirectories) Default false true @@ -275,7 +280,7 @@ WIN32;_DEBUG;_LIB;%(PreprocessorDefinitions) true ProgramDatabase - $(SolutionDir)dep\YBaseLib\Include;$(SolutionDir)dep\libsamplerate\include;$(SolutionDir)dep\glad\include;$(SolutionDir)src;%(AdditionalIncludeDirectories) + $(SolutionDir)dep\YBaseLib\Include;$(SolutionDir)dep\libsamplerate\include;$(SolutionDir)dep\glad\include;$(SolutionDir)dep\libcue\include;$(SolutionDir)src;%(AdditionalIncludeDirectories) true stdcpp17 @@ -296,7 +301,7 @@ _ITERATOR_DEBUG_LEVEL=1;WIN32;_DEBUGFAST;_DEBUG;_LIB;%(PreprocessorDefinitions) true ProgramDatabase - $(SolutionDir)dep\YBaseLib\Include;$(SolutionDir)dep\libsamplerate\include;$(SolutionDir)dep\glad\include;$(SolutionDir)src;%(AdditionalIncludeDirectories) + $(SolutionDir)dep\YBaseLib\Include;$(SolutionDir)dep\libsamplerate\include;$(SolutionDir)dep\glad\include;$(SolutionDir)dep\libcue\include;$(SolutionDir)src;%(AdditionalIncludeDirectories) Default false true @@ -320,7 +325,7 @@ true WIN32;NDEBUG;_LIB;%(PreprocessorDefinitions) true - $(SolutionDir)dep\YBaseLib\Include;$(SolutionDir)dep\libsamplerate\include;$(SolutionDir)dep\glad\include;$(SolutionDir)src;%(AdditionalIncludeDirectories) + $(SolutionDir)dep\YBaseLib\Include;$(SolutionDir)dep\libsamplerate\include;$(SolutionDir)dep\glad\include;$(SolutionDir)dep\libcue\include;$(SolutionDir)src;%(AdditionalIncludeDirectories) true stdcpp17 @@ -344,7 +349,7 @@ true WIN32;NDEBUG;_LIB;%(PreprocessorDefinitions) true - $(SolutionDir)dep\YBaseLib\Include;$(SolutionDir)dep\libsamplerate\include;$(SolutionDir)dep\glad\include;$(SolutionDir)src;%(AdditionalIncludeDirectories) + $(SolutionDir)dep\YBaseLib\Include;$(SolutionDir)dep\libsamplerate\include;$(SolutionDir)dep\glad\include;$(SolutionDir)dep\libcue\include;$(SolutionDir)src;%(AdditionalIncludeDirectories) true true stdcpp17 @@ -369,7 +374,7 @@ true WIN32;NDEBUG;_LIB;%(PreprocessorDefinitions) true - $(SolutionDir)dep\YBaseLib\Include;$(SolutionDir)dep\libsamplerate\include;$(SolutionDir)dep\glad\include;$(SolutionDir)src;%(AdditionalIncludeDirectories) + $(SolutionDir)dep\YBaseLib\Include;$(SolutionDir)dep\libsamplerate\include;$(SolutionDir)dep\glad\include;$(SolutionDir)dep\libcue\include;$(SolutionDir)src;%(AdditionalIncludeDirectories) true stdcpp17 @@ -393,7 +398,7 @@ true WIN32;NDEBUG;_LIB;%(PreprocessorDefinitions) true - $(SolutionDir)dep\YBaseLib\Include;$(SolutionDir)dep\libsamplerate\include;$(SolutionDir)dep\glad\include;$(SolutionDir)src;%(AdditionalIncludeDirectories) + $(SolutionDir)dep\YBaseLib\Include;$(SolutionDir)dep\libsamplerate\include;$(SolutionDir)dep\glad\include;$(SolutionDir)dep\libcue\include;$(SolutionDir)src;%(AdditionalIncludeDirectories) true true stdcpp17 diff --git a/src/common/common.vcxproj.filters b/src/common/common.vcxproj.filters index 063903cf6..bde502a5b 100644 --- a/src/common/common.vcxproj.filters +++ b/src/common/common.vcxproj.filters @@ -42,6 +42,8 @@ + + diff --git a/src/common/types.h b/src/common/types.h index 5b8ee1d09..9f3aa9138 100644 --- a/src/common/types.h +++ b/src/common/types.h @@ -145,12 +145,12 @@ constexpr u32 Truncate32(TValue value) } // BCD helpers -inline u8 DecimalToBCD(u8 value) +constexpr u8 DecimalToBCD(u8 value) { return ((value / 10) << 4) + (value % 10); } -inline u8 BCDToDecimal(u8 value) +constexpr u8 BCDToDecimal(u8 value) { return ((value >> 4) * 10) + (value % 16); } diff --git a/src/core/cdrom.cpp b/src/core/cdrom.cpp index 6f91a83fd..b91315c60 100644 --- a/src/core/cdrom.cpp +++ b/src/core/cdrom.cpp @@ -108,7 +108,7 @@ bool CDROM::DoState(StateWrapper& sw) sw.Do(&m_data_fifo); sw.Do(&m_sector_buffer); - u64 media_lba = m_media ? m_media->GetCurrentLBA() : 0; + u32 media_lba = m_media ? m_media->GetPositionOnDisc() : 0; std::string media_filename = m_media ? m_media->GetFileName() : std::string(); sw.Do(&media_filename); sw.Do(&media_lba); @@ -124,8 +124,8 @@ bool CDROM::DoState(StateWrapper& sw) m_media.reset(); if (!media_filename.empty()) { - m_media = std::make_unique(); - if (!m_media->Open(media_filename.c_str()) || !m_media->Seek(media_lba)) + m_media = CDImage::Open(media_filename.c_str()); + if (!m_media || !m_media->Seek(media_lba)) { Log_ErrorPrintf("Failed to re-insert CD media from save state: '%s'. Ejecting.", media_filename.c_str()); RemoveMedia(); @@ -138,8 +138,8 @@ bool CDROM::DoState(StateWrapper& sw) bool CDROM::InsertMedia(const char* filename) { - auto media = std::make_unique(); - if (!media->Open(filename)) + auto media = CDImage::Open(filename); + if (!media) { Log_ErrorPrintf("Failed to open media at '%s'", filename); return false; @@ -593,7 +593,7 @@ void CDROM::ExecuteCommand() { Assert(m_setloc_dirty); StopReading(); - if (!m_media || !m_media->Seek(m_setloc.minute, m_setloc.second, m_setloc.frame)) + if (!m_media || !m_media->Seek(m_setloc)) { Panic("Error in Setloc command"); return; @@ -649,7 +649,7 @@ void CDROM::ExecuteCommand() // TODO: Seek timing and clean up... if (m_setloc_dirty) { - if (!m_media || !m_media->Seek(m_setloc.minute, m_setloc.second, m_setloc.frame)) + if (!m_media || !m_media->Seek(m_setloc)) { Panic("Seek error"); } @@ -830,7 +830,7 @@ void CDROM::DoSectorRead() std::memcpy(&m_last_sector_header, &m_sector_buffer[SECTOR_SYNC_SIZE], sizeof(m_last_sector_header)); std::memcpy(&m_last_sector_subheader, &m_sector_buffer[SECTOR_SYNC_SIZE + sizeof(m_last_sector_header)], sizeof(m_last_sector_subheader)); - Log_DevPrintf("Read sector %llu: mode %u submode 0x%02X", m_media->GetCurrentLBA(), + Log_DevPrintf("Read sector %u: mode %u submode 0x%02X", m_media->GetPositionOnDisc(), ZeroExtend32(m_last_sector_header.sector_mode), ZeroExtend32(m_last_sector_subheader.submode.bits)); bool pass_to_cpu = true; @@ -1034,10 +1034,13 @@ void CDROM::LoadDataFIFO() void CDROM::DrawDebugWindow() { + static const ImVec4 active_color{1.0f, 1.0f, 1.0f, 1.0f}; + static const ImVec4 inactive_color{0.4f, 0.4f, 0.4f, 1.0f}; + if (!m_show_cdrom_state) return; - ImGui::SetNextWindowSize(ImVec2(800, 400), ImGuiCond_FirstUseEver); + ImGui::SetNextWindowSize(ImVec2(800, 500), ImGuiCond_FirstUseEver); if (!ImGui::Begin("CDROM State", &m_show_cdrom_state)) { ImGui::End(); @@ -1049,11 +1052,15 @@ void CDROM::DrawDebugWindow() { if (m_media) { - const auto [pos_minute, pos_second, pos_frame] = m_media->GetPositionMSF(); + const auto [disc_minute, disc_second, disc_frame] = m_media->GetMSFPositionOnDisc(); + const auto [track_minute, track_second, track_frame] = m_media->GetMSFPositionInTrack(); ImGui::Text("Filename: %s", m_media->GetFileName().c_str()); - ImGui::Text("Position (MSF): %02u:%02u:%02u", pos_minute, pos_second, pos_frame); - ImGui::Text("Position (LBA): %llu", m_media->GetCurrentLBA()); + ImGui::Text("Disc Position: MSF[%02u:%02u:%02u] LBA[%u]", disc_minute, disc_second, disc_frame); + ImGui::Text("Track Position: Number[%u] MSF[%02u:%02u:%02u] LBA[%u]", m_media->GetTrackNumber(), track_minute, + track_second, track_frame); + ImGui::Text("Last Sector: %02X:%02X:%02X (Mode %u)", m_last_sector_header.minute, m_last_sector_header.second, + m_last_sector_header.frame, m_last_sector_header.sector_mode); } else { @@ -1072,62 +1079,99 @@ void CDROM::DrawDebugWindow() ImGui::Text("Mode Status"); ImGui::NextColumn(); - ImGui::Text("ADPBUSY: %s", m_status.ADPBUSY ? "Yes" : "No"); + ImGui::TextColored(m_status.ADPBUSY ? active_color : inactive_color, "ADPBUSY: %s", + m_status.ADPBUSY ? "Yes" : "No"); ImGui::NextColumn(); - ImGui::Text("Error: %s", m_secondary_status.error ? "Yes" : "No"); + ImGui::TextColored(m_secondary_status.error ? active_color : inactive_color, "Error: %s", + m_secondary_status.error ? "Yes" : "No"); ImGui::NextColumn(); - ImGui::Text("CDDA: %s", m_mode.cdda ? "Yes" : "No"); + ImGui::TextColored(m_mode.cdda ? active_color : inactive_color, "CDDA: %s", m_mode.cdda ? "Yes" : "No"); ImGui::NextColumn(); - ImGui::Text("PRMEMPTY: %s", m_status.PRMEMPTY ? "Yes" : "No"); + ImGui::TextColored(m_status.PRMEMPTY ? active_color : inactive_color, "PRMEMPTY: %s", + m_status.PRMEMPTY ? "Yes" : "No"); ImGui::NextColumn(); - ImGui::Text("Motor On: %s", m_secondary_status.motor_on ? "Yes" : "No"); + ImGui::TextColored(m_secondary_status.motor_on ? active_color : inactive_color, "Motor On: %s", + m_secondary_status.motor_on ? "Yes" : "No"); ImGui::NextColumn(); - ImGui::Text("Auto Pause: %s", m_mode.auto_pause ? "Yes" : "No"); + ImGui::TextColored(m_mode.auto_pause ? active_color : inactive_color, "Auto Pause: %s", + m_mode.auto_pause ? "Yes" : "No"); ImGui::NextColumn(); - ImGui::Text("PRMWRDY: %s", m_status.PRMWRDY ? "Yes" : "No"); + ImGui::TextColored(m_status.PRMWRDY ? active_color : inactive_color, "PRMWRDY: %s", + m_status.PRMWRDY ? "Yes" : "No"); ImGui::NextColumn(); - ImGui::Text("Seek Error: %s", m_secondary_status.seek_error ? "Yes" : "No"); + ImGui::TextColored(m_secondary_status.seek_error ? active_color : inactive_color, "Seek Error: %s", + m_secondary_status.seek_error ? "Yes" : "No"); ImGui::NextColumn(); - ImGui::Text("Report Audio: %s", m_mode.report_audio ? "Yes" : "No"); + ImGui::TextColored(m_mode.report_audio ? active_color : inactive_color, "Report Audio: %s", + m_mode.report_audio ? "Yes" : "No"); ImGui::NextColumn(); - ImGui::Text("RSLRRDY: %s", m_status.RSLRRDY ? "Yes" : "No"); + ImGui::TextColored(m_status.RSLRRDY ? active_color : inactive_color, "RSLRRDY: %s", + m_status.RSLRRDY ? "Yes" : "No"); ImGui::NextColumn(); - ImGui::Text("ID Error: %s", m_secondary_status.id_error ? "Yes" : "No"); + ImGui::TextColored(m_secondary_status.id_error ? active_color : inactive_color, "ID Error: %s", + m_secondary_status.id_error ? "Yes" : "No"); ImGui::NextColumn(); - ImGui::Text("XA Filter: %s (File %u Channel %u)", m_mode.xa_filter ? "Yes" : "No", m_filter_file_number, - m_filter_channel_number); + ImGui::TextColored(m_mode.xa_filter ? active_color : inactive_color, "XA Filter: %s (File %u Channel %u)", + m_mode.xa_filter ? "Yes" : "No", m_filter_file_number, m_filter_channel_number); ImGui::NextColumn(); - ImGui::Text("DRQSTS: %s", m_status.DRQSTS ? "Yes" : "No"); + ImGui::TextColored(m_status.DRQSTS ? active_color : inactive_color, "DRQSTS: %s", m_status.DRQSTS ? "Yes" : "No"); ImGui::NextColumn(); - ImGui::Text("Shell Open: %s", m_secondary_status.shell_open ? "Yes" : "No"); + ImGui::TextColored(m_secondary_status.shell_open ? active_color : inactive_color, "Shell Open: %s", + m_secondary_status.shell_open ? "Yes" : "No"); ImGui::NextColumn(); - ImGui::Text("Ignore Bit: %s", m_mode.ignore_bit ? "Yes" : "No"); + ImGui::TextColored(m_mode.ignore_bit ? active_color : inactive_color, "Ignore Bit: %s", + m_mode.ignore_bit ? "Yes" : "No"); ImGui::NextColumn(); - ImGui::Text("BUSYSTS: %s", m_status.BUSYSTS ? "Yes" : "No"); + ImGui::TextColored(m_status.BUSYSTS ? active_color : inactive_color, "BUSYSTS: %s", + m_status.BUSYSTS ? "Yes" : "No"); ImGui::NextColumn(); - ImGui::Text("Reading: %s", m_secondary_status.reading ? "Yes" : "No"); + ImGui::TextColored(m_secondary_status.reading ? active_color : inactive_color, "Reading: %s", + m_secondary_status.reading ? "Yes" : "No"); ImGui::NextColumn(); - ImGui::Text("Read Raw Sectors: %s", m_mode.read_raw_sector ? "Yes" : "No"); + ImGui::TextColored(m_mode.read_raw_sector ? active_color : inactive_color, "Read Raw Sectors: %s", + m_mode.read_raw_sector ? "Yes" : "No"); ImGui::NextColumn(); ImGui::NextColumn(); - ImGui::Text("Seeking: %s", m_secondary_status.seeking ? "Yes" : "No"); + ImGui::TextColored(m_secondary_status.seeking ? active_color : inactive_color, "Seeking: %s", + m_secondary_status.seeking ? "Yes" : "No"); ImGui::NextColumn(); - ImGui::Text("XA Enable: %s", m_mode.xa_enable ? "Yes" : "No"); + ImGui::TextColored(m_mode.xa_enable ? active_color : inactive_color, "XA Enable: %s", + m_mode.xa_enable ? "Yes" : "No"); ImGui::NextColumn(); ImGui::NextColumn(); - ImGui::Text("Playing CDDA: %s", m_secondary_status.playing_cdda ? "Yes" : "No"); + ImGui::TextColored(m_secondary_status.playing_cdda ? active_color : inactive_color, "Playing CDDA: %s", + m_secondary_status.playing_cdda ? "Yes" : "No"); ImGui::NextColumn(); - ImGui::Text("Double Speed: %s", m_mode.double_speed ? "Yes" : "No"); + ImGui::TextColored(m_mode.double_speed ? active_color : inactive_color, "Double Speed: %s", + m_mode.double_speed ? "Yes" : "No"); ImGui::NextColumn(); ImGui::Columns(1); + ImGui::NewLine(); + + ImGui::Text("Interrupt Enable Register: 0x%02X", m_interrupt_enable_register); + ImGui::Text("Interrupt Flag Register: 0x%02X", m_interrupt_flag_register); + } + + if (ImGui::CollapsingHeader("CD Audio", ImGuiTreeNodeFlags_DefaultOpen)) + { + const bool playing_anything = m_reading && m_mode.xa_enable; + ImGui::TextColored(playing_anything ? active_color : inactive_color, "Playing: %s", + (m_reading && m_mode.xa_enable) ? "XA-ADPCM" : "Disabled"); + ImGui::TextColored(m_muted ? inactive_color : active_color, "Muted: %s", m_muted ? "Yes" : "No"); + ImGui::Text("Left Output: Left Channel=%02X (%u%%), Right Channel=%02X (%u%%)", m_cd_audio_volume_matrix[0][0], + ZeroExtend32(m_cd_audio_volume_matrix[0][0]) * 100 / 0x80, m_cd_audio_volume_matrix[0][1], + ZeroExtend32(m_cd_audio_volume_matrix[0][1]) * 100 / 0x80); + ImGui::Text("Right Output: Left Channel=%02X (%u%%), Right Channel=%02X (%u%%)", m_cd_audio_volume_matrix[1][0], + ZeroExtend32(m_cd_audio_volume_matrix[1][0]) * 100 / 0x80, m_cd_audio_volume_matrix[1][1], + ZeroExtend32(m_cd_audio_volume_matrix[1][1]) * 100 / 0x80); } ImGui::End(); diff --git a/src/core/cdrom.h b/src/core/cdrom.h index ff4a869c7..0a5e584b4 100644 --- a/src/core/cdrom.h +++ b/src/core/cdrom.h @@ -207,7 +207,7 @@ private: u8 m_interrupt_enable_register = INTERRUPT_REGISTER_MASK; u8 m_interrupt_flag_register = 0; - Loc m_setloc = {}; + CDImage::Position m_setloc = {}; bool m_setloc_dirty = false; u8 m_filter_file_number = 0;