Updated VGMStream to r1702-8-gb1325840

CQTexperiment
Christopher Snowhill 2022-01-18 03:13:34 -08:00
parent 39a5ee8ab7
commit c05dc28a8d
18 changed files with 1038 additions and 981 deletions

View File

@ -154,6 +154,8 @@
8346D98525BF83B300D1A8B0 /* compresswave_decoder_lib.c in Sources */ = {isa = PBXBuildFile; fileRef = 8346D98025BF83B300D1A8B0 /* compresswave_decoder_lib.c */; };
8346D98625BF83B300D1A8B0 /* compresswave_decoder.c in Sources */ = {isa = PBXBuildFile; fileRef = 8346D98125BF83B300D1A8B0 /* compresswave_decoder.c */; };
8346D98725BF83B300D1A8B0 /* compresswave_decoder_lib.h in Headers */ = {isa = PBXBuildFile; fileRef = 8346D98225BF83B300D1A8B0 /* compresswave_decoder_lib.h */; };
8347C7482796D76700FA8A7D /* info.c in Sources */ = {isa = PBXBuildFile; fileRef = 8347C7432796D76700FA8A7D /* info.c */; };
8347C7492796D76700FA8A7D /* seek.c in Sources */ = {isa = PBXBuildFile; fileRef = 8347C7472796D76700FA8A7D /* seek.c */; };
8349A8DF1FE6251F00E26435 /* vorbis_custom_utils_vid1.c in Sources */ = {isa = PBXBuildFile; fileRef = 8349A8DC1FE6251E00E26435 /* vorbis_custom_utils_vid1.c */; };
8349A8E11FE6251F00E26435 /* ea_mt_decoder.c in Sources */ = {isa = PBXBuildFile; fileRef = 8349A8DE1FE6251F00E26435 /* ea_mt_decoder.c */; };
8349A8E81FE6253900E26435 /* blocked_dec.c in Sources */ = {isa = PBXBuildFile; fileRef = 8349A8E21FE6253800E26435 /* blocked_dec.c */; };
@ -974,6 +976,8 @@
8346D98025BF83B300D1A8B0 /* compresswave_decoder_lib.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = compresswave_decoder_lib.c; sourceTree = "<group>"; };
8346D98125BF83B300D1A8B0 /* compresswave_decoder.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = compresswave_decoder.c; sourceTree = "<group>"; };
8346D98225BF83B300D1A8B0 /* compresswave_decoder_lib.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = compresswave_decoder_lib.h; sourceTree = "<group>"; };
8347C7432796D76700FA8A7D /* info.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = info.c; sourceTree = "<group>"; };
8347C7472796D76700FA8A7D /* seek.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = seek.c; sourceTree = "<group>"; };
8349A8DC1FE6251E00E26435 /* vorbis_custom_utils_vid1.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = vorbis_custom_utils_vid1.c; sourceTree = "<group>"; };
8349A8DE1FE6251F00E26435 /* ea_mt_decoder.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = ea_mt_decoder.c; sourceTree = "<group>"; };
8349A8E21FE6253800E26435 /* blocked_dec.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = blocked_dec.c; sourceTree = "<group>"; };
@ -1631,12 +1635,14 @@
83AA7F892519C076004C5298 /* decode.c */,
83AA7F862519C076004C5298 /* decode.h */,
83A3F0711E3AD8B900D6A794 /* formats.c */,
8347C7432796D76700FA8A7D /* info.c */,
83C7282522BC8C1400678B4A /* mixing.c */,
83C7282422BC8C1400678B4A /* mixing.h */,
83C7282622BC8C1400678B4A /* plugins.c */,
83C7282322BC8C1300678B4A /* plugins.h */,
83AA7F882519C076004C5298 /* render.c */,
83AA7F872519C076004C5298 /* render.h */,
8347C7472796D76700FA8A7D /* seek.c */,
83A3F0731E3AD8B900D6A794 /* stack_alloc.h */,
836F6F1718BDC2190095E648 /* streamfile.c */,
836F6F1818BDC2190095E648 /* streamfile.h */,
@ -2904,6 +2910,7 @@
834FE0BB215C798C000A5D3D /* celt_fsb_decoder.c in Sources */,
836F702518BDC2190095E648 /* rwx.c in Sources */,
836F6F3A18BDC2190095E648 /* sdx2_decoder.c in Sources */,
8347C7482796D76700FA8A7D /* info.c in Sources */,
836F6FAF18BDC2190095E648 /* ngc_dsp_std.c in Sources */,
836F6F7118BDC2190095E648 /* ast.c in Sources */,
834FE0BF215C79A9000A5D3D /* flat.c in Sources */,
@ -3138,6 +3145,7 @@
836F6F2A18BDC2190095E648 /* lsf_decoder.c in Sources */,
8349A9151FE6258200E26435 /* ps2_xa2_rrp.c in Sources */,
836F703518BDC2190095E648 /* svs.c in Sources */,
8347C7492796D76700FA8A7D /* seek.c in Sources */,
836F6FA318BDC2190095E648 /* naomi_spsd.c in Sources */,
8306B0B920984552000302D4 /* blocked_matx.c in Sources */,
83A21F7B201D895B000F04B9 /* blocked_xvag.c in Sources */,

View File

@ -1546,7 +1546,13 @@ int vgmstream_do_loop(VGMSTREAM* vgmstream) {
}
}
/* loop codecs */
//TODO: improve
/* loop codecs that need special handling, usually:
* - on hit_loop, current offset is copied to loop_ch[].offset
* - some codecs will overwrite loop_ch[].offset with a custom value
* - loop_ch[] is copied to ch[] (with custom value)
* - then codec will use ch[]'s offset
* regular codecs may use copied loop_ch[] offset without issue */
seek_codec(vgmstream);
/* restore! */

View File

@ -65,6 +65,7 @@ static const char* extension_list[] = {
"al2",
"ams", //txth/reserved [Super Dragon Ball Z (PS2) ELF names]
"amts", //fake extension/header id for .stm (renamed? to be removed?)
"an2",
"ao",
"ap",
"apc",
@ -98,7 +99,6 @@ static const char* extension_list[] = {
"bdsp",
"bfstm",
"bfwav",
"bfwavnsmbu", //fake extension for New Super Smash Bros U (renamed to fix bug)
"bg00",
"bgm",
"bgw",

View File

@ -0,0 +1,447 @@
#include <ctype.h>
#include "vgmstream.h"
#include "coding/coding.h"
#include "mixing.h"
/*******************************************************************************/
/* TEXT */
/*******************************************************************************/
static void describe_get_time(int32_t samples, int sample_rate, double* p_time_mm, double* p_time_ss) {
double seconds = (double)samples / sample_rate;
*p_time_mm = (int)(seconds / 60.0);
*p_time_ss = seconds - *p_time_mm * 60.0f;
if (*p_time_ss >= 59.999) /* avoid round up to 60.0 when printing to %06.3f */
*p_time_ss = 59.999;
}
/* Write a description of the stream into array pointed by desc, which must be length bytes long.
* Will always be null-terminated if length > 0 */
void describe_vgmstream(VGMSTREAM* vgmstream, char* desc, int length) {
#define TEMPSIZE (256+32)
char temp[TEMPSIZE];
double time_mm, time_ss;
desc[0] = '\0';
if (!vgmstream) {
snprintf(temp,TEMPSIZE, "NULL VGMSTREAM");
concatn(length,desc,temp);
return;
}
snprintf(temp,TEMPSIZE, "sample rate: %d Hz\n", vgmstream->sample_rate);
concatn(length,desc,temp);
snprintf(temp,TEMPSIZE, "channels: %d\n", vgmstream->channels);
concatn(length,desc,temp);
{
int output_channels = 0;
mixing_info(vgmstream, NULL, &output_channels);
if (output_channels != vgmstream->channels) {
snprintf(temp,TEMPSIZE, "input channels: %d\n", vgmstream->channels); /* repeated but mainly for plugins */
concatn(length,desc,temp);
snprintf(temp,TEMPSIZE, "output channels: %d\n", output_channels);
concatn(length,desc,temp);
}
}
if (vgmstream->channel_layout) {
int cl = vgmstream->channel_layout;
/* not "channel layout: " to avoid mixups with "layout: " */
snprintf(temp,TEMPSIZE, "channel mask: 0x%x /", vgmstream->channel_layout);
concatn(length,desc,temp);
if (cl & speaker_FL) concatn(length,desc," FL");
if (cl & speaker_FR) concatn(length,desc," FR");
if (cl & speaker_FC) concatn(length,desc," FC");
if (cl & speaker_LFE) concatn(length,desc," LFE");
if (cl & speaker_BL) concatn(length,desc," BL");
if (cl & speaker_BR) concatn(length,desc," BR");
if (cl & speaker_FLC) concatn(length,desc," FLC");
if (cl & speaker_FRC) concatn(length,desc," FRC");
if (cl & speaker_BC) concatn(length,desc," BC");
if (cl & speaker_SL) concatn(length,desc," SL");
if (cl & speaker_SR) concatn(length,desc," SR");
if (cl & speaker_TC) concatn(length,desc," TC");
if (cl & speaker_TFL) concatn(length,desc," TFL");
if (cl & speaker_TFC) concatn(length,desc," TFC");
if (cl & speaker_TFR) concatn(length,desc," TFR");
if (cl & speaker_TBL) concatn(length,desc," TBL");
if (cl & speaker_TBC) concatn(length,desc," TBC");
if (cl & speaker_TBR) concatn(length,desc," TBR");
concatn(length,desc,"\n");
}
/* times mod sounds avoid round up to 60.0 */
if (vgmstream->loop_start_sample >= 0 && vgmstream->loop_end_sample > vgmstream->loop_start_sample) {
if (!vgmstream->loop_flag) {
concatn(length,desc,"looping: disabled\n");
}
describe_get_time(vgmstream->loop_start_sample, vgmstream->sample_rate, &time_mm, &time_ss);
snprintf(temp,TEMPSIZE, "loop start: %d samples (%1.0f:%06.3f seconds)\n", vgmstream->loop_start_sample, time_mm, time_ss);
concatn(length,desc,temp);
describe_get_time(vgmstream->loop_end_sample, vgmstream->sample_rate, &time_mm, &time_ss);
snprintf(temp,TEMPSIZE, "loop end: %d samples (%1.0f:%06.3f seconds)\n", vgmstream->loop_end_sample, time_mm, time_ss);
concatn(length,desc,temp);
}
describe_get_time(vgmstream->num_samples, vgmstream->sample_rate, &time_mm, &time_ss);
snprintf(temp,TEMPSIZE, "stream total samples: %d (%1.0f:%06.3f seconds)\n", vgmstream->num_samples, time_mm, time_ss);
concatn(length,desc,temp);
snprintf(temp,TEMPSIZE, "encoding: ");
concatn(length,desc,temp);
get_vgmstream_coding_description(vgmstream, temp, TEMPSIZE);
concatn(length,desc,temp);
concatn(length,desc,"\n");
snprintf(temp,TEMPSIZE, "layout: ");
concatn(length,desc,temp);
get_vgmstream_layout_description(vgmstream, temp, TEMPSIZE);
concatn(length, desc, temp);
concatn(length,desc,"\n");
if (vgmstream->layout_type == layout_interleave && vgmstream->channels > 1) {
snprintf(temp,TEMPSIZE, "interleave: %#x bytes\n", (int32_t)vgmstream->interleave_block_size);
concatn(length,desc,temp);
if (vgmstream->interleave_first_block_size && vgmstream->interleave_first_block_size != vgmstream->interleave_block_size) {
snprintf(temp,TEMPSIZE, "interleave first block: %#x bytes\n", (int32_t)vgmstream->interleave_first_block_size);
concatn(length,desc,temp);
}
if (vgmstream->interleave_last_block_size && vgmstream->interleave_last_block_size != vgmstream->interleave_block_size) {
snprintf(temp,TEMPSIZE, "interleave last block: %#x bytes\n", (int32_t)vgmstream->interleave_last_block_size);
concatn(length,desc,temp);
}
}
/* codecs with configurable frame size */
if (vgmstream->frame_size > 0 || vgmstream->interleave_block_size > 0) {
int32_t frame_size = vgmstream->frame_size > 0 ? vgmstream->frame_size : vgmstream->interleave_block_size;
switch (vgmstream->coding_type) {
case coding_MSADPCM:
case coding_MSADPCM_int:
case coding_MSADPCM_ck:
case coding_MS_IMA:
case coding_MC3:
case coding_WWISE_IMA:
case coding_REF_IMA:
case coding_PSX_cfg:
snprintf(temp,TEMPSIZE, "frame size: %#x bytes\n", frame_size);
concatn(length,desc,temp);
break;
default:
break;
}
}
snprintf(temp,TEMPSIZE, "metadata from: ");
concatn(length,desc,temp);
get_vgmstream_meta_description(vgmstream, temp, TEMPSIZE);
concatn(length,desc,temp);
concatn(length,desc,"\n");
snprintf(temp,TEMPSIZE, "bitrate: %d kbps\n", get_vgmstream_average_bitrate(vgmstream) / 1000);
concatn(length,desc,temp);
/* only interesting if more than one */
if (vgmstream->num_streams > 1) {
snprintf(temp,TEMPSIZE, "stream count: %d\n", vgmstream->num_streams);
concatn(length,desc,temp);
}
if (vgmstream->num_streams > 1) {
snprintf(temp,TEMPSIZE, "stream index: %d\n", vgmstream->stream_index == 0 ? 1 : vgmstream->stream_index);
concatn(length,desc,temp);
}
if (vgmstream->stream_name[0] != '\0') {
snprintf(temp,TEMPSIZE, "stream name: %s\n", vgmstream->stream_name);
concatn(length,desc,temp);
}
if (vgmstream->config_enabled) {
int32_t samples = vgmstream->pstate.play_duration;
describe_get_time(samples, vgmstream->sample_rate, &time_mm, &time_ss);
snprintf(temp,TEMPSIZE, "play duration: %d samples (%1.0f:%06.3f seconds)\n", samples, time_mm, time_ss);
concatn(length,desc,temp);
}
}
void describe_vgmstream_info(VGMSTREAM* vgmstream, vgmstream_info* info) {
if (!info) {
return;
}
memset(info, 0, sizeof(*info));
if (!vgmstream) {
return;
}
info->sample_rate = vgmstream->sample_rate;
info->channels = vgmstream->channels;
{
int output_channels = 0;
mixing_info(vgmstream, NULL, &output_channels);
if (output_channels != vgmstream->channels) {
info->mixing_info.input_channels = vgmstream->channels;
info->mixing_info.output_channels = output_channels;
}
}
info->channel_layout = vgmstream->channel_layout;
if (vgmstream->loop_start_sample >= 0 && vgmstream->loop_end_sample > vgmstream->loop_start_sample) {
info->loop_info.start = vgmstream->loop_start_sample;
info->loop_info.end = vgmstream->loop_end_sample;
}
info->num_samples = vgmstream->num_samples;
get_vgmstream_coding_description(vgmstream, info->encoding, sizeof(info->encoding));
get_vgmstream_layout_description(vgmstream, info->layout, sizeof(info->layout));
if (vgmstream->layout_type == layout_interleave && vgmstream->channels > 1) {
info->interleave_info.value = vgmstream->interleave_block_size;
if (vgmstream->interleave_first_block_size && vgmstream->interleave_first_block_size != vgmstream->interleave_block_size) {
info->interleave_info.first_block = vgmstream->interleave_first_block_size;
}
if (vgmstream->interleave_last_block_size && vgmstream->interleave_last_block_size != vgmstream->interleave_block_size) {
info->interleave_info.last_block = vgmstream->interleave_last_block_size;
}
}
/* codecs with configurable frame size */
if (vgmstream->frame_size > 0 || vgmstream->interleave_block_size > 0) {
int32_t frame_size = vgmstream->frame_size > 0 ? vgmstream->frame_size : vgmstream->interleave_block_size;
switch (vgmstream->coding_type) {
case coding_MSADPCM:
case coding_MSADPCM_int:
case coding_MSADPCM_ck:
case coding_MS_IMA:
case coding_MC3:
case coding_WWISE_IMA:
case coding_REF_IMA:
case coding_PSX_cfg:
info->frame_size = frame_size;
break;
default:
break;
}
}
get_vgmstream_meta_description(vgmstream, info->metadata, sizeof(info->metadata));
info->bitrate = get_vgmstream_average_bitrate(vgmstream);
/* only interesting if more than one */
if (vgmstream->num_streams > 1) {
info->stream_info.total = vgmstream->num_streams;
}
else {
info->stream_info.total = 1;
}
if (vgmstream->num_streams > 1) {
info->stream_info.current = vgmstream->stream_index == 0 ? 1 : vgmstream->stream_index;
}
if (vgmstream->stream_name[0] != '\0') {
snprintf(info->stream_info.name, sizeof(info->stream_info.name), "%s", vgmstream->stream_name);
}
}
/*******************************************************************************/
/* BITRATE */
/*******************************************************************************/
#define BITRATE_FILES_MAX 128 /* arbitrary max, but +100 segments have been observed */
typedef struct {
uint32_t hash[BITRATE_FILES_MAX]; /* already used streamfiles */
int subsong[BITRATE_FILES_MAX]; /* subsongs of those streamfiles (could be incorporated to the hash?) */
int count;
int count_max;
} bitrate_info_t;
static uint32_t hash_sf(STREAMFILE* sf) {
int i;
char path[PATH_LIMIT];
uint32_t hash = 2166136261;
get_streamfile_name(sf, path, sizeof(path));
/* our favorite garbo hash a.k.a FNV-1 32b */
i = 0;
while (path[i] != '\0') {
char c = tolower(path[i]);
hash = (hash * 16777619) ^ (uint8_t)c;
i++;
}
return hash;
}
/* average bitrate helper to get STREAMFILE for a channel, since some codecs may use their own */
static STREAMFILE* get_vgmstream_average_bitrate_channel_streamfile(VGMSTREAM* vgmstream, int channel) {
if (vgmstream->coding_type == coding_NWA) {
return nwa_get_streamfile(vgmstream->codec_data);
}
if (vgmstream->coding_type == coding_ACM) {
return acm_get_streamfile(vgmstream->codec_data);
}
if (vgmstream->coding_type == coding_COMPRESSWAVE) {
return compresswave_get_streamfile(vgmstream->codec_data);
}
#ifdef VGM_USE_VORBIS
if (vgmstream->coding_type == coding_OGG_VORBIS) {
return ogg_vorbis_get_streamfile(vgmstream->codec_data);
}
#endif
if (vgmstream->coding_type == coding_CRI_HCA) {
return hca_get_streamfile(vgmstream->codec_data);
}
#ifdef VGM_USE_FFMPEG
if (vgmstream->coding_type == coding_FFmpeg) {
return ffmpeg_get_streamfile(vgmstream->codec_data);
}
#endif
#if defined(VGM_USE_MP4V2) && defined(VGM_USE_FDKAAC)
if (vgmstream->coding_type == coding_MP4_AAC) {
mp4_aac_codec_data *data = vgmstream->codec_data;
return data ? data->if_file.streamfile : NULL;
}
#endif
return vgmstream->ch[channel].streamfile;
}
static int get_vgmstream_file_bitrate_from_size(size_t size, int sample_rate, int32_t length_samples) {
if (sample_rate == 0 || length_samples == 0) return 0;
if (length_samples < 100) return 0; /* ignore stupid bitrates caused by some segments */
return (int)((int64_t)size * 8 * sample_rate / length_samples);
}
static int get_vgmstream_file_bitrate_from_streamfile(STREAMFILE* sf, int sample_rate, int32_t length_samples) {
if (sf == NULL) return 0;
return get_vgmstream_file_bitrate_from_size(get_streamfile_size(sf), sample_rate, length_samples);
}
static int get_vgmstream_file_bitrate_main(VGMSTREAM* vgmstream, bitrate_info_t* br, int* p_uniques) {
int i, ch;
int bitrate = 0;
/* Recursively get bitrate and fill the list of streamfiles if needed (to filter),
* since layouts can include further vgmstreams that may also share streamfiles.
*
* Because of how data, layers and segments can be combined it's possible to
* fool this in various ways; metas should report stream_size in complex cases
* to get accurate bitrates (particularly for subsongs). An edge case is when
* segments use only a few samples from a full file (like Wwise transitions), bitrates
* become a bit high since its hard to detect only part of the file is needed. */
if (vgmstream->layout_type == layout_segmented) {
int uniques = 0;
segmented_layout_data *data = (segmented_layout_data *) vgmstream->layout_data;
for (i = 0; i < data->segment_count; i++) {
bitrate += get_vgmstream_file_bitrate_main(data->segments[i], br, &uniques);
}
if (uniques)
bitrate /= uniques; /* average */
}
else if (vgmstream->layout_type == layout_layered) {
layered_layout_data *data = vgmstream->layout_data;
for (i = 0; i < data->layer_count; i++) {
bitrate += get_vgmstream_file_bitrate_main(data->layers[i], br, NULL);
}
}
else {
/* Add channel bitrate if streamfile hasn't been used before, so bitrate doesn't count repeats
* (like same STREAMFILE reopened per channel, also considering SFs may be wrapped). */
for (ch = 0; ch < vgmstream->channels; ch++) {
uint32_t hash_cur;
int subsong_cur;
STREAMFILE* sf_cur;
int is_unique = 1; /* default to "no other SFs exist" */
/* compares paths (hashes for faster compares) + subsongs (same file + different subsong = "different" file) */
sf_cur = get_vgmstream_average_bitrate_channel_streamfile(vgmstream, ch);
if (!sf_cur) continue;
hash_cur = hash_sf(sf_cur);
subsong_cur = vgmstream->stream_index;
for (i = 0; i < br->count; i++) {
uint32_t hash_cmp = br->hash[i];
int subsong_cmp = br->subsong[i];
if (hash_cur == hash_cmp && subsong_cur == subsong_cmp) {
is_unique = 0;
break;
}
}
if (is_unique) {
size_t stream_size;
if (br->count >= br->count_max) goto fail;
if (vgmstream->stream_size) {
/* stream_size applies to both channels but should add once and detect repeats (for current subsong) */
stream_size = get_vgmstream_file_bitrate_from_size(vgmstream->stream_size, vgmstream->sample_rate, vgmstream->num_samples);
}
else {
stream_size = get_vgmstream_file_bitrate_from_streamfile(sf_cur, vgmstream->sample_rate, vgmstream->num_samples);
}
/* possible in cases like using silence codec */
if (!stream_size)
break;
br->hash[br->count] = hash_cur;
br->subsong[br->count] = subsong_cur;
br->count++;
if (p_uniques)
(*p_uniques)++;
bitrate += stream_size;
break;
}
}
}
return bitrate;
fail:
return 0;
}
/* Return the average bitrate in bps of all unique data contained within this stream.
* This is the bitrate of the *file*, as opposed to the bitrate of the *codec*, meaning
* it counts extra data like block headers and padding. While this can be surprising
* sometimes (as it's often higher than common codec bitrates) it isn't wrong per se. */
int get_vgmstream_average_bitrate(VGMSTREAM* vgmstream) {
bitrate_info_t br = {0};
br.count_max = BITRATE_FILES_MAX;
return get_vgmstream_file_bitrate_main(vgmstream, &br, NULL);
}

View File

@ -105,7 +105,7 @@ VGMSTREAM* init_vgmstream_awb_memory(STREAMFILE* sf, STREAMFILE* sf_acb) {
subfile_size = read_u32le(subfile_offset + 0x04,sf) + 0x08; /* padded size, use RIFF's */
}
else if (is_id32be(subfile_offset,sf, "CWAV")) { /* (type 9=CWAV) */
init_vgmstream = init_vgmstream_rwsd; /* Sonic: Lost World (3DS) */
init_vgmstream = init_vgmstream_bcwav; /* Sonic: Lost World (3DS) */
extension = "bcwav";
}
else if (read_u32be(subfile_offset + 0x08,sf) >= 8000 && read_u32be(subfile_offset + 0x08,sf) <= 48000 &&

View File

@ -1,109 +1,144 @@
#include "meta.h"
#include "../coding/coding.h"
#include "../util/endianness.h"
/* FWAV - Nintendo streams */
/* FWAV and CWAV are basically identical except always LE */
typedef enum { FWAV, CWAV } bxwav_type_t;
static VGMSTREAM* init_vgmstream_bxwav(STREAMFILE* sf, bxwav_type_t type);
/* FWAV - NintendoWare binary caFe wave (WiiU and Switch games) */
VGMSTREAM* init_vgmstream_bfwav(STREAMFILE* sf) {
VGMSTREAM* vgmstream = NULL;
off_t start_offset;
off_t info_offset, data_offset;
int channels, loop_flag, codec, sample_rate;
int big_endian;
size_t interleave = 0;
int32_t (*read_32bit)(off_t,STREAMFILE*) = NULL;
int16_t (*read_16bit)(off_t,STREAMFILE*) = NULL;
/* checks */
if (!is_id32be(0x00, sf, "FWAV"))
goto fail;
/* .bfwavnsmbu: fake extension to detect New Super Mario Bros U files with weird sample rate */
if (!check_extensions(sf, "bfwav,fwav,bfwavnsmbu"))
/* .bfwav: used?
* .fwav: header id */
if (!check_extensions(sf, "bfwav,fwav"))
goto fail;
return init_vgmstream_bxwav(sf, FWAV);
fail:
return NULL;
}
/* CWAV - NintendoWare binary CTR wave (3DS games) */
VGMSTREAM* init_vgmstream_bcwav(STREAMFILE* sf) {
/* checks */
if (!is_id32be(0x00, sf, "CWAV"))
goto fail;
/* .bcwav: standard 3DS (though rare as usually found in .bcsar) [Adventure Bar Story (3DS), LBX (3DS)]
* .adpcm: 80's Overdrive (3DS)
* .bms: 3D Classics Kirby's Adventure (3DS)
* .sfx: Wizdom (3DS)
* .str: Pac-Man and the Ghostly Adventures 2 (3DS)
* .zic: Wizdom (3DS) */
if (!check_extensions(sf, "bcwav,adpcm,bms,sfx,str,zic"))
goto fail;
return init_vgmstream_bxwav(sf, CWAV);
fail:
return NULL;
}
static VGMSTREAM* init_vgmstream_bxwav(STREAMFILE* sf, bxwav_type_t type) {
VGMSTREAM* vgmstream = NULL;
uint32_t info_offset, data_offset, chtb_offset;
int channels, loop_flag, codec, sample_rate;
int big_endian;
int32_t num_samples, loop_start;
read_u32_t read_u32;
read_s32_t read_s32;
read_u16_t read_u16;
read_s16_t read_s16;
/* BOM check */
if (read_u16be(0x04, sf) == 0xFEFF) {
read_32bit = read_32bitBE;
read_16bit = read_16bitBE;
if (read_u16be(0x04, sf) == 0xFEFF) { /* WiiU */
big_endian = 1;
} else if (read_u16be(0x04, sf) == 0xFFFE) {
read_32bit = read_32bitLE;
read_16bit = read_16bitLE;
read_u32 = read_u32be;
read_s32 = read_s32be;
read_u16 = read_u16be;
read_s16 = read_s16be;
}
else if (read_u16le(0x04, sf) == 0xFEFF) { /* 3DS, Switch */
big_endian = 0;
} else {
read_u32 = read_u32le;
read_s32 = read_s32le;
read_u16 = read_u16le;
read_s16 = read_s16le;
}
else {
goto fail;
}
/* FWAV header */
/* header */
/* 0x06(2): header size (0x40) */
/* 0x08: version (0x00010200) */
/* 0x08: version */
/* - FWAV: 0x00010200 */
/* - CWAV: 0x00000002 (Kirby's Adventure), 0x00000102 (common), 0x00010102 (FE Fates, Hyrule Warriors Legends) */
/* 0x0c: file size */
/* 0x10(2): sections (2) */
/* 0x14(2): info mark (0x7000) */
info_offset = read_32bit(0x18, sf);
info_offset = read_u32(0x18, sf);
/* 0x1c: info size */
/* 0x20(2): data mark (0x7001) */
data_offset = read_32bit(0x24, sf);
data_offset = read_u32(0x24, sf);
/* 0x28: data size */
/* rest: padding */
/* INFO section */
if (!is_id32be(info_offset, sf, "INFO"))
if (!is_id32be(info_offset + 0x00, sf, "INFO"))
goto fail;
/* 0x04: size */
codec = read_u8(info_offset + 0x08, sf);
loop_flag = read_u8(info_offset + 0x09, sf);
sample_rate = read_32bit(info_offset + 0x0C, sf);
channels = read_32bit(info_offset + 0x1C, sf);
/* 0x0a: padding */
sample_rate = read_u32(info_offset + 0x0C, sf);
loop_start = read_s32(info_offset + 0x10, sf);
num_samples = read_s32(info_offset + 0x14, sf);
/* 0x18: original loop start? (slightly lower) */
chtb_offset = info_offset + 0x1C;
channels = read_u32(chtb_offset + 0x00, sf);
/* channel table is parsed at the end */
//TODO remove
if (check_extensions(sf, "bfwavnsmbu"))
sample_rate = 16000;
/* parse channel table */
{
off_t channel1_info, data_start;
int i;
channel1_info = info_offset + 0x1c + read_32bit(info_offset+0x20+0x08*0+0x04, sf);
data_start = read_32bit(channel1_info+0x04, sf); /* within "DATA" after 0x08 */
/* channels use absolute offsets but should be ok as interleave */
interleave = 0;
if (channels > 1) {
off_t channel2_info = info_offset + 0x1c + read_32bit(info_offset+0x20+0x08*1+0x04, sf);
interleave = read_32bit(channel2_info+0x04, sf) - data_start;
}
start_offset = data_offset + 0x08 + data_start;
/* validate all channels just in case of multichannel with non-constant interleave */
for (i = 0; i < channels; i++) {
/* channel table, 0x00: flag (0x7100), 0x04: channel info offset */
off_t channel_info = info_offset + 0x1c + read_32bit(info_offset+0x20+0x08*i+0x04, sf);
/* channel info, 0x00(2): flag (0x1f00), 0x04: offset, 0x08(2): ADPCM flag (0x0300), 0x0c: ADPCM offset */
if ((uint16_t)read_16bit(channel_info+0x00, sf) != 0x1F00)
goto fail;
if (read_32bit(channel_info+0x04, sf) != data_start + interleave*i)
goto fail;
}
}
/* DATA section */
if (!is_id32be(data_offset + 0x00, sf, "DATA"))
goto fail;
/* build the VGMSTREAM */
vgmstream = allocate_vgmstream(channels, loop_flag);
if (!vgmstream) goto fail;
switch(type) {
case FWAV: vgmstream->meta_type = meta_FWAV; break;
case CWAV: vgmstream->meta_type = meta_CWAV; break;
default: goto fail;
}
vgmstream->sample_rate = sample_rate;
vgmstream->num_samples = read_32bit(info_offset + 0x14, sf);
vgmstream->loop_start_sample = read_32bit(info_offset + 0x10, sf);
vgmstream->loop_end_sample = vgmstream->num_samples;
vgmstream->meta_type = meta_FWAV;
vgmstream->layout_type = (channels == 1) ? layout_none : layout_interleave;
vgmstream->interleave_block_size = interleave;
vgmstream->num_samples = num_samples;
vgmstream->loop_start_sample = loop_start;
vgmstream->loop_end_sample = num_samples;
if (type == CWAV)
vgmstream->allow_dual_stereo = 1; /* LEGO 3DS games */
vgmstream->layout_type = layout_none;
/* only 0x02 is known, other codecs are probably from bxstm that do use them */
switch (codec) {
case 0x00:
vgmstream->coding_type = coding_PCM8;
@ -113,29 +148,80 @@ VGMSTREAM* init_vgmstream_bfwav(STREAMFILE* sf) {
vgmstream->coding_type = big_endian ? coding_PCM16BE : coding_PCM16LE;
break;
case 0x02:
case 0x02: /* common */
vgmstream->coding_type = coding_NGC_DSP;
{
int i, c;
off_t coef_header, coef_offset;
for (i = 0; i < vgmstream->channels; i++) {
for (c = 0; c < 16; c++) {
coef_header = info_offset + 0x1C + read_32bit(info_offset + 0x24 + (i*0x08), sf);
coef_offset = read_32bit(coef_header + 0x0c, sf) + coef_header;
vgmstream->ch[i].adpcm_coef[c] = read_16bit(coef_offset + c*2, sf);
}
}
}
/* coefs are read below */
break;
default: /* 0x03: IMA? */
case 0x03:
vgmstream->coding_type = coding_3DS_IMA;
/* hist is read below */
break;
default:
goto fail;
}
if (!vgmstream_open_stream(vgmstream,sf,start_offset))
if (!vgmstream_open_stream_bf(vgmstream, sf, data_offset, 1))
goto fail;
/* parse channel table and offsets
* (usually the interleave/distance is fixed, but in theory could be non-standard, so assign manually) */
{
int ch, i;
for (ch = 0; ch < channels; ch++) {
uint32_t chnf_offset, chdt_offset;
/* channel entry: */
/* - 0x00: mark (0x7100) */
/* - 0x02: padding */
/* - 0x04: channel info offset (from channel table offset) */
chnf_offset = read_u32(chtb_offset + 0x04 + ch * 0x08 + 0x04, sf) + chtb_offset;
/* channel info: */
/* 0x00: mark (0x1F00) */
/* 0x02: padding */
/* 0x04: offset to channel data (from DATA offset after size ) */
/* 0x08: ADPCM mark (0x0300=DSP, 0x0301=IMA, 0x0000=none) */
/* 0x0a: padding */
/* 0x0c: ADPCM offset (from channel info offset), 0xFFFFFFFF otherwise */
/* 0x10: null? */
if (read_u16(chnf_offset + 0x00, sf) != 0x1F00)
goto fail;
chdt_offset = read_u32(chnf_offset + 0x04, sf) + data_offset + 0x08;
vgmstream->ch[ch].channel_start_offset = chdt_offset;
vgmstream->ch[ch].offset = chdt_offset;
switch(codec) {
case 0x02: {
/* standard DSP coef + predictor + hists + loop predictor + loop hists */
uint32_t coef_offset = read_u32(chnf_offset + 0x0c, sf) + chnf_offset;
for (i = 0; i < 16; i++) {
vgmstream->ch[ch].adpcm_coef[i] = read_s16(coef_offset + 0x00 + i*0x02, sf);
}
vgmstream->ch[ch].adpcm_history1_16 = read_s16(coef_offset + 0x22, sf);
vgmstream->ch[ch].adpcm_history2_16 = read_s16(coef_offset + 0x24, sf);
break;
}
case 0x03: {
/* hist + step */
uint32_t coef_offset = read_u32(chnf_offset + 0x0c, sf) + chnf_offset;
vgmstream->ch[ch].adpcm_history1_16 = read_s16(coef_offset + 0x00, sf);
vgmstream->ch[ch].adpcm_step_index = read_s16(coef_offset + 0x02, sf);
break;
}
default:
break;
}
}
}
return vgmstream;
fail:

View File

@ -1,67 +1,83 @@
#include "meta.h"
#include "../coding/coding.h"
/* BWAV - NintendoWare(?) [Super Mario Maker 2 (Switch)] */
VGMSTREAM * init_vgmstream_bwav(STREAMFILE *streamFile) {
VGMSTREAM * vgmstream = NULL;
/* BWAV - NintendoWare wavs [Super Mario Maker 2 (Switch)] */
VGMSTREAM* init_vgmstream_bwav(STREAMFILE* sf) {
VGMSTREAM* vgmstream = NULL;
off_t start_offset;
int channel_count, loop_flag, codec;
int channels, loop_flag, codec, sample_rate;
int32_t num_samples, loop_start, loop_end;
size_t interleave = 0;
/* checks */
if (!check_extensions(streamFile, "bwav"))
if (!is_id32be(0x00, sf, "BWAV"))
goto fail;
if (read_32bitBE(0x00, streamFile) != 0x42574156) /* "BWAV" */
if (!check_extensions(sf, "bwav"))
goto fail;
/* 0x04: BOM */
/* 0x06: version? */
/* 0x08: ??? */
/* 0x0c: null? */
channel_count = read_16bitLE(0x0E, streamFile);
/* 0x04: BOM (always 0xFEFF = LE) */
/* 0x06: version? (0x0001) */
/* 0x08: crc32? (supposedly from all channel data without padding) */
if (read_u16le(0x0c, sf) != 0x00) /* supposedly prefetch flag */
goto fail;
channels = read_u16le(0x0e, sf);
/* - per channel (size 0x4c) */
codec = read_16bitLE(0x10, streamFile);
/* see below */
start_offset = read_32bitLE(0x40, streamFile);
loop_flag = read_32bitLE(0x4C, streamFile) != -1;
if (channel_count > 1)
interleave = read_32bitLE(0x8C, streamFile) - start_offset;
codec = read_u16le(0x10 + 0x00, sf);
/* 0x02: channel layout (0=L, 1=R, 2=C) */
sample_rate = read_s32le(0x10 + 0x04, sf);
/* 0x08: num_samples for full (non-prefetch) file? */
num_samples = read_s32le(0x10 + 0x0c, sf);
/* 0x10: coefs */
/* 0x30: offset for full (non-prefetch) file? */
start_offset = read_u32le(0x10 + 0x34, sf);
/* 0x38: flag? (always 1) */
loop_end = read_s32le(0x10 + 0x3C, sf);
loop_start = read_s32le(0x10 + 0x40, sf);
/* 0x44: start predictor? */
/* 0x46: hist1 sample? */
/* 0x48: hist2 sample? */
/* 0x4a: null? */
loop_flag = (loop_end != -1);
//TODO should make sure channels match and offsets make a proper interleave (see bfwav)
if (channels > 1)
interleave = read_u32le(0x8C, sf) - start_offset;
/* build the VGMSTREAM */
vgmstream = allocate_vgmstream(channel_count, loop_flag);
vgmstream = allocate_vgmstream(channels, loop_flag);
if (!vgmstream) goto fail;
vgmstream->sample_rate = read_32bitLE(0x14, streamFile);
vgmstream->num_samples = read_32bitLE(0x18, streamFile);
vgmstream->loop_start_sample = read_32bitLE(0x50, streamFile);
vgmstream->loop_end_sample = read_32bitLE(0x4C, streamFile);
vgmstream->meta_type = meta_BWAV;
vgmstream->allow_dual_stereo = 1;
vgmstream->sample_rate = sample_rate;
vgmstream->num_samples = num_samples;
vgmstream->loop_start_sample = loop_start;
vgmstream->loop_end_sample = loop_end;
vgmstream->allow_dual_stereo = 1; /* Animal Crossing: Happy Home Paradise */
switch(codec) {
case 0x0000:
case 0x0000: /* Ring Fit Adventure (Switch) */
vgmstream->coding_type = coding_PCM16LE;
vgmstream->layout_type = layout_interleave;
vgmstream->interleave_block_size = interleave;
break;
case 0x0001:
case 0x0001: /* Super Mario Maker 2 (Switch) */
vgmstream->coding_type = coding_NGC_DSP;
vgmstream->layout_type = layout_interleave;
vgmstream->interleave_block_size = interleave;
dsp_read_coefs_le(vgmstream, streamFile, 0x20, 0x4C);
dsp_read_coefs_le(vgmstream, sf, 0x20, 0x4C);
break;
default:
goto fail;
}
if (!vgmstream_open_stream(vgmstream,streamFile,start_offset))
if (!vgmstream_open_stream(vgmstream, sf, start_offset))
goto fail;
return vgmstream;

View File

@ -201,7 +201,7 @@ VGMSTREAM* init_vgmstream_cpk_memory(STREAMFILE* sf, STREAMFILE* sf_acb) {
if (!vgmstream) goto fail;
break;
case CWAV: /* Metal Gear Solid: Snake Eater 3D (3DS) */
vgmstream = init_vgmstream_rwsd(temp_sf);
vgmstream = init_vgmstream_bcwav(temp_sf);
if (!vgmstream) goto fail;
break;
case ADX: /* Sonic Generations (3DS) */

View File

@ -564,7 +564,8 @@ VGMSTREAM * init_vgmstream_bcstm(STREAMFILE* streamFile);
VGMSTREAM * init_vgmstream_bfstm(STREAMFILE* streamFile);
VGMSTREAM * init_vgmstream_bfwav(STREAMFILE* streamFile);
VGMSTREAM* init_vgmstream_bfwav(STREAMFILE* sf);
VGMSTREAM* init_vgmstream_bcwav(STREAMFILE* sf);
VGMSTREAM * init_vgmstream_kt_g1l(STREAMFILE* streamFile);
VGMSTREAM * init_vgmstream_kt_wiibgm(STREAMFILE* streamFile);

View File

@ -2,13 +2,12 @@
#include "../coding/coding.h"
#include "../util.h"
/* Wii RWAV, 3DS CWAV */
/* Wii RWAV */
struct rwav_data {
typedef struct {
// in
off_t offset;
STREAMFILE *streamFile;
int big_endian;
STREAMFILE *sf;
int32_t (*read_32bit)(off_t,STREAMFILE*);
// out
@ -16,71 +15,45 @@ struct rwav_data {
off_t start_offset;
off_t info_chunk;
off_t wave_offset;
};
} rwav_data_t;
static void read_rwav(struct rwav_data * rd)
{
static void read_rwav(rwav_data_t* rd) {
off_t chunk_table_offset;
off_t chunk_table_step;
off_t info_chunk;
off_t data_chunk;
if (rd->big_endian)
{
/* "RWAV" */
if ((uint32_t)read_32bitBE(rd->offset,rd->streamFile)!=0x52574156)
return;
/* big endian, version 2 */
if ((uint32_t)read_32bitBE(rd->offset+4,rd->streamFile)!=0xFEFF0102)
return;
chunk_table_offset = rd->offset+0x10;
chunk_table_step = 8;
}
else
{
/* "CWAV" */
if ((uint32_t)read_32bitBE(rd->offset,rd->streamFile)!=0x43574156)
return;
/* little endian, version 2 */
if ((uint32_t)read_32bitBE(rd->offset+4,rd->streamFile)!=0xFFFE4000 ||
(
(uint32_t)read_32bitBE(rd->offset+8,rd->streamFile)!=0x00000002 && /* Kirby's Adventure */
(uint32_t)read_32bitBE(rd->offset+8,rd->streamFile)!=0x00000102 && /* common */
(uint32_t)read_32bitBE(rd->offset+8,rd->streamFile)!=0x00010102
)
)
return;
chunk_table_offset = rd->offset+0x18;
chunk_table_step = 0xc;
}
info_chunk = rd->offset+rd->read_32bit(chunk_table_offset,rd->streamFile);
/* "INFO" */
if ((uint32_t)read_32bitBE(info_chunk,rd->streamFile)!=0x494e464f)
if (!is_id32be(rd->offset, rd->sf, "RWAV"))
return;
data_chunk = rd->offset+rd->read_32bit(chunk_table_offset+chunk_table_step,rd->streamFile);
/* "DATA" */
if ((uint32_t)read_32bitBE(data_chunk,rd->streamFile)!=0x44415441)
/* big endian, version 2 */
if (read_u32be(rd->offset+4,rd->sf) != 0xFEFF0102)
return;
rd->start_offset = data_chunk + 8;
rd->info_chunk = info_chunk + 8;
chunk_table_offset = rd->offset + 0x10;
chunk_table_step = 0x08;
info_chunk = rd->offset + rd->read_32bit(chunk_table_offset, rd->sf);
if (!is_id32be(info_chunk, rd->sf, "INFO"))
return;
data_chunk = rd->offset + rd->read_32bit(chunk_table_offset + chunk_table_step, rd->sf);
if (!is_id32be(data_chunk, rd->sf, "DATA"))
return;
rd->start_offset = data_chunk + 0x08;
rd->info_chunk = info_chunk + 0x08;
rd->version = 2;
rd->wave_offset = info_chunk - 8; // pretend to have a WAVE
rd->wave_offset = info_chunk - 0x08; /* pretend to have a WAVE */
return;
}
static void read_rwar(struct rwav_data * rd)
{
if ((uint32_t)read_32bitBE(rd->offset,rd->streamFile)!=0x52574152) /* "RWAR" */
static void read_rwar(rwav_data_t* rd) {
if (!is_id32be(rd->offset, rd->sf, "RWAR"))
return;
if ((uint32_t)read_32bitBE(rd->offset+4,rd->streamFile)!=0xFEFF0100) /* version 0 */
if (read_u32be(rd->offset + 0x04, rd->sf) != 0xFEFF0100) /* version 0 */
return;
rd->offset += 0x60;
@ -92,23 +65,20 @@ static void read_rwar(struct rwav_data * rd)
/* RWSD is quite similar to BRSTM, but can contain several streams.
* Still, some games use it for single streams. We only support the
* single stream form here */
VGMSTREAM * init_vgmstream_rwsd(STREAMFILE *streamFile) {
VGMSTREAM * vgmstream = NULL;
VGMSTREAM* init_vgmstream_rwsd(STREAMFILE* sf) {
VGMSTREAM* vgmstream = NULL;
char filename[PATH_LIMIT];
coding_t coding_type;
size_t wave_length;
int codec;
int channel_count;
int channels;
int loop_flag;
int rwar = 0;
int rwav = 0;
struct rwav_data rwav_data;
rwav_data_t rwav_data;
size_t stream_size;
int big_endian = 1;
int32_t (*read_32bit)(off_t,STREAMFILE*) = NULL;
int16_t (*read_16bit)(off_t,STREAMFILE*) = NULL;
@ -118,44 +88,29 @@ VGMSTREAM * init_vgmstream_rwsd(STREAMFILE *streamFile) {
rwav_data.wave_offset = -1;
/* check extension, case insensitive */
streamFile->get_name(streamFile,filename,sizeof(filename));
sf->get_name(sf,filename,sizeof(filename));
if (check_extensions(streamFile, "rwsd")) {
if (check_extensions(sf, "rwsd")) {
;
}
else if (check_extensions(streamFile, "rwar")) {
else if (check_extensions(sf, "rwar")) {
rwar = 1;
}
else if (check_extensions(streamFile, "rwav")) {
else if (check_extensions(sf, "rwav")) {
rwav = 1;
}
/* .bcwav: standard 3DS
* .bms: 3D Classics Kirby's Adventure (3DS)
* .sfx: Wizdom (3DS)
* .str: Pac-Man and the Ghostly Adventures 2 (3DS)
* .zic: Wizdom (3DS) */
else if (check_extensions(streamFile, "bcwav,bms,sfx,str,zic")) {
rwav = 1; // cwav, similar to little endian rwav
big_endian = 0;
}
else {
goto fail;
}
if (big_endian) {
read_16bit = read_16bitBE;
read_32bit = read_32bitBE;
}
else {
read_16bit = read_16bitLE;
read_32bit = read_32bitLE;
}
read_16bit = read_16bitBE;
read_32bit = read_32bitBE;
/* check header */
if (rwar || rwav) {
rwav_data.offset = 0;
rwav_data.streamFile = streamFile;
rwav_data.big_endian = big_endian;
rwav_data.sf = sf;
rwav_data.read_32bit = read_32bit;
if (rwar) read_rwar(&rwav_data);
@ -163,33 +118,34 @@ VGMSTREAM * init_vgmstream_rwsd(STREAMFILE *streamFile) {
if (rwav_data.wave_offset < 0) goto fail;
}
else {
if ((uint32_t)read_32bitBE(0,streamFile)!=0x52575344) /* "RWSD" */
if (!is_id32be(0x00, sf, "RWSD"))
goto fail;
switch (read_32bitBE(4,streamFile)) {
switch (read_u32be(0x04, sf)) {
case 0xFEFF0102:
/* ideally we would look through the chunk list for a WAVE chunk,
* but it's always in the same order */
/* get WAVE offset, check */
rwav_data.wave_offset = read_32bit(0x18,streamFile);
if ((uint32_t)read_32bitBE(rwav_data.wave_offset,streamFile)!=0x57415645) /* "WAVE" */
rwav_data.wave_offset = read_32bit(0x18,sf);
if (!is_id32be(0x00, sf, "WAVE"))
goto fail;
/* get WAVE size, check */
wave_length = read_32bit(0x1c,streamFile);
if (read_32bit(rwav_data.wave_offset+4,streamFile)!=wave_length)
wave_length = read_32bit(0x1c,sf);
if (read_32bit(rwav_data.wave_offset + 0x04,sf) != wave_length)
goto fail;
/* check wave count */
if (read_32bit(rwav_data.wave_offset+8,streamFile) != 1)
if (read_32bit(rwav_data.wave_offset + 0x08,sf) != 1)
goto fail; /* only support 1 */
rwav_data.version = 2;
break;
case 0xFEFF0103:
rwav_data.offset = 0xe0;
rwav_data.streamFile = streamFile;
rwav_data.big_endian = big_endian;
rwav_data.sf = sf;
rwav_data.read_32bit = read_32bit;
read_rwar(&rwav_data);
@ -204,126 +160,87 @@ VGMSTREAM * init_vgmstream_rwsd(STREAMFILE *streamFile) {
}
/* get type details */
codec = read_8bit(rwav_data.wave_offset+0x10,streamFile);
loop_flag = read_8bit(rwav_data.wave_offset+0x11,streamFile);
if (big_endian)
channel_count = read_8bit(rwav_data.wave_offset+0x12,streamFile);
else
channel_count = read_32bit(rwav_data.wave_offset+0x24,streamFile);
codec = read_u8(rwav_data.wave_offset+0x10,sf);
loop_flag = read_u8(rwav_data.wave_offset+0x11,sf);
channels = read_u8(rwav_data.wave_offset+0x12,sf);
/* build the VGMSTREAM */
vgmstream = allocate_vgmstream(channels,loop_flag);
if (!vgmstream) goto fail;
vgmstream->num_samples = dsp_nibbles_to_samples(read_32bit(rwav_data.wave_offset+0x1c,sf));
vgmstream->loop_start_sample = dsp_nibbles_to_samples(read_32bit(rwav_data.wave_offset+0x18,sf));
vgmstream->sample_rate = (uint16_t)read_16bit(rwav_data.wave_offset + 0x14,sf);
vgmstream->loop_end_sample = vgmstream->num_samples;
switch (codec) {
case 0:
coding_type = coding_PCM8;
vgmstream->coding_type = coding_PCM8;
break;
case 1:
if (big_endian)
coding_type = coding_PCM16BE;
else
coding_type = coding_PCM16LE;
vgmstream->coding_type = coding_PCM16BE;
break;
case 2:
coding_type = coding_NGC_DSP;
break;
case 3:
coding_type = coding_3DS_IMA;
vgmstream->coding_type = coding_NGC_DSP;
break;
default:
goto fail;
}
if (channel_count < 1) goto fail;
/* build the VGMSTREAM */
vgmstream = allocate_vgmstream(channel_count,loop_flag);
if (!vgmstream) goto fail;
if (big_endian) {
vgmstream->num_samples = dsp_nibbles_to_samples(read_32bit(rwav_data.wave_offset+0x1c,streamFile));
vgmstream->loop_start_sample = dsp_nibbles_to_samples(read_32bit(rwav_data.wave_offset+0x18,streamFile));
}
else {
vgmstream->num_samples = read_32bit(rwav_data.wave_offset+0x1c,streamFile);
vgmstream->loop_start_sample = read_32bit(rwav_data.wave_offset+0x18,streamFile);
}
vgmstream->sample_rate = (uint16_t)read_16bit(rwav_data.wave_offset+0x14,streamFile);
vgmstream->loop_end_sample = vgmstream->num_samples;
vgmstream->coding_type = coding_type;
vgmstream->layout_type = layout_none;
if (rwar)
if (rwar) {
vgmstream->meta_type = meta_RWAR;
else if (rwav) {
if (big_endian) {
vgmstream->meta_type = meta_RWAV;
}
else {
vgmstream->meta_type = meta_CWAV;
vgmstream->allow_dual_stereo = 1; /* LEGO 3DS games */
}
}
else
else if (rwav) {
vgmstream->meta_type = meta_RWAV;
}
else {
vgmstream->meta_type = meta_RWSD;
}
{
off_t data_start_offset;
off_t codec_info_offset;
int i,j;
int i, j;
for (j=0;j<vgmstream->channels;j++) {
if (rwar || rwav)
{
if (big_endian)
{
for (j = 0 ; j < vgmstream->channels; j++) {
if (rwar || rwav) {
/* This is pretty nasty, so an explaination is in order.
* At 0x10 in the info_chunk is the offset of a table with
* one entry per channel. Each entry in this table is itself
* an offset to a set of information for the channel. The
* first element in the set is the offset into DATA of the
* channel.
* The second element is the
* offset of the codec-specific setup for the channel. */
* At 0x10 in the info_chunk is the offset of a table with
* one entry per channel. Each entry in this table is itself
* an offset to a set of information for the channel. The
* first element in the set is the offset into DATA of the channel.
* The second element is the offset of the codec-specific setup for the channel. */
off_t channel_info_offset;
channel_info_offset = rwav_data.info_chunk +
read_32bit(rwav_data.info_chunk+
read_32bit(rwav_data.info_chunk+0x10,streamFile)+j*4,
streamFile);
off_t channel_info_offset = rwav_data.info_chunk +
read_32bit(rwav_data.info_chunk +
read_32bit(rwav_data.info_chunk + 0x10,sf) + j*0x04, sf);
data_start_offset = rwav_data.start_offset +
read_32bit(channel_info_offset+0, streamFile);
read_32bit(channel_info_offset + 0x00, sf);
codec_info_offset = rwav_data.info_chunk +
read_32bit(channel_info_offset+4, streamFile);
}
read_32bit(channel_info_offset + 0x04, sf);
else
{
// CWAV uses some relative offsets
off_t cur_pos = rwav_data.info_chunk + 0x14; // channel count
cur_pos = cur_pos + read_32bit(cur_pos + 4 + j*8 + 4,streamFile);
vgmstream->ch[j].channel_start_offset =
vgmstream->ch[j].offset = data_start_offset;
// size is at cur_pos + 4
data_start_offset = rwav_data.start_offset + read_32bit(cur_pos + 4, streamFile);
// codec-specific info is at cur_pos + 0xC
codec_info_offset = cur_pos + read_32bit(cur_pos + 0xC,streamFile);
}
vgmstream->ch[j].channel_start_offset=
vgmstream->ch[j].offset=data_start_offset;
} else {
// dummy for RWSD, must be a proper way to work this out
codec_info_offset=rwav_data.wave_offset+0x6c+j*0x30;
codec_info_offset = rwav_data.wave_offset + 0x6c + j*0x30;
}
if (vgmstream->coding_type == coding_NGC_DSP) {
for (i=0;i<16;i++) {
vgmstream->ch[j].adpcm_coef[i]=read_16bit(codec_info_offset+i*2,streamFile);
for (i = 0; i < 16; i++) {
vgmstream->ch[j].adpcm_coef[i] = read_16bit(codec_info_offset + i*0x2, sf);
}
}
if (vgmstream->coding_type == coding_3DS_IMA) {
vgmstream->ch[j].adpcm_history1_16 = read_16bit(codec_info_offset,streamFile);
vgmstream->ch[j].adpcm_step_index = read_16bit(codec_info_offset+2,streamFile);
vgmstream->ch[j].adpcm_history1_16 = read_16bit(codec_info_offset + 0x00,sf);
vgmstream->ch[j].adpcm_step_index = read_16bit(codec_info_offset + 0x02,sf);
}
}
}
@ -333,15 +250,16 @@ VGMSTREAM * init_vgmstream_rwsd(STREAMFILE *streamFile) {
}
else {
if (rwav_data.version == 2)
rwav_data.start_offset = read_32bit(8,streamFile);
rwav_data.start_offset = read_32bit(0x08, sf);
}
stream_size = read_32bit(rwav_data.wave_offset+0x50,streamFile);
stream_size = read_32bit(rwav_data.wave_offset + 0x50,sf);
/* open the file for reading by each channel */
{
int i;
for (i=0;i<channel_count;i++) {
vgmstream->ch[i].streamfile = streamFile->open(streamFile,filename,STREAMFILE_DEFAULT_BUFFER_SIZE);
for (i=0;i<channels;i++) {
vgmstream->ch[i].streamfile = sf->open(sf,filename,STREAMFILE_DEFAULT_BUFFER_SIZE);
if (!vgmstream->ch[i].streamfile) goto fail;
@ -356,8 +274,7 @@ VGMSTREAM * init_vgmstream_rwsd(STREAMFILE *streamFile) {
return vgmstream;
/* clean up anything we may have opened */
fail:
if (vgmstream) close_vgmstream(vgmstream);
close_vgmstream(vgmstream);
return NULL;
}

View File

@ -461,7 +461,7 @@ VGMSTREAM* init_vgmstream_txth(STREAMFILE* sf) {
{
int16_t (*read_16bit)(off_t, STREAMFILE*) = txth.coef_big_endian ? read_16bitBE : read_16bitLE;
int16_t (*get_16bit)(const uint8_t* p) = txth.coef_big_endian ? get_16bitBE : get_16bitLE;
VGM_LOG("coef=%x\n",txth.coef_offset );
for (i = 0; i < vgmstream->channels; i++) {
if (txth.coef_mode == 0) { /* normal coefs */
for (j = 0; j < 16; j++) {

View File

@ -26,7 +26,7 @@ VGMSTREAM* init_vgmstream_ubi_ckd_cwav(STREAMFILE* sf) {
temp_sf = setup_ubi_ckd_cwav_streamfile(sf);
if (!temp_sf) goto fail;
vgmstream = init_vgmstream_rwsd(temp_sf);
vgmstream = init_vgmstream_bcwav(temp_sf);
if (!vgmstream) goto fail;
return vgmstream;

View File

@ -38,8 +38,9 @@ VGMSTREAM* init_vgmstream_xa(STREAMFILE* sf) {
* .str: often videos and sometimes speech/music
* .pxa: Mortal Kombat 4 (PS1)
* .grn: Micro Machines (CDi)
* .an2: Croc (PS1) movies
* (extensionless): bigfiles [Castlevania: Symphony of the Night (PS1)] */
if (!check_extensions(sf,"xa,str,pxa,grn,"))
if (!check_extensions(sf,"xa,str,pxa,grn,an2,"))
goto fail;
/* Proper XA comes in raw (BIN 2352 mode2/form2) CD sectors, that contain XA subheaders.
@ -78,11 +79,11 @@ VGMSTREAM* init_vgmstream_xa(STREAMFILE* sf) {
case 1: bps = 8; break; /* Micro Machines (CDi) */
default: goto fail;
}
switch((xa_header >> 6) & 1) { /* 6: emphasis (applies a filter) */
switch((xa_header >> 6) & 1) { /* 6: emphasis flag (should apply a de-emphasis filter) */
case 0: break;
default: /* shouldn't be used by games */
vgm_logi("XA: unknown emphasis found\n");
goto fail;
default: /* very rare, waveform looks ok so maybe not needed [Croc (PS1) PACKx.str] */
vgm_logi("XA: emphasis found\n");
break;
}
switch((xa_header >> 7) & 1) { /* 7: reserved */
case 0: break;

View File

@ -244,7 +244,7 @@ void reset_layout(VGMSTREAM* vgmstream) {
}
}
static int render_layout(sample_t* buf, int32_t sample_count, VGMSTREAM* vgmstream) {
int render_layout(sample_t* buf, int32_t sample_count, VGMSTREAM* vgmstream) {
/* current_sample goes between loop points (if looped) or up to max samples,
* must detect beyond that decoders would encounter garbage data */
@ -509,243 +509,3 @@ int render_vgmstream(sample_t* buf, int32_t sample_count, VGMSTREAM* vgmstream)
return samples_done;
}
/*****************************************************************************/
static void seek_force_loop(VGMSTREAM* vgmstream, int loop_count) {
/* only called after hit loop */
if (!vgmstream->hit_loop)
return;
/* pretend decoder reached loop end so state is set to loop start */
vgmstream->loop_count = loop_count - 1; /* seeking to first loop must become ++ > 0 */
vgmstream->current_sample = vgmstream->loop_end_sample;
vgmstream_do_loop(vgmstream);
}
static void seek_force_decode(VGMSTREAM* vgmstream, int samples) {
sample_t* tmpbuf = vgmstream->tmpbuf;
size_t tmpbuf_size = vgmstream->tmpbuf_size;
int32_t buf_samples = tmpbuf_size / vgmstream->channels; /* base channels, no need to apply mixing */
while (samples) {
int to_do = samples;
if (to_do > buf_samples)
to_do = buf_samples;
render_layout(tmpbuf, to_do, vgmstream);
/* no mixing */
samples -= to_do;
}
}
void seek_vgmstream(VGMSTREAM* vgmstream, int32_t seek_sample) {
play_state_t* ps = &vgmstream->pstate;
int play_forever = vgmstream->config.play_forever;
int32_t decode_samples = 0;
int loop_count = -1;
int is_looped = vgmstream->loop_flag || vgmstream->loop_target > 0; /* loop target disabled loop flag during decode */
/* cleanup */
if (seek_sample < 0)
seek_sample = 0;
/* play forever can seek past max */
if (vgmstream->config_enabled && seek_sample > ps->play_duration && !play_forever)
seek_sample = ps->play_duration;
#if 0 //todo move below, needs to clamp in decode part
/* optimize as layouts can seek faster internally */
if (vgmstream->layout_type == layout_segmented) {
seek_layout_segmented(vgmstream, seek_sample);
if (vgmstream->config_enabled) {
vgmstream->pstate.play_position = seek_sample;
}
return;
}
else if (vgmstream->layout_type == layout_layered) {
seek_layout_layered(vgmstream, seek_sample);
if (vgmstream->config_enabled) {
vgmstream->pstate.play_position = seek_sample;
}
return;
}
#endif
/* will decode and loop until seek sample, but slower */
//todo apply same loop logic as below, or pretend we have play_forever + settings?
if (!vgmstream->config_enabled) {
//;VGM_LOG("SEEK: simple seek=%i, cur=%i\n", seek_sample, vgmstream->current_sample);
if (seek_sample < vgmstream->current_sample) {
decode_samples = seek_sample;
reset_vgmstream(vgmstream);
}
else {
decode_samples = seek_sample - vgmstream->current_sample;
}
seek_force_decode(vgmstream, decode_samples);
return;
}
//todo could improve performance bit if hit_loop wasn't lost when calling reset
//todo wrong seek with ignore fade, also for layered layers (pass count to force loop + layers)
/* seeking to requested sample normally means decoding and discarding up to that point (from
* the beginning, or current position), but can be optimized a bit to decode less with some tricks:
* - seek may fall in part of the song that isn't actually decoding (due to config, like padding)
* - if file loops there is no need to decode N full loops, seek can be set relative to loop region
* - can decode to seek sample from current position or loop start depending on lowest
*
* some of the cases below could be simplified but the logic to get this going is kinda mind-bending
*
* (ex. with file = 100, pad=5s, trim=3s, loop=20s..90s)
* | pad-begin | body-begin | body-loop0 | body-loop1 | body-loop2 | fade | pad-end + beyond)
* 0 5s (-3s) 25s 95s 165s 235s 245s Ns
*/
//;VGM_LOG("SEEK: seek sample=%i, is_looped=%i\n", seek_sample, is_looped);
/* start/pad-begin: consume pad samples */
if (seek_sample < ps->pad_begin_duration) {
/* seek=3: pad=5-3=2 */
decode_samples = 0;
reset_vgmstream(vgmstream);
ps->pad_begin_left = ps->pad_begin_duration - seek_sample;
//;VGM_LOG("SEEK: pad start / dec=%i\n", decode_samples);
}
/* body: find position relative to decoder's current sample */
else if (play_forever || seek_sample < ps->pad_begin_duration + ps->body_duration + ps->fade_duration) {
/* seek=10 would be seekr=10-5+3=8 inside decoder */
int32_t seek_relative = seek_sample - ps->pad_begin_duration + ps->trim_begin_duration;
//;VGM_LOG("SEEK: body / seekr=%i, curr=%i\n", seek_relative, vgmstream->current_sample);
/* seek can be in some part of the body, depending on looped/decoder's current/etc */
if (!is_looped && seek_relative < vgmstream->current_sample) {
/* seekr=50s, curr=95 > restart + decode=50s */
decode_samples = seek_relative;
reset_vgmstream(vgmstream);
//;VGM_LOG("SEEK: non-loop reset / dec=%i\n", decode_samples);
}
else if (!is_looped && seek_relative < vgmstream->num_samples) {
/* seekr=95s, curr=50 > decode=95-50=45s */
decode_samples = seek_relative - vgmstream->current_sample;
//;VGM_LOG("SEEK: non-loop forward / dec=%i\n", decode_samples);
}
else if (!is_looped) {
/* seekr=120s (outside decode, can happen when body is set manually) */
decode_samples = 0;
vgmstream->current_sample = vgmstream->num_samples + 1;
//;VGM_LOG("SEEK: non-loop silence / dec=%i\n", decode_samples);
}
else if (seek_relative < vgmstream->loop_start_sample) {
/* seekr=6s > 6-5+3 > seek=4s inside decoder < 20s: decode 4s from start, or 1s if current was at 3s */
if (seek_relative < vgmstream->current_sample) {
/* seekr=9s, current=10s > decode=9s from start */
decode_samples = seek_relative;
reset_vgmstream(vgmstream);
//;VGM_LOG("SEEK: loop start reset / dec=%i\n", decode_samples);
}
else {
/* seekr=9s, current=8s > decode=1s from current */
decode_samples = seek_relative - vgmstream->current_sample;
//;VGM_LOG("SEEK: loop start forward / dec=%i\n", decode_samples);
}
}
else {
/* seek can be clamped between loop parts (relative to decoder's current_sample) to minimize decoding */
int32_t loop_body, loop_seek, loop_curr;
/* current must have reached loop start at some point */
if (!vgmstream->hit_loop) {
int32_t skip_samples;
if (vgmstream->current_sample >= vgmstream->loop_start_sample) {
VGM_LOG("SEEK: bad current sample %i vs %i\n", vgmstream->current_sample, vgmstream->loop_start_sample);
reset_vgmstream(vgmstream);
}
skip_samples = (vgmstream->loop_start_sample - vgmstream->current_sample);
//;VGM_LOG("SEEK: must loop / skip=%i, curr=%i\n", skip_samples, vgmstream->current_sample);
seek_force_decode(vgmstream, skip_samples);
}
/* current must be in loop area (shouldn't happen?) */
if (vgmstream->current_sample < vgmstream->loop_start_sample
|| vgmstream->current_sample < vgmstream->loop_end_sample) {
//;VGM_LOG("SEEK: current outside loop area / curr=%i, ls=%i, le=%i\n", vgmstream->current_sample, vgmstream->current_sample, vgmstream->loop_end_sample);
seek_force_loop(vgmstream, 0);
}
loop_body = (vgmstream->loop_end_sample - vgmstream->loop_start_sample);
loop_seek = seek_relative - vgmstream->loop_start_sample;
loop_count = loop_seek / loop_body;
loop_seek = loop_seek % loop_body;
loop_curr = vgmstream->current_sample - vgmstream->loop_start_sample;
/* when "ignore fade" is used and seek falls into non-fade part, this needs to seek right before it
so when calling seek_force_loop detection kicks in, and non-fade then decodes normally */
if (vgmstream->loop_target && vgmstream->loop_target == loop_count) {
loop_seek = loop_body;
}
//;VGM_LOG("SEEK: in loop / seekl=%i, loops=%i, cur=%i, dec=%i\n", loop_seek, loop_count, loop_curr, decode_samples);
if (loop_seek < loop_curr) {
decode_samples += loop_seek;
seek_force_loop(vgmstream, loop_count);
//;VGM_LOG("SEEK: loop reset / dec=%i, loop=%i\n", decode_samples, loop_count);
}
else {
decode_samples += (loop_seek - loop_curr);
//;VGM_LOG("SEEK: loop forward / dec=%i, loop=%i\n", decode_samples, loop_count);
}
/* adjust fade if seek ends in fade region */
if (!play_forever
&& seek_sample >= ps->pad_begin_duration + ps->body_duration
&& seek_sample < ps->pad_begin_duration + ps->body_duration + ps->fade_duration) {
ps->fade_left = ps->pad_begin_duration + ps->body_duration + ps->fade_duration - seek_sample;
//;VGM_LOG("SEEK: in fade / fade=%i, %i\n", ps->fade_left, ps->fade_duration);
}
}
/* done at the end in case of reset */
ps->pad_begin_left = 0;
ps->trim_begin_left = 0;
}
/* pad end and beyond: ignored */
else {
decode_samples = 0;
ps->pad_begin_left = 0;
ps->trim_begin_left = 0;
if (!is_looped)
vgmstream->current_sample = vgmstream->num_samples + 1;
//;VGM_LOG("SEEK: end silence / dec=%i\n", decode_samples);
/* looping decoder state isn't changed (seek backwards could use current sample) */
}
seek_force_decode(vgmstream, decode_samples);
vgmstream->pstate.play_position = seek_sample;
}

View File

@ -5,6 +5,7 @@
void free_layout(VGMSTREAM* vgmstream);
void reset_layout(VGMSTREAM* vgmstream);
int render_layout(sample_t* buf, int32_t sample_count, VGMSTREAM* vgmstream);
#endif

View File

@ -0,0 +1,245 @@
#include "vgmstream.h"
#include "layout/layout.h"
#include "render.h"
#include "decode.h"
#include "mixing.h"
#include "plugins.h"
static void seek_force_loop(VGMSTREAM* vgmstream, int loop_count) {
/* only called after hit loop */
if (!vgmstream->hit_loop)
return;
/* pretend decoder reached loop end so state is set to loop start */
vgmstream->loop_count = loop_count - 1; /* seeking to first loop must become ++ > 0 */
vgmstream->current_sample = vgmstream->loop_end_sample;
vgmstream_do_loop(vgmstream);
}
static void seek_force_decode(VGMSTREAM* vgmstream, int samples) {
sample_t* tmpbuf = vgmstream->tmpbuf;
size_t tmpbuf_size = vgmstream->tmpbuf_size;
int32_t buf_samples = tmpbuf_size / vgmstream->channels; /* base channels, no need to apply mixing */
while (samples) {
int to_do = samples;
if (to_do > buf_samples)
to_do = buf_samples;
render_layout(tmpbuf, to_do, vgmstream);
/* no mixing */
samples -= to_do;
}
}
void seek_vgmstream(VGMSTREAM* vgmstream, int32_t seek_sample) {
play_state_t* ps = &vgmstream->pstate;
int play_forever = vgmstream->config.play_forever;
int32_t decode_samples = 0;
int loop_count = -1;
int is_looped = vgmstream->loop_flag || vgmstream->loop_target > 0; /* loop target disabled loop flag during decode */
/* cleanup */
if (seek_sample < 0)
seek_sample = 0;
/* play forever can seek past max */
if (vgmstream->config_enabled && seek_sample > ps->play_duration && !play_forever)
seek_sample = ps->play_duration;
#if 0 //todo move below, needs to clamp in decode part
/* optimize as layouts can seek faster internally */
if (vgmstream->layout_type == layout_segmented) {
seek_layout_segmented(vgmstream, seek_sample);
if (vgmstream->config_enabled) {
vgmstream->pstate.play_position = seek_sample;
}
return;
}
else if (vgmstream->layout_type == layout_layered) {
seek_layout_layered(vgmstream, seek_sample);
if (vgmstream->config_enabled) {
vgmstream->pstate.play_position = seek_sample;
}
return;
}
#endif
/* will decode and loop until seek sample, but slower */
//todo apply same loop logic as below, or pretend we have play_forever + settings?
if (!vgmstream->config_enabled) {
//;VGM_LOG("SEEK: simple seek=%i, cur=%i\n", seek_sample, vgmstream->current_sample);
if (seek_sample < vgmstream->current_sample) {
decode_samples = seek_sample;
reset_vgmstream(vgmstream);
}
else {
decode_samples = seek_sample - vgmstream->current_sample;
}
seek_force_decode(vgmstream, decode_samples);
return;
}
//todo could improve performance bit if hit_loop wasn't lost when calling reset
//todo wrong seek with ignore fade, also for layered layers (pass count to force loop + layers)
/* seeking to requested sample normally means decoding and discarding up to that point (from
* the beginning, or current position), but can be optimized a bit to decode less with some tricks:
* - seek may fall in part of the song that isn't actually decoding (due to config, like padding)
* - if file loops there is no need to decode N full loops, seek can be set relative to loop region
* - can decode to seek sample from current position or loop start depending on lowest
*
* some of the cases below could be simplified but the logic to get this going is kinda mind-bending
*
* (ex. with file = 100, pad=5s, trim=3s, loop=20s..90s)
* | pad-begin | body-begin | body-loop0 | body-loop1 | body-loop2 | fade | pad-end + beyond)
* 0 5s (-3s) 25s 95s 165s 235s 245s Ns
*/
//;VGM_LOG("SEEK: seek sample=%i, is_looped=%i\n", seek_sample, is_looped);
/* start/pad-begin: consume pad samples */
if (seek_sample < ps->pad_begin_duration) {
/* seek=3: pad=5-3=2 */
decode_samples = 0;
reset_vgmstream(vgmstream);
ps->pad_begin_left = ps->pad_begin_duration - seek_sample;
//;VGM_LOG("SEEK: pad start / dec=%i\n", decode_samples);
}
/* body: find position relative to decoder's current sample */
else if (play_forever || seek_sample < ps->pad_begin_duration + ps->body_duration + ps->fade_duration) {
/* seek=10 would be seekr=10-5+3=8 inside decoder */
int32_t seek_relative = seek_sample - ps->pad_begin_duration + ps->trim_begin_duration;
//;VGM_LOG("SEEK: body / seekr=%i, curr=%i\n", seek_relative, vgmstream->current_sample);
/* seek can be in some part of the body, depending on looped/decoder's current/etc */
if (!is_looped && seek_relative < vgmstream->current_sample) {
/* seekr=50s, curr=95 > restart + decode=50s */
decode_samples = seek_relative;
reset_vgmstream(vgmstream);
//;VGM_LOG("SEEK: non-loop reset / dec=%i\n", decode_samples);
}
else if (!is_looped && seek_relative < vgmstream->num_samples) {
/* seekr=95s, curr=50 > decode=95-50=45s */
decode_samples = seek_relative - vgmstream->current_sample;
//;VGM_LOG("SEEK: non-loop forward / dec=%i\n", decode_samples);
}
else if (!is_looped) {
/* seekr=120s (outside decode, can happen when body is set manually) */
decode_samples = 0;
vgmstream->current_sample = vgmstream->num_samples + 1;
//;VGM_LOG("SEEK: non-loop silence / dec=%i\n", decode_samples);
}
else if (seek_relative < vgmstream->loop_start_sample) {
/* seekr=6s > 6-5+3 > seek=4s inside decoder < 20s: decode 4s from start, or 1s if current was at 3s */
if (seek_relative < vgmstream->current_sample) {
/* seekr=9s, current=10s > decode=9s from start */
decode_samples = seek_relative;
reset_vgmstream(vgmstream);
//;VGM_LOG("SEEK: loop start reset / dec=%i\n", decode_samples);
}
else {
/* seekr=9s, current=8s > decode=1s from current */
decode_samples = seek_relative - vgmstream->current_sample;
//;VGM_LOG("SEEK: loop start forward / dec=%i\n", decode_samples);
}
}
else {
/* seek can be clamped between loop parts (relative to decoder's current_sample) to minimize decoding */
int32_t loop_body, loop_seek, loop_curr;
/* current must have reached loop start at some point */
if (!vgmstream->hit_loop) {
int32_t skip_samples;
if (vgmstream->current_sample >= vgmstream->loop_start_sample) {
VGM_LOG("SEEK: bad current sample %i vs %i\n", vgmstream->current_sample, vgmstream->loop_start_sample);
reset_vgmstream(vgmstream);
}
skip_samples = (vgmstream->loop_start_sample - vgmstream->current_sample);
//;VGM_LOG("SEEK: must loop / skip=%i, curr=%i\n", skip_samples, vgmstream->current_sample);
seek_force_decode(vgmstream, skip_samples);
}
/* current must be in loop area (shouldn't happen?) */
if (vgmstream->current_sample < vgmstream->loop_start_sample
|| vgmstream->current_sample < vgmstream->loop_end_sample) {
//;VGM_LOG("SEEK: current outside loop area / curr=%i, ls=%i, le=%i\n", vgmstream->current_sample, vgmstream->current_sample, vgmstream->loop_end_sample);
seek_force_loop(vgmstream, 0);
}
loop_body = (vgmstream->loop_end_sample - vgmstream->loop_start_sample);
loop_seek = seek_relative - vgmstream->loop_start_sample;
loop_count = loop_seek / loop_body;
loop_seek = loop_seek % loop_body;
loop_curr = vgmstream->current_sample - vgmstream->loop_start_sample;
/* when "ignore fade" is used and seek falls into non-fade part, this needs to seek right before it
so when calling seek_force_loop detection kicks in, and non-fade then decodes normally */
if (vgmstream->loop_target && vgmstream->loop_target == loop_count) {
loop_seek = loop_body;
}
//;VGM_LOG("SEEK: in loop / seekl=%i, loops=%i, cur=%i, dec=%i\n", loop_seek, loop_count, loop_curr, decode_samples);
if (loop_seek < loop_curr) {
decode_samples += loop_seek;
seek_force_loop(vgmstream, loop_count);
//;VGM_LOG("SEEK: loop reset / dec=%i, loop=%i\n", decode_samples, loop_count);
}
else {
decode_samples += (loop_seek - loop_curr);
//;VGM_LOG("SEEK: loop forward / dec=%i, loop=%i\n", decode_samples, loop_count);
}
/* adjust fade if seek ends in fade region */
if (!play_forever
&& seek_sample >= ps->pad_begin_duration + ps->body_duration
&& seek_sample < ps->pad_begin_duration + ps->body_duration + ps->fade_duration) {
ps->fade_left = ps->pad_begin_duration + ps->body_duration + ps->fade_duration - seek_sample;
//;VGM_LOG("SEEK: in fade / fade=%i, %i\n", ps->fade_left, ps->fade_duration);
}
}
/* done at the end in case of reset */
ps->pad_begin_left = 0;
ps->trim_begin_left = 0;
}
/* pad end and beyond: ignored */
else {
decode_samples = 0;
ps->pad_begin_left = 0;
ps->trim_begin_left = 0;
if (!is_looped)
vgmstream->current_sample = vgmstream->num_samples + 1;
//;VGM_LOG("SEEK: end silence / dec=%i\n", decode_samples);
/* looping decoder state isn't changed (seek backwards could use current sample) */
}
seek_force_decode(vgmstream, decode_samples);
vgmstream->pstate.play_position = seek_sample;
}

View File

@ -6,6 +6,7 @@
typedef uint32_t (*read_u32_t)(off_t, STREAMFILE*);
typedef int32_t (*read_s32_t)(off_t, STREAMFILE*);
typedef uint16_t (*read_u16_t)(off_t, STREAMFILE*);
typedef int16_t (*read_s16_t)(off_t, STREAMFILE*);
typedef float (*read_f32_t)(off_t, STREAMFILE*);
//todo move here

View File

@ -5,7 +5,6 @@
#include <stdio.h>
#include <string.h>
#include <math.h>
#include <ctype.h>
#include "vgmstream.h"
#include "meta/meta.h"
#include "layout/layout.h"
@ -22,6 +21,7 @@ VGMSTREAM* (*init_vgmstream_functions[])(STREAMFILE* sf) = {
init_vgmstream_adx,
init_vgmstream_brstm,
init_vgmstream_bfwav,
init_vgmstream_bcwav,
init_vgmstream_nds_strm,
init_vgmstream_afc,
init_vgmstream_ast,
@ -556,17 +556,19 @@ VGMSTREAM* (*init_vgmstream_functions[])(STREAMFILE* sf) = {
/*****************************************************************************/
/* INIT/META */
/*****************************************************************************/
#define LOCAL_ARRAY_LENGTH(array) (sizeof(array) / sizeof(array[0]))
/* internal version with all parameters */
static VGMSTREAM* init_vgmstream_internal(STREAMFILE* sf) {
int i, fcns_size;
int i, fcns_count;
if (!sf)
return NULL;
fcns_size = (sizeof(init_vgmstream_functions)/sizeof(init_vgmstream_functions[0]));
fcns_count = LOCAL_ARRAY_LENGTH(init_vgmstream_functions);
/* try a series of formats, see which works */
for (i = 0; i < fcns_size; i++) {
for (i = 0; i < fcns_count; i++) {
/* call init function and see if valid VGMSTREAM was returned */
VGMSTREAM* vgmstream = (init_vgmstream_functions[i])(sf);
if (!vgmstream)
@ -900,264 +902,6 @@ void vgmstream_set_loop_target(VGMSTREAM* vgmstream, int loop_target) {
/* MISC */
/*******************************************************************************/
static void describe_get_time(int32_t samples, int sample_rate, double* p_time_mm, double* p_time_ss) {
double seconds = (double)samples / sample_rate;
*p_time_mm = (int)(seconds / 60.0);
*p_time_ss = seconds - *p_time_mm * 60.0f;
if (*p_time_ss >= 59.999) /* avoid round up to 60.0 when printing to %06.3f */
*p_time_ss = 59.999;
}
/* Write a description of the stream into array pointed by desc, which must be length bytes long.
* Will always be null-terminated if length > 0 */
void describe_vgmstream(VGMSTREAM* vgmstream, char* desc, int length) {
#define TEMPSIZE (256+32)
char temp[TEMPSIZE];
double time_mm, time_ss;
desc[0] = '\0';
if (!vgmstream) {
snprintf(temp,TEMPSIZE, "NULL VGMSTREAM");
concatn(length,desc,temp);
return;
}
snprintf(temp,TEMPSIZE, "sample rate: %d Hz\n", vgmstream->sample_rate);
concatn(length,desc,temp);
snprintf(temp,TEMPSIZE, "channels: %d\n", vgmstream->channels);
concatn(length,desc,temp);
{
int output_channels = 0;
mixing_info(vgmstream, NULL, &output_channels);
if (output_channels != vgmstream->channels) {
snprintf(temp,TEMPSIZE, "input channels: %d\n", vgmstream->channels); /* repeated but mainly for plugins */
concatn(length,desc,temp);
snprintf(temp,TEMPSIZE, "output channels: %d\n", output_channels);
concatn(length,desc,temp);
}
}
if (vgmstream->channel_layout) {
int cl = vgmstream->channel_layout;
/* not "channel layout: " to avoid mixups with "layout: " */
snprintf(temp,TEMPSIZE, "channel mask: 0x%x /", vgmstream->channel_layout);
concatn(length,desc,temp);
if (cl & speaker_FL) concatn(length,desc," FL");
if (cl & speaker_FR) concatn(length,desc," FR");
if (cl & speaker_FC) concatn(length,desc," FC");
if (cl & speaker_LFE) concatn(length,desc," LFE");
if (cl & speaker_BL) concatn(length,desc," BL");
if (cl & speaker_BR) concatn(length,desc," BR");
if (cl & speaker_FLC) concatn(length,desc," FLC");
if (cl & speaker_FRC) concatn(length,desc," FRC");
if (cl & speaker_BC) concatn(length,desc," BC");
if (cl & speaker_SL) concatn(length,desc," SL");
if (cl & speaker_SR) concatn(length,desc," SR");
if (cl & speaker_TC) concatn(length,desc," TC");
if (cl & speaker_TFL) concatn(length,desc," TFL");
if (cl & speaker_TFC) concatn(length,desc," TFC");
if (cl & speaker_TFR) concatn(length,desc," TFR");
if (cl & speaker_TBL) concatn(length,desc," TBL");
if (cl & speaker_TBC) concatn(length,desc," TBC");
if (cl & speaker_TBR) concatn(length,desc," TBR");
concatn(length,desc,"\n");
}
/* times mod sounds avoid round up to 60.0 */
if (vgmstream->loop_start_sample >= 0 && vgmstream->loop_end_sample > vgmstream->loop_start_sample) {
if (!vgmstream->loop_flag) {
concatn(length,desc,"looping: disabled\n");
}
describe_get_time(vgmstream->loop_start_sample, vgmstream->sample_rate, &time_mm, &time_ss);
snprintf(temp,TEMPSIZE, "loop start: %d samples (%1.0f:%06.3f seconds)\n", vgmstream->loop_start_sample, time_mm, time_ss);
concatn(length,desc,temp);
describe_get_time(vgmstream->loop_end_sample, vgmstream->sample_rate, &time_mm, &time_ss);
snprintf(temp,TEMPSIZE, "loop end: %d samples (%1.0f:%06.3f seconds)\n", vgmstream->loop_end_sample, time_mm, time_ss);
concatn(length,desc,temp);
}
describe_get_time(vgmstream->num_samples, vgmstream->sample_rate, &time_mm, &time_ss);
snprintf(temp,TEMPSIZE, "stream total samples: %d (%1.0f:%06.3f seconds)\n", vgmstream->num_samples, time_mm, time_ss);
concatn(length,desc,temp);
snprintf(temp,TEMPSIZE, "encoding: ");
concatn(length,desc,temp);
get_vgmstream_coding_description(vgmstream, temp, TEMPSIZE);
concatn(length,desc,temp);
concatn(length,desc,"\n");
snprintf(temp,TEMPSIZE, "layout: ");
concatn(length,desc,temp);
get_vgmstream_layout_description(vgmstream, temp, TEMPSIZE);
concatn(length, desc, temp);
concatn(length,desc,"\n");
if (vgmstream->layout_type == layout_interleave && vgmstream->channels > 1) {
snprintf(temp,TEMPSIZE, "interleave: %#x bytes\n", (int32_t)vgmstream->interleave_block_size);
concatn(length,desc,temp);
if (vgmstream->interleave_first_block_size && vgmstream->interleave_first_block_size != vgmstream->interleave_block_size) {
snprintf(temp,TEMPSIZE, "interleave first block: %#x bytes\n", (int32_t)vgmstream->interleave_first_block_size);
concatn(length,desc,temp);
}
if (vgmstream->interleave_last_block_size && vgmstream->interleave_last_block_size != vgmstream->interleave_block_size) {
snprintf(temp,TEMPSIZE, "interleave last block: %#x bytes\n", (int32_t)vgmstream->interleave_last_block_size);
concatn(length,desc,temp);
}
}
/* codecs with configurable frame size */
if (vgmstream->frame_size > 0 || vgmstream->interleave_block_size > 0) {
int32_t frame_size = vgmstream->frame_size > 0 ? vgmstream->frame_size : vgmstream->interleave_block_size;
switch (vgmstream->coding_type) {
case coding_MSADPCM:
case coding_MSADPCM_int:
case coding_MSADPCM_ck:
case coding_MS_IMA:
case coding_MC3:
case coding_WWISE_IMA:
case coding_REF_IMA:
case coding_PSX_cfg:
snprintf(temp,TEMPSIZE, "frame size: %#x bytes\n", frame_size);
concatn(length,desc,temp);
break;
default:
break;
}
}
snprintf(temp,TEMPSIZE, "metadata from: ");
concatn(length,desc,temp);
get_vgmstream_meta_description(vgmstream, temp, TEMPSIZE);
concatn(length,desc,temp);
concatn(length,desc,"\n");
snprintf(temp,TEMPSIZE, "bitrate: %d kbps\n", get_vgmstream_average_bitrate(vgmstream) / 1000);
concatn(length,desc,temp);
/* only interesting if more than one */
if (vgmstream->num_streams > 1) {
snprintf(temp,TEMPSIZE, "stream count: %d\n", vgmstream->num_streams);
concatn(length,desc,temp);
}
if (vgmstream->num_streams > 1) {
snprintf(temp,TEMPSIZE, "stream index: %d\n", vgmstream->stream_index == 0 ? 1 : vgmstream->stream_index);
concatn(length,desc,temp);
}
if (vgmstream->stream_name[0] != '\0') {
snprintf(temp,TEMPSIZE, "stream name: %s\n", vgmstream->stream_name);
concatn(length,desc,temp);
}
if (vgmstream->config_enabled) {
int32_t samples = vgmstream->pstate.play_duration;
describe_get_time(samples, vgmstream->sample_rate, &time_mm, &time_ss);
snprintf(temp,TEMPSIZE, "play duration: %d samples (%1.0f:%06.3f seconds)\n", samples, time_mm, time_ss);
concatn(length,desc,temp);
}
}
void describe_vgmstream_info(VGMSTREAM* vgmstream, vgmstream_info* info) {
if (!info) {
return;
}
memset(info, 0, sizeof(*info));
if (!vgmstream) {
return;
}
info->sample_rate = vgmstream->sample_rate;
info->channels = vgmstream->channels;
{
int output_channels = 0;
mixing_info(vgmstream, NULL, &output_channels);
if (output_channels != vgmstream->channels) {
info->mixing_info.input_channels = vgmstream->channels;
info->mixing_info.output_channels = output_channels;
}
}
info->channel_layout = vgmstream->channel_layout;
if (vgmstream->loop_start_sample >= 0 && vgmstream->loop_end_sample > vgmstream->loop_start_sample) {
info->loop_info.start = vgmstream->loop_start_sample;
info->loop_info.end = vgmstream->loop_end_sample;
}
info->num_samples = vgmstream->num_samples;
get_vgmstream_coding_description(vgmstream, info->encoding, sizeof(info->encoding));
get_vgmstream_layout_description(vgmstream, info->layout, sizeof(info->layout));
if (vgmstream->layout_type == layout_interleave && vgmstream->channels > 1) {
info->interleave_info.value = vgmstream->interleave_block_size;
if (vgmstream->interleave_first_block_size && vgmstream->interleave_first_block_size != vgmstream->interleave_block_size) {
info->interleave_info.first_block = vgmstream->interleave_first_block_size;
}
if (vgmstream->interleave_last_block_size && vgmstream->interleave_last_block_size != vgmstream->interleave_block_size) {
info->interleave_info.last_block = vgmstream->interleave_last_block_size;
}
}
/* codecs with configurable frame size */
if (vgmstream->frame_size > 0 || vgmstream->interleave_block_size > 0) {
int32_t frame_size = vgmstream->frame_size > 0 ? vgmstream->frame_size : vgmstream->interleave_block_size;
switch (vgmstream->coding_type) {
case coding_MSADPCM:
case coding_MSADPCM_int:
case coding_MSADPCM_ck:
case coding_MS_IMA:
case coding_MC3:
case coding_WWISE_IMA:
case coding_REF_IMA:
case coding_PSX_cfg:
info->frame_size = frame_size;
break;
default:
break;
}
}
get_vgmstream_meta_description(vgmstream, info->metadata, sizeof(info->metadata));
info->bitrate = get_vgmstream_average_bitrate(vgmstream);
/* only interesting if more than one */
if (vgmstream->num_streams > 1) {
info->stream_info.total = vgmstream->num_streams;
}
else {
info->stream_info.total = 1;
}
if (vgmstream->num_streams > 1) {
info->stream_info.current = vgmstream->stream_index == 0 ? 1 : vgmstream->stream_index;
}
if (vgmstream->stream_name[0] != '\0') {
snprintf(info->stream_info.name, sizeof(info->stream_info.name), "%s", vgmstream->stream_name);
}
}
/* See if there is a second file which may be the second channel, given an already opened mono vgmstream.
* If a suitable file is found, open it and change opened_vgmstream to a stereo vgmstream. */
static void try_dual_file_stereo(VGMSTREAM* opened_vgmstream, STREAMFILE* sf, VGMSTREAM*(*init_vgmstream_function)(STREAMFILE*)) {
@ -1326,6 +1070,8 @@ static void try_dual_file_stereo(VGMSTREAM* opened_vgmstream, STREAMFILE* sf, VG
/* stereo! */
opened_vgmstream->channels = 2;
if (opened_vgmstream->layout_type == layout_interleave)
opened_vgmstream->layout_type = layout_none; /* fixes some odd cases */
/* discard the second VGMSTREAM */
mixing_close(new_vgmstream);
@ -1342,184 +1088,6 @@ fail:
return;
}
/*******************************************************************************/
/* BITRATE */
/*******************************************************************************/
#define BITRATE_FILES_MAX 128 /* arbitrary max, but +100 segments have been observed */
typedef struct {
uint32_t hash[BITRATE_FILES_MAX]; /* already used streamfiles */
int subsong[BITRATE_FILES_MAX]; /* subsongs of those streamfiles (could be incorporated to the hash?) */
int count;
int count_max;
} bitrate_info_t;
static uint32_t hash_sf(STREAMFILE* sf) {
int i;
char path[PATH_LIMIT];
uint32_t hash = 2166136261;
get_streamfile_name(sf, path, sizeof(path));
/* our favorite garbo hash a.k.a FNV-1 32b */
i = 0;
while (path[i] != '\0') {
char c = tolower(path[i]);
hash = (hash * 16777619) ^ (uint8_t)c;
i++;
}
return hash;
}
/* average bitrate helper to get STREAMFILE for a channel, since some codecs may use their own */
static STREAMFILE* get_vgmstream_average_bitrate_channel_streamfile(VGMSTREAM* vgmstream, int channel) {
if (vgmstream->coding_type == coding_NWA) {
return nwa_get_streamfile(vgmstream->codec_data);
}
if (vgmstream->coding_type == coding_ACM) {
return acm_get_streamfile(vgmstream->codec_data);
}
if (vgmstream->coding_type == coding_COMPRESSWAVE) {
return compresswave_get_streamfile(vgmstream->codec_data);
}
#ifdef VGM_USE_VORBIS
if (vgmstream->coding_type == coding_OGG_VORBIS) {
return ogg_vorbis_get_streamfile(vgmstream->codec_data);
}
#endif
if (vgmstream->coding_type == coding_CRI_HCA) {
return hca_get_streamfile(vgmstream->codec_data);
}
#ifdef VGM_USE_FFMPEG
if (vgmstream->coding_type == coding_FFmpeg) {
return ffmpeg_get_streamfile(vgmstream->codec_data);
}
#endif
#if defined(VGM_USE_MP4V2) && defined(VGM_USE_FDKAAC)
if (vgmstream->coding_type == coding_MP4_AAC) {
mp4_aac_codec_data *data = vgmstream->codec_data;
return data ? data->if_file.streamfile : NULL;
}
#endif
return vgmstream->ch[channel].streamfile;
}
static int get_vgmstream_file_bitrate_from_size(size_t size, int sample_rate, int32_t length_samples) {
if (sample_rate == 0 || length_samples == 0) return 0;
if (length_samples < 100) return 0; /* ignore stupid bitrates caused by some segments */
return (int)((int64_t)size * 8 * sample_rate / length_samples);
}
static int get_vgmstream_file_bitrate_from_streamfile(STREAMFILE* sf, int sample_rate, int32_t length_samples) {
if (sf == NULL) return 0;
return get_vgmstream_file_bitrate_from_size(get_streamfile_size(sf), sample_rate, length_samples);
}
static int get_vgmstream_file_bitrate_main(VGMSTREAM* vgmstream, bitrate_info_t* br, int* p_uniques) {
int i, ch;
int bitrate = 0;
/* Recursively get bitrate and fill the list of streamfiles if needed (to filter),
* since layouts can include further vgmstreams that may also share streamfiles.
*
* Because of how data, layers and segments can be combined it's possible to
* fool this in various ways; metas should report stream_size in complex cases
* to get accurate bitrates (particularly for subsongs). An edge case is when
* segments use only a few samples from a full file (like Wwise transitions), bitrates
* become a bit high since its hard to detect only part of the file is needed. */
if (vgmstream->layout_type == layout_segmented) {
int uniques = 0;
segmented_layout_data *data = (segmented_layout_data *) vgmstream->layout_data;
for (i = 0; i < data->segment_count; i++) {
bitrate += get_vgmstream_file_bitrate_main(data->segments[i], br, &uniques);
}
if (uniques)
bitrate /= uniques; /* average */
}
else if (vgmstream->layout_type == layout_layered) {
layered_layout_data *data = vgmstream->layout_data;
for (i = 0; i < data->layer_count; i++) {
bitrate += get_vgmstream_file_bitrate_main(data->layers[i], br, NULL);
}
}
else {
/* Add channel bitrate if streamfile hasn't been used before, so bitrate doesn't count repeats
* (like same STREAMFILE reopened per channel, also considering SFs may be wrapped). */
for (ch = 0; ch < vgmstream->channels; ch++) {
uint32_t hash_cur;
int subsong_cur;
STREAMFILE* sf_cur;
int is_unique = 1; /* default to "no other SFs exist" */
/* compares paths (hashes for faster compares) + subsongs (same file + different subsong = "different" file) */
sf_cur = get_vgmstream_average_bitrate_channel_streamfile(vgmstream, ch);
if (!sf_cur) continue;
hash_cur = hash_sf(sf_cur);
subsong_cur = vgmstream->stream_index;
for (i = 0; i < br->count; i++) {
uint32_t hash_cmp = br->hash[i];
int subsong_cmp = br->subsong[i];
if (hash_cur == hash_cmp && subsong_cur == subsong_cmp) {
is_unique = 0;
break;
}
}
if (is_unique) {
size_t stream_size;
if (br->count >= br->count_max) goto fail;
if (vgmstream->stream_size) {
/* stream_size applies to both channels but should add once and detect repeats (for current subsong) */
stream_size = get_vgmstream_file_bitrate_from_size(vgmstream->stream_size, vgmstream->sample_rate, vgmstream->num_samples);
}
else {
stream_size = get_vgmstream_file_bitrate_from_streamfile(sf_cur, vgmstream->sample_rate, vgmstream->num_samples);
}
/* possible in cases like using silence codec */
if (!stream_size)
break;
br->hash[br->count] = hash_cur;
br->subsong[br->count] = subsong_cur;
br->count++;
if (p_uniques)
(*p_uniques)++;
bitrate += stream_size;
break;
}
}
}
return bitrate;
fail:
return 0;
}
/* Return the average bitrate in bps of all unique data contained within this stream.
* This is the bitrate of the *file*, as opposed to the bitrate of the *codec*, meaning
* it counts extra data like block headers and padding. While this can be surprising
* sometimes (as it's often higher than common codec bitrates) it isn't wrong per se. */
int get_vgmstream_average_bitrate(VGMSTREAM* vgmstream) {
bitrate_info_t br = {0};
br.count_max = BITRATE_FILES_MAX;
return get_vgmstream_file_bitrate_main(vgmstream, &br, NULL);
}
/**
* Inits vgmstream, doing two things: