From c05dc28a8d3830aed25edfd5cf8cb4face10e196 Mon Sep 17 00:00:00 2001 From: Christopher Snowhill Date: Tue, 18 Jan 2022 03:13:34 -0800 Subject: [PATCH] Updated VGMStream to r1702-8-gb1325840 --- .../libvgmstream.xcodeproj/project.pbxproj | 8 + Frameworks/vgmstream/vgmstream/src/decode.c | 8 +- Frameworks/vgmstream/vgmstream/src/formats.c | 2 +- Frameworks/vgmstream/vgmstream/src/info.c | 447 +++++++++++++++++ Frameworks/vgmstream/vgmstream/src/meta/awb.c | 2 +- .../vgmstream/vgmstream/src/meta/bfwav.c | 246 ++++++---- .../vgmstream/vgmstream/src/meta/bwav.c | 70 +-- Frameworks/vgmstream/vgmstream/src/meta/cpk.c | 2 +- .../vgmstream/vgmstream/src/meta/meta.h | 3 +- .../vgmstream/vgmstream/src/meta/rwsd.c | 279 ++++------- .../vgmstream/vgmstream/src/meta/txth.c | 2 +- .../vgmstream/src/meta/ubi_ckd_cwav.c | 2 +- Frameworks/vgmstream/vgmstream/src/meta/xa.c | 11 +- Frameworks/vgmstream/vgmstream/src/render.c | 242 +--------- Frameworks/vgmstream/vgmstream/src/render.h | 1 + Frameworks/vgmstream/vgmstream/src/seek.c | 245 ++++++++++ .../vgmstream/vgmstream/src/util/endianness.h | 1 + .../vgmstream/vgmstream/src/vgmstream.c | 448 +----------------- 18 files changed, 1038 insertions(+), 981 deletions(-) create mode 100644 Frameworks/vgmstream/vgmstream/src/info.c create mode 100644 Frameworks/vgmstream/vgmstream/src/seek.c diff --git a/Frameworks/vgmstream/libvgmstream.xcodeproj/project.pbxproj b/Frameworks/vgmstream/libvgmstream.xcodeproj/project.pbxproj index db7dc66eb..97ab84660 100644 --- a/Frameworks/vgmstream/libvgmstream.xcodeproj/project.pbxproj +++ b/Frameworks/vgmstream/libvgmstream.xcodeproj/project.pbxproj @@ -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 = ""; }; 8346D98125BF83B300D1A8B0 /* compresswave_decoder.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = compresswave_decoder.c; sourceTree = ""; }; 8346D98225BF83B300D1A8B0 /* compresswave_decoder_lib.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = compresswave_decoder_lib.h; sourceTree = ""; }; + 8347C7432796D76700FA8A7D /* info.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = info.c; sourceTree = ""; }; + 8347C7472796D76700FA8A7D /* seek.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = seek.c; sourceTree = ""; }; 8349A8DC1FE6251E00E26435 /* vorbis_custom_utils_vid1.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = vorbis_custom_utils_vid1.c; sourceTree = ""; }; 8349A8DE1FE6251F00E26435 /* ea_mt_decoder.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = ea_mt_decoder.c; sourceTree = ""; }; 8349A8E21FE6253800E26435 /* blocked_dec.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = blocked_dec.c; sourceTree = ""; }; @@ -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 */, diff --git a/Frameworks/vgmstream/vgmstream/src/decode.c b/Frameworks/vgmstream/vgmstream/src/decode.c index 37672c3a1..0ee85100e 100644 --- a/Frameworks/vgmstream/vgmstream/src/decode.c +++ b/Frameworks/vgmstream/vgmstream/src/decode.c @@ -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! */ diff --git a/Frameworks/vgmstream/vgmstream/src/formats.c b/Frameworks/vgmstream/vgmstream/src/formats.c index a599021ce..2883f8d90 100644 --- a/Frameworks/vgmstream/vgmstream/src/formats.c +++ b/Frameworks/vgmstream/vgmstream/src/formats.c @@ -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", diff --git a/Frameworks/vgmstream/vgmstream/src/info.c b/Frameworks/vgmstream/vgmstream/src/info.c new file mode 100644 index 000000000..6d711ed9e --- /dev/null +++ b/Frameworks/vgmstream/vgmstream/src/info.c @@ -0,0 +1,447 @@ +#include +#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); +} diff --git a/Frameworks/vgmstream/vgmstream/src/meta/awb.c b/Frameworks/vgmstream/vgmstream/src/meta/awb.c index 177ca3664..a10eb741b 100644 --- a/Frameworks/vgmstream/vgmstream/src/meta/awb.c +++ b/Frameworks/vgmstream/vgmstream/src/meta/awb.c @@ -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 && diff --git a/Frameworks/vgmstream/vgmstream/src/meta/bfwav.c b/Frameworks/vgmstream/vgmstream/src/meta/bfwav.c index 679aa7c69..74d6724e7 100644 --- a/Frameworks/vgmstream/vgmstream/src/meta/bfwav.c +++ b/Frameworks/vgmstream/vgmstream/src/meta/bfwav.c @@ -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: diff --git a/Frameworks/vgmstream/vgmstream/src/meta/bwav.c b/Frameworks/vgmstream/vgmstream/src/meta/bwav.c index a0cf1df1d..12037492c 100644 --- a/Frameworks/vgmstream/vgmstream/src/meta/bwav.c +++ b/Frameworks/vgmstream/vgmstream/src/meta/bwav.c @@ -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; diff --git a/Frameworks/vgmstream/vgmstream/src/meta/cpk.c b/Frameworks/vgmstream/vgmstream/src/meta/cpk.c index b04e7d80e..f42f2b731 100644 --- a/Frameworks/vgmstream/vgmstream/src/meta/cpk.c +++ b/Frameworks/vgmstream/vgmstream/src/meta/cpk.c @@ -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) */ diff --git a/Frameworks/vgmstream/vgmstream/src/meta/meta.h b/Frameworks/vgmstream/vgmstream/src/meta/meta.h index 51375bd6e..6538d48f4 100644 --- a/Frameworks/vgmstream/vgmstream/src/meta/meta.h +++ b/Frameworks/vgmstream/vgmstream/src/meta/meta.h @@ -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); diff --git a/Frameworks/vgmstream/vgmstream/src/meta/rwsd.c b/Frameworks/vgmstream/vgmstream/src/meta/rwsd.c index 673143fe8..669d08aef 100644 --- a/Frameworks/vgmstream/vgmstream/src/meta/rwsd.c +++ b/Frameworks/vgmstream/vgmstream/src/meta/rwsd.c @@ -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;jchannels;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;ich[i].streamfile = streamFile->open(streamFile,filename,STREAMFILE_DEFAULT_BUFFER_SIZE); + for (i=0;ich[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; } diff --git a/Frameworks/vgmstream/vgmstream/src/meta/txth.c b/Frameworks/vgmstream/vgmstream/src/meta/txth.c index dd673084b..8c54b65a1 100644 --- a/Frameworks/vgmstream/vgmstream/src/meta/txth.c +++ b/Frameworks/vgmstream/vgmstream/src/meta/txth.c @@ -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++) { diff --git a/Frameworks/vgmstream/vgmstream/src/meta/ubi_ckd_cwav.c b/Frameworks/vgmstream/vgmstream/src/meta/ubi_ckd_cwav.c index f7f51af22..b96f6970b 100644 --- a/Frameworks/vgmstream/vgmstream/src/meta/ubi_ckd_cwav.c +++ b/Frameworks/vgmstream/vgmstream/src/meta/ubi_ckd_cwav.c @@ -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; diff --git a/Frameworks/vgmstream/vgmstream/src/meta/xa.c b/Frameworks/vgmstream/vgmstream/src/meta/xa.c index 0227e4400..e4645bb95 100644 --- a/Frameworks/vgmstream/vgmstream/src/meta/xa.c +++ b/Frameworks/vgmstream/vgmstream/src/meta/xa.c @@ -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; diff --git a/Frameworks/vgmstream/vgmstream/src/render.c b/Frameworks/vgmstream/vgmstream/src/render.c index 153101320..5e5243a21 100644 --- a/Frameworks/vgmstream/vgmstream/src/render.c +++ b/Frameworks/vgmstream/vgmstream/src/render.c @@ -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; -} diff --git a/Frameworks/vgmstream/vgmstream/src/render.h b/Frameworks/vgmstream/vgmstream/src/render.h index d9461367d..4a3b1de30 100644 --- a/Frameworks/vgmstream/vgmstream/src/render.h +++ b/Frameworks/vgmstream/vgmstream/src/render.h @@ -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 diff --git a/Frameworks/vgmstream/vgmstream/src/seek.c b/Frameworks/vgmstream/vgmstream/src/seek.c new file mode 100644 index 000000000..46ef3abc8 --- /dev/null +++ b/Frameworks/vgmstream/vgmstream/src/seek.c @@ -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; +} diff --git a/Frameworks/vgmstream/vgmstream/src/util/endianness.h b/Frameworks/vgmstream/vgmstream/src/util/endianness.h index d4a2e49c4..527d25f49 100644 --- a/Frameworks/vgmstream/vgmstream/src/util/endianness.h +++ b/Frameworks/vgmstream/vgmstream/src/util/endianness.h @@ -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 diff --git a/Frameworks/vgmstream/vgmstream/src/vgmstream.c b/Frameworks/vgmstream/vgmstream/src/vgmstream.c index 1b83af06b..13cf7641d 100644 --- a/Frameworks/vgmstream/vgmstream/src/vgmstream.c +++ b/Frameworks/vgmstream/vgmstream/src/vgmstream.c @@ -5,7 +5,6 @@ #include #include #include -#include #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: