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:
parent
b701a4d347
commit
6dd01e07c1
341 changed files with 43611 additions and 36782 deletions
File diff suppressed because it is too large
Load diff
|
@ -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
|
||||
|
|
0
Frameworks/vgmstream/vgmstream/src/api_helpers.h
Normal file
0
Frameworks/vgmstream/vgmstream/src/api_helpers.h
Normal file
66
Frameworks/vgmstream/vgmstream/src/api_streamfile.h
Normal file
66
Frameworks/vgmstream/vgmstream/src/api_streamfile.h
Normal 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
|
0
Frameworks/vgmstream/vgmstream/src/api_tags.h
Normal file
0
Frameworks/vgmstream/vgmstream/src/api_tags.h
Normal file
0
Frameworks/vgmstream/vgmstream/src/api_version.h
Normal file
0
Frameworks/vgmstream/vgmstream/src/api_version.h
Normal file
84
Frameworks/vgmstream/vgmstream/src/base/api_decode_base.c
Normal file
84
Frameworks/vgmstream/vgmstream/src/base/api_decode_base.c
Normal 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
|
138
Frameworks/vgmstream/vgmstream/src/base/api_decode_open.c
Normal file
138
Frameworks/vgmstream/vgmstream/src/base/api_decode_open.c
Normal 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
|
142
Frameworks/vgmstream/vgmstream/src/base/api_decode_play.c
Normal file
142
Frameworks/vgmstream/vgmstream/src/base/api_decode_play.c
Normal 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
|
91
Frameworks/vgmstream/vgmstream/src/base/api_helpers.c
Normal file
91
Frameworks/vgmstream/vgmstream/src/base/api_helpers.c
Normal 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
|
63
Frameworks/vgmstream/vgmstream/src/base/api_internal.h
Normal file
63
Frameworks/vgmstream/vgmstream/src/base/api_internal.h
Normal 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
|
148
Frameworks/vgmstream/vgmstream/src/base/api_libsf.c
Normal file
148
Frameworks/vgmstream/vgmstream/src/base/api_libsf.c
Normal 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
|
69
Frameworks/vgmstream/vgmstream/src/base/api_tags.c
Normal file
69
Frameworks/vgmstream/vgmstream/src/base/api_tags.c
Normal 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
|
107
Frameworks/vgmstream/vgmstream/src/base/config.c
Normal file
107
Frameworks/vgmstream/vgmstream/src/base/config.c
Normal 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);
|
||||
}
|
|
@ -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 */
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
118
Frameworks/vgmstream/vgmstream/src/base/mixer.c
Normal file
118
Frameworks/vgmstream/vgmstream/src/base/mixer.c
Normal 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);
|
||||
}
|
16
Frameworks/vgmstream/vgmstream/src/base/mixer.h
Normal file
16
Frameworks/vgmstream/vgmstream/src/base/mixer.h
Normal 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
|
142
Frameworks/vgmstream/vgmstream/src/base/mixer_ops_common.c
Normal file
142
Frameworks/vgmstream/vgmstream/src/base/mixer_ops_common.c
Normal 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;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
66
Frameworks/vgmstream/vgmstream/src/base/mixer_priv.h
Normal file
66
Frameworks/vgmstream/vgmstream/src/base/mixer_priv.h
Normal 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
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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
|
|
@ -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 */
|
||||
|
|
|
@ -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 */
|
||||
/* ****************************************** */
|
||||
|
|
|
@ -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:
|
||||
|
|
28
Frameworks/vgmstream/vgmstream/src/base/sbuf.h
Normal file
28
Frameworks/vgmstream/vgmstream/src/base/sbuf.h
Normal 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
|
|
@ -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;
|
||||
}
|
||||
|
|
94
Frameworks/vgmstream/vgmstream/src/base/streamfile_api.c
Normal file
94
Frameworks/vgmstream/vgmstream/src/base/streamfile_api.c
Normal 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
|
150
Frameworks/vgmstream/vgmstream/src/base/streamfile_buffer.c
Normal file
150
Frameworks/vgmstream/vgmstream/src/base/streamfile_buffer.c
Normal 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;
|
||||
}
|
89
Frameworks/vgmstream/vgmstream/src/base/streamfile_clamp.c
Normal file
89
Frameworks/vgmstream/vgmstream/src/base/streamfile_clamp.c
Normal 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;
|
||||
}
|
|
@ -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;
|
||||
}
|
104
Frameworks/vgmstream/vgmstream/src/base/streamfile_io.c
Normal file
104
Frameworks/vgmstream/vgmstream/src/base/streamfile_io.c
Normal 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);
|
||||
}
|
169
Frameworks/vgmstream/vgmstream/src/base/streamfile_multifile.c
Normal file
169
Frameworks/vgmstream/vgmstream/src/base/streamfile_multifile.c
Normal 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;
|
||||
}
|
394
Frameworks/vgmstream/vgmstream/src/base/streamfile_stdio.c
Normal file
394
Frameworks/vgmstream/vgmstream/src/base/streamfile_stdio.c
Normal 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
|
||||
}
|
61
Frameworks/vgmstream/vgmstream/src/base/streamfile_wrap.c
Normal file
61
Frameworks/vgmstream/vgmstream/src/base/streamfile_wrap.c
Normal 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;
|
||||
}
|
268
Frameworks/vgmstream/vgmstream/src/base/tags.c
Normal file
268
Frameworks/vgmstream/vgmstream/src/base/tags.c
Normal 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);
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
#include "coding.h"
|
||||
#include "circus_decoder_lib.h"
|
||||
#include "libs/circus_vq_lib.h"
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 */
|
||||
|
|
|
@ -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 */
|
|
@ -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 */
|
||||
|
|
|
@ -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;
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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_ */
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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 */
|
||||
|
|
|
@ -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),
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
#include "coding.h"
|
||||
#include "ice_decoder_icelib.h"
|
||||
#include "libs/icelib.h"
|
||||
|
||||
|
||||
typedef struct {
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
/* ************************************************************* */
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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>
|
||||
|
|
@ -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) {
|
|
@ -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;
|
||||
|
|
@ -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
|
|
@ -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;
|
||||
}
|
|
@ -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
|
File diff suppressed because it is too large
Load diff
|
@ -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 */
|
|
@ -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,
|
|
@ -1,5 +1,5 @@
|
|||
#ifndef _DATA_H_
|
||||
#define _DATA_H_
|
||||
#ifndef _G7221_DATA_H_
|
||||
#define _G7221_DATA_H_
|
||||
|
||||
#include <stdint.h>
|
||||
|
|
@ -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) {
|
||||
|
|
@ -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>
|
||||
|
|
@ -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
|
||||
|
|
@ -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)
|
||||
|
|
@ -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
|
357
Frameworks/vgmstream/vgmstream/src/coding/libs/nwa_lib.c
Normal file
357
Frameworks/vgmstream/vgmstream/src/coding/libs/nwa_lib.c
Normal 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;
|
||||
}
|
|
@ -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
|
|
@ -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);
|
||||
}
|
|
@ -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
|
|
@ -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);
|
|
@ -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
|
|
@ -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],
|
|
@ -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 {
|
|
@ -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;
|
||||
}
|
|
@ -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 */
|
||||
|
|
@ -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).
|
613
Frameworks/vgmstream/vgmstream/src/coding/libs/utkdec.c
Normal file
613
Frameworks/vgmstream/vgmstream/src/coding/libs/utkdec.c
Normal 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;
|
||||
}
|
50
Frameworks/vgmstream/vgmstream/src/coding/libs/utkdec.h
Normal file
50
Frameworks/vgmstream/vgmstream/src/coding/libs/utkdec.h
Normal 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
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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
Loading…
Reference in a new issue