Updated VGMStream to r1050-3644-gc9e2016f
First new build on an Apple Silicon build machineCQTexperiment
parent
d21aa09771
commit
81a4d92d72
File diff suppressed because it is too large
Load Diff
|
@ -345,7 +345,7 @@ typedef struct { //todo simplify
|
|||
|
||||
ogg_vorbis_codec_data* init_ogg_vorbis(STREAMFILE* sf, off_t start, off_t size, ogg_vorbis_io* io);
|
||||
void decode_ogg_vorbis(ogg_vorbis_codec_data* data, sample_t* outbuf, int32_t samples_to_do, int channels);
|
||||
void reset_ogg_vorbis(VGMSTREAM* vgmstream);
|
||||
void reset_ogg_vorbis(ogg_vorbis_codec_data* data);
|
||||
void seek_ogg_vorbis(ogg_vorbis_codec_data* data, int32_t num_sample);
|
||||
void free_ogg_vorbis(ogg_vorbis_codec_data* data);
|
||||
|
||||
|
@ -353,6 +353,7 @@ int ogg_vorbis_get_comment(ogg_vorbis_codec_data* data, const char** comment);
|
|||
void ogg_vorbis_get_info(ogg_vorbis_codec_data* data, int* p_channels, int* p_sample_rate);
|
||||
void ogg_vorbis_get_samples(ogg_vorbis_codec_data* data, int* p_samples);
|
||||
void ogg_vorbis_set_disable_reordering(ogg_vorbis_codec_data* data, int set);
|
||||
void ogg_vorbis_set_force_seek(ogg_vorbis_codec_data* data, int set);
|
||||
STREAMFILE* ogg_vorbis_get_streamfile(ogg_vorbis_codec_data* data);
|
||||
|
||||
|
||||
|
|
|
@ -933,12 +933,14 @@ void ffmpeg_set_force_seek(ffmpeg_codec_data* data) {
|
|||
|
||||
const char* ffmpeg_get_metadata_value(ffmpeg_codec_data* data, const char* key) {
|
||||
AVDictionary* avd;
|
||||
AVDictionaryEntry* avde;
|
||||
AVDictionaryEntry* avde = NULL;
|
||||
|
||||
if (!data || !data->codec)
|
||||
return NULL;
|
||||
|
||||
avd = data->formatCtx->streams[data->streamIndex]->metadata;
|
||||
avd = data->formatCtx->streams[data->streamIndex]->metadata; /* per stream (like Ogg) */
|
||||
if (!avd)
|
||||
avd = data->formatCtx->metadata; /* per format (like Flac) */
|
||||
if (!avd)
|
||||
return NULL;
|
||||
|
||||
|
|
|
@ -42,7 +42,10 @@ hca_codec_data* init_hca(STREAMFILE* sf) {
|
|||
clHCA_clear(data->handle);
|
||||
|
||||
status = clHCA_DecodeHeader(data->handle, header_buffer, header_size); /* parse header */
|
||||
if (status < 0) goto fail;
|
||||
if (status < 0) {
|
||||
VGM_LOG("HCA: unsupported header found, %i\n", status);
|
||||
goto fail;
|
||||
}
|
||||
|
||||
status = clHCA_getInfo(data->handle, &data->info); /* extract header info */
|
||||
if (status < 0) goto fail;
|
||||
|
@ -117,6 +120,8 @@ void decode_hca(hca_codec_data* data, sample_t* outbuf, int32_t samples_to_do) {
|
|||
break;
|
||||
}
|
||||
|
||||
data->current_block++;
|
||||
|
||||
/* decode frame */
|
||||
status = clHCA_DecodeBlock(data->handle, (void*)(data->data_buffer), blockSize);
|
||||
if (status < 0) {
|
||||
|
@ -127,7 +132,6 @@ void decode_hca(hca_codec_data* data, sample_t* outbuf, int32_t samples_to_do) {
|
|||
/* extract samples */
|
||||
clHCA_ReadSamples16(data->handle, data->sample_buffer);
|
||||
|
||||
data->current_block++;
|
||||
data->samples_consumed = 0;
|
||||
data->samples_filled += data->info.samplesPerBlock;
|
||||
}
|
||||
|
@ -175,6 +179,13 @@ void free_hca(hca_codec_data* data) {
|
|||
}
|
||||
|
||||
|
||||
/* ************************************************************************* */
|
||||
|
||||
/* Test a single HCA key and assign an score for comparison. Multiple keys could potentially result
|
||||
* in "playable" results (mostly silent with random clips), so it also checks the resulting PCM.
|
||||
* Currently wrong keys should be detected during decoding+un-xor test, so this score may not
|
||||
* be needed anymore, but keep around in case CRI breaks those tests in the future. */
|
||||
|
||||
/* arbitrary scale to simplify score comparisons */
|
||||
#define HCA_KEY_SCORE_SCALE 10
|
||||
/* ignores beginning frames (~10 is not uncommon, Dragalia Lost vocal layers have lots) */
|
||||
|
@ -211,7 +222,8 @@ int test_hca_key(hca_codec_data* data, unsigned long long keycode) {
|
|||
/* read and test frame */
|
||||
bytes = read_streamfile(data->data_buffer, offset, blockSize, data->streamfile);
|
||||
if (bytes != blockSize) {
|
||||
total_score = -1;
|
||||
/* normally this shouldn't happen, but pre-fetch ACB stop with frames in half, so just keep score */
|
||||
//total_score = -1;
|
||||
break;
|
||||
}
|
||||
|
||||
|
|
|
@ -13,24 +13,27 @@ struct ogg_vorbis_codec_data {
|
|||
int ovf_init;
|
||||
int bitstream;
|
||||
int disable_reordering; /* Xiph Ogg must reorder channels on output, but some pre-ordered games don't need it */
|
||||
int force_seek; /* Ogg with wrong granules can't seek correctly */
|
||||
|
||||
int32_t discard;
|
||||
|
||||
ogg_vorbis_io io;
|
||||
vorbis_comment *comment;
|
||||
vorbis_comment* comment;
|
||||
int comment_number;
|
||||
vorbis_info *info;
|
||||
vorbis_info* info;
|
||||
};
|
||||
|
||||
|
||||
static void pcm_convert_float_to_16(int channels, sample_t *outbuf, int samples_to_do, float **pcm, int disable_ordering);
|
||||
static void pcm_convert_float_to_16(int channels, sample_t* outbuf, int start_sample, int samples_to_do, float** pcm, int disable_ordering);
|
||||
|
||||
static size_t ov_read_func(void *ptr, size_t size, size_t nmemb, void *datasource);
|
||||
static int ov_seek_func(void *datasource, ogg_int64_t offset, int whence);
|
||||
static long ov_tell_func(void *datasource);
|
||||
static int ov_close_func(void *datasource);
|
||||
static size_t ov_read_func(void* ptr, size_t size, size_t nmemb, void* datasource);
|
||||
static int ov_seek_func(void* datasource, ogg_int64_t offset, int whence);
|
||||
static long ov_tell_func(void* datasource);
|
||||
static int ov_close_func(void* datasource);
|
||||
|
||||
|
||||
ogg_vorbis_codec_data* init_ogg_vorbis(STREAMFILE *sf, off_t start, off_t size, ogg_vorbis_io *io) {
|
||||
ogg_vorbis_codec_data *data = NULL;
|
||||
ogg_vorbis_codec_data* init_ogg_vorbis(STREAMFILE* sf, off_t start, off_t size, ogg_vorbis_io* io) {
|
||||
ogg_vorbis_codec_data* data = NULL;
|
||||
ov_callbacks callbacks = {0};
|
||||
|
||||
//todo clean up
|
||||
|
@ -105,7 +108,7 @@ fail:
|
|||
return NULL;
|
||||
}
|
||||
|
||||
static size_t ov_read_func(void *ptr, size_t size, size_t nmemb, void *datasource) {
|
||||
static size_t ov_read_func(void* ptr, size_t size, size_t nmemb, void* datasource) {
|
||||
ogg_vorbis_io *io = datasource;
|
||||
size_t bytes_read, items_read;
|
||||
|
||||
|
@ -129,8 +132,8 @@ static size_t ov_read_func(void *ptr, size_t size, size_t nmemb, void *datasourc
|
|||
return items_read;
|
||||
}
|
||||
|
||||
static int ov_seek_func(void *datasource, ogg_int64_t offset, int whence) {
|
||||
ogg_vorbis_io *io = datasource;
|
||||
static int ov_seek_func(void* datasource, ogg_int64_t offset, int whence) {
|
||||
ogg_vorbis_io* io = datasource;
|
||||
ogg_int64_t base_offset, new_offset;
|
||||
|
||||
switch (whence) {
|
||||
|
@ -148,7 +151,6 @@ static int ov_seek_func(void *datasource, ogg_int64_t offset, int whence) {
|
|||
break;
|
||||
}
|
||||
|
||||
|
||||
new_offset = base_offset + offset;
|
||||
if (new_offset < 0 || new_offset > io->size) {
|
||||
return -1; /* *must* return -1 if stream is unseekable */
|
||||
|
@ -158,12 +160,12 @@ static int ov_seek_func(void *datasource, ogg_int64_t offset, int whence) {
|
|||
}
|
||||
}
|
||||
|
||||
static long ov_tell_func(void *datasource) {
|
||||
ogg_vorbis_io *io = datasource;
|
||||
static long ov_tell_func(void* datasource) {
|
||||
ogg_vorbis_io* io = datasource;
|
||||
return io->offset;
|
||||
}
|
||||
|
||||
static int ov_close_func(void *datasource) {
|
||||
static int ov_close_func(void* datasource) {
|
||||
/* needed as setting ov_close_func in ov_callbacks to NULL doesn't seem to work
|
||||
* (closing the streamfile is done in free_ogg_vorbis) */
|
||||
return 0;
|
||||
|
@ -171,10 +173,10 @@ static int ov_close_func(void *datasource) {
|
|||
|
||||
/* ********************************************** */
|
||||
|
||||
void decode_ogg_vorbis(ogg_vorbis_codec_data *data, sample_t *outbuf, int32_t samples_to_do, int channels) {
|
||||
void decode_ogg_vorbis(ogg_vorbis_codec_data* data, sample_t* outbuf, int32_t samples_to_do, int channels) {
|
||||
int samples_done = 0;
|
||||
long rc;
|
||||
float **pcm_channels; /* pointer to Xiph's double array buffer */
|
||||
long start, rc;
|
||||
float** pcm_channels; /* pointer to Xiph's double array buffer */
|
||||
|
||||
while (samples_done < samples_to_do) {
|
||||
rc = ov_read_float(
|
||||
|
@ -184,10 +186,23 @@ void decode_ogg_vorbis(ogg_vorbis_codec_data *data, sample_t *outbuf, int32_t sa
|
|||
&data->bitstream); /* bitstream */
|
||||
if (rc <= 0) goto fail; /* rc is samples done */
|
||||
|
||||
pcm_convert_float_to_16(channels, outbuf, rc, pcm_channels, data->disable_reordering);
|
||||
if (data->discard) {
|
||||
start = data->discard;
|
||||
if (start > rc)
|
||||
start = rc;
|
||||
|
||||
outbuf += rc * channels;
|
||||
samples_done += rc;
|
||||
data->discard -= start;
|
||||
if (start == rc) /* consume all */
|
||||
continue;
|
||||
}
|
||||
else {
|
||||
start = 0;
|
||||
}
|
||||
|
||||
pcm_convert_float_to_16(channels, outbuf, start, rc, pcm_channels, data->disable_reordering);
|
||||
|
||||
outbuf += (rc - start) * channels;
|
||||
samples_done += (rc - start);
|
||||
|
||||
|
||||
#if 0 // alt decoding
|
||||
|
@ -217,8 +232,8 @@ fail:
|
|||
}
|
||||
|
||||
/* vorbis encodes channels in non-standard order, so we remap during conversion to fix this oddity.
|
||||
* (feels a bit weird as one would think you could leave as-is and set the player's output
|
||||
* order, but that isn't possible and remapping is what FFmpeg and every other plugin does). */
|
||||
* (feels a bit weird as one would think you could leave as-is and set the player's output order,
|
||||
* but that isn't possible and remapping like this is what FFmpeg and every other plugin does). */
|
||||
static const int xiph_channel_map[8][8] = {
|
||||
{ 0 }, /* 1ch: FC > same */
|
||||
{ 0, 1 }, /* 2ch: FL FR > same */
|
||||
|
@ -231,7 +246,7 @@ static const int xiph_channel_map[8][8] = {
|
|||
};
|
||||
|
||||
/* converts from internal Vorbis format to standard PCM and remaps (mostly from Xiph's decoder_example.c) */
|
||||
static void pcm_convert_float_to_16(int channels, sample_t * outbuf, int samples_to_do, float ** pcm, int disable_ordering) {
|
||||
static void pcm_convert_float_to_16(int channels, sample_t* outbuf, int start_sample, int samples_to_do, float** pcm, int disable_ordering) {
|
||||
int ch, s, ch_map;
|
||||
sample_t *ptr;
|
||||
float *channel;
|
||||
|
@ -244,7 +259,7 @@ static void pcm_convert_float_to_16(int channels, sample_t * outbuf, int samples
|
|||
(channels > 8) ? ch : xiph_channel_map[channels - 1][ch]; /* put Vorbis' ch to other outbuf's ch */
|
||||
ptr = outbuf + ch;
|
||||
channel = pcm[ch_map];
|
||||
for (s = 0; s < samples_to_do; s++) {
|
||||
for (s = start_sample; s < samples_to_do; s++) {
|
||||
int val = (int)floor(channel[s] * 32767.0f + 0.5f); /* use floorf? doesn't seem any faster */
|
||||
if (val > 32767) val = 32767;
|
||||
else if (val < -32768) val = -32768;
|
||||
|
@ -257,23 +272,34 @@ static void pcm_convert_float_to_16(int channels, sample_t * outbuf, int samples
|
|||
|
||||
/* ********************************************** */
|
||||
|
||||
void reset_ogg_vorbis(VGMSTREAM *vgmstream) {
|
||||
ogg_vorbis_codec_data *data = vgmstream->codec_data;
|
||||
void reset_ogg_vorbis(ogg_vorbis_codec_data* data) {
|
||||
if (!data) return;
|
||||
|
||||
/* this seek cleans internal buffers */
|
||||
ov_pcm_seek(&data->ogg_vorbis_file, 0);
|
||||
/* this raw seek cleans internal buffers, and it's preferable to
|
||||
* ov_pcm_seek as doesn't get confused by wrong granules */
|
||||
ov_raw_seek(&data->ogg_vorbis_file, 0);
|
||||
//;VGM_ASSERT(res != 0, "OGG: bad reset=%i\n", res);
|
||||
|
||||
data->discard = 0;
|
||||
}
|
||||
|
||||
void seek_ogg_vorbis(ogg_vorbis_codec_data *data, int32_t num_sample) {
|
||||
void seek_ogg_vorbis(ogg_vorbis_codec_data* data, int32_t num_sample) {
|
||||
if (!data) return;
|
||||
|
||||
/* this seek crosslaps to avoid possible clicks, so seeking to 0 will
|
||||
* decode a bit differently than ov_pcm_seek */
|
||||
/* special seek for games with bad granule positions (since ov_*_seek uses granules to seek) */
|
||||
if (data->force_seek) {
|
||||
reset_ogg_vorbis(data);
|
||||
data->discard = num_sample;
|
||||
return;
|
||||
}
|
||||
|
||||
/* this seek crosslaps to avoid possible clicks, so seeking to 0 + discarding
|
||||
* will decode a bit differently than ov_pcm_seek */
|
||||
ov_pcm_seek_lap(&data->ogg_vorbis_file, num_sample);
|
||||
//VGM_ASSERT(res != 0, "OGG: bad seek=%i\n", res); /* not seen, in theory could give error */
|
||||
}
|
||||
|
||||
void free_ogg_vorbis(ogg_vorbis_codec_data *data) {
|
||||
void free_ogg_vorbis(ogg_vorbis_codec_data* data) {
|
||||
if (!data) return;
|
||||
|
||||
if (data->ovf_init)
|
||||
|
@ -285,7 +311,7 @@ void free_ogg_vorbis(ogg_vorbis_codec_data *data) {
|
|||
|
||||
/* ********************************************** */
|
||||
|
||||
int ogg_vorbis_get_comment(ogg_vorbis_codec_data *data, const char **comment) {
|
||||
int ogg_vorbis_get_comment(ogg_vorbis_codec_data* data, const char** comment) {
|
||||
if (!data) return 0;
|
||||
|
||||
/* dumb reset */
|
||||
|
@ -302,7 +328,7 @@ int ogg_vorbis_get_comment(ogg_vorbis_codec_data *data, const char **comment) {
|
|||
return 1;
|
||||
}
|
||||
|
||||
void ogg_vorbis_get_info(ogg_vorbis_codec_data *data, int *p_channels, int *p_sample_rate) {
|
||||
void ogg_vorbis_get_info(ogg_vorbis_codec_data* data, int* p_channels, int* p_sample_rate) {
|
||||
if (!data) {
|
||||
if (p_channels) *p_channels = 0;
|
||||
if (p_sample_rate) *p_sample_rate = 0;
|
||||
|
@ -313,7 +339,7 @@ void ogg_vorbis_get_info(ogg_vorbis_codec_data *data, int *p_channels, int *p_sa
|
|||
if (p_sample_rate) *p_sample_rate = (int)data->info->rate;
|
||||
}
|
||||
|
||||
void ogg_vorbis_get_samples(ogg_vorbis_codec_data *data, int *p_samples) {
|
||||
void ogg_vorbis_get_samples(ogg_vorbis_codec_data* data, int* p_samples) {
|
||||
if (!data) {
|
||||
if (p_samples) *p_samples = 0;
|
||||
return;
|
||||
|
@ -322,13 +348,19 @@ void ogg_vorbis_get_samples(ogg_vorbis_codec_data *data, int *p_samples) {
|
|||
if (p_samples) *p_samples = ov_pcm_total(&data->ogg_vorbis_file,-1);
|
||||
}
|
||||
|
||||
void ogg_vorbis_set_disable_reordering(ogg_vorbis_codec_data *data, int set) {
|
||||
void ogg_vorbis_set_disable_reordering(ogg_vorbis_codec_data* data, int set) {
|
||||
if (!data) return;
|
||||
|
||||
data->disable_reordering = set;
|
||||
}
|
||||
|
||||
STREAMFILE* ogg_vorbis_get_streamfile(ogg_vorbis_codec_data *data) {
|
||||
void ogg_vorbis_set_force_seek(ogg_vorbis_codec_data* data, int set) {
|
||||
if (!data) return;
|
||||
|
||||
data->force_seek = set;
|
||||
}
|
||||
|
||||
STREAMFILE* ogg_vorbis_get_streamfile(ogg_vorbis_codec_data* data) {
|
||||
if (!data) return NULL;
|
||||
return data->io.streamfile;
|
||||
}
|
||||
|
|
|
@ -13,8 +13,8 @@
|
|||
* - https://github.com/PCSX2/pcsx2/blob/master/pcsx2/VUops.cpp
|
||||
*
|
||||
* Codec has no apparent name, but most functions mention "St" (stream?) and "Sac" (sound audio
|
||||
* container?) and handler lib may be "Csd". Looks inspired by MPEG (much simplified) with bits
|
||||
* from other codecs (per-file codebook and 1024 samples).
|
||||
* compression?) and main lib is called "Sac" (Sac.cpp/Sac.dsm), also set in a "Csd" ELF. Looks inspired
|
||||
* by MPEG (much simplified) with bits from other codecs (per-file codebook and 1024 samples).
|
||||
*
|
||||
* Original decoder is mainly implemented in the PS2's VU1, a coprocessor specialized in vector/SIMD
|
||||
* and parallel instructions. As VU1 works with many 128 bit registers (typically x4 floats) algorithm
|
||||
|
@ -134,7 +134,7 @@ static inline int16_t clamp_s16(int16_t value, int16_t min, int16_t max) {
|
|||
|
||||
|
||||
/* converts 4 huffman codes to 4 spectrums coefs */
|
||||
//SUB_1188
|
||||
//SUB_1188 (Pass1_Start?)
|
||||
static void unpack_code4(REG_VF* spectrum, const REG_VF* spc1, const REG_VF* spc2, const REG_VF* code, const REG_VF* idx, int out_pos) {
|
||||
const REG_VF* ST = SCALE_TABLE;
|
||||
REG_VF tbc1, tbc2, out;
|
||||
|
@ -282,8 +282,8 @@ static void transform_dot_product(REG_VF* mac, const REG_VF* spectrum, const REG
|
|||
MADD (_xyzw, mac, &spectrum[pos_i+7], &TT[pos_t+7]);
|
||||
}
|
||||
|
||||
/* take spectrum coefs and, ahem, transform somehow, possibly using a SIMD'd FFT/DCT table. */
|
||||
//SUB_1410
|
||||
/* take spectrum coefs and, ahem, transform somehow, possibly using a SIMD'd DCT table. */
|
||||
//SUB_1410 (Idct_Start?)
|
||||
static void transform(REG_VF* wave, const REG_VF* spectrum) {
|
||||
const REG_VF* TT = TRANSFORM_TABLE;
|
||||
int i, j;
|
||||
|
@ -330,7 +330,7 @@ static void transform(REG_VF* wave, const REG_VF* spectrum) {
|
|||
|
||||
|
||||
/* process and apply window/overlap. Similar to MP3's synth granule function. */
|
||||
//SUB_1690
|
||||
//SUB_1690 (Pass3_Start?)
|
||||
static void process(REG_VF* wave, REG_VF* hist) {
|
||||
const REG_VF* ST = SYNTH_TABLE;
|
||||
int i, j;
|
||||
|
@ -918,7 +918,7 @@ static void finalize_output(tac_handle_t* h) {
|
|||
REG_VF* wave_l = h->wave[0];
|
||||
REG_VF* wave_r = h->wave[1];
|
||||
|
||||
/* Combine joint stereo channels that encode diffs in L/R. In pseudo-mono files R has */
|
||||
/* Combine joint stereo channels that encode diffs in L/R ("MS stereo"). In pseudo-mono files R has */
|
||||
/* all samples as 0 (R only saves 28 huffman codes, signalling no coefs per 1+27 bands) */
|
||||
for (i = 0; i < TAC_TOTAL_POINTS * 8; i++) {
|
||||
REG_VF samples_l, samples_r;
|
||||
|
|
|
@ -31,7 +31,7 @@ typedef struct {
|
|||
uint16_t frame_last; /* valid samples in final frame - 1 (lower = outputs less, 0 = outputs 1), even for non-looped files */
|
||||
uint32_t loop_offset; /* points to a block; file size if not looped */
|
||||
uint32_t file_size; /* block aligned; actual file size can be a bit smaller if last block is truncated */
|
||||
uint32_t joint_stereo; /* usually 0 and rarely 1 */
|
||||
uint32_t joint_stereo; /* usually 0 and rarely 1 ("MSStereoMode") */
|
||||
uint32_t empty; /* always null */
|
||||
} tac_header_t;
|
||||
|
||||
|
|
|
@ -71,7 +71,7 @@ static const REG_VF ANTIALIASING_TABLE[64/4] = {
|
|||
|
||||
/* Seemingly scale factor table (needs an extra index, see end).
|
||||
* ELF seems to store only xy, zw is just mirrored. */
|
||||
static const REG_VF SCALE_TABLE[2048/4 + 4/4] = {
|
||||
static const REG_VF SCALE_TABLE[2048/4 + 4/4] = { //SacPass1TableVU?
|
||||
/* part 1 */
|
||||
{ .i = {0x00000000,0x3F7F8000,0x00000000,0x3F7F8000} }, //+0.000000000f, +0.998046875f, +0.000000000f, +0.998046875f
|
||||
{ .i = {0x3A000000,0x3F5744FD,0x3A000000,0x3F5744FD} }, //+0.000488281f, +0.840896428f, +0.000488281f, +0.840896428f
|
||||
|
@ -592,8 +592,8 @@ static const REG_VF SCALE_TABLE[2048/4 + 4/4] = {
|
|||
{ .i = {0x00000000,0x00000000,0x00000000,0x00000000} }, //+0.000000000f, +0.000000000f, +0.000000000f, +0.000000000f
|
||||
};
|
||||
|
||||
/* FFT?/DCT? table (Hanning window?) */
|
||||
static const REG_VF TRANSFORM_TABLE[1024/4] = {
|
||||
/* IDCT? table (Hanning window?) */
|
||||
static const REG_VF TRANSFORM_TABLE[1024/4] = { //SacIdctTableVU?
|
||||
{ .i = {0x3F3504F3,0x3F7FB10F,0x3F7EC46D,0x3F7D3AAC} }, //+0.707106769f, +0.998795450f, +0.995184720f, +0.989176512f
|
||||
{ .i = {0x3F7B14BE,0x3F7853F8,0x3F74FA0B,0x3F710908} }, //+0.980785251f, +0.970031261f, +0.956940353f, +0.941544056f
|
||||
{ .i = {0x3F6C835E,0x3F676BD8,0x3F61C597,0x3F5B941A} }, //+0.923879504f, +0.903989315f, +0.881921232f, +0.857728601f
|
||||
|
@ -854,7 +854,7 @@ static const REG_VF TRANSFORM_TABLE[1024/4] = {
|
|||
|
||||
/* standard MPEG1 filter bank (synth_window)
|
||||
* Seems divided into 2 parts, or at least loaded to VU1 memory in 2 steps */
|
||||
static const REG_VF WINDOW_TABLE[512/4] = {
|
||||
static const REG_VF WINDOW_TABLE[512/4] = { //SacPass3TableVU?
|
||||
/* part 1 */
|
||||
{ .i = {0x00000000,0xB7800074,0xB7800074,0xB7800074} }, //+0.000000000f, -0.000015259f, -0.000015259f, -0.000015259f
|
||||
{ .i = {0xB7800074,0xB7800074,0xB7800074,0xB8000074} }, //-0.000015259f, -0.000015259f, -0.000015259f, -0.000030518f
|
||||
|
|
|
@ -219,7 +219,7 @@ void reset_codec(VGMSTREAM* vgmstream) {
|
|||
|
||||
#ifdef VGM_USE_VORBIS
|
||||
if (vgmstream->coding_type == coding_OGG_VORBIS) {
|
||||
reset_ogg_vorbis(vgmstream);
|
||||
reset_ogg_vorbis(vgmstream->codec_data);
|
||||
}
|
||||
|
||||
if (vgmstream->coding_type == coding_VORBIS_custom) {
|
||||
|
|
|
@ -83,6 +83,7 @@ static const char* extension_list[] = {
|
|||
"atsl4",
|
||||
"atx",
|
||||
"aud",
|
||||
"audio", //txth/reserved [Grimm Echoes (Android)]
|
||||
"aus",
|
||||
"awb",
|
||||
"awc",
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
#ifdef VGM_USE_FFMPEG
|
||||
|
||||
static int read_pos_file(uint8_t* buf, size_t bufsize, STREAMFILE* sf);
|
||||
static int find_ogg_loops(ffmpeg_codec_data* data, int32_t* p_loop_start, int32_t* p_loop_end);
|
||||
static int find_meta_loops(ffmpeg_codec_data* data, int32_t* p_loop_start, int32_t* p_loop_end);
|
||||
|
||||
/* parses any format supported by FFmpeg and not handled elsewhere:
|
||||
* - MP3 (.mp3, .mus): Marc Ecko's Getting Up (PC)
|
||||
|
@ -58,9 +58,9 @@ VGMSTREAM* init_vgmstream_ffmpeg(STREAMFILE* sf) {
|
|||
}
|
||||
}
|
||||
|
||||
/* try to read Ogg loop tags (abridged) */
|
||||
if (loop_flag == 0 && read_u32be(0x00, sf) == 0x4F676753) { /* "OggS" */
|
||||
loop_flag = find_ogg_loops(data, &loop_start, &loop_end);
|
||||
/* try to read Ogg/Flac loop tags (abridged) */
|
||||
if (!loop_flag && (is_id32be(0x00, sf, "OggS") || is_id32be(0x00, sf, "fLaC"))) {
|
||||
loop_flag = find_meta_loops(data, &loop_start, &loop_end);
|
||||
}
|
||||
|
||||
/* hack for AAC files (will return 0 samples if not an actual file) */
|
||||
|
@ -155,22 +155,30 @@ fail:
|
|||
}
|
||||
|
||||
|
||||
/* loop tag handling could be unified with ogg_vorbis.c, but that one has a extra features too */
|
||||
static int find_ogg_loops(ffmpeg_codec_data* data, int32_t* p_loop_start, int32_t* p_loop_end) {
|
||||
/* loop tag handling could be unified with ogg_vorbis.c, but that one has a extra features too.
|
||||
* Also has support for flac meta loops, that can be used by stuff like Platformer Game Engine
|
||||
* or ZDoom/Duke Nukem 3D source ports (maybe RPG Maker too). */
|
||||
//todo call ffmpeg_get_next_tag and iterate like ogg_vorbis.c
|
||||
//todo also save title
|
||||
static int find_meta_loops(ffmpeg_codec_data* data, int32_t* p_loop_start, int32_t* p_loop_end) {
|
||||
char* endptr;
|
||||
const char* value;
|
||||
int loop_flag = 0;
|
||||
int32_t loop_start = -1, loop_end = -1;
|
||||
|
||||
// Try to detect the loop flags based on current file metadata
|
||||
// Try to detect the loop flags based on current file metadata (ignores case)
|
||||
value = ffmpeg_get_metadata_value(data, "LoopStart");
|
||||
if (value != NULL) {
|
||||
if (!value)
|
||||
value = ffmpeg_get_metadata_value(data, "LOOP_START"); /* ZDoom/DN3D */
|
||||
if (value) {
|
||||
loop_start = strtol(value, &endptr, 10);
|
||||
loop_flag = 1;
|
||||
}
|
||||
|
||||
value = ffmpeg_get_metadata_value(data, "LoopEnd");
|
||||
if (value != NULL) {
|
||||
if (!value)
|
||||
value = ffmpeg_get_metadata_value(data, "LOOP_END"); /* ZDoom/DN3D */
|
||||
if (value) {
|
||||
loop_end = strtol(value, &endptr, 10);
|
||||
loop_flag = 1;
|
||||
}
|
||||
|
|
|
@ -11,6 +11,7 @@ typedef struct {
|
|||
int flags;
|
||||
|
||||
int channels;
|
||||
int layers;
|
||||
int sample_rate;
|
||||
int32_t num_samples;
|
||||
int32_t loop_start;
|
||||
|
@ -33,8 +34,7 @@ typedef struct {
|
|||
|
||||
/* ********************************************************************************** */
|
||||
|
||||
static layered_layout_data* build_layered_fsb5_celt(STREAMFILE* sf, fsb5_header* fsb5);
|
||||
static layered_layout_data* build_layered_fsb5_atrac9(STREAMFILE* sf, fsb5_header* fsb5, off_t configs_offset, size_t configs_size);
|
||||
static layered_layout_data* build_layered_fsb5(STREAMFILE* sf, fsb5_header* fsb5);
|
||||
|
||||
/* FSB5 - Firelight's FMOD Studio SoundBank format */
|
||||
VGMSTREAM* init_vgmstream_fsb5(STREAMFILE* sf) {
|
||||
|
@ -50,23 +50,24 @@ VGMSTREAM* init_vgmstream_fsb5(STREAMFILE* sf) {
|
|||
if (!check_extensions(sf,"fsb,snd"))
|
||||
goto fail;
|
||||
|
||||
if (read_32bitBE(0x00,sf) != 0x46534235) /* "FSB5" */
|
||||
if (!is_id32be(0x00,sf, "FSB5"))
|
||||
goto fail;
|
||||
|
||||
/* 0x00 is rare (seen in Tales from Space Vita) */
|
||||
fsb5.version = read_32bitLE(0x04,sf);
|
||||
if (fsb5.version != 0x00 && fsb5.version != 0x01) goto fail;
|
||||
fsb5.version = read_u32le(0x04,sf);
|
||||
if (fsb5.version != 0x00 && fsb5.version != 0x01)
|
||||
goto fail;
|
||||
|
||||
fsb5.total_subsongs = read_32bitLE(0x08,sf);
|
||||
fsb5.sample_header_size = read_32bitLE(0x0C,sf);
|
||||
fsb5.name_table_size = read_32bitLE(0x10,sf);
|
||||
fsb5.sample_data_size = read_32bitLE(0x14,sf);
|
||||
fsb5.codec = read_32bitLE(0x18,sf);
|
||||
fsb5.total_subsongs = read_u32le(0x08,sf);
|
||||
fsb5.sample_header_size = read_u32le(0x0C,sf);
|
||||
fsb5.name_table_size = read_u32le(0x10,sf);
|
||||
fsb5.sample_data_size = read_u32le(0x14,sf);
|
||||
fsb5.codec = read_u32le(0x18,sf);
|
||||
/* version 0x01 - 0x1c(4): zero, 0x24(16): hash, 0x34(8): unk
|
||||
* version 0x00 has an extra field (always 0?) at 0x1c */
|
||||
if (fsb5.version == 0x01) {
|
||||
/* found by tests and assumed to be flags, no games known */
|
||||
fsb5.flags = read_32bitLE(0x20,sf);
|
||||
fsb5.flags = read_u32le(0x20,sf);
|
||||
}
|
||||
fsb5.base_header_size = (fsb5.version==0x00) ? 0x40 : 0x3C;
|
||||
|
||||
|
@ -87,8 +88,8 @@ VGMSTREAM* init_vgmstream_fsb5(STREAMFILE* sf) {
|
|||
off_t data_offset = 0;
|
||||
uint32_t sample_mode1, sample_mode2; /* maybe one uint64? */
|
||||
|
||||
sample_mode1 = (uint32_t)read_32bitLE(fsb5.sample_header_offset+0x00,sf);
|
||||
sample_mode2 = (uint32_t)read_32bitLE(fsb5.sample_header_offset+0x04,sf);
|
||||
sample_mode1 = read_u32le(fsb5.sample_header_offset+0x00,sf);
|
||||
sample_mode2 = read_u32le(fsb5.sample_header_offset+0x04,sf);
|
||||
stream_header_size += 0x08;
|
||||
|
||||
/* get samples */
|
||||
|
@ -133,7 +134,7 @@ VGMSTREAM* init_vgmstream_fsb5(STREAMFILE* sf) {
|
|||
uint32_t extraflag, extraflag_type, extraflag_size, extraflag_end;
|
||||
|
||||
do {
|
||||
extraflag = read_32bitLE(extraflag_offset,sf);
|
||||
extraflag = read_u32le(extraflag_offset,sf);
|
||||
extraflag_type = (extraflag >> 25) & 0x7F; /* bits 32..26 (7) */
|
||||
extraflag_size = (extraflag >> 1) & 0xFFFFFF; /* bits 25..1 (24)*/
|
||||
extraflag_end = (extraflag & 0x01); /* bit 0 (1) */
|
||||
|
@ -142,15 +143,15 @@ VGMSTREAM* init_vgmstream_fsb5(STREAMFILE* sf) {
|
|||
if (i + 1 == target_subsong) {
|
||||
switch(extraflag_type) {
|
||||
case 0x01: /* channels */
|
||||
fsb5.channels = read_8bit(extraflag_offset+0x04,sf);
|
||||
fsb5.channels = read_u8(extraflag_offset+0x04,sf);
|
||||
break;
|
||||
case 0x02: /* sample rate */
|
||||
fsb5.sample_rate = read_32bitLE(extraflag_offset+0x04,sf);
|
||||
fsb5.sample_rate = read_s32le(extraflag_offset+0x04,sf);
|
||||
break;
|
||||
case 0x03: /* loop info */
|
||||
fsb5.loop_start = read_32bitLE(extraflag_offset+0x04,sf);
|
||||
fsb5.loop_start = read_s32le(extraflag_offset+0x04,sf);
|
||||
if (extraflag_size > 0x04) { /* probably not needed */
|
||||
fsb5.loop_end = read_32bitLE(extraflag_offset+0x08,sf);
|
||||
fsb5.loop_end = read_s32le(extraflag_offset+0x08,sf);
|
||||
fsb5.loop_end += 1; /* correct compared to FMOD's tools */
|
||||
}
|
||||
//;VGM_LOG("FSB5: stream %i loop start=%i, loop end=%i, samples=%i\n", i, fsb5.loop_start, fsb5.loop_end, fsb5.num_samples);
|
||||
|
@ -183,7 +184,7 @@ VGMSTREAM* init_vgmstream_fsb5(STREAMFILE* sf) {
|
|||
case 0x05: /* unknown 32b */
|
||||
/* rare, found in Tearaway (Vita) with value 0 in first stream and
|
||||
* Shantae and the Seven Sirens (Mobile) with value 0x0003bd72 BE in #44 (Arena Town) */
|
||||
VGM_LOG("FSB5: stream %i flag %x with value %08x\n", i, extraflag_type, read_32bitLE(extraflag_offset+0x04,sf));
|
||||
VGM_LOG("FSB5: stream %i flag %x with value %08x\n", i, extraflag_type, read_u32le(extraflag_offset+0x04,sf));
|
||||
break;
|
||||
case 0x06: /* XMA seek table */
|
||||
/* no need for it */
|
||||
|
@ -204,14 +205,17 @@ VGMSTREAM* init_vgmstream_fsb5(STREAMFILE* sf) {
|
|||
* 0x08: table_size (total_entries = seek_table_size / (4+4)), not counting this value; can be 0
|
||||
* 0x0C: sample number (only some samples are saved in the table)
|
||||
* 0x10: offset within data, pointing to a FSB vorbis block (with the 16b block size header)
|
||||
* (xN entries)
|
||||
*/
|
||||
* (xN entries) */
|
||||
break;
|
||||
case 0x0d: /* peak volume float (optional setting when making fsb) */
|
||||
break;
|
||||
case 0x0f: /* OPUS data size not counting frames headers */
|
||||
break;
|
||||
case 0x0e: /* number of layered Vorbis channels [Invisible, Inc. (Switch)] */
|
||||
case 0x0e: /* Vorbis intra-layers (multichannel FMOD ~2021) [Invisible, Inc. (Switch), Just Cause 4 (PC)] */
|
||||
fsb5.layers = read_u32le(extraflag_offset+0x04,sf);
|
||||
/* info only as decoding is standard Vorbis that handles Nch multichannel (channels is 1 here) */
|
||||
fsb5.channels = fsb5.channels * fsb5.layers;
|
||||
break;
|
||||
default:
|
||||
VGM_LOG("FSB5: stream %i unknown flag 0x%x at %x + 0x04 (size 0x%x)\n", i, extraflag_type, (uint32_t)extraflag_offset, extraflag_size);
|
||||
break;
|
||||
|
@ -240,8 +244,8 @@ VGMSTREAM* init_vgmstream_fsb5(STREAMFILE* sf) {
|
|||
else {
|
||||
off_t next_data_offset;
|
||||
uint32_t next_sample_mode1, next_sample_mode2;
|
||||
next_sample_mode1 = (uint32_t)read_32bitLE(fsb5.sample_header_offset+stream_header_size+0x00,sf);
|
||||
next_sample_mode2 = (uint32_t)read_32bitLE(fsb5.sample_header_offset+stream_header_size+0x04,sf);
|
||||
next_sample_mode1 = read_u32le(fsb5.sample_header_offset+stream_header_size+0x00,sf);
|
||||
next_sample_mode2 = read_u32le(fsb5.sample_header_offset+stream_header_size+0x04,sf);
|
||||
next_data_offset = (((next_sample_mode2 & 0x03) << 25) | ((next_sample_mode1 >> 7) & 0x1FFFFFF)) << 5;
|
||||
|
||||
fsb5.stream_size = next_data_offset - data_offset;
|
||||
|
@ -254,17 +258,18 @@ VGMSTREAM* init_vgmstream_fsb5(STREAMFILE* sf) {
|
|||
fsb5.sample_header_offset += stream_header_size;
|
||||
}
|
||||
/* target stream not found*/
|
||||
if (!fsb5.stream_offset || !fsb5.stream_size) goto fail;
|
||||
if (!fsb5.stream_offset || !fsb5.stream_size)
|
||||
goto fail;
|
||||
|
||||
/* get stream name */
|
||||
if (fsb5.name_table_size) {
|
||||
off_t name_suboffset = fsb5.base_header_size + fsb5.sample_header_size + 0x04*(target_subsong-1);
|
||||
fsb5.name_offset = fsb5.base_header_size + fsb5.sample_header_size + read_32bitLE(name_suboffset,sf);
|
||||
fsb5.name_offset = fsb5.base_header_size + fsb5.sample_header_size + read_u32le(name_suboffset,sf);
|
||||
}
|
||||
|
||||
|
||||
/* build the VGMSTREAM */
|
||||
vgmstream = allocate_vgmstream(fsb5.channels,fsb5.loop_flag);
|
||||
vgmstream = allocate_vgmstream(fsb5.channels, fsb5.loop_flag);
|
||||
if (!vgmstream) goto fail;
|
||||
|
||||
vgmstream->sample_rate = fsb5.sample_rate;
|
||||
|
@ -379,10 +384,10 @@ VGMSTREAM* init_vgmstream_fsb5(STREAMFILE* sf) {
|
|||
|
||||
#ifdef VGM_USE_CELT
|
||||
case 0x0C: { /* FMOD_SOUND_FORMAT_CELT [BIT.TRIP Presents Runner2 (PC), Full Bore (PC)] */
|
||||
int is_multistream = fsb5.channels > 2;
|
||||
fsb5.layers = (fsb5.channels <= 2) ? 1 : (fsb5.channels+1) / 2;
|
||||
|
||||
if (is_multistream) {
|
||||
vgmstream->layout_data = build_layered_fsb5_celt(sf, &fsb5);
|
||||
if (fsb5.layers > 1) {
|
||||
vgmstream->layout_data = build_layered_fsb5(sf, &fsb5);
|
||||
if (!vgmstream->layout_data) goto fail;
|
||||
vgmstream->coding_type = coding_CELT_FSB;
|
||||
vgmstream->layout_type = layout_layered;
|
||||
|
@ -399,22 +404,18 @@ VGMSTREAM* init_vgmstream_fsb5(STREAMFILE* sf) {
|
|||
|
||||
#ifdef VGM_USE_ATRAC9
|
||||
case 0x0D: {/* FMOD_SOUND_FORMAT_AT9 */
|
||||
int is_multistream;
|
||||
off_t configs_offset = fsb5.extradata_offset;
|
||||
size_t configs_size = fsb5.extradata_size;
|
||||
|
||||
|
||||
/* skip frame size in newer FSBs [Day of the Tentacle Remastered (Vita), Tearaway Unfolded (PS4)] */
|
||||
if (configs_size >= 0x08 && (uint8_t)read_8bit(configs_offset, sf) != 0xFE) { /* ATRAC9 sync */
|
||||
configs_offset += 0x04;
|
||||
configs_size -= 0x04;
|
||||
if (fsb5.extradata_size >= 0x08
|
||||
&& read_u8(fsb5.extradata_offset, sf) != 0xFE) { /* not ATRAC9 sync */
|
||||
fsb5.extradata_offset += 0x04;
|
||||
fsb5.extradata_size -= 0x04;
|
||||
}
|
||||
|
||||
is_multistream = (configs_size / 0x04) > 1;
|
||||
fsb5.layers = (fsb5.extradata_size / 0x04);
|
||||
|
||||
if (is_multistream) {
|
||||
/* multichannel made of various streams [Little Big Planet (Vita)] */
|
||||
vgmstream->layout_data = build_layered_fsb5_atrac9(sf, &fsb5, configs_offset, configs_size);
|
||||
if (fsb5.layers > 1) {
|
||||
/* multichannel made of various layers [Little Big Planet (Vita)] */
|
||||
vgmstream->layout_data = build_layered_fsb5(sf, &fsb5);
|
||||
if (!vgmstream->layout_data) goto fail;
|
||||
vgmstream->coding_type = coding_ATRAC9;
|
||||
vgmstream->layout_type = layout_layered;
|
||||
|
@ -424,7 +425,7 @@ VGMSTREAM* init_vgmstream_fsb5(STREAMFILE* sf) {
|
|||
atrac9_config cfg = {0};
|
||||
|
||||
cfg.channels = vgmstream->channels;
|
||||
cfg.config_data = read_32bitBE(configs_offset,sf);
|
||||
cfg.config_data = read_u32be(fsb5.extradata_offset,sf);
|
||||
//cfg.encoder_delay = 0x100; //todo not used? num_samples seems to count all data
|
||||
|
||||
vgmstream->codec_data = init_atrac9(&cfg);
|
||||
|
@ -441,9 +442,9 @@ VGMSTREAM* init_vgmstream_fsb5(STREAMFILE* sf) {
|
|||
uint8_t buf[0x100];
|
||||
int bytes, format, average_bps, block_align;
|
||||
|
||||
format = read_16bitBE(fsb5.extradata_offset+0x00,sf);
|
||||
block_align = (uint16_t)read_16bitBE(fsb5.extradata_offset+0x02,sf);
|
||||
average_bps = (uint32_t)read_32bitBE(fsb5.extradata_offset+0x04,sf);
|
||||
format = read_u16be(fsb5.extradata_offset+0x00,sf);
|
||||
block_align = read_u16be(fsb5.extradata_offset+0x02,sf);
|
||||
average_bps = read_u32be(fsb5.extradata_offset+0x04,sf);
|
||||
/* rest: seek entries + mini seek table? */
|
||||
/* XWMA encoder only does up to 6ch (doesn't use FSB multistreams for more) */
|
||||
|
||||
|
@ -460,15 +461,14 @@ VGMSTREAM* init_vgmstream_fsb5(STREAMFILE* sf) {
|
|||
case 0x0F: {/* FMOD_SOUND_FORMAT_VORBIS [Shantae Half Genie Hero (PC), Pokemon Go (iOS)] */
|
||||
vorbis_custom_config cfg = {0};
|
||||
|
||||
cfg.channels = vgmstream->channels;
|
||||
cfg.sample_rate = vgmstream->sample_rate;
|
||||
cfg.setup_id = read_32bitLE(fsb5.extradata_offset,sf);
|
||||
cfg.channels = fsb5.channels;
|
||||
cfg.sample_rate = fsb5.sample_rate;
|
||||
cfg.setup_id = read_u32le(fsb5.extradata_offset,sf);
|
||||
|
||||
vgmstream->layout_type = layout_none;
|
||||
vgmstream->coding_type = coding_VORBIS_custom;
|
||||
vgmstream->codec_data = init_vorbis_custom(sf, fsb5.stream_offset, VORBIS_FSB, &cfg);
|
||||
if (!vgmstream->codec_data) goto fail;
|
||||
|
||||
vgmstream->coding_type = coding_VORBIS_custom;
|
||||
vgmstream->layout_type = layout_none;
|
||||
break;
|
||||
}
|
||||
#endif
|
||||
|
@ -509,15 +509,26 @@ fail:
|
|||
}
|
||||
|
||||
|
||||
static layered_layout_data* build_layered_fsb5_celt(STREAMFILE* sf, fsb5_header* fsb5) {
|
||||
static layered_layout_data* build_layered_fsb5(STREAMFILE* sf, fsb5_header* fsb5) {
|
||||
layered_layout_data* data = NULL;
|
||||
STREAMFILE* temp_sf = NULL;
|
||||
int i, layers = (fsb5->channels+1) / 2;
|
||||
size_t interleave;
|
||||
size_t interleave, config = 0;
|
||||
int i, layer_channels;
|
||||
|
||||
if (read_32bitBE(fsb5->stream_offset+0x00,sf) != 0x17C30DF3) /* FSB CELT frame ID */
|
||||
|
||||
/* init layout */
|
||||
data = init_layout_layered(fsb5->layers);
|
||||
if (!data) goto fail;
|
||||
|
||||
for (i = 0; i < fsb5->layers; i++) {
|
||||
switch (fsb5->codec) {
|
||||
case 0x0C: { /* CELT */
|
||||
/* 2ch+2ch..+1ch or 2ch+2ch..+2ch = check last layer */
|
||||
layer_channels = (i+1 == fsb5->layers && fsb5->channels % 2 == 1) ? 1 : 2;
|
||||
|
||||
if (read_u32be(fsb5->stream_offset+0x00,sf) != 0x17C30DF3) /* FSB CELT frame ID */
|
||||
goto fail;
|
||||
interleave = 0x04+0x04+read_32bitLE(fsb5->stream_offset+0x04,sf); /* frame size */
|
||||
interleave = 0x04+0x04+read_u32le(fsb5->stream_offset+0x04,sf); /* frame size */
|
||||
|
||||
//todo unknown interleave for max quality odd channel streams (found in test files)
|
||||
/* FSB5 odd channels use 2ch+2ch...+1ch streams, and the last only goes up to 0x17a, and other
|
||||
|
@ -525,86 +536,36 @@ static layered_layout_data* build_layered_fsb5_celt(STREAMFILE* sf, fsb5_header*
|
|||
* however streams other than the last seem to be padded with 0s somehow and wont work */
|
||||
if (interleave > 0x17a && (fsb5->channels % 2 == 1))
|
||||
interleave = 0x17a;
|
||||
|
||||
|
||||
/* init layout */
|
||||
data = init_layout_layered(layers);
|
||||
if (!data) goto fail;
|
||||
|
||||
/* open each layer subfile (1/2ch CELT streams: 2ch+2ch..+1ch or 2ch+2ch..+2ch) */
|
||||
for (i = 0; i < layers; i++) {
|
||||
int layer_channels = (i+1 == layers && fsb5->channels % 2 == 1)
|
||||
? 1 : 2; /* last layer can be 1/2ch */
|
||||
|
||||
/* build the layer VGMSTREAM */
|
||||
data->layers[i] = allocate_vgmstream(layer_channels, fsb5->loop_flag);
|
||||
if (!data->layers[i]) goto fail;
|
||||
|
||||
data->layers[i]->sample_rate = fsb5->sample_rate;
|
||||
data->layers[i]->num_samples = fsb5->num_samples;
|
||||
data->layers[i]->loop_start_sample = fsb5->loop_start;
|
||||
data->layers[i]->loop_end_sample = fsb5->loop_end;
|
||||
|
||||
#ifdef VGM_USE_CELT
|
||||
data->layers[i]->codec_data = init_celt_fsb(layer_channels, CELT_0_11_0);
|
||||
if (!data->layers[i]->codec_data) goto fail;
|
||||
data->layers[i]->coding_type = coding_CELT_FSB;
|
||||
data->layers[i]->layout_type = layout_none;
|
||||
#else
|
||||
goto fail;
|
||||
#endif
|
||||
|
||||
temp_sf = setup_fsb5_streamfile(sf, fsb5->stream_offset, fsb5->stream_size, layers, i, interleave);
|
||||
if (!temp_sf) goto fail;
|
||||
|
||||
if (!vgmstream_open_stream(data->layers[i], temp_sf, 0x00))
|
||||
goto fail;
|
||||
|
||||
close_streamfile(temp_sf);
|
||||
temp_sf = NULL;
|
||||
break;
|
||||
}
|
||||
|
||||
/* setup layered VGMSTREAMs */
|
||||
if (!setup_layout_layered(data))
|
||||
goto fail;
|
||||
return data;
|
||||
|
||||
fail:
|
||||
close_streamfile(temp_sf);
|
||||
free_layout_layered(data);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
|
||||
static layered_layout_data* build_layered_fsb5_atrac9(STREAMFILE* sf, fsb5_header* fsb5, off_t configs_offset, size_t configs_size) {
|
||||
layered_layout_data* data = NULL;
|
||||
STREAMFILE* temp_sf = NULL;
|
||||
int i, layers = (configs_size / 0x04);
|
||||
size_t interleave = 0;
|
||||
|
||||
|
||||
/* init layout */
|
||||
data = init_layout_layered(layers);
|
||||
if (!data) goto fail;
|
||||
|
||||
/* open each layer subfile (2ch+2ch..+1/2ch) */
|
||||
for (i = 0; i < layers; i++) {
|
||||
uint32_t config = read_32bitBE(configs_offset + 0x04*i, sf);
|
||||
int channel_index, layer_channels;
|
||||
case 0x0D: { /* ATRAC9 */
|
||||
int channel_index;
|
||||
size_t frame_size;
|
||||
|
||||
/* 2ch+2ch..+1/2ch */
|
||||
config = read_u32be(fsb5->extradata_offset + 0x04*i, sf); /* ATRAC9 config */
|
||||
|
||||
/* parse ATRAC9 config (see VGAudio docs) */
|
||||
channel_index = ((config >> 17) & 0x7);
|
||||
frame_size = (((config >> 5) & 0x7FF) + 1) * (1 << ((config >> 3) & 0x2)); /* frame size * superframe index */
|
||||
if (channel_index > 2)
|
||||
goto fail; /* only 1/2ch expected */
|
||||
|
||||
layer_channels = (channel_index==0) ? 1 : 2;
|
||||
if (interleave == 0)
|
||||
interleave = frame_size;
|
||||
//todo in test files with 2ch+..+1ch interleave is off (uses some strange padding)
|
||||
break;
|
||||
}
|
||||
|
||||
case 0x0F: { /* VORBIS */
|
||||
layer_channels = fsb5->channels;
|
||||
interleave = 0;
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
goto fail;
|
||||
}
|
||||
|
||||
/* build the layer VGMSTREAM */
|
||||
data->layers[i] = allocate_vgmstream(layer_channels, fsb5->loop_flag);
|
||||
|
@ -615,8 +576,20 @@ static layered_layout_data* build_layered_fsb5_atrac9(STREAMFILE* sf, fsb5_heade
|
|||
data->layers[i]->loop_start_sample = fsb5->loop_start;
|
||||
data->layers[i]->loop_end_sample = fsb5->loop_end;
|
||||
|
||||
|
||||
switch (fsb5->codec) {
|
||||
#ifdef VGM_USE_CELT
|
||||
case 0x0C: { /* CELT */
|
||||
data->layers[i]->codec_data = init_celt_fsb(layer_channels, CELT_0_11_0);
|
||||
if (!data->layers[i]->codec_data) goto fail;
|
||||
data->layers[i]->coding_type = coding_CELT_FSB;
|
||||
data->layers[i]->layout_type = layout_none;
|
||||
break;
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef VGM_USE_ATRAC9
|
||||
{
|
||||
case 0x0D: { /* ATRAC9 */
|
||||
atrac9_config cfg = {0};
|
||||
|
||||
cfg.channels = layer_channels;
|
||||
|
@ -627,12 +600,16 @@ static layered_layout_data* build_layered_fsb5_atrac9(STREAMFILE* sf, fsb5_heade
|
|||
if (!data->layers[i]->codec_data) goto fail;
|
||||
data->layers[i]->coding_type = coding_ATRAC9;
|
||||
data->layers[i]->layout_type = layout_none;
|
||||
break;
|
||||
}
|
||||
#else
|
||||
goto fail;
|
||||
#endif
|
||||
|
||||
temp_sf = setup_fsb5_streamfile(sf, fsb5->stream_offset, fsb5->stream_size, layers, i, interleave);
|
||||
default:
|
||||
goto fail;
|
||||
}
|
||||
|
||||
|
||||
temp_sf = setup_fsb5_streamfile(sf, fsb5->stream_offset, fsb5->stream_size, fsb5->layers, i, interleave);
|
||||
if (!temp_sf) goto fail;
|
||||
|
||||
if (!vgmstream_open_stream(data->layers[i], temp_sf, 0x00))
|
||||
|
|
|
@ -10,11 +10,11 @@ static void find_hca_key(hca_codec_data* hca_data, uint64_t* p_keycode, uint16_t
|
|||
|
||||
|
||||
/* CRI HCA - streamed audio from CRI ADX2/Atom middleware */
|
||||
VGMSTREAM * init_vgmstream_hca(STREAMFILE* sf) {
|
||||
VGMSTREAM* init_vgmstream_hca(STREAMFILE* sf) {
|
||||
return init_vgmstream_hca_subkey(sf, 0x0000);
|
||||
}
|
||||
|
||||
VGMSTREAM * init_vgmstream_hca_subkey(STREAMFILE* sf, uint16_t subkey) {
|
||||
VGMSTREAM* init_vgmstream_hca_subkey(STREAMFILE* sf, uint16_t subkey) {
|
||||
VGMSTREAM * vgmstream = NULL;
|
||||
hca_codec_data* hca_data = NULL;
|
||||
clHCA_stInfo* hca_info;
|
||||
|
@ -122,6 +122,9 @@ fail:
|
|||
static inline void test_key(hca_codec_data* hca_data, uint64_t key, uint16_t subkey, int* best_score, uint64_t* best_keycode) {
|
||||
int score;
|
||||
|
||||
//;VGM_LOG("HCA: test key=%08x%08x, subkey=%04x\n",
|
||||
// (uint32_t)((key >> 32) & 0xFFFFFFFF), (uint32_t)(key & 0xFFFFFFFF), subkey);
|
||||
|
||||
if (subkey) {
|
||||
key = key * ( ((uint64_t)subkey << 16u) | ((uint16_t)~subkey + 2u) );
|
||||
}
|
||||
|
@ -135,6 +138,9 @@ static inline void test_key(hca_codec_data* hca_data, uint64_t key, uint16_t sub
|
|||
if (score < 0)
|
||||
return;
|
||||
|
||||
//;VGM_LOG("HCA: ok key=%08x%08x, subkey=%04x, score=%i\n",
|
||||
// (uint32_t)((key >> 32) & 0xFFFFFFFF), (uint32_t)(key & 0xFFFFFFFF), subkey, score);
|
||||
|
||||
/* update if something better is found */
|
||||
if (*best_score <= 0 || (score < *best_score && score > 0)) {
|
||||
*best_score = score;
|
||||
|
|
|
@ -193,6 +193,7 @@ static const hcakey_info hcakey_list[] = {
|
|||
{4988006236073}, // 000004895C56FFA9
|
||||
|
||||
// Castle & Dragon (iOS/Android)
|
||||
// Gunbit (Android)
|
||||
{20140528}, // 00000000013351F0
|
||||
|
||||
// Uta no Prince sama Shining Live (iOS/Android)
|
||||
|
@ -373,9 +374,6 @@ static const hcakey_info hcakey_list[] = {
|
|||
/* Re:Zero - Lost in Memories (Android) */
|
||||
{1611432018519751642}, // 165CF4E2138F7BDA
|
||||
|
||||
/* D4DJ Groovy Mix (Android) [base files] */
|
||||
{393410674916959300}, // 0575ACECA945A444
|
||||
|
||||
/* Toji no Miko: Kizamishi Issen no Tomoshibi (Android) */
|
||||
{62057514034227932}, // 00DC78FAEFA76ADC
|
||||
|
||||
|
@ -391,6 +389,224 @@ static const hcakey_info hcakey_list[] = {
|
|||
/* Sakura Kakumei (iOS/Android) */
|
||||
{382759}, // 000000000005D727
|
||||
|
||||
/* Uma Musume (Android) */
|
||||
{75923756697503}, // 0000450D608C479F
|
||||
|
||||
/* D4DJ Groovy Mix (Android) [base files] */
|
||||
{393410674916959300}, // 0575ACECA945A444
|
||||
/* D4DJ Groovy Mix (Android) [music_* files, per-song later mixed with subkey] */
|
||||
{0x59f449354d063308},
|
||||
{0x33848be13a2884a3},
|
||||
{0xf7e53533d82d48dd},
|
||||
{0x244a92885ab77b7c},
|
||||
{0xce0796d2a956dc5a},
|
||||
{0x73667711348f833f},
|
||||
{0x8032f83cbf0076a1},
|
||||
{0x7a708e291692abb9},
|
||||
{0x9ebb560685327081},
|
||||
{0x065c2f8500bc12c8},
|
||||
{0x73621a0d321e60c2},
|
||||
{0xfcce3164db70522d},
|
||||
{0xf4093992cadd3708},
|
||||
{0xf965a1086b3179c3},
|
||||
{0x54aaada4a1b8deef},
|
||||
{0xd4d2a706a06377ef},
|
||||
{0x0de4959221bc2675},
|
||||
{0x2ecdf66c680f3a45},
|
||||
{0x24c0b49097e9ebff},
|
||||
{0xc28331aab2612584},
|
||||
{0x6750f4d05183bc01},
|
||||
{0xda65af760e02c6ee},
|
||||
{0x217782495c8b2972},
|
||||
{0xbf7712e175c0b265},
|
||||
{0x3804d53c43293080},
|
||||
{0xd0ed8e940d8ed705},
|
||||
{0xf74cf8d4a5d008ce},
|
||||
{0xeb8aac34dc178f65},
|
||||
{0xbf5902d516db6ed5},
|
||||
{0xad071dce0c070e65},
|
||||
{0x56ecfc7ef4c65be8},
|
||||
{0xf42d31b5ecd1aec1},
|
||||
{0x0a8ee7a3a20ce822},
|
||||
{0xb9cedc6d6738d481},
|
||||
{0xdc2680acfd1b9b64},
|
||||
{0x9fbd8a172d5ba3e3},
|
||||
{0xb65d86624a857788},
|
||||
{0x8f5f05c835f7280e},
|
||||
{0x55912db4388961ac},
|
||||
{0x4ba9a9471f49b74e},
|
||||
{0x6ba36cadf1e045cf},
|
||||
{0x230c9509bbc3df0d},
|
||||
{0x7148dda3afa76439},
|
||||
{0xa6cefd4472568948},
|
||||
{0xfe31517282d40690},
|
||||
{0x0a6a15cc9722257d},
|
||||
{0x447d08ca3148599d},
|
||||
{0xb30acd0a43754e5c},
|
||||
{0xc05f8e4ea8c3e487},
|
||||
{0x8463554672bfb716},
|
||||
{0x0d40ccba5e10385a},
|
||||
{0xeeaf8d2458ccdb36},
|
||||
{0x0d80d3dcc7c75cea},
|
||||
{0x8efa09c6df3991a4},
|
||||
{0x6867cc75639ee0c3},
|
||||
{0xdf30ed86c3d00ffb},
|
||||
{0xf7e11ec9c94402f1},
|
||||
{0xdb03ecca6a0151e2},
|
||||
{0x212bbee264be5b06},
|
||||
{0x87025d78a57af15b},
|
||||
{0x6139edfb8889032d},
|
||||
{0xe67f4da6012c5d24},
|
||||
{0x05e3eba376e0b3dd},
|
||||
{0xf7edc5d72fdd6ceb},
|
||||
{0x031e072678ad18a3},
|
||||
{0x290fbc93e184af1e},
|
||||
{0xfd3ea450350d666f},
|
||||
{0x037d1452c192b1e6},
|
||||
{0x15f82c1617013c36},
|
||||
{0x5d1f3fdbbb036f8d},
|
||||
{0x5089e16d7a676ab1},
|
||||
{0x15bb78c31db0a0b6},
|
||||
{0xe4a1737fa3d34ccb},
|
||||
{0xd2cb7692d690b3a7},
|
||||
{0x1bbad843d5971358},
|
||||
{0x7ed1fa0b6ec8f9b3},
|
||||
{0x529969b7e1e9ac18},
|
||||
{0x2f3528a4b9eaa0f7},
|
||||
{0x90fefcd350bd2cb8},
|
||||
{0xee8da2806a13eecf},
|
||||
{0xcd3fb92065d9f373},
|
||||
{0x67b89634319c1d36},
|
||||
{0x114245b98dcb75bf},
|
||||
{0xaff9df030e63e5ba},
|
||||
{0x0aebfdf85aae4424},
|
||||
{0x4a4462cb0375001e},
|
||||
{0xfd9fa5bcb347c01b},
|
||||
{0x7ce69eed81f01019},
|
||||
{0xcb3d9329d40490b2},
|
||||
{0xe0b8bb03c74bb3d0},
|
||||
{0xd9a00c9bc93014a8},
|
||||
{0x84dc42f5a05f77cf},
|
||||
{0x420d4dd413053980},
|
||||
{0xff7640b46d72b337},
|
||||
{0x711ef85045b8c26e},
|
||||
{0xe553dba6592293d8},
|
||||
{0x9ebbaf63ffe9d9ef},
|
||||
{0x00e978d394512cfd},
|
||||
{0x6f9735c02faf6aae},
|
||||
{0x8258ddd6a1d0849b},
|
||||
{0xe5e83d31e64273f8},
|
||||
{0x35128087963cd5be},
|
||||
{0x9de6ace9a0e62f44},
|
||||
{0xd4c36ab962153420},
|
||||
{0xe77aa2f3c90a4e84},
|
||||
{0x9f6881f6d7a91658},
|
||||
{0x6c6c1fd51e28a1e7},
|
||||
{0x867d47a7d8376402},
|
||||
{0x79c5f00d243e3097},
|
||||
{0x8f0e96b4f71f724f},
|
||||
{0xcccb5077d978def4},
|
||||
{0x8ad213dddedc9022},
|
||||
{0x6aa0ff881da270e7},
|
||||
{0xf616642579ba5850},
|
||||
{0x5205a666f976d42f},
|
||||
{0xa139c29e97fcefb4},
|
||||
{0xdb402bd08d522f34},
|
||||
{0x2f2c0ff3ff235bd6},
|
||||
{0xa0316b536c8b7540},
|
||||
{0x260a354b925afeaf},
|
||||
{0x567d295828f1b08a},
|
||||
{0xc24049b9f7ed3105},
|
||||
{0x8815d2dffd77a71e},
|
||||
{0x2b4a83e7d54d0554},
|
||||
{0xf6b0dc07ea8ebeb7},
|
||||
{0xbb7be9c7c620f504},
|
||||
{0x7465c7c473e53a40},
|
||||
{0xa4481f97a8d4d01c},
|
||||
{0x0046fd87a21859ac},
|
||||
{0xf1db3c1d9542063a},
|
||||
{0xaba147637d52efbe},
|
||||
{0x298a0fa05c3f355f},
|
||||
{0x465e30321a4091f2},
|
||||
{0xc40c398f7e80d184},
|
||||
{0xa76262c2557be76f},
|
||||
{0xaef2954dc3657336},
|
||||
{0xa3711cc06f9b86c2},
|
||||
{0xcb60232f2f27ace5},
|
||||
{0x4c7d7c251c6bfa95},
|
||||
{0xf877dea1180b9b90},
|
||||
{0x9ce13dcb2fb389cc},
|
||||
{0xcbf4f1324081e0a6},
|
||||
{0x444dda6d55d76095},
|
||||
{0xb2bd99fa559b9062},
|
||||
{0x1ed521f6dd691255},
|
||||
{0xdfad847a86a126bb},
|
||||
{0x2a47feac8dc3ca9c},
|
||||
{0xc352bbf3d519256e},
|
||||
{0xfdec74b23d8b494b},
|
||||
{0x1dd21a1244ca12f1},
|
||||
{0xaf9d7a05b0fc3d9e},
|
||||
{0xa662be1601e49476},
|
||||
{0xd2ce91dbfc209b10},
|
||||
{0x57bdc58e4c06fc76},
|
||||
{0xe350bffcdc9cb686},
|
||||
{0xc7da8e6f0e2fe399},
|
||||
{0x984363837811b08a},
|
||||
{0xdcd2a403fb01e164},
|
||||
{0xb2770dced3cfd9a7},
|
||||
{0x0df31e26a7b036a2},
|
||||
{0x3f25fe3395b3154c},
|
||||
{0x889d47adc9595ffa},
|
||||
{0xc04264e8f34ad5c0},
|
||||
{0xc222e70e4a79f7c3},
|
||||
{0x7c7dd6d9f3761102},
|
||||
{0x904f50c5ce8ec6e4},
|
||||
{0xb7bff4fbf66be43f},
|
||||
{0x776c4aded0bca5d1},
|
||||
{0x38ad99a045dc971f},
|
||||
{0xb921c3992807dadd},
|
||||
{0x68d576c631e61265},
|
||||
{0xcede847721873fc2},
|
||||
{0x40443974a0a86b8b},
|
||||
{0x57111c24801b44a1},
|
||||
{0x6a15a9610d10d210},
|
||||
{0xb18fb83ee356fb94},
|
||||
{0x59b1257242c40109},
|
||||
{0x22ef086d7d6ce520},
|
||||
{0x76254d1ef50c004c},
|
||||
{0x7678588b0adf59df},
|
||||
{0x4fffee4065d22bec},
|
||||
{0x0dc128f2fd48bf4b},
|
||||
{0xfb647d074e53fab6},
|
||||
{0x55b7b25821375a02},
|
||||
{0x5b877af6e52af19b},
|
||||
{0xba26e58923a5da5d},
|
||||
{0x52d065d9ccdb8696},
|
||||
{0xf0c624dc0385adae},
|
||||
{0xb7a5297198a73155},
|
||||
{0xda08e9d3961c93f2},
|
||||
{0x8328668369631cc1},
|
||||
{0xb140168a47d55b92},
|
||||
{0x6699616be2c50115},
|
||||
{0xcee66d585d689851},
|
||||
{0x5771a2c76f36c898},
|
||||
{0x5e91a3790c32e2b3},
|
||||
{0xe4e11a71fe620e3a},
|
||||
{0x1bb363adcf4eb3f8},
|
||||
{0xa691936caf4d91d0},
|
||||
{0x94466db0d3c10f4b},
|
||||
{0x47f52330df2ead11},
|
||||
{0x33848be13a2884a3},
|
||||
{0xc9f159f60b065f91},
|
||||
{0xdd9ca800a7123d6f},
|
||||
{0xa090c8ebf8463d05},
|
||||
{0xa5c1adeb7919845f},
|
||||
{0x58d97e6f3d1aee86},
|
||||
{0x71b5fa3761d6726d},
|
||||
{0x1980271cfe0da9bd},
|
||||
{0x945cdb3cf1f29e52},
|
||||
{0x7f0feac6be7def5b},
|
||||
|
||||
/* Dragalia Lost (iOS/Android) */
|
||||
{2967411924141, subkeys_dgl, sizeof(subkeys_dgl) / sizeof(subkeys_dgl[0]) }, // 000002B2E7889CAD
|
||||
|
||||
|
|
|
@ -441,6 +441,7 @@ VGMSTREAM* init_vgmstream_ogg_vorbis_callbacks(STREAMFILE* sf, ov_callbacks* cal
|
|||
size_t stream_size = ovmi->stream_size ?
|
||||
ovmi->stream_size :
|
||||
get_streamfile_size(sf) - start;
|
||||
int force_seek = 0;
|
||||
int disable_reordering = ovmi->disable_reordering;
|
||||
|
||||
|
||||
|
@ -539,12 +540,20 @@ VGMSTREAM* init_vgmstream_ogg_vorbis_callbacks(STREAMFILE* sf, ov_callbacks* cal
|
|||
loop_end_found = 1;
|
||||
}
|
||||
}
|
||||
else if (strstr(comment,"LOOPMS=") == comment) { /* Sonic Robo Blast 2 */
|
||||
/* Convert from milliseconds to samples. */
|
||||
/* (x ms) * (y samples/s) / (1000 ms/s) */
|
||||
loop_start = atol(strrchr(comment,'=')+1) * sample_rate / 1000;
|
||||
else if (strstr(comment,"LOOPMS=") == comment) { /* Sonic Robo Blast 2 (PC) */
|
||||
loop_start = atol(strrchr(comment,'=')+1) * sample_rate / 1000; /* ms to samples */
|
||||
loop_flag = (loop_start >= 0);
|
||||
}
|
||||
else if (strstr(comment,"COMMENT=- loopTime ") == comment) { /* Aristear Remain (PC) */
|
||||
loop_start = atol(strrchr(comment,' ')+1) / 1000.0f * sample_rate; /* ms to samples */
|
||||
loop_flag = (loop_start >= 0);
|
||||
|
||||
/* files have all page granule positions -1 except a few close to loop. This throws off
|
||||
* libvorbis seeking (that uses granules), so we need manual fix = slower. Could be detected
|
||||
* by checking granules in the first new OggS pages (other games from same dev don't use
|
||||
* loopTime not have wrong granules though) */
|
||||
force_seek = 1;
|
||||
}
|
||||
|
||||
/* Hatsune Miku Project DIVA games, though only 'Arcade Future Tone' has >4ch files
|
||||
* ENCODER tag is common but ogg_vorbis_encode looks unique enough
|
||||
|
@ -562,10 +571,11 @@ VGMSTREAM* init_vgmstream_ogg_vorbis_callbacks(STREAMFILE* sf, ov_callbacks* cal
|
|||
}
|
||||
|
||||
ogg_vorbis_set_disable_reordering(data, disable_reordering);
|
||||
ogg_vorbis_set_force_seek(data, force_seek);
|
||||
|
||||
|
||||
/* build the VGMSTREAM */
|
||||
vgmstream = allocate_vgmstream(channels,loop_flag);
|
||||
vgmstream = allocate_vgmstream(channels, loop_flag);
|
||||
if (!vgmstream) goto fail;
|
||||
|
||||
vgmstream->codec_data = data;
|
||||
|
|
|
@ -633,6 +633,22 @@ static VGMSTREAM* init_subfile(txth_header* txth) {
|
|||
if (txth->num_samples)
|
||||
vgmstream->num_samples = txth->num_samples;
|
||||
|
||||
/* load some fields for possible calcs */
|
||||
if (!txth->channels)
|
||||
txth->channels = vgmstream->channels;
|
||||
if (!txth->sample_rate)
|
||||
txth->sample_rate = vgmstream->sample_rate;
|
||||
if (!txth->interleave)
|
||||
txth->interleave = vgmstream->interleave_block_size;
|
||||
if (!txth->interleave_last)
|
||||
txth->interleave_last = vgmstream->interleave_last_block_size;
|
||||
//if (!txth->loop_flag) //?
|
||||
// txth->loop_flag = vgmstream->loop_flag;
|
||||
/* sometimes headers set loop start but getting loop_end before subfile init is hard */
|
||||
if (!txth->loop_end_sample && txth->loop_flag)
|
||||
txth->loop_end_sample = vgmstream->num_samples;
|
||||
|
||||
/* other init */
|
||||
if (txth->loop_flag) {
|
||||
vgmstream_force_loop(vgmstream, txth->loop_flag, txth->loop_start_sample, txth->loop_end_sample);
|
||||
}
|
||||
|
@ -647,19 +663,6 @@ static VGMSTREAM* init_subfile(txth_header* txth) {
|
|||
//todo: other combos with subsongs + subfile?
|
||||
|
||||
|
||||
/* load some fields for possible calcs */
|
||||
if (!txth->channels)
|
||||
txth->channels = vgmstream->channels;
|
||||
if (!txth->sample_rate)
|
||||
txth->sample_rate = vgmstream->sample_rate;
|
||||
if (!txth->interleave)
|
||||
txth->interleave = vgmstream->interleave_block_size;
|
||||
if (!txth->interleave_last)
|
||||
txth->interleave_last = vgmstream->interleave_last_block_size;
|
||||
//if (!txth->loop_flag) //?
|
||||
// txth->loop_flag = vgmstream->loop_flag;
|
||||
|
||||
|
||||
close_streamfile(sf_sub);
|
||||
return vgmstream;
|
||||
|
||||
|
@ -786,12 +789,12 @@ static void set_body_chunk(txth_header* txth) {
|
|||
}
|
||||
}
|
||||
|
||||
static int parse_keyval(STREAMFILE* sf, txth_header* txth, const char * key, char * val);
|
||||
static int parse_num(STREAMFILE* sf, txth_header* txth, const char * val, uint32_t * out_value);
|
||||
static int parse_string(STREAMFILE* sf, txth_header* txth, const char * val, char * str);
|
||||
static int parse_coef_table(STREAMFILE* sf, txth_header* txth, const char * val, uint8_t * out_value, size_t out_size);
|
||||
static int parse_name_table(txth_header* txth, char * val);
|
||||
static int is_string(const char * val, const char * cmp);
|
||||
static int parse_keyval(STREAMFILE* sf, txth_header* txth, const char* key, char* val);
|
||||
static int parse_num(STREAMFILE* sf, txth_header* txth, const char* val, uint32_t* out_value);
|
||||
static int parse_string(STREAMFILE* sf, txth_header* txth, const char* val, char* str);
|
||||
static int parse_coef_table(STREAMFILE* sf, txth_header* txth, const char* val, uint8_t* out_value, size_t out_size);
|
||||
static int parse_name_table(txth_header* txth, char* val);
|
||||
static int is_string(const char* val, const char* cmp);
|
||||
static int get_bytes_to_samples(txth_header* txth, uint32_t bytes);
|
||||
static int get_padding_size(txth_header* txth, int discard_empty);
|
||||
|
||||
|
@ -818,9 +821,13 @@ static int parse_txth(txth_header* txth) {
|
|||
}
|
||||
|
||||
/* read lines */
|
||||
while (txt_offset < file_size) {
|
||||
{
|
||||
char line[TXT_LINE_MAX];
|
||||
char key[TXT_LINE_MAX] = {0}, val[TXT_LINE_MAX] = {0}; /* at least as big as a line to avoid overflows (I hope) */
|
||||
char key[TXT_LINE_MAX];
|
||||
char val[TXT_LINE_MAX];
|
||||
/* at least as big as a line to avoid overflows (I hope) */
|
||||
|
||||
while (txt_offset < file_size) {
|
||||
int ok, bytes_read, line_ok;
|
||||
|
||||
bytes_read = read_line(line, sizeof(line), txt_offset, txth->sf_text, &line_ok);
|
||||
|
@ -837,6 +844,7 @@ static int parse_txth(txth_header* txth) {
|
|||
if (!parse_keyval(txth->sf, txth, key, val)) /* read key/val */
|
||||
goto fail;
|
||||
}
|
||||
}
|
||||
|
||||
if (!txth->loop_flag_set)
|
||||
txth->loop_flag = txth->loop_end_sample && txth->loop_end_sample != 0xFFFFFFFF;
|
||||
|
@ -852,7 +860,7 @@ fail:
|
|||
return 0;
|
||||
}
|
||||
|
||||
static int parse_keyval(STREAMFILE* sf_, txth_header* txth, const char * key, char * val) {
|
||||
static int parse_keyval(STREAMFILE* sf_, txth_header* txth, const char* key, char* val) {
|
||||
//;VGM_LOG("TXTH: key=%s, val=%s\n", key, val);
|
||||
|
||||
/* CODEC */
|
||||
|
@ -1197,6 +1205,8 @@ static int parse_keyval(STREAMFILE* sf_, txth_header* txth, const char * key, ch
|
|||
|
||||
/* HEADER/BODY CONFIG */
|
||||
else if (is_string(key,"header_file")) {
|
||||
|
||||
/* first remove old head if needed */
|
||||
if (txth->sf_head_opened) {
|
||||
close_streamfile(txth->sf_head);
|
||||
txth->sf_head = NULL;
|
||||
|
@ -1205,7 +1215,10 @@ static int parse_keyval(STREAMFILE* sf_, txth_header* txth, const char * key, ch
|
|||
|
||||
if (is_string(val,"null")) { /* reset */
|
||||
if (!txth->streamfile_is_txth) {
|
||||
txth->sf_head = txth->sf;
|
||||
txth->sf_head = txth->sf; /* base non-txth file */
|
||||
}
|
||||
else {
|
||||
goto fail; /* .txth, nothing to fall back */
|
||||
}
|
||||
}
|
||||
else if (val[0]=='*' && val[1]=='.') { /* basename + extension */
|
||||
|
@ -1222,6 +1235,8 @@ static int parse_keyval(STREAMFILE* sf_, txth_header* txth, const char * key, ch
|
|||
}
|
||||
}
|
||||
else if (is_string(key,"body_file")) {
|
||||
|
||||
/* first remove old body if needed */
|
||||
if (txth->sf_body_opened) {
|
||||
close_streamfile(txth->sf_body);
|
||||
txth->sf_body = NULL;
|
||||
|
@ -1230,7 +1245,10 @@ static int parse_keyval(STREAMFILE* sf_, txth_header* txth, const char * key, ch
|
|||
|
||||
if (is_string(val,"null")) { /* reset */
|
||||
if (!txth->streamfile_is_txth) {
|
||||
txth->sf_body = txth->sf;
|
||||
txth->sf_body = txth->sf; /* base non-txth file */
|
||||
}
|
||||
else {
|
||||
goto fail; /* .txth, nothing to fall back */
|
||||
}
|
||||
}
|
||||
else if (val[0]=='*' && val[1]=='.') { /* basename + extension */
|
||||
|
@ -1316,10 +1334,11 @@ static int parse_keyval(STREAMFILE* sf_, txth_header* txth, const char * key, ch
|
|||
|
||||
return 1;
|
||||
fail:
|
||||
VGM_LOG("TXTH: error parsing key=%s, val=%s\n", key, val);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int is_substring(const char * val, const char * cmp, int inline_field) {
|
||||
static int is_substring(const char* val, const char* cmp, int inline_field) {
|
||||
char chr;
|
||||
int len = strlen(cmp);
|
||||
/* "val" must contain "cmp" entirely */
|
||||
|
@ -1339,7 +1358,7 @@ static int is_substring(const char * val, const char * cmp, int inline_field) {
|
|||
return len;
|
||||
}
|
||||
|
||||
static int is_string(const char * val, const char * cmp) {
|
||||
static int is_string(const char* val, const char* cmp) {
|
||||
int len = is_substring(val, cmp, 0);
|
||||
if (!len) return 0;
|
||||
|
||||
|
@ -1353,11 +1372,11 @@ static int is_string(const char * val, const char * cmp) {
|
|||
|
||||
return len;
|
||||
}
|
||||
static int is_string_field(const char * val, const char * cmp) {
|
||||
static int is_string_field(const char* val, const char* cmp) {
|
||||
return is_substring(val, cmp, 1);
|
||||
}
|
||||
|
||||
static uint16_t get_string_wchar(const char * val, int pos, int *csize) {
|
||||
static uint16_t get_string_wchar(const char* val, int pos, int* csize) {
|
||||
uint16_t wchar = 0;
|
||||
|
||||
if ((val[pos] & 0x80) && val[pos+1] != '\0') {
|
||||
|
@ -1379,10 +1398,11 @@ static uint16_t get_string_wchar(const char * val, int pos, int *csize) {
|
|||
|
||||
return wchar;
|
||||
}
|
||||
static int is_string_match(const char * text, const char * pattern) {
|
||||
int t_pos = 0, p_pos = 0;
|
||||
static int is_string_match(const char* text, const char* pattern) {
|
||||
int t_pos = 0, p_pos = 0, t_len = 0, p_len = 0;
|
||||
int p_size, t_size;
|
||||
uint16_t p_char, t_char;
|
||||
|
||||
//;VGM_LOG("TXTH: match '%s' vs '%s'\n", text,pattern);
|
||||
|
||||
/* compares 2 strings (case insensitive, to a point) allowing wildcards
|
||||
|
@ -1390,7 +1410,7 @@ static int is_string_match(const char * text, const char * pattern) {
|
|||
*
|
||||
* does some bleh UTF-8 handling, consuming dual bytes if needed (codepages set char's eighth bit).
|
||||
* as such it's slower than standard funcs, but it's not like we need it to be ultra fast.
|
||||
* */
|
||||
*/
|
||||
|
||||
while (text[t_pos] != '\0' && pattern[p_pos] != '\0') {
|
||||
//;VGM_LOG("TXTH: compare '%s' vs '%s'\n", (text+t_pos), (pattern+p_pos));
|
||||
|
@ -1402,10 +1422,28 @@ static int is_string_match(const char * text, const char * pattern) {
|
|||
|
||||
while(text[t_pos] != '\0') {
|
||||
t_char = get_string_wchar(text, t_pos, &t_size);
|
||||
//;VGM_LOG("TXTH: consume %i '%s'\n", t_size, (text+t_pos) );
|
||||
//;VGM_LOG("TXTH: consume %i '%s'\n", t_size, (text+t_pos));
|
||||
|
||||
if (t_char == p_char)
|
||||
/* break from this wildcard (AKA possible match = stop consuming) only if:
|
||||
* - rest of string has the same length (=could be a match)
|
||||
* - there are more wildcards (=would consume other parts)
|
||||
* otherwise current wildcard must keep consuming text (without this,
|
||||
* sound_1_1.adx vs *_1.adx wouldn't match since the _ would stop too early)
|
||||
*/
|
||||
if (t_char == p_char) {
|
||||
if (strchr(&pattern[p_pos], '*'))
|
||||
break;
|
||||
|
||||
if (!t_len || !p_len) { /* lazy init helpful? */
|
||||
t_len = strlen(text);
|
||||
p_len = strlen(pattern);
|
||||
}
|
||||
|
||||
//;VGM_LOG("TXTH: possible match '%s' vs '%s'\n", (text+t_pos), (pattern+p_pos));
|
||||
/* not strcmp to allow case insensitive-ness, handled below */
|
||||
if (t_len - t_pos == p_len - p_pos)
|
||||
break;
|
||||
}
|
||||
t_pos += t_size;
|
||||
}
|
||||
}
|
||||
|
@ -1415,21 +1453,24 @@ static int is_string_match(const char * text, const char * pattern) {
|
|||
p_pos++;
|
||||
t_pos += t_size;
|
||||
}
|
||||
else { /* must match 1:1 */
|
||||
else { /* must match char 1:1 */
|
||||
//;VGM_LOG("TXTH: test 1:1 '%s' vs '%s'\n", (text+t_pos), (pattern+p_pos));
|
||||
t_char = get_string_wchar(text, t_pos, &t_size);
|
||||
p_char = get_string_wchar(pattern, p_pos, &p_size);
|
||||
if (p_char != t_char)
|
||||
break;
|
||||
p_pos += p_size;
|
||||
t_pos += t_size;
|
||||
p_pos += p_size;
|
||||
}
|
||||
}
|
||||
|
||||
//;VGM_LOG("TXTH: current '%s' vs '%s'\n", (text+t_pos), (pattern+p_pos));
|
||||
//;VGM_LOG("TXTH: match '%s' vs '%s' = %s\n", text,pattern, (text[t_pos] == '\0' && pattern[p_pos] == '\0') ? "true" : "false");
|
||||
|
||||
/* either all chars consumed/matched and both pos point to null, or one didn't so string didn't match */
|
||||
return text[t_pos] == '\0' && pattern[p_pos] == '\0';
|
||||
}
|
||||
static int parse_string(STREAMFILE* sf, txth_header* txth, const char * val, char * str) {
|
||||
static int parse_string(STREAMFILE* sf, txth_header* txth, const char* val, char* str) {
|
||||
int n = 0;
|
||||
|
||||
/* read string without trailing spaces */
|
||||
|
@ -1438,7 +1479,7 @@ static int parse_string(STREAMFILE* sf, txth_header* txth, const char * val, cha
|
|||
return n;
|
||||
}
|
||||
|
||||
static int parse_coef_table(STREAMFILE* sf, txth_header* txth, const char * val, uint8_t * out_value, size_t out_size) {
|
||||
static int parse_coef_table(STREAMFILE* sf, txth_header* txth, const char* val, uint8_t* out_value, size_t out_size) {
|
||||
uint32_t byte;
|
||||
int done = 0;
|
||||
|
||||
|
@ -1466,7 +1507,7 @@ fail:
|
|||
return 0;
|
||||
}
|
||||
|
||||
static int parse_name_table(txth_header* txth, char * name_list) {
|
||||
static int parse_name_table(txth_header* txth, char* name_list) {
|
||||
STREAMFILE* nameFile = NULL;
|
||||
off_t txt_offset, file_size;
|
||||
char fullname[PATH_LIMIT];
|
||||
|
@ -1516,9 +1557,12 @@ static int parse_name_table(txth_header* txth, char * name_list) {
|
|||
txth->name_values_count = 0;
|
||||
|
||||
/* read lines and find target filename, format is (filename): value1, ... valueN */
|
||||
while (txt_offset < file_size) {
|
||||
{
|
||||
char line[TXT_LINE_MAX];
|
||||
char key[TXT_LINE_MAX] = {0}, val[TXT_LINE_MAX] = {0};
|
||||
char key[TXT_LINE_MAX];
|
||||
char val[TXT_LINE_MAX];
|
||||
|
||||
while (txt_offset < file_size) {
|
||||
int ok, bytes_read, line_ok;
|
||||
|
||||
bytes_read = read_line(line, sizeof(line), txt_offset, nameFile, &line_ok);
|
||||
|
@ -1567,6 +1611,7 @@ static int parse_name_table(txth_header* txth, char * name_list) {
|
|||
break; /* target found */
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* ignore if name is not actually found (values will return 0) */
|
||||
|
||||
|
@ -1578,7 +1623,7 @@ fail:
|
|||
}
|
||||
|
||||
|
||||
static int parse_num(STREAMFILE* sf, txth_header* txth, const char * val, uint32_t * out_value) {
|
||||
static int parse_num(STREAMFILE* sf, txth_header* txth, const char* val, uint32_t* out_value) {
|
||||
/* out_value can be these, save before modifying */
|
||||
uint32_t value_mul = txth->value_mul;
|
||||
uint32_t value_div = txth->value_div;
|
||||
|
@ -1624,8 +1669,10 @@ static int parse_num(STREAMFILE* sf, txth_header* txth, const char * val, uint32
|
|||
int hex = (val[1]=='0' && val[2]=='x');
|
||||
|
||||
/* can happen when loading .txth and not setting body/head */
|
||||
if (!sf)
|
||||
if (!sf) {
|
||||
VGM_LOG("TXTH: wrong header\n");
|
||||
goto fail;
|
||||
}
|
||||
|
||||
/* read exactly N fields in the expected format */
|
||||
if (strchr(val,':') && strchr(val,'$')) {
|
||||
|
@ -1641,8 +1688,10 @@ static int parse_num(STREAMFILE* sf, txth_header* txth, const char * val, uint32
|
|||
/* adjust offset */
|
||||
offset += txth->base_offset;
|
||||
|
||||
if (/*offset < 0 ||*/ offset > get_streamfile_size(sf))
|
||||
if (/*offset < 0 ||*/ offset > get_streamfile_size(sf)) {
|
||||
VGM_LOG("TXTH: wrong offset %x + %x\n", offset - txth->base_offset, txth->base_offset);
|
||||
goto fail;
|
||||
}
|
||||
|
||||
if (ed1 == 'B' && ed2 == 'E')
|
||||
big_endian = 1;
|
||||
|
@ -1652,11 +1701,12 @@ static int parse_num(STREAMFILE* sf, txth_header* txth, const char * val, uint32
|
|||
if (subsong_spacing)
|
||||
offset = offset + subsong_spacing * (txth->target_subsong - 1);
|
||||
|
||||
//;VGM_LOG("TXTH: read at offset %x + %x\n", offset - txth->base_offset, txth->base_offset);
|
||||
switch(size) {
|
||||
case 1: value = (uint8_t)read_8bit(offset,sf); break;
|
||||
case 2: value = big_endian ? (uint16_t)read_16bitBE(offset,sf) : (uint16_t)read_16bitLE(offset,sf); break;
|
||||
case 3: value = (big_endian ? (uint32_t)read_32bitBE(offset,sf) : (uint32_t)read_32bitLE(offset,sf)) & 0x00FFFFFF; break;
|
||||
case 4: value = big_endian ? (uint32_t)read_32bitBE(offset,sf) : (uint32_t)read_32bitLE(offset,sf); break;
|
||||
case 1: value = read_u8(offset,sf); break;
|
||||
case 2: value = big_endian ? read_u16be(offset,sf) : read_u16le(offset,sf); break;
|
||||
case 3: value = (big_endian ? read_u32be(offset,sf) : read_u32le(offset,sf)) & 0x00FFFFFF; break;
|
||||
case 4: value = big_endian ? read_u32be(offset,sf) : read_u32le(offset,sf); break;
|
||||
default: goto fail;
|
||||
}
|
||||
value_read = 1;
|
||||
|
@ -1728,7 +1778,7 @@ static int parse_num(STREAMFILE* sf, txth_header* txth, const char * val, uint32
|
|||
/* move to next field (if any) */
|
||||
val += n;
|
||||
|
||||
//;VGM_LOG("TXTH: val='%s', n=%i, brackets=%i, result=%i\n", val, n, brackets, result);
|
||||
//;VGM_LOG("TXTH: val='%s', n=%i, brackets=%i, result=0x%x\n", val, n, brackets, result);
|
||||
}
|
||||
|
||||
/* unbalanced brackets */
|
||||
|
@ -1750,6 +1800,7 @@ static int parse_num(STREAMFILE* sf, txth_header* txth, const char * val, uint32
|
|||
//;VGM_LOG("TXTH: final result %u (0x%x)\n", result, result);
|
||||
return 1;
|
||||
fail:
|
||||
VGM_LOG("TXTH: error parsing num '%s'\n", val);
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
|
|
@ -1262,7 +1262,7 @@ static inline int is_match(const char* str1, const char* str2) {
|
|||
static void parse_params(txtp_entry* entry, char* params) {
|
||||
/* parse params: #(commands) */
|
||||
int n, nc, nm, mc;
|
||||
char command[TXTP_LINE_MAX] = {0};
|
||||
char command[TXTP_LINE_MAX];
|
||||
play_config_t* tcfg = &entry->config;
|
||||
|
||||
entry->range_start = 0;
|
||||
|
@ -1793,7 +1793,7 @@ fail:
|
|||
|
||||
static int is_substring(const char* val, const char* cmp) {
|
||||
int n;
|
||||
char subval[TXTP_LINE_MAX] = {0};
|
||||
char subval[TXTP_LINE_MAX];
|
||||
|
||||
/* read string without trailing spaces or comments/commands */
|
||||
if (sscanf(val, " %s%n[^ #\t\r\n]%n", subval, &n, &n) != 1)
|
||||
|
@ -1897,10 +1897,14 @@ static txtp_header* parse_txtp(STREAMFILE* sf) {
|
|||
|
||||
|
||||
/* read and parse lines */
|
||||
while (txt_offset < file_size) {
|
||||
{
|
||||
char line[TXTP_LINE_MAX];
|
||||
char key[TXTP_LINE_MAX] = {0}, val[TXTP_LINE_MAX] = {0}; /* at least as big as a line to avoid overflows (I hope) */
|
||||
char filename[TXTP_LINE_MAX] = {0};
|
||||
char key[TXTP_LINE_MAX];
|
||||
char val[TXTP_LINE_MAX];
|
||||
char filename[TXTP_LINE_MAX];
|
||||
/* at least as big as a line to avoid overflows (I hope) */
|
||||
|
||||
while (txt_offset < file_size) {
|
||||
int ok, bytes_read, line_ok;
|
||||
|
||||
bytes_read = read_line(line, sizeof(line), txt_offset, sf, &line_ok);
|
||||
|
@ -1908,7 +1912,7 @@ static txtp_header* parse_txtp(STREAMFILE* sf) {
|
|||
|
||||
txt_offset += bytes_read;
|
||||
|
||||
/* get key/val (ignores lead/trail spaces, # may be commands or comments) */
|
||||
/* try key/val (ignores lead/trail spaces, # may be commands or comments) */
|
||||
ok = sscanf(line, " %[^ \t#=] = %[^\t\r\n] ", key,val);
|
||||
if (ok == 2) { /* key=val */
|
||||
if (!parse_keyval(txtp, key, val)) /* read key/val */
|
||||
|
@ -1927,12 +1931,14 @@ static txtp_header* parse_txtp(STREAMFILE* sf) {
|
|||
if (!add_entry(txtp, filename, 0))
|
||||
goto fail;
|
||||
}
|
||||
}
|
||||
|
||||
/* mini-txth: if no entries are set try with filename, ex. from "song.ext#3.txtp" use "song.ext#3"
|
||||
* (it's possible to have default "commands" inside the .txtp plus filename+settings) */
|
||||
if (txtp->entry_count == 0) {
|
||||
char filename[PATH_LIMIT] = {0};
|
||||
char filename[PATH_LIMIT];
|
||||
|
||||
filename[0] = '\0';
|
||||
get_streamfile_basename(sf, filename, sizeof(filename));
|
||||
|
||||
add_entry(txtp, filename, 0);
|
||||
|
|
Loading…
Reference in New Issue