Merge pull request #266 from bnnm/xa-aif-eamt

XA, AIF, EA-MT
This commit is contained in:
Christopher Snowhill 2018-07-23 19:07:35 -07:00 committed by GitHub
commit eee451aa68
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 296 additions and 191 deletions

View File

@ -82,7 +82,7 @@ size_t ps_cfg_bytes_to_samples(size_t bytes, size_t frame_size, int channels);
void decode_hevag(VGMSTREAMCHANNEL * stream, sample * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do); void decode_hevag(VGMSTREAMCHANNEL * stream, sample * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do);
/* xa_decoder */ /* xa_decoder */
void decode_xa(VGMSTREAM * stream, sample * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do, int channel); void decode_xa(VGMSTREAMCHANNEL * stream, sample * 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); size_t xa_bytes_to_samples(size_t bytes, int channels, int is_blocked);
/* ea_xa_decoder */ /* ea_xa_decoder */

View File

@ -549,6 +549,9 @@ static void flush_ea_mt_internal(VGMSTREAM *vgmstream, int is_start) {
ctx->ptr = ctx->buffer; ctx->ptr = ctx->buffer;
ctx->end = ctx->buffer + bytes; ctx->end = ctx->buffer + bytes;
ctx->bits_count = 0; ctx->bits_count = 0;
if (is_start)
ctx->parsed_header = 0;
} }
} }

View File

@ -8,8 +8,9 @@
/* XA ADPCM gain values */ /* XA ADPCM gain values */
static const double K0[4] = { 0.0, 0.9375, 1.796875, 1.53125 }; static const double K0[4] = { 0.0, 0.9375, 1.796875, 1.53125 };
static const double K1[4] = { 0.0, 0.0, -0.8125,-0.859375}; static const double K1[4] = { 0.0, 0.0, -0.8125,-0.859375};
static int IK0(int fid) { return ((int)((-K0[fid]) * (1 << 10))); } /* K0/1 floats to int, K*2^10 = K*(1<<10) = K*1024 */ /* K0/1 floats to int, K*2^10 = K*(1<<10) = K*1024 */
static int IK1(int fid) { return ((int)((-K1[fid]) * (1 << 10))); } static int get_IK0(int fid) { return ((int)((-K0[fid]) * (1 << 10))); }
static int get_IK1(int fid) { return ((int)((-K1[fid]) * (1 << 10))); }
/* 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.
@ -29,8 +30,8 @@ static int IK1(int fid) { return ((int)((-K1[fid]) * (1 << 10))); }
* (rounding differences should be inaudible, so public implementations may be approximations) * (rounding differences should be inaudible, so public implementations may be approximations)
* *
* Various XA descendants (PS-ADPCM, EA-XA, NGC DTK, FADPCM, etc) do filters/rounding slightly * Various XA descendants (PS-ADPCM, EA-XA, NGC DTK, FADPCM, etc) do filters/rounding slightly
* differently, using one of the above methods in software/CPU, but in XA's case may be done like * differently, maybe using one of the above methods in software/CPU, but in XA's case may be done
* the SNES/SPC700 BRR, with specific per-filter ops. * like the SNES/SPC700 BRR, with specific per-filter ops.
* int coef tables commonly use N = 6 or 8, so K0 0.9375*64 = 60 or 0.9375*256 = 240 * int coef tables commonly use N = 6 or 8, so K0 0.9375*64 = 60 or 0.9375*256 = 240
* PS1 XA is apparently upsampled and interpolated to 44100, vgmstream doesn't simulate this. * PS1 XA is apparently upsampled and interpolated to 44100, vgmstream doesn't simulate this.
* *
@ -38,29 +39,56 @@ static int IK1(int fid) { return ((int)((-K1[fid]) * (1 << 10))); }
* BRR info (no$sns): http://problemkaputt.de/fullsnes.htm#snesapudspbrrsamples * BRR info (no$sns): http://problemkaputt.de/fullsnes.htm#snesapudspbrrsamples
* (bsnes): https://gitlab.com/higan/higan/blob/master/higan/sfc/dsp/brr.cpp * (bsnes): https://gitlab.com/higan/higan/blob/master/higan/sfc/dsp/brr.cpp
*/ */
void decode_xa(VGMSTREAM * vgmstream, sample * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do, int channel) {
static int head_table[8] = {0,2,8,10}; void decode_xa(VGMSTREAMCHANNEL * stream, sample * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do, int channel) {
VGMSTREAMCHANNEL * stream = &(vgmstream->ch[channel]); off_t frame_offset, sp_offset;
off_t sp_offset; int i,j, frames_in, samples_done = 0, sample_count = 0;
int i; size_t bytes_per_frame, samples_per_frame;
int frames_in, sample_count = 0;
int32_t coef1, coef2, coef_index, shift_factor;
int32_t hist1 = stream->adpcm_history1_32; int32_t hist1 = stream->adpcm_history1_32;
int32_t hist2 = stream->adpcm_history2_32; int32_t hist2 = stream->adpcm_history2_32;
/* external interleave (fixed size), mono/stereo */ /* external interleave (fixed size), mono/stereo */
frames_in = first_sample / (28*2 / channelspacing); bytes_per_frame = 0x80;
first_sample = first_sample % 28; samples_per_frame = 28*8 / channelspacing;
frames_in = first_sample / samples_per_frame;
first_sample = first_sample % samples_per_frame;
/* hack for mono/stereo handling */ /* data layout (mono):
vgmstream->xa_get_high_nibble = !vgmstream->xa_get_high_nibble; * - CD-XA audio is divided into sectors ("audio blocks"), each with 18 size 0x80 frames
if (first_sample && channelspacing==1) * (handled externally, this decoder only gets frames)
vgmstream->xa_get_high_nibble = !vgmstream->xa_get_high_nibble; * - 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
*/
frame_offset = stream->offset + bytes_per_frame*frames_in;
/* parse current sound unit (subframe) sound parameters */ if (read_32bitBE(frame_offset+0x00,stream->streamfile) != read_32bitBE(frame_offset+0x04,stream->streamfile) ||
sp_offset = stream->offset+head_table[frames_in]+vgmstream->xa_get_high_nibble; read_32bitBE(frame_offset+0x08,stream->streamfile) != read_32bitBE(frame_offset+0x0c,stream->streamfile)) {
coef_index = (read_8bit(sp_offset,stream->streamfile) >> 4) & 0xf; VGM_LOG("bad frames at %lx\n", frame_offset);
shift_factor = (read_8bit(sp_offset,stream->streamfile) ) & 0xf; }
/* decode subframes */
for (i = 0; i < 8 / channelspacing; i++) {
int32_t coef1, coef2;
uint8_t coef_index, shift_factor;
/* parse current subframe (sound unit)'s header (sound parameters) */
sp_offset = frame_offset + 0x04 + i*channelspacing + channel;
coef_index = ((uint8_t)read_8bit(sp_offset,stream->streamfile) >> 4) & 0xf;
shift_factor = ((uint8_t)read_8bit(sp_offset,stream->streamfile) >> 0) & 0xf;
VGM_ASSERT(coef_index > 4 || shift_factor > 12, "XA: incorrect coefs/shift at %lx\n", sp_offset); VGM_ASSERT(coef_index > 4 || shift_factor > 12, "XA: incorrect coefs/shift at %lx\n", sp_offset);
if (coef_index > 4) if (coef_index > 4)
@ -68,18 +96,34 @@ void decode_xa(VGMSTREAM * vgmstream, sample * outbuf, int channelspacing, int32
if (shift_factor > 12) if (shift_factor > 12)
shift_factor = 9; /* supposedly, from Nocash PSX docs */ shift_factor = 9; /* supposedly, from Nocash PSX docs */
coef1 = IK0(coef_index); coef1 = get_IK0(coef_index);
coef2 = IK1(coef_index); coef2 = get_IK1(coef_index);
/* decode nibbles */ /* decode subframe nibbles */
for (i = first_sample; i < first_sample + samples_to_do; i++) { for(j = 0; j < 28; j++) {
uint8_t nibbles;
int32_t new_sample; int32_t new_sample;
uint8_t nibbles = (uint8_t)read_8bit(stream->offset+0x10+frames_in+(i*0x04),stream->streamfile);
new_sample = vgmstream->xa_get_high_nibble ? off_t su_offset = (channelspacing==1) ?
frame_offset + 0x10 + j*0x04 + (i/2) : /* mono */
frame_offset + 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) */
/* skip half decodes to make sure hist isn't touched (kinda hack-ish) */
if (!(sample_count >= first_sample && samples_done < samples_to_do)) {
sample_count++;
continue;
}
nibbles = (uint8_t)read_8bit(su_offset,stream->streamfile);
new_sample = get_high_nibble ?
(nibbles >> 4) & 0x0f : (nibbles >> 4) & 0x0f :
(nibbles ) & 0x0f; (nibbles ) & 0x0f;
new_sample = (int16_t)((new_sample << 12) & 0xf000) >> shift_factor; /* 16b sign extend + scale */ new_sample = (int16_t)((new_sample << 12) & 0xf000) >> shift_factor; /* 16b sign extend + scale */
new_sample = new_sample << 4; new_sample = new_sample << 4;
new_sample = new_sample - ((coef1*hist1 + coef2*hist2) >> 10); new_sample = new_sample - ((coef1*hist1 + coef2*hist2) >> 10);
@ -89,8 +133,11 @@ void decode_xa(VGMSTREAM * vgmstream, sample * outbuf, int channelspacing, int32
new_sample = new_sample >> 4; new_sample = new_sample >> 4;
new_sample = clamp16(new_sample); new_sample = clamp16(new_sample);
outbuf[sample_count] = new_sample; outbuf[samples_done * channelspacing] = new_sample;
sample_count += channelspacing; samples_done++;
sample_count++;
}
} }
stream->adpcm_history1_32 = hist1; stream->adpcm_history1_32 = hist1;
@ -99,10 +146,9 @@ void decode_xa(VGMSTREAM * vgmstream, sample * outbuf, int channelspacing, int32
size_t xa_bytes_to_samples(size_t bytes, int channels, int is_blocked) { size_t xa_bytes_to_samples(size_t bytes, int channels, int is_blocked) {
if (is_blocked) { if (is_blocked) {
//todo with -0x10 misses the last sector, not sure if bug or feature return (bytes / 0x930) * (28*8/ channels) * 18;
return ((bytes - 0x10) / 0x930) * (0x900 - 18*0x10) * 2 / channels;
} }
else { else {
return ((bytes / 0x80)*0xE0) / 2; return (bytes / 0x80) * (28*8 / channels);
} }
} }

View File

@ -32,6 +32,7 @@ static const char* extension_list[] = {
"afc", "afc",
"agsc", "agsc",
"ahx", "ahx",
"ai",
//"aif", //common //"aif", //common
"aifc", //common? "aifc", //common?
"aifcl", //fake extension, for AIF??? "aifcl", //fake extension, for AIF???
@ -637,6 +638,7 @@ static const layout_info layout_info_list[] = {
{layout_blocked_ea_wve_ad10, "blocked (EA WVE Ad10)"}, {layout_blocked_ea_wve_ad10, "blocked (EA WVE Ad10)"},
{layout_blocked_sthd, "blocked (STHD)"}, {layout_blocked_sthd, "blocked (STHD)"},
{layout_blocked_h4m, "blocked (H4M)"}, {layout_blocked_h4m, "blocked (H4M)"},
{layout_blocked_xa_aiff, "blocked (XA AIFF)"},
}; };
static const meta_info meta_info_list[] = { static const meta_info meta_info_list[] = {

View File

@ -205,6 +205,9 @@ static void block_update(VGMSTREAM * vgmstream) {
case layout_blocked_h4m: case layout_blocked_h4m:
block_update_h4m(vgmstream->next_block_offset,vgmstream); block_update_h4m(vgmstream->next_block_offset,vgmstream);
break; break;
case layout_blocked_xa_aiff:
block_update_xa_aiff(vgmstream->next_block_offset,vgmstream);
break;
default: default:
break; break;
} }

View File

@ -2,30 +2,32 @@
#include "../coding/coding.h" #include "../coding/coding.h"
#include "../vgmstream.h" #include "../vgmstream.h"
/* set up for the block at the given offset */ /* parse a CD-XA raw mode2/form2 sector */
void block_update_xa(off_t block_offset, VGMSTREAM * vgmstream) { void block_update_xa(off_t block_offset, VGMSTREAM * vgmstream) {
STREAMFILE* streamFile = vgmstream->ch[0].streamfile;
int i; int i;
int8_t currentChannel=0; size_t block_samples;
int8_t subAudio=0; uint8_t xa_submode;
vgmstream->xa_get_high_nibble = 1; /* reset nibble order */
/* don't change this variable in the init process */
if (vgmstream->samples_into_block != 0)
vgmstream->xa_sector_length += 0x80;
/* XA mode2/form2 sector, size 0x930 /* XA mode2/form2 sector, size 0x930
* 0x00: sync word * 0x00: sync word
* 0x0c: header = minute, second, sector, mode (always 0x02) * 0x0c: header = minute, second, sector, mode (always 0x02)
* 0x10: subheader = file, channel (marker), submode flags, xa header * 0x10: subheader = file, channel (substream marker), submode flags, xa header
* 0x14: subheader again * 0x14: subheader again (for error correction)
* 0x18: data * 0x18: data
* 0x918: unused * 0x918: unused
* 0x92c: EDC/checksum or null * 0x92c: EDC/checksum or null
* 0x930: end * 0x930: end
* (in non-blocked ISO 2048 mode1/data chunks are 0x800)
*/ */
/* channel markers supposedly could be used to interleave streams, ex. audio languages within video
* (extractors may split .XA using channels?) */
VGM_ASSERT(block_offset + 0x930 < get_streamfile_size(streamFile) &&
(uint8_t)read_8bit(block_offset + 0x000 + 0x11,streamFile) !=
(uint8_t)read_8bit(block_offset + 0x930 + 0x11,streamFile),
"XA block: subchannel change at %lx\n", block_offset);
/* submode flag bits (typical audio value = 0x64) /* submode flag bits (typical audio value = 0x64)
* - 7: end of file * - 7: end of file
* - 6: real time mode * - 6: real time mode
@ -36,38 +38,22 @@ void block_update_xa(off_t block_offset, VGMSTREAM * vgmstream) {
* - 1: video sector * - 1: video sector
* - 0: end of audio * - 0: end of audio
*/ */
xa_submode = (uint8_t)read_8bit(block_offset + 0x12,streamFile);
// We get to the end of a sector ? /* audio sector must set/not set certain flags, as per spec */
if (vgmstream->xa_sector_length == (18*0x80)) { if ((xa_submode & 0x20) && !(xa_submode & 0x08) && (xa_submode & 0x04) && !(xa_submode & 0x02) ) {
vgmstream->xa_sector_length = 0; block_samples = (28*8 / vgmstream->channels) * 18; /* size 0x900, 18 frames of size 0x80 with 8 subframes of 28 samples */
// 0x30 of unused bytes/sector :(
if (!vgmstream->xa_headerless) {
block_offset += 0x30;
begin:
// Search for selected channel & valid audio
currentChannel = read_8bit(block_offset-0x07,vgmstream->ch[0].streamfile);
subAudio = read_8bit(block_offset-0x06,vgmstream->ch[0].streamfile);
// audio is coded as 0x64
if (!((subAudio==0x64) && (currentChannel==vgmstream->xa_channel))) {
// go to next sector
block_offset += 0x930;
if (currentChannel!=-1) goto begin;
}
} }
else {
block_samples = 0; /* not an audio sector */
;VGM_LOG("XA block: non audio block found at %lx\n", block_offset);
} }
vgmstream->current_block_offset = block_offset; vgmstream->current_block_offset = block_offset;
vgmstream->current_block_samples = block_samples;
vgmstream->next_block_offset = block_offset + 0x930;
// Quid : how to stop the current channel ??? for (i = 0; i < vgmstream->channels; i++) {
// i set up 0 to current_block_size to make vgmstream not playing bad samples vgmstream->ch[i].offset = block_offset + 0x18;
// another way to do it ???
// (as the number of samples can be false in cd-xa due to multi-channels)
vgmstream->current_block_size = (currentChannel==-1 ? 0 : 0x70);
vgmstream->next_block_offset = vgmstream->current_block_offset + 0x80;
for (i=0;i<vgmstream->channels;i++) {
vgmstream->ch[i].offset = vgmstream->current_block_offset;
} }
} }

View File

@ -0,0 +1,19 @@
#include "layout.h"
#include "../coding/coding.h"
#include "../vgmstream.h"
/* parse a XA AIFF block (CD sector without the 0x18 subheader) */
void block_update_xa_aiff(off_t block_offset, VGMSTREAM * vgmstream) {
int i;
size_t block_samples;
block_samples = (28*8 / vgmstream->channels) * 18; /* size 0x900, 18 frames of size 0x80 with 8 subframes of 28 samples */
vgmstream->current_block_offset = block_offset;
vgmstream->current_block_samples = block_samples;
vgmstream->next_block_offset = block_offset + 0x914;
for (i = 0; i < vgmstream->channels; i++) {
vgmstream->ch[i].offset = block_offset;
}
}

View File

@ -44,6 +44,7 @@ void block_update_ea_wve_au00(off_t block_offset, VGMSTREAM * vgmstream);
void block_update_ea_wve_ad10(off_t block_offset, VGMSTREAM * vgmstream); void block_update_ea_wve_ad10(off_t block_offset, VGMSTREAM * vgmstream);
void block_update_sthd(off_t block_offset, VGMSTREAM * vgmstream); void block_update_sthd(off_t block_offset, VGMSTREAM * vgmstream);
void block_update_h4m(off_t block_offset, VGMSTREAM * vgmstream); void block_update_h4m(off_t block_offset, VGMSTREAM * vgmstream);
void block_update_xa_aiff(off_t block_offset, VGMSTREAM * vgmstream);
/* other layouts */ /* other layouts */
void render_vgmstream_interleave(sample * buffer, int32_t sample_count, VGMSTREAM * vgmstream); void render_vgmstream_interleave(sample * buffer, int32_t sample_count, VGMSTREAM * vgmstream);

View File

@ -1922,6 +1922,10 @@
RelativePath=".\layout\blocked_xa.c" RelativePath=".\layout\blocked_xa.c"
> >
</File> </File>
<File
RelativePath=".\layout\blocked_xa_aiff.c"
>
</File>
<File <File
RelativePath=".\layout\blocked_xvas.c" RelativePath=".\layout\blocked_xvas.c"
> >

View File

@ -524,6 +524,7 @@
<ClCompile Include="layout\blocked_ws_aud.c" /> <ClCompile Include="layout\blocked_ws_aud.c" />
<ClCompile Include="layout\blocked_wsi.c" /> <ClCompile Include="layout\blocked_wsi.c" />
<ClCompile Include="layout\blocked_xa.c" /> <ClCompile Include="layout\blocked_xa.c" />
<ClCompile Include="layout\blocked_xa_aiff.c" />
<ClCompile Include="layout\blocked_xvas.c" /> <ClCompile Include="layout\blocked_xvas.c" />
<ClCompile Include="..\ext_libs\clHCA.c" /> <ClCompile Include="..\ext_libs\clHCA.c" />
</ItemGroup> </ItemGroup>

View File

@ -1138,6 +1138,9 @@
<ClCompile Include="layout\blocked_xa.c"> <ClCompile Include="layout\blocked_xa.c">
<Filter>layout\Source Files</Filter> <Filter>layout\Source Files</Filter>
</ClCompile> </ClCompile>
<ClCompile Include="layout\blocked_xa_aiff.c">
<Filter>layout\Source Files</Filter>
</ClCompile>
<ClCompile Include="layout\blocked_xvas.c"> <ClCompile Include="layout\blocked_xvas.c">
<Filter>layout\Source Files</Filter> <Filter>layout\Source Files</Filter>
</ClCompile> </ClCompile>

View File

@ -26,8 +26,7 @@ static uint32_t read80bitSANE(off_t offset, STREAMFILE *streamFile) {
return mantissa*((buf[0]&0x80)?-1:1); return mantissa*((buf[0]&0x80)?-1:1);
} }
static uint32_t find_marker(STREAMFILE *streamFile, off_t MarkerChunkOffset, static uint32_t find_marker(STREAMFILE *streamFile, off_t MarkerChunkOffset, int marker_id) {
int marker_id) {
uint16_t marker_count; uint16_t marker_count;
int i; int i;
off_t marker_offset; off_t marker_offset;
@ -52,72 +51,59 @@ static uint32_t find_marker(STREAMFILE *streamFile, off_t MarkerChunkOffset,
/* Audio Interchange File Format AIFF/AIFF-C - from Mac/3DO games */ /* Audio Interchange File Format AIFF/AIFF-C - from Mac/3DO games */
VGMSTREAM * init_vgmstream_aifc(STREAMFILE *streamFile) { VGMSTREAM * init_vgmstream_aifc(STREAMFILE *streamFile) {
VGMSTREAM * vgmstream = NULL; VGMSTREAM * vgmstream = NULL;
off_t start_offset = 0;
off_t file_size = -1; size_t file_size;
int channel_count = 0; coding_t coding_type = 0;
int sample_count = 0; int channel_count = 0, sample_count = 0, sample_size = 0, sample_rate = 0;
int sample_size = 0; int interleave = 0;
int sample_rate = 0;
int coding_type = -1;
off_t start_offset = -1;
int interleave = -1;
int loop_flag = 0; int loop_flag = 0;
int32_t loop_start = -1; int32_t loop_start = 0, loop_end = 0;
int32_t loop_end = -1;
int AIFFext = 0; int is_aiff_ext = 0, is_aifc_ext = 0, is_aiff = 0, is_aifc = 0;
int AIFCext = 0; int FormatVersionChunkFound = 0, CommonChunkFound = 0, SoundDataChunkFound = 0, MarkerChunkFound = 0, InstrumentChunkFound = 0;
int AIFF = 0; off_t MarkerChunkOffset = -1, InstrumentChunkOffset = -1;
int AIFC = 0;
int FormatVersionChunkFound = 0;
int CommonChunkFound = 0;
int SoundDataChunkFound = 0;
int MarkerChunkFound = 0;
off_t MarkerChunkOffset = -1;
int InstrumentChunkFound =0;
off_t InstrumentChunkOffset = -1;
/* checks */ /* checks */
/* .aif: common (AIFF or AIFC), .aiff: common AIFF, .aifc: common AIFC /* .aif: common (AIFF or AIFC), .aiff: common AIFF, .aifc: common AIFC
* .cbd2: M2 games, .bgm: Super Street Fighter II Turbo (3DO), aifcl/aiffl: for plugins? */ * .cbd2: M2 games
* .bgm: Super Street Fighter II Turbo (3DO)
* .acm: Crusader - No Remorse (SAT)
* .adp: Sonic Jam (SAT)
* .ai: Dragon Force (SAT)
* .aifcl/aiffl: for plugins? */
if (check_extensions(streamFile, "aif")) { if (check_extensions(streamFile, "aif")) {
AIFCext = 1; is_aifc_ext = 1;
AIFFext = 1; is_aiff_ext = 1;
} }
else if (check_extensions(streamFile, "aifc,aifcl,afc,cbd2,bgm")) { else if (check_extensions(streamFile, "aifc,aifcl,afc,cbd2,bgm")) {
AIFCext = 1; is_aifc_ext = 1;
} }
else if (check_extensions(streamFile, "aiff,aiffl")) { else if (check_extensions(streamFile, "aiff,acm,adp,ai,aiffl")) {
AIFFext = 1; is_aiff_ext = 1;
}
else {
goto fail;
}
/* check header */
if ((uint32_t)read_32bitBE(0,streamFile)==0x464F524D && /* "FORM" */
/* check that file = header (8) + data */
(uint32_t)read_32bitBE(4,streamFile)+8==get_streamfile_size(streamFile))
{
if ((uint32_t)read_32bitBE(8,streamFile)==0x41494643) /* "AIFC" */
{
if (!AIFCext) goto fail;
AIFC = 1;
}
else if ((uint32_t)read_32bitBE(8,streamFile)==0x41494646) /* "AIFF" */
{
if (!AIFFext) goto fail;
AIFF = 1;
}
else goto fail;
} }
else { else {
goto fail; goto fail;
} }
file_size = get_streamfile_size(streamFile); file_size = get_streamfile_size(streamFile);
if ((uint32_t)read_32bitBE(0x00,streamFile) != 0x464F524D && /* "FORM" */
(uint32_t)read_32bitBE(0x04,streamFile)+0x08 != file_size)
goto fail;
if ((uint32_t)read_32bitBE(0x08,streamFile) == 0x41494643) { /* "AIFC" */
if (!is_aifc_ext) goto fail;
is_aifc = 1;
}
else if ((uint32_t)read_32bitBE(0x08,streamFile) == 0x41494646) { /* "AIFF" */
if (!is_aiff_ext) goto fail;
is_aiff = 1;
}
else {
goto fail;
}
/* read through chunks to verify format and find metadata */ /* read through chunks to verify format and find metadata */
{ {
@ -125,7 +111,7 @@ VGMSTREAM * init_vgmstream_aifc(STREAMFILE *streamFile) {
while (current_chunk < file_size) { while (current_chunk < file_size) {
uint32_t chunk_type = read_32bitBE(current_chunk,streamFile); uint32_t chunk_type = read_32bitBE(current_chunk,streamFile);
off_t chunk_size = read_32bitBE(current_chunk+4,streamFile); off_t chunk_size = read_32bitBE(current_chunk+0x04,streamFile);
/* chunks must be padded to an even number of bytes but chunk /* chunks must be padded to an even number of bytes but chunk
* size does not include that padding */ * size does not include that padding */
@ -136,14 +122,14 @@ VGMSTREAM * init_vgmstream_aifc(STREAMFILE *streamFile) {
switch(chunk_type) { switch(chunk_type) {
case 0x46564552: /* "FVER" (version info) */ case 0x46564552: /* "FVER" (version info) */
if (FormatVersionChunkFound) goto fail; if (FormatVersionChunkFound) goto fail;
if (AIFF) goto fail; /* plain AIFF shouldn't have */ if (is_aiff) goto fail; /* plain AIFF shouldn't have */
FormatVersionChunkFound = 1; FormatVersionChunkFound = 1;
/* specific size */ /* specific size */
if (chunk_size != 4) goto fail; if (chunk_size != 4) goto fail;
/* Version 1 of AIFF-C spec timestamp */ /* Version 1 of AIFF-C spec timestamp */
if ((uint32_t)read_32bitBE(current_chunk+8,streamFile) != 0xA2805140) goto fail; if ((uint32_t)read_32bitBE(current_chunk+0x08,streamFile) != 0xA2805140) goto fail;
break; break;
case 0x434F4D4D: /* "COMM" (main header) */ case 0x434F4D4D: /* "COMM" (main header) */
@ -153,11 +139,11 @@ VGMSTREAM * init_vgmstream_aifc(STREAMFILE *streamFile) {
channel_count = read_16bitBE(current_chunk+8,streamFile); channel_count = read_16bitBE(current_chunk+8,streamFile);
if (channel_count <= 0) goto fail; if (channel_count <= 0) goto fail;
sample_count = (uint32_t)read_32bitBE(current_chunk+0x0a,streamFile); /* number of blocks, actually */ sample_count = (uint32_t)read_32bitBE(current_chunk+0x0a,streamFile); /* sometimes number of blocks */
sample_size = read_16bitBE(current_chunk+0x0e,streamFile); sample_size = read_16bitBE(current_chunk+0x0e,streamFile);
sample_rate = read80bitSANE(current_chunk+0x10,streamFile); sample_rate = read80bitSANE(current_chunk+0x10,streamFile);
if (AIFC) { if (is_aifc) {
switch (read_32bitBE(current_chunk+0x1a,streamFile)) { switch (read_32bitBE(current_chunk+0x1a,streamFile)) {
case 0x53445832: /* "SDX2" [3DO games: Super Street Fighter II Turbo (3DO), etc] */ case 0x53445832: /* "SDX2" [3DO games: Super Street Fighter II Turbo (3DO), etc] */
coding_type = coding_SDX2; coding_type = coding_SDX2;
@ -171,7 +157,7 @@ VGMSTREAM * init_vgmstream_aifc(STREAMFILE *streamFile) {
coding_type = coding_DVI_IMA_int; coding_type = coding_DVI_IMA_int;
if (channel_count != 1) break; /* don't know how stereo DVI is laid out */ if (channel_count != 1) break; /* don't know how stereo DVI is laid out */
break; break;
case 0x696D6134: /* "ima4" [Alida (PC) Lunar SSS (iOS)] */ case 0x696D6134: /* "ima4" [Alida (PC), Lunar SSS (iOS)] */
coding_type = coding_APPLE_IMA4; coding_type = coding_APPLE_IMA4;
interleave = 0x22; interleave = 0x22;
sample_count = sample_count * ((interleave-0x2)*2); sample_count = sample_count * ((interleave-0x2)*2);
@ -182,7 +168,7 @@ VGMSTREAM * init_vgmstream_aifc(STREAMFILE *streamFile) {
} }
/* string size and human-readable AIFF-C codec follows */ /* string size and human-readable AIFF-C codec follows */
} }
else if (AIFF) { else if (is_aiff) {
switch (sample_size) { switch (sample_size) {
case 8: case 8:
coding_type = coding_PCM8; coding_type = coding_PCM8;
@ -192,6 +178,9 @@ VGMSTREAM * init_vgmstream_aifc(STREAMFILE *streamFile) {
coding_type = coding_PCM16BE; coding_type = coding_PCM16BE;
interleave = 2; interleave = 2;
break; break;
case 4: /* Crusader: No Remorse (SAT), Road Rash (3DO) */
coding_type = coding_XA;
break;
default: default:
VGM_LOG("AIFF: unknown codec\n"); VGM_LOG("AIFF: unknown codec\n");
goto fail; goto fail;
@ -200,10 +189,12 @@ VGMSTREAM * init_vgmstream_aifc(STREAMFILE *streamFile) {
break; break;
case 0x53534E44: /* "SSND" (main data) */ case 0x53534E44: /* "SSND" (main data) */
case 0x4150434D: /* "APCM" (main data for XA) */
if (SoundDataChunkFound) goto fail; if (SoundDataChunkFound) goto fail;
SoundDataChunkFound = 1; SoundDataChunkFound = 1;
start_offset = current_chunk + 16 + read_32bitBE(current_chunk+8,streamFile); start_offset = current_chunk + 0x10 + read_32bitBE(current_chunk+0x08,streamFile);
/* when "APCM" XA frame size is at 0x0c, fixed to 0x914 */
break; break;
case 0x4D41524B: /* "MARK" (loops) */ case 0x4D41524B: /* "MARK" (loops) */
@ -229,10 +220,10 @@ VGMSTREAM * init_vgmstream_aifc(STREAMFILE *streamFile) {
} }
} }
if (AIFC) { if (is_aifc) {
if (!FormatVersionChunkFound || !CommonChunkFound || !SoundDataChunkFound) if (!FormatVersionChunkFound || !CommonChunkFound || !SoundDataChunkFound)
goto fail; goto fail;
} else if (AIFF) { } else if (is_aiff) {
if (!CommonChunkFound || !SoundDataChunkFound) if (!CommonChunkFound || !SoundDataChunkFound)
goto fail; goto fail;
} }
@ -260,7 +251,8 @@ VGMSTREAM * init_vgmstream_aifc(STREAMFILE *streamFile) {
* We shouldn't have a loop point that overflows an int32_t * We shouldn't have a loop point that overflows an int32_t
* anyway. */ * anyway. */
loop_flag = 1; loop_flag = 1;
if (loop_start==loop_end) loop_flag = 0; if (loop_start==loop_end)
loop_flag = 0;
} }
} }
} }
@ -276,17 +268,28 @@ VGMSTREAM * init_vgmstream_aifc(STREAMFILE *streamFile) {
vgmstream->loop_end_sample = loop_end; vgmstream->loop_end_sample = loop_end;
vgmstream->coding_type = coding_type; vgmstream->coding_type = coding_type;
if (coding_type == coding_XA) {
vgmstream->layout_type = layout_blocked_xa_aiff;
/* AIFF XA can use sample rates other than 37800/18900 */
/* some Crusader: No Remorse tracks have XA headers with incorrect 0xFF, rip bug/encoder feature? */
}
else {
vgmstream->layout_type = (channel_count > 1) ? layout_interleave : layout_none; vgmstream->layout_type = (channel_count > 1) ? layout_interleave : layout_none;
vgmstream->interleave_block_size = interleave; vgmstream->interleave_block_size = interleave;
}
if (AIFC) if (is_aifc)
vgmstream->meta_type = meta_AIFC; vgmstream->meta_type = meta_AIFC;
else if (AIFF) else if (is_aiff)
vgmstream->meta_type = meta_AIFF; vgmstream->meta_type = meta_AIFF;
if (!vgmstream_open_stream(vgmstream,streamFile,start_offset)) if (!vgmstream_open_stream(vgmstream,streamFile,start_offset))
goto fail; goto fail;
if (vgmstream->layout_type == layout_blocked_xa_aiff)
block_update_xa_aiff(start_offset,vgmstream);
return vgmstream; return vgmstream;
fail: fail:

View File

@ -7,12 +7,13 @@ VGMSTREAM * init_vgmstream_cdxa(STREAMFILE *streamFile) {
VGMSTREAM * vgmstream = NULL; VGMSTREAM * vgmstream = NULL;
off_t start_offset; off_t start_offset;
int loop_flag = 0, channel_count, sample_rate; int loop_flag = 0, channel_count, sample_rate;
int xa_channel=0;
int is_blocked; int is_blocked;
size_t file_size = get_streamfile_size(streamFile); size_t file_size = get_streamfile_size(streamFile);
/* check extension (.xa: common, .str: sometimes used) */ /* checks
if ( !check_extensions(streamFile,"xa,str") ) * .xa: common, .str: sometimes (mainly videos)
* .adp: Phantasy Star Collection (SAT) raw XA */
if ( !check_extensions(streamFile,"xa,str,adp") )
goto fail; goto fail;
/* Proper XA comes in raw (BIN 2352 mode2/form2) CD sectors, that contain XA subheaders. /* Proper XA comes in raw (BIN 2352 mode2/form2) CD sectors, that contain XA subheaders.
@ -33,7 +34,7 @@ VGMSTREAM * init_vgmstream_cdxa(STREAMFILE *streamFile) {
is_blocked = 1; is_blocked = 1;
start_offset = 0x00; start_offset = 0x00;
} }
else { /* headerless */ else { /* headerless and possibly incorrectly ripped */
is_blocked = 0; is_blocked = 0;
start_offset = 0x00; start_offset = 0x00;
} }
@ -50,29 +51,35 @@ VGMSTREAM * init_vgmstream_cdxa(STREAMFILE *streamFile) {
test_offset += (is_blocked ? 0x18 : 0x00); /* header */ test_offset += (is_blocked ? 0x18 : 0x00); /* header */
for (i = 0; i < (sector_size/block_size); i++) { for (i = 0; i < (sector_size/block_size); i++) {
/* first 0x10 ADPCM predictors should be 0..3 index */ /* XA headers checks: filter indexes should be 0..3, and shifts 0..C */
for (j = 0; j < 16; j++) { for (j = 0; j < 16; j++) {
uint8_t header = read_8bit(test_offset + i, streamFile); uint8_t header = (uint8_t)read_8bit(test_offset + i, streamFile);
if (((header >> 4) & 0xF) > 3) if (((header >> 4) & 0xF) > 0x03)
goto fail;
if (((header >> 0) & 0xF) > 0x0c)
goto fail; goto fail;
} }
/* XA headers pairs are repeated */
if (read_32bitBE(test_offset+0x00,streamFile) != read_32bitBE(test_offset+0x04,streamFile) ||
read_32bitBE(test_offset+0x08,streamFile) != read_32bitBE(test_offset+0x0c,streamFile))
goto fail;
test_offset += 0x80; test_offset += 0x80;
} }
test_offset += (is_blocked ? 0x18 : 0x00); /* footer */ test_offset += (is_blocked ? 0x18 : 0x00); /* footer */
} }
/* (the above could get fooled by files with many 0s at the beginning;
* this could be detected as blank XA frames should have have 0c0c0c0c... headers */
} }
/* data is ok: parse header */ /* data is ok: parse header */
if (is_blocked) { if (is_blocked) {
uint8_t xa_header;
/* parse 0x18 sector header (also see xa_blocked.c) */ /* parse 0x18 sector header (also see xa_blocked.c) */
xa_channel = read_8bit(start_offset + 0x11,streamFile); uint8_t xa_header = (uint8_t)read_8bit(start_offset + 0x13,streamFile);
xa_header = read_8bit(start_offset + 0x13,streamFile);
switch((xa_header >> 0) & 3) { /* 0..1: stereo */ switch((xa_header >> 0) & 3) { /* 0..1: mono/stereo */
case 0: channel_count = 1; break; case 0: channel_count = 1; break;
case 1: channel_count = 2; break; case 1: channel_count = 2; break;
default: goto fail; default: goto fail;
@ -82,16 +89,49 @@ VGMSTREAM * init_vgmstream_cdxa(STREAMFILE *streamFile) {
case 1: sample_rate = 18900; break; case 1: sample_rate = 18900; break;
default: goto fail; default: goto fail;
} }
VGM_ASSERT(((xa_header >> 4) & 3) == 1, /* 4..5: bits per sample (0=4, 1=8) */ switch((xa_header >> 4) & 3) { /* 4..5: bits per sample (0=4, 1=8) */
"XA: 8 bits per sample mode found\n"); /* spec only? */ case 0: break;
/* 6: emphasis (applies a filter but apparently not used by games) default: /* PS1 games only do 4b */
* XA is also filtered when resampled to 44100 during output, differently from PS-ADPCM */ VGM_LOG("XA: unknown bits per sample found\n");
/* 7: reserved */ goto fail;
}
switch((xa_header >> 6) & 1) { /* 6: emphasis (applies a filter) */
case 0: break;
default: /* shouldn't be used by games */
VGM_LOG("XA: unknown emphasis found\n");
break;
}
switch((xa_header >> 7) & 1) { /* 7: reserved */
case 0: break;
default:
VGM_LOG("XA: unknown reserved bit found\n");
break;
}
}
else {
/* headerless */
if (check_extensions(streamFile,"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);
/* 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;
sample_rate = 22050;
} }
else { else {
/* headerless, probably will go wrong */
channel_count = 2; channel_count = 2;
sample_rate = 44100; /* not 37800? */ sample_rate = 44100;
}
}
else {
/* incorrectly ripped standard XA */
channel_count = 2;
sample_rate = 37800;
}
} }
@ -100,23 +140,19 @@ VGMSTREAM * init_vgmstream_cdxa(STREAMFILE *streamFile) {
if (!vgmstream) goto fail; if (!vgmstream) goto fail;
vgmstream->sample_rate = sample_rate; vgmstream->sample_rate = sample_rate;
//todo do block_updates to find num_samples? (to skip non-audio blocks)
vgmstream->num_samples = xa_bytes_to_samples(file_size - start_offset, channel_count, is_blocked); vgmstream->num_samples = xa_bytes_to_samples(file_size - start_offset, channel_count, is_blocked);
vgmstream->xa_headerless = !is_blocked;
vgmstream->xa_channel = xa_channel;
vgmstream->coding_type = coding_XA;
vgmstream->layout_type = layout_blocked_xa;
vgmstream->meta_type = meta_PSX_XA; vgmstream->meta_type = meta_PSX_XA;
vgmstream->coding_type = coding_XA;
if (is_blocked) vgmstream->layout_type = is_blocked ? layout_blocked_xa : layout_none;
start_offset += 0x18; /* move to first frame (hack for xa_blocked.c) */
/* open the file for reading */ /* open the file for reading */
if ( !vgmstream_open_stream(vgmstream, streamFile, start_offset) ) if ( !vgmstream_open_stream(vgmstream, streamFile, start_offset) )
goto fail; goto fail;
if (vgmstream->layout_type == layout_blocked_xa)
block_update_xa(start_offset,vgmstream); block_update_xa(start_offset,vgmstream);
return vgmstream; return vgmstream;
fail: fail:

View File

@ -943,6 +943,7 @@ void render_vgmstream(sample * buffer, int32_t sample_count, VGMSTREAM * vgmstre
case layout_blocked_ea_wve_ad10: case layout_blocked_ea_wve_ad10:
case layout_blocked_sthd: case layout_blocked_sthd:
case layout_blocked_h4m: case layout_blocked_h4m:
case layout_blocked_xa_aiff:
render_vgmstream_blocked(buffer,sample_count,vgmstream); render_vgmstream_blocked(buffer,sample_count,vgmstream);
break; break;
case layout_aix: case layout_aix:
@ -1077,6 +1078,7 @@ int get_vgmstream_samples_per_frame(VGMSTREAM * vgmstream) {
return 32; return 32;
case coding_XA: case coding_XA:
return 28*8 / vgmstream->channels; /* 8 subframes per frame, mono/stereo */
case coding_PSX: case coding_PSX:
case coding_PSX_badflags: case coding_PSX_badflags:
case coding_HEVAG: case coding_HEVAG:
@ -1241,7 +1243,7 @@ int get_vgmstream_frame_size(VGMSTREAM * vgmstream) {
return 0x22; return 0x22;
case coding_XA: case coding_XA:
return 0x0e*vgmstream->channels; return 0x80;
case coding_PSX: case coding_PSX:
case coding_PSX_badflags: case coding_PSX_badflags:
case coding_HEVAG: case coding_HEVAG:
@ -1581,7 +1583,7 @@ void decode_vgmstream(VGMSTREAM * vgmstream, int samples_written, int samples_to
break; break;
case coding_XA: case coding_XA:
for (chan=0;chan<vgmstream->channels;chan++) { for (chan=0;chan<vgmstream->channels;chan++) {
decode_xa(vgmstream,buffer+samples_written*vgmstream->channels+chan, decode_xa(&vgmstream->ch[chan],buffer+samples_written*vgmstream->channels+chan,
vgmstream->channels,vgmstream->samples_into_block, vgmstream->channels,vgmstream->samples_into_block,
samples_to_do,chan); samples_to_do,chan);
} }

View File

@ -254,6 +254,7 @@ typedef enum {
layout_blocked_ea_wve_ad10, /* EA WVE Ad10 blocks */ layout_blocked_ea_wve_ad10, /* EA WVE Ad10 blocks */
layout_blocked_sthd, /* Dream Factory STHD */ layout_blocked_sthd, /* Dream Factory STHD */
layout_blocked_h4m, /* H4M video */ layout_blocked_h4m, /* H4M video */
layout_blocked_xa_aiff, /* XA in AIFF files [Crusader: No Remorse (SAT), Road Rash (3DO)] */
/* otherwise odd */ /* otherwise odd */
layout_aix, /* CRI AIX's wheels within wheels */ layout_aix, /* CRI AIX's wheels within wheels */
@ -796,11 +797,6 @@ typedef struct {
int codec_endian; /* little/big endian marker; name is left vague but usually means big endian */ int codec_endian; /* little/big endian marker; name is left vague but usually means big endian */
int codec_version; /* flag for codecs with minor variations */ int codec_version; /* flag for codecs with minor variations */
uint8_t xa_channel; /* XA ADPCM: selected channel */
int32_t xa_sector_length; /* XA ADPCM: XA block */
uint8_t xa_headerless; /* XA ADPCM: headerless XA */
int8_t xa_get_high_nibble; /* XA ADPCM: mono/stereo nibble selection (XA state could be simplified) */
int32_t ws_output_size; /* WS ADPCM: output bytes for this block */ int32_t ws_output_size; /* WS ADPCM: output bytes for this block */
void * start_vgmstream; /* a copy of the VGMSTREAM as it was at the beginning of the stream (for AAX/AIX/SCD) */ void * start_vgmstream; /* a copy of the VGMSTREAM as it was at the beginning of the stream (for AAX/AIX/SCD) */