mirror of
https://github.com/vgmstream/vgmstream.git
synced 2024-11-14 18:47:39 +01:00
commit
eee451aa68
@ -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);
|
||||
|
||||
/* 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);
|
||||
|
||||
/* ea_xa_decoder */
|
||||
|
@ -549,6 +549,9 @@ static void flush_ea_mt_internal(VGMSTREAM *vgmstream, int is_start) {
|
||||
ctx->ptr = ctx->buffer;
|
||||
ctx->end = ctx->buffer + bytes;
|
||||
ctx->bits_count = 0;
|
||||
|
||||
if (is_start)
|
||||
ctx->parsed_header = 0;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -8,8 +8,9 @@
|
||||
/* XA ADPCM gain values */
|
||||
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 int IK0(int fid) { return ((int)((-K0[fid]) * (1 << 10))); } /* K0/1 floats to int, K*2^10 = K*(1<<10) = K*1024 */
|
||||
static int IK1(int fid) { return ((int)((-K1[fid]) * (1 << 10))); }
|
||||
/* K0/1 floats to int, K*2^10 = K*(1<<10) = K*1024 */
|
||||
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.
|
||||
* 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)
|
||||
*
|
||||
* 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
|
||||
* the SNES/SPC700 BRR, with specific per-filter ops.
|
||||
* differently, maybe using one of the above methods in software/CPU, but in XA's case may be done
|
||||
* 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
|
||||
* 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
|
||||
* (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};
|
||||
VGMSTREAMCHANNEL * stream = &(vgmstream->ch[channel]);
|
||||
off_t sp_offset;
|
||||
int i;
|
||||
int frames_in, sample_count = 0;
|
||||
int32_t coef1, coef2, coef_index, shift_factor;
|
||||
|
||||
void decode_xa(VGMSTREAMCHANNEL * stream, sample * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do, int channel) {
|
||||
off_t frame_offset, sp_offset;
|
||||
int i,j, 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;
|
||||
|
||||
/* external interleave (fixed size), mono/stereo */
|
||||
frames_in = first_sample / (28*2 / channelspacing);
|
||||
first_sample = first_sample % 28;
|
||||
bytes_per_frame = 0x80;
|
||||
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 */
|
||||
vgmstream->xa_get_high_nibble = !vgmstream->xa_get_high_nibble;
|
||||
if (first_sample && channelspacing==1)
|
||||
vgmstream->xa_get_high_nibble = !vgmstream->xa_get_high_nibble;
|
||||
/* 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
|
||||
*/
|
||||
frame_offset = stream->offset + bytes_per_frame*frames_in;
|
||||
|
||||
/* parse current sound unit (subframe) sound parameters */
|
||||
sp_offset = stream->offset+head_table[frames_in]+vgmstream->xa_get_high_nibble;
|
||||
coef_index = (read_8bit(sp_offset,stream->streamfile) >> 4) & 0xf;
|
||||
shift_factor = (read_8bit(sp_offset,stream->streamfile) ) & 0xf;
|
||||
if (read_32bitBE(frame_offset+0x00,stream->streamfile) != read_32bitBE(frame_offset+0x04,stream->streamfile) ||
|
||||
read_32bitBE(frame_offset+0x08,stream->streamfile) != read_32bitBE(frame_offset+0x0c,stream->streamfile)) {
|
||||
VGM_LOG("bad frames at %lx\n", frame_offset);
|
||||
}
|
||||
|
||||
|
||||
/* 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);
|
||||
if (coef_index > 4)
|
||||
@ -68,18 +96,34 @@ void decode_xa(VGMSTREAM * vgmstream, sample * outbuf, int channelspacing, int32
|
||||
if (shift_factor > 12)
|
||||
shift_factor = 9; /* supposedly, from Nocash PSX docs */
|
||||
|
||||
coef1 = IK0(coef_index);
|
||||
coef2 = IK1(coef_index);
|
||||
coef1 = get_IK0(coef_index);
|
||||
coef2 = get_IK1(coef_index);
|
||||
|
||||
|
||||
/* decode nibbles */
|
||||
for (i = first_sample; i < first_sample + samples_to_do; i++) {
|
||||
/* decode subframe nibbles */
|
||||
for(j = 0; j < 28; j++) {
|
||||
uint8_t nibbles;
|
||||
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 ) & 0x0f;
|
||||
|
||||
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);
|
||||
@ -89,8 +133,11 @@ void decode_xa(VGMSTREAM * vgmstream, sample * outbuf, int channelspacing, int32
|
||||
new_sample = new_sample >> 4;
|
||||
new_sample = clamp16(new_sample);
|
||||
|
||||
outbuf[sample_count] = new_sample;
|
||||
sample_count += channelspacing;
|
||||
outbuf[samples_done * channelspacing] = new_sample;
|
||||
samples_done++;
|
||||
|
||||
sample_count++;
|
||||
}
|
||||
}
|
||||
|
||||
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) {
|
||||
if (is_blocked) {
|
||||
//todo with -0x10 misses the last sector, not sure if bug or feature
|
||||
return ((bytes - 0x10) / 0x930) * (0x900 - 18*0x10) * 2 / channels;
|
||||
return (bytes / 0x930) * (28*8/ channels) * 18;
|
||||
}
|
||||
else {
|
||||
return ((bytes / 0x80)*0xE0) / 2;
|
||||
return (bytes / 0x80) * (28*8 / channels);
|
||||
}
|
||||
}
|
||||
|
@ -32,6 +32,7 @@ static const char* extension_list[] = {
|
||||
"afc",
|
||||
"agsc",
|
||||
"ahx",
|
||||
"ai",
|
||||
//"aif", //common
|
||||
"aifc", //common?
|
||||
"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_sthd, "blocked (STHD)"},
|
||||
{layout_blocked_h4m, "blocked (H4M)"},
|
||||
{layout_blocked_xa_aiff, "blocked (XA AIFF)"},
|
||||
};
|
||||
|
||||
static const meta_info meta_info_list[] = {
|
||||
|
@ -205,6 +205,9 @@ static void block_update(VGMSTREAM * vgmstream) {
|
||||
case layout_blocked_h4m:
|
||||
block_update_h4m(vgmstream->next_block_offset,vgmstream);
|
||||
break;
|
||||
case layout_blocked_xa_aiff:
|
||||
block_update_xa_aiff(vgmstream->next_block_offset,vgmstream);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
@ -2,30 +2,32 @@
|
||||
#include "../coding/coding.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) {
|
||||
STREAMFILE* streamFile = vgmstream->ch[0].streamfile;
|
||||
int i;
|
||||
int8_t currentChannel=0;
|
||||
int8_t subAudio=0;
|
||||
size_t block_samples;
|
||||
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
|
||||
* 0x00: sync word
|
||||
* 0x0c: header = minute, second, sector, mode (always 0x02)
|
||||
* 0x10: subheader = file, channel (marker), submode flags, xa header
|
||||
* 0x14: subheader again
|
||||
* 0x10: subheader = file, channel (substream marker), submode flags, xa header
|
||||
* 0x14: subheader again (for error correction)
|
||||
* 0x18: data
|
||||
* 0x918: unused
|
||||
* 0x92c: EDC/checksum or null
|
||||
* 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)
|
||||
* - 7: end of file
|
||||
* - 6: real time mode
|
||||
@ -36,38 +38,22 @@ void block_update_xa(off_t block_offset, VGMSTREAM * vgmstream) {
|
||||
* - 1: video sector
|
||||
* - 0: end of audio
|
||||
*/
|
||||
xa_submode = (uint8_t)read_8bit(block_offset + 0x12,streamFile);
|
||||
|
||||
// We get to the end of a sector ?
|
||||
if (vgmstream->xa_sector_length == (18*0x80)) {
|
||||
vgmstream->xa_sector_length = 0;
|
||||
|
||||
// 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;
|
||||
}
|
||||
/* audio sector must set/not set certain flags, as per spec */
|
||||
if ((xa_submode & 0x20) && !(xa_submode & 0x08) && (xa_submode & 0x04) && !(xa_submode & 0x02) ) {
|
||||
block_samples = (28*8 / vgmstream->channels) * 18; /* size 0x900, 18 frames of size 0x80 with 8 subframes of 28 samples */
|
||||
}
|
||||
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_samples = block_samples;
|
||||
vgmstream->next_block_offset = block_offset + 0x930;
|
||||
|
||||
// Quid : how to stop the current channel ???
|
||||
// i set up 0 to current_block_size to make vgmstream not playing bad samples
|
||||
// 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;
|
||||
for (i = 0; i < vgmstream->channels; i++) {
|
||||
vgmstream->ch[i].offset = block_offset + 0x18;
|
||||
}
|
||||
}
|
||||
|
19
src/layout/blocked_xa_aiff.c
Normal file
19
src/layout/blocked_xa_aiff.c
Normal 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;
|
||||
}
|
||||
}
|
@ -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_sthd(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 */
|
||||
void render_vgmstream_interleave(sample * buffer, int32_t sample_count, VGMSTREAM * vgmstream);
|
||||
|
@ -1922,6 +1922,10 @@
|
||||
RelativePath=".\layout\blocked_xa.c"
|
||||
>
|
||||
</File>
|
||||
<File
|
||||
RelativePath=".\layout\blocked_xa_aiff.c"
|
||||
>
|
||||
</File>
|
||||
<File
|
||||
RelativePath=".\layout\blocked_xvas.c"
|
||||
>
|
||||
|
@ -524,6 +524,7 @@
|
||||
<ClCompile Include="layout\blocked_ws_aud.c" />
|
||||
<ClCompile Include="layout\blocked_wsi.c" />
|
||||
<ClCompile Include="layout\blocked_xa.c" />
|
||||
<ClCompile Include="layout\blocked_xa_aiff.c" />
|
||||
<ClCompile Include="layout\blocked_xvas.c" />
|
||||
<ClCompile Include="..\ext_libs\clHCA.c" />
|
||||
</ItemGroup>
|
||||
|
@ -1138,6 +1138,9 @@
|
||||
<ClCompile Include="layout\blocked_xa.c">
|
||||
<Filter>layout\Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="layout\blocked_xa_aiff.c">
|
||||
<Filter>layout\Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="layout\blocked_xvas.c">
|
||||
<Filter>layout\Source Files</Filter>
|
||||
</ClCompile>
|
||||
|
131
src/meta/aifc.c
131
src/meta/aifc.c
@ -26,8 +26,7 @@ static uint32_t read80bitSANE(off_t offset, STREAMFILE *streamFile) {
|
||||
return mantissa*((buf[0]&0x80)?-1:1);
|
||||
}
|
||||
|
||||
static uint32_t find_marker(STREAMFILE *streamFile, off_t MarkerChunkOffset,
|
||||
int marker_id) {
|
||||
static uint32_t find_marker(STREAMFILE *streamFile, off_t MarkerChunkOffset, int marker_id) {
|
||||
uint16_t marker_count;
|
||||
int i;
|
||||
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 */
|
||||
VGMSTREAM * init_vgmstream_aifc(STREAMFILE *streamFile) {
|
||||
VGMSTREAM * vgmstream = NULL;
|
||||
|
||||
off_t file_size = -1;
|
||||
int channel_count = 0;
|
||||
int sample_count = 0;
|
||||
int sample_size = 0;
|
||||
int sample_rate = 0;
|
||||
int coding_type = -1;
|
||||
off_t start_offset = -1;
|
||||
int interleave = -1;
|
||||
off_t start_offset = 0;
|
||||
size_t file_size;
|
||||
coding_t coding_type = 0;
|
||||
int channel_count = 0, sample_count = 0, sample_size = 0, sample_rate = 0;
|
||||
int interleave = 0;
|
||||
|
||||
int loop_flag = 0;
|
||||
int32_t loop_start = -1;
|
||||
int32_t loop_end = -1;
|
||||
int32_t loop_start = 0, loop_end = 0;
|
||||
|
||||
int AIFFext = 0;
|
||||
int AIFCext = 0;
|
||||
int AIFF = 0;
|
||||
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;
|
||||
int is_aiff_ext = 0, is_aifc_ext = 0, is_aiff = 0, is_aifc = 0;
|
||||
int FormatVersionChunkFound = 0, CommonChunkFound = 0, SoundDataChunkFound = 0, MarkerChunkFound = 0, InstrumentChunkFound = 0;
|
||||
off_t MarkerChunkOffset = -1, InstrumentChunkOffset = -1;
|
||||
|
||||
|
||||
/* checks */
|
||||
/* .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")) {
|
||||
AIFCext = 1;
|
||||
AIFFext = 1;
|
||||
is_aifc_ext = 1;
|
||||
is_aiff_ext = 1;
|
||||
}
|
||||
else if (check_extensions(streamFile, "aifc,aifcl,afc,cbd2,bgm")) {
|
||||
AIFCext = 1;
|
||||
is_aifc_ext = 1;
|
||||
}
|
||||
else if (check_extensions(streamFile, "aiff,aiffl")) {
|
||||
AIFFext = 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 if (check_extensions(streamFile, "aiff,acm,adp,ai,aiffl")) {
|
||||
is_aiff_ext = 1;
|
||||
}
|
||||
else {
|
||||
goto fail;
|
||||
}
|
||||
|
||||
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 */
|
||||
{
|
||||
@ -125,7 +111,7 @@ VGMSTREAM * init_vgmstream_aifc(STREAMFILE *streamFile) {
|
||||
|
||||
while (current_chunk < file_size) {
|
||||
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
|
||||
* size does not include that padding */
|
||||
@ -136,14 +122,14 @@ VGMSTREAM * init_vgmstream_aifc(STREAMFILE *streamFile) {
|
||||
switch(chunk_type) {
|
||||
case 0x46564552: /* "FVER" (version info) */
|
||||
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;
|
||||
|
||||
/* specific size */
|
||||
if (chunk_size != 4) goto fail;
|
||||
|
||||
/* 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;
|
||||
|
||||
case 0x434F4D4D: /* "COMM" (main header) */
|
||||
@ -153,11 +139,11 @@ VGMSTREAM * init_vgmstream_aifc(STREAMFILE *streamFile) {
|
||||
channel_count = read_16bitBE(current_chunk+8,streamFile);
|
||||
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_rate = read80bitSANE(current_chunk+0x10,streamFile);
|
||||
|
||||
if (AIFC) {
|
||||
if (is_aifc) {
|
||||
switch (read_32bitBE(current_chunk+0x1a,streamFile)) {
|
||||
case 0x53445832: /* "SDX2" [3DO games: Super Street Fighter II Turbo (3DO), etc] */
|
||||
coding_type = coding_SDX2;
|
||||
@ -171,7 +157,7 @@ VGMSTREAM * init_vgmstream_aifc(STREAMFILE *streamFile) {
|
||||
coding_type = coding_DVI_IMA_int;
|
||||
if (channel_count != 1) break; /* don't know how stereo DVI is laid out */
|
||||
break;
|
||||
case 0x696D6134: /* "ima4" [Alida (PC) Lunar SSS (iOS)] */
|
||||
case 0x696D6134: /* "ima4" [Alida (PC), Lunar SSS (iOS)] */
|
||||
coding_type = coding_APPLE_IMA4;
|
||||
interleave = 0x22;
|
||||
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 */
|
||||
}
|
||||
else if (AIFF) {
|
||||
else if (is_aiff) {
|
||||
switch (sample_size) {
|
||||
case 8:
|
||||
coding_type = coding_PCM8;
|
||||
@ -192,6 +178,9 @@ VGMSTREAM * init_vgmstream_aifc(STREAMFILE *streamFile) {
|
||||
coding_type = coding_PCM16BE;
|
||||
interleave = 2;
|
||||
break;
|
||||
case 4: /* Crusader: No Remorse (SAT), Road Rash (3DO) */
|
||||
coding_type = coding_XA;
|
||||
break;
|
||||
default:
|
||||
VGM_LOG("AIFF: unknown codec\n");
|
||||
goto fail;
|
||||
@ -200,10 +189,12 @@ VGMSTREAM * init_vgmstream_aifc(STREAMFILE *streamFile) {
|
||||
break;
|
||||
|
||||
case 0x53534E44: /* "SSND" (main data) */
|
||||
case 0x4150434D: /* "APCM" (main data for XA) */
|
||||
if (SoundDataChunkFound) goto fail;
|
||||
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;
|
||||
|
||||
case 0x4D41524B: /* "MARK" (loops) */
|
||||
@ -229,10 +220,10 @@ VGMSTREAM * init_vgmstream_aifc(STREAMFILE *streamFile) {
|
||||
}
|
||||
}
|
||||
|
||||
if (AIFC) {
|
||||
if (is_aifc) {
|
||||
if (!FormatVersionChunkFound || !CommonChunkFound || !SoundDataChunkFound)
|
||||
goto fail;
|
||||
} else if (AIFF) {
|
||||
} else if (is_aiff) {
|
||||
if (!CommonChunkFound || !SoundDataChunkFound)
|
||||
goto fail;
|
||||
}
|
||||
@ -260,7 +251,8 @@ VGMSTREAM * init_vgmstream_aifc(STREAMFILE *streamFile) {
|
||||
* We shouldn't have a loop point that overflows an int32_t
|
||||
* anyway. */
|
||||
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->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->interleave_block_size = interleave;
|
||||
}
|
||||
|
||||
if (AIFC)
|
||||
if (is_aifc)
|
||||
vgmstream->meta_type = meta_AIFC;
|
||||
else if (AIFF)
|
||||
else if (is_aiff)
|
||||
vgmstream->meta_type = meta_AIFF;
|
||||
|
||||
|
||||
if (!vgmstream_open_stream(vgmstream,streamFile,start_offset))
|
||||
goto fail;
|
||||
|
||||
if (vgmstream->layout_type == layout_blocked_xa_aiff)
|
||||
block_update_xa_aiff(start_offset,vgmstream);
|
||||
|
||||
return vgmstream;
|
||||
|
||||
fail:
|
||||
|
@ -7,12 +7,13 @@ VGMSTREAM * init_vgmstream_cdxa(STREAMFILE *streamFile) {
|
||||
VGMSTREAM * vgmstream = NULL;
|
||||
off_t start_offset;
|
||||
int loop_flag = 0, channel_count, sample_rate;
|
||||
int xa_channel=0;
|
||||
int is_blocked;
|
||||
size_t file_size = get_streamfile_size(streamFile);
|
||||
|
||||
/* check extension (.xa: common, .str: sometimes used) */
|
||||
if ( !check_extensions(streamFile,"xa,str") )
|
||||
/* checks
|
||||
* .xa: common, .str: sometimes (mainly videos)
|
||||
* .adp: Phantasy Star Collection (SAT) raw XA */
|
||||
if ( !check_extensions(streamFile,"xa,str,adp") )
|
||||
goto fail;
|
||||
|
||||
/* 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;
|
||||
start_offset = 0x00;
|
||||
}
|
||||
else { /* headerless */
|
||||
else { /* headerless and possibly incorrectly ripped */
|
||||
is_blocked = 0;
|
||||
start_offset = 0x00;
|
||||
}
|
||||
@ -50,29 +51,35 @@ VGMSTREAM * init_vgmstream_cdxa(STREAMFILE *streamFile) {
|
||||
test_offset += (is_blocked ? 0x18 : 0x00); /* header */
|
||||
|
||||
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++) {
|
||||
uint8_t header = read_8bit(test_offset + i, streamFile);
|
||||
if (((header >> 4) & 0xF) > 3)
|
||||
uint8_t header = (uint8_t)read_8bit(test_offset + i, streamFile);
|
||||
if (((header >> 4) & 0xF) > 0x03)
|
||||
goto fail;
|
||||
if (((header >> 0) & 0xF) > 0x0c)
|
||||
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 += (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 */
|
||||
if (is_blocked) {
|
||||
uint8_t xa_header;
|
||||
|
||||
/* parse 0x18 sector header (also see xa_blocked.c) */
|
||||
xa_channel = read_8bit(start_offset + 0x11,streamFile);
|
||||
xa_header = read_8bit(start_offset + 0x13,streamFile);
|
||||
uint8_t xa_header = (uint8_t)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 1: channel_count = 2; break;
|
||||
default: goto fail;
|
||||
@ -82,16 +89,49 @@ VGMSTREAM * init_vgmstream_cdxa(STREAMFILE *streamFile) {
|
||||
case 1: sample_rate = 18900; break;
|
||||
default: goto fail;
|
||||
}
|
||||
VGM_ASSERT(((xa_header >> 4) & 3) == 1, /* 4..5: bits per sample (0=4, 1=8) */
|
||||
"XA: 8 bits per sample mode found\n"); /* spec only? */
|
||||
/* 6: emphasis (applies a filter but apparently not used by games)
|
||||
* XA is also filtered when resampled to 44100 during output, differently from PS-ADPCM */
|
||||
/* 7: reserved */
|
||||
switch((xa_header >> 4) & 3) { /* 4..5: bits per sample (0=4, 1=8) */
|
||||
case 0: break;
|
||||
default: /* PS1 games only do 4b */
|
||||
VGM_LOG("XA: unknown bits per sample found\n");
|
||||
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 {
|
||||
/* headerless, probably will go wrong */
|
||||
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;
|
||||
|
||||
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->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;
|
||||
|
||||
if (is_blocked)
|
||||
start_offset += 0x18; /* move to first frame (hack for xa_blocked.c) */
|
||||
vgmstream->coding_type = coding_XA;
|
||||
vgmstream->layout_type = is_blocked ? layout_blocked_xa : layout_none;
|
||||
|
||||
/* open the file for reading */
|
||||
if ( !vgmstream_open_stream(vgmstream, streamFile, start_offset) )
|
||||
goto fail;
|
||||
|
||||
if (vgmstream->layout_type == layout_blocked_xa)
|
||||
block_update_xa(start_offset,vgmstream);
|
||||
|
||||
return vgmstream;
|
||||
|
||||
fail:
|
||||
|
@ -943,6 +943,7 @@ void render_vgmstream(sample * buffer, int32_t sample_count, VGMSTREAM * vgmstre
|
||||
case layout_blocked_ea_wve_ad10:
|
||||
case layout_blocked_sthd:
|
||||
case layout_blocked_h4m:
|
||||
case layout_blocked_xa_aiff:
|
||||
render_vgmstream_blocked(buffer,sample_count,vgmstream);
|
||||
break;
|
||||
case layout_aix:
|
||||
@ -1077,6 +1078,7 @@ int get_vgmstream_samples_per_frame(VGMSTREAM * vgmstream) {
|
||||
return 32;
|
||||
|
||||
case coding_XA:
|
||||
return 28*8 / vgmstream->channels; /* 8 subframes per frame, mono/stereo */
|
||||
case coding_PSX:
|
||||
case coding_PSX_badflags:
|
||||
case coding_HEVAG:
|
||||
@ -1241,7 +1243,7 @@ int get_vgmstream_frame_size(VGMSTREAM * vgmstream) {
|
||||
return 0x22;
|
||||
|
||||
case coding_XA:
|
||||
return 0x0e*vgmstream->channels;
|
||||
return 0x80;
|
||||
case coding_PSX:
|
||||
case coding_PSX_badflags:
|
||||
case coding_HEVAG:
|
||||
@ -1581,7 +1583,7 @@ void decode_vgmstream(VGMSTREAM * vgmstream, int samples_written, int samples_to
|
||||
break;
|
||||
case coding_XA:
|
||||
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,
|
||||
samples_to_do,chan);
|
||||
}
|
||||
|
@ -254,6 +254,7 @@ typedef enum {
|
||||
layout_blocked_ea_wve_ad10, /* EA WVE Ad10 blocks */
|
||||
layout_blocked_sthd, /* Dream Factory STHD */
|
||||
layout_blocked_h4m, /* H4M video */
|
||||
layout_blocked_xa_aiff, /* XA in AIFF files [Crusader: No Remorse (SAT), Road Rash (3DO)] */
|
||||
|
||||
/* otherwise odd */
|
||||
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_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 */
|
||||
|
||||
void * start_vgmstream; /* a copy of the VGMSTREAM as it was at the beginning of the stream (for AAX/AIX/SCD) */
|
||||
|
Loading…
Reference in New Issue
Block a user