mirror of
https://github.com/RetroDECK/Duckstation.git
synced 2024-11-22 22:05:38 +00:00
Common: Add helper functions for decoding CD-XA ADPCM
This commit is contained in:
parent
b2632db0a7
commit
2b6ebf9955
|
@ -16,10 +16,9 @@ public:
|
||||||
DATA_SECTOR_SIZE = 2048,
|
DATA_SECTOR_SIZE = 2048,
|
||||||
SECTOR_SYNC_SIZE = 12,
|
SECTOR_SYNC_SIZE = 12,
|
||||||
SECTOR_HEADER_SIZE = 4,
|
SECTOR_HEADER_SIZE = 4,
|
||||||
SECTOR_XA_SUBHEADER_SIZE = 4,
|
|
||||||
FRAMES_PER_SECOND = 75, // "sectors"
|
FRAMES_PER_SECOND = 75, // "sectors"
|
||||||
SECONDS_PER_MINUTE = 60,
|
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
|
enum class ReadMode : u32
|
||||||
|
@ -37,33 +36,6 @@ public:
|
||||||
u8 sector_mode;
|
u8 sector_mode;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct XASubHeader
|
|
||||||
{
|
|
||||||
u8 file_number;
|
|
||||||
u8 channel_number;
|
|
||||||
union Submode
|
|
||||||
{
|
|
||||||
u8 bits;
|
|
||||||
BitField<u8, bool, 0, 1> eor;
|
|
||||||
BitField<u8, bool, 1, 1> video;
|
|
||||||
BitField<u8, bool, 2, 1> audio;
|
|
||||||
BitField<u8, bool, 3, 1> data;
|
|
||||||
BitField<u8, bool, 4, 1> trigger;
|
|
||||||
BitField<u8, bool, 5, 1> form2;
|
|
||||||
BitField<u8, bool, 6, 1> realtime;
|
|
||||||
BitField<u8, bool, 7, 1> eof;
|
|
||||||
} submode;
|
|
||||||
union Codinginfo
|
|
||||||
{
|
|
||||||
u8 bits;
|
|
||||||
|
|
||||||
BitField<u8, u8, 0, 2> mono_stereo;
|
|
||||||
BitField<u8, u8, 2, 2> sample_rate;
|
|
||||||
BitField<u8, u8, 4, 2> bits_per_sample;
|
|
||||||
BitField<u8, bool, 6, 1> emphasis;
|
|
||||||
} codinginfo;
|
|
||||||
};
|
|
||||||
|
|
||||||
// Conversion helpers.
|
// Conversion helpers.
|
||||||
static constexpr u64 MSFToLBA(u32 pregap_seconds, u32 minute, u32 second, u32 frame);
|
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);
|
static constexpr void LBAToMSF(u32 pregap_seconds, u64 lba, u32* minute, u32* second, u32* frame);
|
||||||
|
|
98
src/common/cd_xa.cpp
Normal file
98
src/common/cd_xa.cpp
Normal file
|
@ -0,0 +1,98 @@
|
||||||
|
#include "cd_xa.h"
|
||||||
|
#include "cd_image.h"
|
||||||
|
#include <algorithm>
|
||||||
|
#include <array>
|
||||||
|
|
||||||
|
namespace CDXA {
|
||||||
|
static constexpr std::array<s32, 4> s_xa_adpcm_filter_table_pos = {{0, 60, 115, 98}};
|
||||||
|
static constexpr std::array<s32, 4> s_xa_adpcm_filter_table_neg = {{0, 0, -52, -55}};
|
||||||
|
|
||||||
|
template<bool IS_STEREO, bool IS_8BIT>
|
||||||
|
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<s16>(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<s16>(std::clamp<s32>(interp_sample, -0x8000, 0x7FFF));
|
||||||
|
out_samples_ptr += out_samples_increment;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
template<bool IS_STEREO, bool IS_8BIT>
|
||||||
|
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<IS_STEREO, IS_8BIT>(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<const XASubHeader*>(
|
||||||
|
reinterpret_cast<const u8*>(data) + CDImage::SECTOR_SYNC_SIZE + sizeof(CDImage::SectorHeader));
|
||||||
|
|
||||||
|
// The XA subheader is repeated?
|
||||||
|
const u8* chunk_ptr = reinterpret_cast<const u8*>(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<false, false>(chunk_ptr, samples, last_samples);
|
||||||
|
else
|
||||||
|
DecodeXA_ADPCMChunks<true, false>(chunk_ptr, samples, last_samples);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (subheader->codinginfo.mono_stereo != 1)
|
||||||
|
DecodeXA_ADPCMChunks<false, true>(chunk_ptr, samples, last_samples);
|
||||||
|
else
|
||||||
|
DecodeXA_ADPCMChunks<true, true>(chunk_ptr, samples, last_samples);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace CDXA
|
70
src/common/cd_xa.h
Normal file
70
src/common/cd_xa.h
Normal file
|
@ -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<u8, bool, 0, 1> eor;
|
||||||
|
BitField<u8, bool, 1, 1> video;
|
||||||
|
BitField<u8, bool, 2, 1> audio;
|
||||||
|
BitField<u8, bool, 3, 1> data;
|
||||||
|
BitField<u8, bool, 4, 1> trigger;
|
||||||
|
BitField<u8, bool, 5, 1> form2;
|
||||||
|
BitField<u8, bool, 6, 1> realtime;
|
||||||
|
BitField<u8, bool, 7, 1> eof;
|
||||||
|
} submode;
|
||||||
|
union Codinginfo
|
||||||
|
{
|
||||||
|
u8 bits;
|
||||||
|
|
||||||
|
BitField<u8, u8, 0, 2> mono_stereo;
|
||||||
|
BitField<u8, u8, 2, 2> sample_rate;
|
||||||
|
BitField<u8, u8, 4, 2> bits_per_sample;
|
||||||
|
BitField<u8, bool, 6, 1> 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<u8, u8, 0, 4> shift;
|
||||||
|
BitField<u8, u8, 4, 2> 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
|
|
@ -56,6 +56,7 @@
|
||||||
<ClInclude Include="state_wrapper.h" />
|
<ClInclude Include="state_wrapper.h" />
|
||||||
<ClInclude Include="types.h" />
|
<ClInclude Include="types.h" />
|
||||||
<ClInclude Include="type_registry.h" />
|
<ClInclude Include="type_registry.h" />
|
||||||
|
<ClInclude Include="cd_xa.h" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ClCompile Include="audio.cpp" />
|
<ClCompile Include="audio.cpp" />
|
||||||
|
@ -74,6 +75,7 @@
|
||||||
<ClCompile Include="object_type_info.cpp" />
|
<ClCompile Include="object_type_info.cpp" />
|
||||||
<ClCompile Include="property.cpp" />
|
<ClCompile Include="property.cpp" />
|
||||||
<ClCompile Include="state_wrapper.cpp" />
|
<ClCompile Include="state_wrapper.cpp" />
|
||||||
|
<ClCompile Include="cd_xa.cpp" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<Natvis Include="bitfield.natvis" />
|
<Natvis Include="bitfield.natvis" />
|
||||||
|
|
|
@ -22,6 +22,7 @@
|
||||||
<ClInclude Include="fifo_queue.h" />
|
<ClInclude Include="fifo_queue.h" />
|
||||||
<ClInclude Include="cd_image.h" />
|
<ClInclude Include="cd_image.h" />
|
||||||
<ClInclude Include="audio_stream.h" />
|
<ClInclude Include="audio_stream.h" />
|
||||||
|
<ClInclude Include="cd_xa.h" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ClCompile Include="hdd_image.cpp" />
|
<ClCompile Include="hdd_image.cpp" />
|
||||||
|
@ -40,6 +41,7 @@
|
||||||
<ClCompile Include="gl_texture.cpp" />
|
<ClCompile Include="gl_texture.cpp" />
|
||||||
<ClCompile Include="cd_image.cpp" />
|
<ClCompile Include="cd_image.cpp" />
|
||||||
<ClCompile Include="audio_stream.cpp" />
|
<ClCompile Include="audio_stream.cpp" />
|
||||||
|
<ClCompile Include="cd_xa.cpp" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<Natvis Include="bitfield.natvis" />
|
<Natvis Include="bitfield.natvis" />
|
||||||
|
|
Loading…
Reference in a new issue