diff --git a/doc/TXTH.md b/doc/TXTH.md index bcf782dc..f933cf67 100644 --- a/doc/TXTH.md +++ b/doc/TXTH.md @@ -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) diff --git a/src/coding/xa_decoder.c b/src/coding/xa_decoder.c index b7b9deaf..49aab74a 100644 --- a/src/coding/xa_decoder.c +++ b/src/coding/xa_decoder.c @@ -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") diff --git a/src/decode.c b/src/decode.c index f706d9aa..37672c3a 100644 --- a/src/decode.c +++ b/src/decode.c @@ -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; diff --git a/src/meta/txth.c b/src/meta/txth.c index c97b37aa..9822b498 100644 --- a/src/meta/txth.c +++ b/src/meta/txth.c @@ -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 */ diff --git a/src/vgmstream.h b/src/vgmstream.h index ce20b989..cd3d88e2 100644 --- a/src/vgmstream.h +++ b/src/vgmstream.h @@ -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) */