Add TXTH codec XA_EA [Road Rash videos (SAT)]

This commit is contained in:
bnnm 2021-10-24 13:12:38 +02:00
parent be7e4821d7
commit 2570fcac4d
5 changed files with 67 additions and 17 deletions

View File

@ -157,6 +157,8 @@ as explained below, but often will use default values. Accepted codec strings:
# * For rare EA games [Harry Potter and the Chamber of Secrets (PC)]
# - XA CD-XA ADPCM (ISO 2048 mode1/data streams without subchannels)
# * For rare Saturn and PS2 games [Phantasy Star Collection (SAT), Fantavision (PS2), EA SAT videos]
# - XA_EA Electronic Arts XA ADPCM variation
# * For rare Saturn games [EA SAT videos]
# - CP_YM Capcom's Yamaha ADPCM
# * For rare Saturn games [Marvel Super Heroes vs Street Fighter (SAT)]
codec = (codec string)

View File

@ -5,14 +5,36 @@
//#define XA_FLOAT 1
#if XA_FLOAT
/* floats as defined by the spec, but PS1's SPU would use int math */
static const float K0[4] = { 0.0, 0.9375, 1.796875, 1.53125 };
static const float K1[4] = { 0.0, 0.0, -0.8125, -0.859375 };
static const float K0[4+12] = { 0.0, 0.9375, 1.796875, 1.53125 };
static const float K1[4+12] = { 0.0, 0.0, -0.8125, -0.859375 };
#else
/* K0/1 floats to int with N=6: K*2^6 = K*(1<<6) = K*64 */
static const int K0[4] = { 0, 60, 115, 98 };
static const int K1[4] = { 0, 0, -52, -55 };
/* K0/1 floats to int with N=6: K*2^6 = K*(1<<6) = K*64 (upper ranges are supposedly 0)*/
static const int K0[4+12] = { 0, 60, 115, 98 };
static const int K1[4+12] = { 0, 0, -52, -55 };
#endif
/* EA's extended XA (N=8), reverse engineered from SAT exes. Basically the same with minor
* diffs and extra steps probably for the SH2 CPU (only does 1/2/8 shifts) */
static const int16_t EA_TABLE[16][2] = {
{ 0, 0 },
{ 240, 0 },
{ 460, -208 },
{ 392, -220 },
{ 488, -240 },
{ 328, -208 },
{ 440, -168 },
{ 420, -188 },
{ 432, -176 },
{ 240, -16 },
{ 416, -192 },
{ 424, -160 },
{ 288, -8 },
{ 436, -188 },
{ 224, -1 },
{ 272, -16 },
};
/* Sony XA ADPCM, defined for CD-DA/CD-i in the "Red Book" (private) or "Green Book" (public) specs.
* The algorithm basically is BRR (Bit Rate Reduction) from the SNES SPC700, while the data layout is new.
*
@ -26,6 +48,7 @@ typedef struct {
int32_t hist2;
int subframes;
int is_xa8;
int is_ea;
} xa_t;
static void decode_xa_frame(xa_t* xa, int32_t first_sample, int32_t samples_to_do, int channel) {
@ -37,7 +60,7 @@ static void decode_xa_frame(xa_t* xa, int32_t first_sample, int32_t samples_to_d
for (i = 0; i < xa->subframes / xa->channels; i++) {
uint8_t sp;
int index, shift, sp_pos;
#if XA_FLOAT
#ifdef XA_FLOAT
float coef1, coef2;
#else
int32_t coef1, coef2;
@ -52,14 +75,19 @@ static void decode_xa_frame(xa_t* xa, int32_t first_sample, int32_t samples_to_d
shift = (sp >> 0) & 0xf;
/* mastered values like 0xFF exist [Micro Machines (CDi), demo and release] */
VGM_ASSERT_ONCE(index > 4 || shift > shift_max, "XA: incorrect coefs/shift %x\n", sp);
if (index > 4)
index = 0; /* only 4 filters are used, rest is apparently 0 */
VGM_ASSERT_ONCE(shift > shift_max, "XA: incorrect shift %x\n", sp);
if (shift > shift_max)
shift = shift_limit;
coef1 = K0[index];
coef2 = K1[index];
if (xa->is_ea) {
coef1 = EA_TABLE[index][0];
coef2 = EA_TABLE[index][1];
}
else {
VGM_ASSERT_ONCE(index > 4, "XA: incorrect coefs %x\n", sp);
coef1 = K0[index];
coef2 = K1[index];
}
/* decode subframe nibbles */
@ -99,12 +127,19 @@ static void decode_xa_frame(xa_t* xa, int32_t first_sample, int32_t samples_to_d
#if XA_FLOAT
sample = sample + (coef1 * xa->hist1 + coef2 * xa->hist2);
#else
sample = sample + ((coef1 * xa->hist1 + coef2 * xa->hist2 + 32) >> 6);
if (xa->is_ea) /* sample << 8 actually but UB on negatives */
sample = (sample * 256 + coef1 * xa->hist1 + coef2 * xa->hist2) >> 8;
else
sample = sample + ((coef1 * xa->hist1 + coef2 * xa->hist2 + 32) >> 6);
#endif
xa->hist2 = xa->hist1;
xa->hist1 = sample;
xa->sbuf[samples_done * xa->channels] = clamp16(sample); /* don't clamp hist */
sample = clamp16(sample); /* don't clamp hist */
if (xa->is_ea)
xa->hist1 = sample; /* do clamp hist */
xa->sbuf[samples_done * xa->channels] = sample;
samples_done++;
sample_count++;
@ -123,8 +158,9 @@ void decode_xa(VGMSTREAM* v, sample_t* outbuf, int32_t samples_to_do) {
int32_t first_sample = v->samples_into_block;
xa_t xa;
xa.channels = v->channels;
xa.channels = v->channels > 1 ? 2 : 1; /* only stereo/mono modes */
xa.is_xa8 = (v->coding_type == coding_XA8);
xa.is_ea = (v->coding_type == coding_XA_EA);
xa.subframes = (xa.is_xa8) ? 4 : 8;
/* external interleave (fixed size), mono/stereo */
@ -139,8 +175,10 @@ void decode_xa(VGMSTREAM* v, sample_t* outbuf, int32_t samples_to_do) {
if (bytes != sizeof(xa.frame)) /* ignore EOF errors */
memset(xa.frame + bytes, 0, bytes_per_frame - bytes);
VGM_ASSERT_ONCE(get_u32be(xa.frame+0x0) != get_u32be(xa.frame+0x4) || get_u32be(xa.frame+0x8) != get_u32be(xa.frame+0xC),
"bad frames at %x\n", frame_offset);
/* headers should repeat in pairs, except in EA's modified XA */
VGM_ASSERT_ONCE(!xa.is_ea &&
(get_u32be(xa.frame+0x0) != get_u32be(xa.frame+0x4) || get_u32be(xa.frame+0x8) != get_u32be(xa.frame+0xC)),
"bad frames at %x\n", frame_offset);
for (ch = 0; ch < xa.channels; ch++) {
VGMSTREAMCHANNEL* stream = &v->ch[ch];
@ -212,10 +250,11 @@ size_t xa_bytes_to_samples(size_t bytes, int channels, int is_blocked, int is_fo
* Info (Green Book): https://www.lscdweb.com/data/downloadables/2/8/cdi_may94_r2.pdf
* BRR info (no$sns): http://problemkaputt.de/fullsnes.htm#snesapudspbrrsamples
* (bsnes): https://github.com/byuu/bsnes/blob/master/bsnes/sfc/dsp/SPC_DSP.cpp#L316
* Note that this is just called "ADPCM" in the "CD-ROM XA" spec (rather than "XA ADPCM" being the actual name).
*/
/* data layout (mono):
* - CD-XA audio is divided into sectors ("audio blocks"), each with 18*0x80 frames
* - CD XA audio is divided into sectors ("audio blocks"), each with 18*0x80 frames
* (sectors are handled externally, this decoder only sees 0x80 frames)
* - each frame ("sound group") is divided into 8 interleaved subframes ("sound unit"), with
* 8*0x01 subframe headers x2 ("sound parameters") first then 28*0x04 subframe nibbles ("sound data")

View File

@ -442,6 +442,7 @@ int get_vgmstream_samples_per_frame(VGMSTREAM* vgmstream) {
return 0; /* variable (block-controlled) */
case coding_XA:
case coding_XA_EA:
return 28*8 / vgmstream->channels; /* 8 subframes per frame, mono/stereo */
case coding_XA8:
return 28*4 / vgmstream->channels; /* 4 subframes per frame, mono/stereo */
@ -660,6 +661,7 @@ int get_vgmstream_frame_size(VGMSTREAM* vgmstream) {
return 0x00; /* variable (block-controlled) */
case coding_XA:
case coding_XA_EA:
case coding_XA8:
return 0x80;
case coding_PSX:
@ -1007,6 +1009,7 @@ void decode_vgmstream(VGMSTREAM* vgmstream, int samples_written, int samples_to_
}
break;
case coding_XA:
case coding_XA_EA:
case coding_XA8: {
decode_xa(vgmstream, buffer, samples_to_do);
break;

View File

@ -41,6 +41,7 @@ typedef enum {
EAXA = 31, /* Electronic Arts EA-XA 4-bit ADPCM v1 */
OKI4S = 32, /* OKI ADPCM with 16-bit output (unlike OKI/VOX/Dialogic ADPCM's 12-bit) */
XA,
XA_EA,
CP_YM,
UNKNOWN = 99,
@ -262,6 +263,7 @@ VGMSTREAM* init_vgmstream_txth(STREAMFILE* sf) {
case ASF: coding = coding_ASF; break;
case EAXA: coding = coding_EA_XA; break;
case XA: coding = coding_XA; break;
case XA_EA: coding = coding_XA_EA; break;
case CP_YM: coding = coding_CP_YM; break;
default:
goto fail;
@ -375,6 +377,7 @@ VGMSTREAM* init_vgmstream_txth(STREAMFILE* sf) {
case coding_OKI16:
case coding_OKI4S:
case coding_XA:
case coding_XA_EA:
case coding_CP_YM:
vgmstream->layout_type = layout_none;
break;
@ -948,6 +951,7 @@ static txth_codec_t parse_codec(txth_header* txth, const char* val) {
else if (is_string(val,"ASF")) return ASF;
else if (is_string(val,"EAXA")) return EAXA;
else if (is_string(val,"XA")) return XA;
else if (is_string(val,"XA_EA")) return XA_EA;
else if (is_string(val,"CP_YM")) return CP_YM;
/* special handling */
else if (is_string(val,"name_value")) return txth->name_values[0];
@ -2031,6 +2035,7 @@ static int get_bytes_to_samples(txth_header* txth, uint32_t bytes) {
case EAXA:
return ea_xa_bytes_to_samples(bytes, txth->channels);
case XA:
case XA_EA:
return xa_bytes_to_samples(bytes, txth->channels, 0, 0, 4);
/* XMA bytes-to-samples is done at the end as the value meanings are a bit different */

View File

@ -87,6 +87,7 @@ typedef enum {
coding_XA, /* CD-ROM XA 4-bit */
coding_XA8, /* CD-ROM XA 8-bit */
coding_XA_EA, /* EA's Saturn XA (not to be confused with EA-XA) */
coding_PSX, /* Sony PS ADPCM (VAG) */
coding_PSX_badflags, /* Sony PS ADPCM with custom flag byte */
coding_PSX_cfg, /* Sony PS ADPCM with configurable frame size (int math) */