Updated VGMStream to r1917-201-g7ab622d3

Also updated filename extensions,  and the interface plugins.

Signed-off-by: Christopher Snowhill <kode54@gmail.com>
This commit is contained in:
Christopher Snowhill 2024-08-17 03:20:11 -07:00
parent 7b2fcf7c94
commit 626a9e2205
341 changed files with 43611 additions and 36782 deletions

File diff suppressed because it is too large Load diff

View file

@ -2,29 +2,50 @@
#define _API_H_
#ifdef BUILD_VGMSTREAM
#include "base/plugins.h"
#include "base/plugins.h" //TODO: to be removed
#else
#include <libvgmstream/plugins.h>
#endif
//#define LIBVGMSTREAM_ENABLE 1
#if LIBVGMSTREAM_ENABLE
//possible future public/opaque API
#if 0
/* vgmstream's public API
*
* By default vgmstream behaves like a simple decoder (extract samples until stream end), but you can configure it
* to loop N times or even downmix. In other words, it also behaves a bit like a player.
*
* It exposes multiple options and convenience functions beyond simple decoding mainly for various plugins,
* since it was faster moving shared behavior to core rather than reimplementing every time.
*
* All this may make the API a bit twisted and coupled (sorry, tried my best), probably will improve later. Probably.
*
* Notes:
* - vgmstream may dynamically allocate stuff as needed (not too much beyond some setup buffers, but varies per format)
* - previously the only way to use vgmstream was accesing its internals. Now there is an API internals may change in the future
* - some details described in the API may not happen at the moment (they are defined for future internal changes)
* - main reason it uses the slighly long-winded libvgmstream_* names is that internals use the vgmstream_* 'namespace'
* - c-strings should be in UTF-8
* - the API is still WIP and may be slightly buggy overall due to lack of time, to be improved later
* - vgmstream's features are mostly stable, but this API may be tweaked from time to time (check API_VERSION)
*
* Basic usage (also see api_example.c):
* - libvgmstream_init(...) // base context
* - libvgmstream_setup(...) // config if needed
* - libvgmstream_open(...) // setup format
* - libvgmstream_play(...) // main decode
* - output samples + repeat libvgmstream_play until stream is done
* - libvgmstream_free(...) // cleanup
*/
#include <stdint.h>
#include "api_streamfile.h"
#include <stdbool.h>
/* Current API version (major=breaking API/ABI changes, minor=compatible ABI changes).
* Internal bug fixes or added formats don't change these (see commit revision).
* Regular vgmstream features or formats are stable and are rarely removed, while this API may change from time to time */
#define LIBVGMSTREAM_API_VERSION_MAJOR 0
#define LIBVGMSTREAM_API_VERSION_MINOR 0
/* define standard C param call and name mangling (to avoid __stdcall / .defs) */
/* standard C param call and name mangling (to avoid __stdcall / .defs) */
//#define LIBVGMSTREAM_CALL __cdecl //needed?
//LIBVGMSTREAM_API (type) LIBVGMSTREAM_CALL libvgmstream_function(...);
/* define external function types (during compilation) */
//LIBVGMSTREAM_API void LIBVGMSTREAM_CALL vgmstream_function(void);
/* define external function behavior (during compilation) */
#if defined(LIBVGMSTREAM_EXPORT)
#define LIBVGMSTREAM_API __declspec(dllexport) /* when exporting/creating vgmstream DLL */
#elif defined(LIBVGMSTREAM_IMPORT)
@ -33,104 +54,11 @@
#define LIBVGMSTREAM_API /* nothing, internal/default */
#endif
/* opaque vgmstream context/handle */
typedef struct libvgmstream_t libvgmstream_t;
/* init base vgmstream context */
libvgmstream_t* libvgmstream_init(void);
typedef struct {
int downmix_max_channels; // max number of channels
//int upmix_min_channels; // adds channels until min
} libvgmstream_config_t;
/* pass default config, that will be applied to song on open (some formats like TXTP may override
* these settings).
* May only be called without song loaded (before _open or after _close), otherwise ignored. */
void libvgmstream_setup(libvgmstream_t* vctx, libvgmstream_config_t* vcfg);
//void libvgmstream_buffer(libvgmstream_t* vctx, int samples, int max_samples);
/* Opens a new STREAMFILE to play. Returns < 0 on error when the file isn't recogniced.
* If file has subsongs, first open usually loads first subsong. get_info then can be used to check
* whether file has more subsongs (total_subsongs > 1), and call others.
* */
int libvgmstream_open(libvgmstream_t* vctx, STREAMFILE* sf);
int libvgmstream_open_subsong(libvgmstream_t* vctx, STREAMFILE* sf, int subsong);
typedef struct {
const int channels;
const int sample_rate;
const int sample_count; /* file's samples (not final duration) */
const int loop_start_sample;
const int loop_end_sample;
const int loop_flag;
const int current_subsong; /* 0=not set, N=loaded subsong N */
const int total_subsongs; /* 0=format has no subsongs, N=has N subsongs */
const int file_bitrate; /* file's average bitrate */
//const int codec_bitrate; /* codec's average bitrate */
/* descriptions */
//const char* codec;
//const char* layout;
//const char* metadata;
//int type; /* 0=pcm16, 1=float32, always interleaved: [0]=ch0, [1]=ch1 ... */
} libvgmstream_into_t;
/* Get info from current song. */
void libvgmstream_t_get_info(libvgmstream_t* vctx, libvgmstream_into_t* vinfo);
libvgmstream_sbuf_t* libgstream_get_sbuf(libvgmstream_t* vctx);
/* Converts samples. returns number of rendered samples, or <=0 if no more
* samples left (will fill buffer with silence) */
int libvgmstream_play(libvgmstream_t* vctx);
/* Gets final time based on config and current song. If config is set to "play forever"
* this still returns final time based on config as a reference. Returns > 0 on success. */
int32_t libvgmstream_get_total_time(libvgmstream_t* vctx);
double libvgmstream_get_total_samples(libvgmstream_t* vctx);
/* Gets current position within song. When "play forever" is set, it'll clamp results to total_time. */
int32_t libvgmstream_get_current_time(libvgmstream_t* vctx);
double libvgmstream_get_current_samples(libvgmstream_t* vctx);
/* Seeks to position */
libvgmstream_t* libvgmstream_seek_absolute_sample(libvgmstream_t* vctx, int32_t sample);
libvgmstream_t* libvgmstream_seek_absolute_time(libvgmstream_t* vctx, double time);
libvgmstream_t* libvgmstream_seek_current_sample(libvgmstream_t* vctx, int32_t sample);
libvgmstream_t* libvgmstream_seek_current_time(libvgmstream_t* vctx, double time);
/* Closes current song. */
void libvgmstream_close(libvgmstream_t* vctx);
/* Frees vgmstream context. */
void libvgmstream_free(libvgmstream_t* vctx);
#if 0
void vgmstream_get_buffer(...);
void vgmstream_format_check(...);
void vgmstream_set_format_whilelist(...);
void vgmstream_set_format_blacklist(...);
const char* vgmstream_describe(...);
const char* vgmstream_get_title(...);
VGMSTREAM_TAGS* vgmstream_get_tagfile(...);
#endif
#include "api_version.h"
#include "api_decode.h"
#include "api_helpers.h"
#include "api_streamfile.h"
#include "api_tags.h"
#endif
#endif

View file

@ -0,0 +1,66 @@
#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 {
LIBVGMSTREAM_STREAMFILE_SEEK_SET = 0,
LIBVGMSTREAM_STREAMFILE_SEEK_CUR = 1,
LIBVGMSTREAM_STREAMFILE_SEEK_END = 2,
//LIBVGMSTREAM_STREAMFILE_SEEK_GET_OFFSET = 3,
//LIBVGMSTREAM_STREAMFILE_SEEK_GET_SIZE = 5,
};
typedef struct libvgmstream_streamfile_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 libvgmstream_streamfile_t* (*open)(void* user_data, const char* filename);
/* free current SF (needed for copied streamfiles) */
void (*close)(struct libvgmstream_streamfile_t* libsf);
} libvgmstream_streamfile_t;
/* helper */
static inline void libvgmstream_streamfile_close(libvgmstream_streamfile_t* libsf) {
if (!libsf || !libsf->close)
return;
libsf->close(libsf);
}
LIBVGMSTREAM_API libvgmstream_streamfile_t* libvgmstream_streamfile_from_filename(const char* filename);
#endif
#endif

View file

@ -0,0 +1,84 @@
#include "api_internal.h"
#if LIBVGMSTREAM_ENABLE
#define INTERNAL_BUF_SAMPLES 1024
LIBVGMSTREAM_API uint32_t libvgmstream_get_version(void) {
return (LIBVGMSTREAM_API_VERSION_MAJOR << 24) | (LIBVGMSTREAM_API_VERSION_MINOR << 16) | (LIBVGMSTREAM_API_VERSION_PATCH << 0);
}
LIBVGMSTREAM_API libvgmstream_t* libvgmstream_init(void) {
libvgmstream_t* lib = NULL;
libvgmstream_priv_t* priv = NULL;
lib = calloc(1, sizeof(libvgmstream_t));
if (!lib) goto fail;
lib->priv = calloc(1, sizeof(libvgmstream_priv_t));
if (!lib->priv) goto fail;
priv = lib->priv;
//TODO only setup on decode? (but may less error prone if always set)
lib->format = &priv->fmt;
lib->decoder = &priv->dec;
priv->cfg.loop_count = 1; //TODO: loop 0 means no loop (improve detection)
return lib;
fail:
libvgmstream_free(lib);
return NULL;
}
LIBVGMSTREAM_API void libvgmstream_free(libvgmstream_t* lib) {
if (!lib)
return;
libvgmstream_priv_t* priv = lib->priv;
if (priv) {
close_vgmstream(priv->vgmstream);
free(priv->buf.data);
}
free(priv);
free(lib);
}
LIBVGMSTREAM_API void libvgmstream_setup(libvgmstream_t* lib, libvgmstream_config_t* cfg) {
if (!lib || !lib->priv)
return;
libvgmstream_priv_t* priv = lib->priv;
if (!cfg) {
memset(&priv->cfg , 0, sizeof(libvgmstream_config_t));
priv->cfg.loop_count = 1; //TODO: loop 0 means no loop (improve detection)
}
else {
priv->cfg = *cfg;
}
//TODO validate, etc
}
void libvgmstream_priv_reset(libvgmstream_priv_t* priv, bool reset_buf) {
//memset(&priv->cfg, 0, sizeof(libvgmstream_config_t)); //config is always valid
memset(&priv->fmt, 0, sizeof(libvgmstream_format_t));
memset(&priv->dec, 0, sizeof(libvgmstream_decoder_t));
//memset(&priv->pos, 0, sizeof(libvgmstream_priv_position_t)); //position info is updated on open
if (reset_buf) {
free(priv->buf.data);
memset(&priv->buf, 0, sizeof(libvgmstream_priv_buf_t));
}
priv->pos.current = 0;
priv->decode_done = false;
}
#endif

View file

@ -0,0 +1,138 @@
#include "api_internal.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);
if (!sf_api)
return;
//TODO: handle internal format_id
sf_api->stream_index = opt->subsong_index;
priv->vgmstream = init_vgmstream_from_STREAMFILE(sf_api);
close_streamfile(sf_api);
}
static void apply_config(libvgmstream_priv_t* priv) {
libvgmstream_config_t* cfg = &priv->cfg;
vgmstream_cfg_t vcfg = {
.disable_config_override = cfg->disable_config_override,
.allow_play_forever = cfg->allow_play_forever,
.play_forever = cfg->play_forever,
.ignore_loop = cfg->ignore_loop,
.force_loop = cfg->force_loop,
.really_force_loop = cfg->really_force_loop,
.ignore_fade = cfg->ignore_fade,
.loop_count = cfg->loop_count,
.fade_time = cfg->fade_time,
.fade_delay = cfg->fade_delay,
};
if (!vcfg.allow_play_forever)
vcfg.play_forever = 0;
vgmstream_apply_config(priv->vgmstream, &vcfg);
}
static void prepare_mixing(libvgmstream_priv_t* priv, libvgmstream_options_t* opt) {
/* enable after config but before outbuf */
if (priv->cfg.auto_downmix_channels) {
vgmstream_mixing_autodownmix(priv->vgmstream, priv->cfg.auto_downmix_channels);
}
else if (opt && opt->stereo_track >= 1) {
vgmstream_mixing_stereo_only(priv->vgmstream, opt->stereo_track - 1);
}
vgmstream_mixing_enable(priv->vgmstream, INTERNAL_BUF_SAMPLES, NULL /*&input_channels*/, NULL /*&output_channels*/);
}
static void update_position(libvgmstream_priv_t* priv) {
libvgmstream_priv_position_t* pos = &priv->pos;
VGMSTREAM* v = priv->vgmstream;
pos->play_forever = vgmstream_get_play_forever(v);
pos->play_samples = vgmstream_get_samples(v);
pos->current = 0;
}
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;
fmt->channels = v->channels;
fmt->input_channels = 0;
vgmstream_mixing_enable(v, 0, &fmt->input_channels, &fmt->channels);
fmt->channel_layout = v->channel_layout;
fmt->stream_samples = v->num_samples;
fmt->loop_start = v->loop_start_sample;
fmt->loop_end = v->loop_end_sample;
fmt->loop_flag = v->loop_flag;
fmt->play_forever = priv->pos.play_forever;
fmt->play_samples = priv->pos.play_samples;
fmt->format_id = v->format_id;
fmt->stream_bitrate = get_vgmstream_average_bitrate(v);
get_vgmstream_coding_description(v, fmt->codec_name, sizeof(fmt->codec_name));
get_vgmstream_layout_description(v, fmt->layout_name, sizeof(fmt->layout_name));
get_vgmstream_meta_description(v, fmt->meta_name, sizeof(fmt->meta_name));
if (v->stream_name[0] != '\0') { //snprintf UB for NULL args
snprintf(fmt->stream_name, sizeof(fmt->stream_name), "%s", v->stream_name);
}
}
LIBVGMSTREAM_API int libvgmstream_open_song(libvgmstream_t* lib, libvgmstream_options_t* opt) {
if (!lib ||!lib->priv)
return LIBVGMSTREAM_ERROR_GENERIC;
if (!opt || !opt->libsf || opt->subsong_index < 0)
return LIBVGMSTREAM_ERROR_GENERIC;
// close loaded song if any + reset
libvgmstream_close_song(lib);
libvgmstream_priv_t* priv = lib->priv;
load_vgmstream(priv, opt);
if (!priv->vgmstream)
return LIBVGMSTREAM_ERROR_GENERIC;
apply_config(priv);
prepare_mixing(priv, opt);
update_position(priv);
update_format_info(priv);
return LIBVGMSTREAM_OK;
}
LIBVGMSTREAM_API void libvgmstream_close_song(libvgmstream_t* lib) {
if (!lib || !lib->priv)
return;
libvgmstream_priv_t* priv = lib->priv;
close_vgmstream(priv->vgmstream);
priv->vgmstream = NULL;
libvgmstream_priv_reset(priv, true);
}
#endif

View file

@ -0,0 +1,142 @@
#include "api_internal.h"
#if LIBVGMSTREAM_ENABLE
#define INTERNAL_BUF_SAMPLES 1024
//TODO: use internal
static void 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;
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;
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.initialized = true;
}
static void update_buf(libvgmstream_priv_t* priv, int samples_done) {
priv->buf.samples = samples_done;
priv->buf.bytes = samples_done * priv->buf.sample_size * priv->buf.channels;
//priv->buf.consumed = 0; //external
if (!priv->pos.play_forever) {
priv->decode_done = (priv->pos.current >= priv->pos.play_samples);
priv->pos.current += samples_done;
}
}
// update decoder info based on last render, though at the moment it's all fixed
static void update_decoder_info(libvgmstream_priv_t* priv, int samples_done) {
// output copy
priv->dec.buf = priv->buf.data;
priv->dec.buf_bytes = priv->buf.bytes;
priv->dec.buf_samples = priv->buf.samples;
priv->dec.done = priv->decode_done;
}
LIBVGMSTREAM_API int libvgmstream_play(libvgmstream_t* lib) {
if (!lib || !lib->priv)
return LIBVGMSTREAM_ERROR_GENERIC;
libvgmstream_priv_t* priv = lib->priv;
if (priv->decode_done)
return LIBVGMSTREAM_ERROR_GENERIC;
reset_buf(priv);
if (!priv->buf.data)
return LIBVGMSTREAM_ERROR_GENERIC;
int to_get = priv->buf.max_samples;
if (!priv->pos.play_forever && to_get + priv->pos.current > priv->pos.play_samples)
to_get = priv->pos.play_samples - priv->pos.current;
int decoded = render_vgmstream(priv->buf.data, to_get, priv->vgmstream);
update_buf(priv, decoded);
update_decoder_info(priv, decoded);
return LIBVGMSTREAM_OK;
}
/* _play decodes a single frame, while this copies partially that frame until frame is over */
LIBVGMSTREAM_API int libvgmstream_fill(libvgmstream_t* lib, void* buf, int buf_samples) {
if (!lib || !lib->priv || !buf || !buf_samples)
return LIBVGMSTREAM_ERROR_GENERIC;
libvgmstream_priv_t* priv = lib->priv;
if (priv->decode_done)
return LIBVGMSTREAM_ERROR_GENERIC;
if (priv->buf.consumed >= priv->buf.samples) {
int err = libvgmstream_play(lib);
if (err < 0) return err;
}
int copy_samples = priv->buf.samples;
if (copy_samples > buf_samples)
copy_samples = buf_samples;
int copy_bytes = priv->buf.sample_size * priv->buf.channels * copy_samples;
int skip_bytes = priv->buf.sample_size * priv->buf.channels * priv->buf.consumed;
memcpy(buf, ((uint8_t*)priv->buf.data) + skip_bytes, copy_bytes);
priv->buf.consumed += copy_samples;
return copy_samples;
}
LIBVGMSTREAM_API int64_t libvgmstream_get_play_position(libvgmstream_t* lib) {
if (!lib || !lib->priv)
return LIBVGMSTREAM_ERROR_GENERIC;
libvgmstream_priv_t* priv = lib->priv;
if (!priv->vgmstream)
return LIBVGMSTREAM_ERROR_GENERIC;
return priv->vgmstream->pstate.play_position;
}
LIBVGMSTREAM_API void libvgmstream_seek(libvgmstream_t* lib, int64_t sample) {
if (!lib || !lib->priv)
return;
libvgmstream_priv_t* priv = lib->priv;
if (!priv->vgmstream)
return;
seek_vgmstream(priv->vgmstream, sample);
priv->pos.current = priv->vgmstream->pstate.play_position;
}
LIBVGMSTREAM_API void libvgmstream_reset(libvgmstream_t* lib) {
if (!lib || !lib->priv)
return;
libvgmstream_priv_t* priv = lib->priv;
if (priv->vgmstream) {
reset_vgmstream(priv->vgmstream);
}
libvgmstream_priv_reset(priv, false);
}
#endif

View file

@ -0,0 +1,91 @@
#include "api_internal.h"
#if LIBVGMSTREAM_ENABLE
static int get_internal_log_level(libvgmstream_loglevel_t level) {
switch(level) {
case LIBVGMSTREAM_LOG_LEVEL_ALL: return VGM_LOG_LEVEL_ALL;
case LIBVGMSTREAM_LOG_LEVEL_DEBUG: return VGM_LOG_LEVEL_DEBUG;
case LIBVGMSTREAM_LOG_LEVEL_INFO: return VGM_LOG_LEVEL_INFO;
case LIBVGMSTREAM_LOG_LEVEL_NONE:
default:
return 0;
}
}
LIBVGMSTREAM_API void libvgmstream_set_log(libvgmstream_log_t* cfg) {
if (!cfg)
return;
int ilevel = get_internal_log_level(cfg->level);
if (cfg->stdout_callback) {
//vgmstream_set_log_stdout(ilevel);
vgm_log_set_callback(NULL, ilevel, 1, NULL);
}
else {
//vgmstream_set_log_callback(ilevel, cfg->callback);
vgm_log_set_callback(NULL, ilevel, 0, cfg->callback);
}
}
LIBVGMSTREAM_API const char** libvgmstream_get_extensions(size_t* size) {
return vgmstream_get_formats(size);
}
LIBVGMSTREAM_API const char** libvgmstream_get_common_extensions(size_t* size) {
return vgmstream_get_common_formats(size);
}
LIBVGMSTREAM_API int libvgmstream_format_describe(libvgmstream_t* lib, char* dst, int dst_size) {
if (!lib || !lib->priv)
return LIBVGMSTREAM_ERROR_GENERIC;
libvgmstream_priv_t* priv = lib->priv;
if (!priv->vgmstream)
return LIBVGMSTREAM_ERROR_GENERIC;
describe_vgmstream(priv->vgmstream, dst, dst_size);
return LIBVGMSTREAM_OK; //TODO return truncated chars
}
LIBVGMSTREAM_API bool libvgmstream_is_valid(const char* filename, libvgmstream_valid_t* cfg) {
if (!filename)
return false;
if (!cfg)
return vgmstream_ctx_is_valid(filename, NULL);
vgmstream_ctx_valid_cfg icfg = {
.is_extension = cfg->is_extension,
.skip_standard = cfg->skip_default,
.reject_extensionless = cfg->reject_extensionless,
.accept_unknown = cfg->accept_unknown,
.accept_common = cfg->accept_common
};
return vgmstream_ctx_is_valid(filename, &icfg);
}
LIBVGMSTREAM_API int libvgmstream_get_title(libvgmstream_t* lib, libvgmstream_title_t* cfg, char* buf, int buf_len) {
if (!buf || !buf_len || !cfg)
return LIBVGMSTREAM_ERROR_GENERIC;
buf[0] = '\0';
if (!lib || !lib->priv)
return LIBVGMSTREAM_ERROR_GENERIC;
libvgmstream_priv_t* priv = lib->priv;
vgmstream_title_t icfg = {
.force_title = cfg->force_title,
.subsong_range = cfg->subsong_range,
.remove_extension = cfg->remove_extension,
.remove_archive = cfg->remove_archive,
};
vgmstream_get_title(buf, buf_len, cfg->filename, priv->vgmstream, &icfg);
return LIBVGMSTREAM_OK;
}
#endif

View file

@ -0,0 +1,63 @@
#ifndef _API_INTERNAL_H_
#define _API_INTERNAL_H_
#include "../libvgmstream.h"
#include "../util/log.h"
#include "../vgmstream.h"
#include "plugins.h"
#if LIBVGMSTREAM_ENABLE
#define LIBVGMSTREAM_OK 0
#define LIBVGMSTREAM_ERROR_GENERIC -1
#define LIBVGMSTREAM_ERROR_DONE -2
/* 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.
* (all the bridging around may be a tiiiiny bit slower but in this day and age potatos are pretty powerful) */
typedef struct {
bool initialized;
void* data;
/* config */
int channels;
int max_bytes;
int max_samples;
int sample_size;
/* state */
int samples;
int bytes;
int consumed;
} libvgmstream_priv_buf_t;
typedef struct {
int64_t play_forever;
int64_t play_samples;
int64_t current;
} libvgmstream_priv_position_t;
/* vgmstream context/handle */
typedef struct {
libvgmstream_format_t fmt; // externally exposed
libvgmstream_decoder_t dec; // externally exposed
libvgmstream_config_t cfg; // internal copy
VGMSTREAM* vgmstream;
libvgmstream_priv_buf_t buf;
libvgmstream_priv_position_t pos;
bool decode_done;
} libvgmstream_priv_t;
void libvgmstream_priv_reset(libvgmstream_priv_t* priv, bool reset_buf);
STREAMFILE* open_api_streamfile(libvgmstream_streamfile_t* libsf);
#endif
#endif

View file

@ -0,0 +1,148 @@
#include "api_internal.h"
#if LIBVGMSTREAM_ENABLE
static libvgmstream_streamfile_t* libvgmstream_streamfile_from_streamfile(STREAMFILE* sf);
/* libvgmstream_streamfile_t for external use, as a default implementation calling some internal SF */
typedef struct {
int64_t offset;
int64_t size;
STREAMFILE* inner_sf;
char name[PATH_LIMIT];
} libsf_data_t;
static int libsf_read(void* user_data, uint8_t* dst, int dst_size) {
libsf_data_t* data = user_data;
if (!data || !dst)
return 0;
int bytes = data->inner_sf->read(data->inner_sf, dst, data->offset, dst_size);
data->offset += bytes;
return bytes;
}
static int64_t libsf_seek(void* user_data, int64_t offset, int whence) {
libsf_data_t* data = user_data;
if (!data)
return -1;
switch (whence) {
case LIBVGMSTREAM_STREAMFILE_SEEK_SET: /* absolute */
break;
case LIBVGMSTREAM_STREAMFILE_SEEK_CUR: /* relative to current */
offset += data->offset;
break;
case LIBVGMSTREAM_STREAMFILE_SEEK_END: /* relative to file end (should be negative) */
offset += data->size;
break;
default:
break;
}
/* clamp offset like fseek */
if (offset > data->size)
offset = data->size;
else if (offset < 0)
offset = 0;
/* main seek */
data->offset = offset;
return 0;
}
static int64_t libsf_get_size(void* user_data) {
libsf_data_t* data = user_data;
if (!data)
return 0;
return data->size;
}
static const char* libsf_get_name(void* user_data) {
libsf_data_t* data = user_data;
if (!data)
return NULL;
if (data->name[0] == '\0') {
data->inner_sf->get_name(data->inner_sf, data->name, sizeof(data->name));
}
return data->name;
}
struct libvgmstream_streamfile_t* libsf_open(void* user_data, const char* filename) {
libsf_data_t* data = user_data;
if (!data || !data->inner_sf)
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);
if (!libsf) {
close_streamfile(sf);
return NULL;
}
return libsf;
}
static void libsf_close(struct libvgmstream_streamfile_t* libsf) {
if (!libsf)
return;
libsf_data_t* data = libsf->user_data;
if (data && data->inner_sf) {
data->inner_sf->close(data->inner_sf);
}
free(data);
free(libsf);
}
static libvgmstream_streamfile_t* libvgmstream_streamfile_from_streamfile(STREAMFILE* sf) {
if (!sf)
return NULL;
libvgmstream_streamfile_t* libsf = NULL;
libsf_data_t* data = NULL;
libsf = calloc(1, sizeof(libvgmstream_streamfile_t));
if (!libsf) goto fail;
libsf->read = libsf_read;
libsf->seek = libsf_seek;
libsf->get_size = libsf_get_size;
libsf->get_name = libsf_get_name;
libsf->open = libsf_open;
libsf->close = libsf_close;
libsf->user_data = calloc(1, sizeof(libsf_data_t));
if (!libsf->user_data) goto fail;
data = libsf->user_data;
data->inner_sf = sf;
data->size = get_streamfile_size(sf);
return libsf;
fail:
libsf_close(libsf);
return NULL;
}
LIBVGMSTREAM_API libvgmstream_streamfile_t* libvgmstream_streamfile_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);
if (!libsf) {
close_streamfile(sf);
return NULL;
}
return libsf;
}
#endif

View file

@ -0,0 +1,69 @@
#include "api_internal.h"
#if LIBVGMSTREAM_ENABLE
typedef struct {
VGMSTREAM_TAGS* vtags;
libvgmstream_streamfile_t* libsf;
STREAMFILE* sf_tags;
} libvgmstream_tags_priv_t;
LIBVGMSTREAM_API libvgmstream_tags_t* libvgmstream_tags_init(libvgmstream_streamfile_t* libsf) {
if (!libsf)
return NULL;
libvgmstream_tags_t* tags = NULL;
libvgmstream_tags_priv_t* priv = NULL;
tags = calloc(1, sizeof(libvgmstream_tags_t));
if (!tags) goto fail;
tags->priv = calloc(1, sizeof(libvgmstream_tags_priv_t));
if (!tags->priv) goto fail;
priv = tags->priv;
priv->vtags = vgmstream_tags_init(&tags->key, &tags->val);
if (!priv->vtags) goto fail;
priv->sf_tags = open_api_streamfile(libsf);
if (!priv->sf_tags) goto fail;
return tags;
fail:
libvgmstream_tags_free(tags);
return NULL;
}
LIBVGMSTREAM_API void libvgmstream_tags_find(libvgmstream_tags_t* tags, const char* target_filename) {
if (!tags || !tags->priv || !target_filename)
return;
//TODO: handle NULL filename?
libvgmstream_tags_priv_t* priv = tags->priv;
vgmstream_tags_reset(priv->vtags, target_filename);
}
LIBVGMSTREAM_API bool libvgmstream_tags_next_tag(libvgmstream_tags_t* tags) {
if (!tags)
return false;
libvgmstream_tags_priv_t* priv = tags->priv;
return vgmstream_tags_next_tag(priv->vtags, priv->sf_tags);
}
LIBVGMSTREAM_API void libvgmstream_tags_free(libvgmstream_tags_t* tags) {
if (!tags)
return;
libvgmstream_tags_priv_t* priv = tags->priv;
if (priv) {
vgmstream_tags_close(priv->vtags);
close_streamfile(priv->sf_tags);
}
free(tags->priv);
free(tags);
}
#endif

View file

@ -0,0 +1,107 @@
#include "../vgmstream.h"
#include "../util/log.h"
#include "plugins.h"
#include "mixing.h"
static void copy_time(bool* dst_flag, int32_t* dst_time, double* dst_time_s, bool* src_flag, int32_t* src_time, double* src_time_s) {
if (!*src_flag)
return;
*dst_flag = 1;
*dst_time = *src_time;
*dst_time_s = *src_time_s;
}
//todo reuse in txtp?
static void load_default_config(play_config_t* def, play_config_t* tcfg) {
/* loop limit: txtp #L > txtp #l > player #L > player #l */
if (tcfg->play_forever) {
def->play_forever = 1;
def->ignore_loop = 0;
}
if (tcfg->loop_count_set) {
def->loop_count = tcfg->loop_count;
def->loop_count_set = 1;
def->ignore_loop = 0;
if (!tcfg->play_forever)
def->play_forever = 0;
}
/* fade priority: #F > #f, #d */
if (tcfg->ignore_fade) {
def->ignore_fade = 1;
}
if (tcfg->fade_delay_set) {
def->fade_delay = tcfg->fade_delay;
def->fade_delay_set = 1;
}
if (tcfg->fade_time_set) {
def->fade_time = tcfg->fade_time;
def->fade_time_set = 1;
}
/* loop priority: #i > #e > #E (respect player's ignore too) */
if (tcfg->really_force_loop) {
//def->ignore_loop = 0;
def->force_loop = 0;
def->really_force_loop = 1;
}
if (tcfg->force_loop) {
//def->ignore_loop = 0;
def->force_loop = 1;
def->really_force_loop = 0;
}
if (tcfg->ignore_loop) {
def->ignore_loop = 1;
def->force_loop = 0;
def->really_force_loop = 0;
}
copy_time(&def->pad_begin_set, &def->pad_begin, &def->pad_begin_s, &tcfg->pad_begin_set, &tcfg->pad_begin, &tcfg->pad_begin_s);
copy_time(&def->pad_end_set, &def->pad_end, &def->pad_end_s, &tcfg->pad_end_set, &tcfg->pad_end, &tcfg->pad_end_s);
copy_time(&def->trim_begin_set, &def->trim_begin, &def->trim_begin_s, &tcfg->trim_begin_set, &tcfg->trim_begin, &tcfg->trim_begin_s);
copy_time(&def->trim_end_set, &def->trim_end, &def->trim_end_s, &tcfg->trim_end_set, &tcfg->trim_end, &tcfg->trim_end_s);
copy_time(&def->body_time_set, &def->body_time, &def->body_time_s, &tcfg->body_time_set, &tcfg->body_time, &tcfg->body_time_s);
def->is_mini_txtp = tcfg->is_mini_txtp;
def->is_txtp = tcfg->is_txtp;
}
static void load_player_config(play_config_t* def, vgmstream_cfg_t* vcfg) {
def->play_forever = vcfg->play_forever;
def->ignore_loop = vcfg->ignore_loop;
def->force_loop = vcfg->force_loop;
def->really_force_loop = vcfg->really_force_loop;
def->ignore_fade = vcfg->ignore_fade;
def->loop_count = vcfg->loop_count;
def->loop_count_set = 1;
def->fade_delay = vcfg->fade_delay;
def->fade_delay_set = 1;
def->fade_time = vcfg->fade_time;
def->fade_time_set = 1;
}
void vgmstream_apply_config(VGMSTREAM* vgmstream, vgmstream_cfg_t* vcfg) {
play_config_t defs = {0};
play_config_t* def = &defs; /* for convenience... */
play_config_t* tcfg = &vgmstream->config;
load_player_config(def, vcfg);
def->config_set = 1;
if (!vcfg->disable_config_override)
load_default_config(def, tcfg);
if (!vcfg->allow_play_forever)
def->play_forever = 0;
/* copy final config back */
*tcfg = *def;
vgmstream->config_enabled = def->config_set;
setup_state_vgmstream(vgmstream);
}

View file

@ -9,6 +9,8 @@
void decode_free(VGMSTREAM* vgmstream) {
if (!vgmstream->codec_data)
return;
#ifdef VGM_USE_VORBIS
if (vgmstream->coding_type == coding_OGG_VORBIS) {
@ -49,6 +51,10 @@ void decode_free(VGMSTREAM* vgmstream) {
free_imuse(vgmstream->codec_data);
}
if (vgmstream->coding_type == coding_ONGAKUKAN_ADPCM) {
free_ongakukan_adp(vgmstream->codec_data);
}
if (vgmstream->coding_type == coding_COMPRESSWAVE) {
free_compresswave(vgmstream->codec_data);
}
@ -120,6 +126,9 @@ void decode_free(VGMSTREAM* vgmstream) {
void decode_seek(VGMSTREAM* vgmstream) {
if (!vgmstream->codec_data)
return;
if (vgmstream->coding_type == coding_CIRCUS_VQ) {
seek_circus_vq(vgmstream->codec_data, vgmstream->loop_current_sample);
}
@ -149,6 +158,10 @@ void decode_seek(VGMSTREAM* vgmstream) {
seek_imuse(vgmstream->codec_data, vgmstream->loop_current_sample);
}
if (vgmstream->coding_type == coding_ONGAKUKAN_ADPCM) {
seek_ongakukan_adp(vgmstream->codec_data, vgmstream->loop_current_sample);
}
if (vgmstream->coding_type == coding_COMPRESSWAVE) {
seek_compresswave(vgmstream->codec_data, vgmstream->loop_current_sample);
}
@ -175,7 +188,7 @@ void decode_seek(VGMSTREAM* vgmstream) {
#if defined(VGM_USE_MP4V2) && defined(VGM_USE_FDKAAC)
if (vgmstream->coding_type == coding_MP4_AAC) {
seek_mp4_aac(vgmstream, vgmstream->loop_sample);
seek_mp4_aac(vgmstream, vgmstream->loop_current_sample);
}
#endif
@ -214,6 +227,8 @@ void decode_seek(VGMSTREAM* vgmstream) {
void decode_reset(VGMSTREAM* vgmstream) {
if (!vgmstream->codec_data)
return;
#ifdef VGM_USE_VORBIS
if (vgmstream->coding_type == coding_OGG_VORBIS) {
@ -254,6 +269,10 @@ void decode_reset(VGMSTREAM* vgmstream) {
reset_imuse(vgmstream->codec_data);
}
if (vgmstream->coding_type == coding_ONGAKUKAN_ADPCM) {
reset_ongakukan_adp(vgmstream->codec_data);
}
if (vgmstream->coding_type == coding_COMPRESSWAVE) {
reset_compresswave(vgmstream->codec_data);
}
@ -387,6 +406,7 @@ int decode_get_samples_per_frame(VGMSTREAM* vgmstream) {
case coding_ACM:
case coding_DERF:
case coding_WADY:
case coding_DPCM_KCEJ:
case coding_NWA:
case coding_SASSC:
case coding_CIRCUS_ADPCM:
@ -415,10 +435,11 @@ int decode_get_samples_per_frame(VGMSTREAM* vgmstream) {
return 2;
case coding_XBOX_IMA:
case coding_XBOX_IMA_mch:
case coding_XBOX_IMA_int:
case coding_XBOX_IMA_mono:
case coding_FSB_IMA:
case coding_WWISE_IMA:
case coding_CD_IMA:
case coding_CD_IMA: /* (0x24 - 0x04) * 2 */
case coding_CRANKCASE_IMA: /* (0x23 - 0x3) * 2 */
return 64;
case coding_APPLE_IMA4:
return 64;
@ -465,7 +486,7 @@ int decode_get_samples_per_frame(VGMSTREAM* vgmstream) {
case coding_MSADPCM:
return (vgmstream->frame_size - 0x07*vgmstream->channels)*2 / vgmstream->channels + 2;
case coding_MSADPCM_int:
case coding_MSADPCM_mono:
case coding_MSADPCM_ck:
return (vgmstream->frame_size - 0x07)*2 + 2;
case coding_WS: /* only works if output sample size is 8 bit, which always is for WS ADPCM */
@ -481,7 +502,7 @@ int decode_get_samples_per_frame(VGMSTREAM* vgmstream) {
return (0x40-0x04) * 2;
case coding_NDS_PROCYON:
return 30;
case coding_L5_555:
case coding_LEVEL5:
return 32;
case coding_LSF:
return 54;
@ -520,6 +541,8 @@ int decode_get_samples_per_frame(VGMSTREAM* vgmstream) {
return 0; /* varies per mode */
case coding_IMUSE:
return 0; /* varies per frame */
case coding_ONGAKUKAN_ADPCM:
return 0; /* actually 1. */
case coding_COMPRESSWAVE:
return 0; /* multiple of 2 */
case coding_EA_MT:
@ -537,7 +560,7 @@ int decode_get_samples_per_frame(VGMSTREAM* vgmstream) {
return 0; /* ~100 (range), ~16 (DCT) */
#if defined(VGM_USE_MP4V2) && defined(VGM_USE_FDKAAC)
case coding_MP4_AAC:
return ((mp4_aac_codec_data*)vgmstream->codec_data)->samples_per_frame;
return mp4_get_samples_per_frame(vgmstream->codec_data);
#endif
#ifdef VGM_USE_ATRAC9
case coding_ATRAC9:
@ -607,6 +630,7 @@ int decode_get_frame_size(VGMSTREAM* vgmstream) {
case coding_CBD2_int:
case coding_DERF:
case coding_WADY:
case coding_DPCM_KCEJ:
case coding_NWA:
case coding_SASSC:
case coding_CIRCUS_ADPCM:
@ -650,10 +674,12 @@ int decode_get_frame_size(VGMSTREAM* vgmstream) {
case coding_XBOX_IMA:
//todo should be 0x48 when stereo, but blocked/interleave layout don't understand stereo codecs
return 0x24; //vgmstream->channels==1 ? 0x24 : 0x48;
case coding_XBOX_IMA_int:
case coding_XBOX_IMA_mono:
case coding_WWISE_IMA:
case coding_CD_IMA:
return 0x24;
case coding_CRANKCASE_IMA:
return 0x23;
case coding_XBOX_IMA_mch:
case coding_FSB_IMA:
return 0x24 * vgmstream->channels;
@ -688,7 +714,7 @@ int decode_get_frame_size(VGMSTREAM* vgmstream) {
return 0x4c*vgmstream->channels;
case coding_MSADPCM:
case coding_MSADPCM_int:
case coding_MSADPCM_mono:
case coding_MSADPCM_ck:
return vgmstream->frame_size;
case coding_WS:
@ -703,7 +729,7 @@ int decode_get_frame_size(VGMSTREAM* vgmstream) {
return 0x40;
case coding_NDS_PROCYON:
return 0x10;
case coding_L5_555:
case coding_LEVEL5:
return 0x12;
case coding_LSF:
return 0x1C;
@ -768,6 +794,36 @@ int decode_get_shortframe_size(VGMSTREAM* vgmstream) {
}
}
/* ugly kludge due to vgmstream's finicky internals, to be improved some day:
* - some codecs have frame sizes AND may also have interleave
* - meaning, ch0 could read 0x100 (frame_size) N times until 0x1000 (interleave)
* then skip 0x1000 per other channels and keep reading 0x100
* (basically: ch0=0x0000..0x1000, ch1=0x1000..0x2000, ch0=0x2000..0x3000, etc)
* - interleave layout assumes by default codecs DON'T update offsets and only interleave does
* - interleave calculates how many frames/samples will read before moving offsets,
* then once 1 channel is done skips original channel data + other channel's data
* - decoders need to calculate current frame offset on every frame since
* offsets only move when interleave moves offsets (ugly)
* - other codecs move offsets internally instead (also ugly)
* - but interleave doesn't know this and will skip too much data
*
* To handle the last case, return a flag here that interleave layout can use to
* separate between both cases when the interleave data is done
* - codec doesn't advance offsets: will skip interleave for all channels including current
* - ex. 2ch, 0x100, 0x1000: after reading 0x100*10 frames offset is still 0x0000 > skips 0x1000*2 (ch0+ch1)
* - codec does advance offsets: will skip interleave for all channels except current
* - ex. 2ch, 0x100, 0x1000: after reading 0x100*10 frames offset is at 0x1000 > skips 0x1000*1 (ch1)
*
* Ideally frame reading + skipping would be moved to some kind of consumer functions
* separate from frame decoding which would simplify all this but meanwhile...
*
* Instead of this flag, codecs could be converted to avoid moving offsets (like most codecs) but it's
* getting hard to understand the root issue so have some wall of text as a reminder.
*/
bool decode_uses_internal_offset_updates(VGMSTREAM* vgmstream) {
return vgmstream->coding_type == coding_MS_IMA || vgmstream->coding_type == coding_MS_IMA_mono;
}
/* Decode samples into the buffer. Assume that we have written samples_written into the
* buffer already, and we have samples_to_do consecutive samples ahead of us (won't call
* more than one frame if configured above to do so).
@ -929,7 +985,7 @@ void decode_vgmstream(VGMSTREAM* vgmstream, int samples_written, int samples_to_
}
break;
case coding_XBOX_IMA:
case coding_XBOX_IMA_int: {
case coding_XBOX_IMA_mono: {
int is_stereo = (vgmstream->channels > 1 && vgmstream->coding_type == coding_XBOX_IMA);
for (ch = 0; ch < vgmstream->channels; ch++) {
decode_xbox_ima(&vgmstream->ch[ch], buffer+ch,
@ -1134,6 +1190,12 @@ void decode_vgmstream(VGMSTREAM* vgmstream, int samples_written, int samples_to_
vgmstream->channels, vgmstream->samples_into_block, samples_to_do);
}
break;
case coding_DPCM_KCEJ:
for (ch = 0; ch < vgmstream->channels; ch++) {
decode_dpcm_kcej(&vgmstream->ch[ch], buffer+ch,
vgmstream->channels, vgmstream->samples_into_block, samples_to_do);
}
break;
case coding_CIRCUS_ADPCM:
for (ch = 0; ch < vgmstream->channels; ch++) {
decode_circus_adpcm(&vgmstream->ch[ch], buffer+ch,
@ -1264,6 +1326,12 @@ void decode_vgmstream(VGMSTREAM* vgmstream, int samples_written, int samples_to_
vgmstream->channels, vgmstream->samples_into_block, samples_to_do);
}
break;
case coding_CRANKCASE_IMA:
for (ch = 0; ch < vgmstream->channels; ch++) {
decode_crankcase_ima(&vgmstream->ch[ch], buffer+ch,
vgmstream->channels, vgmstream->samples_into_block, samples_to_do);
}
break;
case coding_WS:
for (ch = 0; ch < vgmstream->channels; ch++) {
@ -1317,8 +1385,8 @@ void decode_vgmstream(VGMSTREAM* vgmstream, int samples_written, int samples_to_
decode_nwa(vgmstream->codec_data, buffer, samples_to_do);
break;
case coding_MSADPCM:
case coding_MSADPCM_int:
if (vgmstream->channels == 1 || vgmstream->coding_type == coding_MSADPCM_int) {
case coding_MSADPCM_mono:
if (vgmstream->channels == 1 || vgmstream->coding_type == coding_MSADPCM_mono) {
for (ch = 0; ch < vgmstream->channels; ch++) {
decode_msadpcm_mono(vgmstream,buffer+ch,
vgmstream->channels,vgmstream->samples_into_block, samples_to_do, ch,
@ -1379,7 +1447,7 @@ void decode_vgmstream(VGMSTREAM* vgmstream, int samples_written, int samples_to_
vgmstream->channels, vgmstream->samples_into_block, samples_to_do);
}
break;
case coding_L5_555:
case coding_LEVEL5:
for (ch = 0; ch < vgmstream->channels; ch++) {
decode_l5_555(&vgmstream->ch[ch], buffer+ch,
vgmstream->channels, vgmstream->samples_into_block, samples_to_do);
@ -1482,6 +1550,10 @@ void decode_vgmstream(VGMSTREAM* vgmstream, int samples_written, int samples_to_
decode_imuse(vgmstream, buffer, samples_to_do);
break;
case coding_ONGAKUKAN_ADPCM:
decode_ongakukan_adp(vgmstream, buffer, samples_to_do);
break;
case coding_COMPRESSWAVE:
decode_compresswave(vgmstream->codec_data, buffer, samples_to_do);
break;
@ -1607,7 +1679,7 @@ int decode_do_loop(VGMSTREAM* vgmstream) {
vgmstream->loop_next_block_offset = vgmstream->next_block_offset;
//vgmstream->lstate = vgmstream->pstate; /* play state is applied over loops */
vgmstream->hit_loop = 1; /* info that loop is now ready to use */
vgmstream->hit_loop = true; /* info that loop is now ready to use */
}
return 0; /* not looped */

View file

@ -29,4 +29,6 @@ int decode_get_samples_per_shortframe(VGMSTREAM* vgmstream);
int decode_get_shortframe_size(VGMSTREAM* vgmstream);
bool decode_uses_internal_offset_updates(VGMSTREAM* vgmstream);
#endif

View file

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

View file

@ -0,0 +1,118 @@
#include "../vgmstream.h"
#include "../util/channel_mappings.h"
#include "mixing.h"
#include "mixer_priv.h"
#include "mixer.h"
#include "sbuf.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.
*/
mixer_t* mixer_init(int channels) {
mixer_t* mixer = calloc(1, sizeof(mixer_t));
if (!mixer) goto fail;
mixer->chain_size = VGMSTREAM_MAX_MIXING; /* fixed array for now */
mixer->mixing_channels = channels;
mixer->output_channels = channels;
mixer->input_channels = channels;
return mixer;
fail:
mixer_free(mixer);
return NULL;
}
void mixer_free(mixer_t* mixer) {
if (!mixer) return;
free(mixer->mixbuf);
free(mixer);
}
void mixer_update_channel(mixer_t* mixer) {
if (!mixer) return;
/* lame hack for dual stereo, but dual stereo is pretty hack-ish to begin with */
mixer->mixing_channels++;
mixer->output_channels++;
}
bool mixer_is_active(mixer_t* mixer) {
/* no support or not need to apply */
if (!mixer || !mixer->active || mixer->chain_count == 0)
return false;
return true;
}
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;
/* 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))
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);
/* 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)
*/
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
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);
default:
break;
}
}
/* downgrade mix to original output */
sbuf_copy_f32_to_s16(outbuf, mixer->mixbuf, sample_count, mixer->output_channels);
}

View file

@ -0,0 +1,16 @@
#ifndef _MIXER_H_
#define _MIXER_H_
#include "../streamtypes.h"
typedef struct mixer_t mixer_t;
/* internal mixing pre-setup for vgmstream (doesn't imply usage).
* If init somehow fails next calls are ignored. */
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);
bool mixer_is_active(mixer_t* mixer);
#endif

View file

@ -0,0 +1,142 @@
#include "mixer_priv.h"
// TO-DO: some ops can be done with original PCM sbuf to avoid copying to the float sbuf
// when there are no actual float ops (ex. 'swap', if no ' volume' )
// Performance gain is probably fairly small, though.
void mixer_op_swap(mixer_t* mixer, int32_t sample_count, mix_op_t* op) {
float* sbuf = mixer->mixbuf;
for (int s = 0; s < sample_count; s++) {
float temp_f = sbuf[op->ch_dst];
sbuf[op->ch_dst] = sbuf[op->ch_src];
sbuf[op->ch_src] = temp_f;
sbuf += mixer->current_channels;
}
}
void mixer_op_add(mixer_t* mixer, int32_t sample_count, mix_op_t* op) {
float* sbuf = mixer->mixbuf;
/* could optimize when vol == 1 to avoid one multiplication but whatevs (not common) */
for (int s = 0; s < sample_count; s++) {
sbuf[op->ch_dst] = sbuf[op->ch_dst] + sbuf[op->ch_src] * op->vol;
sbuf += mixer->current_channels;
}
}
void mixer_op_volume(mixer_t* mixer, int32_t sample_count, mix_op_t* op) {
float* sbuf = mixer->mixbuf;
if (op->ch_dst < 0) {
/* "all channels", most common case */
for (int s = 0; s < sample_count * mixer->current_channels; s++) {
sbuf[s] = sbuf[s] * op->vol;
}
}
else {
for (int s = 0; s < sample_count; s++) {
sbuf[op->ch_dst] = sbuf[op->ch_dst] * op->vol;
sbuf += mixer->current_channels;
}
}
}
void mixer_op_limit(mixer_t* mixer, int32_t sample_count, mix_op_t* op) {
float* sbuf = mixer->mixbuf;
const float limiter_max = 32767.0f;
const float limiter_min = -32768.0f;
const float temp_max = limiter_max * op->vol;
const float temp_min = limiter_min * op->vol;
/* could optimize when vol == 1 to avoid one multiplication but whatevs (not common) */
for (int s = 0; s < sample_count; s++) {
if (op->ch_dst < 0) {
for (int ch = 0; ch < mixer->current_channels; ch++) {
if (sbuf[ch] > temp_max)
sbuf[ch] = temp_max;
else if (sbuf[ch] < temp_min)
sbuf[ch] = temp_min;
}
}
else {
if (sbuf[op->ch_dst] > temp_max)
sbuf[op->ch_dst] = temp_max;
else if (sbuf[op->ch_dst] < temp_min)
sbuf[op->ch_dst] = temp_min;
}
sbuf += mixer->current_channels;
}
}
void mixer_op_upmix(mixer_t* mixer, int32_t sample_count, mix_op_t* op) {
int max_channels = mixer->current_channels;
mixer->current_channels += 1;
float* sbuf_tmp = mixer->mixbuf + sample_count * mixer->current_channels;
float* sbuf = mixer->mixbuf + sample_count * max_channels;
/* copy 'backwards' as otherwise would overwrite samples before moving them forward */
for (int s = 0; s < sample_count; s++) {
sbuf_tmp -= mixer->current_channels;
sbuf -= max_channels;
int sbuf_ch = max_channels - 1;
for (int ch = mixer->current_channels - 1; ch >= 0; ch--) {
if (ch == op->ch_dst) {
sbuf_tmp[ch] = 0; /* inserted as silent */
}
else {
sbuf_tmp[ch] = sbuf[sbuf_ch]; /* 'pull' channels backward */
sbuf_ch--;
}
}
}
}
void mixer_op_downmix(mixer_t* mixer, int32_t sample_count, mix_op_t* op) {
int max_channels = mixer->current_channels;
mixer->current_channels -= 1;
float* sbuf = mixer->mixbuf;
float* sbuf_tmp = sbuf;
for (int s = 0; s < sample_count; s++) {
for (int ch = 0; ch < op->ch_dst; ch++) {
sbuf_tmp[ch] = sbuf[ch]; /* copy untouched channels */
}
for (int ch = op->ch_dst; ch < max_channels; ch++) {
sbuf_tmp[ch] = sbuf[ch + 1]; /* 'pull' dropped channels back */
}
sbuf_tmp += mixer->current_channels;
sbuf += max_channels;
}
}
void mixer_op_killmix(mixer_t* mixer, int32_t sample_count, mix_op_t* op) {
int max_channels = mixer->current_channels;
mixer->current_channels = op->ch_dst; /* clamp channels */
float* sbuf = mixer->mixbuf;
float* sbuf_tmp = sbuf;
for (int s = 0; s < sample_count; s++) {
for (int ch = 0; ch < mixer->current_channels; ch++) {
sbuf_tmp[ch] = sbuf[ch];
}
sbuf_tmp += mixer->current_channels;
sbuf += max_channels;
}
}

View file

@ -1,65 +1,9 @@
#ifndef _MIXING_FADE_H_
#define _MIXING_FADE_H_
#include "mixing_priv.h"
#include <math.h>
#include "mixer_priv.h"
#include <limits.h>
#include <math.h>
#define MIXING_PI 3.14159265358979323846f
static inline int is_fade_active(mixing_data *data, int32_t current_start, int32_t current_end) {
int i;
for (i = 0; i < data->mixing_count; i++) {
mix_command_data *mix = &data->mixing_chain[i];
int32_t fade_start, fade_end;
float vol_start = mix->vol_start;
if (mix->command != MIX_FADE)
continue;
/* check is current range falls within a fade
* (assuming fades were already optimized on add) */
if (mix->time_pre < 0 && vol_start == 1.0) {
fade_start = mix->time_start; /* ignore unused */
}
else {
fade_start = mix->time_pre < 0 ? 0 : mix->time_pre;
}
fade_end = mix->time_post < 0 ? INT_MAX : mix->time_post;
//;VGM_LOG("MIX: fade test, tp=%i, te=%i, cs=%i, ce=%i\n", mix->time_pre, mix->time_post, current_start, current_end);
if (current_start < fade_end && current_end > fade_start) {
//;VGM_LOG("MIX: fade active, cs=%i < fe=%i and ce=%i > fs=%i\n", current_start, fade_end, current_end, fade_start);
return 1;
}
}
return 0;
}
static inline int32_t get_current_pos(VGMSTREAM* vgmstream, int32_t sample_count) {
int32_t current_pos;
if (vgmstream->config_enabled) {
return vgmstream->pstate.play_position;
}
if (vgmstream->loop_flag && vgmstream->loop_count > 0) {
int loop_pre = vgmstream->loop_start_sample; /* samples before looping */
int loop_into = (vgmstream->current_sample - vgmstream->loop_start_sample); /* samples after loop */
int loop_samples = (vgmstream->loop_end_sample - vgmstream->loop_start_sample); /* looped section */
current_pos = loop_pre + (loop_samples * vgmstream->loop_count) + loop_into - sample_count;
}
else {
current_pos = (vgmstream->current_sample - sample_count);
}
return current_pos;
}
static inline float get_fade_gain_curve(char shape, float index) {
float gain;
@ -68,7 +12,7 @@ static inline float get_fade_gain_curve(char shape, float index) {
return index;
}
//todo optimizations: interleave calcs, maybe use cosf, powf, etc? (with extra defines)
//TODO optimizations: interleave calcs
/* (curve math mostly from SoX/FFmpeg) */
switch(shape) {
@ -76,25 +20,27 @@ static inline float get_fade_gain_curve(char shape, float index) {
* (alt calculations with 'exp' from FFmpeg use (factor)*ln(0.1) = -NN.N... */
case 'E': /* exponential (for fade-outs, closer to natural decay of sound) */
//gain = pow(0.1f, (1.0f - index) * 2.5f);
gain = exp(-5.75646273248511f * (1.0f - index));
//gain = powf(0.1f, (1.0f - index) * 2.5f);
gain = expf(-5.75646273248511f * (1.0f - index));
break;
case 'L': /* logarithmic (inverse of the above, maybe for crossfades) */
//gain = 1 - pow(0.1f, (index) * 2.5f);
gain = 1 - exp(-5.75646273248511f * (index));
//gain = 1 - powf(0.1f, (index) * 2.5f);
gain = 1 - expf(-5.75646273248511f * (index));
break;
case 'H': /* raised sine wave or cosine wave (for more musical crossfades) */
gain = (1.0f - cos(index * MIXING_PI)) / 2.0f;
gain = (1.0f - cosf(index * MIXING_PI)) / 2.0f;
break;
case 'Q': /* quarter of sine wave (for musical fades) */
gain = sin(index * MIXING_PI / 2.0f);
gain = sinf(index * MIXING_PI / 2.0f);
break;
case 'p': /* parabola (maybe for crossfades) */
gain = 1.0f - sqrt(1.0f - index);
gain = 1.0f - sqrtf(1.0f - index);
break;
case 'P': /* inverted parabola (maybe for fades) */
gain = (1.0f - (1.0f - index) * (1.0f - index));
break;
@ -108,28 +54,28 @@ static inline float get_fade_gain_curve(char shape, float index) {
return gain;
}
static int get_fade_gain(mix_command_data *mix, float *out_cur_vol, int32_t current_subpos) {
static bool get_fade_gain(mix_op_t* op, float* out_cur_vol, int32_t current_subpos) {
float cur_vol = 0.0f;
if ((current_subpos >= mix->time_pre || mix->time_pre < 0) && current_subpos < mix->time_start) {
cur_vol = mix->vol_start; /* before */
if ((current_subpos >= op->time_pre || op->time_pre < 0) && current_subpos < op->time_start) {
cur_vol = op->vol_start; /* before */
}
else if (current_subpos >= mix->time_end && (current_subpos < mix->time_post || mix->time_post < 0)) {
cur_vol = mix->vol_end; /* after */
else if (current_subpos >= op->time_end && (current_subpos < op->time_post || op->time_post < 0)) {
cur_vol = op->vol_end; /* after */
}
else if (current_subpos >= mix->time_start && current_subpos < mix->time_end) {
else if (current_subpos >= op->time_start && current_subpos < op->time_end) {
/* in between */
float range_vol, range_dur, range_idx, index, gain;
if (mix->vol_start < mix->vol_end) { /* fade in */
range_vol = mix->vol_end - mix->vol_start;
range_dur = mix->time_end - mix->time_start;
range_idx = current_subpos - mix->time_start;
if (op->vol_start < op->vol_end) { /* fade in */
range_vol = op->vol_end - op->vol_start;
range_dur = op->time_end - op->time_start;
range_idx = current_subpos - op->time_start;
index = range_idx / range_dur;
} else { /* fade out */
range_vol = mix->vol_end - mix->vol_start;
range_dur = mix->time_end - mix->time_start;
range_idx = mix->time_end - current_subpos;
range_vol = op->vol_end - op->vol_start;
range_dur = op->time_end - op->time_start;
range_idx = op->time_end - current_subpos;
index = range_idx / range_dur;
}
@ -149,23 +95,79 @@ static int get_fade_gain(mix_command_data *mix, float *out_cur_vol, int32_t curr
* curves are complementary (exponential fade-in ~= logarithmic fade-out); the following
* are described taking fade-in = normal.
*/
gain = get_fade_gain_curve(mix->shape, index);
gain = get_fade_gain_curve(op->shape, index);
if (mix->vol_start < mix->vol_end) { /* fade in */
cur_vol = mix->vol_start + range_vol * gain;
if (op->vol_start < op->vol_end) { /* fade in */
cur_vol = op->vol_start + range_vol * gain;
} else { /* fade out */
cur_vol = mix->vol_end - range_vol * gain; //mix->vol_start - range_vol * (1 - gain);
cur_vol = op->vol_end - range_vol * gain; //mix->vol_start - range_vol * (1 - gain);
}
}
else {
/* fade is outside reach */
goto fail;
return false;
}
*out_cur_vol = cur_vol;
return 1;
fail:
return 0;
return true;
}
#endif
void mixer_op_fade(mixer_t* mixer, int32_t sample_count, mix_op_t* mix) {
float* sbuf = mixer->mixbuf;
float new_gain = 0.0f;
int channels = mixer->current_channels;
int32_t current_subpos = mixer->current_subpos;
//TODO optimize for case 0?
for (int s = 0; s < sample_count; s++) {
bool fade_applies = get_fade_gain(mix, &new_gain, current_subpos);
if (!fade_applies) //TODO optimize?
continue;
if (mix->ch_dst < 0) {
for (int ch = 0; ch < channels; ch++) {
sbuf[ch] = sbuf[ch] * new_gain;
}
}
else {
sbuf[mix->ch_dst] = sbuf[mix->ch_dst] * new_gain;
}
sbuf += channels;
current_subpos++;
}
mixer->current_subpos = current_subpos;
}
bool mixer_op_fade_is_active(mixer_t* mixer, int32_t current_start, int32_t current_end) {
for (int i = 0; i < mixer->chain_count; i++) {
mix_op_t* mix = &mixer->chain[i];
int32_t fade_start, fade_end;
float vol_start = mix->vol_start;
if (mix->type != MIX_FADE)
continue;
/* check is current range falls within a fade
* (assuming fades were already optimized on add) */
if (mix->time_pre < 0 && vol_start == 1.0f) {
fade_start = mix->time_start; /* ignore unused */
}
else {
fade_start = mix->time_pre < 0 ? 0 : mix->time_pre;
}
fade_end = mix->time_post < 0 ? INT_MAX : mix->time_post;
//;VGM_LOG("MIX: fade test, tp=%i, te=%i, cs=%i, ce=%i\n", mix->time_pre, mix->time_post, current_start, current_end);
if (current_start < fade_end && current_end > fade_start) {
//;VGM_LOG("MIX: fade active, cs=%i < fe=%i and ce=%i > fs=%i\n", current_start, fade_end, current_end, fade_start);
return true;
}
}
return false;
}

View file

@ -0,0 +1,66 @@
#ifndef _MIXER_PRIV_H_
#define _MIXER_PRIV_H_
#include "../streamtypes.h"
#include "mixer.h"
#define VGMSTREAM_MAX_MIXING 512
typedef enum {
MIX_SWAP,
MIX_ADD,
MIX_VOLUME,
MIX_LIMIT,
MIX_UPMIX,
MIX_DOWNMIX,
MIX_KILLMIX,
MIX_FADE
} mix_type_t;
typedef struct {
mix_type_t type;
/* common */
int ch_dst;
int ch_src;
float vol;
/* fade envelope */
float vol_start; /* volume from pre to start */
float vol_end; /* volume from end to post */
char shape; /* curve type */
int32_t time_pre; /* position before time_start where vol_start applies (-1 = beginning) */
int32_t time_start; /* fade start position where vol changes from vol_start to vol_end */
int32_t time_end; /* fade end position where vol changes from vol_start to vol_end */
int32_t time_post; /* position after time_end where vol_end applies (-1 = end) */
} mix_op_t;
struct mixer_t {
int input_channels; /* starting channels before mixing */
int output_channels; /* resulting channels after mixing */
int mixing_channels; /* max channels needed to mix */
bool active; /* mixing working */
int chain_count; /* op number */
size_t chain_size; /* max ops */
mix_op_t chain[VGMSTREAM_MAX_MIXING]; /* effects to apply (could be alloc'ed but to simplify...) */
/* fades only apply at some points, other mixes are active */
bool has_non_fade;
bool has_fade;
float* mixbuf; /* internal mixing buffer */
int current_channels; /* state: channels may increase/decrease during ops */
int32_t current_subpos; /* state: current sample pos in the stream */
};
void mixer_op_swap(mixer_t* mixer, int32_t sample_count, mix_op_t* op);
void mixer_op_add(mixer_t* mixer, int32_t sample_count, mix_op_t* op);
void mixer_op_volume(mixer_t* mixer, int32_t sample_count, mix_op_t* op);
void mixer_op_limit(mixer_t* mixer, int32_t sample_count, mix_op_t* op);
void mixer_op_upmix(mixer_t* mixer, int32_t sample_count, mix_op_t* op);
void mixer_op_downmix(mixer_t* mixer, int32_t sample_count, mix_op_t* op);
void mixer_op_killmix(mixer_t* mixer, int32_t sample_count, mix_op_t* op);
void mixer_op_fade(mixer_t* mixer, int32_t sample_count, mix_op_t* op);
bool mixer_op_fade_is_active(mixer_t* mixer, int32_t current_start, int32_t current_end);
#endif

View file

@ -1,268 +1,72 @@
#include "../vgmstream.h"
#include "../util/channel_mappings.h"
#include "mixing.h"
#include "mixing_priv.h"
#include "mixing_fades.h"
#include "plugins.h"
#include "mixer.h"
#include "mixer_priv.h"
#include "sbuf.h"
#include "../layout/layout.h"
#include <math.h>
#include <limits.h>
//TODO simplify
/**
* Mixing lets vgmstream modify the resulting sample buffer before final output.
* This can be implemented in a number of ways but it's done like it is considering
* overall simplicity in coding, usage and performance (main complexity is allowing
* down/upmixing). Code is mostly independent with some hooks in the main vgmstream
* code.
* 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)
*
* It works using two buffers:
* - outbuf: plugin's pcm16 buffer, at least input_channels*sample_count
* - mixbuf: internal's pcmfloat buffer, at least mixing_channels*sample_count
* outbuf starts with decoded samples of vgmstream->channel size. This unsures that
* if no mixing is done (most common case) we can skip copying samples between buffers.
* Resulting outbuf after mixing has samples for ->output_channels (plus garbage).
* - output_channels is the resulting total channels (that may be less/more/equal)
* - input_channels is normally ->channels or ->output_channels when it's higher
*
* First, a meta (ex. TXTP) or plugin may add mixing commands through the API,
* validated so non-sensical mixes are ignored (to ensure mixing code doesn't
* have to recheck every time). Then, before starting to decode mixing must be
* manually activated, because plugins need to be ready for possibly different
* input/output channels. API could be improved but this way we can avoid having
* to update all plugins, while allowing internal setup and layer/segment mixing
* (may change in the future for simpler usage).
*
* Then after decoding normally, vgmstream applies mixing internally:
* - detect if mixing is active and needs to be done at this point (some effects
* like fades only apply after certain time) and skip otherwise.
* - copy outbuf to mixbuf, as using a float buffer to increase accuracy (most ops
* apply float volumes) and slightly improve performance (avoids doing
* int16-to-float casts per mix, as it's not free)
* - apply all mixes on mixbuf
* - copy mixbuf to outbuf
* segmented/layered layouts handle mixing on their own.
*
* Mixing is tuned for most common case (no mix except fade-out at the end) and is
* fast enough but not super-optimized yet, there is some penalty the more effects
* are applied. Maybe could add extra sub-ops to avoid ifs and dumb values (volume=0.0
* could simply use a clear op), only use mixbuf if necessary (swap can be done without
* mixbuf if it goes first) or add function pointer indexes but isn't too important.
* Operations are applied once per "step" with 1 sample from all channels to simplify code
* (and maybe improve memory cache?), though maybe it should call one function per operation.
*/
/* ******************************************************************* */
static void sbuf_copy_f32_to_s16(int16_t* buf_s16, float* buf_f32, int samples, int channels) {
for (int s = 0; s < samples * channels; s++) {
/* 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
*/
buf_s16[s] = clamp16( (int32_t)buf_f32[s] );
static int32_t get_current_pos(VGMSTREAM* vgmstream, int32_t sample_count) {
int32_t current_pos;
if (vgmstream->config_enabled) {
return vgmstream->pstate.play_position;
}
if (vgmstream->loop_flag && vgmstream->loop_count > 0) {
int loop_pre = vgmstream->loop_start_sample; /* samples before looping */
int loop_into = (vgmstream->current_sample - vgmstream->loop_start_sample); /* samples after loop */
int loop_samples = (vgmstream->loop_end_sample - vgmstream->loop_start_sample); /* looped section */
current_pos = loop_pre + (loop_samples * vgmstream->loop_count) + loop_into - sample_count;
}
else {
current_pos = (vgmstream->current_sample - sample_count);
}
return current_pos;
}
void mix_vgmstream(sample_t *outbuf, int32_t sample_count, VGMSTREAM* vgmstream) {
mixing_data *data = vgmstream->mixing_data;
int ch, s, m, ok;
int32_t current_subpos = 0;
float temp_f, temp_min, temp_max, cur_vol = 0.0f;
float *temp_mixbuf;
sample_t *temp_outbuf;
const float limiter_max = 32767.0f;
const float limiter_min = -32768.0f;
/* no support or not need to apply */
if (!data || !data->mixing_on || data->mixing_count == 0)
if (!mixer_is_active(vgmstream->mixer))
return;
/* try to skip if no fades apply (set but does nothing yet) + only has fades */
if (data->has_fade) {
int32_t current_pos = get_current_pos(vgmstream, sample_count);
//;VGM_LOG("MIX: fade test %i, %i\n", data->has_non_fade, is_fade_active(data, current_pos, current_pos + sample_count));
if (!data->has_non_fade && !is_fade_active(data, current_pos, current_pos + sample_count))
return;
//;VGM_LOG("MIX: fade pos=%i\n", current_pos);
current_subpos = current_pos;
}
int32_t current_pos = get_current_pos(vgmstream, sample_count);
/* use advancing buffer pointers to simplify logic */
temp_mixbuf = data->mixbuf; /* you'd think using a int32 temp buf would be faster but somehow it's slower? */
temp_outbuf = outbuf;
/* mixing ops are designed to apply in order, all channels per 1 sample 'step'. Since some ops change
* total channels, channel number meaning varies as ops move them around, ex:
* - 4ch w/ "1-2,2+3" = ch1<>ch3, ch2(old ch1)+ch3 = 4ch: ch2 ch1+ch3 ch3 ch4
* - 4ch w/ "2+3,1-2" = ch2+ch3, ch1<>ch2(modified) = 4ch: ch2+ch3 ch1 ch3 ch4
* - 2ch w/ "1+2,1u" = ch1+ch2, ch1(add and push rest) = 3ch: ch1' ch1+ch2 ch2
* - 2ch w/ "1u,1+2" = ch1(add and push rest) = 3ch: ch1'+ch1 ch1 ch2
* - 2ch w/ "1-2,1d" = ch1<>ch2, ch1(drop and move ch2(old ch1) to ch1) = ch1
* - 2ch w/ "1d,1-2" = ch1(drop and pull rest), ch1(do nothing, ch2 doesn't exist now) = ch2
*/
/* apply mixes in order per channel */
for (s = 0; s < sample_count; s++) {
/* reset after new sample 'step'*/
float *stpbuf = temp_mixbuf;
int step_channels = vgmstream->channels;
for (ch = 0; ch < step_channels; ch++) {
stpbuf[ch] = temp_outbuf[ch]; /* copy current 'lane' */
}
for (m = 0; m < data->mixing_count; m++) {
mix_command_data *mix = &data->mixing_chain[m];
switch(mix->command) {
case MIX_SWAP:
temp_f = stpbuf[mix->ch_dst];
stpbuf[mix->ch_dst] = stpbuf[mix->ch_src];
stpbuf[mix->ch_src] = temp_f;
break;
case MIX_ADD:
stpbuf[mix->ch_dst] = stpbuf[mix->ch_dst] + stpbuf[mix->ch_src] * mix->vol;
break;
case MIX_ADD_COPY:
stpbuf[mix->ch_dst] = stpbuf[mix->ch_dst] + stpbuf[mix->ch_src];
break;
case MIX_VOLUME:
if (mix->ch_dst < 0) {
for (ch = 0; ch < step_channels; ch++) {
stpbuf[ch] = stpbuf[ch] * mix->vol;
}
}
else {
stpbuf[mix->ch_dst] = stpbuf[mix->ch_dst] * mix->vol;
}
break;
case MIX_LIMIT:
temp_max = limiter_max * mix->vol;
temp_min = limiter_min * mix->vol;
if (mix->ch_dst < 0) {
for (ch = 0; ch < step_channels; ch++) {
if (stpbuf[ch] > temp_max)
stpbuf[ch] = temp_max;
else if (stpbuf[ch] < temp_min)
stpbuf[ch] = temp_min;
}
}
else {
if (stpbuf[mix->ch_dst] > temp_max)
stpbuf[mix->ch_dst] = temp_max;
else if (stpbuf[mix->ch_dst] < temp_min)
stpbuf[mix->ch_dst] = temp_min;
}
break;
case MIX_UPMIX:
step_channels += 1;
for (ch = step_channels - 1; ch > mix->ch_dst; ch--) {
stpbuf[ch] = stpbuf[ch-1]; /* 'push' channels forward (or pull backwards) */
}
stpbuf[mix->ch_dst] = 0; /* inserted as silent */
break;
case MIX_DOWNMIX:
step_channels -= 1;
for (ch = mix->ch_dst; ch < step_channels; ch++) {
stpbuf[ch] = stpbuf[ch+1]; /* 'pull' channels back */
}
break;
case MIX_KILLMIX:
step_channels = mix->ch_dst; /* clamp channels */
break;
case MIX_FADE:
ok = get_fade_gain(mix, &cur_vol, current_subpos);
if (!ok) {
break; /* fade doesn't apply right now */
}
if (mix->ch_dst < 0) {
for (ch = 0; ch < step_channels; ch++) {
stpbuf[ch] = stpbuf[ch] * cur_vol;
}
}
else {
stpbuf[mix->ch_dst] = stpbuf[mix->ch_dst] * cur_vol;
}
break;
default:
break;
}
}
current_subpos++;
temp_mixbuf += step_channels;
temp_outbuf += vgmstream->channels;
}
/* copy resulting temp mix to output */
sbuf_copy_f32_to_s16(outbuf, data->mixbuf, sample_count, data->output_channels);
mixer_process(vgmstream->mixer, outbuf, sample_count, current_pos);
}
/* ******************************************************************* */
void mixing_init(VGMSTREAM* vgmstream) {
mixing_data *data = calloc(1, sizeof(mixing_data));
if (!data) goto fail;
data->mixing_size = VGMSTREAM_MAX_MIXING; /* fixed array for now */
data->mixing_channels = vgmstream->channels;
data->output_channels = vgmstream->channels;
vgmstream->mixing_data = data;
return;
fail:
free(data);
return;
}
void mixing_close(VGMSTREAM* vgmstream) {
mixing_data *data = NULL;
if (!vgmstream) return;
data = vgmstream->mixing_data;
if (!data) return;
free(data->mixbuf);
free(data);
}
void mixing_update_channel(VGMSTREAM* vgmstream) {
mixing_data *data = vgmstream->mixing_data;
if (!data) return;
/* lame hack for dual stereo, but dual stereo is pretty hack-ish to begin with */
data->mixing_channels++;
data->output_channels++;
}
/* ******************************************************************* */
static int fix_layered_channel_layout(VGMSTREAM* vgmstream) {
int i;
mixing_data* data = vgmstream->mixing_data;
mixer_t* mixer = vgmstream->mixer;
layered_layout_data* layout_data;
uint32_t prev_cl;
@ -272,7 +76,7 @@ static int fix_layered_channel_layout(VGMSTREAM* vgmstream) {
layout_data = vgmstream->layout_data;
/* mainly layer-v (in cases of layers-within-layers should cascade) */
if (data->output_channels != layout_data->layers[0]->channels)
if (mixer->output_channels != layout_data->layers[0]->channels)
return 0;
/* check all layers share layout (implicitly works as a channel check, if not 0) */
@ -280,7 +84,7 @@ static int fix_layered_channel_layout(VGMSTREAM* vgmstream) {
if (prev_cl == 0)
return 0;
for (i = 1; i < layout_data->layer_count; i++) {
for (int i = 1; i < layout_data->layer_count; i++) {
uint32_t layer_cl = layout_data->layers[i]->channel_layout;
if (prev_cl != layer_cl)
return 0;
@ -294,7 +98,7 @@ static int fix_layered_channel_layout(VGMSTREAM* vgmstream) {
/* channel layout + down/upmixing = ?, salvage what we can */
static void fix_channel_layout(VGMSTREAM* vgmstream) {
mixing_data* data = vgmstream->mixing_data;
mixer_t* mixer = vgmstream->mixer;
if (fix_layered_channel_layout(vgmstream))
goto done;
@ -302,7 +106,7 @@ static void fix_channel_layout(VGMSTREAM* vgmstream) {
/* segments should share channel layout automatically */
/* a bit wonky but eh... */
if (vgmstream->channel_layout && vgmstream->channels != data->output_channels) {
if (vgmstream->channel_layout && vgmstream->channels != mixer->output_channels) {
vgmstream->channel_layout = 0;
}
@ -310,22 +114,23 @@ done:
((VGMSTREAM*)vgmstream->start_vgmstream)->channel_layout = vgmstream->channel_layout;
}
void mixing_setup(VGMSTREAM* vgmstream, int32_t max_sample_count) {
mixing_data *data = vgmstream->mixing_data;
float *mixbuf_re = NULL;
if (!data) goto fail;
void mixing_setup(VGMSTREAM* vgmstream, int32_t max_sample_count) {
mixer_t* mixer = vgmstream->mixer;
if (!mixer)
return;
/* special value to not actually enable anything (used to query values) */
if (max_sample_count <= 0)
goto fail;
return;
/* create or alter internal buffer */
mixbuf_re = realloc(data->mixbuf, max_sample_count*data->mixing_channels*sizeof(float));
float* mixbuf_re = realloc(mixer->mixbuf, max_sample_count * mixer->mixing_channels * sizeof(float));
if (!mixbuf_re) goto fail;
data->mixbuf = mixbuf_re;
data->mixing_on = 1;
mixer->mixbuf = mixbuf_re;
mixer->active = true;
fix_channel_layout(vgmstream);
@ -340,14 +145,15 @@ fail:
}
void mixing_info(VGMSTREAM* vgmstream, int* p_input_channels, int* p_output_channels) {
mixing_data *data = vgmstream->mixing_data;
mixer_t* mixer = vgmstream->mixer;
int input_channels, output_channels;
if (!data) goto fail;
if (!mixer)
goto fail;
output_channels = data->output_channels;
if (data->output_channels > vgmstream->channels)
input_channels = data->output_channels;
output_channels = mixer->output_channels;
if (mixer->output_channels > vgmstream->channels)
input_channels = mixer->output_channels;
else
input_channels = vgmstream->channels;

View file

@ -2,24 +2,19 @@
#define _MIXING_H_
#include "../vgmstream.h"
#include "../util/log.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);
/* internal mixing pre-setup for vgmstream (doesn't imply usage).
* If init somehow fails next calls are ignored. */
void mixing_init(VGMSTREAM* vgmstream);
void mixing_close(VGMSTREAM* vgmstream);
void mixing_update_channel(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
* of down/upmixing without querying input/output_channels). */
void mixing_setup(VGMSTREAM * vgmstream, int32_t max_sample_count);
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);
/* adds mixes filtering and optimizing if needed */
void mixing_push_swap(VGMSTREAM* vgmstream, int ch_dst, int ch_src);
@ -39,4 +34,4 @@ void mixing_macro_crosslayer(VGMSTREAM* vgmstream, int max, char mode);
void mixing_macro_downmix(VGMSTREAM* vgmstream, int max /*, mapping_t output_mapping*/);
#endif /* _MIXING_H_ */
#endif

View file

@ -1,172 +1,170 @@
#include "../vgmstream.h"
#include "../util/channel_mappings.h"
#include "mixing.h"
#include "mixing_priv.h"
#include "mixer_priv.h"
#include <math.h>
#include <limits.h>
static int add_mixing(VGMSTREAM* vgmstream, mix_command_data *mix) {
mixing_data *data = vgmstream->mixing_data;
if (!data) return 0;
static bool add_mixing(VGMSTREAM* vgmstream, mix_op_t* op) {
mixer_t* mixer = vgmstream->mixer;
if (!mixer)
return false;
if (data->mixing_on) {
VGM_LOG("MIX: ignoring new mixes when mixing active\n");
return 0; /* to avoid down/upmixing after activation */
if (mixer->active) {
VGM_LOG("MIX: ignoring new ops when mixer is active\n");
return false; /* to avoid down/upmixing after activation */
}
if (data->mixing_count + 1 > data->mixing_size) {
if (mixer->chain_count + 1 > mixer->chain_size) {
VGM_LOG("MIX: too many mixes\n");
return 0;
return false;
}
data->mixing_chain[data->mixing_count] = *mix; /* memcpy */
data->mixing_count++;
mixer->chain[mixer->chain_count] = *op; /* memcpy */
mixer->chain_count++;
if (mix->command == MIX_FADE) {
data->has_fade = 1;
if (op->type == MIX_FADE) {
mixer->has_fade = true;
}
else {
data->has_non_fade = 1;
mixer->has_non_fade = true;
}
//;VGM_LOG("MIX: total %i\n", data->mixing_count);
return 1;
//;VGM_LOG("MIX: total %i\n", data->chain_count);
return true;
}
void mixing_push_swap(VGMSTREAM* vgmstream, int ch_dst, int ch_src) {
mixing_data *data = vgmstream->mixing_data;
mix_command_data mix = {0};
mixer_t* mixer = vgmstream->mixer;
mix_op_t op = {0};
if (ch_dst < 0 || ch_src < 0 || ch_dst == ch_src) return;
if (!data || ch_dst >= data->output_channels || ch_src >= data->output_channels) return;
mix.command = MIX_SWAP;
mix.ch_dst = ch_dst;
mix.ch_src = ch_src;
if (!mixer || ch_dst >= mixer->output_channels || ch_src >= mixer->output_channels) return;
op.type = MIX_SWAP;
op.ch_dst = ch_dst;
op.ch_src = ch_src;
add_mixing(vgmstream, &mix);
add_mixing(vgmstream, &op);
}
void mixing_push_add(VGMSTREAM* vgmstream, int ch_dst, int ch_src, double volume) {
mixing_data *data = vgmstream->mixing_data;
mix_command_data mix = {0};
if (!data) return;
mixer_t* mixer = vgmstream->mixer;
mix_op_t op = {0};
if (!mixer) return;
//if (volume < 0.0) return; /* negative volume inverts the waveform */
if (volume == 0.0) return; /* ch_src becomes silent and nothing is added */
if (ch_dst < 0 || ch_src < 0) return;
if (!data || ch_dst >= data->output_channels || ch_src >= data->output_channels) return;
if (!mixer || ch_dst >= mixer->output_channels || ch_src >= mixer->output_channels) return;
mix.command = (volume == 1.0) ? MIX_ADD_COPY : MIX_ADD;
mix.ch_dst = ch_dst;
mix.ch_src = ch_src;
mix.vol = volume;
op.type = MIX_ADD;
op.ch_dst = ch_dst;
op.ch_src = ch_src;
op.vol = volume;
//;VGM_LOG("MIX: add %i+%i*%f\n", ch_dst,ch_src,volume);
add_mixing(vgmstream, &mix);
add_mixing(vgmstream, &op);
}
void mixing_push_volume(VGMSTREAM* vgmstream, int ch_dst, double volume) {
mixing_data *data = vgmstream->mixing_data;
mix_command_data mix = {0};
mixer_t* mixer = vgmstream->mixer;
mix_op_t op = {0};
//if (ch_dst < 0) return; /* means all channels */
//if (volume < 0.0) return; /* negative volume inverts the waveform */
if (volume == 1.0) return; /* no change */
if (!data || ch_dst >= data->output_channels) return;
if (!mixer || ch_dst >= mixer->output_channels) return;
mix.command = MIX_VOLUME; //if (volume == 0.0) MIX_VOLUME0 /* could simplify */
mix.ch_dst = ch_dst;
mix.vol = volume;
op.type = MIX_VOLUME; //if (volume == 0.0) MIX_VOLUME0 /* could simplify */
op.ch_dst = ch_dst;
op.vol = volume;
//;VGM_LOG("MIX: volume %i*%f\n", ch_dst,volume);
add_mixing(vgmstream, &mix);
add_mixing(vgmstream, &op);
}
void mixing_push_limit(VGMSTREAM* vgmstream, int ch_dst, double volume) {
mixing_data *data = vgmstream->mixing_data;
mix_command_data mix = {0};
mixer_t* mixer = vgmstream->mixer;
mix_op_t op = {0};
//if (ch_dst < 0) return; /* means all channels */
if (volume < 0.0) return;
if (volume == 1.0) return; /* no actual difference */
if (!data || ch_dst >= data->output_channels) return;
if (!mixer || ch_dst >= mixer->output_channels) return;
//if (volume == 0.0) return; /* dumb but whatevs */
mix.command = MIX_LIMIT;
mix.ch_dst = ch_dst;
mix.vol = volume;
op.type = MIX_LIMIT;
op.ch_dst = ch_dst;
op.vol = volume;
add_mixing(vgmstream, &mix);
add_mixing(vgmstream, &op);
}
void mixing_push_upmix(VGMSTREAM* vgmstream, int ch_dst) {
mixing_data *data = vgmstream->mixing_data;
mix_command_data mix = {0};
mixer_t* mixer = vgmstream->mixer;
mix_op_t op = {0};
int ok;
if (ch_dst < 0) return;
if (!data || ch_dst > data->output_channels || data->output_channels +1 > VGMSTREAM_MAX_CHANNELS) return;
if (!mixer || ch_dst > mixer->output_channels || mixer->output_channels +1 > VGMSTREAM_MAX_CHANNELS) return;
/* dst can be == output_channels here, since we are inserting */
mix.command = MIX_UPMIX;
mix.ch_dst = ch_dst;
op.type = MIX_UPMIX;
op.ch_dst = ch_dst;
ok = add_mixing(vgmstream, &mix);
ok = add_mixing(vgmstream, &op);
if (ok) {
data->output_channels += 1;
if (data->mixing_channels < data->output_channels)
data->mixing_channels = data->output_channels;
mixer->output_channels += 1;
if (mixer->mixing_channels < mixer->output_channels)
mixer->mixing_channels = mixer->output_channels;
}
}
void mixing_push_downmix(VGMSTREAM* vgmstream, int ch_dst) {
mixing_data *data = vgmstream->mixing_data;
mix_command_data mix = {0};
mixer_t* mixer = vgmstream->mixer;
mix_op_t op = {0};
int ok;
if (ch_dst < 0) return;
if (!data || ch_dst >= data->output_channels || data->output_channels - 1 < 1) return;
if (!mixer || ch_dst >= mixer->output_channels || mixer->output_channels - 1 < 1) return;
mix.command = MIX_DOWNMIX;
mix.ch_dst = ch_dst;
op.type = MIX_DOWNMIX;
op.ch_dst = ch_dst;
ok = add_mixing(vgmstream, &mix);
ok = add_mixing(vgmstream, &op);
if (ok) {
data->output_channels -= 1;
mixer->output_channels -= 1;
}
}
void mixing_push_killmix(VGMSTREAM* vgmstream, int ch_dst) {
mixing_data *data = vgmstream->mixing_data;
mix_command_data mix = {0};
int ok;
mixer_t* mixer = vgmstream->mixer;
mix_op_t op = {0};
if (ch_dst <= 0) return; /* can't kill from first channel */
if (!data || ch_dst >= data->output_channels) return;
if (!mixer || ch_dst >= mixer->output_channels) return;
mix.command = MIX_KILLMIX;
mix.ch_dst = ch_dst;
op.type = MIX_KILLMIX;
op.ch_dst = ch_dst;
//;VGM_LOG("MIX: killmix %i\n", ch_dst);
ok = add_mixing(vgmstream, &mix);
bool ok = add_mixing(vgmstream, &op);
if (ok) {
data->output_channels = ch_dst; /* clamp channels */
mixer->output_channels = ch_dst; /* clamp channels */
}
}
static mix_command_data* get_last_fade(mixing_data *data, int target_channel) {
int i;
for (i = data->mixing_count; i > 0; i--) {
mix_command_data *mix = &data->mixing_chain[i-1];
if (mix->command != MIX_FADE)
static mix_op_t* get_last_fade(mixer_t* mixer, int target_channel) {
for (int i = mixer->chain_count; i > 0; i--) {
mix_op_t* op = &mixer->chain[i-1];
if (op->type != MIX_FADE)
continue;
if (mix->ch_dst == target_channel)
return mix;
if (op->ch_dst == target_channel)
return op;
}
return NULL;
@ -175,13 +173,13 @@ static mix_command_data* get_last_fade(mixing_data *data, int target_channel) {
void mixing_push_fade(VGMSTREAM* vgmstream, int ch_dst, double vol_start, double vol_end, char shape,
int32_t time_pre, int32_t time_start, int32_t time_end, int32_t time_post) {
mixing_data *data = vgmstream->mixing_data;
mix_command_data mix = {0};
mix_command_data *mix_prev;
mixer_t* mixer = vgmstream->mixer;
mix_op_t op = {0};
mix_op_t* op_prev;
//if (ch_dst < 0) return; /* means all channels */
if (!data || ch_dst >= data->output_channels) return;
if (!mixer || ch_dst >= mixer->output_channels) return;
if (time_pre > time_start || time_start > time_end || (time_post >= 0 && time_end > time_post)) return;
if (time_start < 0 || time_end < 0) return;
//if (time_pre < 0 || time_post < 0) return; /* special meaning of file start/end */
@ -192,15 +190,15 @@ void mixing_push_fade(VGMSTREAM* vgmstream, int ch_dst, double vol_start, double
if (shape == '(' || shape == ')')
shape = 'H';
mix.command = MIX_FADE;
mix.ch_dst = ch_dst;
mix.vol_start = vol_start;
mix.vol_end = vol_end;
mix.shape = shape;
mix.time_pre = time_pre;
mix.time_start = time_start;
mix.time_end = time_end;
mix.time_post = time_post;
op.type = MIX_FADE;
op.ch_dst = ch_dst;
op.vol_start = vol_start;
op.vol_end = vol_end;
op.shape = shape;
op.time_pre = time_pre;
op.time_start = time_start;
op.time_end = time_end;
op.time_post = time_post;
/* cancel fades and optimize a bit when using negative pre/post:
@ -216,33 +214,33 @@ void mixing_push_fade(VGMSTREAM* vgmstream, int ch_dst, double vol_start, double
* as they're uncommon and hard to optimize
* fades cancel fades of the same channel, and 'all channel' (-1) fades also cancel 'all channels'
*/
mix_prev = get_last_fade(data, mix.ch_dst);
if (mix_prev == NULL) {
op_prev = get_last_fade(mixer, op.ch_dst);
if (op_prev == NULL) {
if (vol_start == 1.0 && time_pre < 0)
time_pre = time_start; /* fade-out helds default volume before fade start can be clamped */
if (vol_end == 1.0 && time_post < 0)
time_post = time_end; /* fade-in helds default volume after fade end can be clamped */
}
else if (mix_prev->time_post < 0 || mix.time_pre < 0) {
else if (op_prev->time_post < 0 || op.time_pre < 0) {
int is_prev = 1;
/* test if prev is really cancelled by this */
if ((mix_prev->time_end > mix.time_start) ||
(mix_prev->time_post >= 0 && mix_prev->time_post > mix.time_start) ||
(mix.time_pre >= 0 && mix.time_pre < mix_prev->time_end))
if ((op_prev->time_end > op.time_start) ||
(op_prev->time_post >= 0 && op_prev->time_post > op.time_start) ||
(op.time_pre >= 0 && op.time_pre < op_prev->time_end))
is_prev = 0;
if (is_prev) {
/* change negative values to actual points */
if (mix_prev->time_post < 0 && mix.time_pre < 0) {
mix_prev->time_post = mix_prev->time_end;
mix.time_pre = mix_prev->time_post;
if (op_prev->time_post < 0 && op.time_pre < 0) {
op_prev->time_post = op_prev->time_end;
op.time_pre = op_prev->time_post;
}
if (mix_prev->time_post >= 0 && mix.time_pre < 0) {
mix.time_pre = mix_prev->time_post;
if (op_prev->time_post >= 0 && op.time_pre < 0) {
op.time_pre = op_prev->time_post;
}
else if (mix_prev->time_post < 0 && mix.time_pre >= 0) {
mix_prev->time_post = mix.time_pre;
else if (op_prev->time_post < 0 && op.time_pre >= 0) {
op_prev->time_post = op.time_pre;
}
/* else: both define start/ends, do nothing */
}
@ -250,5 +248,5 @@ void mixing_push_fade(VGMSTREAM* vgmstream, int ch_dst, double vol_start, double
}
//;VGM_LOG("MIX: fade %i^%f~%f=%c@%i~%i~%i~%i\n", ch_dst, vol_start, vol_end, shape, time_pre, time_start, time_end, time_post);
add_mixing(vgmstream, &mix);
add_mixing(vgmstream, &op);
}

View file

@ -1,7 +1,8 @@
#include "../vgmstream.h"
#include "../util/channel_mappings.h"
#include "../layout/layout.h"
#include "mixing.h"
#include "mixing_priv.h"
#include "mixer_priv.h"
#include <math.h>
#include <limits.h>
@ -11,10 +12,8 @@
#define MIX_MACRO_BGM 'b'
void mixing_macro_volume(VGMSTREAM* vgmstream, double volume, uint32_t mask) {
mixing_data *data = vgmstream->mixing_data;
int ch;
if (!data)
mixer_t* mixer = vgmstream->mixer;
if (!mixer)
return;
if (mask == 0) {
@ -22,7 +21,7 @@ void mixing_macro_volume(VGMSTREAM* vgmstream, double volume, uint32_t mask) {
return;
}
for (ch = 0; ch < data->output_channels; ch++) {
for (int ch = 0; ch < mixer->output_channels; ch++) {
if (!((mask >> ch) & 1))
continue;
mixing_push_volume(vgmstream, ch, volume);
@ -30,10 +29,8 @@ void mixing_macro_volume(VGMSTREAM* vgmstream, double volume, uint32_t mask) {
}
void mixing_macro_track(VGMSTREAM* vgmstream, uint32_t mask) {
mixing_data *data = vgmstream->mixing_data;
int ch;
if (!data)
mixer_t* mixer = vgmstream->mixer;
if (!mixer)
return;
if (mask == 0) {
@ -41,7 +38,7 @@ void mixing_macro_track(VGMSTREAM* vgmstream, uint32_t mask) {
}
/* reverse remove all channels (easier this way as when removing channels numbers change) */
for (ch = data->output_channels - 1; ch >= 0; ch--) {
for (int ch = mixer->output_channels - 1; ch >= 0; ch--) {
if ((mask >> ch) & 1)
continue;
mixing_push_downmix(vgmstream, ch);
@ -51,16 +48,13 @@ void mixing_macro_track(VGMSTREAM* vgmstream, uint32_t mask) {
/* get highest channel count */
static int get_layered_max_channels(VGMSTREAM* vgmstream) {
int i, max;
layered_layout_data* data;
if (vgmstream->layout_type != layout_layered)
return 0;
data = vgmstream->layout_data;
layered_layout_data* data = vgmstream->layout_data;
max = 0;
for (i = 0; i < data->layer_count; i++) {
int max = 0;
for (int i = 0; i < data->layer_count; i++) {
int output_channels = 0;
mixing_info(data->layers[i], NULL, &output_channels);
@ -73,11 +67,6 @@ static int get_layered_max_channels(VGMSTREAM* vgmstream) {
}
static int is_layered_auto(VGMSTREAM* vgmstream, int max, char mode) {
int i;
mixing_data *data = vgmstream->mixing_data;
layered_layout_data* l_data;
if (vgmstream->layout_type != layout_layered)
return 0;
@ -86,15 +75,16 @@ static int is_layered_auto(VGMSTREAM* vgmstream, int max, char mode) {
return 0;
/* no channel down/upmixing (cannot guess output) */
for (i = 0; i < data->mixing_count; i++) {
mix_command_t mix = data->mixing_chain[i].command;
if (mix == MIX_UPMIX || mix == MIX_DOWNMIX || mix == MIX_KILLMIX) /*mix == MIX_SWAP || ??? */
mixer_t* mixer = vgmstream->mixer;
for (int i = 0; i < mixer->chain_count; i++) {
mix_type_t type = mixer->chain[i].type;
if (type == MIX_UPMIX || type == MIX_DOWNMIX || type == MIX_KILLMIX) /*type == MIX_SWAP || ??? */
return 0;
}
/* only previsible cases */
l_data = vgmstream->layout_data;
for (i = 0; i < l_data->layer_count; i++) {
layered_layout_data* l_data = vgmstream->layout_data;
for (int i = 0; i < l_data->layer_count; i++) {
int output_channels = 0;
mixing_info(l_data->layers[i], NULL, &output_channels);
@ -110,9 +100,8 @@ static int is_layered_auto(VGMSTREAM* vgmstream, int max, char mode) {
/* special layering, where channels are respected (so Ls only go to Ls), also more optimized */
static void mixing_macro_layer_auto(VGMSTREAM* vgmstream, int max, char mode) {
layered_layout_data* ldata = vgmstream->layout_data;
int i, ch;
int target_layer = 0, target_chs = 0, ch_max, target_ch = 0, target_silence = 0;
int ch_num;
int target_layer = 0, target_chs = 0, target_ch = 0, target_silence = 0;
int ch_num, ch_max;
/* With N layers like: (ch1 ch2) (ch1 ch2 ch3 ch4) (ch1 ch2), output is normally 2+4+2=8ch.
* We want to find highest layer (ch1..4) = 4ch, add other channels to it and drop them */
@ -120,7 +109,7 @@ static void mixing_macro_layer_auto(VGMSTREAM* vgmstream, int max, char mode) {
/* find target "main" channels (will be first most of the time) */
ch_num = 0;
ch_max = 0;
for (i = 0; i < ldata->layer_count; i++) {
for (int i = 0; i < ldata->layer_count; i++) {
int layer_chs = 0;
mixing_info(ldata->layers[i], NULL, &layer_chs);
@ -148,7 +137,7 @@ static void mixing_macro_layer_auto(VGMSTREAM* vgmstream, int max, char mode) {
/* add other channels to target (assumes standard channel mapping to simplify)
* most of the time all layers will have same number of channels though */
ch_num = 0;
for (i = 0; i < ldata->layer_count; i++) {
for (int i = 0; i < ldata->layer_count; i++) {
int layer_chs = 0;
if (target_layer == i) {
@ -165,7 +154,7 @@ static void mixing_macro_layer_auto(VGMSTREAM* vgmstream, int max, char mode) {
if (layer_chs == target_chs) {
/* 1:1 mapping */
for (ch = 0; ch < layer_chs; ch++) {
for (int ch = 0; ch < layer_chs; ch++) {
mixing_push_add(vgmstream, target_ch + ch, ch_num + ch, 1.0);
}
}
@ -184,7 +173,7 @@ static void mixing_macro_layer_auto(VGMSTREAM* vgmstream, int max, char mode) {
break;
default: /* less common */
//TODO add other mixes, depends on target_chs + mapping (ex. 4.0 to 5.0 != 5.1, 2.1 xiph to 5.1 != 5.1 xiph)
for (ch = 0; ch < layer_chs; ch++) {
for (int ch = 0; ch < layer_chs; ch++) {
mixing_push_add(vgmstream, target_ch + ch, ch_num + ch, 1.0);
}
break;
@ -196,13 +185,13 @@ static void mixing_macro_layer_auto(VGMSTREAM* vgmstream, int max, char mode) {
/* drop non-target channels */
ch_num = 0;
for (i = 0; i < ldata->layer_count; i++) {
for (int i = 0; i < ldata->layer_count; i++) {
if (i < target_layer) { /* least common, hopefully (slower to drop chs 1 by 1) */
int layer_chs = 0;
mixing_info(ldata->layers[i], NULL, &layer_chs);
for (ch = 0; ch < layer_chs; ch++) {
for (int ch = 0; ch < layer_chs; ch++) {
mixing_push_downmix(vgmstream, ch_num); //+ ch
}
@ -220,10 +209,10 @@ static void mixing_macro_layer_auto(VGMSTREAM* vgmstream, int max, char mode) {
void mixing_macro_layer(VGMSTREAM* vgmstream, int max, uint32_t mask, char mode) {
mixing_data *data = vgmstream->mixing_data;
mixer_t* mixer = vgmstream->mixer;
int current, ch, output_channels, selected_channels;
if (!data)
if (!mixer)
return;
if (is_layered_auto(vgmstream, max, mode)) {
@ -236,7 +225,7 @@ void mixing_macro_layer(VGMSTREAM* vgmstream, int max, uint32_t mask, char mode)
if (max == 0) /* auto calculate */
max = get_layered_max_channels(vgmstream);
if (max <= 0 || data->output_channels <= max)
if (max <= 0 || mixer->output_channels <= max)
return;
/* set all channels (non-existant channels will be ignored) */
@ -245,7 +234,7 @@ void mixing_macro_layer(VGMSTREAM* vgmstream, int max, uint32_t mask, char mode)
}
/* save before adding fake channels */
output_channels = data->output_channels;
output_channels = mixer->output_channels;
/* count possibly set channels */
selected_channels = 0;
@ -304,19 +293,19 @@ void mixing_macro_layer(VGMSTREAM* vgmstream, int max, uint32_t mask, char mode)
}
void mixing_macro_crosstrack(VGMSTREAM* vgmstream, int max) {
mixing_data *data = vgmstream->mixing_data;
mixer_t* mixer = vgmstream->mixer;
int current, ch, track, track_ch, track_num, output_channels;
int32_t change_pos, change_next, change_time;
if (!data)
if (!mixer)
return;
if (max <= 0 || data->output_channels <= max)
if (max <= 0 || mixer->output_channels <= max)
return;
if (!vgmstream->loop_flag) /* maybe force loop? */
return;
/* this probably only makes sense for even channels so upmix before if needed) */
output_channels = data->output_channels;
output_channels = mixer->output_channels;
if (output_channels % 2) {
mixing_push_upmix(vgmstream, output_channels);
output_channels += 1;
@ -368,19 +357,19 @@ void mixing_macro_crosstrack(VGMSTREAM* vgmstream, int max) {
}
void mixing_macro_crosslayer(VGMSTREAM* vgmstream, int max, char mode) {
mixing_data *data = vgmstream->mixing_data;
mixer_t* mixer = vgmstream->mixer;
int current, ch, layer, layer_ch, layer_num, loop, output_channels;
int32_t change_pos, change_time;
if (!data)
if (!mixer)
return;
if (max <= 0 || data->output_channels <= max)
if (max <= 0 || mixer->output_channels <= max)
return;
if (!vgmstream->loop_flag) /* maybe force loop? */
return;
/* this probably only makes sense for even channels so upmix before if needed) */
output_channels = data->output_channels;
output_channels = mixer->output_channels;
if (output_channels % 2) {
mixing_push_upmix(vgmstream, output_channels);
output_channels += 1;
@ -481,7 +470,7 @@ typedef enum {
} mixing_position_t;
void mixing_macro_downmix(VGMSTREAM* vgmstream, int max /*, mapping_t output_mapping*/) {
mixing_data *data = vgmstream->mixing_data;
mixer_t* mixer = vgmstream->mixer;
int ch, output_channels, mp_in, mp_out, ch_in, ch_out;
channel_mapping_t input_mapping, output_mapping;
const double vol_max = 1.0;
@ -490,15 +479,15 @@ void mixing_macro_downmix(VGMSTREAM* vgmstream, int max /*, mapping_t output_map
double matrix[16][16] = {{0}};
if (!data)
if (!mixer)
return;
if (max <= 1 || data->output_channels <= max || max >= 8)
if (max <= 1 || mixer->output_channels <= max || max >= 8)
return;
/* assume WAV defaults if not set */
input_mapping = vgmstream->channel_layout;
if (input_mapping == 0) {
switch(data->output_channels) {
switch(mixer->output_channels) {
case 1: input_mapping = mapping_MONO; break;
case 2: input_mapping = mapping_STEREO; break;
case 3: input_mapping = mapping_2POINT1; break;
@ -544,7 +533,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 = data->output_channels;
output_channels = mixer->output_channels;
for (ch = 0; ch < max; ch++) {
mixing_push_upmix(vgmstream, 0);
}

View file

@ -1,52 +0,0 @@
#ifndef _MIXING_PRIV_H_
#define _MIXING_PRIV_H_
#include "../vgmstream.h"
#define VGMSTREAM_MAX_MIXING 512
/* mixing info */
typedef enum {
MIX_SWAP,
MIX_ADD,
MIX_ADD_COPY,
MIX_VOLUME,
MIX_LIMIT,
MIX_UPMIX,
MIX_DOWNMIX,
MIX_KILLMIX,
MIX_FADE
} mix_command_t;
typedef struct {
mix_command_t command;
/* common */
int ch_dst;
int ch_src;
float vol;
/* fade envelope */
float vol_start; /* volume from pre to start */
float vol_end; /* volume from end to post */
char shape; /* curve type */
int32_t time_pre; /* position before time_start where vol_start applies (-1 = beginning) */
int32_t time_start; /* fade start position where vol changes from vol_start to vol_end */
int32_t time_end; /* fade end position where vol changes from vol_start to vol_end */
int32_t time_post; /* position after time_end where vol_end applies (-1 = end) */
} mix_command_data;
typedef struct {
int mixing_channels; /* max channels needed to mix */
int output_channels; /* resulting channels after mixing */
int mixing_on; /* mixing allowed */
int mixing_count; /* mixing number */
size_t mixing_size; /* mixing max */
mix_command_data mixing_chain[VGMSTREAM_MAX_MIXING]; /* effects to apply (could be alloc'ed but to simplify...) */
float* mixbuf; /* internal mixing buffer */
/* fades only apply at some points, other mixes are active */
int has_non_fade;
int has_fade;
} mixing_data;
#endif

View file

@ -1,7 +1,5 @@
#include "../vgmstream.h"
#include "../util/log.h"
#include "../util/reader_sf.h"
#include "../util/reader_text.h"
#include "plugins.h"
#include "mixing.h"
@ -16,8 +14,13 @@ int vgmstream_ctx_is_valid(const char* filename, vgmstream_ctx_valid_cfg *cfg) {
const char* extension;
int i;
bool is_extension = cfg && cfg->is_extension;
bool reject_extensionless = cfg && cfg->reject_extensionless;
bool skip_standard = cfg && cfg->skip_standard;
bool accept_common = cfg && cfg->accept_common;
bool accept_unknown = cfg && cfg->accept_common;
if (cfg->is_extension) {
if (is_extension) {
extension = filename;
} else {
extension = filename_extension(filename);
@ -26,15 +29,15 @@ 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 && !cfg->is_extension)
if (len <= 0 && is_extension)
return 0;
if (len > 1 && (filename[len - 1] == '/' || filename[len - 1] == '\\'))
return 0;
return !cfg->reject_extensionless;
return !reject_extensionless;
}
/* try in default list */
if (!cfg->skip_standard) {
if (!skip_standard) {
extension_list = vgmstream_get_formats(&extension_list_len);
for (i = 0; i < extension_list_len; i++) {
if (strcasecmp(extension, extension_list[i]) == 0) {
@ -44,7 +47,7 @@ int vgmstream_ctx_is_valid(const char* filename, vgmstream_ctx_valid_cfg *cfg) {
}
/* try in common extensions */
if (cfg->accept_common) {
if (accept_common) {
extension_list = vgmstream_get_common_formats(&extension_list_len);
for (i = 0; i < extension_list_len; i++) {
if (strcasecmp(extension, extension_list[i]) == 0)
@ -53,7 +56,7 @@ int vgmstream_ctx_is_valid(const char* filename, vgmstream_ctx_valid_cfg *cfg) {
}
/* allow anything not in the normal list but not in common extensions */
if (cfg->accept_unknown) {
if (accept_unknown) {
int is_common = 0;
extension_list = vgmstream_get_common_formats(&extension_list_len);
@ -76,7 +79,12 @@ void vgmstream_get_title(char* buf, int buf_len, const char* filename, VGMSTREAM
char* pos2;
char temp[1024];
if (!buf || !buf_len)
return;
buf[0] = '\0';
if (!vgmstream || !filename)
return;
/* name without path */
pos = strrchr(filename, '\\');
@ -144,385 +152,11 @@ void vgmstream_get_title(char* buf, int buf_len, const char* filename, VGMSTREAM
buf[buf_len - 1] = '\0';
}
static void copy_time(int* dst_flag, int32_t* dst_time, double* dst_time_s, int* src_flag, int32_t* src_time, double* src_time_s) {
if (!*src_flag)
return;
*dst_flag = 1;
*dst_time = *src_time;
*dst_time_s = *src_time_s;
}
//todo reuse in txtp?
static void load_default_config(play_config_t* def, play_config_t* tcfg) {
/* loop limit: txtp #L > txtp #l > player #L > player #l */
if (tcfg->play_forever) {
def->play_forever = 1;
def->ignore_loop = 0;
}
if (tcfg->loop_count_set) {
def->loop_count = tcfg->loop_count;
def->loop_count_set = 1;
def->ignore_loop = 0;
if (!tcfg->play_forever)
def->play_forever = 0;
}
/* fade priority: #F > #f, #d */
if (tcfg->ignore_fade) {
def->ignore_fade = 1;
}
if (tcfg->fade_delay_set) {
def->fade_delay = tcfg->fade_delay;
def->fade_delay_set = 1;
}
if (tcfg->fade_time_set) {
def->fade_time = tcfg->fade_time;
def->fade_time_set = 1;
}
/* loop priority: #i > #e > #E (respect player's ignore too) */
if (tcfg->really_force_loop) {
//def->ignore_loop = 0;
def->force_loop = 0;
def->really_force_loop = 1;
}
if (tcfg->force_loop) {
//def->ignore_loop = 0;
def->force_loop = 1;
def->really_force_loop = 0;
}
if (tcfg->ignore_loop) {
def->ignore_loop = 1;
def->force_loop = 0;
def->really_force_loop = 0;
}
copy_time(&def->pad_begin_set, &def->pad_begin, &def->pad_begin_s, &tcfg->pad_begin_set, &tcfg->pad_begin, &tcfg->pad_begin_s);
copy_time(&def->pad_end_set, &def->pad_end, &def->pad_end_s, &tcfg->pad_end_set, &tcfg->pad_end, &tcfg->pad_end_s);
copy_time(&def->trim_begin_set, &def->trim_begin, &def->trim_begin_s, &tcfg->trim_begin_set, &tcfg->trim_begin, &tcfg->trim_begin_s);
copy_time(&def->trim_end_set, &def->trim_end, &def->trim_end_s, &tcfg->trim_end_set, &tcfg->trim_end, &tcfg->trim_end_s);
copy_time(&def->body_time_set, &def->body_time, &def->body_time_s, &tcfg->body_time_set, &tcfg->body_time, &tcfg->body_time_s);
def->is_mini_txtp = tcfg->is_mini_txtp;
def->is_txtp = tcfg->is_txtp;
}
static void load_player_config(play_config_t* def, vgmstream_cfg_t* vcfg) {
def->play_forever = vcfg->play_forever;
def->ignore_loop = vcfg->ignore_loop;
def->force_loop = vcfg->force_loop;
def->really_force_loop = vcfg->really_force_loop;
def->ignore_fade = vcfg->ignore_fade;
def->loop_count = vcfg->loop_count;
def->loop_count_set = 1;
def->fade_delay = vcfg->fade_delay;
def->fade_delay_set = 1;
def->fade_time = vcfg->fade_time;
def->fade_time_set = 1;
}
void vgmstream_apply_config(VGMSTREAM* vgmstream, vgmstream_cfg_t* vcfg) {
play_config_t defs = {0};
play_config_t* def = &defs; /* for convenience... */
play_config_t* tcfg = &vgmstream->config;
load_player_config(def, vcfg);
def->config_set = 1;
if (!vcfg->disable_config_override)
load_default_config(def, tcfg);
if (!vcfg->allow_play_forever)
def->play_forever = 0;
/* copy final config back */
*tcfg = *def;
vgmstream->config_enabled = def->config_set;
setup_state_vgmstream(vgmstream);
}
/* ****************************************** */
/* TAGS: loads key=val tags from a file */
/* ****************************************** */
#define VGMSTREAM_TAGS_LINE_MAX 2048
/* opaque tag state */
struct VGMSTREAM_TAGS {
/* extracted output */
char key[VGMSTREAM_TAGS_LINE_MAX];
char val[VGMSTREAM_TAGS_LINE_MAX];
/* file to find tags for */
int targetname_len;
char targetname[VGMSTREAM_TAGS_LINE_MAX];
/* path of targetname */
char targetpath[VGMSTREAM_TAGS_LINE_MAX];
/* tag section for filename (see comments below) */
int section_found;
off_t section_start;
off_t section_end;
off_t offset;
/* commands */
int autotrack_on;
int autotrack_written;
int track_count;
int exact_match;
int autoalbum_on;
int autoalbum_written;
};
static void tags_clean(VGMSTREAM_TAGS* tag) {
int i;
int val_len = strlen(tag->val);
/* remove trailing spaces */
for (i = val_len - 1; i > 0; i--) {
if (tag->val[i] != ' ')
break;
tag->val[i] = '\0';
}
}
VGMSTREAM_TAGS* vgmstream_tags_init(const char* *tag_key, const char* *tag_val) {
VGMSTREAM_TAGS* tags = malloc(sizeof(VGMSTREAM_TAGS));
if (!tags) goto fail;
*tag_key = tags->key;
*tag_val = tags->val;
return tags;
fail:
return NULL;
}
void vgmstream_tags_close(VGMSTREAM_TAGS *tags) {
free(tags);
}
/* Find next tag and return 1 if found.
*
* Tags can be "global" @TAGS, "command" $TAGS, and "file" %TAGS for a target filename.
* To extract tags we must find either global tags, or the filename's tag "section"
* where tags apply: (# @TAGS ) .. (other_filename) ..(# %TAGS section).. (target_filename).
* When a new "other_filename" is found that offset is marked as section_start, and when
* target_filename is found it's marked as section_end. Then we can begin extracting tags
* within that section, until all tags are exhausted. Global tags are extracted as found,
* so they always go first, also meaning any tags after file's section are ignored.
* Command tags have special meanings and are output after all section tags. */
int vgmstream_tags_next_tag(VGMSTREAM_TAGS* tags, STREAMFILE* tagfile) {
off_t file_size = get_streamfile_size(tagfile);
char currentname[VGMSTREAM_TAGS_LINE_MAX] = {0};
char line[VGMSTREAM_TAGS_LINE_MAX];
int ok, bytes_read, line_ok, n1,n2;
if (!tags)
return 0;
/* prepare file start and skip BOM if needed */
if (tags->offset == 0) {
if ((uint16_t)read_16bitLE(0x00, tagfile) == 0xFFFE ||
(uint16_t)read_16bitLE(0x00, tagfile) == 0xFEFF) {
tags->offset = 0x02;
if (tags->section_start == 0)
tags->section_start = 0x02;
}
else if (((uint32_t)read_32bitBE(0x00, tagfile) & 0xFFFFFF00) == 0xEFBBBF00) {
tags->offset = 0x03;
if (tags->section_start == 0)
tags->section_start = 0x03;
}
}
/* read lines */
while (tags->offset <= file_size) {
/* after section: no more tags to extract */
if (tags->section_found && tags->offset >= tags->section_end) {
/* write extra tags after all regular tags */
if (tags->autotrack_on && !tags->autotrack_written) {
sprintf(tags->key, "%s", "TRACK");
sprintf(tags->val, "%i", tags->track_count);
tags->autotrack_written = 1;
return 1;
}
if (tags->autoalbum_on && !tags->autoalbum_written && tags->targetpath[0] != '\0') {
const char* path;
path = strrchr(tags->targetpath,'\\');
if (!path) {
path = strrchr(tags->targetpath,'/');
}
if (!path) {
path = tags->targetpath;
}
sprintf(tags->key, "%s", "ALBUM");
sprintf(tags->val, "%s", path+1);
tags->autoalbum_written = 1;
return 1;
}
goto fail;
}
bytes_read = read_line(line, sizeof(line), tags->offset, tagfile, &line_ok);
if (!line_ok || bytes_read == 0) goto fail;
tags->offset += bytes_read;
if (tags->section_found) {
/* find possible file tag */
ok = sscanf(line, "# %%%[^%%]%% %[^\r\n] ", tags->key,tags->val); /* key with spaces */
if (ok != 2)
ok = sscanf(line, "# %%%[^ \t] %[^\r\n] ", tags->key,tags->val); /* key without */
if (ok == 2) {
tags_clean(tags);
return 1;
}
}
else {
if (line[0] == '#') {
/* find possible global command */
ok = sscanf(line, "# $%n%[^ \t]%n %[^\r\n]", &n1, tags->key, &n2, tags->val);
if (ok == 1 || ok == 2) {
int key_len = n2 - n1;
if (strncasecmp(tags->key, "AUTOTRACK", key_len) == 0) {
tags->autotrack_on = 1;
}
else if (strncasecmp(tags->key, "AUTOALBUM", key_len) == 0) {
tags->autoalbum_on = 1;
}
else if (strncasecmp(tags->key, "EXACTMATCH", key_len) == 0) {
tags->exact_match = 1;
}
continue; /* not an actual tag */
}
/* find possible global tag */
ok = sscanf(line, "# @%[^@]@ %[^\r\n]", tags->key, tags->val); /* key with spaces */
if (ok != 2)
ok = sscanf(line, "# @%[^ \t] %[^\r\n]", tags->key, tags->val); /* key without */
if (ok == 2) {
tags_clean(tags);
return 1;
}
continue; /* next line */
}
/* find possible filename and section start/end
* (.m3u seem to allow filenames with whitespaces before, make sure to trim) */
ok = sscanf(line, " %n%[^\r\n]%n ", &n1, currentname, &n2);
if (ok == 1) {
int currentname_len = n2 - n1;
int filename_found = 0;
/* we want to match file with the same name (case insensitive), OR a virtual .txtp with
* the filename inside to ease creation of tag files with config, also check end char to
* tell apart the unlikely case of having both 'bgm01.ad.txtp' and 'bgm01.adp.txtp' */
/* try exact match (strcasecmp works ok even for UTF-8) */
if (currentname_len == tags->targetname_len &&
strncasecmp(currentname, tags->targetname, currentname_len) == 0) {
filename_found = 1;
}
else if (!tags->exact_match) {
/* try tagfile is "bgm.adx" + target is "bgm.adx #(cfg) .txtp" */
if (currentname_len < tags->targetname_len &&
strncasecmp(currentname, tags->targetname, currentname_len) == 0 &&
vgmstream_is_virtual_filename(tags->targetname)) {
char c = tags->targetname[currentname_len];
filename_found = (c==' ' || c == '.' || c == '#');
}
/* tagfile has "bgm.adx (...) .txtp" + target has "bgm.adx" */
else if (tags->targetname_len < currentname_len &&
strncasecmp(tags->targetname, currentname, tags->targetname_len) == 0 &&
vgmstream_is_virtual_filename(currentname)) {
char c = currentname[tags->targetname_len];
filename_found = (c==' ' || c == '.' || c == '#');
}
}
if (filename_found) {
/* section ok, start would be set before this (or be 0) */
tags->section_end = tags->offset;
tags->section_found = 1;
tags->offset = tags->section_start;
}
else {
/* mark new possible section */
tags->section_start = tags->offset;
}
tags->track_count++; /* new track found (target filename or not) */
continue;
}
/* empty/bad line, probably */
}
}
/* may reach here if read up to file_size but no section was found */
fail:
tags->key[0] = '\0';
tags->val[0] = '\0';
return 0;
}
void vgmstream_tags_reset(VGMSTREAM_TAGS* tags, const char* target_filename) {
char *path;
if (!tags)
return;
memset(tags, 0, sizeof(VGMSTREAM_TAGS));
//todo validate sizes and copy sensible max
/* get base name */
strcpy(tags->targetpath, target_filename);
/* Windows CMD accepts both \\ and /, and maybe plugin uses either */
path = strrchr(tags->targetpath,'\\');
if (!path) {
path = strrchr(tags->targetpath,'/');
}
if (path != NULL) {
path[0] = '\0'; /* leave targetpath with path only */
path = path+1;
}
if (path) {
strcpy(tags->targetname, path);
} else {
tags->targetpath[0] = '\0';
strcpy(tags->targetname, target_filename);
}
tags->targetname_len = strlen(tags->targetname);
}
/* ****************************************** */
/* MIXING: modifies vgmstream output */
/* ****************************************** */
void vgmstream_mixing_enable(VGMSTREAM* vgmstream, int32_t max_sample_count, int *input_channels, int *output_channels) {
void vgmstream_mixing_enable(VGMSTREAM* vgmstream, int32_t max_sample_count, int* input_channels, int* output_channels) {
mixing_setup(vgmstream, max_sample_count);
mixing_info(vgmstream, input_channels, output_channels);
@ -531,7 +165,7 @@ void vgmstream_mixing_enable(VGMSTREAM* vgmstream, int32_t max_sample_count, int
setup_vgmstream(vgmstream);
}
void vgmstream_mixing_autodownmix(VGMSTREAM *vgmstream, int max_channels) {
void vgmstream_mixing_autodownmix(VGMSTREAM* vgmstream, int max_channels) {
if (max_channels <= 0)
return;
@ -547,7 +181,7 @@ void vgmstream_mixing_autodownmix(VGMSTREAM *vgmstream, int max_channels) {
return;
}
void vgmstream_mixing_stereo_only(VGMSTREAM *vgmstream, int start) {
void vgmstream_mixing_stereo_only(VGMSTREAM* vgmstream, int start) {
if (start < 0)
return;
/* could check to avoid making mono files in edge cases but meh */

View file

@ -13,6 +13,14 @@
#endif
/* List supported formats and return elements in the list, for plugins that need to know.
* The list disables some common formats that may conflict (.wav, .ogg, etc). */
const char** vgmstream_get_formats(size_t* size);
/* same, but for common-but-disabled formats in the above list. */
const char** vgmstream_get_common_formats(size_t* size);
/* ****************************************** */
/* CONTEXT: simplifies plugin code */
/* ****************************************** */

View file

@ -223,6 +223,8 @@ void setup_state_vgmstream(VGMSTREAM* vgmstream) {
/*****************************************************************************/
void render_free(VGMSTREAM* vgmstream) {
if (!vgmstream->layout_data)
return;
if (vgmstream->layout_type == layout_segmented) {
free_layout_segmented(vgmstream->layout_data);
@ -281,7 +283,7 @@ int render_layout(sample_t* buf, int32_t sample_count, VGMSTREAM* vgmstream) {
case layout_blocked_xvas:
case layout_blocked_thp:
case layout_blocked_filp:
case layout_blocked_ivaud:
case layout_blocked_rage_aud:
case layout_blocked_ea_swvr:
case layout_blocked_adm:
case layout_blocked_ps2_iab:
@ -302,6 +304,7 @@ int render_layout(sample_t* buf, int32_t sample_count, VGMSTREAM* vgmstream) {
case layout_blocked_vid1:
case layout_blocked_ubi_sce:
case layout_blocked_tt_ad:
case layout_blocked_vas:
render_vgmstream_blocked(buf, sample_count, vgmstream);
break;
case layout_segmented:

View file

@ -0,0 +1,28 @@
#ifndef _SBUF_H
#define _SBUF_H
#include "../streamtypes.h"
// TODO decide if using float 1.0 style or 32767 style (fuzzy PCM changes when doing that)
static inline 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
}
}
static inline 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
}
}
#endif

View file

@ -5,13 +5,13 @@
#include "mixing.h"
#include "plugins.h"
static void seek_force_loop(VGMSTREAM* vgmstream, int loop_count) {
/* pretend decoder reached loop end so internal state is set like jumping to loop start
* (no effect in some layouts but that is ok) */
static void seek_force_loop_end(VGMSTREAM* vgmstream, int loop_count) {
/* only called after hit loop */
if (!vgmstream->hit_loop)
return;
/* pretend decoder reached loop end so state is set to loop start */
vgmstream->loop_count = loop_count - 1; /* seeking to first loop must become ++ > 0 */
vgmstream->current_sample = vgmstream->loop_end_sample;
decode_do_loop(vgmstream);
@ -33,45 +33,148 @@ static void seek_force_decode(VGMSTREAM* vgmstream, int samples) {
}
}
static void seek_body(VGMSTREAM* vgmstream, int32_t seek_sample) {
//;VGM_LOG("SEEK: body / seekr=%i, curr=%i\n", seek_sample, vgmstream->current_sample);
int32_t decode_samples;
play_state_t* ps = &vgmstream->pstate;
bool is_looped = vgmstream->loop_flag || vgmstream->loop_target > 0; /* loop target disabled loop flag during decode */
bool is_config = vgmstream->config_enabled;
int play_forever = vgmstream->config.play_forever;
/* seek=10 would be seekr=10-5+3=8 inside decoder */
int32_t seek_relative = seek_sample - ps->pad_begin_duration + ps->trim_begin_duration;
/* seek can be in some part of the body, depending on looping/decoder's current position/etc */
if (!is_looped && seek_relative < vgmstream->current_sample) {
/* non-looped seek before decoder's position: restart + consume (seekr=50s, curr=95 > restart + decode=50s) */
decode_samples = seek_relative;
reset_vgmstream(vgmstream);
//;VGM_LOG("SEEK: non-loop reset / dec=%i\n", decode_samples);
}
else if (!is_looped && seek_relative < vgmstream->num_samples) {
/* non-looped seek after decoder's position: consume (seekr=95s, curr=50 > decode=95-50=45s) */
decode_samples = seek_relative - vgmstream->current_sample;
//;VGM_LOG("SEEK: non-loop forward / dec=%i\n", decode_samples);
}
else if (!is_looped) {
/* after num_samples, can happen when body is set manually (seekr=120s) */
decode_samples = 0;
vgmstream->current_sample = vgmstream->num_samples + 1;
//;VGM_LOG("SEEK: non-loop silence / dec=%i\n", decode_samples);
}
else if (seek_relative < vgmstream->loop_start_sample) {
/* looped seek before decoder's position: restart + consume or just consume */
if (seek_relative < vgmstream->current_sample) {
/* seekr=9s, current=10s > decode=9s from start */
decode_samples = seek_relative;
reset_vgmstream(vgmstream);
//;VGM_LOG("SEEK: loop start reset / dec=%i\n", decode_samples);
}
else {
/* seekr=9s, current=8s > decode=1s from current */
decode_samples = seek_relative - vgmstream->current_sample;
//;VGM_LOG("SEEK: loop start forward / dec=%i\n", decode_samples);
}
}
else {
/* looped seek after loop start: can be clamped between loop parts (relative to decoder's current_sample) to minimize decoding */
//int32_t loop_outr = (vgmstream->num_samples - vgmstream->loop_end_sample);
int32_t loop_part = (vgmstream->loop_end_sample - vgmstream->loop_start_sample); /* samples of 1 looped part */
int32_t loop_seek = (seek_relative - vgmstream->loop_start_sample); /* samples within loop region */
int loop_count = loop_seek / loop_part; /* not accurate when loop_target is set */
loop_seek = loop_seek % loop_part; /* clamp within single loop after calcs */
/* current must have reached loop start at some point, otherwise force it (NOTE: some layouts don't actually set hit_loop) */
if (!vgmstream->hit_loop) {
if (vgmstream->current_sample > vgmstream->loop_start_sample) { /* may be 0 */
VGM_LOG("SEEK: bad current sample %i vs %i\n", vgmstream->current_sample, vgmstream->loop_start_sample);
reset_vgmstream(vgmstream);
}
int32_t skip_samples = (vgmstream->loop_start_sample - vgmstream->current_sample);
//;VGM_LOG("SEEK: force loop region / skip=%i, curr=%i\n", skip_samples, vgmstream->current_sample);
seek_force_decode(vgmstream, skip_samples);
}
/* current must be in loop area (may happen at start since it's smaller than loop_end) */
if (vgmstream->current_sample < vgmstream->loop_start_sample
|| vgmstream->current_sample < vgmstream->loop_end_sample) {
//;VGM_LOG("SEEK: outside loop region / curr=%i, ls=%i, le=%i\n", vgmstream->current_sample, vgmstream->current_sample, vgmstream->loop_end_sample);
seek_force_loop_end(vgmstream, 0);
}
//;VGM_LOG("SEEK: in loop region / seekr=%i, seekl=%i, loops=%i, dec_curr=%i\n", seek_relative, loop_seek, loop_count, loop_curr);
/* when "ignore fade" is set and seek falls into the outro part (loop count if bigged than expected), adjust seek
* to do a whole part + outro samples (should probably calculate correct loop_count before but...) */
if (vgmstream->loop_target && loop_count >= vgmstream->loop_target) {
loop_seek = loop_part + (seek_relative - vgmstream->loop_start_sample) - vgmstream->loop_target * loop_part;
loop_count = vgmstream->loop_target - 1; /* so seek_force_loop_end detection kicks in and adds +1 */
//;VGM_LOG("SEEK: outro outside / seek=%i, count=%i\n", decode_samples, loop_seek, loop_count);
}
int32_t loop_curr = vgmstream->current_sample - vgmstream->loop_start_sample;
if (loop_seek < loop_curr) {
decode_samples = loop_seek;
seek_force_loop_end(vgmstream, loop_count);
//;VGM_LOG("SEEK: loop reset / dec=%i, loop=%i\n", decode_samples, loop_count);
}
else {
decode_samples = (loop_seek - loop_curr);
vgmstream->loop_count = loop_count;
//;VGM_LOG("SEEK: loop forward / dec=%i, loop=%i\n", decode_samples, loop_count);
}
/* adjust fade if seek ends in fade region */
if (is_config && !play_forever
&& seek_sample >= ps->pad_begin_duration + ps->body_duration
&& seek_sample < ps->pad_begin_duration + ps->body_duration + ps->fade_duration) {
ps->fade_left = ps->pad_begin_duration + ps->body_duration + ps->fade_duration - seek_sample;
//;VGM_LOG("SEEK: in fade / fade=%i, %i\n", ps->fade_left, ps->fade_duration);
}
}
seek_force_decode(vgmstream, decode_samples);
//;VGM_LOG("SEEK: decode=%i, current=%i\n", decode_samples, vgmstream->current_sample);
}
void seek_vgmstream(VGMSTREAM* vgmstream, int32_t seek_sample) {
play_state_t* ps = &vgmstream->pstate;
int play_forever = vgmstream->config.play_forever;
int32_t decode_samples = 0;
int loop_count = -1;
int is_looped = vgmstream->loop_flag || vgmstream->loop_target > 0; /* loop target disabled loop flag during decode */
bool is_looped = vgmstream->loop_flag || vgmstream->loop_target > 0; /* loop target disabled loop flag during decode */
bool is_config = vgmstream->config_enabled;
/* cleanup */
if (seek_sample < 0)
seek_sample = 0;
/* play forever can seek past max */
if (vgmstream->config_enabled && seek_sample > ps->play_duration && !play_forever)
if (is_config && seek_sample > ps->play_duration && !play_forever)
seek_sample = ps->play_duration;
#if 0 //todo move below, needs to clamp in decode part
/* optimize as layouts can seek faster internally */
if (vgmstream->layout_type == layout_segmented) {
seek_layout_segmented(vgmstream, seek_sample);
if (vgmstream->config_enabled) {
vgmstream->pstate.play_position = seek_sample;
}
return;
}
else if (vgmstream->layout_type == layout_layered) {
seek_layout_layered(vgmstream, seek_sample);
if (vgmstream->config_enabled) {
vgmstream->pstate.play_position = seek_sample;
}
return;
}
#endif
/* will decode and loop until seek sample, but slower */
//todo apply same loop logic as below, or pretend we have play_forever + settings?
if (!vgmstream->config_enabled) {
if (!is_config) {
int32_t decode_samples;
//;VGM_LOG("SEEK: simple seek=%i, cur=%i\n", seek_sample, vgmstream->current_sample);
if (seek_sample < vgmstream->current_sample) {
decode_samples = seek_sample;
@ -104,142 +207,49 @@ void seek_vgmstream(VGMSTREAM* vgmstream, int32_t seek_sample) {
//;VGM_LOG("SEEK: seek sample=%i, is_looped=%i\n", seek_sample, is_looped);
/* start/pad-begin: consume pad samples */
if (seek_sample < ps->pad_begin_duration) {
if (is_config && seek_sample < ps->pad_begin_duration) {
/* seek=3: pad=5-3=2 */
decode_samples = 0;
reset_vgmstream(vgmstream);
ps->pad_begin_left = ps->pad_begin_duration - seek_sample;
//;VGM_LOG("SEEK: pad start / dec=%i\n", decode_samples);
}
/* body: find position relative to decoder's current sample */
else if (play_forever || seek_sample < ps->pad_begin_duration + ps->body_duration + ps->fade_duration) {
/* seek=10 would be seekr=10-5+3=8 inside decoder */
int32_t seek_relative = seek_sample - ps->pad_begin_duration + ps->trim_begin_duration;
//;VGM_LOG("SEEK: body / seekr=%i, curr=%i\n", seek_relative, vgmstream->current_sample);
/* seek can be in some part of the body, depending on looped/decoder's current/etc */
if (!is_looped && seek_relative < vgmstream->current_sample) {
/* seekr=50s, curr=95 > restart + decode=50s */
decode_samples = seek_relative;
reset_vgmstream(vgmstream);
//;VGM_LOG("SEEK: non-loop reset / dec=%i\n", decode_samples);
}
else if (!is_looped && seek_relative < vgmstream->num_samples) {
/* seekr=95s, curr=50 > decode=95-50=45s */
decode_samples = seek_relative - vgmstream->current_sample;
//;VGM_LOG("SEEK: non-loop forward / dec=%i\n", decode_samples);
}
else if (!is_looped) {
/* seekr=120s (outside decode, can happen when body is set manually) */
decode_samples = 0;
vgmstream->current_sample = vgmstream->num_samples + 1;
//;VGM_LOG("SEEK: non-loop silence / dec=%i\n", decode_samples);
}
else if (seek_relative < vgmstream->loop_start_sample) {
/* seekr=6s > 6-5+3 > seek=4s inside decoder < 20s: decode 4s from start, or 1s if current was at 3s */
if (seek_relative < vgmstream->current_sample) {
/* seekr=9s, current=10s > decode=9s from start */
decode_samples = seek_relative;
reset_vgmstream(vgmstream);
//;VGM_LOG("SEEK: loop start reset / dec=%i\n", decode_samples);
}
else {
/* seekr=9s, current=8s > decode=1s from current */
decode_samples = seek_relative - vgmstream->current_sample;
//;VGM_LOG("SEEK: loop start forward / dec=%i\n", decode_samples);
}
}
else {
/* seek can be clamped between loop parts (relative to decoder's current_sample) to minimize decoding */
int32_t loop_body, loop_seek, loop_curr;
/* current must have reached loop start at some point */
if (!vgmstream->hit_loop) {
int32_t skip_samples;
if (vgmstream->current_sample >= vgmstream->loop_start_sample) {
VGM_LOG("SEEK: bad current sample %i vs %i\n", vgmstream->current_sample, vgmstream->loop_start_sample);
reset_vgmstream(vgmstream);
}
skip_samples = (vgmstream->loop_start_sample - vgmstream->current_sample);
//;VGM_LOG("SEEK: must loop / skip=%i, curr=%i\n", skip_samples, vgmstream->current_sample);
seek_force_decode(vgmstream, skip_samples);
}
/* current must be in loop area (shouldn't happen?) */
if (vgmstream->current_sample < vgmstream->loop_start_sample
|| vgmstream->current_sample < vgmstream->loop_end_sample) {
//;VGM_LOG("SEEK: current outside loop area / curr=%i, ls=%i, le=%i\n", vgmstream->current_sample, vgmstream->current_sample, vgmstream->loop_end_sample);
seek_force_loop(vgmstream, 0);
}
loop_body = (vgmstream->loop_end_sample - vgmstream->loop_start_sample);
loop_seek = seek_relative - vgmstream->loop_start_sample;
loop_count = loop_seek / loop_body;
loop_seek = loop_seek % loop_body;
loop_curr = vgmstream->current_sample - vgmstream->loop_start_sample;
/* when "ignore fade" is used and seek falls into non-fade part, this needs to seek right before it
so when calling seek_force_loop detection kicks in, and non-fade then decodes normally */
if (vgmstream->loop_target && vgmstream->loop_target == loop_count) {
loop_seek = loop_body;
}
//;VGM_LOG("SEEK: in loop / seekl=%i, loops=%i, cur=%i, dec=%i\n", loop_seek, loop_count, loop_curr, decode_samples);
if (loop_seek < loop_curr) {
decode_samples += loop_seek;
seek_force_loop(vgmstream, loop_count);
//;VGM_LOG("SEEK: loop reset / dec=%i, loop=%i\n", decode_samples, loop_count);
}
else {
decode_samples += (loop_seek - loop_curr);
//;VGM_LOG("SEEK: loop forward / dec=%i, loop=%i\n", decode_samples, loop_count);
}
/* adjust fade if seek ends in fade region */
if (!play_forever
&& seek_sample >= ps->pad_begin_duration + ps->body_duration
&& seek_sample < ps->pad_begin_duration + ps->body_duration + ps->fade_duration) {
ps->fade_left = ps->pad_begin_duration + ps->body_duration + ps->fade_duration - seek_sample;
//;VGM_LOG("SEEK: in fade / fade=%i, %i\n", ps->fade_left, ps->fade_duration);
}
}
/* done at the end in case of reset */
ps->pad_begin_left = 0;
ps->trim_begin_left = 0;
//;VGM_LOG("SEEK: pad start / dec=%i\n", 0);
}
/* pad end and beyond: ignored */
else {
decode_samples = 0;
else if (is_config && !play_forever && seek_sample >= ps->pad_begin_duration + ps->body_duration + ps->fade_duration) {
ps->pad_begin_left = 0;
ps->trim_begin_left = 0;
if (!is_looped)
vgmstream->current_sample = vgmstream->num_samples + 1;
//;VGM_LOG("SEEK: end silence / dec=%i\n", decode_samples);
//;VGM_LOG("SEEK: end silence / dec=%i\n", 0);
/* looping decoder state isn't changed (seek backwards could use current sample) */
}
/* body: seek relative to decoder's current sample */
else {
#if 0
//TODO calculate samples into loop number N, and into fade region (segmented layout can only seek to loop start)
seek_force_decode(vgmstream, decode_samples);
/* optimize as layouts can seek faster internally */
if (vgmstream->layout_type == layout_segmented) {
seek_layout_segmented(vgmstream, seek_sample);
}
else if (vgmstream->layout_type == layout_layered) {
seek_layout_layered(vgmstream, seek_sample);
}
else
#endif
seek_body(vgmstream, seek_sample);
vgmstream->pstate.play_position = seek_sample;
/* done at the end in case of reset (that restores these values) */
if (is_config) {
ps->pad_begin_left = 0;
ps->trim_begin_left = 0;
}
}
if (is_config)
vgmstream->pstate.play_position = seek_sample;
}

View file

@ -0,0 +1,94 @@
#include "api_internal.h"
#if LIBVGMSTREAM_ENABLE
/* STREAMFILE for internal use, that bridges calls to external libvgmstream_streamfile_t */
static STREAMFILE* open_api_streamfile_internal(libvgmstream_streamfile_t* libsf, bool external_libsf);
typedef struct {
STREAMFILE vt;
libvgmstream_streamfile_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);
return sf->libsf->read(user_data, dst, length);
}
static size_t api_get_size(API_STREAMFILE* sf) {
void* user_data = sf->libsf->user_data;
return sf->libsf->get_size(user_data);
}
static offv_t api_get_offset(API_STREAMFILE* sf) {
return 0; //sf->libsf->get_offset(sf->inner_sf); /* default */
}
static void api_get_name(API_STREAMFILE* sf, char* name, size_t name_size) {
void* user_data = sf->libsf->user_data;
if (!name || !name_size)
return;
name[0] = '\0';
const char* external_name = sf->libsf->get_name(user_data);
if (!external_name)
return;
snprintf(name, name_size, "%s", external_name);
name[name_size - 1] = '\0';
}
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);
STREAMFILE* new_sf = open_api_streamfile_internal(libsf, false);
if (!new_sf) {
libvgmstream_streamfile_close(libsf);
}
return new_sf;
}
static void api_close(API_STREAMFILE* sf) {
if (sf && !sf->external_libsf) {
sf->libsf->close(sf->libsf);
}
free(sf);
}
static STREAMFILE* open_api_streamfile_internal(libvgmstream_streamfile_t* libsf, bool external_libsf) {
API_STREAMFILE* this_sf = NULL;
if (!libsf)
return NULL;
this_sf = calloc(1, sizeof(API_STREAMFILE));
if (!this_sf) return NULL;
/* set callbacks and internals */
this_sf->vt.read = (void*)api_read;
this_sf->vt.get_size = (void*)api_get_size;
this_sf->vt.get_offset = (void*)api_get_offset;
this_sf->vt.get_name = (void*)api_get_name;
this_sf->vt.open = (void*)api_open;
this_sf->vt.close = (void*)api_close;
//this_sf->vt.stream_index = sf->stream_index;
this_sf->libsf = libsf;
this_sf->external_libsf = external_libsf;
return &this_sf->vt;
}
STREAMFILE* open_api_streamfile(libvgmstream_streamfile_t* libsf) {
return open_api_streamfile_internal(libsf, true);
}
#endif

View file

@ -0,0 +1,150 @@
#include "../streamfile.h"
#include "../util/log.h"
typedef struct {
STREAMFILE vt;
STREAMFILE* inner_sf;
offv_t offset; /* last read offset (info) */
offv_t buf_offset; /* current buffer data start */
uint8_t* buf; /* data buffer */
size_t buf_size; /* max buffer size */
size_t valid_size; /* current buffer size */
size_t file_size; /* buffered file size */
} BUFFER_STREAMFILE;
static size_t buffer_read(BUFFER_STREAMFILE* sf, uint8_t* dst, offv_t offset, size_t length) {
size_t read_total = 0;
if (!dst || length <= 0 || offset < 0)
return 0;
/* is the part of the requested length in the buffer? */
if (offset >= sf->buf_offset && offset < sf->buf_offset + sf->valid_size) {
size_t buf_limit;
int buf_into = (int)(offset - sf->buf_offset);
buf_limit = sf->valid_size - buf_into;
if (buf_limit > length)
buf_limit = length;
memcpy(dst, sf->buf + buf_into, buf_limit);
read_total += buf_limit;
length -= buf_limit;
offset += buf_limit;
dst += buf_limit;
}
#ifdef VGM_DEBUG_OUTPUT
if (offset < sf->buf_offset) {
//VGM_LOG("buffer: rebuffer, requested %x vs %x (sf %x)\n", (uint32_t)offset, (uint32_t)sf->buf_offset, (uint32_t)sf);
}
#endif
/* read the rest of the requested length */
while (length > 0) {
size_t buf_limit;
/* ignore requests at EOF */
if (offset >= sf->file_size) {
//offset = sf->file_size; /* seems fseek doesn't clamp offset */
VGM_ASSERT_ONCE(offset > sf->file_size, "buffer: reading over file_size 0x%x @ 0x%x + 0x%x\n", sf->file_size, (uint32_t)offset, length);
break;
}
/* fill the buffer (offset now is beyond buf_offset) */
sf->buf_offset = offset;
sf->valid_size = sf->inner_sf->read(sf->inner_sf, sf->buf, sf->buf_offset, sf->buf_size);
/* decide how much must be read this time */
if (length > sf->buf_size)
buf_limit = sf->buf_size;
else
buf_limit = length;
/* give up on partial reads (EOF) */
if (sf->valid_size < buf_limit) {
memcpy(dst, sf->buf, sf->valid_size);
offset += sf->valid_size;
read_total += sf->valid_size;
break;
}
/* use the new buffer */
memcpy(dst, sf->buf, buf_limit);
offset += buf_limit;
read_total += buf_limit;
length -= buf_limit;
dst += buf_limit;
}
sf->offset = offset; /* last fread offset */
return read_total;
}
static size_t buffer_get_size(BUFFER_STREAMFILE* sf) {
return sf->file_size; /* cache */
}
static offv_t buffer_get_offset(BUFFER_STREAMFILE* sf) {
return sf->offset; /* cache */
}
static void buffer_get_name(BUFFER_STREAMFILE* sf, char* name, size_t name_size) {
sf->inner_sf->get_name(sf->inner_sf, name, name_size); /* default */
}
static STREAMFILE* buffer_open(BUFFER_STREAMFILE* sf, const char* const filename, size_t buf_size) {
STREAMFILE* new_inner_sf = sf->inner_sf->open(sf->inner_sf,filename,buf_size);
return open_buffer_streamfile(new_inner_sf, buf_size); /* original buffer size is preferable? */
}
static void buffer_close(BUFFER_STREAMFILE* sf) {
sf->inner_sf->close(sf->inner_sf);
free(sf->buf);
free(sf);
}
STREAMFILE* open_buffer_streamfile(STREAMFILE* sf, size_t buf_size) {
BUFFER_STREAMFILE* this_sf = NULL;
if (!sf) goto fail;
if (buf_size == 0)
buf_size = STREAMFILE_DEFAULT_BUFFER_SIZE;
this_sf = calloc(1, sizeof(BUFFER_STREAMFILE));
if (!this_sf) goto fail;
/* set callbacks and internals */
this_sf->vt.read = (void*)buffer_read;
this_sf->vt.get_size = (void*)buffer_get_size;
this_sf->vt.get_offset = (void*)buffer_get_offset;
this_sf->vt.get_name = (void*)buffer_get_name;
this_sf->vt.open = (void*)buffer_open;
this_sf->vt.close = (void*)buffer_close;
this_sf->vt.stream_index = sf->stream_index;
this_sf->inner_sf = sf;
this_sf->buf_size = buf_size;
this_sf->buf = calloc(buf_size, sizeof(uint8_t));
if (!this_sf->buf) goto fail;
this_sf->file_size = sf->get_size(sf);
return &this_sf->vt;
fail:
if (this_sf) free(this_sf->buf);
free(this_sf);
return NULL;
}
STREAMFILE* open_buffer_streamfile_f(STREAMFILE* sf, size_t buffer_size) {
STREAMFILE* new_sf = open_buffer_streamfile(sf, buffer_size);
if (!new_sf)
close_streamfile(sf);
return new_sf;
}

View file

@ -0,0 +1,89 @@
#include "../streamfile.h"
#include "../util/vgmstream_limits.h"
typedef struct {
STREAMFILE vt;
STREAMFILE* inner_sf;
offv_t start;
size_t size;
} CLAMP_STREAMFILE;
static size_t clamp_read(CLAMP_STREAMFILE* sf, uint8_t* dst, offv_t offset, size_t length) {
offv_t inner_offset = sf->start + offset;
size_t clamp_length = length;
if (offset + length > sf->size) {
if (offset >= sf->size)
clamp_length = 0;
else
clamp_length = sf->size - offset;
}
return sf->inner_sf->read(sf->inner_sf, dst, inner_offset, clamp_length);
}
static size_t clamp_get_size(CLAMP_STREAMFILE* sf) {
return sf->size;
}
static offv_t clamp_get_offset(CLAMP_STREAMFILE* sf) {
return sf->inner_sf->get_offset(sf->inner_sf) - sf->start;
}
static void clamp_get_name(CLAMP_STREAMFILE* sf, char* name, size_t name_size) {
sf->inner_sf->get_name(sf->inner_sf, name, name_size); /* default */
}
static STREAMFILE* clamp_open(CLAMP_STREAMFILE* sf, const char* const filename, size_t buf_size) {
char original_filename[PATH_LIMIT];
STREAMFILE* new_inner_sf = NULL;
new_inner_sf = sf->inner_sf->open(sf->inner_sf,filename,buf_size);
sf->inner_sf->get_name(sf->inner_sf, original_filename, PATH_LIMIT);
/* detect re-opening the file */
if (strcmp(filename, original_filename) == 0) {
return open_clamp_streamfile(new_inner_sf, sf->start, sf->size); /* clamp again */
} else {
return new_inner_sf;
}
}
static void clamp_close(CLAMP_STREAMFILE* sf) {
sf->inner_sf->close(sf->inner_sf);
free(sf);
}
STREAMFILE* open_clamp_streamfile(STREAMFILE* sf, offv_t start, size_t size) {
CLAMP_STREAMFILE* this_sf = NULL;
if (!sf || size == 0) return NULL;
if (start + size > get_streamfile_size(sf)) return NULL;
this_sf = calloc(1, sizeof(CLAMP_STREAMFILE));
if (!this_sf) return NULL;
/* set callbacks and internals */
this_sf->vt.read = (void*)clamp_read;
this_sf->vt.get_size = (void*)clamp_get_size;
this_sf->vt.get_offset = (void*)clamp_get_offset;
this_sf->vt.get_name = (void*)clamp_get_name;
this_sf->vt.open = (void*)clamp_open;
this_sf->vt.close = (void*)clamp_close;
this_sf->vt.stream_index = sf->stream_index;
this_sf->inner_sf = sf;
this_sf->start = start;
this_sf->size = size;
return &this_sf->vt;
}
STREAMFILE* open_clamp_streamfile_f(STREAMFILE* sf, offv_t start, size_t size) {
STREAMFILE* new_sf = open_clamp_streamfile(sf, start, size);
if (!new_sf)
close_streamfile(sf);
return new_sf;
}

View file

@ -0,0 +1,99 @@
#include "../streamfile.h"
#include "../util/vgmstream_limits.h"
typedef struct {
STREAMFILE vt;
STREAMFILE* inner_sf;
char fakename[PATH_LIMIT];
int fakename_len;
} FAKENAME_STREAMFILE;
static size_t fakename_read(FAKENAME_STREAMFILE* sf, uint8_t* dst, offv_t offset, size_t length) {
return sf->inner_sf->read(sf->inner_sf, dst, offset, length); /* default */
}
static size_t fakename_get_size(FAKENAME_STREAMFILE* sf) {
return sf->inner_sf->get_size(sf->inner_sf); /* default */
}
static offv_t fakename_get_offset(FAKENAME_STREAMFILE* sf) {
return sf->inner_sf->get_offset(sf->inner_sf); /* default */
}
static void fakename_get_name(FAKENAME_STREAMFILE* sf, char* name, size_t name_size) {
int copy_size = sf->fakename_len + 1;
if (copy_size > name_size)
copy_size = name_size;
memcpy(name, sf->fakename, copy_size);
name[copy_size - 1] = '\0';
}
static STREAMFILE* fakename_open(FAKENAME_STREAMFILE* sf, const char* const filename, size_t buf_size) {
/* detect re-opening the file */
if (strcmp(filename, sf->fakename) == 0) {
STREAMFILE* new_inner_sf;
char original_filename[PATH_LIMIT];
sf->inner_sf->get_name(sf->inner_sf, original_filename, PATH_LIMIT);
new_inner_sf = sf->inner_sf->open(sf->inner_sf, original_filename, buf_size);
return open_fakename_streamfile(new_inner_sf, sf->fakename, NULL);
}
else {
return sf->inner_sf->open(sf->inner_sf, filename, buf_size);
}
}
static void fakename_close(FAKENAME_STREAMFILE* sf) {
sf->inner_sf->close(sf->inner_sf);
free(sf);
}
STREAMFILE* open_fakename_streamfile(STREAMFILE* sf, const char* fakename, const char* fakeext) {
FAKENAME_STREAMFILE* this_sf = NULL;
if (!sf || (!fakename && !fakeext)) return NULL;
this_sf = calloc(1, sizeof(FAKENAME_STREAMFILE));
if (!this_sf) return NULL;
/* set callbacks and internals */
this_sf->vt.read = (void*)fakename_read;
this_sf->vt.get_size = (void*)fakename_get_size;
this_sf->vt.get_offset = (void*)fakename_get_offset;
this_sf->vt.get_name = (void*)fakename_get_name;
this_sf->vt.open = (void*)fakename_open;
this_sf->vt.close = (void*)fakename_close;
this_sf->vt.stream_index = sf->stream_index;
this_sf->inner_sf = sf;
/* copy passed name or retain current, and swap extension if expected */
if (fakename) {
strcpy(this_sf->fakename, fakename);
} else {
sf->get_name(sf, this_sf->fakename, PATH_LIMIT);
}
if (fakeext) {
char* ext = strrchr(this_sf->fakename, '.');
if (ext != NULL) {
ext[1] = '\0'; /* truncate past dot */
} else {
strcat(this_sf->fakename, "."); /* no extension = add dot */
}
strcat(this_sf->fakename, fakeext);
}
this_sf->fakename_len = strlen(this_sf->fakename);
return &this_sf->vt;
}
STREAMFILE* open_fakename_streamfile_f(STREAMFILE* sf, const char* fakename, const char* fakeext) {
STREAMFILE* new_sf = open_fakename_streamfile(sf, fakename, fakeext);
if (!new_sf)
close_streamfile(sf);
return new_sf;
}

View file

@ -0,0 +1,104 @@
#include "../streamfile.h"
typedef struct {
STREAMFILE vt;
STREAMFILE* inner_sf;
void* data; /* state for custom reads, malloc'ed + copied on open (to re-open streamfiles cleanly) */
size_t data_size;
size_t (*read_callback)(STREAMFILE*, uint8_t*, off_t, size_t, void*); /* custom read to modify data before copying into buffer */
size_t (*size_callback)(STREAMFILE*, void*); /* size when custom reads make data smaller/bigger than underlying streamfile */
int (*init_callback)(STREAMFILE*, void*); /* init the data struct members somehow, return >= 0 if ok */
void (*close_callback)(STREAMFILE*, void*); /* close the data struct members somehow */
/* read doesn't use offv_t since callbacks would need to be modified */
} IO_STREAMFILE;
static size_t io_read(IO_STREAMFILE* sf, uint8_t* dst, offv_t offset, size_t length) {
return sf->read_callback(sf->inner_sf, dst, (off_t)offset, length, sf->data);
}
static size_t io_get_size(IO_STREAMFILE* sf) {
if (sf->size_callback)
return sf->size_callback(sf->inner_sf, sf->data);
else
return sf->inner_sf->get_size(sf->inner_sf); /* default */
}
static offv_t io_get_offset(IO_STREAMFILE* sf) {
return sf->inner_sf->get_offset(sf->inner_sf); /* default */
}
static void io_get_name(IO_STREAMFILE* sf, char* name, size_t name_size) {
sf->inner_sf->get_name(sf->inner_sf, name, name_size); /* default */
}
static STREAMFILE* io_open(IO_STREAMFILE* sf, const char* const filename, size_t buf_size) {
STREAMFILE* new_inner_sf = sf->inner_sf->open(sf->inner_sf,filename,buf_size);
return open_io_streamfile_ex(new_inner_sf, sf->data, sf->data_size, sf->read_callback, sf->size_callback, sf->init_callback, sf->close_callback);
}
static void io_close(IO_STREAMFILE* sf) {
if (sf->close_callback)
sf->close_callback(sf->inner_sf, sf->data);
sf->inner_sf->close(sf->inner_sf);
free(sf->data);
free(sf);
}
STREAMFILE* open_io_streamfile_ex(STREAMFILE* sf, void* data, size_t data_size, void* read_callback, void* size_callback, void* init_callback, void* close_callback) {
IO_STREAMFILE* this_sf = NULL;
if (!sf) goto fail;
if ((data && !data_size) || (!data && data_size)) goto fail;
this_sf = calloc(1, sizeof(IO_STREAMFILE));
if (!this_sf) goto fail;
/* set callbacks and internals */
this_sf->vt.read = (void*)io_read;
this_sf->vt.get_size = (void*)io_get_size;
this_sf->vt.get_offset = (void*)io_get_offset;
this_sf->vt.get_name = (void*)io_get_name;
this_sf->vt.open = (void*)io_open;
this_sf->vt.close = (void*)io_close;
this_sf->vt.stream_index = sf->stream_index;
this_sf->inner_sf = sf;
if (data) {
this_sf->data = malloc(data_size);
if (!this_sf->data) goto fail;
memcpy(this_sf->data, data, data_size);
}
this_sf->data_size = data_size;
this_sf->read_callback = read_callback;
this_sf->size_callback = size_callback;
this_sf->init_callback = init_callback;
this_sf->close_callback = close_callback;
if (this_sf->init_callback) {
int ok = this_sf->init_callback(this_sf->inner_sf, this_sf->data);
if (ok < 0) goto fail;
}
return &this_sf->vt;
fail:
if (this_sf) free(this_sf->data);
free(this_sf);
return NULL;
}
STREAMFILE* open_io_streamfile_ex_f(STREAMFILE* sf, void* data, size_t data_size, void* read_callback, void* size_callback, void* init_callback, void* close_callback) {
STREAMFILE* new_sf = open_io_streamfile_ex(sf, data, data_size, read_callback, size_callback, init_callback, close_callback);
if (!new_sf)
close_streamfile(sf);
return new_sf;
}
STREAMFILE* open_io_streamfile(STREAMFILE* sf, void* data, size_t data_size, void* read_callback, void* size_callback) {
return open_io_streamfile_ex(sf, data, data_size, read_callback, size_callback, NULL, NULL);
}
STREAMFILE* open_io_streamfile_f(STREAMFILE* sf, void* data, size_t data_size, void* read_callback, void* size_callback) {
return open_io_streamfile_ex_f(sf, data, data_size, read_callback, size_callback, NULL, NULL);
}

View file

@ -0,0 +1,169 @@
#include "../streamfile.h"
#include "../util/vgmstream_limits.h"
typedef struct {
STREAMFILE vt;
STREAMFILE** inner_sfs;
size_t inner_sfs_size;
size_t *sizes;
offv_t size;
offv_t offset;
} MULTIFILE_STREAMFILE;
static size_t multifile_read(MULTIFILE_STREAMFILE* sf, uint8_t* dst, offv_t offset, size_t length) {
int i, segment = 0;
offv_t segment_offset = 0;
size_t done = 0;
if (offset > sf->size) {
sf->offset = sf->size;
return 0;
}
/* map external offset to multifile offset */
for (i = 0; i < sf->inner_sfs_size; i++) {
size_t segment_size = sf->sizes[i];
/* check if offset falls in this segment */
if (offset >= segment_offset && offset < segment_offset + segment_size) {
segment = i;
segment_offset = offset - segment_offset;
break;
}
segment_offset += segment_size;
}
/* reads can span multiple segments */
while(done < length) {
if (segment >= sf->inner_sfs_size) /* over last segment, not fully done */
break;
/* reads over segment size are ok, will return smaller value and continue next segment */
done += sf->inner_sfs[segment]->read(sf->inner_sfs[segment], dst + done, segment_offset, length - done);
segment++;
segment_offset = 0;
}
sf->offset = offset + done;
return done;
}
static size_t multifile_get_size(MULTIFILE_STREAMFILE* sf) {
return sf->size;
}
static offv_t multifile_get_offset(MULTIFILE_STREAMFILE* sf) {
return sf->offset;
}
static void multifile_get_name(MULTIFILE_STREAMFILE* sf, char* name, size_t name_size) {
sf->inner_sfs[0]->get_name(sf->inner_sfs[0], name, name_size);
}
static STREAMFILE* multifile_open(MULTIFILE_STREAMFILE* sf, const char* const filename, size_t buf_size) {
char original_filename[PATH_LIMIT];
STREAMFILE* new_sf = NULL;
STREAMFILE** new_inner_sfs = NULL;
int i;
sf->inner_sfs[0]->get_name(sf->inner_sfs[0], original_filename, PATH_LIMIT);
/* detect re-opening the file */
if (strcmp(filename, original_filename) == 0) { /* same multifile */
new_inner_sfs = calloc(sf->inner_sfs_size, sizeof(STREAMFILE*));
if (!new_inner_sfs) goto fail;
for (i = 0; i < sf->inner_sfs_size; i++) {
sf->inner_sfs[i]->get_name(sf->inner_sfs[i], original_filename, PATH_LIMIT);
new_inner_sfs[i] = sf->inner_sfs[i]->open(sf->inner_sfs[i], original_filename, buf_size);
if (!new_inner_sfs[i]) goto fail;
}
new_sf = open_multifile_streamfile(new_inner_sfs, sf->inner_sfs_size);
if (!new_sf) goto fail;
free(new_inner_sfs);
return new_sf;
}
else {
return sf->inner_sfs[0]->open(sf->inner_sfs[0], filename, buf_size); /* regular file */
}
fail:
if (new_inner_sfs) {
for (i = 0; i < sf->inner_sfs_size; i++)
close_streamfile(new_inner_sfs[i]);
}
free(new_inner_sfs);
return NULL;
}
static void multifile_close(MULTIFILE_STREAMFILE* sf) {
int i;
for (i = 0; i < sf->inner_sfs_size; i++) {
for (i = 0; i < sf->inner_sfs_size; i++) {
close_streamfile(sf->inner_sfs[i]);
}
}
free(sf->inner_sfs);
free(sf->sizes);
free(sf);
}
STREAMFILE* open_multifile_streamfile(STREAMFILE** sfs, size_t sfs_size) {
MULTIFILE_STREAMFILE* this_sf = NULL;
int i;
if (!sfs || !sfs_size) return NULL;
for (i = 0; i < sfs_size; i++) {
if (!sfs[i]) return NULL;
}
this_sf = calloc(1, sizeof(MULTIFILE_STREAMFILE));
if (!this_sf) goto fail;
/* set callbacks and internals */
this_sf->vt.read = (void*)multifile_read;
this_sf->vt.get_size = (void*)multifile_get_size;
this_sf->vt.get_offset = (void*)multifile_get_offset;
this_sf->vt.get_name = (void*)multifile_get_name;
this_sf->vt.open = (void*)multifile_open;
this_sf->vt.close = (void*)multifile_close;
this_sf->vt.stream_index = sfs[0]->stream_index;
this_sf->inner_sfs_size = sfs_size;
this_sf->inner_sfs = calloc(sfs_size, sizeof(STREAMFILE*));
if (!this_sf->inner_sfs) goto fail;
this_sf->sizes = calloc(sfs_size, sizeof(size_t));
if (!this_sf->sizes) goto fail;
for (i = 0; i < this_sf->inner_sfs_size; i++) {
this_sf->inner_sfs[i] = sfs[i];
this_sf->sizes[i] = sfs[i]->get_size(sfs[i]);
this_sf->size += this_sf->sizes[i];
}
return &this_sf->vt;
fail:
if (this_sf) {
free(this_sf->inner_sfs);
free(this_sf->sizes);
}
free(this_sf);
return NULL;
}
STREAMFILE* open_multifile_streamfile_f(STREAMFILE** sfs, size_t sfs_size) {
STREAMFILE* new_sf = open_multifile_streamfile(sfs, sfs_size);
if (!new_sf) {
int i;
for (i = 0; i < sfs_size; i++) {
close_streamfile(sfs[i]);
}
}
return new_sf;
}

View file

@ -0,0 +1,394 @@
#include "../streamfile.h"
#include "../util/vgmstream_limits.h"
#include "../util/log.h"
#include "../util/sf_utils.h"
#include "../vgmstream.h"
/* for dup/fdopen in some systems */
#ifndef _MSC_VER
#include <unistd.h>
#endif
// for testing purposes; generally slower since reads often aren't optimized for unbuffered IO
//#define DISABLE_BUFFER
/* Enables a minor optimization when reopening file descriptors.
* Some systems/compilers have issues though, and dupe'd FILEs may fread garbage data in rare cases,
* possibly due to underlying buffers that get shared/thrashed by dup(). Seen for example in some .HPS and Ubi
* bigfiles (some later MSVC versions) or PS2 .RSD (Mac), where 2nd channel = 2nd SF reads garbage at some points.
*
* Keep it for other systems since this is (probably) kinda useful, though a more sensible approach would be
* redoing SF/FILE/buffer handling to avoid re-opening as much. */
#if !defined (_MSC_VER) && !defined (__ANDROID__) && !defined (__APPLE__)
#define USE_STDIO_FDUP 1
#endif
/* For (rarely needed) +2GB file support we use fseek64/ftell64. Those are usually available
* but may depend on compiler.
* - MSVC: +VS2008 should work
* - GCC/MingW: should be available
* - GCC/Linux: should be available but some systems may need __USE_FILE_OFFSET64,
* that we (probably) don't want since that turns off_t to off64_t
* - Clang: seems only defined on Linux/GNU environments, somehow emscripten is out
* (unsure about Clang Win since apparently they define _MSC_VER)
* - Android: API +24 if not using __USE_FILE_OFFSET64
* Not sure if fopen64 is needed in some cases.
*/
#if defined(_MSC_VER) //&& defined(__MSVCRT__)
/* MSVC fixes (MinG64 seems to set MSVCRT too, but we want it below) */
#include <io.h>
#define fopen_v fopen
#if (_MSC_VER >= 1400)
#define fseek_v _fseeki64
#define ftell_v _ftelli64
#else
#define fseek_v fseek
#define ftell_v ftell
#endif
#ifdef fileno
#undef fileno
#endif
#define fileno _fileno
#define fdopen _fdopen
#define dup _dup
//#ifndef off64_t
// #define off_t/off64_t __int64
//#endif
#elif defined(VGMSTREAM_USE_IO64) || defined(__MINGW32__) || defined(__MINGW64__)
/* force, or known to work */
#define fopen_v fopen
#define fseek_v fseeko64 //fseeko
#define ftell_v ftello64 //ftello
#elif defined(XBMC) || defined(__EMSCRIPTEN__) || defined (__ANDROID__)
/* may depend on version */
#define fopen_v fopen
#define fseek_v fseek
#define ftell_v ftell
#else
/* other Linux systems may already use off64_t in fseeko/ftello? */
#define fopen_v fopen
#define fseek_v fseeko
#define ftell_v ftello
#endif
/* a STREAMFILE that operates via standard IO using a buffer */
typedef struct {
STREAMFILE vt; /* callbacks */
FILE* infile; /* actual FILE */
char name[PATH_LIMIT]; /* FILE filename */
int name_len; /* cache */
offv_t offset; /* last read offset (info) */
offv_t buf_offset; /* current buffer data start */
uint8_t* buf; /* data buffer */
size_t buf_size; /* max buffer size */
size_t valid_size; /* current buffer size */
size_t file_size; /* buffered file size */
} STDIO_STREAMFILE;
static STREAMFILE* open_stdio_streamfile_buffer(const char* const filename, size_t buf_size);
static STREAMFILE* open_stdio_streamfile_buffer_by_file(FILE *infile, const char* const filename, size_t buf_size);
static size_t stdio_read(STDIO_STREAMFILE* sf, uint8_t* dst, offv_t offset, size_t length) {
size_t read_total = 0;
if (/*!sf->infile ||*/ !dst || length <= 0 || offset < 0)
return 0;
#ifdef DISABLE_BUFFER
if (offset != sf->offset) {
fseek_v(sf->infile, offset, SEEK_SET);
}
read_total = fread(dst, sizeof(uint8_t), length, sf->infile);
sf->offset = offset + read_total;
return read_total;
#else
//;VGM_LOG("stdio: read %lx + %x (buf %lx + %x)\n", offset, length, sf->buf_offset, sf->valid_size, sf->buf_size);
/* is the part of the requested length in the buffer? */
if (offset >= sf->buf_offset && offset < sf->buf_offset + sf->valid_size) {
size_t buf_limit;
int buf_into = (int)(offset - sf->buf_offset);
buf_limit = sf->valid_size - buf_into;
if (buf_limit > length)
buf_limit = length;
//;VGM_LOG("stdio: copy buf %lx + %x (+ %x) (buf %lx + %x)\n", offset, length_to_read, (length - length_to_read), sf->buf_offset, sf->valid_size);
memcpy(dst, sf->buf + buf_into, buf_limit);
read_total += buf_limit;
length -= buf_limit;
offset += buf_limit;
dst += buf_limit;
}
#ifdef VGM_DEBUG_OUTPUT
if (offset < sf->buf_offset && length > 0) {
//VGM_LOG("stdio: rebuffer, requested %x vs %x (sf %x)\n", (uint32_t)offset, (uint32_t)sf->buf_offset, (uint32_t)sf);
//sf->rebuffer++;
//if (rebuffer > N) ...
}
#endif
/* possible if all data was copied to buf and FD closed */
if (!sf->infile)
return read_total;
/* read the rest of the requested length */
while (length > 0) {
size_t length_to_read;
/* ignore requests at EOF */
if (offset >= sf->file_size) {
//offset = sf->file_size; /* seems fseek doesn't clamp offset */
VGM_ASSERT_ONCE(offset > sf->file_size, "STDIO: reading over file_size 0x%x @ 0x%x + 0x%x\n", sf->file_size, (uint32_t)offset, length);
break;
}
/* position to new offset */
if (fseek_v(sf->infile, offset, SEEK_SET)) {
break; /* this shouldn't happen in our code */
}
#if 0
/* old workaround for USE_STDIO_FDUP bug, keep it here for a while as a reminder just in case */
//fseek_v(sf->infile, ftell_v(sf->infile), SEEK_SET);
#endif
/* fill the buffer (offset now is beyond buf_offset) */
sf->buf_offset = offset;
sf->valid_size = fread(sf->buf, sizeof(uint8_t), sf->buf_size, sf->infile);
//;VGM_LOG("stdio: read buf %lx + %x\n", sf->buf_offset, sf->valid_size);
/* decide how much must be read this time */
if (length > sf->buf_size)
length_to_read = sf->buf_size;
else
length_to_read = length;
/* give up on partial reads (EOF) */
if (sf->valid_size < length_to_read) {
memcpy(dst, sf->buf, sf->valid_size);
offset += sf->valid_size;
read_total += sf->valid_size;
break;
}
/* use the new buffer */
memcpy(dst, sf->buf, length_to_read);
offset += length_to_read;
read_total += length_to_read;
length -= length_to_read;
dst += length_to_read;
}
sf->offset = offset; /* last fread offset */
return read_total;
#endif
}
static size_t stdio_get_size(STDIO_STREAMFILE* sf) {
return sf->file_size;
}
static offv_t stdio_get_offset(STDIO_STREAMFILE* sf) {
return sf->offset;
}
static void stdio_get_name(STDIO_STREAMFILE* sf, char* name, size_t name_size) {
int copy_size = sf->name_len + 1;
if (copy_size > name_size)
copy_size = name_size;
memcpy(name, sf->name, copy_size);
name[copy_size - 1] = '\0';
}
static STREAMFILE* stdio_open(STDIO_STREAMFILE* sf, const char* const filename, size_t buf_size) {
if (!filename)
return NULL;
#ifdef USE_STDIO_FDUP
/* minor optimization when reopening files, see comment in #define above */
/* if same name, duplicate the file descriptor we already have open */
if (sf->infile && !strcmp(sf->name,filename)) {
int new_fd;
FILE *new_file = NULL;
if (((new_fd = dup(fileno(sf->infile))) >= 0) && (new_file = fdopen(new_fd, "rb"))) {
STREAMFILE* new_sf = open_stdio_streamfile_buffer_by_file(new_file, filename, buf_size);
if (new_sf)
return new_sf;
fclose(new_file);
}
if (new_fd >= 0 && !new_file)
close(new_fd); /* fdopen may fail when opening too many files */
/* on failure just close and try the default path (which will probably fail a second time) */
}
#endif
return open_stdio_streamfile_buffer(filename, buf_size);
}
static void stdio_close(STDIO_STREAMFILE* sf) {
if (sf->infile)
fclose(sf->infile);
free(sf->buf);
free(sf);
}
static STREAMFILE* open_stdio_streamfile_buffer_by_file(FILE* infile, const char* const filename, size_t buf_size) {
uint8_t* buf = NULL;
STDIO_STREAMFILE* this_sf = NULL;
if (buf_size <= 0)
buf_size = STREAMFILE_DEFAULT_BUFFER_SIZE;
buf = calloc(buf_size, sizeof(uint8_t));
if (!buf) goto fail;
this_sf = calloc(1, sizeof(STDIO_STREAMFILE));
if (!this_sf) goto fail;
this_sf->vt.read = (void*)stdio_read;
this_sf->vt.get_size = (void*)stdio_get_size;
this_sf->vt.get_offset = (void*)stdio_get_offset;
this_sf->vt.get_name = (void*)stdio_get_name;
this_sf->vt.open = (void*)stdio_open;
this_sf->vt.close = (void*)stdio_close;
this_sf->infile = infile;
this_sf->buf_size = buf_size;
this_sf->buf = buf;
this_sf->name_len = strlen(filename);
if (this_sf->name_len >= sizeof(this_sf->name))
goto fail;
memcpy(this_sf->name, filename, this_sf->name_len);
this_sf->name[this_sf->name_len] = '\0';
/* cache file_size */
if (infile) {
fseek_v(this_sf->infile, 0x00, SEEK_END);
this_sf->file_size = ftell_v(this_sf->infile);
fseek_v(this_sf->infile, 0x00, SEEK_SET);
}
else {
this_sf->file_size = 0; /* allow virtual, non-existing files */
}
/* Typically fseek(o)/ftell(o) may only handle up to ~2.14GB, signed 32b = 0x7FFFFFFF (rarely
* happens in giant banks like FSB/KTSR). Should work if configured properly using ftell_v, log otherwise. */
if (this_sf->file_size == 0xFFFFFFFF) { /* -1 on error */
vgm_logi("STREAMFILE: file size too big (report)\n");
goto fail; /* can be ignored but may result in strange/unexpected behaviors */
}
/* Rarely a TXTP needs to open *many* streamfiles = many file descriptors = reaches OS limit = error.
* Ideally should detect better and open/close as needed or reuse FDs for files that don't play at
* the same time, but it's complex since every SF is separate (would need some kind of FD manager).
* For the time being, if the file is smaller that buffer we can just read it fully and close the FD,
* that should help since big TXTP usually just need many small files.
* Doubles as an optimization as most files given will be read fully into buf on first read. */
if (this_sf->file_size && this_sf->file_size < this_sf->buf_size && this_sf->infile) {
//;VGM_LOG("stdio: fit filesize %x into buf %x\n", sf->file_size, sf->buf_size);
this_sf->buf_offset = 0;
this_sf->valid_size = fread(this_sf->buf, sizeof(uint8_t), this_sf->file_size, this_sf->infile);
fclose(this_sf->infile);
this_sf->infile = NULL;
}
return &this_sf->vt;
fail:
free(buf);
free(this_sf);
return NULL;
}
static STREAMFILE* open_stdio_streamfile_buffer(const char* const filename, size_t bufsize) {
FILE* infile = NULL;
STREAMFILE* sf = NULL;
infile = fopen_v(filename,"rb");
if (!infile) {
/* allow non-existing files in some cases */
if (!vgmstream_is_virtual_filename(filename))
return NULL;
}
sf = open_stdio_streamfile_buffer_by_file(infile, filename, bufsize);
if (!sf) {
if (infile) fclose(infile);
}
return sf;
}
STREAMFILE* open_stdio_streamfile(const char* filename) {
return open_stdio_streamfile_buffer(filename, 0);
}
STREAMFILE* open_stdio_streamfile_by_file(FILE* file, const char* filename) {
return open_stdio_streamfile_buffer_by_file(file, filename, 0);
}
/* ************************************************************************* */
void dump_streamfile(STREAMFILE* sf, int num) {
#ifdef VGM_DEBUG_OUTPUT
offv_t offset = 0;
FILE* f = NULL;
if (num >= 0) {
char filename[PATH_LIMIT];
char dumpname[PATH_LIMIT];
get_streamfile_filename(sf, filename, sizeof(filename));
snprintf(dumpname, sizeof(dumpname), "%s_%02i.dump", filename, num);
f = fopen_v(dumpname,"wb");
if (!f) return;
}
VGM_LOG("dump streamfile %i: size %x\n", num, get_streamfile_size(sf));
while (offset < get_streamfile_size(sf)) {
uint8_t buf[0x8000];
size_t bytes;
bytes = read_streamfile(buf, offset, sizeof(buf), sf);
if(!bytes) {
VGM_LOG("dump streamfile: can't read at %x\n", (uint32_t)offset);
break;
}
if (f)
fwrite(buf, sizeof(uint8_t), bytes, f);
else if (num == -1)
VGM_LOGB(buf, bytes, 0);
//else: don't do anything (read test)
offset += bytes;
}
if (f) {
fclose(f);
}
#endif
}

View file

@ -0,0 +1,61 @@
#include "../streamfile.h"
//todo stream_index: copy? pass? funtion? external?
//todo use realnames on reopen? simplify?
//todo use safe string ops, this ain't easy
typedef struct {
STREAMFILE vt;
STREAMFILE* inner_sf;
} WRAP_STREAMFILE;
static size_t wrap_read(WRAP_STREAMFILE* sf, uint8_t* dst, offv_t offset, size_t length) {
return sf->inner_sf->read(sf->inner_sf, dst, offset, length); /* default */
}
static size_t wrap_get_size(WRAP_STREAMFILE* sf) {
return sf->inner_sf->get_size(sf->inner_sf); /* default */
}
static offv_t wrap_get_offset(WRAP_STREAMFILE* sf) {
return sf->inner_sf->get_offset(sf->inner_sf); /* default */
}
static void wrap_get_name(WRAP_STREAMFILE* sf, char* name, size_t name_size) {
sf->inner_sf->get_name(sf->inner_sf, name, name_size); /* default */
}
static STREAMFILE* wrap_open(WRAP_STREAMFILE* sf, const char* const filename, size_t buf_size) {
return sf->inner_sf->open(sf->inner_sf, filename, buf_size); /* default (don't call open_wrap_streamfile) */
}
static void wrap_close(WRAP_STREAMFILE* sf) {
//sf->inner_sf->close(sf->inner_sf); /* don't close */
free(sf);
}
STREAMFILE* open_wrap_streamfile(STREAMFILE* sf) {
WRAP_STREAMFILE* this_sf = NULL;
if (!sf) return NULL;
this_sf = calloc(1, sizeof(WRAP_STREAMFILE));
if (!this_sf) return NULL;
/* set callbacks and internals */
this_sf->vt.read = (void*)wrap_read;
this_sf->vt.get_size = (void*)wrap_get_size;
this_sf->vt.get_offset = (void*)wrap_get_offset;
this_sf->vt.get_name = (void*)wrap_get_name;
this_sf->vt.open = (void*)wrap_open;
this_sf->vt.close = (void*)wrap_close;
this_sf->vt.stream_index = sf->stream_index;
this_sf->inner_sf = sf;
return &this_sf->vt;
}
STREAMFILE* open_wrap_streamfile_f(STREAMFILE* sf) {
STREAMFILE* new_sf = open_wrap_streamfile(sf);
if (!new_sf)
close_streamfile(sf);
return new_sf;
}

View file

@ -0,0 +1,268 @@
#include "../vgmstream.h"
#include "../util/log.h"
#include "../util/reader_sf.h"
#include "../util/reader_text.h"
#include "plugins.h"
/* TAGS: loads key=val tags from a file */
#define VGMSTREAM_TAGS_LINE_MAX 2048
/* opaque tag state */
struct VGMSTREAM_TAGS {
/* extracted output */
char key[VGMSTREAM_TAGS_LINE_MAX];
char val[VGMSTREAM_TAGS_LINE_MAX];
/* file to find tags for */
int targetname_len;
char targetname[VGMSTREAM_TAGS_LINE_MAX];
/* path of targetname */
char targetpath[VGMSTREAM_TAGS_LINE_MAX];
/* tag section for filename (see comments below) */
int section_found;
off_t section_start;
off_t section_end;
off_t offset;
/* commands */
int autotrack_on;
int autotrack_written;
int track_count;
int exact_match;
int autoalbum_on;
int autoalbum_written;
};
static void tags_clean(VGMSTREAM_TAGS* tag) {
int i;
int val_len = strlen(tag->val);
/* remove trailing spaces */
for (i = val_len - 1; i > 0; i--) {
if (tag->val[i] != ' ')
break;
tag->val[i] = '\0';
}
}
VGMSTREAM_TAGS* vgmstream_tags_init(const char* *tag_key, const char* *tag_val) {
VGMSTREAM_TAGS* tags = malloc(sizeof(VGMSTREAM_TAGS));
if (!tags) goto fail;
*tag_key = tags->key;
*tag_val = tags->val;
return tags;
fail:
return NULL;
}
void vgmstream_tags_close(VGMSTREAM_TAGS *tags) {
free(tags);
}
/* Find next tag and return 1 if found.
*
* Tags can be "global" @TAGS, "command" $TAGS, and "file" %TAGS for a target filename.
* To extract tags we must find either global tags, or the filename's tag "section"
* where tags apply: (# @TAGS ) .. (other_filename) ..(# %TAGS section).. (target_filename).
* When a new "other_filename" is found that offset is marked as section_start, and when
* target_filename is found it's marked as section_end. Then we can begin extracting tags
* within that section, until all tags are exhausted. Global tags are extracted as found,
* so they always go first, also meaning any tags after file's section are ignored.
* Command tags have special meanings and are output after all section tags. */
int vgmstream_tags_next_tag(VGMSTREAM_TAGS* tags, STREAMFILE* tagfile) {
off_t file_size = get_streamfile_size(tagfile);
char currentname[VGMSTREAM_TAGS_LINE_MAX] = {0};
char line[VGMSTREAM_TAGS_LINE_MAX];
int ok, bytes_read, line_ok, n1,n2;
if (!tags)
return 0;
/* prepare file start and skip BOM if needed */
if (tags->offset == 0) {
size_t bom_size = read_bom(tagfile);
tags->offset = bom_size;
if (tags->section_start == 0)
tags->section_start = bom_size;
}
/* read lines */
while (tags->offset <= file_size) {
/* after section: no more tags to extract */
if (tags->section_found && tags->offset >= tags->section_end) {
/* write extra tags after all regular tags */
if (tags->autotrack_on && !tags->autotrack_written) {
sprintf(tags->key, "%s", "TRACK");
sprintf(tags->val, "%i", tags->track_count);
tags->autotrack_written = 1;
return 1;
}
if (tags->autoalbum_on && !tags->autoalbum_written && tags->targetpath[0] != '\0') {
const char* path;
path = strrchr(tags->targetpath,'\\');
if (!path) {
path = strrchr(tags->targetpath,'/');
}
if (!path) {
path = tags->targetpath;
}
sprintf(tags->key, "%s", "ALBUM");
sprintf(tags->val, "%s", path+1);
tags->autoalbum_written = 1;
return 1;
}
goto fail;
}
bytes_read = read_line(line, sizeof(line), tags->offset, tagfile, &line_ok);
if (!line_ok || bytes_read == 0) goto fail;
tags->offset += bytes_read;
if (tags->section_found) {
/* find possible file tag */
ok = sscanf(line, "# %%%[^%%]%% %[^\r\n] ", tags->key,tags->val); /* key with spaces */
if (ok != 2)
ok = sscanf(line, "# %%%[^ \t] %[^\r\n] ", tags->key,tags->val); /* key without */
if (ok == 2) {
tags_clean(tags);
return 1;
}
}
else {
if (line[0] == '#') {
/* find possible global command */
ok = sscanf(line, "# $%n%[^ \t]%n %[^\r\n]", &n1, tags->key, &n2, tags->val);
if (ok == 1 || ok == 2) {
int key_len = n2 - n1;
if (strncasecmp(tags->key, "AUTOTRACK", key_len) == 0) {
tags->autotrack_on = 1;
}
else if (strncasecmp(tags->key, "AUTOALBUM", key_len) == 0) {
tags->autoalbum_on = 1;
}
else if (strncasecmp(tags->key, "EXACTMATCH", key_len) == 0) {
tags->exact_match = 1;
}
continue; /* not an actual tag */
}
/* find possible global tag */
ok = sscanf(line, "# @%[^@]@ %[^\r\n]", tags->key, tags->val); /* key with spaces */
if (ok != 2)
ok = sscanf(line, "# @%[^ \t] %[^\r\n]", tags->key, tags->val); /* key without */
if (ok == 2) {
tags_clean(tags);
return 1;
}
continue; /* next line */
}
/* find possible filename and section start/end
* (.m3u seem to allow filenames with whitespaces before, make sure to trim) */
ok = sscanf(line, " %n%[^\r\n]%n ", &n1, currentname, &n2);
if (ok == 1) {
int currentname_len = n2 - n1;
int filename_found = 0;
/* we want to match file with the same name (case insensitive), OR a virtual .txtp with
* the filename inside to ease creation of tag files with config, also check end char to
* tell apart the unlikely case of having both 'bgm01.ad.txtp' and 'bgm01.adp.txtp' */
/* try exact match (strcasecmp works ok even for UTF-8) */
if (currentname_len == tags->targetname_len &&
strncasecmp(currentname, tags->targetname, currentname_len) == 0) {
filename_found = 1;
}
else if (!tags->exact_match) {
/* try tagfile is "bgm.adx" + target is "bgm.adx #(cfg) .txtp" */
if (currentname_len < tags->targetname_len &&
strncasecmp(currentname, tags->targetname, currentname_len) == 0 &&
vgmstream_is_virtual_filename(tags->targetname)) {
char c = tags->targetname[currentname_len];
filename_found = (c==' ' || c == '.' || c == '#');
}
/* tagfile has "bgm.adx (...) .txtp" + target has "bgm.adx" */
else if (tags->targetname_len < currentname_len &&
strncasecmp(tags->targetname, currentname, tags->targetname_len) == 0 &&
vgmstream_is_virtual_filename(currentname)) {
char c = currentname[tags->targetname_len];
filename_found = (c==' ' || c == '.' || c == '#');
}
}
if (filename_found) {
/* section ok, start would be set before this (or be 0) */
tags->section_end = tags->offset;
tags->section_found = 1;
tags->offset = tags->section_start;
}
else {
/* mark new possible section */
tags->section_start = tags->offset;
}
tags->track_count++; /* new track found (target filename or not) */
continue;
}
/* empty/bad line, probably */
}
}
/* may reach here if read up to file_size but no section was found */
fail:
tags->key[0] = '\0';
tags->val[0] = '\0';
return 0;
}
void vgmstream_tags_reset(VGMSTREAM_TAGS* tags, const char* target_filename) {
char *path;
if (!tags)
return;
memset(tags, 0, sizeof(VGMSTREAM_TAGS));
//todo validate sizes and copy sensible max
/* get base name */
strcpy(tags->targetpath, target_filename);
/* Windows CMD accepts both \\ and /, and maybe plugin uses either */
path = strrchr(tags->targetpath,'\\');
if (!path) {
path = strrchr(tags->targetpath,'/');
}
if (path != NULL) {
path[0] = '\0'; /* leave targetpath with path only */
path = path+1;
}
if (path) {
strcpy(tags->targetname, path);
} else {
tags->targetpath[0] = '\0';
strcpy(tags->targetname, target_filename);
}
tags->targetname_len = strlen(tags->targetname);
}

View file

@ -4,30 +4,28 @@
/* Activision / EXAKT Entertainment's DPCM for Supercar Street Challenge */
#if 0
To build table:
int32_t bring_round(int32_t v)
{
int32_t bring_round(int32_t v) {
return v | (v >> 12);
}
for (i=0x00;i<0x20;i++)
SASSC_steps[i] = bring_round(i<<4);
sassc_steps[i] = bring_round(i<<4);
for (i=0x20;i<0x40;i++)
SASSC_steps[i] = bring_round(((i-0x20)*7+0x20)<<4);
sassc_steps[i] = bring_round(((i-0x20)*7+0x20)<<4);
for (i=0x40;i<0x60;i++)
SASSC_steps[i] = bring_round(((i-0x40)*24+0x100)<<4);
sassc_steps[i] = bring_round(((i-0x40)*24+0x100)<<4);
for (i=0x60;i<0x80;i++)
SASSC_steps[i] = bring_round(((i-0x60)*96+0x400)<<4);
sassc_steps[i] = bring_round(((i-0x60)*96+0x400)<<4);
for (i=0x80;i<0xFF;i++)
SASSC_steps[i] = -SASSC_steps[i-0x80];
sassc_steps[i] = -sassc_steps[i-0x80];
SASSC_steps[0xFF] = SASSC_steps[0x7F];
sassc_steps[0xFF] = sassc_steps[0x7F];
#endif
int32_t SASSC_steps[256] =
{
static const int32_t sassc_steps[256] = {
0, 16, 32, 48, 64, 80, 96, 112,
128, 144, 160, 176, 192, 208, 224, 240,
256, 272, 288, 304, 320, 336, 352, 368,
@ -63,13 +61,14 @@ int32_t SASSC_steps[256] =
-53261, -54797, -56333, -57870, -59406, -60942, -62479, 64015,
};
void decode_sassc(VGMSTREAMCHANNEL * stream, sample * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do) {
void decode_sassc(VGMSTREAMCHANNEL* stream, sample_t* outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do) {
int i;
int32_t sample_count;
int32_t hist = stream->adpcm_history1_32;
for(i=first_sample,sample_count=0; i<first_sample+samples_to_do; i++,sample_count+=channelspacing) {
hist = hist + SASSC_steps[(uint8_t)read_8bit(stream->offset+i,stream->streamfile)];
for (i = first_sample, sample_count = 0; i < first_sample + samples_to_do; i++, sample_count += channelspacing) {
uint8_t index = read_u8(stream->offset + i, stream->streamfile);
hist = hist + sassc_steps[index];
outbuf[sample_count] = clamp16(hist);
}

View file

@ -1,8 +1,8 @@
#include "coding.h"
#include "acm_decoder_libacm.h"
#include "libs/libacm.h"
#include <stdio.h>
/* libacm 1.2 (despite what libacm.h says) from: https://github.com/markokr/libacm */
/* libacm 1.2 (despite libacm.h saying 1.1) from: https://github.com/markokr/libacm */
/* libacm interface */
@ -26,10 +26,10 @@ acm_codec_data* init_acm(STREAMFILE* sf, int force_channel_number) {
acm_codec_data* data = NULL;
data = calloc(1,sizeof(acm_codec_data));
data = calloc(1, sizeof(acm_codec_data));
if (!data) goto fail;
data->io_config = calloc(1,sizeof(acm_io_config));
data->io_config = calloc(1, sizeof(acm_io_config));
if (!data->io_config) goto fail;
data->streamfile = reopen_streamfile(sf, 0);
@ -74,12 +74,12 @@ void decode_acm(acm_codec_data* data, sample_t* outbuf, int32_t samples_to_do, i
while (samples_read < samples_to_do) {
int32_t bytes_read_just_now = acm_read(
acm,
(char*)(outbuf+samples_read*channelspacing),
(samples_to_do-samples_read)*sizeof(sample)*channelspacing,
(char*)(outbuf + samples_read * channelspacing),
(samples_to_do - samples_read) * sizeof(sample_t) * channelspacing,
0,2,1);
if (bytes_read_just_now > 0) {
samples_read += bytes_read_just_now/sizeof(sample)/channelspacing;
samples_read += bytes_read_just_now / sizeof(sample_t) / channelspacing;
} else {
return;
}

View file

@ -90,7 +90,7 @@ void decode_adx(VGMSTREAMCHANNEL* stream, sample_t* outbuf, int channelspacing,
stream->adpcm_history2_32 = hist2;
if ((coding_type == coding_CRI_ADX_enc_8 || coding_type == coding_CRI_ADX_enc_9) && !(i % 32)) {
for (i =0; i < stream->adx_channels; i++) {
for (i = 0; i < channelspacing; i++) {
adx_next_key(stream);
}
}

View file

@ -33,7 +33,7 @@ atrac9_codec_data* init_atrac9(atrac9_config* cfg) {
data->handle = Atrac9GetHandle();
if (!data->handle) goto fail;
put_32bitBE(config_data, cfg->config_data);
put_u32be(config_data, cfg->config_data);
status = Atrac9InitDecoder(data->handle, config_data);
if (status < 0) goto fail;
@ -50,9 +50,9 @@ atrac9_codec_data* init_atrac9(atrac9_config* cfg) {
/* must hold at least one superframe and its samples */
data->data_buffer_size = data->info.superframeSize;
/* extra leeway as Atrac9Decode seems to overread ~2 bytes (doesn't affect decoding though) */
data->data_buffer = calloc(sizeof(uint8_t), data->data_buffer_size + 0x10);
/* while ATRAC9 uses float internally, Sony's API only return PCM16 */
data->sample_buffer = calloc(sizeof(sample_t), data->info.channels * data->info.frameSamples * data->info.framesInSuperframe);
data->data_buffer = calloc(data->data_buffer_size + 0x10, sizeof(uint8_t));
/* while ATRAC9 uses float internally, Sony's API only returns PCM16 */
data->sample_buffer = calloc(data->info.channels * data->info.frameSamples * data->info.framesInSuperframe, sizeof(sample_t));
data->samples_to_discard = cfg->encoder_delay;
@ -89,7 +89,7 @@ void decode_atrac9(VGMSTREAM* vgmstream, sample_t* outbuf, int32_t samples_to_do
memcpy(outbuf + samples_done*channels,
data->sample_buffer + data->samples_used*channels,
samples_to_get*channels * sizeof(sample));
samples_to_get*channels * sizeof(sample_t));
samples_done += samples_to_get;
}
@ -131,7 +131,7 @@ void decode_atrac9(VGMSTREAM* vgmstream, sample_t* outbuf, int32_t samples_to_do
decode_fail:
/* on error just put some 0 samples */
VGM_LOG("ATRAC9: decode fail at %x, missing %i samples\n", (uint32_t)stream->offset, (samples_to_do - samples_done));
memset(outbuf + samples_done * channels, 0, (samples_to_do - samples_done) * sizeof(sample) * channels);
memset(outbuf + samples_done * channels, 0, (samples_to_do - samples_done) * sizeof(sample_t) * channels);
}
void reset_atrac9(atrac9_codec_data* data) {
@ -150,7 +150,7 @@ void reset_atrac9(atrac9_codec_data* data) {
data->handle = Atrac9GetHandle();
if (!data->handle) goto fail;
put_32bitBE(config_data, data->config.config_data);
put_u32be(config_data, data->config.config_data);
status = Atrac9InitDecoder(data->handle, config_data);
if (status < 0) goto fail;
}
@ -221,7 +221,7 @@ void free_atrac9(atrac9_codec_data* data) {
}
static int atrac9_parse_config(uint32_t atrac9_config, int *out_sample_rate, int *out_channels, size_t *out_frame_size, size_t *out_samples_per_frame) {
static int atrac9_parse_config(uint32_t config_data, int* p_sample_rate, int* p_channels, size_t* p_frame_size, size_t* p_samples_per_frame) {
static const int sample_rate_table[16] = {
11025, 12000, 16000, 22050, 24000, 32000, 44100, 48000,
44100, 48000, 64000, 88200, 96000,128000,176400,192000
@ -235,13 +235,13 @@ static int atrac9_parse_config(uint32_t atrac9_config, int *out_sample_rate, int
};
int superframe_size, frames_per_superframe, samples_per_frame, samples_per_superframe;
uint32_t sync = (atrac9_config >> 24) & 0xff; /* 8b */
uint8_t sample_rate_index = (atrac9_config >> 20) & 0x0f; /* 4b */
uint8_t channels_index = (atrac9_config >> 17) & 0x07; /* 3b */
/* uint8_t validation bit = (atrac9_config >> 16) & 0x01; */ /* 1b */
size_t frame_size = (atrac9_config >> 5) & 0x7FF; /* 11b */
size_t superframe_index = (atrac9_config >> 3) & 0x3; /* 2b */
/* uint8_t unused = (atrac9_config >> 0) & 0x7);*/ /* 3b */
uint32_t sync = (config_data >> 24) & 0xff; /* 8b */
uint8_t sample_rate_index = (config_data >> 20) & 0x0f; /* 4b */
uint8_t channels_index = (config_data >> 17) & 0x07; /* 3b */
/* uint8_t validation bit = (config_data >> 16) & 0x01; */ /* 1b */
size_t frame_size = (config_data >> 5) & 0x7FF; /* 11b */
size_t superframe_index = (config_data >> 3) & 0x3; /* 2b */
/* uint8_t unused = (config_data >> 0) & 0x7);*/ /* 3b */
superframe_size = ((frame_size+1) << superframe_index);
frames_per_superframe = (1 << superframe_index);
@ -250,14 +250,14 @@ static int atrac9_parse_config(uint32_t atrac9_config, int *out_sample_rate, int
if (sync != 0xFE)
goto fail;
if (out_sample_rate)
*out_sample_rate = sample_rate_table[sample_rate_index];
if (out_channels)
*out_channels = channel_table[channels_index];
if (out_frame_size)
*out_frame_size = superframe_size;
if (out_samples_per_frame)
*out_samples_per_frame = samples_per_superframe;
if (p_sample_rate)
*p_sample_rate = sample_rate_table[sample_rate_index];
if (p_channels)
*p_channels = channel_table[channels_index];
if (p_frame_size)
*p_frame_size = superframe_size;
if (p_samples_per_frame)
*p_samples_per_frame = samples_per_superframe;
return 1;
fail:
@ -268,9 +268,9 @@ size_t atrac9_bytes_to_samples(size_t bytes, atrac9_codec_data* data) {
return bytes / data->info.superframeSize * (data->info.frameSamples * data->info.framesInSuperframe);
}
size_t atrac9_bytes_to_samples_cfg(size_t bytes, uint32_t atrac9_config) {
size_t atrac9_bytes_to_samples_cfg(size_t bytes, uint32_t config_data) {
size_t frame_size, samples_per_frame;
if (!atrac9_parse_config(atrac9_config, NULL, NULL, &frame_size, &samples_per_frame))
if (!atrac9_parse_config(config_data, NULL, NULL, &frame_size, &samples_per_frame))
return 0;
return bytes / frame_size * samples_per_frame;
}

View file

@ -67,7 +67,7 @@ celt_codec_data* init_celt_fsb(int channels, celt_lib_t version) {
goto fail;
}
data->sample_buffer = calloc(sizeof(sample), data->channel_mode * FSB_CELT_SAMPLES_PER_FRAME);
data->sample_buffer = calloc(data->channel_mode * FSB_CELT_SAMPLES_PER_FRAME, sizeof(sample_t));
if (!data->sample_buffer) goto fail;
/* there is ~128 samples of encoder delay, but FMOD DLLs don't discard it? */
@ -103,7 +103,7 @@ void decode_celt_fsb(VGMSTREAM* vgmstream, sample_t* outbuf, int32_t samples_to_
memcpy(outbuf + samples_done*channels,
data->sample_buffer + data->samples_used*channels,
samples_to_get*channels * sizeof(sample));
samples_to_get*channels * sizeof(sample_t));
samples_done += samples_to_get;
}
@ -158,7 +158,7 @@ void decode_celt_fsb(VGMSTREAM* vgmstream, sample_t* outbuf, int32_t samples_to_
decode_fail:
/* on error just put some 0 samples */
VGM_LOG("CELT: decode fail at %x, missing %i samples\n", (uint32_t)stream->offset, (samples_to_do - samples_done));
memset(outbuf + samples_done * channels, 0, (samples_to_do - samples_done) * sizeof(sample) * channels);
memset(outbuf + samples_done * channels, 0, (samples_to_do - samples_done) * sizeof(sample_t) * channels);
}
void reset_celt_fsb(celt_codec_data* data) {

View file

@ -1,5 +1,5 @@
#include "coding.h"
#include "circus_decoder_lib.h"
#include "libs/circus_vq_lib.h"

View file

@ -4,8 +4,9 @@
#include "../vgmstream.h"
#include "../util/reader_sf.h"
#include "../util/reader_get_nibbles.h"
#include "../util/log.h"
//todo remove
#include "hca_decoder_clhca.h"
#include "libs/clhca.h"
/* adx_decoder */
void decode_adx(VGMSTREAMCHANNEL* stream, sample_t* outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do, int32_t frame_bytes, coding_t coding_type, uint32_t codec_config);
@ -13,7 +14,7 @@ void adx_next_key(VGMSTREAMCHANNEL* stream);
/* g721_decoder */
void decode_g721(VGMSTREAMCHANNEL* stream, sample * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do);
void decode_g721(VGMSTREAMCHANNEL* stream, sample_t* outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do);
void g72x_init_state(struct g72x_state* state_ptr);
@ -45,6 +46,7 @@ void decode_ubi_ima(VGMSTREAMCHANNEL* stream, sample_t* outbuf, int channelspaci
void decode_ubi_sce_ima(VGMSTREAMCHANNEL* stream, sample_t* outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do, int channel);
void decode_h4m_ima(VGMSTREAMCHANNEL* stream, sample_t* outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do, int channel, uint16_t frame_format);
void decode_cd_ima(VGMSTREAMCHANNEL* stream, sample_t* outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do);
void decode_crankcase_ima(VGMSTREAMCHANNEL* stream, sample_t* outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do);
size_t ima_bytes_to_samples(size_t bytes, int channels);
size_t ms_ima_bytes_to_samples(size_t bytes, int block_align, int channels);
size_t xbox_ima_bytes_to_samples(size_t bytes, int channels);
@ -125,14 +127,14 @@ size_t xa_bytes_to_samples(size_t bytes, int channels, int is_blocked, int is_fo
/* ea_xa_decoder */
void decode_ea_xa(VGMSTREAMCHANNEL* stream, sample * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do, int channel, int is_stereo);
void decode_ea_xa_v2(VGMSTREAMCHANNEL* stream, sample * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do);
void decode_maxis_xa(VGMSTREAMCHANNEL* stream, sample * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do, int channel);
void decode_ea_xa(VGMSTREAMCHANNEL* stream, sample_t* outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do, int channel, int is_stereo);
void decode_ea_xa_v2(VGMSTREAMCHANNEL* stream, sample_t* outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do);
void decode_maxis_xa(VGMSTREAMCHANNEL* stream, sample_t* outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do, int channel);
int32_t ea_xa_bytes_to_samples(size_t bytes, int channels);
/* ea_xas_decoder */
void decode_ea_xas_v0(VGMSTREAMCHANNEL* stream, sample * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do);
void decode_ea_xas_v0(VGMSTREAMCHANNEL* stream, sample_t* outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do);
void decode_ea_xas_v1(VGMSTREAMCHANNEL* stream, sample_t* outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do, int channel);
@ -144,7 +146,7 @@ void decode_cbd2_int(VGMSTREAMCHANNEL* stream, sample_t* outbuf, int channelspac
/* ws_decoder */
void decode_ws(VGMSTREAM* vgmstream, int channel, sample * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do);
void decode_ws(VGMSTREAM* vgmstream, int channel, sample_t* outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do);
/* acm_decoder */
@ -196,11 +198,11 @@ void decode_nds_procyon(VGMSTREAMCHANNEL* stream, sample_t* outbuf, int channels
/* l5_555_decoder */
void decode_l5_555(VGMSTREAMCHANNEL* stream, sample * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do);
void decode_l5_555(VGMSTREAMCHANNEL* stream, sample_t* outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do);
/* sassc_decoder */
void decode_sassc(VGMSTREAMCHANNEL* stream, sample * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do);
void decode_sassc(VGMSTREAMCHANNEL* stream, sample_t* outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do);
/* lsf_decode */
@ -216,7 +218,7 @@ void decode_mta2(VGMSTREAMCHANNEL* stream, sample_t* outbuf, int channelspacing,
/* mc3_decoder */
void decode_mc3(VGMSTREAM* vgmstream, VGMSTREAMCHANNEL* stream, sample * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do, int channel);
void decode_mc3(VGMSTREAM* vgmstream, VGMSTREAMCHANNEL* stream, sample_t* outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do, int channel);
/* fadpcm_decoder */
@ -242,11 +244,14 @@ int32_t tantalus_bytes_to_samples(size_t bytes, int channels);
/* derf_decoder */
void decode_derf(VGMSTREAMCHANNEL* stream, sample * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do);
void decode_derf(VGMSTREAMCHANNEL* stream, sample_t* outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do);
/* wady_decoder */
void decode_wady(VGMSTREAMCHANNEL* stream, sample_t* outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do);
/* dpcm_kcej_decoder */
void decode_dpcm_kcej(VGMSTREAMCHANNEL* stream, sample_t* outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do);
/* circus_decoder */
typedef struct circus_codec_data circus_codec_data;
@ -290,6 +295,16 @@ void reset_imuse(imuse_codec_data* data);
void seek_imuse(imuse_codec_data* data, int32_t num_sample);
void free_imuse(imuse_codec_data* data);
/* ongakukan_adp_decoder */
typedef struct ongakukan_adp_data ongakukan_adp_data;
ongakukan_adp_data* init_ongakukan_adp(STREAMFILE* sf, int32_t data_offset, int32_t data_size,
bool sound_is_adpcm);
void decode_ongakukan_adp(VGMSTREAM* vgmstream, sample_t* outbuf, int32_t samples_to_do);
void reset_ongakukan_adp(ongakukan_adp_data* data);
void seek_ongakukan_adp(ongakukan_adp_data* data, int32_t current_sample);
void free_ongakukan_adp(ongakukan_adp_data* data);
int32_t ongakukan_adp_get_samples(ongakukan_adp_data* data);
/* compresswave_decoder */
typedef struct compresswave_codec_data compresswave_codec_data;
@ -305,9 +320,10 @@ STREAMFILE* compresswave_get_streamfile(compresswave_codec_data* data);
/* ea_mt_decoder*/
typedef struct ea_mt_codec_data ea_mt_codec_data;
ea_mt_codec_data* init_ea_mt(int channels, int type);
ea_mt_codec_data* init_ea_mt(int channels, int pcm_blocks);
ea_mt_codec_data* init_ea_mt_loops(int channels, int pcm_blocks, int loop_sample, off_t* loop_offsets);
void decode_ea_mt(VGMSTREAM* vgmstream, sample * outbuf, int channelspacing, int32_t samples_to_do, int channel);
ea_mt_codec_data* init_ea_mt_cbx(int channels);
void decode_ea_mt(VGMSTREAM* vgmstream, sample_t* outbuf, int channelspacing, int32_t samples_to_do, int channel);
void reset_ea_mt(VGMSTREAM* vgmstream);
void flush_ea_mt(VGMSTREAM* vgmstream);
void seek_ea_mt(VGMSTREAM* vgmstream, int32_t num_sample);
@ -477,7 +493,6 @@ typedef enum {
MPEG_EAL32P, /* EALayer3 v2 "PCM", custom frames with v2 header + bigger PCM blocks? */
MPEG_EAL32S, /* EALayer3 v2 "Spike", custom frames with v2 header + smaller PCM blocks? */
MPEG_LYN, /* N streams of fixed interleave */
MPEG_AWC, /* N streams in block layout (music) or absolute offsets (sfx) */
MPEG_EAMP3 /* custom frame header + MPEG frame + PCM blocks */
} mpeg_custom_t;
@ -514,7 +529,8 @@ int mpeg_get_sample_rate(mpeg_codec_data* data);
long mpeg_bytes_to_samples(long bytes, const mpeg_codec_data* data);
uint32_t mpeg_get_tag_size(STREAMFILE* sf, uint32_t offset, uint32_t header);
int mpeg_get_frame_info(STREAMFILE* sf, off_t offset, mpeg_frame_info* info);
bool mpeg_get_frame_info(STREAMFILE* sf, off_t offset, mpeg_frame_info* info);
bool mpeg_get_frame_info_h(uint32_t header, mpeg_frame_info* info);
int test_ahx_key(STREAMFILE* sf, off_t offset, crikey_t* crikey);
#endif
@ -545,10 +561,19 @@ void free_g719(g719_codec_data* data, int channels);
#if defined(VGM_USE_MP4V2) && defined(VGM_USE_FDKAAC)
/* mp4_aac_decoder */
void decode_mp4_aac(mp4_aac_codec_data* data, sample * outbuf, int32_t samples_to_do, int channels);
typedef struct mp4_aac_codec_data mp4_aac_codec_data;
mp4_aac_codec_data* init_mp4_aac(STREAMFILE* sf);
void decode_mp4_aac(mp4_aac_codec_data* data, sample_t* outbuf, int32_t samples_to_do, int channels);
void reset_mp4_aac(VGMSTREAM* vgmstream);
void seek_mp4_aac(VGMSTREAM* vgmstream, int32_t num_sample);
void free_mp4_aac(mp4_aac_codec_data* data);
STREAMFILE* mp4_aac_get_streamfile(mp4_aac_codec_data* data);
int32_t mp4_aac_get_samples(mp4_aac_codec_data* data);
int32_t mp4_aac_get_samples_per_frame(mp4_aac_codec_data* data);
int mp4_aac_get_sample_rate(mp4_aac_codec_data* data);
int mp4_aac_get_channels(mp4_aac_codec_data* data);
#endif
@ -567,7 +592,7 @@ void reset_atrac9(atrac9_codec_data* data);
void seek_atrac9(VGMSTREAM* vgmstream, int32_t num_sample);
void free_atrac9(atrac9_codec_data* data);
size_t atrac9_bytes_to_samples(size_t bytes, atrac9_codec_data* data);
size_t atrac9_bytes_to_samples_cfg(size_t bytes, uint32_t atrac9_config);
size_t atrac9_bytes_to_samples_cfg(size_t bytes, uint32_t config_data);
#endif
@ -742,4 +767,4 @@ int mpc_get_samples(STREAMFILE* sf, off_t offset, int32_t* p_samples, int32_t* p
/* helper to pass a wrapped, clamped, fake extension-ed, SF to another meta */
STREAMFILE* setup_subfile_streamfile(STREAMFILE* sf, offv_t subfile_offset, size_t subfile_size, const char* extension);
#endif /*_CODING_H*/
#endif

View file

@ -1,6 +1,6 @@
#include "coding.h"
#include "coding_utils_samples.h"
#include "compresswave_decoder_lib.h"
#include "libs/compresswave_lib.h"
#define COMPRESSWAVE_MAX_FRAME_SAMPLES 0x1000 /* arbitrary but should be multiple of 2 for 22050 mode */

View file

@ -1,27 +0,0 @@
#ifndef _COMPRESSWAVE_DECODER_LIB_H
#define _COMPRESSWAVE_DECODER_LIB_H
#include "../streamfile.h"
typedef struct TCompressWaveData TCompressWaveData;
void TCompressWaveData_GetLoopState(TCompressWaveData* this);
void TCompressWaveData_SetLoopState(TCompressWaveData* this);
TCompressWaveData* TCompressWaveData_Create(void);
void TCompressWaveData_Free(TCompressWaveData* this);
int TCompressWaveData_Rendering(TCompressWaveData* this, int16_t* buf, uint32_t Len);
int TCompressWaveData_LoadFromStream(TCompressWaveData* this, STREAMFILE* ss);
void TCompressWaveData_SetCipherCode(TCompressWaveData* this, uint32_t Num);
void TCompressWaveData_Play(TCompressWaveData* this, int loop);
void TCompressWaveData_Stop(TCompressWaveData* this);
void TCompressWaveData_Previous(TCompressWaveData* this);
void TCompressWaveData_Pause(TCompressWaveData* this);
void TCompressWaveData_SetVolume(TCompressWaveData* this, float vol, float fade);
float TCompressWaveData_GetVolume(TCompressWaveData* this);
float TCompressWaveData_GetSetVolume(TCompressWaveData* this);
float TCompressWaveData_GetFade(TCompressWaveData* this);
float TCompressWaveData_GetPlayTime(TCompressWaveData* this);
float TCompressWaveData_GetTotalTime(TCompressWaveData* this);
#endif /*_COMPRESSWAVE_DECODER_LIB_H */

View file

@ -18,7 +18,7 @@ static const int derf_steps[96] = {
};
/* Xilam DERF DPCM for Stupid Invaders (PC), decompiled from the exe */
void decode_derf(VGMSTREAMCHANNEL * stream, sample * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do) {
void decode_derf(VGMSTREAMCHANNEL* stream, sample_t* outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do) {
int i, sample_pos = 0, index;
int32_t hist = stream->adpcm_history1_32;
off_t frame_offset = stream->offset; /* frame size is 1 */

View file

@ -0,0 +1,44 @@
#include "coding.h"
static int expand_code(uint8_t code) {
int neg = code & 0x80;
int cmd = code & 0x07;
int v;
if (cmd == 7)
v = (code & 0x78) << 8;
else
v = ((code & 0x78) | 0x80) << 7;
v = (v >> cmd);
if (neg)
v = -v;
return v;
}
/* from the decompilation, mono mode seems to do this:
* hist_a += expand_code(codes[i++])
* hist_b += decode_byte(codes[i++])
* sample = (hist_a + hist_b) / 2;
* out[s++] = sample; //L
* out[s++] = sample; //R, repeated for to make fake stereo)
* Existing files seem to be all stereo though
*/
/* decompiled from the exe */
void decode_dpcm_kcej(VGMSTREAMCHANNEL* stream, sample_t* outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do) {
off_t frame_offset = stream->offset; /* frame size is 1 */
int32_t hist = stream->adpcm_history1_32;
int sample_pos = 0;
for (int i = first_sample; i < first_sample + samples_to_do; i++) {
uint8_t code = read_u8(frame_offset + i, stream->streamfile);
hist += expand_code(code);
outbuf[sample_pos] = hist; /* no clamp */
sample_pos += channelspacing;
}
stream->adpcm_history1_32 = hist;
}

View file

@ -1,17 +1,8 @@
#include "coding.h"
#include "libs/utkdec.h"
#include "ea_mt_decoder_utk.h"
/* Decodes EA MicroTalk */
/* Decodes EA MicroTalk (speech codec) using utkencode lib (slightly modified for vgmstream).
* EA separates MT10:1 and MT5:1 (bigger frames), but apparently are the same
* with different encoding parameters. Later revisions may have PCM blocks (rare).
*
* Decoder by Andrew D'Addesio: https://github.com/daddesio/utkencode
* Info: http://wiki.niotso.org/UTK
*/
//#define UTK_MAKE_U32(a,b,c,d) ((a)|((b)<<8)|((c)<<16)|((d)<<24))
#define UTK_ROUND(x) ((x) >= 0.0f ? ((x)+0.5f) : ((x)-0.5f))
#define UTK_MIN(x,y) ((x)<(y)?(x):(y))
#define UTK_MAX(x,y) ((x)>(y)?(x):(y))
@ -26,21 +17,30 @@ struct ea_mt_codec_data {
off_t loop_offset;
int loop_sample;
int pcm_blocks;
int samples_filled;
int samples_used;
int samples_done;
int samples_discard;
void* utk_context;
void* ctx;
};
static size_t ea_mt_read_callback(void *dest, int size, void *arg);
static ea_mt_codec_data* init_ea_mt_internal(utk_type_t type, int channels, int loop_sample, off_t* loop_offsets);
ea_mt_codec_data* init_ea_mt(int channels, int pcm_blocks) {
return init_ea_mt_loops(channels, pcm_blocks, 0, NULL);
}
ea_mt_codec_data* init_ea_mt_loops(int channels, int pcm_blocks, int loop_sample, off_t *loop_offsets) {
return init_ea_mt_internal(pcm_blocks ? UTK_EA_PCM : UTK_EA, channels, loop_sample, loop_offsets);
}
ea_mt_codec_data* init_ea_mt_cbx(int channels) {
return init_ea_mt_internal(UTK_CBX, channels, 0, NULL);
}
static ea_mt_codec_data* init_ea_mt_internal(utk_type_t type, int channels, int loop_sample, off_t* loop_offsets) {
ea_mt_codec_data* data = NULL;
int i;
@ -48,16 +48,14 @@ ea_mt_codec_data* init_ea_mt_loops(int channels, int pcm_blocks, int loop_sample
if (!data) goto fail;
for (i = 0; i < channels; i++) {
data[i].utk_context = calloc(1, sizeof(UTKContext));
if (!data[i].utk_context) goto fail;
utk_init(data[i].utk_context);
data[i].ctx = utk_init(type);
if (!data[i].ctx) goto fail;
data[i].pcm_blocks = pcm_blocks;
data[i].loop_sample = loop_sample;
if (loop_offsets)
data[i].loop_offset = loop_offsets[i];
utk_set_callback(data[i].utk_context, data[i].buffer, UTK_BUFFER_SIZE, &data[i], &ea_mt_read_callback);
utk_set_callback(data[i].ctx, data[i].buffer, UTK_BUFFER_SIZE, &data[i], &ea_mt_read_callback);
}
return data;
@ -71,10 +69,9 @@ void decode_ea_mt(VGMSTREAM* vgmstream, sample_t* outbuf, int channelspacing, in
int i;
ea_mt_codec_data* data = vgmstream->codec_data;
ea_mt_codec_data* ch_data = &data[channel];
UTKContext* ctx = ch_data->utk_context;
int samples_done = 0;
float* fbuf = utk_get_samples(ch_data->ctx);
while (samples_done < samples_to_do) {
if (ch_data->samples_filled) {
@ -98,7 +95,7 @@ void decode_ea_mt(VGMSTREAM* vgmstream, sample_t* outbuf, int channelspacing, in
samples_to_get = samples_to_do - samples_done;
for (i = ch_data->samples_used; i < ch_data->samples_used + samples_to_get; i++) {
int pcm = UTK_ROUND(ctx->decompressed_frame[i]);
int pcm = UTK_ROUND(fbuf[i]);
outbuf[0] = (int16_t)UTK_CLAMP(pcm, -32768, 32767);
outbuf += channelspacing;
}
@ -119,19 +116,20 @@ void decode_ea_mt(VGMSTREAM* vgmstream, sample_t* outbuf, int channelspacing, in
/* offset is usually at loop_offset here, but not always (ex. loop_sample < 432) */
ch_data->offset = ch_data->loop_offset;
utk_set_ptr(ctx, 0, 0); /* reset the buffer reader */
utk_reset(ctx); /* decoder init (all fields must be reset, for some edge cases) */
utk_set_buffer(ch_data->ctx, 0, 0); /* reset the buffer reader */
utk_reset(ch_data->ctx); /* decoder init (all fields must be reset, for some edge cases) */
}
}
else {
/* new frame */
if (ch_data->pcm_blocks)
utk_rev3_decode_frame(ctx);
else
utk_decode_frame(ctx);
int samples = utk_decode_frame(ch_data->ctx);
if (samples < 0) {
VGM_LOG("wrong decode: %i\n", samples);
samples = 432;
}
ch_data->samples_used = 0;
ch_data->samples_filled = 432;
ch_data->samples_filled = samples;
}
}
}
@ -143,23 +141,20 @@ static void flush_ea_mt_offsets(VGMSTREAM* vgmstream, int is_start, int samples_
if (!data) return;
/* EA-MT frames are VBR (not byte-aligned?), so utk_decoder reads new buffer data automatically.
/* EA-MT frames are VBR and not byte-aligned, so utk_decoder reads new buffer data automatically.
* When decoding starts or a SCHl block changes, flush_ea_mt must be called to reset the state.
* A bit hacky but would need some restructuring otherwise. */
for (i = 0; i < vgmstream->channels; i++) {
UTKContext* ctx = data[i].utk_context;
data[i].streamfile = vgmstream->ch[i].streamfile; /* maybe should keep its own STREAMFILE? */
data[i].streamfile = vgmstream->ch[i].streamfile;
if (is_start)
data[i].offset = vgmstream->ch[i].channel_start_offset;
else
data[i].offset = vgmstream->ch[i].offset;
utk_set_ptr(ctx, 0, 0); /* reset the buffer reader */
utk_set_buffer(data[i].ctx, 0, 0); /* reset the buffer reader */
if (is_start) {
utk_reset(ctx);
ctx->parsed_header = 0;
utk_reset(data[i].ctx);
data[i].samples_done = 0;
}
@ -187,7 +182,7 @@ void free_ea_mt(ea_mt_codec_data* data, int channels) {
return;
for (i = 0; i < channels; i++) {
free(data[i].utk_context);
utk_free(data[i].ctx);
}
free(data);
}

View file

@ -1,469 +0,0 @@
#ifndef _EA_MT_DECODER_UTK_H_
#define _EA_MT_DECODER_UTK_H_
#include <stdint.h>
#include <string.h>
/* Note: This struct assumes a member alignment of 4 bytes.
** This matters when pitch_lag > 216 on the first subframe of any given frame. */
typedef struct UTKContext {
uint8_t *buffer;
size_t buffer_size;
void *arg;
size_t (*read_callback)(void *dest, int size, void *arg);
const uint8_t *ptr, *end;
int parsed_header;
unsigned int bits_value;
int bits_count;
int reduced_bw;
int multipulse_thresh;
float fixed_gains[64];
float rc[12];
float synth_history[12];
float adapt_cb[324];
float decompressed_frame[432];
} UTKContext;
enum {
MDL_NORMAL = 0,
MDL_LARGEPULSE = 1
};
static const float utk_rc_table[64] = {
+0.0f,
-.99677598476409912109375f, -.99032700061798095703125f, -.983879029750823974609375f, -.977430999279022216796875f,
-.970982015132904052734375f, -.964533984661102294921875f, -.958085000514984130859375f, -.9516370296478271484375f,
-.930754005908966064453125f, -.904959976673126220703125f, -.879167020320892333984375f, -.853372991085052490234375f,
-.827579021453857421875f, -.801786005496978759765625f, -.775991976261138916015625f, -.75019800662994384765625f,
-.724404990673065185546875f, -.6986110210418701171875f, -.6706349849700927734375f, -.61904799938201904296875f,
-.567460000514984130859375f, -.515873014926910400390625f, -.4642859995365142822265625f, -.4126980006694793701171875f,
-.361110985279083251953125f, -.309523999691009521484375f, -.257937014102935791015625f, -.20634900033473968505859375f,
-.1547619998455047607421875f, -.10317499935626983642578125f, -.05158700048923492431640625f,
+0.0f,
+.05158700048923492431640625f, +.10317499935626983642578125f, +.1547619998455047607421875f, +.20634900033473968505859375f,
+.257937014102935791015625f, +.309523999691009521484375f, +.361110985279083251953125f, +.4126980006694793701171875f,
+.4642859995365142822265625f, +.515873014926910400390625f, +.567460000514984130859375f, +.61904799938201904296875f,
+.6706349849700927734375f, +.6986110210418701171875f, +.724404990673065185546875f, +.75019800662994384765625f,
+.775991976261138916015625f, +.801786005496978759765625f, +.827579021453857421875f, +.853372991085052490234375f,
+.879167020320892333984375f, +.904959976673126220703125f, +.930754005908966064453125f, +.9516370296478271484375f,
+.958085000514984130859375f, +.964533984661102294921875f, +.970982015132904052734375f, +.977430999279022216796875f,
+.983879029750823974609375f, +.99032700061798095703125f, +.99677598476409912109375f
};
static const uint8_t utk_codebooks[2][256] = {
{ /* normal model */
4, 6, 5, 9, 4, 6, 5, 13, 4, 6, 5, 10, 4, 6, 5, 17,
4, 6, 5, 9, 4, 6, 5, 14, 4, 6, 5, 10, 4, 6, 5, 21,
4, 6, 5, 9, 4, 6, 5, 13, 4, 6, 5, 10, 4, 6, 5, 18,
4, 6, 5, 9, 4, 6, 5, 14, 4, 6, 5, 10, 4, 6, 5, 25,
4, 6, 5, 9, 4, 6, 5, 13, 4, 6, 5, 10, 4, 6, 5, 17,
4, 6, 5, 9, 4, 6, 5, 14, 4, 6, 5, 10, 4, 6, 5, 22,
4, 6, 5, 9, 4, 6, 5, 13, 4, 6, 5, 10, 4, 6, 5, 18,
4, 6, 5, 9, 4, 6, 5, 14, 4, 6, 5, 10, 4, 6, 5, 0,
4, 6, 5, 9, 4, 6, 5, 13, 4, 6, 5, 10, 4, 6, 5, 17,
4, 6, 5, 9, 4, 6, 5, 14, 4, 6, 5, 10, 4, 6, 5, 21,
4, 6, 5, 9, 4, 6, 5, 13, 4, 6, 5, 10, 4, 6, 5, 18,
4, 6, 5, 9, 4, 6, 5, 14, 4, 6, 5, 10, 4, 6, 5, 26,
4, 6, 5, 9, 4, 6, 5, 13, 4, 6, 5, 10, 4, 6, 5, 17,
4, 6, 5, 9, 4, 6, 5, 14, 4, 6, 5, 10, 4, 6, 5, 22,
4, 6, 5, 9, 4, 6, 5, 13, 4, 6, 5, 10, 4, 6, 5, 18,
4, 6, 5, 9, 4, 6, 5, 14, 4, 6, 5, 10, 4, 6, 5, 2
}, { /* large-pulse model */
4, 11, 7, 15, 4, 12, 8, 19, 4, 11, 7, 16, 4, 12, 8, 23,
4, 11, 7, 15, 4, 12, 8, 20, 4, 11, 7, 16, 4, 12, 8, 27,
4, 11, 7, 15, 4, 12, 8, 19, 4, 11, 7, 16, 4, 12, 8, 24,
4, 11, 7, 15, 4, 12, 8, 20, 4, 11, 7, 16, 4, 12, 8, 1,
4, 11, 7, 15, 4, 12, 8, 19, 4, 11, 7, 16, 4, 12, 8, 23,
4, 11, 7, 15, 4, 12, 8, 20, 4, 11, 7, 16, 4, 12, 8, 28,
4, 11, 7, 15, 4, 12, 8, 19, 4, 11, 7, 16, 4, 12, 8, 24,
4, 11, 7, 15, 4, 12, 8, 20, 4, 11, 7, 16, 4, 12, 8, 3,
4, 11, 7, 15, 4, 12, 8, 19, 4, 11, 7, 16, 4, 12, 8, 23,
4, 11, 7, 15, 4, 12, 8, 20, 4, 11, 7, 16, 4, 12, 8, 27,
4, 11, 7, 15, 4, 12, 8, 19, 4, 11, 7, 16, 4, 12, 8, 24,
4, 11, 7, 15, 4, 12, 8, 20, 4, 11, 7, 16, 4, 12, 8, 1,
4, 11, 7, 15, 4, 12, 8, 19, 4, 11, 7, 16, 4, 12, 8, 23,
4, 11, 7, 15, 4, 12, 8, 20, 4, 11, 7, 16, 4, 12, 8, 28,
4, 11, 7, 15, 4, 12, 8, 19, 4, 11, 7, 16, 4, 12, 8, 24,
4, 11, 7, 15, 4, 12, 8, 20, 4, 11, 7, 16, 4, 12, 8, 3
}
};
static const struct {
int next_model;
int code_size;
float pulse_value;
} utk_commands[29] = {
{MDL_LARGEPULSE, 8, 0.0f},
{MDL_LARGEPULSE, 7, 0.0f},
{MDL_NORMAL, 8, 0.0f},
{MDL_NORMAL, 7, 0.0f},
{MDL_NORMAL, 2, 0.0f},
{MDL_NORMAL, 2, -1.0f},
{MDL_NORMAL, 2, +1.0f},
{MDL_NORMAL, 3, -1.0f},
{MDL_NORMAL, 3, +1.0f},
{MDL_LARGEPULSE, 4, -2.0f},
{MDL_LARGEPULSE, 4, +2.0f},
{MDL_LARGEPULSE, 3, -2.0f},
{MDL_LARGEPULSE, 3, +2.0f},
{MDL_LARGEPULSE, 5, -3.0f},
{MDL_LARGEPULSE, 5, +3.0f},
{MDL_LARGEPULSE, 4, -3.0f},
{MDL_LARGEPULSE, 4, +3.0f},
{MDL_LARGEPULSE, 6, -4.0f},
{MDL_LARGEPULSE, 6, +4.0f},
{MDL_LARGEPULSE, 5, -4.0f},
{MDL_LARGEPULSE, 5, +4.0f},
{MDL_LARGEPULSE, 7, -5.0f},
{MDL_LARGEPULSE, 7, +5.0f},
{MDL_LARGEPULSE, 6, -5.0f},
{MDL_LARGEPULSE, 6, +5.0f},
{MDL_LARGEPULSE, 8, -6.0f},
{MDL_LARGEPULSE, 8, +6.0f},
{MDL_LARGEPULSE, 7, -6.0f},
{MDL_LARGEPULSE, 7, +6.0f}
};
static int utk_read_byte(UTKContext *ctx)
{
if (ctx->ptr < ctx->end)
return *ctx->ptr++;
if (ctx->read_callback) {
size_t bytes_copied = ctx->read_callback(ctx->buffer, ctx->buffer_size, ctx->arg);
if (bytes_copied > 0 && bytes_copied <= ctx->buffer_size) {
ctx->ptr = ctx->buffer;
ctx->end = ctx->buffer + bytes_copied;
return *ctx->ptr++;
}
}
return 0;
}
static int16_t utk_read_i16(UTKContext *ctx)
{
int x = utk_read_byte(ctx);
x = (x << 8) | utk_read_byte(ctx);
return x;
}
static int utk_read_bits(UTKContext *ctx, int count)
{
int ret = ctx->bits_value & ((1 << count) - 1);
ctx->bits_value >>= count;
ctx->bits_count -= count;
if (ctx->bits_count < 8) {
/* read another byte */
ctx->bits_value |= utk_read_byte(ctx) << ctx->bits_count;
ctx->bits_count += 8;
}
return ret;
}
static void utk_parse_header(UTKContext *ctx)
{
int i;
float multiplier;
ctx->reduced_bw = utk_read_bits(ctx, 1);
ctx->multipulse_thresh = 32 - utk_read_bits(ctx, 4);
ctx->fixed_gains[0] = 8.0f * (1 + utk_read_bits(ctx, 4));
multiplier = 1.04f + utk_read_bits(ctx, 6)*0.001f;
for (i = 1; i < 64; i++)
ctx->fixed_gains[i] = ctx->fixed_gains[i-1] * multiplier;
}
static void utk_decode_excitation(UTKContext *ctx, int use_multipulse, float *out, int stride)
{
int i;
if (use_multipulse) {
/* multi-pulse model: n pulses are coded explicitly; the rest are zero */
int model, cmd;
model = 0;
i = 0;
while (i < 108) {
cmd = utk_codebooks[model][ctx->bits_value & 0xff];
model = utk_commands[cmd].next_model;
utk_read_bits(ctx, utk_commands[cmd].code_size);
if (cmd > 3) {
/* insert a pulse with magnitude <= 6.0f */
out[i] = utk_commands[cmd].pulse_value;
i += stride;
} else if (cmd > 1) {
/* insert between 7 and 70 zeros */
int count = 7 + utk_read_bits(ctx, 6);
if (i + count * stride > 108)
count = (108 - i)/stride;
while (count > 0) {
out[i] = 0.0f;
i += stride;
count--;
}
} else {
/* insert a pulse with magnitude >= 7.0f */
int x = 7;
while (utk_read_bits(ctx, 1))
x++;
if (!utk_read_bits(ctx, 1))
x *= -1;
out[i] = (float)x;
i += stride;
}
}
} else {
/* RELP model: entire residual (excitation) signal is coded explicitly */
i = 0;
while (i < 108) {
if (!utk_read_bits(ctx, 1))
out[i] = 0.0f;
else if (!utk_read_bits(ctx, 1))
out[i] = -2.0f;
else
out[i] = 2.0f;
i += stride;
}
}
}
static void rc_to_lpc(const float *rc, float *lpc)
{
int i, j;
float tmp1[12];
float tmp2[12];
for (i = 10; i >= 0; i--)
tmp2[1+i] = rc[i];
tmp2[0] = 1.0f;
for (i = 0; i < 12; i++) {
float x = -tmp2[11] * rc[11];
for (j = 10; j >= 0; j--) {
x -= tmp2[j] * rc[j];
tmp2[j+1] = x * rc[j] + tmp2[j];
}
tmp1[i] = tmp2[0] = x;
for (j = 0; j < i; j++)
x -= tmp1[i-1-j] * lpc[j];
lpc[i] = x;
}
}
static void utk_lp_synthesis_filter(UTKContext *ctx, int offset, int num_blocks)
{
int i, j, k;
float lpc[12];
float *ptr = &ctx->decompressed_frame[offset];
rc_to_lpc(ctx->rc, lpc);
for (i = 0; i < num_blocks; i++) {
for (j = 0; j < 12; j++) {
float x = *ptr;
for (k = 0; k < j; k++)
x += lpc[k] * ctx->synth_history[k-j+12];
for (; k < 12; k++)
x += lpc[k] * ctx->synth_history[k-j];
ctx->synth_history[11-j] = x;
*ptr++ = x;
}
}
}
/*
** Public functions.
*/
static int utk_decode_frame(UTKContext *ctx)
{
int i, j;
int use_multipulse = 0;
float excitation[5+108+5];
float rc_delta[12];
if (!ctx->bits_count) {
ctx->bits_value = utk_read_byte(ctx);
ctx->bits_count = 8;
}
if (!ctx->parsed_header) {
utk_parse_header(ctx);
ctx->parsed_header = 1;
}
memset(&excitation[0], 0, 5*sizeof(float));
memset(&excitation[5+108], 0, 5*sizeof(float));
/* read the reflection coefficients */
for (i = 0; i < 12; i++) {
int idx;
if (i == 0) {
idx = utk_read_bits(ctx, 6);
if (idx < ctx->multipulse_thresh)
use_multipulse = 1;
} else if (i < 4) {
idx = utk_read_bits(ctx, 6);
} else {
idx = 16 + utk_read_bits(ctx, 5);
}
rc_delta[i] = (utk_rc_table[idx] - ctx->rc[i])*0.25f;
}
/* decode four subframes */
for (i = 0; i < 4; i++) {
int pitch_lag = utk_read_bits(ctx, 8);
float pitch_gain = (float)utk_read_bits(ctx, 4)/15.0f;
float fixed_gain = ctx->fixed_gains[utk_read_bits(ctx, 6)];
if (!ctx->reduced_bw) {
utk_decode_excitation(ctx, use_multipulse, &excitation[5], 1);
} else {
/* residual (excitation) signal is encoded at reduced bandwidth */
int align = utk_read_bits(ctx, 1);
int zero = utk_read_bits(ctx, 1);
utk_decode_excitation(ctx, use_multipulse, &excitation[5+align], 2);
if (zero) {
/* fill the remaining samples with zero
** (spectrum is duplicated into high frequencies) */
for (j = 0; j < 54; j++)
excitation[5+(1-align)+2*j] = 0.0f;
} else {
/* interpolate the remaining samples
** (spectrum is low-pass filtered) */
float *ptr = &excitation[5+(1-align)];
for (j = 0; j < 108; j += 2)
ptr[j] = ptr[j-5] * 0.01803267933428287506103515625f
- ptr[j-3] * 0.114591561257839202880859375f
+ ptr[j-1] * 0.597385942935943603515625f
+ ptr[j+1] * 0.597385942935943603515625f
- ptr[j+3] * 0.114591561257839202880859375f
+ ptr[j+5] * 0.01803267933428287506103515625f;
/* scale by 0.5f to give the sinc impulse response unit energy */
fixed_gain *= 0.5f;
}
}
for (j = 0; j < 108; j++)
ctx->decompressed_frame[108*i+j] = fixed_gain * excitation[5+j]
+ pitch_gain * ctx->adapt_cb[108*i+216-pitch_lag+j];
}
for (i = 0; i < 324; i++)
ctx->adapt_cb[i] = ctx->decompressed_frame[108+i];
for (i = 0; i < 4; i++) {
for (j = 0; j < 12; j++)
ctx->rc[j] += rc_delta[j];
utk_lp_synthesis_filter(ctx, 12*i, i < 3 ? 1 : 33);
}
return 0;
}
static void utk_init(UTKContext *ctx)
{
memset(ctx, 0, sizeof(*ctx));
}
static void utk_reset(UTKContext *ctx)
{
/* resets the internal state, leaving the external config/buffers
* untouched (could be reset externally or using utk_set_x) */
ctx->parsed_header = 0;
ctx->bits_value = 0;
ctx->bits_count = 0;
ctx->reduced_bw = 0;
ctx->multipulse_thresh = 0;
memset(ctx->fixed_gains, 0, sizeof(ctx->fixed_gains));
memset(ctx->rc, 0, sizeof(ctx->rc));
memset(ctx->synth_history, 0, sizeof(ctx->synth_history));
memset(ctx->adapt_cb, 0, sizeof(ctx->adapt_cb));
memset(ctx->decompressed_frame, 0, sizeof(ctx->decompressed_frame));
}
static void utk_set_callback(UTKContext *ctx, uint8_t *buffer, size_t buffer_size, void *arg, size_t (*read_callback)(void *, int , void *))
{
/* prepares for external reading */
ctx->buffer = buffer;
ctx->buffer_size = buffer_size;
ctx->arg = arg;
ctx->read_callback = read_callback;
/* reset the bit reader */
ctx->bits_count = 0;
}
static void utk_set_ptr(UTKContext *ctx, const uint8_t *ptr, const uint8_t *end)
{
/* sets the pointer to an external data buffer (can also be used to
* reset the buffered data if set to ptr/end 0) */
ctx->ptr = ptr;
ctx->end = end;
/* reset the bit reader */
ctx->bits_count = 0;
}
/*
** MicroTalk Revision 3 decoding function.
*/
static int utk_rev3_decode_frame(UTKContext *ctx)
{
int pcm_data_present = (utk_read_byte(ctx) == 0xee);
int i;
utk_decode_frame(ctx);
/* unread the last 8 bits and reset the bit reader */
ctx->ptr--;
ctx->bits_count = 0;
if (pcm_data_present) {
/* Overwrite n samples at a given offset in the decoded frame with
** raw PCM data. */
int offset = utk_read_i16(ctx);
int count = utk_read_i16(ctx);
/* sx.exe does not do any bounds checking or clamping of these two
** fields (see 004274D1 in sx.exe v3.01.01), which means a specially
** crafted MT5:1 file can crash sx.exe.
** We will throw an error instead. */
if (offset < 0 || offset > 432) {
return -1; /* invalid PCM offset */
}
if (count < 0 || count > 432 - offset) {
return -2; /* invalid PCM count */
}
for (i = 0; i < count; i++)
ctx->decompressed_frame[offset+i] = (float)utk_read_i16(ctx);
}
return 0;
}
#endif /* _EA_MT_DECODER_UTK_H_ */

View file

@ -33,7 +33,7 @@ static const float xa_coefs[16][2] = {
void decode_ea_xas_v1(VGMSTREAMCHANNEL* stream, sample_t* outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do, int channel) {
uint8_t frame[0x4c] = {0};
off_t frame_offset;
int group, row, i, samples_done = 0, sample_count = 0;
int samples_done = 0, sample_count = 0;
size_t bytes_per_frame, samples_per_frame;
@ -55,11 +55,11 @@ void decode_ea_xas_v1(VGMSTREAMCHANNEL* stream, sample_t* outbuf, int channelspa
/* parse group headers */
for (group = 0; group < 4; group++) {
for (int group = 0; group < 4; group++) {
float coef1, coef2;
int16_t hist1, hist2;
uint8_t shift;
uint32_t group_header = (uint32_t)get_32bitLE(frame + group*0x4); /* always LE */
uint32_t group_header = get_u32le(frame + group*0x4); /* always LE */
coef1 = xa_coefs[group_header & 0x0F][0];
coef2 = xa_coefs[group_header & 0x0F][1];
@ -80,8 +80,8 @@ void decode_ea_xas_v1(VGMSTREAMCHANNEL* stream, sample_t* outbuf, int channelspa
sample_count++;
/* process nibbles per group */
for (row = 0; row < 15; row++) {
for (i = 0; i < 1*2; i++) {
for (int row = 0; row < 15; row++) {
for (int i = 0; i < 1 * 2; i++) {
uint8_t nibbles = frame[4*4 + row*0x04 + group + i/2];
int sample;
@ -113,10 +113,10 @@ void decode_ea_xas_v1(VGMSTREAMCHANNEL* stream, sample_t* outbuf, int channelspa
/* EA-XAS v0 (xas0), without complex layouts and closer to EA-XA. Somewhat based on daemon1's decoder. */
void decode_ea_xas_v0(VGMSTREAMCHANNEL * stream, sample * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do) {
void decode_ea_xas_v0(VGMSTREAMCHANNEL* stream, sample_t* outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do) {
uint8_t frame[0x13] = {0};
off_t frame_offset;
int i, frames_in, samples_done = 0, sample_count = 0;
int frames_in, samples_done = 0, sample_count = 0;
size_t bytes_per_frame, samples_per_frame;
@ -129,14 +129,14 @@ void decode_ea_xas_v0(VGMSTREAMCHANNEL * stream, sample * outbuf, int channelspa
frame_offset = stream->offset + bytes_per_frame * frames_in;
read_streamfile(frame, frame_offset, bytes_per_frame, stream->streamfile); /* ignore EOF errors */
//todo see above
//TODO make expand function and fuse with above
/* process frame */
{
float coef1, coef2;
int16_t hist1, hist2;
uint8_t shift;
uint32_t frame_header = (uint32_t)get_32bitLE(frame); /* always LE */
uint32_t frame_header = get_u32le(frame); /* always LE */
coef1 = xa_coefs[frame_header & 0x0F][0];
coef2 = xa_coefs[frame_header & 0x0F][1];
@ -157,7 +157,7 @@ void decode_ea_xas_v0(VGMSTREAMCHANNEL * stream, sample * outbuf, int channelspa
sample_count++;
/* process nibbles */
for (i = 0; i < 0x0f*2; i++) {
for (int i = 0; i < 0x0f * 2; i++) {
uint8_t nibbles = frame[0x02 + 0x02 + i/2];
int sample;

View file

@ -430,10 +430,26 @@ fail:
return NULL;
}
/* FFmpeg internals (roughly) for reference:
*
* AVFormatContext // base info extracted from input file
* AVStream // substreams
* AVCodecParameters // codec id, channels, format, ...
*
* AVCodecContext // sample rate and general info
*
* - open avformat to get all possible format info (needs file or custom IO)
* - open avcodec based on target stream + codec info from avformat
* - decode chunks of data (feed style)
* - read next frame into packet via avformat
* - decode packet via avcodec
* - handle samples
*/
static int init_ffmpeg_config(ffmpeg_codec_data* data, int target_subsong, int reset) {
int errcode = 0;
/* basic IO/format setup */
/* custom IO/format setup */
data->buffer = av_malloc(FFMPEG_DEFAULT_IO_BUFFER_SIZE);
if (!data->buffer) goto fail;
@ -448,13 +464,14 @@ static int init_ffmpeg_config(ffmpeg_codec_data* data, int target_subsong, int r
//data->inputFormatCtx = av_find_input_format("h264"); /* set directly? */
/* on reset could use AVFormatContext.iformat to reload old format too */
/* format detection */
errcode = avformat_open_input(&data->formatCtx, NULL /*""*/, NULL, NULL);
if (errcode < 0) goto fail;
errcode = avformat_find_stream_info(data->formatCtx, NULL);
if (errcode < 0) goto fail;
/* find valid audio stream and set other streams to discard */
/* find valid audio stream and set other streams to be discarded */
{
int i, stream_index, stream_count;
@ -485,24 +502,22 @@ static int init_ffmpeg_config(ffmpeg_codec_data* data, int target_subsong, int r
data->stream_count = stream_count;
}
/* setup codec with stream info */
/* setup codec from stream info */
data->codecCtx = avcodec_alloc_context3(NULL);
if (!data->codecCtx) goto fail;
errcode = avcodec_parameters_to_context(data->codecCtx, data->formatCtx->streams[data->stream_index]->codecpar);
if (errcode < 0) goto fail;
/* deprecated and seemingly not needed */
//av_codec_set_pkt_timebase(data->codecCtx, stream->time_base);
//av_codec_set_pkt_timebase(data->codecCtx, stream->time_base); /* deprecated and seemingly not needed */
/* not useddeprecated and seemingly not needed */
data->codec = avcodec_find_decoder(data->codecCtx->codec_id);
if (!data->codec) goto fail;
errcode = avcodec_open2(data->codecCtx, data->codec, NULL);
if (errcode < 0) goto fail;
/* prepare codec and frame/packet buffers */
/* prepare frame/packet buffers */
data->packet = av_malloc(sizeof(AVPacket)); /* av_packet_alloc? */
if (!data->packet) goto fail;
av_new_packet(data->packet, 0);
@ -757,19 +772,29 @@ static void copy_samples(ffmpeg_codec_data* data, sample_t* outbuf, int samples_
switch (data->codecCtx->sample_fmt) {
/* unused? */
case AV_SAMPLE_FMT_U8P: if (is_planar) { samples_u8p_to_s16(outbuf, ibuf, channels, samples_to_do, data->samples_consumed); break; }
// fall through
case AV_SAMPLE_FMT_U8: samples_u8_to_s16(outbuf, ibuf, channels, samples_to_do, data->samples_consumed); break;
/* common */
case AV_SAMPLE_FMT_S16P: if (is_planar) { samples_s16p_to_s16(outbuf, ibuf, channels, samples_to_do, data->samples_consumed); break; }
// fall through
case AV_SAMPLE_FMT_S16: samples_s16_to_s16(outbuf, ibuf, channels, samples_to_do, data->samples_consumed); break;
/* possibly FLAC and other lossless codecs */
case AV_SAMPLE_FMT_S32P: if (is_planar) { samples_s32p_to_s16(outbuf, ibuf, channels, samples_to_do, data->samples_consumed); break; }
// fall through
case AV_SAMPLE_FMT_S32: samples_s32_to_s16(outbuf, ibuf, channels, samples_to_do, data->samples_consumed); break;
/* mainly MDCT-like codecs (Ogg, AAC, etc) */
case AV_SAMPLE_FMT_FLTP: if (is_planar) { samples_fltp_to_s16(outbuf, ibuf, channels, samples_to_do, data->samples_consumed, data->invert_floats_set); break; }
// fall through
case AV_SAMPLE_FMT_FLT: samples_flt_to_s16(outbuf, ibuf, channels, samples_to_do, data->samples_consumed, data->invert_floats_set); break;
/* possibly PCM64 only (not enabled) */
case AV_SAMPLE_FMT_DBLP: if (is_planar) { samples_dblp_to_s16(outbuf, ibuf, channels, samples_to_do, data->samples_consumed); break; }
// fall through
case AV_SAMPLE_FMT_DBL: samples_dbl_to_s16(outbuf, ibuf, channels, samples_to_do, data->samples_consumed); break;
default:
break;
}

View file

@ -273,7 +273,7 @@ static size_t opus_io_size(STREAMFILE* sf, opus_io_data* data) {
break;
}
if (data_size == 0) {
if (data_size <= 0 || data_size >= 0xFFFFF) { /* arbitrary max + catch -1/EOF */
VGM_LOG("OPUS: data_size is 0 at %x\n", (uint32_t)offset);
return 0; /* bad rip? or could 'break' and truck along */
}
@ -514,7 +514,7 @@ static size_t make_opus_header(uint8_t* buf, int buf_size, opus_config *cfg) {
/* set mapping family */
if (cfg->channels > 2 || cfg->stream_count > 1) {
mapping_family = 1; //todo test 255
mapping_family = 1;
header_size += 0x01 + 0x01 + cfg->channels; /* table size */
}
@ -525,7 +525,7 @@ static size_t make_opus_header(uint8_t* buf, int buf_size, opus_config *cfg) {
if (header_size > buf_size) {
VGM_LOG("OPUS: buffer can't hold header\n");
goto fail;
return 0;
}
put_u32be(buf+0x00, get_id32be("Opus"));
@ -539,21 +539,29 @@ static size_t make_opus_header(uint8_t* buf, int buf_size, opus_config *cfg) {
/* set mapping table */
if (mapping_family > 0) {
int i;
/* test if external mappings are correctly set, as incorrect values result in wrong output
* (ex. all 0s would mean "write channel L in every channel)")*/
bool mappings_set = false;
for (int i = 0; i < cfg->channels; i++) {
if (cfg->channel_mapping[i]) {
mappings_set = true;
break;
}
}
/* total streams (mono/stereo) */
put_u8(buf+0x13, cfg->stream_count);
/* stereo streams (6ch can be 2ch+2ch+1ch+1ch = 2 coupled in 4 streams) */
put_u8(buf+0x14, cfg->coupled_count);
/* mapping per channel (order of channels, ex: 00 01 04 05 02 03) */
for (i = 0; i < cfg->channels; i++) {
put_u8(buf+0x15+i, cfg->channel_mapping[i]);
for (int i = 0; i < cfg->channels; i++) {
uint8_t mapping = (mappings_set) ? cfg->channel_mapping[i] : i;
put_u8(buf+0x15+i, mapping);
}
}
return header_size;
fail:
return 0;
}
static size_t make_opus_comment(uint8_t* buf, int buf_size) {
@ -568,11 +576,11 @@ static size_t make_opus_comment(uint8_t* buf, int buf_size) {
if (comment_size > buf_size) {
VGM_LOG("OPUS: buffer can't hold comment\n");
goto fail;
return 0;
}
put_u32be(buf+0x00, 0x4F707573); /* "Opus" header magic */
put_u32be(buf+0x04, 0x54616773); /* "Tags" header magic */
put_u32be(buf+0x00, get_id32be("Opus"));
put_u32be(buf+0x04, get_id32be("Tags"));
put_u32le(buf+0x08, vendor_string_length);
memcpy (buf+0x0c, vendor_string, vendor_string_length);
put_u32le(buf+0x0c + vendor_string_length+0x00, 1); /* user_comment_list_length */
@ -580,8 +588,6 @@ static size_t make_opus_comment(uint8_t* buf, int buf_size) {
memcpy (buf+0x0c + vendor_string_length+0x08, user_comment_0_string, user_comment_0_length);
return comment_size;
fail:
return 0;
}
static size_t make_oggs_first(uint8_t* buf, int buf_size, opus_config* cfg) {
@ -636,7 +642,7 @@ static size_t get_table_frame_size(opus_io_data* data, int frame) {
static size_t custom_opus_get_samples(off_t offset, size_t stream_size, STREAMFILE* sf, opus_type_t type) {
size_t num_samples = 0;
off_t end_offset = offset + stream_size;
int packet = 0;
//int packet = 0;
if (end_offset > get_streamfile_size(sf)) {
VGM_LOG("OPUS: wrong end offset found\n");
@ -693,7 +699,7 @@ static size_t custom_opus_get_samples(off_t offset, size_t stream_size, STREAMFI
num_samples += packet_samples;
offset += skip_size + data_size;
packet++;
//packet++;
}
return num_samples;

View file

@ -22,7 +22,7 @@ static int ffmpeg_make_riff_atrac3(uint8_t* buf, size_t buf_size, size_t sample_
put_u16le(buf+0x14, 0x0270); /* ATRAC3 codec */
put_u16le(buf+0x16, channels);
put_u32le(buf+0x18, sample_rate);
put_u32le(buf+0x1c, sample_rate * channels / sizeof(sample)); /* average bytes per second (wrong) */
put_u32le(buf+0x1c, sample_rate * channels / sizeof(sample_t)); /* average bytes per second (wrong) */
put_u16le(buf+0x20, block_align); /* block align */
put_u16le(buf+0x24, 0x0e); /* extra data size */
@ -215,7 +215,7 @@ static int ffmpeg_make_riff_atrac3plus(uint8_t* buf, int buf_size, uint32_t data
put_u16le(buf+0x14, 0xfffe); /* WAVEFORMATEXTENSIBLE */
put_u16le(buf+0x16, channels);
put_u32le(buf+0x18, sample_rate);
put_u32le(buf+0x1c, sample_rate * channels / sizeof(sample)); /* average bytes per second (wrong) */
put_u32le(buf+0x1c, sample_rate * channels / sizeof(sample_t)); /* average bytes per second (wrong) */
put_u32le(buf+0x20, block_align); /* block align */
put_u16le(buf+0x24, 0x22); /* extra data size */
@ -417,7 +417,7 @@ static int ffmpeg_make_riff_xma1(uint8_t* buf, size_t buf_size, size_t data_size
}
}
put_u32le(buf+off+0x00, sample_rate*stream_channels / sizeof(sample)); /* average bytes per second (wrong, unneeded) */
put_u32le(buf+off+0x00, sample_rate*stream_channels / sizeof(sample_t)); /* average bytes per second (wrong, unneeded) */
put_u32le(buf+off+0x04, sample_rate);
put_u32le(buf+off+0x08, 0); /* loop start */
put_u32le(buf+off+0x0c, 0); /* loop end */
@ -477,7 +477,7 @@ static int ffmpeg_make_riff_xma2(uint8_t* buf, size_t buf_size, size_t data_size
default: speakers = 0; break;
}
bytecount = sample_count * channels * sizeof(sample);
bytecount = sample_count * channels * sizeof(sample_t);
memcpy (buf+0x00, "RIFF", 0x04);
put_u32le(buf+0x04, buf_max - (0x04 * 2) + data_size); /* riff size */
@ -488,8 +488,8 @@ static int ffmpeg_make_riff_xma2(uint8_t* buf, size_t buf_size, size_t data_size
put_u16le(buf+0x14, 0x0166); /* XMA2 */
put_u16le(buf+0x16, channels);
put_u32le(buf+0x18, sample_rate);
put_u32le(buf+0x1c, sample_rate * channels / sizeof(sample)); /* average bytes per second (wrong, unneeded) */
put_u16le(buf+0x20, (uint16_t)(channels * sizeof(sample))); /* block align */
put_u32le(buf+0x1c, sample_rate * channels / sizeof(sample_t)); /* average bytes per second (wrong, unneeded) */
put_u16le(buf+0x20, (uint16_t)(channels * sizeof(sample_t))); /* block align */
put_u16le(buf+0x22, 16); /* bits per sample */
put_u16le(buf+0x24, 0x22); /* extra data size */

View file

@ -65,24 +65,24 @@ quan(
*/
static int
fmult(
int an,
int srn)
int an,
int srn)
{
short anmag, anexp, anmant;
short wanexp, wanmant;
short retval;
short anmag, anexp, anmant;
short wanexp, wanmant;
short retval;
anmag = (an > 0) ? an : ((-an) & 0x1FFF);
anexp = quan(anmag, power2, 15) - 6;
anmant = (anmag == 0) ? 32 :
(anexp >= 0) ? anmag >> anexp : anmag << -anexp;
wanexp = anexp + ((srn >> 6) & 0xF) - 13;
anmag = (an > 0) ? an : ((-an) & 0x1FFF);
anexp = quan(anmag, power2, 15) - 6;
anmant = (anmag == 0) ? 32 :
(anexp >= 0) ? anmag >> anexp : anmag << -anexp;
wanexp = anexp + ((srn >> 6) & 0xF) - 13;
wanmant = (anmant * (srn & 077) + 0x30) >> 4;
retval = (wanexp >= 0) ? ((wanmant << wanexp) & 0x7FFF) :
(wanmant >> -wanexp);
wanmant = (anmant * (srn & 077) + 0x30) >> 4;
retval = (wanexp >= 0) ? ((wanmant << wanexp) & 0x7FFF) :
(wanmant >> -wanexp);
return (((an ^ srn) < 0) ? -retval : retval);
return (((an ^ srn) < 0) ? -retval : retval);
}
/*
@ -94,25 +94,25 @@ fmult(
*/
void
g72x_init_state(
struct g72x_state *state_ptr)
struct g72x_state *state_ptr)
{
int cnta;
int cnta;
state_ptr->yl = 34816;
state_ptr->yu = 544;
state_ptr->dms = 0;
state_ptr->dml = 0;
state_ptr->ap = 0;
for (cnta = 0; cnta < 2; cnta++) {
state_ptr->a[cnta] = 0;
state_ptr->pk[cnta] = 0;
state_ptr->sr[cnta] = 32;
}
for (cnta = 0; cnta < 6; cnta++) {
state_ptr->b[cnta] = 0;
state_ptr->dq[cnta] = 32;
}
state_ptr->td = 0;
state_ptr->yl = 34816;
state_ptr->yu = 544;
state_ptr->dms = 0;
state_ptr->dml = 0;
state_ptr->ap = 0;
for (cnta = 0; cnta < 2; cnta++) {
state_ptr->a[cnta] = 0;
state_ptr->pk[cnta] = 0;
state_ptr->sr[cnta] = 32;
}
for (cnta = 0; cnta < 6; cnta++) {
state_ptr->b[cnta] = 0;
state_ptr->dq[cnta] = 32;
}
state_ptr->td = 0;
}
/*
@ -123,15 +123,15 @@ g72x_init_state(
*/
static int
predictor_zero(
struct g72x_state *state_ptr)
struct g72x_state *state_ptr)
{
int i;
int sezi;
int i;
int sezi;
sezi = fmult(state_ptr->b[0] >> 2, state_ptr->dq[0]);
for (i = 1; i < 6; i++) /* ACCUM */
sezi += fmult(state_ptr->b[i] >> 2, state_ptr->dq[i]);
return (sezi);
sezi = fmult(state_ptr->b[0] >> 2, state_ptr->dq[0]);
for (i = 1; i < 6; i++) /* ACCUM */
sezi += fmult(state_ptr->b[i] >> 2, state_ptr->dq[i]);
return (sezi);
}
/*
* predictor_pole()
@ -141,10 +141,10 @@ predictor_zero(
*/
static int
predictor_pole(
struct g72x_state *state_ptr)
struct g72x_state *state_ptr)
{
return (fmult(state_ptr->a[1] >> 2, state_ptr->sr[1]) +
fmult(state_ptr->a[0] >> 2, state_ptr->sr[0]));
return (fmult(state_ptr->a[1] >> 2, state_ptr->sr[1]) +
fmult(state_ptr->a[0] >> 2, state_ptr->sr[0]));
}
/*
* step_size()
@ -154,24 +154,24 @@ predictor_pole(
*/
static int
step_size(
struct g72x_state *state_ptr)
struct g72x_state *state_ptr)
{
int y;
int dif;
int al;
int y;
int dif;
int al;
if (state_ptr->ap >= 256)
return (state_ptr->yu);
else {
y = state_ptr->yl >> 6;
dif = state_ptr->yu - y;
al = state_ptr->ap >> 2;
if (dif > 0)
y += (dif * al) >> 6;
else if (dif < 0)
y += (dif * al + 0x3F) >> 6;
return (y);
}
if (state_ptr->ap >= 256)
return (state_ptr->yu);
else {
y = state_ptr->yl >> 6;
dif = state_ptr->yu - y;
al = state_ptr->ap >> 2;
if (dif > 0)
y += (dif * al) >> 6;
else if (dif < 0)
y += (dif * al + 0x3F) >> 6;
return (y);
}
}
/*
@ -183,25 +183,25 @@ step_size(
*/
static int
reconstruct(
int sign, /* 0 for non-negative value */
int dqln, /* G.72x codeword */
int y) /* Step size multiplier */
int sign, /* 0 for non-negative value */
int dqln, /* G.72x codeword */
int y) /* Step size multiplier */
{
short dql; /* Log of 'dq' magnitude */
short dex; /* Integer part of log */
short dqt;
short dq; /* Reconstructed difference signal sample */
short dql; /* Log of 'dq' magnitude */
short dex; /* Integer part of log */
short dqt;
short dq; /* Reconstructed difference signal sample */
dql = dqln + (y >> 2); /* ADDA */
dql = dqln + (y >> 2); /* ADDA */
if (dql < 0) {
return ((sign) ? -0x8000 : 0);
} else { /* ANTILOG */
dex = (dql >> 7) & 15;
dqt = 128 + (dql & 127);
dq = (dqt << 7) >> (14 - dex);
return ((sign) ? (dq - 0x8000) : dq);
}
if (dql < 0) {
return ((sign) ? -0x8000 : 0);
} else { /* ANTILOG */
dex = (dql >> 7) & 15;
dqt = 128 + (dql & 127);
dq = (dqt << 7) >> (14 - dex);
return ((sign) ? (dq - 0x8000) : dq);
}
}
@ -212,212 +212,212 @@ reconstruct(
*/
static void
update(
/*int code_size,*/ /* distinguish 723_40 with others */
int y, /* quantizer step size */
int wi, /* scale factor multiplier */
int fi, /* for long/short term energies */
int dq, /* quantized prediction difference */
int sr, /* reconstructed signal */
int dqsez, /* difference from 2-pole predictor */
struct g72x_state *state_ptr) /* coder state pointer */
/*int code_size,*/ /* distinguish 723_40 with others */
int y, /* quantizer step size */
int wi, /* scale factor multiplier */
int fi, /* for long/short term energies */
int dq, /* quantized prediction difference */
int sr, /* reconstructed signal */
int dqsez, /* difference from 2-pole predictor */
struct g72x_state *state_ptr) /* coder state pointer */
{
int cnt;
short mag, exp; /* Adaptive predictor, FLOAT A */
short a2p; /* LIMC */
short a1ul; /* UPA1 */
short pks1; /* UPA2 */
short fa1;
char tr; /* tone/transition detector */
short ylint, thr2, dqthr;
short ylfrac, thr1;
short pk0;
int cnt;
short mag, exp; /* Adaptive predictor, FLOAT A */
short a2p; /* LIMC */
short a1ul; /* UPA1 */
short pks1; /* UPA2 */
short fa1;
char tr; /* tone/transition detector */
short ylint, thr2, dqthr;
short ylfrac, thr1;
short pk0;
pk0 = (dqsez < 0) ? 1 : 0; /* needed in updating predictor poles */
pk0 = (dqsez < 0) ? 1 : 0; /* needed in updating predictor poles */
mag = dq & 0x7FFF; /* prediction difference magnitude */
/* TRANS */
ylint = state_ptr->yl >> 15; /* exponent part of yl */
ylfrac = (state_ptr->yl >> 10) & 0x1F; /* fractional part of yl */
thr1 = (32 + ylfrac) << ylint; /* threshold */
thr2 = (ylint > 9) ? 31 << 10 : thr1; /* limit thr2 to 31 << 10 */
dqthr = (thr2 + (thr2 >> 1)) >> 1; /* dqthr = 0.75 * thr2 */
if (state_ptr->td == 0) /* signal supposed voice */
tr = 0;
else if (mag <= dqthr) /* supposed data, but small mag */
tr = 0; /* treated as voice */
else /* signal is data (modem) */
tr = 1;
mag = dq & 0x7FFF; /* prediction difference magnitude */
/* TRANS */
ylint = state_ptr->yl >> 15; /* exponent part of yl */
ylfrac = (state_ptr->yl >> 10) & 0x1F; /* fractional part of yl */
thr1 = (32 + ylfrac) << ylint; /* threshold */
thr2 = (ylint > 9) ? 31 << 10 : thr1; /* limit thr2 to 31 << 10 */
dqthr = (thr2 + (thr2 >> 1)) >> 1; /* dqthr = 0.75 * thr2 */
if (state_ptr->td == 0) /* signal supposed voice */
tr = 0;
else if (mag <= dqthr) /* supposed data, but small mag */
tr = 0; /* treated as voice */
else /* signal is data (modem) */
tr = 1;
/*
* Quantizer scale factor adaptation.
*/
/*
* Quantizer scale factor adaptation.
*/
/* FUNCTW & FILTD & DELAY */
/* update non-steady state step size multiplier */
state_ptr->yu = y + ((wi - y) >> 5);
/* FUNCTW & FILTD & DELAY */
/* update non-steady state step size multiplier */
state_ptr->yu = y + ((wi - y) >> 5);
/* LIMB */
if (state_ptr->yu < 544) /* 544 <= yu <= 5120 */
state_ptr->yu = 544;
else if (state_ptr->yu > 5120)
state_ptr->yu = 5120;
/* LIMB */
if (state_ptr->yu < 544) /* 544 <= yu <= 5120 */
state_ptr->yu = 544;
else if (state_ptr->yu > 5120)
state_ptr->yu = 5120;
/* FILTE & DELAY */
/* update steady state step size multiplier */
state_ptr->yl += state_ptr->yu + ((-state_ptr->yl) >> 6);
/* FILTE & DELAY */
/* update steady state step size multiplier */
state_ptr->yl += state_ptr->yu + ((-state_ptr->yl) >> 6);
/*
* Adaptive predictor coefficients.
*/
if (tr == 1) { /* reset a's and b's for modem signal */
state_ptr->a[0] = 0;
state_ptr->a[1] = 0;
state_ptr->b[0] = 0;
state_ptr->b[1] = 0;
state_ptr->b[2] = 0;
state_ptr->b[3] = 0;
state_ptr->b[4] = 0;
state_ptr->b[5] = 0;
/*
* Adaptive predictor coefficients.
*/
if (tr == 1) { /* reset a's and b's for modem signal */
state_ptr->a[0] = 0;
state_ptr->a[1] = 0;
state_ptr->b[0] = 0;
state_ptr->b[1] = 0;
state_ptr->b[2] = 0;
state_ptr->b[3] = 0;
state_ptr->b[4] = 0;
state_ptr->b[5] = 0;
a2p=0; /* won't be used, clear warning */
} else { /* update a's and b's */
pks1 = pk0 ^ state_ptr->pk[0]; /* UPA2 */
} else { /* update a's and b's */
pks1 = pk0 ^ state_ptr->pk[0]; /* UPA2 */
/* update predictor pole a[1] */
a2p = state_ptr->a[1] - (state_ptr->a[1] >> 7);
if (dqsez != 0) {
fa1 = (pks1) ? state_ptr->a[0] : -state_ptr->a[0];
if (fa1 < -8191) /* a2p = function of fa1 */
a2p -= 0x100;
else if (fa1 > 8191)
a2p += 0xFF;
else
a2p += fa1 >> 5;
/* update predictor pole a[1] */
a2p = state_ptr->a[1] - (state_ptr->a[1] >> 7);
if (dqsez != 0) {
fa1 = (pks1) ? state_ptr->a[0] : -state_ptr->a[0];
if (fa1 < -8191) /* a2p = function of fa1 */
a2p -= 0x100;
else if (fa1 > 8191)
a2p += 0xFF;
else
a2p += fa1 >> 5;
if (pk0 ^ state_ptr->pk[1])
/* LIMC */
if (a2p <= -12160)
a2p = -12288;
else if (a2p >= 12416)
a2p = 12288;
else
a2p -= 0x80;
else if (a2p <= -12416)
a2p = -12288;
else if (a2p >= 12160)
a2p = 12288;
else
a2p += 0x80;
}
/* TRIGB & DELAY */
state_ptr->a[1] = a2p;
/* UPA1 */
/* update predictor pole a[0] */
state_ptr->a[0] -= state_ptr->a[0] >> 8;
if (dqsez != 0) {
if (pks1 == 0)
state_ptr->a[0] += 192;
else
state_ptr->a[0] -= 192;
if (pk0 ^ state_ptr->pk[1])
/* LIMC */
if (a2p <= -12160)
a2p = -12288;
else if (a2p >= 12416)
a2p = 12288;
else
a2p -= 0x80;
else if (a2p <= -12416)
a2p = -12288;
else if (a2p >= 12160)
a2p = 12288;
else
a2p += 0x80;
}
/* LIMD */
a1ul = 15360 - a2p;
if (state_ptr->a[0] < -a1ul)
state_ptr->a[0] = -a1ul;
else if (state_ptr->a[0] > a1ul)
state_ptr->a[0] = a1ul;
/* TRIGB & DELAY */
state_ptr->a[1] = a2p;
/* UPB : update predictor zeros b[6] */
for (cnt = 0; cnt < 6; cnt++) {
/*if (code_size == 5)*/ /* for 40Kbps G.723 */
/* state_ptr->b[cnt] -= state_ptr->b[cnt] >> 9;*/
/*else*/ /* for G.721 and 24Kbps G.723 */
state_ptr->b[cnt] -= state_ptr->b[cnt] >> 8;
if (dq & 0x7FFF) { /* XOR */
if ((dq ^ state_ptr->dq[cnt]) >= 0)
state_ptr->b[cnt] += 128;
else
state_ptr->b[cnt] -= 128;
}
}
}
/* UPA1 */
/* update predictor pole a[0] */
state_ptr->a[0] -= state_ptr->a[0] >> 8;
if (dqsez != 0) {
if (pks1 == 0)
state_ptr->a[0] += 192;
else
state_ptr->a[0] -= 192;
}
for (cnt = 5; cnt > 0; cnt--)
state_ptr->dq[cnt] = state_ptr->dq[cnt-1];
/* FLOAT A : convert dq[0] to 4-bit exp, 6-bit mantissa f.p. */
if (mag == 0) {
state_ptr->dq[0] = (dq >= 0) ? 0x20 : 0xFC20;
} else {
exp = quan(mag, power2, 15);
state_ptr->dq[0] = (dq >= 0) ?
(exp << 6) + ((mag << 6) >> exp) :
(exp << 6) + ((mag << 6) >> exp) - 0x400;
}
/* LIMD */
a1ul = 15360 - a2p;
if (state_ptr->a[0] < -a1ul)
state_ptr->a[0] = -a1ul;
else if (state_ptr->a[0] > a1ul)
state_ptr->a[0] = a1ul;
state_ptr->sr[1] = state_ptr->sr[0];
/* FLOAT B : convert sr to 4-bit exp., 6-bit mantissa f.p. */
if (sr == 0) {
state_ptr->sr[0] = 0x20;
} else if (sr > 0) {
exp = quan(sr, power2, 15);
state_ptr->sr[0] = (exp << 6) + ((sr << 6) >> exp);
} else if (sr > -32768) {
mag = -sr;
exp = quan(mag, power2, 15);
state_ptr->sr[0] = (exp << 6) + ((mag << 6) >> exp) - 0x400;
} else
state_ptr->sr[0] = 0xFC20;
/* UPB : update predictor zeros b[6] */
for (cnt = 0; cnt < 6; cnt++) {
/*if (code_size == 5)*/ /* for 40Kbps G.723 */
/* state_ptr->b[cnt] -= state_ptr->b[cnt] >> 9;*/
/*else*/ /* for G.721 and 24Kbps G.723 */
state_ptr->b[cnt] -= state_ptr->b[cnt] >> 8;
if (dq & 0x7FFF) { /* XOR */
if ((dq ^ state_ptr->dq[cnt]) >= 0)
state_ptr->b[cnt] += 128;
else
state_ptr->b[cnt] -= 128;
}
}
}
/* DELAY A */
state_ptr->pk[1] = state_ptr->pk[0];
state_ptr->pk[0] = pk0;
for (cnt = 5; cnt > 0; cnt--)
state_ptr->dq[cnt] = state_ptr->dq[cnt-1];
/* FLOAT A : convert dq[0] to 4-bit exp, 6-bit mantissa f.p. */
if (mag == 0) {
state_ptr->dq[0] = (dq >= 0) ? 0x20 : 0xFC20;
} else {
exp = quan(mag, power2, 15);
state_ptr->dq[0] = (dq >= 0) ?
(exp << 6) + ((mag << 6) >> exp) :
(exp << 6) + ((mag << 6) >> exp) - 0x400;
}
/* TONE */
if (tr == 1) /* this sample has been treated as data */
state_ptr->td = 0; /* next one will be treated as voice */
else if (a2p < -11776) /* small sample-to-sample correlation */
state_ptr->td = 1; /* signal may be data */
else /* signal is voice */
state_ptr->td = 0;
state_ptr->sr[1] = state_ptr->sr[0];
/* FLOAT B : convert sr to 4-bit exp., 6-bit mantissa f.p. */
if (sr == 0) {
state_ptr->sr[0] = 0x20;
} else if (sr > 0) {
exp = quan(sr, power2, 15);
state_ptr->sr[0] = (exp << 6) + ((sr << 6) >> exp);
} else if (sr > -32768) {
mag = -sr;
exp = quan(mag, power2, 15);
state_ptr->sr[0] = (exp << 6) + ((mag << 6) >> exp) - 0x400;
} else
state_ptr->sr[0] = 0xFC20;
/*
* Adaptation speed control.
*/
state_ptr->dms += (fi - state_ptr->dms) >> 5; /* FILTA */
state_ptr->dml += (((fi << 2) - state_ptr->dml) >> 7); /* FILTB */
/* DELAY A */
state_ptr->pk[1] = state_ptr->pk[0];
state_ptr->pk[0] = pk0;
if (tr == 1)
state_ptr->ap = 256;
else if (y < 1536) /* SUBTC */
state_ptr->ap += (0x200 - state_ptr->ap) >> 4;
else if (state_ptr->td == 1)
state_ptr->ap += (0x200 - state_ptr->ap) >> 4;
else if (abs((state_ptr->dms << 2) - state_ptr->dml) >=
(state_ptr->dml >> 3))
state_ptr->ap += (0x200 - state_ptr->ap) >> 4;
else
state_ptr->ap += (-state_ptr->ap) >> 4;
/* TONE */
if (tr == 1) /* this sample has been treated as data */
state_ptr->td = 0; /* next one will be treated as voice */
else if (a2p < -11776) /* small sample-to-sample correlation */
state_ptr->td = 1; /* signal may be data */
else /* signal is voice */
state_ptr->td = 0;
/*
* Adaptation speed control.
*/
state_ptr->dms += (fi - state_ptr->dms) >> 5; /* FILTA */
state_ptr->dml += (((fi << 2) - state_ptr->dml) >> 7); /* FILTB */
if (tr == 1)
state_ptr->ap = 256;
else if (y < 1536) /* SUBTC */
state_ptr->ap += (0x200 - state_ptr->ap) >> 4;
else if (state_ptr->td == 1)
state_ptr->ap += (0x200 - state_ptr->ap) >> 4;
else if (abs((state_ptr->dms << 2) - state_ptr->dml) >=
(state_ptr->dml >> 3))
state_ptr->ap += (0x200 - state_ptr->ap) >> 4;
else
state_ptr->ap += (-state_ptr->ap) >> 4;
}
/*
* Maps G.721 code word to reconstructed scale factor normalized log
* magnitude values.
*/
static short _dqlntab[16] = {-2048, 4, 135, 213, 273, 323, 373, 425,
425, 373, 323, 273, 213, 135, 4, -2048};
static short _dqlntab[16] = {-2048, 4, 135, 213, 273, 323, 373, 425,
425, 373, 323, 273, 213, 135, 4, -2048};
/* Maps G.721 code word to log of scale factor multiplier. */
static short _witab[16] = {-12, 18, 41, 64, 112, 198, 355, 1122,
1122, 355, 198, 112, 64, 41, 18, -12};
static short _witab[16] = {-12, 18, 41, 64, 112, 198, 355, 1122,
1122, 355, 198, 112, 64, 41, 18, -12};
/*
* Maps G.721 code words to a set of values whose long and short
* term averages are computed and then compared to give an indication
* how stationary (steady state) the signal is.
*/
static short _fitab[16] = {0, 0, 0, 0x200, 0x200, 0x200, 0x600, 0xE00,
0xE00, 0x600, 0x200, 0x200, 0x200, 0, 0, 0};
static short _fitab[16] = {0, 0, 0, 0x200, 0x200, 0x200, 0x600, 0xE00,
0xE00, 0x600, 0x200, 0x200, 0x200, 0, 0, 0};
/*
* g721_decoder()
*
@ -429,39 +429,39 @@ static short _fitab[16] = {0, 0, 0, 0x200, 0x200, 0x200, 0x600, 0xE00,
*/
static int
g721_decoder(
int i,
struct g72x_state *state_ptr)
int i,
struct g72x_state *state_ptr)
{
short sezi, sei, sez, se; /* ACCUM */
short y; /* MIX */
short sr; /* ADDB */
short dq;
short dqsez;
short sezi, sei, sez, se; /* ACCUM */
short y; /* MIX */
short sr; /* ADDB */
short dq;
short dqsez;
i &= 0x0f; /* mask to get proper bits */
sezi = predictor_zero(state_ptr);
sez = sezi >> 1;
sei = sezi + predictor_pole(state_ptr);
se = sei >> 1; /* se = estimated signal */
i &= 0x0f; /* mask to get proper bits */
sezi = predictor_zero(state_ptr);
sez = sezi >> 1;
sei = sezi + predictor_pole(state_ptr);
se = sei >> 1; /* se = estimated signal */
y = step_size(state_ptr); /* dynamic quantizer step size */
y = step_size(state_ptr); /* dynamic quantizer step size */
dq = reconstruct(i & 0x08, _dqlntab[i], y); /* quantized diff. */
dq = reconstruct(i & 0x08, _dqlntab[i], y); /* quantized diff. */
sr = (dq < 0) ? (se - (dq & 0x3FFF)) : se + dq; /* reconst. signal */
sr = (dq < 0) ? (se - (dq & 0x3FFF)) : se + dq; /* reconst. signal */
dqsez = sr - se + sez; /* pole prediction diff. */
dqsez = sr - se + sez; /* pole prediction diff. */
update(y, _witab[i] << 5, _fitab[i], dq, sr, dqsez, state_ptr);
update(y, _witab[i] << 5, _fitab[i], dq, sr, dqsez, state_ptr);
return (sr << 2); /* sr was 14-bit dynamic range */
return (sr << 2); /* sr was 14-bit dynamic range */
}
void decode_g721(VGMSTREAMCHANNEL * stream, sample * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do) {
void decode_g721(VGMSTREAMCHANNEL* stream, sample_t* outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do) {
int i;
int32_t sample_count;
for (i=first_sample,sample_count=0; i<first_sample+samples_to_do; i++,sample_count+=channelspacing) {
for (i = first_sample, sample_count = 0; i < first_sample + samples_to_do; i++, sample_count += channelspacing) {
outbuf[sample_count]=
g721_decoder(
read_8bit(stream->offset+i/2,stream->streamfile)>>(i&1?4:0),

View file

@ -1,7 +1,7 @@
#include "coding.h"
#ifdef VGM_USE_G7221
#include "g7221_decoder_lib.h"
#include "libs/g7221_lib.h"
#define G7221_MAX_FRAME_SIZE 0x78 /* 960/8 */
#define G7221_MAX_FRAME_SAMPLES 640 /* 32000/50 */
@ -73,19 +73,23 @@ void decode_g7221(VGMSTREAM* vgmstream, sample_t* outbuf, int channelspacing, in
void reset_g7221(g7221_codec_data* data) {
int i;
if (!data) return;
if (!data)
return;
for (i = 0; i < data->channels; i++) {
for (int i = 0; i < data->channels; i++) {
if (!data->ch)
continue;
g7221_reset(data->ch[i].handle);
}
}
void free_g7221(g7221_codec_data* data) {
int i;
if (!data) return;
if (!data)
return;
for (i = 0; i < data->channels; i++) {
for (int i = 0; i < data->channels; i++) {
if (!data->ch)
continue;
g7221_free(data->ch[i].handle);
}
free(data->ch);
@ -93,10 +97,12 @@ void free_g7221(g7221_codec_data* data) {
}
void set_key_g7221(g7221_codec_data* data, const uint8_t* key) {
int i;
if (!data) return;
if (!data)
return;
for (i = 0; i < data->channels; i++) {
for (int i = 0; i < data->channels; i++) {
if (!data->ch)
continue;
g7221_set_key(data->ch[i].handle, key);
}
}

View file

@ -1,5 +1,5 @@
#include "coding.h"
#include "hca_decoder_clhca.h"
#include "libs/clhca.h"
struct hca_codec_data {
@ -95,7 +95,7 @@ void decode_hca(hca_codec_data* data, sample_t* outbuf, int32_t samples_to_do) {
memcpy(outbuf + samples_done*channels,
data->sample_buffer + data->samples_consumed*channels,
samples_to_get*channels * sizeof(sample));
samples_to_get*channels * sizeof(sample_t));
samples_done += samples_to_get;
}
@ -110,7 +110,7 @@ void decode_hca(hca_codec_data* data, sample_t* outbuf, int32_t samples_to_do) {
/* EOF/error */
if (data->current_block >= data->info.blockCount) {
memset(outbuf, 0, (samples_to_do - samples_done) * channels * sizeof(sample));
memset(outbuf, 0, (samples_to_do - samples_done) * channels * sizeof(sample_t));
break;
}

View file

@ -1,5 +1,5 @@
#include "coding.h"
#include "ice_decoder_icelib.h"
#include "libs/icelib.h"
typedef struct {

View file

@ -11,7 +11,18 @@
* - expand type: IMA style or variations; low or high nibble first
*/
static const int ADPCMTable[90] = {
//TODO: decide on a better name and location
static inline int _clamp_s32(int value, int min, int max) {
if (value < min)
return min;
else if (value > max)
return max;
else
return value;
}
static const int16_t ima_step_size_table[89+1] = {
7, 8, 9, 10, 11, 12, 13, 14,
16, 17, 19, 21, 23, 25, 28, 31,
34, 37, 41, 45, 50, 55, 60, 66,
@ -28,15 +39,15 @@ static const int ADPCMTable[90] = {
0 /* garbage value for Ubisoft IMA (see blocked_ubi_sce.c) */
};
static const int IMA_IndexTable[16] = {
static const int8_t ima_index_table[16] = {
-1, -1, -1, -1, 2, 4, 6, 8,
-1, -1, -1, -1, 2, 4, 6, 8
};
/* Original IMA expansion, using shift+ADDs to avoid MULs (slow back then) */
static void std_ima_expand_nibble(VGMSTREAMCHANNEL * stream, off_t byte_offset, int nibble_shift, int32_t * hist1, int32_t * step_index) {
int sample_nibble, sample_decoded, step, delta;
static void std_ima_expand_nibble_data(uint8_t byte, int shift, int32_t* hist1, int32_t* index) {
int code, sample, step, delta;
/* simplified through math from:
* - diff = (code + 1/2) * (step / 4)
@ -44,21 +55,26 @@ static void std_ima_expand_nibble(VGMSTREAMCHANNEL * stream, off_t byte_offset,
* > diff = (step * nibble / 4) + (step / 8)
* final diff = [signed] (step / 8) + (step / 4) + (step / 2) + (step) [when code = 4+2+1] */
sample_nibble = (read_8bit(byte_offset,stream->streamfile) >> nibble_shift)&0xf; /* ADPCM code */
sample_decoded = *hist1; /* predictor value */
step = ADPCMTable[*step_index]; /* current step */
code = (byte >> shift) & 0xf;
sample = *hist1; /* predictor value */
step = ima_step_size_table[*index]; /* current step */
delta = step >> 3;
if (sample_nibble & 1) delta += step >> 2;
if (sample_nibble & 2) delta += step >> 1;
if (sample_nibble & 4) delta += step;
if (sample_nibble & 8) delta = -delta;
sample_decoded += delta;
if (code & 1) delta += step >> 2;
if (code & 2) delta += step >> 1;
if (code & 4) delta += step;
if (code & 8) delta = -delta;
sample += delta;
*hist1 = clamp16(sample_decoded);
*step_index += IMA_IndexTable[sample_nibble];
if (*step_index < 0) *step_index=0;
if (*step_index > 88) *step_index=88;
*hist1 = clamp16(sample);
*index += ima_index_table[code];
if (*index < 0) *index = 0;
if (*index > 88) *index = 88;
}
static void std_ima_expand_nibble(VGMSTREAMCHANNEL * stream, off_t byte_offset, int nibble_shift, int32_t * hist1, int32_t * step_index) {
uint8_t byte = read_u8(byte_offset,stream->streamfile);
std_ima_expand_nibble_data(byte, nibble_shift, hist1, step_index);
}
/* Apple's IMA variation. Exactly the same except it uses 16b history (probably more sensitive to overflow/sign extend?) */
@ -67,7 +83,7 @@ static void std_ima_expand_nibble_16(VGMSTREAMCHANNEL * stream, off_t byte_offse
sample_nibble = (read_8bit(byte_offset,stream->streamfile) >> nibble_shift)&0xf;
sample_decoded = *hist1;
step = ADPCMTable[*step_index];
step = ima_step_size_table[*step_index];
delta = step >> 3;
if (sample_nibble & 1) delta += step >> 2;
@ -77,7 +93,7 @@ static void std_ima_expand_nibble_16(VGMSTREAMCHANNEL * stream, off_t byte_offse
sample_decoded += delta;
*hist1 = clamp16(sample_decoded); /* no need for this, actually */
*step_index += IMA_IndexTable[sample_nibble];
*step_index += ima_index_table[sample_nibble];
if (*step_index < 0) *step_index=0;
if (*step_index > 88) *step_index=88;
}
@ -95,7 +111,7 @@ static void std_ima_expand_nibble_mul(VGMSTREAMCHANNEL * stream, off_t byte_offs
sample_nibble = (read_8bit(byte_offset,stream->streamfile) >> nibble_shift)&0xf;
sample_decoded = *hist1;
step = ADPCMTable[*step_index];
step = ima_step_size_table[*step_index];
delta = (sample_nibble & 0x7);
delta = ((delta * 2 + 1) * step) >> 3;
@ -103,7 +119,7 @@ static void std_ima_expand_nibble_mul(VGMSTREAMCHANNEL * stream, off_t byte_offs
sample_decoded += delta;
*hist1 = clamp16(sample_decoded);
*step_index += IMA_IndexTable[sample_nibble];
*step_index += ima_index_table[sample_nibble];
if (*step_index < 0) *step_index=0;
if (*step_index > 88) *step_index=88;
}
@ -114,7 +130,7 @@ static void nw_ima_expand_nibble(VGMSTREAMCHANNEL * stream, off_t byte_offset, i
sample_nibble = (read_8bit(byte_offset,stream->streamfile) >> nibble_shift)&0xf;
sample_decoded = *hist1;
step = ADPCMTable[*step_index];
step = ima_step_size_table[*step_index];
sample_decoded = sample_decoded << 3;
delta = (sample_nibble & 0x07);
@ -124,7 +140,7 @@ static void nw_ima_expand_nibble(VGMSTREAMCHANNEL * stream, off_t byte_offset, i
sample_decoded = sample_decoded >> 3;
*hist1 = clamp16(sample_decoded);
*step_index += IMA_IndexTable[sample_nibble];
*step_index += ima_index_table[sample_nibble];
if (*step_index < 0) *step_index=0;
if (*step_index > 88) *step_index=88;
}
@ -136,11 +152,11 @@ static void snds_ima_expand_nibble(VGMSTREAMCHANNEL * stream, off_t byte_offset,
sample_nibble = (read_8bit(byte_offset,stream->streamfile) >> nibble_shift)&0xf;
sample_decoded = *hist1;
*step_index += IMA_IndexTable[sample_nibble];
*step_index += ima_index_table[sample_nibble];
if (*step_index < 0) *step_index=0;
if (*step_index > 88) *step_index=88;
step = ADPCMTable[*step_index];
step = ima_step_size_table[*step_index];
delta = (sample_nibble & 7) * step / 4 + step / 8; /* standard IMA */
if (sample_nibble & 8) delta = -delta;
@ -155,7 +171,7 @@ static void otns_ima_expand_nibble(VGMSTREAMCHANNEL * stream, off_t byte_offset,
sample_nibble = (read_8bit(byte_offset,stream->streamfile) >> nibble_shift)&0xf;
sample_decoded = *hist1;
step = ADPCMTable[*step_index];
step = ima_step_size_table[*step_index];
delta = 0;
if(sample_nibble & 4) delta = step * 4;
@ -166,7 +182,7 @@ static void otns_ima_expand_nibble(VGMSTREAMCHANNEL * stream, off_t byte_offset,
sample_decoded += delta;
*hist1 = clamp16(sample_decoded);
*step_index += IMA_IndexTable[sample_nibble];
*step_index += ima_index_table[sample_nibble];
if (*step_index < 0) *step_index=0;
if (*step_index > 88) *step_index=88;
}
@ -177,7 +193,7 @@ static void wv6_ima_expand_nibble(VGMSTREAMCHANNEL * stream, off_t byte_offset,
sample_nibble = (read_8bit(byte_offset,stream->streamfile) >> nibble_shift)&0xf;
sample_decoded = *hist1;
step = ADPCMTable[*step_index];
step = ima_step_size_table[*step_index];
delta = (sample_nibble & 0x7);
delta = ((delta * step) >> 3) + ((delta * step) >> 2);
@ -185,7 +201,7 @@ static void wv6_ima_expand_nibble(VGMSTREAMCHANNEL * stream, off_t byte_offset,
sample_decoded += delta;
*hist1 = clamp16(sample_decoded);
*step_index += IMA_IndexTable[sample_nibble];
*step_index += ima_index_table[sample_nibble];
if (*step_index < 0) *step_index=0;
if (*step_index > 88) *step_index=88;
}
@ -196,7 +212,7 @@ static void hv_ima_expand_nibble(VGMSTREAMCHANNEL * stream, off_t byte_offset, i
sample_nibble = (read_8bit(byte_offset,stream->streamfile) >> nibble_shift)&0xf;
sample_decoded = *hist1;
step = ADPCMTable[*step_index];
step = ima_step_size_table[*step_index];
delta = (sample_nibble & 0x7);
delta = (delta * step) >> 2;
@ -204,7 +220,7 @@ static void hv_ima_expand_nibble(VGMSTREAMCHANNEL * stream, off_t byte_offset, i
sample_decoded += delta;
*hist1 = clamp16(sample_decoded);
*step_index += IMA_IndexTable[sample_nibble];
*step_index += ima_index_table[sample_nibble];
if (*step_index < 0) *step_index=0;
if (*step_index > 88) *step_index=88;
}
@ -215,7 +231,7 @@ static void ffta2_ima_expand_nibble(VGMSTREAMCHANNEL * stream, off_t byte_offset
sample_nibble = (read_8bit(byte_offset,stream->streamfile) >> nibble_shift)&0xf; /* ADPCM code */
sample_decoded = *hist1; /* predictor value */
step = ADPCMTable[*step_index] * 0x100; /* current step (table in ROM is pre-multiplied though) */
step = ima_step_size_table[*step_index] * 0x100; /* current step (table in ROM is pre-multiplied though) */
delta = step >> 3;
if (sample_nibble & 1) delta += step >> 2;
@ -233,7 +249,7 @@ static void ffta2_ima_expand_nibble(VGMSTREAMCHANNEL * stream, off_t byte_offset
*hist1 = sample_decoded;
*out_sample = (short)((sample_decoded + 128) / 256); /* int16 sample rounding, hist is kept as int32 */
*step_index += IMA_IndexTable[sample_nibble];
*step_index += ima_index_table[sample_nibble];
if (*step_index < 0) *step_index=0;
if (*step_index > 88) *step_index=88;
}
@ -244,7 +260,7 @@ static void blitz_ima_expand_nibble(VGMSTREAMCHANNEL * stream, off_t byte_offset
sample_nibble = (read_8bit(byte_offset,stream->streamfile) >> nibble_shift)&0xf; /* ADPCM code */
sample_decoded = *hist1; /* predictor value */
step = ADPCMTable[*step_index]; /* current step */
step = ima_step_size_table[*step_index]; /* current step */
/* table has 2 different values, not enough to bother adding the full table */
if (step == 22385)
@ -260,7 +276,7 @@ static void blitz_ima_expand_nibble(VGMSTREAMCHANNEL * stream, off_t byte_offset
/* in Zapper somehow the exe tries to clamp hist but actually doesn't (bug? not in Lilo & Stitch),
* seems the pcm buffer must be clamped outside though to fix some scratchiness */
*hist1 = sample_decoded;//clamp16(sample_decoded);
*step_index += IMA_IndexTable[sample_nibble];
*step_index += ima_index_table[sample_nibble];
if (*step_index < 0) *step_index=0;
if (*step_index > 88) *step_index=88;
}
@ -274,7 +290,7 @@ static void mtf_ima_expand_nibble(VGMSTREAMCHANNEL * stream, off_t byte_offset,
sample_nibble = (read_8bit(byte_offset,stream->streamfile) >> nibble_shift) & 0xf;
sample_decoded = *hist1;
step = ADPCMTable[*step_index];
step = ima_step_size_table[*step_index];
delta = step * (2 * sample_nibble - 15);
sample_decoded += delta;
@ -334,7 +350,7 @@ static void cd_ima_expand_nibble(uint8_t byte, int shift, int32_t* hist1, int32_
sample += delta;
*hist1 = clamp16(sample);
*index += IMA_IndexTable[code];
*index += ima_index_table[code];
if (*index < 0) *index=0;
if (*index > 88) *index=88;
}
@ -821,8 +837,7 @@ void decode_dat4_ima(VGMSTREAMCHANNEL * stream, sample_t * outbuf, int channelsp
hist1 = read_16bitLE(header_offset,stream->streamfile);
step_index = read_8bit(header_offset+2,stream->streamfile);
//todo clip step_index?
step_index = _clamp_s32(step_index, 0, 88); /* probably pre-adjusted */
}
for (i=first_sample,sample_count=0; i<first_sample+samples_to_do; i++,sample_count+=channelspacing) {
@ -1098,14 +1113,14 @@ void decode_ubi_ima(VGMSTREAMCHANNEL * stream, sample_t * outbuf, int channelspa
/* write PCM samples, must be written to match header's num_samples (hist mustn't) */
max_samples_to_do = ((samples_to_do > header_samples) ? header_samples : samples_to_do);
for (i = first_sample; i < max_samples_to_do; i++, sample_count += channelspacing) {
outbuf[sample_count] = read_16bit(offset + channel*sizeof(sample) + i*channelspacing*sizeof(sample),stream->streamfile);
outbuf[sample_count] = read_16bit(offset + channel * sizeof(sample_t) + i*channelspacing * sizeof(sample_t), stream->streamfile);
first_sample++;
samples_to_do--;
}
/* header done */
if (i == header_samples) {
stream->offset = offset + header_samples*channelspacing*sizeof(sample);
stream->offset = offset + header_samples*channelspacing * sizeof(sample_t);
}
}
@ -1232,15 +1247,6 @@ void decode_h4m_ima(VGMSTREAMCHANNEL * stream, sample_t * outbuf, int channelspa
stream->adpcm_step_index = step_index;
}
/* test... */
static inline int _clamp_s32(int value, int min, int max) {
if (value < min)
return min;
else if (value > max)
return max;
else
return value;
}
/* Crystal Dynamics IMA. Original code uses mind-bending intrinsics, so this may not be fully accurate.
* Has another table with delta_table MMX combos, and uses header sample (first nibble is always 0). */
@ -1287,6 +1293,43 @@ void decode_cd_ima(VGMSTREAMCHANNEL* stream, sample_t* outbuf, int channelspacin
stream->adpcm_step_index = step_index;
}
/* Crankcase Audio IMA, from libs (internally CrankcaseAudio::ADPCM and revadpcm) */
void decode_crankcase_ima(VGMSTREAMCHANNEL* stream, sample_t* outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do) {
uint8_t frame[0x23] = {0};
int i, frames_in, sample_pos = 0, block_samples, frame_size;
int32_t hist1 = stream->adpcm_history1_32;
int step_index = stream->adpcm_step_index;
uint32_t frame_offset;
/* external interleave (fixed size), mono */
frame_size = 0x23;
block_samples = (frame_size - 0x3) * 2;
frames_in = first_sample / block_samples;
first_sample = first_sample % block_samples;
frame_offset = stream->offset + frame_size * frames_in;
read_streamfile(frame, frame_offset, frame_size, stream->streamfile); /* ignore EOF errors */
/* normal header (hist+step), mono */
if (first_sample == 0) {
hist1 = get_s16be(frame + 0x00);
step_index = get_u8(frame + 0x02); /* no reserved value at 0x03 unlike other IMAs (misaligned reads?) */
step_index = _clamp_s32(step_index, 0, 88);
}
/* decode nibbles (layout: straight in mono) */
for (i = first_sample; i < first_sample + samples_to_do; i++) {
int pos = 0x03 + (i/2);
int shift = (i & 1 ? 4:0); /* low first */
std_ima_expand_nibble_data(frame[pos], shift, &hist1, &step_index);
outbuf[sample_pos] = (short)(hist1); /* internally output to float using "sample / 32767.0" */
sample_pos += channelspacing;
}
stream->adpcm_history1_32 = hist1;
stream->adpcm_step_index = step_index;
}
/* ************************************************************* */

View file

@ -1,13 +1,15 @@
#include "coding.h"
#include "../util.h"
/* AKA iShiftVal__8snd_strm */
static const int32_t l5_scales[32] = {
0x00001000, 0x0000144E, 0x000019C5, 0x000020B4, 0x00002981, 0x000034AC, 0x000042D9, 0x000054D6,
0x00006BAB, 0x000088A4, 0x0000AD69, 0x0000DC13, 0x0001174C, 0x00016275, 0x0001C1D8, 0x00023AE5,
0x0002D486, 0x0003977E, 0x00048EEE, 0x0005C8F3, 0x00075779, 0x0009513E, 0x000BD31C, 0x000F01B5,
0x00130B82, 0x00182B83, 0x001EAC92, 0x0026EDB2, 0x00316777, 0x003EB2E6, 0x004F9232, 0x0064FBD1
0x00130B82, 0x00182B83, 0x001EAC92, 0x0026EDB2, 0x00316777, 0x003EB2E6, 0x004F9232, 0x0064FBD1,
};
/* reverse engineered from exe (SLPM_624.90's DecAdpcm__8snd_strmFRQ28snd_strm10SOUND_HEADiPsPUci / SLUS_212.07's @258D70)*/
void decode_l5_555(VGMSTREAMCHANNEL* stream, sample_t* outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do) {
uint8_t frame[0x12] = {0};
off_t frame_offset;
@ -31,7 +33,7 @@ void decode_l5_555(VGMSTREAMCHANNEL* stream, sample_t* outbuf, int channelspacin
/* parse frame header */
frame_offset = stream->offset + bytes_per_frame * frames_in;
read_streamfile(frame, frame_offset, bytes_per_frame, stream->streamfile); /* ignore EOF errors */
header = get_u32le(frame);
header = get_u16le(frame);
coef_index = (header >> 10) & 0x1f;
pos_scale = l5_scales[(header >> 5) & 0x1f];
neg_scale = l5_scales[(header >> 0) & 0x1f];
@ -41,18 +43,19 @@ void decode_l5_555(VGMSTREAMCHANNEL* stream, sample_t* outbuf, int channelspacin
coef3 = stream->adpcm_coef_3by32[coef_index * 3 + 2];
for (i = first_sample; i < first_sample + samples_to_do; i++) {
int32_t prediction, sample = 0;
/* sample is 64b in PS2 registers, though encoder probably won't let it get too high */
int32_t prediction, sample;
uint8_t nibbles = frame[0x02 + i/2];
sample = (i&1) ?
get_low_nibble_signed(nibbles):
get_high_nibble_signed(nibbles);
prediction = -(hist1 * coef1 + hist2 * coef2 + hist3 * coef3);
prediction = (hist1 * coef1 + hist2 * coef2 + hist3 * coef3);
if (sample >= 0)
sample = (prediction + sample * pos_scale) >> 12;
sample = (sample * pos_scale - prediction) >> 12;
else
sample = (prediction + sample * neg_scale) >> 12;
sample = (sample * neg_scale - prediction) >> 12;
sample = clamp16(sample);
outbuf[sample_count] = sample;

View file

@ -1,5 +1,5 @@
#ifndef _CIRCUS_DECODER_LIB_DATA_H_
#define _CIRCUS_DECODER_LIB_DATA_H_
#ifndef _CIRCUS_VQ_DATA_H_
#define _CIRCUS_VQ_DATA_H_
#include <stdint.h>

View file

@ -14,15 +14,13 @@
* https://bitbucket.org/losnoco/foo_adpcm/src/master/foo_oki/source/libpcm/libpcm.cpp
*/
#include "circus_decoder_lib.h"
#include "circus_decoder_lib_data.h"
#include "circus_vq_lib.h"
#include "circus_vq_data.h"
#include "circus_decoder_lzxpcm.h"
#include "circus_vq_lzxpcm.h"
/* use miniz (API-compatible) to avoid adding external zlib just for this codec
* - https://github.com/richgel999/miniz */
#include "../util/miniz.h"
//#include "zlib.h"
#include "../../util/zlib_vgmstream.h"
//#define XPCM_CODEC_PCM 0
@ -330,7 +328,7 @@ circus_handle_t* circus_init(off_t start, uint8_t codec, uint8_t flags) {
handle->flags = flags; //(config >> 8) & 0xFF;
scale_index = (handle->flags & 0xF);
if (scale_index > 5) goto fail;
if (scale_index >= 5) goto fail;
handle->scales = scale_table[scale_index];
if (handle->codec == XPCM_CODEC_VQ_DEFLATE) {

View file

@ -1,7 +1,7 @@
#ifndef _CIRCUS_DECODER_LIB_H_
#define _CIRCUS_DECODER_LIB_H_
#ifndef _CIRCUS_VQ_LIB_H_
#define _CIRCUS_VQ_LIB_H_
#include "../streamfile.h"
#include "../../streamfile.h"
typedef struct circus_handle_t circus_handle_t;

View file

@ -1,3 +1,6 @@
#ifndef _CIRCUS_VQ_LZXPCM_H_
#define _CIRCUS_VQ_LZXPCM_H_
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
@ -295,3 +298,5 @@ static int lzxpcm_decompress_full(uint8_t* dst, size_t dst_size, const uint8_t*
return 0;
}
#endif
#endif

View file

@ -25,7 +25,7 @@
//--------------------------------------------------
// Includes
//--------------------------------------------------
#include "hca_decoder_clhca.h"
#include "clhca.h"
#include <stddef.h>
#include <stdlib.h>
#include <memory.h>
@ -212,61 +212,63 @@ static void bitreader_init(clData* br, const void *data, int size) {
/* CRI's bitreader only handles 16b max during decode (header just reads bytes)
* so maybe could be optimized by ignoring higher cases */
static unsigned int bitreader_peek(clData* br, int bitsize) {
const unsigned int bit = br->bit;
const unsigned int bit_rem = bit & 7;
const unsigned int size = br->size;
static unsigned int bitreader_peek(clData* br, int bits_read) {
const unsigned int bit_pos = br->bit;
const unsigned int bit_rem = bit_pos & 7;
const unsigned int bit_size = br->size;
unsigned int v = 0;
unsigned int bit_offset, bit_left;
unsigned int bit_offset, bits_left;
if (!(bit + bitsize <= size))
if (bit_pos + bits_read > bit_size)
return v;
if (bits_read == 0) /* may happen when resolution is 0 (dequantize_coefficients) */
return v;
bit_offset = bitsize + bit_rem;
bit_left = size - bit;
if (bit_left >= 32 && bit_offset >= 25) {
bit_offset = bits_read + bit_rem;
bits_left = bit_size - bit_pos;
if (bits_left >= 32 && bit_offset >= 25) {
static const unsigned int mask[8] = {
0xFFFFFFFF,0x7FFFFFFF,0x3FFFFFFF,0x1FFFFFFF,
0x0FFFFFFF,0x07FFFFFF,0x03FFFFFF,0x01FFFFFF
};
const unsigned char* data = &br->data[bit >> 3];
const unsigned char* data = &br->data[bit_pos >> 3];
v = data[0];
v = (v << 8) | data[1];
v = (v << 8) | data[2];
v = (v << 8) | data[3];
v &= mask[bit_rem];
v >>= 32 - bit_rem - bitsize;
v >>= 32 - bit_rem - bits_read;
}
else if (bit_left >= 24 && bit_offset >= 17) {
else if (bits_left >= 24 && bit_offset >= 17) {
static const unsigned int mask[8] = {
0xFFFFFF,0x7FFFFF,0x3FFFFF,0x1FFFFF,
0x0FFFFF,0x07FFFF,0x03FFFF,0x01FFFF
};
const unsigned char* data = &br->data[bit >> 3];
const unsigned char* data = &br->data[bit_pos >> 3];
v = data[0];
v = (v << 8) | data[1];
v = (v << 8) | data[2];
v &= mask[bit_rem];
v >>= 24 - bit_rem - bitsize;
v >>= 24 - bit_rem - bits_read;
}
else if (bit_left >= 16 && bit_offset >= 9) {
else if (bits_left >= 16 && bit_offset >= 9) {
static const unsigned int mask[8] = {
0xFFFF,0x7FFF,0x3FFF,0x1FFF,0x0FFF,0x07FF,0x03FF,0x01FF
};
const unsigned char* data = &br->data[bit >> 3];
const unsigned char* data = &br->data[bit_pos >> 3];
v = data[0];
v = (v << 8) | data[1];
v &= mask[bit_rem];
v >>= 16 - bit_rem - bitsize;
v >>= 16 - bit_rem - bits_read;
}
else {
static const unsigned int mask[8] = {
0xFF,0x7F,0x3F,0x1F,0x0F,0x07,0x03,0x01
};
const unsigned char* data = &br->data[bit >> 3];
const unsigned char* data = &br->data[bit_pos >> 3];
v = data[0];
v &= mask[bit_rem];
v >>= 8 - bit_rem - bitsize;
v >>= 8 - bit_rem - bits_read;
}
return v;
}

View file

@ -1,33 +1,29 @@
#ifndef _clHCA_H
#define _clHCA_H
#ifdef __cplusplus
extern "C" {
#endif
#ifndef _CLHCA_H_
#define _CLHCA_H_
/* Must pass at least 8 bytes of data to this function.
* Returns <0 on non-match, or header size on success. */
int clHCA_isOurFile(const void *data, unsigned int size);
int clHCA_isOurFile(const void* data, unsigned int size);
/* The opaque state structure. */
typedef struct clHCA clHCA;
/* In case you wish to allocate and reset the structure on your own. */
int clHCA_sizeof(void);
void clHCA_clear(clHCA *);
void clHCA_done(clHCA *);
void clHCA_clear(clHCA* hca);
void clHCA_done(clHCA* hca);
/* Or you could let the library allocate it. */
clHCA * clHCA_new(void);
void clHCA_delete(clHCA *);
clHCA* clHCA_new(void);
void clHCA_delete(clHCA* hca);
/* Parses the HCA header. Must be called before any decoding may be performed,
* and size must be at least headerSize long. The recommended way is to detect
* the header length with clHCA_isOurFile, then read data and call this.
* May be called multiple times to reset decoder state.
* Returns 0 on success, <0 on failure. */
int clHCA_DecodeHeader(clHCA *, const void *data, unsigned int size);
int clHCA_DecodeHeader(clHCA* hca, const void* data, unsigned int size);
typedef struct clHCA_stInfo {
unsigned int version;
@ -44,7 +40,7 @@ typedef struct clHCA_stInfo {
unsigned int loopStartDelay; /* samples in block before loop starts */
unsigned int loopEndPadding; /* samples in block after loop ends */
unsigned int samplesPerBlock; /* should be 1024 */
const char *comment;
const char* comment;
unsigned int encryptionEnabled; /* requires keycode */
/* Derived sample formulas:
@ -57,37 +53,33 @@ typedef struct clHCA_stInfo {
/* Retrieves header information for decoding and playback (it's the caller's responsability
* to apply looping, encoder delay/skip samples, etc). May be called after clHCA_DecodeHeader.
* Returns 0 on success, <0 on failure. */
int clHCA_getInfo(clHCA *, clHCA_stInfo *out);
int clHCA_getInfo(clHCA* hca, clHCA_stInfo* out);
/* Decodes a single frame, from data after headerSize. Should be called after
* clHCA_DecodeHeader and size must be at least blockSize long.
* Data may be modified if encrypted.
* Returns 0 on success, <0 on failure. */
int clHCA_DecodeBlock(clHCA *, void *data, unsigned int size);
int clHCA_DecodeBlock(clHCA* hca, void* data, unsigned int size);
/* Extracts signed and clipped 16 bit samples into sample buffer.
* May be called after clHCA_DecodeBlock, and will return the same data until
* next decode. Buffer must be at least (samplesPerBlock*channels) long. */
void clHCA_ReadSamples16(clHCA *, signed short * outSamples);
void clHCA_ReadSamples16(clHCA* hca, short* outSamples);
/* Sets a 64 bit encryption key, to properly decode blocks. This may be called
* multiple times to change the key, before or after clHCA_DecodeHeader.
* Key is ignored if the file is not encrypted. */
void clHCA_SetKey(clHCA *, unsigned long long keycode);
void clHCA_SetKey(clHCA* hca, unsigned long long keycode);
/* Tests a single frame for validity, mainly to test if current key is correct.
* Returns <0 on incorrect block (wrong key), 0 on silent block (not useful to determine)
* and >0 if block is correct (the closer to 1 the more likely).
* Incorrect keys may give a few valid frames, so it's best to test a number of them
* and select the key with scores closer to 1. */
int clHCA_TestBlock(clHCA *hca, void *data, unsigned int size);
int clHCA_TestBlock(clHCA* hca, void* data, unsigned int size);
/* Resets the internal decode state, used when restarting to decode the file from the beginning.
* Without it there are minor differences, mainly useful when testing a new key. */
void clHCA_DecodeReset(clHCA * hca);
#ifdef __cplusplus
}
#endif
void clHCA_DecodeReset(clHCA* hca);
#endif

View file

@ -0,0 +1,27 @@
#ifndef _COMPRESSWAVE_LIB_H
#define _COMPRESSWAVE_LIB_H
#include "../../streamfile.h"
typedef struct TCompressWaveData TCompressWaveData;
void TCompressWaveData_GetLoopState(TCompressWaveData* self);
void TCompressWaveData_SetLoopState(TCompressWaveData* self);
TCompressWaveData* TCompressWaveData_Create(void);
void TCompressWaveData_Free(TCompressWaveData* self);
int TCompressWaveData_Rendering(TCompressWaveData* self, int16_t* buf, uint32_t Len);
int TCompressWaveData_LoadFromStream(TCompressWaveData* self, STREAMFILE* ss);
void TCompressWaveData_SetCipherCode(TCompressWaveData* self, uint32_t Num);
void TCompressWaveData_Play(TCompressWaveData* self, int loop);
void TCompressWaveData_Stop(TCompressWaveData* self);
void TCompressWaveData_Previous(TCompressWaveData* self);
void TCompressWaveData_Pause(TCompressWaveData* self);
void TCompressWaveData_SetVolume(TCompressWaveData* self, float vol, float fade);
float TCompressWaveData_GetVolume(TCompressWaveData* self);
float TCompressWaveData_GetSetVolume(TCompressWaveData* self);
float TCompressWaveData_GetFade(TCompressWaveData* self);
float TCompressWaveData_GetPlayTime(TCompressWaveData* self);
float TCompressWaveData_GetTotalTime(TCompressWaveData* self);
#endif /*_COMPRESSWAVE_LIB_H */

View file

@ -1,5 +1,5 @@
#include <stdlib.h>
#include "g7221_decoder_aes.h"
#include "g7221_aes.h"
/* Namco's NUS AES is just standard AES-192 in ECB mode, so this can be swapped with another lib,
* if more code needs AES. Most implementations out there either use pre-calculated look-up tables,

View file

@ -1,5 +1,5 @@
#ifndef _DATA_H_
#define _DATA_H_
#ifndef _G7221_DATA_H_
#define _G7221_DATA_H_
#include <stdint.h>

View file

@ -2,9 +2,9 @@
#include <stdlib.h>
#include <string.h>
#include "g7221_decoder_lib.h"
#include "g7221_decoder_aes.h"
#include "g7221_lib.h"
#include "g7221_aes.h"
#include "g7221_data.h"
/* Decodes Siren14 from Namco's BNSF, a mono MLT/DCT-based codec for speech/sound (low bandwidth).
* Reverse engineered for various exes with info from Polycom's reference int decoder.
@ -46,8 +46,6 @@
* access indexes with (idx & max) and clamp buffer reads
*/
#include "g7221_decoder_lib_data.h"
/*****************************************************************************
* IMLT
*****************************************************************************/
@ -1074,7 +1072,7 @@ static int unpack_frame(int bit_rate, const uint8_t* data, int frame_size, /*int
if (test_errors) {
int max_pad_bytes = 0x8; /* usually 0x04 and rarely ~0x08 */
int bits_left = 8 * expected_frame_size - bitpos;
int i, endpos, test_bits;
int endpos, test_bits;
if (bits_left > 0) {

View file

@ -1,8 +1,8 @@
/*
Interface to Namco G.722.1 decoder
*/
#ifndef _G7221_DECODER_LIB_H
#define _G7221_DECODER_LIB_H
#ifndef _G7221_LIB_H
#define _G7221_LIB_H
#include <stdint.h>

View file

@ -26,12 +26,10 @@
#include <string.h>
#include <stdlib.h>
#include "ice_decoder_icelib.h"
#include "icelib.h"
/* use miniz (API-compatible) to avoid adding external zlib just for this codec
* - https://github.com/richgel999/miniz */
#include "../util/miniz.h"
//#include "zlib.h"
#include "../../util/zlib_vgmstream.h"
#define ICESND_MAX_CHANNELS 2

View file

@ -24,7 +24,7 @@
#include <stdlib.h>
#include <string.h>
#include "acm_decoder_libacm.h" //"libacm.h"//vgmstream mod
#include "libacm.h"
#define ACM_BUFLEN (64*1024)

View file

@ -23,7 +23,7 @@
#include <stdio.h>
#include <string.h>
#include "acm_decoder_libacm.h" //"libacm.h"//vgmstream mod
#include "libacm.h"
#define WAVC_HEADER_LEN 28
#define ACM_HEADER_LEN 14

View file

@ -0,0 +1,357 @@
/* Originally from nwatowav.cc (2007.7.28 version) by jagarl.
* - http://www.creator.club.ne.jp/~jagarl/
*
* Converted to .c by hcs (redone as a lib without RIFF/main handling), some cleanup by bnnm.
*
* nwa format (abridged from the original docs)
* NWA Header
* data offset index
* data block<0>
* data block<1>
* ...
* data block<N>
*
* - NWA header: 0x2c with nwa info (channels, compression level, etc), no magic number
* - data offset index: pointers to data blocks
* - data block: variable sized DPCM blocks to fixed size PCM (a,b,c compresses to (a),b-a,c-b),
* DPCM codes use variable bits. Usually for 16-bit PCM ends ups using 6-8 bits.
* - Block format:
* - mono: initial PCM (8 or 16-bit) then bitstream
* - stereo: initial PCM for left + right channel then bitstream
* Differential accuracy isn't high so initial PCM is used to correct data in each block (?)
* - bitstream: Roughly each code has an 'exponent' (2 bits) + 'mantissa' (variable bits).
* Depending on compression level + type it configures final shift value and matissa bits.
* There is a run length encoding mode in some cases (Tomoyo After voice files).
* Bitstream bytes follow little endian.
* (some examples here in the original, see decoder).
*/
/* Original copyright: */
/*
* Copyright 2001-2007 jagarl / Kazunori Ueno <jagarl@creator.club.ne.jp>
* All Rights Reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted.
*
* jagarl
*
*
*
* Copyright
*
*
* 使
*
* THIS SOFTWARE IS PROVIDED BY KAZUNORI 'jagarl' UENO ``AS IS'' AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL KAZUNORI UENO BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
* OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
* BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
* USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
* DAMAGE.
*
*/
#include <stdlib.h>
#include "nwa_lib.h"
#include "../../util/reader_sf.h"
//NWAInfo::UseRunLength
static int is_use_runlength(NWAData* nwa) {
if (nwa->channels == 2 && nwa->bps == 16 && nwa->complevel == 2) {
return 0; /* sw2 */
}
if (nwa->complevel == 5) {
if (nwa->channels == 2)
return 0; // BGM*.nwa in Little Busters!
return 1; // Tomoyo After (.nwk koe file)
}
return 0;
}
NWAData* nwalib_open(STREAMFILE* sf) {
uint8_t header[0x2c] = {0};
int i;
NWAData* const nwa = malloc(sizeof(NWAData));
if (!nwa) goto fail;
//NWAData::ReadHeader
read_streamfile(header, 0x00, sizeof(header), sf);
nwa->channels = get_s16le(header+0x00);
nwa->bps = get_s16le(header+0x02);
nwa->freq = get_s32le(header+0x04);
nwa->complevel = get_s32le(header+0x08);
nwa->dummy = get_s32le(header+0x0c);
nwa->blocks = get_s32le(header+0x10);
nwa->datasize = get_s32le(header+0x14);
nwa->compdatasize = get_s32le(header+0x18);
nwa->samplecount = get_s32le(header+0x1c);
nwa->blocksize = get_s32le(header+0x20);
nwa->restsize = get_s32le(header+0x24);
nwa->dummy2 = get_s32le(header+0x28);
nwa->offsets = NULL;
nwa->outdata = NULL;
nwa->outdata_readpos = NULL;
nwa->tmpdata = NULL;
nwa->filesize = get_streamfile_size(sf);
if (nwa->blocks <= 0 || nwa->blocks > 1000000)
/* 1時間を超える曲ってのはないでしょ*/ //surely there won't be songs over 1 hour
goto fail;
// NWAData::CheckHeader:
if (nwa->channels != 1 && nwa->channels != 2)
goto fail;
if (nwa->bps != 8 && nwa->bps != 16)
goto fail;
// (PCM not handled)
if (nwa->complevel < 0 || nwa->complevel > 5)
goto fail;
if (nwa->filesize != nwa->compdatasize)
goto fail;
if (nwa->datasize != nwa->samplecount * (nwa->bps / 8))
goto fail;
if (nwa->samplecount != (nwa->blocks - 1) * nwa->blocksize + nwa->restsize)
goto fail;
/* offset index 読み込み */ //read offset index
nwa->offsets = malloc(sizeof(off_t) * nwa->blocks);
if (!nwa->offsets) goto fail;
for (i = 0; i < nwa->blocks; i++) {
int32_t o = read_s32le(0x2c + i*4, sf);
if (o < 0) goto fail;
nwa->offsets[i] = o;
}
if (nwa->offsets[nwa->blocks-1] >= nwa->compdatasize)
goto fail;
nwa->use_runlength = is_use_runlength(nwa);
nwa->curblock = 0;
//extra
if (nwa->restsize > nwa->blocksize) {
nwa->outdata = malloc(sizeof(int16_t) * nwa->restsize);
}
else {
nwa->outdata = malloc(sizeof(int16_t) * nwa->blocksize);
}
if (!nwa->outdata)
goto fail;
/* これ以上の大きさはないだろう、、、 */ //probably not over this size
nwa->tmpdata = malloc(sizeof(uint8_t) * nwa->blocksize * (nwa->bps / 8) * 2);
if (!nwa->tmpdata)
goto fail;
nwa->outdata_readpos = nwa->outdata;
nwa->samples_in_buffer = 0;
return nwa;
fail:
nwalib_close(nwa);
return NULL;
}
void nwalib_close(NWAData * nwa) {
if (!nwa) return;
free(nwa->offsets);
free(nwa->outdata);
free(nwa->tmpdata);
free(nwa);
}
//NWAData::Rewind
void nwalib_reset(NWAData* nwa) {
nwa->curblock = 0;
nwa->outdata_readpos = nwa->outdata;
nwa->samples_in_buffer = 0;
}
// can serve up 8 bits at a time
static int getbits(const uint8_t** p_data, int* shift, int bits) {
int ret;
if (*shift > 8) {
(*p_data)++;
*shift -= 8;
}
ret = get_s16le(*p_data) >> *shift;
*shift += bits;
return ret & ((1 << bits) - 1); /* mask */
}
// NWADecode
static void decode_block(NWAData* nwa, const uint8_t* data, int outdatasize) {
short d[2];
int i;
int shift = 0;
int dsize = outdatasize / (nwa->bps / 8);
int flip_flag = 0; /* stereo 用 */ //for stereo
int runlength = 0;
/* 最初のデータを読み込む */ //read initial data
for (i = 0; i < nwa->channels; i++) {
if (nwa->bps == 8) {
d[i] = get_s8(data);
data += 1;
}
else { /* nwa->bps == 16 */
d[i] = get_s16le(data);
data += 2;
}
}
for (i = 0; i < dsize; i++) {
if (runlength == 0) { /* コピーループ中でないならデータ読み込み */ //read data if not in the copy loop
int type = getbits(&data, &shift, 3);
/* type により分岐0, 1-6, 7 */ //fork depending on type
if (type == 7) {
/* 7 : 大きな差分 */ //big diff
/* RunLength() 有効時CompLevel==5, 音声ファイル) では無効 */ //invalid when using RLE (comp=5, voice file)
if (getbits(&data, &shift, 1) == 1) {
d[flip_flag] = 0; /* 未使用 */ //unused
}
else {
int BITS, SHIFT;
if (nwa->complevel >= 3) {
BITS = 8;
SHIFT = 9;
}
else {
BITS = 8 - nwa->complevel;
SHIFT = 2 + 7 + nwa->complevel;
}
{
const int MASK1 = (1 << (BITS - 1));
const int MASK2 = (1 << (BITS - 1)) - 1;
int b = getbits(&data, &shift, BITS);
if (b & MASK1)
d[flip_flag] -= (b & MASK2) << SHIFT;
else
d[flip_flag] += (b & MASK2) << SHIFT;
}
}
}
else if (type != 0) {
/* 1-6 : 通常の差分 */ //normal diff
int BITS, SHIFT;
if (nwa->complevel >= 3) {
BITS = nwa->complevel + 3;
SHIFT = 1 + type;
}
else {
BITS = 5 - nwa->complevel;
SHIFT = 2 + type + nwa->complevel;
}
{
const int MASK1 = (1 << (BITS - 1));
const int MASK2 = (1 << (BITS - 1)) - 1;
int b = getbits(&data, &shift, BITS);
if (b & MASK1)
d[flip_flag] -= (b & MASK2) << SHIFT;
else
d[flip_flag] += (b & MASK2) << SHIFT;
}
}
else { /* type == 0 */
/* ランレングス圧縮なしの場合はなにもしない */ //does nothing in case of no RLE compression
if (nwa->use_runlength) {
/* ランレングス圧縮ありの場合 */ //in case of RLE compression
runlength = getbits(&data, &shift, 1);
if (runlength == 1) {
runlength = getbits(&data, &shift, 2);
if (runlength == 3) {
runlength = getbits(&data, &shift, 8);
}
}
}
}
}
else {
runlength--;
}
if (nwa->bps == 8) {
nwa->outdata[i] = d[flip_flag] * 256; //extra (original outputs 8-bit)
}
else {
nwa->outdata[i] = d[flip_flag];
}
if (nwa->channels == 2)
flip_flag ^= 1; /* channel 切り替え */ //channel swap
}
nwa->samples_in_buffer = dsize;
}
//NWAData::Decode
int nwalib_decode(STREAMFILE* sf, NWAData* nwa) {
/* some wav/pcm handling skipped here */
/* 今回読み込む/デコードするデータの大きさを得る */ //get current read/decode data size
int curblocksize, curcompsize;
if (nwa->curblock != nwa->blocks - 1) {
curblocksize = nwa->blocksize * (nwa->bps / 8);
curcompsize = nwa->offsets[nwa->curblock + 1] - nwa->offsets[nwa->curblock];
if (curblocksize >= nwa->blocksize * (nwa->bps / 8) * 2) {
return -1; // Fatal error
}
}
else { //last block
curblocksize = nwa->restsize * (nwa->bps / 8);
curcompsize = nwa->blocksize * (nwa->bps / 8) * 2;
}
// (in practice compsize is ~200-400 and blocksize ~0x800, but last block can be different)
/* データ読み込み */ //data read (may read less on last block?)
read_streamfile(nwa->tmpdata, nwa->offsets[nwa->curblock], curcompsize, sf);
nwa->samples_in_buffer = 0;
nwa->outdata_readpos = nwa->outdata;
decode_block(nwa, nwa->tmpdata, curblocksize);
nwa->curblock++; //todo check not over max blocks?
return 0;
}
//NWAFILE::Seek (not too similar)
void nwalib_seek(STREAMFILE* sf, NWAData* nwa, int32_t seekpos) {
int dest_block = seekpos / (nwa->blocksize / nwa->channels);
int32_t remainder = seekpos % (nwa->blocksize / nwa->channels);
nwa->curblock = dest_block;
nwalib_decode(sf, nwa);
nwa->outdata_readpos = nwa->outdata + remainder * nwa->channels;
nwa->samples_in_buffer -= remainder*nwa->channels;
}

View file

@ -30,11 +30,11 @@
*
*/
#ifndef _NWA_DECODER_H
#define _NWA_DECODER_H
#ifndef _NWA_LIB_H
#define _NWA_LIB_H
#ifdef BUILD_VGMSTREAM
#include "../streamfile.h"
#include "../../streamfile.h"
#else
#include "streamfile.h"
#endif

View file

@ -0,0 +1,223 @@
/* Decodes Ongakukan ADPCM, found in their PS2 and PSP games.
* Basically their take on ADPCM with some companding and quantization involved.
*
* Original decoder is a mix of COP0 and VU1 code, however PS2 floats aren't actually used (if at all)
* when it comes to converting encoded sample data (consisting of a single byte with two 4-bit nibbles, respectively) to PCM16.
*
* The decoder you see here is a hand-crafted, faithful C adaptation of original MIPS R5900 (PS2) and R4000 (PSP) code, from various executables of their games.
* As a consequence of all this, a new, entirely custom decoder had to be designed from the ground-up into vgmstream. No info surrounding this codec was available. */
/* Additional notes:
* - This code does not support PCM16 sound data, in any way, shape, or form.
* -- Ongakukan's internal sound engine from their PS2 and PSP games allow for only two codecs: signed PCM16, and their own take on ADPCM, respectively.
* -- However, much of that support is reliant on a flag that's set to either one of the two codecs depending on the opened file extension.
* Basically, how it works is: if sound data is "PCM16" (available to "wav" and "ads" files), set flag to 0.
* If sound data is "ADPCM" (available to "adp" files), set it to 1.
* Code handles this flag as a boolean var; 0 is "false" and 1 is "true".
* -- As vgmstream has built-in support for the former codec (and the many metas that use it) however, despite being fairly easy to add here,
* re-implementing one from scratch would be a wasted effort regardless; it is consequentially not included. */
#include <stdlib.h>
#include "../../util/reader_sf.h"
#include "ongakukan_adp_lib.h"
/* the struct that oversees everything. */
struct ongakukan_adp_t
{
STREAMFILE* sf; /* streamfile var. */
long int data_offset; /* current offset of data that's being read. */
long int start_offset; /* base offset of encoded sound data. */
long int data_size; /* sound data size, basically ADP size if it didn't have 44 bytes more. */
long int sample_work; /* total number of samples, calc'd using data_size as a base. */
long int alt_sample_work1; /* represents current number of samples as they're decoded. */
long int alt_sample_work2; /* represents the many samples left to go through. */
long int samples_filled; /* how many samples were filled to vgmstream buffer. */
long int samples_consumed; /* how many samples vgmstream buffer had to consume. */
bool sound_is_adpcm; /* false = no (see "additional notes" above) , true = yes */
bool sample_startpoint_present; /* false = failed to make startpoint, true = startpoint present */
char sample_mode; /* 0 = creates decoding setup, 1 = continue decoding data with setup in place */
bool sample_pair_is_decoded; /* false = no, true = yes */
unsigned char base_pair; /* represents a read byte from ADPCM data, consisting of two 4-bit nibbles each.*/
long int base_scale; /* how loud should this sample be. */
short int sample_hist[2]; /* two pairs of signed 16-bit data, representing samples. yes, it is void. */
};
/* filter table consisting of 16 numbers each. */
static const short int ongakukan_adpcm_filter[16] = { 233, 549, 453, 375, 310, 233, 233, 233, 233, 233, 233, 233, 310, 375, 453, 549 };
/* streamfile read function declararion, more may be added in the future. */
static uint8_t read_u8_wrapper(ongakukan_adp_t* handle);
/* function declarations for the inner workings of codec data. */
static bool set_up_sample_startpoint(ongakukan_adp_t* handle);
static void decode_ongakukan_adpcm_samples(ongakukan_adp_t* handle);
/* codec management functions, meant to oversee and supervise ADP data from the top-down.
* in layman terms, they control how ADP data should be handled and when. */
ongakukan_adp_t* ongakukan_adpcm_init(STREAMFILE* sf, long int data_offset, long int data_size, bool sound_is_adpcm)
{
ongakukan_adp_t* handle = NULL;
if (!sound_is_adpcm)
return NULL;
/* allocate handle. */
handle = calloc(1, sizeof(ongakukan_adp_t));
if (!handle) goto fail;
/* now, to set up the rest of the handle with the data we have... */
handle->sf = sf;
handle->data_offset = data_offset;
handle->start_offset = data_offset;
handle->data_size = data_size;
handle->sample_mode = 0;
handle->sound_is_adpcm = sound_is_adpcm;
handle->sample_startpoint_present = set_up_sample_startpoint(handle);
/* if we failed in planting up the seeds for an ADPCM decoder, we simply throw in the towel and take a walk in the park. */
if (handle->sample_startpoint_present == false) { goto fail; }
return handle;
fail:
ongakukan_adpcm_free(handle);
return NULL;
}
void ongakukan_adpcm_free(ongakukan_adp_t* handle)
{
if (!handle) return;
free(handle);
}
void ongakukan_adpcm_reset(ongakukan_adp_t* handle)
{
if (!handle) return;
/* wipe out certain values from handle so we can start over. */
handle->data_offset = handle->start_offset;
handle->sample_pair_is_decoded = false;
handle->sample_mode = 0;
handle->alt_sample_work1 = 0;
handle->alt_sample_work2 = handle->sample_work;
}
void ongakukan_adpcm_seek(ongakukan_adp_t* handle, long int target_sample)
{
if (!handle) return;
char ts_modulus = 0; /* ts_modulus is here to ensure target_sample gets rounded to a multiple of 2. */
long int ts_data_offset = 0; /* ts_data_offset is basically data_offset but with (left(if PCM)/right(if ADPCM))-shifted target_sample calc by 1. */
ts_data_offset = target_sample >> 1;
ts_modulus = target_sample % 2;
target_sample = target_sample - ts_modulus;
/* if ADPCM, right-shift the former first then have ts_modulus calc remainder of target_sample by 2 so we can subtract it with ts_modulus.
* this is needed for the two counters that the decoder has that can both add and subtract with 2, respectively
* (and in order, too; meaning one counter does "plus 2" while the other does "minus 2",
* and though they're fairly useless ATM, you pretty much want to leave them alone). */
/* anyway, we'll have to tell decoder that target_sample is calling and wants to go somewhere right now,
* so we'll have data_offset reposition itself to where sound data for that sample ought to be
* and (as of now) reset basically all decode state up to this point so we can continue to decode all sample pairs without issue. */
handle->data_offset = handle->start_offset + ts_data_offset;
handle->sample_pair_is_decoded = false;
handle->sample_mode = 0;
handle->alt_sample_work1 = target_sample;
handle->alt_sample_work2 = handle->sample_work - target_sample;
/* for now, just do what reset_all_ongakukan_adpcm does but for the current sample instead of literally everything.
* seek_ongakukan_adpcm_pos in its current state is a bit more involved than the above, but works. */
}
long int ongakukan_adpcm_get_num_samples(ongakukan_adp_t* handle)
{
if (!handle) return 0;
return handle->sample_work;
}
short* ongakukan_adpcm_get_sample_hist(ongakukan_adp_t* handle)
{
if (!handle) return 0;
return handle->sample_hist;
}
/* function definitions for the inner workings of codec data. */
static bool set_up_sample_startpoint(ongakukan_adp_t* handle)
{
/* make decoder fail hard if streamfile object isn't opened or downright useless. */
if (!handle->sf) return false;
if (handle->sound_is_adpcm == 0) { return false; }
else { /* num_samples but for Ongakukan ADPCM sound data. */ handle->sample_work = handle->data_size << 1; }
/* set "beginning" and "end" sample vars and send a "message" that we went through no sample yet.*/
handle->alt_sample_work1 = 0;
handle->alt_sample_work2 = handle->sample_work;
handle->sample_pair_is_decoded = false;
return true;
}
void ongakukan_adpcm_decode_data(ongakukan_adp_t* handle)
{
/* set samples_filled to 0 and have our decoder go through every sample that exists in the sound data.*/
decode_ongakukan_adpcm_samples(handle);
/* if setup is established for further decoding, switch gears and have the decoder use that setup for as long as possible. */
/* if sample pair is decoded, advance to next byte, tell our handle that we went through 2 samples and make decoder go through next available data again. */
if (handle->sample_pair_is_decoded == true)
{
handle->data_offset++;
handle->alt_sample_work1 += 2;
handle->alt_sample_work2 -= 2;
handle->sample_pair_is_decoded = false;
}
}
static void decode_ongakukan_adpcm_samples(ongakukan_adp_t* handle)
{
unsigned char nibble1 = 0, nibble2 = 0; /* two chars representing a 4-bit nibble. */
long int nibble1_1 = 0, nibble2_1 = 0; /* two long ints representing pure sample data. */
if (handle->sample_pair_is_decoded == false)
{
/* sample_mode being 0 means we can just do a setup for future sample decoding so we have nothing to worry about in the future. */
if (handle->sample_mode == 0)
{
/* set "base scale", two "sample hist"s, and "base pair", respectively. */
handle->base_scale = 0x10;
handle->sample_hist[0] = 0;
handle->sample_hist[1] = 0;
handle->base_pair = 0;
handle->sample_mode = 1; /* indicates we have the setup we need to decode samples. */
}
handle->base_pair = (uint8_t)read_u8_wrapper(handle);
nibble1 = handle->base_pair & 0xf;
nibble1_1 = nibble1 + -8;
nibble2 = (handle->base_pair >> 4) & 0xf;
nibble2_1 = nibble2 + -8;
nibble2_1 = nibble2_1 * handle->base_scale;
handle->sample_hist[0] = handle->sample_hist[1] + nibble2_1;
handle->base_scale = (handle->base_scale * (ongakukan_adpcm_filter[nibble2])) >> 8;
nibble1_1 = nibble1_1 * handle->base_scale;
handle->sample_hist[1] = handle->sample_hist[0] + nibble1_1;
handle->base_scale = (handle->base_scale * (ongakukan_adpcm_filter[nibble1])) >> 8;
handle->sample_pair_is_decoded = true;
}
}
/* streamfile read function definitions at the very bottom. */
static uint8_t read_u8_wrapper(ongakukan_adp_t* handle)
{
if ((handle->data_offset - handle->start_offset) > handle->data_size) return 0;
if ((handle->data_offset - handle->start_offset) < 0) return 0;
return read_u8((off_t)(handle->data_offset), handle->sf);
}

View file

@ -0,0 +1,27 @@
#ifndef _ONGAKUKAN_ADP_LIB_H_
#define _ONGAKUKAN_ADP_LIB_H_
/* Ongakukan ADPCM codec, found in PS2 and PSP games. */
#include "../../util/reader_sf.h"
/* typedef struct */
typedef struct ongakukan_adp_t ongakukan_adp_t;
/* function declaration for we need to set up the codec data. */
ongakukan_adp_t* ongakukan_adpcm_init(STREAMFILE* sf, long int data_offset, long int data_size,
bool sound_is_adpcm);
/* function declaration for freeing all memory related to ongakukan_adp_t struct var. */
void ongakukan_adpcm_free(ongakukan_adp_t* handle);
void ongakukan_adpcm_reset(ongakukan_adp_t* handle);
void ongakukan_adpcm_seek(ongakukan_adp_t* handle, long int target_sample);
/* function declaration for when we need to get (and send) certain values from ongakukan_adp_t handle */
long int ongakukan_adpcm_get_num_samples(ongakukan_adp_t* handle);
short* ongakukan_adpcm_get_sample_hist(ongakukan_adp_t* handle);
/* function declaration for actually decoding samples, can't be that hard, right? */
void ongakukan_adpcm_decode_data(ongakukan_adp_t* handle);
#endif

View file

@ -1,7 +1,7 @@
#include <stdlib.h>
#include <string.h>
#include <math.h>
#include "relic_decoder_lib.h"
#include "relic_lib.h"
/* Relic Codec decoder, a fairly simple mono-interleave DCT-based codec.
*
@ -11,7 +11,7 @@
*/
/* mixfft.c */
extern void fft(int n, float* xRe, float* xIm, float* yRe, float* yIm);
extern void relic_mixfft_fft(int n, float* xRe, float* xIm, float* yRe, float* yIm);
#define RELIC_MAX_CHANNELS 2
@ -92,7 +92,7 @@ static int apply_idct(const float* freq, float* wave, const float* dct, int dct_
}
/* main FFT */
fft(dct_quarter, in_re, in_im, out_re, out_im);
relic_mixfft_fft(dct_quarter, in_re, in_im, out_re, out_im);
/* postrotation, window and reorder? */
factor = 8.0 / sqrt(dct_size);

View file

@ -1,5 +1,5 @@
#ifndef _RELIC_DECODER_LIB_H_
#define _RELIC_DECODER_LIB_H_
#ifndef _RELIC_LIB_H_
#define _RELIC_LIB_H_
#include <stdint.h>
@ -20,4 +20,4 @@ int relic_decode_frame(relic_handle_t* handle, uint8_t* buf, int channel);
void relic_get_pcm16(relic_handle_t* handle, int16_t* outbuf, int32_t samples, int32_t skip);
#endif/*_RELIC_DECODER_LIB_H_ */
#endif

View file

@ -594,7 +594,7 @@ static void twiddleTransf(int sofarRadix, int radix, int remainRadix,
}
} /* twiddleTransf */
/*static*/ void fft(int n, float *xRe, float *xIm,
/*static void fft*/ void relic_mixfft_fft(int n, float *xRe, float *xIm,
float *yRe, float *yIm)
{
int sofarRadix[maxFactorCount],

View file

@ -1,5 +1,7 @@
#ifndef _TAC_DECODER_LIB_DATA_H_
#define _TAC_DECODER_LIB_DATA_H_
#ifndef _TAC_DATA_H_
#define _TAC_DATA_H_
#include <stdint.h>
/* VU1 register simulation, needs type conversion at times (should be optimized out by compiler). */
typedef union {

View file

@ -38,9 +38,9 @@
/**********************************************************************************/
/* DEFINITIONS */
/**********************************************************************************/
#include "tac_decoder_lib_data.h"
#include "tac_decoder_lib_ops.h"
#include "tac_decoder_lib.h"
#include "tac_data.h"
#include "tac_ops.h"
#include "tac_lib.h"
//#define TAC_MAX_FRAME_SIZE 0x300 /* typically around ~0x1d0, observed max is ~0x2e2 */
#define TAC_CODED_BANDS 27
@ -1252,9 +1252,9 @@ int tac_decode_frame(tac_handle_t* handle, const uint8_t* block) {
static inline int16_t clamp16f(float sample) {
if (sample > 32767.0)
if (sample > 32767.0f)
return 32767;
else if (sample < -32768.0)
else if (sample < -32768.0f)
return -32768;
return (int16_t)sample;
}

View file

@ -1,5 +1,5 @@
#ifndef _TAC_DECODER_LIB_H_
#define _TAC_DECODER_LIB_H_
#ifndef _TAC_LIB_H_
#define _TAC_LIB_H_
/* tri-Ace Codec (TAC) lib, found in PS2 games */

View file

@ -1,8 +1,6 @@
#ifndef _TAC_DECODER_LIB_OPS_H_
#define _TAC_DECODER_LIB_OPS_H_
#ifndef _TAC_OPS_H_
#define _TAC_OPS_H_
#include <math.h>
#include "tac_decoder_lib_ops.h"
/* The following ops are similar to VU1's ops, but not quite the same. For example VU1 has special op
* registers like the ACC, and updates zero/neg/etc flags per op (plus added here a few helper ops).

View file

@ -0,0 +1,613 @@
#include <stdbool.h>
#include <stdlib.h>
#include <string.h>
#include "utkdec.h"
// AKA 'UTALKSTATE'
struct utk_context_t {
/* config */
utk_type_t type;
int parsed_header;
/* state */
struct bitreader_t {
const uint8_t* ptr;
uint32_t bits_value;
int bits_count;
/* extra (OG MT/CBX just loads ptr memory externally) */
const uint8_t* end;
void* arg;
uint8_t* buffer;
size_t buffer_size;
size_t (*read_callback)(void* dst, int size, void* arg);
} br;
bool reduced_bandwidth;
int multipulse_threshold;
float fixed_gains[64];
float rc_data[12];
float synth_history[12];
float subframes[324 + 432];
/* adapt_cb indexes may read from samples, join both + ptr to avoid
* struct aligment issues (typically doesn't matter but for completeness) */
float* adapt_cb; /* subframes + 0 */
float* samples; /* subframes + 324 */
};
/* AKA 'bitmask'; (1 << count) - 1 is probably faster now but OG code uses a table */
static const uint8_t mask_table[8] = {
0x01,0x03,0x07,0x0F,0x1F,0x3F,0x7F,0xFF
};
/* AKA 'coeff_table', reflection coefficients (rounded) that correspond to hex values in exes (actual float is longer)
* note this table is mirrored: for (i = 1 .. 32) t[64 - i] = -t[i]) */
static const float utk_rc_table[64] = {
/* 6b index start */
+0.000000f, -0.996776f, -0.990327f, -0.983879f,
-0.977431f, -0.970982f, -0.964534f, -0.958085f,
-0.951637f, -0.930754f, -0.904960f, -0.879167f,
-0.853373f, -0.827579f, -0.801786f, -0.775992f,
/* 5b index start */
-0.750198f, -0.724405f, -0.698611f, -0.670635f,
-0.619048f, -0.567460f, -0.515873f, -0.464286f,
-0.412698f, -0.361111f, -0.309524f, -0.257937f,
-0.206349f, -0.154762f, -0.103175f, -0.051587f,
+0.000000f, +0.051587f, +0.103175f, +0.154762f,
+0.206349f, +0.257937f, +0.309524f, +0.361111f,
+0.412698f, +0.464286f, +0.515873f, +0.567460f,
+0.619048f, +0.670635f, +0.698611f, +0.724405f,
+0.750198f, +0.775992f, +0.801786f, +0.827579f,
+0.853373f, +0.879167f, +0.904960f, +0.930754f,
+0.951637f, +0.958085f, +0.964534f, +0.970982f,
+0.977431f, +0.983879f, +0.990327f, +0.996776f,
};
// AKA 'index_table'
static const uint8_t utk_codebooks[2][256] = {
/* normal model */
{
4, 6, 5, 9, 4, 6, 5, 13, 4, 6, 5, 10, 4, 6, 5, 17,
4, 6, 5, 9, 4, 6, 5, 14, 4, 6, 5, 10, 4, 6, 5, 21,
4, 6, 5, 9, 4, 6, 5, 13, 4, 6, 5, 10, 4, 6, 5, 18,
4, 6, 5, 9, 4, 6, 5, 14, 4, 6, 5, 10, 4, 6, 5, 25,
4, 6, 5, 9, 4, 6, 5, 13, 4, 6, 5, 10, 4, 6, 5, 17,
4, 6, 5, 9, 4, 6, 5, 14, 4, 6, 5, 10, 4, 6, 5, 22,
4, 6, 5, 9, 4, 6, 5, 13, 4, 6, 5, 10, 4, 6, 5, 18,
4, 6, 5, 9, 4, 6, 5, 14, 4, 6, 5, 10, 4, 6, 5, 0,
4, 6, 5, 9, 4, 6, 5, 13, 4, 6, 5, 10, 4, 6, 5, 17,
4, 6, 5, 9, 4, 6, 5, 14, 4, 6, 5, 10, 4, 6, 5, 21,
4, 6, 5, 9, 4, 6, 5, 13, 4, 6, 5, 10, 4, 6, 5, 18,
4, 6, 5, 9, 4, 6, 5, 14, 4, 6, 5, 10, 4, 6, 5, 26,
4, 6, 5, 9, 4, 6, 5, 13, 4, 6, 5, 10, 4, 6, 5, 17,
4, 6, 5, 9, 4, 6, 5, 14, 4, 6, 5, 10, 4, 6, 5, 22,
4, 6, 5, 9, 4, 6, 5, 13, 4, 6, 5, 10, 4, 6, 5, 18,
4, 6, 5, 9, 4, 6, 5, 14, 4, 6, 5, 10, 4, 6, 5, 2
},
/* large-pulse model */
{
4, 11, 7, 15, 4, 12, 8, 19, 4, 11, 7, 16, 4, 12, 8, 23,
4, 11, 7, 15, 4, 12, 8, 20, 4, 11, 7, 16, 4, 12, 8, 27,
4, 11, 7, 15, 4, 12, 8, 19, 4, 11, 7, 16, 4, 12, 8, 24,
4, 11, 7, 15, 4, 12, 8, 20, 4, 11, 7, 16, 4, 12, 8, 1,
4, 11, 7, 15, 4, 12, 8, 19, 4, 11, 7, 16, 4, 12, 8, 23,
4, 11, 7, 15, 4, 12, 8, 20, 4, 11, 7, 16, 4, 12, 8, 28,
4, 11, 7, 15, 4, 12, 8, 19, 4, 11, 7, 16, 4, 12, 8, 24,
4, 11, 7, 15, 4, 12, 8, 20, 4, 11, 7, 16, 4, 12, 8, 3,
4, 11, 7, 15, 4, 12, 8, 19, 4, 11, 7, 16, 4, 12, 8, 23,
4, 11, 7, 15, 4, 12, 8, 20, 4, 11, 7, 16, 4, 12, 8, 27,
4, 11, 7, 15, 4, 12, 8, 19, 4, 11, 7, 16, 4, 12, 8, 24,
4, 11, 7, 15, 4, 12, 8, 20, 4, 11, 7, 16, 4, 12, 8, 1,
4, 11, 7, 15, 4, 12, 8, 19, 4, 11, 7, 16, 4, 12, 8, 23,
4, 11, 7, 15, 4, 12, 8, 20, 4, 11, 7, 16, 4, 12, 8, 28,
4, 11, 7, 15, 4, 12, 8, 19, 4, 11, 7, 16, 4, 12, 8, 24,
4, 11, 7, 15, 4, 12, 8, 20, 4, 11, 7, 16, 4, 12, 8, 3
},
};
enum {
MDL_NORMAL = 0,
MDL_LARGEPULSE = 1
};
// AKA 'decode_table'
static const struct {
int next_model;
int code_size;
float pulse_value;
} utk_commands[29] = {
{MDL_LARGEPULSE, 8, 0.0f},
{MDL_LARGEPULSE, 7, 0.0f},
{MDL_NORMAL, 8, 0.0f},
{MDL_NORMAL, 7, 0.0f},
{MDL_NORMAL, 2, 0.0f},
{MDL_NORMAL, 2, -1.0f},
{MDL_NORMAL, 2, +1.0f},
{MDL_NORMAL, 3, -1.0f},
{MDL_NORMAL, 3, +1.0f},
{MDL_LARGEPULSE, 4, -2.0f},
{MDL_LARGEPULSE, 4, +2.0f},
{MDL_LARGEPULSE, 3, -2.0f},
{MDL_LARGEPULSE, 3, +2.0f},
{MDL_LARGEPULSE, 5, -3.0f},
{MDL_LARGEPULSE, 5, +3.0f},
{MDL_LARGEPULSE, 4, -3.0f},
{MDL_LARGEPULSE, 4, +3.0f},
{MDL_LARGEPULSE, 6, -4.0f},
{MDL_LARGEPULSE, 6, +4.0f},
{MDL_LARGEPULSE, 5, -4.0f},
{MDL_LARGEPULSE, 5, +4.0f},
{MDL_LARGEPULSE, 7, -5.0f},
{MDL_LARGEPULSE, 7, +5.0f},
{MDL_LARGEPULSE, 6, -5.0f},
{MDL_LARGEPULSE, 6, +5.0f},
{MDL_LARGEPULSE, 8, -6.0f},
{MDL_LARGEPULSE, 8, +6.0f},
{MDL_LARGEPULSE, 7, -6.0f},
{MDL_LARGEPULSE, 7, +6.0f}
};
/* In Lego Batman 2 gain[0] = 1.068 while other games (Lego Marvel, Lego SW) is 64.0f.
* The latter makes more sense and the former is audibly worse so it was probably a bug. */
static const float cbx_fixed_gains[64] = {
64.0f, 68.351997f, 72.999931f, 77.963921f,
83.265465f, 88.927513f, 94.974579f, 101.43285f,
108.33028f, 115.69673f, 123.5641f, 131.96646f,
140.94017f, 150.52409f, 160.75972f, 171.69138f,
183.36638f, 195.83528f, 209.15207f, 223.3744f,
238.56386f, 254.78619f, 272.11163f, 290.6152f,
310.37701f, 331.48264f, 354.02344f, 378.09702f,
403.80759f, 431.26648f, 460.59259f, 491.91287f,
525.36292f, 561.08759f, 599.24152f, 639.98993f,
683.50922f, 729.98779f, 779.62695f, 832.64154f,
889.26111f, 949.73083f, 1014.3125f, 1083.2858f,
1156.9491f, 1235.6216f, 1319.6438f, 1409.3795f,
1505.2173f, 1607.572f, 1716.8868f, 1833.6351f,
1958.3223f, 2091.488f, 2233.7092f, 2385.6013f,
2547.822f, 2721.0737f, 2906.1067f, 3103.7219f,
3314.7749f, 3540.1794f, 3780.9116f, 4038.0134f,
};
/* Bitreader in OG code can only read from set ptr; doesn't seem to check bounds though.
* Incidentally bitreader functions seem to be used only in MT and not in other EA stuff. */
static uint8_t read_byte(struct bitreader_t* br) {
if (br->ptr < br->end)
return *br->ptr++;
if (br->read_callback) {
size_t bytes_copied = br->read_callback(br->buffer, br->buffer_size, br->arg);
if (bytes_copied > 0 && bytes_copied <= br->buffer_size) {
br->ptr = br->buffer;
br->end = br->buffer + bytes_copied;
return *br->ptr++;
}
}
return 0;
}
static int16_t read_s16(struct bitreader_t* br) {
int x = read_byte(br);
x = (x << 8) | read_byte(br);
return x;
}
static void init_bits(struct bitreader_t* br) {
if (!br->bits_count) {
br->bits_value = read_byte(br);
br->bits_count = 8;
}
}
static uint8_t peek_bits(struct bitreader_t* br, int count) {
uint8_t mask = mask_table[count - 1];
return br->bits_value & mask;
}
/* aka 'getbits', LSB style and assumes count <= 8, which is always true since sizes are known and don't depend on the bitstream. */
static uint8_t read_bits(struct bitreader_t* br, int count) {
uint8_t mask = mask_table[count - 1];
uint8_t ret = br->bits_value & mask;
br->bits_value >>= count;
br->bits_count -= count;
if (br->bits_count < 8) {
/* read another byte */
br->bits_value |= read_byte(br) << br->bits_count;
br->bits_count += 8;
}
return ret;
}
/* AKA 'discardbits', as found in OG code (no return) */
static void consume_bits(struct bitreader_t* br, int count) {
read_bits(br, count);
}
static void parse_header(utk_context_t* ctx) {
if (ctx->type == UTK_CBX) {
/* CBX uses fixed parameters unlike EA-MT, probably encoder defaults for MT10:1
* equivalent to EA-MT with base_thre = 8, base_gain = 7, base_mult = 28 (plus rounding diffs).
* OG CBX code uses values/tables directly rather than config though */
ctx->reduced_bandwidth = true;
ctx->multipulse_threshold = 32 - 8;
ctx->fixed_gains[0] = cbx_fixed_gains[0];
for (int i = 1; i < 64; i++) {
ctx->fixed_gains[i] = cbx_fixed_gains[i];
}
}
else {
ctx->reduced_bandwidth = read_bits(&ctx->br, 1) == 1;
int base_thre = read_bits(&ctx->br, 4);
int base_gain = read_bits(&ctx->br, 4);
int base_mult = read_bits(&ctx->br, 6);
ctx->multipulse_threshold = 32 - base_thre;
ctx->fixed_gains[0] = 8.0f * (1 + base_gain);
float multiplier = 1.04f + base_mult * 0.001f;
for (int i = 1; i < 64; i++) {
ctx->fixed_gains[i] = ctx->fixed_gains[i-1] * multiplier;
}
}
}
static void decode_excitation(utk_context_t* ctx, bool use_multipulse, float* out, int stride) {
int i = 0;
if (use_multipulse) {
/* multi-pulse model: n pulses are coded explicitly; the rest are zero */
int model = 0;
while (i < 108) {
int huffman_code = peek_bits(&ctx->br, 8); /* variable-length, may consume less */
int cmd = utk_codebooks[model][huffman_code];
model = utk_commands[cmd].next_model;
consume_bits(&ctx->br, utk_commands[cmd].code_size);
if (cmd > 3) {
/* insert a pulse with magnitude <= 6.0f */
out[i] = utk_commands[cmd].pulse_value;
i += stride;
}
else if (cmd > 1) {
/* insert between 7 and 70 zeros */
int count = 7 + read_bits(&ctx->br, 6);
if (i + count * stride > 108)
count = (108 - i) / stride;
while (count > 0) {
out[i] = 0.0f;
i += stride;
count--;
}
}
else {
/* insert a pulse with magnitude >= 7.0f */
int x = 7;
while (read_bits(&ctx->br, 1)) {
x++;
}
if (!read_bits(&ctx->br, 1))
x *= -1;
out[i] = (float)x;
i += stride;
}
}
}
else {
/* RELP model: entire residual (excitation) signal is coded explicitly */
while (i < 108) {
int bits = 0;
float val = 0.0f;
/* peek + partial consume code (bitreader is LSB so this is equivalent to reading bit by bit, but OG handles it like this) */
int huffman_code = peek_bits(&ctx->br, 2); /* variable-length, may consume less */
switch (huffman_code) {
case 0: //value 00 = h.code: 0
case 2: //value 10 = h.code: 0
val = 0.0f;
bits = 1;
break;
case 1: //value 01 = h.code: 10
val = -2.0f;
bits = 2;
break;
case 3: //value 11 = h.code: 11
val = 2.0f;
bits = 2;
break;
default:
break;
}
consume_bits(&ctx->br, bits);
out[i] = val;
i += stride;
}
}
}
// AKA ref_to_lpc
static void rc_to_lpc(const float* rc_data, float* lpc) {
int j;
float tmp1[12];
float tmp2[12];
for (int i = 10; i >= 0; i--) {
tmp2[i + 1] = rc_data[i];
}
tmp2[0] = 1.0f;
for (int i = 0; i < 12; i++) {
float x = -(rc_data[11] * tmp2[11]);
for (j = 10; j >= 0; j--) {
x -= (rc_data[j] * tmp2[j]);
tmp2[j + 1] = x * rc_data[j] + tmp2[j];
}
tmp2[0] = x;
tmp1[i] = x;
for (j = 0; j < i; j++) {
x -= tmp1[i - 1 - j] * lpc[j];
}
lpc[i] = x;
}
}
// AKA 'filter'
static void lp_synthesis_filter(utk_context_t* ctx, int offset, int blocks) {
int i, j, k;
float lpc[12];
float* ptr = &ctx->samples[offset];
rc_to_lpc(ctx->rc_data, lpc);
for (i = 0; i < blocks; i++) {
/* OG: unrolled x12*12 */
for (j = 0; j < 12; j++) {
float x = *ptr;
for (k = 0; k < j; k++) {
x += lpc[k] * ctx->synth_history[k - j + 12];
}
for (; k < 12; k++) {
x += lpc[k] * ctx->synth_history[k - j + 0];
}
ctx->synth_history[11 - j] = x;
*ptr++ = x;
/* CBX only: samples are multiplied by 12582912.0, then coerce_int(sample[i]) on output
* to get final int16, as a pseudo-optimization; not sure if worth replicating */
}
}
}
// AKA 'interpolate', OG sometimes inlines this (sx3, not B&B/CBX) */
static void interpolate_rest(float* excitation) {
for (int i = 0; i < 108; i += 2) {
float tmp1 = (excitation[i - 5] + excitation[i + 5]) * 0.01803268f;
float tmp2 = (excitation[i - 3] + excitation[i + 3]) * 0.11459156f;
float tmp3 = (excitation[i - 1] + excitation[i + 1]) * 0.59738597f;
excitation[i] = tmp1 - tmp2 + tmp3;
}
}
// AKA 'decodemut'
static void decode_frame_main(utk_context_t* ctx) {
bool use_multipulse = false;
float excitation[5 + 108 + 5]; /* extra +5*2 for interpolation */
float rc_delta[12];
/* OG code usually calls this init/parse header after creation rather than on frame decode,
* but use a flag for now since buffer can be set/reset after init */
init_bits(&ctx->br);
if (!ctx->parsed_header) {
parse_header(ctx);
ctx->parsed_header = 1;
}
/* read the reflection coefficients (OG unrolled) */
for (int i = 0; i < 12; i++) {
int idx;
if (i == 0) {
idx = read_bits(&ctx->br, 6);
if (idx < ctx->multipulse_threshold)
use_multipulse = true;
}
else if (i < 4) {
idx = read_bits(&ctx->br, 6);
}
else {
idx = 16 + read_bits(&ctx->br, 5);
}
rc_delta[i] = (utk_rc_table[idx] - ctx->rc_data[i]) * 0.25f;
}
/* decode four subframes (AKA 'readsamples' but inline'd) */
for (int i = 0; i < 4; i++) {
int pitch_lag = read_bits(&ctx->br, 8);
int pitch_value = read_bits(&ctx->br, 4);
int gain_index = read_bits(&ctx->br, 6);
float pitch_gain = (float)pitch_value / 15.0f; /* may be compiled as: value * 0.6..67 (float or double) */
float fixed_gain = ctx->fixed_gains[gain_index];
if (!ctx->reduced_bandwidth) {
/* full bandwidth (probably MT5:1) */
decode_excitation(ctx, use_multipulse, &excitation[5 + 0], 1);
/* OG: CBX doesn't have this flag and removes the if (so not 100% same code as MT) */
}
else {
/* residual (excitation) signal is encoded at reduced bandwidth */
int align = read_bits(&ctx->br, 1);
int zero_flag = read_bits(&ctx->br, 1);
decode_excitation(ctx, use_multipulse, &excitation[5 + align], 2);
if (zero_flag) {
/* fill the remaining samples with zero (spectrum is duplicated into high frequencies) */
for (int j = 0; j < 54; j++) {
excitation[5 + (1 - align) + 2 * j] = 0.0f;
}
}
else {
/* 0'd first + last samples for interpolation */
memset(&excitation[0], 0, 5 * sizeof(float));
memset(&excitation[5 + 108], 0, 5 * sizeof(float));
/* interpolate the remaining samples (spectrum is low-pass filtered) */
interpolate_rest(&excitation[5 + (1 - align)]);
/* scale by 0.5f to give the sinc impulse response unit energy */
fixed_gain *= 0.5f;
}
}
/* OG: sometimes unrolled */
for (int j = 0; j < 108; j++) {
/* This has potential to read garbage from fixed_gains/samples (-39 ~ +648). The former
* seems avoided by the encoder but we'll clamp it just in case, while the later is common
* and seemingly used on purpose, so it's allowed via joining adapt_cb + samples bufs. */
int idx = 108 * i + 216 - pitch_lag + j;
if (idx < 0) /* OG: not done but shouldn't matter */
idx = 0;
float tmp1 = fixed_gain * excitation[5 + j];
float tmp2 = pitch_gain * ctx->adapt_cb[idx];
ctx->samples[108 * i + j] = tmp1 + tmp2;
}
}
/* OG: may be compiler-optimized to memcpy */
for (int i = 0; i < 324; i++) {
ctx->adapt_cb[i] = ctx->samples[108 + i];
}
/* OG: unrolled x4 */
for (int i = 0; i < 4; i++) {
for (int j = 0; j < 12; j++) {
ctx->rc_data[j] += rc_delta[j];
}
int blocks = i < 3 ? 1 : 33;
lp_synthesis_filter(ctx, 12 * i, blocks);
}
}
static int decode_frame_pcm(utk_context_t* ctx) {
int pcm_data_present = (read_byte(&ctx->br) == 0xEE);
int i;
decode_frame_main(ctx);
/* unread the last 8 bits and reset the bit reader
* (a bit odd but should be safe in all cases, assuming ptr has been set) */
ctx->br.ptr--;
ctx->br.bits_count = 0;
if (pcm_data_present) {
/* Overwrite n samples at a given offset in the decoded frame with raw PCM data. */
int offset = read_s16(&ctx->br);
int count = read_s16(&ctx->br);
/* sx.exe does not do any bounds checking or clamping of these two
* fields (see 004274D1 in sx.exe v3.01.01), which means a specially
* crafted MT5:1 file can crash it. We will throw an error instead. */
if (offset < 0 || offset > 432) {
return -1; /* invalid PCM offset */
}
if (count < 0 || count > 432 - offset) {
return -2; /* invalid PCM count */
}
for (i = 0; i < count; i++) {
ctx->samples[offset+i] = (float)read_s16(&ctx->br);
}
}
return 432;
}
//
int utk_decode_frame(utk_context_t* ctx) {
if (ctx->type == UTK_EA_PCM) {
return decode_frame_pcm(ctx);
}
else {
decode_frame_main(ctx);
return 432;
}
}
utk_context_t* utk_init(utk_type_t type) {
utk_context_t* ctx = calloc(1, sizeof(utk_context_t));
if (!ctx) return NULL;
//memset(ctx, 0, sizeof(*ctx));
ctx->type = type;
ctx->adapt_cb = ctx->subframes + 0;
ctx->samples = ctx->subframes + 324;
return ctx;
}
void utk_free(utk_context_t* ctx) {
free(ctx);
}
void utk_reset(utk_context_t* ctx) {
/* resets the internal state, leaving the external config/buffers
* untouched (could be reset externally or using utk_set_x) */
ctx->parsed_header = 0;
ctx->br.bits_value = 0;
ctx->br.bits_count = 0;
ctx->reduced_bandwidth = 0;
ctx->multipulse_threshold = 0;
memset(ctx->fixed_gains, 0, sizeof(ctx->fixed_gains));
memset(ctx->rc_data, 0, sizeof(ctx->rc_data));
memset(ctx->synth_history, 0, sizeof(ctx->synth_history));
memset(ctx->subframes, 0, sizeof(ctx->subframes));
}
void utk_set_callback(utk_context_t* ctx, uint8_t* buffer, size_t buffer_size, void *arg, size_t (*read_callback)(void *, int , void *)) {
ctx->br.buffer = buffer;
ctx->br.buffer_size = buffer_size;
ctx->br.arg = arg;
ctx->br.read_callback = read_callback;
/* reset the bit reader */
ctx->br.bits_count = 0;
}
void utk_set_buffer(utk_context_t* ctx, const uint8_t* buf, size_t buf_size) {
ctx->br.ptr = buf;
ctx->br.end = buf + buf_size;
/* reset the bit reader */
ctx->br.bits_count = 0;
}
float* utk_get_samples(utk_context_t* ctx) {
return ctx->samples;
}

View file

@ -0,0 +1,50 @@
#ifndef _UTKDEK_H_
#define _UTKDEK_H_
#include <stdint.h>
/* Decodes Electronic Arts' MicroTalk (a multipulse CELP/RELP speech codec) using utkencode lib,
* slightly modified for vgmstream based on decompilation of EA and CBX code.
* Original by Andrew D'Addesio: https://github.com/daddesio/utkencode (UNLICENSE/public domain)
* Info: http://wiki.niotso.org/UTK
*
* EA classifies MT as MT10:1 (smaller frames) and MT5:1 (bigger frames), but both are the same
* with different encoding parameters. Later revisions may have PCM blocks (rare). This codec was
* also reused by Traveller Tales in CBX (same devs?) with minor modifications.
* Internally it's sometimes called "UTalk" too.
*
* TODO:
* - lazy/avoid peeking/overreading when no bits left (OG code does it though, shouldn't matter)
* - same with read_callback (doesn't affect anything but cleaner)
*/
typedef enum {
UTK_EA, // standard EA-MT (MT10 or MT5)
UTK_EA_PCM, // EA-MT with PCM blocks
UTK_CBX, // Traveller's Tales Chatterbox
} utk_type_t;
/* opaque struct */
typedef struct utk_context_t utk_context_t;
/* inits UTK */
utk_context_t* utk_init(utk_type_t type);
/* frees UTK */
void utk_free(utk_context_t*);
/* reset/flush */
void utk_reset(utk_context_t* ctx);
/* loads current data (can also be used to reset buffered data if set to 0) */
void utk_set_buffer(utk_context_t* ctx, const uint8_t* buf, size_t buf_size);
/* prepares for external streaming (buf is where reads store data, arg is any external params for the callback) */
void utk_set_callback(utk_context_t* ctx, uint8_t* buf, size_t buf_size, void* arg, size_t (*read_callback)(void*, int , void*));
/* main decode; returns decoded samples on ok (always >0), < 0 on error */
int utk_decode_frame(utk_context_t* ctx);
/* get sample buf (shouldn't change between calls); sample type is PCM float (+-32768 but not clamped) */
float* utk_get_samples(utk_context_t* ctx);
#endif

View file

@ -1,7 +1,160 @@
#include "../vgmstream.h"
#if defined(VGM_USE_MP4V2) && defined(VGM_USE_FDKAAC)
static void convert_samples(INT_PCM * src, sample * dest, int32_t count) {
#ifdef VGM_USE_MP4V2
#define MP4V2_NO_STDINT_DEFS
#include <mp4v2/mp4v2.h>
#endif
#ifdef VGM_USE_FDKAAC
#include <aacdecoder_lib.h>
#endif
typedef struct {
STREAMFILE* streamfile;
uint64_t start;
uint64_t offset;
uint64_t size;
} mp4_streamfile;
struct mp4_aac_codec_data {
mp4_streamfile if_file;
MP4FileHandle h_mp4file;
MP4TrackId track_id;
unsigned long sampleId;
unsigned long numSamples;
UINT codec_init_data_size;
HANDLE_AACDECODER h_aacdecoder;
unsigned int sample_ptr;
unsigned int samples_per_frame
unsigned int samples_discard;
INT_PCM sample_buffer[( (6) * (2048)*4 )];
};
// VGM_USE_MP4V2
static void* mp4_file_open( const char* name, MP4FileMode mode ) {
char * endptr;
#ifdef _MSC_VER
unsigned __int64 ptr = _strtoui64( name, &endptr, 16 );
#else
unsigned long ptr = strtoul( name, &endptr, 16 );
#endif
return (void*) ptr;
}
static int mp4_file_seek( void* handle, int64_t pos ) {
mp4_streamfile * file = ( mp4_streamfile * ) handle;
if ( pos > file->size ) pos = file->size;
pos += file->start;
file->offset = pos;
return 0;
}
static int mp4_file_get_size( void* handle, int64_t* size ) {
mp4_streamfile * file = ( mp4_streamfile * ) handle;
*size = file->size;
return 0;
}
static int mp4_file_read( void* handle, void* buffer, int64_t size, int64_t* nin, int64_t maxChunkSize ) {
mp4_streamfile * file = ( mp4_streamfile * ) handle;
int64_t max_size = file->size - file->offset - file->start;
if ( size > max_size ) size = max_size;
if ( size > 0 )
{
*nin = read_streamfile( (uint8_t *) buffer, file->offset, size, file->streamfile );
file->offset += *nin;
}
else
{
*nin = 0;
return 1;
}
return 0;
}
static int mp4_file_write( void* handle, const void* buffer, int64_t size, int64_t* nout, int64_t maxChunkSize ) {
return 1;
}
static int mp4_file_close( void* handle ) {
return 0;
}
MP4FileProvider mp4_file_provider = { mp4_file_open, mp4_file_seek, mp4_file_read, mp4_file_write, mp4_file_close, mp4_file_get_size };
mp4_aac_codec_data* init_mp4_aac(STREAMFILE* sf) {
char filename[PATH_LIMIT];
uint32_t start = 0;
uint32_t size = get_streamfile_size(sf);
CStreamInfo* stream_info = NULL;
uint8_t* buffer = NULL;
uint32_t buffer_size;
UINT ubuffer_size, bytes_valid;
mp4_aac_codec_data* data = calloc(1, sizeof(mp4_aac_codec_data));
if (!data) goto fail;
data->if_file.streamfile = sf;
data->if_file.start = start;
data->if_file.offset = start;
data->if_file.size = size;
/* Big ol' kludge! */
sprintf( filename, "%p", &data->if_file );
data->h_mp4file = MP4ReadProvider( filename, &mp4_file_provider );
if ( !data->h_mp4file ) goto fail;
if ( MP4GetNumberOfTracks(data->h_mp4file, MP4_AUDIO_TRACK_TYPE, '\000') != 1 ) goto fail;
data->track_id = MP4FindTrackId( data->h_mp4file, 0, MP4_AUDIO_TRACK_TYPE, '\000' );
data->h_aacdecoder = aacDecoder_Open( TT_MP4_RAW, 1 );
if ( !data->h_aacdecoder ) goto fail;
MP4GetTrackESConfiguration( data->h_mp4file, data->track_id, (uint8_t**)(&buffer), (uint32_t*)(&buffer_size));
ubuffer_size = buffer_size;
if ( aacDecoder_ConfigRaw( data->h_aacdecoder, &buffer, &ubuffer_size ) ) goto fail;
free( buffer ); buffer = NULL;
data->sampleId = 1;
data->numSamples = MP4GetTrackNumberOfSamples( data->h_mp4file, data->track_id );
if (!MP4ReadSample(data->h_mp4file, data->track_id, data->sampleId, (uint8_t**)(&buffer), (uint32_t*)(&buffer_size), 0, 0, 0, 0)) goto fail;
ubuffer_size = buffer_size;
bytes_valid = buffer_size;
if ( aacDecoder_Fill( data->h_aacdecoder, &buffer, &ubuffer_size, &bytes_valid ) || bytes_valid ) goto fail;
if ( aacDecoder_DecodeFrame( data->h_aacdecoder, data->sample_buffer, ( (6) * (2048)*4 ), 0 ) ) goto fail;
free( buffer ); buffer = NULL;
data->sample_ptr = 0;
stream_info = aacDecoder_GetStreamInfo( data->h_aacdecoder );
data->samples_per_frame = stream_info->frameSize;
data->samples_discard = 0;
sf->get_name( sf, filename, sizeof(filename) );
data->if_file.streamfile = sf->open(sf, filename, 0);
if (!data->if_file.streamfile) goto fail;
return data;
fail:
free( buffer ); buffer = NULL;
free_mp4_aac(data);
}
static void convert_samples(INT_PCM * src, sample_t* dest, int32_t count) {
int32_t i;
for ( i = 0; i < count; i++ ) {
INT_PCM sample = *src++;
@ -11,7 +164,7 @@ static void convert_samples(INT_PCM * src, sample * dest, int32_t count) {
}
}
void decode_mp4_aac(mp4_aac_codec_data * data, sample * outbuf, int32_t samples_to_do, int channels) {
void decode_mp4_aac(mp4_aac_codec_data * data, sample_t* outbuf, int32_t samples_to_do, int channels) {
int samples_done = 0;
uint8_t * buffer = NULL;
@ -46,7 +199,7 @@ void decode_mp4_aac(mp4_aac_codec_data * data, sample * outbuf, int32_t samples_
while ( samples_done < samples_to_do ) {
if (data->sampleId >= data->numSamples) {
memset(outbuf, 0, (samples_to_do - samples_done) * stream_info->numChannels * sizeof(sample));
memset(outbuf, 0, (samples_to_do - samples_done) * stream_info->numChannels * sizeof(sample_t));
break;
}
if (!MP4ReadSample( data->h_mp4file, data->track_id, ++data->sampleId, (uint8_t**)(&buffer), (uint32_t*)(&buffer_size), 0, 0, 0, 0)) return;
@ -111,4 +264,38 @@ void free_mp4_aac(mp4_aac_codec_data * data) {
}
}
void mp4_aac_get_streamfile(mp4_aac_codec_data* data) {
if (!data)
return NULL;
return data->if_file.streamfile;
}
int32_t mp4_aac_get_samples(mp4_aac_codec_data* data) {
if (!data)
return 0;
return (int32_t)(data->samples_per_frame * data->numSamples);
}
int32_t mp4_aac_get_samples_per_frame(mp4_aac_codec_data* data) {
if (!data)
return 0;
return (int32_t)(data->samples_per_frame);
}
int mp4_aac_get_sample_rate(mp4_aac_codec_data* data) {
if (!data)
return 0;
CStreamInfo* stream_info = aacDecoder_GetStreamInfo( data->h_aacdecoder );
return stream_info->sample_rate;
}
int mp4_aac_get_channels(mp4_aac_codec_data* data) {
if (!data)
return 0;
CStreamInfo* stream_info = aacDecoder_GetStreamInfo( data->h_aacdecoder );
return stream_info->numChannels;
}
#endif

View file

@ -8,7 +8,7 @@ int mpeg_custom_setup_init_default(STREAMFILE* sf, off_t start_offset, mpeg_code
/* get frame info at offset */
if ( !mpeg_get_frame_info(sf, start_offset, &info))
if (!mpeg_get_frame_info(sf, start_offset, &info))
goto fail;
switch(info.layer) {
case 1: *coding_type = coding_MPEG_layer1; break;
@ -118,7 +118,7 @@ fail:
/* writes data to the buffer and moves offsets */
int mpeg_custom_parse_frame_default(VGMSTREAMCHANNEL* stream, mpeg_codec_data* data, int num_stream) {
mpeg_custom_stream *ms = data->streams[num_stream];
mpeg_custom_stream* ms = &data->streams[num_stream];
mpeg_frame_info info;
size_t current_data_size = 0;
size_t current_padding = 0;
@ -264,7 +264,7 @@ fail:
* Gets info from a MPEG frame header at offset. Normally you would use mpg123_info but somehow
* it's wrong at times (maybe because we use an ancient version) so here we do our thing.
*/
static int mpeg_get_frame_info_h(uint32_t header, mpeg_frame_info* info) {
bool mpeg_get_frame_info_h(uint32_t header, mpeg_frame_info* info) {
/* index tables */
static const int versions[4] = { /* MPEG 2.5 */ 3, /* reserved */ -1, /* MPEG 2 */ 2, /* MPEG 1 */ 1 };
static const int layers[4] = { -1,3,2,1 };
@ -330,12 +330,12 @@ static int mpeg_get_frame_info_h(uint32_t header, mpeg_frame_info* info) {
default: goto fail;
}
return 1;
return true;
fail:
return 0;
return false;
}
int mpeg_get_frame_info(STREAMFILE* sf, off_t offset, mpeg_frame_info* info) {
bool mpeg_get_frame_info(STREAMFILE* sf, off_t offset, mpeg_frame_info* info) {
uint32_t header = read_u32be(offset, sf);
return mpeg_get_frame_info_h(header, info);
}

View file

@ -130,7 +130,7 @@ fail:
/* writes data to the buffer and moves offsets, transforming AHX frames as needed */
int mpeg_custom_parse_frame_ahx(VGMSTREAMCHANNEL* stream, mpeg_codec_data* data, int num_stream) {
mpeg_custom_stream *ms = data->streams[num_stream];
mpeg_custom_stream* ms = &data->streams[num_stream];
size_t curr_size = 0;
size_t file_size = get_streamfile_size(stream->streamfile);

View file

@ -1,184 +0,0 @@
#include "mpeg_decoder.h"
#ifdef VGM_USE_MPEG
/**
* AWC music uses blocks (sfx doesn't), the fun part being each channel has different num_samples/frames
* per block, so it's unsuitable for the normal "blocked" layout and parsed here instead.
* Channel data is separate within the block (first all frames of ch0, then ch1, etc), padded, and sometimes
* the last few frames of a channel are repeated in the new block (marked with the "discard samples" field).
*/
/* block header size, algined/padded to 0x800 */
static size_t get_block_header_size(STREAMFILE *streamFile, off_t offset, mpeg_codec_data *data) {
size_t header_size = 0;
int i;
int entries = data->config.channels;
int32_t (*read_32bit)(off_t,STREAMFILE*) = data->config.big_endian ? read_32bitBE : read_32bitLE;
for (i = 0; i < entries; i++) {
header_size += 0x18;
header_size += read_32bit(offset + 0x18*i + 0x04, streamFile) * 0x04; /* entries in the table */
}
if (header_size % 0x800) /* padded */
header_size += 0x800 - (header_size % 0x800);
return header_size;
}
/* find data that repeats in the beginning of a new block at the end of last block */
static size_t get_repeated_data_size(STREAMFILE *streamFile, off_t new_offset, off_t last_offset) {
uint8_t new_frame[0x1000];/* buffer to avoid fseek back and forth */
mpeg_frame_info info;
off_t off;
int i;
/* read block first frame */
if ( !mpeg_get_frame_info(streamFile, new_offset, &info))
goto fail;
if (info.frame_size > 0x1000)
goto fail;
if (read_streamfile(new_frame,new_offset, info.frame_size,streamFile) != info.frame_size)
goto fail;
/* find the frame in last bytes of prev block */
off = last_offset - 0x4000; /* typical max is 5-10 frames of ~0x200, no way to know exact size */
while (off < last_offset) {
/* compare frame vs prev block data */
for (i = 0; i < info.frame_size; i++) {
if ((uint8_t)read_8bit(off+i,streamFile) != new_frame[i])
break;
}
/* frame fully compared? */
if (i == info.frame_size)
return last_offset - off;
else
off += i+1;
}
fail:
VGM_LOG("AWC: can't find repeat size, new=0x%08x, last=0x%08x\n", (uint32_t)new_offset, (uint32_t)last_offset);
return 0; /* keep on truckin' */
}
/* init config and validate */
int mpeg_custom_setup_init_awc(STREAMFILE *streamFile, off_t start_offset, mpeg_codec_data *data, coding_t *coding_type) {
mpeg_frame_info info;
int is_music;
/* start_offset can point to a block header that always starts with 0 (music) or normal data (sfx) */
is_music = read_32bitBE(start_offset, streamFile) == 0x00000000;
if (is_music)
start_offset += get_block_header_size(streamFile, start_offset, data);
/* get frame info at offset */
if ( !mpeg_get_frame_info(streamFile, start_offset, &info))
goto fail;
switch(info.layer) {
case 1: *coding_type = coding_MPEG_layer1; break;
case 2: *coding_type = coding_MPEG_layer2; break;
case 3: *coding_type = coding_MPEG_layer3; break;
default: goto fail;
}
data->channels_per_frame = info.channels;
data->samples_per_frame = info.frame_samples;
/* extra checks */
if (is_music) {
if (data->config.chunk_size <= 0)
goto fail; /* needs block size */
}
/* no big encoder delay added (for sfx they can start in less than ~300 samples) */
return 1;
fail:
return 0;
}
/* writes data to the buffer and moves offsets, parsing AWC blocks */
int mpeg_custom_parse_frame_awc(VGMSTREAMCHANNEL *stream, mpeg_codec_data *data, int num_stream) {
mpeg_custom_stream *ms = data->streams[num_stream];
mpeg_frame_info info;
size_t current_data_size = 0, data_offset;
size_t file_size = get_streamfile_size(stream->streamfile);
int i;
/* blocked layout used for music */
if (data->config.chunk_size) {
off_t last_offset = stream->offset; /* when block end needs to be known */
/* block ended for this channel, move to next block start */
if (ms->current_size_count > 0 && ms->current_size_count == ms->current_size_target) {
//mpg123_open_feed(ms->m); //todo reset maybe needed?
data_offset = stream->offset - stream->channel_start_offset; /* ignoring header */
data_offset -= data_offset % data->config.chunk_size; /* start of current block */
stream->offset = stream->channel_start_offset + data_offset + data->config.chunk_size;
ms->current_size_count = 0;
ms->current_size_target = 0;
}
/* just in case, shouldn't happen */
if (stream->offset >= file_size) {
goto fail;
}
/* block starts for this channel, point to mpeg data */
if (ms->current_size_count == 0) {
int32_t (*read_32bit)(off_t,STREAMFILE*) = data->config.big_endian ? read_32bitBE : read_32bitLE;
off_t channel_offset = 0;
/* block has a header with base info per channel and table per channel (see blocked_awc.c) */
ms->decode_to_discard = read_32bit(stream->offset + 0x18*num_stream + 0x08, stream->streamfile);
ms->current_size_target = read_32bit(stream->offset + 0x18*num_stream + 0x14, stream->streamfile);
for (i = 0; i < num_stream; i++) { /* num_stream serves as channel */
size_t channel_size = read_32bit(stream->offset + 0x18*i + 0x14, stream->streamfile);
if (channel_size % 0x10) /* 32b aligned */
channel_size += 0x10 - channel_size % 0x10;
channel_offset += channel_size;
}
//;VGM_ASSERT(ms->decode_to_discard > 0, "AWC: s%i discard of %x found at chunk %lx\n", num_stream, ms->decode_to_discard, stream->offset);
stream->offset += channel_offset + get_block_header_size(stream->streamfile, stream->offset, data);
/* A new block may repeat frame bytes from prev block, and decode_to_discard has the number of repeated samples.
* However in RDR PS3 (not GTA5?) the value can be off (ie. discards 1152 but the repeat decodes to ~1152*4).
* I can't figure out why, so just find and skip the repeat data manually (probably better for mpg123 too) */
if (ms->decode_to_discard) {
size_t repeat = get_repeated_data_size(stream->streamfile, stream->offset, last_offset);
if (repeat > 0)
ms->decode_to_discard = 0;
stream->offset += repeat;
ms->current_size_target -= repeat;
}
}
}
/* update frame */
if ( !mpeg_get_frame_info(stream->streamfile, stream->offset, &info) )
goto fail;
current_data_size = info.frame_size;
ms->bytes_in_buffer = read_streamfile(ms->buffer,stream->offset, current_data_size, stream->streamfile);
stream->offset += current_data_size;
ms->current_size_count += current_data_size;
return 1;
fail:
return 0;
}
#endif

Some files were not shown because too many files have changed in this diff Show more