From 2b6ebf99559196491da9df06bf90b09665d218db Mon Sep 17 00:00:00 2001 From: Connor McLaughlin Date: Tue, 15 Oct 2019 17:24:11 +1000 Subject: [PATCH] Common: Add helper functions for decoding CD-XA ADPCM --- src/common/cd_image.h | 30 +--------- src/common/cd_xa.cpp | 98 +++++++++++++++++++++++++++++++ src/common/cd_xa.h | 70 ++++++++++++++++++++++ src/common/common.vcxproj | 2 + src/common/common.vcxproj.filters | 2 + 5 files changed, 173 insertions(+), 29 deletions(-) create mode 100644 src/common/cd_xa.cpp create mode 100644 src/common/cd_xa.h diff --git a/src/common/cd_image.h b/src/common/cd_image.h index 8b3f6e1a8..b8f9f57ac 100644 --- a/src/common/cd_image.h +++ b/src/common/cd_image.h @@ -16,10 +16,9 @@ public: DATA_SECTOR_SIZE = 2048, SECTOR_SYNC_SIZE = 12, SECTOR_HEADER_SIZE = 4, - SECTOR_XA_SUBHEADER_SIZE = 4, FRAMES_PER_SECOND = 75, // "sectors" SECONDS_PER_MINUTE = 60, - FRAMES_PER_MINUTE = FRAMES_PER_SECOND * SECONDS_PER_MINUTE, + FRAMES_PER_MINUTE = FRAMES_PER_SECOND * SECONDS_PER_MINUTE }; enum class ReadMode : u32 @@ -37,33 +36,6 @@ public: u8 sector_mode; }; - struct XASubHeader - { - u8 file_number; - u8 channel_number; - union Submode - { - u8 bits; - BitField eor; - BitField video; - BitField audio; - BitField data; - BitField trigger; - BitField form2; - BitField realtime; - BitField eof; - } submode; - union Codinginfo - { - u8 bits; - - BitField mono_stereo; - BitField sample_rate; - BitField bits_per_sample; - BitField emphasis; - } codinginfo; - }; - // Conversion helpers. static constexpr u64 MSFToLBA(u32 pregap_seconds, u32 minute, u32 second, u32 frame); static constexpr void LBAToMSF(u32 pregap_seconds, u64 lba, u32* minute, u32* second, u32* frame); diff --git a/src/common/cd_xa.cpp b/src/common/cd_xa.cpp new file mode 100644 index 000000000..4c5ec82ea --- /dev/null +++ b/src/common/cd_xa.cpp @@ -0,0 +1,98 @@ +#include "cd_xa.h" +#include "cd_image.h" +#include +#include + +namespace CDXA { +static constexpr std::array s_xa_adpcm_filter_table_pos = {{0, 60, 115, 98}}; +static constexpr std::array s_xa_adpcm_filter_table_neg = {{0, 0, -52, -55}}; + +template +static void DecodeXA_ADPCMChunk(const u8* chunk_ptr, s16* samples, s32* last_samples) +{ + // The data layout is annoying here. Each word of data is interleaved with the other blocks, requiring multiple + // passes to decode the whole chunk. + constexpr u32 NUM_BLOCKS = IS_8BIT ? 4 : 8; + constexpr u32 WORDS_PER_BLOCK = 28; + + const u8* headers_ptr = chunk_ptr + 4; + const u8* words_ptr = chunk_ptr + 16; + + for (u32 block = 0; block < NUM_BLOCKS; block++) + { + const XA_ADPCMBlockHeader block_header{headers_ptr[block]}; + const u8 shift = block_header.GetShift(); + const u8 filter = block_header.GetFilter(); + const s32 filter_pos = s_xa_adpcm_filter_table_pos[filter]; + const s32 filter_neg = s_xa_adpcm_filter_table_neg[filter]; + + s16* out_samples_ptr = + IS_STEREO ? &samples[(block / 2) * (WORDS_PER_BLOCK * 2) + (block % 2)] : &samples[block * WORDS_PER_BLOCK]; + constexpr u32 out_samples_increment = IS_STEREO ? 2 : 1; + + for (u32 word = 0; word < 28; word++) + { + // NOTE: assumes LE + u32 word_data; + std::memcpy(&word_data, &words_ptr[word * sizeof(u32)], sizeof(word_data)); + + // extract nibble from block + const u32 nibble = IS_8BIT ? ((word_data >> (block * 8)) & 0xFF) : ((word_data >> (block * 4)) & 0x0F); + const s16 sample = static_cast(Truncate16(nibble << 12)) >> shift; + + // mix in previous values + s32* prev = IS_STEREO ? &last_samples[(block & 1) * 2] : last_samples; + const s32 interp_sample = s32(sample) + ((prev[0] * filter_pos) + (prev[1] * filter_neg) + 32) / 64; + + // update previous values + prev[1] = prev[0]; + prev[0] = interp_sample; + + *out_samples_ptr = static_cast(std::clamp(interp_sample, -0x8000, 0x7FFF)); + out_samples_ptr += out_samples_increment; + } + } +} + +template +static void DecodeXA_ADPCMChunks(const u8* chunk_ptr, s16* samples, s32* last_samples) +{ + constexpr u32 NUM_CHUNKS = 18; + constexpr u32 CHUNK_SIZE_IN_BYTES = 128; + constexpr u32 WORDS_PER_CHUNK = 28; + constexpr u32 SAMPLES_PER_CHUNK = 28 * (IS_8BIT ? 4 : 8); + + for (u32 i = 0; i < NUM_CHUNKS; i++) + { + DecodeXA_ADPCMChunk(chunk_ptr, samples, last_samples); + samples += SAMPLES_PER_CHUNK; + chunk_ptr += CHUNK_SIZE_IN_BYTES; + } +} + +void DecodeADPCMSector(const void* data, s16* samples, s32* last_samples) +{ + const XASubHeader* subheader = reinterpret_cast( + reinterpret_cast(data) + CDImage::SECTOR_SYNC_SIZE + sizeof(CDImage::SectorHeader)); + + // The XA subheader is repeated? + const u8* chunk_ptr = reinterpret_cast(data) + CDImage::SECTOR_SYNC_SIZE + sizeof(CDImage::SectorHeader) + + sizeof(XASubHeader) + 4; + + if (subheader->codinginfo.bits_per_sample != 1) + { + if (subheader->codinginfo.mono_stereo != 1) + DecodeXA_ADPCMChunks(chunk_ptr, samples, last_samples); + else + DecodeXA_ADPCMChunks(chunk_ptr, samples, last_samples); + } + else + { + if (subheader->codinginfo.mono_stereo != 1) + DecodeXA_ADPCMChunks(chunk_ptr, samples, last_samples); + else + DecodeXA_ADPCMChunks(chunk_ptr, samples, last_samples); + } +} + +} // namespace CDXA diff --git a/src/common/cd_xa.h b/src/common/cd_xa.h new file mode 100644 index 000000000..3e996d16c --- /dev/null +++ b/src/common/cd_xa.h @@ -0,0 +1,70 @@ +#pragma once +#include "bitfield.h" +#include "types.h" + +namespace CDXA { +enum +{ + XA_SUBHEADER_SIZE = 4, + XA_ADPCM_SAMPLES_PER_SECTOR_4BIT = 4032, // 28 words * 8 nibbles per word * 18 chunks + XA_ADPCM_SAMPLES_PER_SECTOR_8BIT = 2016 // 28 words * 4 bytes per word * 18 chunks +}; + +struct XASubHeader +{ + u8 file_number; + u8 channel_number; + union Submode + { + u8 bits; + BitField eor; + BitField video; + BitField audio; + BitField data; + BitField trigger; + BitField form2; + BitField realtime; + BitField eof; + } submode; + union Codinginfo + { + u8 bits; + + BitField mono_stereo; + BitField sample_rate; + BitField bits_per_sample; + BitField emphasis; + + bool IsStereo() const { return mono_stereo == 1; } + bool IsHalfSampleRate() const { return sample_rate == 1; } + u32 GetSampleRate() const { return sample_rate == 1 ? 18900 : 37800; } + u32 GetBitsPerSample() const { return bits_per_sample == 1 ? 8 : 4; } + u32 GetSamplesPerSector() const + { + return bits_per_sample == 1 ? XA_ADPCM_SAMPLES_PER_SECTOR_8BIT : XA_ADPCM_SAMPLES_PER_SECTOR_4BIT; + } + } codinginfo; +}; + +union XA_ADPCMBlockHeader +{ + u8 bits; + + BitField shift; + BitField filter; + + // For both 4bit and 8bit ADPCM, reserved shift values 13..15 will act same as shift=9). + u8 GetShift() const + { + const u8 shift_value = shift; + return (shift_value > 12) ? 9 : shift_value; + } + + u8 GetFilter() const { return filter; } +}; +static_assert(sizeof(XA_ADPCMBlockHeader) == 1, "XA-ADPCM block header is one byte"); + +// Decodes XA-ADPCM samples in an audio sector. Stereo samples are interleaved with left first. +void DecodeADPCMSector(const void* data, s16* samples, s32* last_samples); + +} // namespace CDXA \ No newline at end of file diff --git a/src/common/common.vcxproj b/src/common/common.vcxproj index 2a2e9f5da..7dfcf0ec5 100644 --- a/src/common/common.vcxproj +++ b/src/common/common.vcxproj @@ -56,6 +56,7 @@ + @@ -74,6 +75,7 @@ + diff --git a/src/common/common.vcxproj.filters b/src/common/common.vcxproj.filters index d98ca1c1a..063903cf6 100644 --- a/src/common/common.vcxproj.filters +++ b/src/common/common.vcxproj.filters @@ -22,6 +22,7 @@ + @@ -40,6 +41,7 @@ +