diff --git a/Frameworks/vgmstream/libvgmstream.xcodeproj/project.pbxproj b/Frameworks/vgmstream/libvgmstream.xcodeproj/project.pbxproj index 6a3e7da46..566d9c8f9 100644 --- a/Frameworks/vgmstream/libvgmstream.xcodeproj/project.pbxproj +++ b/Frameworks/vgmstream/libvgmstream.xcodeproj/project.pbxproj @@ -388,8 +388,8 @@ 834F7ED52C70A786003AC386 /* plugins.c in Sources */ = {isa = PBXBuildFile; fileRef = 834F7EB12C70A786003AC386 /* plugins.c */; }; 834F7ED62C70A786003AC386 /* plugins.h in Headers */ = {isa = PBXBuildFile; fileRef = 834F7EB22C70A786003AC386 /* plugins.h */; settings = {ATTRIBUTES = (Public, ); }; }; 834F7ED72C70A786003AC386 /* render.c in Sources */ = {isa = PBXBuildFile; fileRef = 834F7EB32C70A786003AC386 /* render.c */; }; - 834F7ED82C70A786003AC386 /* render.h in Headers */ = {isa = PBXBuildFile; fileRef = 834F7EB42C70A786003AC386 /* render.h */; }; - 834F7ED92C70A786003AC386 /* sbuf.h in Headers */ = {isa = PBXBuildFile; fileRef = 834F7EB52C70A786003AC386 /* sbuf.h */; }; + 834F7ED82C70A786003AC386 /* render.h in Headers */ = {isa = PBXBuildFile; fileRef = 834F7EB42C70A786003AC386 /* render.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 834F7ED92C70A786003AC386 /* sbuf.h in Headers */ = {isa = PBXBuildFile; fileRef = 834F7EB52C70A786003AC386 /* sbuf.h */; settings = {ATTRIBUTES = (Public, ); }; }; 834F7EDA2C70A786003AC386 /* seek.c in Sources */ = {isa = PBXBuildFile; fileRef = 834F7EB62C70A786003AC386 /* seek.c */; }; 834F7EDB2C70A786003AC386 /* streamfile_api.c in Sources */ = {isa = PBXBuildFile; fileRef = 834F7EB72C70A786003AC386 /* streamfile_api.c */; }; 834F7EDC2C70A786003AC386 /* streamfile_buffer.c in Sources */ = {isa = PBXBuildFile; fileRef = 834F7EB82C70A786003AC386 /* streamfile_buffer.c */; }; @@ -442,6 +442,8 @@ 834FE10F215C79ED000A5D3D /* sdf.c in Sources */ = {isa = PBXBuildFile; fileRef = 834FE0E6215C79EC000A5D3D /* sdf.c */; }; 834FE110215C79ED000A5D3D /* msv.c in Sources */ = {isa = PBXBuildFile; fileRef = 834FE0E7215C79EC000A5D3D /* msv.c */; }; 834FE111215C79ED000A5D3D /* ck.c in Sources */ = {isa = PBXBuildFile; fileRef = 834FE0E8215C79EC000A5D3D /* ck.c */; }; + 835096F22C9979DD00163D93 /* libvgmstream.h in Headers */ = {isa = PBXBuildFile; fileRef = 835096F02C9979DD00163D93 /* libvgmstream.h */; }; + 835096F32C9979DD00163D93 /* libvgmstream_streamfile.h in Headers */ = {isa = PBXBuildFile; fileRef = 835096F12C9979DD00163D93 /* libvgmstream_streamfile.h */; }; 8350C0551E071881009E0A93 /* xma.c in Sources */ = {isa = PBXBuildFile; fileRef = 8350C0541E071881009E0A93 /* xma.c */; }; 8351F32D2212B57000A606E4 /* 208.c in Sources */ = {isa = PBXBuildFile; fileRef = 8351F32A2212B57000A606E4 /* 208.c */; }; 8351F32E2212B57000A606E4 /* ubi_bao_streamfile.h in Headers */ = {isa = PBXBuildFile; fileRef = 8351F32B2212B57000A606E4 /* ubi_bao_streamfile.h */; }; @@ -1352,6 +1354,8 @@ 834FE0E6215C79EC000A5D3D /* sdf.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = sdf.c; sourceTree = ""; }; 834FE0E7215C79EC000A5D3D /* msv.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = msv.c; sourceTree = ""; }; 834FE0E8215C79EC000A5D3D /* ck.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = ck.c; sourceTree = ""; }; + 835096F02C9979DD00163D93 /* libvgmstream.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = libvgmstream.h; sourceTree = ""; }; + 835096F12C9979DD00163D93 /* libvgmstream_streamfile.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = libvgmstream_streamfile.h; sourceTree = ""; }; 8350C0541E071881009E0A93 /* xma.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = xma.c; sourceTree = ""; }; 8351F32A2212B57000A606E4 /* 208.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = 208.c; sourceTree = ""; }; 8351F32B2212B57000A606E4 /* ubi_bao_streamfile.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ubi_bao_streamfile.h; sourceTree = ""; }; @@ -2096,28 +2100,30 @@ 836F6DDE18BDC2180095E648 /* src */ = { isa = PBXGroup; children = ( + 833E82D72A28572100CD0580 /* api.h */, 834F7E232C709C7A003AC386 /* api_decode.h */, 834F7E222C709C7A003AC386 /* api_helpers.h */, 834F7E1B2C709AE0003AC386 /* api_streamfile.h */, 834F7E1C2C709AE0003AC386 /* api_tags.h */, 834F7E852C709FED003AC386 /* api_version.h */, - 833E82D72A28572100CD0580 /* api.h */, 834F7EC02C70A786003AC386 /* base */, 834F7DA22C7093EA003AC386 /* coding */, 83A3F0711E3AD8B900D6A794 /* formats.c */, 836F6DFF18BDC2180095E648 /* layout */, + 835096F02C9979DD00163D93 /* libvgmstream.h */, + 835096F12C9979DD00163D93 /* libvgmstream_streamfile.h */, 836F6E2718BDC2180095E648 /* meta */, - 836F6F1718BDC2190095E648 /* streamfile.c */, 836F6F1818BDC2190095E648 /* streamfile.h */, + 836F6F1718BDC2190095E648 /* streamfile.c */, 836F6F1918BDC2190095E648 /* streamtypes.h */, + 836F6F1B18BDC2190095E648 /* util.h */, 83D26A7C26E66DC2001A9475 /* util */, 836F6F1A18BDC2190095E648 /* util.c */, - 836F6F1B18BDC2190095E648 /* util.h */, - 834F7E902C70A1AB003AC386 /* vgmstream_init.c */, - 834F7E8F2C70A1AB003AC386 /* vgmstream_init.h */, - 833E82D52A2856E500CD0580 /* vgmstream_types.h */, - 836F6F1C18BDC2190095E648 /* vgmstream.c */, 836F6F1D18BDC2190095E648 /* vgmstream.h */, + 836F6F1C18BDC2190095E648 /* vgmstream.c */, + 834F7E8F2C70A1AB003AC386 /* vgmstream_init.h */, + 834F7E902C70A1AB003AC386 /* vgmstream_init.c */, + 833E82D52A2856E500CD0580 /* vgmstream_types.h */, ); path = src; sourceTree = ""; @@ -2771,9 +2777,10 @@ 83256CCA28666C620036D9C0 /* synths.h in Headers */, 833E82D82A28572100CD0580 /* api.h in Headers */, 833E82D62A2856E500CD0580 /* vgmstream_types.h in Headers */, + 834F7ED92C70A786003AC386 /* sbuf.h in Headers */, + 834F7ED82C70A786003AC386 /* render.h in Headers */, 834F7EC52C70A786003AC386 /* api_internal.h in Headers */, 833E82D02A2856B200CD0580 /* reader_get.h in Headers */, - 834F7ED92C70A786003AC386 /* sbuf.h in Headers */, 834F7DF12C7093EA003AC386 /* relic_lib.h in Headers */, 8346D97A25BF838C00D1A8B0 /* idtech_streamfile.h in Headers */, 83256CE228666C620036D9C0 /* init_costabs.h in Headers */, @@ -2820,6 +2827,8 @@ 83256CD628666C620036D9C0 /* newhuffman.h in Headers */, 835C883722CC17BE001B4B3F /* ogg_vorbis_streamfile.h in Headers */, 837CEAFE23487F2C00E62A4A /* jstm_streamfile.h in Headers */, + 835096F22C9979DD00163D93 /* libvgmstream.h in Headers */, + 835096F32C9979DD00163D93 /* libvgmstream_streamfile.h in Headers */, 834F7DB02C7093EA003AC386 /* circus_vq_lib.h in Headers */, 832BF82021E0514B006F50F1 /* zsnd_streamfile.h in Headers */, 834F7E862C709FED003AC386 /* api_version.h in Headers */, @@ -2837,7 +2846,6 @@ 8373342723F60CDC00DE14DC /* lrmd_streamfile.h in Headers */, 83C7281122BC893D00678B4A /* 9tav_streamfile.h in Headers */, 83256CD928666C620036D9C0 /* costabs.h in Headers */, - 834F7ED82C70A786003AC386 /* render.h in Headers */, 83C7280F22BC893D00678B4A /* xwb_xsb.h in Headers */, 8315868B26F586F900803A3A /* m2_psb.h in Headers */, 83256CD428666C620036D9C0 /* icy.h in Headers */, diff --git a/Frameworks/vgmstream/vgmstream/src/base/api_decode_base.c b/Frameworks/vgmstream/vgmstream/src/base/api_decode_base.c index 7cfd53b2c..82dcf9f80 100644 --- a/Frameworks/vgmstream/vgmstream/src/base/api_decode_base.c +++ b/Frameworks/vgmstream/vgmstream/src/base/api_decode_base.c @@ -1,4 +1,5 @@ #include "api_internal.h" +#include "mixing.h" #if LIBVGMSTREAM_ENABLE #define INTERNAL_BUF_SAMPLES 1024 @@ -81,4 +82,27 @@ void libvgmstream_priv_reset(libvgmstream_priv_t* priv, bool reset_buf) { priv->decode_done = false; } +libvgmstream_sample_t api_get_output_sample_type(libvgmstream_priv_t* priv) { + VGMSTREAM* v = priv->vgmstream; + sfmt_t format = mixing_get_output_sample_type(v); + switch(format) { + case SFMT_S16: return LIBVGMSTREAM_SAMPLE_PCM16; + case SFMT_FLT: return LIBVGMSTREAM_SAMPLE_FLOAT; + default: + return 0x00; //??? + } +} + +int api_get_sample_size(libvgmstream_sample_t sample_type) { + switch(sample_type) { + case LIBVGMSTREAM_SAMPLE_PCM24: + case LIBVGMSTREAM_SAMPLE_PCM32: + case LIBVGMSTREAM_SAMPLE_FLOAT: + return 0x04; + case LIBVGMSTREAM_SAMPLE_PCM16: + default: + return 0x02; + } +} + #endif diff --git a/Frameworks/vgmstream/vgmstream/src/base/api_decode_open.c b/Frameworks/vgmstream/vgmstream/src/base/api_decode_open.c index 6dc96367c..299e177be 100644 --- a/Frameworks/vgmstream/vgmstream/src/base/api_decode_open.c +++ b/Frameworks/vgmstream/vgmstream/src/base/api_decode_open.c @@ -1,8 +1,8 @@ #include "api_internal.h" +#include "sbuf.h" +#include "mixing.h" #if LIBVGMSTREAM_ENABLE -#define INTERNAL_BUF_SAMPLES 1024 - static void load_vgmstream(libvgmstream_priv_t* priv, libvgmstream_options_t* opt) { STREAMFILE* sf_api = open_api_streamfile(opt->libsf); @@ -49,6 +49,13 @@ static void prepare_mixing(libvgmstream_priv_t* priv, libvgmstream_options_t* op vgmstream_mixing_stereo_only(priv->vgmstream, opt->stereo_track - 1); } + if (priv->cfg.force_pcm16) { + mixing_macro_output_sample_format(priv->vgmstream, SFMT_S16); + } + else if (priv->cfg.force_float) { + mixing_macro_output_sample_format(priv->vgmstream, SFMT_FLT); + } + vgmstream_mixing_enable(priv->vgmstream, INTERNAL_BUF_SAMPLES, NULL /*&input_channels*/, NULL /*&output_channels*/); } @@ -65,10 +72,6 @@ static void update_format_info(libvgmstream_priv_t* priv) { libvgmstream_format_t* fmt = &priv->fmt; VGMSTREAM* v = priv->vgmstream; - fmt->sample_size = 0x02; - fmt->sample_type = LIBVGMSTREAM_SAMPLE_PCM16; - fmt->sample_rate = v->sample_rate; - fmt->subsong_index = v->stream_index; fmt->subsong_count = v->num_streams; @@ -77,6 +80,11 @@ static void update_format_info(libvgmstream_priv_t* priv) { vgmstream_mixing_enable(v, 0, &fmt->input_channels, &fmt->channels); fmt->channel_layout = v->channel_layout; + fmt->sample_type = api_get_output_sample_type(priv); + fmt->sample_size = api_get_sample_size(fmt->sample_type); + + fmt->sample_rate = v->sample_rate; + fmt->stream_samples = v->num_samples; fmt->loop_start = v->loop_start_sample; fmt->loop_end = v->loop_end_sample; diff --git a/Frameworks/vgmstream/vgmstream/src/base/api_decode_play.c b/Frameworks/vgmstream/vgmstream/src/base/api_decode_play.c index 97b8da858..bc1dad7ce 100644 --- a/Frameworks/vgmstream/vgmstream/src/base/api_decode_play.c +++ b/Frameworks/vgmstream/vgmstream/src/base/api_decode_play.c @@ -1,32 +1,45 @@ #include "api_internal.h" +#include "mixing.h" + #if LIBVGMSTREAM_ENABLE -#define INTERNAL_BUF_SAMPLES 1024 -//TODO: use internal -static void reset_buf(libvgmstream_priv_t* priv) { - /* state reset */ +static bool reset_buf(libvgmstream_priv_t* priv) { + // state reset priv->buf.samples = 0; priv->buf.bytes = 0; priv->buf.consumed = 0; if (priv->buf.initialized) - return; - int output_channels = priv->vgmstream->channels; - int input_channels = priv->vgmstream->channels; + return true; + + // calc input/output values to reserve buf (should be as big as input or output) + int input_channels = 0, output_channels = 0; vgmstream_mixing_enable(priv->vgmstream, 0, &input_channels, &output_channels); //query - /* static config */ - priv->buf.channels = input_channels; - if (priv->buf.channels < output_channels) - priv->buf.channels = output_channels; + int min_channels = input_channels; + if (min_channels < output_channels) + min_channels = output_channels; + + sfmt_t input_sfmt = mixing_get_input_sample_type(priv->vgmstream); + sfmt_t output_sfmt = mixing_get_output_sample_type(priv->vgmstream); + int input_sample_size = sfmt_get_sample_size(input_sfmt); + int output_sample_size = sfmt_get_sample_size(output_sfmt); + + int min_sample_size = input_sample_size; + if (min_sample_size < output_sample_size) + min_sample_size = output_sample_size; - priv->buf.sample_size = sizeof(sample_t); priv->buf.max_samples = INTERNAL_BUF_SAMPLES; - priv->buf.max_bytes = priv->buf.max_samples * priv->buf.sample_size * priv->buf.channels; - priv->buf.data = malloc(priv->buf.max_bytes); + priv->buf.sample_size = output_sample_size; + priv->buf.channels = output_channels; + + int max_bytes = priv->buf.max_samples * min_sample_size * min_channels; + priv->buf.data = malloc(max_bytes); + if (!priv->buf.data) return false; priv->buf.initialized = true; + return true; } static void update_buf(libvgmstream_priv_t* priv, int samples_done) { @@ -59,8 +72,7 @@ LIBVGMSTREAM_API int libvgmstream_render(libvgmstream_t* lib) { if (priv->decode_done) return LIBVGMSTREAM_ERROR_GENERIC; - reset_buf(priv); - if (!priv->buf.data) + if (!reset_buf(priv)) return LIBVGMSTREAM_ERROR_GENERIC; int to_get = priv->buf.max_samples; diff --git a/Frameworks/vgmstream/vgmstream/src/base/api_internal.h b/Frameworks/vgmstream/vgmstream/src/base/api_internal.h index fec1035f9..289944fd8 100644 --- a/Frameworks/vgmstream/vgmstream/src/base/api_internal.h +++ b/Frameworks/vgmstream/vgmstream/src/base/api_internal.h @@ -11,6 +11,8 @@ #define LIBVGMSTREAM_ERROR_GENERIC -1 #define LIBVGMSTREAM_ERROR_DONE -2 +#define INTERNAL_BUF_SAMPLES 1024 + /* self-note: various API functions are just bridges to internal stuff. * Rather than changing the internal stuff to handle API structs/etc, * leave internals untouched for a while so external plugins/users may adapt. @@ -20,11 +22,10 @@ typedef struct { bool initialized; void* data; - /* config */ - int channels; - int max_bytes; + /* config (output values channels/size after mixing, though buf may be as big as input size) */ int max_samples; - int sample_size; + int channels; /* */ + int sample_size; /* state */ int samples; @@ -56,8 +57,10 @@ typedef struct { void libvgmstream_priv_reset(libvgmstream_priv_t* priv, bool reset_buf); +libvgmstream_sample_t api_get_output_sample_type(libvgmstream_priv_t* priv); +int api_get_sample_size(libvgmstream_sample_t sample_type); -STREAMFILE* open_api_streamfile(libvgmstream_streamfile_t* libsf); +STREAMFILE* open_api_streamfile(libstreamfile_t* libsf); #endif #endif diff --git a/Frameworks/vgmstream/vgmstream/src/base/api_libsf.c b/Frameworks/vgmstream/vgmstream/src/base/api_libsf.c index c2318bd71..e08e24948 100644 --- a/Frameworks/vgmstream/vgmstream/src/base/api_libsf.c +++ b/Frameworks/vgmstream/vgmstream/src/base/api_libsf.c @@ -1,9 +1,9 @@ #include "api_internal.h" #if LIBVGMSTREAM_ENABLE -static libvgmstream_streamfile_t* libvgmstream_streamfile_from_streamfile(STREAMFILE* sf); +static libstreamfile_t* libstreamfile_from_streamfile(STREAMFILE* sf); -/* libvgmstream_streamfile_t for external use, as a default implementation calling some internal SF */ +/* libstreamfile_t for external use, as a default implementation calling some internal SF */ typedef struct { int64_t offset; @@ -29,12 +29,12 @@ static int64_t libsf_seek(void* user_data, int64_t offset, int whence) { return -1; switch (whence) { - case LIBVGMSTREAM_STREAMFILE_SEEK_SET: /* absolute */ + case LIBSTREAMFILE_SEEK_SET: /* absolute */ break; - case LIBVGMSTREAM_STREAMFILE_SEEK_CUR: /* relative to current */ + case LIBSTREAMFILE_SEEK_CUR: /* relative to current */ offset += data->offset; break; - case LIBVGMSTREAM_STREAMFILE_SEEK_END: /* relative to file end (should be negative) */ + case LIBSTREAMFILE_SEEK_END: /* relative to file end (should be negative) */ offset += data->size; break; default: @@ -71,16 +71,16 @@ static const char* libsf_get_name(void* user_data) { return data->name; } -struct libvgmstream_streamfile_t* libsf_open(void* user_data, const char* filename) { +static libstreamfile_t* libsf_open(void* user_data, const char* filename) { libsf_data_t* data = user_data; - if (!data || !data->inner_sf) + if (!data || !data->inner_sf || !filename) return NULL; STREAMFILE* sf = data->inner_sf->open(data->inner_sf, filename, 0); if (!sf) return NULL; - libvgmstream_streamfile_t* libsf = libvgmstream_streamfile_from_streamfile(sf); + libstreamfile_t* libsf = libstreamfile_from_streamfile(sf); if (!libsf) { close_streamfile(sf); return NULL; @@ -89,7 +89,7 @@ struct libvgmstream_streamfile_t* libsf_open(void* user_data, const char* filena return libsf; } -static void libsf_close(struct libvgmstream_streamfile_t* libsf) { +static void libsf_close(libstreamfile_t* libsf) { if (!libsf) return; @@ -101,14 +101,14 @@ static void libsf_close(struct libvgmstream_streamfile_t* libsf) { free(libsf); } -static libvgmstream_streamfile_t* libvgmstream_streamfile_from_streamfile(STREAMFILE* sf) { +static libstreamfile_t* libstreamfile_from_streamfile(STREAMFILE* sf) { if (!sf) return NULL; - libvgmstream_streamfile_t* libsf = NULL; + libstreamfile_t* libsf = NULL; libsf_data_t* data = NULL; - libsf = calloc(1, sizeof(libvgmstream_streamfile_t)); + libsf = calloc(1, sizeof(libstreamfile_t)); if (!libsf) goto fail; libsf->read = libsf_read; @@ -132,12 +132,12 @@ fail: } -LIBVGMSTREAM_API libvgmstream_streamfile_t* libvgmstream_streamfile_open_from_stdio(const char* filename) { +LIBVGMSTREAM_API libstreamfile_t* libstreamfile_open_from_stdio(const char* filename) { STREAMFILE* sf = open_stdio_streamfile(filename); if (!sf) return NULL; - libvgmstream_streamfile_t* libsf = libvgmstream_streamfile_from_streamfile(sf); + libstreamfile_t* libsf = libstreamfile_from_streamfile(sf); if (!libsf) { close_streamfile(sf); return NULL; diff --git a/Frameworks/vgmstream/vgmstream/src/base/api_tags.c b/Frameworks/vgmstream/vgmstream/src/base/api_tags.c index 48a9b0bcb..ed1ed1b2d 100644 --- a/Frameworks/vgmstream/vgmstream/src/base/api_tags.c +++ b/Frameworks/vgmstream/vgmstream/src/base/api_tags.c @@ -3,11 +3,11 @@ typedef struct { VGMSTREAM_TAGS* vtags; - libvgmstream_streamfile_t* libsf; + libstreamfile_t* libsf; STREAMFILE* sf_tags; } libvgmstream_tags_priv_t; -LIBVGMSTREAM_API libvgmstream_tags_t* libvgmstream_tags_init(libvgmstream_streamfile_t* libsf) { +LIBVGMSTREAM_API libvgmstream_tags_t* libvgmstream_tags_init(libstreamfile_t* libsf) { if (!libsf) return NULL; diff --git a/Frameworks/vgmstream/vgmstream/src/base/decode.c b/Frameworks/vgmstream/vgmstream/src/base/decode.c index 4177741dc..f1f30caca 100644 --- a/Frameworks/vgmstream/vgmstream/src/base/decode.c +++ b/Frameworks/vgmstream/vgmstream/src/base/decode.c @@ -837,7 +837,7 @@ void decode_vgmstream(VGMSTREAM* vgmstream, int samples_filled, int samples_to_d switch (vgmstream->coding_type) { case coding_SILENCE: - sbuf_silence(buffer, samples_to_do, vgmstream->channels, 0); + sbuf_silence_s16(buffer, samples_to_do, vgmstream->channels, 0); break; case coding_CRI_ADX: diff --git a/Frameworks/vgmstream/vgmstream/src/base/mixer.c b/Frameworks/vgmstream/vgmstream/src/base/mixer.c index fefe3ac3e..7ce06043f 100644 --- a/Frameworks/vgmstream/vgmstream/src/base/mixer.c +++ b/Frameworks/vgmstream/vgmstream/src/base/mixer.c @@ -7,26 +7,22 @@ #include #include -//TODO simplify /** * Mixer modifies decoded sample buffer before final output. This is implemented - * mostly with simplicity in mind rather than performance. Process: + * with simplicity in mind rather than performance. Process: * - detect if mixing applies at current moment or exit (mini performance optimization) * - copy/upgrade buf to float mixbuf if needed * - do mixing ops * - copy/downgrade mixbuf to original buf if needed * - * Mixing may add or remove channels. input_channels is the buf's original channels, - * and output_channels the resulting buf's channels. buf and mixbuf must be - * as big as max channels (mixing_channels). - * - * Mixing ops are added by a meta (ex. TXTP) or plugin through the API. Non-sensical - * mixes are ignored (to avoid rechecking every time). - * - * Currently, mixing must be manually enabled before starting to decode, because plugins - * need to setup bigger bufs when upmixing. (to be changed) + * Mixing ops are added by a meta (ex. TXTP) or plugins through API. Non-sensical config + * is ignored on add (to avoid rechecking every time). * - * segmented/layered layouts handle mixing on their own. + * Mixing may add or remove channels or change sample format. external buf and internal mixbuf + * are expected to be as big as needed. Currently, mixing must be manually enabled before starting + * to decode, because plugins need to setup appropriate bufs. (to be changed) + * + * segmented/layered layouts handle mixing vgmstream sample bufs on their own. */ mixer_t* mixer_init(int channels) { @@ -62,57 +58,66 @@ void mixer_update_channel(mixer_t* mixer) { bool mixer_is_active(mixer_t* mixer) { /* no support or not need to apply */ - if (!mixer || !mixer->active || mixer->chain_count == 0) + if (!mixer || !mixer->active) return false; - return true; + if (mixer->chain_count > 0) + return true; + + if (mixer->force_type != SFMT_NONE) + return true; + + return false; } +void mixer_process(mixer_t* mixer, sbuf_t* sbuf, int32_t current_pos) { -void mixer_process(mixer_t* mixer, sample_t* outbuf, int32_t sample_count, int32_t current_pos) { - - /* no support or not need to apply */ - if (!mixer || !mixer->active || mixer->chain_count == 0) - return; + /* external */ + //if (!mixer_is_active(mixer)) + // return; /* try to skip if no fades apply (set but does nothing yet) + only has fades * (could be done in mix op but avoids upgrading bufs in some cases) */ mixer->current_subpos = 0; if (mixer->has_fade) { //;VGM_LOG("MIX: fade test %i, %i\n", data->has_non_fade, mixer_op_fade_is_active(data, current_pos, current_pos + sample_count)); - if (!mixer->has_non_fade && !mixer_op_fade_is_active(mixer, current_pos, current_pos + sample_count)) + if (!mixer->has_non_fade && !mixer_op_fade_is_active(mixer, current_pos, current_pos + sbuf->filled)) return; //;VGM_LOG("MIX: fade pos=%i\n", current_pos); mixer->current_subpos = current_pos; } - // upgrade buf for mixing (somehow using float buf rather than int32 is faster?) - sbuf_copy_s16_to_f32(mixer->mixbuf, outbuf, sample_count, mixer->input_channels); + // remix to temp buf for mixing (somehow using float buf rather than int32 is faster?) + sbuf_copy_to_f32(mixer->mixbuf, sbuf); - /* apply mixing ops in order. Some ops change total channels they may change around: - * - 2ch w/ "1+2,1u" = ch1+ch2, ch1(add and push rest) = 3ch: ch1' ch1+ch2 ch2 - * - 2ch w/ "1u" = downmix to 1ch (current_channels decreases once) - */ + // apply mixing ops in order. current_channels may increase or decrease per op + // - 2ch w/ "1+2,1u" = ch1+ch2, ch1(add and push rest) = 3ch: ch1' ch1+ch2 ch2 + // - 2ch w/ "1u" = downmix to 1ch (current_channels decreases once) mixer->current_channels = mixer->input_channels; for (int m = 0; m < mixer->chain_count; m++) { mix_op_t* mix = &mixer->chain[m]; - //TODO: set callback + //TO-DO: set callback switch(mix->type) { - case MIX_SWAP: mixer_op_swap(mixer, sample_count, mix); break; - case MIX_ADD: mixer_op_add(mixer, sample_count, mix); break; - case MIX_VOLUME: mixer_op_volume(mixer, sample_count, mix); break; - case MIX_LIMIT: mixer_op_limit(mixer, sample_count, mix); break; - case MIX_UPMIX: mixer_op_upmix(mixer, sample_count, mix); break; - case MIX_DOWNMIX: mixer_op_downmix(mixer, sample_count, mix); break; - case MIX_KILLMIX: mixer_op_killmix(mixer, sample_count, mix); break; - case MIX_FADE: mixer_op_fade(mixer, sample_count, mix); + case MIX_SWAP: mixer_op_swap(mixer, sbuf->filled, mix); break; + case MIX_ADD: mixer_op_add(mixer, sbuf->filled, mix); break; + case MIX_VOLUME: mixer_op_volume(mixer, sbuf->filled, mix); break; + case MIX_LIMIT: mixer_op_limit(mixer, sbuf->filled, mix); break; + case MIX_UPMIX: mixer_op_upmix(mixer, sbuf->filled, mix); break; + case MIX_DOWNMIX: mixer_op_downmix(mixer, sbuf->filled, mix); break; + case MIX_KILLMIX: mixer_op_killmix(mixer, sbuf->filled, mix); break; + case MIX_FADE: mixer_op_fade(mixer, sbuf->filled, mix); default: break; } } - /* downgrade mix to original output */ - sbuf_copy_f32_to_s16(outbuf, mixer->mixbuf, sample_count, mixer->output_channels); + // setup + remix to output buf (buf is expected to be big enough to handle config) + sbuf->channels = mixer->output_channels; + if (mixer->force_type) { + sbuf->fmt = mixer->force_type; + } + + sbuf_copy_from_f32(sbuf, mixer->mixbuf); } diff --git a/Frameworks/vgmstream/vgmstream/src/base/mixer.h b/Frameworks/vgmstream/vgmstream/src/base/mixer.h index 0382f3cd6..793888a88 100644 --- a/Frameworks/vgmstream/vgmstream/src/base/mixer.h +++ b/Frameworks/vgmstream/vgmstream/src/base/mixer.h @@ -2,6 +2,7 @@ #define _MIXER_H_ #include "../streamtypes.h" +#include "sbuf.h" typedef struct mixer_t mixer_t; @@ -10,7 +11,7 @@ typedef struct mixer_t mixer_t; mixer_t* mixer_init(int channels); void mixer_free(mixer_t* mixer); void mixer_update_channel(mixer_t* mixer); -void mixer_process(mixer_t* mixer, sample_t *outbuf, int32_t sample_count, int32_t current_pos); +void mixer_process(mixer_t* mixer, sbuf_t* sbuf, int32_t current_pos); bool mixer_is_active(mixer_t* mixer); #endif diff --git a/Frameworks/vgmstream/vgmstream/src/base/mixer_priv.h b/Frameworks/vgmstream/vgmstream/src/base/mixer_priv.h index 8e265fee4..73fe757e7 100644 --- a/Frameworks/vgmstream/vgmstream/src/base/mixer_priv.h +++ b/Frameworks/vgmstream/vgmstream/src/base/mixer_priv.h @@ -2,6 +2,7 @@ #define _MIXER_PRIV_H_ #include "../streamtypes.h" #include "mixer.h" +#include "sbuf.h" #define VGMSTREAM_MAX_MIXING 512 @@ -52,6 +53,7 @@ struct mixer_t { int current_channels; /* state: channels may increase/decrease during ops */ int32_t current_subpos; /* state: current sample pos in the stream */ + sfmt_t force_type; }; void mixer_op_swap(mixer_t* mixer, int32_t sample_count, mix_op_t* op); diff --git a/Frameworks/vgmstream/vgmstream/src/base/mixing.c b/Frameworks/vgmstream/vgmstream/src/base/mixing.c index afd4313e5..3db330d27 100644 --- a/Frameworks/vgmstream/vgmstream/src/base/mixing.c +++ b/Frameworks/vgmstream/vgmstream/src/base/mixing.c @@ -1,36 +1,14 @@ +#include +#include #include "../vgmstream.h" #include "../util/channel_mappings.h" +#include "../layout/layout.h" #include "mixing.h" #include "mixer.h" #include "mixer_priv.h" #include "sbuf.h" -#include "../layout/layout.h" -#include -#include -//TODO simplify -/** - * Mixer modifies decoded sample buffer before final output. This is implemented - * mostly with simplicity in mind rather than performance. Process: - * - detect if mixing applies at current moment or exit (mini performance optimization) - * - copy/upgrade buf to float mixbuf if needed - * - do mixing ops - * - copy/downgrade mixbuf to original buf if needed - * - * Mixing may add or remove channels. input_channels is the buf's original channels, - * and output_channels the resulting buf's channels. buf and mixbuf must be - * as big as max channels (mixing_channels). - * - * Mixing ops are added by a meta (ex. TXTP) or plugin through the API. Non-sensical - * mixes are ignored (to avoid rechecking every time). - * - * Currently, mixing must be manually enabled before starting to decode, because plugins - * need to setup bigger bufs when upmixing. (to be changed) - * - * segmented/layered layouts handle mixing on their own. - */ - -/* ******************************************************************* */ +/* Wrapper/helpers for vgmstream's "mixer", which does main sample buffer transformations */ static int32_t get_current_pos(VGMSTREAM* vgmstream, int32_t sample_count) { int32_t current_pos; @@ -53,14 +31,14 @@ static int32_t get_current_pos(VGMSTREAM* vgmstream, int32_t sample_count) { return current_pos; } -void mix_vgmstream(sample_t *outbuf, int32_t sample_count, VGMSTREAM* vgmstream) { +void mix_vgmstream(sbuf_t* sbuf, VGMSTREAM* vgmstream) { /* no support or not need to apply */ if (!mixer_is_active(vgmstream->mixer)) return; - int32_t current_pos = get_current_pos(vgmstream, sample_count); + int32_t current_pos = get_current_pos(vgmstream, sbuf->filled); - mixer_process(vgmstream->mixer, outbuf, sample_count, current_pos); + mixer_process(vgmstream->mixer, sbuf, current_pos); } /* ******************************************************************* */ @@ -148,8 +126,11 @@ void mixing_info(VGMSTREAM* vgmstream, int* p_input_channels, int* p_output_chan mixer_t* mixer = vgmstream->mixer; int input_channels, output_channels; - if (!mixer) - goto fail; + if (!mixer) { + if (p_input_channels) *p_input_channels = vgmstream->channels; + if (p_output_channels) *p_output_channels = vgmstream->channels; + return; + } output_channels = mixer->output_channels; if (mixer->output_channels > vgmstream->channels) @@ -159,11 +140,23 @@ void mixing_info(VGMSTREAM* vgmstream, int* p_input_channels, int* p_output_chan if (p_input_channels) *p_input_channels = input_channels; if (p_output_channels) *p_output_channels = output_channels; - - //;VGM_LOG("MIX: channels %i, in=%i, out=%i, mix=%i\n", vgmstream->channels, input_channels, output_channels, data->mixing_channels); - return; -fail: - if (p_input_channels) *p_input_channels = vgmstream->channels; - if (p_output_channels) *p_output_channels = vgmstream->channels; - return; +} + +sfmt_t mixing_get_input_sample_type(VGMSTREAM* vgmstream) { + // TODO: check vgmstream + // TODO: on layered/segments, detect biggest value and use that (ex. if one of the layers uses flt > flt) + return SFMT_S16; +} + +sfmt_t mixing_get_output_sample_type(VGMSTREAM* vgmstream) { + sfmt_t input_fmt = mixing_get_input_sample_type(vgmstream); + + mixer_t* mixer = vgmstream->mixer; + if (!mixer) + return input_fmt; + + if (mixer->force_type) + return mixer->force_type; + + return input_fmt; } diff --git a/Frameworks/vgmstream/vgmstream/src/base/mixing.h b/Frameworks/vgmstream/vgmstream/src/base/mixing.h index 53d2ab8f1..409f30250 100644 --- a/Frameworks/vgmstream/vgmstream/src/base/mixing.h +++ b/Frameworks/vgmstream/vgmstream/src/base/mixing.h @@ -2,11 +2,12 @@ #define _MIXING_H_ #include "../vgmstream.h" -#include "../util/log.h" +#include "../util/log.h" //TODO remove +#include "sbuf.h" /* Applies mixing commands to the sample buffer. Mixing must be externally enabled and * outbuf must big enough to hold output_channels*samples_to_do */ -void mix_vgmstream(sample_t *outbuf, int32_t sample_count, VGMSTREAM* vgmstream); +void mix_vgmstream(sbuf_t* sbuf, VGMSTREAM* vgmstream); /* Call to let vgmstream apply mixing, which must handle input/output_channels. * Once mixing is active any new mixes are ignored (to avoid the possibility @@ -14,7 +15,10 @@ void mix_vgmstream(sample_t *outbuf, int32_t sample_count, VGMSTREAM* vgmstream) void mixing_setup(VGMSTREAM* vgmstream, int32_t max_sample_count); /* gets current mixing info */ -void mixing_info(VGMSTREAM* vgmstream, int *input_channels, int *output_channels); +void mixing_info(VGMSTREAM* vgmstream, int* input_channels, int* output_channels); + +sfmt_t mixing_get_input_sample_type(VGMSTREAM* vgmstream); +sfmt_t mixing_get_output_sample_type(VGMSTREAM* vgmstream); /* adds mixes filtering and optimizing if needed */ void mixing_push_swap(VGMSTREAM* vgmstream, int ch_dst, int ch_src); @@ -32,6 +36,7 @@ void mixing_macro_layer(VGMSTREAM* vgmstream, int max, uint32_t mask, char mode) void mixing_macro_crosstrack(VGMSTREAM* vgmstream, int max); void mixing_macro_crosslayer(VGMSTREAM* vgmstream, int max, char mode); void mixing_macro_downmix(VGMSTREAM* vgmstream, int max /*, mapping_t output_mapping*/); +void mixing_macro_output_sample_format(VGMSTREAM* vgmstream, sfmt_t type); #endif diff --git a/Frameworks/vgmstream/vgmstream/src/base/mixing_macros.c b/Frameworks/vgmstream/vgmstream/src/base/mixing_macros.c index fa3ce258a..a52cdb4f7 100644 --- a/Frameworks/vgmstream/vgmstream/src/base/mixing_macros.c +++ b/Frameworks/vgmstream/vgmstream/src/base/mixing_macros.c @@ -1,10 +1,10 @@ +#include +#include #include "../vgmstream.h" #include "../util/channel_mappings.h" #include "../layout/layout.h" #include "mixing.h" #include "mixer_priv.h" -#include -#include #define MIX_MACRO_VOCALS 'v' @@ -471,7 +471,7 @@ typedef enum { void mixing_macro_downmix(VGMSTREAM* vgmstream, int max /*, mapping_t output_mapping*/) { mixer_t* mixer = vgmstream->mixer; - int ch, output_channels, mp_in, mp_out, ch_in, ch_out; + int output_channels, mp_in, mp_out, ch_in, ch_out; channel_mapping_t input_mapping, output_mapping; const double vol_max = 1.0; const double vol_sqrt = 1 / sqrt(2); @@ -534,7 +534,7 @@ void mixing_macro_downmix(VGMSTREAM* vgmstream, int max /*, mapping_t output_map /* save and make N fake channels at the beginning for easier calcs */ output_channels = mixer->output_channels; - for (ch = 0; ch < max; ch++) { + for (int ch = 0; ch < max; ch++) { mixing_push_upmix(vgmstream, 0); } @@ -542,13 +542,13 @@ void mixing_macro_downmix(VGMSTREAM* vgmstream, int max /*, mapping_t output_map ch_in = 0; for (mp_in = 0; mp_in < 16; mp_in++) { /* read input mapping (ex. 5.1) and find channel */ - if (!(input_mapping & (1<mixer; + if (!mixer) + return; + + // optimization (may skip initializing mixer) + sfmt_t input_fmt = mixing_get_input_sample_type(vgmstream); + if (input_fmt == type) + return; + mixer->force_type = type; +} diff --git a/Frameworks/vgmstream/vgmstream/src/base/plugins.c b/Frameworks/vgmstream/vgmstream/src/base/plugins.c index ea56ddd91..3e51a04e5 100644 --- a/Frameworks/vgmstream/vgmstream/src/base/plugins.c +++ b/Frameworks/vgmstream/vgmstream/src/base/plugins.c @@ -12,7 +12,6 @@ int vgmstream_ctx_is_valid(const char* filename, vgmstream_ctx_valid_cfg *cfg) { const char** extension_list; size_t extension_list_len; const char* extension; - int i; bool is_extension = cfg && cfg->is_extension; bool reject_extensionless = cfg && cfg->reject_extensionless; @@ -29,19 +28,19 @@ int vgmstream_ctx_is_valid(const char* filename, vgmstream_ctx_valid_cfg *cfg) { /* some metas accept extensionless files, but make sure it's not a path (unlikely but...) */ if (strlen(extension) <= 0) { int len = strlen(filename); /* foobar passes an extension as so len may be still 0 */ - if (len <= 0 && is_extension) - return 0; + if (len <= 0 && !is_extension) + return false; if (len > 1 && (filename[len - 1] == '/' || filename[len - 1] == '\\')) - return 0; + return false; return !reject_extensionless; } /* try in default list */ if (!skip_standard) { extension_list = vgmstream_get_formats(&extension_list_len); - for (i = 0; i < extension_list_len; i++) { + for (int i = 0; i < extension_list_len; i++) { if (strcasecmp(extension, extension_list[i]) == 0) { - return 1; + return true; } } } @@ -49,29 +48,29 @@ int vgmstream_ctx_is_valid(const char* filename, vgmstream_ctx_valid_cfg *cfg) { /* try in common extensions */ if (accept_common) { extension_list = vgmstream_get_common_formats(&extension_list_len); - for (i = 0; i < extension_list_len; i++) { + for (int i = 0; i < extension_list_len; i++) { if (strcasecmp(extension, extension_list[i]) == 0) - return 1; + return true; } } /* allow anything not in the normal list but not in common extensions */ if (accept_unknown) { - int is_common = 0; + bool is_common = false; extension_list = vgmstream_get_common_formats(&extension_list_len); - for (i = 0; i < extension_list_len; i++) { + for (int i = 0; i < extension_list_len; i++) { if (strcasecmp(extension, extension_list[i]) == 0) { - is_common = 1; + is_common = true; break; } } if (!is_common) - return 1; + return true; } - return 0; + return false; } void vgmstream_get_title(char* buf, int buf_len, const char* filename, VGMSTREAM* vgmstream, vgmstream_title_t* cfg) { diff --git a/Frameworks/vgmstream/vgmstream/src/base/render.c b/Frameworks/vgmstream/vgmstream/src/base/render.c index f34234901..700d92b2f 100644 --- a/Frameworks/vgmstream/vgmstream/src/base/render.c +++ b/Frameworks/vgmstream/vgmstream/src/base/render.c @@ -3,7 +3,6 @@ #include "render.h" #include "decode.h" #include "mixing.h" -#include "sbuf.h" /* VGMSTREAM RENDERING @@ -73,7 +72,10 @@ void render_reset(VGMSTREAM* vgmstream) { } } -int render_layout(sample_t* buf, int32_t sample_count, VGMSTREAM* vgmstream) { +int render_layout(sbuf_t* sbuf, VGMSTREAM* vgmstream) { + void* buf = sbuf->buf; + int sample_count = sbuf->samples; + if (sample_count == 0) return 0; @@ -82,9 +84,7 @@ int render_layout(sample_t* buf, int32_t sample_count, VGMSTREAM* vgmstream) { // nothing to decode: return blank buf (not ">=" to allow layouts to loop in some cases when == happens) if (vgmstream->current_sample > vgmstream->num_samples) { - int channels = vgmstream->channels; - - memset(buf, 0, sample_count * sizeof(sample_t) * channels); + sbuf_silence_rest(sbuf); return sample_count; } @@ -137,10 +137,10 @@ int render_layout(sample_t* buf, int32_t sample_count, VGMSTREAM* vgmstream) { render_vgmstream_blocked(buf, sample_count, vgmstream); break; case layout_segmented: - render_vgmstream_segmented(buf, sample_count,vgmstream); + render_vgmstream_segmented(sbuf, vgmstream); break; case layout_layered: - render_vgmstream_layered(buf, sample_count, vgmstream); + render_vgmstream_layered(sbuf, vgmstream); break; default: break; @@ -148,7 +148,6 @@ int render_layout(sample_t* buf, int32_t sample_count, VGMSTREAM* vgmstream) { // decode past stream samples: blank rest of buf if (vgmstream->current_sample > vgmstream->num_samples) { - int channels = vgmstream->channels; int32_t excess, decoded; excess = (vgmstream->current_sample - vgmstream->num_samples); @@ -156,7 +155,8 @@ int render_layout(sample_t* buf, int32_t sample_count, VGMSTREAM* vgmstream) { excess = sample_count; decoded = sample_count - excess; - memset(buf + decoded * channels, 0, excess * sizeof(sample_t) * channels); + sbuf_silence_part(sbuf, decoded, sample_count - decoded); + return sample_count; } @@ -165,79 +165,63 @@ int render_layout(sample_t* buf, int32_t sample_count, VGMSTREAM* vgmstream) { /*****************************************************************************/ -typedef struct { - //sbuf_t sbuf; - int16_t* tmpbuf; - int samples_to_do; - int samples_done; -} render_helper_t; - - // consumes samples from decoder -static void play_op_trim(VGMSTREAM* vgmstream, render_helper_t* renderer) { +static void play_op_trim(VGMSTREAM* vgmstream, sbuf_t* sbuf) { play_state_t* ps = &vgmstream->pstate; if (!ps->trim_begin_left) return; - if (!renderer->samples_to_do) + if (sbuf->samples <= 0) return; - // simpler using external buf? - //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 */ - sample_t* tmpbuf = renderer->tmpbuf; - int buf_samples = renderer->samples_to_do; + sbuf_t sbuf_tmp = *sbuf; + int buf_samples = sbuf->samples; while (ps->trim_begin_left) { int to_do = ps->trim_begin_left; if (to_do > buf_samples) to_do = buf_samples; - render_layout(tmpbuf, to_do, vgmstream); + sbuf_tmp.samples = to_do; + int done = render_layout(&sbuf_tmp, vgmstream); /* no mixing */ - ps->trim_begin_left -= to_do; + + ps->trim_begin_left -= done; } } // adds empty samples to buf -static void play_op_pad_begin(VGMSTREAM* vgmstream, render_helper_t* renderer) { +static void play_op_pad_begin(VGMSTREAM* vgmstream, sbuf_t* sbuf) { play_state_t* ps = &vgmstream->pstate; if (!ps->pad_begin_left) return; //if (ps->play_position > ps->play_begin_duration) //implicit // return; - int channels = ps->output_channels; - int buf_samples = renderer->samples_to_do; - int to_do = ps->pad_begin_left; - if (to_do > buf_samples) - to_do = buf_samples; + if (to_do > sbuf->samples) + to_do = sbuf->samples; + + sbuf_silence_part(sbuf, 0, to_do); - memset(renderer->tmpbuf, 0, to_do * sizeof(sample_t) * channels); ps->pad_begin_left -= to_do; - - renderer->samples_done += to_do; - renderer->samples_to_do -= to_do; - renderer->tmpbuf += to_do * channels; /* as if mixed */ + sbuf->filled += to_do; } // fades (modifies volumes) part of buf -static void play_op_fade(VGMSTREAM* vgmstream, sample_t* buf, int samples_done) { +static void play_op_fade(VGMSTREAM* vgmstream, sbuf_t* sbuf) { play_config_t* pc = &vgmstream->config; play_state_t* ps = &vgmstream->pstate; if (pc->play_forever || !ps->fade_left) return; - if (ps->play_position + samples_done < ps->fade_start) + if (ps->play_position + sbuf->filled < ps->fade_start) return; int start, fade_pos; - int channels = ps->output_channels; int32_t to_do = ps->fade_left; if (ps->play_position < ps->fade_start) { - start = samples_done - (ps->play_position + samples_done - ps->fade_start); + start = sbuf->filled - (ps->play_position + sbuf->filled - ps->fade_start); fade_pos = 0; } else { @@ -245,73 +229,65 @@ static void play_op_fade(VGMSTREAM* vgmstream, sample_t* buf, int samples_done) fade_pos = ps->play_position - ps->fade_start; } - if (to_do > samples_done - start) - to_do = samples_done - start; + if (to_do > sbuf->filled - start) + to_do = sbuf->filled - start; - //TODO: use delta fadedness to improve performance? - for (int s = start; s < start + to_do; s++, fade_pos++) { - double fadedness = (double)(ps->fade_duration - fade_pos) / ps->fade_duration; - for (int ch = 0; ch < channels; ch++) { - buf[s * channels + ch] = (sample_t)(buf[s*channels + ch] * fadedness); - } - } + sbuf_fadeout(sbuf, start, to_do, fade_pos, ps->fade_duration); ps->fade_left -= to_do; - - /* next samples after fade end would be pad end/silence, so we can just memset */ - memset(buf + (start + to_do) * channels, 0, (samples_done - to_do - start) * sizeof(sample_t) * channels); } // adds null samples after decode // pad-end works like fades, where part of buf is samples and part is padding (blank) // (beyond pad end normally is silence, except with segmented layout) -static int play_op_pad_end(VGMSTREAM* vgmstream, sample_t* buf, int samples_done) { +static void play_op_pad_end(VGMSTREAM* vgmstream, sbuf_t* sbuf) { play_config_t* pc = &vgmstream->config; play_state_t* ps = &vgmstream->pstate; if (pc->play_forever) - return 0; - if (samples_done == 0) - return 0; - if (ps->play_position + samples_done < ps->pad_end_start) - return 0; - - int channels = vgmstream->pstate.output_channels; - int skip = 0; - int32_t to_do; + return; + if (ps->play_position + sbuf->filled < ps->pad_end_start) + return; + int start; + int to_do; if (ps->play_position < ps->pad_end_start) { - skip = ps->pad_end_start - ps->play_position; + start = ps->pad_end_start - ps->play_position; to_do = ps->pad_end_duration; } else { - skip = 0; + start = 0; to_do = (ps->pad_end_start + ps->pad_end_duration) - ps->play_position; } - if (to_do > samples_done - skip) - to_do = samples_done - skip; + if (to_do > sbuf->filled - start) + to_do = sbuf->filled - start; - memset(buf + (skip * channels), 0, to_do * sizeof(sample_t) * channels); - return skip + to_do; + sbuf_silence_part(sbuf, start, to_do); + + //TO-DO: this never adds samples and just silences them, since decoder always returns something + //sbuf->filled += ? } -// clamp final play_position + done samples. Probably doesn't matter, but just in case. -static void play_adjust_totals(VGMSTREAM* vgmstream, render_helper_t* renderer, int sample_count) { +// clamp final play_position + done samples. Probably doesn't matter (maybe for plugins checking length), but just in case. +static void play_adjust_totals(VGMSTREAM* vgmstream, sbuf_t* sbuf) { play_state_t* ps = &vgmstream->pstate; - ps->play_position += renderer->samples_done; + ps->play_position += sbuf->filled; + + if (vgmstream->config.play_forever) + return; + if (ps->play_position <= ps->play_duration) + return; /* usually only happens when mixing layers of different lengths (where decoder keeps calling render) */ - if (!vgmstream->config.play_forever && ps->play_position > ps->play_duration) { - int excess = ps->play_position - ps->play_duration; - if (excess > sample_count) - excess = sample_count; + int excess = ps->play_position - ps->play_duration; + if (excess > sbuf->samples) + excess = sbuf->samples; + sbuf->filled = (sbuf->samples - excess); - renderer->samples_done = (sample_count - excess); - - ps->play_position = ps->play_duration; - } + /* clamp */ + ps->play_position = ps->play_duration; } /*****************************************************************************/ @@ -324,51 +300,54 @@ static void play_adjust_totals(VGMSTREAM* vgmstream, render_helper_t* renderer, * \ end-fade | * * Which part we are in depends on play_position. Because vgmstream render's - * buf may fall anywhere in the middle of all that. Since some ops add "fake" (non-decoded) - * samples to buf, we need to + * buf may fall anywhere in the middle of all that. Some ops add "fake" (non-decoded) + * samples to buf. */ -int render_vgmstream(sample_t* buf, int32_t sample_count, VGMSTREAM* vgmstream) { - render_helper_t renderer = {0}; - renderer.tmpbuf = buf; - renderer.samples_done = 0; - renderer.samples_to_do = sample_count; +int render_main(sbuf_t* sbuf, VGMSTREAM* vgmstream) { - //sbuf_init16(&renderer.sbuf, buf, sample_count, vgmstream->channels); - - - /* simple mode with no settings (just skip everything below) */ + /* simple mode with no play settings (just skip everything below) */ if (!vgmstream->config_enabled) { - render_layout(buf, renderer.samples_to_do, vgmstream); - mix_vgmstream(buf, renderer.samples_to_do, vgmstream); - return renderer.samples_to_do; + render_layout(sbuf, vgmstream); + sbuf->filled = sbuf->samples; + + mix_vgmstream(sbuf, vgmstream); + + return sbuf->filled; } + /* trim decoder output (may go anywhere before main render since it doesn't use render output, but easier first) */ + play_op_trim(vgmstream, sbuf); + /* adds empty samples to buf and moves it */ - play_op_pad_begin(vgmstream, &renderer); - - /* trim decoder output (may go anywhere before main render since it doesn't use render output) */ - play_op_trim(vgmstream, &renderer); + play_op_pad_begin(vgmstream, sbuf); - /* main decode */ - int done = render_layout(renderer.tmpbuf, renderer.samples_to_do, vgmstream); - mix_vgmstream(renderer.tmpbuf, done, vgmstream); + /* main decode (use temp buf to "consume") */ + sbuf_t sbuf_tmp = *sbuf; + sbuf_consume(&sbuf_tmp, sbuf_tmp.filled); + int done = render_layout(&sbuf_tmp, vgmstream); + sbuf->filled += done; + mix_vgmstream(sbuf, vgmstream); /* simple fadeout over decoded data (after mixing since usually results in less samples) */ - play_op_fade(vgmstream, renderer.tmpbuf, done); + play_op_fade(vgmstream, sbuf); + /* silence leftover buf samples (after fade as rarely may mix decoded buf + trim samples when no fade is set) * (could be done before render to "consume" buf but doesn't matter much) */ - play_op_pad_end(vgmstream, renderer.tmpbuf, done); - - renderer.samples_done += done; - //renderer.samples_to_do -= done; //not useful at this point - //renderer.tmpbuf += done * vgmstream->pstate.output_channels; + play_op_pad_end(vgmstream, sbuf); - play_adjust_totals(vgmstream, &renderer, sample_count); - return renderer.samples_done; + play_adjust_totals(vgmstream, sbuf); + return sbuf->filled; +} + +int render_vgmstream(sample_t* buf, int32_t sample_count, VGMSTREAM* vgmstream) { + sbuf_t sbuf = {0}; + sbuf_init_s16(&sbuf, buf, sample_count, vgmstream->channels); + + return render_main(&sbuf, vgmstream); } diff --git a/Frameworks/vgmstream/vgmstream/src/base/render.h b/Frameworks/vgmstream/vgmstream/src/base/render.h index 488546fe6..7f01e56ae 100644 --- a/Frameworks/vgmstream/vgmstream/src/base/render.h +++ b/Frameworks/vgmstream/vgmstream/src/base/render.h @@ -1,11 +1,17 @@ #ifndef _RENDER_H #define _RENDER_H +#ifdef BUILD_VGMSTREAM #include "../vgmstream.h" +#else +#include "vgmstream.h" +#endif +#include "sbuf.h" void render_free(VGMSTREAM* vgmstream); void render_reset(VGMSTREAM* vgmstream); -int render_layout(sample_t* buf, int32_t sample_count, VGMSTREAM* vgmstream); +int render_layout(sbuf_t* sbuf, VGMSTREAM* vgmstream); +int render_main(sbuf_t* sbuf, VGMSTREAM* vgmstream); #endif diff --git a/Frameworks/vgmstream/vgmstream/src/base/sbuf.c b/Frameworks/vgmstream/vgmstream/src/base/sbuf.c index c0c07945e..e6d49c511 100644 --- a/Frameworks/vgmstream/vgmstream/src/base/sbuf.c +++ b/Frameworks/vgmstream/vgmstream/src/base/sbuf.c @@ -1,85 +1,412 @@ #include #include +//#include #include "../util.h" #include "sbuf.h" -#if 0 -/* skips N samples from current sbuf */ -void sbuf_init16(sbuf_t* sbuf, int16_t* buf, int samples, int channels) { + +void sbuf_init(sbuf_t* sbuf, sfmt_t format, void* buf, int samples, int channels) { + memset(sbuf, 0, sizeof(sbuf_t)); + sbuf->buf = buf; + sbuf->samples = samples; + sbuf->channels = channels; + sbuf->fmt = format; +} + +void sbuf_init_s16(sbuf_t* sbuf, int16_t* buf, int samples, int channels) { memset(sbuf, 0, sizeof(sbuf_t)); sbuf->buf = buf; sbuf->samples = samples; sbuf->channels = channels; sbuf->fmt = SFMT_S16; } + +void sbuf_init_f32(sbuf_t* sbuf, float* buf, int samples, int channels) { + memset(sbuf, 0, sizeof(sbuf_t)); + sbuf->buf = buf; + sbuf->samples = samples; + sbuf->channels = channels; + sbuf->fmt = SFMT_F32; +} + + +int sfmt_get_sample_size(sfmt_t fmt) { + switch(fmt) { + case SFMT_F32: + case SFMT_FLT: + return 0x04; + case SFMT_S16: + return 0x02; + default: + return 0; + } +} + +void* sbuf_get_filled_buf(sbuf_t* sbuf) { + int sample_size = sfmt_get_sample_size(sbuf->fmt); + + uint8_t* buf = sbuf->buf; + buf += sbuf->filled * sbuf->channels * sample_size; + return buf; +} + +void sbuf_consume(sbuf_t* sbuf, int count) { + int sample_size = sfmt_get_sample_size(sbuf->fmt); + if (sample_size <= 0) + return; + if (count > sbuf->samples || count > sbuf->filled) //TODO? + return; + + uint8_t* buf = sbuf->buf; + buf += count * sbuf->channels * sample_size; + + sbuf->buf = buf; + sbuf->filled -= count; + sbuf->samples -= count; +} + +/* when casting float to int, value is simply truncated: + * - (int)1.7 = 1, (int)-1.7 = -1 + * alts for more accurate rounding could be: + * - (int)floor(f) + * - (int)(f < 0 ? f - 0.5f : f + 0.5f) + * - (((int) (f1 + 32768.5)) - 32768) + * - etc + * but since +-1 isn't really audible we'll just cast, as it's the fastest + * + * Regular C float-to-int casting ("int i = (int)f") is somewhat slow due to IEEE + * float requirements, but C99 adds some faster-but-less-precise casting functions. + * MSVC added this in VS2015 (_MSC_VER 1900) but doesn't seem inlined and is very slow. + * It's slightly faster (~5%) but causes fuzzy PCM<>float<>PCM conversions. + */ +static inline int float_to_int(float val) { +#if 1 + return (int)val; +#elif defined(_MSC_VER) + return (int)val; +#else + return lrintf(val); #endif - - -// TODO decide if using float 1.0 style or 32767 style (fuzzy PCM changes when doing that) -void sbuf_copy_s16_to_f32(float* buf_f32, int16_t* buf_s16, int samples, int channels) { - for (int s = 0; s < samples * channels; s++) { - buf_f32[s] = (float)buf_s16[s]; // / 32767.0f - } } -void sbuf_copy_f32_to_s16(int16_t* buf_s16, float* buf_f32, int samples, int channels) { - /* when casting float to int, value is simply truncated: - * - (int)1.7 = 1, (int)-1.7 = -1 - * alts for more accurate rounding could be: - * - (int)floor(f) - * - (int)(f < 0 ? f - 0.5f : f + 0.5f) - * - (((int) (f1 + 32768.5)) - 32768) - * - etc - * but since +-1 isn't really audible we'll just cast, as it's the fastest - */ - for (int s = 0; s < samples * channels; s++) { - buf_s16[s] = clamp16( buf_f32[s]); // * 32767.0f - } +static inline int double_to_int(double val) { +#if 1 + return (int)val; +#elif defined(_MSC_VER) + return (int)val; +#else + return lrint(val); +#endif } -void sbuf_copy_samples(sample_t* dst, int dst_channels, sample_t* src, int src_channels, int samples_to_do, int samples_filled) { - int pos = samples_filled * dst_channels; +static inline float double_to_float(double val) { + return (float)val; +} - if (src_channels == dst_channels) { /* most common and probably faster */ - for (int s = 0; s < samples_to_do * dst_channels; s++) { - dst[pos + s] = src[s]; - } - } - else { - for (int s = 0; s < samples_to_do; s++) { - for (int ch = 0; ch < src_channels; ch++) { - dst[pos + s * dst_channels + ch] = src[s * src_channels + ch]; +//TODO decide if using float 1.0 style or 32767 style (fuzzy PCM when doing that) +//TODO: maybe use macro-style templating (but kinda ugly) +void sbuf_copy_to_f32(float* dst, sbuf_t* sbuf) { + + switch(sbuf->fmt) { + case SFMT_S16: { + int16_t* src = sbuf->buf; + for (int s = 0; s < sbuf->filled * sbuf->channels; s++) { + dst[s] = (float)src[s]; // / 32767.0f } - for (int ch = src_channels; ch < dst_channels; ch++) { - dst[pos + s * dst_channels + ch] = 0; + break; + } + + case SFMT_FLT: + case SFMT_F32: { + float* src = sbuf->buf; + for (int s = 0; s < sbuf->filled * sbuf->channels; s++) { + dst[s] = src[s]; + } + break; + } + default: + break; + } +} + +void sbuf_copy_from_f32(sbuf_t* sbuf, float* src) { + switch(sbuf->fmt) { + case SFMT_S16: { + int16_t* dst = sbuf->buf; + for (int s = 0; s < sbuf->filled * sbuf->channels; s++) { + dst[s] = clamp16(float_to_int(src[s])); + } + break; + } + case SFMT_F32: { + float* dst = sbuf->buf; + for (int s = 0; s < sbuf->filled * sbuf->channels; s++) { + dst[s] = src[s]; + } + break; + } + case SFMT_FLT: { + float* dst = sbuf->buf; + for (int s = 0; s < sbuf->filled * sbuf->channels; s++) { + dst[s] = src[s] / 32768.0f; + } + break; + } + default: + break; + } +} + + +/* ugly thing to avoid repeating functions */ +#define sbuf_copy_segments_internal(dst, src, src_pos, dst_pos, src_max) \ + while (src_pos < src_max) { \ + dst[dst_pos++] = src[src_pos++]; \ + } + +#define sbuf_copy_segments_internal_s16(dst, src, src_pos, dst_pos, src_max, value) \ + while (src_pos < src_max) { \ + dst[dst_pos++] = clamp16(float_to_int(src[src_pos++] * value)); \ + } + +#define sbuf_copy_segments_internal_flt(dst, src, src_pos, dst_pos, src_max, value) \ + while (src_pos < src_max) { \ + dst[dst_pos++] = float_to_int(src[src_pos++] * value); \ + } + +void sbuf_copy_segments(sbuf_t* sdst, sbuf_t* ssrc) { + /* uncommon so probably fine albeit slower-ish, 0'd other channels first */ + if (ssrc->channels != sdst->channels) { + sbuf_silence_part(sdst, sdst->filled, ssrc->filled); + sbuf_copy_layers(sdst, ssrc, 0, ssrc->filled); +#if 0 + // "faster" but lots of extra ifs, not worth it + while (src_pos < src_max) { + for (int ch = 0; ch < dst_channels; ch++) { + dst[dst_pos++] = ch >= src_channels ? 0 : src[src_pos++]; } } +#endif + return; + } + + int src_pos = 0; + int dst_pos = sdst->filled * sdst->channels; + int src_max = ssrc->filled * ssrc->channels; + + // define all posible combos, probably there is a better way to handle this but... + + if (sdst->fmt == SFMT_S16 && ssrc->fmt == SFMT_S16) { + int16_t* dst = sdst->buf; + int16_t* src = ssrc->buf; + sbuf_copy_segments_internal(dst, src, src_pos, dst_pos, src_max); + } + else if (sdst->fmt == SFMT_F32 && ssrc->fmt == SFMT_S16) { + float* dst = sdst->buf; + int16_t* src = ssrc->buf; + sbuf_copy_segments_internal(dst, src, src_pos, dst_pos, src_max); + } + else if ((sdst->fmt == SFMT_F32 && ssrc->fmt == SFMT_F32) || (sdst->fmt == SFMT_FLT && ssrc->fmt == SFMT_FLT)) { + float* dst = sdst->buf; + float* src = ssrc->buf; + sbuf_copy_segments_internal(dst, src, src_pos, dst_pos, src_max); + } + // to s16 + else if (sdst->fmt == SFMT_S16 && ssrc->fmt == SFMT_F32) { + int16_t* dst = sdst->buf; + float* src = ssrc->buf; + sbuf_copy_segments_internal_s16(dst, src, src_pos, dst_pos, src_max, 1.0f); + } + else if (sdst->fmt == SFMT_S16 && ssrc->fmt == SFMT_FLT) { + int16_t* dst = sdst->buf; + float* src = ssrc->buf; + sbuf_copy_segments_internal_s16(dst, src, src_pos, dst_pos, src_max, 32768.0f); + } + // to f32 + else if (sdst->fmt == SFMT_F32 && ssrc->fmt == SFMT_FLT) { + float* dst = sdst->buf; + float* src = ssrc->buf; + sbuf_copy_segments_internal_flt(dst, src, src_pos, dst_pos, src_max, 32768.0f); + } + // to flt + else if (sdst->fmt == SFMT_FLT && ssrc->fmt == SFMT_S16) { + float* dst = sdst->buf; + int16_t* src = ssrc->buf; + sbuf_copy_segments_internal_flt(dst, src, src_pos, dst_pos, src_max, (1/32768.0f)); + } + else if (sdst->fmt == SFMT_FLT && ssrc->fmt == SFMT_F32) { + float* dst = sdst->buf; + float* src = ssrc->buf; + sbuf_copy_segments_internal_flt(dst, src, src_pos, dst_pos, src_max, (1/32768.0f)); } } -/* copy interleaving */ -void sbuf_copy_layers(sample_t* dst, int dst_channels, sample_t* src, int src_channels, int samples_to_do, int samples_filled, int dst_ch_start) { - // dst_channels == src_channels isn't likely - for (int src_ch = 0; src_ch < src_channels; src_ch++) { - for (int s = 0; s < samples_to_do; s++) { - int src_pos = s * src_channels + src_ch; - int dst_pos = (samples_filled + s) * dst_channels + dst_ch_start; - dst[dst_pos] = src[src_pos]; +//TODO fix missing ->channels +/* ugly thing to avoid repeating functions */ +#define sbuf_copy_layers_internal(dst, src, src_pos, dst_pos, src_filled, dst_expected, src_channels, dst_ch_step) \ + for (int s = 0; s < src_filled; s++) { \ + for (int src_ch = 0; src_ch < src_channels; src_ch++) { \ + dst[dst_pos++] = src[src_pos++]; \ + } \ + dst_pos += dst_ch_step; \ + } \ + \ + for (int s = src_filled; s < dst_expected; s++) { \ + for (int src_ch = 0; src_ch < src_channels; src_ch++) { \ + dst[dst_pos++] = 0; \ + } \ + dst_pos += dst_ch_step; \ + } + +// float +-1.0 <> pcm +-32768.0 +#define sbuf_copy_layers_internal_s16(dst, src, src_pos, dst_pos, src_filled, dst_expected, src_channels, dst_ch_step, value) \ + for (int s = 0; s < src_filled; s++) { \ + for (int src_ch = 0; src_ch < src_channels; src_ch++) { \ + dst[dst_pos++] = clamp16(float_to_int(src[src_pos++] * value)); \ + } \ + dst_pos += dst_ch_step; \ + } \ + \ + for (int s = src_filled; s < dst_expected; s++) { \ + for (int src_ch = 0; src_ch < src_channels; src_ch++) { \ + dst[dst_pos++] = 0; \ + } \ + dst_pos += dst_ch_step; \ + } + +// float +-1.0 <> pcm +-32768.0 +#define sbuf_copy_layers_internal_flt(dst, src, src_pos, dst_pos, src_filled, dst_expected, src_channels, dst_ch_step, value) \ + for (int s = 0; s < src_filled; s++) { \ + for (int src_ch = 0; src_ch < src_channels; src_ch++) { \ + dst[dst_pos++] = float_to_int(src[src_pos++] * value); \ + } \ + dst_pos += dst_ch_step; \ + } \ + \ + for (int s = src_filled; s < dst_expected; s++) { \ + for (int src_ch = 0; src_ch < src_channels; src_ch++) { \ + dst[dst_pos++] = 0; \ + } \ + dst_pos += dst_ch_step; \ + } + +/* copy interleaving: dst ch1 ch2 ch3 ch4 w/ src ch1 ch2 ch1 ch2 = only fill dst ch1 ch2 */ +// dst_channels == src_channels isn't likely so ignore that optimization +// sometimes one layer has less samples than others and need to 0-fill rest +void sbuf_copy_layers(sbuf_t* sdst, sbuf_t* ssrc, int dst_ch_start, int dst_expected) { + int src_filled = ssrc->filled; + int src_channels = ssrc->channels; + int dst_ch_step = (sdst->channels - ssrc->channels); \ + int src_pos = 0; + int dst_pos = sdst->filled * sdst->channels + dst_ch_start; + + // define all posible combos, probably there is a better way to handle this but... + + // 1:1 + if (sdst->fmt == SFMT_S16 && ssrc->fmt == SFMT_S16) { + int16_t* dst = sdst->buf; + int16_t* src = ssrc->buf; + sbuf_copy_layers_internal(dst, src, src_pos, dst_pos, src_filled, dst_expected, src_channels, dst_ch_step); + } + else if (sdst->fmt == SFMT_F32 && ssrc->fmt == SFMT_S16) { + float* dst = sdst->buf; + int16_t* src = ssrc->buf; + sbuf_copy_layers_internal(dst, src, src_pos, dst_pos, src_filled, dst_expected, src_channels, dst_ch_step); + } + else if ((sdst->fmt == SFMT_F32 && ssrc->fmt == SFMT_F32) || (sdst->fmt == SFMT_FLT && ssrc->fmt == SFMT_FLT)) { + float* dst = sdst->buf; + float* src = ssrc->buf; + sbuf_copy_layers_internal(dst, src, src_pos, dst_pos, src_filled, dst_expected, src_channels, dst_ch_step); + } + // to s16 + else if (sdst->fmt == SFMT_S16 && ssrc->fmt == SFMT_F32) { + int16_t* dst = sdst->buf; + float* src = ssrc->buf; + sbuf_copy_layers_internal_s16(dst, src, src_pos, dst_pos, src_filled, dst_expected, src_channels, dst_ch_step, 1.0f); + } + else if (sdst->fmt == SFMT_S16 && ssrc->fmt == SFMT_FLT) { + int16_t* dst = sdst->buf; + float* src = ssrc->buf; + sbuf_copy_layers_internal_s16(dst, src, src_pos, dst_pos, src_filled, dst_expected, src_channels, dst_ch_step, 32768.0f); + } + // to f32 + else if (sdst->fmt == SFMT_F32 && ssrc->fmt == SFMT_FLT) { + float* dst = sdst->buf; + float* src = ssrc->buf; + sbuf_copy_layers_internal_flt(dst, src, src_pos, dst_pos, src_filled, dst_expected, src_channels, dst_ch_step, 32768.0f); + } + // to flt + else if (sdst->fmt == SFMT_FLT && ssrc->fmt == SFMT_S16) { + float* dst = sdst->buf; + int16_t* src = ssrc->buf; + sbuf_copy_layers_internal_flt(dst, src, src_pos, dst_pos, src_filled, dst_expected, src_channels, dst_ch_step, (1/32768.0f)); + } + else if (sdst->fmt == SFMT_FLT && ssrc->fmt == SFMT_F32) { + float* dst = sdst->buf; + float* src = ssrc->buf; + sbuf_copy_layers_internal_flt(dst, src, src_pos, dst_pos, src_filled, dst_expected, src_channels, dst_ch_step, (1/32768.0f)); + } +} + +void sbuf_silence_s16(sample_t* dst, int samples, int channels, int filled) { + memset(dst + filled * channels, 0, (samples - filled) * channels * sizeof(sample_t)); +} + +void sbuf_silence_part(sbuf_t* sbuf, int from, int count) { + int sample_size = sfmt_get_sample_size(sbuf->fmt); + + uint8_t* buf = sbuf->buf; + buf += from * sbuf->channels * sample_size; + memset(buf, 0, count * sbuf->channels * sample_size); +} + +void sbuf_silence_rest(sbuf_t* sbuf) { + sbuf_silence_part(sbuf, sbuf->filled, sbuf->samples - sbuf->filled); +} + +void sbuf_fadeout(sbuf_t* sbuf, int start, int to_do, int fade_pos, int fade_duration) { + //TODO: use interpolated fadedness to improve performance? + //TODO: use float fadedness? + + int s = start * sbuf->channels; + int s_end = (start + to_do) * sbuf->channels; + + + switch(sbuf->fmt) { + case SFMT_S16: { + int16_t* buf = sbuf->buf; + while (s < s_end) { + double fadedness = (double)(fade_duration - fade_pos) / fade_duration; + fade_pos++; + + for (int ch = 0; ch < sbuf->channels; ch++) { + buf[s] = double_to_int(buf[s] * fadedness); + s++; + } + } + break; } - dst_ch_start++; + case SFMT_FLT: + case SFMT_F32: { + float* buf = sbuf->buf; + while (s < s_end) { + double fadedness = (double)(fade_duration - fade_pos) / fade_duration; + fade_pos++; + + for (int ch = 0; ch < sbuf->channels; ch++) { + buf[s] = double_to_float(buf[s] * fadedness); + s++; + } + } + break; + } + default: + break; } -} -void sbuf_silence(sample_t* dst, int samples, int channels, int filled) { - memset(dst + filled * channels, 0, (samples - filled) * channels * sizeof(sample_t)); -} - -bool sbuf_realloc(sample_t** dst, int samples, int channels) { - sample_t* outbuf_re = realloc(*dst, samples * channels * sizeof(sample_t)); - if (!outbuf_re) return false; - - *dst = outbuf_re; - return true; + /* next samples after fade end would be pad end/silence */ + int count = sbuf->filled - (start + to_do); + sbuf_silence_part(sbuf, start + to_do, count); } diff --git a/Frameworks/vgmstream/vgmstream/src/base/sbuf.h b/Frameworks/vgmstream/vgmstream/src/base/sbuf.h index 3d11b4df4..02a56061c 100644 --- a/Frameworks/vgmstream/vgmstream/src/base/sbuf.h +++ b/Frameworks/vgmstream/vgmstream/src/base/sbuf.h @@ -1,52 +1,57 @@ #ifndef _SBUF_H_ #define _SBUF_H_ +#ifdef BUILD_VGMSTREAM #include "../streamtypes.h" +#else +#include "streamtypes.h" +#endif -#if 0 -/* interleaved: buffer for all channels = [ch*s] = (ch1 ch2 ch1 ch2 ch1 ch2 ch1 ch2 ...) */ -/* planar: buffer per channel = [ch][s] = (c1 c1 c1 c1 ...) (c2 c2 c2 c2 ...) */ +/* All types are interleaved (buffer for all channels = [ch*s] = ch1 ch2 ch1 ch2 ch1 ch2 ...) + * rather than planar (buffer per channel = [ch][s] = c1 c1 c1 c1 ... c2 c2 c2 c2 ...) */ typedef enum { SFMT_NONE, - SFMT_S16, - SFMT_F32, + SFMT_S16, /* standard PCM16 */ //SFMT_S24, //SFMT_S32, - //SFMT_S16P, - //SFMT_F32P, + SFMT_F32, /* pcm-like float (+-32768), for internal use (simpler pcm > f32 plus some decoders use this) */ + SFMT_FLT, /* standard float (+-1.0), for external players */ } sfmt_t; +/* simple buffer info to pass around, for internal mixing + * meant to held existing sound buffer pointers rather than alloc'ing directly (some ops will swap/move its internals) */ typedef struct { void* buf; /* current sample buffer */ - int samples; /* max samples */ - int channels; /* interleaved step or planar buffers */ sfmt_t fmt; /* buffer type */ - //int filled; /* samples in buffer */ - //int planar; + int channels; /* interleaved step or planar buffers */ + int samples; /* max samples */ + int filled; /* samples in buffer */ } sbuf_t; -void sbuf_init16(sbuf_t* sbuf, int16_t* buf, int samples, int channels); +/* it's probably slightly faster to make some function inline'd, but aren't called that often to matter (given big enough total samples) */ -void sbuf_clamp(sbuf_t* sbuf, int samples); +void sbuf_init(sbuf_t* sbuf, sfmt_t format, void* buf, int samples, int channels); +void sbuf_init_s16(sbuf_t* sbuf, int16_t* buf, int samples, int channels); +void sbuf_init_f32(sbuf_t* sbuf, float* buf, int samples, int channels); -/* skips N samples from current sbuf */ -void sbuf_consume(sbuf_t* sbuf, int samples); -#endif +int sfmt_get_sample_size(sfmt_t fmt); -/* it's probably slightly faster to make those inline'd, but aren't called that often to matter (given big enough total samples) */ +void* sbuf_get_filled_buf(sbuf_t* sbuf); -// TODO decide if using float 1.0 style or 32767 style (fuzzy PCM changes when doing that) -void sbuf_copy_s16_to_f32(float* buf_f32, int16_t* buf_s16, int samples, int channels); +/* move buf by samples amount to simplify some code (will lose base buf pointer) */ +void sbuf_consume(sbuf_t* sbuf, int count); -void sbuf_copy_f32_to_s16(int16_t* buf_s16, float* buf_f32, int samples, int channels); +/* helpers to copy between buffers; note they assume dst and src aren't the same buf */ +void sbuf_copy_to_f32(float* dst, sbuf_t* sbuf); +void sbuf_copy_from_f32(sbuf_t* sbuf, float* src); +void sbuf_copy_segments(sbuf_t* sdst, sbuf_t* ssrc); +void sbuf_copy_layers(sbuf_t* sdst, sbuf_t* ssrc, int dst_ch_start, int expected); -void sbuf_copy_samples(sample_t* dst, int dst_channels, sample_t* src, int src_channels, int samples_to_do, int samples_filled); +void sbuf_silence_s16(sample_t* dst, int samples, int channels, int filled); +void sbuf_silence_rest(sbuf_t* sbuf); +void sbuf_silence_part(sbuf_t* sbuf, int from, int count); -void sbuf_copy_layers(sample_t* dst, int dst_channels, sample_t* src, int src_channels, int samples_to_do, int samples_filled, int dst_ch_start); - -void sbuf_silence(sample_t* dst, int samples, int channels, int filled); - -bool sbuf_realloc(sample_t** dst, int samples, int channels); +void sbuf_fadeout(sbuf_t* sbuf, int start, int to_do, int fade_pos, int fade_duration); #endif diff --git a/Frameworks/vgmstream/vgmstream/src/base/seek.c b/Frameworks/vgmstream/vgmstream/src/base/seek.c index cfa80b0db..b5a575ecf 100644 --- a/Frameworks/vgmstream/vgmstream/src/base/seek.c +++ b/Frameworks/vgmstream/vgmstream/src/base/seek.c @@ -4,6 +4,7 @@ #include "decode.h" #include "mixing.h" #include "plugins.h" +#include "sbuf.h" /* pretend decoder reached loop end so internal state is set like jumping to loop start * (no effect in some layouts but that is ok) */ @@ -18,18 +19,23 @@ static void seek_force_loop_end(VGMSTREAM* vgmstream, int loop_count) { } 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 */ + void* tmpbuf = vgmstream->tmpbuf; + int buf_samples = vgmstream->tmpbuf_size / vgmstream->channels / sizeof(float); /* base decoder channels, no need to apply mixing */ + + sbuf_t sbuf_tmp; + sbuf_init(&sbuf_tmp, mixing_get_input_sample_type(vgmstream), tmpbuf, buf_samples, vgmstream->channels); while (samples) { int to_do = samples; if (to_do > buf_samples) to_do = buf_samples; + sbuf_tmp.samples = to_do; + render_layout(&sbuf_tmp, vgmstream); - render_layout(tmpbuf, to_do, vgmstream); /* no mixing */ samples -= to_do; + + sbuf_tmp.filled = 0; // discard buf } } diff --git a/Frameworks/vgmstream/vgmstream/src/base/streamfile_api.c b/Frameworks/vgmstream/vgmstream/src/base/streamfile_api.c index 2acd440c7..4ba8deb07 100644 --- a/Frameworks/vgmstream/vgmstream/src/base/streamfile_api.c +++ b/Frameworks/vgmstream/vgmstream/src/base/streamfile_api.c @@ -1,22 +1,22 @@ #include "api_internal.h" #if LIBVGMSTREAM_ENABLE -/* STREAMFILE for internal use, that bridges calls to external libvgmstream_streamfile_t */ +/* STREAMFILE for internal use, that bridges calls to external libstreamfile_t */ -static STREAMFILE* open_api_streamfile_internal(libvgmstream_streamfile_t* libsf, bool external_libsf); +static STREAMFILE* open_api_streamfile_internal(libstreamfile_t* libsf, bool external_libsf); typedef struct { STREAMFILE vt; - libvgmstream_streamfile_t* libsf; + libstreamfile_t* libsf; bool external_libsf; //TODO: improve } API_STREAMFILE; static size_t api_read(API_STREAMFILE* sf, uint8_t* dst, offv_t offset, size_t length) { void* user_data = sf->libsf->user_data; - sf->libsf->seek(sf->libsf->user_data, offset, LIBVGMSTREAM_STREAMFILE_SEEK_SET); + sf->libsf->seek(sf->libsf->user_data, offset, LIBSTREAMFILE_SEEK_SET); return sf->libsf->read(user_data, dst, length); } @@ -46,11 +46,11 @@ static void api_get_name(API_STREAMFILE* sf, char* name, size_t name_size) { } static STREAMFILE* api_open(API_STREAMFILE* sf, const char* filename, size_t buf_size) { - libvgmstream_streamfile_t* libsf = sf->libsf->open(sf->libsf->user_data, filename); + libstreamfile_t* libsf = sf->libsf->open(sf->libsf->user_data, filename); STREAMFILE* new_sf = open_api_streamfile_internal(libsf, false); if (!new_sf) { - libvgmstream_streamfile_close(libsf); + libstreamfile_close(libsf); } return new_sf; @@ -63,7 +63,7 @@ static void api_close(API_STREAMFILE* sf) { free(sf); } -static STREAMFILE* open_api_streamfile_internal(libvgmstream_streamfile_t* libsf, bool external_libsf) { +static STREAMFILE* open_api_streamfile_internal(libstreamfile_t* libsf, bool external_libsf) { API_STREAMFILE* this_sf = NULL; if (!libsf) @@ -87,7 +87,7 @@ static STREAMFILE* open_api_streamfile_internal(libvgmstream_streamfile_t* libsf return &this_sf->vt; } -STREAMFILE* open_api_streamfile(libvgmstream_streamfile_t* libsf) { +STREAMFILE* open_api_streamfile(libstreamfile_t* libsf) { return open_api_streamfile_internal(libsf, true); } diff --git a/Frameworks/vgmstream/vgmstream/src/coding/coding_utils_samples.h b/Frameworks/vgmstream/vgmstream/src/coding/coding_utils_samples.h index 05b55da34..659d30123 100644 --- a/Frameworks/vgmstream/vgmstream/src/coding/coding_utils_samples.h +++ b/Frameworks/vgmstream/vgmstream/src/coding/coding_utils_samples.h @@ -12,7 +12,7 @@ typedef struct { //TODO may be more useful with filled+consumed and not moving *samples? } s16buf_t; -static void s16buf_silence(sample_t** p_outbuf, int32_t* p_samples_silence, int channels) { +static inline void s16buf_silence(sample_t** p_outbuf, int32_t* p_samples_silence, int channels) { int samples_silence; samples_silence = *p_samples_silence; @@ -23,7 +23,7 @@ static void s16buf_silence(sample_t** p_outbuf, int32_t* p_samples_silence, int *p_samples_silence -= samples_silence; } -static void s16buf_discard(sample_t** p_outbuf, s16buf_t* sbuf, int32_t* p_samples_discard) { +static inline void s16buf_discard(sample_t** p_outbuf, s16buf_t* sbuf, int32_t* p_samples_discard) { int samples_discard; samples_discard = *p_samples_discard; @@ -39,7 +39,7 @@ static void s16buf_discard(sample_t** p_outbuf, s16buf_t* sbuf, int32_t* p_sampl } /* copy, move and mark consumed samples */ -static void s16buf_consume(sample_t** p_outbuf, s16buf_t* sbuf, int32_t* p_samples_consume) { +static inline void s16buf_consume(sample_t** p_outbuf, s16buf_t* sbuf, int32_t* p_samples_consume) { int samples_consume; samples_consume = *p_samples_consume; diff --git a/Frameworks/vgmstream/vgmstream/src/coding/ffmpeg_decoder.c b/Frameworks/vgmstream/vgmstream/src/coding/ffmpeg_decoder.c index ddfcad7ce..32ee25d7c 100644 --- a/Frameworks/vgmstream/vgmstream/src/coding/ffmpeg_decoder.c +++ b/Frameworks/vgmstream/vgmstream/src/coding/ffmpeg_decoder.c @@ -432,11 +432,16 @@ fail: /* FFmpeg internals (roughly) for reference: * + * // metadata info first extracted * AVFormatContext // base info extracted from input file * AVStream // substreams * AVCodecParameters // codec id, channels, format, ... * - * AVCodecContext // sample rate and general info + * // codec info passed to 'decode' functions + * AVCodecContext // codec's sample rate, priv data and general info + * AVPacket // encoded data (1 or N frames) passed to decoder + * AVFrame // decoded data (audio samples + channels + etc) received from decoder + * // (bufs are internally managed) * * - open avformat to get all possible format info (needs file or custom IO) * - open avcodec based on target stream + codec info from avformat @@ -444,8 +449,13 @@ fail: * - read next frame into packet via avformat * - decode packet via avcodec * - handle samples -*/ - + * + * In FFmpeg, each "avformat" defines an struct with format info/config and read_probe (detection) + read_header + * (setup format/codec params) + read_packet (demux single frame) functions. Meanwhile "avcodec" defines an struct + * with config and decode_init/close (setup, may use first block's data to init itself or some extradata from + * avformat) + decode_frame (loads AVFrame from AVPacket) + decode_flush (reset state). + * Codec/demuxer contexts aren't alloc'd manually and instead they declare priv data size. + */ static int init_ffmpeg_config(ffmpeg_codec_data* data, int target_subsong, int reset) { int errcode = 0; diff --git a/Frameworks/vgmstream/vgmstream/src/coding/imuse_decoder.c b/Frameworks/vgmstream/vgmstream/src/coding/imuse_decoder.c index 3b9d35f9d..238c7ff8f 100644 --- a/Frameworks/vgmstream/vgmstream/src/coding/imuse_decoder.c +++ b/Frameworks/vgmstream/vgmstream/src/coding/imuse_decoder.c @@ -1,5 +1,5 @@ #include "coding.h" - +#include "coding_utils_samples.h" /* LucasArts' iMUSE decoder, mainly for VIMA (like IMA but with variable frame and code sizes). * Reverse engineered from various .exe @@ -122,31 +122,6 @@ static const int8_t* index_tables_v2[8] = { /* ************************** */ -typedef struct { - /*const*/ int16_t* samples; - int filled; - int channels; - //todo may be more useful with filled/full? use 2 mark methods? -} sbuf_t; - -/* copy, move and mark consumed samples */ -static void sbuf_consume(sample_t** p_outbuf, int32_t* p_samples_to_do, sbuf_t* sbuf) { - int samples_consume; - - samples_consume = *p_samples_to_do; - if (samples_consume > sbuf->filled) - samples_consume = sbuf->filled; - - /* memcpy is safe when filled/samples_copy is 0 (but must pass non-NULL bufs) */ - memcpy(*p_outbuf, sbuf->samples, samples_consume * sbuf->channels * sizeof(int16_t)); - - sbuf->samples += samples_consume * sbuf->channels; - sbuf->filled -= samples_consume; - - *p_outbuf += samples_consume * sbuf->channels; - *p_samples_to_do -= samples_consume; -} - static int clamp_s32(int val, int min, int max) { if (val > max) return max; @@ -174,7 +149,7 @@ struct imuse_codec_data { uint16_t adpcm_table[64 * 89]; /* state */ - sbuf_t sbuf; + s16buf_t sbuf; int current_block; int16_t samples[MAX_BLOCK_SIZE / sizeof(int16_t) * MAX_CHANNELS]; }; @@ -309,7 +284,7 @@ fail: /* **************************************** */ -static void decode_vima1(sbuf_t* sbuf, uint8_t* buf, size_t data_left, int block_num, uint16_t* adpcm_table) { +static void decode_vima1(s16buf_t* sbuf, uint8_t* buf, size_t data_left, int block_num, uint16_t* adpcm_table) { int ch, i, j, s; int bitpos; int adpcm_history[MAX_CHANNELS] = {0}; @@ -434,7 +409,7 @@ static int decode_block1(imuse_codec_data* data, uint8_t* block, size_t data_lef return 1; } -static void decode_data2(sbuf_t* sbuf, uint8_t* buf, size_t data_left, int block_num) { +static void decode_data2(s16buf_t* sbuf, uint8_t* buf, size_t data_left, int block_num) { int i, j; int channels = sbuf->channels; @@ -453,7 +428,7 @@ static void decode_data2(sbuf_t* sbuf, uint8_t* buf, size_t data_left, int block } } -static void decode_vima2(sbuf_t* sbuf, uint8_t* buf, size_t data_left, uint16_t* adpcm_table) { +static void decode_vima2(s16buf_t* sbuf, uint8_t* buf, size_t data_left, uint16_t* adpcm_table) { int ch, i, s; int bitpos; int adpcm_history[MAX_CHANNELS] = {0}; @@ -622,14 +597,14 @@ void decode_imuse(VGMSTREAM* vgmstream, sample_t* outbuf, int32_t samples_to_do) while (samples_to_do > 0) { - sbuf_t* sbuf = &data->sbuf; + s16buf_t* sbuf = &data->sbuf; if (sbuf->filled == 0) { ok = decode_block(sf, data); if (!ok) goto fail; } - sbuf_consume(&outbuf, &samples_to_do, sbuf); + s16buf_consume(&outbuf, sbuf, &samples_to_do); } return; diff --git a/Frameworks/vgmstream/vgmstream/src/coding/mpeg_custom_utils_ealayer3.c b/Frameworks/vgmstream/vgmstream/src/coding/mpeg_custom_utils_ealayer3.c index e86cc55e5..60d0af548 100644 --- a/Frameworks/vgmstream/vgmstream/src/coding/mpeg_custom_utils_ealayer3.c +++ b/Frameworks/vgmstream/vgmstream/src/coding/mpeg_custom_utils_ealayer3.c @@ -698,7 +698,7 @@ static int ealayer3_write_pcm_block(VGMSTREAMCHANNEL* stream, mpeg_codec_data* d bytes_filled = sizeof(sample_t) * ms->samples_filled * channels_per_frame; - if (bytes_filled + eaf->pcm_size > ms->output_buffer_size) { + if (bytes_filled + eaf->pcm_size > ms->sbuf_size) { VGM_LOG("EAL3: can't fill the sample buffer with 0x%x\n", eaf->pcm_size); goto fail; } @@ -709,10 +709,12 @@ static int ealayer3_write_pcm_block(VGMSTREAMCHANNEL* stream, mpeg_codec_data* d } if (eaf->v1_pcm_samples || eaf->v1_offset_samples) { - uint8_t* outbuf = ms->output_buffer + bytes_filled; + uint8_t* outbuf = ms->sbuf; off_t pcm_offset = stream->offset + eaf->pre_size + eaf->common_size; size_t decode_to_discard; + outbuf += bytes_filled; + VGM_ASSERT(eaf->v1_offset_samples > 576, "EAL3: big discard %i at 0x%x\n", eaf->v1_offset_samples, (uint32_t)stream->offset); VGM_ASSERT(eaf->v1_pcm_samples > 0x100, "EAL3: big samples %i at 0x%x\n", eaf->v1_pcm_samples, (uint32_t)stream->offset); VGM_ASSERT(eaf->v1_offset_samples > 0 && eaf->v1_pcm_samples == 0, "EAL3: offset_samples without pcm_samples\n"); /* not seen but could work */ @@ -742,10 +744,12 @@ static int ealayer3_write_pcm_block(VGMSTREAMCHANNEL* stream, mpeg_codec_data* d } if (eaf->v2_extended_flag) { - uint8_t* outbuf = ms->output_buffer + bytes_filled; + uint8_t* outbuf = ms->sbuf; off_t pcm_offset = stream->offset + eaf->pre_size + eaf->common_size; size_t usable_samples, decode_to_discard; + outbuf += bytes_filled; + /* V2P usually only copies big PCM, while V2S discards then copies few samples (similar to V1b). * Unlike V1b, both modes seem to use 'packed' PCM block */ ealayer3_copy_pcm_block(outbuf, pcm_offset, eaf->v2_pcm_samples, channels_per_frame, 1, stream->streamfile); diff --git a/Frameworks/vgmstream/vgmstream/src/coding/mpeg_custom_utils_eamp3.c b/Frameworks/vgmstream/vgmstream/src/coding/mpeg_custom_utils_eamp3.c index 6b59e1e43..1db0b224d 100644 --- a/Frameworks/vgmstream/vgmstream/src/coding/mpeg_custom_utils_eamp3.c +++ b/Frameworks/vgmstream/vgmstream/src/coding/mpeg_custom_utils_eamp3.c @@ -131,7 +131,7 @@ static int eamp3_write_pcm_block(VGMSTREAMCHANNEL *stream, mpeg_codec_data *data bytes_filled = sizeof(sample_t) * ms->samples_filled * data->channels_per_frame; - if (bytes_filled + eaf->pcm_size > ms->output_buffer_size) { + if (bytes_filled + eaf->pcm_size > ms->sbuf_size) { VGM_LOG("EAMP3: can't fill the sample buffer with 0x%x\n", eaf->pcm_size); goto fail; } @@ -143,7 +143,8 @@ static int eamp3_write_pcm_block(VGMSTREAMCHANNEL *stream, mpeg_codec_data *data for (i = 0; i < eaf->pcm_number * data->channels_per_frame; i++) { off_t pcm_offset = stream->offset + eaf->pre_size + eaf->mpeg_size + sizeof(sample_t)*i; int16_t pcm_sample = read_s16le(pcm_offset,stream->streamfile); - put_s16le(ms->output_buffer + bytes_filled + sizeof(sample_t) * i, pcm_sample); + uint8_t* sbuf = ms->sbuf; + put_s16le(sbuf + bytes_filled + sizeof(sample_t) * i, pcm_sample); } ms->samples_filled += eaf->pcm_number; diff --git a/Frameworks/vgmstream/vgmstream/src/coding/mpeg_decoder.c b/Frameworks/vgmstream/vgmstream/src/coding/mpeg_decoder.c index 6363e258b..6dbd9050d 100644 --- a/Frameworks/vgmstream/vgmstream/src/coding/mpeg_decoder.c +++ b/Frameworks/vgmstream/vgmstream/src/coding/mpeg_decoder.c @@ -170,9 +170,9 @@ mpeg_codec_data* init_mpeg_custom(STREAMFILE* sf, off_t start_offset, coding_t* if (!data->streams[i].handle) goto fail; /* size could be any value */ - data->streams[i].output_buffer_size = sizeof(sample_t) * data->channels_per_frame * data->samples_per_frame; - data->streams[i].output_buffer = calloc(data->streams[i].output_buffer_size, sizeof(uint8_t)); - if (!data->streams[i].output_buffer) goto fail; + data->streams[i].sbuf_size = sizeof(sample_t) * data->channels_per_frame * data->samples_per_frame; + data->streams[i].sbuf = calloc(data->streams[i].sbuf_size, sizeof(uint8_t)); + if (!data->streams[i].sbuf) goto fail; /* one per stream as sometimes mpg123 can't read the whole buffer in one pass */ data->streams[i].buffer_size = data->default_buffer_size; @@ -246,16 +246,15 @@ void decode_mpeg(VGMSTREAM* vgmstream, sample_t* outbuf, int32_t samples_to_do, * Feeds raw data and extracts decoded samples as needed. */ static void decode_mpeg_standard(VGMSTREAMCHANNEL* stream, mpeg_codec_data* data, sample_t* outbuf, int32_t samples_to_do, int channels) { - int samples_done = 0; - unsigned char *outbytes = (unsigned char *)outbuf; + int samples_done = 0; while (samples_done < samples_to_do) { size_t bytes_done; int rc, bytes_to_do; /* read more raw data */ if (!data->buffer_full) { - data->bytes_in_buffer = read_streamfile(data->buffer,stream->offset,data->buffer_size,stream->streamfile); + data->bytes_in_buffer = read_streamfile(data->buffer, stream->offset, data->buffer_size, stream->streamfile); /* end of stream, fill rest with 0s */ if (data->bytes_in_buffer <= 0) { @@ -264,32 +263,32 @@ static void decode_mpeg_standard(VGMSTREAMCHANNEL* stream, mpeg_codec_data* data break; } - data->buffer_full = 1; - data->buffer_used = 0; + data->buffer_full = true; + data->buffer_used = false; stream->offset += data->bytes_in_buffer; } - bytes_to_do = (samples_to_do-samples_done)*sizeof(sample_t)*channels; + bytes_to_do = (samples_to_do-samples_done) * channels * sizeof(sample_t); /* feed new raw data to the decoder if needed, copy decoded results to output */ if (!data->buffer_used) { - rc = mpg123_decode(data->m, data->buffer,data->bytes_in_buffer, outbytes, bytes_to_do, &bytes_done); - data->buffer_used = 1; + rc = mpg123_decode(data->m, data->buffer, data->bytes_in_buffer, outbuf, bytes_to_do, &bytes_done); + data->buffer_used = true; } else { - rc = mpg123_decode(data->m, NULL,0, outbytes, bytes_to_do, &bytes_done); + rc = mpg123_decode(data->m, NULL,0, outbuf, bytes_to_do, &bytes_done); } /* not enough raw data, request more */ if (rc == MPG123_NEED_MORE) { - data->buffer_full = 0; + data->buffer_full = false; } VGM_ASSERT(rc != MPG123_NEED_MORE && rc != MPG123_OK, "MPEG: error %i\n", rc); /* update copied samples */ samples_done += bytes_done / sizeof(sample_t) / channels; - outbytes += bytes_done; + outbuf += bytes_done / sizeof(sample_t); } } @@ -301,14 +300,14 @@ static void decode_mpeg_standard(VGMSTREAMCHANNEL* stream, mpeg_codec_data* data . Depletes the stream's sample buffers before decoding more, so it doesn't run out of buffer space. */ static void decode_mpeg_custom(VGMSTREAM* vgmstream, mpeg_codec_data* data, sample_t* outbuf, int32_t samples_to_do, int channels) { - int i, samples_done = 0; + int samples_done = 0; while (samples_done < samples_to_do) { int samples_to_copy = -1; /* find max to copy from all streams (equal for all channels) */ - for (i = 0; i < data->streams_size; i++) { - size_t samples_in_stream = data->streams[i].samples_filled - data->streams[i].samples_used; + for (int i = 0; i < data->streams_size; i++) { + size_t samples_in_stream = data->streams[i].samples_filled - data->streams[i].samples_used; if (samples_to_copy < 0 || samples_in_stream < samples_to_copy) samples_to_copy = samples_in_stream; } @@ -320,7 +319,7 @@ static void decode_mpeg_custom(VGMSTREAM* vgmstream, mpeg_codec_data* data, samp if (samples_to_discard > data->samples_to_discard) samples_to_discard = data->samples_to_discard; - for (i = 0; i < data->streams_size; i++) { + for (int i = 0; i < data->streams_size; i++) { data->streams[i].samples_used += samples_to_discard; } data->samples_to_discard -= samples_to_discard; @@ -329,24 +328,21 @@ static void decode_mpeg_custom(VGMSTREAM* vgmstream, mpeg_codec_data* data, samp /* mux streams channels (1/2ch combos) to outbuf (Nch) */ if (samples_to_copy > 0) { - int ch, stream; - if (samples_to_copy > samples_to_do - samples_done) samples_to_copy = samples_to_do - samples_done; - ch = 0; - for (stream = 0; stream < data->streams_size; stream++) { + int ch = 0; + for (int stream = 0; stream < data->streams_size; stream++) { mpeg_custom_stream* ms = &data->streams[stream]; - sample_t* inbuf = (sample_t *)ms->output_buffer; + sample_t* sbuf = ms->sbuf; int stream_channels = ms->channels_per_frame; - int stream_ch, s; - for (stream_ch = 0; stream_ch < stream_channels; stream_ch++) { - for (s = 0; s < samples_to_copy; s++) { + for (int stream_ch = 0; stream_ch < stream_channels; stream_ch++) { + for (int s = 0; s < samples_to_copy; s++) { size_t stream_sample = (ms->samples_used+s)*stream_channels + stream_ch; size_t buffer_sample = (samples_done+s)*channels + ch; - outbuf[buffer_sample] = inbuf[stream_sample]; + outbuf[buffer_sample] = sbuf[stream_sample]; } ch++; } @@ -361,7 +357,7 @@ static void decode_mpeg_custom(VGMSTREAM* vgmstream, mpeg_codec_data* data, samp /* Handle offsets depending on the data layout (may only use half VGMSTREAMCHANNELs with 2ch streams) * With multiple offsets they should already start in the first frame of each stream. */ - for (i=0; i < data->streams_size; i++) { + for (int i = 0; i < data->streams_size; i++) { switch(data->type) { //case MPEG_FSB: /* same offset: alternate frames between streams (maybe needed for weird layouts?) */ @@ -385,6 +381,7 @@ static void decode_mpeg_custom_stream(VGMSTREAMCHANNEL* stream, mpeg_codec_data* int rc, ok; mpeg_custom_stream* ms = &data->streams[num_stream]; int channels_per_frame = ms->channels_per_frame; + uint8_t* sbuf = ms->sbuf; //;VGM_LOG("MPEG: decode stream%i @ 0x%08lx (filled=%i, used=%i, buffer_full=%i)\n", num_stream, stream->offset, ms->samples_filled, ms->samples_used, ms->buffer_full); @@ -424,8 +421,8 @@ static void decode_mpeg_custom_stream(VGMSTREAMCHANNEL* stream, mpeg_codec_data* /* parse frame may not touch the buffer (only move offset, or fill the sample buffer) */ if (ms->bytes_in_buffer) { - ms->buffer_full = 1; - ms->buffer_used = 0; + ms->buffer_full = true; + ms->buffer_used = false; } } @@ -436,26 +433,25 @@ static void decode_mpeg_custom_stream(VGMSTREAMCHANNEL* stream, mpeg_codec_data* //;VGM_LOG("MPEG: feed new data and get samples\n"); rc = mpg123_decode(ms->handle, ms->buffer, ms->bytes_in_buffer, - (unsigned char*)ms->output_buffer + bytes_filled, ms->output_buffer_size - bytes_filled, + sbuf + bytes_filled, ms->sbuf_size - bytes_filled, &bytes_done); - ms->buffer_used = 1; + ms->buffer_used = true; } else { //;VGM_LOG("MPEG: get samples from old data\n"); rc = mpg123_decode(ms->handle, NULL, 0, - (unsigned char*)ms->output_buffer + bytes_filled, ms->output_buffer_size - bytes_filled, + sbuf + bytes_filled, ms->sbuf_size - bytes_filled, &bytes_done); } - samples_filled = (bytes_done / sizeof(sample_t) / channels_per_frame); + samples_filled = bytes_done / channels_per_frame / sizeof(sample_t); /* discard for weird features (EALayer3 and PCM blocks, AWC and repeated frames) */ if (ms->decode_to_discard) { - size_t bytes_to_discard = 0; size_t decode_to_discard = ms->decode_to_discard; if (decode_to_discard > samples_filled) decode_to_discard = samples_filled; - bytes_to_discard = sizeof(sample_t) * decode_to_discard * channels_per_frame; + size_t bytes_to_discard = sizeof(sample_t) * decode_to_discard * channels_per_frame; bytes_done -= bytes_to_discard; ms->decode_to_discard -= decode_to_discard; @@ -469,7 +465,7 @@ static void decode_mpeg_custom_stream(VGMSTREAMCHANNEL* stream, mpeg_codec_data* * (but only with empty mpg123 buffer, EA blocks wait for all samples decoded before advancing blocks) */ if (!bytes_done && rc == MPG123_NEED_MORE) { //;VGM_LOG("MPEG: need more raw data to get samples (bytes_done=%x)\n", bytes_done); - ms->buffer_full = 0; + ms->buffer_full = false; } @@ -479,8 +475,8 @@ static void decode_mpeg_custom_stream(VGMSTREAMCHANNEL* stream, mpeg_codec_data* decode_fail: /* 0-fill but continue with other streams */ bytes_filled = ms->samples_filled * channels_per_frame * sizeof(sample_t); - memset(ms->output_buffer + bytes_filled, 0, ms->output_buffer_size - bytes_filled); - ms->samples_filled = (ms->output_buffer_size / channels_per_frame / sizeof(sample_t)); + memset(sbuf + bytes_filled, 0, ms->sbuf_size - bytes_filled); + ms->samples_filled = (ms->sbuf_size / channels_per_frame / sizeof(sample_t)); } @@ -503,7 +499,7 @@ void free_mpeg(mpeg_codec_data* data) { continue; mpg123_delete(data->streams[i].handle); free(data->streams[i].buffer); - free(data->streams[i].output_buffer); + free(data->streams[i].sbuf); } free(data->streams); } @@ -586,9 +582,8 @@ static void flush_mpeg(mpeg_codec_data* data, int is_loop) { mpg123_open_feed(data->m); /* mpg123_feedseek won't work */ } else { - int i; /* re-start from 0 */ - for (i=0; i < data->streams_size; i++) { + for (int i = 0; i < data->streams_size; i++) { if (!data->streams) continue; @@ -597,8 +592,8 @@ static void flush_mpeg(mpeg_codec_data* data, int is_loop) { if (is_loop && data->custom && !(data->type == MPEG_FSB)) mpg123_open_feed(data->streams[i].handle); data->streams[i].bytes_in_buffer = 0; - data->streams[i].buffer_full = 0; - data->streams[i].buffer_used = 0; + data->streams[i].buffer_full = false; + data->streams[i].buffer_used = false; data->streams[i].samples_filled = 0; data->streams[i].samples_used = 0; data->streams[i].current_size_count = 0; @@ -610,8 +605,8 @@ static void flush_mpeg(mpeg_codec_data* data, int is_loop) { } data->bytes_in_buffer = 0; - data->buffer_full = 0; - data->buffer_used = 0; + data->buffer_full = false; + data->buffer_used = false; } int mpeg_get_sample_rate(mpeg_codec_data* data) { diff --git a/Frameworks/vgmstream/vgmstream/src/coding/mpeg_decoder.h b/Frameworks/vgmstream/vgmstream/src/coding/mpeg_decoder.h index b6ce50c76..49b0d2e76 100644 --- a/Frameworks/vgmstream/vgmstream/src/coding/mpeg_decoder.h +++ b/Frameworks/vgmstream/vgmstream/src/coding/mpeg_decoder.h @@ -12,16 +12,16 @@ /* represents a single MPEG stream */ typedef struct { /* per stream as sometimes mpg123 must be fed in passes if data is big enough (ex. EALayer3 multichannel) */ - uint8_t *buffer; /* raw data buffer */ + uint8_t* buffer; /* raw data buffer */ size_t buffer_size; size_t bytes_in_buffer; - int buffer_full; /* raw buffer has been filled */ - int buffer_used; /* raw buffer has been fed to the decoder */ + bool buffer_full; /* raw buffer has been filled */ + bool buffer_used; /* raw buffer has been fed to the decoder */ mpg123_handle* handle; /* MPEG decoder */ - uint8_t *output_buffer; /* decoded samples from this stream (in bytes for mpg123) */ - size_t output_buffer_size; + void* sbuf; /* decoded samples from this stream */ + size_t sbuf_size; /* in bytes for mpg123 */ size_t samples_filled; /* data in the buffer (in samples) */ size_t samples_used; /* data extracted from the buffer */ @@ -37,9 +37,10 @@ struct mpeg_codec_data { uint8_t* buffer; /* raw data buffer */ size_t buffer_size; size_t bytes_in_buffer; - int buffer_full; /* raw buffer has been filled */ - int buffer_used; /* raw buffer has been fed to the decoder */ - mpg123_handle *m; /* MPEG decoder */ + bool buffer_full; /* raw buffer has been filled */ + bool buffer_used; /* raw buffer has been fed to the decoder */ + + mpg123_handle* m; /* MPEG decoder */ struct mpg123_frameinfo mi; /* start info, so it's available even when resetting */ /* for internal use */ diff --git a/Frameworks/vgmstream/vgmstream/src/coding/tac_decoder.c b/Frameworks/vgmstream/vgmstream/src/coding/tac_decoder.c index fc4ed6960..5bf1f6fa8 100644 --- a/Frameworks/vgmstream/vgmstream/src/coding/tac_decoder.c +++ b/Frameworks/vgmstream/vgmstream/src/coding/tac_decoder.c @@ -12,10 +12,10 @@ struct tac_codec_data { int encoder_delay; uint8_t buf[TAC_BLOCK_SIZE]; - int feed_block; + bool feed_block; off_t offset; - int16_t* samples; + int16_t samples[TAC_FRAME_SAMPLES * TAC_CHANNELS]; int frame_samples; /* frame state */ @@ -38,7 +38,7 @@ tac_codec_data* init_tac(STREAMFILE* sf) { data->handle = tac_init(data->buf, bytes); if (!data->handle) goto fail; - data->feed_block = 0; /* ok to use current block */ + data->feed_block = false; /* ok to use current block */ data->offset = bytes; data->channels = TAC_CHANNELS; data->frame_samples = TAC_FRAME_SAMPLES; @@ -46,9 +46,6 @@ tac_codec_data* init_tac(STREAMFILE* sf) { data->encoder_delay = 0; data->samples_discard = data->encoder_delay; - data->samples = malloc(data->channels * data->frame_samples * sizeof(int16_t)); - if (!data->samples) goto fail; - return data; fail: free_tac(data); @@ -56,58 +53,55 @@ fail: } -static int decode_frame(tac_codec_data* data) { +static bool decode_frame(tac_codec_data* data) { int err; data->sbuf.samples = data->samples; - data->sbuf.channels = 2; + data->sbuf.channels = data->channels; data->sbuf.filled = 0; err = tac_decode_frame(data->handle, data->buf); if (err == TAC_PROCESS_NEXT_BLOCK) { - data->feed_block = 1; - return 1; + data->feed_block = true; + return true; } if (err == TAC_PROCESS_DONE) { VGM_LOG("TAC: process done (EOF) %i\n", err); - goto fail; /* shouldn't reach this */ + return false; /* shouldn't reach this */ } if (err != TAC_PROCESS_OK) { VGM_LOG("TAC: process error %i\n", err); - goto fail; + return false; } tac_get_samples_pcm16(data->handle, data->sbuf.samples); data->sbuf.filled = data->frame_samples; - return 1; -fail: - return 0; + return true; } -static int read_frame(tac_codec_data* data, STREAMFILE* sf) { +static bool read_frame(tac_codec_data* data, STREAMFILE* sf) { /* new block must be read only when signaled by lib */ - if (data->feed_block) { - int bytes = read_streamfile(data->buf, data->offset, sizeof(data->buf), sf); - data->offset += bytes; - data->feed_block = 0; - if (bytes <= 0) goto fail; /* can read less that buf near EOF */ - } + if (!data->feed_block) + return true; + + int bytes = read_streamfile(data->buf, data->offset, sizeof(data->buf), sf); + data->offset += bytes; + data->feed_block = 0; + if (bytes <= 0) return false; /* can read less that buf near EOF */ - return 1; -fail: - return 0; + return true; } void decode_tac(VGMSTREAM* vgmstream, sample_t* outbuf, int32_t samples_to_do) { VGMSTREAMCHANNEL* stream = &vgmstream->ch[0]; tac_codec_data* data = vgmstream->codec_data; - int ok; + bool ok; while (samples_to_do > 0) { @@ -132,7 +126,7 @@ void decode_tac(VGMSTREAM* vgmstream, sample_t* outbuf, int32_t samples_to_do) { fail: /* on error just put some 0 samples */ VGM_LOG("TAC: decode fail at %x, missing %i samples\n", (uint32_t)data->offset, samples_to_do); - s16buf_silence(&outbuf, &samples_to_do, data->channels); + s16buf_silence(&outbuf, &samples_to_do, data->sbuf.channels); } @@ -141,12 +135,10 @@ void reset_tac(tac_codec_data* data) { tac_reset(data->handle); - data->offset = 0; - data->feed_block = 1; + data->feed_block = true; + data->offset = 0x00; data->sbuf.filled = 0; data->samples_discard = data->encoder_delay; - - return; } void seek_tac(tac_codec_data* data, int32_t num_sample) { @@ -162,18 +154,18 @@ void seek_tac(tac_codec_data* data, int32_t num_sample) { if (loop_sample == num_sample) { tac_set_loop(data->handle); /* direct looping */ - data->samples_discard = hdr->loop_discard; + data->feed_block = true; data->offset = hdr->loop_offset; - data->feed_block = 1; data->sbuf.filled = 0; + data->samples_discard = hdr->loop_discard; } else { tac_reset(data->handle); - data->samples_discard = num_sample; - data->offset = 0; - data->feed_block = 1; + data->feed_block = true; + data->offset = 0x00; data->sbuf.filled = 0; + data->samples_discard = num_sample; } } @@ -182,6 +174,5 @@ void free_tac(tac_codec_data* data) { return; tac_free(data->handle); - free(data->samples); free(data); } diff --git a/Frameworks/vgmstream/vgmstream/src/formats.c b/Frameworks/vgmstream/vgmstream/src/formats.c index 9bf6e1238..ef4fbde15 100644 --- a/Frameworks/vgmstream/vgmstream/src/formats.c +++ b/Frameworks/vgmstream/vgmstream/src/formats.c @@ -392,7 +392,7 @@ static const char* extension_list[] = { "npsf", //fake extension/header id for .nps (in bigfiles) "nsa", "nsopus", - "ntx", + "nfx", "nub", "nub2", "nus3audio", @@ -454,6 +454,7 @@ static const char* extension_list[] = { "rsnd", //txth/reserved [Birushana: Ichijuu no Kaze (Switch)] "rsp", "rstm", //fake extension/header id for .rstm (in bigfiles) + "rvw", //txth/reserved [Half-Minute Hero (PC)] "rvws", "rwar", "rwav", @@ -497,6 +498,7 @@ static const char* extension_list[] = { "scd", "sch", "sd9", + "sdl", "sdp", //txth/reserved [Metal Gear Arcade (AC)] "sdf", "sdt", @@ -586,6 +588,7 @@ static const char* extension_list[] = { "utk", "uv", + "v", "v0", //"v1", //dual channel with v0 "va3", diff --git a/Frameworks/vgmstream/vgmstream/src/layout/blocked.c b/Frameworks/vgmstream/vgmstream/src/layout/blocked.c index 1684604d3..ad3355aec 100644 --- a/Frameworks/vgmstream/vgmstream/src/layout/blocked.c +++ b/Frameworks/vgmstream/vgmstream/src/layout/blocked.c @@ -92,7 +92,7 @@ void render_vgmstream_blocked(sample_t* outbuf, int32_t sample_count, VGMSTREAM* return; decode_fail: - sbuf_silence(outbuf, sample_count, vgmstream->channels, samples_filled); + sbuf_silence_s16(outbuf, sample_count, vgmstream->channels, samples_filled); } /* helper functions to parse new block */ diff --git a/Frameworks/vgmstream/vgmstream/src/layout/flat.c b/Frameworks/vgmstream/vgmstream/src/layout/flat.c index 6cf979ff5..5a58605c2 100644 --- a/Frameworks/vgmstream/vgmstream/src/layout/flat.c +++ b/Frameworks/vgmstream/vgmstream/src/layout/flat.c @@ -38,5 +38,5 @@ void render_vgmstream_flat(sample_t* outbuf, int32_t sample_count, VGMSTREAM* vg return; decode_fail: - sbuf_silence(outbuf, sample_count, vgmstream->channels, samples_filled); + sbuf_silence_s16(outbuf, sample_count, vgmstream->channels, samples_filled); } diff --git a/Frameworks/vgmstream/vgmstream/src/layout/interleave.c b/Frameworks/vgmstream/vgmstream/src/layout/interleave.c index 5d9195385..63bde81ac 100644 --- a/Frameworks/vgmstream/vgmstream/src/layout/interleave.c +++ b/Frameworks/vgmstream/vgmstream/src/layout/interleave.c @@ -147,7 +147,7 @@ void render_vgmstream_interleave(sample_t* outbuf, int32_t sample_count, VGMSTRE layout_config_t layout = {0}; if (!setup_helper(&layout, vgmstream)) { VGM_LOG_ONCE("INTERLEAVE: wrong config found\n"); - sbuf_silence(outbuf, sample_count, vgmstream->channels, 0); + sbuf_silence_s16(outbuf, sample_count, vgmstream->channels, 0); return; } @@ -193,5 +193,5 @@ void render_vgmstream_interleave(sample_t* outbuf, int32_t sample_count, VGMSTRE return; decode_fail: - sbuf_silence(outbuf, sample_count, vgmstream->channels, samples_filled); + sbuf_silence_s16(outbuf, sample_count, vgmstream->channels, samples_filled); } diff --git a/Frameworks/vgmstream/vgmstream/src/layout/layered.c b/Frameworks/vgmstream/vgmstream/src/layout/layered.c index 746b959c2..8af5f0a8c 100644 --- a/Frameworks/vgmstream/vgmstream/src/layout/layered.c +++ b/Frameworks/vgmstream/vgmstream/src/layout/layered.c @@ -4,6 +4,7 @@ #include "../base/mixing.h" #include "../base/plugins.h" #include "../base/sbuf.h" +#include "../base/render.h" #define VGMSTREAM_MAX_LAYERS 255 #define VGMSTREAM_LAYER_SAMPLE_BUFFER 8192 @@ -12,14 +13,16 @@ /* Decodes samples for layered streams. * Each decoded vgmstream 'layer' (which may have different codecs and number of channels) * is mixed into a final buffer, creating a single super-vgmstream. */ -void render_vgmstream_layered(sample_t* outbuf, int32_t sample_count, VGMSTREAM* vgmstream) { +void render_vgmstream_layered(sbuf_t* sdst, VGMSTREAM* vgmstream) { layered_layout_data* data = vgmstream->layout_data; + sbuf_t ssrc_tmp; + sbuf_t* ssrc = &ssrc_tmp; int samples_per_frame = VGMSTREAM_LAYER_SAMPLE_BUFFER; int samples_this_block = vgmstream->num_samples; /* do all samples if possible */ - int samples_filled = 0; - while (samples_filled < sample_count) { + //int samples_filled = 0; + while (sdst->filled < sdst->samples) { int ch; if (vgmstream->loop_flag && decode_do_loop(vgmstream)) { @@ -28,37 +31,36 @@ void render_vgmstream_layered(sample_t* outbuf, int32_t sample_count, VGMSTREAM* } int samples_to_do = decode_get_samples_to_do(samples_this_block, samples_per_frame, vgmstream); - if (samples_to_do > sample_count - samples_filled) - samples_to_do = sample_count - samples_filled; + if (samples_to_do > sdst->samples - sdst->filled) + samples_to_do = sdst->samples - sdst->filled; if (samples_to_do <= 0) { /* when decoding more than num_samples */ - VGM_LOG_ONCE("LAYERED: wrong samples_to_do\n"); + VGM_LOG_ONCE("LAYERED: wrong %i samples_to_do (%i filled vs %i samples)\n", samples_to_do, sdst->filled, sdst->samples); goto decode_fail; } /* decode all layers */ ch = 0; for (int current_layer = 0; current_layer < data->layer_count; current_layer++) { + /* layers may have their own number of channels/format (buf is as big as needed) */ + sfmt_t format = mixing_get_input_sample_type(data->layers[current_layer]); + sbuf_init(ssrc, format, data->buffer, samples_to_do, data->layers[current_layer]->channels); - /* layers may have their own number of channels */ - int layer_channels; - mixing_info(data->layers[current_layer], NULL, &layer_channels); - - render_vgmstream(data->buffer, samples_to_do, data->layers[current_layer]); + render_main(ssrc, data->layers[current_layer]); /* mix layer samples to main samples */ - sbuf_copy_layers(outbuf, data->output_channels, data->buffer, layer_channels, samples_to_do, samples_filled, ch); - ch += layer_channels; + sbuf_copy_layers(sdst, ssrc, ch, samples_to_do); + ch += ssrc->channels; } - samples_filled += samples_to_do; + sdst->filled += samples_to_do; vgmstream->current_sample += samples_to_do; vgmstream->samples_into_block += samples_to_do; } return; decode_fail: - sbuf_silence(outbuf, sample_count, data->output_channels, samples_filled); + sbuf_silence_rest(sdst); } @@ -125,10 +127,11 @@ fail: } bool setup_layout_layered(layered_layout_data* data) { - - /* setup each VGMSTREAM (roughly equivalent to vgmstream.c's init_vgmstream_internal stuff) */ int max_input_channels = 0; int max_output_channels = 0; + int max_sample_size = 0; + + /* setup each VGMSTREAM (roughly equivalent to vgmstream.c's init_vgmstream_internal stuff) */ for (int i = 0; i < data->layer_count; i++) { if (data->layers[i] == NULL) { VGM_LOG("LAYERED: no vgmstream in %i\n", i); @@ -140,7 +143,7 @@ bool setup_layout_layered(layered_layout_data* data) { return false; } - /* different layers may have different input/output channels */ + /* different layers may have different input/output channels or sample formats */ int layer_input_channels, layer_output_channels; mixing_info(data->layers[i], &layer_input_channels, &layer_output_channels); @@ -163,6 +166,10 @@ bool setup_layout_layered(layered_layout_data* data) { #endif } + int current_sample_size = sfmt_get_sample_size( mixing_get_input_sample_type(data->layers[i]) ); + if (max_sample_size < current_sample_size) + max_sample_size = current_sample_size; + /* loops and other values could be mismatched, but should be handled on allocate */ /* init mixing */ @@ -179,8 +186,9 @@ bool setup_layout_layered(layered_layout_data* data) { return false; /* create internal buffer big enough for mixing all layers */ - if (!sbuf_realloc(&data->buffer, VGMSTREAM_LAYER_SAMPLE_BUFFER, max_input_channels)) - goto fail; + free(data->buffer); + data->buffer = malloc(VGMSTREAM_LAYER_SAMPLE_BUFFER * max_input_channels * max_sample_size); + if (!data->buffer) goto fail; data->input_channels = max_input_channels; data->output_channels = max_output_channels; @@ -190,7 +198,7 @@ fail: return false; /* caller is expected to free */ } -void free_layout_layered(layered_layout_data *data) { +void free_layout_layered(layered_layout_data* data) { if (!data) return; @@ -202,7 +210,7 @@ void free_layout_layered(layered_layout_data *data) { free(data); } -void reset_layout_layered(layered_layout_data *data) { +void reset_layout_layered(layered_layout_data* data) { if (!data) return; diff --git a/Frameworks/vgmstream/vgmstream/src/layout/layout.h b/Frameworks/vgmstream/vgmstream/src/layout/layout.h index ddbc8da9c..1c298c36e 100644 --- a/Frameworks/vgmstream/vgmstream/src/layout/layout.h +++ b/Frameworks/vgmstream/vgmstream/src/layout/layout.h @@ -1,10 +1,11 @@ -#ifndef _LAYOUT_H -#define _LAYOUT_H +#ifndef _LAYOUT_H_ +#define _LAYOUT_H_ #include "../streamtypes.h" #include "../vgmstream.h" #include "../util/reader_sf.h" #include "../util/log.h" +#include "../base/sbuf.h" /* basic layouts */ void render_vgmstream_flat(sample_t* buffer, int32_t sample_count, VGMSTREAM* vgmstream); @@ -21,10 +22,10 @@ typedef struct { sample_t* buffer; int input_channels; /* internal buffer channels */ int output_channels; /* resulting channels (after mixing, if applied) */ - int mixed_channels; /* segments have different number of channels */ + bool mixed_channels; /* segments have different number of channels */ } segmented_layout_data; -void render_vgmstream_segmented(sample_t* buffer, int32_t sample_count, VGMSTREAM* vgmstream); +void render_vgmstream_segmented(sbuf_t* sbuf, VGMSTREAM* vgmstream); segmented_layout_data* init_layout_segmented(int segment_count); bool setup_layout_segmented(segmented_layout_data* data); void free_layout_segmented(segmented_layout_data* data); @@ -45,7 +46,7 @@ typedef struct { int curr_layer; /* helper */ } layered_layout_data; -void render_vgmstream_layered(sample_t* buffer, int32_t sample_count, VGMSTREAM* vgmstream); +void render_vgmstream_layered(sbuf_t* sbuf, VGMSTREAM* vgmstream); layered_layout_data* init_layout_layered(int layer_count); bool setup_layout_layered(layered_layout_data* data); void free_layout_layered(layered_layout_data* data); diff --git a/Frameworks/vgmstream/vgmstream/src/layout/segmented.c b/Frameworks/vgmstream/vgmstream/src/layout/segmented.c index c411fa338..02b14f6f4 100644 --- a/Frameworks/vgmstream/vgmstream/src/layout/segmented.c +++ b/Frameworks/vgmstream/vgmstream/src/layout/segmented.c @@ -4,6 +4,7 @@ #include "../base/mixing.h" #include "../base/plugins.h" #include "../base/sbuf.h" +#include "../base/render.h" #define VGMSTREAM_MAX_SEGMENTS 1024 #define VGMSTREAM_SEGMENT_SAMPLE_BUFFER 8192 @@ -12,18 +13,15 @@ /* Decodes samples for segmented streams. * Chains together sequential vgmstreams, for data divided into separate sections or files * (like one part for intro and other for loop segments, which may even use different codecs). */ -void render_vgmstream_segmented(sample_t* outbuf, int32_t sample_count, VGMSTREAM* vgmstream) { +void render_vgmstream_segmented(sbuf_t* sbuf, VGMSTREAM* vgmstream) { segmented_layout_data* data = vgmstream->layout_data; - bool use_internal_buffer = false; + sbuf_t ssrc_tmp; + sbuf_t* ssrc = &ssrc_tmp; - /* normally uses outbuf directly (faster?) but could need internal buffer if downmixing */ - if (vgmstream->channels != data->input_channels || data->mixed_channels) { - use_internal_buffer = true; - } if (data->current_segment >= data->segment_count) { VGM_LOG_ONCE("SEGMENT: wrong current segment\n"); - sbuf_silence(outbuf, sample_count, data->output_channels, 0); + sbuf_silence_rest(sbuf); return; } @@ -31,10 +29,10 @@ void render_vgmstream_segmented(sample_t* outbuf, int32_t sample_count, VGMSTREA mixing_info(data->segments[data->current_segment], NULL, ¤t_channels); int samples_this_block = vgmstream_get_samples(data->segments[data->current_segment]); - int samples_filled = 0; - while (samples_filled < sample_count) { + while (sbuf->filled < sbuf->samples) { int samples_to_do; - sample_t* buf; + sfmt_t segment_format; + void* buf_filled = NULL; if (vgmstream->loop_flag && decode_do_loop(vgmstream)) { /* handle looping (loop_layout has been called below, changes segments/state) */ @@ -62,9 +60,9 @@ void render_vgmstream_segmented(sample_t* outbuf, int32_t sample_count, VGMSTREA } - samples_to_do = decode_get_samples_to_do(samples_this_block, sample_count, vgmstream); - if (samples_to_do > sample_count - samples_filled) - samples_to_do = sample_count - samples_filled; + samples_to_do = decode_get_samples_to_do(samples_this_block, sbuf->samples, vgmstream); + if (samples_to_do > sbuf->samples - sbuf->filled) + samples_to_do = sbuf->samples - sbuf->filled; if (samples_to_do > VGMSTREAM_SEGMENT_SAMPLE_BUFFER /*&& use_internal_buffer*/) /* always for fade/etc mixes */ samples_to_do = VGMSTREAM_SEGMENT_SAMPLE_BUFFER; @@ -73,25 +71,33 @@ void render_vgmstream_segmented(sample_t* outbuf, int32_t sample_count, VGMSTREA goto decode_fail; } - buf = use_internal_buffer ? data->buffer : &outbuf[samples_filled * data->output_channels]; - render_vgmstream(buf, samples_to_do, data->segments[data->current_segment]); + segment_format = mixing_get_input_sample_type(data->segments[data->current_segment]); + sbuf_init(ssrc, segment_format, data->buffer, samples_to_do, data->segments[data->current_segment]->channels); - if (use_internal_buffer) { - sbuf_copy_samples(outbuf, data->output_channels, data->buffer, current_channels, samples_to_do, samples_filled); + // try to use part of outbuf directly if not remixed (minioptimization) //TODO improve detection + if (vgmstream->channels == data->input_channels && sbuf->fmt == segment_format && !data->mixed_channels) { + buf_filled = sbuf_get_filled_buf(sbuf); + ssrc->buf = buf_filled; } - samples_filled += samples_to_do; + render_main(ssrc, data->segments[data->current_segment]); + + // returned buf may have changed + if (ssrc->buf != buf_filled) { + sbuf_copy_segments(sbuf, ssrc); + } + + sbuf->filled += samples_to_do; vgmstream->current_sample += samples_to_do; vgmstream->samples_into_block += samples_to_do; } return; decode_fail: - sbuf_silence(outbuf, sample_count, data->output_channels, samples_filled); + sbuf_silence_rest(sbuf); } - void seek_layout_segmented(VGMSTREAM* vgmstream, int32_t seek_sample) { segmented_layout_data* data = vgmstream->layout_data; @@ -145,8 +151,10 @@ fail: } bool setup_layout_segmented(segmented_layout_data* data) { - int max_input_channels = 0, max_output_channels = 0, mixed_channels = 0; - + int max_input_channels = 0; + int max_output_channels = 0; + int max_sample_size = 0; + bool mixed_channels = false; /* setup each VGMSTREAM (roughly equivalent to vgmstream.c's init_vgmstream_internal stuff) */ for (int i = 0; i < data->segment_count; i++) { @@ -187,7 +195,7 @@ bool setup_layout_segmented(segmented_layout_data* data) { mixing_info(data->segments[i-1], NULL, &prev_output_channels); if (segment_output_channels != prev_output_channels) { - mixed_channels = 1; + mixed_channels = true; //VGM_LOG("SEGMENTED: segment %i has wrong channels %i vs prev channels %i\n", i, segment_output_channels, prev_output_channels); //goto fail; } @@ -202,6 +210,10 @@ bool setup_layout_segmented(segmented_layout_data* data) { // goto fail; } + int current_sample_size = sfmt_get_sample_size( mixing_get_input_sample_type(data->segments[i]) ); + if (max_sample_size < current_sample_size) + max_sample_size = current_sample_size; + /* init mixing */ mixing_setup(data->segments[i], VGMSTREAM_SEGMENT_SAMPLE_BUFFER); @@ -213,8 +225,9 @@ bool setup_layout_segmented(segmented_layout_data* data) { return false; /* create internal buffer big enough for mixing */ - if (!sbuf_realloc(&data->buffer, VGMSTREAM_SEGMENT_SAMPLE_BUFFER, max_input_channels)) - goto fail; + free(data->buffer); + data->buffer = malloc(VGMSTREAM_SEGMENT_SAMPLE_BUFFER * max_input_channels * max_sample_size); + if (!data->buffer) goto fail; data->input_channels = max_input_channels; data->output_channels = max_output_channels; diff --git a/Frameworks/vgmstream/vgmstream/src/libvgmstream.h b/Frameworks/vgmstream/vgmstream/src/libvgmstream.h index 3ac947eb1..5afd47872 100644 --- a/Frameworks/vgmstream/vgmstream/src/libvgmstream.h +++ b/Frameworks/vgmstream/vgmstream/src/libvgmstream.h @@ -1,7 +1,7 @@ #ifndef _LIBVGMSTREAM_H_ #define _LIBVGMSTREAM_H_ -//#define LIBVGMSTREAM_ENABLE 1 +#define LIBVGMSTREAM_ENABLE 1 #if LIBVGMSTREAM_ENABLE /* libvgmstream: vgmstream's public API @@ -188,6 +188,7 @@ typedef struct { // ** this type of downmixing is very simplistic and not recommended bool force_pcm16; // forces output buffer to be remixed into PCM16 + bool force_float; // forces output buffer to be remixed into float } libvgmstream_config_t; @@ -201,7 +202,7 @@ LIBVGMSTREAM_API void libvgmstream_setup(libvgmstream_t* lib, libvgmstream_confi /* configures how vgmstream opens the format */ typedef struct { - libvgmstream_streamfile_t* libsf; // custom IO streamfile that provides reader info for vgmstream + libstreamfile_t* libsf; // custom IO streamfile that provides reader info for vgmstream // ** not needed after _open and should be closed, as vgmstream re-opens its own SFs internally as needed int subsong_index; // target subsong (1..N) or 0 = default/first @@ -345,7 +346,7 @@ typedef struct { * - libsf should point to a !tags.m3u file * - unlike libvgmstream_open, sf tagfile must be valid during the tag extraction process. */ -LIBVGMSTREAM_API libvgmstream_tags_t* libvgmstream_tags_init(libvgmstream_streamfile_t* libsf); +LIBVGMSTREAM_API libvgmstream_tags_t* libvgmstream_tags_init(libstreamfile_t* libsf); /* Finds tags for a new filename. Must be called first before extracting tags. */ diff --git a/Frameworks/vgmstream/vgmstream/src/libvgmstream_streamfile.h b/Frameworks/vgmstream/vgmstream/src/libvgmstream_streamfile.h new file mode 100644 index 000000000..06315b00b --- /dev/null +++ b/Frameworks/vgmstream/vgmstream/src/libvgmstream_streamfile.h @@ -0,0 +1,67 @@ +#ifndef _LIBVGMSTREAM_STREAMFILE_H_ +#define _LIBVGMSTREAM_STREAMFILE_H_ +#include "libvgmstream.h" +#if LIBVGMSTREAM_ENABLE + +/* vgmstream's IO API, defined as a "streamfile" (SF). + * + * vgmstream roughly assumes there is an underlying filesystem (as usual in games): seeking + reading from arbitrary offsets, + * opening companion files, filename tests, etc. If your case is too different you may still create a partial streamfile: returning + * a fake filename, only handling "open" that reopens itself (same filename), etc. Simpler formats will probably work just fine. + */ + + +enum { + LIBSTREAMFILE_SEEK_SET = 0, + LIBSTREAMFILE_SEEK_CUR = 1, + LIBSTREAMFILE_SEEK_END = 2, + //LIBSTREAMFILE_SEEK_GET_OFFSET = 3, + //LIBSTREAMFILE_SEEK_GET_SIZE = 5, +}; + +// maybe "libvgmstream_streamfile_t" but it was getting unwieldly +typedef struct libstreamfile_t { + //uint32_t flags; // info flags for vgmstream + void* user_data; // any internal structure + + /* read 'length' data at internal offset to 'dst' + * - assumes 0 = failure/EOF + */ + int (*read)(void* user_data, uint8_t* dst, int dst_size); + + /* seek to offset + * - note that vgmstream needs to seek + read fairly often (to be optimized later) + */ + int64_t (*seek)(void* user_data, int64_t offset, int whence); + + /* get max offset (typically for checks or calculations) + */ + int64_t (*get_size)(void* user_data); + + /* get current filename (used to open same or other streamfiles and heuristics; no need to be a real path) + */ + const char* (*get_name)(void* user_data); + + /* open another streamfile from filename (may be some path/protocol, or same as current get_name = reopen) + * - vgmstream opens stuff based on current get_name (relative), so there shouldn't be need to transform this path + */ + struct libstreamfile_t* (*open)(void* user_data, const char* filename); + + /* free current SF (needed for copied streamfiles) */ + void (*close)(struct libstreamfile_t* libsf); + +} libstreamfile_t; + + +/* helper */ +static inline void libstreamfile_close(libstreamfile_t* libsf) { + if (!libsf || !libsf->close) + return; + libsf->close(libsf); +} + + +LIBVGMSTREAM_API libstreamfile_t* libstreamfile_open_from_stdio(const char* filename); + +#endif +#endif diff --git a/Frameworks/vgmstream/vgmstream/src/meta/ads.c b/Frameworks/vgmstream/vgmstream/src/meta/ads.c index 719f2f87a..aa0c8ce65 100644 --- a/Frameworks/vgmstream/vgmstream/src/meta/ads.c +++ b/Frameworks/vgmstream/vgmstream/src/meta/ads.c @@ -22,8 +22,9 @@ VGMSTREAM* init_vgmstream_ads(STREAMFILE* sf) { * .pcm: Taisho Mononoke Ibunroku (PS2) * .adx: Armored Core 3 (PS2) * (extensionless): MotoGP (PS2) - * .800: Mobile Suit Gundam: The One Year War (PS2) */ - if (!check_extensions(sf, "ads,ss2,pcm,adx,,800")) + * .800: Mobile Suit Gundam: The One Year War (PS2) + * .sdl: Innocent Life: A Futuristic Harvest Moon (Special Edition) (PS2) */ + if (!check_extensions(sf, "ads,ss2,pcm,adx,,800,sdl")) goto fail; if (read_u32le(0x04,sf) != 0x18 && /* standard header size */ diff --git a/Frameworks/vgmstream/vgmstream/src/meta/bik.c b/Frameworks/vgmstream/vgmstream/src/meta/bik.c index 0a1f94ce1..1c181f91f 100644 --- a/Frameworks/vgmstream/vgmstream/src/meta/bik.c +++ b/Frameworks/vgmstream/vgmstream/src/meta/bik.c @@ -6,16 +6,13 @@ static bool bink_get_info(STREAMFILE* sf, int target_subsong, int* p_total_subso /* BINK 1/2 - RAD Game Tools movies (audio/video format) */ VGMSTREAM* init_vgmstream_bik(STREAMFILE* sf) { - VGMSTREAM * vgmstream = NULL; - int channels = 0, loop_flag = 0, sample_rate = 0, num_samples = 0; - int total_subsongs = 0, target_subsong = sf->stream_index; - size_t stream_size; + VGMSTREAM* vgmstream = NULL; /* checks */ /* bink1/2 header, followed by version-char (audio is the same) */ if ((read_u32be(0x00,sf) & 0xffffff00) != get_id32be("BIK\0") && (read_u32be(0x00,sf) & 0xffffff00) != get_id32be("KB2\0")) - goto fail; + return NULL; /* .bik/bk2: standard * .bik2: older? @@ -25,7 +22,13 @@ VGMSTREAM* init_vgmstream_bik(STREAMFILE* sf) { * .vid: Etrange Libellules games [Alice in Wonderland (PC)] * .bika: fake extension for demuxed audio */ if (!check_extensions(sf,"bik,bk2,bik2,ps3,xmv,xen,vid,bika")) - goto fail; + return NULL; + + /* this typically handles regular or demuxed videos, but .bik with a 4x4 video made for audio do exist [Viva PiƱata (DS)] */ + + int channels = 0, loop_flag = 0, sample_rate = 0, num_samples = 0; + int total_subsongs = 0, target_subsong = sf->stream_index; + size_t stream_size; /* find target stream info and samples */ if (!bink_get_info(sf, target_subsong, &total_subsongs, &stream_size, &channels, &sample_rate, &num_samples)) diff --git a/Frameworks/vgmstream/vgmstream/src/meta/bnk_sony.c b/Frameworks/vgmstream/vgmstream/src/meta/bnk_sony.c index 7c4c57a02..67d2890ed 100644 --- a/Frameworks/vgmstream/vgmstream/src/meta/bnk_sony.c +++ b/Frameworks/vgmstream/vgmstream/src/meta/bnk_sony.c @@ -3,7 +3,7 @@ #include "../coding/coding.h" #include "../util/endianness.h" -typedef enum { NONE, DUMMY, PSX, PCM16, MPEG, ATRAC9, HEVAG, RIFF_ATRAC9 } bnk_codec; +typedef enum { NONE, DUMMY, EXTERNAL, PSX, PCM16, MPEG, ATRAC9, HEVAG, RIFF_ATRAC9, XVAG_ATRAC9 } bnk_codec; typedef struct { bnk_codec codec; @@ -47,9 +47,6 @@ typedef struct { uint32_t start_offset; uint32_t stream_offset; - uint32_t bank_name_offset; - uint32_t stream_name_offset; - uint32_t stream_name_size; uint32_t stream_size; uint32_t interleave; @@ -69,10 +66,10 @@ VGMSTREAM* init_vgmstream_bnk_sony(STREAMFILE* sf) { bnk_header_t h = {0}; /* checks */ - if (!parse_bnk_v3(sf, &h)) - return NULL; if (!check_extensions(sf, "bnk")) return NULL; + if (!parse_bnk_v3(sf, &h)) + return NULL; /* build the VGMSTREAM */ @@ -85,15 +82,6 @@ VGMSTREAM* init_vgmstream_bnk_sony(STREAMFILE* sf) { vgmstream->meta_type = meta_BNK_SONY; - if (h.stream_name_size >= STREAM_NAME_SIZE || h.stream_name_size <= 0) - h.stream_name_size = STREAM_NAME_SIZE; - - /* replace this with reading into the buffer ASAP when processing tables? */ - if (h.bank_name_offset) - read_string(h.bank_name, h.stream_name_size, h.bank_name_offset, sf); - if (h.stream_name_offset) - read_string(h.stream_name, h.stream_name_size, h.stream_name_offset, sf); - if (h.stream_name[0]) { get_streamfile_basename(sf, file_name, STREAM_NAME_SIZE); if (h.bank_name[0] && strcmp(file_name, h.bank_name) != 0) @@ -115,6 +103,39 @@ VGMSTREAM* init_vgmstream_bnk_sony(STREAMFILE* sf) { return temp_vs; } + case EXTERNAL: { + VGMSTREAM* temp_vs = NULL; + STREAMFILE* temp_sf = NULL; + + + /* try with both stream_name and bank_name/stream_name? */ + temp_sf = open_streamfile_by_filename(sf, h.stream_name); + if (!temp_sf) { /* create dummy stream if it can't be found */ + temp_vs = init_vgmstream_silence_container(h.total_subsongs); + if (!temp_vs) goto fail; + + temp_vs->meta_type = vgmstream->meta_type; + snprintf(temp_vs->stream_name, STREAM_NAME_SIZE, "%s [not found]", vgmstream->stream_name); + + close_vgmstream(vgmstream); + return temp_vs; + } + + /* are external streams always xvag? it shouldn't be hardcoded like this, but... */ + /* and at that point does this also need to be put behind #ifdef VGM_USE_ATRAC9? */ + /* known BNK v12 externals use XVAG MPEG but it functions differently in general */ + temp_vs = init_vgmstream_xvag(temp_sf); + close_streamfile(temp_sf); + if (!temp_vs) goto fail; + + temp_vs->num_streams = vgmstream->num_streams; + temp_vs->meta_type = vgmstream->meta_type; + strcpy(temp_vs->stream_name, vgmstream->stream_name); + + close_vgmstream(vgmstream); + return temp_vs; + } + #ifdef VGM_USE_ATRAC9 case ATRAC9: { atrac9_config cfg = {0}; @@ -154,6 +175,30 @@ VGMSTREAM* init_vgmstream_bnk_sony(STREAMFILE* sf) { close_vgmstream(vgmstream); return temp_vs; } + + case XVAG_ATRAC9: { + VGMSTREAM* temp_vs = NULL; + STREAMFILE* temp_sf = NULL; + + + temp_sf = setup_subfile_streamfile(sf, h.start_offset, h.stream_size, "xvag"); + if (!temp_sf) goto fail; + temp_sf->stream_index = 1; + + temp_vs = init_vgmstream_xvag(temp_sf); + close_streamfile(temp_sf); + if (!temp_vs) goto fail; + + /* maybe also a separate warning/fail if XVAG returns more than 1 subsong? */ + + temp_vs->num_streams = vgmstream->num_streams; + //temp_vs->stream_size = vgmstream->stream_size; + temp_vs->meta_type = vgmstream->meta_type; + strcpy(temp_vs->stream_name, vgmstream->stream_name); + + close_vgmstream(vgmstream); + return temp_vs; + } #endif #ifdef VGM_USE_MPEG case MPEG: { @@ -356,7 +401,7 @@ static bool process_tables(STREAMFILE* sf, bnk_header_t* h) { break; case 0x03: /* Yu-Gi-Oh! GX - The Beginning of Destiny (PS2) */ - case 0x04: /* Test banks */ + case 0x04: /* EyePet (PS3), Test banks */ case 0x05: /* Ratchet & Clank (PS3) */ case 0x08: /* Playstation Home Arcade (Vita) */ case 0x09: /* Puyo Puyo Tetris (PS4) */ @@ -396,11 +441,13 @@ static bool process_tables(STREAMFILE* sf, bnk_header_t* h) { h->table2_suboffset = 0x00; break; - /* later version have a few more tables (some optional) and work slightly differently (header is part of wave) */ + /* later versions have a few more tables (some optional) and work slightly differently (header is part of wave) */ case 0x1a: /* Demon's Souls (PS5) */ + case 0x1c: /* The Last of Us Part II */ case 0x23: { /* The Last of Us (PC) */ - uint32_t tables_offset = h->sblk_offset + (h->sblk_version <= 0x1a ? 0x120 : 0x128); - uint32_t counts_offset = tables_offset + (h->sblk_version <= 0x1a ? 0x98 : 0xb0); + uint32_t bank_name_offset = h->sblk_offset + (h->sblk_version <= 0x1c ? 0x1c : 0x20); + uint32_t tables_offset = h->sblk_offset + (h->sblk_version <= 0x1c ? 0x120 : 0x128); + uint32_t counts_offset = tables_offset + (h->sblk_version <= 0x1c ? 0x98 : 0xb0); //h->table1_offset = h->sblk_offset + read_u32(tables_offset+0x00,sf); /* sounds/cues */ //h->table2_offset = 0; @@ -408,6 +455,8 @@ static bool process_tables(STREAMFILE* sf, bnk_header_t* h) { //h->sounds_entries = read_u16(counts_offset+0x00,sf); //h->grains_entries = read_u16(counts_offset+0x02,sf); h->stream_entries = read_u16(counts_offset+0x06,sf); + + read_string(h->bank_name, STREAM_NAME_SIZE, bank_name_offset, sf); break; } @@ -465,6 +514,7 @@ static bool process_headers(STREAMFILE* sf, bnk_header_t* h) { break; case 0x1a: + case 0x1c: case 0x23: h->total_subsongs = h->stream_entries; h->table3_entry_offset = (h->target_subsong - 1) * 0x08; @@ -493,7 +543,7 @@ static bool process_headers(STREAMFILE* sf, bnk_header_t* h) { //;VGM_LOG("BNK: subsongs %i, table2_entry=%x, table3_entry=%x\n", h->total_subsongs, h->table2_entry_offset, h->table3_entry_offset); - if (h->target_subsong < 0 || h->target_subsong > h->total_subsongs || h->total_subsongs < 1) + if (!h->zlsd_offset && (h->target_subsong < 0 || h->target_subsong > h->total_subsongs || h->total_subsongs < 1)) goto fail; /* this means some subsongs repeat streams, that can happen in some sfx banks, whatevs */ if (h->total_subsongs != h->stream_entries) { @@ -503,6 +553,10 @@ static bool process_headers(STREAMFILE* sf, bnk_header_t* h) { //;VGM_LOG("BNK: header entry at %x\n", h->table3_offset + h->table3_entry_offset); + /* is currently working on ZLSD streams */ + if (h->zlsd_offset && h->target_subsong > h->total_subsongs) + return true; + sndh_offset = h->table3_offset + h->table3_entry_offset; /* parse sounds */ @@ -580,8 +634,9 @@ static bool process_headers(STREAMFILE* sf, bnk_header_t* h) { h->sample_rate = (int)read_f32(sndh_offset+0x4c,sf); break; - case 0x1a: /* Demon's Souls (PS5) */ - case 0x23: /* The Last of Us (PC) */ + case 0x1a: + case 0x1c: + case 0x23: h->stream_offset = read_u32(sndh_offset+0x00,sf); /* rest is part of data, handled later */ break; @@ -607,10 +662,15 @@ static bool process_names(STREAMFILE* sf, bnk_header_t* h) { if (h->table4_offset <= h->sblk_offset) return true; + /* is currently working on ZLSD streams */ + if (h->zlsd_offset && h->target_subsong > h->total_subsongs) + return true; + int i; int table4_entry_id = -1; uint32_t table4_entry_idx, table4_entries_offset, table4_names_offset; uint32_t entry_offset, entry_count; + uint32_t stream_name_offset; switch (h->sblk_version) { case 0x03: @@ -637,30 +697,30 @@ static bool process_names(STREAMFILE* sf, bnk_header_t* h) { * and using that as the index for the chunk offsets * name_sect_offset + (chunk_idx[result] * 0x14); */ - if (read_u8(h->table4_offset, sf)) - h->bank_name_offset = h->table4_offset; + read_string(h->bank_name, STREAM_NAME_SIZE, h->table4_offset, sf); table4_entries_offset = h->table4_offset + 0x18; table4_names_offset = h->table4_offset + read_u32(h->table4_offset + 0x08, sf); for (i = 0; i < 32; i++) { table4_entry_idx = read_u16(table4_entries_offset + (i * 2), sf); - h->stream_name_offset = table4_names_offset + (table4_entry_idx * 0x14); + stream_name_offset = table4_names_offset + (table4_entry_idx * 0x14); /* searches the chunk until it finds the target name/index, or breaks at empty name */ - while (read_u8(h->stream_name_offset, sf)) { + while (read_u8(stream_name_offset, sf)) { /* in case it goes somewhere out of bounds unexpectedly */ - if (((read_u8(h->stream_name_offset + 0x00, sf) + read_u8(h->stream_name_offset + 0x04, sf) + - read_u8(h->stream_name_offset + 0x08, sf) + read_u8(h->stream_name_offset + 0x0C, sf)) & 0x1F) != i) + if (((read_u8(stream_name_offset + 0x00, sf) + read_u8(stream_name_offset + 0x04, sf) + + read_u8(stream_name_offset + 0x08, sf) + read_u8(stream_name_offset + 0x0C, sf)) & 0x1F) != i) goto fail; - if (read_u16(h->stream_name_offset + 0x10, sf) == table4_entry_id) + if (read_u16(stream_name_offset + 0x10, sf) == table4_entry_id) { + read_string(h->stream_name, STREAM_NAME_SIZE, stream_name_offset, sf); goto loop_break; /* to break out of the for+while loop simultaneously */ //break; - h->stream_name_offset += 0x14; + } + stream_name_offset += 0x14; } } //goto fail; /* didn't find any valid index? */ - h->stream_name_offset = 0; - loop_break: +loop_break: break; case 0x04: @@ -687,15 +747,15 @@ static bool process_names(STREAMFILE* sf, bnk_header_t* h) { * 0x08: ? (2x int16) * 0x0C: section index (int16) */ - if (read_u8(h->table4_offset, sf)) - h->bank_name_offset = h->table4_offset; + read_string(h->bank_name, STREAM_NAME_SIZE, h->table4_offset, sf); table4_entries_offset = h->table4_offset + read_u32(h->table4_offset + 0x08, sf); table4_names_offset = h->table4_offset + read_u32(h->table4_offset + 0x0C, sf); for (i = 0; i < h->sounds_entries; i++) { if (read_u16(table4_entries_offset + (i * 0x10) + 0x0C, sf) == table4_entry_id) { - h->stream_name_offset = table4_names_offset + read_u32(table4_entries_offset + (i * 0x10), sf); + stream_name_offset = table4_names_offset + read_u32(table4_entries_offset + (i * 0x10), sf); + read_string(h->stream_name, STREAM_NAME_SIZE, stream_name_offset, sf); break; } } @@ -726,8 +786,7 @@ static bool process_names(STREAMFILE* sf, bnk_header_t* h) { /* 0x0c: table4 size */ /* variable: entries */ /* variable: names (null terminated) */ - if (read_u8(h->table4_offset, sf)) - h->bank_name_offset = h->table4_offset; + read_string(h->bank_name, STREAM_NAME_SIZE, h->table4_offset, sf); table4_entries_offset = h->table4_offset + read_u32(h->table4_offset + 0x08, sf); table4_names_offset = table4_entries_offset + (0x10 * h->sounds_entries); @@ -737,7 +796,8 @@ static bool process_names(STREAMFILE* sf, bnk_header_t* h) { for (i = 0; i < h->sounds_entries; i++) { int entry_id = read_u16(table4_entries_offset + (i * 0x10) + 0x0c, sf); if (entry_id == table4_entry_id) { - h->stream_name_offset = table4_names_offset + read_u32(table4_entries_offset + (i * 0x10) + 0x00, sf); + stream_name_offset = table4_names_offset + read_u32(table4_entries_offset + (i * 0x10) + 0x00, sf); + read_string(h->stream_name, STREAM_NAME_SIZE, stream_name_offset, sf); break; } } @@ -761,8 +821,13 @@ static bool process_data(STREAMFILE* sf, bnk_header_t* h) { read_s32_t read_s32 = h->big_endian ? read_s32be : read_s32le; read_u64_t read_u64 = h->big_endian ? read_u64be : read_u64le; + /* is currently working on ZLSD streams */ + if (h->zlsd_offset && h->target_subsong > h->total_subsongs) + return true; + int subtype, loop_length; uint32_t extradata_size = 0, postdata_size = 0; + uint32_t stream_name_size, stream_name_offset; h->start_offset = h->data_offset + h->stream_offset; uint32_t info_offset = h->start_offset; @@ -822,8 +887,9 @@ static bool process_data(STREAMFILE* sf, bnk_header_t* h) { * 200 = send LFE * 400 = send center */ - if ((h->stream_flags & 0x80) && h->sblk_version <= 3) { - h->codec = PCM16; /* rare [Wipeout HD (PS3)]-v3 */ + if ((h->stream_flags & 0x80) && h->sblk_version <= 4) { + /* rare [Wipeout HD (PS3)-v3, EyePet (PS3)-v4] */ + h->codec = PCM16; } else { h->loop_flag = ps_find_loop_offsets(sf, h->start_offset, h->stream_size, h->channels, h->interleave, &h->loop_start, &h->loop_end); @@ -907,7 +973,7 @@ static bool process_data(STREAMFILE* sf, bnk_header_t* h) { case 0x0c: /* has two different variants under the same version - one for PS3 and another for PS4 */ - subtype = read_u32(h->start_offset + 0x00,sf); /* might be u16 at 0x02 instead? (implied by PS4's subtypes) */ + subtype = read_u16(h->start_offset + 0x02, sf); if (read_u32(h->start_offset + 0x04, sf) != 0x01) { /* type? */ VGM_LOG("BNK: unknown subtype\n"); goto fail; @@ -958,9 +1024,7 @@ static bool process_data(STREAMFILE* sf, bnk_header_t* h) { } else { switch (subtype) { /* PS4 */ - /* if subtype is u16 @ 0x02, then 0x00 is PCM and 0x01 is VAG */ - case 0x00: /* PCM mono? */ - case 0x01: /* PCM stereo? */ + case 0x00: /* PCM */ /* 0x10: null? */ h->channels = read_u32(h->start_offset + 0x14, sf); h->interleave = 0x02; @@ -972,7 +1036,7 @@ static bool process_data(STREAMFILE* sf, bnk_header_t* h) { h->codec = PCM16; break; - case 0x10000: /* PS-ADPCM (HEVAG?) */ + case 0x01: /* PS-ADPCM (HEVAG?) */ /* 0x10: num samples */ h->channels = read_u32(h->start_offset + 0x14, sf); h->interleave = 0x10; @@ -1057,6 +1121,7 @@ static bool process_data(STREAMFILE* sf, bnk_header_t* h) { break; case 0x1a: + case 0x1c: case 0x23: if (h->stream_offset == 0xFFFFFFFF) { h->channels = 1; @@ -1065,30 +1130,62 @@ static bool process_data(STREAMFILE* sf, bnk_header_t* h) { } /* pre-info */ - h->stream_name_size = read_u64(info_offset+0x00,sf); - h->stream_name_offset = info_offset + 0x08; - info_offset += h->stream_name_size + 0x08; + stream_name_size = read_u64(info_offset+0x00,sf); + stream_name_offset = info_offset + 0x08; + info_offset += stream_name_size + 0x08; h->stream_size = read_u64(info_offset + 0x00,sf); /* after this offset */ - h->stream_size += 0x08 + h->stream_name_size + 0x08; - /* 0x08: max block/etc size? (0x00010000/00030000) */ - /* 0x0c: always 1? */ - extradata_size = read_u64(info_offset + 0x10,sf) + 0x08 + h->stream_name_size + 0x18; + h->stream_size += 0x08 + stream_name_size + 0x08; + /* 0x08: 0/1 for PCM (Mono/Stereo?), 0/1/2/3 for ATRAC9 (channels/2)? */ + subtype = read_u16(info_offset + 0x0a, sf); + /* 0x0c: always 1 - using this to detect whether it's an SBlk or ZLSD/exteral sound for now */ + extradata_size = read_u64(info_offset + 0x10,sf) + 0x08 + stream_name_size + 0x18; + + if (stream_name_size >= STREAM_NAME_SIZE || stream_name_size <= 0) + stream_name_size = STREAM_NAME_SIZE; + read_string(h->stream_name, stream_name_size, stream_name_offset, sf); + + /* size check is necessary, otherwise it risks a false positive with the ZLSD version number */ + if (info_offset + 0x10 > h->data_offset + h->data_size || read_u32(info_offset + 0x0c, sf) != 0x01) { + h->channels = 1; + h->codec = EXTERNAL; + break; + } + info_offset += 0x18; /* actual stream info */ - /* 0x00: extradata size (without pre-info, also above) */ - h->atrac9_info = read_u32be(info_offset+0x04,sf); - h->num_samples = read_s32(info_offset+0x08,sf); - h->channels = read_u32(info_offset+0x0c,sf); - h->loop_start = read_s32(info_offset+0x10,sf); - h->loop_end = read_s32(info_offset+0x14,sf); - /* 0x18: loop flag (0=loop, -1=no) */ - /* rest: null */ + switch (subtype) { + case 0x00: /* PCM */ + h->num_samples = read_s32(info_offset + 0x00, sf); + h->channels = read_u32(info_offset + 0x04, sf); + /* 0x08: loop flag? (always -1) */ + + h->codec = PCM16; + break; + + /* should be split, but 0x1A/0x1C has no other known codecs yet */ + case 0x01: /* ATRAC9 (0x23) */ + case 0x03: /* ATRAC9 (0x1A/0x1C) */ + /* 0x00: extradata size (without pre-info, also above) */ + h->atrac9_info = read_u32be(info_offset + 0x04, sf); + h->num_samples = read_s32(info_offset + 0x08, sf); + h->channels = read_u32(info_offset + 0x0c, sf); + h->loop_start = read_s32(info_offset + 0x10, sf); + h->loop_end = read_s32(info_offset + 0x14, sf); + /* 0x18: loop flag (0=loop, -1=no) */ + /* rest: null */ + + h->codec = RIFF_ATRAC9; + break; + + default: + vgm_logi("BNK: unknown subtype %x (report)\n", subtype); + goto fail; + } + /* no sample rate (probably fixed to 48000/system's, but seen in RIFF) */ h->sample_rate = 48000; - - h->codec = RIFF_ATRAC9; /* unsure how other codecs would work */ break; default: @@ -1106,36 +1203,75 @@ fail: return false; } - -/* zlsd part: parse extra footer (vox?) data */ +/* zlsd part: parse external stream prefetch data */ static bool process_zlsd(STREAMFILE* sf, bnk_header_t* h) { if (!h->zlsd_offset) return true; + /* TODO: ZLSD contains FNV1-32 hashes of the SBlk external streams, + * but with the way it's all currently set up, it isn't as simple to + * map appropriate hashes to existing SBlk streams. So for now these + * won't have a "proper" stream name visible. + */ + + int zlsd_subsongs, target_subsong; + uint32_t zlsd_table_offset, zlsd_table_entry_offset, stream_name_hash; read_u32_t read_u32 = h->big_endian ? read_u32be : read_u32le; - if (read_u32(h->zlsd_offset+0x00,sf) != get_id32be("DSLZ")) + if (read_u32(h->zlsd_offset + 0x00, sf) != get_id32be("DSLZ")) return false; /* 0x04: version? (1) */ - int zlsd_count = read_u32(h->zlsd_offset+0x08,sf); - /* 0x0c: start */ + zlsd_subsongs = read_u32(h->zlsd_offset + 0x08, sf); + /* 0x0c: start (most of the time) */ + /* 0x10: start if 64-bit zlsd_subsongs? seen in SBlk 0x1A/0x1C */ + zlsd_table_offset = read_u32(h->zlsd_offset + 0x0C, sf); /* rest: null */ - if (zlsd_count) { - vgm_logi("BNK: unsupported ZLSD subsongs found\n"); - goto fail; + /* files can have both SBlk+ZLSD streams */ + if (zlsd_subsongs < 1) { + if (h->total_subsongs < 1) + goto fail; + return true; } - /* per entry (for v23) - * 00: crc (not referenced elsewhere) + if (!zlsd_table_offset) + goto fail; /* 64-bit entries count? */ + + /* per entry (for SBlk v0x23) + * 00: fnv1-32 hash of the stream name * 04: stream offset (from this offset) * 08: null (part of offset?) * 0c: stream size * 10: offset/size? - * 14: null */ + * 14/18: null */ /* known streams are standard XVAG (no subsongs) */ + /* target_subsong is negative if it's working on SBlk streams */ + target_subsong = h->target_subsong - h->total_subsongs - 1; + h->total_subsongs += zlsd_subsongs; + + if (h->target_subsong < 0 || h->target_subsong > h->total_subsongs) + goto fail; + + if (target_subsong < 0) + return true; + + zlsd_table_entry_offset = h->zlsd_offset + zlsd_table_offset + target_subsong * 0x18; + h->start_offset = zlsd_table_entry_offset + 0x04 + read_u32(zlsd_table_entry_offset + 0x04, sf); + h->stream_size = read_u32(zlsd_table_entry_offset + 0x0C, sf); + stream_name_hash = read_u32(zlsd_table_entry_offset + 0x00, sf); + + /* should be a switch case, but no other formats known yet */ + if (!is_id32be(h->start_offset, sf, "XVAG")) { + vgm_logi("BNK: unsupported ZLSD subfile found (report)\n"); + goto fail; + } + + snprintf(h->stream_name, STREAM_NAME_SIZE, "%u [pre]", stream_name_hash); + h->channels = 1; /* dummy, real channels will be retrieved from xvag/riff */ + h->codec = XVAG_ATRAC9; + return true; fail: return false; @@ -1145,11 +1281,11 @@ fail: /* parse SCREAM bnk (usually SFX but also used for music) */ static bool parse_bnk_v3(STREAMFILE* sf, bnk_header_t* h) { - /* bnk/SCREAM tool version (v2 is a bit different, not seen v1) */ + /* bnk/SCREAM tool version (v2 is a bit different, not seen v1) */ if (read_u32be(0x00,sf) == 0x03) { /* PS3 */ h->big_endian = 1; } - else if (read_u32le(0x00,sf) == 0x03) { /* PS2/PSP/Vita/PS4 */ + else if (read_u32le(0x00,sf) == 0x03) { /* PS2/PSP/Vita/PS4/PS5 */ h->big_endian = 0; } else { @@ -1163,7 +1299,7 @@ static bool parse_bnk_v3(STREAMFILE* sf, bnk_header_t* h) { return false; /* in theory a bank can contain multiple blocks but only those are used */ - /* section sizes don't include padding (sometimes aligned to 0x10/0x800) */ + /* file is sometimes aligned to 0x10/0x800, so this can't be used for total size checks */ h->sblk_offset = read_u32(0x08,sf); //h->sblk_size = read_u32(0x0c,sf); h->data_offset = read_u32(0x10,sf); @@ -1195,12 +1331,12 @@ static bool parse_bnk_v3(STREAMFILE* sf, bnk_header_t* h) { * - 0x10: block number * - 0x11: padding * version >= v0x1a: - * - 0x0c: hash (0x10) - * - 0x1c: filename (0x100?) + * - 0x0c: uuid (0x10) + * - 0x1c: bank name (0x100?) * version ~= v0x23: * - 0x0c: null (depends on flags? v1a=0x05, v23=0x07) - * - 0x10: hash (0x10) - * - 0x20: filename (0x100?) + * - 0x10: uuid (0x10) + * - 0x20: bank name (0x100?) */ //;VGM_LOG("BNK: h->sblk_offset=%lx, h->data_offset=%lx, h->sblk_version %x\n", h->sblk_offset, h->data_offset, h->sblk_version); //TODO handle, in rare cases may contain subsongs (unsure how are referenced but has its own number) diff --git a/Frameworks/vgmstream/vgmstream/src/meta/btsnd.c b/Frameworks/vgmstream/vgmstream/src/meta/btsnd.c index 97f5fd289..1e89b613d 100644 --- a/Frameworks/vgmstream/vgmstream/src/meta/btsnd.c +++ b/Frameworks/vgmstream/vgmstream/src/meta/btsnd.c @@ -10,24 +10,27 @@ VGMSTREAM* init_vgmstream_btsnd(STREAMFILE* sf) { /* checks */ - if (!check_extensions(sf, "btsnd")) + uint32_t type = read_u32be(0x00,sf); + if (type != 0x00 && type != 0x02) return NULL; - uint32_t type = read_u32be(0x00,sf); - if (type == 0x00) { - loop_flag = 0; - } - else if (type == 0x02) { - loop_flag = 1; - } - else { + if (!check_extensions(sf, "btsnd")) return NULL; - } loop_start = read_s32be(0x04, sf); /* non-looping: 0 or some number lower than samples */ start_offset = 0x08; channels = 2; + // maybe 'type' is just a version number and is always meant to loop? + loop_flag = false; + if (type == 0x00) { + // Petit Computer BIG (WiiU): doesn't loop (fades), Splatoon (WiiU): loops + loop_flag = loop_start > 0; + } + else if (type == 0x02) { + loop_flag = true; + } + /* extra checks since format is so simple */ data_size = get_streamfile_size(sf); num_samples = pcm16_bytes_to_samples(data_size - start_offset, channels); diff --git a/Frameworks/vgmstream/vgmstream/src/meta/fsb5_fev.c b/Frameworks/vgmstream/vgmstream/src/meta/fsb5_fev.c index 105c4c8bd..ce2005140 100644 --- a/Frameworks/vgmstream/vgmstream/src/meta/fsb5_fev.c +++ b/Frameworks/vgmstream/vgmstream/src/meta/fsb5_fev.c @@ -60,6 +60,10 @@ VGMSTREAM* init_vgmstream_fsb5_fev_bank(STREAMFILE* sf) { case 0x534E4448: /* "SNDH" */ bank_offset = offset; bank_size = chunk_size; + if (bank_size == 0) { + vgm_logi("FSB: bank has no subsongs (ignore)\n"); + goto fail; + } break; default: @@ -84,10 +88,9 @@ VGMSTREAM* init_vgmstream_fsb5_fev_bank(STREAMFILE* sf) { * 0x84: SCP Unity (PC) [~2020] * 0x86: Hades (Switch) [~2020] */ size_t entry_size = version <= 0x28 ? 0x04 : 0x08; - int i, banks; /* 0x00: unknown (chunk version? ex LE: 0x00080003, 0x00080005) */ - banks = (bank_size - 0x04) / entry_size; + int banks = (bank_size - 0x04) / entry_size; /* multiple banks is possible but rare [Hades (Switch), Guacamelee 2 (Switch)], * must map bank (global) subsong to FSB (internal) subsong */ @@ -97,7 +100,7 @@ VGMSTREAM* init_vgmstream_fsb5_fev_bank(STREAMFILE* sf) { fsb5_pos = 0; fsb5_subsong = -1; total_subsongs = 0; - for (i = 0; i < banks; i++) { + for (int i = 0; i < banks; i++) { //TODO: fsb5_size fails for v0x28< + encrypted, but only used with multibanks = unlikely uint32_t fsb5_offset = read_u32le(bank_offset + 0x04 + entry_size*i + 0x00,sf); uint32_t fsb5_size = read_u32le(bank_offset + 0x08 + entry_size*i,sf); diff --git a/Frameworks/vgmstream/vgmstream/src/meta/fsb_keys.h b/Frameworks/vgmstream/vgmstream/src/meta/fsb_keys.h index 3b9173fcc..35603bafd 100644 --- a/Frameworks/vgmstream/vgmstream/src/meta/fsb_keys.h +++ b/Frameworks/vgmstream/vgmstream/src/meta/fsb_keys.h @@ -68,6 +68,7 @@ static const fsbkey_info fsbkey_list[] = { { MODE_FSB4, FSBKEY_ADD("AjaxIsTheGoodestBoy") }, // Hello Kitty: Island Adventure (iOS) { MODE_FSB5, FSBKEY_ADD("resoforce") }, // Rivals of Aether 2 (PC) { MODE_FSB5, FSBKEY_ADD("3cfe772db5b55b806541d3faf894020e") }, // Final Fantasy XV: War for Eos (Android) + { MODE_FSB5, FSBKEY_ADD("aj#$kLucf2lh}eqh") }, // Forza Motorsport 2023 (PC) /* some games use a key per file, generated from the filename * (could add all of them but there are a lot of songs, so external .fsbkey are probably better) */ diff --git a/Frameworks/vgmstream/vgmstream/src/meta/hca_keys.h b/Frameworks/vgmstream/vgmstream/src/meta/hca_keys.h index f6c64daf9..0396ef590 100644 --- a/Frameworks/vgmstream/vgmstream/src/meta/hca_keys.h +++ b/Frameworks/vgmstream/vgmstream/src/meta/hca_keys.h @@ -1326,6 +1326,12 @@ static const hcakey_info hcakey_list[] = { // Code Geass: Lost Stories (Android) {9182735170}, // 0000000223556B42 + // Super Robot Wars DD (Android) + {464113616464131416}, // 0670DCE00CC43558 + + // Ange Re:Link (Android) + {9666854456}, // 0000000240307E38 + }; #endif/*_HCA_KEYS_H_*/ diff --git a/Frameworks/vgmstream/vgmstream/src/meta/nus3bank.c b/Frameworks/vgmstream/vgmstream/src/meta/nus3bank.c index 277cab413..b0e617e32 100644 --- a/Frameworks/vgmstream/vgmstream/src/meta/nus3bank.c +++ b/Frameworks/vgmstream/vgmstream/src/meta/nus3bank.c @@ -243,7 +243,7 @@ VGMSTREAM* init_vgmstream_nus3bank(STREAMFILE* sf) { vgmstream->num_streams = total_subsongs; if (name_offset) - read_string(vgmstream->stream_name, name_size, name_offset, sf); + read_string_sz(vgmstream->stream_name, STREAM_NAME_SIZE, name_size, name_offset, sf); close_streamfile(temp_sf); diff --git a/Frameworks/vgmstream/vgmstream/src/meta/psb.c b/Frameworks/vgmstream/vgmstream/src/meta/psb.c index 3c03b01c4..be393bed8 100644 --- a/Frameworks/vgmstream/vgmstream/src/meta/psb.c +++ b/Frameworks/vgmstream/vgmstream/src/meta/psb.c @@ -48,17 +48,18 @@ typedef struct { int32_t num_samples; int32_t body_samples; int32_t intro_samples; - int32_t skip_samples; + int32_t intro_skip; + int32_t body_skip; int loop_flag; - int loop_range; + bool loop_range; int32_t loop_start; int32_t loop_end; - int loop_test; + bool loop_type_unknown; } psb_header_t; -static int parse_psb(STREAMFILE* sf, psb_header_t* psb); +static bool parse_psb(STREAMFILE* sf, psb_header_t* psb); static segmented_layout_data* build_segmented_psb_opus(STREAMFILE* sf, psb_header_t* psb); @@ -244,7 +245,7 @@ VGMSTREAM* init_vgmstream_psb(STREAMFILE* sf) { * - loop_start + loop_length [LoM (PC/And), Namco Museum V1 (PC), Senxin Aleste (PC)] * - loop_start + loop_end [G-Darius (Sw)] * (only in some cases of "loop" field so shouldn't happen to often) */ - if (psb.loop_test) { + if (psb.loop_type_unknown) { if (psb.loop_start + psb.loop_end <= vgmstream->num_samples) { vgmstream->loop_end_sample += psb.loop_start; /* assumed, matches num_samples in LoM and Namco but not in Senjin Aleste (unknown in G-Darius) */ @@ -266,30 +267,33 @@ fail: static segmented_layout_data* build_segmented_psb_opus(STREAMFILE* sf, psb_header_t* psb) { segmented_layout_data* data = NULL; - int i, pos = 0, segment_count = 0, max_count = 2; //TODO improve //TODO these use standard switch opus (VBR), could sub-file? but skip_samples becomes more complex uint32_t offsets[] = {psb->intro_offset, psb->body_offset}; uint32_t sizes[] = {psb->intro_size, psb->body_size}; - uint32_t samples[] = {psb->intro_samples, psb->body_samples}; - uint32_t skips[] = {0, psb->skip_samples}; + int32_t samples[] = {psb->intro_samples, psb->body_samples}; + int32_t skips[] = {0, psb->body_skip}; + + int pos = 0, max_count = 2; /* intro + body (looped songs) or just body (standard songs) * In full loops intro is 0 samples with a micro 1-frame opus [Nekopara (Switch)] */ - if (offsets[0] && samples[0]) + int segment_count = 0; + if (offsets[0] && samples[0] > 0) segment_count++; - if (offsets[1] && samples[1]) + if (offsets[1] && samples[1] > 0) segment_count++; /* init layout */ data = init_layout_segmented(segment_count); if (!data) goto fail; - for (i = 0; i < max_count; i++) { - if (!offsets[i] || !samples[i]) + for (int i = 0; i < max_count; i++) { + if (!offsets[i] || samples[i] <= 0) continue; + #ifdef VGM_USE_FFMPEG { int start = read_u32le(offsets[i] + 0x10, sf) + 0x08; @@ -337,14 +341,13 @@ fail: static layered_layout_data* build_layered_psb(STREAMFILE* sf, psb_header_t* psb) { layered_layout_data* data = NULL; - int i; /* init layout */ data = init_layout_layered(psb->layers); if (!data) goto fail; - for (i = 0; i < psb->layers; i++) { + for (int i = 0; i < psb->layers; i++) { switch (psb->codec) { case PCM: { VGMSTREAM* v = allocate_vgmstream(1, 0); @@ -392,7 +395,7 @@ fail: /*****************************************************************************/ -static int prepare_fmt(STREAMFILE* sf, psb_header_t* psb) { +static bool prepare_fmt(STREAMFILE* sf, psb_header_t* psb) { uint32_t offset = psb->fmt_offset; if (!offset) return 1; /* other codec, probably */ @@ -429,12 +432,12 @@ static int prepare_fmt(STREAMFILE* sf, psb_header_t* psb) { } - return 1; + return true; fail: - return 0; + return false; } -static int prepare_codec(STREAMFILE* sf, psb_header_t* psb) { +static bool prepare_codec(STREAMFILE* sf, psb_header_t* psb) { const char* spec = psb->tmp->spec; const char* ext = psb->tmp->ext; @@ -456,7 +459,7 @@ static int prepare_codec(STREAMFILE* sf, psb_header_t* psb) { default: goto fail; } - return 1; + return true; } /* try console strings */ @@ -471,23 +474,28 @@ static int prepare_codec(STREAMFILE* sf, psb_header_t* psb) { if (strcmp(ext, ".opus") == 0) { psb->codec = OPUSNX; - psb->body_samples -= psb->skip_samples; + psb->body_samples -= psb->body_skip; - /* When setting loopstr="range:N,M", doesn't seem to transition properly (clicks) unless aligned (not always?) - * > N=intro's sampleCount, M=intro+body's sampleCount - skipSamples - default_skip, but not always - * [Anonymous;Code (Switch)-bgm08, B-Project: Ryuusei Fantasia (Switch)-bgm27] */ - if (psb->loop_range) { - //TODO read actual default skip - psb->intro_samples -= 120; - psb->body_samples -= 120; + /* Sometimes intro + body loops don't seem to transition properly (clicks) unless aligned, but not always + * > N=intro's sampleCount, M=intro+body's sampleCount - skipSamples - default_skip. [B-Project: Ryuusei Fantasia (Switch)-bgm27] + * However this click may happen too in other codecs, so it's probably an encoder quirk [Anonymous;Code (Switch)-bgm08 SW opus vs PC msadpcm] + * There is a 'loopstr="range:N,M"' value, which may match intro + body samples, or be slightly smaller than body samples. + * Since presumably the point of separate intro + body is manual looping via subfiles, assume loopstr is just info and not used. + * skipSamples may not be set with full loops [The Quintessential Quintuplets: Memories of a Quintessential Summer (Switch)-bgm02 vs bgm03] */ + #if 0 + if (psb->body_skip) { + int default_skip = 120; //TODO read actual value, but harder to fix loops later + if (psb->intro_samples > default_skip) psb->intro_samples -= default_skip; //this seems to match loop_start plus may be 0 + if (psb->body_samples > default_skip) psb->body_samples -= default_skip; } + #endif if (!psb->loop_flag) psb->loop_flag = psb->intro_samples > 0; psb->loop_start = psb->intro_samples; psb->loop_end = psb->body_samples + psb->intro_samples; psb->num_samples = psb->intro_samples + psb->body_samples; - return 1; + return true; } /* Legend of Mana (Switch), layered */ @@ -495,7 +503,7 @@ static int prepare_codec(STREAMFILE* sf, psb_header_t* psb) { psb->codec = DSP; psb->channels = psb->layers; - return 1; + return true; } /* Castlevania Advance Collection (Switch), layered */ @@ -504,13 +512,13 @@ static int prepare_codec(STREAMFILE* sf, psb_header_t* psb) { psb->bps = 16; psb->channels = psb->layers; - return 1; + return true; } } if (strcmp(spec, "ps3") == 0) { psb->codec = RIFF_AT3; - return 1; + return true; } if (strcmp(spec, "vita") == 0 || strcmp(spec, "ps4") == 0) { @@ -518,7 +526,7 @@ static int prepare_codec(STREAMFILE* sf, psb_header_t* psb) { psb->codec = RIFF_AT9; else psb->codec = VAG; - return 1; + return true; } if (strcmp(spec, "and") == 0) { @@ -527,29 +535,29 @@ static int prepare_codec(STREAMFILE* sf, psb_header_t* psb) { if (strcmp(ext, ".ogg") == 0) { psb->codec = OGG_VORBIS; - return 1; + return true; } if (strcmp(ext, ".wav") == 0) { psb->codec = RIFF_WAV; - return 1; + return true; } } fail: vgm_logi("PSB: unknown codec (report)\n"); - return 0; + return false; } -static int prepare_name(psb_header_t* psb) { +static bool prepare_name(psb_header_t* psb) { const char* main_name = psb->tmp->voice; const char* sub_name = psb->tmp->uniq; char* buf = psb->readable_name; int buf_size = sizeof(psb->readable_name); if (!main_name) /* shouldn't happen */ - return 1; + return true; if (!sub_name) sub_name = psb->tmp->wav; @@ -575,19 +583,19 @@ static int prepare_name(psb_header_t* psb) { snprintf(buf, buf_size, "%s", main_name); } - return 1; + return true; } -static int prepare_psb_extra(STREAMFILE* sf, psb_header_t* psb) { +static bool prepare_psb_extra(STREAMFILE* sf, psb_header_t* psb) { if (!prepare_fmt(sf, psb)) goto fail; if (!prepare_codec(sf, psb)) goto fail; if (!prepare_name(psb)) goto fail; - return 1; + return true; fail: - return 0; + return false; } @@ -600,15 +608,14 @@ fail: * - data/dpds/fmt/wav/loop * - pan: array [N.0 .. 0.N] (when N layers, in practice just a wonky L/R definition) */ -static int parse_psb_channels(psb_header_t* psb, psb_node_t* nchans) { - int i; +static bool parse_psb_channels(psb_header_t* psb, psb_node_t* nchans) { psb_node_t nchan, narch, nsub, node; psb->layers = psb_node_get_count(nchans); if (psb->layers == 0) goto fail; if (psb->layers > PSB_MAX_LAYERS) goto fail; - for (i = 0; i < psb->layers; i++) { + for (int i = 0; i < psb->layers; i++) { psb_data_t data; psb_type_t type; @@ -664,7 +671,7 @@ static int parse_psb_channels(psb_header_t* psb, psb_node_t* nchans) { psb->loop_end = le.num; } - psb->loop_test = 1; /* loop end meaning varies*/ + psb->loop_type_unknown = true; /* loop end meaning varies*/ } } @@ -673,7 +680,7 @@ static int parse_psb_channels(psb_header_t* psb, psb_node_t* nchans) { psb->body_offset = data.offset; psb->body_size = data.size; psb->body_samples = psb_node_get_integer(&node, "sampleCount"); - psb->skip_samples = psb_node_get_integer(&node, "skipSampleCount"); /* fixed to seek_preroll? (80ms) */ + psb->body_skip = psb_node_get_integer(&node, "skipSampleCount"); /* fixed to seek_preroll? (80ms) */ } if (psb_node_by_key(&narch, "intro", &node)) { @@ -681,6 +688,8 @@ static int parse_psb_channels(psb_header_t* psb, psb_node_t* nchans) { psb->intro_offset = data.offset; psb->intro_size = data.size; psb->intro_samples = psb_node_get_integer(&node, "sampleCount"); + psb->intro_skip = psb_node_get_integer(&node, "skipSampleCount"); /* fixed to seek_preroll? (80ms) */ + vgm_asserti(psb->intro_skip, "PSB: intro skip found\n"); } data = psb_node_get_data(&narch, "dpds"); @@ -714,15 +723,16 @@ static int parse_psb_channels(psb_header_t* psb, psb_node_t* nchans) { goto fail; } } - return 1; + + return true; fail: VGM_LOG("psb: can't parse channel\n"); - return 0; + return false; } /* parse a single archive, that can contain extra info here or inside channels */ -static int parse_psb_voice(psb_header_t* psb, psb_node_t* nvoice) { +static bool parse_psb_voice(psb_header_t* psb, psb_node_t* nvoice) { psb_node_t nsong, nchans; @@ -757,7 +767,7 @@ static int parse_psb_voice(psb_header_t* psb, psb_node_t* nvoice) { /* loopstr values: * - "none", w/ loop=0 * - "all", w/ loop = 2 [Legend of Mana (multi)] - * - "range:N,M", w/ loop = 2 [Anonymous;Code (Switch)] */ + * - "range:N,M", w/ loop = 2 [Anonymous;Code (Switch/PC)], assumed to be info info */ psb->loop_range = loopstr && strncmp(loopstr, "range:", 6) == 0; /* slightly different in rare cases */ } @@ -769,10 +779,10 @@ static int parse_psb_voice(psb_header_t* psb, psb_node_t* nvoice) { * - group? */ - return 1; + return true; fail: VGM_LOG("psb: can't parse voice\n"); - return 0; + return false; } /* .psb is binary JSON-like structure that can be used to hold various formats, we want audio data: @@ -790,7 +800,7 @@ fail: * From decompilations, audio code reads common keys up to "archData", then depends on game (not unified). * Keys are (seemingly) stored in text order. */ -static int parse_psb(STREAMFILE* sf, psb_header_t* psb) { +static bool parse_psb(STREAMFILE* sf, psb_header_t* psb) { psb_temp_t tmp = {0}; psb_context_t* ctx = NULL; psb_node_t nroot, nvoice; @@ -801,7 +811,9 @@ static int parse_psb(STREAMFILE* sf, psb_header_t* psb) { ctx = psb_init(sf); if (!ctx) goto fail; +#ifdef VGM_DEBUG_OUTPUT //psb_print(ctx); +#endif /* main process */ psb_get_root(ctx, &nroot); @@ -838,25 +850,9 @@ static int parse_psb(STREAMFILE* sf, psb_header_t* psb) { psb->tmp = NULL; psb_close(ctx); - return 1; + return true; fail: psb_close(ctx); VGM_LOG("psb: can't parse PSB\n"); - return 0; + return false; } - -#if 0 -typedef struct { - void* init; - const char* id32; - const char* exts; -} metadef_t; - -metadef_t md_psb = { - .init = init_vgmstream_psb, - .exts = "psb", - .id32 = "PSB\0", //24b/masked IDs? - .id32 = get_id32be("PSB\0"), //??? - .idfn = psb_check_id, -} -#endif diff --git a/Frameworks/vgmstream/vgmstream/src/meta/riff.c b/Frameworks/vgmstream/vgmstream/src/meta/riff.c index 1d94fabe1..2d4ceda4b 100644 --- a/Frameworks/vgmstream/vgmstream/src/meta/riff.c +++ b/Frameworks/vgmstream/vgmstream/src/meta/riff.c @@ -402,8 +402,9 @@ VGMSTREAM* init_vgmstream_riff(STREAMFILE* sf) { * .wax: Lamborghini (Xbox) * .voi: Sol Trigger (PSP)[ATRAC3] * .se: Rockman X4 (PC) + * .v: Rozen Maiden: Duellwalzer (PS2) */ - if (!check_extensions(sf, "wav,lwav,xwav,mwv,da,dax,cd,med,snd,adx,adp,xss,xsew,adpcm,adw,wd,,sbv,wvx,str,at3,rws,aud,at9,ckd,saf,ima,nsa,pcm,xvag,ogg,logg,p1d,xms,mus,dat,ldat,wma,lwma,caf,wax,voi,se")) { + if (!check_extensions(sf, "wav,lwav,xwav,mwv,da,dax,cd,med,snd,adx,adp,xss,xsew,adpcm,adw,wd,,sbv,wvx,str,at3,rws,aud,at9,ckd,saf,ima,nsa,pcm,xvag,ogg,logg,p1d,xms,mus,dat,ldat,wma,lwma,caf,wax,voi,se,v")) { return NULL; } diff --git a/Frameworks/vgmstream/vgmstream/src/meta/sqex_scd.c b/Frameworks/vgmstream/vgmstream/src/meta/sqex_scd.c index 7cc6d7a3d..8dd3d6121 100644 --- a/Frameworks/vgmstream/vgmstream/src/meta/sqex_scd.c +++ b/Frameworks/vgmstream/vgmstream/src/meta/sqex_scd.c @@ -197,7 +197,7 @@ VGMSTREAM* init_vgmstream_sqex_scd(STREAMFILE* sf) { /* actual Ogg init */ ogg_vgmstream = init_vgmstream_ogg_vorbis_config(sf, start_offset, &ovmi); if (ogg_vgmstream && name_offset) - read_string(ogg_vgmstream->stream_name, PATH_LIMIT, name_offset, sf); + read_string(ogg_vgmstream->stream_name, STREAM_NAME_SIZE, name_offset, sf); return ogg_vgmstream; } #endif diff --git a/Frameworks/vgmstream/vgmstream/src/meta/sqex_sead.c b/Frameworks/vgmstream/vgmstream/src/meta/sqex_sead.c index 0112fa82b..763d936c8 100644 --- a/Frameworks/vgmstream/vgmstream/src/meta/sqex_sead.c +++ b/Frameworks/vgmstream/vgmstream/src/meta/sqex_sead.c @@ -244,6 +244,48 @@ VGMSTREAM* init_vgmstream_sqex_sead(STREAMFILE* sf) { } #endif +#ifdef VGM_USE_FFMPEG + case 0x05: { /* XMA2 [Kingdom Hearts 3 (X1)] */ + start_offset = sead.extradata_offset + sead.extradata_size; + + /* extradata */ + // 00: null? + // 03: XMA sub-version? (4) + // 04: extradata base size (without seek) + // 06: seek entries + // 08: XMA sample rate (ex. may be 47999) + // 0c: bitrate? + // 10: block size? + // 14: null? + // 18: total samples (with encoder delay) + // 1c: frame size? + // 20: null? + // 24: total samples (without encoder delay) + // 28: loop start + // 2c: loop length? + // 30+ seek table + + int block_size = read_u32(sead.extradata_offset+0x10,sf); + if (!block_size) + goto fail; + int block_count = sead.stream_size + 1; + int num_samples = read_u32(sead.extradata_offset+0x24,sf); + + vgmstream->codec_data = init_ffmpeg_xma2_raw(sf, start_offset, sead.stream_size, num_samples, sead.channels, sead.sample_rate, block_size, block_count); + if (!vgmstream->codec_data) goto fail; + vgmstream->coding_type = coding_FFmpeg; + vgmstream->layout_type = layout_none; + + vgmstream->num_samples = num_samples; + vgmstream->loop_start_sample = sead.loop_start; + vgmstream->loop_end_sample = sead.loop_end; + + //xma_fix_raw_samples(vgmstream, sf, start_offset, sead.stream_size, 0, 0,1); + + break; + } +#endif + #ifdef VGM_USE_MPEG case 0x06: { /* MSMP3 (MSF subfile) [Dragon Quest Builders (PS3)] */ mpeg_custom_config cfg = {0}; @@ -310,7 +352,6 @@ VGMSTREAM* init_vgmstream_sqex_sead(STREAMFILE* sf) { } } - case 0x05: /* XMA2 (extradata may be a XMA2 fmt extra chunk) */ case 0x08: /* SWITCHOPUS (no extradata?) */ default: vgm_logi("SQEX SEAD: unknown codec %x\n", sead.codec); @@ -339,58 +380,45 @@ static void sead_cat(char* dst, int dst_max, const char* src) { } static void build_readable_sab_name(sead_header_t* sead, STREAMFILE* sf, uint32_t sndname_offset, uint32_t sndname_size) { - char * buf = sead->readable_name; + char* buf = sead->readable_name; int buf_size = sizeof(sead->readable_name); - char descriptor[255], name[255]; - - if (sead->filename_size > 255 || sndname_size > 255) - goto fail; + char descriptor[256], name[256]; if (buf[0] == '\0') { /* init */ - read_string(descriptor,sead->filename_size+1, sead->filename_offset, sf); - read_string(name, sndname_size+1, sndname_offset, sf); + read_string_sz(descriptor, sizeof(descriptor), sead->filename_size, sead->filename_offset, sf); + read_string_sz(name, sizeof(name), sndname_size, sndname_offset, sf); snprintf(buf,buf_size, "%s/%s", descriptor, name); } else { /* add */ - read_string(name, sndname_size+1, sndname_offset, sf); + read_string_sz(name, sizeof(name), sndname_size, sndname_offset, sf); sead_cat(buf, buf_size, "; "); sead_cat(buf, buf_size, name); } - return; -fail: - VGM_LOG("SEAD: bad sab name found\n"); } static void build_readable_mab_name(sead_header_t* sead, STREAMFILE* sf) { - char * buf = sead->readable_name; + char* buf = sead->readable_name; int buf_size = sizeof(sead->readable_name); - char descriptor[255], name[255], mode[255]; + char descriptor[256], name[256], mode[256]; - if (sead->filename_size > 255 || sead->muscname_size > 255 || sead->sectname_size > 255 || sead->modename_size > 255) - goto fail; - - read_string(descriptor,sead->filename_size+1,sead->filename_offset, sf); - //read_string(filename,sead->muscname_size+1,sead->muscname_offset, sf); /* same as filename, not too interesting */ + read_string_sz(descriptor, sizeof(descriptor), sead->filename_size, sead->filename_offset, sf); + //read_string_sz(filename, sizeof(filename), sead->muscname_size, sead->muscname_offset, sf); /* same as filename, not too interesting */ if (sead->sectname_offset) - read_string(name,sead->sectname_size+1,sead->sectname_offset, sf); + read_string_sz(name, sizeof(name), sead->sectname_size,sead->sectname_offset, sf); else if (sead->instname_offset) - read_string(name,sead->instname_size+1,sead->instname_offset, sf); + read_string_sz(name, sizeof(name), sead->instname_size, sead->instname_offset, sf); else strcpy(name, "?"); if (sead->modename_offset > 0) - read_string(mode,sead->modename_size+1,sead->modename_offset, sf); + read_string_sz(mode, sizeof(mode), sead->modename_size,sead->modename_offset, sf); /* default mode in most files */ if (sead->modename_offset == 0 || strcmp(mode, "Mode") == 0 || strcmp(mode, "Mode0") == 0) snprintf(buf,buf_size, "%s/%s", descriptor, name); else snprintf(buf,buf_size, "%s/%s/%s", descriptor, name, mode); - - return; -fail: - VGM_LOG("SEAD: bad mab name found\n"); } static void parse_sead_mab_name(sead_header_t* sead, STREAMFILE* sf) { diff --git a/Frameworks/vgmstream/vgmstream/src/meta/txth.c b/Frameworks/vgmstream/vgmstream/src/meta/txth.c index 4caebdeb1..c9c7728f3 100644 --- a/Frameworks/vgmstream/vgmstream/src/meta/txth.c +++ b/Frameworks/vgmstream/vgmstream/src/meta/txth.c @@ -330,8 +330,7 @@ VGMSTREAM* init_vgmstream_txth(STREAMFILE* sf) { vgmstream->num_streams = txth.subsong_count; vgmstream->stream_size = txth.data_size; if (txth.name_offset_set) { - size_t name_size = txth.name_size ? txth.name_size + 1 : STREAM_NAME_SIZE; - read_string(vgmstream->stream_name,name_size, txth.name_offset,txth.sf_head); + read_string_sz(vgmstream->stream_name, STREAM_NAME_SIZE, txth.name_size, txth.name_offset, txth.sf_head); } /* codec specific (taken from GENH with minimal changes) */ @@ -780,6 +779,9 @@ static VGMSTREAM* init_subfile(txth_header* txth) { } //todo: other combos with subsongs + subfile? + if (txth->name_offset_set) { + read_string_sz(vgmstream->stream_name, STREAM_NAME_SIZE, txth->name_size, txth->name_offset, txth->sf_head); + } close_streamfile(sf_sub); return vgmstream; diff --git a/Frameworks/vgmstream/vgmstream/src/util/m2_psb.c b/Frameworks/vgmstream/vgmstream/src/util/m2_psb.c index 90276633a..78d35a69c 100644 --- a/Frameworks/vgmstream/vgmstream/src/util/m2_psb.c +++ b/Frameworks/vgmstream/vgmstream/src/util/m2_psb.c @@ -784,6 +784,8 @@ int psb_node_exists(const psb_node_t* node, const char* key) { /******************************************************************************/ /* ETC */ +#ifdef VGM_DEBUG_OUTPUT + #define PSB_DEPTH_STEP 2 static void print_internal(psb_node_t* curr, int depth) { @@ -862,3 +864,4 @@ void psb_print(psb_context_t* ctx) { psb_get_root(ctx, &node); print_internal(&node, 0); } +#endif diff --git a/Frameworks/vgmstream/vgmstream/src/util/reader.c b/Frameworks/vgmstream/vgmstream/src/util/reader.c index 341485674..6e7b008f6 100644 --- a/Frameworks/vgmstream/vgmstream/src/util/reader.c +++ b/Frameworks/vgmstream/vgmstream/src/util/reader.c @@ -120,29 +120,41 @@ size_t read_bom(STREAMFILE* sf) { return 0x00; } -size_t read_string(char* buf, size_t buf_size, off_t offset, STREAMFILE* sf) { - size_t pos; +size_t read_string_sz(char* buf, size_t buf_size, size_t string_size, off_t offset, STREAMFILE* sf) { - for (pos = 0; pos < buf_size; pos++) { + // read up to buf, or stop before if size is set; in either case will stop at 0x00 + size_t max_size = buf_size; + if (string_size > 0 && string_size < max_size) + max_size = string_size + 1; + + for (size_t pos = 0; pos < max_size; pos++) { uint8_t byte = read_u8(offset + pos, sf); - char c = (char)byte; - if (buf) buf[pos] = c; - if (c == '\0') + if (buf) buf[pos] = (char)byte; + + // done + if (byte == '\0') return pos; - if (pos+1 == buf_size) { /* null at maxsize and don't validate (expected to be garbage) */ + + // null at maxsize and don't validate (expected to be garbage) + if (pos + 1 == max_size) { if (buf) buf[pos] = '\0'; - return buf_size; + return max_size; } - /* UTF-8 only goes to 0x7F, but allow a bunch of Windows-1252 codes that some games use */ + + // UTF-8 only goes to 0x7F, but allow a bunch of Windows-1252 codes that some games use if (byte < 0x20 || byte > 0xF0) - goto fail; + break; } -fail: + // error or wrong max_size if (buf) buf[0] = '\0'; return 0; } +size_t read_string(char* buf, size_t buf_size, off_t offset, STREAMFILE* sf) { + return read_string_sz(buf, buf_size, 0, offset, sf); +} + size_t read_string_utf16(char* buf, size_t buf_size, off_t offset, STREAMFILE* sf, int big_endian) { size_t pos, offpos; read_u16_t read_u16 = big_endian ? read_u16be : read_u16le; diff --git a/Frameworks/vgmstream/vgmstream/src/util/reader_text.h b/Frameworks/vgmstream/vgmstream/src/util/reader_text.h index 80475e58b..7fa188a3b 100644 --- a/Frameworks/vgmstream/vgmstream/src/util/reader_text.h +++ b/Frameworks/vgmstream/vgmstream/src/util/reader_text.h @@ -11,7 +11,10 @@ size_t read_line(char* buf, int buf_size, off_t offset, STREAMFILE* sf, int* p_l /* skip BOM if needed */ size_t read_bom(STREAMFILE* sf); -/* reads a c-string (ANSI only), up to bufsize or NULL, returning size. buf is optional (works as get_string_size). */ +/* Reads a C-string (ANSI only), up to buf_size, string_size (no need to include null char), or NULL (whichever is happens first). + * Returning size and buf is optional (works as get_string_size without it). Will always null-terminate the string. */ +size_t read_string_sz(char* buf, size_t buf_size, size_t string_size, off_t offset, STREAMFILE* sf); +/* same but without known string_size */ size_t read_string(char* buf, size_t buf_size, off_t offset, STREAMFILE* sf); /* reads a UTF16 string... but actually only as ANSI (discards the upper byte) */ size_t read_string_utf16(char* buf, size_t buf_size, off_t offset, STREAMFILE* sf, int big_endian); diff --git a/Frameworks/vgmstream/vgmstream/src/vgmstream.c b/Frameworks/vgmstream/vgmstream/src/vgmstream.c index 3d37461b5..6234e6523 100644 --- a/Frameworks/vgmstream/vgmstream/src/vgmstream.c +++ b/Frameworks/vgmstream/vgmstream/src/vgmstream.c @@ -216,18 +216,19 @@ VGMSTREAM* allocate_vgmstream(int channels, int loop_flag) { if (!vgmstream->loop_ch) goto fail; } - /* garbage buffer for decode discarding (local bufs may cause stack overflows with segments/layers) - * in theory the bigger the better but in practice there isn't much difference */ - vgmstream->tmpbuf_size = 0x10000; /* for all channels */ - vgmstream->tmpbuf = malloc(sizeof(sample_t) * vgmstream->tmpbuf_size); - if (!vgmstream->tmpbuf) goto fail; - vgmstream->channels = channels; vgmstream->loop_flag = loop_flag; vgmstream->mixer = mixer_init(vgmstream->channels); /* pre-init */ //if (!vgmstream->mixer) goto fail; + //TODO: improve/init later to minimize memory + /* garbage buffer for seeking/discarding (local bufs may cause stack overflows with segments/layers) + * in theory the bigger the better but in practice there isn't much difference. */ + vgmstream->tmpbuf_size = 1024 * 2 * channels * sizeof(float); + vgmstream->tmpbuf = malloc(vgmstream->tmpbuf_size); + if (!vgmstream->tmpbuf) goto fail; + /* BEWARE: merge_vgmstream does some free'ing too */ //vgmstream->stream_name_size = STREAM_NAME_SIZE; diff --git a/Frameworks/vgmstream/vgmstream/src/vgmstream.h b/Frameworks/vgmstream/vgmstream/src/vgmstream.h index 0b5372f34..2ba80c00e 100644 --- a/Frameworks/vgmstream/vgmstream/src/vgmstream.h +++ b/Frameworks/vgmstream/vgmstream/src/vgmstream.h @@ -247,8 +247,8 @@ typedef struct { int loop_count; /* counter of complete loops (1=looped once) */ int loop_target; /* max loops before continuing with the stream end (loops forever if not set) */ - sample_t* tmpbuf; /* garbage buffer used for seeking/trimming */ - size_t tmpbuf_size; /* for all channels (samples = tmpbuf_size / channels) */ + void* tmpbuf; /* garbage buffer used for seeking/trimming */ + size_t tmpbuf_size; /* for all channels (samples = tmpbuf_size / channels / sample_size) */ } VGMSTREAM;