Merge pull request #817 from bnnm/xa-xwb

- Add XA 8-bit mode [Micro Machines (CD-i)]
- Add .xwb with ATRAC9 variation [Owlboy (PS4)]
This commit is contained in:
bnnm 2021-02-24 23:47:39 +01:00 committed by GitHub
commit 4ad77d4d10
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 185 additions and 132 deletions

View File

@ -208,10 +208,11 @@ Some plugins have options that allow any extension (common or unknown) to be
played, making renaming unnecessary. You may need to adjust plugin priority in
player's options first.
Also be aware that some plugins can tell the player they handle some extension,
then not actually play it. This makes the file unplayable as vgmstream doesn't
even get the chance to parse that file, so you may need to disable the offending
plugin or rename the file (for example this may happen with .asf and foobar).
Also be aware that other plugins (not vgmstream's) can tell the player they
handle some extension, then not actually play it. This makes the file unplayable
as vgmstream doesn't even get the chance to parse that file, so you may need to
disable the offending plugin or rename the file (for example this may happen with
.asf and foobar2000).
When extracting from a bigfile, sometimes internal files don't have an actual
name+extension. Those should be renamed to its proper/common extension, as the
@ -222,9 +223,10 @@ Note that vgmstream also accepts certain extension-less files too.
### Demuxed videos
vgmstream also supports audio from videos, but usually must be demuxed (extracted
without modification) first, since vgmstream doesn't attempt to support them.
without modification) first, since vgmstream doesn't attempt to support most of them
(it does support a few video formats as-is though).
The easiest way to do this is using VGMToolBox's "Video Demultiplexer" option
The easiest way to do this is using *VGMToolBox*'s "Video Demultiplexer" option
for common game video formats (`.bik`, `.vp6`, `.pss`, `.pam`, `.pmf`, `.usm`, `.xmv`, etc).
For standard videos formats (`.avi`, `.mp4`, `.webm`, `.m2v`, `.ogv`, etc) not supported
@ -302,8 +304,8 @@ the only option is renaming the companion extension to lowercase.
A particularly nasty variation of that is that some formats load files by full
name (e.g. `STREAM.SS0`), but sometimes the actual filename is in other case
(`Stream.ss0`), and some files could even point to that with another case. You
could try adding *symlinks* in various upper/lower/mixed cases to handle this.
(`Stream.ss0`), and some files could even point to that with yet another case.
You could try adding *symlinks* in various upper/lower/mixed cases to handle this.
Currently there isn't any way to know what exact name is needed (other than
hex-editting), though only a few formats do this, mainly *Ubisoft* banks.
@ -409,14 +411,16 @@ Some games layer a huge number of channels, that are disabled or downmixed
during gameplay. The player may be unable to play those files (for example
foobar can only play up to 8 channels, and Winamp depends on your sound
card). For those files you can set the "downmix" option in vgmstream, that
can reduce the number of channels to a playable amount. Note that this type
of downmixing is very generic, not meant to be used when converting to other
formats (channels are re-assigned and volumes modified in simplistic ways,
since it can't guess how the file should be properly adjusted).
can reduce the number of channels to a playable amount.
Note that this type of downmixing is very generic (not meant to be used when
converting to other formats), channels are re-assigned and volumes modified
in simplistic ways, since it can't guess how the file should be properly
adjusted. Most likely it will sound a bit quieter than usual.
You can also choose which channels to play using *TXTP*. For example, create
a file named `song.adx#C1,2.txtp` to play only channels 1 and 2 from `song.adx`.
*TXTP* also has command to tweak how files is downmixed.
*TXTP* also has command to set how files are downmixed.
## Tagging

View File

@ -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 */

View File

@ -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);
}
}

View File

@ -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);

View File

@ -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)"},

View File

@ -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 {

View File

@ -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;

View File

@ -351,10 +351,11 @@ VGMSTREAM* init_vgmstream_xwb(STREAMFILE* sf) {
/* Stardew Valley (Switch), Skulls of the Shogun (Switch): full interleaved DSPs (including headers) */
xwb.codec = DSP;
}
else if (xwb.version == XACT3_0_MAX && xwb.codec == XMA2
&& xwb.bits_per_sample == 0x01 && xwb.block_align == 0x04
&& xwb.data_size == 0x4e0a1000) { /* some kind of id? */
/* Stardew Valley (Vita), standard RIFF with ATRAC9 */
else if (xwb.version == XACT3_0_MAX && (xwb.codec == XMA2 || xwb.codec == PCM)
&& xwb.bits_per_sample == 0x01 && xwb.block_align == 0x02*xwb.channels
&& is_id32be(xwb.stream_offset, sf, "RIFF") /* clashes with XMA2 */
/*&& xwb.data_size == 0x4e0a1000*/) { /* some kind of id in Stardew Valley? */
/* Stardew Valley (Vita), Owlboy (PS4): standard RIFF with ATRAC9 */
xwb.codec = ATRAC9_RIFF;
}

View File

@ -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) */