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)] # * For rare EA games [Harry Potter and the Chamber of Secrets (PC)]
# - XA CD-XA ADPCM (ISO 2048 mode1/data streams without subchannels) # - 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] # * 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 # - CP_YM Capcom's Yamaha ADPCM
# * For rare Saturn games [Marvel Super Heroes vs Street Fighter (SAT)] # * For rare Saturn games [Marvel Super Heroes vs Street Fighter (SAT)]
codec = (codec string) codec = (codec string)

View File

@ -5,14 +5,36 @@
//#define XA_FLOAT 1 //#define XA_FLOAT 1
#if XA_FLOAT #if XA_FLOAT
/* floats as defined by the spec, but PS1's SPU would use int math */ /* 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 K0[4+12] = { 0.0, 0.9375, 1.796875, 1.53125 };
static const float K1[4] = { 0.0, 0.0, -0.8125, -0.859375 }; static const float K1[4+12] = { 0.0, 0.0, -0.8125, -0.859375 };
#else #else
/* K0/1 floats to int with N=6: K*2^6 = K*(1<<6) = K*64 */ /* 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] = { 0, 60, 115, 98 }; static const int K0[4+12] = { 0, 60, 115, 98 };
static const int K1[4] = { 0, 0, -52, -55 }; static const int K1[4+12] = { 0, 0, -52, -55 };
#endif #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. /* 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. * 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; int32_t hist2;
int subframes; int subframes;
int is_xa8; int is_xa8;
int is_ea;
} xa_t; } xa_t;
static void decode_xa_frame(xa_t* xa, int32_t first_sample, int32_t samples_to_do, int channel) { 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++) { for (i = 0; i < xa->subframes / xa->channels; i++) {
uint8_t sp; uint8_t sp;
int index, shift, sp_pos; int index, shift, sp_pos;
#if XA_FLOAT #ifdef XA_FLOAT
float coef1, coef2; float coef1, coef2;
#else #else
int32_t coef1, coef2; 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; shift = (sp >> 0) & 0xf;
/* mastered values like 0xFF exist [Micro Machines (CDi), demo and release] */ /* 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); VGM_ASSERT_ONCE(shift > shift_max, "XA: incorrect shift %x\n", sp);
if (index > 4)
index = 0; /* only 4 filters are used, rest is apparently 0 */
if (shift > shift_max) if (shift > shift_max)
shift = shift_limit; shift = shift_limit;
coef1 = K0[index]; if (xa->is_ea) {
coef2 = K1[index]; 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 */ /* 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 #if XA_FLOAT
sample = sample + (coef1 * xa->hist1 + coef2 * xa->hist2); sample = sample + (coef1 * xa->hist1 + coef2 * xa->hist2);
#else #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 #endif
xa->hist2 = xa->hist1; xa->hist2 = xa->hist1;
xa->hist1 = sample; 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++; samples_done++;
sample_count++; 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; int32_t first_sample = v->samples_into_block;
xa_t xa; 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_xa8 = (v->coding_type == coding_XA8);
xa.is_ea = (v->coding_type == coding_XA_EA);
xa.subframes = (xa.is_xa8) ? 4 : 8; xa.subframes = (xa.is_xa8) ? 4 : 8;
/* external interleave (fixed size), mono/stereo */ /* 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 */ if (bytes != sizeof(xa.frame)) /* ignore EOF errors */
memset(xa.frame + bytes, 0, bytes_per_frame - bytes); 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), /* headers should repeat in pairs, except in EA's modified XA */
"bad frames at %x\n", frame_offset); 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++) { for (ch = 0; ch < xa.channels; ch++) {
VGMSTREAMCHANNEL* stream = &v->ch[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 * 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 * 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 * (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): /* 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) * (sectors are handled externally, this decoder only sees 0x80 frames)
* - each frame ("sound group") is divided into 8 interleaved subframes ("sound unit"), with * - 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") * 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) */ return 0; /* variable (block-controlled) */
case coding_XA: case coding_XA:
case coding_XA_EA:
return 28*8 / vgmstream->channels; /* 8 subframes per frame, mono/stereo */ return 28*8 / vgmstream->channels; /* 8 subframes per frame, mono/stereo */
case coding_XA8: case coding_XA8:
return 28*4 / vgmstream->channels; /* 4 subframes per frame, mono/stereo */ 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) */ return 0x00; /* variable (block-controlled) */
case coding_XA: case coding_XA:
case coding_XA_EA:
case coding_XA8: case coding_XA8:
return 0x80; return 0x80;
case coding_PSX: case coding_PSX:
@ -1007,6 +1009,7 @@ void decode_vgmstream(VGMSTREAM* vgmstream, int samples_written, int samples_to_
} }
break; break;
case coding_XA: case coding_XA:
case coding_XA_EA:
case coding_XA8: { case coding_XA8: {
decode_xa(vgmstream, buffer, samples_to_do); decode_xa(vgmstream, buffer, samples_to_do);
break; break;

View File

@ -41,6 +41,7 @@ typedef enum {
EAXA = 31, /* Electronic Arts EA-XA 4-bit ADPCM v1 */ 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) */ OKI4S = 32, /* OKI ADPCM with 16-bit output (unlike OKI/VOX/Dialogic ADPCM's 12-bit) */
XA, XA,
XA_EA,
CP_YM, CP_YM,
UNKNOWN = 99, UNKNOWN = 99,
@ -262,6 +263,7 @@ VGMSTREAM* init_vgmstream_txth(STREAMFILE* sf) {
case ASF: coding = coding_ASF; break; case ASF: coding = coding_ASF; break;
case EAXA: coding = coding_EA_XA; break; case EAXA: coding = coding_EA_XA; break;
case XA: coding = coding_XA; break; case XA: coding = coding_XA; break;
case XA_EA: coding = coding_XA_EA; break;
case CP_YM: coding = coding_CP_YM; break; case CP_YM: coding = coding_CP_YM; break;
default: default:
goto fail; goto fail;
@ -375,6 +377,7 @@ VGMSTREAM* init_vgmstream_txth(STREAMFILE* sf) {
case coding_OKI16: case coding_OKI16:
case coding_OKI4S: case coding_OKI4S:
case coding_XA: case coding_XA:
case coding_XA_EA:
case coding_CP_YM: case coding_CP_YM:
vgmstream->layout_type = layout_none; vgmstream->layout_type = layout_none;
break; 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,"ASF")) return ASF;
else if (is_string(val,"EAXA")) return EAXA; else if (is_string(val,"EAXA")) return EAXA;
else if (is_string(val,"XA")) return XA; 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; else if (is_string(val,"CP_YM")) return CP_YM;
/* special handling */ /* special handling */
else if (is_string(val,"name_value")) return txth->name_values[0]; 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: case EAXA:
return ea_xa_bytes_to_samples(bytes, txth->channels); return ea_xa_bytes_to_samples(bytes, txth->channels);
case XA: case XA:
case XA_EA:
return xa_bytes_to_samples(bytes, txth->channels, 0, 0, 4); 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 */ /* 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_XA, /* CD-ROM XA 4-bit */
coding_XA8, /* CD-ROM XA 8-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, /* Sony PS ADPCM (VAG) */
coding_PSX_badflags, /* Sony PS ADPCM with custom flag byte */ coding_PSX_badflags, /* Sony PS ADPCM with custom flag byte */
coding_PSX_cfg, /* Sony PS ADPCM with configurable frame size (int math) */ coding_PSX_cfg, /* Sony PS ADPCM with configurable frame size (int math) */