From a2a4a48524fcb207af01396bc4da8d7f66cb4c3a Mon Sep 17 00:00:00 2001 From: bnnm Date: Wed, 24 Feb 2021 23:40:20 +0100 Subject: [PATCH] Add XA 8-bit mode [Micro Machines (CD-i)] --- src/coding/coding.h | 4 +- src/coding/xa_decoder.c | 131 ++++++++++++++++++++++++---------------- src/decode.c | 8 ++- src/formats.c | 2 + src/layout/blocked_xa.c | 56 +++++++++-------- src/meta/xa.c | 74 ++++++++++++----------- src/vgmstream.h | 3 +- 7 files changed, 163 insertions(+), 115 deletions(-) diff --git a/src/coding/coding.h b/src/coding/coding.h index bb90b3f5..42fb19e7 100644 --- a/src/coding/coding.h +++ b/src/coding/coding.h @@ -110,8 +110,8 @@ void decode_hevag(VGMSTREAMCHANNEL* stream, sample_t* outbuf, int channelspacing /* xa_decoder */ -void decode_xa(VGMSTREAMCHANNEL* stream, sample_t* outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do, int channel); -size_t xa_bytes_to_samples(size_t bytes, int channels, int is_blocked, int is_form2); +void decode_xa(VGMSTREAMCHANNEL* stream, sample_t* outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do, int channel, int is_xa8); +size_t xa_bytes_to_samples(size_t bytes, int channels, int is_blocked, int is_form2, int bps); /* ea_xa_decoder */ diff --git a/src/coding/xa_decoder.c b/src/coding/xa_decoder.c index 6aa4fffd..8f266501 100644 --- a/src/coding/xa_decoder.c +++ b/src/coding/xa_decoder.c @@ -38,45 +38,57 @@ static const int IK1[4] = { 0, 0, 832, 880 }; * PS1 XA is apparently upsampled and interpolated to 44100, vgmstream doesn't simulate this. * * XA has an 8-bit decoding and "emphasis" modes, that no PS1 game actually uses, but apparently - * are supported by the CD hardware and will play if found. + * are supported by the CD hardware and will play if found. Some odd CD-i game does use 8-bit mode. + * Official "sound quality level" modes: + * - Level A: 37.8hz, 8-bit + * - Level B: 37.8hz, 4-bit + * - Level C: 18.9hz, 4-bit * * 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 */ -void decode_xa(VGMSTREAMCHANNEL* stream, sample_t* outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do, int channel) { +/* data layout (mono): + * - CD-XA audio is divided into sectors ("audio blocks"), each with 18*0x80 frames + * (sectors handled externally, this decoder only sees N 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") + * - subframe headers: 0..3 + repeat 0..3 + 4..7 + repeat 4..7 (where N = subframe N header) + * (repeats may be for error correction, though probably unused) + * header has a "range" N (gain of 2^N, or simplified as a shift) and a "filter" (control gains K0 and K1) + * - subframe nibbles: 32b with nibble0 for subframes 0..8, 32b with nibble1 for subframes 0..8, etc + * (low first: 32b = sf1-n0 sf0-n0 sf3-n0 sf2-n0 sf5-n0 sf4-n0 sf7-n0 sf6-n0, etc) + * + * stereo layout is the same but alternates channels: subframe 0/2/4/6=L, subframe 1/3/5/7=R + * + * example: + * subframe 0: header @ 0x00 or 0x04, 28 nibbles (low) @ 0x10,14,18,1c,20 ... 7c + * subframe 1: header @ 0x01 or 0x05, 28 nibbles (high) @ 0x10,14,18,1c,20 ... 7c + * subframe 2: header @ 0x02 or 0x06, 28 nibbles (low) @ 0x11,15,19,1d,21 ... 7d + * ... + * subframe 7: header @ 0x0b or 0x0f, 28 nibbles (high) @ 0x13,17,1b,1f,23 ... 7f + * + * 8-bit layout is similar but only has 4 subframes + subframe bytes, so half the samples: + * subframe 0: header @ 0x00 or 0x04/08/0c, 28 bytes @ 0x10,14,18,1c,20 ... 7c + * subframe 1: header @ 0x01 or 0x05/09/0d, 28 bytes @ 0x11,16,19,1d,21 ... 7d + * ... + * subframe 3: header @ 0x03 or 0x07/0b/0f, 28 bytes @ 0x13,17,1b,1f,23 ... 7f + */ + +void decode_xa(VGMSTREAMCHANNEL* stream, sample_t* outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do, int channel, int is_xa8) { uint8_t frame[0x80] = {0}; off_t frame_offset; int i,j, sp_pos, frames_in, samples_done = 0, sample_count = 0; size_t bytes_per_frame, samples_per_frame; int32_t hist1 = stream->adpcm_history1_32; int32_t hist2 = stream->adpcm_history2_32; + int subframes = (is_xa8) ? 4 : 8; - /* data layout (mono): - * - CD-XA audio is divided into sectors ("audio blocks"), each with 18 size 0x80 frames - * (handled externally, this decoder only gets frames) - * - a frame ("sound group") is divided into 8 subframes ("sound unit"), with - * subframe headers ("sound parameters") first then subframe nibbles ("sound data") - * - headers: 0..3 + repeat 0..3 + 4..7 + repeat 4..7 (where N = subframe N header) - * (repeats may be for error correction, though probably unused) - * - nibbles: 32b with nibble0 for subframes 0..8, 32b with nibble1 for subframes 0..8, etc - * (low first: 32b = sf1-n0 sf0-n0 sf3-n0 sf2-n0 sf5-n0 sf4-n0 sf7-n0 sf6-n0, etc) - * - * stereo layout is the same but alternates channels: subframe 0/2/4/6=L, subframe 1/3/5/7=R - * - * example: - * subframe 0: header @ 0x00 or 0x04, 28 nibbles (low) @ 0x10,14,18,1c,20 ... 7c - * subframe 1: header @ 0x01 or 0x05, 28 nibbles (high) @ 0x10,14,18,1c,20 ... 7c - * subframe 2: header @ 0x02 or 0x06, 28 nibbles (low) @ 0x11,15,19,1d,21 ... 7d - * ... - * subframe 7: header @ 0x0b or 0x0f, 28 nibbles (high) @ 0x13,17,1b,1f,23 ... 7f - */ - /* external interleave (fixed size), mono/stereo */ bytes_per_frame = 0x80; - samples_per_frame = 28*8 / channelspacing; + samples_per_frame = 28*subframes / channelspacing; frames_in = first_sample / samples_per_frame; first_sample = first_sample % samples_per_frame; @@ -84,25 +96,27 @@ void decode_xa(VGMSTREAMCHANNEL* stream, sample_t* outbuf, int channelspacing, i frame_offset = stream->offset + bytes_per_frame * frames_in; read_streamfile(frame, frame_offset, bytes_per_frame, stream->streamfile); /* ignore EOF errors */ - VGM_ASSERT(get_32bitBE(frame+0x0) != get_32bitBE(frame+0x4) || get_32bitBE(frame+0x8) != get_32bitBE(frame+0xC), + VGM_ASSERT(get_u32be(frame+0x0) != get_u32be(frame+0x4) || get_u32be(frame+0x8) != get_u32be(frame+0xC), "bad frames at %x\n", (uint32_t)frame_offset); - /* decode subframes */ - for (i = 0; i < 8 / channelspacing; i++) { + for (i = 0; i < subframes / channelspacing; i++) { int32_t coef1, coef2; uint8_t coef_index, shift_factor; /* parse current subframe (sound unit)'s header (sound parameters) */ - sp_pos = 0x04 + i*channelspacing + channel; + sp_pos = is_xa8 ? + i*channelspacing + channel: + 0x04 + i*channelspacing + channel; coef_index = (frame[sp_pos] >> 4) & 0xf; shift_factor = (frame[sp_pos] >> 0) & 0xf; - VGM_ASSERT(coef_index > 4 || shift_factor > 12, "XA: incorrect coefs/shift at %x\n", (uint32_t)frame_offset + sp_pos); + /* mastered values like 0xFF exist [Micro Machines (CDi), demo and release] */ + VGM_ASSERT(coef_index > 4 || shift_factor > (is_xa8 ? 8 : 12), "XA: incorrect coefs/shift at %x\n", (uint32_t)frame_offset + sp_pos); if (coef_index > 4) coef_index = 0; /* only 4 filters are used, rest is apparently 0 */ - if (shift_factor > 12) - shift_factor = 9; /* supposedly, from Nocash PSX docs */ + if (shift_factor > (is_xa8 ? 8 : 12)) + shift_factor = (is_xa8 ? 8 : 9); /* supposedly, from Nocash PSX docs (in 8-bit mode max range should be 8 though) */ coef1 = IK0[coef_index]; coef2 = IK1[coef_index]; @@ -110,15 +124,7 @@ void decode_xa(VGMSTREAMCHANNEL* stream, sample_t* outbuf, int channelspacing, i /* decode subframe nibbles */ for(j = 0; j < 28; j++) { - uint8_t nibbles; - int32_t new_sample; - - int su_pos = (channelspacing==1) ? - 0x10 + j*0x04 + (i/2) : /* mono */ - 0x10 + j*0x04 + i; /* stereo */ - int get_high_nibble = (channelspacing==1) ? - (i&1) : /* mono (even subframes = low, off subframes = high) */ - (channel == 1); /* stereo (L channel / even subframes = low, R channel / odd subframes = high) */ + int32_t sample; /* skip half decodes to make sure hist isn't touched (kinda hack-ish) */ if (!(sample_count >= first_sample && samples_done < samples_to_do)) { @@ -126,22 +132,39 @@ void decode_xa(VGMSTREAMCHANNEL* stream, sample_t* outbuf, int channelspacing, i continue; } - nibbles = frame[su_pos]; + if (is_xa8) { + int su_pos = (channelspacing==1) ? + 0x10 + j*0x04 + i : /* mono */ + 0x10 + j*0x04 + i*2 + channel; /* stereo */ - new_sample = get_high_nibble ? - (nibbles >> 4) & 0x0f : - (nibbles >> 0) & 0x0f; + sample = frame[su_pos]; + sample = (int16_t)((sample << 8) & 0xff00) >> shift_factor; /* 16b sign extend + scale */ + } + else { + uint8_t nibbles; + int su_pos = (channelspacing==1) ? + 0x10 + j*0x04 + (i/2) : /* mono */ + 0x10 + j*0x04 + i; /* stereo */ + int get_high_nibble = (channelspacing==1) ? + (i&1) : /* mono (even subframes = low, off subframes = high) */ + (channel == 1); /* stereo (L channel / even subframes = low, R channel / odd subframes = high) */ - new_sample = (int16_t)((new_sample << 12) & 0xf000) >> shift_factor; /* 16b sign extend + scale */ - new_sample = new_sample << 4; - new_sample = new_sample - ((coef1*hist1 + coef2*hist2) >> 10); + nibbles = frame[su_pos]; + sample = get_high_nibble ? + (nibbles >> 4) & 0x0f : + (nibbles >> 0) & 0x0f; + sample = (int16_t)((sample << 12) & 0xf000) >> shift_factor; /* 16b sign extend + scale */ + } + + sample = sample << 4; /* scale for current IK */ + sample = sample - ((coef1*hist1 + coef2*hist2) >> 10); hist2 = hist1; - hist1 = new_sample; /* must go before clamp, somehow */ - new_sample = new_sample >> 4; - new_sample = clamp16(new_sample); + hist1 = sample; /* must go before clamp, somehow */ + sample = sample >> 4; + sample = clamp16(sample); - outbuf[samples_done * channelspacing] = new_sample; + outbuf[samples_done * channelspacing] = sample; samples_done++; sample_count++; @@ -152,11 +175,13 @@ void decode_xa(VGMSTREAMCHANNEL* stream, sample_t* outbuf, int channelspacing, i stream->adpcm_history2_32 = hist2; } -size_t xa_bytes_to_samples(size_t bytes, int channels, int is_blocked, int is_form2) { + +size_t xa_bytes_to_samples(size_t bytes, int channels, int is_blocked, int is_form2, int bps) { + int subframes = (bps == 8) ? 4 : 8; if (is_blocked) { - return (bytes / 0x930) * (28*8/ channels) * (is_form2 ? 18 : 16); + return (bytes / 0x930) * (28*subframes/ channels) * (is_form2 ? 18 : 16); } else { - return (bytes / 0x80) * (28*8 / channels); + return (bytes / 0x80) * (28*subframes / channels); } } diff --git a/src/decode.c b/src/decode.c index b2934cea..17ecbb92 100644 --- a/src/decode.c +++ b/src/decode.c @@ -439,6 +439,8 @@ int get_vgmstream_samples_per_frame(VGMSTREAM* vgmstream) { case coding_XA: 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 */ case coding_PSX: case coding_PSX_badflags: case coding_HEVAG: @@ -649,6 +651,7 @@ int get_vgmstream_frame_size(VGMSTREAM* vgmstream) { return 0x00; /* variable (block-controlled) */ case coding_XA: + case coding_XA8: return 0x80; case coding_PSX: case coding_PSX_badflags: @@ -985,11 +988,14 @@ void decode_vgmstream(VGMSTREAM* vgmstream, int samples_written, int samples_to_ } break; case coding_XA: + case coding_XA8: { + int is_xa8 = (vgmstream->coding_type == coding_XA8); for (ch = 0; ch < vgmstream->channels; ch++) { decode_xa(&vgmstream->ch[ch], buffer+ch, - vgmstream->channels, vgmstream->samples_into_block, samples_to_do, ch); + vgmstream->channels, vgmstream->samples_into_block, samples_to_do, ch, is_xa8); } break; + } case coding_EA_XA: case coding_EA_XA_int: { int is_stereo = (vgmstream->channels > 1 && vgmstream->coding_type == coding_EA_XA); diff --git a/src/formats.c b/src/formats.c index 7eb9dc02..ff9b91d5 100644 --- a/src/formats.c +++ b/src/formats.c @@ -194,6 +194,7 @@ static const char* extension_list[] = { "genh", "gin", "gms", + "grn", "gsb", "gsf", "gtd", @@ -717,6 +718,7 @@ static const coding_info coding_info_list[] = { {coding_G721, "CCITT G.721 4-bit ADPCM"}, {coding_XA, "CD-ROM XA 4-bit ADPCM"}, + {coding_XA8, "CD-ROM XA 8-bit ADPCM"}, {coding_PSX, "Playstation 4-bit ADPCM"}, {coding_PSX_badflags, "Playstation 4-bit ADPCM (bad flags)"}, {coding_PSX_cfg, "Playstation 4-bit ADPCM (configurable)"}, diff --git a/src/layout/blocked_xa.c b/src/layout/blocked_xa.c index 8e36d390..60b12896 100644 --- a/src/layout/blocked_xa.c +++ b/src/layout/blocked_xa.c @@ -3,12 +3,12 @@ #include "../vgmstream.h" /* parse a CD-XA raw mode2/form2 sector */ -void block_update_xa(off_t block_offset, VGMSTREAM * vgmstream) { - STREAMFILE* streamFile = vgmstream->ch[0].streamfile; +void block_update_xa(off_t block_offset, VGMSTREAM* vgmstream) { + STREAMFILE* sf = vgmstream->ch[0].streamfile; int i, is_audio; size_t block_samples; uint16_t xa_config, target_config; - uint8_t xa_submode; + uint8_t xa_submode, xa_header; /* XA mode2/form2 sector, size 0x930 @@ -22,8 +22,29 @@ void block_update_xa(off_t block_offset, VGMSTREAM * vgmstream) { * 0x930: end * Sectors with no data may exist near other with data */ - xa_config = read_u16be(block_offset + 0x10, streamFile); - target_config = vgmstream->codec_config; + xa_config = read_u16be(block_offset + 0x10, sf); /* file + channel */ + + /* submode flag bits (typical audio value = 0x64 01100100) + * - 7 (0x80 10000000): end of file (usually at last sector of a channel or at data end) + * - 6 (0x40 01000000): real time sector (special control flag) + * - 5 (0x20 00100000): sector form (0=form1, 1=form2) + * - 4 (0x10 00010000): trigger (generates interrupt for the application) + * - 3 (0x08 00001000): data sector + * - 2 (0x04 00000100): audio sector + * - 1 (0x02 00000010): video sector + * - 0 (0x01 00000001): end of audio (optional for non-real time XAs) + * Empty sectors with no flags may exist interleaved with other with audio/data. + */ + xa_submode = read_u8(block_offset + 0x12, sf); + + /* header bits: + * - 7 (0x80 10000000): reserved + * - 6 (0x40 01000000): emphasis (applies filter, same as CD-DA emphasis) + * - 4 (0x30 00110000): bits per sample (0=4-bit, 1=8-bit) + * - 2 (0x0C 00001100): sample rate (0=37.8hz, 1=18.9hz) + * - 0 (0x03 00000011): channels (0=mono, 1=stereo) + */ + xa_header = read_u8(block_offset + 0x13, sf); /* Sector subheader's file+channel markers are used to interleave streams (music/sfx/voices) * by reading one target file+channel while ignoring the rest. This is needed to adjust @@ -38,36 +59,23 @@ void block_update_xa(off_t block_offset, VGMSTREAM * vgmstream) { * Extractors deinterleave and split .xa using file + channel + EOF flags. * 'Channel' here doesn't mean "audio channel", just a fancy name for substreams (mono or stereo). * Files can go up to 255, normally file 0=sequential, 1+=interleaved */ - - - /* submode flag bits (typical audio value = 0x64 01100100) - * - 7 (0x80 10000000): end of file (usually at last sector of a channel or at data end) - * - 6 (0x40 01000000): real time sector (special control flag) - * - 5 (0x20 00100000): sector form (0=form1, 1=form2) - * - 4 (0x10 00010000): trigger (generates interrupt for the application) - * - 3 (0x08 00001000): data sector - * - 2 (0x04 00000100): audio sector - * - 1 (0x02 00000010): video sector - * - 0 (0x01 00000001): end of audio (optional for non-real time XAs) - * Empty sectors with no flags may exist interleaved with other with audio/data. - */ - xa_submode = read_u8(block_offset + 0x12,streamFile); + target_config = vgmstream->codec_config; /* audio sector must set/not set certain flags, as per spec (in theory form2 only too) */ is_audio = !(xa_submode & 0x08) && (xa_submode & 0x04) && !(xa_submode & 0x02); - if (xa_config != target_config) { block_samples = 0; /* not a target sector */ } else if (is_audio) { + int subframes = ((xa_header >> 4) & 3) == 1 ? 4 : 8; /* 8-bit mode = 4 subframes */ if (xa_submode & 0x20) { - /* form2 audio: size 0x900, 18 frames of size 0x80 with 8 subframes of 28 samples */ - block_samples = (28*8 / vgmstream->channels) * 18; + /* form2 audio: size 0x900, 18 frames of size 0x80 with N subframes of 28 samples */ + block_samples = (28*subframes / vgmstream->channels) * 18; } else { /* rare, found with empty audio [Glint Glitters (PS1), Dance! Dance! Dance! (PS1)] */ - /* form1 audio: size 0x800, 16 frames of size 0x80 with 8 subframes of 28 samples (rest is garbage/other data) */ - block_samples = (28*8 / vgmstream->channels) * 16; + /* form1 audio: size 0x800, 16 frames of size 0x80 with N subframes of 28 samples (rest is garbage/other data) */ + block_samples = (28*subframes / vgmstream->channels) * 16; } } else { diff --git a/src/meta/xa.c b/src/meta/xa.c index 49417cc5..5f17f21d 100644 --- a/src/meta/xa.c +++ b/src/meta/xa.c @@ -3,17 +3,17 @@ #include "../coding/coding.h" -static int xa_read_subsongs(STREAMFILE *sf, int target_subsong, off_t start, uint16_t *p_stream_config, off_t *p_stream_offset, size_t *p_stream_size, int *p_form2); -static int xa_check_format(STREAMFILE *sf, off_t offset, int is_blocked); +static int xa_read_subsongs(STREAMFILE* sf, int target_subsong, off_t start, uint16_t* p_stream_config, off_t* p_stream_offset, size_t* p_stream_size, int* p_form2); +static int xa_check_format(STREAMFILE* sf, off_t offset, int is_blocked); /* XA - from Sony PS1 and Philips CD-i CD audio, also Saturn streams */ -VGMSTREAM * init_vgmstream_xa(STREAMFILE *streamFile) { - VGMSTREAM * vgmstream = NULL; +VGMSTREAM* init_vgmstream_xa(STREAMFILE* sf) { + VGMSTREAM* vgmstream = NULL; off_t start_offset; - int loop_flag = 0, channel_count, sample_rate; + int loop_flag = 0, channels, sample_rate, bps; int is_riff = 0, is_blocked = 0, is_form2 = 0; size_t stream_size = 0; - int total_subsongs = 0, target_subsong = streamFile->stream_index; + int total_subsongs = 0, target_subsong = sf->stream_index; uint16_t target_config = 0; @@ -23,26 +23,27 @@ VGMSTREAM * init_vgmstream_xa(STREAMFILE *streamFile) { * .str: often videos and sometimes speech/music * .adp: Phantasy Star Collection (SAT) raw XA * .pxa: Mortal Kombat 4 (PS1) + * .grn: Micro Machines (CDi) * (extensionless): bigfiles [Castlevania: Symphony of the Night (PS1)] */ - if (!check_extensions(streamFile,"xa,str,adp,pxa,")) + if (!check_extensions(sf,"xa,str,adp,pxa,grn,")) goto fail; /* Proper XA comes in raw (BIN 2352 mode2/form2) CD sectors, that contain XA subheaders. * Also has minimal support for headerless (ISO 2048 mode1/data) mode. */ /* check RIFF header = raw (optional, added when ripping and not part of the CD data) */ - if (read_u32be(0x00,streamFile) == 0x52494646 && /* "RIFF" */ - read_u32be(0x08,streamFile) == 0x43445841 && /* "CDXA" */ - read_u32be(0x0C,streamFile) == 0x666D7420) { /* "fmt " */ + if (read_u32be(0x00,sf) == 0x52494646 && /* "RIFF" */ + read_u32be(0x08,sf) == 0x43445841 && /* "CDXA" */ + read_u32be(0x0C,sf) == 0x666D7420) { /* "fmt " */ is_blocked = 1; is_riff = 1; start_offset = 0x2c; /* after "data", ignore RIFF values as often are wrong */ } else { /* sector sync word = raw */ - if (read_u32be(0x00,streamFile) == 0x00FFFFFF && - read_u32be(0x04,streamFile) == 0xFFFFFFFF && - read_u32be(0x08,streamFile) == 0xFFFFFF00) { + if (read_u32be(0x00,sf) == 0x00FFFFFF && + read_u32be(0x04,sf) == 0xFFFFFFFF && + read_u32be(0x08,sf) == 0xFFFFFF00) { is_blocked = 1; start_offset = 0x00; } @@ -53,26 +54,26 @@ VGMSTREAM * init_vgmstream_xa(STREAMFILE *streamFile) { } /* test for XA data, since format is raw-ish (with RIFF it's assumed to be ok) */ - if (!is_riff && !xa_check_format(streamFile, start_offset, is_blocked)) + if (!is_riff && !xa_check_format(sf, start_offset, is_blocked)) goto fail; /* find subsongs as XA can interleave sectors using 'file' and 'channel' makers (see blocked_xa.c) */ if (/*!is_riff &&*/ is_blocked) { - total_subsongs = xa_read_subsongs(streamFile, target_subsong, start_offset, &target_config, &start_offset, &stream_size, &is_form2); + total_subsongs = xa_read_subsongs(sf, target_subsong, start_offset, &target_config, &start_offset, &stream_size, &is_form2); if (total_subsongs <= 0) goto fail; } else { - stream_size = get_streamfile_size(streamFile) - start_offset; + stream_size = get_streamfile_size(sf) - start_offset; } /* data is ok: parse header */ if (is_blocked) { /* parse 0x18 sector header (also see blocked_xa.c) */ - uint8_t xa_header = read_u8(start_offset + 0x13,streamFile); + uint8_t xa_header = read_u8(start_offset + 0x13,sf); switch((xa_header >> 0) & 3) { /* 0..1: mono/stereo */ - case 0: channel_count = 1; break; - case 1: channel_count = 2; break; + case 0: channels = 1; break; + case 1: channels = 2; break; default: goto fail; } switch((xa_header >> 2) & 3) { /* 2..3: sample rate */ @@ -80,11 +81,10 @@ VGMSTREAM * init_vgmstream_xa(STREAMFILE *streamFile) { case 1: sample_rate = 18900; break; default: goto fail; } - switch((xa_header >> 4) & 3) { /* 4..5: bits per sample (0=4-bit ADPCM, 1=8-bit ADPCM) */ - case 0: break; - default: /* PS1 games only do 4-bit */ - VGM_LOG("XA: unknown bits per sample found\n"); - goto fail; + switch((xa_header >> 4) & 3) { /* 4..5: bits per sample */ + case 0: bps = 4; break; /* PS1 games only do 4-bit ADPCM */ + case 1: bps = 8; break; /* Micro Machines (CDi) */ + default: goto fail; } switch((xa_header >> 6) & 1) { /* 6: emphasis (applies a filter) */ case 0: break; @@ -101,38 +101,44 @@ VGMSTREAM * init_vgmstream_xa(STREAMFILE *streamFile) { } else { /* headerless */ - if (check_extensions(streamFile,"adp")) { + if (check_extensions(sf,"adp")) { /* Phantasy Star Collection (SAT) raw files */ /* most are stereo, though a few (mainly sfx banks, sometimes using .bin) are mono */ char filename[PATH_LIMIT] = {0}; - get_streamfile_filename(streamFile, filename,PATH_LIMIT); + get_streamfile_filename(sf, filename,PATH_LIMIT); /* detect PS1 mono files, very lame but whatevs, no way to detect XA mono/stereo */ if (filename[0]=='P' && filename[1]=='S' && filename[2]=='1' && filename[3]=='S') { - channel_count = 1; + channels = 1; sample_rate = 22050; } else { - channel_count = 2; + channels = 2; sample_rate = 44100; } + bps = 4; } else { /* incorrectly ripped standard XA */ - channel_count = 2; + channels = 2; sample_rate = 37800; + bps = 4; } } + /* untested */ + if (bps == 8 && channels == 1) + goto fail; + /* build the VGMSTREAM */ - vgmstream = allocate_vgmstream(channel_count,loop_flag); + vgmstream = allocate_vgmstream(channels, loop_flag); if (!vgmstream) goto fail; vgmstream->meta_type = meta_XA; vgmstream->sample_rate = sample_rate; - vgmstream->coding_type = coding_XA; + vgmstream->coding_type = bps == 8 ? coding_XA8 : coding_XA; vgmstream->layout_type = is_blocked ? layout_blocked_xa : layout_none; if (is_blocked) { vgmstream->codec_config = target_config; @@ -144,9 +150,9 @@ VGMSTREAM * init_vgmstream_xa(STREAMFILE *streamFile) { } } - vgmstream->num_samples = xa_bytes_to_samples(stream_size, channel_count, is_blocked, is_form2); + vgmstream->num_samples = xa_bytes_to_samples(stream_size, channels, is_blocked, is_form2, bps); - if ( !vgmstream_open_stream(vgmstream, streamFile, start_offset) ) + if (!vgmstream_open_stream(vgmstream, sf, start_offset)) goto fail; return vgmstream; @@ -230,7 +236,7 @@ typedef struct xa_subsong_t { * * Bigfiles that paste tons of XA together are slow to parse since we need to read every sector to * count totals, but XA subsong handling is mainly for educational purposes. */ -static int xa_read_subsongs(STREAMFILE *sf, int target_subsong, off_t start, uint16_t *p_stream_config, off_t *p_stream_offset, size_t *p_stream_size, int *p_form2) { +static int xa_read_subsongs(STREAMFILE* sf, int target_subsong, off_t start, uint16_t* p_stream_config, off_t* p_stream_offset, size_t* p_stream_size, int* p_form2) { xa_subsong_t *cur_subsong = NULL; xa_subsong_t subsongs[XA_SUBSONG_MAX] = {0}; const size_t sector_size = 0x930; diff --git a/src/vgmstream.h b/src/vgmstream.h index ff918a08..3c091c58 100644 --- a/src/vgmstream.h +++ b/src/vgmstream.h @@ -102,7 +102,8 @@ typedef enum { coding_G721, /* CCITT G.721 */ - coding_XA, /* CD-ROM XA */ + coding_XA, /* CD-ROM XA 4-bit */ + coding_XA8, /* CD-ROM XA 8-bit */ 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) */