Updated VGMStream to r1951-50-g1d836a36

This commit is contained in:
Christopher Snowhill 2024-09-17 02:19:58 -07:00
parent 8498bba881
commit 5c8441eb22
56 changed files with 1440 additions and 775 deletions

View file

@ -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 = "<group>"; };
834FE0E7215C79EC000A5D3D /* msv.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = msv.c; sourceTree = "<group>"; };
834FE0E8215C79EC000A5D3D /* ck.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = ck.c; sourceTree = "<group>"; };
835096F02C9979DD00163D93 /* libvgmstream.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = libvgmstream.h; sourceTree = "<group>"; };
835096F12C9979DD00163D93 /* libvgmstream_streamfile.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = libvgmstream_streamfile.h; sourceTree = "<group>"; };
8350C0541E071881009E0A93 /* xma.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = xma.c; sourceTree = "<group>"; };
8351F32A2212B57000A606E4 /* 208.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = 208.c; sourceTree = "<group>"; };
8351F32B2212B57000A606E4 /* ubi_bao_streamfile.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ubi_bao_streamfile.h; sourceTree = "<group>"; };
@ -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 = "<group>";
@ -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 */,

View file

@ -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

View file

@ -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;

View file

@ -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;

View file

@ -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

View file

@ -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;

View file

@ -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;

View file

@ -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:

View file

@ -7,26 +7,22 @@
#include <math.h>
#include <limits.h>
//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);
}

View file

@ -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

View file

@ -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);

View file

@ -1,36 +1,14 @@
#include <math.h>
#include <limits.h>
#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 <math.h>
#include <limits.h>
//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;
}

View file

@ -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

View file

@ -1,10 +1,10 @@
#include <math.h>
#include <limits.h>
#include "../vgmstream.h"
#include "../util/channel_mappings.h"
#include "../layout/layout.h"
#include "mixing.h"
#include "mixer_priv.h"
#include <math.h>
#include <limits.h>
#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<<mp_in)))
if (!(input_mapping & (1 << mp_in)))
continue;
ch_out = 0;
for (mp_out = 0; mp_out < 16; mp_out++) {
/* read output mapping (ex. 2.0) and find channel */
if (!(output_mapping & (1<<mp_out)))
if (!(output_mapping & (1 << mp_out)))
continue;
mixing_push_add(vgmstream, ch_out, max + ch_in, matrix[mp_in][mp_out]);
@ -565,3 +565,16 @@ void mixing_macro_downmix(VGMSTREAM* vgmstream, int max /*, mapping_t output_map
/* remove unneeded channels */
mixing_push_killmix(vgmstream, max);
}
void mixing_macro_output_sample_format(VGMSTREAM* vgmstream, sfmt_t type) {
mixer_t* mixer = vgmstream->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;
}

View file

@ -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) {

View file

@ -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);
}

View file

@ -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

View file

@ -1,85 +1,412 @@
#include <stdlib.h>
#include <string.h>
//#include <math.h>
#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);
}

View file

@ -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

View file

@ -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
}
}

View file

@ -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);
}

View file

@ -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;

View file

@ -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;

View file

@ -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;

View file

@ -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);

View file

@ -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;

View file

@ -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) {

View file

@ -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 */

View file

@ -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);
}

View file

@ -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",

View file

@ -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 */

View file

@ -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);
}

View file

@ -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);
}

View file

@ -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;

View file

@ -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);

View file

@ -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, &current_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;

View file

@ -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.
*/

View file

@ -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

View file

@ -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 */

View file

@ -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))

View file

@ -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)

View file

@ -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);

View file

@ -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);

View file

@ -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) */

View file

@ -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_*/

View file

@ -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);

View file

@ -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

View file

@ -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;
}

View file

@ -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

View file

@ -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) {

View file

@ -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;

View file

@ -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

View file

@ -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;

View file

@ -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);

View file

@ -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;

View file

@ -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;